Skip to content

Commit

Permalink
Add a util for handling media readyState related logic.
Browse files Browse the repository at this point in the history
Fixes #2555.

Change-Id: I735064952bc425bfb18d6c5681921ae1691eb348
  • Loading branch information
ismena committed May 19, 2020
1 parent 4874921 commit 26d2e98
Show file tree
Hide file tree
Showing 7 changed files with 83 additions and 71 deletions.
1 change: 1 addition & 0 deletions build/types/core
Expand Up @@ -75,6 +75,7 @@
+../../lib/util/manifest_filter.js
+../../lib/util/manifest_parser_utils.js
+../../lib/util/map_utils.js
+../../lib/util/media_ready_state_utils.js
+../../lib/util/mime_utils.js
+../../lib/util/mp4_parser.js
+../../lib/util/multi_map.js
Expand Down
14 changes: 7 additions & 7 deletions lib/media/playhead.js
Expand Up @@ -12,6 +12,7 @@ goog.require('shaka.log');
goog.require('shaka.media.GapJumpingController');
goog.require('shaka.media.TimeRangesUtils');
goog.require('shaka.media.VideoWrapper');
goog.require('shaka.util.MediaReadyState');
goog.require('shaka.util.IReleasable');
goog.require('shaka.util.Timer');

Expand Down Expand Up @@ -88,13 +89,12 @@ shaka.media.SrcEqualsPlayhead = class {
this.mediaElement_.currentTime = newTime;
}
};
if (this.mediaElement_.readyState == 0) {
this.eventManager_.listenOnce(
this.mediaElement_, 'loadeddata', onLoaded);
} else {
// It's already loaded.
onLoaded();
}

shaka.util.MediaReadyState.waitForReadyState(this.mediaElement_,
HTMLMediaElement.HAVE_CURRENT_DATA,
this.eventManager_, () => {
onLoaded();
});
}

/** @override */
Expand Down
44 changes: 13 additions & 31 deletions lib/media/video_wrapper.js
Expand Up @@ -10,6 +10,7 @@ goog.require('goog.asserts');
goog.require('shaka.log');
goog.require('shaka.util.EventManager');
goog.require('shaka.util.IReleasable');
goog.require('shaka.util.MediaReadyState');
goog.require('shaka.util.Timer');


Expand Down Expand Up @@ -52,11 +53,12 @@ shaka.media.VideoWrapper = class {
// ready. If the video element is not ready, we cannot set the time. To work
// around this, we will wait for the "loadedmetadata" event which tells us
// that the media element is now ready.
if (video.readyState > 0) {
this.setStartTime_(startTime);
} else {
this.delaySetStartTime_(startTime);
}
shaka.util.MediaReadyState.waitForReadyState(this.video_,
HTMLMediaElement.HAVE_METADATA,
this.eventManager_,
() => {
this.setStartTime_(this.startTime_);
});
}


Expand Down Expand Up @@ -96,35 +98,15 @@ shaka.media.VideoWrapper = class {
if (this.video_.readyState > 0) {
this.mover_.moveTo(time);
} else {
this.delaySetStartTime_(time);
shaka.util.MediaReadyState.waitForReadyState(this.video_,
HTMLMediaElement.HAVE_METADATA,
this.eventManager_,
() => {
this.setStartTime_(this.startTime_);
});
}
}

/**
* If the media element is not ready, we can't set |currentTime|. To work
* around this we will listen for the "loadedmetadata" event so that we can
* set the start time once the element is ready.
*
* @param {number} startTime
* @private
*/
delaySetStartTime_(startTime) {
const readyEvent = 'loadedmetadata';

// Since we are going to override what the start time should be, we need to
// save it so that |getTime| can return the most accurate start time
// possible.
this.startTime_ = startTime;

// The media element is not ready to accept changes to current time. We need
// to cache them and then execute them once the media element is ready.
this.eventManager_.unlisten(this.video_, readyEvent);

this.eventManager_.listenOnce(this.video_, readyEvent, () => {
this.setStartTime_(startTime);
});
}


/**
* Set the start time for the content. The given start time will be ignored if
Expand Down
31 changes: 15 additions & 16 deletions lib/player.js
Expand Up @@ -35,6 +35,7 @@ goog.require('shaka.util.Functional');
goog.require('shaka.util.IDestroyable');
goog.require('shaka.util.LanguageUtils');
goog.require('shaka.util.ManifestParserUtils');
goog.require('shaka.util.MediaReadyState');
goog.require('shaka.util.MimeUtils');
goog.require('shaka.util.Platform');
goog.require('shaka.util.PlayerConfiguration');
Expand Down Expand Up @@ -2049,26 +2050,24 @@ shaka.Player = class extends shaka.util.FakeEventTarget {

// This is fully loaded when we have loaded the first frame.
const fullyLoaded = new shaka.util.PublicPromise();
if (this.video_.readyState >= HTMLMediaElement.HAVE_CURRENT_DATA) {
this.setupPreferredAudioOnSrc_();
this.setupPreferredTextOnSrc_();
// Already done!
fullyLoaded.resolve();
} else if (this.video_.error) {
shaka.util.MediaReadyState.waitForReadyState(this.video_,
HTMLMediaElement.HAVE_CURRENT_DATA,
this.eventManager_,
() => {
this.setupPreferredAudioOnSrc_();
this.setupPreferredTextOnSrc_();
fullyLoaded.resolve();
});

if (this.video_.error) {
// Already failed!
fullyLoaded.reject(this.videoErrorToShakaError_());
} else {
// Wait for success or failure.
this.eventManager_.listenOnce(this.video_, 'loadeddata', () => {
this.setupPreferredAudioOnSrc_();
this.setupPreferredTextOnSrc_();
fullyLoaded.resolve();
});
this.eventManager_.listenOnce(this.video_, 'error', () => {
fullyLoaded.reject(this.videoErrorToShakaError_());
});
}

this.eventManager_.listenOnce(this.video_, 'error', () => {
fullyLoaded.reject(this.videoErrorToShakaError_());
});

// Call video.load. This is required for iOS Safari to properly fire events.
has.mediaElement.load();

Expand Down
12 changes: 5 additions & 7 deletions lib/polyfill/patchedmediakeys_apple.js
Expand Up @@ -390,13 +390,11 @@ shaka.polyfill.PatchedMediaKeysApple.MediaKeys = class {
// Some browsers require that readyState >=1 before mediaKeys can be
// set, so check this and wait for loadedmetadata if we are not in the
// correct state
if (media.readyState >= 1) {
media.webkitSetMediaKeys(this.nativeMediaKeys_);
} else {
this.eventManager_.listenOnce(media, 'loadedmetadata', () => {
media.webkitSetMediaKeys(this.nativeMediaKeys_);
});
}
shaka.util.MediaReadyState.waitForReadyState(media,
HTMLMediaElement.HAVE_METADATA,
this.eventManager_, () => {
media.webkitSetMediaKeys(this.nativeMediaKeys_);
});

return Promise.resolve();
} catch (exception) {
Expand Down
15 changes: 5 additions & 10 deletions lib/polyfill/patchedmediakeys_ms.js
Expand Up @@ -339,21 +339,16 @@ shaka.polyfill.PatchedMediaKeysMs.MediaKeys = class {
/** @type {shaka.util.EventManager.ListenerType} */
(PatchedMediaKeysMs.onMsNeedKey_));

const setMediaKeysDeferred = () => {
media.msSetMediaKeys(this.nativeMediaKeys_);
media.removeEventListener('loadedmetadata', setMediaKeysDeferred);
};

// Wrap native HTMLMediaElement.msSetMediaKeys with a Promise.
try {
// IE11/Edge requires that readyState >=1 before mediaKeys can be set,
// so check this and wait for loadedmetadata if we are not in the
// correct state
if (media.readyState >= 1) {
media.msSetMediaKeys(this.nativeMediaKeys_);
} else {
media.addEventListener('loadedmetadata', setMediaKeysDeferred);
}
shaka.util.MediaReadyState.waitForReadyState(media,
HTMLMediaElement.HAVE_METADATA,
this.eventManager_, () => {
media.msSetMediaKeys(this.nativeMediaKeys_);
});

return Promise.resolve();
} catch (exception) {
Expand Down
37 changes: 37 additions & 0 deletions lib/util/media_ready_state_utils.js
@@ -0,0 +1,37 @@
/** @license
* Copyright 2016 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

goog.provide('shaka.util.MediaReadyState');


shaka.util.MediaReadyState = class {
/*
* @param {!HTMLMediaElement} mediaElement
* @param {HTMLMediaElement.ReadyState} readyState
* @param {!function()} callback
*/
static waitForReadyState(mediaElement, readyState, eventManager, callback) {
if (readyState == HTMLMediaElement.HAVE_NOTHING ||
mediaElement.readyState >= readyState) {
callback();
} else {
const MediaReadyState = shaka.util.MediaReadyState;
const eventName =
MediaReadyState.READY_STATES_TO_EVENT_NAMES_.get(readyState);
eventManager.listenOnce(mediaElement, eventName, callback);
}
}
};

/**
* @const {!Map.<number, string>}
* @private
*/
shaka.util.MediaReadyState.READY_STATES_TO_EVENT_NAMES_ = new Map([
[HTMLMediaElement.HAVE_METADATA, 'loadedmetadata'],
[HTMLMediaElement.HAVE_CURRENT_DATA, 'loadeddata'],
[HTMLMediaElement.HAVE_FUTURE_DATA, 'canplay'],
[HTMLMediaElement.HAVE_ENOUGH_DATA, 'canplaythrough'],
]);

0 comments on commit 26d2e98

Please sign in to comment.