docs(VoiceCall): Make lib play music
3 hours :v
This commit is contained in:
		@@ -60,3 +60,558 @@ let i = setInterval(() => {
 | 
			
		||||
	else console.log('waiting for voice connection');
 | 
			
		||||
}, 250);
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
# Play Music with module (support Play, Pause, Search, Skip, Previous, Volume, Loop)
 | 
			
		||||
 | 
			
		||||
```js
 | 
			
		||||
/* Copyright aiko-chan-ai @2022. All rights reserved. */
 | 
			
		||||
const DjsVoice = require('@discordjs/voice');
 | 
			
		||||
const Discord = require('discord.js-selfbot-v13');
 | 
			
		||||
const playDL = require('play-dl');
 | 
			
		||||
const EventEmitter = require('events');
 | 
			
		||||
const Event = {
 | 
			
		||||
    READY: 'ready',
 | 
			
		||||
    NO_SEARCH_RESULT: 'searchNoResult',
 | 
			
		||||
    SEARCH_RESULT: 'searchResult',
 | 
			
		||||
    PLAY_SONG: 'playSong',
 | 
			
		||||
    ADD_SONG: 'addSong',
 | 
			
		||||
    ADD_PLAYLIST: 'addPlaylist',
 | 
			
		||||
    LEAVE_VC: 'disconnect',
 | 
			
		||||
    FINISH: 'finish',
 | 
			
		||||
    EMPTY: 'empty',
 | 
			
		||||
    ERROR: 'error',
 | 
			
		||||
}
 | 
			
		||||
class Stack {
 | 
			
		||||
    constructor() {
 | 
			
		||||
        this.data = [];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    push(item) {
 | 
			
		||||
        return this.data.push(item);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pop() {
 | 
			
		||||
        return this.data.pop();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    peek() {
 | 
			
		||||
        return this.data[this.length - 1];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get length() {
 | 
			
		||||
        return this.data.length;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    isEmpty() {
 | 
			
		||||
        return this.length === 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    reset () {
 | 
			
		||||
        this.data = [];
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class Queue {
 | 
			
		||||
    constructor() {
 | 
			
		||||
        this.data = [];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    enqueue(item) {
 | 
			
		||||
        return this.data.unshift(item);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    dequeue() {
 | 
			
		||||
        return this.data.pop();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    peek() {
 | 
			
		||||
        return this.data[this.length - 1];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get length() {
 | 
			
		||||
        return this.data.length;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    isEmpty() {
 | 
			
		||||
        return this.data.length === 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    reset() {
 | 
			
		||||
        this.data = [];
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
class Player extends EventEmitter {
 | 
			
		||||
    constructor(client, options = {}) {
 | 
			
		||||
        super();
 | 
			
		||||
        if (!client || !client instanceof Discord.Client) throw new Error('Invalid Discord Client (Selfbot)');
 | 
			
		||||
        Object.defineProperty(this, 'client', { value: client });
 | 
			
		||||
        this._playDl = playDL;
 | 
			
		||||
        this._queue = new Queue();
 | 
			
		||||
        this._previousSongs = new Stack();
 | 
			
		||||
        this.song = null;
 | 
			
		||||
        this.guild = undefined;
 | 
			
		||||
        this.channel = undefined;
 | 
			
		||||
        this._currentResourceAudio = undefined;
 | 
			
		||||
        this._currentTime = 0;
 | 
			
		||||
        this._playingTime = null;
 | 
			
		||||
        this.isPlaying = false;
 | 
			
		||||
        this.volume = 100;
 | 
			
		||||
        this.loopMode = 0;
 | 
			
		||||
        this._timeoutEmpty = undefined;
 | 
			
		||||
        this._player = DjsVoice.createAudioPlayer({
 | 
			
		||||
            behaviors: {
 | 
			
		||||
                noSubscriber: DjsVoice.NoSubscriberBehavior.Play,
 | 
			
		||||
            },
 | 
			
		||||
        });
 | 
			
		||||
        this._playerEvent();
 | 
			
		||||
        this._validateOptions(options);
 | 
			
		||||
        this._discordEvent();
 | 
			
		||||
        this._privateEvent();
 | 
			
		||||
    }
 | 
			
		||||
    get currentTime() {
 | 
			
		||||
        return this._currentTime || Date.now() - this._playingTime;
 | 
			
		||||
    }
 | 
			
		||||
    get currentConnection() {
 | 
			
		||||
        return DjsVoice.getVoiceConnection(this.guild?.id || null);
 | 
			
		||||
    }
 | 
			
		||||
    get queue() {
 | 
			
		||||
        return this._queue.data;
 | 
			
		||||
    }
 | 
			
		||||
    get previousSongs() {
 | 
			
		||||
        return this._previousSongs.data;
 | 
			
		||||
    }
 | 
			
		||||
    authorization() {
 | 
			
		||||
        this._playDl.authorization();
 | 
			
		||||
    }
 | 
			
		||||
    _validateOptions(options) {
 | 
			
		||||
        if (typeof options !== 'object') throw new Error(`Invalid options type (Required: object, got: ${typeof options})`);
 | 
			
		||||
        this.options = {
 | 
			
		||||
            directLink: true,
 | 
			
		||||
            joinNewVoiceChannel: true,
 | 
			
		||||
            waitingUserToPlayInDMs: true,
 | 
			
		||||
            nsfw: false,
 | 
			
		||||
            leaveOnEmpty: true,
 | 
			
		||||
            leaveOnFinish: true,
 | 
			
		||||
            leaveOnStop: true,
 | 
			
		||||
            savePreviousSongs: true,
 | 
			
		||||
            emptyCooldown: 10_000,
 | 
			
		||||
        }
 | 
			
		||||
        if (typeof options.directLink === 'boolean') {
 | 
			
		||||
            this.options.directLink = options.directLink;
 | 
			
		||||
        }
 | 
			
		||||
        if (typeof options.joinNewVoiceChannel === 'boolean') {
 | 
			
		||||
            this.options.joinNewVoiceChannel = options.joinNewVoiceChannel;
 | 
			
		||||
        }
 | 
			
		||||
        if (typeof options.waitingUserToPlayInDMs === 'boolean') {
 | 
			
		||||
            this.options.waitingUserToPlayInDMs = options.waitingUserToPlayInDMs;
 | 
			
		||||
        }
 | 
			
		||||
        if (typeof options.nsfw === 'boolean') {
 | 
			
		||||
            this.options.nsfw = options.nsfw;
 | 
			
		||||
        }
 | 
			
		||||
        if (typeof options.leaveOnEmpty === 'boolean') {
 | 
			
		||||
            if (typeof options.emptyCooldown === 'number') {
 | 
			
		||||
                this.options.leaveOnEmpty = options.leaveOnEmpty;
 | 
			
		||||
                this.options.emptyCooldown = options.emptyCooldown;
 | 
			
		||||
            } else {
 | 
			
		||||
                this.options.leaveOnEmpty = false;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if (typeof options.leaveOnFinish === 'boolean') {
 | 
			
		||||
            this.options.leaveOnFinish = options.leaveOnFinish;
 | 
			
		||||
        }
 | 
			
		||||
        if (typeof options.leaveOnStop === 'boolean') {
 | 
			
		||||
            this.options.leaveOnStop = options.leaveOnStop;
 | 
			
		||||
        }
 | 
			
		||||
        if (typeof options.savePreviousSongs === 'boolean') {
 | 
			
		||||
            this.options.savePreviousSongs = options.savePreviousSongs;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    async play(options = {}) {
 | 
			
		||||
        const {
 | 
			
		||||
            message,
 | 
			
		||||
            channel,
 | 
			
		||||
            query,
 | 
			
		||||
        } = options;
 | 
			
		||||
        if (!(message instanceof Discord.Message)) throw new Error(`Invalid message type (Required: Message, got: ${typeof message})`);
 | 
			
		||||
        if (channel &&
 | 
			
		||||
            (
 | 
			
		||||
                channel instanceof Discord.DMChannel ||
 | 
			
		||||
                channel instanceof Discord.PartialGroupDMChannel ||
 | 
			
		||||
                channel instanceof Discord.VoiceChannel ||
 | 
			
		||||
                channel instanceof Discord.StageChannel
 | 
			
		||||
            )
 | 
			
		||||
        ) {
 | 
			
		||||
            let checkChangeVC = false;
 | 
			
		||||
            if (!this.channel) this.channel = channel;
 | 
			
		||||
            else {
 | 
			
		||||
                if (this.options.joinNewVoiceChannel) {
 | 
			
		||||
                    if (this.channel.id !== channel.id) checkChangeVC = true;
 | 
			
		||||
                    this.channel = channel;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            this.guild = channel.guild;
 | 
			
		||||
            if (typeof query !== 'string') throw new Error(`Invalid query type (Required: string, got: ${typeof query})`);
 | 
			
		||||
            const result = await this.search(message, query);
 | 
			
		||||
            if (result.length < 1) {
 | 
			
		||||
                throw new Error('No search result with the given query: ' + query);
 | 
			
		||||
            } else {
 | 
			
		||||
                for (let i = 0; i < result.length; i++) {
 | 
			
		||||
                    this._queue.enqueue(result[i]);
 | 
			
		||||
                }
 | 
			
		||||
                if (!this.isPlaying) {
 | 
			
		||||
                    this._skip(checkChangeVC);
 | 
			
		||||
                } else if (!result[0].playlist) {
 | 
			
		||||
                    this.emit(Event.ADD_SONG, result[0]);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            throw new Error(`Invalid channel. Make sure the channel is a DMChannel | PartialGroupDMChannel | VoiceChannel | StageChannel.`);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    async search(message, query, limit = 1) {
 | 
			
		||||
        if (!(message instanceof Discord.Message)) throw new Error(`Invalid message type (Required: Message, got: ${typeof message})`);
 | 
			
		||||
        if (typeof query !== 'string') throw new Error(`Invalid query type (Required: string, got: ${typeof query})`);
 | 
			
		||||
        if (typeof limit !== 'number') throw new Error(`Invalid limit type (Required: number, got: ${typeof limit})`);
 | 
			
		||||
        if (limit < 1) {
 | 
			
		||||
            limit = 1;
 | 
			
		||||
            process.emitWarning(`Invalid limit value (Required: 1 or more, got: ${limit})`);
 | 
			
		||||
        };
 | 
			
		||||
        if (limit > 10) {
 | 
			
		||||
            limit = 10;
 | 
			
		||||
            process.emitWarning(`Invalid limit value (Required: 10 or less, got: ${limit})`);
 | 
			
		||||
        };
 | 
			
		||||
        if (/^(https?\:\/\/)?(www\.youtube\.com|youtu\.be)\/.+$/.test(query)) {
 | 
			
		||||
            const validateData = this._playDl.yt_validate(query);
 | 
			
		||||
            if (validateData == 'video') {
 | 
			
		||||
                const result = await this._playDl.video_info(query);
 | 
			
		||||
                return [result.video_details];
 | 
			
		||||
            } else if (validateData == 'playlist') {
 | 
			
		||||
                const result = await this._playDl.playlist_info(query);
 | 
			
		||||
                this.emit(Event.ADD_PLAYLIST, result);
 | 
			
		||||
                const allVideo = await result.all_videos();
 | 
			
		||||
                return allVideo.map(video => {
 | 
			
		||||
                    Object.defineProperty(video, 'playlist', { value: result });
 | 
			
		||||
                    return video;
 | 
			
		||||
                });
 | 
			
		||||
            } else {
 | 
			
		||||
                return this.emit(Event.ERROR, new Error('Invalid YouTube URL: ' + query));
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            const result = await this._playDl.search(query, {
 | 
			
		||||
                limit,
 | 
			
		||||
                unblurNSFWThumbnails: this.options.nsfw,
 | 
			
		||||
            });
 | 
			
		||||
            if (result.length < 1) {
 | 
			
		||||
                this.emit(Event.NO_SEARCH_RESULT, message, query, limit);
 | 
			
		||||
                return [];
 | 
			
		||||
            } else {
 | 
			
		||||
                this.emit(Event.SEARCH_RESULT, message, result, query, limit);
 | 
			
		||||
                return result;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    setLoopMode(mode) {
 | 
			
		||||
        if ([0, 1, 2].includes(mode)) {
 | 
			
		||||
            this._loopMode = mode;
 | 
			
		||||
        } else {
 | 
			
		||||
            throw new Error(`Invalid mode value (Required: 0 [No loop], 1 [Loop song], 2 [Loop queue], got: ${mode})`);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    async createStream(url) {
 | 
			
		||||
        const stream = await this._playDl.stream(url);
 | 
			
		||||
        const resource = DjsVoice.createAudioResource(stream.stream, {
 | 
			
		||||
            inputType: stream.type,
 | 
			
		||||
            inlineVolume: true,
 | 
			
		||||
        });
 | 
			
		||||
        this._currentResourceAudio = resource;
 | 
			
		||||
    }
 | 
			
		||||
    _play() {
 | 
			
		||||
        this._player.play(this._currentResourceAudio);
 | 
			
		||||
    }
 | 
			
		||||
    setVolume(volume) {
 | 
			
		||||
        if (!this._currentResourceAudio) throw new Error('No current resource audio');
 | 
			
		||||
        if (typeof volume !== 'number') throw new Error(`Invalid volume type (Required: number, got: ${typeof volume})`);
 | 
			
		||||
        if (volume < 0) {
 | 
			
		||||
            volume = 0;
 | 
			
		||||
            process.emitWarning(`Invalid volume value (Required: 0 or more, got: ${volume})`);
 | 
			
		||||
        } else if (volume > 100) {
 | 
			
		||||
            volume = 100;
 | 
			
		||||
            process.emitWarning(`Invalid volume value (Required: 100 or less, got: ${volume})`);
 | 
			
		||||
        }
 | 
			
		||||
        this._currentResourceAudio.volume.setVolume((volume / 100).toFixed(2));
 | 
			
		||||
        this.volume = (volume / 100).toFixed(2) * 100;
 | 
			
		||||
        return (volume / 100).toFixed(2) * 100;
 | 
			
		||||
    }
 | 
			
		||||
    pause() {
 | 
			
		||||
        if (!this._currentResourceAudio) throw new Error('No current resource audio');
 | 
			
		||||
        this._player.pause();
 | 
			
		||||
    }
 | 
			
		||||
    resume() {
 | 
			
		||||
        if (!this._currentResourceAudio) throw new Error('No current resource audio');
 | 
			
		||||
        this._player.unpause();
 | 
			
		||||
    }
 | 
			
		||||
    stop() {
 | 
			
		||||
        if (!this._currentResourceAudio) throw new Error('No current resource audio');
 | 
			
		||||
        this._stop(false, this.options.leaveOnStop);
 | 
			
		||||
    }
 | 
			
		||||
    _stop(finish = false, force = false) {
 | 
			
		||||
        if (!this._currentResourceAudio) return;
 | 
			
		||||
        this._queue.reset();
 | 
			
		||||
        this._previousSongs.reset();
 | 
			
		||||
        this._timeoutEmpty = undefined;
 | 
			
		||||
        this._player.stop();
 | 
			
		||||
        this._currentTime = 0;
 | 
			
		||||
        this._currentResourceAudio = null;
 | 
			
		||||
        this._playingTime = 0;
 | 
			
		||||
        this.isPlaying = false;
 | 
			
		||||
        this.channel = null;
 | 
			
		||||
        this.guild = null;
 | 
			
		||||
        this.song = null;
 | 
			
		||||
        this.volume = 100;
 | 
			
		||||
        if (force || !finish && this.options.leaveOnStop || finish && this.options.leaveOnFinish) this.currentConnection?.destroy();
 | 
			
		||||
    }
 | 
			
		||||
    skip() {
 | 
			
		||||
        this._skip();
 | 
			
		||||
    }
 | 
			
		||||
    async _skip(checkChangeVC = false) {
 | 
			
		||||
        if (!this._queue.length) throw new Error('No song in the queue');
 | 
			
		||||
        const currentSong = this.song;
 | 
			
		||||
        if (this.loopMode == 0) {
 | 
			
		||||
            if (this.options.savePreviousSongs) this._previousSongs.push(currentSong);
 | 
			
		||||
            const nextSong = this._queue.dequeue();
 | 
			
		||||
            this.song = nextSong;
 | 
			
		||||
        } else if (this.loopMode == 1) {
 | 
			
		||||
            this.song = currentSong;
 | 
			
		||||
        } else if (this.loopMode == 2) {
 | 
			
		||||
            this._queue.enqueue(currentSong);
 | 
			
		||||
            const nextSong = this._queue.dequeue();
 | 
			
		||||
            this.song = nextSong;
 | 
			
		||||
        }
 | 
			
		||||
        await this.createStream(this.song.url);
 | 
			
		||||
        await this.joinVC(checkChangeVC);
 | 
			
		||||
        this.emit(Event.PLAY_SONG, this.song);
 | 
			
		||||
        if (!this.guild?.id) await _awaitDM();
 | 
			
		||||
        this._play();
 | 
			
		||||
        this._playingTime = Date.now();
 | 
			
		||||
    }
 | 
			
		||||
    async previous() {
 | 
			
		||||
        if (!this._previousSongs.length) throw new Error('No previous songs');
 | 
			
		||||
        const previousSong = this._previousSongs.pop();
 | 
			
		||||
        this.song = previousSong;
 | 
			
		||||
        await createStream(this.song.url);
 | 
			
		||||
        await this.joinVC();
 | 
			
		||||
        this._play();
 | 
			
		||||
        this.emit(Event.PLAY_SONG, this.song, this.queue);
 | 
			
		||||
    }
 | 
			
		||||
    async joinVC(changeVC = false) {
 | 
			
		||||
        if (this.currentConnection && !changeVC) {
 | 
			
		||||
            this.currentConnection.subscribe(this._player);
 | 
			
		||||
        } else if (this.channel instanceof Discord.VoiceChannel) {
 | 
			
		||||
            const connection = DjsVoice.joinVoiceChannel({
 | 
			
		||||
                channelId: this.channel.id,
 | 
			
		||||
                guildId: this.guild.id,
 | 
			
		||||
                adapterCreator: this.guild.voiceAdapterCreator,
 | 
			
		||||
            });
 | 
			
		||||
            await DjsVoice.entersState(
 | 
			
		||||
                connection,
 | 
			
		||||
                DjsVoice.VoiceConnectionStatus.Ready,
 | 
			
		||||
                10_000,
 | 
			
		||||
            );
 | 
			
		||||
            connection.subscribe(this._player);
 | 
			
		||||
        } else if (this.channel instanceof Discord.StageChannel) {
 | 
			
		||||
            const connection = DjsVoice.joinVoiceChannel({
 | 
			
		||||
                channelId: this.channel.id,
 | 
			
		||||
                guildId: this.guild.id,
 | 
			
		||||
                adapterCreator: this.guild.voiceAdapterCreator,
 | 
			
		||||
            });
 | 
			
		||||
            await DjsVoice.entersState(
 | 
			
		||||
                connection,
 | 
			
		||||
                DjsVoice.VoiceConnectionStatus.Ready,
 | 
			
		||||
                10_000,
 | 
			
		||||
            );
 | 
			
		||||
            connection.subscribe(this._player);
 | 
			
		||||
            await this.channel.guild.me.voice
 | 
			
		||||
                .setSuppressed(false)
 | 
			
		||||
                .catch(async () => {
 | 
			
		||||
                    return await this.channel.guild.me.voice
 | 
			
		||||
                        .setRequestToSpeak(true);
 | 
			
		||||
                });
 | 
			
		||||
        } else {
 | 
			
		||||
            const connection = this.channel.voiceConnection || await this.channel.call();
 | 
			
		||||
            connection.subscribe(this._player);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    _discordEvent() {
 | 
			
		||||
        // Event sus .-.
 | 
			
		||||
        this.client.on('voiceStateUpdate', (oldState, newState) => {
 | 
			
		||||
            if (!this._currentResourceAudio) return;
 | 
			
		||||
            if (newState.guild?.id == this.guild?.id) {
 | 
			
		||||
                if (oldState.channel?.id !== newState.channel?.id && oldState.channel?.id && newState.channel?.id && newState.id == this.client.user.id) {
 | 
			
		||||
                    // change vc
 | 
			
		||||
                }
 | 
			
		||||
                if (newState.id == this.client.user.id && oldState.channel?.members?.has(this.client.user.id) && !newState.channel?.members?.has(this.client.user.id)) {
 | 
			
		||||
                    this._stop();
 | 
			
		||||
                    this.emit(Event.LEAVE_VC);  
 | 
			
		||||
                }
 | 
			
		||||
                if (newState.channel?.members?.has(this.client.user.id) && !newState.channel?.members?.filter(m => m.id != this.client.user.id && !m.bot).size) {
 | 
			
		||||
                    // empty
 | 
			
		||||
                    if (this.options.leaveOnEmpty && !this._timeoutEmpty) {
 | 
			
		||||
                        this._timeoutEmpty = setTimeout(() => {
 | 
			
		||||
                            this._stop(false, true);
 | 
			
		||||
                            this.emit(Event.EMPTY);
 | 
			
		||||
                        }, this.options.emptyCooldown);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                if (newState.channel?.members?.has(this.client.user.id) && newState.channel?.members?.filter(m => m.id != this.client.user.id && !m.bot).size > 0) {
 | 
			
		||||
                    // not empty
 | 
			
		||||
                    if (this._timeoutEmpty) clearTimeout(this._timeoutEmpty);
 | 
			
		||||
                    this._timeoutEmpty = undefined;
 | 
			
		||||
                }
 | 
			
		||||
            } else if (!this.guild?.id && !newState.guild?.id) {
 | 
			
		||||
                // DM channels
 | 
			
		||||
                if (!newState.channel?.voiceUsers?.filter(m => m.id != this.client.user.id).size) {
 | 
			
		||||
                    // empty
 | 
			
		||||
                    if (this.options.leaveOnEmpty && !this._timeoutEmpty) {
 | 
			
		||||
                        this._timeoutEmpty = setTimeout(() => {
 | 
			
		||||
                            this._stop(false, true);
 | 
			
		||||
                            this.emit(Event.EMPTY);
 | 
			
		||||
                        }, this.options.emptyCooldown);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                if (newState.channel?.voiceUsers?.filter(m => m.id != this.client.user.id).size > 0) {
 | 
			
		||||
                    // not empty
 | 
			
		||||
                    if (this._timeoutEmpty) clearTimeout(this._timeoutEmpty);
 | 
			
		||||
                    this._timeoutEmpty = undefined;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    _awaitDM () {
 | 
			
		||||
        if (!this.options.waitingUserToPlayInDMs) return true;
 | 
			
		||||
        return new Promise(resolve => {
 | 
			
		||||
            let i = setInterval(() => {
 | 
			
		||||
                const m = this.channel.voiceUsers.get(this.client.user.id);
 | 
			
		||||
                if (m) {
 | 
			
		||||
                    clearInterval(i);
 | 
			
		||||
                    resolve(true);
 | 
			
		||||
                }
 | 
			
		||||
            }, 250);
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
    _privateEvent() {
 | 
			
		||||
        this.on('next_song', async () => {
 | 
			
		||||
            await this._skip().catch(() => {
 | 
			
		||||
                this.emit(Event.FINISH);
 | 
			
		||||
                this._stop(true);
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    _playerEvent() {
 | 
			
		||||
        const player = this._player;
 | 
			
		||||
        player.on('stateChange', async (oldState, newState) => {
 | 
			
		||||
            // idle -> playing
 | 
			
		||||
            // idle -> buffering
 | 
			
		||||
            // buffering -> playing
 | 
			
		||||
            // playing -> idle 
 | 
			
		||||
            if (newState.status.toLowerCase() == 'idle') {
 | 
			
		||||
                this.isPlaying = false;
 | 
			
		||||
            } else if (newState.status.toLowerCase() == 'paused' || newState.status.toLowerCase() == 'autopaused') {
 | 
			
		||||
                this.isPlaying = false;
 | 
			
		||||
            } else {
 | 
			
		||||
                this.isPlaying = true;
 | 
			
		||||
            }
 | 
			
		||||
            this._currentTime = newState.playbackDuration;
 | 
			
		||||
            //
 | 
			
		||||
            if (oldState.status == 'playing' && newState.status == 'idle') {
 | 
			
		||||
                this.emit('next_song');
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        player.on('error', (e) => {
 | 
			
		||||
            this.emit(Event.ERROR, e);
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
module.exports = Player;
 | 
			
		||||
 | 
			
		||||
/* Example
 | 
			
		||||
const player = new Player(client, options);
 | 
			
		||||
 | 
			
		||||
player
 | 
			
		||||
.on('playSong', song => {
 | 
			
		||||
	console.log(`Now playing: ${song.title}`);
 | 
			
		||||
})
 | 
			
		||||
.on('addSong', song => {
 | 
			
		||||
	console.log(`Added: ${song.title}`);
 | 
			
		||||
})
 | 
			
		||||
.on('addPlaylist', playlist => {
 | 
			
		||||
	console.log(`Added Playlist: ${playlist.title}`);
 | 
			
		||||
})
 | 
			
		||||
.on('disconnect', () => {
 | 
			
		||||
	console.log('Disconnected from voice channel.');
 | 
			
		||||
})
 | 
			
		||||
.on('finish', () => {
 | 
			
		||||
	console.log('finish.');
 | 
			
		||||
})
 | 
			
		||||
.on('empty', () => {
 | 
			
		||||
	console.log('empty voice channel.');
 | 
			
		||||
})
 | 
			
		||||
.on('error', error => {
 | 
			
		||||
	console.log('Music error', error);
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
client.player = player;
 | 
			
		||||
 | 
			
		||||
// Method
 | 
			
		||||
 | 
			
		||||
client.player.play({
 | 
			
		||||
      message,
 | 
			
		||||
      channel: message.member.voice.channel, // VoiceChannel | DMChannel | StageChannel | GroupDMChannel
 | 
			
		||||
      query: string,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
client.player.skip();
 | 
			
		||||
 | 
			
		||||
client.player.previous();
 | 
			
		||||
 | 
			
		||||
client.player.pause();
 | 
			
		||||
 | 
			
		||||
client.player.resume();
 | 
			
		||||
 | 
			
		||||
client.player.setVolume(50); // 50%
 | 
			
		||||
 | 
			
		||||
client.player.setLoopMode(1); // 0: none, 1: song, 2: queue;
 | 
			
		||||
 | 
			
		||||
client.player.stop();
 | 
			
		||||
 | 
			
		||||
// Options
 | 
			
		||||
 | 
			
		||||
options = {
 | 
			
		||||
            directLink: true, // Whether or not play direct link of the song (not support)
 | 
			
		||||
            joinNewVoiceChannel: true, // Whether or not joining the new voice channel when using #play method
 | 
			
		||||
            waitingUserToPlayInDMs: true, // Waiting User join Call to play in DM channels
 | 
			
		||||
            nsfw: false, // Whether or not play NSFW
 | 
			
		||||
            leaveOnEmpty: true, // Whether or not leave voice channel when empty (not working)
 | 
			
		||||
            leaveOnFinish: true, // Whether or not leave voice channel when finish
 | 
			
		||||
            leaveOnStop: true, //  Whether or not leave voice channel when stop
 | 
			
		||||
            savePreviousSongs: true, // Whether or not save previous songs
 | 
			
		||||
            emptyCooldown: 10_000, // Cooldown when empty voice channel
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Properties
 | 
			
		||||
 | 
			
		||||
song = Song;
 | 
			
		||||
guild = Discord.Guild;
 | 
			
		||||
channel = Channel;
 | 
			
		||||
client = Discord.Client;
 | 
			
		||||
isPlaying = false;
 | 
			
		||||
volume = 100;
 | 
			
		||||
currentTime = Unix timestamp miliseconds;
 | 
			
		||||
currentConnection = VoiceConnection;
 | 
			
		||||
queue: Song[];
 | 
			
		||||
previousSongs: Song[];
 | 
			
		||||
 | 
			
		||||
*/
 | 
			
		||||
```
 | 
			
		||||
		Reference in New Issue
	
	Block a user