HLS & DASH Video Streaming

Share on:

Overview

We all have used media platforms likes YouTube, Netflix, PrimeVideo, DailyMotion etc. Multi-media industry has evolved over the period of time and has been one of the main area of technical advancement. Multi-media solutions, platforms and technology has moved at rapid pace in last decade or so. Considering the adoption rate for newer technologies, media domain stands first and should be followed by ecommerce etc. With everyone getting access to high speed internet, demand for consuming media content particularly videos has rapidly increased.

There have been many solutions to tackle this problem. Flash, HLS and DASH are some of the techniques which are famous. Though flash is not used much these days, but it served its purpose when needed :stuck_out_tongue_winking_eye: In this article our focus would be mainly on HLS & DASH technique. I won't go much in depth of technical implementation as we can get best knowledge from official documentation and wiki links. Instead, I would rather focus on how to implement a simple video streaming server using these 2 technologies and consume it in chrome browser.

Multi-media is very huge and advance topic. It is not possible to cover even surfaces of it. We will stick to the basic requirement of server implementation.

 

HLS & DASH

Index file i.e .mpd or .m3u8 file here contains the definition of the other files and format which are sent over the network. This index file is what is sent at the very beginning of the request to the client. Then the specific client which have the capability to understand, decode and operate takes the control over. Don't interchange HLS and DASH, they are 2 different approaches and have their respective significance.

In short, non-technical basic steps are followed:

  • Generate index files
  • Send over network
  • Capable client reads this file
  • Try to read chuks which are specified in index file
  • Plays videos and provide other options available.

More information here:

 

MIME Type and Extension

Note that these files have their special mime type.

For HLS:

  • .m3u8 index file extension, it is application/x-mpegURL
  • .ts media segment, it is video/MP2T

For DASH:

  • .mpd index file, it is application/dash+xml
  • .m4s media segment, it is video/mp4

 

Generating Video sequences and its index file

For our use-case we are considering streaming a static content i.e we will create segments from main file and distribute it from our server. To create hls index file and segments run following command

For HLS:

1ffmpeg -i main.mp4 
2    -profile:v baseline -level 3.0 -s 640x360 
3    -start_number 0 -hls_time 10 -hls_list_size 0 
4    -f hls main_hls_output/main.m3u8

For DASH:

1ffmpeg -i main.mp4 
2    -profile:v baseline -level 3.0 -s 640x360 
3    -start_number 0 -hls_time 10 -hls_list_size 0 
4    -f dash main_mpd_output/main.mpd

This command assumes that you have ffmpeg in classpath

 

Serving File from server

Once we have created sequences and its index file. Let's share them over network. For simple serving purpose we are using micronaut java web framework.

Let's create:

  • Controller for handling request for (index, segment and small config)
  • A file manager to deal with index and segment files

Content for HlsController.java is as follows:

 1@Controller("/hls")
 2public class HlsController {
 3
 4    @Value("${config.video.hls.folder}")
 5    private String folder;
 6
 7    @Value("${config.video.hls.index}")
 8    private String index;
 9
10    private FileManager fileManager;
11
12    @Inject
13    public HlsController(FileManager fileManager) {
14        this.fileManager = fileManager;
15    }
16
17    @PostConstruct
18    public void initFields() {
19        fileManager.init(folder, index);
20    }
21
22    @Get("index.m3u8")
23    @Produces("application/x-mpegURL")
24    public File index() {
25        return fileManager.getIndex();
26    }
27
28    @Get(uri = "/{filename}")
29    @Produces("video/MP2T")
30    public File getFile(String filename) {
31        return fileManager.getFile(filename);
32    }
33
34    @Get("/config")
35    @Produces(MediaType.APPLICATION_JSON)
36    public Map<String, String> config() {
37        final HashMap<String, String> config = new HashMap<>();
38        config.put("folder", fileManager.getFolder().getAbsolutePath());
39        config.put("index", fileManager.getIndex().getAbsolutePath());
40        return config;
41    }
42
43    @Get
44    @Produces(MediaType.APPLICATION_JSON)
45    public Map<String, Object> list() {
46        final HashMap<String, Object> response = new HashMap<>();
47        response.put("type", "HLS");
48        response.put("files", fileManager.list());
49        return response;
50    }
51}

Content for DashController.java is as follows:

 1@Controller("/dash")
 2public class DashController {
 3
 4    @Value("${config.video.dash.folder}")
 5    private String folder;
 6
 7    @Value("${config.video.dash.index}")
 8    private String index;
 9    private FileManager fileManager;
10
11    @Inject
12    public DashController(FileManager fileManager) {
13        this.fileManager = fileManager;
14    }
15
16    @PostConstruct
17    public void postInit() {
18        this.fileManager.init(folder, index);
19    }
20
21    @Get("index.mpd")
22    @Produces("application/dash+xml")
23    public File index() {
24        return fileManager.getIndex();
25    }
26
27    @Get(uri = "/{filename}")
28    @Produces("video/mp4")
29    public File getFile(String filename) {
30        return fileManager.getFile(filename);
31    }
32
33    @Get("/config")
34    @Produces(MediaType.APPLICATION_JSON)
35    public Map<String, String> config() {
36        final HashMap<String, String> config = new HashMap<>();
37        config.put("folder", fileManager.getFolder().getAbsolutePath());
38        config.put("index", fileManager.getIndex().getAbsolutePath());
39        return config;
40    }
41
42    @Get
43    @Produces(MediaType.APPLICATION_JSON)
44    public Map<String, Object> list() {
45        final HashMap<String, Object> response = new HashMap<>();
46        response.put("type", "DASH");
47        response.put("files", fileManager.list());
48        return response;
49    }
50}

Our FileManager.java per instance for each Controller is as follows:

 1@Prototype
 2public class FileManager {
 3
 4    @Getter
 5    private File folder;
 6
 7    @Getter
 8    private File index;
 9
10    public void init(String folder, String index) {
11        this.folder = new File(folder);
12        this.index = new File(this.folder, index);
13    }
14
15    public File getFile(String filename) {
16        return new File(this.folder, filename);
17    }
18
19    public List<String> list() {
20        return folder != null ?
21                Stream.of(folder.list()).collect(Collectors.toList()) :
22                Collections.emptyList();
23    }
24
25}

So far, we have created controller, a file manager and serving these from following urls:

  • http://localhost:8080/dash/
  • http://localhost:8080/hls/

 

Config

For controller to access config folder and file values, we need to provide it in application.yml file, here are the file contents:

 1---
 2micronaut:
 3  application:
 4    name: Video Server
 5---
 6
 7config:
 8  video:
 9    dash:
10      folder: D:\silentsudo\video_streaming\main_mpd_output\
11      index: main.mpd
12    hls:
13      folder: D:\silentsudo\video_streaming\main_hls_output\
14      index: main.m3u8

main_mpd_output or main_hls_output folder is the place where our generated index file and segment are present(generated from ffmpeg command above). If this folder is empty then we will no be able to see video stream happening.

 

Test with chrome browser and extension

Once we have our server up, running and serving all files in proper content type. Let's test it out.

For simple testing, install Chrome Plugin https://chrome.google.com/webstore/detail/native-mpeg-dash-%2B-hls-pl/cjfbmleiaobegagekpmlhmaadepdeedn and paste this link http://localhost:8080/hls/main.m3u8 in address bar and hit enter.

You should be able to see video stream playing. Alternative to see what is going on in background press ctrl+shit+i in chrome browser to open developer mode, switch to network tab and reload page. You should be able to see files http://localhost:8080/dash/mainX.ts being downloaded in the order specified in .m3u8 file.

Enjoy :movie_camera:

comments powered by Disqus