From 831961b37337bd9ef3ffad4a407037de1c55c139 Mon Sep 17 00:00:00 2001 From: Brandon Casey Date: Thu, 6 Dec 2018 15:15:18 -0500 Subject: [PATCH] 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. --- .../control-bar/progress-control/seek-bar.js | 10 ++-- src/js/live-tracker.js | 46 ++++++++++++++----- test/unit/live-tracker.test.js | 25 ++-------- 3 files changed, 43 insertions(+), 38 deletions(-) diff --git a/src/js/control-bar/progress-control/seek-bar.js b/src/js/control-bar/progress-control/seek-bar.js index ef41526c8..0ddc9ffd8 100644 --- a/src/js/control-bar/progress-control/seek-bar.js +++ b/src/js/control-bar/progress-control/seek-bar.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()); diff --git a/src/js/live-tracker.js b/src/js/live-tracker.js index ca9fbb143..1d150642f 100644 --- a/src/js/live-tracker.js +++ b/src/js/live-tracker.js @@ -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,6 +119,7 @@ class LiveTracker extends Component { this.pastSeekEnd_ = 0; this.lastSeekEnd_ = null; this.behindLiveEdge_ = null; + this.timeupdateSeen_ = false; this.clearInterval(this.trackingInterval_); this.trackingInterval_ = null; @@ -107,6 +127,12 @@ class LiveTracker extends Component { 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() { diff --git a/test/unit/live-tracker.test.js b/test/unit/live-tracker.test.js index af1989e6b..b1deb47a5 100644 --- a/test/unit/live-tracker.test.js +++ b/test/unit/live-tracker.test.js @@ -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,7 +154,7 @@ 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'); }); @@ -177,7 +162,7 @@ QUnit.module('LiveTracker', () => { 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'); });