Skip to content

About

A YouTube client for old Android phones (4.1+) — no Google services, mini player, subtitles, and the original YouTube 2016 / 2017-2018 themes

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 

Repository files navigation

OldTube

Отдельный YouTube-клиент для старых Android — от 4.1 Jelly Bean до 7.x и выше. Интерфейс в стиле приложения 2016–2017 года: белая панель, красный акцент, вкладки с иконками, крупные карточки видео.

Подписки, история просмотров и поиска, загрузки хранятся локально на устройстве.

Всё работает на самом телефоне: ни сервера, ни компьютера, ни аккаунта не нужно.

Как удаётся получать видео

Запрос к /youtubei/v1/player «с холода» получает LOGIN_REQUIRED, каким бы клиентом ни представлялся, а мобильные клиенты вдобавок требуют аттестацию приложения (FAILED_PRECONDITION), которую подделать нельзя. Рабочая комбинация оказалась такой:

  1. Сначала завести личность. Обычный заход на youtube.com, как сделал бы браузер: оттуда берутся visitorData и cookie сеанса (Session.java).
  2. Затем запрашивать плеер клиентом VISIONOS. Единственный, который отвечает телефону: аттестации не требует, poToken тоже.
  3. Передать signatureTimestamp со страницы — тогда YouTube отдаёт прямые ссылки вместо зашифрованных, и исполнять его JavaScript не нужно.
  4. Запрос плеера идёт анонимно даже при выполненном входе — эти клиенты отвергают запрос с учётными данными (INVALID_ARGUMENT), а вход там всё равно ничего не открывает.

Ссылки приходят без n-параметра, то есть не задушены. Но «не задушены» ещё не значит «быстро»: как именно их качать — отдельный разговор, см. ниже. Формат — H.264 + AAC, ровно то, что старое железо декодирует аппаратно.

Почему обычные клиенты не работают на старых Android

Три отдельные проблемы, и приложение решает каждую:

Проблема Решение в этом проекте
На Android 4.x системный TLS не умеет в TLS 1.2 — YouTube просто не отвечает В APK вшит Conscrypt (BoringSSL), он ставится первым JCE-провайдером
Современные библиотеки требуют minSdk 19–21 Прибиты последние версии с minSdk 14–16: AppCompat 1.3.1, OkHttp 3.12.13, ExoPlayer 2.19.1
У старых чипов нет аппаратного декодера VP9 По умолчанию выбираются только H.264/AAC, приоритет — готовым muxed-потокам

Как получить APK

Форкни/залей репозиторий на GitHub — сборка запустится сама (.github/workflows/build.yml). Потом: вкладка Actions → последний запуск → артефакт OldTube-APK.

Локально (нужны JDK 17 и Android SDK):

gradle :app:assembleDebug

APK окажется в app/build/outputs/apk/debug/OldTube-debug.apk; релизная сборка даёт OldTube.apk — имя задано в app/build.gradle, чтобы выкладывать файл как есть, без app-release.apk.

Что прикладывать к выкладке — в папке release/: screenshots/ (экраны всех трёх тем) и icons/ (значки 512×512 и логотипы, в том числе знаки на прозрачном фоне).

Как устроено получение видео

Порядок источников, первый удачный выигрывает:

  1. InnerTube — приватный API самого YouTube, без посредников. Перебираются клиенты VISIONOS → ANDROID_KIDS → TVHTML5 → ANDROID_VR → IOS → WEB. Отвечает обычно первый же; остальные держатся на случай отказа.

    ANDROID_KIDS стоит вторым, чтобы быстрее всех распознавать детские видео (см. ниже). Для обычных видео это ещё один запасной клиент на случай, если VISIONOS не ответил вовсе.

    Ответ клиента ещё не значит, что видео будет играть: у части клиентов ссылки выглядят нормальными, а медиасервер отвечает на них 403 — им нужен proof-of-origin токен, которого приложению взять негде. Поэтому перед тем как принять ответ, запрашиваются два байта потока; отказ — и перебор идёт дальше. Иначе поиск останавливался бы на клиенте, который ничего не отдаёт.

  2. Мост — только если его адрес вписан в настройках. По умолчанию поле пустое, и этот шаг просто пропускается.

  3. Piped — если YouTube вернул только signatureCipher. Piped расшифровывает его на своей стороне. Перебирается список инстансов.

  4. Invidious — то же самое, другой API.

Запасные серверы отключаются в настройках, если хочется работать только напрямую.

Парсинг ответов InnerTube сделан рекурсивным обходом JSON (Json.collect), а не по жёсткому пути — YouTube меняет структуру ответа несколько раз в год, и обход переживает такие изменения.

Детские видео

В приложении их нет — сознательно. Видео с пометкой «сделано для детей» YouTube обычным клиентам не отдаёт: те, что в каталоге YouTube Kids, играли бы через ANDROID_KIDS, а остальные — только с токеном, который вырабатывают лишь официальные приложения и браузер. Вместо того чтобы играть половину и спотыкаться на второй, приложение не показывает их вовсе.

Трудность в том, что пометки «для детей» YouTube не публикует нигде, где её можно прочесть: ни в поиске, ни в ленте, ни даже в ответе плеера (isFamilySafe там true почти у всего). Выдаёт такое видео только поведение: VISIONOS отвечает UNPLAYABLE, а другой клиент при этом видео видит. Удалённое или закрытое видео так не выглядит — его не видит никто.

Поэтому канал узнаётся в момент, когда открываешь его видео: экран закрывается с коротким сообщением, а канал попадает в список скрытых и больше не появляется ни в ленте, ни в поиске, ни в похожих, ни в истории. Вернуть все скрытые каналы можно в настройках, на вкладке «Общие». Из затравки главной ленты убран «мультфильм», который тянул детское сам.

Почему видео не останавливается на подгрузку

Одним длинным запросом файл с googlevideo приходит ровно со скоростью воспроизведения. Замер на этом проекте:

видео требует:            2124 КБ/с
один длинный запрос:      2121 КБ/с  = 1.0x
кусками по 2 МБ:         15331 КБ/с  = 7.2x
кусками по 6 МБ:         18263 КБ/с  = 8.6x

Ровно 1.0x — это не совпадение, а политика сервера. И это хуже, чем кажется: при такой скорости буфер не может набраться наперёд вообще, запаса нет, и любая просадка связи сразу становится паузой, из которой уже не выбраться — догонять нечем.

Тот же сервер, те же байты, запрошенные диапазонами по 2 МБ, отдаются в семь раз быстрее. Поэтому player/ChunkedDataSource.java подменяет один бесконечный запрос чередой range-запросов; плеер выше об этом ничего не знает и видит обычный непрерывный поток.

Остальное из той же оперы:

  • Буфер считается в байтах, а не в секундах — шестая часть кучи приложения (6–24 МБ). Секунды означают совершенно разный объём на 360p и на 720p, а помещаться надо в кучу телефона 2013 года.
  • Таймаут чтения — 8 секунд вместо 30. googlevideo иногда принимает соединение и замолкает; с тридцатью секундами это тридцатисекундная пауза посреди видео, с восемью — моргание, потому что повтор почти всегда попадает на здоровое соединение.
  • Звук берётся до 128 кбит/с. Разницы с 256 на телефоне не слышно, а байты настоящие и на слабой связи отнимаются у картинки.
  • Перемотка — на ближайший опорный кадр. Точная требует декодировать всё от предыдущего ключевого кадра: на этом железе это и есть та задержка после перемотки, которую видно.

Комментарии

Их нет в ответе плеера и нет в /next — там лежит только токен, ведущий к ним, причём среди нескольких чужих токенов сразу. Приложение достаёт именно тот, что принадлежит секции комментариев, и уже по нему запрашивает список (extract/Comments.java).

Форматов у YouTube сейчас два, и отдаёт он то один, то другой: старый commentRenderer, где всё лежит внутри, и новый — где у комментария остался только ключ, а текст уехал в отдельную таблицу сущностей ниже по ответу. Читаются оба.

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

Так это устроено не из любви к панелям. Раньше комментарии жили прямо в прокрутке страницы просмотра, где не переиспользуется ничего — каждый загруженный комментарий оставался в памяти, — а кнопка «показать ещё» дописывала новые ниже себя, за пределы экрана, отчего выглядела нерабочей.

Нажатие на «105 ответов» поднимает вторую панель — ветку. Сам комментарий стоит в её начале, ответы идут под ним со сдвигом. У каждой ветки свой токен, так что это отдельный запрос, и длинная ветка тоже догружается по частям. «Назад» закрывает по одному: ветка, комментарии, потом уже плеер.

Число комментариев в заголовке появляется не всегда: YouTube отдаёт шапку секции в нескольких видах, и в части из них счётчика нет вовсе.

Мини-плеер

Потяните плеер вниз (или нажмите «назад») — видео свернётся в панель у нижнего края, и можно листать ленту дальше, не прерывая просмотр. Тап по панели разворачивает обратно на то же место.

Плеер живёт не внутри экрана просмотра, а в player/Playback.java, общий для всего приложения; экран и панель — просто поверхности, которые к нему подключаются. PlaybackService держит процесс живым, чтобы звук не обрывался при выходе из приложения.

Свайп вниз не уносит экран, а превращает его в панель: сначала гаснет всё, что ниже плеера — в панели этого нет, — а оставшееся сжимается к левому нижнему углу, туда, где окажется миниатюра. К моменту, когда палец прошёл достаточно, на экране уже почти та форма, в которую всё складывается. Окно экрана просмотра при этом сквозное, так что за ним видно главный экран, а не белый лист.

Само сворачивание — одно непрерывное движение, и ведёт его сам экран, а не оконная анимация: двигается только плеер. Он уменьшается от левого верхнего угла и едет ровно на место миниатюры в панели, а страница под ним гаснет. Как только движение пошло, панель уже поднимается под плеером — он садится в неё, а не ждёт, пока она появится. Резкий смах вниз считается за сворачивание, даже если палец прошёл немного.

Обратный путь — тот же кадр наоборот: по нажатию на полоску экран просмотра открывается уже сжатым до её миниатюры и вырастает из неё, без отдельной оконной анимации.

На Android до 7 плеер показывается через TextureView, а не SurfaceView (res/layout/player_surface.xml и layout-v24/). Там SurfaceView не умеет двигаться и масштабироваться вместе с анимацией: сворачивание разваливалось на скачок, а после передачи картинка часто оставалась чёрной. На Android 7 и новее остаётся SurfaceView — он экономнее. До Android 6 декодер вдобавок нельзя перенаправить на другую поверхность, он пересоздаётся, поэтому после передачи делается перемотка на текущее же место — иначе кадр не появлялся до нажатия паузы.

Передача картинки устроена так, чтобы не было ни чёрного кадра, ни провала: панель забирает видео, пока экран просмотра ещё стоит над ней, застыв на последнем кадре ровно поверх миниатюры, и экран уходит только когда панель нарисовала свой первый кадр (но не дольше 0,4 с — на паузе новый кадр может не прийти).

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

Главный экран

Четыре вкладки, как в приложении 2016 года: «Главная», «В тренде», «Подписки», «Профиль».

  • Главная — видео 2015–2020 годов и дальше без конца: у конца списка подгружается ещё порция случайных тем и лет той эпохи.
  • В тренде (значок — огонёк, как в приложении 2016 года) — хиты YouTube 2015–2020, в первую очередь ютуберы и шоу того времени из СНГ: Ивангай, SlivkiShow, +100500, This is Хорошо, Поззи, Хованский, Джарахов, Масленников, Куплинов, Мармок, вДудь, Wylsacom и ещё около сорока. Каждый день берутся 14 из них плюс несколько тем эпохи («вайны 2016», «майнкрафт 2015»…), поиск идёт с сортировкой по просмотрам, остаются видео 2015–2020. Клипов не больше 8 на весь список, по одному через каждые десять видео; от одного канала — не больше четырёх роликов. Список собирается раз в сутки и хранится в файле до полуночи; ровно в 00:00 открытая вкладка обновляется сама, а если телефон спал — при следующем взгляде на неё.
  • Подписки — над лентой ряд кружков с каналами, на которые подписаны, как в приложении тех лет: нажатие открывает канал, стрелка справа — список всех подписок по именам. Берётся из таблицы subs, без сети.
  • Профиль — сверху шапка: фон, круглое фото и имя. Это не аккаунт: имя лежит в настройках, обе картинки копируются в папку приложения, YouTube о них не знает. Нажатие на шапку — «Изменить имя», «Выбрать фото», «Выбрать фон», «Убрать фон». Пока фона нет, полоса серая, как было у канала без шапки; пока нет фото — круг с первой буквой имени. Ниже — всё, что телефон хранит сам: «История просмотра», «Смотреть позже», «Понравившиеся», «Скачанные» и «Мои плейлисты». Аккаунта за этим нет: списки лежат в базе приложения (saved, playlists, list_items) и дальше телефона не уходят. Любая строка открывает один и тот же экран (ui/LocalListActivity.java) — список с «Воспроизвести все», долгое нажатие убирает видео из списка (у скачанного — удаляет файл). Плейлист создаётся строкой «Новый плейлист», долгое нажатие на него — переименовать или удалить. История — исключение: это запись о просмотренном, а не очередь, поэтому над ней нет ни «Воспроизвести все», ни числа видео, нажатие просто открывает видео, а долгое убирает одну запись, а не стирает историю целиком.

Плеер: субтитры, звук, полный экран

  • CC только включает и выключает субтитры, без окон. Язык выбирается в настройках плеера (шестерёнка), и кнопка его запоминает. Пока выбора не было, берутся русские субтитры, написанные человеком, а если их нет — автоматические на языке видео. Включённые субтитры остаются включёнными и в следующих видео.
  • Субтитры загружаются в формате JSON и переводятся в чистый WebVTT (player/CaptionDataSource.java): белый текст на тёмной подложке, по центру, чуть выше нижнего края; пока видны кнопки плеера, текст поднимается над полосой перемотки.
  • Стрелки по сторонам от паузы — предыдущее и следующее видео, как в плеере YouTube 2016 года (перемотку на ±10 секунд делает двойной тап). «Следующее» — следующее видео плейлиста, а вне плейлиста — первое из «Следующих видео», которое ещё не смотрели. «Предыдущее» — предыдущее видео плейлиста, а вне плейлиста — то, что смотрели перед этим (приложение помнит до 50 последних), с того места, где его оставили; если возвращаться некуда — текущее видео с начала. Работает и в полноэкранном режиме: полный экран остаётся, меняется видео и название, а экран просмотра под ним подстраивается сам и не отбирает картинку.
  • Кнопка «плей/пауза» — просто белый значок без круга, как в YouTube. У ExoPlayer значок нарисован в залитом круге, поэтому кнопка своя; в конце видео она запускает его сначала. Раньше субтитры не появлялись вовсе: у части клиентов ссылка уже содержит fmt=srv3, приложение дописывало второй fmt, и YouTube отдавал XML, который плеер не читает. Автоматические субтитры YouTube в WebVTT к тому же прижаты к левому краю и повторяют строки — теперь этого нет.
  • Кнопку CC раньше гасил сам ExoPlayer — он считал, что субтитров нет, пока их не включили. Теперь у кнопок CC и шестерёнки свои id, и ExoPlayer их не трогает.
  • Звук — на языке оригинала. YouTube озвучивает новые видео на десятки языков (у MrBeast — 24 дубляжа, 120 аудиопотоков), и раньше приложение брало поток с наибольшим битрейтом — то есть случайный язык. Каждый поток помечен в ссылке (xtags=acont=original против acont=dubbed) и в audioTrack; играет тот, что YouTube называет оригиналом. Копии «стабильной громкости» отбрасываются.
  • Озвучка по выбору. Настройки → Общие → «Выбор озвучки в плеере»: когда включено, в шестерёнке плеера появляется пункт «Озвучка» со всеми языками видео (только у видео, где их больше одного). Выбор действует на одно видео, следующее снова начинается с оригинала. Когда выключено, пункта нет. Заметка: YouTube не всегда считает оригиналом то, что ждёшь, — у новых видео А4 оригиналом помечена английская дорожка, а русская — дубляжом.
  • Во весь экран — кнопка справа от времени. Это отдельный непрозрачный горизонтальный экран (FullscreenActivity), который забирает картинку у плеера. Экран просмотра повернуть нельзя: его окно прозрачное (чтобы сворачиваться в полоску), а Android 8.0 такому окну фиксированную ориентацию не даёт. В полном экране вместе с кнопками появляется название видео сверху слева и канал под ним, на затемнении, как в YouTube.

Лайки

Число лайков берётся из кнопки самого YouTube (likeButtonViewModel), а не из поля likeCountIfIndifferent, которого он больше не отдаёт — поэтому счётчик и пропал. Дизлайки YouTube не публикует уже несколько лет, так что под ними числа нет.

Свой лайк или дизлайк ставится и снимается, но живёт на телефоне: отправить оценку в YouTube без аккаунта нельзя, а аккаунта здесь нет. Отметка помнится (таблица ratings) и подсвечивает кнопку красным, когда видео открываешь снова.

Экран видео

Под плеером одна прокручиваемая лента: описание, кнопки, канал с числом подписчиков, комментарии — и дальше «Следующие видео» без конца. Сначала идёт список самого YouTube, страница за страницей, а когда он кончается — случайная подборка видео той эпохи.

Раньше список похожих видео лежал внутри прокрутки страницы: такой список один раз меряет себя и показывает только то, что поместилось в тот раз, — отсюда были «2 или 5 видео», и больше после того, как свернёшь описание.

Кнопки под видео — «Нравится», «Не нравится», «Поделиться», «Добавить в». Последняя открывает окно «Добавить куда?»: «Смотреть позже», три своих плейлиста (у тех, где видео уже есть, стоит галочка, рядом — сколько в них видео), «Показать все плейлисты», «Новый плейлист» и «Скачать» — скачивание переехало сюда, отдельной кнопки у него больше нет. Лайк заодно кладёт видео в «Понравившиеся», снятый лайк — забирает обратно.

Поиск

  • История. Запросы запоминаются (последние 30); при пустом поле видна история, долгое нажатие удаляет запрос, внизу — «Очистить историю поиска».
  • Подсказки. Пока печатаешь, сначала совпадения из истории, потом популярные запросы 2015–2020 (около двухсот, от блогеров и игр до мемов и клипов, EraQueries.java), а через четверть секунды — подсказки самого YouTube. Стрелка справа подставляет подсказку в строку, не начиная поиск.
  • Канал сверху. Над результатами — карточка канала, который, скорее всего, искали: аватар, @имя, подписчики, кнопка «Подписаться».
  • Фильтры — те же, что у YouTube (сортировка, дата загрузки, длительность, HD, 4K, субтитры, трансляции), плюс год загрузки: можно отметить любые годы с 2005-го или одной кнопкой «2014–2020» / «2021–2026». Такого фильтра у YouTube нет, поэтому запрос задаётся и отдельно для каждого года («minecraft 2015»), а в выдаче остаются только видео, загруженные в отмеченные годы. Если за них нет ничего — например, у канала нет видео 2014 года, — поиск идёт на год вперёд, потом ещё, пока что-то не найдётся, и пишет над результатами, за какой год показано. Пока фильтр включён, на его кнопке горит красная точка.

Языки

Настройки → «Языки»: «Как в системе», «Русский» или English. Переводится всё: меню, надписи, ошибки, уведомления о загрузке. На этом же языке YouTube присылает счётчики и даты («1.2M views · 9 years ago»). Регион — что считается популярным — остаётся регионом телефона. Английский лежит в res/values-en/strings.xml, русский — основной, в res/values/.

До Android 13 у приложений нет своего языка, поэтому каждый экран наследует BaseActivity, который оборачивает контекст в выбранный язык (util/LocaleHelper.java). Ресурсы самого приложения, из которых берутся ошибки и уведомления, переключаются тоже. После выбора языка все экраны пересоздаются, видео при этом доигрывает в мини-плеере. Кэш вкладки «В тренде» хранится отдельно для каждого языка.

Темы

Настройки → «Темы»: OldTube (по умолчанию), Оригинальный YouTube 2016 и Оригинальный YouTube 2017–2018. У каждого варианта в настройках — строка его значков.

В обеих темах YouTube значки по всему приложению заменяются на те, что были у него тех лет (он рисовал их в Material Design): домик, огонёк, стопка подписок, человечек профиля, лупа, большой палец вверх и вниз, изогнутая стрелка «Поделиться», треугольник и две полосы в плеере, три точки вместо шестерёнки в плеере. Различаются темы не значками, а самим экраном.

2016. Краснеет вся верхняя полоса — шапка, вкладки под ней и строка состояния над ними: значки и подписи на ней белые, у выбранной вкладки белая черта. Логотип — сам знак YouTube (белый значок с красным треугольником, res/drawable/yt_logo.xml) и слово «YouTube» белым рядом; квадрат под значок квадратный, иначе рисунок расплющивает. Полоса красится из ui/Chrome.java, который вызывается у каждого экрана (BaseActivity.onPostCreate) и находит шапку по общим id toolbar / top_bar и tabs.

2017–2018. Шапка снова белая, знак YouTube красный (yt_logo_red.xml), а вкладки переезжают вниз — значок и подпись под ним, выбранная красная, — туда, куда YouTube их тогда перенёс и где они с тех пор и остались. Мини-плеер стоит над этой полосой, как и положено. Полоса строится в MainActivity.buildBottomTabs().

Значок приложения тоже принадлежит теме: у 2016 — значок YouTube того года (тёмно-красный экран), у 2017–2018 — ярко-красный, который пришёл ему на смену. Android выдаёт приложению один значок, жёстко записанный в манифесте, поэтому в манифесте лежат три activity-alias — три двери в один и тот же экран, каждая со своим значком, — и открыта всегда ровно одна (ui/LauncherIcon.java). Сами значки нарисованы под каждую плотность (mipmap-*/ic_launcher_2016.png, ic_launcher_2017.png).

Сами значки устроены без правки разметок: в них их заменяет свой ThemedViewInflater (он указан в теме как viewInflaterClass и видит каждый app:srcCompat), а те, что ставятся из кода, проходят через Icons.get(). Соответствие «свой значок → значок YouTube» — в ui/Icons.java, сами значки — res/drawable/yt_*.xml.

Плейлисты

На экране плейлиста — кнопка «Воспроизвести все» и число видео. Нажатие на любое видео плейлиста тоже запускает плейлист, с этого видео.

Во время плейлиста под плеером — белая полоса: название плейлиста, «2 из 8» и что будет дальше. Стрелка раскрывает список видео поверх страницы (текущее отмечено) и прячет его обратно; «назад» тоже его прячет. Если выбрать в списке другое видео, список остаётся раскрытым и на его экране. Длинный плейлист догружается по мере приближения к концу.

Когда видео кончается, следующее включается само — и на экране просмотра, и в мини-плеере: очередь (player/PlayQueue.java) живёт рядом с общим плеером, а не в экране, поэтому следующее видео находится и запускается даже при закрытом экране просмотра. Видео, которое не играет, пропускается. Любое видео не из плейлиста заканчивает плейлист.

Канал

Три вкладки: «Главная», «Видео», «Плейлисты».

  • Главная — как первая страница канала в YouTube: три самых популярных видео, два новых и несколько плейлистов. У каждой полки есть «Все» — открывает соответствующую вкладку (у популярных сразу с нужной сортировкой).
  • Видео — список канала с двумя значками сверху: порядок и календарь. Порядок берётся у самого YouTube («Новые», «Популярные», «Старые»): это его собственные кнопки, каждая с токеном продолжения, поэтому список в любом порядке листается дальше как обычно.
  • Календарь — годы загрузки. Годы и порядок работают вместе: «Новые» — последние видео тех лет, «Старые» — самые ранние, «Популярные» — самые просматриваемые.

Годы нельзя найти, листая список канала: у канала с долгой историей 2016 год лежит в сотнях видео от начала, и перебор сдавался раньше. Поэтому при выбранных годах видео ищутся через поиск YouTube («имя канала» + год, по запросу на год), из ответа остаются только видео этого канала и только тех лет, а порядок приложение задаёт само (YtService.channelVideosInYears).

Кнопка «О канале» под названием открывает описание, дату регистрации, число подписчиков, просмотров и видео, страну и ссылки.

Вкладки «Видео» и «Плейлисты» — у каждой своя подгрузка и своё место прокрутки. Плейлист открывается отдельным экраном со своим списком.

Эти сведения грузятся заранее, вместе с каналом, поэтому окно «О канале» открывается уже заполненным.

Мост — необязательный запасной путь

телефон  ──►  мост на ПК  ──►  yt-dlp  ──►  YouTube
   ▲                               │
   └──── прямая ссылка H.264/AAC ──┘

Он не нужен для обычной работы — телефон справляется сам. Мост остаётся на случай, если YouTube снова поменяет правила и способ выше перестанет работать: тогда достаточно запустить его и вписать адрес в настройках, не дожидаясь новой версии приложения. Он же даёт склейку видео и звука для скачивания.

Запуск (нужен JDK 11+ и yt-dlp; ffmpeg — только для скачивания):

cd server
java OldTubeBridge.java --yt-dlp=/путь/к/yt-dlp --ffmpeg=/папка/с/ffmpeg 8099

На Windows проще запустить server/start-bridge.bat — он сам найдёт инструменты рядом с собой. При старте сервер печатает адреса, которые нужно вписать в приложении: Настройки → Свой сервер (мост yt-dlp).

Эндпоинты:

Запрос Что делает
GET /v/{id} отдаёт JSON от yt-dlp как есть — разбирает его само приложение
GET /dl/{id}?h=720 склеивает видео и звук в один MP4; отвечает 202, пока готовит, потом отдаёт файл с поддержкой докачки
GET /health проверка живости

Мост слушает всю локальную сеть без пароля — это нормально для домашней сети, но не выставляйте его наружу. Телефон должен быть в той же сети Wi-Fi. Пока поле в настройках пустое, приложение к мосту не обращается вовсе.

Скачивание

YouTube больше не отдаёт готовых файлов «видео+звук» вовсе — только раздельные дорожки. Поэтому склейку делает мост: ffmpeg собирает H.264 и AAC в один MP4, а телефон скачивает результат. Проверено: на выходе h264 1280x720 + aac 2ch в одном файле.

Файлы: /sdcard/Movies/OldTube/. Прерванная загрузка продолжается с того же места (HTTP Range) — и на стороне телефона, и на стороне моста.

Без моста «Скачать» (в окне «Добавить куда?») честно скажет, что сохранять нечего.

Структура

extract/   InnerTube, комментарии, Piped, Invidious, фасад YtService, JSON-обход
net/       OkHttp + Conscrypt, загрузчик картинок
model/     Video, Track, StreamSet, Playlist, SearchFilter
db/        SQLite: подписки, история, загрузки, оценки, свои списки и плейлисты
ui/        экраны и адаптеры
dl/        сервис загрузки

Ограничения

  • Способ получения потоков держится на клиенте VISIONOS. Если Google закроет и его, воспроизведение остановится до обновления — на этот случай в настройках есть поле для моста.
  • Скачивание без моста ограничено. YouTube больше не отдаёт готовых файлов «видео+звук», а склеивать их мультиплексором на телефоне 2013 года — не та задача, которую стоит решать. Без моста кнопка честно скажет, что сохранять нечего.
  • Вход в аккаунт на воспроизведение не влияет (проверено) — он нужен только для персональных данных и не является обязательным.
  • Шифрованные подписи (signatureCipher) не расшифровываются локально — это потребовало бы исполнять JS-код YouTube на телефоне 2013 года. Расшифровку, к слову, теперь не сделать и на компьютере тем способом, каким её делают сторонние клиенты: в сборках плеера (player_ias, player_ias_tce, player_es6, обе телевизионные) функции подписи в извлекаемом виде больше нет — операции спрятаны в таблицу строк. Практического значения это пока не имеет: клиенты из списка выше отдают прямые ссылки.
  • Публичные инстансы Piped и Invidious на момент написания практически все лежат либо закрыли API (500/502/403/401), поэтому в настройках есть поля для своего адреса — списки в Piped.java и Invidious.java протухают быстро.
  • Вкладки «В тренде» нет: YouTube убрал страницу трендов, и browseId FEtrending теперь отвечает 400. «Главная» использует FEwhat_to_watch.

Правовая заметка

Клиент обращается к тем же публичным endpoint'ам, что и официальные приложения. Скачивание видео противоречит условиям использования YouTube — решение о том, скачивать ли конкретное видео, остаётся за пользователем.

About

A YouTube client for old Android phones (4.1+) — no Google services, mini player, subtitles, and the original YouTube 2016 / 2017-2018 themes

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors