class Player: """ Code responsible for playing media """ def __init__(self): """ Initialise object """ auth = Auth(kodiutils.get_tokens_path()) self._api = Api(auth.get_tokens()) self._stream = Stream(auth.get_tokens()) def play(self, category, item): """ Play the requested item. :type category: string :type item: string """ # Check if inputstreamhelper is correctly installed if not self._check_inputstream(): kodiutils.end_of_directory() return try: # Get stream information resolved_stream = self._stream.get_stream(category, item) except UnavailableException: kodiutils.ok_dialog(message=kodiutils.localize( 30712)) # The video is unavailable... kodiutils.end_of_directory() return except LimitReachedException: kodiutils.ok_dialog( message=kodiutils.localize(30713) ) # You have reached the maximum amount of concurrent streams... kodiutils.end_of_directory() return info_dict = { 'tvshowtitle': resolved_stream.program, 'title': resolved_stream.title, 'duration': resolved_stream.duration, } prop_dict = {} stream_dict = { 'duration': resolved_stream.duration, } upnext_data = None # Lookup metadata try: if category in ['movies', 'oneoffs']: info_dict.update({'mediatype': 'movie'}) # Get details movie_details = self._api.get_movie(item) if movie_details: info_dict.update({ 'plot': movie_details.description, 'year': movie_details.year, 'aired': movie_details.aired, }) elif category == 'episodes': info_dict.update({'mediatype': 'episode'}) # There is no direct API to get episode details, so we go trough the cached program details program = self._api.get_program(resolved_stream.program_id) if program: episode_details = self._api.get_episode_from_program( program, item) if episode_details: info_dict.update({ 'plot': episode_details.description, 'season': episode_details.season, 'episode': episode_details.number, }) # Lookup the next episode next_episode_details = self._api.get_next_episode_from_program( program, episode_details.season, episode_details.number) # Create the data for Up Next if next_episode_details: upnext_data = self.generate_upnext( episode_details, next_episode_details) else: _LOGGER.warning('Unknown category %s', category) except UnavailableException: # We continue without details. # This allows to play some programs that don't have metadata (yet). pass # If we have enabled the Manifest proxy, route the call trough that. if kodiutils.get_setting_bool('manifest_proxy'): try: # Python 3 from urllib.parse import urlencode except ImportError: # Python 2 from urllib import urlencode port = kodiutils.get_setting_int('manifest_proxy_port') if not port: kodiutils.notification(message=kodiutils.localize(30718), icon='error') kodiutils.end_of_directory() return url = 'http://127.0.0.1:{port}/manifest?{path}'.format( port=port, path=urlencode({'path': resolved_stream.url})) else: url = resolved_stream.url license_key = self._stream.create_license_key( resolved_stream.license_url) # Play this item kodiutils.play(url, license_key, resolved_stream.title, {}, info_dict, prop_dict, stream_dict, subtitles=resolved_stream.subtitles) # Wait for playback to start kodi_player = KodiPlayer() if not kodi_player.waitForPlayBack(url=url): # Playback didn't start return # Send Up Next data if upnext_data and kodiutils.get_setting_bool('useupnext'): _LOGGER.debug("Sending Up Next data: %s", upnext_data) self.send_upnext(upnext_data) @staticmethod def _check_inputstream(): """ Check if inputstreamhelper and inputstream.adaptive are fine. :rtype boolean """ try: from inputstreamhelper import Helper is_helper = Helper('mpd', drm='com.widevine.alpha') if not is_helper.check_inputstream(): # inputstreamhelper has already shown an error return False except ImportError: kodiutils.ok_dialog( message=kodiutils.localize(30708)) # Please reboot Kodi return False return True @staticmethod def generate_upnext(current_episode, next_episode): """ Construct the data for Up Next. :type current_episode: resources.lib.streamz.api.Episode :type next_episode: resources.lib.streamz.api.Episode """ upnext_info = dict( current_episode=dict( episodeid=current_episode.episode_id, tvshowid=current_episode.program_id, title=current_episode.name, art={ 'poster': current_episode.poster, 'landscape': current_episode.thumb, 'fanart': current_episode.fanart, }, season=current_episode.season, episode=current_episode.number, showtitle=current_episode.program_name, plot=current_episode.description, playcount=None, rating=None, firstaired=current_episode.aired[:10] if current_episode.aired else '', runtime=current_episode.duration, ), next_episode=dict( episodeid=next_episode.episode_id, tvshowid=next_episode.program_id, title=next_episode.name, art={ 'poster': next_episode.poster, 'landscape': next_episode.thumb, 'fanart': next_episode.fanart, }, season=next_episode.season, episode=next_episode.number, showtitle=next_episode.program_name, plot=next_episode.description, playcount=None, rating=None, firstaired=next_episode.aired[:10] if next_episode.aired else '', runtime=next_episode.duration, ), play_url='plugin://plugin.video.streamz/play/catalog/episodes/%s' % next_episode.episode_id, ) return upnext_info @staticmethod def send_upnext(upnext_info): """ Send a message to Up Next with information about the next Episode. :type upnext_info: object """ from base64 import b64encode from json import dumps data = [kodiutils.to_unicode(b64encode(dumps(upnext_info).encode()))] sender = '{addon_id}.SIGNAL'.format(addon_id='plugin.video.streamz') kodiutils.notify(sender=sender, message='upnext_data', data=data)
class Player: """ Code responsible for playing media """ def __init__(self): """ Initialise object """ self._auth = Auth(kodiutils.get_setting('username'), kodiutils.get_setting('password'), kodiutils.get_setting('loginprovider'), kodiutils.get_setting('profile'), kodiutils.get_tokens_path()) self._api = Api(self._auth) self._stream = Stream(self._auth) def play(self, category, item): """ Play the requested item. :type category: string :type item: string """ # Check if inputstreamhelper is correctly installed if not self._check_inputstream(): kodiutils.end_of_directory() return try: # Get stream information resolved_stream = self._stream.get_stream(category, item) except StreamGeoblockedException: kodiutils.ok_dialog(heading=kodiutils.localize(30709), message=kodiutils.localize( 30710)) # This video is geo-blocked... kodiutils.end_of_directory() return except StreamUnavailableException: kodiutils.ok_dialog(heading=kodiutils.localize(30711), message=kodiutils.localize( 30712)) # The video is unavailable... kodiutils.end_of_directory() return info_dict = { 'tvshowtitle': resolved_stream.program, 'title': resolved_stream.title, 'duration': resolved_stream.duration, } prop_dict = {} stream_dict = { 'duration': resolved_stream.duration, } upnext_data = None # Lookup metadata try: if category in ['movies', 'oneoffs']: info_dict.update({'mediatype': 'movie'}) # Get details movie_details = self._api.get_movie(item) if movie_details: info_dict.update({ 'plot': movie_details.description, 'year': movie_details.year, 'aired': movie_details.aired, }) elif category == 'episodes': info_dict.update({'mediatype': 'episode'}) # There is no direct API to get episode details, so we go trough the cached program details program = self._api.get_program(resolved_stream.program_id) if program: episode_details = self._api.get_episode_from_program( program, item) if episode_details: info_dict.update({ 'plot': episode_details.description, 'season': episode_details.season, 'episode': episode_details.number, }) # Lookup the next episode next_episode_details = self._api.get_next_episode_from_program( program, episode_details.season, episode_details.number) # Create the data for Up Next if next_episode_details: upnext_data = self.generate_upnext( episode_details, next_episode_details) elif category == 'channels': info_dict.update({'mediatype': 'episode'}) # For live channels, we need to keep on updating the manifest # This might not be needed, and could be done with the Location-tag updates if inputstream.adaptive supports it # See https://github.com/peak3d/inputstream.adaptive/pull/298#issuecomment-524206935 prop_dict.update({ 'inputstream.adaptive.manifest_update_parameter': 'full', }) else: _LOGGER.warning('Unknown category %s', category) except UnavailableException: # We continue without details. # This allows to play some programs that don't have metadata (yet). pass license_key = self._stream.create_license_key( resolved_stream.license_url) # Play this item kodiutils.play(resolved_stream.url, license_key, resolved_stream.title, {}, info_dict, prop_dict, stream_dict) # Wait for playback to start kodi_player = KodiPlayer() if not kodi_player.waitForPlayBack(url=resolved_stream.url): # Playback didn't start return # Send Up Next data if upnext_data and kodiutils.get_setting_bool('useupnext'): _LOGGER.debug("Sending Up Next data: %s", upnext_data) self.send_upnext(upnext_data) @staticmethod def _check_inputstream(): """ Check if inputstreamhelper and inputstream.adaptive are fine. :rtype boolean """ try: from inputstreamhelper import Helper is_helper = Helper('mpd', drm='com.widevine.alpha') if not is_helper.check_inputstream(): # inputstreamhelper has already shown an error return False except ImportError: kodiutils.ok_dialog( message=kodiutils.localize(30708)) # Please reboot Kodi return False return True @staticmethod def generate_upnext(current_episode, next_episode): """ Construct the data for Up Next. :type current_episode: resources.lib.streamz.api.Episode :type next_episode: resources.lib.streamz.api.Episode """ upnext_info = dict( current_episode=dict( episodeid=current_episode.episode_id, tvshowid=current_episode.program_id, title=current_episode.name, art={ 'thumb': current_episode.cover, }, season=current_episode.season, episode=current_episode.number, showtitle=current_episode.program_name, plot=current_episode.description, playcount=None, rating=None, firstaired=current_episode.aired[:10] if current_episode.aired else '', runtime=current_episode.duration, ), next_episode=dict( episodeid=next_episode.episode_id, tvshowid=next_episode.program_id, title=next_episode.name, art={ 'thumb': next_episode.cover, }, season=next_episode.season, episode=next_episode.number, showtitle=next_episode.program_name, plot=next_episode.description, playcount=None, rating=None, firstaired=next_episode.aired[:10] if next_episode.aired else '', runtime=next_episode.duration, ), play_url='plugin://plugin.video.streamz/play/catalog/episodes/%s' % next_episode.episode_id, ) return upnext_info @staticmethod def send_upnext(upnext_info): """ Send a message to Up Next with information about the next Episode. :type upnext_info: object """ from base64 import b64encode from json import dumps data = [kodiutils.to_unicode(b64encode(dumps(upnext_info).encode()))] sender = '{addon_id}.SIGNAL'.format(addon_id='plugin.video.streamz') kodiutils.notify(sender=sender, message='upnext_data', data=data)