1#![allow(clippy::too_many_arguments)]
7
8use crate::tlv;
9use anyhow;
10use serde_json;
11
12
13use crate::clusters::helpers::{serialize_opt_bytes_as_hex};
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
19#[repr(u8)]
20pub enum Characteristic {
21 Forcedsubtitles = 0,
23 Describesvideo = 1,
25 Easytoread = 2,
27 Framebased = 3,
29 Mainprogram = 4,
31 Originalcontent = 5,
33 Voiceovertranslation = 6,
35 Caption = 7,
37 Subtitle = 8,
39 Alternate = 9,
41 Supplementary = 10,
43 Commentary = 11,
45 Dubbedtranslation = 12,
47 Description = 13,
49 Metadata = 14,
51 Enhancedaudiointelligibility = 15,
53 Emergency = 16,
55 Karaoke = 17,
57}
58
59impl Characteristic {
60 pub fn from_u8(value: u8) -> Option<Self> {
62 match value {
63 0 => Some(Characteristic::Forcedsubtitles),
64 1 => Some(Characteristic::Describesvideo),
65 2 => Some(Characteristic::Easytoread),
66 3 => Some(Characteristic::Framebased),
67 4 => Some(Characteristic::Mainprogram),
68 5 => Some(Characteristic::Originalcontent),
69 6 => Some(Characteristic::Voiceovertranslation),
70 7 => Some(Characteristic::Caption),
71 8 => Some(Characteristic::Subtitle),
72 9 => Some(Characteristic::Alternate),
73 10 => Some(Characteristic::Supplementary),
74 11 => Some(Characteristic::Commentary),
75 12 => Some(Characteristic::Dubbedtranslation),
76 13 => Some(Characteristic::Description),
77 14 => Some(Characteristic::Metadata),
78 15 => Some(Characteristic::Enhancedaudiointelligibility),
79 16 => Some(Characteristic::Emergency),
80 17 => Some(Characteristic::Karaoke),
81 _ => None,
82 }
83 }
84
85 pub fn to_u8(self) -> u8 {
87 self as u8
88 }
89}
90
91impl From<Characteristic> for u8 {
92 fn from(val: Characteristic) -> Self {
93 val as u8
94 }
95}
96
97#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
98#[repr(u8)]
99pub enum PlaybackState {
100 Playing = 0,
102 Paused = 1,
104 Notplaying = 2,
106 Buffering = 3,
108}
109
110impl PlaybackState {
111 pub fn from_u8(value: u8) -> Option<Self> {
113 match value {
114 0 => Some(PlaybackState::Playing),
115 1 => Some(PlaybackState::Paused),
116 2 => Some(PlaybackState::Notplaying),
117 3 => Some(PlaybackState::Buffering),
118 _ => None,
119 }
120 }
121
122 pub fn to_u8(self) -> u8 {
124 self as u8
125 }
126}
127
128impl From<PlaybackState> for u8 {
129 fn from(val: PlaybackState) -> Self {
130 val as u8
131 }
132}
133
134#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
135#[repr(u8)]
136pub enum Status {
137 Success = 0,
139 Invalidstateforcommand = 1,
141 Notallowed = 2,
143 Notactive = 3,
145 Speedoutofrange = 4,
147 Seekoutofrange = 5,
149}
150
151impl Status {
152 pub fn from_u8(value: u8) -> Option<Self> {
154 match value {
155 0 => Some(Status::Success),
156 1 => Some(Status::Invalidstateforcommand),
157 2 => Some(Status::Notallowed),
158 3 => Some(Status::Notactive),
159 4 => Some(Status::Speedoutofrange),
160 5 => Some(Status::Seekoutofrange),
161 _ => None,
162 }
163 }
164
165 pub fn to_u8(self) -> u8 {
167 self as u8
168 }
169}
170
171impl From<Status> for u8 {
172 fn from(val: Status) -> Self {
173 val as u8
174 }
175}
176
177#[derive(Debug, serde::Serialize)]
180pub struct PlaybackPosition {
181 pub updated_at: Option<u64>,
182 pub position: Option<u64>,
183}
184
185#[derive(Debug, serde::Serialize)]
186pub struct TrackAttributes {
187 pub language_code: Option<String>,
188 pub characteristics: Option<Vec<Characteristic>>,
189 pub display_name: Option<String>,
190}
191
192#[derive(Debug, serde::Serialize)]
193pub struct Track {
194 pub id: Option<String>,
195 pub track_attributes: Option<TrackAttributes>,
196}
197
198pub fn encode_rewind(audio_advance_unmuted: bool) -> anyhow::Result<Vec<u8>> {
202 let tlv = tlv::TlvItemEnc {
203 tag: 0,
204 value: tlv::TlvItemValueEnc::StructInvisible(vec![
205 (0, tlv::TlvItemValueEnc::Bool(audio_advance_unmuted)).into(),
206 ]),
207 };
208 Ok(tlv.encode()?)
209}
210
211pub fn encode_fast_forward(audio_advance_unmuted: bool) -> anyhow::Result<Vec<u8>> {
213 let tlv = tlv::TlvItemEnc {
214 tag: 0,
215 value: tlv::TlvItemValueEnc::StructInvisible(vec![
216 (0, tlv::TlvItemValueEnc::Bool(audio_advance_unmuted)).into(),
217 ]),
218 };
219 Ok(tlv.encode()?)
220}
221
222pub fn encode_skip_forward(delta_position_milliseconds: u64) -> anyhow::Result<Vec<u8>> {
224 let tlv = tlv::TlvItemEnc {
225 tag: 0,
226 value: tlv::TlvItemValueEnc::StructInvisible(vec![
227 (0, tlv::TlvItemValueEnc::UInt64(delta_position_milliseconds)).into(),
228 ]),
229 };
230 Ok(tlv.encode()?)
231}
232
233pub fn encode_skip_backward(delta_position_milliseconds: u64) -> anyhow::Result<Vec<u8>> {
235 let tlv = tlv::TlvItemEnc {
236 tag: 0,
237 value: tlv::TlvItemValueEnc::StructInvisible(vec![
238 (0, tlv::TlvItemValueEnc::UInt64(delta_position_milliseconds)).into(),
239 ]),
240 };
241 Ok(tlv.encode()?)
242}
243
244pub fn encode_seek(position: u64) -> anyhow::Result<Vec<u8>> {
246 let tlv = tlv::TlvItemEnc {
247 tag: 0,
248 value: tlv::TlvItemValueEnc::StructInvisible(vec![
249 (0, tlv::TlvItemValueEnc::UInt64(position)).into(),
250 ]),
251 };
252 Ok(tlv.encode()?)
253}
254
255pub fn encode_activate_audio_track(track_id: String, audio_output_index: Option<u8>) -> anyhow::Result<Vec<u8>> {
257 let tlv = tlv::TlvItemEnc {
258 tag: 0,
259 value: tlv::TlvItemValueEnc::StructInvisible(vec![
260 (0, tlv::TlvItemValueEnc::String(track_id)).into(),
261 (1, tlv::TlvItemValueEnc::UInt8(audio_output_index.unwrap_or(0))).into(),
262 ]),
263 };
264 Ok(tlv.encode()?)
265}
266
267pub fn encode_activate_text_track(track_id: String) -> anyhow::Result<Vec<u8>> {
269 let tlv = tlv::TlvItemEnc {
270 tag: 0,
271 value: tlv::TlvItemValueEnc::StructInvisible(vec![
272 (0, tlv::TlvItemValueEnc::String(track_id)).into(),
273 ]),
274 };
275 Ok(tlv.encode()?)
276}
277
278pub fn decode_current_state(inp: &tlv::TlvItemValue) -> anyhow::Result<PlaybackState> {
282 if let tlv::TlvItemValue::Int(v) = inp {
283 PlaybackState::from_u8(*v as u8).ok_or_else(|| anyhow::anyhow!("Invalid enum value"))
284 } else {
285 Err(anyhow::anyhow!("Expected Integer"))
286 }
287}
288
289pub fn decode_start_time(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u64>> {
291 if let tlv::TlvItemValue::Int(v) = inp {
292 Ok(Some(*v))
293 } else {
294 Ok(None)
295 }
296}
297
298pub fn decode_duration(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u64>> {
300 if let tlv::TlvItemValue::Int(v) = inp {
301 Ok(Some(*v))
302 } else {
303 Ok(None)
304 }
305}
306
307pub fn decode_sampled_position(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<PlaybackPosition>> {
309 if let tlv::TlvItemValue::List(_fields) = inp {
310 let item = tlv::TlvItem { tag: 0, value: inp.clone() };
312 Ok(Some(PlaybackPosition {
313 updated_at: item.get_int(&[0]),
314 position: item.get_int(&[1]),
315 }))
316 } else {
320 Ok(None)
321 }
323}
324
325pub fn decode_playback_speed(inp: &tlv::TlvItemValue) -> anyhow::Result<u8> {
327 if let tlv::TlvItemValue::Int(v) = inp {
328 Ok(*v as u8)
329 } else {
330 Err(anyhow::anyhow!("Expected UInt8"))
331 }
332}
333
334pub fn decode_seek_range_end(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u64>> {
336 if let tlv::TlvItemValue::Int(v) = inp {
337 Ok(Some(*v))
338 } else {
339 Ok(None)
340 }
341}
342
343pub fn decode_seek_range_start(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<u64>> {
345 if let tlv::TlvItemValue::Int(v) = inp {
346 Ok(Some(*v))
347 } else {
348 Ok(None)
349 }
350}
351
352pub fn decode_active_audio_track(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<Track>> {
354 if let tlv::TlvItemValue::List(_fields) = inp {
355 let item = tlv::TlvItem { tag: 0, value: inp.clone() };
357 Ok(Some(Track {
358 id: item.get_string_owned(&[0]),
359 track_attributes: {
360 if let Some(nested_tlv) = item.get(&[1]) {
361 if let tlv::TlvItemValue::List(_) = nested_tlv {
362 let nested_item = tlv::TlvItem { tag: 1, value: nested_tlv.clone() };
363 Some(TrackAttributes {
364 language_code: nested_item.get_string_owned(&[0]),
365 characteristics: {
366 if let Some(tlv::TlvItemValue::List(l)) = nested_item.get(&[1]) {
367 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();
368 Some(items)
369 } else {
370 None
371 }
372 },
373 display_name: nested_item.get_string_owned(&[2]),
374 })
375 } else {
376 None
377 }
378 } else {
379 None
380 }
381 },
382 }))
383 } else {
387 Ok(None)
388 }
390}
391
392pub fn decode_available_audio_tracks(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<Track>> {
394 let mut res = Vec::new();
395 if let tlv::TlvItemValue::List(v) = inp {
396 for item in v {
397 res.push(Track {
398 id: item.get_string_owned(&[0]),
399 track_attributes: {
400 if let Some(nested_tlv) = item.get(&[1]) {
401 if let tlv::TlvItemValue::List(_) = nested_tlv {
402 let nested_item = tlv::TlvItem { tag: 1, value: nested_tlv.clone() };
403 Some(TrackAttributes {
404 language_code: nested_item.get_string_owned(&[0]),
405 characteristics: {
406 if let Some(tlv::TlvItemValue::List(l)) = nested_item.get(&[1]) {
407 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();
408 Some(items)
409 } else {
410 None
411 }
412 },
413 display_name: nested_item.get_string_owned(&[2]),
414 })
415 } else {
416 None
417 }
418 } else {
419 None
420 }
421 },
422 });
423 }
424 }
425 Ok(res)
426}
427
428pub fn decode_active_text_track(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<Track>> {
430 if let tlv::TlvItemValue::List(_fields) = inp {
431 let item = tlv::TlvItem { tag: 0, value: inp.clone() };
433 Ok(Some(Track {
434 id: item.get_string_owned(&[0]),
435 track_attributes: {
436 if let Some(nested_tlv) = item.get(&[1]) {
437 if let tlv::TlvItemValue::List(_) = nested_tlv {
438 let nested_item = tlv::TlvItem { tag: 1, value: nested_tlv.clone() };
439 Some(TrackAttributes {
440 language_code: nested_item.get_string_owned(&[0]),
441 characteristics: {
442 if let Some(tlv::TlvItemValue::List(l)) = nested_item.get(&[1]) {
443 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();
444 Some(items)
445 } else {
446 None
447 }
448 },
449 display_name: nested_item.get_string_owned(&[2]),
450 })
451 } else {
452 None
453 }
454 } else {
455 None
456 }
457 },
458 }))
459 } else {
463 Ok(None)
464 }
466}
467
468pub fn decode_available_text_tracks(inp: &tlv::TlvItemValue) -> anyhow::Result<Vec<Track>> {
470 let mut res = Vec::new();
471 if let tlv::TlvItemValue::List(v) = inp {
472 for item in v {
473 res.push(Track {
474 id: item.get_string_owned(&[0]),
475 track_attributes: {
476 if let Some(nested_tlv) = item.get(&[1]) {
477 if let tlv::TlvItemValue::List(_) = nested_tlv {
478 let nested_item = tlv::TlvItem { tag: 1, value: nested_tlv.clone() };
479 Some(TrackAttributes {
480 language_code: nested_item.get_string_owned(&[0]),
481 characteristics: {
482 if let Some(tlv::TlvItemValue::List(l)) = nested_item.get(&[1]) {
483 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();
484 Some(items)
485 } else {
486 None
487 }
488 },
489 display_name: nested_item.get_string_owned(&[2]),
490 })
491 } else {
492 None
493 }
494 } else {
495 None
496 }
497 },
498 });
499 }
500 }
501 Ok(res)
502}
503
504
505pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
517 if cluster_id != 0x0506 {
519 return format!("{{\"error\": \"Invalid cluster ID. Expected 0x0506, got {}\"}}", cluster_id);
520 }
521
522 match attribute_id {
523 0x0000 => {
524 match decode_current_state(tlv_value) {
525 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
526 Err(e) => format!("{{\"error\": \"{}\"}}", e),
527 }
528 }
529 0x0001 => {
530 match decode_start_time(tlv_value) {
531 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
532 Err(e) => format!("{{\"error\": \"{}\"}}", e),
533 }
534 }
535 0x0002 => {
536 match decode_duration(tlv_value) {
537 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
538 Err(e) => format!("{{\"error\": \"{}\"}}", e),
539 }
540 }
541 0x0003 => {
542 match decode_sampled_position(tlv_value) {
543 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
544 Err(e) => format!("{{\"error\": \"{}\"}}", e),
545 }
546 }
547 0x0004 => {
548 match decode_playback_speed(tlv_value) {
549 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
550 Err(e) => format!("{{\"error\": \"{}\"}}", e),
551 }
552 }
553 0x0005 => {
554 match decode_seek_range_end(tlv_value) {
555 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
556 Err(e) => format!("{{\"error\": \"{}\"}}", e),
557 }
558 }
559 0x0006 => {
560 match decode_seek_range_start(tlv_value) {
561 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
562 Err(e) => format!("{{\"error\": \"{}\"}}", e),
563 }
564 }
565 0x0007 => {
566 match decode_active_audio_track(tlv_value) {
567 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
568 Err(e) => format!("{{\"error\": \"{}\"}}", e),
569 }
570 }
571 0x0008 => {
572 match decode_available_audio_tracks(tlv_value) {
573 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
574 Err(e) => format!("{{\"error\": \"{}\"}}", e),
575 }
576 }
577 0x0009 => {
578 match decode_active_text_track(tlv_value) {
579 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
580 Err(e) => format!("{{\"error\": \"{}\"}}", e),
581 }
582 }
583 0x000A => {
584 match decode_available_text_tracks(tlv_value) {
585 Ok(value) => serde_json::to_string(&value).unwrap_or_else(|_| "null".to_string()),
586 Err(e) => format!("{{\"error\": \"{}\"}}", e),
587 }
588 }
589 _ => format!("{{\"error\": \"Unknown attribute ID: {}\"}}", attribute_id),
590 }
591}
592
593pub fn get_attribute_list() -> Vec<(u32, &'static str)> {
598 vec![
599 (0x0000, "CurrentState"),
600 (0x0001, "StartTime"),
601 (0x0002, "Duration"),
602 (0x0003, "SampledPosition"),
603 (0x0004, "PlaybackSpeed"),
604 (0x0005, "SeekRangeEnd"),
605 (0x0006, "SeekRangeStart"),
606 (0x0007, "ActiveAudioTrack"),
607 (0x0008, "AvailableAudioTracks"),
608 (0x0009, "ActiveTextTrack"),
609 (0x000A, "AvailableTextTracks"),
610 ]
611}
612
613pub fn get_command_list() -> Vec<(u32, &'static str)> {
616 vec![
617 (0x00, "Play"),
618 (0x01, "Pause"),
619 (0x02, "Stop"),
620 (0x03, "StartOver"),
621 (0x04, "Previous"),
622 (0x05, "Next"),
623 (0x06, "Rewind"),
624 (0x07, "FastForward"),
625 (0x08, "SkipForward"),
626 (0x09, "SkipBackward"),
627 (0x0B, "Seek"),
628 (0x0C, "ActivateAudioTrack"),
629 (0x0D, "ActivateTextTrack"),
630 (0x0E, "DeactivateTextTrack"),
631 ]
632}
633
634pub fn get_command_name(cmd_id: u32) -> Option<&'static str> {
635 match cmd_id {
636 0x00 => Some("Play"),
637 0x01 => Some("Pause"),
638 0x02 => Some("Stop"),
639 0x03 => Some("StartOver"),
640 0x04 => Some("Previous"),
641 0x05 => Some("Next"),
642 0x06 => Some("Rewind"),
643 0x07 => Some("FastForward"),
644 0x08 => Some("SkipForward"),
645 0x09 => Some("SkipBackward"),
646 0x0B => Some("Seek"),
647 0x0C => Some("ActivateAudioTrack"),
648 0x0D => Some("ActivateTextTrack"),
649 0x0E => Some("DeactivateTextTrack"),
650 _ => None,
651 }
652}
653
654pub fn get_command_schema(cmd_id: u32) -> Option<Vec<crate::clusters::codec::CommandField>> {
655 match cmd_id {
656 0x00 => Some(vec![]),
657 0x01 => Some(vec![]),
658 0x02 => Some(vec![]),
659 0x03 => Some(vec![]),
660 0x04 => Some(vec![]),
661 0x05 => Some(vec![]),
662 0x06 => Some(vec![
663 crate::clusters::codec::CommandField { tag: 0, name: "audio_advance_unmuted", kind: crate::clusters::codec::FieldKind::Bool, optional: false, nullable: false },
664 ]),
665 0x07 => Some(vec![
666 crate::clusters::codec::CommandField { tag: 0, name: "audio_advance_unmuted", kind: crate::clusters::codec::FieldKind::Bool, optional: false, nullable: false },
667 ]),
668 0x08 => Some(vec![
669 crate::clusters::codec::CommandField { tag: 0, name: "delta_position_milliseconds", kind: crate::clusters::codec::FieldKind::U64, optional: false, nullable: false },
670 ]),
671 0x09 => Some(vec![
672 crate::clusters::codec::CommandField { tag: 0, name: "delta_position_milliseconds", kind: crate::clusters::codec::FieldKind::U64, optional: false, nullable: false },
673 ]),
674 0x0B => Some(vec![
675 crate::clusters::codec::CommandField { tag: 0, name: "position", kind: crate::clusters::codec::FieldKind::U64, optional: false, nullable: false },
676 ]),
677 0x0C => Some(vec![
678 crate::clusters::codec::CommandField { tag: 0, name: "track_id", kind: crate::clusters::codec::FieldKind::String, optional: false, nullable: false },
679 crate::clusters::codec::CommandField { tag: 1, name: "audio_output_index", kind: crate::clusters::codec::FieldKind::U8, optional: false, nullable: true },
680 ]),
681 0x0D => Some(vec![
682 crate::clusters::codec::CommandField { tag: 0, name: "track_id", kind: crate::clusters::codec::FieldKind::String, optional: false, nullable: false },
683 ]),
684 0x0E => Some(vec![]),
685 _ => None,
686 }
687}
688
689pub fn encode_command_json(cmd_id: u32, args: &serde_json::Value) -> anyhow::Result<Vec<u8>> {
690 match cmd_id {
691 0x00 => Ok(vec![]),
692 0x01 => Ok(vec![]),
693 0x02 => Ok(vec![]),
694 0x03 => Ok(vec![]),
695 0x04 => Ok(vec![]),
696 0x05 => Ok(vec![]),
697 0x06 => {
698 let audio_advance_unmuted = crate::clusters::codec::json_util::get_bool(args, "audio_advance_unmuted")?;
699 encode_rewind(audio_advance_unmuted)
700 }
701 0x07 => {
702 let audio_advance_unmuted = crate::clusters::codec::json_util::get_bool(args, "audio_advance_unmuted")?;
703 encode_fast_forward(audio_advance_unmuted)
704 }
705 0x08 => {
706 let delta_position_milliseconds = crate::clusters::codec::json_util::get_u64(args, "delta_position_milliseconds")?;
707 encode_skip_forward(delta_position_milliseconds)
708 }
709 0x09 => {
710 let delta_position_milliseconds = crate::clusters::codec::json_util::get_u64(args, "delta_position_milliseconds")?;
711 encode_skip_backward(delta_position_milliseconds)
712 }
713 0x0B => {
714 let position = crate::clusters::codec::json_util::get_u64(args, "position")?;
715 encode_seek(position)
716 }
717 0x0C => {
718 let track_id = crate::clusters::codec::json_util::get_string(args, "track_id")?;
719 let audio_output_index = crate::clusters::codec::json_util::get_opt_u8(args, "audio_output_index")?;
720 encode_activate_audio_track(track_id, audio_output_index)
721 }
722 0x0D => {
723 let track_id = crate::clusters::codec::json_util::get_string(args, "track_id")?;
724 encode_activate_text_track(track_id)
725 }
726 0x0E => Ok(vec![]),
727 _ => Err(anyhow::anyhow!("unknown command ID: 0x{:02X}", cmd_id)),
728 }
729}
730
731#[derive(Debug, serde::Serialize)]
732pub struct PlaybackResponse {
733 pub status: Option<Status>,
734 pub data: Option<String>,
735}
736
737pub fn decode_playback_response(inp: &tlv::TlvItemValue) -> anyhow::Result<PlaybackResponse> {
741 if let tlv::TlvItemValue::List(_fields) = inp {
742 let item = tlv::TlvItem { tag: 0, value: inp.clone() };
743 Ok(PlaybackResponse {
744 status: item.get_int(&[0]).and_then(|v| Status::from_u8(v as u8)),
745 data: item.get_string_owned(&[1]),
746 })
747 } else {
748 Err(anyhow::anyhow!("Expected struct fields"))
749 }
750}
751
752pub async fn play(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<PlaybackResponse> {
756 let tlv = conn.invoke_request2(endpoint, crate::clusters::defs::CLUSTER_ID_MEDIA_PLAYBACK, crate::clusters::defs::CLUSTER_MEDIA_PLAYBACK_CMD_ID_PLAY, &[]).await?;
757 decode_playback_response(&tlv)
758}
759
760pub async fn pause(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<PlaybackResponse> {
762 let tlv = conn.invoke_request2(endpoint, crate::clusters::defs::CLUSTER_ID_MEDIA_PLAYBACK, crate::clusters::defs::CLUSTER_MEDIA_PLAYBACK_CMD_ID_PAUSE, &[]).await?;
763 decode_playback_response(&tlv)
764}
765
766pub async fn stop(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<PlaybackResponse> {
768 let tlv = conn.invoke_request2(endpoint, crate::clusters::defs::CLUSTER_ID_MEDIA_PLAYBACK, crate::clusters::defs::CLUSTER_MEDIA_PLAYBACK_CMD_ID_STOP, &[]).await?;
769 decode_playback_response(&tlv)
770}
771
772pub async fn start_over(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<PlaybackResponse> {
774 let tlv = conn.invoke_request2(endpoint, crate::clusters::defs::CLUSTER_ID_MEDIA_PLAYBACK, crate::clusters::defs::CLUSTER_MEDIA_PLAYBACK_CMD_ID_STARTOVER, &[]).await?;
775 decode_playback_response(&tlv)
776}
777
778pub async fn previous(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<PlaybackResponse> {
780 let tlv = conn.invoke_request2(endpoint, crate::clusters::defs::CLUSTER_ID_MEDIA_PLAYBACK, crate::clusters::defs::CLUSTER_MEDIA_PLAYBACK_CMD_ID_PREVIOUS, &[]).await?;
781 decode_playback_response(&tlv)
782}
783
784pub async fn next(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<PlaybackResponse> {
786 let tlv = conn.invoke_request2(endpoint, crate::clusters::defs::CLUSTER_ID_MEDIA_PLAYBACK, crate::clusters::defs::CLUSTER_MEDIA_PLAYBACK_CMD_ID_NEXT, &[]).await?;
787 decode_playback_response(&tlv)
788}
789
790pub async fn rewind(conn: &crate::controller::Connection, endpoint: u16, audio_advance_unmuted: bool) -> anyhow::Result<PlaybackResponse> {
792 let tlv = conn.invoke_request2(endpoint, crate::clusters::defs::CLUSTER_ID_MEDIA_PLAYBACK, crate::clusters::defs::CLUSTER_MEDIA_PLAYBACK_CMD_ID_REWIND, &encode_rewind(audio_advance_unmuted)?).await?;
793 decode_playback_response(&tlv)
794}
795
796pub async fn fast_forward(conn: &crate::controller::Connection, endpoint: u16, audio_advance_unmuted: bool) -> anyhow::Result<PlaybackResponse> {
798 let tlv = conn.invoke_request2(endpoint, crate::clusters::defs::CLUSTER_ID_MEDIA_PLAYBACK, crate::clusters::defs::CLUSTER_MEDIA_PLAYBACK_CMD_ID_FASTFORWARD, &encode_fast_forward(audio_advance_unmuted)?).await?;
799 decode_playback_response(&tlv)
800}
801
802pub async fn skip_forward(conn: &crate::controller::Connection, endpoint: u16, delta_position_milliseconds: u64) -> anyhow::Result<PlaybackResponse> {
804 let tlv = conn.invoke_request2(endpoint, crate::clusters::defs::CLUSTER_ID_MEDIA_PLAYBACK, crate::clusters::defs::CLUSTER_MEDIA_PLAYBACK_CMD_ID_SKIPFORWARD, &encode_skip_forward(delta_position_milliseconds)?).await?;
805 decode_playback_response(&tlv)
806}
807
808pub async fn skip_backward(conn: &crate::controller::Connection, endpoint: u16, delta_position_milliseconds: u64) -> anyhow::Result<PlaybackResponse> {
810 let tlv = conn.invoke_request2(endpoint, crate::clusters::defs::CLUSTER_ID_MEDIA_PLAYBACK, crate::clusters::defs::CLUSTER_MEDIA_PLAYBACK_CMD_ID_SKIPBACKWARD, &encode_skip_backward(delta_position_milliseconds)?).await?;
811 decode_playback_response(&tlv)
812}
813
814pub async fn seek(conn: &crate::controller::Connection, endpoint: u16, position: u64) -> anyhow::Result<PlaybackResponse> {
816 let tlv = conn.invoke_request2(endpoint, crate::clusters::defs::CLUSTER_ID_MEDIA_PLAYBACK, crate::clusters::defs::CLUSTER_MEDIA_PLAYBACK_CMD_ID_SEEK, &encode_seek(position)?).await?;
817 decode_playback_response(&tlv)
818}
819
820pub async fn activate_audio_track(conn: &crate::controller::Connection, endpoint: u16, track_id: String, audio_output_index: Option<u8>) -> anyhow::Result<()> {
822 conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_MEDIA_PLAYBACK, crate::clusters::defs::CLUSTER_MEDIA_PLAYBACK_CMD_ID_ACTIVATEAUDIOTRACK, &encode_activate_audio_track(track_id, audio_output_index)?).await?;
823 Ok(())
824}
825
826pub async fn activate_text_track(conn: &crate::controller::Connection, endpoint: u16, track_id: String) -> anyhow::Result<()> {
828 conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_MEDIA_PLAYBACK, crate::clusters::defs::CLUSTER_MEDIA_PLAYBACK_CMD_ID_ACTIVATETEXTTRACK, &encode_activate_text_track(track_id)?).await?;
829 Ok(())
830}
831
832pub async fn deactivate_text_track(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<()> {
834 conn.invoke_request(endpoint, crate::clusters::defs::CLUSTER_ID_MEDIA_PLAYBACK, crate::clusters::defs::CLUSTER_MEDIA_PLAYBACK_CMD_ID_DEACTIVATETEXTTRACK, &[]).await?;
835 Ok(())
836}
837
838pub async fn read_current_state(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<PlaybackState> {
840 let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_MEDIA_PLAYBACK, crate::clusters::defs::CLUSTER_MEDIA_PLAYBACK_ATTR_ID_CURRENTSTATE).await?;
841 decode_current_state(&tlv)
842}
843
844pub async fn read_start_time(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Option<u64>> {
846 let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_MEDIA_PLAYBACK, crate::clusters::defs::CLUSTER_MEDIA_PLAYBACK_ATTR_ID_STARTTIME).await?;
847 decode_start_time(&tlv)
848}
849
850pub async fn read_duration(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Option<u64>> {
852 let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_MEDIA_PLAYBACK, crate::clusters::defs::CLUSTER_MEDIA_PLAYBACK_ATTR_ID_DURATION).await?;
853 decode_duration(&tlv)
854}
855
856pub async fn read_sampled_position(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Option<PlaybackPosition>> {
858 let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_MEDIA_PLAYBACK, crate::clusters::defs::CLUSTER_MEDIA_PLAYBACK_ATTR_ID_SAMPLEDPOSITION).await?;
859 decode_sampled_position(&tlv)
860}
861
862pub async fn read_playback_speed(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<u8> {
864 let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_MEDIA_PLAYBACK, crate::clusters::defs::CLUSTER_MEDIA_PLAYBACK_ATTR_ID_PLAYBACKSPEED).await?;
865 decode_playback_speed(&tlv)
866}
867
868pub async fn read_seek_range_end(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Option<u64>> {
870 let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_MEDIA_PLAYBACK, crate::clusters::defs::CLUSTER_MEDIA_PLAYBACK_ATTR_ID_SEEKRANGEEND).await?;
871 decode_seek_range_end(&tlv)
872}
873
874pub async fn read_seek_range_start(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Option<u64>> {
876 let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_MEDIA_PLAYBACK, crate::clusters::defs::CLUSTER_MEDIA_PLAYBACK_ATTR_ID_SEEKRANGESTART).await?;
877 decode_seek_range_start(&tlv)
878}
879
880pub async fn read_active_audio_track(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Option<Track>> {
882 let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_MEDIA_PLAYBACK, crate::clusters::defs::CLUSTER_MEDIA_PLAYBACK_ATTR_ID_ACTIVEAUDIOTRACK).await?;
883 decode_active_audio_track(&tlv)
884}
885
886pub async fn read_available_audio_tracks(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Vec<Track>> {
888 let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_MEDIA_PLAYBACK, crate::clusters::defs::CLUSTER_MEDIA_PLAYBACK_ATTR_ID_AVAILABLEAUDIOTRACKS).await?;
889 decode_available_audio_tracks(&tlv)
890}
891
892pub async fn read_active_text_track(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Option<Track>> {
894 let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_MEDIA_PLAYBACK, crate::clusters::defs::CLUSTER_MEDIA_PLAYBACK_ATTR_ID_ACTIVETEXTTRACK).await?;
895 decode_active_text_track(&tlv)
896}
897
898pub async fn read_available_text_tracks(conn: &crate::controller::Connection, endpoint: u16) -> anyhow::Result<Vec<Track>> {
900 let tlv = conn.read_request2(endpoint, crate::clusters::defs::CLUSTER_ID_MEDIA_PLAYBACK, crate::clusters::defs::CLUSTER_MEDIA_PLAYBACK_ATTR_ID_AVAILABLETEXTTRACKS).await?;
901 decode_available_text_tracks(&tlv)
902}
903
904#[derive(Debug, serde::Serialize)]
905pub struct StateChangedEvent {
906 pub current_state: Option<PlaybackState>,
907 pub start_time: Option<u64>,
908 pub duration: Option<u64>,
909 pub sampled_position: Option<PlaybackPosition>,
910 pub playback_speed: Option<u8>,
911 pub seek_range_end: Option<u64>,
912 pub seek_range_start: Option<u64>,
913 #[serde(serialize_with = "serialize_opt_bytes_as_hex")]
914 pub data: Option<Vec<u8>>,
915 pub audio_advance_unmuted: Option<bool>,
916}
917
918pub fn decode_state_changed_event(inp: &tlv::TlvItemValue) -> anyhow::Result<StateChangedEvent> {
922 if let tlv::TlvItemValue::List(_fields) = inp {
923 let item = tlv::TlvItem { tag: 0, value: inp.clone() };
924 Ok(StateChangedEvent {
925 current_state: item.get_int(&[0]).and_then(|v| PlaybackState::from_u8(v as u8)),
926 start_time: item.get_int(&[1]),
927 duration: item.get_int(&[2]),
928 sampled_position: {
929 if let Some(nested_tlv) = item.get(&[3]) {
930 if let tlv::TlvItemValue::List(_) = nested_tlv {
931 let nested_item = tlv::TlvItem { tag: 3, value: nested_tlv.clone() };
932 Some(PlaybackPosition {
933 updated_at: nested_item.get_int(&[0]),
934 position: nested_item.get_int(&[1]),
935 })
936 } else {
937 None
938 }
939 } else {
940 None
941 }
942 },
943 playback_speed: item.get_int(&[4]).map(|v| v as u8),
944 seek_range_end: item.get_int(&[5]),
945 seek_range_start: item.get_int(&[6]),
946 data: item.get_octet_string_owned(&[7]),
947 audio_advance_unmuted: item.get_bool(&[8]),
948 })
949 } else {
950 Err(anyhow::anyhow!("Expected struct fields"))
951 }
952}
953