def conclude_playback(playqueue, pos): """ ONLY if actually being played (e.g. at 5th position of a playqueue). Decide on direct play, direct stream, transcoding path to direct paths: file itself PMS URL Web URL audiostream (e.g. let user choose) subtitle stream (e.g. let user choose) Init Kodi Playback (depending on situation): start playback return PKC listitem attached to result """ LOG.info('Concluding playback for playqueue position %s', pos) result = Playback_Successful() listitem = PKC_ListItem() item = playqueue.items[pos] if item.xml is not None: # Got a Plex element api = API(item.xml) api.setPartNumber(item.part) api.CreateListItemFromPlexItem(listitem) playutils = PlayUtils(api, item) playurl = playutils.getPlayUrl() else: playurl = item.file listitem.setPath(tryEncode(playurl)) if item.playmethod in ('DirectStream', 'DirectPlay'): listitem.setSubtitles(api.externalSubs()) else: playutils.audio_subtitle_prefs(listitem) if state.RESUME_PLAYBACK is True: state.RESUME_PLAYBACK = False if (item.offset is None and item.plex_type not in (v.PLEX_TYPE_SONG, v.PLEX_TYPE_CLIP)): with plexdb.Get_Plex_DB() as plex_db: plex_dbitem = plex_db.getItem_byId(item.plex_id) file_id = plex_dbitem[1] if plex_dbitem else None with kodidb.GetKodiDB('video') as kodi_db: item.offset = kodi_db.get_resume(file_id) LOG.info('Resuming playback at %s', item.offset) listitem.setProperty('StartOffset', str(item.offset)) listitem.setProperty('resumetime', str(item.offset)) # Reset the resumable flag state.RESUMABLE = False result.listitem = listitem pickle_me(result) LOG.info('Done concluding playback')
def process_play(self, plex_id, kodi_id=None): """ Processes Kodi playback init for ONE item """ log.info("Process_play called with plex_id %s, kodi_id %s" % (plex_id, kodi_id)) if window('plex_authenticated') != "true": log.error('Not yet authenticated for PMS, abort starting playback') # Todo: Warn user with dialog return xml = GetPlexMetadata(plex_id) try: xml[0].attrib except (IndexError, TypeError, AttributeError): log.error('Could not get a PMS xml for plex id %s' % plex_id) return api = API(xml[0]) if api.getType() == v.PLEX_TYPE_PHOTO: # Photo result = Playback_Successful() listitem = PKC_ListItem() listitem = api.CreateListItemFromPlexItem(listitem) result.listitem = listitem else: # Video and Music playqueue = self.playqueue.get_playqueue_from_type( v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[api.getType()]) with lock: result = PlaybackUtils(xml, playqueue).play( plex_id, kodi_id, xml.attrib.get('librarySectionUUID')) log.info('Done process_play, playqueues: %s' % self.playqueue.playqueues) return result
def play_resume(playqueue, xml, stack): """ If there exists a resume point, Kodi will ask the user whether to continue playback. We thus need to use setResolvedUrl "correctly". Mind that there might be several parts! """ result = Playback_Successful() listitem = PKC_ListItem() # Only get the very first item of our playqueue (i.e. the very first part) stack_item = stack.pop(0) api = API(xml[0]) item = PL.playlist_item_from_xml(playqueue, xml[0], kodi_id=stack_item['kodi_id'], kodi_type=stack_item['kodi_type']) api.setPartNumber(item.part) item.playcount = stack_item['playcount'] item.offset = stack_item['offset'] item.part = stack_item['part'] api.CreateListItemFromPlexItem(listitem) playutils = PlayUtils(api, item) playurl = playutils.getPlayUrl() listitem.setPath(tryEncode(playurl)) if item.playmethod in ('DirectStream', 'DirectPlay'): listitem.setSubtitles(api.externalSubs()) else: playutils.audio_subtitle_prefs(listitem) result.listitem = listitem # Add to our playlist playqueue.items.append(item) # This will release default.py with setResolvedUrl pickle_me(result) # Add remaining parts to the playlist, if any if stack: _process_stack(playqueue, stack)
def _ensure_resolve(): """ Will check whether RESOLVE=True and if so, fail Kodi playback startup with the path 'PKC_Dummy_Path_Which_Fails' using setResolvedUrl (and some pickling) This way we're making sure that other Python instances (calling default.py) will be destroyed. """ if RESOLVE is True: state.PKC_CAUSED_STOP = True result = Playback_Successful() result.listitem = PKC_ListItem(path='PKC_Dummy_Path_Which_Fails') pickle_me(result)
def playback_triage(plex_id=None, plex_type=None, path=None, resolve=True): """ Hit this function for addon path playback, Plex trailers, etc. Will setup playback first, then on second call complete playback. Will set Playback_Successful() with potentially a PKC_ListItem() attached (to be consumed by setResolvedURL in default.py) If trailers or additional (movie-)parts are added, default.py is released and a completely new player instance is called with a new playlist. This circumvents most issues with Kodi & playqueues Set resolve to False if you do not want setResolvedUrl to be called on the first pass - e.g. if you're calling this function from the original service.py Python instance """ LOG.info('playback_triage called with plex_id %s, plex_type %s, path %s', plex_id, plex_type, path) if not state.AUTHENTICATED: LOG.error('Not yet authenticated for PMS, abort starting playback') if resolve is True: # Release default.py pickle_me(Playback_Successful()) # "Unauthorized for PMS" dialog('notification', lang(29999), lang(30017)) return playqueue = PQ.get_playqueue_from_type( v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[plex_type]) pos = js.get_position(playqueue.playlistid) # Can return -1 (as in "no playlist") pos = pos if pos != -1 else 0 LOG.debug('playQueue position: %s for %s', pos, playqueue) # Have we already initiated playback? try: playqueue.items[pos] except IndexError: # Release our default.py before starting our own Kodi player instance if resolve is True: state.PKC_CAUSED_STOP = True result = Playback_Successful() result.listitem = PKC_ListItem(path='PKC_Dummy_Path_Which_Fails') pickle_me(result) playback_init(plex_id, plex_type, playqueue) else: # kick off playback on second pass conclude_playback(playqueue, pos)
def _ensure_resolve(abort=False): """ Will check whether RESOLVE=True and if so, fail Kodi playback startup with the path 'PKC_Dummy_Path_Which_Fails' using setResolvedUrl (and some pickling) This way we're making sure that other Python instances (calling default.py) will be destroyed. """ if RESOLVE: LOG.debug('Passing dummy path to Kodi') if not state.CONTEXT_MENU_PLAY: # Because playback won't start with context menu play state.PKC_CAUSED_STOP = True result = Playback_Successful() result.listitem = PKC_ListItem(path='PKC_Dummy_Path_Which_Fails') pickle_me(result) if abort: # Reset some playback variables state.CONTEXT_MENU_PLAY = False state.FORCE_TRANSCODE = False state.RESUME_PLAYBACK = False
def play(self, plex_id, kodi_id=None, plex_lib_UUID=None): """ plex_lib_UUID: xml attribute 'librarySectionUUID', needed for posting to the PMS """ log.info("Playbackutils called") item = self.xml[0] api = API(item) playqueue = self.playqueue xml = None result = Playback_Successful() listitem = ListItem() playutils = putils.PlayUtils(item) playurl = playutils.getPlayUrl() if not playurl: log.error('No playurl found, aborting') return if kodi_id in (None, 'plextrailer', 'plexnode'): # Item is not in Kodi database, is a trailer/clip or plex redirect # e.g. plex.tv watch later api.CreateListItemFromPlexItem(listitem) api.set_listitem_artwork(listitem) if kodi_id == 'plexnode': # Need to get yet another xml to get final url window('plex_%s.playmethod' % playurl, clear=True) xml = downloadutils.DownloadUtils().downloadUrl( '{server}%s' % item[0][0].attrib.get('key')) try: xml[0].attrib except (TypeError, AttributeError): log.error('Could not download %s' % item[0][0].attrib.get('key')) return playurl = tryEncode(xml[0].attrib.get('key')) window('plex_%s.playmethod' % playurl, value='DirectStream') playmethod = window('plex_%s.playmethod' % playurl) if playmethod == "Transcode": window('plex_%s.playmethod' % playurl, clear=True) playurl = tryEncode(playutils.audioSubsPref( listitem, tryDecode(playurl))) window('plex_%s.playmethod' % playurl, "Transcode") listitem.setPath(playurl) api.set_playback_win_props(playurl, listitem) result.listitem = listitem return result kodi_type = v.KODITYPE_FROM_PLEXTYPE[api.getType()] kodi_id = int(kodi_id) # ORGANIZE CURRENT PLAYLIST ################ contextmenu_play = window('plex_contextplay') == 'true' window('plex_contextplay', clear=True) homeScreen = getCondVisibility('Window.IsActive(home)') sizePlaylist = len(playqueue.items) if contextmenu_play: # Need to start with the items we're inserting here startPos = sizePlaylist else: # Can return -1 startPos = max(playqueue.kodi_pl.getposition(), 0) self.currentPosition = startPos propertiesPlayback = window('plex_playbackProps') == "true" introsPlaylist = False dummyPlaylist = False log.info("Playing from contextmenu: %s" % contextmenu_play) log.info("Playlist start position: %s" % startPos) log.info("Playlist plugin position: %s" % self.currentPosition) log.info("Playlist size: %s" % sizePlaylist) # RESUME POINT ################ seektime, runtime = api.getRuntime() if window('plex_customplaylist.seektime'): # Already got seektime, e.g. from playqueue & Plex companion seektime = int(window('plex_customplaylist.seektime')) # We need to ensure we add the intro and additional parts only once. # Otherwise we get a loop. if not propertiesPlayback: window('plex_playbackProps', value="true") log.info("Setting up properties in playlist.") # Where will the player need to start? # Do we need to get trailers? trailers = False if (api.getType() == v.PLEX_TYPE_MOVIE and not seektime and sizePlaylist < 2 and settings('enableCinema') == "true"): if settings('askCinema') == "true": trailers = xbmcgui.Dialog().yesno( lang(29999), "Play trailers?") else: trailers = True # Post to the PMS. REUSE THE PLAYQUEUE! xml = init_plex_playqueue(plex_id, plex_lib_UUID, mediatype=api.getType(), trailers=trailers) try: get_playlist_details_from_xml(playqueue, xml=xml) except KeyError: return if (not homeScreen and not seektime and sizePlaylist < 2 and window('plex_customplaylist') != "true" and not contextmenu_play): # Need to add a dummy file because the first item will fail log.debug("Adding dummy file to playlist.") dummyPlaylist = True add_listitem_to_Kodi_playlist( playqueue, startPos, xbmcgui.ListItem(), playurl, xml[0]) # Remove the original item from playlist remove_from_Kodi_playlist( playqueue, startPos+1) # Readd the original item to playlist - via jsonrpc so we have # full metadata add_item_to_kodi_playlist( playqueue, self.currentPosition+1, kodi_id=kodi_id, kodi_type=kodi_type, file=playurl) self.currentPosition += 1 # -- ADD TRAILERS ################ if trailers: for i, item in enumerate(xml): if i == len(xml) - 1: # Don't add the main movie itself break self.add_trailer(item) introsPlaylist = True # -- ADD MAIN ITEM ONLY FOR HOMESCREEN ############## if homeScreen and not seektime and not sizePlaylist: # Extend our current playlist with the actual item to play # only if there's no playlist first log.info("Adding main item to playlist.") add_item_to_kodi_playlist( playqueue, self.currentPosition, kodi_id, kodi_type) elif contextmenu_play: if window('useDirectPaths') == 'true': # Cannot add via JSON with full metadata because then we # Would be using the direct path log.debug("Adding contextmenu item for direct paths") if window('plex_%s.playmethod' % playurl) == "Transcode": window('plex_%s.playmethod' % playurl, clear=True) playurl = tryEncode(playutils.audioSubsPref( listitem, tryDecode(playurl))) window('plex_%s.playmethod' % playurl, value="Transcode") api.CreateListItemFromPlexItem(listitem) api.set_playback_win_props(playurl, listitem) api.set_listitem_artwork(listitem) add_listitem_to_Kodi_playlist( playqueue, self.currentPosition+1, convert_PKC_to_listitem(listitem), playurl, kodi_item={'id': kodi_id, 'type': kodi_type}) else: # Full metadata$ add_item_to_kodi_playlist( playqueue, self.currentPosition+1, kodi_id, kodi_type) self.currentPosition += 1 if seektime: window('plex_customplaylist.seektime', value=str(seektime)) # Ensure that additional parts are played after the main item self.currentPosition += 1 # -- CHECK FOR ADDITIONAL PARTS ################ if len(item[0]) > 1: self.add_part(item, api, kodi_id, kodi_type) if dummyPlaylist: # Added a dummy file to the playlist, # because the first item is going to fail automatically. log.info("Processed as a playlist. First item is skipped.") # Delete the item that's gonna fail! del playqueue.items[startPos] # Don't attach listitem return result # We just skipped adding properties. Reset flag for next time. elif propertiesPlayback: log.debug("Resetting properties playback flag.") window('plex_playbackProps', clear=True) # SETUP MAIN ITEM ########## # For transcoding only, ask for audio/subs pref if (window('plex_%s.playmethod' % playurl) == "Transcode" and not contextmenu_play): window('plex_%s.playmethod' % playurl, clear=True) playurl = tryEncode(playutils.audioSubsPref( listitem, tryDecode(playurl))) window('plex_%s.playmethod' % playurl, value="Transcode") listitem.setPath(playurl) api.set_playback_win_props(playurl, listitem) api.set_listitem_artwork(listitem) # PLAYBACK ################ if (homeScreen and seektime and window('plex_customplaylist') != "true" and not contextmenu_play): log.info("Play as a widget item") api.CreateListItemFromPlexItem(listitem) result.listitem = listitem return result elif ((introsPlaylist and window('plex_customplaylist') == "true") or (homeScreen and not sizePlaylist) or contextmenu_play): # Playlist was created just now, play it. # Contextmenu plays always need this log.info("Play playlist from starting position %s" % startPos) # Need a separate thread because Player won't return in time thread = Thread(target=Player().play, args=(playqueue.kodi_pl, None, False, startPos)) thread.setDaemon(True) thread.start() # Don't attach listitem return result else: log.info("Play as a regular item") result.listitem = listitem return result
def play(self, plex_id, kodi_id=None, plex_lib_UUID=None): """ plex_lib_UUID: xml attribute 'librarySectionUUID', needed for posting to the PMS """ log.info("Playbackutils called") item = self.xml[0] api = API(item) playqueue = self.playqueue xml = None result = Playback_Successful() listitem = ListItem() playutils = putils.PlayUtils(item) playurl = playutils.getPlayUrl() if not playurl: log.error('No playurl found, aborting') return if kodi_id in (None, 'plextrailer', 'plexnode'): # Item is not in Kodi database, is a trailer/clip or plex redirect # e.g. plex.tv watch later api.CreateListItemFromPlexItem(listitem) api.set_listitem_artwork(listitem) if kodi_id == 'plexnode': # Need to get yet another xml to get final url window('plex_%s.playmethod' % playurl, clear=True) xml = downloadutils.DownloadUtils().downloadUrl( '{server}%s' % item[0][0].attrib.get('key')) try: xml[0].attrib except (TypeError, AttributeError): log.error('Could not download %s' % item[0][0].attrib.get('key')) return playurl = tryEncode(xml[0].attrib.get('key')) window('plex_%s.playmethod' % playurl, value='DirectStream') playmethod = window('plex_%s.playmethod' % playurl) if playmethod == "Transcode": playutils.audioSubsPref(listitem, tryDecode(playurl)) listitem.setPath(playurl) api.set_playback_win_props(playurl, listitem) result.listitem = listitem return result kodi_type = v.KODITYPE_FROM_PLEXTYPE[api.getType()] kodi_id = int(kodi_id) # ORGANIZE CURRENT PLAYLIST ################ contextmenu_play = window('plex_contextplay') == 'true' window('plex_contextplay', clear=True) homeScreen = getCondVisibility('Window.IsActive(home)') sizePlaylist = len(playqueue.items) if contextmenu_play: # Need to start with the items we're inserting here startPos = sizePlaylist else: # Can return -1 startPos = max(playqueue.kodi_pl.getposition(), 0) self.currentPosition = startPos propertiesPlayback = window('plex_playbackProps') == "true" introsPlaylist = False dummyPlaylist = False log.info("Playing from contextmenu: %s" % contextmenu_play) log.info("Playlist start position: %s" % startPos) log.info("Playlist plugin position: %s" % self.currentPosition) log.info("Playlist size: %s" % sizePlaylist) # RESUME POINT ################ seektime, runtime = api.getRuntime() if window('plex_customplaylist.seektime'): # Already got seektime, e.g. from playqueue & Plex companion seektime = int(window('plex_customplaylist.seektime')) # We need to ensure we add the intro and additional parts only once. # Otherwise we get a loop. if not propertiesPlayback: window('plex_playbackProps', value="true") log.info("Setting up properties in playlist.") # Where will the player need to start? # Do we need to get trailers? trailers = False if (api.getType() == v.PLEX_TYPE_MOVIE and not seektime and sizePlaylist < 2 and settings('enableCinema') == "true"): if settings('askCinema') == "true": trailers = xbmcgui.Dialog().yesno(lang(29999), "Play trailers?") else: trailers = True # Post to the PMS. REUSE THE PLAYQUEUE! xml = init_plex_playqueue(plex_id, plex_lib_UUID, mediatype=api.getType(), trailers=trailers) try: get_playlist_details_from_xml(playqueue, xml=xml) except KeyError: return if (not homeScreen and not seektime and sizePlaylist < 2 and window('plex_customplaylist') != "true" and not contextmenu_play): # Need to add a dummy file because the first item will fail log.debug("Adding dummy file to playlist.") dummyPlaylist = True add_listitem_to_Kodi_playlist(playqueue, startPos, xbmcgui.ListItem(), playurl, xml[0]) # Remove the original item from playlist remove_from_Kodi_playlist(playqueue, startPos + 1) # Readd the original item to playlist - via jsonrpc so we have # full metadata add_item_to_kodi_playlist(playqueue, self.currentPosition + 1, kodi_id=kodi_id, kodi_type=kodi_type, file=playurl) self.currentPosition += 1 # -- ADD TRAILERS ################ if trailers: for i, item in enumerate(xml): if i == len(xml) - 1: # Don't add the main movie itself break self.add_trailer(item) introsPlaylist = True # -- ADD MAIN ITEM ONLY FOR HOMESCREEN ############## if homeScreen and not seektime and not sizePlaylist: # Extend our current playlist with the actual item to play # only if there's no playlist first log.info("Adding main item to playlist.") add_item_to_kodi_playlist(playqueue, self.currentPosition, kodi_id, kodi_type) elif contextmenu_play: if state.DIRECT_PATHS: # Cannot add via JSON with full metadata because then we # Would be using the direct path log.debug("Adding contextmenu item for direct paths") if window('plex_%s.playmethod' % playurl) == "Transcode": playutils.audioSubsPref(listitem, tryDecode(playurl)) api.CreateListItemFromPlexItem(listitem) api.set_playback_win_props(playurl, listitem) api.set_listitem_artwork(listitem) add_listitem_to_Kodi_playlist( playqueue, self.currentPosition + 1, convert_PKC_to_listitem(listitem), file=playurl, kodi_item={ 'id': kodi_id, 'type': kodi_type }) else: # Full metadata$ add_item_to_kodi_playlist(playqueue, self.currentPosition + 1, kodi_id, kodi_type) self.currentPosition += 1 if seektime: window('plex_customplaylist.seektime', value=str(seektime)) # Ensure that additional parts are played after the main item self.currentPosition += 1 # -- CHECK FOR ADDITIONAL PARTS ################ if len(item[0]) > 1: self.add_part(item, api, kodi_id, kodi_type) if dummyPlaylist: # Added a dummy file to the playlist, # because the first item is going to fail automatically. log.info("Processed as a playlist. First item is skipped.") # Delete the item that's gonna fail! del playqueue.items[startPos] # Don't attach listitem return result # We just skipped adding properties. Reset flag for next time. elif propertiesPlayback: log.debug("Resetting properties playback flag.") window('plex_playbackProps', clear=True) # SETUP MAIN ITEM ########## # For transcoding only, ask for audio/subs pref if (window('plex_%s.playmethod' % playurl) == "Transcode" and not contextmenu_play): playutils.audioSubsPref(listitem, tryDecode(playurl)) listitem.setPath(playurl) api.set_playback_win_props(playurl, listitem) api.set_listitem_artwork(listitem) # PLAYBACK ################ if (homeScreen and seektime and window('plex_customplaylist') != "true" and not contextmenu_play): log.info("Play as a widget item") api.CreateListItemFromPlexItem(listitem) result.listitem = listitem return result elif ((introsPlaylist and window('plex_customplaylist') == "true") or (homeScreen and not sizePlaylist) or contextmenu_play): # Playlist was created just now, play it. # Contextmenu plays always need this log.info("Play playlist from starting position %s" % startPos) # Need a separate thread because Player won't return in time thread = Thread(target=Player().play, args=(playqueue.kodi_pl, None, False, startPos)) thread.setDaemon(True) thread.start() # Don't attach listitem return result else: log.info("Play as a regular item") result.listitem = listitem return result
def process_indirect(key, offset, resolve=True): """ Called e.g. for Plex "Play later" - Plex items where we need to fetch an additional xml for the actual playurl. In the PMS metadata, indirect="1" is set. Will release default.py with setResolvedUrl Set resolve to False if playback should be kicked off directly, not via setResolvedUrl """ LOG.info('process_indirect called with key: %s, offset: %s', key, offset) result = Playback_Successful() if key.startswith('http') or key.startswith('{server}'): xml = DU().downloadUrl(key) elif key.startswith('/system/services'): xml = DU().downloadUrl('http://node.plexapp.com:32400%s' % key) else: xml = DU().downloadUrl('{server}%s' % key) try: xml[0].attrib except (TypeError, IndexError, AttributeError): LOG.error('Could not download PMS metadata') if resolve is True: # Release default.py pickle_me(result) return if offset != '0': offset = int(v.PLEX_TO_KODI_TIMEFACTOR * float(offset)) # Todo: implement offset api = API(xml[0]) listitem = PKC_ListItem() api.CreateListItemFromPlexItem(listitem) playqueue = PQ.get_playqueue_from_type( v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[api.getType()]) playqueue.clear() item = PL.Playlist_Item() item.xml = xml[0] item.offset = int(offset) item.plex_type = v.PLEX_TYPE_CLIP item.playmethod = 'DirectStream' # Need to get yet another xml to get the final playback url xml = DU().downloadUrl('http://node.plexapp.com:32400%s' % xml[0][0][0].attrib['key']) try: xml[0].attrib except (TypeError, IndexError, AttributeError): LOG.error('Could not download last xml for playurl') if resolve is True: # Release default.py pickle_me(result) return playurl = xml[0].attrib['key'] item.file = playurl listitem.setPath(tryEncode(playurl)) playqueue.items.append(item) if resolve is True: result.listitem = listitem pickle_me(result) else: thread = Thread(target=Player().play, args={ 'item': tryEncode(playurl), 'listitem': listitem }) thread.setDaemon(True) LOG.info('Done initializing PKC playback, starting Kodi player') thread.start()