# -*- coding: utf-8 -*- from __future__ import absolute_import, division, unicode_literals from logging import getLogger import xbmc from .plex_api import API from .playutils import PlayUtils from .windows.resume import resume_dialog from . import app, plex_functions as PF, utils, json_rpc, variables as v, \ widgets, playlist_func as PL, playqueue as PQ LOG = getLogger('PLEX.playstrm') class PlayStrmException(Exception): """ Any Exception associated with playstrm """ pass class PlayStrm(object): ''' Workflow: Strm that calls our webservice in database. When played, the webserivce returns a dummy file to play. Meanwhile, PlayStrm adds the real listitems for items to play to the playlist. ''' def __init__(self, params, server_id=None): LOG.debug('Starting PlayStrm with server_id %s, params: %s', server_id, params) self.xml = None self.playqueue_item = None self.api = None self.start_index = None self.index = None self.server_id = server_id self.plex_id = utils.cast(int, params['plex_id']) self.plex_type = params.get('plex_type') if params.get('synched') and params['synched'].lower() == 'false': self.synched = False else: self.synched = True self.kodi_id = utils.cast(int, params.get('kodi_id')) self.kodi_type = params.get('kodi_type') self._get_xml() self.name = self.api.title() if ((self.kodi_id is None or self.kodi_type is None) and self.xml[0].get('pkc_db_item')): self.kodi_id = self.xml[0].get('pkc_db_item')['kodi_id'] self.kodi_type = self.xml[0].get('pkc_db_item')['kodi_type'] self.transcode = params.get('transcode') if self.transcode is None: self.transcode = utils.settings('playFromTranscode.bool') if utils.settings('playFromStream.bool') else None if app.PLAYSTATE.audioplaylist: LOG.debug('Audio playlist detected') self.playqueue = PQ.get_playqueue_from_type(v.KODI_TYPE_AUDIO) else: LOG.debug('Video playlist detected') self.playqueue = PQ.get_playqueue_from_type(v.KODI_TYPE_VIDEO) self.kodi_playlist = self.playqueue.kodi_pl def __repr__(self): return ("{{" "'name': '{self.name}', " "'plex_id': {self.plex_id}, " "'plex_type': '{self.plex_type}', " "'kodi_id': {self.kodi_id}, " "'kodi_type': '{self.kodi_type}', " "'server_id': '{self.server_id}', " "'transcode': {self.transcode}, " "'start_index': {self.start_index}, " "'index': {self.index}" "}}").format(self=self).encode('utf-8') __str__ = __repr__ def playlist_add_json(self): playlistid = self.kodi_playlist.getPlayListId() LOG.debug('Adding kodi_id %s, kodi_type %s to playlist %s at index %s', self.kodi_id, self.kodi_type, playlistid, self.index) if self.index is None: json_rpc.playlist_add(playlistid, {'%sid' % self.kodi_type: self.kodi_id}) else: json_rpc.playlist_insert({'playlistid': playlistid, 'position': self.index, 'item': {'%sid' % self.kodi_type: self.kodi_id}}) def playlist_add(self, url, listitem): self.kodi_playlist.add(url=url, listitem=listitem, index=self.index) self.playqueue_item.file = url.decode('utf-8') self.playqueue.items.insert(self.index, self.playqueue_item) self.index += 1 def remove_from_playlist(self, index): LOG.debug('Removing playlist item number %s from %s', index, self) json_rpc.playlist_remove(self.kodi_playlist.getPlayListId(), index) def _get_xml(self): self.xml = PF.GetPlexMetadata(self.plex_id) if self.xml in (None, 401): raise PlayStrmException('No xml received from the PMS') if self.synched: # Adds a new key 'pkc_db_item' to self.xml[0].attrib widgets.attach_kodi_ids(self.xml) else: self.xml[0].set('pkc_db_item', None) self.api = API(self.xml[0]) def set_playqueue_item(self, xml, kodi_id, kodi_type): self.playqueue_item = PL.playlist_item_from_xml(xml, kodi_id=kodi_id, kodi_type=kodi_type) self.playqueue_item.force_transcode = self.transcode def start_playback(self, index=0): LOG.debug('Starting playback at %s', index) xbmc.Player().play(self.kodi_playlist, startpos=index, windowed=False) def play(self, start_position=None, delayed=True): ''' Create and add listitems to the Kodi playlist. ''' LOG.debug('play called with start_position %s, delayed %s', start_position, delayed) if start_position is not None: self.start_index = start_position else: self.start_index = max(self.kodi_playlist.getposition(), 0) self.index = self.start_index self._set_playlist() LOG.info('Initiating play for %s', self) if not delayed: self.start_playback(self.start_index) return self.index def play_folder(self, position=None): ''' When an entire queue is requested, If requested from Kodi, kodi_type is provided, add as Kodi would, otherwise queue playlist items using strm links to setup playback later. ''' self.start_index = position or max(self.kodi_playlist.size(), 0) self.index = self.start_index + 1 LOG.info('Play folder plex_id %s, index: %s', self.plex_id, self.index) if self.kodi_id and self.kodi_type: self.playlist_add_json() self.index += 1 else: listitem = widgets.get_listitem(self.xml[0], resume=True) url = 'http://127.0.0.1:%s/plex/play/file.strm' % v.WEBSERVICE_PORT args = { 'mode': 'play', 'plex_id': self.plex_id, 'plex_type': self.api.plex_type() } if self.kodi_id: args['kodi_id'] = self.kodi_id if self.kodi_type: args['kodi_type'] = self.kodi_type if self.server_id: args['server_id'] = self.server_id if self.transcode: args['transcode'] = True url = utils.extend_url(url, args).encode('utf-8') listitem.setPath(url) self.playlist_add(url, listitem) return self.index - 1 def _set_playlist(self): ''' Verify seektime, set intros, set main item and set additional parts. Detect the seektime for video type content. Verify the default video action set in Kodi for accurate resume behavior. ''' seektime = self._resume() trailers = False if (not seektime and self.plex_type == v.PLEX_TYPE_MOVIE and utils.settings('enableCinema') == 'true'): if utils.settings('askCinema') == "true": # "Play trailers?" trailers = utils.yesno_dialog(utils.lang(29999), utils.lang(33016)) or False else: trailers = True LOG.debug('Playing trailers: %s', trailers) xml = PF.init_plex_playqueue(self.plex_id, self.xml.get('librarySectionUUID'), mediatype=self.plex_type, trailers=trailers) if xml is None: LOG.error('Could not get playqueue for UUID %s for %s', self.xml.get('librarySectionUUID'), self) # "Play error" utils.dialog('notification', utils.lang(29999), utils.lang(30128), icon='{error}') app.PLAYSTATE.context_menu_play = False app.PLAYSTATE.resume_playback = False return PL.get_playlist_details_from_xml(self.playqueue, xml) # See that we add trailers, if they exist in the xml return self._add_intros(xml) # Add the main item if seektime: listitem = widgets.get_listitem(self.xml[0], resume=True) else: listitem = widgets.get_listitem(self.xml[0], resume=False) listitem.setSubtitles(self.api.cache_external_subs()) self.set_playqueue_item(self.xml[0], self.kodi_id, self.kodi_type) play = PlayUtils(self.api, self.playqueue_item) url = play.getPlayUrl().encode('utf-8') listitem.setPath(url) self.playlist_add(url, listitem) # Add additional file parts, if any exist self._add_additional_parts() def _resume(self): ''' Resume item if available. Returns bool or raise an PlayStrmException if resume was cancelled by user. ''' seektime = app.PLAYSTATE.resume_playback app.PLAYSTATE.resume_playback = None if app.PLAYSTATE.autoplay: seektime = False LOG.info('Skip resume for autoplay') elif seektime is None: resume = self.api.resume_point() if resume: seektime = resume_dialog(resume) LOG.info('User chose resume: %s', seektime) if seektime is None: raise PlayStrmException('User backed out of resume dialog.') app.PLAYSTATE.autoplay = True return seektime def _add_intros(self, xml): ''' if we have any play them when the movie/show is not being resumed. ''' if not len(xml) > 1: LOG.debug('No trailers returned from the PMS') return for intro in xml: api = API(intro) if not api.plex_type() == v.PLEX_TYPE_CLIP: # E.g. the main item we're looking at - skip! continue LOG.debug('Adding trailer: %s', api.title()) listitem = widgets.get_listitem(intro, resume=False) self.set_playqueue_item(intro, None, None) play = PlayUtils(api, self.playqueue_item) url = play.getPlayUrl().encode('utf-8') listitem.setPath(url) self.playlist_add(url, listitem) def _add_additional_parts(self): ''' Create listitems and add them to the stack of playlist. ''' for part, _ in enumerate(self.xml[0][0]): if part == 0: # The first part that we've already added continue self.api.set_part_number(part) LOG.debug('Adding addional part %s', part) self.set_playqueue_item(self.xml[0], self.kodi_id, self.kodi_type) self.playqueue_item.part = part listitem = widgets.get_listitem(self.xml[0], resume=False) listitem.setSubtitles(self.api.cache_external_subs()) playqueue_item = PL.playlist_item_from_xml(self.xml[0]) play = PlayUtils(self.api, playqueue_item) url = play.getPlayUrl().encode('utf-8') listitem.setPath(url) self.playlist_add(url, listitem)