diff --git a/src/camera-manager/manager.ts b/src/camera-manager/manager.ts index 89b0e8f5..ae06d8e4 100644 --- a/src/camera-manager/manager.ts +++ b/src/camera-manager/manager.ts @@ -157,7 +157,7 @@ export class CameraManager { return true; } - public async reset(): Promise { + public async destroy(): Promise { await this._store.reset(); } diff --git a/src/card-controller/card-element-manager.ts b/src/card-controller/card-element-manager.ts index 04cd163b..1b6f32dd 100644 --- a/src/card-controller/card-element-manager.ts +++ b/src/card-controller/card-element-manager.ts @@ -152,7 +152,7 @@ export class CardElementManager { // reconnection, to ensure the state subscription/unsubscription works // correctly for triggers. this._api.getInitializationManager().uninitialize(InitializationAspect.CAMERAS); - this._api.getCameraManager().reset(); + this._api.getCameraManager().destroy(); this._element.removeEventListener( 'mousemove', diff --git a/src/card-controller/config/config-manager.ts b/src/card-controller/config/config-manager.ts index 2aaa033e..7ff48c13 100644 --- a/src/card-controller/config/config-manager.ts +++ b/src/card-controller/config/config-manager.ts @@ -141,6 +141,7 @@ export class ConfigManager { !isEqual(previousConfig?.cameras_global, this._overriddenConfig?.cameras_global)) ) { this._api.getInitializationManager().uninitialize(InitializationAspect.CAMERAS); + this._api.getCameraManager().destroy(); } if ( diff --git a/src/card-controller/controller.ts b/src/card-controller/controller.ts index d0425ffe..8158015b 100644 --- a/src/card-controller/controller.ts +++ b/src/card-controller/controller.ts @@ -159,6 +159,9 @@ export class CardController public getCameraManager(): CameraManager { return this._cameraManager; } + public createCameraManager(): void { + this._cameraManager = new CameraManager(this); + } public getCameraURLManager(): CameraURLManager { return this._cameraURLManager; @@ -236,6 +239,9 @@ export class CardController public getMicrophoneManager(): MicrophoneManager { return this._microphoneManager; } + public createMicrophoneManager(): void { + this._microphoneManager = new MicrophoneManager(this); + } public getQueryStringManager(): QueryStringManager { return this._queryStringManager; diff --git a/src/card-controller/initialization-manager.ts b/src/card-controller/initialization-manager.ts index 248f8ff6..5e2c3cd5 100644 --- a/src/card-controller/initialization-manager.ts +++ b/src/card-controller/initialization-manager.ts @@ -92,16 +92,25 @@ export class InitializationManager { if ( !(await this._initializer.initializeMultipleIfNecessary({ - [InitializationAspect.CAMERAS]: async () => - await this._api.getCameraManager().initializeCamerasFromConfig(), + [InitializationAspect.CAMERAS]: async () => { + // Recreate the camera manager to guarantee an immediate re-render. + // See: https://github.com/dermotduffy/frigate-hass-card/issues/1811 + // See: https://github.com/dermotduffy/frigate-hass-card/issues/1769 + this._api.createCameraManager(); + return 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 + // first stream initialized. + // See: https://github.com/dermotduffy/frigate-hass-card/issues/1235 ...(this._api.getMicrophoneManager().shouldConnectOnInitialization() && { - [InitializationAspect.MICROPHONE_CONNECT]: async () => - await this._api.getMicrophoneManager().connect(), + [InitializationAspect.MICROPHONE_CONNECT]: async () => { + // Recreate the microphone manager to guarantee an immediate + // re-render. + this._api.createMicrophoneManager(); + return await this._api.getMicrophoneManager().connect(); + }, }), })) ) { diff --git a/src/card-controller/types.ts b/src/card-controller/types.ts index e9e25e86..96c128e4 100644 --- a/src/card-controller/types.ts +++ b/src/card-controller/types.ts @@ -85,6 +85,7 @@ export interface CardConditionAPI { export interface CardConfigAPI { getAutomationsManager(): AutomationsManager; + getCameraManager(): CameraManager; getCardElementManager(): CardElementManager; getConditionsManager(): ConditionsManager; getConfigManager(): ConfigManager; @@ -93,6 +94,7 @@ export interface CardConfigAPI { getMediaLoadedInfoManager(): MediaLoadedInfoManager; getMediaPlayerManager(): MediaPlayerManager; getMessageManager(): MessageManager; + getMicrophoneManager(): MicrophoneManager; getStatusBarItemManager(): StatusBarItemManager; getStyleManager(): StyleManager; getViewManager(): ViewManager; @@ -165,7 +167,12 @@ export interface CardHASSAPI { } export interface CardInitializerAPI { + createCameraManager(): void; getCameraManager(): CameraManager; + + createMicrophoneManager(): void; + getMicrophoneManager(): MicrophoneManager; + getCardElementManager(): CardElementManager; getConfigManager(): ConfigManager; getDefaultManager(): DefaultManager; @@ -173,7 +180,6 @@ export interface CardInitializerAPI { getHASSManager(): HASSManager; getMediaPlayerManager(): MediaPlayerManager; getMessageManager(): MessageManager; - getMicrophoneManager(): MicrophoneManager; getQueryStringManager(): QueryStringManager; getResolvedMediaCache(): ResolvedMediaCache; getViewManager(): ViewManager; diff --git a/tests/camera-manager/manager.test.ts b/tests/camera-manager/manager.test.ts index 6d30c615..43909bff 100644 --- a/tests/camera-manager/manager.test.ts +++ b/tests/camera-manager/manager.test.ts @@ -1290,7 +1290,7 @@ describe('CameraManager', async () => { }); }); - it('should reset', async () => { + it('should destroy', async () => { const api = createCardAPI(); const engine = mock(); vi.mocked(api.getHASSManager().getHASS).mockReturnValue(createHASS()); @@ -1300,7 +1300,7 @@ describe('CameraManager', async () => { expect(manager.getStore().getCameraCount()).toBe(1); - await manager.reset(); + await manager.destroy(); expect(manager.getStore().getCameraCount()).toBe(0); }); diff --git a/tests/card-controller/controller.test.ts b/tests/card-controller/controller.test.ts index ee93f7e1..9e30cb1f 100644 --- a/tests/card-controller/controller.test.ts +++ b/tests/card-controller/controller.test.ts @@ -282,6 +282,26 @@ describe('CardController', () => { }); }); + describe('creaters ', () => { + it('createCameraManager', () => { + const controller = createController(); + const original = controller.getCameraManager(); + + controller.createCameraManager(); + + expect(controller.getCameraManager()).not.toBe(original); + }); + + it('createMicrophoneManager', () => { + const controller = createController(); + const original = controller.getMicrophoneManager(); + + controller.createMicrophoneManager(); + + expect(controller.getMicrophoneManager()).not.toBe(original); + }); + }); + describe('handlers', () => { it('hostConnected', () => { createController().hostConnected();