@simplito/privmx-webendpoint
Version:
PrivMX Web Endpoint library
65 lines (64 loc) • 2.22 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.ActiveSpeakerDetector = exports.DEFAULTS = void 0;
exports.DEFAULTS = {
rmsEmaAlpha: 0.2, // szybka reakcja na mowę
noiseEmaAlpha: 0.02, // wolna adaptacja tła
thresholdOffset: 6, // dB (jeśli RMS w dB)
activityWindowMs: 400,
holdMs: 200,
};
class ActiveSpeakerDetector {
opts;
speakers = new Map();
activeSpeaker = null;
constructor(opts) {
this.opts = opts;
}
onFrame({ id, rms, timestamp }) {
const state = this.getOrCreateState(id, rms);
state.emaRms = this.opts.rmsEmaAlpha * rms + (1 - this.opts.rmsEmaAlpha) * state.emaRms;
if (state.emaRms < state.noiseFloor + this.opts.thresholdOffset) {
state.noiseFloor =
this.opts.noiseEmaAlpha * state.emaRms +
(1 - this.opts.noiseEmaAlpha) * state.noiseFloor;
}
const adaptiveThreshold = state.noiseFloor + this.opts.thresholdOffset;
if (state.emaRms >= adaptiveThreshold) {
state.lastAboveThresholdTs = timestamp;
state.activeSince = timestamp;
state.active = true;
}
else {
state.active = false;
state.activeSince = 0;
}
return this.selectActiveSpeakers(timestamp);
}
selectActiveSpeakers(now) {
let bestId = null;
let bestRms = -Infinity;
for (const [id, state] of this.speakers.entries()) {
state.active =
now - state.lastAboveThresholdTs <= this.opts.activityWindowMs &&
now - state.activeSince < this.opts.holdMs;
}
return Array.from(this.speakers.values());
}
getOrCreateState(id, rms) {
let state = this.speakers.get(id);
if (!state) {
state = {
streamId: id,
emaRms: rms,
noiseFloor: rms,
lastAboveThresholdTs: -Infinity,
active: false,
activeSince: 0,
};
this.speakers.set(id, state);
}
return state;
}
}
exports.ActiveSpeakerDetector = ActiveSpeakerDetector;