| Crates.io | nowhear |
| lib.rs | nowhear |
| version | 0.1.1 |
| created_at | 2026-01-13 13:54:47.555582+00 |
| updated_at | 2026-01-14 12:09:33.19242+00 |
| description | Cross-platform media playback information watcher |
| homepage | |
| repository | https://github.com/akiomik/nowhear |
| max_upload_size | |
| id | 2040235 |
| size | 204,102 |
Cross-platform library for monitoring media playback information in Rust.
| Platform | API | Supported Players |
|---|---|---|
| Linux | MPRIS (D-Bus) | Spotify, VLC, Rhythmbox, Chromium, Firefox, and any MPRIS-compatible player |
| macOS | AppleScript | Music.app, Spotify |
| Windows | Windows Media Control | Spotify, Windows Media Player, VLC, iTunes, Chrome/Edge, and any SMTC-compatible app |
Add this to your Cargo.toml:
[dependencies]
nowhear = "0.1"
tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] }
futures = "0.3"
use nowhear::{MediaSource, MediaSourceBuilder, Result};
#[tokio::main]
async fn main() -> Result<()> {
// Create a media source
let source = MediaSourceBuilder::new().build().await?;
// List all active media players
let players = source.list_players().await?;
println!("Active players: {players:?}");
// Get information for a specific player
if let Some(player_name) = players.first() {
let info = source.get_player(player_name).await?;
println!("Player: {}", info.player_name);
println!("State: {:?}", info.playback_state);
if let Some(track) = info.current_track {
println!("Track: {} - {}", track.artist.join(", "), track.title);
}
}
Ok(())
}
use futures::StreamExt;
use nowhear::{MediaEvent, MediaSource, MediaSourceBuilder, Result};
#[tokio::main]
async fn main() -> Result<()> {
let source = MediaSourceBuilder::new().build().await?;
// Create an event stream
let mut stream = source.event_stream().await?;
// Process events as they arrive
while let Some(event) = stream.next().await {
match event {
MediaEvent::TrackChanged { player_name, track } => {
println!("🎵 {} - {}", track.artist.join(", "), track.title);
}
MediaEvent::StateChanged { player_name, state } => {
println!("▶️ Playback state: {:?}", state);
}
MediaEvent::PlayerAdded { player_name } => {
println!("➕ Player added: {}", player_name);
}
MediaEvent::PlayerRemoved { player_name } => {
println!("➖ Player removed: {}", player_name);
}
_ => {}
}
}
Ok(())
}
pub trait MediaSource: Send + Sync {
/// List all available players
async fn list_players(&self) -> Result<Vec<String>>;
/// Get information for a specific player
async fn get_player(&self, player_name: &str) -> Result<PlayerInfo>;
/// Create an event stream that yields media events
async fn event_stream(&self) -> Result<EventStream>;
}
pub enum MediaEvent {
/// A new track started playing
TrackChanged { player_name: String, track: Track },
/// Playback state changed (playing, paused, stopped)
StateChanged { player_name: String, state: PlaybackState },
/// Playback position changed (seek)
PositionChanged { player_name: String, position: Duration },
/// Volume changed
VolumeChanged { player_name: String, volume: f64 },
/// A new player appeared
PlayerAdded { player_name: String },
/// A player disappeared
PlayerRemoved { player_name: String },
}
pub struct Track {
pub title: String,
pub artist: Vec<String>,
pub album: Option<String>,
pub album_artist: Option<Vec<String>>,
pub track_number: Option<u32>,
pub duration: Option<Duration>,
pub art_url: Option<String>,
}
Run the included examples:
# Basic usage - list players and get current playback info
cargo run --example basic
# Stream events - monitor media events in real-time
cargo run --example stream
Run build for all platforms:
just build
Run build for a specific platform:
# Linux
just build-linux
# macOS
just build-macos
# Windows
just build-windows