由于所有的 Player 都有這個邏輯因此可以將這部分再抽象成一個 AbsPlayer:
abstract class AbsPlayerIDataSource, CB : ICallback>
: IPlayer
最后整個 Player 的類圖如下所示:
image.png
這里我們不關注 Player 的功能具體是如何實現的,比如如何推流,如何拉流,如何進行 RTC 等。畢竟每個項目底層所用的服務商 sdk 各不相同,技術實現也不同,因此這里我們只從架構的層面去探討。
2、Player 的切換
Player 的切換針對的就是部分場景 RTC,這里我們引入 SwitchablePlayer 的概念專門用于此種場景,而其本身也繼承自 AbsPlayer, 具備 Player 的所有功能。只不過這些功能是通過裝飾者模式由其內部真正的 Player 來實現,同時增加了 Switch 的能力。再講到 Switch 能力之前先來思考幾個問題。
- 何時觸發 Switch?
- 如何進行 Switch?
- Switch 的目標對象 Player 從何而來?
第一個問題何時觸發 Switch :我們知道只要觸發 Switch 就意味著需要啟動另外的 Player,而啟動 Player 又需要上面提到的 IDataSource,因此我們只需要判斷啟動 Player 所傳入的 IDataSource 類型和當前 Player 的 IDataSource 類型是否相同,如果不同便可觸發。判斷的具體邏輯是對比當前 Player 泛型參數的 IDataSource 類型( AbsPlayer第一個范型參數 )和傳入的 IDataSource 類型來實現。
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
}
}
}
第二個問題如何進行 Switch :這個就比較簡單了只需要停止掉當前的 Player 再啟動目標 Player 即可。
第三個問題 Switch 的目標對象 Player 從何而來 :SwitchablePlayer 并不清楚業務需要哪些 Player ,只是對 Player 功能的一層包裝以及維護 Switch 功能,因此具體的 Player 創建需要由業務層來實現, SwitchablePlayer 只提供一個獲取 Player 的抽象方法例如:
abstract fun getPlayer(ds: IDataSource): AbsPlayer<out IDataSource, out ICallback>?
另外由于進行 Switch 的時候會停止掉當前的 Player,而被停止的 Player 是否能復用,如果能復用則可以將其緩存起來,下次使用優先從緩存中獲得。整個SwitchablePlayer對應的流程如圖所示:
image.png
在使用時調用者可以根據自己的業務定義相關 Player,例如在直播-> PK 的業務中,涉及到兩個 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、流程封裝
對于整個 RTC 流程的封裝需要搞清楚兩件事情:
- RTC 的主體流程是怎樣的
- 業務調用方需要的是什么,關注的又是什么
由于 RTC 的主體流程和日常打電話相似,所以筆者以此類比,這樣大家更容易理解。下圖所示即為整個通話過程。
搞清楚整個流程后,接下來就是搞清楚第二件事情,業務調用方需要的是什么,關注的又是什么。結合上圖來看關注的大概有三點:
- 第一就是需要具備撥打和掛斷的入口;( Player 的 Start 和 Stop )
- 第二就是要能知道當前的通話狀態比如是否正在連通,是否已經接通,是否通話結束;( Player 的 狀態維護 )
- 第三就是一些反饋比如對方未接通,對方不在服務區,手機號是空號等。( Player 的 核心事件回調即之前提到的 ICallback )
而至于它是如何連通的,底層做了哪些操作,撥打電話的人對此毫不關心。基于上述我們的整體功能設計所要關注的點就有了。
1、通過設計一個 manager 來管理 Player 并對外暴露 Start 和 Stop 方法。
2、對 Player 進行狀態維護,并讓其狀態可被上層監聽。
3、Player 的一些核心事件回調也可被上層監聽。
其中第一點和第三點比較簡單,這里就不做過多的贅述。第二點狀態維護,筆者使用了 StateMachine 狀態機來實現,在不同的狀態執行不同的操作,同時每一種狀態都對應一個狀態碼,上層可以通過監聽狀態碼來感知狀態變化。
image.png
狀態碼和核心事件的設置這里使用了 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 {
//上層狀態監聽
rtcState.observeForever {
when (it) {
RtcStatus.CONNECT_END -> {
ToastHelper.showToast("通話結束")
}
}
}
}
//......省略其他代碼
}
到這里整個腳手架的方案設計就結束了,其中服務商 SDK 封裝部分以及監控部分,筆者準備放到下期再來講解。
總結
本文介紹了 RTC 腳手架產生的背景,并以通俗易懂的方式一步步闡述設計過程以及最終實現。在此期間發現問題,解決問題,引出思考。由于受限于篇幅,不能將每一個點都進行詳盡的介紹,有興趣的同學如有疑問,可以留言,一起探討學習。
-
RTC
+關注
關注
2文章
530瀏覽量
66319 -
騰訊云
+關注
關注
0文章
208瀏覽量
16770
發布評論請先 登錄
相關推薦
評論