Intereting Posts
Невозможно получить данные изображения из таблицы и отображаемого изображения PHP и MySQL: использование «NOT NULL AUTO_INCREMENT» на таблице, которая усекает Symfony2 / Doctrine: SQL to DQL для построителя запросов в репозитории, чтобы создать форму поиска Как выполнить поиск символа в любом порядке (12 букв, из которых 6 должно быть слово) с PHP? Почему по умолчанию строковые функции PHP не являются многобайтовыми? как заполнить при выпадающем основании значение из другого выпадающего списка в php и jquery? Запомнить? 24-часовое приложение для Android-приложений с веб-сервером PHP Safari 5.1 in не позволяет источнику <video> обнаруживать переменные $ _SESSION Код Regex / php, чтобы проверить, является ли URL-адресом короткий URL-адрес Получить или переназначить сеанс пользователя из ip и user-agent Переходы Laravel: класс «не найден» Как установить флажок с несколькими массивами с форматами html Как я могу перенести PHP preg_split в Java для особого случая unserializing значения в ADODB? Обновить таблицу mysql с помощью запроса select из другой базы данных

Safari не показывает длительность mp3, поданного с php, правильно

Исходный вопрос Я обслуживаю mp3-файл с помощью действия контроллера ZF2. Это прекрасно работает во всех браузерах, кроме Safari на OS X и iPhone / iPad. Звук воспроизводится, но продолжительность отображается только как NaN: NaN, тогда как в каждом другом браузере отображается правильная продолжительность.

Я просмотрел все потоки на SO, говоря о той же проблеме, и похоже, что это имеет какое-то отношение к заголовкам ответов и заголовкам Content-Range и Accept-Ranges, в частности. Я пробовал все разные комбинации, но до сих пор безуспешно – Safari по-прежнему отказывается отображать длительность правильно.

Соответствующий фрагмент кода выглядит следующим образом:

$path = $teaserAudioPath . DIRECTORY_SEPARATOR . $teaserFile; $fp = fopen($path, 'r'); $etag = md5(serialize(fstat($fp))); fclose($fp); $fsize = filesize($path); $shortlen = $fsize - 1; $response->setStatusCode(Response::STATUS_CODE_200); $response->getHeaders() ->addHeaderLine('Pragma', 'public') ->addHeaderLine('Expires', -1) ->addHeaderLine('Content-Type', 'audio/mpeg, audio/x-mpeg, audio/x-mpeg-3, audio/mpeg3') ->addHeaderLine('Content-Length', $fsize) ->addHeaderLine('Content-Disposition', 'attachment; filename="teaser.mp3"') ->addHeaderLine('Content-Transfer-Encoding', 'binary') ->addHeaderLine('Content-Range', 'bytes 0-' . $shortlen . '/' . $fsize) ->addHeaderLine('Accept-Ranges', 'bytes') ->addHeaderLine('X-Pad', 'avoid browser bug') ->addHeaderLine('Cache-Control', 'no-cache') ->addHeaderLine('Etag', $etag); $response->setContent(file_get_contents($path)); return $response; 

Игрок (я использую mediaelementjs) выглядит так в Safari: Обратите внимание на NaN: NaN

Я также попытался интерпретировать заголовок запроса HTTP_RANGE на основе другого примера, например:

  $fileSize = filesize($path); $fileTime = date('r', filemtime($path)); $fileHandle = fopen($path, '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($path)); $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($path) .'"', 'Content-Type' => 'audio/mpeg', // $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); } fclose($fileHandle); 

Это все равно дает мне такой же результат в Safari. Я даже попытался использовать основные функции PHP вместо объекта ZF2 Response для визуализации ответа, используя вызовы header () и readfile (), но это тоже не работает.

Любые идеи о том, как решить эту проблему, приветствуются.

Edit Как было предложено @MarcB, я сравнивал заголовки ответов двух запросов. Первый запрос – это действие PHP, служащее файлами данных mp3, а второе – когда я просматриваю один и тот же mp3-файл напрямую. Сначала заголовки были не совсем одинаковыми, но я изменил PHP-скрипт, чтобы соответствовать заголовкам прямой загрузки, см. Скриншоты Firebug ниже:

  1. Заголовки ответов, обслуживаемые PHP: введите описание изображения здесь

  2. Ответные заголовки прямая загрузка: введите описание изображения здесь

Как вы видите, они точно такие же, за исключением заголовка «Дата», но это потому, что между запросами было около полутора минут. Тем не менее Safari утверждает, что это прямая трансляция, когда я пытаюсь передать файл из PHP-скрипта, и поэтому аудиоплеер все еще показывает NaN в течение общего времени, когда я загружаю его таким образом. Есть ли способ сказать Safari просто загрузить весь файл и просто доверять мне, когда я говорю, что это не прямая трансляция?

Также может ли быть, что Safari отправляет разные заголовки запросов, и, следовательно, заголовки ответов также различны? Я обычно делаю свою отладку в Firefox с помощью Firebug. Когда я открываю URL-адрес файла MP3 в Safari, например, я не могу открыть диалоговое окно Web Inspector. Есть ли другой способ просмотреть, какие заголовки отправляются и принимаются Safari?

Изменить 2 Теперь я использую простую функцию потока, реализующую запросы диапазона. Это, похоже, работает на моей dev-машине даже в Safari, но не на реальном VPS-сервере, где работает сайт.

Функция, которую я использую сейчас (любезно предоставлена ​​другим SO-er, не помню точной ссылки):

 private function stream($file, $content_type = 'application/octet-stream', $logger) { // Make sure the files exists, otherwise we are wasting our time if (!file_exists($file)) { $logger->debug('File not found'); header("HTTP/1.1 404 Not Found"); exit(); } // Get file size $filesize = sprintf("%u", filesize($file)); // Handle 'Range' header if (isset($_SERVER['HTTP_RANGE'])) { $range = $_SERVER['HTTP_RANGE']; $logger->debug('Got Range: ' . $range); } elseif ($apache = apache_request_headers()) { $logger->debug('Got Apache headers: ' . print_r($apache, 1)); $headers = array(); foreach ($apache as $header => $val) { $headers[strtolower($header)] = $val; } if (isset($headers['range'])) { $range = $headers['range']; } else $range = FALSE; } else $range = FALSE; // Is range if ($range) { $partial = true; list ($param, $range) = explode('=', $range); // Bad request - range unit is not 'bytes' if (strtolower(trim($param)) != 'bytes') { header("HTTP/1.1 400 Invalid Request"); exit(); } // Get range values $range = explode(',', $range); $range = explode('-', $range[0]); // Deal with range values if ($range[0] === '') { $end = $filesize - 1; $start = $end - intval($range[0]); } else if ($range[1] === '') { $start = intval($range[0]); $end = $filesize - 1; } else { // Both numbers present, return specific range $start = intval($range[0]); $end = intval($range[1]); if ($end >= $filesize || (! $start && (! $end || $end == ($filesize - 1)))) $partial = false; // Invalid range/whole file specified, return whole file } $length = $end - $start + 1; } // No range requested else $partial = false; // Send standard headers header("Content-Type: $content_type"); header("Content-Length: $filesize"); header('X-Pad: avoid browser bug'); header('Accept-Ranges: bytes'); header('Connection: Keep-Alive"'); // send extra headers for range handling... if ($partial) { header('HTTP/1.1 206 Partial Content'); header("Content-Range: bytes $start-$end/$filesize"); if (! $fp = fopen($file, 'rb')) { header("HTTP/1.1 500 Internal Server Error"); exit(); } if ($start) fseek($fp, $start); while ($length) { set_time_limit(0); $read = ($length > 8192) ? 8192 : $length; $length -= $read; print(fread($fp, $read)); } fclose($fp); } // just send the whole file else readfile($file); exit(); } 

Затем это вызывается в действии контроллера:

  $path = $teaserAudioPath . DIRECTORY_SEPARATOR . $teaserFile; $fsize = filesize($path); $this->stream($path, 'audio/mpeg', $logger); 

Я добавил некоторые протоколирования для целей отладки, и разница, похоже, находится в заголовках запросов. На моей локальной машине dev, где она работает, я получаю это в журнале:

 2014-03-09T18:01:17-07:00 DEBUG (7): Got Range: bytes=0-1 2014-03-09T18:01:18-07:00 DEBUG (7): Got Range: bytes=0-502423 2014-03-09T18:01:18-07:00 DEBUG (7): Got Range: bytes=131072-502423 

На VPS, где он не работает, я получаю следующее:

 2014-03-09T18:02:25-07:00 DEBUG (7): Got Range: bytes=0-1 2014-03-09T18:02:29-07:00 DEBUG (7): Got Range: bytes=0-1 2014-03-09T18:02:35-07:00 DEBUG (7): Got Apache headers: Array ( [Accept] => */* [Accept-Encoding] => identity [Connection] => close [Cookie] => __utma=71101845.663885222.1368064857.1368814780.1368818927.55; _nsz9=385E69DA4D1C04EEB22937B75731EFEF7F2445091454C0AEA12658A483606D07; PHPSESSID=c6745c6c8f61460747409fdd9643804c; _ga=GA1.2.663885222.1368064857 [Host] => <edited out> [Icy-Metadata] => 1 [Referer] => <edited out> [User-Agent] => Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_1) AppleWebKit/537.73.11 (KHTML, like Gecko) Version/7.0.1 Safari/537.73.11 [X-Playback-Session-Id] => 04E79834-DEB5-47F6-AF22-CFDA0B45B99F ) 

Так или иначе, на реальном сервере никогда не выполняется первоначальный запрос для первых двух байтов, который Safari использует для определения того, поддерживает ли сервер запросы диапазона запросов (дважды), но запрос диапазона для реальных данных никогда не выполняется. Вместо этого я получаю кучу странных заголовков запросов, возвращенных вызовом apache_request_headers () в функции потока. Я не получаю это на своей локальной машине dev, которая также запускает Apache.

Любые идеи были бы очень благодарны, действительно вытащили мои волосы здесь.

Related of "Safari не показывает длительность mp3, поданного с php, правильно"

Сегодня вечером я потратил некоторое время на аналогичную проблему – звуковые теги, которые отлично работают на большинстве браузеров, но не имеют должного прогресса в Safari. Я нашел решение, которое работает для меня, надеюсь, это сработает и для вас.

Я также прочитал другие вопросы о подобных проблемах, и все они говорили о работе с заголовком Range. Есть несколько фрагментов, плавающих вокруг этой цели, чтобы справиться с заголовком Range. Я нашел короткую (ish) функцию на github, которая работает для меня. https://github.com/pomle/php-serveFilePartial

Однако мне пришлось сделать одно изменение в файле. В строке 38:

 header(sprintf('Content-Range: bytes %d-%d/%d', $byteOffset, $byteLength - 1, $fileSize)); 

Я сделал небольшую модификацию (удалил -1)

 header(sprintf('Content-Range: bytes %d-%d/%d', $byteOffset, $byteLength, $fileSize)); 

Я отправил вопрос в github только сейчас, объясняя, почему я сделал это изменение. То, как я нашел эту проблему, интересно: похоже, что Safari не доверяет серверу, когда он говорит, что может предоставить частичный контент: Safari (или технически Quicktime, я думаю) запрашивает байты 0-1 файла с заголовком диапазона, подобным этому :

 Range: bytes=0-1 

как его первый запрос к файлу. Если сервер возвращает весь файл – он обрабатывает файл как «поток», который не имеет начала или конца. Если сервер отвечает одним байтом из этого файла и правильными заголовками , он попросит несколько разных диапазонов этого файла (которые сильно перекрываются в том, что кажется очень неуместным). Я вижу, что вы уже это заметили, и что вы испытали, что Safari / Quicktime делает только первый запрос диапазона (0-1) и последующие «реальные» запросы диапазона. По моему мнению, это происходит из-за того, что ваш сервер не удовлетворил «удовлетворительный» ответ в диапазоне, поэтому он отказался от всей идеи запроса диапазона. Я столкнулся с этой проблемой, когда использовал связанную функцию serverFilePartial, прежде чем настраивать ее. Однако после «исправления» этой строки Safari / Quicktime, похоже, доволен первым ответом и продолжает делать последующие запросы в диапазоне, а индикатор выполнения и все появляется, и мы все хороши.

Итак, короткая история, дайте связанной библиотеке возможность и посмотрите, работает ли она для вас, как это было для меня 🙂 Я знаю, что вы уже нашли решение php, которое работает на вашей машине dev, но не на вашей производственной машине, но может быть, мое другое решение будет отличаться на вашей машине VPS? стоит попробовать.

как дополнение к информации, я считаю, что это может быть связано с этой ошибкой https://bugs.webkit.org/show_bug.cgi?id=82672

Было предложено несколько «обходных решений»:

 xhr.setRequestHeader("If-None-Match", "webkit-no-cache"); 

Надеюсь, это поможет вам или людям с подобными проблемами.

вы создаете экземпляр игрока самостоятельно через javascript? Просто попробуйте загрузить свой ресурс через

 player.setSrc("http://www.xxx.de/controller/action"); 

и слушать «canplay» – событие, если вы хотите играть напрямую:

 player.addEventListener("canplay", function(){ this.play(); });