Commit Graph

3 Commits

Author SHA1 Message Date
Huang Xin 17de9357dd feat(reader): redesign the TTS control as a mini player with an expandable player sheet (#4996)
* feat(reader): add formatCountdown helper for TTS timer chips

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(reader): extract shared TTS playback info hook

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(reader): add TTS scrubber with buffer-ahead fill

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(reader): add TTS speed preset chips

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(reader): add persistent TTS mini player

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(reader): add full TTS player sheet with voice and timer sub-views

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(reader): cover player sheet view reset on reopen

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(reader): replace TTS icon and popup with mini player and player sheet

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(reader): reserve mini player clearance and retire showTTSBar setting

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore: retire shipped TTS follow-ups from TODOS

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore(i18n): translate the TTS player strings

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(reader): pin mini player transport LTR and unmount the closed player sheet

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(reader): collapse sheet speed, voice, and timer controls into one row

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(tts): stabilize timeline estimates with cumulative voice calibration

Replace the per-sentence EMA with the cumulative ratio of all measured chars to all measured seconds per voice, so the estimated remainder converges instead of re-pricing on every quirky sentence. Legacy stored calibrations migrate as a small prior.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(reader): hide the mini player while the player sheet is open

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(tts): clear stale highlights across sections and stop sentence flash in word mode

Entering a section now scrubs the TTS highlight from every live view, not just the primary, so the outgoing section's last word no longer stays lit in the preloaded neighbor. reapplyCurrentHighlight no longer redraws the whole sentence during word-mode playback while awaiting the first word boundary.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* style(reader): move the mini player progress line to the bottom edge

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(reader): make the TTS progress bar and scrubber legible in eink mode

Grey tints wash out on e-ink: the mini player track gets a 1px hairline with a solid base-content fill (buffer fill hidden), and the sheet scrubber gets a crisp 1px border marking the track extent.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* style(reader): drop the player sheet header label on the main view

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 16:37:59 +02:00
Huang Xin e08622b416 feat(sync): route library sync exclusively to the selected cloud provider (#4380) (#4975)
While WebDAV or Google Drive is the selected cloud sync backend, the
native Readest Cloud book/progress/note channels are gated off and the
file-sync engine owns library data end to end:

- isSyncCategoryEnabled returns false for book/progress/note (and their
  legacy aliases) when a third-party provider is selected. A runtime
  override, deliberately not written into syncCategories: the user's own
  toggles persist and take effect again on switch-back. Account channels
  (settings, stats, dictionaries, fonts, textures, OPDS catalogs) always
  stay native.
- persistActiveCloudProvider is the single write path for provider
  switching, used by the chooser, both connect/disconnect flows, and the
  Drive OAuth callback (which previously bypassed the cross-window
  broadcast). The broadcast carries ONLY the enabled flags plus
  providerSelectedAt, never credentials or sync cursors, and only on
  switch events, so a stale window's routine save cannot revert a switch.
- buildWebDAVConnectSettings no longer pre-sets enabled: activation
  belongs to withActiveCloudProvider, so the fresh-connect path now gets
  the syncBooks auto-flip and the providerSelectedAt stamp.
- Sync health: fileSyncStore records lastError per backend; the durable
  lastSyncedAt stays in provider settings. The SettingsMenu sync row
  reads Synced via provider / Sync failed and its tap (with pull to
  refresh and BackupWindow, all routed through pullLibrary) runs the
  file engine via the shared runActiveFileLibrarySync helper instead of
  a gated native pull that would toast undefined book(s) synced.
- Mixed-fleet detection: while gated, the auto-sync interval runs a
  read-only probe of /api/sync since providerSelectedAt; any newer book
  row means another device still syncs natively, and a once-per-session
  notice explains the fork instead of leaving it silent.
- Readest-Cloud-only affordances hide while a third-party provider is
  selected: the quota row becomes a caption naming the active provider,
  Auto Upload disappears from the menu and command palette, the
  BookItem upload badge and the Transfer Queue Upload All button hide.
- providerSelectedAt added to both provider settings types and the
  backup blacklist.

Stacked on the quota-decoupling change for #4959; requires the
metadata-parity change so gated channels lose nothing users can see.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 18:05:54 +02:00
Huang Xin 42f9b8fe3c feat(tts): gapless Web Audio playback engine for Edge TTS with chapter timeline and seek (#4931)
* feat(tts): add PCM speech-bounds detection for sentence audio trimming

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(tts): add WSOLA time-stretch for pitch-preserved playback rate

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(tts): add sentence duration store with per-voice speaking-rate calibration

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* refactor(tts): serve edge audio as ArrayBuffer with in-flight fetch dedup

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(tts): add WebAudioPlayer with gapless chunk scheduling and backpressure

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(tts): play edge TTS through gapless Web Audio pipeline

Replaces the per-sentence audio element with trimmed, time-stretched
buffers scheduled on the shared AudioContext. Marks dispatch at audible
time so schedule-ahead cannot run foliate's cursor past the voice; a
decode failure or missing audio skips the chunk instead of wedging the
session; pause and resume ride context suspend and resume with no iOS
rewind hack; the object-URL cache is gone.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(tts): add section timeline with measured and estimated sentence durations

Includes the foliate-js submodule bump for the getSentences export
(fork branch feat/tts-get-sentences; fork PR must merge before this
lands so the pinned SHA resolves).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(tts): expose section playback position and sentence-snapped seeking

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(tts): surface playback position and seek in the media session

Position state is clamped, never skipped, so the lock-screen scrubber
stays live when estimates overshoot; seekto units map per backend
(native ms, web seconds). The AudioContext warms up in the tts-speak
gesture path before any await, and the silent keep-alive element now
runs on all platforms so desktop hardware media keys survive the
removal of the per-sentence audio element.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(tts): add seekable chapter progress bar to the TTS panel

The scrubber joins the transport cluster with a thin range-xs track and
flanking tabular time labels so it cannot be misgrabbed for the chunky
rate slider (which persists a global setting). States: reserved
disabled slot until the lazy timeline lands, persists across chapter
transitions, optimistic thumb with failure toast, monotonic position,
tilde-prefixed estimated totals, sentence-event updates under e-ink.
The popup grows only when a timeline-capable client is active.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs: record deferred TTS listening-engine follow-ups

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs: record background TTS decoupling design decisions

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(tts): slim the panel scrubber to a native track with remaining time

Match the footer Jump to Location slider (plain native range: thin
track, small thumb) instead of the chunky daisyUI pill, show remaining
time with a minus prefix on the right, and drop the This chapter
caption. Popup height shrinks accordingly. Verified live in Chrome.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore: pin foliate-js to merged main with getSentences export

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(tts): catch autoplay rejection from the keep-alive element

Running the silent keep-alive on all platforms exposed an un-awaited
play() that headless Chromium rejects without a user gesture, failing
CI on unhandled rejections while every test passed. The keep-alive is
best-effort; the production path is gesture-qualified.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 10:50:22 +02:00