Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Streaming issues with HLS setup using Nginx and FFmpeg, and TS video files

I've been working on setting up an HLS stream on my Raspberry Pi to broadcast video from a security camera that's physically connected to my Raspberry Pi through my web server, making it accessible via my website. The .ts video files and the .m3u8 playlist are correctly being served from /var/www/html/hls. However, when I attempt to load the stream on Safari (as well as other browsers), the video continuously appears to be loading without ever displaying any content.

Here are some details about my setup:

  • Camera: I am using an Arducam 1080p Day & Night Vision USB Camera which is available on /dev/video0.
  • Server Configuration: I haven't noticed any errors in the Safari console or on the server logs. When I access the .ts files directly from the browser, they only show a black screen but they do play.

Given the situation, I suspect there might be an issue with my FFmpeg command or possibly with my Nginx configuration.

Here is what I have:

ffmpeg stream service: /etc/systemd/system/ffmpeg-stream.service

[Unit]
Description=FFmpeg RTMP Stream
After=network.target

[Service]
ExecStart=/usr/local/bin/start_ffmpeg.sh
Restart=always
User=jacobanderson
Group=jacobanderson
StandardError=syslog
SyslogIdentifier=ffmpeg-stream
Environment=FFMPEG_LOGLEVEL=error

[Install]
WantedBy=multi-user.target

ffmpeg command: /usr/local/bin/start_ffmpeg.sh

#!/bin/bash

/usr/bin/ffmpeg -f v4l2 -input_format mjpeg -video_size 1280x720 -framerate 30 -i /dev/video0 -vcodec libx264 -preset veryfast -acodec aac -strict -2 -f flv rtmp://localhost/live/

nginx.conf: /etc/nginx/nginx.conf

user www-data;
worker_processes auto;
pid /run/nginx.pid;
error_log /var/log/nginx/error.log;
include /etc/nginx/modules-enabled/*.conf;

events {
    worker_connections 768;
    # multi_accept on;
}

rtmp {
    server {
        listen 1935;
        chunk_size 4096;
        #allow publish 127.0.0.1;
        #deny publish all;

    application live {
        #allow 192.168.0.100;
        live on;
        hls on;
        hls_path /var/www/html/hls;
        hls_fragment 3;
        hls_nested on; 
        #hls_fragment_naming stream;
        hls_playlist_length 120;
        hls_cleanup on;
        hls_continuous on;
        #deny play all;
    }
    }
}

http {
    ##
    # Basic Settings
    ##

    sendfile on;
    #sendfile off;
    tcp_nopush on;
    types_hash_max_size 2048;
    # server_tokens off;

    # Additional for video
    directio 512;

    # server_names_hash_bucket_size 64;
    # server_name_in_redirect off;

    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    ##
    # SSL Settings
    ##

    ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;
    #ssl_protocols TLSv1.2 TLSv1.3; # Use only secure protocols
    ssl_prefer_server_ciphers on;
    #ssl_ciphers "HIGH:!aNULL:!MD5";

    ##
    # Logging Settings
    ##

    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;

    ##
    # Gzip Settings
    ##

    #gzip on;
    gzip off;  # Ensure gzip is off for HLS

    ##
    # Virtual Host Configs
    ##

    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*;
}

sites-available: /etc/nginx/sites-available/myStream.mysite.com

server {
    listen 443 ssl;
    server_name myStream.mysite.com;

    ssl_certificate /etc/letsencrypt/live/myStream.mysite.com/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/myStream.mysite.com/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot

    location / {
        root /var/www/html/hls;
        index index.html;
    }

    location /hls {
        # Password protection
        auth_basic "Restricted Content";
        auth_basic_user_file /etc/nginx/.htpasswd;

        # Disable cache
        add_header Cache-Control no-cache;

        # CORS setup
        add_header 'Access-Control-Allow-Origin' '*' always;
        add_header 'Access-Control-Expose-Headers' 'Content-Length';

        # Allow CORS preflight requests
        if ($request_method = 'OPTIONS') {
            add_header 'Access-Control-Allow-Origin' '*';
            add_header 'Access-Control-Max-Age' 1728000;
            add_header 'Content-Type' 'text/plain charset=UTF-8';
            add_header 'Content-Length' 0;
            return 204;
        }

        types {
            application/vnd.apple.mpegurl m3u8;
            video/mp2t ts;
        text/html html;
        text/css css;
        }

    root /var/www/html;
    }
}

server {
    listen 80;
    server_name myStream.mysite.com;

    if ($host = myStream.mysite.com) {
        return 301 https://$host$request_uri;
    }

    return 404; # managed by Certbot
}

index.html: /var/www/html/hls/index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>HLS Stream</title>
    <link href="https://vjs.zencdn.net/7.10.2/video-js.css" rel="stylesheet" />
    <script src="./js/hls.min.js"></script>
</head>
<body>
    <video-js id="my-video" class="video-js vjs-default-skin" controls preload="auto" width="640" height="264"
              autoplay muted data-setup='{"fluid": true}'>
        <!--<source src="https://myStream.mysite.com/hls/index.m3u8" type="application/x-mpegURL">-->
    <source src="https://myStream.mysite.com/hls/index.m3u8" type="application/vnd.apple.mpegurl">
    </video-js>

    <script src="https://vjs.zencdn.net/7.10.2/video.js"></script>
    <script>
        if (Hls.isSupported()) {
            var video = document.getElementById('my-video_html5_api'); // Updated ID to target the correct video element
            var hls = new Hls();
            hls.loadSource('https://myStream.mysite.com/hls/index.m3u8');
            hls.attachMedia(video);
            hls.on(Hls.Events.MANIFEST_PARSED,function() {
                video.play();
            });
        } else if (video.canPlayType('application/vnd.apple.mpegurl')) {
            video.src = 'https://myStream.mysite.com/hls/index.m3u8';
            video.addEventListener('loadedmetadata', function() {
                video.play();
            });
        }
    </script>
</body>
</html>

Has anyone experienced similar issues or can spot an error in my configuration? Any help would be greatly appreciated as I have already invested over 30 hours trying to resolve this.

like image 533
Jacob Anderson Avatar asked Nov 17 '25 17:11

Jacob Anderson


1 Answers

I finally figured out a solution, it was to change:

#!/bin/bash

/usr/bin/ffmpeg -f v4l2 -input_format mjpeg -video_size 1280x720 -framerate 30 -i /dev/video0 -vcodec libx264 -preset veryfast -acodec aac -strict -2 -f flv rtmp://localhost/live/

to

#!/bin/bash

/usr/bin/ffmpeg -f v4l2 -input_format mjpeg -video_size 1280x720 -framerate 30 -i /dev/video0 -vcodec libx264 -preset veryfast -tune zerolatency -g 60 -sc_threshold 0 -acodec aac -b:a 128k -ar 44100 -maxrate 1500k -bufsize 3000k -f hls -hls_time 4 -hls_playlist_type event -hls_segment_filename '/var/www/html/hls/stream%03d.ts' /var/www/html/hls/index.m3u8

I think this solution works as it bypasses RTMP, if someone knows of a good alternative solution that involves actually using the RTMP please lmk in the comments or as an alternate solution and I will accept it (if it works).

like image 64
Jacob Anderson Avatar answered Nov 21 '25 06:11

Jacob Anderson