mirror of
https://github.com/iv-org/videojs-quality-selector.git
synced 2024-11-23 05:39:44 +00:00
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:
commit
94a9648b03
@ -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)
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -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
80
src/js/util/SafeSeek.js
Normal 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;
|
||||
},
|
||||
});
|
Loading…
Reference in New Issue
Block a user