Skip to content

Commit

Permalink
找到Android WebView中按住录音touch事件被打断的原因,已彻底解决 xiangyuecn#46
Browse files Browse the repository at this point in the history
  • Loading branch information
xiangyuecn committed Sep 6, 2019
1 parent 8b91ce7 commit f3ee77f
Show file tree
Hide file tree
Showing 7 changed files with 27 additions and 22 deletions.
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -212,8 +212,6 @@ IOS其他浏览器||

*2019-07-22*[#34](https://github.com/xiangyuecn/Recorder/issues/34)反馈研究后发现,问题一:macOS、IOS的Safari对连续调用录音(中途未调用close)是有问题的,但只要调用close后再重复录音就没有问题。问题二:IOS上如果录音之前先播放了任何Audio,录音过程将会变得很诡异,但如果先录音,就不存在此问题。chrome、firefox正常的很。目测这两个问题是非我等屌丝能够解决的,于是报告给苹果家程序员看看,因此发了个[帖子](https://forums.developer.apple.com/message/373108),顺手在`Feedback Assistant`提交了`bug report`,但好几天过去了没有任何回应(顺带给微软一个好评)。

*2019-09-05* 已知 [#46](https://github.com/xiangyuecn/Recorder/issues/46) `Android WebView`内调用getUserMedia方法发起授权请求时会打断touch事件,表现为触发了`touchcancel`事件,`touchend`事件始终不会触发,在做按住录音功能时应留意此问题。虽然`open`后一直不调用`close`可以规避此问题,但不建议这样做,每次录音后调用`close`兼容性更好,也更加友好;可以在每次`按住录音按钮`被按之前都重新进行一次`open`调用;或者Android Hybrid App中用RecordApp开启Native原生录音来避免此问题,因为App可以做到请求到权限后,后续录音不会再去进行权限请求。




Expand Down Expand Up @@ -481,6 +479,9 @@ public void onPermissionRequest(PermissionRequest request) {
如果不出意外,App内显示的网页就能正常录音了。

### 备忘小插曲
排查 [#46](https://github.com/xiangyuecn/Recorder/issues/46) `Android WebView`内长按录音不能收到`touchend`问题时,发现touch事件会被打断,反复折腾,最终发现是每次检测权限都会调用`Activity.requestPermissions`,而`requestPermissions`会造成WebView打断touch事件,进而产生H5、AppNative原生录都会产生此问题;最后老实把精简掉的`checkSelfPermission`加上检测一下是否已授权,就没有此问题了,囧。

### 附带测试项目
[app-support-sample/demo_android](https://github.com/xiangyuecn/Recorder/tree/master/app-support-sample/demo_android)目录中提供了Android测试源码(如果不想自己打包可以用打包好的apk来测试,文件名为`app-debug.apk.zip`,自行去掉.zip后缀)。

Expand Down
3 changes: 0 additions & 3 deletions app-support-sample/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,6 @@ IOS其他浏览器||
- `IOS-Weixin`使用的`微信JsSDK`单次调用录音最长为60秒,底层已屏蔽了这个限制,超时后会立即重启接续录音,因此当在IOS微信上录音时,超过60秒还未停止,将重启录音,中间可能会导致短暂的停顿感觉。
- `demo_ios`中swift代码使用的`AVAudioRecorder`来录音,由于录音数据是通过这个对象写入文件来获取的,可能是因为存在文件写入缓存的原因,数据并非实时的flush到文件的,因此实时发送给js的数据存在300ms左右的滞后;`AudioQueue``AudioUnit`之类的更强大的工具文章又少,代码又多,本质上是因为不会用,所以就成这样了。
- `Android WebView`本身是支持录音的(古董版本就算啦),仅需处理网页授权即可,但Android里面使用网页的录音权限问题可能比原生的权限机制要复杂,为了简化js端的复杂性(出问题了好甩锅),不管是Android还是IOS都实现一下可能会简单很多;另外Android和IOS的音频编码并非易事,且不易更新,使用js编码引擎大大简化App的逻辑;因此就有了Android版的Hybrid App Demo。
- 已知 [#46](https://github.com/xiangyuecn/Recorder/issues/46) `Android WebView`内发起授权请求时(如:H5的getUserMedia方法调用、App的权限请求)会打断touch事件,表现为触发了`touchcancel`事件,`touchend`事件始终不会触发,在做按住录音功能时应留意此问题。如果你要使用按住录音功能,解决办法:在每次`按住录音按钮`被按之前都重新进行一次`RequestPermission`调用;或者Android Hybrid App中用RecordApp开启Native原生录音来避免此问题,因为App可以做到请求到权限后,后续录音不会再去进行权限请求。



Expand All @@ -177,8 +176,6 @@ IOS其他浏览器||

`fail`: `fn(errMsg,isUserNotAllow)` 没有权限或者不能录音时回调,如果是用户主动拒绝的录音权限,除了有错误消息外,`isUserNotAllow=true`,方便程序中做不同的提示,提升用户主动授权概率

注:如果需要使用按住录音这种功能,参考上面的功能限制部分,请求权限可能会导致touch事件被打断,解决办法也参考上面。


## 【静态方法】RecordApp.Start(set,success,fail)
开始录音,需先调用`RecordApp.RequestPermission`
Expand Down
Binary file modified app-support-sample/demo_android/app-debug.apk.zip
Binary file not shown.
2 changes: 2 additions & 0 deletions app-support-sample/demo_android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ android {
targetSdkVersion 28
versionCode 1
versionName "1.0"

resValue "string","app_build_time",System.currentTimeMillis().toString()
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,10 @@ public void onTextChanged(CharSequence s, int start, int before, int count) {
@Override
public void afterTextChanged(Editable s) {}
});

//app编译时间
SimpleDateFormat dateformat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault());
Log.i(LogTag, "App编译时间:"+dateformat.format(Long.parseLong(getResources().getString(R.string.app_build_time))));
}


Expand All @@ -98,6 +102,18 @@ public void afterTextChanged(Editable s) {}
@Override
public void Request(String[] keys, final Runnable True, final Runnable False) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
boolean has=true;
for(String k : keys){
if( MainActivity.this.checkSelfPermission(k) != PackageManager.PERMISSION_GRANTED){
has=false;
}
}
if(has){
//已授权,已知requestPermissions调用会导致webview长按录音时会打断touch事件,提早检测早退出
True.run();
return;
}

PermissionCall=new Runnable() {
@Override
public void run() {
Expand Down Expand Up @@ -163,7 +179,7 @@ public void run() {
}
}
private String time(){
SimpleDateFormat formatter=new SimpleDateFormat("HH:mm:ss", Locale.CHINA);
SimpleDateFormat formatter=new SimpleDateFormat("HH:mm:ss", Locale.getDefault());
return formatter.format(new Date());
}
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,7 @@ static public class RecordApis {
static private final String LogTag="RecordApis";

static public void Async_recordPermission(final Request req) {
checkPermission(false, req, new Callback<Object, Object>() {
checkPermission(req, new Callback<Object, Object>() {
public Object Call(Object result, Exception hasError) {
if(result!=null){
req.callback(1, null);
Expand All @@ -344,16 +344,10 @@ public Object Call(Object result, Exception hasError) {
}
});
}
static private boolean hasPermission;//如果检测到已授权,就不要再继续检测了,Activity.requestPermissions调用会导致webview长按录音时无法触发touchend事件 https://github.com/xiangyuecn/Recorder/issues/46
static private void checkPermission(boolean useCache, final Request req, final Callback<Object, Object> callback){
if(useCache && hasPermission){
callback.Call("ok", null);
return;
}
static private void checkPermission(final Request req, final Callback<Object, Object> callback){
req.jsBridge.usesPermission.Request(new String[]{"android.permission.RECORD_AUDIO"}, new Runnable() {
@Override
public void run() {
hasPermission=true;
callback.Call("ok", null);
}
}, new Runnable() {
Expand All @@ -378,7 +372,7 @@ static public void Sync_recordAlive(Request req){
static public void Async_recordStart(final Request req){
DestroyCurrent();

checkPermission(true, req, new Callback<Object, Object>() {
checkPermission(req, new Callback<Object, Object>() {
public Object Call(Object result, Exception hasError) {
if(result==null){
req.callback(null, "没有录音权限");
Expand Down
9 changes: 2 additions & 7 deletions assets/zdemo.index.webrtc.js
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ var realTimeSendTryStop=function(recSet){


//按住发语音
var rtcVoiceStart,rtcVoiceDownEvent,rtcVoiceDownHit,rtcVoiceTipsV;
var rtcVoiceStart,rtcVoiceDownEvent,rtcVoiceDownHit;
$("body").bind("mousedown touchstart",function(e){
var elem=$(".webrtcVoiceBtn");
if(e.target!=elem[0]){
Expand All @@ -185,11 +185,6 @@ $("body").bind("mousedown touchstart",function(e){
rtcVoiceDownHit=setTimeout(function(){
rtcVoiceStart=true;

if(!rtcVoiceTipsV){
rtcVoiceTipsV=1;
reclog('<span style="color:#f60">已知`Android WebView`内发起授权请求时(如:H5的getUserMedia方法调用、App的权限请求)会打断touch事件,表现为触发了`touchcancel`事件,`touchend`事件始终不会触发,在做按住录音功能时应留意此问题。如果你要使用按住录音功能,解决办法:在每次`按住录音按钮`被按之前都重新进行一次权限请求方法的调用;或者Android Hybrid App中用RecordApp开启Native原生录音来避免此问题,因为App可以做到请求到权限后,后续录音不会再去进行权限请求。</span>');
};

//开始录音
recstart(function(err){
if(err){
Expand All @@ -216,7 +211,7 @@ $("body").bind("mousedown touchstart",function(e){
//结束录音
recstop(function(err,data){
if(e.type=="touchcancel"){
rtcMsgView("[中断]touch事件被中断",false);
rtcMsgView("[事件]touch事件被打断",false);
return;
};
if(err){
Expand Down

0 comments on commit f3ee77f

Please sign in to comment.