@sqhead/mux.js
Version:
A collection of lightweight utilities for inspecting and manipulating video container formats.
136 lines (112 loc) • 3.78 kB
JavaScript
'use strict';
var Stream = require('../utils/stream.js');
var mp4 = require('../mp4/mp4-generator.js');
var VIDEO_PROPERTIES = [
'width',
'height',
'profileIdc',
'levelIdc',
'profileCompatibility'
];
/**
* A Stream that can combine multiple streams (ie. audio & video)
* into a single output segment for MSE. Also supports audio-only
* and video-only streams.
*/
var CoalesceStream = function(options) {
// Number of Tracks per output segment
// If greater than 1, we combine multiple
// tracks into a single segment
this.numberOfTracks = 0;
this.pendingTracks = [];
this.pendingBoxes = [];
this.videoTrack = null;
this.pendingBytes = 0;
this.emittedTracks = 0;
CoalesceStream.prototype.init.call(this);
// Take output from multiple
this.push = function(output) {
// Add this track to the list of pending tracks and store
// important information required for the construction of
// the final segment
this.pendingTracks.push(output.track);
this.pendingBoxes.push(output.boxes);
this.pendingBytes += output.boxes.byteLength;
if (output.track.type === 'video') {
this.videoTrack = output.track;
} else {
console.log("Error: unknown output type: " + output.track.type);
}
};
};
CoalesceStream.prototype = new Stream();
CoalesceStream.prototype.flush = function(flushSource) {
var
offset = 0,
event = {
captions: [],
captionStreams: {},
metadata: [],
info: {}
},
initSegment,
i;
if (this.pendingTracks.length < this.numberOfTracks) {
if (flushSource !== 'VideoSegmentStream') {
// Return because we haven't received a flush from a data-generating
// portion of the segment (meaning that we have only recieved meta-data
// or captions.)
return;
} else if (this.pendingTracks.length === 0) {
// In the case where we receive a flush without any data having been
// received we consider it an emitted track for the purposes of coalescing
// `done` events.
// We do this for the case where there is an audio and video track in the
// segment but no audio data. (seen in several playlists with alternate
// audio tracks and no audio present in the main TS segments.)
this.emittedTracks++;
if (this.emittedTracks >= this.numberOfTracks) {
this.trigger('done');
this.emittedTracks = 0;
}
return;
}
}
if (this.videoTrack) {
VIDEO_PROPERTIES.forEach(function(prop) {
event.info[prop] = this.videoTrack[prop];
}, this);
}
if (this.pendingTracks.length === 1) {
event.type = this.pendingTracks[0].type;
} else {
event.type = 'combined';
}
this.emittedTracks += this.pendingTracks.length;
initSegment = mp4.initSegment(this.pendingTracks);
// Create a new typed array to hold the init segment
event.initSegment = new Uint8Array(initSegment.byteLength);
// Create an init segment containing a moov
// and track definitions
event.initSegment.set(initSegment);
// Create a new typed array to hold the moof+mdats
event.data = new Uint8Array(this.pendingBytes);
// Append each moof+mdat (one per track) together
for (i = 0; i < this.pendingBoxes.length; i++) {
event.data.set(this.pendingBoxes[i], offset);
offset += this.pendingBoxes[i].byteLength;
}
// Reset stream state
this.pendingTracks.length = 0;
this.videoTrack = null;
this.pendingBoxes.length = 0;
this.pendingBytes = 0;
// Emit the built segment
this.trigger('data', event);
// Only emit `done` if all tracks have been flushed and emitted
if (this.emittedTracks >= this.numberOfTracks) {
this.trigger('done');
this.emittedTracks = 0;
}
};
module.exports = CoalesceStream;