Skip to content

Commit

Permalink
Parse default duration in WVTT MP4s
Browse files Browse the repository at this point in the history
This adds support for WVTT samples with no sample duration by parsing
the default duration from the "tfhd" box.  If neither is provided, we
now log an error to the console.

Closes #919

Change-Id: Icc5550de7bc6018253004a5d3429ccc083ff6cb6
  • Loading branch information
joeyparrish committed Aug 7, 2017
1 parent 98c4c14 commit 7d36a20
Show file tree
Hide file tree
Showing 3 changed files with 116 additions and 56 deletions.
42 changes: 38 additions & 4 deletions lib/text/mp4_vtt_parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ shaka.text.Mp4VttParser.prototype.parseMedia = function(data, time) {
var sawTFDT = false;
var sawTRUN = false;
var sawMDAT = false;
var defaultDuration = null;

new Mp4Parser()
.box('moof', Mp4Parser.children)
Expand All @@ -130,6 +131,13 @@ shaka.text.Mp4VttParser.prototype.parseMedia = function(data, time) {
box.reader.readUint32() :
box.reader.readUint64();
})
.fullBox('tfhd', function(box) {
goog.asserts.assert(
box.flags != null,
'A TFHD box should have a valid flags value');
defaultDuration = Mp4VttParser.parseTFHD_(
box.flags, box.reader);
})
.fullBox('trun', function(box) {
sawTRUN = true;
goog.asserts.assert(
Expand Down Expand Up @@ -173,12 +181,13 @@ shaka.text.Mp4VttParser.prototype.parseMedia = function(data, time) {
var presentation = presentations[i];
var payload = payloads[i];

if (presentation.duration) {
var duration = presentation.duration || defaultDuration;
if (duration) {
var startTime = presentation.timeOffset ?
baseTime + presentation.timeOffset :
currentTime;

currentTime = startTime + presentation.duration;
currentTime = startTime + duration;

// The payload can be null as that would mean that it was a VTTE and
// was only inserted to keep the presentation times in sync with the
Expand All @@ -189,6 +198,8 @@ shaka.text.Mp4VttParser.prototype.parseMedia = function(data, time) {
time.periodStart + startTime / this.timescale_,
time.periodStart + currentTime / this.timescale_));
}
} else {
shaka.log.error('WVTT sample duration unknown, and no default found!');
}
}

Expand All @@ -213,6 +224,30 @@ shaka.text.Mp4VttParser.prototype.parseMedia = function(data, time) {
shaka.text.Mp4VttParser.TimeSegment;


/**
* @param {number} flags
* @param {!shaka.util.DataViewReader} reader
* @return {?number} the default_sample_duration field, if present
* @private
*/
shaka.text.Mp4VttParser.parseTFHD_ = function(flags, reader) {
// skip "track_ID"
reader.skip(4);

// skip "base_data_offset" if present
if (flags & 0x000001) { reader.skip(8); }

// skip "sample_description_index" if present
if (flags & 0x000002) { reader.skip(4); }

// read and return "default_sample_duration" if present
if (flags & 0x000008) { return reader.readUint32(); }

// There is no "default_sample_duration".
return null;
};


/**
* @param {number} version
* @param {number} flags
Expand All @@ -232,7 +267,6 @@ shaka.text.Mp4VttParser.parseTRUN_ = function(version, flags, reader) {
var samples = [];

for (var sampleIndex = 0; sampleIndex < sampleCount; sampleIndex++) {

/** @type {shaka.text.Mp4VttParser.TimeSegment} */
var sample = {
duration: null,
Expand All @@ -246,7 +280,7 @@ shaka.text.Mp4VttParser.parseTRUN_ = function(version, flags, reader) {
if (flags & 0x000200) { reader.skip(4); }

// skip "sample_flags" if present
if (flags & 0x000400) {reader.skip(4); }
if (flags & 0x000400) { reader.skip(4); }

// read "sample_time_offset" if present
if (flags & 0x000800) {
Expand Down
Binary file added test/test/assets/vtt-segment-no-duration.mp4
Binary file not shown.
130 changes: 78 additions & 52 deletions test/text/mp4_vtt_parser_unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,17 @@
* limitations under the License.
*/

describe('Mp4vttParser', function() {
describe('Mp4VttParser', function() {
/** @const */
var vttInitSegmentUri = '/base/test/test/assets/vtt-init.mp4';
/** @const */
var vttSegmentUri = '/base/test/test/assets/vtt-segment.mp4';
/** @const */
var vttSegSettingsUri = '/base/test/test/assets/vtt-segment-settings.mp4';
/** @const */
var vttSegNoDurationUri =
'/base/test/test/assets/vtt-segment-no-duration.mp4';
/** @const */
var audioInitSegmentUri = '/base/test/test/assets/sintel-audio-init.mp4';

/** @type {!ArrayBuffer} */
Expand All @@ -32,6 +35,8 @@ describe('Mp4vttParser', function() {
/** @type {!ArrayBuffer} */
var vttSegSettings;
/** @type {!ArrayBuffer} */
var vttSegNoDuration;
/** @type {!ArrayBuffer} */
var audioInitSegment;

/** @type {boolean} */
Expand All @@ -53,12 +58,14 @@ describe('Mp4vttParser', function() {
shaka.test.Util.fetch(vttInitSegmentUri),
shaka.test.Util.fetch(vttSegmentUri),
shaka.test.Util.fetch(vttSegSettingsUri),
shaka.test.Util.fetch(vttSegNoDurationUri),
shaka.test.Util.fetch(audioInitSegmentUri)
]).then(function(responses) {
vttInitSegment = responses[0];
vttSegment = responses[1];
vttSegSettings = responses[2];
audioInitSegment = responses[3];
vttSegNoDuration = responses[3];
audioInitSegment = responses[4];
}).catch(fail).then(done);
});

Expand All @@ -74,76 +81,95 @@ describe('Mp4vttParser', function() {
});

it('parses media segment', function() {
var cues =
[
{
start: 111.8,
end: 115.8,
payload: 'It has shed much innocent blood.\n'
},
{
start: 118,
end: 120,
payload:
'You\'re a fool for traveling alone,\nso completely unprepared.\n'
}
];
var cues = [
{
start: 111.8,
end: 115.8,
payload: 'It has shed much innocent blood.\n'
},
{
start: 118,
end: 120,
payload:
'You\'re a fool for traveling alone,\nso completely unprepared.\n'
}
];

var parser = new shaka.text.Mp4VttParser();
parser.parseInit(vttInitSegment);
var time = {periodStart: 0, segmentStart: 0, segmentEnd: 0 };
var time = {periodStart: 0, segmentStart: 0, segmentEnd: 0};
var result = parser.parseMedia(vttSegment, time);
verifyHelper(cues, result);
});

it('parses media segment containing settings', function() {
var Cue = shaka.text.Cue;
var cues =
[
{
start: 111.8,
end: 115.8,
payload: 'It has shed much innocent blood.\n',
align: 'right',
size: 50,
position: 10
},
{
start: 118,
end: 120,
payload:
'You\'re a fool for traveling alone,\nso completely unprepared.\n',
writingDirection: Cue.writingDirection.VERTICAL_LEFT_TO_RIGHT,
line: 1
}
];
var cues = [
{
start: 111.8,
end: 115.8,
payload: 'It has shed much innocent blood.\n',
align: 'right',
size: 50,
position: 10
},
{
start: 118,
end: 120,
payload:
'You\'re a fool for traveling alone,\nso completely unprepared.\n',
writingDirection: Cue.writingDirection.VERTICAL_LEFT_TO_RIGHT,
line: 1
}
];

var parser = new shaka.text.Mp4VttParser();
parser.parseInit(vttInitSegment);
var time = {periodStart: 0, segmentStart: 0, segmentEnd: 0 };
var time = {periodStart: 0, segmentStart: 0, segmentEnd: 0};
var result = parser.parseMedia(vttSegSettings, time);
verifyHelper(cues, result);
});

it('parses media segments without a sample duration', function() {
// Regression test for https://github.com/google/shaka-player/issues/919
var cues = [
{ start: 10, end: 11, payload: 'cue 10' },
{ start: 11, end: 12, payload: 'cue 11' },
{ start: 12, end: 13, payload: 'cue 12' },
{ start: 13, end: 14, payload: 'cue 13' },
{ start: 14, end: 15, payload: 'cue 14' },
{ start: 15, end: 16, payload: 'cue 15' },
{ start: 16, end: 17, payload: 'cue 16' },
{ start: 17, end: 18, payload: 'cue 17' },
{ start: 18, end: 19, payload: 'cue 18' },
{ start: 19, end: 20, payload: 'cue 19' }
];

var parser = new shaka.text.Mp4VttParser();
parser.parseInit(vttInitSegment);
var time = {periodStart: 0, segmentStart: 0, segmentEnd: 0};
var result = parser.parseMedia(vttSegNoDuration, time);
verifyHelper(cues, result);
});

it('accounts for offset', function() {
var cues =
[
{
start: 121.8,
end: 125.8,
payload: 'It has shed much innocent blood.\n'
},
{
start: 128,
end: 130,
payload:
'You\'re a fool for traveling alone,\nso completely unprepared.\n'
}
];
var cues = [
{
start: 121.8,
end: 125.8,
payload: 'It has shed much innocent blood.\n'
},
{
start: 128,
end: 130,
payload:
'You\'re a fool for traveling alone,\nso completely unprepared.\n'
}
];

var parser = new shaka.text.Mp4VttParser();
parser.parseInit(vttInitSegment);
var time = {periodStart: 10, segmentStart: 0, segmentEnd: 0 };
var time = {periodStart: 10, segmentStart: 0, segmentEnd: 0};
var result = parser.parseMedia(vttSegment, time);
verifyHelper(cues, result);
});
Expand Down

0 comments on commit 7d36a20

Please sign in to comment.