Flutter audioplayers插件在iPhone/Web端无法获取音频时长
问题根因
iOS全系列浏览器、桌面Safari(包括Flutter Web在这些环境的运行时,基于WebKit内核)加载音频时,不会先下载完整文件,会先发起小范围的HTTP Range请求,只读取音频头部的元数据块来计算时长。
你当前的代理逻辑没有正确实现标准Range响应规则:无论客户端是否发起Range请求,都固定返回全量文件+强制写入覆盖全文件的Content-Range头,不符合WebKit内核对分段响应的校验规则,内核无法正确读取元数据,自然拿不到音频时长。安卓、桌面Chrome等环境容错性更强,即使Range响应不规范也能通过下载部分文件解析时长,所以不会复现问题。
修复步骤
- 移除无条件返回全量
Content-Range头的逻辑,仅在客户端携带Range请求头时返回分段响应 - 分段请求场景下必须返回
206 Partial Content状态码,Content-Length设置为当前返回分段的实际长度,而非整个文件的大小 - 非分段的普通请求场景下,返回
200 OK状态码+完整文件长度,不要携带Content-Range头 - 确保返回
Accept-Ranges: bytes头,明确告知客户端服务端支持分段请求
修正后参考代码
function printAudio($audioID) { $s3FileUrl = "my_file_url"; header_remove(); // 建议替换为AWS S3 SDK的headObject方法获取源文件信息,比curl/GET请求效率更高 $fileMeta = getS3FileMeta($s3FileUrl); $fileSize = $fileMeta['size']; $mimeType = $fileMeta['mime_type']; $etag = $fileMeta['etag']; $lastModified = $fileMeta['last_modified']; // 公共响应头 header("Accept-Ranges: bytes"); header("Content-Type: " . $mimeType); header("ETag: " . $etag); header("Last-Modified: " . $lastModified); $rangeHeader = $_SERVER['HTTP_RANGE'] ?? ''; if (preg_match('/^bytes=(\d*)-(\d*)$/', trim($rangeHeader), $rangeMatch)) { $start = $rangeMatch[1] === '' ? 0 : (int)$rangeMatch[1]; $end = $rangeMatch[2] === '' ? $fileSize - 1 : (int)$rangeMatch[2]; $end = min($end, $fileSize - 1); if ($start > $end) { header("HTTP/1.1 416 Requested Range Not Satisfiable"); header("Content-Range: bytes */$fileSize"); exit; } $segmentLength = $end - $start + 1; header("HTTP/1.1 206 Partial Content"); header("Content-Length: " . $segmentLength); header("Content-Range: bytes $start-$end/$fileSize"); // 仅输出请求的分段内容,不要返回整个文件 outputFileSegment($s3FileUrl, $start, $segmentLength); } else { header("HTTP/1.1 200 OK"); header("Content-Length: " . $fileSize); readfile($s3FileUrl); } exit; } // 获取S3源文件元信息,建议替换为S3 SDK对应方法 function getS3FileMeta($url) { $ch = curl_init($url); curl_setopt_array($ch, [ CURLOPT_NOBODY => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HEADER => true, CURLOPT_FOLLOWLOCATION => true ]); curl_exec($ch); $info = curl_getinfo($ch); curl_close($ch); return [ 'size' => $info['download_content_length'], 'mime_type' => $info['content_type'], 'etag' => $info['etag'] ?? '', 'last_modified' => $info['last_modified'] ?? gmdate('D, d M Y H:i:s') . ' GMT' ]; } // 输出文件指定分段内容 function outputFileSegment($url, $start, $length) { $ch = curl_init($url); curl_setopt_array($ch, [ CURLOPT_RANGE => "$start-" . ($start + $length - 1), CURLOPT_BINARYTRANSFER => true, CURLOPT_FOLLOWLOCATION => true ]); curl_exec($ch); curl_close($ch); }
额外可选方案
如果不想自行维护代理层的Range请求逻辑,可以直接生成S3预签名URL传给audioplayers插件,S3原生符合Range请求规范,不需要额外处理即可在所有环境正常获取音频时长。
注意:HTTP响应头本身大小写不敏感,之前尝试修改头的大小写没有效果是正常的,不需要在这部分浪费时间。
内容的提问来源于stack exchange,提问作者Adrián Pastoriza
相关产品推荐
相关产品推荐

