basic voice interface
This commit is contained in:
parent
8aa932d04d
commit
3bcbfd59d9
3 changed files with 142 additions and 2 deletions
|
@ -1,6 +1,7 @@
|
||||||
{
|
{
|
||||||
"printWidth": 120,
|
"printWidth": 120,
|
||||||
"trailingComma": "none",
|
"trailingComma": "none",
|
||||||
"singleQuote": true,
|
"singleQuote": false,
|
||||||
"tabWidth": 4
|
"tabWidth": 4,
|
||||||
|
"semi": true
|
||||||
}
|
}
|
90
src/VoiceNative/VoiceSubscription.ts
Normal file
90
src/VoiceNative/VoiceSubscription.ts
Normal 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 };
|
49
src/VoiceNative/VoiceUtils.ts
Normal file
49
src/VoiceNative/VoiceUtils.ts
Normal 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 }
|
Loading…
Reference in a new issue