@discordjs/voice
Advanced tools
Comparing version 0.5.6 to 0.6.0
@@ -40,5 +40,3 @@ "use strict"; | ||
} | ||
stream_1.once(this.playStream, 'readable') | ||
.then(() => (this.started = true)) | ||
.catch(util_1.noop); | ||
this.playStream.once('readable', () => (this.started = true)); | ||
} | ||
@@ -45,0 +43,0 @@ /** |
@@ -950,2 +950,195 @@ /// <reference types="node" /> | ||
/** | ||
* The different behaviors an audio receive stream can have for deciding when to end. | ||
*/ | ||
declare enum EndBehaviorType { | ||
/** | ||
* The stream will only end when manually destroyed. | ||
*/ | ||
Manual = 0, | ||
/** | ||
* The stream will end after a given time period of silence/no audio packets. | ||
*/ | ||
AfterSilence = 1, | ||
/** | ||
* The stream will end after a given time period of no audio packets. | ||
*/ | ||
AfterInactivity = 2 | ||
} | ||
declare type EndBehavior = { | ||
behavior: EndBehaviorType.Manual; | ||
} | { | ||
behavior: EndBehaviorType.AfterSilence | EndBehaviorType.AfterInactivity; | ||
duration: number; | ||
}; | ||
interface AudioReceiveStreamOptions extends ReadableOptions { | ||
end: EndBehavior; | ||
} | ||
declare function createDefaultAudioReceiveStreamOptions(): AudioReceiveStreamOptions; | ||
/** | ||
* A readable stream of Opus packets received from a specific entity | ||
* in a Discord voice connection. | ||
*/ | ||
declare class AudioReceiveStream extends Readable { | ||
/** | ||
* The end behavior of the receive stream. | ||
*/ | ||
readonly end: EndBehavior; | ||
private endTimeout?; | ||
constructor({ end, ...options }: AudioReceiveStreamOptions); | ||
push(buffer: Buffer | null): boolean; | ||
private renewEndTimeout; | ||
_read(): void; | ||
} | ||
/** | ||
* The events that a SpeakingMap can emit | ||
*/ | ||
interface SpeakingMapEvents { | ||
/** | ||
* Emitted when a user starts speaking. | ||
*/ | ||
start: (userId: string) => Awaited<void>; | ||
/** | ||
* Emitted when a user stops speaking. | ||
*/ | ||
end: (userId: string) => Awaited<void>; | ||
} | ||
/** | ||
* Tracks the speaking states of users in a voice channel. | ||
*/ | ||
declare class SpeakingMap extends TypedEmitter<SpeakingMapEvents> { | ||
/** | ||
* The delay after a packet is received from a user until they're marked as not speaking anymore. | ||
*/ | ||
static readonly DELAY = 100; | ||
/** | ||
* The currently speaking users, mapped to the milliseconds since UNIX epoch at which they started speaking. | ||
*/ | ||
readonly users: Map<string, number>; | ||
private readonly speakingTimeouts; | ||
constructor(); | ||
onPacket(userId: string): void; | ||
private startTimeout; | ||
} | ||
/** | ||
* The known data for a user in a Discord voice connection | ||
*/ | ||
interface VoiceUserData { | ||
/** | ||
* The SSRC of the user's audio stream | ||
*/ | ||
audioSSRC: number; | ||
/** | ||
* The SSRC of the user's video stream (if one exists). | ||
* Cannot be 0. If undefined, the user has no video stream. | ||
*/ | ||
videoSSRC?: number; | ||
/** | ||
* The Discord user ID of the user | ||
*/ | ||
userId: string; | ||
} | ||
/** | ||
* The events that an SSRCMap may emit. | ||
*/ | ||
interface SSRCMapEvents { | ||
create: (newData: VoiceUserData) => Awaited<void>; | ||
update: (oldData: VoiceUserData | undefined, newData: VoiceUserData) => Awaited<void>; | ||
delete: (deletedData: VoiceUserData) => Awaited<void>; | ||
} | ||
/** | ||
* Maps audio SSRCs to data of users in voice connections. | ||
*/ | ||
declare class SSRCMap extends TypedEmitter<SSRCMapEvents> { | ||
/** | ||
* The underlying map | ||
*/ | ||
private readonly map; | ||
constructor(); | ||
/** | ||
* Updates the map with new user data | ||
* | ||
* @param data The data to update with | ||
*/ | ||
update(data: VoiceUserData): void; | ||
/** | ||
* Gets the stored voice data of a user. | ||
* | ||
* @param target The target, either their user ID or audio SSRC | ||
*/ | ||
get(target: number | string): VoiceUserData | undefined; | ||
/** | ||
* Deletes the stored voice data about a user. | ||
* | ||
* @param target The target of the delete operation, either their audio SSRC or user ID | ||
* @returns The data that was deleted, if any | ||
*/ | ||
delete(target: number | string): VoiceUserData | undefined; | ||
} | ||
/** | ||
* Attaches to a VoiceConnection, allowing you to receive audio packets from other | ||
* users that are speaking. | ||
* | ||
* @beta | ||
*/ | ||
declare class VoiceReceiver { | ||
/** | ||
* The attached connection of this receiver. | ||
*/ | ||
readonly voiceConnection: VoiceConnection; | ||
/** | ||
* Maps SSRCs to Discord user IDs. | ||
*/ | ||
readonly ssrcMap: SSRCMap; | ||
/** | ||
* The current audio subscriptions of this receiver. | ||
*/ | ||
readonly subscriptions: Map<string, AudioReceiveStream>; | ||
/** | ||
* The connection data of the receiver. | ||
* @internal | ||
*/ | ||
connectionData: Partial<ConnectionData>; | ||
/** | ||
* The speaking map of the receiver. | ||
*/ | ||
readonly speaking: SpeakingMap; | ||
constructor(voiceConnection: VoiceConnection); | ||
/** | ||
* Called when a packet is received on the attached connection's WebSocket. | ||
* | ||
* @param packet The received packet | ||
* @internal | ||
*/ | ||
onWsPacket(packet: any): void; | ||
private decrypt; | ||
/** | ||
* Parses an audio packet, decrypting it to yield an Opus packet. | ||
* | ||
* @param buffer The buffer to parse | ||
* @param mode The encryption mode | ||
* @param nonce The nonce buffer used by the connection for encryption | ||
* @param secretKey The secret key used by the connection for encryption | ||
* @returns The parsed Opus packet | ||
*/ | ||
private parsePacket; | ||
/** | ||
* Called when the UDP socket of the attached connection receives a message. | ||
* | ||
* @param msg The received message | ||
* @internal | ||
*/ | ||
onUdpMessage(msg: Buffer): void; | ||
/** | ||
* Creates a subscription for the given user ID. | ||
* | ||
* @param target The ID of the user to subscribe to | ||
* @returns A readable stream of Opus packets received from the target | ||
*/ | ||
subscribe(userId: string, options?: Partial<AudioReceiveStreamOptions>): AudioReceiveStream; | ||
} | ||
/** | ||
* The various status codes a voice connection can hold at any one time. | ||
@@ -1102,2 +1295,7 @@ */ | ||
/** | ||
* The receiver of this voice connection. You should join the voice channel with `selfDeaf` set | ||
* to false for this feature to work properly. | ||
*/ | ||
readonly receiver: VoiceReceiver; | ||
/** | ||
* The debug logger function, if debugging is enabled. | ||
@@ -1136,2 +1334,9 @@ */ | ||
/** | ||
* Called when the networking state changes, and the new ws/udp packet/message handlers need to be rebound | ||
* to the new instances. | ||
* @param newState - The new networking state | ||
* @param oldState - The old networking state, if there is one | ||
*/ | ||
private updateReceiveBindings; | ||
/** | ||
* Attempts to configure a networking instance for this voice connection using the received packets. | ||
@@ -1308,5 +1513,5 @@ * Both packets are required, and any existing networking instance will be destroyed. | ||
* @param status - The status that the voice connection should be in | ||
* @param maxTime - The maximum time we are allowing for this to occur | ||
* @param timeoutOrSignal - The maximum time we are allowing for this to occur, or a signal that will abort the operation | ||
*/ | ||
declare function entersState(target: VoiceConnection, status: VoiceConnectionStatus, maxTime: number): Promise<VoiceConnection>; | ||
declare function entersState(target: VoiceConnection, status: VoiceConnectionStatus, timeoutOrSignal: number | AbortSignal): Promise<VoiceConnection>; | ||
/** | ||
@@ -1317,5 +1522,5 @@ * Allows an audio player a specified amount of time to enter a given state, otherwise rejects with an error. | ||
* @param status - The status that the audio player should be in | ||
* @param maxTime - The maximum time we are allowing for this to occur | ||
* @param timeoutOrSignal - The maximum time we are allowing for this to occur, or a signal that will abort the operation | ||
*/ | ||
declare function entersState(target: AudioPlayer, status: AudioPlayerStatus, maxTime: number): Promise<AudioPlayer>; | ||
declare function entersState(target: AudioPlayer, status: AudioPlayerStatus, timeoutOrSignal: number | AbortSignal): Promise<AudioPlayer>; | ||
@@ -1351,132 +1556,2 @@ /** | ||
/** | ||
* A readable stream of Opus packets received from a specific entity | ||
* in a Discord voice connection. | ||
*/ | ||
declare class AudioReceiveStream extends Readable { | ||
constructor(options?: ReadableOptions); | ||
_read(): void; | ||
} | ||
/** | ||
* The known data for a user in a Discord voice connection | ||
*/ | ||
interface VoiceUserData { | ||
/** | ||
* The SSRC of the user's audio stream | ||
*/ | ||
audioSSRC: number; | ||
/** | ||
* The SSRC of the user's video stream (if one exists). | ||
* Cannot be 0. If undefined, the user has no video stream. | ||
*/ | ||
videoSSRC?: number; | ||
/** | ||
* The Discord user ID of the user | ||
*/ | ||
userId: string; | ||
} | ||
/** | ||
* The events that an SSRCMap may emit. | ||
*/ | ||
interface SSRCMapEvents { | ||
update: (oldData: VoiceUserData | undefined, newData: VoiceUserData) => Awaited<void>; | ||
delete: (deletedData: VoiceUserData) => Awaited<void>; | ||
} | ||
/** | ||
* Maps audio SSRCs to data of users in voice connections. | ||
*/ | ||
declare class SSRCMap extends TypedEmitter<SSRCMapEvents> { | ||
/** | ||
* The underlying map | ||
*/ | ||
private readonly map; | ||
constructor(); | ||
/** | ||
* Updates the map with new user data | ||
* | ||
* @param data The data to update with | ||
*/ | ||
update(data: VoiceUserData): void; | ||
/** | ||
* Gets the stored voice data of a user. | ||
* | ||
* @param target The target, either their user ID or audio SSRC | ||
*/ | ||
get(target: number | string): VoiceUserData | undefined; | ||
/** | ||
* Deletes the stored voice data about a user. | ||
* | ||
* @param target The target of the delete operation, either their audio SSRC or user ID | ||
* @returns The data that was deleted, if any | ||
*/ | ||
delete(target: number | string): VoiceUserData | undefined; | ||
} | ||
/** | ||
* Attaches to a VoiceConnection, allowing you to receive audio packets from other | ||
* users that are speaking. | ||
* | ||
* @beta | ||
*/ | ||
declare class VoiceReceiver { | ||
/** | ||
* The attached connection of this receiver. | ||
*/ | ||
readonly voiceConnection: VoiceConnection; | ||
/** | ||
* Maps SSRCs to Discord user IDs. | ||
*/ | ||
readonly ssrcMap: SSRCMap; | ||
/** | ||
* The current audio subscriptions of this receiver. | ||
*/ | ||
readonly subscriptions: Map<number, AudioReceiveStream>; | ||
/** | ||
* The connection information for this receiver. Used to decrypt incoming packets. | ||
*/ | ||
private connectionData; | ||
constructor(voiceConnection: VoiceConnection); | ||
/** | ||
* Called when a packet is received on the attached connection's WebSocket. | ||
* | ||
* @param packet The received packet | ||
*/ | ||
private onWsPacket; | ||
private decrypt; | ||
/** | ||
* Parses an audio packet, decrypting it to yield an Opus packet. | ||
* | ||
* @param buffer The buffer to parse | ||
* @param mode The encryption mode | ||
* @param nonce The nonce buffer used by the connection for encryption | ||
* @param secretKey The secret key used by the connection for encryption | ||
* @returns The parsed Opus packet | ||
*/ | ||
private parsePacket; | ||
/** | ||
* Called when the UDP socket of the attached connection receives a message. | ||
* | ||
* @param msg The received message | ||
*/ | ||
private onUdpMessage; | ||
/** | ||
* Creates a subscription for the given target, specified either by their SSRC or user ID. | ||
* | ||
* @param target The audio SSRC or user ID to subscribe to | ||
* @returns A readable stream of Opus packets received from the target | ||
*/ | ||
subscribe(target: string | number): AudioReceiveStream; | ||
} | ||
/** | ||
* Creates a new voice receiver for the given voice connection. | ||
* | ||
* @param voiceConnection The voice connection to attach to | ||
* @beta | ||
* @remarks | ||
* Voice receive is an undocumented part of the Discord API - voice receive is not guaranteed | ||
* to be stable and may break without notice. | ||
*/ | ||
declare function createVoiceReceiver(voiceConnection: VoiceConnection): VoiceReceiver; | ||
export { AudioPlayer, AudioPlayerBufferingState, AudioPlayerError, AudioPlayerEvents, AudioPlayerIdleState, AudioPlayerPausedState, AudioPlayerPlayingState, AudioPlayerState, AudioPlayerStatus, AudioReceiveStream, AudioResource, CreateAudioPlayerOptions, CreateVoiceConnectionOptions, DiscordGatewayAdapterCreator, DiscordGatewayAdapterImplementerMethods, DiscordGatewayAdapterLibraryMethods, JoinVoiceChannelOptions, NoSubscriberBehavior, PlayerSubscription, ProbeInfo, SSRCMap, SSRCMapEvents, StreamType, VoiceConnection, VoiceConnectionConnectingState, VoiceConnectionDestroyedState, VoiceConnectionDisconnectReason, VoiceConnectionDisconnectedBaseState, VoiceConnectionDisconnectedOtherState, VoiceConnectionDisconnectedState, VoiceConnectionDisconnectedWebSocketState, VoiceConnectionEvents, VoiceConnectionReadyState, VoiceConnectionSignallingState, VoiceConnectionState, VoiceConnectionStatus, VoiceReceiver, VoiceUserData, createAudioPlayer, createAudioResource, createVoiceReceiver, demuxProbe, entersState, generateDependencyReport, getGroups, getVoiceConnection, getVoiceConnections, joinVoiceChannel, validateDiscordOpusHead }; | ||
export { AudioPlayer, AudioPlayerBufferingState, AudioPlayerError, AudioPlayerEvents, AudioPlayerIdleState, AudioPlayerPausedState, AudioPlayerPlayingState, AudioPlayerState, AudioPlayerStatus, AudioReceiveStream, AudioReceiveStreamOptions, AudioResource, CreateAudioPlayerOptions, CreateVoiceConnectionOptions, DiscordGatewayAdapterCreator, DiscordGatewayAdapterImplementerMethods, DiscordGatewayAdapterLibraryMethods, EndBehavior, EndBehaviorType, JoinVoiceChannelOptions, NoSubscriberBehavior, PlayerSubscription, ProbeInfo, SSRCMap, SSRCMapEvents, StreamType, VoiceConnection, VoiceConnectionConnectingState, VoiceConnectionDestroyedState, VoiceConnectionDisconnectReason, VoiceConnectionDisconnectedBaseState, VoiceConnectionDisconnectedOtherState, VoiceConnectionDisconnectedState, VoiceConnectionDisconnectedWebSocketState, VoiceConnectionEvents, VoiceConnectionReadyState, VoiceConnectionSignallingState, VoiceConnectionState, VoiceConnectionStatus, VoiceReceiver, VoiceUserData, createAudioPlayer, createAudioResource, createDefaultAudioReceiveStreamOptions, demuxProbe, entersState, generateDependencyReport, getGroups, getVoiceConnection, getVoiceConnections, joinVoiceChannel, validateDiscordOpusHead }; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.AudioReceiveStream = void 0; | ||
exports.AudioReceiveStream = exports.createDefaultAudioReceiveStreamOptions = exports.EndBehaviorType = void 0; | ||
const stream_1 = require("stream"); | ||
const AudioPlayer_1 = require("../audio/AudioPlayer"); | ||
/** | ||
* The different behaviors an audio receive stream can have for deciding when to end. | ||
*/ | ||
var EndBehaviorType; | ||
(function (EndBehaviorType) { | ||
/** | ||
* The stream will only end when manually destroyed. | ||
*/ | ||
EndBehaviorType[EndBehaviorType["Manual"] = 0] = "Manual"; | ||
/** | ||
* The stream will end after a given time period of silence/no audio packets. | ||
*/ | ||
EndBehaviorType[EndBehaviorType["AfterSilence"] = 1] = "AfterSilence"; | ||
/** | ||
* The stream will end after a given time period of no audio packets. | ||
*/ | ||
EndBehaviorType[EndBehaviorType["AfterInactivity"] = 2] = "AfterInactivity"; | ||
})(EndBehaviorType = exports.EndBehaviorType || (exports.EndBehaviorType = {})); | ||
function createDefaultAudioReceiveStreamOptions() { | ||
return { | ||
end: { | ||
behavior: EndBehaviorType.Manual, | ||
}, | ||
}; | ||
} | ||
exports.createDefaultAudioReceiveStreamOptions = createDefaultAudioReceiveStreamOptions; | ||
/** | ||
* A readable stream of Opus packets received from a specific entity | ||
@@ -10,3 +37,3 @@ * in a Discord voice connection. | ||
class AudioReceiveStream extends stream_1.Readable { | ||
constructor(options) { | ||
constructor({ end, ...options }) { | ||
super({ | ||
@@ -16,3 +43,20 @@ ...options, | ||
}); | ||
this.end = end; | ||
} | ||
push(buffer) { | ||
if (buffer) { | ||
if (this.end.behavior === EndBehaviorType.AfterInactivity || | ||
(this.end.behavior === EndBehaviorType.AfterSilence && | ||
(buffer.compare(AudioPlayer_1.SILENCE_FRAME) !== 0 || typeof this.endTimeout === 'undefined'))) { | ||
this.renewEndTimeout(this.end); | ||
} | ||
} | ||
return super.push(buffer); | ||
} | ||
renewEndTimeout(end) { | ||
if (this.endTimeout) { | ||
clearTimeout(this.endTimeout); | ||
} | ||
this.endTimeout = setTimeout(() => this.push(null), end.duration); | ||
} | ||
// eslint-disable-next-line @typescript-eslint/no-empty-function | ||
@@ -19,0 +63,0 @@ _read() { } |
@@ -25,2 +25,4 @@ "use strict"; | ||
this.map.set(data.audioSSRC, newValue); | ||
if (!existing) | ||
this.emit('create', newValue); | ||
this.emit('update', existing, newValue); | ||
@@ -27,0 +29,0 @@ } |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.createVoiceReceiver = exports.VoiceReceiver = void 0; | ||
const AudioPlayer_1 = require("../audio/AudioPlayer"); | ||
exports.VoiceReceiver = void 0; | ||
const Secretbox_1 = require("../util/Secretbox"); | ||
const AudioReceiveStream_1 = require("./AudioReceiveStream"); | ||
const SpeakingMap_1 = require("./SpeakingMap"); | ||
const SSRCMap_1 = require("./SSRCMap"); | ||
@@ -18,61 +18,7 @@ /** | ||
this.ssrcMap = new SSRCMap_1.SSRCMap(); | ||
this.speaking = new SpeakingMap_1.SpeakingMap(); | ||
this.subscriptions = new Map(); | ||
this.connectionData = {}; | ||
const onWsPacket = (packet) => this.onWsPacket(packet); | ||
const onUdpMessage = (msg) => this.onUdpMessage(msg); | ||
const applyConnectionData = (connectionData) => { | ||
this.connectionData = { | ||
...this.connectionData, | ||
...connectionData, | ||
}; | ||
if (connectionData.packetsPlayed === 0) { | ||
this.voiceConnection.playOpusPacket(AudioPlayer_1.SILENCE_FRAME); | ||
} | ||
}; | ||
// Bind listeners for updates | ||
const onNetworkingChange = (oldState, newState) => { | ||
const oldWs = Reflect.get(oldState, 'ws'); | ||
const oldUdp = Reflect.get(oldState, 'udp'); | ||
const newWs = Reflect.get(newState, 'ws'); | ||
const newUdp = Reflect.get(newState, 'udp'); | ||
const connectionData = Reflect.get(newState, 'connectionData'); | ||
if (connectionData) | ||
applyConnectionData(connectionData); | ||
if (newWs !== oldWs) { | ||
oldWs === null || oldWs === void 0 ? void 0 : oldWs.off('packet', onWsPacket); | ||
newWs === null || newWs === void 0 ? void 0 : newWs.on('packet', onWsPacket); | ||
} | ||
if (newUdp !== oldUdp) { | ||
oldUdp === null || oldUdp === void 0 ? void 0 : oldUdp.off('message', onUdpMessage); | ||
newUdp === null || newUdp === void 0 ? void 0 : newUdp.on('message', onUdpMessage); | ||
} | ||
}; | ||
this.voiceConnection.on('stateChange', (oldState, newState) => { | ||
const oldNetworking = Reflect.get(oldState, 'networking'); | ||
const newNetworking = Reflect.get(newState, 'networking'); | ||
if (newNetworking !== oldNetworking) { | ||
oldNetworking === null || oldNetworking === void 0 ? void 0 : oldNetworking.off('stateChange', onNetworkingChange); | ||
newNetworking === null || newNetworking === void 0 ? void 0 : newNetworking.on('stateChange', onNetworkingChange); | ||
if (newNetworking) { | ||
const ws = Reflect.get(newNetworking.state, 'ws'); | ||
const udp = Reflect.get(newNetworking.state, 'udp'); | ||
const connectionData = Reflect.get(newNetworking.state, 'connectionData'); | ||
ws === null || ws === void 0 ? void 0 : ws.on('packet', onWsPacket); | ||
udp === null || udp === void 0 ? void 0 : udp.on('message', onUdpMessage); | ||
if (connectionData) | ||
applyConnectionData(connectionData); | ||
} | ||
} | ||
}); | ||
// Bind listeners for the existing state | ||
const networking = Reflect.get(voiceConnection.state, 'networking'); | ||
if (networking) { | ||
const ws = Reflect.get(networking.state, 'ws'); | ||
const udp = Reflect.get(networking.state, 'udp'); | ||
const connectionData = Reflect.get(networking.state, 'connectionData'); | ||
ws === null || ws === void 0 ? void 0 : ws.on('packet', onWsPacket); | ||
udp === null || udp === void 0 ? void 0 : udp.on('message', onUdpMessage); | ||
if (connectionData) | ||
applyConnectionData(connectionData); | ||
} | ||
this.onWsPacket = this.onWsPacket.bind(this); | ||
this.onUdpMessage = this.onUdpMessage.bind(this); | ||
} | ||
@@ -83,2 +29,3 @@ /** | ||
* @param packet The received packet | ||
* @internal | ||
*/ | ||
@@ -161,2 +108,3 @@ onWsPacket(packet) { | ||
* @param msg The received message | ||
* @internal | ||
*/ | ||
@@ -167,8 +115,9 @@ onUdpMessage(msg) { | ||
const ssrc = msg.readUInt32BE(8); | ||
const stream = this.subscriptions.get(ssrc); | ||
if (!stream) | ||
return; | ||
const userData = this.ssrcMap.get(ssrc); | ||
if (!userData) | ||
return; | ||
this.speaking.onPacket(userData.userId); | ||
const stream = this.subscriptions.get(userData.userId); | ||
if (!stream) | ||
return; | ||
if (this.connectionData.encryptionMode && this.connectionData.nonceBuffer && this.connectionData.secretKey) { | ||
@@ -185,19 +134,17 @@ const packet = this.parsePacket(msg, this.connectionData.encryptionMode, this.connectionData.nonceBuffer, this.connectionData.secretKey); | ||
/** | ||
* Creates a subscription for the given target, specified either by their SSRC or user ID. | ||
* Creates a subscription for the given user ID. | ||
* | ||
* @param target The audio SSRC or user ID to subscribe to | ||
* @param target The ID of the user to subscribe to | ||
* @returns A readable stream of Opus packets received from the target | ||
*/ | ||
subscribe(target) { | ||
var _a; | ||
const ssrc = (_a = this.ssrcMap.get(target)) === null || _a === void 0 ? void 0 : _a.audioSSRC; | ||
if (!ssrc) { | ||
throw new Error(`No known SSRC for ${target}`); | ||
} | ||
const existing = this.subscriptions.get(ssrc); | ||
subscribe(userId, options) { | ||
const existing = this.subscriptions.get(userId); | ||
if (existing) | ||
return existing; | ||
const stream = new AudioReceiveStream_1.AudioReceiveStream(); | ||
stream.once('close', () => this.subscriptions.delete(ssrc)); | ||
this.subscriptions.set(ssrc, stream); | ||
const stream = new AudioReceiveStream_1.AudioReceiveStream({ | ||
...AudioReceiveStream_1.createDefaultAudioReceiveStreamOptions(), | ||
...options, | ||
}); | ||
stream.once('close', () => this.subscriptions.delete(userId)); | ||
this.subscriptions.set(userId, stream); | ||
return stream; | ||
@@ -207,15 +154,2 @@ } | ||
exports.VoiceReceiver = VoiceReceiver; | ||
/** | ||
* Creates a new voice receiver for the given voice connection. | ||
* | ||
* @param voiceConnection The voice connection to attach to | ||
* @beta | ||
* @remarks | ||
* Voice receive is an undocumented part of the Discord API - voice receive is not guaranteed | ||
* to be stable and may break without notice. | ||
*/ | ||
function createVoiceReceiver(voiceConnection) { | ||
return new VoiceReceiver(voiceConnection); | ||
} | ||
exports.createVoiceReceiver = createVoiceReceiver; | ||
//# sourceMappingURL=VoiceReceiver.js.map |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.entersState = void 0; | ||
const abortAfter_1 = require("./abortAfter"); | ||
const events_1 = require("events"); | ||
/** | ||
@@ -9,23 +11,17 @@ * Allows a target a specified amount of time to enter a given state, otherwise rejects with an error. | ||
* @param status - The status that the target should be in | ||
* @param maxTime - The maximum time we are allowing for this to occur | ||
* @param timeoutOrSignal - The maximum time we are allowing for this to occur, or a signal that will abort the operation | ||
*/ | ||
function entersState(target, status, maxTime) { | ||
if (target.state.status === status) { | ||
return Promise.resolve(target); | ||
async function entersState(target, status, timeoutOrSignal) { | ||
if (target.state.status !== status) { | ||
const [ac, signal] = typeof timeoutOrSignal === 'number' ? abortAfter_1.abortAfter(timeoutOrSignal) : [undefined, timeoutOrSignal]; | ||
try { | ||
await events_1.once(target, status, { signal }); | ||
} | ||
finally { | ||
ac === null || ac === void 0 ? void 0 : ac.abort(); | ||
} | ||
} | ||
let cleanup; | ||
return new Promise((resolve, reject) => { | ||
const timeout = setTimeout(() => reject(new Error(`Did not enter state ${status} within ${maxTime}ms`)), maxTime); | ||
target.once(status, resolve); | ||
target.once('error', reject); | ||
cleanup = () => { | ||
clearTimeout(timeout); | ||
target.off(status, resolve); | ||
target.off('error', reject); | ||
}; | ||
}) | ||
.then(() => target) | ||
.finally(cleanup); | ||
return target; | ||
} | ||
exports.entersState = entersState; | ||
//# sourceMappingURL=entersState.js.map |
@@ -8,2 +8,3 @@ "use strict"; | ||
const tiny_typed_emitter_1 = require("tiny-typed-emitter"); | ||
const receive_1 = require("./receive"); | ||
/** | ||
@@ -71,2 +72,3 @@ * The various status codes a voice connection can hold at any one time. | ||
this.rejoinAttempts = 0; | ||
this.receiver = new receive_1.VoiceReceiver(this); | ||
this.onNetworkingClose = this.onNetworkingClose.bind(this); | ||
@@ -103,9 +105,13 @@ this.onNetworkingStateChange = this.onNetworkingStateChange.bind(this); | ||
const newSubscription = Reflect.get(newState, 'subscription'); | ||
if (oldNetworking && oldNetworking !== newNetworking) { | ||
oldNetworking.off('debug', this.onNetworkingDebug); | ||
oldNetworking.on('error', util_1.noop); | ||
oldNetworking.off('error', this.onNetworkingError); | ||
oldNetworking.off('close', this.onNetworkingClose); | ||
oldNetworking.off('stateChange', this.onNetworkingStateChange); | ||
oldNetworking.destroy(); | ||
if (oldNetworking !== newNetworking) { | ||
if (oldNetworking) { | ||
oldNetworking.on('error', util_1.noop); | ||
oldNetworking.off('debug', this.onNetworkingDebug); | ||
oldNetworking.off('error', this.onNetworkingError); | ||
oldNetworking.off('close', this.onNetworkingClose); | ||
oldNetworking.off('stateChange', this.onNetworkingStateChange); | ||
oldNetworking.destroy(); | ||
} | ||
if (newNetworking) | ||
this.updateReceiveBindings(newNetworking.state, oldNetworking === null || oldNetworking === void 0 ? void 0 : oldNetworking.state); | ||
} | ||
@@ -115,2 +121,8 @@ if (newState.status === VoiceConnectionStatus.Ready) { | ||
} | ||
else if (newState.status === VoiceConnectionStatus.Destroyed) { | ||
for (const stream of this.receiver.subscriptions.values()) { | ||
if (!stream.destroyed) | ||
stream.destroy(); | ||
} | ||
} | ||
// If destroyed, the adapter can also be destroyed so it can be cleaned up by the user | ||
@@ -169,2 +181,24 @@ if (oldState.status !== VoiceConnectionStatus.Destroyed && newState.status === VoiceConnectionStatus.Destroyed) { | ||
/** | ||
* Called when the networking state changes, and the new ws/udp packet/message handlers need to be rebound | ||
* to the new instances. | ||
* @param newState - The new networking state | ||
* @param oldState - The old networking state, if there is one | ||
*/ | ||
updateReceiveBindings(newState, oldState) { | ||
var _a; | ||
const oldWs = Reflect.get(oldState !== null && oldState !== void 0 ? oldState : {}, 'ws'); | ||
const newWs = Reflect.get(newState, 'ws'); | ||
const oldUdp = Reflect.get(oldState !== null && oldState !== void 0 ? oldState : {}, 'udp'); | ||
const newUdp = Reflect.get(newState, 'udp'); | ||
if (oldWs !== newWs) { | ||
oldWs === null || oldWs === void 0 ? void 0 : oldWs.off('packet', this.receiver.onWsPacket); | ||
newWs === null || newWs === void 0 ? void 0 : newWs.on('packet', this.receiver.onWsPacket); | ||
} | ||
if (oldUdp !== newUdp) { | ||
oldUdp === null || oldUdp === void 0 ? void 0 : oldUdp.off('message', this.receiver.onUdpMessage); | ||
newUdp === null || newUdp === void 0 ? void 0 : newUdp.on('message', this.receiver.onUdpMessage); | ||
} | ||
this.receiver.connectionData = (_a = Reflect.get(newState, 'connectionData')) !== null && _a !== void 0 ? _a : {}; | ||
} | ||
/** | ||
* Attempts to configure a networking instance for this voice connection using the received packets. | ||
@@ -248,2 +282,3 @@ * Both packets are required, and any existing networking instance will be destroyed. | ||
onNetworkingStateChange(oldState, newState) { | ||
this.updateReceiveBindings(newState, oldState); | ||
if (oldState.code === newState.code) | ||
@@ -250,0 +285,0 @@ return; |
{ | ||
"name": "@discordjs/voice", | ||
"version": "0.5.6", | ||
"version": "0.6.0", | ||
"description": "Implementation of the Discord Voice API for Node.js", | ||
@@ -41,2 +41,5 @@ "main": "dist/index.js", | ||
], | ||
"engines": { | ||
"node": ">=16.0.0" | ||
}, | ||
"dependencies": { | ||
@@ -57,3 +60,3 @@ "@types/ws": "^7.4.4", | ||
"@types/jest": "^26.0.23", | ||
"@types/node": "^15.12.2", | ||
"@types/node": "^16.4.13", | ||
"@typescript-eslint/eslint-plugin": "^4.26.1", | ||
@@ -60,0 +63,0 @@ "@typescript-eslint/parser": "^4.26.1", |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
381118
58
4512