Bug 1602841 - Implement keystroke handling for PiP window for video controls. r=mconley

This implements keystroke handling behind a pref "media.videocontrols.picture-in-picture.keyboard-controls.enabled".
This patch handles all the keystrokes for video controls, which include play, pause,
volume decrease and increase, mute, unmute, seek forward and backward for 15 seconds
or by 10% of the max video duration, seek to beginning and seek to end.
This reuses the key handler logic from https://searchfox.org/mozilla-central/rev/cfd1cc461f1efe0d66c2fdc17c024a203d5a2fd8/toolkit/content/widgets/videocontrols.js#1687-1810.

Differential Revision: https://phabricator.services.mozilla.com/D60631

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Gabriel Luong 2020-01-26 06:21:23 +00:00
Родитель fb42ac6003
Коммит aa684c0c76
3 изменённых файлов: 164 добавлений и 1 удалений

Просмотреть файл

@ -1497,6 +1497,8 @@ pref("media.autoplay.default", 1); // 0=Allowed, 1=Blocked, 5=All Blocked
pref("media.videocontrols.picture-in-picture.enabled", true);
pref("media.videocontrols.picture-in-picture.video-toggle.enabled", true);
// Enable keyboard controls for Picture-in-Picture.
pref("media.videocontrols.picture-in-picture.keyboard-controls.enabled", true);
// Show the audio toggle for Picture-in-Picture.
#ifdef NIGHTLY_BUILD

Просмотреть файл

@ -1108,6 +1108,10 @@ class PictureInPictureChild extends JSWindowActorChild {
this.unmute();
break;
}
case "PictureInPicture:KeyDown": {
this.keyDown(message.data);
break;
}
case "PictureInPicture:KeyToggle": {
this.keyToggle();
break;
@ -1247,6 +1251,138 @@ class PictureInPictureChild extends JSWindowActorChild {
}
}
/**
* This reuses the keyHandler logic in the VideoControlsWidget
* https://searchfox.org/mozilla-central/rev/cfd1cc461f1efe0d66c2fdc17c024a203d5a2fd8/toolkit/content/widgets/videocontrols.js#1687-1810.
* There are future plans to eventually combine the two implementations.
*/
keyDown({ altKey, shiftKey, metaKey, ctrlKey, keyCode }) {
let video = this.getWeakVideo();
if (!video) {
return;
}
var keystroke = "";
if (altKey) {
keystroke += "alt-";
}
if (shiftKey) {
keystroke += "shift-";
}
if (this.contentWindow.navigator.platform.startsWith("Mac")) {
if (metaKey) {
keystroke += "accel-";
}
if (ctrlKey) {
keystroke += "control-";
}
} else {
if (metaKey) {
keystroke += "meta-";
}
if (ctrlKey) {
keystroke += "accel-";
}
}
switch (keyCode) {
case this.contentWindow.KeyEvent.DOM_VK_UP:
keystroke += "upArrow";
break;
case this.contentWindow.KeyEvent.DOM_VK_DOWN:
keystroke += "downArrow";
break;
case this.contentWindow.KeyEvent.DOM_VK_LEFT:
keystroke += "leftArrow";
break;
case this.contentWindow.KeyEvent.DOM_VK_RIGHT:
keystroke += "rightArrow";
break;
case this.contentWindow.KeyEvent.DOM_VK_HOME:
keystroke += "home";
break;
case this.contentWindow.KeyEvent.DOM_VK_END:
keystroke += "end";
break;
case this.contentWindow.KeyEvent.DOM_VK_SPACE:
keystroke += "space";
break;
}
const isVideoStreaming = video.duration == +Infinity;
var oldval, newval;
try {
switch (keystroke) {
case "space" /* Toggle Play / Pause */:
if (video.paused || video.ended) {
video.play();
} else {
video.pause();
}
break;
case "downArrow" /* Volume decrease */:
oldval = video.volume;
video.volume = oldval < 0.1 ? 0 : oldval - 0.1;
video.muted = false;
break;
case "upArrow" /* Volume increase */:
oldval = video.volume;
video.volume = oldval > 0.9 ? 1 : oldval + 0.1;
video.muted = false;
break;
case "accel-downArrow" /* Mute */:
video.muted = true;
break;
case "accel-upArrow" /* Unmute */:
video.muted = false;
break;
case "leftArrow": /* Seek back 15 seconds */
case "accel-leftArrow" /* Seek back 10% */:
if (isVideoStreaming) {
return;
}
oldval = video.currentTime;
if (keystroke == "leftArrow") {
newval = oldval - 15;
} else {
newval = oldval - video.duration / 10;
}
video.currentTime = newval >= 0 ? newval : 0;
break;
case "rightArrow": /* Seek forward 15 seconds */
case "accel-rightArrow" /* Seek forward 10% */:
if (isVideoStreaming) {
return;
}
oldval = video.currentTime;
var maxtime = video.duration;
if (keystroke == "rightArrow") {
newval = oldval + 15;
} else {
newval = oldval + maxtime / 10;
}
video.currentTime = newval <= maxtime ? newval : maxtime;
break;
case "home" /* Seek to beginning */:
if (!isVideoStreaming) {
video.currentTime = 0;
}
break;
case "end" /* Seek to end */:
if (!isVideoStreaming && video.currentTime != video.duration) {
video.currentTime = video.duration;
}
break;
default:
}
} catch (e) {
/* ignore any exception from setting video.currentTime */
}
}
/**
* The keyboard was used to attempt to open Picture-in-Picture. In this case,
* find the focused window, and open Picture-in-Picture for the first

Просмотреть файл

@ -15,6 +15,8 @@ const { AppConstants } = ChromeUtils.import(
const AUDIO_TOGGLE_ENABLED_PREF =
"media.videocontrols.picture-in-picture.audio-toggle.enabled";
const KEYBOARD_CONTROLS_ENABLED_PREF =
"media.videocontrols.picture-in-picture.keyboard-controls.enabled";
// Time to fade the Picture-in-Picture video controls after first opening.
const CONTROLS_FADE_TIMEOUT_MS = 3000;
@ -172,9 +174,22 @@ let Player = {
case "keydown": {
if (event.keyCode == KeyEvent.DOM_VK_TAB) {
this.controls.setAttribute("keying", true);
} else if (event.keyCode == KeyEvent.DOM_VK_ESCAPE) {
} else if (
event.keyCode == KeyEvent.DOM_VK_ESCAPE &&
this.controls.hasAttribute("keying")
) {
this.controls.removeAttribute("keying");
} else if (
Services.prefs.getBoolPref(KEYBOARD_CONTROLS_ENABLED_PREF, false) &&
!this.controls.hasAttribute("keying") &&
(event.keyCode != KeyEvent.DOM_VK_SPACE || !event.target.id)
) {
// Pressing "space" fires a "keydown" event which can also trigger a control
// button's "click" event. Handle the "keydown" event only when the event did
// not originate from a control button and it is not a "space" keypress.
this.onKeyDown(event);
}
break;
}
@ -235,6 +250,16 @@ let Player = {
}
},
onKeyDown(event) {
this.actor.sendAsyncMessage("PictureInPicture:KeyDown", {
altKey: event.altKey,
shiftKey: event.shiftKey,
metaKey: event.metaKey,
ctrlKey: event.ctrlKey,
keyCode: event.keyCode,
});
},
onMouseOut(event) {
if (
window.screenX != this.lastScreenX ||