Search code examples
bashffmpeghttp-live-streaming

Encrypted HLS works as live stream, doesn't work as VOD


I've found some crude bash script for encoding and encrypting video file, into a HLS stream, and I've edited it slightly (I have no idea about bash):

#!/bin/bash

set -e     # Exit on errors

tsFile="$1"

if ! [ -f "$tsFile" -a -r "$tsFile" ]; then
    echo "First argument is required" >&2
    exit 2
fi

if [ -z "$3" ]; then
    output="output"
else
    output="$3"
fi


keyFile="$output.key"
keyInfoFile="$output.keyinfo"
playList="$output.m3u8"

if [ -z "$4" ]; then
    separator='-'
else 
    separator="$4"
fi

splitFilePrefix="$output$separator"


if [ -d "$2" ]; then
    outDir="$2"
else
    mkdir "$2" || exit 1
    outDir="$2"
fi

tempDir="$outDir/.$$_tmp"
keyFile="$outDir/$keyFile"

mkdir $tempDir

echo "$outdir/$keyFile\n$outdir/$keyFile" > "$outdir/$keyInfoFile"


ffmpeg -i "$tsFile" -hls_time 5 -hls_list_size 0 -hls_segment_filename "$tempDir/$splitFilePrefix%03d.ts" -strict -2 "$tempDir/$playList"

openssl rand 16 > $keyFile
encryptionKey=`cat $keyFile | hexdump -e '16/1 "%02x"'`

numberOfTsFiles=$(( `ls "$tempDir/$splitFilePrefix"*.ts | wc -l` -1 ))

for i in $(seq -f "%03g" 0 $numberOfTsFiles); do
    initializationVector=`printf '%032x' $(( 10#$i))`
    openssl aes-128-cbc -e -in "$tempDir/$splitFilePrefix"$i.ts \
    -out "$outDir/$splitFilePrefix"$i.ts -nosalt -iv $initializationVector -K $encryptionKey
done

{
    head -4 "$tempDir/$playList"
    echo '#EXT-X-KEY:METHOD=AES-128,URI='"$keyFile"
    egrep "$tempDir/$playList" -vie '#EXT-X-TARGETDURATION:' \
    | tail -n +4
} > "$outDir/$playList"

#rm -r "$tempDir"

This results in a something like this:

#EXTM3U
#EXT-X-VERSION:3
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-KEY:METHOD=AES-128,URI=output.key
#EXT-X-TARGETDURATION:11
#EXTINF:10.176833,
output-000.ts
#EXTINF:8.341667,
output-001.ts
#EXTINF:8.341667,
output-002.ts
#EXTINF:3.136467,
output-003.ts
#EXT-X-ENDLIST

This almost works. However I need an VOD, not a live stream. So, I added line:

#EXT-X-PLAYLIST-TYPE:VOD

And now it doesn't work with encrypted segments, only with unencrypted ones. I thought all segments are crypted separately? Also, even with unencrypted files, the info about total length isn't present. How can I fix that?


Solution

  • Here are a few pointers based on my own experiments which seem to work on VLC, iOS and Android.

    • Initialization Vectors

      When no IV is specified in the playlist each segment has a default IV equal to the media sequence. Make sure segment-000 has IV=0, segment-001 has IV=1 and so on.

    • Quoting the URI

      iOS doesn't seem to like a playlist where the URI doesn't use quotes so use EXT-X-KEY:METHOD=AES-128,URI="output.key"

    • Playlist type VOD

      EXT-X-PLAYLIST-TYPE is optional and, as long as you have the EXT-X-ENDLIST at the end, the playlist is treated as static and allows you to seek. With or without this tag both VLC and iOS treat your playlist as VOD.

      Concerning the media duration, VLC shows 0 wile iOS shows the correct value.

      If you do specify EXT-X-PLAYLIST-TYPE:VOD make sure it comes after EXT-X-VERSION:3 or VLC won't like it.