mirror of
https://github.com/shaka-project/shaka-player.git
synced 2026-06-14 15:56:38 +03:00
fix: Fix rapid keyboard-based seeking
Seeking rapidly with the keyboard controls now leverages the existing logic for handling rapid mouse-based seeking. A new method (changeTo()) has been added to manage this. This also moves the keyboard control handling from keyup to keydown events, to handle holding down the key. Previously, holding down the key triggered the native key handling of the input element. When you hold down a key, you get multiple keydown events. When you release it, you get a single keyup event. So when the user holds down the left arrow, we weren't actually doing anything. Instead, the keydown handler built into the input element was handling the event. By switching to keydown events, we get those multiple events when the key is held, and we get to handle them the way we want, instead of letting the input element handle them in a way we can't control. Finally, since we already had a keydown handler on the window to manage tab navigation, this renames the two handlers to make it clear which keydown events they handle (on which target). Closes #3234 Change-Id: I862159adb238436dac7df6451a0f3e3c3f912360
This commit is contained in:
Vendored
+31
-16
@@ -970,7 +970,7 @@ shaka.ui.Controls = class extends shaka.util.FakeEventTarget {
|
||||
// Listen for key down events to detect tab and enable outline
|
||||
// for focused elements.
|
||||
this.eventManager_.listen(window, 'keydown', (e) => {
|
||||
this.onKeyDown_(/** @type {!KeyboardEvent} */(e));
|
||||
this.onWindowKeyDown_(/** @type {!KeyboardEvent} */(e));
|
||||
});
|
||||
|
||||
// Listen for click events to dismiss the settings menus.
|
||||
@@ -1010,8 +1010,8 @@ shaka.ui.Controls = class extends shaka.util.FakeEventTarget {
|
||||
this.onCastStatusChange_();
|
||||
});
|
||||
|
||||
this.eventManager_.listen(this.videoContainer_, 'keyup', (e) => {
|
||||
this.onKeyUp_(/** @type {!KeyboardEvent} */(e));
|
||||
this.eventManager_.listen(this.videoContainer_, 'keydown', (e) => {
|
||||
this.onControlsKeyDown_(/** @type {!KeyboardEvent} */(e));
|
||||
});
|
||||
|
||||
this.eventManager_.listen(
|
||||
@@ -1273,7 +1273,7 @@ shaka.ui.Controls = class extends shaka.util.FakeEventTarget {
|
||||
* @param {!KeyboardEvent} event
|
||||
* @private
|
||||
*/
|
||||
onKeyUp_(event) {
|
||||
onControlsKeyDown_(event) {
|
||||
const activeElement = document.activeElement;
|
||||
const isVolumeBar = activeElement && activeElement.classList ?
|
||||
activeElement.classList.contains('shaka-volume-bar') : false;
|
||||
@@ -1295,24 +1295,28 @@ shaka.ui.Controls = class extends shaka.util.FakeEventTarget {
|
||||
case 'ArrowLeft':
|
||||
// If it's not focused on the volume bar, move the seek time backward
|
||||
// for 5 sec. Otherwise, the volume will be adjusted automatically.
|
||||
if (!isVolumeBar) {
|
||||
this.seek_(this.video_.currentTime - 5, event);
|
||||
if (this.seekBar_ && !isVolumeBar) {
|
||||
this.seek_(this.seekBar_.getValue() - 5);
|
||||
}
|
||||
break;
|
||||
case 'ArrowRight':
|
||||
// If it's not focused on the volume bar, move the seek time forward
|
||||
// for 5 sec. Otherwise, the volume will be adjusted automatically.
|
||||
if (!isVolumeBar) {
|
||||
this.seek_(this.video_.currentTime + 5, event);
|
||||
if (this.seekBar_ && !isVolumeBar) {
|
||||
this.seek_(this.seekBar_.getValue() + 5);
|
||||
}
|
||||
break;
|
||||
// Jump to the beginning of the video's seek range.
|
||||
case 'Home':
|
||||
this.seek_(this.player_.seekRange().start, event);
|
||||
if (this.seekBar_) {
|
||||
this.seek_(this.player_.seekRange().start);
|
||||
}
|
||||
break;
|
||||
// Jump to the end of the video's seek range.
|
||||
case 'End':
|
||||
this.seek_(this.player_.seekRange().end, event);
|
||||
if (this.seekBar_) {
|
||||
this.seek_(this.player_.seekRange().end);
|
||||
}
|
||||
break;
|
||||
// Pause or play by pressing space on the seek bar.
|
||||
case ' ':
|
||||
@@ -1352,12 +1356,16 @@ shaka.ui.Controls = class extends shaka.util.FakeEventTarget {
|
||||
|
||||
/**
|
||||
* Update the video's current time based on the keyboard operations.
|
||||
*
|
||||
* @param {number} currentTime
|
||||
* @param {!Event} event
|
||||
* @private
|
||||
*/
|
||||
seek_(currentTime, event) {
|
||||
this.video_.currentTime = currentTime;
|
||||
seek_(currentTime) {
|
||||
goog.asserts.assert(
|
||||
this.seekBar_, 'Caller of seek_ must check for seekBar_ first!');
|
||||
|
||||
this.seekBar_.changeTo(currentTime);
|
||||
|
||||
if (this.isOpaque()) {
|
||||
// Only update the time and seek range if it's visible.
|
||||
this.updateTimeAndSeekRange_();
|
||||
@@ -1397,7 +1405,7 @@ shaka.ui.Controls = class extends shaka.util.FakeEventTarget {
|
||||
* @param {!KeyboardEvent} event
|
||||
* @private
|
||||
*/
|
||||
onKeyDown_(event) {
|
||||
onWindowKeyDown_(event) {
|
||||
// Add the key to the pressed keys set when it's pressed.
|
||||
this.pressedKeys_.add(event.key);
|
||||
|
||||
@@ -1429,14 +1437,21 @@ shaka.ui.Controls = class extends shaka.util.FakeEventTarget {
|
||||
* menu (pressing Tab key to go forward, or pressing Shift + Tab keys to go
|
||||
* backward), make sure it's focused only on the elements of the overflow
|
||||
* panel.
|
||||
* This is called by onKeyDown_() function, when there's a settings overflow
|
||||
* menu open, and the Tab key / Shift+Tab keys are pressed.
|
||||
*
|
||||
* This is called by onWindowKeyDown_() function, when there's a settings
|
||||
* overflow menu open, and the Tab key / Shift+Tab keys are pressed.
|
||||
*
|
||||
* @param {!Event} event
|
||||
* @private
|
||||
*/
|
||||
keepFocusInMenu_(event) {
|
||||
const openSettingsMenus = this.menus_.filter(
|
||||
(menu) => !menu.classList.contains('shaka-hidden'));
|
||||
if (!openSettingsMenus.length) {
|
||||
// For example, this occurs when you hit escape to close the menu.
|
||||
return;
|
||||
}
|
||||
|
||||
const settingsMenu = openSettingsMenus[0];
|
||||
if (settingsMenu.childNodes.length) {
|
||||
// Get the first and the last displaying child element from the overflow
|
||||
|
||||
+4
-1
@@ -300,6 +300,9 @@ shaka.extern.IUIRangeElement = class {
|
||||
|
||||
/** @param {number} value */
|
||||
setValue(value) {}
|
||||
|
||||
/** @param {number} value */
|
||||
changeTo(value) {}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -369,7 +372,7 @@ shaka.extern.IUISettingsMenu = class {
|
||||
* SeekBar, you should consider using shaka.ui.RangeElement or
|
||||
* shaka.ui.SeekBar as your base class.
|
||||
*
|
||||
* @extends {shaka.extern.IUIElement}
|
||||
* @extends {shaka.extern.IUIRangeElement}
|
||||
* @interface
|
||||
* @exportDoc
|
||||
*/
|
||||
|
||||
@@ -9,6 +9,7 @@ goog.provide('shaka.ui.RangeElement');
|
||||
|
||||
goog.require('shaka.ui.Element');
|
||||
goog.require('shaka.util.Dom');
|
||||
goog.require('shaka.util.Timer');
|
||||
goog.requireType('shaka.ui.Controls');
|
||||
|
||||
|
||||
@@ -51,6 +52,11 @@ shaka.ui.RangeElement = class extends shaka.ui.Element {
|
||||
this.bar =
|
||||
/** @type {!HTMLInputElement} */ (document.createElement('input'));
|
||||
|
||||
/** @private {shaka.util.Timer} */
|
||||
this.endFakeChangeTimer_ = new shaka.util.Timer(() => {
|
||||
this.onChangeEnd();
|
||||
});
|
||||
|
||||
this.bar.classList.add('shaka-range-element');
|
||||
this.bar.classList.add(...barClassNames);
|
||||
this.bar.type = 'range';
|
||||
@@ -105,6 +111,16 @@ shaka.ui.RangeElement = class extends shaka.ui.Element {
|
||||
});
|
||||
}
|
||||
|
||||
/** @override */
|
||||
release() {
|
||||
if (this.endFakeChangeTimer_) {
|
||||
this.endFakeChangeTimer_.stop();
|
||||
this.endFakeChangeTimer_ = null;
|
||||
}
|
||||
|
||||
super.release();
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
* @export
|
||||
@@ -138,6 +154,35 @@ shaka.ui.RangeElement = class extends shaka.ui.Element {
|
||||
*/
|
||||
onChangeEnd() {}
|
||||
|
||||
/**
|
||||
* Called to implement keyboard-based changes, where this is no clear "end".
|
||||
* This will simulate events like onChangeStart(), onChange(), and
|
||||
* onChangeEnd() as appropriate.
|
||||
*
|
||||
* @override
|
||||
* @export
|
||||
*/
|
||||
changeTo(value) {
|
||||
if (!this.isChanging_) {
|
||||
this.isChanging_ = true;
|
||||
this.onChangeStart();
|
||||
}
|
||||
|
||||
const min = parseFloat(this.bar.min);
|
||||
const max = parseFloat(this.bar.max);
|
||||
|
||||
if (value > max) {
|
||||
this.bar.value = max;
|
||||
} else if (value < min) {
|
||||
this.bar.value = min;
|
||||
} else {
|
||||
this.bar.value = value;
|
||||
}
|
||||
this.onChange();
|
||||
|
||||
this.endFakeChangeTimer_.tickAfter(/* seconds= */ 0.5);
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
* @export
|
||||
|
||||
Reference in New Issue
Block a user