Очень давно я ничего не писал себе в блог, но настоло время вспомнить старые добрые времена и снова заняться этим грязным делом. Наверняка многие сталкивались с такой штукой как автоплей в разного рода приложениях, когда ты доходишь до видео в фиде, и оно начинает автоматически играть. Такую штуку можно сделать разными способами, есть готовые библиотеки которые уже реализовали эти решения, есть либы которые имеют баги, ну или в моем случае мне нужно было реализовать не обычный плеер, из-за этого решения которые я находил не подходили просто мне.
Мой же пример ориентирован больше на людей которые любят собственные решения и которые хотят понимать что происходит у них в приложении и как это все работает. Опять же, сразу скажу что вариант который я использую в этой статье не мое личное решение, я его нашел на просторах Github'a, и при чем не у одного человека, по этому я не могу утверждать кому оно пренадлежит, но к сожалению оно было изобретено до меня и я могу только им пользоваться. :)
Теперь после такого долгого вступления можно и начать кодить. Для начала нужно создать пустой проект в который мы подключим наши библиотеки. Писать я нынче буду на котлине, он более так сказать модный щас, да и в принципе с ним получается на порядок меньше кода, по этому будем ориентироваться на него.
app/build.gradle
Зачем нам нужна поддержка java 8? Для того что бы мы могли использовать лямбды в нашем коде, так и красивее и выглядит более понятно без простыней реализаций интерфейсов.
По порядку нафига нам столько библиотек:
— androidx — подключается автоматически и нужен для подключения всех сдк которые нужны для работы с андроид.
— rxjava2 и rxandroid — нужны для того что бы мы могли использовать наш AutoPlayRecyclerView который написан с помощью RxJava
— exoplayer — нужен для работы с видео, с его помощью мы будем проигрывать HLS видео в нашем приложении.
— recyclerview — нужен нам для того что бы мы могли наследоваться от него и создать наш собственный RecyclerView с помощью которого будем проигрывать видео.
Далее нам нужно создать вспомогательные классы для нашего AutoPlayRecyclerView, у нас их будет 2, это ViewHolder и LinearLayoutManager.
AutoPlayLinearLayoutManager.kt
Этот класс нам нужен для нашего кастомного RecyclerView для того что бы ограничить количество активных айтемов на экране что бы не засрать память телефона и тот ушел бы в OutOfMemory, у нас наш RecyclerView будет recyclable. То есть айтемы за пределами экрана будут удаляться и реюзаться.
VideoHolder.kt
Тут мы создали отдельный холдер который у нас будет иметь три абстрактных метода через которые мы будем управлять нашим холдером в котором будут наши видео и который будет наследовать этот холдер.
AutoPlayVideoRecyclerView.java
Оооо, с чего бы начать, даже не знаю, у насв этом классе надо выделить пару важных методов:
— initView — логично что в этом методе мы инициализируем наш RecyclerView, задаем LayoutManager, говорим что нам нужно хранить максимум 5 айтемов в нашем списке, получаем высоту экрана для расчета какое видео нам нужно включать, и инициализируем ScrollListener что бы далее получать позицию на экране и включать или выключать видео.
— checkPositionHandingViewHolder — расчитывает эту позицию и по текущему VideoHolder'у он стопает видео если он за пределами зоны видимости.
— getHeightScreen — получает высоту экрана.
— createSubject — с задержкой в 300 милисекунд определяет местоположение центра экрана и включает видео которое находится в этом положении. Задержка сделанна для того что бы не нужно было играть каждое видео которое попадает в фокус, а только то на котором пользователь остановился и желает посмотреть.
— playVideo — проигрывает текущее видео, которое оказалось в фокусе у нашего createSubject().
getViewHolderCenterScreen — очевидно что расчитывает центр экрана и находит на нем VideoHolder который нужно проиграть.
— getPercentViewHolderInScreen — расчитывает насколько точно этот VideoHolder нужно играть, вохможно он не настолько в центре экрана как допустим VideoHolder перед ним или после…
— getLimitPositionInScreen — расчитывает лимиты с верху и снизу экрана, собственно он нам говорит что VideoHolder внизу экрана присутствует или нет, и так же про верхнюю часть экрана. Это offset который нужен что бы у нас играли видео не только в центре экрана но и снизу и сверху, на пример когда по центру у нас не 100% от getPercentViewHolderInScreen().
Вот такая сложная фигня этот класс, его единственного я не смог переписать на котлин, в нем просто творится адская магия, но сам факт, эта магия работает и прям очень даже не плохо, главное не тупить :) В целом этот класс у нас готов, дальше нам нужно создать плеер который мы будем использовать для отображения видео.
Для начала нам нужно создать bootstrap который будет хранить в себе все нужные параметры и настройки. Потом создадим все нужные листенеры, енамы и фабрики. А в конце уже будем создавать собственно сам плеер.
VideoPlayerQuality.kt
Нам нужен этот енам для того что бы разделить качество видосов на низкое и высокое, потому что HLS это видео поток в котором можно регулировать качество видосов, высокое качество нужно для широкоформатного стриминга, на пример в фулл скрине, или там стрим с телефона на телевизор по chromecast'у, а в списке мы будем использовать LOWEST формат для экономии памяти и батареи.
StartupTrackSelectionFactory.kt
Этот класс нам нужен для адаптивного стриминга, про который я говорил выше, в этом классе мы говорим стриму что стартовать нам нужно как можно быстрее, по этому в начале проигрыша видео, мы видео играем в низком качестве, в самом низком в котором можно, а по мере проигрывания если появляется возможность улучшить его, мы повышаем.
ExoPlayerVideoPlayerBootstrap.kt
Что же у нас тут происходит, а происходит вот что, в теле init мы определяем в каком формате нам нужно настраивать поток, и устанавливаем track selector который выбирает в каком качестве играть, в низком или в высоком. Далее создаем SimpleExoPlayer и инициализируем его и указываем что видео у нас будут играть по кругу.
— lowQualityTrackSelectorFactory и highQualityTrackSelectorFactory — нам нужны чисто для сокращения кода в init, в первом мы устанавливаем низкое качество потока, во втором высокое.
— buildMediaSource — нужен для инициализации потока который мы будем проигрывать, собственно тут мы указываем ссылку на видео которое хотим проиграть.
Так же нам понадобится листенер который будет использовать ExoPlayer для понимания статуса видео, готово ли оно для проигрывания или может оно буферится, или запаузено…
PlayerStateChangedListener.kt
Собственно метод onPlayerStateChanged и нужен, его мы и дублируем в наш плеер через интерфейс, остальные методы для нас бесполезны, ну кроме onPlayerError, но он и так выведет нам нужную инфу в случае ошибки в лог.
Теперь мы дошли наконец-то до нашего плеера, ура! Но для начала давайте посмотрим как выглядит xml у плеера.
view_video_player.xml
Здесь у нас все придельно тривиально, у нас есть PlayerView и поверх него у нас лежит ProgressBar который будет показываться при загрузке видео и будет прятаться при включении потока.
VideoPlayerView.kt
— videoHeight — метод который возвращает высоту видео, он нам нужен для установки высоты видео в айтеме в адаптере.
— init — у нас инициализирует нашу вьюху и устанавливает качество стрима — LOWEST.
— initPlayer нам нужен прежде всего для того что бы мы могли проинициализировать наш плеер когда фокус падает на айтем который пользователь захотел просмотреть. В этом методе мы инициализируем наш bootstrap класс, создаем плеер, задаем все нужные параметры — такие как:
— задаем плеер в котором нужно играть видео.
— устанавливаем что нам нужно что экран будет включен все время сколько видео будет проигрываться.
— видео будет стартовать с начала каждый раз когда будет заканчиваться.
— мы отключили контролы в плеере, по этому они не будут отображаться.
— и установили ресайз для видео что бы оно растягивало его по ширине и высоте, в том резолюшене который мы задали расчитав его в методе videoHeight.
Так же мы задали mediaSource и вызвали prepeare что бы видео начало прогружаться. И так же, кинули коллбек для onPlayerVisibleListener в холдер что у нас появился активный плеер которым дальше из адаптера мы сможем управлять, паузить, резюмить или останавливать.
Дальше идут методы playVideo, pauseVideom resumeVideo и stopVideo — они все повторяют стандартный функционал в котором мы говорим что нам делать с потоком, ну и дергаем нужные переключатели для сохранения статуса потока, что бы не запутаться.
— releasePlayer — останавливает плеер и уничтожает все его инстансы, это нам нужно для того что бы эти переменные не висели без толку если мы их уже не используем.
— onPlayerStateChanged — это наш метод из PlayerStateChangedListener, который возвращает стейт потока, в этом методе мы регулируем, когда показывать прогресс бар, когда его прятать и опять же управляем переключателем isBuffering который нужен нам для разного рода проверок в будущем.
Дальше начинается самая легкая часть, мы создадим адаптер, холдер и подключим все эти штуки в MainActivity для отображения.
Но для начала нам нужно создать модель в которую мы будем загружать ссылки на поток и названия видео.
VideosModel.kt
Обожаю котлин за это :)
ViewHolder.kt
Мы унаследовали наш ViewHolder от VideoHolder который у нас используется в нашем AutoPlayRecyclerView, и оно нам предложило создать три метода которые собственно мы и прописывали там. В videoLayout мы передаем наш плеер что бы тот мог управлять им по надобности, а в play и stop прописываем включение и выключение потока.
— init — в этом методе мы инициализируем наш колбек который будет возвращать текущий активный холдер на экране, который проигрывается.
— в методе bind мы просто отображаем что у нас находится в модельке, а это тайтл видео. И задаем высоту видео в айтеме.
item_video_view_holder.xml
Просто текст и наш плеер который мы создали, этого достаточно нам для примера как я считаю.
VideoRecyclerAdapter.kt
Адаптер у нас в полне стандартный, в нем мы просто подписываемся на наш интерфейс который возвращает нам активный плеер, и дальше у нас появляется возможность паузить видео, резюмить или останавливать вообще, на пример когда нам нужно по сворачиванию или выключению приложения сделать так что бы видео не играло в фоне.
activity_main.xml
В нашей активити у нас будет отображаться только один RecyclerView который мы написали, более нам больше ничего и не нужно.
MainActivity.kt
— onCreate — у нас инициализирует список с ссылками на потоки видео, и адаптер который будет отображать их.
— onPause, onDestroy, onStop и onResume нам нужны для сохранения жизненного цикла приложения, если пользователь свернет, развернет или выключит приложение, у нас наш поток остановится и не будет играть в фоне, и не создаст проблем при использовании нашего приложения пользователю.
Вроде бы ничего не забыл. Статья оказалась огромной просто, но в любом случае это отличный пример того как можно сделать автоплей в своем приложении.
Исходники:
GitHub
В случае с этой статьей, я просто приведу пример своего кастомного RecyclerView который я использовал для проигрывания видео в списке. Но так же хочу привести примеры уже готовых решений, таких на пример как Toro Player, который реализован на основе ExoPlayer'a и который выполняет свою работу просто отлично, но к сожалению я не смог найти ему применения у себя в проекте. Но сразу скажу что я советую его всем кто хочет сделать у себя автоплей, стандартный с одним плеером в айтеме — это то что вам нужно.
Теперь после такого долгого вступления можно и начать кодить. Для начала нужно создать пустой проект в который мы подключим наши библиотеки. Писать я нынче буду на котлине, он более так сказать модный щас, да и в принципе с ним получается на порядок меньше кода, по этому будем ориентироваться на него.
app/build.gradle
Зачем нам нужна поддержка java 8? Для того что бы мы могли использовать лямбды в нашем коде, так и красивее и выглядит более понятно без простыней реализаций интерфейсов.
По порядку нафига нам столько библиотек:
— androidx — подключается автоматически и нужен для подключения всех сдк которые нужны для работы с андроид.
— rxjava2 и rxandroid — нужны для того что бы мы могли использовать наш AutoPlayRecyclerView который написан с помощью RxJava
— exoplayer — нужен для работы с видео, с его помощью мы будем проигрывать HLS видео в нашем приложении.
— recyclerview — нужен нам для того что бы мы могли наследоваться от него и создать наш собственный RecyclerView с помощью которого будем проигрывать видео.
Далее нам нужно создать вспомогательные классы для нашего AutoPlayRecyclerView, у нас их будет 2, это ViewHolder и LinearLayoutManager.
AutoPlayLinearLayoutManager.kt
Этот класс нам нужен для нашего кастомного RecyclerView для того что бы ограничить количество активных айтемов на экране что бы не засрать память телефона и тот ушел бы в OutOfMemory, у нас наш RecyclerView будет recyclable. То есть айтемы за пределами экрана будут удаляться и реюзаться.
VideoHolder.kt
Тут мы создали отдельный холдер который у нас будет иметь три абстрактных метода через которые мы будем управлять нашим холдером в котором будут наши видео и который будет наследовать этот холдер.
AutoPlayVideoRecyclerView.java
Оооо, с чего бы начать, даже не знаю, у насв этом классе надо выделить пару важных методов:
— initView — логично что в этом методе мы инициализируем наш RecyclerView, задаем LayoutManager, говорим что нам нужно хранить максимум 5 айтемов в нашем списке, получаем высоту экрана для расчета какое видео нам нужно включать, и инициализируем ScrollListener что бы далее получать позицию на экране и включать или выключать видео.
— checkPositionHandingViewHolder — расчитывает эту позицию и по текущему VideoHolder'у он стопает видео если он за пределами зоны видимости.
— getHeightScreen — получает высоту экрана.
— createSubject — с задержкой в 300 милисекунд определяет местоположение центра экрана и включает видео которое находится в этом положении. Задержка сделанна для того что бы не нужно было играть каждое видео которое попадает в фокус, а только то на котором пользователь остановился и желает посмотреть.
— playVideo — проигрывает текущее видео, которое оказалось в фокусе у нашего createSubject().
getViewHolderCenterScreen — очевидно что расчитывает центр экрана и находит на нем VideoHolder который нужно проиграть.
— getPercentViewHolderInScreen — расчитывает насколько точно этот VideoHolder нужно играть, вохможно он не настолько в центре экрана как допустим VideoHolder перед ним или после…
— getLimitPositionInScreen — расчитывает лимиты с верху и снизу экрана, собственно он нам говорит что VideoHolder внизу экрана присутствует или нет, и так же про верхнюю часть экрана. Это offset который нужен что бы у нас играли видео не только в центре экрана но и снизу и сверху, на пример когда по центру у нас не 100% от getPercentViewHolderInScreen().
Вот такая сложная фигня этот класс, его единственного я не смог переписать на котлин, в нем просто творится адская магия, но сам факт, эта магия работает и прям очень даже не плохо, главное не тупить :) В целом этот класс у нас готов, дальше нам нужно создать плеер который мы будем использовать для отображения видео.
Для начала нам нужно создать bootstrap который будет хранить в себе все нужные параметры и настройки. Потом создадим все нужные листенеры, енамы и фабрики. А в конце уже будем создавать собственно сам плеер.
VideoPlayerQuality.kt
Нам нужен этот енам для того что бы разделить качество видосов на низкое и высокое, потому что HLS это видео поток в котором можно регулировать качество видосов, высокое качество нужно для широкоформатного стриминга, на пример в фулл скрине, или там стрим с телефона на телевизор по chromecast'у, а в списке мы будем использовать LOWEST формат для экономии памяти и батареи.
StartupTrackSelectionFactory.kt
Этот класс нам нужен для адаптивного стриминга, про который я говорил выше, в этом классе мы говорим стриму что стартовать нам нужно как можно быстрее, по этому в начале проигрыша видео, мы видео играем в низком качестве, в самом низком в котором можно, а по мере проигрывания если появляется возможность улучшить его, мы повышаем.
ExoPlayerVideoPlayerBootstrap.kt
Что же у нас тут происходит, а происходит вот что, в теле init мы определяем в каком формате нам нужно настраивать поток, и устанавливаем track selector который выбирает в каком качестве играть, в низком или в высоком. Далее создаем SimpleExoPlayer и инициализируем его и указываем что видео у нас будут играть по кругу.
— lowQualityTrackSelectorFactory и highQualityTrackSelectorFactory — нам нужны чисто для сокращения кода в init, в первом мы устанавливаем низкое качество потока, во втором высокое.
— buildMediaSource — нужен для инициализации потока который мы будем проигрывать, собственно тут мы указываем ссылку на видео которое хотим проиграть.
Так же нам понадобится листенер который будет использовать ExoPlayer для понимания статуса видео, готово ли оно для проигрывания или может оно буферится, или запаузено…
PlayerStateChangedListener.kt
Собственно метод onPlayerStateChanged и нужен, его мы и дублируем в наш плеер через интерфейс, остальные методы для нас бесполезны, ну кроме onPlayerError, но он и так выведет нам нужную инфу в случае ошибки в лог.
Теперь мы дошли наконец-то до нашего плеера, ура! Но для начала давайте посмотрим как выглядит xml у плеера.
view_video_player.xml
Здесь у нас все придельно тривиально, у нас есть PlayerView и поверх него у нас лежит ProgressBar который будет показываться при загрузке видео и будет прятаться при включении потока.
VideoPlayerView.kt
— videoHeight — метод который возвращает высоту видео, он нам нужен для установки высоты видео в айтеме в адаптере.
— init — у нас инициализирует нашу вьюху и устанавливает качество стрима — LOWEST.
— initPlayer нам нужен прежде всего для того что бы мы могли проинициализировать наш плеер когда фокус падает на айтем который пользователь захотел просмотреть. В этом методе мы инициализируем наш bootstrap класс, создаем плеер, задаем все нужные параметры — такие как:
— задаем плеер в котором нужно играть видео.
— устанавливаем что нам нужно что экран будет включен все время сколько видео будет проигрываться.
— видео будет стартовать с начала каждый раз когда будет заканчиваться.
— мы отключили контролы в плеере, по этому они не будут отображаться.
— и установили ресайз для видео что бы оно растягивало его по ширине и высоте, в том резолюшене который мы задали расчитав его в методе videoHeight.
Так же мы задали mediaSource и вызвали prepeare что бы видео начало прогружаться. И так же, кинули коллбек для onPlayerVisibleListener в холдер что у нас появился активный плеер которым дальше из адаптера мы сможем управлять, паузить, резюмить или останавливать.
Дальше идут методы playVideo, pauseVideom resumeVideo и stopVideo — они все повторяют стандартный функционал в котором мы говорим что нам делать с потоком, ну и дергаем нужные переключатели для сохранения статуса потока, что бы не запутаться.
— releasePlayer — останавливает плеер и уничтожает все его инстансы, это нам нужно для того что бы эти переменные не висели без толку если мы их уже не используем.
— onPlayerStateChanged — это наш метод из PlayerStateChangedListener, который возвращает стейт потока, в этом методе мы регулируем, когда показывать прогресс бар, когда его прятать и опять же управляем переключателем isBuffering который нужен нам для разного рода проверок в будущем.
Дальше начинается самая легкая часть, мы создадим адаптер, холдер и подключим все эти штуки в MainActivity для отображения.
Но для начала нам нужно создать модель в которую мы будем загружать ссылки на поток и названия видео.
VideosModel.kt
Обожаю котлин за это :)
ViewHolder.kt
Мы унаследовали наш ViewHolder от VideoHolder который у нас используется в нашем AutoPlayRecyclerView, и оно нам предложило создать три метода которые собственно мы и прописывали там. В videoLayout мы передаем наш плеер что бы тот мог управлять им по надобности, а в play и stop прописываем включение и выключение потока.
— init — в этом методе мы инициализируем наш колбек который будет возвращать текущий активный холдер на экране, который проигрывается.
— в методе bind мы просто отображаем что у нас находится в модельке, а это тайтл видео. И задаем высоту видео в айтеме.
item_video_view_holder.xml
Просто текст и наш плеер который мы создали, этого достаточно нам для примера как я считаю.
VideoRecyclerAdapter.kt
Адаптер у нас в полне стандартный, в нем мы просто подписываемся на наш интерфейс который возвращает нам активный плеер, и дальше у нас появляется возможность паузить видео, резюмить или останавливать вообще, на пример когда нам нужно по сворачиванию или выключению приложения сделать так что бы видео не играло в фоне.
activity_main.xml
В нашей активити у нас будет отображаться только один RecyclerView который мы написали, более нам больше ничего и не нужно.
MainActivity.kt
— onCreate — у нас инициализирует список с ссылками на потоки видео, и адаптер который будет отображать их.
— onPause, onDestroy, onStop и onResume нам нужны для сохранения жизненного цикла приложения, если пользователь свернет, развернет или выключит приложение, у нас наш поток остановится и не будет играть в фоне, и не создаст проблем при использовании нашего приложения пользователю.
Вроде бы ничего не забыл. Статья оказалась огромной просто, но в любом случае это отличный пример того как можно сделать автоплей в своем приложении.
Исходники:
GitHub
Спасибо, Глеб! Ваше "грязное дело" да пусть не отсохнет:) Приятно было узнать, что Вы из Харькова.
ОтветитьУдалитьСпасибо :)
Удалить