baidu_netdisk_sdk/playlist/mod.rs
1//! Playlist and media playback functionality
2//!
3//! This module provides access to Baidu NetDisk's playlist and media streaming features:
4//!
5//! # Features
6//!
7//! - **Playlist management**: List playlists and their contents
8//! - **Media streaming**: Get media playback information and m3u8 streams
9//! - **Quality selection**: Video/Audio quality enums with VIP level support
10//! - **Transcoding check**: Verify if media is fully transcoded
11//!
12//! # Quick Start
13//!
14//! ```
15//! use baidu_netdisk_sdk::{BaiduNetDiskClient, playlist::VideoQuality};
16//!
17//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
18//! // Create client and load token
19//! let client = BaiduNetDiskClient::builder().build()?;
20//! client.load_token_from_env()?;
21//!
22//! // List all playlists
23//! let playlists = client.playlist().get_playlist_list().await?;
24//!
25//! // Get media m3u8 with highest quality for VIP 2
26//! let m3u8 = client.playlist()
27//! .get_video_m3u8_highest("/video.mp4", 2)
28//! .await?;
29//! # Ok(())
30//! # }
31//! ```
32
33use log::{debug, error, info};
34use serde::{Deserialize, Serialize};
35use std::sync::Arc;
36
37use crate::client::TokenGetter;
38use crate::errors::{NetDiskError, NetDiskResult};
39use crate::http::HttpClient;
40
41/// Video quality levels for m3u8 streaming
42///
43/// # Examples
44///
45/// ```
46/// use baidu_netdisk_sdk::playlist::VideoQuality;
47///
48/// let quality = VideoQuality::Quality1080P;
49/// assert_eq!(quality.to_media_type(), "M3U8_AUTO_1080");
50///
51/// // Get highest quality for VIP level
52/// let quality = VideoQuality::highest_for_vip_level(2);
53/// assert_eq!(quality, VideoQuality::Quality1080P);
54/// ```
55#[derive(Debug, Clone, Copy, PartialEq, Eq)]
56pub enum VideoQuality {
57 /// 480P quality - available to all users
58 Quality480P,
59 /// 720P quality - highest for regular users
60 Quality720P,
61 /// 1080P quality - usually requires super VIP
62 Quality1080P,
63}
64
65impl VideoQuality {
66 /// Get the corresponding media_type string for API
67 ///
68 /// # Examples
69 ///
70 /// ```
71 /// use baidu_netdisk_sdk::playlist::VideoQuality;
72 ///
73 /// assert_eq!(VideoQuality::Quality480P.to_media_type(), "M3U8_AUTO_480");
74 /// assert_eq!(VideoQuality::Quality720P.to_media_type(), "M3U8_AUTO_720");
75 /// assert_eq!(VideoQuality::Quality1080P.to_media_type(), "M3U8_AUTO_1080");
76 /// ```
77 pub fn to_media_type(self) -> &'static str {
78 match self {
79 VideoQuality::Quality480P => "M3U8_AUTO_480",
80 VideoQuality::Quality720P => "M3U8_AUTO_720",
81 VideoQuality::Quality1080P => "M3U8_AUTO_1080",
82 }
83 }
84
85 /// Get the highest quality available for a given VIP level
86 ///
87 /// - **VIP 0-1**: Max 480P
88 /// - **VIP 2+**: Max 1080P (includes 720P)
89 ///
90 /// # Examples
91 ///
92 /// ```
93 /// use baidu_netdisk_sdk::playlist::VideoQuality;
94 ///
95 /// assert_eq!(VideoQuality::highest_for_vip_level(0), VideoQuality::Quality480P);
96 /// assert_eq!(VideoQuality::highest_for_vip_level(2), VideoQuality::Quality1080P);
97 /// ```
98 pub fn highest_for_vip_level(vip_level: u32) -> Self {
99 match vip_level {
100 0 | 1 => VideoQuality::Quality480P,
101 _ => VideoQuality::Quality1080P,
102 }
103 }
104
105 /// Get all qualities available for a given VIP level (from lowest to highest)
106 ///
107 /// # Examples
108 ///
109 /// ```
110 /// use baidu_netdisk_sdk::playlist::VideoQuality;
111 ///
112 /// let qualities = VideoQuality::available_for_vip_level(2);
113 /// assert_eq!(qualities.len(), 3);
114 /// assert_eq!(qualities[0], VideoQuality::Quality480P);
115 /// assert_eq!(qualities[2], VideoQuality::Quality1080P);
116 /// ```
117 pub fn available_for_vip_level(vip_level: u32) -> Vec<Self> {
118 match vip_level {
119 0 | 1 => vec![VideoQuality::Quality480P],
120 _ => vec![
121 VideoQuality::Quality480P,
122 VideoQuality::Quality720P,
123 VideoQuality::Quality1080P,
124 ],
125 }
126 }
127}
128
129/// Audio quality levels for m3u8 streaming
130///
131/// # Examples
132///
133/// ```
134/// use baidu_netdisk_sdk::playlist::AudioQuality;
135///
136/// let quality = AudioQuality::Quality128K;
137/// assert_eq!(quality.to_media_type(), "M3U8_MP3_128");
138/// ```
139#[derive(Debug, Clone, Copy, PartialEq, Eq)]
140pub enum AudioQuality {
141 /// 128kbps MP3
142 Quality128K,
143}
144
145impl AudioQuality {
146 /// Get the corresponding media_type string for API
147 ///
148 /// # Examples
149 ///
150 /// ```
151 /// use baidu_netdisk_sdk::playlist::AudioQuality;
152 ///
153 /// assert_eq!(AudioQuality::Quality128K.to_media_type(), "M3U8_MP3_128");
154 /// ```
155 pub fn to_media_type(self) -> &'static str {
156 match self {
157 AudioQuality::Quality128K => "M3U8_MP3_128",
158 }
159 }
160}
161
162/// Playlist client for interacting with playlist-related APIs
163///
164/// # Examples
165///
166/// ```
167/// use baidu_netdisk_sdk::BaiduNetDiskClient;
168///
169/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
170/// let client = BaiduNetDiskClient::builder().build()?;
171/// client.load_token_from_env()?;
172///
173/// // Access playlist functionality
174/// let playlists = client.playlist().get_playlist_list().await?;
175/// # Ok(())
176/// # }
177/// ```
178#[derive(Debug, Clone)]
179pub struct PlaylistClient {
180 http_client: HttpClient,
181 token_getter: Arc<dyn TokenGetter>,
182}
183
184impl PlaylistClient {
185 /// Create a new PlaylistClient instance
186 ///
187 /// Usually you don't need to call this directly - use `BaiduNetDiskClient::playlist()` instead.
188 pub fn new(http_client: HttpClient, token_getter: Arc<dyn TokenGetter>) -> Self {
189 PlaylistClient {
190 http_client,
191 token_getter,
192 }
193 }
194
195 /// Get a reference to the internal HTTP client
196 pub fn http_client(&self) -> &HttpClient {
197 &self.http_client
198 }
199
200 /// Get a list of playlists with default options
201 ///
202 /// # Examples
203 ///
204 /// ```
205 /// use baidu_netdisk_sdk::BaiduNetDiskClient;
206 ///
207 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
208 /// let client = BaiduNetDiskClient::builder().build()?;
209 /// client.load_token_from_env()?;
210 ///
211 /// let playlists = client.playlist().get_playlist_list().await?;
212 /// println!("Found {} playlists", playlists.list.len());
213 /// # Ok(())
214 /// # }
215 /// ```
216 pub async fn get_playlist_list(&self) -> NetDiskResult<PlaylistList> {
217 self.get_playlist_list_with_options(PlaylistListOptions::default())
218 .await
219 }
220
221 /// Get a list of playlists with custom options
222 ///
223 /// This is a lower-level method for advanced use cases.
224 /// Most users should use `get_playlist_list()` instead.
225 pub async fn get_playlist_list_with_options(
226 &self,
227 options: PlaylistListOptions,
228 ) -> NetDiskResult<PlaylistList> {
229 let token = self.token_getter.get_token().await?;
230 let mut params = Vec::new();
231
232 params.push(("method", "list".to_string()));
233 params.push(("access_token", token.access_token.clone()));
234
235 if let Some(p) = options.page {
236 params.push(("page", p.to_string()));
237 }
238 if let Some(s) = options.psize {
239 params.push(("psize", s.to_string()));
240 }
241
242 let params_ref: Vec<(&str, &str)> = params.iter().map(|(k, v)| (*k, v.as_str())).collect();
243
244 debug!("Getting playlist list with options: {:?}", options);
245
246 let response: PlaylistListResponse = self
247 .http_client
248 .get("/rest/2.0/xpan/broadcast/list", Some(¶ms_ref))
249 .await?;
250
251 if response.errno != 0 {
252 let errmsg = response.errmsg.as_deref().unwrap_or("Unknown error");
253 return Err(NetDiskError::api_error(response.errno, errmsg));
254 }
255
256 let list = response.list.unwrap_or_default();
257 info!(
258 "Playlist list retrieved successfully, count: {}",
259 list.len()
260 );
261
262 Ok(PlaylistList {
263 has_more: response.has_more,
264 list,
265 })
266 }
267
268 /// Get playlist file download list with default options
269 ///
270 /// # Examples
271 ///
272 /// ```
273 /// use baidu_netdisk_sdk::BaiduNetDiskClient;
274 ///
275 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
276 /// let client = BaiduNetDiskClient::builder().build()?;
277 /// client.load_token_from_env()?;
278 ///
279 /// // First get playlists to find mb_id
280 /// let playlists = client.playlist().get_playlist_list().await?;
281 /// if let Some(playlist) = playlists.list.first() {
282 /// // Then get files in playlist
283 /// let files = client.playlist()
284 /// .get_playlist_file_list(playlist.mb_id)
285 /// .await?;
286 /// println!("Found {} files", files.list.len());
287 /// }
288 /// # Ok(())
289 /// # }
290 /// ```
291 pub async fn get_playlist_file_list(&self, mb_id: u64) -> NetDiskResult<PlaylistFileList> {
292 self.get_playlist_file_list_with_options(mb_id, PlaylistFileListOptions::default())
293 .await
294 }
295
296 /// Get playlist file download list with custom options
297 ///
298 /// This is a lower-level method for advanced use cases.
299 /// Most users should use `get_playlist_file_list()` instead.
300 pub async fn get_playlist_file_list_with_options(
301 &self,
302 mb_id: u64,
303 options: PlaylistFileListOptions,
304 ) -> NetDiskResult<PlaylistFileList> {
305 let token = self.token_getter.get_token().await?;
306 let mut params = Vec::new();
307
308 params.push(("access_token", token.access_token.clone()));
309 params.push(("mb_id", mb_id.to_string()));
310
311 if let Some(s) = options.showmeta {
312 params.push(("showmeta", s.to_string()));
313 }
314 if let Some(p) = options.page {
315 params.push(("page", p.to_string()));
316 }
317 if let Some(s) = options.psize {
318 params.push(("psize", s.to_string()));
319 }
320
321 let params_ref: Vec<(&str, &str)> = params.iter().map(|(k, v)| (*k, v.as_str())).collect();
322
323 debug!(
324 "Getting playlist file list for mb_id: {} with options: {:?}",
325 mb_id, options
326 );
327
328 let response: PlaylistFileListResponse = self
329 .http_client
330 .post("/rest/2.0/xpan/broadcast/filelist", Some(¶ms_ref))
331 .await?;
332
333 if response.errno != 0 {
334 let errmsg = response.errmsg.as_deref().unwrap_or("Unknown error");
335 return Err(NetDiskError::api_error(response.errno, errmsg));
336 }
337
338 let list = response.list.unwrap_or_default();
339 info!(
340 "Playlist file list retrieved successfully, count: {}",
341 list.len()
342 );
343
344 Ok(PlaylistFileList {
345 has_more: response.has_more,
346 list,
347 })
348 }
349
350 /// Get media playback information (audio or video)
351 ///
352 /// Takes either fs_id or path (one of them is required)
353 ///
354 /// # Examples
355 ///
356 /// ```
357 /// use baidu_netdisk_sdk::BaiduNetDiskClient;
358 ///
359 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
360 /// let client = BaiduNetDiskClient::builder().build()?;
361 /// client.load_token_from_env()?;
362 ///
363 /// // Get by path
364 /// let info = client.playlist()
365 /// .get_media_play_info(None, Some("/video.mp4"), "M3U8_AUTO_1080")
366 /// .await?;
367 ///
368 /// // Or get by fs_id
369 /// let info = client.playlist()
370 /// .get_media_play_info(Some(123456), None, "M3U8_AUTO_1080")
371 /// .await?;
372 /// # Ok(())
373 /// # }
374 /// ```
375 pub async fn get_media_play_info(
376 &self,
377 fsid: Option<u64>,
378 path: Option<&str>,
379 media_type: &str,
380 ) -> NetDiskResult<MediaPlayInfo> {
381 let token = self.token_getter.get_token().await?;
382 let mut params = Vec::new();
383
384 params.push(("method", "streaming".to_string()));
385 params.push(("access_token", token.access_token.clone()));
386 params.push(("type", media_type.to_string()));
387
388 if let Some(f) = fsid {
389 params.push(("fid", f.to_string()));
390 }
391 if let Some(p) = path {
392 params.push(("path", p.to_string()));
393 }
394
395 let params_ref: Vec<(&str, &str)> = params.iter().map(|(k, v)| (*k, v.as_str())).collect();
396
397 let headers = [
398 (
399 "User-Agent",
400 "xpanvideo;netdisk;iPhone13;ios-iphone;15.1;ts",
401 ),
402 ("Host", "pan.baidu.com"),
403 ("Accept", "*/*"),
404 ("Accept-Language", "zh-CN,zh;q=0.9"),
405 ];
406
407 debug!("Getting media play info with params: {:?}", params_ref);
408
409 let response: MediaPlayInfoResponse = self
410 .http_client
411 .get_with_headers("/rest/2.0/xpan/file", Some(¶ms_ref), Some(&headers))
412 .await?;
413
414 if response.errno != 0 {
415 let errmsg = response.errmsg.as_deref().unwrap_or("Unknown error");
416 return Err(NetDiskError::api_error(response.errno, errmsg));
417 }
418
419 let list = response.list.unwrap_or_default();
420 info!("Media play info retrieved successfully");
421
422 Ok(MediaPlayInfo {
423 list,
424 request_id: response.request_id,
425 })
426 }
427
428 /// Fetch m3u8 playlist content with special headers
429 ///
430 /// This is for checking if the media is fully transcoded
431 ///
432 /// # Examples
433 ///
434 /// ```
435 /// use baidu_netdisk_sdk::BaiduNetDiskClient;
436 ///
437 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
438 /// let client = BaiduNetDiskClient::builder().build()?;
439 ///
440 /// // First get play info to get m3u8_url
441 /// // Then fetch the content
442 /// // let content = client.playlist().fetch_m3u8(m3u8_url).await?;
443 /// # Ok(())
444 /// # }
445 /// ```
446 pub async fn fetch_m3u8(&self, m3u8_url: &str) -> NetDiskResult<String> {
447 debug!("Fetching m3u8 content from: {}", m3u8_url);
448
449 let client = reqwest::Client::new();
450 let response = client
451 .get(m3u8_url)
452 .header(
453 "User-Agent",
454 "xpanvideo;netdisk;iPhone13;ios-iphone;15.1;ts",
455 )
456 .header("Host", "pan.baidu.com")
457 .send()
458 .await?;
459
460 if !response.status().is_success() {
461 return Err(NetDiskError::Unknown {
462 message: format!("Failed to fetch m3u8: {}", response.status()),
463 });
464 }
465
466 let content = response.text().await?;
467 debug!(
468 "Successfully fetched m3u8 content, length: {}",
469 content.len()
470 );
471
472 Ok(content)
473 }
474
475 /// Check if media is fully transcoded by checking if m3u8 contains #EXT-X-ENDLIST
476 ///
477 /// # Examples
478 ///
479 /// ```
480 /// use baidu_netdisk_sdk::BaiduNetDiskClient;
481 ///
482 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
483 /// let client = BaiduNetDiskClient::builder().build()?;
484 ///
485 /// // Check if transcoding is complete
486 /// // let is_complete = client.playlist().is_media_fully_transcoded(m3u8_url).await?;
487 /// # Ok(())
488 /// # }
489 /// ```
490 pub async fn is_media_fully_transcoded(&self, m3u8_url: &str) -> NetDiskResult<bool> {
491 let content = self.fetch_m3u8(m3u8_url).await?;
492 Ok(content.contains("#EXT-X-ENDLIST"))
493 }
494
495 /// Get raw m3u8 content for media file by path
496 ///
497 /// This returns the raw m3u8 playlist content. It does NOT poll for completion.
498 /// Use `is_media_fully_transcoded` or implement your own polling if needed.
499 ///
500 /// # Examples
501 ///
502 /// ```
503 /// use baidu_netdisk_sdk::BaiduNetDiskClient;
504 ///
505 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
506 /// let client = BaiduNetDiskClient::builder().build()?;
507 /// client.load_token_from_env()?;
508 ///
509 /// let m3u8 = client.playlist()
510 /// .get_media_m3u8_content("/video.mp4", "M3U8_AUTO_1080")
511 /// .await?;
512 ///
513 /// // Check if fully transcoded
514 /// let is_complete = m3u8.contains("#EXT-X-ENDLIST");
515 /// # Ok(())
516 /// # }
517 /// ```
518 pub async fn get_media_m3u8_content(
519 &self,
520 path: &str,
521 media_type: &str,
522 ) -> NetDiskResult<String> {
523 let token = self.token_getter.get_token().await?;
524 let params = [
525 ("method", "streaming".to_string()),
526 ("access_token", token.access_token.clone()),
527 ("path", path.to_string()),
528 ("type", media_type.to_string()),
529 ];
530
531 let params_ref: Vec<(&str, &str)> = params.iter().map(|(k, v)| (*k, v.as_str())).collect();
532
533 debug!(
534 "Getting raw m3u8 content for path: {}, type: {}",
535 path, media_type
536 );
537
538 let mut url = reqwest::Url::parse("https://pan.baidu.com/rest/2.0/xpan/file")?;
539 {
540 let mut pairs = url.query_pairs_mut();
541 for (key, value) in ¶ms_ref {
542 pairs.append_pair(key, value);
543 }
544 }
545
546 debug!("Built URL for m3u8: {}", url);
547
548 let client = reqwest::Client::new();
549 let response = client
550 .get(url.clone())
551 .header(
552 "User-Agent",
553 "xpanvideo;netdisk;iPhone13;ios-iphone;15.1;ts",
554 )
555 .header("Host", "pan.baidu.com")
556 .header("Accept", "*/*")
557 .header("Accept-Language", "zh-CN,zh;q=0.9")
558 .send()
559 .await?;
560
561 if !response.status().is_success() {
562 let status = response.status();
563 let body = response.text().await.unwrap_or_default();
564 error!("Failed to get m3u8 content: {} - {}", status, body);
565 return Err(NetDiskError::http_error(status.as_u16(), url.as_ref()));
566 }
567
568 let content = response.text().await?;
569 debug!(
570 "Successfully fetched m3u8 content, length: {}",
571 content.len()
572 );
573
574 Ok(content)
575 }
576
577 // Convenience methods using quality enums
578
579 /// Get video m3u8 content with specified quality
580 ///
581 /// # Examples
582 ///
583 /// ```
584 /// use baidu_netdisk_sdk::BaiduNetDiskClient;
585 /// use baidu_netdisk_sdk::playlist::VideoQuality;
586 ///
587 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
588 /// let client = BaiduNetDiskClient::builder().build()?;
589 /// client.load_token_from_env()?;
590 ///
591 /// let m3u8 = client.playlist()
592 /// .get_video_m3u8("/video.mp4", VideoQuality::Quality1080P)
593 /// .await?;
594 /// # Ok(())
595 /// # }
596 /// ```
597 pub async fn get_video_m3u8(&self, path: &str, quality: VideoQuality) -> NetDiskResult<String> {
598 self.get_media_m3u8_content(path, quality.to_media_type())
599 .await
600 }
601
602 /// Get video m3u8 content with highest available quality for given VIP level
603 ///
604 /// # Examples
605 ///
606 /// ```
607 /// use baidu_netdisk_sdk::BaiduNetDiskClient;
608 ///
609 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
610 /// let client = BaiduNetDiskClient::builder().build()?;
611 /// client.load_token_from_env()?;
612 ///
613 /// // Get highest quality for VIP 2 (1080P)
614 /// let m3u8 = client.playlist()
615 /// .get_video_m3u8_highest("/video.mp4", 2)
616 /// .await?;
617 /// # Ok(())
618 /// # }
619 /// ```
620 pub async fn get_video_m3u8_highest(
621 &self,
622 path: &str,
623 vip_level: u32,
624 ) -> NetDiskResult<String> {
625 let quality = VideoQuality::highest_for_vip_level(vip_level);
626 self.get_video_m3u8(path, quality).await
627 }
628
629 /// Get audio m3u8 content with specified quality
630 ///
631 /// # Examples
632 ///
633 /// ```
634 /// use baidu_netdisk_sdk::BaiduNetDiskClient;
635 /// use baidu_netdisk_sdk::playlist::AudioQuality;
636 ///
637 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
638 /// let client = BaiduNetDiskClient::builder().build()?;
639 /// client.load_token_from_env()?;
640 ///
641 /// let m3u8 = client.playlist()
642 /// .get_audio_m3u8("/audio.mp3", AudioQuality::Quality128K)
643 /// .await?;
644 /// # Ok(())
645 /// # }
646 /// ```
647 pub async fn get_audio_m3u8(&self, path: &str, quality: AudioQuality) -> NetDiskResult<String> {
648 self.get_media_m3u8_content(path, quality.to_media_type())
649 .await
650 }
651
652 /// Get audio m3u8 content with default quality (128K)
653 ///
654 /// # Examples
655 ///
656 /// ```
657 /// use baidu_netdisk_sdk::BaiduNetDiskClient;
658 ///
659 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
660 /// let client = BaiduNetDiskClient::builder().build()?;
661 /// client.load_token_from_env()?;
662 ///
663 /// let m3u8 = client.playlist()
664 /// .get_audio_m3u8_default("/audio.mp3")
665 /// .await?;
666 /// # Ok(())
667 /// # }
668 /// ```
669 pub async fn get_audio_m3u8_default(&self, path: &str) -> NetDiskResult<String> {
670 self.get_audio_m3u8(path, AudioQuality::Quality128K).await
671 }
672}
673
674/// Options for get_playlist_list
675#[derive(Debug, Deserialize, Serialize, Clone, Default)]
676pub struct PlaylistListOptions {
677 /// Current page number (default 1)
678 pub page: Option<i32>,
679 /// Number of items per page (default 20)
680 pub psize: Option<i32>,
681}
682
683impl PlaylistListOptions {
684 /// Create a new PlaylistListOptions with default values
685 pub fn new() -> Self {
686 Self::default()
687 }
688
689 /// Set page number
690 pub fn page(mut self, page: i32) -> Self {
691 self.page = Some(page);
692 self
693 }
694
695 /// Set page size
696 pub fn psize(mut self, psize: i32) -> Self {
697 self.psize = Some(psize);
698 self
699 }
700}
701
702/// Options for get_playlist_file_list
703#[derive(Debug, Deserialize, Serialize, Clone, Default)]
704pub struct PlaylistFileListOptions {
705 /// Show file details (1 or 0)
706 pub showmeta: Option<i32>,
707 /// Current page number (default 1)
708 pub page: Option<i32>,
709 /// Number of items per page (default 20)
710 pub psize: Option<i32>,
711}
712
713impl PlaylistFileListOptions {
714 /// Create a new PlaylistFileListOptions with default values
715 pub fn new() -> Self {
716 Self::default()
717 }
718
719 /// Set showmeta flag (1 to show details, 0 otherwise)
720 pub fn showmeta(mut self, showmeta: i32) -> Self {
721 self.showmeta = Some(showmeta);
722 self
723 }
724
725 /// Set page number
726 pub fn page(mut self, page: i32) -> Self {
727 self.page = Some(page);
728 self
729 }
730
731 /// Set page size
732 pub fn psize(mut self, psize: i32) -> Self {
733 self.psize = Some(psize);
734 self
735 }
736}
737
738/// Playlist information
739#[derive(Debug, Deserialize, Serialize, Clone)]
740pub struct PlaylistInfo {
741 /// Playlist name
742 pub name: String,
743 /// Playlist ID
744 pub mb_id: u64,
745 /// File count in playlist
746 pub file_count: u32,
747 /// Creation time (timestamp)
748 pub ctime: u32,
749 /// Modification time (timestamp)
750 pub mtime: u32,
751 /// Broadcast type (0: audio)
752 pub btype: u32,
753 /// Broadcast sub-type (0: normal, 1: music, 2: course)
754 pub bstype: u32,
755}
756
757/// List of playlists
758#[derive(Debug, Deserialize, Serialize, Clone)]
759pub struct PlaylistList {
760 pub has_more: u32,
761 pub list: Vec<PlaylistInfo>,
762}
763
764/// Playlist file information
765#[derive(Debug, Deserialize, Serialize, Clone)]
766pub struct PlaylistFileInfo {
767 /// File server ID
768 pub fs_id: String,
769 /// File path
770 pub path: String,
771 /// Broadcast file modification time
772 pub broadcast_file_mtime: u64,
773 /// Server creation time (optional)
774 #[serde(default)]
775 pub server_ctime: Option<String>,
776 /// Server modification time (optional)
777 #[serde(default)]
778 pub server_mtime: Option<String>,
779 /// Local creation time (optional)
780 #[serde(default)]
781 pub local_ctime: Option<String>,
782 /// Local modification time (optional)
783 #[serde(default)]
784 pub local_mtime: Option<String>,
785 /// Is directory (optional)
786 #[serde(default)]
787 pub isdir: Option<String>,
788 /// File size (optional)
789 #[serde(default)]
790 pub size: Option<String>,
791 /// File category (optional)
792 #[serde(default)]
793 pub category: Option<String>,
794 /// Server MD5 hash (optional)
795 #[serde(default)]
796 pub md5: Option<String>,
797 /// Privacy level (optional)
798 #[serde(default)]
799 pub privacy: Option<String>,
800 /// File name (optional)
801 #[serde(default)]
802 pub server_filename: Option<String>,
803}
804
805/// List of playlist files
806#[derive(Debug, Deserialize, Serialize, Clone)]
807pub struct PlaylistFileList {
808 pub has_more: u32,
809 pub list: Vec<PlaylistFileInfo>,
810}
811
812/// Media file entry for playback
813#[derive(Debug, Deserialize, Serialize, Clone)]
814pub struct MediaFileEntry {
815 /// File ID
816 pub fs_id: u64,
817 /// File name
818 pub server_filename: String,
819 /// File path
820 pub path: String,
821 /// File size
822 pub size: u64,
823 /// Category
824 pub category: i32,
825 /// Media info
826 pub media_info: Option<MediaInfo>,
827}
828
829/// Media information
830#[derive(Debug, Deserialize, Serialize, Clone)]
831pub struct MediaInfo {
832 /// Video or audio streams
833 pub streams: Option<Vec<MediaStream>>,
834 /// Duration in seconds
835 pub duration: Option<f64>,
836 /// Bitrate
837 pub bitrate: Option<i32>,
838}
839
840/// Media stream information
841#[derive(Debug, Deserialize, Serialize, Clone)]
842pub struct MediaStream {
843 /// Stream type
844 pub stream_type: Option<String>,
845 /// Video width
846 pub width: Option<i32>,
847 /// Video height
848 pub height: Option<i32>,
849 /// Codec
850 pub codec: Option<String>,
851 /// Playback URL
852 pub url: Option<String>,
853 /// Video or audio file
854 pub file: Option<MediaFile>,
855}
856
857/// Media file for playback
858#[derive(Debug, Deserialize, Serialize, Clone)]
859pub struct MediaFile {
860 /// File size
861 pub size: u64,
862 /// MD5
863 pub md5: String,
864 /// Server filename
865 pub server_filename: String,
866 /// Path
867 pub path: String,
868 /// File extension
869 pub file_ext: String,
870 /// Video info
871 pub video: Option<VideoInfo>,
872 /// Audio info
873 pub audio: Option<AudioInfo>,
874}
875
876/// Video information
877#[derive(Debug, Deserialize, Serialize, Clone)]
878pub struct VideoInfo {
879 pub width: Option<i32>,
880 pub height: Option<i32>,
881 pub duration: Option<f64>,
882 pub bitrate: Option<i32>,
883 pub codec: Option<String>,
884}
885
886/// Audio information
887#[derive(Debug, Deserialize, Serialize, Clone)]
888pub struct AudioInfo {
889 pub duration: Option<f64>,
890 pub bitrate: Option<i32>,
891 pub codec: Option<String>,
892 pub sample_rate: Option<i32>,
893}
894
895/// Media playback information
896#[derive(Debug, Deserialize, Serialize, Clone)]
897pub struct MediaPlayInfo {
898 pub list: Vec<MediaFileEntry>,
899 pub request_id: u64,
900}
901
902#[derive(Debug, Deserialize)]
903struct PlaylistListResponse {
904 has_more: u32,
905 #[serde(default)]
906 list: Option<Vec<PlaylistInfo>>,
907 errno: i32,
908 errmsg: Option<String>,
909}
910
911#[derive(Debug, Deserialize)]
912struct PlaylistFileListResponse {
913 has_more: u32,
914 #[serde(default)]
915 list: Option<Vec<PlaylistFileInfo>>,
916 errno: i32,
917 errmsg: Option<String>,
918}
919
920#[derive(Debug, Deserialize)]
921struct MediaPlayInfoResponse {
922 #[serde(default)]
923 list: Option<Vec<MediaFileEntry>>,
924 request_id: u64,
925 errno: i32,
926 errmsg: Option<String>,
927}