Build a Custom FLV Stream Player with HTML5 and JavaScriptFLV (Flash Video) used to be the dominant container for web video. Although Flash is deprecated and browsers no longer support native FLV playback, many archived streams and legacy workflows still produce FLV files. This guide shows how to build a custom FLV stream player using modern web technologies: an HTML5 UI, JavaScript for control and decoding, and a small decoding library to handle FLV data. The result is a practical, reusable player that can play FLV files/streams in browsers without relying on Adobe Flash.
Overview and approach
- Goal: Create a browser-based player that accepts an FLV file or FLV over HTTP (progressive) and plays it using the HTML5
- Constraints: Browsers do not natively decode the FLV container. We’ll either:
- Remux FLV into ISO BMFF (fMP4) segments and feed them to MSE, or
- Demux FLV and decode raw H.264/AAC frames into WebCodecs (where supported) or into a WebAssembly decoder.
- Recommended path for broad compatibility: demux FLV, extract H.264 and AAC elementary streams, remux into fragmented MP4 (fMP4) and append to MediaSource. This avoids heavy in-browser decoding and leverages native hardware decoders.
What you’ll need
- Basic HTML/CSS for the UI.
- JavaScript for fetching streams, demuxing, remuxing, and MSE integration.
- Libraries:
- flv.js (or a smaller FLV demuxer) — demuxes FLV and can push data to MSE.
- mp4box.js or mux.js — to build fMP4 segments if you do remuxing manually.
- Optionally: WebSocket or Fetch for streaming FLV over network.
- A server or locally served FLV files (CORS must allow access).
Architecture
- Fetch FLV bytes (Fetch with ReadableStream or WebSocket).
- Demux FLV to extract audio/video packets (timestamps, config frames).
- Initialize MediaSource and SourceBuffers for video/audio (fMP4 MIME types).
- Remux packets into fMP4 segments and append to the SourceBuffers.
- Implement player controls: play/pause, seek (if supported), volume, quality selection.
- Handle buffering, errors, and end-of-stream.
Key implementation steps
1) HTML UI
Include a
<video id="player" controls width="800" crossorigin="anonymous"></video>
2) Initialize MediaSource
Create MediaSource and attach to the video element. Set up SourceBuffers once init segments are available.
const video = document.getElementById('player'); const mediaSource = new MediaSource(); video.src = URL.createObjectURL(mediaSource); mediaSource.addEventListener('sourceopen', onSourceOpen);
3) Fetch and demux FLV
Use fetch with ReadableStream or WebSocket to receive FLV. If using flv.js, it handles much of this and can attach directly to a
Example (conceptual):
async function streamFlv(url, demuxer) { const resp = await fetch(url); const reader = resp.body.getReader(); while (true) { const { value, done } = await reader.read(); if (done) break; demuxer.appendBuffer(value); } demuxer.flush(); }
4) Remux into fMP4
When you receive codec configuration (e.g., SPS/PPS, AAC headers), create init segments for fMP4 using mp4box.js or mux.js, then create SourceBuffers with matching MIME types:
- Video: ‘video/mp4; codecs=“avc1.42C01E”’ (codec string from SPS)
- Audio: ‘audio/mp4; codecs=“mp4a.40.2”’
Generate and append init segments, then package subsequent frames into MP4 fragments (moof+mdat) and append.
mp4box.js usage sketch:
const mp4boxFile = MP4Box.createFile(); // configure tracks with codec info... // when you have a buffer: mp4boxFile.appendBuffer(arrayBuffer); mp4boxFile.flush();
Note: mp4box.js API requires ArrayBuffer with proper boundaries; study its docs for sample creation.
5) Handling timestamps, continuity, and seeking
Map FLV timestamps to MSE timeline. FLV timestamps are in milliseconds. Use consistent base timestamp and adjust when appending. For seeking, if server supports range requests or keyframe indexed seeking, request appropriate segments and reset SourceBuffers as needed.
6) Fallback: use flv.js
If you want a shortcut, flv.js implements the full pipeline: fetches FLV, demuxes, remuxes to fMP4 and feeds MSE. Example:
<script src="https://cdn.jsdelivr.net/npm/flv.js/dist/flv.min.js"></script> <script> if (flvjs.isSupported()) { const flvPlayer = flvjs.createPlayer({ type: 'flv', url: 'video.flv' }); flvPlayer.attachMediaElement(document.getElementById('player')); flvPlayer.load(); flvPlayer.play(); } </script>
This is production-ready for progressive FLV or FLV over HTTP/WebSocket.
Performance and browser support notes
- MediaSource Extensions (MSE) are widely supported on desktop browsers and modern mobile browsers, but codec support (H.264/AAC) may vary on some platforms.
- Using remuxing into fMP4 leverages native hardware decoders for efficiency.
- WebCodecs provides lower-level access to decoders but requires decoding frames manually—useful for custom processing or in browsers without MSE support for certain containers.
Example: Minimal working player using flv.js
<!doctype html> <html> <head><meta charset="utf-8"><title>FLV Player</title></head> <body> <video id="player" controls width="720"></video> <script src="https://cdn.jsdelivr.net/npm/flv.js/dist/flv.min.js"></script> <script> const url = 'https://example.com/sample.flv'; if (flvjs.isSupported()) { const player = flvjs.createPlayer({ type: 'flv', url }); player.attachMediaElement(document.getElementById('player')); player.load(); // optional: player.play(); player.on(flvjs.Events.ERROR, (errType, errDetail) => { console.error('FLV error', errType, errDetail); }); } else { document.body.innerText = 'FLV not supported in this browser.'; } </script> </body> </html>
Common pitfalls and troubleshooting
- CORS: Ensure the FLV resource allows cross-origin requests if hosted on another domain.
- Codec mismatch: Verify SPS/PPS and AAC config produce correct codec strings for SourceBuffer creation.
- Fragment boundaries: Improper moof/mdat construction will cause SourceBuffer.appendBuffer errors.
- Latency: Buffering strategy, keyframe interval, and fetch chunk sizes affect startup delay and live latency.
Extensions and advanced features
- Live streaming: Use chunked transfer or WebSocket to deliver FLV in real-time; handle segment eviction to limit memory.
- Playback analytics: expose events for buffering, bitrate, and errors.
- Transcoding fallback: if client doesn’t support required codecs, perform server-side transcoding to HLS/DASH or transcode to widely supported codecs.
- DRM: integrate with Encrypted Media Extensions (EME) after remuxing into fMP4 if content protection is needed.
Conclusion
Building a custom FLV stream player today means bridging the gap between an obsolete container and modern browser playback capabilities. The practical approach is to demux FLV in JavaScript, remux to fragmented MP4, and feed MSE. For most projects, flv.js offers a mature, ready-made implementation; for bespoke needs, a custom demux/remux pipeline gives maximum control.
Leave a Reply