mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-05-24 02:02:36 -04:00
Add subtitles to the player
This commit is contained in:
parent
9b62cb8a93
commit
6eccb2cede
@ -20,7 +20,7 @@ services:
|
|||||||
- postgres
|
- postgres
|
||||||
volumes:
|
volumes:
|
||||||
- ./back:/app
|
- ./back:/app
|
||||||
- /app/out
|
- /app/out/
|
||||||
- kyoo:/var/lib/kyoo
|
- kyoo:/var/lib/kyoo
|
||||||
- ./video:/video
|
- ./video:/video
|
||||||
front:
|
front:
|
||||||
@ -29,7 +29,8 @@ services:
|
|||||||
dockerfile: Dockerfile.dev
|
dockerfile: Dockerfile.dev
|
||||||
volumes:
|
volumes:
|
||||||
- ./front:/app
|
- ./front:/app
|
||||||
- /app/nodes_modules
|
- /app/node_modules/
|
||||||
|
- /app/.next/
|
||||||
ports:
|
ports:
|
||||||
- "3000:3000"
|
- "3000:3000"
|
||||||
restart: on-failure
|
restart: on-failure
|
||||||
@ -55,6 +56,7 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
- POSTGRES_USER=${POSTGRES_USER}
|
- POSTGRES_USER=${POSTGRES_USER}
|
||||||
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
|
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
|
||||||
|
- POSTGRES_DB=${POSTGRES_DB}
|
||||||
volumes:
|
volumes:
|
||||||
- db:/var/lib/postgresql/data
|
- db:/var/lib/postgresql/data
|
||||||
|
|
||||||
|
@ -7,5 +7,6 @@
|
|||||||
"mute": "Toggle mute",
|
"mute": "Toggle mute",
|
||||||
"volume": "Volume",
|
"volume": "Volume",
|
||||||
"subtitles": "Subtitles",
|
"subtitles": "Subtitles",
|
||||||
|
"subtitle-none": "None",
|
||||||
"fullscreen": "Fullscreen"
|
"fullscreen": "Fullscreen"
|
||||||
}
|
}
|
||||||
|
@ -7,5 +7,6 @@
|
|||||||
"mute": "Muet",
|
"mute": "Muet",
|
||||||
"volume": "Volume",
|
"volume": "Volume",
|
||||||
"subtitles": "Sous titres",
|
"subtitles": "Sous titres",
|
||||||
|
"subtitle-none": "Aucun",
|
||||||
"fullscreen": "Plein-écran"
|
"fullscreen": "Plein-écran"
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,8 @@
|
|||||||
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
* along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
const CopyPlugin = require("copy-webpack-plugin");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {import("next").NextConfig}
|
* @type {import("next").NextConfig}
|
||||||
*/
|
*/
|
||||||
@ -25,6 +27,21 @@ const nextConfig = {
|
|||||||
reactStrictMode: true,
|
reactStrictMode: true,
|
||||||
swcMinify: true,
|
swcMinify: true,
|
||||||
output: "standalone",
|
output: "standalone",
|
||||||
|
webpack: (config) => {
|
||||||
|
config.plugins = [
|
||||||
|
...config.plugins,
|
||||||
|
new CopyPlugin({
|
||||||
|
patterns: [
|
||||||
|
{
|
||||||
|
context: "node_modules/@jellyfin/libass-wasm/dist/js/",
|
||||||
|
from: "*",
|
||||||
|
to: "static/chunks/",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
return config;
|
||||||
|
},
|
||||||
async redirects() {
|
async redirects() {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@emotion/react": "^11.9.3",
|
"@emotion/react": "^11.9.3",
|
||||||
"@emotion/styled": "^11.9.3",
|
"@emotion/styled": "^11.9.3",
|
||||||
|
"@jellyfin/libass-wasm": "^4.1.1",
|
||||||
"@mui/icons-material": "^5.8.4",
|
"@mui/icons-material": "^5.8.4",
|
||||||
"@mui/material": "^5.8.7",
|
"@mui/material": "^5.8.7",
|
||||||
"next": "12.2.2",
|
"next": "12.2.2",
|
||||||
@ -38,6 +39,7 @@
|
|||||||
"@types/node": "18.0.3",
|
"@types/node": "18.0.3",
|
||||||
"@types/react": "18.0.15",
|
"@types/react": "18.0.15",
|
||||||
"@types/react-dom": "18.0.6",
|
"@types/react-dom": "18.0.6",
|
||||||
|
"copy-webpack-plugin": "^11.0.0",
|
||||||
"eslint": "8.19.0",
|
"eslint": "8.19.0",
|
||||||
"eslint-config-next": "12.2.2",
|
"eslint-config-next": "12.2.2",
|
||||||
"eslint-config-prettier": "^8.5.0",
|
"eslint-config-prettier": "^8.5.0",
|
||||||
|
@ -25,7 +25,11 @@ import { Link } from "~/utils/link";
|
|||||||
import { Image } from "./poster";
|
import { Image } from "./poster";
|
||||||
|
|
||||||
export const episodeDisplayNumber = (
|
export const episodeDisplayNumber = (
|
||||||
episode: { seasonNumber?: number; episodeNumber?: number; absoluteNumber?: number },
|
episode: {
|
||||||
|
seasonNumber?: number | null;
|
||||||
|
episodeNumber?: number | null;
|
||||||
|
absoluteNumber?: number | null;
|
||||||
|
},
|
||||||
def?: string,
|
def?: string,
|
||||||
) => {
|
) => {
|
||||||
if (typeof episode.seasonNumber === "number" && typeof episode.episodeNumber === "number")
|
if (typeof episode.seasonNumber === "number" && typeof episode.episodeNumber === "number")
|
||||||
|
@ -60,6 +60,7 @@ export const TrackP = ResourceP.extend({
|
|||||||
*/
|
*/
|
||||||
displayName: z.string(),
|
displayName: z.string(),
|
||||||
});
|
});
|
||||||
|
export type Track = z.infer<typeof TrackP>;
|
||||||
|
|
||||||
export const ChapterP = z.object({
|
export const ChapterP = z.object({
|
||||||
/**
|
/**
|
||||||
|
@ -228,6 +228,78 @@ const Item = ({ item, layout }: { item?: LibraryItem; layout: Layout }) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const SortByMenu = ({
|
||||||
|
sortKey,
|
||||||
|
setSort,
|
||||||
|
sortOrd,
|
||||||
|
setSortOrd,
|
||||||
|
anchor,
|
||||||
|
onClose,
|
||||||
|
}: {
|
||||||
|
sortKey: SortBy;
|
||||||
|
setSort: (sort: SortBy) => void;
|
||||||
|
sortOrd: SortOrd;
|
||||||
|
setSortOrd: (sort: SortOrd) => void;
|
||||||
|
anchor: HTMLElement;
|
||||||
|
onClose: () => void;
|
||||||
|
}) => {
|
||||||
|
const router = useRouter();
|
||||||
|
const { t } = useTranslation("browse");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Menu
|
||||||
|
id="sortby-menu"
|
||||||
|
MenuListProps={{
|
||||||
|
"aria-labelledby": "sortby",
|
||||||
|
}}
|
||||||
|
anchorEl={anchor}
|
||||||
|
open={!!anchor}
|
||||||
|
onClose={onClose}
|
||||||
|
>
|
||||||
|
{Object.values(SortBy).map((x) => (
|
||||||
|
<MenuItem
|
||||||
|
key={x}
|
||||||
|
selected={sortKey === x}
|
||||||
|
onClick={() => setSort(x)}
|
||||||
|
component={Link}
|
||||||
|
to={{ query: { ...router.query, sortBy: `${sortKey}-${sortOrd}` } }}
|
||||||
|
shallow
|
||||||
|
replace
|
||||||
|
>
|
||||||
|
<ListItemText>{t(`browse.sortkey.${x}`)}</ListItemText>
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
<Divider />
|
||||||
|
<MenuItem
|
||||||
|
selected={sortOrd === SortOrd.Asc}
|
||||||
|
onClick={() => setSortOrd(SortOrd.Asc)}
|
||||||
|
component={Link}
|
||||||
|
to={{ query: { ...router.query, sortBy: `${sortKey}-${sortOrd}` } }}
|
||||||
|
shallow
|
||||||
|
replace
|
||||||
|
>
|
||||||
|
<ListItemIcon>
|
||||||
|
<South fontSize="small" />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText>{t("browse.sortord.asc")}</ListItemText>
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem
|
||||||
|
selected={sortOrd === SortOrd.Desc}
|
||||||
|
onClick={() => setSortOrd(SortOrd.Desc)}
|
||||||
|
component={Link}
|
||||||
|
to={{ query: { ...router.query, sortBy: `${sortKey}-${sortOrd}` } }}
|
||||||
|
shallow
|
||||||
|
replace
|
||||||
|
>
|
||||||
|
<ListItemIcon>
|
||||||
|
<North fontSize="small" />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText>{t("browse.sortord.desc")}</ListItemText>
|
||||||
|
</MenuItem>
|
||||||
|
</Menu>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const BrowseSettings = ({
|
const BrowseSettings = ({
|
||||||
sortKey,
|
sortKey,
|
||||||
setSort,
|
setSort,
|
||||||
@ -244,7 +316,6 @@ const BrowseSettings = ({
|
|||||||
setLayout: (layout: Layout) => void;
|
setLayout: (layout: Layout) => void;
|
||||||
}) => {
|
}) => {
|
||||||
const [sortAnchor, setSortAnchor] = useState<HTMLElement | null>(null);
|
const [sortAnchor, setSortAnchor] = useState<HTMLElement | null>(null);
|
||||||
const router = useRouter();
|
|
||||||
const { t } = useTranslation("browse");
|
const { t } = useTranslation("browse");
|
||||||
|
|
||||||
const switchViewTitle = layout === Layout.Grid
|
const switchViewTitle = layout === Layout.Grid
|
||||||
@ -265,7 +336,7 @@ const BrowseSettings = ({
|
|||||||
aria-controls={sortAnchor ? "sorby-menu" : undefined}
|
aria-controls={sortAnchor ? "sorby-menu" : undefined}
|
||||||
aria-haspopup="true"
|
aria-haspopup="true"
|
||||||
aria-expanded={sortAnchor ? "true" : undefined}
|
aria-expanded={sortAnchor ? "true" : undefined}
|
||||||
onClick={(event: MouseEvent<HTMLElement>) => setSortAnchor(event.currentTarget)}
|
onClick={(event) => setSortAnchor(event.currentTarget)}
|
||||||
>
|
>
|
||||||
<Sort />
|
<Sort />
|
||||||
{t("browse.sortby", { key: t(`browse.sortkey.${sortKey}`) })}
|
{t("browse.sortby", { key: t(`browse.sortkey.${sortKey}`) })}
|
||||||
@ -282,56 +353,16 @@ const BrowseSettings = ({
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
</Box>
|
</Box>
|
||||||
<Menu
|
{sortAnchor && (
|
||||||
id="sortby-menu"
|
<SortByMenu
|
||||||
MenuListProps={{
|
sortKey={sortKey}
|
||||||
"aria-labelledby": "sortby",
|
sortOrd={sortOrd}
|
||||||
}}
|
setSort={setSort}
|
||||||
anchorEl={sortAnchor}
|
setSortOrd={setSortOrd}
|
||||||
open={!!sortAnchor}
|
anchor={sortAnchor}
|
||||||
onClose={() => setSortAnchor(null)}
|
onClose={() => setSortAnchor(null)}
|
||||||
>
|
/>
|
||||||
{Object.values(SortBy).map((x) => (
|
)}
|
||||||
<MenuItem
|
|
||||||
key={x}
|
|
||||||
selected={sortKey === x}
|
|
||||||
onClick={() => setSort(x)}
|
|
||||||
component={Link}
|
|
||||||
to={{ query: { ...router.query, sortBy: `${sortKey}-${sortOrd}` } }}
|
|
||||||
shallow
|
|
||||||
replace
|
|
||||||
>
|
|
||||||
<ListItemText>{t(`browse.sortkey.${x}`)}</ListItemText>
|
|
||||||
</MenuItem>
|
|
||||||
))}
|
|
||||||
<Divider />
|
|
||||||
<MenuItem
|
|
||||||
selected={sortOrd === SortOrd.Asc}
|
|
||||||
onClick={() => setSortOrd(SortOrd.Asc)}
|
|
||||||
component={Link}
|
|
||||||
to={{ query: { ...router.query, sortBy: `${sortKey}-${sortOrd}` } }}
|
|
||||||
shallow
|
|
||||||
replace
|
|
||||||
>
|
|
||||||
<ListItemIcon>
|
|
||||||
<South fontSize="small" />
|
|
||||||
</ListItemIcon>
|
|
||||||
<ListItemText>{t("browse.sortord.asc")}</ListItemText>
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem
|
|
||||||
selected={sortOrd === SortOrd.Desc}
|
|
||||||
onClick={() => setSortOrd(SortOrd.Desc)}
|
|
||||||
component={Link}
|
|
||||||
to={{ query: { ...router.query, sortBy: `${sortKey}-${sortOrd}` } }}
|
|
||||||
shallow
|
|
||||||
replace
|
|
||||||
>
|
|
||||||
<ListItemIcon>
|
|
||||||
<North fontSize="small" />
|
|
||||||
</ListItemIcon>
|
|
||||||
<ListItemText>{t("browse.sortord.desc")}</ListItemText>
|
|
||||||
</MenuItem>
|
|
||||||
</Menu>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -20,10 +20,18 @@
|
|||||||
|
|
||||||
import { QueryIdentifier, QueryPage } from "~/utils/query";
|
import { QueryIdentifier, QueryPage } from "~/utils/query";
|
||||||
import { withRoute } from "~/utils/router";
|
import { withRoute } from "~/utils/router";
|
||||||
import { WatchItem, WatchItemP, Chapter } from "~/models/resources/watch-item";
|
import { WatchItem, WatchItemP, Chapter, Track } from "~/models/resources/watch-item";
|
||||||
import { useFetch } from "~/utils/query";
|
import { useFetch } from "~/utils/query";
|
||||||
import { ErrorPage } from "~/components/errors";
|
import { ErrorPage } from "~/components/errors";
|
||||||
import { useState, useRef, useEffect, HTMLProps, memo, useMemo, useCallback } from "react";
|
import {
|
||||||
|
useState,
|
||||||
|
useRef,
|
||||||
|
useEffect,
|
||||||
|
memo,
|
||||||
|
useMemo,
|
||||||
|
useCallback,
|
||||||
|
RefObject,
|
||||||
|
} from "react";
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
CircularProgress,
|
CircularProgress,
|
||||||
@ -32,6 +40,10 @@ import {
|
|||||||
Typography,
|
Typography,
|
||||||
Skeleton,
|
Skeleton,
|
||||||
Slider,
|
Slider,
|
||||||
|
Menu,
|
||||||
|
MenuItem,
|
||||||
|
ListItemText,
|
||||||
|
BoxProps,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import useTranslation from "next-translate/useTranslation";
|
import useTranslation from "next-translate/useTranslation";
|
||||||
import {
|
import {
|
||||||
@ -50,7 +62,11 @@ import {
|
|||||||
} from "@mui/icons-material";
|
} from "@mui/icons-material";
|
||||||
import { Poster } from "~/components/poster";
|
import { Poster } from "~/components/poster";
|
||||||
import { episodeDisplayNumber } from "~/components/episode";
|
import { episodeDisplayNumber } from "~/components/episode";
|
||||||
|
import { Link } from "~/utils/link";
|
||||||
import NextLink from "next/link";
|
import NextLink from "next/link";
|
||||||
|
import { useRouter } from "next/router";
|
||||||
|
// @ts-ignore
|
||||||
|
import SubtitleOctopus from "@jellyfin/libass-wasm"
|
||||||
|
|
||||||
const toTimerString = (timer: number, duration?: number) => {
|
const toTimerString = (timer: number, duration?: number) => {
|
||||||
if (!duration) duration = timer;
|
if (!duration) duration = timer;
|
||||||
@ -58,6 +74,74 @@ const toTimerString = (timer: number, duration?: number) => {
|
|||||||
return new Date(timer * 1000).toISOString().substring(14, 19);
|
return new Date(timer * 1000).toISOString().substring(14, 19);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const SubtitleMenu = ({
|
||||||
|
subtitles,
|
||||||
|
setSubtitle,
|
||||||
|
selectedID,
|
||||||
|
anchor,
|
||||||
|
onClose,
|
||||||
|
}: {
|
||||||
|
subtitles: Track[];
|
||||||
|
setSubtitle: (subtitle: Track | null) => void;
|
||||||
|
selectedID?: number;
|
||||||
|
anchor: HTMLElement;
|
||||||
|
onClose: () => void;
|
||||||
|
}) => {
|
||||||
|
const router = useRouter();
|
||||||
|
const { t } = useTranslation("player");
|
||||||
|
const { subtitle, ...queryWithoutSubs } = router.query;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Menu
|
||||||
|
id="subtitle-menu"
|
||||||
|
MenuListProps={{
|
||||||
|
"aria-labelledby": "subtitle",
|
||||||
|
}}
|
||||||
|
anchorEl={anchor}
|
||||||
|
open={!!anchor}
|
||||||
|
onClose={onClose}
|
||||||
|
anchorOrigin={{
|
||||||
|
vertical: "top",
|
||||||
|
horizontal: "center",
|
||||||
|
}}
|
||||||
|
transformOrigin={{
|
||||||
|
vertical: "bottom",
|
||||||
|
horizontal: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<MenuItem
|
||||||
|
selected={!selectedID}
|
||||||
|
onClick={() => {
|
||||||
|
setSubtitle(null);
|
||||||
|
onClose();
|
||||||
|
}}
|
||||||
|
component={Link}
|
||||||
|
to={{ query: queryWithoutSubs }}
|
||||||
|
shallow
|
||||||
|
replace
|
||||||
|
>
|
||||||
|
<ListItemText>{t("subtitle-none")}</ListItemText>
|
||||||
|
</MenuItem>
|
||||||
|
{subtitles.map((sub) => (
|
||||||
|
<MenuItem
|
||||||
|
key={sub.id}
|
||||||
|
selected={selectedID == sub.id}
|
||||||
|
onClick={() => {
|
||||||
|
setSubtitle(sub);
|
||||||
|
onClose();
|
||||||
|
}}
|
||||||
|
component={Link}
|
||||||
|
to={{ query: { ...router.query, subtitle: sub.language ?? sub.id } }}
|
||||||
|
shallow
|
||||||
|
replace
|
||||||
|
>
|
||||||
|
<ListItemText>{sub.displayName}</ListItemText>
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Menu>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const LoadingIndicator = () => {
|
const LoadingIndicator = () => {
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
@ -304,24 +388,50 @@ const LeftButtons = memo(function LeftButtons({
|
|||||||
const RightButtons = memo(function RightButton({
|
const RightButtons = memo(function RightButton({
|
||||||
isFullscreen,
|
isFullscreen,
|
||||||
toggleFullscreen,
|
toggleFullscreen,
|
||||||
|
subtitles,
|
||||||
|
selectedSubtitle,
|
||||||
|
selectSubtitle,
|
||||||
}: {
|
}: {
|
||||||
isFullscreen: boolean;
|
isFullscreen: boolean;
|
||||||
toggleFullscreen: () => void;
|
toggleFullscreen: () => void;
|
||||||
|
subtitles?: Track[];
|
||||||
|
selectedSubtitle: Track | null;
|
||||||
|
selectSubtitle: (track: Track | null) => void;
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation("player");
|
const { t } = useTranslation("player");
|
||||||
|
const [subtitleAnchor, setSubtitleAnchor] = useState<HTMLButtonElement | null>(null);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ "> *": { mx: "8px !important" } }}>
|
<Box sx={{ "> *": { m: "8px !important" } }}>
|
||||||
<Tooltip title={t("subtitles")}>
|
{subtitles && (
|
||||||
<IconButton aria-label={t("subtitles")} sx={{ color: "white" }}>
|
<Tooltip title={t("subtitles")}>
|
||||||
<ClosedCaption />
|
<IconButton
|
||||||
</IconButton>
|
id="sortby"
|
||||||
</Tooltip>
|
aria-label={t("subtitles")}
|
||||||
|
aria-controls={subtitleAnchor ? "subtitle-menu" : undefined}
|
||||||
|
aria-haspopup="true"
|
||||||
|
aria-expanded={subtitleAnchor ? "true" : undefined}
|
||||||
|
onClick={(event) => setSubtitleAnchor(event.currentTarget)}
|
||||||
|
sx={{ color: "white" }}
|
||||||
|
>
|
||||||
|
<ClosedCaption />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
<Tooltip title={t("fullscreen")}>
|
<Tooltip title={t("fullscreen")}>
|
||||||
<IconButton onClick={toggleFullscreen} aria-label={t("fullscreen")} sx={{ color: "white" }}>
|
<IconButton onClick={toggleFullscreen} aria-label={t("fullscreen")} sx={{ color: "white" }}>
|
||||||
{isFullscreen ? <FullscreenExit /> : <Fullscreen />}
|
{isFullscreen ? <FullscreenExit /> : <Fullscreen />}
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
{subtitleAnchor && (
|
||||||
|
<SubtitleMenu
|
||||||
|
subtitles={subtitles!}
|
||||||
|
anchor={subtitleAnchor}
|
||||||
|
setSubtitle={selectSubtitle}
|
||||||
|
selectedID={selectedSubtitle?.id}
|
||||||
|
onClose={() => setSubtitleAnchor(null)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -356,6 +466,67 @@ const Back = memo(function Back({ name, href }: { name?: string; href: string })
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const useSubtitleController = (player: RefObject<HTMLVideoElement>): [Track | null, (value: Track | null) => void] => {
|
||||||
|
const [selectedSubtitle, setSubtitle] = useState<Track | null>(null);
|
||||||
|
const [htmlTrack, setHtmlTrack] = useState<HTMLTrackElement | null>(null);
|
||||||
|
const [subocto, setSubOcto] = useState<SubtitleOctopus | null>(null);
|
||||||
|
|
||||||
|
return [
|
||||||
|
selectedSubtitle,
|
||||||
|
useCallback(
|
||||||
|
(value: Track | null) => {
|
||||||
|
const removeHtmlSubtitle = () => {
|
||||||
|
if (htmlTrack) htmlTrack.remove();
|
||||||
|
setHtmlTrack(null);
|
||||||
|
};
|
||||||
|
const removeOctoSub = () => {
|
||||||
|
if (subocto) {
|
||||||
|
subocto.freeTrack();
|
||||||
|
subocto.dispose();
|
||||||
|
}
|
||||||
|
setSubOcto(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!player.current) return;
|
||||||
|
|
||||||
|
setSubtitle(value);
|
||||||
|
if (!value) {
|
||||||
|
removeHtmlSubtitle();
|
||||||
|
removeOctoSub();
|
||||||
|
} else if (value.codec === "vtt" || value.codec === "srt") {
|
||||||
|
removeOctoSub();
|
||||||
|
const track: HTMLTrackElement = htmlTrack ?? document.createElement("track");
|
||||||
|
track.kind = "subtitles";
|
||||||
|
track.label = value.displayName;
|
||||||
|
if (value.language) track.srclang = value.language;
|
||||||
|
track.src = `subtitle/${value.slug}.vtt`;
|
||||||
|
track.className = "subtitle_container";
|
||||||
|
track.default = true;
|
||||||
|
track.onload = () => {
|
||||||
|
if (player.current) player.current.textTracks[0].mode = "showing";
|
||||||
|
};
|
||||||
|
player.current.appendChild(track);
|
||||||
|
setHtmlTrack(track);
|
||||||
|
} else if (value.codec === "ass") {
|
||||||
|
removeHtmlSubtitle();
|
||||||
|
removeOctoSub();
|
||||||
|
setSubOcto(
|
||||||
|
new SubtitleOctopus({
|
||||||
|
video: player.current,
|
||||||
|
subUrl: `/api/subtitle/${value.slug}`,
|
||||||
|
workerUrl: "/_next/static/chunks/subtitles-octopus-worker.js",
|
||||||
|
legacyWorkerUrl: "/_next/static/chunks/subtitles-octopus-worker-legacy.js",
|
||||||
|
/* fonts: */
|
||||||
|
renderMode: "wasm-blend",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[htmlTrack, subocto, player],
|
||||||
|
),
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
const useVideoController = () => {
|
const useVideoController = () => {
|
||||||
const player = useRef<HTMLVideoElement>(null);
|
const player = useRef<HTMLVideoElement>(null);
|
||||||
const [isPlaying, setPlay] = useState(true);
|
const [isPlaying, setPlay] = useState(true);
|
||||||
@ -366,6 +537,7 @@ const useVideoController = () => {
|
|||||||
const [volume, setVolume] = useState(100);
|
const [volume, setVolume] = useState(100);
|
||||||
const [isMuted, setMute] = useState(false);
|
const [isMuted, setMute] = useState(false);
|
||||||
const [isFullscreen, setFullscreen] = useState(false);
|
const [isFullscreen, setFullscreen] = useState(false);
|
||||||
|
const [selectedSubtitle, selectSubtitle] = useSubtitleController(player);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!player?.current?.duration) return;
|
if (!player?.current?.duration) return;
|
||||||
@ -373,7 +545,7 @@ const useVideoController = () => {
|
|||||||
}, [player]);
|
}, [player]);
|
||||||
|
|
||||||
const togglePlay = useCallback(() => {
|
const togglePlay = useCallback(() => {
|
||||||
if (!player?.current) return;
|
if (!player.current) return;
|
||||||
if (!isPlaying) {
|
if (!isPlaying) {
|
||||||
player.current.play();
|
player.current.play();
|
||||||
} else {
|
} else {
|
||||||
@ -390,7 +562,7 @@ const useVideoController = () => {
|
|||||||
}
|
}
|
||||||
}, [isFullscreen]);
|
}, [isFullscreen]);
|
||||||
|
|
||||||
const videoProps: HTMLProps<HTMLVideoElement> = useMemo(
|
const videoProps: BoxProps<"video"> = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
ref: player,
|
ref: player,
|
||||||
onClick: togglePlay,
|
onClick: togglePlay,
|
||||||
@ -418,7 +590,17 @@ const useVideoController = () => {
|
|||||||
[player, togglePlay, toggleFullscreen],
|
[player, togglePlay, toggleFullscreen],
|
||||||
);
|
);
|
||||||
return {
|
return {
|
||||||
state: { isPlaying, isLoading, progress, duration, buffered, volume, isMuted, isFullscreen },
|
state: {
|
||||||
|
isPlaying,
|
||||||
|
isLoading,
|
||||||
|
progress,
|
||||||
|
duration,
|
||||||
|
buffered,
|
||||||
|
volume,
|
||||||
|
isMuted,
|
||||||
|
isFullscreen,
|
||||||
|
selectedSubtitle,
|
||||||
|
},
|
||||||
videoProps,
|
videoProps,
|
||||||
togglePlay,
|
togglePlay,
|
||||||
toggleMute: useCallback(() => {
|
toggleMute: useCallback(() => {
|
||||||
@ -439,6 +621,7 @@ const useVideoController = () => {
|
|||||||
},
|
},
|
||||||
[player],
|
[player],
|
||||||
),
|
),
|
||||||
|
selectSubtitle,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -447,19 +630,31 @@ const query = (slug: string): QueryIdentifier<WatchItem> => ({
|
|||||||
parser: WatchItemP,
|
parser: WatchItemP,
|
||||||
});
|
});
|
||||||
|
|
||||||
//
|
// Callback used to hide the controls when the mouse goes iddle. This is stored globally to clear the old timeout
|
||||||
|
// if the mouse moves again
|
||||||
let mouseCallback: NodeJS.Timeout;
|
let mouseCallback: NodeJS.Timeout;
|
||||||
|
|
||||||
const Player: QueryPage<{ slug: string }> = ({ slug }) => {
|
const Player: QueryPage<{ slug: string }> = ({ slug }) => {
|
||||||
const { data, error } = useFetch(query(slug));
|
const { data, error } = useFetch(query(slug));
|
||||||
const {
|
const {
|
||||||
state: { isPlaying, isLoading, progress, duration, buffered, volume, isMuted, isFullscreen },
|
state: {
|
||||||
|
isPlaying,
|
||||||
|
isLoading,
|
||||||
|
progress,
|
||||||
|
duration,
|
||||||
|
buffered,
|
||||||
|
volume,
|
||||||
|
isMuted,
|
||||||
|
isFullscreen,
|
||||||
|
selectedSubtitle,
|
||||||
|
},
|
||||||
videoProps,
|
videoProps,
|
||||||
togglePlay,
|
togglePlay,
|
||||||
toggleMute,
|
toggleMute,
|
||||||
toggleFullscreen,
|
toggleFullscreen,
|
||||||
setProgress,
|
setProgress,
|
||||||
setVolume,
|
setVolume,
|
||||||
|
selectSubtitle,
|
||||||
} = useVideoController();
|
} = useVideoController();
|
||||||
const [showHover, setHover] = useState(false);
|
const [showHover, setHover] = useState(false);
|
||||||
const [mouseMoved, setMouseMoved] = useState(false);
|
const [mouseMoved, setMouseMoved] = useState(false);
|
||||||
@ -494,7 +689,7 @@ const Player: QueryPage<{ slug: string }> = ({ slug }) => {
|
|||||||
<Box
|
<Box
|
||||||
component="video"
|
component="video"
|
||||||
src={data?.link.direct}
|
src={data?.link.direct}
|
||||||
{...(videoProps as any)}
|
{...videoProps}
|
||||||
sx={{
|
sx={{
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
top: 0,
|
top: 0,
|
||||||
@ -511,15 +706,19 @@ const Player: QueryPage<{ slug: string }> = ({ slug }) => {
|
|||||||
<Box
|
<Box
|
||||||
onMouseEnter={() => setHover(true)}
|
onMouseEnter={() => setHover(true)}
|
||||||
onMouseLeave={() => setHover(false)}
|
onMouseLeave={() => setHover(false)}
|
||||||
sx={ displayControls ? {
|
sx={
|
||||||
visibility: "visible",
|
displayControls
|
||||||
opacity: 1,
|
? {
|
||||||
transition: "opacity .2s ease-in",
|
visibility: "visible",
|
||||||
} : {
|
opacity: 1,
|
||||||
visibility: "hidden",
|
transition: "opacity .2s ease-in",
|
||||||
opacity: 0,
|
}
|
||||||
transition: "opacity .4s ease-out, visibility 0s .4s",
|
: {
|
||||||
}}
|
visibility: "hidden",
|
||||||
|
opacity: 0,
|
||||||
|
transition: "opacity .4s ease-out, visibility 0s .4s",
|
||||||
|
}
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<Back
|
<Back
|
||||||
name={data?.name}
|
name={data?.name}
|
||||||
@ -566,7 +765,13 @@ const Player: QueryPage<{ slug: string }> = ({ slug }) => {
|
|||||||
{toTimerString(progress, duration)} : {toTimerString(duration)}
|
{toTimerString(progress, duration)} : {toTimerString(duration)}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<RightButtons isFullscreen={isFullscreen} toggleFullscreen={toggleFullscreen} />
|
<RightButtons
|
||||||
|
isFullscreen={isFullscreen}
|
||||||
|
toggleFullscreen={toggleFullscreen}
|
||||||
|
subtitles={data?.subtitles}
|
||||||
|
selectedSubtitle={selectedSubtitle}
|
||||||
|
selectSubtitle={selectSubtitle}
|
||||||
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
117
front/yarn.lock
117
front/yarn.lock
@ -195,6 +195,11 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45"
|
resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45"
|
||||||
integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==
|
integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==
|
||||||
|
|
||||||
|
"@jellyfin/libass-wasm@^4.1.1":
|
||||||
|
version "4.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@jellyfin/libass-wasm/-/libass-wasm-4.1.1.tgz#d1c0e789844e1ad5d3b36acaeb7351e59f5b7d9a"
|
||||||
|
integrity sha512-xQVJw+lZUg4U1TmLS80reBECfPtpCgRF8hhUSvUUQM9g68OvINyUU3K2yqRH+8tomGpghiRaIcr/bUJ83e0veA==
|
||||||
|
|
||||||
"@mui/base@5.0.0-alpha.88":
|
"@mui/base@5.0.0-alpha.88":
|
||||||
version "5.0.0-alpha.88"
|
version "5.0.0-alpha.88"
|
||||||
resolved "https://registry.yarnpkg.com/@mui/base/-/base-5.0.0-alpha.88.tgz#0930d1849c74ba62a28ab2d8533de88764173ba4"
|
resolved "https://registry.yarnpkg.com/@mui/base/-/base-5.0.0-alpha.88.tgz#0930d1849c74ba62a28ab2d8533de88764173ba4"
|
||||||
@ -404,6 +409,11 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@types/ms" "*"
|
"@types/ms" "*"
|
||||||
|
|
||||||
|
"@types/json-schema@^7.0.9":
|
||||||
|
version "7.0.11"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3"
|
||||||
|
integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==
|
||||||
|
|
||||||
"@types/json5@^0.0.29":
|
"@types/json5@^0.0.29":
|
||||||
version "0.0.29"
|
version "0.0.29"
|
||||||
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
|
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
|
||||||
@ -535,6 +545,20 @@ acorn@^8.7.1:
|
|||||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.1.tgz#0197122c843d1bf6d0a5e83220a788f278f63c30"
|
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.1.tgz#0197122c843d1bf6d0a5e83220a788f278f63c30"
|
||||||
integrity sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==
|
integrity sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==
|
||||||
|
|
||||||
|
ajv-formats@^2.1.1:
|
||||||
|
version "2.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-2.1.1.tgz#6e669400659eb74973bbf2e33327180a0996b520"
|
||||||
|
integrity sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==
|
||||||
|
dependencies:
|
||||||
|
ajv "^8.0.0"
|
||||||
|
|
||||||
|
ajv-keywords@^5.0.0:
|
||||||
|
version "5.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-5.1.0.tgz#69d4d385a4733cdbeab44964a1170a88f87f0e16"
|
||||||
|
integrity sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==
|
||||||
|
dependencies:
|
||||||
|
fast-deep-equal "^3.1.3"
|
||||||
|
|
||||||
ajv@^6.10.0, ajv@^6.12.4:
|
ajv@^6.10.0, ajv@^6.12.4:
|
||||||
version "6.12.6"
|
version "6.12.6"
|
||||||
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
|
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
|
||||||
@ -545,6 +569,16 @@ ajv@^6.10.0, ajv@^6.12.4:
|
|||||||
json-schema-traverse "^0.4.1"
|
json-schema-traverse "^0.4.1"
|
||||||
uri-js "^4.2.2"
|
uri-js "^4.2.2"
|
||||||
|
|
||||||
|
ajv@^8.0.0, ajv@^8.8.0:
|
||||||
|
version "8.11.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.11.0.tgz#977e91dd96ca669f54a11e23e378e33b884a565f"
|
||||||
|
integrity sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==
|
||||||
|
dependencies:
|
||||||
|
fast-deep-equal "^3.1.1"
|
||||||
|
json-schema-traverse "^1.0.0"
|
||||||
|
require-from-string "^2.0.2"
|
||||||
|
uri-js "^4.2.2"
|
||||||
|
|
||||||
ansi-regex@^5.0.1:
|
ansi-regex@^5.0.1:
|
||||||
version "5.0.1"
|
version "5.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304"
|
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304"
|
||||||
@ -774,6 +808,18 @@ copy-anything@^3.0.2:
|
|||||||
dependencies:
|
dependencies:
|
||||||
is-what "^4.1.6"
|
is-what "^4.1.6"
|
||||||
|
|
||||||
|
copy-webpack-plugin@^11.0.0:
|
||||||
|
version "11.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-11.0.0.tgz#96d4dbdb5f73d02dd72d0528d1958721ab72e04a"
|
||||||
|
integrity sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ==
|
||||||
|
dependencies:
|
||||||
|
fast-glob "^3.2.11"
|
||||||
|
glob-parent "^6.0.1"
|
||||||
|
globby "^13.1.1"
|
||||||
|
normalize-path "^3.0.0"
|
||||||
|
schema-utils "^4.0.0"
|
||||||
|
serialize-javascript "^6.0.0"
|
||||||
|
|
||||||
core-js-pure@^3.20.2:
|
core-js-pure@^3.20.2:
|
||||||
version "3.23.4"
|
version "3.23.4"
|
||||||
resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.23.4.tgz#aba5c7fb297063444f6bf93afb0362151679a012"
|
resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.23.4.tgz#aba5c7fb297063444f6bf93afb0362151679a012"
|
||||||
@ -1180,6 +1226,17 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
|
|||||||
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
|
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
|
||||||
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
|
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
|
||||||
|
|
||||||
|
fast-glob@^3.2.11:
|
||||||
|
version "3.2.12"
|
||||||
|
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.12.tgz#7f39ec99c2e6ab030337142da9e0c18f37afae80"
|
||||||
|
integrity sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==
|
||||||
|
dependencies:
|
||||||
|
"@nodelib/fs.stat" "^2.0.2"
|
||||||
|
"@nodelib/fs.walk" "^1.2.3"
|
||||||
|
glob-parent "^5.1.2"
|
||||||
|
merge2 "^1.3.0"
|
||||||
|
micromatch "^4.0.4"
|
||||||
|
|
||||||
fast-glob@^3.2.9:
|
fast-glob@^3.2.9:
|
||||||
version "3.2.11"
|
version "3.2.11"
|
||||||
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.11.tgz#a1172ad95ceb8a16e20caa5c5e56480e5129c1d9"
|
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.11.tgz#a1172ad95ceb8a16e20caa5c5e56480e5129c1d9"
|
||||||
@ -1351,6 +1408,17 @@ globby@^11.1.0:
|
|||||||
merge2 "^1.4.1"
|
merge2 "^1.4.1"
|
||||||
slash "^3.0.0"
|
slash "^3.0.0"
|
||||||
|
|
||||||
|
globby@^13.1.1:
|
||||||
|
version "13.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/globby/-/globby-13.1.2.tgz#29047105582427ab6eca4f905200667b056da515"
|
||||||
|
integrity sha512-LKSDZXToac40u8Q1PQtZihbNdTYSNMuWe+K5l+oa6KgDzSvVrHXlJy40hUP522RjAIoNLJYBJi7ow+rbFpIhHQ==
|
||||||
|
dependencies:
|
||||||
|
dir-glob "^3.0.1"
|
||||||
|
fast-glob "^3.2.11"
|
||||||
|
ignore "^5.2.0"
|
||||||
|
merge2 "^1.4.1"
|
||||||
|
slash "^4.0.0"
|
||||||
|
|
||||||
has-bigints@^1.0.1, has-bigints@^1.0.2:
|
has-bigints@^1.0.1, has-bigints@^1.0.2:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa"
|
resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa"
|
||||||
@ -1580,6 +1648,11 @@ json-schema-traverse@^0.4.1:
|
|||||||
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
|
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
|
||||||
integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
|
integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
|
||||||
|
|
||||||
|
json-schema-traverse@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2"
|
||||||
|
integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==
|
||||||
|
|
||||||
json-stable-stringify-without-jsonify@^1.0.1:
|
json-stable-stringify-without-jsonify@^1.0.1:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651"
|
resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651"
|
||||||
@ -1981,6 +2054,11 @@ next@12.2.2:
|
|||||||
"@next/swc-win32-ia32-msvc" "12.2.2"
|
"@next/swc-win32-ia32-msvc" "12.2.2"
|
||||||
"@next/swc-win32-x64-msvc" "12.2.2"
|
"@next/swc-win32-x64-msvc" "12.2.2"
|
||||||
|
|
||||||
|
normalize-path@^3.0.0:
|
||||||
|
version "3.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
|
||||||
|
integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
|
||||||
|
|
||||||
object-assign@^4.1.1:
|
object-assign@^4.1.1:
|
||||||
version "4.1.1"
|
version "4.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
|
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
|
||||||
@ -2183,6 +2261,13 @@ queue-microtask@^1.2.2:
|
|||||||
resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
|
resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
|
||||||
integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
|
integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
|
||||||
|
|
||||||
|
randombytes@^2.1.0:
|
||||||
|
version "2.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a"
|
||||||
|
integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==
|
||||||
|
dependencies:
|
||||||
|
safe-buffer "^5.1.0"
|
||||||
|
|
||||||
react-dom@18.2.0:
|
react-dom@18.2.0:
|
||||||
version "18.2.0"
|
version "18.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d"
|
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d"
|
||||||
@ -2260,6 +2345,11 @@ remove-accents@0.4.2:
|
|||||||
resolved "https://registry.yarnpkg.com/remove-accents/-/remove-accents-0.4.2.tgz#0a43d3aaae1e80db919e07ae254b285d9e1c7bb5"
|
resolved "https://registry.yarnpkg.com/remove-accents/-/remove-accents-0.4.2.tgz#0a43d3aaae1e80db919e07ae254b285d9e1c7bb5"
|
||||||
integrity sha512-7pXIJqJOq5tFgG1A2Zxti3Ht8jJF337m4sowbuHsW30ZnkQFnDzy9qBNhgzX8ZLW4+UBcXiiR7SwR6pokHsxiA==
|
integrity sha512-7pXIJqJOq5tFgG1A2Zxti3Ht8jJF337m4sowbuHsW30ZnkQFnDzy9qBNhgzX8ZLW4+UBcXiiR7SwR6pokHsxiA==
|
||||||
|
|
||||||
|
require-from-string@^2.0.2:
|
||||||
|
version "2.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909"
|
||||||
|
integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==
|
||||||
|
|
||||||
resolve-from@^4.0.0:
|
resolve-from@^4.0.0:
|
||||||
version "4.0.0"
|
version "4.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
|
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
|
||||||
@ -2309,6 +2399,11 @@ sade@^1.7.3:
|
|||||||
dependencies:
|
dependencies:
|
||||||
mri "^1.1.0"
|
mri "^1.1.0"
|
||||||
|
|
||||||
|
safe-buffer@^5.1.0:
|
||||||
|
version "5.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
|
||||||
|
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
|
||||||
|
|
||||||
safe-buffer@~5.1.1:
|
safe-buffer@~5.1.1:
|
||||||
version "5.1.2"
|
version "5.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
|
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
|
||||||
@ -2321,6 +2416,16 @@ scheduler@^0.23.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
loose-envify "^1.1.0"
|
loose-envify "^1.1.0"
|
||||||
|
|
||||||
|
schema-utils@^4.0.0:
|
||||||
|
version "4.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-4.0.0.tgz#60331e9e3ae78ec5d16353c467c34b3a0a1d3df7"
|
||||||
|
integrity sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==
|
||||||
|
dependencies:
|
||||||
|
"@types/json-schema" "^7.0.9"
|
||||||
|
ajv "^8.8.0"
|
||||||
|
ajv-formats "^2.1.1"
|
||||||
|
ajv-keywords "^5.0.0"
|
||||||
|
|
||||||
semver@^6.3.0:
|
semver@^6.3.0:
|
||||||
version "6.3.0"
|
version "6.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
|
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
|
||||||
@ -2333,6 +2438,13 @@ semver@^7.3.7:
|
|||||||
dependencies:
|
dependencies:
|
||||||
lru-cache "^6.0.0"
|
lru-cache "^6.0.0"
|
||||||
|
|
||||||
|
serialize-javascript@^6.0.0:
|
||||||
|
version "6.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz#efae5d88f45d7924141da8b5c3a7a7e663fefeb8"
|
||||||
|
integrity sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==
|
||||||
|
dependencies:
|
||||||
|
randombytes "^2.1.0"
|
||||||
|
|
||||||
shebang-command@^2.0.0:
|
shebang-command@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea"
|
resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea"
|
||||||
@ -2359,6 +2471,11 @@ slash@^3.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"
|
resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"
|
||||||
integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==
|
integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==
|
||||||
|
|
||||||
|
slash@^4.0.0:
|
||||||
|
version "4.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/slash/-/slash-4.0.0.tgz#2422372176c4c6c5addb5e2ada885af984b396a7"
|
||||||
|
integrity sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==
|
||||||
|
|
||||||
source-map-js@^1.0.1:
|
source-map-js@^1.0.1:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
|
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user