refactor: dedupe director audio loading helpers

This commit is contained in:
OpenClaw Agent
2026-07-09 14:17:30 +00:00
parent 5df6ed6669
commit a1a4be4fc3

View File

@@ -1904,17 +1904,7 @@ class TimelineEditor {
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
const audioBuffer = await audioCtx.decodeAudioData(arrayBuffer);
const channelData = audioBuffer.getChannelData(0);
const peaks = [];
const numPeaks = 200;
const step = Math.floor(channelData.length / numPeaks);
for (let i = 0; i < numPeaks; i++) {
let max = 0;
for (let j = 0; j < step; j++) {
const val = Math.abs(channelData[i * step + j]);
if (val > max) max = val;
}
peaks.push(max);
}
const peaks = this._computeWaveformPeaks(channelData);
for (let s of this.timeline.audioSegments) {
if (s.id === audSegId || (blobUrl && s._blobUrl === blobUrl)) {
s.waveformPeaks = peaks;
@@ -1948,6 +1938,96 @@ class TimelineEditor {
return true;
}
_ensureAudioContext() {
if (!this.audioContext) {
this.audioContext = new (window.AudioContext || window.webkitAudioContext)();
}
return this.audioContext;
}
_getAudioCacheKey(seg) {
if (seg.audioFile) return seg.audioFile;
if (seg._blobUrl) return seg._blobUrl;
if (seg.audioB64) return `b64:${seg.audioB64.length}:${seg.audioB64.slice(0, 64)}`;
return null;
}
_getAudioUrl(seg) {
if (seg._blobUrl) return seg._blobUrl;
const parts = (seg.audioFile || "").split(/[/\\\\]/);
const filename = parts.pop() || "";
const subfolder = parts.join("/");
return api.apiURL(`/view?filename=${encodeURIComponent(filename)}&type=input&subfolder=${encodeURIComponent(subfolder)}`);
}
async _decodeBase64Audio(audioB64) {
const binaryString = window.atob(audioB64);
const len = binaryString.length;
const bytes = new Uint8Array(len);
for (let i = 0; i < len; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
return await this._ensureAudioContext().decodeAudioData(bytes.buffer);
}
_computeWaveformPeaks(channelData, numPeaks = 200) {
if (!channelData || !channelData.length) return Array(numPeaks).fill(0);
const step = Math.max(1, Math.floor(channelData.length / numPeaks));
const peaks = [];
for (let i = 0; i < numPeaks; i++) {
let max = 0;
for (let j = 0; j < step; j++) {
const idx = i * step + j;
if (idx >= channelData.length) break;
const val = Math.abs(channelData[idx]);
if (val > max) max = val;
}
peaks.push(max);
}
return peaks;
}
async _loadAudioBuffer(seg) {
if (!seg.audioFile && !seg._blobUrl && !seg.audioB64) return null;
const audioContext = this._ensureAudioContext();
this._audioBufferCache = this._audioBufferCache || new Map();
this._audioBufferPromises = this._audioBufferPromises || new Map();
const cacheKey = this._getAudioCacheKey(seg);
if (cacheKey && this._audioBufferCache.has(cacheKey)) {
return this._audioBufferCache.get(cacheKey);
}
if (cacheKey && this._audioBufferPromises.has(cacheKey)) {
return await this._audioBufferPromises.get(cacheKey);
}
const decodePromise = (async () => {
if (seg.audioB64 && !seg.audioFile && !seg._blobUrl) {
return await this._decodeBase64Audio(seg.audioB64);
}
const resp = await fetch(this._getAudioUrl(seg));
const arrayBuffer = await resp.arrayBuffer();
return await audioContext.decodeAudioData(arrayBuffer);
})();
if (cacheKey) {
this._audioBufferPromises.set(cacheKey, decodePromise);
}
try {
const audioBuffer = await decodePromise;
if (cacheKey) {
this._audioBufferCache.set(cacheKey, audioBuffer);
}
return audioBuffer;
} finally {
if (cacheKey) {
this._audioBufferPromises.delete(cacheKey);
}
}
}
async _preloadAudioSegment(seg) {
if (seg._audioBuffer || seg._decoding) return;
if (!seg.audioFile && !seg._blobUrl) return;
@@ -1963,38 +2043,7 @@ class TimelineEditor {
return;
}
if (!this.audioContext) {
this.audioContext = new (window.AudioContext || window.webkitAudioContext)();
}
const parts = (seg.audioFile || "").split(/[/\\\\]/);
const filename = parts.pop() || '';
const subfolder = parts.join('/');
const audioUrl = seg._blobUrl || api.apiURL(`/view?filename=${encodeURIComponent(filename)}&type=input&subfolder=${encodeURIComponent(subfolder)}`);
this._audioBufferCache = this._audioBufferCache || new Map();
this._audioBufferPromises = this._audioBufferPromises || new Map();
const cacheKey = seg.audioFile || audioUrl;
let audioBuffer;
if (this._audioBufferCache.has(cacheKey)) {
audioBuffer = this._audioBufferCache.get(cacheKey);
} else if (this._audioBufferPromises.has(cacheKey)) {
audioBuffer = await this._audioBufferPromises.get(cacheKey);
} else {
const decodePromise = (async () => {
const resp = await fetch(audioUrl);
const arrayBuffer = await resp.arrayBuffer();
return await this.audioContext.decodeAudioData(arrayBuffer);
})();
this._audioBufferPromises.set(cacheKey, decodePromise);
try {
audioBuffer = await decodePromise;
this._audioBufferCache.set(cacheKey, audioBuffer);
} finally {
this._audioBufferPromises.delete(cacheKey);
}
}
const audioBuffer = await this._loadAudioBuffer(seg);
const matchingSegs = this.timeline.audioSegments.filter(s => s.audioFile === seg.audioFile || s._blobUrl === seg._blobUrl);
for (const s of matchingSegs) {
@@ -2030,38 +2079,7 @@ class TimelineEditor {
return;
}
if (!this.audioContext) {
this.audioContext = new (window.AudioContext || window.webkitAudioContext)();
}
const parts = (mockSeg.audioFile || "").split(/[/\\\\]/);
const filename = parts.pop() || '';
const subfolder = parts.join('/');
const audioUrl = mockSeg._blobUrl || api.apiURL(`/view?filename=${encodeURIComponent(filename)}&type=input&subfolder=${encodeURIComponent(subfolder)}`);
this._audioBufferCache = this._audioBufferCache || new Map();
this._audioBufferPromises = this._audioBufferPromises || new Map();
const cacheKey = mockSeg.audioFile || audioUrl;
let audioBuffer;
if (this._audioBufferCache.has(cacheKey)) {
audioBuffer = this._audioBufferCache.get(cacheKey);
} else if (this._audioBufferPromises.has(cacheKey)) {
audioBuffer = await this._audioBufferPromises.get(cacheKey);
} else {
const decodePromise = (async () => {
const resp = await fetch(audioUrl);
const arrayBuffer = await resp.arrayBuffer();
return await this.audioContext.decodeAudioData(arrayBuffer);
})();
this._audioBufferPromises.set(cacheKey, decodePromise);
try {
audioBuffer = await decodePromise;
this._audioBufferCache.set(cacheKey, audioBuffer);
} finally {
this._audioBufferPromises.delete(cacheKey);
}
}
const audioBuffer = await this._loadAudioBuffer(mockSeg);
seg._audioBuffer = audioBuffer;
} catch (e) {
console.warn("Failed to preload motion audio segment:", e);
@@ -4691,17 +4709,7 @@ class TimelineEditor {
const clipFrames = Math.max(1, Math.ceil(clipDurationSecs * frameRate));
const channelData = audioBuffer.getChannelData(0);
const peaks = [];
const numPeaks = 200;
const step = Math.floor(channelData.length / numPeaks);
for (let i = 0; i < numPeaks; i++) {
let max = 0;
for (let j = 0; j < step; j++) {
const val = Math.abs(channelData[i * step + j]);
if (val > max) max = val;
}
peaks.push(max);
}
const peaks = this._computeWaveformPeaks(channelData);
let newLength = clipFrames;
let newStart = targetFrameStart;
@@ -11474,47 +11482,11 @@ class TimelineEditor {
// Build audio buffer
let audioBuffer = item.originalSeg._audioBuffer;
if (!audioBuffer) {
if (mockSeg.audioFile || mockSeg._blobUrl) {
const parts = (mockSeg.audioFile || "").split(/[/\\\\]/);
const filename = parts.pop() || '';
const subfolder = parts.join('/');
const audioUrl = mockSeg._blobUrl || api.apiURL(`/view?filename=${encodeURIComponent(filename)}&type=input&subfolder=${encodeURIComponent(subfolder)}`);
this._audioBufferCache = this._audioBufferCache || new Map();
this._audioBufferPromises = this._audioBufferPromises || new Map();
const cacheKey = mockSeg.audioFile || audioUrl;
if (this._audioBufferCache.has(cacheKey)) {
audioBuffer = this._audioBufferCache.get(cacheKey);
} else if (this._audioBufferPromises.has(cacheKey)) {
audioBuffer = await this._audioBufferPromises.get(cacheKey);
} else {
const decodePromise = (async () => {
const resp = await fetch(audioUrl);
const arrayBuffer = await resp.arrayBuffer();
return await this.audioContext.decodeAudioData(arrayBuffer);
})();
this._audioBufferPromises.set(cacheKey, decodePromise);
try {
audioBuffer = await decodePromise;
this._audioBufferCache.set(cacheKey, audioBuffer);
} finally {
this._audioBufferPromises.delete(cacheKey);
}
}
item.originalSeg._audioBuffer = audioBuffer;
} else if (mockSeg.audioB64) {
const binaryString = window.atob(mockSeg.audioB64);
const len = binaryString.length;
const bytes = new Uint8Array(len);
for (let i = 0; i < len; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
audioBuffer = await this.audioContext.decodeAudioData(bytes.buffer);
item.originalSeg._audioBuffer = audioBuffer;
} else {
if (!mockSeg.audioFile && !mockSeg._blobUrl && !mockSeg.audioB64) {
return;
}
audioBuffer = await this._loadAudioBuffer(mockSeg);
item.originalSeg._audioBuffer = audioBuffer;
}
if (this._currentPlayId !== playId || !this.isPlaying) return;