Split shorts from regular videos with a vertical shorts feed

- Detect shorts by duration (SHORTS_MAX_DURATION_SECONDS, default 180)
  and expose is_short in feed, video and channel-activity DTOs.
- Feed gains type=all|long|short and anchor; lists show a two-mode
  'Обычные | Shorts' filter (no 'Все') persisted in the URL.
- Clicking a short opens /shorts/🆔 a vertical scroll-snap feed with
  autoplay for the active slide, context-aware endpoints and infinite
  loading.
- Keep the sound choice across swipes, syncing with the player's own
  mute control and guarding against the widget's stale isMuted() reads.
This commit is contained in:
vrubelroman 2026-09-28 09:33:35 +00:00
parent 43adec5224
commit 252f38597d
25 changed files with 1297 additions and 88 deletions

View file

@ -10,6 +10,7 @@
- Skeleton, Google OAuth (single-user allow-list), sync подписок/видео (APScheduler), категории (CRUD + many-to-many), лента с курсорной пагинацией, YouTube-плеер, полная MeTube-интеграция (download/delete, Socket.IO live-статусы, recovery после рестарта).
- Режим «Каналы» на главной (`?view=channels`): переключатель «Лента | Каналы», список подписанных каналов по свежести последнего видео с тремя последними роликами в строке; фильтр категорий из сайдбара действует в обоих режимах.
- Раздел **«На сервере»** — скачанные видео; `/saved` перенаправляет на `/local`.
- Разделение видео на **Shorts** и обычные по длительности (`SHORTS_MAX_DURATION_SECONDS`, по умолчанию 180 сек; неизвестная длительность — обычное видео): сегмент-фильтр «Обычные | Shorts» во всех списках видео (по умолчанию «Обычные», `?type=short` для Shorts; `?type=all`/`?type=long`/неизвестное трактуются как «Обычные») и вертикальная full-screen лента Shorts (`/shorts/:youtubeVideoId`) с автоплеем активного ролика и бесконечной подгрузкой в контексте открытого фильтра. REST `GET /api/feed` по-прежнему поддерживает `type=all|long|short` (совместимость с будущим mobile/PWA-клиентом), UI вариант `all` не использует.
- Тёмный адаптивный интерфейс: общая навигация, категории с постоянными URL, поиск по названиям видео и каналов, мобильное меню.
- Отписанные каналы скрываются из списка каналов.
- Реальная отписка от канала на YouTube (`subscriptions.delete`) реализована по запросу пользователя сверх исходного MVP. Из-за этого OAuth scope расширен с `youtube.readonly` до полного `youtube` (read/write) — см. `backend/app/services/google_oauth.py`.
@ -17,7 +18,7 @@
### Что осталось / сознательно отложено
- Дополнительные идеи после MVP пока не реализованы: PWA, watch later, Shorts-фильтр, SponsorBlock и т.д.
- Дополнительные идеи после MVP пока не реализованы: PWA, watch later, SponsorBlock и т.д.
- Hardening (retry/recovery, тесты) выполнялся по факту находок в live-тестировании, а не отдельным проходом — см. `git log` для конкретных багфиксов (flapping статусов загрузки, неверный id в MeTube `/delete`, OAuth scope mismatch).
## Стек