Skip to content

Commit f46192c

Browse files
bossanova808bossanova808
andauthored
[script.service.playbackresumer] 2.0.8 (#2770)
Co-authored-by: bossanova808 <[email protected]>
1 parent 018a31f commit f46192c

File tree

7 files changed

+170
-40
lines changed

7 files changed

+170
-40
lines changed
Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,27 @@
11
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2-
<addon id="script.service.playbackresumer" name="Kodi Playback Resumer" version="2.0.7" provider-name="bossanova808, bradvido88">
2+
<addon id="script.service.playbackresumer" name="Kodi Playback Resumer" version="2.0.8" provider-name="bossanova808, bradvido88">
33
<requires>
44
<import addon="xbmc.python" version="3.0.0"/>
55
<import addon="script.module.bossanova808" version="1.0.0"/>
66
</requires>
77
<extension point="xbmc.service" library="default.py" />
88
<extension point="xbmc.addon.metadata">
99
<summary lang="en_GB">Periodically sets the resume point of videos and can automatically resume last played video if Kodi crashes.</summary>
10+
<summary lang="sv_SE">Ställer in återuppspelningspunkten för videor med jämna mellanrum och kan automatiskt återuppspela den senast spelade videon om Kodi kraschar.</summary>
1011
<description lang="en_GB">
1112
Runs as a service and will periodically update the resume point while videos are playing, so you can re-start from where you were in the event of a crash. It can also automatically resume a video if Kodi was shutdown while playing it. See setting to configure how often the resume point is set, and whether to automatically resume.
1213
</description>
14+
<description lang="sv_SE">
15+
Körs som en tjänst och uppdaterar regelbundet återuppspelningspunkten vid spelning medan videor spelas upp, så att du kan starta om från där du var i händelse av ett krascher. Den kan också automatiskt återuppspela en video om Kodi stängdes av vid spelning. Se inställningarna för att konfigurera hur ofta återuppspelningspunkten ska ställas in och om den ska återuppspelas automatiskt.
16+
</description>
1317
<platform>all</platform>
1418
<license>GPL-3.0-only</license>
1519
<website>https://github.com/bossanova808/script.service.playbackresumer</website>
1620
<source>https://github.com/bossanova808/script.service.playbackresumer</source>
1721
<forum>https://forum.kodi.tv/showthread.php?tid=355383</forum>
1822
<email>[email protected]</email>
19-
<news>v2.0.7
20-
- Remove old common code, use new module
23+
<news>v2.0.8
24+
- Minor changes for Piers
2125
</news>
2226
<assets>
2327
<icon>icon.png</icon>
@@ -26,3 +30,4 @@
2630
</addon>
2731

2832

33+

script.service.playbackresumer/changelog.txt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
1-
v2.0.6
1+
v2.0.8
2+
- Minor changes for Piers
3+
4+
v2.0.7
5+
- Remove old common code, use new module
26

7+
v2.0.6
38
- Add support for non library videos
49

510
v2.0.5
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# XBMC Media Center Swedish language file
2+
# Addon Name: XBMC Playback Resumer
3+
# Addon id: script.service.playbackresumer
4+
# Addon version: 1.2.0
5+
# Addon Provider: bradvido88,bossanova808
6+
msgid ""
7+
msgstr ""
8+
"Project-Id-Version: XBMC-Addons\n"
9+
"Report-Msgid-Bugs-To: [email protected]\n"
10+
"POT-Creation-Date: 2014-01-12 02:29+0000\n"
11+
"PO-Revision-Date: 2025-10-01 11:42+0200\n"
12+
"Last-Translator: Daniel Nylander <[email protected]>\n"
13+
"Language-Team: sv\n"
14+
"Language: sv\n"
15+
"MIME-Version: 1.0\n"
16+
"Content-Type: text/plain; charset=UTF-8\n"
17+
"Content-Transfer-Encoding: 8bit\n"
18+
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
19+
"X-Generator: Poedit 3.7\n"
20+
21+
msgctxt "Addon Summary"
22+
msgid "Periodically sets the resume point while videos are playing and optionally automatically resumes last played video if XBMC crashes or is shutdown during playback."
23+
msgstr "Ställer regelbundet in återupptagningspunkten medan videor spelas upp och återupptar automatiskt den senast spelade videon om XBMC kraschar eller stängs av under uppspelning."
24+
25+
msgctxt "Addon Description"
26+
msgid "This addon runs in the background as a service and will periodically update the resume point while videos are playing. It will also automatically resume a video if XBMC was shutdown while playing it. See setting to configure how often the resume point is set and whether to automatically resume."
27+
msgstr "Detta tillägg körs i bakgrunden som en tjänst och kommer regelbundet att uppdatera återuppspelningspunkten medan videor spelas upp. Det kommer också automatiskt att återuppta en video om XBMC stängdes av medan den spelades. Se Inställning för att konfigurera hur ofta återuppspelningspunkten ska ställas in och om den ska återupptas automatiskt."
28+
29+
msgctxt "#32000"
30+
msgid "Resumer"
31+
msgstr "Återuppspelare"
32+
33+
msgctxt "#32001"
34+
msgid "Save resume point every X seconds"
35+
msgstr "Spara återuppspelningspunkt var X:e sekund"
36+
37+
msgctxt "#32002"
38+
msgid "Auto-Resume playback at startup"
39+
msgstr "Återuppta uppspelningen automatiskt vid start"
40+
41+
msgctxt "#32003"
42+
msgid "Auto-Play a random video if nothing is playing"
43+
msgstr "Spela automatiskt upp en slumpmässig video om inget spelas"
44+
45+
msgctxt "#32030"
46+
msgid "Exclude"
47+
msgstr "Exkludera"
48+
49+
msgctxt "#32031"
50+
msgid "Exclude Live TV"
51+
msgstr "Exkludera direktsänd TV"
52+
53+
msgctxt "#32032"
54+
msgid "Exclude HTTP sources"
55+
msgstr "Exkludera HTTP-källor"
56+
57+
msgctxt "#32033"
58+
msgid "Exclude path"
59+
msgstr "Exkludera sökväg"
60+
61+
msgctxt "#32034"
62+
msgid "Folder's path (and subfolders)"
63+
msgstr "Mappens sökväg (och undermappar)"

script.service.playbackresumer/resources/lib/monitor.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,12 @@ def __init__(self, *args, **kwargs):
1111
Logger.debug('KodiEventMonitor __init__')
1212

1313
def onSettingsChanged(self):
14+
"""
15+
Handle Kodi settings changes by reloading the add-on configuration from settings.
16+
17+
Invoked when Kodi reports settings have changed; calls the Store to reload configuration so runtime state reflects updated settings.
18+
"""
1419
Logger.info('onSettingsChanged - reload them.')
1520
Store.load_config_from_settings()
1621

17-
def onAbortRequested(self):
18-
Logger.debug('onAbortRequested')
22+
Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,26 @@
1-
from bossanova808.utilities import *
2-
# noinspection PyPackages
3-
from .store import Store
41
import xbmc
52
# noinspection PyPackages
63
from .monitor import KodiEventMonitor
74
# noinspection PyPackages
85
from .player import KodiPlayer
6+
# noinspection PyPackages
7+
from .store import Store
8+
9+
from bossanova808.logger import Logger
910

1011

1112
def run():
1213
"""
13-
This is 'main'
14-
15-
:return:
14+
Start the addon: initialize logging and global state, configure Kodi monitor and player, attempt to resume or start playback, then run the main event loop until an abort is requested.
15+
16+
This function:
17+
- Starts the logger and creates the global Store.
18+
- Instantiates and stores Kodi event monitor and player objects.
19+
- Attempts to resume previous playback; if nothing resumed and no video is playing, triggers autoplay when enabled.
20+
- Enters a loop that waits for an abort request and exits when one is detected.
21+
- Stops the logger before returning.
1622
"""
17-
footprints()
23+
Logger.start()
1824
# load settings and create the store for our globals
1925
Store()
2026
Store.kodi_event_monitor = KodiEventMonitor(xbmc.Monitor)
@@ -26,7 +32,8 @@ def run():
2632

2733
while not Store.kodi_event_monitor.abortRequested():
2834
if Store.kodi_event_monitor.waitForAbort(1):
35+
Logger.debug('onAbortRequested')
2936
# Abort was requested while waiting. We should exit
3037
break
3138

32-
footprints(False)
39+
Logger.stop()

script.service.playbackresumer/resources/lib/player.py

Lines changed: 67 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from bossanova808.logger import Logger
44
from bossanova808.notify import Notify
5-
from bossanova808.utilities import *
5+
from bossanova808.utilities import send_kodi_json
66

77
# noinspection PyPackages
88
from .store import Store
@@ -17,7 +17,14 @@ class KodiPlayer(xbmc.Player):
1717
This class represents/monitors the Kodi video player
1818
"""
1919

20-
def __init__(self, *args):
20+
# noinspection PyUnusedLocal
21+
def __init__(self, *_args):
22+
"""
23+
Initialize the KodiPlayer instance and bind it to xbmc.Player.
24+
25+
Parameters:
26+
*_args: Optional positional arguments accepted for compatibility; any values passed are ignored.
27+
"""
2128
xbmc.Player.__init__(self)
2229
Logger.debug('KodiPlayer __init__')
2330

@@ -32,17 +39,32 @@ def onPlayBackEnded(self): # video ended normally (user didn't stop it)
3239
self.autoplay_random_if_enabled()
3340

3441
def onPlayBackStopped(self):
42+
"""
43+
Handle the playback-stopped event and mark the current resume point as managed by Kodi.
44+
45+
When playback stops, record a sentinel resume value indicating that Kodi should retain or handle the resume point (internal sentinel -2).
46+
"""
3547
Logger.info("onPlayBackStopped")
3648
self.update_resume_point(-2)
3749

38-
def onPlayBackSeek(self, time, seekOffset):
39-
Logger.info(f'onPlayBackSeek time {time}, seekOffset {seekOffset}')
50+
def onPlayBackSeek(self, time_to_seek, seek_offset):
51+
"""
52+
Handle a user-initiated seek during playback and update the stored resume point.
53+
54+
When a seek occurs, attempt to record the current playback time as the resume point.
55+
If reading the current playback time raises a RuntimeError (e.g., seeked past the end),
56+
clear the stored resume point.
57+
58+
Parameters:
59+
time_to_seek (float): The target time position of the seek (seconds).
60+
seek_offset (float): The relative offset of the seek from the previous position (seconds).
61+
"""
62+
Logger.info(f'onPlayBackSeek time {time_to_seek}, seekOffset {seek_offset}')
4063
try:
4164
self.update_resume_point(self.getTime())
4265
except RuntimeError:
4366
Logger.warning("Could not get playing time - seeked past end? Clearing resume point.")
4467
self.update_resume_point(0)
45-
pass
4668

4769
def onPlayBackSeekChapter(self, chapter):
4870
Logger.info(f'onPlayBackSeekChapter chapter: {chapter}')
@@ -51,7 +73,6 @@ def onPlayBackSeekChapter(self, chapter):
5173
except RuntimeError:
5274
Logger.warning("Could not get playing time - seeked past end? Clearing resume point.")
5375
self.update_resume_point(0)
54-
pass
5576

5677
def onAVStarted(self):
5778
Logger.info("onAVStarted")
@@ -86,7 +107,11 @@ def update_resume_point(self, seconds):
86107
"""
87108
This is where the work is done - stores a new resume point in the Kodi library for the currently playing file
88109
89-
:param: seconds: the time to update the resume point to. @todo add notes on -1, -2 etc here!
110+
:param seconds: target resume time in seconds.
111+
Special values:
112+
-2 -> stopped normally, let Kodi persist native resume (no-op here)
113+
-1 -> end-of-file, clear resume point (sends 0)
114+
0 -> explicit clear resume point
90115
:param: Store.library_id: the Kodi library id of the currently playing file
91116
:return: None
92117
"""
@@ -139,11 +164,13 @@ def update_resume_point(self, seconds):
139164
seconds = 0
140165

141166
# if current time > Kodi's ignorepercentatend setting
142-
percent_played = int((seconds * 100) / Store.length_of_currently_playing_file)
143-
if percent_played > (100 - Store.ignore_percent_at_end):
144-
Logger.info(f'Not updating resume point as current percent played ({percent_played}) is above Kodi\'s ignorepercentatend'
145-
f' setting of {Store.ignore_percent_at_end}')
146-
return
167+
# if current time > Kodi's ignorepercentatend setting
168+
total = Store.length_of_currently_playing_file
169+
if total:
170+
percent_played = int((seconds * 100) / total)
171+
if percent_played > (100 - Store.ignore_percent_at_end):
172+
Logger.info(f"Not updating resume point as current percent played ({percent_played}) is above Kodi's ignorepercentatend setting of {Store.ignore_percent_at_end}")
173+
return
147174

148175
# OK, BELOW HERE, we're probably going to set a resume point
149176

@@ -228,9 +255,12 @@ def update_resume_point(self, seconds):
228255

229256
def resume_if_was_playing(self):
230257
"""
231-
Automatically resume a video after a crash, if one was playing...
232-
233-
:return:
258+
Attempt to resume playback after a previous shutdown if resuming is enabled and saved resume data exist.
259+
260+
If configured and valid resume data are present, the player will start the saved file and seek to the stored resume time; on any failure or if no resume data are applicable, no playback is resumed.
261+
262+
Returns:
263+
True if playback was resumed and seeked to the saved position, False otherwise.
234264
"""
235265

236266
if Store.resume_on_startup \
@@ -242,7 +272,7 @@ def resume_if_was_playing(self):
242272
resume_point = float(f.read())
243273
except Exception:
244274
Logger.error("Error reading resume point from file, therefore not resuming.")
245-
return
275+
return False
246276

247277
# neg 1 means the video wasn't playing when Kodi ended
248278
if resume_point < 0:
@@ -252,13 +282,17 @@ def resume_if_was_playing(self):
252282
with open(Store.file_to_store_last_played, 'r') as f:
253283
full_path = f.read()
254284

255-
str_timestamp = '%d:%02d' % (resume_point / 60, resume_point % 60)
256-
Logger.info(f'Will resume playback at {str_timestamp} of {full_path}')
285+
if not full_path:
286+
Logger.info("No last-played file found; skipping resume.")
287+
return False
288+
289+
mins, secs = divmod(int(resume_point), 60)
290+
str_timestamp = f'{mins}:{secs:02d}'
257291

258292
self.play(full_path)
259293

260294
# wait up to 10 secs for the video to start playing before we try to seek
261-
for i in range(0, 1000):
295+
for _ in range(100):
262296
if not self.isPlayingVideo() and not Store.kodi_event_monitor.abortRequested():
263297
xbmc.sleep(100)
264298
else:
@@ -270,19 +304,25 @@ def resume_if_was_playing(self):
270304

271305
def get_random_library_video(self):
272306
"""
273-
Get a random video from the library for playback
274-
275-
:return:
307+
Selects a random video file path from the Kodi library.
308+
309+
Chooses among episodes, movies, and music videos and returns the file path of a randomly selected item if one exists. Updates Store.video_types_in_library to reflect whether a given type is present. If the library contains no eligible videos, no selection is made.
310+
311+
Returns:
312+
str: File path of the selected video.
313+
False: If no episodes, movies, or music videos exist in the library.
276314
"""
277315

278316
# Short circuit if library is empty
279317
if not Store.video_types_in_library['episodes'] \
280318
and not Store.video_types_in_library['movies'] \
281319
and not Store.video_types_in_library['musicvideos']:
282320
Logger.warning('No episodes, movies, or music videos exist in the Kodi library. Cannot autoplay a random video.')
283-
return
321+
return False
284322

285323
random_int = randint(0, 2)
324+
result_type = None
325+
method = None
286326
if random_int == 0:
287327
result_type = 'episodes'
288328
method = "GetEpisodes"
@@ -347,7 +387,10 @@ def autoplay_random_if_enabled(self):
347387
if not self.isPlayingVideo() \
348388
and (video_playlist.getposition() == -1 or video_playlist.getposition() == video_playlist.size()):
349389
full_path = self.get_random_library_video()
350-
Logger.info("Auto-playing next random video because nothing is playing and playlist is empty: " + full_path)
390+
if not full_path:
391+
Logger.info("No random video available to autoplay.")
392+
return
393+
Logger.info(f"Auto-playing next random video because nothing is playing and playlist is empty: {full_path}")
351394
self.play(full_path)
352395
Notify.info(f'Auto-playing random video: {full_path}')
353396
else:

script.service.playbackresumer/resources/lib/store.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1-
from bossanova808.utilities import *
2-
from bossanova808.logger import Logger
31
import os
42
import json
53
import xml.etree.ElementTree as ElementTree
64

75
import xbmc
6+
import xbmcvfs
7+
8+
from bossanova808.constants import PROFILE, ADDON
9+
from bossanova808.logger import Logger
10+
from bossanova808.utilities import get_setting, get_setting_as_bool
811

912

1013
class Store:

0 commit comments

Comments
 (0)