mirror of
https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web.git
synced 2025-11-12 04:03:42 +08:00
feat: 更新语音合成接口,支持流式播放和多种音频格式
This commit is contained in:
@@ -1,19 +1,38 @@
|
||||
type TTSPlayer = {
|
||||
init: () => void;
|
||||
play: (audioBuffer: ArrayBuffer, onended: () => void | null) => Promise<void>;
|
||||
play: (
|
||||
audioBuffer: ArrayBuffer | AudioBuffer,
|
||||
onended: () => void | null,
|
||||
) => Promise<void>;
|
||||
playQueue: (
|
||||
audioBuffers: (ArrayBuffer | AudioBuffer)[],
|
||||
onended: () => void | null,
|
||||
) => Promise<void>;
|
||||
addToQueue: (audioBuffer: ArrayBuffer | AudioBuffer) => void;
|
||||
startStreamPlay: (onended: () => void | null) => void;
|
||||
finishStreamPlay: () => void;
|
||||
stop: () => void;
|
||||
};
|
||||
|
||||
export function createTTSPlayer(): TTSPlayer {
|
||||
let audioContext: AudioContext | null = null;
|
||||
let audioBufferSourceNode: AudioBufferSourceNode | null = null;
|
||||
let isPlaying = false;
|
||||
let playQueue: (ArrayBuffer | AudioBuffer)[] = [];
|
||||
let currentOnended: (() => void | null) | null = null;
|
||||
let isStreamMode = false;
|
||||
let streamFinished = false;
|
||||
|
||||
const init = () => {
|
||||
console.log("[TTSPlayer] init");
|
||||
audioContext = new (window.AudioContext || window.webkitAudioContext)();
|
||||
audioContext.suspend();
|
||||
};
|
||||
|
||||
const play = async (audioBuffer: ArrayBuffer | AudioBuffer, onended: () => void | null) => {
|
||||
const play = async (
|
||||
audioBuffer: ArrayBuffer | AudioBuffer,
|
||||
onended: () => void | null,
|
||||
) => {
|
||||
if (audioBufferSourceNode) {
|
||||
audioBufferSourceNode.stop();
|
||||
audioBufferSourceNode.disconnect();
|
||||
@@ -33,17 +52,109 @@ export function createTTSPlayer(): TTSPlayer {
|
||||
audioBufferSourceNode.onended = onended;
|
||||
};
|
||||
|
||||
const stop = () => {
|
||||
const playNext = async () => {
|
||||
if (playQueue.length === 0) {
|
||||
// 在流模式下,如果队列为空但流还没结束,等待
|
||||
if (isStreamMode && !streamFinished) {
|
||||
setTimeout(() => playNext(), 100);
|
||||
return;
|
||||
}
|
||||
|
||||
isPlaying = false;
|
||||
isStreamMode = false;
|
||||
streamFinished = false;
|
||||
if (currentOnended) {
|
||||
currentOnended();
|
||||
currentOnended = null;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const nextBuffer = playQueue.shift()!;
|
||||
let buffer: AudioBuffer;
|
||||
if (nextBuffer instanceof AudioBuffer) {
|
||||
buffer = nextBuffer;
|
||||
} else {
|
||||
buffer = await audioContext!.decodeAudioData(nextBuffer);
|
||||
}
|
||||
|
||||
if (audioBufferSourceNode) {
|
||||
audioBufferSourceNode.stop();
|
||||
audioBufferSourceNode.disconnect();
|
||||
}
|
||||
|
||||
audioBufferSourceNode = audioContext!.createBufferSource();
|
||||
audioBufferSourceNode.buffer = buffer;
|
||||
audioBufferSourceNode.connect(audioContext!.destination);
|
||||
audioBufferSourceNode.onended = () => {
|
||||
playNext();
|
||||
};
|
||||
|
||||
await audioContext!.resume();
|
||||
audioBufferSourceNode.start();
|
||||
};
|
||||
|
||||
const playQueueMethod = async (
|
||||
audioBuffers: (ArrayBuffer | AudioBuffer)[],
|
||||
onended: () => void | null,
|
||||
) => {
|
||||
playQueue = [...audioBuffers];
|
||||
currentOnended = onended;
|
||||
if (!isPlaying) {
|
||||
isPlaying = true;
|
||||
await playNext();
|
||||
}
|
||||
};
|
||||
|
||||
const addToQueue = (audioBuffer: ArrayBuffer | AudioBuffer) => {
|
||||
if (streamFinished) {
|
||||
return;
|
||||
}
|
||||
playQueue.push(audioBuffer);
|
||||
};
|
||||
|
||||
const startStreamPlay = (onended: () => void | null) => {
|
||||
isStreamMode = true;
|
||||
streamFinished = false;
|
||||
playQueue = [];
|
||||
currentOnended = onended;
|
||||
|
||||
if (!isPlaying) {
|
||||
isPlaying = true;
|
||||
playNext();
|
||||
}
|
||||
};
|
||||
|
||||
const finishStreamPlay = () => {
|
||||
streamFinished = true;
|
||||
};
|
||||
|
||||
const stop = async () => {
|
||||
console.log("[TTSPlayer] stop");
|
||||
playQueue = [];
|
||||
isPlaying = false;
|
||||
isStreamMode = false;
|
||||
streamFinished = true;
|
||||
currentOnended = null;
|
||||
|
||||
if (audioBufferSourceNode) {
|
||||
audioBufferSourceNode.stop();
|
||||
audioBufferSourceNode.disconnect();
|
||||
audioBufferSourceNode = null;
|
||||
}
|
||||
if (audioContext) {
|
||||
audioContext.close();
|
||||
await audioContext.close();
|
||||
audioContext = null;
|
||||
}
|
||||
};
|
||||
|
||||
return { init, play, stop };
|
||||
}
|
||||
return {
|
||||
init,
|
||||
play,
|
||||
playQueue: playQueueMethod,
|
||||
addToQueue,
|
||||
startStreamPlay,
|
||||
finishStreamPlay,
|
||||
stop,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user