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 {
const config = this._api.getConfigManager().getConfig();
if (!config) {
return false;
}
return (
this._initializer.isInitializedMultiple([
InitializationAspect.LANGUAGES,
InitializationAspect.SIDE_LOAD_ELEMENTS,
InitializationAspect.CAMERAS,
...(config.live.microphone.always_connected
? [InitializationAspect.MICROPHONE_CONNECT]
: []),
]) &&
// If there's no view, re-initialize (e.g. config changes).
this._api.getViewManager().hasView()
@@ -54,15 +62,25 @@ export class InitializationManager {
return false;
}
if (!this._api.getConfigManager().hasConfig()) {
const config = this._api.getConfigManager().getConfig();
if (!config) {
return false;
}
if (
!(await this._initializer.initializeIfNecessary(
InitializationAspect.CAMERAS,
async () => await this._api.getCameraManager().initializeCamerasFromConfig(),
))
!(await this._initializer.initializeMultipleIfNecessary({
[InitializationAspect.CAMERAS]: async () =>
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;
}
@@ -104,9 +122,6 @@ export class InitializationManager {
...(config.menu.buttons.media_player.enabled
? [InitializationAspect.MEDIA_PLAYERS]
: []),
...(config.live.microphone.always_connected
? [InitializationAspect.MICROPHONE_CONNECT]
: []),
])
) {
return true;
@@ -118,10 +133,6 @@ export class InitializationManager {
[InitializationAspect.MEDIA_PLAYERS]: async () =>
await this._api.getMediaPlayerManager().initialize(),
}),
...(config.live.microphone.always_connected && {
[InitializationAspect.MICROPHONE_CONNECT]: async () =>
await this._api.getMicrophoneManager().connect(),
}),
}))
) {
return false;
@@ -19,11 +19,20 @@ describe('InitializationManager', () => {
});
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', () => {
const api = createCardAPI();
const initializer = mock<Initializer>();
const manager = new InitializationManager(api, initializer);
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(createConfig());
initializer.isInitializedMultiple.mockReturnValue(false);
expect(manager.isInitializedMandatory()).toBeFalsy();
@@ -34,6 +43,7 @@ describe('InitializationManager', () => {
const initializer = mock<Initializer>();
const manager = new InitializationManager(api, initializer);
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(createConfig());
initializer.isInitializedMultiple.mockReturnValue(true);
expect(manager.isInitializedMandatory()).toBeFalsy();
@@ -44,6 +54,27 @@ describe('InitializationManager', () => {
const initializer = mock<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);
vi.mocked(api.getViewManager().hasView).mockReturnValue(true);
@@ -67,7 +98,7 @@ describe('InitializationManager', () => {
it('successfully', async () => {
const api = createCardAPI();
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.getQueryStringManager().hasViewRelatedActions).mockReturnValue(
false,
@@ -80,12 +111,31 @@ describe('InitializationManager', () => {
expect(sideLoadHomeAssistantElements).toBeCalled();
expect(api.getCameraManager().initializeCamerasFromConfig).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 () => {
const api = createCardAPI();
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.getQueryStringManager().hasViewRelatedActions).mockReturnValue(true);
const manager = new InitializationManager(api);
@@ -98,7 +148,7 @@ describe('InitializationManager', () => {
it('with message set during initialization', async () => {
const api = createCardAPI();
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.getQueryStringManager().hasViewRelatedActions).mockReturnValue(
false,
@@ -124,12 +174,13 @@ describe('InitializationManager', () => {
it('with cameras in progress', async () => {
const api = createCardAPI();
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 manager = new InitializationManager(api, initializer);
initializer.initializeMultipleIfNecessary.mockResolvedValue(true);
initializer.initializeIfNecessary.mockResolvedValue(false);
initializer.initializeMultipleIfNecessary
.mockResolvedValueOnce(true)
.mockResolvedValueOnce(false);
expect(await manager.initializeMandatory()).toBeFalsy();
});
@@ -154,17 +205,11 @@ describe('InitializationManager', () => {
},
},
},
live: {
microphone: {
always_connected: false,
},
},
}),
);
expect(await manager.initializeBackgroundIfNecessary()).toBeTruthy();
expect(api.getMediaPlayerManager().initialize).not.toBeCalled();
expect(api.getMicrophoneManager().connect).not.toBeCalled();
});
it('successfully with all inititalizers', async () => {
@@ -180,21 +225,15 @@ describe('InitializationManager', () => {
},
},
},
live: {
microphone: {
always_connected: true,
},
},
}),
);
expect(await manager.initializeBackgroundIfNecessary()).toBeTruthy();
expect(api.getMediaPlayerManager().initialize).toBeCalled();
expect(api.getMicrophoneManager().connect).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();
vi.mocked(api.getHASSManager().getHASS).mockReturnValue(createHASS());
vi.mocked(api.getConfigManager().getConfig).mockReturnValue(
@@ -206,11 +245,6 @@ describe('InitializationManager', () => {
},
},
},
live: {
microphone: {
always_connected: true,
},
},
}),
);
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
// back-sliding.
thresholdAutoUpdate: true,
statements: 72.73,
branches: 61.93,
statements: 72.76,
branches: 61.95,
functions: 74.01,
lines: 72.63,
lines: 72.66,
},
},
});