How can I record voice from a VC using discord.js?

I want to record voice from a specific voice channel, I already tried to do it many times but didn't find the solution, I either get a slowed down .pcm audio, an unaudible audio, an audio that progressively slowes down or a 1 second long static sound audio. I'm on Node.js v20.12.2, also I'm coding in TypeScript
132 Replies
d.js toolkit
d.js toolkit4mo ago
- What's your exact discord.js npm list discord.js and node node -v version? - Not a discord.js issue? Check out #other-js-ts. - Consider reading #how-to-get-help to improve your question! - Explain what exactly your issue is. - Post the full error stack trace, not just the top part! - Show your code! - Issue solved? Press the button!
ayman
aymanOP4mo ago
I already looked at it and implemented the code to my bot and it just gave me a bunch of .ogg files of short static sounds, i even cloned the "recorder" folder in that repository and still got the same issue
pat
pat4mo ago
what's your original code for trying to get it in pcm & fwiw recorder worksonmymachine make sure you have encryption libraries for decrypting the data i guess? & opus libraries for decoding opus
const { generateDependencyReport } = require('@discordjs/voice');

console.log(generateDependencyReport());
const { generateDependencyReport } = require('@discordjs/voice');

console.log(generateDependencyReport());
ayman
aymanOP4mo ago
this was my very original code trying to make the recording happen through a slash command: https://sourceb.in/ySomvmqOES similar to the voice-example in the discordjs github repository but without creating a different function
ayman
aymanOP4mo ago
in that code the "pipeline" function popped up a type error tho (the link to that error is in the code of the link ive provided you), but i ignored it and still ran the code, and it just created a bunch of pcm files and when i listened to them raw on audacity they were just very short static sounds
No description
pat
pat4mo ago
how about deps then
ayman
aymanOP4mo ago
what?
pat
pat4mo ago
this
ayman
aymanOP4mo ago
also didn't understand what you tried to say here oh yeah im installing them i just have sodium left
pat
pat4mo ago
& for what its worth recorder works on my machine you dont need them all - only one
ayman
aymanOP4mo ago
then i have all of them installed except sodium like it works for you?
pat
pat4mo ago
yes and i get proper files that i can open and hear my voice in no static
ayman
aymanOP4mo ago
but what code have you used? like your own or the one from the voice-examples repo
pat
pat4mo ago
exactly the one from voice-examples/recorder
ayman
aymanOP4mo ago
damn
pat
pat4mo ago
works with no changes
ayman
aymanOP4mo ago
with the dependecies installed right
pat
pat4mo ago
i'd hope they were installed otherwise i'd be decrypting packets from thin air
ayman
aymanOP4mo ago
😭 so its okay if i don't have sodium installed?
pat
pat4mo ago
yes sodiums a pain in the ass to install anyways
ayman
aymanOP4mo ago
fr i see that it told me to change thee version of visual studio and it didn't let me i still have the same problem, even if im using the same code as in the repository :monkaStop: i installed the same dependecies in that repo code it creates a bunch of ogg or pcm files (yes i tried both extensions) and all of them are either static or just some weird sound
pat
pat4mo ago
are you able to send one here out of curiosity
ayman
aymanOP4mo ago
one of the audio files?
pat
pat4mo ago
yes
ayman
aymanOP4mo ago
yeah
ayman
aymanOP4mo ago
wait what the ogg
pat
pat4mo ago
and if you play that second one it works for you? what os are you using
ayman
aymanOP4mo ago
windows 10
pat
pat4mo ago
and what are you using to open the file
ayman
aymanOP4mo ago
audacity
pat
pat4mo ago
i think the issue is you are missing a codec to properly play the audio
ayman
aymanOP4mo ago
wdym
pat
pat4mo ago
like to play the audio on your machine you need a codec to properly understand the data in the files
ayman
aymanOP4mo ago
may i ask, what is a codec? 😭
pat
pat4mo ago
tldr; translates the audio into something you can listen to
ayman
aymanOP4mo ago
oohh i see so i should convert it to mp3? it would be audible
pat
pat4mo ago
the ogg file you sent
ayman
aymanOP4mo ago
yes
pat
pat4mo ago
looks like this in audacity for me
No description
pat
pat4mo ago
is that right?
ayman
aymanOP4mo ago
wait ima check
ayman
aymanOP4mo ago
..
No description
pat
pat4mo ago
lol
ayman
aymanOP4mo ago
maybe because i imported as raw data
pat
pat4mo ago
if you just drag it in is it correct that's what i did
ayman
aymanOP4mo ago
oh yeah i opened as a normal audio file and it looked like it looked to you
pat
pat4mo ago
and it plays correctly?
ayman
aymanOP4mo ago
yeah plays like on discord
pat
pat4mo ago
okay so its not a problem with a codec it's that you are trying to read it incorrectly by opening as raw lol
ayman
aymanOP4mo ago
yeah 💀 and i guess for the file mass creation i should just place the createWriteStream outside of the speaking.on event listener
pat
pat4mo ago
what oh you want longer audio files
ayman
aymanOP4mo ago
yeah
pat
pat4mo ago
increase the duration in end
ayman
aymanOP4mo ago
that was just a portion of what i said in the voice call
pat
pat4mo ago
when u stop speaking it will wait 100 ms before ending the audio stream
ayman
aymanOP4mo ago
oh yeah
pat
pat4mo ago
if u increase it to like 2000 u have 2 seconds before it cuts off
ayman
aymanOP4mo ago
yeah i see is it bad if i set the end behavior to manual?
pat
pat4mo ago
no idea what it entails honestly
ayman
aymanOP4mo ago
because i wanted to do this feature for moderation and i prefer recording the whole voice call until its stopped manually
pat
pat4mo ago
theoretically the opus stream should never end if you do that
ayman
aymanOP4mo ago
yeah i have to call the .destroy function i suppose
pat
pat4mo ago
yea just chuck a opusStream.destroy() when ur done i guess
ayman
aymanOP4mo ago
yea
ayman
aymanOP4mo ago
for this error i should just place "as any" on the needed parameters bc i really dont know what is its problem
No description
ayman
aymanOP4mo ago
thats my bot's source code not the voice-examples one
pat
pat4mo ago
if you compile does it still show that error
ayman
aymanOP4mo ago
no its just a type error from typescript
pat
pat4mo ago
whats your typescript version
ayman
aymanOP4mo ago
the latest one wait 5.5.4 is that even the latest
pat
pat4mo ago
weird, i have 5.7.0-dev and 4.9.5 and it doesn't show that
ayman
aymanOP4mo ago
you have two versions? i will try installing one of them maybe its something they have fixed on the dev version
pat
pat4mo ago
well kinda, vscode version and globally installed version can just change it with ctrl shift p
ayman
aymanOP4mo ago
oh okay uh also one question are you using the latest versions of @discordjs/voice and @discordjs/opus? or the same ones in the repository
pat
pat4mo ago
if by latest you mean dev version yes
ayman
aymanOP4mo ago
i meant this 🤓
No description
pat
pat4mo ago
types shouldnt be different either way
ayman
aymanOP4mo ago
yeah i guess
pat
pat4mo ago
¯\_(ツ)_/¯
pat
pat4mo ago
No description
ayman
aymanOP4mo ago
i see
pat
pat4mo ago
tired of type errors
ayman
aymanOP4mo ago
pat is there any way you could send me here an example of your recordings? to see what it would sound like dont use your voice if u want just do fart sounds with yo mouth
pat
pat4mo ago
💀
ayman
aymanOP4mo ago
thats what i did i wanna compare both audios it can be short if you don't wanna do it just let me know tho its fine nevermind.
ayman
aymanOP4mo ago
it now records, but im getting a warning of max listeners and it suddenly stops the recording: https://sourceb.in/Aat4yDAnQv
SourceBin
memory leak
Instantly share your code with the world.
ayman
aymanOP4mo ago
it happens only when i talk
pat
pat4mo ago
if (receiver.subscriptions.has(userId)) return;
ayman
aymanOP4mo ago
oh it worked, thank you uh also, when i stop the recording by myself, i get a premature close error from the pipeline function is there any way to prevent that?
pat
pat4mo ago
does it look like this
node:internal/process/promises:288
triggerUncaughtException(err, true /* fromPromise */);
^

Error [ERR_STREAM_PREMATURE_CLOSE]: Premature close
at new NodeError (node:internal/errors:393:5)
at AudioReceiveStream.onclose (node:internal/streams/end-of-stream:142:30)
at AudioReceiveStream.emit (node:events:525:35)
at emitCloseNT (node:internal/streams/destroy:132:10)
at process.processTicksAndRejections (node:internal/process/task_queues:81:21) {
code: 'ERR_STREAM_PREMATURE_CLOSE'
}
node:internal/process/promises:288
triggerUncaughtException(err, true /* fromPromise */);
^

Error [ERR_STREAM_PREMATURE_CLOSE]: Premature close
at new NodeError (node:internal/errors:393:5)
at AudioReceiveStream.onclose (node:internal/streams/end-of-stream:142:30)
at AudioReceiveStream.emit (node:events:525:35)
at emitCloseNT (node:internal/streams/destroy:132:10)
at process.processTicksAndRejections (node:internal/process/task_queues:81:21) {
code: 'ERR_STREAM_PREMATURE_CLOSE'
}
ayman
aymanOP4mo ago
yes
ayman
aymanOP4mo ago
No description
ayman
aymanOP4mo ago
it comes from there
No description
pat
pat4mo ago
when are you ending it
ayman
aymanOP4mo ago
when i run the "stop" subcommand wait
pat
pat4mo ago
are you piping it into the file stream in that stop command or still in the start speaking part
ayman
aymanOP4mo ago
in the start speaking part you want the whole command code so you can see?
pat
pat4mo ago
naw just try the former piping it after you are done with the stream instead
ayman
aymanOP4mo ago
i have the "pipeline" function in the "start" subcommand part and i destroy the opus, out streams and the voice connection aswell at the "stop" subcommand part when im done with the ogg stream?
pat
pat4mo ago
pipe after you destroy the voice stream sry the file stream part was wrong i meant the ogg stream audio > ogg > file
ayman
aymanOP4mo ago
wait so after running "VoiceConnection.destroy()" i should pipe the file?
pat
pat4mo ago
the audioreceivestream opusstream
ayman
aymanOP4mo ago
so i should place the
pipeline(audio_stream, ogg_stream , output, (err: NodeJS.ErrnoException | null) => {
if (err) console.error(`${Fg.makeRed("[STREAM_PIPELINE_ERROR]")} error recording file ${makeItalic(file_name_ex)} -`, err);
else console.info(`${Fg.makeGreen("[STREAM_PIPELINE_SUCCESS]")} recorded ${makeItalic(file_name_ex)}`);
});
pipeline(audio_stream, ogg_stream , output, (err: NodeJS.ErrnoException | null) => {
if (err) console.error(`${Fg.makeRed("[STREAM_PIPELINE_ERROR]")} error recording file ${makeItalic(file_name_ex)} -`, err);
else console.info(`${Fg.makeGreen("[STREAM_PIPELINE_SUCCESS]")} recorded ${makeItalic(file_name_ex)}`);
});
after the connection.destroy() function audio_stream is the opus stream output is the write stream
pat
pat4mo ago
try it, should work but i am doing this from reading code not actual experience
ayman
aymanOP4mo ago
alrighty nah it still gave me the same error, but its fine thank you for trying i will look for a solution if i find one
pat
pat4mo ago
@eman this looks awful but
const receiver = connection.receiver;

for (const [k, v] of receiver.subscriptions) {
v.push(null);

const oggStream = new prism.opus.OggLogicalBitstream({
opusHead: new prism.opus.OpusHead({
channelCount: 2,
sampleRate: 48000,
}),
pageSizeControl: {
maxPackets: 10,
},
});

const filename = `./recordings/${Date.now()}-${k}.ogg`;

const out = createWriteStream(filename);

pipeline(v, oggStream, out, (err) => {
if (err) {
console.warn(`❌ Error recording file ${filename} - ${err.message}`);
} else {
console.log(`✅ Recorded ${filename}`);
}
});
}
const receiver = connection.receiver;

for (const [k, v] of receiver.subscriptions) {
v.push(null);

const oggStream = new prism.opus.OggLogicalBitstream({
opusHead: new prism.opus.OpusHead({
channelCount: 2,
sampleRate: 48000,
}),
pageSizeControl: {
maxPackets: 10,
},
});

const filename = `./recordings/${Date.now()}-${k}.ogg`;

const out = createWriteStream(filename);

pipeline(v, oggStream, out, (err) => {
if (err) {
console.warn(`❌ Error recording file ${filename} - ${err.message}`);
} else {
console.log(`✅ Recorded ${filename}`);
}
});
}
ayman
aymanOP4mo ago
yes?
pat
pat4mo ago
however, some weird side effects; no silence is recorded & it wont console log so if you speak, wait 5 seconds and then speak again it will be as if you didnt leave any gap at all
ayman
aymanOP4mo ago
i don't understand what are you trying to tell me?
pat
pat4mo ago
that code works & doesn't get premature end error to save w/ manual end behavior
ayman
aymanOP4mo ago
using it with a for loop?
pat
pat4mo ago
for loop doesn't matter; that would just give you all active subscriptions the stream.push(null) is the difference here
ayman
aymanOP4mo ago
aahh "v" is the opus stream?
pat
pat4mo ago
yes
ayman
aymanOP4mo ago
but what end behavior manual?
pat
pat4mo ago
yes
ayman
aymanOP4mo ago
alr alr let me try this should i add the if (receiver.subscriptions.has(user_id)) return; to it?
pat
pat4mo ago
well the idea for that one was when a user starts speaking you don't want to save multiple files
ayman
aymanOP4mo ago
what but is that code inside the speaking.on event? i managed to only save one file with the event
pat
pat4mo ago
...edited out bc i was wrong incase you are reading this in the future... receiver.speaking.on('start', userId => { receiver.subscribe(userId, { end: { behavior: EndBehaviorType.Manual } }); });
ayman
aymanOP4mo ago
const receiver = connection.receiver;

receiver.speaking.on('start', receiver.subscribe)

for (const [k, v] of receiver.subscriptions) {
v.push(null);

const oggStream = new prism.opus.OggLogicalBitstream({
opusHead: new prism.opus.OpusHead({
channelCount: 2,
sampleRate: 48000,
}),
pageSizeControl: {
maxPackets: 10,
},
});

const filename = `./recordings/${Date.now()}-${k}.ogg`;

const out = createWriteStream(filename);

pipeline(v, oggStream, out, (err) => {
if (err) {
console.warn(`❌ Error recording file ${filename} - ${err.message}`);
} else {
console.log(`✅ Recorded ${filename}`);
}
});
}
const receiver = connection.receiver;

receiver.speaking.on('start', receiver.subscribe)

for (const [k, v] of receiver.subscriptions) {
v.push(null);

const oggStream = new prism.opus.OggLogicalBitstream({
opusHead: new prism.opus.OpusHead({
channelCount: 2,
sampleRate: 48000,
}),
pageSizeControl: {
maxPackets: 10,
},
});

const filename = `./recordings/${Date.now()}-${k}.ogg`;

const out = createWriteStream(filename);

pipeline(v, oggStream, out, (err) => {
if (err) {
console.warn(`❌ Error recording file ${filename} - ${err.message}`);
} else {
console.log(`✅ Recorded ${filename}`);
}
});
}
like that?
pat
pat4mo ago
nevermind, you have to expand it, sad if you have a start+end command, you would subscribe in the start and the for loop in the end command, it will handle ending the streams and creating the files
ayman
aymanOP4mo ago
wait so this i have to put it in the end command:
for (const [k, v] of receiver.subscriptions) {
v.push(null);

const oggStream = new prism.opus.OggLogicalBitstream({
opusHead: new prism.opus.OpusHead({
channelCount: 2,
sampleRate: 48000,
}),
pageSizeControl: {
maxPackets: 10,
},
});

const filename = `./recordings/${Date.now()}-${k}.ogg`;

const out = createWriteStream(filename);

pipeline(v, oggStream, out, (err) => {
if (err) {
console.warn(`❌ Error recording file ${filename} - ${err.message}`);
} else {
console.log(`✅ Recorded ${filename}`);
}
});
}
for (const [k, v] of receiver.subscriptions) {
v.push(null);

const oggStream = new prism.opus.OggLogicalBitstream({
opusHead: new prism.opus.OpusHead({
channelCount: 2,
sampleRate: 48000,
}),
pageSizeControl: {
maxPackets: 10,
},
});

const filename = `./recordings/${Date.now()}-${k}.ogg`;

const out = createWriteStream(filename);

pipeline(v, oggStream, out, (err) => {
if (err) {
console.warn(`❌ Error recording file ${filename} - ${err.message}`);
} else {
console.log(`✅ Recorded ${filename}`);
}
});
}
and this i have to put it in the start command:
const receiver = connection.receiver;

receiver.speaking.on('start', userId => { receiver.subscribe(userId, { end: { behavior: EndBehaviorType.Manual } }); });
const receiver = connection.receiver;

receiver.speaking.on('start', userId => { receiver.subscribe(userId, { end: { behavior: EndBehaviorType.Manual } }); });
pat
pat4mo ago
nah reread the message i sent i edited it because it was wrong
ayman
aymanOP4mo ago
oh yeah i knew that i supposed that you wrote receiver.speaking.on('start', receiver.subscribe) to write it faster
pat
pat4mo ago
💀 totally but yeah I tested using stream.push(null) and it saved to a file when I ran it
ayman
aymanOP4mo ago
so I should do that and this
pat
pat4mo ago
yes
ayman
aymanOP4mo ago
ive figured out that i get that error when I manually stop the opus stream i guess because i set the end behavior to aftersilence and it actually worked and didn't give me any error also i don't think i will be implementing the code you've gave me because it would change the whole structure of mines and i don't really want that, sorry about that, appreciate your help tho also that error does not really affect the audio so if i don't find a solution for it i would just ignore that specific error tbh
pat
pat4mo ago
alright
ayman
aymanOP4mo ago
Uh i guess now my original problem is now solved, im now closing this post Thank you so much for your help pat and qjuh too
Want results from more Discord servers?
Add your server