Смерть Кощея в списке рекомендаций (можно ли сделать уютным и безопасным Ютюб?)
Вступление коротко: хочу рассказать про онлайн плеер Ютюб для Андроида с локальными плейлистами, каналами и рекомендациями.
Вступление развернутое:
Некоторое время назад я столкнулся ровно с такой проблемой, как и автор замечательного приложения Channel Whitelist, и определил для себя к ней ровно такое же отношение: я хочу иметь возможность время от времени давать ребенку планшет или смартфон с мультиками, но меня совершенно не устраивает, куда через 2–3 клика заводит ребенка список рекомендаций в стандартных приложениях — клиентах Ютюб.
К сожалению, после установки приложения Channel Whitelist уже у него был обнаружен другой более прозаичный, но всё равно фатальный недостаток — NIH мне (и, главное, сыну) показался не очень удобным его интерфейс, особенно после привычки использовать плеер YouTube Kids.
В общем, еще через некоторое время я созрел, чтобы сделать свою реализацию. Еще через некоторое время стало возможным поставить тег на первый релиз.
Основные возможности:
- Добавляйте любимые каналы и плейлисты — они будут сохранены и проиндексированы в локальной базе
- Внутри добавленных плейлистов выключайте лишние ролики, если они вам не нужны
- Список рекомендаций генерируется случайно только из добавленных в приложение каналов и плейлистов
Исходники открыты, лицензия GPLv3: https://github.com/sadr0b0t/yashlang/
Дальше обзор основных возможностей более подробно, плюс немного технических подробностей о том, как играть видео с Ютюб в вашем приложении на Андроид без использования АПИ Гугл и веб-оберток.
На главном экране и на экране плеера: случайные рекомендации из неслучайных каналов
Мгновенный поиск по локальной базе
=>
Добавить новый канал или плейлист
Искать по имени онлайн или вставить известный адрес. Список роликов канала или плейлиста сохраняется в локальную базу, иконки не кэшируются.
Динамический плейлист — играть результаты поиска
=>
В рекомендациях под видео будут только ролики, удовлетворяющие поисковому запросу.
Аналогичным образом, если открыть видео из настроек плейлиста, в списке рекомендаций будут только ролики из этого же плейлиста.
Плейлисты и каналы можно временно выключать и снова включать
Обратите внимание: ролики из выключенного плейлиста исчезнут также из результатов поиска, истории просмотров и из списка любимых. Но не стоит переживать, они опять появятся там сразу после того, как плейлист будет снова включен.
Внести ролик в черный список
Заблокированный ролик не будет отображаться в рекомендациях, в результатах поиска, исчезнет из списка любимых и из истории просмотров. Ролик всё еще будет виден в настройках плейлиста.
Просмотреть черный список и снова включить элементы, заблокированные по ошибке:
Настройки > меню в заголовке > Черный список
Любимые ролики и история просмотров
Любимые ролики на экране плеера отмечаются звёздочкой в правом верхнем углу.
Контекстные меню в заголовке экрана и по долгому клику в галереях и списках
Копировать имя или адрес видео или плейлиста в экране просмотра или в любом списке.
Быстрый старт — добавить рекомендованные каналы и плейлисты
=>
Приложение сразу станет выглядеть так, как на скриншотах выше.
Ненужные каналы и плейлисты можно выключить или удалить в настройках.
Установка
Страница проекта: https://github.com/sadr0b0t/yashlang/
на английском языке: https://github.com/sadr0b0t/yashlang/blob/master/README.en.md
релизы: https://github.com/sadr0b0t/yashlang/releases
Имейте ввиду, что переключаться между разными версиями из разных источников на одном устройстве не получится из-за разных подписей файла apk, перед установкой версии из нового источника придется установленную версию сначала удалить вместе с данными — кэшем плейлистов и историей просмотров (или придумать, как эти данные перенести).
Технические детали
Не требует аккаунт Гугл/Ютюб, нужен только интернет, использует библиотеки:
- NewPipeExtractor для получения данных с сервиса YouTube и
- ExoPlayer для проигрывания видео.
Открытый исходный код, свободная лицензия GPLv3.
вопрос: Парсить сайты без разрешения (или с явным запретом) авторов вообще законно? Гугл удаляет из Гугл-плея приложения, которые не используют их API, а парсят их сайты, т.к. они нарушают их пользовательское соглашение.
ответ: конечно, законно, это ваше дело, какой инструмент использовать для чтения общедоступной информации. Больше того: Суд США полностью легализовал скрапинг сайтов и запретил ему технически препятствовать, но у Гула может быть другое мнение, лично у меня пока нет желания отправляться в американский суд их переубеждать.
Немного кода
Библиотека NewPipeExtractor — вспомогательный проект плеера NewPipe, позволяет загружать список роликов для указанного канала или плейлиста, загружать подробную информацию об известном видео (то, что видно на веб-странице ролика), получать адрес иконки видео, а так же получать адрес потока видео.
Код для загрузки плейлиста немного громоздкий, поэтому здесь приводить его не буду, кому интересно — загляните в исходники, в основном это класс ContentLoader.
Посмотрим, как получить адрес потока видео по адресу публичной странички видео и играть его в плеере.
Подключить библиотеку в
app/build.gradle
dependencies {
...
// NewPipe: youtube parser
// https://github.com/TeamNewPipe/NewPipeExtractor
implementation "com.github.TeamNewPipe:NewPipeExtractor:v0.17.4"
...
}
Любопытно, что после этого ее всё равно не получится использовать, т.к. примеры будут ругаться на недостающий класс Downloader. Его можно скопировать в проект из каталога автоматических тестов NewPipeExtractor/extractor/src/test/java/org/schabi/newpipe/Downloader.java — работает для версии 0.17.4 (похоже, что в более новой версии библиотеки эта часть была переделана, но нужно еще проверить).
Получить адрес потока видео по адресу странички видео на сайте Ютюб:
app/src/main/java/su/sadrobot/yashlang/controller/ContentLoader.java
public String extractYtStreamUrl(final String ytVidUrl) throws ExtractionException, IOException {
// https://github.com/TeamNewPipe/NewPipeExtractor/blob/dev/extractor/src/test/java/org/schabi/newpipe/extractor/services/youtube/YoutubeStreamExtractorDefaultTest.java
NewPipe.init(Downloader.getInstance(), new Localization("GB", "en"));
final YoutubeStreamExtractor extractor = (YoutubeStreamExtractor) YouTube
.getStreamExtractor(ytVidUrl);
extractor.fetchPage();
final String streamUrl = extractor.getVideoStreams().size() > 0 ? extractor.getVideoStreams().get(0).getUrl() : null;
// for (final VideoStream stream : extractor.getVideoStreams()) {
// stream.getUrl();
// }
return streamUrl;
}
В качестве адреса видео ytVidUrl может быть публичный адрес странички любого видео на сайте Ютюб, например https://www.youtube.com/watch? v=pd2RlatmNRk
Плеер будет ExoPlayer от самой Google. Это не веб-обертка над Ютюб, а самый настоящий встраиваемый плеер для проигрывания любых видеороликов, достаточно гибкий и настраиваемый. В том числе умеет играть потоки видео с Ютюба, если указать ему правильный адрес. Адрес потока мы получили только что, поэтому посмотрим, как его отправить в плеер.
Подключить библиотеку в проект app/build.gradle:
dependencies {
...
// google Exoplayer
// https://github.com/google/ExoPlayer
// https://exoplayer.dev/
implementation 'com.google.android.exoplayer:exoplayer:2.10.8'
...
}
Все нюансы размещения компонента плеера на экране приложения рассматривать не будем (можете посмотреть в примерах на сайте проекта или в коде), посмотрим только на то, как запустить проигрывание видео с Ютюба в плеере по полученному выше адресу:
app/src/main/java/su/sadrobot/yashlang/WatchVideoActivity.java
private void playVideoStream(final String streamUrl, final long seekTo) {
if (streamUrl == null) {
// остановить проигрывание текущего ролика, если был загружен
videoPlayerView.getPlayer().stop(true);
} else {
// https://exoplayer.dev/
// https://github.com/google/ExoPlayer
final Uri mp4VideoUri = Uri.parse(streamUrl);
final MediaSource videoSource = new ProgressiveMediaSource.Factory(videoDataSourceFactory)
.createMediaSource(mp4VideoUri);
// Поставим на паузу старое видео, пока готовим новое
if (videoPlayerView.getPlayer().getPlaybackState() != Player.STATE_ENDED) {
// Если ставить на паузу здесь после того, как плеер встал на паузу сам, закончив
// играть видео, получим здесь второе событие STATE_ENDED, поэтому нам нужна здесь
// специальная проверка.
// При этом значение getPlayWhenReady() останется true, поэтому проверяем именно состояние.
// https://github.com/google/ExoPlayer/issues/2272
videoPlayerView.getPlayer().setPlayWhenReady(false);
}
// Prepare the player with the source.
((SimpleExoPlayer) videoPlayerView.getPlayer()).prepare(videoSource);
// Укажем текущую позицию сразу при загрузке видео
// (в коментах что-то пишут что-то про датасорсы, которые поддерживают или не поддерживают
// переходы seek при загрузке, похоже, что это фигня - просто делаем seek сразу после загрузки)
// Exoplayer plays new Playlist from the beginning instead of provided position
// https://github.com/google/ExoPlayer/issues/4375
// How to load stream in the desired position? #2197
// https://github.com/google/ExoPlayer/issues/2197
// в этом месте нормлаьный duration еще не доступен, поэтому его не проверяем
//if(seekTo > 0 && seekTo < videoPlayerView.getPlayer().getDuration()) {
if (seekTo > 0) {
// на 5 секунд раньше
videoPlayerView.getPlayer().seekTo(seekTo - 5000 > 0 ? seekTo - 5000 : 0);
}
videoPlayerView.getPlayer().setPlayWhenReady(true);
}
}
Известные проблемы
- Не будет играть ролики с возрастными ограничениями, требующие логин в аккаунт Гугл/Ютюб
например: Илья Муромец, Киноконцерн «Мосфильм», Руслан и Людмила 1-ая серия / Ruslan and Lyudmila film 1, Киноконцерн «Мосфильм»
совет: добавлять такие ролики в черный список или попросить автора ролика снять ограничение, выставленное по ошибке.
- Не будет играть некоторые ролики-трансляции, для которых сервис возвращает нулевую длину (для таких роликов продолжительность в списках и галерее отмечена как »[dur undef]»)
например: Ну Погоди! Все Выпуски Союзмультфильм HD (Мультики для детей), Мультики студии Союзмультфильм, Топ мультиков Союзмультфильм, Мультики студии Союзмультфильм
совет: добавлять такие ролики в черный список.
- Ролики, доступные только по прямым ссылкам, могут не попасть в локальный плейлист, даже если вы загружаете все ролики пользователя
например: Укрощение огня 1 серия, Киноконцерн «Мосфильм»
Если встретите публичный ролик, который не требует логин, играет в браузере, но не играет в плеере, присылайте баг-репорт (вполне возможно, проблема уже исправлена в новой версии NewPipeExtractor и нужно будет только обновить сборку с этой версией, например).
Интерфейс может подтормаживать при медленном (но не выключенном) интернете
В итоге
Сын переехал с планшета на смарт-тв Самсунг, который не умеет запускать приложения Андроид. Поэтому лучший родительский контроль — всё равно личный.
Но приложение получилось достаточно удобным для того, чтобы я начал его использовать сам. Первое впечатление с ранних работающих версий — попал в другой мир. Весь контент загружается с ютюба, но это уже не ютюб, а нечто другое, безопасное и контролируемое, как будто вынул из глаза сколопендру и посадил её в стеклянную банку. И дело именно в рекомендациях.