From b0c499a83a036c06fb98e5cf8d1d8ab92a477fae Mon Sep 17 00:00:00 2001 From: TheAlan404 Date: Wed, 1 May 2024 14:06:20 +0300 Subject: [PATCH] features, fixes, improvements --- src/api/context/VideoPlayerProvider.tsx | 7 ++- src/api/platforms/invid/invidious.ts | 34 ++++++++++- src/api/platforms/lt/lighttube.ts | 4 ++ src/api/platforms/public.ts | 37 ++++++++++++ src/api/types/format.ts | 1 + src/api/types/instances.ts | 20 ++++--- src/components/cards/InstanceCard.tsx | 56 ++++++++++--------- src/components/options/OptionsContext.tsx | 2 +- src/components/options/OptionsRouter.tsx | 2 + src/components/options/comps/FormatSelect.tsx | 13 +++-- .../{ => links}/ChangeInstanceButton.tsx | 2 +- .../options/links/OpenWithButton.tsx | 24 ++++++++ .../options/{ => links}/OptionsButton.tsx | 2 +- src/components/options/views/MainView.tsx | 6 +- src/components/options/views/OpenWith.tsx | 28 ++++++++++ src/components/player/VideoPlayer.tsx | 2 +- .../player/controls/PlayerTimestamp.tsx | 1 - src/components/ui/ErrorMessage.tsx | 2 +- src/site/Root.tsx | 2 +- src/utils/cleanDescription.ts | 2 +- 20 files changed, 193 insertions(+), 54 deletions(-) rename src/components/options/{ => links}/ChangeInstanceButton.tsx (91%) create mode 100644 src/components/options/links/OpenWithButton.tsx rename src/components/options/{ => links}/OptionsButton.tsx (90%) create mode 100644 src/components/options/views/OpenWith.tsx diff --git a/src/api/context/VideoPlayerProvider.tsx b/src/api/context/VideoPlayerProvider.tsx index d4753a7..ef3d60e 100644 --- a/src/api/context/VideoPlayerProvider.tsx +++ b/src/api/context/VideoPlayerProvider.tsx @@ -58,11 +58,14 @@ export const VideoPlayerProvider = ({ let info = await api.getVideoInfo(videoID); setVideoInfo(info); + console.log("Fetched VideoData", info); + let chapters = parseChapters(info.description); setActiveChapters({ type: "video", chapters, }); + console.log("Chapters from description:", chapters); } catch(e) { console.log(e); setError(e); @@ -83,7 +86,7 @@ export const VideoPlayerProvider = ({ useEffect(() => { console.log("Setting URL to", activeFormat?.url); - videoElement.src = activeFormat?.url; + if(videoElement.src != activeFormat?.url) videoElement.src = activeFormat?.url; }, [activeFormat]); useVideoEventListener(videoElement, "ended", () => { @@ -95,7 +98,7 @@ export const VideoPlayerProvider = ({ }); useVideoEventListener(videoElement, "error", (e) => { - if(!videoElement.src) return; + if(!videoElement.src || videoElement.src.endsWith("/undefined")) return; setPlayState("error"); console.log(e.error); console.log(videoElement.error); diff --git a/src/api/platforms/invid/invidious.ts b/src/api/platforms/invid/invidious.ts index d7e48f5..68315c5 100644 --- a/src/api/platforms/invid/invidious.ts +++ b/src/api/platforms/invid/invidious.ts @@ -51,10 +51,16 @@ export class InvidiousAPIProvider implements APIProvider { }; } + formatURLProxied(uri: string) { + let url = new URL(uri); + url.host = this.instance.url.split("://")[1]; + return url.href; + } + getVideoInfo = async (id: string) => { let v: InvidiousVideoData = await this.request(`videos/${id}`); - console.log(v); + console.log({ invidiousVideoData: v }); return { id, @@ -74,6 +80,7 @@ export class InvidiousAPIProvider implements APIProvider { ...v.formatStreams.map((f, i) => ({ type: "basic", id: `basic-${i}`, + isProxied: false, itag: f.itag, url: f.url, mimeType: f.type, @@ -85,12 +92,37 @@ export class InvidiousAPIProvider implements APIProvider { ...v.adaptiveFormats.map((f, i) => ({ type: "adaptive", id: `adaptive-${i}`, + isProxied: false, itag: f.itag, url: f.url, mimeType: f.type, fps: f.fps, bitrate: Number(f.bitrate), } as VideoFormat)), + ...(this.instance.supportsProxy ? [ + ...v.formatStreams.map((f, i) => ({ + type: "basic", + id: `basic-${i}-proxy`, + itag: f.itag, + url: this.formatURLProxied(f.url), + isProxied: true, + mimeType: f.type, + fps: f.fps, + width: Number(f.size.split("x")[0]), + height: Number(f.size.split("x")[1]), + bitrate: Number(f.bitrate), + } as VideoFormat)), + ...v.adaptiveFormats.map((f, i) => ({ + type: "adaptive", + id: `adaptive-${i}-proxy`, + itag: f.itag, + url: this.formatURLProxied(f.url), + isProxied: true, + mimeType: f.type, + fps: f.fps, + bitrate: Number(f.bitrate), + } as VideoFormat)), + ] : []), ], } as VideoData; }; diff --git a/src/api/platforms/lt/lighttube.ts b/src/api/platforms/lt/lighttube.ts index 3019782..6fddae4 100644 --- a/src/api/platforms/lt/lighttube.ts +++ b/src/api/platforms/lt/lighttube.ts @@ -92,11 +92,13 @@ export class LTAPIProvider implements APIProvider { ...f, type: "basic", id: `basic-${i}`, + isProxied: false, } as VideoFormat)), ...ltPlayer.adaptiveFormats.map((f, i) => ({ ...f, type: "adaptive", id: `adaptive-${i}`, + isProxied: false, } as VideoFormat)), ...ltPlayer.formats.map((f, i) => ({ @@ -104,12 +106,14 @@ export class LTAPIProvider implements APIProvider { type: "basic", id: `proxy-basic-${i}`, url: `${this.instance.url}/proxy/media/${id}/${f.itag}`, + isProxied: true, } as VideoFormat)), ...ltPlayer.adaptiveFormats.map((f, i) => ({ ...f, type: "adaptive", id: `proxy-adaptive-${i}`, url: `${this.instance.url}/proxy/media/${id}/${f.itag}`, + isProxied: true, } as VideoFormat)), ], }; diff --git a/src/api/platforms/public.ts b/src/api/platforms/public.ts index e560e44..77d0395 100644 --- a/src/api/platforms/public.ts +++ b/src/api/platforms/public.ts @@ -24,6 +24,7 @@ export const fetchLightTubePublicInstances = async () => { name: i.host, url: `${i.scheme}://${i.host}`, region: i.country, + supportsProxy: i.proxyEnabled == "all", notes: (i.isCloudflare ? "cloudflare" : ""), } as Instance)); }; @@ -55,5 +56,41 @@ export const fetchInvidiousPublicInstances = async () => { url: i.uri, name, region: i.region, + supportsProxy: true, + } as Instance)); +}; + +// --- poketube --- + +const POKETUBE_PUBLIC_INSTANCES = "https://poketube.fun/api/instances.json"; + +interface PublicPoketubeInstanceDetails { + uri: string; + CLOUDFLARE: boolean; + piwik: boolean; + proxy: boolean; + official: boolean; + DEFAULT: boolean; + region: string; + software: { + name: string; + version: string; + branch: string; + }, +} + +type PublicPoketubeInstance = [string, PublicPoketubeInstanceDetails]; + +export const fetchPoketubePublicInstances = async () => { + const res = await fetch(POKETUBE_PUBLIC_INSTANCES); + const li: PublicPoketubeInstance[] = await res.json(); + + return li + .map(([name, i]) => ({ + type: "poketube", + url: i.uri, + name, + supportsProxy: i.proxy, + notes: i.CLOUDFLARE ? "cloudflare" : "", } as Instance)); }; diff --git a/src/api/types/format.ts b/src/api/types/format.ts index cf034b4..0e20035 100644 --- a/src/api/types/format.ts +++ b/src/api/types/format.ts @@ -3,6 +3,7 @@ export type VideoFormatType = "basic" | "adaptive" | "dash" | "hls"; export interface VideoFormat { type: VideoFormatType; id: string; + isProxied: boolean; itag: string; url: string; diff --git a/src/api/types/instances.ts b/src/api/types/instances.ts index 8ffee55..5a1aa3f 100644 --- a/src/api/types/instances.ts +++ b/src/api/types/instances.ts @@ -1,17 +1,21 @@ -export interface LTInstance { - type: "lighttube"; +export interface BaseInstance { name: string; url: string; notes?: string; region?: string; + supportsProxy?: boolean; }; -export interface InvidiousInstance { +export interface LTInstance extends BaseInstance { + type: "lighttube"; +}; + +export interface InvidiousInstance extends BaseInstance { type: "invidious"; - name: string; - url: string; - notes?: string; - region?: string; }; -export type Instance = LTInstance | InvidiousInstance; +export interface PoketubeInstance extends BaseInstance { + type: "poketube"; +}; + +export type Instance = LTInstance | InvidiousInstance | PoketubeInstance; diff --git a/src/components/cards/InstanceCard.tsx b/src/components/cards/InstanceCard.tsx index 4b00295..52a5641 100644 --- a/src/components/cards/InstanceCard.tsx +++ b/src/components/cards/InstanceCard.tsx @@ -48,35 +48,37 @@ export const InstanceCard = ({ {instance.url} - {withControls && ( - - { - e.stopPropagation(); - if (fav) - removeFavourite(instance); - else - addFavourite(instance); - }} + + + {instance.region} + + {withControls && ( + - {fav ? ( - - ) : ( - - )} - - - )} + { + e.stopPropagation(); + if (fav) + removeFavourite(instance); + else + addFavourite(instance); + }} + > + {fav ? ( + + ) : ( + + )} + + + )} + - - {instance.notes} - diff --git a/src/components/options/OptionsContext.tsx b/src/components/options/OptionsContext.tsx index 0bfc5b0..bd3e64b 100644 --- a/src/components/options/OptionsContext.tsx +++ b/src/components/options/OptionsContext.tsx @@ -4,7 +4,7 @@ import { useSoundEffect } from "../../hooks/useSoundEffect"; import { Drawer, ScrollArea } from "@mantine/core"; import { OptionsRouter } from "./OptionsRouter"; -export type OptionsView = "main" | "instanceSelect"; +export type OptionsView = "main" | "instanceSelect" | "openWith" | "formatSelect"; export interface OptionsContextAPI { opened: boolean; diff --git a/src/components/options/OptionsRouter.tsx b/src/components/options/OptionsRouter.tsx index 812cba1..0e9e02d 100644 --- a/src/components/options/OptionsRouter.tsx +++ b/src/components/options/OptionsRouter.tsx @@ -3,6 +3,7 @@ import { useContext } from "react"; import { OptionsContext } from "./OptionsContext"; import { OptionsMainView } from "./views/MainView"; import { OptionsInstanceView } from "./views/InstanceView"; +import { OptionsOpenWithView } from "./views/OpenWith"; export const OptionsRouter = () => { const { view } = useContext(OptionsContext); @@ -11,6 +12,7 @@ export const OptionsRouter = () => { {view == "main" && } {view == "instanceSelect" && } + {view == "openWith" && } ); }; diff --git a/src/components/options/comps/FormatSelect.tsx b/src/components/options/comps/FormatSelect.tsx index 8bc0893..506f7bd 100644 --- a/src/components/options/comps/FormatSelect.tsx +++ b/src/components/options/comps/FormatSelect.tsx @@ -15,15 +15,16 @@ export const FormatSelect = () => { w="100%" label={"Format"} value={activeFormat.itag} - onChange={(v) => setFormat(availableFormats[Number(v)]!)} - data={availableFormats.map((f, i) => i.toString())} - renderOption={({ option: { value: i }, checked }) => ( + onChange={(v) => setFormat(v)} + data={availableFormats.map((f, i) => f.id)} + renderOption={({ option: { value }, checked }) => ( - {availableFormats[Number(i)].itag} - {availableFormats[Number(i)].mimeType} - {availableFormats[Number(i)].fps} + {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} diff --git a/src/components/options/ChangeInstanceButton.tsx b/src/components/options/links/ChangeInstanceButton.tsx similarity index 91% rename from src/components/options/ChangeInstanceButton.tsx rename to src/components/options/links/ChangeInstanceButton.tsx index 7af0187..5d52e61 100644 --- a/src/components/options/ChangeInstanceButton.tsx +++ b/src/components/options/links/ChangeInstanceButton.tsx @@ -1,7 +1,7 @@ import { Button } from "@mantine/core"; import { IconSettings } from "@tabler/icons-react"; import { useContext } from "react"; -import { OptionsContext } from "./OptionsContext"; +import { OptionsContext } from "../OptionsContext"; export const ChangeInstanceButton = () => { const { open, setView } = useContext(OptionsContext); diff --git a/src/components/options/links/OpenWithButton.tsx b/src/components/options/links/OpenWithButton.tsx new file mode 100644 index 0000000..56c924a --- /dev/null +++ b/src/components/options/links/OpenWithButton.tsx @@ -0,0 +1,24 @@ +import { Button, ButtonProps } from "@mantine/core"; +import { IconExternalLink } from "@tabler/icons-react"; +import { useContext } from "react"; +import { OptionsContext } from "../OptionsContext"; + +export const OpenWithButton = (props: ButtonProps) => { + const { open, setView } = useContext(OptionsContext); + + return ( + + ); +}; diff --git a/src/components/options/OptionsButton.tsx b/src/components/options/links/OptionsButton.tsx similarity index 90% rename from src/components/options/OptionsButton.tsx rename to src/components/options/links/OptionsButton.tsx index 39ba83e..8426eed 100644 --- a/src/components/options/OptionsButton.tsx +++ b/src/components/options/links/OptionsButton.tsx @@ -1,7 +1,7 @@ import { ActionIcon, Tooltip } from "@mantine/core"; import { IconSettings } from "@tabler/icons-react"; import { useContext } from "react"; -import { OptionsContext } from "./OptionsContext"; +import { OptionsContext } from "../OptionsContext"; export const OptionsButton = () => { const { toggle } = useContext(OptionsContext); diff --git a/src/components/options/views/MainView.tsx b/src/components/options/views/MainView.tsx index d2408bf..a41d31b 100644 --- a/src/components/options/views/MainView.tsx +++ b/src/components/options/views/MainView.tsx @@ -6,11 +6,12 @@ import { FormatSelect } from "../comps/FormatSelect"; import { PlaybackSpeed } from "../comps/PlaybackSpeed"; import { PreferencesList } from "../comps/PreferencesList"; import { RefetchInfo } from "../comps/RefetchInfo"; +import { OpenWithButton } from "../links/OpenWithButton"; export const OptionsMainView = () => { - const { playState } = useContext(VideoPlayerContext); + const { videoInfo } = useContext(VideoPlayerContext); - const loaded = playState !== "error" && playState !== "loading"; + const loaded = !!videoInfo; return ( @@ -18,6 +19,7 @@ export const OptionsMainView = () => { + {!loaded && ( Video not loaded diff --git a/src/components/options/views/OpenWith.tsx b/src/components/options/views/OpenWith.tsx new file mode 100644 index 0000000..3ebce57 --- /dev/null +++ b/src/components/options/views/OpenWith.tsx @@ -0,0 +1,28 @@ +import { Button, Group, Stack } from "@mantine/core"; +import { IconArrowLeft } from "@tabler/icons-react"; +import { useContext } from "react"; +import { OptionsContext } from "../OptionsContext"; + +export const OptionsOpenWithView = () => { + const { setView } = useContext(OptionsContext); + + return ( + + + + + + meow + + + ); +}; diff --git a/src/components/player/VideoPlayer.tsx b/src/components/player/VideoPlayer.tsx index a500e79..397e656 100644 --- a/src/components/player/VideoPlayer.tsx +++ b/src/components/player/VideoPlayer.tsx @@ -8,7 +8,7 @@ import { ProgressBar } from "./bar/ProgressBar"; import { useDisclosure, useDocumentTitle, useFullscreen, useHotkeys, useHover, useMergedRef } from "@mantine/hooks"; import { IconAlertTriangle, IconReload } from "@tabler/icons-react"; import { FullscreenButton } from "./controls/FullscreenButton"; -import { OptionsButton } from "../options/OptionsButton"; +import { OptionsButton } from "../options/links/OptionsButton"; import { useSoundEffect } from "../../hooks/useSoundEffect"; import { usePreference } from "../../api/pref/Preferences"; import { ErrorMessage } from "../ui/ErrorMessage"; diff --git a/src/components/player/controls/PlayerTimestamp.tsx b/src/components/player/controls/PlayerTimestamp.tsx index 7367590..fb90dbe 100644 --- a/src/components/player/controls/PlayerTimestamp.tsx +++ b/src/components/player/controls/PlayerTimestamp.tsx @@ -13,7 +13,6 @@ export const PlayerTimestamp = () => { }); const progressText = secondsToTimestamp(progress); - console.log(activeChapters.chapters, progress); const currentChapter = activeChapters.chapters[activeChapters.chapters.findIndex((x) => x.time > progress) - 1]; return ( diff --git a/src/components/ui/ErrorMessage.tsx b/src/components/ui/ErrorMessage.tsx index 1951b2d..5dcf6c4 100644 --- a/src/components/ui/ErrorMessage.tsx +++ b/src/components/ui/ErrorMessage.tsx @@ -2,7 +2,7 @@ import { Box, Button, Stack, Text } from "@mantine/core"; import { IconAlertTriangle, IconReload } from "@tabler/icons-react"; import { useSoundEffect } from "../../hooks/useSoundEffect"; import { useEffect } from "react"; -import { ChangeInstanceButton } from "../options/ChangeInstanceButton"; +import { ChangeInstanceButton } from "../options/links/ChangeInstanceButton"; const CORS_ERROR_MESSAGE = "NetworkError when attempting to fetch resource."; diff --git a/src/site/Root.tsx b/src/site/Root.tsx index 13088e6..4f3c585 100644 --- a/src/site/Root.tsx +++ b/src/site/Root.tsx @@ -3,7 +3,7 @@ import { nprogress } from "@mantine/nprogress"; import { IconBrandYoutube, IconCat } from "@tabler/icons-react"; import { useContext, useEffect } from "react"; import { Link, Outlet, useNavigation, useSearchParams } from "react-router-dom"; -import { OptionsButton } from "../components/options/OptionsButton"; +import { OptionsButton } from "../components/options/links/OptionsButton"; import { SearchBar } from "../components/search/SearchBar"; import { VideoPlayerContext } from "../api/context/VideoPlayerContext"; diff --git a/src/utils/cleanDescription.ts b/src/utils/cleanDescription.ts index d02dc60..a88373a 100644 --- a/src/utils/cleanDescription.ts +++ b/src/utils/cleanDescription.ts @@ -1,5 +1,5 @@ export const cleanDescription = (text = "") => { let parser = new DOMParser(); - let doc = parser.parseFromString(text, "text/html"); + let doc = parser.parseFromString(text.replaceAll("
", "\n"), "text/html"); return doc.documentElement.textContent; };