Skip to content
7 changes: 7 additions & 0 deletions .coderabbit.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +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
4 changes: 1 addition & 3 deletions plugin.py
Original file line number Diff line number Diff line change
@@ -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()
2 changes: 1 addition & 1 deletion resources/language/resource.language.en_gb/strings.po
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,4 @@ msgstr ""

msgctxt "#32009"
msgid "Enable Switchback context menu items?"
msgstr ""
msgstr ""
34 changes: 0 additions & 34 deletions resources/lib/playback.py

This file was deleted.

84 changes: 48 additions & 36 deletions resources/lib/player.py
Original file line number Diff line number Diff line change
@@ -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):
"""
Expand All @@ -24,29 +28,29 @@ 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.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)
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.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)
Store.current_playback.update_playback_details(file, item)

# Playback finished 'naturally'
def onPlayBackEnded(self):
self.onPlaybackFinished()

Expand All @@ -65,45 +69,41 @@ def onPlaybackFinished():
Logger.error("onPlaybackFinished with no current playback details available?! ...not recording this playback")
return

Logger.debug("onPlaybackFinished with Store.current_playback:", Store.current_playback)
Logger.debug("onPlaybackFinished with Store.switchback.list: ", Store.switchback.list)
Store.switchback.load_or_init()

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 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')

# If we Switchbacked to an episode, force Kodi to browse to the Show/Season
if switchback_playback == 'true':
if Store.current_playback.type == "episode":
Logger.info(f"Force browsing to tvshow/season of just finished playback")
# 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" 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
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:
# 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:
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)

# 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("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
Expand All @@ -116,9 +116,21 @@ 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()

# 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)')
9 changes: 7 additions & 2 deletions resources/lib/store.py
Original file line number Diff line number Diff line change
@@ -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


Expand Down Expand Up @@ -49,10 +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: 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}")

Expand Down
Loading