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.

implement tunein provider

+162 -16
+2 -2
Cargo.lock
··· 3820 3820 3821 3821 [[package]] 3822 3822 name = "tunein" 3823 - version = "0.1.2" 3823 + version = "0.1.3" 3824 3824 source = "registry+https://github.com/rust-lang/crates.io-index" 3825 - checksum = "fbb48ce12eca4db1649e7d311e782e9b8a341c844cec57b5b8d1e332381e323f" 3825 + checksum = "9d33b9d790869737661ff1f34a95da32a1288373c5b78c66d098cff2df045055" 3826 3826 dependencies = [ 3827 3827 "anyhow", 3828 3828 "m3u",
+1 -1
Cargo.toml
··· 50 50 tokio = {version = "1.24.2", features = ["tokio-macros", "macros", "rt", "rt-multi-thread"]} 51 51 tonic = "0.8.3" 52 52 tonic-web = "0.4.0" 53 - tunein = "0.1.2" 53 + tunein = "0.1.3" 54 54 url = "2.3.1" 55 55 56 56 [build-dependencies]
+1
src/main.rs
··· 13 13 mod provider; 14 14 mod search; 15 15 mod server; 16 + mod tags; 16 17 mod tui; 17 18 mod types; 18 19 mod visualization;
+1 -1
src/provider/mod.rs
··· 8 8 #[async_trait] 9 9 pub trait Provider { 10 10 async fn search(&self, name: String) -> Result<Vec<Station>, Error>; 11 - async fn get_station(&self, id: String) -> Result<(), Error>; 11 + async fn get_station(&self, id: String) -> Result<Option<Station>, Error>; 12 12 async fn browse(&self, category: String) -> Result<Vec<Station>, Error>; 13 13 }
+11 -8
src/provider/radiobrowser.rs
··· 38 38 Ok(stations) 39 39 } 40 40 41 - async fn get_station(&self, name: String) -> Result<(), Error> { 42 - let station = self 41 + async fn get_station(&self, name: String) -> Result<Option<Station>, Error> { 42 + let stations = self 43 43 .client 44 44 .get_stations() 45 45 .name(&name) ··· 47 47 .send() 48 48 .await 49 49 .map_err(|e| anyhow::anyhow!(format!("{}", e)))?; 50 - println!("Station: {:#?}", station); 51 - Ok(()) 50 + match stations.len() { 51 + 0 => Ok(None), 52 + _ => Ok(Some(Station::from(stations[0].clone()))), 53 + } 52 54 } 53 55 54 56 async fn browse(&self, category: String) -> Result<Vec<Station>, Error> { ··· 56 58 .client 57 59 .get_stations() 58 60 .tag(&category) 59 - .limit("20") 61 + .limit("100") 60 62 .send() 61 63 .await 62 64 .map_err(|e| anyhow::anyhow!(format!("{}", e)))?; ··· 80 82 #[tokio::test] 81 83 pub async fn test_get_station() { 82 84 let provider = Radiobrowser::new().await; 83 - let name = "alternariveradio.us".to_string(); 84 - provider.get_station(name).await.unwrap(); 85 + let name = "AlternativeRadio.us".to_string(); 86 + let station = provider.get_station(name).await.unwrap(); 87 + assert!(station.is_some()) 85 88 } 86 89 87 90 #[tokio::test] ··· 92 95 .into_iter() 93 96 .map(|x| Station::from(x)) 94 97 .collect::<Vec<Station>>(); 95 - assert!(stations.len() == 20) 98 + assert!(stations.len() == 100) 96 99 } 97 100 }
+81 -4
src/provider/tunein.rs
··· 3 3 use super::Provider; 4 4 use anyhow::Error; 5 5 use async_trait::async_trait; 6 + use tunein::TuneInClient; 6 7 7 - pub struct Tunein {} 8 + pub struct Tunein { 9 + client: TuneInClient, 10 + } 11 + 12 + impl Tunein { 13 + pub fn new() -> Self { 14 + Self { 15 + client: TuneInClient::new(), 16 + } 17 + } 18 + } 8 19 9 20 #[async_trait] 10 21 impl Provider for Tunein { 11 22 async fn search(&self, name: String) -> Result<Vec<Station>, Error> { 12 - Ok(vec![]) 23 + let results = self 24 + .client 25 + .search(&name) 26 + .await 27 + .map_err(|e| Error::msg(e.to_string()))?; 28 + let stations = results.into_iter().map(|x| Station::from(x)).collect(); 29 + Ok(stations) 13 30 } 14 31 15 - async fn get_station(&self, id: String) -> Result<(), Error> { 16 - Ok(()) 32 + async fn get_station(&self, id: String) -> Result<Option<Station>, Error> { 33 + let stations = self 34 + .client 35 + .get_station(&id) 36 + .await 37 + .map_err(|e| Error::msg(e.to_string()))?; 38 + match stations.len() { 39 + 0 => Ok(None), 40 + _ => Ok(Some(Station::from(stations[0].clone()))), 41 + } 17 42 } 18 43 19 44 async fn browse(&self, category: String) -> Result<Vec<Station>, Error> { 45 + let category = match category.as_str() { 46 + "by location" => Some(tunein::types::Category::ByLocation), 47 + "by language" => Some(tunein::types::Category::ByLanguage), 48 + "sports" => Some(tunein::types::Category::Sports), 49 + "talk" => Some(tunein::types::Category::Talk), 50 + "music" => Some(tunein::types::Category::Music), 51 + "local radio" => Some(tunein::types::Category::LocalRadio), 52 + "podcasts" => Some(tunein::types::Category::Podcasts), 53 + _ => return Err(Error::msg("Invalid category")), 54 + }; 55 + 56 + let stations = self 57 + .client 58 + .browse(category) 59 + .await 60 + .map_err(|e| Error::msg(e.to_string()))?; 61 + 20 62 Ok(vec![]) 21 63 } 22 64 } 65 + 66 + #[cfg(test)] 67 + mod tests { 68 + use super::*; 69 + 70 + #[tokio::test] 71 + pub async fn test_search() { 72 + let provider = Tunein::new(); 73 + let name = "alternativeradio"; 74 + let stations = provider.search(name.to_string()).await.unwrap(); 75 + println!("Search: {:#?}", stations); 76 + assert!(stations.len() > 0) 77 + } 78 + 79 + #[tokio::test] 80 + pub async fn test_get_station() { 81 + let provider = Tunein::new(); 82 + let name = "s288303".to_string(); 83 + let station = provider.get_station(name).await.unwrap(); 84 + println!("Station: {:#?}", station); 85 + assert!(station.is_some()) 86 + } 87 + 88 + #[tokio::test] 89 + pub async fn test_browse() { 90 + let provider = Tunein::new(); 91 + let stations = provider.browse("music".to_string()).await.unwrap(); 92 + let stations = stations 93 + .into_iter() 94 + .map(|x| Station::from(x)) 95 + .collect::<Vec<Station>>(); 96 + println!("Browse: {:#?}", stations); 97 + assert!(stations.len() == 0) 98 + } 99 + }
+36
src/tags.rs
··· 1 + pub const TAGS: &[&str] = &[ 2 + "pop", 3 + "music", 4 + "news", 5 + "rock", 6 + "classical", 7 + "talk", 8 + "hits", 9 + "radio", 10 + "entretenimiento", 11 + "dance", 12 + "oldies", 13 + "estaciรณn", 14 + "80s", 15 + "mรฉxico", 16 + "fm", 17 + "christian", 18 + "public radio", 19 + "jazz", 20 + "mรบsica", 21 + "top 40", 22 + "classic hits", 23 + "90s", 24 + "community radio", 25 + "norteamรฉrica", 26 + "electronic", 27 + "pop music", 28 + "adult contemporary", 29 + "moi merino", 30 + "classic rock", 31 + "latinoamรฉrica", 32 + "espaรฑol", 33 + "country", 34 + "local news", 35 + "alternative", 36 + ];
+29
src/types.rs
··· 1 1 use radiobrowser::ApiStation; 2 + use tunein::types::{SearchResult, StationLinkDetails}; 2 3 3 4 #[derive(Debug, Clone)] 4 5 pub struct Station { ··· 20 21 } 21 22 } 22 23 } 24 + 25 + impl From<SearchResult> for Station { 26 + fn from(result: SearchResult) -> Station { 27 + Station { 28 + id: result.guide_id.unwrap_or_default(), 29 + name: result.text, 30 + bitrate: result 31 + .bitrate 32 + .unwrap_or("0".to_string()) 33 + .parse() 34 + .unwrap_or_default(), 35 + codec: "".to_string(), 36 + stream_url: "".to_string(), 37 + } 38 + } 39 + } 40 + 41 + impl From<StationLinkDetails> for Station { 42 + fn from(details: StationLinkDetails) -> Station { 43 + Station { 44 + id: "".to_string(), 45 + name: "".to_string(), 46 + bitrate: details.bitrate, 47 + stream_url: details.url, 48 + codec: details.media_type.to_uppercase(), 49 + } 50 + } 51 + }