微信小程式元件初體驗

大明發表於2019-02-28

需求背景

最近接到了一個需求,使用者在播放音訊的時候,有的音訊長度過長,使用者希望自主控制音訊的進度和時長。但是微信提供原生的<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等元件之間的關係,可玩性很高。

估計不久之後就有一套第三方元件支援小程式的各項功能。

相關文章