diff --git a/bricks/audio.js b/bricks/audio.js index 2f06e57..d8b687c 100755 --- a/bricks/audio.js +++ b/bricks/audio.js @@ -267,6 +267,56 @@ bricks.SttClient = class extends bricks.VBox { this.result_text.set_text(txt); } } + +bricks.ContinueAudioPlayer = class extends bricks.AudioPlayer { + constructor(opts){ + this.options = opts + this.audioContext = null; + this.nextStartTime = 0; + this.started = false; + this.initAudioContext(); // 初始化音频上下文 + } + initAudioContext() { + this.audioContext = new (window.AudioContext || window.webkitAudioContext)(); + this.nextStartTime = this.audioContext.currentTime; + this.started = true; + } + handleAudioTrack(arrayBuffer) { + this.audioContext.decodeAudioData(arrayBuffer).then(decodedBuffer => { + const source = this.audioContext.createBufferSource(); + source.buffer = decodedBuffer; + source.connect(this.audioContext.destination); + + // 连续播放安排 + const startTime = Math.max(this.audioContext.currentTime, this.nextStartTime); + source.start(startTime); + + this.nextStartTime = startTime + decodedBuffer.duration; + }).catch(err => { + console.error("Error decoding audio data:", err); + }); + } + pauseAudio() { + if (this.audioContext && this.audioContext.state === 'running') { + this.audioContext.suspend().then(() => { + console.log("Audio playback paused."); + }); + } + } + + /** + * ▶️ 恢复音频播放(继续 AudioContext) + */ + resumeAudio() { + if (this.audioContext && this.audioContext.state === 'suspended') { + this.audioContext.resume().then(() => { + console.log("Audio playback resumed."); + }); + } + } +} bricks.Factory.register('AudioPlayer', bricks.AudioPlayer); bricks.Factory.register('AudioRecorder', bricks.AudioRecorder); bricks.Factory.register('SttClient', bricks.SttClient); + + diff --git a/bricks/continueaudio.js b/bricks/continueaudio.js new file mode 100644 index 0000000..8c34981 --- /dev/null +++ b/bricks/continueaudio.js @@ -0,0 +1,134 @@ +var bricks = window.bricks || {}; + +bricks.ContinueAudioPlayer = class extends class bricks.VBox { + constructor(options) { + super(options); + this.ws_url = options.ws_url; + this.options = options || {}; + this.audioContext = null; + this.gainNode = null; + this.nextStartTime = 0; + this.started = false; + this.muted = false; + this.volume = 1.0; + + this.initAudioContext(); + this.initWebSocket(); + } + + initAudioContext() { + this.audioContext = new (window.AudioContext || window.webkitAudioContext)(); + this.gainNode = this.audioContext.createGain(); + this.gainNode.gain.value = this.volume; + this.gainNode.connect(this.audioContext.destination); + + this.nextStartTime = this.audioContext.currentTime; + this.started = true; + } + + base64ToArrayBuffer(base64) { + const binaryStr = atob(base64); + const len = binaryStr.length; + const bytes = new Uint8Array(len); + for (let i = 0; i < len; i++) { + bytes[i] = binaryStr.charCodeAt(i); + } + return bytes.buffer; + } + + handleAudioTrack(arrayBuffer) { + this.audioContext.decodeAudioData(arrayBuffer).then(decodedBuffer => { + const source = this.audioContext.createBufferSource(); + source.buffer = decodedBuffer; + source.connect(this.gainNode); + + const startTime = Math.max(this.audioContext.currentTime, this.nextStartTime); + source.start(startTime); + + this.nextStartTime = startTime + decodedBuffer.duration; + + if (typeof this.options.onStart === 'function') { + this.options.onStart(); + } + + source.onended = () => { + if (typeof this.options.onEnd === 'function') { + this.options.onEnd(); + } + }; + }).catch(err => { + console.error("Error decoding audio data:", err); + }); + } + + /** + * ⏸ 暂停播放 + */ + pauseAudio() { + if (this.audioContext && this.audioContext.state === 'running') { + this.audioContext.suspend().then(() => { + if (typeof this.options.onPause === 'function') { + this.options.onPause(); + } + }); + } + } + + /** + * ▶️ 恢复播放 + */ + resumeAudio() { + if (this.audioContext && this.audioContext.state === 'suspended') { + this.audioContext.resume().then(() => { + if (typeof this.options.onResume === 'function') { + this.options.onResume(); + } + }); + } + } + + /** + * 🔁 重新开始播放 + */ + restart() { + console.log("Restarting audio playback..."); + if (this.audioContext && this.audioContext.state !== 'closed') { + this.audioContext.close().then(() => { + this.initAudioContext(); + }); + } else { + this.initAudioContext(); + } + } + + /** + * 🔊 设置音量(0.0 - 1.0) + */ + setVolume(value) { + this.volume = Math.max(0, Math.min(1, value)); + if (this.gainNode) { + this.gainNode.gain.value = this.muted ? 0 : this.volume; + } + this.emit('onVolumeChange', this.volume); + } + + /** + * 🔇 切换静音 + */ + toggleMute() { + this.muted = !this.muted; + this.gainNode.gain.value = this.muted ? 0 : this.volume; + this.emit('onVolumeChange', this.muted ? 0 : this.volume); + } + + /** + * 🧩 触发事件回调 + */ + emit(eventName, ...args) { + if (typeof this.options[eventName] === 'function') { + this.options[eventName](...args); + } + } +} + +bricks.Factory.register('ContinueAudioPlayer', bricks.ContinueAudioPlayer);