use anyhow::Result;
use std::pin::Pin;
#[cfg(feature = "cipher")]
use std::sync::Mutex as StdMutex;
#[cfg(target_arch = "wasm32")]
use std::sync::Mutex;
use std::{future::Future, sync::Arc};
#[cfg(not(target_arch = "wasm32"))]
use tokio::sync::Mutex;
#[cfg(target_arch = "wasm32")]
use wasm_bindgen::prelude::wasm_bindgen;
use crate::cache::{CacheAccess, MemoryCacheStore, PlayerCacheHandle};
#[cfg(feature = "cipher")]
use crate::cipher::decipher::{SignatureDecipher, SignatureDecipherHandle};
use crate::cookies::DomainCookies;
use crate::yt_interface::{YtClient, YtManifest, YtStreamResponse, YtVideoInfo};
use crate::{
extractor::extract::{InfoExtractor, YtExtractor},
yt_interface::VideoId,
};
#[cfg_attr(
target_arch = "wasm32",
derive(serde::Serialize, serde::Deserialize, tsify::Tsify),
tsify(into_wasm_abi, from_wasm_abi),
serde(rename_all = "camelCase"),
serde(default)
)]
#[derive(Default)]
pub struct TydleOptions {
pub auth_cookies: DomainCookies,
pub prefer_insecure: bool,
pub source_address: String,
pub proxy_address: String,
pub default_client: YtClient,
pub force_default_client: bool,
}
pub struct Tydle<P, C>
where
P: CacheAccess<(String, String)> + PlayerCacheHandle + Send + Sync + 'static,
C: CacheAccess + Send + Sync + 'static,
{
yt_extractor: Arc<Mutex<YtExtractor<P, C>>>,
#[cfg(feature = "cipher")]
signature_decipher: Arc<StdMutex<SignatureDecipher<P, C>>>,
}
impl Tydle<MemoryCacheStore<(String, String)>, MemoryCacheStore> {
#[cfg(not(target_arch = "wasm32"))]
pub fn new(options: TydleOptions) -> Result<Self> {
let player_cache = Arc::new(MemoryCacheStore::new());
let code_cache = Arc::new(MemoryCacheStore::new());
let yt_extractor = YtExtractor::new(player_cache.clone(), code_cache.clone(), options)?;
#[cfg(feature = "cipher")]
let signature_decipher = SignatureDecipher::new(player_cache, code_cache);
Ok(Self {
yt_extractor: Arc::new(Mutex::new(yt_extractor)),
#[cfg(feature = "cipher")]
signature_decipher: Arc::new(StdMutex::new(signature_decipher)),
})
}
}
impl<P, C> Tydle<P, C>
where
P: CacheAccess<(String, String)> + PlayerCacheHandle + Send + Sync + 'static,
C: CacheAccess + Send + Sync + 'static,
{
#[cfg(not(target_arch = "wasm32"))]
pub fn new_with_cache(options: TydleOptions, player_cache: P, code_cache: C) -> Result<Self> {
let player_cache = Arc::new(player_cache);
let code_cache = Arc::new(code_cache);
let yt_extractor = YtExtractor::new(player_cache.clone(), code_cache.clone(), options)?;
#[cfg(feature = "cipher")]
let signature_decipher = SignatureDecipher::new(player_cache, code_cache);
Ok(Self {
yt_extractor: Arc::new(Mutex::new(yt_extractor)),
#[cfg(feature = "cipher")]
signature_decipher: Arc::new(StdMutex::new(signature_decipher)),
})
}
}
pub trait Extract {
fn get_manifest<'a>(&'a self, video_id: &'a VideoId) -> Self::ExtractManifestFut<'a>;
fn get_video_info<'a>(&'a self, video_id: &'a VideoId) -> Self::ExtractInfoFut<'a>;
fn get_video_info_from_manifest<'a>(
&'a self,
manifest: &'a YtManifest,
) -> Self::ExtractInfoFut<'a>;
fn get_streams_from_manifest<'a>(
&'a self,
manifest: &'a YtManifest,
) -> Self::ExtractStreamFut<'a>;
fn get_streams<'a>(&'a self, video_id: &'a VideoId) -> Self::ExtractStreamFut<'a>;
type ExtractStreamFut<'a>: Future<Output = Result<YtStreamResponse>> + 'a
where
Self: 'a;
type ExtractInfoFut<'a>: Future<Output = Result<YtVideoInfo>> + 'a
where
Self: 'a;
type ExtractManifestFut<'a>: Future<Output = Result<YtManifest>> + 'a
where
Self: 'a;
}
#[cfg(feature = "cipher")]
pub trait Cipher {
fn decipher_signature<'a>(
&'a self,
signature: String,
player_url: String,
) -> Self::DecipherFut<'a>;
type DecipherFut<'a>: Future<Output = Result<String>> + 'a
where
Self: 'a;
}
impl<P, C> Extract for Tydle<P, C>
where
P: crate::cache::CacheAccess<(String, String)> + PlayerCacheHandle + Send + Sync + 'static,
C: crate::cache::CacheAccess + Send + Sync + 'static,
{
#[cfg(not(target_arch = "wasm32"))]
type ExtractStreamFut<'a> = Pin<Box<dyn Future<Output = Result<YtStreamResponse>> + Send + 'a>>;
#[cfg(not(target_arch = "wasm32"))]
type ExtractInfoFut<'a> = Pin<Box<dyn Future<Output = Result<YtVideoInfo>> + Send + 'a>>;
#[cfg(not(target_arch = "wasm32"))]
type ExtractManifestFut<'a> = Pin<Box<dyn Future<Output = Result<YtManifest>> + Send + 'a>>;
#[cfg(target_arch = "wasm32")]
type ExtractStreamFut<'a> = Pin<Box<dyn Future<Output = Result<YtStreamResponse>> + 'a>>;
#[cfg(target_arch = "wasm32")]
type ExtractInfoFut<'a> = Pin<Box<dyn Future<Output = Result<YtVideoInfo>> + 'a>>;
#[cfg(target_arch = "wasm32")]
type ExtractManifestFut<'a> = Pin<Box<dyn Future<Output = Result<YtManifest>> + 'a>>;
fn get_streams<'a>(&'a self, video_id: &'a VideoId) -> Self::ExtractStreamFut<'a> {
Box::pin(async move {
#[cfg(not(target_arch = "wasm32"))]
let extractor = self.yt_extractor.lock().await;
#[cfg(target_arch = "wasm32")]
let extractor = self
.yt_extractor
.lock()
.map_err(|e| anyhow::anyhow!(e.to_string()))?;
extractor.extract_streams(video_id).await
})
}
fn get_manifest<'a>(&'a self, video_id: &'a VideoId) -> Self::ExtractManifestFut<'a> {
Box::pin(async move {
#[cfg(not(target_arch = "wasm32"))]
let extractor = self.yt_extractor.lock().await;
#[cfg(target_arch = "wasm32")]
let extractor = self
.yt_extractor
.lock()
.map_err(|e| anyhow::anyhow!(e.to_string()))?;
extractor.extract_manifest(video_id).await
})
}
fn get_video_info<'a>(&'a self, video_id: &'a VideoId) -> Self::ExtractInfoFut<'a> {
Box::pin(async move {
#[cfg(not(target_arch = "wasm32"))]
let extractor = self.yt_extractor.lock().await;
#[cfg(target_arch = "wasm32")]
let extractor = self
.yt_extractor
.lock()
.map_err(|e| anyhow::anyhow!(e.to_string()))?;
extractor.extract_video_info(video_id).await
})
}
fn get_streams_from_manifest<'a>(
&'a self,
manifest: &'a YtManifest,
) -> Self::ExtractStreamFut<'a> {
Box::pin(async move {
#[cfg(not(target_arch = "wasm32"))]
let extractor = self.yt_extractor.lock().await;
#[cfg(target_arch = "wasm32")]
let extractor = self
.yt_extractor
.lock()
.map_err(|e| anyhow::anyhow!(e.to_string()))?;
extractor.extract_streams_from_manifest(manifest).await
})
}
fn get_video_info_from_manifest<'a>(
&'a self,
manifest: &'a YtManifest,
) -> Self::ExtractInfoFut<'a> {
Box::pin(async move {
#[cfg(not(target_arch = "wasm32"))]
let extractor = self.yt_extractor.lock().await;
#[cfg(target_arch = "wasm32")]
let extractor = self
.yt_extractor
.lock()
.map_err(|e| anyhow::anyhow!(e.to_string()))?;
extractor.extract_video_info_from_manifest(manifest).await
})
}
}
#[cfg(feature = "cipher")]
impl<P, C> Cipher for Tydle<P, C>
where
P: crate::cache::CacheAccess<(String, String)> + PlayerCacheHandle + Send + Sync + 'static,
C: crate::cache::CacheAccess + Send + Sync + 'static,
{
type DecipherFut<'a> = Pin<Box<dyn Future<Output = Result<String>> + 'a>>;
fn decipher_signature<'a>(
&'a self,
signature: String,
player_url: String,
) -> Self::DecipherFut<'a> {
Box::pin(async move {
let signature_decipher = self
.signature_decipher
.lock()
.map_err(|e| anyhow::anyhow!(e.to_string()))?;
signature_decipher.decipher(signature, player_url).await
})
}
}
#[cfg(target_arch = "wasm32")]
mod wasm_api {
use super::*;
use wasm_bindgen::JsValue;
#[wasm_bindgen]
pub struct TydleClient {
inner: Tydle<MemoryCacheStore<(String, String)>, MemoryCacheStore>,
}
#[wasm_bindgen]
impl TydleClient {
#[wasm_bindgen(constructor)]
pub fn new(options: Option<TydleOptions>) -> Result<TydleClient, JsValue> {
let player_cache = Arc::new(MemoryCacheStore::new());
let code_cache = Arc::new(MemoryCacheStore::new());
let yt_extractor = YtExtractor::new(
player_cache.clone(),
code_cache.clone(),
options.unwrap_or_default(),
)
.map_err(|e| JsValue::from_str(&e.to_string()))?;
let signature_decipher = SignatureDecipher::new(player_cache, code_cache);
Ok(TydleClient {
inner: Tydle {
yt_extractor: Arc::new(Mutex::new(yt_extractor)),
signature_decipher: Arc::new(Mutex::new(signature_decipher)),
},
})
}
#[wasm_bindgen(js_name = "fetchStreams")]
pub async fn fetch_streams(
&self,
#[wasm_bindgen(js_name = "videoId")] video_id: String,
) -> Result<YtStreamResponse, JsValue> {
let id = VideoId::new(&video_id).map_err(|e| JsValue::from_str(&e.to_string()))?;
Ok(self
.inner
.get_streams(&id)
.await
.map_err(|e| JsValue::from_str(&e.to_string()))?)
}
#[wasm_bindgen(js_name = "fetchVideoInfo")]
pub async fn fetch_video_info(
&self,
#[wasm_bindgen(js_name = "videoId")] video_id: String,
) -> Result<YtVideoInfo, JsValue> {
let id = VideoId::new(&video_id).map_err(|e| JsValue::from_str(&e.to_string()))?;
Ok(self
.inner
.get_video_info(&id)
.await
.map_err(|e| JsValue::from_str(&e.to_string()))?)
}
#[wasm_bindgen(js_name = "fetchVideoInfoFromManifest")]
pub async fn fetch_video_info_from_manifest(
&self,
manifest: YtManifest,
) -> Result<YtVideoInfo, JsValue> {
Ok(self
.inner
.get_video_info_from_manifest(&manifest)
.await
.map_err(|e| JsValue::from_str(&e.to_string()))?)
}
#[wasm_bindgen(js_name = "fetchStreamsFromManifest")]
pub async fn fetch_streams_from_manifest(
&self,
manifest: YtManifest,
) -> Result<YtStreamResponse, JsValue> {
Ok(self
.inner
.get_streams_from_manifest(&manifest)
.await
.map_err(|e| JsValue::from_str(&e.to_string()))?)
}
#[wasm_bindgen(js_name = "fetchManifest")]
pub async fn fetch_manifest(
&self,
#[wasm_bindgen(js_name = "videoId")] video_id: String,
) -> Result<YtManifest, JsValue> {
let id = VideoId::new(&video_id).map_err(|e| JsValue::from_str(&e.to_string()))?;
Ok(self
.inner
.get_manifest(&id)
.await
.map_err(|e| JsValue::from_str(&e.to_string()))?)
}
#[wasm_bindgen(js_name = "decipherSignature")]
pub async fn decipher_signature_js(
&self,
signature: String,
#[wasm_bindgen(js_name = "playerUrl")] player_url: String,
) -> Result<String, JsValue> {
let res = self
.inner
.decipher_signature(signature, player_url)
.await
.map_err(|e| JsValue::from_str(&e.to_string()))?;
Ok(res)
}
}
}