youzack是我开发的一个能进行英语听力逐句精听训练以及背单词的网站,为了方便移动端的使用,我使用Uni-App来进行youzack移动端的开发,因为Uni-App不仅可以用来开发微信小程序,也可以打包为App。
在开发的过程中,我封装了三个大家可能用到的Uni-App组件,他们分别是:支持音频倍速播放以及自定义UI的音频播放器yz-audio、支持自动网络重试请求的zackRetrier、支持类似于锤子手机的“大爆炸分词”的yz-text。
源码地址:
https://github.com/yangzhongke/uniapp-youzack-components
下面分别来介绍他们的用法以及实现原理:
一、倍速音频播放器yz-audio
youzack中需要播放听力音频,Uni-App有组件,但是有如下缺点:不被推荐使用了;不支持变速播放功能;界面不能自定义。因此我开发了yz-audio这个组件。
插件地址:
https://ext.dcloud.net.cn/plugin?id=4246
yz-audio特点:支持音频的变速播放;界面中内置了三个可以自定义内容的文本区域,可以响应点击事件,也可以使用slot自定义UI;
基本使用方式:
在代码中播放以及设置名字、海报图片等:
var player1 = this.$refs.player1; player1.setSrc(
"https://vkceyugu.cdn.bspapp.com/VKCEYUGU-hello-uniapp/2cc220e0-c27a-11ea-9dfb-6da8e309e0d8.mp3");
player1.setPoster("
https://vkceyugu.cdn.bspapp.com/VKCEYUGU-uni-app-doc/7fbf26a0-4f4a-11eb-b680-7980c8a877b8.png");//海报图片
player1.setSinger("贝多芬");//设置歌手
player1.setName("致爱丽丝");//设置作品名字
方法说明:
方法名称 | 参数 | 说明 |
setSrc | value:string类型,音频文件Url | 设置要播放的音频文件 |
setPoster | value: string类型,海报图片Url | 设置要要显示的海报图片(显示到播放按钮下) |
setName | value: string类型,作品名 | 设置作品名 |
setSinger | value: string类型,歌手名 | 设置歌手名 |
stop | 无 | 停止播放 |
seek | t:number类型,进度(秒) | 跳转到第t秒播放 |
play | 无 | 播放 |
pause | 无 | 暂停播放 |
playbackRate | value:number类型,倍速 | 设置播放倍速,具体可选值请参考Uni-App文档里uni.createVideoContext的playbackRate方法。 需要特别注意:playbackRate方法不能在play/setSrc之前或者之后立即调用,否则只有很少几率会成功。如果需要在play/setSrc后调用,最好延时调用playbackRate。 |
播放器提供了三个文本区域供用户使用,可以在这三个文本区域中显示自定义内容,也可以响应它们的点击事件从而把它们当成按钮使用。也可以使用slot设置自定义UI。
属性说明:
属性名 | 类型 | 默认值 | 说明 |
isButton1Visible | boolean | false | 按钮1是否可见 |
button1Text | string | 按钮1的文本 | |
isButton2Visible | boolean | false | 按钮2是否可见 |
button2Text | string | 按钮2的文本 | |
isButton3Visible | boolean | false | 按钮3是否可见 |
button3Text | string | 按钮3的文本 |
有三个事件“Button1Click、Button2Click、Button3Click”,当三个按钮点击的时候就会分别触发。
例子代码:
isButton1Visible="true" isButton2Visible="true" isButton3Visible="true" @Button1Click="btn1Click">
如果这三个文本无法满足要求,也可以使用名字为extraCtrls的插槽来自定义这块的UI,如下:
youzack.com
事件说明:
名称 | 参数 | 说明 |
Button1Click | 无 | 按钮1被点击 |
Button2Click | 无 | 按钮2被点击 |
Button3Click | 无 | 按钮3被点击 |
play | 无 | 开始播放 |
pause | 无 | 暂停 |
ended | 无 | 结束播放 |
timeUpdate | {currentTime, duration},来自于video控件的timeUpdate事件参数的detail属性值。 | 播放进度变化时触发 |
error | 错误对象 | 视频播放出错时触发 |
注意事项:
1、 只有video组件支持倍速播放,所以这个组件是采用的video来播放音频,因此支持的音频文件格式取决于video组件的支持情况;
2、 由于VideoContext的内部实现的未知原因,不能在play方法之前或者之后立即调用playbackRate来修改倍速,否则很可能不会生效。最好由用户触发的动作来调用playbackRate,如果必须在play方法前后调用,建议通过setTimeout设置一个延时来执行playbackRate。
比如如下是不行的:
var player1 = this.$refs.player1;
player1.setSrc("https://www.test.com/test.mp3");
player1.playbackRate(0.5);
可以如下修改:
var player1 = this.$refs.player1;
player1.setSrc("https://www.test.com/test.mp3");
setTimeout(function(){player1.playbackRate(0.5);},500);
3、 由于组件是使用video来播放音频,因此具有video的所有缺点,比如不能实现后台播放。我曾经想换用getBackgroundAudioManager()来替换video,但是发现BackgroundAudioManager响应速度很慢,无法实现精细、及时的进度控制。
4、 如果需要音频的锁屏播放,可以在需要后台播放的时候(需要用户操作触发),暂停yz-audio的播放,然后再调用getBackgroundAudioManager实现后台播放。
5、 如果仔细观察播放器,你可能会发现右上角有一个小黑点,那个其实就是一个尺寸非常小的video组件。但是这个video组件不能完全隐藏,否则在ios下将会无法播放。
6、 由于播放器属于个性化需求非常强的组件,众口难调,因此这个组件很难完全满足所有人的要求,因此这个组件我只是开放源代码,各位可以根据自己的需要修改,我将不会进行后续维护、新需求增加等。
二、网络请求重试zackRetrier
在网络信号不强等情况下,网络请求可能会失败,因此我开发了一个能自动对失败的请求进行重试的组件zackRetrier,如果N次重试都失败了,还会弹出对话框问用户“是否再次重试”。在请求处理期间会自动显示loading提示。
插件地址:
https://ext.dcloud.net.cn/plugin?id=4247
用法:
首先引入组件import {zRetrier} from "@
/js_sdk/yz-zackRetrier/zackRetrier/index.js";
调用发送网络请求:zRetrier({url:"
https://api.youzack.com/test"}),zRetrier函数返回的是Promise对象,所以既可以用await的方式获取调用结果,也可以使用then()方式。返回值是一个长度为2的数组,数组的第一个元素是错误对象,如果调用成功了,则第一个元素是null,数组的第二个元素是服务器端返回的响应。
示例代码1
zRetrier({url:"https://api.youzack.com/test",manualRetryContent:"登录失败,是否重试"})
.then(res=>{
let err = res[0];
let resp = res[1];
if(err)
{
uni.showModal({
content:"调用失败"+err.errMsg
});
return;
}
uni.showModal({
content:"调用成功"+resp
});
});
示例代码2:
let err1,resp1;
[err1,resp1] = await zRetrier({url:"
https://api.youzack.com/test",manualRetryContent:"登录失败,是否重试"});
if(err1)
{
uni.showModal({
content:"调用失败"+err1.errMsg
});
return;
}
uni.showModal({
content:"调用成功"+resp1
});
zRetrier函数只有一个参数,这个参数会完整的传递给内部封装的uni.request方法,所以如果想设定method、headers等,用法和uni.request一样。除此之外,zRetrier的参数还有其他属性。
参数:
1) retryTimes:整数类型。代表一次失败之后,最多自动重试retryTimes次,默认值是3。
2) retryWaitTime:整数类型,代表每次自动重试之前等待的毫秒数。默认值是200。
3) autoLoading:boolean类型,代表请求期间是否自动显示loading,默认值是true。
4) manualRetryContent:string类型。如果设置了manualRetryContent的值,则在retryTimes次自动重试都失败后,会显示重试对话框,然后对话框中显示manualRetryContent的内容。对话框中有【取消】、【重试】两个按钮,如果用户点击了【重试】按钮之后,会再自动最大重试retryTimes次。
感谢C#中发明了await、async关键字极大的简化了异步编程,并且让JavaScript从C#中把这两个关键字借鉴了过来,最终能让我实现这个组件起来太简单了。
注意事项:由于这个组件可能多次重试后端接口,所有后端的接口需要是幂等的,也就是调用一次和调用N次的效果应该是一样的。
三、允许自定义选择菜单的“分词大爆炸”yz-text
为了方便用户直接查询例句中某个单词或者词组的中文翻译,youzack小程序中需要允许用户对于界面中的英文选中进行查询。微信小程序中,可以对于设定user-select=”true”来允许用户自由选择其中的文字,在Android手机中,选中文字会弹出一个只带【复制】一个菜单项的菜单,在苹果手机中,选中文字会弹出一个带【查询】等菜单项的菜单。但是这个菜单中是无法自定义菜单项的。
我想监听剪切板中内容的变化,来实现“用户复制单词后自动查询单词”的功能。但是微信小程序中没有提供监听剪切板内容变化的API,所以只能用定时器监听剪切板中的内容。发布后,有用户反映,在小米的某些手机上,手机系统会频繁的提示“应用正在读取剪切板中的内容”,非常影响使用。这个可以理解,为了保护用户隐私,避免很多应用通过监听剪切板来偷窥用户的内容,因此新版的IOS和Android中都会做出类似的提示。
因此我换了另外一种思路,我借鉴锤子手机中“分词大爆炸”的效果,在用户长按一段文字后,弹出对话框,在对话框中对于文字进行分词,然后允许用户选中一个或者多个单词,然后进行查询。这样不仅规避了问题,而且用户的使用体验更好,真是无心插柳柳成荫呀。
插件地址:
https://ext.dcloud.net.cn/plugin?id=4249
组件用法:
text属性中就是要显示的文字,长按文字后就会弹出分词对话框。
属性说明:
属性名 | 类型 | 默认值 | 说明 |
text | string | 空 | 显示的文字 |
tips | string | “选择文本(可多选)后,可以点击【翻译】” | 按钮底下的描述文字 |
button1Text | string | '翻译' | 第一个按钮的文本 |
事件说明:
名称 | 参数 | 说明 |
split | {value,’’,words:[]} | 如果不监听split事件或者split事件中设定e.words不是空,则按照英文进行分词。 split事件中对文字进行自定义分词,e.value是文字,e.words是分析后的字符串数组。 |
search | 字符串 | 点击第一个按钮后触发,参数是用户选择的大爆炸文字。 |
示例代码:
tips="选中文字进行搜索" button1Text="搜索">
split:function(e)
{
e.words=e.value.split("");
},
search:function(s)
{
uni.showToast({
title:s
});
},
示例中的中文分词用的是简单的一元分词,你可以用更智能的其他分词算法。
我主要做后端开发,Uni-App等这些都是做这个小程序时候才现学的,所以如果有做的不好的地方,请指正,谢谢。