Browse and listen to thousands of radio stations across the globe right from your terminal ๐ŸŒŽ ๐Ÿ“ป ๐ŸŽตโœจ
radio rust tokio web-radio command-line-tool tui

feat(radiobrowser): use a provider for playing radio stations

+65 -63
+27 -14
src/app.rs
··· 118 118 }, 119 119 frame, 120 120 ); 121 - render_line( 122 - "Now Playing ", 123 - &state.now_playing, 124 - Rect { 125 - x: size.x, 126 - y: size.y + 2, 127 - width: size.width, 128 - height: 1, 129 - }, 130 - frame, 131 - ); 121 + 122 + if !state.now_playing.is_empty() { 123 + render_line( 124 + "Now Playing ", 125 + &state.now_playing, 126 + Rect { 127 + x: size.x, 128 + y: size.y + 2, 129 + width: size.width, 130 + height: 1, 131 + }, 132 + frame, 133 + ); 134 + } 135 + 132 136 render_line( 133 137 "Genre ", 134 138 &state.genre, 135 139 Rect { 136 140 x: size.x, 137 - y: size.y + 3, 141 + y: match state.now_playing.is_empty() { 142 + true => size.y + 2, 143 + false => size.y + 3, 144 + }, 138 145 width: size.width, 139 146 height: 1, 140 147 }, ··· 145 152 &state.description, 146 153 Rect { 147 154 x: size.x, 148 - y: size.y + 4, 155 + y: match state.now_playing.is_empty() { 156 + true => size.y + 3, 157 + false => size.y + 4, 158 + }, 149 159 width: size.width, 150 160 height: 1, 151 161 }, ··· 156 166 &format!("{} kbps", &state.br), 157 167 Rect { 158 168 x: size.x, 159 - y: size.y + 5, 169 + y: match state.now_playing.is_empty() { 170 + true => size.y + 4, 171 + false => size.y + 5, 172 + }, 160 173 width: size.width, 161 174 height: 1, 162 175 },
+9 -47
src/play.rs
··· 2 2 3 3 use anyhow::Error; 4 4 use hyper::header::HeaderValue; 5 - use tunein::TuneInClient; 6 5 7 6 use crate::{ 8 7 app::{App, State}, 9 8 cfg::{SourceOptions, UiOptions}, 10 9 decoder::Mp3Decoder, 11 - extract::{extract_stream_url, get_currently_playing}, 12 10 provider::{radiobrowser::Radiobrowser, tunein::Tunein, Provider}, 13 11 tui, 14 12 }; 15 13 16 14 pub async fn exec(name_or_id: &str, provider: &str) -> Result<(), Error> { 15 + let _provider = provider; 17 16 let provider: Box<dyn Provider> = match provider { 18 17 "tunein" => Box::new(Tunein::new()), 19 18 "radiobrowser" => Box::new(Radiobrowser::new().await), ··· 24 23 ))) 25 24 } 26 25 }; 26 + let station = provider.get_station(name_or_id.to_string()).await?; 27 + if station.is_none() { 28 + return Err(Error::msg("No station found")); 29 + } 27 30 28 - let client = TuneInClient::new(); 29 - let results = client 30 - .get_station(name_or_id) 31 - .await 32 - .map_err(|e| Error::msg(e.to_string()))?; 33 - let (url, playlist_type, _, id) = match results.is_empty() { 34 - true => { 35 - let results = client 36 - .search(name_or_id) 37 - .await 38 - .map_err(|e| Error::msg(e.to_string()))?; 39 - match results.first() { 40 - Some(result) => { 41 - if result.r#type != Some("audio".to_string()) { 42 - return Err(Error::msg("No station found")); 43 - } 44 - let id = result.guide_id.as_ref().unwrap(); 45 - let station = client 46 - .get_station(id) 47 - .await 48 - .map_err(|e| Error::msg(e.to_string()))?; 49 - let station = station.first().unwrap(); 50 - ( 51 - station.url.clone(), 52 - station.playlist_type.clone(), 53 - station.media_type.clone(), 54 - id.clone(), 55 - ) 56 - } 57 - None => ("".to_string(), None, "".to_string(), "".to_string()), 58 - } 59 - } 60 - false => { 61 - let result = results.first().unwrap(); 62 - ( 63 - result.url.clone(), 64 - result.playlist_type.clone(), 65 - result.media_type.clone(), 66 - name_or_id.to_string(), 67 - ) 68 - } 69 - }; 70 - let now_playing = get_currently_playing(&id).await?; 71 - let stream_url = extract_stream_url(&url, playlist_type).await?; 72 - println!("{}", stream_url); 31 + let station = station.unwrap(); 32 + let stream_url = station.stream_url.clone(); 33 + let id = station.id.clone(); 34 + let now_playing = station.playing.clone().unwrap_or_default(); 73 35 74 36 let (cmd_tx, cmd_rx) = tokio::sync::mpsc::unbounded_channel::<State>(); 75 37 let (frame_tx, frame_rx) = std::sync::mpsc::channel::<minimp3::Frame>();
+19 -1
src/provider/tunein.rs
··· 36 36 .await 37 37 .map_err(|e| Error::msg(e.to_string()))?; 38 38 match stations.len() { 39 - 0 => Ok(None), 39 + 0 => { 40 + let results = self.search(id.clone()).await?; 41 + let station = results.first().cloned(); 42 + match station { 43 + Some(st) => { 44 + let stations = self 45 + .client 46 + .get_station(&st.id) 47 + .await 48 + .map_err(|e| Error::msg(e.to_string()))?; 49 + let mut station = Station::from(stations[0].clone()); 50 + station.id = st.id.clone(); 51 + station.name = st.name.clone(); 52 + station.playing = st.playing.clone(); 53 + return Ok(Some(station)); 54 + } 55 + None => Ok(None), 56 + } 57 + } 40 58 _ => Ok(Some(Station::from(stations[0].clone()))), 41 59 } 42 60 }
+10 -1
src/types.rs
··· 1 + use std::thread; 2 + 1 3 use radiobrowser::ApiStation; 2 4 use tunein::types::{SearchResult, StationLinkDetails}; 5 + 6 + use crate::extract::extract_stream_url; 3 7 4 8 #[derive(Debug, Clone)] 5 9 pub struct Station { ··· 60 64 61 65 impl From<StationLinkDetails> for Station { 62 66 fn from(details: StationLinkDetails) -> Station { 67 + let handle = thread::spawn(move || { 68 + let rt = tokio::runtime::Runtime::new().unwrap(); 69 + rt.block_on(extract_stream_url(&details.url, details.playlist_type)) 70 + }); 71 + let stream_url = handle.join().unwrap().unwrap(); 63 72 Station { 64 73 id: Default::default(), 65 74 name: Default::default(), 66 75 bitrate: details.bitrate, 67 - stream_url: details.url, 76 + stream_url, 68 77 codec: details.media_type.to_uppercase(), 69 78 playing: None, 70 79 }