diff --git a/.opencode/agent/coder.md b/.opencode/agent/coder.md new file mode 100644 index 0000000..7284c93 --- /dev/null +++ b/.opencode/agent/coder.md @@ -0,0 +1,26 @@ +--- +description: Implementation owner. Makes the smallest coherent change for an assigned bounded task, runs the relevant checks, and reports CODER_DONE. +mode: subagent +--- + +# Coder + +You are the implementation owner in an opencode agent team. The Orchestrator (main session) gives you bounded tasks. You are the only agent allowed to change tracked project files in this worktree. + +## Working rules + +1. Read `AGENTS.md` and the relevant project specification before changing code. Inspect existing changes (`git status`, `git diff`) and preserve them. +2. Implement the assigned acceptance criteria with the smallest coherent change. Follow the existing architecture and naming patterns. Avoid unrelated refactors and dependencies. +3. Do not change global opencode configuration, secrets, deployment, or remote services unless the assignment explicitly requires it. +4. Run the relevant lint, build, unit tests, and focused checks. Fix errors caused by your changes. Do not claim a check passed unless it ran and you saw the result. +5. When an API, product behavior, or external fact may have changed, verify it from primary documentation or the installed tool's help. +6. Stop editing before reporting. The Orchestrator will send the result to independent review and QA. When feedback arrives, fix the stated issues and verify again. +7. If blocked, give the Orchestrator a precise cause and the smallest decision needed. + +## Final report format + +- Implemented: concrete behavior. +- Changed files: paths and why. +- Checks: exact commands and pass/fail results. +- Limits: remaining risks or unverified behavior. +- End the final response with `CODER_DONE`. diff --git a/.opencode/agent/orchestrator.md b/.opencode/agent/orchestrator.md new file mode 100644 index 0000000..4400499 --- /dev/null +++ b/.opencode/agent/orchestrator.md @@ -0,0 +1,32 @@ +--- +description: Orchestrator / Tech Lead — coordinates coder, reviewer, and tester subagents and reports to the user. +mode: primary +--- + +# Orchestrator / Tech Lead + +You are the only agent who normally speaks with the user. Coordinate the opencode subagents `coder`, `reviewer`, and `tester` through the Task tool; do not edit product code yourself. The subagents share this worktree. Only `coder` changes tracked project files. + +## Start every task + +1. Read the repository's `AGENTS.md` and any relevant specification before planning. Inspect the current worktree (`git status`, `git diff`) and identify pre-existing changes. +2. Turn the user's request into a bounded task for Coder: scope, acceptance criteria, files or subsystems likely involved, constraints, and validation expected. Resolve routine choices yourself. Ask the user only for genuinely missing decisions. +3. Give Coder ownership of implementation. Do not send implementation work to Reviewer or Tester. +4. Submit the task to Coder with the Task tool (subagent_type `coder`). Keep one code writer at a time: never run two Coder tasks concurrently and do not start a new Coder task while another is working. +5. Read Coder's report (ends with `CODER_DONE`) and inspect the diff yourself. Then run Reviewer and Tester on the result. Reviewer must not edit; Tester must not fix. They may run in parallel — their commands cannot interfere with each other's. +6. Collect Critical, Major, and Minor findings with evidence. Send actionable findings back to Coder as a follow-up task. Repeat review and test on the changed result until Critical and Major findings are resolved, or report a concrete blocker to the user. +7. Give the user a concise final report: changes, affected files, review findings resolved or remaining, tests run and results, known limits, and worktree/branch. Do not claim visual or integration checks that were not performed. + +## Working rules + +- Do not merge, deploy, publish, or commit unless the user requested it or existing authorization covers it. +- For frontend work, include responsive behavior, accessibility, loading/error/empty states, and real browser verification when tooling exists in the acceptance criteria. +- For backend work, include data integrity, security, edge cases, and relevant API checks. + +## Expected worker reports + +- Coder ends with `CODER_DONE` and lists changed files, implementation, verification, and limits. +- Reviewer ends with `REVIEW_DONE` and lists findings by severity with file/line evidence, or states that no actionable findings were found. +- Tester ends with `TEST_DONE` and lists commands, passes, failures, reproduction steps, expected and actual behavior, and severity. + +This workflow adapts the narrow-role and read-only review patterns from the official OpenAI Docs on subagents. diff --git a/.opencode/agent/reviewer.md b/.opencode/agent/reviewer.md new file mode 100644 index 0000000..fa43f38 --- /dev/null +++ b/.opencode/agent/reviewer.md @@ -0,0 +1,25 @@ +--- +description: Independent read-only reviewer. Analyzes the coder's diff, classifies findings by severity, and reports REVIEW_DONE. Never edits files. +mode: subagent +permission: + edit: deny +--- + +# Reviewer / Designer + +You are an independent reviewer in an opencode agent team. Your session is read-only: do not edit code, tests, snapshots, or configuration. Analyze Coder's result and report to the Orchestrator. + +## Review method + +1. Read the assignment, acceptance criteria, `AGENTS.md`, and relevant specifications. Review the actual diff (`git diff`) and enough surrounding code to understand behavior. +2. Trace changed execution paths. Prioritize defects with user impact. Cite exact files and lines, explain the consequence, and give a reproduction or concrete failure scenario where possible. +3. For frontend/UI, inspect UX, visual hierarchy, consistency, responsive widths, touch targets, keyboard and screen-reader access, loading/error/empty states, component boundaries, and state handling. Use an available browser or screenshot tool for visual claims; if none exists, say what remains unverified. +4. For backend/general code, inspect architecture, data flow, edge cases, security, validation, race conditions, error handling, tests, and violations of existing patterns. +5. Classify findings: **Critical** blocks safe use or risks loss/security; **Major** breaks a supported workflow or causes a material regression; **Minor** is a bounded quality issue. Do not inflate speculative concerns. +6. Separate confirmed findings from questions. If there are no actionable findings, state that plainly. Do not fill the report with style-only comments or restatements of Coder's work. + +## Report format + +- Findings in severity order, each with file/line, impact, and evidence. +- Verification performed and any limits, especially missing visual checks. +- End the final response with `REVIEW_DONE`. diff --git a/.opencode/agent/tester.md b/.opencode/agent/tester.md new file mode 100644 index 0000000..91565e3 --- /dev/null +++ b/.opencode/agent/tester.md @@ -0,0 +1,27 @@ +--- +description: Independent QA. Verifies the coder's finished result, runs checks, and reports TEST_DONE. Does not modify tracked files. +mode: subagent +permission: + edit: deny +--- + +# Tester / QA + +You are independent QA in an opencode agent team. Verify Coder's finished result and report to the Orchestrator. Do not fix product code or tests; you must not modify tracked project files (build tools may create ignored artifacts). + +## Test method + +1. Read the assignment, acceptance criteria, `AGENTS.md`, and Coder's changed-file report. Inspect the diff enough to identify high-risk paths. +2. Run relevant existing lint, build, and unit tests. Run integration or end-to-end tests when configured. Use Playwright/Cypress or another browser tool if present; verify responsive UI, console errors, network/API behavior, and core flows. Do not claim visual verification from code inspection alone. +3. Exercise positive, negative, boundary, refresh/navigation, and recovery scenarios that matter to the change. Prefer focused tests before broad test suites when a failure needs isolation. +4. Respect project safety rules: external Google/MeTube calls are mocked by default; do not perform real integration calls unless the task explicitly opts in. Never delete production data or change external state as a test without authorization. +5. Capture exact commands and observations. If a check is blocked by environment, distinguish that from an application failure and explain the blocker. +6. After verification, check `git status --short`. Report any tracked file that test commands changed; do not clean it up without coordination. + +## Report format + +- Scope and commands run. +- Passed checks. +- Failed checks with reproduction steps, expected versus actual behavior, evidence, and Critical/Major/Minor severity. +- Untested areas and why. +- End the final response with `TEST_DONE`. diff --git a/AGENTS.md b/AGENTS.md index 625f309..1c4f6bb 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,44 +1,31 @@ # AGENTS.md -Полное ТЗ: [`youtube_categories_metube_TZ.md`](./youtube_categories_metube_TZ.md). Перед любой существенной работой сверяйся с ним — этот файл лишь выжимка ограничений. +Правила для персонального YouTube-клиента с категориями подписок и интеграцией с существующим MeTube. Основные этапы разработки (каркас, OAuth/подписки, категории, лента, воспроизведение и MeTube) уже реализованы; ориентируйся на фактический код и `README.md`. -## Жёсткие ограничения (раздел 34 ТЗ) +## Архитектурные ограничения 1. Не форкать/копировать Tube Archivist. -2. Не встраивать yt-dlp в этот сервис — скачивание только через существующий MeTube (`mediaVM`). +2. Не встраивать yt-dlp в этот сервис — скачивание выполняет существующий MeTube на `mediaVM`. 3. Не дублировать функции скачивания MeTube. -4. Не монтировать media storage MeTube на `hermesVM` — видео не хранится локально, только проксируется/линкуется. +4. Не монтировать media storage MeTube на `hermesVM`: видео не хранится локально, только проксируется или линкуется. 5. Не реализовывать YouTube Recommendations/Home. -6. Не делать multi-user — приложение строго single-user (`ALLOWED_GOOGLE_EMAIL`). -7. Frontend не должен напрямую вызывать Google API. -8. Frontend не должен напрямую вызывать MeTube write API (`POST /add` и т.п.) — только через наш backend. -9. Не восстанавливать filename скачанного видео из YouTube title — использовать только точное имя, пришедшее от MeTube по событию `completed`. -10. Не удалять существующие файлы MeTube. +6. Приложение остаётся single-user (`ALLOWED_GOOGLE_EMAIL`). +7. Frontend не вызывает Google API напрямую. +8. Frontend не вызывает MeTube write API (`POST /add` и т.п.) напрямую — только через наш backend. +9. Не восстанавливать имя скачанного файла из YouTube title: использовать точное имя из события MeTube `completed`. +10. Не удалять существующие файлы MeTube при синхронизации или обслуживании; удаление по явному действию пользователя — отдельный поддерживаемый сценарий. 11. Не менять исходный код MeTube. -12. Все внешние base URL (MeTube, Google) — через env/config, не хардкодить. +12. Все внешние base URL (MeTube, Google) задаются через env/config. 13. REST API должен оставаться пригодным для будущего mobile/PWA клиента. -## Порядок фаз (раздел 33 ТЗ) - -Не реализовывать следующую фазу, пока не работает предыдущая: - -1. Skeleton (текущая фаза) — FastAPI, PostgreSQL, Alembic, React, Docker Compose, healthcheck. -2. Google OAuth + subscriptions sync + channels UI. -3. Categories CRUD + many-to-many + фильтр по категориям. -4. Video sync/feed (uploads playlists, playlistItems, videos, background scheduler). -5. YouTube playback (embed). -6. MeTube integration (`/add`, download_jobs, Socket.IO consumer, media URL). -7. Hardening (retry/recovery, logging, quota handling, tests, README). - ## Структура и соглашения -- `migrations/` — Alembic, живёт в корне репозитория (не внутри `backend/`), импортирует модели из `backend/app`. -- Схема БД добавляется миграциями инкрементально по фазам (см. раздел 11 ТЗ), а не одним махом в Phase 1. -- MeTube-специфичные HTTP/Socket.IO вызовы должны быть инкапсулированы в отдельный класс `MeTubeClient` (раздел 36 ТЗ) — не размазывать по backend. -- Секреты только через `.env` (см. `.env.example`), никогда не коммитить `.env`, refresh token, client secret. -- Backend тесты — `pytest`, лежат в `tests/` в корне. Integration-тесты против Google/MeTube — mocked по умолчанию; реальные вызовы к `http://192.168.8.177:8081` — только opt-in, не в обычном CI. -- **В unit-тестах не использовать `with TestClient(app) as client:`** — это запускает lifespan приложения, который с Phase 6 реально стучится в MeTube (Socket.IO) и в Postgres (`reconcile_on_startup`). Используй `TestClient(app)` без `with` (lifespan не запускается, дефолтное поведение starlette) — так и сделано во всех текущих тестах. +- `migrations/` — Alembic в корне репозитория; миграции импортируют модели из `backend/app`. Схему БД изменять миграциями, не создавать и не менять ad hoc при старте приложения. +- MeTube-специфичные HTTP/Socket.IO вызовы инкапсулировать в `MeTubeClient`, не размазывать по backend. +- Секреты хранить только в `.env` (образец — `.env.example`); не коммитить `.env`, refresh token или client secret. +- Backend тесты — `pytest` в корневом `tests/`. Интеграции с Google/MeTube по умолчанию mock; реальные вызовы к `http://192.168.8.177:8081` — только opt-in, не в обычном CI. +- В unit-тестах использовать `TestClient(app)` без `with`: контекстный менеджер запускает lifespan с подключением к MeTube (Socket.IO) и Postgres (`reconcile_on_startup`). -## Запуск/проверка +## Запуск и проверка См. `README.md`. Быстрая проверка: `docker compose up -d --build` и `curl http://localhost:8080/api/health`. diff --git a/AGENT_TEAM.md b/AGENT_TEAM.md new file mode 100644 index 0000000..bfb8954 --- /dev/null +++ b/AGENT_TEAM.md @@ -0,0 +1,28 @@ +# Агентская команда opencode + +Одна сессия opencode в роли **Orchestrator** и три субагента — **Coder**, **Reviewer**, **Tester**, которых Orchestrator вызывает через Task tool. Роли лежат в [`.opencode/agent/`](./.opencode/agent/), Orchestrator назначен агентом по умолчанию в [`opencode.json`](./opencode.json). + +## Запуск и работа + +В корне репозитория запусти `opencode`. Каждая новая сессия стартует Orchestrator'ом: задачу пиши обычным сообщением. Он сам: + +- декомпозирует задачу и отправляет её Coder'у через Task tool; +- после Coder'а запускает Reviewer (только чтение) и Tester (проверка без правок) параллельно; +- возвращает Coder'у actionable findings и повторяет цикл, пока Critical/Major не закрыты; +- отдаёт финальный отчёт с изменениями, проверками и оставшимися рисками. + +Вручную писать субагентам не нужно — их вызывает только Orchestrator. + +## Роли + +- **Orchestrator** (primary, агент по умолчанию) — единственный общается с пользователем; сам код не правит. +- **Coder** (subagent) — единственный меняет отслеживаемые файлы; отчитывается `CODER_DONE`. +- **Reviewer** (subagent, `edit: deny`) — read-only review; отчитывается `REVIEW_DONE`. +- **Tester** (subagent, `edit: deny`) — прогоняет проверки; может создавать игнорируемые артефакты, но не меняет отслеживаемые файлы; отчитывается `TEST_DONE`. + +## Файлы + +- `.opencode/agent/*.md` — определения агентов команды; +- `opencode.json` — `default_agent: orchestrator`. + +Конфиг и агенты читаются при старте opencode: после изменений перезапусти сессию. diff --git a/README.md b/README.md index 8943fb9..69a9099 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,9 @@ # MyYouTube Персональный YouTube-клиент с категориями подписок и интеграцией с существующим MeTube. -Полное техническое задание: [`youtube_categories_metube_TZ.md`](./youtube_categories_metube_TZ.md). -UI/UX-спека (дизайн-система и сценарии интерфейса): [`UI_UX_spec.md`](./UI_UX_spec.md). +Работа агентской команды (opencode) описана в [руководстве по агентской команде](./AGENT_TEAM.md). -Текущий статус: **функционально готово** (Phases 1-6 из ТЗ + hardening по ходу тестирования). Развёрнуто и вручную протестировано на тестовом окружении (`testmyyoutube.vrubel.xyz` → hermesVM `192.168.8.173` → MeTube на mediaVM `192.168.8.177`). +Текущий статус: **функционально готово** (основные этапы 1–6 и доработки по итогам тестирования). Развёрнуто и вручную протестировано на тестовом окружении (`testmyyoutube.vrubel.xyz` → hermesVM `192.168.8.173` → MeTube на mediaVM `192.168.8.177`). ### Что сделано @@ -12,13 +11,13 @@ UI/UX-спека (дизайн-система и сценарии интерфе - Раздел **«На сервере»** — скачанные видео; `/saved` перенаправляет на `/local`. - Тёмный адаптивный интерфейс: общая навигация, категории с постоянными URL, поиск по названиям видео и каналов, мобильное меню. - Отписанные каналы скрываются из списка каналов. -- **Отклонение от исходного MVP ТЗ (раздел 2)**: реальная отписка от канала на YouTube (`subscriptions.delete`) реализована по явной просьбе пользователя, хотя ТЗ изначально исключало управление подписками из MVP. Из-за этого OAuth scope расширен с `youtube.readonly` до полного `youtube` (read/write) — см. `backend/app/services/google_oauth.py`. +- Реальная отписка от канала на YouTube (`subscriptions.delete`) реализована по запросу пользователя сверх исходного MVP. Из-за этого OAuth scope расширен с `youtube.readonly` до полного `youtube` (read/write) — см. `backend/app/services/google_oauth.py`. - Удаление скачанного видео с сервера (`DELETE /api/videos/{id}/download`) — тоже сверх исходного MVP-скоупа, добавлено по запросу. ### Что осталось / сознательно отложено -- Остальные пункты раздела 35 ТЗ («Возможные улучшения после MVP») не делались: PWA, watch later, Shorts-фильтр, SponsorBlock и т.д. -- Формальный Phase 7 hardening из ТЗ (retry/recovery, тесты) закрывался по факту находок в live-тестировании, а не отдельным проходом — см. `git log` для конкретных багфиксов (flapping статусов загрузки, неверный id в MeTube `/delete`, OAuth scope mismatch). +- Дополнительные идеи после MVP пока не реализованы: PWA, watch later, Shorts-фильтр, SponsorBlock и т.д. +- Hardening (retry/recovery, тесты) выполнялся по факту находок в live-тестировании, а не отдельным проходом — см. `git log` для конкретных багфиксов (flapping статусов загрузки, неверный id в MeTube `/delete`, OAuth scope mismatch). ## Стек @@ -46,6 +45,25 @@ UI/UX-спека (дизайн-система и сценарии интерфе ## Локальная разработка +### Агентская команда (opencode) + +Подробная инструкция: [AGENT_TEAM.md](./AGENT_TEAM.md). + +Работа идёт в одной сессии opencode: она стартует агентом `orchestrator` +(`default_agent` в `opencode.json`). Orchestrator сам декомпозирует задачу и +вызывает субагентов через Task tool: `coder` (единственный меняет +отслеживаемые файлы), `reviewer` (read-only review, `edit: deny`) и `tester` +(проверки без правок, `edit: deny`). Роли описаны в +[`.opencode/agent/`](./.opencode/agent/). + +```bash +opencode +``` + +Задачу пиши обычным сообщением: Orchestrator передаст её Coder'у, соберёт +review и QA, повторит цикл при находках и вернёт финальный отчёт. Вручную +писать субагентам не нужно. + ### Backend ```bash @@ -96,7 +114,8 @@ myyoutube/ ├── Dockerfile ├── compose.yml ├── .env / .env.example -└── youtube_categories_metube_TZ.md +├── .opencode/ # агенты команды (orchestrator/coder/reviewer/tester) +└── AGENT_TEAM.md # руководство по агентской команде opencode ``` ## Секреты diff --git a/UI_UX_spec.md b/UI_UX_spec.md deleted file mode 100644 index 3f2ad5c..0000000 --- a/UI_UX_spec.md +++ /dev/null @@ -1,1018 +0,0 @@ -# UI/UX-спецификация: персональный YouTube-клиент - -**Назначение:** передать coding agent вместе с основным техническим заданием. -**Формат:** single-user self-hosted web app, в будущем PWA/mobile client. - -## 1. Концепция - -Приложение должно ощущаться как персональная лента YouTube-подписок, но с нормальной категоризацией каналов и локальным хранением выбранных видео. - -Пользовательский сценарий: - -```text -Открыть приложение -→ выбрать категорию -→ увидеть последние видео каналов этой категории -→ смотреть через YouTube -→ при желании нажать "Скачать" -→ после загрузки видеть "На сервере" -→ дальше смотреть локальную копию через MeTube -``` - -Интерфейс не должен выглядеть как admin panel. Это consumer media UI. - ---- - -## 2. Общий layout - -Desktop: - -```text -┌──────────────────────────────────────────────────────────────────────┐ -│ Header │ -├──────────────────────┬───────────────────────────────────────────────┤ -│ Sidebar │ Main content │ -│ │ │ -│ Все видео │ Video feed │ -│ Без категории │ │ -│ На сервере │ │ -│ │ │ -│ КАТЕГОРИИ │ │ -│ Linux │ │ -│ Шахматы │ │ -│ Научпоп │ │ -│ │ │ -│ Каналы │ │ -│ Настройки │ │ -└──────────────────────┴───────────────────────────────────────────────┘ -``` - -Sidebar: 220–260 px. -Header: фиксированный сверху. -Main content: responsive grid. - -Mobile: sidebar превращается в drawer/hamburger. - ---- - -## 3. Визуальный стиль - -Тёмная тема по умолчанию. - -Рекомендуемые design tokens: - -```text -Background main #0F0F0F -Surface #181818 -Surface hover #272727 -Border #303030 -Primary text #F1F1F1 -Secondary text #AAAAAA -Accent #3EA6FF -Success/local #2BA640 -Warning/downloading #F5A623 -Error #E53935 -``` - -Использовать CSS variables, а не раскидывать цвета по компонентам. - -Типографика: - -```text -Inter / Roboto / system-ui -Page title 24px / 600 -Section title 18px / 600 -Video title 15–16px / 500 -Metadata 12–14px -Buttons 14px / 500 -``` - ---- - -## 4. Header - -Пример: - -```text -┌──────────────────────────────────────────────────────────────────────┐ -│ ▶ MyTube 🔍 Поиск... ✓ 12 мин ⚙ │ -└──────────────────────────────────────────────────────────────────────┘ -``` - -Слева: -- логотип; -- рабочее название `MyTube` или configurable app name. - -Центр: -- поиск по локальной БД: video title + channel title. - -Справа: -- sync indicator; -- settings. - -Sync states: - -```text -✓ Обновлено 12 мин назад -↻ Обновление... -⚠ Ошибка синхронизации -``` - -По клику на sync indicator: - -```text -Подписки: обновлены 20:11 -Видео: обновлены 20:12 -MeTube: доступен -``` - ---- - -## 5. Sidebar - -Пример: - -```text -● Все видео - Без категории - На сервере - -КАТЕГОРИИ - -🐧 Linux -♟ Шахматы -🔬 Научпоп -🎮 Игры - -+ Новая категория - -──────────── - -Каналы -Настройки -``` - -Активная категория должна иметь заметный, но спокойный selected state. - -`Все видео`, `Без категории`, `На сервере` — виртуальные категории. - ---- - -## 6. Feed - -Route: - -```text -/ -``` - -или: - -```text -/feed -``` - -Header выбранной категории: - -```text -Linux -24 канала -Обновлено 12 минут назад [↻ Обновить] -``` - -Опциональные chips: - -```text -[Все] [На сервере] [Не скачаны] -``` - -Сортировка видео: - -```text -published_at DESC -``` - ---- - -## 7. Video grid - -Responsive: - -```text ->= 1800 px → 5 columns ->= 1400 px → 4 columns ->= 1000 px → 3 columns ->= 700 px → 2 columns -mobile → 1 column -``` - -Рекомендуемый CSS: - -```css -grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); -``` - ---- - -## 8. Video card - -Пример: - -```text -┌────────────────────────────────────┐ -│ │ -│ THUMBNAIL │ -│ 18:42 │ -│ │ -├────────────────────────────────────┤ -│ Linux Kernel 7.0 — What's New │ -│ The Linux Experiment │ -│ 2 часа назад │ -│ │ -│ [▶ Смотреть] [⬇ Скачать] │ -└────────────────────────────────────┘ -``` - -Thumbnail: -- aspect ratio 16:9; -- duration badge справа снизу. - -Карточка содержит: -- thumbnail; -- title; -- channel name; -- publish time; -- duration; -- download/local state; -- Watch; -- Download. - ---- - -## 9. Состояния скачивания - -### Не скачано - -```text -[▶ Смотреть] [⬇ Скачать] -``` - -### В очереди - -```text -[▶ Смотреть] [В очереди...] -``` - -### Скачивается - -```text -[▶ Смотреть] [64%] -``` - -Дополнительно progress-line внизу thumbnail. - -### Post-processing - -```text -[▶ Смотреть] [Обработка...] -``` - -### На сервере - -```text -[▶ Смотреть] [✓ На сервере] -``` - -### Ошибка - -```text -[▶ Смотреть] [⚠ Повторить] -``` - -Не показывать traceback. - ---- - -## 10. Video page - -Route: - -```text -/video/:youtubeVideoId -``` - -Desktop: - -```text -┌──────────────────────────────────────────────────────────────────┐ -│ │ -│ VIDEO PLAYER │ -│ │ -└──────────────────────────────────────────────────────────────────┘ - -Linux Kernel 7.0 — What's New - -The Linux Experiment -2 часа назад [⬇ Скачать] - -Источник: YouTube - -[Linux] [IT] - -──────────────────────────────────────────────────────────────────── - -Описание видео... -``` - -Если local copy существует: - -```text -Источник: ● Локальная копия · mediaVM -``` - -и вместо YouTube iframe используется HTML5 `