UNPKG

videostream-mp4box

Version:

JavaScript version of GPAC's MP4Box tool

1,109 lines (1,035 loc) 39.9 kB
var Log = require('./../src/log'); var MP4Box = require('./../src/mp4box'); Log.setLogLevel(Log.d); var testFiles = [ { // 0 desc: "non-fragmented MP4 file with single MPEG-AVC stream", url: './mp4/h264bl.mp4', info_: {"duration":360000,"timescale":600,"isFragmented":false,"isProgressive":true,"hasIOD":true,"brands":["isom","isom"],"created":new Date("2014-04-10T18:23:58.000Z"),"modified":new Date("2014-04-10T18:23:58.000Z"),"tracks":[{"id":1,"references":[],"created":new Date("2012-02-13T23:07:31.000Z"),"modified":new Date("2014-04-10T18:23:59.000Z"),"movie_duration":360000,"layer":0,"alternate_group":0,"volume":0,"matrix":{"0":65536,"1":0,"2":0,"3":0,"4":65536,"5":0,"6":0,"7":0,"8":1073741824},"track_width":320,"track_height":180,"timescale":25000,"duration":15000000,"codec":"avc1.42c00d","language":"und","nb_samples":15000,"video":{"width":320,"height":180}}],"audioTracks":[],"videoTracks":[{"id":1,"references":[],"created":new Date("2012-02-13T23:07:31.000Z"),"modified":new Date("2014-04-10T18:23:59.000Z"),"movie_duration":360000,"layer":0,"alternate_group":0,"volume":0,"matrix":{"0":65536,"1":0,"2":0,"3":0,"4":65536,"5":0,"6":0,"7":0,"8":1073741824},"track_width":320,"track_height":180,"timescale":25000,"duration":15000000,"codec":"avc1.42c00d","language":"und","nb_samples":15000,"video":{"width":320,"height":180}}],"subtitleTracks":[],"metadataTracks":[],"hintTracks":[]}, }, { // 1 desc: "fragmented MP4 file with single MPEG-AVC stream", url: './mp4/a.mp4' }, { // 2 desc: "non-fragmented MP4 file with MPEG-4 AAC stream", url: './mp4/aaclow.mp4' }, { // 3 desc: "non-fragmented MP4 file with two AVC video streams", url: './mp4/2v.mp4' }, { // 4 desc: "non-fragmented MP4 file with AVC, AAC and WebVTT", url: './mp4/avw.mp4' }, { // 5 desc: "non-fragmented MP4 file with 1 WebVTT stream", url: './mp4/subtitle-srt-wvtt.mp4' }, { // 6 desc: "non-fragmented MP4 file with 1 text:tx3g stream", url: './mp4/subtitle-srt-tx3g.mp4' }, { // 7 desc: "non-fragmented MP4 file with 1 text:stse stream", url: './mp4/anim-svg.mp4' }, { // 8 desc: "non-fragmented MP4 file with 1 subt:stpp stream", url: './mp4/subtitle-ttml-stpp.mp4' }, { // 9 desc: "non-fragmented MP4 file with single AVC stream, moov is last box", url: './mp4/moov_last.mp4' }, { // 10 desc: "long movie", url: './mp4/Bad.Influence.se4ep13.mp4' //url: './mp4-torrents/g.mp4' } ]; function getFileRange(url, start, end, callback) { var xhr = new XMLHttpRequest(); xhr.open("GET", url, true); xhr.responseType = "arraybuffer"; if (start !== 0 || end !== Infinity) { xhr.setRequestHeader('Range', 'bytes=' + start + '-' + (end == Infinity ? '':end)); } xhr.onreadystatechange = function (e) { if ((xhr.status == 200 || xhr.status == 206 || xhr.status == 304 || xhr.status == 416) && xhr.readyState == this.DONE) { xhr.response.fileStart = start; callback(xhr.response); } }; xhr.send(); } function getFile(url, callback) { getFileRange(url, 0, Infinity, callback); } function runBasicTest(index) { QUnit.asyncTest(testFiles[index].desc, function( assert ) { var timeout = window.setTimeout(function() { assert.ok(false, "Timeout"); QUnit.start(); }, 10000); var mp4box = new MP4Box(); mp4box.onReady = function(info) { window.clearTimeout(timeout); assert.ok(true, "moov found!" ); if (testFiles[index].info) { assert.deepEqual(info, testFiles[index].info, "Movie information is correct"); } QUnit.start(); } getFile(testFiles[index].url, function (buffer) { mp4box.appendBuffer(buffer); }); }); } QUnit.module("One-chunk parsing and fire onReady when moov is parsed "); for (var i = 0; i < testFiles.length; i++) { runBasicTest(i); } QUnit.module("Advanced chunk parsing"); QUnit.test( "appending invalid buffer", function( assert ) { var mp4box = new MP4Box(); assert.throws(function() { mp4box.appendBuffer(null) }, "Exception thrown because of null buffer"); assert.throws(function() { mp4box.appendBuffer(new ArrayBuffer()) }, "Exception thrown because of missing fileStart property"); var b = new ArrayBuffer(0); b.fileStart = 0; mp4box.appendBuffer(b); }); QUnit.asyncTest( "appending 2 non-overlapping chunks (mid-moov cut, in order: 1 2)", function( assert ) { var index = 0; var timeout = window.setTimeout(function() { assert.ok(false, "Timeout"); QUnit.start(); }, 2000); var mp4box = new MP4Box(); mp4box.onReady = function(info) { window.clearTimeout(timeout); assert.ok(true, "moov found!" ); assert.equal(mp4box.nextBuffers.length, 1, "1 buffer remaining" ); if (testFiles[index].info) { assert.deepEqual(info, testFiles[index].info, "Movie information is correct"); } QUnit.start(); } getFileRange(testFiles[index].url, 0, 24999, function (buffer) { mp4box.appendBuffer(buffer); getFileRange(testFiles[index].url, 25000, Infinity, function (buffer) { mp4box.appendBuffer(buffer); }); }); }); QUnit.asyncTest( "appending 2 non-overlapping chunks (mid-mdat cut, in order: 1 2)", function( assert ) { var index = 0; var timeout = window.setTimeout(function() { assert.ok(false, "Timeout"); QUnit.start(); }, 2000); var mp4box = new MP4Box(); QUnit.stop(); mp4box.onReady = function(info) { window.clearTimeout(timeout); assert.ok(true, "moov found!" ); assert.equal(mp4box.nextBuffers.length, 1, "1 buffer remaining" ); if (testFiles[index].info) { assert.deepEqual(info, testFiles[index].info, "Movie information is correct"); } QUnit.start(); } getFileRange(testFiles[index].url, 0, 79999, function (buffer) { mp4box.appendBuffer(buffer); getFileRange(testFiles[index].url, 80000, Infinity, function (buffer) { mp4box.appendBuffer(buffer); assert.ok(true, "second buffer appended!" ); assert.equal(mp4box.nextBuffers.length, 2, "2 buffers remaining" ); QUnit.start(); }); }); }); QUnit.asyncTest( "appending 2 non-overlapping chunks (mid-moov cut, out-of-order: 2 1)", function( assert ) { var index = 0; var timeout = window.setTimeout(function() { assert.ok(false, "Timeout"); QUnit.start(); }, 2000); var mp4box = new MP4Box(); mp4box.onReady = function(info) { window.clearTimeout(timeout); assert.ok(true, "moov found!" ); assert.equal(mp4box.nextBuffers.length, 1, "1 buffer remaining" ); if (testFiles[index].info) { assert.deepEqual(info, testFiles[index].info, "Movie information is correct"); } QUnit.start(); } getFileRange(testFiles[index].url, 25000, Infinity, function (buffer) { mp4box.appendBuffer(buffer); getFileRange(testFiles[index].url, 0, 24999, function (buffer) { mp4box.appendBuffer(buffer); }); }); }); QUnit.asyncTest( "appending 2 non-overlapping chunks (mid-mdat cut, out-of-order: 2 1)", function( assert ) { var index = 0; var timeout = window.setTimeout(function() { assert.ok(false, "Timeout"); QUnit.start(); }, 2000); var mp4box = new MP4Box(); mp4box.onReady = function(info) { window.clearTimeout(timeout); assert.ok(true, "moov found!" ); assert.equal(mp4box.nextBuffers.length, 2, "2 buffers remaining" ); QUnit.start(); } getFileRange(testFiles[index].url, 80000, Infinity, function (buffer) { mp4box.appendBuffer(buffer); getFileRange(testFiles[index].url, 0, 79999, function (buffer) { mp4box.appendBuffer(buffer); }); }); }); QUnit.asyncTest( "appending 2 overlapping chunks (mid-moov cut, in order: 1 2)", function( assert ) { var index = 0; var timeout = window.setTimeout(function() { assert.ok(false, "Timeout"); QUnit.start(); }, 2000); var mp4box = new MP4Box(); mp4box.onReady = function(info) { window.clearTimeout(timeout); assert.ok(true, "moov found!" ); assert.equal(mp4box.nextBuffers.length, 1, "1 buffer remaining" ); if (testFiles[index].info) { assert.deepEqual(info, testFiles[index].info, "Movie information is correct"); } QUnit.start(); } getFileRange(testFiles[index].url, 0, 24999, function (buffer) { mp4box.appendBuffer(buffer); getFileRange(testFiles[index].url, 24000, Infinity, function (buffer) { mp4box.appendBuffer(buffer); }); }); }); QUnit.asyncTest( "appending 2 overlapping chunks (mid-mdat cut, in order: 1 2)", function( assert ) { var index = 0; var timeout = window.setTimeout(function() { assert.ok(false, "Timeout"); QUnit.start(); }, 2000); var mp4box = new MP4Box(); QUnit.stop(); mp4box.onReady = function(info) { window.clearTimeout(timeout); assert.ok(true, "moov found!" ); assert.equal(mp4box.nextBuffers.length, 1, "1 buffer remaining" ); if (testFiles[index].info) { assert.deepEqual(info, testFiles[index].info, "Movie information is correct"); } QUnit.start(); } getFileRange(testFiles[index].url, 0, 79999, function (buffer) { mp4box.appendBuffer(buffer); getFileRange(testFiles[index].url, 70000, Infinity, function (buffer) { mp4box.appendBuffer(buffer); assert.ok(true, "second buffer appended!" ); assert.equal(mp4box.nextBuffers.length, 2, "2 buffers remaining" ); QUnit.start(); }); }); }); QUnit.asyncTest( "appending 2 overlapping chunks (mid-mdat cut, incomplete mdat, in order: 1 2)", function( assert ) { var index = 0; var timeout = window.setTimeout(function() { assert.ok(false, "Timeout"); QUnit.start(); }, 2000); var mp4box = new MP4Box(); QUnit.stop(); mp4box.onReady = function(info) { window.clearTimeout(timeout); assert.ok(true, "moov found!" ); assert.equal(mp4box.nextBuffers.length, 1, "1 buffer remaining" ); if (testFiles[index].info) { assert.deepEqual(info, testFiles[index].info, "Movie information is correct"); } QUnit.start(); } getFileRange(testFiles[index].url, 0, 79999, function (buffer) { mp4box.appendBuffer(buffer); getFileRange(testFiles[index].url, 70000, 100000, function (buffer) { mp4box.appendBuffer(buffer); assert.ok(true, "second buffer appended!" ); assert.equal(mp4box.nextBuffers.length, 2, "2 buffers remaining" ); QUnit.start(); }); }); }); QUnit.asyncTest( "appending 2 overlapping chunks (mid-mdat cut, out of order: 2 1)", function( assert ) { var index = 0; var timeout = window.setTimeout(function() { assert.ok(false, "Timeout"); QUnit.start(); }, 2000); var mp4box = new MP4Box(); mp4box.onReady = function(info) { window.clearTimeout(timeout); assert.ok(true, "moov found!" ); assert.equal(mp4box.nextBuffers.length, 2, "2 buffer remaining" ); if (testFiles[index].info) { assert.deepEqual(info, testFiles[index].info, "Movie information is correct"); } QUnit.start(); } getFileRange(testFiles[index].url, 80000, Infinity, function (buffer) { mp4box.appendBuffer(buffer); getFileRange(testFiles[index].url, 0, 89999, function (buffer) { mp4box.appendBuffer(buffer); }); }); }); QUnit.asyncTest( "appending 3 non-overlapping chunks (mid-moov cut, order: 1 3 2)", function( assert ) { var index = 0; var timeout = window.setTimeout(function() { assert.ok(false, "Timeout"); QUnit.start(); }, 2000); var mp4box = new MP4Box(); mp4box.onReady = function(info) { window.clearTimeout(timeout); assert.ok(true, "moov found!" ); assert.equal(mp4box.nextBuffers.length, 1, "1 buffer remaining" ); if (testFiles[index].info) { assert.deepEqual(info, testFiles[index].info, "Movie information is correct"); } QUnit.start(); } getFileRange(testFiles[index].url, 0, 24999, function (buffer) { mp4box.appendBuffer(buffer); getFileRange(testFiles[index].url, 50000, Infinity, function (buffer) { mp4box.appendBuffer(buffer); getFileRange(testFiles[index].url, 25000, 49999, function (buffer) { mp4box.appendBuffer(buffer); }); }); }); }); QUnit.asyncTest( "appending 3 non-overlapping chunks (mid-mdat cut, order: 1 3 2)", function( assert ) { var index = 0; var timeout = window.setTimeout(function() { assert.ok(false, "Timeout"); QUnit.start(); }, 2000); var mp4box = new MP4Box(); QUnit.stop(); mp4box.onReady = function(info) { window.clearTimeout(timeout); assert.ok(true, "moov found!" ); assert.equal(mp4box.nextBuffers.length, 1, "1 buffer remaining" ); if (testFiles[index].info) { assert.deepEqual(info, testFiles[index].info, "Movie information is correct"); } QUnit.start(); } getFileRange(testFiles[index].url, 0, 79999, function (buffer) { mp4box.appendBuffer(buffer); getFileRange(testFiles[index].url, 100000, Infinity, function (buffer) { mp4box.appendBuffer(buffer); getFileRange(testFiles[index].url, 80000, 99999, function (buffer) { mp4box.appendBuffer(buffer); assert.equal(mp4box.nextBuffers.length, 3, "3 buffer remaining" ); QUnit.start(); }); }); }); }); QUnit.asyncTest( "appending twice the same small buffer (mid-moov)", function( assert ) { var index = 0; var timeout = window.setTimeout(function() { assert.ok(false, "Timeout"); QUnit.start(); }, 2000); var mp4box = new MP4Box(); mp4box.onReady = function(info) { window.clearTimeout(timeout); assert.ok(true, "moov found!" ); assert.equal(mp4box.nextBuffers.length, 1, "1 buffer remaining" ); if (testFiles[index].info) { assert.deepEqual(info, testFiles[index].info, "Movie information is correct"); } QUnit.start(); } getFileRange(testFiles[index].url, 0, 24999, function (buffer) { mp4box.appendBuffer(buffer); getFileRange(testFiles[index].url, 0, 24999, function (buffer) { mp4box.appendBuffer(buffer); getFileRange(testFiles[index].url, 25000, Infinity, function (buffer) { mp4box.appendBuffer(buffer); }); }); }); }); QUnit.asyncTest( "appending twice the same small buffer (mid-mdat)", function( assert ) { var index = 0; var timeout = window.setTimeout(function() { assert.ok(false, "Timeout"); QUnit.start(); }, 2000); var mp4box = new MP4Box(); QUnit.stop(); mp4box.onReady = function(info) { window.clearTimeout(timeout); assert.ok(true, "moov found!" ); assert.equal(mp4box.nextBuffers.length, 1, "1 buffer remaining" ); if (testFiles[index].info) { assert.deepEqual(info, testFiles[index].info, "Movie information is correct"); } QUnit.start(); } getFileRange(testFiles[index].url, 0, 79999, function (buffer) { mp4box.appendBuffer(buffer); getFileRange(testFiles[index].url, 80000, 99999, function (buffer) { mp4box.appendBuffer(buffer); getFileRange(testFiles[index].url, 80000, 99999, function (buffer) { mp4box.appendBuffer(buffer); getFileRange(testFiles[index].url, 100000, Infinity, function (buffer) { mp4box.appendBuffer(buffer); assert.equal(mp4box.nextBuffers.length, 3, "3 buffer remaining" ); QUnit.start(); }); }); }); }); }); QUnit.asyncTest( "appending twice the whole file as a buffer", function( assert ) { var index = 0; var timeout = window.setTimeout(function() { assert.ok(false, "Timeout"); QUnit.start(); }, 2000); var mp4box = new MP4Box(); QUnit.stop(); mp4box.onReady = function(info) { window.clearTimeout(timeout); assert.ok(true, "moov found!" ); if (testFiles[index].info) { assert.deepEqual(info, testFiles[index].info, "Movie information is correct"); } QUnit.start(); } getFileRange(testFiles[index].url, 0, Infinity, function (buffer) { mp4box.appendBuffer(buffer); getFileRange(testFiles[index].url, 0, Infinity, function (buffer) { mp4box.appendBuffer(buffer); assert.equal(mp4box.nextBuffers.length, 1, "1 buffer stored" ); QUnit.start(); }); }); }); QUnit.asyncTest( "appending a smaller duplicated buffer (in moov)", function( assert ) { var index = 0; var timeout = window.setTimeout(function() { assert.ok(false, "Timeout"); QUnit.start(); }, 2000); var mp4box = new MP4Box(); mp4box.onReady = function(info) { window.clearTimeout(timeout); assert.ok(true, "moov found!" ); assert.equal(mp4box.nextBuffers.length, 1, "1 buffer stored" ); if (testFiles[index].info) { assert.deepEqual(info, testFiles[index].info, "Movie information is correct"); } QUnit.start(); } getFileRange(testFiles[index].url, 0, 24999, function (buffer) { mp4box.appendBuffer(buffer); getFileRange(testFiles[index].url, 0, 9999, function (buffer) { mp4box.appendBuffer(buffer); getFileRange(testFiles[index].url, 25000, Infinity, function (buffer) { mp4box.appendBuffer(buffer); }); }); }); }); QUnit.asyncTest( "appending a smaller duplicated buffer (in mdat)", function( assert ) { var index = 0; var timeout = window.setTimeout(function() { assert.ok(false, "Timeout"); QUnit.start(); }, 2000); var mp4box = new MP4Box(); mp4box.onReady = function(info) { window.clearTimeout(timeout); assert.ok(true, "moov found!" ); assert.equal(mp4box.nextBuffers.length, 3, "3 buffer stored" ); if (testFiles[index].info) { assert.deepEqual(info, testFiles[index].info, "Movie information is correct"); } QUnit.start(); } getFileRange(testFiles[index].url, 80000, 99999, function (buffer) { mp4box.appendBuffer(buffer); getFileRange(testFiles[index].url, 70000, 89999, function (buffer) { mp4box.appendBuffer(buffer); getFileRange(testFiles[index].url, 0, 69999, function (buffer) { mp4box.appendBuffer(buffer); }); }); }); }); QUnit.asyncTest( "appending a buffer overlapping on mdat at the beginning", function( assert ) { var index = 0; var timeout = window.setTimeout(function() { assert.ok(false, "Timeout"); QUnit.start(); }, 2000); var mp4box = new MP4Box(); mp4box.onReady = function(info) { window.clearTimeout(timeout); assert.ok(true, "moov found!" ); assert.equal(mp4box.nextBuffers.length, 2, "2 buffer stored" ); if (testFiles[index].info) { assert.deepEqual(info, testFiles[index].info, "Movie information is correct"); } QUnit.start(); } getFileRange(testFiles[index].url, 80000, 99999, function (buffer) { mp4box.appendBuffer(buffer); getFileRange(testFiles[index].url, 80000, 89999, function (buffer) { mp4box.appendBuffer(buffer); getFileRange(testFiles[index].url, 0, 79999, function (buffer) { mp4box.appendBuffer(buffer); }); }); }); }); QUnit.asyncTest( "appending a larger duplicated buffer of another buffer", function( assert ) { var index = 0; var timeout = window.setTimeout(function() { assert.ok(false, "Timeout"); QUnit.start(); }, 2000); var mp4box = new MP4Box(); mp4box.onReady = function(info) { window.clearTimeout(timeout); assert.ok(true, "moov found!" ); assert.equal(mp4box.nextBuffers.length, 1, "1 buffer stored" ); if (testFiles[index].info) { assert.deepEqual(info, testFiles[index].info, "Movie information is correct"); } QUnit.start(); } getFileRange(testFiles[index].url, 0, 24999, function (buffer) { mp4box.appendBuffer(buffer); getFileRange(testFiles[index].url, 0, 49999, function (buffer) { mp4box.appendBuffer(buffer); getFileRange(testFiles[index].url, 50000, Infinity, function (buffer) { mp4box.appendBuffer(buffer); }); }); }); }); QUnit.asyncTest( "appending an overlapping buffer", function( assert ) { var index = 0; var timeout = window.setTimeout(function() { assert.ok(false, "Timeout"); QUnit.start(); }, 2000); var mp4box = new MP4Box(); mp4box.onReady = function(info) { window.clearTimeout(timeout); assert.ok(true, "moov found!" ); assert.equal(mp4box.nextBuffers.length, 1, "1 buffer stored" ); if (testFiles[index].info) { assert.deepEqual(info, testFiles[index].info, "Movie information is correct"); } QUnit.start(); } getFileRange(testFiles[index].url, 0, 24999, function (buffer) { mp4box.appendBuffer(buffer); getFileRange(testFiles[index].url, 10000, 49999, function (buffer) { mp4box.appendBuffer(buffer); getFileRange(testFiles[index].url, 50000, Infinity, function (buffer) { mp4box.appendBuffer(buffer); }); }); }); }); QUnit.asyncTest( "appending a buffer overlapping more than one existing buffer", function( assert ) { var index = 0; var timeout = window.setTimeout(function() { assert.ok(false, "Timeout"); QUnit.start(); }, 2000); var mp4box = new MP4Box(); mp4box.onReady = function(info) { window.clearTimeout(timeout); assert.ok(true, "moov found!" ); assert.equal(mp4box.nextBuffers.length, 1, "1 buffer stored" ); if (testFiles[index].info) { assert.deepEqual(info, testFiles[index].info, "Movie information is correct"); } QUnit.start(); } getFileRange(testFiles[index].url, 0, 24999, function (buffer) { mp4box.appendBuffer(buffer); getFileRange(testFiles[index].url, 25000, 49999, function (buffer) { mp4box.appendBuffer(buffer); getFileRange(testFiles[index].url, 20000, 60000, function (buffer) { mp4box.appendBuffer(buffer); getFileRange(testFiles[index].url, 60000, Infinity, function (buffer) { mp4box.appendBuffer(buffer); }); }); }); }); }); QUnit.asyncTest( "appending only one buffer with fileStart different from zero", function( assert ) { var index = 0; var timeout = window.setTimeout(function() { assert.ok(true, "Timeout"); QUnit.start(); }, 2000); var mp4box = new MP4Box(); mp4box.onReady = function(info) { window.clearTimeout(timeout); assert.ok(false, "moov found!" ); QUnit.start(); } getFileRange(testFiles[index].url, 25000, 49999, function (buffer) { mp4box.appendBuffer(buffer); }); }); QUnit.asyncTest( "appending an overlapping smaller buffer", function( assert ) { var index = 0; var timeout = window.setTimeout(function() { assert.ok(false, "Timeout"); QUnit.start(); }, 2000); var mp4box = new MP4Box(); mp4box.onReady = function(info) { window.clearTimeout(timeout); assert.ok(true, "moov found!" ); assert.equal(mp4box.nextBuffers.length, 1, "1 buffer stored" ); if (testFiles[index].info) { assert.deepEqual(info, testFiles[index].info, "Movie information is correct"); } QUnit.start(); } getFileRange(testFiles[index].url, 0, 24999, function (buffer) { mp4box.appendBuffer(buffer); getFileRange(testFiles[index].url, 10000, 15000, function (buffer) { mp4box.appendBuffer(buffer); getFileRange(testFiles[index].url, 25000, Infinity, function (buffer) { mp4box.appendBuffer(buffer); }); }); }); }); QUnit.asyncTest( "appending many overlapping buffers", function( assert ) { var index = 0; var timeout = window.setTimeout(function() { assert.ok(false, "Timeout"); QUnit.start(); }, 2000); var mp4box = new MP4Box(); mp4box.onReady = function(info) { window.clearTimeout(timeout); assert.ok(true, "moov found!" ); assert.equal(mp4box.nextBuffers.length, 1, "1 buffer stored" ); if (testFiles[index].info) { assert.deepEqual(info, testFiles[index].info, "Movie information is correct"); } QUnit.start(); } getFileRange(testFiles[index].url, 1000, 80000, function (buffer) { mp4box.appendBuffer(buffer); getFileRange(testFiles[index].url, 550, 650, function (buffer) { mp4box.appendBuffer(buffer); getFileRange(testFiles[index].url, 650, 750, function (buffer) { mp4box.appendBuffer(buffer); getFileRange(testFiles[index].url, 750, 850, function (buffer) { mp4box.appendBuffer(buffer); getFileRange(testFiles[index].url, 850, 950, function (buffer) { mp4box.appendBuffer(buffer); getFileRange(testFiles[index].url, 950, 1050, function (buffer) { mp4box.appendBuffer(buffer); getFileRange(testFiles[index].url, 0, 1050, function (buffer) { mp4box.appendBuffer(buffer); }); }); }); }); }); }); }); }); QUnit.module("Segmentation/Extraction tests"); QUnit.asyncTest( "Basic Segmentation", function( assert ) { var index = 0; var track_id; var timeout = window.setTimeout(function() { assert.ok(false, "Timeout"); QUnit.start(); }, 2000); var mp4box = new MP4Box(); mp4box.onSegment = function(id, user, buffer, sampleNum) { assert.ok(true, "First segment received!" ); assert.equal(id, track_id, "track id is correct"); assert.equal(user, null, "user is correct"); assert.equal(sampleNum, 10, "number of samples is correct"); assert.ok(buffer.byteLength, "buffer is not empty"); mp4box.unsetSegmentOptions(track_id); window.clearTimeout(timeout); QUnit.start(); } mp4box.onReady = function(info) { assert.ok(true, "moov found!" ); track_id = info.tracks[0].id; mp4box.setSegmentOptions(track_id, null, { nbSamples: 10, rapAlignement: true } ); mp4box.initializeSegmentation(); } getFile(testFiles[index].url, function (buffer) { mp4box.appendBuffer(buffer); }); }); QUnit.asyncTest( "Segmentation when no sample is ready", function( assert ) { var index = 0; var track_id; var timeout = window.setTimeout(function() { assert.ok(true, "Timeout"); QUnit.start(); }, 2000); var mp4box = new MP4Box(); mp4box.onSegment = function(id, user, buffer, sampleNum) { assert.ok(false, "No segment data"); QUnit.start(); } mp4box.onReady = function(info) { assert.ok(true, "moov found!" ); track_id = info.tracks[0].id; mp4box.setSegmentOptions(track_id, null, { nbSamples: 10, rapAlignement: true } ); mp4box.initializeSegmentation(); } getFileRange(testFiles[index].url, 0, 68500, function (buffer) { mp4box.appendBuffer(buffer); }); }); QUnit.asyncTest( "Segmentation without callback", function( assert ) { var index = 0; var track_id; var timeout = window.setTimeout(function() { assert.ok(false, "Timeout"); QUnit.start(); }, 2000); var mp4box = new MP4Box(); mp4box.onReady = function(info) { assert.ok(true, "moov found!" ); track_id = info.tracks[0].id; mp4box.setSegmentOptions(track_id, null, { nbSamples: 10, rapAlignement: true } ); mp4box.initializeSegmentation(); } getFile(testFiles[index].url, function (buffer) { mp4box.appendBuffer(buffer); window.clearTimeout(timeout); assert.ok(true, "append ended before timeout!" ); QUnit.start(); }); }); QUnit.asyncTest( "Basic Extraction", function( assert ) { var index = 0; var track_id; var timeout = window.setTimeout(function() { assert.ok(false, "Timeout"); QUnit.start(); }, 2000); var mp4box = new MP4Box(); mp4box.onSamples = function(id, user, samples) { assert.ok(true, "First extracted samples received!" ); assert.equal(id, track_id, "track id is correct"); assert.equal(user, null, "user is correct"); assert.equal(samples.length, 10, "got 10 samples!"); mp4box.unsetExtractionOptions(track_id); window.clearTimeout(timeout); QUnit.start(); } mp4box.onReady = function(info) { assert.ok(true, "moov found!" ); track_id = info.tracks[0].id; mp4box.setExtractionOptions(track_id, null, { nbSamples: 10, rapAlignement: true } ); } getFile(testFiles[index].url, function (buffer) { mp4box.appendBuffer(buffer); }); }); QUnit.asyncTest( "Extraction when no sample is ready", function( assert ) { var index = 0; var track_id; var timeout = window.setTimeout(function() { assert.ok(true, "Timeout"); QUnit.start(); }, 2000); var mp4box = new MP4Box(); mp4box.onSamples = function(id, user, samples) { assert.ok(false, "No sample data"); QUnit.start(); } mp4box.onReady = function(info) { assert.ok(true, "moov found!" ); track_id = info.tracks[0].id; mp4box.setExtractionOptions(track_id, null, { nbSamples: 10, rapAlignement: true } ); } getFileRange(testFiles[index].url, 0, 68500, function (buffer) { mp4box.appendBuffer(buffer); }); }); QUnit.asyncTest( "Extraction without callback", function( assert ) { var index = 0; var track_id; var timeout = window.setTimeout(function() { assert.ok(false, "Timeout"); QUnit.start(); }, 2000); var mp4box = new MP4Box(); mp4box.onReady = function(info) { assert.ok(true, "moov found!" ); track_id = info.tracks[0].id; mp4box.setExtractionOptions(track_id, null, { nbSamples: 10, rapAlignement: true } ); } getFile(testFiles[index].url, function (buffer) { mp4box.appendBuffer(buffer); window.clearTimeout(timeout); assert.ok(true, "append ended before timeout!" ); QUnit.start(); }); }); QUnit.module("Parsing-driven download"); QUnit.asyncTest( "Moov-last", function( assert ) { var mp4box = new MP4Box(); getFileRange('./mp4/moov_last.mp4', 0, 19, function (buffer) { var next_pos = mp4box.appendBuffer(buffer); assert.equal(next_pos, 32, "Next position after first append corresponds to next box start"); getFileRange('./mp4/moov_last.mp4', 20, 39, function (buffer) { var next_pos = mp4box.appendBuffer(buffer); assert.equal(next_pos, 40, "Next position after second append corresponds to next box start"); getFileRange('./mp4/moov_last.mp4', 40, 100, function (buffer) { var next_pos = mp4box.appendBuffer(buffer); assert.equal(next_pos, 1309934+40, "Next position after third append corresponds to moov position"); QUnit.start(); }); }); }); }); QUnit.asyncTest( "mdat progressive download", function( assert ) { var index = 0; var mp4box = new MP4Box(); getFileRange(testFiles[index].url, 0, 79999, function (buffer) { var next_pos = mp4box.appendBuffer(buffer); assert.equal(next_pos, 80000, "Next position after first append corresponds to end of previous buffer (moov entirely parsed)"); getFileRange(testFiles[index].url, 80000, 119999, function (buffer) { var next_pos = mp4box.appendBuffer(buffer); assert.equal(next_pos, 120000, "Next position after second append corresponds to end of previous buffer (contiguous append)"); getFileRange(testFiles[index].url, 200000, 259999, function (buffer) { var next_pos = mp4box.appendBuffer(buffer); assert.equal(next_pos, 120000, "Next position after third append corresponds to end of second buffer (non-contiguous append)"); getFileRange(testFiles[index].url, 120000, 199999, function (buffer) { var next_pos = mp4box.appendBuffer(buffer); assert.equal(next_pos, 260000, "Next position after fourth append corresponds to end of all buffer (all-contiguous)"); QUnit.start(); }); }); }); }); }); QUnit.module("Seek tests"); QUnit.asyncTest( "full download and seek at rap 0", function( assert ) { var index = 0; var timeout = window.setTimeout(function() { assert.ok(false, "Timeout"); QUnit.start(); }, 2000); var mp4box = new MP4Box(); var seekStep = 0; var seekTime = 1.1; var doExtraction = false; var track_id; mp4box.onSamples = function(id, user, samples) { assert.equal(doExtraction, true, "Callback called only when samples are extracted"); assert.notEqual(seekStep, 2, "Callback should never be reached on step 2"); assert.equal(samples.length, 1, "One sample received"); if (seekStep === 0) { assert.equal(samples[0].is_rap, true, "Step 0 Sample RAP status matches"); assert.equal(samples[0].dts, 25000, "Step 0 Sample DTS matches"); assert.equal(samples[0].cts, 25000, "Step 0 Sample CTS matches"); assert.equal(samples[0].size, 3158, "Step 0 Sample size matches"); } else if (seekStep === 1) { assert.equal(samples[0].is_rap, false, "Step 1 Sample RAP status matches"); assert.equal(samples[0].dts, 27000, "Step 1 Sample DTS matches"); assert.equal(samples[0].cts, 27000, "Step 1 Sample CTS matches"); assert.equal(samples[0].size, 176, "Step 1 Sample size matches"); } mp4box.unsetExtractionOptions(track_id); if (seekStep === 1) { window.clearTimeout(timeout); QUnit.start(); } } mp4box.onReady = function(info) { assert.ok(true, "moov found!" ); track_id = info.tracks[0].id; } getFile(testFiles[index].url, function (buffer) { /* appending the whole buffer without setting any extraction option, no sample will be processed */ mp4box.appendBuffer(buffer); /* setting extraction option and then seeking and calling sample processing */ seekStep = 0; mp4box.setExtractionOptions(track_id, null, { nbSamples: 1, rapAlignement: true } ); doExtraction = true; mp4box.seek(seekTime, true); // find preceeding rap mp4box.flush(); /* setting extraction option and then seeking and calling sample processing */ seekStep = 1; mp4box.setExtractionOptions(track_id, null, { nbSamples: 1, rapAlignement: true } ); mp4box.seek(seekTime, false); // don't seek on rap mp4box.flush(); seekStep = 2; mp4box.seek(10000, false); }); }); QUnit.asyncTest( "Seek without moov", function( assert ) { var index = 0; var timeout = window.setTimeout(function() { assert.ok(false, "Timeout"); QUnit.start(); }, 2000); var mp4box = new MP4Box(); getFileRange(testFiles[index].url, 0, 10, function (buffer) { mp4box.appendBuffer(buffer); assert.throws(function() { mp4box.seek(10, true); }, "Exception thrown because moov not found"); window.clearTimeout(timeout); QUnit.start(); }); }); QUnit.asyncTest( "Seek in the past", function( assert ) { var index = 0; var timeout = window.setTimeout(function() { assert.ok(false, "Timeout"); QUnit.start(); }, 2000); var mp4box = new MP4Box(); var seekStep = 0; var seekTime0 = 1.1; var seekTime1 = 0.1; var track_id; mp4box.onSamples = function(id, user, samples) { if (seekStep === 0) { assert.equal(samples[0].is_rap, true, "Step 0 Sample RAP status matches"); assert.equal(samples[0].dts, 25000, "Step 0 Sample DTS matches"); assert.equal(samples[0].cts, 25000, "Step 0 Sample CTS matches"); assert.equal(samples[0].size, 3158, "Step 0 Sample size matches"); } else if (seekStep === 1) { assert.equal(samples[0].is_rap, true, "Step 1 Sample RAP status matches"); assert.equal(samples[0].dts, 0, "Step 1 Previous Sample DTS matches"); assert.equal(samples[0].cts, 0, "Step 1 Sample CTS matches"); assert.equal(samples[0].size, 3291, "Step 1 Sample size matches"); } mp4box.unsetExtractionOptions(track_id); if (seekStep === 1) { window.clearTimeout(timeout); QUnit.start(); } } mp4box.onReady = function(info) { assert.ok(true, "moov found!" ); track_id = info.tracks[0].id; } getFile(testFiles[index].url, function (buffer) { /* appending the whole buffer without setting any extraction option, no sample will be processed */ mp4box.appendBuffer(buffer); /* setting extraction option and then seeking and calling sample processing */ seekStep = 0; mp4box.setExtractionOptions(track_id, null, { nbSamples: 1, rapAlignement: true } ); doExtraction = true; mp4box.seek(seekTime0, true); // find preceeding rap mp4box.flush(); /* setting extraction option and then seeking and calling sample processing */ seekStep = 1; mp4box.setExtractionOptions(track_id, null, { nbSamples: 1, rapAlignement: true } ); mp4box.seek(seekTime1, true); // find preceeding rap mp4box.flush(); }); }); QUnit.asyncTest( "Seek and fetch out of order", function( assert ) { var index = 0; var timeout = window.setTimeout(function() { assert.ok(false, "Timeout"); QUnit.start(); }, 2000); var mp4box = new MP4Box(); var track_id; mp4box.onSamples = function(id, user, samples) { console.log("Getting sample for time:"+samples[0].dts/samples[0].timescale); if (samples[0].dts === 10000000) { window.clearTimeout(timeout); QUnit.start(); } } mp4box.onReady = function(info) { assert.ok(true, "moov found!" ); track_id = info.tracks[0].id; /* setting extraction option and then seeking and calling sample processing */ mp4box.setExtractionOptions(track_id, null, { nbSamples: 1000, rapAlignement: true } ); /* getting the first 1000 samples */ getFileRange(testFiles[index].url, 68190, 371814, function (buffer) { mp4box.appendBuffer(buffer); mp4box.seek(560, true); // fetching the last group of 1000 samples in the file getFileRange(testFiles[index].url, 3513891, Infinity, function(buffer) { mp4box.appendBuffer(buffer); mp4box.seek(400, true); getFileRange(testFiles[index].url, 2558231, 2797139, function(buffer) { mp4box.appendBuffer(buffer); }); }); }); } getFileRange(testFiles[index].url, 0, 68190, function (buffer) { /* appending the ftyp/moov */ mp4box.appendBuffer(buffer); }); }); QUnit.module("Write tests"); QUnit.asyncTest( "Generate initialization segment", function( assert ) { var index = 0; var track_id; var timeout = window.setTimeout(function() { assert.ok(false, "Timeout"); QUnit.start(); }, 2000); var mp4box = new MP4Box(); mp4box.onReady = function(info) { window.clearTimeout(timeout); assert.ok(true, "moov found!" ); track_id = info.tracks[0].id; mp4box.setSegmentOptions(track_id, null, { nbSamples: 10, rapAlignement: false } ); assert.ok(mp4box.getInitializationSegment(), "Init segments generated"); QUnit.start(); } getFile(testFiles[index].url, function (buffer) { mp4box.appendBuffer(buffer); }); }); QUnit.asyncTest( "Write-back the entire file", function( assert ) { var index = 0; var timeout = window.setTimeout(function() { assert.ok(false, "Timeout"); QUnit.start(); }, 2000); var mp4box = new MP4Box(); mp4box.onReady = function(info) { window.clearTimeout(timeout); assert.ok(true, "moov found!" ); var b = mp4box.writeFile(); QUnit.start(); } getFile(testFiles[index].url, function (buffer) { mp4box.appendBuffer(buffer); }); }); QUnit.module("Playback test"); QUnit.asyncTest( "Long Segmentation", function( assert ) { var index = 10; var mp4box = new MP4Box(); var start = 0; var size = 5000000; //1MB var nbFragSamples = 10000; var lastSample; Log.setLogLevel(Log.i); function getNext() { getFileRange(testFiles[index].url, start, start+size-1, function (buffer) { mp4box.appendBuffer(buffer); if (buffer.byteLength === size) { start += size; getNext(); } }); } mp4box.onSegment = function(id, user, buffer, sampleNum) { assert.ok(true, "Segment received!" ); if (sampleNum === lastSample) { mp4box.unsetSegmentOptions(track_id); QUnit.start(); } } mp4box.onSamples = function(id, user, samples) { assert.ok(true, "Samples received!" ); console.log("Memory usage: used/total/limit "+console.memory.usedJSHeapSize+"/"+console.memory.totalJSHeapSize+"/"+console.memory.jsHeapSizeLimit); if (samples === lastSample) { mp4box.unsetSegmentOptions(track_id); QUnit.start(); } } mp4box.onReady = function(info) { assert.ok(true, "moov found!" ); track_id = info.tracks[0].id; mp4box.setSegmentOptions(track_id, null, { nbSamples: nbFragSamples, rapAlignement: true } ); if (info.tracks[0].nb_samples % nbFragSamples === 0) { lastSample = info.tracks[0].nb_samples - nbFragSamples; } else { lastSample = info.tracks[0].nb_samples - info.tracks[0].nb_samples % nbFragSamples; } mp4box.initializeSegmentation(); getNext(); } getFileRange(testFiles[index].url, start, start+size-1, function (buffer) { start += size; mp4box.appendBuffer(buffer); }); }); /*QUnit.module("misc"); QUnit.asyncTest( "Byte-by-byte parsing", function( assert ) { var index = 0; var timeout = window.setTimeout(function() { assert.ok(false, "Timeout"); QUnit.start(); }, 5000); var mp4box = new MP4Box(); mp4box.onReady = function(info) { window.clearTimeout(timeout); assert.ok(true, "moov found!" ); if (testFiles[index].info) { assert.deepEqual(info, testFiles[index].info, "Movie information is correct"); } QUnit.start(); } var xhr_callback = function (buffer) { for (var i = 0; i < buffer.byteLength; i++) { var b1 = new Uint8Array(1); var bf = new Uint8Array(buffer); b1[0] = bf[i]; b1.buffer.fileStart = i; mp4box.appendBuffer(b1.buffer); } }; getFileRange(testFiles[index].url, 0, Infinity, xhr_callback); });*/ /* Not yet tested: - error on extraction/segmentation settings before onReady - onMoovStart event (partial parsing & entire parsing) - seek - flush - track ref - segment from fragmentation - release samples - release buffers - extract VTT samples - mp4 features - edit list - boxes: - uuid - large box - version 1: mvhd, tkhd, mdhd, hdlr, ctts, stss, stsh, co64, stsc, stsz, mehd, subs - cslg, stsh, co64 - descriptors: - large desc - unknown desc - depends, url, ocr */