1use std::{collections::HashMap, time::Duration};
2
3use flatbuffers::FlatBufferBuilder;
4
5pub mod fcast_flatbuffers {
6 #![allow(dead_code)]
7 #![allow(unused_imports)]
8 #![allow(clippy::extra_unused_lifetimes)]
9 #![allow(clippy::missing_safety_doc)]
10 #![allow(clippy::derivable_impls)]
11
12 include!("v4_generated.rs");
13}
14
15pub use flatbuffers;
16
17pub use fcast_flatbuffers::fcast::{v4 as flat, v4::PlaybackState};
18
19pub const MAX_PACKET_SIZE: usize = 512 * 1024;
20
21pub struct ConstructedMessage<'a> {
22 builder: flatbuffers::FlatBufferBuilder<'a>,
23}
24
25impl std::fmt::Debug for ConstructedMessage<'_> {
26 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
27 f.write_str("ConstructedMessage { ... }")
28 }
29}
30
31impl PartialEq for ConstructedMessage<'_> {
32 fn eq(&self, _other: &Self) -> bool {
33 false
34 }
35}
36
37impl std::ops::Deref for ConstructedMessage<'_> {
38 type Target = [u8];
39
40 fn deref(&self) -> &Self::Target {
41 self.builder.finished_data()
42 }
43}
44
45pub struct MessageBuilder<'a> {
46 builder: flatbuffers::FlatBufferBuilder<'a>,
47}
48
49use paste::paste;
50use smol_str::SmolStr;
51
52macro_rules! create_msg {
53 ($self:expr, $name:expr, $($field:ident $(: $value:expr)? ),* $(,)?) => {{
54 paste! {
55 let value = flat::[<$name>]::create(
56 &mut $self.builder,
57 &flat:: [<$name Args>] {
58 $($field: create_msg!(@value $field $(: $value)?)),*
59 }
60
61 ).as_union_value();
62 $self.create_and_finish_envelope(flat::Message::[<$name>], value)
63 }
64 }};
65 (@value $field:ident : $value:expr) => { $value };
66 (@value $field:ident) => { $field };
67}
68
69macro_rules! create_str {
70 ($self:expr, $str:expr) => {
71 Some($self.builder.create_string(&$str))
72 };
73}
74
75macro_rules! maybe_create_str {
76 ($self:expr, $str:expr) => {
77 if let Some(s) = $str {
78 create_str!($self, s)
79 } else {
80 None
81 }
82 };
83}
84
85macro_rules! create_device_info {
86 ($self:expr, $device_info:expr) => {{
87 let args = flat::DeviceInfoArgs {
88 display_name: maybe_create_str!($self, &$device_info.display_name),
89 app_name: maybe_create_str!($self, &$device_info.app_name),
90 app_version: maybe_create_str!($self, &$device_info.app_version),
91 };
92
93 flat::DeviceInfo::create(&mut $self.builder, &args)
94 }};
95}
96
97impl<'a> MessageBuilder<'a> {
98 #[allow(clippy::new_without_default)]
99 pub fn new() -> Self {
100 Self {
101 builder: FlatBufferBuilder::new(),
102 }
103 }
104
105 fn create_and_finish_envelope(
106 mut self,
107 payload_type: flat::Message,
108 payload: flatbuffers::WIPOffset<flatbuffers::UnionWIPOffset>,
109 ) -> ConstructedMessage<'a> {
110 let envelope = flat::Packet::create(
111 &mut self.builder,
112 &flat::PacketArgs {
113 payload_type,
114 payload: Some(payload),
115 },
116 );
117
118 self.builder.finish(envelope, None);
119
120 ConstructedMessage {
121 builder: self.builder,
122 }
123 }
124
125 pub fn progress_changed_raw(
126 mut self,
127 position: Option<&flat::Time>,
128 duration: Option<&flat::Time>,
129 ) -> ConstructedMessage<'a> {
130 create_msg!(self, ProgressChanged, position, duration)
131 }
132
133 pub fn progress_changed(
134 self,
135 position: flat::Time,
136 duration: flat::Time,
137 ) -> ConstructedMessage<'a> {
138 self.progress_changed_raw(Some(&position), Some(&duration))
139 }
140
141 pub fn set_progress_update_interval_raw(
142 mut self,
143 interval: Option<&flat::Time>,
144 ) -> ConstructedMessage<'a> {
145 create_msg!(self, SetProgressUpdateInterval, interval)
146 }
147
148 pub fn set_progress_update_interval(self, interval: flat::Time) -> ConstructedMessage<'a> {
149 self.set_progress_update_interval_raw(Some(&interval))
150 }
151
152 pub fn volume_changed(mut self, volume: f32) -> ConstructedMessage<'a> {
153 create_msg!(self, VolumeChanged, volume)
154 }
155
156 pub fn speed_changed(mut self, speed: f32) -> ConstructedMessage<'a> {
157 create_msg!(self, SpeedChanged, speed)
158 }
159
160 pub fn playback_state_changed(mut self, state: flat::PlaybackState) -> ConstructedMessage<'a> {
161 create_msg!(self, PlaybackStateChanged, state)
162 }
163
164 pub fn change_track(
165 mut self,
166 id: Option<u32>,
167 typ: flat::MediaTrackType,
168 ) -> ConstructedMessage<'a> {
169 create_msg!(self, ChangeTrack, id, track_type: typ)
170 }
171
172 pub fn add_subtitle_source(
173 mut self,
174 url: &str,
175 select: bool,
176 name: Option<&str>,
177 ) -> ConstructedMessage<'a> {
178 let url = self.builder.create_string(url);
179 let name = name.map(|n| self.builder.create_string(n));
180 create_msg!(
181 self,
182 AddSubtitleSource,
183 url: Some(url),
184 select,
185 name,
186 metadata: None
187 )
188 }
189
190 pub fn companion_hello_request(mut self) -> ConstructedMessage<'a> {
191 create_msg!(self, CompanionHelloRequest,)
192 }
193
194 pub fn companion_hello_response(mut self, provider_id: u16) -> ConstructedMessage<'a> {
195 create_msg!(self, CompanionHelloResponse, provider_id)
196 }
197
198 pub fn companion_resource_info_request(
199 mut self,
200 request_id: u32,
201 resource_id: u32,
202 ) -> ConstructedMessage<'a> {
203 create_msg!(self, CompanionResourceInfoRequest, request_id, resource_id)
204 }
205
206 pub fn companion_resource_info_response(
207 mut self,
208 request_id: u32,
209 content_type: &str,
210 size: Option<u64>,
211 ) -> ConstructedMessage<'a> {
212 let content_type = self.builder.create_string(content_type);
213 let (resource_size_type, resource_size) = match size {
214 Some(size) => (
215 flat::CompanionResourceSize::Known,
216 flat::KnownResourceSize::create(
217 &mut self.builder,
218 &flat::KnownResourceSizeArgs { size },
219 )
220 .as_union_value(),
221 ),
222 None => (
223 flat::CompanionResourceSize::Unknown,
224 flat::UnknownResourceSize::create(
225 &mut self.builder,
226 &flat::UnknownResourceSizeArgs {},
227 )
228 .as_union_value(),
229 ),
230 };
231 create_msg!(
232 self,
233 CompanionResourceInfoResponse,
234 request_id,
235 content_type: Some(content_type),
236 resource_size_type: resource_size_type,
237 resource_size: Some(resource_size)
238 )
239 }
240
241 pub fn sender_introduction(mut self, device_info: &DeviceInfo) -> ConstructedMessage<'a> {
242 let device_info = create_device_info!(self, device_info);
243 create_msg!(self, SenderIntroduction, device_info: Some(device_info))
244 }
245
246 pub fn start_mirroring_session(mut self, session_id: u16) -> ConstructedMessage<'a> {
247 create_msg!(self, StartMirroringSession, session_id)
248 }
249
250 pub fn mirroring_session_description(
251 mut self,
252 session_id: u16,
253 sdp: &str,
254 ) -> ConstructedMessage<'a> {
255 let sdp = self.builder.create_string(sdp);
256 create_msg!(self, MirroringSessionDescription, session_id, sdp: Some(sdp))
257 }
258
259 pub fn stop_playback(mut self) -> ConstructedMessage<'a> {
260 create_msg!(self, StopPlayback,)
261 }
262
263 fn strip_flat_media_item(
264 &mut self,
265 item: flat::MediaItem<'_>,
266 ) -> flatbuffers::WIPOffset<flat::MediaItem<'a>> {
267 let extra_metadata = read_extra_metadata(&item)
268 .filter(|m| !m.is_empty())
269 .map(|m| self.build_extra_metadata(&m));
270 let args = flat::MediaItemArgs {
271 container: create_str!(self, item.container()),
272 source_url: create_str!(self, item.source_url()),
273 start_time: item.start_time(),
274 volume: item.volume(),
275 speed: item.speed(),
276 headers: None, title: item.title().map(|s| self.builder.create_string(s)),
278 thumbnail_url: item.thumbnail_url().map(|s| self.builder.create_string(s)),
279 metadata_type: flat::Metadata::NONE,
281 metadata: None,
282 extra_metadata,
283 };
284 flat::MediaItem::create(&mut self.builder, &args)
285 }
286
287 pub fn from_play_stripped(mut self, play: &flat::Load) -> Option<ConstructedMessage<'a>> {
288 let (source_type, source) = match play.source_type() {
289 flat::MediaSource::Single => {
290 let item = self
291 .strip_flat_media_item(play.source_as_single()?)
292 .as_union_value();
293 (flat::MediaSource::Single, item)
294 }
295 flat::MediaSource::Queue => {
296 let msg = play.source_as_queue()?;
297 let queue_items = msg
298 .items()
299 .iter()
300 .map(|queue_item| {
301 let item = self.strip_flat_media_item(queue_item.media_item());
302 flat::QueueItem::create(
303 &mut self.builder,
304 &flat::QueueItemArgs {
305 media_item: Some(item),
306 playback_duration: queue_item.playback_duration(),
307 },
308 )
309 })
310 .collect::<Vec<_>>();
311
312 let items = self.builder.create_vector(&queue_items);
313 let queue = flat::Queue::create(
314 &mut self.builder,
315 &flat::QueueArgs {
316 items: Some(items),
317 start_index: msg.start_index(),
318 autoplay: msg.autoplay(),
319 },
320 )
321 .as_union_value();
322
323 (flat::MediaSource::Queue, queue)
324 }
325 _ => return None,
326 };
327
328 Some(create_msg!(self, Load, source_type, source: Some(source)))
329 }
330
331 pub fn from_queue_insert_stripped(
332 mut self,
333 insert: &flat::QueueInsert,
334 ) -> Option<ConstructedMessage<'a>> {
335 let position = match insert.position_type() {
336 flat::QueuePosition::Index => QueuePosition::Index(insert.position_as_index()?.index()),
337 flat::QueuePosition::Front => QueuePosition::Front,
338 flat::QueuePosition::Back => QueuePosition::Back,
339 _ => return None,
340 };
341 let (pos_type, pos) = self.queue_position(position);
342 let item = self.strip_flat_media_item(insert.item().media_item());
343 let q_item = flat::QueueItem::create(
344 &mut self.builder,
345 &flat::QueueItemArgs {
346 media_item: Some(item),
347 playback_duration: insert.item().playback_duration(),
348 },
349 );
350 Some(
351 create_msg!(self, QueueInsert, item: Some(q_item), position_type: pos_type, position: Some(pos)),
352 )
353 }
354
355 fn build_meta_value(
359 &mut self,
360 value: &MetaValue,
361 ) -> (
362 flat::GenericMetaValue,
363 flatbuffers::WIPOffset<flatbuffers::UnionWIPOffset>,
364 ) {
365 match value {
366 MetaValue::String(s) => {
367 let v = self.builder.create_string(s);
368 let off = flat::GenericMetaString::create(
369 &mut self.builder,
370 &flat::GenericMetaStringArgs { value: Some(v) },
371 );
372 (flat::GenericMetaValue::String, off.as_union_value())
373 }
374 MetaValue::Float(f) => {
375 let off = flat::GenericMetaFloat::create(
376 &mut self.builder,
377 &flat::GenericMetaFloatArgs { value: *f },
378 );
379 (flat::GenericMetaValue::Float, off.as_union_value())
380 }
381 MetaValue::Int(i) => {
382 let off = flat::GenericMetaInt::create(
383 &mut self.builder,
384 &flat::GenericMetaIntArgs { value: *i },
385 );
386 (flat::GenericMetaValue::Int, off.as_union_value())
387 }
388 MetaValue::List(items) => {
389 let wrapped = items
390 .iter()
391 .map(|v| {
392 let (value_type, value) = self.build_meta_value(v);
393 flat::WrappedGenericMetaValue::create(
394 &mut self.builder,
395 &flat::WrappedGenericMetaValueArgs { value_type, value: Some(value) },
396 )
397 })
398 .collect::<Vec<_>>();
399 let vec = self.builder.create_vector(&wrapped);
400 let off = flat::GenericMetaList::create(
401 &mut self.builder,
402 &flat::GenericMetaListArgs { value: Some(vec) },
403 );
404 (flat::GenericMetaValue::List, off.as_union_value())
405 }
406 MetaValue::KvPair { key, value } => {
407 let kv = self.build_meta_kv(key, value);
408 (flat::GenericMetaValue::KVPair, kv.as_union_value())
409 }
410 }
411 }
412
413 fn build_meta_kv(
414 &mut self,
415 key: &str,
416 value: &MetaValue,
417 ) -> flatbuffers::WIPOffset<flat::MetadataKV<'a>> {
418 let (value_type, value_off) = self.build_meta_value(value);
419 let key = self.builder.create_string(key);
420 flat::MetadataKV::create(
421 &mut self.builder,
422 &flat::MetadataKVArgs { key: Some(key), value_type, value: Some(value_off) },
423 )
424 }
425
426 fn build_extra_metadata(
429 &mut self,
430 extra: &HashMap<String, MetaValue>,
431 ) -> flatbuffers::WIPOffset<flatbuffers::Vector<'a, flatbuffers::ForwardsUOffset<flat::MetadataKV<'a>>>>
432 {
433 let mut entries: Vec<(&String, &MetaValue)> = extra.iter().collect();
434 entries.sort_by(|a, b| a.0.cmp(b.0));
435 let kvs = entries
436 .into_iter()
437 .map(|(k, v)| self.build_meta_kv(k, v))
438 .collect::<Vec<_>>();
439 self.builder.create_vector(&kvs)
440 }
441
442 fn construct_media_item(
443 &mut self,
444 item: MediaItem,
445 ) -> flatbuffers::WIPOffset<flat::MediaItem<'a>> {
446 let headers = if let Some(headers) = item.headers {
447 let elems = headers
448 .iter()
449 .map(|(k, v)| {
450 let header = flat::RequestHeaderArgs {
451 key: create_str!(self, k),
452 value: create_str!(self, v),
453 };
454 flat::RequestHeader::create(&mut self.builder, &header)
455 })
456 .collect::<Vec<_>>();
457 Some(self.builder.create_vector(&elems))
458 } else {
459 None
460 };
461
462 let (metadata_type, metadata) = match item.metadata {
463 Some(Metadata::Video { .. }) => {
464 let meta = flat::VideoMetadataArgs { chapters: None };
465 let meta = flat::VideoMetadata::create(&mut self.builder, &meta).as_union_value();
466 (flat::Metadata::Video, Some(meta))
467 }
468 Some(Metadata::Audio { artist, album }) => {
469 let meta = flat::AudioMetadataArgs {
470 artist: maybe_create_str!(self, artist),
471 album: maybe_create_str!(self, album),
472 chapters: None,
473 };
474 let meta = flat::AudioMetadata::create(&mut self.builder, &meta).as_union_value();
475 (flat::Metadata::Audio, Some(meta))
476 }
477 None => (flat::Metadata::NONE, None),
478 };
479
480 let extra_metadata = item
481 .extra_metadata
482 .as_ref()
483 .filter(|m| !m.is_empty())
484 .map(|m| self.build_extra_metadata(m));
485
486 let start_time = item
487 .start_time
488 .map(|s| flat::Time::new(Duration::from_secs_f64(s).as_micros() as u64));
489 let item = flat::MediaItemArgs {
490 container: create_str!(self, item.container),
491 source_url: create_str!(self, item.source_url),
492 start_time: start_time.as_ref(),
493 volume: item.volume,
494 speed: item.speed,
495 headers,
496 title: maybe_create_str!(self, item.title),
497 thumbnail_url: maybe_create_str!(self, item.thumbnail_url),
498 metadata_type,
499 metadata,
500 extra_metadata,
501 };
502
503 flat::MediaItem::create(&mut self.builder, &item)
504 }
505
506 pub fn load_single(mut self, item: MediaItem) -> ConstructedMessage<'a> {
507 let item = self.construct_media_item(item).as_union_value();
508 create_msg!(self, Load, source_type: flat::MediaSource::Single, source: Some(item))
509 }
510
511 pub fn load_queue<'m>(
512 mut self,
513 items: impl Iterator<Item = MediaItem<'m>>,
514 start_index: Option<u8>,
515 ) -> ConstructedMessage<'a> {
516 let items = items
517 .map(|item| {
518 let item = self.construct_media_item(item);
519 flat::QueueItem::create(
520 &mut self.builder,
521 &flat::QueueItemArgs {
522 media_item: Some(item),
523 playback_duration: None,
524 },
525 )
526 })
527 .collect::<Vec<_>>();
528
529 let items = self.builder.create_vector(&items);
530 let queue = flat::Queue::create(
531 &mut self.builder,
532 &flat::QueueArgs {
533 items: Some(items),
534 start_index,
535 autoplay: false,
536 },
537 )
538 .as_union_value();
539
540 create_msg!(self, Load, source_type: flat::MediaSource::Queue, source: Some(queue))
541 }
542
543 fn queue_position(
544 &mut self,
545 position: QueuePosition,
546 ) -> (
547 flat::QueuePosition,
548 flatbuffers::WIPOffset<flatbuffers::UnionWIPOffset>,
549 ) {
550 match position {
551 QueuePosition::Index(index) => (
552 flat::QueuePosition::Index,
553 flat::QueueIndex::create(&mut self.builder, &flat::QueueIndexArgs { index })
554 .as_union_value(),
555 ),
556 QueuePosition::Front => (
557 flat::QueuePosition::Front,
558 flat::QueueMarkerFront::create(&mut self.builder, &flat::QueueMarkerFrontArgs {})
559 .as_union_value(),
560 ),
561 QueuePosition::Back => (
562 flat::QueuePosition::Back,
563 flat::QueueMarkerBack::create(&mut self.builder, &flat::QueueMarkerBackArgs {})
564 .as_union_value(),
565 ),
566 }
567 }
568
569 pub fn queue_remove(mut self, position: QueuePosition) -> ConstructedMessage<'a> {
570 let (typ, position) = self.queue_position(position);
571 create_msg!(self, QueueRemove, position_type: typ, position: Some(position))
572 }
573
574 pub fn queue_insert(
575 mut self,
576 item: MediaItem,
577 position: QueuePosition,
578 ) -> ConstructedMessage<'a> {
579 let (pos_type, position) = self.queue_position(position);
580 let item = self.construct_media_item(item);
581 let q_item = flat::QueueItem::create(
582 &mut self.builder,
583 &flat::QueueItemArgs {
584 media_item: Some(item),
585 playback_duration: None,
586 },
587 );
588 create_msg!(self, QueueInsert, item: Some(q_item), position_type: pos_type, position: Some(position))
589 }
590
591 pub fn queue_select(mut self, position: QueuePosition) -> ConstructedMessage<'a> {
592 let (position_type, position) = self.queue_position(position);
593 create_msg!(self, QueueItemSelected, position_type, position: Some(position))
594 }
595
596 pub fn tracks_available(
597 mut self,
598 tracks: impl Iterator<Item = MediaTrack>,
599 ) -> ConstructedMessage<'a> {
600 let tracks_vec = tracks
601 .map(|track| {
602 let title = if let Some(title) = track.title {
603 Some(self.builder.create_string(&title))
604 } else {
605 None
606 };
607 let iso_639 = self.builder.create_string(&track.iso_639);
608 let (metadata_type, metadata) = match track.metadata {
609 Some(MediaTrackMetadata::Video) => (
610 flat::MediaTrackMetadata::Video,
611 Some(
612 flat::VideoTrackMeta::create(
613 &mut self.builder,
614 &flat::VideoTrackMetaArgs { resolution: None },
615 )
616 .as_union_value(),
617 ),
618 ),
619 Some(MediaTrackMetadata::Audio) => (
620 flat::MediaTrackMetadata::Audio,
621 Some(
622 flat::AudioTrackMeta::create(
623 &mut self.builder,
624 &flat::AudioTrackMetaArgs {},
625 )
626 .as_union_value(),
627 ),
628 ),
629 Some(MediaTrackMetadata::Subtitle) => (
630 flat::MediaTrackMetadata::Subtitle,
631 Some(
632 flat::SubtitleTrackMeta::create(
633 &mut self.builder,
634 &flat::SubtitleTrackMetaArgs {},
635 )
636 .as_union_value(),
637 ),
638 ),
639 None => (flat::MediaTrackMetadata::NONE, None),
640 };
641 flat::MediaTrack::create(
642 &mut self.builder,
643 &flat::MediaTrackArgs {
644 id: track.id,
645 title,
646 iso_639: Some(iso_639),
647 metadata_type,
648 metadata,
649 },
650 )
651 })
652 .collect::<Vec<_>>();
653 let tracks = self.builder.create_vector(&tracks_vec);
654
655 create_msg!(self, TracksAvailable, tracks: Some(tracks))
656 }
657
658 fn create_str_vector(
659 &mut self,
660 strs: impl Iterator<Item = &'static str>,
661 ) -> flatbuffers::WIPOffset<flatbuffers::Vector<'a, flatbuffers::ForwardsUOffset<&'a str>>>
662 {
663 let strs = strs
664 .map(|p| self.builder.create_string(p))
665 .collect::<Vec<_>>();
666 self.builder.create_vector(&strs)
667 }
668
669 pub fn receiver_introduction(
670 mut self,
671 device_info: &DeviceInfo,
672 supported_streaming_protocols: impl Iterator<Item = &'static str>,
673 supported_containers: impl Iterator<Item = &'static str>,
674 supported_video_formats: impl Iterator<Item = &'static str>,
675 supported_audio_formats: impl Iterator<Item = &'static str>,
676 supported_subtitle_formats: impl Iterator<Item = &'static str>,
677 supported_hdr_formats: impl Iterator<Item = &'static str>,
678 supported_image_formats: impl Iterator<Item = &'static str>,
679 supports_external_subtitles: bool,
680 supports_mirroring: bool,
681 volume_step_interval: f32,
682 ) -> ConstructedMessage<'a> {
683 let device_info = Some(create_device_info!(self, device_info));
684 let protocols = self.create_str_vector(supported_streaming_protocols);
685 let containers = self.create_str_vector(supported_containers);
686 let videos = self.create_str_vector(supported_video_formats);
687 let audios = self.create_str_vector(supported_audio_formats);
688 let subtitles = self.create_str_vector(supported_subtitle_formats);
689 let hdrs = self.create_str_vector(supported_hdr_formats);
690 let images = self.create_str_vector(supported_image_formats);
691
692 let media_capabilities = flat::MediaCapabilities::create(
693 &mut self.builder,
694 &flat::MediaCapabilitiesArgs {
695 protocols: Some(protocols),
696 containers: Some(containers),
697 video_formats: Some(videos),
698 audio_formats: Some(audios),
699 subtitle_formats: Some(subtitles),
700 hdr_formats: Some(hdrs),
701 image_formats: Some(images),
702 external_subtitles: supports_external_subtitles,
703 mirroring: supports_mirroring,
704 },
705 );
706 let display_capabilities = flat::DisplayCapabilities::create(
707 &mut self.builder,
708 &flat::DisplayCapabilitiesArgs { resolution: None },
709 );
710 let audio_capabilities = flat::AudioCapabilities::create(
711 &mut self.builder,
712 &flat::AudioCapabilitiesArgs {
713 volume_step_interval,
714 },
715 );
716
717 let capabilities = Some(flat::ReceiverCapabilities::create(
718 &mut self.builder,
719 &flat::ReceiverCapabilitiesArgs {
720 media: Some(media_capabilities),
721 display: Some(display_capabilities),
722 audio: Some(audio_capabilities),
723 },
724 ));
725
726 create_msg!(self, ReceiverIntroduction, device_info, capabilities)
727 }
728
729 pub fn error(
730 mut self,
731 packet_num: Option<u32>,
732 kind: flat::ErrorKind,
733 ) -> ConstructedMessage<'a> {
734 create_msg!(self, Error, kind, packet_num)
735 }
736
737 pub fn companion_resource_request(
738 mut self,
739 request_id: u32,
740 resource_id: u32,
741 read_head: Option<flat::ResourceReadHead>,
742 ) -> ConstructedMessage<'a> {
743 create_msg!(self, CompanionResourceRequest, request_id, resource_id, read_head: read_head.as_ref())
744 }
745}
746
747#[derive(Debug, Clone, PartialEq)]
751pub enum MetaValue {
752 String(String),
753 Float(f64),
754 Int(i64),
755 List(Vec<MetaValue>),
756 KvPair { key: String, value: Box<MetaValue> },
758}
759
760macro_rules! read_meta_union {
765 ($holder:expr) => {{
766 let holder = $holder;
767 match holder.value_type() {
768 flat::GenericMetaValue::String => MetaValue::String(
769 holder
770 .value_as_string()
771 .and_then(|s| s.value())
772 .unwrap_or_default()
773 .to_owned(),
774 ),
775 flat::GenericMetaValue::Float => {
776 MetaValue::Float(holder.value_as_float().map(|f| f.value()).unwrap_or_default())
777 }
778 flat::GenericMetaValue::Int => {
779 MetaValue::Int(holder.value_as_int().map(|i| i.value()).unwrap_or_default())
780 }
781 flat::GenericMetaValue::List => holder
782 .value_as_list()
783 .map(read_meta_list)
784 .unwrap_or_else(|| MetaValue::List(Vec::new())),
785 flat::GenericMetaValue::KVPair => holder
786 .value_as_kvpair()
787 .map(|inner| MetaValue::KvPair {
788 key: inner.key().to_owned(),
789 value: Box::new(read_meta_kv_value(&inner)),
790 })
791 .unwrap_or_else(|| MetaValue::List(Vec::new())),
792 _ => MetaValue::List(Vec::new()),
793 }
794 }};
795}
796
797fn read_meta_kv_value(kv: &flat::MetadataKV) -> MetaValue {
798 read_meta_union!(kv)
799}
800
801fn read_wrapped_meta_value(wrapped: &flat::WrappedGenericMetaValue) -> MetaValue {
802 read_meta_union!(wrapped)
803}
804
805fn read_meta_list(list: flat::GenericMetaList) -> MetaValue {
806 let Some(items) = list.value() else {
807 return MetaValue::List(Vec::new());
808 };
809 MetaValue::List(items.iter().map(|w| read_wrapped_meta_value(&w)).collect())
810}
811
812pub fn read_extra_metadata(item: &flat::MediaItem) -> Option<HashMap<String, MetaValue>> {
815 let kvs = item.extra_metadata()?;
816 let mut map = HashMap::with_capacity(kvs.len());
817 for kv in kvs {
818 map.insert(kv.key().to_owned(), read_meta_kv_value(&kv));
819 }
820 Some(map)
821}
822
823pub enum Metadata {
824 Video {
825 subtitle_url: Option<String>,
826 },
827 Audio {
828 artist: Option<String>,
829 album: Option<String>,
830 },
831}
832
833pub struct MediaItem<'a> {
834 pub container: String,
836 pub source_url: String,
837 pub start_time: Option<f64>,
839 pub volume: Option<f32>,
841 pub speed: Option<f32>,
843 pub headers: Option<HashMap<String, String>>,
845 pub title: Option<&'a str>,
846 pub thumbnail_url: Option<&'a str>,
847 pub metadata: Option<Metadata>,
848 pub extra_metadata: Option<HashMap<String, MetaValue>>,
849}
850
851#[derive(Debug)]
852pub struct DeviceInfo {
853 pub display_name: Option<String>,
854 pub app_name: Option<String>,
855 pub app_version: Option<String>,
856}
857
858pub enum MediaTrackMetadata {
859 Video,
860 Audio,
861 Subtitle,
862}
863
864pub struct MediaTrack {
865 pub id: u32,
866 pub title: Option<SmolStr>,
867 pub iso_639: SmolStr,
868 pub metadata: Option<MediaTrackMetadata>,
869}
870
871#[derive(Debug, Clone, Copy, PartialEq)]
872pub enum QueuePosition {
873 Index(u8),
874 Front,
875 Back,
876}
877
878#[cfg(test)]
879mod tests {
880 use super::*;
881
882 fn media_item_with_extra(extra: HashMap<String, MetaValue>) -> MediaItem<'static> {
883 MediaItem {
884 container: "video/mp4".to_owned(),
885 source_url: "http://example.test/v.mp4".to_owned(),
886 start_time: None,
887 volume: None,
888 speed: None,
889 headers: None,
890 title: Some("Title"),
891 thumbnail_url: None,
892 metadata: None,
893 extra_metadata: Some(extra),
894 }
895 }
896
897 #[test]
902 fn extra_metadata_survives_relay_strip() {
903 let mut extra = HashMap::new();
904 extra.insert("director".to_owned(), MetaValue::String("Sacha".to_owned()));
905 extra.insert("year".to_owned(), MetaValue::Int(2008));
906 extra.insert("rating".to_owned(), MetaValue::Float(4.5));
907 extra.insert(
908 "tags".to_owned(),
909 MetaValue::List(vec![
910 MetaValue::String("cgi".to_owned()),
911 MetaValue::String("short".to_owned()),
912 ]),
913 );
914 extra.insert(
915 "credits".to_owned(),
916 MetaValue::KvPair {
917 key: "writer".to_owned(),
918 value: Box::new(MetaValue::String("Proog".to_owned())),
919 },
920 );
921
922 let msg = MessageBuilder::new().load_single(media_item_with_extra(extra));
924 let load = flat::root_as_packet(&msg).unwrap().payload_as_load().unwrap();
925
926 let relayed = MessageBuilder::new().from_play_stripped(&load).unwrap();
928 let single = flat::root_as_packet(&relayed)
929 .unwrap()
930 .payload_as_load()
931 .unwrap()
932 .source_as_single()
933 .unwrap();
934
935 let got = read_extra_metadata(&single).expect("relayed item keeps extra_metadata");
936 assert_eq!(got.get("director"), Some(&MetaValue::String("Sacha".to_owned())));
937 assert_eq!(got.get("year"), Some(&MetaValue::Int(2008)));
938 assert_eq!(got.get("rating"), Some(&MetaValue::Float(4.5)));
939 assert_eq!(
940 got.get("tags"),
941 Some(&MetaValue::List(vec![
942 MetaValue::String("cgi".to_owned()),
943 MetaValue::String("short".to_owned()),
944 ]))
945 );
946 assert_eq!(
947 got.get("credits"),
948 Some(&MetaValue::KvPair {
949 key: "writer".to_owned(),
950 value: Box::new(MetaValue::String("Proog".to_owned())),
951 })
952 );
953 assert!(single.headers().is_none());
955 }
956}