You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Android WebRTC:如何识别远程视频流停止并提示接收端?

当然可以!针对你使用的org.webrtc:google-webrtc:1.0.21217版本,我们可以通过WebRTC的回调和状态监听来精准检测远端视频流是否停止,下面给你两种实用的实现方案和代码示例:

方案一:通过帧接收超时判断(覆盖网络中断/主动停止场景)

WebRTC的VideoTrack会通过VideoSinkonFrame方法推送视频帧,我们可以自定义一个VideoSink实现,记录最后一次收到帧的时间,定时检查是否超过设定阈值(比如3秒),以此判断远端流是否停止。

自定义VideoSink实现

import org.webrtc.VideoFrame
import org.webrtc.VideoSink
import java.util.concurrent.Executors
import java.util.concurrent.ScheduledFuture
import java.util.concurrent.TimeUnit

class RemoteVideoStatusSink(private val onVideoStopped: () -> Unit) : VideoSink {
    private var lastFrameReceivedTime = System.currentTimeMillis()
    private val scheduler = Executors.newSingleThreadScheduledExecutor()
    private var checkTask: ScheduledFuture<*>? = null
    // 超时阈值,可根据业务需求调整
    private val STOP_THRESHOLD_MS = 3000L

    init {
        // 启动每秒一次的定时检查任务
        checkTask = scheduler.scheduleAtFixedRate({
            val currentTime = System.currentTimeMillis()
            if (currentTime - lastFrameReceivedTime > STOP_THRESHOLD_MS) {
                // 触发视频停止回调
                onVideoStopped.invoke()
                // 停止检查任务,避免重复触发提示
                checkTask?.cancel(true)
            }
        }, 0, 1, TimeUnit.SECONDS)
    }

    override fun onFrame(frame: VideoFrame?) {
        frame?.let {
            // 更新最后收到帧的时间戳
            lastFrameReceivedTime = System.currentTimeMillis()
            // 如果之前触发过停止提示,恢复定时检查
            if (checkTask?.isCancelled == true) {
                restartCheckTask()
            }
        }
    }

    private fun restartCheckTask() {
        checkTask = scheduler.scheduleAtFixedRate({
            val currentTime = System.currentTimeMillis()
            if (currentTime - lastFrameReceivedTime > STOP_THRESHOLD_MS) {
                onVideoStopped.invoke()
                checkTask?.cancel(true)
            }
        }, 0, 1, TimeUnit.SECONDS)
    }

    // 释放资源(比如通话结束时调用)
    fun release() {
        checkTask?.cancel(true)
        scheduler.shutdown()
    }
}

在PeerConnection回调中使用

// 实现PeerConnection.Observer的onTrack方法
override fun onTrack(transceiver: RtpTransceiver?) {
    transceiver?.receiver?.track()?.let { track ->
        if (track is VideoTrack) {
            val videoStatusSink = RemoteVideoStatusSink {
                // 切换到UI线程显示提示
                runOnUiThread {
                    Toast.makeText(this@YourActivity, "远端视频已停止", Toast.LENGTH_LONG).show()
                }
            }
            track.addSink(videoStatusSink)
            // 注意:在通话结束或页面销毁时,记得调用videoStatusSink.release()释放资源
        }
    }
}

方案二:监听MediaStreamTrack状态变化(针对主动停止场景)

当远端主动停止视频采集(比如关闭摄像头)时,MediaStreamTrack的状态会变为ENDED,我们可以通过注册状态监听器来捕获这个事件。

代码示例

override fun onTrack(transceiver: RtpTransceiver?) {
    transceiver?.receiver?.track()?.let { track ->
        if (track is VideoTrack) {
            // 注册Track状态观察者
            track.registerTrackObserver(object : MediaStreamTrack.TrackObserver {
                override fun onChanged() {
                    val trackState = track.state()
                    runOnUiThread {
                        when (trackState) {
                            MediaStreamTrack.TrackState.ENDED -> {
                                Toast.makeText(this@YourActivity, "远端视频已停止", Toast.LENGTH_LONG).show()
                            }
                            MediaStreamTrack.TrackState.LIVE -> {
                                // 视频恢复,可隐藏提示消息
                            }
                            else -> {}
                        }
                    }
                }
            })
        }
    }
}

方案对比

  • 方案一:覆盖场景更全面,既能检测远端主动停止,也能检测网络中断导致的帧传输停止;
  • 方案二:更轻量化,仅针对远端主动停止采集的场景,依赖Track状态的正确上报。

实际项目中可以结合两种方案,提升检测的准确性和覆盖范围。

内容的提问来源于stack exchange,提问作者hemi_p

火山引擎 最新活动