93 lines
2.5 KiB
TypeScript
93 lines
2.5 KiB
TypeScript
import { useEffect, useMemo, useState } from "react";
|
|
|
|
import type {
|
|
EnvironmentBackground,
|
|
EnvironmentMediaItem,
|
|
} from "../core/environment/environmentSettings";
|
|
|
|
type ReadyEnvironmentMediaItem = EnvironmentMediaItem & {
|
|
url: string;
|
|
mediaKind: "image" | "video";
|
|
};
|
|
|
|
function isReadyMediaItem(
|
|
item: EnvironmentMediaItem,
|
|
): item is ReadyEnvironmentMediaItem {
|
|
return Boolean(item.url && item.mediaKind);
|
|
}
|
|
|
|
export function EnvironmentBackgroundMedia({
|
|
background,
|
|
}: {
|
|
background: EnvironmentBackground;
|
|
}) {
|
|
const items = useMemo(
|
|
() => background.items.filter(isReadyMediaItem),
|
|
[background.items],
|
|
);
|
|
const playlistIdentity = items.map((item) => `${item.id}:${item.url}`).join("|");
|
|
const [activeIndex, setActiveIndex] = useState(0);
|
|
const [failedIds, setFailedIds] = useState<Set<string>>(new Set());
|
|
const playableItems = items.filter((item) => !failedIds.has(item.id));
|
|
const activeItem = playableItems[activeIndex] ?? playableItems[0] ?? null;
|
|
|
|
useEffect(() => {
|
|
setActiveIndex(0);
|
|
setFailedIds(new Set());
|
|
}, [playlistIdentity]);
|
|
|
|
useEffect(() => {
|
|
if (
|
|
!background.enabled
|
|
|| !activeItem
|
|
|| activeItem.mediaKind !== "image"
|
|
|| playableItems.length < 2
|
|
) {
|
|
return;
|
|
}
|
|
const timer = window.setTimeout(() => {
|
|
setActiveIndex((current) => (current + 1) % playableItems.length);
|
|
}, background.imageDurationSeconds * 1_000);
|
|
return () => window.clearTimeout(timer);
|
|
}, [
|
|
activeItem,
|
|
background.enabled,
|
|
background.imageDurationSeconds,
|
|
playableItems.length,
|
|
]);
|
|
|
|
if (!background.enabled || !activeItem) return null;
|
|
|
|
return (
|
|
<div className="landing-stage__media" aria-hidden="true">
|
|
{activeItem.mediaKind === "video" ? (
|
|
<video
|
|
key={activeItem.url}
|
|
src={activeItem.url}
|
|
autoPlay
|
|
muted
|
|
loop={playableItems.length === 1}
|
|
playsInline
|
|
onEnded={() => setActiveIndex((current) => (
|
|
(current + 1) % playableItems.length
|
|
))}
|
|
onError={() => {
|
|
setFailedIds((current) => new Set(current).add(activeItem.id));
|
|
setActiveIndex(0);
|
|
}}
|
|
/>
|
|
) : (
|
|
<img
|
|
key={activeItem.url}
|
|
src={activeItem.url}
|
|
alt=""
|
|
onError={() => {
|
|
setFailedIds((current) => new Set(current).add(activeItem.id));
|
|
setActiveIndex(0);
|
|
}}
|
|
/>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|