赞
踩
终于折腾好了(使用FlashWavRecorder实现IE11浏览器录音后用科大讯飞转文字,我的是Vue.js项目),对一个前端仔来说,听到要兼容IE,都是不情愿的,但是需求来了,那就折腾起来吧。(主要这需求砍不了,菜刀没带)。但是经过这几天折腾,记录一下,不足之处,还望指教~
首先呢,明确下我们的目标,支持IE11浏览器录音后转文字。我们用逆向思维思考下,音频转文字。我们目前用的是科大讯飞的SDK。前端的话,把我们拿到的音频数据,包装好,调用一下后端给的接口,然后后端调讯飞SDK,转化为文字,发回来给我们。所以我们最主要是拿到音频数据,即录音。那我们难点是IE11上录音,因为IE 不支持 navigator.mediaDevices.getUserMedia这样的API。这里用JS调用flash去实现IE上录音。(应该还可以调用ActiveX,没尝试)。
这里在github找到两个可以运行起来的demo。传送门:recorder.js,FlashWavRecorder
这两个提供的example 都可以正常跑起来。recorder.js音质比较好,第二个杂音大点。但recorder.js不知道怎么取到录音后的音频数据,照理说,上传都可以实现了,拿到音频数据,理应可以。或许是我的能力问题,还请指点。FlashWavRecorder 有getBase64的方法,所以就选用FlashWavRecorder 了。
调用别人的SDK,就要符合别人的游戏规则。如下摘自科大讯飞接入规则
在调用所有业务接口时,都需要在 Http Request Header 中配置以下参数用于授权认证:
参数 | 格式 | 说明 | 必须 |
---|---|---|---|
X-Appid | string | 讯飞开放平台注册申请应用的应用ID(appid) | 是 |
X-CurTime | string | 当前UTC时间戳,从1970年1月1日0点0 分0 秒开始到现在的秒数 | 是 |
X-Param | string | 相关参数JSON串经Base64编码后的字符串,见各接口详细说明 | 是 |
X-CheckSum | string | 令牌,计算方法:MD5(apiKey + curTime + param),三个值拼接的字符串,进行MD5哈希计算(32位小写),其中apiKey由讯飞提供,调用方管理。 | 是 |
讯飞demo中有这个字段 文档没写
body | string | dataUrl |
注:
checkSum 生成示例:
- String apiKey="abcd1234";
- String curTime="1502607694";
- String param="eyAiYXVmIjogImF1ZGlvL0wxNjtyYXR...";
- String checkSum=MD5(apiKey+curTime+param);
Copy
在调用所有业务接口时,授权认证通过后,服务端会检查调用方ip是否在讯飞开放平台配置的ip白名单中,对于没有配置到白名单中的IP发来的请求,服务端会拒绝服务。 注:
- {
- "code":"10105",
- "desc":"illegal access|illegal client_ip",
- "data":"",
- "sid":"xxxxxx"
- }
我们请求后端接口类似这样:
- that.$http.postObj('restful/voice/recognition', {
- 'body': sendurl,
- 'X-Appid': that.appid,
- 'X-CurTime': time,
- 'X-Param': xParam,
- 'X-CheckSum': xChecksum
- }).then((res) => {
- //成功识别语音后,在这里拿到文字 eg:res.data.data
- console.log(res)
- }
- }).catch((err) => {
- console.log(err)
- });
- //basic.js
-
- $(function () {
- //这里的$(function () {}) 原生写法是function DOMContentLoaded(){};
- //document.addEventListener('DOMContentLoaded', DOMContentLoaded, false);
-
- var RECORDER_APP_ID = "recorderApp";
- var $level = $('.level .progress');
- var appWidth = 24;
- var appHeight = 24;
- var flashvars = {'upload_image': '../images/upload.png'};
- var params = {};
- var attributes = {'id': RECORDER_APP_ID, 'name': RECORDER_APP_ID};
- swfobject.embedSWF("../recorder.swf", "flashcontent", appWidth, appHeight, "11.0.0", "", flashvars, params, attributes);
- //注意这句,要确保swfobject.js 已经加载好了 要准备个容器不然报错 <div id="flashcontent" />
- //这里使用 document.getElementById(RECORDER_APP_ID).style.top='-3333px'
- // 因为把swf对象设置为display:none 或者visibility:hidden 都是使用不了
- window.fwr_event_handler = function fwr_event_handler() {//flash插件相关状态反馈
- var name, $controls;
- switch (arguments[0]) {
- case "ready":
- FWRecorder.uploadFormId = "#uploadForm";
- FWRecorder.uploadFieldName = "upload_file[filename]";
- FWRecorder.connect(RECORDER_APP_ID, 0);//如果老是报 recoder is null 这里没有调进来
- FWRecorder.recorderOriginalWidth = appWidth;
- FWRecorder.recorderOriginalHeight = appHeight;
- break;
-
- case "permission_panel_closed":
- FWRecorder.recorderOriginalWidth = 0;
- FWRecorder.recorderOriginalHeight = 0;
- document.getElementById(RECORDER_APP_ID).style.top='-3333px'//隐藏白白一块
- FWRecorder.defaultSize();
- break;
- case "microphone_user_request":
- FWRecorder.showPermissionWindow();//这里请求后 会弹出 是否 允许 使用麦克风的请求
- break;
-
- ....这里省略局部代码
- case "microphone_level"://这里用于显示声波。如果成功接入麦克风,录音的时候会一直调用
- $level.css({height: arguments[1] * 100 + '%'});
- break;
-
- ....这里省略局部代码
- };
- .......这里省略局部代码
- });
以上主要是理解swfobject.embedSWF的用法。这里有详细的传送门: swfobject.js使用
- //recoder.js
-
-
- (function(global) {
- var Recorder;
-
- var RECORDED_AUDIO_TYPE = "audio/wav";
-
- Recorder = {
- recorder: null,
- recorderOriginalWidth: 0,
- recorderOriginalHeight: 0,
- uploadFormId: null,
- uploadFieldName: null,
- isReady: false,
-
- connect: function(name, attempts) {//连接麦克风 主要是init recorder
- if(navigator.appName.indexOf("Microsoft") != -1) {
- Recorder.recorder = window[name];
- } else {
- Recorder.recorder = document[name];//swfObject对象 ,这里的 name='recorderApp'
- }
-
- if(attempts >= 40) {
- return;
- }
-
- // flash app needs time to load and initialize
- if(Recorder.recorder && Recorder.recorder.init) {
- Recorder.recorderOriginalWidth = Recorder.recorder.width;
- Recorder.recorderOriginalHeight = Recorder.recorder.height;
- if(Recorder.uploadFormId && $) {
- var frm = $(Recorder.uploadFormId);
- Recorder.recorder.init(frm.attr('action').toString(), Recorder.uploadFieldName, frm.serializeArray());
- }
- return;
- }
-
- setTimeout(function() {Recorder.connect(name, attempts+1);}, 100);
- },
-
- playBack: function(name) {//播放
- // TODO: Rename to `playback`
- Recorder.recorder.playBack(name);
- },
-
- stopRecording: function() {//停止录音
- Recorder.recorder.stopRecording();
- },
-
- resize: function(width, height) {//设置 麦克风权限请求框大小
- Recorder.recorder.width = width + "px";
- Recorder.recorder.height = height + "px";
- },
-
- defaultSize: function() {
- Recorder.resize(Recorder.recorderOriginalWidth, Recorder.recorderOriginalHeight);
- },
-
- show: function() {
- Recorder.recorder.show();
- },
-
- hide: function() {
- Recorder.recorder.hide();
- },
-
- getBase64: function(name) {//来获取base64数据 如果需要转回来 window.atob("base64")
- var data = Recorder.recorder.getBase64(name);
- return 'data:' + RECORDED_AUDIO_TYPE + ';base64,' + data;
- },
-
- getBlob: function(name) {//这里转化为blod 后面讯飞转文字的时候要用到。当然可以自己转
- var base64Data = Recorder.getBase64(name).split(',')[1];
- return base64toBlob(base64Data, RECORDED_AUDIO_TYPE);
- },
- showPermissionWindow: function(options) {
- Recorder.resize(240, 160);
- // need to wait until app is resized before displaying permissions screen
- var permissionCommand = function() {
- if (options && options.permanent) {
- Recorder.recorder.permitPermanently();
- } else {
- Recorder.recorder.permit();
- }
- };
- setTimeout(permissionCommand, 1);
- },
- };
-
- function base64toBlob(b64Data, contentType, sliceSize) {
- contentType = contentType || '';
- sliceSize = sliceSize || 512;
-
- var byteCharacters = atob(b64Data);
- var byteArrays = [];
-
- for (var offset = 0; offset < byteCharacters.length; offset += sliceSize) {
- var slice = byteCharacters.slice(offset, offset + sliceSize);
-
- var byteNumbers = new Array(slice.length);
- for (var i = 0; i < slice.length; i++) {
- byteNumbers[i] = slice.charCodeAt(i);
- }
-
- var byteArray = new Uint8Array(byteNumbers);
- byteArrays.push(byteArray);
- }
-
- return new Blob(byteArrays, {type: contentType});
- }
-
- global.FWRecorder = Recorder;
-
-
- })(this);
然后写几个button 接入就好了。 大家可以动手试一试。
注意:如果是在vue.js中当成模块的时候,ES6 的模块自动采用严格模式,不管你有没有在模块头部加上"use strict";
不能使用arguments.callee;不能使用arguments.caller;禁止this指向全局对象;不能使用fn.caller和fn.arguments获取函数调用的堆栈 等等 所以引入swfobject.js 的时候 要把 arguments.callee 替换掉。 传送门:严格模式下arguments.callee代替思路
- 例如有用arguments.callee的函数里,
-
- function (){if(J){return}try{j.documentElement.doScroll("left")}catch(X){setTimeout(arguments.callee,0);
-
-
- 把匿名函数 改为具名函数 ? swfobject.js 里面好像有7处需要改
-
- function fn(){if(J){return}try{j.documentElement.doScroll("left")}catch(X){setTimeout(fn,0);
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。