An art project of mine; showing 30 second video clips of calm places & moments I've enjoyed being in. stream.place/byjp.me
video streaming art

Add check for files & loop

This ensures we always reconnect if stream.place goes down (unlikely, but may as well be careful!)

+60 -16
+2
Dockerfile
··· 2 2 3 3 RUN apk add --no-cache bash coreutils 4 4 5 + COPY check.sh /usr/local/bin/check.sh 6 + RUN chmod +x /usr/local/bin/check.sh 5 7 COPY stream.sh /usr/local/bin/stream.sh 6 8 RUN chmod +x /usr/local/bin/stream.sh 7 9
+24
check.sh
··· 1 + #!/bin/bash 2 + # 3 + # Validates all .ts files in the current directory have a video stream. 4 + # Run this before streaming to catch bad encodes. 5 + 6 + bad=0 7 + 8 + for f in *.ts; do 9 + [[ -f "$f" ]] || continue 10 + 11 + if ! ffprobe -v error -select_streams v:0 \ 12 + -show_entries stream=codec_type -of csv=p=0 "$f" 2>/dev/null \ 13 + | grep -q video; then 14 + echo "INVALID: $f" >&2 15 + bad=$((bad + 1)) 16 + fi 17 + done 18 + 19 + if [[ $bad -gt 0 ]]; then 20 + echo "$bad invalid file(s) found" >&2 21 + exit 1 22 + fi 23 + 24 + echo "All files valid"
+34 -16
stream.sh
··· 4 4 [[ -z "$STREAM_KEY" ]] && echo "STREAM_KEY is required" >&2 && exit 1 5 5 6 6 HISTORY_SIZE=5 7 - declare -a history=() 7 + RETRY_DELAY=1 8 + MAX_RETRY_DELAY=30 9 + 10 + pick_video() { 11 + local all_videos=() 12 + for f in *.ts; do 13 + [[ -f "$f" ]] || continue 14 + all_videos+=("$f") 15 + done 8 16 9 - CURRENT_VIDEO_TRACKER=$(mktemp) 10 - trap 'echo "Last playing: $(cat "$CURRENT_VIDEO_TRACKER")" >&2; rm -f "$CURRENT_VIDEO_TRACKER"' EXIT 17 + [[ ${#all_videos[@]} -eq 0 ]] && return 1 11 18 12 - pick_video() { 13 - local all_videos=(*.ts) 14 19 local available=() 15 - 16 - # Filter out recently played videos 17 20 for v in "${all_videos[@]}"; do 18 21 local in_history=0 19 22 for h in "${history[@]}"; do ··· 22 25 [[ $in_history -eq 0 ]] && available+=("$v") 23 26 done 24 27 25 - # If fewer videos than history size, use all 26 28 [[ ${#available[@]} -eq 0 ]] && available=("${all_videos[@]}") 27 29 28 - # Pick random from available 29 30 echo "${available[$((RANDOM % ${#available[@]}))]}" 30 31 } 31 32 33 + # Outer loop: reconnect on disconnect 32 34 while true; do 33 - video=$(pick_video) 35 + echo "Connecting to $RTMP_URL..." >&2 36 + declare -a history=() 34 37 35 - # Update history (add new, keep last HISTORY_SIZE) 36 - history+=("$video") 37 - (( ${#history[@]} > HISTORY_SIZE )) && history=("${history[@]:1}") 38 + # Inner loop: pick and stream videos, piped into ffmpeg 39 + while true; do 40 + video=$(pick_video) 41 + if [[ -z "$video" ]]; then 42 + echo "No .ts files found, waiting..." >&2 43 + sleep 5 44 + continue 45 + fi 46 + 47 + history+=("$video") 48 + (( ${#history[@]} > HISTORY_SIZE )) && history=("${history[@]:1}") 49 + 50 + echo "Playing: $video" >&2 51 + cat "$video" 52 + done | ffmpeg -loglevel error -re -fflags +genpts -i pipe:0 -c copy -f flv "$RTMP_URL/$STREAM_KEY" 38 53 39 - echo "$video" > "$CURRENT_VIDEO_TRACKER" 40 - cat "$video" 41 - done | ffmpeg -loglevel error -re -fflags +genpts -i pipe:0 -c copy -f flv "$RTMP_URL/$STREAM_KEY" 54 + echo "Stream disconnected, reconnecting in ${RETRY_DELAY}s..." >&2 55 + sleep "$RETRY_DELAY" 56 + 57 + RETRY_DELAY=$(( RETRY_DELAY * 2 )) 58 + (( RETRY_DELAY > MAX_RETRY_DELAY )) && RETRY_DELAY=$MAX_RETRY_DELAY 59 + done