Understanding Adaptive Bitrate Streaming

Tonmoy DebTonmoy Deb
10 min read

YouTube is one platform that most of us use in our day-to-day activities. One thing we tend to take for granted is how smoothly these videos stream. Users can pick up video quality on YouTube, and if mine is slow, it automatically adjusts itself to deliver the content in lower quality so that it can be watched without interruptions. This technology is called Adaptive Bitrate Streaming, with which I would like to discuss both its working and importance.

Problem 1: Video Being Slow to Load

When you attempt to play a video file in an HTML video player, your browser buffers the whole video file before beginning playback. Whatever the size file and whatever your network condition were, you'd wait for a good while. The bigger the file size, the longer you'd be waiting for—definitely a user experience killer.

Problem 2: Quality Switching Is Inefficient

For this purpose, possible solutions to the above problems seem to involve the conversion of video files into various quality formats, where users can finally select the best based on their network speed or preference. It sure is a good idea but fails miserably in execution. Why?

Here's a user waiting for the video to be loaded in full. After watching half the video, if they change their mind and switch quality, then from the beginning of the video, the player reloads and starts playing, thus stalling itself for a bit longer. Frustration, inefficiency. Very much so.

Now, let us delve further into how Adaptive Bitrate Streaming successfully addresses these issues by allowing for streamlined viewing.

Solution: Adaptive Bitrate Streaming

This is a method where video streaming dynamically switches qualities depending on client network speed and instead of loading the whole video file at once, it loads a small portion of the video chunk by chunk. After all of this users can experience smooth video streaming. let’s deep dive into this.

Segmented Video Streaming

The whole video is divided into smaller segments (2-10 seconds each). and to track all the segments there is one playlist file that takes care of the serialization of the segments. So at the end, we pass this playlist file to the user and the user loads the video segment by segment. There is no need to wait for the whole file to fetch

Multiple Quality

The video file is stored in multiple-quality with smaller segments. To track all the qualities there will be a master playlist file that lists all the quality options to the user and the user can select from the options. All the quality video is stored in smaller segments so the user can switch quality whenever he wants. only the current chunk will be loaded again.

Realtime Quality Adjustments

on the master playlist file, there is a property called BANDWIDTH. Using that system will automatically switch video quality based on user network speed.

How to implement ABR?

There are primarily two ways to implement adaptive streaming

HTTP Live Streaming (HLS)

Developed by Apple, HLS uses .m3u8 playlists and is widely supported on iOS/macOS. It originally used MPEG-TS but now supports fMP4 and has a low-latency variant (LL-HLS).

Dynamic Adaptive Streaming Over HTTP (DASH)

An open-standard streaming protocol that uses .mpd files and supports fMP4. It offers broader codec support (H.265, VP9, AV1) and is widely used on Android, Windows, and web platforms.

For today's article, I’ll continue with the HLS method. So enough talking dive into some examples.

Implementing HLS AVR

Tools

Before diving into examples let me just quickly introduce our main tool.

FFmpeg: As we are talking about encoding & decoding stuff. We will need ffmpeg for this media file encoding. so make sure you’ve installed it on your device. for reference, you can follow this documentation.

Now we are ready to dive into examples.

Step 1: Convert Media Files Into HLS Format

In this step, we will convert media files to small segments in HLS format. To achieve this we need to run this command.

ffmpeg -i input.mp4 -codec: copy -start_number 0 -hls_time 5 -hls_list_size 0 -f hls output.m3u8

Explanations:

  • -i input.mp4 specifies our input file for this command

  • -codec: copy this specifies to copy the input file codec instead of re-encoding for the output file

  • -start_number 0 specifies the starting number for the segments

  • -hls_time 5 specifies each segment duration in seconds. in our case, it’s 5 seconds.

  • -hls_list_size 0 This specifies how many segments should be stored in the output playlist file. to store all the segments we need to use 0 value.

  • -f hls output.m3 This specifies output format as hls format and file name as output.m3u8

After executing this command you will see multiple .ts file was generated based on your input file size and one output.m3u8 file is generated. This file is our playlist file that tracks all the segments & this file is served to the client.

Step 2: Playing HLS Format File

In general, we just pass the file URL to the HTML video element & that’s it, we can play the video file. However, we don’t have any native support for the HLS format file. we need to use a third-party package called hls.js. Here is a simple example of that

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>HLS Video Preview</title>
    <script src="<https://cdn.jsdelivr.net/npm/hls.js@1.4.12/dist/hls.min.js>"></script>
    <style>
        body {
            font-family: Arial, sans-serif;
            text-align: center;
        }
    </style>
</head>
<body>

    <h2>HLS Video Preview</h2>

    <video id="hls-video" controls width="640" height="360"></video>

    <script>
        const video = document.getElementById('hls-video');
        const hlsUrl = "./output.m3u8"; // Replace with your HLS URL

        if (Hls.isSupported()) {
            const hls = new Hls();
            hls.loadSource(hlsUrl);
            hls.attachMedia(video);
        } else if (video.canPlayType('application/vnd.apple.mpegurl')) {
            // Fallback for Safari (which supports native HLS)
            video.src = hlsUrl;
        } else {
            alert("Your browser does not support HLS playback.");
        }
    </script>

</body>
</html>

If we try to preview (use vs code live server extension) this file on the browser we will see the video is playing as it is. If your video size is large, you will notice the difference between normal video file streaming & hls encoded video file streaming. If you want to explore more you can open the Network Tab from browser developer tools. We will see that the video segments are loaded one by one.

Step 3: Converting Into Multiple Qualities

Currently, we’re providing video chunk by chunk which is already lot optimized but we need to give a quality switching feature to users also. to achieve that we need to customize our command a little bit.

ffmpeg -y -i "input.mp4" \\
  -vf scale=w=-2:h=720:force_original_aspect_ratio=decrease:force_divisible_by=2 -c:a aac -ar 48000 -c:v h264 -profile:v main -crf 26 -sc_threshold 0 -g 48 -keyint_min 48 -hls_time 4 -hls_playlist_type vod -b:v 3000k -maxrate 3600k -bufsize 6000k -b:a 128k -hls_segment_filename "720p_%03d.ts" -f hls "720p.m3u8" \\
  -vf scale=w=-2:h=480:force_original_aspect_ratio=decrease:force_divisible_by=2 -c:a aac -ar 48000 -c:v h264 -profile:v main -crf 28 -sc_threshold 0 -g 48 -keyint_min 48 -hls_time 4 -hls_playlist_type vod -b:v 1500k -maxrate 1800k -bufsize 3000k -b:a 128k -hls_segment_filename "480p_%03d.ts" -f hls "480p.m3u8" \\
  -vf scale=w=-2:h=360:force_original_aspect_ratio=decrease:force_divisible_by=2 -c:a aac -ar 48000 -c:v h264 -profile:v main -crf 30 -sc_threshold 0 -g 48 -keyint_min 48 -hls_time 4 -hls_playlist_type vod -b:v 800k -maxrate 960k -bufsize 1600k -b:a 96k -hls_segment_filename "360p_%03d.ts" -f hls "360p.m3u8" \\
  -vf scale=w=-2:h=240:force_original_aspect_ratio=decrease:force_divisible_by=2 -c:a aac -ar 48000 -c:v h264 -profile:v main -crf 32 -sc_threshold 0 -g 48 -keyint_min 48 -hls_time 4 -hls_playlist_type vod -b:v 400k -maxrate 480k -bufsize 800k -b:a 64k -hls_segment_filename "240p_%03d.ts" -f hls "240p.m3u8"

Explanations:

  • -y This is to allow automatically all operations. in some cases, ffmpeg might ask you for your confirmation (like overwriting a file)

  • -i "input.mp4" This specifies the input file

  • -vf scale=w=-2:h=720 This resize video into 720p while maintaining the aspect ratio (using w=-2 it automatically calculates the width)

  • force_original_aspect_ratio=decrease This prevents the video from stretching while resizing

  • force_divisible_by=2 Ensures video dimension is always even number, to prevent runtime encoding error.

  • -c:a aac -ar 48000 Encode audio format in ACC format with 48 kHz sampling rate.

  • -c:v h264 -profile:v main Encoding video in H.264 format main profile

  • -crf 26 Constant Rate Factor (CRF) controls video quality

    • Higher Value = low quality, small file size

    • Lower Value = better quality, large file size

  • -sc_threshold 0 Ensures uniform keyframe spacing

  • -g 48 Sets Group of Pictures to 48

  • -keyint_min 48 Ensure minimum keyframe interval 48

  • -hls_time 4 Each segment duration is set to 4 seconds

  • -hls_playlist_type vod Generates a fixed playlist with all segments

  • -b:v 3000k Sets video bitrate to 3Mbps

  • -maxrate 3600k -bufsize 6000k Controls bitrate fluctuations for better stability

  • -b:a 128k Sets auto bitrate to 128kbps

  • -hls_segment_filename "720p_%03d.ts" Defines segment file name. We will generate segments for different qualities so to differentiate them we need to use a specific file name format.

  • -f hls "720p.m3u8" Specifying playlist output file path

The same command options are applied for other qualities also with different values for sure. Now we need to create a master playlist file to track different quality playlists. here is an example of the master playlist.

#EXTM3U
#EXT-X-VERSION:3

# 720p Stream
#EXT-X-STREAM-INF:BANDWIDTH=3600000,RESOLUTION=1280x720
720p.m3u8

# 480p Stream
#EXT-X-STREAM-INF:BANDWIDTH=1800000,RESOLUTION=854x480
480p.m3u8

# 360p Stream
#EXT-X-STREAM-INF:BANDWIDTH=960000,RESOLUTION=640x360
360p.m3u8

# 240p Streamjust 
#EXT-X-STREAM-INF:BANDWIDTH=480000,RESOLUTION=426x240
240p.m3u8

Explanations:

  • #EXTM3U Marks this as an HLS playlist.

  • #EXT-X-VERSION:3 HLS version 3, widely supported.

  • ****#EXT-X-STREAM-INF ****Each section defines a video quality option:

    • BANDWIDTH=3600000 Maximum bitrate for this stream (in bits per second). this helps with auto video quality switching based on the user network connection speed

    • RESOLUTION=1280x720 Video resolution.

    • 720p.m3u8 Points to the playlist file for that resolution.

Now to preview this video provide this master.m3u8 file path in the preview example and open in a browser. Now to test whether the auto quality switching is working or not you can use the Network Throttle option from the Browser Network Tab. If you choose Slow 4G from there then you will notice the video player automatically reduces the video quality. if you disable the throttle option then video quality will be switched back to high quality automatically.

Step 4: Manual Quality Switching

Now if you want to give a feature to your users to switch video quality manually then you can just update our preview example with the following code.

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>HLS Video Preview with Quality Selector</title>
    <script src="<https://cdn.jsdelivr.net/npm/hls.js@1.4.12/dist/hls.min.js>"></script>
    <style>
        body {
            font-family: Arial, sans-serif;
            text-align: center;
        }

        #quality-selector {
            margin-top: 10px;
            padding: 5px;
        }
    </style>
</head>

<body>

    <h2>HLS Video Preview</h2>

    <video id="hls-video" controls width="640" height="360"></video>
    <br>
    <label for="quality-selector">Select Quality:</label>
    <select id="quality-selector" disabled>
        <option>Auto</option>
    </select>

    <script>
        const video = document.getElementById('hls-video');
        const qualitySelector = document.getElementById('quality-selector');
        const hlsUrl = "./master.m3u8"; // Replace with your .m3u8 URL

        if (Hls.isSupported()) {
            const hls = new Hls();
            hls.loadSource(hlsUrl);
            hls.attachMedia(video);

            hls.on(Hls.Events.MANIFEST_PARSED, () => {
                const levels = hls.levels.map((level, index) => ({
                    index,
                    label: level.height ? `${level.height}p` : `${level.bitrate / 1000} kbps`
                }));

                qualitySelector.innerHTML = `<option value="-1">Auto</option>`;
                levels.forEach(level => {
                    qualitySelector.innerHTML += `<option value="${level.index}">${level.label}</option>`;
                });

                qualitySelector.disabled = false;
            });

            qualitySelector.addEventListener('change', (event) => {
                const selectedLevel = parseInt(event.target.value);
                hls.currentLevel = selectedLevel;
            });
        } else if (video.canPlayType('application/vnd.apple.mpegurl')) {
            // Fallback for Safari (which supports native HLS)
            video.src = hlsUrl;
        } else {
            alert("Your browser does not support HLS playback.");
        }
    </script>

</body>

    </html>

Here first we list all the available quality options and then let the user select his desired video quality and enjoy.

Explanation

After loading the master playlist file it provides available quality options in hls.levels and for changing quality we can set the level index in hls.currentLevel. By default, it’s -1 means automatic switching based on network bandwidth

Real-world Implementation

This article covers only required commands but in Real-world applications, you might need to implement various technologies. to demonstrate that I made a project using Nest JS & React JS. You will find the source code here. again it’s not production-grade code or something, it’s just an example of using the commands in your project.

Conclusion

If you've followed this guide, congratulations! 🎉 You now understand Adaptive Bitrate Streaming (ABR) and how to implement HLS with FFmpeg. While this article covers the basics, real-world applications require additional considerations like backend integration and optimization strategies. Once you're comfortable with these fundamentals, you can explore low-latency HLS (LL-HLS) for even better performance.

Stay tuned for more advanced topics!

10
Subscribe to my newsletter

Read articles from Tonmoy Deb directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Tonmoy Deb
Tonmoy Deb

Web app developer who loves clean, organized code. Passionate about building projects, solving problems, and sharing knowledge through insightful coding blogs.