md2link

Player — Feature Spec (PO View)

DraftMay 25, 2026

Player — Feature Spec (PO View)

Spec dành cho Product Owner — mô tả tính năng Player của AI Music MV Maker (brand "Melix") theo đúng những gì đã có trong code hiện tại (branch feat/pixelplayer-port-redo).

Player là trình phát nhạc thống nhất cho AI-gen songs + nhạc thư viện thiết bị, dạng sheet overlay xuyên suốt app.

⚠️ Quy ước: tài liệu này CHỈ giữ những gì đã wired-UI và chạy thực tế trong code hiện tại. Các tính năng có code nhưng chưa wire UI (Queue sheet, Sleep timer picker, Song Info, Share, Save Queue, Album art carousel, Multi-select queue, Cast, Lyrics, EQ…) tạm bỏ — đợi PO confirm thứ tự enable trước khi đưa vào spec.

Liên quan: Unified Player Sheet trong spec.md.


1. Mục tiêu sản phẩm

Player là thanh phát nhạc always-on dưới đáy app — user nghe AI song vừa tạo hoặc nhạc thư viện thiết bị, vẫn duyệt được tab Create / Library / Settings mà không gián đoạn. Tận dụng codebase PixelPlayer (MIT) để rút ngắn thời gian build.

Trong scope hiện tại

  • Mini player ngang neo trên bottom-nav.
  • Full player full-screen với album art + controls cơ bản.
  • Sheet states Hidden / Mini / Full với drag-to-expand + predictive back.
  • Favorite toggle (đã wired với Favorites repository).
  • Persistent across tab swipes.
  • Dynamic theme từ album art (opt-in qua remote config).
  • External file open (ExternalPlayerActivity — VIEW/SEND intent).
  • Notification lock-screen + sleep-timer-fires-pause (chạy ngầm qua service).

Ngoài scope hiện tại

Queue UI sheet, Sleep timer picker, Song Info sheet, Share button, Save Queue, Cast, Lyrics, EQ, Album art carousel, Multi-select trong queue — code đã có ở holder/scoped state nhưng chưa wire UI trigger trong Full player. Sẽ bổ sung sau khi PO chốt thứ tự enable.


2. User Story chính

Là user vừa tạo AI song, tôi muốn một thanh player hiện ngay dưới đáy app để play/pause nhanh và mở rộng xem chi tiết — không bị mất khi tôi chuyển tab.


3. Trạng thái Player Sheet — minh hoạ

Player có 3 state chính. Layout đơn giản:

3.1 Hidden — chưa có bài nào / user swipe-dismiss

 ┌────────────────────────────────────┐
 │                                    │
 │                                    │
 │         Library / Create /         │
 │           Settings tab             │
 │                                    │
 │                                    │
 │                                    │
 ├────────────────────────────────────┤
 │  ⌂ Home    🎵 Library    ⚙ Setting │   ← bottom-nav (không có sheet)
 └────────────────────────────────────┘

3.2 Mini — bài đang phát, sheet collapsed

 ┌────────────────────────────────────┐
 │                                    │
 │         Library / Create /         │
 │           Settings tab             │
 │                                    │
 │                                    │
 │ ┌────────────────────────────────┐ │
 │ │ ▢▢▢  Bài hát đang phát   ▶/⏸ │ │   ← Mini ~72dp, neo trên nav
 │ │      Tên nghệ sĩ              │ │     gradient brand blue fade →
 │ └────────────────────────────────┘ │
 │ ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ │   ← hairline progress 1px
 ├────────────────────────────────────┤
 │  ⌂ Home    🎵 Library    ⚙ Setting │
 └────────────────────────────────────┘

 Tương tác:
   • Tap card → expand Full
   • Kéo dọc lên >40dp → expand
   • Vuốt ngang quá ngưỡng → dismiss (Hidden, nhạc vẫn phát ngầm)

3.3 Full — full-screen player

 ┌────────────────────────────────────┐
 │  ⌄        Playing from album       │   ← TopBar:
 │              [tên album]      ♡    │     ⌄ collapse | album | ♡ favorite
 │                                    │
 │         ╔════════════════╗         │
 │         ║                ║         │
 │         ║                ║         │
 │         ║   Album art    ║         │   ← Hero 280dp (TĨNH, không carousel)
 │         ║   280 x 280    ║         │
 │         ║                ║         │
 │         ║                ║         │
 │         ╚════════════════╝         │
 │                                    │
 │           Tên bài hát              │   ← TitleBlock (marquee 1 dòng)
 │            Tên artist              │
 │                                    │
 │  ∿∿∿∿∿●━━━━━━━━━━━━━━━━━━━━━━━━━  │   ← WavyMusicSlider
 │  01:23                       03:45 │     (sóng khi play, thẳng khi pause)
 │                                    │
 │     ⤨      ⏮      ●▶      ⏭     ↻ │   ← MainControls (5 nút):
 │  Shuffle  Prev  Play/Pause Next Rep│     shuffle / prev / play / next /
 │                                    │     repeat (off → all → one)
 │                                    │
 │ Background: gradient brand blue    │
 │ (radial center top), hoặc dynamic  │
 │ palette từ album art (nếu          │
 │ enable_dynamic_palette = true)     │
 └────────────────────────────────────┘

 Tương tác:
   • Tap ⌄ → collapse Mini
   • Kéo dọc xuống → collapse smooth theo finger
   • Bấm back (Android 14+): preview gesture co dần Full→Mini
   • Tap ♡ → toggle favorite (gọi FavoritesRepository)
   • Drag slider → seek

3.4 External Player — Activity riêng

 ┌────────────────────────────────────┐
 │ (Không có MonetizeD7 splash,       │
 │  không có 3-tab shell)             │
 │                                    │
 │         ╔════════════════╗         │
 │         ║                ║         │
 │         ║   Album art    ║         │
 │         ║                ║         │
 │         ╚════════════════╝         │
 │                                    │
 │       Tên file mp3 vừa mở          │
 │                                    │
 │   ━━━━━━●━━━━━━━━━━━━━━━━━━━━━━   │
 │                                    │
 │       [ Mở trong Melix ]           │
 │       [ Đóng ]                     │
 └────────────────────────────────────┘

 Khi nào hiển thị:
   • User chọn file mp3 từ Files app → "Open with Melix"
   • User Share file mp3 từ app khác → chọn Melix
 Bypass: splash + 3-tab shell. Granted FLAG_GRANT_READ_URI_PERMISSION.

4. Entry points (đã wired)

Trigger Hành vi
Tap song row trong Library openSongById → build queue từ tất cả AI-gen songs → auto-show Mini
Tap nút Play trong VideoResult / AI-gen result playItem(item) → auto-show Mini
Tap mini card Mini → Full
Kéo mini dọc lên >40dp Mini → Full
Vuốt ngang mini quá ngưỡng Dismiss (Hidden, nhạc tiếp tục)
Tap ⌄ trong Full Full → Mini
Bấm back (Android 14+) Predictive back — preview Full→Mini theo gesture
Bấm back (Android <14) Instant collapse Full → Mini
Tap ♡ favorite Toggle FavoritesRepository, icon update
Drag slider Seek (optimistic UI + audio seek)
Tap controls (shuffle/prev/play/next/repeat) Delegate xuống AudioController
Vào Create / Preview MV (stopAndHide()) Auto pause + hide. Tap focused song lại → repromote
Mở file mp3 từ Files / Share sheet ExternalPlayerActivity overlay (bypass splash)
Tap notification (lock-screen) Mở MainActivity, sheet ở state hiện tại
Sleep timer hết giờ (qua service ngầm) Service auto pause, sheet giữ Mini

5. Màn hình chi tiết

S1 — Hidden

Sheet không render, không chiếm pixel. Bottom-nav sát đáy bình thường.

S2 — Mini player (~72dp)

  • Layout (trái → phải): album art vuông 56dp + title (1 dòng, truncate) + artist (1 dòng) + nút play/pause.
  • Background card solid #1A1A1A.
  • Gradient brand blue subtle lan từ phải (alpha fade → 0 khi expand).
  • Hairline border 1dp, fade khi expand.
  • Hairline progress bar 1px dưới đáy card theo % bài.
  • Shadow 3dp (tắt khi queue mở hoặc expand > 18%).
  • Corner radius 14dp → 0dp khi expand.
  • Tap toàn card → expand. Kéo dọc lên → expand smooth. Vuốt ngang → dismiss.

S3 — Full player

  • Background: radial gradient brand blue (center top, fade về #020202 ở 70%) hoặc dynamic palette từ album art nếu remote config enable_dynamic_palette = true.
  • TopBar: ⌄ collapse (trái) | "Playing from album" + tên album (giữa) | ♡ favorite (phải).
  • Hero: Album art TĨNH 280×280dp với corner 20dp (CHƯA có carousel/HorizontalPager).
  • TitleBlock: title (subtitle bold, basicMarquee 1 dòng) + artist (caption regular, marquee 1 dòng).
  • SeekRow: WavyMusicSlider (sóng nhấp nhô khi play, thẳng khi pause) + time current / total. Drag = optimistic UI; release = commit seek.
  • MainControls (5 nút ngang): Shuffle | Previous | Play-Pause (big circle) | Next | Repeat (cycle OFF → ALL → ONE).

S4 — External Player overlay

  • Activity riêng, không qua splash.
  • Compact UI: album art + tên file + slider + nút "Open in app" / "Close".

S5 — Predictive Back (Android 14+)

  • User kéo gesture back từ cạnh → sheet co dần Full → Mini theo finger (preview).
  • Release qua ngưỡng → commit Mini; dưới ngưỡng → snap về Full.
  • Android <14: back = instant collapse.

6. Scenarios chính

6.1 First play in session (Happy)

Given chưa phát bài nào (Hidden), When tap row đầu Library, Then sheet slide-up Hidden → Mini + play.

6.2 Expand (Happy)

Given Mini visible, When tap card / kéo lên >40dp, Then animate Mini → Full.

6.3 Favorite toggle (Happy)

Given Full mở, When tap ♡, Then Favorites DB cập nhật, icon đổi (outline ↔ filled đỏ).

6.4 Seek (Happy)

Given Full mở, When drag slider thumb, Then UI follow finger real-time; release → commit seek.

6.5 Skip via button (Happy)

Given Full mở, queue ≥2 bài, When tap ⏭, Then play bài kế.

6.6 Shuffle/Repeat (Happy)

Given Full mở, When tap shuffle / repeat, Then mode cycle (off / all / one), AudioController update.

6.7 Tab swipe persistence (Happy)

Given Mini visible đang phát, When vuốt sang tab Create, Then Mini vẫn ở đó, nhạc tiếp tục.

6.8 Generation flow handover (Happy)

Given đang nghe AI song A, When vào CreateSong, Then auto pause + hide. Về Library tap A lại → repromote + resume.

6.9 Predictive back (Happy, Android 14+)

Given Full mở, When kéo gesture back từ cạnh, Then sheet co dần theo finger; release qua ngưỡng → Mini.

6.10 Mini swipe-dismiss (Happy)

Given Mini visible, When vuốt ngang quá ngưỡng, Then Mini → Hidden, nhạc tiếp tục ngầm.

6.11 Re-tap focused song (Edge)

Given Mini visible với bài A đang play, When tap row A lần nữa, Then no-op (không restart).

6.12 Re-tap khi sheet Hidden (Edge)

Given sheet Hidden (vừa swipe-dismiss) nhưng bài A vẫn ở currentItem, When tap row A, Then repromote sheet → Mini + resume.

6.13 External file open (Happy)

Given user chọn "Mở bằng Melix" từ Files app, When Activity nhận intent, Then ExternalPlayerActivity overlay compact, play ngay.

6.14 Notification controls (Happy)

Given đang play, When lock màn hình, Then notification hiện play/pause/next với artwork. Tap notification → mở MainActivity.

6.15 Connectivity error (Edge)

Given đang play streaming, mạng yếu, When ExoPlayer error, Then ConnectivityStateHolder phát hiện offline; (UI surface defer — chỉ holder ready).

6.16 Process killed (Edge)

Given đang phát, When process killed, Then service foreground giữ playback; nếu service cũng bị kill → lần mở lại sheet Hidden + queue trống.