1use std::collections::HashMap;
2
3use serde::{de, ser, Deserialize, Serialize};
4use serde_json::{json, Value};
5use serde_repr::{Deserialize_repr, Serialize_repr};
6
7macro_rules! get_from_map {
8 ($map:expr, $key:expr) => {
9 $map.get($key).ok_or(de::Error::missing_field($key))
10 };
11}
12
13#[derive(Debug, Clone, PartialEq, Eq)]
14pub enum MetadataObject {
15 Generic {
16 title: Option<String>,
17 thumbnail_url: Option<String>,
18 custom: Option<Value>,
19 },
20}
21
22impl Serialize for MetadataObject {
23 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
24 where
25 S: serde::Serializer,
26 {
27 match self {
28 MetadataObject::Generic {
29 title,
30 thumbnail_url,
31 custom,
32 } => {
33 let mut map = serde_json::Map::new();
34 map.insert("type".to_owned(), json!(0u64));
35 map.insert(
36 "title".to_owned(),
37 match title {
38 Some(t) => Value::String(t.to_owned()),
39 None => Value::Null,
40 },
41 );
42 map.insert(
43 "thumbnailUrl".to_owned(),
44 match thumbnail_url {
45 Some(t) => Value::String(t.to_owned()),
46 None => Value::Null,
47 },
48 );
49 if let Some(custom) = custom {
50 map.insert("custom".to_owned(), custom.clone());
51 }
52 map.serialize(serializer)
53 }
54 }
55 }
56}
57
58impl<'de> Deserialize<'de> for MetadataObject {
59 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
60 where
61 D: serde::Deserializer<'de>,
62 {
63 let mut map = serde_json::Map::deserialize(deserializer)?;
64
65 let type_ = map
66 .remove("type")
67 .ok_or(de::Error::missing_field("type"))?
68 .as_u64()
69 .ok_or(de::Error::custom("`type` is not an integer"))?;
70 let rest = Value::Object(map);
71
72 match type_ {
73 0 => {
74 let title = match rest.get("title") {
75 Some(t) => Some(
76 t.as_str()
77 .ok_or(de::Error::custom("`title` is not a string"))?
78 .to_owned(),
79 ),
80 None => None,
81 };
82 let thumbnail_url = match rest.get("thumbnailUrl") {
83 Some(t) => Some(
84 t.as_str()
85 .ok_or(de::Error::custom("`thumbnailUrl` is not a string"))?
86 .to_owned(),
87 ),
88 None => None,
89 };
90 Ok(Self::Generic {
91 title,
92 thumbnail_url,
93 custom: rest.get("custom").cloned(),
94 })
95 }
96 _ => Err(de::Error::custom(format!("Unknown metadata type {type_}"))),
97 }
98 }
99}
100
101#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
102pub struct PlayMessage {
103 pub container: String,
105 pub url: Option<String>,
107 pub content: Option<String>,
109 pub time: Option<f64>,
111 pub volume: Option<f64>,
113 pub speed: Option<f64>,
115 pub headers: Option<HashMap<String, String>>,
117 pub metadata: Option<MetadataObject>,
118}
119
120#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Deserialize_repr, Serialize_repr)]
121#[repr(u8)]
122pub enum ContentType {
123 #[default]
124 Playlist = 0,
125}
126
127#[derive(Debug, PartialEq, Clone, Default, Serialize, Deserialize)]
128pub struct MediaItem {
129 pub container: String,
131 pub url: Option<String>,
133 pub content: Option<String>,
135 pub time: Option<f64>,
137 pub volume: Option<f64>,
139 pub speed: Option<f64>,
141 pub cache: Option<bool>,
143 #[serde(rename = "showDuration")]
145 pub show_duration: Option<f64>,
146 pub headers: Option<HashMap<String, String>>,
148 pub metadata: Option<MetadataObject>,
149}
150
151#[derive(Debug, PartialEq, Clone, Default, Serialize, Deserialize)]
152pub struct PlaylistContent {
153 #[serde(rename = "contentType")]
154 pub variant: ContentType,
155 pub items: Vec<MediaItem>,
156 pub offset: Option<u64>, pub volume: Option<f64>,
160 pub speed: Option<f64>,
162 #[serde(rename = "forwardCache")]
164 pub forward_cache: Option<u64>,
165 #[serde(rename = "backwardCache")]
167 pub backward_cache: Option<u64>,
168 pub metadata: Option<MetadataObject>,
169}
170
171#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize_repr, Deserialize_repr)]
172#[repr(u8)]
173pub enum PlaybackState {
174 Idle = 0,
175 Playing = 1,
176 Paused = 2,
177}
178
179#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
180pub struct PlaybackUpdateMessage {
181 #[serde(rename = "generationTime")]
183 pub generation_time: u64,
184 pub state: PlaybackState,
186 pub time: Option<f64>,
188 pub duration: Option<f64>,
190 pub speed: Option<f64>,
192 #[serde(rename = "itemIndex")]
194 pub item_index: Option<u64>,
195}
196
197#[derive(Debug, PartialEq, Eq, Clone, Default, Serialize, Deserialize)]
198pub struct InitialSenderMessage {
199 #[serde(rename = "displayName")]
200 pub display_name: Option<String>,
201 #[serde(rename = "appName")]
202 pub app_name: Option<String>,
203 #[serde(rename = "appVersion")]
204 pub app_version: Option<String>,
205}
206
207#[derive(Debug, PartialEq, Eq, Clone, Default, Serialize, Deserialize)]
208pub struct LivestreamCapabilities {
209 pub whep: Option<bool>,
211}
212
213#[derive(Debug, PartialEq, Eq, Clone, Default, Serialize, Deserialize)]
214pub struct AVCapabilities {
215 pub livestream: Option<LivestreamCapabilities>,
216}
217
218#[derive(Debug, PartialEq, Eq, Clone, Default, Serialize, Deserialize)]
219pub struct ReceiverCapabilities {
220 pub av: Option<AVCapabilities>,
221}
222
223#[allow(dead_code)]
224#[derive(Debug, PartialEq, Clone, Default, Serialize, Deserialize)]
225pub struct InitialReceiverMessage {
226 #[serde(rename = "displayName")]
227 pub display_name: Option<String>,
228 #[serde(rename = "appName")]
229 pub app_name: Option<String>,
230 #[serde(rename = "appVersion")]
231 pub app_version: Option<String>,
232 #[serde(rename = "playData")]
233 pub play_data: Option<PlayMessage>,
234 #[serde(rename = "experimentalCapabilities")]
235 pub experimental_capabilities: Option<ReceiverCapabilities>,
236}
237
238#[allow(dead_code)]
239#[derive(Debug, PartialEq, Clone, Default, Serialize, Deserialize)]
240pub struct PlayUpdateMessage {
241 #[serde(rename = "generationTime")]
242 pub generation_time: Option<u64>,
243 #[serde(rename = "playData")]
244 pub play_data: Option<PlayMessage>,
245}
246
247#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
248pub struct SetPlaylistItemMessage {
249 #[serde(rename = "itemIndex")]
250 pub item_index: u64,
251}
252
253#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
254pub enum KeyNames {
255 ArrowLeft,
256 ArrowRight,
257 ArrowUp,
258 ArrowDown,
259 Enter,
260}
261
262#[allow(dead_code)]
263impl KeyNames {
264 pub fn all() -> Vec<String> {
265 vec![
266 "ArrowLeft".to_owned(),
267 "ArrowRight".to_owned(),
268 "ArrowUp".to_owned(),
269 "ArrowDown".to_owned(),
270 "Enter".to_owned(),
271 ]
272 }
273}
274
275#[derive(Debug, PartialEq, Eq, Clone)]
276pub enum EventSubscribeObject {
277 MediaItemStart,
278 MediaItemEnd,
279 MediaItemChanged,
280 KeyDown { keys: Vec<String> },
281 KeyUp { keys: Vec<String> },
282}
283
284impl Serialize for EventSubscribeObject {
285 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
286 where
287 S: serde::Serializer,
288 {
289 let mut map = serde_json::Map::new();
290 let type_val: u64 = match self {
291 EventSubscribeObject::MediaItemStart => 0,
292 EventSubscribeObject::MediaItemEnd => 1,
293 EventSubscribeObject::MediaItemChanged => 2,
294 EventSubscribeObject::KeyDown { .. } => 3,
295 EventSubscribeObject::KeyUp { .. } => 4,
296 };
297
298 map.insert("type".to_owned(), json!(type_val));
299
300 let keys = match self {
301 EventSubscribeObject::KeyDown { keys } => Some(keys),
302 EventSubscribeObject::KeyUp { keys } => Some(keys),
303 _ => None,
304 };
305 if let Some(keys) = keys {
306 map.insert("keys".to_owned(), json!(keys));
307 }
308
309 map.serialize(serializer)
310 }
311}
312
313impl<'de> Deserialize<'de> for EventSubscribeObject {
314 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
315 where
316 D: serde::Deserializer<'de>,
317 {
318 let mut map = serde_json::Map::deserialize(deserializer)?;
319 let type_ = map
320 .remove("type")
321 .ok_or(de::Error::missing_field("type"))?
322 .as_u64()
323 .ok_or(de::Error::custom("`type` is not an integer"))?;
324 let rest = Value::Object(map);
325
326 match type_ {
327 0 => Ok(Self::MediaItemStart),
328 1 => Ok(Self::MediaItemEnd),
329 2 => Ok(Self::MediaItemChanged),
330 3 | 4 => {
331 let keys = get_from_map!(rest, "keys")?
332 .as_array()
333 .ok_or(de::Error::custom("`type` is not an array"))?
334 .iter()
335 .map(|v| v.as_str().map(|s| s.to_owned()))
336 .collect::<Option<Vec<String>>>()
337 .ok_or(de::Error::custom("`type` is not an array of strings"))?;
338 if type_ == 3 {
339 Ok(Self::KeyDown { keys })
340 } else {
341 Ok(Self::KeyUp { keys })
342 }
343 }
344 _ => Err(de::Error::custom(format!("Unknown event type {type_}"))),
345 }
346 }
347}
348
349#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
350pub struct SubscribeEventMessage {
351 pub event: EventSubscribeObject,
352}
353
354#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
355pub struct UnsubscribeEventMessage {
356 pub event: EventSubscribeObject,
357}
358
359#[derive(Debug, PartialEq, Eq, Clone, Copy)]
360#[repr(u8)]
361pub enum EventType {
362 MediaItemStart = 0,
363 MediaItemEnd = 1,
364 MediaItemChange = 2,
365 KeyDown = 3,
366 KeyUp = 4,
367}
368
369#[derive(Debug, PartialEq, Clone)]
370#[allow(clippy::large_enum_variant)]
371pub enum EventObject {
372 MediaItem {
373 variant: EventType,
374 item: MediaItem,
375 },
376 Key {
377 variant: EventType,
378 key: String,
379 repeat: bool,
380 handled: bool,
381 },
382}
383
384impl Serialize for EventObject {
385 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
386 where
387 S: serde::Serializer,
388 {
389 let mut map = serde_json::Map::new();
390
391 match self {
392 EventObject::MediaItem { variant, item } => {
393 map.insert("type".to_owned(), json!(*variant as u8));
394 map.insert(
395 "item".to_owned(),
396 serde_json::to_value(item).map_err(ser::Error::custom)?,
397 );
398 }
399 EventObject::Key {
400 variant,
401 key,
402 repeat,
403 handled,
404 } => {
405 map.insert("type".to_owned(), json!(*variant as u8));
406 map.insert(
407 "key".to_owned(),
408 serde_json::to_value(key).map_err(ser::Error::custom)?,
409 );
410 map.insert(
411 "repeat".to_owned(),
412 serde_json::to_value(repeat).map_err(ser::Error::custom)?,
413 );
414 map.insert(
415 "handled".to_owned(),
416 serde_json::to_value(handled).map_err(ser::Error::custom)?,
417 );
418 }
419 }
420
421 map.serialize(serializer)
422 }
423}
424
425impl<'de> Deserialize<'de> for EventObject {
426 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
427 where
428 D: de::Deserializer<'de>,
429 {
430 let mut map = serde_json::Map::deserialize(deserializer)?;
431 let type_ = map
432 .remove("type")
433 .ok_or(de::Error::missing_field("type"))?
434 .as_u64()
435 .ok_or(de::Error::custom("`type` is not an integer"))?;
436 let rest = Value::Object(map);
437
438 match type_ {
439 #[allow(clippy::manual_range_patterns)]
440 0 | 1 | 2 => {
441 let variant = match type_ {
442 0 => EventType::MediaItemStart,
443 1 => EventType::MediaItemEnd,
444 _ => EventType::MediaItemChange,
445 };
446 let item = get_from_map!(rest, "item")?;
447 Ok(Self::MediaItem {
448 variant,
449 item: MediaItem::deserialize(item).map_err(de::Error::custom)?,
450 })
451 }
452 3 | 4 => {
453 let variant = if type_ == 3 {
454 EventType::KeyDown
455 } else {
456 EventType::KeyUp
457 };
458 Ok(Self::Key {
459 variant,
460 key: get_from_map!(rest, "key")?
461 .as_str()
462 .ok_or(de::Error::custom("`key` is not a string"))?
463 .to_owned(),
464 repeat: get_from_map!(rest, "repeat")?
465 .as_bool()
466 .ok_or(de::Error::custom("`repeat` is not a bool"))?,
467 handled: get_from_map!(rest, "handled")?
468 .as_bool()
469 .ok_or(de::Error::custom("`handled` is not a bool"))?,
470 })
471 }
472 _ => Err(de::Error::custom(format!("Unknown event type {type_}"))),
473 }
474 }
475}
476
477#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
478pub struct EventMessage {
479 #[serde(rename = "generationTime")]
480 pub generation_time: u64,
481 pub event: EventObject,
482}
483
484#[cfg(test)]
485mod tests {
486 use super::*;
487
488 macro_rules! s {
489 ($s:expr) => {
490 ($s).to_string()
491 };
492 }
493
494 #[test]
495 fn serialize_metadata_object() {
496 assert_eq!(
497 &serde_json::to_string(&MetadataObject::Generic {
498 title: Some(s!("abc")),
499 thumbnail_url: Some(s!("def")),
500 custom: Some(serde_json::Value::Null),
501 })
502 .unwrap(),
503 r#"{"custom":null,"thumbnailUrl":"def","title":"abc","type":0}"#
504 );
505 assert_eq!(
506 &serde_json::to_string(&MetadataObject::Generic {
507 title: None,
508 thumbnail_url: None,
509 custom: Some(serde_json::Value::Null),
510 })
511 .unwrap(),
512 r#"{"custom":null,"thumbnailUrl":null,"title":null,"type":0}"#
513 );
514 assert_eq!(
515 &serde_json::to_string(&MetadataObject::Generic {
516 title: Some(s!("abc")),
517 thumbnail_url: Some(s!("def")),
518 custom: None,
519 })
520 .unwrap(),
521 r#"{"thumbnailUrl":"def","title":"abc","type":0}"#
522 );
523 }
524
525 #[test]
526 fn deserialize_metadata_object() {
527 assert_eq!(
528 serde_json::from_str::<MetadataObject>(
529 r#"{"type":0,"title":"abc","thumbnailUrl":"def","custom":null}"#
530 )
531 .unwrap(),
532 MetadataObject::Generic {
533 title: Some(s!("abc")),
534 thumbnail_url: Some(s!("def")),
535 custom: Some(serde_json::Value::Null),
536 }
537 );
538 assert_eq!(
539 serde_json::from_str::<MetadataObject>(r#"{"type":0,"custom":null}"#).unwrap(),
540 MetadataObject::Generic {
541 title: None,
542 thumbnail_url: None,
543 custom: Some(serde_json::Value::Null),
544 }
545 );
546 assert_eq!(
547 serde_json::from_str::<MetadataObject>(r#"{"type":0}"#).unwrap(),
548 MetadataObject::Generic {
549 title: None,
550 thumbnail_url: None,
551 custom: None,
552 }
553 );
554 assert!(serde_json::from_str::<MetadataObject>(r#"{"type":1"#).is_err());
555 }
556
557 #[test]
558 fn serialize_event_sub_obj() {
559 assert_eq!(
560 &serde_json::to_string(&EventSubscribeObject::MediaItemStart).unwrap(),
561 r#"{"type":0}"#
562 );
563 assert_eq!(
564 &serde_json::to_string(&EventSubscribeObject::MediaItemEnd).unwrap(),
565 r#"{"type":1}"#
566 );
567 assert_eq!(
568 &serde_json::to_string(&EventSubscribeObject::MediaItemChanged).unwrap(),
569 r#"{"type":2}"#
570 );
571 assert_eq!(
572 &serde_json::to_string(&EventSubscribeObject::KeyDown { keys: vec![] }).unwrap(),
573 r#"{"keys":[],"type":3}"#
574 );
575 assert_eq!(
576 &serde_json::to_string(&EventSubscribeObject::KeyDown { keys: vec![] }).unwrap(),
577 r#"{"keys":[],"type":3}"#
578 );
579 assert_eq!(
580 &serde_json::to_string(&EventSubscribeObject::KeyUp {
581 keys: vec![s!("abc"), s!("def")]
582 })
583 .unwrap(),
584 r#"{"keys":["abc","def"],"type":4}"#
585 );
586 assert_eq!(
587 &serde_json::to_string(&EventSubscribeObject::KeyDown {
588 keys: vec![s!("abc"), s!("def")]
589 })
590 .unwrap(),
591 r#"{"keys":["abc","def"],"type":3}"#
592 );
593 assert_eq!(
594 &serde_json::to_string(&EventSubscribeObject::KeyDown {
595 keys: vec![s!("\"\"")]
596 })
597 .unwrap(),
598 r#"{"keys":["\"\""],"type":3}"#
599 );
600 }
601
602 #[test]
603 fn deserialize_event_sub_obj() {
604 assert_eq!(
605 serde_json::from_str::<EventSubscribeObject>(r#"{"type":0}"#).unwrap(),
606 EventSubscribeObject::MediaItemStart
607 );
608 assert_eq!(
609 serde_json::from_str::<EventSubscribeObject>(r#"{"type":1}"#).unwrap(),
610 EventSubscribeObject::MediaItemEnd
611 );
612 assert_eq!(
613 serde_json::from_str::<EventSubscribeObject>(r#"{"type":2}"#).unwrap(),
614 EventSubscribeObject::MediaItemChanged
615 );
616 assert_eq!(
617 serde_json::from_str::<EventSubscribeObject>(r#"{"keys":[],"type":3}"#).unwrap(),
618 EventSubscribeObject::KeyDown { keys: vec![] }
619 );
620 assert_eq!(
621 serde_json::from_str::<EventSubscribeObject>(r#"{"keys":[],"type":4}"#).unwrap(),
622 EventSubscribeObject::KeyUp { keys: vec![] }
623 );
624 assert_eq!(
625 serde_json::from_str::<EventSubscribeObject>(r#"{"keys":["abc","def"],"type":3}"#)
626 .unwrap(),
627 EventSubscribeObject::KeyDown {
628 keys: vec![s!("abc"), s!("def")]
629 }
630 );
631 assert_eq!(
632 serde_json::from_str::<EventSubscribeObject>(r#"{"keys":["abc","def"],"type":4}"#)
633 .unwrap(),
634 EventSubscribeObject::KeyUp {
635 keys: vec![s!("abc"), s!("def")]
636 }
637 );
638 assert!(serde_json::from_str::<EventSubscribeObject>(r#""type":5}"#).is_err());
639 }
640
641 const EMPTY_TEST_MEDIA_ITEM: MediaItem = MediaItem {
642 container: String::new(),
643 url: None,
644 content: None,
645 time: None,
646 volume: None,
647 speed: None,
648 cache: None,
649 show_duration: None,
650 headers: None,
651 metadata: None,
652 };
653 const TEST_MEDIA_ITEM_JSON: &str = r#"{"cache":null,"container":"","content":null,"headers":null,"metadata":null,"showDuration":null,"speed":null,"time":null,"url":null,"volume":null}"#;
654
655 #[test]
656 fn serialize_event_obj() {
657 assert_eq!(
658 serde_json::to_string(&EventObject::MediaItem {
659 variant: EventType::MediaItemStart,
660 item: EMPTY_TEST_MEDIA_ITEM.clone(),
661 })
662 .unwrap(),
663 format!(r#"{{"item":{TEST_MEDIA_ITEM_JSON},"type":0}}"#),
664 );
665 assert_eq!(
666 serde_json::to_string(&EventObject::MediaItem {
667 variant: EventType::MediaItemEnd,
668 item: EMPTY_TEST_MEDIA_ITEM.clone(),
669 })
670 .unwrap(),
671 format!(r#"{{"item":{TEST_MEDIA_ITEM_JSON},"type":1}}"#),
672 );
673 assert_eq!(
674 serde_json::to_string(&EventObject::MediaItem {
675 variant: EventType::MediaItemChange,
676 item: EMPTY_TEST_MEDIA_ITEM.clone(),
677 })
678 .unwrap(),
679 format!(r#"{{"item":{TEST_MEDIA_ITEM_JSON},"type":2}}"#),
680 );
681 assert_eq!(
682 &serde_json::to_string(&EventObject::Key {
683 variant: EventType::KeyDown,
684 key: s!(""),
685 repeat: false,
686 handled: false,
687 })
688 .unwrap(),
689 r#"{"handled":false,"key":"","repeat":false,"type":3}"#
690 );
691 assert_eq!(
692 &serde_json::to_string(&EventObject::Key {
693 variant: EventType::KeyUp,
694 key: s!(""),
695 repeat: false,
696 handled: false,
697 })
698 .unwrap(),
699 r#"{"handled":false,"key":"","repeat":false,"type":4}"#
700 );
701 }
702
703 #[test]
704 fn deserialize_event_obj() {
705 assert_eq!(
706 serde_json::from_str::<EventObject>(&format!(
707 r#"{{"item":{TEST_MEDIA_ITEM_JSON},"type":0}}"#
708 ))
709 .unwrap(),
710 EventObject::MediaItem {
711 variant: EventType::MediaItemStart,
712 item: EMPTY_TEST_MEDIA_ITEM.clone(),
713 }
714 );
715 assert_eq!(
716 serde_json::from_str::<EventObject>(&format!(
717 r#"{{"item":{TEST_MEDIA_ITEM_JSON},"type":1}}"#
718 ))
719 .unwrap(),
720 EventObject::MediaItem {
721 variant: EventType::MediaItemEnd,
722 item: EMPTY_TEST_MEDIA_ITEM.clone(),
723 }
724 );
725 assert_eq!(
726 serde_json::from_str::<EventObject>(&format!(
727 r#"{{"item":{TEST_MEDIA_ITEM_JSON},"type":2}}"#
728 ))
729 .unwrap(),
730 EventObject::MediaItem {
731 variant: EventType::MediaItemChange,
732 item: EMPTY_TEST_MEDIA_ITEM.clone(),
733 }
734 );
735 assert_eq!(
736 serde_json::from_str::<EventObject>(
737 r#"{"handled":false,"key":"","repeat":false,"type":3}"#
738 )
739 .unwrap(),
740 EventObject::Key {
741 variant: EventType::KeyDown,
742 key: s!(""),
743 repeat: false,
744 handled: false,
745 }
746 );
747 assert_eq!(
748 serde_json::from_str::<EventObject>(
749 r#"{"handled":false,"key":"","repeat":false,"type":4}"#
750 )
751 .unwrap(),
752 EventObject::Key {
753 variant: EventType::KeyUp,
754 key: s!(""),
755 repeat: false,
756 handled: false,
757 }
758 );
759 assert!(serde_json::from_str::<EventObject>(r#"{"type":5}"#).is_err());
760 }
761
762 #[test]
763 fn serialize_playlist_content() {
764 assert_eq!(
765 serde_json::to_string(&PlaylistContent {
766 variant: ContentType::Playlist,
767 items: Vec::new(),
768 offset: None,
769 volume: None,
770 speed: None,
771 forward_cache: None,
772 backward_cache: None,
773 metadata: None
774 })
775 .unwrap(),
776 r#"{"contentType":0,"items":[],"offset":null,"volume":null,"speed":null,"forwardCache":null,"backwardCache":null,"metadata":null}"#,
777 );
778 assert_eq!(
779 serde_json::to_string(&PlaylistContent {
780 variant: ContentType::Playlist,
781 items: vec![MediaItem {
782 container: "video/mp4".to_string(),
783 url: Some("abc".to_string()),
784 content: None,
785 time: None,
786 volume: None,
787 speed: None,
788 cache: None,
789 show_duration: None,
790 headers: None,
791 metadata: None
792 }],
793 offset: None,
794 volume: None,
795 speed: None,
796 forward_cache: None,
797 backward_cache: None,
798 metadata: None
799 })
800 .unwrap(),
801 r#"{"contentType":0,"items":[{"container":"video/mp4","url":"abc","content":null,"time":null,"volume":null,"speed":null,"cache":null,"showDuration":null,"headers":null,"metadata":null}],"offset":null,"volume":null,"speed":null,"forwardCache":null,"backwardCache":null,"metadata":null}"#,
802 );
803 }
804}