feat: Add VLC player API endpoints and associated tests

- Implemented VLC player API endpoints for playing and stopping sounds.
- Added tests for successful playback, error handling, and authentication scenarios.
- Created utility function to get sound file paths based on sound properties.
- Refactored player service to utilize shared sound path utility.
- Enhanced test coverage for sound file path utility with various sound types.
- Introduced tests for VLC player service, including subprocess handling and play count tracking.
This commit is contained in:
JSC
2025-07-30 20:46:49 +02:00
parent 1b0d291ad3
commit dd10ef5d41
9 changed files with 1413 additions and 134 deletions

View File

@@ -9,15 +9,14 @@ from pathlib import Path
from typing import Any
import vlc # type: ignore[import-untyped]
from sqlmodel import select
from app.core.logging import get_logger
from app.models.sound import Sound
from app.models.sound_played import SoundPlayed
from app.repositories.playlist import PlaylistRepository
from app.repositories.sound import SoundRepository
from app.repositories.user import UserRepository
from app.services.socket import socket_manager
from app.utils.audio import get_sound_file_path
logger = get_logger(__name__)
@@ -198,7 +197,7 @@ class PlayerService:
return
# Get sound file path
sound_path = self._get_sound_file_path(self.state.current_sound)
sound_path = get_sound_file_path(self.state.current_sound)
if not sound_path.exists():
logger.error("Sound file not found: %s", sound_path)
return
@@ -344,6 +343,12 @@ class PlayerService:
if self.state.status != PlayerStatus.STOPPED:
await self._stop_playback()
# Set first track as current if no current track and playlist has sounds
if not self.state.current_sound_id and sounds:
self.state.current_sound_index = 0
self.state.current_sound = sounds[0]
self.state.current_sound_id = sounds[0].id
logger.info(
"Loaded playlist: %s (%s sounds)",
current_playlist.name,
@@ -360,21 +365,6 @@ class PlayerService:
"""Get current player state."""
return self.state.to_dict()
def _get_sound_file_path(self, sound: Sound) -> Path:
"""Get the file path for a sound."""
# Determine the correct subdirectory based on sound type
subdir = "extracted" if sound.type.upper() == "EXT" else sound.type.lower()
# Use normalized file if available, otherwise original
if sound.is_normalized and sound.normalized_filename:
return (
Path("sounds/normalized")
/ subdir
/ sound.normalized_filename
)
return (
Path("sounds/originals") / subdir / sound.filename
)
def _get_next_index(self, current_index: int) -> int | None:
"""Get next track index based on current mode."""
@@ -501,7 +491,6 @@ class PlayerService:
session = self.db_session_factory()
try:
sound_repo = SoundRepository(session)
user_repo = UserRepository(session)
# Update sound play count
sound = await sound_repo.get_by_id(sound_id)
@@ -519,37 +508,17 @@ class PlayerService:
else:
logger.warning("Sound %s not found for play count update", sound_id)
# Record play history for admin user (ID 1) as placeholder
# This could be refined to track per-user play history
admin_user = await user_repo.get_by_id(1)
if admin_user:
# Check if already recorded for this user using proper query
stmt = select(SoundPlayed).where(
SoundPlayed.user_id == admin_user.id,
SoundPlayed.sound_id == sound_id,
)
result = await session.exec(stmt)
existing = result.first()
if not existing:
sound_played = SoundPlayed(
user_id=admin_user.id,
sound_id=sound_id,
)
session.add(sound_played)
logger.info(
"Created SoundPlayed record for user %s, sound %s",
admin_user.id,
sound_id,
)
else:
logger.info(
"SoundPlayed record already exists for user %s, sound %s",
admin_user.id,
sound_id,
)
else:
logger.warning("Admin user (ID 1) not found for play history")
# Record play history without user_id for player-based plays
# Always create a new SoundPlayed record for each play event
sound_played = SoundPlayed(
user_id=None, # No user_id for player-based plays
sound_id=sound_id,
)
session.add(sound_played)
logger.info(
"Created SoundPlayed record for player play, sound %s",
sound_id,
)
await session.commit()
logger.info("Successfully recorded play count for sound %s", sound_id)