Permalink
Browse files

fix(liveui): seek to live should be immediate and other tweaks (#5650)

- Make sure that we seek to live playback on the first timeupdate
- Do not report that we are not live before playback has started (a timeupdate has been seen)
- Prevent negative seekable increments
- We can seek past the seekable value in the video element, so we use that to seek to live, rather than waiting for a seekable end change.
  • Loading branch information...
BrandonOCasey authored and gkatsev committed Dec 6, 2018
1 parent 62f9e78 commit 831961b37337bd9ef3ffad4a407037de1c55c139
Showing with 43 additions and 38 deletions.
  1. +3 −7 src/js/control-bar/progress-control/seek-bar.js
  2. +35 −11 src/js/live-tracker.js
  3. +5 −20 test/unit/live-tracker.test.js
@@ -52,6 +52,7 @@ class SeekBar extends Slider {
this.on(this.player_, 'timeupdate', this.update);
this.on(this.player_, 'ended', this.handleEnded);
this.on(this.player_, 'durationchange', this.update);
this.on(this.player_.liveTracker, 'liveedgechange', this.update);

// when playing, let's ensure we smoothly update the play progress bar
// via an interval
@@ -109,7 +110,7 @@ class SeekBar extends Slider {
let duration = this.player_.duration();

if (liveTracker.isLive()) {
duration = this.player_.liveTracker.seekableEnd();
duration = this.player_.liveTracker.liveCurrentTime();
}

if (liveTracker.seekableEnd() === Infinity) {
@@ -255,12 +256,7 @@ class SeekBar extends Slider {
}
} else {
const seekableStart = liveTracker.seekableStart();
const seekableEnd = liveTracker.seekableEnd();

if (distance === 1) {
liveTracker.seekToLiveEdge();
return;
}
const seekableEnd = liveTracker.liveCurrentTime();

newTime = seekableStart + (distance * liveTracker.liveWindow());

@@ -16,6 +16,10 @@ 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();
const seekableIncrement = this.seekableIncrement_;
@@ -49,7 +53,7 @@ class LiveTracker extends Component {
// end against current time, with a fudge value of half a second.
if (newSeekEnd !== this.lastSeekEnd_) {
if (this.lastSeekEnd_) {
this.seekableIncrement_ = newSeekEnd - this.lastSeekEnd_;
this.seekableIncrement_ = Math.abs(newSeekEnd - this.lastSeekEnd_);
}

this.pastSeekEnd_ = 0;
@@ -90,6 +94,21 @@ class LiveTracker extends Component {

this.on(this.player_, 'play', this.trackLive_);
this.on(this.player_, 'pause', this.trackLive_);
this.one(this.player_, 'play', this.handlePlay);

// this is to prevent showing that we are not live
// before a video starts to play
if (!this.timeupdateSeen_) {
this.handleTimeupdate = () => {
this.timeupdateSeen_ = true;
this.handleTimeupdate = null;
};
this.one(this.player_, 'timeupdate', this.handleTimeupdate);
}
}

handlePlay() {
this.one(this.player_, 'timeupdate', this.seekToLiveEdge);
}

/**
@@ -100,13 +119,20 @@ class LiveTracker extends Component {
this.pastSeekEnd_ = 0;
this.lastSeekEnd_ = null;
this.behindLiveEdge_ = null;
this.timeupdateSeen_ = false;

this.clearInterval(this.trackingInterval_);
this.trackingInterval_ = null;
this.seekableIncrement_ = 12;

this.off(this.player_, 'play', this.trackLive_);
this.off(this.player_, 'pause', this.trackLive_);
this.off(this.player_, 'play', this.handlePlay);
this.off(this.player_, 'timeupdate', this.seekToLiveEdge);
if (this.handleTimeupdate) {
this.off(this.player_, 'timeupdate', this.handleTimeupdate);
this.handleTimeupdate = null;
}
}

/**
@@ -159,13 +185,13 @@ class LiveTracker extends Component {
* Get the live time window
*/
liveWindow() {
const seekableEnd = this.seekableEnd();
const liveCurrentTime = this.liveCurrentTime();

if (seekableEnd === Infinity) {
if (liveCurrentTime === Infinity) {
return Infinity;
}

return seekableEnd - this.seekableStart();
return liveCurrentTime - this.seekableStart();
}

/**
@@ -218,13 +244,11 @@ class LiveTracker extends Component {
return;
}

this.player().pause();
this.player().addClass('vjs-waiting');
this.one('seekableendchange', () => {
this.player().removeClass('vjs-waiting');
this.player().currentTime(this.seekableEnd());
this.player().play();
});
this.player_.currentTime(this.liveCurrentTime());

if (this.player_.paused()) {
this.player_.play();
}
}

dispose() {
@@ -66,6 +66,7 @@ QUnit.module('LiveTracker', () => {
});

QUnit.test('Triggers liveedgechange when we fall behind and catch up', function(assert) {
this.player.trigger('timeupdate');
this.player.currentTime = () => 0;
this.clock.tick(20000);

@@ -94,10 +95,9 @@ QUnit.module('LiveTracker', () => {
});

QUnit.test('seeks to live edge on seekableendchange', function(assert) {
this.player.trigger('timeupdate');

this.liveTracker.seekableIncrement_ = 2;
let pauseCalls = 0;
let playCalls = 0;
let currentTime = 0;

this.player.currentTime = (ct) => {
@@ -107,28 +107,13 @@ QUnit.module('LiveTracker', () => {
return 0;
};

this.player.play = () => {
playCalls++;
};

this.player.pause = () => {
pauseCalls++;
};
this.clock.tick(3000);

assert.ok(this.liveTracker.pastSeekEnd() > 2, 'pastSeekEnd should be over 2s');

this.liveTracker.seekToLiveEdge();

assert.ok(this.player.hasClass('vjs-waiting'), 'player should be waiting');
assert.equal(pauseCalls, 1, 'should be paused');
this.player.seekable = () => createTimeRanges(0, 2);

this.clock.tick(30);
assert.equal(this.seekableEndChanges, 1, 'should be one seek end change');
assert.equal(currentTime, 2, 'should have seeked to seekableEnd');
assert.equal(playCalls, 1, 'should be playing');
assert.notOk(this.player.hasClass('vjs-waiting'), 'player should not be waiting');
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) {
@@ -169,15 +154,15 @@ QUnit.module('LiveTracker', () => {
QUnit.test('single seekable, helpers should be correct', function(assert) {
// simple
this.player.seekable = () => createTimeRanges(10, 50);
assert.strictEqual(this.liveTracker.liveWindow(), 40, 'liveWindow is 40s');
assert.strictEqual(this.liveTracker.liveWindow(), 40.03, 'liveWindow is 40s');
assert.strictEqual(this.liveTracker.seekableStart(), 10, 'seekableStart is 10s');
assert.strictEqual(this.liveTracker.seekableEnd(), 50, 'seekableEnd is 50s');
});

QUnit.test('multiple seekables, helpers should be correct', function(assert) {
// multiple
this.player.seekable = () => createTimeRanges([[0, 1], [2, 3], [4, 5]]);
assert.strictEqual(this.liveTracker.liveWindow(), 5, 'liveWindow is 5s');
assert.strictEqual(this.liveTracker.liveWindow(), 5.03, 'liveWindow is 5s');
assert.strictEqual(this.liveTracker.seekableStart(), 0, 'seekableStart is 0s');
assert.strictEqual(this.liveTracker.seekableEnd(), 5, 'seekableEnd is 5s');
});

0 comments on commit 831961b

Please sign in to comment.