basic voice interface

This commit is contained in:
Snowflake107 2021-06-11 14:51:51 +05:45
parent 8aa932d04d
commit 3bcbfd59d9
3 changed files with 142 additions and 2 deletions

View file

@ -1,6 +1,7 @@
{ {
"printWidth": 120, "printWidth": 120,
"trailingComma": "none", "trailingComma": "none",
"singleQuote": true, "singleQuote": false,
"tabWidth": 4 "tabWidth": 4,
"semi": true
} }

View file

@ -0,0 +1,90 @@
import {
AudioPlayer,
AudioResource,
createAudioPlayer,
createAudioResource,
entersState,
StreamType,
VoiceConnection,
VoiceConnectionStatus
} from "@discordjs/voice";
import { Duplex, Readable } from "stream";
class VoiceSubscription {
public readonly voiceConnection: VoiceConnection;
public readonly audioPlayer: AudioPlayer;
public connectPromise?: Promise<void>;
constructor(connection: VoiceConnection) {
this.voiceConnection = connection;
this.audioPlayer = createAudioPlayer();
connection.subscribe(this.audioPlayer);
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));
}
});
}
/**
* Creates stream
* @param {Readable|Duplex|string} src The stream source
* @param {({type?:StreamType;data?:any;inlineVolume?:boolean})} [ops] Options
* @returns {AudioResource}
*/
createStream(src: Readable | Duplex | string, ops?: { type?: StreamType, data?: any, inlineVolume?: boolean }) {
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;
}
/**
* 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 };

View file

@ -0,0 +1,49 @@
import { VoiceChannel, StageChannel } from "discord.js";
import { entersState, joinVoiceChannel, VoiceConnection, VoiceConnectionStatus } from "@discordjs/voice";
import { VoiceSubscription } from "./VoiceSubscription";
class VoiceUtils {
constructor() {
throw new Error("Cannot instantiate static class!");
}
/**
* Joins a voice channel
* @param {StageChannel|VoiceChannel} channel The voice channel
* @param {({deaf?: boolean;maxTime?: number;})} [options] Join options
* @returns {Promise<VoiceSubscription>}
*/
public static async connect(
channel: VoiceChannel | StageChannel,
options?: {
deaf?: boolean,
maxTime?: number
}): Promise<VoiceSubscription> {
let conn = joinVoiceChannel({
guildId: channel.guild.id,
channelId: channel.id,
adapterCreator: channel.guild.voiceAdapterCreator,
selfDeaf: Boolean(options?.deaf)
});
try {
conn = await entersState(conn, VoiceConnectionStatus.Ready, options?.maxTime ?? 20000);
return new VoiceSubscription(conn);
} catch(err) {
conn.destroy();
throw err;
}
}
/**
* Disconnects voice connection
* @param {VoiceConnection} connection The voice connection
*/
public static disconnect(connection: VoiceConnection) {
connection.destroy();
}
}
export { VoiceUtils }