uniapp安卓端实现语音合成播报

最初尝试使用讯飞语音合成方式,能获取到语音数据,但是数据是base64格式的,在安卓端无法播放,网上有说通过转成blob格式的url可以播放,但是uniapp不支持转换的api;于是后面又想其他办法,使用安卓插件播报原生安卓语音播报插件 - DCloud 插件市场

方案一(讯飞语音合成)

1.在讯飞后台注册登录获得APPID等信息  讯飞语音合成控制台

2.在项目根目录使用npm安装crypto-js

npm i crypto-js

3.新建xunfei.js文件,替换讯飞的APPID等3个配置

// 讯飞语音合成api文档	https://www.xfyun.cn/doc/tts/online_tts/API.html
import CryptoJS from 'crypto-js'
import {Base64} from './base64.js';

const APPID = "替换自己的APPID";
const API_SECRET = "替换自己的API_SECRET";
const API_KEY = "替换自己的API_KEY";

const URL = "wss://tts-api.xfyun.cn/v2/tts"
const HOST = "tts-api.xfyun.cn"

function getWssUrl(date) {
	date = date||(new Date().toGMTString())
	let signatureOrigin = `host: ${HOST}\ndate: ${date}\nGET /v2/tts HTTP/1.1`
	let signatureSha = CryptoJS.HmacSHA256(signatureOrigin, API_SECRET)
	let signature = CryptoJS.enc.Base64.stringify(signatureSha)
	let authorizationOrigin = `api_key="${API_KEY}", algorithm="hmac-sha256", headers="host date request-line", signature="${signature}"`
	let authStr = CryptoJS.enc.Base64.stringify(CryptoJS.enc.Utf8.parse(authorizationOrigin))
	return URL + "?authorization=" + authStr + "&date=" + date + "&host=" + HOST
	
}
const wssUrl=getWssUrl()
// console.log("讯飞语音合成wssUrl",wssUrl)

export const speak = (word) => {
	if (!word) {return}
	const socketTask=uni.connectSocket({
		url: wssUrl,
		success: res=> {
			console.log("讯飞websocket连接成功",res);
		},
		fail: err=> {
			console.log("讯飞websocket连接失败",err);
		},
	});

	//连接建立完毕,读取数据识别
	let buffs = ""

	socketTask.onOpen(data => {
		let params = {
			"common": {
				"app_id": APPID
			},
			"business": {
				// aue: "raw",
				aue: "lame",//mp3 (当aue=lame时需传参sfl=1)
				sfl: 1,//开启流式返回mp3格式音频
				
				auf: "audio/L16;rate=16000",
				vcn: "xiaoyan",//aisjiuxu	aisxping aisjinger aisbabyxu
				tte: "UTF8",
				speed:60,//默认50,可选0-100
				volume:80,//默认50,可选0-100
			},
			"data": {
				"text": Base64.encode(word),
				"status": 2
			}
		}
		socketTask.send({
			data:JSON.stringify(params),
			success: res=> {
				console.log("讯飞websocket发送成功",res);
			},
			fail: err=> {
				console.log("讯飞websocket发送失败",err);
			},
		})

		socketTask.onMessage(res => {
			let ds = JSON.parse(res.data)
			console.log("接收到websocket消息:",ds)
			buffs+=ds.data.audio//返回的是base64数据
			if (ds.code === 0 && ds.data.status === 2) { //status为2表示合成完成
				console.log("音频合成完成");
				toPlay();
				socketTask.close();
			}
		})
	})
	socketTask.onError(err=>{
		console.log("讯飞websocket发生错误",err);
	})

	function toPlay() {
		const base64data='data:audio/mp3;base64,' + buffs
		let audioContext = uni.createInnerAudioContext();
		audioContext.autoplay = true;
		audioContext.src = base64data
		
		audioContext.play()
		audioContext.onEnded(() => {
			console.log("播放完成");
			audioContext.destroy()
			audioContext=null
		})
		audioContext.onCanplay(() => {
			console.log("可以播放音频了");
		})
		audioContext.onPlay(() => {
		  console.log('开始播放');
		});
		audioContext.onError((res) => {
		  console.log("播放失败",res);
		});
	}
	
}

上面引入的base64.js,也可以自己npm安装base64插件

export const Base64 = {
    keyStr: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=',
    encode(input) {
        let output = '';
        let chr1, chr2, chr3, enc1, enc2, enc3, enc4;
        let i = 0;
        input = Base64.utf8Encode(input);
        while (i < input.length) {
            chr1 = input.charCodeAt(i++);
            chr2 = input.charCodeAt(i++);
            chr3 = input.charCodeAt(i++);
            enc1 = chr1 >> 2;
            enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
            enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
            enc4 = chr3 & 63;
            if (isNaN(chr2)) {
                enc3 = enc4 = 64;
            } else if (isNaN(chr3)) {
                enc4 = 64;
            }
            output = output +
                Base64.keyStr.charAt(enc1) + Base64.keyStr.charAt(enc2) +
                Base64.keyStr.charAt(enc3) + Base64.keyStr.charAt(enc4);
        }
        return output;
    },
    decode(input) {
        let output = '';
        let chr1, chr2, chr3;
        let enc1, enc2, enc3, enc4;
        let i = 0;
        input = input.replace(/[^A-Za-z0-9+/=]/g, '');
        while (i < input.length) {
            enc1 = Base64.keyStr.indexOf(input.charAt(i++));
            enc2 = Base64.keyStr.indexOf(input.charAt(i++));
            enc3 = Base64.keyStr.indexOf(input.charAt(i++));
            enc4 = Base64.keyStr.indexOf(input.charAt(i++));
            chr1 = (enc1 << 2) | (enc2 >> 4);
            chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
            chr3 = ((enc3 & 3) << 6) | enc4;
            output = output + String.fromCharCode(chr1);
            if (enc3 !== 64) {
                output = output + String.fromCharCode(chr2);
            }
            if (enc4 !== 64) {
                output = output + String.fromCharCode(chr3);
            }
        }
        output = Base64.utf8Decode(output);
        return output;
    },
    utf8Encode(string) {
        string = string.replace(/\r\n/g, '\n');
        let utfString = '';
        for (let i = 0; i < string.length; i++) {
            let c = string.charCodeAt(i);
            if (c < 128) {
                utfString += String.fromCharCode(c);
            } else if ((c > 127) && (c < 2048)) {
                utfString += String.fromCharCode((c >> 6) | 192);
                utfString += String.fromCharCode((c & 63) | 128);
            } else {
                utfString += String.fromCharCode((c >> 12) | 224);
                utfString += String.fromCharCode(((c >> 6) & 63) | 128);
                utfString += String.fromCharCode((c & 63) | 128);
            }
        }
        return utfString;
    },
    utf8Decode(utfString) {
        let string = '';
        let i = 0;
        let c = 0;
        let c2 = 0;
        let c3 = 0;
        while (i < utfString.length) {
            c = utfString.charCodeAt(i);
            if (c < 128) {
                string += String.fromCharCode(c);
                i++;
            } else if ((c > 191) && (c < 224)) {
                c2 = utfString.charCodeAt(i + 1);
                string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
                i += 2;
            } else {
                c2 = utfString.charCodeAt(i + 1);
                c3 = utfString.charCodeAt(i + 2);
                string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
                i += 3;
            }
        }
        return string;
    }
};

4.在main.js中引入

import {speak} from './xunfei.js' 
Vue.prototype.$speak=speak

//后续直接在需要的地方通过this.$speak('需要播报的文字')

PS:该方式在H5端播放短文本语句可以正常播放,长文本的播放不了,可以尝试分段播放,将websocket收到的数据push到一个数组中,然后一段一段播放(会有卡顿现象);在安卓端直接播放不了;大家有需要的可以尝试将base64转为blob,再用URL.createObjectURL()转换成临时url地址,或者服务端转成mp3数据

方案二(原生安卓语音播报插件) 推荐此方式,没有播报条数限制,讯飞每天有500条免费额度

在插件市场购买(0元)云打包插件 插件地址 然后按下图步骤选择该插件,然后重新制作自定义调试基座

然后在main.js中导入插件

// #ifdef APP
// 原生安卓语音播报插件,手机端可用,PDA需要安装tts语音引擎
let androidTTSPlugin = uni.requireNativePlugin('Tellsea-AndroidTTSPlugin');
androidTTSPlugin.init((e) => {
	let res = JSON.parse(e);
	if (res.code == 200) {
		console.log("初始化成功",res.msg);
	} else {
		console.log("初始化失败",res.msg);
	}
});
androidTTSPlugin.testPlugin('测试插件', (e) => {
	let res = JSON.parse(e);
	if (res.code == 200) {
		console.log('测试成功');
	} else {
		console.log("测试失败",res.msg);
	}
});
Vue.prototype.$speak=text=>{
	androidTTSPlugin.textToSpeech(text, (e) => {
        let res = JSON.parse(e);
        if (res.code == 200) {
            console.log("播报成功",res.msg);
        } else {
            console.log("播报失败",res.msg);
        }
    });
}
// #endif

后面在页面中使用语音播报api

this.$speak("上班了,打卡成功")

PS:如果设备播报没有声音需要查看设备是否安装了TTS语音引擎,没有的话可以安装微软的TTS语音引擎,安装完成之后,在 设置->文字转语音输出->首选引擎 将安装的TTS设置为首选引擎,然后打开TTS引擎 配置电池优化为允许后台持续运行

下载地址:下载地址

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

分享一个Typecho博客系统专用的CloudFlare缓存规则,优化加速一下下!

好久都没有更新Typecho博客了,最近几天明月测试了一套适用于Typecho博客系统的CloudFlare缓存规则,经过近一周时间的测试确定有效,并且加速效果特别突出,今天就无偿分享给大家,也算是为国内Typecho生态添砖加瓦了吧! 总结下来,这套CloudFlare缓存规则带来的好处就是可以…

华为机试HJ108求最小公倍数

华为机试HJ108求最小公倍数 题目&#xff1a; 想法&#xff1a; 要找到输入的两个数的最小公倍数&#xff0c;这个最小公倍数要大于等于其中最大的那个数值&#xff0c;遍历最大的那个数值的倍数&#xff0c;最大的最小公倍数就是输入的两个数值的乘积 input_number_list i…

tableau饼图与环形图绘制 - 5

饼图与环形图绘制 1. 饼图绘制1.1 饼图绘制1.2 显示标签1.3 显示合计百分比1.4 设置百分比格式1.5 添加说明1.6 图像导出 2. 环形图绘制2.1 选择标记为饼图2.1 饼图绘制2.2 双饼图绘制2.3 设置大饼图大小2.4 设置双轴2.5 设置小饼图属性 1. 饼图绘制 酒店价格等级饼图 1.1 饼…

作业/数据结构/2024/7/8

链表的相关操作作业&#xff1a; 1】 按值修改 2】按值查找&#xff0c;返回当前节点的地址 &#xff08;先不考虑重复&#xff0c;如果有重复&#xff0c;返回第一个&#xff09; 3】 逆置(反转) 4】释放链表 main.c #include "head.h"int main(int argc, con…

品牌文化五大维度,构建品牌竞争力的秘诀!

品牌文化对于企业的发展和成功至关重要。 品牌文化不仅是企业和消费者之间的纽带&#xff0c;也是企业内部员工的凝聚力。 在当今竞争激烈的市场环境中&#xff0c;建立一个有活力和影响力的品牌文化是每个企业都需要认真思考和实践的事情。 品牌文化的五大维度包括价值观、…

VMware vSAN替换存储解决方案如何选择?

What is vSAN &#xff1f; 是一款软件定义的企业存储解决方案&#xff0c;支持超融合基础架构系统。vSAN与VMware vSphere 完全集成在一起&#xff0c;作为ESXi Hypervisor内的分布式软件层&#xff0c;通过整合、池化ESXi各个主机上的存储资源&#xff0c;为vSphere虚拟化平…

医疗器械网络安全| 常见安全漏洞与防护措施

医疗器械网络安全顾问​https://link.zhihu.com/?targethttps%3A//www.wanyun.cn/Support%3Fshare%3D24315_ea8a0e47-b38d-4cd6-8ed1-9e7711a8ad5e 一、常见安全漏洞 医疗器械软件在现代医疗体系中扮演着至关重要的角色&#xff0c;然而&#xff0c;随着技术的不断发展&…

(2)滑动窗口算法练习:无重复字符的最长子串

无重复字符的最长子串 题目链接&#xff1a;3. 无重复字符的最长子串 - 力扣&#xff08;LeetCode&#xff09; 给定一个字符串 s &#xff0c;请你找出其中不含有重复字符的最长子串的长度。 输入: s "abcabcbb" 输出: 3 解释: 因为无重复字符的最长子串是"a…

计算机网络知识汇总

目录 前言 概述 1、互联网的组成 2、端系统之间的两种通信方式 1、客户-服务器方式 2、对等连接方式&#xff08;P2P&#xff09; 3、交换技术 4、时延 5、利用率 6、协议 7、计算机网络体系结构 8、ISP 物理层 链路层 网络层 传输层 应用层 前言 最近准备找工作…

Linux 网络--TCP协议收包流程(NAPI机制)

Linux 网络--TCP协议收包流程&#xff08;NAPI机制&#xff09; 平台环境简介&#xff1a;宿主机: ubuntu18.04Linux内核源码版本: Linux-4.15网卡驱动: Intel e1000 &#xff08;ubuntu 虚拟机默认网卡驱动&#xff09;协议&#xff1a;TCP协议&#xff0c;本文分析收包过程 本…

Python编程学习笔记(3)--- 操作列表

1、遍历列表 遍历列表可以采用for循环的方法&#xff0c;需要对列表中的每一个元素都执行相同的操作。 具体事实如下&#xff1a; name ["ada","cdb","dbc","bad","jinb"] for Name in name:print(Name)运行结果&#x…

html H5 dialog弹窗学习,实现弹窗显示内容 替代confirm、alert

html H5 dialog弹窗学习,实现弹窗内容 替代confirm 框架使用的mui,使用mui.confirm() 弹窗内容过多时,弹窗被撑的到屏幕外去了,使用H5 dialog 标签自定义一个固定大小的弹窗,内容过多时可下拉显示 效果展示 隐私政策内容很多,可以下拉显示 代码 myDialog.css dialog{p…

tomcat 项目迁移,无法将项目作为服务service启动

背景 测试服务器需要迁移到正式服务器上&#xff0c;为了方便省事&#xff0c;将测试服务器上的一些文件直接复制到正式服务器 问题 使用startup启动项目之后&#xff0c;可以直接使用使用tomcat9w启动&#xff0c;或者作为服务service启动的时候&#xff0c;显示无法访问到资源…

STM32实战篇:按键控制LED

按键控制LED 功能要求 有两个按键&#xff0c;分别控制两个LED灯。当按键按下后&#xff0c;灯的亮暗状态改变。实物如下图所示&#xff1a; 由图可知&#xff0c;按键一端直接接地&#xff0c;故另一端所对应IO引脚的输入模式应该为上拉输入模式。 实现代码 #include "…

昇思25天学习打卡营第1天|小试牛刀

这里写自昇思25天学习打卡营第1天|小试牛刀定义目录标题 昇思25天学习打卡营第1天学习了初学入门之基本介绍。了解了昇思MindSpore和华为昇腾AI全栈。训练营中的教程丰富&#xff0c;有初学入门、应用实践和量子计算等。学习打卡营是很好的提升自己的机会。 昇腾计算&#xff…

电脑清理c盘内存空间怎么清理免费 怎么清理c盘的垃圾文件又不删除有用文件

在计算机使用过程中&#xff0c;随着时间的推移&#xff0c;C盘空间可能会被各种临时文件、缓存和无用的注册表项占用。这不仅会导致C盘空间不足&#xff0c;还可能影响计算机的性能。那么怎么样清理C盘内存空间&#xff0c;怎么样清理C盘的垃圾避开系统文件呢&#xff1f; 一…

JVM原理(二三):JVM虚拟机线程安全的实现方法

1. 互斥同步 互斥同步(MutualExclusion&Synchronization)是一种最常见也是最主要的并发正确性保障手段。同步是指在多个线程并发访问共享数据时&#xff0c;保证共享数据在同一个时刻只被一条(或者是一些&#xff0c;当使用信号量的时候)线程使用。而互斥是实现同步的一种…

Msfvenom制作自己的专属Shell

Msfvenom制作自己的专属Shell 如何通过Msfvenom来生成用户自己的专属Shell?有时候我们上传Shell到目标主机后&#xff0c;不仅我们自己可以连接&#xff0c;其他用户也可以连接&#xff0c;有时候会导致我们丢失该Shell&#xff0c;甚至该shell被用户发现并查杀。 实验环境 …

数据仓库哈哈

数据仓库 基本概念数据库&#xff08;database&#xff09;和数据仓库&#xff08;Data Warehouse&#xff09;的异同 整体架构分层架构方法论ER模型&#xff08;建模理论&#xff09;维度模型 何为分层第一层&#xff1a;数据源&#xff08;ODS ER模型&#xff09;设计要点日志…

C++进阶:继承和多态

文章目录 ❤️继承&#x1fa77;继承与友元&#x1f9e1;继承和静态成员&#x1f49b;菱形继承及菱形虚拟继承&#x1f49a;继承和组合 ❤️多态&#x1fa77;什么是多态&#xff1f;&#x1f9e1;多态的定义以及实现&#x1f49b;虚函数&#x1f49a;虚函数的重写&#x1f499…