matc/clusters/codec/
media_playback.rs

1//! Matter TLV encoders and decoders for Media Playback Cluster
2//! Cluster ID: 0x0506
3//!
4//! This file is automatically generated from MediaPlayback.xml
5
6use crate::tlv;
7use anyhow;
8use serde_json;
9
10
11// Import serialization helpers for octet strings
12use crate::clusters::helpers::{serialize_opt_bytes_as_hex};
13
14// Enum definitions
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
17#[repr(u8)]
18pub enum Characteristic {
19    /// Textual information meant for display when no other text representation is selected. It is used to clarify dialogue, alternate languages, texted graphics or location/person IDs that are not otherwise covered in the dubbed/localized audio.
20    Forcedsubtitles = 0,
21    /// Textual or audio media component containing a textual description (intended for audio synthesis) or an audio description describing a visual component
22    Describesvideo = 1,
23    /// Simplified or reduced captions as specified in [United States Code Title 47 CFR 79.103(c)(9)].
24    Easytoread = 2,
25    /// A media characteristic that indicates that a track selection option includes frame-based content.
26    Framebased = 3,
27    /// Main media component(s) which is/are intended for presentation if no other information is provided
28    Mainprogram = 4,
29    /// A media characteristic that indicates that a track or media selection option contains original content.
30    Originalcontent = 5,
31    /// A media characteristic that indicates that a track or media selection option contains a language translation and verbal interpretation of spoken dialog.
32    Voiceovertranslation = 6,
33    /// Textual media component containing transcriptions of spoken dialog and auditory cues such as sound effects and music for the hearing impaired.
34    Caption = 7,
35    /// Textual transcriptions of spoken dialog.
36    Subtitle = 8,
37    /// Textual media component containing transcriptions of spoken dialog and auditory cues such as sound effects and music for the hearing impaired.
38    Alternate = 9,
39    /// Media content component that is supplementary to a media content component of a different media component type.
40    Supplementary = 10,
41    /// Experience that contains a commentary (e.g. director’s commentary) (typically audio)
42    Commentary = 11,
43    /// Experience that contains an element that is presented in a different language from the original (e.g. dubbed audio, translated captions)
44    Dubbedtranslation = 12,
45    /// Textual or audio media component containing a textual description (intended for audio synthesis) or an audio description describing a visual component
46    Description = 13,
47    /// Media component containing information intended to be processed by application specific elements.
48    Metadata = 14,
49    /// Experience containing an element for improved intelligibility of the dialogue.
50    Enhancedaudiointelligibility = 15,
51    /// Experience that provides information, about a current emergency, that is intended to enable the protection of life, health, safety, and property, and may also include critical details regarding the emergency and how to respond to the emergency.
52    Emergency = 16,
53    /// Textual representation of a songs’ lyrics, usually in the same language as the associated song as specified in [SMPTE ST 2067-2].
54    Karaoke = 17,
55}
56
57impl Characteristic {
58    /// Convert from u8 value
59    pub fn from_u8(value: u8) -> Option<Self> {
60        match value {
61            0 => Some(Characteristic::Forcedsubtitles),
62            1 => Some(Characteristic::Describesvideo),
63            2 => Some(Characteristic::Easytoread),
64            3 => Some(Characteristic::Framebased),
65            4 => Some(Characteristic::Mainprogram),
66            5 => Some(Characteristic::Originalcontent),
67            6 => Some(Characteristic::Voiceovertranslation),
68            7 => Some(Characteristic::Caption),
69            8 => Some(Characteristic::Subtitle),
70            9 => Some(Characteristic::Alternate),
71            10 => Some(Characteristic::Supplementary),
72            11 => Some(Characteristic::Commentary),
73            12 => Some(Characteristic::Dubbedtranslation),
74            13 => Some(Characteristic::Description),
75            14 => Some(Characteristic::Metadata),
76            15 => Some(Characteristic::Enhancedaudiointelligibility),
77            16 => Some(Characteristic::Emergency),
78            17 => Some(Characteristic::Karaoke),
79            _ => None,
80        }
81    }
82
83    /// Convert to u8 value
84    pub fn to_u8(self) -> u8 {
85        self as u8
86    }
87}
88
89impl From<Characteristic> for u8 {
90    fn from(val: Characteristic) -> Self {
91        val as u8
92    }
93}
94
95#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
96#[repr(u8)]
97pub enum PlaybackState {
98    /// Media is currently playing (includes FF and REW)
99    Playing = 0,
100    /// Media is currently paused
101    Paused = 1,
102    /// Media is not currently playing
103    Notplaying = 2,
104    /// Media is not currently buffering and playback will start when buffer has been filled
105    Buffering = 3,
106}
107
108impl PlaybackState {
109    /// Convert from u8 value
110    pub fn from_u8(value: u8) -> Option<Self> {
111        match value {
112            0 => Some(PlaybackState::Playing),
113            1 => Some(PlaybackState::Paused),
114            2 => Some(PlaybackState::Notplaying),
115            3 => Some(PlaybackState::Buffering),
116            _ => None,
117        }
118    }
119
120    /// Convert to u8 value
121    pub fn to_u8(self) -> u8 {
122        self as u8
123    }
124}
125
126impl From<PlaybackState> for u8 {
127    fn from(val: PlaybackState) -> Self {
128        val as u8
129    }
130}
131
132#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
133#[repr(u8)]
134pub enum Status {
135    /// Succeeded
136    Success = 0,
137    /// Requested playback command is invalid in the current playback state.
138    Invalidstateforcommand = 1,
139    /// Requested playback command is not allowed in the current playback state. For example, attempting to fast-forward during a commercial might return NotAllowed.
140    Notallowed = 2,
141    /// This endpoint is not active for playback.
142    Notactive = 3,
143    /// The FastForward or Rewind Command was issued but the media is already playing back at the fastest speed supported by the server in the respective direction.
144    Speedoutofrange = 4,
145    /// The Seek Command was issued with a value of position outside of the allowed seek range of the media.
146    Seekoutofrange = 5,
147}
148
149impl Status {
150    /// Convert from u8 value
151    pub fn from_u8(value: u8) -> Option<Self> {
152        match value {
153            0 => Some(Status::Success),
154            1 => Some(Status::Invalidstateforcommand),
155            2 => Some(Status::Notallowed),
156            3 => Some(Status::Notactive),
157            4 => Some(Status::Speedoutofrange),
158            5 => Some(Status::Seekoutofrange),
159            _ => None,
160        }
161    }
162
163    /// Convert to u8 value
164    pub fn to_u8(self) -> u8 {
165        self as u8
166    }
167}
168
169impl From<Status> for u8 {
170    fn from(val: Status) -> Self {
171        val as u8
172    }
173}
174
175// Struct definitions
176
177#[derive(Debug, serde::Serialize)]
178pub struct PlaybackPosition {
179    pub updated_at: Option<u64>,
180    pub position: Option<u64>,
181}
182
183#[derive(Debug, serde::Serialize)]
184pub struct TrackAttributes {
185    pub language_code: Option<String>,
186    pub characteristics: Option<Vec<Characteristic>>,
187    pub display_name: Option<String>,
188}
189
190#[derive(Debug, serde::Serialize)]
191pub struct Track {
192    pub id: Option<String>,
193    pub track_attributes: Option<TrackAttributes>,
194}
195
196// Command encoders
197
198/// Encode Rewind command (0x06)
199pub fn encode_rewind(audio_advance_unmuted: bool) -> anyhow::Result<Vec<u8>> {
200    let tlv = tlv::TlvItemEnc {
201        tag: 0,
202        value: tlv::TlvItemValueEnc::StructInvisible(vec![
203        (0, tlv::TlvItemValueEnc::Bool(audio_advance_unmuted)).into(),
204        ]),
205    };
206    Ok(tlv.encode()?)
207}
208
209/// Encode FastForward command (0x07)
210pub fn encode_fast_forward(audio_advance_unmuted: bool) -> anyhow::Result<Vec<u8>> {
211    let tlv = tlv::TlvItemEnc {
212        tag: 0,
213        value: tlv::TlvItemValueEnc::StructInvisible(vec![
214        (0, tlv::TlvItemValueEnc::Bool(audio_advance_unmuted)).into(),
215        ]),
216    };
217    Ok(tlv.encode()?)
218}
219
220/// Encode SkipForward command (0x08)
221pub fn encode_skip_forward(delta_position_milliseconds: u64) -> anyhow::Result<Vec<u8>> {
222    let tlv = tlv::TlvItemEnc {
223        tag: 0,
224        value: tlv::TlvItemValueEnc::StructInvisible(vec![
225        (0, tlv::TlvItemValueEnc::UInt64(delta_position_milliseconds)).into(),
226        ]),
227    };
228    Ok(tlv.encode()?)
229}
230
231/// Encode SkipBackward command (0x09)
232pub fn encode_skip_backward(delta_position_milliseconds: u64) -> anyhow::Result<Vec<u8>> {
233    let tlv = tlv::TlvItemEnc {
234        tag: 0,
235        value: tlv::TlvItemValueEnc::StructInvisible(vec![
236        (0, tlv::TlvItemValueEnc::UInt64(delta_position_milliseconds)).into(),
237        ]),
238    };
239    Ok(tlv.encode()?)
240}
241
242/// Encode Seek command (0x0B)
243pub fn encode_seek(position: u64) -> anyhow::Result<Vec<u8>> {
244    let tlv = tlv::TlvItemEnc {
245        tag: 0,
246        value: tlv::TlvItemValueEnc::StructInvisible(vec![
247        (0, tlv::TlvItemValueEnc::UInt64(position)).into(),
248        ]),
249    };
250    Ok(tlv.encode()?)
251}
252
253/// Encode ActivateAudioTrack command (0x0C)
254pub fn encode_activate_audio_track(track_id: String, audio_output_index: Option<u8>) -> anyhow::Result<Vec<u8>> {
255    let tlv = tlv::TlvItemEnc {
256        tag: 0,
257        value: tlv::TlvItemValueEnc::StructInvisible(vec![
258        (0, tlv::TlvItemValueEnc::String(track_id)).into(),
259        (1, tlv::TlvItemValueEnc::UInt8(audio_output_index.unwrap_or(0))).into(),
260        ]),
261    };
262    Ok(tlv.encode()?)
263}
264
265/// Encode ActivateTextTrack command (0x0D)
266pub fn encode_activate_text_track(track_id: String) -> anyhow::Result<Vec<u8>> {
267    let tlv = tlv::TlvItemEnc {
268        tag: 0,
269        value: tlv::TlvItemValueEnc::StructInvisible(vec![
270        (0, tlv::TlvItemValueEnc::String(track_id)).into(),
271        ]),
272    };
273    Ok(tlv.encode()?)
274}
275
276// Attribute decoders
277
278/// Decode CurrentState attribute (0x0000)
279pub fn decode_current_state(inp: &tlv::TlvItemValue) -> anyhow::Result<PlaybackState> {
280    if let tlv::TlvItemValue::Int(v) = inp {
281        PlaybackState::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
282    } else {
283        Err(anyhow::anyhow!("Expected Integer"))
284    }
285}
286
287/// Decode StartTime attribute (0x0001)
288pub fn decode_start_time(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u64>> {
289    if let tlv::TlvItemValue::Int(v) = inp {
290        Ok(Some(*v))
291    } else {
292        Ok(None)
293    }
294}
295
296/// Decode Duration attribute (0x0002)
297pub fn decode_duration(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u64>> {
298    if let tlv::TlvItemValue::Int(v) = inp {
299        Ok(Some(*v))
300    } else {
301        Ok(None)
302    }
303}
304
305/// Decode SampledPosition attribute (0x0003)
306pub fn decode_sampled_position(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<PlaybackPosition>> {
307    if let tlv::TlvItemValue::List(_fields) = inp {
308        // Struct with fields
309        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
310        Ok(Some(PlaybackPosition {
311                updated_at: item.get_int(&[0]),
312                position: item.get_int(&[1]),
313        }))
314    //} else if let tlv::TlvItemValue::Null = inp {
315    //    // Null value for nullable struct
316    //    Ok(None)
317    } else {
318    Ok(None)
319    //    Err(anyhow::anyhow!("Expected struct fields or null"))
320    }
321}
322
323/// Decode PlaybackSpeed attribute (0x0004)
324pub fn decode_playback_speed(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
325    if let tlv::TlvItemValue::Int(v) = inp {
326        Ok(*v as u8)
327    } else {
328        Err(anyhow::anyhow!("Expected UInt8"))
329    }
330}
331
332/// Decode SeekRangeEnd attribute (0x0005)
333pub fn decode_seek_range_end(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u64>> {
334    if let tlv::TlvItemValue::Int(v) = inp {
335        Ok(Some(*v))
336    } else {
337        Ok(None)
338    }
339}
340
341/// Decode SeekRangeStart attribute (0x0006)
342pub fn decode_seek_range_start(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u64>> {
343    if let tlv::TlvItemValue::Int(v) = inp {
344        Ok(Some(*v))
345    } else {
346        Ok(None)
347    }
348}
349
350/// Decode ActiveAudioTrack attribute (0x0007)
351pub fn decode_active_audio_track(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<Track>> {
352    if let tlv::TlvItemValue::List(_fields) = inp {
353        // Struct with fields
354        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
355        Ok(Some(Track {
356                id: item.get_string_owned(&[0]),
357                track_attributes: {
358                    if let Some(nested_tlv) = item.get(&[1]) {
359                        if let tlv::TlvItemValue::List(_) = nested_tlv {
360                            let nested_item = tlv::TlvItem { tag: 1, value: nested_tlv.clone() };
361                            Some(TrackAttributes {
362                language_code: nested_item.get_string_owned(&[0]),
363                characteristics: {
364                    if let Some(tlv::TlvItemValue::List(l)) = nested_item.get(&[1]) {
365                        let items: Vec<Characteristic> = l.iter().filter_map(|e| { if let tlv::TlvItemValue::Int(v) = &e.value { Characteristic::from_u8(*v as u8) } else { None } }).collect();
366                        Some(items)
367                    } else {
368                        None
369                    }
370                },
371                display_name: nested_item.get_string_owned(&[2]),
372                            })
373                        } else {
374                            None
375                        }
376                    } else {
377                        None
378                    }
379                },
380        }))
381    //} else if let tlv::TlvItemValue::Null = inp {
382    //    // Null value for nullable struct
383    //    Ok(None)
384    } else {
385    Ok(None)
386    //    Err(anyhow::anyhow!("Expected struct fields or null"))
387    }
388}
389
390/// Decode AvailableAudioTracks attribute (0x0008)
391pub fn decode_available_audio_tracks(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<Track>> {
392    let mut res = Vec::new();
393    if let tlv::TlvItemValue::List(v) = inp {
394        for item in v {
395            res.push(Track {
396                id: item.get_string_owned(&[0]),
397                track_attributes: {
398                    if let Some(nested_tlv) = item.get(&[1]) {
399                        if let tlv::TlvItemValue::List(_) = nested_tlv {
400                            let nested_item = tlv::TlvItem { tag: 1, value: nested_tlv.clone() };
401                            Some(TrackAttributes {
402                language_code: nested_item.get_string_owned(&[0]),
403                characteristics: {
404                    if let Some(tlv::TlvItemValue::List(l)) = nested_item.get(&[1]) {
405                        let items: Vec<Characteristic> = l.iter().filter_map(|e| { if let tlv::TlvItemValue::Int(v) = &e.value { Characteristic::from_u8(*v as u8) } else { None } }).collect();
406                        Some(items)
407                    } else {
408                        None
409                    }
410                },
411                display_name: nested_item.get_string_owned(&[2]),
412                            })
413                        } else {
414                            None
415                        }
416                    } else {
417                        None
418                    }
419                },
420            });
421        }
422    }
423    Ok(res)
424}
425
426/// Decode ActiveTextTrack attribute (0x0009)
427pub fn decode_active_text_track(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<Track>> {
428    if let tlv::TlvItemValue::List(_fields) = inp {
429        // Struct with fields
430        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
431        Ok(Some(Track {
432                id: item.get_string_owned(&[0]),
433                track_attributes: {
434                    if let Some(nested_tlv) = item.get(&[1]) {
435                        if let tlv::TlvItemValue::List(_) = nested_tlv {
436                            let nested_item = tlv::TlvItem { tag: 1, value: nested_tlv.clone() };
437                            Some(TrackAttributes {
438                language_code: nested_item.get_string_owned(&[0]),
439                characteristics: {
440                    if let Some(tlv::TlvItemValue::List(l)) = nested_item.get(&[1]) {
441                        let items: Vec<Characteristic> = l.iter().filter_map(|e| { if let tlv::TlvItemValue::Int(v) = &e.value { Characteristic::from_u8(*v as u8) } else { None } }).collect();
442                        Some(items)
443                    } else {
444                        None
445                    }
446                },
447                display_name: nested_item.get_string_owned(&[2]),
448                            })
449                        } else {
450                            None
451                        }
452                    } else {
453                        None
454                    }
455                },
456        }))
457    //} else if let tlv::TlvItemValue::Null = inp {
458    //    // Null value for nullable struct
459    //    Ok(None)
460    } else {
461    Ok(None)
462    //    Err(anyhow::anyhow!("Expected struct fields or null"))
463    }
464}
465
466/// Decode AvailableTextTracks attribute (0x000A)
467pub fn decode_available_text_tracks(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<Track>> {
468    let mut res = Vec::new();
469    if let tlv::TlvItemValue::List(v) = inp {
470        for item in v {
471            res.push(Track {
472                id: item.get_string_owned(&[0]),
473                track_attributes: {
474                    if let Some(nested_tlv) = item.get(&[1]) {
475                        if let tlv::TlvItemValue::List(_) = nested_tlv {
476                            let nested_item = tlv::TlvItem { tag: 1, value: nested_tlv.clone() };
477                            Some(TrackAttributes {
478                language_code: nested_item.get_string_owned(&[0]),
479                characteristics: {
480                    if let Some(tlv::TlvItemValue::List(l)) = nested_item.get(&[1]) {
481                        let items: Vec<Characteristic> = l.iter().filter_map(|e| { if let tlv::TlvItemValue::Int(v) = &e.value { Characteristic::from_u8(*v as u8) } else { None } }).collect();
482                        Some(items)
483                    } else {
484                        None
485                    }
486                },
487                display_name: nested_item.get_string_owned(&[2]),
488                            })
489                        } else {
490                            None
491                        }
492                    } else {
493                        None
494                    }
495                },
496            });
497        }
498    }
499    Ok(res)
500}
501
502
503// JSON dispatcher function
504
505/// Decode attribute value and return as JSON string
506///
507/// # Parameters
508/// * `cluster_id` - The cluster identifier
509/// * `attribute_id` - The attribute identifier
510/// * `tlv_value` - The TLV value to decode
511///
512/// # Returns
513/// JSON string representation of the decoded value or error
514pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
515    // Verify this is the correct cluster
516    if cluster_id != 0x0506 {
517        return format!("{{\"error\": \"Invalid cluster ID. Expected 0x0506, got {}\"}}", cluster_id);
518    }
519
520    match attribute_id {
521        0x0000 => {
522            match decode_current_state(tlv_value) {
523                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
524                Err(e) => format!("{{\"error\": \"{}\"}}", e),
525            }
526        }
527        0x0001 => {
528            match decode_start_time(tlv_value) {
529                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
530                Err(e) => format!("{{\"error\": \"{}\"}}", e),
531            }
532        }
533        0x0002 => {
534            match decode_duration(tlv_value) {
535                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
536                Err(e) => format!("{{\"error\": \"{}\"}}", e),
537            }
538        }
539        0x0003 => {
540            match decode_sampled_position(tlv_value) {
541                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
542                Err(e) => format!("{{\"error\": \"{}\"}}", e),
543            }
544        }
545        0x0004 => {
546            match decode_playback_speed(tlv_value) {
547                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
548                Err(e) => format!("{{\"error\": \"{}\"}}", e),
549            }
550        }
551        0x0005 => {
552            match decode_seek_range_end(tlv_value) {
553                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
554                Err(e) => format!("{{\"error\": \"{}\"}}", e),
555            }
556        }
557        0x0006 => {
558            match decode_seek_range_start(tlv_value) {
559                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
560                Err(e) => format!("{{\"error\": \"{}\"}}", e),
561            }
562        }
563        0x0007 => {
564            match decode_active_audio_track(tlv_value) {
565                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
566                Err(e) => format!("{{\"error\": \"{}\"}}", e),
567            }
568        }
569        0x0008 => {
570            match decode_available_audio_tracks(tlv_value) {
571                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
572                Err(e) => format!("{{\"error\": \"{}\"}}", e),
573            }
574        }
575        0x0009 => {
576            match decode_active_text_track(tlv_value) {
577                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
578                Err(e) => format!("{{\"error\": \"{}\"}}", e),
579            }
580        }
581        0x000A => {
582            match decode_available_text_tracks(tlv_value) {
583                Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
584                Err(e) => format!("{{\"error\": \"{}\"}}", e),
585            }
586        }
587        _ => format!("{{\"error\": \"Unknown attribute ID: {}\"}}", attribute_id),
588    }
589}
590
591/// Get list of all attributes supported by this cluster
592///
593/// # Returns
594/// Vector of tuples containing (attribute_id, attribute_name)
595pub fn get_attribute_list() -> Vec<(u32, &'static str)> {
596    vec![
597        (0x0000, "CurrentState"),
598        (0x0001, "StartTime"),
599        (0x0002, "Duration"),
600        (0x0003, "SampledPosition"),
601        (0x0004, "PlaybackSpeed"),
602        (0x0005, "SeekRangeEnd"),
603        (0x0006, "SeekRangeStart"),
604        (0x0007, "ActiveAudioTrack"),
605        (0x0008, "AvailableAudioTracks"),
606        (0x0009, "ActiveTextTrack"),
607        (0x000A, "AvailableTextTracks"),
608    ]
609}
610
611#[derive(Debug, serde::Serialize)]
612pub struct PlaybackResponse {
613    pub status: Option<Status>,
614    pub data: Option<String>,
615}
616
617// Command response decoders
618
619/// Decode PlaybackResponse command response (0A)
620pub fn decode_playback_response(inp: &tlv::TlvItemValue) -> anyhow::Result<PlaybackResponse> {
621    if let tlv::TlvItemValue::List(_fields) = inp {
622        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
623        Ok(PlaybackResponse {
624                status: item.get_int(&[0]).and_then(|v| Status::from_u8(v as u8)),
625                data: item.get_string_owned(&[1]),
626        })
627    } else {
628        Err(anyhow::anyhow!("Expected struct fields"))
629    }
630}
631
632#[derive(Debug, serde::Serialize)]
633pub struct StateChangedEvent {
634    pub current_state: Option<PlaybackState>,
635    pub start_time: Option<u64>,
636    pub duration: Option<u64>,
637    pub sampled_position: Option<PlaybackPosition>,
638    pub playback_speed: Option<u8>,
639    pub seek_range_end: Option<u64>,
640    pub seek_range_start: Option<u64>,
641    #[serde(serialize_with = "serialize_opt_bytes_as_hex")]
642    pub data: Option<Vec<u8>>,
643    pub audio_advance_unmuted: Option<bool>,
644}
645
646// Event decoders
647
648/// Decode StateChanged event (0x00, priority: info)
649pub fn decode_state_changed_event(inp: &tlv::TlvItemValue) -> anyhow::Result<StateChangedEvent> {
650    if let tlv::TlvItemValue::List(_fields) = inp {
651        let item = tlv::TlvItem { tag: 0, value: inp.clone() };
652        Ok(StateChangedEvent {
653                                current_state: item.get_int(&[0]).and_then(|v| PlaybackState::from_u8(v as u8)),
654                                start_time: item.get_int(&[1]),
655                                duration: item.get_int(&[2]),
656                                sampled_position: {
657                    if let Some(nested_tlv) = item.get(&[3]) {
658                        if let tlv::TlvItemValue::List(_) = nested_tlv {
659                            let nested_item = tlv::TlvItem { tag: 3, value: nested_tlv.clone() };
660                            Some(PlaybackPosition {
661                updated_at: nested_item.get_int(&[0]),
662                position: nested_item.get_int(&[1]),
663                            })
664                        } else {
665                            None
666                        }
667                    } else {
668                        None
669                    }
670                },
671                                playback_speed: item.get_int(&[4]).map(|v| v as u8),
672                                seek_range_end: item.get_int(&[5]),
673                                seek_range_start: item.get_int(&[6]),
674                                data: item.get_octet_string_owned(&[7]),
675                                audio_advance_unmuted: item.get_bool(&[8]),
676        })
677    } else {
678        Err(anyhow::anyhow!("Expected struct fields"))
679    }
680}
681