def play(self, channel): manifest_url = self.get_manifest(channel) device_id = PluginCache.get_by_key("device_id") customer_id = PluginCache.get_by_key("entitlements")["customer_id"] is_helper = inputstreamhelper.Helper(PROTOCOL, drm=DRM) if is_helper.check_inputstream(): from addon import plugin play_item = xbmcgui.ListItem(path=manifest_url) play_item.setMimeType('application/xml+dash') play_item.setContentLookup(False) if kodi_version_major() >= 19: play_item.setProperty('inputstream', is_helper.inputstream_addon) else: play_item.setProperty('inputstreamaddon', is_helper.inputstream_addon) play_item.setProperty('inputstream.adaptive.manifest_type', PROTOCOL) play_item.setProperty('inputstream.adaptive.license_type', DRM) play_item.setProperty('inputstream.adaptive.manifest_update_parameter', 'full') play_item.setProperty('inputstream.adaptive.license_key', '%(url)s|Content-Type=text/plain;charset=UTF-8&User-Agent=%(ua)s|b{%(payload)s}|JBlicense' % dict( url=LICENSE_URL, ua=quote(USER_AGENT), payload=widevine_payload_package(device_id, customer_id), )) play_item.setProperty('inputstream.adaptive.license_flags', "persistent_storage") xbmcplugin.setResolvedUrl(plugin.handle, True, listitem=play_item)
def __init__(self): """PlayerInfo initialisation""" self.resumepoints = ResumePoints() self.apihelper = ApiHelper(Favorites(), self.resumepoints) self.last_pos = None self.listen = False self.paused = False self.total = 100 self.positionthread = None self.quit = Event() self.asset_str = None # FIXME On Kodi 17, use ListItem.Filenameandpath because Player.FilenameAndPath returns the stream manifest url and # this definitely breaks "Up Next" on Kodi 17, but this is not supported or available through the Kodi add-on repo anyway self.path_infolabel = 'ListItem.Filenameandpath' if kodi_version_major( ) < 18 else 'Player.FilenameAndPath' self.path = None self.title = None self.ep_id = None self.episode_id = None self.episode_title = None self.video_id = None from random import randint self.thread_id = randint(1, 10001) log(3, '[PlayerInfo {id}] Initialized', id=self.thread_id) super(PlayerInfo, self).__init__()
def _version_check(self): first_run, settings_version, addon_version = self._first_run() if first_run: # 2.0.0 version: changed plugin:// url interface: show warning that Kodi favourites and what-was-watched will break if settings_version == '' and has_credentials(): ok_dialog(localize(30978), localize(30979)) if addon_version == '2.2.1': # 2.2.1 version: changed artwork: delete old cached artwork delete_cached_thumbnail(get_addon_info('fanart').replace('.png', '.jpg')) delete_cached_thumbnail(get_addon_info('icon')) # 2.2.1 version: moved tokens: delete old tokens from tokenresolver import TokenResolver TokenResolver().delete_tokens() # Make user aware that timeshift functionality will not work without ISA when user starts up the first time if settings_version == '' and kodi_version_major() > 17 and not has_inputstream_adaptive(): ok_dialog(message=localize(30988))
def _handle_bad_stream_error(protocol, code=None, reason=None): """Show a localized error message in Kodi GUI for a failing VRT NU stream based on protocol: hls, hls_aes, mpeg_dash) message: VRT NU stream <stream_type> problem, try again with (InputStream Adaptive) (and) (DRM) enabled/disabled: 30959=and DRM, 30960=disabled, 30961=enabled """ # HLS AES DRM failed if protocol == 'hls_aes' and not supports_drm(): message = localize(30962, protocol=protocol.upper(), version=kodi_version_major()) elif protocol == 'hls_aes' and not has_inputstream_adaptive() and not get_setting_bool('usedrm', default=True): message = localize(30958, protocol=protocol.upper(), component=localize(30959), state=localize(30961)) elif protocol == 'hls_aes' and has_inputstream_adaptive(): message = localize(30958, protocol=protocol.upper(), component='Widevine DRM', state=localize(30961)) elif protocol == 'hls_aes' and get_setting_bool('usedrm', default=True): message = localize(30958, protocol=protocol.upper(), component='InputStream Adaptive', state=localize(30961)) else: message = localize(30958, protocol=protocol.upper(), component='InputStream Adaptive', state=localize(30960)) heading = 'HTTP Error {code}: {reason}'.format(code=code, reason=reason) if code and reason else None log_error('Unable to play stream. {error}', error=heading) ok_dialog(heading=heading, message=message) end_of_directory()
def get_stream(self, video, roaming=False, api_data=None): """Main streamservice function""" if not api_data: api_data = self._get_api_data(video) stream_json = self._get_stream_json(api_data, roaming) if not stream_json: # Roaming token failed if roaming: message = localize( 30964 ) # Geoblock error: Cannot be played, need Belgian phone number validation return self._handle_stream_api_error(message) # X-VRT-Token failed message = localize( 30963) # You need a VRT NU account to play this stream. return self._handle_stream_api_error(message) if 'targetUrls' in stream_json: # DRM support for ketnet junior/uplynk streaming service uplynk = 'uplynk.com' in stream_json.get('targetUrls')[0].get( 'url') vudrm_token = stream_json.get('drm') vualto_license_url = stream_json.get( 'vualto_license_url') or self._get_vualto_license_url() drm_stream = (vudrm_token or uplynk) # Select streaming protocol if not drm_stream and has_inputstream_adaptive(): protocol = 'mpeg_dash' elif drm_stream and self._can_play_drm: protocol = 'mpeg_dash' elif vudrm_token: protocol = 'hls_aes' else: protocol = 'hls' # Get stream manifest url manifest_url = next((stream.get('url') for stream in stream_json.get('targetUrls') if stream.get('type') == protocol), None) # Failed to get compatible manifest url, ask user to toggle "Use Widevine DRM" if manifest_url is None: available_protocols = [ stream.get('type') for stream in stream_json.get('targetUrls') ] if protocol not in available_protocols: error_json = { 'message': '%s is not available for this stream, please try toggling the "Use Widevine DRM" setting' % protocol } message = localize( 30989) # Failed to load a compatible stream return self._handle_stream_api_error(message, error_json) else: # External virtual subclip, live-to-VOD from past 24 hours archived livestream (airdate feature) if video.get('start_date') and video.get('end_date'): manifest_url += '?t=' + video.get( 'start_date') + '-' + video.get('end_date') # Fix virtual subclip from datetime import timedelta duration = timedelta( milliseconds=stream_json.get('duration', 0)) manifest_url = self._fix_virtualsubclip(manifest_url, duration) # Prepare stream for Kodi player if protocol == 'mpeg_dash' and drm_stream: log(2, 'Protocol: mpeg_dash drm') if vudrm_token: encryption_json = '{{"token":"{0}","drm_info":[D{{SSM}}],"kid":"{{KID}}"}}'.format( vudrm_token) license_key = self._get_license_key( key_url=vualto_license_url, key_type='D', key_value=encryption_json, key_headers={ 'Content-Type': 'text/plain;charset=UTF-8' }) else: license_key = self._get_license_key( key_url=self._UPLYNK_LICENSE_URL, key_type='R') stream = StreamURLS(manifest_url, license_key=license_key, use_inputstream_adaptive=True) elif protocol == 'mpeg_dash': log(2, 'Protocol: mpeg_dash') stream = StreamURLS(manifest_url, use_inputstream_adaptive=True) else: log(2, 'Protocol: {protocol}', protocol=protocol) # Fix 720p quality for HLS livestreams manifest_url = manifest_url.replace( '.m3u8?', '.m3u8?hd&' ) if '.m3u8?' in manifest_url else manifest_url + '?hd' # Play HLS directly in Kodi Player on Kodi 17 if kodi_version_major( ) < 18 or not has_inputstream_adaptive(): stream = self._select_hls_substreams( manifest_url, protocol) else: stream = StreamURLS(manifest_url, use_inputstream_adaptive=True) return stream # VRT Geoblock: failed to get stream, now try again with roaming enabled if stream_json.get('code') in self._GEOBLOCK_ERROR_CODES: log_error('VRT Geoblock: {msg}', msg=stream_json.get('message')) if not roaming: return self.get_stream(video, roaming=True, api_data=api_data) if stream_json.get('code') == self._INVALID_LOCATION: message = localize( 30965 ) # Geoblock error: Blocked on your geographical location based on your IP address return self._handle_stream_api_error(message, stream_json) if stream_json.get('code') == self._BELGIUM_ONLY: message = localize( 30973 ) # Geoblock error: This program can only be played from EU return self._handle_stream_api_error(message, stream_json) message = localize( 30964 ) # Geoblock error: Cannot be played, need Belgian phone number validation return self._handle_stream_api_error(message, stream_json) if stream_json.get('code') == 'VIDEO_NOT_FOUND': # Refresh listing invalidate_caches('*.json') container_reload() message = localize(30987) # No stream found return self._handle_stream_api_error(message, stream_json) if stream_json.get('code') == 'ERROR_AGE_RESTRICTED': message = localize( 30990 ) # Cannot be played, VRT NU account not allowed to access 12+ content return self._handle_stream_api_error(message, stream_json) # Failed to get stream, handle error message = localize(30954) # Whoops something went wrong return self._handle_stream_api_error(message, stream_json)
def onPlayBackStarted(self): # pylint: disable=invalid-name """Called when user starts playing a file""" self.path = getInfoLabel(self.path_infolabel) if self.path.startswith('plugin://plugin.video.vrt.nu/'): self.listen = True else: self.listen = False return log(3, '[PlayerInfo {id}] Event onPlayBackStarted', id=self.thread_id) # Set property to let wait_for_resumepoints function know that update resume is busy set_property('vrtnu_resumepoints', 'busy') # Update previous episode when using "Up Next" if self.path.startswith('plugin://plugin.video.vrt.nu/play/upnext'): self.push_position(position=self.last_pos, total=self.total) # Reset episode data self.video_id = None self.episode_id = None self.episode_title = None self.asset_str = None self.title = None ep_id = play_url_to_id(self.path) # Avoid setting resumepoints for livestreams for channel in CHANNELS: if ep_id.get('video_id') and ep_id.get('video_id') == channel.get( 'live_stream_id'): log(3, '[PlayerInfo {id}] Avoid setting resumepoints for livestream {video_id}', id=self.thread_id, video_id=ep_id.get('video_id')) self.listen = False # Reset vrtnu_resumepoints property before return set_property('vrtnu_resumepoints', None) return # Get episode data needed to update resumepoints from VRT NU Search API episode = self.apihelper.get_single_episode_data( video_id=ep_id.get('video_id'), whatson_id=ep_id.get('whatson_id'), video_url=ep_id.get('video_url')) # Avoid setting resumepoints without episode data if episode is None: # Reset vrtnu_resumepoints property before return set_property('vrtnu_resumepoints', None) return from metadata import Metadata self.video_id = episode.get('videoId') or None self.episode_id = episode.get('episodeId') or None self.episode_title = episode.get('title') or None self.asset_str = Metadata(None, None).get_asset_str(episode) self.title = episode.get('program') # Kodi 17 doesn't have onAVStarted if kodi_version_major() < 18: self.onAVStarted()