抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

支付宝小程序, ios 上音频播放失败、DOM 魔改、ref 引用、免域名调用失效 等各种各样的坑

我选了两个垃圾平台,自己踩了两倍的坑

1、踩坑之 ios 音频播放失效

比如说一个简单的 uniapp 的音频播放,下面俩函数绑定到对应的播放停止按钮上。代码如下

1
2
3
4
5
6
7
8
9
10
11
12
const testaudio = uni.createInnerAudioContext();
testaudio.src = 'https://m701.music.126.net/20231121002122/6cab596b822faccb93fdf042dadb9bcc/jdyyaac/obj/w5rDlsOJwrLDjj7CmsOj/31223625128/85a6/7924/89bd/de76bcf275daa45deba6da310e78c31c.m4a';
testaudio.onCanplay(() => {
console.log('音频1加载完成');
});

function play1() {
testaudio.play();
}
function stop1() {
testaudio.pause();
}

uniapp 编译到支付宝小程序了,然后我使用真机测试,成功播放。但是诡异的就是我朋友的手机测试失败。

一连试了好几个人,发现是安卓能正常播放,ios 失败,太诡异了。 但是那个加载完成又是实打实的,就是播放出了问题。

支付宝开发者平台文档啊,社区啊看了半天,然后发现一个问同样问题的,时间停止在 23 年 3 月,没人回,没解决。

我服了,这么严重的功能问题,支付宝小程序你那文档怎么写的,ios 放不出来音频啊,近一半的用户啊

解决

最后 google 了一晚上,瞎试了好多方法,还去看了微信小程序社区,最后解决了。

ios 有个默认全局静音的策略,支付宝 app 估计是开了,需要给音频设置静音也能播放才行

1
testaudio.obeyMuteSwitch = false;

设置完再给朋友手机真机测试,音频放出来了,我也成功因为支付宝平台的垃圾文档浪费了一晚上时间

2、踩坑之音频 audioContext 对象只能同时存在 6 个

在做无障碍组件,盲人使用,所以需要音频播放

我的解决方案就是先把现成的音频放到 oss 对象储存上,然后获取外链,直接播放

uniapp 里音频播放大概要这么写

1
2
3
4
5
6
7
let audio = uni.createInnerAudioContext();
audio.src = 'xxxx.xxxxx.url';
// 这个必须写,不然ios放不出声音
audio.obeyMuteSwitch = false;
audio.onCanplay(() => {
audio.play();
});

本来打算直接给每个组件都这么直接 new 好,然后点击直接调用 audio.play 完事

结果发现放多了音频之后,前面最早弄的就放不出声音来了

解决

反复实验发现,支付宝小程序是同时只能存在 6 个 audio 上下文 audioContext 对象,多了的话后面的会覆盖前面的,也就放不出声音来了

所以解决就是随用随 new,放完就释放。大概就是这样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let audio: UniApp.InnerAudioContext | null;
function audioCreatePlay() {
audio = uni.createInnerAudioContext();
audio.src = props.audioUrl;
// 这个必须写,不然ios放不出声音
audio.obeyMuteSwitch = false;
audio.onCanplay(() => {
audio?.play();
});
}
// 用
audioCreatePlay();
// 用完直接释放
audio?.destroy();
audio = null;

3、踩坑之注意 vue3 单项数据流

好吧这个其实跟 uniapp 没啥关系,纯纯是我不遵守 vue 规范

我试图直接在父组件用ref()声明完响应变量之后,直接传递引用给子组件,然后子组件拿着变量引用改变此变量,能让父组件的此 ref 变量变化

结果当然是不行

解决

这么写没遵守 vue 框架单项数据流的规定,当然不行

就算是 v-moudle 的双向绑定,其实也是封装的传方法和监控,然后子组件调父组件方法完成两边同步

所以说还是应该正常完成子组件传父组件数据的过程,在父组件定义函数操纵自己的变量,然后子组件defineEmits暴露,子组件使用此传参,完成子传父

4、踩坑之支付宝不支持事件委托,因为它魔改了 DOM

起因是我想实现组件点击计数,点击到 5 次没有交互的位置,就默认做点什么;如果点击的是有交互的地方,就不干什么,不能给每个东西都绑点击函数吧,所以我想到了事件委托,给根组件绑一个函数就完事了

结果发现是,做不到,点击的对象上怎么啥方法都没有,常用的e.target.closest()直接找不到函数

绑定在 dom 上的函数,形参e,然后我打印了一下e,发现确实没法了,他们给 dom 改完了

比如说有个简单的结构如下

1
2
3
4
5
6
7
<view class="wai" onTap="onTapTest" data-wai="wai" id="wai">
456
<view class="nei" data-nei="nei" id="nei">
123
<view class="neinei" data-nei="neinei" id="neinei"> 000 </view>
</view>
</view>
1
2
3
onTapTest(e){
console.log(e);
}

然后打印结果很令人难受,说明这个 dom 里是啥都没有,对比浏览器的 dom 就这么点东西

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
//点击最外面的打印
{
"currentTarget": {
"dataset": {
"wai": "wai"
},
"id": "wai",
"offsetLeft": 0,
"offsetTop": 94,
"tagName": "view"
},
"detail": {
"clientX": 70,
"clientY": 263,
"pageX": 70,
"pageY": 263
},
"target": {
"dataset": {
"wai": "wai"
},
"id": "wai",
"offsetLeft": 0,
"offsetTop": 94,
"tagName": "view",
"targetDataset": {
"wai": "wai"
}
},
"timeStamp": 1700819502522,
"type": "tap",
"mark": {}
}
// 点击中间的打印
{
"currentTarget": {
"dataset": {
"wai": "wai"
},
"id": "wai",
"offsetLeft": 0,
"offsetTop": 94,
"tagName": "view"
},
"detail": {
"clientX": 80,
"clientY": 156,
"pageX": 80,
"pageY": 156
},
"target": {
"dataset": {
"wai": "wai"
},
"id": "nei",
"offsetLeft": 0,
"offsetTop": 113,
"tagName": "view",
"targetDataset": {
"nei": "nei"
}
},
"timeStamp": 1700819522863,
"type": "tap",
"mark": {}
}

// 点击最中间的打印
{
"currentTarget": {
"dataset": {
"wai": "wai"
},
"id": "wai",
"offsetLeft": 0,
"offsetTop": 94,
"tagName": "view"
},
"detail": {
"clientX": 28,
"clientY": 153,
"pageX": 28,
"pageY": 153
},
"target": {
"dataset": {
"wai": "wai"
},
"id": "neinei",
"offsetLeft": 0,
"offsetTop": 132,
"tagName": "view",
"targetDataset": {
"nei": "neinei"
}
},
"timeStamp": 1700819539734,
"type": "tap",
"mark": {}
}

解决

根据打印出来的东西来看,冒泡还在,currentTarget始终指向绑定事件的最外层元素,target指向实际被点击的元素,且携带着各自的dataset信息。

但是确实鼠标点击事件的 target 全改了,各种方法没有了,没法拦截点击到最外层,事件委托算基本用不了了

当时我要做的是组件点击计数,点击到 5 次没有交互的位置,就默认做点什么;如果点击的是有交互的地方,就不干什么

笨方法,写个全局变量,直接给所有交互组件都绑个函数,点击变量置 0;给最根组件也绑个函数,点击变++。然后变量监听,到了 5 就触发某函数

非常笨的方法了,反正是实现了

5、踩坑之单页面组件渲染

uni 路由跳转的话,其实是切换了页面。我想不出现那个页面转换的动画,想让它感觉是直接刷新的

但是 uniapp 没 vue 的 router 路由管理,所以我只能用手动用条件渲染切换组件的方式进行

1
<component :is="..."></component>

然后发现 is 条件渲染也不支持。。。这就是 uniapp

我想浏览器真 vue 的 router 了 😭😭😭 想怎么弄子路由就怎么弄子路由 😭😭😭

解决

组件用v-if得了。。。

1
2
3
<component1 v-if="condition"></component1>
<component2 v-if="condition"></component2>
<component3 v-if="condition"></component3>

23/12/7 补充

今天看同伴代码,看出来别的解决办法了。uniapp 里有 TabBar 组件的存在,app 里挺常用的东西,本来是用来做底部栏,单个页面内路由跳转,不刷新。

所以给了 uni.switchTab() 方法,按规定在 page.json 里配置好底部栏对应页面,调用就能直接在单个页面里切换,不刷新的效果。

但这是 uniapp 自带的默认底部栏,写配置就有的那个,太丑了,没法用,所以干脆直接隐藏掉,只用给的方法切换页面。小程序没给自定义底部栏的方法,只能自己搓,分别在各页面底部用,然后 pinia 分享状态记住当前选择

6、踩坑之支付宝小程序插件,uniapp 里没法给组件导入,只能给页面引入

结合上一条,我想在组件里引入支付宝小程序的插件,实现页面里面的调用,无缝刷新

但是发现支付宝小程序原生插件引入不了组件里

解决

没法了,不给组件引入了,直接给页面引入

本来只管路由组件切换的页面,这下写了一堆东西,调用插件的逻辑全写里面了,真没法

7、踩坑之 callContainer 免域名调用实现长轮询

支付宝小程序,后端部署在支付宝小程序云托管上的话,关联了对应小程序就有免域名调用可以用。

官方文档在此callContainer(其实官方文档写的一坨史,有时间能单开一篇专门说踩过的坑。而且完全照搬的微信小程序云,支付宝那找不到解决方案的,直接去看微信的社区,简直 1:1 复刻,那边帮你把坑踩好了)

现在要实现简单的后端主动推送。因为业务不复杂,所以不想用 websocket 了,直接上长轮询完事

但是诡异的事就是,后端自测 60s 长轮询,没问题,我自己用小程序云的公网测试网址测,也没问题,但是小程序上真用免域名调用就 6s 超时,超时直接 throw 出调用失败

我都怀疑官网写的默认 timeout 参数是 60000 是少写了个零

解决

小程序开发工具的问题。反复在调用前后打印时间戳验证时间,发现免域名调用自定义的 6s 以内的超时时间,都能正确实现,写多少就是多少,throw 出来的是超时错误。但是写超过 6s 的超时时间,就直接 6s 给你 throw 出调用失败,看来最大上限是 6s

解决方法是上真机调试,纯纯 ide 的问题。ide 设置的 6s 上限,手机真测就是 60s 的上限,60s 内的自定义超时时间都有效,超过 60s 才上限显示是 60s 调用失败

补充

其实想过备选方案是直接在小程序上跑测试域名调试,反正最后调的小程序云,又不是不能用。

而且这么调还直接走的my.request,这个接口有中断方法,太棒了。(免域名调用没提供调用中断,太牛逼了这个,这都没有,长轮询只能轮到后端给结果或者超时)

IDE 上一切安好,有中断就不用处理很多由于没有中断方法引出的问题。

但是实际上就是不能用。。。看了文档说

为什么 my.request 接口设置超时时间后依旧超时?
timeout 设置超时时间,Android 会不生效(默认是 30 秒,不同的网络状态也不一样,wifi 15 秒, 4G 20 秒)后续会更新统一,具体更新请关注小程序 更新日志。

确实没法了,用不了算了,凑合用免域名调用,超时至少都有 55s 打底

8、踩坑之隐藏支付宝小程序的默认 tabbar

支付宝小程序的默认 tabbar 很丑,并且自定义只能改颜色和图标,产品画的那复杂的底部栏实现不了,所以就用自定义方案吧。

自定义更是简单粗暴,直接uni.hideTabBar()隐藏原底部栏,然后自己搓一个普通组件当底部栏,在各个分页面都创建一个,状态用 pinia 共享
直接当替身使者,原底部栏只调用它的uni.switchTab()

ide 上跑着挺不错,真机调试也没事,但是一上线就直接出现了uni.hideTabBar()调用失败,一直报此页面不在 tabbar 上,就造成页面卡死,下面的也点不动了

解决

进入含在 tabbar 上的页面别用普通的uni.navigateTo()或者uni.redirectTo(),直接用uni.switchTab()进入。

因为普通方法进入可能调uni.hideTabBar()的时机底部栏还没加载,就出错了

但是用uni.switchTab()处必然加载好了的,就不会有这个错误

评论