Android MediaPlayer. Розширюємо можливості з допомогою проксі


Реалізація проксі для стандартного компонента MediaPlayer несе в собі набагато більше переваг, ніж може здатися на перший погляд. У цій статті докладно розповідається, як це все працює і про перспективи розвитку подібної технології.

Довгий час я шукав найбільш вдалу реалізацію медіаплеєра під андройд для свого програми Media Library. Я відчував ExoPlayer. У нього немає банальної підтримки flv. На що розробники сказали, що немає часу на додавання підтримки цього формату. Далі я став використовувати Vitamio. І Vitamio показався спершу більш менш підходящим рішенням, але поспілкувавшись з його розробником Crossle я дізнався, що допомоги від нього не чекати. Баги він не виправляє і взагалі вже майже ніхто не займається цим проектом. Так як багів було дуже багато, я повернувся на стандартний плеєр. Він мені видався найбільш швидким і безглючною. Звичайно в ньому немає стільки функцій, як в ExoPlayer або Vitamio. Тому я почав вивчати, як додати їх самостійно.
VideoView користуватися я не став і зробив кастомный клас плеєра. У нього я додав багато своїх Event-ів на всі випадки життя. Плеєр заснований на textureView. Шукав спосіб, як змусити плеєр грати у фоновому режимі і при пересоздании Activity. З SurfaceView такого не вийшло...TextureView — працює відмінно. Крути, крути… а музика грає. В сервіс переводити плеєр я так і не знайшов причини. А навіщо, коли і так все добре працює. Як тільки причина з'явитися — сервіс буде створений.

Завдання було дві простих — створити кеш для плеєра і змусити його грати з FTP. Про це мене попросив один користувач мого плеєра. Йому хочеться слухати музику зі свого NASа, який підтримує протокол FTP.
Як програмісту мені зрозуміло, що спочатку треба було написати через проксі і проксі організувати конвертацію HTTP<>FTP запитів і передачу даних. Що я і зробив у своєму проекті ImmortalPlayer.

У перспективі цю проксі можна використовувати з VItamio (у якого з кешем все не так добре) і тоді вийде зовсім комбаїн, який грає всі формати і підтримує всі протоколи), але не рекомендую так робити. Так як різноманітність форматів — це не тільки користь, але і зло. Для плеєра потрібна підтримка основних форматів і деяких другорядних, а не всіх підряд. Це призведе до чистоти і підвищення якості матеріалу у користувачів. Крім цього підтримка форматів повинна бути на системному рівні, вбудованими кодеками, а не на рівні плеєра. Vitamio — має безліч багів з розсинхронізацією звуку і відео, низьку продуктивність і збільшує розмір програми apk на 10-15 мегабайт.

Ключові особливості:

Визначаємо права доступу:
AndroidManifest.xml
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAl_storage" />
<uses-permission android:name="android.permission.WAKE_LOCK" />

Wake_Lock — для того що б плеєр не припиняв грати, коли пристрій знаходиться в сплячому режимі.
WRITE_EXTERNAL_STORAGE — для запису кеша на зовнішній носій пристрою.

MainActivity.java
proxy = new HttpGetProxy();
proxy.setPaths("/ProxyBuffer", videoUrl, BUFFER_SIZE, NUM_FILES, getActivity().getApplicationContext(), false, ftplogin, ftppass, false);
String proxyUrl = proxy.getLocalURL();
textureView.setVideoPath(proxyUrl);

Присвоюємо новий екземпляр класу змінної proxy. І задаємо параметри:
proxy.setPaths(папка куди зберігати кеш, сама посилання на файл в інтернеті, розмір кешу, кількість файлів в кеші, контекст, видаляти чи не докаченние файли?, логін ФТП, пароль ФТП, режим сумісності FTP?);
Отримуємо шлях проксі для плеєра String proxyUrl = proxy.getLocalURL();
Стартуємо плеєр textureView.setVideoPath(proxyUrl);

Проксі стартує вже з рядка proxy = new HttpGetProxy();. Є команда на зупинку proxy.stopProxy(). По цій команді закривається локальний сокет, що очікує запитів від плеєра і після цього завершується Thread проксі. Грубо, але поки не знайшов більш м'якого виходу.

Не складно здогадатися, як зробити проксі тільки для читання. Що б нічого не писалося на карту пам'яті. Поки це не реалізовано, але можна поставити по нулях значення BUFFER_SIZE, NUM_FILES і всі файли будуть видалятися.

Алгоритм роботи плеєра:

Написавши першу версію проксі я зіткнувся з особливостями плеєра. Не знаю з чим це пов'язано, але проблеми виникають з великими flac файлами. Плеєр не зовсім розрахований на такого роду файли і тому має ще баги в алгоритмі їх відтворення.

Типове відтворення починається з двох запитів. Чому не з одного для мене досі загадка. Тобто плеєр запитує файл цілком, читає перші 50к-100кб і скидає підключення. Запитує файл цілком вдруге і починає читати знову з початку файл. Далі запити стають з зазначенням початкового байта.
Пробував зробити двох потокову проксі і думав, що він намагається читати в двох потоках, але плеєр вперто не хотів надсилати два запиту одночасно. Тільки послідовно. Причому перше з'єднання він рве майже відразу.
Так ось це не всі проблеми. З якоїсь дивної особливо після першої порції файлу, плеєр починає читати кінець файлу. Вибирає якусь точку перед кінцем і читає до кінця. Наприклад, останні 200кб. Мабуть шукає якісь дані, які не знайшов на початку або перевіряє, чи щось. Потім продовжує читати спочатку.
Проблеми з flac файлами — це низька швидкість проксі при перемотуванні і поганий алгоритм. При перемотуванні flac плеєр шукає підходяще початок потоку. Тобто він не з будь-якого байта може почати грати. Тому робить 1-6 запитів. Мабуть в цей момент задіюються якісь таймаут, що б плеєр працював швидко, тому я збільшив швидкість роботи сокетів таймаутом 1500ms… Без таймауту сокети довго висять у бездіяльності і замість 1-6 запитів відбувається лише 1-2. Що знижує ймовірність того, що плеєр знайде початок. А якщо плеєр не знаходить початок, то видає помилку про не підтримуваному форматі. Таймаут повністю вирішує проблему зависання сокетів, але не вирішує проблему роботи алгоритму плеєра. Так як навіть без проксі при перемотуванні flac файлів плеєр періодично видає помилку «не підтримуваний формат».

Перевірено — плеєр робить однакову кількість запитів, як з проксі, так і без. З проксі запити виконуються трохи повільніше. Приблизно на 50-200мс… З-за різних перетворень і відкриття\закриття сокетів, що було максимально зменшено в останніх версіях.

Алгоритм роботи HTTP, FTP:

Саме процедура закриття сокетів або потоків забирає найбільше часу. Саме від цих процедур і можна відмовитися в деяких випадках для збільшення швидкодії.
З допомогою FTP команди RETR відбувається отримання файлу. Команда ABOR необхідна для коректного переривання передачі файлу. Так, це правильно і добре. Адже тільки деякі FTP сервера можуть працювати відразу приймаючи нову команду. Тобто без команди ABOR — відразу REST, RETR і вже отримуємо новий файл з зазначеного в REST байта. Відправка команди ABOR займає близько 100мс… Це погіршує комфорт і швидкість проксі. Для цього і створений «режим сумісності FTP». У цьому режимі відбувається відправлення команди ABOR. Як я не намагався автоматизувати вибір режиму роботи — не вийшло. Тому зробив вручну. Без команди ABOR теж повинно працювати. Просто буде відбуватися нова авторизація, замість переривання і може виникнути проблема з сесіями. Необхідно дивитися лог FTP — якщо авторизація відбувається майже кожен раз, то краще увімкнути режим.

Налаштування FTP сервера:

Особливо тестів було не багато. Точно має працювати в пасивному режимі відкритому 21 портом для авторизації 1000 портів (може менше) для передачі даних. 1000 портів повинно бути відкрито на фаєрволі і зазначено в фтп сервері. Число одночасних сесій 10. Таймаут сесії 10 хвилин. Таймаут з'єднання 1 хвилина.

Алгоритм роботи HTTP проксі:



Цифрами позначена послідовність дій.

Для FTP сервера послідовність буде трохи іншою. В проксі є ще алгоритми переходу на читання з локального файлу, якщо недоступний інтернет або немає відповіді від сервера. Місцями алгоритми дуже складні. Я максимально намагався створювати короткі конструкції. Думаю ще можна щось зменшити.
Для реалізації FTP протоколу я використовував бібліотеки Apache Commons Net. Вона підтримує і деякі інші протоколи: FTPS,FTP over HTTP (experimental),NNTP,SMTP(S),POP3(S),IMAP(S),Telnet,TFTP,Finger,Whois,rexec/rcmd/rlogin,Time (rdate) and Daytime,Echo,Discard,NTP/SNTP.
Думаю на основі тих з них, які підтримують прийом даних з віддаленого сервера і старт з певного байта можна зробити проксі для плеєра і грати мультимедіа.

Алгоритм збереження файлу:

Потребує доопрацювання, але навіть зараз працює чудово. Суть проста — якщо ви прослухали весь файл, то він перейменовується в оригінальне розширення та ім'я. Перевірка здійснюється з точністю до байта в момент закінчення читання файлу. Якщо перейменовувати не розширення, а ім'я файлу, то мультимедіа пристрою сканер зрозуміє що це якесь мультимедіа і почне його сканувати. І якщо раптом файл виявиться битим ви отримаєте 100% завантаження процесора до перезавантаження пристрою. Можливо в різних андройд системах щось по різному, але у мене на 4.4.2 так.
Проблема зараз в тому, що кешується тільки те, що запитує і читає плеєр. Потрібна організація якийсь докачки в той час, коли плеєр читає нічого, що б не навантажувати канал і припинення її без затримки для запиту плеєра. Поки не виходить вловити момент, коли плеєр нічого не читає з інтернету.

GitHub:
github.com/master255/ImmortalPlayer
Робочий приклад:
play.google.com/store/apps/details?id=com.immortalplayer

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.