微信JSSDK与录音相关的坑
微信JSSDK与录音相关的坑
最近一直在做微信JSSDK与录音相关的功能开发, 遇到了各种奇尺大坑, 时不时冷不丁地被坑一道, 让我时常想嘶吼: "微信JSSDK就是个大腊鸡!!!!!!!!!!"
现在工作得到阶段性成果, 有时间休息总结下, 故来整理一下这段时间碰到的bug, 希望做个前车之鉴, 劝大家谨慎入坑.
checkJsApi
功能: 判断当前客户端版本是否支持指定JS接口
转载: http://www.fwqtg.net/%E5%BE%AE%E4%BF%A1jssdk%E4%B8%8E%E5%BD%95%E9%9F%B3%E7%9B%B8%E5%85%B3%E7%9A%84%E5%9D%91.html
我遇到的一个不大不小的坑是: onVoiceRecordEnd和onVoicePlayEnd总是返回false, 即使这两个接口是被支持的!
还有要注意的是, 这个API只是检查当前客户端版本是否支持该API, 与该API是否开启并没有卵关系! 所以当你check某个API发现是true, 但是又怎么调用都不起作用的时候, 记得检查下wx.config中的jsApiList参数.
startRecord, stopRecord
微信JSSDK的API都有一个大问题, 就是如果调用时间间隔过短, 就非常可能产生无效调用. 无效调用的意思是, 虽然你调用了某个API, 但是相当于没调用, 它不会触发success, fail或complete中的任何一个callback!
打比方说, 你做一个"按下录音, 抬手停止录音"的功能, 如果用户点击了一下录音按钮, 相当于快速地startRecord然后stopRecord, 那么stopRecord是极有可能是无效的, 不会执行任何callback.
然而问题还不止这个, 微信JSSDK的调用是异步的. 举例来说就是, 你调用startRecord的时间, 和startRecord的success的callback被执行的时间可能间隔了若干毫秒甚至秒. 这意味着, 用户点击按钮可能会造成: 虽然是先调用startRecord再调用stopRecord, 但是可能stopRecord先于startRecord调用成功!
我做了各种尝试后意识到, 微信JSSDK太脆弱, 频繁操作就会被玩儿坏. 所以我最终的结论是: ==一定要在微信JSSDK外面包一层, 控制API的调用频率.==
以录音为例, (我曾经的解决方案见这里, 但是后来发现频繁地调用stopRecord还是会有问题) 我现在的解决方案是: 当用户按下录音时调用startRecord, 然后一秒之内抬手都会提示用户"录音太短", 然后在第一秒结束时再调用stopRecord, 这样可以确保两个API之间的调用间隔至少一秒, 不会崩掉.
最靠谱的方法还是用状态机来做, 我目前的状态机如下图. 图中没有包含uploadVoice, uploadVoice期间应该不允许用户操作按钮.

WinPhone上无法录音
通过WinPhone访问微信官方测试页面http://demo.open.weixin.qq.com/jssdk尝试调用录音接口, 然而并没有卵用!!!! (WinPhone用户再次受到一万点伤害)
好, 接下来只讨论iPhone和Android用户…
iPhone中的假录音状态
机型: iPhone
症状: 微信显示"录音中", 但是其实没有在录音! 此时, startRecord会失败, 错误信息startRecord:fail;stopRecord不调用任何callback, 仍然显示录音中. 那就这么挂了? 不…
触发方式:
- 录音中按Home返回桌面
- 录音中从屏幕底部上划打开iPhone的设置菜单, 再关闭菜单.
解法: 此时你需要先调用stopRecord, 虽然不会触发任何callback, 但微信内部一定设置了某个flag, 然后就可以正常调用startRecord了.
呵呵… 一口鲜血喷向屏幕.
你无法知道是否进入了假录音状态, 所以这个问题暂时无解!
不startRecord, 直接stopRecord有什么效果?
我的测试结果是, 三个callback都不会被触发!
我觉得这应该算是一个bug, 如果没有在录音但是调用了stopRecord, 那应该失败并且errMsg为NotRecording.
录音中按Home返回桌面
iPhone: 录音中断, 回到微信进入假录音状态.
Android: 录音继续, 回到微信可以正常stopRecord.
录音中打开设置菜单
iPhone: 从屏幕底部上滑可以打开设置菜单, 关闭菜单后进入假录音状态.
Android: 从屏幕上部下滑可以打开设置菜单, 关闭菜单后可以正常停止录音.
录音中关闭WebApp
iPhone: 录音结束, 回到WebApp可以正常开启录音.
Android: 录音继续, 回到WebApp调用startRecord失败提示recording.
录音中刷新WebApp
iPhone: 微信不再显示"录音中", 但是实际上此时在录音… startRecord失败, 错误信息startRecord:fail;stopRecord会成功.
Android: 我没有刷新按钮… (Android用户起立鼓掌)
iPhone上录音后播放audio声音变得特别小
这是一个微信JSSDK炒鸡恶心的BUG… 只在iPhone出现: 在你使用的是外放(就是声音是从手机下方的小音箱里面放出来的)的前提下, 录音之后, 播放audio/video声音会转而从听筒(就是不插耳机打电话是耳朵对着的位置)播放出来. 如果你不知道的话, 会以为录音之后播放audio/video声音变得特别小.
但是, 如果你用耳机的话则不会受到影响, 因为声音会始终从耳机里播放出来. (难道你要我告诉用户, "请带上耳机使用本产品"嘛?!)
后来我们发现playVoice一次之后, audio/video的声音就会, 神奇地, 又从外放里播放出来了… 无语凝噎.jpg
所以, 有一个丑, 但是管用的workaround… 就是, 每次stopRecord成功后之后立即调用playVoice和stopVoice… 这样至少听筒模式的bug解了.
但是还有个问题是, 如果你播放的是用户刚刚录的音, 有可能播放录音时会发出"噗"的一声… 这是录音时录进去的杂音. 想要解决这个问题你就必须要上传一个空白的语音到微信, 然后在WebApp启动的时候通过serverId下载这个语音, 然后每次stopRecord的时候播放这个空白语音…
然后还有个问题就是, 你上传的这个语音必须是临时素材, 这样下载下来才会是localId, 可以用playVoice播放; 如果你上传语音作为用旧素材的话, 下载下来是二进制, 不能用playVoice播放…
然后还有个问题就是, 临时素材只会被微信保留三天… 三天后过期… 所以你必须每三天上传一段新的空白语音, 更新serverId…
抱头痛哭.jpg
Android上无法实现长按录音的功能?
经过测试发现, (很多?)Android上无法实现长按录音, 为什么?
因为我发现, 只要调用了startRecord就会触发按钮的touchcancel事件. 于是, 之后的touch相关事件就不会再被监听了. (本人猜测, 这和下面的uploadVoice导致UI卡住是一个问题, 在Android上微信JSSDK的执行会卡住UI导致触发了按键的touchcancel事件)
这意味着即使你手还按在按键上, 浏览器已经认为你松手了; 等你真的松手的时候, 浏览器并没有在监听touchend事件了…
所以目前, 虽然是一套代码运行在iPhone和Android上, 但是iPhone上就是长按录音, 但是Android上需要用户点击开始(响应touchstart), 点击结束(响应touchend)…
事实上, 两次点击录音比长按录音更容易出bug, 因为第一次和第二次点击之间用户可以随便乱操作, 很容易出现各种问题; 但是长按期间, 用户乱操作的可能性会低得多.
(突然想到, 可不可以在Android系统上, 当调用startRecord后触发touchcancel时, 手动触发一下按键的touchstart事件, 这样让按钮能够监听touchend? 之后做做实验)
初次以及未知时间间隔之后, 提示用户"是否开启录音"
新用户进入WebApp后第一次调用startRecord的时候, 微信会弹出一个对话框, 询问用户"是否开启录音".
这对于"长按录音"操作来说, 非常影响体验, 因为用户按到一半需要松手去点对话框.
如果用户一不小心点了"否", 那你可以去哭了, 接下的录音API调用会一直失败.
如果用户点了"是", 接下来, 至少一段时间内, 用户可以安心地录音了, 不会弹出对话框.
但是, 恶心的是, 在不确定的(至少目前我还没找到规律)时间之后(比如4天之后)用户再次打开WebApp时微信可能会重新询问"是否开启录音"…
有一个不完美的解决方法是: 在刚刚打开WebApp的时候就尝试录音一下. 如果微信弹出对话框, 用户可以在这个时候点选是/否, 而不至于影响真正录音时的体验.
吐槽onVoiceRecordEnd和onVoicePlayEnd
先说onVoiceRecordEnd: 微信录音的最长时间是1分钟, 超过这个时间录音会自动停止. onVoiceRecordEnd的作用就是注册一个callback, 当录音超时的时候执行.
我想吐槽的是这个API的设计, 我觉得更合理的方式是应该在startRecord的时候就注册进去, 因为这个超时逻辑和stopRecord逻辑有很大程度上是重叠的, 比如错误处理, 重置录音按钮样式等, 而这些逻辑在startRecord的时候就是已知的了.
就是说这个API应该设计成这样:
wx.startRecord({ success: // ... fail: //... complete: //... onVoiceRecordEnd: //... })
但是现在的onVoiceRecordEnd却是一个独立的API:
wx.onVoiceRecordEnd({ // 录音时间超过一分钟没有停止的时候会执行 complete 回调 complete: function (res) { var localId = res.localId; } });
微信是觉得我的WebApp里面只会有一种onVoiceRecordEnd逻辑么? 事实上同一个WebApp的不同页面很可能有不同的onVoiceRecordEnd逻辑.
所以我现在只能在startRecord的技术上由封装了一层, 当startRecord成功的时候将当前的onVoiceRecordEnd逻辑注入到wx.onVoiceRecordEnd中.
onVoicePlayEnd的作用是注册一个callback, 当用户录音播放结束时执行. 它和onVoiceRecordEnd非常类似, 也是一个被处理出来的API. 它应该在playVoice的时候就被注册.
uploadVoice
录音结束后, 你是无法直接访问录音数据的, 需要先调用uploadVoice将数据上传到微信服务器, 然后让你的后台再从微信服务器下载数据… 这个设计直接导致了处理录音数据时间的增加.
微信这么做, 应该是希望保护用户隐私, 但是如果能提供一个新的授权方式(目前只支持两种授权方式, snsapi_base和snsapi_userinfo), 在用户允许录音并且可以直接上传语音到对方服务器, 那就太好了.
Android上, 上传/下载录音时UI会卡住
uploadVoice提供了一个选项isShowProgressTips, 默认为1, 表示显示进度提示. 吐槽1: 但是并没有说不显示进度应该设置几, 虽然0是常用的falsy. 文档不细致, 还要人去试. 吐槽2: 这变量名起的… 我觉得语法有问题,showProgressTip就行了.
这都不重要, 恶心的是: 在Android上, 即使设置了isShowProgressTips为0, 即不显示进度提示, UI依然会被卡住不动, 待上传完之后UI才能继续正常运行. 这个问题在iPhone上就没有.
所以, 比较好的解决方法是, 如果是iPhone则根据需要设置isShowProgressTips, 但Android上最好设置isShowProgressTips为1.
playVoice, stopVoice
这对接口有着和startRecord和stopRecord一样的问题–调用不能过于频繁否则会玩儿坏微信, 需要在微信的接口上再封装一层以控制调用频率.
假设你录制了多个语音, 在播放其中一个的时候, 播放另一个会产生不确定的结果. 有的时候第二个录音正常播放, 但是有的时候第二个播放不出来. 当你连续播放三个乃至更多的音频的时候问题更多.
还有一个问题就是onVoicePlayEnd在这种情况下也是不稳定的, 连续播放N个音频时, onVoicePlayEnd是不一定触发的…
即使你每次播放录音B之前调用stopVoice停掉正在播放的录音A, 这行为也是不可靠的… 有时候能停下来, 有时候会直接播放录音B的后半截, 并且随后的播放录音行为会乱掉.
这些恶心的bug意味着你必须要限制UI上的操作频率和顺序(比方说当播放一个录音的时候, 其他播放录音按钮是被禁用的), 也就无法创造一个操作流畅(比如随便点击某个录音按钮就可以停掉正在播放的录音而播放新的录音)的用户体验了.
stopVoice的小bug
这个bug跟其他bug比起来可以忽略, 但是仍然蛮影响用户体验, 就是: 在录音播放的最后几秒钟调用stopVoice一次, 会显示ok说明停止录音成功, 但是声音仍然在播放, 继续调用stopVoice会失败, 错误信息为not playing.
(这位处女座请把你手里的板砖放下, 谢谢.)
结语
总之, 微信JSSDK还只是提供了基本功能, 但是健壮性远不足以支撑丰富交互的WebApp. 这种基于微信JSSDK开发的WebApp会有诸多限制, 远无法达到NativeApp的效果.
但是, 基于微信的WebApp有着易访问, 易传播的巨大优势, 可以作为NativeApp开发之前的一个试点项目. 最终为了确保良好的用户体验, NativeApp还是必不可少的.
微信JSSDK与录音相关的坑的更多相关文章
- 微信JSSDK与录音相关的坑
欢迎各位转载, 以让微信团队重视这些恼人的BUG. 请注明出处微信JSSDK与录音相关的坑 by lzl124631x 最近一直在做微信JSSDK与录音相关的功能开发, 遇到了各种奇尺大坑, 时不时冷 ...
- 微信JS-SDK选择图片遇到的坑
微信JS-SDK选择图片遇到的坑 有个需求要在微信企业号里面做开发,有个功能是选择图片,使用input标签肯定是不管用了,Android手机上不能多选,所以使用了微信的JS-SDK提供的相关API,这 ...
- 从零开始实现基于微信JS-SDK的录音与语音评价功能
最近接受了一个新的需求,希望制作一个基于微信的英语语音评价页面.即点击录音按钮,用户录音说出预设的英文,根据用户的发音给出对应的评价.以下是简单的Demo: 就可以满足我们的需求比如照相或者录制视频,但是考虑界面美观性,有时 ...
- drawRect:和layoutSubview的区别
关于这两个方法的区别 还是有点意思的. UIView的setNeedsDisplay和setNeedsLayout方法.首先两个方法都是异步执行的.setNeedsDisplay会调用自动调用draw ...
- python可视化基础
常用的python可视化工具包是matplotlib,seaborn是在matplotlib基础上做的进一步封装.入坑python可视化,对有些人来说如同望山跑死马,心气上早输了一节.其实学习一门新知 ...
- HTML5:'data-'属性的作用是什么
在大家查看HTML时,经常会看到data-role.data-theme等的使用,比如:通过如下代码即可实现页眉的效果: <div data-role="header"> ...
- STL:vector<bool> 和bitset
今天某个地方要用到很多位标记于是想着可以用下bitset,不过发现居然是编译时确定空间的,不能动态分配.那就只能用vector来代替一下了,不过发现居然有vector<bool>这个特化模 ...
- Wannafly挑战赛9 E - 组一组
链接:https://www.nowcoder.net/acm/contest/71/E来源:牛客网 题目描述 有一个长为 n 的数列 A,其中有 m 个限制条件,条件有两种: 1.对于区间 [l,r ...
- 查看Linux 、Nginx、 MySQL 、 PHP 版本的方法
参考:查看Linux .Apache . MySQL . PHP 版本的方法 1.查看Linux版本: uname -a: more /etc/issue; cat /proc/version; 2. ...
- GDAL线面互转换(2)
在上一个文章中介绍了线转化为面和面转化为线,其主要的实现思路就是把面中的点取出来构成线,把线中的点取出来构成面,实际上就是一个硬拷贝,无奈客户的实际需求并非如此,客户想要线转面的时候几条相交线构成面, ...
- CRM Online Outlook Client Configuration Wizard
CRM Outlook客户端满足和便捷了用户对office outlook和CRM两个程序的使用需求.通过CRM outlook 客户端,用户可以像在浏览器中访问CRM一样,流畅的读写CRM数据.同时 ...
- Android ListView几个重要属性
1.transciptMode属性,需要用ListView或者其它显示大量Items的控件实时跟踪或者查看信息,并且希望最新的条目可以自动滚动到可视范围内.通过设置的控件transcriptMode属 ...