From d5f3db078feb922ad3c9d11d66e801512d39fd37 Mon Sep 17 00:00:00 2001 From: angelblue05 Date: Sun, 11 Oct 2015 09:25:10 -0500 Subject: [PATCH] Readjust playback using playlists Instead of fighting against Kodi, work with already set playlist and adding items to it. Added extra comments to help understand. --- resources/lib/KodiMonitor.py | 10 +- resources/lib/PlaybackUtils.py | 188 ++++++++++---- resources/lib/Player.py | 438 +++++++++++++++++---------------- 3 files changed, 382 insertions(+), 254 deletions(-) diff --git a/resources/lib/KodiMonitor.py b/resources/lib/KodiMonitor.py index 07821c91..cd90151b 100644 --- a/resources/lib/KodiMonitor.py +++ b/resources/lib/KodiMonitor.py @@ -33,7 +33,7 @@ class Kodi_Monitor( xbmc.Monitor ): #this library monitor is used to detect a watchedstate change by the user through the library #as well as detect when a library item has been deleted to pass the delete to the Emby server - def onNotification (self,sender,method,data): + def onNotification (self, sender, method, data): WINDOW = self.WINDOW downloadUtils = DownloadUtils() @@ -50,6 +50,8 @@ class Kodi_Monitor( xbmc.Monitor ): if embyid != None: + playurl = xbmc.Player().getPlayingFile() + WINDOW = xbmcgui.Window( 10000 ) username = WINDOW.getProperty('currUser') userid = WINDOW.getProperty('userId%s' % username) @@ -152,6 +154,12 @@ class Kodi_Monitor( xbmc.Monitor ): url='{server}/mediabrowser/Items/' + id xbmc.log('Deleting via URL: ' + url) DownloadUtils().downloadUrl(url, type="DELETE") + + elif method == "Playlist.OnClear": + self.logMsg("Clear playback properties.", 2) + utils.window('PlaylistIntroSet', clear=True) + utils.window('PlaylistsetDummy', clear=True) + utils.window('PlaylistAdditional', clear=True) def clearProperty(self, type, id): # The sleep is necessary since VideoLibrary.OnUpdate diff --git a/resources/lib/PlaybackUtils.py b/resources/lib/PlaybackUtils.py index 20c8d593..cb9490be 100644 --- a/resources/lib/PlaybackUtils.py +++ b/resources/lib/PlaybackUtils.py @@ -43,12 +43,11 @@ class PlaybackUtils(): username = utils.window('currUser') server = utils.window('server%s' % username) - listItem = xbmcgui.ListItem() id = result['Id'] userdata = result['UserData'] - # Get the playurl - direct play, direct stream or transcoding playurl = PlayUtils().getPlayUrl(server, id, result) + listItem = xbmcgui.ListItem() if utils.window('playurlFalse') == "true": # Playurl failed - set in PlayUtils.py @@ -56,6 +55,8 @@ class PlaybackUtils(): self.logMsg("Failed to retrieve the playback path/url or dialog was cancelled.", 1) return xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, listItem) + ############### RESUME POINT ################ + # Resume point for widget only timeInfo = api.getTimeInfo(result) jumpBackSec = int(utils.settings('resumeJumpBack')) @@ -74,78 +75,169 @@ class PlaybackUtils(): if resume_result == 0: # User selected to resume, append resume point to listitem listItem.setProperty('StartOffset', str(seekTime)) - + elif resume_result > 0: # User selected to start from beginning seekTime = 0 - elif resume_result < 0: - # User cancelled the dialog + else: # User cancelled the dialog self.logMsg("User cancelled resume dialog.", 1) return xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, listItem) + ############### ORGANIZE CURRENT PLAYLIST ################ - # In order, intros, original item requested and any additional parts - playstack = [] + # In order, intros, original item requested and any additional part + playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO) + startPos = max(playlist.getposition(), 0) # Can return -1 + sizePlaylist = playlist.size() + currentPosition = startPos - # Check for intros - if utils.settings('disableCinema') == "false" and not seekTime: + self.logMsg("Playlist start position: %s" % startPos, 2) + self.logMsg("Playlist current position: %s" % currentPosition, 2) + self.logMsg("Playlist size: %s" % sizePlaylist, 2) + + # Properties to ensure we have have proper playlists with additional items. + introsPlaylist = False + introProperty = utils.window('PlaylistIntroSet') == "true" + dummyProperty = utils.window('PlaylistsetDummy') == "true" + additionalProperty = utils.window('PlaylistAdditional') == "true" + + ############### -- CHECK FOR INTROS ################ + + if utils.settings('disableCinema') == "false" and not introProperty and not seekTime: # if we have any play them when the movie/show is not being resumed url = "{server}/mediabrowser/Users/{UserId}/Items/%s/Intros?format=json&ImageTypeLimit=1&Fields=Etag" % id + intros = doUtils.downloadUrl(url) if intros['TotalRecordCount'] != 0: - for intro in intros['Items']: - introPlayurl = PlayUtils().getPlayUrl(server, intro['Id'], intro) - self.setProperties(introPlayurl, intro) - playstack.append(introPlayurl) + # The server randomly returns one custom intro + intro = intros['Items'][0] + introId = intro['Id'] + introListItem = xbmcgui.ListItem() + introPlayurl = PlayUtils().getPlayUrl(server, introId, intro) - # Add original item - playstack.append(playurl) - self.setProperties(playurl, result) + self.logMsg("Intro play: %s" % introPlayurl, 1) + + self.setProperties(introPlayurl, intro, introListItem) + self.setListItemProps(server, introId, introListItem, intro) + + introsPlaylist = True + utils.window('PlaylistIntroSet', value="true") + playlist.add(introPlayurl, introListItem, index=currentPosition) + currentPosition += 1 + elif introProperty: + # Play main item, do not play the intro since we already played it. Reset property for next time. + utils.window('PlaylistIntroSet', clear=True) + self.logMsg("Clear intro property.", 2) + + ############### -- SETUP MAIN ITEM ################ + + ##### Set listitem and properties for main item + self.logMsg("Returned playurl: %s" % playurl, 1) + listItem.setPath(playurl) + self.setProperties(playurl, result, listItem) + mainArt = API().getArtwork(result, "Primary") listItem.setThumbnailImage(mainArt) listItem.setIconImage(mainArt) - # Get additional parts/playurl - if result.get('PartCount'): + if introsPlaylist and not sizePlaylist: + # Extend our current playlist with the actual item to play only if there's no playlist first + self.logMsg("No playlist detected at the start. Creating playlist with intro and play item.", 1) + self.logMsg("Playlist current position: %s" % (currentPosition), 1) + playlist.add(playurl, listItem, index=currentPosition) + currentPosition += 1 + + ############### -- CHECK FOR ADDITIONAL PARTS ################ + + if result.get('PartCount') and not additionalProperty: + # Only add to the playlist after intros have played url = "{server}/mediabrowser/Videos/%s/AdditionalParts" % id + parts = doUtils.downloadUrl(url) for part in parts['Items']: - additionalPlayurl = PlayUtils().getPlayUrl(server, part['Id'], part) - self.setProperties(additionalPlayurl, part) - playstack.append(additionalPlayurl) + partId = part['Id'] + additionalPlayurl = PlayUtils().getPlayUrl(server, partId, part) + additionalListItem = xbmcgui.ListItem() + # Set listitem and properties for each additional parts + self.logMsg("Adding to playlist: %s position: %s" % (additionalPlayurl, currentPosition), 1) + self.setProperties(additionalPlayurl, part, additionalListItem) + self.setListItemProps(server, partId, additionalListItem, part) - if len(playstack) > 1: - # Convert list in stack:// playurl - playMethod = utils.window('%splaymethod' % playurl) - playurl = "stack://%s" % " , ".join(playstack) - # Set new window properties for combined playurl - utils.window("%splaymethod" % playurl, value=playMethod) - self.setProperties(playurl, result) + # Add item to playlist, after the main item + utils.window('PlaylistAdditional', value="true") + playlist.add(additionalPlayurl, additionalListItem, index=currentPosition+1) + currentPosition += 1 + + elif additionalProperty: + # Additional parts are already set, reset property for next time + utils.window('PlaylistAdditional', clear=True) + self.logMsg("Clear additional property", 2) - self.logMsg("Returned playurl: %s" % playurl, 1) - listItem.setPath(playurl) + ############### PLAYBACK ################ - if utils.window("%splaymethod" % playurl) != "Transcode": - # Only for direct play and direct stream - # Append external subtitles to stream - subtitleList = self.externalSubs(id, playurl, server, result['MediaSources']) - listItem.setSubtitles(subtitleList) - - - # Launch the playback - only set the listitem props if we're not using the setresolvedurl approach if setup == "service" or xbmc.getCondVisibility('Window.IsActive(home)'): # Sent via websocketclient.py or default.py but via widgets + self.logMsg("Detecting playback happening via service.py or home menu.", 1) self.setListItemProps(server, id, listItem, result) - xbmc.Player().play(playurl, listItem) + + playlistPlayer = False + + if introsPlaylist and not sizePlaylist: + # Extend our current playlist with the actual item to play only if there's no playlist first + playlistPlayer = True + + elif sizePlaylist > 0 and not dummyProperty: + # Playlist will fail on the current position. Adding dummy url + playlist.add(playurl, index=startPos) + self.logMsg("Adding dummy path as replacement for position: %s" % startPos, 2) + utils.window('PlaylistsetDummy', value="true") + playlistPlayer = True + + elif dummyProperty: + # Already failed, play the item as a single item + utils.window('PlaylistsetDummy', clear=True) + self.logMsg("Clear dummy property.", 2) + + + if playlistPlayer: + self.logMsg("Processed as a playlist.", 1) + return xbmc.Player().play(playlist) + else: + self.logMsg("Processed as a single item.", 1) + return xbmc.Player().play(playurl, listItem) + elif setup == "default": - # Sent via default.py - xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listItem) + self.logMsg("Detecting playback happening via default.py.", 1) + playlistPlayer = False - def externalSubs(self, id, playurl, server, mediaSources): + if sizePlaylist > 0 and not dummyProperty: + # Playlist will fail on the current position. Adding dummy url + playlist.add(playurl, index=startPos) + self.logMsg("Adding dummy path as replacement for position: %s" % startPos, 2) + utils.window('PlaylistsetDummy', value="true") + playlistPlayer = True + elif dummyProperty: + # Already failed, play the item as a single item + utils.window('PlaylistsetDummy', clear=True) + self.logMsg("Clear dummy property.", 2) + + + if playlistPlayer: + self.logMsg("Processed as a playlist.", 1) + return xbmc.Player().play(playlist, startpos=startPos) + else: # Sent via default.py + self.logMsg("Processed as a single item.", 1) + return xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listItem) + + + def externalSubs(self, id, playurl, mediaSources): + + username = utils.window('currUser') + server = utils.window('server%s' % username) externalsubs = [] mapping = {} @@ -176,7 +268,7 @@ class PlaybackUtils(): return externalsubs - def setProperties(self, playurl, result): + def setProperties(self, playurl, result, listItem): # Set runtimeticks, type, refresh_id and item_id id = result.get('Id') type = result.get('Type', "") @@ -190,9 +282,15 @@ class PlaybackUtils(): else: utils.window("%srefresh_id" % playurl, value=id) + if utils.window("%splaymethod" % playurl) != "Transcode": + # Only for direct play and direct stream + # Append external subtitles to stream + subtitleList = self.externalSubs(id, playurl, result['MediaSources']) + listItem.setSubtitles(subtitleList) + def setArt(self, list, name, path): - if name in ("thumb", "fanart_image", "small_poster", "tiny_poster", "medium_landscape", "medium_poster", "small_fanartimage", "medium_fanartimage", "fanart_noindicators"): + if name in {"thumb", "fanart_image", "small_poster", "tiny_poster", "medium_landscape", "medium_poster", "small_fanartimage", "medium_fanartimage", "fanart_noindicators"}: list.setProperty(name, path) else: list.setArt({name:path}) @@ -298,7 +396,7 @@ class PlaybackUtils(): def AddToPlaylist(self, itemIds): - self.logMsg("PlayBackUtils", "== ENTER: PLAYAllItems ==") + self.logMsg("== ENTER: PLAYAllItems ==") doUtils = self.doUtils playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO) diff --git a/resources/lib/Player.py b/resources/lib/Player.py index 20926bfa..2346d4d9 100644 --- a/resources/lib/Player.py +++ b/resources/lib/Player.py @@ -70,145 +70,179 @@ class Player( xbmc.Player ): xbmcplayer = self.xbmcplayer self.stopAll() - # Get current file - if stack://, get currently playing item - currentFile = xbmcplayer.getPlayingFile() - if "stack://" in currentFile: - self.stackFiles = currentFile - currentFile = self.currentStackItem(currentFile) - else: - self.stackFiles = None - self.currentFile = currentFile - self.stackElapsed = 0 + # Get current file + try: + currentFile = xbmcplayer.getPlayingFile() + xbmc.sleep(200) + except: + currentFile = "" + count = 0 + while not currentFile: - self.logMsg("ONPLAYBACK_STARTED: %s" % currentFile, 0) - - # We may need to wait for info to be set in kodi monitor - itemId = utils.window("%sitem_id" % currentFile) - tryCount = 0 - while not itemId: - - xbmc.sleep(500) - itemId = utils.window("%sitem_id" % currentFile) - if tryCount == 20: # try 20 times or about 10 seconds - break - else: tryCount += 1 - - else: - # Only proceed if an itemId was found. - runtime = utils.window("%sruntimeticks" % currentFile) - refresh_id = utils.window("%srefresh_id" % currentFile) - playMethod = utils.window("%splaymethod" % currentFile) - itemType = utils.window("%stype" % currentFile) - mapping = utils.window("%sIndexMapping" % currentFile) - seekTime = xbmc.Player().getTime() + xbmc.sleep(100) + try: + currentFile = xbmcplayer.getPlayingFile() + except: pass - self.logMsg("Mapping for subtitles index: %s" % mapping, 2) + if count == 5: # try 5 times + self.logMsg("Cancelling playback report...", 1) + break + else: count += 1 - # Get playback volume - volume_query = '{"jsonrpc": "2.0", "method": "Application.GetProperties", "params": {"properties": ["volume","muted"]}, "id": 1}' - result = xbmc.executeJSONRPC(volume_query) - result = json.loads(result) - volume = result.get('result').get('volume') - muted = result.get('result').get('muted') - - url = "{server}/mediabrowser/Sessions/Playing" - postdata = { - - 'QueueableMediaTypes': "Video", - 'CanSeek': True, - 'ItemId': itemId, - 'MediaSourceId': itemId, - 'PlayMethod': playMethod, - 'VolumeLevel': volume, - 'PositionTicks': int(seekTime * 10000000) - int(self.stackElapsed * 10000000), - 'IsMuted': muted - } - - # Get the current audio track and subtitles - if playMethod == "Transcode": - # property set in PlayUtils.py - postdata['AudioStreamIndex'] = utils.window("%sAudioStreamIndex" % currentFile) - postdata['SubtitleStreamIndex'] = utils.window("%sSubtitleStreamIndex" % currentFile) + if currentFile: + # if stack://, get currently playing item + if "stack://" in currentFile: + self.stackFiles = currentFile + currentFile = self.currentStackItem(currentFile) else: - track_query = '{"jsonrpc": "2.0", "method": "Player.GetProperties", "params": {"playerid": 1,"properties": ["currentsubtitle","currentaudiostream","subtitleenabled"]} , "id": 1}' - result = xbmc.executeJSONRPC(track_query) + self.stackFiles = None + self.currentFile = currentFile + self.stackElapsed = 0 + + self.logMsg("ONPLAYBACK_STARTED: %s" % currentFile, 0) + + + # We may need to wait for info to be set in kodi monitor + itemId = utils.window("%sitem_id" % currentFile) + tryCount = 0 + while not itemId: + + xbmc.sleep(200) + itemId = utils.window("%sitem_id" % currentFile) + if tryCount == 20: # try 20 times or about 10 seconds + self.logMsg("Could not find itemId, cancelling playback report...", 1) + break + else: tryCount += 1 + + else: + # Only proceed if an itemId was found. + runtime = utils.window("%sruntimeticks" % currentFile) + refresh_id = utils.window("%srefresh_id" % currentFile) + playMethod = utils.window("%splaymethod" % currentFile) + itemType = utils.window("%stype" % currentFile) + seekTime = xbmcplayer.getTime() + + + # Get playback volume + volume_query = '{"jsonrpc": "2.0", "method": "Application.GetProperties", "params": {"properties": ["volume","muted"]}, "id": 1}' + result = xbmc.executeJSONRPC(volume_query) result = json.loads(result) - - # Audio tracks - indexAudio = result.get('result', 0) - if indexAudio: - indexAudio = indexAudio.get('currentaudiostream', {}).get('index', 0) - # Subtitles tracks - indexSubs = result.get('result', 0) - if indexSubs: - indexSubs = indexSubs.get('currentsubtitle', {}).get('index', 0) - # If subtitles are enabled - subsEnabled = result.get('result', "") - if subsEnabled: - subsEnabled = subsEnabled.get('subtitleenabled', "") - - # Postdata for the audio and subs tracks - audioTracks = len(xbmc.Player().getAvailableAudioStreams()) - postdata['AudioStreamIndex'] = indexAudio + 1 - - if subsEnabled and len(xbmc.Player().getAvailableSubtitleStreams()) > 0: - - if mapping: - externalIndex = json.loads(mapping) - else: # Direct paths scenario - externalIndex = "" + result = result.get('result') - if externalIndex: - # If there's external subtitles added via PlaybackUtils - if externalIndex.get(str(indexSubs)): - # If the current subtitle is in the mapping - postdata['SubtitleStreamIndex'] = externalIndex[str(indexSubs)] - else: - # Internal subtitle currently selected - external = len(externalIndex) - postdata['SubtitleStreamIndex'] = indexSubs - external + audioTracks + 1 + volume = result.get('volume') + muted = result.get('muted') + + # Postdata structure to send to Emby server + url = "{server}/mediabrowser/Sessions/Playing" + postdata = { + + 'QueueableMediaTypes': "Video", + 'CanSeek': True, + 'ItemId': itemId, + 'MediaSourceId': itemId, + 'PlayMethod': playMethod, + 'VolumeLevel': volume, + 'PositionTicks': int(seekTime * 10000000) - int(self.stackElapsed * 10000000), + 'IsMuted': muted + } + + # Get the current audio track and subtitles + if playMethod == "Transcode": + # property set in PlayUtils.py + postdata['AudioStreamIndex'] = utils.window("%sAudioStreamIndex" % currentFile) + postdata['SubtitleStreamIndex'] = utils.window("%sSubtitleStreamIndex" % currentFile) + + else: + # Get the current kodi audio and subtitles and convert to Emby equivalent + track_query = '{"jsonrpc": "2.0", "method": "Player.GetProperties", "params": {"playerid": 1,"properties": ["currentsubtitle","currentaudiostream","subtitleenabled"]} , "id": 1}' + result = xbmc.executeJSONRPC(track_query) + result = json.loads(result) + result = result.get('result') + + try: # Audio tracks + indexAudio = result['currentaudiostream']['index'] + except KeyError: + indexAudio = 0 + + try: # Subtitles tracks + indexSubs = result['currentsubtitle']['index'] + except KeyError: + indexSubs = 0 + + try: # If subtitles are enabled + subsEnabled = result['subtitleenabled'] + except KeyError: + subsEnabled = "" + + # Postdata for the audio + postdata['AudioStreamIndex'] = indexAudio + 1 + + # Postdata for the subtitles + if subsEnabled and len(xbmc.Player().getAvailableSubtitleStreams()) > 0: + + # Number of audiotracks to help get Emby Index + audioTracks = len(xbmc.Player().getAvailableAudioStreams()) + mapping = utils.window("%sIndexMapping" % currentFile) + + if mapping: # Set in PlaybackUtils.py + + self.logMsg("Mapping for external subtitles index: %s" % mapping, 2) + externalIndex = json.loads(mapping) + + if externalIndex.get(str(indexSubs)): + # If the current subtitle is in the mapping + postdata['SubtitleStreamIndex'] = externalIndex[str(indexSubs)] + else: + # Internal subtitle currently selected + postdata['SubtitleStreamIndex'] = indexSubs - len(externalIndex) + audioTracks + 1 + + else: # Direct paths enabled scenario or no external subtitles set + postdata['SubtitleStreamIndex'] = indexSubs + audioTracks + 1 else: - # No external subtitles added via PlayUtils - postdata['SubtitleStreamIndex'] = indexSubs + audioTracks + 1 - else: - postdata['SubtitleStreamIndex'] = "" - - # Post playback to server - self.logMsg("Sending POST play started.", 1) - self.doUtils.downloadUrl(url, postBody=postdata, type="POST") - - # save data map for updates and position calls - data = { + postdata['SubtitleStreamIndex'] = "" - 'runtime': runtime, - 'item_id': itemId, - 'refresh_id': refresh_id, - 'currentfile': currentFile, - 'AudioStreamIndex': postdata['AudioStreamIndex'], - 'SubtitleStreamIndex': postdata['SubtitleStreamIndex'], - 'playmethod': playMethod, - 'Type': itemType, - 'currentPosition': int(seekTime) - int(self.stackElapsed) - } - - self.played_information[currentFile] = data - self.logMsg("ADDING_FILE: %s" % self.played_information, 1) - # log some playback stats - '''if(itemType != None): - if(self.playStats.get(itemType) != None): - count = self.playStats.get(itemType) + 1 - self.playStats[itemType] = count - else: - self.playStats[itemType] = 1 + # Post playback to server + self.logMsg("Sending POST play started: %s." % postdata, 2) + self.doUtils.downloadUrl(url, postBody=postdata, type="POST") + + # Ensure we do have a runtime + if not runtime: + runtime = xbmcplayer.getTotalTime() + self.logMsg("Runtime is missing, grabbing runtime from Kodi player: %s" % runtime, 1) + + # Save data map for updates and position calls + data = { - if(playMethod != None): - if(self.playStats.get(playMethod) != None): - count = self.playStats.get(playMethod) + 1 - self.playStats[playMethod] = count - else: - self.playStats[playMethod] = 1''' + 'runtime': runtime, + 'item_id': itemId, + 'refresh_id': refresh_id, + 'currentfile': currentFile, + 'AudioStreamIndex': postdata['AudioStreamIndex'], + 'SubtitleStreamIndex': postdata['SubtitleStreamIndex'], + 'playmethod': playMethod, + 'Type': itemType, + 'currentPosition': int(seekTime) - int(self.stackElapsed) + } + + self.played_information[currentFile] = data + self.logMsg("ADDING_FILE: %s" % self.played_information, 1) + + # log some playback stats + '''if(itemType != None): + if(self.playStats.get(itemType) != None): + count = self.playStats.get(itemType) + 1 + self.playStats[itemType] = count + else: + self.playStats[itemType] = 1 + + if(playMethod != None): + if(self.playStats.get(playMethod) != None): + count = self.playStats.get(playMethod) + 1 + self.playStats[playMethod] = count + else: + self.playStats[playMethod] = 1''' def reportPlayback(self): @@ -221,25 +255,26 @@ class Player( xbmc.Player ): # only report playback if emby has initiated the playback (item_id has value) if data: - # Get playback information - itemId = data.get("item_id") - audioindex = data.get("AudioStreamIndex") - subtitleindex = data.get("SubtitleStreamIndex") - playTime = data.get("currentPosition") - playMethod = data.get("playmethod") - paused = data.get("paused") - - if paused is None: - paused = False + itemId = data['item_id'] + audioindex = data['AudioStreamIndex'] + subtitleindex = data['SubtitleStreamIndex'] + playTime = data['currentPosition'] + playMethod = data['playmethod'] + paused = data.get('paused', False) + # Get playback volume volume_query = '{"jsonrpc": "2.0", "method": "Application.GetProperties", "params": {"properties": ["volume","muted"]}, "id": 1}' result = xbmc.executeJSONRPC(volume_query) result = json.loads(result) - volume = result.get('result').get('volume') - muted = result.get('result').get('muted') + result = result.get('result') + volume = result.get('volume') + muted = result.get('muted') + + + # Postdata for the websocketclient report postdata = { 'QueueableMediaTypes': "Video", @@ -247,78 +282,67 @@ class Player( xbmc.Player ): 'ItemId': itemId, 'MediaSourceId': itemId, 'PlayMethod': playMethod, + 'PositionTicks': int(playTime * 10000000) - int(self.stackElapsed * 10000000), 'IsPaused': paused, 'VolumeLevel': volume, 'IsMuted': muted } - if playTime: - postdata['PositionTicks'] = int(playTime * 10000000) - int(self.stackElapsed * 10000000) - if playMethod == "Transcode": - - data['AudioStreamIndex'] = audioindex - data['SubtitleStreamIndex'] = subtitleindex + # Track can't be changed, keep reporting the same index + postdata['AudioStreamIndex'] = audioindex + postdata['AudioStreamIndex'] = subtitleindex else: # Get current audio and subtitles track track_query = '{"jsonrpc": "2.0", "method": "Player.GetProperties", "params": {"playerid":1,"properties": ["currentsubtitle","currentaudiostream","subtitleenabled"]} , "id": 1}' result = xbmc.executeJSONRPC(track_query) result = json.loads(result) - # Audio tracks - indexAudio = result.get('result', 0) - if indexAudio: - indexAudio = indexAudio.get('currentaudiostream', {}).get('index', 0) - # Subtitles tracks - indexSubs = result.get('result', 0) - if indexSubs: - indexSubs = indexSubs.get('currentsubtitle', {}).get('index', 0) - # If subtitles are enabled - subsEnabled = result.get('result', "") - if subsEnabled: - subsEnabled = subsEnabled.get('subtitleenabled', "") + result = result.get('result') - # Convert back into an Emby index - audioTracks = len(xbmc.Player().getAvailableAudioStreams()) - indexAudio = indexAudio + 1 + try: # Audio tracks + indexAudio = result['currentaudiostream']['index'] + except KeyError: + indexAudio = 0 + + try: # Subtitles tracks + indexSubs = result['currentsubtitle']['index'] + except KeyError: + indexSubs = 0 + try: # If subtitles are enabled + subsEnabled = result['subtitleenabled'] + except KeyError: + subsEnabled = "" + + # Postdata for the audio + data['AudioStreamIndex'], postdata['AudioStreamIndex'] = [indexAudio + 1] * 2 + + # Postdata for the subtitles if subsEnabled and len(xbmc.Player().getAvailableSubtitleStreams()) > 0: - WINDOW = xbmcgui.Window(10000) - mapping = WINDOW.getProperty("%sIndexMapping" % currentFile) - if mapping: - externalIndex = json.loads(mapping) - else: # Direct paths scenario - externalIndex = "" + # Number of audiotracks to help get Emby Index + audioTracks = len(xbmc.Player().getAvailableAudioStreams()) + mapping = utils.window("%sIndexMapping" % currentFile) + + if mapping: # Set in PlaybackUtils.py + + self.logMsg("Mapping for external subtitles index: %s" % mapping, 2) + externalIndex = json.loads(mapping) - if externalIndex: - # If there's external subtitles added via PlaybackUtils if externalIndex.get(str(indexSubs)): # If the current subtitle is in the mapping - indexSubs = externalIndex[str(indexSubs)] + data['SubtitleStreamIndex'], postdata['SubtitleStreamIndex'] = [externalIndex[str(indexSubs)]] * 2 else: # Internal subtitle currently selected - external = len(externalIndex) - indexSubs = indexSubs - external + audioTracks + 1 - else: - # No external subtitles added via PlayUtils - audioTracks = len(xbmc.Player().getAvailableAudioStreams()) - indexSubs = indexSubs + audioTracks + 1 + data['SubtitleStreamIndex'], postdata['SubtitleStreamIndex'] = [indexSubs - len(externalIndex) + audioTracks + 1] * 2 + + else: # Direct paths enabled scenario or no external subtitles set + data['SubtitleStreamIndex'], postdata['SubtitleStreamIndex'] = [indexSubs + audioTracks + 1] * 2 else: - indexSubs = "" - - if audioindex == indexAudio: - postdata['AudioStreamIndex'] = audioindex - else: - postdata['AudioStreamIndex'] = indexAudio - data['AudioStreamIndex'] = indexAudio - - if subtitleindex == indexSubs: - postdata['SubtitleStreamIndex'] = subtitleindex - else: - postdata['SubtitleStreamIndex'] = indexSubs - data['SubtitleStreamIndex'] = indexSubs + data['SubtitleStreamIndex'], postdata['SubtitleStreamIndex'] = [""] * 2 + # Report progress via websocketclient postdata = json.dumps(postdata) self.logMsg("Report: %s" % postdata, 2) self.ws.sendProgressUpdate(postdata) @@ -329,9 +353,9 @@ class Player( xbmc.Player ): self.logMsg("PLAYBACK_PAUSED: %s" % currentFile, 2) if self.played_information.get(currentFile): - self.played_information[currentFile]['paused'] = "true" + self.played_information[currentFile]['paused'] = True - self.reportPlayback() + self.reportPlayback() def onPlayBackResumed( self ): @@ -339,22 +363,20 @@ class Player( xbmc.Player ): self.logMsg("PLAYBACK_RESUMED: %s" % currentFile, 2) if self.played_information.get(currentFile): - self.played_information[currentFile]['paused'] = "false" + self.played_information[currentFile]['paused'] = False - self.reportPlayback() + self.reportPlayback() def onPlayBackSeek( self, time, seekOffset ): - - self.logMsg("PLAYBACK_SEEK", 2) - xbmcplayer = self.xbmcplayer # Make position when seeking a bit more accurate - position = xbmcplayer.getTime() currentFile = self.currentFile + self.logMsg("PLAYBACK_SEEK: %s" % currentFile, 2) if self.played_information.get(currentFile): + position = self.xbmcplayer.getTime() self.played_information[currentFile]['currentPosition'] = position - self.reportPlayback() + self.reportPlayback() def onPlayBackStopped( self ): # Will be called when user stops xbmc playing a file @@ -371,7 +393,7 @@ class Player( xbmc.Player ): if not self.played_information: return - self.logMsg("Played_information: %s" % str(self.played_information), 1) + self.logMsg("Played_information: %s" % self.played_information, 1) # Process each items for item in self.played_information: @@ -379,15 +401,15 @@ class Player( xbmc.Player ): if data: self.logMsg("Item path: %s" % item, 2) - self.logMsg("Item data: %s" % str(data), 2) + self.logMsg("Item data: %s" % data, 2) - runtime = data.get('runtime') - currentPosition = data.get('currentPosition') - itemId = data.get('item_id') - refresh_id = data.get('refresh_id') - currentFile = data.get('currentfile') - type = data.get('Type') - playMethod = data.get('playmethod') + runtime = data['runtime'] + currentPosition = data['currentPosition'] + itemId = data['item_id'] + refresh_id = data['refresh_id'] + currentFile = data['currentfile'] + type = data['Type'] + playMethod = data['playmethod'] if currentPosition and runtime: percentComplete = (currentPosition * 10000000) / int(runtime) @@ -409,7 +431,7 @@ class Player( xbmc.Player ): if percentComplete >= markPlayedAt and offerDelete: # Item could be stacked, so only offer to delete the main item. if not self.stackFiles or itemId == utils.window('%sitem_id' % self.stackFiles): - return_value = xbmcgui.Dialog().yesno("Offer Delete", "Delete %s" % data.get('currentfile').split("/")[-1], "on Emby Server?") + return_value = xbmcgui.Dialog().yesno("Offer Delete", "Delete %s" % currentFile.split("/")[-1], "on Emby Server?") if return_value: # Delete Kodi entry before Emby listItem = [itemId]