Add support for connecting microphone on card load.
This commit is contained in:
+28
-8
@@ -152,6 +152,7 @@ enum InitializationAspect {
|
||||
SIDE_LOAD_ELEMENTS = 'side-load-elements',
|
||||
MEDIA_PLAYERS = 'media-players',
|
||||
CAMERAS = 'cameras',
|
||||
MICROPHONE = 'microphone',
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1009,8 +1010,6 @@ class FrigateCard extends LitElement {
|
||||
setPerformanceCSSStyles(this, this._cardWideConfig?.performance);
|
||||
}
|
||||
|
||||
this._initializeBackground();
|
||||
|
||||
if (changedProps.has('_view')) {
|
||||
this._setPropertiesForExpandedMode();
|
||||
}
|
||||
@@ -1029,6 +1028,9 @@ class FrigateCard extends LitElement {
|
||||
this._getConfig().live.microphone.disconnect_seconds,
|
||||
);
|
||||
}
|
||||
|
||||
// Must be called after the microphoneController is created.
|
||||
this._initializeBackground();
|
||||
}
|
||||
|
||||
protected _setPropertiesForMinMaxHeight(): void {
|
||||
@@ -1204,6 +1206,10 @@ class FrigateCard extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
protected async _initializeMicrophone(): Promise<void> {
|
||||
await this._microphoneController?.connect();
|
||||
}
|
||||
|
||||
protected async _initializeMediaPlayers(hass: HomeAssistant): Promise<void> {
|
||||
const isValidMediaPlayer = (entityID: string): boolean => {
|
||||
if (entityID.startsWith('media_player.')) {
|
||||
@@ -1299,19 +1305,33 @@ class FrigateCard extends LitElement {
|
||||
protected _initializeBackground(): void {
|
||||
const hass = this._hass;
|
||||
const config = this._getConfig();
|
||||
const needMediaPlayers = config.menu.buttons.media_player.enabled;
|
||||
if (!hass || !config || !needMediaPlayers) {
|
||||
if (!hass || !config) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._initializer.isInitialized(InitializationAspect.MEDIA_PLAYERS)) {
|
||||
if (
|
||||
this._initializer.isInitializedMultiple([
|
||||
...(config.menu.buttons.media_player.enabled
|
||||
? [InitializationAspect.MEDIA_PLAYERS]
|
||||
: []),
|
||||
...(config.live.microphone.connect_on_load
|
||||
? [InitializationAspect.MICROPHONE]
|
||||
: []),
|
||||
])
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._initializer
|
||||
.initializeMultipleIfNecessary({
|
||||
[InitializationAspect.MEDIA_PLAYERS]: async () =>
|
||||
await this._initializeMediaPlayers(hass),
|
||||
...(config.menu.buttons.media_player.enabled && {
|
||||
[InitializationAspect.MEDIA_PLAYERS]: async () =>
|
||||
await this._initializeMediaPlayers(hass),
|
||||
}),
|
||||
...(config.live.microphone.connect_on_load && {
|
||||
[InitializationAspect.MICROPHONE]: async () =>
|
||||
await this._initializeMicrophone(),
|
||||
}),
|
||||
})
|
||||
.then((initialized) => {
|
||||
if (initialized) {
|
||||
@@ -1601,7 +1621,7 @@ class FrigateCard extends LitElement {
|
||||
|
||||
// Must requestUpdate to show the correct microphone state in the
|
||||
// menu.
|
||||
this._microphoneController?.connect().then(() => this.requestUpdate());
|
||||
this._initializeMicrophone().then(() => this.requestUpdate());
|
||||
} else if (this._microphoneController?.isConnected()) {
|
||||
this._microphoneController.unmute();
|
||||
this.requestUpdate();
|
||||
|
||||
@@ -40,7 +40,7 @@ class FrigateCardGo2RTCPlayer extends VideoRTC {
|
||||
constructor(microphoneStream?: MediaStream) {
|
||||
super();
|
||||
if (microphoneStream) {
|
||||
this._microphoneStream = this._microphoneStream;
|
||||
this._microphoneStream = microphoneStream;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -194,6 +194,8 @@ export const CONF_LIVE_SHOW_IMAGE_DURING_LOAD =
|
||||
`${CONF_LIVE}.show_image_during_load` as const;
|
||||
export const CONF_LIVE_MICROPHONE_DISCONNECT_SECONDS =
|
||||
`${CONF_LIVE}.microphone.disconnect_seconds` as const;
|
||||
export const CONF_LIVE_MICROPHONE_CONNECT_ON_LOAD =
|
||||
`${CONF_LIVE}.microphone.connect_on_load` as const;
|
||||
export const CONF_LIVE_ZOOMABLE = `${CONF_LIVE}.zoomable` as const;
|
||||
|
||||
const CONF_IMAGE = 'image' as const;
|
||||
|
||||
@@ -156,6 +156,7 @@ import {
|
||||
CONF_VIEW_UPDATE_FORCE,
|
||||
CONF_VIEW_UPDATE_SECONDS,
|
||||
MEDIA_CHUNK_SIZE_MAX,
|
||||
CONF_LIVE_MICROPHONE_CONNECT_ON_LOAD,
|
||||
} from './const.js';
|
||||
import { localize } from './localize/localize.js';
|
||||
import { setLowPerformanceProfile } from './performance.js';
|
||||
@@ -1878,6 +1879,10 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor
|
||||
{ name: 'mdi:microphone' },
|
||||
html`
|
||||
${this._renderNumberInput(CONF_LIVE_MICROPHONE_DISCONNECT_SECONDS)}
|
||||
${this._renderSwitch(
|
||||
CONF_LIVE_MICROPHONE_CONNECT_ON_LOAD,
|
||||
this._defaults.live.microphone.connect_on_load,
|
||||
)}
|
||||
`,
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -220,6 +220,7 @@
|
||||
"lazy_load": "Live cameras are lazily loaded",
|
||||
"lazy_unload": "Live cameras are lazily unloaded",
|
||||
"microphone": {
|
||||
"connect_on_load": "Connect microphone on card load",
|
||||
"disconnect_seconds": "Seconds after which to disconnect microphone (0=never)",
|
||||
"editor_label": "Microphone",
|
||||
"enabled": "Microphone enabled"
|
||||
|
||||
@@ -220,6 +220,7 @@
|
||||
"lazy_load": "Le telecamere dal vivo sono pigramente cariche",
|
||||
"lazy_unload": "Le telecamere dal vivo sono pigramente non caricate",
|
||||
"microphone": {
|
||||
"connect_on_load": "",
|
||||
"disconnect_seconds": "",
|
||||
"editor_label": "",
|
||||
"enabled": ""
|
||||
|
||||
@@ -220,6 +220,7 @@
|
||||
"lazy_load": "As câmeras ao vivo são carregadas lentamente",
|
||||
"lazy_unload": "As câmeras ao vivo são descarregadas preguiçosamente",
|
||||
"microphone": {
|
||||
"connect_on_load": "",
|
||||
"disconnect_seconds": "",
|
||||
"editor_label": "",
|
||||
"enabled": ""
|
||||
|
||||
@@ -212,6 +212,12 @@
|
||||
"layout": "layout",
|
||||
"lazy_load": "As câmeras ao vivo são carregadas lentamente",
|
||||
"lazy_unload": "As câmeras ao vivo são descarregadas preguiçosamente",
|
||||
"microphone": {
|
||||
"connect_on_load": "",
|
||||
"disconnect_seconds": "",
|
||||
"editor_label": "",
|
||||
"enabled": ""
|
||||
},
|
||||
"preload": "Pré-carregar a visualização ao vivo em segundo plano",
|
||||
"show_image_during_load": "Mostar imagem durante o carregamento",
|
||||
"transition_effect": "Efeito de transição de câmera ao vivo",
|
||||
|
||||
@@ -415,6 +415,7 @@ const imageBaseConfigSchema = z.object({
|
||||
|
||||
const microphoneConfigDefault = {
|
||||
disconnect_seconds: 60,
|
||||
connect_on_load: false,
|
||||
};
|
||||
|
||||
const microphoneConfigSchema = z
|
||||
@@ -423,6 +424,7 @@ const microphoneConfigSchema = z
|
||||
.number()
|
||||
.min(0)
|
||||
.default(microphoneConfigDefault.disconnect_seconds),
|
||||
connect_on_load: z.boolean().default(microphoneConfigDefault.connect_on_load),
|
||||
})
|
||||
.default(microphoneConfigDefault);
|
||||
export type MicrophoneConfig = z.infer<typeof microphoneConfigSchema>;
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { afterAll, describe, expect, it, vi } from 'vitest';
|
||||
import { log } from '../../src/utils/debug.js';
|
||||
|
||||
describe('log', () => {
|
||||
const spy = vi.spyOn(global.console, 'debug').mockReturnValue(undefined);
|
||||
afterAll(() => {
|
||||
vi.resetAllMocks();
|
||||
});
|
||||
it('should do nothing without debug logging set', () => {
|
||||
const spy = vi.spyOn(global.console, 'debug');
|
||||
log({}, 'foo');
|
||||
expect(spy).not.toBeCalled();
|
||||
});
|
||||
it('should log debug when appropriately configured', () => {
|
||||
const spy = vi.spyOn(global.console, 'debug');
|
||||
log({ debug: { logging: true } }, 'foo');
|
||||
expect(spy).toBeCalledWith('foo');
|
||||
});
|
||||
|
||||
@@ -15,6 +15,7 @@ describe('MicrophoneController', () => {
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.resetAllMocks();
|
||||
vi.unstubAllGlobals;
|
||||
});
|
||||
|
||||
@@ -48,6 +49,8 @@ describe('MicrophoneController', () => {
|
||||
});
|
||||
|
||||
it('should be forbidden when permission denied', async () => {
|
||||
// Don't actually log messages to the console during the test.
|
||||
vi.spyOn(global.console, 'warn').mockReturnValue(undefined);
|
||||
const controller = new MicrophoneController();
|
||||
navigatorMock.mediaDevices.getUserMedia.mockRejectedValue(new Error());
|
||||
await controller.connect();
|
||||
|
||||
Reference in New Issue
Block a user