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

PHP sleep()函数致Chrome流媒体无法拖动的问题及优化方案

问题与修复方案

问题背景

下面的PHP代码返回的WebM音频在Chrome中可以正常拖动进度条:

header("Content-Type: audio/webm");
header("Accept-Ranges: bytes");
header('X-Accel-Buffering: no');
header('Transfer-Encoding: chunked');

$path = "record.webm";
readfile($path);    
die();

但只要在readfile后加个sleep(1),下载完成的音频就会在Chrome中失去进度拖动能力:

header("Content-Type: audio/webm");
header("Accept-Ranges: bytes");
header('X-Accel-Buffering: no');
header('Transfer-Encoding: chunked');

$path = "record.webm";
readfile($path);
sleep(1);
die();

实际场景是要流式传输正在录制中的WebM音频,我的实现代码如下,但同样存在音频无法拖动的问题,不想用「录制完成后再发起完整下载」的JS方案,求最优修复:

header("Accept-Ranges: bytes");
header("Cache-Control: max-age=0, must-revalidate, private");
header('X-Accel-Buffering: no');
header('Transfer-Encoding: chunked');

$filesize = filesize($path);
$chunkSize = 8192;
$cursor = 0;
$file = fopen($path, 'r');

while($filesize > $cursor) {
    $chunk = fread($file, $chunkSize);
    $cursor += $chunkSize;

    echo $chunk;
    flush();

    if ($filesize < $cursor + $chunkSize) { //检查是否接近文件末尾
        sleep(5); //等待新的录制数据写入
        clearstatcache(true, $path);
        $filesize = filesize($path);
    }
}

fclose($file);

die();

问题根源

Chrome能拖动WebM音频进度条,核心是需要文件包含完整的索引信息(WebM的Cues元素)。而常规的WebM录制逻辑中,索引是在录制结束后才写入文件尾部的:

  1. 流式传输时,浏览器先收到的是没有索引的音频数据,无法解析完整时长,自然不能拖动进度条
  2. 加sleep()后,连接延迟关闭,浏览器会认为数据还在传输,最终接收到的文件可能因为连接关闭时机问题,导致尾部索引未被正确识别,或者文件被截断

修复方案

1. 录制时实时生成WebM索引(最优)

让录制程序边录边写入索引,而不是等到最后才补写。比如用FFmpeg录制时,添加参数:

ffmpeg -i [输入源] -movflags frag_keyframe+empty_moov+default_base_moof output.webm

这种参数生成的是分段式WebM,每一段都带有独立索引,浏览器流式接收时可以逐段解析,直接支持进度拖动。

2. 修正流式传输逻辑,确保文件完整性

如果没法改录制逻辑,调整PHP代码:

  • 必须明确发送Content-Type: audio/webm头,浏览器需要准确识别文件类型才能解析索引
  • 不要无限等待文件增长,设置最大等待次数,确保文件不再更新时立即结束传输,让浏览器拿到当前完整的文件片段(包含已写入的索引)

修改后的代码示例:

header("Content-Type: audio/webm"); // 关键:明确指定音频类型
header("Accept-Ranges: bytes");
header("Cache-Control: max-age=0, must-revalidate, private");
header('X-Accel-Buffering: no');
header('Transfer-Encoding: chunked');

$path = "record.webm";
$chunkSize = 8192;
$file = fopen($path, 'r');
$lastFileSize = filesize($path);
$maxWaitAttempts = 3; // 最多等待3次,避免无限阻塞
$waitCount = 0;

while (true) {
    // 读取当前文件所有可用数据
    while (!feof($file)) {
        $chunk = fread($file, $chunkSize);
        echo $chunk;
        flush();
    }

    // 检查文件是否有新数据写入
    clearstatcache(true, $path);
    $currentSize = filesize($path);
    
    if ($currentSize === $lastFileSize) {
        $waitCount++;
        if ($waitCount >= $maxWaitAttempts) {
            break; // 文件不再增长,结束传输
        }
        sleep(2);
    } else {
        $lastFileSize = $currentSize;
        $waitCount = 0;
    }
}

fclose($file);
die();

3. 支持HTTP范围请求(断点续传)

当用户拖动进度条时,Chrome会发送Range请求,请求文件的特定字节范围。只要PHP代码能处理这个请求,返回对应范围的内容(尤其是包含索引的文件尾部),浏览器就能获取完整时长信息,支持拖动。

核心处理逻辑:

$fileSize = filesize($path);
if (isset($_SERVER['HTTP_RANGE'])) {
    // 解析Range请求
    preg_match('/bytes=(\d+)-(\d+)?/', $_SERVER['HTTP_RANGE'], $matches);
    $start = intval($matches[1]);
    $end = isset($matches[2]) ? intval($matches[2]) : $fileSize - 1;
    
    // 发送206响应头
    header('HTTP/1.1 206 Partial Content');
    header("Content-Range: bytes $start-$end/$fileSize");
    header("Content-Length: " . ($end - $start + 1));
    
    // 定位到起始位置并读取内容
    fseek($file, $start);
    while ($start <= $end) {
        $readSize = min($chunkSize, $end - $start + 1);
        $chunk = fread($file, $readSize);
        echo $chunk;
        flush();
        $start += $readSize;
    }
    fclose($file);
    die();
}

将这段逻辑整合到现有代码中,即可处理浏览器的进度拖动请求。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 19:35:03