赞
踩
业务需求:根据后台返回消息播报语音,要求后台运行可继续播报
实现步骤:
- 建立socket连接监听消息,并建立心跳检测机制,防止socket意外断连
- 将接收到的文字信息转化为音频文件
- 使用uni.getBackgroundAudioManager(),实现后台运行可以持续播报音频
- 解决并发问题,同时接收多个文件按顺序播报
1. 建立socket连接
- onShow() {
- const SocketTask = getApp().globalData.SocketTask;
- if (!SocketTask) {
- this.linkSocket();
- }
- }
- async linkSocket() {
- // 连接Socket服务器
- let SocketTask;
- const TOKEN = store.getters['base/_token'];
- await new Promise((resolve, reject) => {
- SocketTask = uni.connectSocket({
- url: wsUrl,
- header: {
- 'content-type': 'application/json',
- Authorization: TOKEN
- },
- success: () => {
- console.log(`WebSocket connect成功`);
- resolve();
- },
- fail: () => {
- console.log('WebSocket connect失败');
- reject();
- }
- });
- });
- SocketTask.id = this.randomInt(1000, 9999);
- // 初始化心跳,心跳用于检测连接是否正常
- SocketTask.heartCheck = { ...heartCheck };
- SocketTask.reconnectObj = { ...reconnectObj };
- // 赋值全局变量,用于判断是否已连接
- getApp().globalData.SocketTask = this.SocketTask = SocketTask;
- // 开始监听
- this.initEventHandle(this.SocketTask);
- }
- randomInt(min, max) {
- return Math.floor(Math.random() * (max - min + 1) + min);
- }
定义心跳对象
- // ws 心跳对象
- let heartCheck = {
- timeout: 5000, // 收到pong后再次发ping的间隔时间;也是ws连接不上的最大等待时间 之后关掉连接并重连
- timeoutObj: null,
- serverTimeoutObj: null,
- reset: function() {
- clearTimeout(this.timeoutObj);
- clearTimeout(this.serverTimeoutObj);
- return this;
- },
- start: function(SocketTask) {
- this.timeoutObj = setTimeout(() => {
- SocketTask.send({ data: 'ping' });
- this.serverTimeoutObj = setTimeout(() => {
- SocketTask.close();
- }, this.timeout);
- }, this.timeout);
- }
- };
消息监听
- initEventHandle(SocketTask) {
- // 监听消息
- SocketTask.onMessage(res => {
- // 心跳检测,后台返回pong表示连接正常
- if (res.data === 'pong') {
- SocketTask.heartCheck.reset().start(SocketTask);
- } else {
- this.toAudioText = res.data;
- // 接收到文字消息转语音
- this.getMp3();
- }
- });
- SocketTask.onOpen(() => {
- console.log(`${SocketTask.id} WebSocket onOpen`);
- SocketTask.heartCheck.reset().start(SocketTask);
- SocketTask.heartCheck.isAlive = true;
- });
- SocketTask.onError(res => {
- console.log(`${SocketTask.id} WebSocket onError`, res);
- this.reconnect(SocketTask);
- });
- SocketTask.onClose(res => {
- console.log(`${SocketTask.id} WebSocket onClose`, res);
- SocketTask.heartCheck.isAlive = false;
- if (res.code === 4000 || res.code === 4001) {
- console.log(`${SocketTask.id} WebSocket ${res.code}关闭`);
- } else {
- // 非正常关闭 重连
- this.reconnect(SocketTask);
- }
- });
- }
重连机制
- let reconnectObj = {
- timeout: 5000, // 重连间隔时间
- timer: null, // 重连计时器
- lock: false, // 重连锁
- limit: 0 // 重连最多次数
- };
-
- ...
- reconnect(SocketTask) {
- if (SocketTask.reconnectObj.lock) return;
- SocketTask.reconnectObj.lock = true;
- clearTimeout(SocketTask.reconnectObj.timer);
- if (SocketTask.reconnectObj.limit < 24) {
- SocketTask.reconnectObj.timer = setTimeout(() => {
- this.linkSocket();
- SocketTask.reconnectObj.lock = false;
- }, SocketTask.reconnectObj.timeout);
-
- SocketTask.reconnectObj.limit++;
- } else {
- console.log(`${SocketTask.id}WebSocket reach limit 24!!!!!!!`);
- uni.showToast({
- title: '很抱歉,您与服务器失去连接,请重启小程序',
- icon: 'none'
- });
- }
- }
2.使用零七生活API完成文字转语音,api返回二进制音频流,直接在小程序无法播放,需要使用writeFile写入文件后可播放,注意播放完成后使用fs.unlink删除本地文件
- const fs = uni.getFileSystemManager(); // 文件管理器API
- ...
- getMp3() {
- const target = `${wx.env.USER_DATA_PATH}/${new Date().getTime()}.mp3`;
- return new Promise((resolve, reject) => {
- uni.request({
- url: `https://api.oick.cn/txt/apiz.php?text=${this.toAudioText}&spd=5`,
- method: 'GET',
- responseType: 'arraybuffer', // 注意此处,否则返回音频格式不可用
- success: (res: any) => {
- fs.writeFile({
- filePath: target,
- data: res.data,
- encoding: 'binary',
- success: res => {
- // 将接收到的消息推进消息队列
- this.playAudio.push(target);
- },
- fail: err => {
- console.log(err);
- }
- });
- },
- fail: (err: any) => {
- reject(err);
- }
- });
- });
- }
3. 播放背景音频:注意需要在 app.json 中配置 requiredBackgroundModes
属性
为了实现锁屏也可接收消息,进入页面后开始循环播放空音频,保证音频播放不中断,接收到消息后播放消息队列;
- const backgroundAudioManager = uni.getBackgroundAudioManager();
- ...
- onShow() {
- //循环播放音频
- this.playInit();
- }
- playInit() {
- backgroundAudioManager.title = '语音播报';
- backgroundAudioManager.src = kong;
- backgroundAudioManager.onEnded(() => {
- if (this.index < this.playAudio.length) {
- console.log('播放列表:', this.playAudio);
- backgroundAudioManager.src = this.playAudio[this.index];
- // backgroundAudioManager.src = ding;
- this.index++;
- } else {
- backgroundAudioManager.src = kong;
- // 清空播放列表
- if (this.index > 0) {
- this.clearFile();
- this.playAudio = [];
- this.index = 0;
- }
- }
- });
- }
4.解决并发问题,定义playAudio数组用于存放音频队列,当接收多个消息时,将消息推入数组,当播放完一个音频后,检测数组是否为空,如果不为空,播放消息音频,否则继续播放空音频。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。