Browse and listen to thousands of radio stations across the globe right from your terminal 🌎 📻 🎵✨
radio rust tokio web-radio command-line-tool tui
6
fork

Configure Feed

Select the types of activity you want to include in your feed.

at 477ec0f87e203ee6889e61d434b1b19460a77215 157 lines 4.9 kB view raw
1use std::{thread, time::Duration}; 2 3use anyhow::Error; 4use hyper::header::HeaderValue; 5 6use crate::{ 7 app::{App, CurrentDisplayMode, State, Volume}, 8 cfg::{SourceOptions, UiOptions}, 9 decoder::Mp3Decoder, 10 provider::{radiobrowser::Radiobrowser, tunein::Tunein, Provider}, 11 tui, 12}; 13 14pub async fn exec( 15 name_or_id: &str, 16 provider: &str, 17 volume: f32, 18 display_mode: CurrentDisplayMode, 19) -> Result<(), Error> { 20 let _provider = provider; 21 let provider: Box<dyn Provider> = match provider { 22 "tunein" => Box::new(Tunein::new()), 23 "radiobrowser" => Box::new(Radiobrowser::new().await), 24 _ => { 25 return Err(anyhow::anyhow!(format!( 26 "Unsupported provider '{}'", 27 provider 28 ))) 29 } 30 }; 31 let station = provider.get_station(name_or_id.to_string()).await?; 32 if station.is_none() { 33 return Err(Error::msg("No station found")); 34 } 35 36 let station = station.unwrap(); 37 let stream_url = station.stream_url.clone(); 38 let id = station.id.clone(); 39 let now_playing = station.playing.clone().unwrap_or_default(); 40 41 let (cmd_tx, cmd_rx) = tokio::sync::mpsc::unbounded_channel::<State>(); 42 let (sink_cmd_tx, mut sink_cmd_rx) = tokio::sync::mpsc::unbounded_channel::<SinkCommand>(); 43 let (frame_tx, frame_rx) = std::sync::mpsc::channel::<minimp3::Frame>(); 44 45 let ui = UiOptions { 46 scale: 1.0, 47 scatter: false, 48 no_reference: true, 49 no_ui: true, 50 no_braille: false, 51 }; 52 53 let opts = SourceOptions { 54 channels: 2, 55 buffer: 1152, 56 sample_rate: 44100, 57 tune: None, 58 }; 59 60 let mut app = App::new(&ui, &opts, frame_rx, display_mode); 61 let station_name = station.name.clone(); 62 63 thread::spawn(move || { 64 let client = reqwest::blocking::Client::new(); 65 66 let response = client.get(stream_url).send().unwrap(); 67 68 let headers = response.headers(); 69 let volume = Volume::new(volume, false); 70 71 cmd_tx 72 .send(State { 73 name: match headers 74 .get("icy-name") 75 .unwrap_or(&HeaderValue::from_static("Unknown")) 76 .to_str() 77 .unwrap() 78 { 79 "Unknown" => station_name, 80 name => name.to_string(), 81 }, 82 now_playing, 83 genre: headers 84 .get("icy-genre") 85 .unwrap_or(&HeaderValue::from_static("Unknown")) 86 .to_str() 87 .unwrap() 88 .to_string(), 89 description: headers 90 .get("icy-description") 91 .unwrap_or(&HeaderValue::from_static("Unknown")) 92 .to_str() 93 .unwrap() 94 .to_string(), 95 br: headers 96 .get("icy-br") 97 .unwrap_or(&HeaderValue::from_static("")) 98 .to_str() 99 .unwrap() 100 .to_string(), 101 volume: volume.clone(), 102 }) 103 .unwrap(); 104 let location = response.headers().get("location"); 105 106 let response = match location { 107 Some(location) => { 108 let response = client.get(location.to_str().unwrap()).send().unwrap(); 109 let location = response.headers().get("location"); 110 match location { 111 Some(location) => client.get(location.to_str().unwrap()).send().unwrap(), 112 None => response, 113 } 114 } 115 None => response, 116 }; 117 118 let (_stream, handle) = rodio::OutputStream::try_default().unwrap(); 119 let sink = rodio::Sink::try_new(&handle).unwrap(); 120 sink.set_volume(volume.volume_ratio()); 121 let decoder = Mp3Decoder::new(response, frame_tx).unwrap(); 122 sink.append(decoder); 123 124 loop { 125 while let Ok(sink_cmd) = sink_cmd_rx.try_recv() { 126 match sink_cmd { 127 SinkCommand::Play => { 128 sink.play(); 129 } 130 SinkCommand::Pause => { 131 sink.pause(); 132 } 133 SinkCommand::SetVolume(volume) => { 134 sink.set_volume(volume); 135 } 136 } 137 } 138 std::thread::sleep(Duration::from_millis(10)); 139 } 140 }); 141 142 let mut terminal = tui::init()?; 143 app.run(&mut terminal, cmd_rx, sink_cmd_tx, &id).await; 144 tui::restore()?; 145 Ok(()) 146} 147 148/// Command for a sink. 149#[derive(Debug, Clone, PartialEq)] 150pub enum SinkCommand { 151 /// Play. 152 Play, 153 /// Pause. 154 Pause, 155 /// Set the volume. 156 SetVolume(f32), 157}