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.
128 lines
3.3 KiB
128 lines
3.3 KiB
1 month ago
|
// 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()
|
||
|
}
|
||
|
}
|