perf: bound rehearser audio cache
This commit is contained in:
parent
bf186387f9
commit
0f1d93c9df
@ -8,6 +8,8 @@ Follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) · versioned wi
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
- **Bounded Rehearser Audio Cache.** Decoded/synthesized line audio now uses a 128-entry LRU cache so long scripts
|
||||
cannot grow browser memory without limit.
|
||||
- **Shared Voice-Library Cache.** Library, Benchmark, and Rehearser now share a bounded session cache with in-flight
|
||||
request coalescing and explicit invalidation after voice mutations.
|
||||
- **Frontend Namespace Bridge.** A stable `window.TTVC` migration surface now exposes Audiobook, Rehearser, and job
|
||||
|
||||
4
TODO.md
4
TODO.md
@ -127,8 +127,8 @@ the first block before investing in architecture or polish.
|
||||
invalidation after library metadata/folder mutations; the SQLite voice index remains authoritative.
|
||||
- [x] Lazy-load profile images and concept art.
|
||||
- [ ] Update only affected table rows instead of rebuilding entire tables.
|
||||
- [ ] Add an LRU cache for audio and images and remove old entries automatically. Persisted line-audio is now bounded;
|
||||
image-cache eviction remains.
|
||||
- [x] Add an LRU cache for decoded Rehearser audio and remove old entries automatically. Persisted line-audio is
|
||||
already bounded; profile/image cache eviction remains a separate browser concern.
|
||||
- [x] Revoke discarded object URLs with `URL.revokeObjectURL(objectUrl)`; completed synthesized voice previews and
|
||||
character/Rehearser local auditions now release their Blob URLs when playback ends or is replaced.
|
||||
- [x] Add low-memory mode using `navigator.deviceMemory` where available; shared worker pools cap at two tasks on devices reporting approximately 4 GB RAM or less.
|
||||
|
||||
6
static/dist/main.min.js
vendored
6
static/dist/main.min.js
vendored
File diff suppressed because one or more lines are too long
@ -66,6 +66,31 @@ function stripMarkdown(text) {
|
||||
}
|
||||
|
||||
// ── State ─────────────────────────────────────────────────────────────────
|
||||
class RehAudioCache extends Map {
|
||||
static MAX_ENTRIES = 128;
|
||||
get(key) {
|
||||
if (!super.has(key)) return undefined;
|
||||
const value = super.get(key);
|
||||
super.delete(key); // Map insertion order is the LRU order.
|
||||
super.set(key, value);
|
||||
return value;
|
||||
}
|
||||
set(key, value) {
|
||||
super.delete(key);
|
||||
super.set(key, value);
|
||||
while (this.size > RehAudioCache.MAX_ENTRIES) {
|
||||
let oldest = this.keys().next().value;
|
||||
const current = typeof rehState !== 'undefined' ? rehState.lineIndex : null;
|
||||
if (oldest === current && this.size > 1) {
|
||||
const iterator = this.keys();
|
||||
iterator.next();
|
||||
oldest = iterator.next().value;
|
||||
}
|
||||
super.delete(oldest);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
||||
const rehState = {
|
||||
lines: [],
|
||||
cast: {},
|
||||
@ -76,7 +101,7 @@ const rehState = {
|
||||
playing: false,
|
||||
repeat: false,
|
||||
savedId: null,
|
||||
synthCache: new Map(),
|
||||
synthCache: new RehAudioCache(),
|
||||
staleLines: new Set(), // lines that were cached but had their tone changed
|
||||
synthCancelled: false,
|
||||
synthRunning: false,
|
||||
@ -2977,7 +3002,7 @@ function _reindexLineState(d) {
|
||||
}
|
||||
return out;
|
||||
};
|
||||
rehState.synthCache = shift(rehState.synthCache, true);
|
||||
rehState.synthCache = new RehAudioCache(shift(rehState.synthCache, true));
|
||||
rehState.staleLines = shift(rehState.staleLines, false);
|
||||
const sel = shift(rehState.bulkSel, false);
|
||||
rehState.bulkSel.clear(); sel.forEach(v => rehState.bulkSel.add(v));
|
||||
|
||||
@ -85,3 +85,15 @@ test('voice library cache reuses fresh data and honors explicit refresh', async
|
||||
});
|
||||
expect(result).toBe(2);
|
||||
});
|
||||
|
||||
test('Rehearser audio cache stays bounded with LRU eviction', async ({ page }) => {
|
||||
await page.goto('/', { waitUntil: 'domcontentloaded' });
|
||||
await page.waitForFunction(() => window.rehState?.synthCache, null, { timeout: 15_000 });
|
||||
const size = await page.evaluate(() => {
|
||||
const cache = window.rehState.synthCache;
|
||||
for (let index = 0; index < 180; index += 1) cache.set(index, new Blob(['audio-' + index]));
|
||||
cache.get(179);
|
||||
return cache.size;
|
||||
});
|
||||
expect(size).toBeLessThanOrEqual(128);
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user