Transcoding and streaming audio - how to send content range headers

Quick version: how to send the correct Content-Range headers when you do not know the length of the body?

I have a FLAC file. I want to transcode it to MP3 and immediately transfer it to the user. I have something like this so far:

 function transcode(file) { var spawn = require('child_process').spawn var decode = spawn('flac', [ '--decode', '--stdout', file ]) var encode = spawn('lame', [ '-V0', '-', '-' ]) decode.stdout.pipe(encode.stdin) return encode } var express = require('express') var app = express() app.get('/somefile.mp3', function (req, res) { res.setHeader('Accept-Ranges', 'bytes') res.setHeader('Content-Range', 'bytes') res.setHeader('Content-Type', 'audio/mpeg') transcode(file).stdout.pipe(res) }) 

It works as intended, but it is "streaming", so I can’t miss it. Apparently I need to do Content-Range stuff. Usage: https://github.com/visionmedia/node-range-parser

 function sliceStream(start, writeStream, readStream) { var length = 0 var passed = false readStream.on('data', function (buf) { if (passed) return writeStream.write(buf); length += buf.length if (length < start) return; passed = true writeStream.write(buf.slice(length - start)) }) readStream.on('end', function () { writeStream.end() }) } var parseRange = require('range-parser') app.get('/somefile.mp3', function (req, res) { var ranges = parseRange(Infinity, req.headers['range']) if (ranges === -1 || ranges === -2) return res.send(400); var start = ranges[0].start res.setHeader('Accept-Ranges', 'bytes') res.setHeader('Content-Type', 'audio/mpeg') if (!start) { res.setHeader('Content-Range', 'bytes') transcode(file).stdout.pipe(res) return } res.setHeader('Content-Range', 'bytes ' + start + '-') sliceStream(start, transcode(file).stdout, res) }) 

This is where I am stuck. Since I do not wait for the whole song to be encoded, I do not know the size of the song. Since I just got canceled in Chrome, I assume that the Content-Range header is in the wrong format without size.

Also, I'm just now opening the song in my browser, so I assume that it uses the <audio> element.

Suggestions?

+4
source share
1 answer

Yes, your Content-Range header is in the wrong format without size. However, you can try to send the current size that your server has already transcoded. Although I doubt Chrome will handle resizing gracefully ...

There are a few things you do not process :

  • It seems you are not sending the status of 206 Partial Content (perhaps this is being handled by the library, but not sure).
  • It doesn't look like you are even checking the end of a range request. Normally, Chrome sends nothing but 0- , but other browsers will. In fact, some may send multiple ranges in a single request (royal ass pain for support).
  • You are not sending the proper Content-Range response header, since you are also not including the final index of the content being sent. It should look like this:
    Content-Range: bytes 0-2048/3980841
  • Finally, if the client makes a range request that goes out of bounds, that is, none of the range values ​​overlaps the amount of the resource, the service should respond with 416 Requested Range Not Satisfiable .

Edit: I did not test this specific case, but if you transcoded from FLAC to 192 Kbps CBR MP3, I would suggest that there is only a limited set of features that can happen if you sent a slightly inaccurate content length (off less than per 1000 bit):

  • The very end of the sound will be shot at the end by the player. ~ 1000 bits will copy about 5 ms of audio (not obvious to humans).
  • The browser will ignore the final index or length of the content and simply continue to accept and / or request ranges outside the Content-Range that you answered initially until you close the connection or send status 416.
  • Missing / erroneous end of the sound can cause <audio> throw a MEDIA_ERR_NETWORK or MEDIA_ERR_DECODE , which you just have to handle gracefully. (In this case, the sound will still be clipped.)
+6
source