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: {},
2: {}
}
# The LAST playstate once playback is finished
self.old_player_states = {
0: {},
1: {},
2: {}
}
self.played_info = {}
# Currently playing PKC item, a PlaylistItem()

View file

@ -34,7 +34,6 @@ class KodiMonitor(xbmc.Monitor):
xbmc.Monitor.__init__(self)
for playerid in app.PLAYSTATE.player_states:
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.")
def onScanStarted(self, library):
@ -344,6 +343,7 @@ class KodiMonitor(xbmc.Monitor):
# Mechanik for Plex skip intro feature
if utils.settings('enableSkipIntro') == 'true':
status['intro_markers'] = item.api.intro_markers()
item.playerid = playerid
# Remember the currently playing item
app.PLAYSTATE.item = item
# Remember that this player has been active
@ -365,19 +365,9 @@ class KodiMonitor(xbmc.Monitor):
if not app.SYNC.direct_paths:
_notify_upnext(item)
# We need to switch to the Plex streams ONCE upon playback start
if playerid == v.KODI_VIDEO_PLAYER_ID:
# The Kodi player takes forever to initialize all streams
# Especially subtitles, apparently. No way to tell when Kodi
# 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')
task = InitVideoStreams(item)
backgroundthread.BGThreader.addTask(task)
def _on_av_change(self, data):
"""
@ -387,19 +377,8 @@ class KodiMonitor(xbmc.Monitor):
Example data as returned by Kodi:
{'item': {'id': 5, 'type': 'movie'},
'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']
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)
pass
def _playback_cleanup(ended=False):
@ -418,8 +397,6 @@ def _playback_cleanup(ended=False):
app.CONN.plex_transient_token = None
for playerid in app.PLAYSTATE.active_players:
status = app.PLAYSTATE.player_states[playerid]
# Remember the last played item later
app.PLAYSTATE.old_player_states[playerid] = copy.deepcopy(status)
# Stop transcoding
if status['playmethod'] == v.PLAYBACK_METHOD_TRANSCODE:
LOG.debug('Tell the PMS to stop transcoding')
@ -685,3 +662,19 @@ def _videolibrary_onupdate(data):
PF.scrobble(db_item['plex_id'], 'watched')
else:
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.playcount = None
self.offset = None
self.playerid = None
# Transcoding quality, if needed
self.quality = None
# If Plex video consists of several parts; part number
@ -72,11 +73,12 @@ class PlaylistItem(object):
self._audio_streams = None
self._subtitle_streams = None
# Which Kodi streams are active?
self.current_kodi_video_stream = None
self.current_kodi_audio_stream = None
# False means "deactivated", None means "we do not have a Kodi
# equivalent for this Plex subtitle"
self.current_kodi_sub_stream = None
self._current_kodi_video_stream = None
self._current_kodi_audio_stream = None
# Kodi subs can be turned on/off additionally!
self._current_kodi_sub_stream = None
self._current_kodi_sub_stream_enabled = None
self.streams_initialized = False
@property
def plex_id(self):
@ -110,6 +112,48 @@ class PlaylistItem(object):
self._process_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
def current_plex_video_stream(self):
return self.plex_stream_index(self.current_kodi_video_stream, 'video')
@ -170,8 +214,7 @@ class PlaylistItem(object):
elif stream_type == 'video':
return self.video_streams
@staticmethod
def _current_index(stream_type):
def _current_index(self, stream_type):
"""
Kodi might tell us the wrong index for any stream after playback start
Get the correct one!
@ -186,7 +229,7 @@ class PlaylistItem(object):
# Really annoying: Kodi might return wrong results directly after
# playback startup, e.g. a Kodi audio index of 1953718901 (!)
try:
index = function(v.KODI_VIDEO_PLAYER_ID)
index = function(self.playerid)
except TypeError:
# No sensible reply yet
pass
@ -200,31 +243,47 @@ class PlaylistItem(object):
raise RuntimeError('Kodi did not tell us the correct index for %s'
% stream_type)
def init_kodi_streams(self):
def init_streams(self):
"""
Initializes all streams after Kodi has started playing this video
"""
self.current_kodi_video_stream = self._current_index('video')
self.current_kodi_audio_stream = self._current_index('audio')
self.current_kodi_sub_stream = False if not js.get_subtitle_enabled(v.KODI_VIDEO_PLAYER_ID) \
else self._current_index('subtitle')
if not app.PLAYSTATE.item == self:
# Already stopped playback or skipped to the next one
return
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):
"""
Pass in the kodi_stream_index [int] in order to receive the Plex stream
index [int].
stream_type: 'video', 'audio', 'subtitle'
Returns None if unsuccessful
"""
if stream_type == 'audio':
return int(self.audio_streams[kodi_stream_index].get('id'))
elif stream_type == 'video':
return int(self.video_streams[kodi_stream_index].get('id'))
elif stream_type == 'subtitle':
if self.current_kodi_sub_stream_enabled:
try:
return int(self.subtitle_streams[kodi_stream_index].get('id'))
except (IndexError, TypeError):
pass
# 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):
"""
@ -263,16 +322,14 @@ class PlaylistItem(object):
except (IndexError, TypeError):
LOG.debug('Kodi subtitle change detected to a sub %s that is '
'NOT available on the Plex side', kodi_stream_index)
self.current_kodi_sub_stream = None
return
plex_stream_index = 0
else:
LOG.debug('Kodi subtitle change detected: telling Plex about '
'switch to index %s, Plex stream id %s',
kodi_stream_index, plex_stream_index)
self.current_kodi_sub_stream = kodi_stream_index
else:
plex_stream_index = 0
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())
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',
plex_stream_index, kodi_stream_index)
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):
"""
@ -295,24 +351,17 @@ class PlaylistItem(object):
LOG.debug('Changing Plex video stream to %s, Kodi index %s',
plex_stream_index, kodi_stream_index)
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):
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):
def _set_kodi_stream_if_different(self, kodi_index, typus):
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:
LOG.debug('Switching video stream')
app.APP.player.setVideoStream(kodi_index)
else:
LOG.debug('Not switching video stream (no change)')
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:
LOG.debug('Switching audio stream')
app.APP.player.setAudioStream(kodi_index)
@ -326,7 +375,7 @@ class PlaylistItem(object):
LOG.debug('Deactivating Kodi subtitles because the PMS '
'told us to not show any subtitles')
app.APP.player.showSubtitles(False)
self.current_kodi_sub_stream = False
self._current_kodi_sub_stream_enabled = False
return
LOG.debug('The PMS wants to display %s stream with Plex id %s and '
'languageTag %s', typus, plex_index, language_tag)
@ -351,53 +400,12 @@ class PlaylistItem(object):
elif typus == 'video':
self._set_kodi_stream_if_different(kodi_index, 'video')
if typus == 'audio':
self.current_kodi_audio_stream = kodi_index
self._current_kodi_audio_stream = kodi_index
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':
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)
self._current_kodi_video_stream = kodi_index
def on_plex_stream_change(self, video_stream_id=None, audio_stream_id=None,
subtitle_stream_id=None):
@ -412,7 +420,7 @@ class PlaylistItem(object):
'the video stream!', video_stream_id)
return
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:
try:
kodi_index = self.kodi_stream_index(audio_stream_id, 'audio')
@ -421,24 +429,24 @@ class PlaylistItem(object):
'the video stream!', audio_stream_id)
return
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 == 0:
app.APP.player.showSubtitles(False)
kodi_index = False
self._current_kodi_sub_stream_enabled = False
else:
try:
kodi_index = self.kodi_stream_index(subtitle_stream_id,
'subtitle')
except ValueError:
kodi_index = None
LOG.debug('The PMS wanted to change subs, but we could not'
' match the sub with id %s to a Kodi sub',
subtitle_stream_id)
else:
app.APP.player.setSubtitleStream(kodi_index)
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):

View file

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

View file

@ -66,6 +66,9 @@ def timeline_dict(playerid, typus):
'type': typus,
'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)
status = 'paused' if int(info['speed']) == 0 else 'playing'
duration = timing.kodi_time_to_millis(info['totaltime'])
@ -115,6 +118,13 @@ def timeline_dict(playerid, typus):
answ['token'] = playqueue.plex_transient_token
# Process audio and subtitle streams
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['audioStreamID'] = str(item.current_plex_audio_stream)
# Mind the zero - meaning subs are deactivated

View file

@ -445,9 +445,9 @@ CONTENT_FROM_PLEX_TYPE = {
# Plex profile for transcoding and direct streaming
# Uses the empty Generic.xml at Plex Media Server/Resources/Profiles for any
# Playback decisions
PLATFORM = 'Generic'
PLATFORM = 'Kodi'
# 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
STREAMING_HEADERS = {
'X-Plex-Client-Profile-Extra':