Skip to content

Commit

Permalink
format shenanigans
Browse files Browse the repository at this point in the history
  • Loading branch information
TheAlan404 committed May 1, 2024
1 parent b0c499a commit c5bf988
Show file tree
Hide file tree
Showing 11 changed files with 305 additions and 167 deletions.
1 change: 1 addition & 0 deletions src/api/context/VideoPlayerContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export interface VideoPlayerAPI {
setMuted: (m: boolean) => void;

seekTo: (ts: number) => void;
seekToChapterOffset: (offset: number) => void;
};

// @ts-ignore
Expand Down
36 changes: 32 additions & 4 deletions src/api/context/VideoPlayerProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ export const VideoPlayerProvider = ({
setAvailableFormats([]);
setActiveFormat(null);
setVideoInfo(null);
setActiveChapters({
chapters: [],
type: "video",
});
if(!videoID) return;

try {
Expand Down Expand Up @@ -81,7 +85,11 @@ export const VideoPlayerProvider = ({
if(!videoInfo) return;

setAvailableFormats(videoInfo.formats);
setActiveFormat(videoInfo.formats[0]);
setActiveFormat(videoInfo.formats
.filter(f => f.type == "basic")
.filter(f => currentInstance.type != "invidious" || f.isProxied)
.findLast(x=>x)
);
}, [videoInfo]);

useEffect(() => {
Expand Down Expand Up @@ -113,6 +121,27 @@ export const VideoPlayerProvider = ({
setPlayState("playing");
});

const seekTo = (ts: number) => {
videoElement.currentTime = clamp(0, ts, videoElement.duration);
};

const seekToChapterOffset = (offset: number = 0) => {
let currentChapterIndex = activeChapters.chapters.findIndex(c => c.time > videoElement.currentTime) - 1;
let currentChapter = activeChapters.chapters[currentChapterIndex];
if(!currentChapter) {
if(offset == 1) videoElement.fastSeek(videoElement.duration);
return;
}
let chapterProgress = videoElement.currentTime - currentChapter.time;
let targetIndex = currentChapterIndex + offset;
if(offset == -1) {
if(chapterProgress > 3) targetIndex++;
}
let target = activeChapters.chapters[targetIndex];

videoElement.fastSeek(clamp(0, target?.time || 0, videoElement.duration));
}

return (
<VideoPlayerContext.Provider value={{
videoElement,
Expand Down Expand Up @@ -165,9 +194,8 @@ export const VideoPlayerProvider = ({
muted,
setMuted,

seekTo: (ts: number) => {
videoElement.currentTime = clamp(0, ts, videoElement.duration);
},
seekTo,
seekToChapterOffset,
}}>
{children}
</VideoPlayerContext.Provider>
Expand Down
45 changes: 45 additions & 0 deletions src/components/cards/FormatCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { Code, Grid, Group, Paper, Stack, Text } from "@mantine/core";
import { VideoFormat } from "../../api/types/format";
import { IconCheck } from "@tabler/icons-react";

export const FormatCard = ({
format,
isSelected,
onClick,
}: {
format: VideoFormat,
isSelected?: boolean,
onClick?: () => void,
}) => {
return (
<Paper w="100%" withBorder p="xs" bg={isSelected ? "dark.6" : undefined} style={{
cursor: "pointer",
}} onClick={onClick}>
<Grid>
<Grid.Col span="content">
<Stack h="100%" justify="center" align="center">
{isSelected && (
<IconCheck color="var(--mantine-color-green-filled)" />
)}
</Stack>
</Grid.Col>
<Grid.Col span="auto">
<Stack align="start" gap={0}>
<Group w="100%" justify="space-between">
<Code>
{format.id}
</Code>
<Code>
{format.itag}
</Code>
</Group>
<Text>
{format.mimeType}
</Text>
</Stack>
</Grid.Col>
</Grid>
</Paper>
);
};

64 changes: 47 additions & 17 deletions src/components/options/OptionsContext.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { useDisclosure, useHotkeys } from "@mantine/hooks";
import { createContext, useState } from "react";
import { createContext, useCallback, useState } from "react";
import { useSoundEffect } from "../../hooks/useSoundEffect";
import { Drawer, ScrollArea } from "@mantine/core";
import { Button, Drawer, Group, ScrollArea, Text } from "@mantine/core";
import { OptionsRouter } from "./OptionsRouter";
import { IconArrowLeft } from "@tabler/icons-react";

export type OptionsView = "main" | "instanceSelect" | "openWith" | "formatSelect";

Expand All @@ -17,11 +18,11 @@ export interface OptionsContextAPI {

export const OptionsContext = createContext<OptionsContextAPI>({
opened: false,
open: () => {},
close: () => {},
toggle: () => {},
open: () => { },
close: () => { },
toggle: () => { },
view: "main",
setView: () => {},
setView: () => { },
});

export const OptionsProvider = ({ children }: React.PropsWithChildren) => {
Expand All @@ -30,24 +31,24 @@ export const OptionsProvider = ({ children }: React.PropsWithChildren) => {
const openOptionsSfx = useSoundEffect(["openSettings"]);
const closeOptionsSfx = useSoundEffect(["closeSettings"]);

const open = () => {
if(!opened) openOptionsSfx();
const open = useCallback(() => {
if (!opened) openOptionsSfx();
handlers.open();
};
}, [opened]);

const close = () => {
if(opened) closeOptionsSfx();
const close = useCallback(() => {
if (opened) closeOptionsSfx();
handlers.close();
setView("main");
};
}, [opened]);

const toggle = () => {
if(opened) {
const toggle = useCallback(() => {
if (opened) {
close();
} else {
open();
}
};
}, [opened]);

useHotkeys([
["o", () => toggle()],
Expand All @@ -56,7 +57,7 @@ export const OptionsProvider = ({ children }: React.PropsWithChildren) => {
useHotkeys([
["Ctrl + o", () => toggle()],
], [], true);

return (
<OptionsContext.Provider
value={{
Expand All @@ -70,11 +71,40 @@ export const OptionsProvider = ({ children }: React.PropsWithChildren) => {
>
<Drawer
opened={opened}
keepMounted
size="md"
onClose={() => close()}
position="right"
title="Options"
scrollAreaComponent={ScrollArea.Autosize}
styles={{
title: {
width: "100%",
}
}}
title={(
<Group w="100%" grow>
{view !== "main" && (
<Button
variant="light"
color="violet"
leftSection={<IconArrowLeft />}
onClick={() => setView("main")}
fullWidth
size="compact-md"
>
Back
</Button>
)}
<Text>
{({
main: "Options",
formatSelect: "Select Format",
instanceSelect: "Select Instance",
openWith: "Open with...",
} as Record<OptionsView, string>)[view]}
</Text>
</Group>
)}
>
<OptionsRouter />
</Drawer>
Expand Down
2 changes: 2 additions & 0 deletions src/components/options/OptionsRouter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { OptionsContext } from "./OptionsContext";
import { OptionsMainView } from "./views/MainView";
import { OptionsInstanceView } from "./views/InstanceView";
import { OptionsOpenWithView } from "./views/OpenWith";
import { OptionsFormatView } from "./views/FormatView";

export const OptionsRouter = () => {
const { view } = useContext(OptionsContext);
Expand All @@ -13,6 +14,7 @@ export const OptionsRouter = () => {
{view == "main" && <OptionsMainView />}
{view == "instanceSelect" && <OptionsInstanceView />}
{view == "openWith" && <OptionsOpenWithView />}
{view == "formatSelect" && <OptionsFormatView />}
</Stack>
);
};
53 changes: 24 additions & 29 deletions src/components/options/comps/FormatSelect.tsx
Original file line number Diff line number Diff line change
@@ -1,37 +1,32 @@
import { Group, Select, Stack } from "@mantine/core";
import { Grid, Select, Stack, Text, UnstyledButton } from "@mantine/core";
import { useContext } from "react";
import { VideoPlayerContext } from "../../../api/context/VideoPlayerContext";
import { IconCheck } from "@tabler/icons-react";
import { FormatCard } from "../../cards/FormatCard";
import { OptionsContext } from "../OptionsContext";
import { IconArrowRight } from "@tabler/icons-react";

export const FormatSelect = () => {
const {
availableFormats,
activeFormat,
setFormat,
} = useContext(VideoPlayerContext);
const { setView } = useContext(OptionsContext);
const { activeFormat } = useContext(VideoPlayerContext);

return (
<Select
w="100%"
label={"Format"}
value={activeFormat.itag}
onChange={(v) => setFormat(v)}
data={availableFormats.map((f, i) => f.id)}
renderOption={({ option: { value }, checked }) => (
<Group justify="space-between">
<Group>
<Stack>
{availableFormats.find(x => x.id == value).id}
{availableFormats.find(x => x.id == value).itag}
{availableFormats.find(x => x.id == value).mimeType}
{availableFormats.find(x => x.id == value).fps}
</Stack>
</Group>
<Group>
{checked && <IconCheck />}
</Group>
</Group>
)}
/>
<Stack w="100%" gap={0}>
<Text>
Selected Format
</Text>
<UnstyledButton
className="hoverable"
variant="subtle"
color="violet"
onClick={() => setView("formatSelect")}
>
{activeFormat ? (
<FormatCard
format={activeFormat}
isSelected
/>
) : "Loading..."}
</UnstyledButton>
</Stack>
);
};
4 changes: 2 additions & 2 deletions src/components/options/links/OptionsButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ import { useContext } from "react";
import { OptionsContext } from "../OptionsContext";

export const OptionsButton = () => {
const { toggle } = useContext(OptionsContext);
const { open } = useContext(OptionsContext);

return (
<Tooltip label="Options (o)">
<ActionIcon
variant="light"
color="violet"
onClick={() => toggle()}
onClick={() => open()}
>
<IconSettings />
</ActionIcon>
Expand Down
70 changes: 70 additions & 0 deletions src/components/options/views/FormatView.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { useContext, useState } from "react";
import { OptionsContext } from "../OptionsContext";
import { Button, Checkbox, Group, SegmentedControl, Stack, Text } from "@mantine/core";
import { IconArrowLeft } from "@tabler/icons-react";
import { VideoPlayerContext } from "../../../api/context/VideoPlayerContext";
import { FormatCard } from "../../cards/FormatCard";
import { VideoFormatType } from "../../../api/types/format";

export const OptionsFormatView = () => {
const { setView } = useContext(OptionsContext);
const {
availableFormats,
activeFormat,
setFormat,
} = useContext(VideoPlayerContext);

const [filterFormatType, setFilterFormatType] = useState<VideoFormatType | "all">("basic");
const [filterProxy, setFilterProxy] = useState<boolean | null>(null);

const options = availableFormats
.filter(f => filterFormatType == "all" || f.type == filterFormatType)
.filter(f => filterProxy === null || f.isProxied == filterProxy);

return (
<Stack w="100%">
<Stack w="100%">
<Group w="100%" justify="space-between">
<Text>
Format Types
</Text>
<SegmentedControl
data={[
"all",
"basic",
"adaptive",
"dash",
"hls",
]}
value={filterFormatType}
onChange={(v) => setFilterFormatType(v as VideoFormatType | "all")}
/>
</Group>
<Group w="100%" justify="space-between">
<Text>
Proxied
</Text>
<SegmentedControl
data={[
"all",
"true",
"false",
]}
value={filterProxy === null ? "all" : String(filterProxy)}
onChange={(v) => setFilterProxy(v == "all" ? null : (v == "true" ? true : false))}
/>
</Group>
</Stack>
<Stack w="100%">
{options.map((format, i) => (
<FormatCard
format={format}
isSelected={activeFormat.id == format.id}
onClick={() => setFormat(format.id)}
key={i}
/>
))}
</Stack>
</Stack>
);
};
Loading

0 comments on commit c5bf988

Please sign in to comment.