fcast_protocol/
v3.rs

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    /// The MIME type (video/mp4)
104    pub container: String,
105    // The URL to load (optional)
106    pub url: Option<String>,
107    // The content to load (i.e. a DASH manifest, json content, optional)
108    pub content: Option<String>,
109    // The time to start playing in seconds
110    pub time: Option<f64>,
111    // The desired volume (0-1)
112    pub volume: Option<f64>,
113    // The factor to multiply playback speed by (defaults to 1.0)
114    pub speed: Option<f64>,
115    // HTTP request headers to add to the play request Map<string, string>
116    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    /// The MIME type (video/mp4)
130    pub container: String,
131    /// The URL to load (optional)
132    pub url: Option<String>,
133    /// The content to load (i.e. a DASH manifest, json content, optional)
134    pub content: Option<String>,
135    /// The time to start playing in seconds
136    pub time: Option<f64>,
137    /// The desired volume (0-1)
138    pub volume: Option<f64>,
139    /// The factor to multiply playback speed by (defaults to 1.0)
140    pub speed: Option<f64>,
141    /// Indicates if the receiver should preload the media item
142    pub cache: Option<bool>,
143    /// Indicates how long the item content is presented on screen in seconds
144    #[serde(rename = "showDuration")]
145    pub show_duration: Option<f64>,
146    /// HTTP request headers to add to the play request Map<string, string>
147    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    /// Start position of the first item to play from the playlist
157    pub offset: Option<u64>, // int or float?
158    /// The desired volume (0-1)
159    pub volume: Option<f64>,
160    /// The factor to multiply playback speed by (defaults to 1.0)
161    pub speed: Option<f64>,
162    /// Count of media items should be pre-loaded forward from the current view index
163    #[serde(rename = "forwardCache")]
164    pub forward_cache: Option<u64>,
165    /// Count of media items should be pre-loaded backward from the current view index
166    #[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    // The time the packet was generated (unix time milliseconds)
182    #[serde(rename = "generationTime")]
183    pub generation_time: u64,
184    // The playback state
185    pub state: PlaybackState,
186    // The current time playing in seconds
187    pub time: Option<f64>,
188    // The duration in seconds
189    pub duration: Option<f64>,
190    // The playback speed factor
191    pub speed: Option<f64>,
192    // The playlist item index currently being played on receiver
193    #[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    /// https://datatracker.ietf.org/doc/draft-murillo-whep/
210    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}