You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

127 lines
3.3 KiB

// utils/pcm-player.js
export default class PCMPlayer {
constructor(options) {
this.queue = [];
this._options = options
this.AllTexts = [];
this.audioCtx = null;
this.startTime = 0;
this._t = null
}
feed(data) {
if (!this.audioCtx) {
this.audioCtx = wx.createWebAudioContext();
}
this.playPCM(data);
}
playPCM(pcmBuffer, sampleRate = 16000) {
const length = pcmBuffer.byteLength / 2; // 16-bit = 2 bytes per sample
const audioBuffer = this.audioCtx.createBuffer(1, length, sampleRate);
const channelData = audioBuffer.getChannelData(0); // Float32Array
const view = new DataView(pcmBuffer);
// 将 16-bit PCM 转换为 Float32 [-1, 1]
for (let i = 0; i < length; i++) {
const sample = view.getInt16(i * 2, true); // little-endian
channelData[i] = sample / 0x8000; // convert to [-1, 1]
}
this.queue.push(audioBuffer);
if (!this.isPlaying) {
// 开始播放
this.startTime = this.audioCtx.currentTime ?? 0;
this.playNext();
this.isPlaying = true;
setTimeout(() => {
this.syncLoop();
});
}
}
// 监听音频播放进度
syncLoop() {
const loop = () => {
const now = this.audioCtx.currentTime - this.startTime;
// 找当前字幕
const index = this.AllTexts.findIndex(
(s) => now >= s.beginTime && now < s.endTime
);
if (index > -1) {
const obj = this.AllTexts[index];
if (this.AllTexts.length == 1) {
// 最后一条数据
obj.is_final = true
}
const tmp = this.AllTexts.shift();
this._options.onPlay && this._options.onPlay(tmp);
if (!this.AllTexts.length) {
clearTimeout(this._t);
return
}
}
if (!this.AllTexts.length) {
this._t = setTimeout(loop, 16);
} else {
if (now < this.AllTexts[this.AllTexts.length - 1].endTime) {
this._t = setTimeout(loop, 16);
} else {
// 结束
clearTimeout(this._t);
console.log('语音播放结束')
this.destroy()
}
}
};
loop();
}
playNext() {
if (this.queue.length === 0) {
// 结束
this.isPlaying = true;
return;
}
this.isPlaying = true;
const source = this.audioCtx.createBufferSource();
source.buffer = this.queue.shift();
source.connect(this.audioCtx.destination);
source.start();
source.onended = () => {
setTimeout(() => {
this.playNext();
});
};
}
// 增加文字
addText(res) {
if (res.result && res.result.subtitles && res.result.subtitles.length) {
const tmpText = res.result.subtitles.map((it) => {
return {
...it,
content: it.text,
beginTime: it.beginTime / 1000,
endTime: it.endTime / 1000,
request_id: res.requestId,
sessionId: res.sessionId
};
});
this.AllTexts = [...this.AllTexts, ...tmpText];
}
}
close() {
this.isPlaying = false;
this.audioCtx.close();
}
destroy() {
this.queue = [];
this.isPlaying = false;
this.audioCtx && this.audioCtx.close();
this.audioCtx = null;
this.AllTexts = [];
this.startTime = 0;
this._options.onAudioEnd && this._options.onAudioEnd()
}
}