Files
chad/server/plugins/socket/webrtc/Channel.ts

120 lines
3.4 KiB
TypeScript

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<ChannelEvents> {
readonly id: string
readonly persistent: boolean
readonly #audioLevelObserver: types.AudioLevelObserver
readonly #activeSpeakerObserver: types.ActiveSpeakerObserver
readonly #clients = new Map<string, Client>()
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<Channel> {
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<void> {
if (producer.kind !== 'audio')
return
await this.#audioLevelObserver.addProducer({ producerId: producer.id })
await this.#activeSpeakerObserver.addProducer({ producerId: producer.id })
}
async wireClient(client: Client): Promise<void> {
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)
}
}
}
}