Plugin
PictureInPicture
Adds picture in picture to videos (next to the Download button)
1
/*2
* FiveCord — a Discord client mod3
* Copyright (c) 2025 FiveCord4
* SPDX-License-Identifier: GPL-3.0-or-later5
*/6
7
import "./styles.css";8
9
import { definePluginSettings } from "@api/Settings";10
import ErrorBoundary from "@components/ErrorBoundary";11
import { Devs } from "@utils/constants";12
import definePlugin, { OptionType } from "@utils/types";13
import { Tooltip } from "@webpack/common";14
15
const settings = definePluginSettings({16
loop: {17
description: "Whether to make the PiP video loop or not",18
type: OptionType.BOOLEAN,19
default: true,20
restartNeeded: false21
}22
});23
24
export default definePlugin({25
name: "PictureInPicture",26
description: "Adds picture in picture to videos (next to the Download button)",27
tags: ["Media", "Utility"],28
authors: [Devs.Lumap],29
settings,30
patches: [31
{32
find: 039;["VIDEO","CLIP","AUDIO"]039;,33
replacement: {34
match: /(\[\i>0&&\i\.length>0.{0,150}?children:)(\i.slice\(\i\))(?<=showDownload:(\i).+?isVisualMediaType:(\i).+?)/,35
replace: (_, rest, origChildren, showDownload, isVisualMediaType) => `${rest}[${showDownload}&&${isVisualMediaType}&&$self.PictureInPictureButton(),...${origChildren}]`36
}37
}38
],39
40
PictureInPictureButton: ErrorBoundary.wrap(() => {41
return (42
<Tooltip text="Toggle Picture in Picture">43
{tooltipProps => (44
<div45
{...tooltipProps}46
className="vc-pip-button"47
role="button"48
style={{49
cursor: "pointer",50
paddingTop: "4px",51
paddingLeft: "4px",52
paddingRight: "4px",53
}}54
onClick={e => {55
const video = e.currentTarget.parentNode!.parentNode!.querySelector("video")!;56
const videoClone = document.body.appendChild(video.cloneNode(true)) as HTMLVideoElement;57
58
videoClone.loop = settings.store.loop;59
videoClone.style.display = "none";60
videoClone.onleavepictureinpicture = () => videoClone.remove();61
62
function launchPiP() {63
videoClone.currentTime = video.currentTime;64
videoClone.requestPictureInPicture();65
video.pause();66
videoClone.play();67
}68
69
if (videoClone.readyState === 4 /* HAVE_ENOUGH_DATA */)70
launchPiP();71
else72
videoClone.onloadedmetadata = launchPiP;73
}}74
>75
<svg width="24px" height="24px" viewBox="0 0 24 24">76
<path77
fill="currentColor"78
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"79
/>80
</svg>81
</div>82
)}83
</Tooltip>84
);85
}, { noop: true })86
});87