有些H5的頁(yè)面會(huì)有一個(gè)按鈕控制背景音樂(lè)播放负敏,如果只是單一頁(yè)面的話吼过,沒(méi)有什么邏輯可言。但如果涉及到轉(zhuǎn)場(chǎng)苛谷,那么邏輯就復(fù)雜起來(lái)。
涉及操作符
- partition
- switchMapTo
- takeUntil
業(yè)務(wù)邏輯
- 點(diǎn)擊背景音樂(lè)按鈕格郁,則播放音樂(lè)腹殿,再次點(diǎn)擊暫停播放音樂(lè)
- 當(dāng)切換場(chǎng)景的時(shí)候,如果音樂(lè)正在播放例书,則切換新的場(chǎng)景的背景音樂(lè)
- 當(dāng)切換場(chǎng)景的時(shí)候锣尉,如果音樂(lè)已經(jīng)暫停,則等待點(diǎn)擊后再播放新的音樂(lè)
- 當(dāng)有音樂(lè)的時(shí)候决采,按鈕播放旋轉(zhuǎn)動(dòng)畫自沧,暫停播放時(shí)按鈕靜止不動(dòng)
對(duì)于使用者來(lái)說(shuō)再正常不過(guò)的邏輯,開(kāi)發(fā)起來(lái)卻不是那么容易树瞭,因?yàn)樯婕暗铰曇舻募虞d拇厢,切換,暫停和響應(yīng)點(diǎn)擊等晒喷。
這次我們不再寫常規(guī)操作孝偎,大家可以自行腦補(bǔ)。
RxJS實(shí)現(xiàn)
首先我們定義播放按鈕的事件流凉敲,以及切換場(chǎng)景的事件流
let playMusicClickOb = fromEvent(musicBn, 'click')
let changeStageOb = ...//此處省略創(chuàng)建過(guò)程
接下來(lái)我們需要通過(guò)partition操作符分離出兩個(gè)事件流
let [playingStageOb, muteStageOb] = changeStageOb.pipe(partition(_ => isPlaying))
當(dāng)切換場(chǎng)景的時(shí)候邪媳,正在播放和沒(méi)有播放的情況分成兩個(gè)事件流對(duì)象playingStageOb和muteStageOb
接下來(lái)我們就可以利用上面定義好的4個(gè)事件流組合成我們要的邏輯了
rxjs.merge(playingStageOb, muteStageOb.pipe(switchMapTo(playMusicClickOb.pipe(take(1)), outv => outv))).pipe(map((index => {
playAni()//按鈕旋轉(zhuǎn)動(dòng)畫
return Laya.SoundManager.playMusic('stage' + index + ".mp3")
})), switchMapTo(playMusicClickOb.pipe(takeUntil(muteStageOb)), outV => outV)).subscribe(channel => {
if (isPlaying) {
channel.pause()
stopAni()//停止按鈕旋轉(zhuǎn)動(dòng)畫
} else {
channel.resume()
playAni()//按鈕旋轉(zhuǎn)動(dòng)畫
}
})
分析
代碼中分為三個(gè)功能區(qū)
加載音樂(lè)并播放
playAni()//按鈕旋轉(zhuǎn)動(dòng)畫
return Laya.SoundManager.playMusic('stage' + index + ".mp3")
暫停播放
channel.pause()
stopAni()//停止按鈕旋轉(zhuǎn)動(dòng)畫
恢復(fù)播放
channel.resume()
playAni()//按鈕旋轉(zhuǎn)動(dòng)畫
這個(gè)三塊功能何時(shí)執(zhí)行捐顷,是本案最為關(guān)鍵的部分。
我們來(lái)分析
rxjs.merge(playingStageOb, muteStageOb.pipe(switchMapTo(playMusicClickOb.pipe(take(1)), outv => outv))).pipe(map((index => {
首先雨效,最后的map操作符是為了把場(chǎng)景的序號(hào)轉(zhuǎn)換成對(duì)應(yīng)的mp3文件名迅涮,這個(gè)沒(méi)什么好說(shuō)的,可以忽略
map((index => {
所以核心邏輯就是
rxjs.merge(playingStageOb, muteStageOb.pipe(switchMapTo(playMusicClickOb.pipe(take(1)), outv => outv)))
我們觀察徽龟,最外層是merge操作即
rxjs.merge(playingStageOb, muteStageOb.pipe(...))
意思是轉(zhuǎn)場(chǎng)事件觸發(fā)的事件流叮姑,包括正在播放音樂(lè)時(shí)轉(zhuǎn)場(chǎng),以及不在播放音樂(lè)時(shí)轉(zhuǎn)場(chǎng)据悔。其中不在播放音樂(lè)時(shí)轉(zhuǎn)場(chǎng)還有后續(xù)的操作
即
switchMapTo(playMusicClickOb.pipe(take(1)), outv => outv)
這句話的意思是传透,如果在靜音的時(shí)候轉(zhuǎn)場(chǎng),就會(huì)開(kāi)始監(jiān)聽(tīng)playMusicClickOb极颓,即按鈕點(diǎn)擊事件朱盐,take(1)只取一次事件,就立即關(guān)閉菠隆,目的是組合出那種狀態(tài)即——靜音后轉(zhuǎn)場(chǎng)兵琳,然后又點(diǎn)擊了播放音樂(lè)的按鈕。
合起來(lái)骇径,就是在下面兩種情況之一就執(zhí)行加載音樂(lè)并播放音樂(lè)和動(dòng)畫的邏輯
- 正在播放音樂(lè)時(shí)轉(zhuǎn)場(chǎng)
- 靜音時(shí)轉(zhuǎn)場(chǎng)躯肌,然后點(diǎn)擊了播放音樂(lè)的按鈕
下面我們分析身下的邏輯:
})), switchMapTo(playMusicClickOb.pipe(takeUntil(muteStageOb)), outV => outV)).subscribe(channel => {
這段邏輯建立在之前已經(jīng)加載音樂(lè)并且播放起來(lái)后執(zhí)行。當(dāng)之前的邏輯執(zhí)行后破衔,我們通過(guò)switchMapTo
切換成后面這個(gè)事件流
playMusicClickOb.pipe(takeUntil(muteStageOb)), outV => outV)
即如果此時(shí)點(diǎn)擊了音樂(lè)按鈕清女,就會(huì)觸發(fā)直到受到了靜音轉(zhuǎn)場(chǎng)的事件。什么意思晰筛?就是說(shuō)此時(shí)用戶點(diǎn)擊了音樂(lè)播放按鈕嫡丙,就會(huì)在暫停和播放兩種狀態(tài)切換。直到我們暫停的情況下轉(zhuǎn)場(chǎng)了读第,就不再監(jiān)聽(tīng)迄沫。為什么是這樣設(shè)計(jì)呢?假設(shè)我們此時(shí)切換了暫停和播放若干次卦方,我們要轉(zhuǎn)場(chǎng)了羊瘩,如果此時(shí)正好在暫停狀態(tài),那么我轉(zhuǎn)場(chǎng)后盼砍,是什么狀態(tài)呢尘吗?對(duì)了,就是上面
- 靜音時(shí)轉(zhuǎn)場(chǎng)浇坐,然后點(diǎn)擊了播放音樂(lè)的按鈕
的狀態(tài)睬捶,看到?jīng)],所以我們使用takeUntil來(lái)終止當(dāng)前事件流近刘。如果是播放音樂(lè)的狀態(tài)下轉(zhuǎn)場(chǎng)了呢擒贸?這就回到了上面的
- 正在播放音樂(lè)時(shí)轉(zhuǎn)場(chǎng)
的狀態(tài)臀晃,會(huì)執(zhí)行加載音樂(lè)并播放的邏輯,但我們的切換暫停和播放的功能依舊需要運(yùn)行介劫,所以在takeUntil中我們只有一種情況需要終止當(dāng)前事件流就是muteStageOb
是不是有點(diǎn)繞徽惋,多想想就能明白
利用Rx編程,我們復(fù)用了事件流對(duì)象座韵,組合出了各種狀態(tài)下的邏輯险绘,并將實(shí)際執(zhí)行代碼壓縮到最精簡(jiǎn),假如有邏輯需求變化誉碴,也能很快修改宦棺。比如我們需要一開(kāi)始就播放音樂(lè),只需要在merge里面加一個(gè)of(0)——參數(shù)0沒(méi)有任何意義黔帕,純粹為了觸發(fā)事件
rxjs.merge(of(0),playingStageOb, muteStageOb......