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

155 lines
5 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,
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 16:50:43 +05:00
import Track from "../Structures/Track";
import PlayerError from "../utils/PlayerError";
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;
}
2021-06-11 23:19:52 +05:00
class BasicStreamDispatcher extends EventEmitter<VoiceEvents> {
2021-06-11 14:06:51 +05:00
public readonly voiceConnection: VoiceConnection;
public readonly audioPlayer: AudioPlayer;
public connectPromise?: Promise<void>;
2021-06-11 16:50:43 +05:00
public audioResource?: AudioResource<Track>;
2021-06-11 14:06:51 +05:00
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) {
2021-06-11 23:19:52 +05:00
this.end();
2021-06-11 14:06:51 +05:00
} 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) {
2021-06-11 23:19:52 +05:00
this.audioResource = null;
2021-06-11 14:17:42 +05:00
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
2021-06-11 23:19:52 +05:00
* @param {({type?:StreamType;data?:any;})} [ops] Options
2021-06-11 14:06:51 +05:00
* @returns {AudioResource}
*/
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
*/
get status() {
return this.audioPlayer.state.status;
}
2021-06-11 14:28:21 +05:00
/**
* Disconnects from voice
*/
disconnect() {
2021-06-12 00:18:53 +05:00
try {
this.voiceConnection.destroy();
} catch {}
2021-06-11 14:28:21 +05:00
}
2021-06-11 14:06:51 +05:00
/**
* Stops the player
*/
2021-06-11 23:19:52 +05:00
end() {
2021-06-11 14:06:51 +05:00
this.audioPlayer.stop();
}
pause(interpolateSilence?: boolean) {
return this.audioPlayer.pause(interpolateSilence);
}
resume() {
return this.audioPlayer.unpause();
}
2021-06-11 14:06:51 +05:00
/**
* Play stream
* @param {AudioResource} resource The audio resource to play
*/
2021-06-12 00:18:53 +05:00
async playStream(resource: AudioResource<Track> = this.audioResource) {
2021-06-11 16:50:43 +05:00
if (!resource) throw new PlayerError("Audio resource is not available!");
2021-06-11 23:19:52 +05:00
if (!this.audioResource) this.audioResource = resource;
2021-06-12 00:19:48 +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-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 ✌
this.audioResource.volume.setVolumeLogarithmic(value / 200);
2021-06-12 00:18:53 +05:00
return true;
2021-06-11 23:19:52 +05:00
}
2021-06-11 16:50:43 +05:00
get streamTime() {
if (!this.audioResource) return 0;
return this.audioResource.playbackDuration;
}
2021-06-11 14:06:51 +05:00
}
2021-06-11 23:19:52 +05:00
export { BasicStreamDispatcher as StreamDispatcher };