diff --git a/docs/configuration/live.md b/docs/configuration/live.md index 0486e2b6..72c7ea61 100644 --- a/docs/configuration/live.md +++ b/docs/configuration/live.md @@ -55,12 +55,13 @@ live: # [...] ``` -| Option | Default | Description | -| ---------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `button_size` | `40` | The size of the call control buttons in pixels. Must be >= `20`. | -| `lock` | `true` | Whether to lock the rest of the card controls/actions while a call is in progress. Prevents an accidental tap, swipe or action mid-call. Set to `false` to allow interactions regardless of call state. | -| `ringtone` | | The audible chime played while an inbound call (e.g. one started by [`view.triggers.actions.trigger: call`](./view.md?id=trigger-action-configuration)) is ringing. Stops as soon as the call is answered or ended. Manual calls never ring. See [`ringtone`](#ringtone). | -| `unanswered_timeout_seconds` | `60` | The number of seconds an inbound call may ring unanswered before it is automatically ended. The timer is cancelled the moment the call is answered. Set to `0` to disable the timeout. | +| Option | Default | Description | +| ---------------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `button_size` | `40` | The size of the call control buttons in pixels. Must be >= `20`. | +| `enabled` | `true` | Whether the on-screen call controls are shown during a call. Set to `false` to hide them and drive calls from the [`menu`](./menu.md) instead. When hiding the controls, be sure to also remove `call` from `menu.auto_hide` so the menu stays visible allowing the call to be controlled. See [Driving calls from the menu](../examples.md?id=driving-calls-from-the-menu) for a complete example. | +| `lock` | `true` | Whether to lock the rest of the card controls/actions while a call is in progress. Prevents an accidental tap, swipe or action mid-call. Set to `false` to allow interactions regardless of call state. | +| `ringtone` | | The audible chime played while an inbound call (e.g. one started by [`view.triggers.actions.trigger: call`](./view.md?id=trigger-action-configuration)) is ringing. Stops as soon as the call is answered or ended. Manual calls never ring. See [`ringtone`](#ringtone). | +| `unanswered_timeout_seconds` | `60` | The number of seconds an inbound call may ring unanswered before it is automatically ended. The timer is cancelled the moment the call is answered. Set to `0` to disable the timeout. | > [!NOTE] Browser autoplay restrictions may prevent the ringtone from playing > until the page has received a user gesture (e.g. a tap or click). When that diff --git a/docs/examples.md b/docs/examples.md index deee6baf..d7bc6aa6 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -563,6 +563,64 @@ profiles: > the view-only camera to `live_provider: ha` (HLS / receive-only WebRTC, which > never offers a backchannel) instead. +### Driving calls from the menu + +By default on-screen controls will appear mid-card to handle a call. Setting +[`live.controls.call.enabled: false`](configuration/live.md?id=call) hides those +controls so the call can be driven via some other mechanism. In this example, +the card is configured to allow calls to be driven from the menu instead. + +This wires up the menu equivalents of every overlay control. The menu `call` +button starts, ends, and (while ringing) rejects calls, but it cannot _answer_ +an inbound ring -- so a conditional answer button is added that appears only +while ringing. + +```yaml +type: custom:advanced-camera-card +cameras: + - camera_entity: camera.front_door + live_provider: go2rtc + go2rtc: + modes: + - webrtc +profiles: + - doorbell +live: + controls: + call: + # Hide the on-screen call controls overlay -- the menu drives the call. + enabled: false +menu: + # The menu hides during a call by default; keep it visible so its call + # controls stay reachable. + auto_hide: [] + buttons: + # Starts a call, and becomes a hang-up button for the duration of a call. + call: + enabled: true + # Mutes/unmutes your outbound microphone during a call. + microphone: + enabled: true + type: toggle + # Mutes/unmutes the inbound (caller's) audio during a call. + mute: + enabled: true +elements: + # The menu `call` button cannot answer an inbound (ringing) call, so this + # answer button is shown only while ringing to provide that control. + - type: custom:advanced-camera-card-conditional + conditions: + - condition: call + call: ringing + elements: + - type: custom:advanced-camera-card-menu-icon + icon: mdi:phone + title: Answer call + tap_action: + action: custom:advanced-camera-card-action + advanced_camera_card_action: call_answer +``` + ## Events from other cameras `dependencies.cameras` allows events/recordings for other cameras to be shown diff --git a/src/components-lib/editor/schema/live.ts b/src/components-lib/editor/schema/live.ts index ec482167..4cdf6bb4 100644 --- a/src/components-lib/editor/schema/live.ts +++ b/src/components-lib/editor/schema/live.ts @@ -25,6 +25,7 @@ const getCallSchema = (): HAFormExpandableSchema => ({ title: localize('config.live.controls.call.editor_label'), icon: 'mdi:phone', schema: [ + { name: 'enabled', selector: { boolean: {} } }, { name: 'lock', selector: { boolean: {} } }, { name: 'ringtone', diff --git a/src/components/live/carousel.ts b/src/components/live/carousel.ts index 7089501f..d3c365af 100644 --- a/src/components/live/carousel.ts +++ b/src/components/live/carousel.ts @@ -435,7 +435,8 @@ export class AdvancedCameraCardLiveCarousel extends LitElement { !gesturesPTZActive && !this.locked; - const isCallActive = this.call?.cameraID === carouselCameraID; + const isCallControlsActive = + this.liveConfig.controls.call.enabled && this.call?.cameraID === carouselCameraID; const callMediaPlayerController = this._mediaLoadedInfoSinkController.get()?.mediaPlayerController ?? null; @@ -470,7 +471,7 @@ export class AdvancedCameraCardLiveCarousel extends LitElement { > ; const callConfigDefault = { button_size: 40, + enabled: true, lock: true, ringtone: { ...ringtoneConfigDefault }, unanswered_timeout_seconds: 60, @@ -57,6 +58,7 @@ const callConfigDefault = { const callConfigSchema = z.object({ button_size: z.number().min(BUTTON_SIZE_MIN).default(callConfigDefault.button_size), + enabled: z.boolean().default(callConfigDefault.enabled), lock: z.boolean().default(callConfigDefault.lock), ringtone: ringtoneConfigSchema.default(callConfigDefault.ringtone), diff --git a/src/localize/languages/en.json b/src/localize/languages/en.json index 5595c096..d82b6ea6 100644 --- a/src/localize/languages/en.json +++ b/src/localize/languages/en.json @@ -459,6 +459,7 @@ "answer": "Answer call", "button_size": "Call control button size", "editor_label": "Two-way audio call", + "enabled": "Show on-screen call controls", "end": "End 2-way audio call", "lock": "Lock UI during an active call", "mute_audio": "Mute audio", diff --git a/tests/config/types.test.ts b/tests/config/types.test.ts index 5e2bf35a..ed39ae46 100644 --- a/tests/config/types.test.ts +++ b/tests/config/types.test.ts @@ -99,6 +99,7 @@ describe('config defaults', () => { builtin: true, call: { button_size: 40, + enabled: true, lock: true, ringtone: { type: 'chime', repeat: 0 }, unanswered_timeout_seconds: 60, @@ -1848,6 +1849,20 @@ it('should not require title controls to specify all options', () => { ).toBeTruthy(); }); +it('should allow the on-screen call controls overlay to be disabled', () => { + const config = createConfig({ + cameras: [{}], + live: { + controls: { + call: { + enabled: false, + }, + }, + }, + }); + expect(config.live.controls.call.enabled).toBe(false); +}); + it('should strip trailing slashes from go2rtc url', () => { const config = createConfig({ cameras: [