什么是 RTC?
RTC 即 Real-Time Communication 的簡稱是一種給行業提供高并發、低延時、高清流暢、安全可靠的全場景、全互動、全實時的音視頻服務的終端服務。上面是比較官方的解釋,通俗的來講就是一種能夠實現一對一、多對多音視頻通話等眾多功能的服務。目前提供該項服務的服務商有很多例如:聲網、云信、火山引擎、騰訊云等。
背景
目前云音樂旗下 APP 眾多,其中涉及到 RTC 業務的不在少數,例如:常見的音視頻連麥、PK、派對房,1v1 聊天等。由于業務線不同,功能不同,開發者也不同,大家各寫一套,不斷的重復造輪子,因此為了避免重復的開發工作提升開發效率,需要有一套通用的RTC框架。
設計思路
在講具體的方案設計之前,先講一下我的設計思路:
- 功能內聚 :需要將功能都封裝在一個容器里,對外通過接口提供方法調用
- 業務隔離 :不同的業務需要有不同的功能容器
- 統一調用 :所有功能容器需要有統一的調用入口
- 狀態維護 :需要對狀態進行精準維護
- 切換無感 :進行功能容器切換時候,無感知
- 核心可控 :對核心鏈路可監控,故障預警
基于以上 6 點,大致的架構設計如圖所示,這里先不用深究圖中的模塊表示什么,后面會講到,這里只是先了解一下大致的架構:
image.png
接下來我就來講講具體的實現過程。
方案設計
前言:
RTC 的業務場景雖然很多,但本質上卻相差無幾,都是用戶加入到一個共同的房間,然后在房間內進行實時的音視頻通訊。具體到實際項目中大致又可分為兩種:全場景 RTC 和部分場景 RTC。
- 全場景 RTC :整個業務都是通過 RTC 技術實現例如:1v1 音視頻通話、派對房等。
- 部分場景 RTC :即整個業務鏈路中只有一部分使用了 RTC 技術,往往這種業務會涉及到引擎的切換。
不管是哪一種場景,承載核心功能的引擎都是必不可少的,因此我們首先就從引擎開始著手,另外為了方便描述,后續便將引擎統一稱作 Player。
1、Player 的封裝
在與 RTC 相關聯的業務中會涉及到不同類型的 Player,例如:主播開播(推流 Player),觀眾觀看直播(拉流 Player)以及 RTC Player等。它們的功能雖然各不相同,但用法卻有相似之處,例如都有啟動 start,終止 stop 等。因此我們可以將不同的 Player 抽象出一個共同的接口 IPlayer 相關代碼如下:
interface IPlayer<DS : IDataSource, CB : ICallback> {
fun start(ds: DS)
fun stop()
fun setParam(key: String, value: T?)
......
}
其中 IDataSource 和 ICallback 分別是啟動 Player 所需要的數據源和回調,后面的文章中也會多次提到,特別是 IDataSource 它是 Player 啟動的源頭就好比打電話時的電話號碼。
在這里遇到的一個問題點就是由于 Player 內聚了所有的功能除了有一些通用方法外,也有著屬于自己特有的方法,例如:靜音,音量調節等。這些方法眾多而且各不相同無法在 IPlayer 接口中全部列出,即使能全部列出,但隨著業務的迭代 Player 中的方法肯定會不斷變化,不可能每更改一個方法就改一下接口,這顯然不符合程序設計原則。那么如何將不同的方法抽象化,讓上層通過調用同一個方法來執行不同的操作呢?這里通過:
fun setParam(key: String, value: T?)
來實現,其中 key 表示方法的唯一標記,value 表示方法的入參。這樣上層只需要通過調用 setParam 傳入相應的方法標記和方法入參即可調用到對應的方法了。那么如何做到呢?答案也很簡單通過一個中間層建立起一一映射關系。但是 Player 的類型眾多,要是每寫一個 Player 都要寫一個映射邏輯就太麻煩了。所以這里通過 APT 編譯時注解再結合 javapoet 自動生成這個中間層并給它命名為 xxxPlayerWrapper 其內部生成一個 convert 方法,在這個方法內部完成一一映射邏輯。接下來我們看看具體實現過程:
- 首先定義了兩個注解分別作用于具體的 Player 和對應的方法例如:
@Retention(RetentionPolicy.CLASS)
@Target({ElementType.TYPE})
public @interface PlayerClass {
}
@Retention(RetentionPolicy.CLASS)
@Target({ElementType.METHOD})
public @interface PlayerMethod {
String name();
}
@PlayerClass
open class xxxPlayer : IPlayer<xxxDataSource, xxxCallback>() {
@PlayerMethod(name = "key1")
fun method1(v: String) {
....具體實現
}
}
- 一一映射關系建立:
xxxPlayer 和 xxxPlayerWrapper 之間是一個相互依賴關系,互為彼此的成員變量。當調用 xxxPlayer 的接口方法 setParam(key: String, value: T?) 時,會直接調用到 xxxPlayerWrapper 的 convert 方法,convert 方法會根據 key 來找到其所對應的方法名,最后直接調用到 Player 的具體方法。
image.png
-
RTC
+關注
關注
2文章
530瀏覽量
66319 -
騰訊云
+關注
關注
0文章
208瀏覽量
16770
發布評論請先 登錄
相關推薦
評論