feat: add hls.js for m3u8 stream playback
This commit is contained in:
3
bun.lock
3
bun.lock
@@ -5,6 +5,7 @@
|
|||||||
"": {
|
"": {
|
||||||
"name": "myanimelist-ui",
|
"name": "myanimelist-ui",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"hls.js": "^1.6.16",
|
||||||
"htmx.org": "1.9.12",
|
"htmx.org": "1.9.12",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -185,6 +186,8 @@
|
|||||||
|
|
||||||
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
|
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
|
||||||
|
|
||||||
|
"hls.js": ["hls.js@1.6.16", "", {}, "sha512-VSIRpLfRwlAAdGL4wiTucx2ScRipo0ed1FBatWkyt832jC4CReKstga6yIhYVwGu9LOBjuX9wzmRMeQdBJtzEA=="],
|
||||||
|
|
||||||
"htmx.org": ["htmx.org@1.9.12", "", {}, "sha512-VZAohXyF7xPGS52IM8d1T1283y+X4D+Owf3qY1NZ9RuBypyu9l8cGsxUMAG5fEAb/DhT7rDoJ9Hpu5/HxFD3cw=="],
|
"htmx.org": ["htmx.org@1.9.12", "", {}, "sha512-VZAohXyF7xPGS52IM8d1T1283y+X4D+Owf3qY1NZ9RuBypyu9l8cGsxUMAG5fEAb/DhT7rDoJ9Hpu5/HxFD3cw=="],
|
||||||
|
|
||||||
"is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
|
"is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
"lint:go": "golangci-lint run ./..."
|
"lint:go": "golangci-lint run ./..."
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"hls.js": "^1.6.16",
|
||||||
"htmx.org": "1.9.12"
|
"htmx.org": "1.9.12"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -1,25 +1,54 @@
|
|||||||
|
import Hls from "hls.js";
|
||||||
import { state } from "./state";
|
import { state } from "./state";
|
||||||
import { absoluteTimeFromDisplay, displayTimeFromAbsolute, invalidateBounds } from "./timeline";
|
import { absoluteTimeFromDisplay, displayTimeFromAbsolute, invalidateBounds } from "./timeline";
|
||||||
|
|
||||||
|
let hls: Hls | null = null;
|
||||||
|
|
||||||
|
const destroyHLS = (): void => {
|
||||||
|
hls?.destroy();
|
||||||
|
hls = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const destroyVideoSource = (): void => {
|
||||||
|
destroyHLS();
|
||||||
|
state.video.pause();
|
||||||
|
state.video.removeAttribute("src");
|
||||||
|
state.video.load();
|
||||||
|
};
|
||||||
|
|
||||||
|
const shouldUseHLS = (type: string | undefined, url: string): boolean => {
|
||||||
|
if (type === "m3u8") return true;
|
||||||
|
try {
|
||||||
|
const parsed = new URL(url, window.location.href);
|
||||||
|
return parsed.pathname.toLowerCase().endsWith(".m3u8");
|
||||||
|
} catch {
|
||||||
|
return url.toLowerCase().includes(".m3u8");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Force-loads a new video source and preserves playback position.
|
* Force-loads a new video source and preserves playback position.
|
||||||
*
|
*
|
||||||
* Some browsers can be flaky when switching between HLS URLs while playing.
|
* Some browsers can be flaky when switching between HLS URLs while playing.
|
||||||
* Clearing `src` first ensures the media element fully resets before the new URL is set.
|
* Clearing `src` first ensures the media element fully resets before the new URL is set.
|
||||||
*/
|
*/
|
||||||
export const loadVideoSource = (url: string): void => {
|
export const loadVideoSource = (url: string, type?: string): void => {
|
||||||
if (!url) return;
|
if (!url) return;
|
||||||
|
|
||||||
const wasPlaying = !state.video.paused;
|
const wasPlaying = !state.video.paused;
|
||||||
const prevDisplayTime = displayTimeFromAbsolute(state.video.currentTime);
|
const prevDisplayTime = displayTimeFromAbsolute(state.video.currentTime);
|
||||||
|
|
||||||
// Fully reset the element before setting a new source.
|
// Fully reset the element before setting a new source.
|
||||||
state.video.pause();
|
destroyVideoSource();
|
||||||
state.video.removeAttribute("src");
|
|
||||||
state.video.load();
|
|
||||||
|
|
||||||
state.video.src = url;
|
if (shouldUseHLS(type, url) && Hls.isSupported()) {
|
||||||
state.video.load();
|
hls = new Hls();
|
||||||
|
hls.loadSource(url);
|
||||||
|
hls.attachMedia(state.video);
|
||||||
|
} else {
|
||||||
|
state.video.src = url;
|
||||||
|
state.video.load();
|
||||||
|
}
|
||||||
|
|
||||||
// Try an eager seek; if metadata isn't ready yet, main.ts will restore via pendingSeekTime.
|
// Try an eager seek; if metadata isn't ready yet, main.ts will restore via pendingSeekTime.
|
||||||
state.pendingSeekTime = prevDisplayTime;
|
state.pendingSeekTime = prevDisplayTime;
|
||||||
|
|||||||
Reference in New Issue
Block a user