Android Media Player - как отключить запрос диапазона? (прервано потоковое аудио на Nexus 7)
У меня есть приложение для потокового аудио, которое запускает локальный прокси-сервер. Локальный прокси-сервер устанавливает http-соединение с потоковым интернет-источником, локально получает и буферизует потоковые данные. Затем внутри приложения я использую MediaPlayer для подключения к локальному прокси-серверу, используя метод
mediaPlayer.setDataSource(...); // the url of the local proxy server
Все было хорошо (с большим количеством устройств Android и разных версий ОС - 1.5...4.0), до выхода Nexus 7.
В Nexus 7 медиаплеер отказывается воспроизводить источник с локального прокси-сервера.
Когда я взглянул на журналы, кажется, что MediaPlayer использует внутренние запросы диапазона. Мой локальный прокси-сервер не справляется с этим. Возвращает HTTP/1.0 200 OK и данные. Тем не менее, медиаплеер не любит это и выдает исключение:
Caused by: libcore.io.ErrnoException
?:??: W/?(?): [ 07-18 00:08:35.333 4962: 5149 E/radiobee ]
?:??: W/?(?): : sendto failed: ECONNRESET (Connection reset by peer)
?:??: W/?(?): at libcore.io.Posix.sendtoBytes(Native Method)
?:??: W/?(?): at libcore.io.Posix.sendto(Posix.java:146)
?:??: W/?(?): at libcore.io.BlockGuardOs.sendto(BlockGuardOs.java:
?:??: W/?(?): at libcore.io.IoBridge.sendto(IoBridge.java:473)
We requested a content range, but server didn't support that. (responded with 200)
Согласно спецификациям http, если сервер отвечает HTTP / 1.0 вместо 1.1, клиент не должен запускать запрос диапазона (1.0 все равно не поддерживает),
Также, если сервер не поддерживает запрос диапазона, все будет хорошо, если он ответит 200 OK (и это то, что я делаю), но реализация MediaPlayer на Nexus 7 не нравится.
Я взглянул на эту тему: HTTP: Как мне ответить на "Range: bytes=", когда Range не поддерживается?
, где они утверждают, что ответ с 200 OK должен быть достаточно хорошим, но, к сожалению, это не помогает.
Я не уверен, является ли это проблемой с Jelly Bean, или проблемой с реализацией Nexus 7, но это все еще проблема для меня, которую я должен решить.
Опять же, нет никаких запросов диапазона на множестве других устройств Android, использующих то же самое приложение. По какой-то причине эти запросы диапазона происходят сейчас на Nexus 7. (Это может происходить и на других устройствах Android, но, опять же, никогда не случалось со мной до сих пор).
Любой возможный способ отключить диапазон запросов для MediaPlayer?
Если их нет, кто-нибудь может предложить быстрое исправление логики моего прокси-сервера (что именно он должен вернуть, если получит этот запрос диапазона?), Без изменения другой логики, если это возможно?
Похоже, может быть, мне нужно вернуть что-то вроде "HTTP/1.0 206 OK\r\n Частое содержимое \r\n\r\n", но, вероятно, должно быть какое-то значение в конце Частичное содержимое - не уверен, что это должно будь этим.
Ваша помощь будет оценена.
Спасибо..
3 ответа
Мы наконец решили это чистым способом. В нашем случае мы имеем полный контроль над потоковым сервером, но я думаю, вы могли бы сделать это и с локальным прокси. поскольку Build.VERSION_CODES.ICE_CREAM_SANDWICH
можно установить заголовки для MediaPlayer
объект. Поскольку наше приложение позволяет пользователю искать внутри аудиопотока, мы реализовали это Range
заголовок на клиенте. Если сервер отвечает с правильными заголовками, MediaPlayer
не будет пытаться несколько раз запросить поток.
Вот как выглядят заголовки наших серверов для клиента Android:
Content-Type: audio/mpeg
Accept-Ranges: bytes
Content-Length: XXXX
Content-Range: bytes XXX-XXX/XXX
Status: 206
Важной частью является 206
код состояния (частичное содержание). Если вы не отправите этот заголовок, клиент Android попытается повторно запросить источник, несмотря ни на что.
Если ваш плеер не позволяет искать в потоке, вы можете просто установить Range
заголовок к 0-some arbitrary large number
,
Я работаю над крупномасштабным приложением для потоковой передачи аудио (которое использует HTTP-прокси для локального хоста для потоковой передачи звука на MediaPlayer), и столкнулся с этой проблемой, как только у меня появилось устройство JellyBean в Google I/O 2012.
При тестировании качества нашего приложения на разных устройствах (и с информацией, полученной из наших автоматических журналов аварийного завершения и пользовательских журналов регистрации), мы заметили, что некоторые реализации MediaPlayer вели себя так, как я бы назвал, хаотичным (а иногда и просто психотическим) способом.
Не вдаваясь в подробности, это то, что я видел: некоторые реализации делают несколько запросов (иногда 5+) для одного и того же URL. Все эти запросы немного отличались друг от друга тем, что каждый был для другого диапазона байтов (обычно для первых или последних 128 байтов). Был сделан вывод, что MediaPlayer пытается найти встроенные метаданные, после чего через некоторое время он сдается и просто делает обычный запрос, не относящийся к диапазону.
Это не то, что делает стандартная реализация JellyBean MediaPlayer, это просто пример причудливости и общей фрагментации медиа-фреймворка на Android. Тем не менее, решение вышеуказанной ситуации было также решением проблемы JellyBean, а именно:
Пусть ваш локальный прокси ответит кусковой кодировкой
Это заменит заголовок Content-Length на заголовок Tranfer-Encoding: chunked. Это означает, что запрашивающий клиент не будет знать общую длину ресурса и, следовательно, не сможет сделать запрос диапазона, ему просто придется иметь дело с порциями по мере их поступления.
Как я уже сказал, это взлом, но это работает. Это не без его побочных эффектов: прогресс в буфере медиаплеера будет некорректным, поскольку он не знает длину звука (является одним из них), чтобы обойти это, вам придется использовать свои собственные вычисления буферизации (вы потоковое откуда-то через ваш прокси в MediaPlayer, верно? Так вы будете знать общую длину).
Когда вы ищете или пропускаете, или соединение потеряно, и MediaPlayer продолжает повторно подключаться к прокси-серверу, вы должны отправить этот ответ со статусом 206 после того, как получите запрос и диапазон (int) от клиента.
String headers += "HTTP/1.0 206 OK\r\n";
headers += "Content-Type: audio/mpeg\r\n";
headers += "Accept-Ranges: bytes\r\n";
headers += "Content-Length: " + (fileSize-range) + "\r\n";
headers += "Content-Range: bytes "+range + "-" + fileSize + "/*\r\n";
headers += "\r\n";