You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

FFmpeg推流Nginx的HLS流无法在iPhone播放的问题求助

iPhone端HLS直播流无法加载的解决方案

问题概述

用FFmpeg把摄像头RTSP流推送到Nginx RTMP服务器转成HLS流后,桌面Safari、iPad等设备都能正常播放,但iPhone端始终加载失败,偶尔全屏快进时能播放但时间戳异常(比如显示-11:21:42)。用Apple的mediastreamvalidator检测发现部分.ts文件返回404,但浏览器调试时复现不了这个问题,推测是FFmpeg输出的PTS(显示时间戳)异常导致的。


现有配置信息

FFmpeg推流命令

ffmpeg -rtsp_transport tcp -i rtsp://user:pass@192.168.1.XXX/ -f image2 -stream_loop -1 -re -i /tmp/overlay.png -filter_complex "overlay" -vcodec libx264 -preset ultrafast -profile baseline -pix_fmt yuv420p -an -f flv -rtmp_live live -s 1280x720 rtmp://remote.server.com:1935/show/stream

Nginx RTMP配置

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

events {
    worker_connections 768;
    # multi_accept on;
}

http {

    ##
    # Basic Settings
    ##

    # sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65;
    types_hash_max_size 2048;
    # server_tokens off;

    # 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; # Dropping SSLv3, ref: POODLE
    ssl_prefer_server_ciphers on;

    ##
    # Logging Settings
    ##

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

    ##
    # Gzip Settings
    ##

    gzip on;

    # gzip_vary on;
    # gzip_proxied any;
    # gzip_comp_level 6;
    # gzip_buffers 16 8k;
    # gzip_http_version 1.1;
    # gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

    ##
    # Virtual Host Configs
    ##

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

    server {
        listen 8080;

        location ~ \.(mp4|m4a|m4v|mov)$ {
        add_header content_disposition filename=$request_uri;
        add_header accept_ranges bytes;
        }


        location /hls {
        # 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';
            add_header Cache-Control no-cache;
        add_header Last-Modified "";

        # 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;
        }

        root /tmp/;
        }
    }
}


rtmp {
    server {
        listen 1935; # Listen on standard RTMP port
        chunk_size 4000;

        application show {
            live on;
            hls on;
            hls_fragment_naming system;
            hls_fragment 1s;
            hls_path /tmp/hls;
            hls_continuous on;

        hls_variant _720p2628kbs BANDWIDTH=2628000;

        }
    }
}

网页播放代码

<!DOCTYPE html>
<html lang="en"><head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Camera</title>
    <style>
        html, body {
            background: none !important;
            overflow: hidden;
            margin: 0;
            padding: 0;
        }

        video {
            margin: 0 auto;
            width: 100%;
            height: 100vh;
        }

        .video-container {
            width: 100%;
            height: 100vh
        }
    </style>

    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

    <script>
        function waitForPublishedStream(showMsg) {
            if (typeof showMsg === 'undefined') showMsg = true;

            $.get('https://myserver.com/CameraName/hls/stream.m3u8', function (response) {

                if (showMsg) {
                    $('.container-loader .alert-warning').html('Pieslēdzamies kamerai..')
                }

                if (response.state == 'success') {
                    if (response.published) {
                        document.location.reload(true)
                    } else {
                        setTimeout(function () {
                            waitForPublishedStream();
                        }, 2000)
                    }
                }
            })
        }
    </script>
</head>
<body>

            <div class="video-container">
            <video id="video" autoplay="autoplay" muted="muted" controls="controls" playsinline="" src="https://myserver.com/CameraName/hls/stream.m3u8">
                <source src="https://myserver.com/CameraName/hls/stream.m3u8" type="application/vnd.apple.mpegurl">
            </video>
        </div>

<script src="https://cdn.jsdelivr.net/npm/hls.js"></script>

<script>
    $(document).ready(function () {
        const video = document.getElementById('video');

        if (Hls.isSupported()) {

            const hls = new Hls();

            hls.loadSource('https://myserver.com/CameraName/hls/stream.m3u8');
            hls.attachMedia(video);
            hls.on(Hls.Events.MANIFEST_PARSED, function () {
                video.play();
            });
        }
    })
</script>
</body></html>

具体解决方案

1. 修复FFmpeg的PTS异常问题

RTSP流的PTS可能不连续,加上循环的overlay图片会进一步打乱时间戳,调整FFmpeg参数:

  • 添加-fflags +genpts强制生成连续的显示时间戳
  • 添加-use_wallclock_as_timestamps 1用系统时间作为基准时间戳,避免RTSP流自带的异常PTS影响
  • 给overlay滤镜加shortest=1,让输出流时长跟随RTSP流,避免循环图片导致时间戳无限增长

修改后的FFmpeg命令:

ffmpeg -rtsp_transport tcp -fflags +genpts -use_wallclock_as_timestamps 1 -i rtsp://user:pass@192.168.1.XXX/ -f image2 -stream_loop -1 -re -i /tmp/overlay.png -filter_complex "overlay=shortest=1" -vcodec libx264 -preset ultrafast -profile baseline -pix_fmt yuv420p -an -f flv -rtmp_live live -s 1280x720 rtmp://remote.server.com:1935/show/stream

2. 调整Nginx HLS配置适配Apple设备

Apple设备对HLS片段命名和播放列表规则要求严格,修改rtmp配置段:

  • 把hls_fragment_naming system改成hls_fragment_naming sequential,用连续序号命名片段,避免系统时间戳导致的片段名不连续
  • 添加hls_playlist_length 5s限制播放列表长度,防止旧片段被过早删除导致404
  • 添加hls_type live明确标记为直播流类型
  • 移除hls_continuous on,该参数可能导致播放列表引用已被删除的片段

修改后的rtmp配置:

rtmp {
    server {
        listen 1935; # Listen on standard RTMP port
        chunk_size 4000;

        application show {
            live on;
            hls on;
            hls_fragment_naming sequential;
            hls_fragment 1s;
            hls_path /tmp/hls;
            hls_playlist_length 5s;
            hls_type live;

            hls_variant _720p2628kbs BANDWIDTH=2628000;
        }
    }
}

3. 优化网页播放代码适配iPhone Safari

iPhone Safari原生支持HLS,不需要hls.js,避免兼容性冲突:

  • 移除hls.js的加载和相关代码,让Safari用原生能力播放
  • 确保playsinline属性正确设置,避免自动全屏引发的播放异常
  • 给视频添加preload="auto",帮助浏览器提前加载流信息
  • 把等待连接的提示文本改成中文(可选)

修改后的网页代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8">
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Camera</title>
    <style>
        html, body {
            background: none !important;
            overflow: hidden;
            margin: 0;
            padding: 0;
        }

        video {
            margin: 0 auto;
            width: 100%;
            height: 100vh;
        }

        .video-container {
            width: 100%;
            height: 100vh
        }
    </style>

    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

    <script>
        function waitForPublishedStream(showMsg) {
            if (typeof showMsg === 'undefined') showMsg = true;

            $.get('https://myserver.com/CameraName/hls/stream.m3u8', function (response) {
                if (showMsg) {
                    $('.container-loader .alert-warning').html('正在连接摄像头..')
                }

                if (response.state == 'success') {
                    if (response.published) {
                        document.location.reload(true)
                    } else {
                        setTimeout(function () {
                            waitForPublishedStream();
                        }, 2000)
                    }
                }
            })
        }
    </script>
</head>
<body>
    <div class="video-container">
        <video id="video" autoplay muted controls playsinline preload="auto" src="https://myserver.com/CameraName/hls/stream.m3u8">
            <source src="https://myserver.com/CameraName/hls/stream.m3u8" type="application/vnd.apple.mpegurl">
        </video>
    </div>

    <script>
        $(document).ready(function () {
            const video = document.getElementById('video');
            video.addEventListener('loadedmetadata', function() {
                video.play();
            });
        })
    </script>
</body>
</html>

4. 验证修复效果

  1. 重启Nginx服务:sudo systemctl restart nginx
  2. 停止原FFmpeg推流进程,用修改后的命令重新推流
  3. 用mediastreamvalidator重新检测HLS流,确认没有404错误和时间戳异常
  4. 在iPhone上测试播放,检查是否能正常加载、时间戳显示正确

内容的提问来源于stack exchange,提问作者Yllier123

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 15:24:40