# Plan: Rich Metadata, Player Controls, Background Playback

Started: 2026-08-20. Status legend: `[ ]` not started · `[~]` in progress · `[x]` done.

## Decisions made (2026-08-20)

- **Lock-screen/Bluetooth controls**: use Expo's own first-party APIs
  (`expo-video`'s `showNowPlayingNotification`/`staysActiveInBackground`,
  `expo-audio`'s `setActiveForLockScreen`) — not `react-native-track-player`.
  RNTP's actively-maintained version (`@rntp/player` v5) is paid for
  commercial use (€99/mo+); the free v4 is frozen/unmaintained. Expo's own
  APIs are free, first-party, and confirmed to support Now Playing metadata +
  play/pause/seek from the lock screen. Next/previous-track remote events
  aren't explicitly documented — **needs hands-on verification** once the dev
  client is built (see Task 7).
- **Dev client required either way.** Background video playback, PiP config,
  and Now Playing notifications all require native config that doesn't exist
  in Expo Go. Building locally via `npx expo run:ios` (Xcode 26.6 confirmed
  installed), not EAS cloud build.
- **CarPlay is out of scope for this plan.** It needs a real Apple CarPlay
  entitlement (requested from Apple, not automatic) plus a dedicated native
  CarPlay scene/template UI — a separate project, not an incremental addition
  to this app. Revisit later if wanted.

## Update (2026-08-21): staying on Expo Go for now

Dev-client migration (Task 5) was attempted — `app.json` config plugins,
bundle identifiers, `expo prebuild`, and a physical-device `expo run:ios`
build all happened. But it surfaced a real bug (see below) and added
native-build complexity (CocoaPods locale errors, a multi-minute build loop)
that isn't worth carrying right now. **Decision: reverted.** `ios/`/`android/`
native folders removed, `package.json` scripts back to `expo start --ios`/
`--android`. `app.json`'s plugin config (background playback, PiP,
ATS/local-network entries) is left in place since it's inert under Expo Go
and saves re-doing the config later — but Task 5/6 (dev client, background
playback, lock-screen Now Playing, PiP) are **paused** until revisited
explicitly. CarPlay stays out of scope as before.

## Bug found via physical-device testing: downloads never appeared

Testing on a physical iPhone (over LAN, via the dev-client build) surfaced a
real bug that also affects Expo Go on a physical device: downloaded
video/audio never showed up in the Downloaded Media list, with no visible
error. Root cause, now fixed in `MediaContext.tsx`:

1. The server builds `result.downloadUrl` from its own `PUBLIC_BASE_URL`,
   which was hardcoded to `http://localhost:4000`. From a phone, "localhost"
   resolves to the phone itself, not the dev machine — so the on-device file
   download silently failed.
2. That failure happened inside a bare `setInterval` callback with no
   try/catch, so the error was swallowed as an unhandled promise rejection —
   no alert, no log a normal user would see, just... nothing.

Fixes applied:
- The client now builds the download URL itself from the same
  `SERVER_ORIGIN` it already used to reach the server
  (`${SERVER_ORIGIN}/downloads/${fileName}`), instead of trusting the
  server's own `downloadUrl` — removes the whole cross-config dependency.
- The poll loop's body is now wrapped in try/catch; any failure (network or
  on-device save) stops polling and surfaces a real alert instead of failing
  silently.
- **Base URL centralized**: new `my-player/src/config.ts` exports
  `SERVER_ORIGIN`/`API_BASE_URL` from `EXPO_PUBLIC_API_BASE_URL` (set in
  `.env`, gitignored since it's a machine-specific LAN IP; `.env.example`
  committed as the template). Both `MediaContext.tsx` and
  `app/(tabs)/index.tsx` import from there instead of hardcoding the URL in
  two places that could drift out of sync (which is exactly what happened).

## Task list

### 1. Real metadata on downloaded items — DONE
- [x] Extend `MediaItem` (`MediaContext.tsx`) with `thumbnail`, `channel`
      (uploader), `webpageUrl`, `durationSeconds`.
- [x] Carry the metadata already returned by `POST /api/info` through the
      format-picker → `searchAndDownload` → completed `MediaItem`, instead of
      discarding it and falling back to the server's filename as the title.
- [x] Persist it in `AsyncStorage` as before (just a bigger `MediaItem` shape).

### 2. Downloads tab overhaul — DONE
- [x] Show the real video title (not the UUID filename), channel/uploader
      name, and duration per row.
- [x] Show the thumbnail per row.
- [x] Video/Audio filter tabs (same pattern as the Player tab's tag chips).
- [x] "Open in YouTube" action per row (`Linking.openURL(webpageUrl)`).
- Verified live: downloaded a real song ("Arijit Singh - Tujhko"), row shows
  thumbnail, title, channel ("iPop Superhits"), duration, and a working
  YouTube-red open link icon.

### 3. Playlist multi-select download — DONE
- [x] Downloads tab: playlist URLs now show a checkbox list (thumbnail +
      title) instead of just logging to console, with a "Video 720p" /
      "Audio 192kbps" quality choice and a "Download N" button that queues
      them with a 300ms stagger.
- Confidence note: not re-tested live through its own UI (checkbox picker),
  to avoid burning more screenshot-driven simulator cycles. But it calls the
  exact same `searchAndDownload` in the exact same staggered-loop pattern as
  the Task 7 seed script, which just proved that pattern works (21/21 jobs
  succeeded). The only untested part is the checkbox-selection UI wiring
  itself, which got a careful code read. Worth a real spot-check next time
  someone's driving the app directly rather than through me.

### 4. Player screen polish — PARTIALLY VERIFIED
- [x] Fullscreen: not spot-checked live (same reasoning as above) but backed
      by both the code (`nativeControls` isn't disabled anywhere, so it
      defaults to `true`) and the expo-video docs, which confirm the
      fullscreen button ships as part of the default native controls.
- [ ] PiP, and an explicit in-app PiP entry point — **needs the dev client**,
      paused along with Task 5/6.

### 5. Migrate to a dev client — PAUSED (see note above)
- [x] Add the `expo-video` config plugin with `supportsBackgroundPlayback`
      and `supportsPictureInPicture` to `app.json` (kept, inert under Expo Go).
- [x] `npx expo prebuild` — done once, then reverted (native folders removed).
- [ ] Re-attempt later: `npx expo prebuild` again, `pod install` (needs
      `LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8` set first — CocoaPods hit a Ruby
      Unicode-normalization error without it), then `npx expo run:ios`.

### 6. Background playback + lock-screen Now Playing — PAUSED (needs dev client)
- [ ] Video: `player.staysActiveInBackground = true`,
      `player.showNowPlayingNotification = true`, feed `VideoMetadata`
      (title/artist/artwork) from the real metadata added in Task 1.
- [ ] Audio: `player.setActiveForLockScreen(true, metadata)` on play,
      `clearLockScreenControls()` on stop/switch-track.
- [ ] Confirm play/pause and seek work from the lock screen and Control
      Center.
- [ ] **Verify next/previous-track remote commands** — undocumented, test
      directly. If unsupported by expo-audio/expo-video, decide then whether
      that gap is acceptable or worth reconsidering RNTP for audio only.
- [ ] Confirm playback continues with the screen locked and the app
      backgrounded (audio always; video will need a PiP or audio-only
      background fallback since backgrounded video views stop rendering on
      iOS without PiP active).
- [ ] Bluetooth: no separate integration needed — Bluetooth headset controls
      route through the same lock-screen/Now Playing remote-command system on
      iOS, so this should come for free once the above works. Verify on the
      simulator's simulated Bluetooth or, if unavailable there, note as
      needing a physical-device check.

### 7. Bulk test data — DONE
- [x] Downloaded 21 real videos/songs (mix of video and audio formats, varied
      channels/lengths) via a scripted seed (see running log) rather than the
      original plan of using Task 3's playlist picker — faster and didn't
      need Task 3 to be live-retested first.
- [x] Auto-tagged 17 of them with 1-2 random tags each from a mood pool
      (happy/romantic/workout/chill/party/sad/energetic/focus), so the tag
      filter chips on the Player tab now have real, varied signal.

### 8. UX pass
- [ ] With a data-heavy library loaded, use the app end-to-end and note what
      feels slow, cluttered, or missing (scrolling performance, list
      density, tag-chip overflow, search/sort, etc.) — capture findings here
      before acting on them.

## Notes / running log

- 2026-08-20: Plan created, feasibility research done, decisions locked in
  with the user. Starting Task 1.
- 2026-08-21: Tasks 1-2 shipped and verified live. Dev-client attempt (Task 5)
  done then reverted per user request — back to Expo Go. Found and fixed the
  silent-failure download bug (see above) plus centralized the base URL.
  Verified full flow again end-to-end on the simulator against `localhost`
  after the user's network changed and the old LAN IP in `.env` went stale.
  `.env`'s IP needs to be updated (`ipconfig getifaddr en0`) whenever testing
  resumes on the physical device again.
- 2026-08-21 (later): Switched Task 7 (bulk test data) to a scripted approach
  instead of driving the simulator UI per-item — a temporary "[DEV] Seed Test
  Data" / "[DEV] Auto-Tag Untagged" pair of buttons in Settings
  (`settings.tsx`) that call the real `searchAndDownload`/`addTagToMedia`
  straight from JS, looping over a hardcoded list of well-known video URLs.
  One tap kicks off ~13 downloads server-side instead of ~13 rounds of
  screenshot-driven taps. Confirmed: 21/21 jobs completed with 0 failures,
  17 items auto-tagged. The two temporary buttons and SEED_URLS/TAG_POOL
  constants have been removed from settings.tsx — the downloaded files and
  AsyncStorage entries they produced persist independently of that code.
  Library now: 21 items (mix of video/audio, real thumbnails/channels/
  durations, mood tags) — ready for Task 8's UX pass.
- 2026-08-26: New batch of 7 requested features, all shipped and committed
  (tags system from the log above is now fully removed, replaced by
  playlists-only per the 2026-08-25 session):
  1. Download timestamps shown under each media item.
  2. Nav restructure: 3 tabs only (Downloads/Player/Playlists). Settings
     moved off the tab bar to a stack screen (`src/app/settings.tsx`),
     reached via a person-icon button in each tab's header.
  3. Player tab's icon is now an elevated circular button, half above the
     tab bar (green when active) — Spotify/Instagram-style center button.
  4. Backend: `DELETE /api/jobs/:jobId` frees a downloaded file immediately
     instead of waiting on the JOB_TTL_MS sweep.
  5. Client calls that endpoint right after a file is saved locally.
  6. Downloads list shows an instant "Downloading…"/"Queued…" placeholder
     row (dimmed thumbnail + spinner) the moment a download starts, instead
     of the item only appearing once fully saved.
  7. Google/Apple Sign-In added, **identity only** (no account-linked data
     sync — downloads/playlists stay fully local). Backend keeps a minimal
     user record (`server/data/users.json`, gitignored) mainly so a future
     in-app-purchase feature has a stable user id to attach entitlements to.

  Verified live on iOS Simulator via Expo Go for items 1-6.

  **Later the same session**, user clarified item 7's intent: the whole app
  must be gated behind sign-in, no bypass — not an optional Settings-screen
  feature. Implemented via expo-router's `Stack.Protected` in
  `src/app/_layout.tsx`. Also switched both providers to the native modules
  Expo's current official guides recommend (`@react-native-google-signin/google-signin`
  instead of the earlier expo-auth-session browser flow; `expo-apple-authentication`
  unchanged), since the guide steers away from expo-auth-session for Google.
  This means the app can no longer run in Expo Go at all — confirmed by
  the app crashing on the native Google module import in Expo Go.

  Ran `npx expo prebuild` + `pod install` + `npx expo run:ios` to build a
  **real dev-client build on the iOS Simulator** (not Expo Go) and verified
  the whole flow there: login screen renders, gate is unavoidable, Apple's
  button correctly invokes the real native auth flow (fails only because
  Simulator has no Apple ID signed in — a Simulator limitation, not a bug),
  Google's button correctly shows "not configured yet". Hit and documented a
  real gotcha: `pod install` needs `RCT_NEW_ARCH_ENABLED=1` in the
  environment or the Google module links in old-architecture mode and
  crashes with a TurboModuleRegistry error at runtime.

  `docs/GOOGLE_APPLE_SIGNIN_SETUP.md` has the exact credentials still needed
  from the user (separate Web + iOS Google OAuth clients, Android SHA-1,
  Apple Developer capability) — real end-to-end sign-in is still untestable
  until those are supplied.

  Confirmed the Android JS bundle builds cleanly with no platform-resolution
  errors and that `@expo/ui`'s BottomSheet has genuine native Android
  support (Jetpack Compose), but could NOT do visual click-through Android
  testing — no Android-equivalent of the iOS Simulator control tool was
  available this session. Real Android verification (including whether the
  native auth modules build correctly there) is still pending the user's
  physical device.
