logo
当前页

使用微信原生组件实现通话


功能简介

本文将介绍使用微信小程序原生推拉流组件 <live-pusher> 和 <live-player> 进行推拉流。

说明

由于微信小程序原生推拉流组件使用起来比较复杂,推荐开发者使用 ZEGO 封装的 <zego-push> 和 <zego-player> 组件实现视频通话,可参考 微信小程序 - 实现视频通话

前提条件

在实现基本的实时音视频功能之前,请确保:

实现流程

用户通过 ZEGO Express SDK 进行视频通话的基本流程为:

用户 A、B 加入房间,用户 B 预览并将音视频流推送到 ZEGO 云服务(推流),用户 A 收到用户 B 推送音视频流的通知之后,在通知中播放用户 B 的音视频流(拉流)。

1 配置微信小程序后台

在初始化 SDK 前,需要在 微信公众平台 中进行如下配置:

  • 服务器域名配置:在“小程序后台 > 开发管理 > 开发设置 > 服务器域名”中,按照协议分类,将即构 Server 地址、LogUrl、以及用户业务需要用到的地址填到指定的“request合法域名”或“socket合法域名”中。

  • 相关功能开启:在“小程序后台 > 开发管理 > 接口设置 > 接口权限”中,打开 实时播放音视频流实时录制音视频流 功能开关。

2 初始化

1. 创建界面

根据场景需要,为您的项目创建视频通话的用户界面。我们推荐您在项目中添加如下元素:

  • 本地预览窗口
  • 远端视频窗口
  • 结束按钮
注意

小程序推流组件 <live-pusher> 中的 "video-width" 和 "video-height" 存在兼容性问题,可能会出现设置不生效的情况。

参考界面代码:

<view wx:if="{{canShow== 1}}" class="">
  <view class="containerBase">
    <live-pusher class="testpusher" wx:if="{{livePusherUrl}}" url="{{livePusherUrl}}" aspect="3:4" bindstatechange="onPushStateChange" autopush mode="SD" min-bitrate="800" max-bitrate="1500"
    bindaudiovolumenotify="bindaudiovolumenotify"  bindnetstatus="onPushNetStateChange" waiting-image="https://doc-media.zego.im/downloads/pause_publish.png"   ></live-pusher>
    <live-player  wx:for="{{livePlayerList}}" wx:key="streamID" id="{{item.streamID}}" src="{{item.url}}" mode="RTC" autoplay enable-metadata="true" bindmetadatachange="binddatachange"  bindstatechange="onPlayStateChange" bindnetstatus="onPlayNetStateChange"></live-player>
  </view>
  <view class="index-container">
    <view class='input-container'>
      <input value="{{roomID}}" bindinput="bindKeyInput" placeholder="请输入房间 ID" placeholder-style='color: #b3b3b3; font-size: 14px;' class="room-input" />
      <text class="tip"></text>
    </view>
    <view class="button-container">
      <button bindtap="openRoom" data-role="1" data-option="videoAndAudio" hover-class="none" class="openRoom">
        加入房间(推流)
      </button>


      <button bindtap="logout" hover-class="none">退出房间</button>
    </view>
  </view>
</view>
<view class="settings">
  <button wx:if="{{canShow==0}}" open-type="openSetting" bindopensetting="settingCallback">
    授权使用摄像头和麦克风
  </button>
</view>

2. 创建引擎

创建 ZegoExpressEngine 引擎实例,将申请到的 AppID 传入参数 “appID”,将获取到的 Server 地址传入参数 “server”。

// 初始化实例
zg = new ZegoExpressEngine(appID, server);

如果需要注册回调,开发者可根据实际需要,实现 ZegoEvent 中的某些方法,创建引擎后可通过调用 on 接口设置回调。

zg.on('roomStateUpdate', (roomID, state, errorCode, extendedData) => {
    if (state == 'DISCONNECTED') {
        // 与房间断开了连接
	// ...
    }

    if (state == 'CONNECTING') {
        // 与房间尝试连接中
	// ...
    }

    if (state == 'CONNECTED') {
        // 与房间连接成功
	// ...
    }
})

3 登录房间

1. 获取登录 Token

登录房间需要用于验证身份的 Token,获取方式请参考 用户权限控制。如需快速调试,建议使用控制台生成的临时 Token,生成临时 Token 的具体操作请参考 控制台 - 项目管理

2. 登录房间

您可以调用 SDK 的 loginRoom 接口,传入房间 ID 参数 “roomID”、“token” 和用户参数 “user”,登录房间。您可通过监听 roomStateUpdate 回调实时监控自己在本房间内的连接状态,具体请参考 常见通知回调 中的“我在房间内的连接状态变化通知”。

roomID 和 user 的参数由您本地生成,但是需要满足以下条件:

  • 同一个 AppID 内,需保证 “roomID” 全局唯一。
  • 同一个 AppID 内,需保证 “userID” 全局唯一,建议开发者将 “userID” 与自己业务的账号系统进行关联。
注意

为避免错过任何通知,您需要在登录房间前先设置所有的监听回调(如房间状态、用户状态、流状态、推拉流状态等),具体请参考 常见通知回调

// 登录房间,成功则返回 true
const result = await zg.loginRoom(roomID, token, {userID, userName});

4 将自己的音视频流推送到 ZEGO 音视频云

创建对应业务场景的 WXML

根据您的业务场景需求,编写 WXML 文件,创建推拉流组件 <live-pusher> 和 <live-player>。

  • <live-pusher> 组件用于小程序的实时推送音视频流功能。
  • <live-player> 组件用户小程序的实时播放音视频流功能。
说明
<live-pusher class="testpusher" wx:if="{{livePusherUrl}}" url="{{livePusherUrl}}" aspect="3:4" bindstatechange="onPushStateChange" autopush mode="SD" min-bitrate="800" max-bitrate="1500"
    bindaudiovolumenotify="bindaudiovolumenotify"  bindnetstatus="onPushNetStateChange" waiting-image="https://doc-media.zego.im/downloads/pause_publish.png"   ></live-pusher>
<live-player  wx:for="{{livePlayerList}}" wx:key="streamID" id="{{item.streamID}}" src="{{item.url}}" mode="RTC" autoplay enable-metadata="true" bindmetadatachange="binddatachange"  bindstatechange="onPlayStateChange" bindnetstatus="onPlayNetStateChange"></live-player>
  • bindstatechange 表示播放状态变化事件。
  • bindaudiovolumenotify 表示播放音量大小通知。
  • bindnetstatus 表示网络状态通知。

推送音视频流到 ZEGO 音视频云

调用 startPublishingStream 获取推流地址,并将推流地址赋值给 <live-pusher> 组件的 url 属性。

推送音视频流到 ZEGO 音视频云:

您可通过监听 publisherStateUpdate 回调知晓是否成功推送音视频,具体回调设置请参考 常见通知回调 中的“房间内流状态变更的通知”。

// 推流方登录房间成功后触发推流
 let {
      url
    } = await zg.startPublishingStream(this.data.pushStreamID, publishOption);
    this.setData({
        livePusherUrl: url,
        livePusher: wx.createLivePusherContext(),
      },
      () => {
        //开始推流,若组件 live-pusher 设置autopush为true,也可以不执行这一行代码
        this.data.livePusher.start();
      }
    );

5 拉取其他用户的音视频

进行视频通话时,我们需要拉取到其他用户的音视频。

通过调用startPlayingStream 获取拉流地址,并将拉流地址赋值给 <live-player> 组件的 url 属性。

播放拉流音视频:

您可通过监听 playerStateUpdate 回调知晓是否成功拉取音视频,具体请参考 常见通知回调 中的“用户拉取音视频流的状态通知”。

远端用户推送的 “streamID” 可以从 roomStreamUpdate 回调中获得,具体回调设置请参考 常见通知回调 中的“房间内流状态变更的通知”。

const setPlayUrl = (streamID, url, context) => {
  if (!url) {
    console.log(">>>[liveroom-room] setPlayUrl, url is null");
    return;
  }
  console.log("setPlayUrl", streamID, url);
  for (let i = 0; i < context.data.livePlayerList.length; i++) {
    if (
      context.data.livePlayerList[i]["streamID"] === streamID &&
      context.data.livePlayerList[i]["url"] === url
    ) {
      console.log(
        ">>>[liveroom-room] setPlayUrl, streamID and url are repeated"
      );
      return;
    }
  }

  let streamInfo = {
    streamID: "",
    url: ""
  };
  let isStreamRepeated = false;

  // 相同 streamID 的源已存在,更新 Url
  for (let i = 0; i < context.data.livePlayerList.length; i++) {
    if (context.data.livePlayerList[i]["streamID"] === streamID) {
      isStreamRepeated = true;
      context.data.livePlayerList[i]["url"] = url;
      break;
    }
  }

  // 相同 streamID 的源不存在,创建新 player
  if (!isStreamRepeated) {
    streamInfo["streamID"] = streamID;
    streamInfo["url"] = url;
    streamInfo["playerContext"] = wx.createLivePlayerContext(streamID);
    context.data.livePlayerList.push(streamInfo);
  }
  app.globalData.livePlayerList = context.data.livePlayerList
  context.setData({
    livePlayerList: context.data.livePlayerList,
  });
};
const playAll = async (streamList, context) => {
  console.log("streamList", streamList);
  if (streamList.length === 0) {
    console.log("startPlayingStream, streamList is null");
    return;
  }

  // 获取每个 streamId 对应的拉流 url
  for (let i = 0; i < streamList.length; i++) {
    /** 开始拉流,返回拉流地址 */
    try {
      console.error('type', context.data.playSource)
      let {
        streamID,
        url
      } = await zg.startPlayingStream(
        streamList[i].streamID, {
          sourceType: context.data.playSource || "BGP"
        }
      );
      console.log("streamID", streamID, url);
      setPlayUrl(streamID, url, context);
    } catch (error) {
      console.error("error", error);
    }
  }
};
const stopPlayAll = (streamList, context) => {
  if (streamList.length === 0) {
    console.log("stopPlayAll, streamList is empty");
    return;
  }
  let playStreamList = context.data.livePlayerList;
  for (let i = 0; i < streamList.length; i++) {
    let streamID = streamList[i].streamID;
    zg.stopPlayingStream(streamID);
    // 把远程被删除的流从播放的流列表中删除
    for (let j = 0; j < playStreamList.length; j++) {
      if (playStreamList[j]["streamID"] === streamID) {
        playStreamList.splice(j, 1);
        break;
      }
    }
  }
  context.setData({
    livePlayerList: playStreamList
  });
};
// 在 SDK 的回调 roomStreamUpdate 中获取拉流 streamID
// 当用户加入或离开房间时,该事件被触发
zg.on("roomStreamUpdate", (roomID, updateType, streamList) => {
  console.warn("roomStreamUpdate", roomID, updateType, streamList);
  if (updateType === "ADD") {
    this.setData({
      streamList: streamList
    })
    playAll(streamList, this);
  } else {
    stopPlayAll(streamList, this);
  }
});

注意事项

如果用户在音视频通话的过程中,遇到相关错误,可查询 错误码

常用功能

常见通知回调

我在房间内的连接状态变化通知

roomStateUpdate:本地调用 loginRoom 加入房间时,您可通过监听该回调实时监控自己在该房间内的连接状态。

用户可以在回调中根据不同状态处理业务逻辑。

zg.on('roomStateUpdate', (roomID, state, errorCode, extendedData) => {
    if (state == 'DISCONNECTED') {
        // 与房间断开了连接
	// ...
    }

    if (state == 'CONNECTING') {
        // 与房间尝试连接中
	// ...
    }

    if (state == 'CONNECTED') {
        // 与房间连接成功
	// ...
    }
})
状态含义
DISCONNECTED未连接状态,在登录房间前/退出房间后进入该状态。如果登录房间的过程中出现稳态异常,比如 AppID 不正确,或者有相同用户名在其他地方登录导致本端被 KickOut,都会进入该状态。
CONNECTING正在请求连接状态,登录房间动作执行成功后会进入该状态。通常情况下,可通过该状态进行 UI 界面的展示。如果是因为网络质量不佳产生的中断,SDK 内部会进行重试,也会进入正在请求连接状态。
CONNECTED连接成功状态,成功登录房间后进入该状态。此时,用户可以正常收到房间内的用户和流信息增删变化的回调通知。

其他用户进出房间的通知

roomUserUpdate:同一房间内的其他用户进出房间时,您可通过此回调收到通知。登录房间后,当房间内有用户新增或删除时,SDK 会通过该回调通知。

注意

只有调用 loginRoom 接口登录房间时传入 ZegoRoomConfig 配置,且 “userUpdate” 参数取值为 “true” 时,用户才能收到 roomUserUpdate 回调。

// 用户状态更新回调
zg.on('roomUserUpdate', (roomID, updateType, userList) => {
    console.warn(
        `roomUserUpdate: room ${roomID}, user ${updateType === 'ADD' ? 'added' : 'left'} `,
        JSON.stringify(userList),
    );
});

房间内流状态变更的通知

roomStreamUpdate:流状态更新回调。登录房间后,当房间内有用户新推送或删除音视频流时,SDK 会通过该回调通知。

// 流状态更新回调
zg.on('roomStreamUpdate', async (roomID, updateType, streamList, extendedData) => {
    if (updateType == 'ADD') {
        // 流新增,开始拉流
    } else if (updateType == 'DELETE') {
        // 流删除,停止拉流
    }
});

用户推送音视频流的状态通知

用户拉取音视频流的状态通知

停止音视频通话

停止推送/拉取音视频流

1. 停止推流

调用 SDK 的 stopPublishingStream 方法通知 SDK 停止推对应 streamID 的流。

调用微信推流组件上下文的 LivePusherContext.stop 方法停止推流。

// 停止推流
this.data.livePusher.stop();

2. 停止拉流

调用 SDK 的 stopPlayingStream方法通知 SDK 停止拉对应 streamID 的流。

调用微信拉流组件上下文的 LivePlayerContext.stop 方法停止拉流。

// 停止拉流
let playStreamList = this.data.livePlayerList;
for (let i = 0; i < playStreamList.length; i++) {
  let streamID = playStreamList[i].streamID;
  zg.stopPlayingStream(streamID);
  // 把远程被删除的流从播放的流列表中删除
  playStreamList.splice(i, 1);
}
this.setData({
  livePlayerList: playStreamList
});

退出房间

调用 SDK 的 logoutRoom 接口退出房间。

zg.logoutRoom(roomID);

调试视频通话功能

在真机中运行项目,运行成功后,可以看到本端视频画面。

为方便体验,ZEGO 提供了一个 Web 端调试示例,在该页面下,输入相同的 AppID、RoomID,输入不同的 UserID、以及对应的 Token,即可加入同一房间与真机设备互通。当成功开始音视频通话时,可以听到远端的音频,看到远端的视频画面。

视频通话 API 调用时序

整个推拉流过程的 API 调用时序可参考下图:

Previous

直推 CDN

Next

限制说明