赞
踩
思路就是vue前端向后台发送需要播放的语音信息(文字),然后后台返回语音流数据,通过URL.createObjectURL(data) 这个API生成一个URL,然后给audio标签附上url,网页进行语音播放,在网页播放语音就可以避免用户的本地语音库的安装。
1.axios 拦截处理 // respone拦截器 service.interceptors.response.use( response => { const headers = response.headers if (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为上面生成的url let 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(); //调用微服务接口获取音频base64 String 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为上面生成的url let playPromiser = audio.play();//进行播放 //在这里我用一个标志,设置语音开始播放 /* localStorage.setItem("audio", "1");*/ //在谷歌内核中,audio.play()会返回一个promise的值,在IE内核中就不会返回任何的值 //所以如果你要分浏览器,可以判断playPromiser的值来进行操作哦 audio.onended = () => { //onended可以检测语音是否播完 //dosometing this.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为上面生成的url
- let 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.webkitAudioContext if (AudioContext) { this.audioContext = new AudioContext() this.audioContext.resume() } }, /** * AudioContext 播放方式 * * @param response 后台返回音频流 */ playAudioMethodTwo(response) { var _this=this; //将Blob音频流转换成 ArrayBuffer var 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;
}
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。