如何长时间保持打开请求以使用write()方法

时间:2019-06-25 16:09:21

标签: node.js streaming

我需要保持连接打开状态,以便在完成音乐后写入新数据。问题是我的方式是,在第一首歌曲之后,流就停止了。 如何保持连接打开并播放下一首歌曲?

const fs = require('fs');
const express = require('express');
const app = express();
const server = require('http').createServer(app)
const getMP3Duration = require('get-mp3-duration')

let sounds = ['61880.mp3', '62026.mp3', '62041.mp3', '62090.mp3', '62257.mp3', '60763.mp3']

app.get('/current', async (req, res) => {
    let readStream = fs.createReadStream('sounds/61068.mp3')
    let duration = await getMP3Duration(fs.readFileSync('sounds/61068.mp3'))

    let pipe = readStream.pipe(res, {end: false})

    async function put(){
        let file_path = 'sounds/'+sounds[Math.random() * sounds.length-1]

        duration = await getMP3Duration(fs.readFileSync(file_path))

        readStream = fs.createReadStream(file_path)

        readStream.on('data', chunk => {
            console.log(chunk)
            pipe.write(chunk)
        })

        console.log('Current Sound: ', file_path)

        setTimeout(put, duration)
    }

    setTimeout(put, duration)
})

server.listen(3005, async function () {
    console.log('Server is running on port 3005...')
});

3 个答案:

答案 0 :(得分:0)

您应该使用一个库或查看源代码,然后查看它们的作用。 一个不错的是: https://github.com/obastemur/mediaserver

提示:
始终从其他项目中学习来开始您的研究。(可能时或当您没有发明轮子时;))您不是第一个这样做或遇到此问题的人:)

快速搜索短语“ nodejs流mp3 github”给了我一些指示。 祝你好运!

答案 1 :(得分:0)

Express通过对单个请求返回单个响应来工作。发送请求后,需要立即生成一个新请求以触发新响应。

根据您的情况,您希望继续从单个请求中生成新的响应。

可以使用两种方法来解决您的问题:

  1. 更改创建响应的方式以满足用例。
  2. 使用即时通信框架(websocket)。我想到的最好最简单的方法是socket.io

适应快递

这里的解决方案是遵循以下步骤:

  1. 端点/current上的请求进入
  2. 音频序列已准备好
  3. 返回整个序列的流

所以您的处理程序看起来像这样:

const fs = require('fs');
const express = require('express');
const app = express();
const server = require('http').createServer(app);
// Import the PassThrough class to concatenate the streams
const { PassThrough } = require('stream');
// The array of sounds now contain all the sounds
const sounds = ['61068.mp3','61880.mp3', '62026.mp3', '62041.mp3', '62090.mp3', '62257.mp3', '60763.mp3'];


// function which concatenate an array of streams
const concatStreams = streamArray => {
  let pass = new PassThrough();
  let waiting = streamArray.length;
  streamArray.forEach(soundStream => {
    pass = soundStream.pipe(pass, {end: false});
    soundStream.once('end', () => --waiting === 0 && pass.emit('end'));
  });
  return pass;
};

// function which returns a shuffled array
const shuffle = (array) => {
  const a = [...array]; // shallow copy of the array
  for (let i = a.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    [a[i], a[j]] = [a[j], a[i]];
  }
  return a;
};


server.get('/current', (req, res) => {
  // Start by shuffling the array
  const shuffledSounds = shuffle(sounds);

  // Create a readable stream for each sound
  const streams = shuffledSounds.map(sound => fs.createReadStream(`sounds/${sound}`));

  // Concatenate all the streams into a single stream
  const readStream = concatStreams(streams);

  // This will wait until we know the readable stream is actually valid before piping
  readStream.on('open', function () {
    // This just pipes the read stream to the response object (which goes to the client)
    // the response is automatically ended when the stream emits the "end" event
    readStream.pipe(res);
  });
});

请注意,该函数不再需要async关键字。该过程仍然是异步的,但编码基于发射器而不是基于承诺。

如果您想循环播放声音,则可以创建随机播放/映射以进行流/串联的其他步骤。

为了简单起见,我没有包括socketio替代项。

答案 2 :(得分:0)

几次修改后的最终解决方案

我怀疑您的主要问题是随机数组元素生成器。您需要用Math.floor包装您的内容,以四舍五入以确保得到整数:

sounds[Math.floor(Math.random() * sounds.length)]

此外,Readstream.pipe返回目的地,所以您在做什么是有道理的。但是,通过管道进行读取后,通过调用on('data')可能会得到意想不到的结果。 node.js streams docs提到了这一点。我在本地计算机上测试了您的代码,这似乎不是问题,但更改此设置可能很有意义,这样以后您就不会再遇到问题了。

  

选择一种API样式

     

Readable流API跨多个Node.js版本发展,并提供了多种使用流数据的方法。通常,开发人员应选择一种使用数据的方法,并且切勿使用多种方法从单个流中使用数据。具体来说,结合使用on('data'),on('read'),pipe()或异步迭代器可能会导致不直观的行为。

我不会再调用on('data')res.write,而是将它从readStream再次传送到res中。另外,除非您真的想要获得持续时间,否则我将拉出该库并仅使用readStream.end事件对put()进行其他调用。之所以起作用,是因为您在管道传递时传递了false选项,该选项将禁用写入流上的默认end事件功能并使其保持打开状态。但是,它仍然会发出,因此您可以将其用作标记,以了解可读文件何时完成管道传递。这是重构的代码:

const fs = require('fs');
const express = require('express');
const app = express();
const server = require('http').createServer(app)
//const getMP3Duration = require('get-mp3-duration') no longer needed

let sounds = ['61880.mp3', '62026.mp3', '62041.mp3', '62090.mp3', '62257.mp3', '60763.mp3']

app.get('/current', async (req, res) => {
    let readStream = fs.createReadStream('sounds/61068.mp3')
    let duration = await getMP3Duration(fs.readFileSync('sounds/61068.mp3'))

    let pipe = readStream.pipe(res, {end: false})

    function put(){
        let file_path = 'sounds/'+sounds[Math.floor(Math.random() * sounds.length)]

        readStream = fs.createReadStream(file_path)

        // you may also be able to do readStream.pipe(res, {end: false})
        readStream.pipe(pipe, {end: false})

        console.log('Current Sound: ', file_path)

        readStream.on('end', () => {
            put()
        });
    }

    readStream.on('end', () => {
        put()
    });
})

server.listen(3005, async function () {
    console.log('Server is running on port 3005...')
});