diff --git a/app/services/player.py b/app/services/player.py index ecdc2a5..681dd7d 100644 --- a/app/services/player.py +++ b/app/services/player.py @@ -166,94 +166,141 @@ class PlayerService: async def play(self, index: int | None = None) -> None: """Play audio at specified index or current position.""" - # Check if we're resuming from pause - is_resuming = ( + if self._should_resume_playback(index): + await self._resume_playback() + return + + await self._start_new_track(index) + + def _should_resume_playback(self, index: int | None) -> bool: + """Check if we should resume paused playback.""" + return ( index is None and self.state.status == PlayerStatus.PAUSED and self.state.current_sound is not None ) - if is_resuming: - # Simply resume playback - result = self._player.play() - if result == 0: # VLC returns 0 on success - self.state.status = PlayerStatus.PLAYING + async def _resume_playback(self) -> None: + """Resume paused playback.""" + result = self._player.play() + if result == 0: # VLC returns 0 on success + self.state.status = PlayerStatus.PLAYING + self._ensure_play_time_tracking_for_resume() + await self._broadcast_state() - # Ensure play time tracking is initialized for resumed track - if ( - self.state.current_sound_id - and self.state.current_sound_id not in self._play_time_tracking - ): - self._play_time_tracking[self.state.current_sound_id] = { - "total_time": 0, - "last_position": self.state.current_sound_position, - "last_update": time.time(), - "threshold_reached": False, - } + sound_name = ( + self.state.current_sound.name + if self.state.current_sound + else "Unknown" + ) + logger.info("Resumed playing sound: %s", sound_name) + else: + logger.error("Failed to resume playback: VLC error code %s", result) - await self._broadcast_state() - logger.info( - "Resumed playing sound: %s", - ( - self.state.current_sound.name - if self.state.current_sound - else "Unknown" - ), - ) - else: - logger.error("Failed to resume playback: VLC error code %s", result) + def _ensure_play_time_tracking_for_resume(self) -> None: + """Ensure play time tracking is initialized for resumed track.""" + if ( + self.state.current_sound_id + and self.state.current_sound_id not in self._play_time_tracking + ): + self._play_time_tracking[self.state.current_sound_id] = { + "total_time": 0, + "last_position": self.state.current_sound_position, + "last_update": time.time(), + "threshold_reached": False, + } + + async def _start_new_track(self, index: int | None) -> None: + """Start playing a new track.""" + if not self._prepare_sound_for_playback(index): return - # Starting new track or changing track - if index is not None: - if index < 0 or index >= len(self.state.playlist_sounds): - msg = "Invalid sound index" - raise ValueError(msg) - self.state.current_sound_index = index - self.state.current_sound = self.state.playlist_sounds[index] - self.state.current_sound_id = self.state.current_sound.id + if not self._load_and_play_media(): + return + + await self._handle_successful_playback() + + def _prepare_sound_for_playback(self, index: int | None) -> bool: + """Prepare sound for playback, return True if ready.""" + if index is not None and not self._set_sound_by_index(index): + return False if not self.state.current_sound: logger.warning("No sound to play") - return + return False + + return self._validate_sound_file() + + def _set_sound_by_index(self, index: int) -> bool: + """Set current sound by index, return True if valid.""" + if index < 0 or index >= len(self.state.playlist_sounds): + msg = "Invalid sound index" + raise ValueError(msg) + + self.state.current_sound_index = index + self.state.current_sound = self.state.playlist_sounds[index] + self.state.current_sound_id = self.state.current_sound.id + return True + + def _validate_sound_file(self) -> bool: + """Validate sound file exists, return True if valid.""" + if not self.state.current_sound: + return False - # Get sound file path 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 + return False + return True - # Load and play media (new track) + def _load_and_play_media(self) -> bool: + """Load media and start playback, return True if successful.""" if self._vlc_instance is None: logger.error("VLC instance is not initialized. Cannot play media.") - return + return False + if not self.state.current_sound: + logger.error("No current sound to play") + return False + + sound_path = get_sound_file_path(self.state.current_sound) media = self._vlc_instance.media_new(str(sound_path)) self._player.set_media(media) result = self._player.play() - if result == 0: # VLC returns 0 on success - self.state.status = PlayerStatus.PLAYING - self.state.current_sound_duration = self.state.current_sound.duration or 0 - - # Initialize play time tracking for new track - if self.state.current_sound_id: - self._play_time_tracking[self.state.current_sound_id] = { - "total_time": 0, - "last_position": 0, - "last_update": time.time(), - "threshold_reached": False, - } - logger.info( - "Initialized play time tracking for sound %s (duration: %s ms)", - self.state.current_sound_id, - self.state.current_sound_duration, - ) - - await self._broadcast_state() - logger.info("Started playing sound: %s", self.state.current_sound.name) - else: + if result != 0: # VLC returns 0 on success logger.error("Failed to start playback: VLC error code %s", result) + return False + + return True + + async def _handle_successful_playback(self) -> None: + """Handle successful playback start.""" + if not self.state.current_sound: + logger.error("No current sound for successful playback") + return + + self.state.status = PlayerStatus.PLAYING + self.state.current_sound_duration = self.state.current_sound.duration or 0 + + self._initialize_play_time_tracking() + await self._broadcast_state() + logger.info("Started playing sound: %s", self.state.current_sound.name) + + def _initialize_play_time_tracking(self) -> None: + """Initialize play time tracking for new track.""" + if self.state.current_sound_id: + self._play_time_tracking[self.state.current_sound_id] = { + "total_time": 0, + "last_position": 0, + "last_update": time.time(), + "threshold_reached": False, + } + logger.info( + "Initialized play time tracking for sound %s (duration: %s ms)", + self.state.current_sound_id, + self.state.current_sound_duration, + ) async def pause(self) -> None: """Pause playback."""