Skip to content

Commit

Permalink
累积更新:统一日志输出格式,H5和App均提升iframe兼容性,重构ios微信jssdk调用逻辑
Browse files Browse the repository at this point in the history
  • Loading branch information
xiangyuecn committed Nov 17, 2020
1 parent 9bf5307 commit 168ec0d
Show file tree
Hide file tree
Showing 24 changed files with 735 additions and 221 deletions.
6 changes: 3 additions & 3 deletions app-support-sample/QuickStart.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@
<script>
/********将此处后端API地址改成你的地址即可开始测试**********/

/**【需修改】请使用自己的微信JsSDK签名接口、素材下载功能接口,不能用这个空的默认值,微信【强制】要【绑安全域名】,别的站用不了。
后端签名接口参考文档:微信JsSDK wx.config需使用到后端接口进行签名,文档: https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html 阅读:通过config接口注入权限验证配置、附录1-JS-SDK使用权限签名算法
后端素材下载接口参考文档: https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1444738727
/**【需修改】请使用自己的网站后端一个接口地址去实现:微信JsSDK签名、微信录音素材下载两个功能;不能用这个空的默认值,微信【强制】要【绑安全域名】,别的站用不了。此接口地址是用在ios-weixin-config.js中的,如果你要调整请求的参数、或者响应结果格式、或用两个接口地址,需要修改此js中对应的ajax调用
【微信文档】后端签名接口参考:微信JsSDK wx.config需使用到后端接口进行签名,文档: https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html 阅读:通过config接口注入权限验证配置、附录1-JS-SDK使用权限签名算法
【微信文档】后端素材下载接口参考: https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1444738727
**/
var PageSet_RecordAppWxApi="";
/*这个api接口:
Expand Down
13 changes: 10 additions & 3 deletions app-support-sample/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ function createDelayDialog(){

### 【Android】Hybrid App测试

[demo_android](https://github.com/xiangyuecn/Recorder/tree/master/app-support-sample/demo_android)目录内包含Android App测试源码,和核心文件 [RecordAppJsBridge.java](https://github.com/xiangyuecn/Recorder/blob/master/app-support-sample/demo_android/app/src/main/java/com/github/xianyuecn/recorder/RecordAppJsBridge.java) ,详细的原生实现、权限配置等请阅读这个目录内的README;目录内 [app-debug.apk.zip](https://xiangyuecn.gitee.io/recorder/app-support-sample/demo_android/app-debug.apk.zip) 为打包好的debug包(40kb,删掉.zip后缀),或者clone后自行用`Android Studio`编译打包。本demo为java代码,兼容API Level 15+,已测试Android 9.0。
[demo_android](https://github.com/xiangyuecn/Recorder/tree/master/app-support-sample/demo_android)目录内包含Android App测试源码,和核心文件 [RecordAppJsBridge.java](https://github.com/xiangyuecn/Recorder/blob/master/app-support-sample/demo_android/app/src/main/java/com/github/xianyuecn/recorder/RecordAppJsBridge.java) ,详细的原生实现、权限配置等请阅读这个目录内的README;目录内 [app-debug.apk.zip](https://gitee.com/xiangyuecn/Recorder/blob/master/app-support-sample/demo_android/app-debug.apk.zip) 为打包好的debug包(40kb,删掉.zip后缀),或者clone后自行用`Android Studio`编译打包。本demo为java代码,兼容API Level 15+,已测试Android 9.0。

### 【IOS微信】H5测试
[<img src="https://gitee.com/xiangyuecn/Recorder/raw/master/assets/demo-recordapp.png" width="100px">](https://jiebian.life/web/h5/github/recordapp.aspx) https://jiebian.life/web/h5/github/recordapp.aspx
Expand Down Expand Up @@ -270,8 +270,8 @@ IOS其他浏览器||

## 限制功能

- `IOS-Weixin`不支持实时回调,因此当在IOS微信上录音时,实时音量反馈、实时波形、实时转码等功能不会有效果;并且微信素材下载接口下载的amr音频音质勉强能听(总比没有好,自行实现时也许可以使用它的高清接口,不过需要服务器端转码)。
- `IOS-Weixin`使用的`微信JsSDK`单次调用录音最长为60秒,底层已屏蔽了这个限制,超时后会立即重启接续录音,因此当在IOS微信上录音时,超过60秒还未停止,将重启录音,中间可能会导致短暂的停顿感觉。
- `IOS-Weixin`不支持实时回调,因此当在IOS微信上录音时,实时音量反馈、实时波形、实时转码等功能不会有效果;并且微信素材下载接口下载的amr音频音质勉强能听(总比没有好,自行实现时也许可以使用它的高清接口,不过需要服务器端转码);amr原始的采样率为8000hz,如果设置的采样率高于8000,将会自动提升采样率到设置的值(如16000),但音质不可能会变好
- `IOS-Weixin`使用的`微信JsSDK`单次调用录音最长为60秒,底层已屏蔽了这个限制,超时后会立即重启接续录音,因此当在IOS微信上录音时,超过60秒还未停止,将重启微信JsSDK录音,中间可能会导致短暂的停顿感觉。
- `demo_ios`中swift代码使用的`AVAudioRecorder`来录音,由于录音数据是通过这个对象写入文件来获取的,可能是因为存在文件写入缓存的原因,数据并非实时的flush到文件的,因此实时发送给js的数据存在300ms左右的滞后;`AudioQueue``AudioUnit`之类的更强大的工具文章又少,代码又多,本质上是因为不会用,所以就成这样了。
- `Android WebView`本身是支持H5录音的(古董版本就算啦),仅需处理H5网页授权即可,但Android里面使用网页的录音问题可能比原生的录音要复杂,为了简化js端的复杂性(出问题了好甩锅),不管是Android还是IOS都实现一下可能会简单很多;另外Android和IOS的音频编码并非易事,且不易更新,使用js编码引擎大大简化App的逻辑;因此就有了Android版的Hybrid App Demo,如果想使用Android H5录音,请阅读Recorder首页文档中 `Android Hybrid App中录音示例` 这节来开启网页权限即可。

Expand Down Expand Up @@ -349,6 +349,8 @@ IOS-Weixin底层会把从微信素材下载过来的原始音频信息存储在s

`sampleRate` 缓冲PCM的采样率

注意:此方法会自动注入到top层window,如果是iframe并且跨域了,将无法进行注入,需要top层使用postMessage来转发数据给iframe,详细请看`app-native-support.js``addEventListener("message"...)`源码;示例App中已实现了对应的postMessage转发操作,集成示例代码即可正常使用。


## 【静态属性】RecordApp.UseLazyLoad
默认为`true`开启部分非核心组件的延迟加载,不会阻塞`Install``Install`后通过`RecordApp.Current.OnLazyReady`事件来确定组件是否已全部加载;如果设为`false`,将忽略组件的延迟加载属性,`Install`时会将所有组件一次性加载完成后才会`Install`成功。
Expand All @@ -372,6 +374,11 @@ IOS-Weixin底层会把从微信素材下载过来的原始音频信息存储在s

rec中的方法不一定都能使用,主要用来获取内部缓冲用的,比如:实时清理缓冲,当缓冲被清理,Stop时永远会走fail回调。

## 【静态方法】RecordApp.GetStopUsedRec()
获取底层平台录音结束时使用的用来转码音频的Recorder对象实例rec。在Stop成功回调时一定会返回rec对象,Stop回调前和Stop回调后均会返回null。除了微信平台外,其他平台返回的rec和GetStartUsedRecOrNull返回的是同一个对象;(注意如果微信平台的素材下载接口实现了服务器端转码,本方法始终会返回null,这种情况算是比较罕见的功能)。

rec中的方法不一定都能使用,主要用来获取内部缓冲用的,比如额外的格式转码或数据提取。

## 【静态属性】RecordApp.Platforms
支持的平台列表,目前有三个:
1. `Native`: 原生App平台支持,底层由实际的`JsBridge`提供,此平台默认未开启
Expand Down
4 changes: 2 additions & 2 deletions app-support-sample/demo_android/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

# :open_book:Android Hybrid App

目录内包含Android App测试源码,和核心文件 [RecordAppJsBridge.java](https://github.com/xiangyuecn/Recorder/blob/master/app-support-sample/demo_android/app/src/main/java/com/github/xianyuecn/recorder/RecordAppJsBridge.java) ;目录内 [app-debug.apk.zip](https://xiangyuecn.gitee.io/recorder/app-support-sample/demo_android/app-debug.apk.zip) 为打包好的debug包(40kb,删掉.zip后缀),或者clone后自行用`Android Studio`编译打包。本demo为java代码,兼容API Level 15+,已测试Android 9.0。
目录内包含Android App测试源码,和核心文件 [RecordAppJsBridge.java](https://github.com/xiangyuecn/Recorder/blob/master/app-support-sample/demo_android/app/src/main/java/com/github/xianyuecn/recorder/RecordAppJsBridge.java) ;目录内 [app-debug.apk.zip](https://gitee.com/xiangyuecn/Recorder/blob/master/app-support-sample/demo_android/app-debug.apk.zip) 为打包好的debug包(40kb,删掉.zip后缀),或者clone后自行用`Android Studio`编译打包。本demo为java代码,兼容API Level 15+,已测试Android 9.0。

本Demo是对[/app-support-sample/native-config.js](https://github.com/xiangyuecn/Recorder/blob/master/app-support-sample/native-config.js)配置示例中定义的JsBridge接口的实现。

Expand Down Expand Up @@ -30,7 +30,7 @@


## 数据交互
java收到js发起的`RecordAppJsBridge.request`请求,解析请求数据参数,并调用参数中接口对应的java方法,同步执行完后把数据返回给js,如果方法是异步的,将在异步操作完成后java将调用网页的js方法`AppJsBridgeRequest.Call`将数据异步返回。
java收到js发起的`RecordAppJsBridge.request`请求,解析请求数据参数,并调用参数中接口对应的java方法,同步执行完后把数据返回给js,如果方法是异步的,将在异步操作完成后java将调用网页的js方法`AppJsBridgeRequest.Call`将数据异步返回(如果是跨域的iframe中发起的异步调用,数据将会使用postMessage来转发返回)


## 录音接口
Expand Down
Binary file modified app-support-sample/demo_android/app-debug.apk.zip
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,20 @@ public void run() {
}
});
}
public void runScript_JsBridge(String commJs,String funCall, String postMessage){
//如果顶层window没有JsBridge的请求对象,就通过postMessage进行转发,可能是iframe跨域
runScript("(function(){\n"
+commJs
+"\n;if(window['"+JsRequestName+"']){\n"
+funCall
+"\n}else{"
+"\nvar iframes=document.querySelectorAll('iframe');"
+"\nfor(var i=0;i<iframes.length;i++){"
+"\niframes[i].contentWindow.postMessage("+postMessage+",'*')"
+"\n}"
+"\n}\n})()"
);
}



Expand Down Expand Up @@ -276,7 +290,12 @@ public void callback(Object val, String errOrNull){
__callback(false);
}
private void __callback(boolean isExecCall){
jsBridge.runScript(JsRequestName+".Call(" + json.toString() + ");");
jsBridge.runScript_JsBridge(
"var json="+ json.toString()+";"
+"var postMsg={type:'"+JsRequestName+"',action:'Call',data:json};"
, JsRequestName+".Call(json);"
, "postMsg"
);

if(!isExecCall && !isAsync){
jsBridge.Log.e(LogTag,action+"不是异步方法,但调用了回调");
Expand Down Expand Up @@ -666,7 +685,13 @@ private void readAsync(){
logStreamVal.write(data, 0, data.length);
}
sendCount++;
main.runScript(JsRequestName+".Record(\""+ Base64.encodeToString(data, Base64.NO_WRAP)+"\","+sampleRate+")");
main.runScript_JsBridge(
"var b64=\""+ Base64.encodeToString(data, Base64.NO_WRAP)+"\";"
+"var sampleRate="+sampleRate+";"
+"var postMsg={type:'"+JsRequestName+"',action:'Record',data:{pcmDataBase64:b64,sampleRate:sampleRate}};"
, JsRequestName+".Record(b64,sampleRate)"
, "postMsg"
);

if(!firstLog){
main.Log.i(LogTag, "获取到了第一段录音数据:len:"+data.length+" lenSrc:"+count+" bufferLen:"+bufferLen+" sampleRateReq:"+sampleRateReq+" sampleRateSrc:"+sampleRateSrc+" sampleRateCallback:"+sampleRate);
Expand Down
2 changes: 1 addition & 1 deletion app-support-sample/demo_ios/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@


## 数据交互
swift收到js发起的prompt弹框请求,解析弹框携带的数据参数,并调用参数中接口对应的swift方法,同步执行完后把数据返回给prompt弹框,如果方法是异步的,将在异步操作完成后swift将调用网页的js方法`AppJsBridgeRequest.Call`将数据异步返回。
swift收到js发起的prompt弹框请求,解析弹框携带的数据参数,并调用参数中接口对应的swift方法,同步执行完后把数据返回给prompt弹框,如果方法是异步的,将在异步操作完成后swift将调用网页的js方法`AppJsBridgeRequest.Call`将数据异步返回(如果是跨域的iframe中发起的异步调用,数据将会使用postMessage来转发返回)


## 录音接口
Expand Down
28 changes: 26 additions & 2 deletions app-support-sample/demo_ios/recorder/RecordAppJsBridge.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,20 @@ public class RecordAppJsBridge {
self.runScriptFn?(code);
}
}
public func runScript_JsBridge(_ commJs:String,_ funCall:String,_ postMessage:String){
//如果顶层window没有JsBridge的请求对象,就通过postMessage进行转发,可能是iframe跨域
runScript("(function(){\n" +
commJs +
"\n;if(window['" + RecordAppJsBridge.JsRequestName + "']){\n" +
funCall +
"\n}else{" +
"\nvar iframes=document.querySelectorAll('iframe');" +
"\nfor(var i=0;i<iframes.length;i++){" +
"\niframes[i].contentWindow.postMessage(" + postMessage + ",'*')" +
"\n}" +
"\n}\n})()"
);
}
public func close(){
Log=nil;
microphoneUsesPermissionFn=nil;
Expand Down Expand Up @@ -155,7 +169,12 @@ public class RecordAppJsBridge {
__callback(false);
}
private func __callback(_ isExecCall:Bool){
jsBridge?.runScript(RecordAppJsBridge.JsRequestName+".Call("+Code.ToJson(json)+")");
jsBridge?.runScript_JsBridge(
"var json=" + Code.ToJson(json) + ";" +
"var postMsg={type:'" + RecordAppJsBridge.JsRequestName + "',action:'Call',data:json};"
, RecordAppJsBridge.JsRequestName + ".Call(json);"
, "postMsg"
);

if !isExecCall && !isAsync {
jsBridge?.Log?.e(Request.LogTag,action+"不是异步方法,但调用了回调");
Expand Down Expand Up @@ -472,7 +491,12 @@ return [
readTotal+=d1.count;
duration=readTotal/(sampleRateSrc/1000)/2;
sendCount+=1;
main.runScript(RecordAppJsBridge.JsRequestName+".Record(\"\(d1.base64EncodedString())\",\(sampleRateSrc))");
main.runScript_JsBridge(
"var b64=\"" + d1.base64EncodedString() + "\";" +
"var sampleRate=\(sampleRateSrc);" +
"var postMsg={type:'" + RecordAppJsBridge.JsRequestName + "',action:'Record',data:{pcmDataBase64:b64,sampleRate:sampleRate}};"
, RecordAppJsBridge.JsRequestName + ".Record(b64,sampleRate)"
, "postMsg" );

if(!firstLog){
main.Log.i(RecordApis.LogTag, "获取到了第一段录音数据:len:\(d1.count) bufferLen:\(bufferLen) sampleRate:\(sampleRateSrc)");
Expand Down
Loading

0 comments on commit 168ec0d

Please sign in to comment.