In the dynamic landscape of online video streaming, one protocol has emerged as a powerhouse: HTTP Live Streaming (HLS). In this blog post, we'll delve into the intricacies of HLS, exploring its features, benefits, and ways in which it stands out in the realm of streaming protocols. We will also include a quick example on how you can start leveraging HLS using the Transloadit API.

3D holographic shapes around the text 'What is HLS'

What is streaming?

Before we dive into the specifics of HLS, let's first make sure we understand streaming as a broader concept. Streaming refers to the continuous delivery, in real time, of audio or video content over the internet. Unlike traditional downloads, where the entire file needs to be downloaded before the user can start playback, streaming allows users to begin consuming content while it is being delivered.

What is HLS?

The acronym HLS stands for "HTTP Live Streaming".

HLS is a proprietary streaming protocol launched by Apple in 2009. It was created in response to an issue with the original iPhone and the iPhone 2, where video content would buffer as the device kept switching between WiFi and the mobile network. QuickTime was introduced at the time to try and solve this problem. However, due to QuickTime using the non-standard Real-Time Streaming Protocol (RTSP), firewalls often blocked streamed media. Therefore, HLS was created so that media could be streamed using the standard HTTP protocol, and it is now one of the most used streaming protocols.

How does HLS work?

Before a video file can be streamed using HLS, three steps have to first be performed to create an HLS file:

  1. The file is encoded in either the H.264 or H.265 encoding format at several different bitrates. Check out our blog on video compression for more information on what this means.
  2. The encoded files are then divided into segments that are only a few seconds long.
  3. An index is created first for each quality level, which stores the location and timestamp of each segment at that quality level. Another global index then points to the local indexes for each quality level. The global index is the one that is referenced for playback.

These segments are then delivered to the viewer's device via the HTTP protocol. Since the video is encoded at several different bitrates, the quality of the video can be adjusted during playback to match network capabilities. This allows playback to continue even on poor connections, so that you don't have to wait for your favorite movie to buffer before you can watch it.

The diagram below illustrates how the image quality may vary as the network connection fluctuates.

Diagram showing how network connectivity can affect video quality using HLS

Often, the video and audio streams are separated and delivered to the users independently, as audio is usually prioritized for streaming. If the network becomes unstable, the video quality is the first to drop, followed by the audio quality. In extreme cases, this may lead to only the audio stream being played back, although this is rare.

What's the difference between HLS and MPEG-DASH?

Like HLS, MPEG-DASH is a method of streaming media over HTTP that encodes and segments a video before it is streamed to a user. In the same fashion as HLS, MPEG-DASH will also create an index file to keep track of the order the segments should be played back in. However, HLS is a proprietary format created by Apple, whereas MPEG-DASH is an open-source format created by the team at MPEG.

A full comparison of features is shown in the table below.

HLS MPEG-DASH
Encoding format H.264/H.265 Any
Device support Any All apart from Apple devices
Default segment length (s) 6 2
Adaptive bitrate streaming? ✔️ ✔️
Supports ad insertion? ✔️ ✔️
Developer Proprietary Apple product Open source from MPEG
Transport protocol TCP TCP

Does Transloadit support HLS?

Thanks to the /video/adaptive Robot, Transloadit supports both HLS and MPEG-DASH. Let's take a look at a Template to see how you can start using this Robot to create high-quality streamable media.

The below Template will encode a video at three different quality levels, before passing it to the /video/adaptive Robot. It is then segmented and indexed into a .m3u8 playlist, which can be streamed to your users using a CDN.

{
  "steps": {
    ":original": {
      "robot": "/upload/handle"
    },
    "low": {
      "robot": "/video/encode",
      "use": ":original",
      "ffmpeg_stack": "v6.0.0",
      "preset": "hls-270p",
      "result": true,
      "turbo": true
    },
    "mid": {
      "robot": "/video/encode",
      "use": ":original",
      "ffmpeg_stack": "v6.0.0",
      "preset": "hls-360p",
      "result": true,
      "turbo": true
    },
    "high": {
      "robot": "/video/encode",
      "use": ":original",
      "ffmpeg_stack": "v6.0.0",
      "preset": "hls-540p",
      "result": true,
      "turbo": true
    },
    "adaptive": {
      "robot": "/video/adaptive",
      "use": {
        "steps": ["low", "mid", "high"],
        "bundle_steps": true
      },
      "technique": "hls",
      "playlist_name": "my_playlist.m3u8"
    },
    "exported": {
      "robot": "/s3/store",
      "use": ["adaptive", ":original"],
      "path": "hlsdemo/${file.meta.relative_path}/${file.name}",
      "credentials": "YOUR_AWS_CREDENTIALS",
      "url_prefix": "https://demos.transloadit.com/"
    }
  }
}
# Prerequisites: brew install curl jq || sudo apt install curl jq
# To avoid tampering, use Signature Authentication
echo '{
  "template_id": undefined,
  "auth": {
    "key": "YOUR_TRANSLOADIT_KEY"
  },
  "steps": {
    ":original": {
      "robot": "/upload/handle"
    },
    "low": {
      "robot": "/video/encode",
      "use": ":original",
      "ffmpeg_stack": "v6.0.0",
      "preset": "hls-270p",
      "result": true,
      "turbo": true
    },
    "mid": {
      "robot": "/video/encode",
      "use": ":original",
      "ffmpeg_stack": "v6.0.0",
      "preset": "hls-360p",
      "result": true,
      "turbo": true
    },
    "high": {
      "robot": "/video/encode",
      "use": ":original",
      "ffmpeg_stack": "v6.0.0",
      "preset": "hls-540p",
      "result": true,
      "turbo": true
    },
    "adaptive": {
      "robot": "/video/adaptive",
      "use": {
        "steps": ["low", "mid", "high"],
        "bundle_steps": true
      },
      "technique": "hls",
      "playlist_name": "my_playlist.m3u8"
    },
    "exported": {
      "robot": "/s3/store",
      "use": ["adaptive", ":original"],
      "path": "hlsdemo/${file.meta.relative_path}/${file.name}",
      "credentials": "YOUR_AWS_CREDENTIALS",
      "url_prefix": "https://demos.transloadit.com/"
    }
  }
}' |curl 
    --request POST 
    --form 'params=<-' 
    --form myfile1=@./surf.mp4 
  https://api2.transloadit.com/assemblies 
|jq
// Install via Swift Package Manager:
// dependencies: [
//   .package(url: "https://github.com/transloadit/TransloaditKit", .upToNextMajor(from: "3.0.0"))
// ]

// Or via CocoaPods: // pod 'Transloadit', '~> 3.0.0'

// Auth let credentials = Credentials(key: "YOUR_TRANSLOADIT_KEY")

// Init let transloadit = Transloadit(credentials: credentials, session: "URLSession.shared")

// Add files to upload let filesToUpload: [URL] = ...

// Execute let assembly = transloadit.assembly(steps: [_originalStep, lowStep, midStep, highStep, adaptiveStep, exportedStep], andUpload: filesToUpload) { result in   switch result {   case .success(let assembly):     print("Retrieved (assembly)")   case .failure(let error):     print("Assembly error (error)")   } }.pollAssemblyStatus { result in   switch result {   case .success(let assemblyStatus):     print("Received assemblystatus (assemblyStatus)")   case .failure(let error):     print("Caught polling error (error)")   }

<body>
  <form action="/uploads" enctype="multipart/form-data" method="POST">
    <input type="file" name="my_file" multiple="multiple" />
  </form>

<script src="//ajax.googleapis.com/ajax/libs/jquery/3.2.0/jquery.min.js"></script>   <script src="//assets.transloadit.com/js/jquery.transloadit2-v3-latest.js"></script>   <script type="text/javascript">     $(function () {       $('form').transloadit({         wait: true,         triggerUploadOnFileSelection: true,         // To avoid tampering, use Signature Authentication:         // https://transloadit.com/docs/topics/signature-authentication/         auth: {           key: 'YOUR_TRANSLOADIT_KEY',         },         // It's often better store encoding instructions in your account         // and use a template_id instead of adding these steps inline         steps: {           ':original': {             robot: '/upload/handle',           },           low: {             robot: '/video/encode',             use: ':original',             ffmpeg_stack: 'v6.0.0',             preset: 'hls-270p',             result: true,             turbo: true,           },           mid: {             robot: '/video/encode',             use: ':original',             ffmpeg_stack: 'v6.0.0',             preset: 'hls-360p',             result: true,             turbo: true,           },           high: {             robot: '/video/encode',             use: ':original',             ffmpeg_stack: 'v6.0.0',             preset: 'hls-540p',             result: true,             turbo: true,           },           adaptive: {             robot: '/video/adaptive',             use: {               steps: ['low', 'mid', 'high'],               bundle_steps: true,             },             technique: 'hls',             playlist_name: 'my_playlist.m3u8',           },           exported: {             robot: '/s3/store',             use: ['adaptive', ':original'],             path: 'hlsdemo/${file.meta.relative_path}/${file.name}',             credentials: 'YOUR_AWS_CREDENTIALS',             url_prefix: 'https://demos.transloadit.com/',           },         },       })     })   </script> </body>

<!-- This pulls Uppy from our CDN -->
<!-- For smaller self-hosted bundles, install Uppy and plugins manually: -->
<!-- npm i --save @uppy/core @uppy/dashboard @uppy/remote-sources @uppy/transloadit ... -->
<link
  href="https://releases.transloadit.com/uppy/v3.22.2/uppy.min.css"
  rel="stylesheet"
/>
<button id="browse">Select Files</button>
<script type="module">
  import {
    Uppy,
    Dashboard,
    ImageEditor,
    RemoteSources,
    Transloadit,
  } from 'https://releases.transloadit.com/uppy/v3.22.2/uppy.min.mjs'
  const uppy = new Uppy()
    .use(Transloadit, {
      waitForEncoding: true,
      alwaysRunAssembly: true,
      assemblyOptions: {
        params: {
          // To avoid tampering, use Signature Authentication:
          // https://transloadit.com/docs/topics/signature-authentication/
          auth: {
            key: 'YOUR_TRANSLOADIT_KEY',
          },
          // It's often better store encoding instructions in your account
          // and use a template_id instead of adding these steps inline
          steps: {
            ':original': {
              robot: '/upload/handle',
            },
            low: {
              robot: '/video/encode',
              use: ':original',
              ffmpeg_stack: 'v6.0.0',
              preset: 'hls-270p',
              result: true,
              turbo: true,
            },
            mid: {
              robot: '/video/encode',
              use: ':original',
              ffmpeg_stack: 'v6.0.0',
              preset: 'hls-360p',
              result: true,
              turbo: true,
            },
            high: {
              robot: '/video/encode',
              use: ':original',
              ffmpeg_stack: 'v6.0.0',
              preset: 'hls-540p',
              result: true,
              turbo: true,
            },
            adaptive: {
              robot: '/video/adaptive',
              use: {
                steps: ['low', 'mid', 'high'],
                bundle_steps: true,
              },
              technique: 'hls',
              playlist_name: 'my_playlist.m3u8',
            },
            exported: {
              robot: '/s3/store',
              use: ['adaptive', ':original'],
              path: 'hlsdemo/${file.meta.relative_path}/${file.name}',
              credentials: 'YOUR_AWS_CREDENTIALS',
              url_prefix: 'https://demos.transloadit.com/',
            },
          },
        },
      },
    })
    .use(Dashboard, { trigger: '#browse' })
    .use(ImageEditor, { target: Dashboard })
    .use(RemoteSources, {
      companionUrl: 'https://api2.transloadit.com/companion',
    })
    .on('complete', ({ transloadit }) => {
      // Due to waitForEncoding:true this is fired after encoding is done.
      // Alternatively, set waitForEncoding to false and provide a notify_url
      console.log(transloadit) // Array of Assembly Statuses
      transloadit.forEach((assembly) => {
        console.log(assembly.results) // Array of all encoding results
      })
    })
    .on('error', (error) => {
      console.error(error)
    })
</script>
// yarn add transloadit || npm i transloadit

// Import const Transloadit = require('transloadit')

// Init const transloadit = new Transloadit({   authKey: 'YOUR_TRANSLOADIT_KEY',   authSecret: 'MY_TRANSLOADIT_SECRET', })

// Set Encoding Instructions const options = {   files: {     myfile_1: './surf.mp4',   },   params: {     steps: {       ':original': {         robot: '/upload/handle',       },       low: {         robot: '/video/encode',         use: ':original',         ffmpeg_stack: 'v6.0.0',         preset: 'hls-270p',         result: true,         turbo: true,       },       mid: {         robot: '/video/encode',         use: ':original',         ffmpeg_stack: 'v6.0.0',         preset: 'hls-360p',         result: true,         turbo: true,       },       high: {         robot: '/video/encode',         use: ':original',         ffmpeg_stack: 'v6.0.0',         preset: 'hls-540p',         result: true,         turbo: true,       },       adaptive: {         robot: '/video/adaptive',         use: {           steps: ['low', 'mid', 'high'],           bundle_steps: true,         },         technique: 'hls',         playlist_name: 'my_playlist.m3u8',       },       exported: {         robot: '/s3/store',         use: ['adaptive', ':original'],         path: 'hlsdemo/${file.meta.relative_path}/${file.name}',         credentials: 'YOUR_AWS_CREDENTIALS',         url_prefix: 'https://demos.transloadit.com/',       },     },   }, }

// Execute const result = await transloadit.createAssembly(options)

// Show results console.log({ result })

# [sudo] npm install transloadify -g

# Auth export TRANSLOADIT_KEY="YOUR_TRANSLOADIT_KEY"

# Save Encoding Instructions echo '{   "steps": {     ":original": {       "robot": "/upload/handle"     },     "low": {       "robot": "/video/encode",       "use": ":original",       "ffmpeg_stack": "v6.0.0",       "preset": "hls-270p",       "result": true,       "turbo": true     },     "mid": {       "robot": "/video/encode",       "use": ":original",       "ffmpeg_stack": "v6.0.0",       "preset": "hls-360p",       "result": true,       "turbo": true     },     "high": {       "robot": "/video/encode",       "use": ":original",       "ffmpeg_stack": "v6.0.0",       "preset": "hls-540p",       "result": true,       "turbo": true     },     "adaptive": {       "robot": "/video/adaptive",       "use": {         "steps": ["low", "mid", "high"],         "bundle_steps": true       },       "technique": "hls",       "playlist_name": "my_playlist.m3u8"     },     "exported": {       "robot": "/s3/store",       "use": ["adaptive", ":original"],       "path": "hlsdemo/${file.meta.relative_path}/${file.name}",       "credentials": "YOUR_AWS_CREDENTIALS",       "url_prefix": "https://demos.transloadit.com/"     }   } }' > ./steps.json

# Execute transloadify 
  --input "surf.mp4" 
  --steps "./steps.json" 
  --output "./output.example"

// composer require transloadit/php-sdk
use transloadit\Transloadit;

$transloadit = new Transloadit([   "key" => "YOUR_TRANSLOADIT_KEY",   "secret" => "MY_TRANSLOADIT_SECRET", ]);

// Start the Assembly $response = $transloadit->createAssembly([   "files" => ["surf.mp4"],   "params" => [     "steps" => [       ":original" => [         "robot" => "/upload/handle",       ],       "low" => [         "robot" => "/video/encode",         "use" => ":original",         "ffmpeg_stack" => "v6.0.0",         "preset" => "hls-270p",         "result" => true,         "turbo" => true,       ],       "mid" => [         "robot" => "/video/encode",         "use" => ":original",         "ffmpeg_stack" => "v6.0.0",         "preset" => "hls-360p",         "result" => true,         "turbo" => true,       ],       "high" => [         "robot" => "/video/encode",         "use" => ":original",         "ffmpeg_stack" => "v6.0.0",         "preset" => "hls-540p",         "result" => true,         "turbo" => true,       ],       "adaptive" => [         "robot" => "/video/adaptive",         "use" => [           "steps" => ["low", "mid", "high"],           "bundle_steps" => true,         ],         "technique" => "hls",         "playlist_name" => "my_playlist.m3u8",       ],       "exported" => [         "robot" => "/s3/store",         "use" => ["adaptive", ":original"],         "path" => "hlsdemo/${file.meta.relative_path}/${file.name}",         "credentials" => "YOUR_AWS_CREDENTIALS",         "url_prefix" => "https://demos.transloadit.com/",       ],     ],   ], ]);

# gem install transloadit

# $ irb -rubygems # >> require 'transloadit' # => true

transloadit = Transloadit.new([   :key => "YOUR_TRANSLOADIT_KEY", ])

# Set Encoding Instructions _original = transloadit.step(":original", "/upload/handle", {})

low = transloadit.step("low", "/video/encode", [   :use => ":original",   :ffmpeg_stack => "v6.0.0",   :preset => "hls-270p",   :result => true,   :turbo => true ])

mid = transloadit.step("mid", "/video/encode", [   :use => ":original",   :ffmpeg_stack => "v6.0.0",   :preset => "hls-360p",   :result => true,   :turbo => true ])

high = transloadit.step("high", "/video/encode", [   :use => ":original",   :ffmpeg_stack => "v6.0.0",   :preset => "hls-540p",   :result => true,   :turbo => true ])

adaptive = transloadit.step("adaptive", "/video/adaptive", [   :use => [     :steps => ["low", "mid", "high"],     :bundle_steps => true   ],   :technique => "hls",   :playlist_name => "my_playlist.m3u8" ])

exported = transloadit.step("exported", "/s3/store", [   :use => ["adaptive", ":original"],   :path => "hlsdemo/${file.meta.relative_path}/${file.name}",   :credentials => "YOUR_AWS_CREDENTIALS",   :url_prefix => "https://demos.transloadit.com/" ])

transloadit.assembly([   :steps => [_original, low, mid, high, adaptive, exported] ])

# Add files to upload files = [] files.push("surf.mp4")

# Start the Assembly response = assembly.create! *files

until response.finished?   sleep 1; response.reload! end

if !response.error?   # handle success end

# pip install pytransloadit
from transloadit import client

tl = client.Transloadit('YOUR_TRANSLOADIT_KEY', 'MY_TRANSLOADIT_SECRET') assembly = tl.new_assembly()

# Set Encoding Instructions assembly.add_step(":original", "/upload/handle", {})

assembly.add_step("low", "/video/encode", {   'use': ':original',   'ffmpeg_stack': 'v6.0.0',   'preset': 'hls-270p',   'result': True,   'turbo': True })

assembly.add_step("mid", "/video/encode", {   'use': ':original',   'ffmpeg_stack': 'v6.0.0',   'preset': 'hls-360p',   'result': True,   'turbo': True })

assembly.add_step("high", "/video/encode", {   'use': ':original',   'ffmpeg_stack': 'v6.0.0',   'preset': 'hls-540p',   'result': True,   'turbo': True })

assembly.add_step("adaptive", "/video/adaptive", {   'use': {     'steps': ['low', 'mid', 'high'],     'bundle_steps': True   },   'technique': 'hls',   'playlist_name': 'my_playlist.m3u8' })

assembly.add_step("exported", "/s3/store", {   'use': ['adaptive', ':original'],   'path': 'hlsdemo/${file.meta.relative_path}/${file.name}',   'credentials': 'YOUR_AWS_CREDENTIALS',   'url_prefix': 'https://demos.transloadit.com/' })

# Add files to upload assembly.add_file(open('surf.mp4', 'rb'))

# Start the Assembly assembly_response = assembly.create(retries=5, wait=True)

print(assembly_response.data.get('assembly_ssl_url')) # or: print(assembly_response.data['assembly_ssl_url'])

// go get gopkg.in/transloadit/go-sdk.v1
package main

import (   "context"   "fmt"   "github.com/transloadit/go-sdk" )

func main() {   // Create client   options := transloadit.DefaultConfig   options.AuthKey = "YOUR_TRANSLOADIT_KEY"   options.AuthSecret = "MY_TRANSLOADIT_SECRET"   client := transloadit.NewClient(options)      // Initialize new Assembly   assembly := transloadit.NewAssembly()      // Set Encoding Instructions   assembly.AddStep(":original", map[string]interface{}{     "robot": "/upload/handle",   })      assembly.AddStep("low", map[string]interface{}{     "robot": "/video/encode",     "use": ":original",     "ffmpeg_stack": "v6.0.0",     "preset": "hls-270p",     "result": true,     "turbo": true,   })      assembly.AddStep("mid", map[string]interface{}{     "robot": "/video/encode",     "use": ":original",     "ffmpeg_stack": "v6.0.0",     "preset": "hls-360p",     "result": true,     "turbo": true,   })      assembly.AddStep("high", map[string]interface{}{     "robot": "/video/encode",     "use": ":original",     "ffmpeg_stack": "v6.0.0",     "preset": "hls-540p",     "result": true,     "turbo": true,   })      assembly.AddStep("adaptive", map[string]interface{}{     "robot": "/video/adaptive",     "use": map[string]interface{}{       "steps": ["low", "mid", "high"],       "bundle_steps": true,     },     "technique": "hls",     "playlist_name": "my_playlist.m3u8",   })      assembly.AddStep("exported", map[string]interface{}{     "robot": "/s3/store",     "use": ["adaptive", ":original"],     "path": "hlsdemo/${file.meta.relative_path}/${file.name}",     "credentials": "YOUR_AWS_CREDENTIALS",     "url_prefix": "https://demos.transloadit.com/",   })      // Add files to upload   assembly.AddFile("surf.mp4"))      // Start the Assembly   info, err := client.StartAssembly(context.Background(), assembly)   if err != nil {     panic(err)   }      // All files have now been uploaded and the Assembly has started but no   // results are available yet since the conversion has not finished.   // WaitForAssembly provides functionality for polling until the Assembly   // has ended.   info, err = client.WaitForAssembly(context.Background(), info)   if err != nil {     panic(err)   }      fmt.Printf("You can check some results at: ")   fmt.Printf("  - %s\n", info.Results[":original"][0].SSLURL)   fmt.Printf("  - %s\n", info.Results["low"][0].SSLURL)   fmt.Printf("  - %s\n", info.Results["mid"][0].SSLURL)   fmt.Printf("  - %s\n", info.Results["high"][0].SSLURL)   fmt.Printf("  - %s\n", info.Results["adaptive"][0].SSLURL)   fmt.Printf("  - %s\n", info.Results["exported"][0].SSLURL) }

// implementation 'com.transloadit.sdk:transloadit:1.0.0

import com.transloadit.sdk.Assembly; import com.transloadit.sdk.Transloadit; import com.transloadit.sdk.exceptions.LocalOperationException; import com.transloadit.sdk.exceptions.RequestException; import com.transloadit.sdk.response.AssemblyResponse; import java.io.File; import java.util.HashMap; import java.util.Map;

public class Main {   public static void main(String[] args) {     // Initialize the Transloadit client     Transloadit transloadit = new Transloadit("YOUR_TRANSLOADIT_KEY", "MY_TRANSLOADIT_SECRET");          Assembly assembly = transloadit.newAssembly();          // Set Encoding Instructions     Map<String, Object> _originalStepOptions = new HashMap();     assembly.addStep(":original", "/upload/handle", _originalStepOptions);          Map<String, Object> lowStepOptions = new HashMap();     lowStepOptions.put("use", ":original");     lowStepOptions.put("ffmpeg_stack", "v6.0.0");     lowStepOptions.put("preset", "hls-270p");     lowStepOptions.put("result", true);     lowStepOptions.put("turbo", true);     assembly.addStep("low", "/video/encode", lowStepOptions);          Map<String, Object> midStepOptions = new HashMap();     midStepOptions.put("use", ":original");     midStepOptions.put("ffmpeg_stack", "v6.0.0");     midStepOptions.put("preset", "hls-360p");     midStepOptions.put("result", true);     midStepOptions.put("turbo", true);     assembly.addStep("mid", "/video/encode", midStepOptions);          Map<String, Object> highStepOptions = new HashMap();     highStepOptions.put("use", ":original");     highStepOptions.put("ffmpeg_stack", "v6.0.0");     highStepOptions.put("preset", "hls-540p");     highStepOptions.put("result", true);     highStepOptions.put("turbo", true);     assembly.addStep("high", "/video/encode", highStepOptions);          Map<String, Object> adaptiveStepOptions = new HashMap();     adaptiveStepOptions.put("use", {         "steps": new String[] { "low", "mid", "high" },         "bundle_steps": true,       });     adaptiveStepOptions.put("technique", "hls");     adaptiveStepOptions.put("playlist_name", "my_playlist.m3u8");     assembly.addStep("adaptive", "/video/adaptive", adaptiveStepOptions);          Map<String, Object> exportedStepOptions = new HashMap();     exportedStepOptions.put("use", new String[] { "adaptive", ":original" });     exportedStepOptions.put("path", "hlsdemo/${file.meta.relative_path}/${file.name}");     exportedStepOptions.put("credentials", "YOUR_AWS_CREDENTIALS");     exportedStepOptions.put("url_prefix", "https://demos.transloadit.com/");     assembly.addStep("exported", "/s3/store", exportedStepOptions);          // Add files to upload     assembly.addFile(new File("surf.mp4"));          // Start the Assembly     try {       AssemblyResponse response = assembly.save();            // Wait for Assembly to finish executing       while (!response.isFinished()) {         response = transloadit.getAssemblyByUrl(response.getSslUrl());       }            System.out.println(response.getId());       System.out.println(response.getUrl());       System.out.println(response.json());     } catch (RequestException | LocalOperationException e) {       // Handle exception here     }   } }

After downloading our playlist, we can now play back the resulting .m3u8 file and the outcome is seamless.

Taking a look at the generated index file my_playlist.m3u8, we can see that it stores the location of each video quality's index file. In each of these, the segment order is preserved, as well as the timestamp that signifies when to switch between segments.

my_playlist.m3u8
#EXTM3U
#EXT-X-STREAM-INF:BANDWIDTH=687899,FRAME-RATE=30,CODECS="avc1.4d001e,mp4a.40.5",RESOLUTION=480x270
480x270/480x270_534648_30.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=1216861,FRAME-RATE=30,CODECS="avc1.4d001f,mp4a.40.5",RESOLUTION=640x360
640x360/640x360_981616_30.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=1916095,FRAME-RATE=30,CODECS="avc1.4d001f,mp4a.40.5",RESOLUTION=960x540
960x540/960x540_1567376_30.m3u8
#EXT-X-ENDLIST
960x540_1567376_30.m3u8
#EXTM3U
#EXT-X-PLAYLIST-TYPE:VOD
#EXT-X-TARGETDURATION:11
#EXT-X-VERSION:3
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:10.110,
seg__0.ts
#EXTINF:10.060,
seg__1.ts
#EXTINF:1.950,
seg__2.ts
#EXT-X-ENDLIST

Wrapping up

That's all for our deep dive into the HLS streaming protocol! Hopefully this can serve as a spark of inspiration for you to continue your journey into the world of media streaming. If you're looking for the next step in your quest, create a Transloadit account to start transcoding your files using the HLS format.