Смерть Кощея в списке рекомендаций (можно ли сделать уютным и безопасным Ютюб?)

Вступление коротко: хочу рассказать про онлайн плеер Ютюб для Андроида с локальными плейлистами, каналами и рекомендациями.

v6gijredqlwmps9caerjcock7hi.png

Вступление развернутое:
Некоторое время назад я столкнулся ровно с такой проблемой, как и автор замечательного приложения Channel Whitelist, и определил для себя к ней ровно такое же отношение: я хочу иметь возможность время от времени давать ребенку планшет или смартфон с мультиками, но меня совершенно не устраивает, куда через 2–3 клика заводит ребенка список рекомендаций в стандартных приложениях — клиентах Ютюб.

К сожалению, после установки приложения Channel Whitelist уже у него был обнаружен другой более прозаичный, но всё равно фатальный недостаток — NIH мне (и, главное, сыну) показался не очень удобным его интерфейс, особенно после привычки использовать плеер YouTube Kids.

В общем, еще через некоторое время я созрел, чтобы сделать свою реализацию. Еще через некоторое время стало возможным поставить тег на первый релиз.

Основные возможности:


  • Добавляйте любимые каналы и плейлисты — они будут сохранены и проиндексированы в локальной базе
  • Внутри добавленных плейлистов выключайте лишние ролики, если они вам не нужны
  • Список рекомендаций генерируется случайно только из добавленных в приложение каналов и плейлистов

Исходники открыты, лицензия GPLv3: https://github.com/sadr0b0t/yashlang/

Дальше обзор основных возможностей более подробно, плюс немного технических подробностей о том, как играть видео с Ютюб в вашем приложении на Андроид без использования АПИ Гугл и веб-оберток.


На главном экране и на экране плеера: случайные рекомендации из неслучайных каналов

xm4axqnezlqjwvp9wvli8jqwzlg.png

tyzup21qm5fqylfdbeusvaksfts.png

o9otxvwiwfps_acte3fjlw-yzao.png

w2tckomrdku1pbpcp0owbwlwnbq.png

q7xqncnblxcnqg2mkvg2s_pfb_o.png

nfvwlx-kkiwjzshn4qimtjiq65c.png


Мгновенный поиск по локальной базе

nvp8gak5tcomv-ybxxnumk3-wfo.png => v6gijredqlwmps9caerjcock7hi.png


Добавить новый канал или плейлист

qc1ep6rpukdgrbfmiwaivprjx1w.png 3cy3qmvnqncbg-cmxfdll7xtgog.png u8kixf0ygibuyaahrbjjfl0obcq.png x8c0kx1f8tbsqov0fgu4lpejzbs.png

mreritulfhtlbskcxchxzh-4qti.png voaojnv-b7kfdperixfcm618_tg.png huad3tbkk36d3udfwpfhgozfhfu.png fe2x1pyha_ccukl_mvweghasmqy.png

Искать по имени онлайн или вставить известный адрес. Список роликов канала или плейлиста сохраняется в локальную базу, иконки не кэшируются.


Динамический плейлист — играть результаты поиска

mwbbsuevmg76hrqojuw_hy25rrk.png zqwibdvjxb-lu8wgkmz96mhjsye.png => avdumqw_ak4pjgiqpjpseqpluky.png

ypu_8hv-3avu1eodjuckeuat-sg.png

kalqf78l73h7fraeb8lh3z5vtbe.png

В рекомендациях под видео будут только ролики, удовлетворяющие поисковому запросу.

Аналогичным образом, если открыть видео из настроек плейлиста, в списке рекомендаций будут только ролики из этого же плейлиста.


Плейлисты и каналы можно временно выключать и снова включать

tz649zfck92hi9jtpd1lbln4u1k.png p5om38mwyx6kil7qufh9jzqjbf0.png 1xzon61-sbzhypdbvfvspnmrgsm.png af3dlwczwlmfjkbvacyombog0fa.png

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


Внести ролик в черный список

deygfamvxtgx4vexzp4b7rvclwg.png pcp9_0bp_zsgfaaf9q6wetmh988.png gok026tul7xfp8y76jtb8_ay9a0.png

Заблокированный ролик не будет отображаться в рекомендациях, в результатах поиска, исчезнет из списка любимых и из истории просмотров. Ролик всё еще будет виден в настройках плейлиста.

Просмотреть черный список и снова включить элементы, заблокированные по ошибке:
Настройки > меню в заголовке > Черный список

6tew-rtkldkr8wb2ps0tf9co3qk.png kn6vke96-92lpkwopb58z0tlmyo.png


Любимые ролики и история просмотров

qpiy9mzysnj_q9yxcuohsochhh0.png f-kavza5992wz030cq0ef6rodjk.png r-wzza9nzhzez17vzkbubiel528.png

Любимые ролики на экране плеера отмечаются звёздочкой в правом верхнем углу.


Контекстные меню в заголовке экрана и по долгому клику в галереях и списках

3fpg4a-zqgn0h10pft56x6p_-fi.png juzg-a9o97kykwzriroe_lnukt4.png iefb-7yqxaym3ristff37huzz4s.png nluf-w464pfo-fmle_qn0xm9gpk.png

Копировать имя или адрес видео или плейлиста в экране просмотра или в любом списке.


Быстрый старт — добавить рекомендованные каналы и плейлисты

bb82_ydj_6j9qrkwqhdmakps-hc.png nt6mhmnlggxi3n1z43rhprgidh4.png => mgpzbyxxq2u2ioeqmr2nlkuf_qg.png

Приложение сразу станет выглядеть так, как на скриншотах выше.

Ненужные каналы и плейлисты можно выключить или удалить в настройках.


Установка

Страница проекта: 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 и нужно будет только обновить сборку с этой версией, например).


  • Интерфейс может подтормаживать при медленном (но не выключенном) интернете



В итоге

Сын переехал с планшета на смарт-тв Самсунг, который не умеет запускать приложения Андроид. Поэтому лучший родительский контроль — всё равно личный.

Но приложение получилось достаточно удобным для того, чтобы я начал его использовать сам. Первое впечатление с ранних работающих версий — попал в другой мир. Весь контент загружается с ютюба, но это уже не ютюб, а нечто другое, безопасное и контролируемое, как будто вынул из глаза сколопендру и посадил её в стеклянную банку. И дело именно в рекомендациях.

© Habrahabr.ru