feat(audio): open the AI noise suppression gate after passed acceptance
Two-person call on 2026-08-17: filter effective, keyboard gone, unmuting intact on both sides - the acceptance that gates this flag, per the standing rule from the v0.5.0 incident. The gate flips to true, which brings the checkbox and slider back into the in-call settings. The dev override stays in the code as the tool for the next test phase of this kind; a test pins that it does nothing without the regular setting. The rollback lever for any regression is the gate itself, not a deployment revert. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
df4e5eeb85
commit
e3f8a8570a
+12
-10
@@ -145,11 +145,11 @@ Daraus zwei **stehende Regeln** fuer diesen Fork:
|
|||||||
umgeschriebenes Feld. Umgesetzt als bedingtes Spread in `generateRoomOption`
|
umgeschriebenes Feld. Umgesetzt als bedingtes Spread in `generateRoomOption`
|
||||||
(`ConnectionFactory.ts`), abgesichert durch Regressionstests in
|
(`ConnectionFactory.ts`), abgesichert durch Regressionstests in
|
||||||
`ECConnectionFactory.test.ts` (die auf dem alten Stand nachweislich rot waren).
|
`ECConnectionFactory.test.ts` (die auf dem alten Stand nachweislich rot waren).
|
||||||
2. **Feature-Tor:** `AI_NOISE_SUPPRESSION_AVAILABLE` in `src/livekit/aiNoiseSuppression.ts`
|
2. **Feature-Tor:** `AI_NOISE_SUPPRESSION_AVAILABLE` in `src/livekit/aiNoiseSuppression.ts`.
|
||||||
steht auf `false`, bis die Track-Anbindung im Call zu zweit abgenommen ist.
|
Seit der bestandenen Abnahme (2026-08-17, Call zu zweit: Filter wirksam, Tastatur weg,
|
||||||
Das Tor neutralisiert auch Clients, die die Einstellung bereits aktiviert hatten
|
Entmuten intakt) steht es auf `true`. **Bei einer Regression zuerst das Tor schliessen**
|
||||||
(localStorage) - deren Zustand ueberlebt jedes Deployment. Die Bedienelemente in
|
(`false`) - das legt Filter UND Bedienelemente still, neutralisiert auch Clients mit
|
||||||
`SettingsModal.tsx` sind hinter demselben Tor versteckt.
|
aktivierter Einstellung im localStorage, und braucht kein Zurueckrollen der Auslieferung.
|
||||||
|
|
||||||
### Weg B (Entscheidung sorb 2026-08-16): AudioContext nur auf dem Mikrofon-Track
|
### Weg B (Entscheidung sorb 2026-08-16): AudioContext nur auf dem Mikrofon-Track
|
||||||
|
|
||||||
@@ -167,11 +167,13 @@ Die `audioCaptureDefaults` tragen damit in **keinem** Zustand mehr einen
|
|||||||
noch `noiseSuppression` umgeschaltet, damit Browser- und KI-Filter nicht
|
noch `noiseSuppression` umgeschaltet, damit Browser- und KI-Filter nicht
|
||||||
gegeneinander arbeiten.
|
gegeneinander arbeiten.
|
||||||
|
|
||||||
**Testausrollung:** Das Tor bleibt zu. Ein einzelner Test-Client aktiviert den Filter
|
**Testausrollung (durchlaufen, 2026-08-17 bestanden):** Waehrend das Tor zu war,
|
||||||
ueber ZWEI localStorage-Schluessel: `matrix-setting-ai-noise-suppression-dev = true`
|
aktivierte ein einzelner Test-Client den Filter ueber ZWEI localStorage-Schluessel:
|
||||||
(Entwickler-Schalter, bewusst ohne UI) **und** `matrix-setting-ai-noise-suppression =
|
`matrix-setting-ai-noise-suppression-dev = true` (Entwickler-Schalter, bewusst ohne UI)
|
||||||
true`. Die normale Einstellung allein bleibt wirkungslos. Erst nach bestandener
|
**und** `matrix-setting-ai-noise-suppression = true`. Der Entwickler-Schalter bleibt im
|
||||||
Abnahme im Call zu zweit wird das Tor geoeffnet und die UI wieder sichtbar.
|
Code (harmlos: ohne die eigentliche Einstellung wirkungslos, per Test abgesichert) -
|
||||||
|
er ist das Werkzeug fuer die naechste Testphase dieser Art. Die Bedienelemente sitzen
|
||||||
|
in den Einstellungen **im laufenden Call-Widget** (Audio-Reiter), nicht in Element Web.
|
||||||
Einstellungs-Aenderungen wirken ab dem naechsten Beitritt, nicht live im Call.
|
Einstellungs-Aenderungen wirken ab dem naechsten Beitritt, nicht live im Call.
|
||||||
|
|
||||||
## 6. Produktname im Widget (`.env.production`)
|
## 6. Produktname im Widget (`.env.production`)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@sorb/threadnet-call-embedded",
|
"name": "@sorb/threadnet-call-embedded",
|
||||||
"version": "0.19.2-threadnet.10",
|
"version": "0.19.2-threadnet.11",
|
||||||
"files": [
|
"files": [
|
||||||
"README.md",
|
"README.md",
|
||||||
"LICENSE-AGPL-3.0",
|
"LICENSE-AGPL-3.0",
|
||||||
|
|||||||
@@ -66,9 +66,11 @@ describe("applyAiNoiseSuppression (Weg B, #0054)", () => {
|
|||||||
expect(calls).toEqual([]);
|
expect(calls).toEqual([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("a stale enabled setting without the dev override touches nothing", async () => {
|
test("the dev override alone (setting off) touches nothing", async () => {
|
||||||
// sorbs Zustand nach dem Diagnosefenster: Einstellung an, Tor zu.
|
// UND-Verknuepfung im Praedikat: der Schalter der Testphase darf ohne die
|
||||||
aiNoiseSuppressionSetting.setValue(true);
|
// eigentliche Einstellung nichts bewirken.
|
||||||
|
aiNoiseSuppressionDevSetting.setValue(true);
|
||||||
|
aiNoiseSuppressionSetting.setValue(false);
|
||||||
const { track, calls } = makeTrack();
|
const { track, calls } = makeTrack();
|
||||||
|
|
||||||
await applyAiNoiseSuppression(track, logger);
|
await applyAiNoiseSuppression(track, logger);
|
||||||
@@ -79,7 +81,6 @@ describe("applyAiNoiseSuppression (Weg B, #0054)", () => {
|
|||||||
test("sets the audio context BEFORE attaching the processor", async () => {
|
test("sets the audio context BEFORE attaching the processor", async () => {
|
||||||
// Genau die Reihenfolge, an der v0.5.0 gescheitert ist: setProcessor
|
// Genau die Reihenfolge, an der v0.5.0 gescheitert ist: setProcessor
|
||||||
// ohne AudioContext auf dem Track wirft in LiveKit.
|
// ohne AudioContext auf dem Track wirft in LiveKit.
|
||||||
aiNoiseSuppressionDevSetting.setValue(true);
|
|
||||||
aiNoiseSuppressionSetting.setValue(true);
|
aiNoiseSuppressionSetting.setValue(true);
|
||||||
const { track, calls } = makeTrack();
|
const { track, calls } = makeTrack();
|
||||||
|
|
||||||
@@ -91,7 +92,6 @@ describe("applyAiNoiseSuppression (Weg B, #0054)", () => {
|
|||||||
test("a failing processor attach is contained and does not throw", async () => {
|
test("a failing processor attach is contained and does not throw", async () => {
|
||||||
// Der Track ist zu diesem Zeitpunkt publiziert - ein Filterfehler darf
|
// Der Track ist zu diesem Zeitpunkt publiziert - ein Filterfehler darf
|
||||||
// den Call nicht mehr erreichen.
|
// den Call nicht mehr erreichen.
|
||||||
aiNoiseSuppressionDevSetting.setValue(true);
|
|
||||||
aiNoiseSuppressionSetting.setValue(true);
|
aiNoiseSuppressionSetting.setValue(true);
|
||||||
const { track, setProcessor } = makeTrack();
|
const { track, setProcessor } = makeTrack();
|
||||||
setProcessor.mockRejectedValue(new Error("wasm sagt nein"));
|
setProcessor.mockRejectedValue(new Error("wasm sagt nein"));
|
||||||
|
|||||||
@@ -39,19 +39,20 @@ import {
|
|||||||
const ASSET_PFAD = "assets/dfn3";
|
const ASSET_PFAD = "assets/dfn3";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Feature-Tor: solange `false`, ist der Filter fuer ALLE Clients aus - auch
|
* Feature-Tor: bei `false` ist der Filter fuer ALLE Clients aus - auch fuer
|
||||||
* fuer solche, die die Einstellung frueher aktiviert haben (localStorage).
|
* solche, die die Einstellung frueher aktiviert haben (localStorage).
|
||||||
*
|
*
|
||||||
* Warum: `LocalAudioTrack.setProcessor()` verlangt einen AudioContext auf dem
|
* Geschichte: v0.5.0 setzte den Prozessor ueber die Capture-Defaults, ohne
|
||||||
* Track. Den bekommt er nur, wenn der LiveKit-Raum mit `webAudioMix` gebaut
|
* dass der Track einen AudioContext hatte - LiveKit warf "Audio context needs
|
||||||
* wird - und genau das steht in `ConnectionFactory.ts` als unerprobtes
|
* to be set on LocalAudioTrack in order to enable processors", und das
|
||||||
* Upstream-TODO auskommentiert. Ohne AudioContext wirft LiveKit beim Entmuten
|
* Entmuten brach fuer alle (Produktionsvorfall 2026-08-16). Seit Weg B
|
||||||
* "Audio context needs to be set on LocalAudioTrack in order to enable
|
* (#0054) haengt sich der Filter NACH der Publikation an den Mikrofon-Track,
|
||||||
* processors", und es wird nie ein Audio-Track publiziert (Produktionsvorfall
|
* mit eigenem AudioContext nur dort. Abnahme im Call zu zweit bestanden am
|
||||||
* 2026-08-16, v0.5.0). Erst freischalten, wenn `webAudioMix` entschieden und
|
* 2026-08-17 (Filter wirksam, Tastatur weg, Entmuten intakt) - seitdem ist
|
||||||
* im Call zu zweit getestet ist (Folgearbeit zu ADR-0018).
|
* das Tor offen. Bei einer Regression zuerst wieder auf `false` stellen:
|
||||||
|
* das legt den Filter still, ohne die Auslieferung anzufassen.
|
||||||
*/
|
*/
|
||||||
export const AI_NOISE_SUPPRESSION_AVAILABLE = false;
|
export const AI_NOISE_SUPPRESSION_AVAILABLE = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ist der Filter wirksam? Nur wenn das Tor offen ist (oder der
|
* Ist der Filter wirksam? Nur wenn das Tor offen ist (oder der
|
||||||
|
|||||||
@@ -149,27 +149,11 @@ describe("ECConnectionFactory - AI noise suppression off-path (ADR-0018)", () =>
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test("a stale enabled setting (localStorage) is neutralized by the feature gate", () => {
|
test("with the filter active the defaults carry no processor key either (way B)", () => {
|
||||||
// Genau der Zustand eines Clients, der den Filter aktiviert hatte, bevor
|
|
||||||
// das Tor geschlossen wurde: Einstellung an, Tor zu.
|
|
||||||
aiNoiseSuppressionSetting.setValue(true);
|
|
||||||
noiseSuppressionSetting.setValue(true);
|
|
||||||
|
|
||||||
const options = buildRoomOptions();
|
|
||||||
expect(options.audioCaptureDefaults).not.toHaveProperty("processor");
|
|
||||||
expect(options.audioCaptureDefaults).toMatchObject({
|
|
||||||
noiseSuppression: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
aiNoiseSuppressionSetting.setValue(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("even with the filter active (dev override) the defaults carry no processor key", () => {
|
|
||||||
// Weg B aus #0054: der Prozessor wird NACH der Publikation an den Track
|
// Weg B aus #0054: der Prozessor wird NACH der Publikation an den Track
|
||||||
// gehaengt, nie ueber die Capture-Defaults - die getUserMedia-Constraints
|
// gehaengt, nie ueber die Capture-Defaults - die getUserMedia-Constraints
|
||||||
// muessen in JEDEM Zustand upstream-identisch bleiben. Hier aendert sich
|
// muessen in JEDEM Zustand upstream-identisch bleiben. Hier aendert sich
|
||||||
// nur noiseSuppression, damit nicht zwei Filter gegeneinander arbeiten.
|
// nur noiseSuppression, damit nicht zwei Filter gegeneinander arbeiten.
|
||||||
aiNoiseSuppressionDevSetting.setValue(true);
|
|
||||||
aiNoiseSuppressionSetting.setValue(true);
|
aiNoiseSuppressionSetting.setValue(true);
|
||||||
noiseSuppressionSetting.setValue(true);
|
noiseSuppressionSetting.setValue(true);
|
||||||
|
|
||||||
@@ -179,9 +163,24 @@ describe("ECConnectionFactory - AI noise suppression off-path (ADR-0018)", () =>
|
|||||||
noiseSuppression: false,
|
noiseSuppression: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
aiNoiseSuppressionDevSetting.setValue(false);
|
|
||||||
aiNoiseSuppressionSetting.setValue(false);
|
aiNoiseSuppressionSetting.setValue(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("the dev override alone (setting off) changes nothing", () => {
|
||||||
|
// Das Praedikat ist eine UND-Verknuepfung: der Entwickler-Schalter der
|
||||||
|
// Testphase darf ohne die eigentliche Einstellung nichts bewirken.
|
||||||
|
aiNoiseSuppressionDevSetting.setValue(true);
|
||||||
|
aiNoiseSuppressionSetting.setValue(false);
|
||||||
|
noiseSuppressionSetting.setValue(true);
|
||||||
|
|
||||||
|
const options = buildRoomOptions();
|
||||||
|
expect(options.audioCaptureDefaults).not.toHaveProperty("processor");
|
||||||
|
expect(options.audioCaptureDefaults).toMatchObject({
|
||||||
|
noiseSuppression: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
aiNoiseSuppressionDevSetting.setValue(false);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("ECConnectionFactory - ControlledAudioDevice", () => {
|
describe("ECConnectionFactory - ControlledAudioDevice", () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user