use crate::{ BilibiliRequest, BpiClient, BpiError, BpiResponse };
use serde::{ Deserialize, Serialize };
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct DashInfo {
pub video: Vec<DashStream>,
pub audio: Vec<DashStream>,
#[serde(rename = "dolby")]
pub dolby: Option<DashDolby>,
pub flac: Option<DashFlac>,
pub duration: u64,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct DashDolby {
pub r#type: u8,
pub audio: Option<Vec<DashStream>>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct DashFlac {
pub audio: Vec<DashStream>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct DashStream {
pub id: u64,
#[serde(rename = "baseUrl")]
pub base_url: String,
#[serde(rename = "backupUrl")]
pub backup_url: Vec<String>,
pub bandwidth: u64,
#[serde(rename = "mimeType")]
pub mime_type: String,
pub codecs: String,
pub width: Option<u32>,
pub height: Option<u32>,
#[serde(rename = "frameRate")]
pub frame_rate: Option<String>,
pub sar: Option<String>,
pub start_with_sap: Option<u8>,
pub segment_base: Option<serde_json::Value>,
pub md5: Option<String>,
pub size: Option<u64>,
pub db_type: Option<u8>,
pub r#type: Option<String>,
pub stream_name: Option<String>,
pub orientation: Option<u8>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct DurlInfo {
pub order: u32,
pub length: u64,
pub size: u64,
pub ahead: String,
pub vhead: String,
pub url: String,
pub backup_url: Vec<String>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct SupportFormat {
pub quality: u64,
pub format: String,
pub new_description: String,
pub display_desc: String,
pub superscript: String,
pub codecs: Option<Vec<String>>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct PlayUrlResponseData {
pub from: String,
pub result: String,
pub message: String,
pub quality: u64,
pub format: String,
pub timelength: u64,
pub accept_format: String,
pub accept_description: Vec<String>,
pub accept_quality: Vec<u64>,
pub video_codecid: u8,
pub seek_param: String,
pub seek_type: String,
pub durl: Option<Vec<DurlInfo>>,
pub dash: Option<DashInfo>,
pub support_formats: Vec<SupportFormat>,
pub high_format: Option<serde_json::Value>,
pub last_play_time: u64,
pub last_play_cid: u64,
}
impl BpiClient {
pub async fn video_playurl(
&self,
aid: Option<u64>,
bvid: Option<&str>,
cid: u64,
qn: Option<u64>,
fnval: Option<u64>,
fnver: Option<u64>,
fourk: Option<u8>,
platform: Option<&str>,
high_quality: Option<u8>,
try_look: Option<u8>
) -> Result<BpiResponse<PlayUrlResponseData>, BpiError> {
if aid.is_none() && bvid.is_none() {
return Err(BpiError::parse("必须提供 aid 或 bvid"));
}
let mut params = vec![("cid", cid.to_string())];
if let Some(a) = aid {
params.push(("avid", a.to_string()));
}
if let Some(b) = bvid {
params.push(("bvid", b.to_string()));
}
if let Some(q) = qn {
params.push(("qn", q.to_string()));
}
if let Some(f) = fnval {
params.push(("fnval", f.to_string()));
}
if let Some(f) = fnver {
params.push(("fnver", f.to_string()));
}
if let Some(f) = fourk {
params.push(("fourk", f.to_string()));
}
if let Some(p) = platform {
params.push(("platform", p.to_string()));
} else {
params.push(("platform", "pc".to_string()));
}
if let Some(h) = high_quality {
params.push(("high_quality", h.to_string()));
}
if let Some(t) = try_look {
params.push(("try_look", t.to_string()));
}
let params = self.get_wbi_sign2(params).await?;
self
.get("https://api.bilibili.com/x/player/wbi/playurl")
.with_bilibili_headers()
.query(¶ms)
.send_bpi("获取视频流地址").await
}
}
#[cfg(test)]
mod tests {
use super::*;
use tracing::info;
const TEST_AID: u64 = 113898824998659;
const TEST_CID: u64 = 28104724389;
#[tokio::test]
async fn test_video_playurl_mp4_by_aid() -> Result<(), BpiError> {
let bpi = BpiClient::new();
let resp = bpi.video_playurl(
Some(TEST_AID),
None,
TEST_CID,
Some(64),
Some(1),
None,
None,
None,
None,
None
).await?;
let data = resp.into_data()?;
info!("MP4 视频流信息: {:?}", data);
assert!(!data.durl.is_none());
assert_eq!(data.quality, 64);
Ok(())
}
#[tokio::test]
async fn test_video_playurl_4k() -> Result<(), BpiError> {
let bpi = BpiClient::new();
let resp = bpi.video_playurl(
Some(TEST_AID),
None,
TEST_CID,
Some(120),
Some(16 | 128),
Some(0),
Some(1),
None,
None,
None
).await?;
let data = resp.into_data()?;
info!("4K 视频流信息: {:?}", data);
assert!(!data.dash.is_none());
assert_eq!(data.quality, 120);
Ok(())
}
}