I have a music library in .mp3 format stored in a Google Drive folder, with a bunch of music files I want to be able to play one after the other. I am able to read and stream each file individually, but when I try to "queue" all the files from the folder and play them one after the other, it won't wait till one stream (song) is done playing to play the next, and instead starts the next one immediately, which results in only the last song being played out of the entire folder. I'd assume I have to mess around with async/await which I have done earlier in discord.js development, or with Promises and Promise.all(), which I am not familiar with. Here's the relevant part of the code.
var folderId = "'the-folder-id'";
drive.files.list({
q: folderId + " in parents", // to get all the files in the folder
fields: 'files(id)'
}, (err, res) => {
if (err) throw err;
const files = res.data.files;
files.map(file => {
drive.files.get({
fileId: file.id,
alt: 'media'
},
{ responseType: "stream" },
(err, { data }) => {
message.member.voiceChannel.join().then(connection => {
const dispatcher = connection.playStream(data); // doesn't wait for this to finish to play the next stream (song)
}).catch(err => console.log(err));
});
});
});
Note that I have a command to make the bot leave the channel, so it's normal that there isn't any voiceChannel.leave()
in my code, as I don't want it to leave right after the songs have finished playing.
Any advice is welcome, thanks in advance!
If my understanding is correct, how about this answer? Please think of this as just one of several possible answers.
In this answer, the MP3 files downloaded by googleapis are converted to the stream and put to the voice channel with discord.js.
var folderId = "'the-folder-id'";
drive.files.list(
{
q: folderId + " in parents", // to get all the files in the folder
fields: "files(id)"
},
(err, res) => {
if (err) throw err;
const files = res.data.files;
Promise.all(
files.map(file => {
return new Promise((resolve, reject) => {
drive.files.get(
{
fileId: file.id,
alt: "media"
},
{ responseType: "stream" },
(err, { data }) => {
if (err) {
reject(err);
return;
}
let buf = [];
data.on("data", function(e) {
buf.push(e);
});
data.on("end", function() {
const buffer = Buffer.concat(buf);
resolve(buffer);
});
}
);
});
})
)
.then(e => {
const stream = require("stream");
let bufferStream = new stream.PassThrough();
bufferStream.end(Buffer.concat(e));
message.member.voiceChannel
.join()
.then(connection => {
const dispatcher = connection.playStream(bufferStream);
dispatcher.on("end", () => {
// do something
console.log("end");
});
})
.catch(e => console.log(e));
})
.catch(e => console.log(e));
}
);
end
is shown in the console.If I misunderstood your question and this was not the direction you want, I apologize.
In the following sample script, all files in the specific folder on Google Drive are downloaded every one file and that is played with the stream.
var folderId = "'the-folder-id'";
drive.files.list(
{
q: folderId + " in parents",
fields: "files(id,name)"
},
(err, res) => {
if (err) throw err;
const channel = message.member.voiceChannel;
channel
.join()
.then(connection => playFiles(drive, channel, connection, res.data.files))
.catch(e => console.log(e));
}
);
playFiles()
is called from above script.function playFiles(drive, channel, connection, files) {
if (files.length == 0) {
channel.leave();
return;
}
drive.files.get(
{
fileId: files[0].id,
alt: "media"
},
{ responseType: "stream" },
(err, { data }) => {
if (err) throw new Error(err);
console.log(files[0]); // Here, you can see the current playing file at console.
connection
.playStream(data)
.on("end", () => {
files.shift();
playFiles(drive, channel, connection, files);
})
.on("error", err => console.log(err));
}
);
}
channel.leave()
is important. I confirmed that when this is not used, there are the cases that at the next play, the sound cannot be listened from 2nd file. Please be careful this.