Plugin

PictureInPicture

Adds picture in picture to videos (next to the Download button)

Media Utility
index.tsx
Download

Source

src/plugins/pictureInPicture/index.tsx
1import "./styles.css";
2
3import { definePluginSettings } from "@api/Settings";
4import ErrorBoundary from "@components/ErrorBoundary";
5import { Devs } from "@utils/constants";
6import definePlugin, { OptionType } from "@utils/types";
7import { Tooltip } from "@webpack/common";
8
9const settings = definePluginSettings({
10 loop: {
11 description: "Whether to make the PiP video loop or not",
12 type: OptionType.BOOLEAN,
13 default: true,
14 restartNeeded: false
15 }
16});
17
18export default definePlugin({
19 name: "PictureInPicture",
20 description: "Adds picture in picture to videos (next to the Download button)",
21 tags: ["Media", "Utility"],
22 authors: [Devs.Lumap],
23 settings,
24 patches: [
25 {
26 find: '["VIDEO","CLIP","AUDIO"]',
27 replacement: {
28 match: /(\[\i>0&&\i\.length>0.{0,150}?children:)(\i.slice\(\i\))(?<=showDownload:(\i).+?isVisualMediaType:(\i).+?)/,
29 replace: (_, rest, origChildren, showDownload, isVisualMediaType) => `${rest}[${showDownload}&&${isVisualMediaType}&&$self.PictureInPictureButton(),...${origChildren}]`
30 }
31 }
32 ],
33
34 PictureInPictureButton: ErrorBoundary.wrap(() => {
35 return (
36 <Tooltip text="Toggle Picture in Picture">
37 {tooltipProps => (
38 <div
39 {...tooltipProps}
40 className="vc-pip-button"
41 role="button"
42 style={{
43 cursor: "pointer",
44 paddingTop: "4px",
45 paddingLeft: "4px",
46 paddingRight: "4px",
47 }}
48 onClick={e => {
49 const video = e.currentTarget.parentNode!.parentNode!.querySelector("video")!;
50 const videoClone = document.body.appendChild(video.cloneNode(true)) as HTMLVideoElement;
51
52 videoClone.loop = settings.store.loop;
53 videoClone.style.display = "none";
54 videoClone.onleavepictureinpicture = () => videoClone.remove();
55
56 function launchPiP() {
57 videoClone.currentTime = video.currentTime;
58 videoClone.requestPictureInPicture();
59 video.pause();
60 videoClone.play();
61 }
62
63 if (videoClone.readyState === 4 /* HAVE_ENOUGH_DATA */)
64 launchPiP();
65 else
66 videoClone.onloadedmetadata = launchPiP;
67 }}
68 >
69 <svg width="24px" height="24px" viewBox="0 0 24 24">
70 <path
71 fill="currentColor"
72 d="M21 3a1 1 0 0 1 1 1v7h-2V5H4v14h6v2H3a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h18zm0 10a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1h-8a1 1 0 0 1-1-1v-6a1 1 0 0 1 1-1h8zm-1 2h-6v4h6v-4z"
73 />
74 </svg>
75 </div>
76 )}
77 </Tooltip>
78 );
79 }, { noop: true })
80});
81