mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-18 05:25:40 +09:00
fix(voice): recalibrate stored participant and stream volumes too (#1790)
This commit is contained in:
1 parent
06c42ce02f
commit
d5daf61dbd
4 files changed
+74
-3
No files matched your search
@@ -12,6 +12,7 @@ import {
|
|||||||
clampVoiceTrackTotalGain,
|
clampVoiceTrackTotalGain,
|
||||||
clampVoiceVolumePercent,
|
clampVoiceVolumePercent,
|
||||||
composeVoiceVolumeGain,
|
composeVoiceVolumeGain,
|
||||||
|
recalibrateStoredVoiceVolumes,
|
||||||
} from '@app/features/voice/utils/VoiceVolumeUtils';
|
} from '@app/features/voice/utils/VoiceVolumeUtils';
|
||||||
import type {RemoteAudioTrack, RemoteParticipant, Room} from 'livekit-client';
|
import type {RemoteAudioTrack, RemoteParticipant, Room} from 'livekit-client';
|
||||||
import {makeAutoObservable} from 'mobx';
|
import {makeAutoObservable} from 'mobx';
|
||||||
@@ -62,6 +63,7 @@ function getVoiceConnectionContext(): VoiceConnectionContextAccess {
|
|||||||
class ParticipantVolume {
|
class ParticipantVolume {
|
||||||
volumes: Record<string, number> = {};
|
volumes: Record<string, number> = {};
|
||||||
localMutes: Record<string, boolean> = {};
|
localMutes: Record<string, boolean> = {};
|
||||||
|
outputVolumeRecalibratedV1 = false;
|
||||||
connectionVolumesByLocalConnectionId: Record<string, Record<string, number>> = {};
|
connectionVolumesByLocalConnectionId: Record<string, Record<string, number>> = {};
|
||||||
private listeners = new Set<() => void>();
|
private listeners = new Set<() => void>();
|
||||||
|
|
||||||
@@ -81,7 +83,17 @@ class ParticipantVolume {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async initPersistence(): Promise<void> {
|
private async initPersistence(): Promise<void> {
|
||||||
await makePersistent(this, 'ParticipantVolume', ['volumes', 'localMutes']);
|
await makePersistent(this, 'ParticipantVolume', ['volumes', 'localMutes', 'outputVolumeRecalibratedV1']);
|
||||||
|
this.applyOutputVolumeRecalibration();
|
||||||
|
}
|
||||||
|
|
||||||
|
private applyOutputVolumeRecalibration(): void {
|
||||||
|
if (this.outputVolumeRecalibratedV1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.volumes = recalibrateStoredVoiceVolumes(this.volumes);
|
||||||
|
this.outputVolumeRecalibratedV1 = true;
|
||||||
|
this.notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
setVolume(userId: string, volume: number): void {
|
setVolume(userId: string, volume: number): void {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import {Logger} from '@app/features/platform/utils/AppLogger';
|
import {Logger} from '@app/features/platform/utils/AppLogger';
|
||||||
import {makePersistent} from '@app/features/platform/utils/MobXPersistence';
|
import {makePersistent} from '@app/features/platform/utils/MobXPersistence';
|
||||||
import {clampVoiceVolumePercent} from '@app/features/voice/utils/VoiceVolumeUtils';
|
import {clampVoiceVolumePercent, recalibrateStoredVoiceVolumePercent} from '@app/features/voice/utils/VoiceVolumeUtils';
|
||||||
import {
|
import {
|
||||||
STREAM_AUDIO_PREFS_PRUNE_INTERVAL_MS,
|
STREAM_AUDIO_PREFS_PRUNE_INTERVAL_MS,
|
||||||
STREAM_AUDIO_PREFS_TOUCH_INTERVAL_MS,
|
STREAM_AUDIO_PREFS_TOUCH_INTERVAL_MS,
|
||||||
@@ -20,6 +20,7 @@ interface StreamAudioPrefsEntry {
|
|||||||
|
|
||||||
class StreamAudioPrefs {
|
class StreamAudioPrefs {
|
||||||
entries: Record<string, StreamAudioPrefsEntry> = {};
|
entries: Record<string, StreamAudioPrefsEntry> = {};
|
||||||
|
outputVolumeRecalibratedV1 = false;
|
||||||
audioPrefsRevision = 0;
|
audioPrefsRevision = 0;
|
||||||
private pruneIntervalId: number | null = null;
|
private pruneIntervalId: number | null = null;
|
||||||
private listeners = new Set<() => void>();
|
private listeners = new Set<() => void>();
|
||||||
@@ -42,12 +43,26 @@ class StreamAudioPrefs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async initPersistence(): Promise<void> {
|
private async initPersistence(): Promise<void> {
|
||||||
await makePersistent(this, 'StreamAudioPrefs', ['entries']);
|
await makePersistent(this, 'StreamAudioPrefs', ['entries', 'outputVolumeRecalibratedV1']);
|
||||||
|
this.applyOutputVolumeRecalibration();
|
||||||
if (this.hasAnyEntries()) {
|
if (this.hasAnyEntries()) {
|
||||||
this.startPruneInterval();
|
this.startPruneInterval();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private applyOutputVolumeRecalibration(): void {
|
||||||
|
if (this.outputVolumeRecalibratedV1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.entries = Object.fromEntries(
|
||||||
|
Object.entries(this.entries).map(([streamKey, entry]) => [
|
||||||
|
streamKey,
|
||||||
|
{...entry, volume: recalibrateStoredVoiceVolumePercent(entry.volume)},
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
this.outputVolumeRecalibratedV1 = true;
|
||||||
|
}
|
||||||
|
|
||||||
hasEntry(streamKey: string): boolean {
|
hasEntry(streamKey: string): boolean {
|
||||||
return streamKey in this.entries;
|
return streamKey in this.entries;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import {
|
|||||||
boostedVoiceVolumePercentToTrackVolume,
|
boostedVoiceVolumePercentToTrackVolume,
|
||||||
clampVoiceTrackTotalGain,
|
clampVoiceTrackTotalGain,
|
||||||
composeVoiceVolumeGain,
|
composeVoiceVolumeGain,
|
||||||
|
recalibrateStoredVoiceVolumePercent,
|
||||||
|
recalibrateStoredVoiceVolumes,
|
||||||
VOICE_TRACK_MAX_TOTAL_GAIN,
|
VOICE_TRACK_MAX_TOTAL_GAIN,
|
||||||
VOICE_VOLUME_MAX_GAIN,
|
VOICE_VOLUME_MAX_GAIN,
|
||||||
VOICE_VOLUME_MAX_PERCENT,
|
VOICE_VOLUME_MAX_PERCENT,
|
||||||
@@ -70,3 +72,30 @@ describe('clampVoiceTrackTotalGain', () => {
|
|||||||
expect(clampVoiceTrackTotalGain(-1)).toBe(0);
|
expect(clampVoiceTrackTotalGain(-1)).toBe(0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('recalibrateStoredVoiceVolumePercent', () => {
|
||||||
|
it('pulls every boosted stored value back to unity', () => {
|
||||||
|
expect(recalibrateStoredVoiceVolumePercent(200)).toBe(100);
|
||||||
|
expect(recalibrateStoredVoiceVolumePercent(150)).toBe(100);
|
||||||
|
expect(recalibrateStoredVoiceVolumePercent(101)).toBe(100);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('leaves unity and quieter values untouched', () => {
|
||||||
|
expect(recalibrateStoredVoiceVolumePercent(100)).toBe(100);
|
||||||
|
expect(recalibrateStoredVoiceVolumePercent(40)).toBe(40);
|
||||||
|
expect(recalibrateStoredVoiceVolumePercent(0)).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('keeps the same object when nothing needs recalibrating', () => {
|
||||||
|
const volumes = {alice: 100, bob: 40};
|
||||||
|
expect(recalibrateStoredVoiceVolumes(volumes)).toBe(volumes);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('caps only the boosted entries in a stored map', () => {
|
||||||
|
expect(recalibrateStoredVoiceVolumes({alice: 200, bob: 60, carol: 150})).toEqual({
|
||||||
|
alice: 100,
|
||||||
|
bob: 60,
|
||||||
|
carol: 100,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -37,6 +37,21 @@ export function composeVoiceVolumeGain(...volumePercents: Array<number>): number
|
|||||||
return volumePercents.reduce((gain, percent) => gain * boostedVoiceVolumePercentToTrackVolume(percent), 1);
|
return volumePercents.reduce((gain, percent) => gain * boostedVoiceVolumePercentToTrackVolume(percent), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function recalibrateStoredVoiceVolumePercent(value: number): number {
|
||||||
|
if (!Number.isFinite(value) || value <= UNITY_GAIN_PERCENT) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
return UNITY_GAIN_PERCENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function recalibrateStoredVoiceVolumes<T extends Record<string, number>>(volumes: T): T {
|
||||||
|
const entries = Object.entries(volumes);
|
||||||
|
if (!entries.some(([, value]) => recalibrateStoredVoiceVolumePercent(value) !== value)) {
|
||||||
|
return volumes;
|
||||||
|
}
|
||||||
|
return Object.fromEntries(entries.map(([key, value]) => [key, recalibrateStoredVoiceVolumePercent(value)])) as T;
|
||||||
|
}
|
||||||
|
|
||||||
export function clampVoiceTrackTotalGain(gain: number): number {
|
export function clampVoiceTrackTotalGain(gain: number): number {
|
||||||
if (!Number.isFinite(gain)) {
|
if (!Number.isFinite(gain)) {
|
||||||
return 1;
|
return 1;
|
||||||
|
|||||||
Reference in new issue
Block a user