You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Strapi后端MP4视频在Safari无法播放(Chrome正常,疑似CORS相关问题)

Strapi后端MP4视频在Safari无法播放(Chrome正常,疑似CORS相关问题)

我之前也踩过Safari播放Strapi托管视频的坑,这个AbortError十有八九和Safari严格的Range请求依赖、视频编码兼容性或者跨域细节有关,给你几个针对性的排查和解决方向:

一、先确认Strapi是否正确处理Range请求

Safari不像Chrome那样能容忍完整文件加载,它强制要求视频服务支持Range分段请求,如果Strapi返回的是200 OK而不是206 Partial Content,或者响应头缺少必要字段,直接就会 abort 加载。

  1. 用curl测试Range请求:

    curl -I -H "Range: bytes=0-100" "你的完整视频URL"
    

    正常的响应应该包含:

    • 状态码 206 Partial Content
    • 响应头 Accept-Ranges: bytesContent-Range: bytes 0-100/[文件总大小]
  2. 如果Strapi默认的静态文件服务没正确处理Range,你可以加个自定义中间件补全:
    src/middlewares/range-handler.js里写:

    module.exports = (strapi) => {
      return {
        initialize() {
          strapi.app.use(async (ctx, next) => {
            await next();
            if (ctx.method === 'GET' && ctx.url.includes('/uploads/') && ctx.response.status === 200) {
              const range = ctx.headers.range;
              if (range) {
                const fs = require('fs');
                const path = require('path');
                const filePath = path.join(strapi.dirs.static.public, ctx.path);
                try {
                  const fileSize = fs.statSync(filePath).size;
                  const parts = range.replace(/bytes=/, "").split("-");
                  const start = parseInt(parts[0], 10);
                  const end = parts[1] ? parseInt(parts[1], 10) : fileSize - 1;
                  const chunksize = (end - start) + 1;
                  const file = fs.createReadStream(filePath, {start, end});
                  const head = {
                    'Content-Range': `bytes ${start}-${end}/${fileSize}`,
                    'Accept-Ranges': 'bytes',
                    'Content-Length': chunksize,
                    'Content-Type': ctx.response.type,
                  };
                  ctx.response.status = 206;
                  ctx.response.set(head);
                  ctx.body = file;
                } catch (err) {
                  console.error('处理Range请求失败:', err);
                }
              }
            }
          });
        },
      };
    };
    

    然后在config/middlewares.js里把这个中间件加到strapi::cors后面。

二、检查视频文件的编码是否符合Safari标准

Safari对MP4的编码要求非常严格,必须满足:

  • 视频编码:H.264(Baseline/Main/High Profile都可)
  • 音频编码:AAC-LC
  • moov原子(视频元数据)必须在文件开头(“快启动”格式)
  1. 用ffprobe检查编码:

    ffprobe -v error -show_entries stream=codec_name,codec_type "你的本地视频文件"
    

    输出应该类似:

    [STREAM]
    codec_type=video
    codec_name=h264
    [/STREAM]
    [STREAM]
    codec_type=audio
    codec_name=aac
    [/STREAM]
    
  2. 如果moov原子在文件末尾,用ffmpeg转码修正:

    ffmpeg -i input.mp4 -movflags faststart output.mp4
    

    转完后重新上传到Strapi,这一步我当时解决了80%的Safari播放问题!

三、调整跨域和缓存的细节

  1. 把Strapi的CORS origin从*改成具体的前端域名,Safari对通配符*的跨域处理比Chrome严格:

    // config/middlewares.js
    {
      name: 'strapi::cors',
      config: {
        origin: ['http://localhost:3000'], // 不要用*
        methods: ['GET', 'HEAD', 'OPTIONS'],
        headers: ['Content-Type', 'Authorization', 'Range', 'If-Range'], // 加上If-Range
        exposeHeaders: ['Content-Length', 'Content-Range', 'Accept-Ranges'],
        credentials: false,
        keepHeaderOnError: true,
      },
    }
    
  2. 临时绕过Safari的缓存测试:
    在你的代码里给URL加个随机时间戳,彻底绕过缓存(仅测试用):

    const src = data.url ? `${getAssetUrlPrefix()}${data.url}?t=${Date.now()}` : '';
    

    如果这样能播放,说明是缓存导致的,那就在Strapi的响应头里加合适的Cache-Control:
    在自定义中间件里补充:

    ctx.set('Cache-Control', 'public, max-age=31536000, immutable');
    

四、代码层面的小调整

  1. preload="auto"改成preload="metadata",Safari的preload="auto"会提前发起Range请求,有时候会和跨域缓存冲突:

    <video 
      controls 
      crossOrigin="anonymous" 
      muted 
      playsInline 
      preload="metadata" <!-- 修改这里 -->
      ref={videoRef} 
      width="100%"
      poster={`${posterData?.url ? getAssetUrlPrefix() + posterData.url : ''}`}
    >
    
  2. 给video标签加详细的错误捕获,看看具体的错误码:

    const handleVideoError = (e) => {
      const error = e.target.error;
      console.error('Safari视频错误详情:', {
        code: error.code,
        message: error.message
      });
      // 错误码对应:MEDIA_ERR_NETWORK(网络/跨域)、MEDIA_ERR_DECODE(编码问题)等
    };
    
    // 在useEffect里绑定
    useEffect(() => {
      const videoElement = videoRef.current;
      if (!videoElement) return;
      // ... 其他监听
      videoElement.addEventListener('error', handleVideoError);
      return () => {
        // ... 其他移除监听
        videoElement.removeEventListener('error', handleVideoError);
      };
    }, []);
    

五、如果用了第三方存储(比如S3/Cloudinary)

如果你的视频不是存在Strapi本地,而是第三方存储,那问题可能在存储服务的配置:

  • 确保存储服务的CORS规则允许RangeContent-RangeIf-Range
  • 确保存储服务支持Range请求返回206状态码(比如S3默认支持,但要确认Bucket Policy允许GetObject)

建议你先从视频编码检查Range请求测试开始,这两个是Safari播放MP4最常见的坑,我当时就是因为moov原子在文件末尾,转码后直接就好了!

火山引擎 最新活动