feat: Refactor playlist handling in PlayerService and add comprehensive tests

This commit is contained in:
JSC
2025-07-30 22:10:23 +02:00
parent 974fb05087
commit 4e3c489f31
2 changed files with 361 additions and 30 deletions

View File

@@ -5,12 +5,12 @@ import threading
import time
from collections.abc import Callable, Coroutine
from enum import Enum
from pathlib import Path
from typing import Any
import vlc # type: ignore[import-untyped]
from app.core.logging import get_logger
from app.models.playlist import Playlist
from app.models.sound import Sound
from app.models.sound_played import SoundPlayed
from app.repositories.playlist import PlaylistRepository
@@ -316,39 +316,11 @@ class PlayerService:
session = self.db_session_factory()
try:
playlist_repo = PlaylistRepository(session)
# Get main playlist (fallback for now)
current_playlist = await playlist_repo.get_main_playlist()
if current_playlist and current_playlist.id:
# Load playlist sounds
sounds = await playlist_repo.get_playlist_sounds(current_playlist.id)
# Update state
self.state.playlist_id = current_playlist.id
self.state.playlist_name = current_playlist.name
self.state.playlist_sounds = sounds
self.state.playlist_length = len(sounds)
self.state.playlist_duration = sum(
sound.duration or 0 for sound in sounds
)
# Reset current sound if playlist changed
if self.state.current_sound_id and not any(
s.id == self.state.current_sound_id for s in sounds
):
self.state.current_sound_id = None
self.state.current_sound_index = None
self.state.current_sound = None
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
await self._handle_playlist_reload(current_playlist, sounds)
logger.info(
"Loaded playlist: %s (%s sounds)",
current_playlist.name,
@@ -361,6 +333,132 @@ class PlayerService:
await self._broadcast_state()
async def _handle_playlist_reload(
self,
current_playlist: Playlist,
sounds: list[Sound],
) -> None:
"""Handle playlist reload logic with ID comparison."""
# Store previous state for comparison
previous_playlist_id = self.state.playlist_id
previous_current_sound_id = self.state.current_sound_id
previous_current_sound_index = self.state.current_sound_index
# Update basic playlist state
self._update_playlist_state(current_playlist, sounds)
# Handle playlist changes based on ID comparison
if (
current_playlist.id is not None
and previous_playlist_id != current_playlist.id
):
await self._handle_playlist_id_changed(
previous_playlist_id, current_playlist.id, sounds,
)
elif previous_current_sound_id:
await self._handle_same_playlist_track_check(
previous_current_sound_id,
previous_current_sound_index,
sounds,
)
elif sounds:
self._set_first_track_as_current(sounds)
async def _handle_playlist_id_changed(
self,
previous_id: int | None,
current_id: int,
sounds: list[Sound],
) -> None:
"""Handle when playlist ID changes - stop player and reset to first track."""
logger.info(
"Playlist changed from %s to %s - stopping player and resetting",
previous_id,
current_id,
)
if self.state.status != PlayerStatus.STOPPED:
await self._stop_playback()
if sounds:
self._set_first_track_as_current(sounds)
else:
self._clear_current_track()
async def _handle_same_playlist_track_check(
self,
previous_sound_id: int,
previous_index: int | None,
sounds: list[Sound],
) -> None:
"""Handle track checking when playlist ID is the same."""
# Find the current track in the new playlist
new_index = self._find_sound_index(previous_sound_id, sounds)
if new_index is not None:
# Track still exists - update index if it changed
if new_index != previous_index:
logger.info(
"Current track %s moved from index %s to %s",
previous_sound_id,
previous_index,
new_index,
)
# Always set the index and sound reference
self.state.current_sound_index = new_index
self.state.current_sound = sounds[new_index]
else:
# Current track no longer exists in playlist
await self._handle_track_removed(previous_sound_id, sounds)
async def _handle_track_removed(
self,
previous_sound_id: int,
sounds: list[Sound],
) -> None:
"""Handle when current track no longer exists in playlist."""
logger.info(
"Current track %s no longer exists in playlist - stopping and resetting",
previous_sound_id,
)
if self.state.status != PlayerStatus.STOPPED:
await self._stop_playback()
if sounds:
self._set_first_track_as_current(sounds)
else:
self._clear_current_track()
def _update_playlist_state(
self, current_playlist: Playlist, sounds: list[Sound],
) -> None:
"""Update basic playlist state information."""
self.state.playlist_id = current_playlist.id
self.state.playlist_name = current_playlist.name
self.state.playlist_sounds = sounds
self.state.playlist_length = len(sounds)
self.state.playlist_duration = sum(sound.duration or 0 for sound in sounds)
def _find_sound_index(self, sound_id: int, sounds: list[Sound]) -> int | None:
"""Find the index of a sound in the sounds list."""
for i, sound in enumerate(sounds):
if sound.id == sound_id:
return i
return None
def _set_first_track_as_current(self, sounds: list[Sound]) -> None:
"""Set the first track as the current track."""
self.state.current_sound_index = 0
self.state.current_sound = sounds[0]
self.state.current_sound_id = sounds[0].id
def _clear_current_track(self) -> None:
"""Clear the current track state."""
self.state.current_sound_index = None
self.state.current_sound = None
self.state.current_sound_id = None
def get_state(self) -> dict[str, Any]:
"""Get current player state."""
return self.state.to_dict()