Skip to main content

React Video Player

React Video Player Example

Build a full-featured video player using React and HLS.js with HesedVid’s streaming infrastructure.

Basic Player Component

Advanced Features

Quality Selector

Add a quality selector to manually control video quality:

interface Quality {
height: number;
bitrate: number;
level: number;
}
function QualitySelector({ hls }: { hls: Hls | null }) {
const [qualities, setQualities] = useState<Quality[]>([]);
const [currentLevel, setCurrentLevel] = useState(-1);
useEffect(() => {
if (!hls) return;
const updateQualities = () => {
const levels = hls.levels.map((level, index) => ({
height: level.height,
bitrate: level.bitrate,
level: index
}));
setQualities(levels);
setCurrentLevel(hls.currentLevel);
};
hls.on(Hls.Events.MANIFEST_PARSED, updateQualities);
hls.on(Hls.Events.LEVEL_SWITCHED, (_, data) => {
setCurrentLevel(data.level);
});
return () => {
hls.off(Hls.Events.MANIFEST_PARSED, updateQualities);
};
}, [hls]);
const handleQualityChange = (level: number) => {
if (hls) {
hls.currentLevel = level;
}
};
return (
<select
value={currentLevel}
onChange={(e) => handleQualityChange(Number(e.target.value))}
className="bg-black/50 text-white px-2 py-1 rounded"
>
<option value={-1}>Auto</option>
{qualities.map((q) => (
<option key={q.level} value={q.level}>
{q.height}p ({Math.round(q.bitrate / 1000)}kbps)
</option>
))}
</select>
);
}

Playback Analytics

Track viewing analytics:

function useVideoAnalytics(videoRef: React.RefObject<HTMLVideoElement>) {
const [analytics, setAnalytics] = useState({
watchTime: 0,
bufferingTime: 0,
bufferingEvents: 0,
qualitySwitches: 0
});
useEffect(() => {
const video = videoRef.current;
if (!video) return;
let watchStartTime: number | null = null;
let bufferStartTime: number | null = null;
const handlePlay = () => {
watchStartTime = Date.now();
};
const handlePause = () => {
if (watchStartTime) {
setAnalytics(prev => ({
...prev,
watchTime: prev.watchTime + (Date.now() - watchStartTime) / 1000
}));
watchStartTime = null;
}
};
const handleWaiting = () => {
bufferStartTime = Date.now();
setAnalytics(prev => ({
...prev,
bufferingEvents: prev.bufferingEvents + 1
}));
};
const handlePlaying = () => {
if (bufferStartTime) {
setAnalytics(prev => ({
...prev,
bufferingTime: prev.bufferingTime + (Date.now() - bufferStartTime) / 1000
}));
bufferStartTime = null;
}
};
video.addEventListener('play', handlePlay);
video.addEventListener('pause', handlePause);
video.addEventListener('waiting', handleWaiting);
video.addEventListener('playing', handlePlaying);
return () => {
video.removeEventListener('play', handlePlay);
video.removeEventListener('pause', handlePause);
video.removeEventListener('waiting', handleWaiting);
video.removeEventListener('playing', handlePlaying);
};
}, [videoRef]);
return analytics;
}

Thumbnail Preview

Show preview thumbnails on hover:

function ThumbnailPreview({
videoId,
duration,
width = 160,
height = 90
}: {
videoId: string;
duration: number;
width?: number;
height?: number;
}) {
const [thumbnailUrl, setThumbnailUrl] = useState<string | null>(null);
const handleMouseMove = (e: React.MouseEvent<HTMLDivElement>) => {
const rect = e.currentTarget.getBoundingClientRect();
const x = e.clientX - rect.left;
const percentage = x / rect.width;
const timestampMs = Math.floor(percentage * duration * 1000);
// Update thumbnail URL with timestamp
setThumbnailUrl(
`https://worker.hesedvid.com/t/${videoId}/${width}x${height}_${timestampMs}.jpg`
);
};
return (
<div
className="relative h-1 bg-gray-600 cursor-pointer"
onMouseMove={handleMouseMove}
onMouseLeave={() => setThumbnailUrl(null)}
>
{thumbnailUrl && (
<div className="absolute bottom-2 left-1/2 transform -translate-x-1/2">
<img
src={thumbnailUrl}
alt="Preview"
className="rounded shadow-lg"
width={width}
height={height}
/>
</div>
)}
</div>
);
}

Performance Optimization

Preloading

Preload video metadata for faster start:

function preloadVideo(url: string) {
if (Hls.isSupported()) {
const hls = new Hls();
hls.loadSource(url);
// Stop loading after manifest is parsed
hls.on(Hls.Events.MANIFEST_PARSED, () => {
hls.stopLoad();
});
// Clean up after 30 seconds
setTimeout(() => hls.destroy(), 30000);
}
}
// Preload next video in playlist
useEffect(() => {
if (nextVideoUrl) {
preloadVideo(nextVideoUrl);
}
}, [nextVideoUrl]);

Bandwidth Optimization

Monitor and adapt to network conditions:

function useBandwidthMonitor(hls: Hls | null) {
const [bandwidth, setBandwidth] = useState<number | null>(null);
const [recommendation, setRecommendation] = useState<string>('auto');
useEffect(() => {
if (!hls) return;
const checkBandwidth = () => {
const bw = hls.bandwidthEstimate;
setBandwidth(bw);
// Make quality recommendations
if (bw < 1_000_000) { // < 1 Mbps
setRecommendation('480p');
} else if (bw < 3_000_000) { // < 3 Mbps
setRecommendation('720p');
} else {
setRecommendation('1080p');
}
};
hls.on(Hls.Events.FRAG_LOADED, checkBandwidth);
return () => {
hls.off(Hls.Events.FRAG_LOADED, checkBandwidth);
};
}, [hls]);
return { bandwidth, recommendation };
}

Next Steps

  • Add custom controls: Build a custom UI with play/pause, seek bar, volume, and fullscreen.

  • Implement playlists: Create continuous playback with multiple videos.

  • Add captions: Display WebVTT subtitles (coming soon to HesedVid).

  • Track analytics: Send playback events to your analytics service.

  • Resources