fix: Cache 2-way audio detection to avoid re-probing (#2716)
- Closes: #2299
This commit is contained in:
+64
-37
@@ -1,3 +1,6 @@
|
||||
import { add } from 'date-fns';
|
||||
|
||||
import { ExpiringEqualityCache } from '../cache/expiring-cache';
|
||||
import type { EnabledProxyConfig } from '../config/schema/common/proxy';
|
||||
import { homeAssistantSignAndFetch } from '../ha/fetch';
|
||||
import type { HomeAssistant } from '../ha/types';
|
||||
@@ -6,20 +9,17 @@ import type { Endpoint } from '../types';
|
||||
import { errorToConsole } from '../utils/basic';
|
||||
import { go2RTCStreamInfoSchema, type Go2RTCStreamInfo } from './types';
|
||||
|
||||
const getGo2RTCStreamMetadata = async (
|
||||
hass: HomeAssistant,
|
||||
endpoint: Endpoint,
|
||||
timeoutSeconds: number,
|
||||
): Promise<Go2RTCStreamInfo | null> => {
|
||||
try {
|
||||
return await homeAssistantSignAndFetch(hass, endpoint, go2RTCStreamInfoSchema, {
|
||||
timeoutSeconds,
|
||||
});
|
||||
} catch (e) {
|
||||
errorToConsole(e);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
// Cache 2-way capabilities: these only changes when go2rtc configuration or
|
||||
// camera hardware changes.
|
||||
const TWO_WAY_AUDIO_CACHE_SECONDS = 5 * 60;
|
||||
|
||||
// Page-scoped because Home Assistant builds a new card on dashboard navigation,
|
||||
// and every fetch makes go2rtc connect to the camera.
|
||||
// See: https://github.com/dermotduffy/advanced-camera-card/issues/2299
|
||||
const twoWayAudioSupportCache = new ExpiringEqualityCache<
|
||||
string,
|
||||
Promise<boolean | null>
|
||||
>();
|
||||
|
||||
const streamSupports2WayAudio = (streamInfo: Go2RTCStreamInfo | null): boolean => {
|
||||
if (!streamInfo?.producers) {
|
||||
@@ -35,18 +35,35 @@ const streamSupports2WayAudio = (streamInfo: Go2RTCStreamInfo | null): boolean =
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetch go2rtc metadata and determine if the stream supports 2-way audio.
|
||||
* Handles proxy transformation if proxy config requires it.
|
||||
* Returns false if the endpoint is not available or fetch fails.
|
||||
*
|
||||
* Note: Caller is responsible for checking if live_provider is 'go2rtc' before calling.
|
||||
*
|
||||
* @param hass Home Assistant instance.
|
||||
* @param go2rtcMetadataEndpoint The go2rtc metadata endpoint.
|
||||
* @param proxyConfig The resolved proxy configuration for live streams.
|
||||
* @returns True if supports 2-way audio, false otherwise.
|
||||
*/
|
||||
const fetch2WayAudioSupport = async (
|
||||
hass: HomeAssistant,
|
||||
metadataFetchTimeoutSeconds: number,
|
||||
go2rtcMetadataEndpoint: Endpoint,
|
||||
proxyConfig?: EnabledProxyConfig,
|
||||
): Promise<boolean | null> => {
|
||||
const endpoint = await createProxiedEndpointIfNecessary(
|
||||
hass,
|
||||
go2rtcMetadataEndpoint,
|
||||
proxyConfig,
|
||||
{ openLimit: 1 },
|
||||
);
|
||||
if (!endpoint) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return streamSupports2WayAudio(
|
||||
await homeAssistantSignAndFetch(hass, endpoint, go2RTCStreamInfoSchema, {
|
||||
timeoutSeconds: metadataFetchTimeoutSeconds,
|
||||
}),
|
||||
);
|
||||
} catch (e) {
|
||||
errorToConsole(e);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
// Caller must verify the live provider is go2rtc before calling.
|
||||
export const supports2WayAudio = async (
|
||||
hass: HomeAssistant,
|
||||
metadataFetchTimeoutSeconds: number,
|
||||
@@ -57,20 +74,30 @@ export const supports2WayAudio = async (
|
||||
return false;
|
||||
}
|
||||
|
||||
const endpoint = await createProxiedEndpointIfNecessary(
|
||||
hass,
|
||||
go2rtcMetadataEndpoint,
|
||||
proxyConfig,
|
||||
{ openLimit: 1 },
|
||||
);
|
||||
if (!endpoint) {
|
||||
return false;
|
||||
const key = go2rtcMetadataEndpoint.endpoint;
|
||||
const cachedPromise = twoWayAudioSupportCache.get(key);
|
||||
if (cachedPromise) {
|
||||
return (await cachedPromise) ?? false;
|
||||
}
|
||||
|
||||
const streamInfo = await getGo2RTCStreamMetadata(
|
||||
const request = fetch2WayAudioSupport(
|
||||
hass,
|
||||
endpoint,
|
||||
metadataFetchTimeoutSeconds,
|
||||
go2rtcMetadataEndpoint,
|
||||
proxyConfig,
|
||||
);
|
||||
return streamSupports2WayAudio(streamInfo);
|
||||
twoWayAudioSupportCache.set(
|
||||
key,
|
||||
request,
|
||||
add(new Date(), { seconds: TWO_WAY_AUDIO_CACHE_SECONDS }),
|
||||
);
|
||||
|
||||
const isSupported = await request;
|
||||
|
||||
// Inconclusive results are evicted so the next caller retries.
|
||||
if (isSupported === null) {
|
||||
twoWayAudioSupportCache.delete(key);
|
||||
}
|
||||
|
||||
return isSupported ?? false;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user