mirror of
https://github.com/videojs/video.js.git
synced 2024-12-04 10:34:51 +02:00
fix(liveui): tweaks to prevent jitter (#6405)
This commit is contained in:
parent
1dd06a26c0
commit
668c7f44d6
@ -261,6 +261,11 @@ class SeekBar extends Slider {
|
||||
newTime = newTime - 0.1;
|
||||
}
|
||||
} else {
|
||||
|
||||
if (distance >= 0.99) {
|
||||
liveTracker.seekToLiveEdge();
|
||||
return;
|
||||
}
|
||||
const seekableStart = liveTracker.seekableStart();
|
||||
const seekableEnd = liveTracker.liveCurrentTime();
|
||||
|
||||
|
@ -57,7 +57,7 @@ class SeekToLive extends Button {
|
||||
* Update the state of this button if we are at the live edge
|
||||
* or not
|
||||
*/
|
||||
updateLiveEdgeStatus(e) {
|
||||
updateLiveEdgeStatus() {
|
||||
// default to live edge
|
||||
if (!this.player_.liveTracker || this.player_.liveTracker.atLiveEdge()) {
|
||||
this.setAttribute('aria-disabled', true);
|
||||
|
@ -1,19 +1,43 @@
|
||||
import Component from './component.js';
|
||||
import median from './utils/median.js';
|
||||
import mergeOptions from './utils/merge-options.js';
|
||||
import document from 'global/document';
|
||||
import * as browser from './utils/browser.js';
|
||||
import window from 'global/window';
|
||||
import * as Fn from './utils/fn.js';
|
||||
|
||||
const defaults = {
|
||||
// Number of seconds of live window (seekableEnd - seekableStart) that
|
||||
// a video needs to have before the liveui will be shown.
|
||||
trackingThreshold: 30
|
||||
trackingThreshold: 30,
|
||||
liveTolerance: 15
|
||||
};
|
||||
|
||||
/* track when we are at the live edge, and other helpers for live playback */
|
||||
/*
|
||||
track when we are at the live edge, and other helpers for live playback */
|
||||
|
||||
/**
|
||||
* A class for checking live current time and determining when the player
|
||||
* is at or behind the live edge.
|
||||
*/
|
||||
class LiveTracker extends Component {
|
||||
|
||||
/**
|
||||
* Creates an instance of this class.
|
||||
*
|
||||
* @param {Player} player
|
||||
* The `Player` that this class should be attached to.
|
||||
*
|
||||
* @param {Object} [options]
|
||||
* The key/value store of player options.
|
||||
*
|
||||
* @param {number} [options.trackingThreshold=30]
|
||||
* Number of seconds of live window (seekableEnd - seekableStart) that
|
||||
* media needs to have before the liveui will be shown.
|
||||
*
|
||||
* @param {number} [options.liveTolerance=15]
|
||||
* Number of seconds behind live that we have to be
|
||||
* before we will be considered non-live. Note that this will only
|
||||
* be used when playing at the live edge. This allows large seekable end
|
||||
* changes to not effect wether we are live or not.
|
||||
*/
|
||||
constructor(player, options) {
|
||||
// LiveTracker does not need an element
|
||||
const options_ = mergeOptions(defaults, options, {createEl: false});
|
||||
@ -32,6 +56,9 @@ class LiveTracker extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* toggle tracking based on document visiblility
|
||||
*/
|
||||
handleVisibilityChange() {
|
||||
if (this.player_.duration() !== Infinity) {
|
||||
return;
|
||||
@ -44,29 +71,11 @@ class LiveTracker extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
isBehind_() {
|
||||
// don't report that we are behind until a timeupdate has been seen
|
||||
if (!this.timeupdateSeen_) {
|
||||
return false;
|
||||
}
|
||||
const liveCurrentTime = this.liveCurrentTime();
|
||||
const currentTime = this.player_.currentTime();
|
||||
|
||||
// the live edge window is the amount of seconds away from live
|
||||
// that a player can be, but still be considered live.
|
||||
// we add 0.07 because the live tracking happens every 30ms
|
||||
// and we want some wiggle room for short segment live playback
|
||||
const liveEdgeWindow = (this.seekableIncrement_ * 2) + 0.07;
|
||||
|
||||
// on Android liveCurrentTime can bee Infinity, because seekableEnd
|
||||
// can be Infinity, so we handle that case.
|
||||
return liveCurrentTime !== Infinity && (liveCurrentTime - liveEdgeWindow) >= currentTime;
|
||||
}
|
||||
|
||||
// all the functionality for tracking when seek end changes
|
||||
// and for tracking how far past seek end we should be
|
||||
/**
|
||||
* all the functionality for tracking when seek end changes
|
||||
* and for tracking how far past seek end we should be
|
||||
*/
|
||||
trackLive_() {
|
||||
this.pastSeekEnd_ = this.pastSeekEnd_;
|
||||
const seekable = this.player_.seekable();
|
||||
|
||||
// skip undefined seekable
|
||||
@ -74,37 +83,33 @@ class LiveTracker extends Component {
|
||||
return;
|
||||
}
|
||||
|
||||
const newSeekEnd = this.seekableEnd();
|
||||
const newTime = Number(window.performance.now().toFixed(4));
|
||||
const deltaTime = this.lastTime_ === -1 ? 0 : (newTime - this.lastTime_) / 1000;
|
||||
|
||||
// we can only tell if we are behind live, when seekable changes
|
||||
// once we detect that seekable has changed we check the new seek
|
||||
// end against current time, with a fudge value of half a second.
|
||||
if (newSeekEnd !== this.lastSeekEnd_) {
|
||||
if (this.lastSeekEnd_) {
|
||||
// we try to get the best fit value for the seeking increment
|
||||
// variable from the last 12 values.
|
||||
this.seekableIncrementList_ = this.seekableIncrementList_.slice(-11);
|
||||
this.seekableIncrementList_.push(Math.abs(newSeekEnd - this.lastSeekEnd_));
|
||||
if (this.seekableIncrementList_.length > 3) {
|
||||
this.seekableIncrement_ = median(this.seekableIncrementList_);
|
||||
}
|
||||
}
|
||||
this.lastTime_ = newTime;
|
||||
|
||||
this.pastSeekEnd_ = 0;
|
||||
this.lastSeekEnd_ = newSeekEnd;
|
||||
this.trigger('seekableendchange');
|
||||
this.pastSeekEnd_ = this.pastSeekEnd() + deltaTime;
|
||||
|
||||
const liveCurrentTime = this.liveCurrentTime();
|
||||
const currentTime = this.player_.currentTime();
|
||||
|
||||
// we are behind live if any are true
|
||||
// 1. the player is paused
|
||||
// 2. the user seeked to a location 2 seconds away from live
|
||||
// 3. the difference between live and current time is greater
|
||||
// liveTolerance which defaults to 15s
|
||||
let isBehind = this.player_.paused() || this.seekedBehindLive_ ||
|
||||
Math.abs(liveCurrentTime - currentTime) > this.options_.liveTolerance;
|
||||
|
||||
// we cannot be behind if
|
||||
// 1. until we have not seen a timeupdate yet
|
||||
// 2. liveCurrentTime is Infinity, which happens on Android
|
||||
if (!this.timeupdateSeen_ || liveCurrentTime === Infinity) {
|
||||
isBehind = false;
|
||||
}
|
||||
|
||||
// we should reset pastSeekEnd when the value
|
||||
// is much higher than seeking increment.
|
||||
if (this.pastSeekEnd() > this.seekableIncrement_ * 1.5) {
|
||||
this.pastSeekEnd_ = 0;
|
||||
} else {
|
||||
this.pastSeekEnd_ = this.pastSeekEnd() + 0.03;
|
||||
}
|
||||
|
||||
if (this.isBehind_() !== this.behindLiveEdge()) {
|
||||
this.behindLiveEdge_ = this.isBehind_();
|
||||
if (isBehind !== this.behindLiveEdge_) {
|
||||
this.behindLiveEdge_ = isBehind;
|
||||
this.trigger('liveedgechange');
|
||||
}
|
||||
}
|
||||
@ -143,21 +148,41 @@ class LiveTracker extends Component {
|
||||
this.trackingInterval_ = this.setInterval(this.trackLive_, Fn.UPDATE_REFRESH_INTERVAL);
|
||||
this.trackLive_();
|
||||
|
||||
this.on(this.player_, 'play', this.trackLive_);
|
||||
this.on(this.player_, 'pause', this.trackLive_);
|
||||
this.on(this.player_, ['play', 'pause'], this.trackLive_);
|
||||
|
||||
// this is to prevent showing that we are not live
|
||||
// before a video starts to play
|
||||
if (!this.timeupdateSeen_) {
|
||||
this.one(this.player_, 'play', this.handlePlay);
|
||||
this.handleTimeupdate = () => {
|
||||
this.timeupdateSeen_ = true;
|
||||
this.handleTimeupdate = null;
|
||||
};
|
||||
this.one(this.player_, 'timeupdate', this.handleTimeupdate);
|
||||
this.one(this.player_, 'timeupdate', this.handleFirstTimeupdate);
|
||||
} else {
|
||||
this.on(this.player_, 'seeked', this.handleSeeked);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* handle the first timeupdate on the player if it wasn't already playing
|
||||
* when live tracker started tracking.
|
||||
*/
|
||||
handleFirstTimeupdate() {
|
||||
this.timeupdateSeen_ = true;
|
||||
this.on(this.player_, 'seeked', this.handleSeeked);
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep track of what time a seek starts, and listen for seeked
|
||||
* to find where a seek ends.
|
||||
*/
|
||||
handleSeeked() {
|
||||
const timeDiff = Math.abs(this.liveCurrentTime() - this.player_.currentTime());
|
||||
|
||||
this.seekedBehindLive_ = this.skipNextSeeked_ ? false : timeDiff > 2;
|
||||
this.skipNextSeeked_ = false;
|
||||
this.trackLive_();
|
||||
}
|
||||
|
||||
/**
|
||||
* handle the first play on the player, and make sure that we seek
|
||||
* right to the live edge.
|
||||
*/
|
||||
handlePlay() {
|
||||
this.one(this.player_, 'timeupdate', this.seekToLiveEdge);
|
||||
}
|
||||
@ -167,24 +192,22 @@ class LiveTracker extends Component {
|
||||
* their initial value.
|
||||
*/
|
||||
reset_() {
|
||||
this.lastTime_ = -1;
|
||||
this.pastSeekEnd_ = 0;
|
||||
this.lastSeekEnd_ = null;
|
||||
this.behindLiveEdge_ = null;
|
||||
this.lastSeekEnd_ = -1;
|
||||
this.behindLiveEdge_ = true;
|
||||
this.timeupdateSeen_ = false;
|
||||
this.seekedBehindLive_ = false;
|
||||
this.skipNextSeeked_ = false;
|
||||
|
||||
this.clearInterval(this.trackingInterval_);
|
||||
this.trackingInterval_ = null;
|
||||
this.seekableIncrement_ = 12;
|
||||
this.seekableIncrementList_ = [];
|
||||
|
||||
this.off(this.player_, 'play', this.trackLive_);
|
||||
this.off(this.player_, 'pause', this.trackLive_);
|
||||
this.off(this.player_, ['play', 'pause'], this.trackLive_);
|
||||
this.off(this.player_, 'seeked', this.handleSeeked);
|
||||
this.off(this.player_, 'play', this.handlePlay);
|
||||
this.off(this.player_, 'timeupdate', this.handleFirstTimeupdate);
|
||||
this.off(this.player_, 'timeupdate', this.seekToLiveEdge);
|
||||
if (this.handleTimeupdate) {
|
||||
this.off(this.player_, 'timeupdate', this.handleTimeupdate);
|
||||
this.handleTimeupdate = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -195,11 +218,15 @@ class LiveTracker extends Component {
|
||||
return;
|
||||
}
|
||||
this.reset_();
|
||||
this.trigger('liveedgechange');
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper to get the player seekable end
|
||||
* so that we don't have to null check everywhere
|
||||
*
|
||||
* @return {number}
|
||||
* The furthest seekable end or Infinity.
|
||||
*/
|
||||
seekableEnd() {
|
||||
const seekable = this.player_.seekable();
|
||||
@ -218,6 +245,9 @@ class LiveTracker extends Component {
|
||||
/**
|
||||
* A helper to get the player seekable start
|
||||
* so that we don't have to null check everywhere
|
||||
*
|
||||
* @return {number}
|
||||
* The earliest seekable start or 0.
|
||||
*/
|
||||
seekableStart() {
|
||||
const seekable = this.player_.seekable();
|
||||
@ -234,7 +264,13 @@ class LiveTracker extends Component {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the live time window
|
||||
* Get the live time window aka
|
||||
* the amount of time between seekable start and
|
||||
* live current time.
|
||||
*
|
||||
* @return {number}
|
||||
* The amount of seconds that are seekable in
|
||||
* the live video.
|
||||
*/
|
||||
liveWindow() {
|
||||
const liveCurrentTime = this.liveCurrentTime();
|
||||
@ -249,6 +285,9 @@ class LiveTracker extends Component {
|
||||
/**
|
||||
* Determines if the player is live, only checks if this component
|
||||
* is tracking live playback or not
|
||||
*
|
||||
* @return {boolean}
|
||||
* Wether liveTracker is tracking
|
||||
*/
|
||||
isLive() {
|
||||
return this.isTracking();
|
||||
@ -257,6 +296,9 @@ class LiveTracker extends Component {
|
||||
/**
|
||||
* Determines if currentTime is at the live edge and won't fall behind
|
||||
* on each seekableendchange
|
||||
*
|
||||
* @return {boolean}
|
||||
* Wether playback is at the live edge
|
||||
*/
|
||||
atLiveEdge() {
|
||||
return !this.behindLiveEdge();
|
||||
@ -264,26 +306,45 @@ class LiveTracker extends Component {
|
||||
|
||||
/**
|
||||
* get what we expect the live current time to be
|
||||
*
|
||||
* @return {number}
|
||||
* The expected live current time
|
||||
*/
|
||||
liveCurrentTime() {
|
||||
return this.pastSeekEnd() + this.seekableEnd();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns how far past seek end we expect current time to be
|
||||
* The number of seconds that have occured after seekable end
|
||||
* changed. This will be reset to 0 once seekable end changes.
|
||||
*
|
||||
* @return {number}
|
||||
* Seconds past the current seekable end
|
||||
*/
|
||||
pastSeekEnd() {
|
||||
const seekableEnd = this.seekableEnd();
|
||||
|
||||
if (this.lastSeekEnd_ !== -1 && seekableEnd !== this.lastSeekEnd_) {
|
||||
this.pastSeekEnd_ = 0;
|
||||
}
|
||||
this.lastSeekEnd_ = seekableEnd;
|
||||
return this.pastSeekEnd_;
|
||||
}
|
||||
|
||||
/**
|
||||
* If we are currently behind the live edge, aka currentTime will be
|
||||
* behind on a seekableendchange
|
||||
*
|
||||
* @return {boolean}
|
||||
* If we are behind the live edge
|
||||
*/
|
||||
behindLiveEdge() {
|
||||
return this.behindLiveEdge_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wether live tracker is currently tracking or not.
|
||||
*/
|
||||
isTracking() {
|
||||
return typeof this.trackingInterval_ === 'number';
|
||||
}
|
||||
@ -292,18 +353,21 @@ class LiveTracker extends Component {
|
||||
* Seek to the live edge if we are behind the live edge
|
||||
*/
|
||||
seekToLiveEdge() {
|
||||
this.seekedBehindLive_ = false;
|
||||
if (this.atLiveEdge()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// skipNextSeeked_
|
||||
this.skipNextSeeked_ = true;
|
||||
this.player_.currentTime(this.liveCurrentTime());
|
||||
|
||||
if (this.player_.paused()) {
|
||||
this.player_.play();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispose of liveTracker
|
||||
*/
|
||||
dispose() {
|
||||
this.off(document, 'visibilitychange', this.handleVisibilityChange);
|
||||
this.stopTracking();
|
||||
super.dispose();
|
||||
}
|
||||
|
@ -1,17 +0,0 @@
|
||||
/**
|
||||
* Computes the median of an array.
|
||||
*
|
||||
* @param {number[]} arr
|
||||
* Input array of numbers.
|
||||
*
|
||||
* @return {number}
|
||||
* Median value.
|
||||
*/
|
||||
const median = arr => {
|
||||
const mid = Math.floor(arr.length / 2);
|
||||
const sortedList = [...arr].sort((a, b) => a - b);
|
||||
|
||||
return arr.length % 2 !== 0 ? sortedList[mid] : (sortedList[mid - 1] + sortedList[mid]) / 2;
|
||||
};
|
||||
|
||||
export default median;
|
@ -69,7 +69,7 @@ QUnit.module('LiveTracker', () => {
|
||||
}
|
||||
});
|
||||
|
||||
QUnit.test('starts/stop with durationchange and triggers liveedgechange', function(assert) {
|
||||
QUnit.test('with durationchange and triggers liveedgechange', function(assert) {
|
||||
let liveEdgeChange = 0;
|
||||
|
||||
this.liveTracker.on('liveedgechange', () => {
|
||||
@ -83,10 +83,11 @@ QUnit.module('LiveTracker', () => {
|
||||
|
||||
this.player.duration(5);
|
||||
assert.notOk(this.liveTracker.isTracking(), 'not started');
|
||||
assert.equal(liveEdgeChange, 2, 'liveedgechange fired when we stop tracking');
|
||||
|
||||
this.player.duration(Infinity);
|
||||
assert.ok(this.liveTracker.isTracking(), 'started');
|
||||
assert.equal(liveEdgeChange, 2, 'liveedgechange fired again');
|
||||
assert.equal(liveEdgeChange, 3, 'liveedgechange fired again');
|
||||
});
|
||||
|
||||
QUnit.module('tracking', {
|
||||
@ -97,14 +98,10 @@ QUnit.module('LiveTracker', () => {
|
||||
|
||||
this.liveTracker = this.player.liveTracker;
|
||||
this.player.seekable = () => createTimeRanges(0, 30);
|
||||
this.player.paused = () => false;
|
||||
this.player.duration(Infinity);
|
||||
|
||||
this.liveEdgeChanges = 0;
|
||||
this.seekableEndChanges = 0;
|
||||
|
||||
this.liveTracker.on('seekableendchange', () => {
|
||||
this.seekableEndChanges++;
|
||||
});
|
||||
|
||||
this.liveTracker.on('liveedgechange', () => {
|
||||
this.liveEdgeChanges++;
|
||||
@ -117,8 +114,7 @@ QUnit.module('LiveTracker', () => {
|
||||
});
|
||||
|
||||
QUnit.test('Triggers liveedgechange when we fall behind and catch up', function(assert) {
|
||||
|
||||
this.liveTracker.seekableIncrement_ = 6;
|
||||
this.liveTracker.options_.liveTolerance = 6;
|
||||
this.player.seekable = () => createTimeRanges(0, 20);
|
||||
this.player.trigger('timeupdate');
|
||||
this.player.currentTime = () => 14;
|
||||
@ -129,31 +125,63 @@ QUnit.module('LiveTracker', () => {
|
||||
assert.equal(this.liveEdgeChanges, 1, 'should have one live edge change');
|
||||
assert.ok(this.liveTracker.behindLiveEdge(), 'behind live edge');
|
||||
|
||||
this.player.currentTime = () => 20;
|
||||
this.player.currentTime = () => this.liveTracker.liveCurrentTime();
|
||||
this.clock.tick(30);
|
||||
|
||||
assert.equal(this.liveEdgeChanges, 2, 'should have two live edge change');
|
||||
assert.ok(this.liveTracker.atLiveEdge(), 'at live edge');
|
||||
});
|
||||
|
||||
QUnit.test('is behindLiveEdge when paused', function(assert) {
|
||||
this.liveTracker.options_.liveTolerance = 6;
|
||||
this.player.seekable = () => createTimeRanges(0, 20);
|
||||
this.player.trigger('timeupdate');
|
||||
this.player.currentTime = () => 20;
|
||||
this.clock.tick(1000);
|
||||
|
||||
assert.ok(this.liveTracker.atLiveEdge(), 'at live edge');
|
||||
|
||||
this.player.paused = () => true;
|
||||
this.player.trigger('pause');
|
||||
|
||||
assert.equal(this.liveEdgeChanges, 1, 'should have one live edge change');
|
||||
assert.ok(this.liveTracker.behindLiveEdge(), 'behindLiveEdge live edge');
|
||||
});
|
||||
|
||||
QUnit.test('is behindLiveEdge when seeking backwards', function(assert) {
|
||||
this.liveTracker.options_.liveTolerance = 6;
|
||||
this.player.seekable = () => createTimeRanges(0, 20);
|
||||
this.player.trigger('timeupdate');
|
||||
this.player.currentTime = () => 20;
|
||||
this.clock.tick(1000);
|
||||
|
||||
assert.ok(this.liveTracker.atLiveEdge(), 'at live edge');
|
||||
|
||||
this.player.currentTime = () => 17;
|
||||
this.player.trigger('seeked');
|
||||
|
||||
assert.equal(this.liveEdgeChanges, 1, 'should have one live edge change');
|
||||
assert.ok(this.liveTracker.behindLiveEdge(), 'behindLiveEdge live edge');
|
||||
});
|
||||
|
||||
QUnit.test('pastSeekEnd should update when seekable changes', function(assert) {
|
||||
assert.strictEqual(this.liveTracker.liveCurrentTime(), 30.03, 'liveCurrentTime is now 30');
|
||||
assert.strictEqual(this.liveTracker.liveCurrentTime(), 30, 'liveCurrentTime is now 30');
|
||||
this.clock.tick(2010);
|
||||
|
||||
assert.ok(this.liveTracker.pastSeekEnd() > 2, 'pastSeekEnd should be over 2s');
|
||||
|
||||
this.player.seekable = () => createTimeRanges(30, 61);
|
||||
this.player.seekable = () => createTimeRanges(0, 2);
|
||||
|
||||
this.clock.tick(30);
|
||||
assert.strictEqual(this.liveTracker.pastSeekEnd(), 0.03, 'pastSeekEnd start at 0.03 again');
|
||||
assert.strictEqual(this.liveTracker.liveCurrentTime(), 61.03, 'liveCurrentTime is now 2.03');
|
||||
assert.strictEqual(this.liveTracker.liveCurrentTime(), 2.03, 'liveCurrentTime is now 2.03');
|
||||
});
|
||||
|
||||
QUnit.test('seeks to live edge on seekableendchange', function(assert) {
|
||||
QUnit.test('can seek to live edge', function(assert) {
|
||||
this.player.trigger('timeupdate');
|
||||
|
||||
this.player.seekable = () => createTimeRanges(0, 6);
|
||||
this.liveTracker.seekableIncrement_ = 2;
|
||||
this.liveTracker.options_.liveTolerance = 2;
|
||||
let currentTime = 0;
|
||||
|
||||
this.player.currentTime = (ct) => {
|
||||
@ -172,7 +200,7 @@ QUnit.module('LiveTracker', () => {
|
||||
assert.equal(currentTime, this.liveTracker.liveCurrentTime(), 'should have seeked to liveCurrentTime');
|
||||
});
|
||||
|
||||
QUnit.test('does not seek to to live edge if at live edge', function(assert) {
|
||||
QUnit.test('does not seek to live edge if at live edge', function(assert) {
|
||||
let pauseCalls = 0;
|
||||
let playCalls = 0;
|
||||
let currentTime = 0;
|
||||
@ -196,13 +224,12 @@ QUnit.module('LiveTracker', () => {
|
||||
assert.notOk(this.player.hasClass('vjs-waiting'), 'player should not be waiting');
|
||||
assert.equal(pauseCalls, 0, 'should not have called pause');
|
||||
|
||||
this.clock.tick(2000);
|
||||
this.clock.tick(2010);
|
||||
|
||||
assert.ok(this.liveTracker.pastSeekEnd() > 2, 'pastSeekEnd should be over 2s');
|
||||
this.player.seekable = () => createTimeRanges(0, 2);
|
||||
|
||||
this.clock.tick(30);
|
||||
assert.equal(this.seekableEndChanges, 1, 'should be one seek end change');
|
||||
assert.equal(currentTime, 0, 'should not have seeked to seekableEnd');
|
||||
assert.equal(playCalls, 0, 'should not have called play');
|
||||
});
|
||||
@ -215,7 +242,6 @@ QUnit.module('LiveTracker', () => {
|
||||
assert.ok(this.liveTracker.seekToLiveEdge.notCalled, 'seekToLiveEdge was not called yet');
|
||||
|
||||
this.player.trigger('play');
|
||||
this.player.trigger('playing');
|
||||
assert.ok(this.liveTracker.seekToLiveEdge.notCalled, 'seekToLiveEdge was not called yet');
|
||||
|
||||
this.player.trigger('timeupdate');
|
||||
|
@ -4,98 +4,89 @@ import sinon from 'sinon';
|
||||
import computedStyle from '../../src/js/utils/computed-style.js';
|
||||
import { createTimeRange } from '../../src/js/utils/time-ranges.js';
|
||||
|
||||
QUnit.module('SeekToLive', () => {
|
||||
QUnit.module('live with liveui', {
|
||||
beforeEach() {
|
||||
this.clock = sinon.useFakeTimers();
|
||||
QUnit.module('SeekToLive', {
|
||||
beforeEach() {
|
||||
this.clock = sinon.useFakeTimers();
|
||||
this.player = TestHelpers.makePlayer();
|
||||
this.seekToLive = this.player.controlBar.seekToLive;
|
||||
this.getComputedDisplay = () => {
|
||||
return computedStyle(this.seekToLive.el(), 'display');
|
||||
};
|
||||
|
||||
this.player = TestHelpers.makePlayer({liveui: true});
|
||||
this.seekToLive = this.player.controlBar.seekToLive;
|
||||
this.mockLiveui = () => {
|
||||
this.player.paused = () => false;
|
||||
this.player.hasStarted = () => true;
|
||||
this.player.options_.liveui = true;
|
||||
this.player.seekable = () => createTimeRange(0, 45);
|
||||
|
||||
this.getComputedDisplay = () => {
|
||||
return computedStyle(this.seekToLive.el(), 'display');
|
||||
};
|
||||
|
||||
// mock live state
|
||||
this.player.currentTime = () => this.player.liveTracker.liveCurrentTime();
|
||||
this.player.duration(Infinity);
|
||||
},
|
||||
afterEach() {
|
||||
this.player.dispose();
|
||||
this.clock.restore();
|
||||
}
|
||||
});
|
||||
|
||||
QUnit.test('at live edge if liveTracker says we are', function(assert) {
|
||||
this.player.liveTracker.behindLiveEdge = () => false;
|
||||
this.player.liveTracker.trigger('liveedgechange');
|
||||
|
||||
assert.ok(this.seekToLive.hasClass('vjs-at-live-edge'), 'has at live edge class');
|
||||
});
|
||||
|
||||
QUnit.test('behind live edge if liveTracker says we are', function(assert) {
|
||||
this.player.liveTracker.behindLiveEdge = () => true;
|
||||
this.player.liveTracker.trigger('liveedgechange');
|
||||
|
||||
assert.notOk(this.seekToLive.hasClass('vjs-at-live-edge'), 'does not have live edge class');
|
||||
});
|
||||
|
||||
QUnit.test('switch to non live', function(assert) {
|
||||
this.player.duration(4);
|
||||
this.player.trigger('durationchange');
|
||||
assert.equal(this.getComputedDisplay(), 'none', 'is hidden');
|
||||
});
|
||||
|
||||
QUnit.module('live without liveui', {
|
||||
beforeEach() {
|
||||
this.clock = sinon.useFakeTimers();
|
||||
|
||||
this.player = TestHelpers.makePlayer();
|
||||
this.seekToLive = this.player.controlBar.seekToLive;
|
||||
this.player.seekable = () => createTimeRange(0, 45);
|
||||
|
||||
this.getComputedDisplay = () => {
|
||||
return computedStyle(this.seekToLive.el(), 'display');
|
||||
};
|
||||
|
||||
// mock live state
|
||||
this.player.duration(Infinity);
|
||||
},
|
||||
afterEach() {
|
||||
this.player.dispose();
|
||||
this.clock.restore();
|
||||
}
|
||||
});
|
||||
|
||||
QUnit.test('should be hidden', function(assert) {
|
||||
assert.equal(this.getComputedDisplay(), 'none', 'is hidden');
|
||||
});
|
||||
|
||||
QUnit.module('not live', {
|
||||
beforeEach() {
|
||||
this.player = TestHelpers.makePlayer({liveui: true});
|
||||
this.seekToLive = this.player.controlBar.seekToLive;
|
||||
|
||||
this.getComputedDisplay = () => {
|
||||
return computedStyle(this.seekToLive.el(), 'display');
|
||||
};
|
||||
},
|
||||
afterEach() {
|
||||
this.player.dispose();
|
||||
}
|
||||
});
|
||||
|
||||
QUnit.test('should not show or track', function(assert) {
|
||||
assert.equal(this.getComputedDisplay(), 'none', 'is hidden');
|
||||
});
|
||||
|
||||
QUnit.test('switch to live', function(assert) {
|
||||
assert.equal(this.getComputedDisplay(), 'none', 'is hidden');
|
||||
|
||||
this.player.seekable = () => createTimeRange(0, 45);
|
||||
this.player.duration(Infinity);
|
||||
this.player.trigger('durationchange');
|
||||
|
||||
assert.notEqual(this.getComputedDisplay(), 'none', 'is not hidden');
|
||||
});
|
||||
};
|
||||
},
|
||||
afterEach() {
|
||||
this.player.dispose();
|
||||
this.clock.restore();
|
||||
}
|
||||
});
|
||||
|
||||
QUnit.test('liveui enabled, can switch between at and behind live edge ', function(assert) {
|
||||
this.mockLiveui();
|
||||
|
||||
assert.notEqual(this.getComputedDisplay(), 'none', 'is not hidden');
|
||||
assert.ok(this.seekToLive.hasClass('vjs-at-live-edge'), 'has at live edge class');
|
||||
|
||||
this.player.currentTime = () => 0;
|
||||
this.player.seekable = () => createTimeRange(0, 38);
|
||||
this.clock.tick(30);
|
||||
|
||||
assert.notOk(this.seekToLive.hasClass('vjs-at-live-edge'), 'does not have at live edge class');
|
||||
});
|
||||
|
||||
QUnit.test('liveui enabled can show/hide on durationchange', function(assert) {
|
||||
// start out non-live
|
||||
assert.equal(this.getComputedDisplay(), 'none', 'is hidden');
|
||||
assert.notOk(this.seekToLive.hasClass('vjs-at-live-edge'), 'does not have at live edge class');
|
||||
|
||||
// switch to live
|
||||
this.mockLiveui();
|
||||
|
||||
assert.notEqual(this.getComputedDisplay(), 'none', 'is not hidden');
|
||||
assert.ok(this.seekToLive.hasClass('vjs-at-live-edge'), 'has at live edge class');
|
||||
|
||||
// switch to non-live
|
||||
this.player.duration(20);
|
||||
|
||||
assert.equal(this.getComputedDisplay(), 'none', 'is hidden');
|
||||
assert.notOk(this.seekToLive.hasClass('vjs-at-live-edge'), 'does not have at live edge class');
|
||||
|
||||
// back to live again.
|
||||
this.mockLiveui();
|
||||
|
||||
assert.notEqual(this.getComputedDisplay(), 'none', 'is not hidden');
|
||||
assert.ok(this.seekToLive.hasClass('vjs-at-live-edge'), 'has at live edge class');
|
||||
});
|
||||
|
||||
QUnit.test('liveui disabled live window is never shown', function(assert) {
|
||||
assert.equal(this.getComputedDisplay(), 'none', 'is hidden');
|
||||
assert.notOk(this.seekToLive.hasClass('vjs-at-live-edge'), 'does not have at live edge class');
|
||||
|
||||
this.player.paused = () => false;
|
||||
this.player.hasStarted = () => true;
|
||||
this.player.currentTime = () => this.player.liveTracker.liveCurrentTime();
|
||||
|
||||
// liveui false, seekable range is good though
|
||||
this.player.options_.liveui = false;
|
||||
this.player.duration(Infinity);
|
||||
|
||||
assert.equal(this.getComputedDisplay(), 'none', 'is hidden');
|
||||
assert.notOk(this.seekToLive.hasClass('vjs-at-live-edge'), 'does not have at live edge class');
|
||||
|
||||
this.player.duration(10);
|
||||
|
||||
// liveui false
|
||||
this.player.options_.liveui = false;
|
||||
this.player.seekable = () => createTimeRange(0, 29);
|
||||
this.player.duration(Infinity);
|
||||
|
||||
assert.equal(this.getComputedDisplay(), 'none', 'is hidden');
|
||||
assert.notOk(this.seekToLive.hasClass('vjs-at-live-edge'), 'does not have at live edge class');
|
||||
});
|
||||
|
@ -1,24 +0,0 @@
|
||||
/* eslint-env qunit */
|
||||
import median from '../../../src/js/utils/median.js';
|
||||
|
||||
QUnit.module('median');
|
||||
|
||||
QUnit.test('should compute the median', function(assert) {
|
||||
let data;
|
||||
let expected;
|
||||
|
||||
data = [2, 4, 5, 3, 8, 2];
|
||||
expected = 3.5;
|
||||
|
||||
assert.equal(median(data), expected, 'median is correct for the first not sorted array');
|
||||
|
||||
data = [2, 4, 5, 3, 8, 2, 9];
|
||||
expected = 4;
|
||||
|
||||
assert.equal(median(data), expected, 'median is correct for the second not sorted array');
|
||||
|
||||
data = [2, 2, 3, 4, 5, 8, 9];
|
||||
expected = 4;
|
||||
|
||||
assert.equal(median(data), expected, 'median is correct for the sorted array');
|
||||
});
|
Loading…
Reference in New Issue
Block a user