feat(scrobble): submit final scrobble on playback finish with listen threshold
This commit is contained in:
32
player.py
32
player.py
@@ -1,6 +1,7 @@
|
||||
''' A player object that handles playback and data for its respective guild '''
|
||||
|
||||
import asyncio
|
||||
import time
|
||||
import discord
|
||||
|
||||
import data
|
||||
@@ -16,6 +17,7 @@ _default_data: dict[str, any] = {
|
||||
"current-song": None,
|
||||
"current-position": 0,
|
||||
"queue": [],
|
||||
"current-start-ms": None,
|
||||
}
|
||||
|
||||
class Player():
|
||||
@@ -62,6 +64,16 @@ class Player():
|
||||
self._player_loop = loop
|
||||
|
||||
|
||||
@property
|
||||
def current_start_ms(self) -> int | None:
|
||||
''' The epoch time in ms when current song playback started '''
|
||||
return self._data["current-start-ms"]
|
||||
|
||||
@current_start_ms.setter
|
||||
def current_start_ms(self, value: int | None) -> None:
|
||||
self._data["current-start-ms"] = value
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -127,6 +139,24 @@ class Player():
|
||||
return
|
||||
|
||||
logger.debug("Playback finished.")
|
||||
# Attempt a final scrobble submission if sufficient playback occurred
|
||||
try:
|
||||
if self.current_start_ms is not None and self.current_song is not None:
|
||||
now_ms = int(time.time() * 1000)
|
||||
duration_ms = int(self.current_song.duration) * 1000
|
||||
threshold_ms = min(240_000, duration_ms // 2)
|
||||
played_ms = now_ms - int(self.current_start_ms)
|
||||
if played_ms >= threshold_ms:
|
||||
logger.info(
|
||||
f"Submitting scrobble for {self.current_song.title} (played {played_ms}ms, threshold {threshold_ms}ms)"
|
||||
)
|
||||
asyncio.create_task(scrobble(self.current_song.song_id, submission=True, timestamp=int(self.current_start_ms // 1000)))
|
||||
else:
|
||||
logger.info(
|
||||
f"Skipping scrobble for {self.current_song.title} (played {played_ms}ms < threshold {threshold_ms}ms)"
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to submit final scrobble: {e}")
|
||||
try:
|
||||
# Only proceed if voice client is still connected
|
||||
if voice_client and voice_client.is_connected():
|
||||
@@ -150,6 +180,8 @@ class Player():
|
||||
await ui.ErrMsg.msg(interaction, "Voice connection was lost. Please try again.")
|
||||
return
|
||||
|
||||
# Mark playback start time (ms) and start playing
|
||||
self.current_start_ms = int(time.time() * 1000)
|
||||
voice_client.play(audio_src, after=lambda e: loop.create_task(playback_finished(e)))
|
||||
logger.info(f"Started playing: {song.title} by {song.artist}")
|
||||
# Fire-and-forget scrobble (now-playing ping with submission=False)
|
||||
|
||||
@@ -586,7 +586,9 @@ async def scrobble(song_id: str, *, submission: bool=True, timestamp: int=None)
|
||||
"submission": "true" if submission else "false",
|
||||
}
|
||||
if timestamp is not None:
|
||||
scrobble_params["time"] = str(timestamp)
|
||||
# Subsonic expects epoch time in milliseconds
|
||||
time_ms = int(timestamp) * 1000
|
||||
scrobble_params["time"] = str(time_ms)
|
||||
|
||||
params = SUBSONIC_REQUEST_PARAMS | scrobble_params
|
||||
|
||||
|
||||
Reference in New Issue
Block a user