const voice = require('./voice.js')
const { getVoiceConnection, createAudioResource, joinVoiceChannel, createAudioPlayer, NoSubscriberBehavior, AudioPlayer, VoiceConnection } = require('@discordjs/voice')
const EventEmiter = require('node:events')
const search = require('./search.js')
const ytStream = require('yt-stream')
const getMinute = require('./time')
class QueueBuilder extends EventEmiter {
/**
* Create a new queue
* @param {Object} guild The guild of the discord server
* @param {object} options The options for the queue. Automatically configed by Dismusic
* @property {object} guild The guild of the queue
* @property {AudioPlayer} player The player that is used for playing music
* @property {number} volume The current volume of this queue
* @property {boolean} isPaused Tells you whether this queue is paused
* @property {VoiceConnection|undefined} connection The raw discord.js connection for the voice channel connected
* @property {string} loopMode The current loop mode of this queue. It is none, queue or song
* @property {Object[]} tracks The current tracks in this queue
*/
constructor(guild, options) {
super()
this.guild = guild
this.options = options
this.tracks = []
this.audioRes = undefined
this.loopMode = "none"
this.timestamp = undefined
this.connection = getVoiceConnection(this.guild.id) || undefined
this.isPaused = true
this.volume = 100
this.player = createAudioPlayer({
behaviors: {
noSubscriber: NoSubscriberBehavior.Play
}
})
}
/**
* Play something in the voice channel
* @param {object} searchRes The search Results you got from <player>.search function
*/
async play(searchRes) {
const searchedURL = searchRes?.url
if(!searchedURL) throw new Error('[ Dismusic Error ] Search result must be a type of searched track. Got ' + typeof searchRes + " instead")
const isVoiceConnected = getVoiceConnection(this.guild.id) || null
if(!isVoiceConnected) throw new Error('[ Dismusic Error ] Player must be connected to voice before playing anything')
let voicePlayer
searchRes.changeableVolume = this.options?.changeableVolume || true
if(searchRes.source === "YouTube" || searchRes.source === "SoundCloud") {
voicePlayer = await voice.playTrack(searchRes, this.options.useExtractor, this.guild)
} else {
const searchResults = await search.YouTubeSearch(`${searchRes.name} by ${searchRes.author.name} lyrics`)
voicePlayer = await voice.playTrack(searchResults[0], this.options.useExtractor, this.guild)
}
this.isPaused = false
this.audioRes = voicePlayer.resource
const player = voicePlayer.player
this.player = player
this.tracks.push(searchRes)
this.timestamp = Date.now()
player.on('stateChange', async (_oldState, newState) => {
const loopMode = this.loopMode
this.isPaused = true
const status = newState.status
if(status === 'idle' && this.tracks.length !== 0) {
if(loopMode === 'song') {
const latestTrack = this.tracks[0]
if(track.source === "YouTube" || track.source === "SoundCloud") {
const stream = await ytStream.stream(latestTrack.url)
audioRes = createAudioResource(stream.stream, {
inputType: stream.type,
inlineVolume: this.options?.changeableVolume || true
})
} else {
const searchResults = await search.YouTubeSearch(`${latestTrack.name} by ${latestTrack.author.name}`)
const stream = await ytStream.stream(searchResults[0].url)
audioRes = createAudioResource(stream.stream, {
inputType: stream.type,
inlineVolume: this.options?.changeableVolume || true
})
}
this.audioRes = audioRes
player.play(audioRes)
this.emit('EmitTrackStart', track)
this.timestamp = Date.now()
this.isPaused = false
this.audioRes.volume.setVolume(this.volume / 100)
return
}
const track = this.tracks.splice(0, 1)
if(loopMode === 'queue') {
this.tracks.push(track);
this.audioRes.volume.setVolume(this.volume / 100)
}
if(this.tracks.length === 0) return this.emit('emitQueueEnded')
const latestTrack = this.tracks[0]
let audioRes
if(latestTrack.source === "YouTube" || latestTrack.source === "SoundCloud") {
const stream = await ytStream.stream(latestTrack.url)
audioRes = createAudioResource(stream.stream, {
inputType: stream.type,
inlineVolume: this.options?.changeableVolume || true
})
} else {
const searchResults = await search.YouTubeSearch(`${latestTrack.name} by ${latestTrack.author.name}`)
const stream = await ytStream.stream(searchResults[0].url)
audioRes = createAudioResource(stream.stream, {
inputType: stream.type,
inlineVolume: this.options?.changeableVolume || true
})
}
this.audioRes = audioRes
player.play(audioRes)
this.audioRes.volume.setVolume(this.volume / 100)
this.emit('EmitTrackStart', latestTrack)
this.timestamp = Date.now()
} else if(this.tracks.length === 0 && status !== "playing") {
this.emit('emitQueueEnded')
}
this.isPaused = false
})
}
/**
* Connect to a voice channel
* @param {object} channel The discord.js voice channel
* @returns {object} Returns the discord.js connection object
*/
async connectTo(channel) {
const connection = joinVoiceChannel({
channelId: channel.id,
guildId: this.guild.id,
adapterCreator: this.guild.voiceAdapterCreator
})
this.connection = connection
return connection
}
/**
* Skip a track in the queue
* @returns {object} Returns an object that contains the track skipped
*/
async skip() {
const track = this.tracks.splice(0, 1)
if(this.tracks.length === 0) return this.emit('emitQueueEnded')
const newTrack = this.tracks[0]
const source = newTrack.source
let audioRes
if(source === "YouTube" || source === "SoundCloud") {
const stream = await ytStream.stream(newTrack.url)
audioRes = createAudioResource(stream.stream, {
inputType: stream.type,
inlineVolume: this.options?.changeableVolume || true
})
} else {
const searchResults = await search.YouTubeSearch(`${newTrack.name} by ${newTrack.author.name}`)
const stream = await ytStream.stream(searchResults[0].url)
audioRes = createAudioResource(stream.stream, {
inputType: stream.type,
inlineVolume: this.options?.changeableVolume || true
})
}
this.audioRes = audioRes
this.emit('EmitTrackStart', newTrack)
this.player.play(audioRes)
this.timestamp = Date.now()
return track
}
/**
* Add a track to the queue
* @param {object} searchRes The results your found
*/
async addTrack(searchRes) {
this.tracks.push(searchRes)
}
/**
* Add multiple tracks to the queue
* @param {Object[]} playlist The results you found. Good for adding playlists
*/
async addTracks(playlist) {
const currentTracks = this.tracks
const newTracks = [...currentTracks, ...playlist]
this.tracks = newTracks
}
/**
* Pause the player
*/
async pause() {
const audioPlayer = this.player
audioPlayer.pause()
this.pausedTimeStamp = Date.now()
this.isPaused = true
}
/**
* resume the player
*/
async resume() {
const audioPlayer = this.player
if(this.isPaused) {
audioPlayer.unpause()
const pausedTimeStamp = this.pausedTimeStamp - Date.now()
this.timestamp = this.timestamp - pausedTimeStamp
}
this.isPaused = false
}
/**
* Set the volume of the audio player
* @param {number|string} amount The amount of volume you want to set
*/
async setVolume(amount) {
if(!amount) throw new Error('[ Dismusic Error ] Amount must be provided')
const audioResource = this.audioRes
if(!audioResource) throw new Error('[ Dismusic Error ] Could not find audio resources for this guild')
if(Number(amount < 0) || Number(amount > 100)) throw new Error('[ Dismusic Error ] Could not set the volume to lower than 0 or more than 100')
const newAmount = Number(amount) / 100
this.audioRes.volume.setVolume(newAmount)
}
/**
* Set the loop mode of this queue
* @param {string} mode The mode your want to set. Can be `[ 'queue', 'none', 'song' ]`
*/
async setLoopMode(mode) {
const loopModeOptions = [ 'queue', 'none', 'song' ]
if(!loopModeOptions.includes(mode)) throw new Error('[ Dismusic Error ] Loop mode must be one of ' + loopModeOptions.toString() + " got " + mode + " instead")
this.loopMode = mode
}
/**
* Get the current track being played
* @returns {object} The current track being played
*/
async getCurrentTrack() {
const obj = this.tracks[0]
const soFar = Math.floor(Date.now() / 1000) - Math.floor(this.timestamp / 1000)
const time = await getMinute(soFar)
obj.currentTime = time
return obj
}
}
module.exports = QueueBuilder