shengchanzhe ee064c2c55 更新
2023-10-12 13:42:50 +08:00

351 lines
10 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import CryptoJS from 'crypto-js'
const APPID = '2eda6c2e'
const API_SECRET = 'MDEyMzE5YTc5YmQ5NjMwOTU1MWY4N2Y2'
const API_KEY = '12ec1f9d113932575fc4b114a2f60ffd'
// import Worker from './transcode.worker.js'
// const transWorker = new Worker()
const transWorker = new Worker(new URL('./transcode.worker.js', import.meta.url))
console.log(transWorker)
let startTime = ''
let endTime = ''
function getWebSocketUrl() {
return new Promise((resolve, reject) => {
// 请求地址根据语种不同变化
let url = 'wss://iat-api.xfyun.cn/v2/iat'
const host = 'iat-api.xfyun.cn'
const apiKey = API_KEY
const apiSecret = API_SECRET
const date = new Date().toGMTString()
const algorithm = 'hmac-sha256'
const headers = 'host date request-line'
const signatureOrigin = `host: ${host}\ndate: ${date}\nGET /v2/iat HTTP/1.1`
const signatureSha = CryptoJS.HmacSHA256(signatureOrigin, apiSecret)
const signature = CryptoJS.enc.Base64.stringify(signatureSha)
const authorizationOrigin = `api_key="${apiKey}", algorithm="${algorithm}", headers="${headers}", signature="${signature}"`
const authorization = btoa(authorizationOrigin)
url = `${url}?authorization=${authorization}&date=${date}&host=${host}`
console.log(date)
resolve(url)
})
}
const IatRecorder = class {
constructor({ language, accent, appId } = {}) {
const 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) {
// console.log("构造方法中",self.audioData)
self.audioData.push(...event.data)
}
}
// 修改录音听写状态
setStatus(status) {
this.onWillStatusChange && this.status !== status && this.onWillStatusChange(this.status, status)
this.status = status
}
setResultText({ resultText, resultTextTemp } = {}) {
this.onTextChange && this.onTextChange(resultTextTemp || resultText || '')
resultText !== undefined && (this.resultText = resultText)
resultTextTemp !== undefined && (this.resultTextTemp = resultTextTemp)
}
// 修改听写参数
setParams({ language, accent } = {}) {
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) => {
this.recorderStop()
}
iatWS.onclose = (e) => {
console.log('关闭原因', e)
endTime = Date.parse(new Date())
console.log('持续时间', endTime - startTime)
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)
},
(e) => {
getMediaFail(e)
},
)
}
else {
if (navigator.userAgent.toLowerCase().match(/chrome/) && !location.origin.includes('https://'))
alert('chrome下获取浏览器录音功能因为安全性问题需要在localhost或127.0.0.1或https下才能获取权限')
else
alert('无法获取浏览器录音功能请升级浏览器或使用chrome')
this.audioContext && this.audioContext.close()
return
}
// 获取浏览器录音权限成功的回调
let getMediaSuccess = (stream) => {
// 创建一个用于通过JavaScript直接处理音频
this.scriptProcessor = this.audioContext.createScriptProcessor(0, 1, 1)
this.scriptProcessor.onaudioprocess = (e) => {
// 去处理音频数据
if (this.status === 'ing')
transWorker.postMessage(e.inputBuffer.getChannelData(0))
// this.audioData.push(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) => {
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) {
let binary = ''
const bytes = new Uint8Array(buffer)
const len = bytes.byteLength
for (let 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)
const params = {
common: {
app_id: this.appId,
},
business: {
language: this.language, // 小语种可在控制台--语音听写(流式)--方言/语种处添加试用
domain: 'iat',
accent: this.accent, // 中文方言可在控制台--语音听写(流式)--方言/语种处添加试用
},
data: {
status: 0,
format: 'audio/L16;rate=16000',
encoding: 'raw',
audio: this.toBase64(audioData),
},
}
console.log('参数language', this.language)
console.log('参数accent', this.accent)
this.webSocket.send(JSON.stringify(params))
startTime = Date.parse(new Date())
this.handlerInterval = setInterval(() => {
// websocket未连接
if (this.webSocket.readyState !== 1) {
console.log('websocket未连接', this.webSocket)
this.audioData = []
clearInterval(this.handlerInterval)
return
}
if (this.audioData.length === 0) {
console.log('自动关闭', this.status)
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) {
// 识别结束
const jsonData = JSON.parse(resultData)
if (jsonData.data && jsonData.data.result) {
const data = jsonData.data.result
let str = ''
const resultStr = ''
const ws = data.ws
for (let i = 0; i < ws.length; i++)
str = str + ws[i].cw[0].w
console.log('识别的结果为:', str)
// 开启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