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]
|
## [Unreleased]
|
||||||
|
|
||||||
### Added
|
### 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
|
- **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.
|
request coalescing and explicit invalidation after voice mutations.
|
||||||
- **Frontend Namespace Bridge.** A stable `window.TTVC` migration surface now exposes Audiobook, Rehearser, and job
|
- **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.
|
invalidation after library metadata/folder mutations; the SQLite voice index remains authoritative.
|
||||||
- [x] Lazy-load profile images and concept art.
|
- [x] Lazy-load profile images and concept art.
|
||||||
- [ ] Update only affected table rows instead of rebuilding entire tables.
|
- [ ] 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;
|
- [x] Add an LRU cache for decoded Rehearser audio and remove old entries automatically. Persisted line-audio is
|
||||||
image-cache eviction remains.
|
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
|
- [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.
|
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.
|
- [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 ─────────────────────────────────────────────────────────────────
|
// ── 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 = {
|
const rehState = {
|
||||||
lines: [],
|
lines: [],
|
||||||
cast: {},
|
cast: {},
|
||||||
@ -76,7 +101,7 @@ const rehState = {
|
|||||||
playing: false,
|
playing: false,
|
||||||
repeat: false,
|
repeat: false,
|
||||||
savedId: null,
|
savedId: null,
|
||||||
synthCache: new Map(),
|
synthCache: new RehAudioCache(),
|
||||||
staleLines: new Set(), // lines that were cached but had their tone changed
|
staleLines: new Set(), // lines that were cached but had their tone changed
|
||||||
synthCancelled: false,
|
synthCancelled: false,
|
||||||
synthRunning: false,
|
synthRunning: false,
|
||||||
@ -2977,7 +3002,7 @@ function _reindexLineState(d) {
|
|||||||
}
|
}
|
||||||
return out;
|
return out;
|
||||||
};
|
};
|
||||||
rehState.synthCache = shift(rehState.synthCache, true);
|
rehState.synthCache = new RehAudioCache(shift(rehState.synthCache, true));
|
||||||
rehState.staleLines = shift(rehState.staleLines, false);
|
rehState.staleLines = shift(rehState.staleLines, false);
|
||||||
const sel = shift(rehState.bulkSel, false);
|
const sel = shift(rehState.bulkSel, false);
|
||||||
rehState.bulkSel.clear(); sel.forEach(v => rehState.bulkSel.add(v));
|
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);
|
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