feat: Update SoundPlayed model to accept nullable user_id and enhance sound tracking in MusicPlayerService
This commit is contained in:
@@ -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,
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user