Created
July 18, 2025 21:10
-
-
Save jonoroboto/afecba723af5491cbd528d8726311b20 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* eslint-disable @next/next/no-img-element */ | |
"use client"; | |
import { Loader2, PlayIcon } from "lucide-react"; | |
import dynamic from "next/dynamic"; | |
import { type FC, useCallback, useState } from "react"; | |
import { preload } from "react-dom"; | |
import type { MuxVideo } from "@/lib/sanity/sanity.types"; | |
// Types | |
interface VideoPlayProps { | |
poster: string; | |
vidUrl: string; | |
loop?: boolean; | |
muted?: boolean; | |
control?: boolean; | |
} | |
interface VideoProps extends VideoPlayProps { | |
playbackId: string; | |
autoPlay: boolean; | |
loading?: "lazy" | "eager"; | |
} | |
interface VideoThumbnailProps extends VideoPlayProps { | |
loading?: "lazy" | "eager"; | |
onPlay: () => void; | |
} | |
// Constants | |
const VIDEO_DIMENSIONS = { | |
width: 1600, | |
height: 900, | |
} as const; | |
const MUX_BASE_URL = "https://image.mux.com"; | |
const MUX_STREAM_URL = "https://stream.mux.com"; | |
// Utilities | |
const getMuxVideoProps = ( | |
muxId: string, | |
): Pick<VideoPlayProps, "poster" | "vidUrl"> => ({ | |
poster: `${MUX_BASE_URL}/${muxId}/thumbnail.webp?fit_mode=smartcrop&time=0`, | |
vidUrl: `${MUX_STREAM_URL}/${muxId}.m3u8`, | |
}); | |
// Components | |
const LoadingPlaceholder: FC = () => ( | |
<div className="relative aspect-video w-full"> | |
<div className="absolute inset-0 flex items-center justify-center bg-zinc-100"> | |
<Loader2 className="h-8 w-8 animate-spin text-zinc-400" /> | |
</div> | |
</div> | |
); | |
const PlayButton: FC<{ onClick: () => void }> = ({ onClick }) => ( | |
<button | |
type="button" | |
onClick={onClick} | |
className="absolute inset-0 m-auto flex h-[54px] w-[54px] items-center justify-center rounded-full bg-zinc-900/70 ring-1 ring-zinc-600 transition hover:scale-105 hover:bg-zinc-900/80" | |
aria-label="Play video" | |
> | |
<svg | |
width="11" | |
height="14" | |
viewBox="0 0 11 14" | |
fill="none" | |
xmlns="http://www.w3.org/2000/svg" | |
className="ml-0.5" | |
> | |
<path d="M0 14V0L11 7L0 14Z" className="fill-white" /> | |
</svg> | |
</button> | |
); | |
const HlsVideoPlayer = dynamic( | |
() => import("./hls-video").then((mod) => ({ default: mod.HlsVideo })), | |
{ | |
loading: LoadingPlaceholder, | |
ssr: false, | |
}, | |
); | |
const VideoPlayer: FC<VideoPlayProps> = ({ | |
control, | |
loop, | |
muted, | |
poster, | |
vidUrl, | |
}) => ( | |
<HlsVideoPlayer | |
autoPlay | |
poster={poster} | |
vidUrl={vidUrl} | |
loop={control ? loop : undefined} | |
muted={control ? muted : undefined} | |
controls={control} | |
width={VIDEO_DIMENSIONS.width} | |
height={VIDEO_DIMENSIONS.height} | |
className="w-full object-cover" | |
/> | |
); | |
const VideoThumbnail: FC<VideoThumbnailProps> = ({ | |
poster, | |
loading, | |
onPlay, | |
}) => ( | |
<div className="relative w-full "> | |
<img | |
src={poster} | |
width={VIDEO_DIMENSIONS.width} | |
height={VIDEO_DIMENSIONS.height} | |
loading={loading === "lazy" ? "lazy" : "eager"} | |
alt="Video thumbnail" | |
className="h-full w-full object-cover transition-opacity duration-300 hover:opacity-90" | |
/> | |
<PlayButton onClick={onPlay} /> | |
</div> | |
); | |
const VideoWithControls: FC<VideoPlayProps & { loading?: "lazy" | "eager" }> = ( | |
props, | |
) => { | |
const [isPlaying, setIsPlaying] = useState(false); | |
const handlePlay = useCallback(() => setIsPlaying(true), []); | |
return ( | |
<div className="relative w-full"> | |
{isPlaying ? ( | |
<VideoPlayer {...props} /> | |
) : ( | |
<VideoThumbnail {...props} onPlay={handlePlay} /> | |
)} | |
</div> | |
); | |
}; | |
export const Video: FC<VideoProps> = ({ | |
playbackId, | |
control, | |
autoPlay, | |
loop, | |
muted, | |
loading = "eager", | |
}) => { | |
const { poster, vidUrl } = getMuxVideoProps(playbackId); | |
// Preload critical assets | |
preload(vidUrl, { | |
as: "video", | |
fetchPriority: "high", | |
}); | |
preload(poster, { | |
as: "image", | |
fetchPriority: "high", | |
}); | |
const videoProps = { poster, loading, vidUrl, loop, muted, control }; | |
return ( | |
<div className="mx-auto max-w-7xl"> | |
{control && autoPlay ? ( | |
<VideoPlayer {...videoProps} /> | |
) : ( | |
<VideoWithControls {...videoProps} loading={loading} /> | |
)} | |
</div> | |
); | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment