summaryrefslogtreecommitdiff
path: root/node_modules/prism-media/src/opus/OggDemuxer.js
diff options
context:
space:
mode:
Diffstat (limited to 'node_modules/prism-media/src/opus/OggDemuxer.js')
-rw-r--r--node_modules/prism-media/src/opus/OggDemuxer.js118
1 files changed, 118 insertions, 0 deletions
diff --git a/node_modules/prism-media/src/opus/OggDemuxer.js b/node_modules/prism-media/src/opus/OggDemuxer.js
new file mode 100644
index 0000000..5cf0d56
--- /dev/null
+++ b/node_modules/prism-media/src/opus/OggDemuxer.js
@@ -0,0 +1,118 @@
+const { Transform } = require('stream');
+
+const OGG_PAGE_HEADER_SIZE = 26;
+const STREAM_STRUCTURE_VERSION = 0;
+
+const charCode = x => x.charCodeAt(0);
+const OGGS_HEADER = Buffer.from([...'OggS'].map(charCode));
+const OPUS_HEAD = Buffer.from([...'OpusHead'].map(charCode));
+const OPUS_TAGS = Buffer.from([...'OpusTags'].map(charCode));
+
+/**
+ * Demuxes an Ogg stream (containing Opus audio) to output an Opus stream.
+ * @extends {TransformStream}
+ * @memberof opus
+ */
+class OggDemuxer extends Transform {
+ /**
+ * Creates a new OggOpus demuxer.
+ * @param {Object} [options] options that you would pass to a regular Transform stream.
+ * @memberof opus
+ */
+ constructor(options = {}) {
+ super(Object.assign({ readableObjectMode: true }, options));
+ this._remainder = null;
+ this._head = null;
+ this._bitstream = null;
+ }
+
+ _transform(chunk, encoding, done) {
+ if (this._remainder) {
+ chunk = Buffer.concat([this._remainder, chunk]);
+ this._remainder = null;
+ }
+
+ while (chunk) {
+ const result = this._readPage(chunk);
+ if (result) chunk = result;
+ else break;
+ }
+ this._remainder = chunk;
+ done();
+ }
+
+ /**
+ * Reads a page from a buffer
+ * @private
+ * @param {Buffer} chunk the chunk containing the page
+ * @returns {boolean|Buffer} if a buffer, it will be a slice of the excess data of the original, otherwise it will be
+ * false and would indicate that there is not enough data to go ahead with reading this page.
+ */
+ _readPage(chunk) {
+ if (chunk.length < OGG_PAGE_HEADER_SIZE) {
+ return false;
+ }
+ if (!chunk.slice(0, 4).equals(OGGS_HEADER)) {
+ throw Error(`capture_pattern is not ${OGGS_HEADER}`);
+ }
+ if (chunk.readUInt8(4) !== STREAM_STRUCTURE_VERSION) {
+ throw Error(`stream_structure_version is not ${STREAM_STRUCTURE_VERSION}`);
+ }
+
+ if (chunk.length < 27) return false;
+ const pageSegments = chunk.readUInt8(26);
+ if (chunk.length < 27 + pageSegments) return false;
+ const table = chunk.slice(27, 27 + pageSegments);
+ const bitstream = chunk.readUInt32BE(14);
+
+ let sizes = [], totalSize = 0;
+
+ for (let i = 0; i < pageSegments;) {
+ let size = 0, x = 255;
+ while (x === 255) {
+ if (i >= table.length) return false;
+ x = table.readUInt8(i);
+ i++;
+ size += x;
+ }
+ sizes.push(size);
+ totalSize += size;
+ }
+
+ if (chunk.length < 27 + pageSegments + totalSize) return false;
+
+ let start = 27 + pageSegments;
+ for (const size of sizes) {
+ const segment = chunk.slice(start, start + size);
+ const header = segment.slice(0, 8);
+ if (this._head) {
+ if (header.equals(OPUS_TAGS)) this.emit('tags', segment);
+ else if (this._bitstream === bitstream) this.push(segment);
+ } else if (header.equals(OPUS_HEAD)) {
+ this.emit('head', segment);
+ this._head = segment;
+ this._bitstream = bitstream;
+ } else {
+ this.emit('unknownSegment', segment);
+ }
+ start += size;
+ }
+ return chunk.slice(start);
+ }
+}
+
+/**
+ * Emitted when the demuxer encounters the opus head.
+ * @event OggDemuxer#head
+ * @memberof opus
+ * @param {Buffer} segment a buffer containing the opus head data.
+ */
+
+/**
+ * Emitted when the demuxer encounters opus tags.
+ * @event OggDemuxer#tags
+ * @memberof opus
+ * @param {Buffer} segment a buffer containing the opus tags.
+ */
+
+module.exports = OggDemuxer;