2021-04-06 17:58:46 +05:00
import { EventEmitter } from 'events' ;
2021-04-09 15:04:36 +05:00
import { Client , Collection , Snowflake , Collector , Message , VoiceChannel , VoiceState } from 'discord.js' ;
2021-05-14 17:03:17 +05:00
import { LyricsData , PlayerOptions as PlayerOptionsType , PlayerProgressbarOptions , PlayerStats , QueueFilters } from './types/types' ;
2021-04-06 17:58:46 +05:00
import Util from './utils/Util' ;
import AudioFilters from './utils/AudioFilters' ;
2021-04-06 20:38:17 +05:00
import { Queue } from './Structures/Queue' ;
import { Track } from './Structures/Track' ;
2021-04-17 19:11:18 +05:00
import { PlayerErrorEventCodes , PlayerEvents , PlayerOptions } from './utils/Constants' ;
2021-04-06 19:08:01 +05:00
import PlayerError from './utils/PlayerError' ;
import ytdl from 'discord-ytdl-core' ;
2021-04-08 19:42:08 +05:00
import { ExtractorModel } from './Structures/ExtractorModel' ;
2021-04-17 09:01:06 +05:00
import os from 'os' ;
2021-04-06 17:55:29 +05:00
// @ts-ignore
2021-04-06 17:58:46 +05:00
import spotify from 'spotify-url-info' ;
2021-04-06 17:55:29 +05:00
// @ts-ignore
2021-04-06 17:58:46 +05:00
import { Client as SoundCloudClient } from 'soundcloud-scraper' ;
2021-04-07 19:05:35 +05:00
import YouTube from 'youtube-sr' ;
2021-04-06 17:55:29 +05:00
2021-04-06 17:58:46 +05:00
const SoundCloud = new SoundCloudClient ( ) ;
2021-04-04 22:36:40 +05:00
2021-04-21 14:14:04 +05:00
/ * *
* The Player class
* @extends { EventEmitter }
* /
2021-04-06 20:38:17 +05:00
export class Player extends EventEmitter {
2021-04-21 12:09:16 +05:00
public client : Client ;
2021-04-17 19:11:18 +05:00
public options : PlayerOptionsType ;
2021-04-06 17:55:29 +05:00
public filters : typeof AudioFilters ;
2021-04-09 17:59:14 +05:00
/ * *
* The collection of queues in this player
2021-04-21 14:10:44 +05:00
* @type { DiscordCollection < Queue > }
2021-04-09 17:59:14 +05:00
* /
2021-04-09 15:04:36 +05:00
public queues = new Collection < Snowflake , Queue > ( ) ;
2021-05-09 17:41:54 +05:00
/ * *
* Collection of results collectors
* @type { DiscordCollection < DiscordCollector < DiscordSnowflake , DiscordMessage > > }
* @private
* /
2021-04-09 15:04:36 +05:00
private _resultsCollectors = new Collection < string , Collector < Snowflake , Message > > ( ) ;
2021-05-09 17:41:54 +05:00
/ * *
* Collection of cooldowns timeout
* @type { DiscordCollection < Timeout > }
* @private
* /
2021-04-09 15:04:36 +05:00
private _cooldownsTimeout = new Collection < string , NodeJS.Timeout > ( ) ;
2021-04-09 17:59:14 +05:00
/ * *
* The extractor model collection
2021-04-21 14:10:44 +05:00
* @type { DiscordCollection < ExtractorModel > }
2021-04-09 17:59:14 +05:00
* /
2021-04-08 18:03:04 +05:00
public Extractors = new Collection < string , ExtractorModel > ( ) ;
2021-04-04 22:36:40 +05:00
2021-04-09 17:59:14 +05:00
/ * *
* Creates new Player instance
2021-04-21 12:09:16 +05:00
* @param { DiscordClient } client The discord . js client
2021-04-21 12:15:09 +05:00
* @param { PlayerOptions } options Player options
2021-04-09 17:59:14 +05:00
* /
2021-04-17 19:11:18 +05:00
constructor ( client : Client , options? : PlayerOptionsType ) {
2021-04-04 22:36:40 +05:00
super ( ) ;
2021-04-21 14:10:44 +05:00
/ * *
* The discord client that instantiated this player
* @name Player # client
* @type { DiscordClient }
* @readonly
* /
2021-04-06 17:58:46 +05:00
Object . defineProperty ( this , 'client' , {
2021-04-04 22:36:40 +05:00
value : client ,
enumerable : false
} ) ;
2021-04-21 14:10:44 +05:00
/ * *
* The player options
* @type { PlayerOptions }
* /
2021-04-17 19:11:18 +05:00
this . options = Object . assign ( { } , PlayerOptions , options ? ? { } ) ;
2021-04-04 22:36:40 +05:00
// check FFmpeg
void Util . alertFFmpeg ( ) ;
2021-04-06 17:55:29 +05:00
2021-04-21 14:10:44 +05:00
/ * *
* Audio filters
* @type { Object }
* /
2021-04-06 17:55:29 +05:00
this . filters = AudioFilters ;
2021-05-08 14:52:58 +05:00
this . client . on ( 'voiceStateUpdate' , this . _handleVoiceStateUpdate . bind ( this ) ) ;
2021-04-08 19:42:08 +05:00
2021-04-19 13:33:34 +05:00
// auto detect @discord-player/extractor
2021-04-21 17:17:43 +05:00
if ( ! this . options . disableAutoRegister ) {
let nv : any ;
2021-05-08 14:52:58 +05:00
// tslint:disable:no-conditional-assignment
2021-04-21 17:17:43 +05:00
if ( ( nv = Util . require ( '@discord-player/extractor' ) ) ) {
[ 'Attachment' , 'Facebook' , 'Reverbnation' , 'Vimeo' ] . forEach ( ( ext ) = > void this . use ( ext , nv [ ext ] ) ) ;
}
2021-04-19 13:33:34 +05:00
}
2021-04-06 17:55:29 +05:00
}
2021-04-19 18:32:10 +05:00
static get AudioFilters ( ) : typeof AudioFilters {
2021-04-06 17:55:29 +05:00
return AudioFilters ;
}
2021-04-08 18:03:04 +05:00
/ * *
* Define custom extractor in this player
2021-04-21 11:52:59 +05:00
* @param { String } extractorName The extractor name
2021-04-21 10:08:33 +05:00
* @param { any } extractor The extractor itself
2021-04-21 14:10:44 +05:00
* @returns { Player }
2021-04-08 18:03:04 +05:00
* /
2021-04-19 18:32:10 +05:00
use ( extractorName : string , extractor : any ) : Player {
2021-04-08 19:42:08 +05:00
if ( ! extractorName ) throw new PlayerError ( 'Missing extractor name!' , 'PlayerExtractorError' ) ;
2021-04-08 18:03:04 +05:00
2021-04-08 19:42:08 +05:00
const methods = [ 'validate' , 'getInfo' ] ;
2021-04-08 18:03:04 +05:00
for ( const method of methods ) {
2021-05-14 17:03:17 +05:00
if ( typeof extractor [ method ] !== 'function' ) throw new PlayerError ( 'Invalid extractor supplied!' , 'PlayerExtractorError' ) ;
2021-04-08 18:03:04 +05:00
}
this . Extractors . set ( extractorName , new ExtractorModel ( extractorName , extractor ) ) ;
2021-04-08 19:43:33 +05:00
return this ;
2021-04-08 18:03:04 +05:00
}
/ * *
* Remove existing extractor from this player
2021-04-21 11:52:59 +05:00
* @param { String } extractorName The extractor name
2021-04-21 14:10:44 +05:00
* @returns { Boolean }
2021-04-08 18:03:04 +05:00
* /
2021-04-19 18:32:10 +05:00
unuse ( extractorName : string ) : boolean {
2021-04-08 19:42:08 +05:00
if ( ! extractorName ) throw new PlayerError ( 'Missing extractor name!' , 'PlayerExtractorError' ) ;
2021-04-08 18:03:04 +05:00
return this . Extractors . delete ( extractorName ) ;
}
2021-05-09 17:34:25 +05:00
/ * *
* Internal method to search tracks
* @param { DiscordMessage } message The message
* @param { string } query The query
* @param { boolean } [ firstResult = false ] If it should return the first result
2021-06-30 21:21:19 +05:00
* @param { number } [ startFromIndex = 0 ] Prioritise playing the track with following index ( Only works with playlist )
2021-05-09 17:34:25 +05:00
* @returns { Promise < Track > }
* @private
* /
2021-06-30 21:13:07 +05:00
private _searchTracks ( message : Message , query : string , firstResult? : boolean , startFromIndex? : number = 0 ) : Promise < Track > {
2021-04-06 17:55:29 +05:00
return new Promise ( async ( resolve ) = > {
let tracks : Track [ ] = [ ] ;
2021-04-06 17:58:46 +05:00
const queryType = Util . getQueryType ( query ) ;
switch ( queryType ) {
case 'soundcloud_track' :
{
const data = await SoundCloud . getSongInfo ( query ) . catch ( ( ) = > { } ) ;
if ( data ) {
const track = new Track ( this , {
title : data.title ,
url : data.url ,
2021-05-09 18:12:50 +05:00
duration : Util.buildTimeCode ( Util . parseMS ( data . duration ) ) ,
2021-04-06 17:58:46 +05:00
description : data.description ,
thumbnail : data.thumbnail ,
views : data.playCount ,
2021-04-09 19:04:39 +05:00
author : data.author.name ,
2021-04-06 17:58:46 +05:00
requestedBy : message.author ,
fromPlaylist : false ,
source : 'soundcloud' ,
2021-04-09 19:04:39 +05:00
engine : data
2021-04-06 17:58:46 +05:00
} ) ;
tracks . push ( track ) ;
}
2021-04-06 17:55:29 +05:00
}
2021-04-06 17:58:46 +05:00
break ;
case 'spotify_song' :
{
2021-05-14 17:03:17 +05:00
const matchSpotifyURL = query . match ( /https?:\/\/(?:embed\.|open\.)(?:spotify\.com\/)(?:track\/|\?uri=spotify:track:)((\w|-){22})/ ) ;
2021-04-06 17:58:46 +05:00
if ( matchSpotifyURL ) {
2021-05-13 09:08:31 +05:00
const spotifyData = await spotify . getData ( query ) . catch ( ( ) = > { } ) ;
2021-04-06 17:58:46 +05:00
if ( spotifyData ) {
2021-05-13 09:55:32 +05:00
const spotifyTrack = new Track ( this , {
title : spotifyData.name ,
description : spotifyData.description ? ? '' ,
author : spotifyData.artists [ 0 ] ? . name ? ? 'Unknown Artist' ,
url : spotifyData.external_urls?.spotify ? ? query ,
2021-05-14 17:03:17 +05:00
thumbnail : spotifyData.album?.images [ 0 ] ? . url ? ? spotifyData . preview_url ? . length ? ` https://i.scdn.co/image/ ${ spotifyData . preview_url ? . split ( '?cid=' ) [ 1 ] } ` : 'https://www.scdn.co/i/_global/twitter_card-default.jpg' ,
2021-05-13 09:55:32 +05:00
duration : Util.buildTimeCode ( Util . parseMS ( spotifyData . duration_ms ) ) ,
views : 0 ,
requestedBy : message.author ,
fromPlaylist : false ,
source : 'spotify'
2021-04-06 17:58:46 +05:00
} ) ;
2021-05-13 09:55:32 +05:00
2021-05-13 13:10:11 +05:00
if ( this . options . fetchBeforeQueued ) {
2021-05-14 17:03:17 +05:00
const searchQueryString = this . options . disableArtistSearch ? spotifyTrack . title : ` ${ spotifyTrack . title } ${ ' - ' + spotifyTrack . author } ` ;
2021-05-13 13:10:41 +05:00
const ytv = await YouTube . search ( searchQueryString , {
limit : 1 ,
type : 'video'
} ) . catch ( ( e ) = > { } ) ;
if ( ytv && ytv [ 0 ] )
Util . define ( {
target : spotifyTrack ,
prop : 'backupLink' ,
value : ytv [ 0 ] . url
} ) ;
2021-05-13 13:10:11 +05:00
}
2021-05-13 09:55:32 +05:00
tracks = [ spotifyTrack ] ;
2021-04-06 17:58:46 +05:00
}
2021-04-06 17:55:29 +05:00
}
}
2021-04-06 17:58:46 +05:00
break ;
2021-04-07 19:05:35 +05:00
2021-05-09 17:24:20 +05:00
case 'spotify_album' :
case 'spotify_playlist' : {
this . emit ( PlayerEvents . PLAYLIST_PARSE_START , null , message ) ;
const playlist = await spotify . getData ( query ) ;
if ( ! playlist ) return void this . emit ( PlayerEvents . NO_RESULTS , message , query ) ;
2021-05-09 18:16:11 +05:00
// tslint:disable-next-line:no-shadowed-variable
2021-05-13 09:55:32 +05:00
let tracks : Track [ ] = [ ] ;
if ( playlist . type !== 'playlist' )
2021-05-13 13:10:41 +05:00
tracks = await Promise . all (
playlist . tracks . items . map ( async ( m : any ) = > {
const data = new Track ( this , {
title : m.name ? ? '' ,
description : m.description ? ? '' ,
author : m.artists [ 0 ] ? . name ? ? 'Unknown Artist' ,
url : m.external_urls?.spotify ? ? query ,
2021-05-14 17:03:17 +05:00
thumbnail : playlist.images [ 0 ] ? . url ? ? 'https://www.scdn.co/i/_global/twitter_card-default.jpg' ,
2021-05-13 13:10:41 +05:00
duration : Util.buildTimeCode ( Util . parseMS ( m . duration_ms ) ) ,
views : 0 ,
requestedBy : message.author ,
fromPlaylist : true ,
source : 'spotify'
2021-05-13 13:10:11 +05:00
} ) ;
2021-05-13 13:10:41 +05:00
if ( this . options . fetchBeforeQueued ) {
2021-05-14 17:03:17 +05:00
const searchQueryString = this . options . disableArtistSearch ? data . title : ` ${ data . title } ${ ' - ' + data . author } ` ;
2021-05-13 13:10:41 +05:00
const ytv = await YouTube . search ( searchQueryString , {
limit : 1 ,
type : 'video'
} ) . catch ( ( e ) = > { } ) ;
if ( ytv && ytv [ 0 ] )
Util . define ( {
target : data ,
prop : 'backupLink' ,
value : ytv [ 0 ] . url
} ) ;
}
2021-05-13 13:10:11 +05:00
2021-05-13 13:10:41 +05:00
return data ;
} )
) ;
else {
tracks = await Promise . all (
playlist . tracks . items . map ( async ( m : any ) = > {
const data = new Track ( this , {
title : m.track.name ? ? '' ,
description : m.track.description ? ? '' ,
author : m.track.artists [ 0 ] ? . name ? ? 'Unknown Artist' ,
url : m.track.external_urls?.spotify ? ? query ,
2021-05-14 17:03:17 +05:00
thumbnail : m.track.album?.images [ 0 ] ? . url ? ? 'https://www.scdn.co/i/_global/twitter_card-default.jpg' ,
2021-05-13 13:10:41 +05:00
duration : Util.buildTimeCode ( Util . parseMS ( m . track . duration_ms ) ) ,
views : 0 ,
requestedBy : message.author ,
fromPlaylist : true ,
source : 'spotify'
2021-05-13 13:10:11 +05:00
} ) ;
2021-05-13 13:10:41 +05:00
if ( this . options . fetchBeforeQueued ) {
2021-05-14 17:03:17 +05:00
const searchQueryString = this . options . disableArtistSearch ? data . title : ` ${ data . title } ${ ' - ' + data . author } ` ;
2021-05-13 13:10:41 +05:00
const ytv = await YouTube . search ( searchQueryString , {
limit : 1 ,
type : 'video'
} ) . catch ( ( e ) = > { } ) ;
if ( ytv && ytv [ 0 ] )
Util . define ( {
target : data ,
prop : 'backupLink' ,
value : ytv [ 0 ] . url
} ) ;
}
return data ;
} )
) ;
2021-05-13 09:55:32 +05:00
}
2021-05-10 14:12:10 +05:00
2021-04-07 19:05:35 +05:00
if ( ! tracks . length ) return void this . emit ( PlayerEvents . NO_RESULTS , message , query ) ;
const pl = {
. . . playlist ,
tracks ,
2021-05-10 10:31:10 +05:00
duration : tracks?.reduce ( ( a , c ) = > a + ( c ? . durationMS ? ? 0 ) , 0 ) ? ? 0 ,
2021-05-10 14:59:05 +05:00
thumbnail : playlist.images [ 0 ] ? . url ? ? tracks [ 0 ] . thumbnail ,
2021-05-10 15:02:05 +05:00
title : playlist.title ? ? playlist . name ? ? ''
2021-04-07 19:05:35 +05:00
} ;
this . emit ( PlayerEvents . PLAYLIST_PARSE_END , pl , message ) ;
2021-06-30 21:13:07 +05:00
if ( startFromIndex < 0 ) startFromIndex = 0 ;
if ( startFromIndex > tracks . length - 1 ) startFromIndex = tracks . length - 1 ;
2021-04-07 19:05:35 +05:00
if ( this . isPlaying ( message ) ) {
2021-06-30 21:13:07 +05:00
const prioritisedTrack = tracks . splice ( startFromIndex , 1 ) ;
tracks . unshift ( prioritisedTrack ) ;
2021-04-07 19:05:35 +05:00
const queue = this . _addTracksToQueue ( message , tracks ) ;
2021-06-30 21:18:11 +05:00
this . emit ( PlayerEvents . PLAYLIST_ADD , message , queue , pl ) ;
2021-04-07 19:05:35 +05:00
} else {
2021-06-30 21:13:07 +05:00
const track = tracks [ startFromIndex ] ;
const queue = ( await this . _createQueue ( message , track ) . catch ( ( e ) = & gt ; void this . emit ( PlayerEvents . ERROR , e , message ) ) ) as Queue ;
2021-05-10 14:59:05 +05:00
this . emit ( PlayerEvents . PLAYLIST_ADD , message , queue , pl ) ;
2021-06-30 21:13:07 +05:00
tracks . splice ( startFromIndex , 1 ) ;
this . _addTracksToQueue ( message , tracks ) ;
2021-05-10 15:00:13 +05:00
this . emit ( PlayerEvents . TRACK_START , message , queue . tracks [ 0 ] , queue ) ;
2021-04-07 19:05:35 +05:00
}
return ;
}
case 'youtube_playlist' : {
this . emit ( PlayerEvents . PLAYLIST_PARSE_START , null , message ) ;
const playlist = await YouTube . getPlaylist ( query ) ;
if ( ! playlist ) return void this . emit ( PlayerEvents . NO_RESULTS , message , query ) ;
// @ts-ignore
playlist . videos = playlist . videos . map (
( data ) = >
new Track ( this , {
title : data.title ,
url : data.url ,
2021-04-10 22:52:48 +05:00
duration : Util.buildTimeCode ( Util . parseMS ( data . duration ) ) ,
2021-04-07 19:05:35 +05:00
description : data.description ,
thumbnail : data.thumbnail?.displayThumbnailURL ( ) ,
views : data.views ,
author : data.channel.name ,
requestedBy : message.author ,
fromPlaylist : true ,
source : 'youtube'
} )
) ;
// @ts-ignore
playlist . duration = playlist . videos . reduce ( ( a , c ) = > a + c . durationMS , 0 ) ;
// @ts-ignore
playlist . thumbnail = playlist . thumbnail ? . url ? ? playlist . videos [ 0 ] . thumbnail ;
// @ts-ignore
playlist . requestedBy = message . author ;
2021-05-10 15:02:05 +05:00
Object . defineProperty ( playlist , 'tracks' , {
2021-05-10 14:53:24 +05:00
get : ( ) = > playlist . videos ? ? [ ]
} ) ;
2021-04-07 19:05:35 +05:00
this . emit ( PlayerEvents . PLAYLIST_PARSE_END , playlist , message ) ;
// @ts-ignore
2021-05-09 18:16:11 +05:00
// tslint:disable-next-line:no-shadowed-variable
2021-04-07 19:05:35 +05:00
const tracks = playlist . videos as Track [ ] ;
2021-06-30 21:13:07 +05:00
if ( startFromIndex < 0 ) startFromIndex = 0 ;
if ( startFromIndex > tracks . length - 1 ) startFromIndex = tracks . length - 1 ;
2021-04-07 19:05:35 +05:00
if ( this . isPlaying ( message ) ) {
2021-06-30 21:13:07 +05:00
const prioritisedTrack = tracks . splice ( startFromIndex , 1 ) ;
tracks . unshift ( prioritisedTrack ) ;
2021-04-07 19:05:35 +05:00
const queue = this . _addTracksToQueue ( message , tracks ) ;
this . emit ( PlayerEvents . PLAYLIST_ADD , message , queue , playlist ) ;
} else {
2021-06-30 21:13:07 +05:00
const track = tracks [ startFromIndex ] ;
const queue = ( await this . _createQueue ( message , track ) . catch ( ( e ) = & gt ; void this . emit ( PlayerEvents . ERROR , e , message ) ) ) as Queue ;
this . emit ( PlayerEvents . PLAYLIST_ADD , message , queue , pl ) ;
tracks . splice ( startFromIndex , 1 ) ;
this . _addTracksToQueue ( message , tracks ) ;
2021-04-07 19:05:35 +05:00
this . emit ( PlayerEvents . TRACK_START , message , queue . tracks [ 0 ] , queue ) ;
}
2021-04-09 19:04:39 +05:00
return ;
2021-04-07 19:05:35 +05:00
}
case 'soundcloud_playlist' : {
this . emit ( PlayerEvents . PLAYLIST_PARSE_START , null , message ) ;
const data = await SoundCloud . getPlaylist ( query ) . catch ( ( ) = > { } ) ;
if ( ! data ) return void this . emit ( PlayerEvents . NO_RESULTS , message , query ) ;
const res = {
id : data.id ,
title : data.title ,
tracks : [ ] as Track [ ] ,
author : data.author ,
duration : 0 ,
thumbnail : data.thumbnail ,
requestedBy : message.author
} ;
for ( const song of data . tracks ) {
const r = new Track ( this , {
title : song.title ,
url : song.url ,
2021-05-09 18:12:50 +05:00
duration : Util.buildTimeCode ( Util . parseMS ( song . duration ) ) ,
2021-04-07 19:05:35 +05:00
description : song.description ,
thumbnail : song.thumbnail ? ? 'https://soundcloud.com/pwa-icon-192.png' ,
views : song.playCount ? ? 0 ,
author : song.author ? ? data . author ,
requestedBy : message.author ,
fromPlaylist : true ,
source : 'soundcloud' ,
engine : song
} ) ;
res . tracks . push ( r ) ;
}
2021-05-14 17:03:17 +05:00
if ( ! res . tracks . length ) return this . emit ( PlayerEvents . ERROR , PlayerErrorEventCodes . PARSE_ERROR , message ) ;
2021-04-07 19:05:35 +05:00
res . duration = res . tracks . reduce ( ( a , c ) = > a + c . durationMS , 0 ) ;
this . emit ( PlayerEvents . PLAYLIST_PARSE_END , res , message ) ;
2021-06-30 21:13:07 +05:00
if ( startFromIndex < 0 ) res . startFromIndex = 0 ;
if ( startFromIndex > tracks . length - 1 ) startFromIndex = res . tracks . length - 1 ;
2021-04-07 19:05:35 +05:00
if ( this . isPlaying ( message ) ) {
2021-06-30 21:13:07 +05:00
const prioritisedTrack = res . tracks . splice ( startFromIndex , 1 ) ;
res . tracks . unshift ( prioritisedTrack ) ;
2021-04-07 19:05:35 +05:00
this . emit ( PlayerEvents . PLAYLIST_ADD , message , queue , res ) ;
} else {
2021-06-30 21:13:07 +05:00
const track = res . tracks [ startFromIndex ? startFromIndex - 1 : 0 ] ;
2021-05-14 17:03:17 +05:00
const queue = ( await this . _createQueue ( message , track ) . catch ( ( e ) = > void this . emit ( PlayerEvents . ERROR , e , message ) ) ) as Queue ;
2021-04-30 21:09:00 +05:00
this . emit ( PlayerEvents . PLAYLIST_ADD , message , queue , res ) ;
2021-06-30 21:13:07 +05:00
res . tracks . splice ( startFromIndex ? startFromIndex - 1 : 0 , 1 ) ;
2021-04-07 19:05:35 +05:00
this . _addTracksToQueue ( message , res . tracks ) ;
2021-06-30 21:13:07 +05:00
this . emit ( PlayerEvents . TRACK_START , message , queue . tracks [ 0 ] , queue ) ;
2021-04-07 19:05:35 +05:00
}
return ;
}
2021-04-06 17:55:29 +05:00
default :
tracks = await Util . ytSearch ( query , { user : message.author , player : this } ) ;
}
2021-04-07 19:05:35 +05:00
if ( tracks . length < 1 ) return void this . emit ( PlayerEvents . NO_RESULTS , message , query ) ;
if ( firstResult || tracks . length === 1 ) return resolve ( tracks [ 0 ] ) ;
2021-04-06 17:55:29 +05:00
const collectorString = ` ${ message . author . id } - ${ message . channel . id } ` ;
const currentCollector = this . _resultsCollectors . get ( collectorString ) ;
2021-04-06 17:58:46 +05:00
if ( currentCollector ) currentCollector . stop ( ) ;
2021-04-06 17:55:29 +05:00
const collector = message . channel . createMessageCollector ( ( m ) = > m . author . id === message . author . id , {
time : 60000
} ) ;
this . _resultsCollectors . set ( collectorString , collector ) ;
this . emit ( PlayerEvents . SEARCH_RESULTS , message , query , tracks , collector ) ;
2021-04-06 17:58:46 +05:00
collector . on ( 'collect' , ( { content } ) = > {
if ( content === 'cancel' ) {
2021-04-06 17:55:29 +05:00
collector . stop ( ) ;
return this . emit ( PlayerEvents . SEARCH_CANCEL , message , query , tracks ) ;
}
if ( ! isNaN ( content ) && parseInt ( content ) >= 1 && parseInt ( content ) <= tracks . length ) {
const index = parseInt ( content , 10 ) ;
const track = tracks [ index - 1 ] ;
collector . stop ( ) ;
resolve ( track ) ;
} else {
this . emit ( PlayerEvents . SEARCH_INVALID_RESPONSE , message , query , tracks , content , collector ) ;
}
2021-04-06 17:58:46 +05:00
} ) ;
2021-04-07 19:05:35 +05:00
collector . on ( 'end' , ( _ , reason ) = > {
2021-04-06 17:58:46 +05:00
if ( reason === 'time' ) {
2021-04-06 17:55:29 +05:00
this . emit ( PlayerEvents . SEARCH_CANCEL , message , query , tracks ) ;
}
} ) ;
} ) ;
2021-04-04 22:36:40 +05:00
}
2021-04-06 19:06:18 +05:00
2021-04-09 17:59:14 +05:00
/ * *
* Play a song
2021-04-21 12:09:16 +05:00
* @param { DiscordMessage } message The discord . js message object
2021-04-21 10:08:33 +05:00
* @param { string | Track } query Search query , can be ` Player.Track ` instance
2021-05-09 17:34:25 +05:00
* @param { Boolean } [ firstResult = false ] If it should play the first result
2021-06-30 21:21:19 +05:00
* @param { number } [ startFromIndex = 0 ] Prioritise playing the track with following index ( Only works with playlist )
2021-04-09 17:59:14 +05:00
* @example await player . play ( message , "never gonna give you up" , true )
2021-04-21 14:10:44 +05:00
* @returns { Promise < void > }
2021-04-09 17:59:14 +05:00
* /
2021-06-30 21:13:07 +05:00
async play ( message : Message , query : string | Track , firstResult? : boolean , startFromIndex? : number = 0 ) : Promise < void > {
2021-04-06 19:08:01 +05:00
if ( ! message ) throw new PlayerError ( 'Play function needs message' ) ;
if ( ! query ) throw new PlayerError ( 'Play function needs search query as a string or Player.Track object' ) ;
2021-04-06 19:06:18 +05:00
if ( this . _cooldownsTimeout . has ( ` end_ ${ message . guild . id } ` ) ) {
clearTimeout ( this . _cooldownsTimeout . get ( ` end_ ${ message . guild . id } ` ) ) ;
this . _cooldownsTimeout . delete ( ` end_ ${ message . guild . id } ` ) ;
}
2021-04-06 19:08:01 +05:00
if ( typeof query === 'string' ) query = query . replace ( /<(.+)>/g , '$1' ) ;
2021-04-06 19:06:18 +05:00
let track ;
2021-06-28 23:14:45 +05:00
if ( query instanceof Track ) track = query ;
2021-04-06 19:06:18 +05:00
else {
2021-06-28 23:14:45 +05:00
if ( ytdl . validateURL ( query ) ) {
2021-06-28 23:18:54 +05:00
const info = await YouTube . getVideo ( query ) . catch ( ( ) = > { } ) ;
2021-06-28 23:14:45 +05:00
if ( ! info ) return void this . emit ( PlayerEvents . NO_RESULTS , message , query ) ;
2021-06-28 23:18:54 +05:00
if ( info . live && ! this . options . enableLive ) return void this . emit ( PlayerEvents . ERROR , PlayerErrorEventCodes . LIVE_VIDEO , message , new PlayerError ( 'Live video is not enabled!' ) ) ;
const lastThumbnail = typeof info . thumbnail === "string" ? info.thumbnail : info.thumbnail.url ;
2021-06-28 23:14:45 +05:00
track = new Track ( this , {
2021-06-28 23:18:54 +05:00
title : info.title ,
description : info.description || "" ,
author : info.channel.name ,
url : info.url ,
thumbnail : lastThumbnail ,
duration : Util.buildTimeCode ( Util . parseMS ( info . duration * 1000 ) ) ,
views : parseInt ( info . views as unknown as string ) ,
2021-06-28 23:14:45 +05:00
requestedBy : message.author ,
fromPlaylist : false ,
source : 'youtube' ,
2021-06-28 23:18:54 +05:00
live : Boolean ( info . live )
2021-06-28 23:14:45 +05:00
} ) ;
} else {
for ( const [ _ , extractor ] of this . Extractors ) {
if ( extractor . validate ( query ) ) {
const data = await extractor . handle ( query ) ;
if ( data ) {
track = new Track ( this , {
title : data.title ,
description : data.description ,
duration : Util.buildTimeCode ( Util . parseMS ( data . duration ) ) ,
thumbnail : data.thumbnail ,
author : data.author ,
views : data.views ,
engine : data.engine ,
source : data.source ? ? 'arbitrary' ,
fromPlaylist : false ,
requestedBy : message.author ,
url : data.url
} ) ;
2021-06-22 19:44:59 +05:00
2021-06-28 23:14:45 +05:00
if ( extractor . important ) break ;
}
2021-06-22 19:44:59 +05:00
}
}
2021-06-30 21:13:07 +05:00
if ( ! track ) track = await this . _searchTracks ( message , query , firstResult , startFromIndex ) ;
2021-06-28 23:14:45 +05:00
}
2021-04-06 19:06:18 +05:00
}
if ( track ) {
if ( this . isPlaying ( message ) ) {
const queue = this . _addTrackToQueue ( message , track ) ;
this . emit ( PlayerEvents . TRACK_ADD , message , queue , queue . tracks [ queue . tracks . length - 1 ] ) ;
} else {
const queue = await this . _createQueue ( message , track ) ;
if ( queue ) this . emit ( PlayerEvents . TRACK_START , message , queue . tracks [ 0 ] , queue ) ;
}
}
}
2021-04-09 17:59:14 +05:00
/ * *
* Checks if this player is playing in a server
2021-04-21 12:09:16 +05:00
* @param { DiscordMessage } message The message object
2021-04-21 14:10:44 +05:00
* @returns { Boolean }
2021-04-09 17:59:14 +05:00
* /
2021-04-19 18:32:10 +05:00
isPlaying ( message : Message ) : boolean {
2021-04-06 19:08:01 +05:00
return this . queues . some ( ( g ) = > g . guildID === message . guild . id ) ;
2021-04-06 19:06:18 +05:00
}
2021-04-09 17:59:14 +05:00
/ * *
* Returns guild queue object
2021-04-21 12:09:16 +05:00
* @param { DiscordMessage } message The message object
2021-04-21 14:10:44 +05:00
* @returns { Queue }
2021-04-09 17:59:14 +05:00
* /
2021-04-19 18:32:10 +05:00
getQueue ( message : Message ) : Queue {
2021-04-06 19:08:01 +05:00
return this . queues . find ( ( g ) = > g . guildID === message . guild . id ) ;
2021-04-06 19:06:18 +05:00
}
2021-04-09 17:59:14 +05:00
/ * *
* Sets audio filters in this player
2021-04-21 12:09:16 +05:00
* @param { DiscordMessage } message The message object
2021-04-21 10:08:33 +05:00
* @param { QueueFilters } newFilters Audio filters object
2021-04-21 14:10:44 +05:00
* @returns { Promise < void > }
2021-04-09 17:59:14 +05:00
* /
2021-04-06 20:38:17 +05:00
setFilters ( message : Message , newFilters : QueueFilters ) : Promise < void > {
return new Promise ( ( resolve ) = > {
const queue = this . queues . find ( ( g ) = > g . guildID === message . guild . id ) ;
2021-05-14 17:03:17 +05:00
if ( ! queue ) this . emit ( PlayerEvents . ERROR , PlayerErrorEventCodes . NOT_PLAYING , message , new PlayerError ( 'Not playing' ) ) ;
if ( queue . playing . raw . live ) return void this . emit ( PlayerEvents . ERROR , PlayerErrorEventCodes . LIVE_VIDEO , message , new PlayerError ( 'Cannot use setFilters on livestream' ) ) ;
2021-04-14 08:02:02 +05:00
2021-04-06 20:38:17 +05:00
Object . keys ( newFilters ) . forEach ( ( filterName ) = > {
// @ts-ignore
queue . filters [ filterName ] = newFilters [ filterName ] ;
} ) ;
this . _playStream ( queue , true ) . then ( ( ) = > {
resolve ( ) ;
} ) ;
} ) ;
}
2021-04-09 17:59:14 +05:00
/ * *
* Sets track position
2021-04-21 12:09:16 +05:00
* @param { DiscordMessage } message The message object
2021-04-21 11:52:59 +05:00
* @param { Number } time Time in ms to set
2021-04-21 14:10:44 +05:00
* @returns { Promise < void > }
2021-04-09 17:59:14 +05:00
* /
2021-04-07 19:05:35 +05:00
setPosition ( message : Message , time : number ) : Promise < void > {
return new Promise ( ( resolve ) = > {
const queue = this . queues . find ( ( g ) = > g . guildID === message . guild . id ) ;
2021-04-19 19:11:38 +05:00
if ( ! queue ) return this . emit ( PlayerEvents . ERROR , PlayerErrorEventCodes . NOT_PLAYING , message ) ;
2021-04-07 19:05:35 +05:00
if ( typeof time !== 'number' && ! isNaN ( time ) ) time = parseInt ( time ) ;
2021-04-25 15:33:51 +05:00
if ( queue . playing . durationMS <= time ) return this . skip ( message ) ;
2021-05-14 17:03:17 +05:00
if ( queue . voiceConnection . dispatcher . streamTime === time || queue . voiceConnection . dispatcher . streamTime + queue . additionalStreamTime === time ) return resolve ( ) ;
2021-04-07 19:05:35 +05:00
if ( time < 0 ) this . _playStream ( queue , false ) . then ( ( ) = > resolve ( ) ) ;
this . _playStream ( queue , false , time ) . then ( ( ) = > resolve ( ) ) ;
} ) ;
}
2021-04-09 17:59:14 +05:00
/ * *
* Sets track position
2021-04-21 12:09:16 +05:00
* @param { DiscordMessage } message The message object
2021-04-21 11:52:59 +05:00
* @param { Number } time Time in ms to set
2021-04-21 14:10:44 +05:00
* @returns { Promise < void > }
2021-04-09 17:59:14 +05:00
* /
2021-04-19 18:32:10 +05:00
seek ( message : Message , time : number ) : Promise < void > {
2021-04-07 19:05:35 +05:00
return this . setPosition ( message , time ) ;
}
2021-04-09 17:59:14 +05:00
/ * *
* Skips current track
2021-04-21 12:09:16 +05:00
* @param { DiscordMessage } message The message object
2021-04-21 14:10:44 +05:00
* @returns { Boolean }
2021-04-09 17:59:14 +05:00
* /
2021-04-07 19:05:35 +05:00
skip ( message : Message ) : boolean {
const queue = this . getQueue ( message ) ;
if ( ! queue ) {
this . emit ( PlayerEvents . ERROR , PlayerErrorEventCodes . NOT_PLAYING , message ) ;
return false ;
}
if ( ! queue . voiceConnection || ! queue . voiceConnection . dispatcher ) {
this . emit ( PlayerEvents . ERROR , PlayerErrorEventCodes . MUSIC_STARTING , message ) ;
return false ;
}
queue . voiceConnection . dispatcher . end ( ) ;
queue . lastSkipped = true ;
return true ;
}
2021-04-09 17:59:14 +05:00
/ * *
* Moves to a new voice channel
2021-04-21 12:09:16 +05:00
* @param { DiscordMessage } message The message object
* @param { DiscordVoiceChannel } channel New voice channel to move to
2021-04-21 14:10:44 +05:00
* @returns { Boolean }
2021-04-09 17:59:14 +05:00
* /
2021-04-19 18:32:10 +05:00
moveTo ( message : Message , channel? : VoiceChannel ) : boolean {
2021-04-07 19:05:35 +05:00
if ( ! channel || channel . type !== 'voice' ) return ;
const queue = this . queues . find ( ( g ) = > g . guildID === message . guild . id ) ;
if ( ! queue ) {
this . emit ( PlayerEvents . ERROR , PlayerErrorEventCodes . NOT_PLAYING , message ) ;
return false ;
}
if ( ! queue . voiceConnection || ! queue . voiceConnection . dispatcher ) {
this . emit ( PlayerEvents . ERROR , PlayerErrorEventCodes . MUSIC_STARTING , message ) ;
return false ;
}
if ( queue . voiceConnection . channel . id === channel . id ) return ;
queue . voiceConnection . dispatcher . pause ( ) ;
channel
. join ( )
. then ( ( ) = > queue . voiceConnection . dispatcher . resume ( ) )
. catch ( ( ) = > this . emit ( PlayerEvents . ERROR , PlayerErrorEventCodes . UNABLE_TO_JOIN , message ) ) ;
return true ;
}
2021-04-09 17:59:14 +05:00
/ * *
* Pause the playback
2021-04-21 12:09:16 +05:00
* @param { DiscordMessage } message The message object
2021-04-21 14:10:44 +05:00
* @returns { Boolean }
2021-04-09 17:59:14 +05:00
* /
2021-04-19 18:32:10 +05:00
pause ( message : Message ) : boolean {
2021-04-09 15:04:36 +05:00
const queue = this . getQueue ( message ) ;
if ( ! queue ) {
this . emit ( PlayerEvents . ERROR , PlayerErrorEventCodes . NOT_PLAYING , message ) ;
return false ;
}
if ( ! queue . voiceConnection || ! queue . voiceConnection . dispatcher ) {
this . emit ( PlayerEvents . ERROR , PlayerErrorEventCodes . MUSIC_STARTING , message ) ;
return false ;
}
queue . voiceConnection . dispatcher . pause ( ) ;
queue . paused = true ;
return true ;
}
2021-04-09 17:59:14 +05:00
/ * *
* Resume the playback
2021-04-21 12:09:16 +05:00
* @param { DiscordMessage } message The message object
2021-04-21 14:10:44 +05:00
* @returns { Boolean }
2021-04-09 17:59:14 +05:00
* /
2021-04-19 18:32:10 +05:00
resume ( message : Message ) : boolean {
2021-04-09 15:04:36 +05:00
const queue = this . getQueue ( message ) ;
if ( ! queue ) {
this . emit ( PlayerEvents . ERROR , PlayerErrorEventCodes . NOT_PLAYING , message ) ;
return false ;
}
if ( ! queue . voiceConnection || ! queue . voiceConnection . dispatcher ) {
this . emit ( PlayerEvents . ERROR , PlayerErrorEventCodes . MUSIC_STARTING , message ) ;
return false ;
}
queue . voiceConnection . dispatcher . resume ( ) ;
queue . paused = false ;
return true ;
}
2021-04-09 17:59:14 +05:00
/ * *
* Stops the player
2021-04-21 12:09:16 +05:00
* @param { DiscordMessage } message The message object
2021-04-21 14:10:44 +05:00
* @returns { Boolean }
2021-04-09 17:59:14 +05:00
* /
2021-04-19 18:32:10 +05:00
stop ( message : Message ) : boolean {
2021-04-09 15:04:36 +05:00
const queue = this . getQueue ( message ) ;
if ( ! queue ) {
this . emit ( PlayerEvents . ERROR , PlayerErrorEventCodes . NOT_PLAYING , message ) ;
return false ;
}
if ( ! queue . voiceConnection || ! queue . voiceConnection . dispatcher ) {
this . emit ( PlayerEvents . ERROR , PlayerErrorEventCodes . MUSIC_STARTING , message ) ;
return false ;
}
queue . stopped = true ;
queue . tracks = [ ] ;
if ( queue . stream ) queue . stream . destroy ( ) ;
queue . voiceConnection . dispatcher . end ( ) ;
if ( this . options . leaveOnStop ) queue . voiceConnection . channel . leave ( ) ;
this . queues . delete ( message . guild . id ) ;
return true ;
}
2021-04-09 17:59:14 +05:00
/ * *
* Sets music volume
2021-04-21 12:09:16 +05:00
* @param { DiscordMessage } message The message object
2021-04-21 11:52:59 +05:00
* @param { Number } percent The volume percentage / amount to set
2021-04-21 14:10:44 +05:00
* @returns { Boolean }
2021-04-09 17:59:14 +05:00
* /
2021-04-19 18:32:10 +05:00
setVolume ( message : Message , percent : number ) : boolean {
2021-04-09 15:04:36 +05:00
const queue = this . getQueue ( message ) ;
if ( ! queue ) {
this . emit ( PlayerEvents . ERROR , PlayerErrorEventCodes . NOT_PLAYING , message ) ;
return false ;
}
if ( ! queue . voiceConnection || ! queue . voiceConnection . dispatcher ) {
this . emit ( PlayerEvents . ERROR , PlayerErrorEventCodes . MUSIC_STARTING , message ) ;
return false ;
}
queue . volume = percent ;
queue . voiceConnection . dispatcher . setVolumeLogarithmic ( queue . calculatedVolume / 200 ) ;
return true ;
}
2021-04-09 17:59:14 +05:00
/ * *
* Clears the queue
2021-04-21 12:09:16 +05:00
* @param { DiscordMessage } message The message object
2021-04-21 14:10:44 +05:00
* @returns { Boolean }
2021-04-09 17:59:14 +05:00
* /
2021-04-19 18:32:10 +05:00
clearQueue ( message : Message ) : boolean {
2021-04-09 15:04:36 +05:00
const queue = this . getQueue ( message ) ;
if ( ! queue ) {
this . emit ( PlayerEvents . ERROR , PlayerErrorEventCodes . NOT_PLAYING , message ) ;
return false ;
}
queue . tracks = queue . playing ? [ queue . playing ] : [ ] ;
return true ;
}
2021-04-09 17:59:14 +05:00
/ * *
* Plays previous track
2021-04-21 12:09:16 +05:00
* @param { DiscordMessage } message The message object
2021-04-21 14:10:44 +05:00
* @returns { Boolean }
2021-04-09 17:59:14 +05:00
* /
2021-04-19 18:32:10 +05:00
back ( message : Message ) : boolean {
2021-04-09 15:04:36 +05:00
const queue = this . getQueue ( message ) ;
if ( ! queue ) {
this . emit ( PlayerEvents . ERROR , PlayerErrorEventCodes . NOT_PLAYING , message ) ;
return false ;
}
if ( ! queue . voiceConnection || ! queue . voiceConnection . dispatcher ) {
this . emit ( PlayerEvents . ERROR , PlayerErrorEventCodes . MUSIC_STARTING , message ) ;
return false ;
}
queue . tracks . splice ( 1 , 0 , queue . previousTracks . shift ( ) ) ;
queue . voiceConnection . dispatcher . end ( ) ;
queue . lastSkipped = true ;
return true ;
}
2021-04-09 17:59:14 +05:00
/ * *
* Sets repeat mode
2021-04-21 12:09:16 +05:00
* @param { DiscordMessage } message The message object
2021-04-21 11:52:59 +05:00
* @param { Boolean } enabled If it should enable the repeat mode
2021-04-09 17:59:14 +05:00
* /
2021-04-19 18:32:10 +05:00
setRepeatMode ( message : Message , enabled : boolean ) : boolean {
2021-04-09 15:04:36 +05:00
const queue = this . getQueue ( message ) ;
if ( ! queue ) {
this . emit ( PlayerEvents . ERROR , PlayerErrorEventCodes . NOT_PLAYING , message ) ;
return ;
}
queue . repeatMode = Boolean ( enabled ) ;
return queue . repeatMode ;
}
2021-04-09 17:59:14 +05:00
/ * *
* Sets loop mode
2021-04-21 12:09:16 +05:00
* @param { DiscordMessage } message The message object
2021-04-21 11:52:59 +05:00
* @param { Boolean } enabled If it should enable the loop mode
2021-04-21 14:10:44 +05:00
* @returns { Boolean }
2021-04-09 17:59:14 +05:00
* /
2021-04-19 18:32:10 +05:00
setLoopMode ( message : Message , enabled : boolean ) : boolean {
2021-04-09 15:04:36 +05:00
const queue = this . getQueue ( message ) ;
if ( ! queue ) {
this . emit ( PlayerEvents . ERROR , PlayerErrorEventCodes . NOT_PLAYING , message ) ;
return ;
}
queue . loopMode = Boolean ( enabled ) ;
return queue . loopMode ;
}
2021-04-09 17:59:14 +05:00
/ * *
* Returns currently playing track
2021-04-21 12:09:16 +05:00
* @param { DiscordMessage } message The message object
2021-04-21 14:10:44 +05:00
* @returns { Track }
2021-04-09 17:59:14 +05:00
* /
2021-04-19 18:32:10 +05:00
nowPlaying ( message : Message ) : Track {
2021-04-09 15:04:36 +05:00
const queue = this . getQueue ( message ) ;
if ( ! queue ) {
this . emit ( PlayerEvents . ERROR , PlayerErrorEventCodes . NOT_PLAYING , message ) ;
return ;
}
return queue . tracks [ 0 ] ;
}
2021-04-09 17:59:14 +05:00
/ * *
* Shuffles the queue
2021-04-21 12:09:16 +05:00
* @param { DiscordMessage } message The message object
2021-04-21 14:10:44 +05:00
* @returns { Queue }
2021-04-09 17:59:14 +05:00
* /
2021-04-19 18:32:10 +05:00
shuffle ( message : Message ) : Queue {
2021-04-09 15:04:36 +05:00
const queue = this . getQueue ( message ) ;
if ( ! queue ) {
this . emit ( PlayerEvents . ERROR , PlayerErrorEventCodes . NOT_PLAYING , message ) ;
return ;
}
const currentTrack = queue . tracks . shift ( ) ;
for ( let i = queue . tracks . length - 1 ; i > 0 ; i -- ) {
const j = Math . floor ( Math . random ( ) * ( i + 1 ) ) ;
[ queue . tracks [ i ] , queue . tracks [ j ] ] = [ queue . tracks [ j ] , queue . tracks [ i ] ] ;
}
queue . tracks . unshift ( currentTrack ) ;
return queue ;
}
2021-04-09 17:59:14 +05:00
/ * *
* Removes specified track
2021-04-21 12:09:16 +05:00
* @param { DiscordMessage } message The message object
2021-04-21 10:08:33 +05:00
* @param { Track | number } track The track object / id to remove
2021-04-21 14:10:44 +05:00
* @returns { Track }
2021-04-09 17:59:14 +05:00
* /
2021-04-19 18:32:10 +05:00
remove ( message : Message , track : Track | number ) : Track {
2021-04-09 15:04:36 +05:00
const queue = this . getQueue ( message ) ;
if ( ! queue ) {
this . emit ( PlayerEvents . ERROR , PlayerErrorEventCodes . NOT_PLAYING , message ) ;
return ;
}
let trackFound : Track = null ;
if ( typeof track === 'number' ) {
trackFound = queue . tracks [ track ] ;
if ( trackFound ) {
queue . tracks = queue . tracks . filter ( ( t ) = > t !== trackFound ) ;
}
} else {
2021-05-08 14:47:46 +05:00
trackFound = queue . tracks . find ( ( s ) = > s . url === track . url ) ;
2021-04-09 15:04:36 +05:00
if ( trackFound ) {
2021-05-08 14:47:46 +05:00
queue . tracks = queue . tracks . filter ( ( s ) = > s . url !== trackFound . url ) ;
2021-04-09 15:04:36 +05:00
}
}
return trackFound ;
}
2021-04-09 17:59:14 +05:00
/ * *
* Returns time code of currently playing song
2021-04-21 12:09:16 +05:00
* @param { DiscordMessage } message The message object
2021-04-21 11:52:59 +05:00
* @param { Boolean } [ queueTime ] If it should make the time code of the whole queue
2021-04-21 14:10:44 +05:00
* @returns { Object }
2021-04-09 17:59:14 +05:00
* /
2021-04-19 18:32:10 +05:00
getTimeCode ( message : Message , queueTime? : boolean ) : { current : string ; end : string } {
2021-04-09 15:04:36 +05:00
const queue = this . getQueue ( message ) ;
if ( ! queue ) return ;
2021-05-14 17:03:17 +05:00
const previousTracksTime = queue . previousTracks . length > 0 ? queue . previousTracks . reduce ( ( p , c ) = > p + c . durationMS , 0 ) : 0 ;
2021-04-09 15:04:36 +05:00
const currentStreamTime = queueTime ? previousTracksTime + queue.currentStreamTime : queue.currentStreamTime ;
const totalTracksTime = queue . totalTime ;
const totalTime = queueTime ? previousTracksTime + totalTracksTime : queue.playing.durationMS ;
const currentTimecode = Util . buildTimeCode ( Util . parseMS ( currentStreamTime ) ) ;
const endTimecode = Util . buildTimeCode ( Util . parseMS ( totalTime ) ) ;
return {
current : currentTimecode ,
end : endTimecode
} ;
}
2021-04-09 17:59:14 +05:00
/ * *
* Creates progressbar
2021-04-21 12:09:16 +05:00
* @param { DiscordMessage } message The message object
2021-04-21 10:08:33 +05:00
* @param { PlayerProgressbarOptions } [ options ] Progressbar options
2021-04-21 14:10:44 +05:00
* @returns { String }
2021-04-09 17:59:14 +05:00
* /
2021-04-19 18:32:10 +05:00
createProgressBar ( message : Message , options? : PlayerProgressbarOptions ) : string {
2021-04-09 15:04:36 +05:00
const queue = this . getQueue ( message ) ;
if ( ! queue ) return ;
2021-05-14 17:03:17 +05:00
const previousTracksTime = queue . previousTracks . length > 0 ? queue . previousTracks . reduce ( ( p , c ) = > p + c . durationMS , 0 ) : 0 ;
const currentStreamTime = options ? . queue ? previousTracksTime + queue.currentStreamTime : queue.currentStreamTime ;
2021-04-09 15:04:36 +05:00
const totalTracksTime = queue . totalTime ;
const totalTime = options ? . queue ? previousTracksTime + totalTracksTime : queue.playing.durationMS ;
2021-05-14 17:03:17 +05:00
const length = typeof options ? . length === 'number' ? ( options ? . length <= 0 || options ? . length === Infinity ? 15 : options?.length ) : 15 ;
2021-04-09 15:04:36 +05:00
const index = Math . round ( ( currentStreamTime / totalTime ) * length ) ;
2021-05-14 17:03:17 +05:00
const indicator = typeof options ? . indicator === 'string' && options ? . indicator . length > 0 ? options ? . indicator : '🔘' ;
2021-05-01 12:47:47 +05:00
const line = typeof options ? . line === 'string' && options ? . line . length > 0 ? options ? . line : '▬' ;
2021-04-09 15:04:36 +05:00
if ( index >= 1 && index <= length ) {
const bar = line . repeat ( length - 1 ) . split ( '' ) ;
bar . splice ( index , 0 , indicator ) ;
if ( Boolean ( options ? . timecodes ) ) {
const currentTimecode = Util . buildTimeCode ( Util . parseMS ( currentStreamTime ) ) ;
const endTimecode = Util . buildTimeCode ( Util . parseMS ( totalTime ) ) ;
return ` ${ currentTimecode } ┃ ${ bar . join ( '' ) } ┃ ${ endTimecode } ` ;
} else {
return ` ${ bar . join ( '' ) } ` ;
}
} else {
if ( Boolean ( options ? . timecodes ) ) {
const currentTimecode = Util . buildTimeCode ( Util . parseMS ( currentStreamTime ) ) ;
const endTimecode = Util . buildTimeCode ( Util . parseMS ( totalTime ) ) ;
2021-04-09 15:13:07 +05:00
return ` ${ currentTimecode } ┃ ${ indicator } ${ line . repeat ( length - 1 ) } ┃ ${ endTimecode } ` ;
2021-04-09 15:04:36 +05:00
} else {
2021-04-09 15:13:07 +05:00
return ` ${ indicator } ${ line . repeat ( length - 1 ) } ` ;
2021-04-09 15:04:36 +05:00
}
}
}
2021-04-10 10:55:17 +05:00
/ * *
* Gets lyrics of a song
2021-04-21 17:17:43 +05:00
* < warn > You need to have ` @discord-player/extractor ` installed in order to use this method ! < / warn >
2021-04-21 11:52:59 +05:00
* @param { String } query Search query
2021-04-10 11:13:23 +05:00
* @example const lyrics = await player . lyrics ( "alan walker faded" )
* message . channel . send ( lyrics . lyrics ) ;
2021-04-21 14:10:44 +05:00
* @returns { Promise < LyricsData > }
2021-04-10 10:55:17 +05:00
* /
2021-04-19 18:32:10 +05:00
async lyrics ( query : string ) : Promise < LyricsData > {
2021-04-19 13:33:34 +05:00
const extractor = Util . require ( '@discord-player/extractor' ) ;
if ( ! extractor ) throw new PlayerError ( "Cannot call 'Player.lyrics()' without '@discord-player/extractor'" ) ;
2021-05-11 19:03:11 +05:00
const data = await extractor . Lyrics . init ( ) . search ( query ) ;
2021-04-10 10:55:17 +05:00
if ( Array . isArray ( data ) ) return null ;
2021-04-19 18:32:10 +05:00
return data ;
2021-04-10 10:55:17 +05:00
}
2021-04-11 18:27:48 +05:00
/ * *
* Toggle autoplay for youtube streams
2021-04-21 12:09:16 +05:00
* @param { DiscordMessage } message The message object
2021-04-21 11:52:59 +05:00
* @param { Boolean } enable Enable / Disable autoplay
2021-04-21 14:10:44 +05:00
* @returns { boolean }
2021-04-11 18:27:48 +05:00
* /
2021-04-19 19:54:47 +05:00
setAutoPlay ( message : Message , enable : boolean ) : boolean {
2021-04-11 18:27:48 +05:00
const queue = this . getQueue ( message ) ;
if ( ! queue ) return void this . emit ( PlayerEvents . ERROR , PlayerErrorEventCodes . NOT_PLAYING , message ) ;
queue . autoPlay = Boolean ( enable ) ;
2021-04-19 19:54:47 +05:00
return queue . autoPlay ;
2021-04-11 18:27:48 +05:00
}
2021-04-17 09:01:42 +05:00
/ * *
* Player stats
2021-04-21 14:10:44 +05:00
* @returns { PlayerStats }
2021-04-17 09:01:42 +05:00
* /
2021-04-17 09:01:06 +05:00
getStats ( ) : PlayerStats {
return {
uptime : this.client.uptime ,
connections : this.client.voice.connections.size ,
2021-05-09 18:16:11 +05:00
// tslint:disable:no-shadowed-variable
2021-05-14 17:03:17 +05:00
users : this.client.voice.connections.reduce ( ( a , c ) = > a + c . channel . members . filter ( ( a ) = > a . user . id !== this . client . user . id ) . size , 0 ) ,
2021-04-17 09:01:06 +05:00
queues : this.queues.size ,
extractors : this.Extractors.size ,
versions : {
ffmpeg : Util.getFFmpegVersion ( ) ,
node : process.version ,
v8 : process.versions.v8
} ,
system : {
arch : process.arch ,
2021-06-28 23:23:28 +05:00
// @ts-ignore
2021-04-17 09:01:06 +05:00
platform : process.platform ,
cpu : os.cpus ( ) . length ,
memory : {
total : ( process . memoryUsage ( ) . heapTotal / 1024 / 1024 ) . toFixed ( 2 ) ,
usage : ( process . memoryUsage ( ) . heapUsed / 1024 / 1024 ) . toFixed ( 2 ) ,
rss : ( process . memoryUsage ( ) . rss / 1024 / 1024 ) . toFixed ( 2 ) ,
arrayBuffers : ( process . memoryUsage ( ) . arrayBuffers / 1024 / 1024 ) . toFixed ( 2 )
} ,
uptime : process.uptime ( )
}
} ;
}
2021-04-23 23:27:51 +05:00
/ * *
* Jumps to particular track
* @param { DiscordMessage } message The message
* @param { Track | number } track The track to jump to
* @returns { boolean }
* /
jump ( message : Message , track : Track | number ) : boolean {
const toJUMP = this . remove ( message , track ) ;
const queue = this . getQueue ( message ) ;
2021-04-23 23:30:45 +05:00
if ( ! toJUMP || ! queue ) throw new PlayerError ( 'Specified Track not found' ) ;
2021-04-23 23:27:51 +05:00
queue . tracks . splice ( 1 , 0 , toJUMP ) ;
return this . skip ( message ) ;
}
2021-05-09 17:34:25 +05:00
/ * *
* Internal method to handle VoiceStateUpdate events
* @param { DiscordVoiceState } oldState The old voice state
* @param { DiscordVoiceState } newState The new voice state
* @returns { void }
* @private
* /
2021-04-19 18:32:10 +05:00
private _handleVoiceStateUpdate ( oldState : VoiceState , newState : VoiceState ) : void {
2021-04-09 15:04:36 +05:00
const queue = this . queues . find ( ( g ) = > g . guildID === oldState . guild . id ) ;
if ( ! queue ) return ;
if ( newState . member . id === this . client . user . id && ! newState . channelID ) {
queue . stream . destroy ( ) ;
this . queues . delete ( newState . guild . id ) ;
this . emit ( PlayerEvents . BOT_DISCONNECT , queue . firstMessage ) ;
}
if ( ! queue . voiceConnection || ! queue . voiceConnection . channel ) return ;
if ( ! this . options . leaveOnEmpty ) return ;
if ( ! oldState . channelID || newState . channelID ) {
const emptyTimeout = this . _cooldownsTimeout . get ( ` empty_ ${ oldState . guild . id } ` ) ;
2021-05-10 10:15:24 +05:00
2021-05-11 17:20:34 +05:00
// @todo: make stage channels stable
2021-05-10 10:15:24 +05:00
const channelEmpty = Util . isVoiceEmpty ( queue . voiceConnection . channel as VoiceChannel ) ;
2021-04-09 15:04:36 +05:00
if ( ! channelEmpty && emptyTimeout ) {
clearTimeout ( emptyTimeout ) ;
this . _cooldownsTimeout . delete ( ` empty_ ${ oldState . guild . id } ` ) ;
}
} else {
2021-05-10 10:15:24 +05:00
if ( ! Util . isVoiceEmpty ( queue . voiceConnection . channel as VoiceChannel ) ) return ;
2021-04-09 15:04:36 +05:00
const timeout = setTimeout ( ( ) = > {
2021-05-10 10:15:24 +05:00
if ( ! Util . isVoiceEmpty ( queue . voiceConnection . channel as VoiceChannel ) ) return ;
2021-04-09 15:04:36 +05:00
if ( ! this . queues . has ( queue . guildID ) ) return ;
queue . voiceConnection . channel . leave ( ) ;
this . queues . delete ( queue . guildID ) ;
this . emit ( PlayerEvents . CHANNEL_EMPTY , queue . firstMessage , queue ) ;
} , this . options . leaveOnEmptyCooldown || 0 ) ;
this . _cooldownsTimeout . set ( ` empty_ ${ oldState . guild . id } ` , timeout ) ;
}
}
2021-05-09 17:34:25 +05:00
/ * *
* Internal method used to add tracks to the queue
* @param { DiscordMessage } message The discord message
* @param { Track } track The track
* @returns { Queue }
* @private
* /
_addTrackToQueue ( message : Message , track : Track ) : Queue {
2021-04-06 19:06:18 +05:00
const queue = this . getQueue ( message ) ;
2021-05-14 17:03:17 +05:00
if ( ! queue ) this . emit ( PlayerEvents . ERROR , PlayerErrorEventCodes . NOT_PLAYING , message , new PlayerError ( 'Player is not available in this server' ) ) ;
2021-04-06 19:06:18 +05:00
if ( ! track || ! ( track instanceof Track ) ) throw new PlayerError ( 'No track specified to add to the queue' ) ;
queue . tracks . push ( track ) ;
return queue ;
}
2021-05-09 17:34:25 +05:00
/ * *
* Same as ` _addTrackToQueue ` but used for multiple tracks
* @param { DiscordMessage } message Discord message
* @param { Track [ ] } tracks The tracks
* @returns { Queue }
* @private
* /
_addTracksToQueue ( message : Message , tracks : Track [ ] ) : Queue {
2021-04-07 19:05:35 +05:00
const queue = this . getQueue ( message ) ;
2021-05-14 17:03:17 +05:00
if ( ! queue ) throw new PlayerError ( 'Cannot add tracks to queue because no song is currently being played on the server.' ) ;
2021-04-07 19:05:35 +05:00
queue . tracks . push ( . . . tracks ) ;
return queue ;
}
2021-05-09 21:17:01 +05:00
/ * *
* Internal method used to create queue
* @param { DiscordMessage } message The message
* @param { Track } track The track
* @returns { Promise < Queue > }
* @private
* /
2021-04-06 19:06:18 +05:00
private _createQueue ( message : Message , track : Track ) : Promise < Queue > {
return new Promise ( ( resolve ) = > {
2021-04-06 19:08:01 +05:00
const channel = message . member . voice ? message.member.voice.channel : null ;
2021-05-14 17:03:17 +05:00
if ( ! channel ) return void this . emit ( PlayerEvents . ERROR , PlayerErrorEventCodes . NOT_CONNECTED , message , new PlayerError ( 'Voice connection is not available in this server!' ) ) ;
2021-04-06 19:06:18 +05:00
2021-04-11 17:51:50 +05:00
const queue = new Queue ( this , message ) ;
2021-05-31 21:07:34 +05:00
queue . volume = this . options . volume || 100 ;
2021-04-06 19:06:18 +05:00
this . queues . set ( message . guild . id , queue ) ;
2021-04-06 19:08:01 +05:00
channel
. join ( )
. then ( ( connection ) = > {
this . emit ( PlayerEvents . CONNECTION_CREATE , message , connection ) ;
queue . voiceConnection = connection ;
if ( this . options . autoSelfDeaf ) connection . voice . setSelfDeaf ( true ) ;
queue . tracks . push ( track ) ;
this . emit ( PlayerEvents . QUEUE_CREATE , message , queue ) ;
resolve ( queue ) ;
2021-05-14 17:20:05 +05:00
this . _playTrack ( queue , true ) . catch ( ( e ) = > {
this . emit ( PlayerEvents . ERROR , e , queue . firstMessage , queue . playing ) ;
} ) ;
2021-04-06 19:08:01 +05:00
} )
. catch ( ( err ) = > {
this . queues . delete ( message . guild . id ) ;
2021-05-14 17:03:17 +05:00
this . emit ( PlayerEvents . ERROR , PlayerErrorEventCodes . UNABLE_TO_JOIN , message , new PlayerError ( err . message ? ? err ) ) ;
2021-04-06 19:08:01 +05:00
} ) ;
2021-04-06 19:06:18 +05:00
} ) ;
}
2021-05-09 17:34:25 +05:00
/ * *
* Internal method used to init stream playing
* @param { Queue } queue The queue
* @param { boolean } firstPlay If this is a first play
* @returns { Promise < void > }
* @private
* /
2021-04-06 20:38:17 +05:00
private async _playTrack ( queue : Queue , firstPlay : boolean ) : Promise < void > {
2021-04-06 19:06:18 +05:00
if ( queue . stopped ) return ;
2021-05-14 17:20:05 +05:00
if ( ! ( queue . autoPlay && [ 'youtube' , 'spotify' ] . includes ( queue . playing ? . source ) ) && queue . tracks . length === 1 && ! queue . loopMode && ! queue . repeatMode && ! firstPlay ) {
2021-04-06 19:06:18 +05:00
if ( this . options . leaveOnEnd && ! queue . stopped ) {
2021-04-06 19:08:01 +05:00
this . queues . delete ( queue . guildID ) ;
2021-04-06 19:06:18 +05:00
const timeout = setTimeout ( ( ) = > {
queue . voiceConnection . channel . leave ( ) ;
} , this . options . leaveOnEndCooldown || 0 ) ;
this . _cooldownsTimeout . set ( ` end_ ${ queue . guildID } ` , timeout ) ;
}
2021-04-06 19:08:01 +05:00
this . queues . delete ( queue . guildID ) ;
2021-04-06 19:06:18 +05:00
if ( queue . stopped ) {
2021-04-06 20:38:17 +05:00
return void this . emit ( PlayerEvents . MUSIC_STOP , queue . firstMessage ) ;
2021-04-06 19:06:18 +05:00
}
2021-04-06 20:38:17 +05:00
return void this . emit ( PlayerEvents . QUEUE_END , queue . firstMessage , queue ) ;
2021-04-06 19:06:18 +05:00
}
2021-04-11 18:27:48 +05:00
if ( queue . autoPlay && ! queue . repeatMode && ! firstPlay ) {
const oldTrack = queue . tracks . shift ( ) ;
2021-05-14 17:20:05 +05:00
const info = [ 'youtube' , 'spotify' ] . includes ( oldTrack ? . raw . source ) ? await ytdl . getInfo ( ( oldTrack as any ) . backupLink ? ? oldTrack . url ) . catch ( ( e ) = > { } ) : null ;
2021-04-11 18:27:48 +05:00
if ( info ) {
2021-04-14 07:59:06 +05:00
const res = await Util . ytSearch ( info . related_videos [ 0 ] . title , {
player : this ,
limit : 1 ,
user : oldTrack.requestedBy
} )
2021-04-11 18:27:48 +05:00
. then ( ( v ) = > v [ 0 ] )
. catch ( ( e ) = > { } ) ;
if ( res ) {
queue . tracks . push ( res ) ;
if ( queue . loopMode ) queue . tracks . push ( oldTrack ) ;
queue . previousTracks . push ( oldTrack ) ;
}
}
} else if ( ! queue . autoPlay && ! queue . repeatMode && ! firstPlay ) {
2021-04-06 19:06:18 +05:00
const oldTrack = queue . tracks . shift ( ) ;
if ( queue . loopMode ) queue . tracks . push ( oldTrack ) ;
queue . previousTracks . push ( oldTrack ) ;
}
const track = queue . playing ;
queue . lastSkipped = false ;
2021-05-14 17:20:05 +05:00
this . _playStream ( queue , false )
. then ( ( ) = > {
if ( ! firstPlay ) this . emit ( PlayerEvents . TRACK_START , queue . firstMessage , track , queue ) ;
} )
. catch ( ( e ) = > {
this . emit ( PlayerEvents . ERROR , e , queue . firstMessage , queue . playing ) ;
} ) ;
2021-04-06 19:06:18 +05:00
}
2021-05-09 17:34:25 +05:00
/ * *
* Internal method to play audio
* @param { Queue } queue The queue
* @param { boolean } updateFilter If this method was called for audio filter update
* @param { number } [ seek ] Time in ms to seek to
* @returns { Promise < void > }
* @private
* /
2021-04-06 19:06:18 +05:00
private _playStream ( queue : Queue , updateFilter : boolean , seek? : number ) : Promise < void > {
return new Promise ( async ( resolve ) = > {
2021-04-11 18:27:48 +05:00
const ffmpeg = Util . checkFFmpeg ( ) ;
if ( ! ffmpeg ) return ;
2021-04-06 19:06:18 +05:00
2021-05-14 17:03:17 +05:00
const seekTime = typeof seek === 'number' ? seek : updateFilter ? queue . voiceConnection . dispatcher . streamTime + queue.additionalStreamTime : undefined ;
2021-04-06 19:06:18 +05:00
const encoderArgsFilters : string [ ] = [ ] ;
Object . keys ( queue . filters ) . forEach ( ( filterName ) = > {
// @ts-ignore
if ( queue . filters [ filterName ] ) {
// @ts-ignore
encoderArgsFilters . push ( this . filters [ filterName ] ) ;
}
} ) ;
2021-04-06 20:38:17 +05:00
let encoderArgs : string [ ] = [ ] ;
2021-04-06 19:06:18 +05:00
if ( encoderArgsFilters . length < 1 ) {
2021-04-06 19:08:01 +05:00
encoderArgs = [ ] ;
2021-04-06 19:06:18 +05:00
} else {
2021-04-06 19:08:01 +05:00
encoderArgs = [ '-af' , encoderArgsFilters . join ( ',' ) ] ;
2021-04-06 19:06:18 +05:00
}
let newStream : any ;
2021-05-13 09:55:32 +05:00
2021-05-31 21:11:48 +05:00
if ( ! queue . playing ? . raw ? . source ) return void this . emit ( PlayerEvents . ERROR , PlayerErrorEventCodes . VIDEO_UNAVAILABLE , queue . firstMessage , queue . playing , new PlayerError ( "Don't know how to play this item" , 'PlayerError' ) ) ;
2021-05-31 21:07:34 +05:00
2021-05-13 09:55:32 +05:00
// modify spotify
2021-05-13 11:24:28 +05:00
if ( queue . playing . raw . source === 'spotify' && ! ( queue . playing as any ) . backupLink ) {
2021-05-14 17:03:17 +05:00
const searchQueryString = this . options . disableArtistSearch ? queue . playing . title : ` ${ queue . playing . title } ${ ' - ' + queue . playing . author } ` ;
2021-05-13 13:10:41 +05:00
const yteqv = await YouTube . search ( searchQueryString , { type : 'video' , limit : 1 } ) . catch ( ( ) = > { } ) ;
2021-05-13 09:55:32 +05:00
2021-05-14 17:03:17 +05:00
if ( ! yteqv || ! yteqv . length ) return void this . emit ( PlayerEvents . ERROR , PlayerErrorEventCodes . VIDEO_UNAVAILABLE , queue . firstMessage , queue . playing , new PlayerError ( 'Could not find alternative track on youtube!' , 'SpotifyTrackError' ) ) ;
2021-05-13 09:55:32 +05:00
2021-05-13 13:10:11 +05:00
Util . define ( {
target : queue.playing ,
prop : 'backupLink' ,
value : yteqv [ 0 ] . url
2021-05-13 11:24:28 +05:00
} ) ;
2021-05-13 09:55:32 +05:00
}
2021-05-13 11:24:28 +05:00
if ( queue . playing . raw . source === 'youtube' || queue . playing . raw . source === 'spotify' ) {
newStream = ytdl ( ( queue . playing as any ) . backupLink ? ? queue . playing . url , {
2021-04-06 19:06:18 +05:00
opusEncoded : true ,
2021-05-13 11:24:28 +05:00
encoderArgs : queue.playing.raw.live ? [ ] : encoderArgs ,
2021-04-06 19:06:18 +05:00
seek : seekTime / 1000 ,
2021-04-06 19:08:01 +05:00
// tslint:disable-next-line:no-bitwise
2021-04-06 19:06:18 +05:00
highWaterMark : 1 << 25 ,
. . . this . options . ytdlDownloadOptions
} ) ;
} else {
2021-05-14 17:03:17 +05:00
newStream = ytdl . arbitraryStream ( queue . playing . raw . source === 'soundcloud' ? await queue . playing . raw . engine . downloadProgressive ( ) : queue . playing . raw . engine , {
opusEncoded : true ,
encoderArgs ,
seek : seekTime / 1000
} ) ;
2021-04-06 19:06:18 +05:00
}
setTimeout ( ( ) = > {
if ( queue . stream ) queue . stream . destroy ( ) ;
queue . stream = newStream ;
queue . voiceConnection . play ( newStream , {
type : 'opus' ,
2021-05-10 20:42:03 +05:00
bitrate : 'auto'
2021-04-06 19:06:18 +05:00
} ) ;
if ( seekTime ) {
queue . additionalStreamTime = seekTime ;
}
queue . voiceConnection . dispatcher . setVolumeLogarithmic ( queue . calculatedVolume / 200 ) ;
queue . voiceConnection . dispatcher . on ( 'start' , ( ) = > {
resolve ( ) ;
} ) ;
queue . voiceConnection . dispatcher . on ( 'finish' , ( ) = > {
queue . additionalStreamTime = 0 ;
return this . _playTrack ( queue , false ) ;
} ) ;
newStream . on ( 'error' , ( error : Error ) = > {
if ( error . message . toLowerCase ( ) . includes ( 'video unavailable' ) ) {
2021-05-14 17:03:17 +05:00
this . emit ( PlayerEvents . ERROR , PlayerErrorEventCodes . VIDEO_UNAVAILABLE , queue . firstMessage , queue . playing , error ) ;
2021-04-06 19:06:18 +05:00
this . _playTrack ( queue , false ) ;
} else {
this . emit ( PlayerEvents . ERROR , error , queue . firstMessage , error ) ;
}
} ) ;
} , 1000 ) ;
} ) ;
}
2021-04-21 15:49:16 +05:00
toString() {
2021-04-21 17:17:43 +05:00
return ` <Player ${ this . queues . size } > ` ;
2021-04-21 15:49:16 +05:00
}
2021-04-04 22:44:45 +05:00
}
2021-04-06 20:38:17 +05:00
export default Player ;
2021-04-19 18:32:10 +05:00
/ * *
* Emitted when a track starts
* @event Player # trackStart
2021-04-21 12:09:16 +05:00
* @param { DiscordMessage } message The message
2021-04-21 11:35:57 +05:00
* @param { Track } track The track
* @param { Queue } queue The queue
2021-04-19 18:32:10 +05:00
* /
/ * *
* Emitted when a playlist is started
* @event Player # queueCreate
2021-04-21 12:09:16 +05:00
* @param { DiscordMessage } message The message
2021-04-21 11:35:57 +05:00
* @param { Queue } queue The queue
2021-04-19 18:32:10 +05:00
* /
/ * *
* Emitted when the bot is awaiting search results
* @event Player # searchResults
2021-04-21 12:09:16 +05:00
* @param { DiscordMessage } message The message
2021-04-21 11:52:59 +05:00
* @param { String } query The query
2021-04-21 11:35:57 +05:00
* @param { Track [ ] } tracks The tracks
2021-04-21 15:48:07 +05:00
* @param { DiscordCollector } collector The collector
2021-04-19 18:32:10 +05:00
* /
/ * *
* Emitted when the user has sent an invalid response for search results
* @event Player # searchInvalidResponse
2021-04-21 12:09:16 +05:00
* @param { DiscordMessage } message The message
2021-04-21 11:52:59 +05:00
* @param { String } query The query
2021-04-21 11:35:57 +05:00
* @param { Track [ ] } tracks The tracks
2021-04-21 11:52:59 +05:00
* @param { String } invalidResponse The ` invalidResponse ` string
2021-04-21 15:48:07 +05:00
* @param { DiscordCollector } collector The collector
2021-04-19 18:32:10 +05:00
* /
/ * *
* Emitted when the bot has stopped awaiting search results ( timeout )
* @event Player # searchCancel
2021-04-21 12:09:16 +05:00
* @param { DiscordMessage } message The message
2021-04-21 11:52:59 +05:00
* @param { String } query The query
2021-04-21 11:35:57 +05:00
* @param { Track [ ] } tracks The tracks
2021-04-19 18:32:10 +05:00
* /
/ * *
* Emitted when the bot can ' t find related results to the query
* @event Player # noResults
2021-04-21 12:09:16 +05:00
* @param { DiscordMessage } message The message
2021-04-21 11:52:59 +05:00
* @param { String } query The query
2021-04-19 18:32:10 +05:00
* /
/ * *
* Emitted when the bot is disconnected from the channel
* @event Player # botDisconnect
2021-04-21 12:09:16 +05:00
* @param { DiscordMessage } message The message
2021-04-19 18:32:10 +05:00
* /
/ * *
* Emitted when the channel of the bot is empty
* @event Player # channelEmpty
2021-04-21 12:09:16 +05:00
* @param { DiscordMessage } message The message
2021-04-21 11:35:57 +05:00
* @param { Queue } queue The queue
2021-04-19 18:32:10 +05:00
* /
/ * *
* Emitted when the queue of the server is ended
* @event Player # queueEnd
2021-04-21 12:09:16 +05:00
* @param { DiscordMessage } message The message
2021-04-21 11:35:57 +05:00
* @param { Queue } queue The queue
2021-04-19 18:32:10 +05:00
* /
/ * *
* Emitted when a track is added to the queue
* @event Player # trackAdd
2021-04-21 12:09:16 +05:00
* @param { DiscordMessage } message The message
2021-04-21 11:35:57 +05:00
* @param { Queue } queue The queue
* @param { Track } track The track
2021-04-19 18:32:10 +05:00
* /
/ * *
* Emitted when a playlist is added to the queue
* @event Player # playlistAdd
2021-04-21 12:09:16 +05:00
* @param { DiscordMessage } message The message
2021-04-21 11:35:57 +05:00
* @param { Queue } queue The queue
* @param { Object } playlist The playlist
2021-04-19 18:32:10 +05:00
* /
/ * *
2021-05-09 17:34:25 +05:00
* Emitted when an error is triggered .
* < warn > This event should handled properly by the users otherwise it might crash the process ! < / warn >
2021-04-19 18:32:10 +05:00
* @event Player # error
2021-04-21 11:52:59 +05:00
* @param { String } error It can be ` NotConnected ` , ` UnableToJoin ` , ` NotPlaying ` , ` ParseError ` , ` LiveVideo ` or ` VideoUnavailable ` .
2021-04-21 12:09:16 +05:00
* @param { DiscordMessage } message The message
2021-04-19 18:32:10 +05:00
* /
/ * *
* Emitted when discord - player attempts to parse playlist contents ( mostly soundcloud playlists )
* @event Player # playlistParseStart
* @param { Object } playlist Raw playlist ( unparsed )
2021-04-21 12:09:16 +05:00
* @param { DiscordMessage } message The message
2021-04-19 18:32:10 +05:00
* /
/ * *
* Emitted when discord - player finishes parsing playlist contents ( mostly soundcloud playlists )
* @event Player # playlistParseEnd
* @param { Object } playlist The playlist data ( parsed )
2021-04-21 12:09:16 +05:00
* @param { DiscordMessage } message The message
2021-04-19 18:32:28 +05:00
* /
2021-04-21 11:52:59 +05:00
/ * *
* @typedef { Object } PlayerOptions
* @property { Boolean } [ leaveOnEnd = false ] If it should leave on queue end
* @property { Number } [ leaveOnEndCooldown = 0 ] Time in ms to wait before executing ` leaveOnEnd `
* @property { Boolean } [ leaveOnStop = false ] If it should leave on stop command
* @property { Boolean } [ leaveOnEmpty = false ] If it should leave on empty voice channel
* @property { Number } [ leaveOnEmptyCooldown = 0 ] Time in ms to wait before executing ` leaveOnEmpty `
* @property { Boolean } [ autoSelfDeaf = false ] If it should set the client to ` self deaf ` mode on joining
* @property { Boolean } [ enableLive = false ] If it should enable live videos support
* @property { YTDLDownloadOptions } [ ytdlDownloadOptions = { } ] The download options passed to ` ytdl-core `
* @property { Boolean } [ useSafeSearch = false ] If it should use ` safe search ` method for youtube searches
2021-04-21 17:17:43 +05:00
* @property { Boolean } [ disableAutoRegister = false ] If it should disable auto - registeration of ` @discord-player/extractor `
2021-05-10 10:11:28 +05:00
* @property { Boolean } [ disableArtistSearch = false ] If it should disable artist search for spotify
2021-05-13 13:10:11 +05:00
* @property { Boolean } [ fetchBeforeQueued = false ] If it should fetch all songs loaded from spotify before playing
2021-05-31 21:07:34 +05:00
* @property { Number } [ volume = 100 ] Startup player volume
2021-04-21 11:52:59 +05:00
* /
/ * *
2021-04-21 17:08:24 +05:00
* The type of Track source , either :
* * ` soundcloud ` - a stream from SoundCloud
* * ` youtube ` - a stream from YouTube
2021-05-14 05:34:42 +05:00
* * ` spotify ` - a spotify track
2021-04-21 17:08:24 +05:00
* * ` arbitrary ` - arbitrary stream
* @typedef { String } TrackSource
2021-04-21 11:52:59 +05:00
* /
/ * *
* @typedef { Object } TrackData
* @property { String } title The title
* @property { String } description The description
* @property { String } author The author
* @property { String } url The url
* @property { String } duration The duration
* @property { Number } views The view count
2021-04-21 12:09:16 +05:00
* @property { DiscordUser } requestedBy The user who requested this track
2021-04-21 11:52:59 +05:00
* @property { Boolean } fromPlaylist If this track came from a playlist
* @property { TrackSource } [ source ] The track source
* @property { string | Readable } [ engine ] The stream engine
* @property { Boolean } [ live = false ] If this track is livestream instance
* /
/ * *
* @typedef { Object } QueueFilters
* The FFmpeg Filters
* /
/ * *
2021-04-21 17:08:24 +05:00
* The query type , either :
* * ` soundcloud_track ` - a SoundCloud Track
* * ` soundcloud_playlist ` - a SoundCloud Playlist
* * ` spotify_song ` - a Spotify Song
* * ` spotify_album ` - a Spotify album
* * ` spotify_playlist ` - a Spotify playlist
* * ` youtube_video ` - a YouTube video
* * ` youtube_playlist ` - a YouTube playlist
* * ` vimeo ` - a Vimeo link
* * ` facebook ` - a Facebook link
* * ` reverbnation ` - a Reverbnation link
* * ` attachment ` - an attachment link
* * ` youtube_search ` - a YouTube search keyword
* @typedef { String } QueryType The query type
2021-04-21 11:52:59 +05:00
* /
/ * *
* @typedef { Object } ExtractorModelData
* @property { String } title The title
* @property { Number } duration The duration in ms
* @property { String } thumbnail The thumbnail url
* @property { string | Readable } engine The audio engine
* @property { Number } views The views count of this stream
* @property { String } author The author
* @property { String } description The description
* @property { String } url The url
* @property { String } [ version = '0.0.0' ] The extractor version
* @property { Boolean } [ important = false ] Mark as important
* /
/ * *
* @typedef { Object } PlayerProgressbarOptions
* @property { Boolean } [ timecodes ] If it should return progres bar with time codes
* @property { Boolean } [ queue ] if it should return the progress bar of the whole queue
* @property { Number } [ length ] The length of progress bar to build
2021-05-31 21:07:34 +05:00
* @property { String } [ indicator ] The progress indicator
* @property { String } [ line ] The progress bar track
2021-04-21 11:52:59 +05:00
* /
/ * *
* @typedef { Object } LyricsData
* @property { String } title The title of the lyrics
* @property { Number } id The song id
* @property { String } thumbnail The thumbnail
* @property { String } image The image
* @property { String } url The url
2021-05-13 10:41:41 +05:00
* @property { Object } artist The artist info
2021-04-21 11:52:59 +05:00
* @property { String } [ artist . name ] The name of the artist
* @property { Number } [ artist . id ] The ID of the artist
* @property { String } [ artist . url ] The profile link of the artist
* @property { String } [ artist . image ] The artist image url
* @property { String ? } lyrics The lyrics
* /
/ * *
* @typedef { Object } PlayerStats
* @property { Number } uptime The uptime in ms
* @property { Number } connections The number of connections
* @property { Number } users The number of users
* @property { Number } queues The number of queues
* @property { Number } extractors The number of custom extractors registered
* @property { Object } versions The versions metadata
* @property { String } [ versions . ffmpeg ] The ffmpeg version
* @property { String } [ versions . node ] The node version
* @property { String } [ versions . v8 ] The v8 JavaScript engine version
* @property { Object } system The system data
* @property { String } [ system . arch ] The system arch
2021-04-21 17:08:24 +05:00
* @property { String } [ system . platform ] The system platform
2021-04-21 11:52:59 +05:00
* @property { Number } [ system . cpu ] The cpu count
* @property { Object } [ system . memory ] The memory info
* @property { String } [ system . memory . total ] The total memory
* @property { String } [ system . memory . usage ] The memory usage
* @property { String } [ system . memory . rss ] The memory usage in RSS
* @property { String } [ system . memory . arrayBuffers ] The memory usage in ArrayBuffers
* @property { Number } [ system . uptime ] The system uptime
* /
/ * *
* @typedef { Object } TimeData
* @property { Number } days The time in days
* @property { Number } hours The time in hours
* @property { Number } minutes The time in minutes
* @property { Number } seconds The time in seconds
2021-04-21 13:30:54 +05:00
* /