Skip to content

Commit

Permalink
Fix cue issues on IE and Edge
Browse files Browse the repository at this point in the history
  - Avoids zero-duration cues which would throw exceptions on IE/Edge
    and stop playback.  (Warns about these ignored cues.)
  - Pushes offset down into the text parsers since cue times are
    read-only on IE/Edge.  This fixes text errors in multi-period
    content on those browsers.
  - Factors VTTCue/TextTrackCue interface decisions out to TextEngine.
  - Adds new platform integration tests for cues.

Closes #501
Closes #502

Change-Id: Ie2210a91e28149f9c4f7fd34bff847aabbeeea63
  • Loading branch information
joeyparrish committed Aug 31, 2016
1 parent 24856d9 commit a8b012b
Show file tree
Hide file tree
Showing 11 changed files with 282 additions and 108 deletions.
7 changes: 5 additions & 2 deletions lib/media/mp4_ttml_parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,13 @@ goog.require('shaka.util.Mp4Parser');
* Extracts ttml segment from an mp4 file and
* invokes ttml parser to parse it.
* @param {ArrayBuffer} data
* @param {number} offset
* @param {?number} segmentStartTime
* @param {?number} segmentEndTime
* @return {!Array.<!TextTrackCue>}
*/
shaka.media.Mp4TtmlParser = function(data, segmentStartTime, segmentEndTime) {
shaka.media.Mp4TtmlParser =
function(data, offset, segmentStartTime, segmentEndTime) {
var reader = new shaka.util.DataViewReader(
new DataView(data),
shaka.util.DataViewReader.Endianness.BIG_ENDIAN);
Expand All @@ -42,7 +44,8 @@ shaka.media.Mp4TtmlParser = function(data, segmentStartTime, segmentEndTime) {
if (boxSize != shaka.util.Mp4Parser.BOX_NOT_FOUND) {
// mdat box found, use TtmlTextParser to parse the content
return shaka.media.TtmlTextParser(
reader.readBytes(boxSize - 8).buffer, segmentStartTime, segmentEndTime);
reader.readBytes(boxSize - 8).buffer, offset,
segmentStartTime, segmentEndTime);
}
var stppBoxSize = shaka.util.Mp4Parser.findSampleDescriptionBox(
data, shaka.media.Mp4TtmlParser.BOX_TYPE_STPP);
Expand Down
68 changes: 38 additions & 30 deletions lib/media/mp4_vtt_parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,16 @@ goog.require('shaka.util.TextParser');


/**
* Extracts vtt segment from an mp4 file and
* does the mapping to VttCue objects.
* Extracts VTT segment from an MP4 file and does the mapping to cue objects.
*
* @param {ArrayBuffer} data
* @param {number} offset
* @param {?number} segmentStartTime
* @param {?number} segmentEndTime
* @return {!Array.<!TextTrackCue>}
*/
shaka.media.Mp4VttParser = function(data, segmentStartTime, segmentEndTime) {
shaka.media.Mp4VttParser =
function(data, offset, segmentStartTime, segmentEndTime) {
var reader = new shaka.util.DataViewReader(
new DataView(data),
shaka.util.DataViewReader.Endianness.BIG_ENDIAN);
Expand All @@ -48,7 +50,8 @@ shaka.media.Mp4VttParser = function(data, segmentStartTime, segmentEndTime) {
segmentStartTime != null, 'start time should not be null');
goog.asserts.assert(segmentEndTime != null, 'end time should not be null');
return shaka.media.Mp4VttParser.parseData_(
reader.readBytes(boxSize - 8).buffer, segmentStartTime, segmentEndTime);
reader.readBytes(boxSize - 8).buffer, offset,
segmentStartTime, segmentEndTime);
}
var wvttBoxSize = shaka.util.Mp4Parser.findSampleDescriptionBox(
data, shaka.media.Mp4VttParser.BOX_TYPE_WVTT);
Expand All @@ -64,23 +67,26 @@ shaka.media.Mp4VttParser = function(data, segmentStartTime, segmentEndTime) {


/**
* Parses the content of the mdat mp4 box into
* VttCue objects.
* Parses the content of the mdat MP4 box into cue objects.
*
* @param {ArrayBuffer} data
* @param {number} offset
* @param {number} segmentStartTime
* @param {number} segmentEndTime
* @return {!Array.<!TextTrackCue>}
* @private
*/
shaka.media.Mp4VttParser.parseData_ = function(
data, segmentStartTime, segmentEndTime) {
data, offset, segmentStartTime, segmentEndTime) {
var reader = new shaka.util.DataViewReader(
new DataView(data),
shaka.util.DataViewReader.Endianness.BIG_ENDIAN);

segmentStartTime += offset;
segmentEndTime += offset;

var result = [];
// VttCues are represented as vttc boxes. Each box corresponds
// to a cue.
// Cues are represented as vttc boxes. Each box corresponds to a cue.
while (reader.hasMoreData()) {
var boxSize = shaka.util.Mp4Parser.findBox(
shaka.media.Mp4VttParser.BOX_TYPE_VTTC, reader);
Expand All @@ -89,8 +95,10 @@ shaka.media.Mp4VttParser.parseData_ = function(
break;
}
var cue = shaka.media.Mp4VttParser.parseCue_(
reader.readBytes(boxSize - 8).buffer, segmentStartTime, segmentEndTime);
result.push(cue);
reader.readBytes(boxSize - 8).buffer,
segmentStartTime, segmentEndTime);
if (cue)
result.push(cue);
}

return result;
Expand All @@ -112,7 +120,6 @@ shaka.media.Mp4VttParser.parseCue_ = function(
new DataView(data),
shaka.util.DataViewReader.Endianness.BIG_ENDIAN);

var cue = null;
var payload;
var settings;
var id;
Expand Down Expand Up @@ -148,26 +155,27 @@ shaka.media.Mp4VttParser.parseCue_ = function(
throw new shaka.util.Error(
shaka.util.Error.Category.TEXT,
shaka.util.Error.Code.INVALID_MP4_VTT);
} else if (window.VTTCue) {
cue = new VTTCue(segmentStartTime, segmentEndTime, payload);
if (id)
cue.id = id;
if (settings) {
var parser = new shaka.util.TextParser(settings);
var word = parser.readWord();
while (word) {
if (!shaka.media.VttTextParser.parseSetting(cue, word)) {
throw new shaka.util.Error(
shaka.util.Error.Category.TEXT,
shaka.util.Error.Code.INVALID_TEXT_SETTINGS);
}
parser.skipWhitespace();
word = parser.readWord();
}

var cue = shaka.media.TextEngine.makeCue(
segmentStartTime, segmentEndTime, payload);
if (!cue)
return null;

if (id)
cue.id = id;
if (settings) {
var parser = new shaka.util.TextParser(settings);
var word = parser.readWord();
while (word) {
if (!shaka.media.VttTextParser.parseSetting(cue, word)) {
throw new shaka.util.Error(
shaka.util.Error.Category.TEXT,
shaka.util.Error.Code.INVALID_TEXT_SETTINGS);
}
parser.skipWhitespace();
word = parser.readWord();
}
} else {
cue = new TextTrackCue(
Number(segmentStartTime), Number(segmentEndTime), payload);
}

return cue;
Expand Down
35 changes: 31 additions & 4 deletions lib/media/text_engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
goog.provide('shaka.media.TextEngine');

goog.require('goog.asserts');
goog.require('shaka.log');
goog.require('shaka.util.IDestroyable');


Expand Down Expand Up @@ -59,7 +60,9 @@ shaka.media.TextEngine = function(track, mimeType) {
/**
* Parses a text buffer into an array of cues.
*
* @typedef {function(ArrayBuffer, ?number, ?number):!Array.<!TextTrackCue>}
* @typedef {
* function(ArrayBuffer, number, ?number, ?number):!Array.<!TextTrackCue>
* }
* @exportDoc
*/
shaka.media.TextEngine.TextParser;
Expand Down Expand Up @@ -97,6 +100,32 @@ shaka.media.TextEngine.isTypeSupported = function(mimeType) {
};


/**
* Creates a cue using the best platform-specific interface available.
*
* @param {number} startTime
* @param {number} endTime
* @param {string} payload
* @return {TextTrackCue} or null if the parameters were invalid.
* @export
*/
shaka.media.TextEngine.makeCue = function(startTime, endTime, payload) {
if (startTime >= endTime) {
// IE/Edge will throw in this case.
// See issue #501
shaka.log.warning('Invalid cue times: ' + startTime + ' - ' + endTime);
return null;
}

return new shaka.media.TextEngine.CueConstructor_(
startTime, endTime, payload);
};


/** @private {function(new:TextTrackCue, number, number, string)} */
shaka.media.TextEngine.CueConstructor_ = window.VTTCue || window.TextTrackCue;


/** @override */
shaka.media.TextEngine.prototype.destroy = function() {
if (this.track_) {
Expand All @@ -123,16 +152,14 @@ shaka.media.TextEngine.prototype.appendBuffer =
// Start the operation asynchronously to avoid blocking the caller.
return Promise.resolve().then(function() {
// Parse the buffer and add the new cues.
var cues = this.parser_(buffer, startTime, endTime);
var cues = this.parser_(buffer, offset, startTime, endTime);

if (startTime == null || endTime == null) {
// Init segments will not have start/end times passed
return;
}

for (var i = 0; i < cues.length; ++i) {
cues[i].startTime += offset;
cues[i].endTime += offset;
if (cues[i].startTime >= this.appendWindowEnd_) break;
this.track_.addCue(cues[i]);
}
Expand Down
36 changes: 19 additions & 17 deletions lib/media/ttml_text_parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,14 @@ goog.require('shaka.util.StringUtils');
* @namespace
* @summary A TextEngine plugin that parses TTML files.
* @param {ArrayBuffer} data
* @param {number} offset
* @param {?number} segmentStartTime
* @param {?number} segmentEndTime
* @return {!Array.<!TextTrackCue>}
* @throws {shaka.util.Error}
*/
shaka.media.TtmlTextParser = function(data, segmentStartTime, segmentEndTime) {
shaka.media.TtmlTextParser =
function(data, offset, segmentStartTime, segmentEndTime) {
var str = shaka.util.StringUtils.fromUTF8(data);
var ret = [];
var parser = new DOMParser();
Expand Down Expand Up @@ -79,10 +81,9 @@ shaka.media.TtmlTextParser = function(data, segmentStartTime, segmentEndTime) {

for (var i = 0; i < textNodes.length; i++) {
var cue = shaka.media.TtmlTextParser.parseCue_(
textNodes[i], rateInfo, styles, regions);
if (cue) {
textNodes[i], offset, rateInfo, styles, regions);
if (cue)
ret.push(cue);
}
}
}

Expand Down Expand Up @@ -194,14 +195,15 @@ shaka.media.TtmlTextParser.getLeafNodes_ = function(element) {
* Parses an Element into a TextTrackCue or VTTCue.
*
* @param {!Element} cueElement
* @param {number} offset
* @param {!shaka.media.TtmlTextParser.RateInfo_} rateInfo
* @param {!Array.<!Element>} styles
* @param {!Array.<!Element>} regions
* @return {!TextTrackCue}
* @return {TextTrackCue}
* @private
*/
shaka.media.TtmlTextParser.parseCue_ = function(
cueElement, rateInfo, styles, regions) {
cueElement, offset, rateInfo, styles, regions) {

// Get time
var start = shaka.media.TtmlTextParser.parseTime_(
Expand All @@ -221,17 +223,17 @@ shaka.media.TtmlTextParser.parseCue_ = function(
shaka.util.Error.Code.INVALID_TEXT_CUE);
}

var cue;
if (window.VTTCue) {
cue = new VTTCue(start, end, payload);
start += offset;
end += offset;

// Get other properties if available
var region = shaka.media.TtmlTextParser.getElementFromCollection_(
cueElement, 'region', regions);
shaka.media.TtmlTextParser.addStyle_(cue, cueElement, region, styles);
} else {
cue = new TextTrackCue(start, end, payload);
}
var cue = shaka.media.TextEngine.makeCue(start, end, payload);
if (!cue)
return null;

// Get other properties if available
var region = shaka.media.TtmlTextParser.getElementFromCollection_(
cueElement, 'region', regions);
shaka.media.TtmlTextParser.addStyle_(cue, cueElement, region, styles);

return cue;
};
Expand All @@ -240,7 +242,7 @@ shaka.media.TtmlTextParser.parseCue_ = function(
/**
* Adds applicable style properties to a cue.
*
* @param {!VTTCue} cue
* @param {!TextTrackCue} cue
* @param {!Element} cueElement
* @param {Element} region
* @param {!Array.<!Element>} styles
Expand Down
Loading

0 comments on commit a8b012b

Please sign in to comment.