Skip to content

Commit

Permalink
新增runningContext配置项,已知iOS16如果没有用户操作时无法调用AudioContext.resume,修复BufferS…
Browse files Browse the repository at this point in the history
…treamPlayer在iOS16下无法start的问题
  • Loading branch information
xiangyuecn committed Jul 1, 2023
1 parent f682e7a commit 6105fc4
Show file tree
Hide file tree
Showing 14 changed files with 204 additions and 48 deletions.
17 changes: 11 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -480,7 +480,7 @@ iOS其他浏览器|iOS 14.3+|iOS 14.3+

> 此处已清除7个已知问题,大部分无法解决的问题会随着时间消失;问题主要集中在iOS上,好在这玩意能更新
*2023-02-22* iPhone 14:有部分开发者反馈iPhone14上关闭录音后再次打开录音,会出现无法录音的情况,目前并不清楚是只有iPhone14上有问题,还是iOS16均有问题;估计是新的WebKit改了相关源码印度阿三没有测试,js没办法解决此问题,静候iOS更新,也许下一个系统更新就自动修复了;建议针对iOS环境,全局只open一次,不要close,挂在那里录音,可减少iOS系统问题带来的影响(负优化+耗电)。
*2023-02-22* iPhone 14:~~有部分开发者反馈iPhone14上关闭录音后再次打开录音,会出现无法录音的情况,目前并不清楚是只有iPhone14上有问题,还是iOS16均有问题;估计是新的WebKit改了相关源码印度阿三没有测试,js没办法解决此问题,静候iOS更新,也许下一个系统更新就自动修复了;建议针对iOS环境,全局只open一次,不要close,挂在那里录音,可减少iOS系统问题带来的影响(负优化+耗电)~~ 2023-7-1,此问题已修复,原因出在AudioContext上,iOS新版本上似乎不能共用一个AudioContext(新版本每次open均会创建新的AudioContext),并且iOS上AudioContext的resume行为和其他浏览器不相同,如果不是通过用户操作(触摸、点击等)进行调用,将无法resume,参考 [ztest_AudioContext_resume.html](https://xiangyuecn.gitee.io/recorder/assets/ztest_AudioContext_resume.html) 测试用例

*2020-04-26* Safari Bug:据QQ群内`1048506792``190451148`开发者反馈研究发现,IOS ?-13.X Safari内打开录音后,如果切换到了其他标签、或其他App并且播放了任何声音,此时将会中断已打开的录音(系统级的?),切换回正在录音的页面,这个页面的录音功能将会彻底失效,并且刷新也无法恢复录音;表现为关闭录音后再次打开录音,能够正常获得权限,但浏览器返回的采集到的音频为静默的PCM,此时地址栏也并未显示出麦克风图标,刷新这个标签也也是一样不能正常获得录音,只有关掉此标签新打开页面才可正常录音。如果打开录音后关闭了录音,然后切换到其他标签或App播放声音,然后返回录音页面,不会出现此问题。此为Safari的底层Bug。使用长按录音类似的用户交互可大幅度避免踩到这坨翔。

Expand Down Expand Up @@ -536,7 +536,10 @@ set={
//可选直接提供一个媒体流,从这个流中录制、实时处理音频数据(当前Recorder实例独享此流);不提供时为普通的麦克风录音,由getUserMedia提供音频流(所有Recorder实例共享同一个流)
//比如:audio、video标签dom节点的captureStream方法(实验特性,不同浏览器支持程度不高)返回的流;WebRTC中的remote流;自己创建的流等
//注意:流内必须至少存在一条音轨(Audio Track),比如audio标签必须等待到可以开始播放后才会有音轨,否则open会失败


//,runningContext:AudioContext
//可选提供一个state为running状态的AudioContext对象(ctx);默认会在rec.open时自动创建一个新的ctx,无用户操作(触摸、点击等)时调用rec.open的ctx.state可能为suspended,会在rec.start时尝试进行ctx.resume,如果也无用户操作ctx.resume可能不会恢复成running状态(目前仅iOS上有此兼容性问题),导致无法去读取媒体流,这时请提前在用户操作时调用Recorder.GetContext(true)来得到一个running状态AudioContext(用完需调用CloseNewCtx(ctx)关闭)

/*,audioTrackSet:{
deviceId:"",groupId:"" //指定设备的麦克风,通过navigator.mediaDevices.enumerateDevices拉取设备列表,其中kind为audioinput的是麦克风
,noiseSuppression:true //降噪(ANS)开关,不设置时由浏览器控制(一般为默认打开),设为true明确打开,设为false明确关闭
Expand Down Expand Up @@ -569,7 +572,7 @@ set={

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

注意:此方法回调是可能是同步的(异常、或者已持有资源时)也可能是异步的(浏览器弹出权限请求时);一般使用时打开,用完立即关闭;可重复调用,可用来测试是否能录音。
注意:此方法回调是可能是同步的(异常、或者已持有资源时)也可能是异步的(浏览器弹出权限请求时);一般使用时打开,用完立即关闭;可重复调用,可用来测试是否能录音;open和start至少有一个应当在用户操作(触摸、点击等)下进行调用,原因参考`runningContext`配置

另外:普通的麦克风录音时,因为此方法会调起用户授权请求,如果仅仅想知道浏览器是否支持录音(比如:如果浏览器不支持就走另外一套录音方案),应使用`Recorder.Support()`方法。

Expand All @@ -582,7 +585,7 @@ set={
注意:普通的麦克风录音时(所有Recorder实例共享同一个流),如果创建了多个Recorder对象并且调用了open(应避免同时有多个对象进行了open),只有最后一个新建的才有权限进行实际的资源释放(和多个对象close调用顺序无关),浏览器或设备的系统才会不再显示正在录音的提示;直接提供的流 set.sourceStream 无此问题(当前Recorder实例独享此流)。

### 【方法】rec.start()
开始录音,需先调用`open`;未close之前可以反复进行调用开始新的录音。
开始录音,需先调用`open`;未close之前可以反复进行调用开始新的录音。注意:open和start至少有一个应当在用户操作(触摸、点击等)下进行调用,原因参考`runningContext`配置。

只要open成功后,调用此方法是安全的,如果未open强行调用导致的内部错误将不会有任何提示,stop时自然能得到错误;另外open操作可能需要花费比较长时间,如果中途调用了stop,open完成时(同步)的任何start调用将会被自动阻止,也是不会有提示的。

Expand Down Expand Up @@ -667,7 +670,7 @@ function transformOgg(pcmData){
判断浏览器是否支持录音,随时可以调用。注意:仅仅是检测浏览器支持情况,不会判断和调起用户授权(rec.open()会判断用户授权),不会判断是否支持特定格式录音。

### 【静态方法】Recorder.GetContext(tryNew)
获取全局的AudioContext对象,如果浏览器不支持将返回null。tryNew时尝试创建新的非全局对象并返回,失败时依旧返回全局的;成功时返回新的,注意用完必须自己调用`Recorder.CloseNewCtx(ctx)`关闭。
获取全局的AudioContext对象,如果浏览器不支持将返回null。tryNew时尝试创建新的非全局对象并返回,失败时依旧返回全局的;成功时返回新的,注意用完必须自己调用`Recorder.CloseNewCtx(ctx)`关闭。注意:非用户操作(触摸、点击等)时调用返回的ctx.state可能是suspended状态,需要在用户操作时调用ctx.resume恢复成running状态,参考rec的`runningContext`配置。

本方法调用一次后,可通过`Recorder.Ctx`来获得此全局对象,可用于音频文件解码:`Recorder.Ctx.decodeAudioData(fileArrayBuffer)`。本方法是从老版本的`Recorder.Support()`中剥离出来的,调用Support会自动调用一次本方法。已知iOS16中全局对象无法多次用于录音,当前Recorder打开录音时均会尝试创建新的非全局对象,同时会保留一个全局的对象。

Expand Down Expand Up @@ -1011,9 +1014,11 @@ var stream=Recorder.BufferStreamPlayer({
//False(errMsg) 处理失败回调

//sampleRate:16000 //可选input输入的数据默认的采样率,当没有设置解码也没有提供transform时应当明确设置采样率

//runningContext:AudioContext //可选提供一个state为running状态的AudioContext对象(ctx),默认会在start时自动创建一个新的ctx,这个配置的作用请参阅Recorder的runningContext配置
});

//创建好后第一件事就是start打开流,打开后就会开始播放input输入的音频
//创建好后第一件事就是start打开流,打开后就会开始播放input输入的音频;注意:start需要在用户操作(触摸、点击等)时进行调用,原因参考runningContext配置
stream.start(()=>{
stream.currentTime;//当前已播放的时长,单位ms,数值变化时会有onUpdateTime事件
stream.duration;//已输入的全部数据总时长,单位ms,数值变化时会有onUpdateTime事件;实时模式下意义不大,会比实际播放的长,因为实时播放时卡了就会丢弃部分数据不播放
Expand Down
2 changes: 1 addition & 1 deletion assets/demo-ts/dist/index.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion assets/demo-vue/dist/index.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion assets/demo-vue/dist/recordapp.js

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions assets/npm-home/hash-history.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
[
{
"sha1": "e23c1caa0e37bb7ffdf8097f91c22400064173be",
"time": "2023/7/1 22:14:28"
},
{
"sha1": "9901f4660f25031c99f73bc33d97238df9b3febb",
"time": "2023/6/29 21:25:33"
Expand All @@ -14,9 +18,5 @@
{
"sha1": "ef550552c34f40aebd27d186d8608540be998a02",
"time": "2023/2/1 23:06:25"
},
{
"sha1": "1ce1cee1bd9a6532c1105a27493ca1097c174ec1",
"time": "2022-8-7 21:57:00"
}
]
2 changes: 1 addition & 1 deletion assets/npm-home/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "recorder-core",
"version": "1.2.123456.9999",
"description": "Recorder库html5 js 录音 mp3 wav ogg webm amr 格式,支持pc和Android、ios部分浏览器、和Hybrid App(提供Android IOS App源码),微信也是支持的,提供H5版语音通话聊天示例、ASR语音识别转文字 和DTMF编解码",
"description": "Recorder库: html5 js 录音 mp3 wav ogg webm amr g711a g711u 格式,支持pc和Android、iOS部分浏览器、Hybrid App(提供Android iOS App源码)、微信,提供ASR语音识别转文字 H5版语音通话聊天示例 DTMF编码解码",
"homepage": "https://github.com/xiangyuecn/Recorder",
"main": "src/recorder-core.js",
"keywords": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ var setRealtimeOn=function(rtDiscardAll){
if(rtDiscardAll){
stream.set.realtime={discardAll:true};
}
Runtime.Log("切换成了实时模式,如果缓冲中积压的未播放数据量过大,会直接丢弃数据或者加速播放,达到尽快播放新输入的数据的目的,可有效降低播放延迟");
Runtime.Log("切换成了实时模式,如果缓冲中积压的未播放数据量过大,会直接丢弃数据"+(rtDiscardAll?"(discardAll=true)":"并加速播放部分数据")+",达到尽快播放新输入的数据的目的,可有效降低播放延迟");
}
};
var setRealtimeOff=function(){
Expand Down
111 changes: 111 additions & 0 deletions assets/ztest_AudioContext_resume.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">

<title>AudioContext resume回调测试</title>
</head>

<body>
<div>
<button onclick="createClick()">手动创建AudioContext</button>
</div>
<div class="logs"></div>

<script>
var elem=document.querySelector(".logs");
var timeStr=function(){
var now=new Date();
var t=("0"+now.getHours()).substr(-2)
+":"+("0"+now.getMinutes()).substr(-2)
+":"+("0"+now.getSeconds()).substr(-2)
+"."+("00"+now.getMilliseconds()).substr(-3);
return t;
};
var log=function(msg,color,span){
msg="["+timeStr()+"]"+msg;
var node=span?"span":"div",el=span||elem;
var div=document.createElement(node);
div.innerHTML='<'+node+' style="color:'+(!color?"":color==1?"red":color==2?"#0b1":color)+';border-bottom: 1px solid #aaa;padding:5px 8px">'+msg+"</"+node+">";
if(span)span.innerHTML="";
if(el.prepend)el.prepend(div);
else el.appendChild(div);
console.log(msg);
};

log('相关链接:<a href="https://stackoverflow.com/questions/57510426/cannot-resume-audiocontext-in-safari/67520856" target="_blank">Stack Overflow: Cannot resume AudioContext in Safari</a>');
log('相关链接:<a href="https://developer.apple.com/forums/thread/728463" target="_blank">Apple Developer Forums: WKWebView configuration mediaTypesRequiringUserActionForPlayback not working after iOS 16.3.1 update</a>');

log('<span style="font-size:24px">2023-06-30 本测试用于测试AudioContext的resume回调,已知在没有用户操作(触摸、点击等)前调用resume将不会有任何回调,用户操作后任意时间调用resume均能恢复,和audio、video自动播放策略类似。<span style="color:red">但iOS(iOS16)这货独树一帜,完全不参照audio、video自动播放策略,必须每次用户操作时才允许调用resume,否则resume一律无回调。</span></span>');
</script>

<script>
if(!window.AudioContext)window.AudioContext=window.webkitAudioContext;
var create=function(tag,firstMsg){
var ctx=new AudioContext();
var cls="rnd_"+Math.random().toString(16).substr(2);
log(tag+(firstMsg||"开始resume")+" state="+ctx.state+" <span class="+cls+">等待resume结果中...</span>");
var el=document.querySelector("."+cls); ctx.el=el;
if(firstMsg)return ctx;

resumeCall(tag,ctx,el);
return ctx;
};
var resumeCall=function(tag,ctx,el){
ctx.resume().then(function(){
log(tag+"ok state="+ctx.state,2,el);
ctx.close();
}).catch(function(e){
log(tag+"错误 state="+ctx.state+" :"+e.message,1,el);
ctx.close();
});
};


var ctx1=create("","【主要】页面打开立即创建了AudioContext1,等待手动调用resume");
ctx1.el.innerHTML='<button onclick="ctx1ResumeClick()">手动调用resume</button>';
var ctx1ResumeClick=function(){
resumeCall("",ctx1,ctx1.el);
};

var ctx2=create("","【主要】页面打开立即创建了AudioContext2,等待手动延迟调用resume");
ctx2.el.innerHTML='<button onclick="ctx2ResumeClick()">延迟3秒调用resume</button>';
var ctx2ResumeClick=function(){
ctx2.el.innerHTML="延迟3秒中...";
setTimeout(function(){
resumeCall("",ctx2,ctx2.el);
},3000);
};

var autoCtx=create("[自动运行]");
log("> 正在等待5秒后自动创建一个AudioContext,中途可以用户操作一下或者不操作,产生不同结果","#aaa");
setTimeout(function(){
create("[自动运行定时5秒]");
},5000);
autoCtx.el.innerHTML="等待用户任意点击操作...";
window.onclick=function(){
if(!autoCtx)return;
var ctx=autoCtx; autoCtx=0;
if(ctx.state=="suspended"){
log("检测到用户点击操作,正在等待3秒检查自动运行是否成功 ...",0,ctx.el);
setTimeout(function(){
ctx.el.innerHTML="ok state="+ctx.state;
if(ctx.state=="suspended"){
log("[自动运行]resume 等待回调超时",1,ctx.el);
}
},3000);
}
};

var createClick=function(){
create("[手动点击]");
log("> 手动点击,定时3秒后会自动创建","#aaa");
setTimeout(function(){
create("[手动点击定时3秒]");
},3000);
};
</script>

</body>
</html>
Loading

0 comments on commit 6105fc4

Please sign in to comment.