ad82ecc0 by David LaPalomento

Merge pull request #405 from dmlap/remove-transmuxing

Remove old transmuxing infrastructure
2 parents cd596f1a f8527bfc
(function(window) {
var
FlvTag = window.videojs.Hls.FlvTag,
adtsSampleingRates = [
96000, 88200,
64000, 48000,
44100, 32000,
24000, 22050,
16000, 12000
];
window.videojs.Hls.AacStream = function() {
var
next_pts, // :uint
state, // :uint
pes_length, // :int
lastMetaPts,
adtsProtectionAbsent, // :Boolean
adtsObjectType, // :int
adtsSampleingIndex, // :int
adtsChanelConfig, // :int
adtsFrameSize, // :int
adtsSampleCount, // :int
adtsDuration, // :int
aacFrame, // :FlvTag = null;
extraData; // :uint;
this.tags = [];
// (pts:uint):void
this.setTimeStampOffset = function(pts) {
// keep track of the last time a metadata tag was written out
// set the initial value so metadata will be generated before any
// payload data
lastMetaPts = pts - 1000;
};
// (pts:uint, pes_size:int, dataAligned:Boolean):void
this.setNextTimeStamp = function(pts, pes_size, dataAligned) {
next_pts = pts;
pes_length = pes_size;
// If data is aligned, flush all internal buffers
if (dataAligned) {
state = 0;
}
};
// (data:ByteArray, o:int = 0, l:int = 0):void
this.writeBytes = function(data, offset, length) {
var
end, // :int
newExtraData, // :uint
bytesToCopy; // :int
// default arguments
offset = offset || 0;
length = length || 0;
// Do not allow more than 'pes_length' bytes to be written
length = (pes_length < length ? pes_length : length);
pes_length -= length;
end = offset + length;
while (offset < end) {
switch (state) {
default:
state = 0;
break;
case 0:
if (offset >= end) {
return;
}
if (0xFF !== data[offset]) {
console.assert(false, 'Error no ATDS header found');
offset += 1;
state = 0;
return;
}
offset += 1;
state = 1;
break;
case 1:
if (offset >= end) {
return;
}
if (0xF0 !== (data[offset] & 0xF0)) {
console.assert(false, 'Error no ATDS header found');
offset +=1;
state = 0;
return;
}
adtsProtectionAbsent = !!(data[offset] & 0x01);
offset += 1;
state = 2;
break;
case 2:
if (offset >= end) {
return;
}
adtsObjectType = ((data[offset] & 0xC0) >>> 6) + 1;
adtsSampleingIndex = ((data[offset] & 0x3C) >>> 2);
adtsChanelConfig = ((data[offset] & 0x01) << 2);
offset += 1;
state = 3;
break;
case 3:
if (offset >= end) {
return;
}
adtsChanelConfig |= ((data[offset] & 0xC0) >>> 6);
adtsFrameSize = ((data[offset] & 0x03) << 11);
offset += 1;
state = 4;
break;
case 4:
if (offset >= end) {
return;
}
adtsFrameSize |= (data[offset] << 3);
offset += 1;
state = 5;
break;
case 5:
if(offset >= end) {
return;
}
adtsFrameSize |= ((data[offset] & 0xE0) >>> 5);
adtsFrameSize -= (adtsProtectionAbsent ? 7 : 9);
offset += 1;
state = 6;
break;
case 6:
if (offset >= end) {
return;
}
adtsSampleCount = ((data[offset] & 0x03) + 1) * 1024;
adtsDuration = (adtsSampleCount * 1000) / adtsSampleingRates[adtsSampleingIndex];
newExtraData = (adtsObjectType << 11) |
(adtsSampleingIndex << 7) |
(adtsChanelConfig << 3);
// write out metadata tags every 1 second so that the decoder
// is re-initialized quickly after seeking into a different
// audio configuration
if (newExtraData !== extraData || next_pts - lastMetaPts >= 1000) {
aacFrame = new FlvTag(FlvTag.METADATA_TAG);
aacFrame.pts = next_pts;
aacFrame.dts = next_pts;
// AAC is always 10
aacFrame.writeMetaDataDouble("audiocodecid", 10);
aacFrame.writeMetaDataBoolean("stereo", 2 === adtsChanelConfig);
aacFrame.writeMetaDataDouble ("audiosamplerate", adtsSampleingRates[adtsSampleingIndex]);
// Is AAC always 16 bit?
aacFrame.writeMetaDataDouble ("audiosamplesize", 16);
this.tags.push(aacFrame);
extraData = newExtraData;
aacFrame = new FlvTag(FlvTag.AUDIO_TAG, true);
// For audio, DTS is always the same as PTS. We want to set the DTS
// however so we can compare with video DTS to determine approximate
// packet order
aacFrame.pts = next_pts;
aacFrame.dts = aacFrame.pts;
aacFrame.view.setUint16(aacFrame.position, newExtraData);
aacFrame.position += 2;
aacFrame.length = Math.max(aacFrame.length, aacFrame.position);
this.tags.push(aacFrame);
lastMetaPts = next_pts;
}
// Skip the checksum if there is one
offset += 1;
state = 7;
break;
case 7:
if (!adtsProtectionAbsent) {
if (2 > (end - offset)) {
return;
} else {
offset += 2;
}
}
aacFrame = new FlvTag(FlvTag.AUDIO_TAG);
aacFrame.pts = next_pts;
aacFrame.dts = next_pts;
state = 8;
break;
case 8:
while (adtsFrameSize) {
if (offset >= end) {
return;
}
bytesToCopy = (end - offset) < adtsFrameSize ? (end - offset) : adtsFrameSize;
aacFrame.writeBytes(data, offset, bytesToCopy);
offset += bytesToCopy;
adtsFrameSize -= bytesToCopy;
}
this.tags.push(aacFrame);
// finished with this frame
state = 0;
next_pts += adtsDuration;
}
}
};
};
})(this);
(function(window) {
/**
* Parser for exponential Golomb codes, a variable-bitwidth number encoding
* scheme used by h264.
*/
window.videojs.Hls.ExpGolomb = function(workingData) {
var
// the number of bytes left to examine in workingData
workingBytesAvailable = workingData.byteLength,
// the current word being examined
workingWord = 0, // :uint
// the number of bits left to examine in the current word
workingBitsAvailable = 0; // :uint;
// ():uint
this.length = function() {
return (8 * workingBytesAvailable);
};
// ():uint
this.bitsAvailable = function() {
return (8 * workingBytesAvailable) + workingBitsAvailable;
};
// ():void
this.loadWord = function() {
var
position = workingData.byteLength - workingBytesAvailable,
workingBytes = new Uint8Array(4),
availableBytes = Math.min(4, workingBytesAvailable);
if (availableBytes === 0) {
throw new Error('no bytes available');
}
workingBytes.set(workingData.subarray(position,
position + availableBytes));
workingWord = new DataView(workingBytes.buffer).getUint32(0);
// track the amount of workingData that has been processed
workingBitsAvailable = availableBytes * 8;
workingBytesAvailable -= availableBytes;
};
// (count:int):void
this.skipBits = function(count) {
var skipBytes; // :int
if (workingBitsAvailable > count) {
workingWord <<= count;
workingBitsAvailable -= count;
} else {
count -= workingBitsAvailable;
skipBytes = count / 8;
count -= (skipBytes * 8);
workingBytesAvailable -= skipBytes;
this.loadWord();
workingWord <<= count;
workingBitsAvailable -= count;
}
};
// (size:int):uint
this.readBits = function(size) {
var
bits = Math.min(workingBitsAvailable, size), // :uint
valu = workingWord >>> (32 - bits); // :uint
console.assert(size < 32, 'Cannot read more than 32 bits at a time');
workingBitsAvailable -= bits;
if (workingBitsAvailable > 0) {
workingWord <<= bits;
} else if (workingBytesAvailable > 0) {
this.loadWord();
}
bits = size - bits;
if (bits > 0) {
return valu << bits | this.readBits(bits);
} else {
return valu;
}
};
// ():uint
this.skipLeadingZeros = function() {
var leadingZeroCount; // :uint
for (leadingZeroCount = 0 ; leadingZeroCount < workingBitsAvailable ; ++leadingZeroCount) {
if (0 !== (workingWord & (0x80000000 >>> leadingZeroCount))) {
// the first bit of working word is 1
workingWord <<= leadingZeroCount;
workingBitsAvailable -= leadingZeroCount;
return leadingZeroCount;
}
}
// we exhausted workingWord and still have not found a 1
this.loadWord();
return leadingZeroCount + this.skipLeadingZeros();
};
// ():void
this.skipUnsignedExpGolomb = function() {
this.skipBits(1 + this.skipLeadingZeros());
};
// ():void
this.skipExpGolomb = function() {
this.skipBits(1 + this.skipLeadingZeros());
};
// ():uint
this.readUnsignedExpGolomb = function() {
var clz = this.skipLeadingZeros(); // :uint
return this.readBits(clz + 1) - 1;
};
// ():int
this.readExpGolomb = function() {
var valu = this.readUnsignedExpGolomb(); // :int
if (0x01 & valu) {
// the number is odd if the low order bit is set
return (1 + valu) >>> 1; // add 1 to make it even, and divide by 2
} else {
return -1 * (valu >>> 1); // divide by two then make it negative
}
};
// Some convenience functions
// :Boolean
this.readBoolean = function() {
return 1 === this.readBits(1);
};
// ():int
this.readUnsignedByte = function() {
return this.readBits(8);
};
this.loadWord();
};
})(this);
/**
* An object that stores the bytes of an FLV tag and methods for
* querying and manipulating that data.
* @see http://download.macromedia.com/f4v/video_file_format_spec_v10_1.pdf
*/
(function(window) {
window.videojs = window.videojs || {};
window.videojs.Hls = window.videojs.Hls || {};
var hls = window.videojs.Hls;
// (type:uint, extraData:Boolean = false) extends ByteArray
hls.FlvTag = function(type, extraData) {
var
// Counter if this is a metadata tag, nal start marker if this is a video
// tag. unused if this is an audio tag
adHoc = 0, // :uint
// checks whether the FLV tag has enough capacity to accept the proposed
// write and re-allocates the internal buffers if necessary
prepareWrite = function(flv, count) {
var
bytes,
minLength = flv.position + count;
if (minLength < flv.bytes.byteLength) {
// there's enough capacity so do nothing
return;
}
// allocate a new buffer and copy over the data that will not be modified
bytes = new Uint8Array(minLength * 2);
bytes.set(flv.bytes.subarray(0, flv.position), 0);
flv.bytes = bytes;
flv.view = new DataView(flv.bytes.buffer);
},
// commonly used metadata properties
widthBytes = hls.FlvTag.widthBytes || new Uint8Array('width'.length),
heightBytes = hls.FlvTag.heightBytes || new Uint8Array('height'.length),
videocodecidBytes = hls.FlvTag.videocodecidBytes || new Uint8Array('videocodecid'.length),
i;
if (!hls.FlvTag.widthBytes) {
// calculating the bytes of common metadata names ahead of time makes the
// corresponding writes faster because we don't have to loop over the
// characters
// re-test with test/perf.html if you're planning on changing this
for (i = 0; i < 'width'.length; i++) {
widthBytes[i] = 'width'.charCodeAt(i);
}
for (i = 0; i < 'height'.length; i++) {
heightBytes[i] = 'height'.charCodeAt(i);
}
for (i = 0; i < 'videocodecid'.length; i++) {
videocodecidBytes[i] = 'videocodecid'.charCodeAt(i);
}
hls.FlvTag.widthBytes = widthBytes;
hls.FlvTag.heightBytes = heightBytes;
hls.FlvTag.videocodecidBytes = videocodecidBytes;
}
this.keyFrame = false; // :Boolean
switch(type) {
case hls.FlvTag.VIDEO_TAG:
this.length = 16;
break;
case hls.FlvTag.AUDIO_TAG:
this.length = 13;
this.keyFrame = true;
break;
case hls.FlvTag.METADATA_TAG:
this.length = 29;
this.keyFrame = true;
break;
default:
throw("Error Unknown TagType");
}
this.bytes = new Uint8Array(16384);
this.view = new DataView(this.bytes.buffer);
this.bytes[0] = type;
this.position = this.length;
this.keyFrame = extraData; // Defaults to false
// presentation timestamp
this.pts = 0;
// decoder timestamp
this.dts = 0;
// ByteArray#writeBytes(bytes:ByteArray, offset:uint = 0, length:uint = 0)
this.writeBytes = function(bytes, offset, length) {
var
start = offset || 0,
end;
length = length || bytes.byteLength;
end = start + length;
prepareWrite(this, length);
this.bytes.set(bytes.subarray(start, end), this.position);
this.position += length;
this.length = Math.max(this.length, this.position);
};
// ByteArray#writeByte(value:int):void
this.writeByte = function(byte) {
prepareWrite(this, 1);
this.bytes[this.position] = byte;
this.position++;
this.length = Math.max(this.length, this.position);
};
// ByteArray#writeShort(value:int):void
this.writeShort = function(short) {
prepareWrite(this, 2);
this.view.setUint16(this.position, short);
this.position += 2;
this.length = Math.max(this.length, this.position);
};
// Negative index into array
// (pos:uint):int
this.negIndex = function(pos) {
return this.bytes[this.length - pos];
};
// The functions below ONLY work when this[0] == VIDEO_TAG.
// We are not going to check for that because we dont want the overhead
// (nal:ByteArray = null):int
this.nalUnitSize = function() {
if (adHoc === 0) {
return 0;
}
return this.length - (adHoc + 4);
};
this.startNalUnit = function() {
// remember position and add 4 bytes
if (adHoc > 0) {
throw new Error("Attempted to create new NAL wihout closing the old one");
}
// reserve 4 bytes for nal unit size
adHoc = this.length;
this.length += 4;
this.position = this.length;
};
// (nal:ByteArray = null):void
this.endNalUnit = function(nalContainer) {
var
nalStart, // :uint
nalLength; // :uint
// Rewind to the marker and write the size
if (this.length === adHoc + 4) {
// we started a nal unit, but didnt write one, so roll back the 4 byte size value
this.length -= 4;
} else if (adHoc > 0) {
nalStart = adHoc + 4;
nalLength = this.length - nalStart;
this.position = adHoc;
this.view.setUint32(this.position, nalLength);
this.position = this.length;
if (nalContainer) {
// Add the tag to the NAL unit
nalContainer.push(this.bytes.subarray(nalStart, nalStart + nalLength));
}
}
adHoc = 0;
};
/**
* Write out a 64-bit floating point valued metadata property. This method is
* called frequently during a typical parse and needs to be fast.
*/
// (key:String, val:Number):void
this.writeMetaDataDouble = function(key, val) {
var i;
prepareWrite(this, 2 + key.length + 9);
// write size of property name
this.view.setUint16(this.position, key.length);
this.position += 2;
// this next part looks terrible but it improves parser throughput by
// 10kB/s in my testing
// write property name
if (key === 'width') {
this.bytes.set(widthBytes, this.position);
this.position += 5;
} else if (key === 'height') {
this.bytes.set(heightBytes, this.position);
this.position += 6;
} else if (key === 'videocodecid') {
this.bytes.set(videocodecidBytes, this.position);
this.position += 12;
} else {
for (i = 0; i < key.length; i++) {
this.bytes[this.position] = key.charCodeAt(i);
this.position++;
}
}
// skip null byte
this.position++;
// write property value
this.view.setFloat64(this.position, val);
this.position += 8;
// update flv tag length
this.length = Math.max(this.length, this.position);
++adHoc;
};
// (key:String, val:Boolean):void
this.writeMetaDataBoolean = function(key, val) {
var i;
prepareWrite(this, 2);
this.view.setUint16(this.position, key.length);
this.position += 2;
for (i = 0; i < key.length; i++) {
console.assert(key.charCodeAt(i) < 255);
prepareWrite(this, 1);
this.bytes[this.position] = key.charCodeAt(i);
this.position++;
}
prepareWrite(this, 2);
this.view.setUint8(this.position, 0x01);
this.position++;
this.view.setUint8(this.position, val ? 0x01 : 0x00);
this.position++;
this.length = Math.max(this.length, this.position);
++adHoc;
};
// ():ByteArray
this.finalize = function() {
var
dtsDelta, // :int
len; // :int
switch(this.bytes[0]) {
// Video Data
case hls.FlvTag.VIDEO_TAG:
this.bytes[11] = ((this.keyFrame || extraData) ? 0x10 : 0x20 ) | 0x07; // We only support AVC, 1 = key frame (for AVC, a seekable frame), 2 = inter frame (for AVC, a non-seekable frame)
this.bytes[12] = extraData ? 0x00 : 0x01;
dtsDelta = this.pts - this.dts;
this.bytes[13] = (dtsDelta & 0x00FF0000) >>> 16;
this.bytes[14] = (dtsDelta & 0x0000FF00) >>> 8;
this.bytes[15] = (dtsDelta & 0x000000FF) >>> 0;
break;
case hls.FlvTag.AUDIO_TAG:
this.bytes[11] = 0xAF; // 44 kHz, 16-bit stereo
this.bytes[12] = extraData ? 0x00 : 0x01;
break;
case hls.FlvTag.METADATA_TAG:
this.position = 11;
this.view.setUint8(this.position, 0x02); // String type
this.position++;
this.view.setUint16(this.position, 0x0A); // 10 Bytes
this.position += 2;
// set "onMetaData"
this.bytes.set([0x6f, 0x6e, 0x4d, 0x65,
0x74, 0x61, 0x44, 0x61,
0x74, 0x61], this.position);
this.position += 10;
this.bytes[this.position] = 0x08; // Array type
this.position++;
this.view.setUint32(this.position, adHoc);
this.position = this.length;
this.bytes.set([0, 0, 9], this.position);
this.position += 3; // End Data Tag
this.length = this.position;
break;
}
len = this.length - 11;
// write the DataSize field
this.bytes[ 1] = (len & 0x00FF0000) >>> 16;
this.bytes[ 2] = (len & 0x0000FF00) >>> 8;
this.bytes[ 3] = (len & 0x000000FF) >>> 0;
// write the Timestamp
this.bytes[ 4] = (this.dts & 0x00FF0000) >>> 16;
this.bytes[ 5] = (this.dts & 0x0000FF00) >>> 8;
this.bytes[ 6] = (this.dts & 0x000000FF) >>> 0;
this.bytes[ 7] = (this.dts & 0xFF000000) >>> 24;
// write the StreamID
this.bytes[ 8] = 0;
this.bytes[ 9] = 0;
this.bytes[10] = 0;
// Sometimes we're at the end of the view and have one slot to write a
// uint32, so, prepareWrite of count 4, since, view is uint8
prepareWrite(this, 4);
this.view.setUint32(this.length, this.length);
this.length += 4;
this.position += 4;
// trim down the byte buffer to what is actually being used
this.bytes = this.bytes.subarray(0, this.length);
this.frameTime = hls.FlvTag.frameTime(this.bytes);
console.assert(this.bytes.byteLength === this.length);
return this;
};
};
hls.FlvTag.AUDIO_TAG = 0x08; // == 8, :uint
hls.FlvTag.VIDEO_TAG = 0x09; // == 9, :uint
hls.FlvTag.METADATA_TAG = 0x12; // == 18, :uint
// (tag:ByteArray):Boolean {
hls.FlvTag.isAudioFrame = function(tag) {
return hls.FlvTag.AUDIO_TAG === tag[0];
};
// (tag:ByteArray):Boolean {
hls.FlvTag.isVideoFrame = function(tag) {
return hls.FlvTag.VIDEO_TAG === tag[0];
};
// (tag:ByteArray):Boolean {
hls.FlvTag.isMetaData = function(tag) {
return hls.FlvTag.METADATA_TAG === tag[0];
};
// (tag:ByteArray):Boolean {
hls.FlvTag.isKeyFrame = function(tag) {
if (hls.FlvTag.isVideoFrame(tag)) {
return tag[11] === 0x17;
}
if (hls.FlvTag.isAudioFrame(tag)) {
return true;
}
if (hls.FlvTag.isMetaData(tag)) {
return true;
}
return false;
};
// (tag:ByteArray):uint {
hls.FlvTag.frameTime = function(tag) {
var pts = tag[ 4] << 16; // :uint
pts |= tag[ 5] << 8;
pts |= tag[ 6] << 0;
pts |= tag[ 7] << 24;
return pts;
};
})(this);
(function() {
var
H264ExtraData,
ExpGolomb = window.videojs.Hls.ExpGolomb,
FlvTag = window.videojs.Hls.FlvTag;
window.videojs.Hls.H264ExtraData = H264ExtraData = function() {
this.sps = []; // :Array
this.pps = []; // :Array
};
H264ExtraData.prototype.extraDataExists = function() { // :Boolean
return this.sps.length > 0;
};
// (sizeOfScalingList:int, expGolomb:ExpGolomb):void
H264ExtraData.prototype.scaling_list = function(sizeOfScalingList, expGolomb) {
var
lastScale = 8, // :int
nextScale = 8, // :int
j,
delta_scale; // :int
for (j = 0; j < sizeOfScalingList; ++j) {
if (0 !== nextScale) {
delta_scale = expGolomb.readExpGolomb();
nextScale = (lastScale + delta_scale + 256) % 256;
//useDefaultScalingMatrixFlag = ( j = = 0 && nextScale = = 0 )
}
lastScale = (nextScale === 0) ? lastScale : nextScale;
// scalingList[ j ] = ( nextScale == 0 ) ? lastScale : nextScale;
// lastScale = scalingList[ j ]
}
};
/**
* RBSP: raw bit-stream payload. The actual encoded video data.
*
* SPS: sequence parameter set. Part of the RBSP. Metadata to be applied
* to a complete video sequence, like width and height.
*/
H264ExtraData.prototype.getSps0Rbsp = function() { // :ByteArray
var
sps = this.sps[0],
offset = 1,
start = 1,
written = 0,
end = sps.byteLength - 2,
result = new Uint8Array(sps.byteLength);
// In order to prevent 0x0000 01 from being interpreted as a
// NAL start code, occurences of that byte sequence in the
// RBSP are escaped with an "emulation byte". That turns
// sequences of 0x0000 01 into 0x0000 0301. When interpreting
// a NAL payload, they must be filtered back out.
while (offset < end) {
if (sps[offset] === 0x00 &&
sps[offset + 1] === 0x00 &&
sps[offset + 2] === 0x03) {
result.set(sps.subarray(start, offset + 1), written);
written += offset + 1 - start;
start = offset + 3;
}
offset++;
}
result.set(sps.subarray(start), written);
return result.subarray(0, written + (sps.byteLength - start));
};
// (pts:uint):FlvTag
H264ExtraData.prototype.metaDataTag = function(pts) {
var
tag = new FlvTag(FlvTag.METADATA_TAG), // :FlvTag
expGolomb, // :ExpGolomb
profile_idc, // :int
chroma_format_idc, // :int
imax, // :int
i, // :int
pic_order_cnt_type, // :int
num_ref_frames_in_pic_order_cnt_cycle, // :uint
pic_width_in_mbs_minus1, // :int
pic_height_in_map_units_minus1, // :int
frame_mbs_only_flag, // :int
frame_cropping_flag, // :Boolean
frame_crop_left_offset = 0, // :int
frame_crop_right_offset = 0, // :int
frame_crop_top_offset = 0, // :int
frame_crop_bottom_offset = 0, // :int
width,
height;
tag.dts = pts;
tag.pts = pts;
expGolomb = new ExpGolomb(this.getSps0Rbsp());
// :int = expGolomb.readUnsignedByte(); // profile_idc u(8)
profile_idc = expGolomb.readUnsignedByte();
// constraint_set[0-5]_flag, u(1), reserved_zero_2bits u(2), level_idc u(8)
expGolomb.skipBits(16);
// seq_parameter_set_id
expGolomb.skipUnsignedExpGolomb();
if (profile_idc === 100 ||
profile_idc === 110 ||
profile_idc === 122 ||
profile_idc === 244 ||
profile_idc === 44 ||
profile_idc === 83 ||
profile_idc === 86 ||
profile_idc === 118 ||
profile_idc === 128) {
chroma_format_idc = expGolomb.readUnsignedExpGolomb();
if (3 === chroma_format_idc) {
expGolomb.skipBits(1); // separate_colour_plane_flag
}
expGolomb.skipUnsignedExpGolomb(); // bit_depth_luma_minus8
expGolomb.skipUnsignedExpGolomb(); // bit_depth_chroma_minus8
expGolomb.skipBits(1); // qpprime_y_zero_transform_bypass_flag
if (expGolomb.readBoolean()) { // seq_scaling_matrix_present_flag
imax = (chroma_format_idc !== 3) ? 8 : 12;
for (i = 0 ; i < imax ; ++i) {
if (expGolomb.readBoolean()) { // seq_scaling_list_present_flag[ i ]
if (i < 6) {
this.scaling_list(16, expGolomb);
} else {
this.scaling_list(64, expGolomb);
}
}
}
}
}
expGolomb.skipUnsignedExpGolomb(); // log2_max_frame_num_minus4
pic_order_cnt_type = expGolomb.readUnsignedExpGolomb();
if ( 0 === pic_order_cnt_type ) {
expGolomb.readUnsignedExpGolomb(); //log2_max_pic_order_cnt_lsb_minus4
} else if ( 1 === pic_order_cnt_type ) {
expGolomb.skipBits(1); // delta_pic_order_always_zero_flag
expGolomb.skipExpGolomb(); // offset_for_non_ref_pic
expGolomb.skipExpGolomb(); // offset_for_top_to_bottom_field
num_ref_frames_in_pic_order_cnt_cycle = expGolomb.readUnsignedExpGolomb();
for(i = 0 ; i < num_ref_frames_in_pic_order_cnt_cycle ; ++i) {
expGolomb.skipExpGolomb(); // offset_for_ref_frame[ i ]
}
}
expGolomb.skipUnsignedExpGolomb(); // max_num_ref_frames
expGolomb.skipBits(1); // gaps_in_frame_num_value_allowed_flag
pic_width_in_mbs_minus1 = expGolomb.readUnsignedExpGolomb();
pic_height_in_map_units_minus1 = expGolomb.readUnsignedExpGolomb();
frame_mbs_only_flag = expGolomb.readBits(1);
if (0 === frame_mbs_only_flag) {
expGolomb.skipBits(1); // mb_adaptive_frame_field_flag
}
expGolomb.skipBits(1); // direct_8x8_inference_flag
frame_cropping_flag = expGolomb.readBoolean();
if (frame_cropping_flag) {
frame_crop_left_offset = expGolomb.readUnsignedExpGolomb();
frame_crop_right_offset = expGolomb.readUnsignedExpGolomb();
frame_crop_top_offset = expGolomb.readUnsignedExpGolomb();
frame_crop_bottom_offset = expGolomb.readUnsignedExpGolomb();
}
width = ((pic_width_in_mbs_minus1 + 1) * 16) - frame_crop_left_offset * 2 - frame_crop_right_offset * 2;
height = ((2 - frame_mbs_only_flag) * (pic_height_in_map_units_minus1 + 1) * 16) - (frame_crop_top_offset * 2) - (frame_crop_bottom_offset * 2);
tag.writeMetaDataDouble("videocodecid", 7);
tag.writeMetaDataDouble("width", width);
tag.writeMetaDataDouble("height", height);
// tag.writeMetaDataDouble("videodatarate", 0 );
// tag.writeMetaDataDouble("framerate", 0);
return tag;
};
// (pts:uint):FlvTag
H264ExtraData.prototype.extraDataTag = function(pts) {
var
i,
tag = new FlvTag(FlvTag.VIDEO_TAG, true);
tag.dts = pts;
tag.pts = pts;
tag.writeByte(0x01);// version
tag.writeByte(this.sps[0][1]);// profile
tag.writeByte(this.sps[0][2]);// compatibility
tag.writeByte(this.sps[0][3]);// level
tag.writeByte(0xFC | 0x03); // reserved (6 bits), NULA length size - 1 (2 bits)
tag.writeByte(0xE0 | 0x01 ); // reserved (3 bits), num of SPS (5 bits)
tag.writeShort( this.sps[0].length ); // data of SPS
tag.writeBytes( this.sps[0] ); // SPS
tag.writeByte( this.pps.length ); // num of PPS (will there ever be more that 1 PPS?)
for (i = 0 ; i < this.pps.length ; ++i) {
tag.writeShort(this.pps[i].length); // 2 bytes for length of PPS
tag.writeBytes(this.pps[i]); // data of PPS
}
return tag;
};
})();
(function(window) {
var
FlvTag = window.videojs.Hls.FlvTag,
H264ExtraData = window.videojs.Hls.H264ExtraData,
H264Stream,
NALUnitType;
/**
* Network Abstraction Layer (NAL) units are the packets of an H264
* stream. NAL units are divided into types based on their payload
* data. Each type has a unique numeric identifier.
*
* NAL unit
* |- NAL header -|------ RBSP ------|
*
* NAL unit: Network abstraction layer unit. The combination of a NAL
* header and an RBSP.
* NAL header: the encapsulation unit for transport-specific metadata in
* an h264 stream. Exactly one byte.
*/
// incomplete, see Table 7.1 of ITU-T H.264 for 12-32
window.videojs.Hls.NALUnitType = NALUnitType = {
unspecified: 0,
slice_layer_without_partitioning_rbsp_non_idr: 1,
slice_data_partition_a_layer_rbsp: 2,
slice_data_partition_b_layer_rbsp: 3,
slice_data_partition_c_layer_rbsp: 4,
slice_layer_without_partitioning_rbsp_idr: 5,
sei_rbsp: 6,
seq_parameter_set_rbsp: 7,
pic_parameter_set_rbsp: 8,
access_unit_delimiter_rbsp: 9,
end_of_seq_rbsp: 10,
end_of_stream_rbsp: 11
};
window.videojs.Hls.H264Stream = H264Stream = function() {
this._next_pts = 0; // :uint;
this._next_dts = 0; // :uint;
this._h264Frame = null; // :FlvTag
this._oldExtraData = new H264ExtraData(); // :H264ExtraData
this._newExtraData = new H264ExtraData(); // :H264ExtraData
this._nalUnitType = -1; // :int
this._state = 0; // :uint;
this.tags = [];
};
//(pts:uint):void
H264Stream.prototype.setTimeStampOffset = function() {};
//(pts:uint, dts:uint, dataAligned:Boolean):void
H264Stream.prototype.setNextTimeStamp = function(pts, dts, dataAligned) {
// We could end up with a DTS less than 0 here. We need to deal with that!
this._next_pts = pts;
this._next_dts = dts;
// If data is aligned, flush all internal buffers
if (dataAligned) {
this.finishFrame();
}
};
H264Stream.prototype.finishFrame = function() {
if (this._h264Frame) {
// Push SPS before EVERY IDR frame for seeking
if (this._newExtraData.extraDataExists()) {
this._oldExtraData = this._newExtraData;
this._newExtraData = new H264ExtraData();
}
// Check if keyframe and the length of tags.
// This makes sure we write metadata on the first frame of a segment.
if (this._oldExtraData.extraDataExists() &&
(this._h264Frame.keyFrame || this.tags.length === 0)) {
// Push extra data on every IDR frame in case we did a stream change + seek
this.tags.push(this._oldExtraData.metaDataTag(this._h264Frame.pts));
this.tags.push(this._oldExtraData.extraDataTag(this._h264Frame.pts));
}
this._h264Frame.endNalUnit();
this.tags.push(this._h264Frame);
}
this._h264Frame = null;
this._nalUnitType = -1;
this._state = 0;
};
// (data:ByteArray, o:int, l:int):void
H264Stream.prototype.writeBytes = function(data, offset, length) {
var
nalUnitSize, // :uint
start, // :uint
end, // :uint
t; // :int
// default argument values
offset = offset || 0;
length = length || 0;
if (length <= 0) {
// data is empty so there's nothing to write
return;
}
// scan through the bytes until we find the start code (0x000001) for a
// NAL unit and then begin writing it out
// strip NAL start codes as we go
switch (this._state) {
default:
/* falls through */
case 0:
this._state = 1;
/* falls through */
case 1:
// A NAL unit may be split across two TS packets. Look back a bit to
// make sure the prefix of the start code wasn't already written out.
if (data[offset] <= 1) {
nalUnitSize = this._h264Frame ? this._h264Frame.nalUnitSize() : 0;
if (nalUnitSize >= 1 && this._h264Frame.negIndex(1) === 0) {
// ?? ?? 00 | O[01] ?? ??
if (data[offset] === 1 &&
nalUnitSize >= 2 &&
this._h264Frame.negIndex(2) === 0) {
// ?? 00 00 : 01
if (3 <= nalUnitSize && 0 === this._h264Frame.negIndex(3)) {
this._h264Frame.length -= 3; // 00 00 00 : 01
} else {
this._h264Frame.length -= 2; // 00 00 : 01
}
this._state = 3;
return this.writeBytes(data, offset + 1, length - 1);
}
if (length > 1 && data[offset] === 0 && data[offset + 1] === 1) {
// ?? 00 | 00 01
if (nalUnitSize >= 2 && this._h264Frame.negIndex(2) === 0) {
this._h264Frame.length -= 2; // 00 00 : 00 01
} else {
this._h264Frame.length -= 1; // 00 : 00 01
}
this._state = 3;
return this.writeBytes(data, offset + 2, length - 2);
}
if (length > 2 &&
data[offset] === 0 &&
data[offset + 1] === 0 &&
data[offset + 2] === 1) {
// 00 : 00 00 01
// this._h264Frame.length -= 1;
this._state = 3;
return this.writeBytes(data, offset + 3, length - 3);
}
}
}
// allow fall through if the above fails, we may end up checking a few
// bytes a second time. But that case will be VERY rare
this._state = 2;
/* falls through */
case 2:
// Look for start codes in the data from the current offset forward
start = offset;
end = start + length;
for (t = end - 3; offset < t;) {
if (data[offset + 2] > 1) {
// if data[offset + 2] is greater than 1, there is no way a start
// code can begin before offset + 3
offset += 3;
} else if (data[offset + 1] !== 0) {
offset += 2;
} else if (data[offset] !== 0) {
offset += 1;
} else {
// If we get here we have 00 00 00 or 00 00 01
if (data[offset + 2] === 1) {
if (offset > start) {
this._h264Frame.writeBytes(data, start, offset - start);
}
this._state = 3;
offset += 3;
return this.writeBytes(data, offset, end - offset);
}
if (end - offset >= 4 &&
data[offset + 2] === 0 &&
data[offset + 3] === 1) {
if (offset > start) {
this._h264Frame.writeBytes(data, start, offset - start);
}
this._state = 3;
offset += 4;
return this.writeBytes(data, offset, end - offset);
}
// We are at the end of the buffer, or we have 3 NULLS followed by
// something that is not a 1, either way we can step forward by at
// least 3
offset += 3;
}
}
// We did not find any start codes. Try again next packet
this._state = 1;
if (this._h264Frame) {
this._h264Frame.writeBytes(data, start, length);
}
return;
case 3:
// The next byte is the first byte of a NAL Unit
if (this._h264Frame) {
// we've come to a new NAL unit so finish up the one we've been
// working on
switch (this._nalUnitType) {
case NALUnitType.seq_parameter_set_rbsp:
this._h264Frame.endNalUnit(this._newExtraData.sps);
break;
case NALUnitType.pic_parameter_set_rbsp:
this._h264Frame.endNalUnit(this._newExtraData.pps);
break;
case NALUnitType.slice_layer_without_partitioning_rbsp_idr:
this._h264Frame.endNalUnit();
break;
default:
this._h264Frame.endNalUnit();
break;
}
}
// setup to begin processing the new NAL unit
this._nalUnitType = data[offset] & 0x1F;
if (this._h264Frame) {
if (this._nalUnitType === NALUnitType.access_unit_delimiter_rbsp) {
// starting a new access unit, flush the previous one
this.finishFrame();
} else if (this._nalUnitType === NALUnitType.slice_layer_without_partitioning_rbsp_idr) {
this._h264Frame.keyFrame = true;
}
}
// finishFrame may render this._h264Frame null, so we must test again
if (!this._h264Frame) {
this._h264Frame = new FlvTag(FlvTag.VIDEO_TAG);
this._h264Frame.pts = this._next_pts;
this._h264Frame.dts = this._next_dts;
}
this._h264Frame.startNalUnit();
// We know there will not be an overlapping start code, so we can skip
// that test
this._state = 2;
return this.writeBytes(data, offset, length);
} // switch
};
})(this);
/**
* Accepts program elementary stream (PES) data events and parses out
* ID3 metadata from them, if present.
* @see http://id3.org/id3v2.3.0
*/
(function(window, videojs, undefined) {
'use strict';
var
// return a percent-encoded representation of the specified byte range
// @see http://en.wikipedia.org/wiki/Percent-encoding
percentEncode = function(bytes, start, end) {
var i, result = '';
for (i = start; i < end; i++) {
result += '%' + ('00' + bytes[i].toString(16)).slice(-2);
}
return result;
},
// return the string representation of the specified byte range,
// interpreted as UTf-8.
parseUtf8 = function(bytes, start, end) {
return window.decodeURIComponent(percentEncode(bytes, start, end));
},
// return the string representation of the specified byte range,
// interpreted as ISO-8859-1.
parseIso88591 = function(bytes, start, end) {
return window.unescape(percentEncode(bytes, start, end));
},
tagParsers = {
'TXXX': function(tag) {
var i;
if (tag.data[0] !== 3) {
// ignore frames with unrecognized character encodings
return;
}
for (i = 1; i < tag.data.length; i++) {
if (tag.data[i] === 0) {
// parse the text fields
tag.description = parseUtf8(tag.data, 1, i);
// do not include the null terminator in the tag value
tag.value = parseUtf8(tag.data, i + 1, tag.data.length - 1);
break;
}
}
},
'WXXX': function(tag) {
var i;
if (tag.data[0] !== 3) {
// ignore frames with unrecognized character encodings
return;
}
for (i = 1; i < tag.data.length; i++) {
if (tag.data[i] === 0) {
// parse the description and URL fields
tag.description = parseUtf8(tag.data, 1, i);
tag.url = parseUtf8(tag.data, i + 1, tag.data.length);
break;
}
}
},
'PRIV': function(tag) {
var i;
for (i = 0; i < tag.data.length; i++) {
if (tag.data[i] === 0) {
// parse the description and URL fields
tag.owner = parseIso88591(tag.data, 0, i);
break;
}
}
tag.privateData = tag.data.subarray(i + 1);
}
},
MetadataStream;
MetadataStream = function(options) {
var
settings = {
debug: !!(options && options.debug),
// the bytes of the program-level descriptor field in MP2T
// see ISO/IEC 13818-1:2013 (E), section 2.6 "Program and
// program element descriptors"
descriptor: options && options.descriptor
},
// the total size in bytes of the ID3 tag being parsed
tagSize = 0,
// tag data that is not complete enough to be parsed
buffer = [],
// the total number of bytes currently in the buffer
bufferSize = 0,
i;
MetadataStream.prototype.init.call(this);
// calculate the text track in-band metadata track dispatch type
// https://html.spec.whatwg.org/multipage/embedded-content.html#steps-to-expose-a-media-resource-specific-text-track
this.dispatchType = videojs.Hls.SegmentParser.STREAM_TYPES.metadata.toString(16);
if (settings.descriptor) {
for (i = 0; i < settings.descriptor.length; i++) {
this.dispatchType += ('00' + settings.descriptor[i].toString(16)).slice(-2);
}
}
this.push = function(chunk) {
var tag, frameStart, frameSize, frame, i;
// ignore events that don't look like ID3 data
if (buffer.length === 0 &&
(chunk.data.length < 10 ||
chunk.data[0] !== 'I'.charCodeAt(0) ||
chunk.data[1] !== 'D'.charCodeAt(0) ||
chunk.data[2] !== '3'.charCodeAt(0))) {
if (settings.debug) {
videojs.log('Skipping unrecognized metadata packet');
}
return;
}
// add this chunk to the data we've collected so far
buffer.push(chunk);
bufferSize += chunk.data.byteLength;
// grab the size of the entire frame from the ID3 header
if (buffer.length === 1) {
// the frame size is transmitted as a 28-bit integer in the
// last four bytes of the ID3 header.
// The most significant bit of each byte is dropped and the
// results concatenated to recover the actual value.
tagSize = (chunk.data[6] << 21) |
(chunk.data[7] << 14) |
(chunk.data[8] << 7) |
(chunk.data[9]);
// ID3 reports the tag size excluding the header but it's more
// convenient for our comparisons to include it
tagSize += 10;
}
// if the entire frame has not arrived, wait for more data
if (bufferSize < tagSize) {
return;
}
// collect the entire frame so it can be parsed
tag = {
data: new Uint8Array(tagSize),
frames: [],
pts: buffer[0].pts,
dts: buffer[0].dts
};
for (i = 0; i < tagSize;) {
tag.data.set(buffer[0].data, i);
i += buffer[0].data.byteLength;
bufferSize -= buffer[0].data.byteLength;
buffer.shift();
}
// find the start of the first frame and the end of the tag
frameStart = 10;
if (tag.data[5] & 0x40) {
// advance the frame start past the extended header
frameStart += 4; // header size field
frameStart += (tag.data[10] << 24) |
(tag.data[11] << 16) |
(tag.data[12] << 8) |
(tag.data[13]);
// clip any padding off the end
tagSize -= (tag.data[16] << 24) |
(tag.data[17] << 16) |
(tag.data[18] << 8) |
(tag.data[19]);
}
// parse one or more ID3 frames
// http://id3.org/id3v2.3.0#ID3v2_frame_overview
do {
// determine the number of bytes in this frame
frameSize = (tag.data[frameStart + 4] << 24) |
(tag.data[frameStart + 5] << 16) |
(tag.data[frameStart + 6] << 8) |
(tag.data[frameStart + 7]);
if (frameSize < 1) {
return videojs.log('Malformed ID3 frame encountered. Skipping metadata parsing.');
}
frame = {
id: String.fromCharCode(tag.data[frameStart],
tag.data[frameStart + 1],
tag.data[frameStart + 2],
tag.data[frameStart + 3]),
data: tag.data.subarray(frameStart + 10, frameStart + frameSize + 10)
};
if (tagParsers[frame.id]) {
tagParsers[frame.id](frame);
}
tag.frames.push(frame);
frameStart += 10; // advance past the frame header
frameStart += frameSize; // advance past the frame body
} while (frameStart < tagSize);
this.trigger('data', tag);
};
};
MetadataStream.prototype = new videojs.Hls.Stream();
videojs.Hls.MetadataStream = MetadataStream;
})(window, window.videojs);
(function(window, videojs, undefined) {
'use strict';
var box, dinf, ftyp, mdat, mfhd, minf, moof, moov, mvex, mvhd, trak,
tkhd, mdia, mdhd, hdlr, sdtp, stbl, stsd, styp, traf, trex, trun,
types, MAJOR_BRAND, MINOR_VERSION, AVC1_BRAND, VIDEO_HDLR,
AUDIO_HDLR, HDLR_TYPES, ESDS, VMHD, SMHD, DREF, STCO, STSC, STSZ, STTS,
Uint8Array, DataView;
Uint8Array = window.Uint8Array;
DataView = window.DataView;
// 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: [],
sdtp: [],
smhd: [],
stbl: [],
stco: [],
stsc: [],
stsd: [],
stsz: [],
stts: [],
styp: [],
tfdt: [],
tfhd: [],
traf: [],
trak: [],
trun: [],
trex: [],
tkhd: [],
vmhd: []
};
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
]);
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
0x11, // AudioObjectType, AAC LC.
0x90, // samplingFrequencyIndex, 8 -> 16000. channelConfig, 2 -> stereo.
0x06, 0x01, 0x02 // GASpecificConfig
]);
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(type) {
var
payload = Array.prototype.slice.call(arguments, 1),
size = 0,
i = payload.length,
result,
view;
// 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() {
return box(types.dinf, box(types.dref, DREF));
};
ftyp = function() {
return box(types.ftyp, MAJOR_BRAND, MINOR_VERSION, MAJOR_BRAND, AVC1_BRAND);
};
hdlr = function(type) {
return box(types.hdlr, HDLR_TYPES[type]);
};
mdat = function(data) {
return box(types.mdat, data);
};
mdhd = function(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),
(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);
result[13] = (track.samplerate >>> 16) & 0xFF;
result[14] = (track.samplerate >>> 8) & 0xFF;
result[15] = (track.samplerate) & 0xFF;
}
return box(types.mdhd, result);
};
mdia = function(track) {
return box(types.mdia, mdhd(track), hdlr(track.type), minf(track));
};
mfhd = function(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(track) {
return box(types.minf,
track.type === 'video' ? box(types.vmhd, VMHD) : box(types.smhd, SMHD),
dinf(),
stbl(track));
};
moof = function(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(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(tracks) {
var
i = tracks.length,
boxes = [];
while (i--) {
boxes[i] = trex(tracks[i]);
}
return box.apply(null, [types.mvex].concat(boxes));
};
mvhd = function(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(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(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(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(track) {
var sequenceParameterSets = [], pictureParameterSets = [], i;
// assemble the SPSs
for (i = 0; i < track.sps.length; i++) {
sequenceParameterSets.push((track.sps[i].byteLength & 0xFF00) >>> 8);
sequenceParameterSets.push((track.sps[i].byteLength & 0xFF)); // sequenceParameterSetLength
sequenceParameterSets = sequenceParameterSets.concat(Array.prototype.slice.call(track.sps[i])); // SPS
}
// assemble the PPSs
for (i = 0; i < track.pps.length; i++) {
pictureParameterSets.push((track.pps[i].byteLength & 0xFF00) >>> 8);
pictureParameterSets.push((track.pps[i].byteLength & 0xFF));
pictureParameterSets = pictureParameterSets.concat(Array.prototype.slice.call(track.pps[i]));
}
return box(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([
track.sps.length // numOfSequenceParameterSets
]).concat(sequenceParameterSets).concat([
track.pps.length // numOfPictureParameterSets
]).concat(pictureParameterSets))), // "PPS"
box(types.btrt, new Uint8Array([
0x00, 0x1c, 0x9c, 0x80, // bufferSizeDB
0x00, 0x2d, 0xc6, 0xc0, // maxBitrate
0x00, 0x2d, 0xc6, 0xc0
])) // avgBitrate
);
};
audioSample = function(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
]), box(types.esds, ESDS));
};
})();
styp = function() {
return box(types.styp, MAJOR_BRAND, MINOR_VERSION, MAJOR_BRAND);
};
tkhd = function(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(track) {
var trackFragmentHeader, trackFragmentDecodeTime,
trackFragmentRun, sampleDependencyTable, dataOffset;
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
]));
trackFragmentDecodeTime = box(types.tfdt, new Uint8Array([
0x00, // version 0
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x00 // baseMediaDecodeTime
]));
// 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
16 + // 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(track) {
track.duration = track.duration || 0xffffffff;
return box(types.trak,
tkhd(track),
mdia(track));
};
trex = function(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(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(track, offset) {
var bytes, samples, sample, i;
samples = track.samples || [];
offset += 8 + 12 + (16 * samples.length);
bytes = trunHeader(samples, offset);
for (i = 0; i < samples.length; i++) {
sample = samples[i];
bytes = bytes.concat([
(sample.duration & 0xFF000000) >>> 24,
(sample.duration & 0xFF0000) >>> 16,
(sample.duration & 0xFF00) >>> 8,
sample.duration & 0xFF, // sample_duration
(sample.size & 0xFF000000) >>> 24,
(sample.size & 0xFF0000) >>> 16,
(sample.size & 0xFF00) >>> 8,
sample.size & 0xFF, // sample_size
(sample.flags.isLeading << 2) | sample.flags.dependsOn,
(sample.flags.isDependedOn << 6) |
(sample.flags.hasRedundancy << 4) |
(sample.flags.paddingValue << 1) |
sample.flags.isNonSyncSample,
sample.flags.degradationPriority & 0xF0 << 8,
sample.flags.degradationPriority & 0x0F, // sample_flags
(sample.compositionTimeOffset & 0xFF000000) >>> 24,
(sample.compositionTimeOffset & 0xFF0000) >>> 16,
(sample.compositionTimeOffset & 0xFF00) >>> 8,
sample.compositionTimeOffset & 0xFF // sample_composition_time_offset
]);
}
return box(types.trun, new Uint8Array(bytes));
};
audioTrun = function(track, offset) {
var bytes, samples, sample, i;
samples = track.samples || [];
offset += 8 + 12 + (8 * samples.length);
bytes = trunHeader(samples, offset);
for (i = 0; i < samples.length; i++) {
sample = samples[i];
bytes = bytes.concat([
(sample.duration & 0xFF000000) >>> 24,
(sample.duration & 0xFF0000) >>> 16,
(sample.duration & 0xFF00) >>> 8,
sample.duration & 0xFF, // sample_duration
(sample.size & 0xFF000000) >>> 24,
(sample.size & 0xFF0000) >>> 16,
(sample.size & 0xFF00) >>> 8,
sample.size & 0xFF]); // sample_size
}
return box(types.trun, new Uint8Array(bytes));
};
trun = function(track, offset) {
if (track.type === 'audio') {
return audioTrun(track, offset);
} else {
return videoTrun(track, offset);
}
};
})();
window.videojs.mp4 = {
ftyp: ftyp,
mdat: mdat,
moof: moof,
moov: moov,
initSegment: function(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;
}
};
})(window, window.videojs);
(function(window) {
var
videojs = window.videojs,
FlvTag = videojs.Hls.FlvTag,
H264Stream = videojs.Hls.H264Stream,
AacStream = videojs.Hls.AacStream,
MetadataStream = videojs.Hls.MetadataStream,
MP2T_PACKET_LENGTH,
STREAM_TYPES;
/**
* An object that incrementally transmuxes MPEG2 Trasport Stream
* chunks into an FLV.
*/
videojs.Hls.SegmentParser = function() {
var
self = this,
parseTSPacket,
streamBuffer = new Uint8Array(MP2T_PACKET_LENGTH),
streamBufferByteCount = 0,
h264Stream = new H264Stream(),
aacStream = new AacStream();
// expose the stream metadata
self.stream = {
// the mapping between transport stream programs and the PIDs
// that form their elementary streams
programMapTable: {}
};
// allow in-band metadata to be observed
self.metadataStream = new MetadataStream();
// For information on the FLV format, see
// http://download.macromedia.com/f4v/video_file_format_spec_v10_1.pdf.
// Technically, this function returns the header and a metadata FLV tag
// if duration is greater than zero
// duration in seconds
// @return {object} the bytes of the FLV header as a Uint8Array
self.getFlvHeader = function(duration, audio, video) { // :ByteArray {
var
headBytes = new Uint8Array(3 + 1 + 1 + 4),
head = new DataView(headBytes.buffer),
metadata,
result,
metadataLength;
// default arguments
duration = duration || 0;
audio = audio === undefined? true : audio;
video = video === undefined? true : video;
// signature
head.setUint8(0, 0x46); // 'F'
head.setUint8(1, 0x4c); // 'L'
head.setUint8(2, 0x56); // 'V'
// version
head.setUint8(3, 0x01);
// flags
head.setUint8(4, (audio ? 0x04 : 0x00) | (video ? 0x01 : 0x00));
// data offset, should be 9 for FLV v1
head.setUint32(5, headBytes.byteLength);
// init the first FLV tag
if (duration <= 0) {
// no duration available so just write the first field of the first
// FLV tag
result = new Uint8Array(headBytes.byteLength + 4);
result.set(headBytes);
result.set([0, 0, 0, 0], headBytes.byteLength);
return result;
}
// write out the duration metadata tag
metadata = new FlvTag(FlvTag.METADATA_TAG);
metadata.pts = metadata.dts = 0;
metadata.writeMetaDataDouble("duration", duration);
metadataLength = metadata.finalize().length;
result = new Uint8Array(headBytes.byteLength + metadataLength);
result.set(headBytes);
result.set(head.byteLength, metadataLength);
return result;
};
self.flushTags = function() {
h264Stream.finishFrame();
};
/**
* Returns whether a call to `getNextTag()` will be successful.
* @return {boolean} whether there is at least one transmuxed FLV
* tag ready
*/
self.tagsAvailable = function() { // :int {
return h264Stream.tags.length + aacStream.tags.length;
};
/**
* Returns the next tag in decoder-timestamp (DTS) order.
* @returns {object} the next tag to decoded.
*/
self.getNextTag = function() {
var tag;
if (!h264Stream.tags.length) {
// only audio tags remain
tag = aacStream.tags.shift();
} else if (!aacStream.tags.length) {
// only video tags remain
tag = h264Stream.tags.shift();
} else if (aacStream.tags[0].dts < h264Stream.tags[0].dts) {
// audio should be decoded next
tag = aacStream.tags.shift();
} else {
// video should be decoded next
tag = h264Stream.tags.shift();
}
return tag.finalize();
};
self.parseSegmentBinaryData = function(data) { // :ByteArray) {
var
dataPosition = 0,
dataSlice;
// To avoid an extra copy, we will stash overflow data, and only
// reconstruct the first packet. The rest of the packets will be
// parsed directly from data
if (streamBufferByteCount > 0) {
if (data.byteLength + streamBufferByteCount < MP2T_PACKET_LENGTH) {
// the current data is less than a single m2ts packet, so stash it
// until we receive more
// ?? this seems to append streamBuffer onto data and then just give up. I'm not sure why that would be interesting.
videojs.log('data.length + streamBuffer.length < MP2T_PACKET_LENGTH ??');
streamBuffer.readBytes(data, data.length, streamBuffer.length);
return;
} else {
// we have enough data for an m2ts packet
// process it immediately
dataSlice = data.subarray(0, MP2T_PACKET_LENGTH - streamBufferByteCount);
streamBuffer.set(dataSlice, streamBufferByteCount);
parseTSPacket(streamBuffer);
// reset the buffer
streamBuffer = new Uint8Array(MP2T_PACKET_LENGTH);
streamBufferByteCount = 0;
}
}
while (true) {
// Make sure we are TS aligned
while(dataPosition < data.byteLength && data[dataPosition] !== 0x47) {
// If there is no sync byte skip forward until we find one
// TODO if we find a sync byte, look 188 bytes in the future (if
// possible). If there is not a sync byte there, keep looking
dataPosition++;
}
// base case: not enough data to parse a m2ts packet
if (data.byteLength - dataPosition < MP2T_PACKET_LENGTH) {
if (data.byteLength - dataPosition > 0) {
// there are bytes remaining, save them for next time
streamBuffer.set(data.subarray(dataPosition),
streamBufferByteCount);
streamBufferByteCount += data.byteLength - dataPosition;
}
return;
}
// attempt to parse a m2ts packet
if (parseTSPacket(data.subarray(dataPosition, dataPosition + MP2T_PACKET_LENGTH))) {
dataPosition += MP2T_PACKET_LENGTH;
} else {
// If there was an error parsing a TS packet. it could be
// because we are not TS packet aligned. Step one forward by
// one byte and allow the code above to find the next
videojs.log('error parsing m2ts packet, attempting to re-align');
dataPosition++;
}
}
};
/**
* Parses a video/mp2t packet and appends the underlying video and
* audio data onto h264stream and aacStream, respectively.
* @param data {Uint8Array} the bytes of an MPEG2-TS packet,
* including the sync byte.
* @return {boolean} whether a valid packet was encountered
*/
// TODO add more testing to make sure we dont walk past the end of a TS
// packet!
parseTSPacket = function(data) { // :ByteArray):Boolean {
var
offset = 0, // :uint
end = offset + MP2T_PACKET_LENGTH, // :uint
// Payload Unit Start Indicator
pusi = !!(data[offset + 1] & 0x40), // mask: 0100 0000
// packet identifier (PID), a unique identifier for the elementary
// stream this packet describes
pid = (data[offset + 1] & 0x1F) << 8 | data[offset + 2], // mask: 0001 1111
// adaptation_field_control, whether this header is followed by an
// adaptation field, a payload, or both
afflag = (data[offset + 3] & 0x30 ) >>> 4,
patTableId, // :int
patCurrentNextIndicator, // Boolean
patSectionLength, // :uint
programNumber, // :uint
programPid, // :uint
patEntriesEnd, // :uint
pesPacketSize, // :int,
dataAlignmentIndicator, // :Boolean,
ptsDtsIndicator, // :int
pesHeaderLength, // :int
pts, // :uint
dts, // :uint
pmtCurrentNextIndicator, // :Boolean
pmtProgramDescriptorsLength,
pmtSectionLength, // :uint
streamType, // :int
elementaryPID, // :int
ESInfolength; // :int
// Continuity Counter we could use this for sanity check, and
// corrupt stream detection
// cc = (data[offset + 3] & 0x0F);
// move past the header
offset += 4;
// if an adaption field is present, its length is specified by
// the fifth byte of the PES header. The adaptation field is
// used to specify some forms of timing and control data that we
// do not currently use.
if (afflag > 0x01) {
offset += data[offset] + 1;
}
// Handle a Program Association Table (PAT). PATs map PIDs to
// individual programs. If this transport stream was being used
// for television broadcast, a program would probably be
// equivalent to a channel. In HLS, it would be very unusual to
// create an mp2t stream with multiple programs.
if (0x0000 === pid) {
// The PAT may be split into multiple sections and those
// sections may be split into multiple packets. If a PAT
// section starts in this packet, PUSI will be true and the
// first byte of the playload will indicate the offset from
// the current position to the start of the section.
if (pusi) {
offset += 1 + data[offset];
}
patTableId = data[offset];
if (patTableId !== 0x00) {
videojs.log('the table_id of the PAT should be 0x00 but was' +
patTableId.toString(16));
}
// the current_next_indicator specifies whether this PAT is
// currently applicable or is part of the next table to become
// active
patCurrentNextIndicator = !!(data[offset + 5] & 0x01);
if (patCurrentNextIndicator) {
// section_length specifies the number of bytes following
// its position to the end of this section
// section_length = rest of header + (n * entry length) + CRC
// = 5 + (n * 4) + 4
patSectionLength = (data[offset + 1] & 0x0F) << 8 | data[offset + 2];
// move past the rest of the PSI header to the first program
// map table entry
offset += 8;
// we don't handle streams with more than one program, so
// raise an exception if we encounter one
patEntriesEnd = offset + (patSectionLength - 5 - 4);
for (; offset < patEntriesEnd; offset += 4) {
programNumber = (data[offset] << 8 | data[offset + 1]);
programPid = (data[offset + 2] & 0x1F) << 8 | data[offset + 3];
// network PID program number equals 0
// this is primarily an artifact of EBU DVB and can be ignored
if (programNumber === 0) {
self.stream.networkPid = programPid;
} else if (self.stream.pmtPid === undefined) {
// the Program Map Table (PMT) associates the underlying
// video and audio streams with a unique PID
self.stream.pmtPid = programPid;
} else if (self.stream.pmtPid !== programPid) {
throw new Error("TS has more that 1 program");
}
}
}
} else if (pid === self.stream.programMapTable[STREAM_TYPES.h264] ||
pid === self.stream.programMapTable[STREAM_TYPES.adts] ||
pid === self.stream.programMapTable[STREAM_TYPES.metadata]) {
if (pusi) {
// comment out for speed
if (0x00 !== data[offset + 0] || 0x00 !== data[offset + 1] || 0x01 !== data[offset + 2]) {
// look for PES start code
throw new Error("PES did not begin with start code");
}
// var sid:int = data[offset+3]; // StreamID
pesPacketSize = (data[offset + 4] << 8) | data[offset + 5];
dataAlignmentIndicator = (data[offset + 6] & 0x04) !== 0;
ptsDtsIndicator = data[offset + 7];
pesHeaderLength = data[offset + 8]; // TODO sanity check header length
offset += 9; // Skip past PES header
// PTS and DTS are normially stored as a 33 bit number.
// JavaScript does not have a integer type larger than 32 bit
// BUT, we need to convert from 90ns to 1ms time scale anyway.
// so what we are going to do instead, is drop the least
// significant bit (the same as dividing by two) then we can
// divide by 45 (45 * 2 = 90) to get ms.
if (ptsDtsIndicator & 0xC0) {
// the PTS and DTS are not written out directly. For information on
// how they are encoded, see
// http://dvd.sourceforge.net/dvdinfo/pes-hdr.html
pts = (data[offset + 0] & 0x0E) << 28
| (data[offset + 1] & 0xFF) << 21
| (data[offset + 2] & 0xFE) << 13
| (data[offset + 3] & 0xFF) << 6
| (data[offset + 4] & 0xFE) >>> 2;
pts /= 45;
dts = pts;
if (ptsDtsIndicator & 0x40) {// DTS
dts = (data[offset + 5] & 0x0E ) << 28
| (data[offset + 6] & 0xFF ) << 21
| (data[offset + 7] & 0xFE ) << 13
| (data[offset + 8] & 0xFF ) << 6
| (data[offset + 9] & 0xFE ) >>> 2;
dts /= 45;
}
}
// Skip past "optional" portion of PTS header
offset += pesHeaderLength;
if (pid === self.stream.programMapTable[STREAM_TYPES.h264]) {
h264Stream.setNextTimeStamp(pts,
dts,
dataAlignmentIndicator);
} else if (pid === self.stream.programMapTable[STREAM_TYPES.adts]) {
aacStream.setNextTimeStamp(pts,
pesPacketSize,
dataAlignmentIndicator);
}
}
if (pid === self.stream.programMapTable[STREAM_TYPES.adts]) {
aacStream.writeBytes(data, offset, end - offset);
} else if (pid === self.stream.programMapTable[STREAM_TYPES.h264]) {
h264Stream.writeBytes(data, offset, end - offset);
} else if (pid === self.stream.programMapTable[STREAM_TYPES.metadata]) {
self.metadataStream.push({
pts: pts,
dts: dts,
data: data.subarray(offset)
});
}
} else if (self.stream.pmtPid === pid) {
// similarly to the PAT, jump to the first byte of the section
if (pusi) {
offset += 1 + data[offset];
}
if (data[offset] !== 0x02) {
videojs.log('The table_id of a PMT should be 0x02 but was ' +
data[offset].toString(16));
}
// whether this PMT is currently applicable or is part of the
// next table to become active
pmtCurrentNextIndicator = !!(data[offset + 5] & 0x01);
if (pmtCurrentNextIndicator) {
// overwrite any existing program map table
self.stream.programMapTable = {};
// section_length specifies the number of bytes following
// its position to the end of this section
pmtSectionLength = (data[offset + 1] & 0x0f) << 8 | data[offset + 2];
// subtract the length of the program info descriptors
pmtProgramDescriptorsLength = (data[offset + 10] & 0x0f) << 8 | data[offset + 11];
pmtSectionLength -= pmtProgramDescriptorsLength;
// skip CRC and PSI data we dont care about
// rest of header + CRC = 9 + 4
pmtSectionLength -= 13;
// capture the PID of PCR packets so we can ignore them if we see any
self.stream.programMapTable.pcrPid = (data[offset + 8] & 0x1f) << 8 | data[offset + 9];
// align offset to the first entry in the PMT
offset += 12 + pmtProgramDescriptorsLength;
// iterate through the entries
while (0 < pmtSectionLength) {
// the type of data carried in the PID this entry describes
streamType = data[offset + 0];
// the PID for this entry
elementaryPID = (data[offset + 1] & 0x1F) << 8 | data[offset + 2];
if (streamType === STREAM_TYPES.h264 &&
self.stream.programMapTable[streamType] &&
self.stream.programMapTable[streamType] !== elementaryPID) {
throw new Error("Program has more than 1 video stream");
} else if (streamType === STREAM_TYPES.adts &&
self.stream.programMapTable[streamType] &&
self.stream.programMapTable[streamType] !== elementaryPID) {
throw new Error("Program has more than 1 audio Stream");
}
// add the stream type entry to the map
self.stream.programMapTable[streamType] = elementaryPID;
// TODO add support for MP3 audio
// the length of the entry descriptor
ESInfolength = (data[offset + 3] & 0x0F) << 8 | data[offset + 4];
// capture the stream descriptor for metadata streams
if (streamType === STREAM_TYPES.metadata) {
self.metadataStream.descriptor = new Uint8Array(data.subarray(offset + 5, offset + 5 + ESInfolength));
}
// move to the first byte after the end of this entry
offset += 5 + ESInfolength;
pmtSectionLength -= 5 + ESInfolength;
}
}
// We could test the CRC here to detect corruption with extra CPU cost
} else if (self.stream.networkPid === pid) {
// network information specific data (NIT) packet
} else if (0x0011 === pid) {
// Service Description Table
} else if (0x1FFF === pid) {
// NULL packet
} else if (self.stream.programMapTable.pcrPid) {
// program clock reference (PCR) PID for the primary program
// PTS values are sufficient to synchronize playback for us so
// we can safely ignore these
} else {
videojs.log("Unknown PID parsing TS packet: " + pid);
}
return true;
};
self.getTags = function() {
return h264Stream.tags;
};
self.stats = {
h264Tags: function() {
return h264Stream.tags.length;
},
minVideoPts: function() {
return h264Stream.tags[0].pts;
},
maxVideoPts: function() {
return h264Stream.tags[h264Stream.tags.length - 1].pts;
},
aacTags: function() {
return aacStream.tags.length;
},
minAudioPts: function() {
return aacStream.tags[0].pts;
},
maxAudioPts: function() {
return aacStream.tags[aacStream.tags.length - 1].pts;
}
};
};
// MPEG2-TS constants
videojs.Hls.SegmentParser.MP2T_PACKET_LENGTH = MP2T_PACKET_LENGTH = 188;
videojs.Hls.SegmentParser.STREAM_TYPES = STREAM_TYPES = {
h264: 0x1b,
adts: 0x0f,
metadata: 0x15
};
})(window);
/**
* video-js-hls
*
* Copyright (c) 2014 Brightcove
* All rights reserved.
*/
/**
* A stream-based mp2t to mp4 converter. This utility is used to
* deliver mp4s to a SourceBuffer on platforms that support native
* Media Source Extensions. The equivalent process for Flash-based
* platforms can be found in segment-parser.js
*/
(function(window, videojs, undefined) {
'use strict';
var
TransportPacketStream, TransportParseStream, ElementaryStream, VideoSegmentStream,
AudioSegmentStream, Transmuxer, AacStream, H264Stream, NalByteStream,
MP2T_PACKET_LENGTH, H264_STREAM_TYPE, ADTS_STREAM_TYPE,
ADTS_SAMPLING_FREQUENCIES, mp4;
MP2T_PACKET_LENGTH = 188; // bytes
H264_STREAM_TYPE = 0x1b;
ADTS_STREAM_TYPE = 0x0f;
ADTS_SAMPLING_FREQUENCIES = [
96000,
88200,
64000,
48000,
44100,
32000,
24000,
22050,
16000,
12000,
11025,
8000,
7350
];
mp4 = videojs.mp4;
/**
* Splits an incoming stream of binary data into MPEG-2 Transport
* Stream packets.
*/
TransportPacketStream = function() {
var
buffer = new Uint8Array(MP2T_PACKET_LENGTH),
end = 0;
TransportPacketStream.prototype.init.call(this);
/**
* Deliver new bytes to the stream.
*/
this.push = function(bytes) {
var remaining, i;
// clear out any partial packets in the buffer
if (end > 0) {
remaining = MP2T_PACKET_LENGTH - end;
buffer.set(bytes.subarray(0, remaining), end);
// we still didn't write out a complete packet
if (bytes.byteLength < remaining) {
end += bytes.byteLength;
return;
}
bytes = bytes.subarray(remaining);
end = 0;
this.trigger('data', buffer);
}
// if less than a single packet is available, buffer it up for later
if (bytes.byteLength < MP2T_PACKET_LENGTH) {
buffer.set(bytes.subarray(i), end);
end += bytes.byteLength;
return;
}
// parse out all the completed packets
i = 0;
do {
this.trigger('data', bytes.subarray(i, i + MP2T_PACKET_LENGTH));
i += MP2T_PACKET_LENGTH;
remaining = bytes.byteLength - i;
} while (i < bytes.byteLength && remaining >= MP2T_PACKET_LENGTH);
// buffer any partial packets left over
if (remaining > 0) {
buffer.set(bytes.subarray(i));
end = remaining;
}
};
};
TransportPacketStream.prototype = new videojs.Hls.Stream();
/**
* Accepts an MP2T TransportPacketStream and emits data events with parsed
* forms of the individual transport stream packets.
*/
TransportParseStream = function() {
var parsePsi, parsePat, parsePmt, parsePes, self;
TransportParseStream.prototype.init.call(this);
self = this;
this.programMapTable = {};
parsePsi = function(payload, psi) {
var offset = 0;
// PSI packets may be split into multiple sections and those
// sections may be split into multiple packets. If a PSI
// section starts in this packet, the payload_unit_start_indicator
// will be true and the first byte of the payload will indicate
// the offset from the current position to the start of the
// section.
if (psi.payloadUnitStartIndicator) {
offset += payload[offset] + 1;
}
if (psi.type === 'pat') {
parsePat(payload.subarray(offset), psi);
} else {
parsePmt(payload.subarray(offset), psi);
}
};
parsePat = function(payload, pat) {
pat.section_number = payload[7];
pat.last_section_number = payload[8];
// skip the PSI header and parse the first PMT entry
self.pmtPid = (payload[10] & 0x1F) << 8 | payload[11];
pat.pmtPid = self.pmtPid;
};
/**
* Parse out the relevant fields of a Program Map Table (PMT).
* @param payload {Uint8Array} the PMT-specific portion of an MP2T
* packet. The first byte in this array should be the table_id
* field.
* @param pmt {object} the object that should be decorated with
* fields parsed from the PMT.
*/
parsePmt = function(payload, pmt) {
var sectionLength, tableEnd, programInfoLength, offset;
// PMTs can be sent ahead of the time when they should actually
// take effect. We don't believe this should ever be the case
// for HLS but we'll ignore "forward" PMT declarations if we see
// them. Future PMT declarations have the current_next_indicator
// set to zero.
if (!(payload[5] & 0x01)) {
return;
}
// overwrite any existing program map table
self.programMapTable = {};
// the mapping table ends at the end of the current section
sectionLength = (payload[1] & 0x0f) << 8 | payload[2];
tableEnd = 3 + sectionLength - 4;
// to determine where the table is, we have to figure out how
// long the program info descriptors are
programInfoLength = (payload[10] & 0x0f) << 8 | payload[11];
// advance the offset to the first entry in the mapping table
offset = 12 + programInfoLength;
while (offset < tableEnd) {
// add an entry that maps the elementary_pid to the stream_type
self.programMapTable[(payload[offset + 1] & 0x1F) << 8 | payload[offset + 2]] = payload[offset];
// move to the next table entry
// skip past the elementary stream descriptors, if present
offset += ((payload[offset + 3] & 0x0F) << 8 | payload[offset + 4]) + 5;
}
// record the map on the packet as well
pmt.programMapTable = self.programMapTable;
};
parsePes = function(payload, pes) {
var ptsDtsFlags;
if (!pes.payloadUnitStartIndicator) {
pes.data = payload;
return;
}
// find out if this packets starts a new keyframe
pes.dataAlignmentIndicator = (payload[6] & 0x04) !== 0;
// PES packets may be annotated with a PTS value, or a PTS value
// and a DTS value. Determine what combination of values is
// available to work with.
ptsDtsFlags = payload[7];
// PTS and DTS are normally stored as a 33-bit number. Javascript
// performs all bitwise operations on 32-bit integers but it's
// convenient to convert from 90ns to 1ms time scale anyway. So
// what we are going to do instead is drop the least significant
// bit (in effect, dividing by two) then we can divide by 45 (45 *
// 2 = 90) to get ms.
if (ptsDtsFlags & 0xC0) {
// the PTS and DTS are not written out directly. For information
// on how they are encoded, see
// http://dvd.sourceforge.net/dvdinfo/pes-hdr.html
pes.pts = (payload[9] & 0x0E) << 28
| (payload[10] & 0xFF) << 21
| (payload[11] & 0xFE) << 13
| (payload[12] & 0xFF) << 6
| (payload[13] & 0xFE) >>> 2;
pes.pts /= 45;
pes.dts = pes.pts;
if (ptsDtsFlags & 0x40) {
pes.dts = (payload[14] & 0x0E ) << 28
| (payload[15] & 0xFF ) << 21
| (payload[16] & 0xFE ) << 13
| (payload[17] & 0xFF ) << 6
| (payload[18] & 0xFE ) >>> 2;
pes.dts /= 45;
}
}
// the data section starts immediately after the PES header.
// pes_header_data_length specifies the number of header bytes
// that follow the last byte of the field.
pes.data = payload.subarray(9 + payload[8]);
};
/**
* Deliver a new MP2T packet to the stream.
*/
this.push = function(packet) {
var
result = {},
offset = 4;
// make sure packet is aligned on a sync byte
if (packet[0] !== 0x47) {
return this.trigger('error', 'mis-aligned packet');
}
result.payloadUnitStartIndicator = !!(packet[1] & 0x40);
// pid is a 13-bit field starting at the last bit of packet[1]
result.pid = packet[1] & 0x1f;
result.pid <<= 8;
result.pid |= packet[2];
// if an adaption field is present, its length is specified by the
// fifth byte of the TS packet header. The adaptation field is
// used to add stuffing to PES packets that don't fill a complete
// TS packet, and to specify some forms of timing and control data
// that we do not currently use.
if (((packet[3] & 0x30) >>> 4) > 0x01) {
offset += packet[offset] + 1;
}
// parse the rest of the packet based on the type
if (result.pid === 0) {
result.type = 'pat';
parsePsi(packet.subarray(offset), result);
} else if (result.pid === this.pmtPid) {
result.type = 'pmt';
parsePsi(packet.subarray(offset), result);
} else {
result.streamType = this.programMapTable[result.pid];
result.type = 'pes';
parsePes(packet.subarray(offset), result);
}
this.trigger('data', result);
};
};
TransportParseStream.prototype = new videojs.Hls.Stream();
TransportParseStream.STREAM_TYPES = {
h264: 0x1b,
adts: 0x0f
};
/**
* Reconsistutes program elementary stream (PES) packets from parsed
* transport stream packets. That is, if you pipe an
* mp2t.TransportParseStream into a mp2t.ElementaryStream, the output
* events will be events which capture the bytes for individual PES
* packets plus relevant metadata that has been extracted from the
* container.
*/
ElementaryStream = function() {
var
// PES packet fragments
video = {
data: [],
size: 0
},
audio = {
data: [],
size: 0
},
flushStream = function(stream, type) {
var
event = {
type: type,
data: new Uint8Array(stream.size),
},
i = 0,
fragment;
// do nothing if there is no buffered data
if (!stream.data.length) {
return;
}
event.trackId = stream.data[0].pid;
event.pts = stream.data[0].pts;
event.dts = stream.data[0].dts;
// reassemble the packet
while (stream.data.length) {
fragment = stream.data.shift();
event.data.set(fragment.data, i);
i += fragment.data.byteLength;
}
stream.size = 0;
self.trigger('data', event);
},
self;
ElementaryStream.prototype.init.call(this);
self = this;
this.push = function(data) {
({
pat: function() {
// we have to wait for the PMT to arrive as well before we
// have any meaningful metadata
},
pes: function() {
var stream, streamType;
switch (data.streamType) {
case H264_STREAM_TYPE:
stream = video;
streamType = 'video';
break;
case ADTS_STREAM_TYPE:
stream = audio;
streamType = 'audio';
break;
default:
// ignore unknown stream types
return;
}
// if a new packet is starting, we can flush the completed
// packet
if (data.payloadUnitStartIndicator) {
flushStream(stream, streamType);
}
// buffer this fragment until we are sure we've received the
// complete payload
stream.data.push(data);
stream.size += data.data.byteLength;
},
pmt: function() {
var
event = {
type: 'metadata',
tracks: []
},
programMapTable = data.programMapTable,
k,
track;
// translate streams to tracks
for (k in programMapTable) {
if (programMapTable.hasOwnProperty(k)) {
track = {};
track.id = +k;
if (programMapTable[k] === H264_STREAM_TYPE) {
track.codec = 'avc';
track.type = 'video';
} else if (programMapTable[k] === ADTS_STREAM_TYPE) {
track.codec = 'adts';
track.type = 'audio';
}
event.tracks.push(track);
}
}
self.trigger('data', event);
}
})[data.type]();
};
/**
* Flush any remaining input. Video PES packets may be of variable
* length. Normally, the start of a new video packet can trigger the
* finalization of the previous packet. That is not possible if no
* more video is forthcoming, however. In that case, some other
* mechanism (like the end of the file) has to be employed. When it is
* clear that no additional data is forthcoming, calling this method
* will flush the buffered packets.
*/
this.end = function() {
flushStream(video, 'video');
flushStream(audio, 'audio');
};
};
ElementaryStream.prototype = new videojs.Hls.Stream();
/*
* Accepts a ElementaryStream and emits data events with parsed
* AAC Audio Frames of the individual packets. Input audio in ADTS
* format is unpacked and re-emitted as AAC frames.
*
* @see http://wiki.multimedia.cx/index.php?title=ADTS
* @see http://wiki.multimedia.cx/?title=Understanding_AAC
*/
AacStream = function() {
var i = 1, self, buffer;
AacStream.prototype.init.call(this);
self = this;
this.push = function(packet) {
var frameLength;
if (packet.type !== 'audio') {
// ignore non-audio data
return;
}
buffer = packet.data;
// unpack any ADTS frames which have been fully received
while (i + 4 < buffer.length) {
// frame length is a 13 bit integer starting 16 bits from the
// end of the sync sequence
frameLength = ((buffer[i + 2] & 0x03) << 11) |
(buffer[i + 3] << 3) |
((buffer[i + 4] & 0xe0) >> 5);
// deliver the AAC frame
this.trigger('data', {
channelcount: ((buffer[i + 1] & 1) << 3) |
((buffer[i + 2] & 0xc0) >> 6),
samplerate: ADTS_SAMPLING_FREQUENCIES[(buffer[i + 1] & 0x3c) >> 2],
// assume ISO/IEC 14496-12 AudioSampleEntry default of 16
samplesize: 16,
data: buffer.subarray(i + 6, i + frameLength - 1)
});
// flush the finished frame and try again
buffer = buffer.subarray(i + frameLength - 1);
i = 1;
}
};
};
AacStream.prototype = new videojs.Hls.Stream();
/**
* Constructs a single-track, ISO BMFF media segment from AAC data
* events. The output of this stream can be fed to a SourceBuffer
* configured with a suitable initialization segment.
*/
// TODO: share common code with VideoSegmentStream
AudioSegmentStream = function(track) {
var aacFrames = [], aacFramesLength = 0, sequenceNumber = 0;
AudioSegmentStream.prototype.init.call(this);
this.push = function(data) {
// buffer audio data until end() is called
aacFrames.push(data);
aacFramesLength += data.data.byteLength;
};
this.end = function() {
var boxes, currentFrame, data, sample, i, mdat, moof;
// return early if no audio data has been observed
if (aacFramesLength === 0) {
return;
}
// concatenate the audio data to constuct the mdat
data = new Uint8Array(aacFramesLength);
track.samples = [];
i = 0;
while (aacFrames.length) {
currentFrame = aacFrames[0];
sample = {
size: currentFrame.data.byteLength,
duration: 1024 // FIXME calculate for realz
};
track.samples.push(sample);
data.set(currentFrame.data, i);
i += currentFrame.data.byteLength;
aacFrames.shift();
}
aacFramesLength = 0;
mdat = mp4.mdat(data);
moof = mp4.moof(sequenceNumber, [track]);
boxes = new Uint8Array(moof.byteLength + mdat.byteLength);
// bump the sequence number for next time
sequenceNumber++;
boxes.set(moof);
boxes.set(mdat, moof.byteLength);
this.trigger('data', boxes);
};
};
AudioSegmentStream.prototype = new videojs.Hls.Stream();
/**
* Accepts a NAL unit byte stream and unpacks the embedded NAL units.
*/
NalByteStream = function() {
var
syncPoint = 1,
i,
buffer;
NalByteStream.prototype.init.call(this);
this.push = function(data) {
var swapBuffer;
if (!buffer) {
buffer = data.data;
} else {
swapBuffer = new Uint8Array(buffer.byteLength + data.data.byteLength);
swapBuffer.set(buffer);
swapBuffer.set(data.data, buffer.byteLength);
buffer = swapBuffer;
}
// Rec. ITU-T H.264, Annex B
// scan for NAL unit boundaries
// a match looks like this:
// 0 0 1 .. NAL .. 0 0 1
// ^ sync point ^ i
// or this:
// 0 0 1 .. NAL .. 0 0 0
// ^ sync point ^ i
// advance the sync point to a NAL start, if necessary
for (; syncPoint < buffer.byteLength - 3; syncPoint++) {
if (buffer[syncPoint + 2] === 1) {
// the sync point is properly aligned
i = syncPoint + 5;
break;
}
}
while (i < buffer.byteLength) {
// look at the current byte to determine if we've hit the end of
// a NAL unit boundary
switch (buffer[i]) {
case 0:
// skip past non-sync sequences
if (buffer[i - 1] !== 0) {
i += 2;
break;
} else if (buffer[i - 2] !== 0) {
i++;
break;
}
// deliver the NAL unit
this.trigger('data', buffer.subarray(syncPoint + 3, i - 2));
// drop trailing zeroes
do {
i++;
} while (buffer[i] !== 1 && i < buffer.length);
syncPoint = i - 2;
i += 3;
break;
case 1:
// skip past non-sync sequences
if (buffer[i - 1] !== 0 ||
buffer[i - 2] !== 0) {
i += 3;
break;
}
// deliver the NAL unit
this.trigger('data', buffer.subarray(syncPoint + 3, i - 2));
syncPoint = i - 2;
i += 3;
break;
default:
// the current byte isn't a one or zero, so it cannot be part
// of a sync sequence
i += 3;
break;
}
}
// filter out the NAL units that were delivered
buffer = buffer.subarray(syncPoint);
i -= syncPoint;
syncPoint = 0;
};
this.end = function() {
// deliver the last buffered NAL unit
if (buffer && buffer.byteLength > 3) {
this.trigger('data', buffer.subarray(syncPoint + 3));
}
};
};
NalByteStream.prototype = new videojs.Hls.Stream();
/**
* Accepts input from a ElementaryStream and produces H.264 NAL unit data
* events.
*/
H264Stream = function() {
var
nalByteStream = new NalByteStream(),
self,
trackId,
currentPts,
currentDts,
readSequenceParameterSet,
skipScalingList;
H264Stream.prototype.init.call(this);
self = this;
this.push = function(packet) {
if (packet.type !== 'video') {
return;
}
trackId = packet.trackId;
currentPts = packet.pts;
currentDts = packet.dts;
nalByteStream.push(packet);
};
nalByteStream.on('data', function(data) {
var event = {
trackId: trackId,
pts: currentPts,
dts: currentDts,
data: data
};
switch (data[0] & 0x1f) {
case 0x05:
event.nalUnitType = 'slice_layer_without_partitioning_rbsp_idr';
break;
case 0x07:
event.nalUnitType = 'seq_parameter_set_rbsp';
event.config = readSequenceParameterSet(data.subarray(1));
break;
case 0x08:
event.nalUnitType = 'pic_parameter_set_rbsp';
break;
case 0x09:
event.nalUnitType = 'access_unit_delimiter_rbsp';
break;
default:
break;
}
self.trigger('data', event);
});
this.end = function() {
nalByteStream.end();
};
/**
* Advance the ExpGolomb decoder past a scaling list. The scaling
* list is optionally transmitted as part of a sequence parameter
* set and is not relevant to transmuxing.
* @param count {number} the number of entries in this scaling list
* @param expGolombDecoder {object} an ExpGolomb pointed to the
* start of a scaling list
* @see Recommendation ITU-T H.264, Section 7.3.2.1.1.1
*/
skipScalingList = function(count, expGolombDecoder) {
var
lastScale = 8,
nextScale = 8,
j,
deltaScale;
for (j = 0; j < count; j++) {
if (nextScale !== 0) {
deltaScale = expGolombDecoder.readExpGolomb();
nextScale = (lastScale + deltaScale + 256) % 256;
}
lastScale = (nextScale === 0) ? lastScale : nextScale;
}
};
/**
* Read a sequence parameter set and return some interesting video
* properties. A sequence parameter set is the H264 metadata that
* describes the properties of upcoming video frames.
* @param data {Uint8Array} the bytes of a sequence parameter set
* @return {object} an object with configuration parsed from the
* sequence parameter set, including the dimensions of the
* associated video frames.
*/
readSequenceParameterSet = function(data) {
var
frameCropLeftOffset = 0,
frameCropRightOffset = 0,
frameCropTopOffset = 0,
frameCropBottomOffset = 0,
expGolombDecoder, profileIdc, levelIdc, profileCompatibility,
chromaFormatIdc, picOrderCntType,
numRefFramesInPicOrderCntCycle, picWidthInMbsMinus1,
picHeightInMapUnitsMinus1,
frameMbsOnlyFlag,
scalingListCount,
i;
expGolombDecoder = new videojs.Hls.ExpGolomb(data);
profileIdc = expGolombDecoder.readUnsignedByte(); // profile_idc
profileCompatibility = expGolombDecoder.readBits(5); // constraint_set[0-5]_flag
expGolombDecoder.skipBits(3); // u(1), reserved_zero_2bits u(2)
levelIdc = expGolombDecoder.readUnsignedByte(); // level_idc u(8)
expGolombDecoder.skipUnsignedExpGolomb(); // seq_parameter_set_id
// some profiles have more optional data we don't need
if (profileIdc === 100 ||
profileIdc === 110 ||
profileIdc === 122 ||
profileIdc === 244 ||
profileIdc === 44 ||
profileIdc === 83 ||
profileIdc === 86 ||
profileIdc === 118 ||
profileIdc === 128) {
chromaFormatIdc = expGolombDecoder.readUnsignedExpGolomb();
if (chromaFormatIdc === 3) {
expGolombDecoder.skipBits(1); // separate_colour_plane_flag
}
expGolombDecoder.skipUnsignedExpGolomb(); // bit_depth_luma_minus8
expGolombDecoder.skipUnsignedExpGolomb(); // bit_depth_chroma_minus8
expGolombDecoder.skipBits(1); // qpprime_y_zero_transform_bypass_flag
if (expGolombDecoder.readBoolean()) { // seq_scaling_matrix_present_flag
scalingListCount = (chromaFormatIdc !== 3) ? 8 : 12;
for (i = 0; i < scalingListCount; i++) {
if (expGolombDecoder.readBoolean()) { // seq_scaling_list_present_flag[ i ]
if (i < 6) {
skipScalingList(16, expGolombDecoder);
} else {
skipScalingList(64, expGolombDecoder);
}
}
}
}
}
expGolombDecoder.skipUnsignedExpGolomb(); // log2_max_frame_num_minus4
picOrderCntType = expGolombDecoder.readUnsignedExpGolomb();
if (picOrderCntType === 0) {
expGolombDecoder.readUnsignedExpGolomb(); //log2_max_pic_order_cnt_lsb_minus4
} else if (picOrderCntType === 1) {
expGolombDecoder.skipBits(1); // delta_pic_order_always_zero_flag
expGolombDecoder.skipExpGolomb(); // offset_for_non_ref_pic
expGolombDecoder.skipExpGolomb(); // offset_for_top_to_bottom_field
numRefFramesInPicOrderCntCycle = expGolombDecoder.readUnsignedExpGolomb();
for(i = 0; i < numRefFramesInPicOrderCntCycle; i++) {
expGolombDecoder.skipExpGolomb(); // offset_for_ref_frame[ i ]
}
}
expGolombDecoder.skipUnsignedExpGolomb(); // max_num_ref_frames
expGolombDecoder.skipBits(1); // gaps_in_frame_num_value_allowed_flag
picWidthInMbsMinus1 = expGolombDecoder.readUnsignedExpGolomb();
picHeightInMapUnitsMinus1 = expGolombDecoder.readUnsignedExpGolomb();
frameMbsOnlyFlag = expGolombDecoder.readBits(1);
if (frameMbsOnlyFlag === 0) {
expGolombDecoder.skipBits(1); // mb_adaptive_frame_field_flag
}
expGolombDecoder.skipBits(1); // direct_8x8_inference_flag
if (expGolombDecoder.readBoolean()) { // frame_cropping_flag
frameCropLeftOffset = expGolombDecoder.readUnsignedExpGolomb();
frameCropRightOffset = expGolombDecoder.readUnsignedExpGolomb();
frameCropTopOffset = expGolombDecoder.readUnsignedExpGolomb();
frameCropBottomOffset = expGolombDecoder.readUnsignedExpGolomb();
}
return {
profileIdc: profileIdc,
levelIdc: levelIdc,
profileCompatibility: profileCompatibility,
width: ((picWidthInMbsMinus1 + 1) * 16) - frameCropLeftOffset * 2 - frameCropRightOffset * 2,
height: ((2 - frameMbsOnlyFlag) * (picHeightInMapUnitsMinus1 + 1) * 16) - (frameCropTopOffset * 2) - (frameCropBottomOffset * 2)
};
};
};
H264Stream.prototype = new videojs.Hls.Stream();
/**
* Constructs a single-track, ISO BMFF media segment from H264 data
* events. The output of this stream can be fed to a SourceBuffer
* configured with a suitable initialization segment.
* @param track {object} track metadata configuration
*/
VideoSegmentStream = function(track) {
var
sequenceNumber = 0,
nalUnits = [],
nalUnitsLength = 0;
VideoSegmentStream.prototype.init.call(this);
this.push = function(data) {
// buffer video until end() is called
nalUnits.push(data);
nalUnitsLength += data.data.byteLength;
};
this.end = function() {
var startUnit, currentNal, moof, mdat, boxes, i, data, view, sample;
// return early if no video data has been observed
if (nalUnitsLength === 0) {
return;
}
// concatenate the video data and construct the mdat
// first, we have to build the index from byte locations to
// samples (that is, frames) in the video data
data = new Uint8Array(nalUnitsLength + (4 * nalUnits.length));
view = new DataView(data.buffer);
track.samples = [];
// see ISO/IEC 14496-12:2012, section 8.6.4.3
sample = {
size: 0,
flags: {
isLeading: 0,
dependsOn: 1,
isDependedOn: 0,
hasRedundancy: 0,
degradationPriority: 0
}
};
i = 0;
while (nalUnits.length) {
currentNal = nalUnits[0];
// flush the sample we've been building when a new sample is started
if (currentNal.nalUnitType === 'access_unit_delimiter_rbsp') {
if (startUnit) {
// convert the duration to 90kHZ timescale to match the
// timescales specified in the init segment
sample.duration = (currentNal.dts - startUnit.dts) * 90;
track.samples.push(sample);
}
sample = {
size: 0,
flags: {
isLeading: 0,
dependsOn: 1,
isDependedOn: 0,
hasRedundancy: 0,
degradationPriority: 0
},
compositionTimeOffset: currentNal.pts - currentNal.dts
};
startUnit = currentNal;
}
if (currentNal.nalUnitType === 'slice_layer_without_partitioning_rbsp_idr') {
// the current sample is a key frame
sample.flags.dependsOn = 2;
}
sample.size += 4; // space for the NAL length
sample.size += currentNal.data.byteLength;
view.setUint32(i, currentNal.data.byteLength);
i += 4;
data.set(currentNal.data, i);
i += currentNal.data.byteLength;
nalUnits.shift();
}
// record the last sample
if (track.samples.length) {
sample.duration = track.samples[track.samples.length - 1].duration;
}
track.samples.push(sample);
nalUnitsLength = 0;
mdat = mp4.mdat(data);
moof = mp4.moof(sequenceNumber, [track]);
// it would be great to allocate this array up front instead of
// throwing away hundreds of media segment fragments
boxes = new Uint8Array(moof.byteLength + mdat.byteLength);
// bump the sequence number for next time
sequenceNumber++;
boxes.set(moof);
boxes.set(mdat, moof.byteLength);
this.trigger('data', boxes);
};
};
VideoSegmentStream.prototype = new videojs.Hls.Stream();
/**
* A Stream that expects MP2T binary data as input and produces
* corresponding media segments, suitable for use with Media Source
* Extension (MSE) implementations that support the ISO BMFF byte
* stream format, like Chrome.
* @see test/muxer/mse-demo.html for sample usage of a Transmuxer with
* MSE
*/
Transmuxer = function() {
var
self = this,
videoTrack,
audioTrack,
config,
pps,
packetStream, parseStream, elementaryStream,
aacStream, h264Stream,
videoSegmentStream, audioSegmentStream;
Transmuxer.prototype.init.call(this);
// set up the parsing pipeline
packetStream = new TransportPacketStream();
parseStream = new TransportParseStream();
elementaryStream = new ElementaryStream();
aacStream = new AacStream();
h264Stream = new H264Stream();
packetStream.pipe(parseStream);
parseStream.pipe(elementaryStream);
elementaryStream.pipe(aacStream);
elementaryStream.pipe(h264Stream);
// handle incoming data events
h264Stream.on('data', function(data) {
// record the track config
if (data.nalUnitType === 'seq_parameter_set_rbsp' &&
!config) {
config = data.config;
videoTrack.width = config.width;
videoTrack.height = config.height;
videoTrack.sps = [data.data];
videoTrack.profileIdc = config.profileIdc;
videoTrack.levelIdc = config.levelIdc;
videoTrack.profileCompatibility = config.profileCompatibility;
// generate an init segment once all the metadata is available
if (pps) {
self.trigger('data', {
type: 'video',
data: videojs.mp4.initSegment([videoTrack])
});
}
}
if (data.nalUnitType === 'pic_parameter_set_rbsp' &&
!pps) {
pps = data.data;
videoTrack.pps = [data.data];
if (config) {
self.trigger('data', {
type: 'video',
data: videojs.mp4.initSegment([videoTrack])
});
}
}
});
// generate an init segment based on the first audio sample
aacStream.on('data', function(data) {
if (audioTrack && audioTrack.channelcount === undefined) {
audioTrack.channelcount = data.channelcount;
audioTrack.samplerate = data.samplerate;
audioTrack.samplesize = data.samplesize;
self.trigger('data', {
type: 'audio',
data: videojs.mp4.initSegment([audioTrack])
});
}
});
// hook up the segment streams once track metadata is delivered
elementaryStream.on('data', function(data) {
var i, triggerData = function(type) {
return function(segment) {
self.trigger('data', {
type: type,
data: segment
});
};
};
if (data.type === 'metadata') {
i = data.tracks.length;
// scan the tracks listed in the metadata
while (i--) {
// hook up the video segment stream to the first track with h264 data
if (data.tracks[i].type === 'video' && !videoSegmentStream) {
videoTrack = data.tracks[i];
videoSegmentStream = new VideoSegmentStream(videoTrack);
h264Stream.pipe(videoSegmentStream);
videoSegmentStream.on('data', triggerData('video'));
break;
}
// hook up the audio segment stream to the first track with aac data
if (data.tracks[i].type === 'audio' && !audioSegmentStream) {
audioTrack = data.tracks[i];
audioSegmentStream = new AudioSegmentStream(audioTrack);
aacStream.pipe(audioSegmentStream);
audioSegmentStream.on('data', triggerData('audio'));
}
}
}
});
// feed incoming data to the front of the parsing pipeline
this.push = function(data) {
packetStream.push(data);
};
// flush any buffered data
this.end = function() {
elementaryStream.end();
h264Stream.end();
if (videoSegmentStream) {
videoSegmentStream.end();
}
if (audioSegmentStream) {
audioSegmentStream.end();
}
};
};
Transmuxer.prototype = new videojs.Hls.Stream();
window.videojs.mp2t = {
PAT_PID: 0x0000,
MP2T_PACKET_LENGTH: MP2T_PACKET_LENGTH,
H264_STREAM_TYPE: H264_STREAM_TYPE,
ADTS_STREAM_TYPE: ADTS_STREAM_TYPE,
TransportPacketStream: TransportPacketStream,
TransportParseStream: TransportParseStream,
ElementaryStream: ElementaryStream,
VideoSegmentStream: VideoSegmentStream,
Transmuxer: Transmuxer,
AacStream: AacStream,
H264Stream: H264Stream
};
})(window, window.videojs);
(function(window) {
/*
======== A Handy Little QUnit Reference ========
http://api.qunitjs.com/
Test methods:
module(name, {[setup][ ,teardown]})
test(name, callback)
expect(numberOfAssertions)
stop(increment)
start(decrement)
Test assertions:
ok(value, [message])
equal(actual, expected, [message])
notEqual(actual, expected, [message])
deepEqual(actual, expected, [message])
notDeepEqual(actual, expected, [message])
strictEqual(actual, expected, [message])
notStrictEqual(actual, expected, [message])
throws(block, [expected], [message])
*/
var
buffer,
ExpGolomb = window.videojs.Hls.ExpGolomb,
expGolomb;
module('Exponential Golomb coding');
test('small numbers are coded correctly', function() {
var
expected = [
[0xF8, 0],
[0x5F, 1],
[0x7F, 2],
[0x27, 3],
[0x2F, 4],
[0x37, 5],
[0x3F, 6],
[0x11, 7],
[0x13, 8],
[0x15, 9]
],
i = expected.length,
result;
while (i--) {
buffer = new Uint8Array([expected[i][0]]);
expGolomb = new ExpGolomb(buffer);
result = expGolomb.readUnsignedExpGolomb();
equal(expected[i][1], result, expected[i][0] + ' is decoded to ' + expected[i][1]);
}
});
test('drops working data as it is parsed', function() {
var expGolomb = new ExpGolomb(new Uint8Array([0x00, 0xFF]));
expGolomb.skipBits(8);
equal(8, expGolomb.bitsAvailable(), '8 bits remain');
equal(0xFF, expGolomb.readBits(8), 'the second byte is read');
});
test('drops working data when skipping leading zeros', function() {
var expGolomb = new ExpGolomb(new Uint8Array([0x00, 0x00, 0x00, 0x00, 0xFF]));
equal(32, expGolomb.skipLeadingZeros(), '32 leading zeros are dropped');
equal(8, expGolomb.bitsAvailable(), '8 bits remain');
equal(0xFF, expGolomb.readBits(8), 'the second byte is read');
});
test('drops working data when skipping leading zeros', function() {
var expGolomb = new ExpGolomb(new Uint8Array([0x15, 0xab, 0x40, 0xc8, 0xFF]));
equal(3, expGolomb.skipLeadingZeros(), '3 leading zeros are dropped');
equal((8 * 4) + 5, expGolomb.bitsAvailable(), '37 bits remain');
expGolomb.skipBits(1);
equal(0x5a, expGolomb.readBits(8), 'the next bits are read');
});
test('parses a sequence parameter set', function() {
var
sps = new Uint8Array([
0x27, 0x42, 0xe0, 0x0b,
0xa9, 0x18, 0x60, 0x9d,
0x80, 0x35, 0x06, 0x01,
0x06, 0xb6, 0xc2, 0xb5,
0xef, 0x7c, 0x04
]),
expGolomb = new ExpGolomb(sps);
strictEqual(expGolomb.readBits(8), 0x27, 'the NAL type specifies an SPS');
strictEqual(expGolomb.readBits(8), 66, 'profile_idc is 66');
strictEqual(expGolomb.readBits(4), 0x0E, 'constraints 0-3 are correct');
expGolomb.skipBits(4);
strictEqual(expGolomb.readBits(8), 11, 'level_idc is 11');
strictEqual(expGolomb.readUnsignedExpGolomb(), 0, 'seq_parameter_set_id is 0');
strictEqual(expGolomb.readUnsignedExpGolomb(), 1, 'log2_max_frame_num_minus4 is 1');
strictEqual(expGolomb.readUnsignedExpGolomb(), 0, 'pic_order_cnt_type is 0');
strictEqual(expGolomb.readUnsignedExpGolomb(), 3, 'log2_max_pic_order_cnt_lsb_minus4 is 3');
strictEqual(expGolomb.readUnsignedExpGolomb(), 2, 'max_num_ref_frames is 2');
strictEqual(expGolomb.readBits(1), 0, 'gaps_in_frame_num_value_allowed_flag is false');
strictEqual(expGolomb.readUnsignedExpGolomb(), 11, 'pic_width_in_mbs_minus1 is 11');
strictEqual(expGolomb.readUnsignedExpGolomb(), 8, 'pic_height_in_map_units_minus1 is 8');
strictEqual(expGolomb.readBits(1), 1, 'frame_mbs_only_flag is true');
strictEqual(expGolomb.readBits(1), 1, 'direct_8x8_inference_flag is true');
strictEqual(expGolomb.readBits(1), 0, 'frame_cropping_flag is false');
});
})(this);
(function(window) {
/*
======== A Handy Little QUnit Reference ========
http://api.qunitjs.com/
Test methods:
module(name, {[setup][ ,teardown]})
test(name, callback)
expect(numberOfAssertions)
stop(increment)
start(decrement)
Test assertions:
ok(value, [message])
equal(actual, expected, [message])
notEqual(actual, expected, [message])
deepEqual(actual, expected, [message])
notDeepEqual(actual, expected, [message])
strictEqual(actual, expected, [message])
notStrictEqual(actual, expected, [message])
throws(block, [expected], [message])
*/
var FlvTag = window.videojs.Hls.FlvTag;
module('FLV tag');
test('writeBytes with zero length writes the entire array', function() {
var
tag = new FlvTag(FlvTag.VIDEO_TAG),
headerLength = tag.length;
tag.writeBytes(new Uint8Array([0x1, 0x2, 0x3]));
equal(3 + headerLength, tag.length, '3 payload bytes are written');
});
test('writeShort writes a two byte sequence', function() {
var
tag = new FlvTag(FlvTag.VIDEO_TAG),
headerLength = tag.length;
tag.writeShort(0x0102);
equal(2 + headerLength, tag.length, '2 bytes are written');
equal(0x0102,
new DataView(tag.bytes.buffer).getUint16(tag.length - 2),
'the value is written');
});
test('writeBytes grows the internal byte array dynamically', function() {
var
tag = new FlvTag(FlvTag.VIDEO_TAG),
tooManyBytes = new Uint8Array(tag.bytes.byteLength + 1);
try {
tag.writeBytes(tooManyBytes);
ok(true, 'the buffer grew to fit the data');
} catch(e) {
ok(!e, 'the buffer should grow');
}
});
})(this);
(function(videojs) {
module('H264 Stream');
var
nalUnitTypes = window.videojs.Hls.NALUnitType,
FlvTag = window.videojs.Hls.FlvTag,
accessUnitDelimiter = new Uint8Array([
0x00,
0x00,
0x01,
nalUnitTypes.access_unit_delimiter_rbsp
]),
seqParamSet = new Uint8Array([
0x00,
0x00,
0x01,
0x60 | nalUnitTypes.seq_parameter_set_rbsp,
0x00, // profile_idc
0x00, // constraint_set flags
0x00, // level_idc
// seq_parameter_set_id ue(v) 0 => 1
// log2_max_frame_num_minus4 ue(v) 1 => 010
// pic_order_cnt_type ue(v) 0 => 1
// log2_max_pic_order_cnt_lsb_minus4 ue(v) 1 => 010
// max_num_ref_frames ue(v) 1 => 010
// gaps_in_frame_num_value_allowed u(1) 0
// pic_width_in_mbs_minus1 ue(v) 0 => 1
// pic_height_in_map_units_minus1 ue(v) 0 => 1
// frame_mbs_only_flag u(1) 1
// direct_8x8_inference_flag u(1) 0
// frame_cropping_flag u(1) 0
// vui_parameters_present_flag u(1) 0
// 1010 1010 0100 1110 00(00 0000)
0xAA,
0x4E,
0x00
]);
test('metadata is generated for IDRs after a full NAL unit is written', function() {
var
h264Stream = new videojs.Hls.H264Stream(),
idr = new Uint8Array([
0x00,
0x00,
0x01,
nalUnitTypes.slice_layer_without_partitioning_rbsp_idr
]);
h264Stream.setNextTimeStamp(0, 0, true);
h264Stream.writeBytes(accessUnitDelimiter, 0, accessUnitDelimiter.byteLength);
h264Stream.writeBytes(seqParamSet, 0, seqParamSet.byteLength);
h264Stream.writeBytes(idr, 0, idr.byteLength);
h264Stream.setNextTimeStamp(1, 1, true);
strictEqual(h264Stream.tags.length, 3, 'three tags are written');
ok(FlvTag.isMetaData(h264Stream.tags[0].bytes),
'metadata is written');
ok(FlvTag.isVideoFrame(h264Stream.tags[1].bytes),
'picture parameter set is written');
ok(h264Stream.tags[2].keyFrame, 'key frame is written');
});
test('make sure we add metadata and extra data at the beginning of a stream', function() {
var
H264ExtraData = videojs.Hls.H264ExtraData,
oldExtraData = H264ExtraData.prototype.extraDataTag,
oldMetadata = H264ExtraData.prototype.metaDataTag,
h264Stream;
H264ExtraData.prototype.extraDataTag = function() {
return 'extraDataTag';
};
H264ExtraData.prototype.metaDataTag = function() {
return 'metaDataTag';
};
h264Stream = new videojs.Hls.H264Stream();
h264Stream.setTimeStampOffset(0);
h264Stream.setNextTimeStamp(0, 0, true);
// the sps provides the metadata for the stream
h264Stream.writeBytes(seqParamSet, 0, seqParamSet.byteLength);
h264Stream.writeBytes(accessUnitDelimiter, 0, accessUnitDelimiter.byteLength);
// make sure that keyFrame is set to false but that we don't have any tags currently written out
h264Stream._h264Frame.keyFrame = false;
h264Stream.tags = [];
h264Stream.setNextTimeStamp(5, 5, true);
h264Stream.writeBytes(accessUnitDelimiter, 0, accessUnitDelimiter.byteLength);
// flush out the last tag
h264Stream.writeBytes(accessUnitDelimiter, 0, accessUnitDelimiter.byteLength);
strictEqual(h264Stream.tags.length, 4, 'three tags are ready');
strictEqual(h264Stream.tags[0], 'metaDataTag', 'the first tag is the metaDataTag');
strictEqual(h264Stream.tags[1], 'extraDataTag', 'the second tag is the extraDataTag');
H264ExtraData.prototype.extraDataTag = oldExtraData;
H264ExtraData.prototype.metaDataTag = oldMetadata;
});
})(window.videojs);
......@@ -77,25 +77,15 @@ module.exports = function(config) {
'../test/karma-qunit-shim.js',
'../src/videojs-hls.js',
'../src/stream.js',
'../src/flv-tag.js',
'../src/exp-golomb.js',
'../src/h264-extradata.js',
'../src/h264-stream.js',
'../src/aac-stream.js',
'../src/metadata-stream.js',
'../src/segment-parser.js',
'../src/m3u8/m3u8-parser.js',
'../src/xhr.js',
'../src/playlist.js',
'../src/playlist-loader.js',
'../src/decrypter.js',
'../src/mp4-generator.js',
'../src/transmuxer.js',
'../tmp/manifests.js',
'../tmp/expected.js',
'tsSegment-bc.js',
'../src/bin-utils.js',
'../test/muxer/js/mp4-inspector.js',
'../test/*.js',
],
......
......@@ -42,25 +42,15 @@ module.exports = function(config) {
'../test/karma-qunit-shim.js',
'../src/videojs-hls.js',
'../src/stream.js',
'../src/flv-tag.js',
'../src/exp-golomb.js',
'../src/h264-extradata.js',
'../src/h264-stream.js',
'../src/aac-stream.js',
'../src/metadata-stream.js',
'../src/segment-parser.js',
'../src/m3u8/m3u8-parser.js',
'../src/xhr.js',
'../src/playlist.js',
'../src/playlist-loader.js',
'../src/decrypter.js',
'../src/mp4-generator.js',
'../src/transmuxer.js',
'../tmp/manifests.js',
'../tmp/expected.js',
'tsSegment-bc.js',
'../src/bin-utils.js',
'../test/muxer/js/mp4-inspector.js',
'../test/*.js',
],
......
(function(window, videojs, undefined) {
'use strict';
/*
======== A Handy Little QUnit Reference ========
http://api.qunitjs.com/
Test methods:
module(name, {[setup][ ,teardown]})
test(name, callback)
expect(numberOfAssertions)
stop(increment)
start(decrement)
Test assertions:
ok(value, [message])
equal(actual, expected, [message])
notEqual(actual, expected, [message])
deepEqual(actual, expected, [message])
notDeepEqual(actual, expected, [message])
strictEqual(actual, expected, [message])
notStrictEqual(actual, expected, [message])
throws(block, [expected], [message])
*/
var metadataStream, stringToInts, stringToCString, id3Tag, id3Frame;
module('MetadataStream', {
setup: function() {
metadataStream = new videojs.Hls.MetadataStream();
}
});
test('can construct a MetadataStream', function() {
ok(metadataStream, 'does not return null');
});
stringToInts = function(string) {
var result = [], i;
for (i = 0; i < string.length; i++) {
result[i] = string.charCodeAt(i);
}
return result;
};
stringToCString = function(string) {
return stringToInts(string).concat([0x00]);
};
id3Tag = function() {
var
frames = Array.prototype.concat.apply([], Array.prototype.slice.call(arguments)),
result = stringToInts('ID3').concat([
0x03, 0x00, // version 3.0 of ID3v2 (aka ID3v.2.3.0)
0x40, // flags. include an extended header
0x00, 0x00, 0x00, 0x00, // size. set later
// extended header
0x00, 0x00, 0x00, 0x06, // extended header size. no CRC
0x00, 0x00, // extended flags
0x00, 0x00, 0x00, 0x02 // size of padding
], frames),
size;
// size is stored as a sequence of four 7-bit integers with the
// high bit of each byte set to zero
size = result.length - 10;
result[6] = (size >>> 21) & 0x7f;
result[7] = (size >>> 14) & 0x7f;
result[8] = (size >>> 7) & 0x7f;
result[9] = (size) & 0x7f;
return result;
};
id3Frame = function(type) {
var result = stringToInts(type).concat([
0x00, 0x00, 0x00, 0x00, // size
0xe0, 0x00 // flags. tag/file alter preservation, read-only
]),
size = result.length - 10;
// append the fields of the ID3 frame
result = result.concat.apply(result, Array.prototype.slice.call(arguments, 1));
// set the size
size = result.length - 10;
result[4] = (size >>> 24);
result[5] = (size >>> 16) & 0xff;
result[6] = (size >>> 8) & 0xff;
result[7] = (size) & 0xff;
return result;
};
test('parses simple ID3 metadata out of PES packets', function() {
var
events = [],
wxxxPayload = [
0x00 // text encoding. ISO-8859-1
].concat(stringToCString('ad tag URL'), // description
stringToInts('http://example.com/ad?v=1234&q=7')), // value
id3Bytes,
size;
metadataStream.on('data', function(event) {
events.push(event);
});
id3Bytes = new Uint8Array(stringToInts('ID3').concat([
0x03, 0x00, // version 3.0 of ID3v2 (aka ID3v.2.3.0)
0x40, // flags. include an extended header
0x00, 0x00, 0x00, 0x00, // size. set later
// extended header
0x00, 0x00, 0x00, 0x06, // extended header size. no CRC
0x00, 0x00, // extended flags
0x00, 0x00, 0x00, 0x02, // size of padding
// frame 0
// http://id3.org/id3v2.3.0#User_defined_text_information_frame
], id3Frame('WXXX',
wxxxPayload), // value
// frame 1
// custom tag
id3Frame('XINF',
[
0x04, 0x03, 0x02, 0x01 // arbitrary data
]), [
0x00, 0x00 // padding
]));
// set header size field
size = id3Bytes.byteLength - 10;
id3Bytes[6] = (size >>> 21) & 0x7f;
id3Bytes[7] = (size >>> 14) & 0x7f;
id3Bytes[8] = (size >>> 7) & 0x7f;
id3Bytes[9] = (size) & 0x7f;
metadataStream.push({
trackId: 7,
pts: 1000,
dts: 1000,
// header
data: id3Bytes
});
equal(events.length, 1, 'parsed one tag');
equal(events[0].frames.length, 2, 'parsed two frames');
equal(events[0].frames[0].id, 'WXXX', 'parsed a WXXX frame');
deepEqual(new Uint8Array(events[0].frames[0].data),
new Uint8Array(wxxxPayload),
'attached the frame payload');
equal(events[0].frames[1].id, 'XINF', 'parsed a user-defined frame');
deepEqual(new Uint8Array(events[0].frames[1].data),
new Uint8Array([0x04, 0x03, 0x02, 0x01]),
'attached the frame payload');
equal(events[0].pts, 1000, 'did not modify the PTS');
equal(events[0].dts, 1000, 'did not modify the PTS');
});
test('skips non-ID3 metadata events', function() {
var events = [];
metadataStream.on('data', function(event) {
events.push(event);
});
metadataStream.push({
trackId: 7,
pts: 1000,
dts: 1000,
// header
data: new Uint8Array([0])
});
equal(events.length, 0, 'did not emit an event');
});
// missing cases:
// unsynchronization
// CRC
// no extended header
// compressed frames
// encrypted frames
// frame groups
// too large/small tag size values
// too large/small frame size values
test('parses TXXX frames', function() {
var events = [];
metadataStream.on('data', function(event) {
events.push(event);
});
metadataStream.push({
trackId: 7,
pts: 1000,
dts: 900,
// header
data: new Uint8Array(id3Tag(id3Frame('TXXX',
0x03, // utf-8
stringToCString('get done'),
stringToCString('{ "key": "value" }')),
[0x00, 0x00]))
});
equal(events.length, 1, 'parsed one tag');
equal(events[0].frames.length, 1, 'parsed one frame');
equal(events[0].frames[0].id, 'TXXX', 'parsed the frame id');
equal(events[0].frames[0].description, 'get done', 'parsed the description');
deepEqual(JSON.parse(events[0].frames[0].value), { key: 'value' }, 'parsed the value');
});
test('parses WXXX frames', function() {
var events = [], url = 'http://example.com/path/file?abc=7&d=4#ty';
metadataStream.on('data', function(event) {
events.push(event);
});
metadataStream.push({
trackId: 7,
pts: 1000,
dts: 900,
// header
data: new Uint8Array(id3Tag(id3Frame('WXXX',
0x03, // utf-8
stringToCString(''),
stringToInts(url)),
[0x00, 0x00]))
});
equal(events.length, 1, 'parsed one tag');
equal(events[0].frames.length, 1, 'parsed one frame');
equal(events[0].frames[0].id, 'WXXX', 'parsed the frame id');
equal(events[0].frames[0].description, '', 'parsed the description');
equal(events[0].frames[0].url, url, 'parsed the value');
});
test('parses TXXX frames with characters that have a single-digit hexadecimal representation', function() {
var events = [], value = String.fromCharCode(7);
metadataStream.on('data', function(event) {
events.push(event);
});
metadataStream.push({
trackId: 7,
pts: 1000,
dts: 900,
// header
data: new Uint8Array(id3Tag(id3Frame('TXXX',
0x03, // utf-8
stringToCString(''),
stringToCString(value)),
[0x00, 0x00]))
});
equal(events[0].frames[0].value,
value,
'parsed the single-digit character');
});
test('parses PRIV frames', function() {
var
events = [],
payload = stringToInts('arbitrary data may be included in the payload ' +
'of a PRIV frame');
metadataStream.on('data', function(event) {
events.push(event);
});
metadataStream.push({
trackId: 7,
pts: 1000,
dts: 900,
// header
data: new Uint8Array(id3Tag(id3Frame('PRIV',
stringToCString('priv-owner@example.com'),
payload)))
});
equal(events.length, 1, 'parsed a tag');
equal(events[0].frames.length, 1, 'parsed a frame');
equal(events[0].frames[0].id, 'PRIV', 'frame id is PRIV');
equal(events[0].frames[0].owner, 'priv-owner@example.com', 'parsed the owner');
deepEqual(new Uint8Array(events[0].frames[0].privateData),
new Uint8Array(payload),
'parsed the frame private data');
});
test('parses tags split across pushes', function() {
var
events = [],
owner = stringToCString('owner@example.com'),
payload = stringToInts('A TS packet is 188 bytes in length so that it can' +
' be easily transmitted over ATM networks, an ' +
'important medium at one time. We want to be sure' +
' that ID3 frames larger than a TS packet are ' +
'properly re-assembled.'),
tag = new Uint8Array(id3Tag(id3Frame('PRIV', owner, payload))),
front = tag.subarray(0, 100),
back = tag.subarray(100);
metadataStream.on('data', function(event) {
events.push(event);
});
metadataStream.push({
trackId: 7,
pts: 1000,
dts: 900,
data: front
});
equal(events.length, 0, 'parsed zero tags');
metadataStream.push({
trackId: 7,
pts: 1000,
dts: 900,
data: back
});
equal(events.length, 1, 'parsed a tag');
equal(events[0].frames.length, 1, 'parsed a frame');
equal(events[0].frames[0].data.byteLength,
owner.length + payload.length,
'collected data across pushes');
// parses subsequent fragmented tags
tag = new Uint8Array(id3Tag(id3Frame('PRIV',
owner, payload, payload)));
front = tag.subarray(0, 188);
back = tag.subarray(188);
metadataStream.push({
trackId: 7,
pts: 2000,
dts: 2000,
data: front
});
metadataStream.push({
trackId: 7,
pts: 2000,
dts: 2000,
data: back
});
equal(events.length, 2, 'parsed a subseqent frame');
});
test('ignores tags when the header is fragmented', function() {
var
events = [],
tag = new Uint8Array(id3Tag(id3Frame('PRIV',
stringToCString('owner@example.com'),
stringToInts('payload')))),
// split the 10-byte ID3 tag header in half
front = tag.subarray(0, 5),
back = tag.subarray(5);
metadataStream.on('data', function(event) {
events.push(event);
});
metadataStream.push({
trackId: 7,
pts: 1000,
dts: 900,
data: front
});
metadataStream.push({
trackId: 7,
pts: 1000,
dts: 900,
data: back
});
equal(events.length, 0, 'parsed zero tags');
metadataStream.push({
trackId: 7,
pts: 1500,
dts: 1500,
data: new Uint8Array(id3Tag(id3Frame('PRIV',
stringToCString('owner2'),
stringToInts('payload2'))))
});
equal(events.length, 1, 'parsed one tag');
equal(events[0].frames[0].owner, 'owner2', 'dropped the first tag');
});
// https://html.spec.whatwg.org/multipage/embedded-content.html#steps-to-expose-a-media-resource-specific-text-track
test('constructs the dispatch type', function() {
metadataStream = new videojs.Hls.MetadataStream({
descriptor: new Uint8Array([0x03, 0x02, 0x01, 0x00])
});
equal(metadataStream.dispatchType, '1503020100', 'built the dispatch type');
});
})(window, window.videojs);
(function(window, videojs) {
'use strict';
/*
======== A Handy Little QUnit Reference ========
http://api.qunitjs.com/
Test methods:
module(name, {[setup][ ,teardown]})
test(name, callback)
expect(numberOfAssertions)
stop(increment)
start(decrement)
Test assertions:
ok(value, [message])
equal(actual, expected, [message])
notEqual(actual, expected, [message])
deepEqual(actual, expected, [message])
notDeepEqual(actual, expected, [message])
strictEqual(actual, expected, [message])
notStrictEqual(actual, expected, [message])
throws(block, [expected], [message])
*/
var
mp4 = videojs.mp4,
inspectMp4 = videojs.inspectMp4,
validateMvhd, validateTrak, validateTkhd, validateMdia,
validateMdhd, validateHdlr, validateMinf, validateDinf,
validateStbl, validateStsd, validateMvex,
validateVideoSample, validateAudioSample;
module('MP4 Generator');
test('generates a BSMFF ftyp', function() {
var data = mp4.ftyp(), boxes;
ok(data, 'box is not null');
boxes = inspectMp4(data);
equal(1, boxes.length, 'generated a single box');
equal(boxes[0].type, 'ftyp', 'generated ftyp type');
equal(boxes[0].size, data.byteLength, 'generated size');
equal(boxes[0].majorBrand, 'isom', 'major version is "isom"');
equal(boxes[0].minorVersion, 1, 'minor version is one');
});
validateMvhd = function(mvhd) {
equal(mvhd.type, 'mvhd', 'generated a mvhd');
equal(mvhd.duration, 0xffffffff, 'wrote the maximum movie header duration');
equal(mvhd.nextTrackId, 0xffffffff, 'wrote the max next track id');
};
validateTrak = function(trak, expected) {
expected = expected || {};
equal(trak.type, 'trak', 'generated a trak');
equal(trak.boxes.length, 2, 'generated two track sub boxes');
validateTkhd(trak.boxes[0], expected);
validateMdia(trak.boxes[1], expected);
};
validateTkhd = function(tkhd, expected) {
equal(tkhd.type, 'tkhd', 'generated a tkhd');
equal(tkhd.trackId, 7, 'wrote the track id');
deepEqual(tkhd.flags, new Uint8Array([0, 0, 7]), 'flags should equal 7');
equal(tkhd.duration,
expected.duration || Math.pow(2, 32) - 1,
'wrote duration into the track header');
equal(tkhd.width, expected.width || 0, 'wrote width into the track header');
equal(tkhd.height, expected.height || 0, 'wrote height into the track header');
equal(tkhd.volume, 1, 'set volume to 1');
};
validateMdia = function(mdia, expected) {
equal(mdia.type, 'mdia', 'generated an mdia type');
equal(mdia.boxes.length, 3, 'generated three track media sub boxes');
validateMdhd(mdia.boxes[0], expected);
validateHdlr(mdia.boxes[1], expected);
validateMinf(mdia.boxes[2], expected);
};
validateMdhd = function(mdhd, expected) {
equal(mdhd.type, 'mdhd', 'generate an mdhd type');
equal(mdhd.language, 'und', 'wrote undetermined language');
equal(mdhd.timescale, expected.timescale || 90000, 'wrote the timescale');
equal(mdhd.duration,
expected.duration || Math.pow(2, 32) - 1,
'wrote duration into the media header');
};
validateHdlr = function(hdlr, expected) {
equal(hdlr.type, 'hdlr', 'generate an hdlr type');
if (expected.type !== 'audio') {
equal(hdlr.handlerType, 'vide', 'wrote a video handler');
equal(hdlr.name, 'VideoHandler', 'wrote the handler name');
} else {
equal(hdlr.handlerType, 'soun', 'wrote a sound handler');
equal(hdlr.name, 'SoundHandler', 'wrote the sound handler name');
}
};
validateMinf = function(minf, expected) {
equal(minf.type, 'minf', 'generate an minf type');
equal(minf.boxes.length, 3, 'generates three minf sub boxes');
if (expected.type !== 'audio') {
deepEqual({
type: 'vmhd',
size: 20,
version: 0,
flags: new Uint8Array([0, 0, 1]),
graphicsmode: 0,
opcolor: new Uint16Array([0, 0, 0])
}, minf.boxes[0], 'generates a vhmd');
} else {
deepEqual({
type: 'smhd',
size: 16,
version: 0,
flags: new Uint8Array([0, 0, 0]),
balance: 0
}, minf.boxes[0], 'generates an smhd');
}
validateDinf(minf.boxes[1]);
validateStbl(minf.boxes[2], expected);
};
validateDinf = function(dinf) {
deepEqual({
type: 'dinf',
size: 36,
boxes: [{
type: 'dref',
size: 28,
version: 0,
flags: new Uint8Array([0, 0, 0]),
dataReferences: [{
type: 'url ',
size: 12,
version: 0,
flags: new Uint8Array([0, 0, 1])
}]
}]
}, dinf, 'generates a dinf');
};
validateStbl = function(stbl, expected) {
equal(stbl.type, 'stbl', 'generates an stbl type');
equal(stbl.boxes.length, 5, 'generated five stbl child boxes');
validateStsd(stbl.boxes[0], expected);
deepEqual({
type: 'stts',
size: 16,
version: 0,
flags: new Uint8Array([0, 0, 0]),
timeToSamples: []
}, stbl.boxes[1], 'generated an stts');
deepEqual({
type: 'stsc',
size: 16,
version: 0,
flags: new Uint8Array([0, 0, 0]),
sampleToChunks: []
}, stbl.boxes[2], 'generated an stsc');
deepEqual({
type: 'stsz',
version: 0,
size: 20,
flags: new Uint8Array([0, 0, 0]),
sampleSize: 0,
entries: []
}, stbl.boxes[3], 'generated an stsz');
deepEqual({
type: 'stco',
size: 16,
version: 0,
flags: new Uint8Array([0, 0, 0]),
chunkOffsets: []
}, stbl.boxes[4], 'generated and stco');
};
validateStsd = function(stsd, expected) {
equal(stsd.type, 'stsd', 'generated an stsd');
equal(stsd.sampleDescriptions.length, 1, 'generated one sample');
if (expected.type !== 'audio') {
validateVideoSample(stsd.sampleDescriptions[0]);
} else {
validateAudioSample(stsd.sampleDescriptions[0]);
}
};
validateVideoSample = function(sample) {
deepEqual(sample, {
type: 'avc1',
size: 136,
dataReferenceIndex: 1,
width: 600,
height: 300,
horizresolution: 72,
vertresolution: 72,
frameCount: 1,
depth: 24,
config: [{
type: 'avcC',
size: 30,
configurationVersion: 1,
avcProfileIndication: 3,
avcLevelIndication: 5,
profileCompatibility: 7,
lengthSizeMinusOne: 3,
sps: [new Uint8Array([
0, 1, 2
]), new Uint8Array([
3, 4, 5
])],
pps: [new Uint8Array([
6, 7, 8
])]
}, {
type: 'btrt',
size: 20,
bufferSizeDB: 1875072,
maxBitrate: 3000000,
avgBitrate: 3000000
}]
}, 'generated a video sample');
};
validateAudioSample = function(sample) {
deepEqual(sample, {
type: 'mp4a',
size: 75,
dataReferenceIndex: 1,
channelcount: 2,
samplesize: 16,
samplerate: 48000,
streamDescriptor: {
type: 'esds',
version: 0,
flags: new Uint8Array([0, 0, 0]),
size: 39,
esId: 0,
streamPriority: 0,
// these values were hard-coded based on a working audio init segment
decoderConfig: {
avgBitrate: 56000,
maxBitrate: 56000,
bufferSize: 1536,
objectProfileIndication: 64,
streamType: 5
}
}
}, 'generated an audio sample');
};
validateMvex = function(mvex, options) {
options = options || {
sampleDegradationPriority: 1
};
deepEqual({
type: 'mvex',
size: 40,
boxes: [{
type: 'trex',
size: 32,
version: 0,
flags: new Uint8Array([0, 0, 0]),
trackId: 7,
defaultSampleDescriptionIndex: 1,
defaultSampleDuration: 0,
defaultSampleSize: 0,
sampleDependsOn: 0,
sampleIsDependedOn: 0,
sampleHasRedundancy: 0,
samplePaddingValue: 0,
sampleIsDifferenceSample: true,
sampleDegradationPriority: options.sampleDegradationPriority
}]
}, mvex, 'writes a movie extends box');
};
test('generates a video moov', function() {
var
boxes,
data = mp4.moov([{
id: 7,
duration: 100,
width: 600,
height: 300,
type: 'video',
profileIdc: 3,
levelIdc: 5,
profileCompatibility: 7,
sps: [new Uint8Array([0, 1, 2]), new Uint8Array([3, 4, 5])],
pps: [new Uint8Array([6, 7, 8])]
}]);
ok(data, 'box is not null');
boxes = inspectMp4(data);
equal(boxes.length, 1, 'generated a single box');
equal(boxes[0].type, 'moov', 'generated a moov type');
equal(boxes[0].size, data.byteLength, 'generated size');
equal(boxes[0].boxes.length, 3, 'generated three sub boxes');
validateMvhd(boxes[0].boxes[0]);
validateTrak(boxes[0].boxes[1], {
duration: 100,
width: 600,
height: 300
});
validateMvex(boxes[0].boxes[2]);
});
test('generates an audio moov', function() {
var
data = mp4.moov([{
id: 7,
type: 'audio',
channelcount: 2,
samplerate: 48000,
samplesize: 16
}]),
boxes;
ok(data, 'box is not null');
boxes = inspectMp4(data);
equal(boxes.length, 1, 'generated a single box');
equal(boxes[0].type, 'moov', 'generated a moov type');
equal(boxes[0].size, data.byteLength, 'generated size');
equal(boxes[0].boxes.length, 3, 'generated three sub boxes');
validateMvhd(boxes[0].boxes[0]);
validateTrak(boxes[0].boxes[1], {
type: 'audio',
timescale: 48000
});
validateMvex(boxes[0].boxes[2], {
sampleDegradationPriority: 0
});
});
test('generates a sound hdlr', function() {
var boxes, hdlr,
data = mp4.moov([{
duration:100,
type: 'audio'
}]);
ok(data, 'box is not null');
boxes = inspectMp4(data);
hdlr = boxes[0].boxes[1].boxes[1].boxes[1];
equal(hdlr.type, 'hdlr', 'generate an hdlr type');
equal(hdlr.handlerType, 'soun', 'wrote a sound handler');
equal(hdlr.name, 'SoundHandler', 'wrote the handler name');
});
test('generates a video hdlr', function() {
var boxes, hdlr,
data = mp4.moov([{
duration: 100,
width: 600,
height: 300,
type: 'video',
sps: [],
pps: []
}]);
ok(data, 'box is not null');
boxes = inspectMp4(data);
hdlr = boxes[0].boxes[1].boxes[1].boxes[1];
equal(hdlr.type, 'hdlr', 'generate an hdlr type');
equal(hdlr.handlerType, 'vide', 'wrote a video handler');
equal(hdlr.name, 'VideoHandler', 'wrote the handler name');
});
test('generates an initialization segment', function() {
var
data = mp4.initSegment([{
id: 1,
width: 600,
height: 300,
type: 'video',
sps: [new Uint8Array([0])],
pps: [new Uint8Array([1])]
}, {
id: 2,
type: 'audio'
}]),
init, mvhd, trak1, trak2, mvex;
init = videojs.inspectMp4(data);
equal(init.length, 2, 'generated two boxes');
equal(init[0].type, 'ftyp', 'generated a ftyp box');
equal(init[1].type, 'moov', 'generated a moov box');
equal(init[1].boxes[0].duration, 0xffffffff, 'wrote a maximum duration');
mvhd = init[1].boxes[0];
equal(mvhd.type, 'mvhd', 'wrote an mvhd');
trak1 = init[1].boxes[1];
equal(trak1.type, 'trak', 'wrote a trak');
equal(trak1.boxes[0].trackId, 1, 'wrote the first track id');
equal(trak1.boxes[0].width, 600, 'wrote the first track width');
equal(trak1.boxes[0].height, 300, 'wrote the first track height');
equal(trak1.boxes[1].boxes[1].handlerType, 'vide', 'wrote the first track type');
trak2 = init[1].boxes[2];
equal(trak2.type, 'trak', 'wrote a trak');
equal(trak2.boxes[0].trackId, 2, 'wrote the second track id');
equal(trak2.boxes[1].boxes[1].handlerType, 'soun', 'wrote the second track type');
mvex = init[1].boxes[3];
equal(mvex.type, 'mvex', 'wrote an mvex');
});
test('generates a minimal moof', function() {
var
data = mp4.moof(7, [{
id: 17,
samples: [{
duration: 9000,
size: 10,
flags: {
isLeading: 0,
dependsOn: 2,
isDependedOn: 1,
hasRedundancy: 0,
paddingValue: 0,
isNonSyncSample: 0,
degradationPriority: 14
},
compositionTimeOffset: 500
}, {
duration: 10000,
size: 11,
flags: {
isLeading: 0,
dependsOn: 1,
isDependedOn: 0,
hasRedundancy: 0,
paddingValue: 0,
isNonSyncSample: 0,
degradationPriority: 9
},
compositionTimeOffset: 1000
}]
}]),
moof = videojs.inspectMp4(data),
trun,
sdtp;
equal(moof.length, 1, 'generated one box');
equal(moof[0].type, 'moof', 'generated a moof box');
equal(moof[0].boxes.length, 2, 'generated two child boxes');
equal(moof[0].boxes[0].type, 'mfhd', 'generated an mfhd box');
equal(moof[0].boxes[0].sequenceNumber, 7, 'included the sequence_number');
equal(moof[0].boxes[1].type, 'traf', 'generated a traf box');
equal(moof[0].boxes[1].boxes.length, 4, 'generated track fragment info');
equal(moof[0].boxes[1].boxes[0].type, 'tfhd', 'generated a tfhd box');
equal(moof[0].boxes[1].boxes[0].trackId, 17, 'wrote the first track id');
equal(moof[0].boxes[1].boxes[0].baseDataOffset, undefined, 'did not set a base data offset');
equal(moof[0].boxes[1].boxes[1].type, 'tfdt', 'generated a tfdt box');
ok(moof[0].boxes[1].boxes[1].baseMediaDecodeTime >= 0,
'media decode time is non-negative');
trun = moof[0].boxes[1].boxes[2];
equal(trun.type, 'trun', 'generated a trun box');
equal(typeof trun.dataOffset, 'number', 'has a data offset');
ok(trun.dataOffset >= 0, 'has a non-negative data offset');
equal(trun.dataOffset, moof[0].size + 8, 'sets the data offset past the mdat header');
equal(trun.samples.length, 2, 'wrote two samples');
equal(trun.samples[0].duration, 9000, 'wrote a sample duration');
equal(trun.samples[0].size, 10, 'wrote a sample size');
deepEqual(trun.samples[0].flags, {
isLeading: 0,
dependsOn: 2,
isDependedOn: 1,
hasRedundancy: 0,
paddingValue: 0,
isNonSyncSample: 0,
degradationPriority: 14
}, 'wrote the sample flags');
equal(trun.samples[0].compositionTimeOffset, 500, 'wrote the composition time offset');
equal(trun.samples[1].duration, 10000, 'wrote a sample duration');
equal(trun.samples[1].size, 11, 'wrote a sample size');
deepEqual(trun.samples[1].flags, {
isLeading: 0,
dependsOn: 1,
isDependedOn: 0,
hasRedundancy: 0,
paddingValue: 0,
isNonSyncSample: 0,
degradationPriority: 9
}, 'wrote the sample flags');
equal(trun.samples[1].compositionTimeOffset, 1000, 'wrote the composition time offset');
sdtp = moof[0].boxes[1].boxes[3];
equal(sdtp.type, 'sdtp', 'generated an sdtp box');
equal(sdtp.samples.length, 2, 'wrote two samples');
deepEqual(sdtp.samples[0], {
dependsOn: 2,
isDependedOn: 1,
hasRedundancy: 0
}, 'wrote the sample data table');
deepEqual(sdtp.samples[1], {
dependsOn: 1,
isDependedOn: 0,
hasRedundancy: 0
}, 'wrote the sample data table');
});
test('generates a moof for audio', function() {
var
data = mp4.moof(7, [{
id: 17,
type: 'audio',
samples: [{
duration: 9000,
size: 10
}, {
duration: 10000,
size: 11
}]
}]),
moof = videojs.inspectMp4(data),
trun;
deepEqual(moof[0].boxes[1].boxes.length, 3, 'generated three traf children');
trun = moof[0].boxes[1].boxes[2];
ok(trun, 'generated a trun');
equal(trun.dataOffset, data.byteLength + 8, 'calculated the data offset');
deepEqual(trun.samples, [{
duration: 9000,
size: 10
}, {
duration: 10000,
size: 11
}], 'wrote simple audio samples');
});
test('can generate a traf without samples', function() {
var
data = mp4.moof(8, [{
trackId: 13
}]),
moof = videojs.inspectMp4(data);
equal(moof[0].boxes[1].boxes[2].samples.length, 0, 'generated no samples');
});
test('generates an mdat', function() {
var
data = mp4.mdat(new Uint8Array([1, 2, 3, 4])),
mdat = videojs.inspectMp4(data);
equal(mdat.length, 1, 'generated one box');
equal(mdat[0].type, 'mdat', 'generated an mdat box');
deepEqual(mdat[0].byteLength, 4, 'encapsulated the data');
});
})(window, window.videojs);
(function(window, videojs) {
'use strict';
/*
======== A Handy Little QUnit Reference ========
http://api.qunitjs.com/
Test methods:
module(name, {[setup][ ,teardown]})
test(name, callback)
expect(numberOfAssertions)
stop(increment)
start(decrement)
Test assertions:
ok(value, [message])
equal(actual, expected, [message])
notEqual(actual, expected, [message])
deepEqual(actual, expected, [message])
notDeepEqual(actual, expected, [message])
strictEqual(actual, expected, [message])
notStrictEqual(actual, expected, [message])
throws(block, [expected], [message])
*/
var
Uint8Array = window.Uint8Array,
typeBytes = function(type) {
return [
type.charCodeAt(0),
type.charCodeAt(1),
type.charCodeAt(2),
type.charCodeAt(3)
];
},
box = function(type) {
var
array = Array.prototype.slice.call(arguments, 1),
result = [],
size,
i;
// "unwrap" any arrays that were passed as arguments
// e.g. box('etc', 1, [2, 3], 4) -> box('etc', 1, 2, 3, 4)
for (i = 0; i < array.length; i++) {
if (array[i] instanceof Array) {
array.splice.apply(array, [i, 1].concat(array[i]));
}
}
size = 8 + array.length;
result[0] = (size & 0xFF000000) >> 24;
result[1] = (size & 0x00FF0000) >> 16;
result[2] = (size & 0x0000FF00) >> 8;
result[3] = size & 0xFF;
result = result.concat(typeBytes(type));
result = result.concat(array);
return result;
},
unityMatrix = [
0, 0, 0x10, 0,
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0x10, 0,
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
0x40, 0, 0, 0
],
mvhd0 = box('mvhd',
0x00, // version 0
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x01, // creation_time
0x00, 0x00, 0x00, 0x02, // modification_time
0x00, 0x00, 0x00, 0x3c, // timescale
0x00, 0x00, 0x02, 0x58, // 600 = 0x258 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
unityMatrix,
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
0x00, 0x00, 0x00, 0x02),
tkhd0 = box('tkhd',
0x00, // version 0
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x02, // creation_time
0x00, 0x00, 0x00, 0x03, // modification_time
0x00, 0x00, 0x00, 0x01, // track_ID
0x00, 0x00, 0x00, 0x00, // reserved
0x00, 0x00, 0x02, 0x58, // 600 = 0x258 duration
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, // reserved
0x00, 0x00, // layer
0x00, 0x00, // alternate_group
0x00, 0x00, // non-audio track volume
0x00, 0x00, // reserved
unityMatrix,
0x01, 0x2c, 0x00, 0x00, // 300 in 16.16 fixed point
0x00, 0x96, 0x00, 0x00), // 150 in 16.16 fixed point
mdhd0 = box('mdhd',
0x00, // version 0
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x02, // creation_time
0x00, 0x00, 0x00, 0x03, // modification_time
0x00, 0x00, 0x00, 0x3c, // timescale
0x00, 0x00, 0x02, 0x58, // 600 = 0x258 duration
0x15, 0xc7, // 'eng' language
0x00, 0x00);
module('MP4 Inspector');
test('produces an empty array for empty input', function() {
strictEqual(videojs.inspectMp4(new Uint8Array([])).length, 0, 'returned an empty array');
});
test('can parse a Box', function() {
var box = new Uint8Array([
0x00, 0x00, 0x00, 0x00, // size 0
0x00, 0x00, 0x00, 0x00 // boxtype 0
]);
deepEqual(videojs.inspectMp4(box), [{
type: '\u0000\u0000\u0000\u0000',
size: 0,
data: box.subarray(box.byteLength)
}], 'parsed a Box');
});
test('can parse an ftyp', function() {
deepEqual(videojs.inspectMp4(new Uint8Array(box('ftyp',
0x61, 0x76, 0x63, 0x31, // major brand
0x00, 0x00, 0x00, 0x02, // minor version
98, 111, 111, 112, // compatible brands
98, 101, 101, 112 // compatible brands
))), [{
type: 'ftyp',
size: 4 * 6,
majorBrand: 'avc1',
minorVersion: 2,
compatibleBrands: ['boop', 'beep']
}], 'parsed an ftyp');
});
test('can parse a pdin', function() {
deepEqual(videojs.inspectMp4(new Uint8Array(box('pdin',
0x01, // version 1
0x01, 0x02, 0x03, // flags
0x00, 0x00, 0x04, 0x00, // 1024 = 0x400 bytes/second rate
0x00, 0x00, 0x00, 0x01 // initial delay
))), [{
size: 20,
type: 'pdin',
version: 1,
flags: new Uint8Array([1, 2, 3]),
rate: 1024,
initialDelay: 1
}], 'parsed a pdin');
});
test('can parse an mdat', function() {
var mdat = new Uint8Array(box('mdat',
0, 0, 0, 4, // length
0x01, 0x02, 0x03, 0x04 // data
));
deepEqual(videojs.inspectMp4(mdat), [{
size: 16,
type: 'mdat',
nals: [
'slice_layer_without_partitioning_rbsp'
],
byteLength: 8
}], 'parsed an mdat');
});
test('can parse a free or skip', function() {
var
free = new Uint8Array(box('free',
0x01, 0x02, 0x03, 0x04)), // data
skip = new Uint8Array(box('skip',
0x01, 0x02, 0x03, 0x04)); // data
deepEqual(videojs.inspectMp4(free), [{
size: 12,
type: 'free',
data: free.subarray(free.byteLength - 4)
}], 'parsed a free');
deepEqual(videojs.inspectMp4(skip), [{
size: 12,
type: 'skip',
data: skip.subarray(skip.byteLength - 4)
}], 'parsed a skip');
});
test('can parse a version 0 mvhd', function() {
deepEqual(videojs.inspectMp4(new Uint8Array(mvhd0)), [{
type: 'mvhd',
version: 0,
flags: new Uint8Array([0, 0, 0]),
creationTime: new Date(1000 - 2082844800000),
modificationTime: new Date(2000 - 2082844800000),
timescale: 60,
duration: 600,
rate: 1,
volume: 1,
matrix: new Uint32Array(unityMatrix),
size: 108,
nextTrackId: 2
}]);
});
test('can parse a version 0 tkhd', function() {
deepEqual(videojs.inspectMp4(new Uint8Array(tkhd0)), [{
type: 'tkhd',
version: 0,
flags: new Uint8Array([0, 0, 0]),
creationTime: new Date(2000 - 2082844800000),
modificationTime: new Date(3000 - 2082844800000),
size: 92,
trackId: 1,
duration: 600,
layer: 0,
alternateGroup: 0,
volume: 0,
matrix: new Uint32Array(unityMatrix),
width: 300,
height: 150
}]);
});
test('can parse a version 0 mdhd', function() {
deepEqual(videojs.inspectMp4(new Uint8Array(mdhd0)), [{
type: 'mdhd',
version: 0,
flags: new Uint8Array([0, 0, 0]),
creationTime: new Date(2000 - 2082844800000),
modificationTime: new Date(3000 - 2082844800000),
size: 32,
timescale: 60,
duration: 600,
language: 'eng'
}]);
});
test('can parse a moov', function() {
var data =
box('moov',
box('mvhd',
0x01, // version 1
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01, // creation_time
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x02, // modification_time
0x00, 0x00, 0x00, 0x3c, // timescale
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x02, 0x58, // 600 = 0x258 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
unityMatrix,
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
0x00, 0x00, 0x00, 0x02), // next_track_ID
box('trak',
box('tkhd',
0x01, // version 1
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x02, // creation_time
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x03, // modification_time
0x00, 0x00, 0x00, 0x01, // track_ID
0x00, 0x00, 0x00, 0x00, // reserved
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x02, 0x58, // 600 = 0x258 duration
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, // reserved
0x00, 0x00, // layer
0x00, 0x00, // alternate_group
0x00, 0x00, // non-audio track volume
0x00, 0x00, // reserved
unityMatrix,
0x01, 0x2c, 0x00, 0x00, // 300 in 16.16 fixed-point
0x00, 0x96, 0x00, 0x00), // 150 in 16.16 fixed-point
box('mdia',
box('mdhd',
0x01, // version 1
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x02, // creation_time
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x03, // modification_time
0x00, 0x00, 0x00, 0x3c, // timescale
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x02, 0x58, // 600 = 0x258 duration
0x15, 0xc7, // 'eng' language
0x00, 0x00),
box('hdlr',
0x01, // version 1
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x00, // pre_defined
typeBytes('vide'), // handler_type
0x00, 0x00, 0x00, 0x00, // reserved
0x00, 0x00, 0x00, 0x00, // reserved
0x00, 0x00, 0x00, 0x00, // reserved
typeBytes('one'), 0x00), // name
box('minf',
box('dinf',
box('dref',
0x01, // version 1
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x01, // entry_count
box('url ',
0x00, // version
0x00, 0x00, 0x01))), // flags
box('stbl',
box('stsd',
0x01, // version 1
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x00), // entry_count
box('stts',
0x01, // version 1
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x01, // entry_count
0x00, 0x00, 0x00, 0x01, // sample_count
0x00, 0x00, 0x00, 0x01), // sample_delta
box('stsc',
0x01, // version 1
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x01, // entry_count
0x00, 0x00, 0x00, 0x02, // first_chunk
0x00, 0x00, 0x00, 0x03, // samples_per_chunk
0x00, 0x00, 0x00, 0x01), // sample_description_index
box('stco',
0x01, // version 1
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x01, // entry_count
0x00, 0x00, 0x00, 0x01)))))); // chunk_offset
deepEqual(videojs.inspectMp4(new Uint8Array(data)), [{
type: 'moov',
size: 469,
boxes: [{
type: 'mvhd',
version: 1,
flags: new Uint8Array([0, 0, 0]),
creationTime: new Date(1000 - 2082844800000),
modificationTime: new Date(2000 - 2082844800000),
timescale: 60,
duration: 600,
rate: 1,
size: 120,
volume: 1,
matrix: new Uint32Array(unityMatrix),
nextTrackId: 2
}, {
type: 'trak',
size: 341,
boxes: [{
type: 'tkhd',
flags: new Uint8Array([0, 0, 0]),
version: 1,
creationTime: new Date(2000 - 2082844800000),
modificationTime: new Date(3000 - 2082844800000),
size: 104,
trackId: 1,
duration: 600,
layer: 0,
alternateGroup: 0,
volume: 0,
matrix: new Uint32Array(unityMatrix),
width: 300,
height: 150
}, {
type: 'mdia',
size: 229,
boxes: [{
type: 'mdhd',
version: 1,
flags: new Uint8Array([0, 0, 0]),
creationTime: new Date(2000 - 2082844800000),
modificationTime: new Date(3000 - 2082844800000),
timescale: 60,
duration: 600,
language: 'eng',
size: 44
}, {
type: 'hdlr',
version: 1,
flags: new Uint8Array([0, 0, 0]),
handlerType: 'vide',
name: 'one',
size: 37
}, {
type: 'minf',
size: 140,
boxes: [{
type: 'dinf',
size: 36,
boxes: [{
type: 'dref',
size: 28,
version: 1,
flags: new Uint8Array([0, 0, 0]),
dataReferences: [{
type: 'url ',
size: 12,
version: 0,
flags: new Uint8Array([0, 0, 1])
}],
}]
}, {
type: 'stbl',
size: 96,
boxes: [{
type: 'stsd',
size: 16,
version: 1,
flags: new Uint8Array([0, 0, 0]),
sampleDescriptions: [],
}, {
type: 'stts',
size: 24,
version: 1,
flags: new Uint8Array([0, 0, 0]),
timeToSamples: [{
sampleCount: 1,
sampleDelta: 1
}]
}, {
type: 'stsc',
version: 1,
flags: new Uint8Array([0, 0, 0]),
sampleToChunks: [{
firstChunk: 2,
samplesPerChunk: 3,
sampleDescriptionIndex: 1
}],
size: 28
}, {
type: 'stco',
size: 20,
version: 1,
flags: new Uint8Array([0, 0, 0]),
chunkOffsets: [1]
}]
}]
}]
}]
}]
}], 'parsed a moov');
});
test('can parse an mvex', function() {
var mvex =
box('mvex',
box('trex',
0x00, // version
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x01, // track_ID
0x00, 0x00, 0x00, 0x01, // default_sample_description_index
0x00, 0x00, 0x00, 0x02, // default_sample_duration
0x00, 0x00, 0x00, 0x03, // default_sample_size
0x00, 0x61, 0x00, 0x01)); // default_sample_flags
deepEqual(videojs.inspectMp4(new Uint8Array(mvex)), [{
type: 'mvex',
size: 40,
boxes: [{
type: 'trex',
size: 32,
version: 0,
flags: new Uint8Array([0, 0, 0]),
trackId: 1,
defaultSampleDescriptionIndex: 1,
defaultSampleDuration: 2,
defaultSampleSize: 3,
sampleDependsOn: 0,
sampleIsDependedOn: 1,
sampleHasRedundancy: 2,
samplePaddingValue: 0,
sampleIsDifferenceSample: true,
sampleDegradationPriority: 1
}]
}], 'parsed an mvex');
});
test('can parse a video stsd', function() {
var data = box('stsd',
0x00, // version 0
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x01,
box('avc1',
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
0x01, 0x2c, // width = 300
0x00, 0x96, // height = 150
0x00, 0x48, 0x00, 0x00, // horizresolution
0x00, 0x48, 0x00, 0x00, // vertresolution
0x00, 0x00, 0x00, 0x00, // reserved
0x00, 0x01, // frame_count
0x04,
typeBytes('avc1'),
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, // compressorname
0x00, 0x18, // depth = 24
0x11, 0x11, // pre_defined
box('avcC',
0x01, // configurationVersion
0x00, // AVCProfileIndication??
0x00, // profile_compatibility
0x00, // AVCLevelIndication
0x1c, // lengthSizeMinusOne
0xe1, // numOfSequenceParameterSets
0x00, 0x01, // sequenceParameterSetLength
0x00, // "SPS"
0x02, // numOfPictureParameterSets
0x00, 0x02, // pictureParameterSetLength
0x01, 0x02, // "PPS"
0x00, 0x01, // pictureParameterSetLength
0xff), // "PPS"
box('btrt',
0x00, 0x00, 0x00, 0x00, // bufferSizeDB
0x00, 0x00, 0x00, 0x01, // maxBitrate
0x00, 0x00, 0x00, 0x01))); // avgBitrate
deepEqual(videojs.inspectMp4(new Uint8Array(data)), [{
type: 'stsd',
size: 147,
version: 0,
flags: new Uint8Array([0, 0, 0]),
sampleDescriptions: [{
type: 'avc1',
size: 131,
dataReferenceIndex: 1,
width: 300,
height: 150,
horizresolution: 72,
vertresolution: 72,
frameCount: 1,
depth: 24,
config: [{
type: 'avcC',
size: 25,
configurationVersion: 1,
avcProfileIndication: 0,
profileCompatibility: 0,
avcLevelIndication: 0,
lengthSizeMinusOne: 0,
sps: [new Uint8Array(1)],
pps: [new Uint8Array([1, 2]),
new Uint8Array([0xff])]
}, {
type: 'btrt',
size: 20,
bufferSizeDB: 0,
maxBitrate: 1,
avgBitrate: 1
}]
}]
}]);
});
test('can parse an audio stsd', function() {
var data = box('stsd',
0x00, // version 0
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x01, // entry_count
box('mp4a',
0x00, 0x00, 0x00,
0x00, 0x00, 0x00, // reserved
0x00, 0x01, // data_reference_index
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, // reserved
0x00, 0x02, // channelcount
0x00, 0x10, // samplesize
0x00, 0x00, // pre_defined
0x00, 0x00, // reserved
0xbb, 0x80, 0x00, 0x00, // samplerate, fixed-point 16.16
box('esds',
0x00, // version 0
0x00, 0x00, 0x00, // flags
0x03, // tag, ES_DescrTag
0x00, // length
0x00, 0x01, // ES_ID
0x00, // streamDependenceFlag, URL_Flag, reserved, streamPriority
// DecoderConfigDescriptor
0x04, // tag, DecoderConfigDescrTag
0x0d, // length
0x40, // objectProfileIndication, AAC Main
0x15, // streamType, AudioStream. upstream, reserved
0x00, 0x00, 0xff, // bufferSizeDB
0x00, 0x00, 0x00, 0xff, // maxBitrate
0x00, 0x00, 0x00, 0xaa, // avgBitrate
// DecoderSpecificInfo
0x05, // tag, DecoderSpecificInfoTag
0x02, // length
0x11, 0x90, 0x06, 0x01, 0x02))); // decoder specific info
deepEqual(videojs.inspectMp4(new Uint8Array(data)), [{
version: 0,
flags: new Uint8Array([0, 0, 0]),
type: 'stsd',
size: 91,
sampleDescriptions: [{
type: 'mp4a',
dataReferenceIndex: 1,
channelcount: 2,
samplesize: 16,
samplerate: 48000,
size: 75,
streamDescriptor: {
type: 'esds',
version: 0,
size: 39,
flags: new Uint8Array([0, 0, 0]),
esId: 1,
streamPriority: 0,
decoderConfig: {
objectProfileIndication: 0x40,
streamType: 0x05,
bufferSize: 0xff,
maxBitrate: 0xff,
avgBitrate: 0xaa
}
}
}]
}], 'parsed an audio stsd');
});
test('can parse an styp', function() {
deepEqual(videojs.inspectMp4(new Uint8Array(box('styp',
0x61, 0x76, 0x63, 0x31, // major brand
0x00, 0x00, 0x00, 0x02, // minor version
98, 111, 111, 112 // compatible brands
))), [{
type: 'styp',
size: 4 * 5,
majorBrand: 'avc1',
minorVersion: 2,
compatibleBrands: ['boop']
}], 'parsed an styp');
});
test('can parse a vmhd', function() {
var data = box('vmhd',
0x00, // version
0x00, 0x00, 0x00, // flags
0x00, 0x00, // graphicsmode
0x00, 0x00,
0x00, 0x00,
0x00, 0x00); // opcolor
deepEqual(videojs.inspectMp4(new Uint8Array(data)),
[{
type: 'vmhd',
size: 20,
version: 0,
flags: new Uint8Array([0, 0, 0]),
graphicsmode: 0,
opcolor: new Uint16Array([0, 0, 0])
}]);
});
test('can parse an stsz', function() {
var data = box('stsz',
0x00, // version
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x00, // sample_size
0x00, 0x00, 0x00, 0x03, // sample_count
0x00, 0x00, 0x00, 0x01, // entry_size
0x00, 0x00, 0x00, 0x02, // entry_size
0x00, 0x00, 0x00, 0x03); // entry_size
deepEqual(videojs.inspectMp4(new Uint8Array(data)),
[{
type: 'stsz',
size: 32,
version: 0,
flags: new Uint8Array([0, 0, 0]),
sampleSize: 0,
entries: [1, 2, 3]
}]);
});
test('can parse a moof', function() {
var data = box('moof',
box('mfhd',
0x00, // version
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x04), // sequence_number
box('traf',
box('tfhd',
0x00, // version
0x00, 0x00, 0x3b, // flags
0x00, 0x00, 0x00, 0x01, // track_ID
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01, // base_data_offset
0x00, 0x00, 0x00, 0x02, // sample_description_index
0x00, 0x00, 0x00, 0x03, // default_sample_duration,
0x00, 0x00, 0x00, 0x04, // default_sample_size
0x00, 0x00, 0x00, 0x05))); // default_sample_flags
deepEqual(videojs.inspectMp4(new Uint8Array(data)),
[{
type: 'moof',
size: 72,
boxes: [{
type: 'mfhd',
size: 16,
version: 0,
flags: new Uint8Array([0, 0, 0]),
sequenceNumber: 4
},
{
type: 'traf',
size: 48,
boxes: [{
type: 'tfhd',
version: 0,
size: 40,
flags: new Uint8Array([0x00, 0, 0x3b]),
trackId: 1,
baseDataOffset: 1,
sampleDescriptionIndex: 2,
defaultSampleDuration: 3,
defaultSampleSize: 4,
defaultSampleFlags: 5
}]
}]
}]);
});
test('can parse a trun', function() {
var data = box('trun',
0x00, // version
0x00, 0x0b, 0x05, // flags
0x00, 0x00, 0x00, 0x02, // sample_count
0x00, 0x00, 0x00, 0x01, // data_offset
// first_sample_flags
// r:0000 il:10 sdo:01 sido:10 shr:01 spv:111 snss:1
// dp:1111 1110 1101 1100
0x09, 0x9f, 0xfe, 0xdc,
0x00, 0x00, 0x00, 0x09, // sample_duration
0x00, 0x00, 0x00, 0xff, // sample_size
0x00, 0x00, 0x00, 0x00, // sample_composition_time_offset
0x00, 0x00, 0x00, 0x08, // sample_duration
0x00, 0x00, 0x00, 0xfe, // sample_size
0x00, 0x00, 0x00, 0x00); // sample_composition_time_offset
deepEqual(videojs.inspectMp4(new Uint8Array(data)),
[{
type: 'trun',
version: 0,
size: 48,
flags: new Uint8Array([0, 0x0b, 0x05]),
dataOffset: 1,
samples: [{
duration: 9,
size: 0xff,
flags: {
isLeading: 2,
dependsOn: 1,
isDependedOn: 2,
hasRedundancy: 1,
paddingValue: 7,
isNonSyncSample: 1,
degradationPriority: 0xfedc,
},
compositionTimeOffset: 0
}, {
duration: 8,
size: 0xfe,
compositionTimeOffset: 0
}]
}]);
});
test('can parse a trun with per-sample flags', function() {
var data = box('trun',
0x00, // version
0x00, 0x0f, 0x00, // flags
0x00, 0x00, 0x00, 0x01, // sample_count
0x00, 0x00, 0x00, 0x09, // sample_duration
0x00, 0x00, 0x00, 0xff, // sample_size
// sample_flags
// r:0000 il:00 sdo:01, sido:11 shr:00 spv:010 snss:0
// dp: 0001 0010 0011 0100
0x01, 0xc4, 0x12, 0x34,
0x00, 0x00, 0x00, 0x00); // sample_composition_time_offset
deepEqual(videojs.inspectMp4(new Uint8Array(data)),
[{
type: 'trun',
version: 0,
size: 32,
flags: new Uint8Array([0, 0x0f, 0x00]),
samples: [{
duration: 9,
size: 0xff,
flags: {
isLeading: 0,
dependsOn: 1,
isDependedOn: 3,
hasRedundancy: 0,
paddingValue: 2,
isNonSyncSample: 0,
degradationPriority: 0x1234
},
compositionTimeOffset: 0
}]
}]);
});
test('can parse an sdtp', function() {
var data = box('sdtp',
0x00, // version
0x00, 0x00, 0x00, // flags
// reserved + sample_depends_on +
// sample_is_dependend_on + sample_has_redundancy
0x15,
// reserved + sample_depends_on +
// sample_is_dependend_on + sample_has_redundancy
0x27);
deepEqual(videojs.inspectMp4(new Uint8Array(data)), [{
type: 'sdtp',
version: 0,
flags: new Uint8Array([0, 0, 0]),
size: 14,
samples: [{
dependsOn: 1,
isDependedOn: 1,
hasRedundancy: 1
}, {
dependsOn: 2,
isDependedOn: 1,
hasRedundancy: 3
}]
}]);
});
test('can parse a sidx', function(){
var data = box('sidx',
0x00, // version
0x00, 0x00, 0x00, // flags
0x00, 0x00, 0x00, 0x02, // reference_ID
0x00, 0x00, 0x00, 0x01, // timescale
0x01, 0x02, 0x03, 0x04, // earliest_presentation_time
0x00, 0x00, 0x00, 0x00, // first_offset
0x00, 0x00, // reserved
0x00, 0x02, // reference_count
// first reference
0x80, 0x00, 0x00, 0x07, // reference_type(1) + referenced_size(31)
0x00, 0x00, 0x00, 0x08, // subsegment_duration
0x80, 0x00, 0x00, 0x09, // starts_with_SAP(1) + SAP_type(3) + SAP_delta_time(28)
// second reference
0x00, 0x00, 0x00, 0x03, // reference_type(1) + referenced_size(31)
0x00, 0x00, 0x00, 0x04, // subsegment_duration
0x10, 0x00, 0x00, 0x05 // starts_with_SAP(1) + SAP_type(3) + SAP_delta_time(28)
);
deepEqual(videojs.inspectMp4(new Uint8Array(data)),
[{
type: 'sidx',
version: 0,
flags: new Uint8Array([0, 0x00, 0x00]),
timescale: 1,
earliestPresentationTime: 0x01020304,
firstOffset: 0,
referenceId: 2,
size: 56,
references: [{
referenceType: 1,
referencedSize: 7,
subsegmentDuration: 8,
startsWithSap: true,
sapType: 0,
sapDeltaTime: 9
},{
referenceType: 0,
referencedSize: 3,
subsegmentDuration: 4,
startsWithSap: false,
sapType: 1,
sapDeltaTime: 5
}]
}]);
});
test('can parse an smhd', function() {
var data = box('smhd',
0x00, // version
0x00, 0x00, 0x00, // flags
0x00, 0xff, // balance, fixed-point 8.8
0x00, 0x00); // reserved
deepEqual(videojs.inspectMp4(new Uint8Array(data)),
[{
type: 'smhd',
size: 16,
version: 0,
flags: new Uint8Array([0, 0, 0]),
balance: 0xff / Math.pow(2, 8)
}],
'parsed an smhd');
});
test('can parse a tfdt', function() {
var data = box('tfdt',
0x00, // version
0x00, 0x00, 0x00, // flags
0x01, 0x02, 0x03, 0x04); // baseMediaDecodeTime
deepEqual(videojs.inspectMp4(new Uint8Array(data)),
[{
type: 'tfdt',
version: 0,
size: 16,
flags: new Uint8Array([0, 0, 0]),
baseMediaDecodeTime: 0x01020304
}]);
});
test('can parse a series of boxes', function() {
var ftyp = [
0x00, 0x00, 0x00, 0x18 // size 4 * 6 = 24
].concat(typeBytes('ftyp')).concat([
0x69, 0x73, 0x6f, 0x6d, // major brand
0x00, 0x00, 0x00, 0x02, // minor version
98, 101, 101, 112, // compatible brands
98, 111, 111, 112, // compatible brands
]);
deepEqual(videojs.inspectMp4(new Uint8Array(ftyp.concat(ftyp))),
[{
type: 'ftyp',
size: 4 * 6,
majorBrand: 'isom',
minorVersion: 2,
compatibleBrands: ['beep', 'boop']
},{
type: 'ftyp',
size: 4 * 6,
majorBrand: 'isom',
minorVersion: 2,
compatibleBrands: ['beep', 'boop']
}],
'parsed two boxes in series');
});
})(window, window.videojs);
/* ==========================================================================
HTML5 Boilerplate styles - h5bp.com (generated via initializr.com)
========================================================================== */
html,
button,
input,
select,
textarea {
color: #222;
}
body {
font-size: 1em;
line-height: 1.4;
}
::-moz-selection {
background: #b3d4fc;
text-shadow: none;
}
::selection {
background: #b3d4fc;
text-shadow: none;
}
hr {
display: block;
height: 1px;
border: 0;
border-top: 1px solid #ccc;
margin: 1em 0;
padding: 0;
}
img {
vertical-align: middle;
}
fieldset {
border: 0;
margin: 0;
padding: 0;
}
textarea {
resize: vertical;
}
.chromeframe {
margin: 0.2em 0;
background: #ccc;
color: #000;
padding: 0.2em 0;
}
/* ===== Initializr Styles ==================================================
Author: Jonathan Verrecchia - verekia.com/initializr/responsive-template
========================================================================== */
body {
font: 16px/26px Helvetica, Helvetica Neue, Arial;
}
.wrapper {
width: 90%;
margin: 0 5%;
}
/* ===================
ALL: Orange Theme
=================== */
.header-container {
border-bottom: 20px solid #e44d26;
}
.footer-container,
.main aside {
border-top: 20px solid #e44d26;
}
.header-container,
.footer-container,
.main aside {
background: #f16529;
}
.title {
color: white;
}
/* ==============
MOBILE: Menu
============== */
nav ul {
margin: 0;
padding: 0;
}
nav a {
display: block;
margin-bottom: 10px;
padding: 15px 0;
text-align: center;
text-decoration: none;
font-weight: bold;
color: white;
background: #e44d26;
}
nav a:hover,
nav a:visited {
color: white;
}
nav a:hover {
text-decoration: underline;
}
/* ==============
MOBILE: Main
============== */
.main {
padding: 30px 0;
}
.main article h1 {
font-size: 2em;
}
.main aside {
color: white;
padding: 0px 5% 10px;
}
.footer-container footer {
color: white;
padding: 20px 0;
}
/* ===============
ALL: IE Fixes
=============== */
.ie7 .title {
padding-top: 20px;
}
/* ==========================================================================
Author's custom styles
========================================================================== */
section {
clear: both;
}
form label {
display: block;
}
.result-wrapper {
float: left;
margin: 10px 0;
min-width: 422px;
width: 50%;
}
.result {
border: thin solid #aaa;
border-radius: 5px;
font-size: 10px;
line-height: 15px;
margin: 0 5px;
padding: 0 10px;
}
/* ==========================================================================
Media Queries
========================================================================== */
@media only screen and (min-width: 480px) {
/* ====================
INTERMEDIATE: Menu
==================== */
nav a {
float: left;
width: 27%;
margin: 0 1.7%;
padding: 25px 2%;
margin-bottom: 0;
}
nav li:first-child a {
margin-left: 0;
}
nav li:last-child a {
margin-right: 0;
}
/* ========================
INTERMEDIATE: IE Fixes
======================== */
nav ul li {
display: inline;
}
.oldie nav a {
margin: 0 0.7%;
}
}
@media only screen and (min-width: 768px) {
/* ====================
WIDE: CSS3 Effects
==================== */
.header-container,
.main aside {
-webkit-box-shadow: 0 5px 10px #aaa;
-moz-box-shadow: 0 5px 10px #aaa;
box-shadow: 0 5px 10px #aaa;
}
/* ============
WIDE: Menu
============ */
.title {
float: left;
}
nav {
float: right;
width: 38%;
}
/* ============
WIDE: Main
============ */
.main article {
float: left;
width: 100%;
}
}
@media only screen and (min-width: 1140px) {
/* ===============
Maximal Width
=============== */
.wrapper {
width: 1026px; /* 1140px - 10% for margins */
margin: 0 auto;
}
}
/* ==========================================================================
Helper classes
========================================================================== */
.ir {
background-color: transparent;
border: 0;
overflow: hidden;
*text-indent: -9999px;
}
.ir:before {
content: "";
display: block;
width: 0;
height: 150%;
}
.hidden {
display: none !important;
visibility: hidden;
}
.visuallyhidden {
border: 0;
clip: rect(0 0 0 0);
height: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
width: 1px;
}
.visuallyhidden.focusable:active,
.visuallyhidden.focusable:focus {
clip: auto;
height: auto;
margin: 0;
overflow: visible;
position: static;
width: auto;
}
.invisible {
visibility: hidden;
}
.clearfix:before,
.clearfix:after {
content: " ";
display: table;
}
.clearfix:after {
clear: both;
}
.clearfix {
*zoom: 1;
}
/* ==========================================================================
Print styles
========================================================================== */
@media print {
* {
background: transparent !important;
color: #000 !important; /* Black prints faster: h5bp.com/s */
box-shadow: none !important;
text-shadow: none !important;
}
a,
a:visited {
text-decoration: underline;
}
a[href]:after {
content: " (" attr(href) ")";
}
abbr[title]:after {
content: " (" attr(title) ")";
}
/*
* Don't show links for images, or javascript/internal links
*/
.ir a:after,
a[href^="javascript:"]:after,
a[href^="#"]:after {
content: "";
}
pre,
blockquote {
border: 1px solid #999;
page-break-inside: avoid;
}
thead {
display: table-header-group; /* h5bp.com/t */
}
tr,
img {
page-break-inside: avoid;
}
img {
max-width: 100% !important;
}
@page {
margin: 0.5cm;
}
p,
h2,
h3 {
orphans: 3;
widows: 3;
}
h2,
h3 {
page-break-after: avoid;
}
}
/**
* Diff styling
*/
#comparison p {
white-space: normal;
}
#comparison pre {
font-size: 90%;
}
ins, del {
text-decoration: none;
}
ins {
background-color: #C6E746;
}
del {
background-color: #EE5757
}
\ No newline at end of file
/*! normalize.css v1.1.2 | MIT License | git.io/normalize */
/* ==========================================================================
HTML5 display definitions
========================================================================== */
/**
* Correct `block` display not defined in IE 6/7/8/9 and Firefox 3.
*/
article,
aside,
details,
figcaption,
figure,
footer,
header,
hgroup,
main,
nav,
section,
summary {
display: block;
}
/**
* Correct `inline-block` display not defined in IE 6/7/8/9 and Firefox 3.
*/
audio,
canvas,
video {
display: inline-block;
*display: inline;
*zoom: 1;
}
/**
* Prevent modern browsers from displaying `audio` without controls.
* Remove excess height in iOS 5 devices.
*/
audio:not([controls]) {
display: none;
height: 0;
}
/**
* Address styling not present in IE 7/8/9, Firefox 3, and Safari 4.
* Known issue: no IE 6 support.
*/
[hidden] {
display: none;
}
/* ==========================================================================
Base
========================================================================== */
/**
* 1. Correct text resizing oddly in IE 6/7 when body `font-size` is set using
* `em` units.
* 2. Prevent iOS text size adjust after orientation change, without disabling
* user zoom.
*/
html {
font-size: 100%; /* 1 */
-ms-text-size-adjust: 100%; /* 2 */
-webkit-text-size-adjust: 100%; /* 2 */
}
/**
* Address `font-family` inconsistency between `textarea` and other form
* elements.
*/
html,
button,
input,
select,
textarea {
font-family: sans-serif;
}
/**
* Address margins handled incorrectly in IE 6/7.
*/
body {
margin: 0;
}
/* ==========================================================================
Links
========================================================================== */
/**
* Address `outline` inconsistency between Chrome and other browsers.
*/
a:focus {
outline: thin dotted;
}
/**
* Improve readability when focused and also mouse hovered in all browsers.
*/
a:active,
a:hover {
outline: 0;
}
/* ==========================================================================
Typography
========================================================================== */
/**
* Address font sizes and margins set differently in IE 6/7.
* Address font sizes within `section` and `article` in Firefox 4+, Safari 5,
* and Chrome.
*/
h1 {
font-size: 2em;
margin: 0.67em 0;
}
h2 {
font-size: 1.5em;
margin: 0.83em 0;
}
h3 {
font-size: 1.17em;
margin: 1em 0;
}
h4 {
font-size: 1em;
margin: 1.33em 0;
}
h5 {
font-size: 0.83em;
margin: 1.67em 0;
}
h6 {
font-size: 0.67em;
margin: 2.33em 0;
}
/**
* Address styling not present in IE 7/8/9, Safari 5, and Chrome.
*/
abbr[title] {
border-bottom: 1px dotted;
}
/**
* Address style set to `bolder` in Firefox 3+, Safari 4/5, and Chrome.
*/
b,
strong {
font-weight: bold;
}
blockquote {
margin: 1em 40px;
}
/**
* Address styling not present in Safari 5 and Chrome.
*/
dfn {
font-style: italic;
}
/**
* Address differences between Firefox and other browsers.
* Known issue: no IE 6/7 normalization.
*/
hr {
-moz-box-sizing: content-box;
box-sizing: content-box;
height: 0;
}
/**
* Address styling not present in IE 6/7/8/9.
*/
mark {
background: #ff0;
color: #000;
}
/**
* Address margins set differently in IE 6/7.
*/
p,
pre {
margin: 1em 0;
}
/**
* Correct font family set oddly in IE 6, Safari 4/5, and Chrome.
*/
code,
kbd,
pre,
samp {
font-family: monospace, serif;
_font-family: 'courier new', monospace;
font-size: 1em;
}
/**
* Improve readability of pre-formatted text in all browsers.
*/
pre {
white-space: pre;
white-space: pre-wrap;
word-wrap: break-word;
}
/**
* Address CSS quotes not supported in IE 6/7.
*/
q {
quotes: none;
}
/**
* Address `quotes` property not supported in Safari 4.
*/
q:before,
q:after {
content: '';
content: none;
}
/**
* Address inconsistent and variable font size in all browsers.
*/
small {
font-size: 80%;
}
/**
* Prevent `sub` and `sup` affecting `line-height` in all browsers.
*/
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sup {
top: -0.5em;
}
sub {
bottom: -0.25em;
}
/* ==========================================================================
Lists
========================================================================== */
/**
* Address margins set differently in IE 6/7.
*/
dl,
menu,
ol,
ul {
margin: 1em 0;
}
dd {
margin: 0 0 0 40px;
}
/**
* Address paddings set differently in IE 6/7.
*/
menu,
ol,
ul {
padding: 0 0 0 40px;
}
/**
* Correct list images handled incorrectly in IE 7.
*/
nav ul,
nav ol {
list-style: none;
list-style-image: none;
}
/* ==========================================================================
Embedded content
========================================================================== */
/**
* 1. Remove border when inside `a` element in IE 6/7/8/9 and Firefox 3.
* 2. Improve image quality when scaled in IE 7.
*/
img {
border: 0; /* 1 */
-ms-interpolation-mode: bicubic; /* 2 */
}
/**
* Correct overflow displayed oddly in IE 9.
*/
svg:not(:root) {
overflow: hidden;
}
/* ==========================================================================
Figures
========================================================================== */
/**
* Address margin not present in IE 6/7/8/9, Safari 5, and Opera 11.
*/
figure {
margin: 0;
}
/* ==========================================================================
Forms
========================================================================== */
/**
* Correct margin displayed oddly in IE 6/7.
*/
form {
margin: 0;
}
/**
* Define consistent border, margin, and padding.
*/
fieldset {
border: 1px solid #c0c0c0;
margin: 0 2px;
padding: 0.35em 0.625em 0.75em;
}
/**
* 1. Correct color not being inherited in IE 6/7/8/9.
* 2. Correct text not wrapping in Firefox 3.
* 3. Correct alignment displayed oddly in IE 6/7.
*/
legend {
border: 0; /* 1 */
padding: 0;
white-space: normal; /* 2 */
*margin-left: -7px; /* 3 */
}
/**
* 1. Correct font size not being inherited in all browsers.
* 2. Address margins set differently in IE 6/7, Firefox 3+, Safari 5,
* and Chrome.
* 3. Improve appearance and consistency in all browsers.
*/
button,
input,
select,
textarea {
font-size: 100%; /* 1 */
margin: 0; /* 2 */
vertical-align: baseline; /* 3 */
*vertical-align: middle; /* 3 */
}
/**
* Address Firefox 3+ setting `line-height` on `input` using `!important` in
* the UA stylesheet.
*/
button,
input {
line-height: normal;
}
/**
* Address inconsistent `text-transform` inheritance for `button` and `select`.
* All other form control elements do not inherit `text-transform` values.
* Correct `button` style inheritance in Chrome, Safari 5+, and IE 6+.
* Correct `select` style inheritance in Firefox 4+ and Opera.
*/
button,
select {
text-transform: none;
}
/**
* 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
* and `video` controls.
* 2. Correct inability to style clickable `input` types in iOS.
* 3. Improve usability and consistency of cursor style between image-type
* `input` and others.
* 4. Remove inner spacing in IE 7 without affecting normal text inputs.
* Known issue: inner spacing remains in IE 6.
*/
button,
html input[type="button"], /* 1 */
input[type="reset"],
input[type="submit"] {
-webkit-appearance: button; /* 2 */
cursor: pointer; /* 3 */
*overflow: visible; /* 4 */
}
/**
* Re-set default cursor for disabled elements.
*/
button[disabled],
html input[disabled] {
cursor: default;
}
/**
* 1. Address box sizing set to content-box in IE 8/9.
* 2. Remove excess padding in IE 8/9.
* 3. Remove excess padding in IE 7.
* Known issue: excess padding remains in IE 6.
*/
input[type="checkbox"],
input[type="radio"] {
box-sizing: border-box; /* 1 */
padding: 0; /* 2 */
*height: 13px; /* 3 */
*width: 13px; /* 3 */
}
/**
* 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome.
* 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome
* (include `-moz` to future-proof).
*/
input[type="search"] {
-webkit-appearance: textfield; /* 1 */
-moz-box-sizing: content-box;
-webkit-box-sizing: content-box; /* 2 */
box-sizing: content-box;
}
/**
* Remove inner padding and search cancel button in Safari 5 and Chrome
* on OS X.
*/
input[type="search"]::-webkit-search-cancel-button,
input[type="search"]::-webkit-search-decoration {
-webkit-appearance: none;
}
/**
* Remove inner padding and border in Firefox 3+.
*/
button::-moz-focus-inner,
input::-moz-focus-inner {
border: 0;
padding: 0;
}
/**
* 1. Remove default vertical scrollbar in IE 6/7/8/9.
* 2. Improve readability and alignment in all browsers.
*/
textarea {
overflow: auto; /* 1 */
vertical-align: top; /* 2 */
}
/* ==========================================================================
Tables
========================================================================== */
/**
* Remove most spacing between table cells.
*/
table {
border-collapse: collapse;
border-spacing: 0;
}
/*! normalize.css v1.1.2 | MIT License | git.io/normalize */article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}audio:not([controls]){display:none;height:0}[hidden]{display:none}html{font-size:100%;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}html,button,input,select,textarea{font-family:sans-serif}body{margin:0}a:focus{outline:thin dotted}a:active,a:hover{outline:0}h1{font-size:2em;margin:.67em 0}h2{font-size:1.5em;margin:.83em 0}h3{font-size:1.17em;margin:1em 0}h4{font-size:1em;margin:1.33em 0}h5{font-size:.83em;margin:1.67em 0}h6{font-size:.67em;margin:2.33em 0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}blockquote{margin:1em 40px}dfn{font-style:italic}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}mark{background:#ff0;color:#000}p,pre{margin:1em 0}code,kbd,pre,samp{font-family:monospace,serif;_font-family:'courier new',monospace;font-size:1em}pre{white-space:pre;white-space:pre-wrap;word-wrap:break-word}q{quotes:none}q:before,q:after{content:'';content:none}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}dl,menu,ol,ul{margin:1em 0}dd{margin:0 0 0 40px}menu,ol,ul{padding:0 0 0 40px}nav ul,nav ol{list-style:none;list-style-image:none}img{border:0;-ms-interpolation-mode:bicubic}svg:not(:root){overflow:hidden}figure{margin:0}form{margin:0}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0;white-space:normal;*margin-left:-7px}button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}button,input{line-height:normal}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer;*overflow:visible}button[disabled],html input[disabled]{cursor:default}input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0;*height:13px;*width:13px}input[type="search"]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}textarea{overflow:auto;vertical-align:top}table{border-collapse:collapse;border-spacing:0}
\ No newline at end of file
<!DOCTYPE html>
<!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
<!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
<!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]-->
<!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]-->
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title></title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width">
<link rel="stylesheet" href="css/normalize.min.css">
<link rel="stylesheet" href="css/main.css">
<script src="js/vendor/modernizr-2.6.2.min.js"></script>
</head>
<body>
<!--[if lt IE 7]>
<p class="chromeframe">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> or <a href="http://www.google.com/chromeframe/?redirect=true">activate Google Chrome Frame</a> to improve your experience.</p>
<![endif]-->
<div class="header-container">
<header class="wrapper clearfix">
<h1 class="title">Transmux Analyzer</h1>
</header>
</div>
<div class="main-container">
<div class="main wrapper clearfix">
<article>
<header>
<p>
This page can help you compare the results of the
transmuxing performed by videojs-contrib-hls with a known,
working file produced by another tool. You could use
ffmpeg to transform an MPEG-2 transport stream into an FLV
with a command like this:
<pre>ffmpeg -i input.ts -acodec copy -vcodec copy output.flv</pre>
</p>
<p>
This page only compares FLVs files. There is
a <a href="mp4.html">similar utility</a> for testing the mp4
conversion.
</p>
</header>
<section>
<h2>Inputs</h2>
<form id="inputs">
<fieldset>
<label>
Your original MP2T segment:
<input type="file" id="original">
</label>
<label>
Key (optional):
<input type="text" id="key">
</label>
<label>
IV (optional):
<input type="text" id="iv">
</label>
</fieldset>
<label>
A working, FLV version of the underlying stream
produced by another tool:
<input type="file" id="working">
</label>
</form>
</section>
<section>
<h2>Tag Comparison</h2>
<div class="result-wrapper">
<h3>videojs-contrib-hls</h3>
<ol class="vjs-tags">
</ol>
</div>
<div class="result-wrapper">
<h3>Working</h3>
<ol class="working-tags">
</ol>
</div>
</section>
<section>
<h2>Results</h2>
<div class="result-wrapper">
<h3>videojs-contrib-hls</h3>
<div class="vjs-hls-output result">
<p>
The results of transmuxing your input file with
videojs-contrib-hls will show up here.
</p>
</div>
</div>
<div class="result-wrapper">
<h3>Working</h3>
<div class="working-output result">
<p>
The "good" version of the file will show up here.
</p>
</div>
</div>
</section>
</article>
</div> <!-- #main -->
</div> <!-- #main-container -->
<div class="footer-container">
<footer class="wrapper">
<h3>footer</h3>
</footer>
</div>
<script>
window.videojs = {
Hls: {}
};
</script>
<!-- transmuxing -->
<script src="../../src/stream.js"></script>
<script src="../../src/flv-tag.js"></script>
<script src="../../src/exp-golomb.js"></script>
<script src="../../src/h264-stream.js"></script>
<script src="../../src/aac-stream.js"></script>
<script src="../../src/metadata-stream.js"></script>
<script src="../../src/segment-parser.js"></script>
<script src="../../node_modules/pkcs7/dist/pkcs7.unpad.js"></script>
<script src="../../src/decrypter.js"></script>
<script src="../../src/bin-utils.js"></script>
<script>
var inputs = document.getElementById('inputs'),
original = document.getElementById('original'),
key = document.getElementById('key'),
iv = document.getElementById('iv'),
working = document.getElementById('working'),
vjsTags = document.querySelector('.vjs-tags'),
workingTags = document.querySelector('.working-tags'),
vjsOutput = document.querySelector('.vjs-hls-output'),
workingOutput = document.querySelector('.working-output'),
tagTypes = {
0x08: 'audio',
0x09: 'video',
0x12: 'metadata'
};
videojs.log = console.log.bind(console);
original.addEventListener('change', function() {
var reader = new FileReader();
reader.addEventListener('loadend', function() {
var parser = new videojs.Hls.SegmentParser(),
tags = [parser.getFlvHeader()],
tag,
bytes,
hex,
li,
byteLength = 0,
data,
i,
pos;
// clear old tag info
vjsTags.innerHTML = '';
// optionally, decrypt the segment
if (key.value && iv.value) {
bytes = videojs.Hls.decrypt(new Uint8Array(reader.result),
key.value.match(/([0-9a-f]{8})/gi).map(function(e) {
return parseInt(e, 16);
}),
iv.value.match(/([0-9a-f]{8})/gi).map(function(e) {
return parseInt(e, 16);
}));
} else {
bytes = new Uint8Array(reader.result);
}
parser.parseSegmentBinaryData(bytes);
// collect all the tags
while (parser.tagsAvailable()) {
tag = parser.getNextTag();
tags.push(tag.bytes);
li = document.createElement('li');
li.innerHTML = tagTypes[tag.bytes[0]] + ': ' + tag.bytes.byteLength + 'B';
vjsTags.appendChild(li);
}
// create a uint8array for the entire segment and copy everything over
i = tags.length;
while (i--) {
byteLength += tags[i].byteLength;
}
data = new Uint8Array(byteLength);
i = tags.length;
pos = byteLength;
while (i--) {
pos -= tags[i].byteLength;
data.set(tags[i], pos);
}
hex = '<pre>'
hex += videojs.Hls.utils.hexDump(data);
hex += '</pre>'
vjsOutput.innerHTML = hex;
});
reader.readAsArrayBuffer(this.files[0]);
}, false);
working.addEventListener('change', function() {
var reader = new FileReader();
reader.addEventListener('loadend', function() {
var hex = '<pre>',
bytes = new Uint8Array(reader.result),
i = 9, // header
dataSize,
li;
// clear old tag info
workingTags.innerHTML = '';
// traverse the tags
i += 4; // previous tag size
while (i < bytes.byteLength) {
dataSize = bytes[i + 1] << 16;
dataSize |= bytes[i + 2] << 8;
dataSize |= bytes[i + 3];
dataSize += 11;
li = document.createElement('li');
li.innerHTML = tagTypes[bytes[i]] + ': ' + dataSize + 'B';
workingTags.appendChild(li);
i += dataSize; // tag size
i += 4; // previous tag size
}
// output the hex dump
hex += videojs.Hls.utils.hexDump(bytes);
hex += '</pre>';
workingOutput.innerHTML = hex;
});
reader.readAsArrayBuffer(this.files[0]);
}, false);
</script>
<script type="text/plain">
// map nal_unit_types to friendly names
console.log([
'unspecified',
'slice_layer_without_partitioning',
'slice_data_partition_a_layer',
'slice_data_partition_b_layer',
'slice_data_partition_c_layer',
'slice_layer_without_partitioning_idr',
'sei',
'seq_parameter_set',
'pic_parameter_set',
'access_unit_delimiter',
'end_of_seq',
'end_of_stream',
'filler',
'seq_parameter_set_ext',
'prefix_nal_unit',
'subset_seq_parameter_set',
'reserved',
'reserved',
'reserved'
][nalUnitType]);
</script>
</body>
</html>
var moof = [0,0,0,16,109,102,104,100,0,0,0,0,0,0,0,1,0,0,0,164,116,114,97,102,0,0,0,16,116,102,104,100,0,2,0,0,0,0,0,1,0,0,0,16,116,102,100,116,0,0,0,0,0,0,0,0,0,0,0,124,116,114,117,110,0,0,2,5,0,0,0,25,0,0,0,196,0,0,0,0,0,0,12,219,0,0,0,183,0,0,0,172,0,0,0,182,0,0,0,183,0,0,0,198,0,0,0,146,0,0,0,176,0,0,0,190,0,0,0,169,0,0,0,243,0,0,0,174,0,0,0,131,0,0,0,161,0,0,0,144,0,0,0,163,0,0,0,128,0,0,0,174,0,0,0,194,0,0,0,133,0,0,0,183,0,0,0,154,0,0,0,150,0,0,0,169,0,0,0,157];
var mdat = [0,0,2,151,6,5,255,255,147,220,69,233,189,230,217,72,183,150,44,216,32,217,35,238,239,120,50,54,52,32,45,32,99,111,114,101,32,49,49,56,32,114,50,48,56,53,32,56,97,54,50,56,51,53,32,45,32,72,46,50,54,52,47,77,80,69,71,45,52,32,65,86,67,32,99,111,100,101,99,32,45,32,67,111,112,121,108,101,102,116,32,50,48,48,51,45,50,48,49,49,32,45,32,104,116,116,112,58,47,47,119,119,119,46,118,105,100,101,111,108,97,110,46,111,114,103,47,120,50,54,52,46,104,116,109,108,32,45,32,111,112,116,105,111,110,115,58,32,99,97,98,97,99,61,48,32,114,101,102,61,51,32,100,101,98,108,111,99,107,61,49,58,48,58,48,32,97,110,97,108,121,115,101,61,48,120,49,58,48,120,49,49,49,32,109,101,61,104,101,120,32,115,117,98,109,101,61,55,32,112,115,121,61,49,32,112,115,121,95,114,100,61,49,46,48,48,58,48,46,48,48,32,109,105,120,101,100,95,114,101,102,61,49,32,109,101,95,114,97,110,103,101,61,49,54,32,99,104,114,111,109,97,95,109,101,61,49,32,116,114,101,108,108,105,115,61,49,32,56,120,56,100,99,116,61,48,32,99,113,109,61,48,32,100,101,97,100,122,111,110,101,61,50,49,44,49,49,32,102,97,115,116,95,112,115,107,105,112,61,49,32,99,104,114,111,109,97,95,113,112,95,111,102,102,115,101,116,61,45,50,32,116,104,114,101,97,100,115,61,51,32,115,108,105,99,101,100,95,116,104,114,101,97,100,115,61,48,32,110,114,61,48,32,100,101,99,105,109,97,116,101,61,49,32,105,110,116,101,114,108,97,99,101,100,61,48,32,98,108,117,114,97,121,95,99,111,109,112,97,116,61,48,32,99,111,110,115,116,114,97,105,110,101,100,95,105,110,116,114,97,61,48,32,98,102,114,97,109,101,115,61,48,32,119,101,105,103,104,116,112,61,48,32,107,101,121,105,110,116,61,50,53,32,107,101,121,105,110,116,95,109,105,110,61,49,51,32,115,99,101,110,101,99,117,116,61,52,48,32,105,110,116,114,97,95,114,101,102,114,101,115,104,61,48,32,114,99,95,108,111,111,107,97,104,101,97,100,61,52,48,32,114,99,61,99,98,114,32,109,98,116,114,101,101,61,49,32,98,105,116,114,97,116,101,61,52,56,32,114,97,116,101,116,111,108,61,49,46,48,32,113,99,111,109,112,61,48,46,54,48,32,113,112,109,105,110,61,48,32,113,112,109,97,120,61,54,57,32,113,112,115,116,101,112,61,52,32,118,98,118,95,109,97,120,114,97,116,101,61,52,56,32,118,98,118,95,98,117,102,115,105,122,101,61,50,48,48,48,32,110,97,108,95,104,114,100,61,110,111,110,101,32,105,112,95,114,97,116,105,111,61,49,46,52,48,32,97,113,61,49,58,49,46,48,48,0,128,0,0,10,60,101,136,132,25,201,6,34,128,0,195,76,156,156,156,156,156,156,156,156,156,156,156,156,156,156,156,156,156,156,157,117,215,93,117,215,93,117,215,93,117,215,93,117,215,93,117,215,93,117,215,93,117,215,95,255,222,238,80,168,38,192,35,16,144,238,68,216,105,218,55,168,0,162,196,199,82,63,12,225,73,161,167,199,63,255,119,220,120,86,40,104,195,139,254,42,238,91,250,153,255,255,85,225,81,64,72,185,204,154,55,71,225,39,244,108,42,251,175,255,254,171,134,8,60,160,192,171,24,186,235,174,186,235,174,186,235,174,186,235,254,68,82,105,120,193,48,3,216,90,173,113,18,222,1,212,208,33,57,132,214,13,165,0,49,178,166,201,246,232,31,159,114,199,225,248,120,43,94,172,137,32,75,180,11,247,223,215,60,255,225,240,208,6,179,209,69,200,98,8,78,127,56,167,193,4,10,86,12,135,128,50,171,244,132,239,23,93,117,215,93,117,255,255,174,21,39,0,34,47,146,109,95,99,121,145,164,252,245,215,93,127,218,223,102,56,80,108,162,144,4,45,36,192,200,180,217,171,107,207,0,5,118,124,132,86,222,3,228,95,255,135,132,133,95,63,201,94,237,216,117,215,93,253,52,167,211,72,160,150,220,152,252,49,23,119,68,166,120,38,182,158,46,186,235,174,186,235,237,7,14,24,92,172,85,41,52,237,176,36,7,36,243,235,141,132,30,250,171,62,170,183,14,146,109,247,254,0,216,166,193,132,65,114,198,145,17,236,4,181,80,52,183,125,238,240,182,55,155,38,215,252,231,217,97,81,252,12,81,121,32,222,64,100,123,224,117,17,49,226,133,65,77,215,128,8,79,114,61,95,127,46,199,213,220,111,165,80,103,234,179,148,4,110,94,206,245,255,196,106,207,10,148,0,134,53,178,28,75,239,243,54,131,72,65,84,194,41,207,235,70,173,59,32,134,180,210,191,128,35,215,219,55,10,106,126,109,51,86,17,157,104,20,103,247,214,44,196,132,32,28,171,255,255,206,255,213,147,97,133,76,214,102,231,231,241,72,156,56,161,87,89,7,57,85,100,65,255,32,110,253,1,25,209,163,106,105,49,152,90,243,182,81,195,130,157,24,201,118,229,255,196,106,39,10,138,240,68,214,2,163,144,173,82,203,73,174,70,10,101,231,250,63,97,37,178,3,121,219,125,96,145,38,57,34,37,63,255,240,160,68,109,118,202,61,227,203,210,102,142,153,173,76,111,174,186,235,254,31,252,40,104,20,109,73,102,245,50,154,153,215,235,174,186,235,174,190,19,221,127,225,35,195,124,193,179,16,239,119,251,25,126,181,143,140,216,122,35,194,225,107,76,143,235,124,22,30,202,27,52,188,80,232,136,132,137,94,110,191,59,210,95,223,177,114,10,191,252,37,187,154,7,140,177,57,255,61,186,4,194,206,129,208,186,187,46,52,63,207,183,245,54,237,191,235,221,35,76,197,84,140,63,186,52,41,129,82,166,149,255,25,149,23,231,53,175,219,243,232,67,11,191,252,37,252,130,111,2,166,217,10,0,94,211,194,101,84,68,17,31,35,242,222,132,139,208,130,103,95,84,113,195,68,84,93,169,150,64,204,207,71,253,211,174,141,133,58,135,205,35,27,187,30,176,178,60,125,14,145,16,145,43,206,175,130,255,194,66,51,199,59,5,57,117,243,84,108,168,126,150,206,255,93,135,142,157,191,20,156,122,213,80,172,179,98,143,80,240,154,158,36,146,159,145,86,194,237,251,110,225,169,127,194,66,29,151,107,53,213,93,120,115,180,1,184,180,21,41,249,151,77,200,172,235,159,179,35,35,123,180,189,31,190,255,79,130,82,20,8,128,212,22,177,22,61,232,247,30,120,3,79,232,150,179,54,225,239,11,174,186,235,255,183,177,182,20,52,2,228,166,105,102,193,76,2,71,210,89,246,226,238,235,245,215,93,117,215,93,119,223,125,255,210,31,240,248,94,2,25,231,73,255,255,47,210,129,224,115,207,22,18,209,12,2,92,233,134,237,191,192,43,214,64,109,19,255,8,48,37,118,186,235,255,255,224,132,188,0,126,81,156,120,8,99,90,123,107,101,41,126,19,52,38,229,238,98,61,176,47,32,11,119,32,54,137,225,215,93,117,215,93,117,215,93,117,215,95,234,112,255,135,197,224,7,75,35,133,156,62,179,243,170,233,134,170,56,177,31,4,254,52,151,134,30,112,37,243,126,6,143,41,153,143,255,250,172,84,120,32,142,236,197,21,248,182,229,187,255,254,239,194,176,33,18,156,200,41,70,241,254,52,125,104,216,72,212,245,255,238,69,191,248,75,128,27,108,204,4,130,175,242,117,231,180,128,23,218,89,154,72,63,147,174,186,235,174,186,235,175,255,244,30,20,62,0,9,26,140,152,187,140,173,72,149,57,9,16,162,250,87,66,43,56,215,99,158,154,53,171,127,255,219,76,111,60,203,19,244,247,127,143,144,0,152,42,176,51,6,179,229,217,225,160,149,17,207,16,87,78,248,9,225,95,163,21,113,70,213,129,40,107,116,216,109,178,200,144,31,117,163,49,39,243,81,141,245,71,73,230,249,246,126,239,209,240,255,168,97,180,45,192,178,117,74,198,219,174,10,140,240,22,146,16,77,184,39,91,18,251,142,15,32,71,84,247,230,161,195,240,0,168,132,78,73,49,199,205,5,192,67,254,193,46,21,175,76,229,19,0,26,100,168,43,230,37,9,66,51,3,90,66,243,186,94,187,232,26,151,217,89,85,99,255,86,166,46,38,206,80,185,255,88,94,45,103,228,8,76,122,227,98,250,34,184,114,238,200,171,26,49,98,139,255,103,87,173,64,119,90,66,176,207,147,189,132,144,105,120,183,18,140,253,105,131,221,240,202,191,246,169,111,254,223,160,138,73,34,149,138,238,62,184,175,27,224,184,253,44,134,172,190,120,26,228,92,187,202,254,192,220,79,205,151,190,216,230,3,7,219,166,104,91,206,38,255,233,85,85,143,136,231,102,196,227,158,181,26,17,82,208,205,91,9,69,255,185,238,98,122,133,85,19,132,125,95,251,34,196,231,154,186,208,233,249,168,247,255,127,106,63,32,51,67,163,1,190,176,66,92,198,212,84,186,39,254,119,31,56,251,223,4,166,108,149,89,78,236,0,180,201,156,173,94,153,111,254,252,17,108,29,69,70,221,79,156,2,201,254,41,118,45,169,255,216,93,195,83,174,18,107,125,167,192,226,217,88,184,46,127,238,193,177,38,168,69,139,159,223,114,99,37,109,32,53,38,184,207,253,205,158,205,231,141,74,171,76,139,140,193,242,246,180,56,178,144,18,162,41,16,43,255,155,120,145,25,202,221,219,239,184,14,234,58,129,128,223,214,124,110,155,150,205,118,175,251,192,14,148,58,73,44,180,15,29,201,184,122,56,103,137,231,0,45,144,127,145,96,157,75,37,3,180,104,193,173,166,114,208,214,159,223,214,123,73,163,0,5,117,61,156,14,52,198,157,135,213,215,211,130,136,185,173,120,160,245,142,245,169,208,22,106,52,233,254,183,121,49,53,105,86,96,66,175,35,148,64,126,18,240,88,215,207,255,251,73,32,112,196,160,73,215,74,223,186,240,6,64,7,80,27,252,60,91,228,206,175,251,35,194,137,161,5,148,166,55,254,195,187,101,2,100,39,246,127,205,191,128,128,243,166,153,163,24,82,210,161,152,149,209,204,101,111,187,252,105,224,236,86,165,56,18,115,255,255,19,11,171,107,34,156,76,111,152,1,108,42,188,73,174,110,189,157,143,172,198,139,246,182,99,88,234,237,171,127,161,133,33,241,63,39,31,194,101,244,58,204,82,50,28,62,166,246,128,220,58,42,111,17,86,140,183,219,133,4,72,74,141,148,103,54,62,213,36,74,68,137,87,255,208,253,117,215,93,117,215,93,117,255,255,8,66,135,225,191,64,31,51,34,6,19,247,9,99,159,227,240,8,11,56,216,140,0,171,94,141,16,247,76,216,98,241,23,172,125,190,96,61,54,112,80,180,192,176,239,166,82,195,32,168,103,107,73,19,16,119,126,122,54,227,251,62,187,145,247,164,96,213,91,12,71,194,230,205,4,77,193,43,38,167,48,98,200,67,194,185,211,85,240,127,235,140,66,34,35,190,3,243,47,131,132,172,43,63,255,85,46,12,155,217,238,172,55,73,48,115,152,251,227,16,194,71,82,93,43,20,25,8,144,50,139,168,52,255,242,75,9,170,96,249,57,6,239,15,154,49,205,152,41,18,214,74,42,204,86,193,72,239,245,120,127,195,224,24,7,10,192,157,166,143,46,49,16,103,240,10,28,252,128,38,98,217,40,152,86,240,248,65,34,153,30,175,230,88,23,251,139,26,107,20,55,246,234,167,193,135,235,50,68,92,135,184,126,0,41,205,147,8,129,210,235,224,31,154,244,105,117,255,253,224,104,210,82,92,144,252,7,24,71,87,220,90,79,141,97,177,21,191,216,120,146,204,15,63,140,157,223,73,11,152,130,71,124,161,189,225,253,132,239,73,84,11,55,230,109,209,166,131,160,248,153,53,167,193,252,84,86,133,241,126,252,103,226,229,213,170,59,131,255,246,166,96,221,56,138,82,119,81,126,210,136,228,36,142,8,148,27,191,220,34,22,228,86,109,140,200,181,129,226,39,82,198,74,115,255,43,57,17,28,243,227,119,223,73,132,73,57,22,140,177,53,246,255,143,75,122,68,86,182,39,217,212,20,152,234,175,239,251,108,128,166,114,66,141,192,137,255,193,219,140,13,81,113,178,233,155,82,69,68,113,13,204,180,31,192,12,70,64,60,68,247,212,6,130,174,179,151,13,188,29,109,18,3,119,208,43,37,115,65,5,204,144,184,33,24,69,30,254,54,205,201,63,115,80,217,127,127,244,67,49,80,247,168,139,249,83,255,123,114,1,170,237,67,99,234,219,144,215,4,18,236,169,215,251,223,213,178,113,148,52,75,191,239,242,96,125,81,77,29,40,219,89,162,35,20,198,227,120,31,248,154,52,64,85,194,230,91,22,142,6,104,173,3,77,147,26,164,111,254,42,84,1,204,72,61,140,96,5,191,5,174,85,179,228,84,8,175,2,58,99,107,14,98,44,13,255,46,67,245,94,70,125,53,90,241,209,237,160,203,225,219,3,37,181,152,21,246,12,25,243,249,20,173,224,251,164,130,119,149,151,188,64,118,134,136,243,192,253,26,236,17,244,3,6,161,56,241,241,227,58,192,119,65,2,85,69,101,194,147,105,35,212,64,201,135,43,129,87,131,254,24,153,225,6,226,250,93,242,223,246,161,232,38,235,82,149,186,208,131,255,224,119,239,223,128,8,91,254,167,223,255,193,54,146,56,131,228,158,254,240,20,219,226,181,174,62,91,97,110,120,231,111,181,135,34,0,55,131,15,194,73,33,218,198,76,123,70,176,25,60,252,123,173,180,121,252,31,255,220,98,1,198,120,0,43,164,153,155,68,70,100,1,68,194,33,145,96,191,16,42,212,112,86,184,139,195,63,221,241,194,72,146,175,1,192,89,134,143,188,80,73,161,214,37,255,238,53,252,5,209,205,66,96,119,255,3,135,207,225,15,193,0,155,16,161,27,102,216,136,122,189,255,93,117,215,255,255,194,65,87,125,223,120,99,44,240,251,44,240,198,89,225,246,88,127,99,45,199,31,255,239,15,178,207,12,101,158,31,101,158,24,203,15,195,236,183,199,255,199,225,140,183,143,255,247,135,217,103,134,50,195,251,236,183,31,255,247,134,50,207,15,178,207,12,101,158,31,101,158,24,203,64,0,0,0,179,65,154,56,59,131,107,152,241,64,0,64,43,20,0,4,2,190,82,5,192,0,247,128,198,49,192,39,2,192,0,244,164,248,64,96,240,64,253,159,205,107,125,155,241,99,57,170,177,157,93,248,182,8,109,152,126,10,240,110,177,6,248,127,240,66,43,128,99,196,97,211,109,246,255,15,66,111,243,73,209,139,47,134,99,29,98,48,1,125,106,153,161,178,253,244,246,249,131,47,164,243,35,56,252,184,75,220,168,36,246,188,21,127,32,59,136,136,162,27,251,125,184,80,240,104,149,63,146,34,151,230,114,9,227,27,21,177,0,74,89,242,49,208,115,240,219,227,245,103,207,211,115,8,63,2,158,55,17,226,60,71,136,241,29,97,10,194,21,132,43,8,86,16,196,120,143,17,226,60,0,0,0,168,65,154,84,12,224,221,231,129,36,38,44,176,156,92,6,185,112,11,92,114,7,113,149,193,72,145,228,141,60,127,205,254,21,63,130,15,192,7,170,117,110,189,221,229,54,108,180,121,249,13,255,246,99,133,13,192,72,185,113,236,236,105,199,251,34,10,8,193,67,254,41,138,92,65,225,119,155,218,91,49,124,72,107,130,57,27,177,97,254,203,237,159,136,39,218,16,75,110,35,42,240,11,80,206,48,189,1,147,138,63,9,220,40,66,39,69,239,255,37,151,128,21,215,218,239,174,131,83,97,234,147,152,139,131,185,72,39,128,140,212,213,233,155,255,252,61,12,164,199,246,126,48,158,247,251,17,144,94,14,242,82,182,121,212,65,248,25,224,0,0,0,178,65,154,96,87,7,15,124,167,192,18,53,31,218,6,61,141,142,205,92,19,19,230,174,168,39,229,227,2,0,10,183,113,149,63,192,33,216,167,15,46,9,194,65,237,121,45,194,101,248,20,66,226,40,92,212,161,241,238,98,34,255,252,60,83,1,63,20,186,196,19,128,31,102,153,100,123,224,20,114,141,131,206,60,51,158,194,203,247,183,231,102,23,132,143,61,32,3,45,244,60,16,178,23,173,139,117,202,43,115,77,225,30,169,193,69,83,167,122,34,225,89,115,17,17,127,252,61,113,245,236,79,23,228,40,6,169,7,167,17,57,117,35,16,11,22,133,167,87,140,121,239,247,190,205,100,221,143,232,90,43,151,59,7,215,105,70,45,255,62,105,143,190,3,53,194,24,142,34,0,0,0,179,65,154,128,71,2,57,249,75,248,177,32,81,128,135,144,161,22,7,178,4,222,253,152,69,227,177,153,255,238,253,116,5,64,33,150,0,44,80,105,154,147,173,10,20,107,95,176,88,0,45,226,145,39,130,197,244,232,73,63,111,242,226,114,1,36,194,184,8,186,247,21,130,0,32,141,65,35,67,126,2,249,243,225,57,91,87,247,132,123,173,150,190,111,183,137,39,167,152,115,248,140,4,122,245,231,190,135,220,13,90,146,206,43,92,154,19,138,155,201,89,120,3,24,115,44,221,231,129,54,141,235,85,175,136,184,70,93,226,96,129,188,33,86,192,236,175,218,229,255,204,4,108,79,17,228,8,194,47,4,57,43,99,132,137,155,13,255,26,175,110,223,191,40,142,179,1,2,6,136,0,0,0,194,65,154,160,71,6,190,44,94,96,48,227,45,145,132,143,200,39,175,41,56,2,85,90,52,141,108,37,167,137,196,159,240,168,17,247,135,244,17,9,4,251,172,84,36,195,134,252,4,198,205,227,198,31,52,64,90,255,207,196,37,124,70,0,85,146,38,50,154,206,248,37,104,141,18,45,51,251,254,239,48,220,0,33,2,237,5,20,244,140,183,91,5,92,219,29,77,26,202,43,11,118,66,98,193,45,112,187,113,59,217,57,204,38,12,191,250,208,37,196,10,224,140,49,200,67,126,11,119,45,252,62,12,4,21,94,166,225,41,245,228,13,99,252,25,6,51,241,110,246,200,126,180,86,192,210,182,41,58,202,137,128,197,160,158,202,159,239,202,154,101,163,143,119,235,65,93,245,23,174,196,84,174,39,58,72,225,245,173,138,86,250,120,27,224,0,0,0,142,65,154,192,71,2,179,114,196,98,132,121,125,240,152,59,30,12,199,225,7,6,235,232,46,155,71,19,30,2,192,210,165,133,37,172,253,26,9,190,112,167,226,60,64,232,16,174,150,108,134,173,184,93,60,158,253,173,195,145,236,39,148,176,38,110,234,96,1,153,116,166,138,242,246,182,129,241,41,94,220,78,83,30,28,103,223,55,248,255,133,68,97,150,91,216,219,47,55,253,247,30,8,53,18,250,202,8,66,192,92,17,199,124,43,114,211,2,186,97,51,85,93,236,159,198,123,35,72,180,177,177,116,126,200,56,152,190,184,251,180,13,240,0,0,0,172,65,154,224,220,10,205,33,17,115,113,223,97,66,123,88,177,225,111,143,17,0,55,155,49,17,89,92,140,34,78,190,200,12,74,147,26,139,188,129,15,254,242,50,129,239,151,37,98,248,0,94,87,77,175,86,4,116,45,66,214,162,61,99,27,127,147,179,30,1,187,116,141,230,255,21,245,24,84,64,38,125,75,248,18,249,191,7,26,233,226,111,255,215,10,151,5,155,82,0,125,189,81,183,51,124,63,248,80,108,1,0,88,95,191,99,163,209,160,43,110,72,247,117,224,13,234,183,30,225,12,101,108,49,113,47,19,244,53,91,63,55,144,108,8,201,106,103,230,21,197,173,237,108,80,140,74,109,116,189,25,148,223,242,41,93,242,27,232,217,224,104,128,0,0,0,186,65,155,0,220,10,205,175,133,43,16,34,0,192,135,54,189,189,200,125,206,235,108,6,116,18,246,85,235,38,127,189,27,75,215,183,234,97,56,5,158,35,50,80,43,183,4,246,125,151,214,254,81,24,78,163,48,232,148,215,100,136,145,24,117,201,57,76,88,36,236,211,252,223,250,174,120,32,247,14,207,44,252,107,246,136,65,98,154,160,238,150,28,53,9,120,180,183,215,137,49,164,239,81,174,107,194,61,136,134,184,36,45,62,255,2,215,154,105,101,174,27,94,121,173,20,170,97,77,29,87,255,135,207,28,44,188,199,85,207,234,188,19,155,203,146,231,55,194,203,85,99,108,96,184,2,22,18,77,214,61,49,209,100,173,232,163,216,98,25,144,1,111,18,252,197,239,129,65,249,190,234,141,243,254,6,120,0,0,0,165,65,155,32,71,2,179,245,133,11,232,173,213,8,21,9,5,57,230,2,56,148,20,189,247,242,19,241,58,207,111,193,42,162,42,252,188,149,148,184,1,115,100,52,157,237,228,4,244,208,215,234,155,123,241,56,147,97,151,191,4,32,132,167,224,71,95,159,248,250,216,192,86,56,226,153,36,244,245,126,241,155,223,253,176,101,50,156,115,63,159,10,19,31,25,60,240,230,255,58,159,193,1,122,223,2,13,152,78,0,123,221,125,87,191,159,132,150,12,0,131,136,25,6,174,87,175,4,61,98,107,3,52,165,33,248,145,176,8,235,248,251,219,7,255,228,160,196,237,151,27,94,233,195,128,22,245,179,65,168,137,184,31,224,84,128,0,0,0,239,65,155,64,71,2,179,195,60,220,0,147,92,254,223,250,216,62,99,33,14,228,126,77,255,175,224,128,94,27,218,114,225,255,127,190,19,39,181,98,70,138,120,129,130,2,16,3,199,41,63,71,214,191,3,56,206,136,250,213,83,198,58,98,184,96,29,115,161,214,92,173,70,96,99,69,192,239,175,178,206,34,43,224,32,67,250,231,243,142,172,55,182,224,48,232,52,252,227,205,128,140,205,207,200,33,89,187,48,76,28,165,237,101,55,193,71,85,133,142,116,119,13,109,175,94,118,19,156,217,80,65,86,165,63,29,228,128,22,49,10,23,179,167,53,136,20,138,201,196,45,72,31,253,225,5,106,109,183,125,246,32,191,97,202,53,135,148,199,52,249,233,48,130,120,214,63,183,159,250,48,220,51,22,39,194,24,68,223,73,255,195,226,163,166,94,109,62,105,63,139,19,201,41,189,193,50,208,165,224,3,242,155,135,70,113,109,61,242,192,67,120,35,32,215,135,159,132,188,132,2,24,161,13,160,255,225,136,16,32,0,0,0,170,65,155,96,87,2,183,102,224,89,165,0,17,238,159,109,76,41,228,55,213,34,157,128,144,176,30,127,238,76,242,192,4,44,211,223,245,173,148,192,34,20,196,204,192,34,20,196,205,189,132,242,154,4,205,221,76,92,8,107,104,15,137,74,246,226,113,230,203,24,46,229,47,80,17,49,239,70,217,128,150,102,145,246,194,31,152,75,141,125,159,126,228,147,16,242,248,68,21,226,57,97,26,210,252,252,37,249,169,76,62,148,193,57,97,50,136,206,48,99,175,124,4,143,71,225,31,36,16,251,177,96,0,83,130,104,196,70,151,193,240,47,252,64,184,9,141,180,48,1,111,37,142,101,228,71,220,215,211,192,233,66,161,228,201,119,175,15,19,192,129,0,0,0,127,65,155,128,87,2,181,112,165,100,52,0,134,39,4,108,206,82,180,11,82,210,68,0,117,69,199,175,246,80,106,169,48,12,44,99,159,243,250,42,132,133,58,177,126,6,239,41,176,2,164,166,218,221,238,128,219,76,138,115,249,158,39,10,248,67,30,222,246,41,191,222,183,216,90,132,254,69,137,7,225,16,18,121,248,76,223,10,127,195,226,120,213,56,20,37,22,243,182,20,48,127,64,27,191,22,40,234,35,104,50,11,117,184,42,34,139,71,129,143,172,139,22,51,71,224,66,128,0,0,0,157,65,155,160,87,3,87,144,208,17,25,156,21,13,152,240,46,65,222,185,111,101,9,24,120,214,205,93,143,83,63,153,77,63,21,54,26,185,64,18,47,73,157,35,31,0,65,181,83,235,121,77,193,229,106,56,17,191,210,21,210,206,194,112,249,132,224,47,141,126,113,60,115,244,200,32,20,182,254,35,18,229,202,223,172,194,81,190,23,247,223,171,210,149,125,246,42,79,104,166,43,74,175,15,249,37,49,191,225,239,18,44,80,16,194,64,40,115,240,153,189,161,242,150,9,163,244,255,26,164,252,38,104,126,177,5,194,19,60,89,147,40,233,131,190,183,78,253,224,128,249,79,4,47,92,112,33,64,0,0,0,140,65,155,192,87,6,250,195,247,155,192,36,253,125,156,248,174,226,111,32,136,22,159,86,19,46,115,234,254,64,175,59,65,199,49,141,106,115,246,92,104,57,87,166,214,75,157,238,178,147,0,46,226,68,185,89,192,111,52,103,173,86,207,19,133,12,124,1,106,159,146,89,225,6,173,108,117,137,36,6,249,13,237,45,173,240,247,132,35,36,19,243,97,11,37,21,17,97,51,91,229,254,30,41,189,243,240,145,142,170,204,216,235,226,188,212,225,212,137,153,235,172,71,172,177,252,223,226,186,248,32,18,82,135,250,112,91,114,223,129,14,0,0,0,159,65,155,224,87,2,181,102,241,179,190,240,157,100,17,0,53,90,66,59,247,166,223,191,74,214,202,4,243,68,74,176,186,185,251,70,188,197,132,204,239,96,12,34,87,141,51,8,241,229,137,230,94,247,90,202,43,130,187,110,133,74,233,183,91,46,228,156,32,99,151,193,11,67,139,31,228,48,71,173,61,53,179,127,255,146,132,159,246,255,251,16,24,142,255,235,207,188,8,0,175,52,155,229,254,18,44,252,178,249,225,51,121,74,196,95,137,240,223,155,23,252,255,193,198,18,242,4,96,4,57,106,121,146,247,230,42,175,199,85,224,156,103,10,4,205,198,245,148,55,129,47,155,241,14,166,88,255,2,36,0,0,0,124,65,154,0,87,2,181,253,44,39,228,17,0,99,95,101,95,124,67,216,204,130,141,43,87,123,46,119,95,149,49,183,176,158,82,64,153,187,169,128,13,255,162,201,251,247,189,173,160,62,37,43,219,136,190,18,23,226,120,4,121,24,189,191,225,10,216,162,85,203,163,180,113,226,77,62,251,204,191,132,64,176,8,129,164,60,94,27,247,193,128,41,207,194,47,193,36,35,136,97,193,169,119,227,127,244,2,32,4,110,35,132,176,176,64,129,60,33,106,95,133,194,48,34,64,0,0,0,170,65,154,32,87,2,181,102,240,9,203,70,151,249,167,132,201,245,247,228,17,9,249,24,9,145,5,223,207,64,183,242,130,143,200,45,58,79,124,64,36,253,148,39,251,170,191,209,227,18,187,203,192,1,187,45,249,241,178,189,47,76,77,188,95,245,249,215,175,67,133,147,4,150,165,153,54,144,11,212,55,219,251,92,188,109,236,66,123,135,246,141,53,94,255,204,126,1,30,190,218,240,43,2,64,88,13,5,147,182,105,177,236,191,47,195,37,4,158,248,212,175,96,104,194,57,36,13,194,102,201,175,83,255,127,127,172,16,135,144,128,128,73,102,191,182,104,176,82,200,67,88,194,37,90,2,247,127,224,66,125,1,58,249,252,252,35,254,92,8,176,0,0,0,190,65,154,64,103,2,181,126,35,9,214,67,64,38,226,151,163,226,153,253,214,181,179,133,156,135,203,25,15,188,243,89,107,116,253,179,75,32,89,190,0,64,50,242,11,72,190,220,37,8,102,103,169,68,232,255,111,41,184,112,201,24,19,42,62,37,214,238,87,152,95,2,107,212,201,191,241,125,198,21,25,166,122,130,3,58,50,8,207,4,99,170,38,248,123,223,194,184,11,79,84,114,11,120,224,254,63,201,20,199,39,35,230,112,195,224,251,249,159,235,192,103,186,228,221,190,196,89,110,141,168,240,222,161,163,246,76,110,253,95,123,1,205,224,8,94,41,72,80,104,4,41,170,237,249,60,122,96,100,87,198,176,63,0,71,47,200,82,230,109,195,222,38,190,239,135,248,120,66,60,177,159,95,208,4,3,215,9,159,129,98,0,0,0,129,65,154,96,103,2,189,147,9,249,4,64,7,58,225,200,74,114,18,173,80,104,244,223,213,214,206,42,216,191,207,249,151,169,149,66,119,186,202,76,0,209,69,10,120,252,220,4,245,163,62,212,187,244,222,22,12,32,28,41,13,211,48,148,197,29,87,123,230,135,155,5,255,0,225,226,117,155,252,3,0,225,89,236,125,150,215,251,243,127,128,64,60,42,83,224,203,71,66,203,124,33,91,20,48,139,232,126,5,187,191,203,248,128,247,144,104,18,158,105,103,255,129,227,66,56,76,252,11,16,0,0,0,179,65,154,128,87,2,111,150,120,110,37,226,185,184,34,184,122,108,190,108,1,213,136,177,184,0,53,73,102,81,224,0,0,64,27,49,127,58,174,167,22,103,255,12,154,8,175,65,170,246,57,213,98,4,64,24,22,245,79,215,60,230,48,98,109,58,34,45,130,196,158,52,1,141,84,33,152,148,70,229,184,63,178,144,255,249,245,121,184,4,196,245,118,192,86,158,137,95,30,255,118,178,155,15,119,97,253,108,217,13,108,30,32,110,65,97,236,106,211,143,126,171,246,1,128,144,94,2,34,218,111,207,204,171,173,71,254,18,10,95,153,157,159,81,213,155,85,135,235,225,63,86,248,251,200,122,183,125,211,175,114,117,123,255,189,136,27,20,17,223,253,71,252,249,252,71,136,224,107,128,0,0,0,150,65,154,160,103,2,183,155,192,2,30,233,246,202,101,232,81,96,6,23,171,227,35,60,168,192,97,173,242,16,45,158,50,28,25,69,55,252,120,20,132,8,21,142,205,13,44,241,86,124,69,1,255,195,79,4,64,146,131,55,19,127,169,215,80,133,72,63,43,248,89,211,27,228,54,241,241,101,130,206,228,63,2,91,10,81,169,196,61,158,7,173,221,243,198,221,183,176,157,148,210,216,17,231,44,238,103,142,146,55,59,207,216,6,195,155,235,255,195,229,218,230,58,172,15,235,225,226,119,8,61,26,71,76,46,38,208,241,239,35,91,243,191,114,81,252,71,136,224,107,128,0,0,0,146,65,154,192,103,2,57,252,191,38,8,65,176,49,2,48,71,128,50,95,3,134,120,190,0,91,154,22,208,170,62,168,55,210,243,213,120,173,101,255,203,224,10,170,198,65,55,94,245,36,144,195,16,66,10,165,152,197,255,254,30,132,235,254,61,255,153,78,121,252,206,112,248,140,124,100,252,18,70,83,144,208,9,201,68,206,84,41,74,131,239,61,246,246,85,140,238,176,173,10,116,47,91,59,221,229,38,2,81,207,150,254,204,37,119,134,126,234,176,36,0,152,239,204,103,56,31,207,195,217,225,132,43,125,131,113,21,95,105,244,127,63,136,239,168,26,32,0,0,0,165,65,154,224,87,6,190,83,224,1,218,159,240,88,229,55,216,34,250,115,161,158,176,131,240,70,8,2,70,128,22,152,174,215,248,182,0,77,199,110,96,224,128,230,176,137,193,41,126,176,110,4,40,32,30,48,191,26,152,174,191,141,174,131,222,28,29,32,161,127,132,252,129,120,23,78,136,254,95,243,5,62,154,124,16,7,57,56,207,33,96,75,77,245,92,66,197,106,62,205,236,165,69,11,74,150,124,183,154,214,188,220,5,187,93,128,70,9,201,147,55,217,111,41,178,140,60,158,219,165,228,240,134,139,225,188,119,199,191,82,77,130,242,147,251,250,255,184,63,211,41,199,91,7,80,239,155,27,191,103,123,1,163,240,54,192,0,0,0,145,65,155,0,87,6,135,247,228,139,46,224,3,233,94,198,82,128,75,0,17,102,68,109,63,35,64,31,4,50,0,0,3,0,128,116,240,0,16,14,252,161,15,132,37,30,97,62,2,193,135,64,2,58,253,189,155,109,134,30,184,10,8,47,18,8,153,249,190,126,49,222,87,231,204,127,52,41,244,244,195,218,120,203,201,128,1,251,68,190,119,228,51,49,204,76,136,172,67,105,158,63,161,211,215,205,12,109,182,175,148,152,3,218,62,72,2,207,88,15,200,156,60,73,54,59,76,33,74,224,206,188,122,19,127,118,250,63,5,91,152,92,119,220,8,112,0,0,0,4,104,203,140,178];
var mdat_n = [0,0,0,1,9,240,0,0,0,1,39,66,224,11,169,24,96,157,128,53,6,1,6,182,194,181,239,124,4,0,0,0,1,40,222,9,136,0,0,1,6,0,7,130,220,108,0,0,3,0,64,128,0,0,1,6,5,17,3,135,244,78,205,10,75,220,161,148,58,195,212,155,23,31,0,128,0,0,1,37,184,32,32,71,255,252,224,158,40,0,8,52,252,194,144,0,68,56,189,149,17,89,122,21,191,230,42,39,1,133,76,0,53,152,211,185,10,132,55,104,162,22,43,163,142,38,18,196,102,59,65,74,226,190,208,213,237,25,146,204,90,161,62,215,80,111,128,55,135,230,8,121,77,214,206,207,162,149,193,123,30,214,4,100,57,114,69,74,166,255,64,151,71,62,159,63,175,64,209,25,173,108,182,76,249,73,96,72,189,208,149,255,180,63,0,6,44,4,169,148,195,173,86,197,183,252,172,95,10,96,218,157,30,233,229,56,3,243,81,162,247,125,2,91,62,31,166,144,125,248,6,189,54,202,191,175,149,253,254,69,201,6,242,191,157,0,78,99,162,46,102,83,248,202,170,61,164,133,153,136,252,210,227,189,37,106,35,223,251,9,21,154,173,14,243,219,200,230,86,85,69,88,79,191,185,157,94,254,117,93,16,159,255,131,172,87,249,255,90,41,40,190,199,123,251,63,177,53,15,132,104,48,121,255,52,230,216,204,24,255,190,220,126,122,9,122,136,173,240,217,212,143,95,81,12,21,237,81,212,254,129,33,12,115,188,107,24,0,212,129,64,0,116,16,6,106,28,153,228,108,234,78,221,107,215,184,13,2,234,54,47,50,248,25,21,191,231,227,85,82,146,248,127,242,120,178,246,24,127,134,0,9,25,144,145,92,170,196,71,116,128,5,206,6,60,145,214,67,128,123,161,13,134,0,47,148,223,47,182,248,23,70,6,122,16,32,222,220,223,224,112,136,6,13,50,27,29,181,238,135,32,53,113,181,198,98,225,177,218,190,120,115,199,137,230,2,248,5,87,13,131,196,198,154,224,83,252,35,210,4,100,62,9,121,153,6,120,0,198,236,34,0,147,198,135,97,75,232,16,248,16,88,137,10,142,153,239,181,81,224,58,70,147,62,161,245,214,167,23,192,149,158,28,210,243,22,34,196,90,103,13,128,30,6,68,124,143,159,240,167,224,224,186,211,30,50,29,253,199,152,189,120,127,255,236,1,16,247,180,126,143,203,108,145,240,233,255,251,231,220,127,255,239,252,119,255,248,19,111,247,255,34,106,228,127,161,213,13,25,226,178,97,146,75,32,162,100,5,17,133,87,235,79,165,24,31,251,173,214,112,92,244,153,139,219,82,160,17,98,154,213,24,128,5,36,69,47,2,252,73,64,3,209,153,18,219,117,191,255,255,24,19,66,73,252,45,169,255,90,29,228,106,195,93,100,22,255,216,28,31,247,198,170,94,252,58,156,191,255,167,225,50,57,71,192,68,221,199,189,216,174,96,39,166,122,83,22,96,176,90,210,146,124,226,252,241,135,239,0,89,210,15,53,40,62,123,133,183,80,179,141,21,149,22,207,124,163,59,199,249,215,130,66,151,178,203,137,95,253,239,8,79,248,67,238,189,255,255,254,142,52,111,174,235,98,0,64,42,240,168,4,213,244,158,236,188,76,49,215,60,156,25,9,172,255,243,240,255,1,145,94,141,66,36,16,207,252,127,247,64,160,53,0,87,75,68,57,78,104,159,255,3,198,169,236,145,108,71,5,157,135,30,175,120,241,212,153,146,39,255,252,86,21,39,0,8,130,249,41,158,147,221,6,193,72,150,191,1,180,142,200,200,204,207,127,249,252,43,192,70,217,19,26,49,123,98,63,61,33,58,137,94,149,78,223,255,97,142,181,29,128,2,109,52,147,105,38,155,255,240,0,157,6,153,28,172,194,44,165,225,254,148,47,64,225,66,3,39,216,71,212,142,127,235,107,121,187,76,6,112,124,48,134,47,196,32,255,94,192,219,45,184,56,174,16,134,247,185,16,14,159,240,60,213,200,24,206,236,247,255,138,35,2,35,11,238,253,136,171,199,202,23,36,4,44,195,77,110,233,110,157,87,90,200,193,162,169,52,123,238,246,255,253,164,255,96,215,24,56,15,216,150,192,140,163,136,247,196,183,192,32,24,38,216,170,239,252,245,82,48,193,162,100,13,231,243,114,13,183,204,29,141,239,153,230,177,184,133,98,41,197,131,107,121,253,251,77,3,242,94,33,12,225,183,223,245,40,176,0,16,27,9,113,67,69,221,149,181,240,4,45,235,1,168,154,245,225,66,142,184,82,186,152,62,177,123,160,227,101,254,207,236,183,176,69,8,16,71,17,239,111,184,192,25,64,2,77,173,177,32,111,176,161,212,130,17,35,243,26,128,3,37,2,8,150,93,131,15,93,91,8,1,152,0,37,204,75,211,251,30,59,56,195,37,8,11,110,217,65,167,13,194,253,4,54,77,103,7,12,34,111,151,146,33,40,255,31,255,131,206,215,246,125,102,190,150,251,255,132,203,132,80,95,227,129,70,63,3,143,255,125,255,140,12,242,15,255,84,188,40,128,193,174,167,192,0,0,1,37,6,238,8,8,47,248,125,40,145,81,64,0,65,14,53,78,1,71,30,0,2,0,58,160,0,8,6,128,3,23,128,97,29,160,108,27,165,103,247,207,5,5,115,240,149,118,37,34,207,140,214,63,220,3,139,197,101,81,157,183,223,255,248,120,75,255,12,112,240,197,8,135,12,11,122,254,193,25,13,10,36,105,100,171,95,231,238,91,137,154,134,168,134,23,105,193,52,81,118,146,204,110,111,251,137,143,194,168,153,176,86,175,173,245,25,231,160,23,55,244,31,240,248,135,241,46,218,217,27,44,134,0,2,0,14,51,0,3,0,211,33,34,170,235,147,149,201,138,166,67,115,239,113,22,96,105,193,119,156,68,95,112,225,223,236,189,48,92,164,156,33,47,255,41,162,154,247,255,73,252,248,124,158,30,0,217,32,244,181,248,243,4,102,153,52,235,156,233,249,159,60,90,248,19,229,218,176,187,42,252,240,22,97,103,150,100,123,139,191,237,83,142,187,249,127,105,180,150,95,65,255,254,156,102,3,136,71,34,103,192,5,228,39,23,241,202,106,213,124,4,249,9,197,216,162,24,246,202,128,11,200,226,144,29,49,201,1,88,200,193,225,144,214,233,187,92,208,234,41,226,81,144,214,205,0,192,48,12,3,224,235,103,45,240,216,0,42,215,31,63,191,52,34,170,33,89,153,148,192,229,18,249,240,196,223,71,223,80,99,76,81,110,184,137,190,193,0,0,128,25,0,0,234,8,0,32,130,184,90,64,26,9,237,46,98,19,136,83,229,52,240,18,59,6,175,211,100,142,113,122,255,255,4,24,153,233,37,136,113,127,119,192,134,9,28,226,169,206,45,183,124,16,88,70,0,232,57,1,80,50,231,152,41,18,66,70,67,253,231,176,244,255,252,71,17,133,120,9,236,136,103,120,129,24,75,127,171,132,162,204,65,74,123,246,252,225,61,103,30,58,74,97,47,159,26,17,85,156,92,237,33,143,108,235,212,56,4,0,35,16,26,153,56,13,165,101,97,93,248,13,61,89,99,209,63,117,159,231,224,45,62,52,34,186,20,140,230,84,47,253,230,66,41,102,81,57,121,49,152,244,246,72,113,167,67,205,119,45,254,6,149,131,249,82,249,83,225,94,128,24,140,71,171,134,214,218,27,72,105,26,243,123,180,35,136,18,213,16,141,102,249,228,67,33,239,194,122,245,11,164,121,33,145,213,200,71,41,204,58,21,192,56,108,142,241,140,225,217,230,128,79,129,18,14,120,173,43,173,15,132,39,75,0,48,211,181,181,158,242,134,78,238,100,126,42,154,164,121,70,42,234,36,108,146,167,177,93,175,5,165,196,36,111,242,90,250,127,206,145,152,43,224,234,107,25,135,9,212,206,195,160,150,50,253,221,200,139,47,81,188,0,27,235,96,120,8,209,82,89,23,222,191,249,165,255,31,194,175,4,12,37,125,154,69,56,185,155,224,20,118,13,95,229,203,148,226,211,46,195,54,114,142,166,48,148,253,240,19,241,9,69,252,41,79,127,191,208,135,201,130,41,217,169,181,137,149,119,101,239,169,231,224,72,253,4,119,79,95,224,160,191,53,147,248,116,232,12,161,160,10,236,189,49,145,31,128,158,250,23,177,206,24,54,167,102,71,240,221,120,78,126,190,168,224,247,149,43,114,126,255,102,129,228,35,241,40,70,125,95,113,128,247,31,63,172,159,25,134,9,214,254,255,33,131,226,136,205,81,247,254,213,153,76,18,226,157,227,7,195,106,251,202,35,148,74,192,171,179,208,128,247,222,74,226,8,221,142,159,158,181,47,4,225,118,97,14,255,186,41,8,227,200,23,63,152,64,106,14,185,168,58,235,51,235,1,218,9,175,251,235,35,46,128,103,178,132,55,191,234,1,90,128,175,154,151,219,229,46,224,126,221,221,235,160,56,143,255,9,23,74,17,229,163,195,10,16,155,255,190,210,132,121,121,194,188,193,13,67,247,193,243,170,7,63,180,123,25,22,9,95,255,128,7,248,88,2,8,37,93,216,127,17,193,255,248,75,134,59,234,145,196,16,243,212,63,246,222,246,96,42,8,80,166,171,251,238,67,211,109,155,0,42,2,49,40,41,143,103,147,48,0,16,0,192,8,158,167,15,9,187,191,252,144,126,184,176,34,45,16,135,117,91,99,101,32,16,67,206,235,223,252,67,7,255,225,47,91,126,192,184,67,36,99,85,109,127,0,5,0,98,167,138,98,15,236,247,128,0,128,4,0,100,101,42,125,44,253,152,0,16,6,245,202,81,254,95,252,87,80,255,132,180,123,152,0,8,1,1,118,70,63,191,158,211,220,240,0,32,228,182,69,72,186,255,211,249,160,0,135,52,153,21,131,43,141,23,100,2,41,193,100,98,14,188,62,1,0,255,194,91,132,136,35,184,20,87,8,155,159,100,2,213,138,112,44,138,96,143,119,255,201,239,232,41,36,192,0,64,5,128,61,221,237,42,42,79,0,1,0,16,7,159,181,253,191,252,0,15,178,48,45,56,180,219,240,108,3,154,134,126,69,211,191,193,231,160,223,23,13,246,191,241,29,27,255,132,184,164,203,0,6,224,23,4,90,15,252,245,146,33,160,21,99,240,198,160,127,207,127,56,68,3,106,174,14,152,112,28,191,247,98,128,121,86,40,20,78,176,139,127,223,252];
var box = new Uint8Array( moof.concat(mdat) );
(function(window, videojs) {
'use strict';
var
DataView = window.DataView,
/**
* Returns the string representation of an ASCII encoded four byte buffer.
* @param buffer {Uint8Array} a four-byte buffer to translate
* @return {string} the corresponding string
*/
parseType = function(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;
},
parseMp4Date = function(seconds) {
return new Date(seconds * 1000 - 2082844800000);
},
parseSampleFlags = function(flags) {
return {
isLeading: (flags[0] & 0x0c) >>> 2,
dependsOn: flags[0] & 0x03,
isDependedOn: (flags[1] & 0xc0) >>> 6,
hasRedundancy: (flags[1] & 0x30) >>> 4,
paddingValue: (flags[1] & 0x0e) >>> 1,
isNonSyncSample: flags[1] & 0x01,
degradationPriority: (flags[2] << 8) | flags[3]
};
},
nalParse = function(avcStream) {
var
avcView = new DataView(avcStream.buffer, avcStream.byteOffset, avcStream.byteLength),
result = [],
i,
length;
for (i = 0; i < avcStream.length; i += length) {
length = avcView.getUint32(i);
i += 4;
// bail if this doesn't appear to be an H264 stream
if (length <= 0) {
return;
}
switch(avcStream[i] & 0x1F) {
case 0x01:
result.push('slice_layer_without_partitioning_rbsp');
break;
case 0x05:
result.push('slice_layer_without_partitioning_rbsp_idr');
break;
case 0x06:
result.push('sei_rbsp');
break;
case 0x07:
result.push('seq_parameter_set_rbsp');
break;
case 0x08:
result.push('pic_parameter_set_rbsp');
break;
case 0x09:
result.push('access_unit_delimiter_rbsp');
break;
default:
result.push(avcStream[i] & 0x1F);
break;
}
}
return result;
},
// registry of handlers for individual mp4 box types
parse = {
// codingname, not a first-class box type. stsd entries share the
// same format as real boxes so the parsing infrastructure can be
// shared
avc1: function(data) {
var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
return {
dataReferenceIndex: view.getUint16(6),
width: view.getUint16(24),
height: view.getUint16(26),
horizresolution: view.getUint16(28) + (view.getUint16(30) / 16),
vertresolution: view.getUint16(32) + (view.getUint16(34) / 16),
frameCount: view.getUint16(40),
depth: view.getUint16(74),
config: videojs.inspectMp4(data.subarray(78, data.byteLength))
};
},
avcC: function(data) {
var
view = new DataView(data.buffer, data.byteOffset, data.byteLength),
result = {
configurationVersion: data[0],
avcProfileIndication: data[1],
profileCompatibility: data[2],
avcLevelIndication: data[3],
lengthSizeMinusOne: data[4] & 0x03,
sps: [],
pps: []
},
numOfSequenceParameterSets = data[5] & 0x1f,
numOfPictureParameterSets,
nalSize,
offset,
i;
// iterate past any SPSs
offset = 6;
for (i = 0; i < numOfSequenceParameterSets; i++) {
nalSize = view.getUint16(offset);
offset += 2;
result.sps.push(new Uint8Array(data.subarray(offset, offset + nalSize)));
offset += nalSize;
}
// iterate past any PPSs
numOfPictureParameterSets = data[offset];
offset++;
for (i = 0; i < numOfPictureParameterSets; i++) {
nalSize = view.getUint16(offset);
offset += 2;
result.pps.push(new Uint8Array(data.subarray(offset, offset + nalSize)));
offset += nalSize;
}
return result;
},
btrt: function(data) {
var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
return {
bufferSizeDB: view.getUint32(0),
maxBitrate: view.getUint32(4),
avgBitrate: view.getUint32(8)
};
},
esds: function(data) {
return {
version: data[0],
flags: new Uint8Array(data.subarray(1, 4)),
esId: (data[6] << 8) | data[7],
streamPriority: data[8] & 0x1f,
decoderConfig: {
objectProfileIndication: data[11],
streamType: (data[12] >>> 2) & 0x3f,
bufferSize: (data[13] << 16) | (data[14] << 8) | data[15],
maxBitrate: (data[16] << 24) |
(data[17] << 16) |
(data[18] << 8) |
data[19],
avgBitrate: (data[20] << 24) |
(data[21] << 16) |
(data[22] << 8) |
data[23]
}
};
},
ftyp: function(data) {
var
view = new DataView(data.buffer, data.byteOffset, data.byteLength),
result = {
majorBrand: parseType(data.subarray(0, 4)),
minorVersion: view.getUint32(4),
compatibleBrands: []
},
i = 8;
while (i < data.byteLength) {
result.compatibleBrands.push(parseType(data.subarray(i, i + 4)));
i += 4;
}
return result;
},
dinf: function(data) {
return {
boxes: videojs.inspectMp4(data)
};
},
dref: function(data) {
return {
version: data[0],
flags: new Uint8Array(data.subarray(1, 4)),
dataReferences: videojs.inspectMp4(data.subarray(8))
};
},
hdlr: function(data) {
var
view = new DataView(data.buffer, data.byteOffset, data.byteLength),
language,
result = {
version: view.getUint8(0),
flags: new Uint8Array(data.subarray(1, 4)),
handlerType: parseType(data.subarray(8, 12)),
name: ''
},
i = 8;
// parse out the name field
for (i = 24; i < data.byteLength; i++) {
if (data[i] === 0x00) {
// the name field is null-terminated
i++;
break;
}
result.name += String.fromCharCode(data[i]);
}
// decode UTF-8 to javascript's internal representation
// see http://ecmanaut.blogspot.com/2006/07/encoding-decoding-utf8-in-javascript.html
result.name = window.decodeURIComponent(window.escape(result.name));
return result;
},
mdat: function(data) {
return {
byteLength: data.byteLength,
nals: nalParse(data)
};
},
mdhd: function(data) {
var
view = new DataView(data.buffer, data.byteOffset, data.byteLength),
i = 4,
language,
result = {
version: view.getUint8(0),
flags: new Uint8Array(data.subarray(1, 4)),
language: ''
};
if (result.version === 1) {
i += 4;
result.creationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
i += 8;
result.modificationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
i += 4;
result.timescale = view.getUint32(i);
i += 8;
result.duration = view.getUint32(i); // truncating top 4 bytes
} else {
result.creationTime = parseMp4Date(view.getUint32(i));
i += 4;
result.modificationTime = parseMp4Date(view.getUint32(i));
i += 4;
result.timescale = view.getUint32(i);
i += 4;
result.duration = view.getUint32(i);
}
i += 4;
// language is stored as an ISO-639-2/T code in an array of three 5-bit fields
// each field is the packed difference between its ASCII value and 0x60
language = view.getUint16(i);
result.language += String.fromCharCode((language >> 10) + 0x60);
result.language += String.fromCharCode(((language & 0x03c0) >> 5) + 0x60);
result.language += String.fromCharCode((language & 0x1f) + 0x60);
return result;
},
mdia: function(data) {
return {
boxes: videojs.inspectMp4(data)
};
},
mfhd: function(data) {
return {
version: data[0],
flags: new Uint8Array(data.subarray(1, 4)),
sequenceNumber: (data[4] << 24) |
(data[5] << 16) |
(data[6] << 8) |
(data[7])
};
},
minf: function(data) {
return {
boxes: videojs.inspectMp4(data)
};
},
// codingname, not a first-class box type. stsd entries share the
// same format as real boxes so the parsing infrastructure can be
// shared
mp4a: function(data) {
var
view = new DataView(data.buffer, data.byteOffset, data.byteLength),
result = {
// 6 bytes reserved
dataReferenceIndex: view.getUint16(6),
// 4 + 4 bytes reserved
channelcount: view.getUint16(16),
samplesize: view.getUint16(18),
// 2 bytes pre_defined
// 2 bytes reserved
samplerate: view.getUint16(24) + (view.getUint16(26) / 65536)
};
// if there are more bytes to process, assume this is an ISO/IEC
// 14496-14 MP4AudioSampleEntry and parse the ESDBox
if (data.byteLength > 28) {
result.streamDescriptor = videojs.inspectMp4(data.subarray(28))[0];
}
return result;
},
moof: function(data) {
return {
boxes: videojs.inspectMp4(data)
};
},
moov: function(data) {
return {
boxes: videojs.inspectMp4(data)
};
},
mvex: function(data) {
return {
boxes: videojs.inspectMp4(data)
};
},
mvhd: function(data) {
var
view = new DataView(data.buffer, data.byteOffset, data.byteLength),
i = 4,
result = {
version: view.getUint8(0),
flags: new Uint8Array(data.subarray(1, 4))
};
if (result.version === 1) {
i += 4;
result.creationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
i += 8;
result.modificationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
i += 4;
result.timescale = view.getUint32(i);
i += 8
result.duration = view.getUint32(i); // truncating top 4 bytes
} else {
result.creationTime = parseMp4Date(view.getUint32(i));
i += 4;
result.modificationTime = parseMp4Date(view.getUint32(i));
i += 4;
result.timescale = view.getUint32(i);
i += 4;
result.duration = view.getUint32(i);
}
i += 4;
// convert fixed-point, base 16 back to a number
result.rate = view.getUint16(i) + (view.getUint16(i + 2) / 16);
i += 4;
result.volume = view.getUint8(i) + (view.getUint8(i + 1) / 8);
i += 2;
i += 2;
i += 2 * 4;
result.matrix = new Uint32Array(data.subarray(i, i + (9 * 4)));
i += 9 * 4;
i += 6 * 4;
result.nextTrackId = view.getUint32(i);
return result;
},
pdin: function(data) {
var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
return {
version: view.getUint8(0),
flags: new Uint8Array(data.subarray(1, 4)),
rate: view.getUint32(4),
initialDelay: view.getUint32(8)
};
},
sdtp: function(data) {
var
result = {
version: data[0],
flags: new Uint8Array(data.subarray(1, 4)),
samples: []
}, i;
for (i = 4; i < data.byteLength; i++) {
result.samples.push({
dependsOn: (data[i] & 0x30) >> 4,
isDependedOn: (data[i] & 0x0c) >> 2,
hasRedundancy: data[i] & 0x03
});
}
return result;
},
sidx: function(data) {
var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
result = {
version: data[0],
flags: new Uint8Array(data.subarray(1, 4)),
references: [],
referenceId: view.getUint32(4),
timescale: view.getUint32(8),
earliestPresentationTime: view.getUint32(12),
firstOffset: view.getUint32(16)
},
referenceCount = view.getUint16(22),
i;
for (i = 24; referenceCount; i += 12, referenceCount-- ) {
result.references.push({
referenceType: (data[i] & 0x80) >>> 7,
referencedSize: view.getUint32(i) & 0x7FFFFFFF,
subsegmentDuration: view.getUint32(i + 4),
startsWithSap: !!(data[i + 8] & 0x80),
sapType: (data[i + 8] & 0x70) >>> 4,
sapDeltaTime: view.getUint32(i + 8) & 0x0FFFFFFF
});
}
return result;
},
smhd: function(data) {
return {
version: data[0],
flags: new Uint8Array(data.subarray(1, 4)),
balance: data[4] + (data[5] / 256)
};
},
stbl: function(data) {
return {
boxes: videojs.inspectMp4(data)
};
},
stco: function(data) {
var
view = new DataView(data.buffer, data.byteOffset, data.byteLength),
result = {
version: data[0],
flags: new Uint8Array(data.subarray(1, 4)),
chunkOffsets: []
},
entryCount = view.getUint32(4),
i;
for (i = 8; entryCount; i += 4, entryCount--) {
result.chunkOffsets.push(view.getUint32(i));
}
return result;
},
stsc: function(data) {
var
view = new DataView(data.buffer, data.byteOffset, data.byteLength),
entryCount = view.getUint32(4),
result = {
version: data[0],
flags: new Uint8Array(data.subarray(1, 4)),
sampleToChunks: []
},
i;
for (i = 8; entryCount; i += 12, entryCount--) {
result.sampleToChunks.push({
firstChunk: view.getUint32(i),
samplesPerChunk: view.getUint32(i + 4),
sampleDescriptionIndex: view.getUint32(i + 8)
});
}
return result;
},
stsd: function(data) {
return {
version: data[0],
flags: new Uint8Array(data.subarray(1, 4)),
sampleDescriptions: videojs.inspectMp4(data.subarray(8))
};
},
stsz: function(data) {
var
view = new DataView(data.buffer, data.byteOffset, data.byteLength),
result = {
version: data[0],
flags: new Uint8Array(data.subarray(1, 4)),
sampleSize: view.getUint32(4),
entries: []
},
i;
for (i = 12; i < data.byteLength; i += 4) {
result.entries.push(view.getUint32(i));
}
return result;
},
stts: function(data) {
var
view = new DataView(data.buffer, data.byteOffset, data.byteLength),
result = {
version: data[0],
flags: new Uint8Array(data.subarray(1, 4)),
timeToSamples: []
},
entryCount = view.getUint32(4),
i;
for (i = 8; entryCount; i += 8, entryCount--) {
result.timeToSamples.push({
sampleCount: view.getUint32(i),
sampleDelta: view.getUint32(i + 4)
});
}
return result;
},
styp: function(data) {
return parse.ftyp(data);
},
tfdt: function(data) {
return {
version: data[0],
flags: new Uint8Array(data.subarray(1, 4)),
baseMediaDecodeTime: data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7]
};
},
tfhd: function(data) {
var
view = new DataView(data.buffer, data.byteOffset, data.byteLength),
result = {
version: data[0],
flags: new Uint8Array(data.subarray(1, 4)),
trackId: view.getUint32(4)
},
baseDataOffsetPresent = result.flags[2] & 0x01,
sampleDescriptionIndexPresent = result.flags[2] & 0x02,
defaultSampleDurationPresent = result.flags[2] & 0x08,
defaultSampleSizePresent = result.flags[2] & 0x10,
defaultSampleFlagsPresent = result.flags[2] & 0x20,
i;
i = 8;
if (baseDataOffsetPresent) {
i += 4; // truncate top 4 bytes
result.baseDataOffset = view.getUint32(12);
i += 4;
}
if (sampleDescriptionIndexPresent) {
result.sampleDescriptionIndex = view.getUint32(i);
i += 4;
}
if (defaultSampleDurationPresent) {
result.defaultSampleDuration = view.getUint32(i);
i += 4;
}
if (defaultSampleSizePresent) {
result.defaultSampleSize = view.getUint32(i);
i += 4;
}
if (defaultSampleFlagsPresent) {
result.defaultSampleFlags = view.getUint32(i);
}
return result;
},
tkhd: function(data) {
var
view = new DataView(data.buffer, data.byteOffset, data.byteLength),
i = 4,
result = {
version: view.getUint8(0),
flags: new Uint8Array(data.subarray(1, 4)),
};
if (result.version === 1) {
i += 4;
result.creationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
i += 8;
result.modificationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
i += 4;
result.trackId = view.getUint32(i);
i += 4;
i += 8;
result.duration = view.getUint32(i); // truncating top 4 bytes
} else {
result.creationTime = parseMp4Date(view.getUint32(i));
i += 4;
result.modificationTime = parseMp4Date(view.getUint32(i));
i += 4;
result.trackId = view.getUint32(i);
i += 4;
i += 4;
result.duration = view.getUint32(i);
}
i += 4;
i += 2 * 4;
result.layer = view.getUint16(i);
i += 2;
result.alternateGroup = view.getUint16(i);
i += 2;
// convert fixed-point, base 16 back to a number
result.volume = view.getUint8(i) + (view.getUint8(i + 1) / 8);
i += 2;
i += 2;
result.matrix = new Uint32Array(data.subarray(i, i + (9 * 4)));
i += 9 * 4;
result.width = view.getUint16(i) + (view.getUint16(i + 2) / 16);
i += 4;
result.height = view.getUint16(i) + (view.getUint16(i + 2) / 16);
return result;
},
traf: function(data) {
return {
boxes: videojs.inspectMp4(data)
};
},
trak: function(data) {
return {
boxes: videojs.inspectMp4(data)
};
},
trex: function(data) {
var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
return {
version: data[0],
flags: new Uint8Array(data.subarray(1, 4)),
trackId: view.getUint32(4),
defaultSampleDescriptionIndex: view.getUint32(8),
defaultSampleDuration: view.getUint32(12),
defaultSampleSize: view.getUint32(16),
sampleDependsOn: data[20] & 0x03,
sampleIsDependedOn: (data[21] & 0xc0) >> 6,
sampleHasRedundancy: (data[21] & 0x30) >> 4,
samplePaddingValue: (data[21] & 0x0e) >> 1,
sampleIsDifferenceSample: !!(data[21] & 0x01),
sampleDegradationPriority: view.getUint16(22)
};
},
trun: function(data) {
var
result = {
version: data[0],
flags: new Uint8Array(data.subarray(1, 4)),
samples: []
},
view = new DataView(data.buffer, data.byteOffset, data.byteLength),
dataOffsetPresent = result.flags[2] & 0x01,
firstSampleFlagsPresent = result.flags[2] & 0x04,
sampleDurationPresent = result.flags[1] & 0x01,
sampleSizePresent = result.flags[1] & 0x02,
sampleFlagsPresent = result.flags[1] & 0x04,
sampleCompositionTimeOffsetPresent = result.flags[1] & 0x08,
sampleCount = view.getUint32(4),
offset = 8,
sample;
if (dataOffsetPresent) {
result.dataOffset = view.getUint32(offset);
offset += 4;
}
if (firstSampleFlagsPresent && sampleCount) {
sample = {
flags: parseSampleFlags(data.subarray(offset, offset + 4))
};
offset += 4;
if (sampleDurationPresent) {
sample.duration = view.getUint32(offset);
offset += 4;
}
if (sampleSizePresent) {
sample.size = view.getUint32(offset);
offset += 4;
}
if (sampleCompositionTimeOffsetPresent) {
sample.compositionTimeOffset = view.getUint32(offset);
offset += 4;
}
result.samples.push(sample);
sampleCount--;
};
while (sampleCount--) {
sample = {};
if (sampleDurationPresent) {
sample.duration = view.getUint32(offset);
offset += 4;
}
if (sampleSizePresent) {
sample.size = view.getUint32(offset);
offset += 4;
}
if (sampleFlagsPresent) {
sample.flags = parseSampleFlags(data.subarray(offset, offset + 4));
offset += 4;
}
if (sampleCompositionTimeOffsetPresent) {
sample.compositionTimeOffset = view.getUint32(offset);
offset += 4;
}
result.samples.push(sample);
}
return result;
},
'url ': function(data) {
return {
version: data[0],
flags: new Uint8Array(data.subarray(1, 4))
};
},
vmhd: function(data) {
var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
return {
version: data[0],
flags: new Uint8Array(data.subarray(1, 4)),
graphicsmode: view.getUint16(4),
opcolor: new Uint16Array([view.getUint16(6),
view.getUint16(8),
view.getUint16(10)])
};
}
};
/**
* Return a javascript array of box objects parsed from an ISO base
* media file.
* @param data {Uint8Array} the binary data of the media to be inspected
* @return {array} a javascript array of potentially nested box objects
*/
videojs.inspectMp4 = function(data) {
var
i = 0,
result = [],
view = new DataView(data.buffer, data.byteOffset, data.byteLength),
size,
type,
end,
box;
while (i < data.byteLength) {
// parse box data
size = view.getUint32(i),
type = parseType(data.subarray(i + 4, i + 8));
end = size > 1 ? i + size : data.byteLength;
// parse type-specific data
box = (parse[type] || function(data) {
return {
data: data
};
})(data.subarray(i + 8, end));
box.size = size;
box.type = type;
// store this box and move to the next
result.push(box);
i = end;
}
return result;
};
/**
* Returns a textual representation of the javascript represtentation
* of an MP4 file. You can use it as an alternative to
* JSON.stringify() to compare inspected MP4s.
* @param inspectedMp4 {array} the parsed array of boxes in an MP4
* file
* @param depth {number} (optional) the number of ancestor boxes of
* the elements of inspectedMp4. Assumed to be zero if unspecified.
* @return {string} a text representation of the parsed MP4
*/
videojs.textifyMp4 = function(inspectedMp4, depth) {
var indent;
depth = depth || 0;
indent = Array(depth * 2 + 1).join(' ');
// iterate over all the boxes
return inspectedMp4.map(function(box, index) {
// list the box type first at the current indentation level
return indent + box.type + '\n' +
// the type is already included and handle child boxes separately
Object.keys(box).filter(function(key) {
return key !== 'type' && key !== 'boxes';
// output all the box properties
}).map(function(key) {
var prefix = indent + ' ' + key + ': ',
value = box[key];
// print out raw bytes as hexademical
if (value instanceof Uint8Array || value instanceof Uint32Array) {
var bytes = Array.prototype.slice.call(new Uint8Array(value.buffer, value.byteOffset, value.byteLength))
.map(function(byte) {
return ' ' + ('00' + byte.toString(16)).slice(-2);
}).join('').match(/.{1,24}/g);
if (!bytes) {
return prefix + '<>';
}
if (bytes.length === 1) {
return prefix + '<' + bytes.join('').slice(1) + '>';
}
return prefix + '<\n' + bytes.map(function(line) {
return indent + ' ' + line;
}).join('\n') + '\n' + indent + ' >';
}
// stringify generic objects
return prefix +
JSON.stringify(value, null, 2)
.split('\n').map(function(line, index) {
if (index === 0) {
return line;
}
return indent + ' ' + line;
}).join('\n');
}).join('\n') +
// recursively textify the child boxes
(box.boxes ? '\n' + videojs.textifyMp4(box.boxes, depth + 1) : '');
}).join('\n');
};
})(window, window.videojs);
/* Modernizr 2.6.2 (Custom Build) | MIT & BSD
* Build: http://modernizr.com/download/#-fontface-backgroundsize-borderimage-borderradius-boxshadow-flexbox-hsla-multiplebgs-opacity-rgba-textshadow-cssanimations-csscolumns-generatedcontent-cssgradients-cssreflections-csstransforms-csstransforms3d-csstransitions-applicationcache-canvas-canvastext-draganddrop-hashchange-history-audio-video-indexeddb-input-inputtypes-localstorage-postmessage-sessionstorage-websockets-websqldatabase-webworkers-geolocation-inlinesvg-smil-svg-svgclippaths-touch-webgl-shiv-mq-cssclasses-addtest-prefixed-teststyles-testprop-testallprops-hasevent-prefixes-domprefixes-load
*/
;window.Modernizr=function(a,b,c){function D(a){j.cssText=a}function E(a,b){return D(n.join(a+";")+(b||""))}function F(a,b){return typeof a===b}function G(a,b){return!!~(""+a).indexOf(b)}function H(a,b){for(var d in a){var e=a[d];if(!G(e,"-")&&j[e]!==c)return b=="pfx"?e:!0}return!1}function I(a,b,d){for(var e in a){var f=b[a[e]];if(f!==c)return d===!1?a[e]:F(f,"function")?f.bind(d||b):f}return!1}function J(a,b,c){var d=a.charAt(0).toUpperCase()+a.slice(1),e=(a+" "+p.join(d+" ")+d).split(" ");return F(b,"string")||F(b,"undefined")?H(e,b):(e=(a+" "+q.join(d+" ")+d).split(" "),I(e,b,c))}function K(){e.input=function(c){for(var d=0,e=c.length;d<e;d++)u[c[d]]=c[d]in k;return u.list&&(u.list=!!b.createElement("datalist")&&!!a.HTMLDataListElement),u}("autocomplete autofocus list placeholder max min multiple pattern required step".split(" ")),e.inputtypes=function(a){for(var d=0,e,f,h,i=a.length;d<i;d++)k.setAttribute("type",f=a[d]),e=k.type!=="text",e&&(k.value=l,k.style.cssText="position:absolute;visibility:hidden;",/^range$/.test(f)&&k.style.WebkitAppearance!==c?(g.appendChild(k),h=b.defaultView,e=h.getComputedStyle&&h.getComputedStyle(k,null).WebkitAppearance!=="textfield"&&k.offsetHeight!==0,g.removeChild(k)):/^(search|tel)$/.test(f)||(/^(url|email)$/.test(f)?e=k.checkValidity&&k.checkValidity()===!1:e=k.value!=l)),t[a[d]]=!!e;return t}("search tel url email datetime date month week time datetime-local number range color".split(" "))}var d="2.6.2",e={},f=!0,g=b.documentElement,h="modernizr",i=b.createElement(h),j=i.style,k=b.createElement("input"),l=":)",m={}.toString,n=" -webkit- -moz- -o- -ms- ".split(" "),o="Webkit Moz O ms",p=o.split(" "),q=o.toLowerCase().split(" "),r={svg:"http://www.w3.org/2000/svg"},s={},t={},u={},v=[],w=v.slice,x,y=function(a,c,d,e){var f,i,j,k,l=b.createElement("div"),m=b.body,n=m||b.createElement("body");if(parseInt(d,10))while(d--)j=b.createElement("div"),j.id=e?e[d]:h+(d+1),l.appendChild(j);return f=["&#173;",'<style id="s',h,'">',a,"</style>"].join(""),l.id=h,(m?l:n).innerHTML+=f,n.appendChild(l),m||(n.style.background="",n.style.overflow="hidden",k=g.style.overflow,g.style.overflow="hidden",g.appendChild(n)),i=c(l,a),m?l.parentNode.removeChild(l):(n.parentNode.removeChild(n),g.style.overflow=k),!!i},z=function(b){var c=a.matchMedia||a.msMatchMedia;if(c)return c(b).matches;var d;return y("@media "+b+" { #"+h+" { position: absolute; } }",function(b){d=(a.getComputedStyle?getComputedStyle(b,null):b.currentStyle)["position"]=="absolute"}),d},A=function(){function d(d,e){e=e||b.createElement(a[d]||"div"),d="on"+d;var f=d in e;return f||(e.setAttribute||(e=b.createElement("div")),e.setAttribute&&e.removeAttribute&&(e.setAttribute(d,""),f=F(e[d],"function"),F(e[d],"undefined")||(e[d]=c),e.removeAttribute(d))),e=null,f}var a={select:"input",change:"input",submit:"form",reset:"form",error:"img",load:"img",abort:"img"};return d}(),B={}.hasOwnProperty,C;!F(B,"undefined")&&!F(B.call,"undefined")?C=function(a,b){return B.call(a,b)}:C=function(a,b){return b in a&&F(a.constructor.prototype[b],"undefined")},Function.prototype.bind||(Function.prototype.bind=function(b){var c=this;if(typeof c!="function")throw new TypeError;var d=w.call(arguments,1),e=function(){if(this instanceof e){var a=function(){};a.prototype=c.prototype;var f=new a,g=c.apply(f,d.concat(w.call(arguments)));return Object(g)===g?g:f}return c.apply(b,d.concat(w.call(arguments)))};return e}),s.flexbox=function(){return J("flexWrap")},s.canvas=function(){var a=b.createElement("canvas");return!!a.getContext&&!!a.getContext("2d")},s.canvastext=function(){return!!e.canvas&&!!F(b.createElement("canvas").getContext("2d").fillText,"function")},s.webgl=function(){return!!a.WebGLRenderingContext},s.touch=function(){var c;return"ontouchstart"in a||a.DocumentTouch&&b instanceof DocumentTouch?c=!0:y(["@media (",n.join("touch-enabled),("),h,")","{#modernizr{top:9px;position:absolute}}"].join(""),function(a){c=a.offsetTop===9}),c},s.geolocation=function(){return"geolocation"in navigator},s.postmessage=function(){return!!a.postMessage},s.websqldatabase=function(){return!!a.openDatabase},s.indexedDB=function(){return!!J("indexedDB",a)},s.hashchange=function(){return A("hashchange",a)&&(b.documentMode===c||b.documentMode>7)},s.history=function(){return!!a.history&&!!history.pushState},s.draganddrop=function(){var a=b.createElement("div");return"draggable"in a||"ondragstart"in a&&"ondrop"in a},s.websockets=function(){return"WebSocket"in a||"MozWebSocket"in a},s.rgba=function(){return D("background-color:rgba(150,255,150,.5)"),G(j.backgroundColor,"rgba")},s.hsla=function(){return D("background-color:hsla(120,40%,100%,.5)"),G(j.backgroundColor,"rgba")||G(j.backgroundColor,"hsla")},s.multiplebgs=function(){return D("background:url(https://),url(https://),red url(https://)"),/(url\s*\(.*?){3}/.test(j.background)},s.backgroundsize=function(){return J("backgroundSize")},s.borderimage=function(){return J("borderImage")},s.borderradius=function(){return J("borderRadius")},s.boxshadow=function(){return J("boxShadow")},s.textshadow=function(){return b.createElement("div").style.textShadow===""},s.opacity=function(){return E("opacity:.55"),/^0.55$/.test(j.opacity)},s.cssanimations=function(){return J("animationName")},s.csscolumns=function(){return J("columnCount")},s.cssgradients=function(){var a="background-image:",b="gradient(linear,left top,right bottom,from(#9f9),to(white));",c="linear-gradient(left top,#9f9, white);";return D((a+"-webkit- ".split(" ").join(b+a)+n.join(c+a)).slice(0,-a.length)),G(j.backgroundImage,"gradient")},s.cssreflections=function(){return J("boxReflect")},s.csstransforms=function(){return!!J("transform")},s.csstransforms3d=function(){var a=!!J("perspective");return a&&"webkitPerspective"in g.style&&y("@media (transform-3d),(-webkit-transform-3d){#modernizr{left:9px;position:absolute;height:3px;}}",function(b,c){a=b.offsetLeft===9&&b.offsetHeight===3}),a},s.csstransitions=function(){return J("transition")},s.fontface=function(){var a;return y('@font-face {font-family:"font";src:url("https://")}',function(c,d){var e=b.getElementById("smodernizr"),f=e.sheet||e.styleSheet,g=f?f.cssRules&&f.cssRules[0]?f.cssRules[0].cssText:f.cssText||"":"";a=/src/i.test(g)&&g.indexOf(d.split(" ")[0])===0}),a},s.generatedcontent=function(){var a;return y(["#",h,"{font:0/0 a}#",h,':after{content:"',l,'";visibility:hidden;font:3px/1 a}'].join(""),function(b){a=b.offsetHeight>=3}),a},s.video=function(){var a=b.createElement("video"),c=!1;try{if(c=!!a.canPlayType)c=new Boolean(c),c.ogg=a.canPlayType('video/ogg; codecs="theora"').replace(/^no$/,""),c.h264=a.canPlayType('video/mp4; codecs="avc1.42E01E"').replace(/^no$/,""),c.webm=a.canPlayType('video/webm; codecs="vp8, vorbis"').replace(/^no$/,"")}catch(d){}return c},s.audio=function(){var a=b.createElement("audio"),c=!1;try{if(c=!!a.canPlayType)c=new Boolean(c),c.ogg=a.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/,""),c.mp3=a.canPlayType("audio/mpeg;").replace(/^no$/,""),c.wav=a.canPlayType('audio/wav; codecs="1"').replace(/^no$/,""),c.m4a=(a.canPlayType("audio/x-m4a;")||a.canPlayType("audio/aac;")).replace(/^no$/,"")}catch(d){}return c},s.localstorage=function(){try{return localStorage.setItem(h,h),localStorage.removeItem(h),!0}catch(a){return!1}},s.sessionstorage=function(){try{return sessionStorage.setItem(h,h),sessionStorage.removeItem(h),!0}catch(a){return!1}},s.webworkers=function(){return!!a.Worker},s.applicationcache=function(){return!!a.applicationCache},s.svg=function(){return!!b.createElementNS&&!!b.createElementNS(r.svg,"svg").createSVGRect},s.inlinesvg=function(){var a=b.createElement("div");return a.innerHTML="<svg/>",(a.firstChild&&a.firstChild.namespaceURI)==r.svg},s.smil=function(){return!!b.createElementNS&&/SVGAnimate/.test(m.call(b.createElementNS(r.svg,"animate")))},s.svgclippaths=function(){return!!b.createElementNS&&/SVGClipPath/.test(m.call(b.createElementNS(r.svg,"clipPath")))};for(var L in s)C(s,L)&&(x=L.toLowerCase(),e[x]=s[L](),v.push((e[x]?"":"no-")+x));return e.input||K(),e.addTest=function(a,b){if(typeof a=="object")for(var d in a)C(a,d)&&e.addTest(d,a[d]);else{a=a.toLowerCase();if(e[a]!==c)return e;b=typeof b=="function"?b():b,typeof f!="undefined"&&f&&(g.className+=" "+(b?"":"no-")+a),e[a]=b}return e},D(""),i=k=null,function(a,b){function k(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x<style>"+b+"</style>",d.insertBefore(c.lastChild,d.firstChild)}function l(){var a=r.elements;return typeof a=="string"?a.split(" "):a}function m(a){var b=i[a[g]];return b||(b={},h++,a[g]=h,i[h]=b),b}function n(a,c,f){c||(c=b);if(j)return c.createElement(a);f||(f=m(c));var g;return f.cache[a]?g=f.cache[a].cloneNode():e.test(a)?g=(f.cache[a]=f.createElem(a)).cloneNode():g=f.createElem(a),g.canHaveChildren&&!d.test(a)?f.frag.appendChild(g):g}function o(a,c){a||(a=b);if(j)return a.createDocumentFragment();c=c||m(a);var d=c.frag.cloneNode(),e=0,f=l(),g=f.length;for(;e<g;e++)d.createElement(f[e]);return d}function p(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return r.shivMethods?n(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+l().join().replace(/\w+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(r,b.frag)}function q(a){a||(a=b);var c=m(a);return r.shivCSS&&!f&&!c.hasCSS&&(c.hasCSS=!!k(a,"article,aside,figcaption,figure,footer,header,hgroup,nav,section{display:block}mark{background:#FF0;color:#000}")),j||p(a,c),a}var c=a.html5||{},d=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,e=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,f,g="_html5shiv",h=0,i={},j;(function(){try{var a=b.createElement("a");a.innerHTML="<xyz></xyz>",f="hidden"in a,j=a.childNodes.length==1||function(){b.createElement("a");var a=b.createDocumentFragment();return typeof a.cloneNode=="undefined"||typeof a.createDocumentFragment=="undefined"||typeof a.createElement=="undefined"}()}catch(c){f=!0,j=!0}})();var r={elements:c.elements||"abbr article aside audio bdi canvas data datalist details figcaption figure footer header hgroup mark meter nav output progress section summary time video",shivCSS:c.shivCSS!==!1,supportsUnknownElements:j,shivMethods:c.shivMethods!==!1,type:"default",shivDocument:q,createElement:n,createDocumentFragment:o};a.html5=r,q(b)}(this,b),e._version=d,e._prefixes=n,e._domPrefixes=q,e._cssomPrefixes=p,e.mq=z,e.hasEvent=A,e.testProp=function(a){return H([a])},e.testAllProps=J,e.testStyles=y,e.prefixed=function(a,b,c){return b?J(a,b,c):J(a,"pfx")},g.className=g.className.replace(/(^|\s)no-js(\s|$)/,"$1$2")+(f?" js "+v.join(" "):""),e}(this,this.document),function(a,b,c){function d(a){return"[object Function]"==o.call(a)}function e(a){return"string"==typeof a}function f(){}function g(a){return!a||"loaded"==a||"complete"==a||"uninitialized"==a}function h(){var a=p.shift();q=1,a?a.t?m(function(){("c"==a.t?B.injectCss:B.injectJs)(a.s,0,a.a,a.x,a.e,1)},0):(a(),h()):q=0}function i(a,c,d,e,f,i,j){function k(b){if(!o&&g(l.readyState)&&(u.r=o=1,!q&&h(),l.onload=l.onreadystatechange=null,b)){"img"!=a&&m(function(){t.removeChild(l)},50);for(var d in y[c])y[c].hasOwnProperty(d)&&y[c][d].onload()}}var j=j||B.errorTimeout,l=b.createElement(a),o=0,r=0,u={t:d,s:c,e:f,a:i,x:j};1===y[c]&&(r=1,y[c]=[]),"object"==a?l.data=c:(l.src=c,l.type=a),l.width=l.height="0",l.onerror=l.onload=l.onreadystatechange=function(){k.call(this,r)},p.splice(e,0,u),"img"!=a&&(r||2===y[c]?(t.insertBefore(l,s?null:n),m(k,j)):y[c].push(l))}function j(a,b,c,d,f){return q=0,b=b||"j",e(a)?i("c"==b?v:u,a,b,this.i++,c,d,f):(p.splice(this.i++,0,a),1==p.length&&h()),this}function k(){var a=B;return a.loader={load:j,i:0},a}var l=b.documentElement,m=a.setTimeout,n=b.getElementsByTagName("script")[0],o={}.toString,p=[],q=0,r="MozAppearance"in l.style,s=r&&!!b.createRange().compareNode,t=s?l:n.parentNode,l=a.opera&&"[object Opera]"==o.call(a.opera),l=!!b.attachEvent&&!l,u=r?"object":l?"script":"img",v=l?"script":u,w=Array.isArray||function(a){return"[object Array]"==o.call(a)},x=[],y={},z={timeout:function(a,b){return b.length&&(a.timeout=b[0]),a}},A,B;B=function(a){function b(a){var a=a.split("!"),b=x.length,c=a.pop(),d=a.length,c={url:c,origUrl:c,prefixes:a},e,f,g;for(f=0;f<d;f++)g=a[f].split("="),(e=z[g.shift()])&&(c=e(c,g));for(f=0;f<b;f++)c=x[f](c);return c}function g(a,e,f,g,h){var i=b(a),j=i.autoCallback;i.url.split(".").pop().split("?").shift(),i.bypass||(e&&(e=d(e)?e:e[a]||e[g]||e[a.split("/").pop().split("?")[0]]),i.instead?i.instead(a,e,f,g,h):(y[i.url]?i.noexec=!0:y[i.url]=1,f.load(i.url,i.forceCSS||!i.forceJS&&"css"==i.url.split(".").pop().split("?").shift()?"c":c,i.noexec,i.attrs,i.timeout),(d(e)||d(j))&&f.load(function(){k(),e&&e(i.origUrl,h,g),j&&j(i.origUrl,h,g),y[i.url]=2})))}function h(a,b){function c(a,c){if(a){if(e(a))c||(j=function(){var a=[].slice.call(arguments);k.apply(this,a),l()}),g(a,j,b,0,h);else if(Object(a)===a)for(n in m=function(){var b=0,c;for(c in a)a.hasOwnProperty(c)&&b++;return b}(),a)a.hasOwnProperty(n)&&(!c&&!--m&&(d(j)?j=function(){var a=[].slice.call(arguments);k.apply(this,a),l()}:j[n]=function(a){return function(){var b=[].slice.call(arguments);a&&a.apply(this,b),l()}}(k[n])),g(a[n],j,b,n,h))}else!c&&l()}var h=!!a.test,i=a.load||a.both,j=a.callback||f,k=j,l=a.complete||f,m,n;c(h?a.yep:a.nope,!!i),i&&c(i)}var i,j,l=this.yepnope.loader;if(e(a))g(a,0,l,0);else if(w(a))for(i=0;i<a.length;i++)j=a[i],e(j)?g(j,0,l,0):w(j)?B(j):Object(j)===j&&h(j,l);else Object(a)===a&&h(a,l)},B.addPrefix=function(a,b){z[a]=b},B.addFilter=function(a){x.push(a)},B.errorTimeout=1e4,null==b.readyState&&b.addEventListener&&(b.readyState="loading",b.addEventListener("DOMContentLoaded",A=function(){b.removeEventListener("DOMContentLoaded",A,0),b.readyState="complete"},0)),a.yepnope=k(),a.yepnope.executeStack=h,a.yepnope.injectJs=function(a,c,d,e,i,j){var k=b.createElement("script"),l,o,e=e||B.errorTimeout;k.src=a;for(o in d)k.setAttribute(o,d[o]);c=j?h:c||f,k.onreadystatechange=k.onload=function(){!l&&g(k.readyState)&&(l=1,c(),k.onload=k.onreadystatechange=null)},m(function(){l||(l=1,c(1))},e),i?k.onload():n.parentNode.insertBefore(k,n)},a.yepnope.injectCss=function(a,c,d,e,g,i){var e=b.createElement("link"),j,c=i?h:c||f;e.href=a,e.rel="stylesheet",e.type="text/css";for(j in d)e.setAttribute(j,d[j]);g||(n.parentNode.insertBefore(e,n),m(c,0))}}(this,document),Modernizr.load=function(){yepnope.apply(window,[].slice.call(arguments,0))};
// a minimal set of boxes assembled to form a valid BMFF media segment
var mfhd = [
0, 0, 0, 16, 109, 102, 104, 100, 0, 0, 0, 0, 0, 0, 0, 1
]; // sequence number 1
var tfhd = [
0, 0, 0, 16, 116, 102, 104, 100, 0, 2, 0, 0, 0, 0, 0, 1
]; // track id 1
var tfdt = [
0, 0, 0, 16, 116, 102, 100, 116, 0, 0, 0, 0, 0, 0, 0, 0
];
var trun = [
0, 0, 0, 124, 116, 114, 117, 110, 0, 0, 2, 5, 0, 0, 0, 25, 0, 0, 0,
196, 0, 0, 0, 0, 0, 0, 12, 219, 0, 0, 0, 183, 0, 0, 0, 172, 0, 0, 0,
182, 0, 0, 0, 183, 0, 0, 0, 198, 0, 0, 0, 146, 0, 0, 0, 176, 0, 0,
0, 190, 0, 0, 0, 169, 0, 0, 0, 243, 0, 0, 0, 174, 0, 0, 0, 131, 0,
0, 0, 161, 0, 0, 0, 144, 0, 0, 0, 163, 0, 0, 0, 128, 0, 0, 0, 174,
0, 0, 0, 194, 0, 0, 0, 133, 0, 0, 0, 183, 0, 0, 0, 154, 0, 0, 0,
150, 0, 0, 0, 169, 0, 0, 0, 157
];
var mdat = [
0, 0, 28, 188, 109, 100, 97, 116, 0, 0, 2, 151, 6, 5, 255, 255, 147,
220, 69, 233, 189, 230, 217, 72, 183, 150, 44, 216, 32, 217, 35, 238,
239, 120, 50, 54, 52, 32, 45, 32, 99, 111, 114, 101, 32, 49, 49, 56,
32, 114, 50, 48, 56, 53, 32, 56, 97, 54, 50, 56, 51, 53, 32, 45, 32,
72, 46, 50, 54, 52, 47, 77, 80, 69, 71, 45, 52, 32, 65, 86, 67, 32,
99, 111, 100, 101, 99, 32, 45, 32, 67, 111, 112, 121, 108, 101, 102,
116, 32, 50, 48, 48, 51, 45, 50, 48, 49, 49, 32, 45, 32, 104, 116,
116, 112, 58, 47, 47, 119, 119, 119, 46, 118, 105, 100, 101, 111, 108,
97, 110, 46, 111, 114, 103, 47, 120, 50, 54, 52, 46, 104, 116, 109,
108, 32, 45, 32, 111, 112, 116, 105, 111, 110, 115, 58, 32, 99, 97,
98, 97, 99, 61, 48, 32, 114, 101, 102, 61, 51, 32, 100, 101, 98, 108,
111, 99, 107, 61, 49, 58, 48, 58, 48, 32, 97, 110, 97, 108, 121, 115,
101, 61, 48, 120, 49, 58, 48, 120, 49, 49, 49, 32, 109, 101, 61, 104,
101, 120, 32, 115, 117, 98, 109, 101, 61, 55, 32, 112, 115, 121, 61,
49, 32, 112, 115, 121, 95, 114, 100, 61, 49, 46, 48, 48, 58, 48, 46,
48, 48, 32, 109, 105, 120, 101, 100, 95, 114, 101, 102, 61, 49, 32,
109, 101, 95, 114, 97, 110, 103, 101, 61, 49, 54, 32, 99, 104, 114,
111, 109, 97, 95, 109, 101, 61, 49, 32, 116, 114, 101, 108, 108, 105,
115, 61, 49, 32, 56, 120, 56, 100, 99, 116, 61, 48, 32, 99, 113, 109,
61, 48, 32, 100, 101, 97, 100, 122, 111, 110, 101, 61, 50, 49, 44, 49,
49, 32, 102, 97, 115, 116, 95, 112, 115, 107, 105, 112, 61, 49, 32,
99, 104, 114, 111, 109, 97, 95, 113, 112, 95, 111, 102, 102, 115, 101,
116, 61, 45, 50, 32, 116, 104, 114, 101, 97, 100, 115, 61, 51, 32,
115, 108, 105, 99, 101, 100, 95, 116, 104, 114, 101, 97, 100, 115, 61,
48, 32, 110, 114, 61, 48, 32, 100, 101, 99, 105, 109, 97, 116, 101,
61, 49, 32, 105, 110, 116, 101, 114, 108, 97, 99, 101, 100, 61, 48,
32, 98, 108, 117, 114, 97, 121, 95, 99, 111, 109, 112, 97, 116, 61,
48, 32, 99, 111, 110, 115, 116, 114, 97, 105, 110, 101, 100, 95, 105,
110, 116, 114, 97, 61, 48, 32, 98, 102, 114, 97, 109, 101, 115, 61,
48, 32, 119, 101, 105, 103, 104, 116, 112, 61, 48, 32, 107, 101, 121,
105, 110, 116, 61, 50, 53, 32, 107, 101, 121, 105, 110, 116, 95, 109,
105, 110, 61, 49, 51, 32, 115, 99, 101, 110, 101, 99, 117, 116, 61,
52, 48, 32, 105, 110, 116, 114, 97, 95, 114, 101, 102, 114, 101, 115,
104, 61, 48, 32, 114, 99, 95, 108, 111, 111, 107, 97, 104, 101, 97,
100, 61, 52, 48, 32, 114, 99, 61, 99, 98, 114, 32, 109, 98, 116, 114,
101, 101, 61, 49, 32, 98, 105, 116, 114, 97, 116, 101, 61, 52, 56, 32,
114, 97, 116, 101, 116, 111, 108, 61, 49, 46, 48, 32, 113, 99, 111,
109, 112, 61, 48, 46, 54, 48, 32, 113, 112, 109, 105, 110, 61, 48, 32,
113, 112, 109, 97, 120, 61, 54, 57, 32, 113, 112, 115, 116, 101, 112,
61, 52, 32, 118, 98, 118, 95, 109, 97, 120, 114, 97, 116, 101, 61, 52,
56, 32, 118, 98, 118, 95, 98, 117, 102, 115, 105, 122, 101, 61, 50,
48, 48, 48, 32, 110, 97, 108, 95, 104, 114, 100, 61, 110, 111, 110,
101, 32, 105, 112, 95, 114, 97, 116, 105, 111, 61, 49, 46, 52, 48, 32,
97, 113, 61, 49, 58, 49, 46, 48, 48, 0, 128, 0, 0, 10, 60, 101, 136,
132, 25, 201, 6, 34, 128, 0, 195, 76, 156, 156, 156, 156, 156, 156,
156, 156, 156, 156, 156, 156, 156, 156, 156, 156, 156, 156, 157, 117,
215, 93, 117, 215, 93, 117, 215, 93, 117, 215, 93, 117, 215, 93, 117,
215, 93, 117, 215, 93, 117, 215, 95, 255, 222, 238, 80, 168, 38, 192,
35, 16, 144, 238, 68, 216, 105, 218, 55, 168, 0, 162, 196, 199, 82,
63, 12, 225, 73, 161, 167, 199, 63, 255, 119, 220, 120, 86, 40, 104,
195, 139, 254, 42, 238, 91, 250, 153, 255, 255, 85, 225, 81, 64, 72,
185, 204, 154, 55, 71, 225, 39, 244, 108, 42, 251, 175, 255, 254, 171,
134, 8, 60, 160, 192, 171, 24, 186, 235, 174, 186, 235, 174, 186, 235,
174, 186, 235, 254, 68, 82, 105, 120, 193, 48, 3, 216, 90, 173, 113,
18, 222, 1, 212, 208, 33, 57, 132, 214, 13, 165, 0, 49, 178, 166, 201,
246, 232, 31, 159, 114, 199, 225, 248, 120, 43, 94, 172, 137, 32, 75,
180, 11, 247, 223, 215, 60, 255, 225, 240, 208, 6, 179, 209, 69, 200,
98, 8, 78, 127, 56, 167, 193, 4, 10, 86, 12, 135, 128, 50, 171, 244,
132, 239, 23, 93, 117, 215, 93, 117, 255, 255, 174, 21, 39, 0, 34, 47,
146, 109, 95, 99, 121, 145, 164, 252, 245, 215, 93, 127, 218, 223,
102, 56, 80, 108, 162, 144, 4, 45, 36, 192, 200, 180, 217, 171, 107,
207, 0, 5, 118, 124, 132, 86, 222, 3, 228, 95, 255, 135, 132, 133, 95,
63, 201, 94, 237, 216, 117, 215, 93, 253, 52, 167, 211, 72, 160, 150,
220, 152, 252, 49, 23, 119, 68, 166, 120, 38, 182, 158, 46, 186, 235,
174, 186, 235, 237, 7, 14, 24, 92, 172, 85, 41, 52, 237, 176, 36, 7,
36, 243, 235, 141, 132, 30, 250, 171, 62, 170, 183, 14, 146, 109, 247,
254, 0, 216, 166, 193, 132, 65, 114, 198, 145, 17, 236, 4, 181, 80,
52, 183, 125, 238, 240, 182, 55, 155, 38, 215, 252, 231, 217, 97, 81,
252, 12, 81, 121, 32, 222, 64, 100, 123, 224, 117, 17, 49, 226, 133,
65, 77, 215, 128, 8, 79, 114, 61, 95, 127, 46, 199, 213, 220, 111,
165, 80, 103, 234, 179, 148, 4, 110, 94, 206, 245, 255, 196, 106, 207,
10, 148, 0, 134, 53, 178, 28, 75, 239, 243, 54, 131, 72, 65, 84, 194,
41, 207, 235, 70, 173, 59, 32, 134, 180, 210, 191, 128, 35, 215, 219,
55, 10, 106, 126, 109, 51, 86, 17, 157, 104, 20, 103, 247, 214, 44,
196, 132, 32, 28, 171, 255, 255, 206, 255, 213, 147, 97, 133, 76, 214,
102, 231, 231, 241, 72, 156, 56, 161, 87, 89, 7, 57, 85, 100, 65, 255,
32, 110, 253, 1, 25, 209, 163, 106, 105, 49, 152, 90, 243, 182, 81,
195, 130, 157, 24, 201, 118, 229, 255, 196, 106, 39, 10, 138, 240, 68,
214, 2, 163, 144, 173, 82, 203, 73, 174, 70, 10, 101, 231, 250, 63,
97, 37, 178, 3, 121, 219, 125, 96, 145, 38, 57, 34, 37, 63, 255, 240,
160, 68, 109, 118, 202, 61, 227, 203, 210, 102, 142, 153, 173, 76,
111, 174, 186, 235, 254, 31, 252, 40, 104, 20, 109, 73, 102, 245, 50,
154, 153, 215, 235, 174, 186, 235, 174, 190, 19, 221, 127, 225, 35,
195, 124, 193, 179, 16, 239, 119, 251, 25, 126, 181, 143, 140, 216,
122, 35, 194, 225, 107, 76, 143, 235, 124, 22, 30, 202, 27, 52, 188,
80, 232, 136, 132, 137, 94, 110, 191, 59, 210, 95, 223, 177, 114, 10,
191, 252, 37, 187, 154, 7, 140, 177, 57, 255, 61, 186, 4, 194, 206,
129, 208, 186, 187, 46, 52, 63, 207, 183, 245, 54, 237, 191, 235, 221,
35, 76, 197, 84, 140, 63, 186, 52, 41, 129, 82, 166, 149, 255, 25,
149, 23, 231, 53, 175, 219, 243, 232, 67, 11, 191, 252, 37, 252, 130,
111, 2, 166, 217, 10, 0, 94, 211, 194, 101, 84, 68, 17, 31, 35, 242,
222, 132, 139, 208, 130, 103, 95, 84, 113, 195, 68, 84, 93, 169, 150,
64, 204, 207, 71, 253, 211, 174, 141, 133, 58, 135, 205, 35, 27, 187,
30, 176, 178, 60, 125, 14, 145, 16, 145, 43, 206, 175, 130, 255, 194,
66, 51, 199, 59, 5, 57, 117, 243, 84, 108, 168, 126, 150, 206, 255,
93, 135, 142, 157, 191, 20, 156, 122, 213, 80, 172, 179, 98, 143, 80,
240, 154, 158, 36, 146, 159, 145, 86, 194, 237, 251, 110, 225, 169,
127, 194, 66, 29, 151, 107, 53, 213, 93, 120, 115, 180, 1, 184, 180,
21, 41, 249, 151, 77, 200, 172, 235, 159, 179, 35, 35, 123, 180, 189,
31, 190, 255, 79, 130, 82, 20, 8, 128, 212, 22, 177, 22, 61, 232, 247,
30, 120, 3, 79, 232, 150, 179, 54, 225, 239, 11, 174, 186, 235, 255,
183, 177, 182, 20, 52, 2, 228, 166, 105, 102, 193, 76, 2, 71, 210, 89,
246, 226, 238, 235, 245, 215, 93, 117, 215, 93, 119, 223, 125, 255,
210, 31, 240, 248, 94, 2, 25, 231, 73, 255, 255, 47, 210, 129, 224,
115, 207, 22, 18, 209, 12, 2, 92, 233, 134, 237, 191, 192, 43, 214,
64, 109, 19, 255, 8, 48, 37, 118, 186, 235, 255, 255, 224, 132, 188,
0, 126, 81, 156, 120, 8, 99, 90, 123, 107, 101, 41, 126, 19, 52, 38,
229, 238, 98, 61, 176, 47, 32, 11, 119, 32, 54, 137, 225, 215, 93,
117, 215, 93, 117, 215, 93, 117, 215, 95, 234, 112, 255, 135, 197,
224, 7, 75, 35, 133, 156, 62, 179, 243, 170, 233, 134, 170, 56, 177,
31, 4, 254, 52, 151, 134, 30, 112, 37, 243, 126, 6, 143, 41, 153, 143,
255, 250, 172, 84, 120, 32, 142, 236, 197, 21, 248, 182, 229, 187,
255, 254, 239, 194, 176, 33, 18, 156, 200, 41, 70, 241, 254, 52, 125,
104, 216, 72, 212, 245, 255, 238, 69, 191, 248, 75, 128, 27, 108, 204,
4, 130, 175, 242, 117, 231, 180, 128, 23, 218, 89, 154, 72, 63, 147,
174, 186, 235, 174, 186, 235, 175, 255, 244, 30, 20, 62, 0, 9, 26,
140, 152, 187, 140, 173, 72, 149, 57, 9, 16, 162, 250, 87, 66, 43, 56,
215, 99, 158, 154, 53, 171, 127, 255, 219, 76, 111, 60, 203, 19, 244,
247, 127, 143, 144, 0, 152, 42, 176, 51, 6, 179, 229, 217, 225, 160,
149, 17, 207, 16, 87, 78, 248, 9, 225, 95, 163, 21, 113, 70, 213, 129,
40, 107, 116, 216, 109, 178, 200, 144, 31, 117, 163, 49, 39, 243, 81,
141, 245, 71, 73, 230, 249, 246, 126, 239, 209, 240, 255, 168, 97,
180, 45, 192, 178, 117, 74, 198, 219, 174, 10, 140, 240, 22, 146, 16,
77, 184, 39, 91, 18, 251, 142, 15, 32, 71, 84, 247, 230, 161, 195,
240, 0, 168, 132, 78, 73, 49, 199, 205, 5, 192, 67, 254, 193, 46, 21,
175, 76, 229, 19, 0, 26, 100, 168, 43, 230, 37, 9, 66, 51, 3, 90, 66,
243, 186, 94, 187, 232, 26, 151, 217, 89, 85, 99, 255, 86, 166, 46,
38, 206, 80, 185, 255, 88, 94, 45, 103, 228, 8, 76, 122, 227, 98, 250,
34, 184, 114, 238, 200, 171, 26, 49, 98, 139, 255, 103, 87, 173, 64,
119, 90, 66, 176, 207, 147, 189, 132, 144, 105, 120, 183, 18, 140,
253, 105, 131, 221, 240, 202, 191, 246, 169, 111, 254, 223, 160, 138,
73, 34, 149, 138, 238, 62, 184, 175, 27, 224, 184, 253, 44, 134, 172,
190, 120, 26, 228, 92, 187, 202, 254, 192, 220, 79, 205, 151, 190,
216, 230, 3, 7, 219, 166, 104, 91, 206, 38, 255, 233, 85, 85, 143,
136, 231, 102, 196, 227, 158, 181, 26, 17, 82, 208, 205, 91, 9, 69,
255, 185, 238, 98, 122, 133, 85, 19, 132, 125, 95, 251, 34, 196, 231,
154, 186, 208, 233, 249, 168, 247, 255, 127, 106, 63, 32, 51, 67, 163,
1, 190, 176, 66, 92, 198, 212, 84, 186, 39, 254, 119, 31, 56, 251,
223, 4, 166, 108, 149, 89, 78, 236, 0, 180, 201, 156, 173, 94, 153,
111, 254, 252, 17, 108, 29, 69, 70, 221, 79, 156, 2, 201, 254, 41,
118, 45, 169, 255, 216, 93, 195, 83, 174, 18, 107, 125, 167, 192, 226,
217, 88, 184, 46, 127, 238, 193, 177, 38, 168, 69, 139, 159, 223, 114,
99, 37, 109, 32, 53, 38, 184, 207, 253, 205, 158, 205, 231, 141, 74,
171, 76, 139, 140, 193, 242, 246, 180, 56, 178, 144, 18, 162, 41, 16,
43, 255, 155, 120, 145, 25, 202, 221, 219, 239, 184, 14, 234, 58, 129,
128, 223, 214, 124, 110, 155, 150, 205, 118, 175, 251, 192, 14, 148,
58, 73, 44, 180, 15, 29, 201, 184, 122, 56, 103, 137, 231, 0, 45, 144,
127, 145, 96, 157, 75, 37, 3, 180, 104, 193, 173, 166, 114, 208, 214,
159, 223, 214, 123, 73, 163, 0, 5, 117, 61, 156, 14, 52, 198, 157,
135, 213, 215, 211, 130, 136, 185, 173, 120, 160, 245, 142, 245, 169,
208, 22, 106, 52, 233, 254, 183, 121, 49, 53, 105, 86, 96, 66, 175,
35, 148, 64, 126, 18, 240, 88, 215, 207, 255, 251, 73, 32, 112, 196,
160, 73, 215, 74, 223, 186, 240, 6, 64, 7, 80, 27, 252, 60, 91, 228,
206, 175, 251, 35, 194, 137, 161, 5, 148, 166, 55, 254, 195, 187, 101,
2, 100, 39, 246, 127, 205, 191, 128, 128, 243, 166, 153, 163, 24, 82,
210, 161, 152, 149, 209, 204, 101, 111, 187, 252, 105, 224, 236, 86,
165, 56, 18, 115, 255, 255, 19, 11, 171, 107, 34, 156, 76, 111, 152,
1, 108, 42, 188, 73, 174, 110, 189, 157, 143, 172, 198, 139, 246, 182,
99, 88, 234, 237, 171, 127, 161, 133, 33, 241, 63, 39, 31, 194, 101,
244, 58, 204, 82, 50, 28, 62, 166, 246, 128, 220, 58, 42, 111, 17, 86,
140, 183, 219, 133, 4, 72, 74, 141, 148, 103, 54, 62, 213, 36, 74, 68,
137, 87, 255, 208, 253, 117, 215, 93, 117, 215, 93, 117, 255, 255, 8,
66, 135, 225, 191, 64, 31, 51, 34, 6, 19, 247, 9, 99, 159, 227, 240,
8, 11, 56, 216, 140, 0, 171, 94, 141, 16, 247, 76, 216, 98, 241, 23,
172, 125, 190, 96, 61, 54, 112, 80, 180, 192, 176, 239, 166, 82, 195,
32, 168, 103, 107, 73, 19, 16, 119, 126, 122, 54, 227, 251, 62, 187,
145, 247, 164, 96, 213, 91, 12, 71, 194, 230, 205, 4, 77, 193, 43, 38,
167, 48, 98, 200, 67, 194, 185, 211, 85, 240, 127, 235, 140, 66, 34,
35, 190, 3, 243, 47, 131, 132, 172, 43, 63, 255, 85, 46, 12, 155, 217,
238, 172, 55, 73, 48, 115, 152, 251, 227, 16, 194, 71, 82, 93, 43, 20,
25, 8, 144, 50, 139, 168, 52, 255, 242, 75, 9, 170, 96, 249, 57, 6,
239, 15, 154, 49, 205, 152, 41, 18, 214, 74, 42, 204, 86, 193, 72,
239, 245, 120, 127, 195, 224, 24, 7, 10, 192, 157, 166, 143, 46, 49,
16, 103, 240, 10, 28, 252, 128, 38, 98, 217, 40, 152, 86, 240, 248,
65, 34, 153, 30, 175, 230, 88, 23, 251, 139, 26, 107, 20, 55, 246,
234, 167, 193, 135, 235, 50, 68, 92, 135, 184, 126, 0, 41, 205, 147,
8, 129, 210, 235, 224, 31, 154, 244, 105, 117, 255, 253, 224, 104,
210, 82, 92, 144, 252, 7, 24, 71, 87, 220, 90, 79, 141, 97, 177, 21,
191, 216, 120, 146, 204, 15, 63, 140, 157, 223, 73, 11, 152, 130, 71,
124, 161, 189, 225, 253, 132, 239, 73, 84, 11, 55, 230, 109, 209, 166,
131, 160, 248, 153, 53, 167, 193, 252, 84, 86, 133, 241, 126, 252,
103, 226, 229, 213, 170, 59, 131, 255, 246, 166, 96, 221, 56, 138, 82,
119, 81, 126, 210, 136, 228, 36, 142, 8, 148, 27, 191, 220, 34, 22,
228, 86, 109, 140, 200, 181, 129, 226, 39, 82, 198, 74, 115, 255, 43,
57, 17, 28, 243, 227, 119, 223, 73, 132, 73, 57, 22, 140, 177, 53,
246, 255, 143, 75, 122, 68, 86, 182, 39, 217, 212, 20, 152, 234, 175,
239, 251, 108, 128, 166, 114, 66, 141, 192, 137, 255, 193, 219, 140,
13, 81, 113, 178, 233, 155, 82, 69, 68, 113, 13, 204, 180, 31, 192,
12, 70, 64, 60, 68, 247, 212, 6, 130, 174, 179, 151, 13, 188, 29, 109,
18, 3, 119, 208, 43, 37, 115, 65, 5, 204, 144, 184, 33, 24, 69, 30,
254, 54, 205, 201, 63, 115, 80, 217, 127, 127, 244, 67, 49, 80, 247,
168, 139, 249, 83, 255, 123, 114, 1, 170, 237, 67, 99, 234, 219, 144,
215, 4, 18, 236, 169, 215, 251, 223, 213, 178, 113, 148, 52, 75, 191,
239, 242, 96, 125, 81, 77, 29, 40, 219, 89, 162, 35, 20, 198, 227,
120, 31, 248, 154, 52, 64, 85, 194, 230, 91, 22, 142, 6, 104, 173, 3,
77, 147, 26, 164, 111, 254, 42, 84, 1, 204, 72, 61, 140, 96, 5, 191,
5, 174, 85, 179, 228, 84, 8, 175, 2, 58, 99, 107, 14, 98, 44, 13, 255,
46, 67, 245, 94, 70, 125, 53, 90, 241, 209, 237, 160, 203, 225, 219,
3, 37, 181, 152, 21, 246, 12, 25, 243, 249, 20, 173, 224, 251, 164,
130, 119, 149, 151, 188, 64, 118, 134, 136, 243, 192, 253, 26, 236,
17, 244, 3, 6, 161, 56, 241, 241, 227, 58, 192, 119, 65, 2, 85, 69,
101, 194, 147, 105, 35, 212, 64, 201, 135, 43, 129, 87, 131, 254, 24,
153, 225, 6, 226, 250, 93, 242, 223, 246, 161, 232, 38, 235, 82, 149,
186, 208, 131, 255, 224, 119, 239, 223, 128, 8, 91, 254, 167, 223,
255, 193, 54, 146, 56, 131, 228, 158, 254, 240, 20, 219, 226, 181,
174, 62, 91, 97, 110, 120, 231, 111, 181, 135, 34, 0, 55, 131, 15,
194, 73, 33, 218, 198, 76, 123, 70, 176, 25, 60, 252, 123, 173, 180,
121, 252, 31, 255, 220, 98, 1, 198, 120, 0, 43, 164, 153, 155, 68, 70,
100, 1, 68, 194, 33, 145, 96, 191, 16, 42, 212, 112, 86, 184, 139,
195, 63, 221, 241, 194, 72, 146, 175, 1, 192, 89, 134, 143, 188, 80,
73, 161, 214, 37, 255, 238, 53, 252, 5, 209, 205, 66, 96, 119, 255, 3,
135, 207, 225, 15, 193, 0, 155, 16, 161, 27, 102, 216, 136, 122, 189,
255, 93, 117, 215, 255, 255, 194, 65, 87, 125, 223, 120, 99, 44, 240,
251, 44, 240, 198, 89, 225, 246, 88, 127, 99, 45, 199, 31, 255, 239,
15, 178, 207, 12, 101, 158, 31, 101, 158, 24, 203, 15, 195, 236, 183,
199, 255, 199, 225, 140, 183, 143, 255, 247, 135, 217, 103, 134, 50,
195, 251, 236, 183, 31, 255, 247, 134, 50, 207, 15, 178, 207, 12, 101,
158, 31, 101, 158, 24, 203, 64, 0, 0, 0, 179, 65, 154, 56, 59, 131,
107, 152, 241, 64, 0, 64, 43, 20, 0, 4, 2, 190, 82, 5, 192, 0, 247,
128, 198, 49, 192, 39, 2, 192, 0, 244, 164, 248, 64, 96, 240, 64, 253,
159, 205, 107, 125, 155, 241, 99, 57, 170, 177, 157, 93, 248, 182, 8,
109, 152, 126, 10, 240, 110, 177, 6, 248, 127, 240, 66, 43, 128, 99,
196, 97, 211, 109, 246, 255, 15, 66, 111, 243, 73, 209, 139, 47, 134,
99, 29, 98, 48, 1, 125, 106, 153, 161, 178, 253, 244, 246, 249, 131,
47, 164, 243, 35, 56, 252, 184, 75, 220, 168, 36, 246, 188, 21, 127,
32, 59, 136, 136, 162, 27, 251, 125, 184, 80, 240, 104, 149, 63, 146,
34, 151, 230, 114, 9, 227, 27, 21, 177, 0, 74, 89, 242, 49, 208, 115,
240, 219, 227, 245, 103, 207, 211, 115, 8, 63, 2, 158, 55, 17, 226,
60, 71, 136, 241, 29, 97, 10, 194, 21, 132, 43, 8, 86, 16, 196, 120,
143, 17, 226, 60, 0, 0, 0, 168, 65, 154, 84, 12, 224, 221, 231, 129,
36, 38, 44, 176, 156, 92, 6, 185, 112, 11, 92, 114, 7, 113, 149, 193,
72, 145, 228, 141, 60, 127, 205, 254, 21, 63, 130, 15, 192, 7, 170,
117, 110, 189, 221, 229, 54, 108, 180, 121, 249, 13, 255, 246, 99,
133, 13, 192, 72, 185, 113, 236, 236, 105, 199, 251, 34, 10, 8, 193,
67, 254, 41, 138, 92, 65, 225, 119, 155, 218, 91, 49, 124, 72, 107,
130, 57, 27, 177, 97, 254, 203, 237, 159, 136, 39, 218, 16, 75, 110,
35, 42, 240, 11, 80, 206, 48, 189, 1, 147, 138, 63, 9, 220, 40, 66,
39, 69, 239, 255, 37, 151, 128, 21, 215, 218, 239, 174, 131, 83, 97,
234, 147, 152, 139, 131, 185, 72, 39, 128, 140, 212, 213, 233, 155,
255, 252, 61, 12, 164, 199, 246, 126, 48, 158, 247, 251, 17, 144, 94,
14, 242, 82, 182, 121, 212, 65, 248, 25, 224, 0, 0, 0, 178, 65, 154,
96, 87, 7, 15, 124, 167, 192, 18, 53, 31, 218, 6, 61, 141, 142, 205,
92, 19, 19, 230, 174, 168, 39, 229, 227, 2, 0, 10, 183, 113, 149, 63,
192, 33, 216, 167, 15, 46, 9, 194, 65, 237, 121, 45, 194, 101, 248,
20, 66, 226, 40, 92, 212, 161, 241, 238, 98, 34, 255, 252, 60, 83, 1,
63, 20, 186, 196, 19, 128, 31, 102, 153, 100, 123, 224, 20, 114, 141,
131, 206, 60, 51, 158, 194, 203, 247, 183, 231, 102, 23, 132, 143, 61,
32, 3, 45, 244, 60, 16, 178, 23, 173, 139, 117, 202, 43, 115, 77, 225,
30, 169, 193, 69, 83, 167, 122, 34, 225, 89, 115, 17, 17, 127, 252,
61, 113, 245, 236, 79, 23, 228, 40, 6, 169, 7, 167, 17, 57, 117, 35,
16, 11, 22, 133, 167, 87, 140, 121, 239, 247, 190, 205, 100, 221, 143,
232, 90, 43, 151, 59, 7, 215, 105, 70, 45, 255, 62, 105, 143, 190, 3,
53, 194, 24, 142, 34, 0, 0, 0, 179, 65, 154, 128, 71, 2, 57, 249, 75,
248, 177, 32, 81, 128, 135, 144, 161, 22, 7, 178, 4, 222, 253, 152,
69, 227, 177, 153, 255, 238, 253, 116, 5, 64, 33, 150, 0, 44, 80, 105,
154, 147, 173, 10, 20, 107, 95, 176, 88, 0, 45, 226, 145, 39, 130,
197, 244, 232, 73, 63, 111, 242, 226, 114, 1, 36, 194, 184, 8, 186,
247, 21, 130, 0, 32, 141, 65, 35, 67, 126, 2, 249, 243, 225, 57, 91,
87, 247, 132, 123, 173, 150, 190, 111, 183, 137, 39, 167, 152, 115,
248, 140, 4, 122, 245, 231, 190, 135, 220, 13, 90, 146, 206, 43, 92,
154, 19, 138, 155, 201, 89, 120, 3, 24, 115, 44, 221, 231, 129, 54,
141, 235, 85, 175, 136, 184, 70, 93, 226, 96, 129, 188, 33, 86, 192,
236, 175, 218, 229, 255, 204, 4, 108, 79, 17, 228, 8, 194, 47, 4, 57,
43, 99, 132, 137, 155, 13, 255, 26, 175, 110, 223, 191, 40, 142, 179,
1, 2, 6, 136, 0, 0, 0, 194, 65, 154, 160, 71, 6, 190, 44, 94, 96, 48,
227, 45, 145, 132, 143, 200, 39, 175, 41, 56, 2, 85, 90, 52, 141, 108,
37, 167, 137, 196, 159, 240, 168, 17, 247, 135, 244, 17, 9, 4, 251,
172, 84, 36, 195, 134, 252, 4, 198, 205, 227, 198, 31, 52, 64, 90,
255, 207, 196, 37, 124, 70, 0, 85, 146, 38, 50, 154, 206, 248, 37,
104, 141, 18, 45, 51, 251, 254, 239, 48, 220, 0, 33, 2, 237, 5, 20,
244, 140, 183, 91, 5, 92, 219, 29, 77, 26, 202, 43, 11, 118, 66, 98,
193, 45, 112, 187, 113, 59, 217, 57, 204, 38, 12, 191, 250, 208, 37,
196, 10, 224, 140, 49, 200, 67, 126, 11, 119, 45, 252, 62, 12, 4, 21,
94, 166, 225, 41, 245, 228, 13, 99, 252, 25, 6, 51, 241, 110, 246,
200, 126, 180, 86, 192, 210, 182, 41, 58, 202, 137, 128, 197, 160,
158, 202, 159, 239, 202, 154, 101, 163, 143, 119, 235, 65, 93, 245,
23, 174, 196, 84, 174, 39, 58, 72, 225, 245, 173, 138, 86, 250, 120,
27, 224, 0, 0, 0, 142, 65, 154, 192, 71, 2, 179, 114, 196, 98, 132,
121, 125, 240, 152, 59, 30, 12, 199, 225, 7, 6, 235, 232, 46, 155, 71,
19, 30, 2, 192, 210, 165, 133, 37, 172, 253, 26, 9, 190, 112, 167,
226, 60, 64, 232, 16, 174, 150, 108, 134, 173, 184, 93, 60, 158, 253,
173, 195, 145, 236, 39, 148, 176, 38, 110, 234, 96, 1, 153, 116, 166,
138, 242, 246, 182, 129, 241, 41, 94, 220, 78, 83, 30, 28, 103, 223,
55, 248, 255, 133, 68, 97, 150, 91, 216, 219, 47, 55, 253, 247, 30, 8,
53, 18, 250, 202, 8, 66, 192, 92, 17, 199, 124, 43, 114, 211, 2, 186,
97, 51, 85, 93, 236, 159, 198, 123, 35, 72, 180, 177, 177, 116, 126,
200, 56, 152, 190, 184, 251, 180, 13, 240, 0, 0, 0, 172, 65, 154, 224,
220, 10, 205, 33, 17, 115, 113, 223, 97, 66, 123, 88, 177, 225, 111,
143, 17, 0, 55, 155, 49, 17, 89, 92, 140, 34, 78, 190, 200, 12, 74,
147, 26, 139, 188, 129, 15, 254, 242, 50, 129, 239, 151, 37, 98, 248,
0, 94, 87, 77, 175, 86, 4, 116, 45, 66, 214, 162, 61, 99, 27, 127,
147, 179, 30, 1, 187, 116, 141, 230, 255, 21, 245, 24, 84, 64, 38,
125, 75, 248, 18, 249, 191, 7, 26, 233, 226, 111, 255, 215, 10, 151,
5, 155, 82, 0, 125, 189, 81, 183, 51, 124, 63, 248, 80, 108, 1, 0, 88,
95, 191, 99, 163, 209, 160, 43, 110, 72, 247, 117, 224, 13, 234, 183,
30, 225, 12, 101, 108, 49, 113, 47, 19, 244, 53, 91, 63, 55, 144, 108,
8, 201, 106, 103, 230, 21, 197, 173, 237, 108, 80, 140, 74, 109, 116,
189, 25, 148, 223, 242, 41, 93, 242, 27, 232, 217, 224, 104, 128, 0,
0, 0, 186, 65, 155, 0, 220, 10, 205, 175, 133, 43, 16, 34, 0, 192,
135, 54, 189, 189, 200, 125, 206, 235, 108, 6, 116, 18, 246, 85, 235,
38, 127, 189, 27, 75, 215, 183, 234, 97, 56, 5, 158, 35, 50, 80, 43,
183, 4, 246, 125, 151, 214, 254, 81, 24, 78, 163, 48, 232, 148, 215,
100, 136, 145, 24, 117, 201, 57, 76, 88, 36, 236, 211, 252, 223, 250,
174, 120, 32, 247, 14, 207, 44, 252, 107, 246, 136, 65, 98, 154, 160,
238, 150, 28, 53, 9, 120, 180, 183, 215, 137, 49, 164, 239, 81, 174,
107, 194, 61, 136, 134, 184, 36, 45, 62, 255, 2, 215, 154, 105, 101,
174, 27, 94, 121, 173, 20, 170, 97, 77, 29, 87, 255, 135, 207, 28, 44,
188, 199, 85, 207, 234, 188, 19, 155, 203, 146, 231, 55, 194, 203, 85,
99, 108, 96, 184, 2, 22, 18, 77, 214, 61, 49, 209, 100, 173, 232, 163,
216, 98, 25, 144, 1, 111, 18, 252, 197, 239, 129, 65, 249, 190, 234,
141, 243, 254, 6, 120, 0, 0, 0, 165, 65, 155, 32, 71, 2, 179, 245,
133, 11, 232, 173, 213, 8, 21, 9, 5, 57, 230, 2, 56, 148, 20, 189,
247, 242, 19, 241, 58, 207, 111, 193, 42, 162, 42, 252, 188, 149, 148,
184, 1, 115, 100, 52, 157, 237, 228, 4, 244, 208, 215, 234, 155, 123,
241, 56, 147, 97, 151, 191, 4, 32, 132, 167, 224, 71, 95, 159, 248,
250, 216, 192, 86, 56, 226, 153, 36, 244, 245, 126, 241, 155, 223,
253, 176, 101, 50, 156, 115, 63, 159, 10, 19, 31, 25, 60, 240, 230,
255, 58, 159, 193, 1, 122, 223, 2, 13, 152, 78, 0, 123, 221, 125, 87,
191, 159, 132, 150, 12, 0, 131, 136, 25, 6, 174, 87, 175, 4, 61, 98,
107, 3, 52, 165, 33, 248, 145, 176, 8, 235, 248, 251, 219, 7, 255,
228, 160, 196, 237, 151, 27, 94, 233, 195, 128, 22, 245, 179, 65, 168,
137, 184, 31, 224, 84, 128, 0, 0, 0, 239, 65, 155, 64, 71, 2, 179,
195, 60, 220, 0, 147, 92, 254, 223, 250, 216, 62, 99, 33, 14, 228,
126, 77, 255, 175, 224, 128, 94, 27, 218, 114, 225, 255, 127, 190, 19,
39, 181, 98, 70, 138, 120, 129, 130, 2, 16, 3, 199, 41, 63, 71, 214,
191, 3, 56, 206, 136, 250, 213, 83, 198, 58, 98, 184, 96, 29, 115,
161, 214, 92, 173, 70, 96, 99, 69, 192, 239, 175, 178, 206, 34, 43,
224, 32, 67, 250, 231, 243, 142, 172, 55, 182, 224, 48, 232, 52, 252,
227, 205, 128, 140, 205, 207, 200, 33, 89, 187, 48, 76, 28, 165, 237,
101, 55, 193, 71, 85, 133, 142, 116, 119, 13, 109, 175, 94, 118, 19,
156, 217, 80, 65, 86, 165, 63, 29, 228, 128, 22, 49, 10, 23, 179, 167,
53, 136, 20, 138, 201, 196, 45, 72, 31, 253, 225, 5, 106, 109, 183,
125, 246, 32, 191, 97, 202, 53, 135, 148, 199, 52, 249, 233, 48, 130,
120, 214, 63, 183, 159, 250, 48, 220, 51, 22, 39, 194, 24, 68, 223,
73, 255, 195, 226, 163, 166, 94, 109, 62, 105, 63, 139, 19, 201, 41,
189, 193, 50, 208, 165, 224, 3, 242, 155, 135, 70, 113, 109, 61, 242,
192, 67, 120, 35, 32, 215, 135, 159, 132, 188, 132, 2, 24, 161, 13,
160, 255, 225, 136, 16, 32, 0, 0, 0, 170, 65, 155, 96, 87, 2, 183,
102, 224, 89, 165, 0, 17, 238, 159, 109, 76, 41, 228, 55, 213, 34,
157, 128, 144, 176, 30, 127, 238, 76, 242, 192, 4, 44, 211, 223, 245,
173, 148, 192, 34, 20, 196, 204, 192, 34, 20, 196, 205, 189, 132, 242,
154, 4, 205, 221, 76, 92, 8, 107, 104, 15, 137, 74, 246, 226, 113,
230, 203, 24, 46, 229, 47, 80, 17, 49, 239, 70, 217, 128, 150, 102,
145, 246, 194, 31, 152, 75, 141, 125, 159, 126, 228, 147, 16, 242,
248, 68, 21, 226, 57, 97, 26, 210, 252, 252, 37, 249, 169, 76, 62,
148, 193, 57, 97, 50, 136, 206, 48, 99, 175, 124, 4, 143, 71, 225, 31,
36, 16, 251, 177, 96, 0, 83, 130, 104, 196, 70, 151, 193, 240, 47,
252, 64, 184, 9, 141, 180, 48, 1, 111, 37, 142, 101, 228, 71, 220,
215, 211, 192, 233, 66, 161, 228, 201, 119, 175, 15, 19, 192, 129, 0,
0, 0, 127, 65, 155, 128, 87, 2, 181, 112, 165, 100, 52, 0, 134, 39, 4,
108, 206, 82, 180, 11, 82, 210, 68, 0, 117, 69, 199, 175, 246, 80,
106, 169, 48, 12, 44, 99, 159, 243, 250, 42, 132, 133, 58, 177, 126,
6, 239, 41, 176, 2, 164, 166, 218, 221, 238, 128, 219, 76, 138, 115,
249, 158, 39, 10, 248, 67, 30, 222, 246, 41, 191, 222, 183, 216, 90,
132, 254, 69, 137, 7, 225, 16, 18, 121, 248, 76, 223, 10, 127, 195,
226, 120, 213, 56, 20, 37, 22, 243, 182, 20, 48, 127, 64, 27, 191, 22,
40, 234, 35, 104, 50, 11, 117, 184, 42, 34, 139, 71, 129, 143, 172,
139, 22, 51, 71, 224, 66, 128, 0, 0, 0, 157, 65, 155, 160, 87, 3, 87,
144, 208, 17, 25, 156, 21, 13, 152, 240, 46, 65, 222, 185, 111, 101,
9, 24, 120, 214, 205, 93, 143, 83, 63, 153, 77, 63, 21, 54, 26, 185,
64, 18, 47, 73, 157, 35, 31, 0, 65, 181, 83, 235, 121, 77, 193, 229,
106, 56, 17, 191, 210, 21, 210, 206, 194, 112, 249, 132, 224, 47, 141,
126, 113, 60, 115, 244, 200, 32, 20, 182, 254, 35, 18, 229, 202, 223,
172, 194, 81, 190, 23, 247, 223, 171, 210, 149, 125, 246, 42, 79, 104,
166, 43, 74, 175, 15, 249, 37, 49, 191, 225, 239, 18, 44, 80, 16, 194,
64, 40, 115, 240, 153, 189, 161, 242, 150, 9, 163, 244, 255, 26, 164,
252, 38, 104, 126, 177, 5, 194, 19, 60, 89, 147, 40, 233, 131, 190,
183, 78, 253, 224, 128, 249, 79, 4, 47, 92, 112, 33, 64, 0, 0, 0, 140,
65, 155, 192, 87, 6, 250, 195, 247, 155, 192, 36, 253, 125, 156, 248,
174, 226, 111, 32, 136, 22, 159, 86, 19, 46, 115, 234, 254, 64, 175,
59, 65, 199, 49, 141, 106, 115, 246, 92, 104, 57, 87, 166, 214, 75,
157, 238, 178, 147, 0, 46, 226, 68, 185, 89, 192, 111, 52, 103, 173,
86, 207, 19, 133, 12, 124, 1, 106, 159, 146, 89, 225, 6, 173, 108,
117, 137, 36, 6, 249, 13, 237, 45, 173, 240, 247, 132, 35, 36, 19,
243, 97, 11, 37, 21, 17, 97, 51, 91, 229, 254, 30, 41, 189, 243, 240,
145, 142, 170, 204, 216, 235, 226, 188, 212, 225, 212, 137, 153, 235,
172, 71, 172, 177, 252, 223, 226, 186, 248, 32, 18, 82, 135, 250, 112,
91, 114, 223, 129, 14, 0, 0, 0, 159, 65, 155, 224, 87, 2, 181, 102,
241, 179, 190, 240, 157, 100, 17, 0, 53, 90, 66, 59, 247, 166, 223,
191, 74, 214, 202, 4, 243, 68, 74, 176, 186, 185, 251, 70, 188, 197,
132, 204, 239, 96, 12, 34, 87, 141, 51, 8, 241, 229, 137, 230, 94,
247, 90, 202, 43, 130, 187, 110, 133, 74, 233, 183, 91, 46, 228, 156,
32, 99, 151, 193, 11, 67, 139, 31, 228, 48, 71, 173, 61, 53, 179, 127,
255, 146, 132, 159, 246, 255, 251, 16, 24, 142, 255, 235, 207, 188, 8,
0, 175, 52, 155, 229, 254, 18, 44, 252, 178, 249, 225, 51, 121, 74,
196, 95, 137, 240, 223, 155, 23, 252, 255, 193, 198, 18, 242, 4, 96,
4, 57, 106, 121, 146, 247, 230, 42, 175, 199, 85, 224, 156, 103, 10,
4, 205, 198, 245, 148, 55, 129, 47, 155, 241, 14, 166, 88, 255, 2, 36,
0, 0, 0, 124, 65, 154, 0, 87, 2, 181, 253, 44, 39, 228, 17, 0, 99, 95,
101, 95, 124, 67, 216, 204, 130, 141, 43, 87, 123, 46, 119, 95, 149,
49, 183, 176, 158, 82, 64, 153, 187, 169, 128, 13, 255, 162, 201, 251,
247, 189, 173, 160, 62, 37, 43, 219, 136, 190, 18, 23, 226, 120, 4,
121, 24, 189, 191, 225, 10, 216, 162, 85, 203, 163, 180, 113, 226, 77,
62, 251, 204, 191, 132, 64, 176, 8, 129, 164, 60, 94, 27, 247, 193,
128, 41, 207, 194, 47, 193, 36, 35, 136, 97, 193, 169, 119, 227, 127,
244, 2, 32, 4, 110, 35, 132, 176, 176, 64, 129, 60, 33, 106, 95, 133,
194, 48, 34, 64, 0, 0, 0, 170, 65, 154, 32, 87, 2, 181, 102, 240, 9,
203, 70, 151, 249, 167, 132, 201, 245, 247, 228, 17, 9, 249, 24, 9,
145, 5, 223, 207, 64, 183, 242, 130, 143, 200, 45, 58, 79, 124, 64,
36, 253, 148, 39, 251, 170, 191, 209, 227, 18, 187, 203, 192, 1, 187,
45, 249, 241, 178, 189, 47, 76, 77, 188, 95, 245, 249, 215, 175, 67,
133, 147, 4, 150, 165, 153, 54, 144, 11, 212, 55, 219, 251, 92, 188,
109, 236, 66, 123, 135, 246, 141, 53, 94, 255, 204, 126, 1, 30, 190,
218, 240, 43, 2, 64, 88, 13, 5, 147, 182, 105, 177, 236, 191, 47, 195,
37, 4, 158, 248, 212, 175, 96, 104, 194, 57, 36, 13, 194, 102, 201,
175, 83, 255, 127, 127, 172, 16, 135, 144, 128, 128, 73, 102, 191,
182, 104, 176, 82, 200, 67, 88, 194, 37, 90, 2, 247, 127, 224, 66,
125, 1, 58, 249, 252, 252, 35, 254, 92, 8, 176, 0, 0, 0, 190, 65, 154,
64, 103, 2, 181, 126, 35, 9, 214, 67, 64, 38, 226, 151, 163, 226, 153,
253, 214, 181, 179, 133, 156, 135, 203, 25, 15, 188, 243, 89, 107,
116, 253, 179, 75, 32, 89, 190, 0, 64, 50, 242, 11, 72, 190, 220, 37,
8, 102, 103, 169, 68, 232, 255, 111, 41, 184, 112, 201, 24, 19, 42,
62, 37, 214, 238, 87, 152, 95, 2, 107, 212, 201, 191, 241, 125, 198,
21, 25, 166, 122, 130, 3, 58, 50, 8, 207, 4, 99, 170, 38, 248, 123,
223, 194, 184, 11, 79, 84, 114, 11, 120, 224, 254, 63, 201, 20, 199,
39, 35, 230, 112, 195, 224, 251, 249, 159, 235, 192, 103, 186, 228,
221, 190, 196, 89, 110, 141, 168, 240, 222, 161, 163, 246, 76, 110,
253, 95, 123, 1, 205, 224, 8, 94, 41, 72, 80, 104, 4, 41, 170, 237,
249, 60, 122, 96, 100, 87, 198, 176, 63, 0, 71, 47, 200, 82, 230, 109,
195, 222, 38, 190, 239, 135, 248, 120, 66, 60, 177, 159, 95, 208, 4,
3, 215, 9, 159, 129, 98, 0, 0, 0, 129, 65, 154, 96, 103, 2, 189, 147,
9, 249, 4, 64, 7, 58, 225, 200, 74, 114, 18, 173, 80, 104, 244, 223,
213, 214, 206, 42, 216, 191, 207, 249, 151, 169, 149, 66, 119, 186,
202, 76, 0, 209, 69, 10, 120, 252, 220, 4, 245, 163, 62, 212, 187,
244, 222, 22, 12, 32, 28, 41, 13, 211, 48, 148, 197, 29, 87, 123, 230,
135, 155, 5, 255, 0, 225, 226, 117, 155, 252, 3, 0, 225, 89, 236, 125,
150, 215, 251, 243, 127, 128, 64, 60, 42, 83, 224, 203, 71, 66, 203,
124, 33, 91, 20, 48, 139, 232, 126, 5, 187, 191, 203, 248, 128, 247,
144, 104, 18, 158, 105, 103, 255, 129, 227, 66, 56, 76, 252, 11, 16,
0, 0, 0, 179, 65, 154, 128, 87, 2, 111, 150, 120, 110, 37, 226, 185,
184, 34, 184, 122, 108, 190, 108, 1, 213, 136, 177, 184, 0, 53, 73,
102, 81, 224, 0, 0, 64, 27, 49, 127, 58, 174, 167, 22, 103, 255, 12,
154, 8, 175, 65, 170, 246, 57, 213, 98, 4, 64, 24, 22, 245, 79, 215,
60, 230, 48, 98, 109, 58, 34, 45, 130, 196, 158, 52, 1, 141, 84, 33,
152, 148, 70, 229, 184, 63, 178, 144, 255, 249, 245, 121, 184, 4, 196,
245, 118, 192, 86, 158, 137, 95, 30, 255, 118, 178, 155, 15, 119, 97,
253, 108, 217, 13, 108, 30, 32, 110, 65, 97, 236, 106, 211, 143, 126,
171, 246, 1, 128, 144, 94, 2, 34, 218, 111, 207, 204, 171, 173, 71,
254, 18, 10, 95, 153, 157, 159, 81, 213, 155, 85, 135, 235, 225, 63,
86, 248, 251, 200, 122, 183, 125, 211, 175, 114, 117, 123, 255, 189,
136, 27, 20, 17, 223, 253, 71, 252, 249, 252, 71, 136, 224, 107, 128,
0, 0, 0, 150, 65, 154, 160, 103, 2, 183, 155, 192, 2, 30, 233, 246,
202, 101, 232, 81, 96, 6, 23, 171, 227, 35, 60, 168, 192, 97, 173,
242, 16, 45, 158, 50, 28, 25, 69, 55, 252, 120, 20, 132, 8, 21, 142,
205, 13, 44, 241, 86, 124, 69, 1, 255, 195, 79, 4, 64, 146, 131, 55,
19, 127, 169, 215, 80, 133, 72, 63, 43, 248, 89, 211, 27, 228, 54,
241, 241, 101, 130, 206, 228, 63, 2, 91, 10, 81, 169, 196, 61, 158, 7,
173, 221, 243, 198, 221, 183, 176, 157, 148, 210, 216, 17, 231, 44,
238, 103, 142, 146, 55, 59, 207, 216, 6, 195, 155, 235, 255, 195, 229,
218, 230, 58, 172, 15, 235, 225, 226, 119, 8, 61, 26, 71, 76, 46, 38,
208, 241, 239, 35, 91, 243, 191, 114, 81, 252, 71, 136, 224, 107, 128,
0, 0, 0, 146, 65, 154, 192, 103, 2, 57, 252, 191, 38, 8, 65, 176, 49,
2, 48, 71, 128, 50, 95, 3, 134, 120, 190, 0, 91, 154, 22, 208, 170,
62, 168, 55, 210, 243, 213, 120, 173, 101, 255, 203, 224, 10, 170,
198, 65, 55, 94, 245, 36, 144, 195, 16, 66, 10, 165, 152, 197, 255,
254, 30, 132, 235, 254, 61, 255, 153, 78, 121, 252, 206, 112, 248,
140, 124, 100, 252, 18, 70, 83, 144, 208, 9, 201, 68, 206, 84, 41, 74,
131, 239, 61, 246, 246, 85, 140, 238, 176, 173, 10, 116, 47, 91, 59,
221, 229, 38, 2, 81, 207, 150, 254, 204, 37, 119, 134, 126, 234, 176,
36, 0, 152, 239, 204, 103, 56, 31, 207, 195, 217, 225, 132, 43, 125,
131, 113, 21, 95, 105, 244, 127, 63, 136, 239, 168, 26, 32, 0, 0, 0,
165, 65, 154, 224, 87, 6, 190, 83, 224, 1, 218, 159, 240, 88, 229, 55,
216, 34, 250, 115, 161, 158, 176, 131, 240, 70, 8, 2, 70, 128, 22,
152, 174, 215, 248, 182, 0, 77, 199, 110, 96, 224, 128, 230, 176, 137,
193, 41, 126, 176, 110, 4, 40, 32, 30, 48, 191, 26, 152, 174, 191,
141, 174, 131, 222, 28, 29, 32, 161, 127, 132, 252, 129, 120, 23, 78,
136, 254, 95, 243, 5, 62, 154, 124, 16, 7, 57, 56, 207, 33, 96, 75,
77, 245, 92, 66, 197, 106, 62, 205, 236, 165, 69, 11, 74, 150, 124,
183, 154, 214, 188, 220, 5, 187, 93, 128, 70, 9, 201, 147, 55, 217,
111, 41, 178, 140, 60, 158, 219, 165, 228, 240, 134, 139, 225, 188,
119, 199, 191, 82, 77, 130, 242, 147, 251, 250, 255, 184, 63, 211, 41,
199, 91, 7, 80, 239, 155, 27, 191, 103, 123, 1, 163, 240, 54, 192, 0,
0, 0, 145, 65, 155, 0, 87, 6, 135, 247, 228, 139, 46, 224, 3, 233, 94,
198, 82, 128, 75, 0, 17, 102, 68, 109, 63, 35, 64, 31, 4, 50, 0, 0, 3,
0, 128, 116, 240, 0, 16, 14, 252, 161, 15, 132, 37, 30, 97, 62, 2,
193, 135, 64, 2, 58, 253, 189, 155, 109, 134, 30, 184, 10, 8, 47, 18,
8, 153, 249, 190, 126, 49, 222, 87, 231, 204, 127, 52, 41, 244, 244,
195, 218, 120, 203, 201, 128, 1, 251, 68, 190, 119, 228, 51, 49, 204,
76, 136, 172, 67, 105, 158, 63, 161, 211, 215, 205, 12, 109, 182, 175,
148, 152, 3, 218, 62, 72, 2, 207, 88, 15, 200, 156, 60, 73, 54, 59,
76, 33, 74, 224, 206, 188, 122, 19, 127, 118, 250, 63, 5, 91, 152, 92,
119, 220, 8, 112, 0, 0, 0, 4, 104, 203, 140, 178
];
var trafLength = 8 + tfdt.length + tfdt.length + trun.length;
var m4s = [
8 + mfhd.length + trafLength, // size
109, 111, 111, 102 // 'moof'
]
.concat(mfhd)
.concat([
trafLength, // size
116, 114, 97, 102 // 'traf'
].concat(tfhd).concat(tfdt).concat(trun))
.concat(mdat);
<!DOCTYPE html>
<!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
<!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
<!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]-->
<!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]-->
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title></title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width">
<link rel="stylesheet" href="css/normalize.min.css">
<link rel="stylesheet" href="css/main.css">
<script src="js/vendor/modernizr-2.6.2.min.js"></script>
</head>
<body>
<!--[if lt IE 7]>
<p class="chromeframe">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> or <a href="http://www.google.com/chromeframe/?redirect=true">activate Google Chrome Frame</a> to improve your experience.</p>
<![endif]-->
<div class="header-container">
<header class="wrapper clearfix">
<h1 class="title">Transmux Analyzer</h1>
</header>
</div>
<div class="main-container">
<div class="main wrapper clearfix">
<article>
<header>
<p>
This page can help you inspect the results of the
transmuxing to mp4 files performed by
videojs-contrib-hls. It's still a bit tricky to create a
MSE-compatible fragmented MP4. We've had luck
with <a href="http://www.bento4.com/developers/dash/">Bento4</a>
and ffmpeg. If you have both of those utilities installed,
you can create a working MP4 like this:
<pre>
ffmpeg -i movie.ts -vn -codec copy -absf aac_adtstoasc movie-audio.mp4
mp4fragment --track audio --fragment-duration 11000 movie-audio.mp4 movie-audio.m4s
</pre>
<small>Looking for the <a href="index.html">FLV tool</a>?</small>
</header>
<section id="video-place">
</section>
<section>
<h2>Inputs</h2>
<form id="inputs">
<label>
Your original MP2T segment:
<input type="file" id="original">
</label>
<label>
A working, MP4 version of the underlying stream
produced by another tool:
<input type="file" id="working">
</label>
</form>
</section>
<section>
<h2>Comparision</h2>
<div id="comparison">
A diff of the structure of the two MP4s will appear here
once you've specified an input TS file and a known working
MP4.
</div>
</section>
<section>
<h2>Structure</h2>
<div class="result-wrapper">
<h3>videojs-contrib-hls</h3>
<pre class="vjs-boxes">
</pre>
</div>
<div class="result-wrapper">
<h3>Working</h3>
<pre class="working-boxes"></pre>
</div>
</section>
<section>
<h2>Results</h2>
<div class="result-wrapper">
<h3>videojs-contrib-hls</h3>
<div class="vjs-hls-output result">
<p>
The results of transmuxing your input file with
videojs-contrib-hls will show up here.
</p>
</div>
</div>
<div class="result-wrapper">
<h3>Working</h3>
<div class="working-output result">
<p>
The "good" version of the file will show up here.
</p>
</div>
</div>
</section>
</article>
</div> <!-- #main -->
</div> <!-- #main-container -->
<div class="footer-container">
<footer class="wrapper">
<h3>footer</h3>
</footer>
</div>
<script>
window.videojs = window.videojs || {
Hls: {}
};
</script>
<script src="../../node_modules/videojs-contrib-media-sources/node_modules/mux.js/lib/stream.js"></script>
<script src="../../node_modules/videojs-contrib-media-sources/node_modules/mux.js/lib/mp4-generator.js"></script>
<script src="../../node_modules/videojs-contrib-media-sources/node_modules/mux.js/lib/transmuxer.js"></script>
<script src="../../node_modules/videojs-contrib-media-sources/node_modules/mux.js/lib/mp4-inspector.js"></script>
<script src="../../node_modules/videojs-contrib-media-sources/node_modules/mux.js/legacy/flv-tag.js"></script>
<script src="../../node_modules/videojs-contrib-media-sources/node_modules/mux.js/lib/exp-golomb.js"></script>
<script src="../../src/bin-utils.js"></script>
<!-- Include QUnit for object diffs -->
<script src="../../node_modules/qunitjs/qunit/qunit.js"></script>
<script>
/*
MOSTLY STOLEN FROM https://w3c.github.io/media-source/#examples
*/
function setupMSE (videoElement, getNextVideoSegment, getNextAudioSegment) {
function onSourceOpen(videoTag, e) {
var
initVideoSegment = getNextVideoSegment(),
initAudioSegment = getNextAudioSegment(),
numberInited = 0,
videoBuffer, audioBuffer,
mediaSource = e.target;
if (mediaSource.sourceBuffers.length > 0)
return;
if (initVideoSegment) {
videoBuffer = mediaSource.addSourceBuffer('video/mp4;codecs=avc1.4d401f');
}
if (initAudioSegment) {
audioBuffer = mediaSource.addSourceBuffer('audio/mp4;codecs=mp4a.40.2');
}
videoTag.addEventListener('progress', onProgress.bind(videoTag, mediaSource));
if (initVideoSegment == null && initAudioSegment == null) {
// Error fetching the initialization segment. Signal end of stream with an error.
mediaSource.endOfStream("network");
return;
}
// Append the initialization segment.
var firstAppendHandler = function(e) {
var sourceBuffer = e.target;
sourceBuffer.removeEventListener('updateend', firstAppendHandler);
// Append some initial media data.
if (++numberInited === 2) {
onProgress(mediaSource, e);
}
};
if (videoBuffer) {
videoBuffer.addEventListener('updateend', firstAppendHandler);
}
if (audioBuffer) {
audioBuffer.addEventListener('updateend', firstAppendHandler);
}
if (initVideoSegment) {
videoBuffer.appendBuffer(initVideoSegment);
}
if (initAudioSegment) {
audioBuffer.appendBuffer(initAudioSegment);
}
}
function appendNextMediaSegment(getNextMediaSegment, mediaSource, sourceBuffer) {
if (mediaSource.readyState == "closed") {
return;
}
var mediaSegment = getNextMediaSegment();
// If we have run out of stream data, then signal end of stream.
if (mediaSegment == null) {
// mediaSource.endOfStream("network");
return false;
}
// Make sure the previous append is not still pending.
if (sourceBuffer.updating) {
return false;
}
// NOTE: If mediaSource.readyState == “ended”, this appendBuffer() call will
// cause mediaSource.readyState to transition to "open". The web application
// should be prepared to handle multiple “sourceopen” events.
sourceBuffer.appendBuffer(mediaSegment);
return true;
}
/*
function onSeeking(mediaSource, e) {
var video = e.target;
if (mediaSource.readyState == "open") {
// Abort current segment append.
mediaSource.sourceBuffers[0].abort();
}
// Notify the media segment loading code to start fetching data at the
// new playback position.
SeekToMediaSegmentAt(video.currentTime);
// Append a media segment from the new playback position.
appendNextMediaSegment(mediaSource);
}
*/
function onProgress(mediaSource, e) {
(appendNextMediaSegment(getNextVideoSegment, mediaSource, mediaSource.sourceBuffers[0]) &&
appendNextMediaSegment(getNextAudioSegment, mediaSource, mediaSource.sourceBuffers[1]));
}
var mediaSource = new MediaSource();
mediaSource.addEventListener('sourceopen', onSourceOpen.bind(this, videoElement));
videoElement.src = window.URL.createObjectURL(mediaSource);
}
function getSegment (segmentArray) {
var segment = segmentArray.shift();
if (segment) {
return segment.data;
}
return null;
}
</script>
<script>
var inputs = document.getElementById('inputs'),
original = document.getElementById('original'),
working = document.getElementById('working'),
vjsParsed,
workingParsed,
diffParsed,
vjsBytes,
workingBytes,
vjsBoxes = document.querySelector('.vjs-boxes'),
workingBoxes = document.querySelector('.working-boxes'),
vjsOutput = document.querySelector('.vjs-hls-output'),
workingOutput = document.querySelector('.working-output'),
video = document.createElement('video'),
mediaSource = new MediaSource();
document.querySelector('#video-place').appendChild(video);
logevent = function(event) {
console.log(event.type);
};
// output a diff of the two parsed MP4s
diffParsed = function() {
var comparison, diff, transmuxed;
if (!vjsParsed || !workingParsed) {
// wait until both inputs have been provided
return;
}
comparison = document.querySelector('#comparison');
if (workingParsed[0].type === 'moof') {
diff = '<h3>Media Segment Comparision</h3>';
transmuxed = vjsParsed.slice(2);
} else if (workingParsed.length === 2) {
diff = '<h3>Init Segment Comparision</h3>';
transmuxed = vjsParsed.slice(0, 2);
} else {
diff = '<h3>General Comparision</h3>';
transmuxed = vjsParsed;
}
diff += '<p>A <del>red background</del> indicates ' +
'properties present in the transmuxed file but missing from the ' +
'working version. A <ins>green background</ins> indicates ' +
'properties present in the working version but missing in the ' +
'transmuxed output.</p>';
diff += '<pre class="mp4-diff">' +
QUnit.diff(muxjs.textifyMp4(transmuxed, null, ' '),
muxjs.textifyMp4(workingParsed, null, ' ')) +
'</pre>';
comparison.innerHTML = diff;
};
mediaSource.addEventListener('sourceopen', function() {
var
buffer = mediaSource.addSourceBuffer('video/mp4;codecs=avc1.4d400d');
//buffer = mediaSource.addSourceBuffer('audio/mp4;codecs=mp4a.40.2');
buffer.addEventListener('updatestart', logevent);
buffer.addEventListener('updateend', logevent);
buffer.addEventListener('error', logevent);
window.vjsMediaSource = mediaSource;
window.vjsSourceBuffer = buffer;
window.vjsVideo = video;
});
mediaSource.addEventListener('error', logevent);
mediaSource.addEventListener('opened', logevent);
mediaSource.addEventListener('closed', logevent);
mediaSource.addEventListener('sourceended', logevent);
video.src = URL.createObjectURL(mediaSource);
video.addEventListener('error', console.log.bind(console));
// muxjs.log = console.log.bind(console);
original.addEventListener('change', function() {
var reader = new FileReader(),
videoBuffer = [],
audioBuffer = [];
reader.addEventListener('loadend', function() {
var segment = new Uint8Array(reader.result),
transmuxer = new muxjs.mp2t.Transmuxer(),
videoSegments = [],
audioSegments = [],
videoBytesLength = 0,
audioBytesLength = 0,
decodeMe,
bytes,
i, j,
hex = '';
// transmux the MPEG-TS data to BMFF segments
transmuxer.on('data', function(segment) {
if (segment.type === 'video') {
videoSegments.push(segment);
videoBytesLength += segment.data.byteLength;
} else {
audioSegments.push(segment);
audioBytesLength += segment.data.byteLength;
}
});
transmuxer.push(segment);
transmuxer.end();
// XXX - switch to select video/audio to show
decodeMe = videoSegments;
bytes = new Uint8Array(videoBytesLength);
for (j = 0, i = 0; j < decodeMe.length; j++) {
bytes.set(decodeMe[j].data, i);
i += decodeMe[j].byteLength;
}
vjsBytes = bytes;
vjsParsed = muxjs.inspectMp4(bytes);
console.log('transmuxed', vjsParsed);
diffParsed();
// XXX - set one of videoSegments or audioSegments below to an
// empty array to only test one stream
setupMSE(video,
getSegment.bind(null, videoSegments),
getSegment.bind(null, audioSegments));
// clear old box info
vjsBoxes.innerHTML = muxjs.textifyMp4(vjsParsed, null, ' ');
// write out the result
hex += '<pre>';
hex += 'nothing to see here';
hex += '</pre>';
vjsOutput.innerHTML = hex;
video.play();
});
reader.readAsArrayBuffer(this.files[0]);
}, false);
working.addEventListener('change', function() {
var reader = new FileReader();
reader.addEventListener('loadend', function() {
var hex = '',
bytes = new Uint8Array(reader.result);
workingBytes = bytes;
workingParsed = muxjs.inspectMp4(bytes);
console.log('working', workingParsed);
diffParsed();
// clear old box info
workingBoxes.innerHTML = muxjs.textifyMp4(workingParsed, null, ' ');
// output the hex dump
hex += '<pre>';
hex += videojs.Hls.utils.hexDump(bytes);
hex += '</pre>';
workingOutput.innerHTML = hex;
// XXX Media Sources Testing
/* setupMSE(video,
getSegment.bind(null, []),
getSegment.bind(null, [{data: bytes}]));*/
//window.vjsSourceBuffer.appendBuffer(bytes);
});
reader.readAsArrayBuffer(this.files[0]);
}, false);
</script>
</body>
</html>
<!DOCTYPE html>
<!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
<!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
<!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]-->
<!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]-->
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>HLS Media Sources Demo</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width">
<link rel="stylesheet" href="css/normalize.min.css">
<link rel="stylesheet" href="css/main.css">
<script src="js/vendor/modernizr-2.6.2.min.js"></script>
</head>
<body>
<!--[if lt IE 7]>
<p class="chromeframe">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> or <a href="http://www.google.com/chromeframe/?redirect=true">activate Google Chrome Frame</a> to improve your experience.</p>
<![endif]-->
<div class="header-container">
<header class="wrapper clearfix">
<h1 class="title">Media Sources Demo</h1>
</header>
</div>
<div class="main-container">
<div class="main wrapper clearfix">
<article>
<header>
<p>
videojs-contrib-hls can convert MPEG transport streams
into Media Source Extension-compatible media segments on
the fly. Check out the demo below in Chrome!
</p>
</header>
<section>
<video id=demo width=640 height=360 controls>
<p>
Your browser is too old to view this demo. How about
upgrading?
</p>
</video>
</section>
</article>
</div> <!-- #main -->
</div> <!-- #main-container -->
<div class="footer-container">
<footer class="wrapper">
</footer>
</div>
<script src="zencoder-haze-1.js"></script>
<!-- transmuxing -->
<script>
window.videojs = window.videojs || {
Hls: {}
};
</script>
<script src="../../src/stream.js"></script>
<script src="../../src/mp4-generator.js"></script>
<script src="../../src/transmuxer.js"></script>
<script src="../../src/exp-golomb.js"></script>
<script src="js/mp4-inspector.js"></script>
<script src="../../src/bin-utils.js"></script>
<script>
var mediaSource = new MediaSource(),
demo = document.getElementById('demo');
// setup the media source
mediaSource.addEventListener('sourceopen', function() {
var videoBuffer = mediaSource.addSourceBuffer('video/mp4;codecs=avc1.4d400d'),
audioBuffer = mediaSource.addSourceBuffer('audio/mp4;codecs=mp4a.40.2'),
transmuxer = new videojs.mp2t.Transmuxer(),
videoSegments = [],
audioSegments = [];
// expose the machinery for debugging
window.vjsMediaSource = mediaSource;
window.vjsSourceBuffer = videoBuffer;
window.vjsVideo = demo;
// transmux the MPEG-TS data to BMFF segments
transmuxer.on('data', function(segment) {
if (segment.type === 'video') {
videoSegments.push(segment);
} else {
audioSegments.push(segment);
}
});
transmuxer.push(hazeVideo);
transmuxer.end();
// buffer up the video data
videoBuffer.appendBuffer(videoSegments.shift().data);
videoBuffer.addEventListener('updateend', function() {
if (videoSegments.length) {
videoBuffer.appendBuffer(videoSegments.shift().data);
}
});
// buffer up the audio data
audioBuffer.appendBuffer(audioSegments.shift().data);
audioBuffer.addEventListener('updateend', function() {
if (audioSegments.length) {
audioBuffer.appendBuffer(audioSegments.shift().data);
}
});
});
demo.src = URL.createObjectURL(mediaSource);
demo.addEventListener('error', console.log.bind(console));
</script>
</body>
</html>
<!DOCTYPE html>
<!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
<!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
<!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]-->
<!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]-->
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title></title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width">
<link rel="stylesheet" href="css/normalize.min.css">
<link rel="stylesheet" href="css/main.css">
<script src="js/vendor/modernizr-2.6.2.min.js"></script>
<script>
window.videojs = window.videojs || {
Hls: {}
};
</script>
<!--<script src="min-m4s.js"></script>-->
<script src="./js/mdat.js"></script>
<script src="../../src/mp4-generator.js"></script>
</head>
<body>
<!--[if lt IE 7]>
<p class="chromeframe">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> or <a href="http://www.google.com/chromeframe/?redirect=true">activate Google Chrome Frame</a> to improve your experience.</p>
<![endif]-->
<div class="header-container">
<header class="wrapper clearfix">
<h1 class="title">Transmux Analyzer</h1>
</header>
</div>
<div class="main-container">
<div class="main wrapper clearfix">
<article>
<header>
<p>
This page will show a video player that loads a header of an mp4 followed by an mp4 segment.
</p>
</header>
<section>
<video controls autoplay width="320" height="240"></video>
</section>
</article>
</div> <!-- #main -->
</div> <!-- #main-container -->
<div class="footer-container">
<footer class="wrapper">
<h3></h3>
</footer>
</div>
<script>
var ms,
sourceBuffer,
video;
function closed() {
console.log("MediaSource Closed.");
}
var done = false;
function loadFragment() {
if (done) {
return;
}
console.log("LOAD FRAGMENT");
var moov = videojs.mp4.moov(100, 600, 300, "video");
var moof = videojs.mp4.moof([{trackId: 1}]);
var frag = Array.prototype.slice.call(moov);
frag = frag.concat(Array.prototype.slice.call(moof));
frag = frag.concat(Array.prototype.slice.call(mdat));
sourceBuffer.appendBuffer( new Uint8Array( frag ) );
done = true;
}
function loadMdat(){
console.log('mdat', mdat);
setTimeout( function(){
sourceBuffer.appendBuffer(new Uint8Amdat);
},0);
}
function loadInit() {
console.log("LOAD INIT");
var req = new XMLHttpRequest();
req.responseType = "arraybuffer";
req.open("GET", "./ts2/Header.m4s", true);
req.onload = function() {
console.log("INIT DONE LOADING");
sourceBuffer.appendBuffer(new Uint8Array(req.response));
sourceBuffer.addEventListener('update', loadFragment);
};
req.onerror = function () {
window.alert("Could not load init.");
};
req.send();
}
function opened() {
console.log("OPENED");
sourceBuffer = ms.addSourceBuffer('video/mp4;codecs="avc1.42c00d"');
sourceBuffer.addEventListener('updateend', console.log.bind(console));
sourceBuffer.addEventListener('updatestart', console.log.bind(console));
sourceBuffer.addEventListener('update', console.log.bind(console));
loadInit();
}
function load() {
console.log("START");
window.MediaSource = window.MediaSource || window.WebKitMediaSource;
ms = new window.MediaSource();
video = document.querySelector('video');
video.src = window.URL.createObjectURL(ms);
ms.addEventListener('webkitsourceopen', opened, false);
ms.addEventListener('webkitsourceclose', closed, false);
ms.addEventListener('sourceopen', opened, false);
ms.addEventListener('sourceclose', closed, false);
ms.addEventListener('update', console.log.bind(console));
ms.addEventListener('updateend', console.log.bind(console));
}
load();
</script>
</body>
</html>
<!DOCTYPE html>
<!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
<!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
<!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]-->
<!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]-->
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title></title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width">
<link rel="stylesheet" href="css/normalize.min.css">
<link rel="stylesheet" href="css/main.css">
<script src="js/vendor/modernizr-2.6.2.min.js"></script>
</head>
<body>
<!--[if lt IE 7]>
<p class="chromeframe">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> or <a href="http://www.google.com/chromeframe/?redirect=true">activate Google Chrome Frame</a> to improve your experience.</p>
<![endif]-->
<div class="header-container">
<header class="wrapper clearfix">
<h1 class="title">MP4 Media Sources API Test</h1>
</header>
</div>
<div class="main-container">
<div class="main wrapper clearfix">
<article>
<header>
<p>
This page is used to demo the playing of an mp4 file via the mediaSources API
</p>
</header>
<section>
<h2>Inputs</h2>
<form id="inputs">
<label>
Your original MP2T segment:
<input type="file" id="original">
</label>
<label>
A working, MP4 version of the underlying stream
produced by another tool:
<input type="file" id="working">
</label>
</form>
</section>
<section>
<video controls width="320" height="240">>
<source src="sintel_trailer-480p.mp4" type="video/mp4">
Your browser does not support the <code>video</code> element.
</video>
</section>
</article>
</div> <!-- #main -->
</div> <!-- #main-container -->
<div class="footer-container">
<footer class="wrapper">
<h3></h3>
</footer>
</div>
<script>
var inputs = document.getElementById('inputs'),
original = document.getElementById('original'),
working = document.getElementById('working'),
video = document.createElement('video');
working.addEventListener('change', function() {
console.log('File Selectd');
var reader = new FileReader();
reader.addEventListener('loadend', function() {
console.log('File has been loaded');
var bytes = new Uint8Array(reader.result),
ms = new window.MediaSource(),
video = document.querySelector('video');
video.addEventListener("error", function onError(err) {
console.dir("error", err, video.error);
});
video.pause();
video.src = window.URL.createObjectURL(ms);
var onMediaSourceOpen = function() {
console.log('on media open');
ms.removeEventListener('sourceopen', onMediaSourceOpen);
var videoBuffer = ms.addSourceBuffer('video/mp4;codecs="avc1.4D400D"');
videoBuffer.appendBuffer(bytes);
var audioBuffer = ms.addSourceBuffer('audio/mp4;codecs=mp4a.40.2');
};
ms.addEventListener('sourceopen', onMediaSourceOpen);
console.log('appended bytes', bytes);
});
reader.readAsArrayBuffer(this.files[0]);
});
</script>
</body>
</html>
[
{
"type": "ftyp"
},
{
"type": "free"
},
{
"boxes": [
{
"type": "mvhd"
},
{
"boxes": [
{
"type": "mehd"
},
{
"type": "trex"
}
],
"type": "mvex"
},
{
"boxes": [
{
"type": "tkhd"
},
{
"boxes": [
{
"type": "mdhd"
},
{
"type": "hdlr"
},
{
"boxes": [
{
"type": "vmhd"
},
{
"boxes": [
{
"dataReferences": [
{
"type": "url "
}
],
"type": "dref"
}
],
"type": "dinf"
},
{
"boxes": [
{
"sampleDescriptions": [
{
"config": [
{
"type": "avcC"
},
{
"type": "btrt"
}
],
"type": "avc1"
}
],
"type": "stsd"
},
{
"type": "stts"
},
{
"type": "stsc"
},
{
"type": "stsz"
},
{
"type": "stco"
}
],
"type": "stbl"
}
],
"type": "minf"
}
],
"type": "mdia"
}
],
"type": "trak"
}
],
"type": "moov"
}
]
\ No newline at end of file
This diff could not be displayed because it is too large.
(function(window) {
/*
======== A Handy Little QUnit Reference ========
http://api.qunitjs.com/
Test methods:
module(name, {[setup][ ,teardown]})
test(name, callback)
expect(numberOfAssertions)
stop(increment)
start(decrement)
Test assertions:
ok(value, [message])
equal(actual, expected, [message])
notEqual(actual, expected, [message])
deepEqual(actual, expected, [message])
notDeepEqual(actual, expected, [message])
strictEqual(actual, expected, [message])
notStrictEqual(actual, expected, [message])
throws(block, [expected], [message])
*/
var
parser,
expectedHeader = [
0x46, 0x4c, 0x56, 0x01, 0x05, 0x00, 0x00, 0x00,
0x09, 0x00, 0x00, 0x00, 0x00
],
mergeOptions = window.videojs.mergeOptions,
makePat,
makePsi,
makePmt,
makePacket,
testAudioTag,
testVideoTag,
testScriptTag,
asciiFromBytes,
testScriptString,
testScriptEcmaArray,
testNalUnit;
module('segment parser', {
setup: function() {
parser = new window.videojs.Hls.SegmentParser();
}
});
test('creates an flv header', function() {
var header = Array.prototype.slice.call(parser.getFlvHeader());
ok(header, 'the header is truthy');
equal(9 + 4, header.length, 'the header length is correct');
equal(header[0], 'F'.charCodeAt(0), 'the first character is "F"');
equal(header[1], 'L'.charCodeAt(0), 'the second character is "L"');
equal(header[2], 'V'.charCodeAt(0), 'the third character is "V"');
deepEqual(expectedHeader, header, 'the rest of the header is correct');
});
// Create a PMT packet
// @return {Array} bytes
makePmt = function(options) {
var
result = [],
pcr = options.pcr || 0,
entryCount = 0,
k,
sectionLength;
for (k in options.pids) {
if (k !== 'pcr') {
entryCount++;
}
}
// table_id
result.push(0x02);
// section_syntax_indicator '0' reserved section_length
// 13 + (program_info_length) + (n * 5 + ES_info_length[n])
sectionLength = 13 + (5 * entryCount) + 17;
result.push(0x80 | (0xF00 & sectionLength >>> 8));
result.push(sectionLength & 0xFF);
// program_number
result.push(0x00);
result.push(0x01);
// reserved version_number current_next_indicator
result.push(0x01);
// section_number
result.push(0x00);
// last_section_number
result.push(0x00);
// reserved PCR_PID
result.push(0xe0 | (pcr & (0x1f << 8)));
result.push(pcr & 0xff);
// reserved program_info_length
result.push(0xf0);
result.push(0x11); // hard-coded 17 byte descriptor
// program descriptors
result = result.concat([
0x25, 0x0f, 0xff, 0xff,
0x49, 0x44, 0x33, 0x20,
0xff, 0x49, 0x44, 0x33,
0x20, 0x00, 0x1f, 0x00,
0x01
]);
for (k in options.pids) {
// stream_type
result.push(options.pids[k]);
// reserved elementary_PID
result.push(0xe0 | (k & 0x1f00) >>> 8);
result.push(k & 0xff);
// reserved ES_info_length
result.push(0xf0);
result.push(0x00); // ES_info_length = 0
}
// CRC_32
result.push([0x00, 0x00, 0x00, 0x00]); // invalid CRC but we don't check it
return result;
};
// Create a PAT packet
// @return {Array} bytes
makePat = function(options) {
var
result = [],
programEntries = [],
sectionLength,
k;
// build the program entries first
for (k in options.programs) {
// program_number
programEntries.push((k & 0xFF00) >>> 8);
programEntries.push(k & 0x00FF);
// reserved program_map_pid
programEntries.push((options.programs[k] & 0x1f00) >>> 8);
programEntries.push(options.programs[k] & 0xff);
}
sectionLength = programEntries.length + 5 + 4;
// table_id
result.push(0x00);
// section_syntax_indicator '0' reserved section_length
result.push(0x80 | ((0x300 & sectionLength) >>> 8));
result.push(0xff & sectionLength); // section_length
// transport_stream_id
result.push(0x00);
result.push(0x00);
// reserved version_number current_next_indicator
result.push(0x01); // current_next_indicator is 1
// section_number
result.push(0x00);
// last_section_number
result.push(0x00);
// program entries
result = result.concat(programEntries);
return result;
};
// Create a PAT or PMT packet based on the specified options
// @return {Array} bytes
makePsi = function(options) {
var result = [];
// pointer_field
if (options.payloadUnitStartIndicator) {
result.push(0x00);
}
if (options.programs) {
return result.concat(makePat(options));
}
return result.concat(makePmt(options));
};
// Construct an M2TS packet
// @return {Array} bytes
makePacket = function(options) {
var
result = [],
settings = mergeOptions({
payloadUnitStartIndicator: true,
pid: 0x00
}, options);
// header
// sync_byte
result.push(0x47);
// transport_error_indicator payload_unit_start_indicator transport_priority PID
result.push((settings.pid & 0x1f) << 8 |
(settings.payloadUnitStartIndicator ? 0x40 : 0x00));
result.push(settings.pid & 0xff);
// transport_scrambling_control adaptation_field_control continuity_counter
result.push(0x10);
result = result.concat(makePsi(settings));
// ensure the resulting packet is the correct size
result.length = window.videojs.Hls.SegmentParser.MP2T_PACKET_LENGTH;
return result;
};
test('parses PMTs with program descriptors', function() {
var
h264Type = window.videojs.Hls.SegmentParser.STREAM_TYPES.h264,
adtsType = window.videojs.Hls.SegmentParser.STREAM_TYPES.adts;
parser.parseSegmentBinaryData(new Uint8Array(makePacket({
programs: {
0x01: [0x01]
}
}).concat(makePacket({
pid: 0x01,
pids: {
0x02: h264Type, // h264 video
0x03: adtsType // adts audio
}
}))));
strictEqual(parser.stream.pmtPid, 0x01, 'PMT PID is 1');
strictEqual(parser.stream.programMapTable[h264Type], 0x02, 'video is PID 2');
strictEqual(parser.stream.programMapTable[adtsType], 0x03, 'audio is PID 3');
});
test('ignores network information specific data (NIT) in the PAT', function() {
parser.parseSegmentBinaryData(new Uint8Array(makePacket({
programs: {
0x01: [0x01],
0x00: [0x00] // a NIT has a reserved PID of 0x00
}
})));
ok(true, 'did not throw when a NIT is encountered');
});
test('ignores packets with PCR pids', function() {
parser.parseSegmentBinaryData(new Uint8Array(makePacket({
programs: {
0x01: [0x01]
}
}).concat(makePacket({
pid: 0x01,
pcr: 0x02
}))));
equal(parser.stream.programMapTable.pcrPid, 0x02, 'parsed the PCR pid');
});
test('recognizes metadata streams', function() {
parser.parseSegmentBinaryData(new Uint8Array(makePacket({
programs: {
0x01: [0x01]
}
}).concat(makePacket({
pid: 0x01,
pids: {
// Rec. ITU-T H.222.0 (06/2012), Table 2-34
0x02: 0x15 // Metadata carried in PES packets
}
}))));
equal(parser.stream.programMapTable[0x15], 0x02, 'metadata is PID 2');
});
test('recognizes subsequent metadata packets after the payload start', function() {
var packets = [];
parser.metadataStream.push = function(packet) {
packets.push(packet);
};
parser.parseSegmentBinaryData(new Uint8Array(makePacket({
programs: {
0x01: [0x01]
}
}).concat(makePacket({
pid: 0x01,
pids: {
// Rec. ITU-T H.222.0 (06/2012), Table 2-34
0x02: 0x15 // Metadata carried in PES packets
}
})).concat(makePacket({
pid: 0x02,
payloadUnitStartIndicator: false
}))));
equal(packets.length, 1, 'parsed non-payload metadata packet');
});
test('returns undefined for PTS stats when a track is missing', function() {
parser.parseSegmentBinaryData(new Uint8Array(makePacket({
programs: {
0x01: [0x01]
}
})));
strictEqual(parser.stats.h264Tags(), 0, 'no video tags yet');
strictEqual(parser.stats.aacTags(), 0, 'no audio tags yet');
});
test('parses the first bipbop segment', function() {
parser.parseSegmentBinaryData(window.bcSegment);
ok(parser.tagsAvailable(), 'tags are available');
});
testAudioTag = function(tag) {
var
byte = tag.bytes[11],
format = (byte & 0xF0) >>> 4,
soundRate = byte & 0x03,
soundSize = (byte & 0x2) >>> 1,
soundType = byte & 0x1,
aacPacketType = tag.bytes[12];
equal(10, format, 'the audio format is aac');
equal(3, soundRate, 'the sound rate is 44kHhz');
equal(1, soundSize, 'the sound size is 16-bit samples');
equal(1, soundType, 'the sound type is stereo');
ok(aacPacketType === 0 || aacPacketType === 1, 'aac packets should have a valid type');
};
testVideoTag = function (tag) {
var
byte = tag.bytes[11],
frameType = (byte & 0xF0) >>> 4,
codecId = byte & 0x0F,
packetType = tag.bytes[12],
compositionTime = (tag.view.getInt32(13) & 0xFFFFFF00) >> 8;
// payload starts at tag.bytes[16]
// XXX: I'm not sure that frame types 3-5 are invalid
ok(frameType === 1 || frameType === 2,
'the frame type should be valid');
equal(7, codecId, 'the codec ID is AVC for h264');
ok(packetType <= 2 && packetType >= 0, 'the packet type is within [0, 2]');
if (packetType !== 1) {
equal(0,
compositionTime,
'the composition time is zero for non-NALU packets');
}
// TODO: the rest of the bytes are an NLU unit
if (packetType === 0) {
// AVC decoder configuration record
} else {
// NAL units
testNalUnit(tag.bytes.subarray(16));
}
};
testNalUnit = function(bytes) {
var
nalHeader = bytes[0];
// unitType = nalHeader & 0x1F;
equal(0, (nalHeader & 0x80) >>> 7, 'the first bit is always 0');
// equal(90, (nalHeader & 0x60) >>> 5, 'the NAL reference indicator is something');
// ok(unitType > 0, 'NAL unit type ' + unitType + ' is greater than 0');
// ok(unitType < 22 , 'NAL unit type ' + unitType + ' is less than 22');
};
asciiFromBytes = function(bytes) {
var
string = [],
i = bytes.byteLength;
while (i--) {
string[i] = String.fromCharCode(bytes[i]);
}
return string.join('');
};
testScriptString = function(tag, offset, expected) {
var
type = tag.bytes[offset],
stringLength = tag.view.getUint16(offset + 1),
string;
equal(2, type, 'the script element is of string type');
equal(stringLength, expected.length, 'the script string length is correct');
string = asciiFromBytes(tag.bytes.subarray(offset + 3,
offset + 3 + stringLength));
equal(expected, string, 'the string value is "' + expected + '"');
};
testScriptEcmaArray = function(tag, start) {
var
numItems = tag.view.getUint32(start),
i = numItems,
offset = start + 4,
length,
type;
while (i--) {
length = tag.view.getUint16(offset);
// advance offset to the property value
offset += 2 + length;
type = tag.bytes[offset];
ok(type === 1 || type === 0,
'the ecma array property value type is number or boolean');
offset++;
if (type) {
// boolean
ok(tag.bytes[offset] === 0 || tag.bytes[offset] === 1,
'the script boolean value is 0 or 1');
offset++;
} else {
// number
ok(!isNaN(tag.view.getFloat64(offset)), 'the value is not NaN');
offset += 8;
}
}
equal(tag.bytes[offset], 0, 'the property array terminator is valid');
equal(tag.bytes[offset + 1], 0, 'the property array terminator is valid');
equal(tag.bytes[offset + 2], 9, 'the property array terminator is valid');
};
testScriptTag = function(tag) {
testScriptString(tag, 11, 'onMetaData');
// the onMetaData object is stored as an 'ecma array', an array with non-
// integer indices (i.e. a dictionary or hash-map).
equal(8, tag.bytes[24], 'onMetaData is of ecma array type');
testScriptEcmaArray(tag, 25);
};
test('the flv tags are well-formed', function() {
var
byte,
tag,
type,
minVideoPts,
maxVideoPts,
minAudioPts,
maxAudioPts,
currentPts = 0,
lastTime = 0;
parser.parseSegmentBinaryData(window.bcSegment);
minVideoPts = parser.stats.minVideoPts();
maxVideoPts = parser.stats.maxVideoPts();
minAudioPts = parser.stats.minAudioPts();
maxAudioPts = parser.stats.maxAudioPts();
while (parser.tagsAvailable()) {
tag = parser.getNextTag();
type = tag.bytes[0];
ok(tag.pts >= currentPts, 'presentation time stamps are increasing');
currentPts = tag.pts;
// generic flv headers
switch (type) {
case 8: ok(true, 'the type is audio');
ok(minAudioPts <= currentPts, 'not less than minimum audio PTS');
ok(maxAudioPts >= currentPts, 'not greater than max audio PTS');
break;
case 9: ok(true, 'the type is video');
ok(minVideoPts <= currentPts, 'not less than minimum video PTS');
ok(maxVideoPts >= currentPts, 'not greater than max video PTS');
break;
case 18: ok(true, 'the type is script');
break;
default: ok(false, 'the type (' + type + ') is unrecognized');
}
byte = (tag.view.getUint32(1) & 0xFFFFFF00) >>> 8;
equal(tag.bytes.byteLength - 11 - 4, byte, 'the size field is correct');
byte = tag.view.getUint32(5) & 0xFFFFFF00;
ok(byte >= lastTime,
'timestamp is increasing. last pts: ' + lastTime + ' this pts: ' + byte);
lastTime = byte;
// tag type-specific headers
({
8: testAudioTag,
9: testVideoTag,
18: testScriptTag
})[type](tag);
// previous tag size
equal(tag.bytes.byteLength - 4,
tag.view.getUint32(tag.bytes.byteLength - 4),
'the size of the previous tag is correct');
}
});
})(window);
(function(window, videojs) {
'use strict';
/*
======== A Handy Little QUnit Reference ========
http://api.qunitjs.com/
Test methods:
module(name, {[setup][ ,teardown]})
test(name, callback)
expect(numberOfAssertions)
stop(increment)
start(decrement)
Test assertions:
ok(value, [message])
equal(actual, expected, [message])
notEqual(actual, expected, [message])
deepEqual(actual, expected, [message])
notDeepEqual(actual, expected, [message])
strictEqual(actual, expected, [message])
notStrictEqual(actual, expected, [message])
throws(block, [expected], [message])
*/
var
TransportPacketStream = videojs.mp2t.TransportPacketStream,
transportPacketStream,
TransportParseStream = videojs.mp2t.TransportParseStream,
transportParseStream,
ElementaryStream = videojs.mp2t.ElementaryStream,
elementaryStream,
H264Stream = videojs.mp2t.H264Stream,
h264Stream,
VideoSegmentStream = videojs.mp2t.VideoSegmentStream,
videoSegmentStream,
AacStream = videojs.mp2t.AacStream,
aacStream,
Transmuxer = videojs.mp2t.Transmuxer,
transmuxer,
MP2T_PACKET_LENGTH = videojs.mp2t.MP2T_PACKET_LENGTH,
H264_STREAM_TYPE = videojs.mp2t.H264_STREAM_TYPE,
ADTS_STREAM_TYPE = videojs.mp2t.ADTS_STREAM_TYPE,
packetize,
PAT,
PMT,
standalonePes,
validateTrack,
validateTrackFragment,
transportPacket,
videoPes,
audioPes;
module('MP2T Packet Stream', {
setup: function() {
transportPacketStream = new TransportPacketStream();
}
});
test('empty input does not error', function() {
transportPacketStream.push(new Uint8Array([]));
ok(true, 'did not throw');
});
test('parses a generic packet', function() {
var datas = [];
transportPacketStream.on('data', function(event) {
datas.push(event);
});
transportPacketStream.push(new Uint8Array(188));
equal(1, datas.length, 'fired one event');
equal(datas[0].byteLength, 188, 'delivered the packet');
});
test('buffers partial packets', function() {
var datas = [];
transportPacketStream.on('data', function(event) {
datas.push(event);
});
transportPacketStream.push(new Uint8Array(187));
equal(0, datas.length, 'did not fire an event');
transportPacketStream.push(new Uint8Array(189));
equal(2, datas.length, 'fired events');
equal(188, datas[0].byteLength, 'parsed the first packet');
equal(188, datas[1].byteLength, 'parsed the second packet');
});
test('parses multiple packets delivered at once', function() {
var datas = [];
transportPacketStream.on('data', function(event) {
datas.push(event);
});
transportPacketStream.push(new Uint8Array(188 * 3));
equal(3, datas.length, 'fired three events');
equal(188, datas[0].byteLength, 'parsed the first packet');
equal(188, datas[1].byteLength, 'parsed the second packet');
equal(188, datas[2].byteLength, 'parsed the third packet');
});
test('buffers extra after multiple packets', function() {
var datas = [];
transportPacketStream.on('data', function(event) {
datas.push(event);
});
transportPacketStream.push(new Uint8Array(188 * 2 + 10));
equal(2, datas.length, 'fired two events');
equal(188, datas[0].byteLength, 'parsed the first packet');
equal(188, datas[1].byteLength, 'parsed the second packet');
transportPacketStream.push(new Uint8Array(178));
equal(3, datas.length, 'fired a final event');
equal(188, datas[2].length, 'parsed the finel packet');
});
module('MP2T TransportParseStream', {
setup: function() {
transportPacketStream = new TransportPacketStream();
transportParseStream = new TransportParseStream();
transportPacketStream.pipe(transportParseStream);
}
});
test('emits an error on an invalid packet', function() {
var errors = [];
transportParseStream.on('error', function(error) {
errors.push(error);
});
transportParseStream.push(new Uint8Array(188));
equal(1, errors.length, 'emitted an error');
});
test('parses generic packet properties', function() {
var packet;
transportParseStream.on('data', function(data) {
packet = data;
});
transportParseStream.push(new Uint8Array([
0x47, // sync byte
// tei:0 pusi:1 tp:0 pid:0 0000 0000 0001 tsc:01 afc:10 cc:11 padding: 00
0x40, 0x01, 0x6c
]));
ok(packet.payloadUnitStartIndicator, 'parsed payload_unit_start_indicator');
ok(packet.pid, 'parsed PID');
});
test('parses piped data events', function() {
var packet;
transportParseStream.on('data', function(data) {
packet = data;
});
transportParseStream.push(new Uint8Array([
0x47, // sync byte
// tei:0 pusi:1 tp:0 pid:0 0000 0000 0001 tsc:01 afc:10 cc:11 padding: 00
0x40, 0x01, 0x6c
]));
ok(packet, 'parsed a packet');
});
test('parses a data packet with adaptation fields', function() {
var packet;
transportParseStream.on('data', function(data) {
packet = data;
});
transportParseStream.push(new Uint8Array([
0x47, // sync byte
// tei:0 pusi:1 tp:0 pid:0 0000 0000 0000 tsc:01 afc:10 cc:11 afl:00 0000 00 stuffing:00 0000 00 pscp:00 0001 padding:0000
0x40, 0x00, 0x6c, 0x00, 0x00, 0x10
]));
strictEqual(packet.type, 'pat', 'parsed the packet type');
});
test('parses a PES packet', function() {
var packet;
transportParseStream.on('data', function(data) {
packet = data;
});
// setup a program map table
transportParseStream.programMapTable = {
0x0010: videojs.mp2t.H264_STREAM_TYPE
};
transportParseStream.push(new Uint8Array([
0x47, // sync byte
// tei:0 pusi:1 tp:0 pid:0 0000 0000 0010 tsc:01 afc:01 cc:11 padding:00
0x40, 0x02, 0x5c
]));
strictEqual(packet.type, 'pes', 'parsed a PES packet');
});
test('parses packets with variable length adaptation fields and a payload', function() {
var packet;
transportParseStream.on('data', function(data) {
packet = data;
});
// setup a program map table
transportParseStream.programMapTable = {
0x0010: videojs.mp2t.H264_STREAM_TYPE
};
transportParseStream.push(new Uint8Array([
0x47, // sync byte
// tei:0 pusi:1 tp:0 pid:0 0000 0000 0010 tsc:01 afc:11 cc:11 afl:00 0000 11 stuffing:00 0000 0000 00 pscp:00 0001
0x40, 0x02, 0x7c, 0x0c, 0x00, 0x01
]));
strictEqual(packet.type, 'pes', 'parsed a PES packet');
});
/*
Packet Header:
| sb | tei pusi tp pid:5 | pid | tsc afc cc |
with af:
| afl | ... | <data> |
without af:
| <data> |
PAT:
| pf? | ... |
| tid | ssi '0' r sl:4 | sl | tsi:8 |
| tsi | r vn cni | sn | lsn |
with program_number == '0':
| pn | pn | r np:5 | np |
otherwise:
| pn | pn | r pmp:5 | pmp |
*/
PAT = [
0x47, // sync byte
// tei:0 pusi:1 tp:0 pid:0 0000 0000 0000
0x40, 0x00,
// tsc:01 afc:01 cc:0000 pointer_field:0000 0000
0x50, 0x00,
// tid:0000 0000 ssi:0 0:0 r:00 sl:0000 0000 0000
0x00, 0x00, 0x00,
// tsi:0000 0000 0000 0000
0x00, 0x00,
// r:00 vn:00 000 cni:1 sn:0000 0000 lsn:0000 0000
0x01, 0x00, 0x00,
// pn:0000 0000 0000 0001
0x00, 0x01,
// r:000 pmp:0 0000 0010 0000
0x00, 0x10,
// crc32:0000 0000 0000 0000 0000 0000 0000 0000
0x00, 0x00, 0x00, 0x00
];
test('parses the program map table pid from the program association table (PAT)', function() {
var packet;
transportParseStream.on('data', function(data) {
packet = data;
});
transportParseStream.push(new Uint8Array(PAT));
ok(packet, 'parsed a packet');
strictEqual(0x0010, transportParseStream.pmtPid, 'parsed PMT pid');
});
PMT = [
0x47, // sync byte
// tei:0 pusi:1 tp:0 pid:0 0000 0010 0000
0x40, 0x10,
// tsc:01 afc:01 cc:0000 pointer_field:0000 0000
0x50, 0x00,
// tid:0000 0010 ssi:0 0:0 r:00 sl:0000 0001 0111
0x02, 0x00, 0x17,
// pn:0000 0000 0000 0001
0x00, 0x01,
// r:00 vn:00 000 cni:1 sn:0000 0000 lsn:0000 0000
0x01, 0x00, 0x00,
// r:000 ppid:0 0011 1111 1111
0x03, 0xff,
// r:0000 pil:0000 0000 0000
0x00, 0x00,
// h264
// st:0001 1010 r:000 epid:0 0000 0001 0001
0x1b, 0x00, 0x11,
// r:0000 esil:0000 0000 0000
0x00, 0x00,
// adts
// st:0000 1111 r:000 epid:0 0000 0001 0010
0x0f, 0x00, 0x12,
// r:0000 esil:0000 0000 0000
0x00, 0x00,
// crc
0x00, 0x00, 0x00, 0x00
];
test('parse the elementary streams from a program map table', function() {
var packet;
transportParseStream.on('data', function(data) {
packet = data;
});
transportParseStream.pmtPid = 0x0010;
transportParseStream.push(new Uint8Array(PMT.concat(0, 0, 0, 0, 0)));
ok(packet, 'parsed a packet');
ok(transportParseStream.programMapTable, 'parsed a program map');
strictEqual(0x1b, transportParseStream.programMapTable[0x11], 'associated h264 with pid 0x11');
strictEqual(0x0f, transportParseStream.programMapTable[0x12], 'associated adts with pid 0x12');
strictEqual(transportParseStream.programMapTable[0], undefined, 'ignored trailing stuffing bytes');
deepEqual(transportParseStream.programMapTable, packet.programMapTable, 'recorded the PMT');
});
test('parses an elementary stream packet with just a pts', function() {
var packet;
transportParseStream.on('data', function(data) {
packet = data;
});
transportParseStream.programMapTable = {
0x11: 0x1b // pid 0x11 is h264 data
};
transportParseStream.push(new Uint8Array([
0x47, // sync byte
// tei:0 pusi:1 tp:0 pid:0 0000 0001 0001
0x40, 0x11,
// tsc:01 afc:01 cc:0000
0x50,
// pscp:0000 0000 0000 0000 0000 0001
0x00, 0x00, 0x01,
// sid:0000 0000 ppl:0000 0000 0000 1001
0x00, 0x00, 0x09,
// 10 psc:00 pp:0 dai:1 c:0 ooc:0
0x84,
// pdf:10 ef:1 erf:0 dtmf:0 acif:0 pcf:0 pef:0
0xc0,
// phdl:0000 0101 '0010' pts:000 mb:1 pts:0000 0000
0x05, 0x21, 0x00,
// pts:0000 000 mb:1 pts:0000 0000 pts:0000 000 mb:1
0x01, 0x00, 0x01,
// "data":0101
0x11
]));
ok(packet, 'parsed a packet');
equal('pes', packet.type, 'recognized a PES packet');
equal(0x1b, packet.streamType, 'tracked the stream_type');
equal(1, packet.data.byteLength, 'parsed a single data byte');
equal(0x11, packet.data[0], 'parsed the data');
equal(0, packet.pts, 'parsed the pts');
});
test('parses an elementary stream packet with a pts and dts', function() {
var packet;
transportParseStream.on('data', function(data) {
packet = data;
});
transportParseStream.programMapTable = {
0x11: 0x1b // pid 0x11 is h264 data
};
transportParseStream.push(new Uint8Array([
0x47, // sync byte
// tei:0 pusi:1 tp:0 pid:0 0000 0001 0001
0x40, 0x11,
// tsc:01 afc:01 cc:0000
0x50,
// pscp:0000 0000 0000 0000 0000 0001
0x00, 0x00, 0x01,
// sid:0000 0000 ppl:0000 0000 0000 1110
0x00, 0x00, 0x0e,
// 10 psc:00 pp:0 dai:1 c:0 ooc:0
0x84,
// pdf:11 ef:1 erf:0 dtmf:0 acif:0 pcf:0 pef:0
0xe0,
// phdl:0000 1010 '0011' pts:000 mb:1 pts:0000 0000
0x0a, 0x21, 0x00,
// pts:0000 000 mb:1 pts:0000 0000 pts:0000 100 mb:1
0x01, 0x00, 0x09,
// '0001' dts:000 mb:1 dts:0000 0000 dts:0000 000 mb:1
0x11, 0x00, 0x01,
// dts:0000 0000 dts:0000 010 mb:1
0x00, 0x05,
// "data":0101
0x11
]));
ok(packet, 'parsed a packet');
equal('pes', packet.type, 'recognized a PES packet');
equal(0x1b, packet.streamType, 'tracked the stream_type');
equal(1, packet.data.byteLength, 'parsed a single data byte');
equal(0x11, packet.data[0], 'parsed the data');
equal(4 / 90, packet.pts, 'parsed the pts');
equal(2 / 90, packet.dts, 'parsed the dts');
});
/**
* Helper function to create transport stream PES packets
* @param pid {uint8} - the program identifier (PID)
* @param data {arraylike} - the payload bytes
* @payload first {boolean} - true if this PES should be a payload
* unit start
*/
transportPacket = function(pid, data, first) {
var
adaptationFieldLength = 188 - data.length - (first ? 15 : 14),
// transport_packet(), Rec. ITU-T H.222.0, Table 2-2
result = [
// sync byte
0x47,
// tei:0 pusi:1 tp:0 pid:0 0000 0001 0001
0x40, pid,
// tsc:01 afc:11 cc:0000
0x70
].concat([
// afl
adaptationFieldLength & 0xff,
// di:0 rai:0 espi:0 pf:0 of:0 spf:0 tpdf:0 afef:0
0x00
]),
i;
i = adaptationFieldLength - 1;
while (i--) {
// stuffing_bytes
result.push(0xff);
}
// PES_packet(), Rec. ITU-T H.222.0, Table 2-21
result = result.concat([
// pscp:0000 0000 0000 0000 0000 0001
0x00, 0x00, 0x01,
// sid:0000 0000 ppl:0000 0000 0000 0101
0x00, 0x00, 0x05,
// 10 psc:00 pp:0 dai:1 c:0 ooc:0
0x84,
// pdf:00 ef:1 erf:0 dtmf:0 acif:0 pcf:0 pef:0
0x20,
// phdl:0000 0000
0x00
]);
if (first) {
result.push(0x00);
}
return result.concat(data);
};
/**
* Helper function to create video PES packets
* @param data {arraylike} - the payload bytes
* @payload first {boolean} - true if this PES should be a payload
* unit start
*/
videoPes = function(data, first) {
return transportPacket(0x11, [
// NAL unit start code
0x00, 0x00, 0x01
].concat(data), first);
};
standalonePes = videoPes([0xaf, 0x01], true);
/**
* Helper function to create audio PES packets
* @param data {arraylike} - the payload bytes
* @payload first {boolean} - true if this PES should be a payload
* unit start
*/
audioPes = function(data, first) {
var frameLength = data.length + 7;
return transportPacket(0x12, [
0xff, 0xf1, // no CRC
0x10, // AAC Main, 44.1KHz
0xb0 | ((frameLength & 0x1800) >> 11), // 2 channels
(frameLength & 0x7f8) >> 3,
((frameLength & 0x07) << 5) + 7, // frame length in bytes
0x00 // one AAC per ADTS frame
].concat(data), first);
};
test('parses an elementary stream packet without a pts or dts', function() {
var packet;
transportParseStream.on('data', function(data) {
packet = data;
});
// pid 0x11 is h264 data
transportParseStream.programMapTable = {
0x11: H264_STREAM_TYPE
};
transportParseStream.push(new Uint8Array(standalonePes));
ok(packet, 'parsed a packet');
equal('pes', packet.type, 'recognized a PES packet');
equal(0x1b, packet.streamType, 'tracked the stream_type');
equal(2 + 4, packet.data.byteLength, 'parsed two data bytes');
equal(0xaf, packet.data[packet.data.length - 2], 'parsed the first data byte');
equal(0x01, packet.data[packet.data.length - 1], 'parsed the second data byte');
ok(!packet.pts, 'did not parse a pts');
ok(!packet.dts, 'did not parse a dts');
});
module('MP2T ElementaryStream', {
setup: function() {
elementaryStream = new ElementaryStream();
}
});
packetize = function(data) {
var packet = new Uint8Array(MP2T_PACKET_LENGTH);
packet.set(data);
return packet;
};
test('parses metadata events from PSI packets', function() {
var
metadatas = [],
datas = 0,
sortById = function(left, right) {
return left.id - right.id;
};
elementaryStream.on('data', function(data) {
if (data.type === 'metadata') {
metadatas.push(data);
}
datas++;
});
elementaryStream.push({
type: 'pat'
});
elementaryStream.push({
type: 'pmt',
programMapTable: {
1: 0x1b,
2: 0x0f
}
});
equal(1, datas, 'data fired');
equal(1, metadatas.length, 'metadata generated');
metadatas[0].tracks.sort(sortById);
deepEqual(metadatas[0].tracks, [{
id: 1,
codec: 'avc',
type: 'video'
}, {
id: 2,
codec: 'adts',
type: 'audio'
}], 'identified two tracks');
});
test('parses standalone program stream packets', function() {
var packets = [];
elementaryStream.on('data', function(packet) {
packets.push(packet);
});
elementaryStream.push({
type: 'pes',
streamType: ADTS_STREAM_TYPE,
payloadUnitStartIndicator: true,
pts: 7,
dts: 8,
data: new Uint8Array(19)
});
elementaryStream.end();
equal(1, packets.length, 'built one packet');
equal('audio', packets[0].type, 'identified audio data');
equal(19, packets[0].data.byteLength, 'parsed the correct payload size');
});
test('aggregates program stream packets from the transport stream', function() {
var events = [];
elementaryStream.on('data', function(event) {
events.push(event);
});
elementaryStream.push({
type: 'pes',
streamType: H264_STREAM_TYPE,
payloadUnitStartIndicator: true,
pts: 7,
dts: 8,
data: new Uint8Array(7)
});
equal(0, events.length, 'buffers partial packets');
elementaryStream.push({
type: 'pes',
streamType: H264_STREAM_TYPE,
data: new Uint8Array(13)
});
elementaryStream.end();
equal(1, events.length, 'built one packet');
equal('video', events[0].type, 'identified video data');
equal(events[0].pts, 7, 'passed along the pts');
equal(events[0].dts, 8, 'passed along the dts');
equal(20, events[0].data.byteLength, 'concatenated transport packets');
});
test('buffers audio and video program streams individually', function() {
var events = [];
elementaryStream.on('data', function(event) {
events.push(event);
});
elementaryStream.push({
type: 'pes',
payloadUnitStartIndicator: true,
streamType: H264_STREAM_TYPE,
data: new Uint8Array(1)
});
elementaryStream.push({
type: 'pes',
payloadUnitStartIndicator: true,
streamType: ADTS_STREAM_TYPE,
data: new Uint8Array(1)
});
equal(0, events.length, 'buffers partial packets');
elementaryStream.push({
type: 'pes',
streamType: H264_STREAM_TYPE,
data: new Uint8Array(1)
});
elementaryStream.push({
type: 'pes',
streamType: ADTS_STREAM_TYPE,
data: new Uint8Array(1)
});
elementaryStream.end();
equal(2, events.length, 'parsed a complete packet');
equal('video', events[0].type, 'identified video data');
equal('audio', events[1].type, 'identified audio data');
});
test('flushes the buffered packets when a new one of that type is started', function() {
var packets = [];
elementaryStream.on('data', function(packet) {
packets.push(packet);
});
elementaryStream.push({
type: 'pes',
payloadUnitStartIndicator: true,
streamType: H264_STREAM_TYPE,
data: new Uint8Array(1)
});
elementaryStream.push({
type: 'pes',
payloadUnitStartIndicator: true,
streamType: ADTS_STREAM_TYPE,
data: new Uint8Array(7)
});
elementaryStream.push({
type: 'pes',
streamType: H264_STREAM_TYPE,
data: new Uint8Array(1)
});
equal(0, packets.length, 'buffers packets by type');
elementaryStream.push({
type: 'pes',
payloadUnitStartIndicator: true,
streamType: H264_STREAM_TYPE,
data: new Uint8Array(1)
});
equal(1, packets.length, 'built one packet');
equal('video', packets[0].type, 'identified video data');
equal(2, packets[0].data.byteLength, 'concatenated packets');
elementaryStream.end();
equal(3, packets.length, 'built tow more packets');
equal('video', packets[1].type, 'identified video data');
equal(1, packets[1].data.byteLength, 'parsed the video payload');
equal('audio', packets[2].type, 'identified audio data');
equal(7, packets[2].data.byteLength, 'parsed the audio payload');
});
test('drops packets with unknown stream types', function() {
var packets = [];
elementaryStream.on('data', function(packet) {
packets.push(packet);
});
elementaryStream.push({
type: 'pes',
payloadUnitStartIndicator: true,
data: new Uint8Array(1)
});
elementaryStream.push({
type: 'pes',
payloadUnitStartIndicator: true,
data: new Uint8Array(1)
});
equal(packets.length, 0, 'ignored unknown packets');
});
module('H264 Stream', {
setup: function() {
h264Stream = new H264Stream();
}
});
test('unpacks nal units from simple byte stream framing', function() {
var data;
h264Stream.on('data', function(event) {
data = event;
});
// the simplest byte stream framing:
h264Stream.push({
type: 'video',
data: new Uint8Array([
0x00, 0x00, 0x00, 0x01,
0x09, 0x07,
0x00, 0x00, 0x01
])
});
ok(data, 'generated a data event');
equal(data.nalUnitType, 'access_unit_delimiter_rbsp', 'identified an access unit delimiter');
equal(data.data.length, 2, 'calculated nal unit length');
equal(data.data[1], 7, 'read a payload byte');
});
test('unpacks nal units from byte streams split across pushes', function() {
var data;
h264Stream.on('data', function(event) {
data = event;
});
// handles byte streams split across pushes
h264Stream.push({
type: 'video',
data: new Uint8Array([
0x00, 0x00, 0x00, 0x01,
0x09, 0x07, 0x06, 0x05,
0x04
])
});
ok(!data, 'buffers NAL units across events');
h264Stream.push({
type: 'video',
data: new Uint8Array([
0x03, 0x02, 0x01,
0x00, 0x00, 0x01
])
});
ok(data, 'generated a data event');
equal(data.nalUnitType, 'access_unit_delimiter_rbsp', 'identified an access unit delimiter');
equal(data.data.length, 8, 'calculated nal unit length');
equal(data.data[1], 7, 'read a payload byte');
});
test('buffers nal unit trailing zeros across pushes', function() {
var data = [];
h264Stream.on('data', function(event) {
data.push(event);
});
// lots of zeros after the nal, stretching into the next push
h264Stream.push({
type: 'video',
data: new Uint8Array([
0x00, 0x00, 0x00, 0x01,
0x09, 0x07, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00
])
});
equal(data.length, 1, 'delivered the first nal');
h264Stream.push({
type: 'video',
data: new Uint8Array([
0x00, 0x00,
0x00, 0x00, 0x01,
0x09, 0x06,
0x00, 0x00, 0x01
])
});
equal(data.length, 2, 'generated data events');
equal(data[0].data.length, 2, 'ignored trailing zeros');
equal(data[0].data[0], 0x09, 'found the first nal start');
equal(data[1].data.length, 2, 'found the following nal start');
equal(data[1].data[0], 0x09, 'found the second nal start');
});
test('unpacks nal units from byte streams with split sync points', function() {
var data;
h264Stream.on('data', function(event) {
data = event;
});
// handles sync points split across pushes
h264Stream.push({
type: 'video',
data: new Uint8Array([
0x00, 0x00, 0x00, 0x01,
0x09, 0x07,
0x00])
});
ok(!data, 'buffers NAL units across events');
h264Stream.push({
type: 'video',
data: new Uint8Array([
0x00, 0x01
])
});
ok(data, 'generated a data event');
equal(data.nalUnitType, 'access_unit_delimiter_rbsp', 'identified an access unit delimiter');
equal(data.data.length, 2, 'calculated nal unit length');
equal(data.data[1], 7, 'read a payload byte');
});
test('parses nal unit types', function() {
var data;
h264Stream.on('data', function(event) {
data = event;
});
h264Stream.push({
type: 'video',
data: new Uint8Array([
0x00, 0x00, 0x00, 0x01,
0x09
])
});
h264Stream.end();
ok(data, 'generated a data event');
equal(data.nalUnitType, 'access_unit_delimiter_rbsp', 'identified an access unit delimiter');
data = null;
h264Stream.push({
type: 'video',
data: new Uint8Array([
0x00, 0x00, 0x00, 0x01,
0x07,
0x27, 0x42, 0xe0, 0x0b,
0xa9, 0x18, 0x60, 0x9d,
0x80, 0x35, 0x06, 0x01,
0x06, 0xb6, 0xc2, 0xb5,
0xef, 0x7c, 0x04
])
});
h264Stream.end();
ok(data, 'generated a data event');
equal(data.nalUnitType, 'seq_parameter_set_rbsp', 'identified a sequence parameter set');
data = null;
h264Stream.push({
type: 'video',
data: new Uint8Array([
0x00, 0x00, 0x00, 0x01,
0x08, 0x01
])
});
h264Stream.end();
ok(data, 'generated a data event');
equal(data.nalUnitType, 'pic_parameter_set_rbsp', 'identified a picture parameter set');
data = null;
h264Stream.push({
type: 'video',
data: new Uint8Array([
0x00, 0x00, 0x00, 0x01,
0x05, 0x01
])
});
h264Stream.end();
ok(data, 'generated a data event');
equal(data.nalUnitType, 'slice_layer_without_partitioning_rbsp_idr', 'identified a key frame');
});
// MP4 expects H264 (aka AVC) data to be in storage format. Storage
// format is optimized for reliable, random-access media in contrast
// to the byte stream format that retransmits metadata regularly to
// allow decoders to quickly begin operation from wherever in the
// broadcast they begin receiving.
// Details on the byte stream format can be found in Annex B of
// Recommendation ITU-T H.264.
// The storage format is described in ISO/IEC 14496-15
test('strips byte stream framing during parsing', function() {
var data = [];
h264Stream.on('data', function(event) {
data.push(event);
});
h264Stream.push({
type: 'video',
data: new Uint8Array([
// -- NAL unit start
// zero_byte
0x00,
// start_code_prefix_one_3bytes
0x00, 0x00, 0x01,
// nal_unit_type (picture parameter set)
0x08,
// fake data
0x01, 0x02, 0x03, 0x04,
0x05, 0x06, 0x07,
// trailing_zero_8bits * 5
0x00, 0x00, 0x00, 0x00,
0x00,
// -- NAL unit start
// zero_byte
0x00,
// start_code_prefix_one_3bytes
0x00, 0x00, 0x01,
// nal_unit_type (access_unit_delimiter_rbsp)
0x09,
// fake data
0x06, 0x05, 0x04, 0x03,
0x02, 0x01, 0x00
])
});
h264Stream.end();
equal(data.length, 2, 'parsed two NAL units');
deepEqual(new Uint8Array([
0x08,
0x01, 0x02, 0x03, 0x04,
0x05, 0x06, 0x07
]), new Uint8Array(data[0].data), 'parsed the first NAL unit');
deepEqual(new Uint8Array([
0x09,
0x06, 0x05, 0x04, 0x03,
0x02, 0x01, 0x00
]), new Uint8Array(data[1].data), 'parsed the second NAL unit');
});
module('VideoSegmentStream', {
setup: function() {
videoSegmentStream = new VideoSegmentStream({});
}
});
// see ISO/IEC 14496-15, Section 5 "AVC elementary streams and sample definitions"
test('concatenates NAL units into AVC elementary streams', function() {
var segment, boxes;
videoSegmentStream.on('data', function(data) {
segment = data;
});
videoSegmentStream.push({
data: new Uint8Array([
0x08,
0x01, 0x02, 0x03
])
});
videoSegmentStream.push({
data: new Uint8Array([
0x08,
0x04, 0x03, 0x02, 0x01, 0x00
])
});
videoSegmentStream.end();
ok(segment, 'generated a data event');
boxes = videojs.inspectMp4(segment);
equal(boxes[1].byteLength,
(4 + 4) + (4 + 6),
'wrote the correct number of bytes');
deepEqual(new Uint8Array(segment.subarray(boxes[0].size + 8)), new Uint8Array([
0, 0, 0, 4,
0x08, 0x01, 0x02, 0x03,
0, 0, 0, 6,
0x08, 0x04, 0x03, 0x02, 0x01, 0x00
]), 'wrote an AVC stream into the mdat');
});
test('scales DTS values from milliseconds to 90kHz', function() {
var segment, boxes, samples;
videoSegmentStream.on('data', function(data) {
segment = data;
});
videoSegmentStream.push({
data: new Uint8Array([0x09, 0x01]),
nalUnitType: 'access_unit_delimiter_rbsp',
dts: 1
});
videoSegmentStream.push({
data: new Uint8Array([0x09, 0x01]),
nalUnitType: 'access_unit_delimiter_rbsp',
dts: 2
});
videoSegmentStream.push({
data: new Uint8Array([0x09, 0x01]),
nalUnitType: 'access_unit_delimiter_rbsp',
dts: 4
});
videoSegmentStream.end();
boxes = videojs.inspectMp4(segment);
samples = boxes[0].boxes[1].boxes[2].samples;
equal(samples.length, 3, 'generated two samples');
equal(samples[0].duration, 1 * 90, 'multiplied DTS duration by 90');
equal(samples[1].duration, 2 * 90, 'multiplied DTS duration by 90');
equal(samples[2].duration, 2 * 90, 'inferred the final sample duration');
});
module('AAC Stream', {
setup: function() {
aacStream = new AacStream();
}
});
test('generates AAC frame events from ADTS bytes', function() {
var frames = [];
aacStream.on('data', function(frame) {
frames.push(frame);
});
aacStream.push({
type: 'audio',
data: new Uint8Array([
0xff, 0xf1, // no CRC
0x10, // AAC Main, 44.1KHz
0xbc, 0x01, 0x20, // 2 channels, frame length 9 bytes
0x00, // one AAC per ADTS frame
0x12, 0x34, // AAC payload
0x56, 0x78 // extra junk that should be ignored
])
});
equal(frames.length, 1, 'generated one frame');
deepEqual(new Uint8Array(frames[0].data),
new Uint8Array([0x12, 0x34]),
'extracted AAC frame');
equal(frames[0].channelcount, 2, 'parsed channelcount');
equal(frames[0].samplerate, 44100, 'parsed samplerate');
// Chrome only supports 8, 16, and 32 bit sample sizes. Assuming the
// default value of 16 in ISO/IEC 14496-12 AudioSampleEntry is
// acceptable.
equal(frames[0].samplesize, 16, 'parsed samplesize');
});
test('parses across packets', function() {
var frames = [];
aacStream.on('data', function(frame) {
frames.push(frame);
});
aacStream.push({
type: 'audio',
data: new Uint8Array([
0xff, 0xf1, // no CRC
0x10, // AAC Main, 44.1KHz
0xbc, 0x01, 0x20, // 2 channels, frame length 9 bytes
0x00, // one AAC per ADTS frame
0x12, 0x34 // AAC payload 1
])
});
aacStream.push({
type: 'audio',
data: new Uint8Array([
0xff, 0xf1, // no CRC
0x10, // AAC Main, 44.1KHz
0xbc, 0x01, 0x20, // 2 channels, frame length 9 bytes
0x00, // one AAC per ADTS frame
0x9a, 0xbc, // AAC payload 2
0xde, 0xf0 // extra junk that should be ignored
])
});
equal(frames.length, 2, 'parsed two frames');
deepEqual(new Uint8Array(frames[1].data),
new Uint8Array([0x9a, 0xbc]),
'extracted the second AAC frame');
});
// not handled: ADTS with CRC
// ADTS with payload broken across push events
module('Transmuxer', {
setup: function() {
transmuxer = new Transmuxer();
}
});
test('generates a video init segment', function() {
var segments = [];
transmuxer.on('data', function(segment) {
segments.push(segment);
});
transmuxer.push(packetize(PAT));
transmuxer.push(packetize(PMT));
transmuxer.push(packetize(videoPes([
0x08, 0x01 // pic_parameter_set_rbsp
], true)));
transmuxer.push(packetize(videoPes([
0x07, // seq_parameter_set_rbsp
0x27, 0x42, 0xe0, 0x0b,
0xa9, 0x18, 0x60, 0x9d,
0x80, 0x53, 0x06, 0x01,
0x06, 0xb6, 0xc2, 0xb5,
0xef, 0x7c, 0x04
], false)));
transmuxer.end();
equal(segments.length, 2, 'generated init and media segments');
ok(segments[0].data, 'wrote data in the init segment');
equal(segments[0].type, 'video', 'video is the segment type');
});
test('generates an audio init segment', function() {
var segments = [];
transmuxer.on('data', function(segment) {
segments.push(segment);
});
transmuxer.push(packetize(PAT));
transmuxer.push(packetize(PMT));
transmuxer.push(packetize(audioPes([
0x00, 0x01
], true)));
transmuxer.end();
equal(segments.length, 2, 'generated init and media segments');
ok(segments[0].data, 'wrote data in the init segment');
equal(segments[0].type, 'audio', 'audio is the segment type');
});
test('buffers video samples until ended', function() {
var samples = [], boxes;
transmuxer.on('data', function(data) {
samples.push(data);
});
transmuxer.push(packetize(PAT));
transmuxer.push(packetize(PMT));
// buffer a NAL
transmuxer.push(packetize(videoPes([0x09, 0x01], true)));
transmuxer.push(packetize(videoPes([0x00, 0x02])));
// add an access_unit_delimiter_rbsp
transmuxer.push(packetize(videoPes([0x09, 0x03])));
transmuxer.push(packetize(videoPes([0x00, 0x04])));
transmuxer.push(packetize(videoPes([0x00, 0x05])));
// flush everything
transmuxer.end();
equal(samples.length, 1, 'emitted one event');
boxes = videojs.inspectMp4(samples[0].data);
equal(boxes.length, 2, 'generated two boxes');
equal(boxes[0].type, 'moof', 'the first box is a moof');
equal(boxes[1].type, 'mdat', 'the second box is a mdat');
deepEqual(new Uint8Array(samples[0].data.subarray(boxes[0].size + 8)),
new Uint8Array([
0, 0, 0, 2,
0x09, 0x01,
0, 0, 0, 2,
0x00, 0x02,
0, 0, 0, 2,
0x09, 0x03,
0, 0, 0, 2,
0x00, 0x04,
0, 0, 0, 2,
0x00, 0x05]),
'concatenated NALs into an mdat');
});
validateTrack = function(track, metadata) {
var mdia, handlerType;
equal(track.type, 'trak', 'wrote the track type');
equal(track.boxes.length, 2, 'wrote track children');
equal(track.boxes[0].type, 'tkhd', 'wrote the track header');
if (metadata) {
if (metadata.trackId) {
equal(track.boxes[0].trackId, metadata.trackId, 'wrote the track id');
}
if (metadata.width) {
equal(track.boxes[0].width, metadata.width, 'wrote the width');
}
if (metadata.height) {
equal(track.boxes[0].height, metadata.height, 'wrote the height');
}
}
mdia = track.boxes[1];
equal(mdia.type, 'mdia', 'wrote the media');
equal(mdia.boxes.length, 3, 'wrote the mdia children');
equal(mdia.boxes[0].type, 'mdhd', 'wrote the media header');
equal(mdia.boxes[0].language, 'und', 'the language is undefined');
equal(mdia.boxes[0].duration, 0xffffffff, 'the duration is at maximum');
equal(mdia.boxes[1].type, 'hdlr', 'wrote the media handler');
handlerType = mdia.boxes[1].handlerType;
equal(mdia.boxes[2].type, 'minf', 'wrote the media info');
};
validateTrackFragment = function(track, segment, metadata) {
var tfhd, trun, sdtp, i, j, sample, nalUnitType;
equal(track.type, 'traf', 'wrote a track fragment');
equal(track.boxes.length, 4, 'wrote four track fragment children');
tfhd = track.boxes[0];
equal(tfhd.type, 'tfhd', 'wrote a track fragment header');
equal(tfhd.trackId, metadata.trackId, 'wrote the track id');
equal(track.boxes[1].type,
'tfdt',
'wrote a track fragment decode time box');
ok(track.boxes[1].baseMediaDecodeTime >= 0, 'base decode time is non-negative');
trun = track.boxes[2];
ok(trun.dataOffset >= 0, 'set data offset');
equal(trun.dataOffset,
metadata.mdatOffset + 8,
'trun data offset is the size of the moof');
ok(trun.samples.length > 0, 'generated media samples');
for (i = 0, j = trun.dataOffset; i < trun.samples.length; i++) {
sample = trun.samples[i];
ok(sample.duration > 0, 'wrote a positive duration for sample ' + i);
ok(sample.size > 0, 'wrote a positive size for sample ' + i);
ok(sample.compositionTimeOffset >= 0,
'wrote a positive composition time offset for sample ' + i);
ok(sample.flags, 'wrote sample flags');
equal(sample.flags.isLeading, 0, 'the leading nature is unknown');
notEqual(sample.flags.dependsOn, 0, 'sample dependency is not unknown');
notEqual(sample.flags.dependsOn, 4, 'sample dependency is valid');
nalUnitType = segment[j + 4] & 0x1F;
equal(nalUnitType, 9, 'samples begin with an access_unit_delimiter_rbsp');
equal(sample.flags.isDependedOn, 0, 'dependency of other samples is unknown');
equal(sample.flags.hasRedundancy, 0, 'sample redundancy is unknown');
equal(sample.flags.degradationPriority, 0, 'sample degradation priority is zero');
j += sample.size; // advance to the next sample in the mdat
}
sdtp = track.boxes[3];
equal(trun.samples.length,
sdtp.samples.length,
'wrote an equal number of trun and sdtp samples');
for (i = 0; i < sdtp.samples.length; i++) {
sample = sdtp.samples[i];
notEqual(sample.dependsOn, 0, 'sample dependency is not unknown');
equal(trun.samples[i].flags.dependsOn,
sample.dependsOn,
'wrote a consistent dependsOn');
equal(trun.samples[i].flags.isDependedOn,
sample.isDependedOn,
'wrote a consistent isDependedOn');
equal(trun.samples[i].flags.hasRedundancy,
sample.hasRedundancy,
'wrote a consistent hasRedundancy');
}
};
test('parses an example mp2t file and generates media segments', function() {
var
videoSegments = [],
audioSegments = [],
sequenceNumber = window.Infinity,
i, boxes, mfhd;
transmuxer.on('data', function(segment) {
if (segment.type === 'video') {
videoSegments.push(segment);
} else if (segment.type === 'audio') {
audioSegments.push(segment);
}
});
transmuxer.push(window.bcSegment);
transmuxer.end();
equal(videoSegments.length, 2, 'generated two video segments');
equal(audioSegments.length, 2, 'generated two audio segments');
boxes = videojs.inspectMp4(videoSegments[0].data);
equal(boxes.length, 2, 'video init segments are composed of two boxes');
equal(boxes[0].type, 'ftyp', 'the first box is an ftyp');
equal(boxes[1].type, 'moov', 'the second box is a moov');
equal(boxes[1].boxes[0].type, 'mvhd', 'generated an mvhd');
validateTrack(boxes[1].boxes[1], {
trackId: 256,
width: 388,
height: 300
});
// validateTrack(boxes[1].boxes[2], {
// trackId: 257
// });
// equal(boxes[1].boxes[3].type, 'mvex', 'generated an mvex');
boxes = videojs.inspectMp4(videoSegments[1].data);
ok(boxes.length > 0, 'video media segments are not empty');
ok(boxes.length % 2 === 0, 'video media segments are composed of pairs of boxes');
for (i = 0; i < boxes.length; i += 2) {
equal(boxes[i].type, 'moof', 'first box is a moof');
equal(boxes[i].boxes.length, 2, 'the moof has two children');
mfhd = boxes[i].boxes[0];
equal(mfhd.type, 'mfhd', 'mfhd is a child of the moof');
ok(mfhd.sequenceNumber < sequenceNumber, 'sequence numbers are increasing');
sequenceNumber = mfhd.sequenceNumber;
equal(boxes[i + 1].type, 'mdat', 'second box is an mdat');
validateTrackFragment(boxes[i].boxes[1], videoSegments[1].data, {
trackId: 256,
width: 388,
height: 300,
mdatOffset: boxes[0].size
});
}
});
})(window, window.videojs);
......@@ -19,13 +19,6 @@
<script src="../src/videojs-hls.js"></script>
<script src="../src/xhr.js"></script>
<script src="../src/stream.js"></script>
<script src="../src/flv-tag.js"></script>
<script src="../src/exp-golomb.js"></script>
<script src="../src/h264-extradata.js"></script>
<script src="../src/h264-stream.js"></script>
<script src="../src/aac-stream.js"></script>
<script src="../src/metadata-stream.js"></script>
<script src="../src/segment-parser.js"></script>
<!-- M3U8 -->
<script src="../src/m3u8/m3u8-parser.js"></script>
......@@ -42,11 +35,6 @@
<script src="tsSegment-bc.js"></script>
<script src="../src/bin-utils.js"></script>
<!-- mp4 utilities -->
<script src="../src/mp4-generator.js"></script>
<script src="../src/transmuxer.js"></script>
<script src="muxer/js/mp4-inspector.js"></script>
<!-- Test cases -->
<script>
module('environment');
......@@ -56,18 +44,10 @@
});
</script>
<script src="videojs-hls_test.js"></script>
<script src="segment-parser.js"></script>
<script src="h264-stream_test.js"></script>
<script src="exp-golomb_test.js"></script>
<script src="flv-tag_test.js"></script>
<script src="metadata-stream_test.js"></script>
<script src="m3u8_test.js"></script>
<script src="playlist_test.js"></script>
<script src="playlist-loader_test.js"></script>
<script src="decrypter_test.js"></script>
<script src="transmuxer_test.js"></script>
<script src="mp4-inspector_test.js"></script>
<script src="mp4-generator_test.js"></script>
</head>
<body>
<div id="qunit"></div>
......