1use crate::tlv;
7use anyhow;
8use serde_json;
9
10
11use crate::clusters::helpers::{serialize_opt_bytes_as_hex};
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
17#[repr(u8)]
18pub enum Characteristic {
19 Forcedsubtitles = 0,
21 Describesvideo = 1,
23 Easytoread = 2,
25 Framebased = 3,
27 Mainprogram = 4,
29 Originalcontent = 5,
31 Voiceovertranslation = 6,
33 Caption = 7,
35 Subtitle = 8,
37 Alternate = 9,
39 Supplementary = 10,
41 Commentary = 11,
43 Dubbedtranslation = 12,
45 Description = 13,
47 Metadata = 14,
49 Enhancedaudiointelligibility = 15,
51 Emergency = 16,
53 Karaoke = 17,
55}
56
57impl Characteristic {
58 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 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 Playing = 0,
100 Paused = 1,
102 Notplaying = 2,
104 Buffering = 3,
106}
107
108impl PlaybackState {
109 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 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 Success = 0,
137 Invalidstateforcommand = 1,
139 Notallowed = 2,
141 Notactive = 3,
143 Speedoutofrange = 4,
145 Seekoutofrange = 5,
147}
148
149impl Status {
150 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 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#[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
196pub 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
209pub 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
220pub 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
231pub 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
242pub 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
253pub 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
265pub 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
276pub 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
287pub 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
296pub 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
305pub fn decode_sampled_position(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<PlaybackPosition>> {
307 if let tlv::TlvItemValue::List(_fields) = inp {
308 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 {
318 Ok(None)
319 }
321}
322
323pub 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
332pub 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
341pub 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
350pub fn decode_active_audio_track(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<Track>> {
352 if let tlv::TlvItemValue::List(_fields) = inp {
353 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 {
385 Ok(None)
386 }
388}
389
390pub 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
426pub fn decode_active_text_track(inp: &tlv::TlvItemValue) -> anyhow::Result<Option<Track>> {
428 if let tlv::TlvItemValue::List(_fields) = inp {
429 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 {
461 Ok(None)
462 }
464}
465
466pub 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
503pub fn decode_attribute_json(cluster_id: u32, attribute_id: u32, tlv_value: &crate::tlv::TlvItemValue) -> String {
515 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
591pub 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
617pub 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
646pub 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