Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Filters from './lib/Filters';
import Node, { NodeState } from './lib/Node';
import Player, { ConnectionState } from './lib/Player';
import { Player, ConnectionState } from './lib/Player';
import Track from './lib/Track';
import UnresolvedTrack from './lib/UnresolvedTrack';
import { Vulkava } from './lib/Vulkava';
Expand Down Expand Up @@ -39,4 +39,4 @@ export {
NodeOptions,

VERSION
};
};
99 changes: 63 additions & 36 deletions lib/@types/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Node } from '../..';
import Player from '../Player';
import { DefaultQueue, Node } from '../..';
import { Player } from '../Player';
import { AbstractQueue } from '../queue/AbstractQueue';
import Track from '../Track';
import UnresolvedTrack from '../UnresolvedTrack';
Expand Down Expand Up @@ -74,74 +74,101 @@ export type VulkavaOptions = {
/** Vulkava events */
export interface VulkavaEvents {
debug: [message: string];
raw: [
error: [
node: Node,
payload: unknown,
error: Error,
];
nodeConnect: [node: Node];
nodeResume: [node: Node];
nodeDisconnect: [
node: Node,
playerCreate: [player: Player];
playerDestroy: [player: Player];
playerDisconnect: [
player: Player,
code: number,
reason: string,
];
warn: [
playerUpdate: [
oldPlayer: Player,
newPlayer: Player,
];
pong: [
node: Node,
warn: string,
ping?: number,
];
error: [
queueEnd: [player: Player];
raw: [
node: Node,
error: Error,
payload: unknown,
];
trackStart: [
recordFinished: [
node: Node,
guildId: string,
id: string,
];

// This event only works on my lavalink (https://github.com/davidffa/lavalink/releases) and while recording audio
speakingStart: [
player: Player,
track: Track,
userId: string,
];
// This event only works on my lavalink (https://github.com/davidffa/lavalink/releases) and while recording audio
speakingStop: [
player: Player,
userId: string,
];

trackEnd: [
player: Player,
track: Track,
reason: TrackEndReason,
];
trackException: [
player: Player,
track: Track | UnresolvedTrack,
exception: LoadException & { cause: string },
];
trackStart: [
player: Player,
track: Track,
];
trackStuck: [
player: Player,
track: Track,
thresholdMs: number,
];
trackException: [

// This event only works on my lavalink (https://github.com/davidffa/lavalink/releases) and while recording audio
userDisconnect: [
player: Player,
track: Track | UnresolvedTrack,
exception: LoadException & { cause: string },
userId: string,
];
playerCreate: [player: Player];
playerDestroy: [player: Player];
playerDisconnect: [
player: Player,

nodeConnect: [node: Node];
nodeDisconnect: [
node: Node,
code: number,
reason: string,
];
queueEnd: [player: Player];
pong: [
node: Node,
ping?: number,
];
recordFinished: [
nodeResume: [node: Node];
warn: [
node: Node,
guildId: string,
id: string,
warn: string,
];
}

// Speaking Events (only work on my lavalink (https://github.com/davidffa/lavalink/releases) and while recording audio)
speakingStart: [
/** Player events */
export interface PlayerEvents {
queueShuffle: [
player: Player,
userId: string,
oldQueue: DefaultQueue,
newQueue: DefaultQueue,
];
speakingStop: [
seek: [
player: Player,
userId: string,
oldPosition: number,
newPosition: number,
];
userDisconnect: [
skip: [
player: Player,
userId: string,
amount: number,
];
}

Expand Down
4 changes: 2 additions & 2 deletions lib/Filters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
TremoloOptions,
VibratoOptions
} from './@types';
import Player from './Player';
import { Player } from './Player';

export default class Filters {
private readonly player: Player;
Expand Down Expand Up @@ -269,4 +269,4 @@ export default class Filters {

this.player.node?.send(payload);
}
}
}
80 changes: 75 additions & 5 deletions lib/Player.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
/* eslint-disable @typescript-eslint/no-this-alias */

import { EventEmitter } from 'events';

import { Node, Vulkava, AbstractQueue } from '..';
import { PlayerOptions, PlayerState, PlayOptions, VoiceState } from './@types';
import { PlayerEvents, PlayerOptions, PlayerState, PlayOptions, VoiceState } from './@types';
import Filters from './Filters';
import { NodeState } from './Node';
import { DefaultQueue } from './queue/DefaultQueue';
Expand All @@ -13,8 +17,15 @@ export enum ConnectionState {
DISCONNECTED
}

// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
export interface Player {
on<Event extends keyof PlayerEvents>(event: Event, listener: (...args: PlayerEvents[Event]) => void): this;
once<Event extends keyof PlayerEvents>(event: Event, listener: (...args: PlayerEvents[Event]) => void): this;
}

/**
* Represents a Player structure
* @extends EventEmitter
* @prop {Node} node - The node that this player is connected to
* @prop {Filters} filters - The filters instance of this player
* @prop {String} guildId - The guild id of this player
Expand All @@ -31,7 +42,8 @@ export enum ConnectionState {
* @prop {State} state - The state of this player (CONNECTING, CONNECTED, DISCONNECTED)
* @prop {Object} voiceState - The player voicestate
*/
export default class Player {
// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
export class Player extends EventEmitter {
private readonly vulkava: Vulkava;
public node: Node | null;

Expand Down Expand Up @@ -88,6 +100,8 @@ export default class Player {
* @param {AbstractQueue} [options.queue] - The queue for this player
*/
constructor(vulkava: Vulkava, options: PlayerOptions) {
super();

Player.checkOptions(options);

this.vulkava = vulkava;
Expand Down Expand Up @@ -167,7 +181,11 @@ export default class Player {
private assignNode() {
const node = this.vulkava.bestNode;

const oldPlayer = this;

this.node = node;

this.vulkava.emit('playerUpdate', oldPlayer, this);
this.vulkava.emit('debug', `Assigned node ${node.identifier} to player ${this.guildId}`);
}

Expand Down Expand Up @@ -320,6 +338,8 @@ export default class Player {
this.assignNode();
}

const oldPlayer = this;

if (!this.current) {
let newTrack = await this.queue.poll();

Expand All @@ -342,6 +362,8 @@ export default class Player {

this.playing = true;

this.vulkava.emit('playerUpdate', oldPlayer, this);

if (this.node?.options.transport === 'rest') {
this.node?.rest.updatePlayer(this.guildId, {
encodedTrack: this.current.encodedTrack,
Expand Down Expand Up @@ -408,15 +430,27 @@ export default class Player {
* @param {Boolean} state - Whether to enable track looping or not
*/
public setTrackLoop(state: boolean) {
const oldPlayer = this;

this.trackRepeat = state;

if (oldPlayer.trackRepeat !== state) {
this.vulkava.emit('playerUpdate', oldPlayer, this);
}
}

/**
* Sets the queue looping
* @param {Boolean} state - Whether to enable queue looping or not
*/
public setQueueLoop(state: boolean) {
const oldPlayer = this;

this.queueRepeat = state;

if (oldPlayer.queueRepeat !== state) {
this.vulkava.emit('playerUpdate', oldPlayer, this);
}
}

/**
Expand All @@ -427,9 +461,13 @@ export default class Player {
if (!channelId || typeof channelId !== 'string') throw new TypeError('Voice channel id must be a string.');
if (this.voiceChannelId === channelId) return;

const oldPlayer = this;

this.voiceChannelId = channelId;
this.state = ConnectionState.DISCONNECTED;
this.connect();

this.vulkava.emit('playerUpdate', oldPlayer, this);
}

/**
Expand All @@ -438,7 +476,11 @@ export default class Player {
*/
public shuffleQueue() {
if (this.queue instanceof DefaultQueue) {
const oldQueue = this.queue as DefaultQueue;

(this.queue as DefaultQueue).shuffle();

this.emit('queueShuffle', this, oldQueue, this.queue);
}
}

Expand All @@ -455,6 +497,8 @@ export default class Player {
await this.queue.skipNTracks(amount);
}

this.emit('skip', this, amount > this.queue.size ? this.queue.size : amount);

if (this.node?.options.transport === 'rest') {
this.node.rest.updatePlayer(this.guildId, {
encodedTrack: null
Expand All @@ -481,8 +525,14 @@ export default class Player {

if (this.node === null) throw new Error('Assertion failed. The player does not have a node.');

const oldPlayer = this;

this.paused = state;

if (oldPlayer.paused !== state) {
this.vulkava.emit('playerUpdate', oldPlayer, this);
}

if (this.node?.options.transport === 'rest') {
this.node.rest.updatePlayer(this.guildId, {
paused: state
Expand Down Expand Up @@ -512,6 +562,8 @@ export default class Player {
return;
}

this.emit('seek', this, this.current.position, position);

if (this.node?.options.transport === 'rest') {
this.node.rest.updatePlayer(this.guildId, {
position
Expand Down Expand Up @@ -558,13 +610,31 @@ export default class Player {
}

public update(state: PlayerState): void {
if (state.position) this.position = state.position;
if (state.time) this.positionTimestamp = state.time;
const oldPlayer = this;
let hasChanged = false;

if (state.position && this.position !== state.position) {
this.position = state.position;
hasChanged = true;
}

if (state.time && this.positionTimestamp !== state.time) {
this.positionTimestamp = state.time;
hasChanged = true;
}

if (state.connected) {
this.state = ConnectionState.CONNECTED;
if (this.state !== ConnectionState.CONNECTED) {
this.state = ConnectionState.CONNECTED;
hasChanged = true;
}
} else if (this.state === ConnectionState.CONNECTED) {
this.state = ConnectionState.DISCONNECTED;
hasChanged = true;
}

if (hasChanged) {
this.vulkava.emit('playerUpdate', oldPlayer, this);
}
}
}
4 changes: 2 additions & 2 deletions lib/Recorder.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { RecordOptions } from './@types';
import Player from './Player';
import { Player } from './Player';
import { Vulkava } from './Vulkava';

export default class Recorder {
Expand Down Expand Up @@ -76,4 +76,4 @@ export default class Recorder {
guildId: this.player.guildId
});
}
}
}