最终思路

思路就是vue前端向后台发送需要播放的语音信息(文字),然后后台返回语音流数据,通过URL.createObjectURL(data) 这个API生成一个URL,然后给audio标签附上url,网页进行语音播放,在网页播放语音就可以避免用户的本地语音库的安装。

在Vue项目中用Audio实现语音的播放(基础版)

1.axios 拦截处理
// respone拦截器
service.interceptors.response.use(response => {const headers = response.headersif (headers['content-type'] === 'application/octet-stream;charset=UTF-8') {return response.data}}
)2.接口请求
/*** 文字转语音接口*/
export function textToAudio(text) {let jsonData = {text: text,}return request({url: '/api/audio/text_to_audio',method: 'post',data: Qs.stringify(jsonData),responseType: "blob"//后台返回的为语音的流数据})
}
3.请求后台接口
//调用后台
getAudio(text) {textToAudio(text).then(response => {let url = URL.createObjectURL(response);//通过这个API让语音数据转为成一个url地址let audio = new Audio();//在VUE中使用audio标签audio.src = url;//设置audio的src为上面生成的urllet playPromiser = audio.play();//进行播放//在谷歌内核中,audio.play()会返回一个promise的值,在IE内核中就不会返回任何的值//所以如果你要分浏览器,可以判断playPromiser的值来进行操作哦audio.onended = () => {//onended可以检测语音是否播完//dosometin};}).catch(err => {});
},

4.springboot

@ApiOperation(value = "文字转语音", notes = "文字转语音")
@RequestMapping(value = "text_to_audio")
public void textToAudio(String text, HttpServletRequest request , HttpServletResponse response) throws IOException {if (StringUtils.isNotBlank(text)) {//过滤图片,h5标签text = text.replaceAll("\\&[a-zA-Z]{1,10};", "").replaceAll("<[^>]*>", "").replaceAll("[(/>)<]", "").trim();//调用微服务接口获取音频base64String result = "";try {JSONObject json = new JSONObject();JSONObject params = new JSONObject();params.put("content", text);json.put("params", params);String resultStr = HttpClientUtil.postJson(TEXT_TO_ADUIO, json.toString());JSONObject resultJson = JSON.parseObject(resultStr);System.out.println(resultJson.toJSONString());boolean success = resultJson.getInteger("result") == 0;if (!success) {throw new ExternalCallException(resultJson.getString("message"));}result = resultJson.getJSONArray("datas").getJSONObject(0).getString("audioBase64");} catch (Exception e) {log.error("【文字转语音接口调用异常】", e);
//                throw new ExternalCallException(e.getMessage());}//音频数据byte[] audioByte = Base64.getDecoder().decode(result);response.setContentType("application/octet-stream;charset=UTF-8");OutputStream os = new BufferedOutputStream(response.getOutputStream());SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS");String date = sdf.format(new Date());try {//音频流os.write(audioByte);} catch (IOException e) {e.printStackTrace();} finally {if (os != null) {os.flush();os.close();}}}
}

防止因为快速的请求语音数据造成语音播放叠在一起

 
data() {return {audio:true,callmsg:[],}
}
//排队队列   data 是文本信息
queue(data){this.callmsg.push(data);//this.callmsg就是排队队列,点击一次,放进去一个需要播放的信息if (this.audio) {//如果没人this.audio = false;//改为有人排队了this.getAudio();//进行播放操作}},
//语音播放
getAudio() {if (this.callmsg.length > 0) {//如果队列是有人在排队的,这进行播放操作textToAudio(this.callmsg[0]).then(response => {let url = URL.createObjectURL(response);//通过这个API让语音数据转为成一个url地址let audio = new Audio();//在VUE中使用audio标签audio.src = url;//设置audio的src为上面生成的urllet playPromiser = audio.play();//进行播放//在这里我用一个标志,设置语音开始播放/* localStorage.setItem("audio", "1");*///在谷歌内核中,audio.play()会返回一个promise的值,在IE内核中就不会返回任何的值//所以如果你要分浏览器,可以判断playPromiser的值来进行操作哦audio.onended = () => {//onended可以检测语音是否播完//dosometingthis.callmsg.splice(0, 1);//队列的第一个播放完毕,所以删除/* localStorage.setItem("audio", "0");//这里是语音播放完毕*/this.getAudio();//进行下一个请求并播放};}).catch(err => {});} else {//this.audio是一个data数据,用来记录是否有人排队this.audio = true; //如果队列没人排队,就告诉外面已经读完啦}
},

最终实现前端功能代码

<!--语音播放-->
<template><div class="audio"><div><svg-icon v-if="audioPlayVisible" icon-class="play" @click.native="pause":class="{'audio-play-style':true, 'audio-play-style-pc': pc}"/><svg-icon v-if="!audioPlayVisible" icon-class="stop_play" @click.native="play":class="{'audio-play-style':true, 'audio-play-style-pc': pc}"/></div></div>
</template>
<script>import {textToAudio} from '@/api/file'import {isPc} from '@/utils/common'export default {name: "audioPlay",props: {},components: {},mounted() {this.audioObj = new Audio();//在VUE中使用audio标签},created() {},data() {return {//语音播放开关audioPlayVisible: true,mAudioVisible: true,// 是否是PC端pc: isPc(),audioObj:null}},methods: {//暂停pause() {this.audioObj.pause();this.audioPlayVisible=false;},//播放play(){this.audioPlayVisible=true;},//调用后台getAudio(text) {if(!this.audioPlayVisible){return}textToAudio(text).then(response => {console.log('response', response)let url = URL.createObjectURL(response);//通过这个API让语音数据转为成一个url地址this.audioObj.src = url;//设置audio的src为上面生成的urllet playPromiser = this.audioObj.play();//进行播放//在谷歌内核中,audio.play()会返回一个promise的值,在IE内核中就不会返回任何的值//所以如果你要分浏览器,可以判断playPromiser的值来进行操作哦this.audioObj.onended = () => {};}).catch(err => {});},}}
</script><style lang="less">.audio {.audio-play-style {position: absolute;top: 10px;right: 0;font-size: 26px;}.audio-play-style-pc {top: 65px;}}
</style>
// 音频方式二 ----- 初始化
audioInit() {let AudioContext = window.AudioContext || window.webkitAudioContextif (AudioContext) {this.audioContext = new AudioContext()this.audioContext.resume()}
},/***  AudioContext 播放方式** @param response  后台返回音频流*/
playAudioMethodTwo(response) {var _this=this;//将Blob音频流转换成 ArrayBuffervar reader = new FileReader();reader.readAsArrayBuffer(response);reader.onload = function (e) {let arrayBuffer=reader.result;_this.audioContext.decodeAudioData(arrayBuffer).then(function (buffer) {var source = _this.audioContext.createBufferSource();source.buffer = buffer;source.connect(_this.audioContext.destination);source.start();}, function (e) {console.log("FAIL:" + arrayBuffer);});}
},
科大讯飞java 流demo 接口

注意事项:记得导入相关依赖包,hutool 直接maven库搜索

package com.ylz.springboot.modules.external.service.impl;

import com.google.common.collect.Lists;

import com.google.gson.Gson;
import com.google.gson.JsonObject;
import okhttp3.*;
import okio.ByteString;
import org.springframework.data.redis.util.ByteUtils;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.nio.charset.Charset;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * 科大讯飞语音合成
 *
 * @author lhh
 * @Date 2020/5/7 11:06
 */
public class WebTTSWS {
    private static final String hostUrl = "https://tts-api.xfyun.cn/v2/tts"; //http url 不支持解析 ws/wss schema
    private static final String appid = "xxxx";//到控制台-语音合成页面获取
    private static final String apiSecret = "xxxxxx";//到控制台-语音合成页面获取
    private static final String apiKey = "xxxx";//到控制台-语音合成页面获取
    private static final String text = "蜡烛有心,杨柳有心,于是它能低首沉思";
    public static String base64 = "";
    public static final Gson json = new Gson();
    private volatile boolean lock = true;

    public static void main(String[] args) throws Exception {
        for (int i = 0; i < 1; i++) {
            new Thread(() -> {
                WebTTSWS w = new WebTTSWS();
                try {
                    String send = w.send();
                    System.out.println(send);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }

    public String send() throws Exception {
        lock = true;
        base64 = "";
        // 构建鉴权url
        String authUrl = getAuthUrl(hostUrl, apiKey, apiSecret);
        OkHttpClient client = new OkHttpClient.Builder().build();
        //将url中的 schema http://和https://分别替换为ws:// 和 wss://
        String url = authUrl.toString().replace("http://", "ws://").replace("https://", "wss://");
        Request request = new Request.Builder().url(url).build();

        List<byte[]> list = Lists.newArrayList();
        WebSocket webSocket = client.newWebSocket(request, new WebSocketListener() {
            @Override
            public void onOpen(WebSocket webSocket, Response response) {
                super.onOpen(webSocket, response);
                try {
                    System.out.println(response.body().string());
                } catch (IOException e) {
                    e.printStackTrace();
                }
                //发送数据
                JsonObject frame = new JsonObject();
                JsonObject business = new JsonObject();
                JsonObject common = new JsonObject();
                JsonObject data = new JsonObject();
                // 填充common
                common.addProperty("app_id", appid);
                //填充business
                business.addProperty("aue", "lame");
                business.addProperty("sfl", 1);
                business.addProperty("tte", "UTF8");//小语种必须使用UNICODE编码
                business.addProperty("vcn", "aisxping");//到控制台-我的应用-语音合成-添加试用或购买发音人,添加后即显示该发音人参数值,若试用未添加的发音人会报错11200
                business.addProperty("pitch", 50);
                business.addProperty("speed", 50);
                //填充data
                data.addProperty("status", 2);//固定位2
                try {
                    data.addProperty("text", Base64.getEncoder().encodeToString(text.getBytes("utf8")));
                    //使用小语种须使用下面的代码,此处的unicode指的是 utf16小端的编码方式,即"UTF-16LE"”
                    //data.addProperty("text", Base64.getEncoder().encodeToString(text.getBytes("UTF-16LE")));
                } catch (UnsupportedEncodingException e) {
                    e.printStackTrace();
                }
                //填充frame
                frame.add("common", common);
                frame.add("business", business);
                frame.add("data", data);
                webSocket.send(frame.toString());
            }

            @Override
            public void onMessage(WebSocket webSocket, String text) {
                super.onMessage(webSocket, text);
                //处理返回数据
                System.out.println("receive=>" + text);
                ResponseData resp = null;
                try {
                    resp = json.fromJson(text, ResponseData.class);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                if (resp != null) {
                    if (resp.getCode() != 0) {
                        System.out.println("error=>" + resp.getMessage() + " sid=" + resp.getSid());
                        return;
                    }
                    if (resp.getData() != null) {
                        String result = resp.getData().audio;
                        byte[] audio = Base64.getDecoder().decode(result);
                        list.add(audio);
                        // todo  resp.data.status ==2 说明数据全部返回完毕,可以关闭连接,释放资源
                        if (resp.getData().status == 2) {
                            String is = base64Concat(list);
                            base64 = is;
                            lock = false;
                            webSocket.close(1000, "");
                        }
                    }
                }
            }

            @Override
            public void onMessage(WebSocket webSocket, ByteString bytes) {
                super.onMessage(webSocket, bytes);
            }

            @Override
            public void onClosing(WebSocket webSocket, int code, String reason) {
                super.onClosing(webSocket, code, reason);
                System.out.println("socket closing");
            }

            @Override
            public void onClosed(WebSocket webSocket, int code, String reason) {
                super.onClosed(webSocket, code, reason);
                System.out.println("socket closed");
            }

            @Override
            public void onFailure(WebSocket webSocket, Throwable t, Response response) {
                super.onFailure(webSocket, t, response);
                System.out.println("connection failed" + response.message());
            }
        });

        while (lock) {
        }
        return base64;
    }

    /**
     * base64拼接
     */
    String base64Concat(List<byte[]> list) {
        int length = 0;
        for (byte[] b : list) {
            length += b.length;
        }
        byte[] retByte = new byte[length];
        for (byte[] b : list) {
            retByte = ByteUtils.concat(retByte, b);
        }
        return cn.hutool.core.codec.Base64.encode(retByte);
    }

    /**
     * 获取权限地址
     *
     * @param hostUrl
     * @param apiKey
     * @param apiSecret
     * @return
     */
    public static String getAuthUrl(String hostUrl, String apiKey, String apiSecret) throws Exception {
        URL url = new URL(hostUrl);
        SimpleDateFormat format = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
        format.setTimeZone(TimeZone.getTimeZone("GMT"));
        String date = format.format(new Date());
        StringBuilder builder = new StringBuilder("host: ").append(url.getHost()).append("\n").
                append("date: ").append(date).append("\n").
                append("GET ").append(url.getPath()).append(" HTTP/1.1");
        Charset charset = Charset.forName("UTF-8");
        Mac mac = Mac.getInstance("hmacsha256");
        SecretKeySpec spec = new SecretKeySpec(apiSecret.getBytes(charset), "hmacsha256");
        mac.init(spec);
        byte[] hexDigits = mac.doFinal(builder.toString().getBytes(charset));
        String sha = Base64.getEncoder().encodeToString(hexDigits);
        String authorization = String.format("hmac username=\"%s\", algorithm=\"%s\", headers=\"%s\", signature=\"%s\"", apiKey, "hmac-sha256", "host date request-line", sha);
        HttpUrl httpUrl = HttpUrl.parse("https://" + url.getHost() + url.getPath()).newBuilder().
                addQueryParameter("authorization", Base64.getEncoder().encodeToString(authorization.getBytes(charset))).
                addQueryParameter("date", date).
                addQueryParameter("host", url.getHost()).
                build();
        return httpUrl.toString();
    }

    public static class ResponseData {
        private int code;
        private String message;
        private String sid;
        private Data data;

        public int getCode() {
            return code;
        }

        public String getMessage() {
            return this.message;
        }

        public String getSid() {
            return sid;
        }

        public Data getData() {
            return data;
        }
    }

    public static class Data {
        //标志音频是否返回结束  status=1,表示后续还有音频返回,status=2表示所有的音频已经返回
        private int status;
        //返回的音频,base64 编码
        private String audio;
        // 合成进度
        private String ced;
    }

}

Vue+SpringBoot+Audio+科大讯飞 语音合成技术相关推荐

  1. 技术解读 | 科大讯飞语音技术最新进展之二:语音识别与语音合成

    这一篇内容将围绕语音识别与合成方向,继续为大家带来相关技术解析. "风物长宜放眼量".面向人机交互更加自然流畅的未来,智能语音技术进展如何?该走向何方? 以下内容根据讯飞研究院杰出 ...

  2. Java项目:仿小米商城系统(前后端分离+java+vue+Springboot+ssm+mysql+maven+redis)

    源码获取:博客首页 "资源" 里下载! 一.项目简述 本系统功能包括: 基于vue + Springboot前后端分离项目精简版仿小米商城 系统,注册登录,首页展示,商品展示,商品 ...

  3. V部落博客管理平台开源啦! Vue+SpringBoot强强联合! 1

    V部落是一个多用户博客管理平台,采用Vue+SpringBoot开发. 演示地址: http://45.77.146.32:8081/index.html 项目地址:https://github.co ...

  4. 仅用 1/4 数据量还原真人语音100%细节,火山语音上新超自然对话语音合成技术...

    数星星盼月亮,万千杰迷苦等6年,不久之前终于等到周董发新专辑啦!一经上线引爆全网讨论,就像这样: 正当大家沉浸在对那时青葱岁月的美好追忆时,发来上述这段音频的小伙伴表示:这段对话居然是语音合成的!提到 ...

  5. 仅用1/4数据量还原真人语音100%细节 火山语音上新超自然对话语音合成技术

    ‍数据智能产业创新服务媒体 --聚焦数智 · 改变商业 如今,数字化转型成为了抓住新一轮科技革命和产业变革浪潮的关键.无论是中国还是全球其他经济体,都将破局点聚焦于数字化转型.能否成功实现全面的数字化 ...

  6. B站云E办Vue+SpringBoot前后端分离项目——MVC三层架构搭建后台项目

    本项目来源B站云E办,笔记整理了项目搭建的过程和涉及的知识点.对于学习来说,不是复制粘贴代码即可,要知其然知其所以然.希望我的笔记能为大家提供思路,也欢迎各位伙伴的指正. 项目前端学习笔记目录 B站云 ...

  7. 一定要小心AI语音合成技术,我妈就被骗了!

    语音合成技术现在已经非常成熟了.例如高德导航里的各种明星语音包,林志玲为您导航祝您好心情.郭德纲前方有落石车碎人心碎.你镇定一下罗永浩要开始导航了,基本上能够达到以假乱真的效果.大部分时候用户也分不出 ...

  8. 【024】Vue+Springboot+mysql员工考勤管理系统(多角色登录、请假、打卡)(含源码、数据库、运行教程、实验报告)

    前排提示:项目源码已放在文末 基于Vue+Springboot+mysql员工考勤管理系统(多角色登录.请假.打卡) 开发环境:Springboot+Mysql+Vue+Nodejs+Maven+JD ...

  9. Vue+SpringBoot图书管理系统前后端分离(教你一步一步搭建)

    Vue+SpringBoot图书管理系统前后端分离(教你一步一步搭建) 介绍: 说明: 环境搭建 后端环境搭建 1.新建一个工程(只有收费版的idea才有这个选项哦) 2.选择Java8 4.配置工程 ...

最新文章

  1. case when 子查询_Oracle数据库-单表查询
  2. LabVIEW目标对象分类识别(理论篇—5)
  3. C面向对象之透明指针的运用
  4. java mybatis向mysql数据库插入中文出现乱码
  5. SSL/TLS 协议简介与实例分析
  6. C语言复杂的学生成绩管理系统,哭诉、拜求C语言学生成绩管理系统
  7. 中国科学院计算机网络信息中心科学数据中心,中科院计算机网络信息中心发布系列可信共享科学数据公共服务...
  8. 面试题37:两个链表的第一个公共结点
  9. 《白帽子将Web安全》摘抄
  10. 1SE rule details in CCP pruning of CART
  11. MySQL 如何复制表
  12. windows 开启telnet 功能
  13. 守护线程和非守护线程
  14. 怎样才能打开Tuxera NTFS的主界面?
  15. C#读写日志文本文件
  16. 坚果Pro2刷入twrp rec
  17. 网站建设对企业的好处有哪些?
  18. ESP8266作为无线串口设置
  19. 模式识别学习笔记(8)——隐马尔可夫模型
  20. Gdevops广州站:主流数据库的选型、架构设计与迁移实战,一网打尽!

热门文章

  1. 出大事了!IBM的数仓项目黄了,赔了好几亿!
  2. Soso(嗖嗖)移动 java 项目
  3. 带有示例的Python datetime weekday()方法
  4. js:bind(this)这是什么写法
  5. C++和Rust_自从尝了 Rust,Java 突然不香了
  6. sql 授予其他用户权限语句
  7. 解决local variable 'has_fav_course' referenced before assignment(Python)
  8. AutoJS4.1.0实战教程---京东领京豆
  9. 用命令查询邮件服务器ip地址,如何利用nslookup命令查询mx记录?以及邮件相关记录...
  10. 名词解释:DNS,A记录,子域名,CNAME别名,PTR,MX,TXT,SRV,TTL