Skip to main content

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