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录制逻辑中,索引是在录制结束后才写入文件尾部的:
- 流式传输时,浏览器先收到的是没有索引的音频数据,无法解析完整时长,自然不能拖动进度条
- 加
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
相关产品推荐
相关产品推荐

