【vue】网页语音播报

所有文章都是免费查看的,如果有无法查看的情况,烦请联系我修改哈~

一、主要功能

  • 每段语音前带有提示音
  • 可以设置一段语音的播报次数
  • 语音播报可以及时停止(第二遍播报时必须等播报完成才能停止,此处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:

  • 更新次数与状态的代码涉及到与后端交互,这边直接省略(对主体功能无影响)
  • 代码无法直接运行,需要配置测试数据以及触发按钮
  • 提示音
     
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值