diff --git a/addon.xml b/addon.xml index 4fe7c8693..e5a77d0e4 100644 --- a/addon.xml +++ b/addon.xml @@ -1,5 +1,5 @@ - + diff --git a/changelog.txt b/changelog.txt index a46721359..33e03c8e8 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,18 @@ +## v7.1.0+beta.1 +### +- Fix logging/retry of sqlite3.OperationalError +- Fix trying to use ISA for progressive live streams +- Retain list position when refreshing listings +- Add workarounds for trying to play videos using RunPlugin rather PlayMedia + +### Changed +- Update multiple busy dialog crash workaround #891 +- Use all playable codecs but deprioritise if not selected in stream features +- Change default live stream type to suit ISA and Youtube stream availability + +### New +- Allow ask for quality from context menu to override audio only setting + ## v7.0.9.2 ### Fixed - Fix various Kodi 18 listitem setInfo compatibility issues diff --git a/resources/lib/youtube_plugin/kodion/abstract_provider.py b/resources/lib/youtube_plugin/kodion/abstract_provider.py index b3892d0d2..e9789d074 100644 --- a/resources/lib/youtube_plugin/kodion/abstract_provider.py +++ b/resources/lib/youtube_plugin/kodion/abstract_provider.py @@ -12,7 +12,14 @@ import re -from .constants import CHECK_SETTINGS, CONTENT, PATHS, REROUTE_PATH +from .constants import ( + CHECK_SETTINGS, + CONTAINER_ID, + CONTAINER_POSITION, + CONTENT, + PATHS, + REROUTE_PATH, +) from .exceptions import KodionException from .items import ( DirectoryItem, @@ -234,11 +241,21 @@ def reroute(self, context, path=None, params=None, uri=None): if not path: return False + + do_refresh = 'refresh' in params + if path == current_path and params == current_params: - if 'refresh' not in params: + if not do_refresh: return False params['refresh'] += 1 + if do_refresh: + container = context.get_infolabel('System.CurrentControlId') + position = context.get_infolabel('Container.CurrentItem') + else: + container = None + position = None + result = None function_cache = context.get_function_cache() window_return = params.pop('window_return', True) @@ -252,16 +269,22 @@ def reroute(self, context, path=None, params=None, uri=None): except Exception as exc: context.log_error('Rerouting error: |{0}|'.format(exc)) finally: - context.log_debug('Rerouting to |{path}| |{params}|{status}' - .format(path=path, - params=params, + uri = context.create_uri(path, params) + context.log_debug('Rerouting to |{uri}|{status}' + .format(uri=uri, status='' if result else ' failed')) if not result: return False - context.get_ui().set_property(REROUTE_PATH, path) + + ui = context.get_ui() + ui.set_property(REROUTE_PATH, path) + if container and position: + ui.set_property(CONTAINER_ID, container) + ui.set_property(CONTAINER_POSITION, position) + context.execute(''.join(( 'ActivateWindow(Videos, ', - context.create_uri(path, params), + uri, ', return)' if window_return else ')', ))) return True diff --git a/resources/lib/youtube_plugin/kodion/constants/__init__.py b/resources/lib/youtube_plugin/kodion/constants/__init__.py index 84a898be8..eaf286868 100644 --- a/resources/lib/youtube_plugin/kodion/constants/__init__.py +++ b/resources/lib/youtube_plugin/kodion/constants/__init__.py @@ -70,6 +70,9 @@ PLAY_WITH = 'play_with' # Stored data +CONTAINER_ID = 'container_id' +CONTAINER_FOCUS = 'container_focus' +CONTAINER_POSITION = 'container_position' CONTENT_TYPE = 'content_type' DEVELOPER_CONFIGS = 'configs' LICENSE_TOKEN = 'license_token' @@ -127,6 +130,9 @@ 'PLAY_WITH', # Stored data + 'CONTAINER_ID', + 'CONTAINER_FOCUS', + 'CONTAINER_POSITION', 'CONTENT_TYPE', 'DEVELOPER_CONFIGS', 'LICENSE_TOKEN', diff --git a/resources/lib/youtube_plugin/kodion/context/abstract_context.py b/resources/lib/youtube_plugin/kodion/context/abstract_context.py index e13c67949..755501e8e 100644 --- a/resources/lib/youtube_plugin/kodion/context/abstract_context.py +++ b/resources/lib/youtube_plugin/kodion/context/abstract_context.py @@ -240,16 +240,7 @@ def reload_access_manager(self, get_uuid=False): return uuid return access_manager - def get_video_playlist(self): - raise NotImplementedError() - - def get_audio_playlist(self): - raise NotImplementedError() - - def get_video_player(self): - raise NotImplementedError() - - def get_audio_player(self): + def get_playlist_player(self): raise NotImplementedError() def get_ui(self): 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 33f7914bf..b05a3ed1b 100644 --- a/resources/lib/youtube_plugin/kodion/context/xbmc/xbmc_context.py +++ b/resources/lib/youtube_plugin/kodion/context/xbmc/xbmc_context.py @@ -32,7 +32,7 @@ SORT, WAKEUP, ) -from ...player import XbmcPlayer, XbmcPlaylist +from ...player import XbmcPlaylistPlayer from ...settings import XbmcPluginSettings from ...ui import XbmcContextUI from ...utils import ( @@ -320,10 +320,7 @@ def __init__(self, self._version = self._addon.getAddonInfo('version') self._ui = None - self._video_playlist = None - self._audio_playlist = None - self._video_player = None - self._audio_player = None + self._playlist = None atexit.register(self.tear_down) @@ -426,25 +423,12 @@ def get_subtitle_language(self): sub_language = None return sub_language - def get_video_playlist(self): - if not self._video_playlist: - self._video_playlist = XbmcPlaylist('video', proxy(self)) - return self._video_playlist - - def get_audio_playlist(self): - if not self._audio_playlist: - self._audio_playlist = XbmcPlaylist('audio', proxy(self)) - return self._audio_playlist - - def get_video_player(self): - if not self._video_player: - self._video_player = XbmcPlayer('video', proxy(self)) - return self._video_player - - def get_audio_player(self): - if not self._audio_player: - self._audio_player = XbmcPlayer('audio', proxy(self)) - return self._audio_player + def get_playlist_player(self, playlist_type=None): + if not self._playlist or playlist_type: + self._playlist = XbmcPlaylistPlayer(playlist_type, + proxy(self), + retry=3) + return self._playlist def get_ui(self): if not self._ui: @@ -593,10 +577,7 @@ def clone(self, new_path=None, new_params=None): new_context._watch_later_list = self._watch_later_list new_context._ui = self._ui - new_context._video_playlist = self._video_playlist - new_context._audio_playlist = self._audio_playlist - new_context._video_player = self._video_player - new_context._audio_player = self._audio_player + new_context._playlist = self._playlist return new_context @@ -761,10 +742,7 @@ def tear_down(self): attrs = ( '_ui', - '_video_playlist', - '_audio_playlist', - '_video_player', - '_audio_player', + '_playlist', ) for attr in attrs: try: diff --git a/resources/lib/youtube_plugin/kodion/monitors/player_monitor.py b/resources/lib/youtube_plugin/kodion/monitors/player_monitor.py index 59bb42c43..41f906552 100644 --- a/resources/lib/youtube_plugin/kodion/monitors/player_monitor.py +++ b/resources/lib/youtube_plugin/kodion/monitors/player_monitor.py @@ -355,6 +355,9 @@ def cleanup_threads(self, only_ended=True): self.threads = active_threads def onPlayBackStarted(self): + if not self._ui.busy_dialog_active(): + self._ui.clear_property(BUSY_FLAG) + if self._ui.get_property(PLAY_WITH): self._context.execute('Action(SwitchPlayer)') self._context.execute('Action(Stop)') @@ -363,9 +366,6 @@ def onAVStarted(self): if self._ui.get_property(PLAY_WITH): return - if not self._ui.busy_dialog_active(): - self._ui.clear_property(BUSY_FLAG) - playback_data = self._ui.pop_property(PLAYER_DATA) if not playback_data: return diff --git a/resources/lib/youtube_plugin/kodion/monitors/service_monitor.py b/resources/lib/youtube_plugin/kodion/monitors/service_monitor.py index b0cfc33d1..5c920ed42 100644 --- a/resources/lib/youtube_plugin/kodion/monitors/service_monitor.py +++ b/resources/lib/youtube_plugin/kodion/monitors/service_monitor.py @@ -16,6 +16,7 @@ from ..constants import ( ADDON_ID, CHECK_SETTINGS, + CONTAINER_FOCUS, PLUGIN_WAKEUP, REFRESH_CONTAINER, RELOAD_ACCESS_MANAGER, @@ -88,7 +89,7 @@ def onNotification(self, sender, method, data): return group, separator, event = method.partition('.') if event == CHECK_SETTINGS: - if not isinstance(data, dict): + if data: data = json.loads(data) if data == 'defer': self._settings_state = data @@ -113,6 +114,12 @@ def onNotification(self, sender, method, data): self.set_property(WAKEUP, target) elif event == REFRESH_CONTAINER: self.refresh_container() + elif event == CONTAINER_FOCUS: + if data: + data = json.loads(data) + if not data or not self.is_plugin_container(check_all=True): + return + xbmc.executebuiltin('SetFocus({0},{1},absolute)'.format(*data)) elif event == RELOAD_ACCESS_MANAGER: self._context.reload_access_manager() self.refresh_container() diff --git a/resources/lib/youtube_plugin/kodion/player/__init__.py b/resources/lib/youtube_plugin/kodion/player/__init__.py index 62e814d12..83ed44b37 100644 --- a/resources/lib/youtube_plugin/kodion/player/__init__.py +++ b/resources/lib/youtube_plugin/kodion/player/__init__.py @@ -9,8 +9,7 @@ from __future__ import absolute_import, division, unicode_literals -from .xbmc.xbmc_player import XbmcPlayer -from .xbmc.xbmc_playlist import XbmcPlaylist +from .xbmc.xbmc_playlist_player import XbmcPlaylistPlayer -__all__ = ('XbmcPlayer', 'XbmcPlaylist',) +__all__ = ('XbmcPlaylistPlayer',) diff --git a/resources/lib/youtube_plugin/kodion/player/abstract_player.py b/resources/lib/youtube_plugin/kodion/player/abstract_player.py deleted file mode 100644 index ac769d85e..000000000 --- a/resources/lib/youtube_plugin/kodion/player/abstract_player.py +++ /dev/null @@ -1,29 +0,0 @@ -# -*- coding: utf-8 -*- -""" - - Copyright (C) 2014-2016 bromix (plugin.video.youtube) - Copyright (C) 2016-2018 plugin.video.youtube - - SPDX-License-Identifier: GPL-2.0-only - See LICENSES/GPL-2.0-only for more information. -""" - - -class AbstractPlayer(object): - def __init__(self): - pass - - def play(self, playlist_index=-1): - raise NotImplementedError() - - @staticmethod - def stop(): - raise NotImplementedError() - - @staticmethod - def pause(): - raise NotImplementedError() - - @staticmethod - def is_playing(): - raise NotImplementedError() diff --git a/resources/lib/youtube_plugin/kodion/player/abstract_playlist.py b/resources/lib/youtube_plugin/kodion/player/abstract_playlist_player.py similarity index 93% rename from resources/lib/youtube_plugin/kodion/player/abstract_playlist.py rename to resources/lib/youtube_plugin/kodion/player/abstract_playlist_player.py index a840b790f..34c97b833 100644 --- a/resources/lib/youtube_plugin/kodion/player/abstract_playlist.py +++ b/resources/lib/youtube_plugin/kodion/player/abstract_playlist_player.py @@ -9,7 +9,7 @@ """ -class AbstractPlaylist(object): +class AbstractPlaylistPlayer(object): def __init__(self): pass diff --git a/resources/lib/youtube_plugin/kodion/player/xbmc/xbmc_player.py b/resources/lib/youtube_plugin/kodion/player/xbmc/xbmc_player.py deleted file mode 100644 index f290667d3..000000000 --- a/resources/lib/youtube_plugin/kodion/player/xbmc/xbmc_player.py +++ /dev/null @@ -1,58 +0,0 @@ -# -*- coding: utf-8 -*- -""" - - Copyright (C) 2014-2016 bromix (plugin.video.youtube) - Copyright (C) 2016-2018 plugin.video.youtube - - SPDX-License-Identifier: GPL-2.0-only - See LICENSES/GPL-2.0-only for more information. -""" - -from __future__ import absolute_import, division, unicode_literals - -from ..abstract_player import AbstractPlayer -from ...compatibility import xbmc - - -class XbmcPlayer(AbstractPlayer): - def __init__(self, player_type, context): - super(XbmcPlayer, self).__init__() - - self._player_type = player_type - if player_type == 'audio': - self._player_type = 'music' - - self._context = context - - def play(self, playlist_index=-1): - """ - We call the player in this way, because 'Player.play(...)' will call the addon again while the instance is - running. This is somehow shitty, because we couldn't release any resources and in our case we couldn't release - the cache. So this is the solution to prevent a locked database (sqlite). - """ - self._context.execute('Playlist.PlayOffset(%s,%d)' % (self._player_type, playlist_index)) - - """ - playlist = None - if self._player_type == 'video': - playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO) - elif self._player_type == 'music': - playlist = xbmc.PlayList(xbmc.PLAYLIST_MUSIC) - - if playlist_index >= 0: - xbmc.Player().play(item=playlist, startpos=playlist_index) - else: - xbmc.Player().play(item=playlist) - """ - - @staticmethod - def stop(): - xbmc.Player().stop() - - @staticmethod - def pause(): - xbmc.Player().pause() - - @staticmethod - def is_playing(): - return xbmc.Player().isPlaying() diff --git a/resources/lib/youtube_plugin/kodion/player/xbmc/xbmc_playlist.py b/resources/lib/youtube_plugin/kodion/player/xbmc/xbmc_playlist_player.py similarity index 58% rename from resources/lib/youtube_plugin/kodion/player/xbmc/xbmc_playlist.py rename to resources/lib/youtube_plugin/kodion/player/xbmc/xbmc_playlist_player.py index a914c2051..16f54ba6e 100644 --- a/resources/lib/youtube_plugin/kodion/player/xbmc/xbmc_playlist.py +++ b/resources/lib/youtube_plugin/kodion/player/xbmc/xbmc_playlist_player.py @@ -12,33 +12,37 @@ import json -from ..abstract_playlist import AbstractPlaylist +from ..abstract_playlist_player import AbstractPlaylistPlayer from ...compatibility import xbmc from ...items import VideoItem, media_listitem from ...utils.methods import jsonrpc, wait -class XbmcPlaylist(AbstractPlaylist): +class XbmcPlaylistPlayer(AbstractPlaylistPlayer): _CACHE = { - 'playerid': None, - 'playlistid': None + 'player_id': None, + 'playlist_id': None } _PLAYER_PLAYLIST = { + 0: 'music', + 1: 'video', 'video': xbmc.PLAYLIST_VIDEO, # 1 'audio': xbmc.PLAYLIST_MUSIC, # 0 } def __init__(self, playlist_type, context, retry=0): - super(XbmcPlaylist, self).__init__() + super(XbmcPlaylistPlayer, self).__init__() self._context = context - self._playlist = None - playlist_type = self._PLAYER_PLAYLIST.get(playlist_type) - if playlist_type: - self._playlist = xbmc.PlayList(playlist_type) - else: - self._playlist = xbmc.PlayList(self.get_playlistid(retry=retry)) + + playlist_id = self._PLAYER_PLAYLIST.get(playlist_type) + if not playlist_type: + playlist_id = self.get_playlist_id(retry=retry) + self.set_playlist_id(playlist_id) + + self._playlist = xbmc.PlayList(playlist_id) + self._player = xbmc.Player() def clear(self): self._playlist.clear() @@ -57,13 +61,26 @@ def unshuffle(self): def size(self): return self._playlist.size() + def stop(self): + return self._player.stop() + + def pause(self): + return self._player.pause() + + def play_item(self, *args, **kwargs): + return self._player.play(*args, **kwargs) + + def is_playing(self): + return self._player.isPlaying() + @classmethod - def get_playerid(cls, retry=0): - """Function to get active player playerid""" + def get_player_id(cls, retry=0): + """Function to get active player player_id""" - # We don't need to get playerid every time, cache and reuse instead - if cls._CACHE['playerid'] is not None: - return cls._CACHE['playerid'] + # We don't need to get player_id every time, cache and reuse instead + player_id = cls._CACHE['player_id'] + if player_id is not None: + return player_id # Sometimes Kodi gets confused and uses a music playlist for video # content, so get the first active player instead, default to video @@ -79,42 +96,55 @@ def get_playerid(cls, retry=0): wait(2) else: # No active player - cls._CACHE['playerid'] = None + cls.set_player_id(None) return None for player in result: if player.get('type', 'video') in cls._PLAYER_PLAYLIST: - playerid = player.get('playerid') - if playerid is not None: - playerid = int(playerid) + try: + player_id = int(player['playerid']) + except (KeyError, TypeError, ValueError): + continue break else: # No active player - cls._CACHE['playerid'] = None - return None + player_id = None + + cls.set_player_id(player_id) + return player_id - cls._CACHE['playerid'] = playerid - return playerid + @classmethod + def set_player_id(cls, player_id): + """Function to set player_id for requested player type""" + + cls._CACHE['player_id'] = player_id @classmethod - def get_playlistid(cls, retry=0): - """Function to get playlistid of active player""" + def set_playlist_id(cls, playlist_id): + """Function to set playlist_id for requested playlist type""" - # We don't need to get playlistid every time, cache and reuse instead - if cls._CACHE['playlistid'] is not None: - return cls._CACHE['playlistid'] + cls._CACHE['playlist_id'] = playlist_id + + @classmethod + def get_playlist_id(cls, retry=0): + """Function to get playlist_id of active player""" + + # We don't need to get playlist_id every time, cache and reuse instead + playlist_id = cls._CACHE['playlist_id'] + if playlist_id is not None: + return playlist_id result = jsonrpc(method='Player.GetProperties', - params={'playerid': cls.get_playerid(retry=retry), + params={'playerid': cls.get_player_id(retry=retry), 'properties': ['playlistid']}) try: - playlistid = int(result['result']['playlistid']) + playlist_id = int(result['result']['playlistid']) except (KeyError, TypeError, ValueError): - playlistid = cls._PLAYER_PLAYLIST['video'] + playlist_id = cls._PLAYER_PLAYLIST['video'] - cls._CACHE['playlistid'] = playlistid - return playlistid + cls.set_playlist_id(playlist_id) + return playlist_id def get_items(self, properties=None, dumps=False): if properties is None: @@ -148,7 +178,7 @@ def add_items(self, items, loads=False): # jsonrpc(method='Playlist.Add', # params={ - # 'playlistid': self._playlist.getPlayListId(), + # 'playlistid': self._playlist.getPlaylistId(), # 'item': items, # }, # no_response=True) @@ -172,7 +202,7 @@ def play_playlist_item(self, position, resume=False): .format(position)) if not resume: - xbmc.Player().play(self._playlist, startpos=position - 1) + self._player.play(self._playlist, startpos=position - 1) return # JSON Player.Open can be too slow but is needed if resuming is enabled jsonrpc(method='Player.Open', @@ -182,34 +212,49 @@ def play_playlist_item(self, position, resume=False): options={'resume': True}, no_response=True) + def play(self, playlist_index=-1): + """ + We call the player in this way, because 'Player.play(...)' will call the addon again while the instance is + running. This is somehow shitty, because we couldn't release any resources and in our case we couldn't release + the cache. So this is the solution to prevent a locked database (sqlite). + """ + self._context.execute('Playlist.PlayOffset(%s,%d)' % ( + self._PLAYER_PLAYLIST[self._playlist.getPlayListId()], + playlist_index, + )) + + """ + playlist = None + if self._player_type == 'video': + playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO) + elif self._player_type == 'music': + playlist = xbmc.PlayList(xbmc.PLAYLIST_MUSIC) + + if playlist_index >= 0: + xbmc.Player().play(item=playlist, startpos=playlist_index) + else: + xbmc.Player().play(item=playlist) + """ + def get_position(self, offset=0): """ Function to get current playlist position and number of remaining playlist items, where the first item in the playlist is position 1 """ - result = (None, None) - - # Use actual playlistid rather than xbmc.PLAYLIST_VIDEO as Kodi - # sometimes plays video content in a music playlist - playlistid = self._playlist.getPlayListId() - if playlistid is None: - return result - - playlist = xbmc.PlayList(playlistid) - position = playlist.getposition() + position = self._playlist.getposition() # PlayList().getposition() starts from zero unless playlist not active if position < 0: - return result - playlist_size = playlist.size() + return None, None + playlist_size = self._playlist.size() # Use 1 based index value for playlist position position += (offset + 1) # A playlist with only one element has no next item if playlist_size >= 1 and position <= playlist_size: - self._context.log_debug('playlistid: {0}, position - {1}/{2}' - .format(playlistid, + self._context.log_debug('playlist_id: {0}, position - {1}/{2}' + .format(self.get_playlist_id(), position, playlist_size)) - result = (position, (playlist_size - position)) - return result + return position, (playlist_size - position) + return None, None diff --git a/resources/lib/youtube_plugin/kodion/plugin/xbmc/xbmc_plugin.py b/resources/lib/youtube_plugin/kodion/plugin/xbmc/xbmc_plugin.py index 1df1e133d..6d639f6a9 100644 --- a/resources/lib/youtube_plugin/kodion/plugin/xbmc/xbmc_plugin.py +++ b/resources/lib/youtube_plugin/kodion/plugin/xbmc/xbmc_plugin.py @@ -13,10 +13,13 @@ from traceback import format_stack from ..abstract_plugin import AbstractPlugin -from ...compatibility import xbmc, xbmcplugin +from ...compatibility import xbmcplugin from ...constants import ( BUSY_FLAG, CHECK_SETTINGS, + CONTAINER_FOCUS, + CONTAINER_ID, + CONTAINER_POSITION, PLAYLIST_PATH, PLAYLIST_POSITION, PLUGIN_SLEEPING, @@ -34,7 +37,6 @@ playback_item, uri_listitem, ) -from ...player import XbmcPlaylist class XbmcPlugin(AbstractPlugin): @@ -65,65 +67,79 @@ def run(self, provider, context, focused=None): self.handle = context.get_handle() ui = context.get_ui() - if ui.pop_property(BUSY_FLAG).lower() == 'true': - if ui.busy_dialog_active(): - xbmcplugin.endOfDirectory( - self.handle, - succeeded=False, - updateListing=True, - ) + route = ui.pop_property(REROUTE_PATH) + for was_busy in (ui.pop_property(BUSY_FLAG),): + if was_busy: + if ui.busy_dialog_active(): + ui.set_property(BUSY_FLAG) + if route: + break + else: + break + + xbmcplugin.endOfDirectory( + self.handle, + succeeded=False, + ) - playlist = XbmcPlaylist('auto', context, retry=3) - position, remaining = playlist.get_position() - items = playlist.get_items() - playlist.clear() + playlist_player = context.get_playlist_player() + items = playlist_player.get_items() + if not items and not playlist_player.is_playing(): context.log_warning('Multiple busy dialogs active - ' - 'playlist cleared to avoid Kodi crash') - - if position and items: - path = items[position - 1]['file'] - old_path = ui.pop_property(PLAYLIST_PATH) - old_position = ui.pop_property(PLAYLIST_POSITION) - if (old_position and position == int(old_position) - and old_path and path == old_path): - if remaining: - position += 1 - else: - items = None - - if items: - max_wait_time = 30 - while ui.busy_dialog_active(): - max_wait_time -= 1 - if max_wait_time < 0: - context.log_error('Multiple busy dialogs active - ' - 'extended busy period') - break - context.sleep(1) - - context.log_warning('Multiple busy dialogs active - ' - 'reloading playlist') - - num_items = playlist.add_items(items) - if xbmc.Player().isPlaying(): - return False - if position: - max_wait_time = min(position, num_items) - else: - position = 1 - max_wait_time = num_items - while ui.busy_dialog_active() or playlist.size() < position: - max_wait_time -= 1 - if max_wait_time < 0: - context.log_error('Multiple busy dialogs active - ' - 'unable to restart playback') - break - context.sleep(1) + 'plugin call stopped to avoid Kodi crash') + break + + playlist_player.clear() + context.log_warning('Multiple busy dialogs active - ' + 'playlist cleared to avoid Kodi crash') + + position, remaining = playlist_player.get_position() + if position: + path = items[position - 1]['file'] + old_path = ui.pop_property(PLAYLIST_PATH) + old_position = ui.pop_property(PLAYLIST_POSITION) + if (old_position and position == int(old_position) + and old_path and path == old_path): + if remaining: + position += 1 else: - playlist.play_playlist_item(position) + return False + + max_wait_time = 30 + while ui.busy_dialog_active(): + max_wait_time -= 1 + if max_wait_time < 0: + context.log_error('Multiple busy dialogs active - ' + 'extended busy period') + break + context.sleep(1) + + context.log_warning('Multiple busy dialogs active - ' + 'reloading playlist') + + num_items = playlist_player.add_items(items) + if playlist_player.is_playing(): return False + if position: + max_wait_time = min(position, num_items) + else: + position = 1 + max_wait_time = num_items + + while ui.busy_dialog_active() or playlist_player.size() < position: + max_wait_time -= 1 + if max_wait_time < 0: + context.log_error('Multiple busy dialogs active - ' + 'unable to restart playback') + break + context.sleep(1) + else: + playlist_player.play_playlist_item(position) + else: + return False + if ui.get_property(PLUGIN_SLEEPING): context.wakeup(PLUGIN_WAKEUP) @@ -145,7 +161,6 @@ def run(self, provider, context, focused=None): provider.run_wizard(context) try: - route = ui.pop_property(REROUTE_PATH) if route: function_cache = context.get_function_cache() result, options = function_cache.run( @@ -188,26 +203,21 @@ def run(self, provider, context, focused=None): uri = result.get_uri() if result.playable: - ui = context.get_ui() - if not context.is_plugin_path(uri) and ui.busy_dialog_active(): - ui.set_property(BUSY_FLAG) - playlist = XbmcPlaylist('auto', context) - position, _ = playlist.get_position() - items = playlist.get_items() - if position and items: - ui.set_property(PLAYLIST_PATH, - items[position - 1]['file']) - ui.set_property(PLAYLIST_POSITION, str(position)) - item = self._PLAY_ITEM_MAP[result.__class__.__name__]( context, result, show_fanart=context.get_settings().fanart_selection(), ) - result = True - xbmcplugin.setResolvedUrl(self.handle, - succeeded=result, - listitem=item) + result = xbmcplugin.addDirectoryItem(self.handle, + url=uri, + listitem=item) + if route: + playlist_player = context.get_playlist_player() + playlist_player.play_item(item=uri, listitem=item) + else: + xbmcplugin.setResolvedUrl(self.handle, + succeeded=result, + listitem=item) elif uri.startswith('script://'): uri = uri[len('script://'):] @@ -247,4 +257,8 @@ def run(self, provider, context, focused=None): updateListing=update_listing, cacheToDisc=cache_to_disc, ) + container = ui.pop_property(CONTAINER_ID) + position = ui.pop_property(CONTAINER_POSITION) + if container and position: + context.send_notification(CONTAINER_FOCUS, [container, position]) return succeeded diff --git a/resources/lib/youtube_plugin/kodion/sql_store/storage.py b/resources/lib/youtube_plugin/kodion/sql_store/storage.py index 0425dbc56..1b49ec05e 100644 --- a/resources/lib/youtube_plugin/kodion/sql_store/storage.py +++ b/resources/lib/youtube_plugin/kodion/sql_store/storage.py @@ -17,7 +17,7 @@ from threading import Lock from traceback import format_stack -from ..logger import log_error +from ..logger import log_warning, log_error from ..utils.datetime_parser import fromtimestamp, since_epoch from ..utils.methods import make_dirs @@ -232,12 +232,16 @@ def _open(self): isolation_level=None) break except (sqlite3.Error, sqlite3.OperationalError) as exc: - log_error('SQLStorage._open - {exc}:\n{details}'.format( + msg = 'SQLStorage._open - {exc}:\n{details}'.format( exc=exc, details=''.join(format_stack()) - )) - if isinstance(exc, sqlite3.Error): + ) + if isinstance(exc, sqlite3.OperationalError): + log_warning(msg) + time.sleep(0.1) + else: + log_error(msg) return False - time.sleep(0.1) + else: return False @@ -305,12 +309,15 @@ def _execute(cursor, query, values=None, many=False, script=False): return cursor.executescript(query) return cursor.execute(query, values) except (sqlite3.Error, sqlite3.OperationalError) as exc: - log_error('SQLStorage._execute - {exc}:\n{details}'.format( + msg = 'SQLStorage._execute - {exc}:\n{details}'.format( exc=exc, details=''.join(format_stack()) - )) - if isinstance(exc, sqlite3.Error): + ) + if isinstance(exc, sqlite3.OperationalError): + log_warning(msg) + time.sleep(0.1) + else: + log_error(msg) return [] - time.sleep(0.1) return [] def _optimize_file_size(self, defer=False): diff --git a/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_context_ui.py b/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_context_ui.py index 3145cf044..ec9d1a96e 100644 --- a/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_context_ui.py +++ b/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_context_ui.py @@ -218,16 +218,20 @@ def new_line(value=1, cr_before=0, cr_after=0): '[CR]' * cr_after, )) - def set_focus_next_item(self): - list_id = xbmcgui.Window(xbmcgui.getCurrentWindowId()).getFocusId() + @staticmethod + def set_focus_next_item(): + container = xbmc.getInfoLabel('System.CurrentControlId') + position = xbmc.getInfoLabel('Container.CurrentItem') try: - position = xbmc.getInfoLabel('Container.Position') - next_position = int(position) + 1 - self._context.execute('SetFocus({list_id},{position})'.format( - list_id=list_id, position=next_position - )) + position = int(position) + 1 except ValueError: - pass + return + xbmc.executebuiltin( + 'SetFocus({container},{position},absolute)'.format( + container=container, + position=position + ) + ) @staticmethod def busy_dialog_active(): diff --git a/resources/lib/youtube_plugin/youtube/helper/stream_info.py b/resources/lib/youtube_plugin/youtube/helper/stream_info.py index 7d3a03fce..cb90f39c3 100644 --- a/resources/lib/youtube_plugin/youtube/helper/stream_info.py +++ b/resources/lib/youtube_plugin/youtube/helper/stream_info.py @@ -1805,8 +1805,9 @@ def _process_stream_data(self, stream_data, default_lang_code='und'): codec = 'vp9' elif codec.startswith('dts'): codec = 'dts' - if codec not in stream_features or codec not in isa_capabilities: + if codec not in isa_capabilities: continue + preferred_codec = codec in stream_features media_type, container = mime_type.split('/') bitrate = stream.get('bitrate', 0) @@ -1958,6 +1959,7 @@ def _process_stream_data(self, stream_data, default_lang_code='und'): 'container': container, 'codecs': codecs, 'codec': codec, + 'preferred_codec': preferred_codec, 'id': itag, 'width': width, 'height': height, @@ -1995,11 +1997,13 @@ def _stream_sort(stream): return (1,) return ( + - stream['preferred_codec'], - stream['height'], - stream['fps'], - stream['hdr'], - stream['biasedBitrate'], ) if stream['mediaType'] == 'video' else ( + - stream['preferred_codec'], - stream['channels'], - stream['biasedBitrate'], ) diff --git a/resources/lib/youtube_plugin/youtube/helper/utils.py b/resources/lib/youtube_plugin/youtube/helper/utils.py index b492a07de..96878d12e 100644 --- a/resources/lib/youtube_plugin/youtube/helper/utils.py +++ b/resources/lib/youtube_plugin/youtube/helper/utils.py @@ -432,7 +432,7 @@ def update_video_infos(provider, context, video_id_dict, media_item = video_id_dict[video_id] media_item.set_mediatype( CONTENT.AUDIO_TYPE - if audio_only or isinstance(media_item, AudioItem) else + if isinstance(media_item, AudioItem) else CONTENT.VIDEO_TYPE ) @@ -855,12 +855,16 @@ def update_play_info(provider, context, video_id, media_item, video_stream, media_item.set_headers(video_stream['headers']) # set _uses_isa - if media_item.live: - media_item.set_isa(settings.use_isa_live_streams()) - elif media_item.use_hls() or media_item.use_mpd(): - media_item.set_isa(settings.use_isa()) + if media_item.use_hls() or media_item.use_mpd(): + if media_item.live: + use_isa = settings.use_isa_live_streams() + else: + use_isa = settings.use_isa() + else: + use_isa = False + media_item.set_isa(use_isa) - if media_item.use_isa(): + if use_isa: license_info = video_stream.get('license_info', {}) license_proxy = license_info.get('proxy', '') license_url = license_info.get('url', '') @@ -1020,13 +1024,13 @@ def get_shelf_index_by_title(context, json_data, shelf_title): def add_related_video_to_playlist(provider, context, client, v3, video_id): - playlist = context.get_video_playlist() + playlist_player = context.get_playlist_player() - if playlist.size() <= 999: + if playlist_player.size() <= 999: a = 0 add_item = None page_token = '' - playlist_items = playlist.get_items() + playlist_items = playlist_player.get_items() while not add_item and a <= 2: a += 1 @@ -1056,7 +1060,7 @@ def add_related_video_to_playlist(provider, context, client, v3, video_id): continue if add_item: - playlist.add(add_item) + playlist_player.add(add_item) break if not page_token: diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_play.py b/resources/lib/youtube_plugin/youtube/helper/yt_play.py index 1da0a13ae..86790565b 100644 --- a/resources/lib/youtube_plugin/youtube/helper/yt_play.py +++ b/resources/lib/youtube_plugin/youtube/helper/yt_play.py @@ -18,9 +18,12 @@ from ..youtube_exceptions import YouTubeException from ...kodion.compatibility import urlencode, urlunsplit from ...kodion.constants import ( + BUSY_FLAG, PATHS, PLAYBACK_INIT, PLAYER_DATA, + PLAYLIST_PATH, + PLAYLIST_POSITION, PLAY_FORCE_AUDIO, PLAY_PROMPT_QUALITY, PLAY_PROMPT_SUBTITLES, @@ -49,6 +52,7 @@ def _play_stream(provider, context): incognito = params.get('incognito', False) screensaver = params.get('screensaver', False) + audio_only = False is_external = ui.get_property(PLAY_WITH) if ((is_external and settings.alternative_player_web_urls()) or settings.default_player_web_urls()): @@ -59,10 +63,10 @@ def _play_stream(provider, context): ask_for_quality = settings.ask_for_video_quality() if ui.pop_property(PLAY_PROMPT_QUALITY) and not screensaver: ask_for_quality = True - - audio_only = settings.audio_only() - if ui.pop_property(PLAY_FORCE_AUDIO): + elif ui.pop_property(PLAY_FORCE_AUDIO): audio_only = True + else: + audio_only = settings.audio_only() try: streams = client.get_streams(context, @@ -170,8 +174,8 @@ def _play_playlist(provider, context): videos = [] params = context.get_params() - player = context.get_video_player() - player.stop() + playlist_player = context.get_playlist_player() + playlist_player.stop() action = params.get('action') playlist_ids = params.get('playlist_ids') @@ -242,16 +246,15 @@ def _play_playlist(provider, context): return videos # clear the playlist - playlist = context.get_video_playlist() - playlist.clear() - playlist.unshuffle() + playlist_player.clear() + playlist_player.unshuffle() # check if we have a video as starting point for the playlist video_id = params.get('video_id') playlist_position = None if video_id else 0 # add videos to playlist for idx, video in enumerate(videos): - playlist.add(video) + playlist_player.add(video) if playlist_position is None and video.video_id == video_id: playlist_position = idx @@ -264,7 +267,7 @@ def _play_playlist(provider, context): if action == 'queue': return videos, options if context.get_handle() == -1 or action == 'play': - player.play(playlist_index=playlist_position) + playlist_player.play(playlist_index=playlist_position) return False return videos[playlist_position], options @@ -292,15 +295,13 @@ def _play_channel_live(provider, context): except IndexError: return False - player = context.get_video_player() - player.stop() - - playlist = context.get_video_playlist() - playlist.clear() - playlist.add(video_item) + playlist_player = context.get_playlist_player() + playlist_player.stop() + playlist_player.clear() + playlist_player.add(video_item) if context.get_handle() == -1: - player.play(playlist_index=0) + playlist_player.play(playlist_index=0) return False return video_item @@ -311,8 +312,9 @@ def process(provider, context, **_kwargs): params = context.get_params() param_keys = params.keys() - if ({'channel_id', 'playlist_id', 'playlist_ids', 'video_id'} - .isdisjoint(param_keys)): + if {'channel_id', 'playlist_id', 'playlist_ids', 'video_id'}.isdisjoint( + param_keys + ): listitem_path = context.get_listitem_info('FileNameAndPath') if context.is_plugin_path(listitem_path, PATHS.PLAY): video_id = find_video_id(listitem_path) @@ -343,10 +345,31 @@ def process(provider, context, **_kwargs): if force_play: context.execute('Action(Play)') return False + + if context.get_handle() == -1: + context.execute('PlayMedia({0})'.format( + context.create_uri(('play',), params) + )) + return False + + ui.set_property(BUSY_FLAG) + playlist_player = context.get_playlist_player() + position, _ = playlist_player.get_position() + items = playlist_player.get_items() + ui.clear_property(SERVER_POST_START) context.wakeup(SERVER_WAKEUP, timeout=5) media_item = _play_stream(provider, context) ui.set_property(SERVER_POST_START) + + if media_item: + if position and items: + ui.set_property(PLAYLIST_PATH, + items[position - 1]['file']) + ui.set_property(PLAYLIST_POSITION, str(position)) + else: + ui.clear_property(BUSY_FLAG) + return media_item if playlist_id or 'playlist_ids' in params: diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_setup_wizard.py b/resources/lib/youtube_plugin/youtube/helper/yt_setup_wizard.py index d50d80ff4..0758b3230 100644 --- a/resources/lib/youtube_plugin/youtube/helper/yt_setup_wizard.py +++ b/resources/lib/youtube_plugin/youtube/helper/yt_setup_wizard.py @@ -321,9 +321,9 @@ def process_default_settings(context, step, steps, **_kwargs): settings.stream_select(4 if settings.ask_for_video_quality() else 3) settings.set_subtitle_download(False) if current_system_version.compatible(21, 0): - settings.live_stream_type(3) - else: settings.live_stream_type(2) + else: + settings.live_stream_type(1) if not xbmcvfs.exists('special://profile/playercorefactory.xml'): settings.default_player_web_urls(False) if settings.cache_size() < 20: diff --git a/resources/settings.xml b/resources/settings.xml index d01bd58dc..d9c846891 100644 --- a/resources/settings.xml +++ b/resources/settings.xml @@ -688,7 +688,7 @@ 0 - 3 + 2 @@ -706,7 +706,7 @@ 0 - 0 + 1