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
def create_play_record(
cls,
user_id: int,
user_id: int | None,
sound_id: int,
*,
commit: bool = True,

View File

@@ -3,18 +3,27 @@
import os
import threading
import time
from datetime import datetime
from typing import Any, Optional
from zoneinfo import ZoneInfo
import vlc
from flask import current_app, request
from app.models.playlist import Playlist
from app.models.sound import Sound
from app.models.sound_played import SoundPlayed
from app.services.logging_service import LoggingService
from app.services.socketio_service import socketio_service
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:
"""Service for managing a VLC music player with playlist support."""
@@ -47,6 +56,9 @@ class MusicPlayerService:
self._track_ending_handled = (
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:
"""Start a VLC instance with Python bindings."""
@@ -216,12 +228,41 @@ class MusicPlayerService:
self.current_track_index = index
# Reset track ending flag when loading a new track
self._track_ending_handled = False
self._track_play_tracked = (
False # Reset play tracking for new track
)
return True
return False
except Exception as e:
logger.error(f"Error loading track at index {index}: {e}")
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]:
"""Get the file path for a sound, preferring normalized version."""
try:
@@ -268,6 +309,9 @@ class MusicPlayerService:
result = self.player.play()
if result == 0: # Success
self.is_playing = True
self._track_play_tracked = (
False # Track when we first start playing
)
self._emit_player_state()
return True
return False
@@ -430,9 +474,10 @@ class MusicPlayerService:
if not self.current_playlist_id:
return None
# Ensure we have Flask app context
if current_app:
with current_app.app_context():
# 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():
playlist = Playlist.query.get(self.current_playlist_id)
if playlist and 0 <= self.current_track_index < len(
playlist.playlist_sounds
@@ -610,11 +655,24 @@ class MusicPlayerService:
elif self.is_playing and not old_playing:
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
state_changed = (
old_playing != self.is_playing
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