f8527bfc by David LaPalomento

Remove old transmuxing infrastructure

All the transmuxing code has moved into mux.js so we can get rid of the remnants in this project.
1 parent a63f0154
1 (function(window) {
2 var
3 FlvTag = window.videojs.Hls.FlvTag,
4 adtsSampleingRates = [
5 96000, 88200,
6 64000, 48000,
7 44100, 32000,
8 24000, 22050,
9 16000, 12000
10 ];
11
12 window.videojs.Hls.AacStream = function() {
13 var
14 next_pts, // :uint
15 state, // :uint
16 pes_length, // :int
17 lastMetaPts,
18
19 adtsProtectionAbsent, // :Boolean
20 adtsObjectType, // :int
21 adtsSampleingIndex, // :int
22 adtsChanelConfig, // :int
23 adtsFrameSize, // :int
24 adtsSampleCount, // :int
25 adtsDuration, // :int
26
27 aacFrame, // :FlvTag = null;
28 extraData; // :uint;
29
30 this.tags = [];
31
32 // (pts:uint):void
33 this.setTimeStampOffset = function(pts) {
34
35 // keep track of the last time a metadata tag was written out
36 // set the initial value so metadata will be generated before any
37 // payload data
38 lastMetaPts = pts - 1000;
39 };
40
41 // (pts:uint, pes_size:int, dataAligned:Boolean):void
42 this.setNextTimeStamp = function(pts, pes_size, dataAligned) {
43 next_pts = pts;
44 pes_length = pes_size;
45
46 // If data is aligned, flush all internal buffers
47 if (dataAligned) {
48 state = 0;
49 }
50 };
51
52 // (data:ByteArray, o:int = 0, l:int = 0):void
53 this.writeBytes = function(data, offset, length) {
54 var
55 end, // :int
56 newExtraData, // :uint
57 bytesToCopy; // :int
58
59 // default arguments
60 offset = offset || 0;
61 length = length || 0;
62
63 // Do not allow more than 'pes_length' bytes to be written
64 length = (pes_length < length ? pes_length : length);
65 pes_length -= length;
66 end = offset + length;
67 while (offset < end) {
68 switch (state) {
69 default:
70 state = 0;
71 break;
72 case 0:
73 if (offset >= end) {
74 return;
75 }
76 if (0xFF !== data[offset]) {
77 console.assert(false, 'Error no ATDS header found');
78 offset += 1;
79 state = 0;
80 return;
81 }
82
83 offset += 1;
84 state = 1;
85 break;
86 case 1:
87 if (offset >= end) {
88 return;
89 }
90 if (0xF0 !== (data[offset] & 0xF0)) {
91 console.assert(false, 'Error no ATDS header found');
92 offset +=1;
93 state = 0;
94 return;
95 }
96
97 adtsProtectionAbsent = !!(data[offset] & 0x01);
98
99 offset += 1;
100 state = 2;
101 break;
102 case 2:
103 if (offset >= end) {
104 return;
105 }
106 adtsObjectType = ((data[offset] & 0xC0) >>> 6) + 1;
107 adtsSampleingIndex = ((data[offset] & 0x3C) >>> 2);
108 adtsChanelConfig = ((data[offset] & 0x01) << 2);
109
110 offset += 1;
111 state = 3;
112 break;
113 case 3:
114 if (offset >= end) {
115 return;
116 }
117 adtsChanelConfig |= ((data[offset] & 0xC0) >>> 6);
118 adtsFrameSize = ((data[offset] & 0x03) << 11);
119
120 offset += 1;
121 state = 4;
122 break;
123 case 4:
124 if (offset >= end) {
125 return;
126 }
127 adtsFrameSize |= (data[offset] << 3);
128
129 offset += 1;
130 state = 5;
131 break;
132 case 5:
133 if(offset >= end) {
134 return;
135 }
136 adtsFrameSize |= ((data[offset] & 0xE0) >>> 5);
137 adtsFrameSize -= (adtsProtectionAbsent ? 7 : 9);
138
139 offset += 1;
140 state = 6;
141 break;
142 case 6:
143 if (offset >= end) {
144 return;
145 }
146 adtsSampleCount = ((data[offset] & 0x03) + 1) * 1024;
147 adtsDuration = (adtsSampleCount * 1000) / adtsSampleingRates[adtsSampleingIndex];
148
149 newExtraData = (adtsObjectType << 11) |
150 (adtsSampleingIndex << 7) |
151 (adtsChanelConfig << 3);
152
153 // write out metadata tags every 1 second so that the decoder
154 // is re-initialized quickly after seeking into a different
155 // audio configuration
156 if (newExtraData !== extraData || next_pts - lastMetaPts >= 1000) {
157 aacFrame = new FlvTag(FlvTag.METADATA_TAG);
158 aacFrame.pts = next_pts;
159 aacFrame.dts = next_pts;
160
161 // AAC is always 10
162 aacFrame.writeMetaDataDouble("audiocodecid", 10);
163 aacFrame.writeMetaDataBoolean("stereo", 2 === adtsChanelConfig);
164 aacFrame.writeMetaDataDouble ("audiosamplerate", adtsSampleingRates[adtsSampleingIndex]);
165 // Is AAC always 16 bit?
166 aacFrame.writeMetaDataDouble ("audiosamplesize", 16);
167
168 this.tags.push(aacFrame);
169
170 extraData = newExtraData;
171 aacFrame = new FlvTag(FlvTag.AUDIO_TAG, true);
172 // For audio, DTS is always the same as PTS. We want to set the DTS
173 // however so we can compare with video DTS to determine approximate
174 // packet order
175 aacFrame.pts = next_pts;
176 aacFrame.dts = aacFrame.pts;
177
178 aacFrame.view.setUint16(aacFrame.position, newExtraData);
179 aacFrame.position += 2;
180 aacFrame.length = Math.max(aacFrame.length, aacFrame.position);
181
182 this.tags.push(aacFrame);
183
184 lastMetaPts = next_pts;
185 }
186
187 // Skip the checksum if there is one
188 offset += 1;
189 state = 7;
190 break;
191 case 7:
192 if (!adtsProtectionAbsent) {
193 if (2 > (end - offset)) {
194 return;
195 } else {
196 offset += 2;
197 }
198 }
199
200 aacFrame = new FlvTag(FlvTag.AUDIO_TAG);
201 aacFrame.pts = next_pts;
202 aacFrame.dts = next_pts;
203 state = 8;
204 break;
205 case 8:
206 while (adtsFrameSize) {
207 if (offset >= end) {
208 return;
209 }
210 bytesToCopy = (end - offset) < adtsFrameSize ? (end - offset) : adtsFrameSize;
211 aacFrame.writeBytes(data, offset, bytesToCopy);
212 offset += bytesToCopy;
213 adtsFrameSize -= bytesToCopy;
214 }
215
216 this.tags.push(aacFrame);
217
218 // finished with this frame
219 state = 0;
220 next_pts += adtsDuration;
221 }
222 }
223 };
224 };
225
226 })(this);
1 (function(window) {
2
3 /**
4 * Parser for exponential Golomb codes, a variable-bitwidth number encoding
5 * scheme used by h264.
6 */
7 window.videojs.Hls.ExpGolomb = function(workingData) {
8 var
9 // the number of bytes left to examine in workingData
10 workingBytesAvailable = workingData.byteLength,
11
12 // the current word being examined
13 workingWord = 0, // :uint
14
15 // the number of bits left to examine in the current word
16 workingBitsAvailable = 0; // :uint;
17
18 // ():uint
19 this.length = function() {
20 return (8 * workingBytesAvailable);
21 };
22
23 // ():uint
24 this.bitsAvailable = function() {
25 return (8 * workingBytesAvailable) + workingBitsAvailable;
26 };
27
28 // ():void
29 this.loadWord = function() {
30 var
31 position = workingData.byteLength - workingBytesAvailable,
32 workingBytes = new Uint8Array(4),
33 availableBytes = Math.min(4, workingBytesAvailable);
34
35 if (availableBytes === 0) {
36 throw new Error('no bytes available');
37 }
38
39 workingBytes.set(workingData.subarray(position,
40 position + availableBytes));
41 workingWord = new DataView(workingBytes.buffer).getUint32(0);
42
43 // track the amount of workingData that has been processed
44 workingBitsAvailable = availableBytes * 8;
45 workingBytesAvailable -= availableBytes;
46 };
47
48 // (count:int):void
49 this.skipBits = function(count) {
50 var skipBytes; // :int
51 if (workingBitsAvailable > count) {
52 workingWord <<= count;
53 workingBitsAvailable -= count;
54 } else {
55 count -= workingBitsAvailable;
56 skipBytes = count / 8;
57
58 count -= (skipBytes * 8);
59 workingBytesAvailable -= skipBytes;
60
61 this.loadWord();
62
63 workingWord <<= count;
64 workingBitsAvailable -= count;
65 }
66 };
67
68 // (size:int):uint
69 this.readBits = function(size) {
70 var
71 bits = Math.min(workingBitsAvailable, size), // :uint
72 valu = workingWord >>> (32 - bits); // :uint
73
74 console.assert(size < 32, 'Cannot read more than 32 bits at a time');
75
76 workingBitsAvailable -= bits;
77 if (workingBitsAvailable > 0) {
78 workingWord <<= bits;
79 } else if (workingBytesAvailable > 0) {
80 this.loadWord();
81 }
82
83 bits = size - bits;
84 if (bits > 0) {
85 return valu << bits | this.readBits(bits);
86 } else {
87 return valu;
88 }
89 };
90
91 // ():uint
92 this.skipLeadingZeros = function() {
93 var leadingZeroCount; // :uint
94 for (leadingZeroCount = 0 ; leadingZeroCount < workingBitsAvailable ; ++leadingZeroCount) {
95 if (0 !== (workingWord & (0x80000000 >>> leadingZeroCount))) {
96 // the first bit of working word is 1
97 workingWord <<= leadingZeroCount;
98 workingBitsAvailable -= leadingZeroCount;
99 return leadingZeroCount;
100 }
101 }
102
103 // we exhausted workingWord and still have not found a 1
104 this.loadWord();
105 return leadingZeroCount + this.skipLeadingZeros();
106 };
107
108 // ():void
109 this.skipUnsignedExpGolomb = function() {
110 this.skipBits(1 + this.skipLeadingZeros());
111 };
112
113 // ():void
114 this.skipExpGolomb = function() {
115 this.skipBits(1 + this.skipLeadingZeros());
116 };
117
118 // ():uint
119 this.readUnsignedExpGolomb = function() {
120 var clz = this.skipLeadingZeros(); // :uint
121 return this.readBits(clz + 1) - 1;
122 };
123
124 // ():int
125 this.readExpGolomb = function() {
126 var valu = this.readUnsignedExpGolomb(); // :int
127 if (0x01 & valu) {
128 // the number is odd if the low order bit is set
129 return (1 + valu) >>> 1; // add 1 to make it even, and divide by 2
130 } else {
131 return -1 * (valu >>> 1); // divide by two then make it negative
132 }
133 };
134
135 // Some convenience functions
136 // :Boolean
137 this.readBoolean = function() {
138 return 1 === this.readBits(1);
139 };
140
141 // ():int
142 this.readUnsignedByte = function() {
143 return this.readBits(8);
144 };
145
146 this.loadWord();
147
148 };
149 })(this);
1 /**
2 * An object that stores the bytes of an FLV tag and methods for
3 * querying and manipulating that data.
4 * @see http://download.macromedia.com/f4v/video_file_format_spec_v10_1.pdf
5 */
6 (function(window) {
7
8 window.videojs = window.videojs || {};
9 window.videojs.Hls = window.videojs.Hls || {};
10
11 var hls = window.videojs.Hls;
12
13 // (type:uint, extraData:Boolean = false) extends ByteArray
14 hls.FlvTag = function(type, extraData) {
15 var
16 // Counter if this is a metadata tag, nal start marker if this is a video
17 // tag. unused if this is an audio tag
18 adHoc = 0, // :uint
19
20 // checks whether the FLV tag has enough capacity to accept the proposed
21 // write and re-allocates the internal buffers if necessary
22 prepareWrite = function(flv, count) {
23 var
24 bytes,
25 minLength = flv.position + count;
26 if (minLength < flv.bytes.byteLength) {
27 // there's enough capacity so do nothing
28 return;
29 }
30
31 // allocate a new buffer and copy over the data that will not be modified
32 bytes = new Uint8Array(minLength * 2);
33 bytes.set(flv.bytes.subarray(0, flv.position), 0);
34 flv.bytes = bytes;
35 flv.view = new DataView(flv.bytes.buffer);
36 },
37
38 // commonly used metadata properties
39 widthBytes = hls.FlvTag.widthBytes || new Uint8Array('width'.length),
40 heightBytes = hls.FlvTag.heightBytes || new Uint8Array('height'.length),
41 videocodecidBytes = hls.FlvTag.videocodecidBytes || new Uint8Array('videocodecid'.length),
42 i;
43
44 if (!hls.FlvTag.widthBytes) {
45 // calculating the bytes of common metadata names ahead of time makes the
46 // corresponding writes faster because we don't have to loop over the
47 // characters
48 // re-test with test/perf.html if you're planning on changing this
49 for (i = 0; i < 'width'.length; i++) {
50 widthBytes[i] = 'width'.charCodeAt(i);
51 }
52 for (i = 0; i < 'height'.length; i++) {
53 heightBytes[i] = 'height'.charCodeAt(i);
54 }
55 for (i = 0; i < 'videocodecid'.length; i++) {
56 videocodecidBytes[i] = 'videocodecid'.charCodeAt(i);
57 }
58
59 hls.FlvTag.widthBytes = widthBytes;
60 hls.FlvTag.heightBytes = heightBytes;
61 hls.FlvTag.videocodecidBytes = videocodecidBytes;
62 }
63
64 this.keyFrame = false; // :Boolean
65
66 switch(type) {
67 case hls.FlvTag.VIDEO_TAG:
68 this.length = 16;
69 break;
70 case hls.FlvTag.AUDIO_TAG:
71 this.length = 13;
72 this.keyFrame = true;
73 break;
74 case hls.FlvTag.METADATA_TAG:
75 this.length = 29;
76 this.keyFrame = true;
77 break;
78 default:
79 throw("Error Unknown TagType");
80 }
81
82 this.bytes = new Uint8Array(16384);
83 this.view = new DataView(this.bytes.buffer);
84 this.bytes[0] = type;
85 this.position = this.length;
86 this.keyFrame = extraData; // Defaults to false
87
88 // presentation timestamp
89 this.pts = 0;
90 // decoder timestamp
91 this.dts = 0;
92
93 // ByteArray#writeBytes(bytes:ByteArray, offset:uint = 0, length:uint = 0)
94 this.writeBytes = function(bytes, offset, length) {
95 var
96 start = offset || 0,
97 end;
98 length = length || bytes.byteLength;
99 end = start + length;
100
101 prepareWrite(this, length);
102 this.bytes.set(bytes.subarray(start, end), this.position);
103
104 this.position += length;
105 this.length = Math.max(this.length, this.position);
106 };
107
108 // ByteArray#writeByte(value:int):void
109 this.writeByte = function(byte) {
110 prepareWrite(this, 1);
111 this.bytes[this.position] = byte;
112 this.position++;
113 this.length = Math.max(this.length, this.position);
114 };
115
116 // ByteArray#writeShort(value:int):void
117 this.writeShort = function(short) {
118 prepareWrite(this, 2);
119 this.view.setUint16(this.position, short);
120 this.position += 2;
121 this.length = Math.max(this.length, this.position);
122 };
123
124 // Negative index into array
125 // (pos:uint):int
126 this.negIndex = function(pos) {
127 return this.bytes[this.length - pos];
128 };
129
130 // The functions below ONLY work when this[0] == VIDEO_TAG.
131 // We are not going to check for that because we dont want the overhead
132 // (nal:ByteArray = null):int
133 this.nalUnitSize = function() {
134 if (adHoc === 0) {
135 return 0;
136 }
137
138 return this.length - (adHoc + 4);
139 };
140
141 this.startNalUnit = function() {
142 // remember position and add 4 bytes
143 if (adHoc > 0) {
144 throw new Error("Attempted to create new NAL wihout closing the old one");
145 }
146
147 // reserve 4 bytes for nal unit size
148 adHoc = this.length;
149 this.length += 4;
150 this.position = this.length;
151 };
152
153 // (nal:ByteArray = null):void
154 this.endNalUnit = function(nalContainer) {
155 var
156 nalStart, // :uint
157 nalLength; // :uint
158
159 // Rewind to the marker and write the size
160 if (this.length === adHoc + 4) {
161 // we started a nal unit, but didnt write one, so roll back the 4 byte size value
162 this.length -= 4;
163 } else if (adHoc > 0) {
164 nalStart = adHoc + 4;
165 nalLength = this.length - nalStart;
166
167 this.position = adHoc;
168 this.view.setUint32(this.position, nalLength);
169 this.position = this.length;
170
171 if (nalContainer) {
172 // Add the tag to the NAL unit
173 nalContainer.push(this.bytes.subarray(nalStart, nalStart + nalLength));
174 }
175 }
176
177 adHoc = 0;
178 };
179
180 /**
181 * Write out a 64-bit floating point valued metadata property. This method is
182 * called frequently during a typical parse and needs to be fast.
183 */
184 // (key:String, val:Number):void
185 this.writeMetaDataDouble = function(key, val) {
186 var i;
187 prepareWrite(this, 2 + key.length + 9);
188
189 // write size of property name
190 this.view.setUint16(this.position, key.length);
191 this.position += 2;
192
193 // this next part looks terrible but it improves parser throughput by
194 // 10kB/s in my testing
195
196 // write property name
197 if (key === 'width') {
198 this.bytes.set(widthBytes, this.position);
199 this.position += 5;
200 } else if (key === 'height') {
201 this.bytes.set(heightBytes, this.position);
202 this.position += 6;
203 } else if (key === 'videocodecid') {
204 this.bytes.set(videocodecidBytes, this.position);
205 this.position += 12;
206 } else {
207 for (i = 0; i < key.length; i++) {
208 this.bytes[this.position] = key.charCodeAt(i);
209 this.position++;
210 }
211 }
212
213 // skip null byte
214 this.position++;
215
216 // write property value
217 this.view.setFloat64(this.position, val);
218 this.position += 8;
219
220 // update flv tag length
221 this.length = Math.max(this.length, this.position);
222 ++adHoc;
223 };
224
225 // (key:String, val:Boolean):void
226 this.writeMetaDataBoolean = function(key, val) {
227 var i;
228 prepareWrite(this, 2);
229 this.view.setUint16(this.position, key.length);
230 this.position += 2;
231 for (i = 0; i < key.length; i++) {
232 console.assert(key.charCodeAt(i) < 255);
233 prepareWrite(this, 1);
234 this.bytes[this.position] = key.charCodeAt(i);
235 this.position++;
236 }
237 prepareWrite(this, 2);
238 this.view.setUint8(this.position, 0x01);
239 this.position++;
240 this.view.setUint8(this.position, val ? 0x01 : 0x00);
241 this.position++;
242 this.length = Math.max(this.length, this.position);
243 ++adHoc;
244 };
245
246 // ():ByteArray
247 this.finalize = function() {
248 var
249 dtsDelta, // :int
250 len; // :int
251
252 switch(this.bytes[0]) {
253 // Video Data
254 case hls.FlvTag.VIDEO_TAG:
255 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)
256 this.bytes[12] = extraData ? 0x00 : 0x01;
257
258 dtsDelta = this.pts - this.dts;
259 this.bytes[13] = (dtsDelta & 0x00FF0000) >>> 16;
260 this.bytes[14] = (dtsDelta & 0x0000FF00) >>> 8;
261 this.bytes[15] = (dtsDelta & 0x000000FF) >>> 0;
262 break;
263
264 case hls.FlvTag.AUDIO_TAG:
265 this.bytes[11] = 0xAF; // 44 kHz, 16-bit stereo
266 this.bytes[12] = extraData ? 0x00 : 0x01;
267 break;
268
269 case hls.FlvTag.METADATA_TAG:
270 this.position = 11;
271 this.view.setUint8(this.position, 0x02); // String type
272 this.position++;
273 this.view.setUint16(this.position, 0x0A); // 10 Bytes
274 this.position += 2;
275 // set "onMetaData"
276 this.bytes.set([0x6f, 0x6e, 0x4d, 0x65,
277 0x74, 0x61, 0x44, 0x61,
278 0x74, 0x61], this.position);
279 this.position += 10;
280 this.bytes[this.position] = 0x08; // Array type
281 this.position++;
282 this.view.setUint32(this.position, adHoc);
283 this.position = this.length;
284 this.bytes.set([0, 0, 9], this.position);
285 this.position += 3; // End Data Tag
286 this.length = this.position;
287 break;
288 }
289
290 len = this.length - 11;
291
292 // write the DataSize field
293 this.bytes[ 1] = (len & 0x00FF0000) >>> 16;
294 this.bytes[ 2] = (len & 0x0000FF00) >>> 8;
295 this.bytes[ 3] = (len & 0x000000FF) >>> 0;
296 // write the Timestamp
297 this.bytes[ 4] = (this.dts & 0x00FF0000) >>> 16;
298 this.bytes[ 5] = (this.dts & 0x0000FF00) >>> 8;
299 this.bytes[ 6] = (this.dts & 0x000000FF) >>> 0;
300 this.bytes[ 7] = (this.dts & 0xFF000000) >>> 24;
301 // write the StreamID
302 this.bytes[ 8] = 0;
303 this.bytes[ 9] = 0;
304 this.bytes[10] = 0;
305
306 // Sometimes we're at the end of the view and have one slot to write a
307 // uint32, so, prepareWrite of count 4, since, view is uint8
308 prepareWrite(this, 4);
309 this.view.setUint32(this.length, this.length);
310 this.length += 4;
311 this.position += 4;
312
313 // trim down the byte buffer to what is actually being used
314 this.bytes = this.bytes.subarray(0, this.length);
315 this.frameTime = hls.FlvTag.frameTime(this.bytes);
316 console.assert(this.bytes.byteLength === this.length);
317 return this;
318 };
319 };
320
321 hls.FlvTag.AUDIO_TAG = 0x08; // == 8, :uint
322 hls.FlvTag.VIDEO_TAG = 0x09; // == 9, :uint
323 hls.FlvTag.METADATA_TAG = 0x12; // == 18, :uint
324
325 // (tag:ByteArray):Boolean {
326 hls.FlvTag.isAudioFrame = function(tag) {
327 return hls.FlvTag.AUDIO_TAG === tag[0];
328 };
329
330 // (tag:ByteArray):Boolean {
331 hls.FlvTag.isVideoFrame = function(tag) {
332 return hls.FlvTag.VIDEO_TAG === tag[0];
333 };
334
335 // (tag:ByteArray):Boolean {
336 hls.FlvTag.isMetaData = function(tag) {
337 return hls.FlvTag.METADATA_TAG === tag[0];
338 };
339
340 // (tag:ByteArray):Boolean {
341 hls.FlvTag.isKeyFrame = function(tag) {
342 if (hls.FlvTag.isVideoFrame(tag)) {
343 return tag[11] === 0x17;
344 }
345
346 if (hls.FlvTag.isAudioFrame(tag)) {
347 return true;
348 }
349
350 if (hls.FlvTag.isMetaData(tag)) {
351 return true;
352 }
353
354 return false;
355 };
356
357 // (tag:ByteArray):uint {
358 hls.FlvTag.frameTime = function(tag) {
359 var pts = tag[ 4] << 16; // :uint
360 pts |= tag[ 5] << 8;
361 pts |= tag[ 6] << 0;
362 pts |= tag[ 7] << 24;
363 return pts;
364 };
365
366 })(this);
1 (function() {
2 var
3 H264ExtraData,
4 ExpGolomb = window.videojs.Hls.ExpGolomb,
5 FlvTag = window.videojs.Hls.FlvTag;
6
7 window.videojs.Hls.H264ExtraData = H264ExtraData = function() {
8 this.sps = []; // :Array
9 this.pps = []; // :Array
10 };
11
12 H264ExtraData.prototype.extraDataExists = function() { // :Boolean
13 return this.sps.length > 0;
14 };
15
16 // (sizeOfScalingList:int, expGolomb:ExpGolomb):void
17 H264ExtraData.prototype.scaling_list = function(sizeOfScalingList, expGolomb) {
18 var
19 lastScale = 8, // :int
20 nextScale = 8, // :int
21 j,
22 delta_scale; // :int
23
24 for (j = 0; j < sizeOfScalingList; ++j) {
25 if (0 !== nextScale) {
26 delta_scale = expGolomb.readExpGolomb();
27 nextScale = (lastScale + delta_scale + 256) % 256;
28 //useDefaultScalingMatrixFlag = ( j = = 0 && nextScale = = 0 )
29 }
30
31 lastScale = (nextScale === 0) ? lastScale : nextScale;
32 // scalingList[ j ] = ( nextScale == 0 ) ? lastScale : nextScale;
33 // lastScale = scalingList[ j ]
34 }
35 };
36
37 /**
38 * RBSP: raw bit-stream payload. The actual encoded video data.
39 *
40 * SPS: sequence parameter set. Part of the RBSP. Metadata to be applied
41 * to a complete video sequence, like width and height.
42 */
43 H264ExtraData.prototype.getSps0Rbsp = function() { // :ByteArray
44 var
45 sps = this.sps[0],
46 offset = 1,
47 start = 1,
48 written = 0,
49 end = sps.byteLength - 2,
50 result = new Uint8Array(sps.byteLength);
51
52 // In order to prevent 0x0000 01 from being interpreted as a
53 // NAL start code, occurences of that byte sequence in the
54 // RBSP are escaped with an "emulation byte". That turns
55 // sequences of 0x0000 01 into 0x0000 0301. When interpreting
56 // a NAL payload, they must be filtered back out.
57 while (offset < end) {
58 if (sps[offset] === 0x00 &&
59 sps[offset + 1] === 0x00 &&
60 sps[offset + 2] === 0x03) {
61 result.set(sps.subarray(start, offset + 1), written);
62 written += offset + 1 - start;
63 start = offset + 3;
64 }
65 offset++;
66 }
67 result.set(sps.subarray(start), written);
68 return result.subarray(0, written + (sps.byteLength - start));
69 };
70
71 // (pts:uint):FlvTag
72 H264ExtraData.prototype.metaDataTag = function(pts) {
73 var
74 tag = new FlvTag(FlvTag.METADATA_TAG), // :FlvTag
75 expGolomb, // :ExpGolomb
76 profile_idc, // :int
77 chroma_format_idc, // :int
78 imax, // :int
79 i, // :int
80
81 pic_order_cnt_type, // :int
82 num_ref_frames_in_pic_order_cnt_cycle, // :uint
83
84 pic_width_in_mbs_minus1, // :int
85 pic_height_in_map_units_minus1, // :int
86
87 frame_mbs_only_flag, // :int
88 frame_cropping_flag, // :Boolean
89
90 frame_crop_left_offset = 0, // :int
91 frame_crop_right_offset = 0, // :int
92 frame_crop_top_offset = 0, // :int
93 frame_crop_bottom_offset = 0, // :int
94
95 width,
96 height;
97
98 tag.dts = pts;
99 tag.pts = pts;
100 expGolomb = new ExpGolomb(this.getSps0Rbsp());
101
102 // :int = expGolomb.readUnsignedByte(); // profile_idc u(8)
103 profile_idc = expGolomb.readUnsignedByte();
104
105 // constraint_set[0-5]_flag, u(1), reserved_zero_2bits u(2), level_idc u(8)
106 expGolomb.skipBits(16);
107
108 // seq_parameter_set_id
109 expGolomb.skipUnsignedExpGolomb();
110
111 if (profile_idc === 100 ||
112 profile_idc === 110 ||
113 profile_idc === 122 ||
114 profile_idc === 244 ||
115 profile_idc === 44 ||
116 profile_idc === 83 ||
117 profile_idc === 86 ||
118 profile_idc === 118 ||
119 profile_idc === 128) {
120 chroma_format_idc = expGolomb.readUnsignedExpGolomb();
121 if (3 === chroma_format_idc) {
122 expGolomb.skipBits(1); // separate_colour_plane_flag
123 }
124 expGolomb.skipUnsignedExpGolomb(); // bit_depth_luma_minus8
125 expGolomb.skipUnsignedExpGolomb(); // bit_depth_chroma_minus8
126 expGolomb.skipBits(1); // qpprime_y_zero_transform_bypass_flag
127 if (expGolomb.readBoolean()) { // seq_scaling_matrix_present_flag
128 imax = (chroma_format_idc !== 3) ? 8 : 12;
129 for (i = 0 ; i < imax ; ++i) {
130 if (expGolomb.readBoolean()) { // seq_scaling_list_present_flag[ i ]
131 if (i < 6) {
132 this.scaling_list(16, expGolomb);
133 } else {
134 this.scaling_list(64, expGolomb);
135 }
136 }
137 }
138 }
139 }
140
141 expGolomb.skipUnsignedExpGolomb(); // log2_max_frame_num_minus4
142 pic_order_cnt_type = expGolomb.readUnsignedExpGolomb();
143
144 if ( 0 === pic_order_cnt_type ) {
145 expGolomb.readUnsignedExpGolomb(); //log2_max_pic_order_cnt_lsb_minus4
146 } else if ( 1 === pic_order_cnt_type ) {
147 expGolomb.skipBits(1); // delta_pic_order_always_zero_flag
148 expGolomb.skipExpGolomb(); // offset_for_non_ref_pic
149 expGolomb.skipExpGolomb(); // offset_for_top_to_bottom_field
150 num_ref_frames_in_pic_order_cnt_cycle = expGolomb.readUnsignedExpGolomb();
151 for(i = 0 ; i < num_ref_frames_in_pic_order_cnt_cycle ; ++i) {
152 expGolomb.skipExpGolomb(); // offset_for_ref_frame[ i ]
153 }
154 }
155
156 expGolomb.skipUnsignedExpGolomb(); // max_num_ref_frames
157 expGolomb.skipBits(1); // gaps_in_frame_num_value_allowed_flag
158 pic_width_in_mbs_minus1 = expGolomb.readUnsignedExpGolomb();
159 pic_height_in_map_units_minus1 = expGolomb.readUnsignedExpGolomb();
160
161 frame_mbs_only_flag = expGolomb.readBits(1);
162 if (0 === frame_mbs_only_flag) {
163 expGolomb.skipBits(1); // mb_adaptive_frame_field_flag
164 }
165
166 expGolomb.skipBits(1); // direct_8x8_inference_flag
167 frame_cropping_flag = expGolomb.readBoolean();
168 if (frame_cropping_flag) {
169 frame_crop_left_offset = expGolomb.readUnsignedExpGolomb();
170 frame_crop_right_offset = expGolomb.readUnsignedExpGolomb();
171 frame_crop_top_offset = expGolomb.readUnsignedExpGolomb();
172 frame_crop_bottom_offset = expGolomb.readUnsignedExpGolomb();
173 }
174
175 width = ((pic_width_in_mbs_minus1 + 1) * 16) - frame_crop_left_offset * 2 - frame_crop_right_offset * 2;
176 height = ((2 - frame_mbs_only_flag) * (pic_height_in_map_units_minus1 + 1) * 16) - (frame_crop_top_offset * 2) - (frame_crop_bottom_offset * 2);
177
178 tag.writeMetaDataDouble("videocodecid", 7);
179 tag.writeMetaDataDouble("width", width);
180 tag.writeMetaDataDouble("height", height);
181 // tag.writeMetaDataDouble("videodatarate", 0 );
182 // tag.writeMetaDataDouble("framerate", 0);
183
184 return tag;
185 };
186
187 // (pts:uint):FlvTag
188 H264ExtraData.prototype.extraDataTag = function(pts) {
189 var
190 i,
191 tag = new FlvTag(FlvTag.VIDEO_TAG, true);
192
193 tag.dts = pts;
194 tag.pts = pts;
195
196 tag.writeByte(0x01);// version
197 tag.writeByte(this.sps[0][1]);// profile
198 tag.writeByte(this.sps[0][2]);// compatibility
199 tag.writeByte(this.sps[0][3]);// level
200 tag.writeByte(0xFC | 0x03); // reserved (6 bits), NULA length size - 1 (2 bits)
201 tag.writeByte(0xE0 | 0x01 ); // reserved (3 bits), num of SPS (5 bits)
202 tag.writeShort( this.sps[0].length ); // data of SPS
203 tag.writeBytes( this.sps[0] ); // SPS
204
205 tag.writeByte( this.pps.length ); // num of PPS (will there ever be more that 1 PPS?)
206 for (i = 0 ; i < this.pps.length ; ++i) {
207 tag.writeShort(this.pps[i].length); // 2 bytes for length of PPS
208 tag.writeBytes(this.pps[i]); // data of PPS
209 }
210
211 return tag;
212 };
213 })();
1 (function(window) {
2 var
3 FlvTag = window.videojs.Hls.FlvTag,
4 H264ExtraData = window.videojs.Hls.H264ExtraData,
5 H264Stream,
6 NALUnitType;
7
8 /**
9 * Network Abstraction Layer (NAL) units are the packets of an H264
10 * stream. NAL units are divided into types based on their payload
11 * data. Each type has a unique numeric identifier.
12 *
13 * NAL unit
14 * |- NAL header -|------ RBSP ------|
15 *
16 * NAL unit: Network abstraction layer unit. The combination of a NAL
17 * header and an RBSP.
18 * NAL header: the encapsulation unit for transport-specific metadata in
19 * an h264 stream. Exactly one byte.
20 */
21 // incomplete, see Table 7.1 of ITU-T H.264 for 12-32
22 window.videojs.Hls.NALUnitType = NALUnitType = {
23 unspecified: 0,
24 slice_layer_without_partitioning_rbsp_non_idr: 1,
25 slice_data_partition_a_layer_rbsp: 2,
26 slice_data_partition_b_layer_rbsp: 3,
27 slice_data_partition_c_layer_rbsp: 4,
28 slice_layer_without_partitioning_rbsp_idr: 5,
29 sei_rbsp: 6,
30 seq_parameter_set_rbsp: 7,
31 pic_parameter_set_rbsp: 8,
32 access_unit_delimiter_rbsp: 9,
33 end_of_seq_rbsp: 10,
34 end_of_stream_rbsp: 11
35 };
36
37 window.videojs.Hls.H264Stream = H264Stream = function() {
38 this._next_pts = 0; // :uint;
39 this._next_dts = 0; // :uint;
40
41 this._h264Frame = null; // :FlvTag
42
43 this._oldExtraData = new H264ExtraData(); // :H264ExtraData
44 this._newExtraData = new H264ExtraData(); // :H264ExtraData
45
46 this._nalUnitType = -1; // :int
47
48 this._state = 0; // :uint;
49
50 this.tags = [];
51 };
52
53 //(pts:uint):void
54 H264Stream.prototype.setTimeStampOffset = function() {};
55
56 //(pts:uint, dts:uint, dataAligned:Boolean):void
57 H264Stream.prototype.setNextTimeStamp = function(pts, dts, dataAligned) {
58 // We could end up with a DTS less than 0 here. We need to deal with that!
59 this._next_pts = pts;
60 this._next_dts = dts;
61
62 // If data is aligned, flush all internal buffers
63 if (dataAligned) {
64 this.finishFrame();
65 }
66 };
67
68 H264Stream.prototype.finishFrame = function() {
69 if (this._h264Frame) {
70 // Push SPS before EVERY IDR frame for seeking
71 if (this._newExtraData.extraDataExists()) {
72 this._oldExtraData = this._newExtraData;
73 this._newExtraData = new H264ExtraData();
74 }
75
76 // Check if keyframe and the length of tags.
77 // This makes sure we write metadata on the first frame of a segment.
78 if (this._oldExtraData.extraDataExists() &&
79 (this._h264Frame.keyFrame || this.tags.length === 0)) {
80 // Push extra data on every IDR frame in case we did a stream change + seek
81 this.tags.push(this._oldExtraData.metaDataTag(this._h264Frame.pts));
82 this.tags.push(this._oldExtraData.extraDataTag(this._h264Frame.pts));
83 }
84
85 this._h264Frame.endNalUnit();
86 this.tags.push(this._h264Frame);
87
88 }
89
90 this._h264Frame = null;
91 this._nalUnitType = -1;
92 this._state = 0;
93 };
94
95 // (data:ByteArray, o:int, l:int):void
96 H264Stream.prototype.writeBytes = function(data, offset, length) {
97 var
98 nalUnitSize, // :uint
99 start, // :uint
100 end, // :uint
101 t; // :int
102
103 // default argument values
104 offset = offset || 0;
105 length = length || 0;
106
107 if (length <= 0) {
108 // data is empty so there's nothing to write
109 return;
110 }
111
112 // scan through the bytes until we find the start code (0x000001) for a
113 // NAL unit and then begin writing it out
114 // strip NAL start codes as we go
115 switch (this._state) {
116 default:
117 /* falls through */
118 case 0:
119 this._state = 1;
120 /* falls through */
121 case 1:
122 // A NAL unit may be split across two TS packets. Look back a bit to
123 // make sure the prefix of the start code wasn't already written out.
124 if (data[offset] <= 1) {
125 nalUnitSize = this._h264Frame ? this._h264Frame.nalUnitSize() : 0;
126 if (nalUnitSize >= 1 && this._h264Frame.negIndex(1) === 0) {
127 // ?? ?? 00 | O[01] ?? ??
128 if (data[offset] === 1 &&
129 nalUnitSize >= 2 &&
130 this._h264Frame.negIndex(2) === 0) {
131 // ?? 00 00 : 01
132 if (3 <= nalUnitSize && 0 === this._h264Frame.negIndex(3)) {
133 this._h264Frame.length -= 3; // 00 00 00 : 01
134 } else {
135 this._h264Frame.length -= 2; // 00 00 : 01
136 }
137
138 this._state = 3;
139 return this.writeBytes(data, offset + 1, length - 1);
140 }
141
142 if (length > 1 && data[offset] === 0 && data[offset + 1] === 1) {
143 // ?? 00 | 00 01
144 if (nalUnitSize >= 2 && this._h264Frame.negIndex(2) === 0) {
145 this._h264Frame.length -= 2; // 00 00 : 00 01
146 } else {
147 this._h264Frame.length -= 1; // 00 : 00 01
148 }
149
150 this._state = 3;
151 return this.writeBytes(data, offset + 2, length - 2);
152 }
153
154 if (length > 2 &&
155 data[offset] === 0 &&
156 data[offset + 1] === 0 &&
157 data[offset + 2] === 1) {
158 // 00 : 00 00 01
159 // this._h264Frame.length -= 1;
160 this._state = 3;
161 return this.writeBytes(data, offset + 3, length - 3);
162 }
163 }
164 }
165 // allow fall through if the above fails, we may end up checking a few
166 // bytes a second time. But that case will be VERY rare
167 this._state = 2;
168 /* falls through */
169 case 2:
170 // Look for start codes in the data from the current offset forward
171 start = offset;
172 end = start + length;
173 for (t = end - 3; offset < t;) {
174 if (data[offset + 2] > 1) {
175 // if data[offset + 2] is greater than 1, there is no way a start
176 // code can begin before offset + 3
177 offset += 3;
178 } else if (data[offset + 1] !== 0) {
179 offset += 2;
180 } else if (data[offset] !== 0) {
181 offset += 1;
182 } else {
183 // If we get here we have 00 00 00 or 00 00 01
184 if (data[offset + 2] === 1) {
185 if (offset > start) {
186 this._h264Frame.writeBytes(data, start, offset - start);
187 }
188 this._state = 3;
189 offset += 3;
190 return this.writeBytes(data, offset, end - offset);
191 }
192
193 if (end - offset >= 4 &&
194 data[offset + 2] === 0 &&
195 data[offset + 3] === 1) {
196 if (offset > start) {
197 this._h264Frame.writeBytes(data, start, offset - start);
198 }
199 this._state = 3;
200 offset += 4;
201 return this.writeBytes(data, offset, end - offset);
202 }
203
204 // We are at the end of the buffer, or we have 3 NULLS followed by
205 // something that is not a 1, either way we can step forward by at
206 // least 3
207 offset += 3;
208 }
209 }
210
211 // We did not find any start codes. Try again next packet
212 this._state = 1;
213 if (this._h264Frame) {
214 this._h264Frame.writeBytes(data, start, length);
215 }
216 return;
217 case 3:
218 // The next byte is the first byte of a NAL Unit
219
220 if (this._h264Frame) {
221 // we've come to a new NAL unit so finish up the one we've been
222 // working on
223
224 switch (this._nalUnitType) {
225 case NALUnitType.seq_parameter_set_rbsp:
226 this._h264Frame.endNalUnit(this._newExtraData.sps);
227 break;
228 case NALUnitType.pic_parameter_set_rbsp:
229 this._h264Frame.endNalUnit(this._newExtraData.pps);
230 break;
231 case NALUnitType.slice_layer_without_partitioning_rbsp_idr:
232 this._h264Frame.endNalUnit();
233 break;
234 default:
235 this._h264Frame.endNalUnit();
236 break;
237 }
238 }
239
240 // setup to begin processing the new NAL unit
241 this._nalUnitType = data[offset] & 0x1F;
242 if (this._h264Frame) {
243 if (this._nalUnitType === NALUnitType.access_unit_delimiter_rbsp) {
244 // starting a new access unit, flush the previous one
245 this.finishFrame();
246 } else if (this._nalUnitType === NALUnitType.slice_layer_without_partitioning_rbsp_idr) {
247 this._h264Frame.keyFrame = true;
248 }
249 }
250
251 // finishFrame may render this._h264Frame null, so we must test again
252 if (!this._h264Frame) {
253 this._h264Frame = new FlvTag(FlvTag.VIDEO_TAG);
254 this._h264Frame.pts = this._next_pts;
255 this._h264Frame.dts = this._next_dts;
256 }
257
258 this._h264Frame.startNalUnit();
259 // We know there will not be an overlapping start code, so we can skip
260 // that test
261 this._state = 2;
262 return this.writeBytes(data, offset, length);
263 } // switch
264 };
265 })(this);
1 /**
2 * Accepts program elementary stream (PES) data events and parses out
3 * ID3 metadata from them, if present.
4 * @see http://id3.org/id3v2.3.0
5 */
6 (function(window, videojs, undefined) {
7 'use strict';
8 var
9 // return a percent-encoded representation of the specified byte range
10 // @see http://en.wikipedia.org/wiki/Percent-encoding
11 percentEncode = function(bytes, start, end) {
12 var i, result = '';
13 for (i = start; i < end; i++) {
14 result += '%' + ('00' + bytes[i].toString(16)).slice(-2);
15 }
16 return result;
17 },
18 // return the string representation of the specified byte range,
19 // interpreted as UTf-8.
20 parseUtf8 = function(bytes, start, end) {
21 return window.decodeURIComponent(percentEncode(bytes, start, end));
22 },
23 // return the string representation of the specified byte range,
24 // interpreted as ISO-8859-1.
25 parseIso88591 = function(bytes, start, end) {
26 return window.unescape(percentEncode(bytes, start, end));
27 },
28 tagParsers = {
29 'TXXX': function(tag) {
30 var i;
31 if (tag.data[0] !== 3) {
32 // ignore frames with unrecognized character encodings
33 return;
34 }
35
36 for (i = 1; i < tag.data.length; i++) {
37 if (tag.data[i] === 0) {
38 // parse the text fields
39 tag.description = parseUtf8(tag.data, 1, i);
40 // do not include the null terminator in the tag value
41 tag.value = parseUtf8(tag.data, i + 1, tag.data.length - 1);
42 break;
43 }
44 }
45 },
46 'WXXX': function(tag) {
47 var i;
48 if (tag.data[0] !== 3) {
49 // ignore frames with unrecognized character encodings
50 return;
51 }
52
53 for (i = 1; i < tag.data.length; i++) {
54 if (tag.data[i] === 0) {
55 // parse the description and URL fields
56 tag.description = parseUtf8(tag.data, 1, i);
57 tag.url = parseUtf8(tag.data, i + 1, tag.data.length);
58 break;
59 }
60 }
61 },
62 'PRIV': function(tag) {
63 var i;
64
65 for (i = 0; i < tag.data.length; i++) {
66 if (tag.data[i] === 0) {
67 // parse the description and URL fields
68 tag.owner = parseIso88591(tag.data, 0, i);
69 break;
70 }
71 }
72 tag.privateData = tag.data.subarray(i + 1);
73 }
74 },
75 MetadataStream;
76
77 MetadataStream = function(options) {
78 var
79 settings = {
80 debug: !!(options && options.debug),
81
82 // the bytes of the program-level descriptor field in MP2T
83 // see ISO/IEC 13818-1:2013 (E), section 2.6 "Program and
84 // program element descriptors"
85 descriptor: options && options.descriptor
86 },
87 // the total size in bytes of the ID3 tag being parsed
88 tagSize = 0,
89 // tag data that is not complete enough to be parsed
90 buffer = [],
91 // the total number of bytes currently in the buffer
92 bufferSize = 0,
93 i;
94
95 MetadataStream.prototype.init.call(this);
96
97 // calculate the text track in-band metadata track dispatch type
98 // https://html.spec.whatwg.org/multipage/embedded-content.html#steps-to-expose-a-media-resource-specific-text-track
99 this.dispatchType = videojs.Hls.SegmentParser.STREAM_TYPES.metadata.toString(16);
100 if (settings.descriptor) {
101 for (i = 0; i < settings.descriptor.length; i++) {
102 this.dispatchType += ('00' + settings.descriptor[i].toString(16)).slice(-2);
103 }
104 }
105
106 this.push = function(chunk) {
107 var tag, frameStart, frameSize, frame, i;
108
109 // ignore events that don't look like ID3 data
110 if (buffer.length === 0 &&
111 (chunk.data.length < 10 ||
112 chunk.data[0] !== 'I'.charCodeAt(0) ||
113 chunk.data[1] !== 'D'.charCodeAt(0) ||
114 chunk.data[2] !== '3'.charCodeAt(0))) {
115 if (settings.debug) {
116 videojs.log('Skipping unrecognized metadata packet');
117 }
118 return;
119 }
120
121 // add this chunk to the data we've collected so far
122 buffer.push(chunk);
123 bufferSize += chunk.data.byteLength;
124
125 // grab the size of the entire frame from the ID3 header
126 if (buffer.length === 1) {
127 // the frame size is transmitted as a 28-bit integer in the
128 // last four bytes of the ID3 header.
129 // The most significant bit of each byte is dropped and the
130 // results concatenated to recover the actual value.
131 tagSize = (chunk.data[6] << 21) |
132 (chunk.data[7] << 14) |
133 (chunk.data[8] << 7) |
134 (chunk.data[9]);
135
136 // ID3 reports the tag size excluding the header but it's more
137 // convenient for our comparisons to include it
138 tagSize += 10;
139 }
140
141 // if the entire frame has not arrived, wait for more data
142 if (bufferSize < tagSize) {
143 return;
144 }
145
146 // collect the entire frame so it can be parsed
147 tag = {
148 data: new Uint8Array(tagSize),
149 frames: [],
150 pts: buffer[0].pts,
151 dts: buffer[0].dts
152 };
153 for (i = 0; i < tagSize;) {
154 tag.data.set(buffer[0].data, i);
155 i += buffer[0].data.byteLength;
156 bufferSize -= buffer[0].data.byteLength;
157 buffer.shift();
158 }
159
160 // find the start of the first frame and the end of the tag
161 frameStart = 10;
162 if (tag.data[5] & 0x40) {
163 // advance the frame start past the extended header
164 frameStart += 4; // header size field
165 frameStart += (tag.data[10] << 24) |
166 (tag.data[11] << 16) |
167 (tag.data[12] << 8) |
168 (tag.data[13]);
169
170 // clip any padding off the end
171 tagSize -= (tag.data[16] << 24) |
172 (tag.data[17] << 16) |
173 (tag.data[18] << 8) |
174 (tag.data[19]);
175 }
176
177 // parse one or more ID3 frames
178 // http://id3.org/id3v2.3.0#ID3v2_frame_overview
179 do {
180 // determine the number of bytes in this frame
181 frameSize = (tag.data[frameStart + 4] << 24) |
182 (tag.data[frameStart + 5] << 16) |
183 (tag.data[frameStart + 6] << 8) |
184 (tag.data[frameStart + 7]);
185 if (frameSize < 1) {
186 return videojs.log('Malformed ID3 frame encountered. Skipping metadata parsing.');
187 }
188
189 frame = {
190 id: String.fromCharCode(tag.data[frameStart],
191 tag.data[frameStart + 1],
192 tag.data[frameStart + 2],
193 tag.data[frameStart + 3]),
194 data: tag.data.subarray(frameStart + 10, frameStart + frameSize + 10)
195 };
196 if (tagParsers[frame.id]) {
197 tagParsers[frame.id](frame);
198 }
199 tag.frames.push(frame);
200
201 frameStart += 10; // advance past the frame header
202 frameStart += frameSize; // advance past the frame body
203 } while (frameStart < tagSize);
204 this.trigger('data', tag);
205 };
206 };
207 MetadataStream.prototype = new videojs.Hls.Stream();
208
209 videojs.Hls.MetadataStream = MetadataStream;
210 })(window, window.videojs);
1 (function(window, videojs, undefined) {
2 'use strict';
3
4 var box, dinf, ftyp, mdat, mfhd, minf, moof, moov, mvex, mvhd, trak,
5 tkhd, mdia, mdhd, hdlr, sdtp, stbl, stsd, styp, traf, trex, trun,
6 types, MAJOR_BRAND, MINOR_VERSION, AVC1_BRAND, VIDEO_HDLR,
7 AUDIO_HDLR, HDLR_TYPES, ESDS, VMHD, SMHD, DREF, STCO, STSC, STSZ, STTS,
8 Uint8Array, DataView;
9
10 Uint8Array = window.Uint8Array;
11 DataView = window.DataView;
12
13 // pre-calculate constants
14 (function() {
15 var i;
16 types = {
17 avc1: [], // codingname
18 avcC: [],
19 btrt: [],
20 dinf: [],
21 dref: [],
22 esds: [],
23 ftyp: [],
24 hdlr: [],
25 mdat: [],
26 mdhd: [],
27 mdia: [],
28 mfhd: [],
29 minf: [],
30 moof: [],
31 moov: [],
32 mp4a: [], // codingname
33 mvex: [],
34 mvhd: [],
35 sdtp: [],
36 smhd: [],
37 stbl: [],
38 stco: [],
39 stsc: [],
40 stsd: [],
41 stsz: [],
42 stts: [],
43 styp: [],
44 tfdt: [],
45 tfhd: [],
46 traf: [],
47 trak: [],
48 trun: [],
49 trex: [],
50 tkhd: [],
51 vmhd: []
52 };
53
54 for (i in types) {
55 if (types.hasOwnProperty(i)) {
56 types[i] = [
57 i.charCodeAt(0),
58 i.charCodeAt(1),
59 i.charCodeAt(2),
60 i.charCodeAt(3)
61 ];
62 }
63 }
64
65 MAJOR_BRAND = new Uint8Array([
66 'i'.charCodeAt(0),
67 's'.charCodeAt(0),
68 'o'.charCodeAt(0),
69 'm'.charCodeAt(0)
70 ]);
71 AVC1_BRAND = new Uint8Array([
72 'a'.charCodeAt(0),
73 'v'.charCodeAt(0),
74 'c'.charCodeAt(0),
75 '1'.charCodeAt(0)
76 ]);
77 MINOR_VERSION = new Uint8Array([0, 0, 0, 1]);
78 VIDEO_HDLR = new Uint8Array([
79 0x00, // version 0
80 0x00, 0x00, 0x00, // flags
81 0x00, 0x00, 0x00, 0x00, // pre_defined
82 0x76, 0x69, 0x64, 0x65, // handler_type: 'vide'
83 0x00, 0x00, 0x00, 0x00, // reserved
84 0x00, 0x00, 0x00, 0x00, // reserved
85 0x00, 0x00, 0x00, 0x00, // reserved
86 0x56, 0x69, 0x64, 0x65,
87 0x6f, 0x48, 0x61, 0x6e,
88 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'VideoHandler'
89 ]);
90 AUDIO_HDLR = new Uint8Array([
91 0x00, // version 0
92 0x00, 0x00, 0x00, // flags
93 0x00, 0x00, 0x00, 0x00, // pre_defined
94 0x73, 0x6f, 0x75, 0x6e, // handler_type: 'soun'
95 0x00, 0x00, 0x00, 0x00, // reserved
96 0x00, 0x00, 0x00, 0x00, // reserved
97 0x00, 0x00, 0x00, 0x00, // reserved
98 0x53, 0x6f, 0x75, 0x6e,
99 0x64, 0x48, 0x61, 0x6e,
100 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'SoundHandler'
101 ]);
102 HDLR_TYPES = {
103 "video":VIDEO_HDLR,
104 "audio": AUDIO_HDLR
105 };
106 DREF = new Uint8Array([
107 0x00, // version 0
108 0x00, 0x00, 0x00, // flags
109 0x00, 0x00, 0x00, 0x01, // entry_count
110 0x00, 0x00, 0x00, 0x0c, // entry_size
111 0x75, 0x72, 0x6c, 0x20, // 'url' type
112 0x00, // version 0
113 0x00, 0x00, 0x01 // entry_flags
114 ]);
115 ESDS = new Uint8Array([
116 0x00, // version
117 0x00, 0x00, 0x00, // flags
118
119 // ES_Descriptor
120 0x03, // tag, ES_DescrTag
121 0x19, // length
122 0x00, 0x00, // ES_ID
123 0x00, // streamDependenceFlag, URL_flag, reserved, streamPriority
124
125 // DecoderConfigDescriptor
126 0x04, // tag, DecoderConfigDescrTag
127 0x11, // length
128 0x40, // object type
129 0x15, // streamType
130 0x00, 0x06, 0x00, // bufferSizeDB
131 0x00, 0x00, 0xda, 0xc0, // maxBitrate
132 0x00, 0x00, 0xda, 0xc0, // avgBitrate
133
134 // DecoderSpecificInfo
135 0x05, // tag, DecoderSpecificInfoTag
136 0x02, // length
137 // ISO/IEC 14496-3, AudioSpecificConfig
138 0x11, // AudioObjectType, AAC LC.
139 0x90, // samplingFrequencyIndex, 8 -> 16000. channelConfig, 2 -> stereo.
140 0x06, 0x01, 0x02 // GASpecificConfig
141 ]);
142 SMHD = new Uint8Array([
143 0x00, // version
144 0x00, 0x00, 0x00, // flags
145 0x00, 0x00, // balance, 0 means centered
146 0x00, 0x00 // reserved
147 ]);
148 STCO = new Uint8Array([
149 0x00, // version
150 0x00, 0x00, 0x00, // flags
151 0x00, 0x00, 0x00, 0x00 // entry_count
152 ]);
153 STSC = STCO;
154 STSZ = new Uint8Array([
155 0x00, // version
156 0x00, 0x00, 0x00, // flags
157 0x00, 0x00, 0x00, 0x00, // sample_size
158 0x00, 0x00, 0x00, 0x00, // sample_count
159 ]);
160 STTS = STCO;
161 VMHD = new Uint8Array([
162 0x00, // version
163 0x00, 0x00, 0x01, // flags
164 0x00, 0x00, // graphicsmode
165 0x00, 0x00,
166 0x00, 0x00,
167 0x00, 0x00 // opcolor
168 ]);
169 })();
170
171 box = function(type) {
172 var
173 payload = Array.prototype.slice.call(arguments, 1),
174 size = 0,
175 i = payload.length,
176 result,
177 view;
178
179 // calculate the total size we need to allocate
180 while (i--) {
181 size += payload[i].byteLength;
182 }
183 result = new Uint8Array(size + 8);
184 view = new DataView(result.buffer, result.byteOffset, result.byteLength);
185 view.setUint32(0, result.byteLength);
186 result.set(type, 4);
187
188 // copy the payload into the result
189 for (i = 0, size = 8; i < payload.length; i++) {
190 result.set(payload[i], size);
191 size += payload[i].byteLength;
192 }
193 return result;
194 };
195
196 dinf = function() {
197 return box(types.dinf, box(types.dref, DREF));
198 };
199
200 ftyp = function() {
201 return box(types.ftyp, MAJOR_BRAND, MINOR_VERSION, MAJOR_BRAND, AVC1_BRAND);
202 };
203
204 hdlr = function(type) {
205 return box(types.hdlr, HDLR_TYPES[type]);
206 };
207 mdat = function(data) {
208 return box(types.mdat, data);
209 };
210 mdhd = function(track) {
211 var result = new Uint8Array([
212 0x00, // version 0
213 0x00, 0x00, 0x00, // flags
214 0x00, 0x00, 0x00, 0x02, // creation_time
215 0x00, 0x00, 0x00, 0x03, // modification_time
216 0x00, 0x01, 0x5f, 0x90, // timescale, 90,000 "ticks" per second
217
218 (track.duration >>> 24),
219 (track.duration >>> 16) & 0xFF,
220 (track.duration >>> 8) & 0xFF,
221 track.duration & 0xFF, // duration
222 0x55, 0xc4, // 'und' language (undetermined)
223 0x00, 0x00
224 ]);
225
226 // Use the sample rate from the track metadata, when it is
227 // defined. The sample rate can be parsed out of an ADTS header, for
228 // instance.
229 if (track.samplerate) {
230 result[12] = (track.samplerate >>> 24);
231 result[13] = (track.samplerate >>> 16) & 0xFF;
232 result[14] = (track.samplerate >>> 8) & 0xFF;
233 result[15] = (track.samplerate) & 0xFF;
234 }
235 return box(types.mdhd, result);
236 };
237 mdia = function(track) {
238 return box(types.mdia, mdhd(track), hdlr(track.type), minf(track));
239 };
240 mfhd = function(sequenceNumber) {
241 return box(types.mfhd, new Uint8Array([
242 0x00,
243 0x00, 0x00, 0x00, // flags
244 (sequenceNumber & 0xFF000000) >> 24,
245 (sequenceNumber & 0xFF0000) >> 16,
246 (sequenceNumber & 0xFF00) >> 8,
247 sequenceNumber & 0xFF, // sequence_number
248 ]));
249 };
250 minf = function(track) {
251 return box(types.minf,
252 track.type === 'video' ? box(types.vmhd, VMHD) : box(types.smhd, SMHD),
253 dinf(),
254 stbl(track));
255 };
256 moof = function(sequenceNumber, tracks) {
257 var
258 trackFragments = [],
259 i = tracks.length;
260 // build traf boxes for each track fragment
261 while (i--) {
262 trackFragments[i] = traf(tracks[i]);
263 }
264 return box.apply(null, [
265 types.moof,
266 mfhd(sequenceNumber)
267 ].concat(trackFragments));
268 };
269 /**
270 * Returns a movie box.
271 * @param tracks {array} the tracks associated with this movie
272 * @see ISO/IEC 14496-12:2012(E), section 8.2.1
273 */
274 moov = function(tracks) {
275 var
276 i = tracks.length,
277 boxes = [];
278
279 while (i--) {
280 boxes[i] = trak(tracks[i]);
281 }
282
283 return box.apply(null, [types.moov, mvhd(0xffffffff)].concat(boxes).concat(mvex(tracks)));
284 };
285 mvex = function(tracks) {
286 var
287 i = tracks.length,
288 boxes = [];
289
290 while (i--) {
291 boxes[i] = trex(tracks[i]);
292 }
293 return box.apply(null, [types.mvex].concat(boxes));
294 };
295 mvhd = function(duration) {
296 var
297 bytes = new Uint8Array([
298 0x00, // version 0
299 0x00, 0x00, 0x00, // flags
300 0x00, 0x00, 0x00, 0x01, // creation_time
301 0x00, 0x00, 0x00, 0x02, // modification_time
302 0x00, 0x01, 0x5f, 0x90, // timescale, 90,000 "ticks" per second
303 (duration & 0xFF000000) >> 24,
304 (duration & 0xFF0000) >> 16,
305 (duration & 0xFF00) >> 8,
306 duration & 0xFF, // duration
307 0x00, 0x01, 0x00, 0x00, // 1.0 rate
308 0x01, 0x00, // 1.0 volume
309 0x00, 0x00, // reserved
310 0x00, 0x00, 0x00, 0x00, // reserved
311 0x00, 0x00, 0x00, 0x00, // reserved
312 0x00, 0x01, 0x00, 0x00,
313 0x00, 0x00, 0x00, 0x00,
314 0x00, 0x00, 0x00, 0x00,
315 0x00, 0x00, 0x00, 0x00,
316 0x00, 0x01, 0x00, 0x00,
317 0x00, 0x00, 0x00, 0x00,
318 0x00, 0x00, 0x00, 0x00,
319 0x00, 0x00, 0x00, 0x00,
320 0x40, 0x00, 0x00, 0x00, // transformation: unity matrix
321 0x00, 0x00, 0x00, 0x00,
322 0x00, 0x00, 0x00, 0x00,
323 0x00, 0x00, 0x00, 0x00,
324 0x00, 0x00, 0x00, 0x00,
325 0x00, 0x00, 0x00, 0x00,
326 0x00, 0x00, 0x00, 0x00, // pre_defined
327 0xff, 0xff, 0xff, 0xff // next_track_ID
328 ]);
329 return box(types.mvhd, bytes);
330 };
331
332 sdtp = function(track) {
333 var
334 samples = track.samples || [],
335 bytes = new Uint8Array(4 + samples.length),
336 flags,
337 i;
338
339 // leave the full box header (4 bytes) all zero
340
341 // write the sample table
342 for (i = 0; i < samples.length; i++) {
343 flags = samples[i].flags;
344
345 bytes[i + 4] = (flags.dependsOn << 4) |
346 (flags.isDependedOn << 2) |
347 (flags.hasRedundancy);
348 }
349
350 return box(types.sdtp,
351 bytes);
352 };
353
354 stbl = function(track) {
355 return box(types.stbl,
356 stsd(track),
357 box(types.stts, STTS),
358 box(types.stsc, STSC),
359 box(types.stsz, STSZ),
360 box(types.stco, STCO));
361 };
362
363 (function() {
364 var videoSample, audioSample;
365
366 stsd = function(track) {
367
368 return box(types.stsd, new Uint8Array([
369 0x00, // version 0
370 0x00, 0x00, 0x00, // flags
371 0x00, 0x00, 0x00, 0x01
372 ]), track.type === 'video' ? videoSample(track) : audioSample(track));
373 };
374
375 videoSample = function(track) {
376 var sequenceParameterSets = [], pictureParameterSets = [], i;
377
378 // assemble the SPSs
379 for (i = 0; i < track.sps.length; i++) {
380 sequenceParameterSets.push((track.sps[i].byteLength & 0xFF00) >>> 8);
381 sequenceParameterSets.push((track.sps[i].byteLength & 0xFF)); // sequenceParameterSetLength
382 sequenceParameterSets = sequenceParameterSets.concat(Array.prototype.slice.call(track.sps[i])); // SPS
383 }
384
385 // assemble the PPSs
386 for (i = 0; i < track.pps.length; i++) {
387 pictureParameterSets.push((track.pps[i].byteLength & 0xFF00) >>> 8);
388 pictureParameterSets.push((track.pps[i].byteLength & 0xFF));
389 pictureParameterSets = pictureParameterSets.concat(Array.prototype.slice.call(track.pps[i]));
390 }
391
392 return box(types.avc1, new Uint8Array([
393 0x00, 0x00, 0x00,
394 0x00, 0x00, 0x00, // reserved
395 0x00, 0x01, // data_reference_index
396 0x00, 0x00, // pre_defined
397 0x00, 0x00, // reserved
398 0x00, 0x00, 0x00, 0x00,
399 0x00, 0x00, 0x00, 0x00,
400 0x00, 0x00, 0x00, 0x00, // pre_defined
401 (track.width & 0xff00) >> 8,
402 track.width & 0xff, // width
403 (track.height & 0xff00) >> 8,
404 track.height & 0xff, // height
405 0x00, 0x48, 0x00, 0x00, // horizresolution
406 0x00, 0x48, 0x00, 0x00, // vertresolution
407 0x00, 0x00, 0x00, 0x00, // reserved
408 0x00, 0x01, // frame_count
409 0x13,
410 0x76, 0x69, 0x64, 0x65,
411 0x6f, 0x6a, 0x73, 0x2d,
412 0x63, 0x6f, 0x6e, 0x74,
413 0x72, 0x69, 0x62, 0x2d,
414 0x68, 0x6c, 0x73, 0x00,
415 0x00, 0x00, 0x00, 0x00,
416 0x00, 0x00, 0x00, 0x00,
417 0x00, 0x00, 0x00, // compressorname
418 0x00, 0x18, // depth = 24
419 0x11, 0x11 // pre_defined = -1
420 ]), box(types.avcC, new Uint8Array([
421 0x01, // configurationVersion
422 track.profileIdc, // AVCProfileIndication
423 track.profileCompatibility, // profile_compatibility
424 track.levelIdc, // AVCLevelIndication
425 0xff // lengthSizeMinusOne, hard-coded to 4 bytes
426 ].concat([
427 track.sps.length // numOfSequenceParameterSets
428 ]).concat(sequenceParameterSets).concat([
429 track.pps.length // numOfPictureParameterSets
430 ]).concat(pictureParameterSets))), // "PPS"
431 box(types.btrt, new Uint8Array([
432 0x00, 0x1c, 0x9c, 0x80, // bufferSizeDB
433 0x00, 0x2d, 0xc6, 0xc0, // maxBitrate
434 0x00, 0x2d, 0xc6, 0xc0
435 ])) // avgBitrate
436 );
437 };
438
439 audioSample = function(track) {
440 return box(types.mp4a, new Uint8Array([
441
442 // SampleEntry, ISO/IEC 14496-12
443 0x00, 0x00, 0x00,
444 0x00, 0x00, 0x00, // reserved
445 0x00, 0x01, // data_reference_index
446
447 // AudioSampleEntry, ISO/IEC 14496-12
448 0x00, 0x00, 0x00, 0x00, // reserved
449 0x00, 0x00, 0x00, 0x00, // reserved
450 (track.channelcount & 0xff00) >> 8,
451 (track.channelcount & 0xff), // channelcount
452
453 (track.samplesize & 0xff00) >> 8,
454 (track.samplesize & 0xff), // samplesize
455 0x00, 0x00, // pre_defined
456 0x00, 0x00, // reserved
457
458 (track.samplerate & 0xff00) >> 8,
459 (track.samplerate & 0xff),
460 0x00, 0x00 // samplerate, 16.16
461
462 // MP4AudioSampleEntry, ISO/IEC 14496-14
463 ]), box(types.esds, ESDS));
464 };
465 })();
466
467 styp = function() {
468 return box(types.styp, MAJOR_BRAND, MINOR_VERSION, MAJOR_BRAND);
469 };
470
471 tkhd = function(track) {
472 var result = new Uint8Array([
473 0x00, // version 0
474 0x00, 0x00, 0x07, // flags
475 0x00, 0x00, 0x00, 0x00, // creation_time
476 0x00, 0x00, 0x00, 0x00, // modification_time
477 (track.id & 0xFF000000) >> 24,
478 (track.id & 0xFF0000) >> 16,
479 (track.id & 0xFF00) >> 8,
480 track.id & 0xFF, // track_ID
481 0x00, 0x00, 0x00, 0x00, // reserved
482 (track.duration & 0xFF000000) >> 24,
483 (track.duration & 0xFF0000) >> 16,
484 (track.duration & 0xFF00) >> 8,
485 track.duration & 0xFF, // duration
486 0x00, 0x00, 0x00, 0x00,
487 0x00, 0x00, 0x00, 0x00, // reserved
488 0x00, 0x00, // layer
489 0x00, 0x00, // alternate_group
490 0x01, 0x00, // non-audio track volume
491 0x00, 0x00, // reserved
492 0x00, 0x01, 0x00, 0x00,
493 0x00, 0x00, 0x00, 0x00,
494 0x00, 0x00, 0x00, 0x00,
495 0x00, 0x00, 0x00, 0x00,
496 0x00, 0x01, 0x00, 0x00,
497 0x00, 0x00, 0x00, 0x00,
498 0x00, 0x00, 0x00, 0x00,
499 0x00, 0x00, 0x00, 0x00,
500 0x40, 0x00, 0x00, 0x00, // transformation: unity matrix
501 (track.width & 0xFF00) >> 8,
502 track.width & 0xFF,
503 0x00, 0x00, // width
504 (track.height & 0xFF00) >> 8,
505 track.height & 0xFF,
506 0x00, 0x00 // height
507 ]);
508
509 return box(types.tkhd, result);
510 };
511
512 /**
513 * Generate a track fragment (traf) box. A traf box collects metadata
514 * about tracks in a movie fragment (moof) box.
515 */
516 traf = function(track) {
517 var trackFragmentHeader, trackFragmentDecodeTime,
518 trackFragmentRun, sampleDependencyTable, dataOffset;
519
520 trackFragmentHeader = box(types.tfhd, new Uint8Array([
521 0x00, // version 0
522 0x00, 0x00, 0x3a, // flags
523 (track.id & 0xFF000000) >> 24,
524 (track.id & 0xFF0000) >> 16,
525 (track.id & 0xFF00) >> 8,
526 (track.id & 0xFF), // track_ID
527 0x00, 0x00, 0x00, 0x01, // sample_description_index
528 0x00, 0x00, 0x00, 0x00, // default_sample_duration
529 0x00, 0x00, 0x00, 0x00, // default_sample_size
530 0x00, 0x00, 0x00, 0x00 // default_sample_flags
531 ]));
532
533 trackFragmentDecodeTime = box(types.tfdt, new Uint8Array([
534 0x00, // version 0
535 0x00, 0x00, 0x00, // flags
536 0x00, 0x00, 0x00, 0x00 // baseMediaDecodeTime
537 ]));
538
539 // the data offset specifies the number of bytes from the start of
540 // the containing moof to the first payload byte of the associated
541 // mdat
542 dataOffset = (32 + // tfhd
543 16 + // tfdt
544 8 + // traf header
545 16 + // mfhd
546 8 + // moof header
547 8); // mdat header
548
549 // audio tracks require less metadata
550 if (track.type === 'audio') {
551 trackFragmentRun = trun(track, dataOffset);
552 return box(types.traf,
553 trackFragmentHeader,
554 trackFragmentDecodeTime,
555 trackFragmentRun);
556 }
557
558 // video tracks should contain an independent and disposable samples
559 // box (sdtp)
560 // generate one and adjust offsets to match
561 sampleDependencyTable = sdtp(track);
562 trackFragmentRun = trun(track,
563 sampleDependencyTable.length + dataOffset);
564 return box(types.traf,
565 trackFragmentHeader,
566 trackFragmentDecodeTime,
567 trackFragmentRun,
568 sampleDependencyTable);
569 };
570
571 /**
572 * Generate a track box.
573 * @param track {object} a track definition
574 * @return {Uint8Array} the track box
575 */
576 trak = function(track) {
577 track.duration = track.duration || 0xffffffff;
578 return box(types.trak,
579 tkhd(track),
580 mdia(track));
581 };
582
583 trex = function(track) {
584 var result = new Uint8Array([
585 0x00, // version 0
586 0x00, 0x00, 0x00, // flags
587 (track.id & 0xFF000000) >> 24,
588 (track.id & 0xFF0000) >> 16,
589 (track.id & 0xFF00) >> 8,
590 (track.id & 0xFF), // track_ID
591 0x00, 0x00, 0x00, 0x01, // default_sample_description_index
592 0x00, 0x00, 0x00, 0x00, // default_sample_duration
593 0x00, 0x00, 0x00, 0x00, // default_sample_size
594 0x00, 0x01, 0x00, 0x01 // default_sample_flags
595 ]);
596 // the last two bytes of default_sample_flags is the sample
597 // degradation priority, a hint about the importance of this sample
598 // relative to others. Lower the degradation priority for all sample
599 // types other than video.
600 if (track.type !== 'video') {
601 result[result.length - 1] = 0x00;
602 }
603
604 return box(types.trex, result);
605 };
606
607 (function() {
608 var audioTrun, videoTrun, trunHeader;
609
610 // This method assumes all samples are uniform. That is, if a
611 // duration is present for the first sample, it will be present for
612 // all subsequent samples.
613 // see ISO/IEC 14496-12:2012, Section 8.8.8.1
614 trunHeader = function(samples, offset) {
615 var durationPresent = 0, sizePresent = 0,
616 flagsPresent = 0, compositionTimeOffset = 0;
617
618 // trun flag constants
619 if (samples.length) {
620 if (samples[0].duration !== undefined) {
621 durationPresent = 0x1;
622 }
623 if (samples[0].size !== undefined) {
624 sizePresent = 0x2;
625 }
626 if (samples[0].flags !== undefined) {
627 flagsPresent = 0x4;
628 }
629 if (samples[0].compositionTimeOffset !== undefined) {
630 compositionTimeOffset = 0x8;
631 }
632 }
633
634 return [
635 0x00, // version 0
636 0x00,
637 durationPresent | sizePresent | flagsPresent | compositionTimeOffset,
638 0x01, // flags
639 (samples.length & 0xFF000000) >>> 24,
640 (samples.length & 0xFF0000) >>> 16,
641 (samples.length & 0xFF00) >>> 8,
642 samples.length & 0xFF, // sample_count
643 (offset & 0xFF000000) >>> 24,
644 (offset & 0xFF0000) >>> 16,
645 (offset & 0xFF00) >>> 8,
646 offset & 0xFF // data_offset
647 ];
648 };
649
650 videoTrun = function(track, offset) {
651 var bytes, samples, sample, i;
652
653 samples = track.samples || [];
654 offset += 8 + 12 + (16 * samples.length);
655
656 bytes = trunHeader(samples, offset);
657
658 for (i = 0; i < samples.length; i++) {
659 sample = samples[i];
660 bytes = bytes.concat([
661 (sample.duration & 0xFF000000) >>> 24,
662 (sample.duration & 0xFF0000) >>> 16,
663 (sample.duration & 0xFF00) >>> 8,
664 sample.duration & 0xFF, // sample_duration
665 (sample.size & 0xFF000000) >>> 24,
666 (sample.size & 0xFF0000) >>> 16,
667 (sample.size & 0xFF00) >>> 8,
668 sample.size & 0xFF, // sample_size
669 (sample.flags.isLeading << 2) | sample.flags.dependsOn,
670 (sample.flags.isDependedOn << 6) |
671 (sample.flags.hasRedundancy << 4) |
672 (sample.flags.paddingValue << 1) |
673 sample.flags.isNonSyncSample,
674 sample.flags.degradationPriority & 0xF0 << 8,
675 sample.flags.degradationPriority & 0x0F, // sample_flags
676 (sample.compositionTimeOffset & 0xFF000000) >>> 24,
677 (sample.compositionTimeOffset & 0xFF0000) >>> 16,
678 (sample.compositionTimeOffset & 0xFF00) >>> 8,
679 sample.compositionTimeOffset & 0xFF // sample_composition_time_offset
680 ]);
681 }
682 return box(types.trun, new Uint8Array(bytes));
683 };
684
685 audioTrun = function(track, offset) {
686 var bytes, samples, sample, i;
687
688 samples = track.samples || [];
689 offset += 8 + 12 + (8 * samples.length);
690
691 bytes = trunHeader(samples, offset);
692
693 for (i = 0; i < samples.length; i++) {
694 sample = samples[i];
695 bytes = bytes.concat([
696 (sample.duration & 0xFF000000) >>> 24,
697 (sample.duration & 0xFF0000) >>> 16,
698 (sample.duration & 0xFF00) >>> 8,
699 sample.duration & 0xFF, // sample_duration
700 (sample.size & 0xFF000000) >>> 24,
701 (sample.size & 0xFF0000) >>> 16,
702 (sample.size & 0xFF00) >>> 8,
703 sample.size & 0xFF]); // sample_size
704 }
705
706 return box(types.trun, new Uint8Array(bytes));
707 };
708
709 trun = function(track, offset) {
710 if (track.type === 'audio') {
711 return audioTrun(track, offset);
712 } else {
713 return videoTrun(track, offset);
714 }
715 };
716 })();
717
718 window.videojs.mp4 = {
719 ftyp: ftyp,
720 mdat: mdat,
721 moof: moof,
722 moov: moov,
723 initSegment: function(tracks) {
724 var
725 fileType = ftyp(),
726 movie = moov(tracks),
727 result;
728
729 result = new Uint8Array(fileType.byteLength + movie.byteLength);
730 result.set(fileType);
731 result.set(movie, fileType.byteLength);
732 return result;
733 }
734 };
735
736 })(window, window.videojs);
1 (function(window) {
2 var
3 videojs = window.videojs,
4 FlvTag = videojs.Hls.FlvTag,
5 H264Stream = videojs.Hls.H264Stream,
6 AacStream = videojs.Hls.AacStream,
7 MetadataStream = videojs.Hls.MetadataStream,
8 MP2T_PACKET_LENGTH,
9 STREAM_TYPES;
10
11 /**
12 * An object that incrementally transmuxes MPEG2 Trasport Stream
13 * chunks into an FLV.
14 */
15 videojs.Hls.SegmentParser = function() {
16 var
17 self = this,
18 parseTSPacket,
19 streamBuffer = new Uint8Array(MP2T_PACKET_LENGTH),
20 streamBufferByteCount = 0,
21 h264Stream = new H264Stream(),
22 aacStream = new AacStream();
23
24 // expose the stream metadata
25 self.stream = {
26 // the mapping between transport stream programs and the PIDs
27 // that form their elementary streams
28 programMapTable: {}
29 };
30
31 // allow in-band metadata to be observed
32 self.metadataStream = new MetadataStream();
33
34 // For information on the FLV format, see
35 // http://download.macromedia.com/f4v/video_file_format_spec_v10_1.pdf.
36 // Technically, this function returns the header and a metadata FLV tag
37 // if duration is greater than zero
38 // duration in seconds
39 // @return {object} the bytes of the FLV header as a Uint8Array
40 self.getFlvHeader = function(duration, audio, video) { // :ByteArray {
41 var
42 headBytes = new Uint8Array(3 + 1 + 1 + 4),
43 head = new DataView(headBytes.buffer),
44 metadata,
45 result,
46 metadataLength;
47
48 // default arguments
49 duration = duration || 0;
50 audio = audio === undefined? true : audio;
51 video = video === undefined? true : video;
52
53 // signature
54 head.setUint8(0, 0x46); // 'F'
55 head.setUint8(1, 0x4c); // 'L'
56 head.setUint8(2, 0x56); // 'V'
57
58 // version
59 head.setUint8(3, 0x01);
60
61 // flags
62 head.setUint8(4, (audio ? 0x04 : 0x00) | (video ? 0x01 : 0x00));
63
64 // data offset, should be 9 for FLV v1
65 head.setUint32(5, headBytes.byteLength);
66
67 // init the first FLV tag
68 if (duration <= 0) {
69 // no duration available so just write the first field of the first
70 // FLV tag
71 result = new Uint8Array(headBytes.byteLength + 4);
72 result.set(headBytes);
73 result.set([0, 0, 0, 0], headBytes.byteLength);
74 return result;
75 }
76
77 // write out the duration metadata tag
78 metadata = new FlvTag(FlvTag.METADATA_TAG);
79 metadata.pts = metadata.dts = 0;
80 metadata.writeMetaDataDouble("duration", duration);
81 metadataLength = metadata.finalize().length;
82 result = new Uint8Array(headBytes.byteLength + metadataLength);
83 result.set(headBytes);
84 result.set(head.byteLength, metadataLength);
85
86 return result;
87 };
88
89 self.flushTags = function() {
90 h264Stream.finishFrame();
91 };
92
93 /**
94 * Returns whether a call to `getNextTag()` will be successful.
95 * @return {boolean} whether there is at least one transmuxed FLV
96 * tag ready
97 */
98 self.tagsAvailable = function() { // :int {
99 return h264Stream.tags.length + aacStream.tags.length;
100 };
101
102 /**
103 * Returns the next tag in decoder-timestamp (DTS) order.
104 * @returns {object} the next tag to decoded.
105 */
106 self.getNextTag = function() {
107 var tag;
108
109 if (!h264Stream.tags.length) {
110 // only audio tags remain
111 tag = aacStream.tags.shift();
112 } else if (!aacStream.tags.length) {
113 // only video tags remain
114 tag = h264Stream.tags.shift();
115 } else if (aacStream.tags[0].dts < h264Stream.tags[0].dts) {
116 // audio should be decoded next
117 tag = aacStream.tags.shift();
118 } else {
119 // video should be decoded next
120 tag = h264Stream.tags.shift();
121 }
122
123 return tag.finalize();
124 };
125
126 self.parseSegmentBinaryData = function(data) { // :ByteArray) {
127 var
128 dataPosition = 0,
129 dataSlice;
130
131 // To avoid an extra copy, we will stash overflow data, and only
132 // reconstruct the first packet. The rest of the packets will be
133 // parsed directly from data
134 if (streamBufferByteCount > 0) {
135 if (data.byteLength + streamBufferByteCount < MP2T_PACKET_LENGTH) {
136 // the current data is less than a single m2ts packet, so stash it
137 // until we receive more
138
139 // ?? this seems to append streamBuffer onto data and then just give up. I'm not sure why that would be interesting.
140 videojs.log('data.length + streamBuffer.length < MP2T_PACKET_LENGTH ??');
141 streamBuffer.readBytes(data, data.length, streamBuffer.length);
142 return;
143 } else {
144 // we have enough data for an m2ts packet
145 // process it immediately
146 dataSlice = data.subarray(0, MP2T_PACKET_LENGTH - streamBufferByteCount);
147 streamBuffer.set(dataSlice, streamBufferByteCount);
148
149 parseTSPacket(streamBuffer);
150
151 // reset the buffer
152 streamBuffer = new Uint8Array(MP2T_PACKET_LENGTH);
153 streamBufferByteCount = 0;
154 }
155 }
156
157 while (true) {
158 // Make sure we are TS aligned
159 while(dataPosition < data.byteLength && data[dataPosition] !== 0x47) {
160 // If there is no sync byte skip forward until we find one
161 // TODO if we find a sync byte, look 188 bytes in the future (if
162 // possible). If there is not a sync byte there, keep looking
163 dataPosition++;
164 }
165
166 // base case: not enough data to parse a m2ts packet
167 if (data.byteLength - dataPosition < MP2T_PACKET_LENGTH) {
168 if (data.byteLength - dataPosition > 0) {
169 // there are bytes remaining, save them for next time
170 streamBuffer.set(data.subarray(dataPosition),
171 streamBufferByteCount);
172 streamBufferByteCount += data.byteLength - dataPosition;
173 }
174 return;
175 }
176
177 // attempt to parse a m2ts packet
178 if (parseTSPacket(data.subarray(dataPosition, dataPosition + MP2T_PACKET_LENGTH))) {
179 dataPosition += MP2T_PACKET_LENGTH;
180 } else {
181 // If there was an error parsing a TS packet. it could be
182 // because we are not TS packet aligned. Step one forward by
183 // one byte and allow the code above to find the next
184 videojs.log('error parsing m2ts packet, attempting to re-align');
185 dataPosition++;
186 }
187 }
188 };
189
190 /**
191 * Parses a video/mp2t packet and appends the underlying video and
192 * audio data onto h264stream and aacStream, respectively.
193 * @param data {Uint8Array} the bytes of an MPEG2-TS packet,
194 * including the sync byte.
195 * @return {boolean} whether a valid packet was encountered
196 */
197 // TODO add more testing to make sure we dont walk past the end of a TS
198 // packet!
199 parseTSPacket = function(data) { // :ByteArray):Boolean {
200 var
201 offset = 0, // :uint
202 end = offset + MP2T_PACKET_LENGTH, // :uint
203
204 // Payload Unit Start Indicator
205 pusi = !!(data[offset + 1] & 0x40), // mask: 0100 0000
206
207 // packet identifier (PID), a unique identifier for the elementary
208 // stream this packet describes
209 pid = (data[offset + 1] & 0x1F) << 8 | data[offset + 2], // mask: 0001 1111
210
211 // adaptation_field_control, whether this header is followed by an
212 // adaptation field, a payload, or both
213 afflag = (data[offset + 3] & 0x30 ) >>> 4,
214
215 patTableId, // :int
216 patCurrentNextIndicator, // Boolean
217 patSectionLength, // :uint
218 programNumber, // :uint
219 programPid, // :uint
220 patEntriesEnd, // :uint
221
222 pesPacketSize, // :int,
223 dataAlignmentIndicator, // :Boolean,
224 ptsDtsIndicator, // :int
225 pesHeaderLength, // :int
226
227 pts, // :uint
228 dts, // :uint
229
230 pmtCurrentNextIndicator, // :Boolean
231 pmtProgramDescriptorsLength,
232 pmtSectionLength, // :uint
233
234 streamType, // :int
235 elementaryPID, // :int
236 ESInfolength; // :int
237
238 // Continuity Counter we could use this for sanity check, and
239 // corrupt stream detection
240 // cc = (data[offset + 3] & 0x0F);
241
242 // move past the header
243 offset += 4;
244
245 // if an adaption field is present, its length is specified by
246 // the fifth byte of the PES header. The adaptation field is
247 // used to specify some forms of timing and control data that we
248 // do not currently use.
249 if (afflag > 0x01) {
250 offset += data[offset] + 1;
251 }
252
253 // Handle a Program Association Table (PAT). PATs map PIDs to
254 // individual programs. If this transport stream was being used
255 // for television broadcast, a program would probably be
256 // equivalent to a channel. In HLS, it would be very unusual to
257 // create an mp2t stream with multiple programs.
258 if (0x0000 === pid) {
259 // The PAT may be split into multiple sections and those
260 // sections may be split into multiple packets. If a PAT
261 // section starts in this packet, PUSI will be true and the
262 // first byte of the playload will indicate the offset from
263 // the current position to the start of the section.
264 if (pusi) {
265 offset += 1 + data[offset];
266 }
267 patTableId = data[offset];
268
269 if (patTableId !== 0x00) {
270 videojs.log('the table_id of the PAT should be 0x00 but was' +
271 patTableId.toString(16));
272 }
273
274 // the current_next_indicator specifies whether this PAT is
275 // currently applicable or is part of the next table to become
276 // active
277 patCurrentNextIndicator = !!(data[offset + 5] & 0x01);
278 if (patCurrentNextIndicator) {
279 // section_length specifies the number of bytes following
280 // its position to the end of this section
281 // section_length = rest of header + (n * entry length) + CRC
282 // = 5 + (n * 4) + 4
283 patSectionLength = (data[offset + 1] & 0x0F) << 8 | data[offset + 2];
284 // move past the rest of the PSI header to the first program
285 // map table entry
286 offset += 8;
287
288 // we don't handle streams with more than one program, so
289 // raise an exception if we encounter one
290 patEntriesEnd = offset + (patSectionLength - 5 - 4);
291 for (; offset < patEntriesEnd; offset += 4) {
292 programNumber = (data[offset] << 8 | data[offset + 1]);
293 programPid = (data[offset + 2] & 0x1F) << 8 | data[offset + 3];
294 // network PID program number equals 0
295 // this is primarily an artifact of EBU DVB and can be ignored
296 if (programNumber === 0) {
297 self.stream.networkPid = programPid;
298 } else if (self.stream.pmtPid === undefined) {
299 // the Program Map Table (PMT) associates the underlying
300 // video and audio streams with a unique PID
301 self.stream.pmtPid = programPid;
302 } else if (self.stream.pmtPid !== programPid) {
303 throw new Error("TS has more that 1 program");
304 }
305 }
306 }
307 } else if (pid === self.stream.programMapTable[STREAM_TYPES.h264] ||
308 pid === self.stream.programMapTable[STREAM_TYPES.adts] ||
309 pid === self.stream.programMapTable[STREAM_TYPES.metadata]) {
310 if (pusi) {
311 // comment out for speed
312 if (0x00 !== data[offset + 0] || 0x00 !== data[offset + 1] || 0x01 !== data[offset + 2]) {
313 // look for PES start code
314 throw new Error("PES did not begin with start code");
315 }
316
317 // var sid:int = data[offset+3]; // StreamID
318 pesPacketSize = (data[offset + 4] << 8) | data[offset + 5];
319 dataAlignmentIndicator = (data[offset + 6] & 0x04) !== 0;
320 ptsDtsIndicator = data[offset + 7];
321 pesHeaderLength = data[offset + 8]; // TODO sanity check header length
322 offset += 9; // Skip past PES header
323
324 // PTS and DTS are normially stored as a 33 bit number.
325 // JavaScript does not have a integer type larger than 32 bit
326 // BUT, we need to convert from 90ns to 1ms time scale anyway.
327 // so what we are going to do instead, is drop the least
328 // significant bit (the same as dividing by two) then we can
329 // divide by 45 (45 * 2 = 90) to get ms.
330 if (ptsDtsIndicator & 0xC0) {
331 // the PTS and DTS are not written out directly. For information on
332 // how they are encoded, see
333 // http://dvd.sourceforge.net/dvdinfo/pes-hdr.html
334 pts = (data[offset + 0] & 0x0E) << 28
335 | (data[offset + 1] & 0xFF) << 21
336 | (data[offset + 2] & 0xFE) << 13
337 | (data[offset + 3] & 0xFF) << 6
338 | (data[offset + 4] & 0xFE) >>> 2;
339 pts /= 45;
340 dts = pts;
341 if (ptsDtsIndicator & 0x40) {// DTS
342 dts = (data[offset + 5] & 0x0E ) << 28
343 | (data[offset + 6] & 0xFF ) << 21
344 | (data[offset + 7] & 0xFE ) << 13
345 | (data[offset + 8] & 0xFF ) << 6
346 | (data[offset + 9] & 0xFE ) >>> 2;
347 dts /= 45;
348 }
349 }
350
351 // Skip past "optional" portion of PTS header
352 offset += pesHeaderLength;
353
354 if (pid === self.stream.programMapTable[STREAM_TYPES.h264]) {
355 h264Stream.setNextTimeStamp(pts,
356 dts,
357 dataAlignmentIndicator);
358 } else if (pid === self.stream.programMapTable[STREAM_TYPES.adts]) {
359 aacStream.setNextTimeStamp(pts,
360 pesPacketSize,
361 dataAlignmentIndicator);
362 }
363 }
364
365 if (pid === self.stream.programMapTable[STREAM_TYPES.adts]) {
366 aacStream.writeBytes(data, offset, end - offset);
367 } else if (pid === self.stream.programMapTable[STREAM_TYPES.h264]) {
368 h264Stream.writeBytes(data, offset, end - offset);
369 } else if (pid === self.stream.programMapTable[STREAM_TYPES.metadata]) {
370 self.metadataStream.push({
371 pts: pts,
372 dts: dts,
373 data: data.subarray(offset)
374 });
375 }
376 } else if (self.stream.pmtPid === pid) {
377 // similarly to the PAT, jump to the first byte of the section
378 if (pusi) {
379 offset += 1 + data[offset];
380 }
381 if (data[offset] !== 0x02) {
382 videojs.log('The table_id of a PMT should be 0x02 but was ' +
383 data[offset].toString(16));
384 }
385
386 // whether this PMT is currently applicable or is part of the
387 // next table to become active
388 pmtCurrentNextIndicator = !!(data[offset + 5] & 0x01);
389 if (pmtCurrentNextIndicator) {
390 // overwrite any existing program map table
391 self.stream.programMapTable = {};
392 // section_length specifies the number of bytes following
393 // its position to the end of this section
394 pmtSectionLength = (data[offset + 1] & 0x0f) << 8 | data[offset + 2];
395 // subtract the length of the program info descriptors
396 pmtProgramDescriptorsLength = (data[offset + 10] & 0x0f) << 8 | data[offset + 11];
397 pmtSectionLength -= pmtProgramDescriptorsLength;
398 // skip CRC and PSI data we dont care about
399 // rest of header + CRC = 9 + 4
400 pmtSectionLength -= 13;
401
402 // capture the PID of PCR packets so we can ignore them if we see any
403 self.stream.programMapTable.pcrPid = (data[offset + 8] & 0x1f) << 8 | data[offset + 9];
404
405 // align offset to the first entry in the PMT
406 offset += 12 + pmtProgramDescriptorsLength;
407
408 // iterate through the entries
409 while (0 < pmtSectionLength) {
410 // the type of data carried in the PID this entry describes
411 streamType = data[offset + 0];
412 // the PID for this entry
413 elementaryPID = (data[offset + 1] & 0x1F) << 8 | data[offset + 2];
414
415 if (streamType === STREAM_TYPES.h264 &&
416 self.stream.programMapTable[streamType] &&
417 self.stream.programMapTable[streamType] !== elementaryPID) {
418 throw new Error("Program has more than 1 video stream");
419 } else if (streamType === STREAM_TYPES.adts &&
420 self.stream.programMapTable[streamType] &&
421 self.stream.programMapTable[streamType] !== elementaryPID) {
422 throw new Error("Program has more than 1 audio Stream");
423 }
424 // add the stream type entry to the map
425 self.stream.programMapTable[streamType] = elementaryPID;
426
427 // TODO add support for MP3 audio
428
429 // the length of the entry descriptor
430 ESInfolength = (data[offset + 3] & 0x0F) << 8 | data[offset + 4];
431 // capture the stream descriptor for metadata streams
432 if (streamType === STREAM_TYPES.metadata) {
433 self.metadataStream.descriptor = new Uint8Array(data.subarray(offset + 5, offset + 5 + ESInfolength));
434 }
435 // move to the first byte after the end of this entry
436 offset += 5 + ESInfolength;
437 pmtSectionLength -= 5 + ESInfolength;
438 }
439 }
440 // We could test the CRC here to detect corruption with extra CPU cost
441 } else if (self.stream.networkPid === pid) {
442 // network information specific data (NIT) packet
443 } else if (0x0011 === pid) {
444 // Service Description Table
445 } else if (0x1FFF === pid) {
446 // NULL packet
447 } else if (self.stream.programMapTable.pcrPid) {
448 // program clock reference (PCR) PID for the primary program
449 // PTS values are sufficient to synchronize playback for us so
450 // we can safely ignore these
451 } else {
452 videojs.log("Unknown PID parsing TS packet: " + pid);
453 }
454
455 return true;
456 };
457
458 self.getTags = function() {
459 return h264Stream.tags;
460 };
461
462 self.stats = {
463 h264Tags: function() {
464 return h264Stream.tags.length;
465 },
466 minVideoPts: function() {
467 return h264Stream.tags[0].pts;
468 },
469 maxVideoPts: function() {
470 return h264Stream.tags[h264Stream.tags.length - 1].pts;
471 },
472 aacTags: function() {
473 return aacStream.tags.length;
474 },
475 minAudioPts: function() {
476 return aacStream.tags[0].pts;
477 },
478 maxAudioPts: function() {
479 return aacStream.tags[aacStream.tags.length - 1].pts;
480 }
481 };
482 };
483
484 // MPEG2-TS constants
485 videojs.Hls.SegmentParser.MP2T_PACKET_LENGTH = MP2T_PACKET_LENGTH = 188;
486 videojs.Hls.SegmentParser.STREAM_TYPES = STREAM_TYPES = {
487 h264: 0x1b,
488 adts: 0x0f,
489 metadata: 0x15
490 };
491
492 })(window);
1 /**
2 * video-js-hls
3 *
4 * Copyright (c) 2014 Brightcove
5 * All rights reserved.
6 */
7
8 /**
9 * A stream-based mp2t to mp4 converter. This utility is used to
10 * deliver mp4s to a SourceBuffer on platforms that support native
11 * Media Source Extensions. The equivalent process for Flash-based
12 * platforms can be found in segment-parser.js
13 */
14 (function(window, videojs, undefined) {
15 'use strict';
16
17 var
18 TransportPacketStream, TransportParseStream, ElementaryStream, VideoSegmentStream,
19 AudioSegmentStream, Transmuxer, AacStream, H264Stream, NalByteStream,
20 MP2T_PACKET_LENGTH, H264_STREAM_TYPE, ADTS_STREAM_TYPE,
21 ADTS_SAMPLING_FREQUENCIES, mp4;
22
23 MP2T_PACKET_LENGTH = 188; // bytes
24 H264_STREAM_TYPE = 0x1b;
25 ADTS_STREAM_TYPE = 0x0f;
26 ADTS_SAMPLING_FREQUENCIES = [
27 96000,
28 88200,
29 64000,
30 48000,
31 44100,
32 32000,
33 24000,
34 22050,
35 16000,
36 12000,
37 11025,
38 8000,
39 7350
40 ];
41
42 mp4 = videojs.mp4;
43
44 /**
45 * Splits an incoming stream of binary data into MPEG-2 Transport
46 * Stream packets.
47 */
48 TransportPacketStream = function() {
49 var
50 buffer = new Uint8Array(MP2T_PACKET_LENGTH),
51 end = 0;
52
53 TransportPacketStream.prototype.init.call(this);
54
55 /**
56 * Deliver new bytes to the stream.
57 */
58 this.push = function(bytes) {
59 var remaining, i;
60
61 // clear out any partial packets in the buffer
62 if (end > 0) {
63 remaining = MP2T_PACKET_LENGTH - end;
64 buffer.set(bytes.subarray(0, remaining), end);
65
66 // we still didn't write out a complete packet
67 if (bytes.byteLength < remaining) {
68 end += bytes.byteLength;
69 return;
70 }
71
72 bytes = bytes.subarray(remaining);
73 end = 0;
74 this.trigger('data', buffer);
75 }
76
77 // if less than a single packet is available, buffer it up for later
78 if (bytes.byteLength < MP2T_PACKET_LENGTH) {
79 buffer.set(bytes.subarray(i), end);
80 end += bytes.byteLength;
81 return;
82 }
83 // parse out all the completed packets
84 i = 0;
85 do {
86 this.trigger('data', bytes.subarray(i, i + MP2T_PACKET_LENGTH));
87 i += MP2T_PACKET_LENGTH;
88 remaining = bytes.byteLength - i;
89 } while (i < bytes.byteLength && remaining >= MP2T_PACKET_LENGTH);
90 // buffer any partial packets left over
91 if (remaining > 0) {
92 buffer.set(bytes.subarray(i));
93 end = remaining;
94 }
95 };
96 };
97 TransportPacketStream.prototype = new videojs.Hls.Stream();
98
99 /**
100 * Accepts an MP2T TransportPacketStream and emits data events with parsed
101 * forms of the individual transport stream packets.
102 */
103 TransportParseStream = function() {
104 var parsePsi, parsePat, parsePmt, parsePes, self;
105 TransportParseStream.prototype.init.call(this);
106 self = this;
107
108 this.programMapTable = {};
109
110 parsePsi = function(payload, psi) {
111 var offset = 0;
112
113 // PSI packets may be split into multiple sections and those
114 // sections may be split into multiple packets. If a PSI
115 // section starts in this packet, the payload_unit_start_indicator
116 // will be true and the first byte of the payload will indicate
117 // the offset from the current position to the start of the
118 // section.
119 if (psi.payloadUnitStartIndicator) {
120 offset += payload[offset] + 1;
121 }
122
123 if (psi.type === 'pat') {
124 parsePat(payload.subarray(offset), psi);
125 } else {
126 parsePmt(payload.subarray(offset), psi);
127 }
128 };
129
130 parsePat = function(payload, pat) {
131 pat.section_number = payload[7];
132 pat.last_section_number = payload[8];
133
134 // skip the PSI header and parse the first PMT entry
135 self.pmtPid = (payload[10] & 0x1F) << 8 | payload[11];
136 pat.pmtPid = self.pmtPid;
137 };
138
139 /**
140 * Parse out the relevant fields of a Program Map Table (PMT).
141 * @param payload {Uint8Array} the PMT-specific portion of an MP2T
142 * packet. The first byte in this array should be the table_id
143 * field.
144 * @param pmt {object} the object that should be decorated with
145 * fields parsed from the PMT.
146 */
147 parsePmt = function(payload, pmt) {
148 var sectionLength, tableEnd, programInfoLength, offset;
149
150 // PMTs can be sent ahead of the time when they should actually
151 // take effect. We don't believe this should ever be the case
152 // for HLS but we'll ignore "forward" PMT declarations if we see
153 // them. Future PMT declarations have the current_next_indicator
154 // set to zero.
155 if (!(payload[5] & 0x01)) {
156 return;
157 }
158
159 // overwrite any existing program map table
160 self.programMapTable = {};
161
162 // the mapping table ends at the end of the current section
163 sectionLength = (payload[1] & 0x0f) << 8 | payload[2];
164 tableEnd = 3 + sectionLength - 4;
165
166 // to determine where the table is, we have to figure out how
167 // long the program info descriptors are
168 programInfoLength = (payload[10] & 0x0f) << 8 | payload[11];
169
170 // advance the offset to the first entry in the mapping table
171 offset = 12 + programInfoLength;
172 while (offset < tableEnd) {
173 // add an entry that maps the elementary_pid to the stream_type
174 self.programMapTable[(payload[offset + 1] & 0x1F) << 8 | payload[offset + 2]] = payload[offset];
175
176 // move to the next table entry
177 // skip past the elementary stream descriptors, if present
178 offset += ((payload[offset + 3] & 0x0F) << 8 | payload[offset + 4]) + 5;
179 }
180
181 // record the map on the packet as well
182 pmt.programMapTable = self.programMapTable;
183 };
184
185 parsePes = function(payload, pes) {
186 var ptsDtsFlags;
187
188 if (!pes.payloadUnitStartIndicator) {
189 pes.data = payload;
190 return;
191 }
192
193 // find out if this packets starts a new keyframe
194 pes.dataAlignmentIndicator = (payload[6] & 0x04) !== 0;
195 // PES packets may be annotated with a PTS value, or a PTS value
196 // and a DTS value. Determine what combination of values is
197 // available to work with.
198 ptsDtsFlags = payload[7];
199
200 // PTS and DTS are normally stored as a 33-bit number. Javascript
201 // performs all bitwise operations on 32-bit integers but it's
202 // convenient to convert from 90ns to 1ms time scale anyway. So
203 // what we are going to do instead is drop the least significant
204 // bit (in effect, dividing by two) then we can divide by 45 (45 *
205 // 2 = 90) to get ms.
206 if (ptsDtsFlags & 0xC0) {
207 // the PTS and DTS are not written out directly. For information
208 // on how they are encoded, see
209 // http://dvd.sourceforge.net/dvdinfo/pes-hdr.html
210 pes.pts = (payload[9] & 0x0E) << 28
211 | (payload[10] & 0xFF) << 21
212 | (payload[11] & 0xFE) << 13
213 | (payload[12] & 0xFF) << 6
214 | (payload[13] & 0xFE) >>> 2;
215 pes.pts /= 45;
216 pes.dts = pes.pts;
217 if (ptsDtsFlags & 0x40) {
218 pes.dts = (payload[14] & 0x0E ) << 28
219 | (payload[15] & 0xFF ) << 21
220 | (payload[16] & 0xFE ) << 13
221 | (payload[17] & 0xFF ) << 6
222 | (payload[18] & 0xFE ) >>> 2;
223 pes.dts /= 45;
224 }
225 }
226
227 // the data section starts immediately after the PES header.
228 // pes_header_data_length specifies the number of header bytes
229 // that follow the last byte of the field.
230 pes.data = payload.subarray(9 + payload[8]);
231 };
232
233 /**
234 * Deliver a new MP2T packet to the stream.
235 */
236 this.push = function(packet) {
237 var
238 result = {},
239 offset = 4;
240 // make sure packet is aligned on a sync byte
241 if (packet[0] !== 0x47) {
242 return this.trigger('error', 'mis-aligned packet');
243 }
244 result.payloadUnitStartIndicator = !!(packet[1] & 0x40);
245
246 // pid is a 13-bit field starting at the last bit of packet[1]
247 result.pid = packet[1] & 0x1f;
248 result.pid <<= 8;
249 result.pid |= packet[2];
250
251 // if an adaption field is present, its length is specified by the
252 // fifth byte of the TS packet header. The adaptation field is
253 // used to add stuffing to PES packets that don't fill a complete
254 // TS packet, and to specify some forms of timing and control data
255 // that we do not currently use.
256 if (((packet[3] & 0x30) >>> 4) > 0x01) {
257 offset += packet[offset] + 1;
258 }
259
260 // parse the rest of the packet based on the type
261 if (result.pid === 0) {
262 result.type = 'pat';
263 parsePsi(packet.subarray(offset), result);
264 } else if (result.pid === this.pmtPid) {
265 result.type = 'pmt';
266 parsePsi(packet.subarray(offset), result);
267 } else {
268 result.streamType = this.programMapTable[result.pid];
269 result.type = 'pes';
270 parsePes(packet.subarray(offset), result);
271 }
272
273 this.trigger('data', result);
274 };
275 };
276 TransportParseStream.prototype = new videojs.Hls.Stream();
277 TransportParseStream.STREAM_TYPES = {
278 h264: 0x1b,
279 adts: 0x0f
280 };
281
282 /**
283 * Reconsistutes program elementary stream (PES) packets from parsed
284 * transport stream packets. That is, if you pipe an
285 * mp2t.TransportParseStream into a mp2t.ElementaryStream, the output
286 * events will be events which capture the bytes for individual PES
287 * packets plus relevant metadata that has been extracted from the
288 * container.
289 */
290 ElementaryStream = function() {
291 var
292 // PES packet fragments
293 video = {
294 data: [],
295 size: 0
296 },
297 audio = {
298 data: [],
299 size: 0
300 },
301 flushStream = function(stream, type) {
302 var
303 event = {
304 type: type,
305 data: new Uint8Array(stream.size),
306 },
307 i = 0,
308 fragment;
309
310 // do nothing if there is no buffered data
311 if (!stream.data.length) {
312 return;
313 }
314 event.trackId = stream.data[0].pid;
315 event.pts = stream.data[0].pts;
316 event.dts = stream.data[0].dts;
317
318 // reassemble the packet
319 while (stream.data.length) {
320 fragment = stream.data.shift();
321
322 event.data.set(fragment.data, i);
323 i += fragment.data.byteLength;
324 }
325 stream.size = 0;
326
327 self.trigger('data', event);
328 },
329 self;
330
331 ElementaryStream.prototype.init.call(this);
332 self = this;
333
334 this.push = function(data) {
335 ({
336 pat: function() {
337 // we have to wait for the PMT to arrive as well before we
338 // have any meaningful metadata
339 },
340 pes: function() {
341 var stream, streamType;
342
343 switch (data.streamType) {
344 case H264_STREAM_TYPE:
345 stream = video;
346 streamType = 'video';
347 break;
348 case ADTS_STREAM_TYPE:
349 stream = audio;
350 streamType = 'audio';
351 break;
352 default:
353 // ignore unknown stream types
354 return;
355 }
356
357 // if a new packet is starting, we can flush the completed
358 // packet
359 if (data.payloadUnitStartIndicator) {
360 flushStream(stream, streamType);
361 }
362
363 // buffer this fragment until we are sure we've received the
364 // complete payload
365 stream.data.push(data);
366 stream.size += data.data.byteLength;
367 },
368 pmt: function() {
369 var
370 event = {
371 type: 'metadata',
372 tracks: []
373 },
374 programMapTable = data.programMapTable,
375 k,
376 track;
377
378 // translate streams to tracks
379 for (k in programMapTable) {
380 if (programMapTable.hasOwnProperty(k)) {
381 track = {};
382 track.id = +k;
383 if (programMapTable[k] === H264_STREAM_TYPE) {
384 track.codec = 'avc';
385 track.type = 'video';
386 } else if (programMapTable[k] === ADTS_STREAM_TYPE) {
387 track.codec = 'adts';
388 track.type = 'audio';
389 }
390 event.tracks.push(track);
391 }
392 }
393 self.trigger('data', event);
394 }
395 })[data.type]();
396 };
397
398 /**
399 * Flush any remaining input. Video PES packets may be of variable
400 * length. Normally, the start of a new video packet can trigger the
401 * finalization of the previous packet. That is not possible if no
402 * more video is forthcoming, however. In that case, some other
403 * mechanism (like the end of the file) has to be employed. When it is
404 * clear that no additional data is forthcoming, calling this method
405 * will flush the buffered packets.
406 */
407 this.end = function() {
408 flushStream(video, 'video');
409 flushStream(audio, 'audio');
410 };
411 };
412 ElementaryStream.prototype = new videojs.Hls.Stream();
413
414 /*
415 * Accepts a ElementaryStream and emits data events with parsed
416 * AAC Audio Frames of the individual packets. Input audio in ADTS
417 * format is unpacked and re-emitted as AAC frames.
418 *
419 * @see http://wiki.multimedia.cx/index.php?title=ADTS
420 * @see http://wiki.multimedia.cx/?title=Understanding_AAC
421 */
422 AacStream = function() {
423 var i = 1, self, buffer;
424 AacStream.prototype.init.call(this);
425 self = this;
426
427 this.push = function(packet) {
428 var frameLength;
429
430 if (packet.type !== 'audio') {
431 // ignore non-audio data
432 return;
433 }
434
435 buffer = packet.data;
436
437 // unpack any ADTS frames which have been fully received
438 while (i + 4 < buffer.length) {
439 // frame length is a 13 bit integer starting 16 bits from the
440 // end of the sync sequence
441 frameLength = ((buffer[i + 2] & 0x03) << 11) |
442 (buffer[i + 3] << 3) |
443 ((buffer[i + 4] & 0xe0) >> 5);
444
445 // deliver the AAC frame
446 this.trigger('data', {
447 channelcount: ((buffer[i + 1] & 1) << 3) |
448 ((buffer[i + 2] & 0xc0) >> 6),
449 samplerate: ADTS_SAMPLING_FREQUENCIES[(buffer[i + 1] & 0x3c) >> 2],
450 // assume ISO/IEC 14496-12 AudioSampleEntry default of 16
451 samplesize: 16,
452 data: buffer.subarray(i + 6, i + frameLength - 1)
453 });
454
455 // flush the finished frame and try again
456 buffer = buffer.subarray(i + frameLength - 1);
457 i = 1;
458 }
459 };
460 };
461 AacStream.prototype = new videojs.Hls.Stream();
462
463 /**
464 * Constructs a single-track, ISO BMFF media segment from AAC data
465 * events. The output of this stream can be fed to a SourceBuffer
466 * configured with a suitable initialization segment.
467 */
468 // TODO: share common code with VideoSegmentStream
469 AudioSegmentStream = function(track) {
470 var aacFrames = [], aacFramesLength = 0, sequenceNumber = 0;
471 AudioSegmentStream.prototype.init.call(this);
472
473 this.push = function(data) {
474 // buffer audio data until end() is called
475 aacFrames.push(data);
476 aacFramesLength += data.data.byteLength;
477 };
478
479 this.end = function() {
480 var boxes, currentFrame, data, sample, i, mdat, moof;
481 // return early if no audio data has been observed
482 if (aacFramesLength === 0) {
483 return;
484 }
485
486 // concatenate the audio data to constuct the mdat
487 data = new Uint8Array(aacFramesLength);
488 track.samples = [];
489 i = 0;
490 while (aacFrames.length) {
491 currentFrame = aacFrames[0];
492 sample = {
493 size: currentFrame.data.byteLength,
494 duration: 1024 // FIXME calculate for realz
495 };
496 track.samples.push(sample);
497
498 data.set(currentFrame.data, i);
499 i += currentFrame.data.byteLength;
500
501 aacFrames.shift();
502 }
503 aacFramesLength = 0;
504 mdat = mp4.mdat(data);
505
506 moof = mp4.moof(sequenceNumber, [track]);
507 boxes = new Uint8Array(moof.byteLength + mdat.byteLength);
508
509 // bump the sequence number for next time
510 sequenceNumber++;
511
512 boxes.set(moof);
513 boxes.set(mdat, moof.byteLength);
514
515 this.trigger('data', boxes);
516 };
517 };
518 AudioSegmentStream.prototype = new videojs.Hls.Stream();
519
520 /**
521 * Accepts a NAL unit byte stream and unpacks the embedded NAL units.
522 */
523 NalByteStream = function() {
524 var
525 syncPoint = 1,
526 i,
527 buffer;
528 NalByteStream.prototype.init.call(this);
529
530 this.push = function(data) {
531 var swapBuffer;
532
533 if (!buffer) {
534 buffer = data.data;
535 } else {
536 swapBuffer = new Uint8Array(buffer.byteLength + data.data.byteLength);
537 swapBuffer.set(buffer);
538 swapBuffer.set(data.data, buffer.byteLength);
539 buffer = swapBuffer;
540 }
541
542 // Rec. ITU-T H.264, Annex B
543 // scan for NAL unit boundaries
544
545 // a match looks like this:
546 // 0 0 1 .. NAL .. 0 0 1
547 // ^ sync point ^ i
548 // or this:
549 // 0 0 1 .. NAL .. 0 0 0
550 // ^ sync point ^ i
551
552 // advance the sync point to a NAL start, if necessary
553 for (; syncPoint < buffer.byteLength - 3; syncPoint++) {
554 if (buffer[syncPoint + 2] === 1) {
555 // the sync point is properly aligned
556 i = syncPoint + 5;
557 break;
558 }
559 }
560
561 while (i < buffer.byteLength) {
562 // look at the current byte to determine if we've hit the end of
563 // a NAL unit boundary
564 switch (buffer[i]) {
565 case 0:
566 // skip past non-sync sequences
567 if (buffer[i - 1] !== 0) {
568 i += 2;
569 break;
570 } else if (buffer[i - 2] !== 0) {
571 i++;
572 break;
573 }
574
575 // deliver the NAL unit
576 this.trigger('data', buffer.subarray(syncPoint + 3, i - 2));
577
578 // drop trailing zeroes
579 do {
580 i++;
581 } while (buffer[i] !== 1 && i < buffer.length);
582 syncPoint = i - 2;
583 i += 3;
584 break;
585 case 1:
586 // skip past non-sync sequences
587 if (buffer[i - 1] !== 0 ||
588 buffer[i - 2] !== 0) {
589 i += 3;
590 break;
591 }
592
593 // deliver the NAL unit
594 this.trigger('data', buffer.subarray(syncPoint + 3, i - 2));
595 syncPoint = i - 2;
596 i += 3;
597 break;
598 default:
599 // the current byte isn't a one or zero, so it cannot be part
600 // of a sync sequence
601 i += 3;
602 break;
603 }
604 }
605 // filter out the NAL units that were delivered
606 buffer = buffer.subarray(syncPoint);
607 i -= syncPoint;
608 syncPoint = 0;
609 };
610
611 this.end = function() {
612 // deliver the last buffered NAL unit
613 if (buffer && buffer.byteLength > 3) {
614 this.trigger('data', buffer.subarray(syncPoint + 3));
615 }
616 };
617 };
618 NalByteStream.prototype = new videojs.Hls.Stream();
619
620 /**
621 * Accepts input from a ElementaryStream and produces H.264 NAL unit data
622 * events.
623 */
624 H264Stream = function() {
625 var
626 nalByteStream = new NalByteStream(),
627 self,
628 trackId,
629 currentPts,
630 currentDts,
631
632 readSequenceParameterSet,
633 skipScalingList;
634
635 H264Stream.prototype.init.call(this);
636 self = this;
637
638 this.push = function(packet) {
639 if (packet.type !== 'video') {
640 return;
641 }
642 trackId = packet.trackId;
643 currentPts = packet.pts;
644 currentDts = packet.dts;
645
646 nalByteStream.push(packet);
647 };
648
649 nalByteStream.on('data', function(data) {
650 var event = {
651 trackId: trackId,
652 pts: currentPts,
653 dts: currentDts,
654 data: data
655 };
656 switch (data[0] & 0x1f) {
657
658 case 0x05:
659 event.nalUnitType = 'slice_layer_without_partitioning_rbsp_idr';
660 break;
661 case 0x07:
662 event.nalUnitType = 'seq_parameter_set_rbsp';
663 event.config = readSequenceParameterSet(data.subarray(1));
664 break;
665 case 0x08:
666 event.nalUnitType = 'pic_parameter_set_rbsp';
667 break;
668 case 0x09:
669 event.nalUnitType = 'access_unit_delimiter_rbsp';
670 break;
671
672 default:
673 break;
674 }
675 self.trigger('data', event);
676 });
677
678 this.end = function() {
679 nalByteStream.end();
680 };
681
682 /**
683 * Advance the ExpGolomb decoder past a scaling list. The scaling
684 * list is optionally transmitted as part of a sequence parameter
685 * set and is not relevant to transmuxing.
686 * @param count {number} the number of entries in this scaling list
687 * @param expGolombDecoder {object} an ExpGolomb pointed to the
688 * start of a scaling list
689 * @see Recommendation ITU-T H.264, Section 7.3.2.1.1.1
690 */
691 skipScalingList = function(count, expGolombDecoder) {
692 var
693 lastScale = 8,
694 nextScale = 8,
695 j,
696 deltaScale;
697
698 for (j = 0; j < count; j++) {
699 if (nextScale !== 0) {
700 deltaScale = expGolombDecoder.readExpGolomb();
701 nextScale = (lastScale + deltaScale + 256) % 256;
702 }
703
704 lastScale = (nextScale === 0) ? lastScale : nextScale;
705 }
706 };
707
708 /**
709 * Read a sequence parameter set and return some interesting video
710 * properties. A sequence parameter set is the H264 metadata that
711 * describes the properties of upcoming video frames.
712 * @param data {Uint8Array} the bytes of a sequence parameter set
713 * @return {object} an object with configuration parsed from the
714 * sequence parameter set, including the dimensions of the
715 * associated video frames.
716 */
717 readSequenceParameterSet = function(data) {
718 var
719 frameCropLeftOffset = 0,
720 frameCropRightOffset = 0,
721 frameCropTopOffset = 0,
722 frameCropBottomOffset = 0,
723 expGolombDecoder, profileIdc, levelIdc, profileCompatibility,
724 chromaFormatIdc, picOrderCntType,
725 numRefFramesInPicOrderCntCycle, picWidthInMbsMinus1,
726 picHeightInMapUnitsMinus1,
727 frameMbsOnlyFlag,
728 scalingListCount,
729 i;
730
731 expGolombDecoder = new videojs.Hls.ExpGolomb(data);
732 profileIdc = expGolombDecoder.readUnsignedByte(); // profile_idc
733 profileCompatibility = expGolombDecoder.readBits(5); // constraint_set[0-5]_flag
734 expGolombDecoder.skipBits(3); // u(1), reserved_zero_2bits u(2)
735 levelIdc = expGolombDecoder.readUnsignedByte(); // level_idc u(8)
736 expGolombDecoder.skipUnsignedExpGolomb(); // seq_parameter_set_id
737
738 // some profiles have more optional data we don't need
739 if (profileIdc === 100 ||
740 profileIdc === 110 ||
741 profileIdc === 122 ||
742 profileIdc === 244 ||
743 profileIdc === 44 ||
744 profileIdc === 83 ||
745 profileIdc === 86 ||
746 profileIdc === 118 ||
747 profileIdc === 128) {
748 chromaFormatIdc = expGolombDecoder.readUnsignedExpGolomb();
749 if (chromaFormatIdc === 3) {
750 expGolombDecoder.skipBits(1); // separate_colour_plane_flag
751 }
752 expGolombDecoder.skipUnsignedExpGolomb(); // bit_depth_luma_minus8
753 expGolombDecoder.skipUnsignedExpGolomb(); // bit_depth_chroma_minus8
754 expGolombDecoder.skipBits(1); // qpprime_y_zero_transform_bypass_flag
755 if (expGolombDecoder.readBoolean()) { // seq_scaling_matrix_present_flag
756 scalingListCount = (chromaFormatIdc !== 3) ? 8 : 12;
757 for (i = 0; i < scalingListCount; i++) {
758 if (expGolombDecoder.readBoolean()) { // seq_scaling_list_present_flag[ i ]
759 if (i < 6) {
760 skipScalingList(16, expGolombDecoder);
761 } else {
762 skipScalingList(64, expGolombDecoder);
763 }
764 }
765 }
766 }
767 }
768
769 expGolombDecoder.skipUnsignedExpGolomb(); // log2_max_frame_num_minus4
770 picOrderCntType = expGolombDecoder.readUnsignedExpGolomb();
771
772 if (picOrderCntType === 0) {
773 expGolombDecoder.readUnsignedExpGolomb(); //log2_max_pic_order_cnt_lsb_minus4
774 } else if (picOrderCntType === 1) {
775 expGolombDecoder.skipBits(1); // delta_pic_order_always_zero_flag
776 expGolombDecoder.skipExpGolomb(); // offset_for_non_ref_pic
777 expGolombDecoder.skipExpGolomb(); // offset_for_top_to_bottom_field
778 numRefFramesInPicOrderCntCycle = expGolombDecoder.readUnsignedExpGolomb();
779 for(i = 0; i < numRefFramesInPicOrderCntCycle; i++) {
780 expGolombDecoder.skipExpGolomb(); // offset_for_ref_frame[ i ]
781 }
782 }
783
784 expGolombDecoder.skipUnsignedExpGolomb(); // max_num_ref_frames
785 expGolombDecoder.skipBits(1); // gaps_in_frame_num_value_allowed_flag
786
787 picWidthInMbsMinus1 = expGolombDecoder.readUnsignedExpGolomb();
788 picHeightInMapUnitsMinus1 = expGolombDecoder.readUnsignedExpGolomb();
789
790 frameMbsOnlyFlag = expGolombDecoder.readBits(1);
791 if (frameMbsOnlyFlag === 0) {
792 expGolombDecoder.skipBits(1); // mb_adaptive_frame_field_flag
793 }
794
795 expGolombDecoder.skipBits(1); // direct_8x8_inference_flag
796 if (expGolombDecoder.readBoolean()) { // frame_cropping_flag
797 frameCropLeftOffset = expGolombDecoder.readUnsignedExpGolomb();
798 frameCropRightOffset = expGolombDecoder.readUnsignedExpGolomb();
799 frameCropTopOffset = expGolombDecoder.readUnsignedExpGolomb();
800 frameCropBottomOffset = expGolombDecoder.readUnsignedExpGolomb();
801 }
802
803 return {
804 profileIdc: profileIdc,
805 levelIdc: levelIdc,
806 profileCompatibility: profileCompatibility,
807 width: ((picWidthInMbsMinus1 + 1) * 16) - frameCropLeftOffset * 2 - frameCropRightOffset * 2,
808 height: ((2 - frameMbsOnlyFlag) * (picHeightInMapUnitsMinus1 + 1) * 16) - (frameCropTopOffset * 2) - (frameCropBottomOffset * 2)
809 };
810 };
811
812 };
813 H264Stream.prototype = new videojs.Hls.Stream();
814
815 /**
816 * Constructs a single-track, ISO BMFF media segment from H264 data
817 * events. The output of this stream can be fed to a SourceBuffer
818 * configured with a suitable initialization segment.
819 * @param track {object} track metadata configuration
820 */
821 VideoSegmentStream = function(track) {
822 var
823 sequenceNumber = 0,
824 nalUnits = [],
825 nalUnitsLength = 0;
826 VideoSegmentStream.prototype.init.call(this);
827
828 this.push = function(data) {
829 // buffer video until end() is called
830 nalUnits.push(data);
831 nalUnitsLength += data.data.byteLength;
832 };
833
834 this.end = function() {
835 var startUnit, currentNal, moof, mdat, boxes, i, data, view, sample;
836
837 // return early if no video data has been observed
838 if (nalUnitsLength === 0) {
839 return;
840 }
841
842 // concatenate the video data and construct the mdat
843 // first, we have to build the index from byte locations to
844 // samples (that is, frames) in the video data
845 data = new Uint8Array(nalUnitsLength + (4 * nalUnits.length));
846 view = new DataView(data.buffer);
847 track.samples = [];
848
849 // see ISO/IEC 14496-12:2012, section 8.6.4.3
850 sample = {
851 size: 0,
852 flags: {
853 isLeading: 0,
854 dependsOn: 1,
855 isDependedOn: 0,
856 hasRedundancy: 0,
857 degradationPriority: 0
858 }
859 };
860 i = 0;
861 while (nalUnits.length) {
862 currentNal = nalUnits[0];
863 // flush the sample we've been building when a new sample is started
864 if (currentNal.nalUnitType === 'access_unit_delimiter_rbsp') {
865 if (startUnit) {
866 // convert the duration to 90kHZ timescale to match the
867 // timescales specified in the init segment
868 sample.duration = (currentNal.dts - startUnit.dts) * 90;
869 track.samples.push(sample);
870 }
871 sample = {
872 size: 0,
873 flags: {
874 isLeading: 0,
875 dependsOn: 1,
876 isDependedOn: 0,
877 hasRedundancy: 0,
878 degradationPriority: 0
879 },
880 compositionTimeOffset: currentNal.pts - currentNal.dts
881 };
882 startUnit = currentNal;
883 }
884 if (currentNal.nalUnitType === 'slice_layer_without_partitioning_rbsp_idr') {
885 // the current sample is a key frame
886 sample.flags.dependsOn = 2;
887 }
888 sample.size += 4; // space for the NAL length
889 sample.size += currentNal.data.byteLength;
890
891 view.setUint32(i, currentNal.data.byteLength);
892 i += 4;
893 data.set(currentNal.data, i);
894 i += currentNal.data.byteLength;
895
896 nalUnits.shift();
897 }
898 // record the last sample
899 if (track.samples.length) {
900 sample.duration = track.samples[track.samples.length - 1].duration;
901 }
902 track.samples.push(sample);
903 nalUnitsLength = 0;
904 mdat = mp4.mdat(data);
905
906 moof = mp4.moof(sequenceNumber, [track]);
907
908 // it would be great to allocate this array up front instead of
909 // throwing away hundreds of media segment fragments
910 boxes = new Uint8Array(moof.byteLength + mdat.byteLength);
911
912 // bump the sequence number for next time
913 sequenceNumber++;
914
915 boxes.set(moof);
916 boxes.set(mdat, moof.byteLength);
917
918 this.trigger('data', boxes);
919 };
920 };
921 VideoSegmentStream.prototype = new videojs.Hls.Stream();
922
923 /**
924 * A Stream that expects MP2T binary data as input and produces
925 * corresponding media segments, suitable for use with Media Source
926 * Extension (MSE) implementations that support the ISO BMFF byte
927 * stream format, like Chrome.
928 * @see test/muxer/mse-demo.html for sample usage of a Transmuxer with
929 * MSE
930 */
931 Transmuxer = function() {
932 var
933 self = this,
934 videoTrack,
935 audioTrack,
936 config,
937 pps,
938
939 packetStream, parseStream, elementaryStream,
940 aacStream, h264Stream,
941 videoSegmentStream, audioSegmentStream;
942
943 Transmuxer.prototype.init.call(this);
944
945 // set up the parsing pipeline
946 packetStream = new TransportPacketStream();
947 parseStream = new TransportParseStream();
948 elementaryStream = new ElementaryStream();
949 aacStream = new AacStream();
950 h264Stream = new H264Stream();
951
952 packetStream.pipe(parseStream);
953 parseStream.pipe(elementaryStream);
954 elementaryStream.pipe(aacStream);
955 elementaryStream.pipe(h264Stream);
956
957 // handle incoming data events
958 h264Stream.on('data', function(data) {
959 // record the track config
960 if (data.nalUnitType === 'seq_parameter_set_rbsp' &&
961 !config) {
962 config = data.config;
963
964 videoTrack.width = config.width;
965 videoTrack.height = config.height;
966 videoTrack.sps = [data.data];
967 videoTrack.profileIdc = config.profileIdc;
968 videoTrack.levelIdc = config.levelIdc;
969 videoTrack.profileCompatibility = config.profileCompatibility;
970
971 // generate an init segment once all the metadata is available
972 if (pps) {
973 self.trigger('data', {
974 type: 'video',
975 data: videojs.mp4.initSegment([videoTrack])
976 });
977 }
978 }
979 if (data.nalUnitType === 'pic_parameter_set_rbsp' &&
980 !pps) {
981 pps = data.data;
982 videoTrack.pps = [data.data];
983
984 if (config) {
985 self.trigger('data', {
986 type: 'video',
987 data: videojs.mp4.initSegment([videoTrack])
988 });
989 }
990 }
991 });
992 // generate an init segment based on the first audio sample
993 aacStream.on('data', function(data) {
994 if (audioTrack && audioTrack.channelcount === undefined) {
995 audioTrack.channelcount = data.channelcount;
996 audioTrack.samplerate = data.samplerate;
997 audioTrack.samplesize = data.samplesize;
998 self.trigger('data', {
999 type: 'audio',
1000 data: videojs.mp4.initSegment([audioTrack])
1001 });
1002 }
1003 });
1004 // hook up the segment streams once track metadata is delivered
1005 elementaryStream.on('data', function(data) {
1006 var i, triggerData = function(type) {
1007 return function(segment) {
1008 self.trigger('data', {
1009 type: type,
1010 data: segment
1011 });
1012 };
1013 };
1014 if (data.type === 'metadata') {
1015 i = data.tracks.length;
1016
1017 // scan the tracks listed in the metadata
1018 while (i--) {
1019
1020 // hook up the video segment stream to the first track with h264 data
1021 if (data.tracks[i].type === 'video' && !videoSegmentStream) {
1022 videoTrack = data.tracks[i];
1023 videoSegmentStream = new VideoSegmentStream(videoTrack);
1024 h264Stream.pipe(videoSegmentStream);
1025 videoSegmentStream.on('data', triggerData('video'));
1026 break;
1027 }
1028
1029 // hook up the audio segment stream to the first track with aac data
1030 if (data.tracks[i].type === 'audio' && !audioSegmentStream) {
1031 audioTrack = data.tracks[i];
1032 audioSegmentStream = new AudioSegmentStream(audioTrack);
1033 aacStream.pipe(audioSegmentStream);
1034 audioSegmentStream.on('data', triggerData('audio'));
1035 }
1036 }
1037 }
1038 });
1039
1040 // feed incoming data to the front of the parsing pipeline
1041 this.push = function(data) {
1042 packetStream.push(data);
1043 };
1044 // flush any buffered data
1045 this.end = function() {
1046 elementaryStream.end();
1047 h264Stream.end();
1048 if (videoSegmentStream) {
1049 videoSegmentStream.end();
1050 }
1051 if (audioSegmentStream) {
1052 audioSegmentStream.end();
1053 }
1054 };
1055 };
1056 Transmuxer.prototype = new videojs.Hls.Stream();
1057
1058 window.videojs.mp2t = {
1059 PAT_PID: 0x0000,
1060 MP2T_PACKET_LENGTH: MP2T_PACKET_LENGTH,
1061 H264_STREAM_TYPE: H264_STREAM_TYPE,
1062 ADTS_STREAM_TYPE: ADTS_STREAM_TYPE,
1063 TransportPacketStream: TransportPacketStream,
1064 TransportParseStream: TransportParseStream,
1065 ElementaryStream: ElementaryStream,
1066 VideoSegmentStream: VideoSegmentStream,
1067 Transmuxer: Transmuxer,
1068 AacStream: AacStream,
1069 H264Stream: H264Stream
1070 };
1071 })(window, window.videojs);
1 (function(window) {
2 /*
3 ======== A Handy Little QUnit Reference ========
4 http://api.qunitjs.com/
5
6 Test methods:
7 module(name, {[setup][ ,teardown]})
8 test(name, callback)
9 expect(numberOfAssertions)
10 stop(increment)
11 start(decrement)
12 Test assertions:
13 ok(value, [message])
14 equal(actual, expected, [message])
15 notEqual(actual, expected, [message])
16 deepEqual(actual, expected, [message])
17 notDeepEqual(actual, expected, [message])
18 strictEqual(actual, expected, [message])
19 notStrictEqual(actual, expected, [message])
20 throws(block, [expected], [message])
21 */
22 var
23 buffer,
24 ExpGolomb = window.videojs.Hls.ExpGolomb,
25 expGolomb;
26
27 module('Exponential Golomb coding');
28
29 test('small numbers are coded correctly', function() {
30 var
31 expected = [
32 [0xF8, 0],
33 [0x5F, 1],
34 [0x7F, 2],
35 [0x27, 3],
36 [0x2F, 4],
37 [0x37, 5],
38 [0x3F, 6],
39 [0x11, 7],
40 [0x13, 8],
41 [0x15, 9]
42 ],
43 i = expected.length,
44 result;
45
46 while (i--) {
47 buffer = new Uint8Array([expected[i][0]]);
48 expGolomb = new ExpGolomb(buffer);
49 result = expGolomb.readUnsignedExpGolomb();
50 equal(expected[i][1], result, expected[i][0] + ' is decoded to ' + expected[i][1]);
51 }
52 });
53
54 test('drops working data as it is parsed', function() {
55 var expGolomb = new ExpGolomb(new Uint8Array([0x00, 0xFF]));
56 expGolomb.skipBits(8);
57 equal(8, expGolomb.bitsAvailable(), '8 bits remain');
58 equal(0xFF, expGolomb.readBits(8), 'the second byte is read');
59 });
60
61 test('drops working data when skipping leading zeros', function() {
62 var expGolomb = new ExpGolomb(new Uint8Array([0x00, 0x00, 0x00, 0x00, 0xFF]));
63 equal(32, expGolomb.skipLeadingZeros(), '32 leading zeros are dropped');
64 equal(8, expGolomb.bitsAvailable(), '8 bits remain');
65 equal(0xFF, expGolomb.readBits(8), 'the second byte is read');
66 });
67
68 test('drops working data when skipping leading zeros', function() {
69 var expGolomb = new ExpGolomb(new Uint8Array([0x15, 0xab, 0x40, 0xc8, 0xFF]));
70 equal(3, expGolomb.skipLeadingZeros(), '3 leading zeros are dropped');
71 equal((8 * 4) + 5, expGolomb.bitsAvailable(), '37 bits remain');
72 expGolomb.skipBits(1);
73 equal(0x5a, expGolomb.readBits(8), 'the next bits are read');
74 });
75
76 test('parses a sequence parameter set', function() {
77 var
78 sps = new Uint8Array([
79 0x27, 0x42, 0xe0, 0x0b,
80 0xa9, 0x18, 0x60, 0x9d,
81 0x80, 0x35, 0x06, 0x01,
82 0x06, 0xb6, 0xc2, 0xb5,
83 0xef, 0x7c, 0x04
84 ]),
85 expGolomb = new ExpGolomb(sps);
86
87 strictEqual(expGolomb.readBits(8), 0x27, 'the NAL type specifies an SPS');
88 strictEqual(expGolomb.readBits(8), 66, 'profile_idc is 66');
89 strictEqual(expGolomb.readBits(4), 0x0E, 'constraints 0-3 are correct');
90
91 expGolomb.skipBits(4);
92 strictEqual(expGolomb.readBits(8), 11, 'level_idc is 11');
93 strictEqual(expGolomb.readUnsignedExpGolomb(), 0, 'seq_parameter_set_id is 0');
94 strictEqual(expGolomb.readUnsignedExpGolomb(), 1, 'log2_max_frame_num_minus4 is 1');
95 strictEqual(expGolomb.readUnsignedExpGolomb(), 0, 'pic_order_cnt_type is 0');
96 strictEqual(expGolomb.readUnsignedExpGolomb(), 3, 'log2_max_pic_order_cnt_lsb_minus4 is 3');
97 strictEqual(expGolomb.readUnsignedExpGolomb(), 2, 'max_num_ref_frames is 2');
98 strictEqual(expGolomb.readBits(1), 0, 'gaps_in_frame_num_value_allowed_flag is false');
99 strictEqual(expGolomb.readUnsignedExpGolomb(), 11, 'pic_width_in_mbs_minus1 is 11');
100 strictEqual(expGolomb.readUnsignedExpGolomb(), 8, 'pic_height_in_map_units_minus1 is 8');
101 strictEqual(expGolomb.readBits(1), 1, 'frame_mbs_only_flag is true');
102 strictEqual(expGolomb.readBits(1), 1, 'direct_8x8_inference_flag is true');
103 strictEqual(expGolomb.readBits(1), 0, 'frame_cropping_flag is false');
104 });
105
106 })(this);
1 (function(window) {
2 /*
3 ======== A Handy Little QUnit Reference ========
4 http://api.qunitjs.com/
5
6 Test methods:
7 module(name, {[setup][ ,teardown]})
8 test(name, callback)
9 expect(numberOfAssertions)
10 stop(increment)
11 start(decrement)
12 Test assertions:
13 ok(value, [message])
14 equal(actual, expected, [message])
15 notEqual(actual, expected, [message])
16 deepEqual(actual, expected, [message])
17 notDeepEqual(actual, expected, [message])
18 strictEqual(actual, expected, [message])
19 notStrictEqual(actual, expected, [message])
20 throws(block, [expected], [message])
21 */
22 var FlvTag = window.videojs.Hls.FlvTag;
23
24 module('FLV tag');
25
26 test('writeBytes with zero length writes the entire array', function() {
27 var
28 tag = new FlvTag(FlvTag.VIDEO_TAG),
29 headerLength = tag.length;
30 tag.writeBytes(new Uint8Array([0x1, 0x2, 0x3]));
31
32 equal(3 + headerLength, tag.length, '3 payload bytes are written');
33 });
34
35 test('writeShort writes a two byte sequence', function() {
36 var
37 tag = new FlvTag(FlvTag.VIDEO_TAG),
38 headerLength = tag.length;
39 tag.writeShort(0x0102);
40
41 equal(2 + headerLength, tag.length, '2 bytes are written');
42 equal(0x0102,
43 new DataView(tag.bytes.buffer).getUint16(tag.length - 2),
44 'the value is written');
45 });
46
47 test('writeBytes grows the internal byte array dynamically', function() {
48 var
49 tag = new FlvTag(FlvTag.VIDEO_TAG),
50 tooManyBytes = new Uint8Array(tag.bytes.byteLength + 1);
51
52 try {
53 tag.writeBytes(tooManyBytes);
54 ok(true, 'the buffer grew to fit the data');
55 } catch(e) {
56 ok(!e, 'the buffer should grow');
57 }
58 });
59
60 })(this);
1 (function(videojs) {
2 module('H264 Stream');
3
4 var
5 nalUnitTypes = window.videojs.Hls.NALUnitType,
6 FlvTag = window.videojs.Hls.FlvTag,
7
8 accessUnitDelimiter = new Uint8Array([
9 0x00,
10 0x00,
11 0x01,
12 nalUnitTypes.access_unit_delimiter_rbsp
13 ]),
14 seqParamSet = new Uint8Array([
15 0x00,
16 0x00,
17 0x01,
18 0x60 | nalUnitTypes.seq_parameter_set_rbsp,
19 0x00, // profile_idc
20 0x00, // constraint_set flags
21 0x00, // level_idc
22 // seq_parameter_set_id ue(v) 0 => 1
23 // log2_max_frame_num_minus4 ue(v) 1 => 010
24 // pic_order_cnt_type ue(v) 0 => 1
25 // log2_max_pic_order_cnt_lsb_minus4 ue(v) 1 => 010
26 // max_num_ref_frames ue(v) 1 => 010
27 // gaps_in_frame_num_value_allowed u(1) 0
28 // pic_width_in_mbs_minus1 ue(v) 0 => 1
29 // pic_height_in_map_units_minus1 ue(v) 0 => 1
30 // frame_mbs_only_flag u(1) 1
31 // direct_8x8_inference_flag u(1) 0
32 // frame_cropping_flag u(1) 0
33 // vui_parameters_present_flag u(1) 0
34 // 1010 1010 0100 1110 00(00 0000)
35 0xAA,
36 0x4E,
37 0x00
38 ]);
39
40 test('metadata is generated for IDRs after a full NAL unit is written', function() {
41 var
42 h264Stream = new videojs.Hls.H264Stream(),
43 idr = new Uint8Array([
44 0x00,
45 0x00,
46 0x01,
47 nalUnitTypes.slice_layer_without_partitioning_rbsp_idr
48 ]);
49
50 h264Stream.setNextTimeStamp(0, 0, true);
51 h264Stream.writeBytes(accessUnitDelimiter, 0, accessUnitDelimiter.byteLength);
52 h264Stream.writeBytes(seqParamSet, 0, seqParamSet.byteLength);
53 h264Stream.writeBytes(idr, 0, idr.byteLength);
54 h264Stream.setNextTimeStamp(1, 1, true);
55
56 strictEqual(h264Stream.tags.length, 3, 'three tags are written');
57 ok(FlvTag.isMetaData(h264Stream.tags[0].bytes),
58 'metadata is written');
59 ok(FlvTag.isVideoFrame(h264Stream.tags[1].bytes),
60 'picture parameter set is written');
61 ok(h264Stream.tags[2].keyFrame, 'key frame is written');
62 });
63
64 test('make sure we add metadata and extra data at the beginning of a stream', function() {
65 var
66 H264ExtraData = videojs.Hls.H264ExtraData,
67 oldExtraData = H264ExtraData.prototype.extraDataTag,
68 oldMetadata = H264ExtraData.prototype.metaDataTag,
69 h264Stream;
70
71 H264ExtraData.prototype.extraDataTag = function() {
72 return 'extraDataTag';
73 };
74 H264ExtraData.prototype.metaDataTag = function() {
75 return 'metaDataTag';
76 };
77
78 h264Stream = new videojs.Hls.H264Stream();
79
80 h264Stream.setTimeStampOffset(0);
81 h264Stream.setNextTimeStamp(0, 0, true);
82 // the sps provides the metadata for the stream
83 h264Stream.writeBytes(seqParamSet, 0, seqParamSet.byteLength);
84 h264Stream.writeBytes(accessUnitDelimiter, 0, accessUnitDelimiter.byteLength);
85
86 // make sure that keyFrame is set to false but that we don't have any tags currently written out
87 h264Stream._h264Frame.keyFrame = false;
88 h264Stream.tags = [];
89
90 h264Stream.setNextTimeStamp(5, 5, true);
91 h264Stream.writeBytes(accessUnitDelimiter, 0, accessUnitDelimiter.byteLength);
92 // flush out the last tag
93 h264Stream.writeBytes(accessUnitDelimiter, 0, accessUnitDelimiter.byteLength);
94
95 strictEqual(h264Stream.tags.length, 4, 'three tags are ready');
96 strictEqual(h264Stream.tags[0], 'metaDataTag', 'the first tag is the metaDataTag');
97 strictEqual(h264Stream.tags[1], 'extraDataTag', 'the second tag is the extraDataTag');
98
99 H264ExtraData.prototype.extraDataTag = oldExtraData;
100 H264ExtraData.prototype.metaDataTag = oldMetadata;
101 });
102
103 })(window.videojs);
...@@ -77,25 +77,15 @@ module.exports = function(config) { ...@@ -77,25 +77,15 @@ module.exports = function(config) {
77 '../test/karma-qunit-shim.js', 77 '../test/karma-qunit-shim.js',
78 '../src/videojs-hls.js', 78 '../src/videojs-hls.js',
79 '../src/stream.js', 79 '../src/stream.js',
80 '../src/flv-tag.js',
81 '../src/exp-golomb.js',
82 '../src/h264-extradata.js',
83 '../src/h264-stream.js',
84 '../src/aac-stream.js',
85 '../src/metadata-stream.js',
86 '../src/segment-parser.js',
87 '../src/m3u8/m3u8-parser.js', 80 '../src/m3u8/m3u8-parser.js',
88 '../src/xhr.js', 81 '../src/xhr.js',
89 '../src/playlist.js', 82 '../src/playlist.js',
90 '../src/playlist-loader.js', 83 '../src/playlist-loader.js',
91 '../src/decrypter.js', 84 '../src/decrypter.js',
92 '../src/mp4-generator.js',
93 '../src/transmuxer.js',
94 '../tmp/manifests.js', 85 '../tmp/manifests.js',
95 '../tmp/expected.js', 86 '../tmp/expected.js',
96 'tsSegment-bc.js', 87 'tsSegment-bc.js',
97 '../src/bin-utils.js', 88 '../src/bin-utils.js',
98 '../test/muxer/js/mp4-inspector.js',
99 '../test/*.js', 89 '../test/*.js',
100 ], 90 ],
101 91
......
...@@ -42,25 +42,15 @@ module.exports = function(config) { ...@@ -42,25 +42,15 @@ module.exports = function(config) {
42 '../test/karma-qunit-shim.js', 42 '../test/karma-qunit-shim.js',
43 '../src/videojs-hls.js', 43 '../src/videojs-hls.js',
44 '../src/stream.js', 44 '../src/stream.js',
45 '../src/flv-tag.js',
46 '../src/exp-golomb.js',
47 '../src/h264-extradata.js',
48 '../src/h264-stream.js',
49 '../src/aac-stream.js',
50 '../src/metadata-stream.js',
51 '../src/segment-parser.js',
52 '../src/m3u8/m3u8-parser.js', 45 '../src/m3u8/m3u8-parser.js',
53 '../src/xhr.js', 46 '../src/xhr.js',
54 '../src/playlist.js', 47 '../src/playlist.js',
55 '../src/playlist-loader.js', 48 '../src/playlist-loader.js',
56 '../src/decrypter.js', 49 '../src/decrypter.js',
57 '../src/mp4-generator.js',
58 '../src/transmuxer.js',
59 '../tmp/manifests.js', 50 '../tmp/manifests.js',
60 '../tmp/expected.js', 51 '../tmp/expected.js',
61 'tsSegment-bc.js', 52 'tsSegment-bc.js',
62 '../src/bin-utils.js', 53 '../src/bin-utils.js',
63 '../test/muxer/js/mp4-inspector.js',
64 '../test/*.js', 54 '../test/*.js',
65 ], 55 ],
66 56
......
1 (function(window, videojs, undefined) {
2 'use strict';
3 /*
4 ======== A Handy Little QUnit Reference ========
5 http://api.qunitjs.com/
6
7 Test methods:
8 module(name, {[setup][ ,teardown]})
9 test(name, callback)
10 expect(numberOfAssertions)
11 stop(increment)
12 start(decrement)
13 Test assertions:
14 ok(value, [message])
15 equal(actual, expected, [message])
16 notEqual(actual, expected, [message])
17 deepEqual(actual, expected, [message])
18 notDeepEqual(actual, expected, [message])
19 strictEqual(actual, expected, [message])
20 notStrictEqual(actual, expected, [message])
21 throws(block, [expected], [message])
22 */
23
24 var metadataStream, stringToInts, stringToCString, id3Tag, id3Frame;
25
26 module('MetadataStream', {
27 setup: function() {
28 metadataStream = new videojs.Hls.MetadataStream();
29 }
30 });
31
32 test('can construct a MetadataStream', function() {
33 ok(metadataStream, 'does not return null');
34 });
35
36 stringToInts = function(string) {
37 var result = [], i;
38 for (i = 0; i < string.length; i++) {
39 result[i] = string.charCodeAt(i);
40 }
41 return result;
42 };
43
44 stringToCString = function(string) {
45 return stringToInts(string).concat([0x00]);
46 };
47
48 id3Tag = function() {
49 var
50 frames = Array.prototype.concat.apply([], Array.prototype.slice.call(arguments)),
51 result = stringToInts('ID3').concat([
52 0x03, 0x00, // version 3.0 of ID3v2 (aka ID3v.2.3.0)
53 0x40, // flags. include an extended header
54 0x00, 0x00, 0x00, 0x00, // size. set later
55
56 // extended header
57 0x00, 0x00, 0x00, 0x06, // extended header size. no CRC
58 0x00, 0x00, // extended flags
59 0x00, 0x00, 0x00, 0x02 // size of padding
60 ], frames),
61 size;
62
63 // size is stored as a sequence of four 7-bit integers with the
64 // high bit of each byte set to zero
65 size = result.length - 10;
66 result[6] = (size >>> 21) & 0x7f;
67 result[7] = (size >>> 14) & 0x7f;
68 result[8] = (size >>> 7) & 0x7f;
69 result[9] = (size) & 0x7f;
70
71 return result;
72 };
73
74 id3Frame = function(type) {
75 var result = stringToInts(type).concat([
76 0x00, 0x00, 0x00, 0x00, // size
77 0xe0, 0x00 // flags. tag/file alter preservation, read-only
78 ]),
79 size = result.length - 10;
80
81 // append the fields of the ID3 frame
82 result = result.concat.apply(result, Array.prototype.slice.call(arguments, 1));
83
84 // set the size
85 size = result.length - 10;
86 result[4] = (size >>> 24);
87 result[5] = (size >>> 16) & 0xff;
88 result[6] = (size >>> 8) & 0xff;
89 result[7] = (size) & 0xff;
90
91 return result;
92 };
93
94 test('parses simple ID3 metadata out of PES packets', function() {
95 var
96 events = [],
97 wxxxPayload = [
98 0x00 // text encoding. ISO-8859-1
99 ].concat(stringToCString('ad tag URL'), // description
100 stringToInts('http://example.com/ad?v=1234&q=7')), // value
101 id3Bytes,
102 size;
103
104 metadataStream.on('data', function(event) {
105 events.push(event);
106 });
107
108 id3Bytes = new Uint8Array(stringToInts('ID3').concat([
109 0x03, 0x00, // version 3.0 of ID3v2 (aka ID3v.2.3.0)
110 0x40, // flags. include an extended header
111 0x00, 0x00, 0x00, 0x00, // size. set later
112
113 // extended header
114 0x00, 0x00, 0x00, 0x06, // extended header size. no CRC
115 0x00, 0x00, // extended flags
116 0x00, 0x00, 0x00, 0x02, // size of padding
117
118 // frame 0
119 // http://id3.org/id3v2.3.0#User_defined_text_information_frame
120 ], id3Frame('WXXX',
121 wxxxPayload), // value
122 // frame 1
123 // custom tag
124 id3Frame('XINF',
125 [
126 0x04, 0x03, 0x02, 0x01 // arbitrary data
127 ]), [
128 0x00, 0x00 // padding
129 ]));
130
131 // set header size field
132 size = id3Bytes.byteLength - 10;
133 id3Bytes[6] = (size >>> 21) & 0x7f;
134 id3Bytes[7] = (size >>> 14) & 0x7f;
135 id3Bytes[8] = (size >>> 7) & 0x7f;
136 id3Bytes[9] = (size) & 0x7f;
137
138 metadataStream.push({
139 trackId: 7,
140 pts: 1000,
141 dts: 1000,
142
143 // header
144 data: id3Bytes
145 });
146
147 equal(events.length, 1, 'parsed one tag');
148 equal(events[0].frames.length, 2, 'parsed two frames');
149 equal(events[0].frames[0].id, 'WXXX', 'parsed a WXXX frame');
150 deepEqual(new Uint8Array(events[0].frames[0].data),
151 new Uint8Array(wxxxPayload),
152 'attached the frame payload');
153 equal(events[0].frames[1].id, 'XINF', 'parsed a user-defined frame');
154 deepEqual(new Uint8Array(events[0].frames[1].data),
155 new Uint8Array([0x04, 0x03, 0x02, 0x01]),
156 'attached the frame payload');
157 equal(events[0].pts, 1000, 'did not modify the PTS');
158 equal(events[0].dts, 1000, 'did not modify the PTS');
159 });
160
161 test('skips non-ID3 metadata events', function() {
162 var events = [];
163 metadataStream.on('data', function(event) {
164 events.push(event);
165 });
166
167 metadataStream.push({
168 trackId: 7,
169 pts: 1000,
170 dts: 1000,
171
172 // header
173 data: new Uint8Array([0])
174 });
175
176 equal(events.length, 0, 'did not emit an event');
177 });
178
179 // missing cases:
180 // unsynchronization
181 // CRC
182 // no extended header
183 // compressed frames
184 // encrypted frames
185 // frame groups
186 // too large/small tag size values
187 // too large/small frame size values
188
189 test('parses TXXX frames', function() {
190 var events = [];
191 metadataStream.on('data', function(event) {
192 events.push(event);
193 });
194
195 metadataStream.push({
196 trackId: 7,
197 pts: 1000,
198 dts: 900,
199
200 // header
201 data: new Uint8Array(id3Tag(id3Frame('TXXX',
202 0x03, // utf-8
203 stringToCString('get done'),
204 stringToCString('{ "key": "value" }')),
205 [0x00, 0x00]))
206 });
207
208 equal(events.length, 1, 'parsed one tag');
209 equal(events[0].frames.length, 1, 'parsed one frame');
210 equal(events[0].frames[0].id, 'TXXX', 'parsed the frame id');
211 equal(events[0].frames[0].description, 'get done', 'parsed the description');
212 deepEqual(JSON.parse(events[0].frames[0].value), { key: 'value' }, 'parsed the value');
213 });
214
215 test('parses WXXX frames', function() {
216 var events = [], url = 'http://example.com/path/file?abc=7&d=4#ty';
217 metadataStream.on('data', function(event) {
218 events.push(event);
219 });
220
221 metadataStream.push({
222 trackId: 7,
223 pts: 1000,
224 dts: 900,
225
226 // header
227 data: new Uint8Array(id3Tag(id3Frame('WXXX',
228 0x03, // utf-8
229 stringToCString(''),
230 stringToInts(url)),
231 [0x00, 0x00]))
232 });
233
234 equal(events.length, 1, 'parsed one tag');
235 equal(events[0].frames.length, 1, 'parsed one frame');
236 equal(events[0].frames[0].id, 'WXXX', 'parsed the frame id');
237 equal(events[0].frames[0].description, '', 'parsed the description');
238 equal(events[0].frames[0].url, url, 'parsed the value');
239 });
240
241 test('parses TXXX frames with characters that have a single-digit hexadecimal representation', function() {
242 var events = [], value = String.fromCharCode(7);
243 metadataStream.on('data', function(event) {
244 events.push(event);
245 });
246
247 metadataStream.push({
248 trackId: 7,
249 pts: 1000,
250 dts: 900,
251
252 // header
253 data: new Uint8Array(id3Tag(id3Frame('TXXX',
254 0x03, // utf-8
255 stringToCString(''),
256 stringToCString(value)),
257 [0x00, 0x00]))
258 });
259
260 equal(events[0].frames[0].value,
261 value,
262 'parsed the single-digit character');
263 });
264
265 test('parses PRIV frames', function() {
266 var
267 events = [],
268 payload = stringToInts('arbitrary data may be included in the payload ' +
269 'of a PRIV frame');
270
271 metadataStream.on('data', function(event) {
272 events.push(event);
273 });
274
275 metadataStream.push({
276 trackId: 7,
277 pts: 1000,
278 dts: 900,
279
280 // header
281 data: new Uint8Array(id3Tag(id3Frame('PRIV',
282 stringToCString('priv-owner@example.com'),
283 payload)))
284 });
285
286 equal(events.length, 1, 'parsed a tag');
287 equal(events[0].frames.length, 1, 'parsed a frame');
288 equal(events[0].frames[0].id, 'PRIV', 'frame id is PRIV');
289 equal(events[0].frames[0].owner, 'priv-owner@example.com', 'parsed the owner');
290 deepEqual(new Uint8Array(events[0].frames[0].privateData),
291 new Uint8Array(payload),
292 'parsed the frame private data');
293
294 });
295
296 test('parses tags split across pushes', function() {
297 var
298 events = [],
299 owner = stringToCString('owner@example.com'),
300 payload = stringToInts('A TS packet is 188 bytes in length so that it can' +
301 ' be easily transmitted over ATM networks, an ' +
302 'important medium at one time. We want to be sure' +
303 ' that ID3 frames larger than a TS packet are ' +
304 'properly re-assembled.'),
305 tag = new Uint8Array(id3Tag(id3Frame('PRIV', owner, payload))),
306 front = tag.subarray(0, 100),
307 back = tag.subarray(100);
308
309 metadataStream.on('data', function(event) {
310 events.push(event);
311 });
312
313 metadataStream.push({
314 trackId: 7,
315 pts: 1000,
316 dts: 900,
317 data: front
318 });
319
320 equal(events.length, 0, 'parsed zero tags');
321
322 metadataStream.push({
323 trackId: 7,
324 pts: 1000,
325 dts: 900,
326 data: back
327 });
328
329 equal(events.length, 1, 'parsed a tag');
330 equal(events[0].frames.length, 1, 'parsed a frame');
331 equal(events[0].frames[0].data.byteLength,
332 owner.length + payload.length,
333 'collected data across pushes');
334
335 // parses subsequent fragmented tags
336 tag = new Uint8Array(id3Tag(id3Frame('PRIV',
337 owner, payload, payload)));
338 front = tag.subarray(0, 188);
339 back = tag.subarray(188);
340 metadataStream.push({
341 trackId: 7,
342 pts: 2000,
343 dts: 2000,
344 data: front
345 });
346 metadataStream.push({
347 trackId: 7,
348 pts: 2000,
349 dts: 2000,
350 data: back
351 });
352 equal(events.length, 2, 'parsed a subseqent frame');
353 });
354
355 test('ignores tags when the header is fragmented', function() {
356
357 var
358 events = [],
359 tag = new Uint8Array(id3Tag(id3Frame('PRIV',
360 stringToCString('owner@example.com'),
361 stringToInts('payload')))),
362 // split the 10-byte ID3 tag header in half
363 front = tag.subarray(0, 5),
364 back = tag.subarray(5);
365
366 metadataStream.on('data', function(event) {
367 events.push(event);
368 });
369
370 metadataStream.push({
371 trackId: 7,
372 pts: 1000,
373 dts: 900,
374 data: front
375 });
376 metadataStream.push({
377 trackId: 7,
378 pts: 1000,
379 dts: 900,
380 data: back
381 });
382
383 equal(events.length, 0, 'parsed zero tags');
384
385 metadataStream.push({
386 trackId: 7,
387 pts: 1500,
388 dts: 1500,
389 data: new Uint8Array(id3Tag(id3Frame('PRIV',
390 stringToCString('owner2'),
391 stringToInts('payload2'))))
392 });
393 equal(events.length, 1, 'parsed one tag');
394 equal(events[0].frames[0].owner, 'owner2', 'dropped the first tag');
395 });
396
397 // https://html.spec.whatwg.org/multipage/embedded-content.html#steps-to-expose-a-media-resource-specific-text-track
398 test('constructs the dispatch type', function() {
399 metadataStream = new videojs.Hls.MetadataStream({
400 descriptor: new Uint8Array([0x03, 0x02, 0x01, 0x00])
401 });
402
403 equal(metadataStream.dispatchType, '1503020100', 'built the dispatch type');
404 });
405
406 })(window, window.videojs);
1 (function(window, videojs) {
2 'use strict';
3 /*
4 ======== A Handy Little QUnit Reference ========
5 http://api.qunitjs.com/
6
7 Test methods:
8 module(name, {[setup][ ,teardown]})
9 test(name, callback)
10 expect(numberOfAssertions)
11 stop(increment)
12 start(decrement)
13 Test assertions:
14 ok(value, [message])
15 equal(actual, expected, [message])
16 notEqual(actual, expected, [message])
17 deepEqual(actual, expected, [message])
18 notDeepEqual(actual, expected, [message])
19 strictEqual(actual, expected, [message])
20 notStrictEqual(actual, expected, [message])
21 throws(block, [expected], [message])
22 */
23 var
24 mp4 = videojs.mp4,
25 inspectMp4 = videojs.inspectMp4,
26 validateMvhd, validateTrak, validateTkhd, validateMdia,
27 validateMdhd, validateHdlr, validateMinf, validateDinf,
28 validateStbl, validateStsd, validateMvex,
29 validateVideoSample, validateAudioSample;
30
31 module('MP4 Generator');
32
33 test('generates a BSMFF ftyp', function() {
34 var data = mp4.ftyp(), boxes;
35
36 ok(data, 'box is not null');
37
38 boxes = inspectMp4(data);
39 equal(1, boxes.length, 'generated a single box');
40 equal(boxes[0].type, 'ftyp', 'generated ftyp type');
41 equal(boxes[0].size, data.byteLength, 'generated size');
42 equal(boxes[0].majorBrand, 'isom', 'major version is "isom"');
43 equal(boxes[0].minorVersion, 1, 'minor version is one');
44 });
45
46 validateMvhd = function(mvhd) {
47 equal(mvhd.type, 'mvhd', 'generated a mvhd');
48 equal(mvhd.duration, 0xffffffff, 'wrote the maximum movie header duration');
49 equal(mvhd.nextTrackId, 0xffffffff, 'wrote the max next track id');
50 };
51
52 validateTrak = function(trak, expected) {
53 expected = expected || {};
54 equal(trak.type, 'trak', 'generated a trak');
55 equal(trak.boxes.length, 2, 'generated two track sub boxes');
56
57 validateTkhd(trak.boxes[0], expected);
58 validateMdia(trak.boxes[1], expected);
59 };
60
61 validateTkhd = function(tkhd, expected) {
62 equal(tkhd.type, 'tkhd', 'generated a tkhd');
63 equal(tkhd.trackId, 7, 'wrote the track id');
64 deepEqual(tkhd.flags, new Uint8Array([0, 0, 7]), 'flags should equal 7');
65 equal(tkhd.duration,
66 expected.duration || Math.pow(2, 32) - 1,
67 'wrote duration into the track header');
68 equal(tkhd.width, expected.width || 0, 'wrote width into the track header');
69 equal(tkhd.height, expected.height || 0, 'wrote height into the track header');
70 equal(tkhd.volume, 1, 'set volume to 1');
71 };
72
73 validateMdia = function(mdia, expected) {
74 equal(mdia.type, 'mdia', 'generated an mdia type');
75 equal(mdia.boxes.length, 3, 'generated three track media sub boxes');
76
77 validateMdhd(mdia.boxes[0], expected);
78 validateHdlr(mdia.boxes[1], expected);
79 validateMinf(mdia.boxes[2], expected);
80 };
81
82 validateMdhd = function(mdhd, expected) {
83 equal(mdhd.type, 'mdhd', 'generate an mdhd type');
84 equal(mdhd.language, 'und', 'wrote undetermined language');
85 equal(mdhd.timescale, expected.timescale || 90000, 'wrote the timescale');
86 equal(mdhd.duration,
87 expected.duration || Math.pow(2, 32) - 1,
88 'wrote duration into the media header');
89 };
90
91 validateHdlr = function(hdlr, expected) {
92 equal(hdlr.type, 'hdlr', 'generate an hdlr type');
93 if (expected.type !== 'audio') {
94 equal(hdlr.handlerType, 'vide', 'wrote a video handler');
95 equal(hdlr.name, 'VideoHandler', 'wrote the handler name');
96 } else {
97 equal(hdlr.handlerType, 'soun', 'wrote a sound handler');
98 equal(hdlr.name, 'SoundHandler', 'wrote the sound handler name');
99 }
100 };
101
102 validateMinf = function(minf, expected) {
103 equal(minf.type, 'minf', 'generate an minf type');
104 equal(minf.boxes.length, 3, 'generates three minf sub boxes');
105
106 if (expected.type !== 'audio') {
107 deepEqual({
108 type: 'vmhd',
109 size: 20,
110 version: 0,
111 flags: new Uint8Array([0, 0, 1]),
112 graphicsmode: 0,
113 opcolor: new Uint16Array([0, 0, 0])
114 }, minf.boxes[0], 'generates a vhmd');
115 } else {
116 deepEqual({
117 type: 'smhd',
118 size: 16,
119 version: 0,
120 flags: new Uint8Array([0, 0, 0]),
121 balance: 0
122 }, minf.boxes[0], 'generates an smhd');
123 }
124
125 validateDinf(minf.boxes[1]);
126 validateStbl(minf.boxes[2], expected);
127 };
128
129 validateDinf = function(dinf) {
130 deepEqual({
131 type: 'dinf',
132 size: 36,
133 boxes: [{
134 type: 'dref',
135 size: 28,
136 version: 0,
137 flags: new Uint8Array([0, 0, 0]),
138 dataReferences: [{
139 type: 'url ',
140 size: 12,
141 version: 0,
142 flags: new Uint8Array([0, 0, 1])
143 }]
144 }]
145 }, dinf, 'generates a dinf');
146 };
147
148 validateStbl = function(stbl, expected) {
149 equal(stbl.type, 'stbl', 'generates an stbl type');
150 equal(stbl.boxes.length, 5, 'generated five stbl child boxes');
151
152 validateStsd(stbl.boxes[0], expected);
153 deepEqual({
154 type: 'stts',
155 size: 16,
156 version: 0,
157 flags: new Uint8Array([0, 0, 0]),
158 timeToSamples: []
159 }, stbl.boxes[1], 'generated an stts');
160 deepEqual({
161 type: 'stsc',
162 size: 16,
163 version: 0,
164 flags: new Uint8Array([0, 0, 0]),
165 sampleToChunks: []
166 }, stbl.boxes[2], 'generated an stsc');
167 deepEqual({
168 type: 'stsz',
169 version: 0,
170 size: 20,
171 flags: new Uint8Array([0, 0, 0]),
172 sampleSize: 0,
173 entries: []
174 }, stbl.boxes[3], 'generated an stsz');
175 deepEqual({
176 type: 'stco',
177 size: 16,
178 version: 0,
179 flags: new Uint8Array([0, 0, 0]),
180 chunkOffsets: []
181 }, stbl.boxes[4], 'generated and stco');
182 };
183
184 validateStsd = function(stsd, expected) {
185 equal(stsd.type, 'stsd', 'generated an stsd');
186 equal(stsd.sampleDescriptions.length, 1, 'generated one sample');
187 if (expected.type !== 'audio') {
188 validateVideoSample(stsd.sampleDescriptions[0]);
189 } else {
190 validateAudioSample(stsd.sampleDescriptions[0]);
191 }
192 };
193
194 validateVideoSample = function(sample) {
195 deepEqual(sample, {
196 type: 'avc1',
197 size: 136,
198 dataReferenceIndex: 1,
199 width: 600,
200 height: 300,
201 horizresolution: 72,
202 vertresolution: 72,
203 frameCount: 1,
204 depth: 24,
205 config: [{
206 type: 'avcC',
207 size: 30,
208 configurationVersion: 1,
209 avcProfileIndication: 3,
210 avcLevelIndication: 5,
211 profileCompatibility: 7,
212 lengthSizeMinusOne: 3,
213 sps: [new Uint8Array([
214 0, 1, 2
215 ]), new Uint8Array([
216 3, 4, 5
217 ])],
218 pps: [new Uint8Array([
219 6, 7, 8
220 ])]
221 }, {
222 type: 'btrt',
223 size: 20,
224 bufferSizeDB: 1875072,
225 maxBitrate: 3000000,
226 avgBitrate: 3000000
227 }]
228 }, 'generated a video sample');
229 };
230
231 validateAudioSample = function(sample) {
232 deepEqual(sample, {
233 type: 'mp4a',
234 size: 75,
235 dataReferenceIndex: 1,
236 channelcount: 2,
237 samplesize: 16,
238 samplerate: 48000,
239 streamDescriptor: {
240 type: 'esds',
241 version: 0,
242 flags: new Uint8Array([0, 0, 0]),
243 size: 39,
244 esId: 0,
245 streamPriority: 0,
246 // these values were hard-coded based on a working audio init segment
247 decoderConfig: {
248 avgBitrate: 56000,
249 maxBitrate: 56000,
250 bufferSize: 1536,
251 objectProfileIndication: 64,
252 streamType: 5
253 }
254 }
255 }, 'generated an audio sample');
256 };
257
258 validateMvex = function(mvex, options) {
259 options = options || {
260 sampleDegradationPriority: 1
261 };
262 deepEqual({
263 type: 'mvex',
264 size: 40,
265 boxes: [{
266 type: 'trex',
267 size: 32,
268 version: 0,
269 flags: new Uint8Array([0, 0, 0]),
270 trackId: 7,
271 defaultSampleDescriptionIndex: 1,
272 defaultSampleDuration: 0,
273 defaultSampleSize: 0,
274 sampleDependsOn: 0,
275 sampleIsDependedOn: 0,
276 sampleHasRedundancy: 0,
277 samplePaddingValue: 0,
278 sampleIsDifferenceSample: true,
279 sampleDegradationPriority: options.sampleDegradationPriority
280 }]
281 }, mvex, 'writes a movie extends box');
282 };
283
284 test('generates a video moov', function() {
285 var
286 boxes,
287 data = mp4.moov([{
288 id: 7,
289 duration: 100,
290 width: 600,
291 height: 300,
292 type: 'video',
293 profileIdc: 3,
294 levelIdc: 5,
295 profileCompatibility: 7,
296 sps: [new Uint8Array([0, 1, 2]), new Uint8Array([3, 4, 5])],
297 pps: [new Uint8Array([6, 7, 8])]
298 }]);
299
300 ok(data, 'box is not null');
301 boxes = inspectMp4(data);
302 equal(boxes.length, 1, 'generated a single box');
303 equal(boxes[0].type, 'moov', 'generated a moov type');
304 equal(boxes[0].size, data.byteLength, 'generated size');
305 equal(boxes[0].boxes.length, 3, 'generated three sub boxes');
306
307 validateMvhd(boxes[0].boxes[0]);
308 validateTrak(boxes[0].boxes[1], {
309 duration: 100,
310 width: 600,
311 height: 300
312 });
313 validateMvex(boxes[0].boxes[2]);
314 });
315
316 test('generates an audio moov', function() {
317 var
318 data = mp4.moov([{
319 id: 7,
320 type: 'audio',
321 channelcount: 2,
322 samplerate: 48000,
323 samplesize: 16
324 }]),
325 boxes;
326
327 ok(data, 'box is not null');
328 boxes = inspectMp4(data);
329 equal(boxes.length, 1, 'generated a single box');
330 equal(boxes[0].type, 'moov', 'generated a moov type');
331 equal(boxes[0].size, data.byteLength, 'generated size');
332 equal(boxes[0].boxes.length, 3, 'generated three sub boxes');
333
334 validateMvhd(boxes[0].boxes[0]);
335 validateTrak(boxes[0].boxes[1], {
336 type: 'audio',
337 timescale: 48000
338 });
339 validateMvex(boxes[0].boxes[2], {
340 sampleDegradationPriority: 0
341 });
342 });
343
344 test('generates a sound hdlr', function() {
345 var boxes, hdlr,
346 data = mp4.moov([{
347 duration:100,
348 type: 'audio'
349 }]);
350
351 ok(data, 'box is not null');
352
353 boxes = inspectMp4(data);
354
355 hdlr = boxes[0].boxes[1].boxes[1].boxes[1];
356 equal(hdlr.type, 'hdlr', 'generate an hdlr type');
357 equal(hdlr.handlerType, 'soun', 'wrote a sound handler');
358 equal(hdlr.name, 'SoundHandler', 'wrote the handler name');
359 });
360
361 test('generates a video hdlr', function() {
362 var boxes, hdlr,
363 data = mp4.moov([{
364 duration: 100,
365 width: 600,
366 height: 300,
367 type: 'video',
368 sps: [],
369 pps: []
370 }]);
371
372 ok(data, 'box is not null');
373
374 boxes = inspectMp4(data);
375
376 hdlr = boxes[0].boxes[1].boxes[1].boxes[1];
377 equal(hdlr.type, 'hdlr', 'generate an hdlr type');
378 equal(hdlr.handlerType, 'vide', 'wrote a video handler');
379 equal(hdlr.name, 'VideoHandler', 'wrote the handler name');
380 });
381
382 test('generates an initialization segment', function() {
383 var
384 data = mp4.initSegment([{
385 id: 1,
386 width: 600,
387 height: 300,
388 type: 'video',
389 sps: [new Uint8Array([0])],
390 pps: [new Uint8Array([1])]
391 }, {
392 id: 2,
393 type: 'audio'
394 }]),
395 init, mvhd, trak1, trak2, mvex;
396
397 init = videojs.inspectMp4(data);
398 equal(init.length, 2, 'generated two boxes');
399 equal(init[0].type, 'ftyp', 'generated a ftyp box');
400 equal(init[1].type, 'moov', 'generated a moov box');
401 equal(init[1].boxes[0].duration, 0xffffffff, 'wrote a maximum duration');
402
403 mvhd = init[1].boxes[0];
404 equal(mvhd.type, 'mvhd', 'wrote an mvhd');
405
406 trak1 = init[1].boxes[1];
407 equal(trak1.type, 'trak', 'wrote a trak');
408 equal(trak1.boxes[0].trackId, 1, 'wrote the first track id');
409 equal(trak1.boxes[0].width, 600, 'wrote the first track width');
410 equal(trak1.boxes[0].height, 300, 'wrote the first track height');
411 equal(trak1.boxes[1].boxes[1].handlerType, 'vide', 'wrote the first track type');
412
413 trak2 = init[1].boxes[2];
414 equal(trak2.type, 'trak', 'wrote a trak');
415 equal(trak2.boxes[0].trackId, 2, 'wrote the second track id');
416 equal(trak2.boxes[1].boxes[1].handlerType, 'soun', 'wrote the second track type');
417
418 mvex = init[1].boxes[3];
419 equal(mvex.type, 'mvex', 'wrote an mvex');
420 });
421
422 test('generates a minimal moof', function() {
423 var
424 data = mp4.moof(7, [{
425 id: 17,
426 samples: [{
427 duration: 9000,
428 size: 10,
429 flags: {
430 isLeading: 0,
431 dependsOn: 2,
432 isDependedOn: 1,
433 hasRedundancy: 0,
434 paddingValue: 0,
435 isNonSyncSample: 0,
436 degradationPriority: 14
437 },
438 compositionTimeOffset: 500
439 }, {
440 duration: 10000,
441 size: 11,
442 flags: {
443 isLeading: 0,
444 dependsOn: 1,
445 isDependedOn: 0,
446 hasRedundancy: 0,
447 paddingValue: 0,
448 isNonSyncSample: 0,
449 degradationPriority: 9
450 },
451 compositionTimeOffset: 1000
452 }]
453 }]),
454 moof = videojs.inspectMp4(data),
455 trun,
456 sdtp;
457
458 equal(moof.length, 1, 'generated one box');
459 equal(moof[0].type, 'moof', 'generated a moof box');
460 equal(moof[0].boxes.length, 2, 'generated two child boxes');
461 equal(moof[0].boxes[0].type, 'mfhd', 'generated an mfhd box');
462 equal(moof[0].boxes[0].sequenceNumber, 7, 'included the sequence_number');
463 equal(moof[0].boxes[1].type, 'traf', 'generated a traf box');
464 equal(moof[0].boxes[1].boxes.length, 4, 'generated track fragment info');
465 equal(moof[0].boxes[1].boxes[0].type, 'tfhd', 'generated a tfhd box');
466 equal(moof[0].boxes[1].boxes[0].trackId, 17, 'wrote the first track id');
467 equal(moof[0].boxes[1].boxes[0].baseDataOffset, undefined, 'did not set a base data offset');
468
469 equal(moof[0].boxes[1].boxes[1].type, 'tfdt', 'generated a tfdt box');
470 ok(moof[0].boxes[1].boxes[1].baseMediaDecodeTime >= 0,
471 'media decode time is non-negative');
472
473 trun = moof[0].boxes[1].boxes[2];
474 equal(trun.type, 'trun', 'generated a trun box');
475 equal(typeof trun.dataOffset, 'number', 'has a data offset');
476 ok(trun.dataOffset >= 0, 'has a non-negative data offset');
477 equal(trun.dataOffset, moof[0].size + 8, 'sets the data offset past the mdat header');
478 equal(trun.samples.length, 2, 'wrote two samples');
479
480 equal(trun.samples[0].duration, 9000, 'wrote a sample duration');
481 equal(trun.samples[0].size, 10, 'wrote a sample size');
482 deepEqual(trun.samples[0].flags, {
483 isLeading: 0,
484 dependsOn: 2,
485 isDependedOn: 1,
486 hasRedundancy: 0,
487 paddingValue: 0,
488 isNonSyncSample: 0,
489 degradationPriority: 14
490 }, 'wrote the sample flags');
491 equal(trun.samples[0].compositionTimeOffset, 500, 'wrote the composition time offset');
492
493 equal(trun.samples[1].duration, 10000, 'wrote a sample duration');
494 equal(trun.samples[1].size, 11, 'wrote a sample size');
495 deepEqual(trun.samples[1].flags, {
496 isLeading: 0,
497 dependsOn: 1,
498 isDependedOn: 0,
499 hasRedundancy: 0,
500 paddingValue: 0,
501 isNonSyncSample: 0,
502 degradationPriority: 9
503 }, 'wrote the sample flags');
504 equal(trun.samples[1].compositionTimeOffset, 1000, 'wrote the composition time offset');
505
506 sdtp = moof[0].boxes[1].boxes[3];
507 equal(sdtp.type, 'sdtp', 'generated an sdtp box');
508 equal(sdtp.samples.length, 2, 'wrote two samples');
509 deepEqual(sdtp.samples[0], {
510 dependsOn: 2,
511 isDependedOn: 1,
512 hasRedundancy: 0
513 }, 'wrote the sample data table');
514 deepEqual(sdtp.samples[1], {
515 dependsOn: 1,
516 isDependedOn: 0,
517 hasRedundancy: 0
518 }, 'wrote the sample data table');
519 });
520
521 test('generates a moof for audio', function() {
522 var
523 data = mp4.moof(7, [{
524 id: 17,
525 type: 'audio',
526 samples: [{
527 duration: 9000,
528 size: 10
529 }, {
530 duration: 10000,
531 size: 11
532 }]
533 }]),
534 moof = videojs.inspectMp4(data),
535 trun;
536
537 deepEqual(moof[0].boxes[1].boxes.length, 3, 'generated three traf children');
538 trun = moof[0].boxes[1].boxes[2];
539 ok(trun, 'generated a trun');
540 equal(trun.dataOffset, data.byteLength + 8, 'calculated the data offset');
541 deepEqual(trun.samples, [{
542 duration: 9000,
543 size: 10
544 }, {
545 duration: 10000,
546 size: 11
547 }], 'wrote simple audio samples');
548 });
549
550 test('can generate a traf without samples', function() {
551 var
552 data = mp4.moof(8, [{
553 trackId: 13
554 }]),
555 moof = videojs.inspectMp4(data);
556
557 equal(moof[0].boxes[1].boxes[2].samples.length, 0, 'generated no samples');
558 });
559
560 test('generates an mdat', function() {
561 var
562 data = mp4.mdat(new Uint8Array([1, 2, 3, 4])),
563 mdat = videojs.inspectMp4(data);
564
565 equal(mdat.length, 1, 'generated one box');
566 equal(mdat[0].type, 'mdat', 'generated an mdat box');
567 deepEqual(mdat[0].byteLength, 4, 'encapsulated the data');
568 });
569
570 })(window, window.videojs);
1 (function(window, videojs) {
2 'use strict';
3 /*
4 ======== A Handy Little QUnit Reference ========
5 http://api.qunitjs.com/
6
7 Test methods:
8 module(name, {[setup][ ,teardown]})
9 test(name, callback)
10 expect(numberOfAssertions)
11 stop(increment)
12 start(decrement)
13 Test assertions:
14 ok(value, [message])
15 equal(actual, expected, [message])
16 notEqual(actual, expected, [message])
17 deepEqual(actual, expected, [message])
18 notDeepEqual(actual, expected, [message])
19 strictEqual(actual, expected, [message])
20 notStrictEqual(actual, expected, [message])
21 throws(block, [expected], [message])
22 */
23 var
24 Uint8Array = window.Uint8Array,
25 typeBytes = function(type) {
26 return [
27 type.charCodeAt(0),
28 type.charCodeAt(1),
29 type.charCodeAt(2),
30 type.charCodeAt(3)
31 ];
32 },
33 box = function(type) {
34 var
35 array = Array.prototype.slice.call(arguments, 1),
36 result = [],
37 size,
38 i;
39
40 // "unwrap" any arrays that were passed as arguments
41 // e.g. box('etc', 1, [2, 3], 4) -> box('etc', 1, 2, 3, 4)
42 for (i = 0; i < array.length; i++) {
43 if (array[i] instanceof Array) {
44 array.splice.apply(array, [i, 1].concat(array[i]));
45 }
46 }
47
48 size = 8 + array.length;
49
50 result[0] = (size & 0xFF000000) >> 24;
51 result[1] = (size & 0x00FF0000) >> 16;
52 result[2] = (size & 0x0000FF00) >> 8;
53 result[3] = size & 0xFF;
54 result = result.concat(typeBytes(type));
55 result = result.concat(array);
56 return result;
57 },
58 unityMatrix = [
59 0, 0, 0x10, 0,
60 0, 0, 0, 0,
61 0, 0, 0, 0,
62
63 0, 0, 0, 0,
64 0, 0, 0x10, 0,
65 0, 0, 0, 0,
66
67 0, 0, 0, 0,
68 0, 0, 0, 0,
69 0x40, 0, 0, 0
70 ],
71
72 mvhd0 = box('mvhd',
73 0x00, // version 0
74 0x00, 0x00, 0x00, // flags
75 0x00, 0x00, 0x00, 0x01, // creation_time
76 0x00, 0x00, 0x00, 0x02, // modification_time
77 0x00, 0x00, 0x00, 0x3c, // timescale
78 0x00, 0x00, 0x02, 0x58, // 600 = 0x258 duration
79 0x00, 0x01, 0x00, 0x00, // 1.0 rate
80 0x01, 0x00, // 1.0 volume
81 0x00, 0x00, // reserved
82 0x00, 0x00, 0x00, 0x00, // reserved
83 0x00, 0x00, 0x00, 0x00, // reserved
84 unityMatrix,
85 0x00, 0x00, 0x00, 0x00,
86 0x00, 0x00, 0x00, 0x00,
87 0x00, 0x00, 0x00, 0x00,
88 0x00, 0x00, 0x00, 0x00,
89 0x00, 0x00, 0x00, 0x00,
90 0x00, 0x00, 0x00, 0x00, // pre_defined
91 0x00, 0x00, 0x00, 0x02),
92
93 tkhd0 = box('tkhd',
94 0x00, // version 0
95 0x00, 0x00, 0x00, // flags
96 0x00, 0x00, 0x00, 0x02, // creation_time
97 0x00, 0x00, 0x00, 0x03, // modification_time
98 0x00, 0x00, 0x00, 0x01, // track_ID
99 0x00, 0x00, 0x00, 0x00, // reserved
100 0x00, 0x00, 0x02, 0x58, // 600 = 0x258 duration
101 0x00, 0x00, 0x00, 0x00,
102 0x00, 0x00, 0x00, 0x00, // reserved
103 0x00, 0x00, // layer
104 0x00, 0x00, // alternate_group
105 0x00, 0x00, // non-audio track volume
106 0x00, 0x00, // reserved
107 unityMatrix,
108 0x01, 0x2c, 0x00, 0x00, // 300 in 16.16 fixed point
109 0x00, 0x96, 0x00, 0x00), // 150 in 16.16 fixed point
110 mdhd0 = box('mdhd',
111 0x00, // version 0
112 0x00, 0x00, 0x00, // flags
113 0x00, 0x00, 0x00, 0x02, // creation_time
114 0x00, 0x00, 0x00, 0x03, // modification_time
115 0x00, 0x00, 0x00, 0x3c, // timescale
116 0x00, 0x00, 0x02, 0x58, // 600 = 0x258 duration
117 0x15, 0xc7, // 'eng' language
118 0x00, 0x00);
119
120 module('MP4 Inspector');
121
122 test('produces an empty array for empty input', function() {
123 strictEqual(videojs.inspectMp4(new Uint8Array([])).length, 0, 'returned an empty array');
124 });
125
126 test('can parse a Box', function() {
127 var box = new Uint8Array([
128 0x00, 0x00, 0x00, 0x00, // size 0
129 0x00, 0x00, 0x00, 0x00 // boxtype 0
130 ]);
131 deepEqual(videojs.inspectMp4(box), [{
132 type: '\u0000\u0000\u0000\u0000',
133 size: 0,
134 data: box.subarray(box.byteLength)
135 }], 'parsed a Box');
136 });
137
138 test('can parse an ftyp', function() {
139 deepEqual(videojs.inspectMp4(new Uint8Array(box('ftyp',
140 0x61, 0x76, 0x63, 0x31, // major brand
141 0x00, 0x00, 0x00, 0x02, // minor version
142 98, 111, 111, 112, // compatible brands
143 98, 101, 101, 112 // compatible brands
144 ))), [{
145 type: 'ftyp',
146 size: 4 * 6,
147 majorBrand: 'avc1',
148 minorVersion: 2,
149 compatibleBrands: ['boop', 'beep']
150 }], 'parsed an ftyp');
151 });
152
153 test('can parse a pdin', function() {
154 deepEqual(videojs.inspectMp4(new Uint8Array(box('pdin',
155 0x01, // version 1
156 0x01, 0x02, 0x03, // flags
157 0x00, 0x00, 0x04, 0x00, // 1024 = 0x400 bytes/second rate
158 0x00, 0x00, 0x00, 0x01 // initial delay
159 ))), [{
160 size: 20,
161 type: 'pdin',
162 version: 1,
163 flags: new Uint8Array([1, 2, 3]),
164 rate: 1024,
165 initialDelay: 1
166 }], 'parsed a pdin');
167 });
168
169 test('can parse an mdat', function() {
170 var mdat = new Uint8Array(box('mdat',
171 0, 0, 0, 4, // length
172 0x01, 0x02, 0x03, 0x04 // data
173 ));
174 deepEqual(videojs.inspectMp4(mdat), [{
175 size: 16,
176 type: 'mdat',
177 nals: [
178 'slice_layer_without_partitioning_rbsp'
179 ],
180 byteLength: 8
181 }], 'parsed an mdat');
182 });
183
184 test('can parse a free or skip', function() {
185 var
186 free = new Uint8Array(box('free',
187 0x01, 0x02, 0x03, 0x04)), // data
188 skip = new Uint8Array(box('skip',
189 0x01, 0x02, 0x03, 0x04)); // data
190
191 deepEqual(videojs.inspectMp4(free), [{
192 size: 12,
193 type: 'free',
194 data: free.subarray(free.byteLength - 4)
195 }], 'parsed a free');
196 deepEqual(videojs.inspectMp4(skip), [{
197 size: 12,
198 type: 'skip',
199 data: skip.subarray(skip.byteLength - 4)
200 }], 'parsed a skip');
201 });
202
203 test('can parse a version 0 mvhd', function() {
204 deepEqual(videojs.inspectMp4(new Uint8Array(mvhd0)), [{
205 type: 'mvhd',
206 version: 0,
207 flags: new Uint8Array([0, 0, 0]),
208 creationTime: new Date(1000 - 2082844800000),
209 modificationTime: new Date(2000 - 2082844800000),
210 timescale: 60,
211 duration: 600,
212 rate: 1,
213 volume: 1,
214 matrix: new Uint32Array(unityMatrix),
215 size: 108,
216 nextTrackId: 2
217 }]);
218 });
219
220 test('can parse a version 0 tkhd', function() {
221 deepEqual(videojs.inspectMp4(new Uint8Array(tkhd0)), [{
222 type: 'tkhd',
223 version: 0,
224 flags: new Uint8Array([0, 0, 0]),
225 creationTime: new Date(2000 - 2082844800000),
226 modificationTime: new Date(3000 - 2082844800000),
227 size: 92,
228 trackId: 1,
229 duration: 600,
230 layer: 0,
231 alternateGroup: 0,
232 volume: 0,
233 matrix: new Uint32Array(unityMatrix),
234 width: 300,
235 height: 150
236 }]);
237 });
238
239 test('can parse a version 0 mdhd', function() {
240 deepEqual(videojs.inspectMp4(new Uint8Array(mdhd0)), [{
241 type: 'mdhd',
242 version: 0,
243 flags: new Uint8Array([0, 0, 0]),
244 creationTime: new Date(2000 - 2082844800000),
245 modificationTime: new Date(3000 - 2082844800000),
246 size: 32,
247 timescale: 60,
248 duration: 600,
249 language: 'eng'
250 }]);
251 });
252
253 test('can parse a moov', function() {
254 var data =
255 box('moov',
256 box('mvhd',
257 0x01, // version 1
258 0x00, 0x00, 0x00, // flags
259 0x00, 0x00, 0x00, 0x00,
260 0x00, 0x00, 0x00, 0x01, // creation_time
261 0x00, 0x00, 0x00, 0x00,
262 0x00, 0x00, 0x00, 0x02, // modification_time
263 0x00, 0x00, 0x00, 0x3c, // timescale
264 0x00, 0x00, 0x00, 0x00,
265 0x00, 0x00, 0x02, 0x58, // 600 = 0x258 duration
266 0x00, 0x01, 0x00, 0x00, // 1.0 rate
267 0x01, 0x00, // 1.0 volume
268 0x00, 0x00, // reserved
269 0x00, 0x00, 0x00, 0x00, // reserved
270 0x00, 0x00, 0x00, 0x00, // reserved
271 unityMatrix,
272 0x00, 0x00, 0x00, 0x00,
273 0x00, 0x00, 0x00, 0x00,
274 0x00, 0x00, 0x00, 0x00,
275 0x00, 0x00, 0x00, 0x00,
276 0x00, 0x00, 0x00, 0x00,
277 0x00, 0x00, 0x00, 0x00, // pre_defined
278 0x00, 0x00, 0x00, 0x02), // next_track_ID
279 box('trak',
280 box('tkhd',
281 0x01, // version 1
282 0x00, 0x00, 0x00, // flags
283 0x00, 0x00, 0x00, 0x00,
284 0x00, 0x00, 0x00, 0x02, // creation_time
285 0x00, 0x00, 0x00, 0x00,
286 0x00, 0x00, 0x00, 0x03, // modification_time
287 0x00, 0x00, 0x00, 0x01, // track_ID
288 0x00, 0x00, 0x00, 0x00, // reserved
289 0x00, 0x00, 0x00, 0x00,
290 0x00, 0x00, 0x02, 0x58, // 600 = 0x258 duration
291 0x00, 0x00, 0x00, 0x00,
292 0x00, 0x00, 0x00, 0x00, // reserved
293 0x00, 0x00, // layer
294 0x00, 0x00, // alternate_group
295 0x00, 0x00, // non-audio track volume
296 0x00, 0x00, // reserved
297 unityMatrix,
298 0x01, 0x2c, 0x00, 0x00, // 300 in 16.16 fixed-point
299 0x00, 0x96, 0x00, 0x00), // 150 in 16.16 fixed-point
300 box('mdia',
301 box('mdhd',
302 0x01, // version 1
303 0x00, 0x00, 0x00, // flags
304 0x00, 0x00, 0x00, 0x00,
305 0x00, 0x00, 0x00, 0x02, // creation_time
306 0x00, 0x00, 0x00, 0x00,
307 0x00, 0x00, 0x00, 0x03, // modification_time
308 0x00, 0x00, 0x00, 0x3c, // timescale
309 0x00, 0x00, 0x00, 0x00,
310 0x00, 0x00, 0x02, 0x58, // 600 = 0x258 duration
311 0x15, 0xc7, // 'eng' language
312 0x00, 0x00),
313 box('hdlr',
314 0x01, // version 1
315 0x00, 0x00, 0x00, // flags
316 0x00, 0x00, 0x00, 0x00, // pre_defined
317 typeBytes('vide'), // handler_type
318 0x00, 0x00, 0x00, 0x00, // reserved
319 0x00, 0x00, 0x00, 0x00, // reserved
320 0x00, 0x00, 0x00, 0x00, // reserved
321 typeBytes('one'), 0x00), // name
322 box('minf',
323 box('dinf',
324 box('dref',
325 0x01, // version 1
326 0x00, 0x00, 0x00, // flags
327 0x00, 0x00, 0x00, 0x01, // entry_count
328 box('url ',
329 0x00, // version
330 0x00, 0x00, 0x01))), // flags
331 box('stbl',
332 box('stsd',
333 0x01, // version 1
334 0x00, 0x00, 0x00, // flags
335 0x00, 0x00, 0x00, 0x00), // entry_count
336 box('stts',
337 0x01, // version 1
338 0x00, 0x00, 0x00, // flags
339 0x00, 0x00, 0x00, 0x01, // entry_count
340 0x00, 0x00, 0x00, 0x01, // sample_count
341 0x00, 0x00, 0x00, 0x01), // sample_delta
342 box('stsc',
343 0x01, // version 1
344 0x00, 0x00, 0x00, // flags
345 0x00, 0x00, 0x00, 0x01, // entry_count
346 0x00, 0x00, 0x00, 0x02, // first_chunk
347 0x00, 0x00, 0x00, 0x03, // samples_per_chunk
348 0x00, 0x00, 0x00, 0x01), // sample_description_index
349 box('stco',
350 0x01, // version 1
351 0x00, 0x00, 0x00, // flags
352 0x00, 0x00, 0x00, 0x01, // entry_count
353 0x00, 0x00, 0x00, 0x01)))))); // chunk_offset
354
355
356 deepEqual(videojs.inspectMp4(new Uint8Array(data)), [{
357 type: 'moov',
358 size: 469,
359 boxes: [{
360 type: 'mvhd',
361 version: 1,
362 flags: new Uint8Array([0, 0, 0]),
363 creationTime: new Date(1000 - 2082844800000),
364 modificationTime: new Date(2000 - 2082844800000),
365 timescale: 60,
366 duration: 600,
367 rate: 1,
368 size: 120,
369 volume: 1,
370 matrix: new Uint32Array(unityMatrix),
371 nextTrackId: 2
372 }, {
373 type: 'trak',
374 size: 341,
375 boxes: [{
376 type: 'tkhd',
377 flags: new Uint8Array([0, 0, 0]),
378 version: 1,
379 creationTime: new Date(2000 - 2082844800000),
380 modificationTime: new Date(3000 - 2082844800000),
381 size: 104,
382 trackId: 1,
383 duration: 600,
384 layer: 0,
385 alternateGroup: 0,
386 volume: 0,
387 matrix: new Uint32Array(unityMatrix),
388 width: 300,
389 height: 150
390 }, {
391 type: 'mdia',
392 size: 229,
393 boxes: [{
394 type: 'mdhd',
395 version: 1,
396 flags: new Uint8Array([0, 0, 0]),
397 creationTime: new Date(2000 - 2082844800000),
398 modificationTime: new Date(3000 - 2082844800000),
399 timescale: 60,
400 duration: 600,
401 language: 'eng',
402 size: 44
403 }, {
404 type: 'hdlr',
405 version: 1,
406 flags: new Uint8Array([0, 0, 0]),
407 handlerType: 'vide',
408 name: 'one',
409 size: 37
410 }, {
411 type: 'minf',
412 size: 140,
413 boxes: [{
414 type: 'dinf',
415 size: 36,
416 boxes: [{
417 type: 'dref',
418 size: 28,
419 version: 1,
420 flags: new Uint8Array([0, 0, 0]),
421 dataReferences: [{
422 type: 'url ',
423 size: 12,
424 version: 0,
425 flags: new Uint8Array([0, 0, 1])
426 }],
427 }]
428 }, {
429 type: 'stbl',
430 size: 96,
431 boxes: [{
432 type: 'stsd',
433 size: 16,
434 version: 1,
435 flags: new Uint8Array([0, 0, 0]),
436 sampleDescriptions: [],
437 }, {
438 type: 'stts',
439 size: 24,
440 version: 1,
441 flags: new Uint8Array([0, 0, 0]),
442 timeToSamples: [{
443 sampleCount: 1,
444 sampleDelta: 1
445 }]
446 }, {
447 type: 'stsc',
448 version: 1,
449 flags: new Uint8Array([0, 0, 0]),
450 sampleToChunks: [{
451 firstChunk: 2,
452 samplesPerChunk: 3,
453 sampleDescriptionIndex: 1
454 }],
455 size: 28
456 }, {
457 type: 'stco',
458 size: 20,
459 version: 1,
460 flags: new Uint8Array([0, 0, 0]),
461 chunkOffsets: [1]
462 }]
463 }]
464 }]
465 }]
466 }]
467 }], 'parsed a moov');
468 });
469
470 test('can parse an mvex', function() {
471 var mvex =
472 box('mvex',
473 box('trex',
474 0x00, // version
475 0x00, 0x00, 0x00, // flags
476 0x00, 0x00, 0x00, 0x01, // track_ID
477 0x00, 0x00, 0x00, 0x01, // default_sample_description_index
478 0x00, 0x00, 0x00, 0x02, // default_sample_duration
479 0x00, 0x00, 0x00, 0x03, // default_sample_size
480 0x00, 0x61, 0x00, 0x01)); // default_sample_flags
481 deepEqual(videojs.inspectMp4(new Uint8Array(mvex)), [{
482 type: 'mvex',
483 size: 40,
484 boxes: [{
485 type: 'trex',
486 size: 32,
487 version: 0,
488 flags: new Uint8Array([0, 0, 0]),
489 trackId: 1,
490 defaultSampleDescriptionIndex: 1,
491 defaultSampleDuration: 2,
492 defaultSampleSize: 3,
493 sampleDependsOn: 0,
494 sampleIsDependedOn: 1,
495 sampleHasRedundancy: 2,
496 samplePaddingValue: 0,
497 sampleIsDifferenceSample: true,
498 sampleDegradationPriority: 1
499 }]
500 }], 'parsed an mvex');
501 });
502
503 test('can parse a video stsd', function() {
504 var data = box('stsd',
505 0x00, // version 0
506 0x00, 0x00, 0x00, // flags
507 0x00, 0x00, 0x00, 0x01,
508 box('avc1',
509 0x00, 0x00, 0x00,
510 0x00, 0x00, 0x00, // reserved
511 0x00, 0x01, // data_reference_index
512 0x00, 0x00, // pre_defined
513 0x00, 0x00, // reserved
514 0x00, 0x00, 0x00, 0x00,
515 0x00, 0x00, 0x00, 0x00,
516 0x00, 0x00, 0x00, 0x00, // pre_defined
517 0x01, 0x2c, // width = 300
518 0x00, 0x96, // height = 150
519 0x00, 0x48, 0x00, 0x00, // horizresolution
520 0x00, 0x48, 0x00, 0x00, // vertresolution
521 0x00, 0x00, 0x00, 0x00, // reserved
522 0x00, 0x01, // frame_count
523 0x04,
524 typeBytes('avc1'),
525 0x00, 0x00, 0x00, 0x00,
526 0x00, 0x00, 0x00, 0x00,
527 0x00, 0x00, 0x00, 0x00,
528 0x00, 0x00, 0x00, 0x00,
529 0x00, 0x00, 0x00, 0x00,
530 0x00, 0x00, 0x00, 0x00,
531 0x00, 0x00, 0x00, // compressorname
532 0x00, 0x18, // depth = 24
533 0x11, 0x11, // pre_defined
534 box('avcC',
535 0x01, // configurationVersion
536 0x00, // AVCProfileIndication??
537 0x00, // profile_compatibility
538 0x00, // AVCLevelIndication
539 0x1c, // lengthSizeMinusOne
540 0xe1, // numOfSequenceParameterSets
541 0x00, 0x01, // sequenceParameterSetLength
542 0x00, // "SPS"
543 0x02, // numOfPictureParameterSets
544 0x00, 0x02, // pictureParameterSetLength
545 0x01, 0x02, // "PPS"
546 0x00, 0x01, // pictureParameterSetLength
547 0xff), // "PPS"
548 box('btrt',
549 0x00, 0x00, 0x00, 0x00, // bufferSizeDB
550 0x00, 0x00, 0x00, 0x01, // maxBitrate
551 0x00, 0x00, 0x00, 0x01))); // avgBitrate
552 deepEqual(videojs.inspectMp4(new Uint8Array(data)), [{
553 type: 'stsd',
554 size: 147,
555 version: 0,
556 flags: new Uint8Array([0, 0, 0]),
557 sampleDescriptions: [{
558 type: 'avc1',
559 size: 131,
560 dataReferenceIndex: 1,
561 width: 300,
562 height: 150,
563 horizresolution: 72,
564 vertresolution: 72,
565 frameCount: 1,
566 depth: 24,
567 config: [{
568 type: 'avcC',
569 size: 25,
570 configurationVersion: 1,
571 avcProfileIndication: 0,
572 profileCompatibility: 0,
573 avcLevelIndication: 0,
574 lengthSizeMinusOne: 0,
575 sps: [new Uint8Array(1)],
576 pps: [new Uint8Array([1, 2]),
577 new Uint8Array([0xff])]
578 }, {
579 type: 'btrt',
580 size: 20,
581 bufferSizeDB: 0,
582 maxBitrate: 1,
583 avgBitrate: 1
584 }]
585 }]
586 }]);
587 });
588
589 test('can parse an audio stsd', function() {
590 var data = box('stsd',
591 0x00, // version 0
592 0x00, 0x00, 0x00, // flags
593 0x00, 0x00, 0x00, 0x01, // entry_count
594 box('mp4a',
595 0x00, 0x00, 0x00,
596 0x00, 0x00, 0x00, // reserved
597 0x00, 0x01, // data_reference_index
598 0x00, 0x00, 0x00, 0x00,
599 0x00, 0x00, 0x00, 0x00, // reserved
600 0x00, 0x02, // channelcount
601 0x00, 0x10, // samplesize
602 0x00, 0x00, // pre_defined
603 0x00, 0x00, // reserved
604 0xbb, 0x80, 0x00, 0x00, // samplerate, fixed-point 16.16
605 box('esds',
606 0x00, // version 0
607 0x00, 0x00, 0x00, // flags
608 0x03, // tag, ES_DescrTag
609 0x00, // length
610 0x00, 0x01, // ES_ID
611 0x00, // streamDependenceFlag, URL_Flag, reserved, streamPriority
612
613 // DecoderConfigDescriptor
614 0x04, // tag, DecoderConfigDescrTag
615 0x0d, // length
616 0x40, // objectProfileIndication, AAC Main
617 0x15, // streamType, AudioStream. upstream, reserved
618 0x00, 0x00, 0xff, // bufferSizeDB
619 0x00, 0x00, 0x00, 0xff, // maxBitrate
620 0x00, 0x00, 0x00, 0xaa, // avgBitrate
621
622 // DecoderSpecificInfo
623 0x05, // tag, DecoderSpecificInfoTag
624 0x02, // length
625 0x11, 0x90, 0x06, 0x01, 0x02))); // decoder specific info
626
627 deepEqual(videojs.inspectMp4(new Uint8Array(data)), [{
628 version: 0,
629 flags: new Uint8Array([0, 0, 0]),
630 type: 'stsd',
631 size: 91,
632 sampleDescriptions: [{
633 type: 'mp4a',
634 dataReferenceIndex: 1,
635 channelcount: 2,
636 samplesize: 16,
637 samplerate: 48000,
638 size: 75,
639 streamDescriptor: {
640 type: 'esds',
641 version: 0,
642 size: 39,
643 flags: new Uint8Array([0, 0, 0]),
644 esId: 1,
645 streamPriority: 0,
646 decoderConfig: {
647 objectProfileIndication: 0x40,
648 streamType: 0x05,
649 bufferSize: 0xff,
650 maxBitrate: 0xff,
651 avgBitrate: 0xaa
652 }
653 }
654 }]
655 }], 'parsed an audio stsd');
656 });
657
658 test('can parse an styp', function() {
659 deepEqual(videojs.inspectMp4(new Uint8Array(box('styp',
660 0x61, 0x76, 0x63, 0x31, // major brand
661 0x00, 0x00, 0x00, 0x02, // minor version
662 98, 111, 111, 112 // compatible brands
663 ))), [{
664 type: 'styp',
665 size: 4 * 5,
666 majorBrand: 'avc1',
667 minorVersion: 2,
668 compatibleBrands: ['boop']
669 }], 'parsed an styp');
670 });
671
672 test('can parse a vmhd', function() {
673 var data = box('vmhd',
674 0x00, // version
675 0x00, 0x00, 0x00, // flags
676 0x00, 0x00, // graphicsmode
677 0x00, 0x00,
678 0x00, 0x00,
679 0x00, 0x00); // opcolor
680
681 deepEqual(videojs.inspectMp4(new Uint8Array(data)),
682 [{
683 type: 'vmhd',
684 size: 20,
685 version: 0,
686 flags: new Uint8Array([0, 0, 0]),
687 graphicsmode: 0,
688 opcolor: new Uint16Array([0, 0, 0])
689 }]);
690 });
691
692 test('can parse an stsz', function() {
693 var data = box('stsz',
694 0x00, // version
695 0x00, 0x00, 0x00, // flags
696 0x00, 0x00, 0x00, 0x00, // sample_size
697 0x00, 0x00, 0x00, 0x03, // sample_count
698 0x00, 0x00, 0x00, 0x01, // entry_size
699 0x00, 0x00, 0x00, 0x02, // entry_size
700 0x00, 0x00, 0x00, 0x03); // entry_size
701 deepEqual(videojs.inspectMp4(new Uint8Array(data)),
702 [{
703 type: 'stsz',
704 size: 32,
705 version: 0,
706 flags: new Uint8Array([0, 0, 0]),
707 sampleSize: 0,
708 entries: [1, 2, 3]
709 }]);
710 });
711
712 test('can parse a moof', function() {
713 var data = box('moof',
714 box('mfhd',
715 0x00, // version
716 0x00, 0x00, 0x00, // flags
717 0x00, 0x00, 0x00, 0x04), // sequence_number
718 box('traf',
719 box('tfhd',
720 0x00, // version
721 0x00, 0x00, 0x3b, // flags
722 0x00, 0x00, 0x00, 0x01, // track_ID
723 0x00, 0x00, 0x00, 0x00,
724 0x00, 0x00, 0x00, 0x01, // base_data_offset
725 0x00, 0x00, 0x00, 0x02, // sample_description_index
726 0x00, 0x00, 0x00, 0x03, // default_sample_duration,
727 0x00, 0x00, 0x00, 0x04, // default_sample_size
728 0x00, 0x00, 0x00, 0x05))); // default_sample_flags
729 deepEqual(videojs.inspectMp4(new Uint8Array(data)),
730 [{
731 type: 'moof',
732 size: 72,
733 boxes: [{
734 type: 'mfhd',
735 size: 16,
736 version: 0,
737 flags: new Uint8Array([0, 0, 0]),
738 sequenceNumber: 4
739 },
740 {
741 type: 'traf',
742 size: 48,
743 boxes: [{
744 type: 'tfhd',
745 version: 0,
746 size: 40,
747 flags: new Uint8Array([0x00, 0, 0x3b]),
748 trackId: 1,
749 baseDataOffset: 1,
750 sampleDescriptionIndex: 2,
751 defaultSampleDuration: 3,
752 defaultSampleSize: 4,
753 defaultSampleFlags: 5
754 }]
755 }]
756 }]);
757 });
758
759 test('can parse a trun', function() {
760 var data = box('trun',
761 0x00, // version
762 0x00, 0x0b, 0x05, // flags
763 0x00, 0x00, 0x00, 0x02, // sample_count
764 0x00, 0x00, 0x00, 0x01, // data_offset
765 // first_sample_flags
766 // r:0000 il:10 sdo:01 sido:10 shr:01 spv:111 snss:1
767 // dp:1111 1110 1101 1100
768 0x09, 0x9f, 0xfe, 0xdc,
769
770 0x00, 0x00, 0x00, 0x09, // sample_duration
771 0x00, 0x00, 0x00, 0xff, // sample_size
772 0x00, 0x00, 0x00, 0x00, // sample_composition_time_offset
773
774 0x00, 0x00, 0x00, 0x08, // sample_duration
775 0x00, 0x00, 0x00, 0xfe, // sample_size
776 0x00, 0x00, 0x00, 0x00); // sample_composition_time_offset
777 deepEqual(videojs.inspectMp4(new Uint8Array(data)),
778 [{
779 type: 'trun',
780 version: 0,
781 size: 48,
782 flags: new Uint8Array([0, 0x0b, 0x05]),
783 dataOffset: 1,
784 samples: [{
785 duration: 9,
786 size: 0xff,
787 flags: {
788 isLeading: 2,
789 dependsOn: 1,
790 isDependedOn: 2,
791 hasRedundancy: 1,
792 paddingValue: 7,
793 isNonSyncSample: 1,
794 degradationPriority: 0xfedc,
795 },
796 compositionTimeOffset: 0
797 }, {
798 duration: 8,
799 size: 0xfe,
800 compositionTimeOffset: 0
801 }]
802 }]);
803 });
804
805 test('can parse a trun with per-sample flags', function() {
806 var data = box('trun',
807 0x00, // version
808 0x00, 0x0f, 0x00, // flags
809 0x00, 0x00, 0x00, 0x01, // sample_count
810
811 0x00, 0x00, 0x00, 0x09, // sample_duration
812 0x00, 0x00, 0x00, 0xff, // sample_size
813 // sample_flags
814 // r:0000 il:00 sdo:01, sido:11 shr:00 spv:010 snss:0
815 // dp: 0001 0010 0011 0100
816 0x01, 0xc4, 0x12, 0x34,
817 0x00, 0x00, 0x00, 0x00); // sample_composition_time_offset
818 deepEqual(videojs.inspectMp4(new Uint8Array(data)),
819 [{
820 type: 'trun',
821 version: 0,
822 size: 32,
823 flags: new Uint8Array([0, 0x0f, 0x00]),
824 samples: [{
825 duration: 9,
826 size: 0xff,
827 flags: {
828 isLeading: 0,
829 dependsOn: 1,
830 isDependedOn: 3,
831 hasRedundancy: 0,
832 paddingValue: 2,
833 isNonSyncSample: 0,
834 degradationPriority: 0x1234
835 },
836 compositionTimeOffset: 0
837 }]
838 }]);
839
840 });
841
842 test('can parse an sdtp', function() {
843 var data = box('sdtp',
844 0x00, // version
845 0x00, 0x00, 0x00, // flags
846 // reserved + sample_depends_on +
847 // sample_is_dependend_on + sample_has_redundancy
848 0x15,
849 // reserved + sample_depends_on +
850 // sample_is_dependend_on + sample_has_redundancy
851 0x27);
852 deepEqual(videojs.inspectMp4(new Uint8Array(data)), [{
853 type: 'sdtp',
854 version: 0,
855 flags: new Uint8Array([0, 0, 0]),
856 size: 14,
857 samples: [{
858 dependsOn: 1,
859 isDependedOn: 1,
860 hasRedundancy: 1
861 }, {
862 dependsOn: 2,
863 isDependedOn: 1,
864 hasRedundancy: 3
865 }]
866 }]);
867 });
868
869 test('can parse a sidx', function(){
870 var data = box('sidx',
871 0x00, // version
872 0x00, 0x00, 0x00, // flags
873 0x00, 0x00, 0x00, 0x02, // reference_ID
874 0x00, 0x00, 0x00, 0x01, // timescale
875 0x01, 0x02, 0x03, 0x04, // earliest_presentation_time
876 0x00, 0x00, 0x00, 0x00, // first_offset
877 0x00, 0x00, // reserved
878 0x00, 0x02, // reference_count
879 // first reference
880 0x80, 0x00, 0x00, 0x07, // reference_type(1) + referenced_size(31)
881 0x00, 0x00, 0x00, 0x08, // subsegment_duration
882 0x80, 0x00, 0x00, 0x09, // starts_with_SAP(1) + SAP_type(3) + SAP_delta_time(28)
883 // second reference
884 0x00, 0x00, 0x00, 0x03, // reference_type(1) + referenced_size(31)
885 0x00, 0x00, 0x00, 0x04, // subsegment_duration
886 0x10, 0x00, 0x00, 0x05 // starts_with_SAP(1) + SAP_type(3) + SAP_delta_time(28)
887 );
888 deepEqual(videojs.inspectMp4(new Uint8Array(data)),
889 [{
890 type: 'sidx',
891 version: 0,
892 flags: new Uint8Array([0, 0x00, 0x00]),
893 timescale: 1,
894 earliestPresentationTime: 0x01020304,
895 firstOffset: 0,
896 referenceId: 2,
897 size: 56,
898 references: [{
899 referenceType: 1,
900 referencedSize: 7,
901 subsegmentDuration: 8,
902 startsWithSap: true,
903 sapType: 0,
904 sapDeltaTime: 9
905 },{
906 referenceType: 0,
907 referencedSize: 3,
908 subsegmentDuration: 4,
909 startsWithSap: false,
910 sapType: 1,
911 sapDeltaTime: 5
912 }]
913
914 }]);
915 });
916
917 test('can parse an smhd', function() {
918 var data = box('smhd',
919 0x00, // version
920 0x00, 0x00, 0x00, // flags
921 0x00, 0xff, // balance, fixed-point 8.8
922 0x00, 0x00); // reserved
923
924 deepEqual(videojs.inspectMp4(new Uint8Array(data)),
925 [{
926 type: 'smhd',
927 size: 16,
928 version: 0,
929 flags: new Uint8Array([0, 0, 0]),
930 balance: 0xff / Math.pow(2, 8)
931 }],
932 'parsed an smhd');
933 });
934
935 test('can parse a tfdt', function() {
936 var data = box('tfdt',
937 0x00, // version
938 0x00, 0x00, 0x00, // flags
939 0x01, 0x02, 0x03, 0x04); // baseMediaDecodeTime
940 deepEqual(videojs.inspectMp4(new Uint8Array(data)),
941 [{
942 type: 'tfdt',
943 version: 0,
944 size: 16,
945 flags: new Uint8Array([0, 0, 0]),
946 baseMediaDecodeTime: 0x01020304
947 }]);
948 });
949
950 test('can parse a series of boxes', function() {
951 var ftyp = [
952 0x00, 0x00, 0x00, 0x18 // size 4 * 6 = 24
953 ].concat(typeBytes('ftyp')).concat([
954 0x69, 0x73, 0x6f, 0x6d, // major brand
955 0x00, 0x00, 0x00, 0x02, // minor version
956 98, 101, 101, 112, // compatible brands
957 98, 111, 111, 112, // compatible brands
958 ]);
959
960 deepEqual(videojs.inspectMp4(new Uint8Array(ftyp.concat(ftyp))),
961 [{
962 type: 'ftyp',
963 size: 4 * 6,
964 majorBrand: 'isom',
965 minorVersion: 2,
966 compatibleBrands: ['beep', 'boop']
967 },{
968 type: 'ftyp',
969 size: 4 * 6,
970 majorBrand: 'isom',
971 minorVersion: 2,
972 compatibleBrands: ['beep', 'boop']
973 }],
974 'parsed two boxes in series');
975
976 });
977
978 })(window, window.videojs);
1 /* ==========================================================================
2 HTML5 Boilerplate styles - h5bp.com (generated via initializr.com)
3 ========================================================================== */
4
5 html,
6 button,
7 input,
8 select,
9 textarea {
10 color: #222;
11 }
12
13 body {
14 font-size: 1em;
15 line-height: 1.4;
16 }
17
18 ::-moz-selection {
19 background: #b3d4fc;
20 text-shadow: none;
21 }
22
23 ::selection {
24 background: #b3d4fc;
25 text-shadow: none;
26 }
27
28 hr {
29 display: block;
30 height: 1px;
31 border: 0;
32 border-top: 1px solid #ccc;
33 margin: 1em 0;
34 padding: 0;
35 }
36
37 img {
38 vertical-align: middle;
39 }
40
41 fieldset {
42 border: 0;
43 margin: 0;
44 padding: 0;
45 }
46
47 textarea {
48 resize: vertical;
49 }
50
51 .chromeframe {
52 margin: 0.2em 0;
53 background: #ccc;
54 color: #000;
55 padding: 0.2em 0;
56 }
57
58
59 /* ===== Initializr Styles ==================================================
60 Author: Jonathan Verrecchia - verekia.com/initializr/responsive-template
61 ========================================================================== */
62
63 body {
64 font: 16px/26px Helvetica, Helvetica Neue, Arial;
65 }
66
67 .wrapper {
68 width: 90%;
69 margin: 0 5%;
70 }
71
72 /* ===================
73 ALL: Orange Theme
74 =================== */
75
76 .header-container {
77 border-bottom: 20px solid #e44d26;
78 }
79
80 .footer-container,
81 .main aside {
82 border-top: 20px solid #e44d26;
83 }
84
85 .header-container,
86 .footer-container,
87 .main aside {
88 background: #f16529;
89 }
90
91 .title {
92 color: white;
93 }
94
95 /* ==============
96 MOBILE: Menu
97 ============== */
98
99 nav ul {
100 margin: 0;
101 padding: 0;
102 }
103
104 nav a {
105 display: block;
106 margin-bottom: 10px;
107 padding: 15px 0;
108
109 text-align: center;
110 text-decoration: none;
111 font-weight: bold;
112
113 color: white;
114 background: #e44d26;
115 }
116
117 nav a:hover,
118 nav a:visited {
119 color: white;
120 }
121
122 nav a:hover {
123 text-decoration: underline;
124 }
125
126 /* ==============
127 MOBILE: Main
128 ============== */
129
130 .main {
131 padding: 30px 0;
132 }
133
134 .main article h1 {
135 font-size: 2em;
136 }
137
138 .main aside {
139 color: white;
140 padding: 0px 5% 10px;
141 }
142
143 .footer-container footer {
144 color: white;
145 padding: 20px 0;
146 }
147
148 /* ===============
149 ALL: IE Fixes
150 =============== */
151
152 .ie7 .title {
153 padding-top: 20px;
154 }
155
156 /* ==========================================================================
157 Author's custom styles
158 ========================================================================== */
159
160 section {
161 clear: both;
162 }
163
164 form label {
165 display: block;
166 }
167
168 .result-wrapper {
169 float: left;
170 margin: 10px 0;
171 min-width: 422px;
172 width: 50%;
173 }
174
175 .result {
176 border: thin solid #aaa;
177 border-radius: 5px;
178 font-size: 10px;
179 line-height: 15px;
180 margin: 0 5px;
181 padding: 0 10px;
182 }
183
184 /* ==========================================================================
185 Media Queries
186 ========================================================================== */
187
188 @media only screen and (min-width: 480px) {
189
190 /* ====================
191 INTERMEDIATE: Menu
192 ==================== */
193
194 nav a {
195 float: left;
196 width: 27%;
197 margin: 0 1.7%;
198 padding: 25px 2%;
199 margin-bottom: 0;
200 }
201
202 nav li:first-child a {
203 margin-left: 0;
204 }
205
206 nav li:last-child a {
207 margin-right: 0;
208 }
209
210 /* ========================
211 INTERMEDIATE: IE Fixes
212 ======================== */
213
214 nav ul li {
215 display: inline;
216 }
217
218 .oldie nav a {
219 margin: 0 0.7%;
220 }
221 }
222
223 @media only screen and (min-width: 768px) {
224
225 /* ====================
226 WIDE: CSS3 Effects
227 ==================== */
228
229 .header-container,
230 .main aside {
231 -webkit-box-shadow: 0 5px 10px #aaa;
232 -moz-box-shadow: 0 5px 10px #aaa;
233 box-shadow: 0 5px 10px #aaa;
234 }
235
236 /* ============
237 WIDE: Menu
238 ============ */
239
240 .title {
241 float: left;
242 }
243
244 nav {
245 float: right;
246 width: 38%;
247 }
248
249 /* ============
250 WIDE: Main
251 ============ */
252
253 .main article {
254 float: left;
255 width: 100%;
256 }
257 }
258
259 @media only screen and (min-width: 1140px) {
260
261 /* ===============
262 Maximal Width
263 =============== */
264
265 .wrapper {
266 width: 1026px; /* 1140px - 10% for margins */
267 margin: 0 auto;
268 }
269 }
270
271 /* ==========================================================================
272 Helper classes
273 ========================================================================== */
274
275 .ir {
276 background-color: transparent;
277 border: 0;
278 overflow: hidden;
279 *text-indent: -9999px;
280 }
281
282 .ir:before {
283 content: "";
284 display: block;
285 width: 0;
286 height: 150%;
287 }
288
289 .hidden {
290 display: none !important;
291 visibility: hidden;
292 }
293
294 .visuallyhidden {
295 border: 0;
296 clip: rect(0 0 0 0);
297 height: 1px;
298 margin: -1px;
299 overflow: hidden;
300 padding: 0;
301 position: absolute;
302 width: 1px;
303 }
304
305 .visuallyhidden.focusable:active,
306 .visuallyhidden.focusable:focus {
307 clip: auto;
308 height: auto;
309 margin: 0;
310 overflow: visible;
311 position: static;
312 width: auto;
313 }
314
315 .invisible {
316 visibility: hidden;
317 }
318
319 .clearfix:before,
320 .clearfix:after {
321 content: " ";
322 display: table;
323 }
324
325 .clearfix:after {
326 clear: both;
327 }
328
329 .clearfix {
330 *zoom: 1;
331 }
332
333 /* ==========================================================================
334 Print styles
335 ========================================================================== */
336
337 @media print {
338 * {
339 background: transparent !important;
340 color: #000 !important; /* Black prints faster: h5bp.com/s */
341 box-shadow: none !important;
342 text-shadow: none !important;
343 }
344
345 a,
346 a:visited {
347 text-decoration: underline;
348 }
349
350 a[href]:after {
351 content: " (" attr(href) ")";
352 }
353
354 abbr[title]:after {
355 content: " (" attr(title) ")";
356 }
357
358 /*
359 * Don't show links for images, or javascript/internal links
360 */
361
362 .ir a:after,
363 a[href^="javascript:"]:after,
364 a[href^="#"]:after {
365 content: "";
366 }
367
368 pre,
369 blockquote {
370 border: 1px solid #999;
371 page-break-inside: avoid;
372 }
373
374 thead {
375 display: table-header-group; /* h5bp.com/t */
376 }
377
378 tr,
379 img {
380 page-break-inside: avoid;
381 }
382
383 img {
384 max-width: 100% !important;
385 }
386
387 @page {
388 margin: 0.5cm;
389 }
390
391 p,
392 h2,
393 h3 {
394 orphans: 3;
395 widows: 3;
396 }
397
398 h2,
399 h3 {
400 page-break-after: avoid;
401 }
402 }
403
404 /**
405 * Diff styling
406 */
407 #comparison p {
408 white-space: normal;
409 }
410 #comparison pre {
411 font-size: 90%;
412 }
413 ins, del {
414 text-decoration: none;
415 }
416 ins {
417 background-color: #C6E746;
418 }
419
420 del {
421 background-color: #EE5757
422 }
...\ No newline at end of file ...\ No newline at end of file
1 /*! normalize.css v1.1.2 | MIT License | git.io/normalize */
2
3 /* ==========================================================================
4 HTML5 display definitions
5 ========================================================================== */
6
7 /**
8 * Correct `block` display not defined in IE 6/7/8/9 and Firefox 3.
9 */
10
11 article,
12 aside,
13 details,
14 figcaption,
15 figure,
16 footer,
17 header,
18 hgroup,
19 main,
20 nav,
21 section,
22 summary {
23 display: block;
24 }
25
26 /**
27 * Correct `inline-block` display not defined in IE 6/7/8/9 and Firefox 3.
28 */
29
30 audio,
31 canvas,
32 video {
33 display: inline-block;
34 *display: inline;
35 *zoom: 1;
36 }
37
38 /**
39 * Prevent modern browsers from displaying `audio` without controls.
40 * Remove excess height in iOS 5 devices.
41 */
42
43 audio:not([controls]) {
44 display: none;
45 height: 0;
46 }
47
48 /**
49 * Address styling not present in IE 7/8/9, Firefox 3, and Safari 4.
50 * Known issue: no IE 6 support.
51 */
52
53 [hidden] {
54 display: none;
55 }
56
57 /* ==========================================================================
58 Base
59 ========================================================================== */
60
61 /**
62 * 1. Correct text resizing oddly in IE 6/7 when body `font-size` is set using
63 * `em` units.
64 * 2. Prevent iOS text size adjust after orientation change, without disabling
65 * user zoom.
66 */
67
68 html {
69 font-size: 100%; /* 1 */
70 -ms-text-size-adjust: 100%; /* 2 */
71 -webkit-text-size-adjust: 100%; /* 2 */
72 }
73
74 /**
75 * Address `font-family` inconsistency between `textarea` and other form
76 * elements.
77 */
78
79 html,
80 button,
81 input,
82 select,
83 textarea {
84 font-family: sans-serif;
85 }
86
87 /**
88 * Address margins handled incorrectly in IE 6/7.
89 */
90
91 body {
92 margin: 0;
93 }
94
95 /* ==========================================================================
96 Links
97 ========================================================================== */
98
99 /**
100 * Address `outline` inconsistency between Chrome and other browsers.
101 */
102
103 a:focus {
104 outline: thin dotted;
105 }
106
107 /**
108 * Improve readability when focused and also mouse hovered in all browsers.
109 */
110
111 a:active,
112 a:hover {
113 outline: 0;
114 }
115
116 /* ==========================================================================
117 Typography
118 ========================================================================== */
119
120 /**
121 * Address font sizes and margins set differently in IE 6/7.
122 * Address font sizes within `section` and `article` in Firefox 4+, Safari 5,
123 * and Chrome.
124 */
125
126 h1 {
127 font-size: 2em;
128 margin: 0.67em 0;
129 }
130
131 h2 {
132 font-size: 1.5em;
133 margin: 0.83em 0;
134 }
135
136 h3 {
137 font-size: 1.17em;
138 margin: 1em 0;
139 }
140
141 h4 {
142 font-size: 1em;
143 margin: 1.33em 0;
144 }
145
146 h5 {
147 font-size: 0.83em;
148 margin: 1.67em 0;
149 }
150
151 h6 {
152 font-size: 0.67em;
153 margin: 2.33em 0;
154 }
155
156 /**
157 * Address styling not present in IE 7/8/9, Safari 5, and Chrome.
158 */
159
160 abbr[title] {
161 border-bottom: 1px dotted;
162 }
163
164 /**
165 * Address style set to `bolder` in Firefox 3+, Safari 4/5, and Chrome.
166 */
167
168 b,
169 strong {
170 font-weight: bold;
171 }
172
173 blockquote {
174 margin: 1em 40px;
175 }
176
177 /**
178 * Address styling not present in Safari 5 and Chrome.
179 */
180
181 dfn {
182 font-style: italic;
183 }
184
185 /**
186 * Address differences between Firefox and other browsers.
187 * Known issue: no IE 6/7 normalization.
188 */
189
190 hr {
191 -moz-box-sizing: content-box;
192 box-sizing: content-box;
193 height: 0;
194 }
195
196 /**
197 * Address styling not present in IE 6/7/8/9.
198 */
199
200 mark {
201 background: #ff0;
202 color: #000;
203 }
204
205 /**
206 * Address margins set differently in IE 6/7.
207 */
208
209 p,
210 pre {
211 margin: 1em 0;
212 }
213
214 /**
215 * Correct font family set oddly in IE 6, Safari 4/5, and Chrome.
216 */
217
218 code,
219 kbd,
220 pre,
221 samp {
222 font-family: monospace, serif;
223 _font-family: 'courier new', monospace;
224 font-size: 1em;
225 }
226
227 /**
228 * Improve readability of pre-formatted text in all browsers.
229 */
230
231 pre {
232 white-space: pre;
233 white-space: pre-wrap;
234 word-wrap: break-word;
235 }
236
237 /**
238 * Address CSS quotes not supported in IE 6/7.
239 */
240
241 q {
242 quotes: none;
243 }
244
245 /**
246 * Address `quotes` property not supported in Safari 4.
247 */
248
249 q:before,
250 q:after {
251 content: '';
252 content: none;
253 }
254
255 /**
256 * Address inconsistent and variable font size in all browsers.
257 */
258
259 small {
260 font-size: 80%;
261 }
262
263 /**
264 * Prevent `sub` and `sup` affecting `line-height` in all browsers.
265 */
266
267 sub,
268 sup {
269 font-size: 75%;
270 line-height: 0;
271 position: relative;
272 vertical-align: baseline;
273 }
274
275 sup {
276 top: -0.5em;
277 }
278
279 sub {
280 bottom: -0.25em;
281 }
282
283 /* ==========================================================================
284 Lists
285 ========================================================================== */
286
287 /**
288 * Address margins set differently in IE 6/7.
289 */
290
291 dl,
292 menu,
293 ol,
294 ul {
295 margin: 1em 0;
296 }
297
298 dd {
299 margin: 0 0 0 40px;
300 }
301
302 /**
303 * Address paddings set differently in IE 6/7.
304 */
305
306 menu,
307 ol,
308 ul {
309 padding: 0 0 0 40px;
310 }
311
312 /**
313 * Correct list images handled incorrectly in IE 7.
314 */
315
316 nav ul,
317 nav ol {
318 list-style: none;
319 list-style-image: none;
320 }
321
322 /* ==========================================================================
323 Embedded content
324 ========================================================================== */
325
326 /**
327 * 1. Remove border when inside `a` element in IE 6/7/8/9 and Firefox 3.
328 * 2. Improve image quality when scaled in IE 7.
329 */
330
331 img {
332 border: 0; /* 1 */
333 -ms-interpolation-mode: bicubic; /* 2 */
334 }
335
336 /**
337 * Correct overflow displayed oddly in IE 9.
338 */
339
340 svg:not(:root) {
341 overflow: hidden;
342 }
343
344 /* ==========================================================================
345 Figures
346 ========================================================================== */
347
348 /**
349 * Address margin not present in IE 6/7/8/9, Safari 5, and Opera 11.
350 */
351
352 figure {
353 margin: 0;
354 }
355
356 /* ==========================================================================
357 Forms
358 ========================================================================== */
359
360 /**
361 * Correct margin displayed oddly in IE 6/7.
362 */
363
364 form {
365 margin: 0;
366 }
367
368 /**
369 * Define consistent border, margin, and padding.
370 */
371
372 fieldset {
373 border: 1px solid #c0c0c0;
374 margin: 0 2px;
375 padding: 0.35em 0.625em 0.75em;
376 }
377
378 /**
379 * 1. Correct color not being inherited in IE 6/7/8/9.
380 * 2. Correct text not wrapping in Firefox 3.
381 * 3. Correct alignment displayed oddly in IE 6/7.
382 */
383
384 legend {
385 border: 0; /* 1 */
386 padding: 0;
387 white-space: normal; /* 2 */
388 *margin-left: -7px; /* 3 */
389 }
390
391 /**
392 * 1. Correct font size not being inherited in all browsers.
393 * 2. Address margins set differently in IE 6/7, Firefox 3+, Safari 5,
394 * and Chrome.
395 * 3. Improve appearance and consistency in all browsers.
396 */
397
398 button,
399 input,
400 select,
401 textarea {
402 font-size: 100%; /* 1 */
403 margin: 0; /* 2 */
404 vertical-align: baseline; /* 3 */
405 *vertical-align: middle; /* 3 */
406 }
407
408 /**
409 * Address Firefox 3+ setting `line-height` on `input` using `!important` in
410 * the UA stylesheet.
411 */
412
413 button,
414 input {
415 line-height: normal;
416 }
417
418 /**
419 * Address inconsistent `text-transform` inheritance for `button` and `select`.
420 * All other form control elements do not inherit `text-transform` values.
421 * Correct `button` style inheritance in Chrome, Safari 5+, and IE 6+.
422 * Correct `select` style inheritance in Firefox 4+ and Opera.
423 */
424
425 button,
426 select {
427 text-transform: none;
428 }
429
430 /**
431 * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
432 * and `video` controls.
433 * 2. Correct inability to style clickable `input` types in iOS.
434 * 3. Improve usability and consistency of cursor style between image-type
435 * `input` and others.
436 * 4. Remove inner spacing in IE 7 without affecting normal text inputs.
437 * Known issue: inner spacing remains in IE 6.
438 */
439
440 button,
441 html input[type="button"], /* 1 */
442 input[type="reset"],
443 input[type="submit"] {
444 -webkit-appearance: button; /* 2 */
445 cursor: pointer; /* 3 */
446 *overflow: visible; /* 4 */
447 }
448
449 /**
450 * Re-set default cursor for disabled elements.
451 */
452
453 button[disabled],
454 html input[disabled] {
455 cursor: default;
456 }
457
458 /**
459 * 1. Address box sizing set to content-box in IE 8/9.
460 * 2. Remove excess padding in IE 8/9.
461 * 3. Remove excess padding in IE 7.
462 * Known issue: excess padding remains in IE 6.
463 */
464
465 input[type="checkbox"],
466 input[type="radio"] {
467 box-sizing: border-box; /* 1 */
468 padding: 0; /* 2 */
469 *height: 13px; /* 3 */
470 *width: 13px; /* 3 */
471 }
472
473 /**
474 * 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome.
475 * 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome
476 * (include `-moz` to future-proof).
477 */
478
479 input[type="search"] {
480 -webkit-appearance: textfield; /* 1 */
481 -moz-box-sizing: content-box;
482 -webkit-box-sizing: content-box; /* 2 */
483 box-sizing: content-box;
484 }
485
486 /**
487 * Remove inner padding and search cancel button in Safari 5 and Chrome
488 * on OS X.
489 */
490
491 input[type="search"]::-webkit-search-cancel-button,
492 input[type="search"]::-webkit-search-decoration {
493 -webkit-appearance: none;
494 }
495
496 /**
497 * Remove inner padding and border in Firefox 3+.
498 */
499
500 button::-moz-focus-inner,
501 input::-moz-focus-inner {
502 border: 0;
503 padding: 0;
504 }
505
506 /**
507 * 1. Remove default vertical scrollbar in IE 6/7/8/9.
508 * 2. Improve readability and alignment in all browsers.
509 */
510
511 textarea {
512 overflow: auto; /* 1 */
513 vertical-align: top; /* 2 */
514 }
515
516 /* ==========================================================================
517 Tables
518 ========================================================================== */
519
520 /**
521 * Remove most spacing between table cells.
522 */
523
524 table {
525 border-collapse: collapse;
526 border-spacing: 0;
527 }
1 /*! 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 ...\ No newline at end of file
1 <!DOCTYPE html>
2 <!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
3 <!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
4 <!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]-->
5 <!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]-->
6 <head>
7 <meta charset="utf-8">
8 <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
9 <title></title>
10 <meta name="description" content="">
11 <meta name="viewport" content="width=device-width">
12
13 <link rel="stylesheet" href="css/normalize.min.css">
14 <link rel="stylesheet" href="css/main.css">
15
16 <script src="js/vendor/modernizr-2.6.2.min.js"></script>
17 </head>
18 <body>
19 <!--[if lt IE 7]>
20 <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>
21 <![endif]-->
22
23 <div class="header-container">
24 <header class="wrapper clearfix">
25 <h1 class="title">Transmux Analyzer</h1>
26 </header>
27 </div>
28
29 <div class="main-container">
30 <div class="main wrapper clearfix">
31
32 <article>
33 <header>
34 <p>
35 This page can help you compare the results of the
36 transmuxing performed by videojs-contrib-hls with a known,
37 working file produced by another tool. You could use
38 ffmpeg to transform an MPEG-2 transport stream into an FLV
39 with a command like this:
40 <pre>ffmpeg -i input.ts -acodec copy -vcodec copy output.flv</pre>
41 </p>
42 <p>
43 This page only compares FLVs files. There is
44 a <a href="mp4.html">similar utility</a> for testing the mp4
45 conversion.
46 </p>
47 </header>
48 <section>
49 <h2>Inputs</h2>
50 <form id="inputs">
51 <fieldset>
52 <label>
53 Your original MP2T segment:
54 <input type="file" id="original">
55 </label>
56 <label>
57 Key (optional):
58 <input type="text" id="key">
59 </label>
60 <label>
61 IV (optional):
62 <input type="text" id="iv">
63 </label>
64 </fieldset>
65 <label>
66 A working, FLV version of the underlying stream
67 produced by another tool:
68 <input type="file" id="working">
69 </label>
70 </form>
71 </section>
72 <section>
73 <h2>Tag Comparison</h2>
74 <div class="result-wrapper">
75 <h3>videojs-contrib-hls</h3>
76 <ol class="vjs-tags">
77 </ol>
78 </div>
79 <div class="result-wrapper">
80 <h3>Working</h3>
81 <ol class="working-tags">
82 </ol>
83 </div>
84 </section>
85 <section>
86 <h2>Results</h2>
87 <div class="result-wrapper">
88 <h3>videojs-contrib-hls</h3>
89 <div class="vjs-hls-output result">
90 <p>
91 The results of transmuxing your input file with
92 videojs-contrib-hls will show up here.
93 </p>
94 </div>
95 </div>
96 <div class="result-wrapper">
97 <h3>Working</h3>
98 <div class="working-output result">
99 <p>
100 The "good" version of the file will show up here.
101 </p>
102 </div>
103 </div>
104 </section>
105 </article>
106
107 </div> <!-- #main -->
108 </div> <!-- #main-container -->
109
110 <div class="footer-container">
111 <footer class="wrapper">
112 <h3>footer</h3>
113 </footer>
114 </div>
115
116
117 <script>
118 window.videojs = {
119 Hls: {}
120 };
121 </script>
122 <!-- transmuxing -->
123 <script src="../../src/stream.js"></script>
124 <script src="../../src/flv-tag.js"></script>
125 <script src="../../src/exp-golomb.js"></script>
126 <script src="../../src/h264-stream.js"></script>
127 <script src="../../src/aac-stream.js"></script>
128 <script src="../../src/metadata-stream.js"></script>
129 <script src="../../src/segment-parser.js"></script>
130 <script src="../../node_modules/pkcs7/dist/pkcs7.unpad.js"></script>
131 <script src="../../src/decrypter.js"></script>
132
133 <script src="../../src/bin-utils.js"></script>
134 <script>
135 var inputs = document.getElementById('inputs'),
136 original = document.getElementById('original'),
137 key = document.getElementById('key'),
138 iv = document.getElementById('iv'),
139 working = document.getElementById('working'),
140
141 vjsTags = document.querySelector('.vjs-tags'),
142 workingTags = document.querySelector('.working-tags'),
143
144 vjsOutput = document.querySelector('.vjs-hls-output'),
145 workingOutput = document.querySelector('.working-output'),
146
147 tagTypes = {
148 0x08: 'audio',
149 0x09: 'video',
150 0x12: 'metadata'
151 };
152
153 videojs.log = console.log.bind(console);
154
155 original.addEventListener('change', function() {
156 var reader = new FileReader();
157 reader.addEventListener('loadend', function() {
158 var parser = new videojs.Hls.SegmentParser(),
159 tags = [parser.getFlvHeader()],
160 tag,
161 bytes,
162 hex,
163 li,
164 byteLength = 0,
165 data,
166 i,
167 pos;
168
169 // clear old tag info
170 vjsTags.innerHTML = '';
171
172 // optionally, decrypt the segment
173 if (key.value && iv.value) {
174 bytes = videojs.Hls.decrypt(new Uint8Array(reader.result),
175 key.value.match(/([0-9a-f]{8})/gi).map(function(e) {
176 return parseInt(e, 16);
177 }),
178 iv.value.match(/([0-9a-f]{8})/gi).map(function(e) {
179 return parseInt(e, 16);
180 }));
181 } else {
182 bytes = new Uint8Array(reader.result);
183 }
184
185 parser.parseSegmentBinaryData(bytes);
186
187 // collect all the tags
188 while (parser.tagsAvailable()) {
189 tag = parser.getNextTag();
190 tags.push(tag.bytes);
191 li = document.createElement('li');
192 li.innerHTML = tagTypes[tag.bytes[0]] + ': ' + tag.bytes.byteLength + 'B';
193 vjsTags.appendChild(li);
194 }
195 // create a uint8array for the entire segment and copy everything over
196 i = tags.length;
197 while (i--) {
198 byteLength += tags[i].byteLength;
199 }
200 data = new Uint8Array(byteLength);
201 i = tags.length;
202 pos = byteLength;
203 while (i--) {
204 pos -= tags[i].byteLength;
205 data.set(tags[i], pos);
206 }
207
208 hex = '<pre>'
209 hex += videojs.Hls.utils.hexDump(data);
210 hex += '</pre>'
211
212 vjsOutput.innerHTML = hex;
213 });
214 reader.readAsArrayBuffer(this.files[0]);
215 }, false);
216
217 working.addEventListener('change', function() {
218 var reader = new FileReader();
219 reader.addEventListener('loadend', function() {
220 var hex = '<pre>',
221 bytes = new Uint8Array(reader.result),
222 i = 9, // header
223 dataSize,
224 li;
225
226 // clear old tag info
227 workingTags.innerHTML = '';
228
229 // traverse the tags
230 i += 4; // previous tag size
231 while (i < bytes.byteLength) {
232 dataSize = bytes[i + 1] << 16;
233 dataSize |= bytes[i + 2] << 8;
234 dataSize |= bytes[i + 3];
235 dataSize += 11;
236
237 li = document.createElement('li');
238 li.innerHTML = tagTypes[bytes[i]] + ': ' + dataSize + 'B';
239 workingTags.appendChild(li);
240
241 i += dataSize; // tag size
242 i += 4; // previous tag size
243 }
244
245 // output the hex dump
246 hex += videojs.Hls.utils.hexDump(bytes);
247 hex += '</pre>';
248 workingOutput.innerHTML = hex;
249 });
250 reader.readAsArrayBuffer(this.files[0]);
251 }, false);
252 </script>
253 <script type="text/plain">
254 // map nal_unit_types to friendly names
255 console.log([
256 'unspecified',
257 'slice_layer_without_partitioning',
258 'slice_data_partition_a_layer',
259 'slice_data_partition_b_layer',
260 'slice_data_partition_c_layer',
261 'slice_layer_without_partitioning_idr',
262 'sei',
263 'seq_parameter_set',
264 'pic_parameter_set',
265 'access_unit_delimiter',
266 'end_of_seq',
267 'end_of_stream',
268 'filler',
269 'seq_parameter_set_ext',
270 'prefix_nal_unit',
271 'subset_seq_parameter_set',
272 'reserved',
273 'reserved',
274 'reserved'
275 ][nalUnitType]);
276 </script>
277 </body>
278 </html>
1 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];
2 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];
3 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];
4 var box = new Uint8Array( moof.concat(mdat) );
1 (function(window, videojs) {
2 'use strict';
3
4 var
5 DataView = window.DataView,
6 /**
7 * Returns the string representation of an ASCII encoded four byte buffer.
8 * @param buffer {Uint8Array} a four-byte buffer to translate
9 * @return {string} the corresponding string
10 */
11 parseType = function(buffer) {
12 var result = '';
13 result += String.fromCharCode(buffer[0]);
14 result += String.fromCharCode(buffer[1]);
15 result += String.fromCharCode(buffer[2]);
16 result += String.fromCharCode(buffer[3]);
17 return result;
18 },
19 parseMp4Date = function(seconds) {
20 return new Date(seconds * 1000 - 2082844800000);
21 },
22 parseSampleFlags = function(flags) {
23 return {
24 isLeading: (flags[0] & 0x0c) >>> 2,
25 dependsOn: flags[0] & 0x03,
26 isDependedOn: (flags[1] & 0xc0) >>> 6,
27 hasRedundancy: (flags[1] & 0x30) >>> 4,
28 paddingValue: (flags[1] & 0x0e) >>> 1,
29 isNonSyncSample: flags[1] & 0x01,
30 degradationPriority: (flags[2] << 8) | flags[3]
31 };
32 },
33 nalParse = function(avcStream) {
34 var
35 avcView = new DataView(avcStream.buffer, avcStream.byteOffset, avcStream.byteLength),
36 result = [],
37 i,
38 length;
39 for (i = 0; i < avcStream.length; i += length) {
40 length = avcView.getUint32(i);
41 i += 4;
42
43 // bail if this doesn't appear to be an H264 stream
44 if (length <= 0) {
45 return;
46 }
47
48 switch(avcStream[i] & 0x1F) {
49 case 0x01:
50 result.push('slice_layer_without_partitioning_rbsp');
51 break;
52 case 0x05:
53 result.push('slice_layer_without_partitioning_rbsp_idr');
54 break;
55 case 0x06:
56 result.push('sei_rbsp');
57 break;
58 case 0x07:
59 result.push('seq_parameter_set_rbsp');
60 break;
61 case 0x08:
62 result.push('pic_parameter_set_rbsp');
63 break;
64 case 0x09:
65 result.push('access_unit_delimiter_rbsp');
66 break;
67 default:
68 result.push(avcStream[i] & 0x1F);
69 break;
70 }
71 }
72 return result;
73 },
74
75 // registry of handlers for individual mp4 box types
76 parse = {
77 // codingname, not a first-class box type. stsd entries share the
78 // same format as real boxes so the parsing infrastructure can be
79 // shared
80 avc1: function(data) {
81 var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
82 return {
83 dataReferenceIndex: view.getUint16(6),
84 width: view.getUint16(24),
85 height: view.getUint16(26),
86 horizresolution: view.getUint16(28) + (view.getUint16(30) / 16),
87 vertresolution: view.getUint16(32) + (view.getUint16(34) / 16),
88 frameCount: view.getUint16(40),
89 depth: view.getUint16(74),
90 config: videojs.inspectMp4(data.subarray(78, data.byteLength))
91 };
92 },
93 avcC: function(data) {
94 var
95 view = new DataView(data.buffer, data.byteOffset, data.byteLength),
96 result = {
97 configurationVersion: data[0],
98 avcProfileIndication: data[1],
99 profileCompatibility: data[2],
100 avcLevelIndication: data[3],
101 lengthSizeMinusOne: data[4] & 0x03,
102 sps: [],
103 pps: []
104 },
105 numOfSequenceParameterSets = data[5] & 0x1f,
106 numOfPictureParameterSets,
107 nalSize,
108 offset,
109 i;
110
111 // iterate past any SPSs
112 offset = 6;
113 for (i = 0; i < numOfSequenceParameterSets; i++) {
114 nalSize = view.getUint16(offset);
115 offset += 2;
116 result.sps.push(new Uint8Array(data.subarray(offset, offset + nalSize)));
117 offset += nalSize;
118 }
119 // iterate past any PPSs
120 numOfPictureParameterSets = data[offset];
121 offset++;
122 for (i = 0; i < numOfPictureParameterSets; i++) {
123 nalSize = view.getUint16(offset);
124 offset += 2;
125 result.pps.push(new Uint8Array(data.subarray(offset, offset + nalSize)));
126 offset += nalSize;
127 }
128 return result;
129 },
130 btrt: function(data) {
131 var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
132 return {
133 bufferSizeDB: view.getUint32(0),
134 maxBitrate: view.getUint32(4),
135 avgBitrate: view.getUint32(8)
136 };
137 },
138 esds: function(data) {
139 return {
140 version: data[0],
141 flags: new Uint8Array(data.subarray(1, 4)),
142 esId: (data[6] << 8) | data[7],
143 streamPriority: data[8] & 0x1f,
144 decoderConfig: {
145 objectProfileIndication: data[11],
146 streamType: (data[12] >>> 2) & 0x3f,
147 bufferSize: (data[13] << 16) | (data[14] << 8) | data[15],
148 maxBitrate: (data[16] << 24) |
149 (data[17] << 16) |
150 (data[18] << 8) |
151 data[19],
152 avgBitrate: (data[20] << 24) |
153 (data[21] << 16) |
154 (data[22] << 8) |
155 data[23]
156 }
157 };
158 },
159 ftyp: function(data) {
160 var
161 view = new DataView(data.buffer, data.byteOffset, data.byteLength),
162 result = {
163 majorBrand: parseType(data.subarray(0, 4)),
164 minorVersion: view.getUint32(4),
165 compatibleBrands: []
166 },
167 i = 8;
168 while (i < data.byteLength) {
169 result.compatibleBrands.push(parseType(data.subarray(i, i + 4)));
170 i += 4;
171 }
172 return result;
173 },
174 dinf: function(data) {
175 return {
176 boxes: videojs.inspectMp4(data)
177 };
178 },
179 dref: function(data) {
180 return {
181 version: data[0],
182 flags: new Uint8Array(data.subarray(1, 4)),
183 dataReferences: videojs.inspectMp4(data.subarray(8))
184 };
185 },
186 hdlr: function(data) {
187 var
188 view = new DataView(data.buffer, data.byteOffset, data.byteLength),
189 language,
190 result = {
191 version: view.getUint8(0),
192 flags: new Uint8Array(data.subarray(1, 4)),
193 handlerType: parseType(data.subarray(8, 12)),
194 name: ''
195 },
196 i = 8;
197
198 // parse out the name field
199 for (i = 24; i < data.byteLength; i++) {
200 if (data[i] === 0x00) {
201 // the name field is null-terminated
202 i++;
203 break;
204 }
205 result.name += String.fromCharCode(data[i]);
206 }
207 // decode UTF-8 to javascript's internal representation
208 // see http://ecmanaut.blogspot.com/2006/07/encoding-decoding-utf8-in-javascript.html
209 result.name = window.decodeURIComponent(window.escape(result.name));
210
211 return result;
212 },
213 mdat: function(data) {
214 return {
215 byteLength: data.byteLength,
216 nals: nalParse(data)
217 };
218 },
219 mdhd: function(data) {
220 var
221 view = new DataView(data.buffer, data.byteOffset, data.byteLength),
222 i = 4,
223 language,
224 result = {
225 version: view.getUint8(0),
226 flags: new Uint8Array(data.subarray(1, 4)),
227 language: ''
228 };
229 if (result.version === 1) {
230 i += 4;
231 result.creationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
232 i += 8;
233 result.modificationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
234 i += 4;
235 result.timescale = view.getUint32(i);
236 i += 8;
237 result.duration = view.getUint32(i); // truncating top 4 bytes
238 } else {
239 result.creationTime = parseMp4Date(view.getUint32(i));
240 i += 4;
241 result.modificationTime = parseMp4Date(view.getUint32(i));
242 i += 4;
243 result.timescale = view.getUint32(i);
244 i += 4;
245 result.duration = view.getUint32(i);
246 }
247 i += 4;
248 // language is stored as an ISO-639-2/T code in an array of three 5-bit fields
249 // each field is the packed difference between its ASCII value and 0x60
250 language = view.getUint16(i);
251 result.language += String.fromCharCode((language >> 10) + 0x60);
252 result.language += String.fromCharCode(((language & 0x03c0) >> 5) + 0x60);
253 result.language += String.fromCharCode((language & 0x1f) + 0x60);
254
255 return result;
256 },
257 mdia: function(data) {
258 return {
259 boxes: videojs.inspectMp4(data)
260 };
261 },
262 mfhd: function(data) {
263 return {
264 version: data[0],
265 flags: new Uint8Array(data.subarray(1, 4)),
266 sequenceNumber: (data[4] << 24) |
267 (data[5] << 16) |
268 (data[6] << 8) |
269 (data[7])
270 };
271 },
272 minf: function(data) {
273 return {
274 boxes: videojs.inspectMp4(data)
275 };
276 },
277 // codingname, not a first-class box type. stsd entries share the
278 // same format as real boxes so the parsing infrastructure can be
279 // shared
280 mp4a: function(data) {
281 var
282 view = new DataView(data.buffer, data.byteOffset, data.byteLength),
283 result = {
284 // 6 bytes reserved
285 dataReferenceIndex: view.getUint16(6),
286 // 4 + 4 bytes reserved
287 channelcount: view.getUint16(16),
288 samplesize: view.getUint16(18),
289 // 2 bytes pre_defined
290 // 2 bytes reserved
291 samplerate: view.getUint16(24) + (view.getUint16(26) / 65536)
292 };
293
294 // if there are more bytes to process, assume this is an ISO/IEC
295 // 14496-14 MP4AudioSampleEntry and parse the ESDBox
296 if (data.byteLength > 28) {
297 result.streamDescriptor = videojs.inspectMp4(data.subarray(28))[0];
298 }
299 return result;
300 },
301 moof: function(data) {
302 return {
303 boxes: videojs.inspectMp4(data)
304 };
305 },
306 moov: function(data) {
307 return {
308 boxes: videojs.inspectMp4(data)
309 };
310 },
311 mvex: function(data) {
312 return {
313 boxes: videojs.inspectMp4(data)
314 };
315 },
316 mvhd: function(data) {
317 var
318 view = new DataView(data.buffer, data.byteOffset, data.byteLength),
319 i = 4,
320 result = {
321 version: view.getUint8(0),
322 flags: new Uint8Array(data.subarray(1, 4))
323 };
324
325 if (result.version === 1) {
326 i += 4;
327 result.creationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
328 i += 8;
329 result.modificationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
330 i += 4;
331 result.timescale = view.getUint32(i);
332 i += 8
333 result.duration = view.getUint32(i); // truncating top 4 bytes
334 } else {
335 result.creationTime = parseMp4Date(view.getUint32(i));
336 i += 4;
337 result.modificationTime = parseMp4Date(view.getUint32(i));
338 i += 4;
339 result.timescale = view.getUint32(i);
340 i += 4;
341 result.duration = view.getUint32(i);
342 }
343 i += 4;
344
345 // convert fixed-point, base 16 back to a number
346 result.rate = view.getUint16(i) + (view.getUint16(i + 2) / 16);
347 i += 4;
348 result.volume = view.getUint8(i) + (view.getUint8(i + 1) / 8);
349 i += 2;
350 i += 2;
351 i += 2 * 4;
352 result.matrix = new Uint32Array(data.subarray(i, i + (9 * 4)));
353 i += 9 * 4;
354 i += 6 * 4;
355 result.nextTrackId = view.getUint32(i);
356 return result;
357 },
358 pdin: function(data) {
359 var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
360 return {
361 version: view.getUint8(0),
362 flags: new Uint8Array(data.subarray(1, 4)),
363 rate: view.getUint32(4),
364 initialDelay: view.getUint32(8)
365 };
366 },
367 sdtp: function(data) {
368 var
369 result = {
370 version: data[0],
371 flags: new Uint8Array(data.subarray(1, 4)),
372 samples: []
373 }, i;
374
375 for (i = 4; i < data.byteLength; i++) {
376 result.samples.push({
377 dependsOn: (data[i] & 0x30) >> 4,
378 isDependedOn: (data[i] & 0x0c) >> 2,
379 hasRedundancy: data[i] & 0x03
380 });
381 }
382 return result;
383 },
384 sidx: function(data) {
385 var view = new DataView(data.buffer, data.byteOffset, data.byteLength),
386 result = {
387 version: data[0],
388 flags: new Uint8Array(data.subarray(1, 4)),
389 references: [],
390 referenceId: view.getUint32(4),
391 timescale: view.getUint32(8),
392 earliestPresentationTime: view.getUint32(12),
393 firstOffset: view.getUint32(16)
394 },
395 referenceCount = view.getUint16(22),
396 i;
397
398 for (i = 24; referenceCount; i += 12, referenceCount-- ) {
399 result.references.push({
400 referenceType: (data[i] & 0x80) >>> 7,
401 referencedSize: view.getUint32(i) & 0x7FFFFFFF,
402 subsegmentDuration: view.getUint32(i + 4),
403 startsWithSap: !!(data[i + 8] & 0x80),
404 sapType: (data[i + 8] & 0x70) >>> 4,
405 sapDeltaTime: view.getUint32(i + 8) & 0x0FFFFFFF
406 });
407 }
408
409 return result;
410 },
411 smhd: function(data) {
412 return {
413 version: data[0],
414 flags: new Uint8Array(data.subarray(1, 4)),
415 balance: data[4] + (data[5] / 256)
416 };
417 },
418 stbl: function(data) {
419 return {
420 boxes: videojs.inspectMp4(data)
421 };
422 },
423 stco: function(data) {
424 var
425 view = new DataView(data.buffer, data.byteOffset, data.byteLength),
426 result = {
427 version: data[0],
428 flags: new Uint8Array(data.subarray(1, 4)),
429 chunkOffsets: []
430 },
431 entryCount = view.getUint32(4),
432 i;
433 for (i = 8; entryCount; i += 4, entryCount--) {
434 result.chunkOffsets.push(view.getUint32(i));
435 }
436 return result;
437 },
438 stsc: function(data) {
439 var
440 view = new DataView(data.buffer, data.byteOffset, data.byteLength),
441 entryCount = view.getUint32(4),
442 result = {
443 version: data[0],
444 flags: new Uint8Array(data.subarray(1, 4)),
445 sampleToChunks: []
446 },
447 i;
448 for (i = 8; entryCount; i += 12, entryCount--) {
449 result.sampleToChunks.push({
450 firstChunk: view.getUint32(i),
451 samplesPerChunk: view.getUint32(i + 4),
452 sampleDescriptionIndex: view.getUint32(i + 8)
453 });
454 }
455 return result;
456 },
457 stsd: function(data) {
458 return {
459 version: data[0],
460 flags: new Uint8Array(data.subarray(1, 4)),
461 sampleDescriptions: videojs.inspectMp4(data.subarray(8))
462 };
463 },
464 stsz: function(data) {
465 var
466 view = new DataView(data.buffer, data.byteOffset, data.byteLength),
467 result = {
468 version: data[0],
469 flags: new Uint8Array(data.subarray(1, 4)),
470 sampleSize: view.getUint32(4),
471 entries: []
472 },
473 i;
474 for (i = 12; i < data.byteLength; i += 4) {
475 result.entries.push(view.getUint32(i));
476 }
477 return result;
478 },
479 stts: function(data) {
480 var
481 view = new DataView(data.buffer, data.byteOffset, data.byteLength),
482 result = {
483 version: data[0],
484 flags: new Uint8Array(data.subarray(1, 4)),
485 timeToSamples: []
486 },
487 entryCount = view.getUint32(4),
488 i;
489
490 for (i = 8; entryCount; i += 8, entryCount--) {
491 result.timeToSamples.push({
492 sampleCount: view.getUint32(i),
493 sampleDelta: view.getUint32(i + 4)
494 });
495 }
496 return result;
497 },
498 styp: function(data) {
499 return parse.ftyp(data);
500 },
501 tfdt: function(data) {
502 return {
503 version: data[0],
504 flags: new Uint8Array(data.subarray(1, 4)),
505 baseMediaDecodeTime: data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7]
506 };
507 },
508 tfhd: function(data) {
509 var
510 view = new DataView(data.buffer, data.byteOffset, data.byteLength),
511 result = {
512 version: data[0],
513 flags: new Uint8Array(data.subarray(1, 4)),
514 trackId: view.getUint32(4)
515 },
516 baseDataOffsetPresent = result.flags[2] & 0x01,
517 sampleDescriptionIndexPresent = result.flags[2] & 0x02,
518 defaultSampleDurationPresent = result.flags[2] & 0x08,
519 defaultSampleSizePresent = result.flags[2] & 0x10,
520 defaultSampleFlagsPresent = result.flags[2] & 0x20,
521 i;
522
523 i = 8;
524 if (baseDataOffsetPresent) {
525 i += 4; // truncate top 4 bytes
526 result.baseDataOffset = view.getUint32(12);
527 i += 4;
528 }
529 if (sampleDescriptionIndexPresent) {
530 result.sampleDescriptionIndex = view.getUint32(i);
531 i += 4;
532 }
533 if (defaultSampleDurationPresent) {
534 result.defaultSampleDuration = view.getUint32(i);
535 i += 4;
536 }
537 if (defaultSampleSizePresent) {
538 result.defaultSampleSize = view.getUint32(i);
539 i += 4;
540 }
541 if (defaultSampleFlagsPresent) {
542 result.defaultSampleFlags = view.getUint32(i);
543 }
544 return result;
545 },
546 tkhd: function(data) {
547 var
548 view = new DataView(data.buffer, data.byteOffset, data.byteLength),
549 i = 4,
550 result = {
551 version: view.getUint8(0),
552 flags: new Uint8Array(data.subarray(1, 4)),
553 };
554 if (result.version === 1) {
555 i += 4;
556 result.creationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
557 i += 8;
558 result.modificationTime = parseMp4Date(view.getUint32(i)); // truncating top 4 bytes
559 i += 4;
560 result.trackId = view.getUint32(i);
561 i += 4;
562 i += 8;
563 result.duration = view.getUint32(i); // truncating top 4 bytes
564 } else {
565 result.creationTime = parseMp4Date(view.getUint32(i));
566 i += 4;
567 result.modificationTime = parseMp4Date(view.getUint32(i));
568 i += 4;
569 result.trackId = view.getUint32(i);
570 i += 4;
571 i += 4;
572 result.duration = view.getUint32(i);
573 }
574 i += 4;
575 i += 2 * 4;
576 result.layer = view.getUint16(i);
577 i += 2;
578 result.alternateGroup = view.getUint16(i);
579 i += 2;
580 // convert fixed-point, base 16 back to a number
581 result.volume = view.getUint8(i) + (view.getUint8(i + 1) / 8);
582 i += 2;
583 i += 2;
584 result.matrix = new Uint32Array(data.subarray(i, i + (9 * 4)));
585 i += 9 * 4;
586 result.width = view.getUint16(i) + (view.getUint16(i + 2) / 16);
587 i += 4;
588 result.height = view.getUint16(i) + (view.getUint16(i + 2) / 16);
589 return result;
590 },
591 traf: function(data) {
592 return {
593 boxes: videojs.inspectMp4(data)
594 };
595 },
596 trak: function(data) {
597 return {
598 boxes: videojs.inspectMp4(data)
599 };
600 },
601 trex: function(data) {
602 var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
603 return {
604 version: data[0],
605 flags: new Uint8Array(data.subarray(1, 4)),
606 trackId: view.getUint32(4),
607 defaultSampleDescriptionIndex: view.getUint32(8),
608 defaultSampleDuration: view.getUint32(12),
609 defaultSampleSize: view.getUint32(16),
610 sampleDependsOn: data[20] & 0x03,
611 sampleIsDependedOn: (data[21] & 0xc0) >> 6,
612 sampleHasRedundancy: (data[21] & 0x30) >> 4,
613 samplePaddingValue: (data[21] & 0x0e) >> 1,
614 sampleIsDifferenceSample: !!(data[21] & 0x01),
615 sampleDegradationPriority: view.getUint16(22)
616 };
617 },
618 trun: function(data) {
619 var
620 result = {
621 version: data[0],
622 flags: new Uint8Array(data.subarray(1, 4)),
623 samples: []
624 },
625 view = new DataView(data.buffer, data.byteOffset, data.byteLength),
626 dataOffsetPresent = result.flags[2] & 0x01,
627 firstSampleFlagsPresent = result.flags[2] & 0x04,
628 sampleDurationPresent = result.flags[1] & 0x01,
629 sampleSizePresent = result.flags[1] & 0x02,
630 sampleFlagsPresent = result.flags[1] & 0x04,
631 sampleCompositionTimeOffsetPresent = result.flags[1] & 0x08,
632 sampleCount = view.getUint32(4),
633 offset = 8,
634 sample;
635
636 if (dataOffsetPresent) {
637 result.dataOffset = view.getUint32(offset);
638 offset += 4;
639 }
640 if (firstSampleFlagsPresent && sampleCount) {
641 sample = {
642 flags: parseSampleFlags(data.subarray(offset, offset + 4))
643 };
644 offset += 4;
645 if (sampleDurationPresent) {
646 sample.duration = view.getUint32(offset);
647 offset += 4;
648 }
649 if (sampleSizePresent) {
650 sample.size = view.getUint32(offset);
651 offset += 4;
652 }
653 if (sampleCompositionTimeOffsetPresent) {
654 sample.compositionTimeOffset = view.getUint32(offset);
655 offset += 4;
656 }
657 result.samples.push(sample);
658 sampleCount--;
659 };
660 while (sampleCount--) {
661 sample = {};
662 if (sampleDurationPresent) {
663 sample.duration = view.getUint32(offset);
664 offset += 4;
665 }
666 if (sampleSizePresent) {
667 sample.size = view.getUint32(offset);
668 offset += 4;
669 }
670 if (sampleFlagsPresent) {
671 sample.flags = parseSampleFlags(data.subarray(offset, offset + 4));
672 offset += 4;
673 }
674 if (sampleCompositionTimeOffsetPresent) {
675 sample.compositionTimeOffset = view.getUint32(offset);
676 offset += 4;
677 }
678 result.samples.push(sample);
679 }
680 return result;
681 },
682 'url ': function(data) {
683 return {
684 version: data[0],
685 flags: new Uint8Array(data.subarray(1, 4))
686 };
687 },
688 vmhd: function(data) {
689 var view = new DataView(data.buffer, data.byteOffset, data.byteLength);
690 return {
691 version: data[0],
692 flags: new Uint8Array(data.subarray(1, 4)),
693 graphicsmode: view.getUint16(4),
694 opcolor: new Uint16Array([view.getUint16(6),
695 view.getUint16(8),
696 view.getUint16(10)])
697 };
698 }
699 };
700
701 /**
702 * Return a javascript array of box objects parsed from an ISO base
703 * media file.
704 * @param data {Uint8Array} the binary data of the media to be inspected
705 * @return {array} a javascript array of potentially nested box objects
706 */
707 videojs.inspectMp4 = function(data) {
708 var
709 i = 0,
710 result = [],
711 view = new DataView(data.buffer, data.byteOffset, data.byteLength),
712 size,
713 type,
714 end,
715 box;
716
717 while (i < data.byteLength) {
718 // parse box data
719 size = view.getUint32(i),
720 type = parseType(data.subarray(i + 4, i + 8));
721 end = size > 1 ? i + size : data.byteLength;
722
723 // parse type-specific data
724 box = (parse[type] || function(data) {
725 return {
726 data: data
727 };
728 })(data.subarray(i + 8, end));
729 box.size = size;
730 box.type = type;
731
732 // store this box and move to the next
733 result.push(box);
734 i = end;
735 }
736 return result;
737 };
738
739 /**
740 * Returns a textual representation of the javascript represtentation
741 * of an MP4 file. You can use it as an alternative to
742 * JSON.stringify() to compare inspected MP4s.
743 * @param inspectedMp4 {array} the parsed array of boxes in an MP4
744 * file
745 * @param depth {number} (optional) the number of ancestor boxes of
746 * the elements of inspectedMp4. Assumed to be zero if unspecified.
747 * @return {string} a text representation of the parsed MP4
748 */
749 videojs.textifyMp4 = function(inspectedMp4, depth) {
750 var indent;
751 depth = depth || 0;
752 indent = Array(depth * 2 + 1).join(' ');
753
754 // iterate over all the boxes
755 return inspectedMp4.map(function(box, index) {
756
757 // list the box type first at the current indentation level
758 return indent + box.type + '\n' +
759
760 // the type is already included and handle child boxes separately
761 Object.keys(box).filter(function(key) {
762 return key !== 'type' && key !== 'boxes';
763
764 // output all the box properties
765 }).map(function(key) {
766 var prefix = indent + ' ' + key + ': ',
767 value = box[key];
768
769 // print out raw bytes as hexademical
770 if (value instanceof Uint8Array || value instanceof Uint32Array) {
771 var bytes = Array.prototype.slice.call(new Uint8Array(value.buffer, value.byteOffset, value.byteLength))
772 .map(function(byte) {
773 return ' ' + ('00' + byte.toString(16)).slice(-2);
774 }).join('').match(/.{1,24}/g);
775 if (!bytes) {
776 return prefix + '<>';
777 }
778 if (bytes.length === 1) {
779 return prefix + '<' + bytes.join('').slice(1) + '>';
780 }
781 return prefix + '<\n' + bytes.map(function(line) {
782 return indent + ' ' + line;
783 }).join('\n') + '\n' + indent + ' >';
784 }
785
786 // stringify generic objects
787 return prefix +
788 JSON.stringify(value, null, 2)
789 .split('\n').map(function(line, index) {
790 if (index === 0) {
791 return line;
792 }
793 return indent + ' ' + line;
794 }).join('\n');
795 }).join('\n') +
796
797 // recursively textify the child boxes
798 (box.boxes ? '\n' + videojs.textifyMp4(box.boxes, depth + 1) : '');
799 }).join('\n');
800 };
801
802 })(window, window.videojs);
1 /* Modernizr 2.6.2 (Custom Build) | MIT & BSD
2 * 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
3 */
4 ;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))};
1 // a minimal set of boxes assembled to form a valid BMFF media segment
2 var mfhd = [
3 0, 0, 0, 16, 109, 102, 104, 100, 0, 0, 0, 0, 0, 0, 0, 1
4 ]; // sequence number 1
5 var tfhd = [
6 0, 0, 0, 16, 116, 102, 104, 100, 0, 2, 0, 0, 0, 0, 0, 1
7 ]; // track id 1
8 var tfdt = [
9 0, 0, 0, 16, 116, 102, 100, 116, 0, 0, 0, 0, 0, 0, 0, 0
10 ];
11 var trun = [
12 0, 0, 0, 124, 116, 114, 117, 110, 0, 0, 2, 5, 0, 0, 0, 25, 0, 0, 0,
13 196, 0, 0, 0, 0, 0, 0, 12, 219, 0, 0, 0, 183, 0, 0, 0, 172, 0, 0, 0,
14 182, 0, 0, 0, 183, 0, 0, 0, 198, 0, 0, 0, 146, 0, 0, 0, 176, 0, 0,
15 0, 190, 0, 0, 0, 169, 0, 0, 0, 243, 0, 0, 0, 174, 0, 0, 0, 131, 0,
16 0, 0, 161, 0, 0, 0, 144, 0, 0, 0, 163, 0, 0, 0, 128, 0, 0, 0, 174,
17 0, 0, 0, 194, 0, 0, 0, 133, 0, 0, 0, 183, 0, 0, 0, 154, 0, 0, 0,
18 150, 0, 0, 0, 169, 0, 0, 0, 157
19 ];
20 var mdat = [
21 0, 0, 28, 188, 109, 100, 97, 116, 0, 0, 2, 151, 6, 5, 255, 255, 147,
22 220, 69, 233, 189, 230, 217, 72, 183, 150, 44, 216, 32, 217, 35, 238,
23 239, 120, 50, 54, 52, 32, 45, 32, 99, 111, 114, 101, 32, 49, 49, 56,
24 32, 114, 50, 48, 56, 53, 32, 56, 97, 54, 50, 56, 51, 53, 32, 45, 32,
25 72, 46, 50, 54, 52, 47, 77, 80, 69, 71, 45, 52, 32, 65, 86, 67, 32,
26 99, 111, 100, 101, 99, 32, 45, 32, 67, 111, 112, 121, 108, 101, 102,
27 116, 32, 50, 48, 48, 51, 45, 50, 48, 49, 49, 32, 45, 32, 104, 116,
28 116, 112, 58, 47, 47, 119, 119, 119, 46, 118, 105, 100, 101, 111, 108,
29 97, 110, 46, 111, 114, 103, 47, 120, 50, 54, 52, 46, 104, 116, 109,
30 108, 32, 45, 32, 111, 112, 116, 105, 111, 110, 115, 58, 32, 99, 97,
31 98, 97, 99, 61, 48, 32, 114, 101, 102, 61, 51, 32, 100, 101, 98, 108,
32 111, 99, 107, 61, 49, 58, 48, 58, 48, 32, 97, 110, 97, 108, 121, 115,
33 101, 61, 48, 120, 49, 58, 48, 120, 49, 49, 49, 32, 109, 101, 61, 104,
34 101, 120, 32, 115, 117, 98, 109, 101, 61, 55, 32, 112, 115, 121, 61,
35 49, 32, 112, 115, 121, 95, 114, 100, 61, 49, 46, 48, 48, 58, 48, 46,
36 48, 48, 32, 109, 105, 120, 101, 100, 95, 114, 101, 102, 61, 49, 32,
37 109, 101, 95, 114, 97, 110, 103, 101, 61, 49, 54, 32, 99, 104, 114,
38 111, 109, 97, 95, 109, 101, 61, 49, 32, 116, 114, 101, 108, 108, 105,
39 115, 61, 49, 32, 56, 120, 56, 100, 99, 116, 61, 48, 32, 99, 113, 109,
40 61, 48, 32, 100, 101, 97, 100, 122, 111, 110, 101, 61, 50, 49, 44, 49,
41 49, 32, 102, 97, 115, 116, 95, 112, 115, 107, 105, 112, 61, 49, 32,
42 99, 104, 114, 111, 109, 97, 95, 113, 112, 95, 111, 102, 102, 115, 101,
43 116, 61, 45, 50, 32, 116, 104, 114, 101, 97, 100, 115, 61, 51, 32,
44 115, 108, 105, 99, 101, 100, 95, 116, 104, 114, 101, 97, 100, 115, 61,
45 48, 32, 110, 114, 61, 48, 32, 100, 101, 99, 105, 109, 97, 116, 101,
46 61, 49, 32, 105, 110, 116, 101, 114, 108, 97, 99, 101, 100, 61, 48,
47 32, 98, 108, 117, 114, 97, 121, 95, 99, 111, 109, 112, 97, 116, 61,
48 48, 32, 99, 111, 110, 115, 116, 114, 97, 105, 110, 101, 100, 95, 105,
49 110, 116, 114, 97, 61, 48, 32, 98, 102, 114, 97, 109, 101, 115, 61,
50 48, 32, 119, 101, 105, 103, 104, 116, 112, 61, 48, 32, 107, 101, 121,
51 105, 110, 116, 61, 50, 53, 32, 107, 101, 121, 105, 110, 116, 95, 109,
52 105, 110, 61, 49, 51, 32, 115, 99, 101, 110, 101, 99, 117, 116, 61,
53 52, 48, 32, 105, 110, 116, 114, 97, 95, 114, 101, 102, 114, 101, 115,
54 104, 61, 48, 32, 114, 99, 95, 108, 111, 111, 107, 97, 104, 101, 97,
55 100, 61, 52, 48, 32, 114, 99, 61, 99, 98, 114, 32, 109, 98, 116, 114,
56 101, 101, 61, 49, 32, 98, 105, 116, 114, 97, 116, 101, 61, 52, 56, 32,
57 114, 97, 116, 101, 116, 111, 108, 61, 49, 46, 48, 32, 113, 99, 111,
58 109, 112, 61, 48, 46, 54, 48, 32, 113, 112, 109, 105, 110, 61, 48, 32,
59 113, 112, 109, 97, 120, 61, 54, 57, 32, 113, 112, 115, 116, 101, 112,
60 61, 52, 32, 118, 98, 118, 95, 109, 97, 120, 114, 97, 116, 101, 61, 52,
61 56, 32, 118, 98, 118, 95, 98, 117, 102, 115, 105, 122, 101, 61, 50,
62 48, 48, 48, 32, 110, 97, 108, 95, 104, 114, 100, 61, 110, 111, 110,
63 101, 32, 105, 112, 95, 114, 97, 116, 105, 111, 61, 49, 46, 52, 48, 32,
64 97, 113, 61, 49, 58, 49, 46, 48, 48, 0, 128, 0, 0, 10, 60, 101, 136,
65 132, 25, 201, 6, 34, 128, 0, 195, 76, 156, 156, 156, 156, 156, 156,
66 156, 156, 156, 156, 156, 156, 156, 156, 156, 156, 156, 156, 157, 117,
67 215, 93, 117, 215, 93, 117, 215, 93, 117, 215, 93, 117, 215, 93, 117,
68 215, 93, 117, 215, 93, 117, 215, 95, 255, 222, 238, 80, 168, 38, 192,
69 35, 16, 144, 238, 68, 216, 105, 218, 55, 168, 0, 162, 196, 199, 82,
70 63, 12, 225, 73, 161, 167, 199, 63, 255, 119, 220, 120, 86, 40, 104,
71 195, 139, 254, 42, 238, 91, 250, 153, 255, 255, 85, 225, 81, 64, 72,
72 185, 204, 154, 55, 71, 225, 39, 244, 108, 42, 251, 175, 255, 254, 171,
73 134, 8, 60, 160, 192, 171, 24, 186, 235, 174, 186, 235, 174, 186, 235,
74 174, 186, 235, 254, 68, 82, 105, 120, 193, 48, 3, 216, 90, 173, 113,
75 18, 222, 1, 212, 208, 33, 57, 132, 214, 13, 165, 0, 49, 178, 166, 201,
76 246, 232, 31, 159, 114, 199, 225, 248, 120, 43, 94, 172, 137, 32, 75,
77 180, 11, 247, 223, 215, 60, 255, 225, 240, 208, 6, 179, 209, 69, 200,
78 98, 8, 78, 127, 56, 167, 193, 4, 10, 86, 12, 135, 128, 50, 171, 244,
79 132, 239, 23, 93, 117, 215, 93, 117, 255, 255, 174, 21, 39, 0, 34, 47,
80 146, 109, 95, 99, 121, 145, 164, 252, 245, 215, 93, 127, 218, 223,
81 102, 56, 80, 108, 162, 144, 4, 45, 36, 192, 200, 180, 217, 171, 107,
82 207, 0, 5, 118, 124, 132, 86, 222, 3, 228, 95, 255, 135, 132, 133, 95,
83 63, 201, 94, 237, 216, 117, 215, 93, 253, 52, 167, 211, 72, 160, 150,
84 220, 152, 252, 49, 23, 119, 68, 166, 120, 38, 182, 158, 46, 186, 235,
85 174, 186, 235, 237, 7, 14, 24, 92, 172, 85, 41, 52, 237, 176, 36, 7,
86 36, 243, 235, 141, 132, 30, 250, 171, 62, 170, 183, 14, 146, 109, 247,
87 254, 0, 216, 166, 193, 132, 65, 114, 198, 145, 17, 236, 4, 181, 80,
88 52, 183, 125, 238, 240, 182, 55, 155, 38, 215, 252, 231, 217, 97, 81,
89 252, 12, 81, 121, 32, 222, 64, 100, 123, 224, 117, 17, 49, 226, 133,
90 65, 77, 215, 128, 8, 79, 114, 61, 95, 127, 46, 199, 213, 220, 111,
91 165, 80, 103, 234, 179, 148, 4, 110, 94, 206, 245, 255, 196, 106, 207,
92 10, 148, 0, 134, 53, 178, 28, 75, 239, 243, 54, 131, 72, 65, 84, 194,
93 41, 207, 235, 70, 173, 59, 32, 134, 180, 210, 191, 128, 35, 215, 219,
94 55, 10, 106, 126, 109, 51, 86, 17, 157, 104, 20, 103, 247, 214, 44,
95 196, 132, 32, 28, 171, 255, 255, 206, 255, 213, 147, 97, 133, 76, 214,
96 102, 231, 231, 241, 72, 156, 56, 161, 87, 89, 7, 57, 85, 100, 65, 255,
97 32, 110, 253, 1, 25, 209, 163, 106, 105, 49, 152, 90, 243, 182, 81,
98 195, 130, 157, 24, 201, 118, 229, 255, 196, 106, 39, 10, 138, 240, 68,
99 214, 2, 163, 144, 173, 82, 203, 73, 174, 70, 10, 101, 231, 250, 63,
100 97, 37, 178, 3, 121, 219, 125, 96, 145, 38, 57, 34, 37, 63, 255, 240,
101 160, 68, 109, 118, 202, 61, 227, 203, 210, 102, 142, 153, 173, 76,
102 111, 174, 186, 235, 254, 31, 252, 40, 104, 20, 109, 73, 102, 245, 50,
103 154, 153, 215, 235, 174, 186, 235, 174, 190, 19, 221, 127, 225, 35,
104 195, 124, 193, 179, 16, 239, 119, 251, 25, 126, 181, 143, 140, 216,
105 122, 35, 194, 225, 107, 76, 143, 235, 124, 22, 30, 202, 27, 52, 188,
106 80, 232, 136, 132, 137, 94, 110, 191, 59, 210, 95, 223, 177, 114, 10,
107 191, 252, 37, 187, 154, 7, 140, 177, 57, 255, 61, 186, 4, 194, 206,
108 129, 208, 186, 187, 46, 52, 63, 207, 183, 245, 54, 237, 191, 235, 221,
109 35, 76, 197, 84, 140, 63, 186, 52, 41, 129, 82, 166, 149, 255, 25,
110 149, 23, 231, 53, 175, 219, 243, 232, 67, 11, 191, 252, 37, 252, 130,
111 111, 2, 166, 217, 10, 0, 94, 211, 194, 101, 84, 68, 17, 31, 35, 242,
112 222, 132, 139, 208, 130, 103, 95, 84, 113, 195, 68, 84, 93, 169, 150,
113 64, 204, 207, 71, 253, 211, 174, 141, 133, 58, 135, 205, 35, 27, 187,
114 30, 176, 178, 60, 125, 14, 145, 16, 145, 43, 206, 175, 130, 255, 194,
115 66, 51, 199, 59, 5, 57, 117, 243, 84, 108, 168, 126, 150, 206, 255,
116 93, 135, 142, 157, 191, 20, 156, 122, 213, 80, 172, 179, 98, 143, 80,
117 240, 154, 158, 36, 146, 159, 145, 86, 194, 237, 251, 110, 225, 169,
118 127, 194, 66, 29, 151, 107, 53, 213, 93, 120, 115, 180, 1, 184, 180,
119 21, 41, 249, 151, 77, 200, 172, 235, 159, 179, 35, 35, 123, 180, 189,
120 31, 190, 255, 79, 130, 82, 20, 8, 128, 212, 22, 177, 22, 61, 232, 247,
121 30, 120, 3, 79, 232, 150, 179, 54, 225, 239, 11, 174, 186, 235, 255,
122 183, 177, 182, 20, 52, 2, 228, 166, 105, 102, 193, 76, 2, 71, 210, 89,
123 246, 226, 238, 235, 245, 215, 93, 117, 215, 93, 119, 223, 125, 255,
124 210, 31, 240, 248, 94, 2, 25, 231, 73, 255, 255, 47, 210, 129, 224,
125 115, 207, 22, 18, 209, 12, 2, 92, 233, 134, 237, 191, 192, 43, 214,
126 64, 109, 19, 255, 8, 48, 37, 118, 186, 235, 255, 255, 224, 132, 188,
127 0, 126, 81, 156, 120, 8, 99, 90, 123, 107, 101, 41, 126, 19, 52, 38,
128 229, 238, 98, 61, 176, 47, 32, 11, 119, 32, 54, 137, 225, 215, 93,
129 117, 215, 93, 117, 215, 93, 117, 215, 95, 234, 112, 255, 135, 197,
130 224, 7, 75, 35, 133, 156, 62, 179, 243, 170, 233, 134, 170, 56, 177,
131 31, 4, 254, 52, 151, 134, 30, 112, 37, 243, 126, 6, 143, 41, 153, 143,
132 255, 250, 172, 84, 120, 32, 142, 236, 197, 21, 248, 182, 229, 187,
133 255, 254, 239, 194, 176, 33, 18, 156, 200, 41, 70, 241, 254, 52, 125,
134 104, 216, 72, 212, 245, 255, 238, 69, 191, 248, 75, 128, 27, 108, 204,
135 4, 130, 175, 242, 117, 231, 180, 128, 23, 218, 89, 154, 72, 63, 147,
136 174, 186, 235, 174, 186, 235, 175, 255, 244, 30, 20, 62, 0, 9, 26,
137 140, 152, 187, 140, 173, 72, 149, 57, 9, 16, 162, 250, 87, 66, 43, 56,
138 215, 99, 158, 154, 53, 171, 127, 255, 219, 76, 111, 60, 203, 19, 244,
139 247, 127, 143, 144, 0, 152, 42, 176, 51, 6, 179, 229, 217, 225, 160,
140 149, 17, 207, 16, 87, 78, 248, 9, 225, 95, 163, 21, 113, 70, 213, 129,
141 40, 107, 116, 216, 109, 178, 200, 144, 31, 117, 163, 49, 39, 243, 81,
142 141, 245, 71, 73, 230, 249, 246, 126, 239, 209, 240, 255, 168, 97,
143 180, 45, 192, 178, 117, 74, 198, 219, 174, 10, 140, 240, 22, 146, 16,
144 77, 184, 39, 91, 18, 251, 142, 15, 32, 71, 84, 247, 230, 161, 195,
145 240, 0, 168, 132, 78, 73, 49, 199, 205, 5, 192, 67, 254, 193, 46, 21,
146 175, 76, 229, 19, 0, 26, 100, 168, 43, 230, 37, 9, 66, 51, 3, 90, 66,
147 243, 186, 94, 187, 232, 26, 151, 217, 89, 85, 99, 255, 86, 166, 46,
148 38, 206, 80, 185, 255, 88, 94, 45, 103, 228, 8, 76, 122, 227, 98, 250,
149 34, 184, 114, 238, 200, 171, 26, 49, 98, 139, 255, 103, 87, 173, 64,
150 119, 90, 66, 176, 207, 147, 189, 132, 144, 105, 120, 183, 18, 140,
151 253, 105, 131, 221, 240, 202, 191, 246, 169, 111, 254, 223, 160, 138,
152 73, 34, 149, 138, 238, 62, 184, 175, 27, 224, 184, 253, 44, 134, 172,
153 190, 120, 26, 228, 92, 187, 202, 254, 192, 220, 79, 205, 151, 190,
154 216, 230, 3, 7, 219, 166, 104, 91, 206, 38, 255, 233, 85, 85, 143,
155 136, 231, 102, 196, 227, 158, 181, 26, 17, 82, 208, 205, 91, 9, 69,
156 255, 185, 238, 98, 122, 133, 85, 19, 132, 125, 95, 251, 34, 196, 231,
157 154, 186, 208, 233, 249, 168, 247, 255, 127, 106, 63, 32, 51, 67, 163,
158 1, 190, 176, 66, 92, 198, 212, 84, 186, 39, 254, 119, 31, 56, 251,
159 223, 4, 166, 108, 149, 89, 78, 236, 0, 180, 201, 156, 173, 94, 153,
160 111, 254, 252, 17, 108, 29, 69, 70, 221, 79, 156, 2, 201, 254, 41,
161 118, 45, 169, 255, 216, 93, 195, 83, 174, 18, 107, 125, 167, 192, 226,
162 217, 88, 184, 46, 127, 238, 193, 177, 38, 168, 69, 139, 159, 223, 114,
163 99, 37, 109, 32, 53, 38, 184, 207, 253, 205, 158, 205, 231, 141, 74,
164 171, 76, 139, 140, 193, 242, 246, 180, 56, 178, 144, 18, 162, 41, 16,
165 43, 255, 155, 120, 145, 25, 202, 221, 219, 239, 184, 14, 234, 58, 129,
166 128, 223, 214, 124, 110, 155, 150, 205, 118, 175, 251, 192, 14, 148,
167 58, 73, 44, 180, 15, 29, 201, 184, 122, 56, 103, 137, 231, 0, 45, 144,
168 127, 145, 96, 157, 75, 37, 3, 180, 104, 193, 173, 166, 114, 208, 214,
169 159, 223, 214, 123, 73, 163, 0, 5, 117, 61, 156, 14, 52, 198, 157,
170 135, 213, 215, 211, 130, 136, 185, 173, 120, 160, 245, 142, 245, 169,
171 208, 22, 106, 52, 233, 254, 183, 121, 49, 53, 105, 86, 96, 66, 175,
172 35, 148, 64, 126, 18, 240, 88, 215, 207, 255, 251, 73, 32, 112, 196,
173 160, 73, 215, 74, 223, 186, 240, 6, 64, 7, 80, 27, 252, 60, 91, 228,
174 206, 175, 251, 35, 194, 137, 161, 5, 148, 166, 55, 254, 195, 187, 101,
175 2, 100, 39, 246, 127, 205, 191, 128, 128, 243, 166, 153, 163, 24, 82,
176 210, 161, 152, 149, 209, 204, 101, 111, 187, 252, 105, 224, 236, 86,
177 165, 56, 18, 115, 255, 255, 19, 11, 171, 107, 34, 156, 76, 111, 152,
178 1, 108, 42, 188, 73, 174, 110, 189, 157, 143, 172, 198, 139, 246, 182,
179 99, 88, 234, 237, 171, 127, 161, 133, 33, 241, 63, 39, 31, 194, 101,
180 244, 58, 204, 82, 50, 28, 62, 166, 246, 128, 220, 58, 42, 111, 17, 86,
181 140, 183, 219, 133, 4, 72, 74, 141, 148, 103, 54, 62, 213, 36, 74, 68,
182 137, 87, 255, 208, 253, 117, 215, 93, 117, 215, 93, 117, 255, 255, 8,
183 66, 135, 225, 191, 64, 31, 51, 34, 6, 19, 247, 9, 99, 159, 227, 240,
184 8, 11, 56, 216, 140, 0, 171, 94, 141, 16, 247, 76, 216, 98, 241, 23,
185 172, 125, 190, 96, 61, 54, 112, 80, 180, 192, 176, 239, 166, 82, 195,
186 32, 168, 103, 107, 73, 19, 16, 119, 126, 122, 54, 227, 251, 62, 187,
187 145, 247, 164, 96, 213, 91, 12, 71, 194, 230, 205, 4, 77, 193, 43, 38,
188 167, 48, 98, 200, 67, 194, 185, 211, 85, 240, 127, 235, 140, 66, 34,
189 35, 190, 3, 243, 47, 131, 132, 172, 43, 63, 255, 85, 46, 12, 155, 217,
190 238, 172, 55, 73, 48, 115, 152, 251, 227, 16, 194, 71, 82, 93, 43, 20,
191 25, 8, 144, 50, 139, 168, 52, 255, 242, 75, 9, 170, 96, 249, 57, 6,
192 239, 15, 154, 49, 205, 152, 41, 18, 214, 74, 42, 204, 86, 193, 72,
193 239, 245, 120, 127, 195, 224, 24, 7, 10, 192, 157, 166, 143, 46, 49,
194 16, 103, 240, 10, 28, 252, 128, 38, 98, 217, 40, 152, 86, 240, 248,
195 65, 34, 153, 30, 175, 230, 88, 23, 251, 139, 26, 107, 20, 55, 246,
196 234, 167, 193, 135, 235, 50, 68, 92, 135, 184, 126, 0, 41, 205, 147,
197 8, 129, 210, 235, 224, 31, 154, 244, 105, 117, 255, 253, 224, 104,
198 210, 82, 92, 144, 252, 7, 24, 71, 87, 220, 90, 79, 141, 97, 177, 21,
199 191, 216, 120, 146, 204, 15, 63, 140, 157, 223, 73, 11, 152, 130, 71,
200 124, 161, 189, 225, 253, 132, 239, 73, 84, 11, 55, 230, 109, 209, 166,
201 131, 160, 248, 153, 53, 167, 193, 252, 84, 86, 133, 241, 126, 252,
202 103, 226, 229, 213, 170, 59, 131, 255, 246, 166, 96, 221, 56, 138, 82,
203 119, 81, 126, 210, 136, 228, 36, 142, 8, 148, 27, 191, 220, 34, 22,
204 228, 86, 109, 140, 200, 181, 129, 226, 39, 82, 198, 74, 115, 255, 43,
205 57, 17, 28, 243, 227, 119, 223, 73, 132, 73, 57, 22, 140, 177, 53,
206 246, 255, 143, 75, 122, 68, 86, 182, 39, 217, 212, 20, 152, 234, 175,
207 239, 251, 108, 128, 166, 114, 66, 141, 192, 137, 255, 193, 219, 140,
208 13, 81, 113, 178, 233, 155, 82, 69, 68, 113, 13, 204, 180, 31, 192,
209 12, 70, 64, 60, 68, 247, 212, 6, 130, 174, 179, 151, 13, 188, 29, 109,
210 18, 3, 119, 208, 43, 37, 115, 65, 5, 204, 144, 184, 33, 24, 69, 30,
211 254, 54, 205, 201, 63, 115, 80, 217, 127, 127, 244, 67, 49, 80, 247,
212 168, 139, 249, 83, 255, 123, 114, 1, 170, 237, 67, 99, 234, 219, 144,
213 215, 4, 18, 236, 169, 215, 251, 223, 213, 178, 113, 148, 52, 75, 191,
214 239, 242, 96, 125, 81, 77, 29, 40, 219, 89, 162, 35, 20, 198, 227,
215 120, 31, 248, 154, 52, 64, 85, 194, 230, 91, 22, 142, 6, 104, 173, 3,
216 77, 147, 26, 164, 111, 254, 42, 84, 1, 204, 72, 61, 140, 96, 5, 191,
217 5, 174, 85, 179, 228, 84, 8, 175, 2, 58, 99, 107, 14, 98, 44, 13, 255,
218 46, 67, 245, 94, 70, 125, 53, 90, 241, 209, 237, 160, 203, 225, 219,
219 3, 37, 181, 152, 21, 246, 12, 25, 243, 249, 20, 173, 224, 251, 164,
220 130, 119, 149, 151, 188, 64, 118, 134, 136, 243, 192, 253, 26, 236,
221 17, 244, 3, 6, 161, 56, 241, 241, 227, 58, 192, 119, 65, 2, 85, 69,
222 101, 194, 147, 105, 35, 212, 64, 201, 135, 43, 129, 87, 131, 254, 24,
223 153, 225, 6, 226, 250, 93, 242, 223, 246, 161, 232, 38, 235, 82, 149,
224 186, 208, 131, 255, 224, 119, 239, 223, 128, 8, 91, 254, 167, 223,
225 255, 193, 54, 146, 56, 131, 228, 158, 254, 240, 20, 219, 226, 181,
226 174, 62, 91, 97, 110, 120, 231, 111, 181, 135, 34, 0, 55, 131, 15,
227 194, 73, 33, 218, 198, 76, 123, 70, 176, 25, 60, 252, 123, 173, 180,
228 121, 252, 31, 255, 220, 98, 1, 198, 120, 0, 43, 164, 153, 155, 68, 70,
229 100, 1, 68, 194, 33, 145, 96, 191, 16, 42, 212, 112, 86, 184, 139,
230 195, 63, 221, 241, 194, 72, 146, 175, 1, 192, 89, 134, 143, 188, 80,
231 73, 161, 214, 37, 255, 238, 53, 252, 5, 209, 205, 66, 96, 119, 255, 3,
232 135, 207, 225, 15, 193, 0, 155, 16, 161, 27, 102, 216, 136, 122, 189,
233 255, 93, 117, 215, 255, 255, 194, 65, 87, 125, 223, 120, 99, 44, 240,
234 251, 44, 240, 198, 89, 225, 246, 88, 127, 99, 45, 199, 31, 255, 239,
235 15, 178, 207, 12, 101, 158, 31, 101, 158, 24, 203, 15, 195, 236, 183,
236 199, 255, 199, 225, 140, 183, 143, 255, 247, 135, 217, 103, 134, 50,
237 195, 251, 236, 183, 31, 255, 247, 134, 50, 207, 15, 178, 207, 12, 101,
238 158, 31, 101, 158, 24, 203, 64, 0, 0, 0, 179, 65, 154, 56, 59, 131,
239 107, 152, 241, 64, 0, 64, 43, 20, 0, 4, 2, 190, 82, 5, 192, 0, 247,
240 128, 198, 49, 192, 39, 2, 192, 0, 244, 164, 248, 64, 96, 240, 64, 253,
241 159, 205, 107, 125, 155, 241, 99, 57, 170, 177, 157, 93, 248, 182, 8,
242 109, 152, 126, 10, 240, 110, 177, 6, 248, 127, 240, 66, 43, 128, 99,
243 196, 97, 211, 109, 246, 255, 15, 66, 111, 243, 73, 209, 139, 47, 134,
244 99, 29, 98, 48, 1, 125, 106, 153, 161, 178, 253, 244, 246, 249, 131,
245 47, 164, 243, 35, 56, 252, 184, 75, 220, 168, 36, 246, 188, 21, 127,
246 32, 59, 136, 136, 162, 27, 251, 125, 184, 80, 240, 104, 149, 63, 146,
247 34, 151, 230, 114, 9, 227, 27, 21, 177, 0, 74, 89, 242, 49, 208, 115,
248 240, 219, 227, 245, 103, 207, 211, 115, 8, 63, 2, 158, 55, 17, 226,
249 60, 71, 136, 241, 29, 97, 10, 194, 21, 132, 43, 8, 86, 16, 196, 120,
250 143, 17, 226, 60, 0, 0, 0, 168, 65, 154, 84, 12, 224, 221, 231, 129,
251 36, 38, 44, 176, 156, 92, 6, 185, 112, 11, 92, 114, 7, 113, 149, 193,
252 72, 145, 228, 141, 60, 127, 205, 254, 21, 63, 130, 15, 192, 7, 170,
253 117, 110, 189, 221, 229, 54, 108, 180, 121, 249, 13, 255, 246, 99,
254 133, 13, 192, 72, 185, 113, 236, 236, 105, 199, 251, 34, 10, 8, 193,
255 67, 254, 41, 138, 92, 65, 225, 119, 155, 218, 91, 49, 124, 72, 107,
256 130, 57, 27, 177, 97, 254, 203, 237, 159, 136, 39, 218, 16, 75, 110,
257 35, 42, 240, 11, 80, 206, 48, 189, 1, 147, 138, 63, 9, 220, 40, 66,
258 39, 69, 239, 255, 37, 151, 128, 21, 215, 218, 239, 174, 131, 83, 97,
259 234, 147, 152, 139, 131, 185, 72, 39, 128, 140, 212, 213, 233, 155,
260 255, 252, 61, 12, 164, 199, 246, 126, 48, 158, 247, 251, 17, 144, 94,
261 14, 242, 82, 182, 121, 212, 65, 248, 25, 224, 0, 0, 0, 178, 65, 154,
262 96, 87, 7, 15, 124, 167, 192, 18, 53, 31, 218, 6, 61, 141, 142, 205,
263 92, 19, 19, 230, 174, 168, 39, 229, 227, 2, 0, 10, 183, 113, 149, 63,
264 192, 33, 216, 167, 15, 46, 9, 194, 65, 237, 121, 45, 194, 101, 248,
265 20, 66, 226, 40, 92, 212, 161, 241, 238, 98, 34, 255, 252, 60, 83, 1,
266 63, 20, 186, 196, 19, 128, 31, 102, 153, 100, 123, 224, 20, 114, 141,
267 131, 206, 60, 51, 158, 194, 203, 247, 183, 231, 102, 23, 132, 143, 61,
268 32, 3, 45, 244, 60, 16, 178, 23, 173, 139, 117, 202, 43, 115, 77, 225,
269 30, 169, 193, 69, 83, 167, 122, 34, 225, 89, 115, 17, 17, 127, 252,
270 61, 113, 245, 236, 79, 23, 228, 40, 6, 169, 7, 167, 17, 57, 117, 35,
271 16, 11, 22, 133, 167, 87, 140, 121, 239, 247, 190, 205, 100, 221, 143,
272 232, 90, 43, 151, 59, 7, 215, 105, 70, 45, 255, 62, 105, 143, 190, 3,
273 53, 194, 24, 142, 34, 0, 0, 0, 179, 65, 154, 128, 71, 2, 57, 249, 75,
274 248, 177, 32, 81, 128, 135, 144, 161, 22, 7, 178, 4, 222, 253, 152,
275 69, 227, 177, 153, 255, 238, 253, 116, 5, 64, 33, 150, 0, 44, 80, 105,
276 154, 147, 173, 10, 20, 107, 95, 176, 88, 0, 45, 226, 145, 39, 130,
277 197, 244, 232, 73, 63, 111, 242, 226, 114, 1, 36, 194, 184, 8, 186,
278 247, 21, 130, 0, 32, 141, 65, 35, 67, 126, 2, 249, 243, 225, 57, 91,
279 87, 247, 132, 123, 173, 150, 190, 111, 183, 137, 39, 167, 152, 115,
280 248, 140, 4, 122, 245, 231, 190, 135, 220, 13, 90, 146, 206, 43, 92,
281 154, 19, 138, 155, 201, 89, 120, 3, 24, 115, 44, 221, 231, 129, 54,
282 141, 235, 85, 175, 136, 184, 70, 93, 226, 96, 129, 188, 33, 86, 192,
283 236, 175, 218, 229, 255, 204, 4, 108, 79, 17, 228, 8, 194, 47, 4, 57,
284 43, 99, 132, 137, 155, 13, 255, 26, 175, 110, 223, 191, 40, 142, 179,
285 1, 2, 6, 136, 0, 0, 0, 194, 65, 154, 160, 71, 6, 190, 44, 94, 96, 48,
286 227, 45, 145, 132, 143, 200, 39, 175, 41, 56, 2, 85, 90, 52, 141, 108,
287 37, 167, 137, 196, 159, 240, 168, 17, 247, 135, 244, 17, 9, 4, 251,
288 172, 84, 36, 195, 134, 252, 4, 198, 205, 227, 198, 31, 52, 64, 90,
289 255, 207, 196, 37, 124, 70, 0, 85, 146, 38, 50, 154, 206, 248, 37,
290 104, 141, 18, 45, 51, 251, 254, 239, 48, 220, 0, 33, 2, 237, 5, 20,
291 244, 140, 183, 91, 5, 92, 219, 29, 77, 26, 202, 43, 11, 118, 66, 98,
292 193, 45, 112, 187, 113, 59, 217, 57, 204, 38, 12, 191, 250, 208, 37,
293 196, 10, 224, 140, 49, 200, 67, 126, 11, 119, 45, 252, 62, 12, 4, 21,
294 94, 166, 225, 41, 245, 228, 13, 99, 252, 25, 6, 51, 241, 110, 246,
295 200, 126, 180, 86, 192, 210, 182, 41, 58, 202, 137, 128, 197, 160,
296 158, 202, 159, 239, 202, 154, 101, 163, 143, 119, 235, 65, 93, 245,
297 23, 174, 196, 84, 174, 39, 58, 72, 225, 245, 173, 138, 86, 250, 120,
298 27, 224, 0, 0, 0, 142, 65, 154, 192, 71, 2, 179, 114, 196, 98, 132,
299 121, 125, 240, 152, 59, 30, 12, 199, 225, 7, 6, 235, 232, 46, 155, 71,
300 19, 30, 2, 192, 210, 165, 133, 37, 172, 253, 26, 9, 190, 112, 167,
301 226, 60, 64, 232, 16, 174, 150, 108, 134, 173, 184, 93, 60, 158, 253,
302 173, 195, 145, 236, 39, 148, 176, 38, 110, 234, 96, 1, 153, 116, 166,
303 138, 242, 246, 182, 129, 241, 41, 94, 220, 78, 83, 30, 28, 103, 223,
304 55, 248, 255, 133, 68, 97, 150, 91, 216, 219, 47, 55, 253, 247, 30, 8,
305 53, 18, 250, 202, 8, 66, 192, 92, 17, 199, 124, 43, 114, 211, 2, 186,
306 97, 51, 85, 93, 236, 159, 198, 123, 35, 72, 180, 177, 177, 116, 126,
307 200, 56, 152, 190, 184, 251, 180, 13, 240, 0, 0, 0, 172, 65, 154, 224,
308 220, 10, 205, 33, 17, 115, 113, 223, 97, 66, 123, 88, 177, 225, 111,
309 143, 17, 0, 55, 155, 49, 17, 89, 92, 140, 34, 78, 190, 200, 12, 74,
310 147, 26, 139, 188, 129, 15, 254, 242, 50, 129, 239, 151, 37, 98, 248,
311 0, 94, 87, 77, 175, 86, 4, 116, 45, 66, 214, 162, 61, 99, 27, 127,
312 147, 179, 30, 1, 187, 116, 141, 230, 255, 21, 245, 24, 84, 64, 38,
313 125, 75, 248, 18, 249, 191, 7, 26, 233, 226, 111, 255, 215, 10, 151,
314 5, 155, 82, 0, 125, 189, 81, 183, 51, 124, 63, 248, 80, 108, 1, 0, 88,
315 95, 191, 99, 163, 209, 160, 43, 110, 72, 247, 117, 224, 13, 234, 183,
316 30, 225, 12, 101, 108, 49, 113, 47, 19, 244, 53, 91, 63, 55, 144, 108,
317 8, 201, 106, 103, 230, 21, 197, 173, 237, 108, 80, 140, 74, 109, 116,
318 189, 25, 148, 223, 242, 41, 93, 242, 27, 232, 217, 224, 104, 128, 0,
319 0, 0, 186, 65, 155, 0, 220, 10, 205, 175, 133, 43, 16, 34, 0, 192,
320 135, 54, 189, 189, 200, 125, 206, 235, 108, 6, 116, 18, 246, 85, 235,
321 38, 127, 189, 27, 75, 215, 183, 234, 97, 56, 5, 158, 35, 50, 80, 43,
322 183, 4, 246, 125, 151, 214, 254, 81, 24, 78, 163, 48, 232, 148, 215,
323 100, 136, 145, 24, 117, 201, 57, 76, 88, 36, 236, 211, 252, 223, 250,
324 174, 120, 32, 247, 14, 207, 44, 252, 107, 246, 136, 65, 98, 154, 160,
325 238, 150, 28, 53, 9, 120, 180, 183, 215, 137, 49, 164, 239, 81, 174,
326 107, 194, 61, 136, 134, 184, 36, 45, 62, 255, 2, 215, 154, 105, 101,
327 174, 27, 94, 121, 173, 20, 170, 97, 77, 29, 87, 255, 135, 207, 28, 44,
328 188, 199, 85, 207, 234, 188, 19, 155, 203, 146, 231, 55, 194, 203, 85,
329 99, 108, 96, 184, 2, 22, 18, 77, 214, 61, 49, 209, 100, 173, 232, 163,
330 216, 98, 25, 144, 1, 111, 18, 252, 197, 239, 129, 65, 249, 190, 234,
331 141, 243, 254, 6, 120, 0, 0, 0, 165, 65, 155, 32, 71, 2, 179, 245,
332 133, 11, 232, 173, 213, 8, 21, 9, 5, 57, 230, 2, 56, 148, 20, 189,
333 247, 242, 19, 241, 58, 207, 111, 193, 42, 162, 42, 252, 188, 149, 148,
334 184, 1, 115, 100, 52, 157, 237, 228, 4, 244, 208, 215, 234, 155, 123,
335 241, 56, 147, 97, 151, 191, 4, 32, 132, 167, 224, 71, 95, 159, 248,
336 250, 216, 192, 86, 56, 226, 153, 36, 244, 245, 126, 241, 155, 223,
337 253, 176, 101, 50, 156, 115, 63, 159, 10, 19, 31, 25, 60, 240, 230,
338 255, 58, 159, 193, 1, 122, 223, 2, 13, 152, 78, 0, 123, 221, 125, 87,
339 191, 159, 132, 150, 12, 0, 131, 136, 25, 6, 174, 87, 175, 4, 61, 98,
340 107, 3, 52, 165, 33, 248, 145, 176, 8, 235, 248, 251, 219, 7, 255,
341 228, 160, 196, 237, 151, 27, 94, 233, 195, 128, 22, 245, 179, 65, 168,
342 137, 184, 31, 224, 84, 128, 0, 0, 0, 239, 65, 155, 64, 71, 2, 179,
343 195, 60, 220, 0, 147, 92, 254, 223, 250, 216, 62, 99, 33, 14, 228,
344 126, 77, 255, 175, 224, 128, 94, 27, 218, 114, 225, 255, 127, 190, 19,
345 39, 181, 98, 70, 138, 120, 129, 130, 2, 16, 3, 199, 41, 63, 71, 214,
346 191, 3, 56, 206, 136, 250, 213, 83, 198, 58, 98, 184, 96, 29, 115,
347 161, 214, 92, 173, 70, 96, 99, 69, 192, 239, 175, 178, 206, 34, 43,
348 224, 32, 67, 250, 231, 243, 142, 172, 55, 182, 224, 48, 232, 52, 252,
349 227, 205, 128, 140, 205, 207, 200, 33, 89, 187, 48, 76, 28, 165, 237,
350 101, 55, 193, 71, 85, 133, 142, 116, 119, 13, 109, 175, 94, 118, 19,
351 156, 217, 80, 65, 86, 165, 63, 29, 228, 128, 22, 49, 10, 23, 179, 167,
352 53, 136, 20, 138, 201, 196, 45, 72, 31, 253, 225, 5, 106, 109, 183,
353 125, 246, 32, 191, 97, 202, 53, 135, 148, 199, 52, 249, 233, 48, 130,
354 120, 214, 63, 183, 159, 250, 48, 220, 51, 22, 39, 194, 24, 68, 223,
355 73, 255, 195, 226, 163, 166, 94, 109, 62, 105, 63, 139, 19, 201, 41,
356 189, 193, 50, 208, 165, 224, 3, 242, 155, 135, 70, 113, 109, 61, 242,
357 192, 67, 120, 35, 32, 215, 135, 159, 132, 188, 132, 2, 24, 161, 13,
358 160, 255, 225, 136, 16, 32, 0, 0, 0, 170, 65, 155, 96, 87, 2, 183,
359 102, 224, 89, 165, 0, 17, 238, 159, 109, 76, 41, 228, 55, 213, 34,
360 157, 128, 144, 176, 30, 127, 238, 76, 242, 192, 4, 44, 211, 223, 245,
361 173, 148, 192, 34, 20, 196, 204, 192, 34, 20, 196, 205, 189, 132, 242,
362 154, 4, 205, 221, 76, 92, 8, 107, 104, 15, 137, 74, 246, 226, 113,
363 230, 203, 24, 46, 229, 47, 80, 17, 49, 239, 70, 217, 128, 150, 102,
364 145, 246, 194, 31, 152, 75, 141, 125, 159, 126, 228, 147, 16, 242,
365 248, 68, 21, 226, 57, 97, 26, 210, 252, 252, 37, 249, 169, 76, 62,
366 148, 193, 57, 97, 50, 136, 206, 48, 99, 175, 124, 4, 143, 71, 225, 31,
367 36, 16, 251, 177, 96, 0, 83, 130, 104, 196, 70, 151, 193, 240, 47,
368 252, 64, 184, 9, 141, 180, 48, 1, 111, 37, 142, 101, 228, 71, 220,
369 215, 211, 192, 233, 66, 161, 228, 201, 119, 175, 15, 19, 192, 129, 0,
370 0, 0, 127, 65, 155, 128, 87, 2, 181, 112, 165, 100, 52, 0, 134, 39, 4,
371 108, 206, 82, 180, 11, 82, 210, 68, 0, 117, 69, 199, 175, 246, 80,
372 106, 169, 48, 12, 44, 99, 159, 243, 250, 42, 132, 133, 58, 177, 126,
373 6, 239, 41, 176, 2, 164, 166, 218, 221, 238, 128, 219, 76, 138, 115,
374 249, 158, 39, 10, 248, 67, 30, 222, 246, 41, 191, 222, 183, 216, 90,
375 132, 254, 69, 137, 7, 225, 16, 18, 121, 248, 76, 223, 10, 127, 195,
376 226, 120, 213, 56, 20, 37, 22, 243, 182, 20, 48, 127, 64, 27, 191, 22,
377 40, 234, 35, 104, 50, 11, 117, 184, 42, 34, 139, 71, 129, 143, 172,
378 139, 22, 51, 71, 224, 66, 128, 0, 0, 0, 157, 65, 155, 160, 87, 3, 87,
379 144, 208, 17, 25, 156, 21, 13, 152, 240, 46, 65, 222, 185, 111, 101,
380 9, 24, 120, 214, 205, 93, 143, 83, 63, 153, 77, 63, 21, 54, 26, 185,
381 64, 18, 47, 73, 157, 35, 31, 0, 65, 181, 83, 235, 121, 77, 193, 229,
382 106, 56, 17, 191, 210, 21, 210, 206, 194, 112, 249, 132, 224, 47, 141,
383 126, 113, 60, 115, 244, 200, 32, 20, 182, 254, 35, 18, 229, 202, 223,
384 172, 194, 81, 190, 23, 247, 223, 171, 210, 149, 125, 246, 42, 79, 104,
385 166, 43, 74, 175, 15, 249, 37, 49, 191, 225, 239, 18, 44, 80, 16, 194,
386 64, 40, 115, 240, 153, 189, 161, 242, 150, 9, 163, 244, 255, 26, 164,
387 252, 38, 104, 126, 177, 5, 194, 19, 60, 89, 147, 40, 233, 131, 190,
388 183, 78, 253, 224, 128, 249, 79, 4, 47, 92, 112, 33, 64, 0, 0, 0, 140,
389 65, 155, 192, 87, 6, 250, 195, 247, 155, 192, 36, 253, 125, 156, 248,
390 174, 226, 111, 32, 136, 22, 159, 86, 19, 46, 115, 234, 254, 64, 175,
391 59, 65, 199, 49, 141, 106, 115, 246, 92, 104, 57, 87, 166, 214, 75,
392 157, 238, 178, 147, 0, 46, 226, 68, 185, 89, 192, 111, 52, 103, 173,
393 86, 207, 19, 133, 12, 124, 1, 106, 159, 146, 89, 225, 6, 173, 108,
394 117, 137, 36, 6, 249, 13, 237, 45, 173, 240, 247, 132, 35, 36, 19,
395 243, 97, 11, 37, 21, 17, 97, 51, 91, 229, 254, 30, 41, 189, 243, 240,
396 145, 142, 170, 204, 216, 235, 226, 188, 212, 225, 212, 137, 153, 235,
397 172, 71, 172, 177, 252, 223, 226, 186, 248, 32, 18, 82, 135, 250, 112,
398 91, 114, 223, 129, 14, 0, 0, 0, 159, 65, 155, 224, 87, 2, 181, 102,
399 241, 179, 190, 240, 157, 100, 17, 0, 53, 90, 66, 59, 247, 166, 223,
400 191, 74, 214, 202, 4, 243, 68, 74, 176, 186, 185, 251, 70, 188, 197,
401 132, 204, 239, 96, 12, 34, 87, 141, 51, 8, 241, 229, 137, 230, 94,
402 247, 90, 202, 43, 130, 187, 110, 133, 74, 233, 183, 91, 46, 228, 156,
403 32, 99, 151, 193, 11, 67, 139, 31, 228, 48, 71, 173, 61, 53, 179, 127,
404 255, 146, 132, 159, 246, 255, 251, 16, 24, 142, 255, 235, 207, 188, 8,
405 0, 175, 52, 155, 229, 254, 18, 44, 252, 178, 249, 225, 51, 121, 74,
406 196, 95, 137, 240, 223, 155, 23, 252, 255, 193, 198, 18, 242, 4, 96,
407 4, 57, 106, 121, 146, 247, 230, 42, 175, 199, 85, 224, 156, 103, 10,
408 4, 205, 198, 245, 148, 55, 129, 47, 155, 241, 14, 166, 88, 255, 2, 36,
409 0, 0, 0, 124, 65, 154, 0, 87, 2, 181, 253, 44, 39, 228, 17, 0, 99, 95,
410 101, 95, 124, 67, 216, 204, 130, 141, 43, 87, 123, 46, 119, 95, 149,
411 49, 183, 176, 158, 82, 64, 153, 187, 169, 128, 13, 255, 162, 201, 251,
412 247, 189, 173, 160, 62, 37, 43, 219, 136, 190, 18, 23, 226, 120, 4,
413 121, 24, 189, 191, 225, 10, 216, 162, 85, 203, 163, 180, 113, 226, 77,
414 62, 251, 204, 191, 132, 64, 176, 8, 129, 164, 60, 94, 27, 247, 193,
415 128, 41, 207, 194, 47, 193, 36, 35, 136, 97, 193, 169, 119, 227, 127,
416 244, 2, 32, 4, 110, 35, 132, 176, 176, 64, 129, 60, 33, 106, 95, 133,
417 194, 48, 34, 64, 0, 0, 0, 170, 65, 154, 32, 87, 2, 181, 102, 240, 9,
418 203, 70, 151, 249, 167, 132, 201, 245, 247, 228, 17, 9, 249, 24, 9,
419 145, 5, 223, 207, 64, 183, 242, 130, 143, 200, 45, 58, 79, 124, 64,
420 36, 253, 148, 39, 251, 170, 191, 209, 227, 18, 187, 203, 192, 1, 187,
421 45, 249, 241, 178, 189, 47, 76, 77, 188, 95, 245, 249, 215, 175, 67,
422 133, 147, 4, 150, 165, 153, 54, 144, 11, 212, 55, 219, 251, 92, 188,
423 109, 236, 66, 123, 135, 246, 141, 53, 94, 255, 204, 126, 1, 30, 190,
424 218, 240, 43, 2, 64, 88, 13, 5, 147, 182, 105, 177, 236, 191, 47, 195,
425 37, 4, 158, 248, 212, 175, 96, 104, 194, 57, 36, 13, 194, 102, 201,
426 175, 83, 255, 127, 127, 172, 16, 135, 144, 128, 128, 73, 102, 191,
427 182, 104, 176, 82, 200, 67, 88, 194, 37, 90, 2, 247, 127, 224, 66,
428 125, 1, 58, 249, 252, 252, 35, 254, 92, 8, 176, 0, 0, 0, 190, 65, 154,
429 64, 103, 2, 181, 126, 35, 9, 214, 67, 64, 38, 226, 151, 163, 226, 153,
430 253, 214, 181, 179, 133, 156, 135, 203, 25, 15, 188, 243, 89, 107,
431 116, 253, 179, 75, 32, 89, 190, 0, 64, 50, 242, 11, 72, 190, 220, 37,
432 8, 102, 103, 169, 68, 232, 255, 111, 41, 184, 112, 201, 24, 19, 42,
433 62, 37, 214, 238, 87, 152, 95, 2, 107, 212, 201, 191, 241, 125, 198,
434 21, 25, 166, 122, 130, 3, 58, 50, 8, 207, 4, 99, 170, 38, 248, 123,
435 223, 194, 184, 11, 79, 84, 114, 11, 120, 224, 254, 63, 201, 20, 199,
436 39, 35, 230, 112, 195, 224, 251, 249, 159, 235, 192, 103, 186, 228,
437 221, 190, 196, 89, 110, 141, 168, 240, 222, 161, 163, 246, 76, 110,
438 253, 95, 123, 1, 205, 224, 8, 94, 41, 72, 80, 104, 4, 41, 170, 237,
439 249, 60, 122, 96, 100, 87, 198, 176, 63, 0, 71, 47, 200, 82, 230, 109,
440 195, 222, 38, 190, 239, 135, 248, 120, 66, 60, 177, 159, 95, 208, 4,
441 3, 215, 9, 159, 129, 98, 0, 0, 0, 129, 65, 154, 96, 103, 2, 189, 147,
442 9, 249, 4, 64, 7, 58, 225, 200, 74, 114, 18, 173, 80, 104, 244, 223,
443 213, 214, 206, 42, 216, 191, 207, 249, 151, 169, 149, 66, 119, 186,
444 202, 76, 0, 209, 69, 10, 120, 252, 220, 4, 245, 163, 62, 212, 187,
445 244, 222, 22, 12, 32, 28, 41, 13, 211, 48, 148, 197, 29, 87, 123, 230,
446 135, 155, 5, 255, 0, 225, 226, 117, 155, 252, 3, 0, 225, 89, 236, 125,
447 150, 215, 251, 243, 127, 128, 64, 60, 42, 83, 224, 203, 71, 66, 203,
448 124, 33, 91, 20, 48, 139, 232, 126, 5, 187, 191, 203, 248, 128, 247,
449 144, 104, 18, 158, 105, 103, 255, 129, 227, 66, 56, 76, 252, 11, 16,
450 0, 0, 0, 179, 65, 154, 128, 87, 2, 111, 150, 120, 110, 37, 226, 185,
451 184, 34, 184, 122, 108, 190, 108, 1, 213, 136, 177, 184, 0, 53, 73,
452 102, 81, 224, 0, 0, 64, 27, 49, 127, 58, 174, 167, 22, 103, 255, 12,
453 154, 8, 175, 65, 170, 246, 57, 213, 98, 4, 64, 24, 22, 245, 79, 215,
454 60, 230, 48, 98, 109, 58, 34, 45, 130, 196, 158, 52, 1, 141, 84, 33,
455 152, 148, 70, 229, 184, 63, 178, 144, 255, 249, 245, 121, 184, 4, 196,
456 245, 118, 192, 86, 158, 137, 95, 30, 255, 118, 178, 155, 15, 119, 97,
457 253, 108, 217, 13, 108, 30, 32, 110, 65, 97, 236, 106, 211, 143, 126,
458 171, 246, 1, 128, 144, 94, 2, 34, 218, 111, 207, 204, 171, 173, 71,
459 254, 18, 10, 95, 153, 157, 159, 81, 213, 155, 85, 135, 235, 225, 63,
460 86, 248, 251, 200, 122, 183, 125, 211, 175, 114, 117, 123, 255, 189,
461 136, 27, 20, 17, 223, 253, 71, 252, 249, 252, 71, 136, 224, 107, 128,
462 0, 0, 0, 150, 65, 154, 160, 103, 2, 183, 155, 192, 2, 30, 233, 246,
463 202, 101, 232, 81, 96, 6, 23, 171, 227, 35, 60, 168, 192, 97, 173,
464 242, 16, 45, 158, 50, 28, 25, 69, 55, 252, 120, 20, 132, 8, 21, 142,
465 205, 13, 44, 241, 86, 124, 69, 1, 255, 195, 79, 4, 64, 146, 131, 55,
466 19, 127, 169, 215, 80, 133, 72, 63, 43, 248, 89, 211, 27, 228, 54,
467 241, 241, 101, 130, 206, 228, 63, 2, 91, 10, 81, 169, 196, 61, 158, 7,
468 173, 221, 243, 198, 221, 183, 176, 157, 148, 210, 216, 17, 231, 44,
469 238, 103, 142, 146, 55, 59, 207, 216, 6, 195, 155, 235, 255, 195, 229,
470 218, 230, 58, 172, 15, 235, 225, 226, 119, 8, 61, 26, 71, 76, 46, 38,
471 208, 241, 239, 35, 91, 243, 191, 114, 81, 252, 71, 136, 224, 107, 128,
472 0, 0, 0, 146, 65, 154, 192, 103, 2, 57, 252, 191, 38, 8, 65, 176, 49,
473 2, 48, 71, 128, 50, 95, 3, 134, 120, 190, 0, 91, 154, 22, 208, 170,
474 62, 168, 55, 210, 243, 213, 120, 173, 101, 255, 203, 224, 10, 170,
475 198, 65, 55, 94, 245, 36, 144, 195, 16, 66, 10, 165, 152, 197, 255,
476 254, 30, 132, 235, 254, 61, 255, 153, 78, 121, 252, 206, 112, 248,
477 140, 124, 100, 252, 18, 70, 83, 144, 208, 9, 201, 68, 206, 84, 41, 74,
478 131, 239, 61, 246, 246, 85, 140, 238, 176, 173, 10, 116, 47, 91, 59,
479 221, 229, 38, 2, 81, 207, 150, 254, 204, 37, 119, 134, 126, 234, 176,
480 36, 0, 152, 239, 204, 103, 56, 31, 207, 195, 217, 225, 132, 43, 125,
481 131, 113, 21, 95, 105, 244, 127, 63, 136, 239, 168, 26, 32, 0, 0, 0,
482 165, 65, 154, 224, 87, 6, 190, 83, 224, 1, 218, 159, 240, 88, 229, 55,
483 216, 34, 250, 115, 161, 158, 176, 131, 240, 70, 8, 2, 70, 128, 22,
484 152, 174, 215, 248, 182, 0, 77, 199, 110, 96, 224, 128, 230, 176, 137,
485 193, 41, 126, 176, 110, 4, 40, 32, 30, 48, 191, 26, 152, 174, 191,
486 141, 174, 131, 222, 28, 29, 32, 161, 127, 132, 252, 129, 120, 23, 78,
487 136, 254, 95, 243, 5, 62, 154, 124, 16, 7, 57, 56, 207, 33, 96, 75,
488 77, 245, 92, 66, 197, 106, 62, 205, 236, 165, 69, 11, 74, 150, 124,
489 183, 154, 214, 188, 220, 5, 187, 93, 128, 70, 9, 201, 147, 55, 217,
490 111, 41, 178, 140, 60, 158, 219, 165, 228, 240, 134, 139, 225, 188,
491 119, 199, 191, 82, 77, 130, 242, 147, 251, 250, 255, 184, 63, 211, 41,
492 199, 91, 7, 80, 239, 155, 27, 191, 103, 123, 1, 163, 240, 54, 192, 0,
493 0, 0, 145, 65, 155, 0, 87, 6, 135, 247, 228, 139, 46, 224, 3, 233, 94,
494 198, 82, 128, 75, 0, 17, 102, 68, 109, 63, 35, 64, 31, 4, 50, 0, 0, 3,
495 0, 128, 116, 240, 0, 16, 14, 252, 161, 15, 132, 37, 30, 97, 62, 2,
496 193, 135, 64, 2, 58, 253, 189, 155, 109, 134, 30, 184, 10, 8, 47, 18,
497 8, 153, 249, 190, 126, 49, 222, 87, 231, 204, 127, 52, 41, 244, 244,
498 195, 218, 120, 203, 201, 128, 1, 251, 68, 190, 119, 228, 51, 49, 204,
499 76, 136, 172, 67, 105, 158, 63, 161, 211, 215, 205, 12, 109, 182, 175,
500 148, 152, 3, 218, 62, 72, 2, 207, 88, 15, 200, 156, 60, 73, 54, 59,
501 76, 33, 74, 224, 206, 188, 122, 19, 127, 118, 250, 63, 5, 91, 152, 92,
502 119, 220, 8, 112, 0, 0, 0, 4, 104, 203, 140, 178
503 ];
504
505 var trafLength = 8 + tfdt.length + tfdt.length + trun.length;
506 var m4s = [
507 8 + mfhd.length + trafLength, // size
508 109, 111, 111, 102 // 'moof'
509 ]
510 .concat(mfhd)
511 .concat([
512 trafLength, // size
513 116, 114, 97, 102 // 'traf'
514 ].concat(tfhd).concat(tfdt).concat(trun))
515 .concat(mdat);
1 <!DOCTYPE html>
2 <!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
3 <!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
4 <!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]-->
5 <!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]-->
6 <head>
7 <meta charset="utf-8">
8 <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
9 <title></title>
10 <meta name="description" content="">
11 <meta name="viewport" content="width=device-width">
12
13 <link rel="stylesheet" href="css/normalize.min.css">
14 <link rel="stylesheet" href="css/main.css">
15
16 <script src="js/vendor/modernizr-2.6.2.min.js"></script>
17 </head>
18 <body>
19 <!--[if lt IE 7]>
20 <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>
21 <![endif]-->
22
23 <div class="header-container">
24 <header class="wrapper clearfix">
25 <h1 class="title">Transmux Analyzer</h1>
26 </header>
27 </div>
28
29 <div class="main-container">
30 <div class="main wrapper clearfix">
31
32 <article>
33 <header>
34 <p>
35 This page can help you inspect the results of the
36 transmuxing to mp4 files performed by
37 videojs-contrib-hls. It's still a bit tricky to create a
38 MSE-compatible fragmented MP4. We've had luck
39 with <a href="http://www.bento4.com/developers/dash/">Bento4</a>
40 and ffmpeg. If you have both of those utilities installed,
41 you can create a working MP4 like this:
42 <pre>
43 ffmpeg -i movie.ts -vn -codec copy -absf aac_adtstoasc movie-audio.mp4
44 mp4fragment --track audio --fragment-duration 11000 movie-audio.mp4 movie-audio.m4s
45 </pre>
46 <small>Looking for the <a href="index.html">FLV tool</a>?</small>
47 </header>
48 <section id="video-place">
49 </section>
50 <section>
51 <h2>Inputs</h2>
52 <form id="inputs">
53 <label>
54 Your original MP2T segment:
55 <input type="file" id="original">
56 </label>
57 <label>
58 A working, MP4 version of the underlying stream
59 produced by another tool:
60 <input type="file" id="working">
61 </label>
62 </form>
63 </section>
64 <section>
65 <h2>Comparision</h2>
66 <div id="comparison">
67 A diff of the structure of the two MP4s will appear here
68 once you've specified an input TS file and a known working
69 MP4.
70 </div>
71 </section>
72 <section>
73 <h2>Structure</h2>
74 <div class="result-wrapper">
75 <h3>videojs-contrib-hls</h3>
76 <pre class="vjs-boxes">
77 </pre>
78 </div>
79 <div class="result-wrapper">
80 <h3>Working</h3>
81 <pre class="working-boxes"></pre>
82 </div>
83 </section>
84 <section>
85 <h2>Results</h2>
86 <div class="result-wrapper">
87 <h3>videojs-contrib-hls</h3>
88 <div class="vjs-hls-output result">
89 <p>
90 The results of transmuxing your input file with
91 videojs-contrib-hls will show up here.
92 </p>
93 </div>
94 </div>
95 <div class="result-wrapper">
96 <h3>Working</h3>
97 <div class="working-output result">
98 <p>
99 The "good" version of the file will show up here.
100 </p>
101 </div>
102 </div>
103 </section>
104 </article>
105
106 </div> <!-- #main -->
107 </div> <!-- #main-container -->
108
109 <div class="footer-container">
110 <footer class="wrapper">
111 <h3>footer</h3>
112 </footer>
113 </div>
114
115
116 <script>
117 window.videojs = window.videojs || {
118 Hls: {}
119 };
120 </script>
121 <script src="../../node_modules/videojs-contrib-media-sources/node_modules/mux.js/lib/stream.js"></script>
122 <script src="../../node_modules/videojs-contrib-media-sources/node_modules/mux.js/lib/mp4-generator.js"></script>
123 <script src="../../node_modules/videojs-contrib-media-sources/node_modules/mux.js/lib/transmuxer.js"></script>
124 <script src="../../node_modules/videojs-contrib-media-sources/node_modules/mux.js/lib/mp4-inspector.js"></script>
125 <script src="../../node_modules/videojs-contrib-media-sources/node_modules/mux.js/legacy/flv-tag.js"></script>
126 <script src="../../node_modules/videojs-contrib-media-sources/node_modules/mux.js/lib/exp-golomb.js"></script>
127
128 <script src="../../src/bin-utils.js"></script>
129
130 <!-- Include QUnit for object diffs -->
131 <script src="../../node_modules/qunitjs/qunit/qunit.js"></script>
132 <script>
133 /*
134 MOSTLY STOLEN FROM https://w3c.github.io/media-source/#examples
135 */
136 function setupMSE (videoElement, getNextVideoSegment, getNextAudioSegment) {
137 function onSourceOpen(videoTag, e) {
138 var
139 initVideoSegment = getNextVideoSegment(),
140 initAudioSegment = getNextAudioSegment(),
141 numberInited = 0,
142 videoBuffer, audioBuffer,
143 mediaSource = e.target;
144
145 if (mediaSource.sourceBuffers.length > 0)
146 return;
147
148 if (initVideoSegment) {
149 videoBuffer = mediaSource.addSourceBuffer('video/mp4;codecs=avc1.4d401f');
150 }
151 if (initAudioSegment) {
152 audioBuffer = mediaSource.addSourceBuffer('audio/mp4;codecs=mp4a.40.2');
153 }
154
155 videoTag.addEventListener('progress', onProgress.bind(videoTag, mediaSource));
156
157 if (initVideoSegment == null && initAudioSegment == null) {
158 // Error fetching the initialization segment. Signal end of stream with an error.
159 mediaSource.endOfStream("network");
160 return;
161 }
162
163 // Append the initialization segment.
164 var firstAppendHandler = function(e) {
165 var sourceBuffer = e.target;
166 sourceBuffer.removeEventListener('updateend', firstAppendHandler);
167
168 // Append some initial media data.
169 if (++numberInited === 2) {
170 onProgress(mediaSource, e);
171 }
172 };
173
174 if (videoBuffer) {
175 videoBuffer.addEventListener('updateend', firstAppendHandler);
176 }
177 if (audioBuffer) {
178 audioBuffer.addEventListener('updateend', firstAppendHandler);
179 }
180
181 if (initVideoSegment) {
182 videoBuffer.appendBuffer(initVideoSegment);
183 }
184 if (initAudioSegment) {
185 audioBuffer.appendBuffer(initAudioSegment);
186 }
187 }
188
189 function appendNextMediaSegment(getNextMediaSegment, mediaSource, sourceBuffer) {
190 if (mediaSource.readyState == "closed") {
191 return;
192 }
193
194 var mediaSegment = getNextMediaSegment();
195 // If we have run out of stream data, then signal end of stream.
196 if (mediaSegment == null) {
197 // mediaSource.endOfStream("network");
198 return false;
199 }
200
201 // Make sure the previous append is not still pending.
202 if (sourceBuffer.updating) {
203 return false;
204 }
205
206 // NOTE: If mediaSource.readyState == “ended”, this appendBuffer() call will
207 // cause mediaSource.readyState to transition to "open". The web application
208 // should be prepared to handle multiple “sourceopen” events.
209 sourceBuffer.appendBuffer(mediaSegment);
210 return true;
211 }
212 /*
213 function onSeeking(mediaSource, e) {
214 var video = e.target;
215
216 if (mediaSource.readyState == "open") {
217 // Abort current segment append.
218 mediaSource.sourceBuffers[0].abort();
219 }
220
221 // Notify the media segment loading code to start fetching data at the
222 // new playback position.
223 SeekToMediaSegmentAt(video.currentTime);
224
225 // Append a media segment from the new playback position.
226 appendNextMediaSegment(mediaSource);
227 }
228 */
229 function onProgress(mediaSource, e) {
230 (appendNextMediaSegment(getNextVideoSegment, mediaSource, mediaSource.sourceBuffers[0]) &&
231 appendNextMediaSegment(getNextAudioSegment, mediaSource, mediaSource.sourceBuffers[1]));
232 }
233
234 var mediaSource = new MediaSource();
235 mediaSource.addEventListener('sourceopen', onSourceOpen.bind(this, videoElement));
236 videoElement.src = window.URL.createObjectURL(mediaSource);
237 }
238 function getSegment (segmentArray) {
239 var segment = segmentArray.shift();
240 if (segment) {
241 return segment.data;
242 }
243 return null;
244 }
245 </script>
246 <script>
247 var inputs = document.getElementById('inputs'),
248 original = document.getElementById('original'),
249 working = document.getElementById('working'),
250
251 vjsParsed,
252 workingParsed,
253 diffParsed,
254 vjsBytes,
255 workingBytes,
256
257 vjsBoxes = document.querySelector('.vjs-boxes'),
258 workingBoxes = document.querySelector('.working-boxes'),
259
260 vjsOutput = document.querySelector('.vjs-hls-output'),
261 workingOutput = document.querySelector('.working-output'),
262
263 video = document.createElement('video'),
264 mediaSource = new MediaSource();
265
266 document.querySelector('#video-place').appendChild(video);
267
268 logevent = function(event) {
269 console.log(event.type);
270 };
271
272 // output a diff of the two parsed MP4s
273 diffParsed = function() {
274 var comparison, diff, transmuxed;
275 if (!vjsParsed || !workingParsed) {
276 // wait until both inputs have been provided
277 return;
278 }
279 comparison = document.querySelector('#comparison');
280 if (workingParsed[0].type === 'moof') {
281 diff = '<h3>Media Segment Comparision</h3>';
282 transmuxed = vjsParsed.slice(2);
283 } else if (workingParsed.length === 2) {
284 diff = '<h3>Init Segment Comparision</h3>';
285 transmuxed = vjsParsed.slice(0, 2);
286 } else {
287 diff = '<h3>General Comparision</h3>';
288 transmuxed = vjsParsed;
289 }
290 diff += '<p>A <del>red background</del> indicates ' +
291 'properties present in the transmuxed file but missing from the ' +
292 'working version. A <ins>green background</ins> indicates ' +
293 'properties present in the working version but missing in the ' +
294 'transmuxed output.</p>';
295 diff += '<pre class="mp4-diff">' +
296 QUnit.diff(muxjs.textifyMp4(transmuxed, null, ' '),
297 muxjs.textifyMp4(workingParsed, null, ' ')) +
298 '</pre>';
299
300 comparison.innerHTML = diff;
301 };
302
303 mediaSource.addEventListener('sourceopen', function() {
304 var
305 buffer = mediaSource.addSourceBuffer('video/mp4;codecs=avc1.4d400d');
306 //buffer = mediaSource.addSourceBuffer('audio/mp4;codecs=mp4a.40.2');
307 buffer.addEventListener('updatestart', logevent);
308 buffer.addEventListener('updateend', logevent);
309 buffer.addEventListener('error', logevent);
310 window.vjsMediaSource = mediaSource;
311 window.vjsSourceBuffer = buffer;
312 window.vjsVideo = video;
313 });
314 mediaSource.addEventListener('error', logevent);
315 mediaSource.addEventListener('opened', logevent);
316 mediaSource.addEventListener('closed', logevent);
317 mediaSource.addEventListener('sourceended', logevent);
318 video.src = URL.createObjectURL(mediaSource);
319 video.addEventListener('error', console.log.bind(console));
320
321
322 // muxjs.log = console.log.bind(console);
323
324 original.addEventListener('change', function() {
325 var reader = new FileReader(),
326 videoBuffer = [],
327 audioBuffer = [];
328
329 reader.addEventListener('loadend', function() {
330 var segment = new Uint8Array(reader.result),
331 transmuxer = new muxjs.mp2t.Transmuxer(),
332 videoSegments = [],
333 audioSegments = [],
334 videoBytesLength = 0,
335 audioBytesLength = 0,
336 decodeMe,
337 bytes,
338 i, j,
339 hex = '';
340
341 // transmux the MPEG-TS data to BMFF segments
342 transmuxer.on('data', function(segment) {
343 if (segment.type === 'video') {
344 videoSegments.push(segment);
345 videoBytesLength += segment.data.byteLength;
346 } else {
347 audioSegments.push(segment);
348 audioBytesLength += segment.data.byteLength;
349 }
350 });
351
352 transmuxer.push(segment);
353 transmuxer.end();
354 // XXX - switch to select video/audio to show
355 decodeMe = videoSegments;
356 bytes = new Uint8Array(videoBytesLength);
357
358 for (j = 0, i = 0; j < decodeMe.length; j++) {
359 bytes.set(decodeMe[j].data, i);
360 i += decodeMe[j].byteLength;
361 }
362
363 vjsBytes = bytes;
364 vjsParsed = muxjs.inspectMp4(bytes);
365 console.log('transmuxed', vjsParsed);
366 diffParsed();
367
368 // XXX - set one of videoSegments or audioSegments below to an
369 // empty array to only test one stream
370
371 setupMSE(video,
372 getSegment.bind(null, videoSegments),
373 getSegment.bind(null, audioSegments));
374
375 // clear old box info
376 vjsBoxes.innerHTML = muxjs.textifyMp4(vjsParsed, null, ' ');
377
378 // write out the result
379 hex += '<pre>';
380 hex += 'nothing to see here';
381 hex += '</pre>';
382 vjsOutput.innerHTML = hex;
383
384 video.play();
385 });
386
387 reader.readAsArrayBuffer(this.files[0]);
388 }, false);
389
390 working.addEventListener('change', function() {
391 var reader = new FileReader();
392 reader.addEventListener('loadend', function() {
393 var hex = '',
394 bytes = new Uint8Array(reader.result);
395
396
397 workingBytes = bytes;
398 workingParsed = muxjs.inspectMp4(bytes);
399 console.log('working', workingParsed);
400 diffParsed();
401
402 // clear old box info
403 workingBoxes.innerHTML = muxjs.textifyMp4(workingParsed, null, ' ');
404
405 // output the hex dump
406 hex += '<pre>';
407 hex += videojs.Hls.utils.hexDump(bytes);
408 hex += '</pre>';
409 workingOutput.innerHTML = hex;
410
411 // XXX Media Sources Testing
412 /* setupMSE(video,
413 getSegment.bind(null, []),
414 getSegment.bind(null, [{data: bytes}]));*/
415 //window.vjsSourceBuffer.appendBuffer(bytes);
416 });
417 reader.readAsArrayBuffer(this.files[0]);
418 }, false);
419 </script>
420 </body>
421 </html>
1 <!DOCTYPE html>
2 <!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
3 <!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
4 <!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]-->
5 <!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]-->
6 <head>
7 <meta charset="utf-8">
8 <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
9 <title>HLS Media Sources Demo</title>
10 <meta name="description" content="">
11 <meta name="viewport" content="width=device-width">
12
13 <link rel="stylesheet" href="css/normalize.min.css">
14 <link rel="stylesheet" href="css/main.css">
15
16 <script src="js/vendor/modernizr-2.6.2.min.js"></script>
17 </head>
18 <body>
19 <!--[if lt IE 7]>
20 <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>
21 <![endif]-->
22
23 <div class="header-container">
24 <header class="wrapper clearfix">
25 <h1 class="title">Media Sources Demo</h1>
26 </header>
27 </div>
28
29 <div class="main-container">
30 <div class="main wrapper clearfix">
31
32 <article>
33 <header>
34 <p>
35 videojs-contrib-hls can convert MPEG transport streams
36 into Media Source Extension-compatible media segments on
37 the fly. Check out the demo below in Chrome!
38 </p>
39 </header>
40 <section>
41 <video id=demo width=640 height=360 controls>
42 <p>
43 Your browser is too old to view this demo. How about
44 upgrading?
45 </p>
46 </video>
47 </section>
48 </article>
49
50 </div> <!-- #main -->
51 </div> <!-- #main-container -->
52
53 <div class="footer-container">
54 <footer class="wrapper">
55 </footer>
56 </div>
57
58
59 <script src="zencoder-haze-1.js"></script>
60 <!-- transmuxing -->
61 <script>
62 window.videojs = window.videojs || {
63 Hls: {}
64 };
65 </script>
66 <script src="../../src/stream.js"></script>
67 <script src="../../src/mp4-generator.js"></script>
68 <script src="../../src/transmuxer.js"></script>
69 <script src="../../src/exp-golomb.js"></script>
70 <script src="js/mp4-inspector.js"></script>
71
72 <script src="../../src/bin-utils.js"></script>
73 <script>
74 var mediaSource = new MediaSource(),
75 demo = document.getElementById('demo');
76
77 // setup the media source
78 mediaSource.addEventListener('sourceopen', function() {
79 var videoBuffer = mediaSource.addSourceBuffer('video/mp4;codecs=avc1.4d400d'),
80 audioBuffer = mediaSource.addSourceBuffer('audio/mp4;codecs=mp4a.40.2'),
81 transmuxer = new videojs.mp2t.Transmuxer(),
82 videoSegments = [],
83 audioSegments = [];
84
85 // expose the machinery for debugging
86 window.vjsMediaSource = mediaSource;
87 window.vjsSourceBuffer = videoBuffer;
88 window.vjsVideo = demo;
89
90 // transmux the MPEG-TS data to BMFF segments
91 transmuxer.on('data', function(segment) {
92 if (segment.type === 'video') {
93 videoSegments.push(segment);
94 } else {
95 audioSegments.push(segment);
96 }
97 });
98 transmuxer.push(hazeVideo);
99 transmuxer.end();
100
101 // buffer up the video data
102 videoBuffer.appendBuffer(videoSegments.shift().data);
103 videoBuffer.addEventListener('updateend', function() {
104 if (videoSegments.length) {
105 videoBuffer.appendBuffer(videoSegments.shift().data);
106 }
107 });
108
109 // buffer up the audio data
110 audioBuffer.appendBuffer(audioSegments.shift().data);
111 audioBuffer.addEventListener('updateend', function() {
112 if (audioSegments.length) {
113 audioBuffer.appendBuffer(audioSegments.shift().data);
114 }
115 });
116 });
117 demo.src = URL.createObjectURL(mediaSource);
118 demo.addEventListener('error', console.log.bind(console));
119 </script>
120 </body>
121 </html>
1 <!DOCTYPE html>
2 <!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
3 <!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
4 <!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]-->
5 <!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]-->
6 <head>
7 <meta charset="utf-8">
8 <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
9 <title></title>
10 <meta name="description" content="">
11 <meta name="viewport" content="width=device-width">
12
13 <link rel="stylesheet" href="css/normalize.min.css">
14 <link rel="stylesheet" href="css/main.css">
15
16 <script src="js/vendor/modernizr-2.6.2.min.js"></script>
17
18 <script>
19 window.videojs = window.videojs || {
20 Hls: {}
21 };
22 </script>
23 <!--<script src="min-m4s.js"></script>-->
24 <script src="./js/mdat.js"></script>
25 <script src="../../src/mp4-generator.js"></script>
26 </head>
27 <body>
28 <!--[if lt IE 7]>
29 <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>
30 <![endif]-->
31
32 <div class="header-container">
33 <header class="wrapper clearfix">
34 <h1 class="title">Transmux Analyzer</h1>
35 </header>
36 </div>
37
38 <div class="main-container">
39 <div class="main wrapper clearfix">
40
41 <article>
42 <header>
43 <p>
44 This page will show a video player that loads a header of an mp4 followed by an mp4 segment.
45 </p>
46 </header>
47 <section>
48 <video controls autoplay width="320" height="240"></video>
49 </section>
50 </article>
51
52 </div> <!-- #main -->
53 </div> <!-- #main-container -->
54
55 <div class="footer-container">
56 <footer class="wrapper">
57 <h3></h3>
58 </footer>
59 </div>
60
61
62
63
64
65 <script>
66 var ms,
67 sourceBuffer,
68 video;
69
70 function closed() {
71 console.log("MediaSource Closed.");
72 }
73
74 var done = false;
75 function loadFragment() {
76 if (done) {
77 return;
78 }
79
80 console.log("LOAD FRAGMENT");
81
82 var moov = videojs.mp4.moov(100, 600, 300, "video");
83 var moof = videojs.mp4.moof([{trackId: 1}]);
84 var frag = Array.prototype.slice.call(moov);
85 frag = frag.concat(Array.prototype.slice.call(moof));
86 frag = frag.concat(Array.prototype.slice.call(mdat));
87
88
89
90 sourceBuffer.appendBuffer( new Uint8Array( frag ) );
91
92 done = true;
93
94 }
95
96 function loadMdat(){
97 console.log('mdat', mdat);
98 setTimeout( function(){
99 sourceBuffer.appendBuffer(new Uint8Amdat);
100 },0);
101
102 }
103
104 function loadInit() {
105 console.log("LOAD INIT");
106 var req = new XMLHttpRequest();
107 req.responseType = "arraybuffer";
108 req.open("GET", "./ts2/Header.m4s", true);
109
110 req.onload = function() {
111 console.log("INIT DONE LOADING");
112 sourceBuffer.appendBuffer(new Uint8Array(req.response));
113 sourceBuffer.addEventListener('update', loadFragment);
114 };
115
116 req.onerror = function () {
117 window.alert("Could not load init.");
118 };
119
120 req.send();
121
122 }
123
124 function opened() {
125
126 console.log("OPENED");
127 sourceBuffer = ms.addSourceBuffer('video/mp4;codecs="avc1.42c00d"');
128
129 sourceBuffer.addEventListener('updateend', console.log.bind(console));
130 sourceBuffer.addEventListener('updatestart', console.log.bind(console));
131 sourceBuffer.addEventListener('update', console.log.bind(console));
132 loadInit();
133 }
134
135 function load() {
136 console.log("START");
137 window.MediaSource = window.MediaSource || window.WebKitMediaSource;
138
139 ms = new window.MediaSource();
140
141 video = document.querySelector('video');
142 video.src = window.URL.createObjectURL(ms);
143
144 ms.addEventListener('webkitsourceopen', opened, false);
145 ms.addEventListener('webkitsourceclose', closed, false);
146
147 ms.addEventListener('sourceopen', opened, false);
148 ms.addEventListener('sourceclose', closed, false);
149
150 ms.addEventListener('update', console.log.bind(console));
151 ms.addEventListener('updateend', console.log.bind(console));
152 }
153 load();
154 </script>
155 </body>
156 </html>
1 <!DOCTYPE html>
2 <!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
3 <!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
4 <!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]-->
5 <!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]-->
6 <head>
7 <meta charset="utf-8">
8 <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
9 <title></title>
10 <meta name="description" content="">
11 <meta name="viewport" content="width=device-width">
12
13 <link rel="stylesheet" href="css/normalize.min.css">
14 <link rel="stylesheet" href="css/main.css">
15
16 <script src="js/vendor/modernizr-2.6.2.min.js"></script>
17 </head>
18 <body>
19 <!--[if lt IE 7]>
20 <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>
21 <![endif]-->
22
23 <div class="header-container">
24 <header class="wrapper clearfix">
25 <h1 class="title">MP4 Media Sources API Test</h1>
26 </header>
27 </div>
28
29 <div class="main-container">
30 <div class="main wrapper clearfix">
31
32 <article>
33 <header>
34 <p>
35 This page is used to demo the playing of an mp4 file via the mediaSources API
36 </p>
37 </header>
38 <section>
39 <h2>Inputs</h2>
40 <form id="inputs">
41 <label>
42 Your original MP2T segment:
43 <input type="file" id="original">
44 </label>
45 <label>
46 A working, MP4 version of the underlying stream
47 produced by another tool:
48 <input type="file" id="working">
49 </label>
50 </form>
51 </section>
52 <section>
53
54 <video controls width="320" height="240">>
55 <source src="sintel_trailer-480p.mp4" type="video/mp4">
56 Your browser does not support the <code>video</code> element.
57 </video>
58 </section>
59 </article>
60
61 </div> <!-- #main -->
62 </div> <!-- #main-container -->
63
64 <div class="footer-container">
65 <footer class="wrapper">
66 <h3></h3>
67 </footer>
68 </div>
69
70 <script>
71 var inputs = document.getElementById('inputs'),
72 original = document.getElementById('original'),
73 working = document.getElementById('working'),
74
75 video = document.createElement('video');
76
77
78 working.addEventListener('change', function() {
79 console.log('File Selectd');
80 var reader = new FileReader();
81 reader.addEventListener('loadend', function() {
82 console.log('File has been loaded');
83 var bytes = new Uint8Array(reader.result),
84 ms = new window.MediaSource(),
85 video = document.querySelector('video');
86
87 video.addEventListener("error", function onError(err) {
88 console.dir("error", err, video.error);
89 });
90
91 video.pause();
92 video.src = window.URL.createObjectURL(ms);
93
94 var onMediaSourceOpen = function() {
95 console.log('on media open');
96 ms.removeEventListener('sourceopen', onMediaSourceOpen);
97 var videoBuffer = ms.addSourceBuffer('video/mp4;codecs="avc1.4D400D"');
98 videoBuffer.appendBuffer(bytes);
99
100 var audioBuffer = ms.addSourceBuffer('audio/mp4;codecs=mp4a.40.2');
101 };
102
103 ms.addEventListener('sourceopen', onMediaSourceOpen);
104
105 console.log('appended bytes', bytes);
106 });
107 reader.readAsArrayBuffer(this.files[0]);
108 });
109
110 </script>
111
112 </body>
113 </html>
1 [
2 {
3 "type": "ftyp"
4 },
5 {
6 "type": "free"
7 },
8 {
9 "boxes": [
10 {
11 "type": "mvhd"
12 },
13 {
14 "boxes": [
15 {
16 "type": "mehd"
17 },
18 {
19 "type": "trex"
20 }
21 ],
22 "type": "mvex"
23 },
24 {
25 "boxes": [
26 {
27 "type": "tkhd"
28 },
29 {
30 "boxes": [
31 {
32 "type": "mdhd"
33 },
34 {
35
36 "type": "hdlr"
37 },
38 {
39 "boxes": [
40 {
41 "type": "vmhd"
42 },
43 {
44 "boxes": [
45 {
46 "dataReferences": [
47 {
48 "type": "url "
49 }
50 ],
51 "type": "dref"
52 }
53 ],
54 "type": "dinf"
55 },
56 {
57 "boxes": [
58 {
59 "sampleDescriptions": [
60 {
61 "config": [
62 {
63 "type": "avcC"
64 },
65 {
66 "type": "btrt"
67 }
68 ],
69 "type": "avc1"
70 }
71 ],
72 "type": "stsd"
73 },
74 {
75 "type": "stts"
76 },
77 {
78 "type": "stsc"
79 },
80 {
81 "type": "stsz"
82 },
83 {
84 "type": "stco"
85 }
86 ],
87 "type": "stbl"
88 }
89 ],
90 "type": "minf"
91 }
92 ],
93 "type": "mdia"
94 }
95 ],
96 "type": "trak"
97 }
98 ],
99 "type": "moov"
100 }
101 ]
...\ No newline at end of file ...\ No newline at end of file
This diff could not be displayed because it is too large.
1 (function(window) {
2 /*
3 ======== A Handy Little QUnit Reference ========
4 http://api.qunitjs.com/
5
6 Test methods:
7 module(name, {[setup][ ,teardown]})
8 test(name, callback)
9 expect(numberOfAssertions)
10 stop(increment)
11 start(decrement)
12 Test assertions:
13 ok(value, [message])
14 equal(actual, expected, [message])
15 notEqual(actual, expected, [message])
16 deepEqual(actual, expected, [message])
17 notDeepEqual(actual, expected, [message])
18 strictEqual(actual, expected, [message])
19 notStrictEqual(actual, expected, [message])
20 throws(block, [expected], [message])
21 */
22 var
23 parser,
24
25 expectedHeader = [
26 0x46, 0x4c, 0x56, 0x01, 0x05, 0x00, 0x00, 0x00,
27 0x09, 0x00, 0x00, 0x00, 0x00
28 ],
29
30 mergeOptions = window.videojs.mergeOptions,
31
32 makePat,
33 makePsi,
34 makePmt,
35 makePacket,
36
37 testAudioTag,
38 testVideoTag,
39 testScriptTag,
40 asciiFromBytes,
41 testScriptString,
42 testScriptEcmaArray,
43 testNalUnit;
44
45 module('segment parser', {
46 setup: function() {
47 parser = new window.videojs.Hls.SegmentParser();
48 }
49 });
50
51 test('creates an flv header', function() {
52 var header = Array.prototype.slice.call(parser.getFlvHeader());
53 ok(header, 'the header is truthy');
54 equal(9 + 4, header.length, 'the header length is correct');
55 equal(header[0], 'F'.charCodeAt(0), 'the first character is "F"');
56 equal(header[1], 'L'.charCodeAt(0), 'the second character is "L"');
57 equal(header[2], 'V'.charCodeAt(0), 'the third character is "V"');
58
59 deepEqual(expectedHeader, header, 'the rest of the header is correct');
60 });
61
62 // Create a PMT packet
63 // @return {Array} bytes
64 makePmt = function(options) {
65 var
66 result = [],
67 pcr = options.pcr || 0,
68 entryCount = 0,
69 k,
70 sectionLength;
71
72 for (k in options.pids) {
73 if (k !== 'pcr') {
74 entryCount++;
75 }
76 }
77 // table_id
78 result.push(0x02);
79 // section_syntax_indicator '0' reserved section_length
80 // 13 + (program_info_length) + (n * 5 + ES_info_length[n])
81 sectionLength = 13 + (5 * entryCount) + 17;
82 result.push(0x80 | (0xF00 & sectionLength >>> 8));
83 result.push(sectionLength & 0xFF);
84 // program_number
85 result.push(0x00);
86 result.push(0x01);
87 // reserved version_number current_next_indicator
88 result.push(0x01);
89 // section_number
90 result.push(0x00);
91 // last_section_number
92 result.push(0x00);
93 // reserved PCR_PID
94 result.push(0xe0 | (pcr & (0x1f << 8)));
95 result.push(pcr & 0xff);
96 // reserved program_info_length
97 result.push(0xf0);
98 result.push(0x11); // hard-coded 17 byte descriptor
99 // program descriptors
100 result = result.concat([
101 0x25, 0x0f, 0xff, 0xff,
102 0x49, 0x44, 0x33, 0x20,
103 0xff, 0x49, 0x44, 0x33,
104 0x20, 0x00, 0x1f, 0x00,
105 0x01
106 ]);
107 for (k in options.pids) {
108 // stream_type
109 result.push(options.pids[k]);
110 // reserved elementary_PID
111 result.push(0xe0 | (k & 0x1f00) >>> 8);
112 result.push(k & 0xff);
113 // reserved ES_info_length
114 result.push(0xf0);
115 result.push(0x00); // ES_info_length = 0
116 }
117 // CRC_32
118 result.push([0x00, 0x00, 0x00, 0x00]); // invalid CRC but we don't check it
119 return result;
120 };
121
122 // Create a PAT packet
123 // @return {Array} bytes
124 makePat = function(options) {
125 var
126 result = [],
127 programEntries = [],
128 sectionLength,
129 k;
130
131 // build the program entries first
132 for (k in options.programs) {
133 // program_number
134 programEntries.push((k & 0xFF00) >>> 8);
135 programEntries.push(k & 0x00FF);
136 // reserved program_map_pid
137 programEntries.push((options.programs[k] & 0x1f00) >>> 8);
138 programEntries.push(options.programs[k] & 0xff);
139 }
140 sectionLength = programEntries.length + 5 + 4;
141
142 // table_id
143 result.push(0x00);
144 // section_syntax_indicator '0' reserved section_length
145 result.push(0x80 | ((0x300 & sectionLength) >>> 8));
146 result.push(0xff & sectionLength); // section_length
147 // transport_stream_id
148 result.push(0x00);
149 result.push(0x00);
150 // reserved version_number current_next_indicator
151 result.push(0x01); // current_next_indicator is 1
152 // section_number
153 result.push(0x00);
154 // last_section_number
155 result.push(0x00);
156 // program entries
157 result = result.concat(programEntries);
158 return result;
159 };
160
161 // Create a PAT or PMT packet based on the specified options
162 // @return {Array} bytes
163 makePsi = function(options) {
164 var result = [];
165
166 // pointer_field
167 if (options.payloadUnitStartIndicator) {
168 result.push(0x00);
169 }
170 if (options.programs) {
171 return result.concat(makePat(options));
172 }
173 return result.concat(makePmt(options));
174 };
175
176 // Construct an M2TS packet
177 // @return {Array} bytes
178 makePacket = function(options) {
179 var
180 result = [],
181 settings = mergeOptions({
182 payloadUnitStartIndicator: true,
183 pid: 0x00
184 }, options);
185
186 // header
187 // sync_byte
188 result.push(0x47);
189 // transport_error_indicator payload_unit_start_indicator transport_priority PID
190 result.push((settings.pid & 0x1f) << 8 |
191 (settings.payloadUnitStartIndicator ? 0x40 : 0x00));
192 result.push(settings.pid & 0xff);
193 // transport_scrambling_control adaptation_field_control continuity_counter
194 result.push(0x10);
195 result = result.concat(makePsi(settings));
196
197 // ensure the resulting packet is the correct size
198 result.length = window.videojs.Hls.SegmentParser.MP2T_PACKET_LENGTH;
199 return result;
200 };
201
202 test('parses PMTs with program descriptors', function() {
203 var
204 h264Type = window.videojs.Hls.SegmentParser.STREAM_TYPES.h264,
205 adtsType = window.videojs.Hls.SegmentParser.STREAM_TYPES.adts;
206
207 parser.parseSegmentBinaryData(new Uint8Array(makePacket({
208 programs: {
209 0x01: [0x01]
210 }
211 }).concat(makePacket({
212 pid: 0x01,
213 pids: {
214 0x02: h264Type, // h264 video
215 0x03: adtsType // adts audio
216 }
217 }))));
218
219 strictEqual(parser.stream.pmtPid, 0x01, 'PMT PID is 1');
220 strictEqual(parser.stream.programMapTable[h264Type], 0x02, 'video is PID 2');
221 strictEqual(parser.stream.programMapTable[adtsType], 0x03, 'audio is PID 3');
222 });
223
224 test('ignores network information specific data (NIT) in the PAT', function() {
225 parser.parseSegmentBinaryData(new Uint8Array(makePacket({
226 programs: {
227 0x01: [0x01],
228 0x00: [0x00] // a NIT has a reserved PID of 0x00
229 }
230 })));
231
232 ok(true, 'did not throw when a NIT is encountered');
233 });
234
235 test('ignores packets with PCR pids', function() {
236 parser.parseSegmentBinaryData(new Uint8Array(makePacket({
237 programs: {
238 0x01: [0x01]
239 }
240 }).concat(makePacket({
241 pid: 0x01,
242 pcr: 0x02
243 }))));
244
245 equal(parser.stream.programMapTable.pcrPid, 0x02, 'parsed the PCR pid');
246 });
247
248 test('recognizes metadata streams', function() {
249 parser.parseSegmentBinaryData(new Uint8Array(makePacket({
250 programs: {
251 0x01: [0x01]
252 }
253 }).concat(makePacket({
254 pid: 0x01,
255 pids: {
256 // Rec. ITU-T H.222.0 (06/2012), Table 2-34
257 0x02: 0x15 // Metadata carried in PES packets
258 }
259 }))));
260
261 equal(parser.stream.programMapTable[0x15], 0x02, 'metadata is PID 2');
262 });
263
264 test('recognizes subsequent metadata packets after the payload start', function() {
265 var packets = [];
266 parser.metadataStream.push = function(packet) {
267 packets.push(packet);
268 };
269 parser.parseSegmentBinaryData(new Uint8Array(makePacket({
270 programs: {
271 0x01: [0x01]
272 }
273 }).concat(makePacket({
274 pid: 0x01,
275 pids: {
276 // Rec. ITU-T H.222.0 (06/2012), Table 2-34
277 0x02: 0x15 // Metadata carried in PES packets
278 }
279 })).concat(makePacket({
280 pid: 0x02,
281 payloadUnitStartIndicator: false
282 }))));
283
284 equal(packets.length, 1, 'parsed non-payload metadata packet');
285 });
286
287 test('returns undefined for PTS stats when a track is missing', function() {
288 parser.parseSegmentBinaryData(new Uint8Array(makePacket({
289 programs: {
290 0x01: [0x01]
291 }
292 })));
293
294 strictEqual(parser.stats.h264Tags(), 0, 'no video tags yet');
295 strictEqual(parser.stats.aacTags(), 0, 'no audio tags yet');
296 });
297
298 test('parses the first bipbop segment', function() {
299 parser.parseSegmentBinaryData(window.bcSegment);
300
301 ok(parser.tagsAvailable(), 'tags are available');
302 });
303
304 testAudioTag = function(tag) {
305 var
306 byte = tag.bytes[11],
307 format = (byte & 0xF0) >>> 4,
308 soundRate = byte & 0x03,
309 soundSize = (byte & 0x2) >>> 1,
310 soundType = byte & 0x1,
311 aacPacketType = tag.bytes[12];
312
313 equal(10, format, 'the audio format is aac');
314 equal(3, soundRate, 'the sound rate is 44kHhz');
315 equal(1, soundSize, 'the sound size is 16-bit samples');
316 equal(1, soundType, 'the sound type is stereo');
317
318 ok(aacPacketType === 0 || aacPacketType === 1, 'aac packets should have a valid type');
319 };
320
321 testVideoTag = function (tag) {
322 var
323 byte = tag.bytes[11],
324 frameType = (byte & 0xF0) >>> 4,
325 codecId = byte & 0x0F,
326 packetType = tag.bytes[12],
327 compositionTime = (tag.view.getInt32(13) & 0xFFFFFF00) >> 8;
328
329 // payload starts at tag.bytes[16]
330
331 // XXX: I'm not sure that frame types 3-5 are invalid
332 ok(frameType === 1 || frameType === 2,
333 'the frame type should be valid');
334
335 equal(7, codecId, 'the codec ID is AVC for h264');
336 ok(packetType <= 2 && packetType >= 0, 'the packet type is within [0, 2]');
337 if (packetType !== 1) {
338 equal(0,
339 compositionTime,
340 'the composition time is zero for non-NALU packets');
341 }
342
343 // TODO: the rest of the bytes are an NLU unit
344 if (packetType === 0) {
345 // AVC decoder configuration record
346 } else {
347 // NAL units
348 testNalUnit(tag.bytes.subarray(16));
349 }
350 };
351
352 testNalUnit = function(bytes) {
353 var
354 nalHeader = bytes[0];
355 // unitType = nalHeader & 0x1F;
356
357 equal(0, (nalHeader & 0x80) >>> 7, 'the first bit is always 0');
358 // equal(90, (nalHeader & 0x60) >>> 5, 'the NAL reference indicator is something');
359 // ok(unitType > 0, 'NAL unit type ' + unitType + ' is greater than 0');
360 // ok(unitType < 22 , 'NAL unit type ' + unitType + ' is less than 22');
361 };
362
363
364 asciiFromBytes = function(bytes) {
365 var
366 string = [],
367 i = bytes.byteLength;
368
369 while (i--) {
370 string[i] = String.fromCharCode(bytes[i]);
371 }
372 return string.join('');
373 };
374
375 testScriptString = function(tag, offset, expected) {
376 var
377 type = tag.bytes[offset],
378 stringLength = tag.view.getUint16(offset + 1),
379 string;
380
381 equal(2, type, 'the script element is of string type');
382 equal(stringLength, expected.length, 'the script string length is correct');
383 string = asciiFromBytes(tag.bytes.subarray(offset + 3,
384 offset + 3 + stringLength));
385 equal(expected, string, 'the string value is "' + expected + '"');
386 };
387
388 testScriptEcmaArray = function(tag, start) {
389 var
390 numItems = tag.view.getUint32(start),
391 i = numItems,
392 offset = start + 4,
393 length,
394 type;
395
396 while (i--) {
397 length = tag.view.getUint16(offset);
398
399 // advance offset to the property value
400 offset += 2 + length;
401
402 type = tag.bytes[offset];
403 ok(type === 1 || type === 0,
404 'the ecma array property value type is number or boolean');
405 offset++;
406 if (type) {
407 // boolean
408 ok(tag.bytes[offset] === 0 || tag.bytes[offset] === 1,
409 'the script boolean value is 0 or 1');
410 offset++;
411 } else {
412 // number
413 ok(!isNaN(tag.view.getFloat64(offset)), 'the value is not NaN');
414 offset += 8;
415 }
416 }
417 equal(tag.bytes[offset], 0, 'the property array terminator is valid');
418 equal(tag.bytes[offset + 1], 0, 'the property array terminator is valid');
419 equal(tag.bytes[offset + 2], 9, 'the property array terminator is valid');
420 };
421
422 testScriptTag = function(tag) {
423 testScriptString(tag, 11, 'onMetaData');
424
425 // the onMetaData object is stored as an 'ecma array', an array with non-
426 // integer indices (i.e. a dictionary or hash-map).
427 equal(8, tag.bytes[24], 'onMetaData is of ecma array type');
428 testScriptEcmaArray(tag, 25);
429 };
430
431 test('the flv tags are well-formed', function() {
432 var
433 byte,
434 tag,
435 type,
436 minVideoPts,
437 maxVideoPts,
438 minAudioPts,
439 maxAudioPts,
440 currentPts = 0,
441 lastTime = 0;
442 parser.parseSegmentBinaryData(window.bcSegment);
443
444 minVideoPts = parser.stats.minVideoPts();
445 maxVideoPts = parser.stats.maxVideoPts();
446 minAudioPts = parser.stats.minAudioPts();
447 maxAudioPts = parser.stats.maxAudioPts();
448
449 while (parser.tagsAvailable()) {
450 tag = parser.getNextTag();
451 type = tag.bytes[0];
452
453 ok(tag.pts >= currentPts, 'presentation time stamps are increasing');
454 currentPts = tag.pts;
455
456 // generic flv headers
457 switch (type) {
458 case 8: ok(true, 'the type is audio');
459 ok(minAudioPts <= currentPts, 'not less than minimum audio PTS');
460 ok(maxAudioPts >= currentPts, 'not greater than max audio PTS');
461 break;
462 case 9: ok(true, 'the type is video');
463 ok(minVideoPts <= currentPts, 'not less than minimum video PTS');
464 ok(maxVideoPts >= currentPts, 'not greater than max video PTS');
465 break;
466 case 18: ok(true, 'the type is script');
467 break;
468 default: ok(false, 'the type (' + type + ') is unrecognized');
469 }
470
471 byte = (tag.view.getUint32(1) & 0xFFFFFF00) >>> 8;
472 equal(tag.bytes.byteLength - 11 - 4, byte, 'the size field is correct');
473
474 byte = tag.view.getUint32(5) & 0xFFFFFF00;
475 ok(byte >= lastTime,
476 'timestamp is increasing. last pts: ' + lastTime + ' this pts: ' + byte);
477 lastTime = byte;
478
479 // tag type-specific headers
480 ({
481 8: testAudioTag,
482 9: testVideoTag,
483 18: testScriptTag
484 })[type](tag);
485
486 // previous tag size
487 equal(tag.bytes.byteLength - 4,
488 tag.view.getUint32(tag.bytes.byteLength - 4),
489 'the size of the previous tag is correct');
490 }
491 });
492 })(window);
1 (function(window, videojs) {
2 'use strict';
3 /*
4 ======== A Handy Little QUnit Reference ========
5 http://api.qunitjs.com/
6
7 Test methods:
8 module(name, {[setup][ ,teardown]})
9 test(name, callback)
10 expect(numberOfAssertions)
11 stop(increment)
12 start(decrement)
13 Test assertions:
14 ok(value, [message])
15 equal(actual, expected, [message])
16 notEqual(actual, expected, [message])
17 deepEqual(actual, expected, [message])
18 notDeepEqual(actual, expected, [message])
19 strictEqual(actual, expected, [message])
20 notStrictEqual(actual, expected, [message])
21 throws(block, [expected], [message])
22 */
23 var
24 TransportPacketStream = videojs.mp2t.TransportPacketStream,
25 transportPacketStream,
26 TransportParseStream = videojs.mp2t.TransportParseStream,
27 transportParseStream,
28 ElementaryStream = videojs.mp2t.ElementaryStream,
29 elementaryStream,
30 H264Stream = videojs.mp2t.H264Stream,
31 h264Stream,
32 VideoSegmentStream = videojs.mp2t.VideoSegmentStream,
33 videoSegmentStream,
34 AacStream = videojs.mp2t.AacStream,
35 aacStream,
36 Transmuxer = videojs.mp2t.Transmuxer,
37 transmuxer,
38
39 MP2T_PACKET_LENGTH = videojs.mp2t.MP2T_PACKET_LENGTH,
40 H264_STREAM_TYPE = videojs.mp2t.H264_STREAM_TYPE,
41 ADTS_STREAM_TYPE = videojs.mp2t.ADTS_STREAM_TYPE,
42 packetize,
43
44 PAT,
45 PMT,
46 standalonePes,
47 validateTrack,
48 validateTrackFragment,
49
50 transportPacket,
51 videoPes,
52 audioPes;
53
54 module('MP2T Packet Stream', {
55 setup: function() {
56 transportPacketStream = new TransportPacketStream();
57 }
58 });
59
60 test('empty input does not error', function() {
61 transportPacketStream.push(new Uint8Array([]));
62 ok(true, 'did not throw');
63 });
64 test('parses a generic packet', function() {
65 var datas = [];
66 transportPacketStream.on('data', function(event) {
67 datas.push(event);
68 });
69 transportPacketStream.push(new Uint8Array(188));
70
71 equal(1, datas.length, 'fired one event');
72 equal(datas[0].byteLength, 188, 'delivered the packet');
73 });
74
75 test('buffers partial packets', function() {
76 var datas = [];
77 transportPacketStream.on('data', function(event) {
78 datas.push(event);
79 });
80 transportPacketStream.push(new Uint8Array(187));
81
82 equal(0, datas.length, 'did not fire an event');
83
84 transportPacketStream.push(new Uint8Array(189));
85 equal(2, datas.length, 'fired events');
86 equal(188, datas[0].byteLength, 'parsed the first packet');
87 equal(188, datas[1].byteLength, 'parsed the second packet');
88 });
89
90 test('parses multiple packets delivered at once', function() {
91 var datas = [];
92 transportPacketStream.on('data', function(event) {
93 datas.push(event);
94 });
95
96 transportPacketStream.push(new Uint8Array(188 * 3));
97 equal(3, datas.length, 'fired three events');
98 equal(188, datas[0].byteLength, 'parsed the first packet');
99 equal(188, datas[1].byteLength, 'parsed the second packet');
100 equal(188, datas[2].byteLength, 'parsed the third packet');
101 });
102
103 test('buffers extra after multiple packets', function() {
104 var datas = [];
105 transportPacketStream.on('data', function(event) {
106 datas.push(event);
107 });
108
109 transportPacketStream.push(new Uint8Array(188 * 2 + 10));
110 equal(2, datas.length, 'fired two events');
111 equal(188, datas[0].byteLength, 'parsed the first packet');
112 equal(188, datas[1].byteLength, 'parsed the second packet');
113
114 transportPacketStream.push(new Uint8Array(178));
115 equal(3, datas.length, 'fired a final event');
116 equal(188, datas[2].length, 'parsed the finel packet');
117 });
118
119 module('MP2T TransportParseStream', {
120 setup: function() {
121 transportPacketStream = new TransportPacketStream();
122 transportParseStream = new TransportParseStream();
123
124 transportPacketStream.pipe(transportParseStream);
125 }
126 });
127
128 test('emits an error on an invalid packet', function() {
129 var errors = [];
130 transportParseStream.on('error', function(error) {
131 errors.push(error);
132 });
133 transportParseStream.push(new Uint8Array(188));
134
135 equal(1, errors.length, 'emitted an error');
136 });
137
138 test('parses generic packet properties', function() {
139 var packet;
140 transportParseStream.on('data', function(data) {
141 packet = data;
142 });
143
144 transportParseStream.push(new Uint8Array([
145 0x47, // sync byte
146 // tei:0 pusi:1 tp:0 pid:0 0000 0000 0001 tsc:01 afc:10 cc:11 padding: 00
147 0x40, 0x01, 0x6c
148 ]));
149 ok(packet.payloadUnitStartIndicator, 'parsed payload_unit_start_indicator');
150 ok(packet.pid, 'parsed PID');
151 });
152
153 test('parses piped data events', function() {
154 var packet;
155 transportParseStream.on('data', function(data) {
156 packet = data;
157 });
158
159 transportParseStream.push(new Uint8Array([
160 0x47, // sync byte
161 // tei:0 pusi:1 tp:0 pid:0 0000 0000 0001 tsc:01 afc:10 cc:11 padding: 00
162 0x40, 0x01, 0x6c
163 ]));
164
165 ok(packet, 'parsed a packet');
166 });
167
168 test('parses a data packet with adaptation fields', function() {
169 var packet;
170 transportParseStream.on('data', function(data) {
171 packet = data;
172 });
173
174 transportParseStream.push(new Uint8Array([
175 0x47, // sync byte
176 // 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
177 0x40, 0x00, 0x6c, 0x00, 0x00, 0x10
178 ]));
179 strictEqual(packet.type, 'pat', 'parsed the packet type');
180 });
181
182 test('parses a PES packet', function() {
183 var packet;
184 transportParseStream.on('data', function(data) {
185 packet = data;
186 });
187
188 // setup a program map table
189 transportParseStream.programMapTable = {
190 0x0010: videojs.mp2t.H264_STREAM_TYPE
191 };
192
193 transportParseStream.push(new Uint8Array([
194 0x47, // sync byte
195 // tei:0 pusi:1 tp:0 pid:0 0000 0000 0010 tsc:01 afc:01 cc:11 padding:00
196 0x40, 0x02, 0x5c
197 ]));
198 strictEqual(packet.type, 'pes', 'parsed a PES packet');
199 });
200
201 test('parses packets with variable length adaptation fields and a payload', function() {
202 var packet;
203 transportParseStream.on('data', function(data) {
204 packet = data;
205 });
206
207 // setup a program map table
208 transportParseStream.programMapTable = {
209 0x0010: videojs.mp2t.H264_STREAM_TYPE
210 };
211
212 transportParseStream.push(new Uint8Array([
213 0x47, // sync byte
214 // 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
215 0x40, 0x02, 0x7c, 0x0c, 0x00, 0x01
216 ]));
217 strictEqual(packet.type, 'pes', 'parsed a PES packet');
218 });
219
220 /*
221 Packet Header:
222 | sb | tei pusi tp pid:5 | pid | tsc afc cc |
223 with af:
224 | afl | ... | <data> |
225 without af:
226 | <data> |
227
228 PAT:
229 | pf? | ... |
230 | tid | ssi '0' r sl:4 | sl | tsi:8 |
231 | tsi | r vn cni | sn | lsn |
232
233 with program_number == '0':
234 | pn | pn | r np:5 | np |
235 otherwise:
236 | pn | pn | r pmp:5 | pmp |
237 */
238
239 PAT = [
240 0x47, // sync byte
241 // tei:0 pusi:1 tp:0 pid:0 0000 0000 0000
242 0x40, 0x00,
243 // tsc:01 afc:01 cc:0000 pointer_field:0000 0000
244 0x50, 0x00,
245 // tid:0000 0000 ssi:0 0:0 r:00 sl:0000 0000 0000
246 0x00, 0x00, 0x00,
247 // tsi:0000 0000 0000 0000
248 0x00, 0x00,
249 // r:00 vn:00 000 cni:1 sn:0000 0000 lsn:0000 0000
250 0x01, 0x00, 0x00,
251 // pn:0000 0000 0000 0001
252 0x00, 0x01,
253 // r:000 pmp:0 0000 0010 0000
254 0x00, 0x10,
255 // crc32:0000 0000 0000 0000 0000 0000 0000 0000
256 0x00, 0x00, 0x00, 0x00
257 ];
258
259 test('parses the program map table pid from the program association table (PAT)', function() {
260 var packet;
261 transportParseStream.on('data', function(data) {
262 packet = data;
263 });
264
265 transportParseStream.push(new Uint8Array(PAT));
266 ok(packet, 'parsed a packet');
267 strictEqual(0x0010, transportParseStream.pmtPid, 'parsed PMT pid');
268 });
269
270 PMT = [
271 0x47, // sync byte
272 // tei:0 pusi:1 tp:0 pid:0 0000 0010 0000
273 0x40, 0x10,
274 // tsc:01 afc:01 cc:0000 pointer_field:0000 0000
275 0x50, 0x00,
276 // tid:0000 0010 ssi:0 0:0 r:00 sl:0000 0001 0111
277 0x02, 0x00, 0x17,
278 // pn:0000 0000 0000 0001
279 0x00, 0x01,
280 // r:00 vn:00 000 cni:1 sn:0000 0000 lsn:0000 0000
281 0x01, 0x00, 0x00,
282 // r:000 ppid:0 0011 1111 1111
283 0x03, 0xff,
284 // r:0000 pil:0000 0000 0000
285 0x00, 0x00,
286 // h264
287 // st:0001 1010 r:000 epid:0 0000 0001 0001
288 0x1b, 0x00, 0x11,
289 // r:0000 esil:0000 0000 0000
290 0x00, 0x00,
291 // adts
292 // st:0000 1111 r:000 epid:0 0000 0001 0010
293 0x0f, 0x00, 0x12,
294 // r:0000 esil:0000 0000 0000
295 0x00, 0x00,
296 // crc
297 0x00, 0x00, 0x00, 0x00
298 ];
299
300 test('parse the elementary streams from a program map table', function() {
301 var packet;
302 transportParseStream.on('data', function(data) {
303 packet = data;
304 });
305 transportParseStream.pmtPid = 0x0010;
306
307 transportParseStream.push(new Uint8Array(PMT.concat(0, 0, 0, 0, 0)));
308
309 ok(packet, 'parsed a packet');
310 ok(transportParseStream.programMapTable, 'parsed a program map');
311 strictEqual(0x1b, transportParseStream.programMapTable[0x11], 'associated h264 with pid 0x11');
312 strictEqual(0x0f, transportParseStream.programMapTable[0x12], 'associated adts with pid 0x12');
313 strictEqual(transportParseStream.programMapTable[0], undefined, 'ignored trailing stuffing bytes');
314 deepEqual(transportParseStream.programMapTable, packet.programMapTable, 'recorded the PMT');
315 });
316
317 test('parses an elementary stream packet with just a pts', function() {
318 var packet;
319 transportParseStream.on('data', function(data) {
320 packet = data;
321 });
322
323 transportParseStream.programMapTable = {
324 0x11: 0x1b // pid 0x11 is h264 data
325 };
326
327 transportParseStream.push(new Uint8Array([
328 0x47, // sync byte
329 // tei:0 pusi:1 tp:0 pid:0 0000 0001 0001
330 0x40, 0x11,
331 // tsc:01 afc:01 cc:0000
332 0x50,
333 // pscp:0000 0000 0000 0000 0000 0001
334 0x00, 0x00, 0x01,
335 // sid:0000 0000 ppl:0000 0000 0000 1001
336 0x00, 0x00, 0x09,
337 // 10 psc:00 pp:0 dai:1 c:0 ooc:0
338 0x84,
339 // pdf:10 ef:1 erf:0 dtmf:0 acif:0 pcf:0 pef:0
340 0xc0,
341 // phdl:0000 0101 '0010' pts:000 mb:1 pts:0000 0000
342 0x05, 0x21, 0x00,
343 // pts:0000 000 mb:1 pts:0000 0000 pts:0000 000 mb:1
344 0x01, 0x00, 0x01,
345 // "data":0101
346 0x11
347 ]));
348
349 ok(packet, 'parsed a packet');
350 equal('pes', packet.type, 'recognized a PES packet');
351 equal(0x1b, packet.streamType, 'tracked the stream_type');
352 equal(1, packet.data.byteLength, 'parsed a single data byte');
353 equal(0x11, packet.data[0], 'parsed the data');
354 equal(0, packet.pts, 'parsed the pts');
355 });
356
357 test('parses an elementary stream packet with a pts and dts', function() {
358 var packet;
359 transportParseStream.on('data', function(data) {
360 packet = data;
361 });
362
363 transportParseStream.programMapTable = {
364 0x11: 0x1b // pid 0x11 is h264 data
365 };
366
367 transportParseStream.push(new Uint8Array([
368 0x47, // sync byte
369 // tei:0 pusi:1 tp:0 pid:0 0000 0001 0001
370 0x40, 0x11,
371 // tsc:01 afc:01 cc:0000
372 0x50,
373 // pscp:0000 0000 0000 0000 0000 0001
374 0x00, 0x00, 0x01,
375 // sid:0000 0000 ppl:0000 0000 0000 1110
376 0x00, 0x00, 0x0e,
377 // 10 psc:00 pp:0 dai:1 c:0 ooc:0
378 0x84,
379 // pdf:11 ef:1 erf:0 dtmf:0 acif:0 pcf:0 pef:0
380 0xe0,
381 // phdl:0000 1010 '0011' pts:000 mb:1 pts:0000 0000
382 0x0a, 0x21, 0x00,
383 // pts:0000 000 mb:1 pts:0000 0000 pts:0000 100 mb:1
384 0x01, 0x00, 0x09,
385 // '0001' dts:000 mb:1 dts:0000 0000 dts:0000 000 mb:1
386 0x11, 0x00, 0x01,
387 // dts:0000 0000 dts:0000 010 mb:1
388 0x00, 0x05,
389 // "data":0101
390 0x11
391 ]));
392
393 ok(packet, 'parsed a packet');
394 equal('pes', packet.type, 'recognized a PES packet');
395 equal(0x1b, packet.streamType, 'tracked the stream_type');
396 equal(1, packet.data.byteLength, 'parsed a single data byte');
397 equal(0x11, packet.data[0], 'parsed the data');
398 equal(4 / 90, packet.pts, 'parsed the pts');
399 equal(2 / 90, packet.dts, 'parsed the dts');
400 });
401
402 /**
403 * Helper function to create transport stream PES packets
404 * @param pid {uint8} - the program identifier (PID)
405 * @param data {arraylike} - the payload bytes
406 * @payload first {boolean} - true if this PES should be a payload
407 * unit start
408 */
409 transportPacket = function(pid, data, first) {
410 var
411 adaptationFieldLength = 188 - data.length - (first ? 15 : 14),
412 // transport_packet(), Rec. ITU-T H.222.0, Table 2-2
413 result = [
414 // sync byte
415 0x47,
416 // tei:0 pusi:1 tp:0 pid:0 0000 0001 0001
417 0x40, pid,
418 // tsc:01 afc:11 cc:0000
419 0x70
420 ].concat([
421 // afl
422 adaptationFieldLength & 0xff,
423 // di:0 rai:0 espi:0 pf:0 of:0 spf:0 tpdf:0 afef:0
424 0x00
425 ]),
426 i;
427
428 i = adaptationFieldLength - 1;
429 while (i--) {
430 // stuffing_bytes
431 result.push(0xff);
432 }
433
434 // PES_packet(), Rec. ITU-T H.222.0, Table 2-21
435 result = result.concat([
436 // pscp:0000 0000 0000 0000 0000 0001
437 0x00, 0x00, 0x01,
438 // sid:0000 0000 ppl:0000 0000 0000 0101
439 0x00, 0x00, 0x05,
440 // 10 psc:00 pp:0 dai:1 c:0 ooc:0
441 0x84,
442 // pdf:00 ef:1 erf:0 dtmf:0 acif:0 pcf:0 pef:0
443 0x20,
444 // phdl:0000 0000
445 0x00
446 ]);
447 if (first) {
448 result.push(0x00);
449 }
450 return result.concat(data);
451 };
452
453 /**
454 * Helper function to create video PES packets
455 * @param data {arraylike} - the payload bytes
456 * @payload first {boolean} - true if this PES should be a payload
457 * unit start
458 */
459 videoPes = function(data, first) {
460 return transportPacket(0x11, [
461 // NAL unit start code
462 0x00, 0x00, 0x01
463 ].concat(data), first);
464 };
465 standalonePes = videoPes([0xaf, 0x01], true);
466
467 /**
468 * Helper function to create audio PES packets
469 * @param data {arraylike} - the payload bytes
470 * @payload first {boolean} - true if this PES should be a payload
471 * unit start
472 */
473 audioPes = function(data, first) {
474 var frameLength = data.length + 7;
475 return transportPacket(0x12, [
476 0xff, 0xf1, // no CRC
477 0x10, // AAC Main, 44.1KHz
478 0xb0 | ((frameLength & 0x1800) >> 11), // 2 channels
479 (frameLength & 0x7f8) >> 3,
480 ((frameLength & 0x07) << 5) + 7, // frame length in bytes
481 0x00 // one AAC per ADTS frame
482 ].concat(data), first);
483 };
484
485 test('parses an elementary stream packet without a pts or dts', function() {
486
487 var packet;
488 transportParseStream.on('data', function(data) {
489 packet = data;
490 });
491
492 // pid 0x11 is h264 data
493 transportParseStream.programMapTable = {
494 0x11: H264_STREAM_TYPE
495 };
496
497 transportParseStream.push(new Uint8Array(standalonePes));
498
499 ok(packet, 'parsed a packet');
500 equal('pes', packet.type, 'recognized a PES packet');
501 equal(0x1b, packet.streamType, 'tracked the stream_type');
502 equal(2 + 4, packet.data.byteLength, 'parsed two data bytes');
503 equal(0xaf, packet.data[packet.data.length - 2], 'parsed the first data byte');
504 equal(0x01, packet.data[packet.data.length - 1], 'parsed the second data byte');
505 ok(!packet.pts, 'did not parse a pts');
506 ok(!packet.dts, 'did not parse a dts');
507 });
508
509 module('MP2T ElementaryStream', {
510 setup: function() {
511 elementaryStream = new ElementaryStream();
512 }
513 });
514
515 packetize = function(data) {
516 var packet = new Uint8Array(MP2T_PACKET_LENGTH);
517 packet.set(data);
518 return packet;
519 };
520
521 test('parses metadata events from PSI packets', function() {
522 var
523 metadatas = [],
524 datas = 0,
525 sortById = function(left, right) {
526 return left.id - right.id;
527 };
528 elementaryStream.on('data', function(data) {
529 if (data.type === 'metadata') {
530 metadatas.push(data);
531 }
532 datas++;
533 });
534 elementaryStream.push({
535 type: 'pat'
536 });
537 elementaryStream.push({
538 type: 'pmt',
539 programMapTable: {
540 1: 0x1b,
541 2: 0x0f
542 }
543 });
544
545 equal(1, datas, 'data fired');
546 equal(1, metadatas.length, 'metadata generated');
547 metadatas[0].tracks.sort(sortById);
548 deepEqual(metadatas[0].tracks, [{
549 id: 1,
550 codec: 'avc',
551 type: 'video'
552 }, {
553 id: 2,
554 codec: 'adts',
555 type: 'audio'
556 }], 'identified two tracks');
557 });
558
559 test('parses standalone program stream packets', function() {
560 var packets = [];
561 elementaryStream.on('data', function(packet) {
562 packets.push(packet);
563 });
564 elementaryStream.push({
565 type: 'pes',
566 streamType: ADTS_STREAM_TYPE,
567 payloadUnitStartIndicator: true,
568 pts: 7,
569 dts: 8,
570 data: new Uint8Array(19)
571 });
572 elementaryStream.end();
573
574 equal(1, packets.length, 'built one packet');
575 equal('audio', packets[0].type, 'identified audio data');
576 equal(19, packets[0].data.byteLength, 'parsed the correct payload size');
577 });
578
579 test('aggregates program stream packets from the transport stream', function() {
580 var events = [];
581 elementaryStream.on('data', function(event) {
582 events.push(event);
583 });
584
585 elementaryStream.push({
586 type: 'pes',
587 streamType: H264_STREAM_TYPE,
588 payloadUnitStartIndicator: true,
589 pts: 7,
590 dts: 8,
591 data: new Uint8Array(7)
592 });
593 equal(0, events.length, 'buffers partial packets');
594
595 elementaryStream.push({
596 type: 'pes',
597 streamType: H264_STREAM_TYPE,
598 data: new Uint8Array(13)
599 });
600 elementaryStream.end();
601 equal(1, events.length, 'built one packet');
602 equal('video', events[0].type, 'identified video data');
603 equal(events[0].pts, 7, 'passed along the pts');
604 equal(events[0].dts, 8, 'passed along the dts');
605 equal(20, events[0].data.byteLength, 'concatenated transport packets');
606 });
607
608 test('buffers audio and video program streams individually', function() {
609 var events = [];
610 elementaryStream.on('data', function(event) {
611 events.push(event);
612 });
613
614 elementaryStream.push({
615 type: 'pes',
616 payloadUnitStartIndicator: true,
617 streamType: H264_STREAM_TYPE,
618 data: new Uint8Array(1)
619 });
620 elementaryStream.push({
621 type: 'pes',
622 payloadUnitStartIndicator: true,
623 streamType: ADTS_STREAM_TYPE,
624 data: new Uint8Array(1)
625 });
626 equal(0, events.length, 'buffers partial packets');
627
628 elementaryStream.push({
629 type: 'pes',
630 streamType: H264_STREAM_TYPE,
631 data: new Uint8Array(1)
632 });
633 elementaryStream.push({
634 type: 'pes',
635 streamType: ADTS_STREAM_TYPE,
636 data: new Uint8Array(1)
637 });
638 elementaryStream.end();
639 equal(2, events.length, 'parsed a complete packet');
640 equal('video', events[0].type, 'identified video data');
641 equal('audio', events[1].type, 'identified audio data');
642 });
643
644 test('flushes the buffered packets when a new one of that type is started', function() {
645 var packets = [];
646 elementaryStream.on('data', function(packet) {
647 packets.push(packet);
648 });
649 elementaryStream.push({
650 type: 'pes',
651 payloadUnitStartIndicator: true,
652 streamType: H264_STREAM_TYPE,
653 data: new Uint8Array(1)
654 });
655 elementaryStream.push({
656 type: 'pes',
657 payloadUnitStartIndicator: true,
658 streamType: ADTS_STREAM_TYPE,
659 data: new Uint8Array(7)
660 });
661 elementaryStream.push({
662 type: 'pes',
663 streamType: H264_STREAM_TYPE,
664 data: new Uint8Array(1)
665 });
666 equal(0, packets.length, 'buffers packets by type');
667
668 elementaryStream.push({
669 type: 'pes',
670 payloadUnitStartIndicator: true,
671 streamType: H264_STREAM_TYPE,
672 data: new Uint8Array(1)
673 });
674 equal(1, packets.length, 'built one packet');
675 equal('video', packets[0].type, 'identified video data');
676 equal(2, packets[0].data.byteLength, 'concatenated packets');
677
678 elementaryStream.end();
679 equal(3, packets.length, 'built tow more packets');
680 equal('video', packets[1].type, 'identified video data');
681 equal(1, packets[1].data.byteLength, 'parsed the video payload');
682 equal('audio', packets[2].type, 'identified audio data');
683 equal(7, packets[2].data.byteLength, 'parsed the audio payload');
684 });
685
686 test('drops packets with unknown stream types', function() {
687 var packets = [];
688 elementaryStream.on('data', function(packet) {
689 packets.push(packet);
690 });
691 elementaryStream.push({
692 type: 'pes',
693 payloadUnitStartIndicator: true,
694 data: new Uint8Array(1)
695 });
696 elementaryStream.push({
697 type: 'pes',
698 payloadUnitStartIndicator: true,
699 data: new Uint8Array(1)
700 });
701
702 equal(packets.length, 0, 'ignored unknown packets');
703 });
704
705 module('H264 Stream', {
706 setup: function() {
707 h264Stream = new H264Stream();
708 }
709 });
710
711 test('unpacks nal units from simple byte stream framing', function() {
712 var data;
713 h264Stream.on('data', function(event) {
714 data = event;
715 });
716
717 // the simplest byte stream framing:
718 h264Stream.push({
719 type: 'video',
720 data: new Uint8Array([
721 0x00, 0x00, 0x00, 0x01,
722 0x09, 0x07,
723 0x00, 0x00, 0x01
724 ])
725 });
726
727 ok(data, 'generated a data event');
728 equal(data.nalUnitType, 'access_unit_delimiter_rbsp', 'identified an access unit delimiter');
729 equal(data.data.length, 2, 'calculated nal unit length');
730 equal(data.data[1], 7, 'read a payload byte');
731 });
732
733 test('unpacks nal units from byte streams split across pushes', function() {
734 var data;
735 h264Stream.on('data', function(event) {
736 data = event;
737 });
738
739 // handles byte streams split across pushes
740 h264Stream.push({
741 type: 'video',
742 data: new Uint8Array([
743 0x00, 0x00, 0x00, 0x01,
744 0x09, 0x07, 0x06, 0x05,
745 0x04
746 ])
747 });
748 ok(!data, 'buffers NAL units across events');
749
750 h264Stream.push({
751 type: 'video',
752 data: new Uint8Array([
753 0x03, 0x02, 0x01,
754 0x00, 0x00, 0x01
755 ])
756 });
757 ok(data, 'generated a data event');
758 equal(data.nalUnitType, 'access_unit_delimiter_rbsp', 'identified an access unit delimiter');
759 equal(data.data.length, 8, 'calculated nal unit length');
760 equal(data.data[1], 7, 'read a payload byte');
761 });
762
763 test('buffers nal unit trailing zeros across pushes', function() {
764 var data = [];
765 h264Stream.on('data', function(event) {
766 data.push(event);
767 });
768
769 // lots of zeros after the nal, stretching into the next push
770 h264Stream.push({
771 type: 'video',
772 data: new Uint8Array([
773 0x00, 0x00, 0x00, 0x01,
774 0x09, 0x07, 0x00, 0x00,
775 0x00, 0x00, 0x00, 0x00,
776 0x00, 0x00, 0x00, 0x00,
777 0x00
778 ])
779 });
780 equal(data.length, 1, 'delivered the first nal');
781
782 h264Stream.push({
783 type: 'video',
784 data: new Uint8Array([
785 0x00, 0x00,
786 0x00, 0x00, 0x01,
787 0x09, 0x06,
788 0x00, 0x00, 0x01
789 ])
790 });
791 equal(data.length, 2, 'generated data events');
792 equal(data[0].data.length, 2, 'ignored trailing zeros');
793 equal(data[0].data[0], 0x09, 'found the first nal start');
794 equal(data[1].data.length, 2, 'found the following nal start');
795 equal(data[1].data[0], 0x09, 'found the second nal start');
796 });
797
798 test('unpacks nal units from byte streams with split sync points', function() {
799 var data;
800 h264Stream.on('data', function(event) {
801 data = event;
802 });
803
804 // handles sync points split across pushes
805 h264Stream.push({
806 type: 'video',
807 data: new Uint8Array([
808 0x00, 0x00, 0x00, 0x01,
809 0x09, 0x07,
810 0x00])
811 });
812 ok(!data, 'buffers NAL units across events');
813
814 h264Stream.push({
815 type: 'video',
816 data: new Uint8Array([
817 0x00, 0x01
818 ])
819 });
820 ok(data, 'generated a data event');
821 equal(data.nalUnitType, 'access_unit_delimiter_rbsp', 'identified an access unit delimiter');
822 equal(data.data.length, 2, 'calculated nal unit length');
823 equal(data.data[1], 7, 'read a payload byte');
824 });
825
826 test('parses nal unit types', function() {
827 var data;
828 h264Stream.on('data', function(event) {
829 data = event;
830 });
831
832 h264Stream.push({
833 type: 'video',
834 data: new Uint8Array([
835 0x00, 0x00, 0x00, 0x01,
836 0x09
837 ])
838 });
839 h264Stream.end();
840
841 ok(data, 'generated a data event');
842 equal(data.nalUnitType, 'access_unit_delimiter_rbsp', 'identified an access unit delimiter');
843
844 data = null;
845 h264Stream.push({
846 type: 'video',
847 data: new Uint8Array([
848 0x00, 0x00, 0x00, 0x01,
849 0x07,
850 0x27, 0x42, 0xe0, 0x0b,
851 0xa9, 0x18, 0x60, 0x9d,
852 0x80, 0x35, 0x06, 0x01,
853 0x06, 0xb6, 0xc2, 0xb5,
854 0xef, 0x7c, 0x04
855 ])
856 });
857 h264Stream.end();
858 ok(data, 'generated a data event');
859 equal(data.nalUnitType, 'seq_parameter_set_rbsp', 'identified a sequence parameter set');
860
861 data = null;
862 h264Stream.push({
863 type: 'video',
864 data: new Uint8Array([
865 0x00, 0x00, 0x00, 0x01,
866 0x08, 0x01
867 ])
868 });
869 h264Stream.end();
870 ok(data, 'generated a data event');
871 equal(data.nalUnitType, 'pic_parameter_set_rbsp', 'identified a picture parameter set');
872
873 data = null;
874 h264Stream.push({
875 type: 'video',
876 data: new Uint8Array([
877 0x00, 0x00, 0x00, 0x01,
878 0x05, 0x01
879 ])
880 });
881 h264Stream.end();
882 ok(data, 'generated a data event');
883 equal(data.nalUnitType, 'slice_layer_without_partitioning_rbsp_idr', 'identified a key frame');
884 });
885
886 // MP4 expects H264 (aka AVC) data to be in storage format. Storage
887 // format is optimized for reliable, random-access media in contrast
888 // to the byte stream format that retransmits metadata regularly to
889 // allow decoders to quickly begin operation from wherever in the
890 // broadcast they begin receiving.
891 // Details on the byte stream format can be found in Annex B of
892 // Recommendation ITU-T H.264.
893 // The storage format is described in ISO/IEC 14496-15
894 test('strips byte stream framing during parsing', function() {
895 var data = [];
896 h264Stream.on('data', function(event) {
897 data.push(event);
898 });
899
900 h264Stream.push({
901 type: 'video',
902 data: new Uint8Array([
903 // -- NAL unit start
904 // zero_byte
905 0x00,
906 // start_code_prefix_one_3bytes
907 0x00, 0x00, 0x01,
908 // nal_unit_type (picture parameter set)
909 0x08,
910 // fake data
911 0x01, 0x02, 0x03, 0x04,
912 0x05, 0x06, 0x07,
913 // trailing_zero_8bits * 5
914 0x00, 0x00, 0x00, 0x00,
915 0x00,
916
917 // -- NAL unit start
918 // zero_byte
919 0x00,
920 // start_code_prefix_one_3bytes
921 0x00, 0x00, 0x01,
922 // nal_unit_type (access_unit_delimiter_rbsp)
923 0x09,
924 // fake data
925 0x06, 0x05, 0x04, 0x03,
926 0x02, 0x01, 0x00
927 ])
928 });
929 h264Stream.end();
930
931 equal(data.length, 2, 'parsed two NAL units');
932 deepEqual(new Uint8Array([
933 0x08,
934 0x01, 0x02, 0x03, 0x04,
935 0x05, 0x06, 0x07
936 ]), new Uint8Array(data[0].data), 'parsed the first NAL unit');
937 deepEqual(new Uint8Array([
938 0x09,
939 0x06, 0x05, 0x04, 0x03,
940 0x02, 0x01, 0x00
941 ]), new Uint8Array(data[1].data), 'parsed the second NAL unit');
942 });
943
944 module('VideoSegmentStream', {
945 setup: function() {
946 videoSegmentStream = new VideoSegmentStream({});
947 }
948 });
949
950 // see ISO/IEC 14496-15, Section 5 "AVC elementary streams and sample definitions"
951 test('concatenates NAL units into AVC elementary streams', function() {
952 var segment, boxes;
953 videoSegmentStream.on('data', function(data) {
954 segment = data;
955 });
956 videoSegmentStream.push({
957 data: new Uint8Array([
958 0x08,
959 0x01, 0x02, 0x03
960 ])
961 });
962 videoSegmentStream.push({
963 data: new Uint8Array([
964 0x08,
965 0x04, 0x03, 0x02, 0x01, 0x00
966 ])
967 });
968 videoSegmentStream.end();
969
970 ok(segment, 'generated a data event');
971 boxes = videojs.inspectMp4(segment);
972 equal(boxes[1].byteLength,
973 (4 + 4) + (4 + 6),
974 'wrote the correct number of bytes');
975 deepEqual(new Uint8Array(segment.subarray(boxes[0].size + 8)), new Uint8Array([
976 0, 0, 0, 4,
977 0x08, 0x01, 0x02, 0x03,
978 0, 0, 0, 6,
979 0x08, 0x04, 0x03, 0x02, 0x01, 0x00
980 ]), 'wrote an AVC stream into the mdat');
981 });
982
983 test('scales DTS values from milliseconds to 90kHz', function() {
984 var segment, boxes, samples;
985 videoSegmentStream.on('data', function(data) {
986 segment = data;
987 });
988 videoSegmentStream.push({
989 data: new Uint8Array([0x09, 0x01]),
990 nalUnitType: 'access_unit_delimiter_rbsp',
991 dts: 1
992 });
993 videoSegmentStream.push({
994 data: new Uint8Array([0x09, 0x01]),
995 nalUnitType: 'access_unit_delimiter_rbsp',
996 dts: 2
997 });
998 videoSegmentStream.push({
999 data: new Uint8Array([0x09, 0x01]),
1000 nalUnitType: 'access_unit_delimiter_rbsp',
1001 dts: 4
1002 });
1003 videoSegmentStream.end();
1004
1005 boxes = videojs.inspectMp4(segment);
1006 samples = boxes[0].boxes[1].boxes[2].samples;
1007 equal(samples.length, 3, 'generated two samples');
1008 equal(samples[0].duration, 1 * 90, 'multiplied DTS duration by 90');
1009 equal(samples[1].duration, 2 * 90, 'multiplied DTS duration by 90');
1010 equal(samples[2].duration, 2 * 90, 'inferred the final sample duration');
1011 });
1012
1013 module('AAC Stream', {
1014 setup: function() {
1015 aacStream = new AacStream();
1016 }
1017 });
1018
1019 test('generates AAC frame events from ADTS bytes', function() {
1020 var frames = [];
1021 aacStream.on('data', function(frame) {
1022 frames.push(frame);
1023 });
1024 aacStream.push({
1025 type: 'audio',
1026 data: new Uint8Array([
1027 0xff, 0xf1, // no CRC
1028 0x10, // AAC Main, 44.1KHz
1029 0xbc, 0x01, 0x20, // 2 channels, frame length 9 bytes
1030 0x00, // one AAC per ADTS frame
1031 0x12, 0x34, // AAC payload
1032 0x56, 0x78 // extra junk that should be ignored
1033 ])
1034 });
1035
1036 equal(frames.length, 1, 'generated one frame');
1037 deepEqual(new Uint8Array(frames[0].data),
1038 new Uint8Array([0x12, 0x34]),
1039 'extracted AAC frame');
1040 equal(frames[0].channelcount, 2, 'parsed channelcount');
1041 equal(frames[0].samplerate, 44100, 'parsed samplerate');
1042
1043 // Chrome only supports 8, 16, and 32 bit sample sizes. Assuming the
1044 // default value of 16 in ISO/IEC 14496-12 AudioSampleEntry is
1045 // acceptable.
1046 equal(frames[0].samplesize, 16, 'parsed samplesize');
1047 });
1048
1049 test('parses across packets', function() {
1050
1051 var frames = [];
1052 aacStream.on('data', function(frame) {
1053 frames.push(frame);
1054 });
1055 aacStream.push({
1056 type: 'audio',
1057 data: new Uint8Array([
1058 0xff, 0xf1, // no CRC
1059 0x10, // AAC Main, 44.1KHz
1060 0xbc, 0x01, 0x20, // 2 channels, frame length 9 bytes
1061 0x00, // one AAC per ADTS frame
1062 0x12, 0x34 // AAC payload 1
1063 ])
1064 });
1065 aacStream.push({
1066 type: 'audio',
1067 data: new Uint8Array([
1068 0xff, 0xf1, // no CRC
1069 0x10, // AAC Main, 44.1KHz
1070 0xbc, 0x01, 0x20, // 2 channels, frame length 9 bytes
1071 0x00, // one AAC per ADTS frame
1072 0x9a, 0xbc, // AAC payload 2
1073 0xde, 0xf0 // extra junk that should be ignored
1074 ])
1075 });
1076
1077 equal(frames.length, 2, 'parsed two frames');
1078 deepEqual(new Uint8Array(frames[1].data),
1079 new Uint8Array([0x9a, 0xbc]),
1080 'extracted the second AAC frame');
1081 });
1082
1083 // not handled: ADTS with CRC
1084 // ADTS with payload broken across push events
1085
1086 module('Transmuxer', {
1087 setup: function() {
1088 transmuxer = new Transmuxer();
1089 }
1090 });
1091
1092 test('generates a video init segment', function() {
1093 var segments = [];
1094 transmuxer.on('data', function(segment) {
1095 segments.push(segment);
1096 });
1097 transmuxer.push(packetize(PAT));
1098 transmuxer.push(packetize(PMT));
1099 transmuxer.push(packetize(videoPes([
1100 0x08, 0x01 // pic_parameter_set_rbsp
1101 ], true)));
1102 transmuxer.push(packetize(videoPes([
1103 0x07, // seq_parameter_set_rbsp
1104 0x27, 0x42, 0xe0, 0x0b,
1105 0xa9, 0x18, 0x60, 0x9d,
1106 0x80, 0x53, 0x06, 0x01,
1107 0x06, 0xb6, 0xc2, 0xb5,
1108 0xef, 0x7c, 0x04
1109 ], false)));
1110 transmuxer.end();
1111
1112 equal(segments.length, 2, 'generated init and media segments');
1113 ok(segments[0].data, 'wrote data in the init segment');
1114 equal(segments[0].type, 'video', 'video is the segment type');
1115 });
1116
1117 test('generates an audio init segment', function() {
1118 var segments = [];
1119 transmuxer.on('data', function(segment) {
1120 segments.push(segment);
1121 });
1122 transmuxer.push(packetize(PAT));
1123 transmuxer.push(packetize(PMT));
1124 transmuxer.push(packetize(audioPes([
1125 0x00, 0x01
1126 ], true)));
1127 transmuxer.end();
1128
1129 equal(segments.length, 2, 'generated init and media segments');
1130 ok(segments[0].data, 'wrote data in the init segment');
1131 equal(segments[0].type, 'audio', 'audio is the segment type');
1132 });
1133
1134 test('buffers video samples until ended', function() {
1135 var samples = [], boxes;
1136 transmuxer.on('data', function(data) {
1137 samples.push(data);
1138 });
1139 transmuxer.push(packetize(PAT));
1140 transmuxer.push(packetize(PMT));
1141
1142 // buffer a NAL
1143 transmuxer.push(packetize(videoPes([0x09, 0x01], true)));
1144 transmuxer.push(packetize(videoPes([0x00, 0x02])));
1145
1146 // add an access_unit_delimiter_rbsp
1147 transmuxer.push(packetize(videoPes([0x09, 0x03])));
1148 transmuxer.push(packetize(videoPes([0x00, 0x04])));
1149 transmuxer.push(packetize(videoPes([0x00, 0x05])));
1150
1151 // flush everything
1152 transmuxer.end();
1153 equal(samples.length, 1, 'emitted one event');
1154 boxes = videojs.inspectMp4(samples[0].data);
1155 equal(boxes.length, 2, 'generated two boxes');
1156 equal(boxes[0].type, 'moof', 'the first box is a moof');
1157 equal(boxes[1].type, 'mdat', 'the second box is a mdat');
1158 deepEqual(new Uint8Array(samples[0].data.subarray(boxes[0].size + 8)),
1159 new Uint8Array([
1160 0, 0, 0, 2,
1161 0x09, 0x01,
1162 0, 0, 0, 2,
1163 0x00, 0x02,
1164 0, 0, 0, 2,
1165 0x09, 0x03,
1166 0, 0, 0, 2,
1167 0x00, 0x04,
1168 0, 0, 0, 2,
1169 0x00, 0x05]),
1170 'concatenated NALs into an mdat');
1171 });
1172
1173 validateTrack = function(track, metadata) {
1174 var mdia, handlerType;
1175 equal(track.type, 'trak', 'wrote the track type');
1176 equal(track.boxes.length, 2, 'wrote track children');
1177 equal(track.boxes[0].type, 'tkhd', 'wrote the track header');
1178 if (metadata) {
1179 if (metadata.trackId) {
1180 equal(track.boxes[0].trackId, metadata.trackId, 'wrote the track id');
1181 }
1182 if (metadata.width) {
1183 equal(track.boxes[0].width, metadata.width, 'wrote the width');
1184 }
1185 if (metadata.height) {
1186 equal(track.boxes[0].height, metadata.height, 'wrote the height');
1187 }
1188 }
1189
1190 mdia = track.boxes[1];
1191 equal(mdia.type, 'mdia', 'wrote the media');
1192 equal(mdia.boxes.length, 3, 'wrote the mdia children');
1193
1194 equal(mdia.boxes[0].type, 'mdhd', 'wrote the media header');
1195 equal(mdia.boxes[0].language, 'und', 'the language is undefined');
1196 equal(mdia.boxes[0].duration, 0xffffffff, 'the duration is at maximum');
1197
1198 equal(mdia.boxes[1].type, 'hdlr', 'wrote the media handler');
1199 handlerType = mdia.boxes[1].handlerType;
1200
1201 equal(mdia.boxes[2].type, 'minf', 'wrote the media info');
1202 };
1203
1204 validateTrackFragment = function(track, segment, metadata) {
1205 var tfhd, trun, sdtp, i, j, sample, nalUnitType;
1206 equal(track.type, 'traf', 'wrote a track fragment');
1207 equal(track.boxes.length, 4, 'wrote four track fragment children');
1208 tfhd = track.boxes[0];
1209 equal(tfhd.type, 'tfhd', 'wrote a track fragment header');
1210 equal(tfhd.trackId, metadata.trackId, 'wrote the track id');
1211
1212 equal(track.boxes[1].type,
1213 'tfdt',
1214 'wrote a track fragment decode time box');
1215 ok(track.boxes[1].baseMediaDecodeTime >= 0, 'base decode time is non-negative');
1216
1217 trun = track.boxes[2];
1218 ok(trun.dataOffset >= 0, 'set data offset');
1219 equal(trun.dataOffset,
1220 metadata.mdatOffset + 8,
1221 'trun data offset is the size of the moof');
1222 ok(trun.samples.length > 0, 'generated media samples');
1223 for (i = 0, j = trun.dataOffset; i < trun.samples.length; i++) {
1224 sample = trun.samples[i];
1225 ok(sample.duration > 0, 'wrote a positive duration for sample ' + i);
1226 ok(sample.size > 0, 'wrote a positive size for sample ' + i);
1227 ok(sample.compositionTimeOffset >= 0,
1228 'wrote a positive composition time offset for sample ' + i);
1229 ok(sample.flags, 'wrote sample flags');
1230 equal(sample.flags.isLeading, 0, 'the leading nature is unknown');
1231
1232 notEqual(sample.flags.dependsOn, 0, 'sample dependency is not unknown');
1233 notEqual(sample.flags.dependsOn, 4, 'sample dependency is valid');
1234 nalUnitType = segment[j + 4] & 0x1F;
1235 equal(nalUnitType, 9, 'samples begin with an access_unit_delimiter_rbsp');
1236
1237 equal(sample.flags.isDependedOn, 0, 'dependency of other samples is unknown');
1238 equal(sample.flags.hasRedundancy, 0, 'sample redundancy is unknown');
1239 equal(sample.flags.degradationPriority, 0, 'sample degradation priority is zero');
1240
1241 j += sample.size; // advance to the next sample in the mdat
1242 }
1243
1244 sdtp = track.boxes[3];
1245 equal(trun.samples.length,
1246 sdtp.samples.length,
1247 'wrote an equal number of trun and sdtp samples');
1248 for (i = 0; i < sdtp.samples.length; i++) {
1249 sample = sdtp.samples[i];
1250 notEqual(sample.dependsOn, 0, 'sample dependency is not unknown');
1251 equal(trun.samples[i].flags.dependsOn,
1252 sample.dependsOn,
1253 'wrote a consistent dependsOn');
1254 equal(trun.samples[i].flags.isDependedOn,
1255 sample.isDependedOn,
1256 'wrote a consistent isDependedOn');
1257 equal(trun.samples[i].flags.hasRedundancy,
1258 sample.hasRedundancy,
1259 'wrote a consistent hasRedundancy');
1260 }
1261 };
1262
1263 test('parses an example mp2t file and generates media segments', function() {
1264 var
1265 videoSegments = [],
1266 audioSegments = [],
1267 sequenceNumber = window.Infinity,
1268 i, boxes, mfhd;
1269
1270 transmuxer.on('data', function(segment) {
1271 if (segment.type === 'video') {
1272 videoSegments.push(segment);
1273 } else if (segment.type === 'audio') {
1274 audioSegments.push(segment);
1275 }
1276 });
1277 transmuxer.push(window.bcSegment);
1278 transmuxer.end();
1279
1280 equal(videoSegments.length, 2, 'generated two video segments');
1281 equal(audioSegments.length, 2, 'generated two audio segments');
1282
1283 boxes = videojs.inspectMp4(videoSegments[0].data);
1284 equal(boxes.length, 2, 'video init segments are composed of two boxes');
1285 equal(boxes[0].type, 'ftyp', 'the first box is an ftyp');
1286 equal(boxes[1].type, 'moov', 'the second box is a moov');
1287 equal(boxes[1].boxes[0].type, 'mvhd', 'generated an mvhd');
1288 validateTrack(boxes[1].boxes[1], {
1289 trackId: 256,
1290 width: 388,
1291 height: 300
1292 });
1293 // validateTrack(boxes[1].boxes[2], {
1294 // trackId: 257
1295 // });
1296 // equal(boxes[1].boxes[3].type, 'mvex', 'generated an mvex');
1297
1298 boxes = videojs.inspectMp4(videoSegments[1].data);
1299 ok(boxes.length > 0, 'video media segments are not empty');
1300 ok(boxes.length % 2 === 0, 'video media segments are composed of pairs of boxes');
1301 for (i = 0; i < boxes.length; i += 2) {
1302 equal(boxes[i].type, 'moof', 'first box is a moof');
1303 equal(boxes[i].boxes.length, 2, 'the moof has two children');
1304
1305 mfhd = boxes[i].boxes[0];
1306 equal(mfhd.type, 'mfhd', 'mfhd is a child of the moof');
1307 ok(mfhd.sequenceNumber < sequenceNumber, 'sequence numbers are increasing');
1308 sequenceNumber = mfhd.sequenceNumber;
1309
1310 equal(boxes[i + 1].type, 'mdat', 'second box is an mdat');
1311 validateTrackFragment(boxes[i].boxes[1], videoSegments[1].data, {
1312 trackId: 256,
1313 width: 388,
1314 height: 300,
1315 mdatOffset: boxes[0].size
1316 });
1317 }
1318 });
1319
1320 })(window, window.videojs);
...@@ -19,13 +19,6 @@ ...@@ -19,13 +19,6 @@
19 <script src="../src/videojs-hls.js"></script> 19 <script src="../src/videojs-hls.js"></script>
20 <script src="../src/xhr.js"></script> 20 <script src="../src/xhr.js"></script>
21 <script src="../src/stream.js"></script> 21 <script src="../src/stream.js"></script>
22 <script src="../src/flv-tag.js"></script>
23 <script src="../src/exp-golomb.js"></script>
24 <script src="../src/h264-extradata.js"></script>
25 <script src="../src/h264-stream.js"></script>
26 <script src="../src/aac-stream.js"></script>
27 <script src="../src/metadata-stream.js"></script>
28 <script src="../src/segment-parser.js"></script>
29 22
30 <!-- M3U8 --> 23 <!-- M3U8 -->
31 <script src="../src/m3u8/m3u8-parser.js"></script> 24 <script src="../src/m3u8/m3u8-parser.js"></script>
...@@ -42,11 +35,6 @@ ...@@ -42,11 +35,6 @@
42 <script src="tsSegment-bc.js"></script> 35 <script src="tsSegment-bc.js"></script>
43 <script src="../src/bin-utils.js"></script> 36 <script src="../src/bin-utils.js"></script>
44 37
45 <!-- mp4 utilities -->
46 <script src="../src/mp4-generator.js"></script>
47 <script src="../src/transmuxer.js"></script>
48 <script src="muxer/js/mp4-inspector.js"></script>
49
50 <!-- Test cases --> 38 <!-- Test cases -->
51 <script> 39 <script>
52 module('environment'); 40 module('environment');
...@@ -56,18 +44,10 @@ ...@@ -56,18 +44,10 @@
56 }); 44 });
57 </script> 45 </script>
58 <script src="videojs-hls_test.js"></script> 46 <script src="videojs-hls_test.js"></script>
59 <script src="segment-parser.js"></script>
60 <script src="h264-stream_test.js"></script>
61 <script src="exp-golomb_test.js"></script>
62 <script src="flv-tag_test.js"></script>
63 <script src="metadata-stream_test.js"></script>
64 <script src="m3u8_test.js"></script> 47 <script src="m3u8_test.js"></script>
65 <script src="playlist_test.js"></script> 48 <script src="playlist_test.js"></script>
66 <script src="playlist-loader_test.js"></script> 49 <script src="playlist-loader_test.js"></script>
67 <script src="decrypter_test.js"></script> 50 <script src="decrypter_test.js"></script>
68 <script src="transmuxer_test.js"></script>
69 <script src="mp4-inspector_test.js"></script>
70 <script src="mp4-generator_test.js"></script>
71 </head> 51 </head>
72 <body> 52 <body>
73 <div id="qunit"></div> 53 <div id="qunit"></div>
......