use std::collections::HashMap;
use crate::models::{ DashStreams, Fnval, SupportFormat, VideoQuality };
use crate::{ BilibiliRequest, BpiClient, BpiError, BpiResponse };
use serde::{ Deserialize, Serialize };
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CourseVideoStreamData {
#[serde(flatten)]
pub base: crate::models::VideoStreamData,
pub seek_param: String,
pub video_project: bool,
#[serde(rename = "type")]
pub data_type: String,
pub result: String,
pub seek_type: String,
pub from: String,
pub no_rexcode: i32,
pub message: String,
pub fragment_videos: Option<Vec<FragmentVideo>>,
pub status: i32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FragmentVideo {
pub fragment_info: FragmentInfo,
pub playable_status: bool,
pub video_info: VideoInfo,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FragmentInfo {
pub fragment_type: String,
pub index: i64,
pub aid: i64,
pub fragment_position: String,
pub cid: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VideoInfo {
pub no_rexcode: i64,
pub fnval: i64,
pub video_project: bool,
pub expire_time: i64,
pub backup_url: Vec<Option<serde_json::Value>>,
pub fnver: i64,
pub support_formats: Vec<String>,
pub support_description: Vec<String>,
#[serde(rename = "type")]
pub video_info_type: String,
pub url: String,
pub quality: i64,
pub timelength: i64,
pub volume: CourseVolume,
pub accept_formats: Vec<SupportFormat>,
pub support_quality: Vec<i64>,
pub file_info: HashMap<String, FileInfo>,
pub dash: DashStreams,
pub video_codecid: i64,
pub cid: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CourseVolume {
pub measured_i: f64,
pub target_i: f64,
pub target_offset: f64,
pub measured_lra: f64,
pub target_tp: f64,
pub measured_tp: f64,
pub measured_threshold: f64,
pub multi_scene_args: MultiSceneArgs,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MultiSceneArgs {
pub normal_target_i: String,
pub undersized_target_i: String,
pub high_dynamic_target_i: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FileInfo {
pub infos: Vec<FileInfoEntry>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FileInfoEntry {
pub ahead: String,
pub vhead: String,
pub filesize: i64,
pub order: i64,
pub timelength: i64,
}
impl BpiClient {
pub async fn cheese_video_stream(
&self,
avid: u64,
ep_id: u64,
cid: u64,
qn: Option<VideoQuality>,
fnval: Option<Fnval>
) -> Result<BpiResponse<CourseVideoStreamData>, BpiError> {
let mut params = vec![
("avid", avid.to_string()),
("ep_id", ep_id.to_string()),
("cid", cid.to_string()),
("fnver", "0".to_string())
];
if fnval.is_some_and(|f| f.is_fourk()) {
params.push(("fourk", "1".to_string()));
}
if let Some(q) = qn {
params.push(("qn", q.as_u32().to_string()));
}
if let Some(fv) = fnval {
params.push(("fnval", fv.bits().to_string()));
}
self
.get("https://api.bilibili.com/pugv/player/web/playurl")
.with_bilibili_headers()
.query(¶ms)
.send_bpi("获取课程视频流 URL").await
}
}
#[cfg(test)]
mod tests {
use super::*;
const TEST_AVID: u64 = 997984154;
const TEST_EP_ID: u64 = 163956;
const TEST_CID: u64 = 1183682680;
#[tokio::test]
async fn test_cheese_playurl() -> Result<(), Box<BpiError>> {
let bpi = BpiClient::new();
let data = bpi
.cheese_video_stream(
TEST_AVID,
TEST_EP_ID,
TEST_CID,
Some(VideoQuality::P8K),
Some(
Fnval::DASH |
Fnval::FOURK |
Fnval::EIGHTK |
Fnval::HDR |
Fnval::DOLBY_AUDIO |
Fnval::DOLBY_VISION |
Fnval::AV1
)
).await?
.into_data()?;
tracing::info!("{:#?}", data);
Ok(())
}
}