|
@@ -0,0 +1,451 @@
|
|
|
+package com.cr.pages
|
|
|
+
|
|
|
+import android.os.Bundle
|
|
|
+import android.view.LayoutInflater
|
|
|
+import android.view.View
|
|
|
+import android.view.ViewGroup
|
|
|
+import android.widget.Button
|
|
|
+import android.widget.LinearLayout
|
|
|
+import android.widget.TextView
|
|
|
+import androidx.fragment.app.activityViewModels
|
|
|
+import com.bigkoo.pickerview.adapter.ArrayWheelAdapter
|
|
|
+import com.contrarywind.listener.OnItemSelectedListener
|
|
|
+import com.cr.common.CrColorManager
|
|
|
+import com.cr.common.CrLiveManager
|
|
|
+import com.cr.common.CrSaveManager
|
|
|
+import com.cr.common.CrUnitManager
|
|
|
+import com.cr.cruav.CrApplication
|
|
|
+import com.cr.cruav.R
|
|
|
+import com.cr.data.CrUtil
|
|
|
+import com.cr.dialog.DialogInput
|
|
|
+import com.cr.event.BarAction
|
|
|
+import com.cr.event.CrCommonAction
|
|
|
+import com.cr.event.EventCommon
|
|
|
+import com.cr.event.EventFragmentBarAction
|
|
|
+import com.cr.models.CompletionModel
|
|
|
+import com.cr.models.ICompletion
|
|
|
+import com.cr.view.CrViewWheel
|
|
|
+import com.cr.viewmodel.CrLiveStreamVM
|
|
|
+import dji.v5.common.video.channel.VideoChannelState
|
|
|
+
|
|
|
+/**
|
|
|
+ * 操作系统:MAC系统
|
|
|
+ * 创建者:王成
|
|
|
+ * 创建日期:2023/9/12 13:50
|
|
|
+ * 描述:直播页面
|
|
|
+ */
|
|
|
+class FragmentLiveStream : CrNavigationFragment(), View.OnClickListener {
|
|
|
+ // todo: 2023/9/12 控件定义
|
|
|
+ private var wheelLiveQuality: CrViewWheel? = null // define: 2023/9/12 直播品质选择器
|
|
|
+ private var wheelLiveUrl: CrViewWheel? = null // define: 2023/9/12 直播地址选择器
|
|
|
+ private var wheelLiveChannelType: CrViewWheel? = null // define: 2023/9/12 直播通道选择器
|
|
|
+ private var btnActionAppendUrl: LinearLayout? = null // define: 2023/9/12 添加直播地址按钮
|
|
|
+ private var btnActionQuality:LinearLayout?=null // define: 2023/9/14 视频质量
|
|
|
+ private var btnActionChannelType:LinearLayout?= null // define: 2023/9/14 设置视频通道
|
|
|
+ private var lblInfo: TextView? = null // define: 2023/9/13 直播动态信息
|
|
|
+ private var btnStartLive: Button? = null // define: 2023/9/13 直播开始
|
|
|
+ private var btnStopLive: Button? = null // define: 2023/9/13 停止直播
|
|
|
+
|
|
|
+
|
|
|
+ // todo: 2023/9/12 定义变量
|
|
|
+ private var adapterWheelLiveQuality: ArrayWheelAdapter<String>? = null // define: 2023/9/12 适配器
|
|
|
+ private var adapterWheelLiveUrl: ArrayWheelAdapter<String>? = null // define: 2023/9/12 适配器
|
|
|
+ private var adapterWheelChannelType: ArrayWheelAdapter<String>? = null // define: 2023/9/12 适配器
|
|
|
+ private var itemsLiveQuality: MutableList<String>? = null // define: 2023/9/12 直播视频质量数据集
|
|
|
+ private var itemsLiveUrl: MutableList<String>? = null // define: 2023/9/12 直播地址数据集
|
|
|
+ private var itemsLiveChannelType: MutableList<String>? = null // define: 2023/9/12 直播通道选择集
|
|
|
+
|
|
|
+ private var indexLiveQuality:Int = -1 // define: 2023/9/13 直播质量当前索引
|
|
|
+ private var indexLiveChannelType:Int = -1 // define: 2023/9/13 直播通道当前索引
|
|
|
+ private var indexLiveUrl:Int = -1 // define: 2023/9/13 直播地址当前索引
|
|
|
+
|
|
|
+ // todo: 2023/9/13 动态显示的直播信息
|
|
|
+ private var isStreaming: String = "否" // define: 2023/9/13 是否正在直播
|
|
|
+ private var fps: Int = 0 // define: 2023/9/13 帧率
|
|
|
+ private var vbps: Int = 0 // define: 2023/9/13 码率
|
|
|
+ private var resolutionWidth: Int = 0 // define: 2023/9/13 分辨率宽
|
|
|
+ private var resolutionHeight: Int = 0 // define: 2023/9/13 分辨率高
|
|
|
+ private var packetLoss: Int = 0 // define: 2023/9/13 丢包率
|
|
|
+ private var rtt: Int = 0 // define: 2023/9/13 延迟时间
|
|
|
+ private var error: String = "" // define: 2023/9/13 错误信息
|
|
|
+
|
|
|
+
|
|
|
+ // define: 2023/9/12 绑定直播模型
|
|
|
+ private val liveStreamVm: CrLiveStreamVM by activityViewModels()
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 初始化
|
|
|
+ * @param inflater LayoutInflater
|
|
|
+ * @param container ViewGroup?
|
|
|
+ * @param savedInstanceState Bundle?
|
|
|
+ * @return View?
|
|
|
+ */
|
|
|
+ override fun onCreateView(
|
|
|
+ inflater: LayoutInflater,
|
|
|
+ container: ViewGroup?,
|
|
|
+ savedInstanceState: Bundle?
|
|
|
+ ): View? {
|
|
|
+ // todo: 2023/9/13 设置动画方向
|
|
|
+ setAnimationDirection(AnimationDirection.RIGHT)
|
|
|
+ self = this
|
|
|
+ mainView = inflater.inflate(R.layout.frag_live_stream, null)
|
|
|
+ // todo: 2023/9/13 设置导航栏
|
|
|
+ setBar(R.id.nv)
|
|
|
+ // todo: 2023/9/12 挂载控件
|
|
|
+ joinControls()
|
|
|
+ // todo: 2023/9/12 初始化页面
|
|
|
+ initPage()
|
|
|
+ return mainView
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 挂载控件
|
|
|
+ */
|
|
|
+ override fun joinControls() {
|
|
|
+ super.joinControls()
|
|
|
+ mainView?.let {
|
|
|
+ // todo: 2023/9/12 挂接直播地址选择器
|
|
|
+ wheelLiveUrl = it.findViewById(R.id.wheel_live_url)
|
|
|
+ CrUnitManager.setWheel(wheelLiveUrl!!, wheelLiveUrlListener)
|
|
|
+ // todo: 2023/9/12 挂接直播视频质量选择器
|
|
|
+ wheelLiveQuality = it.findViewById(R.id.wheel_live_quality)
|
|
|
+ CrUnitManager.setWheel(wheelLiveQuality!!, wheelLiveQualityListener)
|
|
|
+ // todo: 2023/9/12 挂接直播视频通道选择器
|
|
|
+ wheelLiveChannelType = it.findViewById(R.id.wheel_live_channel_type)
|
|
|
+ CrUnitManager.setWheel(wheelLiveChannelType!!, wheelLiveChannelTypeListener)
|
|
|
+ // todo: 2023/9/12 挂载添加直播地址按钮
|
|
|
+ btnActionAppendUrl = it.findViewById(R.id.btn_append_url)
|
|
|
+ btnActionAppendUrl?.setOnClickListener(this)
|
|
|
+ // todo: 2023/9/13 挂接直播动态信息
|
|
|
+ lblInfo = it.findViewById(R.id.live_info)
|
|
|
+ // todo: 2023/9/13 挂接直播操作按钮
|
|
|
+ btnStartLive = it.findViewById(R.id.btn_start_live)
|
|
|
+ btnStartLive?.setOnClickListener(this)
|
|
|
+ btnStopLive = it.findViewById(R.id.btn_stop_live)
|
|
|
+ btnStopLive?.setOnClickListener(this)
|
|
|
+ btnActionQuality = it.findViewById(R.id.btn_set_quality)
|
|
|
+ btnActionQuality?.setOnClickListener(this)
|
|
|
+ btnActionChannelType = it.findViewById(R.id.btn_set_channel_type)
|
|
|
+ btnActionChannelType?.setOnClickListener(this)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 初始化页面
|
|
|
+ */
|
|
|
+ override fun initPage() {
|
|
|
+ super.initPage()
|
|
|
+ // todo: 2023/9/12 初始化直播视频质量选择器
|
|
|
+ itemsLiveQuality = mutableListOf()
|
|
|
+ adapterWheelLiveQuality = ArrayWheelAdapter(itemsLiveQuality)
|
|
|
+ wheelLiveQuality?.adapter = adapterWheelLiveQuality
|
|
|
+ syncWheelLiveQuality()
|
|
|
+
|
|
|
+ // todo: 2023/9/13 初始化直播地址选择器
|
|
|
+ itemsLiveUrl = mutableListOf()
|
|
|
+ adapterWheelLiveUrl = ArrayWheelAdapter(itemsLiveUrl)
|
|
|
+ wheelLiveUrl?.adapter = adapterWheelLiveUrl
|
|
|
+ syncWheelUrl()
|
|
|
+
|
|
|
+ // todo: 2023/9/13 初始化直播通道选择器
|
|
|
+ itemsLiveChannelType = mutableListOf()
|
|
|
+ adapterWheelChannelType = ArrayWheelAdapter(itemsLiveChannelType)
|
|
|
+ wheelLiveChannelType?.adapter = adapterWheelChannelType
|
|
|
+ syncWheelChannelType()
|
|
|
+
|
|
|
+ // todo: 2023/9/13 订阅
|
|
|
+ initObserve()
|
|
|
+
|
|
|
+ // todo: 2023/9/13 初始化直播信息
|
|
|
+ clearLiveStreamInfo()
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 订阅
|
|
|
+ */
|
|
|
+ private fun initObserve() {
|
|
|
+ // todo: 2023/9/13 注册动态信息监听
|
|
|
+ liveStreamVm.streamStatusInfo.observe(requireActivity()) {
|
|
|
+ it?.let {
|
|
|
+ isStreaming = if (it.isStreaming) "是" else "否"
|
|
|
+ fps = it.fps
|
|
|
+ vbps = it.vbps
|
|
|
+ resolutionWidth = it.videoWidth
|
|
|
+ resolutionHeight = it.videoHeight
|
|
|
+ packetLoss = it.packetLoss
|
|
|
+ rtt = it.rtt
|
|
|
+ activeStreamInfo()
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // todo: 2023/9/13 注册错误监听
|
|
|
+ liveStreamVm.streamErrorInfo.observe(requireActivity()) {
|
|
|
+ it?.let {
|
|
|
+ error = it.description()
|
|
|
+ activeStreamInfo()
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 同步视频质量
|
|
|
+ */
|
|
|
+ private fun syncWheelLiveQuality() {
|
|
|
+ itemsLiveQuality?.clear()
|
|
|
+ var qualityTitleList = CrLiveManager.getInstance().getQualityList()
|
|
|
+ for (title in qualityTitleList) {
|
|
|
+ itemsLiveQuality?.add(title)
|
|
|
+ }
|
|
|
+ wheelLiveQuality?.setSelectItem(itemsLiveQuality!![0])
|
|
|
+ wheelLiveQuality?.onItemSelected()
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 同步码流通道
|
|
|
+ */
|
|
|
+ private fun syncWheelChannelType() {
|
|
|
+ itemsLiveChannelType?.clear()
|
|
|
+ var channelTypeTitleList = CrLiveManager.getInstance().getChannelTypeList()
|
|
|
+ for (title in channelTypeTitleList) {
|
|
|
+ itemsLiveChannelType?.add(title)
|
|
|
+ }
|
|
|
+ wheelLiveChannelType?.setSelectItem(itemsLiveChannelType!![0])
|
|
|
+ wheelLiveChannelType?.onItemSelected()
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 同步直播地址
|
|
|
+ */
|
|
|
+ private fun syncWheelUrl() {
|
|
|
+ itemsLiveUrl?.clear()
|
|
|
+ var urlList = CrSaveManager.getInstance().getLiveUrlList()
|
|
|
+ for (title in urlList) {
|
|
|
+ itemsLiveUrl?.add(title)
|
|
|
+ }
|
|
|
+ wheelLiveUrl?.setSelectItem(itemsLiveUrl!![itemsLiveUrl!!.size - 1])
|
|
|
+ wheelLiveUrl?.onItemSelected()
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 直播URL地址选择器监听
|
|
|
+ */
|
|
|
+ private val wheelLiveUrlListener = OnItemSelectedListener { index ->
|
|
|
+ indexLiveUrl = index
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 直播画面品质选择器监听
|
|
|
+ */
|
|
|
+ private val wheelLiveQualityListener = OnItemSelectedListener { index ->
|
|
|
+ indexLiveQuality = index
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 直播视频通道选择器监听
|
|
|
+ */
|
|
|
+ private val wheelLiveChannelTypeListener = OnItemSelectedListener { index ->
|
|
|
+ indexLiveChannelType = index
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 显示追加直播地址对话框
|
|
|
+ */
|
|
|
+ private fun showAppendLiveUrlDialog() {
|
|
|
+ var dialog = DialogInput(CrApplication.getContext())
|
|
|
+ dialog.setTitle("输入直播地址")
|
|
|
+ dialog.setLabels("直播地址", null)
|
|
|
+ dialog.setButtonsText("设置", "关闭")
|
|
|
+ dialog.setHints("输入直播地址rtmp://...", "")
|
|
|
+ dialog.setContent("rtmp://", "")
|
|
|
+ dialog.setListener(dialogInputListener)
|
|
|
+ dialog.show()
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 添加直播地址对话框监听
|
|
|
+ */
|
|
|
+ private val dialogInputListener = object : DialogInput.DialogInputListener {
|
|
|
+ // todo: 2023/9/12 完成
|
|
|
+ override fun completion(valueOne: String, valueTwo: String, self: DialogInput) {
|
|
|
+ CrSaveManager.getInstance().saveLiveUrl(valueOne)
|
|
|
+ syncWheelUrl()
|
|
|
+ }
|
|
|
+
|
|
|
+ // todo: 2023/9/12 关闭
|
|
|
+ override fun close() {
|
|
|
+
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 恢复初始状态
|
|
|
+ */
|
|
|
+ private fun clearLiveStreamInfo() {
|
|
|
+ fps = -1
|
|
|
+ vbps = -1
|
|
|
+ isStreaming = "否"
|
|
|
+ resolutionWidth = -1
|
|
|
+ resolutionHeight = -1
|
|
|
+ packetLoss = -1
|
|
|
+ rtt = -1
|
|
|
+ error = ""
|
|
|
+ lblInfo?.setTextColor(CrColorManager.getColor(R.color.live_normal))
|
|
|
+ updateLiveStreamInfo()
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 激活直播信息
|
|
|
+ */
|
|
|
+ private fun activeStreamInfo() {
|
|
|
+ lblInfo?.setTextColor(CrColorManager.getColor(R.color.live_active))
|
|
|
+ updateLiveStreamInfo()
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 更新信息展示
|
|
|
+ */
|
|
|
+ private fun updateLiveStreamInfo() {
|
|
|
+ val liveStreamInfo = "是否直播: $isStreaming \n" +
|
|
|
+ "帧率: ${fps}fps \n" +
|
|
|
+ "码率: ${vbps}Kbps \n" +
|
|
|
+ "分辨率: $resolutionWidth * $resolutionHeight \n" +
|
|
|
+ "丢包率: ${packetLoss}% \n" +
|
|
|
+ "延迟: ${rtt}ms \n" +
|
|
|
+ "错误: $error"
|
|
|
+ lblInfo?.text = liveStreamInfo
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 开启直播
|
|
|
+ */
|
|
|
+ private fun startLive() {
|
|
|
+ // todo: 2023/9/13 添加监听
|
|
|
+ liveStreamVm.addStreamStatusListener()
|
|
|
+ // todo: 2023/9/13 获取相关信息
|
|
|
+ wheelLiveQuality?.setSelectItem(
|
|
|
+ CrLiveManager.getInstance().getQualityTitle(liveStreamVm.getLiveStreamQuality())
|
|
|
+ )
|
|
|
+ wheelLiveChannelType?.setSelectItem(
|
|
|
+ CrLiveManager.getInstance().getChannelTypeTitle(liveStreamVm.getVideoChannel())
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
+ // todo: 2023/9/14 操作
|
|
|
+ /**
|
|
|
+ * 开始直播
|
|
|
+ */
|
|
|
+ private fun actionStartLive(){
|
|
|
+ // todo: 2023/9/13 判断是否选择了直播地址
|
|
|
+ if(indexLiveUrl == -1){
|
|
|
+ showWarning("请选择直播地址后重试!")
|
|
|
+ return
|
|
|
+ }
|
|
|
+ // todo: 2023/9/13 判断视频通道是否已开启
|
|
|
+ if(!liveStreamVm.channelTypeIsOpen()){
|
|
|
+ showWarning("请开启视频通道后重试!")
|
|
|
+ return
|
|
|
+ }
|
|
|
+ // todo: 2023/9/13 设置直播地址
|
|
|
+ liveStreamVm.setRTMStreamSettings(itemsLiveUrl!![indexLiveUrl])
|
|
|
+ // todo: 2023/9/13 开始直播
|
|
|
+ liveStreamVm.startStream(object : ICompletion<String> {
|
|
|
+ override fun onCompletion(completion: CompletionModel<String>) {
|
|
|
+ if (completion.isSuccess == true) {
|
|
|
+ showToast("直播开启成功!")
|
|
|
+ startLive()
|
|
|
+ // todo: 2023/9/13 发送隐藏直播信息展示组件
|
|
|
+ CrApplication.getEventBus().post(EventCommon(CrCommonAction.LIVE_INFO_STATE,true))
|
|
|
+ } else {
|
|
|
+ showError(completion.result!!)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 停止直播
|
|
|
+ */
|
|
|
+ private fun actionStopLive(){
|
|
|
+ liveStreamVm.stopStream(object : ICompletion<String> {
|
|
|
+ override fun onCompletion(completion: CompletionModel<String>) {
|
|
|
+ if (completion.isSuccess == true) {
|
|
|
+ showToast("直播已停止")
|
|
|
+ } else {
|
|
|
+ showError(completion.result!!)
|
|
|
+ }
|
|
|
+ // todo: 2023/9/13 初始化信息 移除监听
|
|
|
+ clearLiveStreamInfo()
|
|
|
+ liveStreamVm.removeStreamStatusListener()
|
|
|
+ // todo: 2023/9/13 发送隐藏直播信息展示组件
|
|
|
+ CrApplication.getEventBus().post(EventCommon(CrCommonAction.LIVE_INFO_STATE,false))
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 设置视频质量
|
|
|
+ */
|
|
|
+ private fun actionSetQuality(){
|
|
|
+ // todo: 2023/9/14 判断是否正在直播
|
|
|
+ if(!liveStreamVm.streamStatusInfo?.value!!.isStreaming){
|
|
|
+ showWarning("未直播,无法设置!")
|
|
|
+ }else{
|
|
|
+ var quality = CrLiveManager.getInstance().getQuality(itemsLiveQuality!![indexLiveQuality])
|
|
|
+ liveStreamVm.setLiveStreamQuality(quality)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 设置直播通道
|
|
|
+ */
|
|
|
+ private fun actionSetChannelType(){
|
|
|
+ // todo: 2023/9/14 判断是否正在直播
|
|
|
+ if(!liveStreamVm.streamStatusInfo?.value!!.isStreaming){
|
|
|
+ showWarning("未直播,无法设置!")
|
|
|
+ }else{
|
|
|
+ // todo: 2023/9/14 判断视频流是否开启
|
|
|
+ var channelType = CrLiveManager.getInstance().getChannelType(itemsLiveChannelType!![indexLiveChannelType])
|
|
|
+ if(liveStreamVm.getChannelStatus(channelType) != VideoChannelState.ON){
|
|
|
+ showWarning("视频通道未开启,无法设置!")
|
|
|
+ }else{
|
|
|
+ liveStreamVm.setVideoChannel(channelType)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 覆写点击事件
|
|
|
+ * @param view View
|
|
|
+ */
|
|
|
+ override fun onClick(view: View?) {
|
|
|
+ when (view?.id) {
|
|
|
+ R.id.btn_append_url -> {
|
|
|
+ // todo: 2023/9/12 添加直播地址
|
|
|
+ showAppendLiveUrlDialog()
|
|
|
+ }
|
|
|
+ R.id.btn_start_live -> {
|
|
|
+ // todo: 2023/9/14 开始直播
|
|
|
+ actionStartLive()
|
|
|
+ }
|
|
|
+ R.id.btn_stop_live -> {
|
|
|
+ // todo: 2023/9/14 停止直播
|
|
|
+ actionStopLive()
|
|
|
+ }
|
|
|
+ R.id.btn_set_quality->{
|
|
|
+ // todo: 2023/9/14 设置直播画质
|
|
|
+ actionSetQuality()
|
|
|
+ }
|
|
|
+ R.id.btn_set_channel_type->{
|
|
|
+ // todo: 2023/9/14 设置视频通道
|
|
|
+ actionSetChannelType()
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 覆写关闭方法
|
|
|
+ */
|
|
|
+ override fun dismiss() {
|
|
|
+ CrApplication.getEventBus().post(EventFragmentBarAction(self!!, BarAction.ACTION_DISMISS))
|
|
|
+ }
|
|
|
+}
|