集成DingRTC 小程序 SDK实现基本功能

更新时间:
复制为 MD 格式

DingRTC的基本功能包含初始化SDK、加入频道、本地发布、订阅远端和离开频道等。通过阅读本文,您可以了解DingRTC的基本功能。

前提条件

基本概念

您在接入DingRTC 小程序 SDK时,会接触到以下基础概念:

  • DingRTCClient类,它的实例代表本地客户端,其方法提供加入、离开房间,发布、订阅音视频流等功能;

基本逻辑

  1. 调用createClient()方法创建一个客户端实例;

  2. 调用join()进入一个RTC频道;

  3. 调用publish() 获取推流URL,启动live-pusher完成音视频推流;

  4. 订阅远端音视频轨道:

在加入频道前监听"user-published"事件,可获取到所有远端用户的推流事件,其回调参数包含发布人的信息与音视频轨道的类型;

调用subscribe('mcu', 'audio')来获取频道音频的拉流URL,启动live-player实现音频播放;

调用subscribe(userId, 'video',false)来获取用户视频频的拉流URL,启动live-player实现该用户的视频播放;

操作步骤

说明

本文中的实现方法仅供参考,您可以根据实际业务需求进行开发。

  1. 创建Client对象。

    const DingRTC = require('./path/to/dingrtc_mini_program.umd.0.7.0.js');
    const client = DingRTC.createClient();
  2. 加入频道。

    await client.join({
      appId: 'your_app_id',      // 您在 DingRTC 项目的 AppId
      token: 'your_token',        // 频道鉴权令牌
      uid: 'user_001',            // 用户 ID,仅支持 [A-Za-z0-9_-],长度不超过 64 个字符
      channel: 'channel_001',     // 频道 ID,仅支持 [A-Za-z0-9_-],长度不超过 64 个字符
      userName: '张三'             // 用户名称,长度不超过 UTF-8 编码 64 个字节
    });

    上述代码中的you token需要客户参考使用Token鉴权自行开发。

    加入频道所需信息:

    属性

    类型

    描述

    appId

    string

    您在 DingRTC 项目的 AppId,仅支持大小写字母、数字和下划线。

    channel

    string

    频道 Id,字符内容只允许[A-Za-z0-9_-],长度不超过64个字符。

    token

    string

    频道鉴权令牌

    uid

    string

    标识用户的 Id,字符内容只允许[A-Za-z0-9_-],长度不超过64个字符。

    说明

    同一个用户Id在其他端登录,先入会的端会被后入会的端踢出频道。

    userName

    string

    用户名称,长度不超过UTF-8编码64个字节。

  3. 发布或取消发布本地音视频。

    • 获取发布音频+视频的推流url

      // 调用 publish 方法,SDK 会返回推流配置
      const publishResult = await client.publish();
      
      // publishResult 中包含 pusherUrl,需要绑定到 <live-pusher> 组件
      this.setData({
        pusherUrl: publishResult.pusherUrl,
        enableMic: true,
        enableCamera: true
      });
    • 结合微信组件 live-pusher 实现推流

      <live-pusher 
        id="pusher" 
        url="{{pusherUrl}}" 
        mode="RTC" 
        autopush="{{pusherUrl ? true : false}}"
        enable-mic="{{enableMic}}" 
        enable-camera="{{enableCamera}}"
        device-position="front"
        bindstatechange="onPusherStateChangeEvent" 
        bindnetstatus="onPusherNetStatusEvent"
        binderror="onPushError" />
    • 传递<live-pusher>组件状态给 SDK

      / 网络状态回调
      onPusherNetStatusEvent(event) {
        client.reportPusherNetStatus(event.detail);
      },
      
      // 状态变化回调
      onPusherStateChangeEvent(event) {
        client.reportPusherStateChange(event.detail);
      },
      
      // 错误回调
      onPushError(event) {
        console.error('推流错误:', event.detail);
        client.reportPusherStateChange(event.detail);
      }
    • 音视频采集设备控制(非SDK提供,通过组建属性控制)

      // 静音麦克风
      this.setData({
        enableMic: false
      });
      
      // 取消麦克风静音
      this.setData({
        enableMic: true
      });
      
      // 关闭摄像头
      this.setData({
        enableCamera: false
      });
      
      // 开启摄像头
      this.setData({
        enableCamera: true
      });
    • 取消发布

      await client.unpublish();
      
      // 同时清空页面上的推流 URL
      this.setData({
        pusherUrl: ''
      });

  4. 订阅或取消订阅音视频流。

    • 监听远端用户推流事件

      client.on('user-published', async (user, mediaType, auxiliary) => {
        console.log(`用户 ${user.userId} 发布了 ${mediaType}`);
        
        if (mediaType === 'audio') {
          // 订阅音频合流(全局只需订阅一次)
          const subscribeResult = await client.subscribe('mcu', 'audio', false);
          
          // 更新音频拉流 URL
          this.setData({
            audioRtmpPlayUrl: subscribeResult.rtmpPullUrl
          });
          
        } else if (mediaType === 'video' && !auxiliary) {
          // 订阅视频
          const subscribeResult = await client.subscribe(user.userId, 'video', false);
          
          // 更新用户列表中的视频 URL
          this.setData({
            userList: this.data.userList.map(u => {
              if (u.userId === user.userId) {
                return { ...u, hasVideo: true, rtmpPullUrl: subscribeResult.rtmpPullUrl };
              }
              return u;
            })
          });
        }
      });
      • 音频订阅:当前不支持指定到个人的音频订阅,必须使用 'mcu' 作为 userId 来订阅音频合流

      • 视频订阅:每个远端用户需要单独订阅,获取各自的 RTMP 拉流 URL

      • auxiliary 参数表示是否为辅助流(如屏幕共享),通常为 false

    • 在 WXML 中使用 live-player 组件实现音频合流播放器(隐藏,全局只需一个):

      <live-player 
        wx:if="{{audioRtmpPlayUrl}}" 
        src="{{audioRtmpPlayUrl}}" 
        autoplay="{{audioRtmpPlayUrl ? true : false}}" 
        mode="RTC" 
        style="position: absolute; top:-1rpx; left:-1rpx; width: 1rpx; height: 1rpx;"
        data-user-id="{{'mcu'}}" 
        min-cache="0.2" 
        max-cache="0.8" 
        bindstatechange="onMcuAudioPlayerStateChange" 
        bindnetstatus="onMcuAudioPlayerNetStatus" 
        binderror="onPlayerError" />
    • 在 WXML 中使用 live-player 组件实现视频播放器(每个远端用户一个):

      <block wx:for="{{userList}}" wx:key="userId">
        <live-player 
          wx:if="{{item.hasVideo && item.rtmpPullUrl}}" 
          id="{{item.userId}}" 
          src="{{item.rtmpPullUrl}}" 
          autoplay="true" 
          muted="false" 
          object-fit="cover" 
          style="width:100%;height:100%" 
          data-user-id="{{item.userId}}" 
          min-cache="0.2" 
          max-cache="0.8" 
          bindstatechange="onVideoPlayerStateChange" 
          bindnetstatus="onVideoPlayerNetStatus" 
          binderror="onPlayerError" />
      </block>
    • 传递<live-player>组件状态给 SDK

      // 音频合流状态回调
      onMcuAudioPlayerNetStatus(event) {
        const subParam = {
          uid: 'mcu',
          mediaType: 'audio',
          auxiliary: false,
        };
        client.reportPlayerNetStatus(subParam, event.detail);
      },
      
      onMcuAudioPlayerStateChange(event) {
        const subParam = {
          uid: 'mcu',
          mediaType: 'audio',
          auxiliary: false,
        };
        client.reportPlayerStateChange(subParam, event.detail);
      },
      
      // 视频状态回调
      onVideoPlayerNetStatus(event) {
        const userId = event.currentTarget.dataset.userId;
        const subParam = {
          uid: userId,
          mediaType: 'video',
          auxiliary: false,
        };
        client.reportPlayerNetStatus(subParam, event.detail);
      },
      
      onVideoPlayerStateChange(event) {
        const userId = event.currentTarget.dataset.userId;
        const subParam = {
          uid: userId,
          mediaType: 'video',
          auxiliary: false,
        };
        client.reportPlayerStateChange(subParam, event.detail);
      },
      
      // 错误回调
      onPlayerError(event) {
        const userId = event.currentTarget.dataset.userId;
        console.error(`播放器错误 [${userId}]:`, event.detail);
      }
    • 取消订阅音频和视频

      // 取消订阅视频
      await client.unsubscribe(userId, 'video', false);
      
      // 更新页面数据,移除视频 URL
      this.setData({
        userList: this.data.userList.map(u => {
          if (u.userId === userId) {
            return { ...u, hasVideo: false, rtmpPullUrl: '' };
          }
          return u;
        })
      });
      
      // 取消订阅音频合流(注意:音频仅支持 mcu 合流模式)
      await client.unsubscribe('mcu', 'audio');
      
      // 更新页面数据,标记音频已取消订阅
      this.setData({
        hasAudioSubscribed: false
      });
      • 视频: 可以针对特定用户取消订阅,传入该用户的 userId

      • 音频: 由于小程序 SDK 仅支持 'mcu' 合流模式,取消音频订阅时必须使用 userId = 'mcu',无法单独取消某个用户的音频

      • 取消订阅后,应及时清理页面上对应的资源(如移除 live-player 组件、清空播放地址等),避免内存泄漏

      • 如果用户完全离开频道,SDK 会自动触发 user-unpublished 和 participant-left 事件,无需手动取消订阅

  5. 重连处理。

    // 监听推流重连
    client.on('pub-media-reconnect-started', () => {
      wx.showToast({ title: '推流重连中...', icon: 'none' });
    });
    
    client.on('pub-media-reconnect-succeeded', (newRtmpUrl) => {
      wx.showToast({ title: '推流重连成功', icon: 'none' });
      // 必须更新 live-pusher 的 url 属性
      this.setData({ pusherUrl: newRtmpUrl });
    });
    
    client.on('pub-media-reconnect-failed', () => {
      wx.showModal({ title: '推流重连失败', content: '请检查网络', showCancel: false });
    });
    
    // 监听订阅重连
    client.on('sub-media-reconnect-started', (subParam) => {
      console.log(`用户 ${subParam.uid} 视频重连中...`);
    });
    
    client.on('sub-media-reconnect-succeeded', (subParam, newRtmpUrl) => {
      console.log(`用户 ${subParam.uid} 视频重连成功`);
      // 必须更新对应 live-player 的 src 属性
      this.updatePlayerUrl(subParam.uid, newRtmpUrl);
    });
  6. 错误处理。

    try {
      await client.join({
        appId: 'your_app_id',
        channel: 'channel_001',
        token: 'your_token',
        uid: 'user_001',
        userName: '张三'
      });
    } catch (error) {
      console.error('加入频道失败:', error);
      
      // 获取错误码和错误信息
      const errorCode = error.code;
      const errorMsg = DingRTC.ErrorCodeAndMsgMap[errorCode] || error.message;
      
      console.error(`错误码: ${errorCode}, 错误信息: ${errorMsg}`);
      
      // 根据错误码进行相应处理
      switch (errorCode) {
        case 'INVALID_TOKEN':
        case 'TOKEN_EXPIRED':
          // Token 无效,需要重新获取
          console.warn('Token 已失效,请重新获取');
          break;
          
        case 'UID_ALREADY_IN_USE':
          // UID 已被使用,需要更换 UID
          console.warn('该 UID 已被使用,请更换 UID');
          break;
          
        case 'PERMISSION_DENIED':
          // 权限被拒绝,引导用户授权
          wx.showModal({
            title: '权限提示',
            content: '需要摄像头和麦克风权限才能进行视频通话',
            success: (res) => {
              if (res.confirm) {
                wx.openSetting();
              }
            }
          });
          break;
          
        case 'NETWORK_ERROR':
          // 网络错误,提示用户检查网络
          wx.showToast({
            title: '网络连接异常',
            icon: 'none'
          });
          break;
          
        default:
          wx.showToast({
            title: '操作失败',
            icon: 'none'
          });
      }
    }
  7. 离开频道。

    async leaveChannel() {
      try {
        const leaveResult = await client.leave();
        console.log('离开频道成功:', leaveResult);
        
        // 清空页面数据
        this.setData({
          pusherUrl: '',
          audioRtmpPlayUrl: '',
          userList: []
        });
        
        wx.navigateBack();
      } catch (error) {
        console.error('离开频道失败:', error);
      }
    }
    • 离开频道时务必调用 leave() 方法,以释放相关资源

    • 页面卸载时应确保正确清理客户端实例

    • 建议在 onUnload() 生命周期中调用 leave()

后续步骤

您可以下载示例代码,快速运行Demo,实现频道内和其他人进行实时音视频通话,详情请参见运行 小程序 Demo