Use download queue for spotify downloads
This commit is contained in:
parent
a8b52e9192
commit
6226c01ca2
@ -1,7 +1,7 @@
|
||||
import fs from 'node:fs';
|
||||
|
||||
import { ActivityType, ChannelType, Collection, Guild, GuildTextBasedChannel, VoiceChannel } from 'discord.js';
|
||||
import { AudioPlayer, AudioPlayerStatus, AudioResource, PlayerSubscription, VoiceConnection, createAudioPlayer, createAudioResource, joinVoiceChannel } from '@discordjs/voice';
|
||||
import { ActivityType, ChannelType, Collection, Guild, GuildTextBasedChannel } from 'discord.js';
|
||||
import { AudioPlayer, AudioPlayerStatus, AudioResource, PlayerSubscription, VoiceConnection, VoiceConnectionState, VoiceConnectionStatus, createAudioPlayer, createAudioResource, entersState, joinVoiceChannel } from '@discordjs/voice';
|
||||
import { LoggerClient } from '@navy.gif/logger';
|
||||
|
||||
import Initialisable from '../../interfaces/Initialisable.js';
|
||||
@ -56,6 +56,7 @@ class MusicPlayer implements Initialisable
|
||||
this.#currentSong = null;
|
||||
|
||||
this.#player.on(AudioPlayerStatus.Idle, this.playNext.bind(this));
|
||||
this.#player.on('error', (err) => this.#logger.error(err));
|
||||
}
|
||||
|
||||
get ready ()
|
||||
@ -226,13 +227,13 @@ class MusicPlayer implements Initialisable
|
||||
|
||||
this.#shuffleList = this.#library.getShufflePlaylist();
|
||||
|
||||
this.initialiseVoiceChannels();
|
||||
this.#initialiseVoiceChannels();
|
||||
|
||||
this.#ready = true;
|
||||
this.playNext();
|
||||
}
|
||||
|
||||
initialiseVoiceChannels ()
|
||||
#initialiseVoiceChannels ()
|
||||
{
|
||||
for (const config of this.#options.guilds)
|
||||
{
|
||||
@ -243,25 +244,27 @@ class MusicPlayer implements Initialisable
|
||||
this.#logger.warn(`Invalid id pair given: Invalid guild ${id}`);
|
||||
continue;
|
||||
}
|
||||
const channel = guild.channels.resolve(voiceChannel);
|
||||
if (channel?.type !== ChannelType.GuildVoice || !channel.joinable)
|
||||
{
|
||||
this.#logger.warn(`Invalid id pair given: Invalid channel ${id}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!this.joinVoiceChannel(guild, channel, textOutput))
|
||||
if (!this.#joinVoiceChannel(guild, voiceChannel, textOutput))
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
joinVoiceChannel (guild: Guild, channel: VoiceChannel, output?: string)
|
||||
#joinVoiceChannel (guild: Guild, channelId: string, output?: string)
|
||||
{
|
||||
const channel = guild.channels.resolve(channelId);
|
||||
if (channel?.type !== ChannelType.GuildVoice || !channel.joinable)
|
||||
{
|
||||
this.#logger.warn(`Invalid voice channel given for guild ${guild.id}: ${channelId}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
const connection = joinVoiceChannel({
|
||||
channelId: channel.id,
|
||||
guildId: guild.id,
|
||||
adapterCreator: guild.voiceAdapterCreator
|
||||
});
|
||||
|
||||
const subscription = connection.subscribe(this.#player);
|
||||
if (!subscription)
|
||||
{
|
||||
@ -269,6 +272,11 @@ class MusicPlayer implements Initialisable
|
||||
return false;
|
||||
}
|
||||
|
||||
connection.on('error', error => this.#logger.error('Voice connection error:', error));
|
||||
connection.on(VoiceConnectionStatus.Disconnected, (...args) => this.#handleDisconnect(...args, guild));
|
||||
if (this.#player.state.status === AudioPlayerStatus.AutoPaused)
|
||||
this.#player.unpause();
|
||||
|
||||
let textChannel = null;
|
||||
if (output)
|
||||
{
|
||||
@ -284,6 +292,36 @@ class MusicPlayer implements Initialisable
|
||||
return true;
|
||||
}
|
||||
|
||||
async #handleDisconnect (oldState: VoiceConnectionState, newState: VoiceConnectionState, guild: Guild): Promise<void>
|
||||
{
|
||||
this.#logger.debug(`Voice connection in ${guild.name} changed state from ${oldState.status} to ${newState.status}`);
|
||||
const connectionData = this.#connections.get(guild.id);
|
||||
if (!connectionData)
|
||||
return;
|
||||
|
||||
const { connection } = connectionData;
|
||||
try
|
||||
{
|
||||
await Promise.race([
|
||||
entersState(connection, VoiceConnectionStatus.Signalling, 5_000),
|
||||
entersState(connection, VoiceConnectionStatus.Connecting, 5_000)
|
||||
]);
|
||||
}
|
||||
catch
|
||||
{
|
||||
this.#logger.info(`Connection in ${guild.name} was terminated, attempting reconnect`);
|
||||
// this.#logger.error('Promise error:', err as Error);
|
||||
connection.destroy();
|
||||
this.#connections.delete(guild.id);
|
||||
const details = this.#options.guilds.find((conf) => conf.id === guild.id);
|
||||
if (!details)
|
||||
return;
|
||||
this.#joinVoiceChannel(guild, details.voiceChannel, details.textOutput);
|
||||
}
|
||||
// if (newState.status !== VoiceConnectionStatus.Disconnected)
|
||||
// return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default MusicPlayer;
|
@ -7,11 +7,20 @@ import DiscordClient from '../../DiscordClient.js';
|
||||
import path from 'node:path';
|
||||
import MusicPlayerError from '../../../errors/MusicPlayerError.js';
|
||||
|
||||
type QueueEntry = {
|
||||
resolve: (result: DownloaderResult) => void,
|
||||
reject: (error: Error) => void,
|
||||
id: string,
|
||||
url: string
|
||||
}
|
||||
|
||||
class SpotifyDownloader extends Downloader
|
||||
{
|
||||
#executable;
|
||||
#available;
|
||||
#options: string[];
|
||||
#queue: QueueEntry[];
|
||||
#processing: boolean;
|
||||
constructor (client: DiscordClient)
|
||||
{
|
||||
super(client, {
|
||||
@ -25,12 +34,15 @@ class SpotifyDownloader extends Downloader
|
||||
'--download-quality very_high',
|
||||
'--download-real-time true',
|
||||
'--md-allgenres true',
|
||||
'--skip-previously-downloaded true',
|
||||
// '--skip-previously-downloaded true',
|
||||
'--output "{artist} --- {song_name} --- {album}.{ext}"',
|
||||
'--print-downloads true',
|
||||
];
|
||||
if (process.env.CREDENTIAL_PATH)
|
||||
this.#options.push('--credentials-location ' + process.env.CREDENTIAL_PATH);
|
||||
|
||||
this.#queue = [];
|
||||
this.#processing = false;
|
||||
}
|
||||
|
||||
override test (): void | Promise<void>
|
||||
@ -79,36 +91,61 @@ class SpotifyDownloader extends Downloader
|
||||
});
|
||||
}
|
||||
|
||||
this.logger.info(`Queueing song for download: ${url}`);
|
||||
return new Promise((resolve, reject) =>
|
||||
{
|
||||
childProcess.exec(`"${this.#executable}" --root-path "${this.downloadDir}" ${this.#options.join(' ')} ${url}`, (error, stdout, stderr) =>
|
||||
{
|
||||
if (error)
|
||||
return reject(error);
|
||||
|
||||
const data = this.#getSongData(id);
|
||||
if (!data)
|
||||
throw new Error('Failed to find file reference');
|
||||
|
||||
const { fileName, artist, song, album, ext } = data;
|
||||
const filePath = path.join(this.downloadDir!, fileName);
|
||||
|
||||
if (stderr && !stderr.includes('charmap'))
|
||||
this.logger.debug('stderr', stderr);
|
||||
if (stdout)
|
||||
this.logger.debug('stdout', stdout);
|
||||
resolve({
|
||||
filePath,
|
||||
artist,
|
||||
song,
|
||||
album,
|
||||
ext
|
||||
});
|
||||
this.#queue.push({
|
||||
resolve, reject, id, url
|
||||
});
|
||||
this.#processQueue();
|
||||
// child.stdout?.on('data', (chunk => this.logger.debug('child stdout:', chunk)));
|
||||
});
|
||||
}
|
||||
|
||||
async #processQueue ()
|
||||
{
|
||||
if (this.#processing)
|
||||
return;
|
||||
|
||||
const entry = this.#queue.shift();
|
||||
if (!entry)
|
||||
return;
|
||||
this.#processing = true;
|
||||
|
||||
const { resolve, reject, id, url } = entry;
|
||||
this.logger.info(`Processing queue, downloading ${url}`);
|
||||
childProcess.exec(`"${this.#executable}" --root-path "${this.downloadDir}" ${this.#options.join(' ')} ${url}`, (error, stdout, stderr) =>
|
||||
{
|
||||
if (error)
|
||||
return reject(error);
|
||||
|
||||
if (stderr) // && !stderr.includes('charmap')
|
||||
this.logger.debug('stderr', stderr);
|
||||
if (stdout)
|
||||
this.logger.debug('stdout', stdout);
|
||||
|
||||
const data = this.#getSongData(id);
|
||||
if (!data)
|
||||
throw new Error('Failed to find file reference');
|
||||
|
||||
const { fileName, artist, song, album, ext } = data;
|
||||
const filePath = path.join(this.downloadDir!, fileName);
|
||||
|
||||
resolve({
|
||||
filePath,
|
||||
artist,
|
||||
song,
|
||||
album,
|
||||
ext
|
||||
});
|
||||
|
||||
this.#processing = false;
|
||||
if (this.#queue.length)
|
||||
process.nextTick(this.#processQueue);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
#getSongData (id: string)
|
||||
{
|
||||
const songIdsPath = path.join(this.downloadDir!, '.song_ids');
|
||||
|
Loading…
Reference in New Issue
Block a user