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

fix: preserve original station ID when creating Station from StationLinkDetails

Changed files
+289 -128
src
+235 -125
src/api/tunein.v1alpha1.rs
··· 39 #[derive(Clone, PartialEq, ::prost::Message)] 40 pub struct GetStationDetailsResponse { 41 #[prost(message, optional, tag = "1")] 42 - pub station_link_details: 43 - ::core::option::Option<super::super::objects::v1alpha1::StationLinkDetails>, 44 } 45 #[derive(Clone, PartialEq, ::prost::Message)] 46 pub struct SearchRequest { ··· 61 dead_code, 62 missing_docs, 63 clippy::wildcard_imports, 64 - clippy::let_unit_value 65 )] 66 - use tonic::codegen::http::Uri; 67 use tonic::codegen::*; 68 #[derive(Debug, Clone)] 69 pub struct BrowseServiceClient<T> { 70 inner: tonic::client::Grpc<T>, ··· 108 <T as tonic::client::GrpcService<tonic::body::BoxBody>>::ResponseBody, 109 >, 110 >, 111 - <T as tonic::codegen::Service<http::Request<tonic::body::BoxBody>>>::Error: 112 - Into<StdError> + std::marker::Send + std::marker::Sync, 113 { 114 BrowseServiceClient::new(InterceptedService::new(inner, interceptor)) 115 } ··· 147 pub async fn get_categories( 148 &mut self, 149 request: impl tonic::IntoRequest<super::GetCategoriesRequest>, 150 - ) -> std::result::Result<tonic::Response<super::GetCategoriesResponse>, tonic::Status> 151 - { 152 - self.inner.ready().await.map_err(|e| { 153 - tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 154 - })?; 155 let codec = tonic::codec::ProstCodec::default(); 156 let path = http::uri::PathAndQuery::from_static( 157 "/tunein.v1alpha1.BrowseService/GetCategories", 158 ); 159 let mut req = request.into_request(); 160 - req.extensions_mut().insert(GrpcMethod::new( 161 - "tunein.v1alpha1.BrowseService", 162 - "GetCategories", 163 - )); 164 self.inner.unary(req, path, codec).await 165 } 166 pub async fn browse_category( 167 &mut self, 168 request: impl tonic::IntoRequest<super::BrowseCategoryRequest>, 169 - ) -> std::result::Result<tonic::Response<super::BrowseCategoryResponse>, tonic::Status> 170 - { 171 - self.inner.ready().await.map_err(|e| { 172 - tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 173 - })?; 174 let codec = tonic::codec::ProstCodec::default(); 175 let path = http::uri::PathAndQuery::from_static( 176 "/tunein.v1alpha1.BrowseService/BrowseCategory", 177 ); 178 let mut req = request.into_request(); 179 - req.extensions_mut().insert(GrpcMethod::new( 180 - "tunein.v1alpha1.BrowseService", 181 - "BrowseCategory", 182 - )); 183 self.inner.unary(req, path, codec).await 184 } 185 pub async fn get_station_details( 186 &mut self, 187 request: impl tonic::IntoRequest<super::GetStationDetailsRequest>, 188 - ) -> std::result::Result<tonic::Response<super::GetStationDetailsResponse>, tonic::Status> 189 - { 190 - self.inner.ready().await.map_err(|e| { 191 - tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 192 - })?; 193 let codec = tonic::codec::ProstCodec::default(); 194 let path = http::uri::PathAndQuery::from_static( 195 "/tunein.v1alpha1.BrowseService/GetStationDetails", 196 ); 197 let mut req = request.into_request(); 198 - req.extensions_mut().insert(GrpcMethod::new( 199 - "tunein.v1alpha1.BrowseService", 200 - "GetStationDetails", 201 - )); 202 self.inner.unary(req, path, codec).await 203 } 204 pub async fn search( 205 &mut self, 206 request: impl tonic::IntoRequest<super::SearchRequest>, 207 ) -> std::result::Result<tonic::Response<super::SearchResponse>, tonic::Status> { 208 - self.inner.ready().await.map_err(|e| { 209 - tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 210 - })?; 211 let codec = tonic::codec::ProstCodec::default(); 212 - let path = 213 - http::uri::PathAndQuery::from_static("/tunein.v1alpha1.BrowseService/Search"); 214 let mut req = request.into_request(); 215 req.extensions_mut() 216 .insert(GrpcMethod::new("tunein.v1alpha1.BrowseService", "Search")); ··· 225 dead_code, 226 missing_docs, 227 clippy::wildcard_imports, 228 - clippy::let_unit_value 229 )] 230 use tonic::codegen::*; 231 /// Generated trait containing gRPC methods that should be implemented for use with BrowseServiceServer. ··· 234 async fn get_categories( 235 &self, 236 request: tonic::Request<super::GetCategoriesRequest>, 237 - ) -> std::result::Result<tonic::Response<super::GetCategoriesResponse>, tonic::Status>; 238 async fn browse_category( 239 &self, 240 request: tonic::Request<super::BrowseCategoryRequest>, 241 - ) -> std::result::Result<tonic::Response<super::BrowseCategoryResponse>, tonic::Status>; 242 async fn get_station_details( 243 &self, 244 request: tonic::Request<super::GetStationDetailsRequest>, 245 - ) -> std::result::Result<tonic::Response<super::GetStationDetailsResponse>, tonic::Status>; 246 async fn search( 247 &self, 248 request: tonic::Request<super::SearchRequest>, ··· 269 max_encoding_message_size: None, 270 } 271 } 272 - pub fn with_interceptor<F>(inner: T, interceptor: F) -> InterceptedService<Self, F> 273 where 274 F: tonic::service::Interceptor, 275 { ··· 324 "/tunein.v1alpha1.BrowseService/GetCategories" => { 325 #[allow(non_camel_case_types)] 326 struct GetCategoriesSvc<T: BrowseService>(pub Arc<T>); 327 - impl<T: BrowseService> tonic::server::UnaryService<super::GetCategoriesRequest> 328 - for GetCategoriesSvc<T> 329 - { 330 type Response = super::GetCategoriesResponse; 331 - type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>; 332 fn call( 333 &mut self, 334 request: tonic::Request<super::GetCategoriesRequest>, ··· 365 "/tunein.v1alpha1.BrowseService/BrowseCategory" => { 366 #[allow(non_camel_case_types)] 367 struct BrowseCategorySvc<T: BrowseService>(pub Arc<T>); 368 - impl<T: BrowseService> tonic::server::UnaryService<super::BrowseCategoryRequest> 369 - for BrowseCategorySvc<T> 370 - { 371 type Response = super::BrowseCategoryResponse; 372 - type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>; 373 fn call( 374 &mut self, 375 request: tonic::Request<super::BrowseCategoryRequest>, ··· 406 "/tunein.v1alpha1.BrowseService/GetStationDetails" => { 407 #[allow(non_camel_case_types)] 408 struct GetStationDetailsSvc<T: BrowseService>(pub Arc<T>); 409 - impl<T: BrowseService> 410 - tonic::server::UnaryService<super::GetStationDetailsRequest> 411 - for GetStationDetailsSvc<T> 412 - { 413 type Response = super::GetStationDetailsResponse; 414 - type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>; 415 fn call( 416 &mut self, 417 request: tonic::Request<super::GetStationDetailsRequest>, 418 ) -> Self::Future { 419 let inner = Arc::clone(&self.0); 420 let fut = async move { 421 - <T as BrowseService>::get_station_details(&inner, request).await 422 }; 423 Box::pin(fut) 424 } ··· 448 "/tunein.v1alpha1.BrowseService/Search" => { 449 #[allow(non_camel_case_types)] 450 struct SearchSvc<T: BrowseService>(pub Arc<T>); 451 - impl<T: BrowseService> tonic::server::UnaryService<super::SearchRequest> for SearchSvc<T> { 452 type Response = super::SearchResponse; 453 - type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>; 454 fn call( 455 &mut self, 456 request: tonic::Request<super::SearchRequest>, 457 ) -> Self::Future { 458 let inner = Arc::clone(&self.0); 459 - let fut = 460 - async move { <T as BrowseService>::search(&inner, request).await }; 461 Box::pin(fut) 462 } 463 } ··· 483 }; 484 Box::pin(fut) 485 } 486 - _ => Box::pin(async move { 487 - let mut response = http::Response::new(empty_body()); 488 - let headers = response.headers_mut(); 489 - headers.insert( 490 - tonic::Status::GRPC_STATUS, 491 - (tonic::Code::Unimplemented as i32).into(), 492 - ); 493 - headers.insert( 494 - http::header::CONTENT_TYPE, 495 - tonic::metadata::GRPC_CONTENT_TYPE, 496 - ); 497 - Ok(response) 498 - }), 499 } 500 } 501 } ··· 541 dead_code, 542 missing_docs, 543 clippy::wildcard_imports, 544 - clippy::let_unit_value 545 )] 546 - use tonic::codegen::http::Uri; 547 use tonic::codegen::*; 548 #[derive(Debug, Clone)] 549 pub struct PlaybackServiceClient<T> { 550 inner: tonic::client::Grpc<T>, ··· 588 <T as tonic::client::GrpcService<tonic::body::BoxBody>>::ResponseBody, 589 >, 590 >, 591 - <T as tonic::codegen::Service<http::Request<tonic::body::BoxBody>>>::Error: 592 - Into<StdError> + std::marker::Send + std::marker::Sync, 593 { 594 PlaybackServiceClient::new(InterceptedService::new(inner, interceptor)) 595 } ··· 628 &mut self, 629 request: impl tonic::IntoRequest<super::PlayRequest>, 630 ) -> std::result::Result<tonic::Response<super::PlayResponse>, tonic::Status> { 631 - self.inner.ready().await.map_err(|e| { 632 - tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 633 - })?; 634 let codec = tonic::codec::ProstCodec::default(); 635 - let path = 636 - http::uri::PathAndQuery::from_static("/tunein.v1alpha1.PlaybackService/Play"); 637 let mut req = request.into_request(); 638 req.extensions_mut() 639 .insert(GrpcMethod::new("tunein.v1alpha1.PlaybackService", "Play")); ··· 643 &mut self, 644 request: impl tonic::IntoRequest<super::StopRequest>, 645 ) -> std::result::Result<tonic::Response<super::StopResponse>, tonic::Status> { 646 - self.inner.ready().await.map_err(|e| { 647 - tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 648 - })?; 649 let codec = tonic::codec::ProstCodec::default(); 650 - let path = 651 - http::uri::PathAndQuery::from_static("/tunein.v1alpha1.PlaybackService/Stop"); 652 let mut req = request.into_request(); 653 req.extensions_mut() 654 .insert(GrpcMethod::new("tunein.v1alpha1.PlaybackService", "Stop")); ··· 657 pub async fn play_or_pause( 658 &mut self, 659 request: impl tonic::IntoRequest<super::PlayOrPauseRequest>, 660 - ) -> std::result::Result<tonic::Response<super::PlayOrPauseResponse>, tonic::Status> 661 - { 662 - self.inner.ready().await.map_err(|e| { 663 - tonic::Status::unknown(format!("Service was not ready: {}", e.into())) 664 - })?; 665 let codec = tonic::codec::ProstCodec::default(); 666 let path = http::uri::PathAndQuery::from_static( 667 "/tunein.v1alpha1.PlaybackService/PlayOrPause", 668 ); 669 let mut req = request.into_request(); 670 - req.extensions_mut().insert(GrpcMethod::new( 671 - "tunein.v1alpha1.PlaybackService", 672 - "PlayOrPause", 673 - )); 674 self.inner.unary(req, path, codec).await 675 } 676 } ··· 682 dead_code, 683 missing_docs, 684 clippy::wildcard_imports, 685 - clippy::let_unit_value 686 )] 687 use tonic::codegen::*; 688 /// Generated trait containing gRPC methods that should be implemented for use with PlaybackServiceServer. ··· 699 async fn play_or_pause( 700 &self, 701 request: tonic::Request<super::PlayOrPauseRequest>, 702 - ) -> std::result::Result<tonic::Response<super::PlayOrPauseResponse>, tonic::Status>; 703 } 704 #[derive(Debug)] 705 pub struct PlaybackServiceServer<T> { ··· 722 max_encoding_message_size: None, 723 } 724 } 725 - pub fn with_interceptor<F>(inner: T, interceptor: F) -> InterceptedService<Self, F> 726 where 727 F: tonic::service::Interceptor, 728 { ··· 777 "/tunein.v1alpha1.PlaybackService/Play" => { 778 #[allow(non_camel_case_types)] 779 struct PlaySvc<T: PlaybackService>(pub Arc<T>); 780 - impl<T: PlaybackService> tonic::server::UnaryService<super::PlayRequest> for PlaySvc<T> { 781 type Response = super::PlayResponse; 782 - type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>; 783 fn call( 784 &mut self, 785 request: tonic::Request<super::PlayRequest>, 786 ) -> Self::Future { 787 let inner = Arc::clone(&self.0); 788 - let fut = 789 - async move { <T as PlaybackService>::play(&inner, request).await }; 790 Box::pin(fut) 791 } 792 } ··· 815 "/tunein.v1alpha1.PlaybackService/Stop" => { 816 #[allow(non_camel_case_types)] 817 struct StopSvc<T: PlaybackService>(pub Arc<T>); 818 - impl<T: PlaybackService> tonic::server::UnaryService<super::StopRequest> for StopSvc<T> { 819 type Response = super::StopResponse; 820 - type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>; 821 fn call( 822 &mut self, 823 request: tonic::Request<super::StopRequest>, 824 ) -> Self::Future { 825 let inner = Arc::clone(&self.0); 826 - let fut = 827 - async move { <T as PlaybackService>::stop(&inner, request).await }; 828 Box::pin(fut) 829 } 830 } ··· 853 "/tunein.v1alpha1.PlaybackService/PlayOrPause" => { 854 #[allow(non_camel_case_types)] 855 struct PlayOrPauseSvc<T: PlaybackService>(pub Arc<T>); 856 - impl<T: PlaybackService> tonic::server::UnaryService<super::PlayOrPauseRequest> 857 - for PlayOrPauseSvc<T> 858 - { 859 type Response = super::PlayOrPauseResponse; 860 - type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>; 861 fn call( 862 &mut self, 863 request: tonic::Request<super::PlayOrPauseRequest>, ··· 891 }; 892 Box::pin(fut) 893 } 894 - _ => Box::pin(async move { 895 - let mut response = http::Response::new(empty_body()); 896 - let headers = response.headers_mut(); 897 - headers.insert( 898 - tonic::Status::GRPC_STATUS, 899 - (tonic::Code::Unimplemented as i32).into(), 900 - ); 901 - headers.insert( 902 - http::header::CONTENT_TYPE, 903 - tonic::metadata::GRPC_CONTENT_TYPE, 904 - ); 905 - Ok(response) 906 - }), 907 } 908 } 909 }
··· 39 #[derive(Clone, PartialEq, ::prost::Message)] 40 pub struct GetStationDetailsResponse { 41 #[prost(message, optional, tag = "1")] 42 + pub station_link_details: ::core::option::Option< 43 + super::super::objects::v1alpha1::StationLinkDetails, 44 + >, 45 } 46 #[derive(Clone, PartialEq, ::prost::Message)] 47 pub struct SearchRequest { ··· 62 dead_code, 63 missing_docs, 64 clippy::wildcard_imports, 65 + clippy::let_unit_value, 66 )] 67 use tonic::codegen::*; 68 + use tonic::codegen::http::Uri; 69 #[derive(Debug, Clone)] 70 pub struct BrowseServiceClient<T> { 71 inner: tonic::client::Grpc<T>, ··· 109 <T as tonic::client::GrpcService<tonic::body::BoxBody>>::ResponseBody, 110 >, 111 >, 112 + <T as tonic::codegen::Service< 113 + http::Request<tonic::body::BoxBody>, 114 + >>::Error: Into<StdError> + std::marker::Send + std::marker::Sync, 115 { 116 BrowseServiceClient::new(InterceptedService::new(inner, interceptor)) 117 } ··· 149 pub async fn get_categories( 150 &mut self, 151 request: impl tonic::IntoRequest<super::GetCategoriesRequest>, 152 + ) -> std::result::Result< 153 + tonic::Response<super::GetCategoriesResponse>, 154 + tonic::Status, 155 + > { 156 + self.inner 157 + .ready() 158 + .await 159 + .map_err(|e| { 160 + tonic::Status::unknown( 161 + format!("Service was not ready: {}", e.into()), 162 + ) 163 + })?; 164 let codec = tonic::codec::ProstCodec::default(); 165 let path = http::uri::PathAndQuery::from_static( 166 "/tunein.v1alpha1.BrowseService/GetCategories", 167 ); 168 let mut req = request.into_request(); 169 + req.extensions_mut() 170 + .insert( 171 + GrpcMethod::new("tunein.v1alpha1.BrowseService", "GetCategories"), 172 + ); 173 self.inner.unary(req, path, codec).await 174 } 175 pub async fn browse_category( 176 &mut self, 177 request: impl tonic::IntoRequest<super::BrowseCategoryRequest>, 178 + ) -> std::result::Result< 179 + tonic::Response<super::BrowseCategoryResponse>, 180 + tonic::Status, 181 + > { 182 + self.inner 183 + .ready() 184 + .await 185 + .map_err(|e| { 186 + tonic::Status::unknown( 187 + format!("Service was not ready: {}", e.into()), 188 + ) 189 + })?; 190 let codec = tonic::codec::ProstCodec::default(); 191 let path = http::uri::PathAndQuery::from_static( 192 "/tunein.v1alpha1.BrowseService/BrowseCategory", 193 ); 194 let mut req = request.into_request(); 195 + req.extensions_mut() 196 + .insert( 197 + GrpcMethod::new("tunein.v1alpha1.BrowseService", "BrowseCategory"), 198 + ); 199 self.inner.unary(req, path, codec).await 200 } 201 pub async fn get_station_details( 202 &mut self, 203 request: impl tonic::IntoRequest<super::GetStationDetailsRequest>, 204 + ) -> std::result::Result< 205 + tonic::Response<super::GetStationDetailsResponse>, 206 + tonic::Status, 207 + > { 208 + self.inner 209 + .ready() 210 + .await 211 + .map_err(|e| { 212 + tonic::Status::unknown( 213 + format!("Service was not ready: {}", e.into()), 214 + ) 215 + })?; 216 let codec = tonic::codec::ProstCodec::default(); 217 let path = http::uri::PathAndQuery::from_static( 218 "/tunein.v1alpha1.BrowseService/GetStationDetails", 219 ); 220 let mut req = request.into_request(); 221 + req.extensions_mut() 222 + .insert( 223 + GrpcMethod::new("tunein.v1alpha1.BrowseService", "GetStationDetails"), 224 + ); 225 self.inner.unary(req, path, codec).await 226 } 227 pub async fn search( 228 &mut self, 229 request: impl tonic::IntoRequest<super::SearchRequest>, 230 ) -> std::result::Result<tonic::Response<super::SearchResponse>, tonic::Status> { 231 + self.inner 232 + .ready() 233 + .await 234 + .map_err(|e| { 235 + tonic::Status::unknown( 236 + format!("Service was not ready: {}", e.into()), 237 + ) 238 + })?; 239 let codec = tonic::codec::ProstCodec::default(); 240 + let path = http::uri::PathAndQuery::from_static( 241 + "/tunein.v1alpha1.BrowseService/Search", 242 + ); 243 let mut req = request.into_request(); 244 req.extensions_mut() 245 .insert(GrpcMethod::new("tunein.v1alpha1.BrowseService", "Search")); ··· 254 dead_code, 255 missing_docs, 256 clippy::wildcard_imports, 257 + clippy::let_unit_value, 258 )] 259 use tonic::codegen::*; 260 /// Generated trait containing gRPC methods that should be implemented for use with BrowseServiceServer. ··· 263 async fn get_categories( 264 &self, 265 request: tonic::Request<super::GetCategoriesRequest>, 266 + ) -> std::result::Result< 267 + tonic::Response<super::GetCategoriesResponse>, 268 + tonic::Status, 269 + >; 270 async fn browse_category( 271 &self, 272 request: tonic::Request<super::BrowseCategoryRequest>, 273 + ) -> std::result::Result< 274 + tonic::Response<super::BrowseCategoryResponse>, 275 + tonic::Status, 276 + >; 277 async fn get_station_details( 278 &self, 279 request: tonic::Request<super::GetStationDetailsRequest>, 280 + ) -> std::result::Result< 281 + tonic::Response<super::GetStationDetailsResponse>, 282 + tonic::Status, 283 + >; 284 async fn search( 285 &self, 286 request: tonic::Request<super::SearchRequest>, ··· 307 max_encoding_message_size: None, 308 } 309 } 310 + pub fn with_interceptor<F>( 311 + inner: T, 312 + interceptor: F, 313 + ) -> InterceptedService<Self, F> 314 where 315 F: tonic::service::Interceptor, 316 { ··· 365 "/tunein.v1alpha1.BrowseService/GetCategories" => { 366 #[allow(non_camel_case_types)] 367 struct GetCategoriesSvc<T: BrowseService>(pub Arc<T>); 368 + impl< 369 + T: BrowseService, 370 + > tonic::server::UnaryService<super::GetCategoriesRequest> 371 + for GetCategoriesSvc<T> { 372 type Response = super::GetCategoriesResponse; 373 + type Future = BoxFuture< 374 + tonic::Response<Self::Response>, 375 + tonic::Status, 376 + >; 377 fn call( 378 &mut self, 379 request: tonic::Request<super::GetCategoriesRequest>, ··· 410 "/tunein.v1alpha1.BrowseService/BrowseCategory" => { 411 #[allow(non_camel_case_types)] 412 struct BrowseCategorySvc<T: BrowseService>(pub Arc<T>); 413 + impl< 414 + T: BrowseService, 415 + > tonic::server::UnaryService<super::BrowseCategoryRequest> 416 + for BrowseCategorySvc<T> { 417 type Response = super::BrowseCategoryResponse; 418 + type Future = BoxFuture< 419 + tonic::Response<Self::Response>, 420 + tonic::Status, 421 + >; 422 fn call( 423 &mut self, 424 request: tonic::Request<super::BrowseCategoryRequest>, ··· 455 "/tunein.v1alpha1.BrowseService/GetStationDetails" => { 456 #[allow(non_camel_case_types)] 457 struct GetStationDetailsSvc<T: BrowseService>(pub Arc<T>); 458 + impl< 459 + T: BrowseService, 460 + > tonic::server::UnaryService<super::GetStationDetailsRequest> 461 + for GetStationDetailsSvc<T> { 462 type Response = super::GetStationDetailsResponse; 463 + type Future = BoxFuture< 464 + tonic::Response<Self::Response>, 465 + tonic::Status, 466 + >; 467 fn call( 468 &mut self, 469 request: tonic::Request<super::GetStationDetailsRequest>, 470 ) -> Self::Future { 471 let inner = Arc::clone(&self.0); 472 let fut = async move { 473 + <T as BrowseService>::get_station_details(&inner, request) 474 + .await 475 }; 476 Box::pin(fut) 477 } ··· 501 "/tunein.v1alpha1.BrowseService/Search" => { 502 #[allow(non_camel_case_types)] 503 struct SearchSvc<T: BrowseService>(pub Arc<T>); 504 + impl< 505 + T: BrowseService, 506 + > tonic::server::UnaryService<super::SearchRequest> 507 + for SearchSvc<T> { 508 type Response = super::SearchResponse; 509 + type Future = BoxFuture< 510 + tonic::Response<Self::Response>, 511 + tonic::Status, 512 + >; 513 fn call( 514 &mut self, 515 request: tonic::Request<super::SearchRequest>, 516 ) -> Self::Future { 517 let inner = Arc::clone(&self.0); 518 + let fut = async move { 519 + <T as BrowseService>::search(&inner, request).await 520 + }; 521 Box::pin(fut) 522 } 523 } ··· 543 }; 544 Box::pin(fut) 545 } 546 + _ => { 547 + Box::pin(async move { 548 + let mut response = http::Response::new(empty_body()); 549 + let headers = response.headers_mut(); 550 + headers 551 + .insert( 552 + tonic::Status::GRPC_STATUS, 553 + (tonic::Code::Unimplemented as i32).into(), 554 + ); 555 + headers 556 + .insert( 557 + http::header::CONTENT_TYPE, 558 + tonic::metadata::GRPC_CONTENT_TYPE, 559 + ); 560 + Ok(response) 561 + }) 562 + } 563 } 564 } 565 } ··· 605 dead_code, 606 missing_docs, 607 clippy::wildcard_imports, 608 + clippy::let_unit_value, 609 )] 610 use tonic::codegen::*; 611 + use tonic::codegen::http::Uri; 612 #[derive(Debug, Clone)] 613 pub struct PlaybackServiceClient<T> { 614 inner: tonic::client::Grpc<T>, ··· 652 <T as tonic::client::GrpcService<tonic::body::BoxBody>>::ResponseBody, 653 >, 654 >, 655 + <T as tonic::codegen::Service< 656 + http::Request<tonic::body::BoxBody>, 657 + >>::Error: Into<StdError> + std::marker::Send + std::marker::Sync, 658 { 659 PlaybackServiceClient::new(InterceptedService::new(inner, interceptor)) 660 } ··· 693 &mut self, 694 request: impl tonic::IntoRequest<super::PlayRequest>, 695 ) -> std::result::Result<tonic::Response<super::PlayResponse>, tonic::Status> { 696 + self.inner 697 + .ready() 698 + .await 699 + .map_err(|e| { 700 + tonic::Status::unknown( 701 + format!("Service was not ready: {}", e.into()), 702 + ) 703 + })?; 704 let codec = tonic::codec::ProstCodec::default(); 705 + let path = http::uri::PathAndQuery::from_static( 706 + "/tunein.v1alpha1.PlaybackService/Play", 707 + ); 708 let mut req = request.into_request(); 709 req.extensions_mut() 710 .insert(GrpcMethod::new("tunein.v1alpha1.PlaybackService", "Play")); ··· 714 &mut self, 715 request: impl tonic::IntoRequest<super::StopRequest>, 716 ) -> std::result::Result<tonic::Response<super::StopResponse>, tonic::Status> { 717 + self.inner 718 + .ready() 719 + .await 720 + .map_err(|e| { 721 + tonic::Status::unknown( 722 + format!("Service was not ready: {}", e.into()), 723 + ) 724 + })?; 725 let codec = tonic::codec::ProstCodec::default(); 726 + let path = http::uri::PathAndQuery::from_static( 727 + "/tunein.v1alpha1.PlaybackService/Stop", 728 + ); 729 let mut req = request.into_request(); 730 req.extensions_mut() 731 .insert(GrpcMethod::new("tunein.v1alpha1.PlaybackService", "Stop")); ··· 734 pub async fn play_or_pause( 735 &mut self, 736 request: impl tonic::IntoRequest<super::PlayOrPauseRequest>, 737 + ) -> std::result::Result< 738 + tonic::Response<super::PlayOrPauseResponse>, 739 + tonic::Status, 740 + > { 741 + self.inner 742 + .ready() 743 + .await 744 + .map_err(|e| { 745 + tonic::Status::unknown( 746 + format!("Service was not ready: {}", e.into()), 747 + ) 748 + })?; 749 let codec = tonic::codec::ProstCodec::default(); 750 let path = http::uri::PathAndQuery::from_static( 751 "/tunein.v1alpha1.PlaybackService/PlayOrPause", 752 ); 753 let mut req = request.into_request(); 754 + req.extensions_mut() 755 + .insert( 756 + GrpcMethod::new("tunein.v1alpha1.PlaybackService", "PlayOrPause"), 757 + ); 758 self.inner.unary(req, path, codec).await 759 } 760 } ··· 766 dead_code, 767 missing_docs, 768 clippy::wildcard_imports, 769 + clippy::let_unit_value, 770 )] 771 use tonic::codegen::*; 772 /// Generated trait containing gRPC methods that should be implemented for use with PlaybackServiceServer. ··· 783 async fn play_or_pause( 784 &self, 785 request: tonic::Request<super::PlayOrPauseRequest>, 786 + ) -> std::result::Result< 787 + tonic::Response<super::PlayOrPauseResponse>, 788 + tonic::Status, 789 + >; 790 } 791 #[derive(Debug)] 792 pub struct PlaybackServiceServer<T> { ··· 809 max_encoding_message_size: None, 810 } 811 } 812 + pub fn with_interceptor<F>( 813 + inner: T, 814 + interceptor: F, 815 + ) -> InterceptedService<Self, F> 816 where 817 F: tonic::service::Interceptor, 818 { ··· 867 "/tunein.v1alpha1.PlaybackService/Play" => { 868 #[allow(non_camel_case_types)] 869 struct PlaySvc<T: PlaybackService>(pub Arc<T>); 870 + impl< 871 + T: PlaybackService, 872 + > tonic::server::UnaryService<super::PlayRequest> for PlaySvc<T> { 873 type Response = super::PlayResponse; 874 + type Future = BoxFuture< 875 + tonic::Response<Self::Response>, 876 + tonic::Status, 877 + >; 878 fn call( 879 &mut self, 880 request: tonic::Request<super::PlayRequest>, 881 ) -> Self::Future { 882 let inner = Arc::clone(&self.0); 883 + let fut = async move { 884 + <T as PlaybackService>::play(&inner, request).await 885 + }; 886 Box::pin(fut) 887 } 888 } ··· 911 "/tunein.v1alpha1.PlaybackService/Stop" => { 912 #[allow(non_camel_case_types)] 913 struct StopSvc<T: PlaybackService>(pub Arc<T>); 914 + impl< 915 + T: PlaybackService, 916 + > tonic::server::UnaryService<super::StopRequest> for StopSvc<T> { 917 type Response = super::StopResponse; 918 + type Future = BoxFuture< 919 + tonic::Response<Self::Response>, 920 + tonic::Status, 921 + >; 922 fn call( 923 &mut self, 924 request: tonic::Request<super::StopRequest>, 925 ) -> Self::Future { 926 let inner = Arc::clone(&self.0); 927 + let fut = async move { 928 + <T as PlaybackService>::stop(&inner, request).await 929 + }; 930 Box::pin(fut) 931 } 932 } ··· 955 "/tunein.v1alpha1.PlaybackService/PlayOrPause" => { 956 #[allow(non_camel_case_types)] 957 struct PlayOrPauseSvc<T: PlaybackService>(pub Arc<T>); 958 + impl< 959 + T: PlaybackService, 960 + > tonic::server::UnaryService<super::PlayOrPauseRequest> 961 + for PlayOrPauseSvc<T> { 962 type Response = super::PlayOrPauseResponse; 963 + type Future = BoxFuture< 964 + tonic::Response<Self::Response>, 965 + tonic::Status, 966 + >; 967 fn call( 968 &mut self, 969 request: tonic::Request<super::PlayOrPauseRequest>, ··· 997 }; 998 Box::pin(fut) 999 } 1000 + _ => { 1001 + Box::pin(async move { 1002 + let mut response = http::Response::new(empty_body()); 1003 + let headers = response.headers_mut(); 1004 + headers 1005 + .insert( 1006 + tonic::Status::GRPC_STATUS, 1007 + (tonic::Code::Unimplemented as i32).into(), 1008 + ); 1009 + headers 1010 + .insert( 1011 + http::header::CONTENT_TYPE, 1012 + tonic::metadata::GRPC_CONTENT_TYPE, 1013 + ); 1014 + Ok(response) 1015 + }) 1016 + } 1017 } 1018 } 1019 }
+1 -1
src/app.rs
··· 887 } 888 889 /// Send [`os_media_controls::Command`]. 890 - fn send_os_media_controls_command( 891 os_media_controls: Option<&mut OsMediaControls>, 892 command: os_media_controls::Command<'_>, 893 ) {
··· 887 } 888 889 /// Send [`os_media_controls::Command`]. 890 + pub fn send_os_media_controls_command( 891 os_media_controls: Option<&mut OsMediaControls>, 892 command: os_media_controls::Command<'_>, 893 ) {
+47 -1
src/interactive.rs
··· 7 use ratatui::prelude::*; 8 use ratatui::widgets::{Block, Borders, List, ListItem, ListState, Paragraph}; 9 use tokio::sync::mpsc; 10 11 use crate::audio::{AudioController, PlaybackEvent, PlaybackState}; 12 use crate::extract::get_currently_playing; 13 use crate::favorites::{FavoriteStation, FavoritesStore}; ··· 42 let (input_tx, mut input_rx) = mpsc::unbounded_channel(); 43 spawn_input_thread(input_tx.clone()); 44 45 let mut app = HubApp::new( 46 provider_name.to_string(), 47 provider, 48 audio, 49 favorites, 50 metadata_tx, 51 ); 52 53 let result = loop { ··· 103 metadata_tx: mpsc::UnboundedSender<HubMessage>, 104 now_playing_station_id: Option<String>, 105 next_now_playing_poll: Instant, 106 } 107 108 impl HubApp { ··· 112 audio: AudioController, 113 favorites: FavoritesStore, 114 metadata_tx: mpsc::UnboundedSender<HubMessage>, 115 ) -> Self { 116 let mut ui = UiState::default(); 117 ui.menu_state.select(Some(0)); ··· 129 metadata_tx, 130 now_playing_station_id: None, 131 next_now_playing_poll: Instant::now(), 132 } 133 } 134 ··· 202 }) 203 }) 204 .unwrap_or_else(|| "Unknown".to_string()); 205 - self.render_labeled_line(frame, area, row, "Station ", &station_name); 206 row += 1; 207 208 let now_playing = self ··· 969 self.current_playback = Some(state.clone()); 970 if let Some(station) = self.current_station.as_mut() { 971 station.station.playing = Some(state.now_playing.clone()); 972 } 973 self.set_status(&format!("Now playing {}", state.stream_name)); 974 self.prepare_now_playing_poll(); ··· 996 station.station.playing = Some(now_playing.clone()); 997 } 998 self.set_status(&format!("Now Playing {}", now_playing)); 999 } 1000 } 1001 }
··· 7 use ratatui::prelude::*; 8 use ratatui::widgets::{Block, Borders, List, ListItem, ListState, Paragraph}; 9 use tokio::sync::mpsc; 10 + use tunein_cli::os_media_controls::{self, OsMediaControls}; 11 12 + use crate::app::send_os_media_controls_command; 13 use crate::audio::{AudioController, PlaybackEvent, PlaybackState}; 14 use crate::extract::get_currently_playing; 15 use crate::favorites::{FavoriteStation, FavoritesStore}; ··· 44 let (input_tx, mut input_rx) = mpsc::unbounded_channel(); 45 spawn_input_thread(input_tx.clone()); 46 47 + let os_media_controls = OsMediaControls::new() 48 + .inspect_err(|err| { 49 + eprintln!( 50 + "error: failed to initialize os media controls due to `{}`", 51 + err 52 + ); 53 + }) 54 + .ok(); 55 + 56 let mut app = HubApp::new( 57 provider_name.to_string(), 58 provider, 59 audio, 60 favorites, 61 metadata_tx, 62 + os_media_controls, 63 ); 64 65 let result = loop { ··· 115 metadata_tx: mpsc::UnboundedSender<HubMessage>, 116 now_playing_station_id: Option<String>, 117 next_now_playing_poll: Instant, 118 + os_media_controls: Option<OsMediaControls>, 119 } 120 121 impl HubApp { ··· 125 audio: AudioController, 126 favorites: FavoritesStore, 127 metadata_tx: mpsc::UnboundedSender<HubMessage>, 128 + os_media_controls: Option<OsMediaControls>, 129 ) -> Self { 130 let mut ui = UiState::default(); 131 ui.menu_state.select(Some(0)); ··· 143 metadata_tx, 144 now_playing_station_id: None, 145 next_now_playing_poll: Instant::now(), 146 + os_media_controls, 147 } 148 } 149 ··· 217 }) 218 }) 219 .unwrap_or_else(|| "Unknown".to_string()); 220 + let station_id = self 221 + .current_playback 222 + .as_ref() 223 + .map(|p| p.station.id.as_str()) 224 + .or_else(|| self.current_station.as_ref().map(|s| s.station.id.as_str())) 225 + .unwrap_or("N/A"); 226 + 227 + self.render_labeled_line( 228 + frame, 229 + area, 230 + row, 231 + "Station ", 232 + &format!("{} - {}", station_name, station_id), 233 + ); 234 row += 1; 235 236 let now_playing = self ··· 997 self.current_playback = Some(state.clone()); 998 if let Some(station) = self.current_station.as_mut() { 999 station.station.playing = Some(state.now_playing.clone()); 1000 + station.station.id = state.station.id.clone(); 1001 } 1002 self.set_status(&format!("Now playing {}", state.stream_name)); 1003 self.prepare_now_playing_poll(); ··· 1025 station.station.playing = Some(now_playing.clone()); 1026 } 1027 self.set_status(&format!("Now Playing {}", now_playing)); 1028 + 1029 + let name = self 1030 + .current_station 1031 + .as_ref() 1032 + .map(|s| s.station.name.clone()) 1033 + .unwrap_or_default(); 1034 + 1035 + send_os_media_controls_command( 1036 + self.os_media_controls.as_mut(), 1037 + os_media_controls::Command::SetMetadata(souvlaki::MediaMetadata { 1038 + title: (!now_playing.is_empty()).then_some(now_playing.as_str()), 1039 + album: (!name.is_empty()).then_some(name.as_str()), 1040 + artist: None, 1041 + cover_url: None, 1042 + duration: None, 1043 + }), 1044 + ); 1045 } 1046 } 1047 }
+6 -1
src/provider/tunein.rs
··· 55 None => Ok(None), 56 } 57 } 58 - _ => Ok(Some(Station::from(stations[0].clone()))), 59 } 60 } 61
··· 55 None => Ok(None), 56 } 57 } 58 + _ => { 59 + let mut station = Station::from(stations[0].clone()); 60 + // Preserve the original station ID since StationLinkDetails doesn't contain it 61 + station.id = id; 62 + Ok(Some(station)) 63 + } 64 } 65 } 66