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,
|
|
|
|
VoiceConnectionStatus
|
|
|
|
} from "@discordjs/voice";
|
|
|
|
import { Duplex, Readable } from "stream";
|
2021-06-11 14:17:42 +05:00
|
|
|
import { TypedEmitter as EventEmitter } from "tiny-typed-emitter";
|
2021-06-11 14:06:51 +05:00
|
|
|
|
2021-06-11 14:17:42 +05:00
|
|
|
export interface VoiceEvents {
|
|
|
|
error: (error: AudioPlayerError) => any;
|
|
|
|
debug: (message: string) => any;
|
|
|
|
start: () => any;
|
|
|
|
finish: () => any;
|
|
|
|
}
|
|
|
|
|
|
|
|
class VoiceSubscription extends EventEmitter<VoiceEvents> {
|
2021-06-11 14:06:51 +05:00
|
|
|
public readonly voiceConnection: VoiceConnection;
|
|
|
|
public readonly audioPlayer: AudioPlayer;
|
|
|
|
public connectPromise?: Promise<void>;
|
|
|
|
|
|
|
|
constructor(connection: VoiceConnection) {
|
2021-06-11 14:17:42 +05:00
|
|
|
super();
|
|
|
|
|
2021-06-11 14:06:51 +05:00
|
|
|
this.voiceConnection = connection;
|
|
|
|
this.audioPlayer = createAudioPlayer();
|
|
|
|
|
|
|
|
this.voiceConnection.on("stateChange", (_, newState) => {
|
|
|
|
if (newState.status === VoiceConnectionStatus.Disconnected) {
|
|
|
|
if (this.voiceConnection.reconnectAttempts < 5) {
|
|
|
|
setTimeout(() => {
|
|
|
|
if (this.voiceConnection.state.status === VoiceConnectionStatus.Disconnected) {
|
|
|
|
this.voiceConnection.reconnect();
|
|
|
|
}
|
|
|
|
}, (this.voiceConnection.reconnectAttempts + 1) * 5000).unref();
|
|
|
|
} else {
|
|
|
|
this.voiceConnection.destroy();
|
|
|
|
}
|
|
|
|
} else if (newState.status === VoiceConnectionStatus.Destroyed) {
|
|
|
|
this.stop();
|
|
|
|
} else if (
|
|
|
|
!this.connectPromise &&
|
|
|
|
(newState.status === VoiceConnectionStatus.Connecting ||
|
|
|
|
newState.status === VoiceConnectionStatus.Signalling)
|
|
|
|
) {
|
|
|
|
this.connectPromise = entersState(this.voiceConnection, VoiceConnectionStatus.Ready, 20000)
|
|
|
|
.then(() => undefined)
|
|
|
|
.catch(() => {
|
|
|
|
if (this.voiceConnection.state.status !== VoiceConnectionStatus.Destroyed)
|
|
|
|
this.voiceConnection.destroy();
|
|
|
|
})
|
|
|
|
.finally(() => (this.connectPromise = undefined));
|
|
|
|
}
|
|
|
|
});
|
2021-06-11 14:17:42 +05:00
|
|
|
|
|
|
|
this.audioPlayer.on("stateChange", (oldState, newState) => {
|
|
|
|
if (newState.status === AudioPlayerStatus.Idle && oldState.status !== AudioPlayerStatus.Idle) {
|
|
|
|
void this.emit("finish");
|
|
|
|
} else if (newState.status === AudioPlayerStatus.Playing) {
|
|
|
|
void this.emit("start");
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
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
|
|
|
|
* @param {({type?:StreamType;data?:any;inlineVolume?:boolean})} [ops] Options
|
|
|
|
* @returns {AudioResource}
|
|
|
|
*/
|
2021-06-11 14:17:42 +05:00
|
|
|
createStream(src: Readable | Duplex | string, ops?: { type?: StreamType; data?: any; inlineVolume?: boolean }) {
|
2021-06-11 14:06:51 +05:00
|
|
|
return createAudioResource(src, {
|
|
|
|
inputType: ops?.type ?? StreamType.Arbitrary,
|
|
|
|
metadata: ops?.data,
|
|
|
|
inlineVolume: Boolean(ops?.inlineVolume)
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The player status
|
|
|
|
*/
|
|
|
|
get status() {
|
|
|
|
return this.audioPlayer.state.status;
|
|
|
|
}
|
|
|
|
|
2021-06-11 14:28:21 +05:00
|
|
|
/**
|
|
|
|
* Disconnects from voice
|
|
|
|
*/
|
|
|
|
disconnect() {
|
|
|
|
this.voiceConnection.destroy();
|
|
|
|
}
|
|
|
|
|
2021-06-11 14:06:51 +05:00
|
|
|
/**
|
|
|
|
* Stops the player
|
|
|
|
*/
|
|
|
|
stop() {
|
|
|
|
this.audioPlayer.stop();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Play stream
|
|
|
|
* @param {AudioResource} resource The audio resource to play
|
|
|
|
*/
|
|
|
|
playStream(resource: AudioResource) {
|
|
|
|
this.audioPlayer.play(resource);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export { VoiceSubscription };
|