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) {
|
if (this._microphoneController && cameraCapabilities?.supports2WayAudio) {
|
||||||
const muted = this._microphoneController.isMuted();
|
const muted = this._microphoneController.isMuted();
|
||||||
|
const buttonType = this._getConfig().menu.buttons.microphone.type;
|
||||||
buttons.push({
|
buttons.push({
|
||||||
icon: this._microphoneController.isForbidden()
|
icon: this._microphoneController.isForbidden()
|
||||||
? 'mdi:microphone-message-off'
|
? 'mdi:microphone-message-off'
|
||||||
@@ -630,12 +631,21 @@ class FrigateCard extends LitElement {
|
|||||||
type: 'custom:frigate-card-menu-icon',
|
type: 'custom:frigate-card-menu-icon',
|
||||||
title: localize('config.menu.buttons.microphone'),
|
title: localize('config.menu.buttons.microphone'),
|
||||||
style: muted ? {} : this._getEmphasizedStyle(true),
|
style: muted ? {} : this._getEmphasizedStyle(true),
|
||||||
start_tap_action: createFrigateCardCustomAction(
|
...(buttonType === 'momentary' && {
|
||||||
'microphone_unmute',
|
start_tap_action: createFrigateCardCustomAction(
|
||||||
) as FrigateCardCustomAction,
|
'microphone_unmute',
|
||||||
end_tap_action: createFrigateCardCustomAction(
|
) as FrigateCardCustomAction,
|
||||||
'microphone_mute',
|
end_tap_action: createFrigateCardCustomAction(
|
||||||
) as FrigateCardCustomAction,
|
'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();
|
this.requestUpdate();
|
||||||
break;
|
break;
|
||||||
case 'microphone_unmute':
|
case 'microphone_unmute':
|
||||||
const unmuteAndUpdate = () => {
|
|
||||||
this._microphoneController?.unmute();
|
|
||||||
this.requestUpdate();
|
|
||||||
};
|
|
||||||
if (
|
if (
|
||||||
!this._microphoneController?.isConnected() &&
|
!this._microphoneController?.isConnected() &&
|
||||||
!this._microphoneController?.isForbidden()
|
!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()) {
|
} else if (this._microphoneController?.isConnected()) {
|
||||||
unmuteAndUpdate();
|
this._microphoneController.unmute();
|
||||||
|
this.requestUpdate();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|||||||
+23
-6
@@ -181,14 +181,14 @@ import {
|
|||||||
const MENU_BUTTONS = 'buttons';
|
const MENU_BUTTONS = 'buttons';
|
||||||
const MENU_CAMERAS = 'cameras';
|
const MENU_CAMERAS = 'cameras';
|
||||||
const MENU_CAMERAS_DEPENDENCIES = 'cameras.dependencies';
|
const MENU_CAMERAS_DEPENDENCIES = 'cameras.dependencies';
|
||||||
|
const MENU_CAMERAS_ENGINE = 'cameras.engine';
|
||||||
const MENU_CAMERAS_FRIGATE = 'cameras.frigate';
|
const MENU_CAMERAS_FRIGATE = 'cameras.frigate';
|
||||||
const MENU_CAMERAS_GO2RTC = 'cameras.go2rtc';
|
const MENU_CAMERAS_GO2RTC = 'cameras.go2rtc';
|
||||||
const MENU_CAMERAS_IMAGE = 'cameras.image';
|
const MENU_CAMERAS_IMAGE = 'cameras.image';
|
||||||
|
const MENU_CAMERAS_LIVE_PROVIDER = 'cameras.live_provider';
|
||||||
const MENU_CAMERAS_MOTIONEYE = 'cameras.motioneye';
|
const MENU_CAMERAS_MOTIONEYE = 'cameras.motioneye';
|
||||||
const MENU_CAMERAS_TRIGGERS = 'cameras.triggers';
|
const MENU_CAMERAS_TRIGGERS = 'cameras.triggers';
|
||||||
const MENU_CAMERAS_WEBRTC_CARD = 'cameras.webrtc_card';
|
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_IMAGE_LAYOUT = 'image.layout';
|
||||||
const MENU_LIVE_CONTROLS = 'live.controls';
|
const MENU_LIVE_CONTROLS = 'live.controls';
|
||||||
const MENU_LIVE_CONTROLS_NEXT_PREVIOUS = 'live.controls.next_previous';
|
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_CONTROLS_TITLE = 'live.controls.title';
|
||||||
const MENU_LIVE_LAYOUT = 'live.layout';
|
const MENU_LIVE_LAYOUT = 'live.layout';
|
||||||
const MENU_LIVE_MICROPHONE = 'live.microphone';
|
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_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 = 'media_viewer.controls';
|
||||||
const MENU_MEDIA_VIEWER_CONTROLS_NEXT_PREVIOUS = 'media_viewer.controls.next_previous';
|
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_THUMBNAILS = 'media_viewer.controls.thumbnails';
|
||||||
const MENU_MEDIA_VIEWER_CONTROLS_TIMELINE = 'media_viewer.controls.timeline';
|
const MENU_MEDIA_VIEWER_CONTROLS_TIMELINE = 'media_viewer.controls.timeline';
|
||||||
const MENU_MEDIA_VIEWER_CONTROLS_TITLE = 'media_viewer.controls.title';
|
const MENU_MEDIA_VIEWER_CONTROLS_TITLE = 'media_viewer.controls.title';
|
||||||
const MENU_MEDIA_VIEWER_LAYOUT = 'media_viewer.layout';
|
const MENU_MEDIA_VIEWER_LAYOUT = 'media_viewer.layout';
|
||||||
const MENU_TIMELINE_CONTROLS_THUMBNAILS = 'timeline.controls.thumbnails';
|
|
||||||
const MENU_OPTIONS = 'options';
|
const MENU_OPTIONS = 'options';
|
||||||
const MENU_PERFORMANCE_FEATURES = 'performance.features';
|
const MENU_PERFORMANCE_FEATURES = 'performance.features';
|
||||||
const MENU_PERFORMANCE_STYLE = 'performance.style';
|
const MENU_PERFORMANCE_STYLE = 'performance.style';
|
||||||
|
const MENU_TIMELINE_CONTROLS_THUMBNAILS = 'timeline.controls.thumbnails';
|
||||||
const MENU_VIEW_SCAN = 'scan';
|
const MENU_VIEW_SCAN = 'scan';
|
||||||
|
|
||||||
interface EditorOptionsSet {
|
interface EditorOptionsSet {
|
||||||
@@ -536,6 +536,12 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
|||||||
{ value: 'mjpeg', label: localize('config.cameras.go2rtc.modes.mjpeg') },
|
{ 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 {
|
public setConfig(config: RawFrigateCardConfig): void {
|
||||||
// Note: This does not use Zod to parse the configuration, so it may be
|
// 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
|
// 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.
|
* @param button The name of the button.
|
||||||
* @returns A rendered template.
|
* @returns A rendered template.
|
||||||
*/
|
*/
|
||||||
protected _renderMenuButton(button: string): TemplateResult {
|
protected _renderMenuButton(
|
||||||
|
button: string,
|
||||||
|
additionalOptions?: TemplateResult,
|
||||||
|
): TemplateResult {
|
||||||
const menuButtonAlignments: EditorSelectOption[] = [
|
const menuButtonAlignments: EditorSelectOption[] = [
|
||||||
{ value: '', label: '' },
|
{ value: '', label: '' },
|
||||||
{ value: 'matching', label: localize('config.menu.buttons.alignments.matching') },
|
{ 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`, {
|
${this._renderIconSelector(`${CONF_MENU_BUTTONS}.${button}.icon`, {
|
||||||
label: localize('config.menu.buttons.icon'),
|
label: localize('config.menu.buttons.icon'),
|
||||||
})}
|
})}
|
||||||
|
${additionalOptions}
|
||||||
</div>`
|
</div>`
|
||||||
: ''}
|
: ''}
|
||||||
</div>
|
</div>
|
||||||
@@ -1763,7 +1773,14 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
|||||||
${this._renderMenuButton('expand') /* */}
|
${this._renderMenuButton('expand') /* */}
|
||||||
${this._renderMenuButton('timeline')}
|
${this._renderMenuButton('timeline')}
|
||||||
${this._renderMenuButton('media_player')}
|
${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>
|
</div>
|
||||||
`
|
`
|
||||||
: ''}
|
: ''}
|
||||||
|
|||||||
@@ -280,7 +280,12 @@
|
|||||||
"recordings": "Recordings",
|
"recordings": "Recordings",
|
||||||
"snapshots": "Snapshots",
|
"snapshots": "Snapshots",
|
||||||
"substreams": "Substream(s)",
|
"substreams": "Substream(s)",
|
||||||
"timeline": "Timeline"
|
"timeline": "Timeline",
|
||||||
|
"type": "Button type",
|
||||||
|
"types": {
|
||||||
|
"momentary": "Momentary",
|
||||||
|
"toggle": "Toggle"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"position": "Menu position",
|
"position": "Menu position",
|
||||||
"positions": {
|
"positions": {
|
||||||
|
|||||||
@@ -277,7 +277,12 @@
|
|||||||
"priority": "Priorità",
|
"priority": "Priorità",
|
||||||
"snapshots": "Istantanee",
|
"snapshots": "Istantanee",
|
||||||
"substreams": "Flusso/i secondario/i",
|
"substreams": "Flusso/i secondario/i",
|
||||||
"timeline": "Timeline"
|
"timeline": "Timeline",
|
||||||
|
"type": "",
|
||||||
|
"types": {
|
||||||
|
"momentary": "",
|
||||||
|
"toggle": ""
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"position": "Posizione del menu",
|
"position": "Posizione del menu",
|
||||||
"positions": {
|
"positions": {
|
||||||
|
|||||||
@@ -279,7 +279,12 @@
|
|||||||
"recordings": "Gravações",
|
"recordings": "Gravações",
|
||||||
"snapshots": "Instantâneos",
|
"snapshots": "Instantâneos",
|
||||||
"substreams": "Substream(s)",
|
"substreams": "Substream(s)",
|
||||||
"timeline": "Linha do tempo"
|
"timeline": "Linha do tempo",
|
||||||
|
"type": "",
|
||||||
|
"types": {
|
||||||
|
"momentary": "",
|
||||||
|
"toggle": ""
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"position": "Posição do menu",
|
"position": "Posição do menu",
|
||||||
"positions": {
|
"positions": {
|
||||||
|
|||||||
@@ -265,7 +265,12 @@
|
|||||||
"priority": "Prioridade",
|
"priority": "Prioridade",
|
||||||
"snapshots": "Instantâneos",
|
"snapshots": "Instantâneos",
|
||||||
"substreams": "substreams",
|
"substreams": "substreams",
|
||||||
"timeline": "Linha do tempo"
|
"timeline": "Linha do tempo",
|
||||||
|
"type": "",
|
||||||
|
"types": {
|
||||||
|
"momentary": "",
|
||||||
|
"toggle": ""
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"position": "Posição do menu",
|
"position": "Posição do menu",
|
||||||
"positions": {
|
"positions": {
|
||||||
|
|||||||
+11
-2
@@ -1063,7 +1063,10 @@ const menuConfigDefault = {
|
|||||||
fullscreen: visibleButtonDefault,
|
fullscreen: visibleButtonDefault,
|
||||||
expand: hiddenButtonDefault,
|
expand: hiddenButtonDefault,
|
||||||
media_player: visibleButtonDefault,
|
media_player: visibleButtonDefault,
|
||||||
microphone: hiddenButtonDefault,
|
microphone: {
|
||||||
|
...hiddenButtonDefault,
|
||||||
|
type: 'momentary' as const,
|
||||||
|
},
|
||||||
recordings: hiddenButtonDefault,
|
recordings: hiddenButtonDefault,
|
||||||
},
|
},
|
||||||
button_size: 40,
|
button_size: 40,
|
||||||
@@ -1100,7 +1103,13 @@ const menuConfigSchema = z
|
|||||||
media_player: visibleButtonSchema.default(
|
media_player: visibleButtonSchema.default(
|
||||||
menuConfigDefault.buttons.media_player,
|
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),
|
recordings: hiddenButtonSchema.default(menuConfigDefault.buttons.recordings),
|
||||||
})
|
})
|
||||||
.default(menuConfigDefault.buttons),
|
.default(menuConfigDefault.buttons),
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { errorToConsole } from "./basic";
|
||||||
|
|
||||||
export class MicrophoneController {
|
export class MicrophoneController {
|
||||||
protected _stream?: MediaStream | null;
|
protected _stream?: MediaStream | null;
|
||||||
protected _timerID: number | null = null;
|
protected _timerID: number | null = null;
|
||||||
@@ -19,7 +21,8 @@ export class MicrophoneController {
|
|||||||
audio: true,
|
audio: true,
|
||||||
video: false,
|
video: false,
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e: unknown) {
|
||||||
|
errorToConsole(e as Error);
|
||||||
this._stream = null;
|
this._stream = null;
|
||||||
}
|
}
|
||||||
this._setMute();
|
this._setMute();
|
||||||
|
|||||||
Reference in New Issue
Block a user