由于所有的 Player 都有這個(gè)邏輯因此可以將這部分再抽象成一個(gè) AbsPlayer:
abstract class AbsPlayerIDataSource, CB : ICallback>
: IPlayer
最后整個(gè) Player 的類(lèi)圖如下所示:
image.png
這里我們不關(guān)注 Player 的功能具體是如何實(shí)現(xiàn)的,比如如何推流,如何拉流,如何進(jìn)行 RTC 等。畢竟每個(gè)項(xiàng)目底層所用的服務(wù)商 sdk 各不相同,技術(shù)實(shí)現(xiàn)也不同,因此這里我們只從架構(gòu)的層面去探討。
2、Player 的切換
Player 的切換針對(duì)的就是部分場(chǎng)景 RTC,這里我們引入 SwitchablePlayer 的概念專(zhuān)門(mén)用于此種場(chǎng)景,而其本身也繼承自 AbsPlayer, 具備 Player 的所有功能。只不過(guò)這些功能是通過(guò)裝飾者模式由其內(nèi)部真正的 Player 來(lái)實(shí)現(xiàn),同時(shí)增加了 Switch 的能力。再講到 Switch 能力之前先來(lái)思考幾個(gè)問(wèn)題。
- 何時(shí)觸發(fā) Switch?
- 如何進(jìn)行 Switch?
- Switch 的目標(biāo)對(duì)象 Player 從何而來(lái)?
第一個(gè)問(wèn)題何時(shí)觸發(fā) Switch :我們知道只要觸發(fā) Switch 就意味著需要啟動(dòng)另外的 Player,而啟動(dòng) Player 又需要上面提到的 IDataSource,因此我們只需要判斷啟動(dòng) Player 所傳入的 IDataSource 類(lèi)型和當(dāng)前 Player 的 IDataSource 類(lèi)型是否相同,如果不同便可觸發(fā)。判斷的具體邏輯是對(duì)比當(dāng)前 Player 泛型參數(shù)的 IDataSource 類(lèi)型( AbsPlayer第一個(gè)范型參數(shù) )和傳入的 IDataSource 類(lèi)型來(lái)實(shí)現(xiàn)。
private fun isSourceMatch(
player: AbsPlayer<IDataSource, ICallback>?,
ds: IDataSource
): Boolean {
if (player == null) {
return false
} else {
val clazz = player::class.java
var type = getGenericSuperclass(clazz) ?: return false
while (Types.getRawType(type) != AbsPlayer::class.java) {
type = getGenericSuperclass(type) ?: return false
}
return if (type is ParameterizedType) {
val args = type.actualTypeArguments
if (args.isNullOrEmpty()) {
false
} else {
Types.getRawType(args[0]).isInstance(ds) && isSameSource(player, ds)
}
} else {
false
}
}
}
第二個(gè)問(wèn)題如何進(jìn)行 Switch :這個(gè)就比較簡(jiǎn)單了只需要停止掉當(dāng)前的 Player 再啟動(dòng)目標(biāo) Player 即可。
第三個(gè)問(wèn)題 Switch 的目標(biāo)對(duì)象 Player 從何而來(lái) :SwitchablePlayer 并不清楚業(yè)務(wù)需要哪些 Player ,只是對(duì) Player 功能的一層包裝以及維護(hù) Switch 功能,因此具體的 Player 創(chuàng)建需要由業(yè)務(wù)層來(lái)實(shí)現(xiàn), SwitchablePlayer 只提供一個(gè)獲取 Player 的抽象方法例如:
abstract fun getPlayer(ds: IDataSource): AbsPlayer<out IDataSource, out ICallback>?
另外由于進(jìn)行 Switch 的時(shí)候會(huì)停止掉當(dāng)前的 Player,而被停止的 Player 是否能復(fù)用,如果能復(fù)用則可以將其緩存起來(lái),下次使用優(yōu)先從緩存中獲得。整個(gè)SwitchablePlayer對(duì)應(yīng)的流程如圖所示:
image.png
在使用時(shí)調(diào)用者可以根據(jù)自己的業(yè)務(wù)定義相關(guān) Player,例如在直播-> PK 的業(yè)務(wù)中,涉及到兩個(gè) Player 的切換即:LivePlayer 和 PKPlayer
class LivePKSwitchablePlayer : SwitchablePlayer(false) {
override fun getPlayer(ds: IDataSource): AbsPlayer<out IDataSource, out ICallback> {
return when (ds) {
is LiveDataSource -> {
LivePlayer()
}
is PKDataSource -> {
PKPlayer()
}
else -> LivePlayer()
}
}
}
3、流程封裝
對(duì)于整個(gè) RTC 流程的封裝需要搞清楚兩件事情:
- RTC 的主體流程是怎樣的
- 業(yè)務(wù)調(diào)用方需要的是什么,關(guān)注的又是什么
由于 RTC 的主體流程和日常打電話相似,所以筆者以此類(lèi)比,這樣大家更容易理解。下圖所示即為整個(gè)通話過(guò)程。
搞清楚整個(gè)流程后,接下來(lái)就是搞清楚第二件事情,業(yè)務(wù)調(diào)用方需要的是什么,關(guān)注的又是什么。結(jié)合上圖來(lái)看關(guān)注的大概有三點(diǎn):
- 第一就是需要具備撥打和掛斷的入口;( Player 的 Start 和 Stop )
- 第二就是要能知道當(dāng)前的通話狀態(tài)比如是否正在連通,是否已經(jīng)接通,是否通話結(jié)束;( Player 的 狀態(tài)維護(hù) )
- 第三就是一些反饋比如對(duì)方未接通,對(duì)方不在服務(wù)區(qū),手機(jī)號(hào)是空號(hào)等。( Player 的 核心事件回調(diào)即之前提到的 ICallback )
而至于它是如何連通的,底層做了哪些操作,撥打電話的人對(duì)此毫不關(guān)心?;谏鲜鑫覀兊恼w功能設(shè)計(jì)所要關(guān)注的點(diǎn)就有了。
1、通過(guò)設(shè)計(jì)一個(gè) manager 來(lái)管理 Player 并對(duì)外暴露 Start 和 Stop 方法。
2、對(duì) Player 進(jìn)行狀態(tài)維護(hù),并讓其狀態(tài)可被上層監(jiān)聽(tīng)。
3、Player 的一些核心事件回調(diào)也可被上層監(jiān)聽(tīng)。
其中第一點(diǎn)和第三點(diǎn)比較簡(jiǎn)單,這里就不做過(guò)多的贅述。第二點(diǎn)狀態(tài)維護(hù),筆者使用了 StateMachine 狀態(tài)機(jī)來(lái)實(shí)現(xiàn),在不同的狀態(tài)執(zhí)行不同的操作,同時(shí)每一種狀態(tài)都對(duì)應(yīng)一個(gè)狀態(tài)碼,上層可以通過(guò)監(jiān)聽(tīng)狀態(tài)碼來(lái)感知狀態(tài)變化。
image.png
狀態(tài)碼和核心事件的設(shè)置這里使用了 LiveData 去處理
class RtcHolder : IRtcHolder {
private val _rtcState = MutableLiveData(RtcStatus.IDLE)
private val _rtcEvent = MutableLiveData(RtcEvent.IDLE)
val rtcState = _rtcState.distinctUntilChanged()
val rtcEvent = _rtcEvent.distinctUntilChanged()
private val callBack = object : IRtcCallBack {
override fun onCurrentStateChange(stateCode: Int) {
_rtcState.value = stateCode
}
override fun onEvent(eventCode: Int) {
_rtcEvent.value = eventCode
}
//......省略其他代碼
}
init {
//上層狀態(tài)監(jiān)聽(tīng)
rtcState.observeForever {
when (it) {
RtcStatus.CONNECT_END -> {
ToastHelper.showToast("通話結(jié)束")
}
}
}
}
//......省略其他代碼
}
到這里整個(gè)腳手架的方案設(shè)計(jì)就結(jié)束了,其中服務(wù)商 SDK 封裝部分以及監(jiān)控部分,筆者準(zhǔn)備放到下期再來(lái)講解。
總結(jié)
本文介紹了 RTC 腳手架產(chǎn)生的背景,并以通俗易懂的方式一步步闡述設(shè)計(jì)過(guò)程以及最終實(shí)現(xiàn)。在此期間發(fā)現(xiàn)問(wèn)題,解決問(wèn)題,引出思考。由于受限于篇幅,不能將每一個(gè)點(diǎn)都進(jìn)行詳盡的介紹,有興趣的同學(xué)如有疑問(wèn),可以留言,一起探討學(xué)習(xí)。
-
RTC
+關(guān)注
關(guān)注
2文章
523瀏覽量
66228 -
騰訊云
+關(guān)注
關(guān)注
0文章
207瀏覽量
16750
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論