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