mirror of
https://github.com/RYDE-WORK/Langchain-Chatchat.git
synced 2026-02-07 07:23:29 +08:00
merge dev_fastchat
This commit is contained in:
parent
18fe1b97f9
commit
90eb45ac46
@ -1,243 +0,0 @@
|
|||||||
/*
|
|
||||||
* base64.js
|
|
||||||
*
|
|
||||||
* Licensed under the BSD 3-Clause License.
|
|
||||||
* http://opensource.org/licenses/BSD-3-Clause
|
|
||||||
*
|
|
||||||
* References:
|
|
||||||
* http://en.wikipedia.org/wiki/Base64
|
|
||||||
*/
|
|
||||||
;(function (global, factory) {
|
|
||||||
typeof exports === 'object' && typeof module !== 'undefined'
|
|
||||||
? module.exports = factory(global)
|
|
||||||
: typeof define === 'function' && define.amd
|
|
||||||
? define(factory) : factory(global)
|
|
||||||
}((
|
|
||||||
typeof self !== 'undefined' ? self
|
|
||||||
: typeof window !== 'undefined' ? window
|
|
||||||
: typeof global !== 'undefined' ? global
|
|
||||||
: this
|
|
||||||
), function(global) {
|
|
||||||
'use strict';
|
|
||||||
// existing version for noConflict()
|
|
||||||
global = global || {};
|
|
||||||
var _Base64 = global.Base64;
|
|
||||||
var version = "2.5.1";
|
|
||||||
// if node.js and NOT React Native, we use Buffer
|
|
||||||
var buffer;
|
|
||||||
if (typeof module !== 'undefined' && module.exports) {
|
|
||||||
try {
|
|
||||||
buffer = eval("require('buffer').Buffer");
|
|
||||||
} catch (err) {
|
|
||||||
buffer = undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// constants
|
|
||||||
var b64chars
|
|
||||||
= 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
|
||||||
var b64tab = function(bin) {
|
|
||||||
var t = {};
|
|
||||||
for (var i = 0, l = bin.length; i < l; i++) t[bin.charAt(i)] = i;
|
|
||||||
return t;
|
|
||||||
}(b64chars);
|
|
||||||
var fromCharCode = String.fromCharCode;
|
|
||||||
// encoder stuff
|
|
||||||
var cb_utob = function(c) {
|
|
||||||
if (c.length < 2) {
|
|
||||||
var cc = c.charCodeAt(0);
|
|
||||||
return cc < 0x80 ? c
|
|
||||||
: cc < 0x800 ? (fromCharCode(0xc0 | (cc >>> 6))
|
|
||||||
+ fromCharCode(0x80 | (cc & 0x3f)))
|
|
||||||
: (fromCharCode(0xe0 | ((cc >>> 12) & 0x0f))
|
|
||||||
+ fromCharCode(0x80 | ((cc >>> 6) & 0x3f))
|
|
||||||
+ fromCharCode(0x80 | ( cc & 0x3f)));
|
|
||||||
} else {
|
|
||||||
var cc = 0x10000
|
|
||||||
+ (c.charCodeAt(0) - 0xD800) * 0x400
|
|
||||||
+ (c.charCodeAt(1) - 0xDC00);
|
|
||||||
return (fromCharCode(0xf0 | ((cc >>> 18) & 0x07))
|
|
||||||
+ fromCharCode(0x80 | ((cc >>> 12) & 0x3f))
|
|
||||||
+ fromCharCode(0x80 | ((cc >>> 6) & 0x3f))
|
|
||||||
+ fromCharCode(0x80 | ( cc & 0x3f)));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
var re_utob = /[\uD800-\uDBFF][\uDC00-\uDFFFF]|[^\x00-\x7F]/g;
|
|
||||||
var utob = function(u) {
|
|
||||||
return u.replace(re_utob, cb_utob);
|
|
||||||
};
|
|
||||||
var cb_encode = function(ccc) {
|
|
||||||
var padlen = [0, 2, 1][ccc.length % 3],
|
|
||||||
ord = ccc.charCodeAt(0) << 16
|
|
||||||
| ((ccc.length > 1 ? ccc.charCodeAt(1) : 0) << 8)
|
|
||||||
| ((ccc.length > 2 ? ccc.charCodeAt(2) : 0)),
|
|
||||||
chars = [
|
|
||||||
b64chars.charAt( ord >>> 18),
|
|
||||||
b64chars.charAt((ord >>> 12) & 63),
|
|
||||||
padlen >= 2 ? '=' : b64chars.charAt((ord >>> 6) & 63),
|
|
||||||
padlen >= 1 ? '=' : b64chars.charAt(ord & 63)
|
|
||||||
];
|
|
||||||
return chars.join('');
|
|
||||||
};
|
|
||||||
var btoa = global.btoa ? function(b) {
|
|
||||||
return global.btoa(b);
|
|
||||||
} : function(b) {
|
|
||||||
return b.replace(/[\s\S]{1,3}/g, cb_encode);
|
|
||||||
};
|
|
||||||
var _encode = buffer ?
|
|
||||||
buffer.from && Uint8Array && buffer.from !== Uint8Array.from
|
|
||||||
? function (u) {
|
|
||||||
return (u.constructor === buffer.constructor ? u : buffer.from(u))
|
|
||||||
.toString('base64')
|
|
||||||
}
|
|
||||||
: function (u) {
|
|
||||||
return (u.constructor === buffer.constructor ? u : new buffer(u))
|
|
||||||
.toString('base64')
|
|
||||||
}
|
|
||||||
: function (u) { return btoa(utob(u)) }
|
|
||||||
;
|
|
||||||
var encode = function(u, urisafe) {
|
|
||||||
return !urisafe
|
|
||||||
? _encode(String(u))
|
|
||||||
: _encode(String(u)).replace(/[+\/]/g, function(m0) {
|
|
||||||
return m0 == '+' ? '-' : '_';
|
|
||||||
}).replace(/=/g, '');
|
|
||||||
};
|
|
||||||
var encodeURI = function(u) { return encode(u, true) };
|
|
||||||
// decoder stuff
|
|
||||||
var re_btou = new RegExp([
|
|
||||||
'[\xC0-\xDF][\x80-\xBF]',
|
|
||||||
'[\xE0-\xEF][\x80-\xBF]{2}',
|
|
||||||
'[\xF0-\xF7][\x80-\xBF]{3}'
|
|
||||||
].join('|'), 'g');
|
|
||||||
var cb_btou = function(cccc) {
|
|
||||||
switch(cccc.length) {
|
|
||||||
case 4:
|
|
||||||
var cp = ((0x07 & cccc.charCodeAt(0)) << 18)
|
|
||||||
| ((0x3f & cccc.charCodeAt(1)) << 12)
|
|
||||||
| ((0x3f & cccc.charCodeAt(2)) << 6)
|
|
||||||
| (0x3f & cccc.charCodeAt(3)),
|
|
||||||
offset = cp - 0x10000;
|
|
||||||
return (fromCharCode((offset >>> 10) + 0xD800)
|
|
||||||
+ fromCharCode((offset & 0x3FF) + 0xDC00));
|
|
||||||
case 3:
|
|
||||||
return fromCharCode(
|
|
||||||
((0x0f & cccc.charCodeAt(0)) << 12)
|
|
||||||
| ((0x3f & cccc.charCodeAt(1)) << 6)
|
|
||||||
| (0x3f & cccc.charCodeAt(2))
|
|
||||||
);
|
|
||||||
default:
|
|
||||||
return fromCharCode(
|
|
||||||
((0x1f & cccc.charCodeAt(0)) << 6)
|
|
||||||
| (0x3f & cccc.charCodeAt(1))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
var btou = function(b) {
|
|
||||||
return b.replace(re_btou, cb_btou);
|
|
||||||
};
|
|
||||||
var cb_decode = function(cccc) {
|
|
||||||
var len = cccc.length,
|
|
||||||
padlen = len % 4,
|
|
||||||
n = (len > 0 ? b64tab[cccc.charAt(0)] << 18 : 0)
|
|
||||||
| (len > 1 ? b64tab[cccc.charAt(1)] << 12 : 0)
|
|
||||||
| (len > 2 ? b64tab[cccc.charAt(2)] << 6 : 0)
|
|
||||||
| (len > 3 ? b64tab[cccc.charAt(3)] : 0),
|
|
||||||
chars = [
|
|
||||||
fromCharCode( n >>> 16),
|
|
||||||
fromCharCode((n >>> 8) & 0xff),
|
|
||||||
fromCharCode( n & 0xff)
|
|
||||||
];
|
|
||||||
chars.length -= [0, 0, 2, 1][padlen];
|
|
||||||
return chars.join('');
|
|
||||||
};
|
|
||||||
var _atob = global.atob ? function(a) {
|
|
||||||
return global.atob(a);
|
|
||||||
} : function(a){
|
|
||||||
return a.replace(/\S{1,4}/g, cb_decode);
|
|
||||||
};
|
|
||||||
var atob = function(a) {
|
|
||||||
return _atob(String(a).replace(/[^A-Za-z0-9\+\/]/g, ''));
|
|
||||||
};
|
|
||||||
var _decode = buffer ?
|
|
||||||
buffer.from && Uint8Array && buffer.from !== Uint8Array.from
|
|
||||||
? function(a) {
|
|
||||||
return (a.constructor === buffer.constructor
|
|
||||||
? a : buffer.from(a, 'base64')).toString();
|
|
||||||
}
|
|
||||||
: function(a) {
|
|
||||||
return (a.constructor === buffer.constructor
|
|
||||||
? a : new buffer(a, 'base64')).toString();
|
|
||||||
}
|
|
||||||
: function(a) { return btou(_atob(a)) };
|
|
||||||
var decode = function(a){
|
|
||||||
return _decode(
|
|
||||||
String(a).replace(/[-_]/g, function(m0) { return m0 == '-' ? '+' : '/' })
|
|
||||||
.replace(/[^A-Za-z0-9\+\/]/g, '')
|
|
||||||
);
|
|
||||||
};
|
|
||||||
var noConflict = function() {
|
|
||||||
var Base64 = global.Base64;
|
|
||||||
global.Base64 = _Base64;
|
|
||||||
return Base64;
|
|
||||||
};
|
|
||||||
// export Base64
|
|
||||||
global.Base64 = {
|
|
||||||
VERSION: version,
|
|
||||||
atob: atob,
|
|
||||||
btoa: btoa,
|
|
||||||
fromBase64: decode,
|
|
||||||
toBase64: encode,
|
|
||||||
utob: utob,
|
|
||||||
encode: encode,
|
|
||||||
encodeURI: encodeURI,
|
|
||||||
btou: btou,
|
|
||||||
decode: decode,
|
|
||||||
noConflict: noConflict,
|
|
||||||
__buffer__: buffer
|
|
||||||
};
|
|
||||||
// if ES5 is available, make Base64.extendString() available
|
|
||||||
if (typeof Object.defineProperty === 'function') {
|
|
||||||
var noEnum = function(v){
|
|
||||||
return {value:v,enumerable:false,writable:true,configurable:true};
|
|
||||||
};
|
|
||||||
global.Base64.extendString = function () {
|
|
||||||
Object.defineProperty(
|
|
||||||
String.prototype, 'fromBase64', noEnum(function () {
|
|
||||||
return decode(this)
|
|
||||||
}));
|
|
||||||
Object.defineProperty(
|
|
||||||
String.prototype, 'toBase64', noEnum(function (urisafe) {
|
|
||||||
return encode(this, urisafe)
|
|
||||||
}));
|
|
||||||
Object.defineProperty(
|
|
||||||
String.prototype, 'toBase64URI', noEnum(function () {
|
|
||||||
return encode(this, true)
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
}
|
|
||||||
//
|
|
||||||
// export Base64 to the namespace
|
|
||||||
//
|
|
||||||
if (global['Meteor']) { // Meteor.js
|
|
||||||
Base64 = global.Base64;
|
|
||||||
}
|
|
||||||
// module.exports and AMD are mutually exclusive.
|
|
||||||
// module.exports has precedence.
|
|
||||||
if (typeof module !== 'undefined' && module.exports) {
|
|
||||||
module.exports.Base64 = global.Base64;
|
|
||||||
}
|
|
||||||
else if (typeof define === 'function' && define.amd) {
|
|
||||||
// AMD. Register as an anonymous module.
|
|
||||||
define([], function(){ return global.Base64 });
|
|
||||||
}
|
|
||||||
// that's it!
|
|
||||||
return {Base64: global.Base64}
|
|
||||||
}));
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//////////////////
|
|
||||||
// WEBPACK FOOTER
|
|
||||||
// ./node_modules/_js-base64@2.5.1@js-base64/base64.js
|
|
||||||
// module id = 57
|
|
||||||
// module chunks = 2
|
|
||||||
File diff suppressed because one or more lines are too long
@ -1,109 +0,0 @@
|
|||||||
/*
|
|
||||||
* @Autor: lycheng
|
|
||||||
* @Date: 2019-12-27 15:21:38
|
|
||||||
* @Description:
|
|
||||||
*/
|
|
||||||
function writeString(data, offset, str) {
|
|
||||||
for (var i = 0; i < str.length; i++) {
|
|
||||||
data.setUint8(offset + i, str.charCodeAt(i))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 加wav头
|
|
||||||
* @param {音频arrayBuffer} bytes
|
|
||||||
* @param {采样率} sampleRate
|
|
||||||
* @param {声道数} numChannels
|
|
||||||
* @param {sampleBits} oututSampleBits
|
|
||||||
* @param {小端字节} littleEdian
|
|
||||||
*/
|
|
||||||
function encodeWAV(
|
|
||||||
bytes,
|
|
||||||
sampleRate,
|
|
||||||
numChannels,
|
|
||||||
oututSampleBits,
|
|
||||||
littleEdian = true
|
|
||||||
) {
|
|
||||||
let sampleBits = oututSampleBits
|
|
||||||
let buffer = new ArrayBuffer(44 + bytes.byteLength)
|
|
||||||
let data = new DataView(buffer)
|
|
||||||
let channelCount = numChannels
|
|
||||||
let offset = 0
|
|
||||||
// 资源交换文件标识符
|
|
||||||
writeString(data, offset, 'RIFF')
|
|
||||||
offset += 4
|
|
||||||
// 下个地址开始到文件尾总字节数,即文件大小-8
|
|
||||||
data.setUint32(offset, 36 + bytes.byteLength, true)
|
|
||||||
offset += 4
|
|
||||||
// WAV文件标志
|
|
||||||
writeString(data, offset, 'WAVE')
|
|
||||||
offset += 4
|
|
||||||
// 波形格式标志
|
|
||||||
writeString(data, offset, 'fmt ')
|
|
||||||
offset += 4
|
|
||||||
// 过滤字节,一般为 0x10 = 16
|
|
||||||
data.setUint32(offset, 16, true)
|
|
||||||
offset += 4
|
|
||||||
// 格式类别 (PCM形式采样数据)
|
|
||||||
data.setUint16(offset, 1, true)
|
|
||||||
offset += 2
|
|
||||||
// 通道数
|
|
||||||
data.setUint16(offset, channelCount, true)
|
|
||||||
offset += 2
|
|
||||||
// 采样率,每秒样本数,表示每个通道的播放速度
|
|
||||||
data.setUint32(offset, sampleRate, true)
|
|
||||||
offset += 4
|
|
||||||
// 波形数据传输率 (每秒平均字节数) 单声道×每秒数据位数×每样本数据位/8
|
|
||||||
data.setUint32(offset, channelCount * sampleRate * (sampleBits / 8), true)
|
|
||||||
offset += 4
|
|
||||||
// 快数据调整数 采样一次占用字节数 单声道×每样本的数据位数/8
|
|
||||||
data.setUint16(offset, channelCount * (sampleBits / 8), true)
|
|
||||||
offset += 2
|
|
||||||
// 每样本数据位数
|
|
||||||
data.setUint16(offset, sampleBits, true)
|
|
||||||
offset += 2
|
|
||||||
// 数据标识符
|
|
||||||
writeString(data, offset, 'data')
|
|
||||||
offset += 4
|
|
||||||
// 采样数据总数,即数据总大小-44
|
|
||||||
data.setUint32(offset, bytes.byteLength, true)
|
|
||||||
offset += 4
|
|
||||||
|
|
||||||
// 给wav头增加pcm体
|
|
||||||
for (let i = 0; i < bytes.byteLength; ) {
|
|
||||||
data.setUint8(offset, bytes.getUint8(i), true)
|
|
||||||
offset++
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
|
|
||||||
function downloadWAV(audioData, sampleRate, oututSampleBits) {
|
|
||||||
let wavData = encodeWAV(audioData, sampleRate||44100, 1, oututSampleBits||16)
|
|
||||||
let blob = new Blob([wavData], {
|
|
||||||
type: 'audio/wav',
|
|
||||||
})
|
|
||||||
let defaultName = new Date().getTime()
|
|
||||||
let node = document.createElement('a')
|
|
||||||
node.href = window.URL.createObjectURL(blob)
|
|
||||||
node.download = `${defaultName}.wav`
|
|
||||||
node.click()
|
|
||||||
node.remove()
|
|
||||||
}
|
|
||||||
|
|
||||||
function downloadPCM(audioData) {
|
|
||||||
let blob = new Blob([audioData], {
|
|
||||||
type: 'audio/pcm',
|
|
||||||
})
|
|
||||||
let defaultName = new Date().getTime()
|
|
||||||
let node = document.createElement('a')
|
|
||||||
node.href = window.URL.createObjectURL(blob)
|
|
||||||
node.download = `${defaultName}.pcm`
|
|
||||||
node.click()
|
|
||||||
node.remove()
|
|
||||||
}
|
|
||||||
export {
|
|
||||||
downloadWAV,
|
|
||||||
downloadPCM
|
|
||||||
}
|
|
||||||
@ -1,47 +0,0 @@
|
|||||||
let minSampleRate = 22050
|
|
||||||
self.onmessage = function(e) {
|
|
||||||
transcode.transToAudioData(e.data)
|
|
||||||
}
|
|
||||||
var transcode = {
|
|
||||||
transToAudioData: function(audioDataStr, fromRate = 16000, toRate = 22505) {
|
|
||||||
let outputS16 = transcode.base64ToS16(audioDataStr)
|
|
||||||
let output = transcode.transS16ToF32(outputS16)
|
|
||||||
output = transcode.transSamplingRate(output, fromRate, toRate)
|
|
||||||
output = Array.from(output)
|
|
||||||
self.postMessage({
|
|
||||||
data: output,
|
|
||||||
rawAudioData: Array.from(outputS16)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
transSamplingRate: function(data, fromRate = 44100, toRate = 16000) {
|
|
||||||
var fitCount = Math.round(data.length * (toRate / fromRate))
|
|
||||||
var newData = new Float32Array(fitCount)
|
|
||||||
var springFactor = (data.length - 1) / (fitCount - 1)
|
|
||||||
newData[0] = data[0]
|
|
||||||
for (let i = 1; i < fitCount - 1; i++) {
|
|
||||||
var tmp = i * springFactor
|
|
||||||
var before = Math.floor(tmp).toFixed()
|
|
||||||
var after = Math.ceil(tmp).toFixed()
|
|
||||||
var atPoint = tmp - before
|
|
||||||
newData[i] = data[before] + (data[after] - data[before]) * atPoint
|
|
||||||
}
|
|
||||||
newData[fitCount - 1] = data[data.length - 1]
|
|
||||||
return newData
|
|
||||||
},
|
|
||||||
transS16ToF32: function(input) {
|
|
||||||
var tmpData = []
|
|
||||||
for (let i = 0; i < input.length; i++) {
|
|
||||||
var d = input[i] < 0 ? input[i] / 0x8000 : input[i] / 0x7fff
|
|
||||||
tmpData.push(d)
|
|
||||||
}
|
|
||||||
return new Float32Array(tmpData)
|
|
||||||
},
|
|
||||||
base64ToS16: function(base64AudioData) {
|
|
||||||
base64AudioData = atob(base64AudioData)
|
|
||||||
const outputArray = new Uint8Array(base64AudioData.length)
|
|
||||||
for (let i = 0; i < base64AudioData.length; ++i) {
|
|
||||||
outputArray[i] = base64AudioData.charCodeAt(i)
|
|
||||||
}
|
|
||||||
return new Int16Array(new DataView(outputArray.buffer).buffer)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@ -1,347 +0,0 @@
|
|||||||
//
|
|
||||||
// tts-ws.ts
|
|
||||||
// 科大讯飞语音合成
|
|
||||||
//
|
|
||||||
// Created by panhong on 2023/07/20.
|
|
||||||
//
|
|
||||||
import { downloadPCM, downloadWAV } from './download.js';
|
|
||||||
import CryptoES from "crypto-es"; // 科大讯飞
|
|
||||||
import Enc from 'enc';
|
|
||||||
const transWorker = new Worker(
|
|
||||||
new URL("../kedatts/transcode.worker.js", import.meta.url)
|
|
||||||
);
|
|
||||||
import VConsole from 'vconsole';
|
|
||||||
import { Base64 } from 'js-base64';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取websocket url
|
|
||||||
* APPID,APISecret,APIKey在控制台-我的应用-语音合成(流式版)页面获取,正式使用需要后端配置返回避免泄露
|
|
||||||
*/
|
|
||||||
const APPID = "";
|
|
||||||
const API_SECRET = "";
|
|
||||||
const API_KEY = "";
|
|
||||||
|
|
||||||
function getWebsocketUrl(): Promise<string> {
|
|
||||||
return new Promise<string>((resolve, reject) => {
|
|
||||||
var apiKey = API_KEY;
|
|
||||||
var apiSecret = API_SECRET;
|
|
||||||
var url = 'wss://tts-api.xfyun.cn/v2/tts';
|
|
||||||
var host = location.host;
|
|
||||||
var date = new Date().toGMTString();
|
|
||||||
var algorithm = 'hmac-sha256';
|
|
||||||
var headers = 'host date request-line';
|
|
||||||
var signatureOrigin = `host: ${host}\ndate: ${date}\nGET /v2/tts HTTP/1.1`;
|
|
||||||
var signatureSha = CryptoES.HmacSHA256(signatureOrigin, apiSecret);
|
|
||||||
var signature = CryptoES.enc.Base64.stringify(signatureSha);
|
|
||||||
var authorizationOrigin = `api_key="${apiKey}", algorithm="${algorithm}", headers="${headers}", signature="${signature}"`;
|
|
||||||
var authorization = btoa(authorizationOrigin);
|
|
||||||
url = `${url}?authorization=${authorization}&date=${date}&host=${host}`;
|
|
||||||
resolve(url);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class TTSRecorder {
|
|
||||||
private speed: number;
|
|
||||||
private voice: number;
|
|
||||||
private pitch: number;
|
|
||||||
private voiceName: string;
|
|
||||||
private text: string;
|
|
||||||
private tte: string;
|
|
||||||
private defaultText: string;
|
|
||||||
private appId: string;
|
|
||||||
private audioData: number[];
|
|
||||||
private rawAudioData: number[];
|
|
||||||
private audioDataOffset: number;
|
|
||||||
private status: string;
|
|
||||||
private ttsWS: WebSocket;
|
|
||||||
private playTimeout: ReturnType<typeof setTimeout> | null;
|
|
||||||
private audioContext: AudioContext | null;
|
|
||||||
private bufferSource: AudioBufferSourceNode | null;
|
|
||||||
|
|
||||||
constructor({
|
|
||||||
speed = 50,
|
|
||||||
voice = 50,
|
|
||||||
pitch = 50,
|
|
||||||
voiceName = 'xiaoyan',
|
|
||||||
appId = APPID,
|
|
||||||
text = '',
|
|
||||||
tte = 'UTF8',
|
|
||||||
defaultText = '请输入您要合成的文本',
|
|
||||||
}: {
|
|
||||||
speed?: number;
|
|
||||||
voice?: number;
|
|
||||||
pitch?: number;
|
|
||||||
voiceName?: string;
|
|
||||||
appId?: string;
|
|
||||||
text?: string;
|
|
||||||
tte?: string;
|
|
||||||
defaultText?: string;
|
|
||||||
} = {}) {
|
|
||||||
this.speed = speed;
|
|
||||||
this.voice = voice;
|
|
||||||
this.pitch = pitch;
|
|
||||||
this.voiceName = voiceName;
|
|
||||||
this.text = text;
|
|
||||||
this.tte = tte;
|
|
||||||
this.defaultText = defaultText;
|
|
||||||
this.appId = appId;
|
|
||||||
this.audioData = [];
|
|
||||||
this.rawAudioData = [];
|
|
||||||
this.audioDataOffset = 0;
|
|
||||||
this.status = 'init';
|
|
||||||
this.ttsWS = null;
|
|
||||||
this.playTimeout = null;
|
|
||||||
this.audioContext = null;
|
|
||||||
this.bufferSource = null;
|
|
||||||
transWorker.onmessage = (e: MessageEvent) => {
|
|
||||||
// if(e.data != undefined && e.data.length > 0) {
|
|
||||||
this.audioData.push(...e.data.data);
|
|
||||||
this.rawAudioData.push(...e.data.rawAudioData);
|
|
||||||
// }
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// 修改录音听写状态
|
|
||||||
private setStatus(status: string) {
|
|
||||||
if (this.onWillStatusChange) {
|
|
||||||
this.onWillStatusChange(this.status, status);
|
|
||||||
}
|
|
||||||
this.status = status;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设置合成相关参数
|
|
||||||
public setParams({ speed, voice, pitch, text, voiceName, tte }: {
|
|
||||||
speed?: number;
|
|
||||||
voice?: number;
|
|
||||||
pitch?: number;
|
|
||||||
text?: string;
|
|
||||||
voiceName?: string;
|
|
||||||
tte?: string;
|
|
||||||
}) {
|
|
||||||
speed !== undefined && (this.speed = speed);
|
|
||||||
voice !== undefined && (this.voice = voice);
|
|
||||||
pitch !== undefined && (this.pitch = pitch);
|
|
||||||
text && (this.text = text);
|
|
||||||
tte && (this.tte = tte);
|
|
||||||
voiceName && (this.voiceName = voiceName);
|
|
||||||
this.resetAudio();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 连接websocket
|
|
||||||
private connectWebSocket() {
|
|
||||||
this.setStatus('ttsing');
|
|
||||||
return getWebsocketUrl().then(url => {
|
|
||||||
let ttsWS: WebSocket;
|
|
||||||
if ('WebSocket' in window) {
|
|
||||||
ttsWS = new WebSocket(url);
|
|
||||||
} else if ('MozWebSocket' in window) {
|
|
||||||
ttsWS = new MozWebSocket(url);
|
|
||||||
} else {
|
|
||||||
alert('浏览器不支持WebSocket');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.ttsWS = ttsWS;
|
|
||||||
ttsWS.onopen = e => {
|
|
||||||
this.webSocketSend();
|
|
||||||
this.playTimeout = setTimeout(() => {
|
|
||||||
this.audioPlay();
|
|
||||||
}, 1000);
|
|
||||||
};
|
|
||||||
ttsWS.onmessage = e => {
|
|
||||||
this.result(e.data);
|
|
||||||
};
|
|
||||||
ttsWS.onerror = e => {
|
|
||||||
clearTimeout(this.playTimeout);
|
|
||||||
this.setStatus('errorTTS');
|
|
||||||
alert('WebSocket报错,请f12查看详情');
|
|
||||||
console.error(`详情查看:${encodeURI(url.replace('wss:', 'https:'))}`);
|
|
||||||
};
|
|
||||||
ttsWS.onclose = e => {
|
|
||||||
console.log(e);
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理音频数据
|
|
||||||
private transToAudioData(audioData: number[]) {
|
|
||||||
// Implement your logic here
|
|
||||||
}
|
|
||||||
|
|
||||||
// websocket发送数据
|
|
||||||
// bgs有背景音 0:无背景音(默认值) 1:有背景音
|
|
||||||
// auf 音频采样率,可选值:
|
|
||||||
// audio/L16;rate=8000:合成8K 的音频
|
|
||||||
// audio/L16;rate=16000:合成16K 的音频
|
|
||||||
// auf不传值:合成16K 的音频
|
|
||||||
// aue:音频编码,可选值:
|
|
||||||
// raw:未压缩的pcm
|
|
||||||
// lame:mp3 (当aue=lame时需传参sfl=1)
|
|
||||||
// speex-org-wb;7: 标准开源speex(for speex_wideband,即16k)数字代表指定压缩等级(默认等级为8)
|
|
||||||
// speex-org-nb;7: 标准开源speex(for speex_narrowband,即8k)数字代表指定压缩等级(默认等级为8)
|
|
||||||
// speex;7:压缩格式,压缩等级1~10,默认为7(8k讯飞定制speex)
|
|
||||||
// speex-wb;7:压缩格式,压缩等级1~10,默认为7(16k讯飞定制speex)
|
|
||||||
private webSocketSend() {
|
|
||||||
var params = {
|
|
||||||
common: {
|
|
||||||
app_id: this.appId, // APPID
|
|
||||||
},
|
|
||||||
business: {
|
|
||||||
aue: 'raw',
|
|
||||||
auf: 'audio/L16;rate=16000',
|
|
||||||
vcn: this.voiceName,
|
|
||||||
speed: this.speed,
|
|
||||||
volume: this.voice,
|
|
||||||
pitch: this.pitch,
|
|
||||||
bgs: 0,
|
|
||||||
tte: this.tte,
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
status: 2,
|
|
||||||
text: this.encodeText(
|
|
||||||
this.text || this.defaultText,
|
|
||||||
this.tte === 'unicode' ? 'base64&utf16le' : ''
|
|
||||||
),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
this.ttsWS.send(JSON.stringify(params));
|
|
||||||
}
|
|
||||||
|
|
||||||
private encodeText(text: string, encoding: string): ArrayBuffer | string {
|
|
||||||
switch (encoding) {
|
|
||||||
case 'utf16le': {
|
|
||||||
let buf = new ArrayBuffer(text.length * 4);
|
|
||||||
let bufView = new Uint16Array(buf);
|
|
||||||
for (let i = 0, strlen = text.length; i < strlen; i++) {
|
|
||||||
bufView[i] = text.charCodeAt(i);
|
|
||||||
}
|
|
||||||
return buf;
|
|
||||||
}
|
|
||||||
case 'buffer2Base64': {
|
|
||||||
let binary = '';
|
|
||||||
let bytes = new Uint8Array(text);
|
|
||||||
let len = bytes.byteLength;
|
|
||||||
for (let i = 0; i < len; i++) {
|
|
||||||
binary += String.fromCharCode(bytes[i]);
|
|
||||||
}
|
|
||||||
return window.btoa(binary);
|
|
||||||
}
|
|
||||||
case 'base64&utf16le': {
|
|
||||||
return this.encodeText(
|
|
||||||
this.encodeText(text, 'utf16le') as string,
|
|
||||||
'buffer2Base64'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
return Base64.encode(text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// websocket接收数据的处理
|
|
||||||
private result(resultData: string) {
|
|
||||||
let jsonData = JSON.parse(resultData);
|
|
||||||
// 合成失败
|
|
||||||
if (jsonData.code !== 0) {
|
|
||||||
alert(`合成失败: ${jsonData.code}:${jsonData.message}`);
|
|
||||||
console.error(`${jsonData.code}:${jsonData.message}`);
|
|
||||||
this.resetAudio();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
transWorker.postMessage(jsonData.data.audio);
|
|
||||||
|
|
||||||
if (jsonData.code === 0 && jsonData.data.status === 2) {
|
|
||||||
this.ttsWS.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 重置音频数据
|
|
||||||
private resetAudio() {
|
|
||||||
this.audioStop();
|
|
||||||
this.setStatus('init');
|
|
||||||
this.audioDataOffset = 0;
|
|
||||||
this.audioData = [];
|
|
||||||
this.rawAudioData = [];
|
|
||||||
this.ttsWS && this.ttsWS.close();
|
|
||||||
clearTimeout(this.playTimeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 音频初始化
|
|
||||||
private audioInit() {
|
|
||||||
let AudioContext = window.AudioContext || window.webkitAudioContext;
|
|
||||||
if (AudioContext) {
|
|
||||||
this.audioContext = new AudioContext();
|
|
||||||
this.audioContext.resume();
|
|
||||||
this.audioDataOffset = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 音频播放
|
|
||||||
private audioPlay() {
|
|
||||||
this.setStatus('play');
|
|
||||||
let audioData = this.audioData.slice(this.audioDataOffset);
|
|
||||||
this.audioDataOffset += audioData.length;
|
|
||||||
let audioBuffer = this.audioContext.createBuffer(1, audioData.length, 22050);
|
|
||||||
let nowBuffering = audioBuffer.getChannelData(0);
|
|
||||||
if (audioBuffer.copyToChannel) {
|
|
||||||
audioBuffer.copyToChannel(
|
|
||||||
new Float32Array(audioData),
|
|
||||||
0,
|
|
||||||
0
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
for (let i = 0; i < audioData.length; i++) {
|
|
||||||
nowBuffering[i] = audioData[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let bufferSource = this.bufferSource = this.audioContext.createBufferSource();
|
|
||||||
bufferSource.buffer = audioBuffer;
|
|
||||||
bufferSource.connect(this.audioContext.destination);
|
|
||||||
bufferSource.start();
|
|
||||||
bufferSource.onended = event => {
|
|
||||||
if (this.status !== 'play') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (this.audioDataOffset < this.audioData.length) {
|
|
||||||
this.audioPlay();
|
|
||||||
} else {
|
|
||||||
this.audioStop();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// 音频播放结束
|
|
||||||
private audioStop() {
|
|
||||||
this.setStatus('endPlay');
|
|
||||||
clearTimeout(this.playTimeout);
|
|
||||||
this.audioDataOffset = 0;
|
|
||||||
if (this.bufferSource) {
|
|
||||||
try {
|
|
||||||
this.bufferSource.stop();
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public start() {
|
|
||||||
if (this.audioData.length) {
|
|
||||||
this.audioPlay();
|
|
||||||
} else {
|
|
||||||
if (!this.audioContext) {
|
|
||||||
this.audioInit();
|
|
||||||
}
|
|
||||||
if (!this.audioContext) {
|
|
||||||
alert('该浏览器不支持webAudioApi相关接口');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.connectWebSocket();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public stop() {
|
|
||||||
this.audioStop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default TTSRecorder;
|
|
||||||
@ -1,243 +0,0 @@
|
|||||||
/*
|
|
||||||
* base64.js
|
|
||||||
*
|
|
||||||
* Licensed under the BSD 3-Clause License.
|
|
||||||
* http://opensource.org/licenses/BSD-3-Clause
|
|
||||||
*
|
|
||||||
* References:
|
|
||||||
* http://en.wikipedia.org/wiki/Base64
|
|
||||||
*/
|
|
||||||
;(function (global, factory) {
|
|
||||||
typeof exports === 'object' && typeof module !== 'undefined'
|
|
||||||
? module.exports = factory(global)
|
|
||||||
: typeof define === 'function' && define.amd
|
|
||||||
? define(factory) : factory(global)
|
|
||||||
}((
|
|
||||||
typeof self !== 'undefined' ? self
|
|
||||||
: typeof window !== 'undefined' ? window
|
|
||||||
: typeof global !== 'undefined' ? global
|
|
||||||
: this
|
|
||||||
), function(global) {
|
|
||||||
'use strict';
|
|
||||||
// existing version for noConflict()
|
|
||||||
global = global || {};
|
|
||||||
var _Base64 = global.Base64;
|
|
||||||
var version = "2.5.1";
|
|
||||||
// if node.js and NOT React Native, we use Buffer
|
|
||||||
var buffer;
|
|
||||||
if (typeof module !== 'undefined' && module.exports) {
|
|
||||||
try {
|
|
||||||
buffer = eval("require('buffer').Buffer");
|
|
||||||
} catch (err) {
|
|
||||||
buffer = undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// constants
|
|
||||||
var b64chars
|
|
||||||
= 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
|
||||||
var b64tab = function(bin) {
|
|
||||||
var t = {};
|
|
||||||
for (var i = 0, l = bin.length; i < l; i++) t[bin.charAt(i)] = i;
|
|
||||||
return t;
|
|
||||||
}(b64chars);
|
|
||||||
var fromCharCode = String.fromCharCode;
|
|
||||||
// encoder stuff
|
|
||||||
var cb_utob = function(c) {
|
|
||||||
if (c.length < 2) {
|
|
||||||
var cc = c.charCodeAt(0);
|
|
||||||
return cc < 0x80 ? c
|
|
||||||
: cc < 0x800 ? (fromCharCode(0xc0 | (cc >>> 6))
|
|
||||||
+ fromCharCode(0x80 | (cc & 0x3f)))
|
|
||||||
: (fromCharCode(0xe0 | ((cc >>> 12) & 0x0f))
|
|
||||||
+ fromCharCode(0x80 | ((cc >>> 6) & 0x3f))
|
|
||||||
+ fromCharCode(0x80 | ( cc & 0x3f)));
|
|
||||||
} else {
|
|
||||||
var cc = 0x10000
|
|
||||||
+ (c.charCodeAt(0) - 0xD800) * 0x400
|
|
||||||
+ (c.charCodeAt(1) - 0xDC00);
|
|
||||||
return (fromCharCode(0xf0 | ((cc >>> 18) & 0x07))
|
|
||||||
+ fromCharCode(0x80 | ((cc >>> 12) & 0x3f))
|
|
||||||
+ fromCharCode(0x80 | ((cc >>> 6) & 0x3f))
|
|
||||||
+ fromCharCode(0x80 | ( cc & 0x3f)));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
var re_utob = /[\uD800-\uDBFF][\uDC00-\uDFFFF]|[^\x00-\x7F]/g;
|
|
||||||
var utob = function(u) {
|
|
||||||
return u.replace(re_utob, cb_utob);
|
|
||||||
};
|
|
||||||
var cb_encode = function(ccc) {
|
|
||||||
var padlen = [0, 2, 1][ccc.length % 3],
|
|
||||||
ord = ccc.charCodeAt(0) << 16
|
|
||||||
| ((ccc.length > 1 ? ccc.charCodeAt(1) : 0) << 8)
|
|
||||||
| ((ccc.length > 2 ? ccc.charCodeAt(2) : 0)),
|
|
||||||
chars = [
|
|
||||||
b64chars.charAt( ord >>> 18),
|
|
||||||
b64chars.charAt((ord >>> 12) & 63),
|
|
||||||
padlen >= 2 ? '=' : b64chars.charAt((ord >>> 6) & 63),
|
|
||||||
padlen >= 1 ? '=' : b64chars.charAt(ord & 63)
|
|
||||||
];
|
|
||||||
return chars.join('');
|
|
||||||
};
|
|
||||||
var btoa = global.btoa ? function(b) {
|
|
||||||
return global.btoa(b);
|
|
||||||
} : function(b) {
|
|
||||||
return b.replace(/[\s\S]{1,3}/g, cb_encode);
|
|
||||||
};
|
|
||||||
var _encode = buffer ?
|
|
||||||
buffer.from && Uint8Array && buffer.from !== Uint8Array.from
|
|
||||||
? function (u) {
|
|
||||||
return (u.constructor === buffer.constructor ? u : buffer.from(u))
|
|
||||||
.toString('base64')
|
|
||||||
}
|
|
||||||
: function (u) {
|
|
||||||
return (u.constructor === buffer.constructor ? u : new buffer(u))
|
|
||||||
.toString('base64')
|
|
||||||
}
|
|
||||||
: function (u) { return btoa(utob(u)) }
|
|
||||||
;
|
|
||||||
var encode = function(u, urisafe) {
|
|
||||||
return !urisafe
|
|
||||||
? _encode(String(u))
|
|
||||||
: _encode(String(u)).replace(/[+\/]/g, function(m0) {
|
|
||||||
return m0 == '+' ? '-' : '_';
|
|
||||||
}).replace(/=/g, '');
|
|
||||||
};
|
|
||||||
var encodeURI = function(u) { return encode(u, true) };
|
|
||||||
// decoder stuff
|
|
||||||
var re_btou = new RegExp([
|
|
||||||
'[\xC0-\xDF][\x80-\xBF]',
|
|
||||||
'[\xE0-\xEF][\x80-\xBF]{2}',
|
|
||||||
'[\xF0-\xF7][\x80-\xBF]{3}'
|
|
||||||
].join('|'), 'g');
|
|
||||||
var cb_btou = function(cccc) {
|
|
||||||
switch(cccc.length) {
|
|
||||||
case 4:
|
|
||||||
var cp = ((0x07 & cccc.charCodeAt(0)) << 18)
|
|
||||||
| ((0x3f & cccc.charCodeAt(1)) << 12)
|
|
||||||
| ((0x3f & cccc.charCodeAt(2)) << 6)
|
|
||||||
| (0x3f & cccc.charCodeAt(3)),
|
|
||||||
offset = cp - 0x10000;
|
|
||||||
return (fromCharCode((offset >>> 10) + 0xD800)
|
|
||||||
+ fromCharCode((offset & 0x3FF) + 0xDC00));
|
|
||||||
case 3:
|
|
||||||
return fromCharCode(
|
|
||||||
((0x0f & cccc.charCodeAt(0)) << 12)
|
|
||||||
| ((0x3f & cccc.charCodeAt(1)) << 6)
|
|
||||||
| (0x3f & cccc.charCodeAt(2))
|
|
||||||
);
|
|
||||||
default:
|
|
||||||
return fromCharCode(
|
|
||||||
((0x1f & cccc.charCodeAt(0)) << 6)
|
|
||||||
| (0x3f & cccc.charCodeAt(1))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
var btou = function(b) {
|
|
||||||
return b.replace(re_btou, cb_btou);
|
|
||||||
};
|
|
||||||
var cb_decode = function(cccc) {
|
|
||||||
var len = cccc.length,
|
|
||||||
padlen = len % 4,
|
|
||||||
n = (len > 0 ? b64tab[cccc.charAt(0)] << 18 : 0)
|
|
||||||
| (len > 1 ? b64tab[cccc.charAt(1)] << 12 : 0)
|
|
||||||
| (len > 2 ? b64tab[cccc.charAt(2)] << 6 : 0)
|
|
||||||
| (len > 3 ? b64tab[cccc.charAt(3)] : 0),
|
|
||||||
chars = [
|
|
||||||
fromCharCode( n >>> 16),
|
|
||||||
fromCharCode((n >>> 8) & 0xff),
|
|
||||||
fromCharCode( n & 0xff)
|
|
||||||
];
|
|
||||||
chars.length -= [0, 0, 2, 1][padlen];
|
|
||||||
return chars.join('');
|
|
||||||
};
|
|
||||||
var _atob = global.atob ? function(a) {
|
|
||||||
return global.atob(a);
|
|
||||||
} : function(a){
|
|
||||||
return a.replace(/\S{1,4}/g, cb_decode);
|
|
||||||
};
|
|
||||||
var atob = function(a) {
|
|
||||||
return _atob(String(a).replace(/[^A-Za-z0-9\+\/]/g, ''));
|
|
||||||
};
|
|
||||||
var _decode = buffer ?
|
|
||||||
buffer.from && Uint8Array && buffer.from !== Uint8Array.from
|
|
||||||
? function(a) {
|
|
||||||
return (a.constructor === buffer.constructor
|
|
||||||
? a : buffer.from(a, 'base64')).toString();
|
|
||||||
}
|
|
||||||
: function(a) {
|
|
||||||
return (a.constructor === buffer.constructor
|
|
||||||
? a : new buffer(a, 'base64')).toString();
|
|
||||||
}
|
|
||||||
: function(a) { return btou(_atob(a)) };
|
|
||||||
var decode = function(a){
|
|
||||||
return _decode(
|
|
||||||
String(a).replace(/[-_]/g, function(m0) { return m0 == '-' ? '+' : '/' })
|
|
||||||
.replace(/[^A-Za-z0-9\+\/]/g, '')
|
|
||||||
);
|
|
||||||
};
|
|
||||||
var noConflict = function() {
|
|
||||||
var Base64 = global.Base64;
|
|
||||||
global.Base64 = _Base64;
|
|
||||||
return Base64;
|
|
||||||
};
|
|
||||||
// export Base64
|
|
||||||
global.Base64 = {
|
|
||||||
VERSION: version,
|
|
||||||
atob: atob,
|
|
||||||
btoa: btoa,
|
|
||||||
fromBase64: decode,
|
|
||||||
toBase64: encode,
|
|
||||||
utob: utob,
|
|
||||||
encode: encode,
|
|
||||||
encodeURI: encodeURI,
|
|
||||||
btou: btou,
|
|
||||||
decode: decode,
|
|
||||||
noConflict: noConflict,
|
|
||||||
__buffer__: buffer
|
|
||||||
};
|
|
||||||
// if ES5 is available, make Base64.extendString() available
|
|
||||||
if (typeof Object.defineProperty === 'function') {
|
|
||||||
var noEnum = function(v){
|
|
||||||
return {value:v,enumerable:false,writable:true,configurable:true};
|
|
||||||
};
|
|
||||||
global.Base64.extendString = function () {
|
|
||||||
Object.defineProperty(
|
|
||||||
String.prototype, 'fromBase64', noEnum(function () {
|
|
||||||
return decode(this)
|
|
||||||
}));
|
|
||||||
Object.defineProperty(
|
|
||||||
String.prototype, 'toBase64', noEnum(function (urisafe) {
|
|
||||||
return encode(this, urisafe)
|
|
||||||
}));
|
|
||||||
Object.defineProperty(
|
|
||||||
String.prototype, 'toBase64URI', noEnum(function () {
|
|
||||||
return encode(this, true)
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
}
|
|
||||||
//
|
|
||||||
// export Base64 to the namespace
|
|
||||||
//
|
|
||||||
if (global['Meteor']) { // Meteor.js
|
|
||||||
Base64 = global.Base64;
|
|
||||||
}
|
|
||||||
// module.exports and AMD are mutually exclusive.
|
|
||||||
// module.exports has precedence.
|
|
||||||
if (typeof module !== 'undefined' && module.exports) {
|
|
||||||
module.exports.Base64 = global.Base64;
|
|
||||||
}
|
|
||||||
else if (typeof define === 'function' && define.amd) {
|
|
||||||
// AMD. Register as an anonymous module.
|
|
||||||
define([], function(){ return global.Base64 });
|
|
||||||
}
|
|
||||||
// that's it!
|
|
||||||
return {Base64: global.Base64}
|
|
||||||
}));
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//////////////////
|
|
||||||
// WEBPACK FOOTER
|
|
||||||
// ./node_modules/_js-base64@2.5.1@js-base64/base64.js
|
|
||||||
// module id = 57
|
|
||||||
// module chunks = 2
|
|
||||||
File diff suppressed because one or more lines are too long
@ -1,362 +0,0 @@
|
|||||||
//
|
|
||||||
// iatRecorder.ts
|
|
||||||
// 科大讯飞语音识别
|
|
||||||
//
|
|
||||||
// Created by panhong on 2023/07/20.
|
|
||||||
//
|
|
||||||
import CryptoES from "crypto-es"; // 科大讯飞
|
|
||||||
const transWorker = new Worker(
|
|
||||||
new URL("../until/transcode.worker.js", import.meta.url)
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取websocket url
|
|
||||||
* APPID,APISecret,APIKey在控制台-我的应用-语音合成(流式版)页面获取,正式使用需要后端配置返回避免泄露
|
|
||||||
*/
|
|
||||||
const APPID = "";
|
|
||||||
const API_SECRET = "";
|
|
||||||
const API_KEY = "";
|
|
||||||
|
|
||||||
const getWebSocketUrl = () => {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
// 请求地址根据语种不同变化
|
|
||||||
var url = "wss://iat-api.xfyun.cn/v2/iat";
|
|
||||||
var host = "iat-api.xfyun.cn";
|
|
||||||
var apiKey = API_KEY;
|
|
||||||
var apiSecret = API_SECRET;
|
|
||||||
var date = new Date().toGMTString();
|
|
||||||
var algorithm = "hmac-sha256";
|
|
||||||
var headers = "host date request-line";
|
|
||||||
var signatureOrigin = `host: ${host}\ndate: ${date}\nGET /v2/iat HTTP/1.1`;
|
|
||||||
var signatureSha = CryptoES.HmacSHA256(signatureOrigin, apiSecret);
|
|
||||||
var signature = CryptoES.enc.Base64.stringify(signatureSha);
|
|
||||||
var authorizationOrigin = `api_key="${apiKey}", algorithm="${algorithm}", headers="${headers}", signature="${signature}"`;
|
|
||||||
var authorization = btoa(authorizationOrigin);
|
|
||||||
url = `${url}?authorization=${authorization}&date=${date}&host=${host}`;
|
|
||||||
resolve(url);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
class IatRecorder {
|
|
||||||
public status: string;
|
|
||||||
public accent: string;
|
|
||||||
public language: string;
|
|
||||||
public appId: string;
|
|
||||||
public audioData: any[];
|
|
||||||
public resultText: string;
|
|
||||||
public resultTextTemp: string;
|
|
||||||
public onWillStatusChange?: (
|
|
||||||
prevStatus: string,
|
|
||||||
nextStatus: string
|
|
||||||
) => void;
|
|
||||||
public onTextChange?: (text: string) => void;
|
|
||||||
constructor(
|
|
||||||
{ language, accent, appId } = {} as {
|
|
||||||
language?: string;
|
|
||||||
accent?: string;
|
|
||||||
appId?: string;
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
let self = this;
|
|
||||||
this.status = "null";
|
|
||||||
this.language = language || "zh_cn";
|
|
||||||
this.accent = accent || "mandarin";
|
|
||||||
this.appId = appId || APPID;
|
|
||||||
// 记录音频数据
|
|
||||||
this.audioData = [];
|
|
||||||
// 记录听写结果
|
|
||||||
this.resultText = "";
|
|
||||||
// wpgs下的听写结果需要中间状态辅助记录
|
|
||||||
this.resultTextTemp = "";
|
|
||||||
transWorker.onmessage = function (event) {
|
|
||||||
self.audioData.push(...event.data);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
// 修改录音听写状态
|
|
||||||
setStatus(status:string) {
|
|
||||||
this.onWillStatusChange && this.status !== status && this.onWillStatusChange(this.status, status)
|
|
||||||
this.status = status
|
|
||||||
}
|
|
||||||
// setStatus({ status, resultText, resultTextTemp }:{ string; resultText?: string; resultTextTemp?: string}) {
|
|
||||||
// this.onWillStatusChange && this.status !== status && this.onWillStatusChange(this.status, status, resultTextTemp || resultText || '')
|
|
||||||
// this.status = status
|
|
||||||
// resultText !== undefined && (this.resultText = resultText)
|
|
||||||
// resultTextTemp !== undefined && (this.resultTextTemp = resultTextTemp)
|
|
||||||
// }
|
|
||||||
setResultText({ resultText, resultTextTemp }:{ resultText?: string; resultTextTemp?: string } = {}) {
|
|
||||||
this.onTextChange && this.onTextChange(resultTextTemp || resultText || '')
|
|
||||||
resultText !== undefined && (this.resultText = resultText)
|
|
||||||
resultTextTemp !== undefined && (this.resultTextTemp = resultTextTemp)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 修改听写参数
|
|
||||||
setParams({ language, accent }:{language?:string; accent?:string} = {}) {
|
|
||||||
language && (this.language = language)
|
|
||||||
accent && (this.accent = accent)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 连接websocket
|
|
||||||
connectWebSocket() {
|
|
||||||
return getWebSocketUrl().then(url => {
|
|
||||||
let iatWS
|
|
||||||
if ('WebSocket' in window) {
|
|
||||||
iatWS = new WebSocket(url)
|
|
||||||
} else if ('MozWebSocket' in window) {
|
|
||||||
iatWS = new MozWebSocket(url)
|
|
||||||
} else {
|
|
||||||
alert('浏览器不支持WebSocket')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this.webSocket = iatWS
|
|
||||||
this.setStatus('init')
|
|
||||||
iatWS.onopen = e => {
|
|
||||||
this.setStatus('ing')
|
|
||||||
// 重新开始录音
|
|
||||||
setTimeout(() => {
|
|
||||||
this.webSocketSend()
|
|
||||||
}, 500)
|
|
||||||
}
|
|
||||||
iatWS.onmessage = e => {
|
|
||||||
this.result(e.data)
|
|
||||||
}
|
|
||||||
iatWS.onerror = e => {
|
|
||||||
console.log(`${e.code}`,'onerroronerroronerror')
|
|
||||||
this.recorderStop()
|
|
||||||
}
|
|
||||||
iatWS.onclose = e => {
|
|
||||||
console.log(`${e.code}`,'oncloseonclose')
|
|
||||||
this.recorderStop()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 初始化浏览器录音
|
|
||||||
recorderInit() {
|
|
||||||
navigator.getUserMedia =
|
|
||||||
navigator.getUserMedia ||
|
|
||||||
navigator.webkitGetUserMedia ||
|
|
||||||
navigator.mozGetUserMedia ||
|
|
||||||
navigator.msGetUserMedia
|
|
||||||
|
|
||||||
// 创建音频环境
|
|
||||||
try {
|
|
||||||
this.audioContext = new (window.AudioContext || window.webkitAudioContext)()
|
|
||||||
this.audioContext.resume()
|
|
||||||
if (!this.audioContext) {
|
|
||||||
alert('浏览器不支持webAudioApi相关接口')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
if (!this.audioContext) {
|
|
||||||
alert('浏览器不支持webAudioApi相关接口')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取浏览器录音权限
|
|
||||||
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
|
|
||||||
navigator.mediaDevices
|
|
||||||
.getUserMedia({
|
|
||||||
audio: true,
|
|
||||||
video: false,
|
|
||||||
})
|
|
||||||
.then(stream => {
|
|
||||||
getMediaSuccess(stream)
|
|
||||||
})
|
|
||||||
.catch(e => {
|
|
||||||
getMediaFail(e)
|
|
||||||
})
|
|
||||||
} else if (navigator.getUserMedia) {
|
|
||||||
navigator.getUserMedia(
|
|
||||||
{
|
|
||||||
audio: true,
|
|
||||||
video: false,
|
|
||||||
},
|
|
||||||
stream => {
|
|
||||||
getMediaSuccess(stream)
|
|
||||||
},
|
|
||||||
function (e) {
|
|
||||||
getMediaFail(e)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
if (navigator.userAgent.toLowerCase().match(/chrome/) && location.origin.indexOf('https://') < 0) {
|
|
||||||
alert('chrome下获取浏览器录音功能,因为安全性问题,需要在localhost或127.0.0.1或https下才能获取权限')
|
|
||||||
} else {
|
|
||||||
alert('无法获取浏览器录音功能,请升级浏览器或使用chrome')
|
|
||||||
}
|
|
||||||
this.audioContext && this.audioContext.close()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// 获取浏览器录音权限成功的回调
|
|
||||||
let getMediaSuccess = stream => {
|
|
||||||
console.log('getMediaSuccess')
|
|
||||||
// 创建一个用于通过JavaScript直接处理音频
|
|
||||||
this.scriptProcessor = this.audioContext.createScriptProcessor(0, 1, 1)
|
|
||||||
this.scriptProcessor.onaudioprocess = e => {
|
|
||||||
// 去处理音频数据
|
|
||||||
if (this.status === 'ing') {
|
|
||||||
transWorker.postMessage(e.inputBuffer.getChannelData(0))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 创建一个新的MediaStreamAudioSourceNode 对象,使来自MediaStream的音频可以被播放和操作
|
|
||||||
this.mediaSource = this.audioContext.createMediaStreamSource(stream)
|
|
||||||
// 连接
|
|
||||||
this.mediaSource.connect(this.scriptProcessor)
|
|
||||||
this.scriptProcessor.connect(this.audioContext.destination)
|
|
||||||
this.connectWebSocket()
|
|
||||||
}
|
|
||||||
|
|
||||||
let getMediaFail = (e) => {
|
|
||||||
alert('请求麦克风失败')
|
|
||||||
console.log(e)
|
|
||||||
this.audioContext && this.audioContext.close()
|
|
||||||
this.audioContext = undefined
|
|
||||||
// 关闭websocket
|
|
||||||
if (this.webSocket && this.webSocket.readyState === 1) {
|
|
||||||
this.webSocket.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
recorderStart() {
|
|
||||||
if (!this.audioContext) {
|
|
||||||
this.recorderInit()
|
|
||||||
} else {
|
|
||||||
this.audioContext.resume()
|
|
||||||
this.connectWebSocket()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 暂停录音
|
|
||||||
recorderStop() {
|
|
||||||
// safari下suspend后再次resume录音内容将是空白,设置safari下不做suspend
|
|
||||||
if (!(/Safari/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgen))) {
|
|
||||||
this.audioContext && this.audioContext.suspend()
|
|
||||||
}
|
|
||||||
this.setStatus('end')
|
|
||||||
}
|
|
||||||
// 处理音频数据
|
|
||||||
// transAudioData(audioData) {
|
|
||||||
// audioData = transAudioData.transaction(audioData)
|
|
||||||
// this.audioData.push(...audioData)
|
|
||||||
// }
|
|
||||||
// 对处理后的音频数据进行base64编码,
|
|
||||||
toBase64(buffer) {
|
|
||||||
var binary = ''
|
|
||||||
var bytes = new Uint8Array(buffer)
|
|
||||||
var len = bytes.byteLength
|
|
||||||
for (var i = 0; i < len; i++) {
|
|
||||||
binary += String.fromCharCode(bytes[i])
|
|
||||||
}
|
|
||||||
return window.btoa(binary)
|
|
||||||
}
|
|
||||||
// 向webSocket发送数据
|
|
||||||
webSocketSend() {
|
|
||||||
if (this.webSocket.readyState !== 1) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let audioData = this.audioData.splice(0, 1280)
|
|
||||||
var params = {
|
|
||||||
common: {
|
|
||||||
app_id: this.appId,
|
|
||||||
},
|
|
||||||
business: {
|
|
||||||
language: this.language, //小语种可在控制台--语音听写(流式)--方言/语种处添加试用
|
|
||||||
domain: 'iat',
|
|
||||||
accent: this.accent, //中文方言可在控制台--语音听写(流式)--方言/语种处添加试用
|
|
||||||
vad_eos: 5000,
|
|
||||||
dwa: 'wpgs', //为使该功能生效,需到控制台开通动态修正功能(该功能免费)
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
status: 0,
|
|
||||||
format: 'audio/L16;rate=16000',
|
|
||||||
encoding: 'raw',
|
|
||||||
audio: this.toBase64(audioData),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
console.log(audioData, 'audioData')
|
|
||||||
this.webSocket.send(JSON.stringify(params))
|
|
||||||
this.handlerInterval = setInterval(() => {
|
|
||||||
// websocket未连接
|
|
||||||
if (this.webSocket.readyState !== 1) {
|
|
||||||
this.audioData = []
|
|
||||||
clearInterval(this.handlerInterval)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (this.audioData.length === 0) {
|
|
||||||
if (this.status === 'end') {
|
|
||||||
this.webSocket.send(
|
|
||||||
JSON.stringify({
|
|
||||||
data: {
|
|
||||||
status: 2,
|
|
||||||
format: 'audio/L16;rate=16000',
|
|
||||||
encoding: 'raw',
|
|
||||||
audio: '',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
)
|
|
||||||
this.audioData = []
|
|
||||||
clearInterval(this.handlerInterval)
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
audioData = this.audioData.splice(0, 1280)
|
|
||||||
// 中间帧
|
|
||||||
this.webSocket.send(
|
|
||||||
JSON.stringify({
|
|
||||||
data: {
|
|
||||||
status: 1,
|
|
||||||
format: 'audio/L16;rate=16000',
|
|
||||||
encoding: 'raw',
|
|
||||||
audio: this.toBase64(audioData),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}, 40)
|
|
||||||
}
|
|
||||||
result(resultData) {
|
|
||||||
// 识别结束
|
|
||||||
let jsonData = JSON.parse(resultData)
|
|
||||||
if (jsonData.data && jsonData.data.result) {
|
|
||||||
let data = jsonData.data.result
|
|
||||||
let str = ''
|
|
||||||
let resultStr = ''
|
|
||||||
let ws = data.ws
|
|
||||||
for (let i = 0; i < ws.length; i++) {
|
|
||||||
str = str + ws[i].cw[0].w
|
|
||||||
}
|
|
||||||
// 开启wpgs会有此字段(前提:在控制台开通动态修正功能)
|
|
||||||
// 取值为 "apd"时表示该片结果是追加到前面的最终结果;取值为"rpl" 时表示替换前面的部分结果,替换范围为rg字段
|
|
||||||
if (data.pgs) {
|
|
||||||
if (data.pgs === 'apd') {
|
|
||||||
// 将resultTextTemp同步给resultText
|
|
||||||
this.setResultText({
|
|
||||||
resultText: this.resultTextTemp,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
// 将结果存储在resultTextTemp中
|
|
||||||
this.setResultText({
|
|
||||||
resultTextTemp: this.resultText + str,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
this.setResultText({
|
|
||||||
resultText: this.resultText + str,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (jsonData.code === 0 && jsonData.data.status === 2) {
|
|
||||||
this.webSocket.close()
|
|
||||||
}
|
|
||||||
if (jsonData.code !== 0) {
|
|
||||||
this.webSocket.close()
|
|
||||||
console.log(`${jsonData.code}:${jsonData.message}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
start() {
|
|
||||||
this.recorderStart()
|
|
||||||
this.setResultText({ resultText: '', resultTextTemp: '' })
|
|
||||||
}
|
|
||||||
stop() {
|
|
||||||
this.recorderStop()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
export default IatRecorder
|
|
||||||
@ -1,41 +0,0 @@
|
|||||||
|
|
||||||
|
|
||||||
self.onmessage = function(e){
|
|
||||||
transAudioData.transcode(e.data)
|
|
||||||
}
|
|
||||||
let transAudioData = {
|
|
||||||
transcode(audioData) {
|
|
||||||
let output = transAudioData.to16kHz(audioData)
|
|
||||||
output = transAudioData.to16BitPCM(output)
|
|
||||||
output = Array.from(new Uint8Array(output.buffer))
|
|
||||||
self.postMessage(output)
|
|
||||||
// return output
|
|
||||||
},
|
|
||||||
to16kHz(audioData) {
|
|
||||||
var data = new Float32Array(audioData)
|
|
||||||
var fitCount = Math.round(data.length * (16000 / 44100))
|
|
||||||
var newData = new Float32Array(fitCount)
|
|
||||||
var springFactor = (data.length - 1) / (fitCount - 1)
|
|
||||||
newData[0] = data[0]
|
|
||||||
for (let i = 1; i < fitCount - 1; i++) {
|
|
||||||
var tmp = i * springFactor
|
|
||||||
var before = Math.floor(tmp).toFixed()
|
|
||||||
var after = Math.ceil(tmp).toFixed()
|
|
||||||
var atPoint = tmp - before
|
|
||||||
newData[i] = data[before] + (data[after] - data[before]) * atPoint
|
|
||||||
}
|
|
||||||
newData[fitCount - 1] = data[data.length - 1]
|
|
||||||
return newData
|
|
||||||
},
|
|
||||||
to16BitPCM(input) {
|
|
||||||
var dataLength = input.length * (16 / 8)
|
|
||||||
var dataBuffer = new ArrayBuffer(dataLength)
|
|
||||||
var dataView = new DataView(dataBuffer)
|
|
||||||
var offset = 0
|
|
||||||
for (var i = 0; i < input.length; i++, offset += 2) {
|
|
||||||
var s = Math.max(-1, Math.min(1, input[i]))
|
|
||||||
dataView.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7fff, true)
|
|
||||||
}
|
|
||||||
return dataView
|
|
||||||
},
|
|
||||||
}
|
|
||||||
Loading…
x
Reference in New Issue
Block a user