148 lines
3.4 KiB
JavaScript
148 lines
3.4 KiB
JavaScript
|
|
|
||
|
|
/**
|
||
|
|
* Module dependencies.
|
||
|
|
*/
|
||
|
|
|
||
|
|
var assert = require('assert');
|
||
|
|
var Parser = require('stream-parser');
|
||
|
|
var stringify = require('./stringify');
|
||
|
|
var inherits = require('util').inherits;
|
||
|
|
var Transform = require('stream').Transform;
|
||
|
|
var debug = require('debug')('icecast:writer');
|
||
|
|
|
||
|
|
// for node v0.8.x support, remove after v0.12.x
|
||
|
|
if (!Transform) Transform = require('readable-stream/transform');
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Module exports.
|
||
|
|
*/
|
||
|
|
|
||
|
|
module.exports = Writer;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* The value of the metabyte on each pass is
|
||
|
|
* "ceil(metadata.length / META_BLOCK_SIZE)".
|
||
|
|
*
|
||
|
|
* @api private
|
||
|
|
*/
|
||
|
|
|
||
|
|
var META_BLOCK_SIZE = 16;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* The maximum byte length of a metadata chunk.
|
||
|
|
*
|
||
|
|
* @api private
|
||
|
|
*/
|
||
|
|
|
||
|
|
var MAX_LENGTH = META_BLOCK_SIZE * 255;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Usually, the "metabyte" will just be 1 NUL byte, because there is no metadata
|
||
|
|
* to be sent. Create that 1-byte buffer up-front.
|
||
|
|
*
|
||
|
|
* @api private
|
||
|
|
*/
|
||
|
|
|
||
|
|
var NO_METADATA = new Buffer([0]);
|
||
|
|
|
||
|
|
/**
|
||
|
|
* The `Writer` class is a duplex stream that accepts raw audio/video data and
|
||
|
|
* passes it through untouched. It also has a `queue()` function that will
|
||
|
|
* queue the Writer to inject the metadata into the stream at the next "metaint"
|
||
|
|
* interval.
|
||
|
|
*
|
||
|
|
* @param {Number} metaint the number of raw bytes that should be placed in between "metadata" blocks.
|
||
|
|
* @param {Object} opts optional options object
|
||
|
|
* @api public
|
||
|
|
*/
|
||
|
|
|
||
|
|
function Writer (metaint, opts) {
|
||
|
|
if (!(this instanceof Writer)) {
|
||
|
|
return new Writer(metaint, opts);
|
||
|
|
}
|
||
|
|
if (!isFinite(metaint)) {
|
||
|
|
throw new Error('Writer requires a "metaint" number');
|
||
|
|
}
|
||
|
|
Transform.call(this, opts);
|
||
|
|
|
||
|
|
this.metaint = +metaint;
|
||
|
|
|
||
|
|
this._queue = [];
|
||
|
|
|
||
|
|
this._passthrough(this.metaint, this._inject);
|
||
|
|
}
|
||
|
|
inherits(Writer, Transform);
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Mixin `Parser`.
|
||
|
|
*/
|
||
|
|
|
||
|
|
Parser(Writer.prototype);
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Queues a piece of metadata to be sent along with the stream.
|
||
|
|
* `metadata` may be a String and be any title (up to 4066 chars),
|
||
|
|
* or may be an Object containing at least a "StreamTitle" key, with a String
|
||
|
|
* value. The serialized metadata payload must be <= 4080 bytes.
|
||
|
|
*
|
||
|
|
* @param {String|Object} metadata
|
||
|
|
* @api public
|
||
|
|
*/
|
||
|
|
|
||
|
|
Writer.prototype.queue = function (metadata) {
|
||
|
|
if ('string' == typeof metadata) {
|
||
|
|
// string title
|
||
|
|
metadata = { StreamTitle: metadata };
|
||
|
|
} else if (metadata && Object(metadata) === metadata) {
|
||
|
|
// an object
|
||
|
|
} else {
|
||
|
|
throw new TypeError('don\'t know how to format metadata: ' + metadata);
|
||
|
|
}
|
||
|
|
if (!('StreamTitle' in metadata)) {
|
||
|
|
throw new TypeError('a "StreamTitle" property is required for metadata');
|
||
|
|
}
|
||
|
|
var str = stringify(metadata);
|
||
|
|
var len = Buffer.byteLength(str);
|
||
|
|
if (len > MAX_LENGTH) {
|
||
|
|
throw new Error('metadata must be <= 4080, got: ' + len);
|
||
|
|
}
|
||
|
|
var meta = Math.ceil(len / META_BLOCK_SIZE);
|
||
|
|
var buf = new Buffer(meta * META_BLOCK_SIZE + 1);
|
||
|
|
buf[0] = meta;
|
||
|
|
var written = buf.write(str, 1);
|
||
|
|
for (var i = written + 1; i < buf.length; i++) {
|
||
|
|
// pad with NULL bytes
|
||
|
|
buf[i] = 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
this._queue.push(buf);
|
||
|
|
};
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Backwards-compat.
|
||
|
|
*
|
||
|
|
* @api private
|
||
|
|
*/
|
||
|
|
|
||
|
|
Writer.prototype.queueMetadata = Writer.prototype.queue;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Writes the next "metabyte" to the output stream.
|
||
|
|
*
|
||
|
|
* @param {Function} write output callback function
|
||
|
|
* @api private
|
||
|
|
*/
|
||
|
|
|
||
|
|
Writer.prototype._inject = function (write) {
|
||
|
|
var buffer;
|
||
|
|
if (0 === this._queue.length) {
|
||
|
|
buffer = NO_METADATA;
|
||
|
|
} else {
|
||
|
|
buffer = this._queue.shift();
|
||
|
|
}
|
||
|
|
write(buffer);
|
||
|
|
|
||
|
|
// passthrough "metaint" bytes before injecting the next metabyte
|
||
|
|
this._passthrough(this.metaint, this._inject);
|
||
|
|
};
|