From 5d5e64597e2ece51b21c88ebedc4be460504758c Mon Sep 17 00:00:00 2001 From: "bossanova808@gmail.com" Date: Mon, 22 Sep 2025 13:51:21 +1000 Subject: [PATCH 01/14] Recent changes --- resources/lib/playback.py | 34 ---------------------------- resources/lib/player.py | 36 ++++++++++++++++++++---------- resources/lib/store.py | 7 +++++- resources/lib/switchback_plugin.py | 22 +++++++++++++----- 4 files changed, 46 insertions(+), 53 deletions(-) delete mode 100644 resources/lib/playback.py diff --git a/resources/lib/playback.py b/resources/lib/playback.py deleted file mode 100644 index 3b48068..0000000 --- a/resources/lib/playback.py +++ /dev/null @@ -1,34 +0,0 @@ -import json -from dataclasses import dataclass - - -@dataclass -class Playback: - """ - Stores whatever data we can grab about a Kodi Playback so that we can display it nicely in the Switchback list - """ - file:str = None - path:str = None - type:str = None # episode, movie, video, song - source:str = None # kodi_library, pvr_live, media_file - dbid:int = None - tvshowdbid: int = None - title:str = None - thumbnail:str = None - fanart:str = None - poster:str = None - year:int = None - showtitle:str = None - season:int = None - episode:int = None - resumetime:float = None - totaltime:float = None - duration:float = None - artist:str = None - album:str = None - tracknumber:int = None - channelname: str = None - channelnumberlabel:str = None - channelgroup:str = None - - diff --git a/resources/lib/player.py b/resources/lib/player.py index 67c0d55..6daedc9 100644 --- a/resources/lib/player.py +++ b/resources/lib/player.py @@ -1,7 +1,11 @@ -from bossanova808.utilities import * -from resources.lib.store import Store +import xbmc + +from bossanova808.constants import HOME_WINDOW +from bossanova808.logger import Logger from bossanova808.playback import Playback +from resources.lib.store import Store + class KodiPlayer(xbmc.Player): """ @@ -27,26 +31,27 @@ def onAVStarted(self): # If the current playback was Switchback-triggered from a Kodi ListItem (i.e. not PVR, see hack in switchback_plugin.py), # retrieve the previously recorded Playback details from the list. Set the Home Window properties that have not yet been set. if item.getProperty('Switchback') or HOME_WINDOW.getProperty('Switchback'): - Logger.debug("Switchback triggered playback, so finding and re-using existing Playback object") + Logger.info("Switchback triggered playback, so attempting to find and re-use existing Playback object") Logger.debug("Home Window property is:", HOME_WINDOW.getProperty('Switchback')) Logger.debug("ListItem property is:", item.getProperty('Switchback')) path_to_find = HOME_WINDOW.getProperty('Switchback') or item.getProperty('Switchback') or item.getPath() Store.current_playback = Store.switchback.find_playback_by_path(path_to_find) if Store.current_playback: - Logger.debug("Re-using previously stored Playback object:", Store.current_playback) + Logger.info("Found. Re-using previously stored Playback object:", Store.current_playback) # We won't have access to the listitem once playback is finishes, so set a property now so it can be used/cleared in onPlaybackFinished below Store.update_home_window_switchback_property(Store.current_playback.path) return else: Logger.error(f"Switchback triggered playback, but no playback found in the list for this path - this shouldn't happen?!", path_to_find) - # If we got to here, this was not a Switchback-triggered playback, or for some reason we've been unable to find the Playback - # Create a new Playback object and record the details/ - Logger.debug("Not a Switchback playback, or error retrieving previous Playback, so creating a new Playback object to record details") + # If we got to here, this was not a Switchback-triggered playback, or for some reason we've been unable to find the Playback. + # Create a new Playback object and record the details. + Logger.info("Not a Switchback playback, or error retrieving previous Playback, so creating a new Playback object to record details") Store.current_playback = Playback() Store.current_playback.file = file Store.current_playback.update_playback_details_from_listitem(item) + # Playback finished 'naturally' def onPlayBackEnded(self): self.onPlaybackFinished() @@ -65,11 +70,18 @@ def onPlaybackFinished(): Logger.error("onPlaybackFinished with no current playback details available?! ...not recording this playback") return + # if "pvr://" in Store.current_playback.path: + # Logger.warning("PVR not supported due to Kodi bugs with setResolvedUrl/ListItems, so not recording this playback") + # return + + # Force an update of the Switchback list from disk, in case of changes via the plugin side of things. + Store.switchback.load_or_init() + Logger.debug("onPlaybackFinished with Store.current_playback:", Store.current_playback) Logger.debug("onPlaybackFinished with Store.switchback.list: ", Store.switchback.list) # Was this a Switchback-initiated playback? - # (This property was set above in onAVStarted if the ListItem property was set, or explicitly in the PVR HACK! section in switchback_plugin.py) + # (This property was set above in onAVStarted if the ListItem property was set, or explicitly in the PVR HACK! section in switchback_plugin.py this we only need to test for this) switchback_playback = HOME_WINDOW.getProperty('Switchback') # Clear the property if set, now playback has finished HOME_WINDOW.clearProperty('Switchback') @@ -82,7 +94,7 @@ def onPlaybackFinished(): # Default: Browse to the show window = f'videodb://tvshows/titles/{Store.current_playback.tvshowdbid}' # If the user has Flatten TV shows set to 'never' (=0), browse to the actual season - if Store.flatten_tvshows == 0: + if not Store.flatten_tvshows == 0: window += f'/{Store.current_playback.season}' # Else if the user has Flatten TV shows set to 'If Only One Season' and there is indeed more than one season, browse to the actual season elif Store.flatten_tvshows == 1 and Store.current_playback.totalseasons > 1: @@ -103,7 +115,7 @@ def onPlaybackFinished(): playback_to_remove = Store.switchback.find_playback_by_path(Store.current_playback.path) if playback_to_remove: - Logger.debug("Shuffling list order") + Logger.debug("Updating Playback and list order") # Remove it from its current position Store.switchback.list.remove(playback_to_remove) # Update with the current playback times @@ -116,9 +128,9 @@ def onPlaybackFinished(): # Trim the list to the max length Store.switchback.list = Store.switchback.list[0:Store.maximum_list_length] - # Finally, save the updated PlaybackList - Logger.debug("Saving updated Store.switchback.list:", Store.switchback.list) Store.switchback.save_to_file() + Logger.debug("Saved updated Store.switchback.list:", Store.switchback.list) + # & make sure the context menu items are updated Store.update_switchback_context_menu() diff --git a/resources/lib/store.py b/resources/lib/store.py index fcb4a3e..954ef63 100644 --- a/resources/lib/store.py +++ b/resources/lib/store.py @@ -1,6 +1,10 @@ import os -from bossanova808.utilities import * +import xbmcvfs + +from bossanova808.constants import HOME_WINDOW, PROFILE, ADDON +from bossanova808.logger import Logger +from bossanova808.utilities import get_kodi_setting, set_property, clear_property from bossanova808.playback import PlaybackList @@ -53,6 +57,7 @@ def load_config_from_settings(): @staticmethod def load_config_from_kodi_settings(): + # note that this should be an int, not bool - 0 for Never, 1 for 'if only one season, 2 for Always Store.flatten_tvshows = int(get_kodi_setting('videolibrary.flattentvshows')) Logger.info(f"Flatten TV Shows is: {Store.flatten_tvshows}") diff --git a/resources/lib/switchback_plugin.py b/resources/lib/switchback_plugin.py index af93bbf..e718153 100644 --- a/resources/lib/switchback_plugin.py +++ b/resources/lib/switchback_plugin.py @@ -1,10 +1,14 @@ +import sys from urllib.parse import parse_qs +# noinspection PyUnresolvedReferences import xbmc +import xbmcgui import xbmcplugin from resources.lib.store import Store -from bossanova808.utilities import * +from bossanova808.constants import TRANSLATE, ADDON_ICON +from bossanova808.logger import Logger from bossanova808.notify import Notify @@ -43,6 +47,9 @@ def run(args): else: Logger.info("Switchback mode: default - generate 'folder' of items") + # Force an update of the Switchback list from disk, in case of changes via the service side of things. + Store.switchback.load_or_init() + # Switchback mode - easily swap between switchback.list[0] and switchback.list[1] # If there's only one item in the list, then resume playing that item if mode and mode[0] == "switchback": @@ -58,7 +65,7 @@ def run(args): # Notify the user and set properties so we can identify this playback as having been originated from a Switchback Notify.kodi_notification(f"{switchback_to_play.pluginlabel}", 3000, ADDON_ICON) list_item = switchback_to_play.create_list_item_from_playback(offscreen=True) - # (TODO: remove this hack when setResolvedUrl/ListItems are fixed to properly handle PVR links in listitem.path) + # # (TODO: remove this hack when setResolvedUrl/ListItems are fixed to properly handle PVR links in listitem.path) if "pvr://" in switchback_to_play.path: pvr_hack(switchback_to_play.path) else: @@ -75,15 +82,16 @@ def run(args): if index_to_remove: Logger.info(f"Deleting playback {index_to_remove[0]} from Switchback list") Store.switchback.list.remove(Store.switchback.list[int(index_to_remove[0])]) + # Save the updated list and then reload it, just to be sure Store.switchback.save_to_file() + Store.switchback.load_or_init() Store.update_switchback_context_menu() - # Force refresh the list + # Force refresh the Kodi list display Logger.debug("Force refreshing the container, so Kodi immediately displays the updated Switchback list") xbmc.executebuiltin("Container.Refresh") # (TODO: remove this hack when setResolvedUrl/ListItems are fixed to properly handle PVR links in listitem.path) if mode and mode[0] == "pvr_hack": - xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, xbmcgui.ListItem()) path = parsed_arguments.get('path', None)[0] Logger.debug(f"Triggering PVR Playback hack for {path}") pvr_hack(path) @@ -92,10 +100,12 @@ def run(args): else: for index, playback in enumerate(Store.switchback.list[0:Store.maximum_list_length]): list_item = playback.create_list_item_from_playback() - list_item.addContextMenuItems([(LANGUAGE(32004), "RunPlugin(plugin://plugin.switchback?mode=delete&index=" + str(index) + ")")]) + + list_item.addContextMenuItems([(TRANSLATE(32004), "RunPlugin(plugin://plugin.switchback?mode=delete&index=" + str(index) + ")")]) list_item.setProperty('Switchback', playback.path) - # Don't use playback.path here, as list_item may now have the plugin proxy url for PVR live playback # (TODO: remove this hack when setResolvedUrl/ListItems are fixed to properly handle PVR links in listitem.path) + # Don't use playback.path here, use list_item.getPath(), as the path may now have the plugin proxy url for PVR live playback + Logger.debug(f"^^^ Adding directory Item: {list_item.getPath()}") xbmcplugin.addDirectoryItem(plugin_instance, list_item.getPath(), list_item) xbmcplugin.endOfDirectory(plugin_instance, cacheToDisc=False) From 95d5925271a05db6688df472829275802e3b0e53 Mon Sep 17 00:00:00 2001 From: "bossanova808@gmail.com" Date: Mon, 22 Sep 2025 15:52:43 +1000 Subject: [PATCH 02/14] CR --- resources/lib/player.py | 12 ++++++------ resources/lib/store.py | 4 ++-- resources/lib/switchback_plugin.py | 12 +++++++++--- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/resources/lib/player.py b/resources/lib/player.py index 6daedc9..5098df1 100644 --- a/resources/lib/player.py +++ b/resources/lib/player.py @@ -42,8 +42,7 @@ def onAVStarted(self): Store.update_home_window_switchback_property(Store.current_playback.path) return else: - Logger.error(f"Switchback triggered playback, but no playback found in the list for this path - this shouldn't happen?!", path_to_find) - + Logger.error("Switchback triggered playback, but no playback found in the list for this path - this shouldn't happen?!", path_to_find) # If we got to here, this was not a Switchback-triggered playback, or for some reason we've been unable to find the Playback. # Create a new Playback object and record the details. Logger.info("Not a Switchback playback, or error retrieving previous Playback, so creating a new Playback object to record details") @@ -87,16 +86,17 @@ def onPlaybackFinished(): HOME_WINDOW.clearProperty('Switchback') # If we Switchbacked to an episode, force Kodi to browse to the Show/Season - if switchback_playback == 'true': + if switchback_playback: if Store.current_playback.type == "episode": Logger.info(f"Force browsing to tvshow/season of just finished playback") Logger.debug(f'flatten tvshows {Store.flatten_tvshows} totalseasons {Store.current_playback.totalseasons} dbid {Store.current_playback.dbid} tvshowdbid {Store.current_playback.tvshowdbid}') # Default: Browse to the show window = f'videodb://tvshows/titles/{Store.current_playback.tvshowdbid}' - # If the user has Flatten TV shows set to 'never' (=0), browse to the actual season - if not Store.flatten_tvshows == 0: + # 0 = Never flatten → browse to show root + # 1 = If only one season → browse to season only when there are multiple seasons + # 2 = Always flatten → browse to season + if Store.flatten_tvshows == 2: window += f'/{Store.current_playback.season}' - # Else if the user has Flatten TV shows set to 'If Only One Season' and there is indeed more than one season, browse to the actual season elif Store.flatten_tvshows == 1 and Store.current_playback.totalseasons > 1: window += f'/{Store.current_playback.season}' diff --git a/resources/lib/store.py b/resources/lib/store.py index 954ef63..9a61760 100644 --- a/resources/lib/store.py +++ b/resources/lib/store.py @@ -53,11 +53,11 @@ def load_config_from_settings(): Store.save_across_sessions = ADDON.getSettingBool('save_across_sessions') Logger.info(f"Save across sessions is: {Store.save_across_sessions}") Store.enable_context_menu = ADDON.getSettingBool('enable_context_menu') - Logger.info(f"Enbale context menu is: {Store.enable_context_menu}") + Logger.info(f"Enable context menu is: {Store.enable_context_menu}") @staticmethod def load_config_from_kodi_settings(): - # note that this should be an int, not bool - 0 for Never, 1 for 'if only one season, 2 for Always + # Note: this is an int, not a bool — 0 = Never, 1 = 'If only one season', 2 = Always Store.flatten_tvshows = int(get_kodi_setting('videolibrary.flattentvshows')) Logger.info(f"Flatten TV Shows is: {Store.flatten_tvshows}") diff --git a/resources/lib/switchback_plugin.py b/resources/lib/switchback_plugin.py index e718153..f3ae1ca 100644 --- a/resources/lib/switchback_plugin.py +++ b/resources/lib/switchback_plugin.py @@ -68,6 +68,7 @@ def run(args): # # (TODO: remove this hack when setResolvedUrl/ListItems are fixed to properly handle PVR links in listitem.path) if "pvr://" in switchback_to_play.path: pvr_hack(switchback_to_play.path) + return else: list_item.setProperty('Switchback', switchback_to_play.path) xbmcplugin.setResolvedUrl(plugin_instance, True, list_item) @@ -91,8 +92,12 @@ def run(args): xbmc.executebuiltin("Container.Refresh") # (TODO: remove this hack when setResolvedUrl/ListItems are fixed to properly handle PVR links in listitem.path) - if mode and mode[0] == "pvr_hack": - path = parsed_arguments.get('path', None)[0] + elif mode and mode[0] == "pvr_hack": + path_values = parsed_arguments.get('path') + if not path_values: + Logger.error("Missing 'path' parameter for pvr_hack") + return + path = path_values[0] Logger.debug(f"Triggering PVR Playback hack for {path}") pvr_hack(path) @@ -100,8 +105,9 @@ def run(args): else: for index, playback in enumerate(Store.switchback.list[0:Store.maximum_list_length]): list_item = playback.create_list_item_from_playback() - + # Add delete option to this item list_item.addContextMenuItems([(TRANSLATE(32004), "RunPlugin(plugin://plugin.switchback?mode=delete&index=" + str(index) + ")")]) + # For detecting Switchback playbacks (in player.py) list_item.setProperty('Switchback', playback.path) # (TODO: remove this hack when setResolvedUrl/ListItems are fixed to properly handle PVR links in listitem.path) # Don't use playback.path here, use list_item.getPath(), as the path may now have the plugin proxy url for PVR live playback From 4420b4db121f8f2fc5b977b4d246e16e6c9039bc Mon Sep 17 00:00:00 2001 From: "bossanova808@gmail.com" Date: Mon, 22 Sep 2025 16:40:21 +1000 Subject: [PATCH 03/14] CR But things are ver broken right now, sigh --- resources/lib/player.py | 3 ++- resources/lib/switchback_plugin.py | 23 +++++++++++++++-------- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/resources/lib/player.py b/resources/lib/player.py index 5098df1..f8cdc6e 100644 --- a/resources/lib/player.py +++ b/resources/lib/player.py @@ -43,6 +43,7 @@ def onAVStarted(self): return else: Logger.error("Switchback triggered playback, but no playback found in the list for this path - this shouldn't happen?!", path_to_find) + # If we got to here, this was not a Switchback-triggered playback, or for some reason we've been unable to find the Playback. # Create a new Playback object and record the details. Logger.info("Not a Switchback playback, or error retrieving previous Playback, so creating a new Playback object to record details") @@ -88,7 +89,7 @@ def onPlaybackFinished(): # If we Switchbacked to an episode, force Kodi to browse to the Show/Season if switchback_playback: if Store.current_playback.type == "episode": - Logger.info(f"Force browsing to tvshow/season of just finished playback") + Logger.info("Force browsing to tvshow/season of just finished playback") Logger.debug(f'flatten tvshows {Store.flatten_tvshows} totalseasons {Store.current_playback.totalseasons} dbid {Store.current_playback.dbid} tvshowdbid {Store.current_playback.tvshowdbid}') # Default: Browse to the show window = f'videodb://tvshows/titles/{Store.current_playback.tvshowdbid}' diff --git a/resources/lib/switchback_plugin.py b/resources/lib/switchback_plugin.py index f3ae1ca..f28e94f 100644 --- a/resources/lib/switchback_plugin.py +++ b/resources/lib/switchback_plugin.py @@ -72,6 +72,7 @@ def run(args): else: list_item.setProperty('Switchback', switchback_to_play.path) xbmcplugin.setResolvedUrl(plugin_instance, True, list_item) + return except IndexError: Notify.error(TRANSLATE(32007)) @@ -79,10 +80,19 @@ def run(args): # Delete an item from the Switchback list - e.g. if it is not playing back properly from Switchback if mode and mode[0] == "delete": - index_to_remove = parsed_arguments.get('index', None) - if index_to_remove: - Logger.info(f"Deleting playback {index_to_remove[0]} from Switchback list") - Store.switchback.list.remove(Store.switchback.list[int(index_to_remove[0])]) + index_values = parsed_arguments.get('index') + if index_values: + try: + idx = int(index_values[0]) + except (ValueError, TypeError): + Logger.error("Invalid 'index' parameter for delete:", index_values) + return + if 0 <= idx < len(Store.switchback.list): + Logger.info(f"Deleting playback {idx} from Switchback list") + Store.switchback.list.pop(idx) + else: + Logger.error("Index out of range for delete:", idx) + return # Save the updated list and then reload it, just to be sure Store.switchback.save_to_file() Store.switchback.load_or_init() @@ -109,10 +119,7 @@ def run(args): list_item.addContextMenuItems([(TRANSLATE(32004), "RunPlugin(plugin://plugin.switchback?mode=delete&index=" + str(index) + ")")]) # For detecting Switchback playbacks (in player.py) list_item.setProperty('Switchback', playback.path) - # (TODO: remove this hack when setResolvedUrl/ListItems are fixed to properly handle PVR links in listitem.path) - # Don't use playback.path here, use list_item.getPath(), as the path may now have the plugin proxy url for PVR live playback - Logger.debug(f"^^^ Adding directory Item: {list_item.getPath()}") - xbmcplugin.addDirectoryItem(plugin_instance, list_item.getPath(), list_item) + xbmcplugin.addDirectoryItem(plugin_instance, playback.file, list_item) xbmcplugin.endOfDirectory(plugin_instance, cacheToDisc=False) From 99747ed8186a16266dce35f167351f043f3bcaca Mon Sep 17 00:00:00 2001 From: "bossanova808@gmail.com" Date: Fri, 26 Sep 2025 16:01:53 +1000 Subject: [PATCH 04/14] Tidy up @coderabbitai --- .coderabbit.yaml | 3 +++ resources/lib/player.py | 5 ----- 2 files changed, 3 insertions(+), 5 deletions(-) create mode 100644 .coderabbit.yaml diff --git a/.coderabbit.yaml b/.coderabbit.yaml new file mode 100644 index 0000000..94035cc --- /dev/null +++ b/.coderabbit.yaml @@ -0,0 +1,3 @@ +reviews: + review_instructions: | + Always present code changes in before/after format for easy copy-pasting. Never show diffs. diff --git a/resources/lib/player.py b/resources/lib/player.py index f8cdc6e..71d4647 100644 --- a/resources/lib/player.py +++ b/resources/lib/player.py @@ -109,11 +109,6 @@ def onPlaybackFinished(): # This rather long-windeed approach is used to keep ALL the details recorded from the original playback # (in case they don't make it through when the playback is Switchback initiated - as sometimes seems to be the case) - # for previous_playback in Store.switchback.list: - # if previous_playback.path == Store.current_playback.path: - # playback_to_remove = previous_playback - # break - playback_to_remove = Store.switchback.find_playback_by_path(Store.current_playback.path) if playback_to_remove: Logger.debug("Updating Playback and list order") From b2f2e87f9185d8f9fd54a73b9800cc02f99065e0 Mon Sep 17 00:00:00 2001 From: "bossanova808@gmail.com" Date: Fri, 26 Sep 2025 17:26:28 +1000 Subject: [PATCH 05/14] Update switchback_plugin.py --- resources/lib/switchback_plugin.py | 55 ++++++------------------------ 1 file changed, 11 insertions(+), 44 deletions(-) diff --git a/resources/lib/switchback_plugin.py b/resources/lib/switchback_plugin.py index f28e94f..6753d92 100644 --- a/resources/lib/switchback_plugin.py +++ b/resources/lib/switchback_plugin.py @@ -3,35 +3,14 @@ # noinspection PyUnresolvedReferences import xbmc -import xbmcgui import xbmcplugin from resources.lib.store import Store -from bossanova808.constants import TRANSLATE, ADDON_ICON +from bossanova808.constants import TRANSLATE from bossanova808.logger import Logger from bossanova808.notify import Notify -# PVR HACK! -# ListItems and setResolvedUrl does not handle PVR links properly, see https://forum.kodi.tv/showthread.php?tid=381623 -# (TODO: remove this hack when setResolvedUrl/ListItems are fixed to properly handle PVR links in listitem.path) -def pvr_hack(path): - # Kodi is jonesing for one of these, so give it the sugar it needs, see: https://forum.kodi.tv/showthread.php?tid=381623&pid=3232778#pid3232778 - xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, xbmcgui.ListItem()) - xbmc.PlayList(xbmc.PLAYLIST_VIDEO).clear() - # Get the full details from our stored playback - # pvr_playback = Store.switchback.find_playback_by_path(path) - builtin = f'PlayMedia("{path}"' - if "pvr://recordings" in path: - builtin += f',resume' - builtin += ")" - Logger.debug("Work around PVR links not being handled by ListItem/setResolvedUrl - use PlayMedia:", builtin) - # No ListItem to set a property on here, so set on the Home Window instead - Store.update_home_window_switchback_property(path) - xbmc.executebuiltin(builtin) - - -# noinspection PyUnusedLocal def run(args): Logger.start("(Plugin)") Store() @@ -47,7 +26,7 @@ def run(args): else: Logger.info("Switchback mode: default - generate 'folder' of items") - # Force an update of the Switchback list from disk, in case of changes via the service side of things. + # Always force an update of the Switchback list from disk, in case of changes via the service side of things. Store.switchback.load_or_init() # Switchback mode - easily swap between switchback.list[0] and switchback.list[1] @@ -57,22 +36,20 @@ def run(args): if len(Store.switchback.list) == 1: switchback_to_play = Store.switchback.list[0] Logger.info(f"Playing Switchback[0] - path [{Store.switchback.list[0].path}]") + Logger.info(f"Playing Switchback[0] - file [{Store.switchback.list[0].file}]") else: switchback_to_play = Store.switchback.list[1] Logger.info(f"Playing Switchback[1] - path [{Store.switchback.list[1].path}]") - Logger.info(f"{switchback_to_play.pluginlabel}") + Logger.info(f"Playing Switchback[1] - file [{Store.switchback.list[1].file}]") + + Logger.info(f"Switching back to: {switchback_to_play.pluginlabel} - path [{switchback_to_play.path}] file [{switchback_to_play.file}]") # Notify the user and set properties so we can identify this playback as having been originated from a Switchback - Notify.kodi_notification(f"{switchback_to_play.pluginlabel}", 3000, ADDON_ICON) + Notify.kodi_notification(f"{switchback_to_play.pluginlabel}", 3000, switchback_to_play.poster) list_item = switchback_to_play.create_list_item_from_playback(offscreen=True) - # # (TODO: remove this hack when setResolvedUrl/ListItems are fixed to properly handle PVR links in listitem.path) - if "pvr://" in switchback_to_play.path: - pvr_hack(switchback_to_play.path) - return - else: - list_item.setProperty('Switchback', switchback_to_play.path) - xbmcplugin.setResolvedUrl(plugin_instance, True, list_item) - return + list_item.setProperty('Switchback', switchback_to_play.path) + xbmcplugin.setResolvedUrl(plugin_instance, True, list_item) + return except IndexError: Notify.error(TRANSLATE(32007)) @@ -101,16 +78,6 @@ def run(args): Logger.debug("Force refreshing the container, so Kodi immediately displays the updated Switchback list") xbmc.executebuiltin("Container.Refresh") - # (TODO: remove this hack when setResolvedUrl/ListItems are fixed to properly handle PVR links in listitem.path) - elif mode and mode[0] == "pvr_hack": - path_values = parsed_arguments.get('path') - if not path_values: - Logger.error("Missing 'path' parameter for pvr_hack") - return - path = path_values[0] - Logger.debug(f"Triggering PVR Playback hack for {path}") - pvr_hack(path) - # Default mode - show the whole Switchback List (each of which has a context menu option to delete itself) else: for index, playback in enumerate(Store.switchback.list[0:Store.maximum_list_length]): @@ -119,7 +86,7 @@ def run(args): list_item.addContextMenuItems([(TRANSLATE(32004), "RunPlugin(plugin://plugin.switchback?mode=delete&index=" + str(index) + ")")]) # For detecting Switchback playbacks (in player.py) list_item.setProperty('Switchback', playback.path) - xbmcplugin.addDirectoryItem(plugin_instance, playback.file, list_item) + xbmcplugin.addDirectoryItem(plugin_instance, playback.file if playback.source != "addon" else playback.path, list_item) xbmcplugin.endOfDirectory(plugin_instance, cacheToDisc=False) From 42296667bc300dd0a9306fb78095c93fd7e1ffca Mon Sep 17 00:00:00 2001 From: "bossanova808@gmail.com" Date: Fri, 26 Sep 2025 18:14:42 +1000 Subject: [PATCH 06/14] CR --- .coderabbit.yaml | 2 +- resources/lib/player.py | 2 +- resources/lib/switchback_plugin.py | 24 ++++++++++++------------ 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.coderabbit.yaml b/.coderabbit.yaml index 94035cc..ded150c 100644 --- a/.coderabbit.yaml +++ b/.coderabbit.yaml @@ -1,3 +1,3 @@ reviews: review_instructions: | - Always present code changes in before/after format for easy copy-pasting. Never show diffs. + CRITICAL: Always present code changes in before/after format for easy copy-pasting. NEVER show diffs under any circumstances. Use clear "Before:" and "After:" code blocks only. diff --git a/resources/lib/player.py b/resources/lib/player.py index 71d4647..1ab72b6 100644 --- a/resources/lib/player.py +++ b/resources/lib/player.py @@ -28,7 +28,7 @@ def onAVStarted(self): item = self.getPlayingItem() file = self.getPlayingFile() - # If the current playback was Switchback-triggered from a Kodi ListItem (i.e. not PVR, see hack in switchback_plugin.py), + # If the current playback was Switchback-triggered from a Kodi ListItem, # retrieve the previously recorded Playback details from the list. Set the Home Window properties that have not yet been set. if item.getProperty('Switchback') or HOME_WINDOW.getProperty('Switchback'): Logger.info("Switchback triggered playback, so attempting to find and re-use existing Playback object") diff --git a/resources/lib/switchback_plugin.py b/resources/lib/switchback_plugin.py index 6753d92..58b82c1 100644 --- a/resources/lib/switchback_plugin.py +++ b/resources/lib/switchback_plugin.py @@ -6,13 +6,14 @@ import xbmcplugin from resources.lib.store import Store -from bossanova808.constants import TRANSLATE +from bossanova808.constants import TRANSLATE, HOME_WINDOW from bossanova808.logger import Logger from bossanova808.notify import Notify -def run(args): +def run(): Logger.start("(Plugin)") + # This also forces an update of the Switchback list from disk, in case of changes via the service side of things. Store() plugin_instance = int(sys.argv[1]) @@ -26,9 +27,6 @@ def run(args): else: Logger.info("Switchback mode: default - generate 'folder' of items") - # Always force an update of the Switchback list from disk, in case of changes via the service side of things. - Store.switchback.load_or_init() - # Switchback mode - easily swap between switchback.list[0] and switchback.list[1] # If there's only one item in the list, then resume playing that item if mode and mode[0] == "switchback": @@ -44,16 +42,18 @@ def run(args): Logger.info(f"Switching back to: {switchback_to_play.pluginlabel} - path [{switchback_to_play.path}] file [{switchback_to_play.file}]") - # Notify the user and set properties so we can identify this playback as having been originated from a Switchback - Notify.kodi_notification(f"{switchback_to_play.pluginlabel}", 3000, switchback_to_play.poster) - list_item = switchback_to_play.create_list_item_from_playback(offscreen=True) - list_item.setProperty('Switchback', switchback_to_play.path) - xbmcplugin.setResolvedUrl(plugin_instance, True, list_item) - return - except IndexError: Notify.error(TRANSLATE(32007)) Logger.error("No Switchback found to play") + return + + # Notify the user and set properties so we can identify this playback as having been originated from a Switchback + Notify.kodi_notification(f"{switchback_to_play.pluginlabel}", 3000, switchback_to_play.poster) + list_item = switchback_to_play.create_list_item_from_playback(offscreen=True) + list_item.setProperty('Switchback', switchback_to_play.path) + HOME_WINDOW.setProperty('Switchback', switchback_to_play.path) + xbmcplugin.setResolvedUrl(plugin_instance, True, list_item) + return # Delete an item from the Switchback list - e.g. if it is not playing back properly from Switchback if mode and mode[0] == "delete": From 8dd9cb51287d28e1cd8d6f47eabc4f5159f1a9b6 Mon Sep 17 00:00:00 2001 From: "bossanova808@gmail.com" Date: Fri, 26 Sep 2025 18:34:22 +1000 Subject: [PATCH 07/14] CR --- resources/lib/switchback_plugin.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/resources/lib/switchback_plugin.py b/resources/lib/switchback_plugin.py index 58b82c1..5c33b3b 100644 --- a/resources/lib/switchback_plugin.py +++ b/resources/lib/switchback_plugin.py @@ -23,7 +23,7 @@ def run(): Logger.debug(parsed_arguments) mode = parsed_arguments.get('mode', None) if mode: - Logger.info(f"Switchback mode: {mode}") + Logger.info(f"Switchback mode: {mode[0]}") else: Logger.info("Switchback mode: default - generate 'folder' of items") @@ -70,13 +70,16 @@ def run(): else: Logger.error("Index out of range for delete:", idx) return - # Save the updated list and then reload it, just to be sure - Store.switchback.save_to_file() - Store.switchback.load_or_init() - Store.update_switchback_context_menu() - # Force refresh the Kodi list display - Logger.debug("Force refreshing the container, so Kodi immediately displays the updated Switchback list") - xbmc.executebuiltin("Container.Refresh") + else: + Logger.error("Missing 'index' parameter for delete") + return + + # Save the updated list and then reload it, just to be sure + Store.switchback.save_to_file() + Store.switchback.load_or_init() + Store.update_switchback_context_menu() + Logger.debug("Force refreshing the container, so Kodi immediately displays the updated Switchback list") + xbmc.executebuiltin("Container.Refresh") # Default mode - show the whole Switchback List (each of which has a context menu option to delete itself) else: From 717350a7d6db429a7b7a45cb53b72103e9a34f08 Mon Sep 17 00:00:00 2001 From: "bossanova808@gmail.com" Date: Fri, 26 Sep 2025 18:50:20 +1000 Subject: [PATCH 08/14] CR --- resources/lib/switchback_plugin.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/resources/lib/switchback_plugin.py b/resources/lib/switchback_plugin.py index 5c33b3b..ea1ccbe 100644 --- a/resources/lib/switchback_plugin.py +++ b/resources/lib/switchback_plugin.py @@ -22,14 +22,15 @@ def run(): parsed_arguments = parse_qs(sys.argv[2][1:]) Logger.debug(parsed_arguments) mode = parsed_arguments.get('mode', None) - if mode: - Logger.info(f"Switchback mode: {mode[0]}") + modes = set([m.strip() for m in mode[0].split(",") if m.strip()]) if mode else set() + if modes: + Logger.info(f"Switchback mode: {mode}") else: Logger.info("Switchback mode: default - generate 'folder' of items") # Switchback mode - easily swap between switchback.list[0] and switchback.list[1] # If there's only one item in the list, then resume playing that item - if mode and mode[0] == "switchback": + if "switchback" in modes: try: if len(Store.switchback.list) == 1: switchback_to_play = Store.switchback.list[0] @@ -51,12 +52,11 @@ def run(): Notify.kodi_notification(f"{switchback_to_play.pluginlabel}", 3000, switchback_to_play.poster) list_item = switchback_to_play.create_list_item_from_playback(offscreen=True) list_item.setProperty('Switchback', switchback_to_play.path) - HOME_WINDOW.setProperty('Switchback', switchback_to_play.path) + Store.update_home_window_switchback_property(switchback_to_play.path) xbmcplugin.setResolvedUrl(plugin_instance, True, list_item) - return # Delete an item from the Switchback list - e.g. if it is not playing back properly from Switchback - if mode and mode[0] == "delete": + if "delete" in modes: index_values = parsed_arguments.get('index') if index_values: try: @@ -90,7 +90,6 @@ def run(): # For detecting Switchback playbacks (in player.py) list_item.setProperty('Switchback', playback.path) xbmcplugin.addDirectoryItem(plugin_instance, playback.file if playback.source != "addon" else playback.path, list_item) - xbmcplugin.endOfDirectory(plugin_instance, cacheToDisc=False) # And we're done... From 79f371aff3bcbab9fb4c6ffd26dda91978dcd43d Mon Sep 17 00:00:00 2001 From: "bossanova808@gmail.com" Date: Fri, 26 Sep 2025 18:58:26 +1000 Subject: [PATCH 09/14] Update switchback_plugin.py --- resources/lib/switchback_plugin.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/resources/lib/switchback_plugin.py b/resources/lib/switchback_plugin.py index ea1ccbe..ac47588 100644 --- a/resources/lib/switchback_plugin.py +++ b/resources/lib/switchback_plugin.py @@ -54,6 +54,8 @@ def run(): list_item.setProperty('Switchback', switchback_to_play.path) Store.update_home_window_switchback_property(switchback_to_play.path) xbmcplugin.setResolvedUrl(plugin_instance, True, list_item) + Logger.stop("(Plugin)") + return # Delete an item from the Switchback list - e.g. if it is not playing back properly from Switchback if "delete" in modes: From debcf6f2610ab61781c6d6aa7674995dba1b00a1 Mon Sep 17 00:00:00 2001 From: "bossanova808@gmail.com" Date: Sat, 27 Sep 2025 09:48:11 +1000 Subject: [PATCH 10/14] (WIP) Restore PVR hack to make sure PVR live plays with proper controls --- plugin.py | 4 +- resources/lib/player.py | 2 +- resources/lib/switchback_plugin.py | 77 +++++++++++++++++++++++------- 3 files changed, 61 insertions(+), 22 deletions(-) diff --git a/plugin.py b/plugin.py index b99c35c..73d39f6 100644 --- a/plugin.py +++ b/plugin.py @@ -1,9 +1,7 @@ -import sys - from bossanova808 import exception_logger from resources.lib import switchback_plugin if __name__ == "__main__": with exception_logger.log_exception(): - switchback_plugin.run(sys.argv[1:]) + switchback_plugin.run() diff --git a/resources/lib/player.py b/resources/lib/player.py index 1ab72b6..3b59c84 100644 --- a/resources/lib/player.py +++ b/resources/lib/player.py @@ -49,7 +49,7 @@ def onAVStarted(self): Logger.info("Not a Switchback playback, or error retrieving previous Playback, so creating a new Playback object to record details") Store.current_playback = Playback() Store.current_playback.file = file - Store.current_playback.update_playback_details_from_listitem(item) + Store.current_playback.update_playback_details(item) # Playback finished 'naturally' def onPlayBackEnded(self): diff --git a/resources/lib/switchback_plugin.py b/resources/lib/switchback_plugin.py index ac47588..b2fc1eb 100644 --- a/resources/lib/switchback_plugin.py +++ b/resources/lib/switchback_plugin.py @@ -4,13 +4,30 @@ # noinspection PyUnresolvedReferences import xbmc import xbmcplugin +import xbmcgui from resources.lib.store import Store -from bossanova808.constants import TRANSLATE, HOME_WINDOW +from bossanova808.constants import TRANSLATE from bossanova808.logger import Logger from bossanova808.notify import Notify +# PVR HACK! +# Needed to trigger live PVR playback with proper PVR controls. +# See https://forum.kodi.tv/showthread.php?tid=381623 +def pvr_hack(path): + xbmc.PlayList(xbmc.PLAYLIST_VIDEO).clear() + # Kodi is jonesing for one of these, so give it the sugar it needs, see: https://forum.kodi.tv/showthread.php?tid=381623&pid=3232778#pid3232778 + xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, xbmcgui.ListItem()) + # Get the full details from our stored playback + # pvr_playback = Store.switchback.find_playback_by_path(path) + builtin = f'PlayMedia("{path}")' + Logger.debug("Work around PVR links not being handled by ListItem/setResolvedUrl - use PlayMedia instead:", builtin) + # No ListItem to set a property on here, so set on the Home Window instead + Store.update_home_window_switchback_property(path) + xbmc.executebuiltin(builtin) + + def run(): Logger.start("(Plugin)") # This also forces an update of the Switchback list from disk, in case of changes via the service side of things. @@ -31,34 +48,41 @@ def run(): # Switchback mode - easily swap between switchback.list[0] and switchback.list[1] # If there's only one item in the list, then resume playing that item if "switchback" in modes: - try: - if len(Store.switchback.list) == 1: - switchback_to_play = Store.switchback.list[0] - Logger.info(f"Playing Switchback[0] - path [{Store.switchback.list[0].path}]") - Logger.info(f"Playing Switchback[0] - file [{Store.switchback.list[0].file}]") - else: - switchback_to_play = Store.switchback.list[1] - Logger.info(f"Playing Switchback[1] - path [{Store.switchback.list[1].path}]") - Logger.info(f"Playing Switchback[1] - file [{Store.switchback.list[1].file}]") - - Logger.info(f"Switching back to: {switchback_to_play.pluginlabel} - path [{switchback_to_play.path}] file [{switchback_to_play.file}]") - except IndexError: + # First, determine what to play, if anything... + if not Store.switchback.list: Notify.error(TRANSLATE(32007)) Logger.error("No Switchback found to play") return - # Notify the user and set properties so we can identify this playback as having been originated from a Switchback + if len(Store.switchback.list) == 1: + switchback_to_play = Store.switchback.list[0] + Logger.debug("Switchback to index 0") + else: + switchback_to_play = Store.switchback.list[1] + Logger.debug("Switchback to index 1") + + # We know what to play... + Logger.info(f"Switchback! Switching back to: {switchback_to_play.pluginlabel}") + Logger.debug(f"Path: [{switchback_to_play.path}]") + Logger.debug(f"File: [{switchback_to_play.file}]") Notify.kodi_notification(f"{switchback_to_play.pluginlabel}", 3000, switchback_to_play.poster) - list_item = switchback_to_play.create_list_item_from_playback(offscreen=True) + + # Short circuit here if PVR, see pvr_hack above. + if 'pvr://channels' in switchback_to_play.path: + pvr_hack(switchback_to_play.path) + return + + # Normal path for everything else + list_item = switchback_to_play.create_list_item_from_playback() list_item.setProperty('Switchback', switchback_to_play.path) - Store.update_home_window_switchback_property(switchback_to_play.path) + # Store.update_home_window_switchback_property(switchback_to_play.path) xbmcplugin.setResolvedUrl(plugin_instance, True, list_item) Logger.stop("(Plugin)") return # Delete an item from the Switchback list - e.g. if it is not playing back properly from Switchback - if "delete" in modes: + elif "delete" in modes: index_values = parsed_arguments.get('index') if index_values: try: @@ -83,6 +107,13 @@ def run(): Logger.debug("Force refreshing the container, so Kodi immediately displays the updated Switchback list") xbmc.executebuiltin("Container.Refresh") + # See pvr_hack(path) above + elif "pvr_hack" in modes: + path = parsed_arguments.get('path', None)[0] + Logger.debug(f"Triggering PVR Playback hack for {path}") + pvr_hack(path) + return + # Default mode - show the whole Switchback List (each of which has a context menu option to delete itself) else: for index, playback in enumerate(Store.switchback.list[0:Store.maximum_list_length]): @@ -91,7 +122,17 @@ def run(): list_item.addContextMenuItems([(TRANSLATE(32004), "RunPlugin(plugin://plugin.switchback?mode=delete&index=" + str(index) + ")")]) # For detecting Switchback playbacks (in player.py) list_item.setProperty('Switchback', playback.path) - xbmcplugin.addDirectoryItem(plugin_instance, playback.file if playback.source != "addon" else playback.path, list_item) + # Use the 'proxy' URL if we're dealing with pvr_live and need to trigger the PVR playback hack + if playback.source == "pvr_live": + proxy_url = f"plugin://plugin.switchback?mode=pvr_hack&path={playback.path}" + Logger.debug(f"Creating directory item with pvr_hack proxy url: {proxy_url}") + xbmcplugin.addDirectoryItem(plugin_instance, proxy_url, list_item) + # Otherwise use file for all library things, and path for addons (as those may include tokens etc) + else: + url = playback.file if playback.source not in ["addon", "pvr_live"] else playback.path + Logger.debug(f"Creating directory item with url: {url}") + xbmcplugin.addDirectoryItem(plugin_instance, url, list_item) + xbmcplugin.endOfDirectory(plugin_instance, cacheToDisc=False) # And we're done... From 8295666052f2be6c9f01bb3d211192774fe0f55d Mon Sep 17 00:00:00 2001 From: "bossanova808@gmail.com" Date: Sat, 27 Sep 2025 11:54:30 +1000 Subject: [PATCH 11/14] Playbacks working well... --- resources/lib/player.py | 28 ++++++++++++---------------- resources/lib/switchback_plugin.py | 17 +++++++++++++---- 2 files changed, 25 insertions(+), 20 deletions(-) diff --git a/resources/lib/player.py b/resources/lib/player.py index 3b59c84..cfb6118 100644 --- a/resources/lib/player.py +++ b/resources/lib/player.py @@ -70,15 +70,12 @@ def onPlaybackFinished(): Logger.error("onPlaybackFinished with no current playback details available?! ...not recording this playback") return - # if "pvr://" in Store.current_playback.path: - # Logger.warning("PVR not supported due to Kodi bugs with setResolvedUrl/ListItems, so not recording this playback") - # return - - # Force an update of the Switchback list from disk, in case of changes via the plugin side of things. Store.switchback.load_or_init() - Logger.debug("onPlaybackFinished with Store.current_playback:", Store.current_playback) - Logger.debug("onPlaybackFinished with Store.switchback.list: ", Store.switchback.list) + Logger.debug("onPlaybackFinished with Store.current_playback:") + Logger.debug(Store.current_playback) + Logger.debug("onPlaybackFinished with Store.switchback.list:") + Logger.debug(Store.switchback.list) # Was this a Switchback-initiated playback? # (This property was set above in onAVStarted if the ListItem property was set, or explicitly in the PVR HACK! section in switchback_plugin.py this we only need to test for this) @@ -86,9 +83,10 @@ def onPlaybackFinished(): # Clear the property if set, now playback has finished HOME_WINDOW.clearProperty('Switchback') - # If we Switchbacked to an episode, force Kodi to browse to the Show/Season + # If we Switchbacked to a library episode, force Kodi to browse to the Show/Season + # (NB it is not possible to force Kodi to go to movies and focus a specific movie as far as I can determine) if switchback_playback: - if Store.current_playback.type == "episode": + if Store.current_playback.type == "episode" and Store.current_playback.source == "kodi_library": Logger.info("Force browsing to tvshow/season of just finished playback") Logger.debug(f'flatten tvshows {Store.flatten_tvshows} totalseasons {Store.current_playback.totalseasons} dbid {Store.current_playback.dbid} tvshowdbid {Store.current_playback.tvshowdbid}') # Default: Browse to the show @@ -98,17 +96,12 @@ def onPlaybackFinished(): # 2 = Always flatten → browse to season if Store.flatten_tvshows == 2: window += f'/{Store.current_playback.season}' - elif Store.flatten_tvshows == 1 and Store.current_playback.totalseasons > 1: + elif Store.flatten_tvshows == 1 and (Store.current_playback.totalseasons or 0) > 1: window += f'/{Store.current_playback.season}' - xbmc.executebuiltin(f'ActivateWindow(Videos,{window},return)') - else: - # TODO - is is possible to force Kodi to go to movies and focus a specific movie? - pass - # This rather long-windeed approach is used to keep ALL the details recorded from the original playback + # This rather long-winded approach is used to keep ALL the details recorded from the original playback # (in case they don't make it through when the playback is Switchback initiated - as sometimes seems to be the case) - playback_to_remove = Store.switchback.find_playback_by_path(Store.current_playback.path) if playback_to_remove: Logger.debug("Updating Playback and list order") @@ -130,3 +123,6 @@ def onPlaybackFinished(): # & make sure the context menu items are updated Store.update_switchback_context_menu() + + # And update the current view so if we're in the Switchback plugin listing, it gets refreshed + xbmc.executebuiltin("Container.Refresh") diff --git a/resources/lib/switchback_plugin.py b/resources/lib/switchback_plugin.py index b2fc1eb..fc6b029 100644 --- a/resources/lib/switchback_plugin.py +++ b/resources/lib/switchback_plugin.py @@ -66,7 +66,8 @@ def run(): Logger.info(f"Switchback! Switching back to: {switchback_to_play.pluginlabel}") Logger.debug(f"Path: [{switchback_to_play.path}]") Logger.debug(f"File: [{switchback_to_play.file}]") - Notify.kodi_notification(f"{switchback_to_play.pluginlabel}", 3000, switchback_to_play.poster) + image = switchback_to_play.poster or switchback_to_play.icon + Notify.kodi_notification(f"{switchback_to_play.pluginlabel}", 3000, image) # Short circuit here if PVR, see pvr_hack above. if 'pvr://channels' in switchback_to_play.path: @@ -109,7 +110,11 @@ def run(): # See pvr_hack(path) above elif "pvr_hack" in modes: - path = parsed_arguments.get('path', None)[0] + path_values = parsed_arguments.get('path') + if not path_values or not path_values[0]: + Logger.error("Missing 'path' parameter for pvr_hack") + return + path = path_values[0] Logger.debug(f"Triggering PVR Playback hack for {path}") pvr_hack(path) return @@ -127,10 +132,14 @@ def run(): proxy_url = f"plugin://plugin.switchback?mode=pvr_hack&path={playback.path}" Logger.debug(f"Creating directory item with pvr_hack proxy url: {proxy_url}") xbmcplugin.addDirectoryItem(plugin_instance, proxy_url, list_item) - # Otherwise use file for all library things, and path for addons (as those may include tokens etc) + # TODO -> not sure if URL encoding needed in some cases? Maybe CodeRabbit knows? + # args = urlencode({'mode': 'pvr_hack', 'path': self.path}) + # proxy_url = f"plugin://plugin.switchback/?{args}" + + # Otherwise use file for all Kodi library playbacks, and path for addons (as those may include tokens etc) else: url = playback.file if playback.source not in ["addon", "pvr_live"] else playback.path - Logger.debug(f"Creating directory item with url: {url}") + # Logger.debug(f"Creating directory item with url: {url}") xbmcplugin.addDirectoryItem(plugin_instance, url, list_item) xbmcplugin.endOfDirectory(plugin_instance, cacheToDisc=False) From 7632054470e496825a01c2e4b09728a9efd53b8c Mon Sep 17 00:00:00 2001 From: "bossanova808@gmail.com" Date: Sat, 27 Sep 2025 14:14:43 +1000 Subject: [PATCH 12/14] Improve notifications --- .coderabbit.yaml | 4 ++++ resources/language/resource.language.en_gb/strings.po | 2 +- resources/lib/player.py | 5 +++-- resources/lib/switchback_plugin.py | 2 +- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/.coderabbit.yaml b/.coderabbit.yaml index ded150c..bf48c70 100644 --- a/.coderabbit.yaml +++ b/.coderabbit.yaml @@ -1,3 +1,7 @@ reviews: review_instructions: | CRITICAL: Always present code changes in before/after format for easy copy-pasting. NEVER show diffs under any circumstances. Use clear "Before:" and "After:" code blocks only. + poem: false + in_progress_fortune: false +chat: + art: false diff --git a/resources/language/resource.language.en_gb/strings.po b/resources/language/resource.language.en_gb/strings.po index 3932eaf..a559e2e 100644 --- a/resources/language/resource.language.en_gb/strings.po +++ b/resources/language/resource.language.en_gb/strings.po @@ -43,4 +43,4 @@ msgstr "" msgctxt "#32009" msgid "Enable Switchback context menu items?" -msgstr "" \ No newline at end of file +msgstr "" diff --git a/resources/lib/player.py b/resources/lib/player.py index cfb6118..4d89f3e 100644 --- a/resources/lib/player.py +++ b/resources/lib/player.py @@ -124,5 +124,6 @@ def onPlaybackFinished(): # & make sure the context menu items are updated Store.update_switchback_context_menu() - # And update the current view so if we're in the Switchback plugin listing, it gets refreshed - xbmc.executebuiltin("Container.Refresh") + # Update the current view so if we're in the Switchback plugin listing, it gets refreshed + # Use Kodi's AlarmClock to delay the refresh until UI has settled + xbmc.executebuiltin('AlarmClock(SwitchbackRefresh,Container.Refresh,00:00:01,silent)') diff --git a/resources/lib/switchback_plugin.py b/resources/lib/switchback_plugin.py index fc6b029..5be4fda 100644 --- a/resources/lib/switchback_plugin.py +++ b/resources/lib/switchback_plugin.py @@ -67,7 +67,7 @@ def run(): Logger.debug(f"Path: [{switchback_to_play.path}]") Logger.debug(f"File: [{switchback_to_play.file}]") image = switchback_to_play.poster or switchback_to_play.icon - Notify.kodi_notification(f"{switchback_to_play.pluginlabel}", 3000, image) + Notify.kodi_notification(f"{switchback_to_play.pluginlabel_short}", 3000, image) # Short circuit here if PVR, see pvr_hack above. if 'pvr://channels' in switchback_to_play.path: From 28046bfa2d8c9f3453ea402afcd71dd3ab846dab Mon Sep 17 00:00:00 2001 From: "bossanova808@gmail.com" Date: Sat, 27 Sep 2025 14:15:14 +1000 Subject: [PATCH 13/14] Change for module.bossanova808 change --- resources/lib/player.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/resources/lib/player.py b/resources/lib/player.py index 4d89f3e..29fd49b 100644 --- a/resources/lib/player.py +++ b/resources/lib/player.py @@ -48,8 +48,7 @@ def onAVStarted(self): # Create a new Playback object and record the details. Logger.info("Not a Switchback playback, or error retrieving previous Playback, so creating a new Playback object to record details") Store.current_playback = Playback() - Store.current_playback.file = file - Store.current_playback.update_playback_details(item) + Store.current_playback.update_playback_details(file, item) # Playback finished 'naturally' def onPlayBackEnded(self): From b0788814283096d6e6186effd846c7f4caccda71 Mon Sep 17 00:00:00 2001 From: "bossanova808@gmail.com" Date: Sat, 27 Sep 2025 14:22:53 +1000 Subject: [PATCH 14/14] Use threading with the container refresh Avoid the janky notification from Kodi, seems faster too. --- resources/lib/player.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/resources/lib/player.py b/resources/lib/player.py index 29fd49b..93a7740 100644 --- a/resources/lib/player.py +++ b/resources/lib/player.py @@ -123,6 +123,14 @@ def onPlaybackFinished(): # & make sure the context menu items are updated Store.update_switchback_context_menu() - # Update the current view so if we're in the Switchback plugin listing, it gets refreshed - # Use Kodi's AlarmClock to delay the refresh until UI has settled - xbmc.executebuiltin('AlarmClock(SwitchbackRefresh,Container.Refresh,00:00:01,silent)') + # And update the current view so if we're in the Switchback plugin listing, it gets refreshed + # Use a delayed refresh to ensure Kodi has fully returned to the listing - but don't block, use threading + def delayed_refresh(): + xbmc.sleep(200) # Wait 200ms for UI to settle + xbmc.executebuiltin("Container.Refresh") + + import threading + threading.Thread(target=delayed_refresh).start() + + # ALTERNATIVE, but behaviour is slower/more visually janky + # xbmc.executebuiltin('AlarmClock(SwitchbackRefresh,Container.Refresh,00:00:01,silent)')