diff --git a/src/components/player/atoms/Settings.tsx b/src/components/player/atoms/Settings.tsx index 183d36aa..57a677bb 100644 --- a/src/components/player/atoms/Settings.tsx +++ b/src/components/player/atoms/Settings.tsx @@ -11,6 +11,7 @@ import { useOverlayRouter } from "@/hooks/useOverlayRouter"; import { usePlayerStore } from "@/stores/player/store"; import { SourceQuality, + allQualities, qualityToString, } from "@/stores/player/utils/qualities"; @@ -26,7 +27,7 @@ function QualityOption(props: { textClasses = "text-video-context-type-main text-opacity-40"; return ( - + {props.children} @@ -54,23 +55,28 @@ function QualityView({ id }: { id: string }) { [router, switchQuality] ); + const allVisibleQualities = allQualities.filter((t) => t !== "unknown"); + return ( <> router.navigate("/")}> Quality - {availableQualities.map((v) => ( + {allVisibleQualities.map((v) => ( change(v)} + onClick={ + availableQualities.includes(v) ? () => change(v) : undefined + } + disabled={!availableQualities.includes(v)} > {qualityToString(v)} ))} - router.navigate("/")}> + Automatic quality Toggle diff --git a/src/components/player/display/base.ts b/src/components/player/display/base.ts index 98648ef1..5b5d93db 100644 --- a/src/components/player/display/base.ts +++ b/src/components/player/display/base.ts @@ -1,4 +1,5 @@ import fscreen from "fscreen"; +import Hls from "hls.js"; import { DisplayInterface, @@ -17,15 +18,38 @@ import { makeEmitter } from "@/utils/events"; export function makeVideoElementDisplayInterface(): DisplayInterface { const { emit, on, off } = makeEmitter(); let source: LoadableSource | null = null; + let hls: Hls | null = null; let videoElement: HTMLVideoElement | null = null; let containerElement: HTMLElement | null = null; let isFullscreen = false; let isPausedBeforeSeeking = false; let isSeeking = false; + function setupSource(vid: HTMLVideoElement, src: LoadableSource) { + if (src.type === "hls") { + if (!Hls.isSupported()) throw new Error("HLS not supported"); + + hls = new Hls({ enableWorker: false }); + hls.on(Hls.Events.ERROR, (event, data) => { + console.error("HLS error", data); + if (data.fatal) { + throw new Error( + `HLS ERROR:${data.error?.message ?? "Something went wrong"}` + ); + } + }); + + hls.attachMedia(vid); + hls.loadSource(src.url); + return; + } + + vid.src = src.url; + } + function setSource() { if (!videoElement || !source) return; - videoElement.src = source.url; + setupSource(videoElement, source); videoElement.addEventListener("play", () => { emit("play", undefined); @@ -64,6 +88,7 @@ export function makeVideoElementDisplayInterface(): DisplayInterface { on, off, destroy: () => { + if (hls) hls.destroy(); if (videoElement) { videoElement.src = ""; videoElement.remove(); diff --git a/src/components/player/internals/ContextUtils.tsx b/src/components/player/internals/ContextUtils.tsx index 4f253377..c163e4c0 100644 --- a/src/components/player/internals/ContextUtils.tsx +++ b/src/components/player/internals/ContextUtils.tsx @@ -31,21 +31,29 @@ function Section(props: { children: React.ReactNode }) { return
{props.children}
; } -function Link(props: { - onClick?: () => void; - children: React.ReactNode; - noHover?: boolean; -}) { +function Link(props: { onClick?: () => void; children: React.ReactNode }) { + const classes = classNames( + "flex justify-between items-center py-2 pl-3 pr-3 -ml-3 rounded w-full", + { + "cursor-default": !props.onClick, + "hover:bg-video-context-border hover:bg-opacity-10": !!props.onClick, + } + ); + const styles = { width: "calc(100% + 1.5rem)" }; + + if (!props.onClick) { + return ( +
+ {props.children} +
+ ); + } + return (