Custom HTTP Interceptors in HLS.js for Video Streaming
When building video streaming applications, you might need to intercept HTTP requests for authentication, custom decryption, or analytics during video playback. HLS.js makes this surprisingly straightforward with custom loaders. Let’s explore what HLS.js is and how to implement HTTP interceptors for video fragment loading.
What is HLS.js?
HLS.js is a JavaScript library that enables HTTP Live Streaming (HLS) playback in browsers that don’t natively support it. While Safari handles HLS natively, browsers like Chrome, Firefox, and Edge need HLS.js to parse .m3u8 playlists and stream video fragments seamlessly.
The library handles:
- Parsing HLS manifests (
.m3u8files) - Downloading and buffering video segments
- Adaptive bitrate switching based on network conditions
- Video playback coordination
Installation
Install HLS.js via npm or pnpm:
npm install hls.js
# or
pnpm add hls.js
Basic Usage
Here’s a minimal React implementation:
import { useEffect, useRef } from "react";
import Hls from "hls.js";
export function HlsPlayer() {
const videoRef = useRef<HTMLVideoElement>(null);
const playlistUrl = "https://example.com/video.m3u8";
useEffect(() => {
const video = videoRef.current;
if (!video) return;
if (Hls.isSupported()) {
const hls = new Hls();
hls.loadSource(playlistUrl);
hls.attachMedia(video);
}
}, []);
return <video ref={videoRef} controls />;
}
Implementing a Custom HTTP Interceptor
The real power comes when you need to intercept fragment requests.
Create a custom loader by extending Hls.DefaultConfig.loader:
class CustomLoader extends Hls.DefaultConfig.loader {
load(context: any, config: any, callbacks: any) {
if (context.frag) {
// Handle video fragments with custom logic
fetchVideoFragment(callbacks, context);
} else {
// Use default loader for playlists
super.load(context, config, callbacks);
}
}
};
The context.frag check distinguishes between playlist requests (handled by the default loader) and video fragment requests (where we apply custom logic).
Custom Fragment Fetcher
Here’s how to fetch fragments with custom handling:
async function fetchVideoFragment(callbacks: any, context: any) {
const start = performance.now();
try {
const response = await fetch(context.url, {
// Add custom headers here if needed
headers: {
'Authorization': '{{ Bearer token }}',
'X-Custom-Header': '{{Custom value}}'
}
});
const buffer = await response.arrayBuffer();
const end = performance.now();
callbacks.onSuccess(
{ data: buffer, url: context.url },
{
loading: { start, end },
loaded: buffer.byteLength,
retry: 0,
},
context
);
} catch (error) {
callbacks.onError(
{
code: 0,
text: (error as Error).message,
type: "networkError",
},
{ loading: { start, end: performance.now() }, retry: 0 },
context
);
}
}
This approach lets you:
- Add authentication tokens to fragment requests
- Decrypt encrypted video segments
- Track loading performance metrics
- Implement custom error handling
- Apply transformations to video data before playback
Putting It Together
Wire up the custom loader when initializing HLS:
export function HlsPlayerHttpInterceptor() {
const videoRef = useRef<HTMLVideoElement>(null);
const playlistUrl = "https://example.com/video.m3u8";
useEffect(() => {
const video = videoRef.current;
if (!video) return;
if (Hls.isSupported()) {
const hls = new Hls({
// uses the custom loader
loader: CustomLoader,
});
hls.loadSource(playlistUrl);
hls.attachMedia(video);
}
}, []);
return <video ref={videoRef} controls />;
}
Use Cases
Custom HTTP interceptors are particularly useful for:
- Protected content: Adding authentication headers to video fragment requests
- DRM workflows: Decrypting video segments before playback
- Analytics: Tracking fragment load times and network performance
- Caching strategies: Implementing custom caching logic
- A/B testing: Routing requests to different CDN endpoints