Implement mp4 inspector and tests
As preparation for building a tool to transform mp2t files into mp4s, create a javascript tool that parses an mp4 file. Remove the local qunit so that karma and direct qunit testing happens with the same version of the library. Force the tech to run during tests so that Safari doesn't use native HLS.
Showing
8 changed files
with
606 additions
and
2 deletions
test/fixtures/gear4-0.mp4.js
0 → 100644
This diff could not be displayed because it is too large.
test/fixtures/gear4-1.mp4.js
0 → 100644
This diff could not be displayed because it is too large.
... | @@ -93,7 +93,8 @@ module.exports = function(config) { | ... | @@ -93,7 +93,8 @@ module.exports = function(config) { |
93 | '../tmp/expected.js', | 93 | '../tmp/expected.js', |
94 | 'tsSegment-bc.js', | 94 | 'tsSegment-bc.js', |
95 | '../src/bin-utils.js', | 95 | '../src/bin-utils.js', |
96 | '../test/*.js' | 96 | '../test/*.js', |
97 | '../test/muxer/js/mp4-inspector.js' | ||
97 | ], | 98 | ], |
98 | 99 | ||
99 | plugins: [ | 100 | plugins: [ | ... | ... |
... | @@ -58,7 +58,8 @@ module.exports = function(config) { | ... | @@ -58,7 +58,8 @@ module.exports = function(config) { |
58 | '../tmp/expected.js', | 58 | '../tmp/expected.js', |
59 | 'tsSegment-bc.js', | 59 | 'tsSegment-bc.js', |
60 | '../src/bin-utils.js', | 60 | '../src/bin-utils.js', |
61 | '../test/*.js' | 61 | '../test/*.js', |
62 | '../test/muxer/js/mp4-inspector.js' | ||
62 | ], | 63 | ], |
63 | 64 | ||
64 | plugins: [ | 65 | plugins: [ | ... | ... |
test/mp4-inspector_test.js
0 → 100644
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 | module('MP4 Inspector'); | ||
73 | |||
74 | test('produces an empty array for empty input', function() { | ||
75 | strictEqual(videojs.inspectMp4(new Uint8Array([])).length, 0, 'returned an empty array'); | ||
76 | }); | ||
77 | |||
78 | test('can parse a Box', function() { | ||
79 | var box = new Uint8Array([ | ||
80 | 0x00, 0x00, 0x00, 0x00, // size 0 | ||
81 | 0x00, 0x00, 0x00, 0x00 // boxtype 0 | ||
82 | ]); | ||
83 | deepEqual(videojs.inspectMp4(box), [{ | ||
84 | type: '\u0000\u0000\u0000\u0000', | ||
85 | size: 0, | ||
86 | data: box.subarray(box.byteLength) | ||
87 | }], 'parsed a Box'); | ||
88 | }); | ||
89 | |||
90 | test('can parse an ftyp', function() { | ||
91 | deepEqual(videojs.inspectMp4(new Uint8Array(box('ftyp', | ||
92 | 0x00, 0x00, 0x00, 0x01, // major brand | ||
93 | 0x00, 0x00, 0x00, 0x02, // minor version | ||
94 | 0x00, 0x00, 0x00, 0x03, // compatible brands | ||
95 | 0x00, 0x00, 0x00, 0x04 // compatible brands | ||
96 | ))), [{ | ||
97 | type: 'ftyp', | ||
98 | size: 4 * 6, | ||
99 | majorBrand: 1, | ||
100 | minorVersion: 2, | ||
101 | compatibleBrands: [3, 4] | ||
102 | }], 'parsed an ftyp'); | ||
103 | }); | ||
104 | |||
105 | test('can parse a pdin', function() { | ||
106 | deepEqual(videojs.inspectMp4(new Uint8Array(box('pdin', | ||
107 | 0x01, // version 1 | ||
108 | 0x01, 0x02, 0x03, // flags | ||
109 | 0x00, 0x00, 0x04, 0x00, // 1024 = 0x400 bytes/second rate | ||
110 | 0x00, 0x00, 0x00, 0x01 // initial delay | ||
111 | ))), [{ | ||
112 | size: 20, | ||
113 | type: 'pdin', | ||
114 | version: 1, | ||
115 | flags: new Uint8Array([1, 2, 3]), | ||
116 | rate: 1024, | ||
117 | initialDelay: 1 | ||
118 | }], 'parsed a pdin'); | ||
119 | }); | ||
120 | |||
121 | test('can parse an mdat', function() { | ||
122 | var mdat = new Uint8Array(box('mdat', | ||
123 | 0x01, 0x02, 0x03, 0x04 // data | ||
124 | )); | ||
125 | deepEqual(videojs.inspectMp4(mdat), [{ | ||
126 | size: 12, | ||
127 | type: 'mdat', | ||
128 | data: mdat.subarray(mdat.byteLength - 4) | ||
129 | }], 'parsed an mdat'); | ||
130 | }); | ||
131 | |||
132 | test('can parse a free or skip', function() { | ||
133 | var | ||
134 | free = new Uint8Array(box('free', | ||
135 | 0x01, 0x02, 0x03, 0x04)), // data | ||
136 | skip = new Uint8Array(box('skip', | ||
137 | 0x01, 0x02, 0x03, 0x04)); // data | ||
138 | |||
139 | deepEqual(videojs.inspectMp4(free), [{ | ||
140 | size: 12, | ||
141 | type: 'free', | ||
142 | data: free.subarray(free.byteLength - 4) | ||
143 | }], 'parsed a free'); | ||
144 | deepEqual(videojs.inspectMp4(skip), [{ | ||
145 | size: 12, | ||
146 | type: 'skip', | ||
147 | data: skip.subarray(skip.byteLength - 4) | ||
148 | }], 'parsed a skip'); | ||
149 | }); | ||
150 | |||
151 | test('can parse a moov', function() { | ||
152 | var data = | ||
153 | box('moov', | ||
154 | box('mvhd', | ||
155 | 0x01, // version 1 | ||
156 | 0x00, 0x00, 0x00, // flags | ||
157 | 0x00, 0x00, 0x00, 0x00, | ||
158 | 0x00, 0x00, 0x00, 0x01, // creation_time | ||
159 | 0x00, 0x00, 0x00, 0x00, | ||
160 | 0x00, 0x00, 0x00, 0x02, // modification_time | ||
161 | 0x00, 0x00, 0x00, 0x3c, // timescale | ||
162 | 0x00, 0x00, 0x00, 0x00, | ||
163 | 0x00, 0x00, 0x02, 0x58, // 600 = 0x258 duration | ||
164 | 0x00, 0x01, 0x00, 0x00, // 1.0 rate | ||
165 | 0x01, 0x00, // 1.0 volume | ||
166 | 0x00, 0x00, // reserved | ||
167 | 0x00, 0x00, 0x00, 0x00, // reserved | ||
168 | 0x00, 0x00, 0x00, 0x00, // reserved | ||
169 | unityMatrix, | ||
170 | 0x00, 0x00, 0x00, 0x00, | ||
171 | 0x00, 0x00, 0x00, 0x00, | ||
172 | 0x00, 0x00, 0x00, 0x00, | ||
173 | 0x00, 0x00, 0x00, 0x00, | ||
174 | 0x00, 0x00, 0x00, 0x00, | ||
175 | 0x00, 0x00, 0x00, 0x00, // pre_defined | ||
176 | 0x00, 0x00, 0x00, 0x02), // next_track_ID | ||
177 | box('trak', | ||
178 | box('tkhd', | ||
179 | 0x01, // version 1 | ||
180 | 0x00, 0x00, 0x00, // flags | ||
181 | 0x00, 0x00, 0x00, 0x00, | ||
182 | 0x00, 0x00, 0x00, 0x02, // creation_time | ||
183 | 0x00, 0x00, 0x00, 0x00, | ||
184 | 0x00, 0x00, 0x00, 0x03, // modification_time | ||
185 | 0x00, 0x00, 0x00, 0x01, // track_ID | ||
186 | 0x00, 0x00, 0x00, 0x00, // reserved | ||
187 | 0x00, 0x00, 0x00, 0x00, | ||
188 | 0x00, 0x00, 0x02, 0x58, // 600 = 0x258 duration | ||
189 | 0x00, 0x00, 0x00, 0x00, | ||
190 | 0x00, 0x00, 0x00, 0x00, // reserved | ||
191 | 0x00, 0x00, // layer | ||
192 | 0x00, 0x00, // alternate_group | ||
193 | 0x00, 0x00, // non-audio track volume | ||
194 | 0x00, 0x00, // reserved | ||
195 | unityMatrix, | ||
196 | 0x00, 0x00, 0x01, 0x2c, // 300 = 0x12c width | ||
197 | 0x00, 0x00, 0x00, 0x96), // 150 = 0x96 height | ||
198 | box('mdia', | ||
199 | box('mdhd', | ||
200 | 0x01, // version 1 | ||
201 | 0x00, 0x00, 0x00, // flags | ||
202 | 0x00, 0x00, 0x00, 0x00, | ||
203 | 0x00, 0x00, 0x00, 0x02, // creation_time | ||
204 | 0x00, 0x00, 0x00, 0x00, | ||
205 | 0x00, 0x00, 0x00, 0x03, // modification_time | ||
206 | 0x00, 0x00, 0x00, 0x3c, // timescale | ||
207 | 0x00, 0x00, 0x00, 0x00, | ||
208 | 0x00, 0x00, 0x02, 0x58, // 600 = 0x258 duration | ||
209 | 0x15, 0xc7, // 'eng' language | ||
210 | 0x00, 0x00), | ||
211 | box('hdlr', | ||
212 | 0x01, // version 1 | ||
213 | 0x00, 0x00, 0x00, // flags | ||
214 | 0x00, 0x00, 0x00, 0x00, // pre_defined | ||
215 | typeBytes('vide'), // handler_type | ||
216 | 0x00, 0x00, 0x00, 0x00, // reserved | ||
217 | 0x00, 0x00, 0x00, 0x00, // reserved | ||
218 | 0x00, 0x00, 0x00, 0x00, // reserved | ||
219 | typeBytes('one'), 0x00), // name | ||
220 | box('minf', | ||
221 | box('dinf', | ||
222 | box('dref', | ||
223 | 0x01, // version 1 | ||
224 | 0x00, 0x00, 0x00, // flags | ||
225 | 0x00, 0x00, 0x00, 0x00)), // entry_count | ||
226 | box('stbl', | ||
227 | box('stsd', | ||
228 | 0x01, // version 1 | ||
229 | 0x00, 0x00, 0x00, // flags | ||
230 | 0x00, 0x00, 0x00, 0x00), // entry_count | ||
231 | box('stts', | ||
232 | 0x01, // version 1 | ||
233 | 0x00, 0x00, 0x00, // flags | ||
234 | 0x00, 0x00, 0x00, 0x00), // entry_count | ||
235 | box('stsc', | ||
236 | 0x01, // version 1 | ||
237 | 0x00, 0x00, 0x00, // flags | ||
238 | 0x00, 0x00, 0x00, 0x00), // entry_count | ||
239 | box('stco', | ||
240 | 0x01, // version 1 | ||
241 | 0x00, 0x00, 0x00, // flags | ||
242 | 0x00, 0x00, 0x00, 0x00)))))); // entry_count; | ||
243 | |||
244 | deepEqual(videojs.inspectMp4(new Uint8Array(data)), [{ | ||
245 | size: 433, | ||
246 | type: 'moov', | ||
247 | boxes: [{ | ||
248 | type: 'mvhd', | ||
249 | version: 1, | ||
250 | flags: new Uint8Array([0, 0, 0]), | ||
251 | creationTime: 1, | ||
252 | modificationTime: 2, | ||
253 | timescale: 60, | ||
254 | duration: 600, | ||
255 | rate: 1, | ||
256 | size: 120, | ||
257 | volume: 1, | ||
258 | matrix: new Uint32Array(unityMatrix), | ||
259 | nextTrackId: 2 | ||
260 | }, { | ||
261 | type: 'trak', | ||
262 | size: 305, | ||
263 | boxes: [{ | ||
264 | type: 'tkhd', | ||
265 | flags: new Uint8Array([0, 0, 0]), | ||
266 | version: 1, | ||
267 | creationTime: 2, | ||
268 | modificationTime: 3, | ||
269 | size: 104, | ||
270 | trackId: 1, | ||
271 | duration: 600, | ||
272 | layer: 0, | ||
273 | alternateGroup: 0, | ||
274 | volume: 0, | ||
275 | matrix: new Uint32Array(unityMatrix), | ||
276 | width: 300, | ||
277 | height: 150 | ||
278 | }, { | ||
279 | type: 'mdia', | ||
280 | size: 193, | ||
281 | boxes: [{ | ||
282 | type: 'mdhd', | ||
283 | version: 1, | ||
284 | flags: new Uint8Array([0, 0, 0]), | ||
285 | creationTime: 2, | ||
286 | modificationTime: 3, | ||
287 | timescale: 60, | ||
288 | duration: 600, | ||
289 | language: 'eng', | ||
290 | size: 44 | ||
291 | }, { | ||
292 | type: 'hdlr', | ||
293 | version: 1, | ||
294 | flags: new Uint8Array([0, 0, 0]), | ||
295 | handlerType: 'vide', | ||
296 | name: 'one', | ||
297 | size: 37 | ||
298 | }, { | ||
299 | type: 'minf', | ||
300 | size: 104, | ||
301 | boxes: [{ | ||
302 | type: 'dinf', | ||
303 | size: 24, | ||
304 | boxes: [{ | ||
305 | type: 'dref', | ||
306 | dataReferences: [], | ||
307 | size: 16 | ||
308 | }]}, { | ||
309 | type: 'stbl', | ||
310 | size: 72, | ||
311 | boxes: [{ | ||
312 | type: 'stsd', | ||
313 | sampleDescriptions: [], | ||
314 | size: 16 | ||
315 | }, { | ||
316 | type: 'stts', | ||
317 | timeToSamples: [], | ||
318 | size: 16 | ||
319 | }, { | ||
320 | type: 'stsc', | ||
321 | sampleToChunks: [], | ||
322 | size: 16 | ||
323 | }, { | ||
324 | type: 'stco', | ||
325 | chunkOffsets: [], | ||
326 | size: 16 | ||
327 | }] | ||
328 | }] | ||
329 | }] | ||
330 | }] | ||
331 | }] | ||
332 | }], 'parsed a moov'); | ||
333 | }); | ||
334 | |||
335 | test('can parse a series of boxes', function() { | ||
336 | var ftyp = [ | ||
337 | 0x00, 0x00, 0x00, 0x18 // size 4 * 6 = 24 | ||
338 | ].concat(typeBytes('ftyp')).concat([ | ||
339 | 0x00, 0x00, 0x00, 0x01, // major brand | ||
340 | 0x00, 0x00, 0x00, 0x02, // minor version | ||
341 | 0x00, 0x00, 0x00, 0x03, // compatible brands | ||
342 | 0x00, 0x00, 0x00, 0x04, // compatible brands | ||
343 | ]); | ||
344 | |||
345 | deepEqual(videojs.inspectMp4(new Uint8Array(ftyp.concat(ftyp))), | ||
346 | [{ | ||
347 | type: 'ftyp', | ||
348 | size: 4 * 6, | ||
349 | majorBrand: 1, | ||
350 | minorVersion: 2, | ||
351 | compatibleBrands: [3, 4] | ||
352 | },{ | ||
353 | type: 'ftyp', | ||
354 | size: 4 * 6, | ||
355 | majorBrand: 1, | ||
356 | minorVersion: 2, | ||
357 | compatibleBrands: [3, 4] | ||
358 | }], | ||
359 | 'parsed two boxes in series'); | ||
360 | |||
361 | }); | ||
362 | |||
363 | })(window, window.videojs); |
test/muxer/js/mp4-inspector.js
0 → 100644
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 | |||
20 | // registry of handlers for individual mp4 box types | ||
21 | parse = { | ||
22 | ftyp: function(data) { | ||
23 | var | ||
24 | view = new DataView(data.buffer, data.byteOffset, data.byteLength), | ||
25 | result = { | ||
26 | majorBrand: view.getUint32(0), | ||
27 | minorVersion: view.getUint32(4), | ||
28 | compatibleBrands: [] | ||
29 | }, | ||
30 | i = 8; | ||
31 | while (i < data.byteLength) { | ||
32 | result.compatibleBrands.push(view.getUint32(i)); | ||
33 | i += 4; | ||
34 | } | ||
35 | return result; | ||
36 | }, | ||
37 | dinf: function(data) { | ||
38 | return { | ||
39 | boxes: videojs.inspectMp4(data) | ||
40 | }; | ||
41 | }, | ||
42 | dref: function(data) { | ||
43 | return { | ||
44 | dataReferences: [] | ||
45 | }; | ||
46 | }, | ||
47 | hdlr: function(data) { | ||
48 | var | ||
49 | view = new DataView(data.buffer, data.byteOffset, data.byteLength), | ||
50 | language, | ||
51 | result = { | ||
52 | version: view.getUint8(0), | ||
53 | flags: new Uint8Array(data.subarray(1, 4)), | ||
54 | handlerType: parseType(data.subarray(8, 12)), | ||
55 | name: '' | ||
56 | }, | ||
57 | i = 8; | ||
58 | |||
59 | // parse out the name field | ||
60 | for (i = 24; i < data.byteLength; i++) { | ||
61 | if (data[i] === 0x00) { | ||
62 | // the name field is null-terminated | ||
63 | i++; | ||
64 | break; | ||
65 | } | ||
66 | result.name += String.fromCharCode(data[i]); | ||
67 | } | ||
68 | // decode UTF-8 to javascript's internal representation | ||
69 | // see http://ecmanaut.blogspot.com/2006/07/encoding-decoding-utf8-in-javascript.html | ||
70 | result.name = window.decodeURIComponent(window.escape(result.name)); | ||
71 | |||
72 | return result; | ||
73 | }, | ||
74 | mdhd: function(data) { | ||
75 | var | ||
76 | view = new DataView(data.buffer, data.byteOffset, data.byteLength), | ||
77 | language, | ||
78 | result = { | ||
79 | version: view.getUint8(0), | ||
80 | flags: new Uint8Array(data.subarray(1, 4)), | ||
81 | language: '' | ||
82 | }; | ||
83 | if (result.version === 1) { | ||
84 | result.creationTime = view.getUint32(8); // truncating top 4 bytes | ||
85 | result.modificationTime = view.getUint32(16); // truncating top 4 bytes | ||
86 | result.timescale = view.getUint32(20); | ||
87 | result.duration = view.getUint32(28); // truncating top 4 bytes | ||
88 | } | ||
89 | // language is stored as an ISO-639-2/T code in an array of three 5-bit fields | ||
90 | // each field is the packed difference between its ASCII value and 0x60 | ||
91 | language = view.getUint16(32); | ||
92 | result.language += String.fromCharCode((language >> 10) + 0x60); | ||
93 | result.language += String.fromCharCode(((language & 0x03c0) >> 5) + 0x60); | ||
94 | result.language += String.fromCharCode((language & 0x1f) + 0x60); | ||
95 | |||
96 | return result; | ||
97 | }, | ||
98 | mdia: function(data) { | ||
99 | return { | ||
100 | boxes: videojs.inspectMp4(data) | ||
101 | }; | ||
102 | }, | ||
103 | minf: function(data) { | ||
104 | return { | ||
105 | boxes: videojs.inspectMp4(data) | ||
106 | }; | ||
107 | }, | ||
108 | moov: function(data) { | ||
109 | return { | ||
110 | boxes: videojs.inspectMp4(data) | ||
111 | }; | ||
112 | }, | ||
113 | mvhd: function(data) { | ||
114 | var | ||
115 | view = new DataView(data.buffer, data.byteOffset, data.byteLength), | ||
116 | result = { | ||
117 | version: view.getUint8(0), | ||
118 | flags: new Uint8Array(data.subarray(1, 4)), | ||
119 | // convert fixed-point, base 16 back to a number | ||
120 | rate: view.getUint16(32) + (view.getUint16(34) / 16), | ||
121 | volume: view.getUint8(36) + (view.getUint8(37) / 8), | ||
122 | matrix: new Uint32Array(data.subarray(48, 84)), | ||
123 | nextTrackId: view.getUint32(108) | ||
124 | }; | ||
125 | if (result.version === 1) { | ||
126 | result.creationTime = view.getUint32(8); // truncating top 4 bytes | ||
127 | result.modificationTime = view.getUint32(16); // truncating top 4 bytes | ||
128 | result.timescale = view.getUint32(20); | ||
129 | result.duration = view.getUint32(28); // truncating top 4 bytes | ||
130 | } | ||
131 | return result; | ||
132 | }, | ||
133 | pdin: function(data) { | ||
134 | var view = new DataView(data.buffer, data.byteOffset, data.byteLength); | ||
135 | return { | ||
136 | version: view.getUint8(0), | ||
137 | flags: new Uint8Array(data.subarray(1, 4)), | ||
138 | rate: view.getUint32(4), | ||
139 | initialDelay: view.getUint32(8) | ||
140 | }; | ||
141 | }, | ||
142 | trak: function(data) { | ||
143 | return { | ||
144 | boxes: videojs.inspectMp4(data) | ||
145 | }; | ||
146 | }, | ||
147 | stbl: function(data) { | ||
148 | return { | ||
149 | boxes: videojs.inspectMp4(data) | ||
150 | }; | ||
151 | }, | ||
152 | stco: function(data) { | ||
153 | return { | ||
154 | chunkOffsets: [] | ||
155 | }; | ||
156 | }, | ||
157 | stsc: function(data) { | ||
158 | return { | ||
159 | sampleToChunks: [] | ||
160 | }; | ||
161 | }, | ||
162 | stsd: function(data) { | ||
163 | return { | ||
164 | sampleDescriptions: [] | ||
165 | }; | ||
166 | }, | ||
167 | stts: function(data) { | ||
168 | return { | ||
169 | timeToSamples: [] | ||
170 | }; | ||
171 | }, | ||
172 | tkhd: function(data) { | ||
173 | var | ||
174 | view = new DataView(data.buffer, data.byteOffset, data.byteLength), | ||
175 | result = { | ||
176 | version: view.getUint8(0), | ||
177 | flags: new Uint8Array(data.subarray(1, 4)), | ||
178 | layer: view.getUint16(44), | ||
179 | alternateGroup: view.getUint16(46), | ||
180 | // convert fixed-point, base 16 back to a number | ||
181 | volume: view.getUint8(48) + (view.getUint8(49) / 8), | ||
182 | matrix: new Uint32Array(data.subarray(52, 88)), | ||
183 | width: view.getUint32(88), | ||
184 | height: view.getUint32(92) | ||
185 | }; | ||
186 | if (result.version === 1) { | ||
187 | result.creationTime = view.getUint32(8); // truncating top 4 bytes | ||
188 | result.modificationTime = view.getUint32(16); // truncating top 4 bytes | ||
189 | result.trackId = view.getUint32(20); | ||
190 | result.duration = view.getUint32(32); // truncating top 4 bytes | ||
191 | } | ||
192 | return result; | ||
193 | } | ||
194 | }; | ||
195 | |||
196 | /** | ||
197 | * Return a javascript array of box objects parsed from an ISO base | ||
198 | * media file. | ||
199 | * @param data {Uint8Array} the binary data of the media to be inspected | ||
200 | * @return {array} a javascript array of potentially nested box objects | ||
201 | */ | ||
202 | videojs.inspectMp4 = function(data) { | ||
203 | var | ||
204 | i = 0, | ||
205 | result = [], | ||
206 | view = new DataView(data.buffer, data.byteOffset, data.byteLength), | ||
207 | size, | ||
208 | type, | ||
209 | end, | ||
210 | box; | ||
211 | |||
212 | while (i < data.byteLength) { | ||
213 | // parse box data | ||
214 | size = view.getUint32(i), | ||
215 | type = parseType(data.subarray(i + 4, i + 8)); | ||
216 | end = size > 1 ? i + size : data.byteLength; | ||
217 | |||
218 | // parse type-specific data | ||
219 | box = (parse[type] || function(data) { | ||
220 | return { | ||
221 | data: data | ||
222 | }; | ||
223 | })(data.subarray(i + 8, end)); | ||
224 | box.size = size; | ||
225 | box.type = type; | ||
226 | |||
227 | // store this box and move to the next | ||
228 | result.push(box); | ||
229 | i = end; | ||
230 | } | ||
231 | return result; | ||
232 | }; | ||
233 | })(window, window.videojs); |
... | @@ -42,6 +42,9 @@ | ... | @@ -42,6 +42,9 @@ |
42 | <script src="tsSegment-bc.js"></script> | 42 | <script src="tsSegment-bc.js"></script> |
43 | <script src="../src/bin-utils.js"></script> | 43 | <script src="../src/bin-utils.js"></script> |
44 | 44 | ||
45 | <!-- mp4 utilities --> | ||
46 | <script src="muxer/js/mp4-inspector.js"></script> | ||
47 | |||
45 | <!-- Test cases --> | 48 | <!-- Test cases --> |
46 | <script> | 49 | <script> |
47 | module('environment'); | 50 | module('environment'); |
... | @@ -60,6 +63,7 @@ | ... | @@ -60,6 +63,7 @@ |
60 | <script src="playlist_test.js"></script> | 63 | <script src="playlist_test.js"></script> |
61 | <script src="playlist-loader_test.js"></script> | 64 | <script src="playlist-loader_test.js"></script> |
62 | <script src="decrypter_test.js"></script> | 65 | <script src="decrypter_test.js"></script> |
66 | <script src="mp4-inspector_test.js"></script> | ||
63 | </head> | 67 | </head> |
64 | <body> | 68 | <body> |
65 | <div id="qunit"></div> | 69 | <div id="qunit"></div> | ... | ... |
... | @@ -212,7 +212,9 @@ module('HLS', { | ... | @@ -212,7 +212,9 @@ module('HLS', { |
212 | oldClearTimeout = window.clearTimeout; | 212 | oldClearTimeout = window.clearTimeout; |
213 | oldGlobalOptions = window.videojs.getGlobalOptions(); | 213 | oldGlobalOptions = window.videojs.getGlobalOptions(); |
214 | 214 | ||
215 | // force the HLS tech to run | ||
215 | oldNativeHlsSupport = videojs.Hls.supportsNativeHls; | 216 | oldNativeHlsSupport = videojs.Hls.supportsNativeHls; |
217 | videojs.Hls.supportsNativeHls = false; | ||
216 | 218 | ||
217 | oldDecrypt = videojs.Hls.Decrypter; | 219 | oldDecrypt = videojs.Hls.Decrypter; |
218 | videojs.Hls.Decrypter = function() {}; | 220 | videojs.Hls.Decrypter = function() {}; | ... | ... |
-
Please register or sign in to post a comment