# - Warn-log differencies over reasonable margin (TODO: reasonable margin)
# - Error-log if seeing duplicate CUE-OUT or CUE-IN markers
# - Optionally warn-log if seeing breaks over 12 minutes a hour (TODO)
+# - Synchronize time by lowering target window to 1 second until first changed playlist (syncs to ~1 sec precision)
# - Records first seen ad-block including one segment before and after
-# - very useful for synchronization
+# - very useful for synchronization of SCTE-35/SCTE-104 markers
#
-# Errors goes to stderr
-# Warnings and info goes to stdout
+# Log (info, warnings and errors) goes to stderr
+# Playlist goes to stdout
#
# Syntax:
-# monitor-dai.sh <url-to-hls-stream-including-tokens>
+# monitor-dai.sh <url-to-hls-stream-including-tokens> <optional-record-dir> <delay>
#
BASIC_CURL_PARAMS="--connect-timeout 10 --location --max-time 60 -Ss"
# Used as default 10 seconds pause before retrying
TARGET_DURATION=10
-# Time screw (expected delay in stream in seconds)
-STREAM_TIME_SCREW=105
-
# No adblocks to record
RECORD_AD_BLOCKS=3
function monitorStream() {
local stream="$1"
local recordFolder="$2"
+ local streamDelay="$3"
local nextSegmentLength=0
local nextSeq=-1
local curSeq=-1
local tags=""
local lastUsedSegment="" lastUsedStreamTime=0 # Stored for use of recording previous segment when seeing ad block
local doRecord=0 doStopRecord=0
+ local syncMode=-1 # (-1=get first, x=saw last at, 0=synched) In syncmode we try to request every second until we see a change to get a more precise time sync
real_time=$(date +%s)
- stream_time=$(($real_time - $STREAM_TIME_SCREW))
- pretty_time=$($DATE${stream_time%.*} +"%Y-%m-%d %H:%M:%S")
- echo >&2 "NOTICE: Using time-offset of -$STREAM_TIME_SCREW seconds so time is $pretty_time, real time is $(date ${realtime} +"%Y-%m-%d %H:%M:%S")"
- echo >&2 "$pretty_time: INFO: Started monitoring using: $stream"
+ if [ -n "$streamDelay" ]; then
+ stream_time=$(($real_time - $streamDelay))
+ pretty_time=$($DATE${stream_time%.*} +"%Y-%m-%d %H:%M:%S")
+ echo >&2 "NOTICE: Using time-offset of -$streamDelay seconds so time is $pretty_time, real time is $(date ${realtime} +"%Y-%m-%d %H:%M:%S")"
+ else
+ stream_time="$real_time"
+ pretty_time=$($DATE${stream_time%.*} +"%Y-%m-%d %H:%M:%S")
+ fi
+ echo >&2 "$pretty_time: INFO: Started monitoring using: $stream"
+
while true; do
starttime=$(date +%s)
warn_not_a_playlist=1
stream_time=$(echo "$stream_time + $nextSegmentLength" | bc)
pretty_time=$($DATE${stream_time%.*} +"%Y-%m-%d %H:%M:%S")
if [[ $curSeq -ne $nextSeq ]]; then
- echo >&2 "$pretty_time: WARN: Lost segments ($curSeq > $nextSeq)"
+ echo >&2 "$pretty_time: WARN: Lost segments ($curSeq > $nextSeq)... resetting..."
+ failed=10
fi
#echo >&2 "$pretty_time: Current mode length: $curLength"
nextSeq=$(($curSeq + 1))
# Log stopped recording (after pretty_time moved forward)
if [ "$doStopRecord" == "1" ]; then
doRecord=0
- echo >&2 "$pretty_time: INFO: Stopped recording."
+ #echo >&2 "$pretty_time: INFO: Stopped recording."
doStopRecord=0
fi
+ # Sync time a bit better by using target duration of 1 second in beginning
+ if [ "$syncMode" -gt 0 ]; then
+ real_time="$starttime"
+ syncOffset="$(echo "$target_duration - ($real_time - $syncMode)" | bc)"
+ if [ "$syncOffset" -gt 0 ]; then
+ stream_time="$(echo "$stream_time - $syncOffset" | bc)"
+ pretty_time=$($DATE${stream_time%.*} +"%Y-%m-%d %H:%M:%S")
+ syncMode=0
+ echo >&2 "$pretty_time: INFO: Time was adjusted by -$syncOffset seconds."
+ elif [ "$syncOffset" -eq 0 ]; then
+ syncMode=0
+ else
+ # Resync
+ syncMode=-1
+ fi
+ fi
fi
curSeq=$(($curSeq + 1))
fi
failed=$(($failed + 1))
else
failed=0
+
+ # Use this time for sync offset?
+ if [ $syncMode -eq -1 ]; then
+ syncMode="$starttime"
+ fi
fi
else
rm "$TMPFILE"
endtime=$(date +%s)
- waittime=$(($target_duration - ($endtime-$starttime)))
+ if [ "$syncMode" -ne 0 ]; then
+ # While syncing we only wait one second
+ waittime=$((1 - ($endtime-$starttime)))
+ else
+ waittime=$(($target_duration - ($endtime-$starttime)))
+ fi
if [ "$waittime" -gt 0 ]; then
sleep $waittime
fi
+ if [[ "$failed" -gt 0 && "$syncMode" -ne 0 ]]; then
+ syncMode=-1
+ fi
+
if [ "$failed" -ge 2 ]; then
- echo >&2 "$pretty_time: ERROR: Stream failed twice.. resetting..."
+ if [ "$failed" -ne 10 ]; then
+ echo >&2 "$pretty_time: ERROR: Stream failed twice.. resetting..."
+ fi
return
fi
done
echo >&2 "INFO: (will retry in 10 seconds)"
sleep 10
else
- monitorStream "$streamurl" "$2"
+ monitorStream "$streamurl" "$2" "$3"
fi
done
kill "${myjobs[@]}"
wait
echo "Done."
+ rm "$MYPID"
}
function fatal() {
exit 1
}
+# Token generators
+function addLevel3Tokens() {
+ local secret="$1"
+ local secretno="$2"
+ shift 2
+ while [ -n "$1" ]; do
+ if [[ "$1" =~ ^((.+[[:space:]])URL=([^[:space:]]+))([[:space:]].+)?$ ]]; then
+ echo -n "${BASH_REMATCH[1]}?token=${secretno}"
+ echo -n "${BASH_REMATCH[3]}" | sed -Ee 's/.*https?:\/\/[^/]*//' | openssl sha1 -hmac "$secret" | sed -e 's/(stdin)= //' | cut -c1-20
+ echo "${BASH_REMATCH[4]}"
+ else
+ echo "$1"
+ fi
+ shift
+ done
+}
+
+
+function addDaiTokens() {
+ local secret="$1"
+ shift
+ while [ -n "$1" ]; do
+ if [[ "$1" =~ ^((.+[[:space:]])URL=([^[:space:]]+))([[:space:]].+)?$ ]]; then
+ echo "${BASH_REMATCH[1]}?api-key=$secret${BASH_REMATCH[4]}"
+ else
+ echo "$1"
+ fi
+ shift
+ done
+}
+
+
## MAIN
cd $(dirname $0)
for stream in "${streams[@]}"; do
- NAME="" URL=""
+ NAME="" URL="" DELAY=""
declare ${stream}
- echo "Starting monitor for $NAME ..."
- ./monitor-dai.sh "$URL" >"$playlist_dir/$NAME.m3u8" 2>"$log_dir/$NAME.log" "$recordings_dir/$NAME/" &
+ echo "Starting monitor for $NAME ($URL) ..."
+ ./monitor-dai.sh "$URL" "$recordings_dir/$NAME/" "$DELAY" >"$playlist_dir/$NAME.m3u8" 2>"$log_dir/$NAME.log" &
myjobs+=($!)
done
echo "All started."
echo "(To shutdown all run: kill $BASHPID)"
+ echo "$BASHPID" >"$MYPID"
exec 1>&-