Byte-identical with HF Search 9325faa. List numbers (1. / (3).) stay with their item instead of becoming a spoken sentence; 回到播放位置 toggles to 回到原位置 until the playing sentence leaves the view; hooks used by the VoiceOfML Reader (reveal/viewChanged/locateTop). Uncommitted CW reorganisation in this checkout is not part of this commit.
Byte-identical with VoiceOfML Search e41749f and GitHub Search d6509b5. Adds the optional host document adapter used by the VoiceOfML Reader (growing text, own-realm CSS highlights, readFrom/state/stop). Without an adapter behaviour is unchanged; stop() now also supersedes a pending adapter load.
fix: keep doubled dashes and ellipses inside one TTS clip
- Preview/snippet decode strict UTF-8, then strict GB18030 (CW/1/革命时期的爱情.txt is the only non-UTF-8 TXT of 47,250); UTF-8 files are unchanged.
- First clip may extend to the nearest clause mark within 32 characters; starts/seeks snap back to a clause start within 48; batch ends prefer clause marks before word boundaries. First clips ending on punctuation: 50% -> 87.5% on sampled CCRD text.
perf: play TTS clips from memory, long-poll parts and queue next batch early
- Bounded in-memory MP3 clip cache (48 clips/4 MiB, 4 ahead, 2 concurrent GETs, released on stop/page exit): sentence changes and next/previous/double-click on generated parts no longer wait for an HF round-trip; back-navigation plays held clips before cache_only confirms.
- Long-poll /tts/jobs/{id}?after=N; queue the following batch once the current one publishes.
- Retry client_busy/cancel_pending 429 after ~0.4 s instead of the unreadable 5 s Retry-After.
fix: retry TTS gateway errors and restore CW LFS attribute
- Treat non-JSON (HTML) 502/504 gateway responses as transient TTS errors.
- Report paused playback to mediaSession.
- Bump tts-client.js query version.
- Re-add LFS attribute for CW/1/革命时期的爱情.txt; removing it in 85ff6f28 made the Space serve the pointer text.
fix: align spoken sentences and reuse prepared audio on seeks
Do not merge multiple sentences in one source part; keep connective prefixes with their clauses and preserve lexical tones. Seek within live current/next jobs instead of stop/rebuilding them, with epochs and in-flight guards. Retain only two played/ready descriptors for completed-cache-only reopening; misses prepare just the target, never an old batch. Preserve pause, sleep, source Unicode, inline/audio cleanup and all search behavior.
feat: play first MP3 directly from the synthesis response
Request opt-in inline first audio on explicit play, validate bounded bytes against the original part and decode a temporary local MP3 Blob. Eliminate the first audio GET without pre-generating books. Revoke on clip end, navigation and stop; stale responses allocate nothing. Keep canonical GET and pause-aware decode-error fallback, immutable later parts and all native controls.
feat: read complete words with low-latency first phrases
Use bounded browser word and natural-clause boundaries, protected numbers and original UTF-16 plans rather than fixed-character cuts. First POST waits for published audio; synthesis is fixed at 1.2 independent of playback rate. Hide voice name and replace collapse chevron with a horizontal icon while preserving collapse/stop and preview independence.
feat: compact native preview reader with rolling long-text playback
One preview play icon, fixed female01, compact non-modal native controls and direct CORS audio. Start at visible source or double-clicked sentence; support locate, pitch/rate/sleep and pause-aware bounded prefetch. Remove document-wide 20k cap and iframe startup chain; first batch targets 80 codepoints, subsequent 480. Request opt-in pauses for unknown phones without changing originals.
10 days ago
feat: pilot explicit preview and selection reading with Hitokoto
Reuse the lazy independent CPU reader through a strict two-origin window/channel bridge. Preserve exact preview text and selection context, require explicit synthesis and active replacement, and keep search/filter/preview independent of the persistent player. No index/corpus/backend/model dependencies or SW cache-name changes.
fix(filters): use one stateful expand-collapse button