yamlrtsprtsp-server

How do I setup a fallback from a MP4 file for multiple RTSP streams proxied through MediaMTX?


I've hit a snag with my MediaMTX setup while attempting to initiate a fallback stream using FFmpeg with the runOnInit directive. It appears that the environment variables $RTSP_PORT and $MTX_PATH aren't initialized when runOnInit triggers the command.

My goal is to have MediaMTX manage RTSP streams and establish a fallback stream through FFmpeg that kicks in automatically upon MediaMTX startup. The aim is to seamlessly switch to the fallback stream if any of the primary streams encounter issues.

Expected Behavior:

  1. MediaMTX serves primary RTSP streams (stream1 and stream2).
  2. If primary streams fail, RTSP clients smoothly transition to a fallback stream (fallbackpath).
  3. The fallback stream, configured with FFmpeg, should utilize environment variables ($RTSP_PORT and $MTX_PATH) to dynamically form the RTSP URL.
  4. RTSP clients enjoy uninterrupted playback by automatically alternating between primary and fallback streams.

Here's a simplified version of my YAML configuration:

fallbackpath:
    runOnInit: ffmpeg -re -stream_loop -1 -i "/path/to/fallback/video.mp4" -c:v copy -an -f rtsp "rtsp://127.0.0.1:$RTSP_PORT/$MTX_PATH"
    runOnInitRestart: yes
paths:
    # Primary streams
    stream1:
        source: rtsp://example.com/stream1
        fallback: fallbackpath
    stream2:
        source: rtsp://example.com/stream2
        fallback: fallbackpath
    # More streams...

However, it seems that $RTSP_PORT and $MTX_PATH aren't available when runOnInit is triggered, resulting in errors.

I've attempted using the runOnDemand directive as an alternative, but I'm uncertain if it's suitable for my scenario. Unfortunately, both runOnInit and runOnDemand configurations fail, causing MediaMTX to abort.

I'd appreciate any guidance on ensuring that the environment variables are properly initialized when starting the fallback stream.

Thanks for your assistance!


Solution

  • Probably not the cleanest option and should probably be using MediaMTX built in "fallback:" just don't know how, so scripted my own version using Bash script FFMpeg and FFProbe, the way I figure it if it is on a cron for every 2-5 seconds it will do the trick just fine:

    #!/bin/bash
    
    # Function to extract specific variables from .env file
    extractEnvVariable() {
        local envFile="${1}"
        local variableName="${2}"
        local variableValue=$(grep "^${variableName}=" "$envFile" | cut -d'=' -f2- | tr -d '"' | tr -d "'")
        echo "$variableValue"
    }
    
    # Absolute path to the script directory
    SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
    
    # Paths to the .env and .env-secrets files
    ENV_FILE="$SCRIPT_DIR/../.env"
    
    # Define paths and variables
    pathsConfigPath="$SCRIPT_DIR/../config/paths_config.yml"
    mediamtxConfigPath="/usr/local/etc/mediamtx.yml"
    streamsDirectory="$SCRIPT_DIR/../config/customers/"
    LOG_FILE="/var/log/stream_conductor.log"
    
    # Get FFProbe path, ffmpeg path, fallback URL, and stream URI from .env
    FFPROBE=$(extractEnvVariable "$ENV_FILE" "FFMPEG_FFPROBE_PATH")
    FFMPEG=$(extractEnvVariable "$ENV_FILE" "FFMPEG_FFMPEG_PATH")
    FALLBACK=$(extractEnvVariable "$ENV_FILE" "MEDIAMTX_FALLBACK")
    STREAM_URI=$(extractEnvVariable "$ENV_FILE" "MEDIAMTX_URL")
    STREAM_URI=${STREAM_URI%/}  # Trim trailing forward slash
    
    # Trim trailing forward slash from STREAM_URI
    STREAM_URI=${STREAM_URI%/}
    
    # Clear log file
    > "$LOG_FILE"
    
    # Check if streams directory exists and is a directory
    if [ -d "$streamsDirectory" ]; then
        configContent=""
        # Iterate through customer directories
        customerDirectories=$(ls "$streamsDirectory")
        for customerDirectory in $customerDirectories; do
            if [ "$customerDirectory" != "." ] && [ "$customerDirectory" != ".." ]; then
                customerPath="$streamsDirectory/$customerDirectory"
                # Check if customer directory exists and is a directory
                if [ -d "$customerPath" ]; then
                    streamPath="$customerPath/streams/"
                    # Check if streams directory exists and is a directory
                    if [ -d "$streamPath" ]; then
                        # Loop through stream files
                        for streamFile in "$streamPath"/*; do
                            if [ "$streamFile" != "$streamPath/*" ]; then
                                UUID=$(basename "$streamFile" | cut -c7-) # Assuming stream directory names start with "stream"
                                rtspUrlFile="$streamFile/rtsp_url_$UUID.txt"
                                if [ -f "$rtspUrlFile" ]; then
                                    rtspUrl=$(<"$rtspUrlFile")
    
                                    # Check if RTSP stream is active using FFProbe
                                    if $FFPROBE -v error -select_streams v:0 -show_entries stream=width,height -of csv=p=0 "$rtspUrl" >/dev/null 2>&1; then
                                        # Stream is active, use original RTSP URL
                                        configContent+="    $UUID:\n"
                                        configContent+="        source: $rtspUrl\n"
                                    else
                                        # Stream is not active, fallback to tsdkfallback
                                        configContent+="    $UUID:\n"
                                        configContent+="        source: $STREAM_URI/tsdkfallback\n"
                                    fi
                                fi
                            fi
                        done
                    else
                        echo "Streams directory not found or is not a directory: $streamPath" >> "$LOG_FILE"
                    fi
                else
                    echo "Customer directory not found or is not a directory: $customerPath" >> "$LOG_FILE"
                fi
            fi
        done
        # Add indentation for tsdkfallback entry
        configContent+="    tsdkfallback:\n"
    else
        echo "Streams directory not found or is not a directory: $streamsDirectory" >> "$LOG_FILE"
    fi
    
    # Write the updated content to paths_config.yml
    echo -e "$configContent" > "$pathsConfigPath"
    
    # Load original configuration from mediamtx.yml
    mediamtxConfig=$(<"$mediamtxConfigPath")
    
    # Combine mediamtx.yml and configContent
    combinedConfig="$mediamtxConfig\npaths:\n$configContent"
    
    # Write the combined configuration to config.yml
    outputFilePath="/usr/local/etc/config.yml"
    echo -e "$combinedConfig" > "$outputFilePath"
    
    # Check if MediaMTX process is not already running
    if ! pgrep -x "mediamtx" >/dev/null; then
        # Start MediaMTX
        /usr/local/bin/mediamtx "$outputFilePath" &
    fi
    
    # Check if ffmpeg process is not already running
    if ! pgrep -x "ffmpeg" >/dev/null; then
        # Execute ffmpeg command as a standalone process
        "$FFMPEG" -re -stream_loop -1 -i "$FALLBACK" -c:v copy -an -f rtsp "$STREAM_URI/tsdkfallback" &
    fi
    

    This then outputs this into the config.yml assuming in this particular instance none of the cameras are streaming so it has defaulted all the stream sources to the fallback:

    paths:
        71fc528a-a65d-473e-ac9f-ff4751576cce:
            source: rtsp://localhost:8554/tsdkfallback
        a34dd062-087d-4a68-99d0-612bc4478910:
            source: rtsp://localhost:8554/tsdkfallback
        ab1ac337-e137-4ea1-a4a8-34843abeeffe:
            source: rtsp://localhost:8554/tsdkfallback
        add118ab-1f19-45ce-913d-59e242de3e08:
            source: rtsp://localhost:8554/tsdkfallback
        c5908bd6-9da6-44b2-8ffa-57af78ed01bd:
            source: rtsp://localhost:8554/tsdkfallback
        dda7a9cb-e348-44ce-98f7-cb224c8e4a9d:
            source: rtsp://localhost:8554/tsdkfallback
        tsdkfallback:
    

    I tested this streaming to VLC App and it works