Browse and listen to thousands of radio stations across the globe right from your terminal 🌎 📻 🎵✨
radio
rust
tokio
web-radio
command-line-tool
tui
1//! Operating system level media controls.
2
3use tokio::sync::mpsc::UnboundedReceiver;
4
5/// Operating system level media controls.
6#[derive(Debug)]
7pub struct OsMediaControls {
8 /// Controls that interface with the OS.
9 controls: souvlaki::MediaControls,
10 /// Receiver for events produced by OS level interaction.
11 event_receiver: UnboundedReceiver<souvlaki::MediaControlEvent>,
12}
13
14impl OsMediaControls {
15 /// Create new [`OsMediaControls`].
16 pub fn new() -> Result<Self, souvlaki::Error> {
17 let mut controls = souvlaki::MediaControls::new(souvlaki::PlatformConfig {
18 display_name: "tunein-cli",
19 dbus_name: "tsirysndr.tunein-cli",
20 // TODO: support windows platform
21 hwnd: None,
22 })?;
23
24 let (event_sender, event_receiver) =
25 tokio::sync::mpsc::unbounded_channel::<souvlaki::MediaControlEvent>();
26
27 controls.attach(move |event| {
28 event_sender.send(event).expect("receiver always alive");
29 })?;
30
31 Ok(Self {
32 controls,
33 event_receiver,
34 })
35 }
36
37 /// Try to receive event produced by the operating system.
38 ///
39 /// Is [`None`] if no event is produced.
40 pub fn try_recv_os_event(&mut self) -> Option<souvlaki::MediaControlEvent> {
41 self.event_receiver.try_recv().ok()
42 }
43
44 /// Send the given [`Command`] to the operating system.
45 pub fn send_to_os(&mut self, command: Command) -> Result<(), souvlaki::Error> {
46 match command {
47 Command::Play => self
48 .controls
49 .set_playback(souvlaki::MediaPlayback::Playing { progress: None }),
50 Command::Pause => self
51 .controls
52 .set_playback(souvlaki::MediaPlayback::Paused { progress: None }),
53 Command::SetVolume(volume) => {
54 // NOTE: is supported only for MPRIS backend,
55 // `souvlaki` doesn't provide a way to know this, so
56 // need to use `cfg` attribute like the way it exposes
57 // the platform
58 #[cfg(all(
59 unix,
60 not(any(target_os = "macos", target_os = "ios", target_os = "android"))
61 ))]
62 {
63 self.controls.set_volume(volume)
64 }
65 #[cfg(not(all(
66 unix,
67 not(any(target_os = "macos", target_os = "ios", target_os = "android"))
68 )))]
69 {
70 Ok(())
71 }
72 }
73 Command::SetMetadata(metadata) => self.controls.set_metadata(metadata),
74 }
75 }
76}
77
78/// Commands understood by OS media controls.
79#[derive(Debug, Clone)]
80pub enum Command<'a> {
81 Play,
82 Pause,
83 /// Volume must be between `0.0..=1.0`.
84 SetVolume(f64),
85 /// Set the [`souvlaki::MediaMetadata`].
86 SetMetadata(souvlaki::MediaMetadata<'a>),
87}