Merge pull request #17 from yokuze/fix_no_preload_no_resume_play_16

Refs #16 Fix video does not resume playing after quality change
This commit is contained in:
Ethan Smith 2018-01-09 16:20:37 -05:00 committed by GitHub
commit 94a9648b03
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 118 additions and 7 deletions

View File

@ -3,6 +3,12 @@
In general, this project adheres to [Semantic Versioning](http://semver.org/). If for some
reason we do something that's not strictly semantic, it will be clearly called out below.
## 1.1.2
* Fixed a bug where selecting a quality menu item while a video was playing did not resume
playback after the source changed. Affected Safari and players whose `preload` attribute
was `none` (8feeafb Fixes #16).
## 1.1.1
* Reference underscore as a dependency since we depend on it (931d8a4 See #12)

View File

@ -26,7 +26,6 @@
"homepage": "https://github.com/silvermine/videojs-quality-selector#readme",
"devDependencies": {
"autoprefixer": "7.1.1",
"class.extend": "0.9.2",
"coveralls": "2.13.1",
"eslint": "4.0.0",
"eslint-config-silvermine": "1.3.0",
@ -51,6 +50,7 @@
"video.js": "6.x"
},
"dependencies": {
"class.extend": "0.9.2",
"underscore": "1.8.3"
}
}

View File

@ -3,7 +3,8 @@
var _ = require('underscore'),
events = require('./events'),
qualitySelectorFactory = require('./components/QualitySelector'),
sourceInterceptorFactory = require('./middleware/SourceInterceptor');
sourceInterceptorFactory = require('./middleware/SourceInterceptor'),
SafeSeek = require('./util/SafeSeek');
module.exports = function(videojs) {
videojs = videojs || window.videojs;
@ -12,8 +13,7 @@ module.exports = function(videojs) {
sourceInterceptorFactory(videojs);
videojs.hook('setup', function(player) {
// Add handler to switch sources when the user requests a change
player.on(events.QUALITY_REQUESTED, function(event, newSource) {
function changeQuality(event, newSource) {
var sources = player.currentSources(),
currentTime = player.currentTime(),
isPaused = player.paused(),
@ -29,15 +29,36 @@ module.exports = function(videojs) {
// following updates the original object in `sources`.
selectedSource.selected = true;
if (player._qualitySelectorSafeSeek) {
player._qualitySelectorSafeSeek.onQualitySelectionChange();
}
player.src(sources);
player.one('loadeddata', function() {
player.currentTime(currentTime);
player.ready(function() {
if (!player._qualitySelectorSafeSeek || player._qualitySelectorSafeSeek.hasFinished()) {
// Either we don't have a pending seek action or the one that we have is no
// longer applicable. This block must be within a `player.ready` callback
// because the call to `player.src` above is asynchronous, and so not
// having it within this `ready` callback would cause the SourceInterceptor
// to execute after this block instead of before.
//
// We save the `currentTime` within the SafeSeek instance because if
// multiple QUALITY_REQUESTED events are received before the SafeSeek
// operation finishes, the player's `currentTime` will be `0` if the
// player's `src` is updated but the player's `currentTime` has not yet
// been set by the SafeSeek operation.
player._qualitySelectorSafeSeek = new SafeSeek(player, currentTime);
}
if (!isPaused) {
player.play();
}
});
});
}
// Add handler to switch sources when the user requests a change
player.on(events.QUALITY_REQUESTED, changeQuality);
});
};

View File

@ -13,6 +13,10 @@ module.exports = function(videojs) {
var sources = player.currentSources(),
userSelectedSource, chosenSource;
if (player._qualitySelectorSafeSeek) {
player._qualitySelectorSafeSeek.onPlayerSourcesChange();
}
// There are generally two source options, the one that videojs
// auto-selects and the one that a "user" of this plugin has
// supplied via the `selected` property. `selected` can come from

80
src/js/util/SafeSeek.js Normal file
View File

@ -0,0 +1,80 @@
'use strict';
var Class = require('class.extend');
module.exports = Class.extend({
init: function(player, seekToTime) {
this._player = player;
this._seekToTime = seekToTime;
this._hasFinished = false;
this._keepThisInstanceWhenPlayerSourcesChange = false;
this._seekWhenSafe();
},
_seekWhenSafe: function() {
var HAVE_FUTURE_DATA = 3;
// `readyState` in Video.js is the same as the HTML5 Media element's `readyState`
// property.
//
// `readyState` is an enum of 5 values (0-4), each of which represent a state of
// readiness to play. The meaning of the values range from HAVE_NOTHING (0), meaning
// no data is available to HAVE_ENOUGH_DATA (4), meaning all data is loaded and the
// video can be played all the way through.
//
// In order to seek successfully, the `readyState` must be at least HAVE_FUTURE_DATA
// (3).
//
// @see http://docs.videojs.com/player#readyState
// @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/readyState
// @see https://dev.w3.org/html5/spec-preview/media-elements.html#seek-the-media-controller
if (this._player.readyState() < HAVE_FUTURE_DATA) {
this._seekFn = this._seek.bind(this);
// The `canplay` event means that the `readyState` is at least HAVE_FUTURE_DATA.
this._player.one('canplay', this._seekFn);
} else {
this._seek();
}
},
onPlayerSourcesChange: function() {
if (this._keepThisInstanceWhenPlayerSourcesChange) {
// By setting this to `false`, we know that if the player sources change again
// the change did not originate from a quality selection change, the new sources
// are likely different from the old sources, and so this pending seek no longer
// applies.
this._keepThisInstanceWhenPlayerSourcesChange = false;
} else {
this.cancel();
}
},
onQualitySelectionChange: function() {
// `onPlayerSourcesChange` will cancel this pending seek unless we tell it not to.
// We need to reuse this same pending seek instance because when the player is
// paused, the `preload` attribute is set to `none`, and the user selects one
// quality option and then another, the player cannot seek until the player has
// enough data to do so (and the `canplay` event is fired) and thus on the second
// selection the player's `currentTime()` is `0` and when the video plays we would
// seek to `0` instead of the correct time.
if (!this.hasFinished()) {
this._keepThisInstanceWhenPlayerSourcesChange = true;
}
},
_seek: function() {
this._player.currentTime(this._seekToTime);
this._keepThisInstanceWhenPlayerSourcesChange = false;
this._hasFinished = true;
},
hasFinished: function() {
return this._hasFinished;
},
cancel: function() {
this._player.off('canplay', this._seekFn);
this._keepThisInstanceWhenPlayerSourcesChange = false;
this._hasFinished = true;
},
});