|
@@ -1812,74 +1812,73 @@
|
|
|
queue.splice(index, 0, chunk);
|
|
queue.splice(index, 0, chunk);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 开始转换(发送完整文本,让TTS API处理分割)
|
|
|
|
|
- async function startConversion(fullText) {
|
|
|
|
|
- const loadingIndicator = document.getElementById('loading-indicator');
|
|
|
|
|
- try {
|
|
|
|
|
- stopCurrentPlayback(false);
|
|
|
|
|
- const response = await fetch('http://141.140.15.30:8028/generate', {
|
|
|
|
|
- method: 'POST',
|
|
|
|
|
- headers: { 'Content-Type': 'application/json' },
|
|
|
|
|
- body: JSON.stringify({ text: fullText, voice: "af_heart", speed: 1.0 })
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- if (!response.ok) throw new Error(`服务器错误: ${response.status}`);
|
|
|
|
|
-
|
|
|
|
|
- const reader = response.body.getReader();
|
|
|
|
|
- activeStreamReader = reader;
|
|
|
|
|
- const decoder = new TextDecoder();
|
|
|
|
|
- let buffer = '';
|
|
|
|
|
- const audioQueue = [];
|
|
|
|
|
- let expectedIndex = 0;
|
|
|
|
|
-
|
|
|
|
|
- const playController = new AbortController();
|
|
|
|
|
- activePlayController = playController;
|
|
|
|
|
- const playPromise = playAudioSequentially(audioQueue, playController.signal);
|
|
|
|
|
-
|
|
|
|
|
- while (true) {
|
|
|
|
|
- const { done, value } = await reader.read();
|
|
|
|
|
- if (done) break;
|
|
|
|
|
-
|
|
|
|
|
- buffer += decoder.decode(value, { stream: true });
|
|
|
|
|
- const lines = buffer.split('\n');
|
|
|
|
|
- buffer = lines.pop() || '';
|
|
|
|
|
-
|
|
|
|
|
- for (const line of lines) {
|
|
|
|
|
- if (!line.trim()) continue;
|
|
|
|
|
- const data = parseStreamData(line);
|
|
|
|
|
- if (data.error) {
|
|
|
|
|
- throw new Error(data.error);
|
|
|
|
|
- }
|
|
|
|
|
- if (data.audio) {
|
|
|
|
|
- insertInOrder(audioQueue, createAudioChunk(data));
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 处理剩余缓冲
|
|
|
|
|
- if (buffer.trim()) {
|
|
|
|
|
- const data = parseStreamData(buffer.trim());
|
|
|
|
|
- if (data.audio) {
|
|
|
|
|
- insertInOrder(audioQueue, createAudioChunk(data));
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- audioQueue.push(null); // 结束信号
|
|
|
|
|
- await playPromise;
|
|
|
|
|
- activePlayController = null;
|
|
|
|
|
- activeStreamReader = null;
|
|
|
|
|
-
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- activePlayController = null;
|
|
|
|
|
- activeStreamReader = null;
|
|
|
|
|
- if (error.name === 'AbortError') {
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
- console.error(error);
|
|
|
|
|
- loadingIndicator.textContent = `错误: ${error.message}`;
|
|
|
|
|
- loadingIndicator.style.display = 'block';
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ // 开始转换(同域代理,保持流式逐句播放)
|
|
|
|
|
+ async function startConversion(fullText) {
|
|
|
|
|
+ const loadingIndicator = document.getElementById('loading-indicator');
|
|
|
|
|
+ try {
|
|
|
|
|
+ stopCurrentPlayback(false);
|
|
|
|
|
+ const response = await fetch('/generate', {
|
|
|
|
|
+ method: 'POST',
|
|
|
|
|
+ headers: { 'Content-Type': 'application/json' },
|
|
|
|
|
+ body: JSON.stringify({ user_input: fullText, voice: "af_heart", speed: 1.0 })
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ if (!response.ok) throw new Error(`服务器错误: ${response.status}`);
|
|
|
|
|
+
|
|
|
|
|
+ const reader = response.body.getReader();
|
|
|
|
|
+ activeStreamReader = reader;
|
|
|
|
|
+ const decoder = new TextDecoder();
|
|
|
|
|
+ let buffer = '';
|
|
|
|
|
+ const audioQueue = [];
|
|
|
|
|
+ let expectedIndex = 0;
|
|
|
|
|
+
|
|
|
|
|
+ const playController = new AbortController();
|
|
|
|
|
+ activePlayController = playController;
|
|
|
|
|
+ const playPromise = playAudioSequentially(audioQueue, playController.signal);
|
|
|
|
|
+
|
|
|
|
|
+ while (true) {
|
|
|
|
|
+ const { done, value } = await reader.read();
|
|
|
|
|
+ if (done) break;
|
|
|
|
|
+
|
|
|
|
|
+ buffer += decoder.decode(value, { stream: true });
|
|
|
|
|
+ const lines = buffer.split('\n');
|
|
|
|
|
+ buffer = lines.pop() || '';
|
|
|
|
|
+
|
|
|
|
|
+ for (const line of lines) {
|
|
|
|
|
+ if (!line.trim()) continue;
|
|
|
|
|
+ const data = parseStreamData(line);
|
|
|
|
|
+ if (data.error) {
|
|
|
|
|
+ throw new Error(data.error);
|
|
|
|
|
+ }
|
|
|
|
|
+ if (data.audio) {
|
|
|
|
|
+ insertInOrder(audioQueue, createAudioChunk(data));
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (buffer.trim()) {
|
|
|
|
|
+ const data = parseStreamData(buffer.trim());
|
|
|
|
|
+ if (data.audio) {
|
|
|
|
|
+ insertInOrder(audioQueue, createAudioChunk(data));
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ audioQueue.push(null);
|
|
|
|
|
+ await playPromise;
|
|
|
|
|
+ activePlayController = null;
|
|
|
|
|
+ activeStreamReader = null;
|
|
|
|
|
+
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ activePlayController = null;
|
|
|
|
|
+ activeStreamReader = null;
|
|
|
|
|
+ if (error.name === 'AbortError') {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ console.error(error);
|
|
|
|
|
+ loadingIndicator.textContent = `错误: ${error.message}`;
|
|
|
|
|
+ loadingIndicator.style.display = 'block';
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
// 监听文本选择
|
|
// 监听文本选择
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
@@ -1900,7 +1899,7 @@
|
|
|
loadingIndicator.textContent = '正在转换选中文本...';
|
|
loadingIndicator.textContent = '正在转换选中文本...';
|
|
|
loadingIndicator.style.display = 'block';
|
|
loadingIndicator.style.display = 'block';
|
|
|
|
|
|
|
|
- await startConversion(selectedText);
|
|
|
|
|
|
|
+ await startConversion(selectedText);
|
|
|
} catch (error) {
|
|
} catch (error) {
|
|
|
console.error(error);
|
|
console.error(error);
|
|
|
loadingIndicator.textContent = `错误: ${error.message}`;
|
|
loadingIndicator.textContent = `错误: ${error.message}`;
|
|
@@ -1944,7 +1943,7 @@
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- await startConversion(fullText);
|
|
|
|
|
|
|
+ await startConversion(fullText);
|
|
|
} catch (error) {
|
|
} catch (error) {
|
|
|
console.error(error);
|
|
console.error(error);
|
|
|
loadingIndicator.textContent = `错误: ${error.message}`;
|
|
loadingIndicator.textContent = `错误: ${error.message}`;
|