Merge pull request #175 from zisra/dev

Add Picture-in-picture
This commit is contained in:
mrjvs 2023-03-10 20:29:55 +01:00 committed by GitHub
commit 48214af202
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 129 additions and 47 deletions

View File

@ -1,7 +1,6 @@
window.__CONFIG__ = {
// url must NOT end with a slash
VITE_CORS_PROXY_URL: "",
VITE_TMDB_API_KEY: "b030404650f279792a8d3287232358e3",
VITE_OMDB_API_KEY: "aa0937c0",
};

View File

@ -36,6 +36,7 @@ export enum Icons {
CASTING = "casting",
CIRCLE_EXCLAMATION = "circle_exclamation",
DOWNLOAD = "download",
PICTURE_IN_PICTURE = "pictureInPicture",
}
export interface IconProps {
@ -79,6 +80,7 @@ const iconList: Record<Icons, string> = {
circle_exclamation: `<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" fill="currentColor" viewBox="0 0 512 512"><!--! Font Awesome Pro 6.3.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zm0-384c13.3 0 24 10.7 24 24V264c0 13.3-10.7 24-24 24s-24-10.7-24-24V152c0-13.3 10.7-24 24-24zM224 352a32 32 0 1 1 64 0 32 32 0 1 1 -64 0z"/></svg>`,
casting: "",
download: `<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-download"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="7 10 12 15 17 10"></polyline><line x1="12" y1="15" x2="12" y2="3"></line></svg>`,
pictureInPicture: `<svg xmlns="http://www.w3.org/2000/svg" height="1em" width="1em" fill="currentColor" viewBox="0 0 24 24"><path d="M0 0h24v24H0z" fill="none"/><path d="M19 7h-8v6h8V7zm2-4H3c-1.1 0-2 .9-2 2v14c0 1.1.9 1.98 2 1.98h18c1.1 0 2-.88 2-1.98V5c0-1.1-.9-2-2-2zm0 16.01H3V4.98h18v14.03z"/></svg>`,
};
function ChromeCastButton() {

View File

@ -4,12 +4,13 @@
html,
body {
@apply bg-denim-100 text-denim-700 font-open-sans overflow-x-hidden;
@apply bg-denim-100 font-open-sans text-denim-700 overflow-x-hidden;
min-height: 100vh;
min-height: 100dvh;
}
html[data-full], html[data-full] body {
html[data-full],
html[data-full] body {
overscroll-behavior-y: none;
}
@ -46,7 +47,7 @@ body[data-no-select] {
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
-webkit-box-orient: vertical;
overflow: hidden;
}

View File

@ -61,7 +61,8 @@
"episodes": "Episodes",
"source": "Source",
"captions": "Captions",
"download": "Download"
"download": "Download",
"pictureInPicture": "Picture in Picture"
},
"popouts": {
"sources": "Sources",

View File

@ -38,3 +38,11 @@ export function canWebkitFullscreen(): boolean {
export function canFullscreen(): boolean {
return canFullscreenAnyElement() || canWebkitFullscreen();
}
export function canPictureInPicture(): boolean {
return "pictureInPictureEnabled" in document;
}
export function canWebkitPictureInPicture(): boolean {
return "webkitSupportsPresentationMode" in document.createElement("video");
}

View File

@ -31,6 +31,7 @@ import { PopoutProviderAction } from "@/video/components/popouts/PopoutProviderA
import { ChromecastAction } from "@/video/components/actions/ChromecastAction";
import { CastingTextAction } from "@/video/components/actions/CastingTextAction";
import { DownloadAction } from "@/video/components/actions/DownloadAction";
import { PictureInPictureAction } from "@/video/components/actions/PictureInPictureAction";
type Props = VideoPlayerBaseProps;
@ -144,6 +145,7 @@ export function VideoPlayer(props: Props) {
<div />
<div className="flex items-center justify-center">
<DownloadAction />
<PictureInPictureAction />
<CaptionsSelectionAction />
<SeriesSelectionAction />
<SourceSelectionAction />
@ -161,6 +163,7 @@ export function VideoPlayer(props: Props) {
<ChromecastAction />
<AirplayAction />
<DownloadAction />
<PictureInPictureAction />
<CaptionsSelectionAction />
<FullscreenAction />
</>

View File

@ -28,7 +28,7 @@ export function DownloadAction(props: Props) {
href={isHLS ? undefined : sourceInterface.source?.url}
rel="noreferrer"
target="_blank"
download={title ? normalizeTitle(title) : undefined}
download={title ? `${normalizeTitle(title)}.mp4` : undefined}
>
<VideoPlayerIconButton
className={props.className}

View File

@ -0,0 +1,40 @@
import { Icons } from "@/components/Icon";
import { useIsMobile } from "@/hooks/useIsMobile";
import { useTranslation } from "react-i18next";
import { useControls } from "@/video/state/logic/controls";
import { useVideoPlayerDescriptor } from "@/video/state/hooks";
import { useCallback } from "react";
import {
canPictureInPicture,
canWebkitPictureInPicture,
} from "@/utils/detectFeatures";
import { VideoPlayerIconButton } from "../parts/VideoPlayerIconButton";
interface Props {
className?: string;
}
export function PictureInPictureAction(props: Props) {
const { isMobile } = useIsMobile();
const { t } = useTranslation();
const descriptor = useVideoPlayerDescriptor();
const controls = useControls(descriptor);
const handleClick = useCallback(() => {
controls.togglePictureInPicture();
}, [controls]);
if (!canPictureInPicture() && !canWebkitPictureInPicture()) return null;
return (
<VideoPlayerIconButton
className={props.className}
icon={Icons.PICTURE_IN_PICTURE}
onClick={handleClick}
text={
isMobile ? (t("videoPlayer.buttons.pictureInPicture") as string) : ""
}
/>
);
}

View File

@ -13,6 +13,7 @@ export type ControlMethods = {
setMeta(data?: VideoPlayerMeta): void;
setCurrentEpisode(sId: string, eId: string): void;
setDraggingTime(num: number): void;
togglePictureInPicture(): void;
};
export function useControls(
@ -100,5 +101,9 @@ export function useControls(
updateMeta(descriptor, state);
}
},
togglePictureInPicture() {
state.stateProvider?.togglePictureInPicture();
updateInterface(descriptor, state);
},
};
}

View File

@ -84,6 +84,9 @@ export function createCastingStateProvider(
state.pausedWhenSeeking = state.mediaPlaying.isPaused;
this.pause();
},
togglePictureInPicture() {
// no picture in picture while casting
},
async setVolume(v) {
// clamp time between 0 and 1
let volume = Math.min(v, 1);

View File

@ -19,6 +19,7 @@ export type VideoPlayerStateController = {
setCaption(id: string, url: string): void;
clearCaption(): void;
getId(): string;
togglePictureInPicture(): void;
};
export type VideoPlayerStateProvider = VideoPlayerStateController & {

View File

@ -5,6 +5,8 @@ import {
canFullscreen,
canFullscreenAnyElement,
canWebkitFullscreen,
canPictureInPicture,
canWebkitPictureInPicture,
} from "@/utils/detectFeatures";
import { MWStreamType } from "@/backend/helpers/streams";
import { updateInterface } from "@/video/state/logic/interface";
@ -207,6 +209,23 @@ export function createVideoStateProvider(
updateSource(descriptor, state);
}
},
togglePictureInPicture() {
if (canWebkitPictureInPicture()) {
const webkitPlayer = player as any;
webkitPlayer.webkitSetPresentationMode(
webkitPlayer.webkitPresentationMode === "picture-in-picture"
? "inline"
: "picture-in-picture"
);
}
if (canPictureInPicture()) {
if (player !== document.pictureInPictureElement) {
player.requestPictureInPicture();
} else {
document.exitPictureInPicture();
}
}
},
providerStart() {
this.setVolume(getStoredVolume());

View File

@ -927,10 +927,10 @@
"@babel/helper-validator-identifier" "^7.19.1"
to-fast-properties "^2.0.0"
"@esbuild/linux-x64@0.16.5":
"@esbuild/darwin-arm64@0.16.5":
version "0.16.5"
resolved "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.16.5.tgz"
integrity sha512-vsOwzKN+4NenUTyuoWLmg5dAuO8JKuLD9MXSeENA385XucuOZbblmOMwwgPlHsgVRtSjz38riqPJU2ALI/CWYQ==
resolved "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.16.5.tgz"
integrity sha512-4HlbUMy50cRaHGVriBjShs46WRPshtnVOqkxEGhEuDuJhgZ3regpWzaQxXOcDXFvVwue8RiqDAAcOi/QlVLE6Q==
"@eslint/eslintrc@^1.3.3":
version "1.3.3"
@ -948,9 +948,9 @@
strip-json-comments "^3.1.1"
"@formkit/auto-animate@^1.0.0-beta.5":
version "1.0.0-beta.5"
resolved "https://registry.npmjs.org/@formkit/auto-animate/-/auto-animate-1.0.0-beta.5.tgz"
integrity sha512-WoSwyhAZPOe6RB/IgicOtCHtrWwEpfKIZ/H/nxpKfnZL9CB6hhhBGU5bCdMRw7YpAUF2CDlQa+WWh+gCqz5lDg==
version "1.0.0-beta.6"
resolved "https://registry.npmjs.org/@formkit/auto-animate/-/auto-animate-1.0.0-beta.6.tgz"
integrity sha512-PVDhLAlr+B4Xb7e+1wozBUWmXa6BFU8xUPR/W/E+TsQhPS1qkAdAsJ25keEnFrcePSnXHrOsh3tiFbEToOzV9w==
"@headlessui/react@^1.5.0":
version "1.7.5"
@ -1120,15 +1120,10 @@
magic-string "^0.25.0"
string.prototype.matchall "^4.0.6"
"@swc/core-linux-x64-gnu@1.3.22":
"@swc/core-darwin-arm64@1.3.22":
version "1.3.22"
resolved "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.3.22.tgz"
integrity sha512-FLkbiqsdXsVIFZi6iedx4rSBGX8x0vo/5aDlklSxJAAYOcQpO0QADKP5Yr65iMT1d6ABCt2d+/StpGLF7GWOcA==
"@swc/core-linux-x64-musl@1.3.22":
version "1.3.22"
resolved "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.3.22.tgz"
integrity sha512-giBuw+Z0Bq6fpZ0Y5TcfpcQwf9p/cE1fOQyO/K1XSTn/haQOqFi7421Jq/dFThSARZiXw1u9Om9VFbwxr8VI+A==
resolved "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.3.22.tgz"
integrity sha512-MMhtPsuXp8gpUgr9bs+RZQ2IyFGiUNDG93usCDAFgAF+6VVp+YaAVjET/3/Bx5Lk2WAt0RxT62C9KTEw1YMo3w==
"@swc/core@^1.3.21":
version "1.3.22"
@ -1169,9 +1164,9 @@
integrity sha512-KnRanxnpfpjUTqTCXslZSEdLfXExwgNxYPdiO2WGUj8+HDjFi8R3k5RVKPeSCzLjCcshCAtVO2QBbVuAV4kTnw==
"@types/chrome@*":
version "0.0.210"
resolved "https://registry.npmjs.org/@types/chrome/-/chrome-0.0.210.tgz"
integrity sha512-VSjQu1k6a/rAfuqR1Gi/oxHZj4+t6+LG+GobNI3ZWI6DQ+fmphNSF6TrLHG6BYK2bXc9Gb4c1uXFKRRVLaGl5Q==
version "0.0.217"
resolved "https://registry.npmjs.org/@types/chrome/-/chrome-0.0.217.tgz"
integrity sha512-q8fLzCCoHiR9gYRoqvrx12+HaJjRTqUom5Ks/wLSR8Ac83qAqWaA4NgUBUcDjM1O1ACczygxIHCEENXs1zmbqQ==
dependencies:
"@types/filesystem" "*"
"@types/har-format" "*"
@ -1803,9 +1798,9 @@ camelcase-css@^2.0.1:
integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==
caniuse-lite@^1.0.30001426, caniuse-lite@^1.0.30001449:
version "1.0.30001457"
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001457.tgz"
integrity sha512-SDIV6bgE1aVbK6XyxdURbUE89zY7+k1BBBaOwYwkNCglXlel/E7mELiHC64HQ+W0xSKlqWhV9Wh7iHxUjMs4fA==
version "1.0.30001458"
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001458.tgz"
integrity sha512-lQ1VlUUq5q9ro9X+5gOEyH7i3vm+AYVT1WDCVB69XOZ17KZRhnZ9J0Sqz7wTHQaLBJccNCHq8/Ww5LlOIZbB0w==
chai@^4.3.7:
version "4.3.7"
@ -1944,9 +1939,9 @@ copy-to-clipboard@^3.3.1:
toggle-selection "^1.0.6"
core-js-compat@^3.25.1:
version "3.28.0"
resolved "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.28.0.tgz"
integrity sha512-myzPgE7QodMg4nnd3K1TDoES/nADRStM8Gpz0D6nhkwbmwEnE0ZGJgoWsvQ722FR8D7xS0n0LV556RcEicjTyg==
version "3.29.0"
resolved "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.29.0.tgz"
integrity sha512-ScMn3uZNAFhK2DGoEfErguoiAHhV2Ju+oJo/jK08p7B3f3UhocUrCCkTvnZaiS+edl5nlIoiBXKcwMc6elv4KQ==
dependencies:
browserslist "^4.21.5"
@ -1956,9 +1951,9 @@ core-js-pure@^3.25.1:
integrity sha512-VVXcDpp/xJ21KdULRq/lXdLzQAtX7+37LzpyfFM973il0tWSsDEoyzG38G14AjTpK9VTfiNM9jnFauq/CpaWGQ==
core-js@^3.6.5:
version "3.27.1"
resolved "https://registry.npmjs.org/core-js/-/core-js-3.27.1.tgz"
integrity sha512-GutwJLBChfGCpwwhbYoqfv03LAfmiz7e7D/BNxzeMxwQf10GRSzqiOjx7AmtEk+heiD/JWmBuyBPgFtx0Sg1ww==
version "3.28.0"
resolved "https://registry.npmjs.org/core-js/-/core-js-3.28.0.tgz"
integrity sha512-GiZn9D4Z/rSYvTeg1ljAIsEqFm0LaN9gVtwDCrKL80zHtS31p9BAjmTxVqTQDMpwlMolJZOFntUG2uwyj7DAqw==
cross-spawn@^7.0.2:
version "7.0.3"
@ -2096,7 +2091,7 @@ delayed-stream@~1.0.0:
resolved "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz"
integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==
destr@^1.2.1:
destr@^1.2.2:
version "1.2.2"
resolved "https://registry.npmjs.org/destr/-/destr-1.2.2.tgz"
integrity sha512-lrbCJwD9saUQrqUfXvl6qoM+QN3W7tLV5pAOs+OqOmopCCz/JkE05MHedJR1xfk4IAnZuJXPVuN5+7jNA2ZCiA==
@ -2691,6 +2686,11 @@ fscreen@^1.2.0:
resolved "https://registry.npmjs.org/fscreen/-/fscreen-1.2.0.tgz"
integrity sha512-hlq4+BU0hlPmwsFjwGGzZ+OZ9N/wq9Ljg/sq3pX+2CD7hrJsX9tJgWWK/wiNTFM212CLHWhicOoqwXyZGGetJg==
fsevents@~2.3.2:
version "2.3.2"
resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz"
integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
function-bind@^1.1.1:
version "1.1.1"
resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz"
@ -3252,9 +3252,9 @@ json-stable-stringify-without-jsonify@^1.0.1:
integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==
json5@^1.0.1:
version "1.0.2"
resolved "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz"
integrity sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==
version "1.0.1"
resolved "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz"
integrity sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==
dependencies:
minimist "^1.2.0"
@ -3527,10 +3527,10 @@ natural-compare@^1.4.0:
resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz"
integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==
node-fetch-native@^1.0.1:
version "1.0.1"
resolved "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.0.1.tgz"
integrity sha512-VzW+TAk2wE4X9maiKMlT+GsPU4OMmR1U9CrHSmd3DFLn2IcZ9VJ6M6BBugGfYUnPCLSYxXdZy17M0BEJyhUTwg==
node-fetch-native@^1.0.2:
version "1.0.2"
resolved "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.0.2.tgz"
integrity sha512-KIkvH1jl6b3O7es/0ShyCgWLcfXxlBrLBbP3rOr23WArC66IMcU4DeZEeYEOwnopYhawLTn7/y+YtmASe8DFVQ==
node-releases@^2.0.8:
version "2.0.10"
@ -3625,13 +3625,13 @@ object.values@^1.1.5:
es-abstract "^1.20.4"
ofetch@^1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/ofetch/-/ofetch-1.0.0.tgz"
integrity sha512-d40aof8czZFSQKJa4+F7Ch3UC5D631cK1TTUoK+iNEut9NoiCL+u0vykl/puYVUS2df4tIQl5upQcolIcEzQjQ==
version "1.0.1"
resolved "https://registry.npmjs.org/ofetch/-/ofetch-1.0.1.tgz"
integrity sha512-icBz2JYfEpt+wZz1FRoGcrMigjNKjzvufE26m9+yUiacRQRHwnNlGRPiDnW4op7WX/MR6aniwS8xw8jyVelF2g==
dependencies:
destr "^1.2.1"
node-fetch-native "^1.0.1"
ufo "^1.0.0"
destr "^1.2.2"
node-fetch-native "^1.0.2"
ufo "^1.1.0"
once@^1.3.0:
version "1.4.0"
@ -4746,7 +4746,7 @@ typescript@*, typescript@^4.6.4, "typescript@>=2.8.0 || >= 3.2.0-dev || >= 3.3.0
resolved "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz"
integrity sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==
ufo@^1.0.0, ufo@^1.1.0:
ufo@^1.1.0:
version "1.1.0"
resolved "https://registry.npmjs.org/ufo/-/ufo-1.1.0.tgz"
integrity sha512-LQc2s/ZDMaCN3QLpa+uzHUOQ7SdV0qgv3VBXOolQGXTaaZpIur6PwUclF5nN2hNkiTRcUugXd1zFOW3FLJ135Q==