Merge pull request #1694 from croneter/py3-tell-streams

Tell the PMS and Plex Companion about any stream changes on the Kodi side
This commit is contained in:
croneter 2021-11-05 19:13:05 +01:00 committed by GitHub
commit b795fe94e9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 128 additions and 123 deletions

View file

@ -44,12 +44,6 @@ class PlayState(object):
1: {}, 1: {},
2: {} 2: {}
} }
# The LAST playstate once playback is finished
self.old_player_states = {
0: {},
1: {},
2: {}
}
self.played_info = {} self.played_info = {}
# Currently playing PKC item, a PlaylistItem() # Currently playing PKC item, a PlaylistItem()

View file

@ -34,7 +34,6 @@ class KodiMonitor(xbmc.Monitor):
xbmc.Monitor.__init__(self) xbmc.Monitor.__init__(self)
for playerid in app.PLAYSTATE.player_states: for playerid in app.PLAYSTATE.player_states:
app.PLAYSTATE.player_states[playerid] = copy.deepcopy(app.PLAYSTATE.template) app.PLAYSTATE.player_states[playerid] = copy.deepcopy(app.PLAYSTATE.template)
app.PLAYSTATE.old_player_states[playerid] = copy.deepcopy(app.PLAYSTATE.template)
LOG.info("Kodi monitor started.") LOG.info("Kodi monitor started.")
def onScanStarted(self, library): def onScanStarted(self, library):
@ -344,6 +343,7 @@ class KodiMonitor(xbmc.Monitor):
# Mechanik for Plex skip intro feature # Mechanik for Plex skip intro feature
if utils.settings('enableSkipIntro') == 'true': if utils.settings('enableSkipIntro') == 'true':
status['intro_markers'] = item.api.intro_markers() status['intro_markers'] = item.api.intro_markers()
item.playerid = playerid
# Remember the currently playing item # Remember the currently playing item
app.PLAYSTATE.item = item app.PLAYSTATE.item = item
# Remember that this player has been active # Remember that this player has been active
@ -365,19 +365,9 @@ class KodiMonitor(xbmc.Monitor):
if not app.SYNC.direct_paths: if not app.SYNC.direct_paths:
_notify_upnext(item) _notify_upnext(item)
# We need to switch to the Plex streams ONCE upon playback start
if playerid == v.KODI_VIDEO_PLAYER_ID: if playerid == v.KODI_VIDEO_PLAYER_ID:
# The Kodi player takes forever to initialize all streams task = InitVideoStreams(item)
# Especially subtitles, apparently. No way to tell when Kodi backgroundthread.BGThreader.addTask(task)
# is done :-(
if app.APP.monitor.waitForAbort(5):
return
item.init_kodi_streams()
item.switch_to_plex_stream('video')
if utils.settings('audioStreamPick') == '0':
item.switch_to_plex_stream('audio')
if utils.settings('subtitleStreamPick') == '0':
item.switch_to_plex_stream('subtitle')
def _on_av_change(self, data): def _on_av_change(self, data):
""" """
@ -387,19 +377,8 @@ class KodiMonitor(xbmc.Monitor):
Example data as returned by Kodi: Example data as returned by Kodi:
{'item': {'id': 5, 'type': 'movie'}, {'item': {'id': 5, 'type': 'movie'},
'player': {'playerid': 1, 'speed': 1}} 'player': {'playerid': 1, 'speed': 1}}
PICKING UP CHANGES ON SUBTITLES IS CURRENTLY BROKEN ON THE KODI SIDE!
Kodi subs will never change. Also see json_rpc.py
""" """
playerid = data['player']['playerid'] pass
if not playerid == v.KODI_VIDEO_PLAYER_ID:
# We're just messing with Kodi's videoplayer
return
item = app.PLAYSTATE.item
if item is None:
# Player might've quit
return
item.on_av_change(playerid)
def _playback_cleanup(ended=False): def _playback_cleanup(ended=False):
@ -418,8 +397,6 @@ def _playback_cleanup(ended=False):
app.CONN.plex_transient_token = None app.CONN.plex_transient_token = None
for playerid in app.PLAYSTATE.active_players: for playerid in app.PLAYSTATE.active_players:
status = app.PLAYSTATE.player_states[playerid] status = app.PLAYSTATE.player_states[playerid]
# Remember the last played item later
app.PLAYSTATE.old_player_states[playerid] = copy.deepcopy(status)
# Stop transcoding # Stop transcoding
if status['playmethod'] == v.PLAYBACK_METHOD_TRANSCODE: if status['playmethod'] == v.PLAYBACK_METHOD_TRANSCODE:
LOG.debug('Tell the PMS to stop transcoding') LOG.debug('Tell the PMS to stop transcoding')
@ -685,3 +662,19 @@ def _videolibrary_onupdate(data):
PF.scrobble(db_item['plex_id'], 'watched') PF.scrobble(db_item['plex_id'], 'watched')
else: else:
PF.scrobble(db_item['plex_id'], 'unwatched') PF.scrobble(db_item['plex_id'], 'unwatched')
class InitVideoStreams(backgroundthread.Task):
"""
The Kodi player takes forever to initialize all streams Especially
subtitles, apparently. No way to tell when Kodi is done :-(
"""
def __init__(self, item):
self.item = item
super().__init__()
def run(self):
if app.APP.monitor.waitForAbort(5):
return
self.item.init_streams()

View file

@ -55,6 +55,7 @@ class PlaylistItem(object):
self.playmethod = None self.playmethod = None
self.playcount = None self.playcount = None
self.offset = None self.offset = None
self.playerid = None
# Transcoding quality, if needed # Transcoding quality, if needed
self.quality = None self.quality = None
# If Plex video consists of several parts; part number # If Plex video consists of several parts; part number
@ -72,11 +73,12 @@ class PlaylistItem(object):
self._audio_streams = None self._audio_streams = None
self._subtitle_streams = None self._subtitle_streams = None
# Which Kodi streams are active? # Which Kodi streams are active?
self.current_kodi_video_stream = None self._current_kodi_video_stream = None
self.current_kodi_audio_stream = None self._current_kodi_audio_stream = None
# False means "deactivated", None means "we do not have a Kodi # Kodi subs can be turned on/off additionally!
# equivalent for this Plex subtitle" self._current_kodi_sub_stream = None
self.current_kodi_sub_stream = None self._current_kodi_sub_stream_enabled = None
self.streams_initialized = False
@property @property
def plex_id(self): def plex_id(self):
@ -110,6 +112,48 @@ class PlaylistItem(object):
self._process_streams() self._process_streams()
return self._subtitle_streams return self._subtitle_streams
@property
def current_kodi_video_stream(self):
return self._current_kodi_video_stream
@current_kodi_video_stream.setter
def current_kodi_video_stream(self, value):
if value != self._current_kodi_video_stream:
self.on_kodi_video_stream_change(value)
self._current_kodi_video_stream = value
@property
def current_kodi_audio_stream(self):
return self._current_kodi_audio_stream
@current_kodi_audio_stream.setter
def current_kodi_audio_stream(self, value):
if value != self._current_kodi_audio_stream:
self.on_kodi_audio_stream_change(value)
self._current_kodi_audio_stream = value
@property
def current_kodi_sub_stream_enabled(self):
return self._current_kodi_sub_stream_enabled
@current_kodi_sub_stream_enabled.setter
def current_kodi_sub_stream_enabled(self, value):
if value != self._current_kodi_sub_stream_enabled:
self.on_kodi_subtitle_stream_change(self.current_kodi_sub_stream,
value)
self._current_kodi_sub_stream_enabled = value
@property
def current_kodi_sub_stream(self):
return self._current_kodi_sub_stream
@current_kodi_sub_stream.setter
def current_kodi_sub_stream(self, value):
if value != self._current_kodi_sub_stream:
self.on_kodi_subtitle_stream_change(value,
self.current_kodi_sub_stream_enabled)
self._current_kodi_sub_stream = value
@property @property
def current_plex_video_stream(self): def current_plex_video_stream(self):
return self.plex_stream_index(self.current_kodi_video_stream, 'video') return self.plex_stream_index(self.current_kodi_video_stream, 'video')
@ -170,8 +214,7 @@ class PlaylistItem(object):
elif stream_type == 'video': elif stream_type == 'video':
return self.video_streams return self.video_streams
@staticmethod def _current_index(self, stream_type):
def _current_index(stream_type):
""" """
Kodi might tell us the wrong index for any stream after playback start Kodi might tell us the wrong index for any stream after playback start
Get the correct one! Get the correct one!
@ -186,7 +229,7 @@ class PlaylistItem(object):
# Really annoying: Kodi might return wrong results directly after # Really annoying: Kodi might return wrong results directly after
# playback startup, e.g. a Kodi audio index of 1953718901 (!) # playback startup, e.g. a Kodi audio index of 1953718901 (!)
try: try:
index = function(v.KODI_VIDEO_PLAYER_ID) index = function(self.playerid)
except TypeError: except TypeError:
# No sensible reply yet # No sensible reply yet
pass pass
@ -200,31 +243,47 @@ class PlaylistItem(object):
raise RuntimeError('Kodi did not tell us the correct index for %s' raise RuntimeError('Kodi did not tell us the correct index for %s'
% stream_type) % stream_type)
def init_kodi_streams(self): def init_streams(self):
""" """
Initializes all streams after Kodi has started playing this video Initializes all streams after Kodi has started playing this video
""" """
self.current_kodi_video_stream = self._current_index('video') if not app.PLAYSTATE.item == self:
self.current_kodi_audio_stream = self._current_index('audio') # Already stopped playback or skipped to the next one
self.current_kodi_sub_stream = False if not js.get_subtitle_enabled(v.KODI_VIDEO_PLAYER_ID) \ return
else self._current_index('subtitle') self.init_kodi_streams()
self.switch_to_plex_stream('video')
if utils.settings('audioStreamPick') == '0':
self.switch_to_plex_stream('audio')
if utils.settings('subtitleStreamPick') == '0':
self.switch_to_plex_stream('subtitle')
self.streams_initialized = True
def init_kodi_streams(self):
self._current_kodi_video_stream = self._current_index('video')
self._current_kodi_audio_stream = self._current_index('audio')
self._current_kodi_sub_stream_enabled = js.get_subtitle_enabled(self.playerid)
self._current_kodi_sub_stream = self._current_index('subtitle')
def plex_stream_index(self, kodi_stream_index, stream_type): def plex_stream_index(self, kodi_stream_index, stream_type):
""" """
Pass in the kodi_stream_index [int] in order to receive the Plex stream Pass in the kodi_stream_index [int] in order to receive the Plex stream
index [int]. index [int].
stream_type: 'video', 'audio', 'subtitle' stream_type: 'video', 'audio', 'subtitle'
Returns None if unsuccessful
""" """
if stream_type == 'audio': if stream_type == 'audio':
return int(self.audio_streams[kodi_stream_index].get('id')) return int(self.audio_streams[kodi_stream_index].get('id'))
elif stream_type == 'video': elif stream_type == 'video':
return int(self.video_streams[kodi_stream_index].get('id')) return int(self.video_streams[kodi_stream_index].get('id'))
elif stream_type == 'subtitle': elif stream_type == 'subtitle':
try: if self.current_kodi_sub_stream_enabled:
return int(self.subtitle_streams[kodi_stream_index].get('id')) try:
except (IndexError, TypeError): return int(self.subtitle_streams[kodi_stream_index].get('id'))
pass except (IndexError, TypeError):
# A subtitle that is not available on the Plex side
# deactivating subs
return 0
else:
return 0
def kodi_stream_index(self, plex_stream_index, stream_type): def kodi_stream_index(self, plex_stream_index, stream_type):
""" """
@ -263,16 +322,14 @@ class PlaylistItem(object):
except (IndexError, TypeError): except (IndexError, TypeError):
LOG.debug('Kodi subtitle change detected to a sub %s that is ' LOG.debug('Kodi subtitle change detected to a sub %s that is '
'NOT available on the Plex side', kodi_stream_index) 'NOT available on the Plex side', kodi_stream_index)
self.current_kodi_sub_stream = None plex_stream_index = 0
return else:
LOG.debug('Kodi subtitle change detected: telling Plex about ' LOG.debug('Kodi subtitle change detected: telling Plex about '
'switch to index %s, Plex stream id %s', 'switch to index %s, Plex stream id %s',
kodi_stream_index, plex_stream_index) kodi_stream_index, plex_stream_index)
self.current_kodi_sub_stream = kodi_stream_index
else: else:
plex_stream_index = 0 plex_stream_index = 0
LOG.debug('Kodi subtitle has been deactivated, telling Plex') LOG.debug('Kodi subtitle has been deactivated, telling Plex')
self.current_kodi_sub_stream = False
PF.change_subtitle(plex_stream_index, self.api.part_id()) PF.change_subtitle(plex_stream_index, self.api.part_id())
def on_kodi_audio_stream_change(self, kodi_stream_index): def on_kodi_audio_stream_change(self, kodi_stream_index):
@ -284,7 +341,6 @@ class PlaylistItem(object):
LOG.debug('Changing Plex audio stream to %s, Kodi index %s', LOG.debug('Changing Plex audio stream to %s, Kodi index %s',
plex_stream_index, kodi_stream_index) plex_stream_index, kodi_stream_index)
PF.change_audio_stream(plex_stream_index, self.api.part_id()) PF.change_audio_stream(plex_stream_index, self.api.part_id())
self.current_kodi_audio_stream = kodi_stream_index
def on_kodi_video_stream_change(self, kodi_stream_index): def on_kodi_video_stream_change(self, kodi_stream_index):
""" """
@ -295,24 +351,17 @@ class PlaylistItem(object):
LOG.debug('Changing Plex video stream to %s, Kodi index %s', LOG.debug('Changing Plex video stream to %s, Kodi index %s',
plex_stream_index, kodi_stream_index) plex_stream_index, kodi_stream_index)
PF.change_video_stream(plex_stream_index, self.api.part_id()) PF.change_video_stream(plex_stream_index, self.api.part_id())
self.current_kodi_video_stream = kodi_stream_index
def switch_to_plex_streams(self): def _set_kodi_stream_if_different(self, kodi_index, typus):
self.switch_to_plex_stream('video')
self.switch_to_plex_stream('audio')
self.switch_to_plex_stream('subtitle')
@staticmethod
def _set_kodi_stream_if_different(kodi_index, typus):
if typus == 'video': if typus == 'video':
current = js.get_current_video_stream_index(v.KODI_VIDEO_PLAYER_ID) current = js.get_current_video_stream_index(self.playerid)
if current != kodi_index: if current != kodi_index:
LOG.debug('Switching video stream') LOG.debug('Switching video stream')
app.APP.player.setVideoStream(kodi_index) app.APP.player.setVideoStream(kodi_index)
else: else:
LOG.debug('Not switching video stream (no change)') LOG.debug('Not switching video stream (no change)')
elif typus == 'audio': elif typus == 'audio':
current = js.get_current_audio_stream_index(v.KODI_VIDEO_PLAYER_ID) current = js.get_current_audio_stream_index(self.playerid)
if current != kodi_index: if current != kodi_index:
LOG.debug('Switching audio stream') LOG.debug('Switching audio stream')
app.APP.player.setAudioStream(kodi_index) app.APP.player.setAudioStream(kodi_index)
@ -326,7 +375,7 @@ class PlaylistItem(object):
LOG.debug('Deactivating Kodi subtitles because the PMS ' LOG.debug('Deactivating Kodi subtitles because the PMS '
'told us to not show any subtitles') 'told us to not show any subtitles')
app.APP.player.showSubtitles(False) app.APP.player.showSubtitles(False)
self.current_kodi_sub_stream = False self._current_kodi_sub_stream_enabled = False
return return
LOG.debug('The PMS wants to display %s stream with Plex id %s and ' LOG.debug('The PMS wants to display %s stream with Plex id %s and '
'languageTag %s', typus, plex_index, language_tag) 'languageTag %s', typus, plex_index, language_tag)
@ -351,53 +400,12 @@ class PlaylistItem(object):
elif typus == 'video': elif typus == 'video':
self._set_kodi_stream_if_different(kodi_index, 'video') self._set_kodi_stream_if_different(kodi_index, 'video')
if typus == 'audio': if typus == 'audio':
self.current_kodi_audio_stream = kodi_index self._current_kodi_audio_stream = kodi_index
elif typus == 'subtitle': elif typus == 'subtitle':
self.current_kodi_sub_stream = kodi_index self._current_kodi_sub_stream_enabled = True
self._current_kodi_sub_stream = kodi_index
elif typus == 'video': elif typus == 'video':
self.current_kodi_video_stream = kodi_index self._current_kodi_video_stream = kodi_index
def on_av_change(self, playerid):
"""
Call this method if Kodi reports an "AV-Change"
(event "Player.OnAVChange")
"""
i = 0
while i < 20:
# Really annoying: Kodi might return wrong results directly after
# playback startup, e.g. a Kodi audio index of 1953718901 (!)
kodi_video_stream = js.get_current_video_stream_index(playerid)
kodi_audio_stream = js.get_current_audio_stream_index(playerid)
if kodi_video_stream < len(self.video_streams) and kodi_audio_stream < len(self.audio_streams):
# Correct result!
break
i += 1
if app.APP.monitor.waitForAbort(0.1):
# Need to quit PKC
return
else:
LOG.error('Could not get sensible Kodi indices! kodi_video_stream '
'%s, kodi_audio_stream %s',
kodi_video_stream, kodi_audio_stream)
return
kodi_video_stream = self._current_index('video')
kodi_audio_stream = self._current_index('audio')
sub_enabled = js.get_subtitle_enabled(playerid)
kodi_sub_stream = self._current_index('subtitle')
# Audio
if kodi_audio_stream != self.current_kodi_audio_stream:
self.on_kodi_audio_stream_change(kodi_audio_stream)
# Video
if kodi_video_stream != self.current_kodi_video_stream:
self.on_kodi_video_stream_change(kodi_audio_stream)
# Subtitles - CURRENTLY BROKEN ON THE KODI SIDE!
# current_kodi_sub_stream may also be zero
subs_off = (None, False)
if ((sub_enabled and self.current_kodi_sub_stream in subs_off)
or (not sub_enabled and self.current_kodi_sub_stream not in subs_off)
or (kodi_sub_stream is not None
and kodi_sub_stream != self.current_kodi_sub_stream)):
self.on_kodi_subtitle_stream_change(kodi_sub_stream, sub_enabled)
def on_plex_stream_change(self, video_stream_id=None, audio_stream_id=None, def on_plex_stream_change(self, video_stream_id=None, audio_stream_id=None,
subtitle_stream_id=None): subtitle_stream_id=None):
@ -412,7 +420,7 @@ class PlaylistItem(object):
'the video stream!', video_stream_id) 'the video stream!', video_stream_id)
return return
self._set_kodi_stream_if_different(kodi_index, 'video') self._set_kodi_stream_if_different(kodi_index, 'video')
self.current_kodi_video_stream = kodi_index self._current_kodi_video_stream = kodi_index
if audio_stream_id is not None: if audio_stream_id is not None:
try: try:
kodi_index = self.kodi_stream_index(audio_stream_id, 'audio') kodi_index = self.kodi_stream_index(audio_stream_id, 'audio')
@ -421,24 +429,24 @@ class PlaylistItem(object):
'the video stream!', audio_stream_id) 'the video stream!', audio_stream_id)
return return
self._set_kodi_stream_if_different(kodi_index, 'audio') self._set_kodi_stream_if_different(kodi_index, 'audio')
self.current_kodi_audio_stream = kodi_index self._current_kodi_audio_stream = kodi_index
if subtitle_stream_id is not None: if subtitle_stream_id is not None:
if subtitle_stream_id == 0: if subtitle_stream_id == 0:
app.APP.player.showSubtitles(False) app.APP.player.showSubtitles(False)
kodi_index = False self._current_kodi_sub_stream_enabled = False
else: else:
try: try:
kodi_index = self.kodi_stream_index(subtitle_stream_id, kodi_index = self.kodi_stream_index(subtitle_stream_id,
'subtitle') 'subtitle')
except ValueError: except ValueError:
kodi_index = None
LOG.debug('The PMS wanted to change subs, but we could not' LOG.debug('The PMS wanted to change subs, but we could not'
' match the sub with id %s to a Kodi sub', ' match the sub with id %s to a Kodi sub',
subtitle_stream_id) subtitle_stream_id)
else: else:
app.APP.player.setSubtitleStream(kodi_index) app.APP.player.setSubtitleStream(kodi_index)
app.APP.player.showSubtitles(True) app.APP.player.showSubtitles(True)
self.current_kodi_sub_stream = kodi_index self._current_kodi_sub_stream_enabled = True
self._current_kodi_sub_stream = kodi_index
def playlist_item_from_kodi(kodi_item): def playlist_item_from_kodi(kodi_item):

View file

@ -25,7 +25,7 @@ def proxy_headers():
def proxy_params(): def proxy_params():
params = { params = {
'deviceClass': 'pc', 'deviceClass': 'pc',
'protocolCapabilities': 'timeline,playback,navigation,playqueues', 'protocolCapabilities': 'timeline,playback,navigation,mirror,playqueues',
'protocolVersion': 3 'protocolVersion': 3
} }
if app.ACCOUNT.pms_token: if app.ACCOUNT.pms_token:

View file

@ -66,6 +66,9 @@ def timeline_dict(playerid, typus):
'type': typus, 'type': typus,
'state': 'stopped' 'state': 'stopped'
} }
if typus == v.PLEX_PLAYLIST_TYPE_VIDEO and not item.streams_initialized:
# Not ready yet to send updates
raise TypeError()
protocol, url, port = split_server_uri(app.CONN.server) protocol, url, port = split_server_uri(app.CONN.server)
status = 'paused' if int(info['speed']) == 0 else 'playing' status = 'paused' if int(info['speed']) == 0 else 'playing'
duration = timing.kodi_time_to_millis(info['totaltime']) duration = timing.kodi_time_to_millis(info['totaltime'])
@ -115,6 +118,13 @@ def timeline_dict(playerid, typus):
answ['token'] = playqueue.plex_transient_token answ['token'] = playqueue.plex_transient_token
# Process audio and subtitle streams # Process audio and subtitle streams
if typus == v.PLEX_PLAYLIST_TYPE_VIDEO: if typus == v.PLEX_PLAYLIST_TYPE_VIDEO:
item.current_kodi_video_stream = info['currentvideostream']['index']
item.current_kodi_audio_stream = info['currentaudiostream']['index']
item.current_kodi_sub_stream_enabled = info['subtitleenabled']
try:
item.current_kodi_sub_stream = info['currentsubtitle']['index']
except KeyError:
item.current_kodi_sub_stream = None
answ['videoStreamID'] = str(item.current_plex_video_stream) answ['videoStreamID'] = str(item.current_plex_video_stream)
answ['audioStreamID'] = str(item.current_plex_audio_stream) answ['audioStreamID'] = str(item.current_plex_audio_stream)
# Mind the zero - meaning subs are deactivated # Mind the zero - meaning subs are deactivated

View file

@ -445,9 +445,9 @@ CONTENT_FROM_PLEX_TYPE = {
# Plex profile for transcoding and direct streaming # Plex profile for transcoding and direct streaming
# Uses the empty Generic.xml at Plex Media Server/Resources/Profiles for any # Uses the empty Generic.xml at Plex Media Server/Resources/Profiles for any
# Playback decisions # Playback decisions
PLATFORM = 'Generic' PLATFORM = 'Kodi'
# Version seems to be irrelevant for the generic platform # Version seems to be irrelevant for the generic platform
PLATFORM_VERSION = '1.0.0' PLATFORM_VERSION = KODILONGVERSION
# Overrides (replace=true) any existing entries in generic.xml # Overrides (replace=true) any existing entries in generic.xml
STREAMING_HEADERS = { STREAMING_HEADERS = {
'X-Plex-Client-Profile-Extra': 'X-Plex-Client-Profile-Extra':