use crate::youtube_downloader::{YoutubeMusicDownload, YoutubeMusicDownloader};
use bytes::Bytes;
use futures::Stream;
use rusty_ytdl::{
DownloadOptions, RequestOptions, Video, VideoError, VideoFormat, VideoOptions, VideoQuality,
reqwest,
};
use std::sync::Arc;
#[derive(Clone)]
pub struct NativeYoutubeDownloader {
options: Arc<VideoOptions>,
}
impl NativeYoutubeDownloader {
pub fn new(
dl_chunk_size: u64,
quality: VideoQuality,
po_token: Option<String>,
client: reqwest::Client,
) -> Self {
let custom_filter = |video_format: &VideoFormat| {
video_format.has_audio
&& !video_format.has_video
&& video_format.mime_type.container != "webm"
};
let options = Arc::new(VideoOptions {
quality,
filter: rusty_ytdl::VideoSearchOptions::Custom(Arc::new(custom_filter)),
download_options: DownloadOptions {
dl_chunk_size: Some(dl_chunk_size),
},
request_options: RequestOptions {
client: Some(client),
po_token,
..Default::default()
},
});
Self { options }
}
}
impl YoutubeMusicDownloader for NativeYoutubeDownloader {
type Error = rusty_ytdl::VideoError;
async fn stream_song(
&self,
song_video_id: impl AsRef<str> + Send,
) -> Result<
YoutubeMusicDownload<impl Stream<Item = Result<Bytes, Self::Error>> + Send>,
Self::Error,
> {
let options = self.options.clone();
let song_video_id: String = song_video_id.as_ref().into();
let video = Video::new_with_options(song_video_id, options.as_ref())?;
let stream = video.stream().await?;
let total_size_bytes = stream.content_length();
let stream = into_futures_stream(stream);
Ok(YoutubeMusicDownload {
total_size_bytes,
song: stream,
})
}
}
pub fn into_futures_stream(
youtube_stream: Box<dyn rusty_ytdl::stream::Stream + Send>,
) -> impl futures::Stream<Item = Result<Bytes, VideoError>> + Send {
futures::stream::unfold((youtube_stream, false), |(state, err)| async move {
if err {
return None;
};
let chunk = state.chunk().await;
match chunk {
Err(e) => Some((Err(e), (state, true))),
Ok(Some(bytes)) => Some((Ok(bytes), (state, false))),
Ok(None) => None,
}
})
}