HTTP Live Streaming (HLS-VOD)

In this post, we are going to create a video server that can serve videos with multiple bit-rates, also known as adaptive bit-rate streaming.

We are going to implement the technique by using Apple HTTP Live Streaming (HLS) with Video on Demand (VOD) using a desktop application and IIS.

What is adaptive bitrate streaming?

“Adaptive bitrate streaming is a technique used in streaming multimedia over computer networks” - Wikipedia.

The key point is the adaptation of streaming according to the client machine status, like bandwidth up down. By using HLS, we are going to implement the thought.

What is HLS?

Apple HTTP Live Streaming, known as HLS, is a video streaming protocol based on HTTP.

HLS supports -

  • Live streaming
  • Video on demand (VOD)
  • Multiple bit rates (MBR) Streaming
  • Smart switching streams based on the client environment
  • Media encryption
  • User authentication

Learn more: https://developer.apple.com/documentation/http_live_streaming

We are going to separate the whole process in different steps with the main thought of

  • Transcoding Tools
  • Server Configuration & Testing

Transcoding Tools

In this step, we need to encode the input video file to different segments with VOD playlist. Using FFmpeg, we are going to transcode our input file by command.

We are going to use a Windows Forms application to transcode the video file. Let’s create a new Windows Forms application and prepare the UI.

Open Visual Studio. Go to > File > New > Project and choose Windows Form Application by clicking menu > Windows Desktop.

HTTP Live Streaming (HLS-VOD) 

Go to App.config to add the output folder path.

HTTP Live Streaming (HLS-VOD) 

Manage Encoding Process

Add a class library which will process the task while we are going to transcode the video file. This is a custom solution based on original source https://github.com/vladjerca/FFMpegSharp

HTTP Live Streaming (HLS-VOD) 

This has some modification for multi-bitrate segment command.

FFmpeg

Let’s download the open source FFmpeg libraries from the below download link.

Download: https://www.videohelp.com/download/ffmpeg-3.1.4-win32-static.zip

Version Used- ffmpeg-3.1.4.

Unzip the downloaded file. There will be several folders like below folder image.
HTTP Live Streaming (HLS-VOD)
Open the bin folder to copy the below three files to the application directory. In this example, the files are copied to debug folder to access.
 
HTTP Live Streaming (HLS-VOD) 

Let’s get started with the coding section.

Transcoder

First, we are going to browse the video file (.mp4) with the below code section to display the browsed path in a textbox, which is our actual input path.

  1. txtFile.Text = string.Empty;  
  2. OpenFileDialog ofdFile = new OpenFileDialog();  
  3. ofdFile.Multiselect = false;  
  4. ofdFile.Filter = "Video files (*.mp4)|*.mp4";  
  5. ofdFile.Title = "Select File.";  
  6. if (ofdFile.ShowDialog() == DialogResult.OK)  
  7. {  
  8.     var file = ofdFile.FileNames;  
  9.     txtFile.Text = file[0].ToString();  
  10. }  

Next, we are going to process the video file into a common video resolution with ffmpeg command.

  • 1080p
  • 720p
  • 480p
  • 360p
  • 240p

Here’s the different command for each resolution.

1080p

  1. " -i \"{0}\" -vf scale=w=1920:h=1080:force_original_aspect_ratio=decrease -c:a aac -ar 48000 -c:v h264 -profile:v main -crf 20 -sc_threshold 0 -g 48 -keyint_min 48 -hls_time 4 -hls_playlist_type vod -b:v 5000k -maxrate 5350k -bufsize 7500k -b:a 192k -hls_segment_filename " + fileOutput + "1080p_%d.ts " + fileOutput + "1080p.m3u8"  

720p

  1. " -i \"{0}\" -vf scale=w=1280:h=720:force_original_aspect_ratio=decrease -c:a aac -ar 48000 -c:v h264 -profile:v main -crf 20 -sc_threshold 0 -g 48 -keyint_min 48 -hls_time 4 -hls_playlist_type vod -b:v 2800k -maxrate 2996k -bufsize 4200k -b:a 128k -hls_segment_filename " + fileOutput + "720p_%d.ts " + fileOutput + "720p.m3u8"  

480p

  1. " -i \"{0}\" -vf scale=w=842:h=480:force_original_aspect_ratio=decrease -c:a aac -ar 48000 -c:v h264 -profile:v main -crf 20 -sc_threshold 0 -g 48 -keyint_min 48 -hls_time 4 -hls_playlist_type vod -b:v 1400k -maxrate 1498k -bufsize 2100k -b:a 128k -hls_segment_filename " + fileOutput + "480p_%d.ts " + fileOutput + "480p.m3u8"  

360p

  1. " -i \"{0}\" -vf scale=w=640:h=360:force_original_aspect_ratio=decrease -c:a aac -ar 48000 -c:v h264 -profile:v main -crf 20 -sc_threshold 0 -g 48 -keyint_min 48 -hls_time 4 -hls_playlist_type vod -b:v 800k -maxrate 856k -bufsize 1200k -b:a 96k -hls_segment_filename " + fileOutput + "360p_%d.ts " + fileOutput + "360p.m3u8"  

240p

  1. " -i \"{0}\" -vf scale=w=426:h=240:force_original_aspect_ratio=decrease -c:a aac -ar 48000 -c:v h264 -profile:v main -crf 20 -sc_threshold 0 -g 48 -keyint_min 48 -hls_time 4 -hls_playlist_type vod -b:v 240k -maxrate 240k -bufsize 480k -b:a 64k -hls_segment_filename " + fileOutput + "240p_%d.ts " + fileOutput + "240p.m3u8"  

For multiple bitrate file, we need to create a master playlist. The following code section will create a master playlist with all those command information.

  1. //Create index as master playlist  
  2. string path = fileOutput + "index.m3u8";  
  3. File.Create(path).Dispose();  
  4. string[] line ={  
  5.     "#EXTM3U",  
  6.     "#EXT-X-VERSION:3",  
  7.     "#EXT-X-STREAM-INF:BANDWIDTH=10000,RESOLUTION=426x240",  
  8.     "240p.m3u8",  
  9.     "#EXT-X-STREAM-INF:BANDWIDTH=420000,RESOLUTION=640x360",  
  10.     "360p.m3u8",  
  11.     "#EXT-X-STREAM-INF:BANDWIDTH=680000,RESOLUTION=842x480",  
  12.     "480p.m3u8",  
  13.     "#EXT-X-STREAM-INF:BANDWIDTH=1256000,RESOLUTION=1280x720",  
  14.     "720p.m3u8",  
  15.     "#EXT-X-STREAM-INF:BANDWIDTH=2000000,RESOLUTION=1920x1080",  
  16.     "1080p.m3u8"  
  17. };  
  18.   
  19. File.WriteAllLines(path, line);  

Then with below code section, start the process by passing the command string with input source.

  1. FFMpeg encoder = new FFMpeg();  
  2. encoder.OnProgress += encoder_OnProgress;  
  3. Task.Run(() => encoder.ToTS(inputFile, conversionArgs));  

Finally, here is the complete Transcoding event.

  1. private void btnTranscode_Click(object sender, EventArgs e)  
  2. {  
  3.     try  
  4.     {  
  5.         bool isProcessing = IsProcessRunning("ffmpeg");  
  6.         if (!isProcessing)  
  7.         {  
  8.             if (txtFile.Text != string.Empty)  
  9.             {  
  10.                 string _rootPath = Environment.CurrentDirectory;  
  11.                 string ffmpegOutput = ConfigurationManager.AppSettings["ffmpegOutput"];  
  12.                 this.Cursor = Cursors.WaitCursor;  
  13.                 this.Text = "Transcoding...";  
  14.                 btnTranscode.Text = "Transcoding..";  
  15.   
  16.                 string inputFile = txtFile.Text.ToString();  
  17.                 string fileOutput = ffmpegOutput + toUnderscore(Path.GetFileNameWithoutExtension(inputFile)) + "\\";  
  18.                 if (!Directory.Exists(fileOutput))  
  19.                 {  
  20.                     SetFolderPermission(fileOutput);  
  21.                     DirectoryInfo di = Directory.CreateDirectory(fileOutput);  
  22.                 }  
  23.   
  24.                 //Create index as master playlist  
  25.                 string path = fileOutput + "index.m3u8";  
  26.                 File.Create(path).Dispose();  
  27.                 string[] line ={  
  28.                     "#EXTM3U",  
  29.                     "#EXT-X-VERSION:3",  
  30.                     "#EXT-X-STREAM-INF:BANDWIDTH=10000,RESOLUTION=426x240",  
  31.                     "240p.m3u8",  
  32.                     "#EXT-X-STREAM-INF:BANDWIDTH=420000,RESOLUTION=640x360",  
  33.                     "360p.m3u8",  
  34.                     "#EXT-X-STREAM-INF:BANDWIDTH=680000,RESOLUTION=842x480",  
  35.                     "480p.m3u8",  
  36.                     "#EXT-X-STREAM-INF:BANDWIDTH=1256000,RESOLUTION=1280x720",  
  37.                     "720p.m3u8",  
  38.                     "#EXT-X-STREAM-INF:BANDWIDTH=2000000,RESOLUTION=1920x1080",  
  39.                     "1080p.m3u8"  
  40.                 };  
  41.                 File.WriteAllLines(path, line);  
  42.   
  43.                 //Command  
  44.                 string conversionArgs = string.Format("-hide_banner -y" +  
  45.                                                         " -i \"{0}\" -vf scale=w=426:h=240:force_original_aspect_ratio=decrease -c:a aac -ar 48000 -c:v h264 -profile:v main -crf 20 -sc_threshold 0 -g 48 -keyint_min 48 -hls_time 4 -hls_playlist_type vod -b:v 240k -maxrate 240k -bufsize 480k -b:a 64k -hls_segment_filename " + fileOutput + "240p_%d.ts " + fileOutput + "240p.m3u8" +  
  46.                                                         " -i \"{0}\" -vf scale=w=640:h=360:force_original_aspect_ratio=decrease -c:a aac -ar 48000 -c:v h264 -profile:v main -crf 20 -sc_threshold 0 -g 48 -keyint_min 48 -hls_time 4 -hls_playlist_type vod -b:v 800k -maxrate 856k -bufsize 1200k -b:a 96k -hls_segment_filename " + fileOutput + "360p_%d.ts " + fileOutput + "360p.m3u8" +  
  47.                                                         " -i \"{0}\" -vf scale=w=842:h=480:force_original_aspect_ratio=decrease -c:a aac -ar 48000 -c:v h264 -profile:v main -crf 20 -sc_threshold 0 -g 48 -keyint_min 48 -hls_time 4 -hls_playlist_type vod -b:v 1400k -maxrate 1498k -bufsize 2100k -b:a 128k -hls_segment_filename " + fileOutput + "480p_%d.ts " + fileOutput + "480p.m3u8" +  
  48.                                                         " -i \"{0}\" -vf scale=w=1280:h=720:force_original_aspect_ratio=decrease -c:a aac -ar 48000 -c:v h264 -profile:v main -crf 20 -sc_threshold 0 -g 48 -keyint_min 48 -hls_time 4 -hls_playlist_type vod -b:v 2800k -maxrate 2996k -bufsize 4200k -b:a 128k -hls_segment_filename " + fileOutput + "720p_%d.ts " + fileOutput + "720p.m3u8" +  
  49.                                                         " -i \"{0}\" -vf scale=w=1920:h=1080:force_original_aspect_ratio=decrease -c:a aac -ar 48000 -c:v h264 -profile:v main -crf 20 -sc_threshold 0 -g 48 -keyint_min 48 -hls_time 4 -hls_playlist_type vod -b:v 5000k -maxrate 5350k -bufsize 7500k -b:a 192k -hls_segment_filename " + fileOutput + "1080p_%d.ts " + fileOutput + "1080p.m3u8", inputFile, fileOutput);  
  50.   
  51.                 //Process  
  52.                 FFMpeg encoder = new FFMpeg();  
  53.                 encoder.OnProgress += encoder_OnProgress;  
  54.                 Task.Run(() => encoder.ToTS(inputFile, conversionArgs));  
  55.             }  
  56.         }  
  57.     }  
  58.     catch (Exception ex)  
  59.     {  
  60.         ex.ToString();  
  61.     }  
  62. }  

The following code will show the progress in progress bar.

  1. void encoder_OnProgress(int percentage)  
  2. {  
  3.     try  
  4.     {  
  5.         //Update UI  
  6.         Invoke(new Action(() =>  
  7.         {  
  8.             progressBar1.Value = percentage;  
  9.             this.Text = "Transcoding..." + percentage + "%";  
  10.         }));  
  11.   
  12.         if (percentage == 100)  
  13.         {  
  14.             Invoke(new Action(() =>  
  15.             {  
  16.                 this.btnTranscode.Text = "Transcode";  
  17.                 this.Cursor = Cursors.Default;  
  18.             }));  
  19.         }  
  20.     }  
  21.     catch (Exception ex)  
  22.     {  
  23.         ex.ToString();  
  24.     }  
  25. }  

Now, run the application & browse .mp4 file. Then, click on the Transcode button to start the transcoding process.

HTTP Live Streaming (HLS-VOD) 

Output

Go to > C:\ProgramData\transcode\

There’s a single index file with .m3u8 extension with multi-resolution information of sub playlist which is the master playlist.

  1. #EXTM3U  
  2. #EXT-X-VERSION:3  
  3. #EXT-X-STREAM-INF:BANDWIDTH=10000,RESOLUTION=426x240  
  4. 240p.m3u8  
  5. #EXT-X-STREAM-INF:BANDWIDTH=420000,RESOLUTION=640x360  
  6. 360p.m3u8  
  7. #EXT-X-STREAM-INF:BANDWIDTH=680000,RESOLUTION=842x480  
  8. 480p.m3u8  
  9. #EXT-X-STREAM-INF:BANDWIDTH=1256000,RESOLUTION=1280x720  
  10. 720p.m3u8  
  11. #EXT-X-STREAM-INF:BANDWIDTH=2000000,RESOLUTION=1920x1080  
  12. 1080p.m3u8  

Sub-Playlist

As you can see from master playlist, 240p.m3u8 is listed which have another list of segmented file with .ts extension.
  1. #EXTM3U  
  2. #EXT-X-VERSION:3  
  3. #EXT-X-TARGETDURATION:5  
  4. #EXT-X-MEDIA-SEQUENCE:0  
  5. #EXT-X-PLAYLIST-TYPE:VOD  
  6. #EXTINF:4.004000,  
  7. 240p_0.ts  
  8. #EXTINF:4.004000,  
  9. 240p_1.ts  
  10. #EXT-X-ENDLIST  

Learn more,

https://developer.apple.com/documentation/http_live_streaming/example_playlists_for_http_live_streaming/video_on_demand_playlist_construction

Streaming Server (IIS) Configuration & Testing

Create a new website in IIS to serve the HLS live stream. We need to add MIME (Multipurpose Internet Mail Extensions) types to our website to play .m3u8 extension.

HTTP Live Streaming (HLS-VOD) 

Double click on MIME Types to add a new extension.

HTTP Live Streaming (HLS-VOD) 

As you can see In Web.Config configuration is added.

  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <configuration>  
  3.   <system.webServer>  
  4.     <staticContent>  
  5.       <mimeMap fileExtension=".m3u8" mimeType="application/x-mpegURL" />  
  6.     </staticContent>  
  7.   </system.webServer>  
  8. </configuration>  

Transfer the transcoded file in videos folder; then add player in index.html file to play the video from live streaming server.

HTTP Live Streaming (HLS-VOD) 

Next, we are going to add the video.js player for live streaming in Index.htm.

Video.Js

  1. <link href="https://vjs.zencdn.net/7.4.1/video-js.css" rel="stylesheet" />  
  2. <script src="https://vjs.zencdn.net/ie8/1.1.2/videojs-ie8.min.js"></script>  
  3. <script src='https://vjs.zencdn.net/7.4.1/video.js'></script>  
  4. <script src="https://unpkg.com/videojs-flash/dist/videojs-flash.js"></script>  
  5. <script src="https://unpkg.com/videojs-contrib-hls/dist/videojs-contrib-hls.js"></script>  

Here videojs-contrib-hls.js is HLS library for cross browser HLS support.

Player

As you can see our source path is set to index.m3u8 file from below source tag.
  1. <video class="video-js vjs-default-skin vjs-big-play-centered" data-setup='{"controls": true, "autoplay": true, "aspectRatio":"16:9", "fluid": true}'>  
  2.       <source src="http://localhost:8081/videos/Sunset_at_Bhawal_Resort/index.m3u8" type="application/x-mpegURL" />  
  3. </video>  

Learn more: http://videojs.github.io/videojs-contrib-hls

Finally, this is our Index.htm.

  1. <!DOCTYPE html>  
  2. <html>  
  3. <head>  
  4.     <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />  
  5.     <title>IIS HLS-VOD</title>  
  6.     <link href="https://vjs.zencdn.net/7.4.1/video-js.css" rel="stylesheet" />  
  7.     <script src="https://vjs.zencdn.net/ie8/1.1.2/videojs-ie8.min.js"></script>  
  8.     <script src='https://vjs.zencdn.net/7.4.1/video.js'></script>  
  9.     <script src="https://unpkg.com/videojs-flash/dist/videojs-flash.js"></script>  
  10.     <script src="https://unpkg.com/videojs-contrib-hls/dist/videojs-contrib-hls.js"></script>  
  11. </head>  
  12. <body style="margin:0;padding:0;">  
  13.     <div>  
  14.         <video class="video-js vjs-default-skin vjs-big-play-centered" data-setup='{"controls": true, "autoplay": true, "aspectRatio":"16:9", "fluid": true}'>  
  15.             <source src="http://localhost:8081/videos/Sunset_at_Bhawal_Resort/index.m3u8" type="application/x-mpegURL" />  
  16.         </video>  
  17.     </div>  
  18. </body>  
  19. </html>  

Open browser to test the streaming. Go to URL > http://localhost:8081

Client Test

I am going to use chrome browser for testing. As you can see from the below screen the video is playing in the browser.

HTTP Live Streaming (HLS-VOD) 

Change the screen size to see adaptive live streaming like the above image.

Hope this post is going to clarify the thought behind adaptive video playing also creating a video streaming server in IIS.

References

  • https://developer.apple.com/documentation/http_live_streaming
  • https://developer.apple.com/documentation/http_live_streaming/understanding_the_http_live_streaming_architecture
  • https://developer.apple.com/documentation/http_live_streaming/example_playlists_for_http_live_streaming/event_playlist_construction
  • https://developer.apple.com/documentation/http_live_streaming/example_playlists_for_http_live_streaming/video_on_demand_playlist_construction
  • https://bitmovin.com/adaptive-streaming/
  • https://docs.peer5.com/guides/production-ready-hls-vod/
  • http://www.tothenew.com/blog/adaptive-video-streaming-hls/
  • http://www.tothenew.com/blog/apple-http-live-streaming-hls/
  • http://www.tothenew.com/blog/kick-start-with-video-streaming/
  • https://github.com/vladjerca/FFMpegSharp