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 '''
|
''' A player object that handles playback and data for its respective guild '''
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import time
|
||||||
import discord
|
import discord
|
||||||
|
|
||||||
import data
|
import data
|
||||||
@@ -16,6 +17,7 @@ _default_data: dict[str, any] = {
|
|||||||
"current-song": None,
|
"current-song": None,
|
||||||
"current-position": 0,
|
"current-position": 0,
|
||||||
"queue": [],
|
"queue": [],
|
||||||
|
"current-start-ms": None,
|
||||||
}
|
}
|
||||||
|
|
||||||
class Player():
|
class Player():
|
||||||
@@ -62,6 +64,16 @@ class Player():
|
|||||||
self._player_loop = loop
|
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
|
return
|
||||||
|
|
||||||
logger.debug("Playback finished.")
|
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:
|
try:
|
||||||
# Only proceed if voice client is still connected
|
# Only proceed if voice client is still connected
|
||||||
if voice_client and voice_client.is_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.")
|
await ui.ErrMsg.msg(interaction, "Voice connection was lost. Please try again.")
|
||||||
return
|
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)))
|
voice_client.play(audio_src, after=lambda e: loop.create_task(playback_finished(e)))
|
||||||
logger.info(f"Started playing: {song.title} by {song.artist}")
|
logger.info(f"Started playing: {song.title} by {song.artist}")
|
||||||
# Fire-and-forget scrobble (now-playing ping with submission=False)
|
# 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",
|
"submission": "true" if submission else "false",
|
||||||
}
|
}
|
||||||
if timestamp is not None:
|
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
|
params = SUBSONIC_REQUEST_PARAMS | scrobble_params
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user