import type { types } from 'mediasoup' import type { ActiveSpeakerObserverDominantSpeaker } from 'mediasoup/types' import type { Client } from './Client.ts' import { EventEmitter } from 'node:events' interface ChannelEvents { 'speaking-peers': [{ socketId: string volume: number }[]] 'silence': [] 'active-speaker': [socketId: string] 'empty': [] } export class Channel extends EventEmitter { readonly id: string readonly persistent: boolean readonly #audioLevelObserver: types.AudioLevelObserver readonly #activeSpeakerObserver: types.ActiveSpeakerObserver readonly #clients = new Map() private constructor( id: string, persistent: boolean, audioLevelObserver: types.AudioLevelObserver, activeSpeakerObserver: types.ActiveSpeakerObserver, ) { super() this.id = id this.persistent = persistent this.#audioLevelObserver = audioLevelObserver this.#activeSpeakerObserver = activeSpeakerObserver this.#audioLevelObserver.on('volumes', (volumes: types.AudioLevelObserverVolume[]) => { this.emit('speaking-peers', volumes.map(({ producer, volume }) => { const { socketId } = producer.appData as { socketId: string } return { socketId, volume } })) }) this.#audioLevelObserver.on('silence', () => { this.emit('silence') }) this.#activeSpeakerObserver.on('dominantspeaker', ({ producer }: ActiveSpeakerObserverDominantSpeaker) => { const { socketId } = producer.appData as { socketId: string } this.emit('active-speaker', socketId) }) } static async create(id: string, persistent: boolean, router: types.Router): Promise { const audioLevelObserver = await router.createAudioLevelObserver({ maxEntries: 10, threshold: -80, interval: 800, }) const activeSpeakerObserver = await router.createActiveSpeakerObserver() return new Channel(id, persistent, audioLevelObserver, activeSpeakerObserver) } get clients(): Client[] { return Array.from(this.#clients.values()) } get size(): number { return this.#clients.size } getClient(socketId: string): Client | undefined { return this.#clients.get(socketId) } addClient(client: Client): void { client.channelId = this.id this.#clients.set(client.socketId, client) } kickClient(client: Client): void { this.#clients.delete(client.socketId) if (this.#clients.size === 0) this.emit('empty') } async addAudioProducer(producer: types.Producer): Promise { if (producer.kind !== 'audio') return await this.#audioLevelObserver.addProducer({ producerId: producer.id }) await this.#activeSpeakerObserver.addProducer({ producerId: producer.id }) } async wireClient(client: Client): Promise { for (const otherClient of this.#clients.values()) { if (otherClient.socketId === client.socketId) continue for (const producer of otherClient.producers.values()) { await client.createConsumerFor(producer, otherClient.socketId) } for (const producer of client.producers.values()) { await otherClient.createConsumerFor(producer, client.socketId) } } } unwireClient(client: Client): void { for (const otherClient of this.#clients.values()) { for (const producerId of client.producers.keys()) { otherClient.removeConsumersOf(producerId) } } } }