From b9f4e7f4120255fa1f8e835c4650a006056a221d Mon Sep 17 00:00:00 2001 From: mrjvs Date: Wed, 29 Nov 2023 17:27:17 +0100 Subject: [PATCH] HLS for ios --- src/components/player/display/base.ts | 20 +++++++++++++++++++- src/components/player/utils/videoTracks.ts | 19 +++++++++++++++++++ src/setup/App.tsx | 10 ++++------ src/utils/detectFeatures.ts | 4 ++++ 4 files changed, 46 insertions(+), 7 deletions(-) create mode 100644 src/components/player/utils/videoTracks.ts diff --git a/src/components/player/display/base.ts b/src/components/player/display/base.ts index d9471893..3f44bf21 100644 --- a/src/components/player/display/base.ts +++ b/src/components/player/display/base.ts @@ -17,6 +17,7 @@ import { canFullscreen, canFullscreenAnyElement, canPictureInPicture, + canPlayHlsNatively, canWebkitFullscreen, canWebkitPictureInPicture, } from "@/utils/detectFeatures"; @@ -69,6 +70,10 @@ export function makeVideoElementDisplayInterface(): DisplayInterface { } function setupQualityForHls() { + if (videoElement && canPlayHlsNatively(videoElement)) { + return; // nothing to change + } + if (!hls) return; if (!automaticQuality) { const qualities = hlsLevelsToQualities(hls.levels); @@ -95,8 +100,13 @@ export function makeVideoElementDisplayInterface(): DisplayInterface { function setupSource(vid: HTMLVideoElement, src: LoadableSource) { if (src.type === "hls") { - if (!Hls.isSupported()) throw new Error("HLS not supported"); + if (canPlayHlsNatively(vid)) { + vid.src = src.url; + vid.currentTime = startAt; + return; + } + if (!Hls.isSupported()) throw new Error("HLS not supported"); if (!hls) { hls = new Hls({ maxBufferSize: 500 * 1000 * 1000, // 500 mb of buffering, should load more fragments at once @@ -178,6 +188,14 @@ export function makeVideoElementDisplayInterface(): DisplayInterface { emit("time", videoElement?.currentTime ?? 0) ); videoElement.addEventListener("loadedmetadata", () => { + if ( + source?.type === "hls" && + videoElement && + canPlayHlsNatively(videoElement) + ) { + emit("qualities", ["unknown"]); + emit("changedquality", "unknown"); + } emit("duration", videoElement?.duration ?? 0); }); videoElement.addEventListener("progress", () => { diff --git a/src/components/player/utils/videoTracks.ts b/src/components/player/utils/videoTracks.ts new file mode 100644 index 00000000..3a0fbdb2 --- /dev/null +++ b/src/components/player/utils/videoTracks.ts @@ -0,0 +1,19 @@ +export interface VideoTrack { + selected: boolean; + id: string; + kind: string; + label: string; + language: string; +} + +export type VideoTrackList = Array & { + selectedIndex: number; + getTrackById(id: string): VideoTrack | null; + addEventListener(type: "change", listener: (ev: Event) => any): void; +}; + +export function getVideoTracks(video: HTMLVideoElement): VideoTrackList | null { + const videoAsAny = video as any; + if (!videoAsAny.videoTracks) return null; + return videoAsAny.videoTracks; +} diff --git a/src/setup/App.tsx b/src/setup/App.tsx index 81eda406..03b1e69c 100644 --- a/src/setup/App.tsx +++ b/src/setup/App.tsx @@ -13,6 +13,7 @@ import { generateQuickSearchMediaUrl } from "@/backend/metadata/tmdb"; import { useOnlineListener } from "@/hooks/usePing"; import { AboutPage } from "@/pages/About"; import { AdminPage } from "@/pages/admin/AdminPage"; +import VideoTesterView from "@/pages/developer/VideoTesterView"; import { DmcaPage } from "@/pages/Dmca"; import { NotFoundPage } from "@/pages/errors/NotFoundPage"; import { HomePage } from "@/pages/HomePage"; @@ -106,15 +107,12 @@ function App() { path="/dev" component={lazy(() => import("@/pages/DeveloperPage"))} /> - import("@/pages/developer/VideoTesterView"))} - /> + + + {/* developer routes that can abuse workers are disabled in production */} {process.env.NODE_ENV === "development" ? ( import("@/pages/developer/TestView"))} /> diff --git a/src/utils/detectFeatures.ts b/src/utils/detectFeatures.ts index af62720d..a82a3de3 100644 --- a/src/utils/detectFeatures.ts +++ b/src/utils/detectFeatures.ts @@ -46,3 +46,7 @@ export function canPictureInPicture(): boolean { export function canWebkitPictureInPicture(): boolean { return "webkitSupportsPresentationMode" in document.createElement("video"); } + +export function canPlayHlsNatively(video: HTMLVideoElement): boolean { + return !!video.canPlayType("application/vnd.apple.mpegurl"); +}