//! Messages WebSocket pour la communication Backend ↔ Navigateur use serde::{Deserialize, Serialize}; /// Messages envoyés du Backend → Navigateur #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(tag = "type", rename_all = "snake_case")] #[allow(dead_code)] pub enum ServerMessage { SessionCreated { token: String, renderer_info: RendererInfo, }, /// Envoyé après SessionCreated lors d'une reconnexion pour resynchroniser /// l'état audio du navigateur (URI courante, état de lecture, etc.). StateSync { current_uri: Option, current_metadata: Option, next_uri: Option, next_metadata: Option, playback_state: PlaybackState, position: Option, volume: u16, mute: bool, }, Command { action: TransportAction, #[serde(skip_serializing_if = "Option::is_none")] params: Option, }, SetVolume { volume: u16, }, SetMute { mute: bool, }, Ping, } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] #[allow(dead_code)] pub enum TransportAction { Play, Pause, Stop, Seek, SetUri, SetNextUri, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct CommandParams { #[serde(skip_serializing_if = "Option::is_none")] pub uri: Option, #[serde(skip_serializing_if = "Option::is_none")] pub metadata: Option, #[serde(skip_serializing_if = "Option::is_none")] pub position: Option, } /// Messages envoyés du Navigateur → Backend #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(tag = "type", rename_all = "snake_case")] #[allow(dead_code)] pub enum ClientMessage { Init { capabilities: BrowserCapabilities, }, StateUpdate { state: PlaybackState, }, PositionUpdate { position: String, duration: String, }, MetadataUpdate { metadata: TrackMetadata, }, VolumeUpdate { volume: u16, mute: bool, }, /// Envoyé quand la piste courante se termine naturellement (gapless). /// Le backend fait avancer current → next dans l'état partagé. TrackEnded, Pong, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct BrowserCapabilities { /// Identifiant stable de l'instance navigateur (UUID stocké en localStorage). /// Permet de réutiliser le même renderer UPnP après un reload de page. pub instance_id: String, pub user_agent: String, pub supported_formats: Vec, } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum PlaybackState { Stopped, Playing, Paused, Transitioning, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct TrackMetadata { pub title: Option, pub artist: Option, pub album: Option, pub duration: Option, pub album_art_uri: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct RendererInfo { pub udn: String, pub friendly_name: String, pub model_name: String, pub description_url: String, }