Skip to main content

nowhear/
types.rs

1//! Core types for media playback information.
2//!
3//! This module defines the main data structures used to represent media tracks,
4//! playback states, player information, and events emitted by media players.
5
6use std::time::Duration;
7
8/// Represents a media track with metadata.
9///
10/// This structure contains all available metadata about a track, including basic
11/// information like title and artist, as well as optional metadata such as album
12/// information, track number, duration, and artwork URL.
13///
14/// # Examples
15///
16/// ```
17/// use nowhear::Track;
18/// use std::time::Duration;
19///
20/// let track = Track {
21///     title: "Bohemian Rhapsody".to_string(),
22///     artist: vec!["Queen".to_string()],
23///     album: Some("A Night at the Opera".to_string()),
24///     album_artist: vec!["Queen".to_string()],
25///     track_number: Some(11),
26///     duration: Some(Duration::from_secs(354)),
27///     art_url: None,
28/// };
29/// ```
30#[derive(Debug, Clone, PartialEq, Eq)]
31pub struct Track {
32    /// Track title
33    pub title: String,
34    /// List of artists for this track
35    pub artist: Vec<String>,
36    /// Album name, if available
37    pub album: Option<String>,
38    /// Album artists, if different from track artists
39    pub album_artist: Vec<String>,
40    /// Track number in the album
41    pub track_number: Option<u32>,
42    /// Total duration of the track
43    pub duration: Option<Duration>,
44    /// URL or path to the album artwork
45    pub art_url: Option<String>,
46}
47
48impl Track {
49    /// Creates a track with default "Unknown" values.
50    ///
51    /// This is useful when track information is not available.
52    #[must_use]
53    pub fn unknown() -> Self {
54        Self {
55            title: "Unknown".to_string(),
56            artist: vec![],
57            album: None,
58            album_artist: vec![],
59            track_number: None,
60            duration: None,
61            art_url: None,
62        }
63    }
64}
65
66/// Playback state of a media player.
67///
68/// Represents the current playback state of a media player. This is used in
69/// both [`PlayerInfo`] and [`MediaEvent::StateChanged`].
70#[derive(Debug, Clone, Copy, PartialEq, Eq)]
71pub enum PlaybackState {
72    /// Media is currently playing.
73    Playing,
74    /// Media is paused.
75    ///
76    /// The player has a track loaded but playback is temporarily stopped.
77    Paused,
78    /// Media is stopped or no media is loaded.
79    ///
80    /// The player is idle or has no track loaded.
81    Stopped,
82}
83
84/// Complete information about a media player's current state.
85///
86/// This structure contains comprehensive information about a media player's current state,
87/// including the currently playing track, playback state, position, and volume.
88///
89/// # Examples
90///
91/// ```no_run
92/// use nowhear::{MediaSource, MediaSourceBuilder, Result};
93///
94/// # async fn example() -> Result<()> {
95/// let source = MediaSourceBuilder::new().build().await?;
96/// let player_info = source.get_player("spotify").await?;
97///
98/// if let Some(track) = player_info.current_track {
99///     println!("Playing: {} by {}", track.title, track.artist.join(", "));
100/// }
101/// # Ok(())
102/// # }
103/// ```
104#[derive(Debug, Clone, PartialEq)]
105pub struct PlayerInfo {
106    /// Name or identifier of the player
107    pub player_name: String,
108    /// Currently playing track, if any
109    pub current_track: Option<Track>,
110    /// Current playback state (playing, paused, or stopped)
111    pub playback_state: PlaybackState,
112    /// Current playback position within the track
113    pub position: Option<Duration>,
114    /// Current volume level (0.0 to 1.0)
115    pub volume: Option<f64>,
116}
117
118impl PlayerInfo {
119    /// Creates player info for a player with no active playback.
120    ///
121    /// This is useful when a player is detected but not currently playing any media.
122    /// The returned `PlayerInfo` will have no track, stopped playback state, and
123    /// no position or volume information.
124    ///
125    /// # Arguments
126    ///
127    /// * `player_name` - The name of the player
128    ///
129    /// # Examples
130    ///
131    /// ```
132    /// use nowhear::{PlayerInfo, PlaybackState};
133    ///
134    /// let info = PlayerInfo::empty("spotify");
135    /// assert_eq!(info.player_name, "spotify");
136    /// assert_eq!(info.current_track, None);
137    /// assert_eq!(info.playback_state, PlaybackState::Stopped);
138    /// ```
139    #[must_use]
140    pub fn empty(player_name: impl Into<String>) -> Self {
141        Self {
142            player_name: player_name.into(),
143            current_track: None,
144            playback_state: PlaybackState::Stopped,
145            position: None,
146            volume: None,
147        }
148    }
149}
150
151/// Events emitted by the media source.
152///
153/// These events are generated when media playback state changes across any
154/// monitored player. Subscribe to these events using [`crate::MediaSource::event_stream`].
155///
156/// # Examples
157///
158/// ```no_run
159/// use nowhear::{MediaSource, MediaSourceBuilder, MediaEvent, Result};
160/// use futures::StreamExt;
161///
162/// # async fn example() -> Result<()> {
163/// let source = MediaSourceBuilder::new().build().await?;
164/// let mut stream = source.event_stream().await?;
165///
166/// while let Some(event) = stream.next().await {
167///     match event {
168///         MediaEvent::TrackChanged { player_name, track } => {
169///             println!("{}: Now playing {}", player_name, track.title);
170///         }
171///         MediaEvent::StateChanged { player_name, state } => {
172///             println!("{}: State changed to {:?}", player_name, state);
173///         }
174///         _ => {}
175///     }
176/// }
177/// # Ok(())
178/// # }
179/// ```
180#[derive(Debug, Clone, PartialEq)]
181pub enum MediaEvent {
182    /// A new track started playing.
183    ///
184    /// This event is emitted when the current track changes to a different track.
185    TrackChanged { player_name: String, track: Track },
186
187    /// Playback state changed.
188    ///
189    /// This event is emitted when the player transitions between playing, paused, or stopped states.
190    StateChanged {
191        player_name: String,
192        state: PlaybackState,
193    },
194
195    /// Playback position changed (seek).
196    ///
197    /// This event is emitted when the user seeks to a different position in the track.
198    /// Note: This is not emitted for normal playback progression, only for significant
199    /// position changes (typically > 2 seconds).
200    PositionChanged {
201        player_name: String,
202        position: Duration,
203    },
204
205    /// Volume changed.
206    ///
207    /// This event is emitted when the player's volume level changes.
208    /// The volume value is typically between 0.0 (muted) and 1.0 (maximum).
209    VolumeChanged { player_name: String, volume: f64 },
210
211    /// A new player appeared.
212    ///
213    /// This event is emitted when a new media player starts and becomes available
214    /// for monitoring.
215    PlayerAdded { player_name: String },
216
217    /// A player disappeared.
218    ///
219    /// This event is emitted when a media player stops or becomes unavailable.
220    PlayerRemoved { player_name: String },
221}