discord-player-play-dl/src/VoiceInterface/StreamDispatcher.ts

227 lines
7.3 KiB
TypeScript
Raw Normal View History

2021-06-11 14:06:51 +05:00
import {
AudioPlayer,
2021-06-11 14:17:42 +05:00
AudioPlayerError,
AudioPlayerStatus,
2021-06-11 14:06:51 +05:00
AudioResource,
createAudioPlayer,
createAudioResource,
entersState,
StreamType,
VoiceConnection,
2021-06-17 18:22:03 +05:00
VoiceConnectionStatus,
VoiceConnectionDisconnectReason
2021-06-11 14:06:51 +05:00
} from "@discordjs/voice";
2021-06-12 11:37:41 +05:00
import { StageChannel, VoiceChannel } from "discord.js";
2021-06-11 14:06:51 +05:00
import { Duplex, Readable } from "stream";
2021-06-11 14:17:42 +05:00
import { TypedEmitter as EventEmitter } from "tiny-typed-emitter";
2021-06-11 16:50:43 +05:00
import Track from "../Structures/Track";
2021-06-17 18:22:03 +05:00
import { Util } from "../utils/Util";
2021-06-11 14:06:51 +05:00
2021-06-11 14:17:42 +05:00
export interface VoiceEvents {
2021-06-25 12:24:53 +05:00
/* eslint-disable @typescript-eslint/no-explicit-any */
error: (error: AudioPlayerError) => any;
debug: (message: string) => any;
start: (resource: AudioResource<Track>) => any;
finish: (resource: AudioResource<Track>) => any;
/* eslint-enable @typescript-eslint/no-explicit-any */
2021-06-11 14:17:42 +05:00
}
2021-06-20 19:22:09 +05:00
class StreamDispatcher extends EventEmitter<VoiceEvents> {
2021-06-11 14:06:51 +05:00
public readonly voiceConnection: VoiceConnection;
public readonly audioPlayer: AudioPlayer;
2021-06-23 15:34:53 +05:00
public channel: VoiceChannel | StageChannel;
2021-06-11 16:50:43 +05:00
public audioResource?: AudioResource<Track>;
2021-06-22 15:24:05 +05:00
private readyLock = false;
2021-06-23 15:34:53 +05:00
public paused: boolean;
2021-06-11 14:06:51 +05:00
2021-06-20 19:22:09 +05:00
/**
* Creates new connection object
* @param {VoiceConnection} connection The connection
* @param {VoiceChannel|StageChannel} channel The connected channel
2021-06-21 10:34:49 +05:00
* @private
2021-06-20 19:22:09 +05:00
*/
2021-06-12 11:37:41 +05:00
constructor(connection: VoiceConnection, channel: VoiceChannel | StageChannel) {
2021-06-11 14:17:42 +05:00
super();
2021-06-20 19:22:09 +05:00
/**
* The voice connection
* @type {VoiceConnection}
*/
2021-06-11 14:06:51 +05:00
this.voiceConnection = connection;
2021-06-20 19:22:09 +05:00
/**
* The audio player
* @type {AudioPlayer}
*/
2021-06-11 14:06:51 +05:00
this.audioPlayer = createAudioPlayer();
2021-06-20 19:22:09 +05:00
/**
* The voice channel
* @type {VoiceChannel|StageChannel}
*/
2021-06-12 11:37:41 +05:00
this.channel = channel;
2021-06-11 14:06:51 +05:00
2021-06-23 15:34:53 +05:00
/**
* The paused state
* @type {boolean}
*/
this.paused = false;
2021-06-17 18:22:03 +05:00
this.voiceConnection.on("stateChange", async (_, newState) => {
2021-06-11 14:06:51 +05:00
if (newState.status === VoiceConnectionStatus.Disconnected) {
2021-06-17 18:22:03 +05:00
if (newState.reason === VoiceConnectionDisconnectReason.WebSocketClose && newState.closeCode === 4014) {
try {
await entersState(this.voiceConnection, VoiceConnectionStatus.Connecting, 5000);
} catch {
this.voiceConnection.destroy();
}
} else if (this.voiceConnection.rejoinAttempts < 5) {
await Util.wait((this.voiceConnection.rejoinAttempts + 1) * 5000);
this.voiceConnection.rejoin();
2021-06-11 14:06:51 +05:00
} else {
this.voiceConnection.destroy();
}
} else if (newState.status === VoiceConnectionStatus.Destroyed) {
2021-06-11 23:19:52 +05:00
this.end();
2021-06-17 18:22:03 +05:00
} else if (!this.readyLock && (newState.status === VoiceConnectionStatus.Connecting || newState.status === VoiceConnectionStatus.Signalling)) {
this.readyLock = true;
try {
await entersState(this.voiceConnection, VoiceConnectionStatus.Ready, 20000);
} catch {
if (this.voiceConnection.state.status !== VoiceConnectionStatus.Destroyed) this.voiceConnection.destroy();
} finally {
this.readyLock = false;
}
2021-06-11 14:06:51 +05:00
}
});
2021-06-11 14:17:42 +05:00
this.audioPlayer.on("stateChange", (oldState, newState) => {
2021-06-25 15:10:03 +05:00
if (newState.status === AudioPlayerStatus.Playing) {
if (!this.paused) return void this.emit("start", this.audioResource);
} else if (newState.status === AudioPlayerStatus.Idle && oldState.status !== AudioPlayerStatus.Idle) {
2021-06-12 11:37:41 +05:00
if (!this.paused) {
2021-06-22 15:50:31 +05:00
void this.emit("finish", this.audioResource);
2021-06-12 11:37:41 +05:00
this.audioResource = null;
}
2021-06-11 14:17:42 +05:00
}
});
this.audioPlayer.on("debug", (m) => void this.emit("debug", m));
this.audioPlayer.on("error", (error) => void this.emit("error", error));
this.voiceConnection.subscribe(this.audioPlayer);
2021-06-11 14:06:51 +05:00
}
/**
* Creates stream
* @param {Readable|Duplex|string} src The stream source
2021-06-20 19:22:09 +05:00
* @param {object} [ops={}] Options
2021-06-11 14:06:51 +05:00
* @returns {AudioResource}
*/
2021-06-22 15:24:05 +05:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any
2021-06-11 23:19:52 +05:00
createStream(src: Readable | Duplex | string, ops?: { type?: StreamType; data?: any }) {
2021-06-11 16:50:43 +05:00
this.audioResource = createAudioResource(src, {
2021-06-11 14:06:51 +05:00
inputType: ops?.type ?? StreamType.Arbitrary,
metadata: ops?.data,
2021-06-11 23:19:52 +05:00
inlineVolume: true // we definitely need volume controls, right?
2021-06-11 14:06:51 +05:00
});
2021-06-11 16:50:43 +05:00
return this.audioResource;
2021-06-11 14:06:51 +05:00
}
/**
* The player status
2021-06-20 19:22:09 +05:00
* @type {AudioPlayerStatus}
2021-06-11 14:06:51 +05:00
*/
get status() {
return this.audioPlayer.state.status;
}
2021-06-11 14:28:21 +05:00
/**
* Disconnects from voice
2021-06-20 19:22:09 +05:00
* @returns {void}
2021-06-11 14:28:21 +05:00
*/
disconnect() {
2021-06-12 00:18:53 +05:00
try {
2021-06-22 10:33:20 +05:00
this.audioPlayer.stop(true);
2021-06-12 00:18:53 +05:00
this.voiceConnection.destroy();
2021-06-22 15:24:05 +05:00
} catch {} // eslint-disable-line no-empty
2021-06-11 14:28:21 +05:00
}
2021-06-11 14:06:51 +05:00
/**
* Stops the player
2021-06-20 19:22:09 +05:00
* @returns {void}
2021-06-11 14:06:51 +05:00
*/
2021-06-11 23:19:52 +05:00
end() {
2021-06-11 14:06:51 +05:00
this.audioPlayer.stop();
}
2021-06-20 19:22:09 +05:00
/**
* Pauses the stream playback
* @param {boolean} [interpolateSilence=false] If true, the player will play 5 packets of silence after pausing to prevent audio glitches.
* @returns {boolean}
*/
pause(interpolateSilence?: boolean) {
2021-06-12 11:37:41 +05:00
const success = this.audioPlayer.pause(interpolateSilence);
2021-06-23 15:34:53 +05:00
this.paused = success;
2021-06-12 11:37:41 +05:00
return success;
}
2021-06-20 19:22:09 +05:00
/**
* Resumes the stream playback
* @returns {boolean}
*/
resume() {
2021-06-12 11:37:41 +05:00
const success = this.audioPlayer.unpause();
2021-06-23 15:34:53 +05:00
this.paused = !success;
2021-06-12 11:37:41 +05:00
return success;
}
2021-06-11 14:06:51 +05:00
/**
* Play stream
2021-06-20 19:22:09 +05:00
* @param {AudioResource<Track>} [resource=this.audioResource] The audio resource to play
* @returns {Promise<StreamDispatcher>}
2021-06-11 14:06:51 +05:00
*/
2021-06-12 00:18:53 +05:00
async playStream(resource: AudioResource<Track> = this.audioResource) {
2021-06-14 18:50:36 +05:00
if (!resource) throw new Error("Audio resource is not available!");
2021-06-11 23:19:52 +05:00
if (!this.audioResource) this.audioResource = resource;
2021-06-13 13:06:19 +05:00
if (this.voiceConnection.state.status !== VoiceConnectionStatus.Ready) await entersState(this.voiceConnection, VoiceConnectionStatus.Ready, 20000);
2021-06-11 14:06:51 +05:00
this.audioPlayer.play(resource);
return this;
2021-06-11 14:06:51 +05:00
}
2021-06-11 16:50:43 +05:00
2021-06-20 19:22:09 +05:00
/**
* Sets playback volume
* @param {number} value The volume amount
* @returns {boolean}
*/
2021-06-11 23:19:52 +05:00
setVolume(value: number) {
2021-06-12 00:18:53 +05:00
if (!this.audioResource || isNaN(value) || value < 0 || value > Infinity) return false;
2021-06-11 23:19:52 +05:00
// ye boi logarithmic ✌
2021-06-14 09:46:29 +05:00
this.audioResource.volume.setVolumeLogarithmic(value / 100);
2021-06-12 00:18:53 +05:00
return true;
2021-06-11 23:19:52 +05:00
}
2021-06-20 19:22:09 +05:00
/**
* The current volume
* @type {number}
*/
2021-06-13 13:06:19 +05:00
get volume() {
if (!this.audioResource || !this.audioResource.volume) return 100;
const currentVol = this.audioResource.volume.volume;
2021-06-14 09:46:29 +05:00
return Math.round(Math.pow(currentVol, 1 / 1.660964) * 100);
2021-06-13 13:06:19 +05:00
}
2021-06-20 19:22:09 +05:00
/**
* The playback time
* @type {number}
*/
2021-06-11 16:50:43 +05:00
get streamTime() {
if (!this.audioResource) return 0;
2021-06-20 10:37:59 +05:00
return this.audioResource.playbackDuration;
2021-06-11 16:50:43 +05:00
}
2021-06-11 14:06:51 +05:00
}
2021-06-20 19:22:09 +05:00
export { StreamDispatcher as StreamDispatcher };