本文将介绍腾讯云物联网(IoT)应用端 SDK 的监控拉流能力(
TXIoTMonitorSession):如何在 App 或小程序中拉取已绑定设备的实时音视频流并渲染播放,以及在此基础上进行清晰度切换、语音对讲、多通道观看、截图与录制、PTZ 云台控制等。监控拉流基于腾讯云实时音视频(TRTC)链路实现,相比传统 P2P 直连方案,在连通率、首帧速度、弱网表现与多端同看等方面具备明显优势(详情参见 方案优势)。功能概述
监控拉流指应用端主动向已绑定的设备发起一次实时观看请求,设备按请求的通道与清晰度推送音视频流,应用端订阅并渲染,全过程由
TXIoTMonitorSession 统一管理。典型能力包括:实时画面播放、高清/流畅清晰度切换、远端音频控制、双向语音对讲、多通道同时观看、画面截图与本地录制、PTZ 云台控制。监控拉流广泛应用于家庭安防、可视门铃、看护陪护、门店巡检等消费类音视频设备场景:

方案优势
TXIoTMonitorSession 底层复用腾讯云实时音视频(TRTC)的媒体网络与传输能力:设备与应用端分别就近接入 TRTC 媒体网络并在同一房间内完成推流与订阅,不依赖设备与手机之间的端到端直连。这一架构选择带来的差异如下。与传统 P2P 方案对比

前提条件
在使用监控拉流能力前,请确保已完成以下准备工作:
已开通腾讯云物联网相关服务,并在控制台完成实例、应用和设备的准备工作。可参见 开通服务。
已在客户端工程中集成 IoT 应用端 SDK 并完成登录。可参见 Android 集成与登录、iOS 集成与登录。
目标设备已绑定到当前登录账户名下,或已被分享给当前账户。可参见 设备绑定。
目标设备为支持音视频推流的设备,且当前处于在线状态。
若需要使用语音对讲,已在工程中申请并获取麦克风权限。
使用建议
调用顺序:获取监控会话对象 →
addListener 注册回调 → startRemoteView 绑定渲染视图 → startSession 开始拉流 → 收到 onSessionEstablished 后画面稳定播放 → 结束时 stopRemoteView + stopSession。先绑定视图再开始会话:
startRemoteView 可以在 startSession 之前调用,可省去一次通道协商,出图更快。尽早注册监听:在
startSession 之前完成 addListener(iOS 为 addDelegate),避免错过 onSessionEstablished 等早期回调。及时释放:不再需要监控时调用
stopSession 并移除监听,避免资源泄漏与重复回调。统一错误处理:在
onError 中集中处理错误码,结合错误码给出用户可理解的提示。警告:
监控会话对象为单例,同一时刻只存在一个监控会话。若已有会话正在运行中(已调用
startSession 且尚未 stopSession),再次调用 startSession 会被直接忽略,不会打断或切换已有会话。切换观看设备前必须先调用 stopSession。接入步骤
步骤 1:获取监控会话对象
登录成功后,通过
TXIoTEngine 实例调用 getMonitorSession() 获取监控会话对象。若返回 null(iOS 为 nil),通常是 SDK 尚未登录或登录已过期。TXIoTMonitorSession session = TXIoTEngine.getInstance(context).getMonitorSession();if (session == null) {// SDK 未登录或登录已过期,请先完成登录return;}
TXIoTMonitorSession *session = [[TXIoTEngine getInstance] getMonitorSession];if (session == nil) {// SDK 未登录或登录已过期,请先完成登录return;}
步骤 2:注册监听回调
监控会话的状态变化、首帧渲染、播放状态与错误均通过监听接口返回。请在
startSession 之前完成注册。TXIoTMonitorSessionListener monitorListener = new TXIoTMonitorSessionListener() {@Overridepublic void onSessionEstablished() {// 会话建立成功,所有已请求通道进入播放状态}@Overridepublic void onRenderFirstFrame(int channelId) {// 指定通道首帧已渲染,此时可隐藏加载动画}@Overridepublic void onPlayStateChanged(int channelId, TXIoTPlayState state) {// PLAYING / LOADING / STOPPED,用于驱动 loading 与占位图}@Overridepublic void onSessionReconnecting() {// 链路中断,SDK 正在自动重连}@Overridepublic void onSessionRecovery() {// 重连成功,画面恢复}@Overridepublic void onError(int channelId, TXIoTErrorCode errCode, String errMsg) {// 统一处理错误码与用户提示}// 其他回调按需实现...};session.addListener(monitorListener);
@interface MonitorViewController () <TXIoTMonitorSessionDelegate>@end@implementation MonitorViewController- (void)onSessionEstablished {// 会话建立成功,所有已请求通道进入播放状态}- (void)onRenderFirstFrame:(NSInteger)channelId {// 指定通道首帧已渲染,此时可隐藏加载动画}- (void)onPlayStateChanged:(NSInteger)channelId state:(TXIoTPlayState)state {// Playing / Loading / Stopped,用于驱动 loading 与占位图}- (void)onSessionReconnecting {// 链路中断,SDK 正在自动重连}- (void)onSessionRecovery {// 重连成功,画面恢复}- (void)onError:(NSInteger)channelIderrorCode:(TXIoTErrorCode)errorCodeerrorMessage:(NSString *)errorMessage {// 统一处理错误码与用户提示}@end// 注册代理[session addDelegate:self];// 退出时注销// [session removeDelegate:self];
步骤 3:绑定渲染视图并开始拉流
先通过
startRemoteView 为目标通道绑定渲染视图并指定清晰度,再调用 startSession 与设备建立会话。方法 | 说明 |
startRemoteView | 为 channelId 通道绑定渲染视图并播放实时画面。channelId 表示设备的视频通道,每个通道对应设备的一个摄像头,需与设备端通道编号对齐,单通道设备使用 0;streamType 为码流类型(HD 高清 / SD 流畅)。小程序端不传视图对象,需先用 setIoTPlayer 绑定 <iot-player> 组件。 |
startSession | 与目标设备建立监控会话, deviceId 为目标设备标识(productId + deviceName)。 |
// remoteView 为布局中的 TXCloudVideoViewTXCloudVideoView remoteView = findViewById(R.id.remote_view);int channelId = 0; // 单通道设备使用 0// 先绑定渲染视图,再开始会话,可减少一次通道协商session.startRemoteView(channelId, TXIoTStreamType.HD, remoteView);TXIoTDeviceId deviceId = new TXIoTDeviceId();deviceId.productId = "已绑定到当前账户的 productId";deviceId.deviceName = "已绑定到当前账户的 deviceName";session.startSession(deviceId);
// remoteView 为布局中的 UIViewUIView *remoteView = self.remoteVideoView;NSInteger channelId = 0; // 单通道设备使用 0// 先绑定渲染视图,再开始会话,可减少一次通道协商[session startRemoteView:channelId streamType:TXIoTStreamTypeHD view:remoteView];TXIoTDeviceId *deviceId = [[TXIoTDeviceId alloc] init];deviceId.productId = @"已绑定到当前账户的 productId";deviceId.deviceName = @"已绑定到当前账户的 deviceName";[session startSession:deviceId];
说明:
即使设备没有视频画面(例如纯语音对讲场景),小程序端仍需通过
setIoTPlayer 绑定 <iot-player> 组件,音频通过该组件播放。步骤 4:停止拉流并释放资源
退出监控页面时,先停止通道播放,再停止会话并移除监听。
session.stopRemoteView(0);session.stopSession();session.removeListener(monitorListener);
[session stopRemoteView:0];[session stopSession];[session removeDelegate:self];
// 一般在页面 onHide / onUnload 中调用session.stopRemoteView(0);session.stopSession();session.removeListener(monitorListener);
常用能力
切换视频清晰度
观看过程中可动态切换通道画面的清晰度,无需重新建立会话。应用端切换后,SDK 会将清晰度要求同步给设备端,由设备推送对应清晰度的视频流。
注意:
不同清晰度可能对应不同的计费标准,请确保应用端实际使用的清晰度与用户购买的套餐一致,避免因清晰度与计费套餐不匹配引发计费争议。
// 切换为流畅流session.switchRemoteStream(0, TXIoTStreamType.SD);// 切换为高清流session.switchRemoteStream(0, TXIoTStreamType.HD);
// 切换为流畅流[session switchRemoteStream:0 streamType:TXIoTStreamTypeSD];// 切换为高清流[session switchRemoteStream:0 streamType:TXIoTStreamTypeHD];
远端音频控制与语音对讲
muteRemoteAudio 控制单个通道的远端音频,muteAllRemoteAudio 控制所有通道。开启语音对讲时调用 startLocalAudio 采集并上行本地音频,结束时调用 stopLocalAudio;对讲期间可用 muteLocalAudio 临时停止上行。// 静音 0 号通道的远端声音session.muteRemoteAudio(0, true);// 静音全部通道session.muteAllRemoteAudio(true);// 开启语音对讲(需先获取麦克风权限)session.startLocalAudio();// 对讲过程中临时停止上行session.muteLocalAudio(true);// 结束对讲session.stopLocalAudio();
// 静音 0 号通道的远端声音[session muteRemoteAudio:0 mute:YES];// 静音全部通道[session muteAllRemoteAudio:YES];// 开启语音对讲(需先获取麦克风权限)[session startLocalAudio];// 对讲过程中临时停止上行[session muteLocalAudio:YES];// 结束对讲[session stopLocalAudio];
多通道同时拉流
枪球一体机、多目摄像机等设备包含多个视频通道。为每个通道分别调用
startRemoteView 绑定独立视图即可同时观看,SDK 会在一次会话内统一订阅这些通道。说明:
建议在调用
startSession 之前为全部需要观看的通道都完成 startRemoteView;会话建立后新增通道会触发一次通道协商,画面出图相对更慢。所有已请求通道均进入播放状态后才会回调 onSessionEstablished。// 同时观看 0、1 两个通道session.startRemoteView(0, TXIoTStreamType.HD, remoteView0);session.startRemoteView(1, TXIoTStreamType.HD, remoteView1);session.startSession(deviceId);// 仅关闭 1 号通道,0 号通道继续播放session.stopRemoteView(1);
// 同时观看 0、1 两个通道[session startRemoteView:0 streamType:TXIoTStreamTypeHD view:self.remoteView0];[session startRemoteView:1 streamType:TXIoTStreamTypeHD view:self.remoteView1];[session startSession:deviceId];// 仅关闭 1 号通道,0 号通道继续播放[session stopRemoteView:1];
截图与本地录制
takeSnapshot 对指定通道的当前画面截图,结果通过 onSnapshotComplete 返回。startLocalRecording 将指定通道的音视频录制到本地文件,录制过程通过 onLocalRecordBegin、onLocalRecording、onLocalRecordComplete 回调通知。注意:
TXIoTLocalRecordingParams.filePath 为必填项。若传入空字符串,SDK 会通过 onLocalRecordBegin 回调 ERR_INVALID_PARAMETER(iOS 为 TXIoTErrorCodeInvalidParameter)而不会开始录制。多通道设备的截图与录制均按 channelId 独立进行,如需同时录制多个通道,请对每个通道分别调用。// 截图,结果通过 onSnapshotComplete(channelId, bitmap, errCode) 返回session.takeSnapshot(0);// 本地录制TXIoTLocalRecordingParams params = new TXIoTLocalRecordingParams();params.filePath = getExternalFilesDir(null) + "/monitor_record.mp4";session.startLocalRecording(0, params);// 结束录制,完成后回调 onLocalRecordCompletesession.stopLocalRecording(0);
// 截图,结果通过 onSnapshotComplete:image:errorCode: 返回[session takeSnapshot:0];// 本地录制TXIoTLocalRecordingParams *params = [[TXIoTLocalRecordingParams alloc] init];params.filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"monitor_record.mp4"];[session startLocalRecording:0 params:params];// 结束录制,完成后回调 onLocalRecordComplete:errorCode:storagePath:[session stopLocalRecording:0];
PTZ 云台控制
对支持云台的设备,通过
sendPTZCommand 下发转动与变焦指令,speed 为转动速度。手势拖动等高频场景下 SDK 会自动合并待发送指令并串行下发,业务层无需自行节流。松手时下发 STOP 停止转动。// 向左转动session.sendPTZCommand(0, TXIoTPTZCommand.LEFT, 5);// 松手停止session.sendPTZCommand(0, TXIoTPTZCommand.STOP, 0);// 画面放大session.sendPTZCommand(0, TXIoTPTZCommand.ZOOM_IN, 5);
// 向左转动[session sendPTZCommand:0 command:TXIoTPTZCommandLeft speed:5];// 松手停止[session sendPTZCommand:0 command:TXIoTPTZCommandStop speed:0];// 画面放大[session sendPTZCommand:0 command:TXIoTPTZCommandZoomIn speed:5];
状态与回调通知
监控会话通过监听接口(Android:
TXIoTMonitorSessionListener,iOS:TXIoTMonitorSessionDelegate)回调状态与错误:回调 | 触发时机 | 关键参数 |
onSessionEstablished | 监控会话建立成功,所有已请求通道均进入播放状态。 | 无。 |
onSessionReconnecting | 会话中断,SDK 正在自动重连。 | 无。 |
onSessionRecovery | 会话重连成功,画面恢复。 | 无。 |
onRenderFirstFrame | 指定通道首帧已渲染,可用于隐藏加载动画。 | channelId |
onPlayStateChanged | 播放状态变化,用于驱动 loading 与占位图。 | channelId, state(PLAYING / LOADING / STOPPED) |
onSnapshotComplete | 截图完成。 | channelId 与截图结果(Android 为 Bitmap,iOS 为 TXImage,小程序为图片路径) |
onLocalRecordBegin | 本地录制开始,或因参数非法未能开始。 | channelId, errCode, storagePath |
onLocalRecording | 录制进度更新。 | channelId, durationMs, storagePath |
onLocalRecordComplete | 本地录制结束。 | channelId, errCode, storagePath |
onError | 会话或某通道发生错误。 | channelId, errCode, errMsg |
注意:
会话中断时会先触发
onSessionReconnecting,SDK 自动尝试重连;重连成功触发 onSessionRecovery,无需业务层主动重建会话。若重连仍然失败,最终通过 onError 回调错误。常见问题
调用 startSession 后一直没有画面,如何排查?
按以下顺序排查:
1. 确认监听在
startSession 之前注册,否则可能错过 onSessionEstablished 与早期的 onError。2. 检查是否收到
onError。若为 ERR_DEVICE_OFFLINE,说明设备已离线;若为 ERR_UNAUTHORIZED_OPERATION,说明当前账户没有该设备的权限,请先完成 设备绑定。3. 确认
startRemoteView 传入的 channelId 与设备端通道编号一致。通道编号不匹配时设备不会推送对应通道的流,onPlayStateChanged 会长期停留在 LOADING。4. 确认渲染视图已正确加载且尺寸不为0(小程序端确认
setIoTPlayer 传入的 component-id 与 <iot-player> 一致)。5. 若通道长时间处于
LOADING,SDK 会判定为加载超时并自动重试一次;仍失败则通过 onError 回调,此时应引导用户检查设备网络与在线状态。监控过程中会被视频通话打断吗?
会。监控拉流的优先级低于视频通话。若设备正在被监控,此时对该设备发起视频通话,视频通话会抢占设备的推流能力:应用端会收到
onError 回调 ERR_DEVICE_SWITCH_TO_VOIP(iOS 为 TXIoTErrorCodeDeviceSwitchToVoIP),且当前监控会话会自动停止。业务层应在收到该错误后更新界面状态,不需要再调用 stopSession。视频通话相关能力参见 视频通话。网络抖动导致画面中断,需要业务层自己重连吗?
不需要。SDK 内置自动重连:链路中断后先回调
onSessionReconnecting,重连成功回调 onSessionRecovery。建议在 onSessionReconnecting 中显示重连提示,在 onSessionRecovery 中恢复正常界面。仅当 SDK 自动重连失败时才会通过 onError 回调错误,此时再由业务层决定是否引导用户手动重试。可以同时监控多台设备吗?
不可以。监控会话对象为单例,同一时刻只存在一个监控会话。切换观看设备时,必须先调用
stopSession 停止当前会话,再对新设备调用 startSession;未先停止会话时,新的 startSession 调用会被直接忽略。一台设备有多个摄像头,如何同时查看?
截图与录制可以对多个通道同时进行吗?
可以。
takeSnapshot、startLocalRecording、stopLocalRecording 均以 channelId 为维度独立生效,需要对多个通道同时操作时,对每个通道分别调用即可,各通道的回调通过参数中的 channelId 区分。接口参考
监控拉流相关接口的完整定义参见: