| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173 |
- import { LAppDelegate } from '@/lib/live2d/src/lappdelegate';
- import { ResourceModel } from '@/lib/protocol';
- export class Live2dManager {
- // 单例
- public static getInstance(): Live2dManager {
- if (! this._instance) {
- this._instance = new Live2dManager();
- }
- return this._instance;
- }
- public setReady(ready: boolean) {
- this._ready = ready;
- }
- public isReady(): boolean {
- return this._ready;
- }
- public changeCharacter(character: ResourceModel | null) {
- // _subdelegates中只有一个画布, 所以设置第一个即可
- this._ready = false;
- LAppDelegate.getInstance().changeCharacter(character)
- }
- public setLipFactor(weight: number): void {
- this._lipFactor = weight;
- }
- public getLipFactor(): number {
- return this._lipFactor;
- }
- public pushAudioQueue(audioData: ArrayBuffer): void {
- this._ttsQueue.push(audioData);
- }
- public popAudioQueue(): ArrayBuffer | null {
- if (this._ttsQueue.length > 0) {
- const audioData = this._ttsQueue.shift();
- return audioData;
- } else {
- return null;
- }
- }
- public clearAudioQueue(): void {
- this._ttsQueue = [];
- }
- public playAudio(): ArrayBuffer | null {
- // 如果已停止,不再播放任何音频
- if (this._isStopped) return null;
- if (this._audioIsPlaying) return null; // 如果正在播放则返回
- const audioData = this.popAudioQueue();
- if (audioData == null) return null; // 没有音频数据则返回
- this._audioIsPlaying = true;
- // 播放音频
- const playAudioBuffer = (buffer: AudioBuffer) => {
- // 再次检查是否已停止
- if (this._isStopped) {
- this._audioIsPlaying = false;
- return;
- }
- var source = this._audioContext.createBufferSource();
- source.buffer = buffer;
-
- source.connect(this._audioContext.destination);
- // 监听音频播放完毕事件
- source.onended = () => {
- this._audioIsPlaying = false;
- // 检查是否所有音频都已播放完成
- this.notifyAudioComplete();
- };
- source.start();
- this._audioSource = source;
- }
- // 创建一个新的 ArrayBuffer 并复制数据, 防止原始数据被decodeAudioData释放
- const newAudioData = audioData.slice(0);
- this._audioContext.decodeAudioData(newAudioData).then(
- buffer => {
- // 在 decodeAudioData 完成后,再次检查是否已停止
- if (!this._isStopped) {
- playAudioBuffer(buffer);
- } else {
- this._audioIsPlaying = false;
- }
- }
- ).catch((error) => {
- // decodeAudioData 失败时,重置播放状态
- this._audioIsPlaying = false;
- });
- return audioData;
- }
- public stopAudio(): void {
- // 设置停止标志,防止新的音频开始播放
- this._isStopped = true;
- // 清空队列
- this.clearAudioQueue();
- // 停止当前播放的音频
- if (this._audioSource) {
- try {
- this._audioSource.stop();
- } catch (e) {
- // 如果音频已经停止,忽略错误
- console.log('[Live2dManager] Audio source already stopped');
- }
- this._audioSource = null;
- }
- this._audioIsPlaying = false;
- }
-
- // 重置停止标志,允许新的音频播放
- public resetStopFlag(): void {
- this._isStopped = false;
- }
- public isAudioPlaying(): boolean {
- return this._audioIsPlaying;
- }
- public isAudioQueueEmpty(): boolean {
- return this._ttsQueue.length === 0;
- }
- public isChatComplete(): boolean {
- return !this._audioIsPlaying && this._ttsQueue.length === 0;
- }
- public setOnAudioCompleteCallback(callback: (() => void) | null): void {
- this._onAudioCompleteCallback = callback;
- }
- private notifyAudioComplete(): void {
- // 延迟检查,避免在音频转换间隙错误触发回调
- // 当一段音频播放完成时,可能还有音频正在转换中,需要等待一小段时间
- // 使用 requestAnimationFrame 确保在下一帧检查,给音频转换留出时间
- // 注意:不需要在这里调用 playAudio(),因为 playAudio() 已经在动画循环中每帧被调用了
- requestAnimationFrame(() => {
- // 延迟一小段时间后再检查,确保音频转换有时间完成
- // 这样可以避免在音频转换间隙错误触发回调
- // 使用较长的延迟(100ms),确保音频转换有足够时间完成
- setTimeout(() => {
- // 再次检查队列是否为空,如果队列中有新音频,说明还有音频在转换中,不应该触发回调
- if (this._onAudioCompleteCallback && this.isChatComplete()) {
- this._onAudioCompleteCallback();
- }
- }, 100); // 延迟100ms,给音频转换留出足够时间
- });
- }
- constructor() {
- this._audioContext = new (window.AudioContext || (window as any).webkitAudioContext)();
- this._audioIsPlaying = false;
- this._audioSource = null;
- this._lipFactor = 1.0;
- this._ready = false;
- this._onAudioCompleteCallback = null;
- }
- private static _instance: Live2dManager;
- private _ttsQueue: ArrayBuffer[] = [];
- private _audioContext: AudioContext;
- private _audioIsPlaying: boolean;
- private _audioSource: AudioBufferSourceNode | null;
- private _lipFactor: number;
- private _ready: boolean;
- private _onAudioCompleteCallback: (() => void) | null;
- private _isStopped: boolean = false; // 标记是否已停止
- }
|