feat: Update SoundPlayed model to accept nullable user_id and enhance sound tracking in MusicPlayerService

This commit is contained in:
JSC
2025-07-12 15:56:13 +02:00
parent 842e1dff13
commit 6bbf3dce66
2 changed files with 63 additions and 5 deletions

View File

@@ -77,7 +77,7 @@ class SoundPlayed(db.Model):
@classmethod @classmethod
def create_play_record( def create_play_record(
cls, cls,
user_id: int, user_id: int | None,
sound_id: int, sound_id: int,
*, *,
commit: bool = True, commit: bool = True,

View File

@@ -3,18 +3,27 @@
import os import os
import threading import threading
import time import time
from datetime import datetime
from typing import Any, Optional from typing import Any, Optional
from zoneinfo import ZoneInfo
import vlc import vlc
from flask import current_app, request from flask import current_app, request
from app.models.playlist import Playlist from app.models.playlist import Playlist
from app.models.sound import Sound from app.models.sound import Sound
from app.models.sound_played import SoundPlayed
from app.services.logging_service import LoggingService from app.services.logging_service import LoggingService
from app.services.socketio_service import socketio_service from app.services.socketio_service import socketio_service
logger = LoggingService.get_logger(__name__) logger = LoggingService.get_logger(__name__)
# Constants
TRACK_START_THRESHOLD_MS = 500 # 500 milliseconds - threshold for considering a track as "starting fresh"
STATE_CHANGE_THRESHOLD_MS = (
1000 # 1 second threshold for state change detection
)
class MusicPlayerService: class MusicPlayerService:
"""Service for managing a VLC music player with playlist support.""" """Service for managing a VLC music player with playlist support."""
@@ -47,6 +56,9 @@ class MusicPlayerService:
self._track_ending_handled = ( self._track_ending_handled = (
False # Flag to prevent duplicate ending triggers False # Flag to prevent duplicate ending triggers
) )
self._track_play_tracked = (
False # Flag to track if current track play has been logged
)
def start_vlc_instance(self) -> bool: def start_vlc_instance(self) -> bool:
"""Start a VLC instance with Python bindings.""" """Start a VLC instance with Python bindings."""
@@ -216,12 +228,41 @@ class MusicPlayerService:
self.current_track_index = index self.current_track_index = index
# Reset track ending flag when loading a new track # Reset track ending flag when loading a new track
self._track_ending_handled = False self._track_ending_handled = False
self._track_play_tracked = (
False # Reset play tracking for new track
)
return True return True
return False return False
except Exception as e: except Exception as e:
logger.error(f"Error loading track at index {index}: {e}") logger.error(f"Error loading track at index {index}: {e}")
return False return False
def _track_sound_play(self, sound_id: int) -> None:
"""Track that a sound has been played."""
try:
# Use stored app instance or current_app
app_to_use = self.app or current_app
if app_to_use:
with app_to_use.app_context():
# Get the sound and increment its play count
sound = Sound.query.get(sound_id)
if sound:
sound.play_count += 1
sound.updated_at = datetime.now(tz=ZoneInfo("UTC"))
logger.info(
f"Incremented play count for sound '{sound.name}' (ID: {sound_id})"
)
# Create a sound played record without user_id (anonymous play)
SoundPlayed.create_play_record(
user_id=None, sound_id=sound_id, commit=True
)
logger.info(
f"Created anonymous play record for sound ID: {sound_id}"
)
except Exception as e:
logger.error(f"Error tracking sound play for sound {sound_id}: {e}")
def _get_sound_file_path(self, sound: Sound) -> Optional[str]: def _get_sound_file_path(self, sound: Sound) -> Optional[str]:
"""Get the file path for a sound, preferring normalized version.""" """Get the file path for a sound, preferring normalized version."""
try: try:
@@ -268,6 +309,9 @@ class MusicPlayerService:
result = self.player.play() result = self.player.play()
if result == 0: # Success if result == 0: # Success
self.is_playing = True self.is_playing = True
self._track_play_tracked = (
False # Track when we first start playing
)
self._emit_player_state() self._emit_player_state()
return True return True
return False return False
@@ -430,9 +474,10 @@ class MusicPlayerService:
if not self.current_playlist_id: if not self.current_playlist_id:
return None return None
# Ensure we have Flask app context # Use stored app instance or current_app
if current_app: app_to_use = self.app or current_app
with current_app.app_context(): if app_to_use:
with app_to_use.app_context():
playlist = Playlist.query.get(self.current_playlist_id) playlist = Playlist.query.get(self.current_playlist_id)
if playlist and 0 <= self.current_track_index < len( if playlist and 0 <= self.current_track_index < len(
playlist.playlist_sounds playlist.playlist_sounds
@@ -610,11 +655,24 @@ class MusicPlayerService:
elif self.is_playing and not old_playing: elif self.is_playing and not old_playing:
self._track_ending_handled = False self._track_ending_handled = False
# Track play event when song starts playing from beginning (but only once per track load)
# Only track if we're playing, haven't tracked yet, and current time is near the start (< 5 seconds)
if (
self.is_playing
and not self._track_play_tracked
and self.current_time >= 0
and self.current_time < TRACK_START_THRESHOLD_MS
):
current_track = self.get_current_track()
if current_track:
self._track_sound_play(current_track["id"])
self._track_play_tracked = True
# Emit updates if state changed significantly or periodically # Emit updates if state changed significantly or periodically
state_changed = ( state_changed = (
old_playing != self.is_playing old_playing != self.is_playing
or abs(old_time - self.current_time) or abs(old_time - self.current_time)
> 1000 # More than 1 second difference > STATE_CHANGE_THRESHOLD_MS # More than 1 second difference
) )
# Always emit if playing to keep frontend updated # Always emit if playing to keep frontend updated