所有文章都是免费查看的,如果有无法查看的情况,烦请联系我修改哈~
一、主要功能
- 每段语音前带有提示音
- 可以设置一段语音的播报次数
- 语音播报可以及时停止(
第二遍播报时必须等播报完成才能停止,此处bug暂未处理,欢迎交流指导) - 每当有新数据产生,开始播报,循环监听数据更新
- 离开页面立刻停止播报
- 20250403:修改无限递归造成的内存泄露bug、增加catch防止接口失败导致递归崩溃
二、源代码
<template>
<div>
...
<audio ref="audioPlayer" src="~@/assets/AlarmMusic.wav"></audio>
</div>
</template>
<script>
export default {
data() {
return {
...
// 播报的语音数据
selectList: [
{
voiceNum: 2, // 需播放次数
broadcastedNum: 0, // 已播放次数
content: '', // 播放内容
state: 0, // 0:待播放,1:已播放
}
],
// 语音停止信号
stopPlay: true,
}
},
methods: {
// 开始播报
async startBroadcast() {
await this.wait(1000); // 防止重复获取列表接口
let broadcastList = this.selectList.slice();
this.stopPlay = false;
await this.playNext(broadcastList); // 调用递归播放下一个语音
},
// 递归播放下一个语音
async playNext(broadcastList) {
// 次数刷新时无需重新获取列表
let broadcastList = list;
// 20250403:调整位置
if (this.stopPlay) {
return; // 如果被停止,结束
}
if (list.length <= 0) {
await this.wait(3000); // 休息3s,防止接口被调用频繁
// 每次刷新重新获取播放列表
await this.getList(() => {
// broadcastList = this.selectList.slice(); // 20250403:修改逻辑
return this.playNext(this.selectList.slice()); // 20250403:使用新列表重启流程,重置递归,防止浏览器内存崩溃
});
}
// 20250403:此处重复,去除
// if (broadcastList.length === 0) {
// if (this.stopPlay) {
// return; // 如果被停止,结束
// } else {
// await this.wait(3000);
// await this.playNext([]); // 没有待播放的语音继续等待新的语音
// }
// }
let currentItem = broadcastList[0]; // 获取第一个待播放的语音
// 判断 已播放次数 > 需播放次数,减少次数
if (currentItem && currentItem.broadcastedNum < currentItem.voiceNum) {
// 播放提示音
this.playBeep(() => {
// 提示音播放完后,播放语音
this.playSpeech(currentItem.content, () => {
// 语音播放完后,增加已播放次数
currentItem.broadcastedNum++;
if (currentItem.broadcastedNum === currentItem.voiceNum) {
this.updateBroadcastState(currentItem);
this.playNext([]); // 递归调用播放下一个
} else {
// 次数更新
this.updateBroadcastTimes(currentItem);
this.playNext(broadcastList); // 继续播放
}
});
});
}
},
// 播放提示音,播放完成后回调
playBeep(callback) {
const beep = this.$refs.audioPlayer; // 提示音文件
beep.play();
beep.onended = callback; // 提示音播放完成后调用回调函数
},
// 播放语音并在播放完成后回调
playSpeech(text, callback) {
const utterance = new SpeechSynthesisUtterance(text); // 创建语音
utterance.lang = 'zh-CN';
utterance.volume = 1;
utterance.rate = 1.5;
utterance.pitch = 1;
utterance.onend = callback; // 语音播放完毕后调用回调函数
window.speechSynthesis.speak(utterance); // 启动语音播放
},
// 更新次数
// 20250403:修改代码增加错误处理,防止发布时造成的回调崩溃
async updateBroadcastTimes(currentItem) {
// 播完了把播放次数更新一下
let broadcastNum = currentItem.broadcastedNum;
// 此处是更新次数的后台接口,根据自己代码修改
await addBroadcastedNum(currentItem).catch(() => {});
// 前端更新次数,用于展示
for(let t in this.selectList) {
if(t.Id === currentItem.Id) {
this.selectList[0].broadcastedNum = broadcastNum;
}
}
},
// 更新状态
// 20250403:修改代码增加错误处理,防止发布时造成的回调崩溃
async updateBroadcastState(currentItem) {
// 此处是更新状态的后台接口,根据自己代码修改
await broadcastCompleted(currentItem).catch(() => {});
},
// 停止语音
stopBroadcast() {
this.stopPlay = true;
// 停止所有语音播放
const synth = window.speechSynthesis;
synth.cancel(); // 取消所有正在播放的语音
// 强制停止语音播放(如果 cancel 无效)
if (synth.speaking) {
synth.pause(); // 暂停语音
synth.cancel(); // 再次取消
}
// 停止提示音
const beep = this.$refs.audioPlayer;
beep.pause(); // 暂停播放
beep.currentTime = 0; // 将播放进度重置为 0
},
// 休息
wait(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
},
// 查询列表(包含回调)
// 20250403:修改代码增加错误处理,防止发布时造成的回调崩溃
async getList(callback) {
this.loading = true;
await getNotBroadcastRecord(this.queryParams).then(res => {
this.selectList = res.rows;
this.total = res.total;
}).catch(e => {
console.error("获取未广播记录失败:", e);
this.wait(3000);
this.selectList = []; // 提供默认值
this.total = 0;
}).finally(() => {
this.loading = false;
if (!!callback) {
callback();
} else {
this.startBroadcast();
}
});
}
},
beforeDestroy() {
// 离开页面停止语音播报
this.stopBroadcast();
}
}
</script>
三、PS:
- 更新次数与状态的代码涉及到与后端交互,这边直接省略(对主体功能无影响)
- 代码无法直接运行,需要配置测试数据以及触发按钮
- 提示音