HTML5 audio duration for mp3 is always Infinity
html, html5-audio, php, zend-framework2
Solution
I finally found my solution here. I had to slightly change the code, and here are the changes. Now, if I can fully support multiple ranges, it'll be complete.
(I'm sharing this because all I see are incomplete answers. Hopefully, it will also help someone too.)
$file = $basePath . $media->getFileName();
$fileSize = filesize($file);
$fileTime = date('r', filemtime($file));
$fileHandle = fopen($file, 'r');
$rangeFrom = 0;
$rangeTo = $fileSize - 1;
$etag = md5(serialize(fstat($fileHandle)));
$cacheExpires = new \DateTime();
if (isset($_SERVER['HTTP_RANGE'])) {
if (!preg_match('/^bytes=\d*-\d*(,\d*-\d*)*$/i', $_SERVER['HTTP_RANGE'])) {
$statusCode = 416;
} else {
$ranges = explode(',', substr($_SERVER['HTTP_RANGE'], 6));
foreach ($ranges as $range) {
$parts = explode('-', $range);
$rangeFrom = intval($parts[0]); // If this is empty, this should be 0.
$rangeTo = intval($parts[1]); // If this is empty or greater than than filelength - 1, this should be filelength - 1.
if (empty($rangeTo)) $rangeTo = $fileSize - 1;
if (($rangeFrom > $rangeTo) || ($rangeTo > $fileSize - 1)) {
$statusCode = 416;
} else {
$statusCode = 206;
}
}
}
} else {
$statusCode = 200;
}
if ($statusCode == 416) {
$response = $this->getResponse();
$response->setStatusCode(416); // HTTP/1.1 416 Requested Range Not Satisfiable
$response->addHeaderLine('Content-Range', "bytes */{$fileSize}"); // Required in 416.
} else {
fseek($fileHandle, $rangeFrom);
set_time_limit(0); // try to disable time limit
$response = new Stream();
$response->setStream($fileHandle);
$response->setStatusCode($statusCode);
$response->setStreamName(basename($file));
$headers = new Headers();
$headers->addHeaders(array(
'Pragma' => 'public',
'Expires' => $cacheExpires->format('Y/m/d H:i:s'),
'Cache-Control' => 'no-cache',
'Accept-Ranges' => 'bytes',
'Content-Description' => 'File Transfer',
'Content-Transfer-Encoding' => 'binary',
'Content-Disposition' => 'attachment; filename="' . basename($file) .'"',
'Content-Type' => 'audio/mpeg, audio/x-mpeg, audio/x-mpeg-3, audio/mpeg3', // $media->getFileType(),
'Content-Length' => $fileSize,
'Last-Modified' => $fileTime,
'Etag' => $etag,
'X-Pad' => 'avoid browser bug',
));
if ($statusCode == 206) {
$headers->addHeaderLine('Content-Range', "bytes {$rangeFrom}-{$rangeTo}/{$fileSize}");
}
$response->setHeaders($headers);
}
return $response;
Problem
I'm playing with my pet project and, experimenting with mp3 "streaming" with PHP, I always get the audio duration to return `Infinity`. I've try to search in many places, unfortunately, I can't seem to find the root problem. Note that I had to put that project aside for a while, and everything worked fine before. When I restarted working on the project, I did some UI refactor and found out about this "bug". Can anyone spot something wrong with my code? Context : this is in a controller (ZF2) action, where `$media` is a POPO model. ``` $file = $basePath . $media->getFileName(); $fileSize = filesize($file); $fileHandle = fopen($file, 'r'); $etag = md5(serialize(fstat($fileHandle))); $cacheExpires = new \DateTime(); // TODO : implement ranges... if ( isset($_SERVER['HTTP_RANGE']) ) { // find the requested range // this might be too simplistic, apparently the client can request // multiple ranges, which can become pretty complex, so ignore it for now preg_match('/bytes=(\d+)-(\d+)?/', $_SERVER['HTTP_RANGE'], $matches); $rangeFrom = intval($matches[1]); $rangeTo = intval($matches[2]); $statusCode = 206; } else { $rangeFrom = 0; $rangeTo = $fileSize; $statusCode = 200; } fseek($fileHandle, $rangeFrom); set_time_limit(0); // try to disable time limit $response = new Stream(); $response->setStream($fileHandle); $response->setStatusCode($statusCode); $response->setStreamName(basename($file)); $headers = new Headers(); $headers->addHeaders(array( 'Pragma' => 'public', 'Expires' => $cacheExpires->format('Y/m/d H:i:s'), 'Cache-Control' => 'no-cache', 'Accept-Ranges' => 'bytes', 'Content-Description' => 'File Transfer', 'Content-Transfer-Encoding' => 'binary', 'Content-Disposition' => 'attachment; filename="' . basename($file) .'"', 'Content-Type' => 'audio/mpeg, audio/x-mpeg, audio/x-mpeg-3, audio/mpeg3', // $media->getFileType(), 'Content-Range' => "bytes {$rangeFrom}-{$rangeTo}/{$fileSize}", 'Content-Length' => $fileSize, 'Etag' => $etag, 'X-Pad' => 'avoid browser bug', )); $response->setHeaders($headers); return $response; ``` I have tried playing with almost every HEADER fields (even removing the ranges), but the browser always gets `isFinite(audio.duration) === false`. Everything else seems to work, including playback, flawlessly.