-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathintercom-camera-card.js
More file actions
307 lines (307 loc) · 49.3 KB
/
Copy pathintercom-camera-card.js
File metadata and controls
307 lines (307 loc) · 49.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
// Generated from src/card.js. Run npm run build; do not edit this file.
var w="video,audio",k="video,audio,microphone",m="webrtc";var _="intercom-camera-card:stream:",T="intercom-camera-card:pan:",S="Main";var A="tts.home_assistant_cloud",E=new Set(["unavailable","unknown"]),d=50,u={enabled:!0,title:"Talk",active_title:"Hang up",icon:"mdi:phone",active_icon:"mdi:phone-hangup",color:"#ffffff",active_color:"#ffffff",background:"#159447",hover_background:"#18a95a",active_background:"#d43d32",active_border:"1px solid rgba(255, 255, 255, 0.28)",busy_color:"#07111f",busy_background:"#f5b640",busy_border:"1px solid rgba(255, 255, 255, 0.32)",starting_status:"Connecting microphone",ending_status:"Ending talk",active_status:"Talking",requesting_status:"Enabling microphone"},x="/local/sounds/",B={alert:{color:"#ffffff",background:"#b4552f",hover_background:"#c9643b"},primary:{color:"#ffffff",background:"#1f6feb",hover_background:"#2f81f7"},warning:{color:"#07111f",background:"#f5b640",hover_background:"#fbbf24"},light:{color:"#ffcf6b"},light_on:{color:"#ffffff",background:"#f59e0b",hover_background:"#fbbf24"},disabled:{color:"#d6dde8",background:"rgba(79, 86, 99, 0.62)"}};import{VideoRTC as M}from"/webrtc/video-rtc.js";var p=class extends M{constructor(){super(),this.mode=m,this.background=!1,this.visibilityThreshold=.75,this.connectionGeneration=0,this.connectionTimeout=0,this.signing=!1,this.talking=!1,this.streamVariant="primary",this.localMicrophoneTracks=[],this.keepFrameOnDisconnect=!1,this.posterGeneration=0,this.posterURL=null,this.pendingVideo=null,this.pendingConnectStatus=void 0,this.elementInView=!0,this.visibilityHandlersInstalled=!1,this.visibilityObserver=null,this.handlePageHide=()=>this.disconnectImmediately(),this.handleVisibilityChange=()=>this.syncVisibility(),this.handlePageShow=()=>this.syncVisibility()}disconnectImmediately(){this.background||(this.clearReconnectTimers(),this.ondisconnect())}installVisibilityHandlers(){this.background||this.visibilityHandlersInstalled||(this.visibilityHandlersInstalled=!0,window.addEventListener("pagehide",this.handlePageHide,{passive:!0}),window.addEventListener("pageshow",this.handlePageShow,{passive:!0}),"hidden"in document&&this.visibilityCheck&&document.addEventListener("visibilitychange",this.handleVisibilityChange),"IntersectionObserver"in window&&this.visibilityThreshold&&(this.visibilityObserver=new IntersectionObserver(t=>{t.forEach(e=>{this.elementInView=e.isIntersecting&&e.intersectionRatio>=this.visibilityThreshold,this.syncVisibility()})},{threshold:this.visibilityThreshold}),this.visibilityObserver.observe(this)))}removeVisibilityHandlers(){this.visibilityHandlersInstalled&&(window.removeEventListener("pagehide",this.handlePageHide),window.removeEventListener("pageshow",this.handlePageShow),document.removeEventListener("visibilitychange",this.handleVisibilityChange),this.visibilityObserver?.disconnect(),this.visibilityObserver=null,this.visibilityHandlersInstalled=!1)}syncVisibility(){if(!this.shouldStream()){this.disconnectImmediately();return}this.onconnect()}shouldStream(){return this.hasVideoSource()&&this.isConnected&&document.hidden!==!0&&this.elementInView!==!1}mediaForTalkState(){return this.talking?k:w}hasAlternateStream(){return!!this.config?.alternate_stream}useAlternateStream(){return this.hasAlternateStream()&&this.streamVariant==="alternate"}activeVideoSource(){return this.useAlternateStream()?this.config.alternate_stream:this.config.stream||this.config.url}hasVideoSource(){return!!(this.config?.entity||this.config?.stream||this.config?.url)}streamLabel(t=this.streamVariant){return t==="alternate"?this.config.alternate_label||"Alt":this.config.primary_label||S}streamPreferenceKey(){if(!this.hasAlternateStream())return"";let t=this.config.stream||this.config.url||this.config.entity||"",e=this.config.alternate_stream||"";return`${_}${encodeURIComponent(t)}:${encodeURIComponent(e)}`}loadStreamVariant(){let t=this.streamPreferenceKey();if(!t)return"primary";try{return localStorage.getItem(t)==="alternate"?"alternate":"primary"}catch(e){return console.debug(e),"primary"}}saveStreamVariant(){let t=this.streamPreferenceKey();if(t)try{localStorage.setItem(t,this.streamVariant)}catch(e){console.debug(e)}}onconnect(){if(!this.config||!this.hass)return!1;if(!this.hasVideoSource())return this.showStatus("Camera source unavailable"),!1;if(!this.shouldStream()||this.ws||this.pc||this.signing)return!1;let t=++this.connectionGeneration;this.signing=!0,clearTimeout(this.connectionTimeout),this.connectionTimeout=setTimeout(()=>{this.connectionFailed(new Error("Camera connection timed out"),t)},3e4);let e=this.pendingConnectStatus??(this.talking?this.talkButton.starting_status:"Connecting video");return this.pendingConnectStatus=void 0,e&&this.showStatus(e),this.hass.callWS({type:"auth/sign_path",path:"/api/webrtc/ws"}).then(i=>{if(this.canStartConnection(t)){if(this.signing=!1,this.wsURL=this.buildWebSocketUrl(i.path),!this.wsURL){this.showStatus("Camera source unavailable"),this.setTalkBusy(!1);return}this.openSocket(t)}}).catch(i=>{t===this.connectionGeneration&&(this.signing=!1,console.warn(i),this.showStatus("Connection error",1800),this.setTalkBusy(!1),this.scheduleReconnect())}),!0}canStartConnection(t){return t===this.connectionGeneration&&this.shouldStream()&&!this.ws&&!this.pc}buildWebSocketUrl(t){let e=new URLSearchParams;if(this.config.entity&&!this.useAlternateStream())e.set("entity",this.config.entity);else if(this.activeVideoSource())e.set("url",this.activeVideoSource());else return"";this.config.server&&e.set("server",this.config.server);let i=t.includes("?")?"&":"?";return`ws${this.hass.hassUrl(t).substring(4)}${i}${e}`}openSocket(t){let e=new WebSocket(this.wsURL);this.ws=e,this.wsState=WebSocket.CONNECTING,this.connectTS=Date.now();let i=()=>t===this.connectionGeneration&&e===this.ws;e.addEventListener("open",()=>{if(i())try{this.onopen()}catch(n){this.connectionFailed(n,t)}}),e.addEventListener("message",n=>{if(!(!i()||typeof n.data!="string"))try{let s=JSON.parse(n.data);for(let a of Object.values(this.onmessage))a(s)}catch(s){this.connectionFailed(s,t)}}),e.addEventListener("close",()=>{!i()||this.pcState===WebSocket.OPEN||(this.ondisconnect(),this.showStatus("Reconnecting video"),this.scheduleReconnect())})}send(t){if(this.ws?.readyState===WebSocket.OPEN)try{this.ws.send(JSON.stringify(t))}catch(e){this.connectionFailed(e,this.connectionGeneration)}}onopen(){if(this.wsState=WebSocket.OPEN,this.onmessage={},!("RTCPeerConnection"in window)){this.ondisconnect(),this.showStatus("WebRTC unsupported",3200),this.setTalkBusy(!1);return}this.onmessage.intercom=t=>{t.type==="error"&&(console.warn(t.value),this.ondisconnect(),this.showStatus(this.streamErrorStatus(t.value),3200),this.setTalkBusy(!1),this.scheduleReconnect())},this.onwebrtc()}onpcvideo(t,e=!1){clearTimeout(this.connectionTimeout),this.connectionTimeout=0,super.onpcvideo(t),this.pcState!==WebSocket.CLOSED&&this.talking&&e&&(this.hasLiveMicrophoneTrack()?this.showStatus(this.talkButton.active_status):(this.setTalking(!1),this.showStatus("Microphone unavailable",3200))),this.setTalkBusy(!1)}async createOffer(t){let e=this.connectionGeneration;try{if(this.media.indexOf("microphone")>=0){if(this.talking&&this.showStatus(this.talkButton.requesting_status),!navigator.mediaDevices?.getUserMedia)throw new Error("getUserMedia unavailable");let n=await navigator.mediaDevices.getUserMedia({audio:!0});if(e!==this.connectionGeneration||t!==this.pc||!this.talking||!this.shouldStream())return this.stopStreamTracks(n),null;let s=n.getAudioTracks();if(!s.length)throw this.stopStreamTracks(n),new Error("No microphone track");try{s.forEach(a=>{t.addTransceiver(a,{direction:"sendonly"}),this.localMicrophoneTracks.push(a),a.addEventListener("ended",()=>this.onMicrophoneEnded(a),{once:!0})})}catch(a){throw this.stopTracks(s),a}}}catch(n){if(e!==this.connectionGeneration||t!==this.pc)return null;this.onMicrophoneError(n)}for(let n of["video","audio"])this.media.indexOf(n)>=0&&t.addTransceiver(n,{direction:"recvonly"});let i=await t.createOffer();return e!==this.connectionGeneration||t!==this.pc||(await t.setLocalDescription(i),e!==this.connectionGeneration||t!==this.pc)?null:i}ondisconnect(){this.connectionGeneration++,this.signing=!1,clearTimeout(this.connectionTimeout),this.connectionTimeout=0,clearTimeout(this.statusTimeout),this.statusTimeout=0;let t=!this.keepFrameOnDisconnect;t&&(this.talking&&this.setTalking(!1),this.pendingConnectStatus=void 0,this.setTalkBusy(!1),this.showStatus("",0)),this.wsState=WebSocket.CLOSED,this.ws&&(this.ws.close(),this.ws=null),this.pcState=WebSocket.CLOSED,this.pc&&(this.keepFrameOnDisconnect&&this.capturePoster(),this.stopTracks(this.pc.getSenders().map(e=>e.track)),this.stopTracks(this.pc.getReceivers().map(e=>e.track)),this.pc.close(),this.pc=null),this.stopTracks(this.localMicrophoneTracks),this.localMicrophoneTracks=[],this.stopStreamTracks(this.pendingVideo?.srcObject),this.pendingVideo&&(this.pendingVideo.srcObject=null),this.pendingVideo=null,this.stopStreamTracks(this.video?.srcObject),this.video&&(this.video.removeAttribute("src"),this.video.srcObject=null,t&&this.clearPoster())}stopStreamTracks(t){t?.getTracks&&this.stopTracks(t.getTracks())}stopTracks(t=[]){t.filter(Boolean).forEach(e=>{try{e.stop()}catch(i){console.debug(i)}})}async toggleTalk(){let t=this.$(".talk");if(!this.talkButton.enabled||t.disabled)return;if(!this.talking){this.setTalking(!0),this.setTalkBusy(!0,this.talkButton.starting_status);let i=this.connectionGeneration;if(await this.stopAudioPlayers(),i!==this.connectionGeneration||!this.talking||!this.shouldStream())return}else this.setTalking(!1);this.reconnectKeepingFrame(this.talking?this.talkButton.starting_status:this.talkButton.ending_status)}onMicrophoneError(t){if(console.warn("Intercom microphone unavailable",{secureContext:window.isSecureContext,protocol:window.location.protocol,host:window.location.host,hasMediaDevices:!!navigator.mediaDevices,hasGetUserMedia:!!navigator.mediaDevices?.getUserMedia,errorName:t?.name,errorMessage:t?.message}),this.hasLiveMicrophoneTrack()){this.setTalkBusy(!1),this.talking&&this.showStatus(this.talkButton.active_status);return}if(!this.talking){this.setTalkBusy(!1);return}this.setTalking(!1),this.setTalkBusy(!1),this.showStatus(this.microphoneErrorStatus(t),3200)}microphoneErrorStatus(t){return navigator.mediaDevices?.getUserMedia?t?.name==="NotAllowedError"||t?.name==="PermissionDeniedError"?"Allow microphone access":t?.name==="NotFoundError"||t?.name==="DevicesNotFoundError"?"No microphone found":t?.name==="NotReadableError"||t?.name==="TrackStartError"?"Microphone is busy":"Microphone unavailable":window.isSecureContext===!1?"Microphone needs HTTPS or localhost":"Microphone API unavailable"}setTalking(t){this.talking=!!t,this.mode=m,this.media=this.mediaForTalkState(),this.updateTalkButton(),this.updateStreamToggle(),this.buttons.updateStatefulButtons()}hasLiveMicrophoneTrack(){return this.liveMicrophoneTracks().length>0}liveMicrophoneTracks(){return this.localMicrophoneTracks=this.localMicrophoneTracks.filter(t=>t.readyState==="live"),this.localMicrophoneTracks}onMicrophoneEnded(t){this.localMicrophoneTracks=this.localMicrophoneTracks.filter(e=>e!==t),this.talking&&!this.hasLiveMicrophoneTrack()&&(this.setTalking(!1),this.setTalkBusy(!1),this.showStatus("Microphone disconnected",3200))}async stopAudioPlayers(){let t=this.buttons.audioPlayers();if(!(!t.length||!this.hass?.callService))try{await this.hass.callService("media_player","media_stop",{entity_id:t})}catch(e){console.warn(e)}}streamErrorStatus(t){return t?"Unable to connect to the camera":"Stream error"}reconnectKeepingFrame(t,e=!0){e?this.setTalkBusy(!0,t):this.showStatus(t),this.clearReconnectTimers(),this.pendingConnectStatus=t,this.keepFrameOnDisconnect=!0,this.ondisconnect(),this.keepFrameOnDisconnect=!1,this.onconnect()}scheduleReconnect(t=this.RECONNECT_TIMEOUT){this.clearReconnectTimers();let e=this.connectionGeneration;this.reconnectTID=setTimeout(()=>{this.reconnectTID=0,e===this.connectionGeneration&&this.onconnect()},t)}clearReconnectTimers(){this.disconnectTID&&(clearTimeout(this.disconnectTID),this.disconnectTID=0),this.reconnectTID&&(clearTimeout(this.reconnectTID),this.reconnectTID=0)}capturePoster(){if(!this.video||!this.video.videoWidth||!this.video.videoHeight)return;let t=++this.posterGeneration;try{let e=document.createElement("canvas"),i=Math.min(1,1280/this.video.videoWidth,720/this.video.videoHeight);e.width=Math.max(1,Math.round(this.video.videoWidth*i)),e.height=Math.max(1,Math.round(this.video.videoHeight*i)),e.getContext("2d").drawImage(this.video,0,0,e.width,e.height),e.toBlob(n=>{!n||t!==this.posterGeneration||!this.isConnected||(this.posterURL&&URL.revokeObjectURL(this.posterURL),this.posterURL=URL.createObjectURL(n),this.video.poster=this.posterURL)},"image/jpeg",.82)}catch(e){console.debug(e)}}clearPoster(){this.posterGeneration++,this.posterURL&&URL.revokeObjectURL(this.posterURL),this.posterURL=null,this.video?.removeAttribute("poster")}onwebrtc(){let t=new RTCPeerConnection(this.pcConfig),e=this.connectionGeneration,i=this.media.includes("microphone");this.pc=t,this.pcState=WebSocket.CONNECTING;let n=()=>e===this.connectionGeneration&&t===this.pc,s=!1,a=[];t.addEventListener("icecandidate",o=>{n()&&this.send({type:"webrtc/candidate",value:o.candidate?.toJSON().candidate??""})}),t.addEventListener("connectionstatechange",()=>{if(n())if(t.connectionState==="connected"){let o=t.getTransceivers().filter(l=>["recvonly","sendrecv"].includes(l.currentDirection)).map(l=>l.receiver.track),c=document.createElement("video");this.pendingVideo=c,c.addEventListener("loadeddata",()=>{n()?this.onpcvideo(c,i):this.stopStreamTracks(c.srcObject),c.srcObject=null,this.pendingVideo===c&&(this.pendingVideo=null)},{once:!0}),c.srcObject=new MediaStream(o)}else["failed","disconnected"].includes(t.connectionState)&&(this.ondisconnect(),this.showStatus("Reconnecting video"),this.scheduleReconnect(1e3))}),this.onmessage.webrtc=o=>{if(!n())return;let c;if(o.type==="webrtc/candidate"){let l={candidate:o.value,sdpMid:"0"};s?c=t.addIceCandidate(l):a.push(l)}o.type==="webrtc/answer"&&(c=t.setRemoteDescription({type:"answer",sdp:o.value}).then(()=>{if(n())return s=!0,Promise.all(a.splice(0).map(l=>t.addIceCandidate(l)))})),c?.catch(l=>this.connectionFailed(l,e))},this.createOffer(t).then(o=>{o&&n()&&this.send({type:"webrtc/offer",value:o.sdp})}).catch(o=>this.connectionFailed(o,e))}connectionFailed(t,e){e===this.connectionGeneration&&(console.warn("Intercom WebRTC connection failed",t),this.ondisconnect(),this.showStatus("Connection error",3200),this.scheduleReconnect())}};var f=class{constructor(t){this.card=t,this.leftButtons=[],this.rightButtons=[],this.pendingActionIds=new Set,this.cooldowns=new Set,this.elements=new Map}get config(){return this.card.config}get hass(){return this.card.hass}get talking(){return this.card.talking}configure(){this.dispose(),this.leftButtons=this.normalizeButtons("left"),this.rightButtons=this.normalizeButtons("right");for(let t of[...this.leftButtons,...this.rightButtons])t.dependencies=[...new Set([t.entity,...this.buttonPlayers(t)])].filter(Boolean)}dispose(){for(let t of this.cooldowns)clearTimeout(t);this.cooldowns.clear(),this.pendingActionIds.clear()}normalizeButtons(t){return Array.isArray(this.config.buttons)?this.config.buttons.filter(e=>(e.position||"left")===t).map((e,i)=>this.normalizeButton(e,`${t}-${i}`)):[]}normalizeButton(t,e){let i=typeof t=="string"?{entity:t}:{...t||{}},n=this.buttonDefaults(i),s={...n,...i,states:Object.assign(Object.create(null),n.states)};for(let[c,l]of Object.entries(i.states??{}))s.states[c]={...s.states[c],...l},l.tap_action&&(s.states[c].tap_action=this.normalizeLovelaceAction(l.tap_action));let a=s.title||this.defaultButtonTitle(s)||"",o=this.normalizeLovelaceAction(i.tap_action||n.tap_action||i);return{id:e,entity:i.entity||n.entity,title:a,icon:s.icon||"mdi:circle",color:s.color||"#f3f7ff",background:s.background,hover_background:s.hover_background,border:s.border,audio:!!(s.audio??this.isAudioAction(o)),disabled:s.disabled??!1,hidden:s.hidden??!1,player:s.player||this.playerFromAction(o),states:s.states||{},success_status:s.success_status,cooldown:s.cooldown??700,tap_action:o}}buttonDefaults(t){if(t.sound)return this.soundButtonDefaults(t);if(t.tts||t.message)return this.ttsButtonDefaults(t);let e=this.entityDomain(t.entity);return e==="light"?this.lightButtonDefaults(t):e==="cover"?this.coverButtonDefaults(t):this.appearanceDefaults(t.appearance)}soundButtonDefaults(t){let e=t.player||this.config.player||this.defaultPlayerEntity(t.entity);return e?{...this.appearanceDefaults(t.appearance||"alert"),audio:!0,player:e,icon:t.icon||"mdi:bullhorn",tap_action:{action:"perform-action",perform_action:"media_player.play_media",target:{entity_id:e},data:{media_content_type:"music",media_content_id:this.resolveMediaPath(t.sound)}}}:this.appearanceDefaults(t.appearance||"alert")}ttsButtonDefaults(t){let e=t.player||this.config.player||this.defaultPlayerEntity(t.entity),i=t.tts||t.message,n=t.tts_entity||this.config.tts_entity||A;return!e||!i||!n?this.appearanceDefaults(t.appearance||"alert"):{...this.appearanceDefaults(t.appearance||"alert"),audio:!0,player:e,icon:t.icon||"mdi:message-alert",success_status:t.success_status||"Playing message",tap_action:{action:"perform-action",perform_action:"tts.speak",target:{entity_id:n},data:{media_player_entity_id:e,message:i,cache:t.cache??!0}}}}lightButtonDefaults(t){let e=t.title||this.defaultButtonTitle(t)||"Light";return{...this.appearanceDefaults("light"),icon:t.icon||"mdi:lightbulb-off-outline",tap_action:this.performActionConfig("light.toggle",t.entity),states:{on:{...this.appearanceDefaults("light_on"),title:`${e} on`,icon:"mdi:lightbulb-on"},off:{...this.appearanceDefaults("light"),title:`${e} off`,icon:t.icon||"mdi:lightbulb-off-outline"},unavailable:{...this.appearanceDefaults("disabled"),title:`${e} unavailable`,icon:"mdi:lightbulb-alert",disabled:!0},unknown:{...this.appearanceDefaults("disabled"),title:`${e} unknown`,icon:"mdi:lightbulb-alert",disabled:!0}}}}coverButtonDefaults(t){let e=t.title||this.defaultButtonTitle(t)||"Cover",i=t.icon||this.defaultCoverIcon(t,!1),n=this.defaultCoverIcon(t,!0),s=t.appearance||"primary";return{...this.appearanceDefaults(s),icon:i,states:{closed:{...this.appearanceDefaults(s),title:`Open ${e}`,icon:i,success_status:`Opening ${e}`,tap_action:this.performActionConfig("cover.open_cover",t.entity)},open:{...this.appearanceDefaults(s),title:`Close ${e}`,icon:n,success_status:`Closing ${e}`,tap_action:this.performActionConfig("cover.close_cover",t.entity)},opening:{...this.appearanceDefaults("warning"),title:`${e} opening`,icon:n,disabled:!0},closing:{...this.appearanceDefaults("warning"),title:`${e} closing`,icon:i,disabled:!0},unavailable:{...this.appearanceDefaults("disabled"),title:`${e} unavailable`,icon:"mdi:alert-circle-outline",disabled:!0},unknown:{...this.appearanceDefaults("disabled"),title:`${e} unknown`,icon:"mdi:alert-circle-outline",disabled:!0}}}}appearanceDefaults(t){return{...B[t]||{}}}performActionConfig(t,e){if(e)return{action:"perform-action",perform_action:t,target:{entity_id:e}}}resolveMediaPath(t){return!t||t.startsWith("/")||t.includes("://")?t:x+t}defaultPlayerEntity(t){if(this.entityDomain(t)==="media_player")return t;let e=this.config.stream;if(!(typeof e!="string"||!/^[a-z0-9_]+$/i.test(e)))return`media_player.${e.toLowerCase()}`}defaultCoverIcon(t,e){return`${t.title||""} ${t.entity||""}`.toLowerCase().includes("gate")?e?"mdi:gate-open":"mdi:gate":e?"mdi:garage-open":"mdi:garage"}entityDomain(t){return typeof t=="string"?t.split(".",1)[0]:void 0}defaultButtonTitle(t){let e=t.entity||"",i=e.includes(".")?e.split(".")[1]:"";return i?i.replace(/_/g," ").replace(/\b\w/g,n=>n.toUpperCase()):""}normalizeLovelaceAction(t){if(t)return t.tap_action?this.normalizeLovelaceAction(t.tap_action):t.action?{...t}:t.perform_action?{action:"perform-action",...t}:t.entity?{action:"more-info",entity:t.entity}:{action:"none"}}isAudioAction(t){let e=t?.perform_action||t?.service;return e==="media_player.play_media"||e==="tts.speak"}playerFromAction(t){if(!this.isAudioAction(t))return;let e=t?.perform_action||t?.service,i=t.data||t.service_data,n=e==="tts.speak"?i?.media_player_entity_id:t?.target?.entity_id||i?.entity_id||t?.entity,s=this.asArray(n).filter(a=>this.entityDomain(a)==="media_player");if(s.length)return s.length===1?s[0]:s}asArray(t){return Array.isArray(t)?t:t?[t]:[]}renderActionButtons(){this.elements.clear(),this.renderButtonGroup(".left-buttons",this.leftButtons),this.renderButtonGroup(".right-buttons",this.rightButtons)}renderButtonGroup(t,e){this.card.$(t).replaceChildren(...e.map(n=>this.createActionButton(n)))}createActionButton(t){let e=document.createElement("button");this.elements.set(t.id,e),e.type="button",e.dataset.buttonId=t.id,e.title=t.title,e.setAttribute("aria-label",t.title||t.id);let i=document.createElement("ha-icon");return i.setAttribute("aria-hidden","true"),i.setAttribute("icon",t.icon),e.appendChild(i),this.applyStateToButton(e,t),this.bindActionButton(e,t),e}bindActionButton(t,e){t.addEventListener("click",i=>{i.preventDefault(),!t.disabled&&this.dispatchButtonAction(t,e)})}updateStatefulButtons(t){this.card.shadowRoot&&[...this.leftButtons,...this.rightButtons].forEach(e=>{if(t&&e.dependencies.every(n=>t.states?.[n]===this.hass?.states?.[n]))return;let i=this.elements.get(e.id);i&&this.applyStateToButton(i,e)})}applyStateToButton(t,e){let i=this.entityStateForButton(e),n=this.effectiveStateConfigForButton(e,i),s=this.buttonVisualConfig(e,n),a=t.querySelector("ha-icon");s.icon&&a&&a.setAttribute("icon",s.icon),t.title=s.title||e.id,t.setAttribute("aria-label",s.title||e.id),this.setOptionalVar(t,"--button-color",s.color),this.setOptionalVar(t,"--button-background",s.background),this.setOptionalVar(t,"--button-hover-background",s.hover_background),this.setOptionalVar(t,"--button-border",s.border),i?t.dataset.entityState=i:delete t.dataset.entityState,t.hidden=this.buttonHidden(n),this.syncButtonDisabled(t,e,i,n)}buttonVisualConfig(t,e={}){return{title:e.title??t.title,icon:e.icon??t.icon,color:e.color??t.color,background:e.background??t.background,hover_background:e.hover_background??t.hover_background,border:e.border??t.border}}entityStateForButton(t){if(t.entity)return this.hass?.states&&this.hass.states[t.entity]?.state||"unavailable"}stateConfigForButton(t,e){return e?this.isUnavailableState(e)?t.states?.[e]||t.states?.unavailable||this.unavailableButtonConfig(t,e):t.states?t.states[e]||t.states.default||{}:{}:{}}effectiveStateConfigForButton(t,e=this.entityStateForButton(t)){let i=this.stateConfigForButton(t,e);return{disabled:t.disabled,hidden:t.hidden,...i,...this.audioPlayerAvailabilityConfig(t,i)}}audioPlayerAvailabilityConfig(t,e={}){let i=e.tap_action||t.tap_action;if(!t.audio&&!this.isAudioAction(i))return{};let n=this.playerFromAction(i)||t.player,s=this.asArray(n).filter(c=>this.entityDomain(c)==="media_player");if(!s.length)return this.unavailableButtonConfig(t,"unavailable","Audio target unavailable","mdi:speaker-off");if(!this.hass?.states)return this.unavailableButtonConfig(t,"unavailable",`${t.title||"Audio"} unavailable`,"mdi:speaker-off");let a=s.find(c=>!this.entityAvailable(c));if(!a)return{};let o=`${t.title||this.defaultButtonTitle({entity:a})||"Audio"} unavailable`;return this.unavailableButtonConfig(t,"unavailable",o,"mdi:speaker-off")}unavailableButtonConfig(t,e="unavailable",i,n="mdi:alert-circle-outline"){let s=t.title||this.defaultButtonTitle(t)||"Button";return{...this.appearanceDefaults("disabled"),title:i||`${s} ${e}`,icon:n,disabled:!0}}isUnavailableState(t){return E.has(t)}entityAvailable(t){let e=this.hass?.states?.[t]?.state;return!!e&&!this.isUnavailableState(e)}buttonHidden(t){return!!t.hidden}syncButtonDisabled(t,e,i=this.entityStateForButton(e),n){let s=n||this.effectiveStateConfigForButton(e,i),a=!!s.disabled,o=s.tap_action||e.tap_action,c=this.talking&&(e.audio||this.isAudioAction(o)),l=!o||o.action==="none";t.disabled=this.pendingActionIds.has(e.id)||a||c||l,t.classList.toggle("state-disabled",a),t.classList.toggle("audio-disabled",c),t.classList.toggle("action-disabled",l),c&&(t.title="Hang up before playing audio",t.setAttribute("aria-label",t.title))}setOptionalVar(t,e,i){i!=null?t.style.setProperty(e,i):t.style.removeProperty(e)}buttonPlayers(t){let e=[t.tap_action,...Object.values(t.states).map(i=>i.tap_action)];return[...new Set([...this.asArray(t.player),...e.flatMap(i=>this.asArray(this.playerFromAction(i)))])]}audioPlayers(){let t=[this.config?.player,...[...this.leftButtons,...this.rightButtons].flatMap(e=>this.buttonPlayers(e))].flatMap(e=>this.asArray(e));return[...new Set(t.filter(e=>this.entityDomain(e)==="media_player"&&this.entityAvailable(e)))]}dispatchButtonAction(t,e){if(!t.isConnected||!this.card.shadowRoot?.contains(t)||this.pendingActionIds.has(e.id))return;let i=this.entityStateForButton(e),n=this.effectiveStateConfigForButton(e,i);if(this.buttonHidden(n)||n.disabled)return;let s=n.tap_action||e.tap_action;if(!s||s.action==="none"||this.talking&&(e.audio||this.isAudioAction(s)))return;this.pendingActionIds.add(e.id),t.disabled=!0;let a=setTimeout(()=>{this.cooldowns.delete(a),this.pendingActionIds.delete(e.id),this.updateStatefulButtons()},e.cooldown);this.cooldowns.add(a),s.action==="fire-dom-event"?this.fireDomEvent(s,e):this.fireHassAction(e,n);let o=n.success_status??e.success_status;o&&this.card.showStatus(o,1200)}fireHassAction(t,e){let i=new CustomEvent("hass-action",{bubbles:!0,composed:!0,detail:{action:"tap",config:this.actionHandlerConfig(t,e)}});this.card.dispatchEvent(i)}actionHandlerConfig(t,e={}){return{entity:t.entity,tap_action:e.tap_action||t.tap_action}}fireDomEvent(t,e){let i=t.event_type||"intercom-camera-card-action",{event_data:n,...s}=t;this.card.dispatchEvent(new CustomEvent(i,{bubbles:!0,composed:!0,detail:{...s,...n||{},config:this.config,button:e}}))}};var b=class{constructor(t){this.card=t,this.videoPanX=d,this.mobilePanEnabled=!0,this.handlePanPointerDown=e=>this.onPanPointerDown(e),this.handlePanPointerMove=e=>this.onPanPointerMove(e),this.handlePanPointerEnd=e=>this.onPanPointerEnd(e),this.handleResize=()=>this.updatePanAvailability()}get config(){return this.card.config}get video(){return this.card.video}configure(){this.mobilePanEnabled=this.config.mobile_pan,this.videoPanX=this.loadVideoPanX()}bindPanControls(){let t=this.card.$(".video-wrap");t.addEventListener("pointerdown",this.handlePanPointerDown,{passive:!1}),t.addEventListener("pointermove",this.handlePanPointerMove,{passive:!1}),t.addEventListener("pointerup",this.handlePanPointerEnd,{passive:!1}),t.addEventListener("pointercancel",this.handlePanPointerEnd,{passive:!1}),t.addEventListener("lostpointercapture",this.handlePanPointerEnd,{passive:!1}),this.installPanResizeObserver()}installPanResizeObserver(){if(!this.card.shadowRoot||this.resizeObserver||this.panResizeHandlerInstalled)return;let t=this.card.$(".video-wrap");t&&("ResizeObserver"in window?(this.resizeObserver=new ResizeObserver(this.handleResize),this.resizeObserver.observe(t)):(window.addEventListener("resize",this.handleResize,{passive:!0}),this.panResizeHandlerInstalled=!0))}removePanControls(){this.resizeObserver&&(this.resizeObserver.disconnect(),this.resizeObserver=null),this.panResizeHandlerInstalled&&(window.removeEventListener("resize",this.handleResize),this.panResizeHandlerInstalled=!1),this.panGesture=null,this.card.$?.(".stage")?.classList.remove("panning")}applyVideoPan(){this.card.style.setProperty("--intercom-video-pan-x",`${this.videoPanX}%`),this.updatePanAvailability()}canPanVideo(){return this.mobilePanEnabled&&this.card.hasVideoSource()&&this.isMobilePanContext()&&this.videoHorizontalOverflow()>1}isMobilePanContext(){return window.matchMedia?.("(max-width: 680px), (pointer: coarse)")?.matches??!1}videoHorizontalOverflow(){if(!this.video?.videoWidth||!this.video?.videoHeight||!this.card.shadowRoot||getComputedStyle(this.video).objectFit!=="cover")return 0;let t=this.card.$(".video-wrap"),e=t?.clientWidth||0,i=t?.clientHeight||0;if(!e||!i)return 0;let n=Math.max(e/this.video.videoWidth,i/this.video.videoHeight);return Math.max(0,this.video.videoWidth*n-e)}updatePanAvailability(){this.card.shadowRoot&&this.card.$(".stage")?.classList.toggle("pannable",this.canPanVideo())}onPanPointerDown(t){if(!t.isPrimary||t.button!==0||!this.canPanVideo())return;let e=this.card.$(".video-wrap");this.panGesture={pointerId:t.pointerId,startX:t.clientX,startY:t.clientY,startPanX:this.videoPanX,active:!1,overflow:this.videoHorizontalOverflow()};try{e.setPointerCapture(t.pointerId)}catch(i){console.debug(i)}}onPanPointerMove(t){let e=this.panGesture;if(!e||t.pointerId!==e.pointerId)return;let i=t.clientX-e.startX,n=t.clientY-e.startY;if(!e.active){if(Math.abs(i)<8||Math.abs(i)<Math.abs(n)*1.15)return;e.active=!0,this.card.$(".stage")?.classList.add("panning")}t.preventDefault(),t.stopPropagation();let s=e.overflow;s&&(this.videoPanX=this.clamp(e.startPanX-i/s*100,0,100),this.card.style.setProperty("--intercom-video-pan-x",`${this.videoPanX}%`))}onPanPointerEnd(t){let e=this.panGesture;if(!e||t.pointerId!==e.pointerId)return;e.active&&(t.preventDefault?.(),t.stopPropagation?.(),this.saveVideoPanX()),this.card.$(".stage")?.classList.remove("panning"),this.panGesture=null;let i=this.card.$(".video-wrap");i.hasPointerCapture?.(t.pointerId)&&i.releasePointerCapture(t.pointerId)}panPreferenceKey(){let t=this.card.useAlternateStream()?this.config?.alternate_stream:this.config?.stream||this.config?.url||this.config?.entity||"";return t?`${T}${encodeURIComponent(t)}`:""}loadVideoPanX(){let t=this.panPreferenceKey();if(!t)return d;try{let e=localStorage.getItem(t);if(e===null||e.trim()==="")return d;let i=Number(e);return Number.isFinite(i)?this.clamp(i,0,100):d}catch(e){return console.debug(e),d}}saveVideoPanX(){let t=this.panPreferenceKey();if(t)try{localStorage.setItem(t,String(Math.round(this.videoPanX)))}catch(e){console.debug(e)}}clamp(t,e,i){return Math.min(i,Math.max(e,t))}};var P=`
<style>
:host {
display: block;
--intercom-height: calc(100dvh - 96px);
--intercom-height-mobile: calc(100dvh - 72px);
--intercom-fit: cover;
--intercom-shade-background: linear-gradient(to top, rgba(5, 11, 20, 0.68), rgba(5, 11, 20, 0.20) 56%, rgba(0, 0, 0, 0));
--intercom-controls-gap: 18px;
--intercom-controls-width: 420px;
--intercom-button-size: 68px;
--intercom-button-size-mobile: 56px;
--intercom-icon-size: 30px;
--intercom-button-background: rgba(9, 18, 32, 0.72);
--intercom-button-hover-background: rgba(18, 35, 58, 0.84);
--intercom-button-border: 1px solid rgba(255, 255, 255, 0.22);
--intercom-button-shadow: 0 10px 28px rgba(0, 0, 0, 0.38), inset 0 1px 0 rgba(255, 255, 255, 0.14);
--intercom-icon-shadow: drop-shadow(0 2px 8px rgba(0, 0, 0, 0.42));
--intercom-status-background: rgba(5, 11, 20, 0.72);
height: 100%;
}
ha-card {
display: block;
position: relative;
height: 100%;
min-height: 328px;
overflow: hidden;
border-radius: var(--ha-card-border-radius, 12px);
background: #050505;
box-shadow: none;
}
:host([panel]) ha-card {
height: var(--intercom-height);
}
.stage {
min-height: inherit;
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
background: #050505;
isolation: isolate;
container-type: inline-size;
}
.video-wrap {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
touch-action: auto;
}
video {
display: block;
width: 100%;
height: 100%;
object-fit: var(--intercom-fit);
object-position: var(--intercom-video-pan-x, 50%) center;
background: #050505;
}
.stage.pannable video {
cursor: grab;
}
.stage.pannable .video-wrap {
touch-action: pan-y;
}
.stage.panning video {
cursor: grabbing;
}
.shade {
position: absolute;
inset: auto 0 0 0;
height: 38%;
pointer-events: none;
background: var(--intercom-shade-background);
z-index: 1;
}
.controls {
position: absolute;
left: 50%;
bottom: max(clamp(18px, 4vh, 34px), calc(env(safe-area-inset-bottom) + 12px));
z-index: 2;
box-sizing: border-box;
display: flex;
flex-wrap: wrap;
align-items: center;
align-content: center;
justify-content: center;
row-gap: var(--intercom-controls-gap);
column-gap: var(--intercom-controls-gap);
transform: translateX(-50%);
width: min(var(--intercom-controls-width), calc(100% - 24px));
max-width: min(92%, 680px);
overflow: visible;
}
.button-group {
display: contents;
}
button {
appearance: none;
border: var(--button-border, var(--intercom-button-border));
border-radius: 999px;
color: var(--button-color, var(--intercom-button-color, #fff));
background: var(--button-background, var(--intercom-button-background));
box-shadow: var(--button-shadow, var(--intercom-button-shadow));
box-sizing: border-box;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
width: var(--button-size, var(--intercom-button-size));
height: var(--button-size, var(--intercom-button-size));
aspect-ratio: 1 / 1;
flex: 0 0 var(--button-size, var(--intercom-button-size));
line-height: 0;
overflow: hidden;
padding: 0;
position: relative;
transition:
background 150ms ease,
border-color 150ms ease,
color 150ms ease,
opacity 150ms ease,
transform 150ms ease;
}
button:hover {
background: var(--button-hover-background, var(--intercom-button-hover-background));
}
button:active {
transform: scale(0.94);
}
button:focus-visible {
outline: 2px solid rgba(116, 184, 255, 0.86);
outline-offset: 3px;
}
button[hidden] {
display: none;
}
button[disabled] {
cursor: wait;
opacity: 0.48;
}
button.audio-disabled[disabled] {
cursor: not-allowed;
opacity: 0.38;
}
button.stream-toggle {
position: absolute;
top: 14px;
right: 14px;
z-index: 2;
width: auto;
height: 44px;
min-width: 56px;
flex-basis: auto;
aspect-ratio: auto;
padding: 0 13px;
color: rgba(255, 255, 255, 0.94);
background: rgba(5, 11, 20, 0.56);
border: 1px solid rgba(255, 255, 255, 0.22);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.28);
font: 700 12px/1 var(--paper-font-body1_-_font-family, sans-serif);
letter-spacing: 0;
text-transform: uppercase;
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
}
button.stream-toggle:hover {
background: rgba(18, 35, 58, 0.72);
}
button.stream-toggle[disabled] {
cursor: not-allowed;
}
button::before {
content: "";
position: absolute;
inset: 0;
border-radius: inherit;
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.04);
pointer-events: none;
}
ha-icon {
--mdc-icon-size: var(--button-icon-size, var(--intercom-icon-size));
position: absolute;
top: 50%;
left: 50%;
display: flex;
align-items: center;
box-sizing: border-box;
color: currentColor;
flex: 0 0 auto;
justify-content: center;
width: var(--button-icon-size, var(--intercom-icon-size));
height: var(--button-icon-size, var(--intercom-icon-size));
line-height: 1;
margin: 0;
pointer-events: none;
transform: translate(-50%, -50%);
filter: var(--intercom-icon-shadow);
}
.talk.active {
color: var(--talk-active-color);
background: var(--talk-active-background, var(--button-background, var(--intercom-button-background)));
border: var(--talk-active-border, var(--button-border, var(--intercom-button-border)));
}
.talk.busy {
color: var(--talk-busy-color);
background: var(--talk-busy-background, var(--button-background, var(--intercom-button-background)));
border: var(--talk-busy-border, var(--button-border, var(--intercom-button-border)));
}
.talk.active ha-icon {
filter: drop-shadow(0 2px 8px rgba(0, 0, 0, 0.34));
}
.status {
position: absolute;
top: 14px;
left: 14px;
z-index: 2;
box-sizing: border-box;
min-width: 74px;
max-width: min(calc(100% - 100px), 420px);
padding: 9px 14px;
border-radius: 999px;
color: rgba(255, 255, 255, 0.94);
background: var(--intercom-status-background);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.28);
font: 600 13px/1.25 var(--paper-font-body1_-_font-family, sans-serif);
overflow: hidden;
text-overflow: ellipsis;
white-space: normal;
overflow-wrap: anywhere;
text-align: center;
opacity: 0;
transition: opacity 150ms ease;
pointer-events: none;
}
.status.visible {
opacity: 1;
}
@media (max-width: 680px) {
:host([panel]) ha-card {
height: var(--intercom-height-mobile);
min-height: 360px;
border-radius: 0;
}
}
@container (max-width: 680px) {
.controls {
left: max(8px, env(safe-area-inset-left));
right: max(8px, env(safe-area-inset-right));
display: grid;
grid-template-columns: repeat(auto-fit, minmax(var(--button-size-mobile, var(--intercom-button-size-mobile)), var(--button-size-mobile, var(--intercom-button-size-mobile))));
justify-content: center;
justify-items: center;
row-gap: 9px;
column-gap: 10px;
width: auto;
max-width: none;
transform: none;
}
button {
width: var(--button-size-mobile, var(--intercom-button-size-mobile));
height: var(--button-size-mobile, var(--intercom-button-size-mobile));
flex: none;
}
button.stream-toggle {
top: 12px;
right: 10px;
height: 44px;
min-width: 52px;
padding: 0 11px;
font-size: 11px;
}
}
@container (max-width: 380px) {
.controls {
row-gap: 8px;
column-gap: 8px;
grid-template-columns: repeat(auto-fit, minmax(52px, 52px));
}
button {
width: 52px;
height: 52px;
flex-basis: 52px;
--button-icon-size: 27px;
}
}
@media (prefers-reduced-motion: reduce) {
button, .status { transition: none; }
}
</style>
<ha-card>
<div class="stage">
<div class="video-wrap"></div>
<div class="shade"></div>
<div class="status" role="status" aria-live="polite" aria-atomic="true"></div>
<button class="stream-toggle" type="button" hidden></button>
<div class="controls">
<div class="button-group left-buttons"></div>
<button class="talk" type="button"></button>
<div class="button-group right-buttons"></div>
</div>
</div>
</ha-card>
`;var h=r=>r!==null&&typeof r=="object"&&!Array.isArray(r);function C(r){if(!h(r))throw new Error("Card configuration must be an object.");let t=structuredClone(r);for(let e of["stream","url","entity","alternate_stream","server","primary_label","alternate_label","tts_entity"]){if(t[e]!==void 0&&typeof t[e]!="string")throw new Error(`${e} must be a string.`);typeof t[e]=="string"&&(t[e]=t[e].trim())}if(!t.stream&&!t.url&&!t.entity)throw new Error("Set a camera entity, go2rtc stream, or URL.");if(t.entity&&!/^camera\.[\w]+$/.test(t.entity))throw new Error("entity must be a camera entity.");for(let e of["mobile_pan","pan"])if(t[e]!==void 0&&typeof t[e]!="boolean")throw new Error(`${e} must be true or false.`);if(t.talk!==void 0&&typeof t.talk!="boolean"&&!h(t.talk))throw new Error("talk must be true, false, or an object.");if(h(t.talk))for(let[e,i]of Object.entries(t.talk)){if(e==="enabled"&&typeof i!="boolean")throw new Error("talk.enabled must be true or false.");if(e in u&&e!=="enabled"&&typeof i!="string")throw new Error(`talk.${e} must be a string.`)}if(L(t.player,"player"),t.buttons!==void 0&&!Array.isArray(t.buttons))throw new Error("buttons must be a list.");return t.buttons=(t.buttons??[]).map((e,i)=>{let n=typeof e=="string"?{entity:e}:e,s=`buttons[${i}]`;if(!h(n))throw new Error(`${s} must be an entity ID or an object.`);for(let a of["entity","sound","tts","message","tts_entity"])if(n[a]!==void 0&&typeof n[a]!="string")throw new Error(`${s}.${a} must be a string.`);if(n.position!==void 0&&!["left","right"].includes(n.position))throw new Error(`${s}.position must be left or right.`);if(n.cooldown!==void 0&&(!Number.isFinite(n.cooldown)||n.cooldown<0))throw new Error(`${s}.cooldown must be a non-negative number.`);if(n.states!==void 0&&(!h(n.states)||Object.values(n.states).some(a=>!h(a))))throw new Error(`${s}.states must contain state objects.`);for(let[a,o]of[[s,n],...Object.entries(n.states??{}).map(([c,l])=>[`${s}.states.${c}`,l])]){for(let c of["disabled","hidden"])if(o[c]!==void 0&&typeof o[c]!="boolean")throw new Error(`${a}.${c} must be true or false.`);for(let c of["title","icon","color","background","hover_background","border","success_status"])if(o[c]!==void 0&&typeof o[c]!="string")throw new Error(`${a}.${c} must be a string.`);if(o.tap_action!==void 0&&!h(o.tap_action))throw new Error(`${a}.tap_action must be an object.`)}return L(n.player,`${s}.player`),n}),t.mobile_pan=t.mobile_pan!==!1&&t.pan!==!1,t}function L(r,t){if(r!==void 0&&!(Array.isArray(r)?r:[r]).every(e=>typeof e=="string"&&/^media_player\.[\w]+$/.test(e)))throw new Error(`${t} must contain media_player entity IDs.`)}function g(r){return JSON.stringify(["stream","url","entity","alternate_stream","server"].map(t=>r?.[t]||""))}function v(r){return{...u,...h(r.talk)?r.talk:{},...r.talk===!1?{enabled:!1}:{}}}function D(){return{schema:[{name:"entity",selector:{entity:{domain:"camera"}}},...["stream","alternate_stream","primary_label","alternate_label","server"].map(r=>({name:r,selector:{text:{}}})),{name:"player",selector:{entity:{domain:"media_player",multiple:!0}}},{name:"tts_entity",selector:{entity:{domain:"tts"}}},{name:"talk",selector:{boolean:{}}},{name:"mobile_pan",selector:{boolean:{}}}],computeLabel:({name:r})=>({stream:"go2rtc stream",alternate_stream:"Alternate stream",primary_label:"Primary label",alternate_label:"Alternate label",server:"go2rtc server",player:"Audio players",tts_entity:"Text-to-speech provider",talk:"Talk button",mobile_pan:"Drag to pan on mobile"})[r],computeHelper:({name:r})=>r==="entity"?"Choose a camera entity, or leave this empty and enter a go2rtc stream below.":r==="talk"?"Enabled by default. Configure action buttons and custom talk labels in YAML.":void 0,assertConfig:r=>{if(h(r.talk)||typeof r.player=="string")throw new Error("Use YAML to preserve custom talk settings or a single-string player target.")}}}var y=class extends p{constructor(){super(),this._hass=null,this.config=null,this.pan=new b(this),this.buttons=new f(this),this.talkButton={...u},this.statusHoldUntil=0,this.statusTimeout=0}setConfig(t){let e=C(t),i=g(this.config)!==g(e),n=this.talking&&!v(e).enabled;(i||n)&&this.disconnectImmediately(),this.config=e,i&&(this.streamVariant=this.loadStreamVariant()),this.pan.configure(),this.talkButton=v(e),this.media=this.mediaForTalkState(),this.buttons.configure(),this.video&&this.applyConfigToDom(),(i||n)&&this.onconnect()}set hass(t){let e=this._hass;this._hass=t,this.isConnected&&this.buttons.updateStatefulButtons(e),e||this.onconnect()}get hass(){return this._hass}getCardSize(){return Math.ceil((this.getBoundingClientRect().height||400)/50)}static getConfigForm(){return D()}static getStubConfig(t){let e=Object.keys(t?.states??{}).find(i=>i.startsWith("camera."));return e?{entity:e,talk:!0,mobile_pan:!0}:{stream:"front_door",talk:!0,mobile_pan:!0}}set layout(t){this.toggleAttribute("panel",t==="panel")}getGridOptions(){return{columns:12,min_columns:6,rows:8,min_rows:6}}connectedCallback(){super.connectedCallback(),this.installVisibilityHandlers(),this.pan.installPanResizeObserver(),this.buttons.updateStatefulButtons()}disconnectedCallback(){this.removeVisibilityHandlers(),this.pan.removePanControls(),this.disconnectImmediately(),this.buttons.dispose()}oninit(){this.video=document.createElement("video"),this.video.controls=!1,this.video.playsInline=!0,this.video.preload="auto",this.video.volume=1;let t=this.attachShadow({mode:"open"});t.innerHTML=P,this.$=e=>this.shadowRoot.querySelector(e),this.$(".video-wrap").appendChild(this.video),this.applyConfigToDom(),this.bindControls(),this.pan.bindPanControls(),this.bindVideoLifecycle(),this.installVisibilityHandlers()}bindControls(){this.$(".stage").addEventListener("pointerup",()=>this.enableAudio(),{passive:!0}),this.$(".talk").addEventListener("click",()=>this.toggleTalk()),this.$(".stream-toggle").addEventListener("click",()=>this.toggleStreamVariant())}enableAudio(){this.video&&(this.video.muted=!1,this.video.volume=1,this.play())}bindVideoLifecycle(){this.video.addEventListener("error",()=>{(this.ws||this.pc)&&this.connectionFailed(this.video.error,this.connectionGeneration)}),this.video.addEventListener("playing",()=>{this.clearPoster(),this.setTalkBusy(!1)}),this.video.addEventListener("loadedmetadata",()=>this.pan.updatePanAvailability()),this.video.addEventListener("loadeddata",()=>this.pan.updatePanAvailability()),this.video.addEventListener("playing",()=>this.pan.updatePanAvailability())}applyConfigToDom(){!this.shadowRoot||!this.config||(this.buttons.renderActionButtons(),this.updateStreamToggle(),this.updateTalkButton(),this.buttons.updateStatefulButtons(),this.pan.applyVideoPan(),this.hasVideoSource()||this.showStatus("Camera source unavailable"))}updateStreamToggle(){if(!this.shadowRoot)return;let t=this.$(".stream-toggle");if(t.hidden=!this.hasAlternateStream(),t.hidden)return;let e=this.useAlternateStream()?"primary":"alternate",i=this.streamLabel(),n=this.streamLabel(e),s=this.talking?"Hang up before switching stream":`Switch to ${n}`;t.textContent=i,t.title=s,t.disabled=this.talking,t.setAttribute("aria-label",s),t.setAttribute("aria-pressed",this.useAlternateStream()?"true":"false")}toggleStreamVariant(){!this.hasAlternateStream()||this.talking||(this.streamVariant=this.useAlternateStream()?"primary":"alternate",this.saveStreamVariant(),this.pan.videoPanX=this.pan.loadVideoPanX(),this.pan.applyVideoPan(),this.updateStreamToggle(),this.isConnected&&this.reconnectKeepingFrame(`Switching to ${this.streamLabel()}`,!1))}updateTalkButton(){if(!this.shadowRoot)return;let t=this.$(".talk");if(t.hidden=!this.talkButton.enabled,!this.talkButton.enabled)return;let e=t.querySelector("ha-icon")||document.createElement("ha-icon");e.parentElement||t.appendChild(e),e.setAttribute("aria-hidden","true");let i=this.talking?this.talkButton.active_title:this.talkButton.title,n=this.talking?this.talkButton.active_icon:this.talkButton.icon,s=this.talking?this.talkButton.active_color:this.talkButton.color,a=!this.hasVideoSource(),o=a?"Camera source unavailable":i;t.classList.toggle("active",this.talking),t.classList.toggle("source-disabled",a),t.disabled=a||t.classList.contains("busy")&&!this.talking,t.title=o,t.setAttribute("aria-label",o),t.setAttribute("aria-pressed",this.talking?"true":"false"),e.setAttribute("icon",n),t.style.setProperty("--button-color",s),t.style.setProperty("--talk-active-color",this.talkButton.active_color),t.style.setProperty("--talk-busy-color",this.talkButton.busy_color),this.buttons.setOptionalVar(t,"--button-background",this.talkButton.background),this.buttons.setOptionalVar(t,"--button-hover-background",this.talkButton.hover_background),this.buttons.setOptionalVar(t,"--button-border",this.talkButton.border),this.buttons.setOptionalVar(t,"--talk-active-background",this.talkButton.active_background),this.buttons.setOptionalVar(t,"--talk-active-border",this.talkButton.active_border),this.buttons.setOptionalVar(t,"--talk-busy-background",this.talkButton.busy_background),this.buttons.setOptionalVar(t,"--talk-busy-border",this.talkButton.busy_border)}setTalkBusy(t,e){if(!this.shadowRoot)return;let i=this.$(".talk"),n=!this.hasVideoSource();i.disabled=!!t&&!this.talking||n,i.classList.toggle("busy",!!t),i.classList.toggle("source-disabled",n),i.setAttribute("aria-busy",t?"true":"false"),n&&!t&&(i.title="Camera source unavailable",i.setAttribute("aria-label",i.title)),e?this.showStatus(e):!t&&!this.talking&&Date.now()>=this.statusHoldUntil&&this.showStatus("",0)}showStatus(t,e=0){if(!this.shadowRoot)return;let i=this.$(".status");i.textContent=t||"",i.classList.toggle("visible",!!t),this.statusTimeout&&(clearTimeout(this.statusTimeout),this.statusTimeout=0),t&&e?(this.statusHoldUntil=Date.now()+e,this.statusTimeout=setTimeout(()=>this.showStatus("",0),e)):e||(this.statusHoldUntil=0)}};customElements.get("intercom-camera-card")||customElements.define("intercom-camera-card",y);window.customCards=window.customCards||[];window.customCards.some(r=>r.type==="intercom-camera-card")||window.customCards.push({type:"intercom-camera-card",name:"Intercom Camera Card",preview:!1,description:"A configurable intercom camera card with talk-back controls.",documentationURL:"https://github.com/alphasixtyfive/intercom-camera-card"});export{y as IntercomCameraCard};