Implement momentary and toggle microphone button types.
This commit is contained in:
+29
-12
@@ -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),
|
||||
start_tap_action: createFrigateCardCustomAction(
|
||||
'microphone_unmute',
|
||||
) as FrigateCardCustomAction,
|
||||
end_tap_action: createFrigateCardCustomAction(
|
||||
'microphone_mute',
|
||||
) as FrigateCardCustomAction,
|
||||
...(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
@@ -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>
|
||||
`
|
||||
: ''}
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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
@@ -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),
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user