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}