Skip to content

Commit

Permalink
Track active tracks in Player.
Browse files Browse the repository at this point in the history
Before, the Player would query StreamingEngine for the "active" streams.
The Player would use this to set the |track.active| field.  However,
this causes problems with multi-Period content.  Once StreamingEngine
has buffered through a Period transition, it will report the active
streams from the new Period.  This will mean that in
|player.getVariantTracks|, none of the tracks will be active.

Now, Player will track the IDs of the active tracks for every Period
so it can correctly report which tracks are active.

Closes #680

Change-Id: Ib5ffc88b32e165f088e6472930eeb1b29b5adfe9
  • Loading branch information
TheModMaker committed Feb 9, 2017
1 parent 7610fc1 commit 6b6bfb7
Show file tree
Hide file tree
Showing 4 changed files with 146 additions and 53 deletions.
22 changes: 3 additions & 19 deletions lib/media/streaming_engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -1713,25 +1713,9 @@ shaka.media.StreamingEngine.prototype.findPeriodContainingTime_ = function(
*/
shaka.media.StreamingEngine.prototype.findPeriodContainingStream_ = function(
stream) {
for (var i = 0; i < this.manifest_.periods.length; ++i) {
var period = this.manifest_.periods[i];
if (stream.type == 'text') {
for (var j = 0; j < period.textStreams.length; ++j) {
var textStream = period.textStreams[j];
if (textStream == stream)
return i;
}
} else {
for (var j = 0; j < period.variants.length; ++j) {
var variant = period.variants[j];
if (variant.audio == stream || variant.video == stream ||
(variant.video && variant.video.trickModeVideo == stream)) {
return i;
}
}
}
}
return -1;
goog.asserts.assert(this.manifest_, 'Must not be destroyed');
return shaka.util.StreamUtils.findPeriodContainingStream(
this.manifest_, stream);
};


Expand Down
89 changes: 66 additions & 23 deletions lib/player.js
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,12 @@ shaka.Player = function(video, opt_dependencyInjector) {
/** @private {!Array.<shakaExtern.TimelineRegionInfo>} */
this.pendingTimelineRegions_ = [];

/**
* A map of Period number to a map of content type to stream id.
* @private {!Object.<number, !Object.<string, number>>}
*/
this.activeStreamsByPeriod_ = {};

/** @private {?shakaExtern.PlayerConfiguration} */
this.config_ = this.defaultConfig_();

Expand Down Expand Up @@ -993,14 +999,16 @@ shaka.Player.prototype.selectTrack = function(track, opt_clearBuffer) {
* @export
*/
shaka.Player.prototype.getVariantTracks = function() {
if (!this.streamingEngine_)
if (!this.manifest_)
return [];
this.assertCorrectActiveStreams_();

var activeStreams = this.streamingEngine_.getActiveStreams();
var period = this.streamingEngine_.getCurrentPeriod();
return shaka.util.StreamUtils.getVariantTracks(period,
activeStreams['audio'],
activeStreams['video']);
var currentPeriod = shaka.util.StreamUtils.findPeriodContainingTime(
this.manifest_, this.playhead_.getTime());
var activeStreams = this.activeStreamsByPeriod_[currentPeriod] || {};
return shaka.util.StreamUtils.getVariantTracks(
this.manifest_.periods[currentPeriod], activeStreams['audio'],
activeStreams['video']);
};


Expand All @@ -1013,12 +1021,16 @@ shaka.Player.prototype.getVariantTracks = function() {
* @export
*/
shaka.Player.prototype.getTextTracks = function() {
if (!this.streamingEngine_)
if (!this.manifest_)
return [];

var activeStreams = this.streamingEngine_.getActiveStreams();
var period = this.streamingEngine_.getCurrentPeriod();
return shaka.util.StreamUtils.getTextTracks(period, activeStreams['text'])
this.assertCorrectActiveStreams_();

var currentPeriod = shaka.util.StreamUtils.findPeriodContainingTime(
this.manifest_, this.playhead_.getTime());
var activeStreams = this.activeStreamsByPeriod_[currentPeriod] || {};
return shaka.util.StreamUtils
.getTextTracks(
this.manifest_.periods[currentPeriod], activeStreams['text'])
.filter(function(track) {
// Don't show any tracks that are being loaded still.
return this.loadingTextStreamIds_.indexOf(track.id) < 0;
Expand Down Expand Up @@ -1100,7 +1112,7 @@ shaka.Player.prototype.selectVariantTrack = function(track, opt_clearBuffer) {
this.addToSwitchHistory_(stream, /* fromAdaptation */ false);
}.bind(this));

// Save current text stream to insure that it doesn't get overriden
// Save current text stream to ensure that it doesn't get overridden
// by a default one inside shaka.Player.configure()
var activeStreams = this.streamingEngine_.getActiveStreams();
var currentTextStream = activeStreams['text'];
Expand Down Expand Up @@ -1233,24 +1245,22 @@ shaka.Player.prototype.getStats = function() {
this.updateState_();

var video = null;
var audio = null;
var variant = null;
var videoInfo = this.video_ && this.video_.getVideoPlaybackQuality ?
this.video_.getVideoPlaybackQuality() : {};

if (this.streamingEngine_) {
var activeStreams = this.streamingEngine_.getActiveStreams();
video = activeStreams['video'] || {};
audio = activeStreams['audio'] || {};
if (this.playhead_ && this.manifest_) {
var periodIdx = shaka.util.StreamUtils.findPeriodContainingTime(
this.manifest_, this.playhead_.getTime());
var period = this.manifest_.periods[periodIdx];
var activeStreams = this.activeStreamsByPeriod_[periodIdx];

var period = this.streamingEngine_.getCurrentPeriod();
variant = shaka.util.StreamUtils.getVariantByStreams(audio,
video,
period.variants);
variant = shaka.util.StreamUtils.getVariantByStreamIds(
activeStreams['audio'], activeStreams['video'], period.variants);
video = variant.video || {};
}

if (!video) video = {};
if (!audio) audio = {};
if (!variant) variant = {};

// Clone the internal object so our state cannot be tampered with.
Expand Down Expand Up @@ -1438,6 +1448,13 @@ shaka.Player.prototype.addToSwitchHistory_ = function(stream, fromAdaptation) {
type: stream.type,
fromAdaptation: fromAdaptation
});

goog.asserts.assert(this.manifest_, 'Must not be destroyed');
var periodIndex =
shaka.util.StreamUtils.findPeriodContainingStream(this.manifest_, stream);
if (!this.activeStreamsByPeriod_[periodIndex])
this.activeStreamsByPeriod_[periodIndex] = {};
this.activeStreamsByPeriod_[periodIndex][stream.type] = stream.id;
};


Expand Down Expand Up @@ -1480,6 +1497,7 @@ shaka.Player.prototype.destroyStreaming_ = function() {
this.mediaSourceOpen_ = null;
this.mediaSource_ = null;
this.pendingTimelineRegions_ = [];
this.activeStreamsByPeriod_ = {};
this.deferredSwitches_ = {};
this.stats_ = this.getCleanStats_();

Expand Down Expand Up @@ -1698,6 +1716,31 @@ shaka.Player.prototype.deferredSwitch_ = function(
};


/**
* Verifies that the active streams according to the player match those in
* StreamingEngine.
* @private
*/
shaka.Player.prototype.assertCorrectActiveStreams_ = function() {
if (!this.streamingEngine_ || !this.manifest_ || COMPILED) return;
var StreamUtils = shaka.util.StreamUtils;

var streamingActive = this.streamingEngine_.getActiveStreams();
var streamingPeriodIndex = StreamUtils.findPeriodContainingStream(
this.manifest_, streamingActive['video'] || streamingActive['audio']);
var currentPeriodIndex =
this.manifest_.periods.indexOf(this.streamingEngine_.getCurrentPeriod());
if (streamingPeriodIndex < 0 || streamingPeriodIndex != currentPeriodIndex)
return;

var playerActive = this.activeStreamsByPeriod_[currentPeriodIndex] || {};
for (var type in streamingActive) {
goog.asserts.assert(streamingActive[type].id == playerActive[type],
'Inconsistent active stream');
}
};


/** @private */
shaka.Player.prototype.updateTimeStats_ = function() {
// Only count while we're loaded.
Expand Down Expand Up @@ -1909,7 +1952,7 @@ shaka.Player.prototype.chooseStreams_ =
}
}

needsUpdate.filter(shaka.util.Functional.isNotDuplicate);
needsUpdate = needsUpdate.filter(shaka.util.Functional.isNotDuplicate);

if (needsUpdate.length > 0) {
shaka.log.debug('Choosing new streams for', needsUpdate);
Expand Down
78 changes: 68 additions & 10 deletions lib/util/stream_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -208,22 +208,22 @@ shaka.util.StreamUtils.variantIsCompatible_ =
* Gets an array of Track objects for the given Period
*
* @param {shakaExtern.Period} period
* @param {?shakaExtern.Stream} activeAudio
* @param {?shakaExtern.Stream} activeVideo
* @param {?number} activeAudioId
* @param {?number} activeVideoId
* @return {!Array.<shakaExtern.Track>}
*/
shaka.util.StreamUtils.getVariantTracks =
function(period, activeAudio, activeVideo) {
function(period, activeAudioId, activeVideoId) {
var StreamUtils = shaka.util.StreamUtils;
var variants = StreamUtils.getPlayableVariants(period.variants);
var tracks = variants.map(function(variant) {
var isActive;
if (variant.video && variant.audio) {
isActive = activeVideo == variant.video &&
activeAudio == variant.audio;
isActive = activeVideoId == variant.video.id &&
activeAudioId == variant.audio.id;
} else {
isActive = (variant.video && activeVideo == variant.video) ||
(variant.audio && activeAudio == variant.audio);
isActive = (variant.video && activeVideoId == variant.video.id) ||
(variant.audio && activeAudioId == variant.audio.id);
}
var codecs = '';
if (variant.video) codecs += variant.video.codecs;
Expand Down Expand Up @@ -258,14 +258,14 @@ shaka.util.StreamUtils.getVariantTracks =
* Gets an array of text Track objects for the given Period.
*
* @param {shakaExtern.Period} period
* @param {?shakaExtern.Stream} activeStream
* @param {?number} activeStreamId
* @return {!Array.<shakaExtern.Track>}
*/
shaka.util.StreamUtils.getTextTracks = function(period, activeStream) {
shaka.util.StreamUtils.getTextTracks = function(period, activeStreamId) {
return period.textStreams.map(function(stream) {
return {
id: stream.id,
active: activeStream == stream,
active: activeStreamId == stream.id,
type: 'text',
language: stream.language,
kind: stream.kind,
Expand Down Expand Up @@ -456,6 +456,35 @@ shaka.util.StreamUtils.getVariantByStreams = function(audio, video, variants) {
};


/**
* Finds a Variant with the given video and audio streams, by stream ID.
* Returns null if none were found.
*
* @param {?number} audioId
* @param {?number} videoId
* @param {!Array.<shakaExtern.Variant>} variants
* @return {?shakaExtern.Variant}
*/
shaka.util.StreamUtils.getVariantByStreamIds = function(
audioId, videoId, variants) {
function matchesId(id, stream) {
if (id == null)
return stream == null;
else
return stream.id == id;
}

for (var i = 0; i < variants.length; i++) {
if (matchesId(audioId, variants[i].audio) &&
matchesId(videoId, variants[i].video)) {
return variants[i];
}
}

return null;
};


/**
* Takes a MIME type and optional codecs string and produces the full MIME type.
*
Expand Down Expand Up @@ -486,3 +515,32 @@ shaka.util.StreamUtils.findPeriodContainingTime = function(manifest, time) {
}
return 0;
};


/**
* @param {shakaExtern.Manifest} manifest
* @param {shakaExtern.Stream} stream
* @return {number} The index of the Period which contains |stream|, or -1 if
* no Period contains |stream|.
*/
shaka.util.StreamUtils.findPeriodContainingStream = function(manifest, stream) {
for (var periodIdx = 0; periodIdx < manifest.periods.length; ++periodIdx) {
var period = manifest.periods[periodIdx];
if (stream.type == 'text') {
for (var j = 0; j < period.textStreams.length; ++j) {
var textStream = period.textStreams[j];
if (textStream == stream)
return periodIdx;
}
} else {
for (var j = 0; j < period.variants.length; ++j) {
var variant = period.variants[j];
if (variant.audio == stream || variant.video == stream ||
(variant.video && variant.video.trickModeVideo == stream)) {
return periodIdx;
}
}
}
}
return -1;
};
10 changes: 9 additions & 1 deletion test/player_unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,11 @@ describe('Player', function() {
return networkingEngine;
};
player.createPlayhead = function() {
return {destroy: function() {}, addTimelineRegion: function() {}};
return {
destroy: function() {},
addTimelineRegion: function() {},
getTime: function() { return 0; }
};
};
player.createMediaSource = function() { return Promise.resolve(); };
player.createMediaSourceEngine = function() {
Expand Down Expand Up @@ -821,6 +825,10 @@ describe('Player', function() {
});

it('returns the correct tracks', function() {
// Switch tracks first so we setup the "active" tracks.
player.selectVariantTrack(variantTracks[0]);
player.selectTextTrack(textTracks[0]);

var actualVariantTracks = player.getVariantTracks();
var actualTextTracks = player.getTextTracks();
expect(actualVariantTracks).toEqual(variantTracks);
Expand Down

0 comments on commit 6b6bfb7

Please sign in to comment.