'use strict'; var segments = require('data-files!segments'); var probe = require('../lib/mp4/probe'); var CaptionParser = require('../lib/mp4').CaptionParser; var captionParser; var dashInit = segments['dash-608-captions-init.mp4'](); // This file includes 2 segments data to force a flush // of the first caption. The second caption is at 200s var dashSegment = segments['dash-608-captions-seg.m4s'](); var mp4Helpers = require('./utils/mp4-helpers'); var box = mp4Helpers.box; var seiNalUnitGenerator = require('./utils/sei-nal-unit-generator'); var makeMdatFromCaptionPackets = seiNalUnitGenerator.makeMdatFromCaptionPackets; var characters = seiNalUnitGenerator.characters; var packets0; var version0Moof; var version0Segment; var packets1; var version1Moof; var version1Segment; QUnit.module('MP4 Caption Parser', { beforeEach: function() { captionParser = new CaptionParser(); captionParser.init(); }, afterEach: function() { captionParser.reset(); } }); QUnit.test('parse captions from real segment', function(assert) { var trackIds; var timescales; var cc; trackIds = probe.videoTrackIds(dashInit); timescales = probe.timescale(dashInit); cc = captionParser.parse(dashSegment, trackIds, timescales); assert.equal(cc.captions.length, 1); assert.equal(cc.captions[0].text, '00:00:00', 'real segment caption has correct text'); assert.equal(cc.captions[0].stream, 'CC1', 'real segment caption has correct stream'); assert.equal(cc.captions[0].startTime, 0, 'real segment caption has correct startTime'); assert.equal(cc.captions[0].endTime, 119, 'real segment caption has correct endTime'); assert.equal(cc.captionStreams.CC1, true, 'real segment caption streams have correct settings'); }); QUnit.test('parse captions when init segment received late', function(assert) { var trackIds; var timescales; var cc; trackIds = probe.videoTrackIds(dashInit); timescales = probe.timescale(dashInit); cc = captionParser.parse(dashSegment, [], {}); assert.ok(!cc, 'there should not be any parsed captions yet'); cc = captionParser.parse(dashSegment, trackIds, timescales); assert.equal(cc.captions.length, 1); }); QUnit.test('parseTrackId for version 0 and version 1 boxes', function(assert) { var v0Captions; var v1Captions; v0Captions = captionParser.parse( new Uint8Array(version0Segment), // segment [1], // trackIds { 1: 90000 }); // timescales); assert.equal(v0Captions.captions.length, 1, 'got 1 version0 caption'); assert.equal(v0Captions.captions[0].text, 'test string #1', 'got the expected version0 caption text'); assert.equal(v0Captions.captions[0].stream, 'CC1', 'returned the correct caption stream CC1'); assert.equal(v0Captions.captions[0].startTime, 10 / 90000, 'the start time for version0 caption is correct'); assert.equal(v0Captions.captions[0].endTime, 10 / 90000, 'the end time for version0 caption is correct'); assert.equal(v0Captions.captionStreams.CC1, true, 'stream is CC1'); assert.ok(!v0Captions.captionStreams.CC4, 'stream is not CC4'); // Clear parsed captions captionParser.clearParsedCaptions(); v1Captions = captionParser.parse( new Uint8Array(version1Segment), [2], // trackIds { 2: 90000 }); // timescales assert.equal(v1Captions.captions.length, 1, 'got version1 caption'); assert.equal(v1Captions.captions[0].text, 'test string #2', 'got the expected version1 caption text'); assert.equal(v1Captions.captions[0].stream, 'CC4', 'returned the correct caption stream CC4'); assert.equal(v1Captions.captions[0].startTime, 30 / 90000, 'the start time for version1 caption is correct'); assert.equal(v1Captions.captions[0].endTime, 30 / 90000, 'the end time for version1 caption is correct'); assert.equal(v1Captions.captionStreams.CC4, true, 'stream is CC4'); assert.ok(!v1Captions.captionStreams.CC1, 'stream is not CC1'); }); // --------- // Test Data // --------- // "test string #1", channel 1, field 1 packets0 = [ // Send another command so that the second EOC isn't ignored { ccData: 0x1420, type: 0 }, // RCL, resume caption loading { ccData: 0x1420, type: 0 }, // 'test string #1' { ccData: characters('te'), type: 0 }, { ccData: characters('st'), type: 0 }, { ccData: characters(' s'), type: 0 }, // 'test string #1' continued { ccData: characters('tr'), type: 0 }, { ccData: characters('in'), type: 0 }, { ccData: characters('g '), type: 0 }, { ccData: characters('#1'), type: 0 }, // EOC, End of Caption. End display { ccData: 0x142f, type: 0 }, // EOC, End of Caption. Finished transmitting, begin display { ccData: 0x142f, type: 0 }, // Send another command so that the second EOC isn't ignored { ccData: 0x1420, type: 0 }, // EOC, End of Caption. End display { ccData: 0x142f, type: 0 } ]; // "test string #2", channel 2, field 2 packets1 = [ // Send another command so that the second EOC isn't ignored { ccData: 0x1d20, type: 1 }, // RCL, resume caption loading { ccData: 0x1d20, type: 1 }, // 'test string #2' { ccData: characters('te'), type: 1 }, { ccData: characters('st'), type: 1 }, { ccData: characters(' s'), type: 1 }, // 'test string #2' continued { ccData: characters('tr'), type: 1 }, { ccData: characters('in'), type: 1 }, { ccData: characters('g '), type: 1 }, { ccData: characters('#2'), type: 1 }, // EOC, End of Caption. End display { ccData: 0x1d2f, type: 1 }, // EOC, End of Caption. Finished transmitting, begin display { ccData: 0x1d2f, type: 1 }, // Send another command so that the second EOC isn't ignored { ccData: 0x1d20, type: 1 }, // EOC, End of Caption. End display { ccData: 0x1d2f, type: 1 } ]; /** * version 0: * Uses version 0 boxes, no first sample flags * sample size, flags, duration, composition time offset included. **/ version0Moof = box('moof', box('traf', box('tfhd', 0x00, // version 0x00, 0x00, 0x00, // flags 0x00, 0x00, 0x00, 0x01, // track_ID 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // base_data_offset 0x00, 0x00, 0x00, 0x00, // sample_description_index 0x00, 0x00, 0x00, 0x00, // default_sample_duration 0x00, 0x00, 0x00, 0x00, // default_sample_size 0x00, 0x00, 0x00, 0x00), // default_sample_flags box('tfdt', 0x00, // version 0x00, 0x00, 0x00, // flags 0x00, 0x00, 0x00, 0x00), // baseMediaDecodeTime, box('trun', 0x00, // version 0x00, 0x0f, 0x01, // flags: dataOffsetPresent, sampleDurationPresent, // sampleSizePresent, sampleFlagsPresent, // sampleCompositionTimeOffsetsPresent 0x00, 0x00, 0x00, 0x02, // sample_count 0x00, 0x00, 0x00, 0x00, // data_offset, no first_sample_flags // sample 1 0x00, 0x00, 0x00, 0x0a, // sample_duration = 10 0x00, 0x00, 0x00, 0x0a, // sample_size = 10 0x00, 0x00, 0x00, 0x00, // sample_flags 0x00, 0x00, 0x00, 0x0a, // signed sample_composition_time_offset = 10 // sample 2 0x00, 0x00, 0x00, 0x0a, // sample_duration = 10 0x00, 0x00, 0x00, 0x0a, // sample_size = 10 0x00, 0x00, 0x00, 0x00, // sample_flags 0x00, 0x00, 0x00, 0x14))); // signed sample_composition_time_offset = 20 version0Segment = version0Moof.concat(makeMdatFromCaptionPackets(packets0)); /** * version 1: * Uses version 1 boxes, has first sample flags, * other samples include flags and composition time offset only. **/ version1Moof = box('moof', box('traf', box('tfhd', 0x01, // version 0x00, 0x00, 0x18, // flags 0x00, 0x00, 0x00, 0x02, // track_ID // no base_data_offset, sample_description_index 0x00, 0x00, 0x00, 0x0a, // default_sample_duration = 10 0x00, 0x00, 0x00, 0x0a), // default_sample_size = 10 box('tfdt', 0x01, // version 0x00, 0x00, 0x00, // flags 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14), // baseMediaDecodeTime = 20, box('trun', 0x01, // version 0x00, 0x0c, 0x05, // flags: dataOffsetPresent, sampleFlagsPresent, // firstSampleFlagsPresent, // sampleCompositionTimeOffsetsPresent 0x00, 0x00, 0x00, 0x02, // sample_count 0x00, 0x00, 0x00, 0x00, // data_offset, has first_sample_flags // sample 1 0x00, 0x00, 0x00, 0x00, // sample_flags 0x00, 0x00, 0x00, 0x0a, // signed sample_composition_time_offset = 10 // sample 2 0x00, 0x00, 0x00, 0x00, // sample_flags 0x00, 0x00, 0x00, 0x14))); // signed sample_composition_time_offset = 20 version1Segment = version1Moof.concat(makeMdatFromCaptionPackets(packets1));