不少時候在看文章的時候都會有自動朗讀文章內容的功能,那麼這種功能如何在h5上是怎麼實現的呢,下面就拿我司一個基本需求做爲線索,看是怎麼一步一步實現的
需求提出前端
通過我司產品經理的想法,作出以下功能
1.自動朗讀當前h5頁面文章node
競品——》ios
調研發現,競品h5是app原生實現,而我司都是h5實現文章閱讀,因此開始進行h5的調研axios
對接科大訊飛在線語音合成api
調研發現科大訊飛的在線語音合成能夠基本提供相應功能,決定作一個demo來測試效果
1.控制檯開通權限緩存
2.閱讀文檔服務器
具體代碼以下app
import axios from 'axios' import * as md5 from './md5' axios.defaults.withCredentials = true let Appid = 'xxxxx' let apiKey = 'xxxxxx' let CurTime = Date.parse(new Date()) / 1000 let param = { auf: 'audio/L16;rate=16000', aue: 'lame', voice_name: 'xiaoyan', speed: '50', volume: '50', pitch: '50', engine_type: 'intp65', text_type: 'text' } let Base64 = { encode: (str) => { return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function toSolidBytes(match, p1) { return String.fromCharCode('0x' + p1); })); }, decode: (str) => { // Going backwards: from bytestream, to percent-encoding, to original string. return decodeURIComponent(atob(str).split('').map(function (c) { return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2); }).join('')); } } let xp = Base64.encode(JSON.stringify(param)) let CheckSum = md5.hex_md5(apiKey + CurTime + xp) let headers = { 'X-Appid': Appid, 'X-CurTime': CurTime, 'X-Param': xp, 'X-CheckSum': CheckSum, 'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8' } export function getAloud (text) { // let data = { // text: encodeURI(text) // } var formdata = new FormData() formdata.append('text', text) return axios({ baseURL: window.location.href.includes('demo') ? 'https://api.xfyun.cn' : '/tts', method: 'POST', url: '/v1/service/v1/tts', headers: { ...headers }, data: formdata }) }
通過測試,是返回二進制文件流了可是前端試了各類辦法沒有實現流的播放
node中間層koa
引入node中間層是考慮到文件能夠存儲,能夠放到cdn上進行緩存,能夠減小類似文章的請求科大訊飛接口,能夠減小流量的產生,因此決定加入node中間層
ps:考拉閱讀有node服務器做爲一些中間層的處理。主要技術棧是node + koa2 + pm2async
const md5 = require('../lib/md5.js') const fs = require('fs') const path = require('path') const marked = require('marked') const request = require('request') let Appid = '' let apiKey = '' let CurTime let param = { auf: 'audio/L16;rate=16000', aue: 'lame', voice_name: 'x_yiping', speed: '40', volume: '50', pitch: '50', engine_type: 'intp65', text_type: 'text' } var b = new Buffer(JSON.stringify(param)); let xp = b.toString('base64') let CheckSum let headers exports.getAloud = async ctx => { CurTime = Date.parse(new Date()) / 1000 CheckSum = md5.hex_md5(apiKey + CurTime + xp) headers = { 'X-Appid': Appid, 'X-CurTime': CurTime, 'X-Param': xp, 'X-CheckSum': CheckSum, 'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8' } let id = ctx.request.body.id let text = ctx.request.body.text console.log(ctx.query) var postData = { text: text } let r = request({ url: 'http://api.xfyun.cn/v1/service/v1/tts', // 請求的URL method: 'POST', // 請求方法 headers: headers, formData: postData }, function (error, response, body) { // console.log('error:', error); // Print the error if one occurred // console.log('statusCode:', response && response.statusCode); // Print the response status code if a response was received // console.log('body:', body); // Print the HTML for the Google homepage. }) await new Promise((resolve, reject) => { let filePath = path.join(__dirname, 'public/') + `/${id}.mp3` const upStream = fs.createWriteStream(filePath) r.pipe(upStream) upStream.on('close', () => { console.log('download finished'); resolve() }); }) .then((res) => { ctx.body = { code: 200, message: '語音合成成功', data: { url: 'https://fe.koalareading.com/file/' + id + '.mp3' } } }) }
主要運用request的管道流概念
把後臺返回的二進制文件導入到流裏面,在寫入到文件裏面
最後返回一個url給前端播放使用
此致,測試
//返回url。相同文章惟一id區分,能夠緩存使用 https://fe.koalareading.com/file/1112.mp3
需求demo完成