Merge pull request #1362 from dermotduffy/mic-mandatory

Fix issues with 2-way audio for cameras that support only a single stream
This commit is contained in:
Dermot Duffy
2024-02-03 16:41:07 -08:00
committed by GitHub
3 changed files with 84 additions and 39 deletions
+23 -12
View File
@@ -21,11 +21,19 @@ export class InitializationManager {
} }
public isInitializedMandatory(): boolean { public isInitializedMandatory(): boolean {
const config = this._api.getConfigManager().getConfig();
if (!config) {
return false;
}
return ( return (
this._initializer.isInitializedMultiple([ this._initializer.isInitializedMultiple([
InitializationAspect.LANGUAGES, InitializationAspect.LANGUAGES,
InitializationAspect.SIDE_LOAD_ELEMENTS, InitializationAspect.SIDE_LOAD_ELEMENTS,
InitializationAspect.CAMERAS, InitializationAspect.CAMERAS,
...(config.live.microphone.always_connected
? [InitializationAspect.MICROPHONE_CONNECT]
: []),
]) && ]) &&
// If there's no view, re-initialize (e.g. config changes). // If there's no view, re-initialize (e.g. config changes).
this._api.getViewManager().hasView() this._api.getViewManager().hasView()
@@ -54,15 +62,25 @@ export class InitializationManager {
return false; return false;
} }
if (!this._api.getConfigManager().hasConfig()) { const config = this._api.getConfigManager().getConfig();
if (!config) {
return false; return false;
} }
if ( if (
!(await this._initializer.initializeIfNecessary( !(await this._initializer.initializeMultipleIfNecessary({
InitializationAspect.CAMERAS, [InitializationAspect.CAMERAS]: async () =>
async () => await this._api.getCameraManager().initializeCamerasFromConfig(), await this._api.getCameraManager().initializeCamerasFromConfig(),
))
// Connecting the microphone (if configured) is considered mandatory to
// avoid issues with some cameras that only allow 2-way audio on the
// first stream initialized. See:
// https://github.com/dermotduffy/frigate-hass-card/issues/1235
...(config.live.microphone.always_connected && {
[InitializationAspect.MICROPHONE_CONNECT]: async () =>
await this._api.getMicrophoneManager().connect(),
}),
}))
) { ) {
return false; return false;
} }
@@ -104,9 +122,6 @@ export class InitializationManager {
...(config.menu.buttons.media_player.enabled ...(config.menu.buttons.media_player.enabled
? [InitializationAspect.MEDIA_PLAYERS] ? [InitializationAspect.MEDIA_PLAYERS]
: []), : []),
...(config.live.microphone.always_connected
? [InitializationAspect.MICROPHONE_CONNECT]
: []),
]) ])
) { ) {
return true; return true;
@@ -118,10 +133,6 @@ export class InitializationManager {
[InitializationAspect.MEDIA_PLAYERS]: async () => [InitializationAspect.MEDIA_PLAYERS]: async () =>
await this._api.getMediaPlayerManager().initialize(), await this._api.getMediaPlayerManager().initialize(),
}), }),
...(config.live.microphone.always_connected && {
[InitializationAspect.MICROPHONE_CONNECT]: async () =>
await this._api.getMicrophoneManager().connect(),
}),
})) }))
) { ) {
return false; return false;
@@ -19,11 +19,20 @@ describe('InitializationManager', () => {
}); });
describe('should correctly determine when mandatory initialization is required', () => { describe('should correctly determine when mandatory initialization is required', () => {
it('without config', () => {
const api = createCardAPI();
const initializer = mock<Initializer>();
const manager = new InitializationManager(api, initializer);
expect(manager.isInitializedMandatory()).toBeFalsy();
});
it('without aspects', () => { it('without aspects', () => {
const api = createCardAPI(); const api = createCardAPI();
const initializer = mock<Initializer>(); const initializer = mock<Initializer>();
const manager = new InitializationManager(api, initializer); const manager = new InitializationManager(api, initializer);
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(createConfig());
initializer.isInitializedMultiple.mockReturnValue(false); initializer.isInitializedMultiple.mockReturnValue(false);
expect(manager.isInitializedMandatory()).toBeFalsy(); expect(manager.isInitializedMandatory()).toBeFalsy();
@@ -34,6 +43,7 @@ describe('InitializationManager', () => {
const initializer = mock<Initializer>(); const initializer = mock<Initializer>();
const manager = new InitializationManager(api, initializer); const manager = new InitializationManager(api, initializer);
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(createConfig());
initializer.isInitializedMultiple.mockReturnValue(true); initializer.isInitializedMultiple.mockReturnValue(true);
expect(manager.isInitializedMandatory()).toBeFalsy(); expect(manager.isInitializedMandatory()).toBeFalsy();
@@ -44,6 +54,27 @@ describe('InitializationManager', () => {
const initializer = mock<Initializer>(); const initializer = mock<Initializer>();
const manager = new InitializationManager(api, initializer); const manager = new InitializationManager(api, initializer);
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(createConfig());
initializer.isInitializedMultiple.mockReturnValue(true);
vi.mocked(api.getViewManager().hasView).mockReturnValue(true);
expect(manager.isInitializedMandatory()).toBeTruthy();
});
it('with microphone if configured', () => {
const api = createCardAPI();
const initializer = mock<Initializer>();
const manager = new InitializationManager(api, initializer);
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(
createConfig({
live: {
microphone: {
always_connected: true,
},
},
}),
);
initializer.isInitializedMultiple.mockReturnValue(true); initializer.isInitializedMultiple.mockReturnValue(true);
vi.mocked(api.getViewManager().hasView).mockReturnValue(true); vi.mocked(api.getViewManager().hasView).mockReturnValue(true);
@@ -67,7 +98,7 @@ describe('InitializationManager', () => {
it('successfully', async () => { it('successfully', async () => {
const api = createCardAPI(); const api = createCardAPI();
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(createHASS()); vi.mocked(api.getHASSManager().getHASS).mockReturnValue(createHASS());
vi.mocked(api.getConfigManager().hasConfig).mockReturnValue(true); vi.mocked(api.getConfigManager().getConfig).mockReturnValue(createConfig());
vi.mocked(api.getMessageManager().hasMessage).mockReturnValue(false); vi.mocked(api.getMessageManager().hasMessage).mockReturnValue(false);
vi.mocked(api.getQueryStringManager().hasViewRelatedActions).mockReturnValue( vi.mocked(api.getQueryStringManager().hasViewRelatedActions).mockReturnValue(
false, false,
@@ -80,12 +111,31 @@ describe('InitializationManager', () => {
expect(sideLoadHomeAssistantElements).toBeCalled(); expect(sideLoadHomeAssistantElements).toBeCalled();
expect(api.getCameraManager().initializeCamerasFromConfig).toBeCalled(); expect(api.getCameraManager().initializeCamerasFromConfig).toBeCalled();
expect(api.getViewManager().setViewDefault).toBeCalled(); expect(api.getViewManager().setViewDefault).toBeCalled();
expect(api.getMicrophoneManager().connect).not.toBeCalled();
});
it('successfully with microphone if configured', async () => {
const api = createCardAPI();
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(createHASS());
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(
createConfig({
live: {
microphone: {
always_connected: true,
},
},
}),
);
const manager = new InitializationManager(api);
expect(await manager.initializeMandatory()).toBeTruthy();
expect(api.getMicrophoneManager().connect).toBeCalled();
}); });
it('successfully with querystring view', async () => { it('successfully with querystring view', async () => {
const api = createCardAPI(); const api = createCardAPI();
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(createHASS()); vi.mocked(api.getHASSManager().getHASS).mockReturnValue(createHASS());
vi.mocked(api.getConfigManager().hasConfig).mockReturnValue(true); vi.mocked(api.getConfigManager().getConfig).mockReturnValue(createConfig());
vi.mocked(api.getMessageManager().hasMessage).mockReturnValue(false); vi.mocked(api.getMessageManager().hasMessage).mockReturnValue(false);
vi.mocked(api.getQueryStringManager().hasViewRelatedActions).mockReturnValue(true); vi.mocked(api.getQueryStringManager().hasViewRelatedActions).mockReturnValue(true);
const manager = new InitializationManager(api); const manager = new InitializationManager(api);
@@ -98,7 +148,7 @@ describe('InitializationManager', () => {
it('with message set during initialization', async () => { it('with message set during initialization', async () => {
const api = createCardAPI(); const api = createCardAPI();
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(createHASS()); vi.mocked(api.getHASSManager().getHASS).mockReturnValue(createHASS());
vi.mocked(api.getConfigManager().hasConfig).mockReturnValue(true); vi.mocked(api.getConfigManager().getConfig).mockReturnValue(createConfig());
vi.mocked(api.getMessageManager().hasMessage).mockReturnValue(true); vi.mocked(api.getMessageManager().hasMessage).mockReturnValue(true);
vi.mocked(api.getQueryStringManager().hasViewRelatedActions).mockReturnValue( vi.mocked(api.getQueryStringManager().hasViewRelatedActions).mockReturnValue(
false, false,
@@ -124,12 +174,13 @@ describe('InitializationManager', () => {
it('with cameras in progress', async () => { it('with cameras in progress', async () => {
const api = createCardAPI(); const api = createCardAPI();
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(createHASS()); vi.mocked(api.getHASSManager().getHASS).mockReturnValue(createHASS());
vi.mocked(api.getConfigManager().hasConfig).mockReturnValue(true); vi.mocked(api.getConfigManager().getConfig).mockReturnValue(createConfig());
const initializer = mock<Initializer>(); const initializer = mock<Initializer>();
const manager = new InitializationManager(api, initializer); const manager = new InitializationManager(api, initializer);
initializer.initializeMultipleIfNecessary.mockResolvedValue(true); initializer.initializeMultipleIfNecessary
initializer.initializeIfNecessary.mockResolvedValue(false); .mockResolvedValueOnce(true)
.mockResolvedValueOnce(false);
expect(await manager.initializeMandatory()).toBeFalsy(); expect(await manager.initializeMandatory()).toBeFalsy();
}); });
@@ -154,17 +205,11 @@ describe('InitializationManager', () => {
}, },
}, },
}, },
live: {
microphone: {
always_connected: false,
},
},
}), }),
); );
expect(await manager.initializeBackgroundIfNecessary()).toBeTruthy(); expect(await manager.initializeBackgroundIfNecessary()).toBeTruthy();
expect(api.getMediaPlayerManager().initialize).not.toBeCalled(); expect(api.getMediaPlayerManager().initialize).not.toBeCalled();
expect(api.getMicrophoneManager().connect).not.toBeCalled();
}); });
it('successfully with all inititalizers', async () => { it('successfully with all inititalizers', async () => {
@@ -180,21 +225,15 @@ describe('InitializationManager', () => {
}, },
}, },
}, },
live: {
microphone: {
always_connected: true,
},
},
}), }),
); );
expect(await manager.initializeBackgroundIfNecessary()).toBeTruthy(); expect(await manager.initializeBackgroundIfNecessary()).toBeTruthy();
expect(api.getMediaPlayerManager().initialize).toBeCalled(); expect(api.getMediaPlayerManager().initialize).toBeCalled();
expect(api.getMicrophoneManager().connect).toBeCalled();
expect(api.getCardElementManager().update).toBeCalled(); expect(api.getCardElementManager().update).toBeCalled();
}); });
it('with media player and microphone connect in progress', async () => { it('with media player in progress', async () => {
const api = createCardAPI(); const api = createCardAPI();
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(createHASS()); vi.mocked(api.getHASSManager().getHASS).mockReturnValue(createHASS());
vi.mocked(api.getConfigManager().getConfig).mockReturnValue( vi.mocked(api.getConfigManager().getConfig).mockReturnValue(
@@ -206,11 +245,6 @@ describe('InitializationManager', () => {
}, },
}, },
}, },
live: {
microphone: {
always_connected: true,
},
},
}), }),
); );
const initializer = mock<Initializer>(); const initializer = mock<Initializer>();
+3 -3
View File
@@ -10,10 +10,10 @@ export default defineConfig({
// Thresholds will automatically be updated as coverage improves to avoid // Thresholds will automatically be updated as coverage improves to avoid
// back-sliding. // back-sliding.
thresholdAutoUpdate: true, thresholdAutoUpdate: true,
statements: 72.73, statements: 72.76,
branches: 61.93, branches: 61.95,
functions: 74.01, functions: 74.01,
lines: 72.63, lines: 72.66,
}, },
}, },
}); });