First release
This commit is contained in:
commit
fa6c85266e
2339 changed files with 761050 additions and 0 deletions
148
node_modules/mux.js/cjs/mp4/audio-frame-utils.js
generated
vendored
Normal file
148
node_modules/mux.js/cjs/mp4/audio-frame-utils.js
generated
vendored
Normal file
|
@ -0,0 +1,148 @@
|
|||
"use strict";
|
||||
|
||||
/**
|
||||
* mux.js
|
||||
*
|
||||
* Copyright (c) Brightcove
|
||||
* Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
|
||||
*/
|
||||
var coneOfSilence = require('../data/silence');
|
||||
|
||||
var clock = require('../utils/clock');
|
||||
/**
|
||||
* Sum the `byteLength` properties of the data in each AAC frame
|
||||
*/
|
||||
|
||||
|
||||
var sumFrameByteLengths = function sumFrameByteLengths(array) {
|
||||
var i,
|
||||
currentObj,
|
||||
sum = 0; // sum the byteLength's all each nal unit in the frame
|
||||
|
||||
for (i = 0; i < array.length; i++) {
|
||||
currentObj = array[i];
|
||||
sum += currentObj.data.byteLength;
|
||||
}
|
||||
|
||||
return sum;
|
||||
}; // Possibly pad (prefix) the audio track with silence if appending this track
|
||||
// would lead to the introduction of a gap in the audio buffer
|
||||
|
||||
|
||||
var prefixWithSilence = function prefixWithSilence(track, frames, audioAppendStartTs, videoBaseMediaDecodeTime) {
|
||||
var baseMediaDecodeTimeTs,
|
||||
frameDuration = 0,
|
||||
audioGapDuration = 0,
|
||||
audioFillFrameCount = 0,
|
||||
audioFillDuration = 0,
|
||||
silentFrame,
|
||||
i,
|
||||
firstFrame;
|
||||
|
||||
if (!frames.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
baseMediaDecodeTimeTs = clock.audioTsToVideoTs(track.baseMediaDecodeTime, track.samplerate); // determine frame clock duration based on sample rate, round up to avoid overfills
|
||||
|
||||
frameDuration = Math.ceil(clock.ONE_SECOND_IN_TS / (track.samplerate / 1024));
|
||||
|
||||
if (audioAppendStartTs && videoBaseMediaDecodeTime) {
|
||||
// insert the shortest possible amount (audio gap or audio to video gap)
|
||||
audioGapDuration = baseMediaDecodeTimeTs - Math.max(audioAppendStartTs, videoBaseMediaDecodeTime); // number of full frames in the audio gap
|
||||
|
||||
audioFillFrameCount = Math.floor(audioGapDuration / frameDuration);
|
||||
audioFillDuration = audioFillFrameCount * frameDuration;
|
||||
} // don't attempt to fill gaps smaller than a single frame or larger
|
||||
// than a half second
|
||||
|
||||
|
||||
if (audioFillFrameCount < 1 || audioFillDuration > clock.ONE_SECOND_IN_TS / 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
silentFrame = coneOfSilence()[track.samplerate];
|
||||
|
||||
if (!silentFrame) {
|
||||
// we don't have a silent frame pregenerated for the sample rate, so use a frame
|
||||
// from the content instead
|
||||
silentFrame = frames[0].data;
|
||||
}
|
||||
|
||||
for (i = 0; i < audioFillFrameCount; i++) {
|
||||
firstFrame = frames[0];
|
||||
frames.splice(0, 0, {
|
||||
data: silentFrame,
|
||||
dts: firstFrame.dts - frameDuration,
|
||||
pts: firstFrame.pts - frameDuration
|
||||
});
|
||||
}
|
||||
|
||||
track.baseMediaDecodeTime -= Math.floor(clock.videoTsToAudioTs(audioFillDuration, track.samplerate));
|
||||
return audioFillDuration;
|
||||
}; // If the audio segment extends before the earliest allowed dts
|
||||
// value, remove AAC frames until starts at or after the earliest
|
||||
// allowed DTS so that we don't end up with a negative baseMedia-
|
||||
// DecodeTime for the audio track
|
||||
|
||||
|
||||
var trimAdtsFramesByEarliestDts = function trimAdtsFramesByEarliestDts(adtsFrames, track, earliestAllowedDts) {
|
||||
if (track.minSegmentDts >= earliestAllowedDts) {
|
||||
return adtsFrames;
|
||||
} // We will need to recalculate the earliest segment Dts
|
||||
|
||||
|
||||
track.minSegmentDts = Infinity;
|
||||
return adtsFrames.filter(function (currentFrame) {
|
||||
// If this is an allowed frame, keep it and record it's Dts
|
||||
if (currentFrame.dts >= earliestAllowedDts) {
|
||||
track.minSegmentDts = Math.min(track.minSegmentDts, currentFrame.dts);
|
||||
track.minSegmentPts = track.minSegmentDts;
|
||||
return true;
|
||||
} // Otherwise, discard it
|
||||
|
||||
|
||||
return false;
|
||||
});
|
||||
}; // generate the track's raw mdat data from an array of frames
|
||||
|
||||
|
||||
var generateSampleTable = function generateSampleTable(frames) {
|
||||
var i,
|
||||
currentFrame,
|
||||
samples = [];
|
||||
|
||||
for (i = 0; i < frames.length; i++) {
|
||||
currentFrame = frames[i];
|
||||
samples.push({
|
||||
size: currentFrame.data.byteLength,
|
||||
duration: 1024 // For AAC audio, all samples contain 1024 samples
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
return samples;
|
||||
}; // generate the track's sample table from an array of frames
|
||||
|
||||
|
||||
var concatenateFrameData = function concatenateFrameData(frames) {
|
||||
var i,
|
||||
currentFrame,
|
||||
dataOffset = 0,
|
||||
data = new Uint8Array(sumFrameByteLengths(frames));
|
||||
|
||||
for (i = 0; i < frames.length; i++) {
|
||||
currentFrame = frames[i];
|
||||
data.set(currentFrame.data, dataOffset);
|
||||
dataOffset += currentFrame.data.byteLength;
|
||||
}
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
prefixWithSilence: prefixWithSilence,
|
||||
trimAdtsFramesByEarliestDts: trimAdtsFramesByEarliestDts,
|
||||
generateSampleTable: generateSampleTable,
|
||||
concatenateFrameData: concatenateFrameData
|
||||
};
|
452
node_modules/mux.js/cjs/mp4/caption-parser.js
generated
vendored
Normal file
452
node_modules/mux.js/cjs/mp4/caption-parser.js
generated
vendored
Normal file
|
@ -0,0 +1,452 @@
|
|||
/**
|
||||
* mux.js
|
||||
*
|
||||
* Copyright (c) Brightcove
|
||||
* Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
|
||||
*
|
||||
* Reads in-band CEA-708 captions out of FMP4 segments.
|
||||
* @see https://en.wikipedia.org/wiki/CEA-708
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var discardEmulationPreventionBytes = require('../tools/caption-packet-parser').discardEmulationPreventionBytes;
|
||||
|
||||
var CaptionStream = require('../m2ts/caption-stream').CaptionStream;
|
||||
|
||||
var findBox = require('../mp4/find-box.js');
|
||||
|
||||
var parseTfdt = require('../tools/parse-tfdt.js');
|
||||
|
||||
var parseTrun = require('../tools/parse-trun.js');
|
||||
|
||||
var parseTfhd = require('../tools/parse-tfhd.js');
|
||||
/**
|
||||
* Maps an offset in the mdat to a sample based on the the size of the samples.
|
||||
* Assumes that `parseSamples` has been called first.
|
||||
*
|
||||
* @param {Number} offset - The offset into the mdat
|
||||
* @param {Object[]} samples - An array of samples, parsed using `parseSamples`
|
||||
* @return {?Object} The matching sample, or null if no match was found.
|
||||
*
|
||||
* @see ISO-BMFF-12/2015, Section 8.8.8
|
||||
**/
|
||||
|
||||
|
||||
var mapToSample = function mapToSample(offset, samples) {
|
||||
var approximateOffset = offset;
|
||||
|
||||
for (var i = 0; i < samples.length; i++) {
|
||||
var sample = samples[i];
|
||||
|
||||
if (approximateOffset < sample.size) {
|
||||
return sample;
|
||||
}
|
||||
|
||||
approximateOffset -= sample.size;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
/**
|
||||
* Finds SEI nal units contained in a Media Data Box.
|
||||
* Assumes that `parseSamples` has been called first.
|
||||
*
|
||||
* @param {Uint8Array} avcStream - The bytes of the mdat
|
||||
* @param {Object[]} samples - The samples parsed out by `parseSamples`
|
||||
* @param {Number} trackId - The trackId of this video track
|
||||
* @return {Object[]} seiNals - the parsed SEI NALUs found.
|
||||
* The contents of the seiNal should match what is expected by
|
||||
* CaptionStream.push (nalUnitType, size, data, escapedRBSP, pts, dts)
|
||||
*
|
||||
* @see ISO-BMFF-12/2015, Section 8.1.1
|
||||
* @see Rec. ITU-T H.264, 7.3.2.3.1
|
||||
**/
|
||||
|
||||
|
||||
var findSeiNals = function findSeiNals(avcStream, samples, trackId) {
|
||||
var avcView = new DataView(avcStream.buffer, avcStream.byteOffset, avcStream.byteLength),
|
||||
result = [],
|
||||
seiNal,
|
||||
i,
|
||||
length,
|
||||
lastMatchedSample;
|
||||
|
||||
for (i = 0; i + 4 < avcStream.length; i += length) {
|
||||
length = avcView.getUint32(i);
|
||||
i += 4; // Bail if this doesn't appear to be an H264 stream
|
||||
|
||||
if (length <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (avcStream[i] & 0x1F) {
|
||||
case 0x06:
|
||||
var data = avcStream.subarray(i + 1, i + 1 + length);
|
||||
var matchingSample = mapToSample(i, samples);
|
||||
seiNal = {
|
||||
nalUnitType: 'sei_rbsp',
|
||||
size: length,
|
||||
data: data,
|
||||
escapedRBSP: discardEmulationPreventionBytes(data),
|
||||
trackId: trackId
|
||||
};
|
||||
|
||||
if (matchingSample) {
|
||||
seiNal.pts = matchingSample.pts;
|
||||
seiNal.dts = matchingSample.dts;
|
||||
lastMatchedSample = matchingSample;
|
||||
} else if (lastMatchedSample) {
|
||||
// If a matching sample cannot be found, use the last
|
||||
// sample's values as they should be as close as possible
|
||||
seiNal.pts = lastMatchedSample.pts;
|
||||
seiNal.dts = lastMatchedSample.dts;
|
||||
} else {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log("We've encountered a nal unit without data. See mux.js#233.");
|
||||
break;
|
||||
}
|
||||
|
||||
result.push(seiNal);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
/**
|
||||
* Parses sample information out of Track Run Boxes and calculates
|
||||
* the absolute presentation and decode timestamps of each sample.
|
||||
*
|
||||
* @param {Array<Uint8Array>} truns - The Trun Run boxes to be parsed
|
||||
* @param {Number} baseMediaDecodeTime - base media decode time from tfdt
|
||||
@see ISO-BMFF-12/2015, Section 8.8.12
|
||||
* @param {Object} tfhd - The parsed Track Fragment Header
|
||||
* @see inspect.parseTfhd
|
||||
* @return {Object[]} the parsed samples
|
||||
*
|
||||
* @see ISO-BMFF-12/2015, Section 8.8.8
|
||||
**/
|
||||
|
||||
|
||||
var parseSamples = function parseSamples(truns, baseMediaDecodeTime, tfhd) {
|
||||
var currentDts = baseMediaDecodeTime;
|
||||
var defaultSampleDuration = tfhd.defaultSampleDuration || 0;
|
||||
var defaultSampleSize = tfhd.defaultSampleSize || 0;
|
||||
var trackId = tfhd.trackId;
|
||||
var allSamples = [];
|
||||
truns.forEach(function (trun) {
|
||||
// Note: We currently do not parse the sample table as well
|
||||
// as the trun. It's possible some sources will require this.
|
||||
// moov > trak > mdia > minf > stbl
|
||||
var trackRun = parseTrun(trun);
|
||||
var samples = trackRun.samples;
|
||||
samples.forEach(function (sample) {
|
||||
if (sample.duration === undefined) {
|
||||
sample.duration = defaultSampleDuration;
|
||||
}
|
||||
|
||||
if (sample.size === undefined) {
|
||||
sample.size = defaultSampleSize;
|
||||
}
|
||||
|
||||
sample.trackId = trackId;
|
||||
sample.dts = currentDts;
|
||||
|
||||
if (sample.compositionTimeOffset === undefined) {
|
||||
sample.compositionTimeOffset = 0;
|
||||
}
|
||||
|
||||
sample.pts = currentDts + sample.compositionTimeOffset;
|
||||
currentDts += sample.duration;
|
||||
});
|
||||
allSamples = allSamples.concat(samples);
|
||||
});
|
||||
return allSamples;
|
||||
};
|
||||
/**
|
||||
* Parses out caption nals from an FMP4 segment's video tracks.
|
||||
*
|
||||
* @param {Uint8Array} segment - The bytes of a single segment
|
||||
* @param {Number} videoTrackId - The trackId of a video track in the segment
|
||||
* @return {Object.<Number, Object[]>} A mapping of video trackId to
|
||||
* a list of seiNals found in that track
|
||||
**/
|
||||
|
||||
|
||||
var parseCaptionNals = function parseCaptionNals(segment, videoTrackId) {
|
||||
// To get the samples
|
||||
var trafs = findBox(segment, ['moof', 'traf']); // To get SEI NAL units
|
||||
|
||||
var mdats = findBox(segment, ['mdat']);
|
||||
var captionNals = {};
|
||||
var mdatTrafPairs = []; // Pair up each traf with a mdat as moofs and mdats are in pairs
|
||||
|
||||
mdats.forEach(function (mdat, index) {
|
||||
var matchingTraf = trafs[index];
|
||||
mdatTrafPairs.push({
|
||||
mdat: mdat,
|
||||
traf: matchingTraf
|
||||
});
|
||||
});
|
||||
mdatTrafPairs.forEach(function (pair) {
|
||||
var mdat = pair.mdat;
|
||||
var traf = pair.traf;
|
||||
var tfhd = findBox(traf, ['tfhd']); // Exactly 1 tfhd per traf
|
||||
|
||||
var headerInfo = parseTfhd(tfhd[0]);
|
||||
var trackId = headerInfo.trackId;
|
||||
var tfdt = findBox(traf, ['tfdt']); // Either 0 or 1 tfdt per traf
|
||||
|
||||
var baseMediaDecodeTime = tfdt.length > 0 ? parseTfdt(tfdt[0]).baseMediaDecodeTime : 0;
|
||||
var truns = findBox(traf, ['trun']);
|
||||
var samples;
|
||||
var seiNals; // Only parse video data for the chosen video track
|
||||
|
||||
if (videoTrackId === trackId && truns.length > 0) {
|
||||
samples = parseSamples(truns, baseMediaDecodeTime, headerInfo);
|
||||
seiNals = findSeiNals(mdat, samples, trackId);
|
||||
|
||||
if (!captionNals[trackId]) {
|
||||
captionNals[trackId] = [];
|
||||
}
|
||||
|
||||
captionNals[trackId] = captionNals[trackId].concat(seiNals);
|
||||
}
|
||||
});
|
||||
return captionNals;
|
||||
};
|
||||
/**
|
||||
* Parses out inband captions from an MP4 container and returns
|
||||
* caption objects that can be used by WebVTT and the TextTrack API.
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/API/VTTCue
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/API/TextTrack
|
||||
* Assumes that `probe.getVideoTrackIds` and `probe.timescale` have been called first
|
||||
*
|
||||
* @param {Uint8Array} segment - The fmp4 segment containing embedded captions
|
||||
* @param {Number} trackId - The id of the video track to parse
|
||||
* @param {Number} timescale - The timescale for the video track from the init segment
|
||||
*
|
||||
* @return {?Object[]} parsedCaptions - A list of captions or null if no video tracks
|
||||
* @return {Number} parsedCaptions[].startTime - The time to show the caption in seconds
|
||||
* @return {Number} parsedCaptions[].endTime - The time to stop showing the caption in seconds
|
||||
* @return {String} parsedCaptions[].text - The visible content of the caption
|
||||
**/
|
||||
|
||||
|
||||
var parseEmbeddedCaptions = function parseEmbeddedCaptions(segment, trackId, timescale) {
|
||||
var seiNals; // the ISO-BMFF spec says that trackId can't be zero, but there's some broken content out there
|
||||
|
||||
if (trackId === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
seiNals = parseCaptionNals(segment, trackId);
|
||||
return {
|
||||
seiNals: seiNals[trackId],
|
||||
timescale: timescale
|
||||
};
|
||||
};
|
||||
/**
|
||||
* Converts SEI NALUs into captions that can be used by video.js
|
||||
**/
|
||||
|
||||
|
||||
var CaptionParser = function CaptionParser() {
|
||||
var isInitialized = false;
|
||||
var captionStream; // Stores segments seen before trackId and timescale are set
|
||||
|
||||
var segmentCache; // Stores video track ID of the track being parsed
|
||||
|
||||
var trackId; // Stores the timescale of the track being parsed
|
||||
|
||||
var timescale; // Stores captions parsed so far
|
||||
|
||||
var parsedCaptions; // Stores whether we are receiving partial data or not
|
||||
|
||||
var parsingPartial;
|
||||
/**
|
||||
* A method to indicate whether a CaptionParser has been initalized
|
||||
* @returns {Boolean}
|
||||
**/
|
||||
|
||||
this.isInitialized = function () {
|
||||
return isInitialized;
|
||||
};
|
||||
/**
|
||||
* Initializes the underlying CaptionStream, SEI NAL parsing
|
||||
* and management, and caption collection
|
||||
**/
|
||||
|
||||
|
||||
this.init = function (options) {
|
||||
captionStream = new CaptionStream();
|
||||
isInitialized = true;
|
||||
parsingPartial = options ? options.isPartial : false; // Collect dispatched captions
|
||||
|
||||
captionStream.on('data', function (event) {
|
||||
// Convert to seconds in the source's timescale
|
||||
event.startTime = event.startPts / timescale;
|
||||
event.endTime = event.endPts / timescale;
|
||||
parsedCaptions.captions.push(event);
|
||||
parsedCaptions.captionStreams[event.stream] = true;
|
||||
});
|
||||
};
|
||||
/**
|
||||
* Determines if a new video track will be selected
|
||||
* or if the timescale changed
|
||||
* @return {Boolean}
|
||||
**/
|
||||
|
||||
|
||||
this.isNewInit = function (videoTrackIds, timescales) {
|
||||
if (videoTrackIds && videoTrackIds.length === 0 || timescales && typeof timescales === 'object' && Object.keys(timescales).length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return trackId !== videoTrackIds[0] || timescale !== timescales[trackId];
|
||||
};
|
||||
/**
|
||||
* Parses out SEI captions and interacts with underlying
|
||||
* CaptionStream to return dispatched captions
|
||||
*
|
||||
* @param {Uint8Array} segment - The fmp4 segment containing embedded captions
|
||||
* @param {Number[]} videoTrackIds - A list of video tracks found in the init segment
|
||||
* @param {Object.<Number, Number>} timescales - The timescales found in the init segment
|
||||
* @see parseEmbeddedCaptions
|
||||
* @see m2ts/caption-stream.js
|
||||
**/
|
||||
|
||||
|
||||
this.parse = function (segment, videoTrackIds, timescales) {
|
||||
var parsedData;
|
||||
|
||||
if (!this.isInitialized()) {
|
||||
return null; // This is not likely to be a video segment
|
||||
} else if (!videoTrackIds || !timescales) {
|
||||
return null;
|
||||
} else if (this.isNewInit(videoTrackIds, timescales)) {
|
||||
// Use the first video track only as there is no
|
||||
// mechanism to switch to other video tracks
|
||||
trackId = videoTrackIds[0];
|
||||
timescale = timescales[trackId]; // If an init segment has not been seen yet, hold onto segment
|
||||
// data until we have one.
|
||||
// the ISO-BMFF spec says that trackId can't be zero, but there's some broken content out there
|
||||
} else if (trackId === null || !timescale) {
|
||||
segmentCache.push(segment);
|
||||
return null;
|
||||
} // Now that a timescale and trackId is set, parse cached segments
|
||||
|
||||
|
||||
while (segmentCache.length > 0) {
|
||||
var cachedSegment = segmentCache.shift();
|
||||
this.parse(cachedSegment, videoTrackIds, timescales);
|
||||
}
|
||||
|
||||
parsedData = parseEmbeddedCaptions(segment, trackId, timescale);
|
||||
|
||||
if (parsedData === null || !parsedData.seiNals) {
|
||||
return null;
|
||||
}
|
||||
|
||||
this.pushNals(parsedData.seiNals); // Force the parsed captions to be dispatched
|
||||
|
||||
this.flushStream();
|
||||
return parsedCaptions;
|
||||
};
|
||||
/**
|
||||
* Pushes SEI NALUs onto CaptionStream
|
||||
* @param {Object[]} nals - A list of SEI nals parsed using `parseCaptionNals`
|
||||
* Assumes that `parseCaptionNals` has been called first
|
||||
* @see m2ts/caption-stream.js
|
||||
**/
|
||||
|
||||
|
||||
this.pushNals = function (nals) {
|
||||
if (!this.isInitialized() || !nals || nals.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
nals.forEach(function (nal) {
|
||||
captionStream.push(nal);
|
||||
});
|
||||
};
|
||||
/**
|
||||
* Flushes underlying CaptionStream to dispatch processed, displayable captions
|
||||
* @see m2ts/caption-stream.js
|
||||
**/
|
||||
|
||||
|
||||
this.flushStream = function () {
|
||||
if (!this.isInitialized()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!parsingPartial) {
|
||||
captionStream.flush();
|
||||
} else {
|
||||
captionStream.partialFlush();
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Reset caption buckets for new data
|
||||
**/
|
||||
|
||||
|
||||
this.clearParsedCaptions = function () {
|
||||
parsedCaptions.captions = [];
|
||||
parsedCaptions.captionStreams = {};
|
||||
};
|
||||
/**
|
||||
* Resets underlying CaptionStream
|
||||
* @see m2ts/caption-stream.js
|
||||
**/
|
||||
|
||||
|
||||
this.resetCaptionStream = function () {
|
||||
if (!this.isInitialized()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
captionStream.reset();
|
||||
};
|
||||
/**
|
||||
* Convenience method to clear all captions flushed from the
|
||||
* CaptionStream and still being parsed
|
||||
* @see m2ts/caption-stream.js
|
||||
**/
|
||||
|
||||
|
||||
this.clearAllCaptions = function () {
|
||||
this.clearParsedCaptions();
|
||||
this.resetCaptionStream();
|
||||
};
|
||||
/**
|
||||
* Reset caption parser
|
||||
**/
|
||||
|
||||
|
||||
this.reset = function () {
|
||||
segmentCache = [];
|
||||
trackId = null;
|
||||
timescale = null;
|
||||
|
||||
if (!parsedCaptions) {
|
||||
parsedCaptions = {
|
||||
captions: [],
|
||||
// CC1, CC2, CC3, CC4
|
||||
captionStreams: {}
|
||||
};
|
||||
} else {
|
||||
this.clearParsedCaptions();
|
||||
}
|
||||
|
||||
this.resetCaptionStream();
|
||||
};
|
||||
|
||||
this.reset();
|
||||
};
|
||||
|
||||
module.exports = CaptionParser;
|
47
node_modules/mux.js/cjs/mp4/find-box.js
generated
vendored
Normal file
47
node_modules/mux.js/cjs/mp4/find-box.js
generated
vendored
Normal file
|
@ -0,0 +1,47 @@
|
|||
"use strict";
|
||||
|
||||
var toUnsigned = require('../utils/bin').toUnsigned;
|
||||
|
||||
var parseType = require('./parse-type.js');
|
||||
|
||||
var findBox = function findBox(data, path) {
|
||||
var results = [],
|
||||
i,
|
||||
size,
|
||||
type,
|
||||
end,
|
||||
subresults;
|
||||
|
||||
if (!path.length) {
|
||||
// short-circuit the search for empty paths
|
||||
return null;
|
||||
}
|
||||
|
||||
for (i = 0; i < data.byteLength;) {
|
||||
size = toUnsigned(data[i] << 24 | data[i + 1] << 16 | data[i + 2] << 8 | data[i + 3]);
|
||||
type = parseType(data.subarray(i + 4, i + 8));
|
||||
end = size > 1 ? i + size : data.byteLength;
|
||||
|
||||
if (type === path[0]) {
|
||||
if (path.length === 1) {
|
||||
// this is the end of the path and we've found the box we were
|
||||
// looking for
|
||||
results.push(data.subarray(i + 8, end));
|
||||
} else {
|
||||
// recursively search for the next box along the path
|
||||
subresults = findBox(data.subarray(i + 8, end), path.slice(1));
|
||||
|
||||
if (subresults.length) {
|
||||
results = results.concat(subresults);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
i = end;
|
||||
} // we've finished searching all of data
|
||||
|
||||
|
||||
return results;
|
||||
};
|
||||
|
||||
module.exports = findBox;
|
304
node_modules/mux.js/cjs/mp4/frame-utils.js
generated
vendored
Normal file
304
node_modules/mux.js/cjs/mp4/frame-utils.js
generated
vendored
Normal file
|
@ -0,0 +1,304 @@
|
|||
"use strict";
|
||||
|
||||
/**
|
||||
* mux.js
|
||||
*
|
||||
* Copyright (c) Brightcove
|
||||
* Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
|
||||
*/
|
||||
// Convert an array of nal units into an array of frames with each frame being
|
||||
// composed of the nal units that make up that frame
|
||||
// Also keep track of cummulative data about the frame from the nal units such
|
||||
// as the frame duration, starting pts, etc.
|
||||
var groupNalsIntoFrames = function groupNalsIntoFrames(nalUnits) {
|
||||
var i,
|
||||
currentNal,
|
||||
currentFrame = [],
|
||||
frames = []; // TODO added for LHLS, make sure this is OK
|
||||
|
||||
frames.byteLength = 0;
|
||||
frames.nalCount = 0;
|
||||
frames.duration = 0;
|
||||
currentFrame.byteLength = 0;
|
||||
|
||||
for (i = 0; i < nalUnits.length; i++) {
|
||||
currentNal = nalUnits[i]; // Split on 'aud'-type nal units
|
||||
|
||||
if (currentNal.nalUnitType === 'access_unit_delimiter_rbsp') {
|
||||
// Since the very first nal unit is expected to be an AUD
|
||||
// only push to the frames array when currentFrame is not empty
|
||||
if (currentFrame.length) {
|
||||
currentFrame.duration = currentNal.dts - currentFrame.dts; // TODO added for LHLS, make sure this is OK
|
||||
|
||||
frames.byteLength += currentFrame.byteLength;
|
||||
frames.nalCount += currentFrame.length;
|
||||
frames.duration += currentFrame.duration;
|
||||
frames.push(currentFrame);
|
||||
}
|
||||
|
||||
currentFrame = [currentNal];
|
||||
currentFrame.byteLength = currentNal.data.byteLength;
|
||||
currentFrame.pts = currentNal.pts;
|
||||
currentFrame.dts = currentNal.dts;
|
||||
} else {
|
||||
// Specifically flag key frames for ease of use later
|
||||
if (currentNal.nalUnitType === 'slice_layer_without_partitioning_rbsp_idr') {
|
||||
currentFrame.keyFrame = true;
|
||||
}
|
||||
|
||||
currentFrame.duration = currentNal.dts - currentFrame.dts;
|
||||
currentFrame.byteLength += currentNal.data.byteLength;
|
||||
currentFrame.push(currentNal);
|
||||
}
|
||||
} // For the last frame, use the duration of the previous frame if we
|
||||
// have nothing better to go on
|
||||
|
||||
|
||||
if (frames.length && (!currentFrame.duration || currentFrame.duration <= 0)) {
|
||||
currentFrame.duration = frames[frames.length - 1].duration;
|
||||
} // Push the final frame
|
||||
// TODO added for LHLS, make sure this is OK
|
||||
|
||||
|
||||
frames.byteLength += currentFrame.byteLength;
|
||||
frames.nalCount += currentFrame.length;
|
||||
frames.duration += currentFrame.duration;
|
||||
frames.push(currentFrame);
|
||||
return frames;
|
||||
}; // Convert an array of frames into an array of Gop with each Gop being composed
|
||||
// of the frames that make up that Gop
|
||||
// Also keep track of cummulative data about the Gop from the frames such as the
|
||||
// Gop duration, starting pts, etc.
|
||||
|
||||
|
||||
var groupFramesIntoGops = function groupFramesIntoGops(frames) {
|
||||
var i,
|
||||
currentFrame,
|
||||
currentGop = [],
|
||||
gops = []; // We must pre-set some of the values on the Gop since we
|
||||
// keep running totals of these values
|
||||
|
||||
currentGop.byteLength = 0;
|
||||
currentGop.nalCount = 0;
|
||||
currentGop.duration = 0;
|
||||
currentGop.pts = frames[0].pts;
|
||||
currentGop.dts = frames[0].dts; // store some metadata about all the Gops
|
||||
|
||||
gops.byteLength = 0;
|
||||
gops.nalCount = 0;
|
||||
gops.duration = 0;
|
||||
gops.pts = frames[0].pts;
|
||||
gops.dts = frames[0].dts;
|
||||
|
||||
for (i = 0; i < frames.length; i++) {
|
||||
currentFrame = frames[i];
|
||||
|
||||
if (currentFrame.keyFrame) {
|
||||
// Since the very first frame is expected to be an keyframe
|
||||
// only push to the gops array when currentGop is not empty
|
||||
if (currentGop.length) {
|
||||
gops.push(currentGop);
|
||||
gops.byteLength += currentGop.byteLength;
|
||||
gops.nalCount += currentGop.nalCount;
|
||||
gops.duration += currentGop.duration;
|
||||
}
|
||||
|
||||
currentGop = [currentFrame];
|
||||
currentGop.nalCount = currentFrame.length;
|
||||
currentGop.byteLength = currentFrame.byteLength;
|
||||
currentGop.pts = currentFrame.pts;
|
||||
currentGop.dts = currentFrame.dts;
|
||||
currentGop.duration = currentFrame.duration;
|
||||
} else {
|
||||
currentGop.duration += currentFrame.duration;
|
||||
currentGop.nalCount += currentFrame.length;
|
||||
currentGop.byteLength += currentFrame.byteLength;
|
||||
currentGop.push(currentFrame);
|
||||
}
|
||||
}
|
||||
|
||||
if (gops.length && currentGop.duration <= 0) {
|
||||
currentGop.duration = gops[gops.length - 1].duration;
|
||||
}
|
||||
|
||||
gops.byteLength += currentGop.byteLength;
|
||||
gops.nalCount += currentGop.nalCount;
|
||||
gops.duration += currentGop.duration; // push the final Gop
|
||||
|
||||
gops.push(currentGop);
|
||||
return gops;
|
||||
};
|
||||
/*
|
||||
* Search for the first keyframe in the GOPs and throw away all frames
|
||||
* until that keyframe. Then extend the duration of the pulled keyframe
|
||||
* and pull the PTS and DTS of the keyframe so that it covers the time
|
||||
* range of the frames that were disposed.
|
||||
*
|
||||
* @param {Array} gops video GOPs
|
||||
* @returns {Array} modified video GOPs
|
||||
*/
|
||||
|
||||
|
||||
var extendFirstKeyFrame = function extendFirstKeyFrame(gops) {
|
||||
var currentGop;
|
||||
|
||||
if (!gops[0][0].keyFrame && gops.length > 1) {
|
||||
// Remove the first GOP
|
||||
currentGop = gops.shift();
|
||||
gops.byteLength -= currentGop.byteLength;
|
||||
gops.nalCount -= currentGop.nalCount; // Extend the first frame of what is now the
|
||||
// first gop to cover the time period of the
|
||||
// frames we just removed
|
||||
|
||||
gops[0][0].dts = currentGop.dts;
|
||||
gops[0][0].pts = currentGop.pts;
|
||||
gops[0][0].duration += currentGop.duration;
|
||||
}
|
||||
|
||||
return gops;
|
||||
};
|
||||
/**
|
||||
* Default sample object
|
||||
* see ISO/IEC 14496-12:2012, section 8.6.4.3
|
||||
*/
|
||||
|
||||
|
||||
var createDefaultSample = function createDefaultSample() {
|
||||
return {
|
||||
size: 0,
|
||||
flags: {
|
||||
isLeading: 0,
|
||||
dependsOn: 1,
|
||||
isDependedOn: 0,
|
||||
hasRedundancy: 0,
|
||||
degradationPriority: 0,
|
||||
isNonSyncSample: 1
|
||||
}
|
||||
};
|
||||
};
|
||||
/*
|
||||
* Collates information from a video frame into an object for eventual
|
||||
* entry into an MP4 sample table.
|
||||
*
|
||||
* @param {Object} frame the video frame
|
||||
* @param {Number} dataOffset the byte offset to position the sample
|
||||
* @return {Object} object containing sample table info for a frame
|
||||
*/
|
||||
|
||||
|
||||
var sampleForFrame = function sampleForFrame(frame, dataOffset) {
|
||||
var sample = createDefaultSample();
|
||||
sample.dataOffset = dataOffset;
|
||||
sample.compositionTimeOffset = frame.pts - frame.dts;
|
||||
sample.duration = frame.duration;
|
||||
sample.size = 4 * frame.length; // Space for nal unit size
|
||||
|
||||
sample.size += frame.byteLength;
|
||||
|
||||
if (frame.keyFrame) {
|
||||
sample.flags.dependsOn = 2;
|
||||
sample.flags.isNonSyncSample = 0;
|
||||
}
|
||||
|
||||
return sample;
|
||||
}; // generate the track's sample table from an array of gops
|
||||
|
||||
|
||||
var generateSampleTable = function generateSampleTable(gops, baseDataOffset) {
|
||||
var h,
|
||||
i,
|
||||
sample,
|
||||
currentGop,
|
||||
currentFrame,
|
||||
dataOffset = baseDataOffset || 0,
|
||||
samples = [];
|
||||
|
||||
for (h = 0; h < gops.length; h++) {
|
||||
currentGop = gops[h];
|
||||
|
||||
for (i = 0; i < currentGop.length; i++) {
|
||||
currentFrame = currentGop[i];
|
||||
sample = sampleForFrame(currentFrame, dataOffset);
|
||||
dataOffset += sample.size;
|
||||
samples.push(sample);
|
||||
}
|
||||
}
|
||||
|
||||
return samples;
|
||||
}; // generate the track's raw mdat data from an array of gops
|
||||
|
||||
|
||||
var concatenateNalData = function concatenateNalData(gops) {
|
||||
var h,
|
||||
i,
|
||||
j,
|
||||
currentGop,
|
||||
currentFrame,
|
||||
currentNal,
|
||||
dataOffset = 0,
|
||||
nalsByteLength = gops.byteLength,
|
||||
numberOfNals = gops.nalCount,
|
||||
totalByteLength = nalsByteLength + 4 * numberOfNals,
|
||||
data = new Uint8Array(totalByteLength),
|
||||
view = new DataView(data.buffer); // For each Gop..
|
||||
|
||||
for (h = 0; h < gops.length; h++) {
|
||||
currentGop = gops[h]; // For each Frame..
|
||||
|
||||
for (i = 0; i < currentGop.length; i++) {
|
||||
currentFrame = currentGop[i]; // For each NAL..
|
||||
|
||||
for (j = 0; j < currentFrame.length; j++) {
|
||||
currentNal = currentFrame[j];
|
||||
view.setUint32(dataOffset, currentNal.data.byteLength);
|
||||
dataOffset += 4;
|
||||
data.set(currentNal.data, dataOffset);
|
||||
dataOffset += currentNal.data.byteLength;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}; // generate the track's sample table from a frame
|
||||
|
||||
|
||||
var generateSampleTableForFrame = function generateSampleTableForFrame(frame, baseDataOffset) {
|
||||
var sample,
|
||||
dataOffset = baseDataOffset || 0,
|
||||
samples = [];
|
||||
sample = sampleForFrame(frame, dataOffset);
|
||||
samples.push(sample);
|
||||
return samples;
|
||||
}; // generate the track's raw mdat data from a frame
|
||||
|
||||
|
||||
var concatenateNalDataForFrame = function concatenateNalDataForFrame(frame) {
|
||||
var i,
|
||||
currentNal,
|
||||
dataOffset = 0,
|
||||
nalsByteLength = frame.byteLength,
|
||||
numberOfNals = frame.length,
|
||||
totalByteLength = nalsByteLength + 4 * numberOfNals,
|
||||
data = new Uint8Array(totalByteLength),
|
||||
view = new DataView(data.buffer); // For each NAL..
|
||||
|
||||
for (i = 0; i < frame.length; i++) {
|
||||
currentNal = frame[i];
|
||||
view.setUint32(dataOffset, currentNal.data.byteLength);
|
||||
dataOffset += 4;
|
||||
data.set(currentNal.data, dataOffset);
|
||||
dataOffset += currentNal.data.byteLength;
|
||||
}
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
groupNalsIntoFrames: groupNalsIntoFrames,
|
||||
groupFramesIntoGops: groupFramesIntoGops,
|
||||
extendFirstKeyFrame: extendFirstKeyFrame,
|
||||
generateSampleTable: generateSampleTable,
|
||||
concatenateNalData: concatenateNalData,
|
||||
generateSampleTableForFrame: generateSampleTableForFrame,
|
||||
concatenateNalDataForFrame: concatenateNalDataForFrame
|
||||
};
|
16
node_modules/mux.js/cjs/mp4/index.js
generated
vendored
Normal file
16
node_modules/mux.js/cjs/mp4/index.js
generated
vendored
Normal file
|
@ -0,0 +1,16 @@
|
|||
"use strict";
|
||||
|
||||
/**
|
||||
* mux.js
|
||||
*
|
||||
* Copyright (c) Brightcove
|
||||
* Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
|
||||
*/
|
||||
module.exports = {
|
||||
generator: require('./mp4-generator'),
|
||||
probe: require('./probe'),
|
||||
Transmuxer: require('./transmuxer').Transmuxer,
|
||||
AudioSegmentStream: require('./transmuxer').AudioSegmentStream,
|
||||
VideoSegmentStream: require('./transmuxer').VideoSegmentStream,
|
||||
CaptionParser: require('./caption-parser')
|
||||
};
|
610
node_modules/mux.js/cjs/mp4/mp4-generator.js
generated
vendored
Normal file
610
node_modules/mux.js/cjs/mp4/mp4-generator.js
generated
vendored
Normal file
|
@ -0,0 +1,610 @@
|
|||
/**
|
||||
* mux.js
|
||||
*
|
||||
* Copyright (c) Brightcove
|
||||
* Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
|
||||
*
|
||||
* Functions that generate fragmented MP4s suitable for use with Media
|
||||
* Source Extensions.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var UINT32_MAX = Math.pow(2, 32) - 1;
|
||||
var box, dinf, esds, ftyp, mdat, mfhd, minf, moof, moov, mvex, mvhd, trak, tkhd, mdia, mdhd, hdlr, sdtp, stbl, stsd, traf, trex, trun, types, MAJOR_BRAND, MINOR_VERSION, AVC1_BRAND, VIDEO_HDLR, AUDIO_HDLR, HDLR_TYPES, VMHD, SMHD, DREF, STCO, STSC, STSZ, STTS; // pre-calculate constants
|
||||
|
||||
(function () {
|
||||
var i;
|
||||
types = {
|
||||
avc1: [],
|
||||
// codingname
|
||||
avcC: [],
|
||||
btrt: [],
|
||||
dinf: [],
|
||||
dref: [],
|
||||
esds: [],
|
||||
ftyp: [],
|
||||
hdlr: [],
|
||||
mdat: [],
|
||||
mdhd: [],
|
||||
mdia: [],
|
||||
mfhd: [],
|
||||
minf: [],
|
||||
moof: [],
|
||||
moov: [],
|
||||
mp4a: [],
|
||||
// codingname
|
||||
mvex: [],
|
||||
mvhd: [],
|
||||
pasp: [],
|
||||
sdtp: [],
|
||||
smhd: [],
|
||||
stbl: [],
|
||||
stco: [],
|
||||
stsc: [],
|
||||
stsd: [],
|
||||
stsz: [],
|
||||
stts: [],
|
||||
styp: [],
|
||||
tfdt: [],
|
||||
tfhd: [],
|
||||
traf: [],
|
||||
trak: [],
|
||||
trun: [],
|
||||
trex: [],
|
||||
tkhd: [],
|
||||
vmhd: []
|
||||
}; // In environments where Uint8Array is undefined (e.g., IE8), skip set up so that we
|
||||
// don't throw an error
|
||||
|
||||
if (typeof Uint8Array === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
for (i in types) {
|
||||
if (types.hasOwnProperty(i)) {
|
||||
types[i] = [i.charCodeAt(0), i.charCodeAt(1), i.charCodeAt(2), i.charCodeAt(3)];
|
||||
}
|
||||
}
|
||||
|
||||
MAJOR_BRAND = new Uint8Array(['i'.charCodeAt(0), 's'.charCodeAt(0), 'o'.charCodeAt(0), 'm'.charCodeAt(0)]);
|
||||
AVC1_BRAND = new Uint8Array(['a'.charCodeAt(0), 'v'.charCodeAt(0), 'c'.charCodeAt(0), '1'.charCodeAt(0)]);
|
||||
MINOR_VERSION = new Uint8Array([0, 0, 0, 1]);
|
||||
VIDEO_HDLR = new Uint8Array([0x00, // version 0
|
||||
0x00, 0x00, 0x00, // flags
|
||||
0x00, 0x00, 0x00, 0x00, // pre_defined
|
||||
0x76, 0x69, 0x64, 0x65, // handler_type: 'vide'
|
||||
0x00, 0x00, 0x00, 0x00, // reserved
|
||||
0x00, 0x00, 0x00, 0x00, // reserved
|
||||
0x00, 0x00, 0x00, 0x00, // reserved
|
||||
0x56, 0x69, 0x64, 0x65, 0x6f, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'VideoHandler'
|
||||
]);
|
||||
AUDIO_HDLR = new Uint8Array([0x00, // version 0
|
||||
0x00, 0x00, 0x00, // flags
|
||||
0x00, 0x00, 0x00, 0x00, // pre_defined
|
||||
0x73, 0x6f, 0x75, 0x6e, // handler_type: 'soun'
|
||||
0x00, 0x00, 0x00, 0x00, // reserved
|
||||
0x00, 0x00, 0x00, 0x00, // reserved
|
||||
0x00, 0x00, 0x00, 0x00, // reserved
|
||||
0x53, 0x6f, 0x75, 0x6e, 0x64, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'SoundHandler'
|
||||
]);
|
||||
HDLR_TYPES = {
|
||||
video: VIDEO_HDLR,
|
||||
audio: AUDIO_HDLR
|
||||
};
|
||||
DREF = new Uint8Array([0x00, // version 0
|
||||
0x00, 0x00, 0x00, // flags
|
||||
0x00, 0x00, 0x00, 0x01, // entry_count
|
||||
0x00, 0x00, 0x00, 0x0c, // entry_size
|
||||
0x75, 0x72, 0x6c, 0x20, // 'url' type
|
||||
0x00, // version 0
|
||||
0x00, 0x00, 0x01 // entry_flags
|
||||
]);
|
||||
SMHD = new Uint8Array([0x00, // version
|
||||
0x00, 0x00, 0x00, // flags
|
||||
0x00, 0x00, // balance, 0 means centered
|
||||
0x00, 0x00 // reserved
|
||||
]);
|
||||
STCO = new Uint8Array([0x00, // version
|
||||
0x00, 0x00, 0x00, // flags
|
||||
0x00, 0x00, 0x00, 0x00 // entry_count
|
||||
]);
|
||||
STSC = STCO;
|
||||
STSZ = new Uint8Array([0x00, // version
|
||||
0x00, 0x00, 0x00, // flags
|
||||
0x00, 0x00, 0x00, 0x00, // sample_size
|
||||
0x00, 0x00, 0x00, 0x00 // sample_count
|
||||
]);
|
||||
STTS = STCO;
|
||||
VMHD = new Uint8Array([0x00, // version
|
||||
0x00, 0x00, 0x01, // flags
|
||||
0x00, 0x00, // graphicsmode
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // opcolor
|
||||
]);
|
||||
})();
|
||||
|
||||
box = function box(type) {
|
||||
var payload = [],
|
||||
size = 0,
|
||||
i,
|
||||
result,
|
||||
view;
|
||||
|
||||
for (i = 1; i < arguments.length; i++) {
|
||||
payload.push(arguments[i]);
|
||||
}
|
||||
|
||||
i = payload.length; // calculate the total size we need to allocate
|
||||
|
||||
while (i--) {
|
||||
size += payload[i].byteLength;
|
||||
}
|
||||
|
||||
result = new Uint8Array(size + 8);
|
||||
view = new DataView(result.buffer, result.byteOffset, result.byteLength);
|
||||
view.setUint32(0, result.byteLength);
|
||||
result.set(type, 4); // copy the payload into the result
|
||||
|
||||
for (i = 0, size = 8; i < payload.length; i++) {
|
||||
result.set(payload[i], size);
|
||||
size += payload[i].byteLength;
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
dinf = function dinf() {
|
||||
return box(types.dinf, box(types.dref, DREF));
|
||||
};
|
||||
|
||||
esds = function esds(track) {
|
||||
return box(types.esds, new Uint8Array([0x00, // version
|
||||
0x00, 0x00, 0x00, // flags
|
||||
// ES_Descriptor
|
||||
0x03, // tag, ES_DescrTag
|
||||
0x19, // length
|
||||
0x00, 0x00, // ES_ID
|
||||
0x00, // streamDependenceFlag, URL_flag, reserved, streamPriority
|
||||
// DecoderConfigDescriptor
|
||||
0x04, // tag, DecoderConfigDescrTag
|
||||
0x11, // length
|
||||
0x40, // object type
|
||||
0x15, // streamType
|
||||
0x00, 0x06, 0x00, // bufferSizeDB
|
||||
0x00, 0x00, 0xda, 0xc0, // maxBitrate
|
||||
0x00, 0x00, 0xda, 0xc0, // avgBitrate
|
||||
// DecoderSpecificInfo
|
||||
0x05, // tag, DecoderSpecificInfoTag
|
||||
0x02, // length
|
||||
// ISO/IEC 14496-3, AudioSpecificConfig
|
||||
// for samplingFrequencyIndex see ISO/IEC 13818-7:2006, 8.1.3.2.2, Table 35
|
||||
track.audioobjecttype << 3 | track.samplingfrequencyindex >>> 1, track.samplingfrequencyindex << 7 | track.channelcount << 3, 0x06, 0x01, 0x02 // GASpecificConfig
|
||||
]));
|
||||
};
|
||||
|
||||
ftyp = function ftyp() {
|
||||
return box(types.ftyp, MAJOR_BRAND, MINOR_VERSION, MAJOR_BRAND, AVC1_BRAND);
|
||||
};
|
||||
|
||||
hdlr = function hdlr(type) {
|
||||
return box(types.hdlr, HDLR_TYPES[type]);
|
||||
};
|
||||
|
||||
mdat = function mdat(data) {
|
||||
return box(types.mdat, data);
|
||||
};
|
||||
|
||||
mdhd = function mdhd(track) {
|
||||
var result = new Uint8Array([0x00, // version 0
|
||||
0x00, 0x00, 0x00, // flags
|
||||
0x00, 0x00, 0x00, 0x02, // creation_time
|
||||
0x00, 0x00, 0x00, 0x03, // modification_time
|
||||
0x00, 0x01, 0x5f, 0x90, // timescale, 90,000 "ticks" per second
|
||||
track.duration >>> 24 & 0xFF, track.duration >>> 16 & 0xFF, track.duration >>> 8 & 0xFF, track.duration & 0xFF, // duration
|
||||
0x55, 0xc4, // 'und' language (undetermined)
|
||||
0x00, 0x00]); // Use the sample rate from the track metadata, when it is
|
||||
// defined. The sample rate can be parsed out of an ADTS header, for
|
||||
// instance.
|
||||
|
||||
if (track.samplerate) {
|
||||
result[12] = track.samplerate >>> 24 & 0xFF;
|
||||
result[13] = track.samplerate >>> 16 & 0xFF;
|
||||
result[14] = track.samplerate >>> 8 & 0xFF;
|
||||
result[15] = track.samplerate & 0xFF;
|
||||
}
|
||||
|
||||
return box(types.mdhd, result);
|
||||
};
|
||||
|
||||
mdia = function mdia(track) {
|
||||
return box(types.mdia, mdhd(track), hdlr(track.type), minf(track));
|
||||
};
|
||||
|
||||
mfhd = function mfhd(sequenceNumber) {
|
||||
return box(types.mfhd, new Uint8Array([0x00, 0x00, 0x00, 0x00, // flags
|
||||
(sequenceNumber & 0xFF000000) >> 24, (sequenceNumber & 0xFF0000) >> 16, (sequenceNumber & 0xFF00) >> 8, sequenceNumber & 0xFF // sequence_number
|
||||
]));
|
||||
};
|
||||
|
||||
minf = function minf(track) {
|
||||
return box(types.minf, track.type === 'video' ? box(types.vmhd, VMHD) : box(types.smhd, SMHD), dinf(), stbl(track));
|
||||
};
|
||||
|
||||
moof = function moof(sequenceNumber, tracks) {
|
||||
var trackFragments = [],
|
||||
i = tracks.length; // build traf boxes for each track fragment
|
||||
|
||||
while (i--) {
|
||||
trackFragments[i] = traf(tracks[i]);
|
||||
}
|
||||
|
||||
return box.apply(null, [types.moof, mfhd(sequenceNumber)].concat(trackFragments));
|
||||
};
|
||||
/**
|
||||
* Returns a movie box.
|
||||
* @param tracks {array} the tracks associated with this movie
|
||||
* @see ISO/IEC 14496-12:2012(E), section 8.2.1
|
||||
*/
|
||||
|
||||
|
||||
moov = function moov(tracks) {
|
||||
var i = tracks.length,
|
||||
boxes = [];
|
||||
|
||||
while (i--) {
|
||||
boxes[i] = trak(tracks[i]);
|
||||
}
|
||||
|
||||
return box.apply(null, [types.moov, mvhd(0xffffffff)].concat(boxes).concat(mvex(tracks)));
|
||||
};
|
||||
|
||||
mvex = function mvex(tracks) {
|
||||
var i = tracks.length,
|
||||
boxes = [];
|
||||
|
||||
while (i--) {
|
||||
boxes[i] = trex(tracks[i]);
|
||||
}
|
||||
|
||||
return box.apply(null, [types.mvex].concat(boxes));
|
||||
};
|
||||
|
||||
mvhd = function mvhd(duration) {
|
||||
var bytes = new Uint8Array([0x00, // version 0
|
||||
0x00, 0x00, 0x00, // flags
|
||||
0x00, 0x00, 0x00, 0x01, // creation_time
|
||||
0x00, 0x00, 0x00, 0x02, // modification_time
|
||||
0x00, 0x01, 0x5f, 0x90, // timescale, 90,000 "ticks" per second
|
||||
(duration & 0xFF000000) >> 24, (duration & 0xFF0000) >> 16, (duration & 0xFF00) >> 8, duration & 0xFF, // duration
|
||||
0x00, 0x01, 0x00, 0x00, // 1.0 rate
|
||||
0x01, 0x00, // 1.0 volume
|
||||
0x00, 0x00, // reserved
|
||||
0x00, 0x00, 0x00, 0x00, // reserved
|
||||
0x00, 0x00, 0x00, 0x00, // reserved
|
||||
0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, // transformation: unity matrix
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // pre_defined
|
||||
0xff, 0xff, 0xff, 0xff // next_track_ID
|
||||
]);
|
||||
return box(types.mvhd, bytes);
|
||||
};
|
||||
|
||||
sdtp = function sdtp(track) {
|
||||
var samples = track.samples || [],
|
||||
bytes = new Uint8Array(4 + samples.length),
|
||||
flags,
|
||||
i; // leave the full box header (4 bytes) all zero
|
||||
// write the sample table
|
||||
|
||||
for (i = 0; i < samples.length; i++) {
|
||||
flags = samples[i].flags;
|
||||
bytes[i + 4] = flags.dependsOn << 4 | flags.isDependedOn << 2 | flags.hasRedundancy;
|
||||
}
|
||||
|
||||
return box(types.sdtp, bytes);
|
||||
};
|
||||
|
||||
stbl = function stbl(track) {
|
||||
return box(types.stbl, stsd(track), box(types.stts, STTS), box(types.stsc, STSC), box(types.stsz, STSZ), box(types.stco, STCO));
|
||||
};
|
||||
|
||||
(function () {
|
||||
var videoSample, audioSample;
|
||||
|
||||
stsd = function stsd(track) {
|
||||
return box(types.stsd, new Uint8Array([0x00, // version 0
|
||||
0x00, 0x00, 0x00, // flags
|
||||
0x00, 0x00, 0x00, 0x01]), track.type === 'video' ? videoSample(track) : audioSample(track));
|
||||
};
|
||||
|
||||
videoSample = function videoSample(track) {
|
||||
var sps = track.sps || [],
|
||||
pps = track.pps || [],
|
||||
sequenceParameterSets = [],
|
||||
pictureParameterSets = [],
|
||||
i,
|
||||
avc1Box; // assemble the SPSs
|
||||
|
||||
for (i = 0; i < sps.length; i++) {
|
||||
sequenceParameterSets.push((sps[i].byteLength & 0xFF00) >>> 8);
|
||||
sequenceParameterSets.push(sps[i].byteLength & 0xFF); // sequenceParameterSetLength
|
||||
|
||||
sequenceParameterSets = sequenceParameterSets.concat(Array.prototype.slice.call(sps[i])); // SPS
|
||||
} // assemble the PPSs
|
||||
|
||||
|
||||
for (i = 0; i < pps.length; i++) {
|
||||
pictureParameterSets.push((pps[i].byteLength & 0xFF00) >>> 8);
|
||||
pictureParameterSets.push(pps[i].byteLength & 0xFF);
|
||||
pictureParameterSets = pictureParameterSets.concat(Array.prototype.slice.call(pps[i]));
|
||||
}
|
||||
|
||||
avc1Box = [types.avc1, new Uint8Array([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
|
||||
0x00, 0x01, // data_reference_index
|
||||
0x00, 0x00, // pre_defined
|
||||
0x00, 0x00, // reserved
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // pre_defined
|
||||
(track.width & 0xff00) >> 8, track.width & 0xff, // width
|
||||
(track.height & 0xff00) >> 8, track.height & 0xff, // height
|
||||
0x00, 0x48, 0x00, 0x00, // horizresolution
|
||||
0x00, 0x48, 0x00, 0x00, // vertresolution
|
||||
0x00, 0x00, 0x00, 0x00, // reserved
|
||||
0x00, 0x01, // frame_count
|
||||
0x13, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x6a, 0x73, 0x2d, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x69, 0x62, 0x2d, 0x68, 0x6c, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // compressorname
|
||||
0x00, 0x18, // depth = 24
|
||||
0x11, 0x11 // pre_defined = -1
|
||||
]), box(types.avcC, new Uint8Array([0x01, // configurationVersion
|
||||
track.profileIdc, // AVCProfileIndication
|
||||
track.profileCompatibility, // profile_compatibility
|
||||
track.levelIdc, // AVCLevelIndication
|
||||
0xff // lengthSizeMinusOne, hard-coded to 4 bytes
|
||||
].concat([sps.length], // numOfSequenceParameterSets
|
||||
sequenceParameterSets, // "SPS"
|
||||
[pps.length], // numOfPictureParameterSets
|
||||
pictureParameterSets // "PPS"
|
||||
))), box(types.btrt, new Uint8Array([0x00, 0x1c, 0x9c, 0x80, // bufferSizeDB
|
||||
0x00, 0x2d, 0xc6, 0xc0, // maxBitrate
|
||||
0x00, 0x2d, 0xc6, 0xc0 // avgBitrate
|
||||
]))];
|
||||
|
||||
if (track.sarRatio) {
|
||||
var hSpacing = track.sarRatio[0],
|
||||
vSpacing = track.sarRatio[1];
|
||||
avc1Box.push(box(types.pasp, new Uint8Array([(hSpacing & 0xFF000000) >> 24, (hSpacing & 0xFF0000) >> 16, (hSpacing & 0xFF00) >> 8, hSpacing & 0xFF, (vSpacing & 0xFF000000) >> 24, (vSpacing & 0xFF0000) >> 16, (vSpacing & 0xFF00) >> 8, vSpacing & 0xFF])));
|
||||
}
|
||||
|
||||
return box.apply(null, avc1Box);
|
||||
};
|
||||
|
||||
audioSample = function audioSample(track) {
|
||||
return box(types.mp4a, new Uint8Array([// SampleEntry, ISO/IEC 14496-12
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
|
||||
0x00, 0x01, // data_reference_index
|
||||
// AudioSampleEntry, ISO/IEC 14496-12
|
||||
0x00, 0x00, 0x00, 0x00, // reserved
|
||||
0x00, 0x00, 0x00, 0x00, // reserved
|
||||
(track.channelcount & 0xff00) >> 8, track.channelcount & 0xff, // channelcount
|
||||
(track.samplesize & 0xff00) >> 8, track.samplesize & 0xff, // samplesize
|
||||
0x00, 0x00, // pre_defined
|
||||
0x00, 0x00, // reserved
|
||||
(track.samplerate & 0xff00) >> 8, track.samplerate & 0xff, 0x00, 0x00 // samplerate, 16.16
|
||||
// MP4AudioSampleEntry, ISO/IEC 14496-14
|
||||
]), esds(track));
|
||||
};
|
||||
})();
|
||||
|
||||
tkhd = function tkhd(track) {
|
||||
var result = new Uint8Array([0x00, // version 0
|
||||
0x00, 0x00, 0x07, // flags
|
||||
0x00, 0x00, 0x00, 0x00, // creation_time
|
||||
0x00, 0x00, 0x00, 0x00, // modification_time
|
||||
(track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF, // track_ID
|
||||
0x00, 0x00, 0x00, 0x00, // reserved
|
||||
(track.duration & 0xFF000000) >> 24, (track.duration & 0xFF0000) >> 16, (track.duration & 0xFF00) >> 8, track.duration & 0xFF, // duration
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // reserved
|
||||
0x00, 0x00, // layer
|
||||
0x00, 0x00, // alternate_group
|
||||
0x01, 0x00, // non-audio track volume
|
||||
0x00, 0x00, // reserved
|
||||
0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, // transformation: unity matrix
|
||||
(track.width & 0xFF00) >> 8, track.width & 0xFF, 0x00, 0x00, // width
|
||||
(track.height & 0xFF00) >> 8, track.height & 0xFF, 0x00, 0x00 // height
|
||||
]);
|
||||
return box(types.tkhd, result);
|
||||
};
|
||||
/**
|
||||
* Generate a track fragment (traf) box. A traf box collects metadata
|
||||
* about tracks in a movie fragment (moof) box.
|
||||
*/
|
||||
|
||||
|
||||
traf = function traf(track) {
|
||||
var trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun, sampleDependencyTable, dataOffset, upperWordBaseMediaDecodeTime, lowerWordBaseMediaDecodeTime;
|
||||
trackFragmentHeader = box(types.tfhd, new Uint8Array([0x00, // version 0
|
||||
0x00, 0x00, 0x3a, // flags
|
||||
(track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF, // track_ID
|
||||
0x00, 0x00, 0x00, 0x01, // sample_description_index
|
||||
0x00, 0x00, 0x00, 0x00, // default_sample_duration
|
||||
0x00, 0x00, 0x00, 0x00, // default_sample_size
|
||||
0x00, 0x00, 0x00, 0x00 // default_sample_flags
|
||||
]));
|
||||
upperWordBaseMediaDecodeTime = Math.floor(track.baseMediaDecodeTime / (UINT32_MAX + 1));
|
||||
lowerWordBaseMediaDecodeTime = Math.floor(track.baseMediaDecodeTime % (UINT32_MAX + 1));
|
||||
trackFragmentDecodeTime = box(types.tfdt, new Uint8Array([0x01, // version 1
|
||||
0x00, 0x00, 0x00, // flags
|
||||
// baseMediaDecodeTime
|
||||
upperWordBaseMediaDecodeTime >>> 24 & 0xFF, upperWordBaseMediaDecodeTime >>> 16 & 0xFF, upperWordBaseMediaDecodeTime >>> 8 & 0xFF, upperWordBaseMediaDecodeTime & 0xFF, lowerWordBaseMediaDecodeTime >>> 24 & 0xFF, lowerWordBaseMediaDecodeTime >>> 16 & 0xFF, lowerWordBaseMediaDecodeTime >>> 8 & 0xFF, lowerWordBaseMediaDecodeTime & 0xFF])); // the data offset specifies the number of bytes from the start of
|
||||
// the containing moof to the first payload byte of the associated
|
||||
// mdat
|
||||
|
||||
dataOffset = 32 + // tfhd
|
||||
20 + // tfdt
|
||||
8 + // traf header
|
||||
16 + // mfhd
|
||||
8 + // moof header
|
||||
8; // mdat header
|
||||
// audio tracks require less metadata
|
||||
|
||||
if (track.type === 'audio') {
|
||||
trackFragmentRun = trun(track, dataOffset);
|
||||
return box(types.traf, trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun);
|
||||
} // video tracks should contain an independent and disposable samples
|
||||
// box (sdtp)
|
||||
// generate one and adjust offsets to match
|
||||
|
||||
|
||||
sampleDependencyTable = sdtp(track);
|
||||
trackFragmentRun = trun(track, sampleDependencyTable.length + dataOffset);
|
||||
return box(types.traf, trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun, sampleDependencyTable);
|
||||
};
|
||||
/**
|
||||
* Generate a track box.
|
||||
* @param track {object} a track definition
|
||||
* @return {Uint8Array} the track box
|
||||
*/
|
||||
|
||||
|
||||
trak = function trak(track) {
|
||||
track.duration = track.duration || 0xffffffff;
|
||||
return box(types.trak, tkhd(track), mdia(track));
|
||||
};
|
||||
|
||||
trex = function trex(track) {
|
||||
var result = new Uint8Array([0x00, // version 0
|
||||
0x00, 0x00, 0x00, // flags
|
||||
(track.id & 0xFF000000) >> 24, (track.id & 0xFF0000) >> 16, (track.id & 0xFF00) >> 8, track.id & 0xFF, // track_ID
|
||||
0x00, 0x00, 0x00, 0x01, // default_sample_description_index
|
||||
0x00, 0x00, 0x00, 0x00, // default_sample_duration
|
||||
0x00, 0x00, 0x00, 0x00, // default_sample_size
|
||||
0x00, 0x01, 0x00, 0x01 // default_sample_flags
|
||||
]); // the last two bytes of default_sample_flags is the sample
|
||||
// degradation priority, a hint about the importance of this sample
|
||||
// relative to others. Lower the degradation priority for all sample
|
||||
// types other than video.
|
||||
|
||||
if (track.type !== 'video') {
|
||||
result[result.length - 1] = 0x00;
|
||||
}
|
||||
|
||||
return box(types.trex, result);
|
||||
};
|
||||
|
||||
(function () {
|
||||
var audioTrun, videoTrun, trunHeader; // This method assumes all samples are uniform. That is, if a
|
||||
// duration is present for the first sample, it will be present for
|
||||
// all subsequent samples.
|
||||
// see ISO/IEC 14496-12:2012, Section 8.8.8.1
|
||||
|
||||
trunHeader = function trunHeader(samples, offset) {
|
||||
var durationPresent = 0,
|
||||
sizePresent = 0,
|
||||
flagsPresent = 0,
|
||||
compositionTimeOffset = 0; // trun flag constants
|
||||
|
||||
if (samples.length) {
|
||||
if (samples[0].duration !== undefined) {
|
||||
durationPresent = 0x1;
|
||||
}
|
||||
|
||||
if (samples[0].size !== undefined) {
|
||||
sizePresent = 0x2;
|
||||
}
|
||||
|
||||
if (samples[0].flags !== undefined) {
|
||||
flagsPresent = 0x4;
|
||||
}
|
||||
|
||||
if (samples[0].compositionTimeOffset !== undefined) {
|
||||
compositionTimeOffset = 0x8;
|
||||
}
|
||||
}
|
||||
|
||||
return [0x00, // version 0
|
||||
0x00, durationPresent | sizePresent | flagsPresent | compositionTimeOffset, 0x01, // flags
|
||||
(samples.length & 0xFF000000) >>> 24, (samples.length & 0xFF0000) >>> 16, (samples.length & 0xFF00) >>> 8, samples.length & 0xFF, // sample_count
|
||||
(offset & 0xFF000000) >>> 24, (offset & 0xFF0000) >>> 16, (offset & 0xFF00) >>> 8, offset & 0xFF // data_offset
|
||||
];
|
||||
};
|
||||
|
||||
videoTrun = function videoTrun(track, offset) {
|
||||
var bytesOffest, bytes, header, samples, sample, i;
|
||||
samples = track.samples || [];
|
||||
offset += 8 + 12 + 16 * samples.length;
|
||||
header = trunHeader(samples, offset);
|
||||
bytes = new Uint8Array(header.length + samples.length * 16);
|
||||
bytes.set(header);
|
||||
bytesOffest = header.length;
|
||||
|
||||
for (i = 0; i < samples.length; i++) {
|
||||
sample = samples[i];
|
||||
bytes[bytesOffest++] = (sample.duration & 0xFF000000) >>> 24;
|
||||
bytes[bytesOffest++] = (sample.duration & 0xFF0000) >>> 16;
|
||||
bytes[bytesOffest++] = (sample.duration & 0xFF00) >>> 8;
|
||||
bytes[bytesOffest++] = sample.duration & 0xFF; // sample_duration
|
||||
|
||||
bytes[bytesOffest++] = (sample.size & 0xFF000000) >>> 24;
|
||||
bytes[bytesOffest++] = (sample.size & 0xFF0000) >>> 16;
|
||||
bytes[bytesOffest++] = (sample.size & 0xFF00) >>> 8;
|
||||
bytes[bytesOffest++] = sample.size & 0xFF; // sample_size
|
||||
|
||||
bytes[bytesOffest++] = sample.flags.isLeading << 2 | sample.flags.dependsOn;
|
||||
bytes[bytesOffest++] = sample.flags.isDependedOn << 6 | sample.flags.hasRedundancy << 4 | sample.flags.paddingValue << 1 | sample.flags.isNonSyncSample;
|
||||
bytes[bytesOffest++] = sample.flags.degradationPriority & 0xF0 << 8;
|
||||
bytes[bytesOffest++] = sample.flags.degradationPriority & 0x0F; // sample_flags
|
||||
|
||||
bytes[bytesOffest++] = (sample.compositionTimeOffset & 0xFF000000) >>> 24;
|
||||
bytes[bytesOffest++] = (sample.compositionTimeOffset & 0xFF0000) >>> 16;
|
||||
bytes[bytesOffest++] = (sample.compositionTimeOffset & 0xFF00) >>> 8;
|
||||
bytes[bytesOffest++] = sample.compositionTimeOffset & 0xFF; // sample_composition_time_offset
|
||||
}
|
||||
|
||||
return box(types.trun, bytes);
|
||||
};
|
||||
|
||||
audioTrun = function audioTrun(track, offset) {
|
||||
var bytes, bytesOffest, header, samples, sample, i;
|
||||
samples = track.samples || [];
|
||||
offset += 8 + 12 + 8 * samples.length;
|
||||
header = trunHeader(samples, offset);
|
||||
bytes = new Uint8Array(header.length + samples.length * 8);
|
||||
bytes.set(header);
|
||||
bytesOffest = header.length;
|
||||
|
||||
for (i = 0; i < samples.length; i++) {
|
||||
sample = samples[i];
|
||||
bytes[bytesOffest++] = (sample.duration & 0xFF000000) >>> 24;
|
||||
bytes[bytesOffest++] = (sample.duration & 0xFF0000) >>> 16;
|
||||
bytes[bytesOffest++] = (sample.duration & 0xFF00) >>> 8;
|
||||
bytes[bytesOffest++] = sample.duration & 0xFF; // sample_duration
|
||||
|
||||
bytes[bytesOffest++] = (sample.size & 0xFF000000) >>> 24;
|
||||
bytes[bytesOffest++] = (sample.size & 0xFF0000) >>> 16;
|
||||
bytes[bytesOffest++] = (sample.size & 0xFF00) >>> 8;
|
||||
bytes[bytesOffest++] = sample.size & 0xFF; // sample_size
|
||||
}
|
||||
|
||||
return box(types.trun, bytes);
|
||||
};
|
||||
|
||||
trun = function trun(track, offset) {
|
||||
if (track.type === 'audio') {
|
||||
return audioTrun(track, offset);
|
||||
}
|
||||
|
||||
return videoTrun(track, offset);
|
||||
};
|
||||
})();
|
||||
|
||||
module.exports = {
|
||||
ftyp: ftyp,
|
||||
mdat: mdat,
|
||||
moof: moof,
|
||||
moov: moov,
|
||||
initSegment: function initSegment(tracks) {
|
||||
var fileType = ftyp(),
|
||||
movie = moov(tracks),
|
||||
result;
|
||||
result = new Uint8Array(fileType.byteLength + movie.byteLength);
|
||||
result.set(fileType);
|
||||
result.set(movie, fileType.byteLength);
|
||||
return result;
|
||||
}
|
||||
};
|
12
node_modules/mux.js/cjs/mp4/parse-type.js
generated
vendored
Normal file
12
node_modules/mux.js/cjs/mp4/parse-type.js
generated
vendored
Normal file
|
@ -0,0 +1,12 @@
|
|||
"use strict";
|
||||
|
||||
var parseType = function parseType(buffer) {
|
||||
var result = '';
|
||||
result += String.fromCharCode(buffer[0]);
|
||||
result += String.fromCharCode(buffer[1]);
|
||||
result += String.fromCharCode(buffer[2]);
|
||||
result += String.fromCharCode(buffer[3]);
|
||||
return result;
|
||||
};
|
||||
|
||||
module.exports = parseType;
|
335
node_modules/mux.js/cjs/mp4/probe.js
generated
vendored
Normal file
335
node_modules/mux.js/cjs/mp4/probe.js
generated
vendored
Normal file
|
@ -0,0 +1,335 @@
|
|||
/**
|
||||
* mux.js
|
||||
*
|
||||
* Copyright (c) Brightcove
|
||||
* Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
|
||||
*
|
||||
* Utilities to detect basic properties and metadata about MP4s.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var toUnsigned = require('../utils/bin').toUnsigned;
|
||||
|
||||
var toHexString = require('../utils/bin').toHexString;
|
||||
|
||||
var findBox = require('../mp4/find-box.js');
|
||||
|
||||
var parseType = require('../mp4/parse-type.js');
|
||||
|
||||
var parseTfhd = require('../tools/parse-tfhd.js');
|
||||
|
||||
var parseTrun = require('../tools/parse-trun.js');
|
||||
|
||||
var parseTfdt = require('../tools/parse-tfdt.js');
|
||||
|
||||
var timescale, startTime, compositionStartTime, getVideoTrackIds, getTracks, getTimescaleFromMediaHeader;
|
||||
/**
|
||||
* Parses an MP4 initialization segment and extracts the timescale
|
||||
* values for any declared tracks. Timescale values indicate the
|
||||
* number of clock ticks per second to assume for time-based values
|
||||
* elsewhere in the MP4.
|
||||
*
|
||||
* To determine the start time of an MP4, you need two pieces of
|
||||
* information: the timescale unit and the earliest base media decode
|
||||
* time. Multiple timescales can be specified within an MP4 but the
|
||||
* base media decode time is always expressed in the timescale from
|
||||
* the media header box for the track:
|
||||
* ```
|
||||
* moov > trak > mdia > mdhd.timescale
|
||||
* ```
|
||||
* @param init {Uint8Array} the bytes of the init segment
|
||||
* @return {object} a hash of track ids to timescale values or null if
|
||||
* the init segment is malformed.
|
||||
*/
|
||||
|
||||
timescale = function timescale(init) {
|
||||
var result = {},
|
||||
traks = findBox(init, ['moov', 'trak']); // mdhd timescale
|
||||
|
||||
return traks.reduce(function (result, trak) {
|
||||
var tkhd, version, index, id, mdhd;
|
||||
tkhd = findBox(trak, ['tkhd'])[0];
|
||||
|
||||
if (!tkhd) {
|
||||
return null;
|
||||
}
|
||||
|
||||
version = tkhd[0];
|
||||
index = version === 0 ? 12 : 20;
|
||||
id = toUnsigned(tkhd[index] << 24 | tkhd[index + 1] << 16 | tkhd[index + 2] << 8 | tkhd[index + 3]);
|
||||
mdhd = findBox(trak, ['mdia', 'mdhd'])[0];
|
||||
|
||||
if (!mdhd) {
|
||||
return null;
|
||||
}
|
||||
|
||||
version = mdhd[0];
|
||||
index = version === 0 ? 12 : 20;
|
||||
result[id] = toUnsigned(mdhd[index] << 24 | mdhd[index + 1] << 16 | mdhd[index + 2] << 8 | mdhd[index + 3]);
|
||||
return result;
|
||||
}, result);
|
||||
};
|
||||
/**
|
||||
* Determine the base media decode start time, in seconds, for an MP4
|
||||
* fragment. If multiple fragments are specified, the earliest time is
|
||||
* returned.
|
||||
*
|
||||
* The base media decode time can be parsed from track fragment
|
||||
* metadata:
|
||||
* ```
|
||||
* moof > traf > tfdt.baseMediaDecodeTime
|
||||
* ```
|
||||
* It requires the timescale value from the mdhd to interpret.
|
||||
*
|
||||
* @param timescale {object} a hash of track ids to timescale values.
|
||||
* @return {number} the earliest base media decode start time for the
|
||||
* fragment, in seconds
|
||||
*/
|
||||
|
||||
|
||||
startTime = function startTime(timescale, fragment) {
|
||||
var trafs, baseTimes, result; // we need info from two childrend of each track fragment box
|
||||
|
||||
trafs = findBox(fragment, ['moof', 'traf']); // determine the start times for each track
|
||||
|
||||
baseTimes = [].concat.apply([], trafs.map(function (traf) {
|
||||
return findBox(traf, ['tfhd']).map(function (tfhd) {
|
||||
var id, scale, baseTime; // get the track id from the tfhd
|
||||
|
||||
id = toUnsigned(tfhd[4] << 24 | tfhd[5] << 16 | tfhd[6] << 8 | tfhd[7]); // assume a 90kHz clock if no timescale was specified
|
||||
|
||||
scale = timescale[id] || 90e3; // get the base media decode time from the tfdt
|
||||
|
||||
baseTime = findBox(traf, ['tfdt']).map(function (tfdt) {
|
||||
var version, result;
|
||||
version = tfdt[0];
|
||||
result = toUnsigned(tfdt[4] << 24 | tfdt[5] << 16 | tfdt[6] << 8 | tfdt[7]);
|
||||
|
||||
if (version === 1) {
|
||||
result *= Math.pow(2, 32);
|
||||
result += toUnsigned(tfdt[8] << 24 | tfdt[9] << 16 | tfdt[10] << 8 | tfdt[11]);
|
||||
}
|
||||
|
||||
return result;
|
||||
})[0];
|
||||
baseTime = typeof baseTime === 'number' && !isNaN(baseTime) ? baseTime : Infinity; // convert base time to seconds
|
||||
|
||||
return baseTime / scale;
|
||||
});
|
||||
})); // return the minimum
|
||||
|
||||
result = Math.min.apply(null, baseTimes);
|
||||
return isFinite(result) ? result : 0;
|
||||
};
|
||||
/**
|
||||
* Determine the composition start, in seconds, for an MP4
|
||||
* fragment.
|
||||
*
|
||||
* The composition start time of a fragment can be calculated using the base
|
||||
* media decode time, composition time offset, and timescale, as follows:
|
||||
*
|
||||
* compositionStartTime = (baseMediaDecodeTime + compositionTimeOffset) / timescale
|
||||
*
|
||||
* All of the aforementioned information is contained within a media fragment's
|
||||
* `traf` box, except for timescale info, which comes from the initialization
|
||||
* segment, so a track id (also contained within a `traf`) is also necessary to
|
||||
* associate it with a timescale
|
||||
*
|
||||
*
|
||||
* @param timescales {object} - a hash of track ids to timescale values.
|
||||
* @param fragment {Unit8Array} - the bytes of a media segment
|
||||
* @return {number} the composition start time for the fragment, in seconds
|
||||
**/
|
||||
|
||||
|
||||
compositionStartTime = function compositionStartTime(timescales, fragment) {
|
||||
var trafBoxes = findBox(fragment, ['moof', 'traf']);
|
||||
var baseMediaDecodeTime = 0;
|
||||
var compositionTimeOffset = 0;
|
||||
var trackId;
|
||||
|
||||
if (trafBoxes && trafBoxes.length) {
|
||||
// The spec states that track run samples contained within a `traf` box are contiguous, but
|
||||
// it does not explicitly state whether the `traf` boxes themselves are contiguous.
|
||||
// We will assume that they are, so we only need the first to calculate start time.
|
||||
var tfhd = findBox(trafBoxes[0], ['tfhd'])[0];
|
||||
var trun = findBox(trafBoxes[0], ['trun'])[0];
|
||||
var tfdt = findBox(trafBoxes[0], ['tfdt'])[0];
|
||||
|
||||
if (tfhd) {
|
||||
var parsedTfhd = parseTfhd(tfhd);
|
||||
trackId = parsedTfhd.trackId;
|
||||
}
|
||||
|
||||
if (tfdt) {
|
||||
var parsedTfdt = parseTfdt(tfdt);
|
||||
baseMediaDecodeTime = parsedTfdt.baseMediaDecodeTime;
|
||||
}
|
||||
|
||||
if (trun) {
|
||||
var parsedTrun = parseTrun(trun);
|
||||
|
||||
if (parsedTrun.samples && parsedTrun.samples.length) {
|
||||
compositionTimeOffset = parsedTrun.samples[0].compositionTimeOffset || 0;
|
||||
}
|
||||
}
|
||||
} // Get timescale for this specific track. Assume a 90kHz clock if no timescale was
|
||||
// specified.
|
||||
|
||||
|
||||
var timescale = timescales[trackId] || 90e3; // return the composition start time, in seconds
|
||||
|
||||
return (baseMediaDecodeTime + compositionTimeOffset) / timescale;
|
||||
};
|
||||
/**
|
||||
* Find the trackIds of the video tracks in this source.
|
||||
* Found by parsing the Handler Reference and Track Header Boxes:
|
||||
* moov > trak > mdia > hdlr
|
||||
* moov > trak > tkhd
|
||||
*
|
||||
* @param {Uint8Array} init - The bytes of the init segment for this source
|
||||
* @return {Number[]} A list of trackIds
|
||||
*
|
||||
* @see ISO-BMFF-12/2015, Section 8.4.3
|
||||
**/
|
||||
|
||||
|
||||
getVideoTrackIds = function getVideoTrackIds(init) {
|
||||
var traks = findBox(init, ['moov', 'trak']);
|
||||
var videoTrackIds = [];
|
||||
traks.forEach(function (trak) {
|
||||
var hdlrs = findBox(trak, ['mdia', 'hdlr']);
|
||||
var tkhds = findBox(trak, ['tkhd']);
|
||||
hdlrs.forEach(function (hdlr, index) {
|
||||
var handlerType = parseType(hdlr.subarray(8, 12));
|
||||
var tkhd = tkhds[index];
|
||||
var view;
|
||||
var version;
|
||||
var trackId;
|
||||
|
||||
if (handlerType === 'vide') {
|
||||
view = new DataView(tkhd.buffer, tkhd.byteOffset, tkhd.byteLength);
|
||||
version = view.getUint8(0);
|
||||
trackId = version === 0 ? view.getUint32(12) : view.getUint32(20);
|
||||
videoTrackIds.push(trackId);
|
||||
}
|
||||
});
|
||||
});
|
||||
return videoTrackIds;
|
||||
};
|
||||
|
||||
getTimescaleFromMediaHeader = function getTimescaleFromMediaHeader(mdhd) {
|
||||
// mdhd is a FullBox, meaning it will have its own version as the first byte
|
||||
var version = mdhd[0];
|
||||
var index = version === 0 ? 12 : 20;
|
||||
return toUnsigned(mdhd[index] << 24 | mdhd[index + 1] << 16 | mdhd[index + 2] << 8 | mdhd[index + 3]);
|
||||
};
|
||||
/**
|
||||
* Get all the video, audio, and hint tracks from a non fragmented
|
||||
* mp4 segment
|
||||
*/
|
||||
|
||||
|
||||
getTracks = function getTracks(init) {
|
||||
var traks = findBox(init, ['moov', 'trak']);
|
||||
var tracks = [];
|
||||
traks.forEach(function (trak) {
|
||||
var track = {};
|
||||
var tkhd = findBox(trak, ['tkhd'])[0];
|
||||
var view, tkhdVersion; // id
|
||||
|
||||
if (tkhd) {
|
||||
view = new DataView(tkhd.buffer, tkhd.byteOffset, tkhd.byteLength);
|
||||
tkhdVersion = view.getUint8(0);
|
||||
track.id = tkhdVersion === 0 ? view.getUint32(12) : view.getUint32(20);
|
||||
}
|
||||
|
||||
var hdlr = findBox(trak, ['mdia', 'hdlr'])[0]; // type
|
||||
|
||||
if (hdlr) {
|
||||
var type = parseType(hdlr.subarray(8, 12));
|
||||
|
||||
if (type === 'vide') {
|
||||
track.type = 'video';
|
||||
} else if (type === 'soun') {
|
||||
track.type = 'audio';
|
||||
} else {
|
||||
track.type = type;
|
||||
}
|
||||
} // codec
|
||||
|
||||
|
||||
var stsd = findBox(trak, ['mdia', 'minf', 'stbl', 'stsd'])[0];
|
||||
|
||||
if (stsd) {
|
||||
var sampleDescriptions = stsd.subarray(8); // gives the codec type string
|
||||
|
||||
track.codec = parseType(sampleDescriptions.subarray(4, 8));
|
||||
var codecBox = findBox(sampleDescriptions, [track.codec])[0];
|
||||
var codecConfig, codecConfigType;
|
||||
|
||||
if (codecBox) {
|
||||
// https://tools.ietf.org/html/rfc6381#section-3.3
|
||||
if (/^[a-z]vc[1-9]$/i.test(track.codec)) {
|
||||
// we don't need anything but the "config" parameter of the
|
||||
// avc1 codecBox
|
||||
codecConfig = codecBox.subarray(78);
|
||||
codecConfigType = parseType(codecConfig.subarray(4, 8));
|
||||
|
||||
if (codecConfigType === 'avcC' && codecConfig.length > 11) {
|
||||
track.codec += '.'; // left padded with zeroes for single digit hex
|
||||
// profile idc
|
||||
|
||||
track.codec += toHexString(codecConfig[9]); // the byte containing the constraint_set flags
|
||||
|
||||
track.codec += toHexString(codecConfig[10]); // level idc
|
||||
|
||||
track.codec += toHexString(codecConfig[11]);
|
||||
} else {
|
||||
// TODO: show a warning that we couldn't parse the codec
|
||||
// and are using the default
|
||||
track.codec = 'avc1.4d400d';
|
||||
}
|
||||
} else if (/^mp4[a,v]$/i.test(track.codec)) {
|
||||
// we do not need anything but the streamDescriptor of the mp4a codecBox
|
||||
codecConfig = codecBox.subarray(28);
|
||||
codecConfigType = parseType(codecConfig.subarray(4, 8));
|
||||
|
||||
if (codecConfigType === 'esds' && codecConfig.length > 20 && codecConfig[19] !== 0) {
|
||||
track.codec += '.' + toHexString(codecConfig[19]); // this value is only a single digit
|
||||
|
||||
track.codec += '.' + toHexString(codecConfig[20] >>> 2 & 0x3f).replace(/^0/, '');
|
||||
} else {
|
||||
// TODO: show a warning that we couldn't parse the codec
|
||||
// and are using the default
|
||||
track.codec = 'mp4a.40.2';
|
||||
}
|
||||
} else {
|
||||
// flac, opus, etc
|
||||
track.codec = track.codec.toLowerCase();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var mdhd = findBox(trak, ['mdia', 'mdhd'])[0];
|
||||
|
||||
if (mdhd) {
|
||||
track.timescale = getTimescaleFromMediaHeader(mdhd);
|
||||
}
|
||||
|
||||
tracks.push(track);
|
||||
});
|
||||
return tracks;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
// export mp4 inspector's findBox and parseType for backwards compatibility
|
||||
findBox: findBox,
|
||||
parseType: parseType,
|
||||
timescale: timescale,
|
||||
startTime: startTime,
|
||||
compositionStartTime: compositionStartTime,
|
||||
videoTrackIds: getVideoTrackIds,
|
||||
tracks: getTracks,
|
||||
getTimescaleFromMediaHeader: getTimescaleFromMediaHeader
|
||||
};
|
108
node_modules/mux.js/cjs/mp4/track-decode-info.js
generated
vendored
Normal file
108
node_modules/mux.js/cjs/mp4/track-decode-info.js
generated
vendored
Normal file
|
@ -0,0 +1,108 @@
|
|||
"use strict";
|
||||
|
||||
/**
|
||||
* mux.js
|
||||
*
|
||||
* Copyright (c) Brightcove
|
||||
* Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
|
||||
*/
|
||||
var ONE_SECOND_IN_TS = require('../utils/clock').ONE_SECOND_IN_TS;
|
||||
/**
|
||||
* Store information about the start and end of the track and the
|
||||
* duration for each frame/sample we process in order to calculate
|
||||
* the baseMediaDecodeTime
|
||||
*/
|
||||
|
||||
|
||||
var collectDtsInfo = function collectDtsInfo(track, data) {
|
||||
if (typeof data.pts === 'number') {
|
||||
if (track.timelineStartInfo.pts === undefined) {
|
||||
track.timelineStartInfo.pts = data.pts;
|
||||
}
|
||||
|
||||
if (track.minSegmentPts === undefined) {
|
||||
track.minSegmentPts = data.pts;
|
||||
} else {
|
||||
track.minSegmentPts = Math.min(track.minSegmentPts, data.pts);
|
||||
}
|
||||
|
||||
if (track.maxSegmentPts === undefined) {
|
||||
track.maxSegmentPts = data.pts;
|
||||
} else {
|
||||
track.maxSegmentPts = Math.max(track.maxSegmentPts, data.pts);
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof data.dts === 'number') {
|
||||
if (track.timelineStartInfo.dts === undefined) {
|
||||
track.timelineStartInfo.dts = data.dts;
|
||||
}
|
||||
|
||||
if (track.minSegmentDts === undefined) {
|
||||
track.minSegmentDts = data.dts;
|
||||
} else {
|
||||
track.minSegmentDts = Math.min(track.minSegmentDts, data.dts);
|
||||
}
|
||||
|
||||
if (track.maxSegmentDts === undefined) {
|
||||
track.maxSegmentDts = data.dts;
|
||||
} else {
|
||||
track.maxSegmentDts = Math.max(track.maxSegmentDts, data.dts);
|
||||
}
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Clear values used to calculate the baseMediaDecodeTime between
|
||||
* tracks
|
||||
*/
|
||||
|
||||
|
||||
var clearDtsInfo = function clearDtsInfo(track) {
|
||||
delete track.minSegmentDts;
|
||||
delete track.maxSegmentDts;
|
||||
delete track.minSegmentPts;
|
||||
delete track.maxSegmentPts;
|
||||
};
|
||||
/**
|
||||
* Calculate the track's baseMediaDecodeTime based on the earliest
|
||||
* DTS the transmuxer has ever seen and the minimum DTS for the
|
||||
* current track
|
||||
* @param track {object} track metadata configuration
|
||||
* @param keepOriginalTimestamps {boolean} If true, keep the timestamps
|
||||
* in the source; false to adjust the first segment to start at 0.
|
||||
*/
|
||||
|
||||
|
||||
var calculateTrackBaseMediaDecodeTime = function calculateTrackBaseMediaDecodeTime(track, keepOriginalTimestamps) {
|
||||
var baseMediaDecodeTime,
|
||||
scale,
|
||||
minSegmentDts = track.minSegmentDts; // Optionally adjust the time so the first segment starts at zero.
|
||||
|
||||
if (!keepOriginalTimestamps) {
|
||||
minSegmentDts -= track.timelineStartInfo.dts;
|
||||
} // track.timelineStartInfo.baseMediaDecodeTime is the location, in time, where
|
||||
// we want the start of the first segment to be placed
|
||||
|
||||
|
||||
baseMediaDecodeTime = track.timelineStartInfo.baseMediaDecodeTime; // Add to that the distance this segment is from the very first
|
||||
|
||||
baseMediaDecodeTime += minSegmentDts; // baseMediaDecodeTime must not become negative
|
||||
|
||||
baseMediaDecodeTime = Math.max(0, baseMediaDecodeTime);
|
||||
|
||||
if (track.type === 'audio') {
|
||||
// Audio has a different clock equal to the sampling_rate so we need to
|
||||
// scale the PTS values into the clock rate of the track
|
||||
scale = track.samplerate / ONE_SECOND_IN_TS;
|
||||
baseMediaDecodeTime *= scale;
|
||||
baseMediaDecodeTime = Math.floor(baseMediaDecodeTime);
|
||||
}
|
||||
|
||||
return baseMediaDecodeTime;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
clearDtsInfo: clearDtsInfo,
|
||||
calculateTrackBaseMediaDecodeTime: calculateTrackBaseMediaDecodeTime,
|
||||
collectDtsInfo: collectDtsInfo
|
||||
};
|
1077
node_modules/mux.js/cjs/mp4/transmuxer.js
generated
vendored
Normal file
1077
node_modules/mux.js/cjs/mp4/transmuxer.js
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue