diff --git a/src/js/control-bar/progress-control/seek-bar.js b/src/js/control-bar/progress-control/seek-bar.js index fe2398757..dcc23a090 100644 --- a/src/js/control-bar/progress-control/seek-bar.js +++ b/src/js/control-bar/progress-control/seek-bar.js @@ -186,6 +186,21 @@ class SeekBar extends Slider { return percent; } + /** + * Prevent liveThreshold from causing seeks to seem like they + * are not happening from a user perspective. + * + * @param {number} ct + * current time to seek to + */ + userSeek_(ct) { + if (this.player_.liveTracker && this.player_.liveTracker.isLive()) { + this.player_.liveTracker.nextSeekedFromUser(); + } + + this.player_.currentTime(ct); + } + /** * Get the value of current time but allows for smooth scrubbing, * when player can't keep up. @@ -303,7 +318,7 @@ class SeekBar extends Slider { } // Set new time (tell player to seek to new time) - this.player_.currentTime(newTime); + this.userSeek_(newTime); } enable() { @@ -366,14 +381,14 @@ class SeekBar extends Slider { * Move more quickly fast forward for keyboard-only users */ stepForward() { - this.player_.currentTime(this.player_.currentTime() + STEP_SECONDS); + this.userSeek_(this.player_.currentTime() + STEP_SECONDS); } /** * Move more quickly rewind for keyboard-only users */ stepBack() { - this.player_.currentTime(this.player_.currentTime() - STEP_SECONDS); + this.userSeek_(this.player_.currentTime() - STEP_SECONDS); } /** @@ -409,6 +424,8 @@ class SeekBar extends Slider { * @listens keydown */ handleKeyDown(event) { + const liveTracker = this.player_.liveTracker; + if (keycode.isEventKey(event, 'Space') || keycode.isEventKey(event, 'Enter')) { event.preventDefault(); event.stopPropagation(); @@ -416,25 +433,33 @@ class SeekBar extends Slider { } else if (keycode.isEventKey(event, 'Home')) { event.preventDefault(); event.stopPropagation(); - this.player_.currentTime(0); + this.userSeek_(0); } else if (keycode.isEventKey(event, 'End')) { event.preventDefault(); event.stopPropagation(); - this.player_.currentTime(this.player_.duration()); + if (liveTracker && liveTracker.isLive()) { + this.userSeek_(liveTracker.liveCurrentTime()); + } else { + this.userSeek_(this.player_.duration()); + } } else if (/^[0-9]$/.test(keycode(event))) { event.preventDefault(); event.stopPropagation(); const gotoFraction = (keycode.codes[keycode(event)] - keycode.codes['0']) * 10.0 / 100.0; - this.player_.currentTime(this.player_.duration() * gotoFraction); + if (liveTracker && liveTracker.isLive()) { + this.userSeek_(liveTracker.seekableStart() + (liveTracker.liveWindow() * gotoFraction)); + } else { + this.userSeek_(this.player_.duration() * gotoFraction); + } } else if (keycode.isEventKey(event, 'PgDn')) { event.preventDefault(); event.stopPropagation(); - this.player_.currentTime(this.player_.currentTime() - (STEP_SECONDS * PAGE_KEY_MULTIPLIER)); + this.userSeek_(this.player_.currentTime() - (STEP_SECONDS * PAGE_KEY_MULTIPLIER)); } else if (keycode.isEventKey(event, 'PgUp')) { event.preventDefault(); event.stopPropagation(); - this.player_.currentTime(this.player_.currentTime() + (STEP_SECONDS * PAGE_KEY_MULTIPLIER)); + this.userSeek_(this.player_.currentTime() + (STEP_SECONDS * PAGE_KEY_MULTIPLIER)); } else { // Pass keydown handling up for unsupported keys super.handleKeyDown(event); diff --git a/src/js/live-tracker.js b/src/js/live-tracker.js index e67c2c057..685b750a6 100644 --- a/src/js/live-tracker.js +++ b/src/js/live-tracker.js @@ -191,8 +191,8 @@ class LiveTracker extends Component { handleSeeked() { const timeDiff = Math.abs(this.liveCurrentTime() - this.player_.currentTime()); - this.seekedBehindLive_ = this.skipNextSeeked_ ? false : timeDiff > 2; - this.skipNextSeeked_ = false; + this.seekedBehindLive_ = this.nextSeekedFromUser_ && timeDiff > 2; + this.nextSeekedFromUser_ = false; this.trackLive_(); } @@ -215,7 +215,7 @@ class LiveTracker extends Component { this.behindLiveEdge_ = true; this.timeupdateSeen_ = false; this.seekedBehindLive_ = false; - this.skipNextSeeked_ = false; + this.nextSeekedFromUser_ = false; this.clearInterval(this.trackingInterval_); this.trackingInterval_ = null; @@ -227,6 +227,15 @@ class LiveTracker extends Component { this.off(this.player_, 'timeupdate', this.seekToLiveEdge_); } + /** + * The next seeked event is from the user. Meaning that any seek + * > 2s behind live will be considered behind live for real and + * liveTolerance will be ignored. + */ + nextSeekedFromUser() { + this.nextSeekedFromUser_ = true; + } + /** * stop tracking live playback */ @@ -375,8 +384,7 @@ class LiveTracker extends Component { if (this.atLiveEdge()) { return; } - // skipNextSeeked_ - this.skipNextSeeked_ = true; + this.nextSeekedFromUser_ = false; this.player_.currentTime(this.liveCurrentTime()); } diff --git a/test/unit/live-tracker.test.js b/test/unit/live-tracker.test.js index 5fe56233a..9ff236dc1 100644 --- a/test/unit/live-tracker.test.js +++ b/test/unit/live-tracker.test.js @@ -148,7 +148,7 @@ QUnit.module('LiveTracker', () => { assert.ok(this.liveTracker.behindLiveEdge(), 'behindLiveEdge live edge'); }); - QUnit.test('is behindLiveEdge when seeking backwards', function(assert) { + QUnit.test('is behindLiveEdge when seeking behind liveTolerance with API', function(assert) { this.liveTracker.options_.liveTolerance = 6; this.player.seekable = () => createTimeRanges(0, 20); this.player.trigger('timeupdate'); @@ -157,6 +157,23 @@ QUnit.module('LiveTracker', () => { assert.ok(this.liveTracker.atLiveEdge(), 'at live edge'); + this.player.currentTime = () => 14; + 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('is behindLiveEdge when seeking >2s behind with ui', 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.liveTracker.nextSeekedFromUser(); this.player.currentTime = () => 17; this.player.trigger('seeked');