今天有个需求,想用一下文本转语音的功能。转换接口返回给我们的音频是一个字符串,因此需要做一些转换才能使用。这里记录下。
播放 接口返回的是一个base64加密的字符串,因此我们需要先对其做解密,然后将其转为Blob对象(Binary Large Object,在Web领域,Blob被定义为包含只读数据的类文件对象),赋值给audio标签的src属性。
这是浏览器端执行的代码(我从MDN找了base64转ArrayBuffer的函数来使用):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 <audio src ="" id ="audio" controls ="controls" > wav</audio > <script > function b64ToUint6 (nChr) { return nChr > 64 && nChr < 91 ? nChr - 65 : nChr > 96 && nChr < 123 ? nChr - 71 : nChr > 47 && nChr < 58 ? nChr + 4 : nChr === 43 ? 62 : nChr === 47 ? 63 : 0 ; } function base64DecToArr (sBase64, nBlockSize) { var sB64Enc = sBase64.replace (/[^A-Za-z0-9\+\/]/g , "" ), nInLen = sB64Enc.length , nOutLen = nBlockSize ? Math .ceil ((nInLen * 3 + 1 >>> 2 ) / nBlockSize) * nBlockSize : nInLen * 3 + 1 >>> 2 , aBytes = new Uint8Array (nOutLen); for (var nMod3, nMod4, nUint24 = 0 , nOutIdx = 0 , nInIdx = 0 ; nInIdx < nInLen; nInIdx++) { nMod4 = nInIdx & 3 ; nUint24 |= b64ToUint6 (sB64Enc.charCodeAt (nInIdx)) << 18 - 6 * nMod4; if (nMod4 === 3 || nInLen - nInIdx === 1 ) { for (nMod3 = 0 ; nMod3 < 3 && nOutIdx < nOutLen; nMod3++, nOutIdx++) { aBytes[nOutIdx] = nUint24 >>> (16 >>> nMod3 & 24 ) & 255 ; } nUint24 = 0 ; } } return aBytes; } let str = '这是base64字符串,太长了,我就省略了' ;var myBuffer = base64DecToArr (str).buffer var binaryData = [];binaryData.push (myBuffer); let url = window .URL .createObjectURL (new Blob (binaryData, {type : "audio/x-wav" }))document .getElementById ('audio' ).src = url;</script >
存储 我们在做Demo的时候,直接引入一个.wav的文件会比引入长长的数据字符串要方便得多,因此我找了一下将音频数据写入文件的方法。同样会用到上面的将base64字符串转为ArrayBuffer的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 var fs = require ('fs' ); var request = require ('request' ); let contents = [ '并购重组可视化的动画演示' , 'ST珠江通过定向增发的方式,横向收购京粮股份,最终京粮股份借壳上市。' ]; contents.forEach (async (rawContent) => { let content = encodeURI (rawContent); let appId = '应用的ID' ; let appKey = '应用的秘钥' ; let url = `http://this.is.url/?app_id=${appId} &app_key=${appKey} &voice_type=1&samplint_rate=0&audio_type=1&speed_rate=100&pitch_rate=0&engine_type=0&text=${content} ` ; await translate (url, rawContent + '.mp3' ) })async function translate (url, fileName ) { request ({ url : url, method : "GET" , headers : { "content-type" : "application/json" , }, }, async function (error, response, body ) { if (!error && response.statusCode == 200 ) { let bodyObj = JSON .parse (body); let audioObj = JSON .parse (bodyObj.data ) await saveToWavFile (fileName, audioObj.audio_data ) } else { console .log (error); } }); }async function saveToWavFile (path, base64Str ) { let data = base64DecToArr (base64Str); await fs.writeFile (path, data, async (err) => { if (err) { console .log ('Write failed' ) console .log (err) } else { console .log (`Write to file ${path} successfully` ) } }) }function b64ToUint6 (nChr ) { return nChr > 64 && nChr < 91 ? nChr - 65 : nChr > 96 && nChr < 123 ? nChr - 71 : nChr > 47 && nChr < 58 ? nChr + 4 : nChr === 43 ? 62 : nChr === 47 ? 63 : 0 ; }function base64DecToArr (sBase64, nBlockSize ) { var sB64Enc = sBase64.replace (/[^A-Za-z0-9\+\/]/g , "" ), nInLen = sB64Enc.length , nOutLen = nBlockSize ? Math .ceil ((nInLen * 3 + 1 >>> 2 ) / nBlockSize) * nBlockSize : nInLen * 3 + 1 >>> 2 , aBytes = new Uint8Array (nOutLen); for (var nMod3, nMod4, nUint24 = 0 , nOutIdx = 0 , nInIdx = 0 ; nInIdx < nInLen; nInIdx++) { nMod4 = nInIdx & 3 ; nUint24 |= b64ToUint6 (sB64Enc.charCodeAt (nInIdx)) << 18 - 6 * nMod4; if (nMod4 === 3 || nInLen - nInIdx === 1 ) { for (nMod3 = 0 ; nMod3 < 3 && nOutIdx < nOutLen; nMod3++, nOutIdx++) { aBytes[nOutIdx] = nUint24 >>> (16 >>> nMod3 & 24 ) & 255 ; } nUint24 = 0 ; } } return aBytes; }
由于只是临时用一下,所以代码就写得比较随意了。
这是服务端通过node运行的代码,注意里面不再需要将数据转为Blob对象(服务端默认也是没有Blob这个类的),直接将二进制数组写入文件即可。
另外,请求中的中文,记得用encodeURI转一下,否则会报错。
自动播放 移动设备,特别是iOS上,是不允许自动播放音频的,这是出于用户体验的考虑而进行的一个浏览器规范设计。
那么如果我们想要在移动设备上自动播放音频该怎么办呢?
这种情况我们可以考虑通过一些交互,比如一个开屏页,引导用户先去点一下页面,触发交互动作,然后再播放音频。
如何延迟播放音频 如果需要延迟播放音频,仅靠上面的处理还不够,因为直接在setTimeout中触发audio的play()方法,是无法实现播放的。因此我们在用户交互的时候,先触发play(),然后立即暂停,等到真正需要播放的时间到了,再重新触发play():
1 2 3 4 5 6 7 8 9 document .getElementById ('click' ).onclick = function ( ) { document .getElementById ('myaudio' ).play () document .getElementById ('myaudio' ).pause () setTimeout (function ( ) { console .log ('start playing' ) document .getElementById ('myaudio' ).play () }, 2000 ) }