mirror of
https://github.com/hydralauncher/hydra.git
synced 2026-01-10 21:36:17 +00:00
fix: fixing hls videos
This commit is contained in:
@@ -63,6 +63,7 @@
|
|||||||
"embla-carousel-react": "^8.6.0",
|
"embla-carousel-react": "^8.6.0",
|
||||||
"file-type": "^20.5.0",
|
"file-type": "^20.5.0",
|
||||||
"framer-motion": "^12.15.0",
|
"framer-motion": "^12.15.0",
|
||||||
|
"hls.js": "^1.5.12",
|
||||||
"i18next": "^23.11.2",
|
"i18next": "^23.11.2",
|
||||||
"i18next-browser-languagedetector": "^7.2.1",
|
"i18next-browser-languagedetector": "^7.2.1",
|
||||||
"jsdom": "^24.0.0",
|
"jsdom": "^24.0.0",
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<title>Hydra Launcher</title>
|
<title>Hydra Launcher</title>
|
||||||
<meta
|
<meta
|
||||||
http-equiv="Content-Security-Policy"
|
http-equiv="Content-Security-Policy"
|
||||||
content="default-src 'self' 'unsafe-inline' * data: local:;"
|
content="default-src 'self' 'unsafe-inline' * data: local:; media-src 'self' 'unsafe-inline' * data: local: blob:;"
|
||||||
/>
|
/>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -10,3 +10,4 @@ export * from "./use-download-options-listener";
|
|||||||
export * from "./use-game-card";
|
export * from "./use-game-card";
|
||||||
export * from "./use-search-history";
|
export * from "./use-search-history";
|
||||||
export * from "./use-search-suggestions";
|
export * from "./use-search-suggestions";
|
||||||
|
export * from "./use-hls-video";
|
||||||
|
|||||||
102
src/renderer/src/hooks/use-hls-video.ts
Normal file
102
src/renderer/src/hooks/use-hls-video.ts
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
import { useEffect, useRef } from "react";
|
||||||
|
import Hls from "hls.js";
|
||||||
|
import { logger } from "@renderer/logger";
|
||||||
|
|
||||||
|
interface UseHlsVideoOptions {
|
||||||
|
videoSrc: string | undefined;
|
||||||
|
videoType: string | undefined;
|
||||||
|
autoplay?: boolean;
|
||||||
|
muted?: boolean;
|
||||||
|
loop?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useHlsVideo(
|
||||||
|
videoRef: React.RefObject<HTMLVideoElement>,
|
||||||
|
{ videoSrc, videoType, autoplay, muted, loop }: UseHlsVideoOptions
|
||||||
|
) {
|
||||||
|
const hlsRef = useRef<Hls | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const video = videoRef.current;
|
||||||
|
if (!video || !videoSrc) return;
|
||||||
|
|
||||||
|
const isHls = videoType === "application/x-mpegURL";
|
||||||
|
|
||||||
|
if (!isHls) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Hls.isSupported()) {
|
||||||
|
const hls = new Hls({
|
||||||
|
enableWorker: true,
|
||||||
|
lowLatencyMode: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
hlsRef.current = hls;
|
||||||
|
|
||||||
|
hls.loadSource(videoSrc);
|
||||||
|
hls.attachMedia(video);
|
||||||
|
|
||||||
|
hls.on(Hls.Events.MANIFEST_PARSED, () => {
|
||||||
|
if (autoplay) {
|
||||||
|
video.play().catch((err) => {
|
||||||
|
logger.warn("Failed to autoplay HLS video:", err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
hls.on(Hls.Events.ERROR, (event, data) => {
|
||||||
|
if (data.fatal) {
|
||||||
|
switch (data.type) {
|
||||||
|
case Hls.ErrorTypes.NETWORK_ERROR:
|
||||||
|
logger.error("HLS network error, trying to recover");
|
||||||
|
hls.startLoad();
|
||||||
|
break;
|
||||||
|
case Hls.ErrorTypes.MEDIA_ERROR:
|
||||||
|
logger.error("HLS media error, trying to recover");
|
||||||
|
hls.recoverMediaError();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
logger.error("HLS fatal error, destroying instance");
|
||||||
|
hls.destroy();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
hls.destroy();
|
||||||
|
hlsRef.current = null;
|
||||||
|
};
|
||||||
|
} else if (video.canPlayType("application/vnd.apple.mpegurl")) {
|
||||||
|
video.src = videoSrc;
|
||||||
|
video.load();
|
||||||
|
if (autoplay) {
|
||||||
|
video.play().catch((err) => {
|
||||||
|
logger.warn("Failed to autoplay HLS video:", err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
video.src = "";
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
logger.warn("HLS playback is not supported in this browser");
|
||||||
|
}
|
||||||
|
}, [videoRef, videoSrc, videoType, autoplay, muted, loop]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const video = videoRef.current;
|
||||||
|
if (!video) return;
|
||||||
|
|
||||||
|
if (muted !== undefined) {
|
||||||
|
video.muted = muted;
|
||||||
|
}
|
||||||
|
if (loop !== undefined) {
|
||||||
|
video.loop = loop;
|
||||||
|
}
|
||||||
|
}, [videoRef, muted, loop]);
|
||||||
|
|
||||||
|
return hlsRef.current;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
import useEmblaCarousel from "embla-carousel-react";
|
import useEmblaCarousel from "embla-carousel-react";
|
||||||
import { gameDetailsContext } from "@renderer/context";
|
import { gameDetailsContext } from "@renderer/context";
|
||||||
import { useAppSelector } from "@renderer/hooks";
|
import { useAppSelector } from "@renderer/hooks";
|
||||||
|
import { VideoPlayer } from "./video-player";
|
||||||
import "./gallery-slider.scss";
|
import "./gallery-slider.scss";
|
||||||
|
|
||||||
export function GallerySlider() {
|
export function GallerySlider() {
|
||||||
@@ -106,8 +107,6 @@ export function GallerySlider() {
|
|||||||
|
|
||||||
if (shopDetails?.movies) {
|
if (shopDetails?.movies) {
|
||||||
shopDetails.movies.forEach((video, index) => {
|
shopDetails.movies.forEach((video, index) => {
|
||||||
// Prefer new formats: HLS (best browser support), then DASH H264, then DASH AV1
|
|
||||||
// Fallback to old format: mp4/webm if new formats are not available
|
|
||||||
let videoSrc: string | undefined;
|
let videoSrc: string | undefined;
|
||||||
let videoType: string | undefined;
|
let videoType: string | undefined;
|
||||||
|
|
||||||
@@ -121,11 +120,9 @@ export function GallerySlider() {
|
|||||||
videoSrc = video.dash_av1;
|
videoSrc = video.dash_av1;
|
||||||
videoType = "application/dash+xml";
|
videoType = "application/dash+xml";
|
||||||
} else if (video.mp4?.max) {
|
} else if (video.mp4?.max) {
|
||||||
// Fallback to old format
|
|
||||||
videoSrc = video.mp4.max;
|
videoSrc = video.mp4.max;
|
||||||
videoType = "video/mp4";
|
videoType = "video/mp4";
|
||||||
} else if (video.webm?.max) {
|
} else if (video.webm?.max) {
|
||||||
// Fallback to webm if mp4 is not available
|
|
||||||
videoSrc = video.webm.max;
|
videoSrc = video.webm.max;
|
||||||
videoType = "video/webm";
|
videoType = "video/webm";
|
||||||
}
|
}
|
||||||
@@ -191,19 +188,17 @@ export function GallerySlider() {
|
|||||||
{mediaItems.map((item) => (
|
{mediaItems.map((item) => (
|
||||||
<div key={item.id} className="gallery-slider__slide">
|
<div key={item.id} className="gallery-slider__slide">
|
||||||
{item.type === "video" ? (
|
{item.type === "video" ? (
|
||||||
<video
|
<VideoPlayer
|
||||||
controls
|
videoSrc={item.videoSrc}
|
||||||
className="gallery-slider__media"
|
videoType={item.videoType}
|
||||||
poster={item.poster}
|
poster={item.poster}
|
||||||
|
autoplay={autoplayEnabled}
|
||||||
loop
|
loop
|
||||||
muted
|
muted
|
||||||
autoPlay={autoplayEnabled}
|
controls
|
||||||
|
className="gallery-slider__media"
|
||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
>
|
/>
|
||||||
{item.videoSrc && (
|
|
||||||
<source src={item.videoSrc} type={item.videoType} />
|
|
||||||
)}
|
|
||||||
</video>
|
|
||||||
) : (
|
) : (
|
||||||
<img
|
<img
|
||||||
className="gallery-slider__media"
|
className="gallery-slider__media"
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
import { useRef } from "react";
|
||||||
|
import { useHlsVideo } from "@renderer/hooks";
|
||||||
|
|
||||||
|
interface VideoPlayerProps {
|
||||||
|
videoSrc?: string;
|
||||||
|
videoType?: string;
|
||||||
|
poster?: string;
|
||||||
|
autoplay?: boolean;
|
||||||
|
muted?: boolean;
|
||||||
|
loop?: boolean;
|
||||||
|
controls?: boolean;
|
||||||
|
tabIndex?: number;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function VideoPlayer({
|
||||||
|
videoSrc,
|
||||||
|
videoType,
|
||||||
|
poster,
|
||||||
|
autoplay = false,
|
||||||
|
muted = true,
|
||||||
|
loop = false,
|
||||||
|
controls = true,
|
||||||
|
tabIndex = -1,
|
||||||
|
className,
|
||||||
|
}: VideoPlayerProps) {
|
||||||
|
const videoRef = useRef<HTMLVideoElement>(null);
|
||||||
|
const isHls = videoType === "application/x-mpegURL";
|
||||||
|
|
||||||
|
useHlsVideo(videoRef, {
|
||||||
|
videoSrc,
|
||||||
|
videoType,
|
||||||
|
autoplay,
|
||||||
|
muted,
|
||||||
|
loop,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isHls) {
|
||||||
|
return (
|
||||||
|
<video
|
||||||
|
ref={videoRef}
|
||||||
|
controls={controls}
|
||||||
|
className={className}
|
||||||
|
poster={poster}
|
||||||
|
loop={loop}
|
||||||
|
muted={muted}
|
||||||
|
autoPlay={autoplay}
|
||||||
|
tabIndex={tabIndex}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<video
|
||||||
|
ref={videoRef}
|
||||||
|
controls={controls}
|
||||||
|
className={className}
|
||||||
|
poster={poster}
|
||||||
|
loop={loop}
|
||||||
|
muted={muted}
|
||||||
|
autoPlay={autoplay}
|
||||||
|
tabIndex={tabIndex}
|
||||||
|
>
|
||||||
|
{videoSrc && <source src={videoSrc} type={videoType} />}
|
||||||
|
</video>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@@ -14,7 +14,7 @@ export interface SteamVideoSource {
|
|||||||
"480": string;
|
"480": string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SteamMovies {
|
export interface SteamMovie {
|
||||||
id: number;
|
id: number;
|
||||||
dash_av1?: string;
|
dash_av1?: string;
|
||||||
dash_h264?: string;
|
dash_h264?: string;
|
||||||
@@ -34,7 +34,7 @@ export interface SteamAppDetails {
|
|||||||
short_description: string;
|
short_description: string;
|
||||||
publishers: string[];
|
publishers: string[];
|
||||||
genres: SteamGenre[];
|
genres: SteamGenre[];
|
||||||
movies?: SteamMovies[];
|
movies?: SteamMovie[];
|
||||||
supported_languages: string;
|
supported_languages: string;
|
||||||
screenshots?: SteamScreenshot[];
|
screenshots?: SteamScreenshot[];
|
||||||
pc_requirements: {
|
pc_requirements: {
|
||||||
|
|||||||
@@ -5690,6 +5690,11 @@ hasown@^2.0.2:
|
|||||||
dependencies:
|
dependencies:
|
||||||
function-bind "^1.1.2"
|
function-bind "^1.1.2"
|
||||||
|
|
||||||
|
hls.js@^1.5.12:
|
||||||
|
version "1.6.15"
|
||||||
|
resolved "https://registry.yarnpkg.com/hls.js/-/hls.js-1.6.15.tgz#9ce13080d143a9bc9b903fb43f081e335b8321e5"
|
||||||
|
integrity sha512-E3a5VwgXimGHwpRGV+WxRTKeSp2DW5DI5MWv34ulL3t5UNmyJWCQ1KmLEHbYzcfThfXG8amBL+fCYPneGHC4VA==
|
||||||
|
|
||||||
hoist-non-react-statics@^3.3.2:
|
hoist-non-react-statics@^3.3.2:
|
||||||
version "3.3.2"
|
version "3.3.2"
|
||||||
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
|
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
|
||||||
|
|||||||
Reference in New Issue
Block a user