diff --git a/public/config.js b/public/config.js index eb936081..b69f60eb 100644 --- a/public/config.js +++ b/public/config.js @@ -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", }; diff --git a/src/components/Icon.tsx b/src/components/Icon.tsx index ff1cef8c..382b7133 100644 --- a/src/components/Icon.tsx +++ b/src/components/Icon.tsx @@ -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 = { circle_exclamation: ``, casting: "", download: ``, + pictureInPicture: ``, }; function ChromeCastButton() { diff --git a/src/setup/index.css b/src/setup/index.css index c3d97c26..ebb56bec 100644 --- a/src/setup/index.css +++ b/src/setup/index.css @@ -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; } diff --git a/src/setup/locales/en/translation.json b/src/setup/locales/en/translation.json index 78832ad1..daf248c1 100644 --- a/src/setup/locales/en/translation.json +++ b/src/setup/locales/en/translation.json @@ -61,7 +61,8 @@ "episodes": "Episodes", "source": "Source", "captions": "Captions", - "download": "Download" + "download": "Download", + "pictureInPicture": "Picture in Picture" }, "popouts": { "sources": "Sources", diff --git a/src/utils/detectFeatures.ts b/src/utils/detectFeatures.ts index 15be4c69..af62720d 100644 --- a/src/utils/detectFeatures.ts +++ b/src/utils/detectFeatures.ts @@ -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"); +} diff --git a/src/video/components/VideoPlayer.tsx b/src/video/components/VideoPlayer.tsx index 9553b70c..7aef90bc 100644 --- a/src/video/components/VideoPlayer.tsx +++ b/src/video/components/VideoPlayer.tsx @@ -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) {
+ @@ -161,6 +163,7 @@ export function VideoPlayer(props: Props) { + diff --git a/src/video/components/actions/DownloadAction.tsx b/src/video/components/actions/DownloadAction.tsx index 10d59ce5..307f14c7 100644 --- a/src/video/components/actions/DownloadAction.tsx +++ b/src/video/components/actions/DownloadAction.tsx @@ -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} > { + controls.togglePictureInPicture(); + }, [controls]); + + if (!canPictureInPicture() && !canWebkitPictureInPicture()) return null; + + return ( + + ); +} diff --git a/src/video/state/logic/controls.ts b/src/video/state/logic/controls.ts index 56c89a10..f21ec05a 100644 --- a/src/video/state/logic/controls.ts +++ b/src/video/state/logic/controls.ts @@ -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); + }, }; } diff --git a/src/video/state/providers/castingStateProvider.ts b/src/video/state/providers/castingStateProvider.ts index 961a616d..ab111f37 100644 --- a/src/video/state/providers/castingStateProvider.ts +++ b/src/video/state/providers/castingStateProvider.ts @@ -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); diff --git a/src/video/state/providers/providerTypes.ts b/src/video/state/providers/providerTypes.ts index e34b950d..1643a980 100644 --- a/src/video/state/providers/providerTypes.ts +++ b/src/video/state/providers/providerTypes.ts @@ -19,6 +19,7 @@ export type VideoPlayerStateController = { setCaption(id: string, url: string): void; clearCaption(): void; getId(): string; + togglePictureInPicture(): void; }; export type VideoPlayerStateProvider = VideoPlayerStateController & { diff --git a/src/video/state/providers/videoStateProvider.ts b/src/video/state/providers/videoStateProvider.ts index e5fbaa38..0f0971b5 100644 --- a/src/video/state/providers/videoStateProvider.ts +++ b/src/video/state/providers/videoStateProvider.ts @@ -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()); diff --git a/yarn.lock b/yarn.lock index 0d443fe6..551fa234 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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==