import { html, LitElement, unsafeCSS, type CSSResultGroup, type PropertyValues, type TemplateResult, } from 'lit'; import { customElement, property } from 'lit/decorators.js'; import type { CameraManager } from '../../camera-manager/manager.js'; import type { CallSession } from '../../card-controller/call/types.js'; import type { StateWatcherSubscriptionInterface } from '../../card-controller/hass/state-watcher.js'; import type { MicrophoneManager } from '../../card-controller/microphone-manager.js'; import type { MicrophoneState } from '../../card-controller/types.js'; import type { ViewManagerEpoch } from '../../card-controller/view/types.js'; import { MicrophoneActionsController } from '../../components-lib/live/microphone-actions-controller.js'; import '../../components-lib/live/types.js'; import type { LiveConfig } from '../../config/schema/live.js'; import type { CardWideConfig } from '../../config/schema/types.js'; import type { HomeAssistant } from '../../ha/types.js'; import basicBlockStyle from '../../scss/basic-block.scss'; import { contentsChanged } from '../../utils/basic.js'; import './grid.js'; @customElement('advanced-camera-card-live') export class AdvancedCameraCardLive extends LitElement { @property({ attribute: false }) public hass?: HomeAssistant; @property({ attribute: false }) public stateWatcher?: StateWatcherSubscriptionInterface; @property({ attribute: false }) public viewManagerEpoch?: ViewManagerEpoch; @property({ attribute: false }) public liveConfig?: LiveConfig; @property({ attribute: false }) public cameraManager?: CameraManager; @property({ attribute: false }) public cardWideConfig?: CardWideConfig; @property({ attribute: false }) public microphoneManager?: MicrophoneManager; @property({ attribute: false }) public microphoneState?: MicrophoneState; @property({ attribute: false }) public call?: CallSession; @property({ attribute: false }) public locked?: boolean; @property({ attribute: false, hasChanged: contentsChanged }) public triggeredCameraIDs?: Set; private _microphoneActionsController = new MicrophoneActionsController(); public connectedCallback(): void { super.connectedCallback(); this._microphoneActionsController.setRoot(this); } public disconnectedCallback(): void { this._microphoneActionsController.destroy(); super.disconnectedCallback(); } protected willUpdate(changedProps: PropertyValues): void { if (changedProps.has('liveConfig') || changedProps.has('microphoneManager')) { this._microphoneActionsController.setOptions({ microphoneManager: this.microphoneManager, autoMuteConditions: this.liveConfig?.microphone.auto_mute, autoUnmuteConditions: this.liveConfig?.microphone.auto_unmute, }); } if (changedProps.has('viewManagerEpoch')) { // The live element is also rendered (and this willUpdate runs) when // `live.preload` is true even while the user is on gallery/viewer/ // timeline. `view.camera` is set across all views, so without this gate // `auto_unmute: ['selected']` would prompt for / open the microphone // from a hidden live view. Treat the live view as having no selected // camera unless it is the active view. const view = this.viewManagerEpoch?.manager.getView(); void this._microphoneActionsController.setSelectedCamera( view?.is('live') ? view.camera ?? null : null, ); } if (changedProps.has('call')) { // Gate on `answered`, not mere presence, so `microphone.auto_unmute: // ['call']` doesn't transmit audio during the pre-answer ringing state. // Outbound calls are answered at construction; inbound calls only after // the user accepts. this._microphoneActionsController.setCallAnswered(!!this.call?.answered); } } protected render(): TemplateResult | void { if (!this.hass || !this.cameraManager) { return; } return html` `; } static get styles(): CSSResultGroup { return unsafeCSS(basicBlockStyle); } } declare global { interface HTMLElementTagNameMap { 'advanced-camera-card-live': AdvancedCameraCardLive; } }