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