120 lines
3.4 KiB
TypeScript
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)
|
|
}
|
|
}
|
|
}
|
|
}
|