ogg
Version:
Node.js native binding to libogg
127 lines (108 loc) • 3.4 kB
JavaScript
/**
* Module dependencies.
*/
var debug = require('debug')('ogg:decoder');
var binding = require('./binding');
var inherits = require('util').inherits;
var Writable = require('stream').Writable;
var DecoderStream = require('./decoder-stream');
// node v0.8.x compat
if (!Writable) Writable = require('readable-stream/writable');
/**
* Module exports.
*/
module.exports = Decoder;
/**
* The ogg `Decoder` class. Write an OGG file stream to it, and it'll emit
* "stream" events for each embedded stream. The DecoderStream instances emit
* "packet" events with the raw `ogg_packet` instance to send to an ogg stream
* decoder (like Vorbis, Theora, etc.).
*
* @param {Object} opts Writable stream options
* @api public
*/
function Decoder (opts) {
if (!(this instanceof Decoder)) return new Decoder(opts);
Writable.call(this, opts);
this.oy = new Buffer(binding.sizeof_ogg_sync_state);
var r = binding.ogg_sync_init(this.oy);
if (0 !== r) {
throw new Error('ogg_sync_init() failed: ' + r);
}
}
inherits(Decoder, Writable);
/**
* Writable stream base class `_write()` callback function.
*
* @param {Buffer} chunk
* @param {Function} done
* @api private
*/
Decoder.prototype._write = function (chunk, encoding, done) {
debug('_write(%d bytes)', chunk.length);
// XXX: compat for old Writable API... remove at some point...
if ('function' == typeof encoding) done = encoding;
// allocate space for 1 `ogg_page`
// XXX: we could do this at the per-decoder level, since only 1 ogg_page is
// active (being processed by an ogg decoder) at a time
var stream;
var self = this;
var oy = this.oy;
var page = new Buffer(binding.sizeof_ogg_page);
binding.ogg_sync_write(oy, chunk, chunk.length, afterWrite);
function afterWrite (rtn) {
debug('after _write(%d)', rtn);
if (0 === rtn) {
pageout();
} else {
done(new Error('ogg_sync_write() error: ' + rtn));
}
}
function pageout () {
debug('pageout()');
page.serialno = null;
page.packets = null;
binding.ogg_sync_pageout(oy, page, afterPageout);
}
function afterPageout (rtn, serialno, packets) {
debug('afterPageout(%d, %d, %d)', rtn, serialno, packets);
if (1 === rtn) {
// got a page, now write it to the appropriate DecoderStream
page.serialno = serialno;
page.packets = packets;
self.emit('page', page);
stream = self._stream(serialno);
stream.pagein(page, packets, afterPagein);
} else if (0 === rtn) {
// need more data
done();
} else {
// something bad...
done(new Error('ogg_sync_pageout() error: ' + rtn));
}
}
function afterPagein (err) {
debug('afterPagein(%s)', err);
if (err) return done(err);
// attempt to read out the next page from the `ogg_sync_state`
pageout();
}
};
/**
* Gets an DecoderStream instance for the given "serialno".
* Creates one if necessary, and then emits a "stream" event.
*
* @param {Number} serialno The serial number of the ogg_stream.
* @return {DecoderStream} an DecoderStream for the given serial number.
* @api private
*/
Decoder.prototype._stream = function (serialno) {
debug('_stream(%d)', serialno);
var stream = this[serialno];
if (!stream) {
stream = new DecoderStream(serialno);
this[serialno] = stream;
this.emit('stream', stream);
}
return stream;
};