mirror of
https://github.com/shaka-project/shaka-player.git
synced 2026-06-26 17:46:26 +03:00
Convert TextSourceBuffer to TextEngine
This changes the text APIs to correctly handle buffered ranges of segmented text. b/25517444 Related to issue #150 Change-Id: I3a11b87e8d93376a5012566deb3bf0d015f52391
This commit is contained in:
+1
-1
@@ -14,7 +14,7 @@
|
||||
+../../lib/media/segment_index.js
|
||||
+../../lib/media/segment_reference.js
|
||||
+../../lib/media/streaming_engine.js
|
||||
+../../lib/media/text_source_buffer.js
|
||||
+../../lib/media/text_engine.js
|
||||
+../../lib/media/time_ranges_utils.js
|
||||
+../../lib/media/webm_segment_index_parser.js
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Shaka v2.0 Redesign
|
||||
|
||||
last update: 2016-01-15
|
||||
last update: 2016-02-12
|
||||
|
||||
by: [joeyparrish@google.com](mailto:joeyparrish@google.com)
|
||||
|
||||
@@ -10,8 +10,8 @@ by: [joeyparrish@google.com](mailto:joeyparrish@google.com)
|
||||
We are redesigning Shaka Player to reduce overall complexity, increase
|
||||
modularity, and make it easier to introduce new features that would be too
|
||||
messy in Shaka Player v1.x. We posted code to the preview branch on github
|
||||
at the end of November 2015 and *hope* to have a fully-functional public beta
|
||||
release some time in January 2016.
|
||||
at the end of November 2015 and *hope* to have a public beta release by the
|
||||
end of February 2016.
|
||||
|
||||
|
||||
## Background
|
||||
@@ -175,11 +175,10 @@ StreamingEngine will own MediaSourceEngine, and will be responsible for reading
|
||||
an internal representation of a manifest, fetching content, and feeding content
|
||||
to MediaSourceEngine.
|
||||
|
||||
**[Simplicity]** To simplify segmented text and non-native text formats, we will create a
|
||||
work-alike for SourceBuffer called TextSourceBuffer. MediaSourceEngine and the
|
||||
layers above it will not have to know the details of how text is handled, and
|
||||
segmented text can be streamed exactly the same way as segmented audio and
|
||||
video.
|
||||
**[Simplicity]** To simplify segmented text and non-native text formats, we
|
||||
will create TextEngine. MediaSourceEngine and the layers above it will not
|
||||
have to know the details of how text is handled, and segmented text can be
|
||||
streamed exactly the same way as segmented audio and video.
|
||||
|
||||
**[Extensibility]** Text parsers will be plugin-based, allowing new text formats to be supported
|
||||
without modifying the library.
|
||||
@@ -247,7 +246,7 @@ shaka.net.NetworkingEngine.prototype.registerRequestFilter(filterCallback)
|
||||
|
||||
shaka.net.NetworkingEngine.prototype.registerResponseFilter(filterCallback)
|
||||
|
||||
shaka.media.TextSourceBuffer.registerParser(mimeType, parserCallback)
|
||||
shaka.media.TextEngine.registerParser(mimeType, parserCallback)
|
||||
|
||||
shaka.media.ManifestParser.registerParserByMime(mimeType, parser)
|
||||
|
||||
|
||||
@@ -23,9 +23,9 @@ digraph shaka2_data {
|
||||
|
||||
StreamingEngine -> MediaSourceEngine [ label="ArrayBuffer" ]
|
||||
MediaSourceEngine -> SourceBuffer [ label="ArrayBuffer" ]
|
||||
MediaSourceEngine -> TextSourceBuffer [ label="ArrayBuffer" ]
|
||||
TextSourceBuffer -> TextParser [ label="ArrayBuffer => TextCues" dir=both arrowtail=onormal ]
|
||||
TextSourceBuffer -> TextTrack [ label="TextCues" ]
|
||||
MediaSourceEngine -> TextEngine [ label="ArrayBuffer" ]
|
||||
TextEngine -> TextParser [ label="ArrayBuffer => TextCues" dir=both arrowtail=onormal ]
|
||||
TextEngine -> TextTrack [ label="TextCues" ]
|
||||
|
||||
NetworkingEngine -> RequestFilter [ label="Request => Request" dir=both arrowtail=onormal ]
|
||||
NetworkingEngine -> ResponseFilter [ label="Response => Response" dir=both arrowtail=onormal ]
|
||||
|
||||
@@ -30,10 +30,10 @@ digraph shaka2_ownership {
|
||||
MediaSourceEngine -> MediaSource
|
||||
MediaSourceEngine -> SourceBuffer
|
||||
MediaSourceEngine -> SourceBuffer
|
||||
MediaSourceEngine -> TextSourceBuffer
|
||||
MediaSourceEngine -> TextEngine
|
||||
|
||||
TextSourceBuffer -> TextTrack
|
||||
TextSourceBuffer -> TextParser
|
||||
TextEngine -> TextTrack
|
||||
TextEngine -> TextParser
|
||||
|
||||
subgraph cluster_legend {
|
||||
style=rounded
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
goog.provide('shaka.media.MediaSourceEngine');
|
||||
|
||||
goog.require('shaka.asserts');
|
||||
goog.require('shaka.media.TextSourceBuffer');
|
||||
goog.require('shaka.media.TextEngine');
|
||||
goog.require('shaka.media.TimeRangesUtils');
|
||||
goog.require('shaka.util.EventManager');
|
||||
goog.require('shaka.util.IDestroyable');
|
||||
@@ -50,9 +50,12 @@ shaka.media.MediaSourceEngine = function(mediaSource, textTrack) {
|
||||
/** @private {TextTrack} */
|
||||
this.textTrack_ = textTrack;
|
||||
|
||||
/** @private {!Object.<string, !SourceBuffer>} */
|
||||
/** @private {!Object.<string, SourceBuffer>} */
|
||||
this.sourceBuffers_ = {};
|
||||
|
||||
/** @private {shaka.media.TextEngine} */
|
||||
this.textEngine_ = null;
|
||||
|
||||
/**
|
||||
* @private {!Object.<string,
|
||||
* !Array.<shaka.media.MediaSourceEngine.Operation>>}
|
||||
@@ -89,7 +92,7 @@ shaka.media.MediaSourceEngine.Operation;
|
||||
* @return {boolean}
|
||||
*/
|
||||
shaka.media.MediaSourceEngine.isTypeSupported = function(mimeType) {
|
||||
return shaka.media.TextSourceBuffer.isTypeSupported(mimeType) ||
|
||||
return shaka.media.TextEngine.isTypeSupported(mimeType) ||
|
||||
MediaSource.isTypeSupported(mimeType);
|
||||
};
|
||||
|
||||
@@ -169,6 +172,7 @@ shaka.media.MediaSourceEngine.prototype.destroy = function() {
|
||||
this.eventManager_ = null;
|
||||
this.mediaSource_ = null;
|
||||
this.textTrack_ = null;
|
||||
this.textEngine_ = null;
|
||||
this.sourceBuffers_ = {};
|
||||
if (!COMPILED) {
|
||||
for (var contentType in this.queues_) {
|
||||
@@ -201,26 +205,17 @@ shaka.media.MediaSourceEngine.prototype.init = function(typeConfig) {
|
||||
shaka.media.MediaSourceEngine.isTypeSupported(mimeType),
|
||||
'Type negotiation should happen before MediaSourceEngine.init!');
|
||||
|
||||
var sourceBuffer;
|
||||
if (contentType == 'text') {
|
||||
var textSourceBuffer =
|
||||
new shaka.media.TextSourceBuffer(this.textTrack_, mimeType);
|
||||
// This crazy cast is a hack to satisfy the compiler, since
|
||||
// TextSourceBuffer isn't a subclass of SourceBuffer.
|
||||
sourceBuffer = /** @type {!SourceBuffer} */(/** @type {*} */(
|
||||
textSourceBuffer));
|
||||
this.textEngine_ = new shaka.media.TextEngine(this.textTrack_, mimeType);
|
||||
} else {
|
||||
sourceBuffer = /** @type {!SourceBuffer} */(
|
||||
this.mediaSource_.addSourceBuffer(mimeType));
|
||||
var sourceBuffer = this.mediaSource_.addSourceBuffer(mimeType);
|
||||
this.eventManager_.listen(
|
||||
sourceBuffer, 'error', this.onError_.bind(this, contentType));
|
||||
this.eventManager_.listen(
|
||||
sourceBuffer, 'updateend', this.onUpdateEnd_.bind(this, contentType));
|
||||
this.sourceBuffers_[contentType] = sourceBuffer;
|
||||
this.queues_[contentType] = [];
|
||||
}
|
||||
|
||||
this.eventManager_.listen(
|
||||
sourceBuffer, 'error', this.onError_.bind(this, contentType));
|
||||
this.eventManager_.listen(
|
||||
sourceBuffer, 'updateend', this.onUpdateEnd_.bind(this, contentType));
|
||||
|
||||
this.sourceBuffers_[contentType] = sourceBuffer;
|
||||
this.queues_[contentType] = [];
|
||||
}
|
||||
};
|
||||
|
||||
@@ -232,6 +227,9 @@ shaka.media.MediaSourceEngine.prototype.init = function(typeConfig) {
|
||||
* @return {?number} The timestamp in seconds, or null if nothing is buffered.
|
||||
*/
|
||||
shaka.media.MediaSourceEngine.prototype.bufferStart = function(contentType) {
|
||||
if (contentType == 'text') {
|
||||
return this.textEngine_.bufferStart();
|
||||
}
|
||||
return shaka.media.TimeRangesUtils.bufferStart(
|
||||
this.sourceBuffers_[contentType].buffered);
|
||||
};
|
||||
@@ -244,6 +242,9 @@ shaka.media.MediaSourceEngine.prototype.bufferStart = function(contentType) {
|
||||
* @return {?number} The timestamp in seconds, or null if nothing is buffered.
|
||||
*/
|
||||
shaka.media.MediaSourceEngine.prototype.bufferEnd = function(contentType) {
|
||||
if (contentType == 'text') {
|
||||
return this.textEngine_.bufferEnd();
|
||||
}
|
||||
return shaka.media.TimeRangesUtils.bufferEnd(
|
||||
this.sourceBuffers_[contentType].buffered);
|
||||
};
|
||||
@@ -259,6 +260,9 @@ shaka.media.MediaSourceEngine.prototype.bufferEnd = function(contentType) {
|
||||
*/
|
||||
shaka.media.MediaSourceEngine.prototype.bufferedAheadOf =
|
||||
function(contentType, time) {
|
||||
if (contentType == 'text') {
|
||||
return this.textEngine_.bufferedAheadOf(time);
|
||||
}
|
||||
return shaka.media.TimeRangesUtils.bufferedAheadOf(
|
||||
this.sourceBuffers_[contentType].buffered, time);
|
||||
};
|
||||
@@ -266,13 +270,23 @@ shaka.media.MediaSourceEngine.prototype.bufferedAheadOf =
|
||||
|
||||
/**
|
||||
* Enqueue an operation to append data to the SourceBuffer.
|
||||
* Start and end times are needed for TextEngine, but not for MediaSource.
|
||||
* Start and end times may be null for initialization segments.
|
||||
*
|
||||
* @param {string} contentType
|
||||
* @param {!ArrayBuffer|!ArrayBufferView} data
|
||||
* @param {?number} startTime
|
||||
* @param {?number} endTime
|
||||
* @return {!Promise}
|
||||
*/
|
||||
shaka.media.MediaSourceEngine.prototype.appendBuffer =
|
||||
function(contentType, data) {
|
||||
function(contentType, data, startTime, endTime) {
|
||||
if (contentType == 'text') {
|
||||
shaka.asserts.assert(startTime != null && endTime != null,
|
||||
'text streams do not have init segments!');
|
||||
return this.textEngine_.appendBuffer(
|
||||
data, /** @type {number} */(startTime), /** @type {number} */(endTime));
|
||||
}
|
||||
return this.enqueueOperation_(
|
||||
contentType,
|
||||
this.append_.bind(this, contentType, data));
|
||||
@@ -293,6 +307,9 @@ shaka.media.MediaSourceEngine.prototype.remove =
|
||||
// See https://github.com/google/shaka-player/issues/251
|
||||
shaka.asserts.assert(endTime < Number.MAX_VALUE,
|
||||
'remove() with MAX_VALUE or POSITIVE_INFINITY is not IE-compatible!');
|
||||
if (contentType == 'text') {
|
||||
return this.textEngine_.remove(startTime, endTime);
|
||||
}
|
||||
return this.enqueueOperation_(
|
||||
contentType,
|
||||
this.remove_.bind(this, contentType, startTime, endTime));
|
||||
@@ -306,6 +323,9 @@ shaka.media.MediaSourceEngine.prototype.remove =
|
||||
* @return {!Promise}
|
||||
*/
|
||||
shaka.media.MediaSourceEngine.prototype.clear = function(contentType) {
|
||||
if (contentType == 'text') {
|
||||
return this.textEngine_.remove(0, Number.POSITIVE_INFINITY);
|
||||
}
|
||||
// Note that not all platforms allow clearing to Number.POSITIVE_INFINITY.
|
||||
return this.enqueueOperation_(
|
||||
contentType,
|
||||
@@ -324,6 +344,10 @@ shaka.media.MediaSourceEngine.prototype.clear = function(contentType) {
|
||||
*/
|
||||
shaka.media.MediaSourceEngine.prototype.setTimestampOffset = function(
|
||||
contentType, timestampOffset) {
|
||||
if (contentType == 'text') {
|
||||
this.textEngine_.setTimestampOffset(timestampOffset);
|
||||
return Promise.resolve();
|
||||
}
|
||||
return this.enqueueOperation_(
|
||||
contentType,
|
||||
this.setTimestampOffset_.bind(this, contentType, timestampOffset));
|
||||
@@ -340,6 +364,10 @@ shaka.media.MediaSourceEngine.prototype.setTimestampOffset = function(
|
||||
*/
|
||||
shaka.media.MediaSourceEngine.prototype.setAppendWindowEnd = function(
|
||||
contentType, appendWindowEnd) {
|
||||
if (contentType == 'text') {
|
||||
this.textEngine_.setAppendWindowEnd(appendWindowEnd);
|
||||
return Promise.resolve();
|
||||
}
|
||||
return this.enqueueOperation_(
|
||||
contentType,
|
||||
this.setAppendWindowEnd_.bind(this, contentType, appendWindowEnd));
|
||||
|
||||
@@ -1064,7 +1064,8 @@ shaka.media.StreamingEngine.prototype.initSourceBuffer_ = function(
|
||||
if (this.destroyed_) return;
|
||||
shaka.log.v1(logPrefix, 'appending init segment');
|
||||
|
||||
return this.mediaSourceEngine_.appendBuffer(mediaState.type, initSegment);
|
||||
return this.mediaSourceEngine_.appendBuffer(
|
||||
mediaState.type, initSegment, null /* startTime */, null /* endTime */);
|
||||
}.bind(this));
|
||||
|
||||
return Promise.all([setTimestampOffset, setAppendWindowEnd, appendInit]);
|
||||
@@ -1115,7 +1116,8 @@ shaka.media.StreamingEngine.prototype.append_ = function(
|
||||
if (this.destroyed_) return;
|
||||
shaka.log.v1(logPrefix, 'appending media segment');
|
||||
|
||||
return this.mediaSourceEngine_.appendBuffer(mediaState.type, segment);
|
||||
return this.mediaSourceEngine_.appendBuffer(
|
||||
mediaState.type, segment, reference.startTime, reference.endTime);
|
||||
}.bind(this)).then(function() {
|
||||
if (this.destroyed_) return;
|
||||
shaka.log.v1(logPrefix, 'appended media segment');
|
||||
|
||||
@@ -0,0 +1,247 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2015 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
goog.provide('shaka.media.TextEngine');
|
||||
|
||||
goog.require('shaka.asserts');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Manages text parsers and cues.
|
||||
*
|
||||
* @struct
|
||||
* @constructor
|
||||
* @param {TextTrack} track
|
||||
* @param {string} mimeType
|
||||
*/
|
||||
shaka.media.TextEngine = function(track, mimeType) {
|
||||
/** @private {shaka.media.TextEngine.TextParser} */
|
||||
this.parser_ = shaka.media.TextEngine.parserMap_[mimeType];
|
||||
|
||||
// A more accurate work-alike would throw NotSupportedError here, but this
|
||||
// should not happen if type-negotiation is working as it should.
|
||||
shaka.asserts.assert(this.parser_,
|
||||
'Text type negotiation should have happened already');
|
||||
|
||||
/** @private {TextTrack} */
|
||||
this.track_ = track;
|
||||
|
||||
/** @private {number} */
|
||||
this.timestampOffset_ = 0;
|
||||
|
||||
/** @private {number} */
|
||||
this.appendWindowEnd_ = Number.POSITIVE_INFINITY;
|
||||
|
||||
/** @private {?number} */
|
||||
this.bufferStart_ = null;
|
||||
|
||||
/** @private {?number} */
|
||||
this.bufferEnd_ = null;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Parses a text buffer into an array of cues.
|
||||
*
|
||||
* @typedef {function((ArrayBuffer|ArrayBufferView)):!Array.<!TextTrackCue>}
|
||||
* @exportDoc
|
||||
*/
|
||||
shaka.media.TextEngine.TextParser;
|
||||
|
||||
|
||||
/** @private {!Object.<string, !shaka.media.TextEngine.TextParser>} */
|
||||
shaka.media.TextEngine.parserMap_ = {};
|
||||
|
||||
|
||||
/**
|
||||
* @param {string} mimeType
|
||||
* @param {!shaka.media.TextEngine.TextParser} parser
|
||||
* @export
|
||||
*/
|
||||
shaka.media.TextEngine.registerParser = function(mimeType, parser) {
|
||||
shaka.media.TextEngine.parserMap_[mimeType] = parser;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {string} mimeType
|
||||
* @export
|
||||
*/
|
||||
shaka.media.TextEngine.unregisterParser = function(mimeType) {
|
||||
delete shaka.media.TextEngine.parserMap_[mimeType];
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {string} mimeType
|
||||
* @return {boolean}
|
||||
*/
|
||||
shaka.media.TextEngine.isTypeSupported = function(mimeType) {
|
||||
return !!shaka.media.TextEngine.parserMap_[mimeType];
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {ArrayBuffer|ArrayBufferView} buffer
|
||||
* @param {number} startTime
|
||||
* @param {number} endTime
|
||||
* @return {!Promise}
|
||||
*/
|
||||
shaka.media.TextEngine.prototype.appendBuffer =
|
||||
function(buffer, startTime, endTime) {
|
||||
var offset = this.timestampOffset_;
|
||||
|
||||
startTime += offset;
|
||||
endTime += offset;
|
||||
|
||||
// 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);
|
||||
|
||||
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]);
|
||||
}
|
||||
|
||||
// NOTE: We update the buffered range from the start and end times passed
|
||||
// down from the segment reference, not with the start and end times of the
|
||||
// parsed cues. This is important because some segments may contain no
|
||||
// cues, but we must still consider those ranges buffered.
|
||||
if (this.bufferStart_ == null) {
|
||||
this.bufferStart_ = startTime;
|
||||
} else {
|
||||
// We already had something in buffer, and we assume we are extending the
|
||||
// range from the end.
|
||||
shaka.asserts.assert((startTime - this.bufferEnd_) <= 1,
|
||||
'There should not be a gap in text references >1s');
|
||||
}
|
||||
this.bufferEnd_ = Math.min(endTime, this.appendWindowEnd_);
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {number} start
|
||||
* @param {number} end
|
||||
* @return {!Promise}
|
||||
*/
|
||||
shaka.media.TextEngine.prototype.remove = function(start, end) {
|
||||
// Start the operation asynchronously to avoid blocking the caller.
|
||||
return Promise.resolve().then(function() {
|
||||
var cues = this.track_.cues;
|
||||
var removeMe = [];
|
||||
|
||||
for (var i = 0; i < cues.length; ++i) {
|
||||
if (cues[i].startTime >= end || cues[i].endTime <= start) {
|
||||
// Outside the remove range. Hang on to it.
|
||||
} else {
|
||||
// Remove these in another loop to avoid mutating the TextTrackCueList
|
||||
// while iterating over it. This allows us to avoid making assumptions
|
||||
// about whether or not this.track_.remove() will alter that list.
|
||||
removeMe.push(cues[i]);
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < removeMe.length; ++i) {
|
||||
this.track_.removeCue(removeMe[i]);
|
||||
}
|
||||
|
||||
if (this.bufferStart_ == null) {
|
||||
shaka.asserts.assert(this.bufferEnd_ == null,
|
||||
'end must be null if start is null');
|
||||
shaka.asserts.assert(removeMe.length == 0,
|
||||
'buffer empty, should have removed nothing');
|
||||
} else {
|
||||
shaka.asserts.assert(this.bufferEnd_ != null,
|
||||
'end must be non-null if start is non-null');
|
||||
|
||||
// Update buffered range.
|
||||
if (end <= this.bufferStart_ || start >= this.bufferEnd_) {
|
||||
// No intersection. Nothing was removed.
|
||||
shaka.asserts.assert(removeMe.length == 0,
|
||||
'no intersection, should have removed nothing');
|
||||
} else if (start <= this.bufferStart_ && end >= this.bufferEnd_) {
|
||||
// We wiped out everything.
|
||||
shaka.asserts.assert(this.track_.cues.length == 0,
|
||||
'should be no cues left');
|
||||
this.bufferStart_ = this.bufferEnd_ = null;
|
||||
} else if (start <= this.bufferStart_ && end < this.bufferEnd_) {
|
||||
// We removed from the beginning of the range.
|
||||
this.bufferStart_ = end;
|
||||
} else if (start > this.bufferStart_ && end >= this.bufferEnd_) {
|
||||
// We removed from the end of the range.
|
||||
this.bufferEnd_ = start;
|
||||
} else {
|
||||
// We removed from the middle? StreamingEngine isn't supposed to.
|
||||
shaka.asserts.assert(
|
||||
false, 'removal from the middle is not supported by TextEngine');
|
||||
}
|
||||
}
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
|
||||
/** @param {number} timestampOffset */
|
||||
shaka.media.TextEngine.prototype.setTimestampOffset =
|
||||
function(timestampOffset) {
|
||||
this.timestampOffset_ = timestampOffset;
|
||||
};
|
||||
|
||||
|
||||
/** @param {number} windowEnd */
|
||||
shaka.media.TextEngine.prototype.setAppendWindowEnd =
|
||||
function(windowEnd) {
|
||||
this.appendWindowEnd_ = windowEnd;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {?number} Time in seconds of the beginning of the buffered range,
|
||||
* or null if nothing is buffered.
|
||||
*/
|
||||
shaka.media.TextEngine.prototype.bufferStart = function() {
|
||||
return this.bufferStart_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {?number} Time in seconds of the end of the buffered range,
|
||||
* or null if nothing is buffered.
|
||||
*/
|
||||
shaka.media.TextEngine.prototype.bufferEnd = function() {
|
||||
return this.bufferEnd_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {number} t A timestamp
|
||||
* @return {number} Number of seconds ahead of 't' we have buffered
|
||||
*/
|
||||
shaka.media.TextEngine.prototype.bufferedAheadOf = function(t) {
|
||||
if (this.bufferEnd_ == null || this.bufferEnd_ < t) return 0;
|
||||
|
||||
shaka.asserts.assert(this.bufferStart_ != null,
|
||||
'start should not be null if end is not null');
|
||||
|
||||
if (t < this.bufferStart_) return 0;
|
||||
|
||||
return this.bufferEnd_ - t;
|
||||
};
|
||||
@@ -1,203 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2015 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
goog.provide('shaka.media.TextSourceBuffer');
|
||||
|
||||
goog.require('shaka.asserts');
|
||||
goog.require('shaka.util.FakeEventTarget');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A SourceBuffer work-alike for text types.
|
||||
*
|
||||
* @struct
|
||||
* @constructor
|
||||
* @param {TextTrack} track
|
||||
* @param {string} mimeType
|
||||
* @extends {shaka.util.FakeEventTarget}
|
||||
* @see http://w3c.github.io/media-source/#sourcebuffer
|
||||
*/
|
||||
shaka.media.TextSourceBuffer = function(track, mimeType) {
|
||||
shaka.util.FakeEventTarget.call(this, null);
|
||||
|
||||
/** @private {shaka.media.TextSourceBuffer.TextParser} */
|
||||
this.parser_ = shaka.media.TextSourceBuffer.parserMap_[mimeType];
|
||||
|
||||
// A more accurate work-alike would throw NotSupportedError here, but this
|
||||
// should not happen if type-negotiation is working as it should.
|
||||
shaka.asserts.assert(this.parser_,
|
||||
'Text type negotiation should have happened already');
|
||||
|
||||
/** @private {TextTrack} */
|
||||
this.track_ = track;
|
||||
|
||||
/** @type {boolean} */
|
||||
this.updating = false;
|
||||
|
||||
/**
|
||||
* A work-alike for TimeRanges.
|
||||
* @type {{ length: number,
|
||||
* start: function(number): number,
|
||||
* end: function(number): number }}
|
||||
*/
|
||||
this.buffered = {
|
||||
'length': 0,
|
||||
'start': this.bufferStart_.bind(this),
|
||||
'end': this.bufferEnd_.bind(this)
|
||||
};
|
||||
|
||||
/**
|
||||
* Ignored.
|
||||
* @type {number}
|
||||
*/
|
||||
this.timestampOffset = 0;
|
||||
};
|
||||
goog.inherits(shaka.media.TextSourceBuffer, shaka.util.FakeEventTarget);
|
||||
|
||||
|
||||
/**
|
||||
* Parses a text buffer into an array of cues.
|
||||
*
|
||||
* @typedef {function((ArrayBuffer|ArrayBufferView)):!Array.<!TextTrackCue>}
|
||||
* @exportDoc
|
||||
*/
|
||||
shaka.media.TextSourceBuffer.TextParser;
|
||||
|
||||
|
||||
/** @private {!Object.<string, !shaka.media.TextSourceBuffer.TextParser>} */
|
||||
shaka.media.TextSourceBuffer.parserMap_ = {};
|
||||
|
||||
|
||||
/**
|
||||
* @param {string} mimeType
|
||||
* @param {!shaka.media.TextSourceBuffer.TextParser} parser
|
||||
* @export
|
||||
*/
|
||||
shaka.media.TextSourceBuffer.registerParser = function(mimeType, parser) {
|
||||
shaka.media.TextSourceBuffer.parserMap_[mimeType] = parser;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {string} mimeType
|
||||
* @export
|
||||
*/
|
||||
shaka.media.TextSourceBuffer.unregisterParser = function(mimeType) {
|
||||
delete shaka.media.TextSourceBuffer.parserMap_[mimeType];
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {string} mimeType
|
||||
* @return {boolean}
|
||||
*/
|
||||
shaka.media.TextSourceBuffer.isTypeSupported = function(mimeType) {
|
||||
return !!shaka.media.TextSourceBuffer.parserMap_[mimeType];
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {ArrayBuffer|ArrayBufferView} buffer
|
||||
*/
|
||||
shaka.media.TextSourceBuffer.prototype.appendBuffer = function(buffer) {
|
||||
shaka.asserts.assert(this.updating == false,
|
||||
'Text appendBuffer called while updating!');
|
||||
this.updating = true;
|
||||
|
||||
// Start the operation asynchronously to avoid blocking the caller.
|
||||
Promise.resolve().then(function() {
|
||||
// Parse the buffer and add the new cues.
|
||||
var cues = this.parser_(buffer);
|
||||
|
||||
for (var i = 0; i < cues.length; ++i) {
|
||||
this.track_.addCue(cues[i]);
|
||||
}
|
||||
|
||||
this.update_();
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {number} start
|
||||
* @param {number} end
|
||||
*/
|
||||
shaka.media.TextSourceBuffer.prototype.remove = function(start, end) {
|
||||
shaka.asserts.assert(this.updating == false,
|
||||
'Text remove called while updating!');
|
||||
this.updating = true;
|
||||
|
||||
// Start the operation asynchronously to avoid blocking the caller.
|
||||
Promise.resolve().then(function() {
|
||||
var cues = this.track_.cues;
|
||||
var removeMe = [];
|
||||
|
||||
for (var i = 0; i < cues.length; ++i) {
|
||||
if (cues[i].startTime > end || cues[i].endTime < start) {
|
||||
// Outside the remove range. Hang on to it.
|
||||
} else {
|
||||
// Remove these in another loop to avoid mutating the TextTrackCueList
|
||||
// while iterating over it. This allows us to avoid making assumptions
|
||||
// about whether or not this.track_.remove() will alter that list.
|
||||
removeMe.push(cues[i]);
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < removeMe.length; ++i) {
|
||||
this.track_.removeCue(removeMe[i]);
|
||||
}
|
||||
|
||||
this.update_();
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Perform actions common to all updates (appendBuffer, remove).
|
||||
* @private
|
||||
*/
|
||||
shaka.media.TextSourceBuffer.prototype.update_ = function() {
|
||||
this.buffered.length = this.track_.cues.length ? 1 : 0;
|
||||
this.updating = false;
|
||||
var updateEnd = shaka.util.FakeEvent.create({'type': 'updateend'});
|
||||
this.dispatchEvent(updateEnd);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {number} index
|
||||
* @return {number}
|
||||
* @private
|
||||
*/
|
||||
shaka.media.TextSourceBuffer.prototype.bufferStart_ = function(index) {
|
||||
shaka.asserts.assert(index == 0 && this.buffered.length == 1,
|
||||
'Only one text buffered range allowed!');
|
||||
return this.track_.cues[0].startTime;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {number} index
|
||||
* @return {number}
|
||||
* @private
|
||||
*/
|
||||
shaka.media.TextSourceBuffer.prototype.bufferEnd_ = function(index) {
|
||||
shaka.asserts.assert(index == 0 && this.buffered.length == 1,
|
||||
'Only one text buffered range allowed!');
|
||||
return this.track_.cues[this.track_.cues.length - 1].endTime;
|
||||
};
|
||||
@@ -17,14 +17,14 @@
|
||||
|
||||
goog.provide('shaka.media.VttTextParser');
|
||||
|
||||
goog.require('shaka.media.TextSourceBuffer');
|
||||
goog.require('shaka.media.TextEngine');
|
||||
goog.require('shaka.util.Error');
|
||||
goog.require('shaka.util.TextParser');
|
||||
goog.require('shaka.util.Uint8ArrayUtils');
|
||||
|
||||
|
||||
/**
|
||||
* A TextSourceBuffer plugin that parses WebVTT files.
|
||||
* A TextEngine plugin that parses WebVTT files.
|
||||
*
|
||||
* @param {ArrayBuffer|ArrayBufferView} data
|
||||
* @return {!Array.<!TextTrackCue>}
|
||||
@@ -169,5 +169,4 @@ shaka.media.VttTextParser.parseTime_ = function(parser) {
|
||||
return (miliseconds / 1000) + seconds + (minutes * 60) + (hours * 3600);
|
||||
};
|
||||
|
||||
shaka.media.TextSourceBuffer.registerParser(
|
||||
'text/vtt', shaka.media.VttTextParser);
|
||||
shaka.media.TextEngine.registerParser('text/vtt', shaka.media.VttTextParser);
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
goog.require('shaka.Player');
|
||||
goog.require('shaka.media.ManifestParser');
|
||||
goog.require('shaka.media.PresentationTimeline');
|
||||
goog.require('shaka.media.TextSourceBuffer');
|
||||
goog.require('shaka.media.TextEngine');
|
||||
goog.require('shaka.polyfill.installAll');
|
||||
goog.require('shaka.util.Error');
|
||||
goog.require('shaka.util.StringUtils');
|
||||
|
||||
@@ -37,6 +37,7 @@ describe('MediaSourceEngine', function() {
|
||||
mimeType: 'audio/mp4; codecs="mp4a.40.2"',
|
||||
generator: null
|
||||
}
|
||||
// TODO: add text streams to MSE integration tests
|
||||
};
|
||||
var presentationDuration = 840;
|
||||
|
||||
@@ -95,13 +96,13 @@ describe('MediaSourceEngine', function() {
|
||||
|
||||
function appendInit(type) {
|
||||
var segment = metadata[type].generator.getInitSegment(Date.now() / 1000);
|
||||
return mediaSourceEngine.appendBuffer(type, segment);
|
||||
return mediaSourceEngine.appendBuffer(type, segment, null, null);
|
||||
}
|
||||
|
||||
function append(type, segmentNumber) {
|
||||
var segment = metadata[type].generator.
|
||||
getSegment(segmentNumber, Date.now() / 1000);
|
||||
return mediaSourceEngine.appendBuffer(type, segment);
|
||||
return mediaSourceEngine.appendBuffer(type, segment, null, null);
|
||||
}
|
||||
|
||||
function buffered(type, time) {
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
describe('MediaSourceEngine', function() {
|
||||
var originalIsTypeSupported;
|
||||
var originalTextSourceBuffer;
|
||||
var originalTextEngine;
|
||||
var audioSourceBuffer;
|
||||
var videoSourceBuffer;
|
||||
var mockMediaSource;
|
||||
@@ -36,13 +36,13 @@ describe('MediaSourceEngine', function() {
|
||||
return contentType == 'video' || contentType == 'audio';
|
||||
};
|
||||
|
||||
originalTextSourceBuffer = shaka.media.TextSourceBuffer;
|
||||
shaka.media.TextSourceBuffer = createMockTextSourceBufferCtor();
|
||||
originalTextEngine = shaka.media.TextEngine;
|
||||
shaka.media.TextEngine = createMockTextEngineCtor();
|
||||
});
|
||||
|
||||
afterAll(function() {
|
||||
window.MediaSource.prototype.isTypeSupported = originalIsTypeSupported;
|
||||
shaka.media.TextSourceBuffer = originalTextSourceBuffer;
|
||||
shaka.media.TextEngine = originalTextEngine;
|
||||
});
|
||||
|
||||
beforeEach(function() {
|
||||
@@ -63,13 +63,13 @@ describe('MediaSourceEngine', function() {
|
||||
mediaSourceEngine.init({'audio': 'audio/foo', 'video': 'video/foo'});
|
||||
expect(mockMediaSource.addSourceBuffer).toHaveBeenCalledWith('audio/foo');
|
||||
expect(mockMediaSource.addSourceBuffer).toHaveBeenCalledWith('video/foo');
|
||||
expect(shaka.media.TextSourceBuffer).not.toHaveBeenCalled();
|
||||
expect(shaka.media.TextEngine).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('creates TextSourceBuffers for text types', function() {
|
||||
it('creates TextEngines for text types', function() {
|
||||
mediaSourceEngine.init({'text': 'text/foo'});
|
||||
expect(mockMediaSource.addSourceBuffer).not.toHaveBeenCalled();
|
||||
expect(shaka.media.TextSourceBuffer).toHaveBeenCalled();
|
||||
expect(shaka.media.TextEngine).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -169,7 +169,7 @@ describe('MediaSourceEngine', function() {
|
||||
});
|
||||
|
||||
it('appends the given data', function(done) {
|
||||
mediaSourceEngine.appendBuffer('audio', 1).then(function() {
|
||||
mediaSourceEngine.appendBuffer('audio', 1, null, null).then(function() {
|
||||
expect(audioSourceBuffer.appendBuffer).toHaveBeenCalledWith(1);
|
||||
done();
|
||||
});
|
||||
@@ -178,7 +178,7 @@ describe('MediaSourceEngine', function() {
|
||||
|
||||
it('rejects the promise if this operation throws', function(done) {
|
||||
audioSourceBuffer.appendBuffer.and.throwError(new Error());
|
||||
mediaSourceEngine.appendBuffer('audio', 1).then(function() {
|
||||
mediaSourceEngine.appendBuffer('audio', 1, null, null).then(function() {
|
||||
fail('not reached');
|
||||
done();
|
||||
}, function() {
|
||||
@@ -188,7 +188,7 @@ describe('MediaSourceEngine', function() {
|
||||
});
|
||||
|
||||
it('rejects the promise if this operation fails async', function(done) {
|
||||
mediaSourceEngine.appendBuffer('audio', 1).then(function() {
|
||||
mediaSourceEngine.appendBuffer('audio', 1, null, null).then(function() {
|
||||
fail('not reached');
|
||||
done();
|
||||
}, function() {
|
||||
@@ -200,8 +200,8 @@ describe('MediaSourceEngine', function() {
|
||||
});
|
||||
|
||||
it('queues operations on a single SourceBuffer', function(done) {
|
||||
var p1 = mediaSourceEngine.appendBuffer('audio', 1);
|
||||
var p2 = mediaSourceEngine.appendBuffer('audio', 2);
|
||||
var p1 = mediaSourceEngine.appendBuffer('audio', 1, null, null);
|
||||
var p2 = mediaSourceEngine.appendBuffer('audio', 2, null, null);
|
||||
Util.capturePromiseStatus(p1);
|
||||
Util.capturePromiseStatus(p2);
|
||||
|
||||
@@ -225,9 +225,9 @@ describe('MediaSourceEngine', function() {
|
||||
});
|
||||
|
||||
it('queues operations independently for different types', function(done) {
|
||||
var p1 = mediaSourceEngine.appendBuffer('audio', 1);
|
||||
var p2 = mediaSourceEngine.appendBuffer('audio', 2);
|
||||
var p3 = mediaSourceEngine.appendBuffer('video', 3);
|
||||
var p1 = mediaSourceEngine.appendBuffer('audio', 1, null, null);
|
||||
var p2 = mediaSourceEngine.appendBuffer('audio', 2, null, null);
|
||||
var p3 = mediaSourceEngine.appendBuffer('video', 3, null, null);
|
||||
Util.capturePromiseStatus(p1);
|
||||
Util.capturePromiseStatus(p2);
|
||||
Util.capturePromiseStatus(p3);
|
||||
@@ -268,9 +268,9 @@ describe('MediaSourceEngine', function() {
|
||||
}
|
||||
});
|
||||
|
||||
var p1 = mediaSourceEngine.appendBuffer('audio', 1);
|
||||
var p2 = mediaSourceEngine.appendBuffer('audio', 2);
|
||||
var p3 = mediaSourceEngine.appendBuffer('audio', 3);
|
||||
var p1 = mediaSourceEngine.appendBuffer('audio', 1, null, null);
|
||||
var p2 = mediaSourceEngine.appendBuffer('audio', 2, null, null);
|
||||
var p3 = mediaSourceEngine.appendBuffer('audio', 3, null, null);
|
||||
Util.capturePromiseStatus(p1);
|
||||
Util.capturePromiseStatus(p2);
|
||||
Util.capturePromiseStatus(p3);
|
||||
@@ -467,8 +467,8 @@ describe('MediaSourceEngine', function() {
|
||||
});
|
||||
|
||||
it('waits for all previous operations to complete', function(done) {
|
||||
var p1 = mediaSourceEngine.appendBuffer('audio', 1);
|
||||
var p2 = mediaSourceEngine.appendBuffer('video', 1);
|
||||
var p1 = mediaSourceEngine.appendBuffer('audio', 1, null, null);
|
||||
var p2 = mediaSourceEngine.appendBuffer('video', 1, null, null);
|
||||
var p3 = mediaSourceEngine.endOfStream();
|
||||
Util.capturePromiseStatus(p1);
|
||||
Util.capturePromiseStatus(p2);
|
||||
@@ -498,9 +498,9 @@ describe('MediaSourceEngine', function() {
|
||||
|
||||
it('makes subsequent operations wait', function(done) {
|
||||
var p1 = mediaSourceEngine.endOfStream();
|
||||
var p2 = mediaSourceEngine.appendBuffer('audio', 1);
|
||||
var p3 = mediaSourceEngine.appendBuffer('video', 1);
|
||||
var p4 = mediaSourceEngine.appendBuffer('video', 2);
|
||||
var p2 = mediaSourceEngine.appendBuffer('audio', 1, null, null);
|
||||
var p3 = mediaSourceEngine.appendBuffer('video', 1, null, null);
|
||||
var p4 = mediaSourceEngine.appendBuffer('video', 2, null, null);
|
||||
|
||||
// endOfStream hasn't been called yet because blocking multiple queues
|
||||
// takes an extra tick, even when they are empty.
|
||||
@@ -529,7 +529,7 @@ describe('MediaSourceEngine', function() {
|
||||
it('runs subsequent operations if this operation throws', function(done) {
|
||||
mockMediaSource.endOfStream.and.throwError(new Error());
|
||||
var p1 = mediaSourceEngine.endOfStream();
|
||||
var p2 = mediaSourceEngine.appendBuffer('audio', 1);
|
||||
var p2 = mediaSourceEngine.appendBuffer('audio', 1, null, null);
|
||||
|
||||
expect(audioSourceBuffer.appendBuffer).not.toHaveBeenCalled();
|
||||
|
||||
@@ -564,8 +564,8 @@ describe('MediaSourceEngine', function() {
|
||||
});
|
||||
|
||||
it('waits for all previous operations to complete', function(done) {
|
||||
var p1 = mediaSourceEngine.appendBuffer('audio', 1);
|
||||
var p2 = mediaSourceEngine.appendBuffer('video', 1);
|
||||
var p1 = mediaSourceEngine.appendBuffer('audio', 1, null, null);
|
||||
var p2 = mediaSourceEngine.appendBuffer('video', 1, null, null);
|
||||
var p3 = mediaSourceEngine.setDuration(100);
|
||||
Util.capturePromiseStatus(p1);
|
||||
Util.capturePromiseStatus(p2);
|
||||
@@ -595,9 +595,9 @@ describe('MediaSourceEngine', function() {
|
||||
|
||||
it('makes subsequent operations wait', function(done) {
|
||||
var p1 = mediaSourceEngine.setDuration(100);
|
||||
var p2 = mediaSourceEngine.appendBuffer('audio', 1);
|
||||
var p3 = mediaSourceEngine.appendBuffer('video', 1);
|
||||
var p4 = mediaSourceEngine.appendBuffer('video', 2);
|
||||
var p2 = mediaSourceEngine.appendBuffer('audio', 1, null, null);
|
||||
var p3 = mediaSourceEngine.appendBuffer('video', 1, null, null);
|
||||
var p4 = mediaSourceEngine.appendBuffer('video', 2, null, null);
|
||||
|
||||
// The setter hasn't been called yet because blocking multiple queues
|
||||
// takes an extra tick, even when they are empty.
|
||||
@@ -626,7 +626,7 @@ describe('MediaSourceEngine', function() {
|
||||
it('runs subsequent operations if this operation throws', function(done) {
|
||||
mockMediaSource.durationSetter_.and.throwError(new Error());
|
||||
var p1 = mediaSourceEngine.setDuration(100);
|
||||
var p2 = mediaSourceEngine.appendBuffer('audio', 1);
|
||||
var p2 = mediaSourceEngine.appendBuffer('audio', 1, null, null);
|
||||
|
||||
expect(audioSourceBuffer.appendBuffer).not.toHaveBeenCalled();
|
||||
|
||||
@@ -653,8 +653,8 @@ describe('MediaSourceEngine', function() {
|
||||
});
|
||||
|
||||
it('waits for all operations to complete', function(done) {
|
||||
mediaSourceEngine.appendBuffer('audio', 1);
|
||||
mediaSourceEngine.appendBuffer('video', 1);
|
||||
mediaSourceEngine.appendBuffer('audio', 1, null, null);
|
||||
mediaSourceEngine.appendBuffer('video', 1, null, null);
|
||||
|
||||
var p = mediaSourceEngine.destroy();
|
||||
Util.capturePromiseStatus(p);
|
||||
@@ -675,7 +675,7 @@ describe('MediaSourceEngine', function() {
|
||||
});
|
||||
|
||||
it('resolves even when a pending operation fails', function(done) {
|
||||
var p1 = mediaSourceEngine.appendBuffer('audio', 1);
|
||||
var p1 = mediaSourceEngine.appendBuffer('audio', 1, null, null);
|
||||
var p2 = mediaSourceEngine.destroy();
|
||||
Util.capturePromiseStatus(p1);
|
||||
Util.capturePromiseStatus(p2);
|
||||
@@ -707,8 +707,8 @@ describe('MediaSourceEngine', function() {
|
||||
});
|
||||
|
||||
it('cancels operations that have not yet started', function(done) {
|
||||
mediaSourceEngine.appendBuffer('audio', 1);
|
||||
var rejected = mediaSourceEngine.appendBuffer('audio', 2);
|
||||
mediaSourceEngine.appendBuffer('audio', 1, null, null);
|
||||
var rejected = mediaSourceEngine.appendBuffer('audio', 2, null, null);
|
||||
Util.capturePromiseStatus(rejected);
|
||||
|
||||
expect(audioSourceBuffer.appendBuffer).toHaveBeenCalledWith(1);
|
||||
@@ -733,7 +733,7 @@ describe('MediaSourceEngine', function() {
|
||||
});
|
||||
|
||||
it('cancels blocking operations that have not yet started', function(done) {
|
||||
var p1 = mediaSourceEngine.appendBuffer('audio', 1);
|
||||
var p1 = mediaSourceEngine.appendBuffer('audio', 1, null, null);
|
||||
var p2 = mediaSourceEngine.endOfStream();
|
||||
var p3 = mediaSourceEngine.destroy();
|
||||
Util.capturePromiseStatus(p1);
|
||||
@@ -756,7 +756,7 @@ describe('MediaSourceEngine', function() {
|
||||
|
||||
it('prevents new operations from being added', function(done) {
|
||||
var p = mediaSourceEngine.destroy();
|
||||
var rejected = mediaSourceEngine.appendBuffer('audio', 1);
|
||||
var rejected = mediaSourceEngine.appendBuffer('audio', 1, null, null);
|
||||
Util.capturePromiseStatus(rejected);
|
||||
|
||||
// The promise has already been rejected, but our capture requires 1 tick.
|
||||
@@ -804,8 +804,8 @@ describe('MediaSourceEngine', function() {
|
||||
};
|
||||
}
|
||||
|
||||
function createMockTextSourceBufferCtor() {
|
||||
var ctor = jasmine.createSpy('TextSourceBuffer');
|
||||
function createMockTextEngineCtor() {
|
||||
var ctor = jasmine.createSpy('TextEngine');
|
||||
ctor.isTypeSupported = function() { return true; };
|
||||
ctor.prototype.addEventListener = function() {};
|
||||
ctor.prototype.removeEventListener = function() {};
|
||||
|
||||
@@ -0,0 +1,310 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2015 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
describe('TextEngine', function() {
|
||||
var TextEngine;
|
||||
var dummyData = new ArrayBuffer(0);
|
||||
var dummyMimeType = 'text/fake';
|
||||
|
||||
var mockParser;
|
||||
var mockTrack;
|
||||
var textEngine;
|
||||
|
||||
beforeAll(function() {
|
||||
TextEngine = shaka.media.TextEngine;
|
||||
});
|
||||
|
||||
beforeEach(function() {
|
||||
mockParser = jasmine.createSpy('mockParser');
|
||||
mockTrack = createMockTrack();
|
||||
TextEngine.registerParser(dummyMimeType, mockParser);
|
||||
textEngine = new TextEngine(mockTrack, dummyMimeType);
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
textEngine = null;
|
||||
TextEngine.unregisterParser(dummyMimeType);
|
||||
mockTrack = null;
|
||||
mockParser = null;
|
||||
});
|
||||
|
||||
describe('isTypeSupported', function() {
|
||||
it('reports support only when a parser is installed', function() {
|
||||
TextEngine.unregisterParser(dummyMimeType);
|
||||
expect(TextEngine.isTypeSupported(dummyMimeType)).toBe(false);
|
||||
TextEngine.registerParser(dummyMimeType, mockParser);
|
||||
expect(TextEngine.isTypeSupported(dummyMimeType)).toBe(true);
|
||||
TextEngine.unregisterParser(dummyMimeType);
|
||||
expect(TextEngine.isTypeSupported(dummyMimeType)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('appendBuffer', function() {
|
||||
it('works asynchronously', function(done) {
|
||||
mockParser.and.returnValue([1, 2, 3]);
|
||||
textEngine.appendBuffer(dummyData, 0, 3).catch(fail).then(done);
|
||||
expect(mockTrack.addCue).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('adds cues to the track', function(done) {
|
||||
mockParser.and.returnValue([1, 2, 3]);
|
||||
|
||||
textEngine.appendBuffer(dummyData, 0, 3).then(function() {
|
||||
expect(mockParser).toHaveBeenCalledWith(dummyData);
|
||||
expect(mockTrack.addCue).toHaveBeenCalledWith(1);
|
||||
expect(mockTrack.addCue).toHaveBeenCalledWith(2);
|
||||
expect(mockTrack.addCue).toHaveBeenCalledWith(3);
|
||||
expect(mockTrack.removeCue).not.toHaveBeenCalled();
|
||||
|
||||
mockTrack.addCue.calls.reset();
|
||||
mockParser.calls.reset();
|
||||
|
||||
mockParser.and.returnValue([4, 5]);
|
||||
return textEngine.appendBuffer(dummyData, 3, 5);
|
||||
}).then(function() {
|
||||
expect(mockParser).toHaveBeenCalledWith(dummyData);
|
||||
expect(mockTrack.addCue).toHaveBeenCalledWith(4);
|
||||
expect(mockTrack.addCue).toHaveBeenCalledWith(5);
|
||||
}).catch(fail).then(done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('remove', function() {
|
||||
var cue1;
|
||||
var cue2;
|
||||
var cue3;
|
||||
|
||||
beforeEach(function() {
|
||||
cue1 = createFakeCue(0, 1);
|
||||
cue2 = createFakeCue(1, 2);
|
||||
cue3 = createFakeCue(2, 3);
|
||||
mockParser.and.returnValue([cue1, cue2, cue3]);
|
||||
});
|
||||
|
||||
it('works asynchronously', function(done) {
|
||||
textEngine.appendBuffer(dummyData, 0, 3).then(function() {
|
||||
var p = textEngine.remove(0, 1);
|
||||
expect(mockTrack.removeCue).not.toHaveBeenCalled();
|
||||
return p;
|
||||
}).catch(fail).then(done);
|
||||
});
|
||||
|
||||
it('removes cues which overlap the range', function(done) {
|
||||
textEngine.appendBuffer(dummyData, 0, 3).then(function() {
|
||||
return textEngine.remove(0, 1);
|
||||
}).then(function() {
|
||||
expect(mockTrack.removeCue.calls.allArgs()).toEqual([[cue1]]);
|
||||
|
||||
mockTrack.removeCue.calls.reset();
|
||||
return textEngine.remove(0.5, 1.001);
|
||||
}).then(function() {
|
||||
expect(mockTrack.removeCue.calls.allArgs()).toEqual([[cue2]]);
|
||||
|
||||
mockTrack.removeCue.calls.reset();
|
||||
return textEngine.remove(3, 5);
|
||||
}).then(function() {
|
||||
expect(mockTrack.removeCue).not.toHaveBeenCalled();
|
||||
|
||||
mockTrack.removeCue.calls.reset();
|
||||
return textEngine.remove(2.9999, Number.POSITIVE_INFINITY);
|
||||
}).then(function() {
|
||||
expect(mockTrack.removeCue.calls.allArgs()).toEqual([[cue3]]);
|
||||
}).catch(fail).then(done);
|
||||
});
|
||||
|
||||
it('does nothing when nothing is buffered', function(done) {
|
||||
textEngine.remove(0, 1).then(function() {
|
||||
expect(mockTrack.removeCue).not.toHaveBeenCalled();
|
||||
}).catch(fail).then(done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setTimestampOffset', function() {
|
||||
it('affects the timestamps of parsed cues', function(done) {
|
||||
mockParser.and.callFake(function() {
|
||||
return [createFakeCue(0, 1), createFakeCue(2, 3)];
|
||||
});
|
||||
|
||||
textEngine.appendBuffer(dummyData, 0, 3).then(function() {
|
||||
expect(mockTrack.addCue).toHaveBeenCalledWith(createFakeCue(0, 1));
|
||||
expect(mockTrack.addCue).toHaveBeenCalledWith(createFakeCue(2, 3));
|
||||
|
||||
mockTrack.addCue.calls.reset();
|
||||
textEngine.setTimestampOffset(4);
|
||||
return textEngine.appendBuffer(dummyData, 0, 3);
|
||||
}).then(function() {
|
||||
expect(mockTrack.addCue).toHaveBeenCalledWith(createFakeCue(4, 5));
|
||||
expect(mockTrack.addCue).toHaveBeenCalledWith(createFakeCue(6, 7));
|
||||
}).catch(fail).then(done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('bufferStart/bufferEnd', function() {
|
||||
beforeEach(function() {
|
||||
mockParser.and.callFake(function() {
|
||||
return [createFakeCue(0, 1), createFakeCue(1, 2), createFakeCue(2, 3)];
|
||||
});
|
||||
});
|
||||
|
||||
it('return null when there are no cues', function() {
|
||||
expect(textEngine.bufferStart()).toBe(null);
|
||||
expect(textEngine.bufferEnd()).toBe(null);
|
||||
});
|
||||
|
||||
it('reflect newly-added cues', function(done) {
|
||||
textEngine.appendBuffer(dummyData, 0, 3).then(function() {
|
||||
expect(textEngine.bufferStart()).toBe(0);
|
||||
expect(textEngine.bufferEnd()).toBe(3);
|
||||
|
||||
textEngine.setTimestampOffset(3);
|
||||
return textEngine.appendBuffer(dummyData, 0, 3);
|
||||
}).then(function() {
|
||||
expect(textEngine.bufferStart()).toBe(0);
|
||||
expect(textEngine.bufferEnd()).toBe(6);
|
||||
|
||||
textEngine.setTimestampOffset(7);
|
||||
return textEngine.appendBuffer(dummyData, 0, 3);
|
||||
}).then(function() {
|
||||
expect(textEngine.bufferStart()).toBe(0);
|
||||
expect(textEngine.bufferEnd()).toBe(10);
|
||||
}).catch(fail).then(done);
|
||||
});
|
||||
|
||||
it('reflect newly-removed cues', function(done) {
|
||||
textEngine.appendBuffer(dummyData, 0, 3).then(function() {
|
||||
textEngine.setTimestampOffset(3);
|
||||
return textEngine.appendBuffer(dummyData, 0, 3);
|
||||
}).then(function() {
|
||||
textEngine.setTimestampOffset(7);
|
||||
return textEngine.appendBuffer(dummyData, 0, 3);
|
||||
}).then(function() {
|
||||
expect(textEngine.bufferStart()).toBe(0);
|
||||
expect(textEngine.bufferEnd()).toBe(10);
|
||||
|
||||
return textEngine.remove(0, 3);
|
||||
}).then(function() {
|
||||
expect(textEngine.bufferStart()).toBe(3);
|
||||
expect(textEngine.bufferEnd()).toBe(10);
|
||||
|
||||
return textEngine.remove(8, 11);
|
||||
}).then(function() {
|
||||
expect(textEngine.bufferStart()).toBe(3);
|
||||
expect(textEngine.bufferEnd()).toBe(8);
|
||||
|
||||
return textEngine.remove(11, 20);
|
||||
}).then(function() {
|
||||
expect(textEngine.bufferStart()).toBe(3);
|
||||
expect(textEngine.bufferEnd()).toBe(8);
|
||||
|
||||
return textEngine.remove(0, Number.POSITIVE_INFINITY);
|
||||
}).then(function() {
|
||||
expect(textEngine.bufferStart()).toBe(null);
|
||||
expect(textEngine.bufferEnd()).toBe(null);
|
||||
}).catch(fail).then(done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('bufferedAheadOf', function() {
|
||||
beforeEach(function() {
|
||||
mockParser.and.callFake(function() {
|
||||
return [createFakeCue(0, 1), createFakeCue(1, 2), createFakeCue(2, 3)];
|
||||
});
|
||||
});
|
||||
|
||||
it('returns 0 when there are no cues', function() {
|
||||
expect(textEngine.bufferedAheadOf(0)).toBe(0);
|
||||
});
|
||||
|
||||
it('returns 0 if |t| is not buffered', function(done) {
|
||||
textEngine.setTimestampOffset(3);
|
||||
textEngine.appendBuffer(dummyData, 0, 3).then(function() {
|
||||
expect(textEngine.bufferedAheadOf(2.9)).toBe(0);
|
||||
expect(textEngine.bufferedAheadOf(6.1)).toBe(0);
|
||||
}).catch(fail).then(done);
|
||||
});
|
||||
|
||||
it('returns the distance to the end if |t| is buffered', function(done) {
|
||||
textEngine.appendBuffer(dummyData, 0, 3).then(function() {
|
||||
expect(textEngine.bufferedAheadOf(0)).toBe(3);
|
||||
expect(textEngine.bufferedAheadOf(1)).toBe(2);
|
||||
expect(textEngine.bufferedAheadOf(2.5)).toBeCloseTo(0.5);
|
||||
}).catch(fail).then(done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setAppendWindowEnd', function() {
|
||||
beforeEach(function() {
|
||||
mockParser.and.callFake(function() {
|
||||
return [createFakeCue(0, 1), createFakeCue(1, 2), createFakeCue(2, 3)];
|
||||
});
|
||||
});
|
||||
|
||||
it('limits appended cues', function(done) {
|
||||
textEngine.setAppendWindowEnd(1.9);
|
||||
textEngine.appendBuffer(dummyData, 0, 3).then(function() {
|
||||
expect(mockTrack.addCue).toHaveBeenCalledWith(createFakeCue(0, 1));
|
||||
expect(mockTrack.addCue).toHaveBeenCalledWith(createFakeCue(1, 2));
|
||||
|
||||
mockTrack.addCue.calls.reset();
|
||||
textEngine.setAppendWindowEnd(2.1);
|
||||
return textEngine.appendBuffer(dummyData, 0, 3);
|
||||
}).then(function() {
|
||||
expect(mockTrack.addCue).toHaveBeenCalledWith(createFakeCue(0, 1));
|
||||
expect(mockTrack.addCue).toHaveBeenCalledWith(createFakeCue(1, 2));
|
||||
expect(mockTrack.addCue).toHaveBeenCalledWith(createFakeCue(2, 3));
|
||||
}).catch(fail).then(done);
|
||||
});
|
||||
|
||||
it('limits bufferEnd', function(done) {
|
||||
textEngine.setAppendWindowEnd(1.9);
|
||||
textEngine.appendBuffer(dummyData, 0, 3).then(function() {
|
||||
expect(textEngine.bufferEnd()).toBe(1.9);
|
||||
|
||||
textEngine.setAppendWindowEnd(2.1);
|
||||
return textEngine.appendBuffer(dummyData, 0, 3);
|
||||
}).then(function() {
|
||||
expect(textEngine.bufferEnd()).toBe(2.1);
|
||||
|
||||
textEngine.setAppendWindowEnd(4.1);
|
||||
return textEngine.appendBuffer(dummyData, 0, 3);
|
||||
}).then(function() {
|
||||
expect(textEngine.bufferEnd()).toBe(3);
|
||||
}).catch(fail).then(done);
|
||||
});
|
||||
});
|
||||
|
||||
function createMockTrack() {
|
||||
var track = {
|
||||
addCue: jasmine.createSpy('addCue'),
|
||||
removeCue: jasmine.createSpy('removeCue'),
|
||||
cues: []
|
||||
};
|
||||
track.addCue.and.callFake(function(cue) {
|
||||
track.cues.push(cue);
|
||||
});
|
||||
track.removeCue.and.callFake(function(cue) {
|
||||
var idx = track.cues.indexOf(cue);
|
||||
shaka.asserts.assert(idx >= 0, 'cue does not exist');
|
||||
track.cues.splice(idx, 1);
|
||||
});
|
||||
return track;
|
||||
}
|
||||
|
||||
function createFakeCue(startTime, endTime) {
|
||||
return { startTime: startTime, endTime: endTime };
|
||||
}
|
||||
});
|
||||
@@ -1,301 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2015 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
describe('TextSourceBuffer', function() {
|
||||
var TextSourceBuffer;
|
||||
var dummyData = new ArrayBuffer(0);
|
||||
var dummyMimeType = 'text/fake';
|
||||
|
||||
beforeAll(function() {
|
||||
// Can't set this alias at load-time because the sources may not be loaded
|
||||
// yet.
|
||||
TextSourceBuffer = shaka.media.TextSourceBuffer;
|
||||
});
|
||||
|
||||
describe('isTypeSupported', function() {
|
||||
it('reports support only when a parser is installed', function() {
|
||||
expect(TextSourceBuffer.isTypeSupported(dummyMimeType)).toBe(false);
|
||||
TextSourceBuffer.registerParser(dummyMimeType, function() {});
|
||||
expect(TextSourceBuffer.isTypeSupported(dummyMimeType)).toBe(true);
|
||||
TextSourceBuffer.unregisterParser(dummyMimeType);
|
||||
expect(TextSourceBuffer.isTypeSupported(dummyMimeType)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('appendBuffer', function() {
|
||||
var mockParser;
|
||||
var mockTrack;
|
||||
var sourceBuffer;
|
||||
var eventManager;
|
||||
var onUpdateEnd;
|
||||
|
||||
beforeEach(function() {
|
||||
mockParser = jasmine.createSpy('mockParser');
|
||||
mockTrack = createMockTrack();
|
||||
TextSourceBuffer.registerParser(dummyMimeType, mockParser);
|
||||
sourceBuffer = new TextSourceBuffer(mockTrack, dummyMimeType);
|
||||
onUpdateEnd = jasmine.createSpy('onUpdateEnd');
|
||||
eventManager = new shaka.util.EventManager();
|
||||
eventManager.listen(sourceBuffer, 'updateend', onUpdateEnd);
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
sourceBuffer = null;
|
||||
TextSourceBuffer.unregisterParser(dummyMimeType);
|
||||
mockTrack = null;
|
||||
mockParser = null;
|
||||
eventManager.destroy();
|
||||
eventManager = null;
|
||||
});
|
||||
|
||||
it('works asynchronously', function() {
|
||||
mockParser.and.returnValue([1, 2, 3]);
|
||||
sourceBuffer.appendBuffer(dummyData);
|
||||
expect(mockTrack.addCue).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('dispatches an "updateend" event', function(done) {
|
||||
mockParser.and.returnValue([]);
|
||||
|
||||
expect(sourceBuffer.updating).toBe(false);
|
||||
sourceBuffer.appendBuffer(dummyData);
|
||||
expect(sourceBuffer.updating).toBe(true);
|
||||
|
||||
eventToPromise(onUpdateEnd).then(function() {
|
||||
expect(sourceBuffer.updating).toBe(false);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('adds cues to the track', function(done) {
|
||||
expect(mockParser).not.toHaveBeenCalled();
|
||||
expect(mockTrack.addCue).not.toHaveBeenCalled();
|
||||
expect(mockTrack.removeCue).not.toHaveBeenCalled();
|
||||
|
||||
mockParser.and.returnValue([1, 2, 3]);
|
||||
sourceBuffer.appendBuffer(dummyData);
|
||||
|
||||
eventToPromise(onUpdateEnd).then(function() {
|
||||
expect(mockParser).toHaveBeenCalledWith(dummyData);
|
||||
expect(mockTrack.addCue).toHaveBeenCalledWith(1);
|
||||
expect(mockTrack.addCue).toHaveBeenCalledWith(2);
|
||||
expect(mockTrack.addCue).toHaveBeenCalledWith(3);
|
||||
expect(mockTrack.removeCue).not.toHaveBeenCalled();
|
||||
|
||||
mockTrack.addCue.calls.reset();
|
||||
mockParser.calls.reset();
|
||||
|
||||
mockParser.and.returnValue([4, 5]);
|
||||
sourceBuffer.appendBuffer(dummyData);
|
||||
|
||||
return eventToPromise(onUpdateEnd);
|
||||
}).then(function() {
|
||||
expect(mockParser).toHaveBeenCalledWith(dummyData);
|
||||
expect(mockTrack.addCue).toHaveBeenCalledWith(4);
|
||||
expect(mockTrack.addCue).toHaveBeenCalledWith(5);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('remove', function() {
|
||||
var mockTrack;
|
||||
var sourceBuffer;
|
||||
var eventManager;
|
||||
var onUpdateEnd;
|
||||
|
||||
beforeEach(function() {
|
||||
mockTrack = createMockTrack();
|
||||
TextSourceBuffer.registerParser(dummyMimeType, function() {});
|
||||
sourceBuffer = new TextSourceBuffer(mockTrack, dummyMimeType);
|
||||
onUpdateEnd = jasmine.createSpy('onUpdateEnd');
|
||||
eventManager = new shaka.util.EventManager();
|
||||
eventManager.listen(sourceBuffer, 'updateend', onUpdateEnd);
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
sourceBuffer = null;
|
||||
TextSourceBuffer.unregisterParser(dummyMimeType);
|
||||
mockTrack = null;
|
||||
eventManager.destroy();
|
||||
eventManager = null;
|
||||
});
|
||||
|
||||
it('works asynchronously', function() {
|
||||
var cue1 = createFakeCue(0, 1);
|
||||
var cue2 = createFakeCue(1, 2);
|
||||
var cue3 = createFakeCue(2, 3);
|
||||
mockTrack.cues = [cue1, cue2, cue3];
|
||||
|
||||
sourceBuffer.remove(0, 1);
|
||||
expect(mockTrack.removeCue).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('dispatches an "updateend" event', function(done) {
|
||||
expect(sourceBuffer.updating).toBe(false);
|
||||
sourceBuffer.remove(0, 1);
|
||||
expect(sourceBuffer.updating).toBe(true);
|
||||
|
||||
eventToPromise(onUpdateEnd).then(function() {
|
||||
expect(sourceBuffer.updating).toBe(false);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('removes cues which overlap the range', function(done) {
|
||||
var cue1 = createFakeCue(0, 1);
|
||||
var cue2 = createFakeCue(1, 2);
|
||||
var cue3 = createFakeCue(2, 3);
|
||||
mockTrack.cues = [cue1, cue2, cue3];
|
||||
|
||||
sourceBuffer.remove(0, 1);
|
||||
|
||||
eventToPromise(onUpdateEnd).then(function() {
|
||||
expect(mockTrack.removeCue.calls.allArgs()).toEqual([[cue1], [cue2]]);
|
||||
|
||||
mockTrack.removeCue.calls.reset();
|
||||
sourceBuffer.remove(0.5, 0.9);
|
||||
return eventToPromise(onUpdateEnd);
|
||||
}).then(function() {
|
||||
expect(mockTrack.removeCue.calls.allArgs()).toEqual([[cue1]]);
|
||||
|
||||
mockTrack.removeCue.calls.reset();
|
||||
sourceBuffer.remove(3.00001, 5);
|
||||
return eventToPromise(onUpdateEnd);
|
||||
}).then(function() {
|
||||
expect(mockTrack.removeCue).not.toHaveBeenCalled();
|
||||
|
||||
mockTrack.removeCue.calls.reset();
|
||||
sourceBuffer.remove(0.5, 1.5);
|
||||
return eventToPromise(onUpdateEnd);
|
||||
}).then(function() {
|
||||
expect(mockTrack.removeCue.calls.allArgs()).toEqual([[cue1], [cue2]]);
|
||||
|
||||
mockTrack.removeCue.calls.reset();
|
||||
sourceBuffer.remove(3, Number.POSITIVE_INFINITY);
|
||||
return eventToPromise(onUpdateEnd);
|
||||
}).then(function() {
|
||||
expect(mockTrack.removeCue.calls.allArgs()).toEqual([[cue3]]);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('buffered', function() {
|
||||
var mockTrack;
|
||||
var sourceBuffer;
|
||||
var eventManager;
|
||||
var onUpdateEnd;
|
||||
|
||||
beforeEach(function() {
|
||||
var fakeParser = function() { return []; };
|
||||
mockTrack = createMockTrack();
|
||||
TextSourceBuffer.registerParser(dummyMimeType, fakeParser);
|
||||
sourceBuffer = new TextSourceBuffer(mockTrack, dummyMimeType);
|
||||
onUpdateEnd = jasmine.createSpy('onUpdateEnd');
|
||||
eventManager = new shaka.util.EventManager();
|
||||
eventManager.listen(sourceBuffer, 'updateend', onUpdateEnd);
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
sourceBuffer = null;
|
||||
TextSourceBuffer.unregisterParser(dummyMimeType);
|
||||
mockTrack = null;
|
||||
eventManager.destroy();
|
||||
eventManager = null;
|
||||
});
|
||||
|
||||
it('reflects newly-added cues', function(done) {
|
||||
expect(sourceBuffer.buffered.length).toBe(0);
|
||||
|
||||
var cue1 = createFakeCue(0, 1);
|
||||
var cue2 = createFakeCue(1, 2);
|
||||
mockTrack.cues = [cue1, cue2];
|
||||
// call appendBuffer to trigger the update of buffered.
|
||||
sourceBuffer.appendBuffer(dummyData);
|
||||
|
||||
eventToPromise(onUpdateEnd).then(function() {
|
||||
expect(sourceBuffer.buffered.length).toBe(1);
|
||||
expect(sourceBuffer.buffered.start(0)).toBe(0);
|
||||
expect(sourceBuffer.buffered.end(0)).toBe(2);
|
||||
|
||||
var cue3 = createFakeCue(2, 3);
|
||||
mockTrack.cues = [cue1, cue2, cue3];
|
||||
sourceBuffer.appendBuffer(dummyData);
|
||||
|
||||
return eventToPromise(onUpdateEnd);
|
||||
}).then(function() {
|
||||
expect(sourceBuffer.buffered.length).toBe(1);
|
||||
expect(sourceBuffer.buffered.start(0)).toBe(0);
|
||||
expect(sourceBuffer.buffered.end(0)).toBe(3);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('reflects newly-removed cues', function(done) {
|
||||
var cue1 = createFakeCue(0, 1);
|
||||
var cue2 = createFakeCue(1, 2);
|
||||
var cue3 = createFakeCue(2, 3);
|
||||
mockTrack.cues = [cue1, cue2, cue3];
|
||||
// call appendBuffer to trigger the update of buffered.
|
||||
sourceBuffer.appendBuffer(dummyData);
|
||||
|
||||
eventToPromise(onUpdateEnd).then(function() {
|
||||
expect(sourceBuffer.buffered.length).toBe(1);
|
||||
expect(sourceBuffer.buffered.start(0)).toBe(0);
|
||||
expect(sourceBuffer.buffered.end(0)).toBe(3);
|
||||
|
||||
mockTrack.cues = [cue1, cue2];
|
||||
// call remove to trigger the update of buffered.
|
||||
sourceBuffer.remove(-1, -1);
|
||||
|
||||
return eventToPromise(onUpdateEnd);
|
||||
}).then(function() {
|
||||
expect(sourceBuffer.buffered.length).toBe(1);
|
||||
expect(sourceBuffer.buffered.start(0)).toBe(0);
|
||||
expect(sourceBuffer.buffered.end(0)).toBe(2);
|
||||
|
||||
mockTrack.cues = [];
|
||||
// call remove to trigger the update of buffered.
|
||||
sourceBuffer.remove(-1, -1);
|
||||
|
||||
return eventToPromise(onUpdateEnd);
|
||||
}).then(function() {
|
||||
expect(sourceBuffer.buffered.length).toBe(0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function eventToPromise(eventSpy) {
|
||||
return new Promise(function(resolve) {
|
||||
eventSpy.and.callFake(resolve);
|
||||
});
|
||||
}
|
||||
|
||||
function createMockTrack() {
|
||||
return {
|
||||
addCue: jasmine.createSpy('addCue'),
|
||||
removeCue: jasmine.createSpy('removeCue'),
|
||||
cues: []
|
||||
};
|
||||
}
|
||||
|
||||
function createFakeCue(startTime, endTime) {
|
||||
return { startTime: startTime, endTime: endTime };
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user