All Collections
NFT Metadata
HLS tutorial for video NFTs
HLS tutorial for video NFTs

A quick tutorial for using HLS to stream video NFTs

Jeffy avatar
Written by Jeffy
Updated over a week ago

For an overview of HLS and why we recommend it for video NFTs, see the HLS section in our Video NFTs guide.

How HLS works

HLS delivers videos as a series of small files called media segment files. These are either Video Transport Stream files (*.ts) or fragmented mp4 files (*.mp4 or *.fmp4).

HLS uses a multivariant (master) playlist to start the stream. This playlist is a text file (*.m3u8) listing all the variant playlists files available to the player and additional information about what those variant playlists contain. The variant playlists contain lists of the video segments.

  Master Playlist (.m3u8)
├── Variant Playlist 1 (.m3u8)
│ ├── Video Segment 1 (.ts)
│ ├── Video Segment 2 (.ts)
│ └── ...
├── Variant Playlist 2 (.m3u8)
│ ├── Video Segment 1 (.ts)
│ ├── Video Segment 2 (.ts)
│ └── ...
├── Variant Playlist N (.m3u8)
├── Video Segment 1 (.ts)
├── Video Segment 2 (.ts)
└── ...

A live example

Guild of Guardians uses this setup for their collection. Let's take a look at an example:

The animation_url points to CollectionAsset_Hero_Varik_Base.m3u8, which is the multi-variant playlist and is a text file that holds references to other variant playlists. It also lists the peak bitrate (BANDWIDTH) of each variant—this is used to determined which variant should be played based on the client’s bandwidth.

#EXTM3U #EXT-X-VERSION:3 #EXT-X-STREAM-INF:BANDWIDTH=2944000,RESOLUTION=480x480,NAME="480" CollectionAsset_Hero_Varik_Base_480p.m3u8 #EXT-X-STREAM-INF:BANDWIDTH=5504000,RESOLUTION=720x720,NAME="720" CollectionAsset_Hero_Varik_Base_720p.m3u8 #EXT-X-STREAM-INF:BANDWIDTH=8576000,RESOLUTION=1080x1080,NAME="1080" CollectionAsset_Hero_Varik_Base_1080p.m3u8 #EXT-X-STREAM-INF:BANDWIDTH=16768000,RESOLUTION=1440x1440,NAME="1440" CollectionAsset_Hero_Varik_Base_1440p.m3u8 #EXT-X-STREAM-INF:BANDWIDTH=36224000,RESOLUTION=2000x2000,NAME="2000" CollectionAsset_Hero_Varik_Base_2000p.m3u8

Each variant playlist file then contains the path to the video itself. Here’s how the 720p variant playlist looks like:


You can learn more about the tags (#) in Apple's documentation or check out the tutorial in the next section.

Basic HLS tutorial

In this tutorial, we will create video NFTs in the Immutable marketplace using HLS.


To stream your videos using HLS, you’ll need:

  • A storage solution that supports video streaming

  • A video transcoder to ready your videos for streaming

  • A tool to measure the peak bitrate of your videos

For this tutorial, we used a dedicated Pinata gateway for storage (requires a paid subscription) and ffmpeg for transcoding the videos and measuring the bitrate.

Note: We chose those tools as they were easily accessible. We recommend you select your own tools based on your project needs in a production environment.

Step-by-step tutorial

1. Create the variant videos and multivariant playlists

Use a video transcoder to convert your video to a transport stream (*.ts files) and create variants with different resolutions. Create a multivariant playlist for each variant.

You can do both using ffmpeg. Replace the filenames and run the following in the terminal:

# Transcode for 720p
ffmpeg -i cat.Mov -c:v libx264 -crf 23 -preset ultrafast -c:a copy -hls_time 10 -hls_list_size 0 -s 1280x720 -hls_segment_filename "cat_720p_%03d.ts" cat_720p.m3u8

# Transcode for 480p
ffmpeg -i cat.Mov -c:v libx264 -crf 23 -preset ultrafast -c:a copy -hls_time 10 -hls_list_size 0 -s 854x480 -hls_segment_filename "cat_480p_%03d.ts" cat_480p.m3u8

# Transcode for 360p
ffmpeg -i cat.Mov -c:v libx264 -crf 23 -preset ultrafast -c:a copy -hls_time 10 -hls_list_size 0 -s 640x360 -hls_segment_filename "cat_360p_%03d.ts" cat_360p.m3u8

2. Get the peak/max bitrate for each variant

Using ffmpeg, get the bitrate (kb/s) for each variant. You will need to specify the bitrate in the master playlist. This information helps the client player select the best variant to play according to the user's network and device capabilities.

ffmpeg -i cat_720p_000.ts -c copy -f null /dev/null 2>&1 | grep 'bitrate:'
ffmpeg -i cat_480p_000.ts -c copy -f null /dev/null 2>&1 | grep 'bitrate:'
ffmpeg -i cat_360p_000.ts -c copy -f null /dev/null 2>&1 | grep 'bitrate:'

3. Create a master playlist

Create a master playlist (*.m3u8 file). This is typically done manually. Here's an example:


#EXTM3U - is the file header.

#EXT-X-VERSION - specifies the compatibility version of the playlist file.

#EXT-X-STREAM-INF - indicates that the next URL in the playlist file identifies another playlist file. Every #EXT-X-STREAM-INF tag must include the bandwidth attribute, which is an integer that is the bitrate (obtained in step 2) for each media file, in bits per second (kb/s * 1000).

Video players will usually start playing from the first variant (480p in this example), so the first variant you specify in the list matters. The order of other variants don’t matter.

4. Upload your master + variant playlists and video files

Upload your *.m3u8 files and video files to Pinata or another storage service. All the files should be in the same folder. See example.

5. Create NFT metadata and mint your NFTs

Refer to the NFT minting tutorial if you’re not familiar with this step.

When creating metadata, make sure you set the animation_url to the location of your master playlist and animation_url_mime_type to application/ Example:

"name": "1st NFT",
"description": "This is your 1st nft",
"animation_url": "",
"animation_url_mime_type": "application/",
"attack": 123,
"collectable": true,
"power": "adorable"

6. Check your NFT in the marketplace

In the marketplace, you should see the video of your NFT auto play

In the browser, you can confirm which variant of the video is playing by viewing the network tab in console:

As mentioned in step 3, the first variant in the master playlist will be played first. In the above example, the default 480p variant played first before switching to the 1080p variant based on network conditions.

Did this answer your question?