Color bars

So recently I needed to do a small live stream for 3 to 4 people and due to the nature of the stream, watching Peter Jackson’s “Bad Taste” movie, using Twitch or Youtube was out of the question, so I decided to host my own and give the attendants a link to this site where they would be able to watch the stream live.

Live stream page can be accessed by clicking here. Most likely you will find a placeholder stream 99.9% of the time.

Playing HLS

This section was no trouble, the hls.js project has you covered. In particular, adapting the simple basic usage demo was trivial to do.

Configuring the HTTP server

A few minor things need to be configured on the HTTP server for it to correctly serve HLS media, namely, disallowing cache, COORS and CSP. All examples will focus on nginx since that is the server I use, but the concepts are the same for any other server.

Disable caching

For serving HLS media, the HTTP server must provide an ever-changing .m3u8 file that shall not be cached, and a number of MPEG-TS files that would benefit from being cached if you are behind a caching proxy (Cloud Flare et al) but for simplicity, we will not do the distinction and just tell the clients to never cache HLS files.

On nginx, presuming our HLS files lie on the /live directory, we would need to have the following header:

location /live/ {
  # [...]
  add_header Cache-control "no-store";
}

Sorta disable caching

This is an update to the previous “Disable caching” section.

To take advantage of a caching service, it is desirable to allow caching of the .ts video segments while explicitly forbidding the caching of .m3u8 playlists. Video segments are not long lived so we shall tell the cache to store them for us for a few minutes at most. An nginx configuration that implements this would look like:

#Playlists are never cached (*.m3u8)
location ~ \.m3u8 {
  # [...]
  add_header Cache-control "no-store";
}

#MPEG-TS files may be cached for a short time
location ~ \.ts {
  # [...]
  add_header Cache-control "public, max-age=300";
}

These rules apply to all .ts and .m3u8 files, this shouldn’t be an issue on almost any setup.

Cross-Origin Resource Sharing (CORS)

Most current browsers comply with the Cross-Origin Resource Sharing (CORS) that means we must explicitly allow the usage of our resources by clients, be it clients using our own server o clients from others, hence the cross-origin thing. The HTTP header Access-Control-Allow-Origin controls this behaviour.

If we want to allow anyone to watch the HLS stream from anywhere, we may add the following header to the HLS files being served. Again, presuming the HLS files are on the /live directory, we may add the following config:

location /live/ {
  # [...]
  add_header Access-Control-Allow-Origin "*";
}

Or if we only want the HLS feed to be playable from the same host (origin) and only over https, we can restrict it using this instead:

location /live/ {
  # [...]
  add_header Access-Control-Allow-Origin https://$host;
}

Content Security Policy (CSP)

If you are using a Content Security Policy (CSP) to protect your clients from XSS and other threats, as you should be doing, you would need to accommodate for some needs hls.js has to function properly. The bare minimum required are allowing blob: on the media-src field, and allowing loading hls.js itself, be it with a default policy set to 'self' or by explicitly having a script-src policy.

These policies may apply to the while site if you feel comfortable with them or they may be applied to just the location serving the HLS player. For example, a host wide CSP would look like this:

server {
  # [...]
  add_header Content-Security-Policy "default-src https: 'self';  script-src 'self'; media-src 'self' blob:;";
  # [...]
}

And presuming the player files lie under /player the configuration would move into the relevant location block:

location /player/ {
  # [...]
  add_header Content-Security-Policy "default-src https: 'self';  script-src 'self'; media-src 'self' blob:;";
}

Warning! A miss configured CSP will break your site! Make sure to modify your existing, working, CSP instead of copy & paste the one described here or otherwise not use a CSP at all if you weren’t using one already or feel comfortable not having one.

Generating HLS streams

To feed the player, the HTTP server needs to provide the .m3u8 playlist and the chunked MPEG-TS media content. A quick way to do this with minimum effort is using GStreamer on the server side.

GStreamer is commonly available on popular linux distributions and can take advantage of hardware-accelerated encoding and decoding of formats supported by vaapi along more common encoders/decoders like libav or x264.

These commands will generate a playlist.m3u4 and a number of .ts files which will be used by the HLS player. These must be served by the HTTP server and must not be cached. Make sure the Cache-Control headers are adequate.

Sample HLS stream

A sample HLS stream feed can be generated using GStreamer with the following gst-launch-1.0 command:

gst-launch-1.0 \
  audiotestsrc ! avenc_aac bitrate=64000 ! queue ! muxer. \
  videotestsrc ! vaapih264enc ! queue ! muxer. \
    mpegtsmux name=muxer ! queue ! clocksync ! \
      hlssink playlist-length=3 target-duration=5

This will generate a color bar pattern with a sine wave audio tone, video is encoded with the h264 codec using the vaapi hardware-accelerated encoder while audio is compressed using libav AAC encoder.

Broadcasting a video file

This example takes an MP4 file and broadcasts it live. Video is already h264 + AAC so it will be just remuxed into MPEG Transport Stream.

gst-launch-1.0 \
  filesrc location=bad-taste.mp4 ! qtdemux name=demux \
    demux. ! queue ! "video/x-h264" ! h264parse ! muxer. \
    demux. ! queue ! "audio/mpeg,mpegversion=4" ! aacparse ! muxer. \
      mpegtsmux name=muxer ! queue ! clocksync ! \
        hlssink playlist-length=3 target-duration=5

Live broadcasting using OBS

This will set up an SRT server that OBS can broadcast to, then remux the stream into HLS for the player to consume. On the GStreamer side the command is the following:

gst-launch-1.0 \
  srtsrc uri="srt://:7001?mode=listener" ! tsdemux name=demux \
    demux. ! queue ! "video/x-h264" ! h264parse ! muxer. \
    demux. ! queue ! "audio/mpeg,mpegversion=4" ! aacparse ! muxer. \
      mpegtsmux name=muxer ! queue ! \
        hlssink playlist-length=3 target-duration=5

On the OBS side, the configurations would look like:

OBS config

srt://[ADDRESS]:[PORT]?mode=caller&transtype=live&latency=1000000

Keep in mind the server is doing no authentication of any kind so keep the SRT server bound to a local IP or otherwise outside the open internet.

Broadcasting using VLC

This setup will send an MPEG-TS stream over RTP with VLC. On the server side we will need the following command:

gst-launch-1.0 \
  udpsrc port=5004 caps="application/x-rtp" ! rtpmp2tdepay ! \
  tsdemux name=demux \
    demux. ! queue ! "video/x-h264" ! h264parse ! muxer.
    demux. ! queue ! "audio/mpeg,mpegversion=4" ! aacparse ! muxer. \
      mpegtsmux name=muxer ! queue ! \
        hlssink playlist-length=3 target-duration=5

On the VLC side, the “Stream…” option on the File menu, the following steps shall be followed:

  • Add a new “RTP / MPEG Transport Stream” destination
  • Enter the correct Address/Port values
  • Add a stream name (any)
  • If file is not already MPEG-TS containing h264 + AAC enable transcoding with the following options:
    • Encapsulation: MPEG-TS
    • Video codec: H-264
    • Audio Codec: MPEG 4 Audio (AAC)
  • Click “Stream” on the final screen

RTP does not allow any authentication so keep the server socket outside of the public internet.