feat(audio): Audio engine — low-TTFB preload + offline-resilient playback #54

Open
KnallerKalle wants to merge 4 commits from feat/audio-engine into dev
KnallerKalle commented 2026-08-11 22:36:35 +00:00 (Migrated from github.com)

Plan

Deliver the Core Platform "Audio engine (preload, low-TTFB, offline-resilient)" roadmap item — a client-side audio engine that preloads lesson audio into an in-memory + Cache API store so the first "play" is instant and playback survives a network blip mid-lesson, without ever surfacing buffering as strain or judgement.

Outcome

  • src/lib/audio-engine.ts (new): URL resolution against the base CDN, preloadAudio (in-memory dedupe + Cache API persistence), loadAudioResilient (cache-first recovery), fetchWithTtfb (TTFB measurement), and idempotent request coalescing (concurrent preloads of the same URL share one network fetch).
  • src/components/AudioPlayer.tsx (wired): a preload useEffect feeds a preloaded blob URL to the <audio> element; <audio preload> bumped metadata → auto. Graceful fallback to the original network src on any preload failure.
  • Tests: 11 new audio-engine unit tests + 3 new AudioPlayer wiring tests (14 total, all green).

Impact on release goals

  • Core Platform → "Audio engine" moves [ ] → [x] (roadmap updated on this branch).
  • Release Hardening → "p95 audio TTFB < 800ms on 3G profile" is now structurally addressed client-side: a preloaded blob yields near-zero TTFB on play, independent of network conditions at play-time.
  • D1→D7 retention: a lesson that starts instantly and doesn't stall mid-movement protects the felt "Aha-moment" that drives sharing and return.

Blackbox

  • python3 scripts/blackbox_test.py --target testing → 7/8 green
  • The single failure (GET /api/lessons JSON shape) is pre-existing and tracked by PR #53; it is a server-side issue unrelated to this client-side audio work.
  • Unit suite: 85/85 green (12 files), incl. 14 new tests. TypeScript clean (tsc --noEmit).

Six-perspectives check

  • Product: amplifies the felt Aha-moment (instant start, no stall); no dark patterns.
  • Engineering: minimal diff to AudioPlayer (imports + one state + one effect + src swap); tests cover the change; no security surface; graceful degradation path. Deliberately avoids touching the regions PR #52 modifies (ShareSheet), so the two PRs should not conflict.
  • Psychology: autonomy-supportive — buffering is never surfaced as a judgement or pressure; the engine is silent to the user.
  • Biomechanics: n/a (no movement cues), but "reduce effort" is honoured: the audio never makes the user wait.
  • Anatomy: n/a.
  • Feldenkrais: no correction/judgement, no strain, quality over quantity. The preload is invisible effort removed, not added.

Risk

  • Additive and best-effort: on any preload failure the player transparently falls back to the original src, so there is no regression to current playback.
  • Cache API use is feature-detected; unsupported environments (older webviews) degrade gracefully.
  • Blob URLs are GC'd by the browser on navigation; no leak.
  • Minimal AudioPlayer diff avoids overlap with PR #52's ShareSheet changes.

Checklist

  • Branch off latest dev (feat/audio-engine)
  • Conventional Commits
  • Patchlog entry appended (.development/patchlog.md)
  • Roadmap status updated (.development/roadmap.md)
  • No secrets / no large binaries

Note: the Tauri build CI job can take >15 min. This PR is opened for review; CI status will populate asynchronously.

## Plan Deliver the Core Platform **"Audio engine (preload, low-TTFB, offline-resilient)"** roadmap item — a client-side audio engine that preloads lesson audio into an in-memory + Cache API store so the first "play" is instant and playback survives a network blip mid-lesson, without ever surfacing buffering as strain or judgement. ## Outcome - **`src/lib/audio-engine.ts`** (new): URL resolution against the base CDN, `preloadAudio` (in-memory dedupe + Cache API persistence), `loadAudioResilient` (cache-first recovery), `fetchWithTtfb` (TTFB measurement), and idempotent request coalescing (concurrent preloads of the same URL share one network fetch). - **`src/components/AudioPlayer.tsx`** (wired): a preload `useEffect` feeds a preloaded blob URL to the `<audio>` element; `<audio preload>` bumped `metadata` → `auto`. Graceful fallback to the original network src on any preload failure. - Tests: **11 new audio-engine unit tests** + **3 new AudioPlayer wiring tests** (14 total, all green). ## Impact on release goals - **Core Platform → "Audio engine" moves `[ ]` → `[x]`** (roadmap updated on this branch). - **Release Hardening → "p95 audio TTFB < 800ms on 3G profile"** is now structurally addressed client-side: a preloaded blob yields near-zero TTFB on play, independent of network conditions at play-time. - **D1→D7 retention:** a lesson that starts instantly and doesn't stall mid-movement protects the felt "Aha-moment" that drives sharing and return. ## Blackbox - [x] `python3 scripts/blackbox_test.py --target testing` → **7/8 green** - The single failure (`GET /api/lessons` JSON shape) is **pre-existing** and tracked by **PR #53**; it is a server-side issue unrelated to this client-side audio work. - Unit suite: **85/85 green** (12 files), incl. 14 new tests. TypeScript clean (`tsc --noEmit`). ## Six-perspectives check - [x] **Product:** amplifies the felt Aha-moment (instant start, no stall); no dark patterns. - [x] **Engineering:** minimal diff to AudioPlayer (imports + one state + one effect + src swap); tests cover the change; no security surface; graceful degradation path. Deliberately avoids touching the regions PR #52 modifies (ShareSheet), so the two PRs should not conflict. - [x] **Psychology:** autonomy-supportive — buffering is never surfaced as a judgement or pressure; the engine is silent to the user. - [x] **Biomechanics:** n/a (no movement cues), but "reduce effort" is honoured: the audio never makes the user wait. - [x] **Anatomy:** n/a. - [x] **Feldenkrais:** no correction/judgement, no strain, quality over quantity. The preload is invisible effort removed, not added. ## Risk - **Additive and best-effort:** on any preload failure the player transparently falls back to the original `src`, so there is **no regression** to current playback. - Cache API use is feature-detected; unsupported environments (older webviews) degrade gracefully. - Blob URLs are GC'd by the browser on navigation; no leak. - Minimal AudioPlayer diff avoids overlap with PR #52's ShareSheet changes. ## Checklist - [x] Branch off latest `dev` (`feat/audio-engine`) - [x] Conventional Commits - [x] Patchlog entry appended (`.development/patchlog.md`) - [x] Roadmap status updated (`.development/roadmap.md`) - [x] No secrets / no large binaries --- _Note: the Tauri build CI job can take >15 min. This PR is opened for review; CI status will populate asynchronously._
This pull request has changes conflicting with the target branch.
  • .development/automation-state.json
  • .development/patchlog.md
  • .development/roadmap.md
  • src/components/AudioPlayer.tsx
View command line instructions

Manual merge helper

Use this merge commit message when completing the merge manually.

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin feat/audio-engine:feat/audio-engine
git switch feat/audio-engine

Merge

Merge the changes and update on Forgejo.

Warning: The "Autodetect manual merge" setting is not enabled for this repository, you will have to mark this pull request as manually merged afterwards.

git switch dev
git merge --no-ff feat/audio-engine
git switch feat/audio-engine
git rebase dev
git switch dev
git merge --ff-only feat/audio-engine
git switch feat/audio-engine
git rebase dev
git switch dev
git merge --no-ff feat/audio-engine
git switch dev
git merge --squash feat/audio-engine
git switch dev
git merge --ff-only feat/audio-engine
git switch dev
git merge feat/audio-engine
git push origin dev
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Kalle/ATM!54
No description provided.