UNPKG

detritus-client

Version:

A Typescript NodeJS library to interact with Discord's API, both Rest and Gateway.

267 lines (266 loc) 9.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.VoiceConnection = void 0; const detritus_client_socket_1 = require("detritus-client-socket"); const detritus_utils_1 = require("detritus-utils"); const { MediaCodecs, MediaCodecTypes, } = detritus_client_socket_1.Constants; const basecollection_1 = require("../collections/basecollection"); const constants_1 = require("../constants"); const audioformat_1 = require("../utils/audioformat"); const encoders_1 = require("./encoders"); const handler_1 = require("./handler"); const OpusProperties = [ 'application', 'channels', 'sampleRate', 'use', ]; /** * Voice Connection * @category Media */ class VoiceConnection extends detritus_utils_1.EventSpewer { constructor(client, gateway, options = {}) { super(); this.client = client; this.gateway = gateway; this.handler = new handler_1.MediaHandler(this); this.decodeAudio = !!options.decodeAudio; this.opusDecoder = null; this.opusDecoders = new basecollection_1.BaseCollection(); this.opusEncoder = null; this.formats = { audio: new audioformat_1.AudioFormat({ channels: constants_1.DiscordOpusFormat.CHANNELS, sampleRate: constants_1.DiscordOpusFormat.SAMPLE_RATE, }), }; if (options.opusDecoder || options.decodeAudio) { this.setOpusDecoder(options.opusDecoder); this.decodeAudio = true; } if (options.opusEncoder) { this.setOpusEncoder(options.opusEncoder); } Object.defineProperties(this, { client: { enumerable: false, writable: false }, decodeAudio: { configurable: true, enumerable: false, writable: false }, formats: { writable: false }, gateway: { enumerable: false, writable: false }, handler: { enumerable: false, writable: false }, opusDecoder: { configurable: true, enumerable: false, writable: false }, opusDecoders: { enumerable: false, writable: false }, opusEncoder: { configurable: true, enumerable: false, writable: false }, }); } get channel() { if (this.channelId !== null) { return this.client.channels.get(this.channelId) || null; } return null; } get channelId() { return this.gateway.channelId; } get guild() { if (this.guildId !== null) { return this.client.guilds.get(this.guildId) || null; } return null; } get guildId() { return this.gateway.guildId; } get killed() { return this.gateway.killed; } get member() { if (this.guildId !== null) { return this.client.members.get(this.guildId, this.userId) || null; } return null; } get serverId() { return this.gateway.serverId; } get user() { return this.client.users.get(this.userId) || null; } get userId() { return this.gateway.userId; } get voiceState() { return this.client.voiceStates.get(this.serverId, this.userId) || null; } decode(userId, data, options = {}) { const format = options.format || MediaCodecTypes.AUDIO; const frameDuration = options.frameDuration || this.formats.audio.frameDuration; const type = options.type || MediaCodecs.OPUS; switch (format) { case MediaCodecTypes.AUDIO: { if (type === MediaCodecs.OPUS) { const opusDecoder = this.fetchOpusDecoder(userId); return opusDecoder.decode(data, frameDuration); } } ; break; } throw new Error(`Cannot decode ${options.format}-${options.type} type data`); } fetchOpusDecoder(userId) { if (!this.opusDecoder) { throw new Error('Create an opus decoder before trying to decode opus!'); } if (this.opusDecoders.has(userId)) { return this.opusDecoders.get(userId); } const opusDecoder = new encoders_1.Opus.AudioOpus(this.opusDecoder.sampleRate, this.opusDecoder.channels, { application: this.opusDecoder.application, use: this.opusDecoder.use, }); this.opusDecoders.set(userId, opusDecoder); return opusDecoder; } kill() { this.client.voiceConnections.delete(this.serverId); this.gateway.kill(); this.setOpusEncoder({ kill: true }); this.setOpusDecoder({ kill: true }); this.emit('killed'); this.removeAllListeners(); } sendAudio(data, options = {}) { if (this.killed) { return; } if (!this.gateway.transport) { throw new Error('Transport isn\'t initialized yet!'); } if (!options.isOpus) { if (this.opusEncoder === null) { throw new Error('Cannot send in Non-Opus Data without an opus encoder!'); } data = this.opusEncoder.encode(data, this.formats.audio.frameDuration); } // assume its 48000 sample rate, 2 channels this.gateway.transport.sendAudioFrame(data, { incrementTimestamp: true, timestamp: this.formats.audio.samplesPerFrame, }); } sendAudioSilenceFrame() { if (this.killed) { return; } if (!this.gateway.transport) { throw new Error('Transport isn\'t initialized yet!'); } return this.gateway.transport.sendAudioSilenceFrame(); } setDecodeAudio(value) { Object.defineProperty(this, 'decodeAudio', { value }); } setOpusDecoder(options = {}) { options = Object.assign({}, options); if (options.kill) { if (this.opusDecoder !== null) { Object.defineProperty(this, 'opusDecoder', { value: null }); } if (this.opusDecoders.length) { for (let [userId, decoder] of this.opusDecoders) { decoder.delete(); } this.opusDecoders.clear(); } return; } if (options.use === undefined) { // Check Decoder first if (this.opusDecoder !== null) { options.use = this.opusDecoder.use; } else if (this.opusEncoder !== null) { options.use = this.opusEncoder.use; } } if (this.opusDecoder !== null) { const noChanges = OpusProperties.every((property) => { return options[property] === this.opusDecoder[property]; }); if (noChanges) { return; } } const value = { application: options.application || encoders_1.Opus.Applications.AUDIO, channels: options.channels || this.formats.audio.channels, sampleRate: options.sampleRate || this.formats.audio.sampleRate, use: options.use, }; Object.defineProperty(this, 'opusDecoder', { value }); for (let [userId, decoder] of this.opusDecoders) { decoder.delete(); this.fetchOpusDecoder(userId); } } setOpusEncoder(options = {}) { options = Object.assign({}, options); if (options.kill) { if (this.opusEncoder !== null) { this.opusEncoder.delete(); Object.defineProperty(this, 'opusEncoder', { value: null }); } return; } if (options.use === undefined) { // Check Encoder first if (this.opusEncoder !== null) { options.use = this.opusEncoder.use; } else if (this.opusDecoder !== null) { options.use = this.opusDecoder.use; } } if (this.opusEncoder !== null) { const anyChanges = OpusProperties.some((property) => { return options[property] !== this.opusEncoder[property]; }); if (anyChanges) { this.opusEncoder.delete(); Object.defineProperty(this, 'opusEncoder', { value: null }); } } const opusEncoder = new encoders_1.Opus.AudioOpus(options.sampleRate || this.formats.audio.sampleRate, options.channels || this.formats.audio.channels, { application: options.application || encoders_1.Opus.Applications.AUDIO, use: options.use, }); Object.defineProperty(this, 'opusEncoder', { value: opusEncoder }); } /* Gateway Functions */ async setSpeaking(options) { return new Promise((resolve) => { this.gateway.sendSpeaking(options, resolve); }); } async setState(options) { return new Promise((resolve) => { this.gateway.sendStateUpdate(options, resolve); }); } setDeaf(selfDeaf) { return this.setState({ selfDeaf }); } setMute(selfMute) { return this.setState({ selfMute }); } setVideo(selfVideo) { return this.setState({ selfVideo }); } on(event, listener) { super.on(event, listener); return this; } } exports.VoiceConnection = VoiceConnection;