RTC脚手架的设计与实现(下)

电子说

1.2w人已加入

描述

由于所有的 Player 都有这个逻辑因此可以将这部分再抽象成一个 AbsPlayer:

abstract class AbsPlayerIDataSource, CB : ICallback>
    : IPlayer

最后整个 Player 的类图如下所示:

RTC

image.png

这里我们不关注 Player 的功能具体是如何实现的,比如如何推流,如何拉流,如何进行 RTC 等。毕竟每个项目底层所用的服务商 sdk 各不相同,技术实现也不同,因此这里我们只从架构的层面去探讨。

2、Player 的切换

Player 的切换针对的就是部分场景 RTC,这里我们引入 SwitchablePlayer 的概念专门用于此种场景,而其本身也继承自 AbsPlayer, 具备 Player 的所有功能。只不过这些功能是通过装饰者模式由其内部真正的 Player 来实现,同时增加了 Switch 的能力。再讲到 Switch 能力之前先来思考几个问题。

  1. 何时触发 Switch?
  2. 如何进行 Switch?
  3. 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对应的流程如图所示:

RTC

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 流程的封装需要搞清楚两件事情:

  1. RTC 的主体流程是怎样的
  2. 业务调用方需要的是什么,关注的又是什么

由于 RTC 的主体流程和日常打电话相似,所以笔者以此类比,这样大家更容易理解。下图所示即为整个通话过程。RTC

搞清楚整个流程后,接下来就是搞清楚第二件事情,业务调用方需要的是什么,关注的又是什么。结合上图来看关注的大概有三点:

  • 第一就是需要具备拨打和挂断的入口;( Player 的 Start 和 Stop
  • 第二就是要能知道当前的通话状态比如是否正在连通,是否已经接通,是否通话结束;( Player 的 状态维护
  • 第三就是一些反馈比如对方未接通,对方不在服务区,手机号是空号等。( Player 的 核心事件回调即之前提到的 ICallback

而至于它是如何连通的,底层做了哪些操作,拨打电话的人对此毫不关心。基于上述我们的整体功能设计所要关注的点就有了。

1、通过设计一个 manager 来管理 Player 并对外暴露 Start 和 Stop 方法。

2、对 Player 进行状态维护,并让其状态可被上层监听。

3、Player 的一些核心事件回调也可被上层监听。

其中第一点和第三点比较简单,这里就不做过多的赘述。第二点状态维护,笔者使用了 StateMachine 状态机来实现,在不同的状态执行不同的操作,同时每一种状态都对应一个状态码,上层可以通过监听状态码来感知状态变化。

RTC

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 脚手架产生的背景,并以通俗易懂的方式一步步阐述设计过程以及最终实现。在此期间发现问题,解决问题,引出思考。由于受限于篇幅,不能将每一个点都进行详尽的介绍,有兴趣的同学如有疑问,可以留言,一起探讨学习。

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分