Skip to content

Commit 244e8d3

Browse files
committed
monitor: Improve jump detection
Earlier, we used to simply calc the delta of file progress % between the prev and cur states. If too much delta from last update, send scrobble. But for small files (like, 2 minute duration), with a polling monitor running every 10 seconds, we would hit this condition after each check. Because, the progress delta in 10s is a significant chunk of the total time. This would result in spurious "start" scrobbles every 10s. Now we also estimate the "expected" progress between the two states, based on their "updated_at" times as a % of total duration. Now, if this "expected" progress is too far from the measured progress, we send the scrobble. This assumes that the file is being played at 1x speed.
1 parent 233c5d3 commit 244e8d3

File tree

1 file changed

+29
-3
lines changed

1 file changed

+29
-3
lines changed

trakt_scrobbler/player_monitors/monitor.py

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,31 @@ def progress(self) -> float:
5757
def abs_progress(self) -> float:
5858
return abs(self.progress)
5959

60+
@property
61+
def expected_progress_delta(self) -> float:
62+
"""Calculate expected % of file played between the two states.
63+
Example: duration=50seconds, prev.upd_at=5, cur.upd_at=12
64+
Here, elapsed time=7s; we expect (12-5)/50=7/50=14% of progress delta
65+
"""
66+
if self.prev['duration'] != self.current['duration'] or self.current['state'] != State.Playing:
67+
# doesn't make sense to have an expected progress in these cases.
68+
return 0
69+
return 100 * self.elapsed_realtime / self.current['duration']
70+
71+
@property
72+
def progress_skipped(self) -> float:
73+
"""Compare the progress with the expected progress based on real time.
74+
75+
Continuing above example, if prev.prog=8%, and expected_prog_delta=14%,
76+
cur.prog _should be_ 8+14 = 22%.
77+
We calculate ((cur.prog - prev.prog) - exp_delta), should be close to 0.
78+
"""
79+
return self.progress - self.expected_progress_delta
80+
81+
@property
82+
def abs_progress_skipped(self) -> float:
83+
return abs(self.progress_skipped)
84+
6085

6186
class Monitor(Thread):
6287
"""Generic base class that polls the player for state changes,
@@ -185,6 +210,7 @@ def parse_status(status):
185210
progress = min(round(status['position'] * 100 / status['duration'], 2), 100)
186211
return {
187212
'state': status['state'],
213+
'duration': status['duration'],
188214
'progress': progress,
189215
'media_info': media_info,
190216
'updated_at': time.time(),
@@ -218,10 +244,10 @@ def decide_action(self, prev, current):
218244
not prev
219245
or not transition.is_same_media
220246
or transition.state_changed
221-
or transition.abs_progress > self.skip_interval
247+
or transition.abs_progress_skipped > self.skip_interval
222248
):
223249
yield 'scrobble'
224-
elif transition.state_changed or transition.abs_progress > self.skip_interval:
250+
elif transition.state_changed or transition.abs_progress_skipped > self.skip_interval:
225251
# state changed
226252
if self.preview:
227253
if current['state'] == State.Stopped:
@@ -235,7 +261,7 @@ def decide_action(self, prev, current):
235261
elif self.fast_pause:
236262
if (
237263
current['state'] == State.Stopped
238-
or transition.abs_progress > self.skip_interval
264+
or transition.abs_progress_skipped > self.skip_interval
239265
):
240266
yield 'scrobble'
241267
yield 'exit_fast_pause'

0 commit comments

Comments
 (0)