diff --git a/addon.xml b/addon.xml index 2c7c98659..c8bdb7a99 100644 --- a/addon.xml +++ b/addon.xml @@ -1,5 +1,5 @@ - + diff --git a/changelog.txt b/changelog.txt index 2ab392303..d2ffa7920 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,11 @@ +## v7.0.9+beta.10 +### Fixed +- Don't retry player requests for offline live streams +- Fix incorrectly updating playing live stream details + +### New +- Add context menu item to play live stream from start #320 + ## v7.0.9+beta.9 ### Fixed - Fix not properly defining default audio role #861 diff --git a/resources/language/resource.language.en_gb/strings.po b/resources/language/resource.language.en_gb/strings.po index 71acf1b8e..34a194297 100644 --- a/resources/language/resource.language.en_gb/strings.po +++ b/resources/language/resource.language.en_gb/strings.po @@ -1580,3 +1580,7 @@ msgstr "" msgctxt "#30818" msgid "Are you sure you want to refresh settings.xml?" msgstr "" + +msgctxt "#30819" +msgid "Play from start" +msgstr "" diff --git a/resources/lib/youtube_plugin/kodion/constants/__init__.py b/resources/lib/youtube_plugin/kodion/constants/__init__.py index e7935ae2a..84a898be8 100644 --- a/resources/lib/youtube_plugin/kodion/constants/__init__.py +++ b/resources/lib/youtube_plugin/kodion/constants/__init__.py @@ -66,6 +66,7 @@ PLAY_FORCE_AUDIO = 'audio_only' PLAY_PROMPT_QUALITY = 'ask_for_quality' PLAY_PROMPT_SUBTITLES = 'prompt_for_subtitles' +PLAY_TIMESHIFT = 'timeshift' PLAY_WITH = 'play_with' # Stored data @@ -122,6 +123,7 @@ 'PLAY_FORCE_AUDIO', 'PLAY_PROMPT_QUALITY', 'PLAY_PROMPT_SUBTITLES', + 'PLAY_TIMESHIFT', 'PLAY_WITH', # Stored data diff --git a/resources/lib/youtube_plugin/kodion/context/abstract_context.py b/resources/lib/youtube_plugin/kodion/context/abstract_context.py index ec0e824c3..e13c67949 100644 --- a/resources/lib/youtube_plugin/kodion/context/abstract_context.py +++ b/resources/lib/youtube_plugin/kodion/context/abstract_context.py @@ -19,6 +19,7 @@ PLAY_FORCE_AUDIO, PLAY_PROMPT_QUALITY, PLAY_PROMPT_SUBTITLES, + PLAY_TIMESHIFT, PLAY_WITH, VALUE_FROM_STR, ) @@ -44,6 +45,7 @@ class AbstractContext(object): PLAY_FORCE_AUDIO, PLAY_PROMPT_SUBTITLES, PLAY_PROMPT_QUALITY, + PLAY_TIMESHIFT, PLAY_WITH, 'confirmed', 'clip', diff --git a/resources/lib/youtube_plugin/kodion/context/xbmc/xbmc_context.py b/resources/lib/youtube_plugin/kodion/context/xbmc/xbmc_context.py index 21a69b45e..d53fd3708 100644 --- a/resources/lib/youtube_plugin/kodion/context/xbmc/xbmc_context.py +++ b/resources/lib/youtube_plugin/kodion/context/xbmc/xbmc_context.py @@ -259,6 +259,7 @@ class XbmcContext(AbstractContext): 'video.more': 30548, 'video.play.ask_for_quality': 30730, 'video.play.audio_only': 30708, + 'video.play.timeshift': 30819, 'video.play.with': 30540, 'video.play.with_subtitles': 30702, 'video.queue': 30511, @@ -673,6 +674,7 @@ def use_inputstream_adaptive(self, prompt=False): # functionality 'drm': loose_version('2.2.12'), 'live': loose_version('2.0.12'), + 'timeshift': loose_version('2.5.2'), 'ttml': loose_version('20.0.0'), # properties 'config_prop': loose_version('21.4.11'), diff --git a/resources/lib/youtube_plugin/kodion/items/menu_items.py b/resources/lib/youtube_plugin/kodion/items/menu_items.py index e8999fa71..5ce366f99 100644 --- a/resources/lib/youtube_plugin/kodion/items/menu_items.py +++ b/resources/lib/youtube_plugin/kodion/items/menu_items.py @@ -15,6 +15,7 @@ PLAY_FORCE_AUDIO, PLAY_PROMPT_QUALITY, PLAY_PROMPT_SUBTITLES, + PLAY_TIMESHIFT, PLAY_WITH, ) @@ -431,6 +432,20 @@ def play_ask_for_quality(context, video_id): ) +def play_timeshift(context, video_id): + return ( + context.localize('video.play.timeshift'), + context.create_uri( + (PATHS.PLAY,), + { + 'video_id': video_id, + PLAY_TIMESHIFT: True, + }, + run=True, + ), + ) + + def history_remove(context, video_id): return ( context.localize('history.remove'), diff --git a/resources/lib/youtube_plugin/kodion/items/xbmc/xbmc_items.py b/resources/lib/youtube_plugin/kodion/items/xbmc/xbmc_items.py index a5bc7ecb4..a043c725d 100644 --- a/resources/lib/youtube_plugin/kodion/items/xbmc/xbmc_items.py +++ b/resources/lib/youtube_plugin/kodion/items/xbmc/xbmc_items.py @@ -19,6 +19,7 @@ PLAYLISTITEM_ID, PLAYLIST_ID, PLAY_COUNT, + PLAY_TIMESHIFT, PLAY_WITH, SUBSCRIPTION_ID, VIDEO_ID, @@ -380,7 +381,9 @@ def video_playback_item(context, video_item, show_fanart=None, **_kwargs): settings = context.get_settings() headers = video_item.get_headers() license_key = video_item.get_license_key() - is_external = context.get_ui().get_property(PLAY_WITH) + + ui = context.get_ui() + is_external = ui.get_property(PLAY_WITH) is_strm = context.get_param('strm') mime_type = None @@ -402,6 +405,8 @@ def video_playback_item(context, video_item, show_fanart=None, **_kwargs): } if video_item.use_isa_video() and context.use_inputstream_adaptive(): + capabilities = context.inputstream_adaptive_capabilities() + use_mpd = video_item.use_mpd_video() if use_mpd: manifest_type = 'mpd' @@ -422,18 +427,21 @@ def video_playback_item(context, video_item, show_fanart=None, **_kwargs): else: props['inputstreamaddon'] = 'inputstream.adaptive' - if current_system_version.compatible(21, 0): - isa_capabilities = context.inputstream_adaptive_capabilities() - if video_item.live and 'manifest_config_prop' in isa_capabilities: + if not current_system_version.compatible(21, 0): + props['inputstream.adaptive.manifest_type'] = manifest_type + + if video_item.live: + if 'manifest_config_prop' in capabilities: props['inputstream.adaptive.manifest_config'] = dumps({ 'timeshift_bufferlimit': 4 * 60 * 60, }) - if not settings.verify_ssl() and 'config_prop' in isa_capabilities: - props['inputstream.adaptive.config'] = dumps({ - 'ssl_verify_peer': False, - }) - else: - props['inputstream.adaptive.manifest_type'] = manifest_type + if ui.pop_property(PLAY_TIMESHIFT) and 'timeshift' in capabilities: + props['inputstream.adaptive.play_timeshift_buffer'] = True + + if not settings.verify_ssl() and 'config_prop' in capabilities: + props['inputstream.adaptive.config'] = dumps({ + 'ssl_verify_peer': False, + }) if headers: props['inputstream.adaptive.manifest_headers'] = headers diff --git a/resources/lib/youtube_plugin/youtube/helper/stream_info.py b/resources/lib/youtube_plugin/youtube/helper/stream_info.py index ef5348be5..7d3a03fce 100644 --- a/resources/lib/youtube_plugin/youtube/helper/stream_info.py +++ b/resources/lib/youtube_plugin/youtube/helper/stream_info.py @@ -1422,6 +1422,9 @@ def load_stream_info(self, video_id): and playability.get('desktopLegacyAgeGateReason')): abort = True break + elif status == 'LIVE_STREAM_OFFLINE': + abort = True + break elif status == 'OK': break elif status in { @@ -1526,18 +1529,16 @@ def load_stream_info(self, video_id): thumb_suffix = '' meta_info = { - 'video': { - 'id': video_id, - 'title': unescape(video_details.get('title', '') - .encode('raw_unicode_escape') - .decode('raw_unicode_escape')), - 'status': { - 'unlisted': microformat.get('isUnlisted', False), - 'private': video_details.get('isPrivate', False), - 'crawlable': video_details.get('isCrawlable', False), - 'family_safe': microformat.get('isFamilySafe', False), - 'live': is_live, - }, + 'id': video_id, + 'title': unescape(video_details.get('title', '') + .encode('raw_unicode_escape') + .decode('raw_unicode_escape')), + 'status': { + 'unlisted': microformat.get('isUnlisted', False), + 'private': video_details.get('isPrivate', False), + 'crawlable': video_details.get('isCrawlable', False), + 'family_safe': microformat.get('isFamilySafe', False), + 'live': is_live, }, 'channel': { 'id': video_details.get('channelId', ''), diff --git a/resources/lib/youtube_plugin/youtube/helper/utils.py b/resources/lib/youtube_plugin/youtube/helper/utils.py index a83d23a00..da6d00da1 100644 --- a/resources/lib/youtube_plugin/youtube/helper/utils.py +++ b/resources/lib/youtube_plugin/youtube/helper/utils.py @@ -813,6 +813,13 @@ def update_video_infos(provider, context, video_id_dict, ) ) + if video_item.live: + context_menu.append( + menu_items.play_timeshift( + context, video_id + ) + ) + if context_menu: context_menu.append(menu_items.separator()) video_item.add_context_menu(context_menu) @@ -829,7 +836,7 @@ def update_play_info(provider, context, video_id, video_item, video_stream, settings = context.get_settings() ui = context.get_ui() - meta_data = video_stream.get('meta', None) + meta_data = video_stream.get('meta') if meta_data: video_item.live = meta_data.get('status', {}).get('live', False) video_item.set_subtitles(meta_data.get('subtitles', None)) diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_play.py b/resources/lib/youtube_plugin/youtube/helper/yt_play.py index ff6645da7..9bcf42f03 100644 --- a/resources/lib/youtube_plugin/youtube/helper/yt_play.py +++ b/resources/lib/youtube_plugin/youtube/helper/yt_play.py @@ -24,6 +24,7 @@ PLAY_FORCE_AUDIO, PLAY_PROMPT_QUALITY, PLAY_PROMPT_SUBTITLES, + PLAY_TIMESHIFT, PLAY_WITH, SERVER_POST_START, SERVER_WAKEUP, @@ -108,8 +109,6 @@ def _play_stream(provider, context): video_id) metadata = stream.get('meta', {}) - video_details = metadata.get('video', {}) - if is_external: url = urlunsplit(( 'http', @@ -119,7 +118,8 @@ def _play_stream(provider, context): '', )) stream['url'] = url - video_item = VideoItem(video_details.get('title', ''), stream['url']) + + video_item = VideoItem(metadata.get('title', ''), stream['url']) use_history = not (screensaver or incognito or stream.get('live')) use_remote_history = use_history and settings.use_remote_history() @@ -145,7 +145,7 @@ def _play_stream(provider, context): playback_data = { 'video_id': video_id, 'channel_id': metadata.get('channel', {}).get('id', ''), - 'video_status': video_details.get('status', {}), + 'video_status': metadata.get('status', {}), 'playing_file': video_item.get_uri(), 'play_count': play_count, 'use_remote_history': use_remote_history, @@ -326,6 +326,7 @@ def process(provider, context, **_kwargs): force_play = False for param in {PLAY_FORCE_AUDIO, + PLAY_TIMESHIFT, PLAY_PROMPT_QUALITY, PLAY_PROMPT_SUBTITLES, PLAY_WITH}.intersection(param_keys):