等待每个 playStream() 完成,然后再开始读取下一个

Posted

技术标签:

【中文标题】等待每个 playStream() 完成,然后再开始读取下一个【英文标题】:Wait for each playStream() to finish before starting to read the next one 【发布时间】:2020-04-14 10:32:16 【问题描述】:

我有一个 .mp3 格式的音乐库,存储在 Google Drive 文件夹中,其中包含一堆我希望能够一个接一个地播放的音乐文件。我能够单独读取和流式传输每个文件,但是当我尝试将文件夹中的所有文件“排队”并一个接一个地播放它们时,它不会等到一个流(歌曲)播放完毕才能播放下一首,而是立即开始下一首,这会导致整个文件夹中只播放最后一首歌曲。我假设我必须搞乱我之前在 discord.js 开发中所做的 async/await,或者我不熟悉的 Promises 和 Promise.all()。这是代码的相关部分。

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));
        );
    );
);

请注意,我有一个命令让机器人离开频道,所以我的代码中没有任何voiceChannel.leave() 是正常的,因为我不希望它在歌曲播放完后立即离开。

欢迎任何建议,提前谢谢!

【问题讨论】:

【参考方案1】: 您想通过从 Google 云端硬盘中的特定文件夹下载多个 MP3 文件来播放它们。 您已经能够在语音通道播放 MP3 数据并使用 Drive API。 您希望使用 discord.js 和 googleapis 与 Node.js 来实现此目的。

如果我的理解是正确的,那么这个答案呢?请认为这只是几个可能的答案之一。

修改点:

在这个答案中,googleapis下载的MP3文件通过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));
  
);
在此示例脚本中,当所有 MP3 文件完成后,end 将显示在控制台中。

参考资料:

Promise.all() Class: stream.PassThrough Class Method: Buffer.concat(list[, totalLength])

如果我误解了您的问题并且这不是您想要的方向,我深表歉意。

编辑:

在下面的示例脚本中,Google Drive 上特定文件夹中的所有文件都被下载每个文件并与流一起播放。

示例脚本:

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()的函数。 playFiles() 函数
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() 很重要。确认不使用时,下次播放时,有时无法从第 2 个文件收听声音。请注意这一点。

【讨论】:

所以如果我理解正确的话,这会将所有歌曲变成流然后将它们全部连接起来?如果是这样,这不会将所有歌曲都存储在内存中吗?在这种情况下,假设每个流的大小等于其各自歌曲的大小,该应用程序将耗尽 RAM,因为它只有 512MB 可用,我无法增加它。 @Endyxion 感谢您的回复。对于我提出的脚本不适合您的情况,我深表歉意。这是因为我的技术不好。我添加了一个示例脚本,用于使用流播放每个文件。你能确认一下吗?如果这不是您想要的方向,我很抱歉。 @Endyxion 我的回答是否向您展示了您想要的结果?你能告诉我吗?这对我学习也很有用。如果这可行,与您有相同问题的其他人也可以将您的问题作为可以解决的问题。如果您对我的回答有疑问,我深表歉意。到时候,可以问一下你现在的情况吗?我想学习解决你的问题。

以上是关于等待每个 playStream() 完成,然后再开始读取下一个的主要内容,如果未能解决你的问题,请参考以下文章

播放声音,等待它完成然后做点啥?

Promise.all() 不等待异步进程

函数在继续循环之前不等待条件完成

如何等待 openRead 函数在一个文件上完成,然后再转到下一个文件?

多个 SKSpriteNode 的 SpriteKit 多个 SKActions - 等待全部完成

等待来自父母的信号并再次完成工作和阻塞