/* eslint-disable @typescript-eslint/unified-signatures */ import { EventEmitter } from 'node:events'; export interface SpeakingMap extends EventEmitter { /** * Emitted when a user starts speaking. * * @eventProperty */ on(event: 'start', listener: (userId: string) => void): this; /** * Emitted when a user ends speaking. * * @eventProperty */ on(event: 'end', listener: (userId: string) => void): this; } /** * Tracks the speaking states of users in a voice channel. */ export class SpeakingMap extends EventEmitter { /** * The delay after a packet is received from a user until they're marked as not speaking anymore. */ public static readonly DELAY = 100; /** * The currently speaking users, mapped to the milliseconds since UNIX epoch at which they started speaking. */ public readonly users: Map; private readonly speakingTimeouts: Map; public constructor() { super(); this.users = new Map(); this.speakingTimeouts = new Map(); } public onPacket(userId: string) { const timeout = this.speakingTimeouts.get(userId); if (timeout) { clearTimeout(timeout); } else { this.users.set(userId, Date.now()); this.emit('start', userId); } this.startTimeout(userId); } private startTimeout(userId: string) { this.speakingTimeouts.set( userId, setTimeout(() => { this.emit('end', userId); this.speakingTimeouts.delete(userId); this.users.delete(userId); }, SpeakingMap.DELAY), ); } }