import { GatewayOpcodes } from 'discord-api-types/v9' ; import type { AudioPlayer } from './audio' ; import type { VoiceConnection } from './VoiceConnection' ; export interface JoinConfig { guildId : string ; channelId : string | null ; selfDeaf : boolean ; selfMute : boolean ; group : string ; } param config export function createJoinVoiceChannelPayload ( config ) { return { op : GatewayOpcodes . VoiceStateUpdate , d : { guild_id : config . guildId , channel_id : config . channelId , self_deaf : config . selfDeaf , self_mute : config . selfMute , } , } ; } const groups = new Map < string , Map < string , VoiceConnection > > ( ) ; groups . set ( 'default' , new Map ( ) ) ; function getOrCreateGroup ( group ) { const existing = groups . get ( group ) ; if ( existing ) return existing ; const map = new Map < string , VoiceConnection > ( ) ; groups . set ( group , map ) ; return map ; } returns export function getGroups ( ) { return groups ; } param group returns export function getVoiceConnections ( group ? ) ; param group returns export function getVoiceConnections ( group ) ; param group returns export function getVoiceConnections ( group = 'default' ) { return groups . get ( group ) ; } param guildId param group returns export function getVoiceConnection ( guildId , group = 'default' ) { return getVoiceConnections ( group ) ?. get ( guildId ) ; } export function untrackVoiceConnection ( voiceConnection ) { return getVoiceConnections ( voiceConnection . joinConfig . group ) ?. delete ( voiceConnection . joinConfig . guildId ) ; } export function trackVoiceConnection ( voiceConnection ) { return getOrCreateGroup ( voiceConnection . joinConfig . group ) . set ( voiceConnection . joinConfig . guildId , voiceConnection ) ; } const FRAME_LENGTH = 20 ; let audioCycleInterval ; let nextTime = - 1 ; /**
 * A list of created audio players that are still active and haven't been destroyed.
 */ const audioPlayers = [ ] ; /**
 * Called roughly every 20 milliseconds. Dispatches audio from all players, and then gets the players to prepare
 * the next audio frame.
 */ function audioCycleStep ( ) { if ( nextTime === - 1 ) return ; nextTime += FRAME_LENGTH ; const available = audioPlayers . filter ( ( player ) => player . checkPlayable ( ) ) ; available . forEach ( ( player ) => player [ '_stepDispatch' ] ( ) ) ; prepareNextAudioFrame ( available ) ; } /**
 * Recursively gets the players that have been passed as parameters to prepare audio frames that can be played
 * at the start of the next cycle.
 */ function prepareNextAudioFrame ( players ) { const nextPlayer = players . shift ( ) ; if ( ! nextPlayer ) { if ( nextTime !== - 1 ) { audioCycleInterval = setTimeout ( ( ) => audioCycleStep ( ) , nextTime - Date . now ( ) ) ; } return ; } nextPlayer [ '_stepPrepare' ] ( ) ; setImmediate ( ( ) => prepareNextAudioFrame ( players ) ) ; } param target returns export function hasAudioPlayer ( target ) { return audioPlayers . includes ( target ) ; } param player export function addAudioPlayer ( player ) { if ( hasAudioPlayer ( player ) ) return player ; audioPlayers . push ( player ) ; if ( audioPlayers . length === 1 ) { nextTime = Date . now ( ) ; setImmediate ( ( ) => audioCycleStep ( ) ) ; } return player ; } /**
 * Removes an audio player from the data store tracking list, if it is present there.
 */ export function deleteAudioPlayer ( player ) { const index = audioPlayers . indexOf ( player ) ; if ( index === - 1 ) return ; audioPlayers . splice ( index , 1 ) ; if ( audioPlayers . length === 0 ) { nextTime = - 1 ; if ( typeof audioCycleInterval !== 'undefined' ) clearTimeout ( audioCycleInterval ) ; } }