Implement momentary and toggle microphone button types.

This commit is contained in:
Dermot Duffy
2023-05-06 15:59:57 -07:00
parent 11e1245d67
commit 2c1b8ed785
8 changed files with 91 additions and 25 deletions
+23 -6
View File
@@ -620,6 +620,7 @@ class FrigateCard extends LitElement {
if (this._microphoneController && cameraCapabilities?.supports2WayAudio) {
const muted = this._microphoneController.isMuted();
const buttonType = this._getConfig().menu.buttons.microphone.type;
buttons.push({
icon: this._microphoneController.isForbidden()
? 'mdi:microphone-message-off'
@@ -630,12 +631,21 @@ class FrigateCard extends LitElement {
type: 'custom:frigate-card-menu-icon',
title: localize('config.menu.buttons.microphone'),
style: muted ? {} : this._getEmphasizedStyle(true),
...(buttonType === 'momentary' && {
start_tap_action: createFrigateCardCustomAction(
'microphone_unmute',
) as FrigateCardCustomAction,
end_tap_action: createFrigateCardCustomAction(
'microphone_mute',
) as FrigateCardCustomAction,
}),
...(buttonType === 'toggle' && {
tap_action: createFrigateCardCustomAction(
this._microphoneController.isMuted()
? 'microphone_unmute'
: 'microphone_mute',
) as FrigateCardCustomAction,
}),
});
}
@@ -1577,17 +1587,24 @@ class FrigateCard extends LitElement {
this.requestUpdate();
break;
case 'microphone_unmute':
const unmuteAndUpdate = () => {
this._microphoneController?.unmute();
this.requestUpdate();
};
if (
!this._microphoneController?.isConnected() &&
!this._microphoneController?.isForbidden()
) {
this._microphoneController?.connect().then(unmuteAndUpdate);
// The connect() call is async and make take an arbitrary amount of
// time for the user to grant access to their microphone. With a
// momentary microphone button the mute call (on mouse release) may
// arrive before the connection is even granted, so we unmute first
// before the connection is made, so the mute call on release will not
// be 'overwritten' incorrectly.
this._microphoneController?.unmute();
// Must requestUpdate to show the correct microphone state in the
// menu.
this._microphoneController?.connect().then(() => this.requestUpdate());
} else if (this._microphoneController?.isConnected()) {
unmuteAndUpdate();
this._microphoneController.unmute();
this.requestUpdate();
}
break;
default:
+23 -6
View File
@@ -181,14 +181,14 @@ import {
const MENU_BUTTONS = 'buttons';
const MENU_CAMERAS = 'cameras';
const MENU_CAMERAS_DEPENDENCIES = 'cameras.dependencies';
const MENU_CAMERAS_ENGINE = 'cameras.engine';
const MENU_CAMERAS_FRIGATE = 'cameras.frigate';
const MENU_CAMERAS_GO2RTC = 'cameras.go2rtc';
const MENU_CAMERAS_IMAGE = 'cameras.image';
const MENU_CAMERAS_LIVE_PROVIDER = 'cameras.live_provider';
const MENU_CAMERAS_MOTIONEYE = 'cameras.motioneye';
const MENU_CAMERAS_TRIGGERS = 'cameras.triggers';
const MENU_CAMERAS_WEBRTC_CARD = 'cameras.webrtc_card';
const MENU_CAMERAS_LIVE_PROVIDER = 'cameras.live_provider';
const MENU_CAMERAS_ENGINE = 'cameras.engine';
const MENU_IMAGE_LAYOUT = 'image.layout';
const MENU_LIVE_CONTROLS = 'live.controls';
const MENU_LIVE_CONTROLS_NEXT_PREVIOUS = 'live.controls.next_previous';
@@ -197,18 +197,18 @@ const MENU_LIVE_CONTROLS_TIMELINE = 'live.controls.timeline';
const MENU_LIVE_CONTROLS_TITLE = 'live.controls.title';
const MENU_LIVE_LAYOUT = 'live.layout';
const MENU_LIVE_MICROPHONE = 'live.microphone';
const MENU_MEDIA_GALLERY_CONTROLS_THUMBNAILS = 'media_gallery.controls.thumbnails';
const MENU_MEDIA_GALLERY_CONTROLS_FILTER = 'media_gallery.controls.filter';
const MENU_MEDIA_GALLERY_CONTROLS_THUMBNAILS = 'media_gallery.controls.thumbnails';
const MENU_MEDIA_VIEWER_CONTROLS = 'media_viewer.controls';
const MENU_MEDIA_VIEWER_CONTROLS_NEXT_PREVIOUS = 'media_viewer.controls.next_previous';
const MENU_MEDIA_VIEWER_CONTROLS_THUMBNAILS = 'media_viewer.controls.thumbnails';
const MENU_MEDIA_VIEWER_CONTROLS_TIMELINE = 'media_viewer.controls.timeline';
const MENU_MEDIA_VIEWER_CONTROLS_TITLE = 'media_viewer.controls.title';
const MENU_MEDIA_VIEWER_LAYOUT = 'media_viewer.layout';
const MENU_TIMELINE_CONTROLS_THUMBNAILS = 'timeline.controls.thumbnails';
const MENU_OPTIONS = 'options';
const MENU_PERFORMANCE_FEATURES = 'performance.features';
const MENU_PERFORMANCE_STYLE = 'performance.style';
const MENU_TIMELINE_CONTROLS_THUMBNAILS = 'timeline.controls.thumbnails';
const MENU_VIEW_SCAN = 'scan';
interface EditorOptionsSet {
@@ -536,6 +536,12 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
{ value: 'mjpeg', label: localize('config.cameras.go2rtc.modes.mjpeg') },
];
protected _microphoneButtonTypes: EditorSelectOption[] = [
{ value: '', label: '' },
{ value: 'momentary', label: localize('config.menu.buttons.types.momentary') },
{ value: 'toggle', label: localize('config.menu.buttons.types.toggle') },
];
public setConfig(config: RawFrigateCardConfig): void {
// Note: This does not use Zod to parse the configuration, so it may be
// partially or completely invalid. It's more useful to have a partially
@@ -841,7 +847,10 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
* @param button The name of the button.
* @returns A rendered template.
*/
protected _renderMenuButton(button: string): TemplateResult {
protected _renderMenuButton(
button: string,
additionalOptions?: TemplateResult,
): TemplateResult {
const menuButtonAlignments: EditorSelectOption[] = [
{ value: '', label: '' },
{ value: 'matching', label: localize('config.menu.buttons.alignments.matching') },
@@ -892,6 +901,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
${this._renderIconSelector(`${CONF_MENU_BUTTONS}.${button}.icon`, {
label: localize('config.menu.buttons.icon'),
})}
${additionalOptions}
</div>`
: ''}
</div>
@@ -1763,7 +1773,14 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
${this._renderMenuButton('expand') /* */}
${this._renderMenuButton('timeline')}
${this._renderMenuButton('media_player')}
${this._renderMenuButton('microphone')}
${this._renderMenuButton(
'microphone',
html`${this._renderOptionSelector(
`${CONF_MENU_BUTTONS}.microphone.type`,
this._microphoneButtonTypes,
{ label: localize('config.menu.buttons.type') },
)}`,
)}
</div>
`
: ''}
+6 -1
View File
@@ -280,7 +280,12 @@
"recordings": "Recordings",
"snapshots": "Snapshots",
"substreams": "Substream(s)",
"timeline": "Timeline"
"timeline": "Timeline",
"type": "Button type",
"types": {
"momentary": "Momentary",
"toggle": "Toggle"
}
},
"position": "Menu position",
"positions": {
+6 -1
View File
@@ -277,7 +277,12 @@
"priority": "Priorità",
"snapshots": "Istantanee",
"substreams": "Flusso/i secondario/i",
"timeline": "Timeline"
"timeline": "Timeline",
"type": "",
"types": {
"momentary": "",
"toggle": ""
}
},
"position": "Posizione del menu",
"positions": {
+6 -1
View File
@@ -279,7 +279,12 @@
"recordings": "Gravações",
"snapshots": "Instantâneos",
"substreams": "Substream(s)",
"timeline": "Linha do tempo"
"timeline": "Linha do tempo",
"type": "",
"types": {
"momentary": "",
"toggle": ""
}
},
"position": "Posição do menu",
"positions": {
+6 -1
View File
@@ -265,7 +265,12 @@
"priority": "Prioridade",
"snapshots": "Instantâneos",
"substreams": "substreams",
"timeline": "Linha do tempo"
"timeline": "Linha do tempo",
"type": "",
"types": {
"momentary": "",
"toggle": ""
}
},
"position": "Posição do menu",
"positions": {
+11 -2
View File
@@ -1063,7 +1063,10 @@ const menuConfigDefault = {
fullscreen: visibleButtonDefault,
expand: hiddenButtonDefault,
media_player: visibleButtonDefault,
microphone: hiddenButtonDefault,
microphone: {
...hiddenButtonDefault,
type: 'momentary' as const,
},
recordings: hiddenButtonDefault,
},
button_size: 40,
@@ -1100,7 +1103,13 @@ const menuConfigSchema = z
media_player: visibleButtonSchema.default(
menuConfigDefault.buttons.media_player,
),
microphone: hiddenButtonSchema.default(menuConfigDefault.buttons.microphone),
microphone: hiddenButtonSchema
.extend({
type: z
.enum(['momentary', 'toggle'])
.default(menuConfigDefault.buttons.microphone.type),
})
.default(menuConfigDefault.buttons.microphone),
recordings: hiddenButtonSchema.default(menuConfigDefault.buttons.recordings),
})
.default(menuConfigDefault.buttons),
+4 -1
View File
@@ -1,3 +1,5 @@
import { errorToConsole } from "./basic";
export class MicrophoneController {
protected _stream?: MediaStream | null;
protected _timerID: number | null = null;
@@ -19,7 +21,8 @@ export class MicrophoneController {
audio: true,
video: false,
});
} catch (e) {
} catch (e: unknown) {
errorToConsole(e as Error);
this._stream = null;
}
this._setMute();