From 70cf4e4c3c865c251f771183712844e297e4c1ad Mon Sep 17 00:00:00 2001 From: meandave Date: Wed, 3 Apr 2019 15:10:16 +0000 Subject: [PATCH] Bug 1532773 - Add player controls for PictureInPicture. r=mconley Differential Revision: https://phabricator.services.mozilla.com/D25435 --HG-- extra : moz-landing-system : lando --- browser/components/BrowserGlue.jsm | 2 + toolkit/actors/PictureInPictureChild.jsm | 39 +++++++++++ .../pictureinpicture/PictureInPicture.jsm | 31 ++++++++- .../pictureinpicture/content/player.js | 23 +++++++ .../pictureinpicture/content/player.xhtml | 6 ++ toolkit/modules/ActorManagerParent.jsm | 2 + toolkit/themes/shared/jar.inc.mn | 4 ++ .../shared/pictureinpicture/close-pip.svg | 5 ++ .../themes/shared/pictureinpicture/pause.svg | 4 ++ .../themes/shared/pictureinpicture/play.svg | 4 ++ .../themes/shared/pictureinpicture/player.css | 67 +++++++++++++++++++ .../themes/shared/pictureinpicture/unpip.svg | 4 ++ 12 files changed, 190 insertions(+), 1 deletion(-) create mode 100644 toolkit/themes/shared/pictureinpicture/close-pip.svg create mode 100644 toolkit/themes/shared/pictureinpicture/pause.svg create mode 100644 toolkit/themes/shared/pictureinpicture/play.svg create mode 100644 toolkit/themes/shared/pictureinpicture/unpip.svg diff --git a/browser/components/BrowserGlue.jsm b/browser/components/BrowserGlue.jsm index aac88ae04576..d5f4413870f9 100644 --- a/browser/components/BrowserGlue.jsm +++ b/browser/components/BrowserGlue.jsm @@ -517,6 +517,8 @@ const listeners = { "FormValidation:HidePopup": ["FormValidationHandler"], "PictureInPicture:Request": ["PictureInPicture"], "PictureInPicture:Close": ["PictureInPicture"], + "PictureInPicture:Playing": ["PictureInPicture"], + "PictureInPicture:Paused": ["PictureInPicture"], "Prompt:Open": ["RemotePrompt"], "Reader:FaviconRequest": ["ReaderParent"], "Reader:UpdateReaderButton": ["ReaderParent"], diff --git a/toolkit/actors/PictureInPictureChild.jsm b/toolkit/actors/PictureInPictureChild.jsm index 059a7a9061e7..bdad99b1285f 100644 --- a/toolkit/actors/PictureInPictureChild.jsm +++ b/toolkit/actors/PictureInPictureChild.jsm @@ -16,6 +16,10 @@ var gWeakVideo = null; var gWeakPlayerContent = null; class PictureInPictureChild extends ActorChild { + static videoIsPlaying(video) { + return !!(video.currentTime > 0 && !video.paused && !video.ended && video.readyState > 2); + } + handleEvent(event) { switch (event.type) { case "MozTogglePictureInPicture": { @@ -30,6 +34,14 @@ class PictureInPictureChild extends ActorChild { this.closePictureInPicture(); break; } + case "play": { + this.mm.sendAsyncMessage("PictureInPicture:Playing"); + break; + } + case "pause": { + this.mm.sendAsyncMessage("PictureInPicture:Paused"); + break; + } } } @@ -73,6 +85,7 @@ class PictureInPictureChild extends ActorChild { gWeakVideo = Cu.getWeakReference(video); this.mm.sendAsyncMessage("PictureInPicture:Request", { + playing: PictureInPictureChild.videoIsPlaying(video), videoHeight: video.videoHeight, videoWidth: video.videoWidth, }); @@ -128,6 +141,14 @@ class PictureInPictureChild extends ActorChild { this.setupPlayer(); break; } + case "PictureInPicture:Play": { + this.play(); + break; + } + case "PictureInPicture:Pause": { + this.pause(); + break; + } } } @@ -140,6 +161,8 @@ class PictureInPictureChild extends ActorChild { let originatingWindow = originatingVideo.ownerGlobal; if (originatingWindow) { originatingWindow.addEventListener("pagehide", this); + originatingVideo.addEventListener("play", this); + originatingVideo.addEventListener("pause", this); } } @@ -153,6 +176,8 @@ class PictureInPictureChild extends ActorChild { let originatingWindow = originatingVideo.ownerGlobal; if (originatingWindow) { originatingWindow.removeEventListener("pagehide", this); + originatingVideo.removeEventListener("play", this); + originatingVideo.removeEventListener("pause", this); } } @@ -224,4 +249,18 @@ class PictureInPictureChild extends ActorChild { gWeakPlayerContent = Cu.getWeakReference(this.content); } + + play() { + let video = this.weakVideo; + if (video) { + video.play(); + } + } + + pause() { + let video = this.weakVideo; + if (video) { + video.pause(); + } + } } diff --git a/toolkit/components/pictureinpicture/PictureInPicture.jsm b/toolkit/components/pictureinpicture/PictureInPicture.jsm index 05e3647fdf64..8a4bbd2aaa1f 100644 --- a/toolkit/components/pictureinpicture/PictureInPicture.jsm +++ b/toolkit/components/pictureinpicture/PictureInPicture.jsm @@ -35,9 +35,25 @@ var PictureInPicture = { this.closePipWindow(); break; } + case "PictureInPicture:Playing": { + this.weakPipControls.classList.add("playing"); + break; + } + case "PictureInPicture:Paused": { + this.weakPipControls.classList.remove("playing"); + break; + } } }, + focusTabAndClosePip() { + let gBrowser = this.browser.ownerGlobal.gBrowser; + let tab = gBrowser.getTabForBrowser(this.browser); + gBrowser.selectedTab = tab; + this.unload(); + this.closePipWindow(); + }, + /** * Find and close any pre-existing Picture in Picture windows. */ @@ -48,7 +64,6 @@ var PictureInPicture = { if (win.closed) { continue; } - win.close(); } }, @@ -74,12 +89,26 @@ var PictureInPicture = { * the player component inside it has finished loading. */ async handlePictureInPictureRequest(browser, videoData) { + this.browser = browser; let parentWin = browser.ownerGlobal; this.closePipWindow(); let win = await this.openPipWindow(parentWin, videoData); + this.weakPipControls = win.document.getElementById("controls"); + if (videoData.playing) { + this.weakPipControls.classList.add("playing"); + } win.setupPlayer(browser, videoData); }, + /** + * unload event has been called in player.js, cleanup our preserved + * browser object. + */ + unload() { + delete this.weakPipControls; + delete this.browser; + }, + /** * Open a Picture in Picture window on the same screen as parentWin, * sized based on the information in videoData. diff --git a/toolkit/components/pictureinpicture/content/player.js b/toolkit/components/pictureinpicture/content/player.js index 5c957cbe97e8..6e3c43ec0d04 100644 --- a/toolkit/components/pictureinpicture/content/player.js +++ b/toolkit/components/pictureinpicture/content/player.js @@ -2,6 +2,8 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +const {PictureInPicture} = ChromeUtils.import("resource://gre/modules/PictureInPicture.jsm"); + async function setupPlayer(originatingBrowser, videoData) { window.windowUtils.setChromeMargin(0, 0, 0, 0); let holder = document.querySelector(".player-holder"); @@ -19,12 +21,33 @@ async function setupPlayer(originatingBrowser, videoData) { let mm = browser.frameLoader.messageManager; mm.sendAsyncMessage("PictureInPicture:SetupPlayer"); + document.getElementById("play").addEventListener("click", () => { + mm.sendAsyncMessage("PictureInPicture:Play"); + }); + + document.getElementById("pause").addEventListener("click", () => { + mm.sendAsyncMessage("PictureInPicture:Pause"); + }); + + document.getElementById("unpip").addEventListener("click", () => { + PictureInPicture.focusTabAndClosePip(); + }); + // If the content process hosting the video crashes, let's // just close the window for now. browser.addEventListener("oop-browser-crashed", () => { window.close(); }); + browser.addEventListener("unload", () => { + PictureInPicture.unload(); + }); + await window.promiseDocumentFlushed(() => {}); browser.style.MozWindowDragging = "drag"; + + let close = document.getElementById("close"); + close.addEventListener("click", () => { + window.close(); + }); } diff --git a/toolkit/components/pictureinpicture/content/player.xhtml b/toolkit/components/pictureinpicture/content/player.xhtml index 76b3f7217830..a4b18def5c18 100644 --- a/toolkit/components/pictureinpicture/content/player.xhtml +++ b/toolkit/components/pictureinpicture/content/player.xhtml @@ -21,5 +21,11 @@
+
+
+
+
+
+
diff --git a/toolkit/modules/ActorManagerParent.jsm b/toolkit/modules/ActorManagerParent.jsm index f20ae6f4ed8f..6b281b67fb97 100644 --- a/toolkit/modules/ActorManagerParent.jsm +++ b/toolkit/modules/ActorManagerParent.jsm @@ -225,6 +225,8 @@ let ACTORS = { messages: [ "PictureInPicture:SetupPlayer", + "PictureInPicture:Play", + "PictureInPicture:Pause", ], }, }, diff --git a/toolkit/themes/shared/jar.inc.mn b/toolkit/themes/shared/jar.inc.mn index 158e0e947644..5e248f8c4200 100644 --- a/toolkit/themes/shared/jar.inc.mn +++ b/toolkit/themes/shared/jar.inc.mn @@ -32,6 +32,7 @@ toolkit.jar: skin/classic/global/icons/check.svg (../../shared/icons/check.svg) skin/classic/global/icons/check-partial.svg (../../shared/icons/check-partial.svg) skin/classic/global/icons/close.svg (../../shared/icons/close.svg) + skin/classic/global/pictureinpicture/close-pip.svg (../../shared/pictureinpicture/close-pip.svg) skin/classic/global/icons/columnpicker.svg (../../shared/icons/columnpicker.svg) skin/classic/global/icons/delete.svg (../../shared/icons/delete.svg) skin/classic/global/icons/error.svg (../../shared/icons/error.svg) @@ -44,6 +45,8 @@ toolkit.jar: skin/classic/global/icons/loading@2x.png (../../shared/icons/loading@2x.png) skin/classic/global/icons/more.svg (../../shared/icons/more.svg) skin/classic/global/icons/performance.svg (../../shared/icons/performance.svg) + skin/classic/global/pictureinpicture/pause.svg (../../shared/pictureinpicture/pause.svg) + skin/classic/global/pictureinpicture/play.svg (../../shared/pictureinpicture/play.svg) skin/classic/global/icons/resizer.svg (../../shared/icons/resizer.svg) skin/classic/global/icons/shortcut.svg (../../shared/icons/shortcut.svg) skin/classic/global/icons/spinner-arrow-down.svg (../../shared/icons/spinner-arrow-down.svg) @@ -54,6 +57,7 @@ toolkit.jar: skin/classic/global/icons/arrow-dropdown-12.svg (../../shared/icons/arrow-dropdown-12.svg) skin/classic/global/icons/arrow-dropdown-16.svg (../../shared/icons/arrow-dropdown-16.svg) skin/classic/global/icons/arrow-up-12.svg (../../shared/icons/arrow-up-12.svg) + skin/classic/global/pictureinpicture/unpip.svg (../../shared/pictureinpicture/unpip.svg) skin/classic/global/icons/warning.svg (../../shared/icons/warning.svg) skin/classic/global/illustrations/about-rights.svg (../../shared/illustrations/about-rights.svg) skin/classic/global/icons/blocked.svg (../../shared/incontent-icons/blocked.svg) diff --git a/toolkit/themes/shared/pictureinpicture/close-pip.svg b/toolkit/themes/shared/pictureinpicture/close-pip.svg new file mode 100644 index 000000000000..d4bec54f8dd2 --- /dev/null +++ b/toolkit/themes/shared/pictureinpicture/close-pip.svg @@ -0,0 +1,5 @@ + + + diff --git a/toolkit/themes/shared/pictureinpicture/pause.svg b/toolkit/themes/shared/pictureinpicture/pause.svg new file mode 100644 index 000000000000..29df0834f725 --- /dev/null +++ b/toolkit/themes/shared/pictureinpicture/pause.svg @@ -0,0 +1,4 @@ + + diff --git a/toolkit/themes/shared/pictureinpicture/play.svg b/toolkit/themes/shared/pictureinpicture/play.svg new file mode 100644 index 000000000000..fd76cd1a7b9d --- /dev/null +++ b/toolkit/themes/shared/pictureinpicture/play.svg @@ -0,0 +1,4 @@ + + diff --git a/toolkit/themes/shared/pictureinpicture/player.css b/toolkit/themes/shared/pictureinpicture/player.css index 3b5dd7971f39..13d9c2eba572 100644 --- a/toolkit/themes/shared/pictureinpicture/player.css +++ b/toolkit/themes/shared/pictureinpicture/player.css @@ -2,6 +2,11 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +:root { + --btn-bg-color: rgba(50,50,50,0.55); + --close-btn-bg-color: rgb(211,216,220); +} + body { margin: 0; } @@ -14,5 +19,67 @@ body { } browser { + -moz-window-dragging: drag; flex: 1; } + +#controls { + height: 100%; + left: 0; + position: absolute; + top: 0; + width: 100%; +} + +.control-item { + -moz-window-dragging: no-drag; + background: var(--btn-bg-color); + border-radius: 4px; + bottom: 15px; + cursor: pointer; + height: 15%; + max-height: 32px; + max-width: 32px; + min-height: 16px; + min-width: 16px; + position: absolute; + width: 10%; +} + +#close { + background-color: var(--close-btn-bg-color); + background-image: url("chrome://global/skin/pictureinpicture/close-pip.svg"); + right: 10px; + top: 10px; +} + +#play { + background-image: url("chrome://global/skin/pictureinpicture/play.svg"); + display: block; + left: 55%; +} + +#pause { + background-image: url("chrome://global/skin/pictureinpicture/pause.svg"); + display: none; + left: 55%; +} + +.playing #play { + display: none; +} + +.playing #pause { + display: block; +} + +#unpip { + left: 45%; +} + +#unpip { + background-image: url("chrome://global/skin/pictureinpicture/unpip.svg"); + background-position: 60%; + background-repeat: no-repeat; + background-size: 80%; +} diff --git a/toolkit/themes/shared/pictureinpicture/unpip.svg b/toolkit/themes/shared/pictureinpicture/unpip.svg new file mode 100644 index 000000000000..3d8e564e05a9 --- /dev/null +++ b/toolkit/themes/shared/pictureinpicture/unpip.svg @@ -0,0 +1,4 @@ + +