@@ -576,12 +576,7 @@ def _coerce_audio_id(self, value: Any) -> int | None:
576576 return None
577577
578578 def _audio_inputs_from_layers (self , layers : list [dict [str , Any ]]) -> list [dict [str , Any ]]:
579- """Build audio input options from layers where audioStatus.isAvailable == 1.
580-
581- Prefer layer source.inputId as the option id when available. Some firmware
582- keeps audioStatus.isOpen anchored to one layer (commonly layer 0) while
583- switching source input within that layer.
584- """
579+ """Build audio input options from layers where audioStatus.isAvailable == 1."""
585580 mapped : dict [int , str ] = {}
586581
587582 for layer in layers :
@@ -601,28 +596,21 @@ def _audio_inputs_from_layers(self, layers: list[dict[str, Any]]) -> list[dict[s
601596 continue
602597
603598 source = layer .get ("source" )
604- option_id = layer_id
605599 input_name : str | None = None
606600 if isinstance (source , dict ):
607- source_input_id = self ._coerce_audio_id (source .get ("inputId" ))
608- if source_input_id is not None :
609- option_id = source_input_id
610601 source_name = source .get ("name" )
611602 if isinstance (source_name , str ) and source_name .strip ():
612603 input_name = source_name .strip ()
613604
614605 if not input_name :
615606 input_name = "Input"
616607
617- mapped [option_id ] = f"{ input_name } (Layer { layer_id } )"
608+ mapped [layer_id ] = f"{ input_name } (Layer { layer_id } )"
618609
619- return [{"id" : option_id , "name" : mapped [option_id ]} for option_id in sorted (mapped )]
610+ return [{"id" : layer_id , "name" : mapped [layer_id ]} for layer_id in sorted (mapped )]
620611
621612 def _selected_audio_input_from_layers (self , layers : list [dict [str , Any ]]) -> int | None :
622- """Get selected audio input id from layer audioStatus.isOpen flag.
623-
624- Prefer open layer source.inputId, fallback to open layerId.
625- """
613+ """Get selected audio input id from layer audioStatus.isOpen flag."""
626614 selected_ids : list [int ] = []
627615
628616 for layer in layers :
@@ -639,13 +627,7 @@ def _selected_audio_input_from_layers(self, layers: list[dict[str, Any]]) -> int
639627
640628 is_open = self ._coerce_audio_id (audio_status .get ("isOpen" ))
641629 if is_open == 1 :
642- selected_id = layer_id
643- source = layer .get ("source" )
644- if isinstance (source , dict ):
645- source_input_id = self ._coerce_audio_id (source .get ("inputId" ))
646- if source_input_id is not None :
647- selected_id = source_input_id
648- selected_ids .append (selected_id )
630+ selected_ids .append (layer_id )
649631
650632 if not selected_ids :
651633 return None
@@ -768,197 +750,101 @@ async def async_set_audio_input(
768750 screen_id : int = 0 ,
769751 device_id : int = 0 ,
770752 ) -> bool :
771- """Set active audio input.
772-
773- The provided input_id may represent either a layerId or a source inputId.
774- """
775- selected_option_id = int (input_id )
776- layer_items = await self .async_get_layers_with_details (
777- device_id = int (device_id ),
778- screen_id = int (screen_id ),
779- )
780-
781- audio_layers : list [dict [str , Any ]] = []
782- for layer in layer_items :
783- if not isinstance (layer , dict ):
784- continue
785- layer_id = self ._coerce_audio_id (layer .get ("layerId" ))
786- if layer_id is None :
787- continue
788- audio_status = layer .get ("audioStatus" )
789- if not isinstance (audio_status , dict ):
790- continue
791- if self ._coerce_audio_id (audio_status .get ("isAvailable" )) != 1 :
792- continue
793- audio_layers .append (layer )
794-
795- self ._debug_log (
796- "Audio input set request host=%s screen_id=%s device_id=%s selected_option_id=%s available_audio_layers=%s" ,
797- self ._host ,
798- int (screen_id ),
799- int (device_id ),
800- selected_option_id ,
801- [self ._coerce_audio_id (layer .get ("layerId" )) for layer in audio_layers ],
802- )
803-
804- if not audio_layers :
805- self ._debug_log ("Audio input set aborted: no layers with audioStatus.isAvailable == 1" )
806- return False
807-
808- def _layer_source_input_id (layer : dict [str , Any ]) -> int | None :
809- source = layer .get ("source" )
810- if isinstance (source , dict ):
811- return self ._coerce_audio_id (source .get ("inputId" ))
812- return None
813-
814- selected_layer : dict [str , Any ] | None = None
815- for layer in audio_layers :
816- layer_id = self ._coerce_audio_id (layer .get ("layerId" ))
817- source_input_id = _layer_source_input_id (layer )
818- if layer_id == selected_option_id or source_input_id == selected_option_id :
819- selected_layer = layer
820- break
753+ """Set active audio input via layer/screenLayerLayout using layer detail list."""
754+ selected_layer_id = int (input_id )
755+ detail_payload = {
756+ "deviceId" : int (device_id ),
757+ "screenId" : int (screen_id ),
758+ }
821759
822- if selected_layer is None :
760+ layout_data = await self ._async_request ("layer/detailList" , detail_payload )
761+ if not isinstance (layout_data , dict ):
823762 self ._debug_log (
824- "Audio input set aborted: selected_option_id=%s not matched to any available layer/source available=%s" ,
825- selected_option_id ,
826- [
827- {
828- "layer_id" : self ._coerce_audio_id (layer .get ("layerId" )),
829- "source_input_id" : _layer_source_input_id (layer ),
830- }
831- for layer in audio_layers
832- ],
763+ "Audio input set aborted: layer/detailList returned invalid data host=%s" ,
764+ self ._host ,
833765 )
834766 return False
835767
836- selected_layer_id = self ._coerce_audio_id (selected_layer .get ("layerId" ))
837- if selected_layer_id is None :
768+ layers_key = "screenLayers" if isinstance (layout_data .get ("screenLayers" ), list ) else "layers"
769+ raw_layers = layout_data .get (layers_key )
770+ if not isinstance (raw_layers , list ):
838771 self ._debug_log (
839- "Audio input set aborted: matched layer has no valid layerId selected_option_id=%s" ,
840- selected_option_id ,
772+ "Audio input set aborted: no layers in detailList host=%s keys=%s" ,
773+ self ._host ,
774+ list (layout_data .keys ()),
841775 )
842776 return False
843777
844- any_layer_updated = False
845- selected_layer_updated = False
846-
847- async def _build_write_general_payload (
848- layer : dict [str , Any ], should_open : int
849- ) -> dict [str , Any ] | None :
850- """Build layer/writeGeneral payload for one layer audio open state."""
851- layer_id = self ._coerce_audio_id (layer .get ("layerId" ))
852- current_audio_status = layer .get ("audioStatus" )
853- if layer_id is None or not isinstance (current_audio_status , dict ):
854- return None
855-
856- desired_audio_status = dict (current_audio_status )
857- desired_audio_status ["isOpen" ] = should_open
858-
859- payload_base = {
860- "screenId" : int (screen_id ),
861- "deviceId" : int (device_id ),
862- "layerId" : int (layer_id ),
863- }
778+ updated_layers : list [dict [str , Any ]] = []
779+ selected_found = False
780+ for layer in raw_layers :
781+ if not isinstance (layer , dict ):
782+ continue
783+ layer_copy = dict (layer )
784+ layer_id = self ._coerce_audio_id (layer_copy .get ("layerId" ))
785+ audio_status = layer_copy .get ("audioStatus" )
786+ if not isinstance (audio_status , dict ):
787+ audio_status = {}
788+ updated_audio_status = dict (audio_status )
789+ if layer_id == selected_layer_id :
790+ updated_audio_status ["isOpen" ] = 1
791+ selected_found = True
792+ else :
793+ updated_audio_status ["isOpen" ] = 0
794+ layer_copy ["audioStatus" ] = updated_audio_status
795+ updated_layers .append (layer_copy )
864796
865- general_data : dict [ str , Any ] | None = None
866- layer_detail = await self .async_get_layer_detail (
867- layer_id = int ( layer_id ) ,
868- device_id = int ( device_id ) ,
869- screen_id = int ( screen_id ) ,
797+ if not selected_found :
798+ self ._debug_log (
799+ "Audio input set aborted: selected layer not found selected_layer_id=%s available=%s" ,
800+ selected_layer_id ,
801+ [ self . _coerce_audio_id ( layer . get ( "layerId" )) for layer in updated_layers ] ,
870802 )
871- if isinstance (layer_detail , dict ) and isinstance (layer_detail .get ("general" ), dict ):
872- general_data = dict (layer_detail ["general" ])
873- elif isinstance (layer .get ("general" ), dict ):
874- general_data = dict (layer ["general" ])
875-
876- if general_data is None :
877- self ._debug_log (
878- "Audio input write skipped layer_id=%s: missing general data for layer/writeGeneral" ,
879- layer_id ,
880- )
881- return None
882-
883- write_general_payload = {
884- ** payload_base ,
885- "name" : general_data .get ("name" )
886- if isinstance (general_data .get ("name" ), str ) and general_data .get ("name" ).strip ()
887- else f"Layer { layer_id } " ,
888- "sizeType" : int (general_data .get ("sizeType" , 0 )),
889- "type" : int (general_data .get ("type" , 0 )),
890- "zorder" : int (general_data .get ("zorder" , 0 )),
891- "isBackground" : bool (general_data .get ("isBackground" , False )),
892- "isFreeze" : bool (general_data .get ("isFreeze" , False )),
893- "flipType" : int (general_data .get ("flipType" , 0 )),
894- "audioStatus" : desired_audio_status ,
895- }
896-
897- optional_lock = self ._coerce_audio_id (general_data .get ("lock" ))
898- if optional_lock is not None :
899- write_general_payload ["lock" ] = optional_lock
900-
901- reverse_control = layer_detail .get ("reverseControl" ) if isinstance (layer_detail , dict ) else None
902- if isinstance (reverse_control , dict ):
903- write_general_payload ["reverseControl" ] = reverse_control
904- return write_general_payload
905-
906- for layer in audio_layers :
907- layer_id = self ._coerce_audio_id (layer .get ("layerId" ))
908- current_audio_status = layer .get ("audioStatus" )
909- if layer_id is None or not isinstance (current_audio_status , dict ):
910- continue
911- should_open = 1 if layer is selected_layer else 0
912- payload = await _build_write_general_payload (layer , should_open )
913- if payload is None :
914- continue
915- attempt = await self ._async_request ("layer/writeGeneral" , payload )
916- if attempt is not None :
917- any_layer_updated = True
918- if should_open == 1 :
919- selected_layer_updated = True
803+ return False
920804
921- if any_layer_updated :
922- self ._force_refresh_layer_details = True
805+ write_payload = {
806+ ** detail_payload ,
807+ ** {
808+ key : value
809+ for key , value in layout_data .items ()
810+ if key not in ("screenLayers" , "layers" )
811+ },
812+ layers_key : updated_layers ,
813+ }
923814
924- async def _verify_selected_open () -> tuple [bool , int | None , list [int | None ]]:
925- self ._force_refresh_layer_details = True
926- refreshed_layers = await self .async_get_layers_with_details (
927- device_id = int (device_id ),
928- screen_id = int (screen_id ),
815+ write_result = await self ._async_request ("layer/screenLayerLayout" , write_payload )
816+ if write_result is None :
817+ _LOGGER .warning (
818+ "Audio input apply failed on host=%s selected_layer_id=%s endpoint=layer/screenLayerLayout" ,
819+ self ._host ,
820+ selected_layer_id ,
929821 )
930- selected_after = self ._selected_audio_input_from_layers (refreshed_layers )
822+ return False
823+
824+ self ._force_refresh_layer_details = True
825+ refreshed_layers = await self .async_get_layers_with_details (
826+ device_id = int (device_id ),
827+ screen_id = int (screen_id ),
828+ )
829+ selected_after = self ._selected_audio_input_from_layers (refreshed_layers )
830+ if selected_after != selected_layer_id :
931831 open_layers = [
932832 self ._coerce_audio_id (layer .get ("layerId" ))
933833 for layer in refreshed_layers
934834 if isinstance (layer , dict )
935835 and isinstance (layer .get ("audioStatus" ), dict )
936836 and self ._coerce_audio_id (layer ["audioStatus" ].get ("isOpen" )) == 1
937837 ]
938- is_selected = selected_after in (selected_option_id , selected_layer_id )
939- return is_selected , selected_after , open_layers
940-
941- is_selected_applied , selected_after_write , currently_open = await _verify_selected_open ()
942-
943- if not is_selected_applied :
944838 _LOGGER .warning (
945- "Audio input apply failed on host=%s selected_option_id=%s selected_layer_id=%s selected_after_write=%s open_layers=%s" ,
839+ "Audio input apply failed on host=%s selected_layer_id=%s selected_after_write=%s open_layers=%s endpoint=layer/screenLayerLayout " ,
946840 self ._host ,
947- selected_option_id ,
948841 selected_layer_id ,
949- selected_after_write ,
950- currently_open ,
842+ selected_after ,
843+ open_layers ,
951844 )
845+ return False
952846
953- self ._debug_log (
954- "Audio input write complete selected_layer_id=%s any_layer_updated=%s selected_layer_updated=%s selected_after_write=%s" ,
955- selected_layer_id ,
956- any_layer_updated ,
957- selected_layer_updated ,
958- selected_after_write ,
959- )
960-
961- return any_layer_updated and selected_layer_updated and is_selected_applied
847+ return True
962848
963849 async def async_set_audio_output (
964850 self ,
0 commit comments