diff --git a/src/model/mod.rs b/src/model/mod.rs index 7760263..b5ed216 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -243,6 +243,94 @@ pub struct AudioStream { pub track: Option, } +pub trait YtStream { + fn url(&self) -> &str; + fn itag(&self) -> u32; + fn bitrate(&self) -> u32; + fn averate_bitrate(&self) -> u32; + fn size(&self) -> Option; + fn index_range(&self) -> Option>; + fn init_range(&self) -> Option>; + fn duration_ms(&self) -> Option; + fn mime(&self) -> &str; +} + +impl YtStream for VideoStream { + fn url(&self) -> &str { + &self.url + } + + fn itag(&self) -> u32 { + self.itag + } + + fn bitrate(&self) -> u32 { + self.bitrate + } + + fn averate_bitrate(&self) -> u32 { + self.average_bitrate + } + + fn size(&self) -> Option { + self.size + } + + fn index_range(&self) -> Option> { + self.index_range.clone() + } + + fn init_range(&self) -> Option> { + self.init_range.clone() + } + + fn duration_ms(&self) -> Option { + self.duration_ms + } + + fn mime(&self) -> &str { + &self.mime + } +} + +impl YtStream for AudioStream { + fn url(&self) -> &str { + &self.url + } + + fn itag(&self) -> u32 { + self.itag + } + + fn bitrate(&self) -> u32 { + self.bitrate + } + + fn averate_bitrate(&self) -> u32 { + self.average_bitrate + } + + fn size(&self) -> Option { + Some(self.size) + } + + fn index_range(&self) -> Option> { + self.index_range.clone() + } + + fn init_range(&self) -> Option> { + self.init_range.clone() + } + + fn duration_ms(&self) -> Option { + self.duration_ms + } + + fn mime(&self) -> &str { + &self.mime + } +} + /// Video codec #[derive( Default, Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash, diff --git a/tests/youtube.rs b/tests/youtube.rs index 04c77fc..b119402 100644 --- a/tests/youtube.rs +++ b/tests/youtube.rs @@ -10,7 +10,7 @@ use rustypipe::error::{Error, ExtractionError}; use rustypipe::model::richtext::ToPlaintext; use rustypipe::model::{ AlbumType, AudioCodec, AudioFormat, Channel, FromYtItem, MusicEntityType, Paginator, UrlTarget, - Verification, VideoCodec, VideoFormat, YouTubeItem, + Verification, VideoCodec, VideoFormat, YouTubeItem, YtStream, }; use rustypipe::param::search_filter::{self, SearchFilter}; @@ -54,12 +54,12 @@ async fn get_player_from_client(#[case] client_type: ClientType) { if client_type == ClientType::Ios { let video = player_data .video_only_streams - .iter() + .into_iter() .find(|s| s.itag == 247) .unwrap(); let audio = player_data .audio_streams - .iter() + .into_iter() .find(|s| s.itag == 140) .unwrap(); @@ -82,15 +82,18 @@ async fn get_player_from_client(#[case] client_type: ClientType) { assert_eq!(audio.mime, "audio/mp4; codecs=\"mp4a.40.2\""); assert_eq!(audio.format, AudioFormat::M4a); assert_eq!(audio.codec, AudioCodec::Mp4a); + + check_video_stream(video).await; + check_video_stream(audio).await; } else { let video = player_data .video_only_streams - .iter() + .into_iter() .find(|s| s.itag == 398) .unwrap(); let audio = player_data .audio_streams - .iter() + .into_iter() .find(|s| s.itag == 251) .unwrap(); @@ -114,11 +117,31 @@ async fn get_player_from_client(#[case] client_type: ClientType) { assert_eq!(audio.format, AudioFormat::Webm); assert_eq!(audio.codec, AudioCodec::Opus); assert_eq!(audio.throttled, false); + + check_video_stream(video).await; + check_video_stream(audio).await; } assert!(player_data.expires_in_seconds > 10000); } +/// Request the given stream to check if it returns a valid response +async fn check_video_stream(s: impl YtStream) { + let http = reqwest::Client::new(); + + let resp = http + .get(s.url()) + .send() + .await + .unwrap() + .error_for_status() + .unwrap(); + + if let Some(size) = s.size() { + assert_eq!(resp.content_length().unwrap(), size) + } +} + #[rstest] #[case::music( "ihUZMeYFZHA",