fix: Improve 2-way audio detection (#2293)

- For Frigate cameras, you must be running at least [integration
v5.12.0](https://github.com/blakeblackshear/frigate-hass-integration/releases/tag/v5.12.0).
 - Closes: #2191 

Includes a significant refactor of cameras, and the introduction of a
`2-way-audio` capability that is dynamically fetched from `go2rtc`. One
gotcha is if you previously had a substream with 2-way audio, you may
need to modify

```yaml
 - camera_entity: camera.foo
    capabilities:
      disable_except:
        - substream
```

... to ...

```yaml
 - camera_entity: camera.foo
    capabilities:
      disable_except:
        - substream
        - 2-way-audio
```
This commit is contained in:
Dermot Duffy
2025-12-20 13:06:09 -08:00
committed by GitHub
parent 588ed6c98a
commit 1e821f09c6
47 changed files with 2240 additions and 958 deletions
+19 -23
View File
@@ -6,9 +6,14 @@ import { CameraManagerEngineFactory } from '../../src/camera-manager/engine-fact
import { CameraManagerStore } from '../../src/camera-manager/store.js';
import { Engine } from '../../src/camera-manager/types.js';
import { StateWatcherSubscriptionInterface } from '../../src/card-controller/hass/state-watcher.js';
import { DeviceRegistryManager } from '../../src/ha/registry/device/index.js';
import { EntityRegistryManager } from '../../src/ha/registry/entity/types.js';
import { ResolvedMediaCache } from '../../src/ha/resolved-media.js';
import { TestViewMedia, createCameraConfig } from '../test-utils.js';
import {
TestViewMedia,
createCameraConfig,
createInitializedCamera,
} from '../test-utils.js';
describe('CameraManagerStore', async () => {
const configVisible = createCameraConfig({
@@ -19,7 +24,10 @@ describe('CameraManagerStore', async () => {
hide: true,
});
const engineFactory = new CameraManagerEngineFactory(mock<EntityRegistryManager>());
const engineFactory = new CameraManagerEngineFactory(
mock<EntityRegistryManager>(),
mock<DeviceRegistryManager>(),
);
const engineGeneric = await engineFactory.createEngine(Engine.Generic, {
stateWatcher: mock<StateWatcherSubscriptionInterface>(),
@@ -269,7 +277,7 @@ describe('CameraManagerStore', async () => {
expect(store.getAllDependentCameras('one')).toEqual(new Set(['one', 'two']));
});
it('should return cameras with specific capabilities', () => {
it('should return cameras with specific capabilities', async () => {
const store = new CameraManagerStore();
store.addCamera(
new Camera(
@@ -283,22 +291,18 @@ describe('CameraManagerStore', async () => {
),
);
store.addCamera(
new Camera(
await createInitializedCamera(
createCameraConfig({
id: 'two',
}),
engineGeneric,
{
capabilities: new Capabilities({
clips: true,
}),
},
new Capabilities({ clips: true }),
),
);
expect(store.getAllDependentCameras('one', 'clips')).toEqual(new Set(['two']));
});
it('should return cameras with specific capabilities inclusive of parent', () => {
it('should return cameras with specific capabilities inclusive of parent', async () => {
const store = new CameraManagerStore();
store.addCamera(
new Camera(
@@ -312,16 +316,12 @@ describe('CameraManagerStore', async () => {
),
);
store.addCamera(
new Camera(
await createInitializedCamera(
createCameraConfig({
id: 'two',
}),
engineGeneric,
{
capabilities: new Capabilities({
clips: true,
}),
},
new Capabilities({ clips: true }),
),
);
expect(store.getAllDependentCameras('one', 'clips', { inclusive: true })).toEqual(
@@ -330,19 +330,15 @@ describe('CameraManagerStore', async () => {
});
});
it('getCameraIDsWithCapability', () => {
it('getCameraIDsWithCapability', async () => {
const store = new CameraManagerStore();
store.addCamera(
new Camera(
await createInitializedCamera(
createCameraConfig({
id: 'one',
}),
engineGeneric,
{
capabilities: new Capabilities({
clips: true,
}),
},
new Capabilities({ clips: true }),
),
);
store.addCamera(