save audio - embedded player

This commit is contained in:
snicolast
2025-10-13 20:15:52 +13:00
parent 7f426f5033
commit 5b33e02bc0
5 changed files with 223 additions and 24 deletions

177
web/js/save_audio_player.js Normal file
View File

@@ -0,0 +1,177 @@
import { app } from '../../../scripts/app.js';
import { api } from '../../../scripts/api.js';
const MARGIN = 6;
let stylesInjected = false;
function ensureStyles() {
if (stylesInjected) return;
stylesInjected = true;
const style = document.createElement('style');
style.textContent = `
.indextts2-audio-player {
position: absolute;
z-index: 12;
pointer-events: auto;
display: flex;
flex-direction: column;
gap: 4px;
padding: 6px;
background: var(--bg-color, var(--comfy-menu-bg, #2a2a2a));
border: 1px solid var(--border-color, #444);
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.35);
min-width: 180px;
transition: opacity 0.2s ease, filter 0.2s ease;
}
.indextts2-audio-player[data-state="inactive"] {
opacity: 0.9;
}
.indextts2-audio-player[data-state="inactive"] audio {
pointer-events: none;
filter: grayscale(0.8);
opacity: 0.42;
}
.indextts2-audio-player__title {
font-size: 11px;
font-weight: 600;
color: var(--descrip-text, #c7c7c7);
}
`;
document.head.appendChild(style);
}
function buildTransformStyle(ctx, widgetWidth, y) {
const { canvas } = ctx;
const rect = canvas.getBoundingClientRect();
const matrix = new DOMMatrix()
.scaleSelf(rect.width / canvas.width, rect.height / canvas.height)
.multiplySelf(ctx.getTransform())
.translateSelf(MARGIN, y + MARGIN);
return {
transformOrigin: '0 0',
transform: matrix.toString(),
left: `${rect.left + window.scrollX}px`,
top: `${rect.top + window.scrollY}px`,
};
}
function buildAudioUrl(item) {
if (!item || !item.filename) {
return null;
}
const params = new URLSearchParams({
filename: item.filename,
type: item.type || 'output',
});
if (item.subfolder) {
params.set('subfolder', item.subfolder);
}
let url = api.apiURL(`/view?${params.toString()}`);
if (typeof app.getRandParam === 'function') {
url += app.getRandParam();
}
return url;
}
function setState(container, audioEl, titleEl, clip) {
if (clip?.url) {
if (audioEl.src !== clip.url) {
audioEl.src = clip.url;
audioEl.load();
}
titleEl.textContent = clip.title ?? 'Audio preview';
container.dataset.state = 'active';
} else {
audioEl.pause();
audioEl.removeAttribute('src');
audioEl.load();
titleEl.textContent = 'No audio yet';
container.dataset.state = 'inactive';
}
}
app.registerExtension({
name: 'IndexTTS2.SaveAudioPlayer',
beforeRegisterNodeDef(nodeType, nodeData) {
if (nodeData?.name !== 'IndexTTS2SaveAudio') {
return;
}
const originalOnNodeCreated = nodeType.prototype.onNodeCreated;
nodeType.prototype.onNodeCreated = function (...args) {
originalOnNodeCreated?.apply(this, args);
ensureStyles();
const container = document.createElement('div');
container.className = 'indextts2-audio-player';
const title = document.createElement('div');
title.className = 'indextts2-audio-player__title';
title.textContent = 'No audio yet';
const audio = document.createElement('audio');
audio.controls = true;
audio.style.width = '100%';
container.appendChild(title);
container.appendChild(audio);
document.body.appendChild(container);
const widget = {
name: 'indextts2_audio_widget',
type: 'indextts2_audio_widget',
draw(ctx, node, widgetWidth, y) {
if (!container.isConnected) {
document.body.appendChild(container);
}
const baseWidth = Math.max(160, (node.size?.[0] ?? widgetWidth) - MARGIN * 2);
container.style.width = `${baseWidth}px`;
const style = buildTransformStyle(ctx, widgetWidth, y);
Object.assign(container.style, style);
},
serialize: false,
computeSize() {
return [220, 90];
},
};
this.addCustomWidget(widget);
this.size = [this.size[0], Math.max(this.size[1], 120)];
const originalOnRemoved = this.onRemoved;
this.onRemoved = function () {
container.remove();
originalOnRemoved?.apply(this, arguments);
};
setState(container, audio, title, null);
const originalOnExecuted = this.onExecuted;
this.onExecuted = function (message) {
originalOnExecuted?.apply(this, arguments);
const audioResults =
message?.ui?.audio ||
message?.audio ||
[];
if (Array.isArray(audioResults) && audioResults.length > 0) {
const latest = audioResults[audioResults.length - 1];
const url = buildAudioUrl(latest);
if (url) {
setState(container, audio, title, {
url,
title: latest.filename || 'Audio preview',
});
return;
}
}
setState(container, audio, title, null);
};
};
},
});