Kaushik Sivaprasad
Kaushik Sivaprasad

Reputation: 25

stream audio from nodejs to html5 audio tag

I have multiple issues trying to stream an audio from nodejs server realtime to multiple html clients (that might be connected at later times as well). What I do in the code below, is load 2 mp3 files, in a bufferArray and then when the clients have connected, i keep dequeuing from bufferArray and then throttle at 16384 bytes/sec ~ 128kbits/sec and write it to the clients. But i feel that the buffers are emptied pretty quickly. Is my approach correct. Basically i should ensure all the clients should be playing that same part of the song. And first of all none of my clients are able to play the songs, that is another problem.

Any help would be much appreciated.

var http = require('http');
var fs = require("fs");
var url = require('url');
var stream = require('stream');
var Throttle = require('throttle');
var bufferArray = [];
var clients = [];
var entry = true;
setInterval(function () {
  if(bufferArray.length > 0 && clients.length > 0){
    entry = false;
    var buffer = bufferArray.shift();
    var bufferStream = new stream.PassThrough();
    var throttledStream = new stream.PassThrough();
    var throttle = new Throttle(16384);
    bufferStream.end(new Buffer(buffer));
    bufferStream.pipe(throttle).pipe(throttledStream);
    throttledStream.on('data',function(data){
      console.log("Going to write to all the clients "+clients.length);
      for(var i = 0; i < clients.length; i++){
          clients[i].write(data);
      }
    });
    throttledStream.on('end',function(){
      console.log("finished the buffer. Still have to write tot "+bufferArray.length+" buffers");
      entry = true;
    });
  }
},1);
function readMp3FilesInBuffer(songName,callback){
    var fd = fs.createReadStream("./songs/"+songName);
    fd.on('data',function(chunk){
      bufferArray.push(chunk);
    });
    fd.on('end',callback);
}

readMp3FilesInBuffer("FeelOfLove.mp3",function(){
  readMp3FilesInBuffer("ILoveAfrica.mp3",function () {
    console.log("successfully read the songs..");
    http.createServer(function (request, response) {
      var parsedUrl = url.parse(request.url,true);
      var requestType = parsedUrl.query.requestType;
      if(requestType == "renderHtml"){
        console.log("got a request to render html");
        response.writeHead(200, {
           'Content-Type': 'text/html'
       });
        var htmlStream = fs.createReadStream("./views/audioStreaming.html");
        htmlStream.pipe(response);
      }
      else if (requestType === "stream") {
        response.writeHead(200,{
            "Content-Type": "audio/mpeg",
            'Transfer-Encoding': 'chunked'
        });
        clients.push(response);
        console.log("got a request to stream. Tot clients available : "+clients.length);
      }
      else{
        response.end();
      }
    }).listen(8081,function () {
      console.log("listening");
    });

  });
});

Upvotes: 1

Views: 2617

Answers (1)

Brad
Brad

Reputation: 163262

There's quite a bit of code here, and quite a bit of missing information... but there's also quite a few things worth pointing out that might be the source of your specific problems.

You mentioned clients can't play. Are they ending up in the right route to use your stream? Are they getting the correct headers and data? If not, fix that first.

Only about a third of the streaming media audio players out there actually handle chunked encoding correctly. If you're in-browser (actually on a web page) you'll usually be okay, but outside of that, chunked encoding is a non-starter. You need to force Node.js to skip chunked encoding altogether.

You didn't indicate which throttle module you were using. Is it TooTallNate's by chance? If so, I've had trouble with that module in the past. It hasn't been updated recently, and probably doesn't play nice with modern Node.js "streams3".

In any case, the way you're doing throttling isn't appropriate. While your average bitrate may be 128k, it's not necessarily going to be exactly 128k. You shouldn't assume the bitrate. You could write your own code to read MP3 frame headers and synchronize accordingly, but why do that when it's already been done elsewhere? I would fire up FFmpeg as a child process, and have it run in realtime. Untested, but something like this:

ffmpeg -re -i yourfile.mp3 -map_metadata -1 -acodec copy -f mp3 -

In addition to throttling based on the actual media, this has the benefit of removing all the random ID3 tags and other random crap that software like iTunes likes to embed in MP3, giving you a nice clean stream, with very little overhead.

The next problem you're going to have is that your clients are going to take a long time to buffer and start playback. Having a sizable buffer ready to go to flush to them as soon as they connect solves that problem.

Many browsers (like Chrome) will make range requests, trying to treat your stream like any other MP3 file. If you simply ignore those range requests like you're doing, all will work just fine. You might consider handling them though, based on the specifics of what you're doing.

At the end of the day, you can get this working but there is a lot involved, and a lot of little tweaks to make the stream work for all of your listeners. Might I suggest leaving the stream serving up to something that already does this, like Icecast? You can still source the streams in Node.js, allowing you to tie in any application logic you want around the source of the stream. A bit of self promotion... I have some Node.js code I can license to you for connecting to Icecast. I also have a CDN you can stream to directly from your Node.js app. You can check it out at https://audiopump.co or e-mail me at [email protected]. Even if you decide to do it all yourself though, do consider sourcing the stream from your Node.js app and serving from something else.

Upvotes: 2

Related Questions