前端请求音频返回pcm流进行播放

业务场景是chat回答,点击播放则会将回答内容进行请求,返回音频数据流进行播放

实现方案,因为后端返回的是流式接口,但是流式接口我去截取后用自己完成的流式播放器方法进行播放会存在杂音,但是短句接口返回速度尚可,所以截取需要转音频的短句进行多次调用接口,返回的数据进行处理后存储下来,播放完上一段音频数据后即刻播放下一条。

注意返回的接口数据是pcm的base64编码格式

以下是代码片段

if (type === "播放") {if (audioElement.value) {stopAudio();}currentIndex = 0;audioUrls = [];isPlay.forEach((item, index) => {isPlay[index] = false;});isPlay.push(true);// getSpeech(oldAnswer.replace(/[\n\t\s*]+/g, ""));let text = oldAnswer.replace(/[\n\t\s*]+/g, "");let arr = text.split("。").filter(Boolean);audioUrls = []; // 用于存储所有音频的 URLcurrentIndex = 0; // 当前应该播放的音频索引fetchAndPlayAudios(arr, isPlay.length - 1);
}

let audioUrls = []; // 用于存储所有音频的 URL
let currentIndex = 0; // 当前应该播放的音频索引
let isPlay = [];
async function fetchAndPlayAudios(texts, isPlayIndex) {for (let index = 0; index < texts.length; index++) {console.log("index", index);if (!isPlay[isPlayIndex]) {break;}const res = await getSpeechAPI({input_text: texts[index],spk_id: "0",});// 解码 Base64 数据并存储 URLconst audioUrl = pcmToAudioUrl(res);audioUrls.push(audioUrl);// 如果这是第一个音频,则立即播放它if (index === 0) {playNextAudio();}if (index == texts.length - 1) {isPlay[isPlayIndex] = false;}}
}
const audioElement = ref("");
function playNextAudio() {if (currentIndex < audioUrls.length) {audioElement.value = new Audio(audioUrls[currentIndex]);// 监听音频播放结束事件audioElement.value.addEventListener("ended", () => {audioElement.value.pause(); // 实际上在"ended"事件中,播放已经结束,但这行可以保留作为清晰性audioElement.value.currentTime = 0; // 重置播放位置currentIndex++; // 移动到下一个音频playNextAudio(); // 播放下一个音频(如果有的话)});audioElement.value.play();}
}const stopAudio = () => {audioElement.value.pause(); // 实际上在"ended"事件中,播放已经结束,但这行可以保留作为清晰性audioElement.value.currentTime = 0; // 重置播放位置
};
function pcmToAudioUrl(base64Data) {// console.log(base64Data)let pcmData = base64ToUint8Array(base64Data);// 创建WAV格式的Blob对象 (这是重点!直接创建blob数据是无法播放的!)const wavBlob = createWavBlob(pcmData);// 将URL设置为音频源即可return URL.createObjectURL(wavBlob);// base64编码的pcm16音频数据 转换为unit8格式数据function base64ToUint8Array(base64String) {const padding = "=".repeat((4 - (base64String.length % 4)) % 4);const base64 = (base64String + padding).replace(/-/g, "+").replace(/_/g, "/");const rawData = window.atob(base64);const outputArray = new Uint8Array(rawData.length);for (let i = 0; i < rawData.length; ++i) {outputArray[i] = rawData.charCodeAt(i);}return outputArray;}// 创建WAV格式的Blob对象 (调节采样率来微调语速和音调)function createWavBlob(pcmData) {const format = 1; // 格式代码(1表示PCM)const numChannels = 1; // 声道数量(单声道为1,立体声为2)const sampleRate = 26500; // 采样率(例如44100 Hz)const bitsPerSample = 16; // 每样本的位数(例如16位)const blockAlign = numChannels * (bitsPerSample / 8); // 对齐单位const byteRate = sampleRate * blockAlign; // 每秒的字节数const buffer = new ArrayBuffer(44 + pcmData.length); // WAV文件头部长度为44字节const view = new DataView(buffer);// 写入WAV文件头部信息writeString(view, 0, "RIFF"); // ChunkIDview.setUint32(4, 36 + pcmData.length, true); // ChunkSizewriteString(view, 8, "WAVE"); // FormatwriteString(view, 12, "fmt "); // Subchunk1IDview.setUint32(16, 16, true); // Subchunk1Sizeview.setUint16(20, format, true); // AudioFormatview.setUint16(22, numChannels, true); // NumChannelsview.setUint32(24, sampleRate, true); // SampleRateview.setUint32(28, byteRate, true); // ByteRateview.setUint16(32, blockAlign, true); // BlockAlignview.setUint16(34, bitsPerSample, true); // BitsPerSamplewriteString(view, 36, "data"); // Subchunk2IDview.setUint32(40, pcmData.length, true); // Subchunk2Size// 将PCM数据写入bufferconst pcmDataView = new Uint8Array(buffer, 44);pcmDataView.set(pcmData);return new Blob([view], { type: "audio/wav" });}// 写入字符串到DataView中的指定位置function writeString(view, offset, string) {for (let i = 0; i < string.length; i++) {view.setUint8(offset + i, string.charCodeAt(i));}}
}

业务场景:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/150422.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

composer环境变量(phpstudy集成环境)无法使用问题

composer 不是内部或外部命令,也不是可运行的程序 或批处理文件。 按下WinR组合键打开“运行”&#xff0c;输入sysdm.cpl 回车&#xff0c;打开“系统属性”并切换至“高级”选项卡&#xff0c;点击“环境变量”进行配置 配置完后点击确定&#xff0c;重新打开命令行&#x…

Bootstrap框架-container类,container-fluid类,栅格系统

1.Bootstrap Bootstrap为页面内容和栅格系统包裹了一个.container容器&#xff0c;框架预先定义类 1.1container类 响应式布局容器的宽度 手机-小于768px 宽度设置100%&#xff1b; 平板-大于等于768px 设置宽度为750px 桌面显示器-大于等于992px 设置宽度 970px 大屏幕显…

康养为松,智能为鹤:华为全屋智能画出的松鹤长春图

在道家文化中&#xff0c;喜欢将松与鹤并举&#xff0c;以其长寿与仙逸表达对老年人的美好祝愿。松鹤延年、松龄鹤寿等成语皆出于此。松鹤长春图&#xff0c;也成为国画当中的经久不衰的题材。 当我们迎来老龄化时代&#xff0c;“松鹤长春”则成为了整个社会的共同期待。 根据…

【初阶数据结构】排序——插入排序

目录 前言直接插入排序希尔排序 前言 排序&#xff1a;所谓排序就是使一串记录&#xff0c;按照其中的某个或某些关键字的大小&#xff0c;递增或递减的排列起来的操作。排序算法&#xff0c;就是如何使得记录按照要求排列的方法。   例如&#xff1a;买东西时会根据销量或价…

java并发编程笔记 之 线程和进程

文章目录 前言线程线程优先级和时间片创建多线程及运行线程的状态 进程查看进程的命令进程的通信方式 线程和进程的区别从关系上疑问集锦 前言 并发 1、并发是指在同一时间段内&#xff0c;计算机系统能够处理多个任务的能力。 2、在并发编程中&#xff0c;我们可以理解为多个…

代码随想录算法训练营第三十九天 | 198.打家劫舍 ,213.打家劫舍II,337.打家劫舍III

第三十九天打卡&#xff0c;今天解决打家劫舍系列问题&#xff0c;树形dp比较难。 198.打家劫舍 题目链接 解题过程 dp[i]&#xff1a;考虑下标i&#xff08;包括i&#xff09;以内的房屋&#xff0c;最多可以偷窃的金额为dp[i]。 要么不偷这一间&#xff0c;那就是前面那间…

毕业设计选题:基于ssm+vue+uniapp的校园失物招领小程序

开发语言&#xff1a;Java框架&#xff1a;ssmuniappJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;M…

大瓜-CSP-J/S2024第一轮认证题目涉嫌泄露。竞赛公平能否维护?

2024年全国信息学奥赛&#xff08;CSP-J/S&#xff09;泄题事件在竞赛界掀起了巨大的波澜。这场赛事本应是全国最具公信力的编程竞赛之一&#xff0c;但部分题目在考试前已被某些培训机构押中&#xff0c;这一泄题行为不仅让考生与家长感到愤怒&#xff0c;也让公众对奥赛的公平…

scp 命令:在两台主机间远程传输文件

一、命令简介 ​scp​ 命令使用 SSH ​加密的方式在本地主机和远程主机之间复制文件。 ‍ 二、命令参数 格式 scp [选项] 发送方主机和目录 接收方主机和目录注意&#xff1a;左边是发送方&#xff0c;右边是接收方。固定格式。 示例 #示例1 scp ~/test.txt soulio172.1…

豆包MarsCode体验

这个AI助手贴合做题者的思路&#xff0c;可以实时对代码进行分析&#xff0c;提出纠错、优化、规范性意见&#xff0c;非常好用。

基于数据挖掘的航空客户满意度分析预测系统

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长 QQ 名片 :) 1. 项目简介 航空公司致力于提供多样化的服务以满足乘客需求&#xff0c;包括但不限于提供免费无线网络、免费食物饮品、提供网上预约服务、飞机出口位置、座椅舒适度、卫生状况等&#xff0c;并希望以此提升乘…

构造者模式多种实现方式

构造者模式 ​ 构造者模式建议将对象构造代码从产品类中抽取出来&#xff0c; 并将其放在一个名为构造者的独立对象中 ​ 构建者模式也是用来创建对象&#xff0c;但是相对于工厂模式来说&#xff0c;建造者模式适用于构建复杂对象&#xff0c;而工厂模式适用于创建对象的封装…

asp.net core日志与异常处理小结

asp.net core的webApplicationBuilder中自带了一个日志组件,无需手动注册服务就能直接在控制器中构造注入&#xff0c;本文主要介绍了net core日志与异常处理小结&#xff0c;需要的朋友可以参考下 ILogger简单使用 asp.net core的webApplicationBuilder中自带了一个日志组件…

网络安全-长亭雷池waf的sql绕过,安全狗绕过(5种绕过3+2)

目录 一、环境 二、讲解 三、绕过前思路整理 3.1 思路 3.1.1 入门思路 0x00截断filename 3.1.2 双写上传描述行(差异绕过&#xff09;【成功】 3.1.3双写整个 part 开头部分 3.1.4 构造假的 part 部分 1【成功】 3.1.5 构造假的 part 部分2【成功】 3.1.6 两个 bounda…

闲盒支持的组网方式和注意事项

1. 直连光猫拨号​ 通过光猫拨号&#xff0c;设备直连光猫的设备&#xff0c;需要对光猫开启UPNP并关闭DMZ 如果只接一个盒子&#xff0c;建议直接针对盒子IP开dmz。 2. 直连路由器​ 通过路由器拨号&#xff0c;设备直连路由器的设备&#xff0c;需要对路由器开启UPNP并关闭…

Sql Developer日期显示格式设置

默认时间格式显示 设置时间格式&#xff1a;工具->首选项->数据库->NLS->日期格式: DD-MON-RR 修改为: YYYY-MM-DD HH24:MI:SS 设置完格式显示&#xff1a;

【Java数据结构】 ---对象的比较

乐观学习&#xff0c;乐观生活&#xff0c;才能不断前进啊&#xff01;&#xff01;&#xff01; 我的主页&#xff1a;optimistic_chen 我的专栏&#xff1a;c语言 &#xff0c;Java 欢迎大家访问~ 创作不易&#xff0c;大佬们点赞鼓励下吧~ 前言 上图中&#xff0c;线性表、堆…

【嵌入式linux开发】SPI设备文件操作BMI088传感器

【嵌入式linux开发】SPI设备文件操作BMI088传感器 前言一、数据手册浅读二、代码 前言 在本篇博客中&#xff0c;将从BMI088传感器的数据手册出发&#xff0c;简单了解之后&#xff0c;展示如何通过SPI设备文件与传感器进行通信。除了使用linux文件设备操作spi接口&#xff0c…

微软 Win11 24H2 RP 26100.1876 预览版发布!附详细更新日志

系统之家于9月24日发出最新报道&#xff0c;微软为Release Preview频道的Windows Insider项目成员&#xff0c;发布了适用Windows11 24H2版本更新的 KB5043178&#xff0c;更新后&#xff0c;系统版本号将升至26100.1876。此更新为用户带来了不同的新功能&#xff0c;例如打开开…

力扣每日一题 字符串中最多数目的子序列 贪心 字符串 前缀和

Problem: 2207. 字符串中最多数目的子序列 &#x1f468;‍&#x1f3eb; 参考题解 class Solution {public long maximumSubsequenceCount(String s, String pattern){long res 0;long cnt1 0, cnt2 0;for (int i 0; i < s.length(); i){if (s.charAt(i) pattern.cha…