'FFmpeg | Error: spawn EAGAIN making the bot crash
I developed a Discord Text to Speech bot using Discord.js and Google Cloud Text to Speech API. Everything was normal, until yesterday morning. Since then, this error has just appeared randomly among the day.
2022-02-04T05:40:27.231701+00:00 app[worker.1]: Error: spawn /app/node_modules/ffmpeg-static/ffmpeg EAGAIN
2022-02-04T05:40:27.231702+00:00 app[worker.1]: at Process.ChildProcess._handle.onexit (node:internal/child_process:282:19)
2022-02-04T05:40:27.231702+00:00 app[worker.1]: at onErrorNT (node:internal/child_process:477:16)
2022-02-04T05:40:27.231703+00:00 app[worker.1]: at processTicksAndRejections (node:internal/process/task_queues:83:21)
2022-02-04T05:40:27.231703+00:00 app[worker.1]: Emitted 'error' event on ChildProcess instance at:
2022-02-04T05:40:27.231703+00:00 app[worker.1]: at Process.ChildProcess._handle.onexit (node:internal/child_process:288:12)
2022-02-04T05:40:27.231704+00:00 app[worker.1]: at onErrorNT (node:internal/child_process:477:16)
2022-02-04T05:40:27.231704+00:00 app[worker.1]: at processTicksAndRejections (node:internal/process/task_queues:83:21) {2022-02-04T05:40:27.231705+00:00 app[worker.1]: errno: -11,
2022-02-04T05:40:27.231705+00:00 app[worker.1]: code: 'EAGAIN',
2022-02-04T05:40:27.231706+00:00 app[worker.1]: syscall: 'spawn /app/node_modules/ffmpeg-static/ffmpeg',
2022-02-04T05:40:27.231706+00:00 app[worker.1]: path: '/app/node_modules/ffmpeg-static/ffmpeg',
2022-02-04T05:40:27.231706+00:00 app[worker.1]: spawnargs: [
2022-02-04T05:40:27.231707+00:00 app[worker.1]: '-i', '-',
2022-02-04T05:40:27.231707+00:00 app[worker.1]: '-analyzeduration', '0',
2022-02-04T05:40:27.231707+00:00 app[worker.1]: '-loglevel', '0',
2022-02-04T05:40:27.231708+00:00 app[worker.1]: '-f', 's16le',
2022-02-04T05:40:27.231708+00:00 app[worker.1]: '-ar', '48000',
2022-02-04T05:40:27.231708+00:00 app[worker.1]: '-ac', '2',
2022-02-04T05:40:27.231709+00:00 app[worker.1]: 'pipe:1'
2022-02-04T05:40:27.231709+00:00 app[worker.1]: ]
2022-02-04T05:40:27.231709+00:00 app[worker.1]: }
I found the same issue in the ffmpeg-static GitHub, but the solution they gave was re-installing FFmpeg (which I did) and making sure there was just a single instance/process, but as far as I know, I'm just using one single instance into my Text to Speech Reader.
Here is my code:
const { AudioPlayer, createAudioResource, StreamType, entersState, VoiceConnectionStatus, AudioPlayerStatus, joinVoiceChannel } = require('@discordjs/voice');
const { MessageEmbed, Permissions } = require('discord.js');
const Data = require('../tools/dataLoader.js');
const Translate = require('translate-google');
const Duplex = require('stream').Duplex;
class TextReader{
// Constructor.
constructor(){}
// Lectura de Mensaje.
async readMessage(Client, guild){
// Obtenemos los datos de la guild.
const Guild = Data.Guilds.get(guild.id);
// Si hay un proceso de desconexión, la cancelamos.
if(Guild.disconnectionClock){
clearTimeout(Guild.disconnectionClock);
Guild.disconnectionClock = null;
}
let User = Guild.users.get(Guild.queue[0].resource.member.user.id); // <- Usuario autor del mensaje.
let voiceChannel = Guild.queue[0].resource.member.voice.channel; // <- Canal de la conexión.
// Función de salto de mensaje.
function skipMessage(){
Guild.queue.shift();
if(Guild.queue[0]) return Client.TextReader.readMessage(Client, guild);
else return;
}
// Verificamos que haya espacio en el canal de voz.
if(!voiceChannel || voiceChannel.full) return skipMessage();
let voiceConnection = Guild.voiceConnection; // <- Conexión de voz.
let audioPlayer = new AudioPlayer(); // <- Reproductor de audio.
// Stream de audio y recurso de audio.
let audioResource = null;
// Voz e idioma.
let lang = User.language;
let voice = User.voice;
// Error atrapado
let errorCatched = false;
// Intentamos obtener el stream y el recurso.
try{
// La petición de audio a Google
let [ req ] = await Data.TextToSpeech.synthesizeSpeech({
input: { text: Guild.queue[0].text },
voice: { name: voice, languageCode: lang },
audioConfig: {audioEncoding: 'MP3'}
});
// El stream creado en base a la petición de audio.
let bufferedStream = new Duplex();
bufferedStream.push(req.audioContent);
bufferedStream.push(null);
// El recurso de audio creado.
audioResource = await createAudioResource(bufferedStream, { inputType: StreamType.Arbitrary, inlineVolume: true });
}catch(err){
errorCatched = true;
console.error('[ ' + guild.id + ' ] Getting Stream Error: ' + err);
}
// Si no existe una conexión o ésta se encuentra en otro canal.
if(
(!voiceConnection ||
voiceConnection?.status === VoiceConnectionStatus.Disconnected ||
voiceConnection.joinConfig.channelId !== voiceChannel.id) &&
!errorCatched
){
// Si hay un temporizador de dosconexión, lo apagamos.
if( voiceConnection && Guild.voiceConnection && voiceConnection.joinConfig.channelId !== voiceChannel.id ){
Guild.voiceConnection.destroy();
Guild.voiceConnection = null;
}
// Intentamos entrar en el canal de audio.
try{
voiceConnection = await joinVoiceChannel({
channelId: voiceChannel.id,
guildId: guild.id,
adapterCreator: guild.voiceAdapterCreator,
});
}catch(err){
errorCatched = true;
console.error('[ ' + guild.id + ' ] Voice Connection Error: ' + err);
}
// Intentamos crear una conexión.
try{
if(errorCatched) return;
voiceConnection = await entersState(voiceConnection, VoiceConnectionStatus.Connecting, 5_000);
Guild.voiceConnection = voiceConnection;
}catch(err){
// Verificador de error y destrucción de la conexión existente.
errorCatched = true;
if(voiceConnection) try { voiceConnection.destroy() }catch(err){ }
// Si ya se ha enviado un mensaje de error, salimos.
if(Guild.errorAlerts.has(voiceChannel.id)) return;
// Añadimos el canal a un set.
Guild.errorAlerts.add(voiceChannel.id);
setTimeout(() => { Guild.errorAlerts.delete(voiceChannel.id) }, 30000);
// Construcción de mensaje de error.
let error = Object.assign({}, Guild.embeds['fatalError']);
error.description = error.description.replace(/{channel}/g, '<#' + voiceChannel.id + '>');
// Si hay un mensaje al cuál responder, lo hacemos y salimos.
if(Guild.queue[0].resource) Guild.queue[0].resource.reply({
embeds: [ error ]
}).catch(err => {
if(err.toString() !== 'DiscordAPIError: Missing Permissions') console.error('Error Alert Failed: ' + err);
});
}
}
// Si hay una conexión.
if( voiceConnection?.status === VoiceConnectionStatus.Connected && !errorCatched ){
// Intentamos reproducir el audio.
try{
voiceConnection.subscribe(audioPlayer);
audioPlayer.play(audioResource);
}catch(err){
errorCatched = true;
if(voiceConnection) try { voiceConnection.destroy() }catch(err){ }
console.error('[ ' + guild.id + ' ] Playing Audio Error: ' + err);
}
}
// Si hay un error.
if(errorCatched) return skipMessage();
// Verificamos si el audio no se reproduce.
let handler = setInterval(() => {
clearInterval(handler);
audioPlayer.stop({ force: true });
}, 1500);
audioPlayer.on(AudioPlayerStatus.Playing, async () => {
clearInterval(handler);
Data.messagesRead++;
Guild.talking = true;
});
audioPlayer.on(AudioPlayerStatus.Idle, async () => {
// Estado de reproducción.
Guild.talking = false;
// Cuando finalice de leer un mensaje, reiniciamos el timeout.
clearTimeout(Guild.disconnectionClock);
Guild.disconnectionClock = null;
// Si el canal se autolimpia, borramos el mensaje leído.
if(Guild.queue[0] && Guild.queue[0].type === 'MESSAGE'){
let Channel = Guild.channels.get(Guild.queue[0].resource.channel.id);
if(Guild.cleanupAll || Channel.cleanup){
Guild.queue[0].resource.delete().catch(err => {
if(err.toString() !== 'DiscordAPIError: Unknown Message')
console.error('[ ' + guild.id + ' ] Queue Message Cleanup Error: ' + err)
});
}
}
// Avanzamos en la cola de mensajes a leer.
Guild.queue.shift();
// Si aún hay cola de mensajes...
if(Guild.queue[0]){
// Emitimos el evento nuevamente.
Client.TextReader.readMessage(Client, guild);
// Si no hay cola de mensajes...
}else{
// Creamos un timeout para desconectar el bot.
Guild.disconnectionClock = setTimeout(() => {
// Si el bot ya ha sido desconectado, regresamos.
if(!Guild.voiceConnection) return;
// Destruimos las conexiones.
Guild.voiceConnection.destroy();
Guild.voiceConnection = null;
}, ( Guild.leavingTimer * 1000 ));
}
});
}
}
module.exports = TextReader;
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|
