format with tabs

This commit is contained in:
Jonny_Bro (Nikita) 2023-03-01 23:48:04 +05:00
parent 241a22b5d1
commit 83c6179e1e
16 changed files with 2496 additions and 2495 deletions

View file

@ -3,5 +3,6 @@
"trailingComma": "none",
"singleQuote": false,
"tabWidth": 4,
"useTabs": true,
"semi": true
}

File diff suppressed because it is too large Load diff

View file

@ -1,73 +1,73 @@
import { ExtractorModelData } from "../types/types";
class ExtractorModel {
name: string;
private _raw: any; // eslint-disable-line @typescript-eslint/no-explicit-any
name: string;
private _raw: any; // eslint-disable-line @typescript-eslint/no-explicit-any
/**
* Model for raw Discord Player extractors
* @param {string} extractorName Name of the extractor
* @param {object} data Extractor object
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
constructor(extractorName: string, data: any) {
/**
* The extractor name
* @type {string}
*/
this.name = extractorName;
/**
* Model for raw Discord Player extractors
* @param {string} extractorName Name of the extractor
* @param {object} data Extractor object
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
constructor(extractorName: string, data: any) {
/**
* The extractor name
* @type {string}
*/
this.name = extractorName;
/**
* The raw model
* @name ExtractorModel#_raw
* @type {any}
* @private
*/
Object.defineProperty(this, "_raw", { value: data, configurable: false, writable: false, enumerable: false });
}
/**
* The raw model
* @name ExtractorModel#_raw
* @type {any}
* @private
*/
Object.defineProperty(this, "_raw", { value: data, configurable: false, writable: false, enumerable: false });
}
/**
* Method to handle requests from `Player.play()`
* @param {string} query Query to handle
* @returns {Promise<ExtractorModelData>}
*/
async handle(query: string): Promise<ExtractorModelData> {
const data = await this._raw.getInfo(query);
if (!data) return null;
/**
* Method to handle requests from `Player.play()`
* @param {string} query Query to handle
* @returns {Promise<ExtractorModelData>}
*/
async handle(query: string): Promise<ExtractorModelData> {
const data = await this._raw.getInfo(query);
if (!data) return null;
return {
playlist: data.playlist ?? null,
data:
(data.info as Omit<ExtractorModelData, "playlist">["data"])?.map((m) => ({
title: m.title as string,
duration: m.duration as number,
thumbnail: m.thumbnail as string,
engine: m.engine,
views: m.views as number,
author: m.author as string,
description: m.description as string,
url: m.url as string,
source: m.source || "arbitrary"
})) ?? []
};
}
return {
playlist: data.playlist ?? null,
data:
(data.info as Omit<ExtractorModelData, "playlist">["data"])?.map((m) => ({
title: m.title as string,
duration: m.duration as number,
thumbnail: m.thumbnail as string,
engine: m.engine,
views: m.views as number,
author: m.author as string,
description: m.description as string,
url: m.url as string,
source: m.source || "arbitrary"
})) ?? []
};
}
/**
* Method used by Discord Player to validate query with this extractor
* @param {string} query The query to validate
* @returns {boolean}
*/
validate(query: string): boolean {
return Boolean(this._raw.validate(query));
}
/**
* Method used by Discord Player to validate query with this extractor
* @param {string} query The query to validate
* @returns {boolean}
*/
validate(query: string): boolean {
return Boolean(this._raw.validate(query));
}
/**
* The extractor version
* @type {string}
*/
get version(): string {
return this._raw.version ?? "0.0.0";
}
/**
* The extractor version
* @type {string}
*/
get version(): string {
return this._raw.version ?? "0.0.0";
}
}
export { ExtractorModel };

View file

@ -1,53 +1,53 @@
export enum ErrorStatusCode {
STREAM_ERROR = "StreamError",
AUDIO_PLAYER_ERROR = "AudioPlayerError",
PLAYER_ERROR = "PlayerError",
NO_AUDIO_RESOURCE = "NoAudioResource",
UNKNOWN_GUILD = "UnknownGuild",
INVALID_ARG_TYPE = "InvalidArgType",
UNKNOWN_EXTRACTOR = "UnknownExtractor",
INVALID_EXTRACTOR = "InvalidExtractor",
INVALID_CHANNEL_TYPE = "InvalidChannelType",
INVALID_TRACK = "InvalidTrack",
UNKNOWN_REPEAT_MODE = "UnknownRepeatMode",
TRACK_NOT_FOUND = "TrackNotFound",
NO_CONNECTION = "NoConnection",
DESTROYED_QUEUE = "DestroyedQueue"
STREAM_ERROR = "StreamError",
AUDIO_PLAYER_ERROR = "AudioPlayerError",
PLAYER_ERROR = "PlayerError",
NO_AUDIO_RESOURCE = "NoAudioResource",
UNKNOWN_GUILD = "UnknownGuild",
INVALID_ARG_TYPE = "InvalidArgType",
UNKNOWN_EXTRACTOR = "UnknownExtractor",
INVALID_EXTRACTOR = "InvalidExtractor",
INVALID_CHANNEL_TYPE = "InvalidChannelType",
INVALID_TRACK = "InvalidTrack",
UNKNOWN_REPEAT_MODE = "UnknownRepeatMode",
TRACK_NOT_FOUND = "TrackNotFound",
NO_CONNECTION = "NoConnection",
DESTROYED_QUEUE = "DestroyedQueue"
}
export class PlayerError extends Error {
message: string;
statusCode: ErrorStatusCode;
createdAt = new Date();
message: string;
statusCode: ErrorStatusCode;
createdAt = new Date();
constructor(message: string, code: ErrorStatusCode = ErrorStatusCode.PLAYER_ERROR) {
super();
constructor(message: string, code: ErrorStatusCode = ErrorStatusCode.PLAYER_ERROR) {
super();
this.message = `[${code}] ${message}`;
this.statusCode = code;
this.name = code;
this.message = `[${code}] ${message}`;
this.statusCode = code;
this.name = code;
Error.captureStackTrace(this);
}
Error.captureStackTrace(this);
}
get createdTimestamp() {
return this.createdAt.getTime();
}
get createdTimestamp() {
return this.createdAt.getTime();
}
valueOf() {
return this.statusCode;
}
valueOf() {
return this.statusCode;
}
toJSON() {
return {
stack: this.stack,
code: this.statusCode,
message: this.message,
created: this.createdTimestamp
};
}
toJSON() {
return {
stack: this.stack,
code: this.statusCode,
message: this.message,
created: this.createdTimestamp
};
}
toString() {
return this.stack;
}
toString() {
return this.stack;
}
}

View file

@ -3,136 +3,136 @@ import { Track } from "./Track";
import { PlaylistInitData, PlaylistJSON, TrackJSON, TrackSource } from "../types/types";
class Playlist {
public readonly player: Player;
public tracks: Track[];
public title: string;
public description: string;
public thumbnail: string;
public type: "album" | "playlist";
public source: TrackSource;
public author: {
name: string;
url: string;
};
public id: string;
public url: string;
public readonly rawPlaylist?: any; // eslint-disable-line @typescript-eslint/no-explicit-any
public readonly player: Player;
public tracks: Track[];
public title: string;
public description: string;
public thumbnail: string;
public type: "album" | "playlist";
public source: TrackSource;
public author: {
name: string;
url: string;
};
public id: string;
public url: string;
public readonly rawPlaylist?: any; // eslint-disable-line @typescript-eslint/no-explicit-any
/**
* Playlist constructor
* @param {Player} player The player
* @param {PlaylistInitData} data The data
*/
constructor(player: Player, data: PlaylistInitData) {
/**
* The player
* @name Playlist#player
* @type {Player}
* @readonly
*/
this.player = player;
/**
* Playlist constructor
* @param {Player} player The player
* @param {PlaylistInitData} data The data
*/
constructor(player: Player, data: PlaylistInitData) {
/**
* The player
* @name Playlist#player
* @type {Player}
* @readonly
*/
this.player = player;
/**
* The tracks in this playlist
* @name Playlist#tracks
* @type {Track[]}
*/
this.tracks = data.tracks ?? [];
/**
* The tracks in this playlist
* @name Playlist#tracks
* @type {Track[]}
*/
this.tracks = data.tracks ?? [];
/**
* The author of this playlist
* @name Playlist#author
* @type {object}
*/
this.author = data.author;
/**
* The author of this playlist
* @name Playlist#author
* @type {object}
*/
this.author = data.author;
/**
* The description
* @name Playlist#description
* @type {string}
*/
this.description = data.description;
/**
* The description
* @name Playlist#description
* @type {string}
*/
this.description = data.description;
/**
* The thumbnail of this playlist
* @name Playlist#thumbnail
* @type {string}
*/
this.thumbnail = data.thumbnail;
/**
* The thumbnail of this playlist
* @name Playlist#thumbnail
* @type {string}
*/
this.thumbnail = data.thumbnail;
/**
* The playlist type:
* - `album`
* - `playlist`
* @name Playlist#type
* @type {string}
*/
this.type = data.type;
/**
* The playlist type:
* - `album`
* - `playlist`
* @name Playlist#type
* @type {string}
*/
this.type = data.type;
/**
* The source of this playlist:
* - `youtube`
* - `soundcloud`
* - `spotify`
* - `arbitrary`
* @name Playlist#source
* @type {string}
*/
this.source = data.source;
/**
* The source of this playlist:
* - `youtube`
* - `soundcloud`
* - `spotify`
* - `arbitrary`
* @name Playlist#source
* @type {string}
*/
this.source = data.source;
/**
* The playlist id
* @name Playlist#id
* @type {string}
*/
this.id = data.id;
/**
* The playlist id
* @name Playlist#id
* @type {string}
*/
this.id = data.id;
/**
* The playlist url
* @name Playlist#url
* @type {string}
*/
this.url = data.url;
/**
* The playlist url
* @name Playlist#url
* @type {string}
*/
this.url = data.url;
/**
* The playlist title
* @type {string}
*/
this.title = data.title;
/**
* The playlist title
* @type {string}
*/
this.title = data.title;
/**
* @name Playlist#rawPlaylist
* @type {any}
* @readonly
*/
}
/**
* @name Playlist#rawPlaylist
* @type {any}
* @readonly
*/
}
*[Symbol.iterator]() {
yield* this.tracks;
}
*[Symbol.iterator]() {
yield* this.tracks;
}
/**
* JSON representation of this playlist
* @param {boolean} [withTracks=true] If it should build json with tracks
* @returns {PlaylistJSON}
*/
toJSON(withTracks = true) {
const payload = {
id: this.id,
url: this.url,
title: this.title,
description: this.description,
thumbnail: this.thumbnail,
type: this.type,
source: this.source,
author: this.author,
tracks: [] as TrackJSON[]
};
/**
* JSON representation of this playlist
* @param {boolean} [withTracks=true] If it should build json with tracks
* @returns {PlaylistJSON}
*/
toJSON(withTracks = true) {
const payload = {
id: this.id,
url: this.url,
title: this.title,
description: this.description,
thumbnail: this.thumbnail,
type: this.type,
source: this.source,
author: this.author,
tracks: [] as TrackJSON[]
};
if (withTracks) payload.tracks = this.tracks.map((m) => m.toJSON(true));
if (withTracks) payload.tracks = this.tracks.map((m) => m.toJSON(true));
return payload as PlaylistJSON;
}
return payload as PlaylistJSON;
}
}
export { Playlist };

File diff suppressed because it is too large Load diff

View file

@ -5,185 +5,185 @@ import { Playlist } from "./Playlist";
import { Queue } from "./Queue";
class Track {
public player!: Player;
public title!: string;
public description!: string;
public author!: string;
public url!: string;
public thumbnail!: string;
public duration!: string;
public views!: number;
public requestedBy!: User;
public playlist?: Playlist;
public readonly raw: RawTrackData = {} as RawTrackData;
public readonly id = SnowflakeUtil.generate().toString();
public player!: Player;
public title!: string;
public description!: string;
public author!: string;
public url!: string;
public thumbnail!: string;
public duration!: string;
public views!: number;
public requestedBy!: User;
public playlist?: Playlist;
public readonly raw: RawTrackData = {} as RawTrackData;
public readonly id = SnowflakeUtil.generate().toString();
/**
* Track constructor
* @param {Player} player The player that instantiated this Track
* @param {RawTrackData} data Track data
*/
constructor(player: Player, data: RawTrackData) {
/**
* The player that instantiated this Track
* @name Track#player
* @type {Player}
* @readonly
*/
Object.defineProperty(this, "player", { value: player, enumerable: false });
/**
* Track constructor
* @param {Player} player The player that instantiated this Track
* @param {RawTrackData} data Track data
*/
constructor(player: Player, data: RawTrackData) {
/**
* The player that instantiated this Track
* @name Track#player
* @type {Player}
* @readonly
*/
Object.defineProperty(this, "player", { value: player, enumerable: false });
/**
* Title of this track
* @name Track#title
* @type {string}
*/
/**
* Title of this track
* @name Track#title
* @type {string}
*/
/**
* Description of this track
* @name Track#description
* @type {string}
*/
/**
* Description of this track
* @name Track#description
* @type {string}
*/
/**
* Author of this track
* @name Track#author
* @type {string}
*/
/**
* Author of this track
* @name Track#author
* @type {string}
*/
/**
* URL of this track
* @name Track#url
* @type {string}
*/
/**
* URL of this track
* @name Track#url
* @type {string}
*/
/**
* Thumbnail of this track
* @name Track#thumbnail
* @type {string}
*/
/**
* Thumbnail of this track
* @name Track#thumbnail
* @type {string}
*/
/**
* Duration of this track
* @name Track#duration
* @type {string}
*/
/**
* Duration of this track
* @name Track#duration
* @type {string}
*/
/**
* Views count of this track
* @name Track#views
* @type {number}
*/
/**
* Views count of this track
* @name Track#views
* @type {number}
*/
/**
* Person who requested this track
* @name Track#requestedBy
* @type {User}
*/
/**
* Person who requested this track
* @name Track#requestedBy
* @type {User}
*/
/**
* If this track belongs to playlist
* @name Track#fromPlaylist
* @type {boolean}
*/
/**
* If this track belongs to playlist
* @name Track#fromPlaylist
* @type {boolean}
*/
/**
* Raw track data
* @name Track#raw
* @type {RawTrackData}
*/
/**
* Raw track data
* @name Track#raw
* @type {RawTrackData}
*/
/**
* The track id
* @name Track#id
* @type {Snowflake}
* @readonly
*/
/**
* The track id
* @name Track#id
* @type {Snowflake}
* @readonly
*/
/**
* The playlist which track belongs
* @name Track#playlist
* @type {Playlist}
*/
/**
* The playlist which track belongs
* @name Track#playlist
* @type {Playlist}
*/
void this._patch(data);
}
void this._patch(data);
}
private _patch(data: RawTrackData) {
this.title = escapeMarkdown(data.title ?? "");
this.description = data.description ?? "";
this.author = data.author ?? "";
this.url = data.url ?? "";
this.thumbnail = data.thumbnail ?? "";
this.duration = data.duration ?? "";
this.views = data.views ?? 0;
this.requestedBy = data.requestedBy;
this.playlist = data.playlist;
private _patch(data: RawTrackData) {
this.title = escapeMarkdown(data.title ?? "");
this.description = data.description ?? "";
this.author = data.author ?? "";
this.url = data.url ?? "";
this.thumbnail = data.thumbnail ?? "";
this.duration = data.duration ?? "";
this.views = data.views ?? 0;
this.requestedBy = data.requestedBy;
this.playlist = data.playlist;
// raw
Object.defineProperty(this, "raw", { value: Object.assign({}, { source: data.raw?.source ?? data.source }, data.raw ?? data), enumerable: false });
}
// raw
Object.defineProperty(this, "raw", { value: Object.assign({}, { source: data.raw?.source ?? data.source }, data.raw ?? data), enumerable: false });
}
/**
* The queue in which this track is located
* @type {Queue}
*/
get queue(): Queue {
return this.player.queues.find((q) => q.tracks.some((ab) => ab.id === this.id));
}
/**
* The queue in which this track is located
* @type {Queue}
*/
get queue(): Queue {
return this.player.queues.find((q) => q.tracks.some((ab) => ab.id === this.id));
}
/**
* The track duration in millisecond
* @type {number}
*/
get durationMS(): number {
const times = (n: number, t: number) => {
let tn = 1;
for (let i = 0; i < t; i++) tn *= n;
return t <= 0 ? 1000 : tn * 1000;
};
/**
* The track duration in millisecond
* @type {number}
*/
get durationMS(): number {
const times = (n: number, t: number) => {
let tn = 1;
for (let i = 0; i < t; i++) tn *= n;
return t <= 0 ? 1000 : tn * 1000;
};
return this.duration
.split(":")
.reverse()
.map((m, i) => parseInt(m) * times(60, i))
.reduce((a, c) => a + c, 0);
}
return this.duration
.split(":")
.reverse()
.map((m, i) => parseInt(m) * times(60, i))
.reduce((a, c) => a + c, 0);
}
/**
* Returns source of this track
* @type {TrackSource}
*/
get source() {
return this.raw.source ?? "arbitrary";
}
/**
* Returns source of this track
* @type {TrackSource}
*/
get source() {
return this.raw.source ?? "arbitrary";
}
/**
* String representation of this track
* @returns {string}
*/
toString(): string {
return `${this.title} by ${this.author}`;
}
/**
* String representation of this track
* @returns {string}
*/
toString(): string {
return `${this.title} by ${this.author}`;
}
/**
* Raw JSON representation of this track
* @returns {TrackJSON}
*/
toJSON(hidePlaylist?: boolean) {
return {
id: this.id,
title: this.title,
description: this.description,
author: this.author,
url: this.url,
thumbnail: this.thumbnail,
duration: this.duration,
durationMS: this.durationMS,
views: this.views,
requestedBy: this.requestedBy?.id,
playlist: hidePlaylist ? null : this.playlist?.toJSON() ?? null
} as TrackJSON;
}
/**
* Raw JSON representation of this track
* @returns {TrackJSON}
*/
toJSON(hidePlaylist?: boolean) {
return {
id: this.id,
title: this.title,
description: this.description,
author: this.author,
url: this.url,
thumbnail: this.thumbnail,
duration: this.duration,
durationMS: this.durationMS,
views: this.views,
requestedBy: this.requestedBy?.id,
playlist: hidePlaylist ? null : this.playlist?.toJSON() ?? null
} as TrackJSON;
}
}
export default Track;

View file

@ -1,15 +1,15 @@
import {
AudioPlayer,
AudioPlayerError,
AudioPlayerStatus,
AudioResource,
createAudioPlayer,
createAudioResource,
entersState,
StreamType,
VoiceConnection,
VoiceConnectionStatus,
VoiceConnectionDisconnectReason
AudioPlayer,
AudioPlayerError,
AudioPlayerStatus,
AudioResource,
createAudioPlayer,
createAudioResource,
entersState,
StreamType,
VoiceConnection,
VoiceConnectionStatus,
VoiceConnectionDisconnectReason
} from "@discordjs/voice";
import { StageChannel, VoiceChannel } from "discord.js";
import { Duplex, Readable } from "stream";
@ -19,235 +19,235 @@ import { Util } from "../utils/Util";
import { PlayerError, ErrorStatusCode } from "../Structures/PlayerError";
export interface VoiceEvents {
/* 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 */
/* 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 */
}
class StreamDispatcher extends EventEmitter<VoiceEvents> {
public readonly voiceConnection: VoiceConnection;
public readonly audioPlayer: AudioPlayer;
public channel: VoiceChannel | StageChannel;
public audioResource?: AudioResource<Track>;
private readyLock = false;
public paused: boolean;
public readonly voiceConnection: VoiceConnection;
public readonly audioPlayer: AudioPlayer;
public channel: VoiceChannel | StageChannel;
public audioResource?: AudioResource<Track>;
private readyLock = false;
public paused: boolean;
/**
* Creates new connection object
* @param {VoiceConnection} connection The connection
* @param {VoiceChannel|StageChannel} channel The connected channel
* @private
*/
constructor(connection: VoiceConnection, channel: VoiceChannel | StageChannel, public readonly connectionTimeout: number = 20000) {
super();
/**
* Creates new connection object
* @param {VoiceConnection} connection The connection
* @param {VoiceChannel|StageChannel} channel The connected channel
* @private
*/
constructor(connection: VoiceConnection, channel: VoiceChannel | StageChannel, public readonly connectionTimeout: number = 20000) {
super();
/**
* The voice connection
* @type {VoiceConnection}
*/
this.voiceConnection = connection;
/**
* The voice connection
* @type {VoiceConnection}
*/
this.voiceConnection = connection;
/**
* The audio player
* @type {AudioPlayer}
*/
this.audioPlayer = createAudioPlayer();
/**
* The audio player
* @type {AudioPlayer}
*/
this.audioPlayer = createAudioPlayer();
/**
* The voice channel
* @type {VoiceChannel|StageChannel}
*/
this.channel = channel;
/**
* The voice channel
* @type {VoiceChannel|StageChannel}
*/
this.channel = channel;
/**
* The paused state
* @type {boolean}
*/
this.paused = false;
/**
* The paused state
* @type {boolean}
*/
this.paused = false;
this.voiceConnection.on("stateChange", async (_, newState) => {
if (newState.status === VoiceConnectionStatus.Disconnected) {
if (newState.reason === VoiceConnectionDisconnectReason.WebSocketClose && newState.closeCode === 4014) {
try {
await entersState(this.voiceConnection, VoiceConnectionStatus.Connecting, this.connectionTimeout);
} catch {
try {
this.voiceConnection.destroy();
} catch (err) {
this.emit("error", err as AudioPlayerError);
}
}
} else if (this.voiceConnection.rejoinAttempts < 5) {
await Util.wait((this.voiceConnection.rejoinAttempts + 1) * 5000);
this.voiceConnection.rejoin();
} else {
try {
this.voiceConnection.destroy();
} catch (err) {
this.emit("error", err as AudioPlayerError);
}
}
} else if (newState.status === VoiceConnectionStatus.Destroyed) {
this.end();
} else if (!this.readyLock && (newState.status === VoiceConnectionStatus.Connecting || newState.status === VoiceConnectionStatus.Signalling)) {
this.readyLock = true;
try {
await entersState(this.voiceConnection, VoiceConnectionStatus.Ready, this.connectionTimeout);
} catch {
if (this.voiceConnection.state.status !== VoiceConnectionStatus.Destroyed) {
try {
this.voiceConnection.destroy();
} catch (err) {
this.emit("error", err as AudioPlayerError);
}
}
} finally {
this.readyLock = false;
}
}
});
this.voiceConnection.on("stateChange", async (_, newState) => {
if (newState.status === VoiceConnectionStatus.Disconnected) {
if (newState.reason === VoiceConnectionDisconnectReason.WebSocketClose && newState.closeCode === 4014) {
try {
await entersState(this.voiceConnection, VoiceConnectionStatus.Connecting, this.connectionTimeout);
} catch {
try {
this.voiceConnection.destroy();
} catch (err) {
this.emit("error", err as AudioPlayerError);
}
}
} else if (this.voiceConnection.rejoinAttempts < 5) {
await Util.wait((this.voiceConnection.rejoinAttempts + 1) * 5000);
this.voiceConnection.rejoin();
} else {
try {
this.voiceConnection.destroy();
} catch (err) {
this.emit("error", err as AudioPlayerError);
}
}
} else if (newState.status === VoiceConnectionStatus.Destroyed) {
this.end();
} else if (!this.readyLock && (newState.status === VoiceConnectionStatus.Connecting || newState.status === VoiceConnectionStatus.Signalling)) {
this.readyLock = true;
try {
await entersState(this.voiceConnection, VoiceConnectionStatus.Ready, this.connectionTimeout);
} catch {
if (this.voiceConnection.state.status !== VoiceConnectionStatus.Destroyed) {
try {
this.voiceConnection.destroy();
} catch (err) {
this.emit("error", err as AudioPlayerError);
}
}
} finally {
this.readyLock = false;
}
}
});
this.audioPlayer.on("stateChange", (oldState, newState) => {
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) {
if (!this.paused) {
void this.emit("finish", this.audioResource);
this.audioResource = null;
}
}
});
this.audioPlayer.on("stateChange", (oldState, newState) => {
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) {
if (!this.paused) {
void this.emit("finish", this.audioResource);
this.audioResource = null;
}
}
});
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);
}
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);
}
/**
* Creates stream
* @param {Readable|Duplex|string} src The stream source
* @param {object} [ops] Options
* @returns {AudioResource}
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
createStream(src: Readable | Duplex | string, ops?: { type?: StreamType; data?: any; disableVolume?: boolean }) {
this.audioResource = createAudioResource(src, {
inputType: ops?.type ?? StreamType.Arbitrary,
metadata: ops?.data,
// eslint-disable-next-line no-extra-boolean-cast
inlineVolume: !Boolean(ops?.disableVolume)
});
/**
* Creates stream
* @param {Readable|Duplex|string} src The stream source
* @param {object} [ops] Options
* @returns {AudioResource}
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
createStream(src: Readable | Duplex | string, ops?: { type?: StreamType; data?: any; disableVolume?: boolean }) {
this.audioResource = createAudioResource(src, {
inputType: ops?.type ?? StreamType.Arbitrary,
metadata: ops?.data,
// eslint-disable-next-line no-extra-boolean-cast
inlineVolume: !Boolean(ops?.disableVolume)
});
return this.audioResource;
}
return this.audioResource;
}
/**
* The player status
* @type {AudioPlayerStatus}
*/
get status() {
return this.audioPlayer.state.status;
}
/**
* The player status
* @type {AudioPlayerStatus}
*/
get status() {
return this.audioPlayer.state.status;
}
/**
* Disconnects from voice
* @returns {void}
*/
disconnect() {
try {
this.audioPlayer.stop(true);
this.voiceConnection.destroy();
} catch {} // eslint-disable-line no-empty
}
/**
* Disconnects from voice
* @returns {void}
*/
disconnect() {
try {
this.audioPlayer.stop(true);
this.voiceConnection.destroy();
} catch {} // eslint-disable-line no-empty
}
/**
* Stops the player
* @returns {void}
*/
end() {
this.audioPlayer.stop();
}
/**
* Stops the player
* @returns {void}
*/
end() {
this.audioPlayer.stop();
}
/**
* 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) {
const success = this.audioPlayer.pause(interpolateSilence);
this.paused = success;
return success;
}
/**
* 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) {
const success = this.audioPlayer.pause(interpolateSilence);
this.paused = success;
return success;
}
/**
* Resumes the stream playback
* @returns {boolean}
*/
resume() {
const success = this.audioPlayer.unpause();
this.paused = !success;
return success;
}
/**
* Resumes the stream playback
* @returns {boolean}
*/
resume() {
const success = this.audioPlayer.unpause();
this.paused = !success;
return success;
}
/**
* Play stream
* @param {AudioResource<Track>} [resource=this.audioResource] The audio resource to play
* @returns {Promise<StreamDispatcher>}
*/
async playStream(resource: AudioResource<Track> = this.audioResource) {
if (!resource) throw new PlayerError("Audio resource is not available!", ErrorStatusCode.NO_AUDIO_RESOURCE);
if (resource.ended) return void this.emit("error", new PlayerError("Cannot play a resource that has already ended.") as unknown as AudioPlayerError);
if (!this.audioResource) this.audioResource = resource;
if (this.voiceConnection.state.status !== VoiceConnectionStatus.Ready) {
try {
await entersState(this.voiceConnection, VoiceConnectionStatus.Ready, this.connectionTimeout);
} catch (err) {
return void this.emit("error", err as AudioPlayerError);
}
}
/**
* Play stream
* @param {AudioResource<Track>} [resource=this.audioResource] The audio resource to play
* @returns {Promise<StreamDispatcher>}
*/
async playStream(resource: AudioResource<Track> = this.audioResource) {
if (!resource) throw new PlayerError("Audio resource is not available!", ErrorStatusCode.NO_AUDIO_RESOURCE);
if (resource.ended) return void this.emit("error", new PlayerError("Cannot play a resource that has already ended.") as unknown as AudioPlayerError);
if (!this.audioResource) this.audioResource = resource;
if (this.voiceConnection.state.status !== VoiceConnectionStatus.Ready) {
try {
await entersState(this.voiceConnection, VoiceConnectionStatus.Ready, this.connectionTimeout);
} catch (err) {
return void this.emit("error", err as AudioPlayerError);
}
}
try {
this.audioPlayer.play(resource);
} catch (e) {
this.emit("error", e as AudioPlayerError);
}
try {
this.audioPlayer.play(resource);
} catch (e) {
this.emit("error", e as AudioPlayerError);
}
return this;
}
return this;
}
/**
* Sets playback volume
* @param {number} value The volume amount
* @returns {boolean}
*/
setVolume(value: number) {
if (!this.audioResource?.volume || isNaN(value) || value < 0 || value > Infinity) return false;
/**
* Sets playback volume
* @param {number} value The volume amount
* @returns {boolean}
*/
setVolume(value: number) {
if (!this.audioResource?.volume || isNaN(value) || value < 0 || value > Infinity) return false;
this.audioResource.volume.setVolumeLogarithmic(value / 100);
return true;
}
this.audioResource.volume.setVolumeLogarithmic(value / 100);
return true;
}
/**
* The current volume
* @type {number}
*/
get volume() {
if (!this.audioResource?.volume) return 100;
const currentVol = this.audioResource.volume.volume;
return Math.round(Math.pow(currentVol, 1 / 1.660964) * 100);
}
/**
* The current volume
* @type {number}
*/
get volume() {
if (!this.audioResource?.volume) return 100;
const currentVol = this.audioResource.volume.volume;
return Math.round(Math.pow(currentVol, 1 / 1.660964) * 100);
}
/**
* The playback time
* @type {number}
*/
get streamTime() {
if (!this.audioResource) return 0;
return this.audioResource.playbackDuration;
}
/**
* The playback time
* @type {number}
*/
get streamTime() {
if (!this.audioResource) return 0;
return this.audioResource.playbackDuration;
}
}
export { StreamDispatcher as StreamDispatcher };

View file

@ -3,80 +3,80 @@ import { DiscordGatewayAdapterCreator, joinVoiceChannel, VoiceConnection } from
import { StreamDispatcher } from "./StreamDispatcher";
class VoiceUtils {
public cache: Collection<Snowflake, StreamDispatcher>;
public cache: Collection<Snowflake, StreamDispatcher>;
/**
* The voice utils
* @private
*/
constructor() {
/**
* The cache where voice utils stores stream managers
* @type {Collection<Snowflake, StreamDispatcher>}
*/
this.cache = new Collection<Snowflake, StreamDispatcher>();
}
/**
* The voice utils
* @private
*/
constructor() {
/**
* The cache where voice utils stores stream managers
* @type {Collection<Snowflake, StreamDispatcher>}
*/
this.cache = new Collection<Snowflake, StreamDispatcher>();
}
/**
* Joins a voice channel, creating basic stream dispatch manager
* @param {StageChannel|VoiceChannel} channel The voice channel
* @param {object} [options] Join options
* @returns {Promise<StreamDispatcher>}
*/
public async connect(
channel: VoiceChannel | StageChannel,
options?: {
deaf?: boolean;
maxTime?: number;
}
): Promise<StreamDispatcher> {
const conn = await this.join(channel, options);
const sub = new StreamDispatcher(conn, channel, options.maxTime);
this.cache.set(channel.guild.id, sub);
return sub;
}
/**
* Joins a voice channel, creating basic stream dispatch manager
* @param {StageChannel|VoiceChannel} channel The voice channel
* @param {object} [options] Join options
* @returns {Promise<StreamDispatcher>}
*/
public async connect(
channel: VoiceChannel | StageChannel,
options?: {
deaf?: boolean;
maxTime?: number;
}
): Promise<StreamDispatcher> {
const conn = await this.join(channel, options);
const sub = new StreamDispatcher(conn, channel, options.maxTime);
this.cache.set(channel.guild.id, sub);
return sub;
}
/**
* Joins a voice channel
* @param {StageChannel|VoiceChannel} [channel] The voice/stage channel to join
* @param {object} [options] Join options
* @returns {VoiceConnection}
*/
public async join(
channel: VoiceChannel | StageChannel,
options?: {
deaf?: boolean;
maxTime?: number;
}
) {
const conn = joinVoiceChannel({
guildId: channel.guild.id,
channelId: channel.id,
adapterCreator: channel.guild.voiceAdapterCreator as unknown as DiscordGatewayAdapterCreator,
selfDeaf: Boolean(options.deaf)
});
/**
* Joins a voice channel
* @param {StageChannel|VoiceChannel} [channel] The voice/stage channel to join
* @param {object} [options] Join options
* @returns {VoiceConnection}
*/
public async join(
channel: VoiceChannel | StageChannel,
options?: {
deaf?: boolean;
maxTime?: number;
}
) {
const conn = joinVoiceChannel({
guildId: channel.guild.id,
channelId: channel.id,
adapterCreator: channel.guild.voiceAdapterCreator as unknown as DiscordGatewayAdapterCreator,
selfDeaf: Boolean(options.deaf)
});
return conn;
}
return conn;
}