Skip to content

Commit

Permalink
Allow switching between Periods with and without text
Browse files Browse the repository at this point in the history
We don't allow switching between Periods that are video+audio to
video-only because it is incompatible with MSE.  When the browser
gets to the transition, it will pause waiting for the audio buffer.

However, we should allow switching between Periods that do and do not
have text streams.

Issue #715

Change-Id: I77f3bf92681d8181c90169b8f4a15857b7d9e66d
  • Loading branch information
TheModMaker committed Mar 9, 2017
1 parent d510795 commit 2cc68b2
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 9 deletions.
36 changes: 27 additions & 9 deletions lib/media/streaming_engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,8 @@ shaka.media.StreamingEngine = function(
* waitingToFlushBuffer: boolean,
* clearingBuffer: boolean,
* recovering: boolean,
* hasError: boolean
* hasError: boolean,
* resumeAt: number
* }}
*
* @description
Expand Down Expand Up @@ -250,6 +251,10 @@ shaka.media.StreamingEngine = function(
* @property {boolean} hasError
* True indicates that the stream has encountered an error and has stopped
* updates.
* @property {number} resumeAt
* An override for the time to start performing updates at. If the playhead
* is behind this time, update_() will still start fetching segments from
* this time. If the playhead is ahead of the time, this field is ignored.
*/
shaka.media.StreamingEngine.MediaState_;

Expand Down Expand Up @@ -632,10 +637,12 @@ shaka.media.StreamingEngine.prototype.seeked = function() {
*
* @param {!Object.<shaka.util.ManifestParserUtils.ContentType,
shakaExtern.Stream>} streamsByType
* @param {number=} opt_resumeAt
* @return {!Promise}
* @private
*/
shaka.media.StreamingEngine.prototype.initStreams_ = function(streamsByType) {
shaka.media.StreamingEngine.prototype.initStreams_ = function(
streamsByType, opt_resumeAt) {
var MapUtils = shaka.util.MapUtils;
goog.asserts.assert(this.config_,
'StreamingEngine configure() must be called before init()!');
Expand Down Expand Up @@ -678,7 +685,8 @@ shaka.media.StreamingEngine.prototype.initStreams_ = function(streamsByType) {
waitingToFlushBuffer: false,
clearingBuffer: false,
recovering: false,
hasError: false
hasError: false,
resumeAt: opt_resumeAt || 0
};
this.scheduleUpdate_(this.mediaStates_[type], 0);
}
Expand Down Expand Up @@ -904,6 +912,7 @@ shaka.media.StreamingEngine.prototype.update_ = function(mediaState) {
// Get the next timestamp we need.
var timeNeeded = this.getTimeNeeded_(mediaState, playheadTime);
shaka.log.v2(logPrefix, 'timeNeeded=' + timeNeeded);
mediaState.resumeAt = 0;

var currentPeriodIndex = this.findPeriodContainingStream_(mediaState.stream);
var needPeriodIndex = this.findPeriodContainingTime_(timeNeeded);
Expand Down Expand Up @@ -1013,7 +1022,7 @@ shaka.media.StreamingEngine.prototype.getTimeNeeded_ = function(
// of the timestamps in the manifest), but we need drift free times when
// comparing times against presentation and Period boundaries.
if (!mediaState.lastStream || !mediaState.lastSegmentReference) {
return playheadTime;
return Math.max(playheadTime, mediaState.resumeAt);
}

var lastPeriodIndex =
Expand Down Expand Up @@ -1703,7 +1712,7 @@ shaka.media.StreamingEngine.prototype.handlePeriodTransition_ = function(

// Vet |streamsByType| before switching.
for (var type in this.mediaStates_) {
if (streamsByType[type]) continue;
if (streamsByType[type] || type == ContentType.TEXT) continue;

shaka.log.error(logPrefix,
'invalid Streams chosen: missing ' + type + ' Stream');
Expand All @@ -1714,8 +1723,12 @@ shaka.media.StreamingEngine.prototype.handlePeriodTransition_ = function(
}

for (var type in streamsByType) {
if (this.mediaStates_[type] ||
(type == ContentType.TEXT && this.config_.ignoreTextStreamFailures)) {
if (this.mediaStates_[type]) continue;
if (type == ContentType.TEXT) {
// initStreams_ will switch streams and schedule an update.
this.initStreams_(
{text: streamsByType[ContentType.TEXT]}, needPeriod.startTime);
delete streamsByType[type];
continue;
}

Expand All @@ -1729,8 +1742,13 @@ shaka.media.StreamingEngine.prototype.handlePeriodTransition_ = function(

for (var type in this.mediaStates_) {
var stream = streamsByType[type];
this.switch(type, stream, /* clearBuffer */ false);
this.scheduleUpdate_(this.mediaStates_[type], 0);
if (stream) {
this.switch(type, stream, /* clearBuffer */ false);
this.scheduleUpdate_(this.mediaStates_[type], 0);
} else {
goog.asserts.assert(type == ContentType.TEXT, 'Invalid streams chosen');
delete this.mediaStates_[type];
}
}

// We've already set up the Period so call onCanSwitch_() right now.
Expand Down
5 changes: 5 additions & 0 deletions lib/util/error.js
Original file line number Diff line number Diff line change
Expand Up @@ -504,6 +504,11 @@ shaka.util.Error.Code = {
/**
* The StreamingEngine called onChooseStreams() but the callback receiver
* did not return the correct number or type of Streams.
*
* This can happen when there is multi-Period content where one Period is
* video+audio and another is video-only or audio-only. We don't support this
* case because it is incompatible with MSE. When the browser reaches the
* transition, it will pause, waiting for the audio stream.
*/
'INVALID_STREAMS_CHOSEN': 5005,

Expand Down
54 changes: 54 additions & 0 deletions test/media/streaming_engine_unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -712,6 +712,60 @@ describe('StreamingEngine', function() {
expect(onStartupComplete).toHaveBeenCalled();
});

it('plays when 1st Period doesn\'t have text streams', function() {
setupVod();
manifest.periods[0].textStreams = [];

mediaSourceEngine = new shaka.test.FakeMediaSourceEngine(segmentData);
createStreamingEngine();

playhead.getTime.and.returnValue(0);
onStartupComplete.and.callFake(setupFakeGetTime.bind(null, 0));
onChooseStreams.and.callFake(function(period) {
var chosen = defaultOnChooseStreams(period);
if (period == manifest.periods[0])
delete chosen[ContentType.TEXT];
return chosen;
});

// Here we go!
streamingEngine.init();
runTest();

expect(mediaSourceEngine.segments).toEqual({
audio: [true, true, true, true],
video: [true, true, true, true],
text: [false, false, true, true]
});
});

it('plays when 2nd Period doesn\'t have text streams', function() {
setupVod();
manifest.periods[1].textStreams = [];

mediaSourceEngine = new shaka.test.FakeMediaSourceEngine(segmentData);
createStreamingEngine();

playhead.getTime.and.returnValue(0);
onStartupComplete.and.callFake(setupFakeGetTime.bind(null, 0));
onChooseStreams.and.callFake(function(period) {
var chosen = defaultOnChooseStreams(period);
if (period == manifest.periods[1])
delete chosen[ContentType.TEXT];
return chosen;
});

// Here we go!
streamingEngine.init();
runTest();

expect(mediaSourceEngine.segments).toEqual({
audio: [true, true, true, true],
video: [true, true, true, true],
text: [true, true, false, false]
});
});

describe('handles seeks (VOD)', function() {
var onTick;
var stub = function() {};
Expand Down

0 comments on commit 2cc68b2

Please sign in to comment.