use super::media::MediaItem; use super::state::PlayerState; use crate::settings::AudioSettings; /// Error type for player operations #[derive(Debug, Clone)] pub struct PlayerError { pub message: String, } impl std::fmt::Display for PlayerError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.message) } } impl std::error::Error for PlayerError {} impl PlayerError { pub fn not_implemented() -> Self { Self { message: "Not implemented".to_string(), } } /// Create a playback failure error /// /// Only available on Android where ExoPlayer uses it for JNI errors #[cfg(target_os = "android")] pub fn playback_failed>(message: S) -> Self { Self { message: message.into(), } } } /// Player backend trait - implemented by platform-specific players /// /// TRACES: UR-003, UR-004 | IR-003, IR-004 | DR-004 pub trait PlayerBackend: Send + Sync { /// Load a media item for playback /// TRACES: UR-005 fn load(&mut self, media: &MediaItem) -> Result<(), PlayerError>; /// Start or resume playback /// TRACES: UR-005 fn play(&mut self) -> Result<(), PlayerError>; /// Pause playback /// TRACES: UR-005 fn pause(&mut self) -> Result<(), PlayerError>; /// Stop playback and unload media /// TRACES: UR-005 fn stop(&mut self) -> Result<(), PlayerError>; /// Seek to a position in seconds /// TRACES: UR-005 fn seek(&mut self, position: f64) -> Result<(), PlayerError>; /// Set volume (0.0 - 1.0) /// TRACES: UR-016 fn set_volume(&mut self, volume: f32) -> Result<(), PlayerError>; /// Get current playback position in seconds fn position(&self) -> f64; /// Get total duration in seconds fn duration(&self) -> Option; /// Get current player state fn state(&self) -> PlayerState; /// Get current volume fn volume(&self) -> f32; /// Apply audio settings (crossfade, gapless, normalization) /// /// @req-partial: UR-031 (Linux only) - Crossfade between audio tracks /// @req-partial: UR-032 (Linux only) - Gapless playback for seamless album listening /// @req-partial: UR-033 (Linux only) - Volume normalization to prevent volume jumps /// @req: DR-034 - Crossfade engine with configurable duration (0-12s) /// @req: DR-035 - Gapless playback between sequential tracks /// @req: DR-036 - Volume normalization with preset levels (Loud/Normal/Quiet) fn set_audio_settings(&mut self, _settings: &AudioSettings) -> Result<(), PlayerError> { // Default implementation does nothing - override in platform-specific backends Ok(()) } /// Get current audio settings /// /// @req: DR-034 - Crossfade engine /// @req: DR-035 - Gapless playback /// @req: DR-036 - Volume normalization fn audio_settings(&self) -> AudioSettings { AudioSettings::default() } /// Set the active audio track by stream index /// /// Overridden by the Android (ExoPlayer) backend. `MpvBackend` deliberately /// does **not** override it — MPV is the audio-only backend here, so it keeps /// this `not_implemented()` default and the Linux video path switches track by /// re-opening the stream instead (`player_switch_audio_track`). /// /// TRACES: UR-021 | IR-019, DR-024 fn set_audio_track(&mut self, _stream_index: i32) -> Result<(), PlayerError> { // Default implementation does nothing - override in platform-specific backends Err(PlayerError::not_implemented()) } /// Set the active subtitle track by stream index (None to disable subtitles) /// /// Overridden by the Android (ExoPlayer) backend. `MpvBackend` deliberately /// does **not** override it, so it keeps this `not_implemented()` default; /// the Linux video path renders subtitles as `` children of the /// WebKitGTK HTML5 `