Phase 2A+2B: native preview backend and renderer glue

Rust (2A):
- metadata: deezer_track_uncached (fresh mint, cache-updating, paced) and
  fetch_preview_media — dedicated client whose custom redirect policy
  re-validates every hop against the media allowlist; bounded body read
  (cap enforced on received bytes, not Content-Length's promise); Date
  header captured for provider-clock lease math. httpdate dep added.
- src-tauri/preview.rs: second Player instance + soulstream:preview event
  channel; preview_resolve (fresh mint → host gate → MIME gate → ID3v2
  shell strip → MPEG sync check → clip file + opaque lease id → safe_for_ms
  mirroring lease.js); preview_load with start_paused via the
  desired-paused latch; preview_control/dispose; player_pause_acked
  exposing the Phase 0.4 ack on the MAIN player. 9 unit tests.

JS (2B):
- api.js: preview command surface (opaque lease ids only).
- nativeTransport.js: stamps {sessionId, generation, sessionKey} onto
  backend events; cleared context makes stragglers dispatch nothing.
- productionResolver.js: DTO → MediaLease, failure classes preserved.
- focusBridge.js: production coordinator binding with the documented
  renderer-side generation invariant (buffering counter + gateway).
- service.js: the orchestrator — settle window, resolve→load→play under
  focus, single capped retry, one forward lease prefetch with safe-window
  expiry, stale re-checks before every transport command, close/handoff.
  The service constructs its transport around its own dispatch — the
  factory signature exists because a pre-built transport silently loses
  the event wiring (caught by test).

70 JS tests passing (npm test); metadata 46 + app preview 9 in cargo.
This commit is contained in:
KeelrLeek committed 2026-07-22 18:45:29 -05:00
1 parent 8ede6a8adf
commit 8cb3cd593b
11 files changed
+1536

No files matched your search

+13
View File
@@ -132,6 +132,19 @@ export const preloadCachedStream = (username, path, size, duration) =>
export const streamWaveform = (username, path, size, buckets) =>
invoke("stream_waveform", { username, path, size, buckets });
// Preview tracks (scrollable-track-previews): a fully separate audio path.
// previewResolve returns { ok, lease | failure, retryable } — the signed URL
// never crosses this boundary, only an opaque lease id does.
export const previewResolve = (trackId) => invoke("preview_resolve", { trackId });
export const previewLoad = (leaseId, startPaused) =>
invoke("preview_load", { leaseId, startPaused });
export const previewControl = (action, value = null) =>
invoke("preview_control", { action, value });
export const previewDispose = () => invoke("preview_dispose");
// Main-player silence acknowledgement (audio-focus contract). Throws on
// timeout — callers keep preview parked; they never assume silence.
export const playerPauseAcked = () => invoke("player_pause_acked");
// Window chrome (the app draws its own titlebar; decorations are off).
import { getCurrentWindow } from "@tauri-apps/api/window";
export const win = {