Skip to content

Commit

Permalink
Make Text Displayer The Authority on Text Visibility
Browse files Browse the repository at this point in the history
In order to store what the text visibility should be after we load
content we have a cached value. This was causing problems with src=
content. So this changes text visibility to always be read from the text
displayer.

This allows the cached value to be a write only values. While doing this
change, some things were moved to ensure that the text visibility values
never got out a sync.

Change-Id: I15f7d00c910aaea4ddf80acd558e2819d23305f0
  • Loading branch information
vaage committed Mar 29, 2019
1 parent 5018251 commit 1d1eabc
Showing 1 changed file with 87 additions and 38 deletions.
125 changes: 87 additions & 38 deletions lib/player.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ shaka.Player = function(mediaElement, dependencyInjector) {
*
* @private {boolean}
*/
this.textVisibility_ = false;
this.isTextVisible_ = false;

/** @private {shaka.util.EventManager} */
this.eventManager_ = new shaka.util.EventManager();
Expand Down Expand Up @@ -380,7 +380,6 @@ shaka.Player.prototype.destroy = async function() {
this.abrManagerFactory_ = null;
this.abrManager_ = null;
this.config_ = null;
this.textVisibility_ = false;

if (this.networkingEngine_) {
await this.networkingEngine_.destroy();
Expand Down Expand Up @@ -1155,9 +1154,12 @@ shaka.Player.prototype.onInitializeMediaSourceEngine_ = async function(
new shaka.media.MuxJSClosedCaptionParser() :
new shaka.media.NoopCaptionParser();

// When changing text visibility we need to update both the text displayer
// and streaming engine because we don't always stream text. To ensure that
// text displayer and streaming engine are always in sync, wait until they are
// both initialized before setting the initial value.
const TextDisplayerFactory = this.config_.textDisplayFactory;
const textDisplayer = new TextDisplayerFactory();
textDisplayer.setTextVisibility(this.textVisibility_);

const mediaSourceEngine = this.createMediaSourceEngine(
has.mediaElement, closedCaptionsParser, textDisplayer);
Expand Down Expand Up @@ -1496,6 +1498,12 @@ shaka.Player.prototype.onLoad_ = async function(has, wants) {
this.playhead_.setStartTime(adjustedTime);
}

// Now that media source and streaming engine are both initialized, we can
// update the text visibility to reflect the cached value. We purposely use
// |updateTextVisibility_| directly to go around the caching that
// |setTextTrackVisibility| provides.
await this.updateTextVisibility_(this.isTextVisible_);

// Re-filter the manifest after streams have been chosen.
this.manifest_.periods.forEach(this.filterNewPeriod_.bind(this));
// Dispatch a 'trackschanged' event now that all initial filtering is done.
Expand Down Expand Up @@ -2492,69 +2500,110 @@ shaka.Player.prototype.selectTextLanguage = function(language, role) {


/**
* @return {boolean} True if the current text track is visible.
* Check if the player will be trying to display text. While in an unloaded
* state, this will always return |false|. If content has been loaded, but there
* are no text tracks, this will return if text would be visible if there was
* text tracks.
*
* @return {boolean}
* @export
*/
shaka.Player.prototype.isTextTrackVisible = function() {
// We always cache what the app wants so that even if we don't have anything
// loaded, we know what will happen when we load content. Since we cache it,
// we can always return the cached value, but assert that we are in sync.
if (this.mediaSourceEngine_) {
const displayer = this.mediaSourceEngine_.getTextDisplayer();
goog.asserts.assert(
this.textVisibility_ == displayer.isTextVisible(),
'text visibility cache and actual are out of sync.');
// Until we have loaded content, we don't have any text visible.
if (!this.mediaSourceEngine_) {
return false;
}

return this.textVisibility_;
// Make sure our values are still in-sync.
const actual = this.mediaSourceEngine_.getTextDisplayer().isTextVisible();
const expected = this.isTextVisible_;

goog.asserts.assert(
actual == expected, 'text visibility has fallen out of sync');

// Always return the actual value so that the app has the most accurate
// information (in the case that the values come out of sync in prod).
return actual;
};


/**
* Set the visibility of the current text track, if any.
* Set the visibility of the current text track. If there is no text to
* display, the preference will be kept for when there is text to display.
*
* @param {boolean} on
* @param {boolean} isVisible
* @return {!Promise}
* @export
*/
shaka.Player.prototype.setTextTrackVisibility = async function(on) {
if (on == this.textVisibility_) {
shaka.Player.prototype.setTextTrackVisibility = async function(isVisible) {
const oldVisibilty = this.isTextVisible_;
const newVisibility = isVisible;

if (oldVisibilty == newVisibility) {
return;
}

if (this.mediaSourceEngine_) {
this.mediaSourceEngine_.getTextDisplayer().setTextVisibility(on);
}
this.isTextVisible_ = newVisibility;

// We always want to fire the event but we only do something with the text
// visibility change if we have loaded content with media source.
await this.updateTextVisibility_(isVisible);

this.textVisibility_ = on;
// We need to fire the event after we have updated everything so that
// everything will be in a stable state when the app responds to the
// event.
this.onTextTrackVisibility_();
};


/**
* Update all the components (streaming engine and media source) to change the
* visibility of the text. This should ONLY be called when content was loaded
* with media source.
*
* @param {boolean} isVisible
* @return {!Promise}
*/
shaka.Player.prototype.updateTextVisibility_ = async function(isVisible) {
// Hold of on setting the text visibility until we have all the components we
// need. This ensures that they stay in-sync.
if (!this.mediaSourceEngine_) { return; }
if (!this.streamingEngine_) { return; }

this.mediaSourceEngine_.getTextDisplayer().setTextVisibility(isVisible);

// TODO: Verify and ensure correct behaviour when the config is changed during
// playback.

// When the user wants to see captions, we stream captions. When the user
// doesn't want to see captions, we don't stream captions. The only time this
// is not true is when we are configured to always stream captions. This is to
// give greater control to app developers and content hosts over experience
// and bandwidth.

// If we always stream text, don't do anything special to StreamingEngine.
if (this.config_.streaming.alwaysStreamText) {
return;
}

// Load text stream when the user chooses to show the caption, and pause
// loading text stream when the user chooses to hide the caption.
if (!this.streamingEngine_) {
if (!isVisible) {
this.streamingEngine_.unloadTextStream();
return;
}

const StreamUtils = shaka.util.StreamUtils;
const period = this.getPresentationPeriod_();
goog.asserts.assert(period, 'Should have period with media source content');

if (on) {
let period = this.getPresentationPeriod_();
let textStreams = StreamUtils.filterStreamsByLanguageAndRole(
period.textStreams,
this.currentTextLanguage_,
this.currentTextRole_);
let stream = textStreams[0];
if (stream) {
await this.streamingEngine_.loadNewTextStream(stream);
}
} else {
this.streamingEngine_.unloadTextStream();
const streams = shaka.util.StreamUtils.filterStreamsByLanguageAndRole(
period.textStreams,
this.currentTextLanguage_,
this.currentTextRole_);

// It is possible that there are no streams to play.
if (streams.length == 0) {
return;
}

await this.streamingEngine_.loadNewTextStream(streams[0]);
};


Expand Down

0 comments on commit 1d1eabc

Please sign in to comment.