summaryrefslogtreecommitdiffstats
path: root/browser/extensions/pictureinpicture/video-wrappers
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--browser/extensions/pictureinpicture/video-wrappers/airmozilla.js63
-rw-r--r--browser/extensions/pictureinpicture/video-wrappers/arte.js35
-rw-r--r--browser/extensions/pictureinpicture/video-wrappers/bbc.js31
-rw-r--r--browser/extensions/pictureinpicture/video-wrappers/cbc.js30
-rw-r--r--browser/extensions/pictureinpicture/video-wrappers/dailymotion.js42
-rw-r--r--browser/extensions/pictureinpicture/video-wrappers/disneyplus.js70
-rw-r--r--browser/extensions/pictureinpicture/video-wrappers/edx.js33
-rw-r--r--browser/extensions/pictureinpicture/video-wrappers/hbomax.js48
-rw-r--r--browser/extensions/pictureinpicture/video-wrappers/hotstar.js39
-rw-r--r--browser/extensions/pictureinpicture/video-wrappers/hulu.js71
-rw-r--r--browser/extensions/pictureinpicture/video-wrappers/mock-wrapper.js34
-rw-r--r--browser/extensions/pictureinpicture/video-wrappers/netflix.js94
-rw-r--r--browser/extensions/pictureinpicture/video-wrappers/nytimes.js37
-rw-r--r--browser/extensions/pictureinpicture/video-wrappers/piped.js41
-rw-r--r--browser/extensions/pictureinpicture/video-wrappers/primeVideo.js103
-rw-r--r--browser/extensions/pictureinpicture/video-wrappers/radiocanada.js36
-rw-r--r--browser/extensions/pictureinpicture/video-wrappers/sonyliv.js39
-rw-r--r--browser/extensions/pictureinpicture/video-wrappers/tubi.js35
-rw-r--r--browser/extensions/pictureinpicture/video-wrappers/tubilive.js35
-rw-r--r--browser/extensions/pictureinpicture/video-wrappers/twitch.js19
-rw-r--r--browser/extensions/pictureinpicture/video-wrappers/udemy.js66
-rw-r--r--browser/extensions/pictureinpicture/video-wrappers/videojsWrapper.js38
-rw-r--r--browser/extensions/pictureinpicture/video-wrappers/voot.js37
-rw-r--r--browser/extensions/pictureinpicture/video-wrappers/washingtonpost.js42
-rw-r--r--browser/extensions/pictureinpicture/video-wrappers/yahoo.js38
-rw-r--r--browser/extensions/pictureinpicture/video-wrappers/youtube.js89
26 files changed, 1245 insertions, 0 deletions
diff --git a/browser/extensions/pictureinpicture/video-wrappers/airmozilla.js b/browser/extensions/pictureinpicture/video-wrappers/airmozilla.js
new file mode 100644
index 0000000000..d2e98cbe48
--- /dev/null
+++ b/browser/extensions/pictureinpicture/video-wrappers/airmozilla.js
@@ -0,0 +1,63 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+"use strict";
+
+class PictureInPictureVideoWrapper {
+ play(video) {
+ let playPauseButton = document.querySelector(
+ "#transportControls #playButton"
+ );
+ if (video.paused) {
+ playPauseButton?.click();
+ }
+ }
+
+ pause(video) {
+ let playPauseButton = document.querySelector(
+ "#transportControls #playButton"
+ );
+ if (!video.paused) {
+ playPauseButton?.click();
+ }
+ }
+
+ setMuted(video, shouldMute) {
+ let muteButton = document.querySelector("#transportControls #muteButton");
+ if (video.muted !== shouldMute && muteButton) {
+ muteButton.click();
+ }
+ }
+
+ setCaptionContainerObserver(video, updateCaptionsFunction) {
+ let container = document.querySelector("#absoluteControls");
+
+ if (container) {
+ updateCaptionsFunction("");
+ const callback = function (mutationsList, observer) {
+ let text = container?.querySelector("#overlayCaption").innerText;
+
+ if (!text) {
+ updateCaptionsFunction("");
+ return;
+ }
+
+ updateCaptionsFunction(text);
+ };
+
+ // immediately invoke the callback function to add subtitles to the PiP window
+ callback([1], null);
+
+ let captionsObserver = new MutationObserver(callback);
+
+ captionsObserver.observe(container, {
+ attributes: false,
+ childList: true,
+ subtree: true,
+ });
+ }
+ }
+}
+
+this.PictureInPictureVideoWrapper = PictureInPictureVideoWrapper;
diff --git a/browser/extensions/pictureinpicture/video-wrappers/arte.js b/browser/extensions/pictureinpicture/video-wrappers/arte.js
new file mode 100644
index 0000000000..3d1df1f65f
--- /dev/null
+++ b/browser/extensions/pictureinpicture/video-wrappers/arte.js
@@ -0,0 +1,35 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+"use strict";
+
+class PictureInPictureVideoWrapper {
+ setCaptionContainerObserver(video, updateCaptionsFunction) {
+ let container = document.querySelector(".avp-captions");
+
+ if (container) {
+ updateCaptionsFunction("");
+ const callback = function (mutationsList, observer) {
+ let textNodeList = container.querySelectorAll(".avp-captions-line");
+ if (!textNodeList.length) {
+ updateCaptionsFunction("");
+ return;
+ }
+ updateCaptionsFunction(
+ Array.from(textNodeList, x => x.textContent).join("\n")
+ );
+ };
+ callback([1], null);
+
+ let captionsObserver = new MutationObserver(callback);
+ captionsObserver.observe(container, {
+ attributes: false,
+ childList: true,
+ subtree: true,
+ });
+ }
+ }
+}
+
+this.PictureInPictureVideoWrapper = PictureInPictureVideoWrapper;
diff --git a/browser/extensions/pictureinpicture/video-wrappers/bbc.js b/browser/extensions/pictureinpicture/video-wrappers/bbc.js
new file mode 100644
index 0000000000..a5adcbc534
--- /dev/null
+++ b/browser/extensions/pictureinpicture/video-wrappers/bbc.js
@@ -0,0 +1,31 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+"use strict";
+
+class PictureInPictureVideoWrapper {
+ setCaptionContainerObserver(video, updateCaptionsFunction) {
+ let container = document.querySelector(".p_subtitlesContainer");
+
+ if (container) {
+ updateCaptionsFunction("");
+ const callback = function (mutationsList, observer) {
+ let text = container.querySelector(".p_cueDirUniWrapper")?.innerText;
+ updateCaptionsFunction(text);
+ };
+
+ callback([1], null);
+
+ let captionsObserver = new MutationObserver(callback);
+
+ captionsObserver.observe(container, {
+ attributes: false,
+ childList: true,
+ subtree: true,
+ });
+ }
+ }
+}
+
+this.PictureInPictureVideoWrapper = PictureInPictureVideoWrapper;
diff --git a/browser/extensions/pictureinpicture/video-wrappers/cbc.js b/browser/extensions/pictureinpicture/video-wrappers/cbc.js
new file mode 100644
index 0000000000..595d23594b
--- /dev/null
+++ b/browser/extensions/pictureinpicture/video-wrappers/cbc.js
@@ -0,0 +1,30 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+"use strict";
+
+class PictureInPictureVideoWrapper {
+ play(video) {
+ let playPauseButton = document.querySelector(".video-ui .play-button");
+ if (video.paused) {
+ playPauseButton?.click();
+ }
+ }
+
+ pause(video) {
+ let playPauseButton = document.querySelector(".video-ui .pause-button");
+ if (!video.paused) {
+ playPauseButton?.click();
+ }
+ }
+
+ setMuted(video, shouldMute) {
+ let muteButton = document.querySelector(".video-ui .muted-btn");
+ if (video.muted !== shouldMute && muteButton) {
+ muteButton.click();
+ }
+ }
+}
+
+this.PictureInPictureVideoWrapper = PictureInPictureVideoWrapper;
diff --git a/browser/extensions/pictureinpicture/video-wrappers/dailymotion.js b/browser/extensions/pictureinpicture/video-wrappers/dailymotion.js
new file mode 100644
index 0000000000..161f1ec516
--- /dev/null
+++ b/browser/extensions/pictureinpicture/video-wrappers/dailymotion.js
@@ -0,0 +1,42 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+"use strict";
+
+class PictureInPictureVideoWrapper {
+ setCaptionContainerObserver(video, updateCaptionsFunction) {
+ let container = document.querySelector("#player");
+
+ if (container) {
+ updateCaptionsFunction("");
+ const callback = function (mutationsList, observer) {
+ let textNodeList = container
+ ?.querySelector(".subtitles")
+ ?.querySelectorAll("div");
+
+ if (!textNodeList) {
+ updateCaptionsFunction("");
+ return;
+ }
+
+ updateCaptionsFunction(
+ Array.from(textNodeList, x => x.innerText).join("\n")
+ );
+ };
+
+ // immediately invoke the callback function to add subtitles to the PiP window
+ callback([1], null);
+
+ let captionsObserver = new MutationObserver(callback);
+
+ captionsObserver.observe(container, {
+ attributes: false,
+ childList: true,
+ subtree: true,
+ });
+ }
+ }
+}
+
+this.PictureInPictureVideoWrapper = PictureInPictureVideoWrapper;
diff --git a/browser/extensions/pictureinpicture/video-wrappers/disneyplus.js b/browser/extensions/pictureinpicture/video-wrappers/disneyplus.js
new file mode 100644
index 0000000000..bb5c55e0cb
--- /dev/null
+++ b/browser/extensions/pictureinpicture/video-wrappers/disneyplus.js
@@ -0,0 +1,70 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+"use strict";
+
+class PictureInPictureVideoWrapper {
+ setCaptionContainerObserver(video, updateCaptionsFunction) {
+ // Handle Disney+ (US)
+ let container = document.querySelector(".dss-hls-subtitle-overlay");
+
+ if (container) {
+ const callback = () => {
+ let textNodeList = container.querySelectorAll(
+ ".dss-subtitle-renderer-line"
+ );
+
+ if (!textNodeList.length) {
+ updateCaptionsFunction("");
+ return;
+ }
+
+ updateCaptionsFunction(
+ Array.from(textNodeList, x => x.textContent).join("\n")
+ );
+ };
+
+ // immediately invoke the callback function to add subtitles to the PiP window
+ callback();
+
+ let captionsObserver = new MutationObserver(callback);
+ captionsObserver.observe(container, {
+ attributes: false,
+ childList: true,
+ subtree: true,
+ });
+ return;
+ }
+
+ // Handle Disney+ (non US version)
+ container = document.querySelector(".shaka-text-container");
+ if (container) {
+ updateCaptionsFunction("");
+ const callback = function (mutationsList, observer) {
+ let textNodeList = container?.querySelectorAll("span");
+ if (!textNodeList) {
+ updateCaptionsFunction("");
+ return;
+ }
+
+ updateCaptionsFunction(
+ Array.from(textNodeList, x => x.textContent).join("\n")
+ );
+ };
+
+ // immediately invoke the callback function to add subtitles to the PiP window
+ callback([1], null);
+
+ let captionsObserver = new MutationObserver(callback);
+
+ captionsObserver.observe(container, {
+ attributes: false,
+ childList: true,
+ subtree: true,
+ });
+ }
+ }
+}
+
+this.PictureInPictureVideoWrapper = PictureInPictureVideoWrapper;
diff --git a/browser/extensions/pictureinpicture/video-wrappers/edx.js b/browser/extensions/pictureinpicture/video-wrappers/edx.js
new file mode 100644
index 0000000000..07a3d9f302
--- /dev/null
+++ b/browser/extensions/pictureinpicture/video-wrappers/edx.js
@@ -0,0 +1,33 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+"use strict";
+
+class PictureInPictureVideoWrapper {
+ setCaptionContainerObserver(video, updateCaptionsFunction) {
+ let container = document.querySelector(".video-wrapper");
+
+ if (container) {
+ updateCaptionsFunction("");
+ const callback = function (mutationsList, observer) {
+ let text = container.querySelector(
+ ".closed-captions.is-visible"
+ )?.innerText;
+ updateCaptionsFunction(text);
+ };
+
+ callback([1], null);
+
+ let captionsObserver = new MutationObserver(callback);
+
+ captionsObserver.observe(container, {
+ attributes: true,
+ childList: true,
+ subtree: true,
+ });
+ }
+ }
+}
+
+this.PictureInPictureVideoWrapper = PictureInPictureVideoWrapper;
diff --git a/browser/extensions/pictureinpicture/video-wrappers/hbomax.js b/browser/extensions/pictureinpicture/video-wrappers/hbomax.js
new file mode 100644
index 0000000000..8aff3e0077
--- /dev/null
+++ b/browser/extensions/pictureinpicture/video-wrappers/hbomax.js
@@ -0,0 +1,48 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+"use strict";
+
+class PictureInPictureVideoWrapper {
+ setVolume(video, volume) {
+ video.volume = volume;
+ }
+ isMuted(video) {
+ return video.volume === 0;
+ }
+ setMuted(video, shouldMute) {
+ if (shouldMute) {
+ this.setVolume(video, 0);
+ } else {
+ this.setVolume(video, 1);
+ }
+ }
+ setCaptionContainerObserver(video, updateCaptionsFunction) {
+ let container = document.querySelector(
+ '[data-testid="CueBoxContainer"]'
+ ).parentElement;
+
+ if (container) {
+ updateCaptionsFunction("");
+ const callback = function (mutationsList, observer) {
+ let text = container.querySelector(
+ '[data-testid="CueBoxContainer"]'
+ )?.innerText;
+ updateCaptionsFunction(text);
+ };
+
+ callback([1], null);
+
+ let captionsObserver = new MutationObserver(callback);
+
+ captionsObserver.observe(container, {
+ attributes: false,
+ childList: true,
+ subtree: true,
+ });
+ }
+ }
+}
+
+this.PictureInPictureVideoWrapper = PictureInPictureVideoWrapper;
diff --git a/browser/extensions/pictureinpicture/video-wrappers/hotstar.js b/browser/extensions/pictureinpicture/video-wrappers/hotstar.js
new file mode 100644
index 0000000000..c6b45b1a0a
--- /dev/null
+++ b/browser/extensions/pictureinpicture/video-wrappers/hotstar.js
@@ -0,0 +1,39 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+"use strict";
+
+class PictureInPictureVideoWrapper {
+ setCaptionContainerObserver(video, updateCaptionsFunction) {
+ let container = document.querySelector(".shaka-text-container");
+
+ if (container) {
+ updateCaptionsFunction("");
+ const callback = function (mutationsList, observer) {
+ let textNodeList = container?.querySelectorAll("span");
+ if (!textNodeList) {
+ updateCaptionsFunction("");
+ return;
+ }
+
+ updateCaptionsFunction(
+ Array.from(textNodeList, x => x.textContent).join("\n")
+ );
+ };
+
+ // immediately invoke the callback function to add subtitles to the PiP window
+ callback([1], null);
+
+ let captionsObserver = new MutationObserver(callback);
+
+ captionsObserver.observe(container, {
+ attributes: false,
+ childList: true,
+ subtree: true,
+ });
+ }
+ }
+}
+
+this.PictureInPictureVideoWrapper = PictureInPictureVideoWrapper;
diff --git a/browser/extensions/pictureinpicture/video-wrappers/hulu.js b/browser/extensions/pictureinpicture/video-wrappers/hulu.js
new file mode 100644
index 0000000000..fdaf6d7c18
--- /dev/null
+++ b/browser/extensions/pictureinpicture/video-wrappers/hulu.js
@@ -0,0 +1,71 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+"use strict";
+
+class PictureInPictureVideoWrapper {
+ constructor(video) {
+ this.player = video.wrappedJSObject.__HuluDashPlayer__;
+ }
+ play() {
+ this.player.play();
+ }
+ pause() {
+ this.player.pause();
+ }
+ isMuted(video) {
+ return video.volume === 0;
+ }
+ setMuted(video, shouldMute) {
+ let muteButton = document.querySelector(".VolumeControl > div");
+
+ if (this.isMuted(video) !== shouldMute) {
+ muteButton.click();
+ }
+ }
+ setCurrentTime(video, position) {
+ this.player.currentTime = position;
+ }
+ setCaptionContainerObserver(video, updateCaptionsFunction) {
+ let container = document.querySelector(".ClosedCaption");
+
+ if (container) {
+ updateCaptionsFunction("");
+ const callback = function (mutationsList, observer) {
+ // This will get the subtitles for both live and regular playback videos
+ // and combine them to display. liveVideoText should be an empty string
+ // when the video is regular playback and vice versa. If both
+ // liveVideoText and regularVideoText are non empty strings, which
+ // doesn't seem to be the case, they will both show.
+ let liveVideoText = Array.from(
+ container.querySelectorAll(
+ "#inband-closed-caption > div > div > div"
+ ),
+ x => x.textContent.trim()
+ )
+ .filter(String)
+ .join("\n");
+ let regularVideoText = container.querySelector(".CaptionBox").innerText;
+
+ updateCaptionsFunction(liveVideoText + regularVideoText);
+ };
+
+ // immediately invoke the callback function to add subtitles to the PiP window
+ callback([1], null);
+
+ let captionsObserver = new MutationObserver(callback);
+
+ captionsObserver.observe(container, {
+ attributes: false,
+ childList: true,
+ subtree: true,
+ });
+ }
+ }
+ getDuration(video) {
+ return this.player.duration;
+ }
+}
+
+this.PictureInPictureVideoWrapper = PictureInPictureVideoWrapper;
diff --git a/browser/extensions/pictureinpicture/video-wrappers/mock-wrapper.js b/browser/extensions/pictureinpicture/video-wrappers/mock-wrapper.js
new file mode 100644
index 0000000000..b68ce3fa9b
--- /dev/null
+++ b/browser/extensions/pictureinpicture/video-wrappers/mock-wrapper.js
@@ -0,0 +1,34 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+"use strict";
+
+class PictureInPictureVideoWrapper {
+ play(video) {
+ let playPauseButton = document.querySelector("#player .play-pause-button");
+ playPauseButton.click();
+ }
+
+ pause(video) {
+ let invalidSelector = "#player .pause-button";
+ let playPauseButton = document.querySelector(invalidSelector);
+ playPauseButton.click();
+ }
+
+ setMuted(video, shouldMute) {
+ let muteButton = document.querySelector("#player .mute-button");
+ if (video.muted !== shouldMute && muteButton) {
+ muteButton.click();
+ } else {
+ video.muted = shouldMute;
+ }
+ }
+
+ shouldHideToggle() {
+ let video = document.getElementById("mock-video-controls");
+ return !!video.classList.contains("mock-preview-video");
+ }
+}
+
+this.PictureInPictureVideoWrapper = PictureInPictureVideoWrapper;
diff --git a/browser/extensions/pictureinpicture/video-wrappers/netflix.js b/browser/extensions/pictureinpicture/video-wrappers/netflix.js
new file mode 100644
index 0000000000..e33fe23a24
--- /dev/null
+++ b/browser/extensions/pictureinpicture/video-wrappers/netflix.js
@@ -0,0 +1,94 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+"use strict";
+
+class PictureInPictureVideoWrapper {
+ constructor() {
+ let netflixPlayerAPI =
+ window.wrappedJSObject.netflix.appContext.state.playerApp.getAPI()
+ .videoPlayer;
+ let sessionId = null;
+ for (let id of netflixPlayerAPI.getAllPlayerSessionIds()) {
+ if (id.startsWith("watch-")) {
+ sessionId = id;
+ break;
+ }
+ }
+ this.player = netflixPlayerAPI.getVideoPlayerBySessionId(sessionId);
+ }
+ /**
+ * The Netflix player returns the current time in milliseconds so we convert
+ * to seconds before returning.
+ *
+ * @param {HTMLVideoElement} video The original video element
+ * @returns {number} The current time in seconds
+ */
+ getCurrentTime(video) {
+ return this.player.getCurrentTime() / 1000;
+ }
+ /**
+ * The Netflix player returns the duration in milliseconds so we convert to
+ * seconds before returning.
+ *
+ * @param {HTMLVideoElement} video The original video element
+ * @returns {number} The duration in seconds
+ */
+ getDuration(video) {
+ return this.player.getDuration() / 1000;
+ }
+ play() {
+ this.player.play();
+ }
+ pause() {
+ this.player.pause();
+ }
+
+ setCaptionContainerObserver(video, updateCaptionsFunction) {
+ let container = document.querySelector(".watch-video");
+
+ if (container) {
+ updateCaptionsFunction("");
+ const callback = function (mutationsList, observer) {
+ let text = container.querySelector(".player-timedtext").innerText;
+ updateCaptionsFunction(text);
+ };
+
+ // immediately invoke the callback function to add subtitles to the PiP window
+ callback([1], null);
+
+ let captionsObserver = new MutationObserver(callback);
+
+ captionsObserver.observe(container, {
+ attributes: false,
+ childList: true,
+ subtree: true,
+ });
+ }
+ }
+
+ /**
+ * Set the current time of the video in milliseconds.
+ *
+ * @param {HTMLVideoElement} video The original video element
+ * @param {number} position The new time in seconds
+ */
+ setCurrentTime(video, position) {
+ this.player.seek(position * 1000);
+ }
+ setVolume(video, volume) {
+ this.player.setVolume(volume);
+ }
+ getVolume() {
+ return this.player.getVolume();
+ }
+ setMuted(video, shouldMute) {
+ this.player.setMuted(shouldMute);
+ }
+ isMuted() {
+ return this.player.isMuted();
+ }
+}
+
+this.PictureInPictureVideoWrapper = PictureInPictureVideoWrapper;
diff --git a/browser/extensions/pictureinpicture/video-wrappers/nytimes.js b/browser/extensions/pictureinpicture/video-wrappers/nytimes.js
new file mode 100644
index 0000000000..4f6d8cbe44
--- /dev/null
+++ b/browser/extensions/pictureinpicture/video-wrappers/nytimes.js
@@ -0,0 +1,37 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+"use strict";
+
+class PictureInPictureVideoWrapper {
+ setCaptionContainerObserver(video, updateCaptionsFunction) {
+ let container = document.querySelector(".react-vhs-player");
+
+ if (container) {
+ updateCaptionsFunction("");
+ const callback = function (mutationsList, observer) {
+ let text = container.querySelector(".cueWrap-2P4Ue4VQ")?.innerText;
+ if (!text) {
+ updateCaptionsFunction("");
+ return;
+ }
+
+ updateCaptionsFunction(text);
+ };
+
+ // immediately invoke the callback function to add subtitles to the PiP window
+ callback([1], null);
+
+ let captionsObserver = new MutationObserver(callback);
+
+ captionsObserver.observe(container, {
+ attributes: false,
+ childList: true,
+ subtree: true,
+ });
+ }
+ }
+}
+
+this.PictureInPictureVideoWrapper = PictureInPictureVideoWrapper;
diff --git a/browser/extensions/pictureinpicture/video-wrappers/piped.js b/browser/extensions/pictureinpicture/video-wrappers/piped.js
new file mode 100644
index 0000000000..1cc1c32eb2
--- /dev/null
+++ b/browser/extensions/pictureinpicture/video-wrappers/piped.js
@@ -0,0 +1,41 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+"use strict";
+
+class PictureInPictureVideoWrapper {
+ setCaptionContainerObserver(video, updateCaptionsFunction) {
+ let container = document.querySelector(".player-container");
+
+ if (container) {
+ updateCaptionsFunction("");
+ const callback = function (mutationsList, observer) {
+ let textNodeList = container
+ .querySelector(".shaka-text-wrapper")
+ ?.querySelectorAll('span[style="background-color: black;"]');
+ if (!textNodeList) {
+ updateCaptionsFunction("");
+ return;
+ }
+
+ updateCaptionsFunction(
+ Array.from(textNodeList, x => x.textContent).join("\n")
+ );
+ };
+
+ // immediately invoke the callback function to add subtitles to the PiP window
+ callback([1], null);
+
+ let captionsObserver = new MutationObserver(callback);
+
+ captionsObserver.observe(container, {
+ attributes: false,
+ childList: true,
+ subtree: true,
+ });
+ }
+ }
+}
+
+this.PictureInPictureVideoWrapper = PictureInPictureVideoWrapper;
diff --git a/browser/extensions/pictureinpicture/video-wrappers/primeVideo.js b/browser/extensions/pictureinpicture/video-wrappers/primeVideo.js
new file mode 100644
index 0000000000..28a2bd1575
--- /dev/null
+++ b/browser/extensions/pictureinpicture/video-wrappers/primeVideo.js
@@ -0,0 +1,103 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+"use strict";
+
+class PictureInPictureVideoWrapper {
+ /**
+ * Playing the video when the readyState is HAVE_METADATA (1) can cause play
+ * to fail but it will load the video and trying to play again allows enough
+ * time for the second play to successfully play the video.
+ *
+ * @param {HTMLVideoElement} video
+ * The original video element
+ */
+ play(video) {
+ video.play().catch(() => {
+ video.play();
+ });
+ }
+ /**
+ * Seeking large amounts of time can cause the video readyState to
+ * HAVE_METADATA (1) and it will throw an error when trying to play the video.
+ * To combat this, after seeking we check if the readyState changed and if so,
+ * we will play to video to "load" the video at the new time and then play or
+ * pause the video depending on if the video was playing before we seeked.
+ *
+ * @param {HTMLVideoElement} video
+ * The original video element
+ * @param {number} position
+ * The new time to set the video to
+ * @param {boolean} wasPlaying
+ * True if the video was playing before seeking else false
+ */
+ setCurrentTime(video, position, wasPlaying) {
+ if (wasPlaying === undefined) {
+ this.wasPlaying = !video.paused;
+ }
+ video.currentTime = position;
+ if (video.readyState < video.HAVE_CURRENT_DATA) {
+ video
+ .play()
+ .then(() => {
+ if (!wasPlaying) {
+ video.pause();
+ }
+ })
+ .catch(() => {
+ if (wasPlaying) {
+ this.play(video);
+ }
+ });
+ }
+ }
+ setCaptionContainerObserver(video, updateCaptionsFunction) {
+ let container = document?.querySelector("#dv-web-player");
+
+ if (container) {
+ updateCaptionsFunction("");
+ const callback = function (mutationsList, observer) {
+ // eslint-disable-next-line no-unused-vars
+ for (const mutation of mutationsList) {
+ let text;
+ // windows, mac
+ if (container?.querySelector(".atvwebplayersdk-player-container")) {
+ text = container
+ ?.querySelector(".f35bt6a")
+ ?.querySelector(".atvwebplayersdk-captions-text")?.innerText;
+ } else {
+ // linux
+ text = container
+ ?.querySelector(".persistentPanel")
+ ?.querySelector("span")?.innerText;
+ }
+
+ if (!text) {
+ updateCaptionsFunction("");
+ return;
+ }
+
+ updateCaptionsFunction(text);
+ }
+ };
+
+ // immediately invoke the callback function to add subtitles to the PiP window
+ callback([1], null);
+
+ let captionsObserver = new MutationObserver(callback);
+
+ captionsObserver.observe(container, {
+ attributes: true,
+ childList: true,
+ subtree: true,
+ });
+ }
+ }
+
+ shouldHideToggle(video) {
+ return !!video.classList.contains("tst-video-overlay-player-html5");
+ }
+}
+
+this.PictureInPictureVideoWrapper = PictureInPictureVideoWrapper;
diff --git a/browser/extensions/pictureinpicture/video-wrappers/radiocanada.js b/browser/extensions/pictureinpicture/video-wrappers/radiocanada.js
new file mode 100644
index 0000000000..1a55377493
--- /dev/null
+++ b/browser/extensions/pictureinpicture/video-wrappers/radiocanada.js
@@ -0,0 +1,36 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+"use strict";
+
+class PictureInPictureVideoWrapper {
+ play(video) {
+ let playPauseButton = document.querySelector(
+ ".rcplayer-btn.rcplayer-smallPlayPauseBtn"
+ );
+ if (video.paused) {
+ playPauseButton.click();
+ }
+ }
+
+ pause(video) {
+ let playPauseButton = document.querySelector(
+ ".rcplayer-btn.rcplayer-smallPlayPauseBtn"
+ );
+ if (!video.paused) {
+ playPauseButton.click();
+ }
+ }
+
+ setMuted(video, shouldMute) {
+ let muteButton = document.querySelector(
+ ".rcplayer-bouton-with-panel--volume > button"
+ );
+ if (video.muted !== shouldMute && muteButton) {
+ muteButton.click();
+ }
+ }
+}
+
+this.PictureInPictureVideoWrapper = PictureInPictureVideoWrapper;
diff --git a/browser/extensions/pictureinpicture/video-wrappers/sonyliv.js b/browser/extensions/pictureinpicture/video-wrappers/sonyliv.js
new file mode 100644
index 0000000000..b703aaec2c
--- /dev/null
+++ b/browser/extensions/pictureinpicture/video-wrappers/sonyliv.js
@@ -0,0 +1,39 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+"use strict";
+
+class PictureInPictureVideoWrapper {
+ setCaptionContainerObserver(video, updateCaptionsFunction) {
+ let container = document.querySelector(".player-ui-main-wrapper");
+
+ if (container) {
+ updateCaptionsFunction("");
+ const callback = function (mutationsList, observer) {
+ let text = container.querySelector(
+ `.text-track-wrapper:not([style*="display: none"])`
+ )?.innerText;
+ if (!text) {
+ updateCaptionsFunction("");
+ return;
+ }
+
+ updateCaptionsFunction(text);
+ };
+
+ // immediately invoke the callback function to add subtitles to the PiP window
+ callback([1], null);
+
+ let captionsObserver = new MutationObserver(callback);
+
+ captionsObserver.observe(container, {
+ attributes: false,
+ childList: true,
+ subtree: true,
+ });
+ }
+ }
+}
+
+this.PictureInPictureVideoWrapper = PictureInPictureVideoWrapper;
diff --git a/browser/extensions/pictureinpicture/video-wrappers/tubi.js b/browser/extensions/pictureinpicture/video-wrappers/tubi.js
new file mode 100644
index 0000000000..291dbfddeb
--- /dev/null
+++ b/browser/extensions/pictureinpicture/video-wrappers/tubi.js
@@ -0,0 +1,35 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+"use strict";
+
+class PictureInPictureVideoWrapper {
+ setCaptionContainerObserver(video, updateCaptionsFunction) {
+ let container = document.querySelector(`[data-id="hls"]`);
+
+ if (container) {
+ updateCaptionsFunction("");
+ const callback = function (mutationsList, observer) {
+ let text = container?.querySelector(
+ `[data-id="captionsComponent"]:not([style="display: none;"])`
+ )?.innerText;
+
+ updateCaptionsFunction(text);
+ };
+
+ // immediately invoke the callback function to add subtitles to the PiP window
+ callback([1], null);
+
+ let captionsObserver = new MutationObserver(callback);
+
+ captionsObserver.observe(container, {
+ attributes: true,
+ childList: true,
+ subtree: true,
+ });
+ }
+ }
+}
+
+this.PictureInPictureVideoWrapper = PictureInPictureVideoWrapper;
diff --git a/browser/extensions/pictureinpicture/video-wrappers/tubilive.js b/browser/extensions/pictureinpicture/video-wrappers/tubilive.js
new file mode 100644
index 0000000000..0de748e717
--- /dev/null
+++ b/browser/extensions/pictureinpicture/video-wrappers/tubilive.js
@@ -0,0 +1,35 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+"use strict";
+
+class PictureInPictureVideoWrapper {
+ setCaptionContainerObserver(video, updateCaptionsFunction) {
+ let container = video.parentElement;
+
+ if (container) {
+ updateCaptionsFunction("");
+ const callback = function (mutationsList, observer) {
+ let text =
+ container.querySelector(`.tubi-text-track-container`)?.innerText ||
+ container.querySelector(`.subtitleWindow`)?.innerText;
+
+ updateCaptionsFunction(text);
+ };
+
+ // immediately invoke the callback function to add subtitles to the PiP window
+ callback([1], null);
+
+ let captionsObserver = new MutationObserver(callback);
+
+ captionsObserver.observe(container, {
+ attributes: true,
+ childList: true,
+ subtree: true,
+ });
+ }
+ }
+}
+
+this.PictureInPictureVideoWrapper = PictureInPictureVideoWrapper;
diff --git a/browser/extensions/pictureinpicture/video-wrappers/twitch.js b/browser/extensions/pictureinpicture/video-wrappers/twitch.js
new file mode 100644
index 0000000000..1dd7567c24
--- /dev/null
+++ b/browser/extensions/pictureinpicture/video-wrappers/twitch.js
@@ -0,0 +1,19 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+"use strict";
+
+class PictureInPictureVideoWrapper {
+ isLive(video) {
+ return !document.querySelector(".seekbar-bar");
+ }
+ getDuration(video) {
+ if (this.isLive(video)) {
+ return Infinity;
+ }
+ return video.duration;
+ }
+}
+
+this.PictureInPictureVideoWrapper = PictureInPictureVideoWrapper;
diff --git a/browser/extensions/pictureinpicture/video-wrappers/udemy.js b/browser/extensions/pictureinpicture/video-wrappers/udemy.js
new file mode 100644
index 0000000000..09e3b989dc
--- /dev/null
+++ b/browser/extensions/pictureinpicture/video-wrappers/udemy.js
@@ -0,0 +1,66 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+"use strict";
+
+class PictureInPictureVideoWrapper {
+ play(video) {
+ let playPauseButton = document.querySelector(
+ `[data-purpose="play-button"]`
+ );
+ if (video.paused) {
+ playPauseButton?.click();
+ }
+ }
+
+ pause(video) {
+ let playPauseButton = document.querySelector(
+ `[data-purpose="pause-button"]`
+ );
+ if (!video.paused) {
+ playPauseButton?.click();
+ }
+ }
+
+ setMuted(video, shouldMute) {
+ let muteButton = document.querySelector(
+ `[data-purpose="volume-control-button"]`
+ );
+ if (video.muted !== shouldMute && muteButton) {
+ muteButton.click();
+ }
+ }
+
+ setCaptionContainerObserver(video, updateCaptionsFunction) {
+ let container = video.parentElement;
+
+ if (container) {
+ updateCaptionsFunction("");
+ const callback = function (mutationsList, observer) {
+ let text = container.querySelector(
+ `[data-purpose="captions-cue-text"]`
+ )?.innerText;
+ if (!text) {
+ updateCaptionsFunction("");
+ return;
+ }
+
+ updateCaptionsFunction(text);
+ };
+
+ // immediately invoke the callback function to add subtitles to the PiP window
+ callback([1], null);
+
+ let captionsObserver = new MutationObserver(callback);
+
+ captionsObserver.observe(container, {
+ attributes: true,
+ childList: true,
+ subtree: true,
+ });
+ }
+ }
+}
+
+this.PictureInPictureVideoWrapper = PictureInPictureVideoWrapper;
diff --git a/browser/extensions/pictureinpicture/video-wrappers/videojsWrapper.js b/browser/extensions/pictureinpicture/video-wrappers/videojsWrapper.js
new file mode 100644
index 0000000000..ca3145af4a
--- /dev/null
+++ b/browser/extensions/pictureinpicture/video-wrappers/videojsWrapper.js
@@ -0,0 +1,38 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+"use strict";
+
+// This wrapper supports multiple sites that use video.js player
+class PictureInPictureVideoWrapper {
+ setCaptionContainerObserver(video, updateCaptionsFunction) {
+ let container = document.querySelector(".vjs-text-track-display");
+
+ if (container) {
+ updateCaptionsFunction("");
+ const callback = function (mutationsList, observer) {
+ let text = container.querySelector("div").innerText;
+ if (!text) {
+ updateCaptionsFunction("");
+ return;
+ }
+
+ updateCaptionsFunction(text);
+ };
+
+ // immediately invoke the callback function to add subtitles to the PiP window
+ callback([1], null);
+
+ let captionsObserver = new MutationObserver(callback);
+
+ captionsObserver.observe(container, {
+ attributes: false,
+ childList: true,
+ subtree: true,
+ });
+ }
+ }
+}
+
+this.PictureInPictureVideoWrapper = PictureInPictureVideoWrapper;
diff --git a/browser/extensions/pictureinpicture/video-wrappers/voot.js b/browser/extensions/pictureinpicture/video-wrappers/voot.js
new file mode 100644
index 0000000000..57d903a2e8
--- /dev/null
+++ b/browser/extensions/pictureinpicture/video-wrappers/voot.js
@@ -0,0 +1,37 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+"use strict";
+
+class PictureInPictureVideoWrapper {
+ setCaptionContainerObserver(video, updateCaptionsFunction) {
+ let container = document.querySelector(".playkit-container");
+
+ if (container) {
+ updateCaptionsFunction("");
+ const callback = function (mutationsList, observer) {
+ let text = container.querySelector(".playkit-subtitles").innerText;
+ if (!text) {
+ updateCaptionsFunction("");
+ return;
+ }
+
+ updateCaptionsFunction(text);
+ };
+
+ // immediately invoke the callback function to add subtitles to the PiP window
+ callback([1], null);
+
+ let captionsObserver = new MutationObserver(callback);
+
+ captionsObserver.observe(container, {
+ attributes: false,
+ childList: true,
+ subtree: true,
+ });
+ }
+ }
+}
+
+this.PictureInPictureVideoWrapper = PictureInPictureVideoWrapper;
diff --git a/browser/extensions/pictureinpicture/video-wrappers/washingtonpost.js b/browser/extensions/pictureinpicture/video-wrappers/washingtonpost.js
new file mode 100644
index 0000000000..6d0e57c96a
--- /dev/null
+++ b/browser/extensions/pictureinpicture/video-wrappers/washingtonpost.js
@@ -0,0 +1,42 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+"use strict";
+
+class PictureInPictureVideoWrapper {
+ setCaptionContainerObserver(video, updateCaptionsFunction) {
+ let container = document.querySelector(".powa");
+
+ if (container) {
+ updateCaptionsFunction("");
+ const callback = function (mutationsList, observer) {
+ let subtitleElement = container.querySelector(".powa-sub-torpedo");
+ if (!subtitleElement?.innerText) {
+ updateCaptionsFunction("");
+ return;
+ }
+ let subtitleElementClone = subtitleElement.cloneNode(true);
+ let breaks = subtitleElementClone.getElementsByTagName("br");
+ for (const element of breaks) {
+ element.replaceWith("\n");
+ }
+ let text = subtitleElementClone.innerText;
+
+ updateCaptionsFunction(text);
+ };
+
+ callback([1], null);
+
+ let captionsObserver = new MutationObserver(callback);
+
+ captionsObserver.observe(container, {
+ attributes: false,
+ childList: true,
+ subtree: true,
+ });
+ }
+ }
+}
+
+this.PictureInPictureVideoWrapper = PictureInPictureVideoWrapper;
diff --git a/browser/extensions/pictureinpicture/video-wrappers/yahoo.js b/browser/extensions/pictureinpicture/video-wrappers/yahoo.js
new file mode 100644
index 0000000000..b7d8d3160f
--- /dev/null
+++ b/browser/extensions/pictureinpicture/video-wrappers/yahoo.js
@@ -0,0 +1,38 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+"use strict";
+
+class PictureInPictureVideoWrapper {
+ setCaptionContainerObserver(video, updateCaptionsFunction) {
+ let container = document.querySelector(".vp-main");
+
+ if (container) {
+ updateCaptionsFunction("");
+ const callback = function (mutationsList, observer) {
+ let text = container.querySelector(".vp-cc-element.vp-show")?.innerText;
+
+ if (!text) {
+ updateCaptionsFunction("");
+ return;
+ }
+
+ updateCaptionsFunction(text);
+ };
+
+ // immediately invoke the callback function to add subtitles to the PiP window
+ callback([1], null);
+
+ let captionsObserver = new MutationObserver(callback);
+
+ captionsObserver.observe(container, {
+ attributes: false,
+ childList: true,
+ subtree: true,
+ });
+ }
+ }
+}
+
+this.PictureInPictureVideoWrapper = PictureInPictureVideoWrapper;
diff --git a/browser/extensions/pictureinpicture/video-wrappers/youtube.js b/browser/extensions/pictureinpicture/video-wrappers/youtube.js
new file mode 100644
index 0000000000..8b39e469f9
--- /dev/null
+++ b/browser/extensions/pictureinpicture/video-wrappers/youtube.js
@@ -0,0 +1,89 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * 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/. */
+
+"use strict";
+
+class PictureInPictureVideoWrapper {
+ constructor(video) {
+ // Use shorts player only if video is from YouTube Shorts.
+ let shortsPlayer = video.closest("#shorts-player")?.wrappedJSObject;
+ let isYTShorts = !!(video.baseURI.includes("shorts") && shortsPlayer);
+
+ this.player = isYTShorts
+ ? shortsPlayer
+ : video.closest("#movie_player")?.wrappedJSObject;
+ }
+ isLive(video) {
+ return !!document.querySelector(".ytp-live");
+ }
+ setMuted(video, shouldMute) {
+ if (this.player) {
+ if (shouldMute) {
+ this.player.mute();
+ } else {
+ this.player.unMute();
+ }
+ } else {
+ video.muted = shouldMute;
+ }
+ }
+ getDuration(video) {
+ if (this.isLive(video)) {
+ return Infinity;
+ }
+ return video.duration;
+ }
+ setCaptionContainerObserver(video, updateCaptionsFunction) {
+ let container = document.getElementById("ytp-caption-window-container");
+
+ if (container) {
+ updateCaptionsFunction("");
+ const callback = function (mutationsList, observer) {
+ // eslint-disable-next-line no-unused-vars
+ for (const mutation of mutationsList) {
+ let textNodeList = container
+ .querySelector(".captions-text")
+ ?.querySelectorAll(".caption-visual-line");
+ if (!textNodeList) {
+ updateCaptionsFunction("");
+ return;
+ }
+
+ updateCaptionsFunction(
+ Array.from(textNodeList, x => x.textContent).join("\n")
+ );
+ }
+ };
+
+ // immediately invoke the callback function to add subtitles to the PiP window
+ callback([1], null);
+
+ let captionsObserver = new MutationObserver(callback);
+
+ captionsObserver.observe(container, {
+ attributes: false,
+ childList: true,
+ subtree: true,
+ });
+ }
+ }
+ shouldHideToggle(video) {
+ return !!video.closest(".ytd-video-preview");
+ }
+ setVolume(video, volume) {
+ if (this.player) {
+ this.player.setVolume(volume * 100);
+ } else {
+ video.volume = volume;
+ }
+ }
+ getVolume(video) {
+ if (this.player) {
+ return this.player.getVolume() / 100;
+ }
+ return video.volume;
+ }
+}
+
+this.PictureInPictureVideoWrapper = PictureInPictureVideoWrapper;