UNPKG

@sqhead/mux.js

Version:

A collection of lightweight utilities for inspecting and manipulating video container formats.

136 lines (112 loc) 3.78 kB
'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;