微信小程序组件初体验

需求背景

最近接到了一个需求,用户在播放音频的时候,有的音频长度过长,用户希望自主控制音频的进度和时长。但是微信提供原生的<audio>的组件只能进行播放和暂停的控制,没有时长等元素,因此需要自己做一个播放器的组件。

技术背景

在需求调研之后,关于音频方面的API大概分为两种:

  1. audio相关的createInnerAudioContext:创建并返回一个innerAudioContext对象。这个接口提供了一些音频相关的属性。
  2. 背景音乐相关的getBackgroundAudioManager:可以获取全局唯一的背景音频管理器backgroundAudioManager。和audio类似,提供一些音频相关的方法和属性。

同时,微信从v1.6.3起开放了自定义组件部分,支持简洁的组件化编程。想尝试一下组件化的开发模式,因此准备开发一个player组件来代替audio标签。同时低版本的基础库进行兼容操作。

技术选型

因为需要实现一个有独立作用域的单独组建,并且组建之间尽量不要互相影响。如果用bgm相关API的话需要维护一个全局的backgroundAudioManager,因此选择audio相关的API。

同时因为采取了自定义组件,因此只需要考虑1.6.4版本的基础库即可,只需要wx.createInnerAudioContext()来实现即可。

开发

之前开发过不少其他框架的组件,这次需要设计一个播放器,首先要定位播放器的功能。一个最重要的功能点:拖动控制音频播放进度。围绕着这一点,开始设计输入输出。

创建自定义组件

官方文档描述,一个组件由jsonwxmlwxssjs四个文件组成。首先要在json中标注这是一个组件:

// player.json
{
  "component": true
}
复制代码

在项目中进行应用时,同样在json文件中进行引用:

// page1.json
"usingComponents": {
    "player": "component/player/player"
}
复制代码

即可在项目中类似常规组件的使用。

// page1.wxml
<player name="{{item.name}}" src="{{item.url}}"></player>

复制代码

在js部分,需要用Component构造器构造一个实例来进行组建的管理,制定组件的属性、数据、方法等一些元素。

参数接收

输入最重要的就是音频的url了,其次是音频的其他信息,例如名称、作者、封面等等相关的部分。在组件中,定义对外属性要用properties进行定义。

properties属性是一个Object Map,管理着所有的对外属性,包含三个字段:type 表示属性类型、 value 表示属性初始值、 observer 表示属性值被更改时的响应函数。

如上面的两个输入,即可在构造器中定义:

 properties: {
 	// 这里定义了name和src属性,属性值可以在组件使用时指定
    src: { // 属性名
    	type: String, // 类型(必填),目前接受的类型包括:String, Number, Boolean, Object, Array, null(表示任意类型)
        value: '', // 属性初始值(可选),如果未指定则会根据类型选择一个
        observer: function(newVal, oldVal){} // 属性被改变时执行的函数(可选),也可以写成在methods段中定义的方法名字符串
    },
    name: String // 简化的定义方式
}
复制代码

其中需要注意的一点是,在properties中定义的对外属性,可以在this.data中直接调用,即为初始值。同时,可以用observer定义该值改动之后的回调,很好用。

properties属性中,属性名采用驼峰写法,调用时需要用连字符写法,数据绑定时需要用驼峰写法

作用域

在作用域部分,其实如果希望独立作用域的话,可以把一些实例和属性定义在Component的data部分,这样不与其他组件进行共享,为私有数据进行模板渲染。在探索中,发现如果希望多组件共享属性的话,可以定义在组件外部,进行管理,这点仍需探索总结。

生命周期

自定义组件的生命周期有createdattachedready三种,家在顺序分别是createdattachedreadyready时,页面基本布局完成,相当于onLoad,在这里可以进行一些组件的初始化操作。

另外,还有moveddetached两个生命周期,可以在detached中定义一些注销方面的操作。

audio实例的预加载

因为组件内部定义了一个audio对象,需要在组件加载完毕之后进行初始化的操作,因此在ready中进行实例的定义。

ready: function() {
	let audioItem = wx.createInnerAudioContext()
	audioItem.autoplay = true;
	audioItem.loop = false;
	audioItem.src = this.data.playUrl;
}
复制代码

这时定义了audioItem实例,并存储在data域中。

音频的长度获取

从需求出发,控制音频播放进度的前提是获取音频的长度。wx. createInnerAudioContext()提供了对象duration来获取音频的长度,但是有个前提,**音频必须加载之后才能获取该属性。**经过测试,所有事件回调中,只有onTimeUpdateonStop事件才能成功的获取duration字段。具体如下:

audioItem.onTimeUpdate(function(){
	let totalIndex = audioItem.duration;
	let duration = second2minute(totalIndex);
	that.setData({
	    totalIndex,
	    duration,
	})
})
复制代码

但是onTimeUpdate事件会随着音频的加载一直触发,进而反复执行回调,因此采用定时器的方法,不断的轮询进行数据的加载。

let calcTimer = setInterval(function(){
    calcIndex++;
    // 反复尝试仍获取失败,则取消尝试
    if(calcIndex>50) {
        clearInterval(that.data.calcTimer);
    }
    if(audioItem.duration>0) {
        let totalIndex = audioItem.duration;
        let duration = second2minute(totalIndex);
        that.setData({
            totalIndex,
            duration,
        })
        clearInterval(that.data.calcTimer);
    }
},100)
复制代码

这算是一个实现方式,一般在1000ms之内可以获取总长度。

布局样式

首先看一下播放器的样式:

和自带的标签相比,主要是增加了一个进度条的部分。关于进度条的实现,用户需要来回拖动来进行音频进度的控制。

具体的实现,是用一个<slider>来进行拖动的操作,但是原生的<slider>会比较大,因此可以做一个伪装的进度播放来操作。

具体代码如下:

<view class="player">
    <view class="player-poster">
        <image mode="aspectFill" src="http://img.sharedaka.com/Fkg6nUUzd2bnigSz7vgViBFXrwLq" style="width: 100%;height: 100%;"/>
        <view class="player-poster_button" bindtap="playMusic">
            <image src="/component/player/image/play.png" style="width: 100%;height: 100%;" wx:if="{{!playState}}"/>
            <image src="/component/player/image/stop.png" style="width: 100%;height: 100%;" wx:if="{{playState}}"/>
        </view>
    </view>
    <view class="player-info">
        <view class="player-info_title">{{playName}}</view>
        <view class="player-info_time"></view>
        <view class="player-info_control">
            <text class="time">{{playtime}}</text>
            <progress percent="{{downloadPercent}}" color="#E4E4E4" stroke-width="2" class="player-info_process">
                <text class="playstate" style="left:{{percent}}%"></text>
                <text class="playstate-outer" style="left:{{percent}}%"></text>
                <text class="dpstate" style="width:{{percent}}%"></text>
                <slider disabled="{{duration === '00:00'}}"  class="slider" bindchanging="changeSeek" color="#d33a31" left-icon="cannel" value="{{percent}}"></slider>
            </progress>
            <text class="time">{{duration}}</text>
        </view>
    </view>
</view>
复制代码

具体操作

播放器中主要涉及两个操作:

  1. 音频的播放和暂停。
  2. 音频进度条的拖动效果。
音频播放及暂停

在音频播放时,及触发methods中的playMusic()方法。

首先获取目前播放器的状态及实例:

let playState = this.data.playState;
let audioItem = this.data.audioItem;
复制代码

当播放器在未播放状态下时,执行操作,并开始计时器的计算:

audioItem.play();
let timer = setInterval(function() {
    let timeIndex = ++that.data.timeIndex;
    if(timeIndex > that.data.totalIndex) {
        clearInterval(that.data.timer);
        that.setData({
            timeIndex: 0,
            percent: 0,
            playtime: '00:00',
            playState: false
        })
        audioItem.stop();
        return;
    }
    let playtime = second2minute(timeIndex);
    that.setData({
        timeIndex: timeIndex,
        playtime,
        percent: timeIndex/that.data.totalIndex*100
    });
}, 1000)
复制代码

当播放器在播放状态下,执行操作:

audioItem.pause();
clearInterval(this.data.timer);
复制代码

记得对计时器的存储以及播放器状态的保存

播放器进度调整

当拖动播放器进度时,可以利用bindchanging来进行控制。这时需要计算当前进度是音乐总时长的占比,并跳转到相应的位置播放。

// 调整位置
changeSeek: function(e) {
    let value = e.detail.value;
    let nowIndex = Math.floor(totalIndex * value/100);
    if(playState) {
        audioItem.seek(nowIndex);
    }
    that.setData({
        percent: e.detail.value,
        timeIndex: nowIndex,
    })
}
复制代码

到此,即完成了player播放器的整体开发工作。

兼容性处理

前面说到,目前自定义组件只针对1.6.4以上的用户可以使用。因此需要对以下的版本进行兼容性的操作。

首先在js中判断当前用户基础库的版本:

wx.getSystemInfo({
    success: function(res) {
        let sdk = res.SDKVersion;
        if(sdk > '1.6.3') {
            that.setData({
                canUsePlayer: true
            })
        }
    }
})
复制代码

canUsePlayer来进行版本的标记。在项目中,根据标记选用不同的组件。

<audio name="{{item.extra}}" src="{{qiNiuUrl + item.content}}"  wx:if="{{!canUsePlayer}}"></audio>

<player name="{{item.extra}}" src="{{qiNiuUrl + item.content}}" wx:if="{{canUsePlayer}}"></player>  
复制代码

尾巴

to be continue

近期还有一个新的需求,需要音乐在小程序收起的时候继续播放。估计马上就要踩坑getBackgroundAudioManager了。页面维护一个公用的实例。

小感悟

从最近的一些列新feature可以看出,微信小程序的生态要逐步完善了。分包、直播、组件的开放说明微信小程序的可玩性页逐渐提高。

试水自定义组件的开发,感觉目前的开发模式还是比较简单的,相较于之前用template的模式更加方便,而且还有一些组件之间复用的新特性relations等组件之间的关系,可玩性很高。

估计不久之后就有一套第三方组件支持小程序的各项功能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值