查看科大訊飛語音聽寫demo即可看見最新的js demo
,適用於前端。直接下載即可食用。
貼上主要代碼:
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<link href="index.css" type="text/css" rel="stylesheet"></link>
</head>
<body>
<div>
<div class="service-item service-item-taste">
<h2 class="service-item-title">語音識別</h2>
<div class="service-item-content service-item-taste-content">
<div class="taste-content">
<button class="taste-button ready-button" id="taste_button">開始識別</button>
</div>
<div class="start-taste">
<div class="start-taste-left">
<div class="time-box">
<span class="start-taste-line">
<hr class="hr hr1">
<hr class="hr hr2">
<hr class="hr hr3">
<hr class="hr hr4">
<hr class="hr hr5">
<hr class="hr hr6">
<hr class="hr hr7">
<hr class="hr hr8">
<hr class="hr hr9">
<hr class="hr hr10">
</span>
<span class="total-time"><span class="used-time">00: 00</span> / 01: 00</span>
</span>
</div>
<div class="start-taste-button">
<button class="taste-button start-button">結束識別</button>
</div>
</div>
<div class="output-box" id="result_output"></div>
</div>
</div>
</div>
</div>
<script src="hmac-sha256.js"></script>
<script src="enc-base64-min.js"></script>
<script src="jquery.js"></script>
<script src="index.js"></script>
</body>
</html>
index.js
/**
* Created by lycheng on 2019/8/1.
*
* 語音聽寫流式 WebAPI 接口調用示例 接口文檔(必看):https://doc.xfyun.cn/rest_api/語音聽寫(流式版).html
* webapi 聽寫服務參考帖子(必看):http://bbs.xfyun.cn/forum.php?mod=viewthread&tid=38947&extra=
* 語音聽寫流式WebAPI 服務,熱詞使用方式:登陸開放平臺https://www.xfyun.cn/後,找到控制檯--我的應用---語音聽寫---個性化熱詞,上傳熱詞
* 注意:熱詞只能在識別的時候會增加熱詞的識別權重,需要注意的是增加相應詞條的識別率,但並不是絕對的,具體效果以您測試爲準。
* 錯誤碼鏈接:
* https://www.xfyun.cn/doc/asr/voicedictation/API.html#%E9%94%99%E8%AF%AF%E7%A0%81
* https://www.xfyun.cn/document/error-code (code返回錯誤碼時必看)
* 語音聽寫流式WebAPI 服務,方言或小語種試用方法:登陸開放平臺https://www.xfyun.cn/後,在控制檯--語音聽寫(流式)--方言/語種處添加
* 添加後會顯示該方言/語種的參數值
*
*/
// 音頻轉碼worker
let recorderWorker = new Worker('./transformpcm.worker.js')
// 記錄處理的緩存音頻
let buffer = []
let AudioContext = window.AudioContext || window.webkitAudioContext
let notSupportTip = '請試用chrome瀏覽器且域名爲localhost或127.0.0.1測試'
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia
recorderWorker.onmessage = function(e) {
buffer.push(...e.data.buffer)
}
class IatRecorder {
constructor(config) {
this.config = config
this.state = 'ing'
this.language = config.language || 'zh_cn'
this.accent = config.accent || 'mandarin'
//以下信息在控制檯-我的應用-語音聽寫(流式版)頁面獲取
this.appId = '5d429130'
this.apiKey = 'e8e58c25fe7fcb3fd19a9595761654c8'
this.apiSecret = 'a23d4ed0f9e42147479fe1a5060060d9'
}
start() {
this.stop()
if (navigator.getUserMedia && AudioContext) {
this.state = 'ing'
if (!this.recorder) {
var context = new AudioContext()
this.context = context
this.recorder = context.createScriptProcessor(0, 1, 1)
var getMediaSuccess = (stream) => {
var mediaStream = this.context.createMediaStreamSource(stream)
this.mediaStream = mediaStream
this.recorder.onaudioprocess = (e) => {
this.sendData(e.inputBuffer.getChannelData(0))
}
this.connectWebsocket()
}
var getMediaFail = (e) => {
this.recorder = null
this.mediaStream = null
this.context = null
console.log('請求麥克風失敗')
}
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
navigator.mediaDevices.getUserMedia({
audio: true,
video: false
}).then((stream) => {
getMediaSuccess(stream)
}).catch((e) => {
getMediaFail(e)
})
} else {
navigator.getUserMedia({
audio: true,
video: false
}, (stream) => {
getMediaSuccess(stream)
}, function(e) {
getMediaFail(e)
})
}
} else {
this.connectWebsocket()
}
} else {
var isChrome = navigator.userAgent.toLowerCase().match(/chrome/)
alert(notSupportTip)
}
}
stop() {
this.state = 'end'
try {
this.mediaStream.disconnect(this.recorder)
this.recorder.disconnect()
} catch (e) {}
}
sendData(buffer) {
recorderWorker.postMessage({
command: 'transform',
buffer: buffer
})
}
connectWebsocket() {
var url = 'wss://iat-api.xfyun.cn/v2/iat'
var host = 'iat-api.xfyun.cn'
var apiKey = this.apiKey
var apiSecret = this.apiSecret
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 = CryptoJS.HmacSHA256(signatureOrigin, apiSecret)
var signature = CryptoJS.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}`
if ('WebSocket' in window) {
this.ws = new WebSocket(url)
} else if ('MozWebSocket' in window) {
this.ws = new MozWebSocket(url)
} else {
alert(notSupportTip)
return null
}
this.ws.onopen = (e) => {
this.mediaStream.connect(this.recorder)
this.recorder.connect(this.context.destination)
setTimeout(() => {
this.wsOpened(e)
}, 500)
this.config.onStart && this.config.onStart(e)
}
this.ws.onmessage = (e) => {
this.config.onMessage && this.config.onMessage(e)
this.wsOnMessage(e)
}
this.ws.onerror = (e) => {
this.stop()
this.config.onError && this.config.onError(e)
}
this.ws.onclose = (e) => {
this.stop()
this.config.onClose && this.config.onClose(e)
}
}
wsOpened() {
if (this.ws.readyState !== 1) {
return
}
var audioData = buffer.splice(0, 1280)
console.log('audioData', audioData.length)
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.ArrayBufferToBase64(audioData)
}
}
this.ws.send(JSON.stringify(params))
this.handlerInterval = setInterval(() => {
// websocket未連接
if (this.ws.readyState !== 1) {
clearInterval(this.handlerInterval)
return
}
if (buffer.length === 0) {
if (this.state === 'end') {
this.ws.send(JSON.stringify({
'data': {
'status': 2,
'format': 'audio/L16;rate=16000',
'encoding': 'raw',
'audio': ''
}
}))
clearInterval(this.handlerInterval)
}
return false
}
audioData = buffer.splice(0, 1280)
// 中間幀
this.ws.send(JSON.stringify({
'data': {
'status': 1,
'format': 'audio/L16;rate=16000',
'encoding': 'raw',
'audio': this.ArrayBufferToBase64(audioData)
}
}))
}, 40)
}
wsOnMessage(e) {
let jsonData = JSON.parse(e.data)
// 識別結束
if (jsonData.code === 0 && jsonData.data.status === 2) {
this.ws.close()
}
if (jsonData.code !== 0) {
this.ws.close()
console.log(`${jsonData.code}:${jsonData.message}`)
}
}
ArrayBufferToBase64(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)
}
}
class IatTaste {
constructor() {
var iatRecorder = new IatRecorder({
onClose: () => {
this.stop()
this.reset()
},
onError: (data) => {
this.stop()
this.reset()
alert('WebSocket連接失敗')
},
onMessage: (e) => {
let str = ''
let jsonData = JSON.parse(e.data)
if (jsonData.data && jsonData.data.result) {
this.setResult(jsonData.data.result)
}
},
onStart: () => {
$('hr').addClass('hr')
var dialect = $('.dialect-select').find('option:selected').text()
$('.taste-content').css('display', 'none')
$('.start-taste').addClass('flex-display-1')
$('.dialect-select').css('display', 'none')
$('.start-button').text('結束識別')
$('.time-box').addClass('flex-display-1')
$('.dialect').text(dialect).css('display', 'inline-block')
this.counterDown($('.used-time'))
}
})
this.iatRecorder = iatRecorder
this.counterDownDOM = $('.used-time')
this.counterDownTime = 0
this.text = {
start: '開始識別',
stop: '結束識別'
}
this.resultText = ''
}
start() {
this.iatRecorder.start()
}
stop() {
$('hr').removeClass('hr')
this.iatRecorder.stop()
}
reset() {
this.counterDownTime = 0
clearTimeout(this.counterDownTimeout)
buffer = []
$('.time-box').removeClass('flex-display-1').css('display', 'none')
$('.start-button').text(this.text.start)
$('.dialect').css('display', 'none')
$('.dialect-select').css('display', 'inline-block')
}
init() {
let self = this
$('#taste_button').click(function() {
if (navigator.getUserMedia && AudioContext && recorderWorker) {
self.start()
} else {
alert(notSupportTip)
}
})
$('.start-button').click(function() {
if ($(this).text() === self.text.start) {
$('#result_output').text('')
self.resultText = ''
self.start()
} else {
self.reset()
self.stop()
}
})
}
setResult(data) {
var str = ''
var 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 === 'apd') {
this.resultText = $('#result_output').text()
}
resultStr = this.resultText + str
$('#result_output').text(resultStr)
}
counterDown() {
if (this.counterDownTime === 60) {
this.counterDownDOM.text('01: 00')
this.stop()
} else if (this.counterDownTime > 60) {
this.reset()
return false
} else if (this.counterDownTime >= 0 && this.counterDownTime < 10) {
this.counterDownDOM.text('00: 0' + this.counterDownTime)
} else {
this.counterDownDOM.text('00: ' + this.counterDownTime)
}
this.counterDownTime++
this.counterDownTimeout = setTimeout(() => {
this.counterDown()
}, 1000)
}
}
var iatTaste = new IatTaste()
iatTaste.init()
後期準備修改一下,適用於自己的風格。