Fix Apply effects button staying disabled after streaming preview (v1.14.4)
Streaming playback never produces an in-memory blob, but the enable logic required one. Now enables unconditionally and lazily re-synthesizes a buffered blob on demand if effects are applied before one exists. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
bfd1bfb939
commit
939d33f7cc
@ -9,6 +9,13 @@ Follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) · versioned wi
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## [1.14.4] — 2026-07-08
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- **"Apply effects" button stayed disabled after generating a preview** — streaming playback (the default, lower-latency mode) never produces an in-memory audio blob, and the button's enable logic required one. It now enables regardless of playback mode; if effects are applied before a buffered blob exists, one is synthesized on demand from the same voice/text/instruct/backend used for the preview.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## [1.14.3] — 2026-07-06
|
## [1.14.3] — 2026-07-06
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
@ -10,7 +10,7 @@
|
|||||||
<meta name="format-detection" content="telephone=no">
|
<meta name="format-detection" content="telephone=no">
|
||||||
<meta name="color-scheme" content="light dark">
|
<meta name="color-scheme" content="light dark">
|
||||||
<meta name="theme-color" content="#2563EB">
|
<meta name="theme-color" content="#2563EB">
|
||||||
<meta name="app-version" content="1.14.3">
|
<meta name="app-version" content="1.14.4">
|
||||||
<link rel="manifest" href="/manifest.webmanifest">
|
<link rel="manifest" href="/manifest.webmanifest">
|
||||||
<link rel="icon" href="/static/icon.svg" type="image/svg+xml">
|
<link rel="icon" href="/static/icon.svg" type="image/svg+xml">
|
||||||
<link rel="apple-touch-icon" href="/static/icon.svg">
|
<link rel="apple-touch-icon" href="/static/icon.svg">
|
||||||
@ -27,7 +27,7 @@
|
|||||||
|
|
||||||
<!-- ── Core styles (local — no CDN dependency for first paint) ────────── -->
|
<!-- ── Core styles (local — no CDN dependency for first paint) ────────── -->
|
||||||
<link rel="stylesheet" href="/static/vendor/mdi/materialdesignicons.min.css">
|
<link rel="stylesheet" href="/static/vendor/mdi/materialdesignicons.min.css">
|
||||||
<link rel="stylesheet" href="/static/style.css?v=1.14.3">
|
<link rel="stylesheet" href="/static/style.css?v=1.14.4">
|
||||||
|
|
||||||
|
|
||||||
<!-- ── Flag icons — non-blocking (loaded async, icons appear after JS) ── -->
|
<!-- ── Flag icons — non-blocking (loaded async, icons appear after JS) ── -->
|
||||||
@ -365,7 +365,7 @@ window.toggleNavTree = function(treeId, chevronId) {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- loader.js: fetches sections → loads JS modules → removes skeleton -->
|
<!-- loader.js: fetches sections → loads JS modules → removes skeleton -->
|
||||||
<script src="/static/loader.js?v=1.14.3"></script>
|
<script src="/static/loader.js?v=1.14.4"></script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@ -339,8 +339,6 @@ $('effects-reset-btn')?.addEventListener('click', () => {
|
|||||||
let _effectsSourceBlob = null;
|
let _effectsSourceBlob = null;
|
||||||
|
|
||||||
$('effects-apply-btn')?.addEventListener('click', async () => {
|
$('effects-apply-btn')?.addEventListener('click', async () => {
|
||||||
const blob = previewBlob || _effectsSourceBlob;
|
|
||||||
if (!blob) { toast('Generate audio first', 'error'); return; }
|
|
||||||
const chain = [];
|
const chain = [];
|
||||||
if ($('fx-reverb-on')?.checked) chain.push({ type:'reverb', params: { room_size: +$('fx-reverb-room').value, wet: +$('fx-reverb-wet').value, dry: 1 - +$('fx-reverb-wet').value } });
|
if ($('fx-reverb-on')?.checked) chain.push({ type:'reverb', params: { room_size: +$('fx-reverb-room').value, wet: +$('fx-reverb-wet').value, dry: 1 - +$('fx-reverb-wet').value } });
|
||||||
if ($('fx-compressor-on')?.checked) chain.push({ type:'compressor', params: { threshold_db: +$('fx-comp-thresh').value, ratio: +$('fx-comp-ratio').value } });
|
if ($('fx-compressor-on')?.checked) chain.push({ type:'compressor', params: { threshold_db: +$('fx-comp-thresh').value, ratio: +$('fx-comp-ratio').value } });
|
||||||
@ -350,6 +348,13 @@ $('effects-apply-btn')?.addEventListener('click', async () => {
|
|||||||
const btn = $('effects-apply-btn'), st = $('effects-status');
|
const btn = $('effects-apply-btn'), st = $('effects-status');
|
||||||
btn.disabled = true; if (st) st.textContent = 'Processing…';
|
btn.disabled = true; if (st) st.textContent = 'Processing…';
|
||||||
try {
|
try {
|
||||||
|
let blob = previewBlob || _effectsSourceBlob;
|
||||||
|
if (!blob) {
|
||||||
|
const args = window._effectsSynthArgs;
|
||||||
|
if (!args || typeof fetchTtsPreviewBlob !== 'function') { toast('Generate audio first', 'error'); return; }
|
||||||
|
if (st) st.textContent = 'Synthesizing audio…';
|
||||||
|
blob = await fetchTtsPreviewBlob(args.voice, args.text, 'wav', args.instruct, args.backend);
|
||||||
|
}
|
||||||
const fd = new FormData();
|
const fd = new FormData();
|
||||||
fd.append('audio', blob, 'audio.wav');
|
fd.append('audio', blob, 'audio.wav');
|
||||||
fd.append('effects', JSON.stringify(chain));
|
fd.append('effects', JSON.stringify(chain));
|
||||||
|
|||||||
@ -239,9 +239,16 @@ $('preview-text-area').addEventListener('focus', () => {
|
|||||||
if ($('preview-text-area').value === PREVIEW_SAMPLE_TEXT) $('preview-text-area').value = '';
|
if ($('preview-text-area').value === PREVIEW_SAMPLE_TEXT) $('preview-text-area').value = '';
|
||||||
}, { once:true });
|
}, { once:true });
|
||||||
|
|
||||||
function _onPreviewGenerated(source, voice, text, backend) {
|
function _onPreviewGenerated(source, voice, text, backend, instruct) {
|
||||||
if (typeof effectsSourceBlob !== 'undefined') window._effectsSourceBlob = null;
|
if (typeof effectsSourceBlob !== 'undefined') window._effectsSourceBlob = null;
|
||||||
const ea = $('effects-apply-btn'); if (ea && source.blob) ea.disabled = false;
|
// Streaming playback (the default, lower-latency path) never produces a
|
||||||
|
// blob — audio plays via MSE without ever being fully buffered client-side
|
||||||
|
// — so gating "Apply effects" on source.blob left it permanently disabled
|
||||||
|
// whenever streaming succeeded, which is the common case. Enable it
|
||||||
|
// regardless; the effects handler re-fetches a buffered blob on demand
|
||||||
|
// (see window._effectsSynthArgs) if one isn't already sitting in memory.
|
||||||
|
window._effectsSynthArgs = { voice, text, instruct: instruct || '', backend };
|
||||||
|
const ea = $('effects-apply-btn'); if (ea) ea.disabled = false;
|
||||||
const ap = $('add-to-playlist-btn'); if (ap && source.blob) ap.disabled = false;
|
const ap = $('add-to-playlist-btn'); if (ap && source.blob) ap.disabled = false;
|
||||||
if (typeof historyPush === 'function' && source.blob) historyPush(voice, text, backend, source.blob, source.url);
|
if (typeof historyPush === 'function' && source.blob) historyPush(voice, text, backend, source.blob, source.url);
|
||||||
}
|
}
|
||||||
@ -279,7 +286,7 @@ $('preview-btn').addEventListener('click', async () => {
|
|||||||
await audio.play();
|
await audio.play();
|
||||||
$('save-preview-mp3-btn').disabled = false;
|
$('save-preview-mp3-btn').disabled = false;
|
||||||
$('save-preview-btn').disabled = source.streaming;
|
$('save-preview-btn').disabled = source.streaming;
|
||||||
_onPreviewGenerated(source, voice, text, backend);
|
_onPreviewGenerated(source, voice, text, backend, instruct);
|
||||||
toast(source.streaming ? 'Streaming preview playing' : source.label === 'chunked' ? `Chunked (${text.length} chars) playing` : 'Preview playing', 'success');
|
toast(source.streaming ? 'Streaming preview playing' : source.label === 'chunked' ? `Chunked (${text.length} chars) playing` : 'Preview playing', 'success');
|
||||||
} catch(e) { toast('TTS failed: '+e.message,'error'); }
|
} catch(e) { toast('TTS failed: '+e.message,'error'); }
|
||||||
finally { $('preview-btn').disabled=false; }
|
finally { $('preview-btn').disabled=false; }
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user