UNPKG

@simplito/privmx-webendpoint

Version:

PrivMX Web Endpoint library

65 lines (64 loc) 2.22 kB
"use strict"; 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;