Skip to content

Instantly share code, notes, and snippets.

@datavudeja
Forked from troykelly/README.md
Created December 30, 2025 20:19
Show Gist options
  • Select an option

  • Save datavudeja/c642128235da7e4f10c15d817fd77076 to your computer and use it in GitHub Desktop.

Select an option

Save datavudeja/c642128235da7e4f10c15d817fd77076 to your computer and use it in GitHub Desktop.
Multi Screen Video Playback using ffplay

Stream.sh

stream.sh is a Bash script designed to stream multiple video sources to multiple screens using ffplay. This script ensures that each video stream is displayed on a specified monitor and restarts the stream if it fails. The script is optimized for real-time playback by using various ffplay options to reduce latency and handle frame drops effectively.

Features

  • Streams multiple video sources to multiple screens.
  • Ensures each stream is displayed on the specified monitor.
  • Restarts streams automatically if they fail.
  • Optimized for real-time playback with minimal latency.
  • Detailed logging and debugging output.

Prerequisites

  • ffplay: Part of the ffmpeg suite. Install ffmpeg to get ffplay.
  • wmctrl: A command-line tool to interact with X Window Manager.

Installation

Installing ffmpeg

On Ubuntu/Debian:

sudo apt-get update
sudo apt-get install ffmpeg

On macOS using Homebrew:

brew install ffmpeg

Installing wmctrl

On Ubuntu/Debian:

sudo apt-get install wmctrl

On macOS using Homebrew:

brew install wmctrl

Download and Install Stream.sh

You can download the stream.sh script directly from the gist URL:

curl -o stream.sh https://gist.githubusercontent.com/troykelly/9c9e33390931d3d97ff1d888a6eb7ae1/raw/stream.sh
chmod +x stream.sh

Usage

./stream.sh URL1 URL2 ...
  • Replace URL1, URL2, etc., with the URLs of the video streams you want to display.
  • The script will automatically detect connected screens and map the provided URLs to the screens in the order they are provided.

Example

./stream.sh "https://example.com/stream1" "https://example.com/stream2"

This command will start streaming https://example.com/stream1 on the first detected screen and https://example.com/stream2 on the second detected screen.

How It Works

  1. The script detects the connected screens using xrandr.
  2. It starts an ffplay process for each provided URL, positioning the ffplay window on the specified screen.
  3. If an ffplay process fails, the script automatically restarts the stream on the same screen.
  4. The script optimizes ffplay for real-time playback by using options to reduce latency and handle frame drops.

Script Options

The script uses the following ffplay options to optimize for real-time playback:

  • -fflags nobuffer: Reduces the buffering time.
  • -flags low_delay: Sets low delay for decoding.
  • -strict experimental: Enables experimental features.
  • -fast: Enables faster but less accurate decoding.
  • -framedrop: Allows dropping frames if the decoder is too slow.
  • -sync ext: Synchronizes to the external clock.

Signals

The script handles the following signals:

  • SIGHUP: Terminates all streams and exits cleanly.
  • SIGUSR1: Restarts the stream on the first screen.
  • SIGUSR2: Restarts the stream on the second screen.

Autostart Configuration

To have the script autostart if autologin is enabled, you can place the script in ~/.config/autostart-scripts/ and ensure it is executable. Additionally, you need to create a .desktop file to run the script on startup.

Steps:

  1. Create the autostart-scripts directory (if it doesn't already exist):

    mkdir -p ~/.config/autostart-scripts
  2. Move the script to the autostart-scripts directory:

    mv stream.sh ~/.config/autostart-scripts/
  3. Make sure the script is executable:

    chmod +x ~/.config/autostart-scripts/stream.sh
  4. Create a .desktop file in the ~/.config/autostart/ directory:

    nano ~/.config/autostart/stream.sh.desktop

    Add the following content to the file:

    [Desktop Entry]
    Type=Application
    Exec=/home/YOUR_USERNAME/.config/autostart-scripts/stream.sh "https://example.com/stream1" "https://example.com/stream2"
    Hidden=false
    NoDisplay=false
    X-GNOME-Autostart-enabled=true
    Name[en_US]=Stream.sh
    Name=Stream.sh
    Comment=Start Stream.sh at login

    Replace YOUR_USERNAME with your actual username and update the Exec line with the correct paths and URLs.

  5. Save and exit the file.

    Press CTRL+X, then Y, and then Enter.

Troubleshooting

  • Ensure ffplay and wmctrl are installed and available in your system's PATH.
  • Make sure the URLs provided are accessible and valid video streams.
  • Check the script's output for any error messages or debugging information.
#!/usr/bin/env bash
SCRIPT_NAME=$(basename "$0")
# Function to get the number of screens and their dimensions using xrandr.
get_screens() {
xrandr --query | grep " connected" | awk '{print $1, $3}' | sed 's/primary//'
}
# Function to start a stream on a specific screen and position it.
start_stream() {
local screen_name=$1
local screen_dimensions=$2
local url=$3
local width=$(echo $screen_dimensions | cut -d'x' -f1)
local height=$(echo $screen_dimensions | cut -d'x' -f2 | cut -d'+' -f1)
local x_offset=$(echo $screen_dimensions | cut -d'+' -f2)
local y_offset=$(echo $screen_dimensions | cut -d'+' -f3)
# Provide default values if dimensions are missing
width=${width:-1920}
height=${height:-1080}
x_offset=${x_offset:-0}
y_offset=${y_offset:-0}
echo "Starting ffplay on $screen_name at ${width}x${height}+${x_offset}+${y_offset} with URL: $url"
ffplay -autoexit -window_title "$screen_name" -loglevel quiet -hide_banner -x "$width" -y "$height" -left "$x_offset" -top "$y_offset" -fflags nobuffer -flags low_delay -strict experimental -fast -framedrop -sync ext "$url" &
ffplay_pid=$!
echo $ffplay_pid > "/tmp/ffplay_$screen_name.pid"
echo "Waiting for ffplay window for $screen_name to appear..."
# Poll until ffplay is actually playing
while ! xwininfo -name "$screen_name" &>/dev/null; do
sleep 1
done
echo "Relocating ffplay window for $screen_name to ${width}x${height}+${x_offset}+${y_offset}"
# Move the window to the correct position
wmctrl -r "$screen_name" -e 0,"$x_offset","$y_offset","$width","$height"
}
# Function to stop a stream on a specific screen.
stop_stream() {
local screen_name=$1
local pid_file="/tmp/ffplay_$screen_name.pid"
if [ -f "$pid_file" ]; then
echo "Stopping ffplay for $screen_name"
kill "$(cat $pid_file)" && rm -f "$pid_file"
fi
}
# Function to handle SIGHUP signal.
handle_sighup() {
echo "Received SIGHUP. Terminating all streams."
for screen_name in "${!screens[@]}"; do
stop_stream "$screen_name"
done
exit 0
}
# Function to handle SIGUSR1 signal.
handle_sigusr1() {
echo "Received SIGUSR1. Restarting stream on screen 1."
local screen_name="${screen_names[0]}"
stop_stream "$screen_name"
start_stream "$screen_name" "${screens[$screen_name]}" "${urls[0]}"
}
# Function to handle SIGUSR2 signal.
handle_sigusr2() {
echo "Received SIGUSR2. Restarting stream on screen 2."
local screen_name="${screen_names[1]}"
stop_stream "$screen_name"
start_stream "$screen_name" "${screens[$screen_name]}" "${urls[1]}"
}
# Trap signals
trap handle_sighup SIGHUP
trap handle_sigusr1 SIGUSR1
trap handle_sigusr2 SIGUSR2
# Set DISPLAY to :0 if not already set.
export DISPLAY=${DISPLAY:-:0}
# Ensure wmctrl is installed
if ! command -v wmctrl &>/dev/null; then
echo "wmctrl could not be found. Please install it to use this script."
exit 1
fi
# Main function
main() {
declare -A screens
declare -a urls=("$@")
declare -a screen_names
# Get the number of screens and their dimensions.
while IFS= read -r line; do
screen_name=$(echo "$line" | awk '{ print $1 }')
screen_dimensions=$(echo "$line" | awk '{ print $2 }')
screens["$screen_name"]="$screen_dimensions"
screen_names+=("$screen_name")
done < <(get_screens)
# Print header information.
echo "========================================"
echo " Script: $SCRIPT_NAME"
echo "========================================"
echo "Number of screens: ${#screens[@]}"
for screen_name in "${screen_names[@]}"; do
# Check if screen dimensions are empty and provide default values if necessary
screen_dimensions=${screens[$screen_name]}
if [ -z "$screen_dimensions" ]; then
screen_dimensions="1920x1080+0+0"
screens[$screen_name]=$screen_dimensions
echo " $screen_name: $screen_dimensions (default values)"
else
echo " $screen_name: ${screens[$screen_name]}"
fi
done
echo "========================================"
echo "Streaming URLs:"
for i in "${!urls[@]}"; do
echo " ${screen_names[$i]}: ${urls[$i]}"
done
echo "========================================"
# Check if there are more URLs than screens.
if [ "${#urls[@]}" -gt "${#screens[@]}" ]; then
echo "Warning: More URLs than screens. Only the first ${#screens[@]} streams will be displayed."
fi
# Start streams on available screens.
for i in "${!urls[@]}"; do
start_stream "${screen_names[$i]}" "${screens[${screen_names[$i]}]}" "${urls[$i]}"
done
# Monitor streams and restart if necessary.
while true; do
for i in "${!screen_names[@]}"; do
screen_name="${screen_names[$i]}"
pid_file="/tmp/ffplay_$screen_name.pid"
if [ -f "$pid_file" ]; then
pid=$(cat "$pid_file")
if ! kill -0 "$pid" &>/dev/null; then
echo "Stream on $screen_name has stopped. Restarting..."
start_stream "$screen_name" "${screens[$screen_name]}" "${urls[$i]}"
sleep 5 # Cool-down period to avoid hammering the server.
fi
fi
done
sleep 1
done
}
# Run the main function with all script arguments
main "$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment