WeChat アプレットでのリアルタイムのオーディオとビデオの使用

ミニ プログラムによって提供されるライブ プッシャー コンポーネントとライブ プレーヤー コンポーネントは、主に 2 つのシナリオで使用されます。live-pusher コンポーネントと live-player コンポーネントの両方に RTC と呼ばれるモードがあり、このモードを介してアプレットでリアルタイムのビデオ通話機能を実現できます。この記事では、ライブ プッシャー コンポーネント、ライブ プレーヤー コンポーネント、Tencent Cloud リアルタイム オーディオおよびビデオ製品を使用して、ビデオ通話アプレットを開発する方法を紹介します。

リアルタイム オーディオおよびビデオ製品のアーキテクチャ

Tencent Real-Time Communication (TRTC) は、Tencent の長年にわたるネットワークおよびオーディオおよびビデオ技術における深い蓄積に基づいて、低遅延のインタラクティブ ライブ ブロードキャストと複数人のオーディオおよびビデオの 2 つのソリューションに焦点を当て、開発者にサービスを提供します。オープンであり、開発者が低コスト、低遅延、高品質のオーディオおよびビデオ インタラクティブ ソリューションを迅速に構築できるよう支援することに取り組んでいます。
ここに画像の説明を挿入

製品の特徴

  • ビデオ通話
    は2人以上のビデオ通話で、720P、1080Pの高精細画質をサポートしています。1 つの部屋で同時に最大 300 人がオンラインでサポートでき、最大 50 人が同時にカメラをオンにできます。
  • 音声通話
    は2 人以上の音声通話で、48kHz をサポートし、デュアル チャンネルをサポートします。1 つの部屋で同時に最大 300 人がオンラインでサポートでき、最大 50 人が同時にマイクをオンにすることができます。
  • ビデオ インタラクティブ ライブ ストリーミング ホストと視聴者の間のライブ ブロードキャスト
    をサポートします。アンカークロスルーム(クロスライブルーム)PKに対応。スムーズなマイクの上下をサポートし、切り替えプロセスを待つ必要がなく、アンカー遅延は 300ms 未満です。1 つの部屋でマイクに接続できる人数に制限はなく、最大 50 人が同時にマイクに接続できます。低遅延のライブ ブロードキャスト モードでは、100,000 人の視聴者が同時に再生することをサポートし、再生遅延は 1000 ミリ秒と低くなっています。CDN バイパス ライブ モードでは、視聴者数は無制限です。
  • 音声インタラクティブ ライブ ブロードキャスト アンカーと聴衆の間の音声とマイクのやり取りを
    サポートします。アンカークロスルーム(クロスライブルーム)PKに対応。スムーズなマイクの上下をサポートし、切り替えプロセスを待つ必要がなく、アンカー遅延は 300ms 未満です。1 つの部屋でマイクに接続できる人数に制限はなく、最大 50 人が同時にマイクに接続できます。低遅延のライブ ブロードキャスト モードでは、100,000 人の視聴者が同時に再生することをサポートし、再生遅延は 1000 ミリ秒と低くなっています。CDN バイパス ライブ モードでは、視聴者数は無制限です。

リアルタイム オーディオおよびビデオ SDK の使用手順

Tencent Real-Time Audio and Video SDK を使用する前に、Tencent クラウド アカウントを登録し、実名認証を完了する必要があります。登録が成功した後, リアルタイム オーディオおよびビデオ製品でリアルタイム オーディオおよびビデオ アプリケーションを作成する必要があります. Tencent Cloud は、SDKAppID と対応する UserSig キーをアプリケーションに割り当てます. これらの 2 つのパラメータは、後続のエンコーディングで使用されます. リアルタイム オーディオおよびビデオ アプリケーションを作成する操作方法は次のとおりです。 リアルタイム オーディオおよびビデオ コンソールにログインし、[アプリケーション管理] -> [アプリケーションの作成] を選択します。 リアルタイム オーディオおよびビデオ アプリケーションを正常に作成した
ここに画像の説明を挿入
後、アプリケーション リスト インターフェイスで SDKAppID を確認できます:
ここに画像の説明を挿入
開いたページで上の画像 [機能構成] をクリックすると、開いたページの [クイック スタート] で UserSig キーを確認できます:
ここに画像の説明を挿入
さらに、アプレット ページの次のビデオ コールに使用されるエンコード前の trtc-wx.js ファイルは、次の URL アドレスからダウンロードしてください:
https://web.sdk.qcloud.com/ trtc/miniapp/download/trtc-wx.zip

API 使用ガイドライン

次の図は、Tencent のリアルタイム オーディオおよびビデオ SDK ドキュメントに記載されているリアルタイム オーディオおよびビデオ アプレット API の呼び出しシーケンス図です。
ここに画像の説明を挿入

ステップ 1: TRTC の初期化

TRTC という名前のクラスが trtc-wx パッケージにエクスポートされます. ページの onLoad 関数でこのクラスをインスタンス化し、同時にプッシャーを作成し、TRTC によってスローされたイベントをリッスンする必要があります.

onLoad(options) {
    
    
    //初始化 TRTC 实例
    this.TRTC = new TRTC(this)
//创建 Pusher
const pusher = this.TRTC.createPusher({
    
    beautyLevel: 9})
    //事件监听
    this.bindTRTCRoomEvent()
  },

TRTC クラスの on(EventCode, handler, context) メソッドを使用して、TRTC によってスローされたイベントをリッスンします。次に例を示します。

//事件监听
  bindTRTCRoomEvent() {
    
    
    const TRTC_EVENT = this.TRTC.EVENT
    this.TRTC.on(TRTC_EVENT.ERROR, (event) => {
    
    
      console.log('* room ERROR', event)
    })
}

ステップ 2: ストリーミングを開始する

まず、enterRoom メソッドを呼び出して TRTC ルームに入る必要があります。その後、start() メソッドを呼び出してストリーミングを開始できます。

onLoad(options) {
    
    
    this.TRTC = new TRTC(this)
    const pusher = this.TRTC.createPusher({
    
    beautyLevel: 9})
    this.bindTRTCRoomEvent()
//进入房间
this.TRTC.enterRoom(this.data._rtcConfig)
//开始推流
this.TRTC.getPusherInstance().start() 
  }

enterRoom(params) メソッドを呼び出した後、ルームが存在しない場合、システムは自動的に新しいルームを作成し、それ以外の場合は直接ルームに入ります。

  • sdkAppID: Tencent Cloud アカウントの sdkAppID
  • userID: ルームに入室したユーザーID
  • userSig: サーバーによって発行された userSig
  • roomID: 入りたい部屋の番号。部屋が存在しない場合は、システムが自動的に作成します。
  • strRoomID: 入りたい文字列の部屋番号。このパラメーターを入力すると、文字列の部屋に優先的に入ることができます。
  • シーン: 必要なパラメーター、使用シナリオ:
    rtc: リアルタイム通話、高品質の回線を使用、同じ部屋にいる人数は 300 人を超えないようにしてください。
    ライブ: ライブ ブロードキャスト モード、混合回線を使用、オンラインの 1 つの部屋で 100,000 人をサポート (同時にマイクを使用する人の数は 50 以内に制御する必要があります)

ステップ 3: リモート ストリームを処理する

リモート エンドから新しいビデオ ストリームを受信した場合、このビデオの再生を開始し、このプレーヤーの muteVideo ステータスを false に設定できます。ここで、このプレーヤーの ID を渡す必要があります。更新が完了すると、更新された playerList list を返します。ページの playerList を同期的に更新するだけで済みます。

this.TRTC.on(TRTC_EVENT.REMOTE_VIDEO_ADD, (event) => {
    
    
      console.log('* room REMOTE_VIDEO_ADD',  event)
      const {
    
     player } = event.data
      this.setPlayerAttributesHandler(player, {
    
     muteVideo: false })
    })

リモート ストリームが縮小されたという通知を受け取った場合は、このチャンネルの登録を解除し、muteAudio を true に設定できます。

    this.TRTC.on(TRTC_EVENT.REMOTE_VIDEO_REMOVE, (event) => {
    
    
      console.log('* room REMOTE_VIDEO_REMOVE', event)
      const {
    
     player } = event.data
      this.setPlayerAttributesHandler(player, {
    
     muteVideo: true })
    })

ステップ 4: 音声通話とビデオ通話を終了する

部屋を出るには、exitRoom() を呼び出します。部屋を出るときは、次に部屋に入るときの状態の混乱を防ぐために、ステート マシンの状態をリセットしてページに同期する必要があります。

_hangUp() {
    
    
    const result = this.TRTC.exitRoom()
    this.setData({
    
    
      pusher: result.pusher,
      playerList: result.playerList,
    })
    wx.navigateBack({
    
    delta: 1,})
  }

ステップ 5: ローカルのオーディオ ストリームとビデオ ストリームをアップロードするかどうかを制御する

live-pusher タグの enable-mic と enable-camera の属性を変更する必要がある場合は、setPusherAttributes 関数を呼び出してステート マシンで管理されているプッシュ状態を変更し、返された更新された状態値をページ。

//上行音频流
this.setData({
    
    
  pusher: this.TRTC.setPusherAttributes({
    
    enableMic: true})
})
//上行视频流
this.setData({
    
    
  pusher: this.TRTC.setPusherAttributes({
    
    enableCamera: true})
})

リアルタイムのオーディオとビデオのコード例

次のコードでは、ビデオ会話機能を備えた小さなプログラムを実装します. 小さなプログラムのエントリ ページ (ホームページ) で、通話の両方のユーザーの部屋番号を指定し、現在のランダムなユーザー ID を生成します。ユーザーを選択し、ビデオ通話を行うための通話ページ (ルーム ページ) にジャンプします。次のコードは、ルーム ページのコードです。

サンプルコード: room.wxml

<view class="template-1v1">
  <view wx:for="{
     
     {playerList}}" wx:key="streamID" wx:if="{
     
     {item.src && (item.hasVideo || item.hasAudio)}}" class="view-container player-container {
     
     {item.isVisible?'':'none'}}">
    <live-player
            class="player"
            id="{
     
     {item.streamID}}"
            data-userid="{
     
     {item.userID}}"
            data-streamid="{
     
     {item.streamID}}"
            data-streamtype="{
     
     {item.streamType}}"
            src= "{
     
     {item.src}}"
            mode= "RTC"
            autoplay= "{
     
     {item.autoplay}}"
            mute-audio= "{
     
     {item.muteAudio}}"
            mute-video= "{
     
     {item.muteVideo}}"
            orientation= "{
     
     {item.orientation}}"
            object-fit= "{
     
     {item.objectFit}}"
            background-mute= "{
     
     {item.enableBackgroundMute}}"
            min-cache= "{
     
     {item.minCache}}"
            max-cache= "{
     
     {item.maxCache}}"
            sound-mode= "{
     
     {item.soundMode}}"
            enable-recv-message= "{
     
     {item.enableRecvMessage}}"
            auto-pause-if-navigate= "{
     
     {item.autoPauseIfNavigate}}"
           auto-pause-if-open-native="{
     
     {item.autoPauseIfOpenNative}}"
            debug="{
     
     {debug}}"
            bindstatechange="_playerStateChange"
            bindfullscreenchange="_playerFullscreenChange"
            bindnetstatus="_playerNetStatus"
            bindaudiovolumenotify  ="_playerAudioVolumeNotify"/>
  </view>
  <view class="view-container pusher-container {
     
     {pusher.isVisible?'':'none'}} {
     
     {playerList.length===0? 'fullscreen':''}}">
    <live-pusher
            class="pusher"
            url="{
     
     {pusher.url}}"
            mode="{
     
     {pusher.mode}}"
            autopush="{
     
     {pusher.autopush}}"
            enable-camera="{
     
     {pusher.enableCamera}}"
            enable-mic="{
     
     {pusher.enableMic}}"
            muted="{
     
     {!pusher.enableMic}}"
            enable-agc="{
     
     {pusher.enableAgc}}"
            enable-ans="{
     
     {pusher.enableAns}}"
            enable-ear-monitor="{
     
     {pusher.enableEarMonitor}}"
            auto-focus="{
     
     {pusher.enableAutoFocus}}"
            zoom="{
     
     {pusher.enableZoom}}"
            min-bitrate="{
     
     {pusher.minBitrate}}"
            max-bitrate="{
     
     {pusher.maxBitrate}}"
            video-width="{
     
     {pusher.videoWidth}}"
            video-height="{
     
     {pusher.videoHeight}}"
            beauty="{
     
     {pusher.beautyLevel}}"
            whiteness="{
     
     {pusher.whitenessLevel}}"
            orientation="{
     
     {pusher.videoOrientation}}"
            aspect="{
     
     {pusher.videoAspect}}"
            device-position="{
     
     {pusher.frontCamera}}"
            remote-mirror="{
     
     {pusher.enableRemoteMirror}}"
            local-mirror="{
     
     {pusher.localMirror}}"
            background-mute="{
     
     {pusher.enableBackgroundMute}}"
            audio-quality="{
     
     {pusher.audioQuality}}"
            audio-volume-type="{
     
     {pusher.audioVolumeType}}"
            audio-reverb-type="{
     
     {pusher.audioReverbType}}"
            waiting-image="{
     
     {pusher.waitingImage}}"
            debug="{
     
     {debug}}"
            bindstatechange="_pusherStateChangeHandler"
            bindnetstatus="_pusherNetStatusHandler"
            binderror="_pusherErrorHandler"
            bindbgmstart="_pusherBGMStartHandler"
            bindbgmprogress="_pusherBGMProgressHandler"
            bindbgmcomplete="_pusherBGMCompleteHandler"
            bindaudiovolumenotify="_pusherAudioVolumeNotify"/>
    <view class="loading" wx:if="{
     
     {playerList.length === 0}}">
      <view class="loading-img">
        <image src="../../../static/images/loading.png" class="rotate-img"></image>
      </view>
      <view class="loading-text">等待接听中...</view>
    </view>
  </view>
  <view class="handle-btns">
    <view class="btn-normal" bindtap="_pusherAudioHandler">
      <image class="btn-image" src="{
     
     {pusher.enableMic? '../../../static/images/audio-true.png': '../../../static/images/audio-false.png'}} "></image>
    </view>
    <view class="btn-normal" bindtap="_pusherSwitchCamera" >
      <image class="btn-image" src="../../../static/images/switch.png"></image>
    </view>
    <view class="btn-normal" bindtap="_setPlayerSoundMode">
      <image class="btn-image" src="{
     
     {playerList[0].soundMode === 'ear' ? '../../../static/images/speaker-false.png': '../../../static/images/speaker-true.png'}} "></image>
    </view>
  </view>
  <view class="bottom-btns">
    <view class="btn-normal" data-key="beautyLevel" data-value="9|0" data-value-type="number" bindtap="_setPusherBeautyHandle">
      <image class="btn-image" src="{
     
     {pusher.beautyLevel == 9 ? '../../../static/images/beauty-true.png': '../../../static/images/beauty-false.png'}} "></image>
    </view>
    <view class="btn-hangup" bindtap="_hangUp">
      <image class="btn-image" src="../../../static/images/hangup.png"></image>
    </view>
  </view>
</view>

サンプルコード: room.js

import TRTC from '../../../static/trtc-wx'
Page({
    
    
  data: {
    
    
    _rtcConfig: {
    
    
      sdkAppID: '', //开通实时音视频服务创建应用后分配的sdkAppID
      roomID: '', //房间号可以由您的系统指定
      userID: '', //用户ID可以由您的系统指定
      userSig: '', //身份签名,相当于登录密码的作用
    },
    roomID: 0,
    pusher: null,
    playerList: [],
  },
  onLoad(options) {
    
    
    this.TRTC = new TRTC(this)
    const pusher = this.TRTC.createPusher({
    
    beautyLevel: 9})
    this.setData({
    
    
      _rtcConfig: {
    
    
        userID: options.userID,
        sdkAppID: options.sdkAppID,
        userSig: options.userSig,
        roomID: options.roomID,
      },
      pusher: pusher.pusherAttributes
    })
    //事件监听
    this.bindTRTCRoomEvent()
    //进入房间
    this.setData({
    
    
      pusher: this.TRTC.enterRoom(this.data._rtcConfig),
    }, () => {
    
    
      this.TRTC.getPusherInstance().start() 
    })
  },
  //设置pusher属性
  setPusherAttributesHandler(options) {
    
    
    this.setData({
    
    
      pusher: this.TRTC.setPusherAttributes(options),
    })
  },
  //设置某个player属性
  setPlayerAttributesHandler(player, options) {
    
    
    this.setData({
    
    
      playerList: this.TRTC.setPlayerAttributes(player.streamID, options),
    })
  },
  //事件监听
  bindTRTCRoomEvent() {
    
    
    const TRTC_EVENT = this.TRTC.EVENT
    this.TRTC.on(TRTC_EVENT.ERROR, (event) => {
    
    
      console.log('* room ERROR', event)
    })
    //成功进入房间
    this.TRTC.on(TRTC_EVENT.LOCAL_JOIN, (event) => {
    
    
      console.log('* room LOCAL_JOIN', event)
      this.setPusherAttributesHandler({
    
     enableCamera: true })
      this.setPusherAttributesHandler({
    
     enableMic: true })
    })
    //成功离开房间
    this.TRTC.on(TRTC_EVENT.LOCAL_LEAVE, (event) => {
    
    
      console.log('* room LOCAL_LEAVE', event)
    })
    //远端用户退出
    this.TRTC.on(TRTC_EVENT.REMOTE_USER_LEAVE, (event) => {
    
    
      const {
    
     playerList } = event.data
      this.setData({
    
    
        playerList: playerList
      })
      console.log('* room REMOTE_USER_LEAVE', event)
    })
    //远端用户推送视频
    this.TRTC.on(TRTC_EVENT.REMOTE_VIDEO_ADD, (event) => {
    
    
      console.log('* room REMOTE_VIDEO_ADD',  event)
      const {
    
     player } = event.data
      // 开始播放远端的视频流,默认是不播放的
      this.setPlayerAttributesHandler(player, {
    
     muteVideo: false })
    })
    // 远端用户取消推送视频
    this.TRTC.on(TRTC_EVENT.REMOTE_VIDEO_REMOVE, (event) => {
    
    
      console.log('* room REMOTE_VIDEO_REMOVE', event)
      const {
    
     player } = event.data
      this.setPlayerAttributesHandler(player, {
    
     muteVideo: true })
    })
    // 远端用户推送音频
    this.TRTC.on(TRTC_EVENT.REMOTE_AUDIO_ADD, (event) => {
    
    
      console.log('* room REMOTE_AUDIO_ADD', event)
      const {
    
     player } = event.data
      this.setPlayerAttributesHandler(player, {
    
     muteAudio: false })
    })
    // 远端用户取消推送音频
    this.TRTC.on(TRTC_EVENT.REMOTE_AUDIO_REMOVE, (event) => {
    
    
      console.log('* room REMOTE_AUDIO_REMOVE', event)
      const {
    
     player } = event.data
      this.setPlayerAttributesHandler(player, {
    
     muteAudio: true })
    })
  },
  //挂断退出房间
  _hangUp() {
    
    
    const result = this.TRTC.exitRoom()
    this.setData({
    
    
      pusher: result.pusher,
      playerList: result.playerList,
    })
    wx.navigateBack({
    
    delta: 1,})
  },
  //设置美颜
  _setPusherBeautyHandle() {
    
    
    const  beautyLevel = this.data.pusher.beautyLevel === 0 ? 9 : 0
    this.setPusherAttributesHandler({
    
     beautyLevel })
  },
  //发布/取消发布Audio
  _pusherAudioHandler() {
    
    
    if (this.data.pusher.enableMic) {
    
    
      this.setPusherAttributesHandler({
    
     enableMic: false })
    } else {
    
    
      this.setPusherAttributesHandler({
    
     enableMic: true })
    }
  },
  _pusherSwitchCamera() {
    
    
    const  frontCamera = this.data.pusher.frontCamera === 'front' ? 'back' : 'front'
    this.TRTC.getPusherInstance().switchCamera(frontCamera)
  },
  _setPlayerSoundMode() {
    
    
    if (this.data.playerList.length === 0) {
    
    
      return
    }
    const player = this.TRTC.getPlayerList()
    const soundMode = player[0].soundMode === 'speaker' ? 'ear' : 'speaker'
    this.setPlayerAttributesHandler(player[0], {
    
     soundMode })
  },
  _pusherStateChangeHandler(event) {
    
    
    this.TRTC.pusherEventHandler(event)
  },
  _pusherNetStatusHandler(event) {
    
    
    this.TRTC.pusherNetStatusHandler(event)
  },
  _pusherErrorHandler(event) {
    
    
    this.TRTC.pusherErrorHandler(event)
  },
  _pusherBGMStartHandler(event) {
    
    
    this.TRTC.pusherBGMStartHandler(event)
  },
  _pusherBGMProgressHandler(event) {
    
    
    this.TRTC.pusherBGMProgressHandler(event)
  },
  _pusherBGMCompleteHandler(event) {
    
    
    this.TRTC.pusherBGMCompleteHandler(event)
  },
  _pusherAudioVolumeNotify(event) {
    
    
    this.TRTC.pusherAudioVolumeNotify(event)
  },
  _playerStateChange(event) {
    
    
    this.TRTC.playerEventHandler(event)
  },
  _playerFullscreenChange(event) {
    
    
    this.TRTC.playerFullscreenChange(event)
  },
  _playerNetStatus(event) {
    
    
    this.TRTC.playerNetStatus(event)
  },
  _playerAudioVolumeNotify(event) {
    
    
    this.TRTC.playerAudioVolumeNotify(event)
  },
})

ユーザー署名の生成

アプレットのエントリ ページからルーム ページに入るには、次のパラメータを渡す必要があります。

  • sdkAppID: リアルタイム オーディオおよびビデオ サービスがアクティブ化され、アプリケーションが作成された後に割り当てられる sdkAppID
  • roomID: 部屋番号
  • ユーザーID: ユーザーID
  • userSig: ID 署名
    UserSig は、悪意のある攻撃者がクラウド サービスを使用する権利を盗むのを防ぐために Tencent Cloud によって設計されたセキュリティ保護署名です。クラウド サービスを使用するには、対応する SDK の初期化またはログイン機能で、SDKAppID、UserID、および UserSig の 3 つの主要な情報を提供する必要があります。このうち、SDKAppID はアプリケーションを識別するために使用され、UserID はユーザーを識別するために使用され、UserSig は前者の 2 つに基づいて計算されたセキュリティ署名であり、HMAC SHA256 暗号化アルゴリズムによって計算されます。攻撃者が UserSig を偽造できない限り、クラウド サービスのトラフィックを盗むことはできません。UserSig を計算するためのサーバー コードは次のとおりです。
func GenUserSig(sdkappid int, key string, userid string, expire int) (string, error) {
    
    
    currTime := time.Now().Unix()
    var sigDoc map[string]interface{
    
    }
    sigDoc = make(map[string]interface{
    
    })
    sigDoc["TLS.ver"] = "2.0"
    sigDoc["TLS.identifier"] = identifier
    sigDoc["TLS.sdkappid"] = sdkappid
    sigDoc["TLS.expire"] = expire
    sigDoc["TLS.time"] = currTime
    sigDoc["TLS.sig"] = _hmacsha256(sdkappid, key, userid, currTime, expire)
    data, _:= json.Marshal(sigDoc)

    var b bytes.Buffer
    w := zlib.NewWriter(&b)
    w.Write(data)
    w.Close()
    return base64urlEncode(b.Bytes()), nil
}
func base64urlEncode(data []byte) string {
    
    
    str := base64.StdEncoding.EncodeToString(data)
    str = strings.Replace(str, "+", "*", -1)
    str = strings.Replace(str, "/", "-", -1)
    str = strings.Replace(str, "=", "_", -1)
    return str
}
func _hmacsha256(sdkappid int, key string, identifier string, currTime int64, expire int) string {
    
    
    content := "TLS.identifier:" + identifier + "\n"
    content += "TLS.sdkappid:" + strconv.Itoa(sdkappid) + "\n"
    content += "TLS.time:" + strconv.FormatInt(currTime, 10) + "\n"
    content += "TLS.expire:" + strconv.Itoa(expire) + "\n"
    h := hmac.New(sha256.New, []byte(key))
    h.Write([]byte(content ))
    return base64.StdEncoding.EncodeToString(h.Sum(nil))
}

おすすめ

転載: blog.csdn.net/gz_hm/article/details/127081790