分析 騰訊的 實時音視頻web端demo代碼


 首先我們要會引入TRTC Web SDK

 下載地址:https://cloud.tencent.com/document/product/647/16863#script-.E9.9B.86.E6.88.90

騰訊流播放器文檔地址:https://cloud.tencent.com/document/product/881/20207

騰訊點播播放器與超級播放器:使用文檔:https://cloud.tencent.com/document/product/266/14424

騰訊點播播放器:開發文檔:https://cloud.tencent.com/document/product/266/14603

web端實時通訊IM:https://cloud.tencent.com/document/product/269/37411

 

 

 

 

 

 

 

 

 目前還不知道cdn地址

demo中的index.html中說的很明確,哪些是第三方庫,哪些是腳本

 先看一下index.html的代碼:

<!doctype html>
<html lang="en">

<head>
  <!-- Required meta tags -->
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

  <!-- Material Design for Bootstrap fonts and icons -->
  <!-- <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700|Material+Icons"> -->

  <!-- Material Design for Bootstrap CSS -->
  <link rel="stylesheet" href="./css/bootstrap-material-design.min.css">
  <link rel="stylesheet" href="./css/common.css">
  <link rel="stylesheet" href="./css/toastify.min.css">
  
  <title>TRTC Web SDK Samples - 基礎音視頻通話</title>
</head>

<body>
  <nav class="navbar navbar-light fixed-top rtc-primary-bg">
    <h5>基礎音視頻通話</h5>
  </nav>
  <form id="form">
    <div class="custom-container container">
      <div class="row">
        <div class="custom-row-container">
          <div class="row">
            <div class="col-ms">
              <div class="card custom-card">
                <div class="form-group">
                  <label for="userId" class="bmd-label-floating">用戶ID:</label>
                  <input type="text" class="form-control" name="userId" id="userId">
                </div>
                <div class="form-group bmd-form-group">
                  <label for="roomId" class="bmd-label-floating">房間號:</label>
                  <input type="text" class="form-control" name="roomId" id="roomId">
                </div>
                <div class="form-group bmd-form-group">
                  <button id="join" type="button" class="btn btn-raised btn-primary rtc-primary-bg">加入房間</button>
                  <button id="leave" type="button" class="btn btn-raised btn-primary rtc-primary-bg">離開房間</button>
                  <button id="publish" type="button" class="btn btn-raised btn-primary rtc-primary-bg">開始推流</button>
                  <button id="unpublish" type="button" class="btn btn-raised btn-primary rtc-primary-bg">停止推流</button>
                </div>
              </div>
              <div class="card">
                <button class="btn btn-raised rtc-expand-btn" id="settings" data-toggle="collapse"
                  data-target="#setting-collapse" aria-expanded="false" aria-controls="collapse">
                  設置
                </button>
                <div id="setting-collapse" class="collapse" aria-labelledby="setting-collapse">
                  <div class="card-body">
                    <div class="form-group">
                      <label for="cameraId" class="bmd-label-floating">攝像頭</label>
                      <select class="form-control" id="cameraId" name="cameraId">
                      </select>
                    </div>
                    <div class="form-group">
                      <label for="microphoneId" class="bmd-label-floating">麥克風</label>
                      <select class="form-control" id="microphoneId" name="microphoneId">
                      </select>
                    </div>
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </form>
  <div class="video-grid" id="video_grid">
    <div id="local_stream" class="video-placeholder">
      <div id="local_video_info" class="video-info"></div>
    </div>
  </div>

  <!-- Optional JavaScript -->
  <!-- jQuery first, then Popper.js, then Bootstrap JS -->
  <!-- Demo 相關第三方庫-->
  <script src="./js/jquery-3.2.1.min.js"></script>
  <script src="./js/popper.js"></script>
  <script src="./js/toastify.js"></script>
  <script src="./js/bootstrap-material-design.min.js"></script>
  <script>$(document).ready(function () { $('body').bootstrapMaterialDesign(); });</script>
  <!-- 引入 TRTC WEB SDK 腳本 -->
  <script src="./js/trtc.js"></script>
  <!-- Demo 相關腳本 -->
  <script src="./js/lib-generate-test-usersig.min.js"></script>
  <script src="./js/debug/GenerateTestUserSig.js"></script>
  <script src="./js/utils.js"></script>
  <script src="./js/rtc-client.js"></script>
  <script src="./js/index.js"></script>
</body>

</html>
View Code

 里面有:輸入用戶ID、房間號的輸入框;還有進入房間、離開房間、開始推流、停止推流按鈕

下面來看看邏輯代碼index.js

/* eslint-disable require-jsdoc */

// initialize userId/roomId
$('#userId').val('user_' + parseInt(Math.random() * 100000000));
$('#roomId').val('889988');

let rtc = null;

$('#join').on('click', function(e) {
  e.preventDefault();
  console.log('join');
  if (rtc) return;
  const userId = $('#userId').val();
  const roomId = $('#roomId').val();
  const config = genTestUserSig(userId);
  rtc = new RtcClient({
    userId,
    roomId,
    sdkAppId: config.sdkAppId,
    userSig: config.userSig
  });
  rtc.join();
});

$('#publish').on('click', function(e) {
  e.preventDefault();
  console.log('publish');
  if (!rtc) {
    Toast.error('請先加入房間!');
    return;
  }
  rtc.publish();
});

$('#unpublish').on('click', function(e) {
  e.preventDefault();
  console.log('unpublish');
  if (!rtc) {
    Toast.error('請先加入房間!');
    return;
  }
  rtc.unpublish();
});

$('#leave').on('click', function(e) {
  e.preventDefault();
  console.log('leave');
  if (!rtc) {
    Toast.error('請先加入房間!');
    return;
  }
  rtc.leave();
  rtc = null;
});

$('#settings').on('click', function(e) {
  e.preventDefault();
  $('#settings').toggleClass('btn-raised');
  $('#setting-collapse').collapse();
});
View Code

可以看出。先讓給用戶ID和房間號設置了值,

點擊進入房間按鈕的代碼:

$('#join').on('click', function(e) {
  e.preventDefault();
  console.log('join');
  if (rtc) return;
  const userId = $('#userId').val();
  const roomId = $('#roomId').val();
  const config = genTestUserSig(userId);//根據userId生成一個對象含:sdkAppId、userSig,具體代碼見GenerateTestUserSig.js
  rtc = new RtcClient({//創建一個rtc實例  傳入userid、房間號、sdkAppId、userSig
    userId,
    roomId,
    sdkAppId: config.sdkAppId,
    userSig: config.userSig,//簽名
  });
  rtc.join();//調用join方法
});
GenerateTestUserSig.js:根據SDKAPPIDSECRETKEY(秘鑰), EXPIRETIME(簽名過期時間 默認設置7天不要太短)生成:userSig(簽名)
/* eslint-disable require-jsdoc */
/*
 * Module:   GenerateTestUserSig
 *
 * Function: 用於生成測試用的 UserSig,UserSig 是騰訊雲為其雲服務設計的一種安全保護簽名。
 *           其計算方法是對 SDKAppID、UserID 和 EXPIRETIME 進行加密,加密算法為 HMAC-SHA256。
 *
 * Attention: 請不要將如下代碼發布到您的線上正式版本的 App 中,原因如下:
 *
 *            本文件中的代碼雖然能夠正確計算出 UserSig,但僅適合快速調通 SDK 的基本功能,不適合線上產品,
 *            這是因為客戶端代碼中的 SECRETKEY 很容易被反編譯逆向破解,尤其是 Web 端的代碼被破解的難度幾乎為零。
 *            一旦您的密鑰泄露,攻擊者就可以計算出正確的 UserSig 來盜用您的騰訊雲流量。
 *
 *            正確的做法是將 UserSig 的計算代碼和加密密鑰放在您的業務服務器上,然后由 App 按需向您的服務器獲取實時算出的 UserSig。
 *            由於破解服務器的成本要高於破解客戶端 App,所以服務器計算的方案能夠更好地保護您的加密密鑰。
 *
 * Reference:https://cloud.tencent.com/document/product/647/17275#Server
 */
function genTestUserSig(userID) {
  /**
   * 騰訊雲 SDKAppId,需要替換為您自己賬號下的 SDKAppId。
   *
   * 進入騰訊雲實時音視頻[控制台](https://console.cloud.tencent.com/rav ) 創建應用,即可看到 SDKAppId,
   * 它是騰訊雲用於區分客戶的唯一標識。
   */
  const SDKAPPID = 1400324973;

  /**
   * 簽名過期時間,建議不要設置的過短
   * <p>
   * 時間單位:秒
   * 默認時間:7 x 24 x 60 x 60 = 604800 = 7 天
   */
  const EXPIRETIME = 604800;

  /**
   * 計算簽名用的加密密鑰,獲取步驟如下:
   *
   * step1. 進入騰訊雲實時音視頻[控制台](https://console.cloud.tencent.com/rav ),如果還沒有應用就創建一個,
   * step2. 單擊“應用配置”進入基礎配置頁面,並進一步找到“帳號體系集成”部分。
   * step3. 點擊“查看密鑰”按鈕,就可以看到計算 UserSig 使用的加密的密鑰了,請將其拷貝並復制到如下的變量中
   *
   * 注意:該方案僅適用於調試Demo,正式上線前請將 UserSig 計算代碼和密鑰遷移到您的后台服務器上,以避免加密密鑰泄露導致的流量盜用。
   * 文檔:https://cloud.tencent.com/document/product/647/17275#Server
   */
  const SECRETKEY = 'aaa3e88fffd77e4f3755a423151ea4008a4539aa64d41c0d16c3e1a844e2f3f5';

  // a soft reminder to guide developer to configure sdkAppId/secretKey
  if (SDKAPPID === '' || SECRETKEY === '') {
    alert(
      '請先配置好您的賬號信息: SDKAPPID 及 SECRETKEY ' +
        '\r\n\r\nPlease configure your SDKAPPID/SECRETKEY in js/debug/GenerateTestUserSig.js'
    );
  }
  const generator = new LibGenerateTestUserSig(SDKAPPID, SECRETKEY, EXPIRETIME);
  const userSig = generator.genTestUserSig(userID);
  return {
    sdkAppId: SDKAPPID,
    userSig: userSig
  };
}
View Code

我們還發現加入房間的代碼里 new RtcClient();這樣一個代碼,字面意思是創建一個rtc實例對象;

下面來看看rtc-client.js中的代碼:

/* eslint-disable require-jsdoc */

class RtcClient {//聲明一個RtcClient類
  constructor(options) {//實例屬性
    this.sdkAppId_ = options.sdkAppId;//sdkAppId
    this.userId_ = options.userId;
    this.userSig_ = options.userSig;
    this.roomId_ = options.roomId;

    this.isJoined_ = false;
    this.isPublished_ = false;
    this.localStream_ = null;
    this.remoteStreams_ = [];

    // check if browser is compatible with TRTC
    TRTC.checkSystemRequirements().then(result => {//trtc.js中的TRTC對象
      if (!result) {
        alert('Your browser is not compatible with TRTC! Please download Chrome M72+');
      }
    });
  }

  async join() {//實例方法
    if (this.isJoined_) {
      console.warn('duplicate RtcClient.join() observed');
      return;
    }

    // create a client for RtcClient
    this.client_ = TRTC.createClient({
      mode: 'videoCall', // 實時通話模式
      sdkAppId: this.sdkAppId_,
      userId: this.userId_,
      userSig: this.userSig_
    });

    // 處理 client 事件
    this.handleEvents();

    try {
      // join the room
      await this.client_.join({ roomId: this.roomId_ });
      console.log('join room success');
      Toast.notify('進房成功!');
      this.isJoined_ = true;
    } catch (error) {
      console.error('failed to join room because: ' + error);
      alert(
        '進房失敗原因:' +
          error +
          '\r\n\r\n請確保您的網絡連接是正常的,您可以先體驗一下我們的Demo以確保網絡連接是正常的:' +
          '\r\n https://trtc-1252463788.file.myqcloud.com/web/demo/official-demo/index.html ' +
          '\r\n\r\n另外,請確保您的賬號信息是正確的。' +
          '\r\n請打開鏈接:https://cloud.tencent.com/document/product/647/34342 查詢詳細錯誤信息!'
      );
      Toast.error('進房錯誤!');
      return;
    }

    try {
      // 采集攝像頭和麥克風視頻流
      await this.createLocalStream({ audio: true, video: true });
      Toast.info('攝像頭及麥克風采集成功!');
      console.log('createLocalStream with audio/video success');
    } catch (error) {
      console.error('createLocalStream with audio/video failed: ' + error);
      alert(
        '請確認已連接攝像頭和麥克風並授予其訪問權限!\r\n\r\n 如果您沒有連接攝像頭或麥克風,您可以通過調整第60行代碼來關閉未連接設備的采集請求!'
      );
      try {
        // fallback to capture camera only
        await this.createLocalStream({ audio: false, video: true });
        Toast.info('采集攝像頭成功!');
      } catch (error) {
        console.error('createLocalStream with video failed: ' + error);
        return;
      }
    }

    this.localStream_.on('player-state-changed', event => {
      console.log(`local stream ${event.type} player is ${event.state}`);
      if (event.type === 'video' && event.state === 'PLAYING') {
        // dismiss the remote user UI placeholder
      } else if (event.type === 'video' && event.state === 'STOPPPED') {
        // show the remote user UI placeholder
      }
    });

    // 在名為 ‘local_stream’ 的 div 容器上播放本地音視頻
    this.localStream_.play('local_stream');

    // publish local stream by default after join the room
    await this.publish();
    Toast.notify('發布本地流成功!');
  }

  async leave() {
    if (!this.isJoined_) {
      console.warn('leave() - leave without join()d observed');
      Toast.error('請先加入房間!');
      return;
    }

    if (this.isPublished_) {
      // ensure the local stream has been unpublished before leaving.
      await this.unpublish(true);
    }

    try {
      // leave the room
      await this.client_.leave();
      Toast.notify('退房成功!');
      this.isJoined_ = false;
    } catch (error) {
      console.error('failed to leave the room because ' + error);
      location.reload();
    } finally {
      // 停止本地流,關閉本地流內部的音視頻播放器
      this.localStream_.stop();
      // 關閉本地流,釋放攝像頭和麥克風訪問權限
      this.localStream_.close();
      this.localStream_ = null;
    }
  }

  async publish() {
    if (!this.isJoined_) {
      Toast.error('請先加入房間再點擊開始推流!');
      console.warn('publish() - please join() firstly');
      return;
    }
    if (this.isPublished_) {
      console.warn('duplicate RtcClient.publish() observed');
      Toast.error('當前正在推流!');
      return;
    }
    try {
      // 發布本地流
      await this.client_.publish(this.localStream_);
      Toast.info('發布本地流成功!');
      this.isPublished_ = true;
    } catch (error) {
      console.error('failed to publish local stream ' + error);
      Toast.error('發布本地流失敗!');
      this.isPublished_ = false;
    }
  }

  async unpublish(isLeaving) {
    if (!this.isJoined_) {
      console.warn('unpublish() - please join() firstly');
      Toast.error('請先加入房間再停止推流!');
      return;
    }
    if (!this.isPublished_) {
      console.warn('RtcClient.unpublish() called but not published yet');
      Toast.error('當前尚未發布本地流!');
      return;
    }

    try {
      // 停止發布本地流
      await this.client_.unpublish(this.localStream_);
      this.isPublished_ = false;
      Toast.info('停止發布本地流成功!');
    } catch (error) {
      console.error('failed to unpublish local stream because ' + error);
      Toast.error('停止發布本地流失敗!');
      if (!isLeaving) {
        console.warn('leaving the room because unpublish failure observed');
        Toast.error('停止發布本地流失敗,退出房間!');
        this.leave();
      }
    }
  }

  async createLocalStream(options) {
    this.localStream_ = TRTC.createStream({
      audio: options.audio, // 采集麥克風
      video: options.video, // 采集攝像頭
      userId: this.userId_
      // cameraId: getCameraId(),
      // microphoneId: getMicrophoneId()
    });
    // 設置視頻分辨率幀率和碼率
    this.localStream_.setVideoProfile('480p');

    await this.localStream_.initialize();
  }

  handleEvents() {
    // 處理 client 錯誤事件,錯誤均為不可恢復錯誤,建議提示用戶后刷新頁面
    this.client_.on('error', err => {
      console.error(err);
      alert(err);
      Toast.error('客戶端錯誤:' + err);
      // location.reload();
    });

    // 處理用戶被踢事件,通常是因為房間內有同名用戶引起,這種問題一般是應用層邏輯錯誤引起的
    // 應用層請盡量使用不同用戶ID進房
    this.client_.on('client-banned', err => {
      console.error('client has been banned for ' + err);
      Toast.error('用戶被踢出房間!');
      // location.reload();
    });

    // 遠端用戶進房通知 - 僅限主動推流用戶
    this.client_.on('peer-join', evt => {
      const userId = evt.userId;
      console.log('peer-join ' + userId);
      Toast.notify('遠端用戶進房 - ' + userId);
    });
    // 遠端用戶退房通知 - 僅限主動推流用戶
    this.client_.on('peer-leave', evt => {
      const userId = evt.userId;
      console.log('peer-leave ' + userId);
      Toast.notify('遠端用戶退房 - ' + userId);
    });

    // 處理遠端流增加事件
    this.client_.on('stream-added', evt => {
      const remoteStream = evt.stream;
      const id = remoteStream.getId();
      const userId = remoteStream.getUserId();
      console.log(`remote stream added: [${userId}] ID: ${id} type: ${remoteStream.getType()}`);
      Toast.info('遠端流增加 - ' + userId);
      console.log('subscribe to this remote stream');
      // 遠端流默認已訂閱所有音視頻,此處可指定只訂閱音頻或者音視頻,不能僅訂閱視頻。
      // 如果不想觀看該路遠端流,可調用 this.client_.unsubscribe(remoteStream) 取消訂閱
      this.client_.subscribe(remoteStream);
    });

    // 遠端流訂閱成功事件
    this.client_.on('stream-subscribed', evt => {
      const remoteStream = evt.stream;
      const id = remoteStream.getId();
      this.remoteStreams_.push(remoteStream);
      addView(id);
      // 在指定的 div 容器上播放音視頻
      remoteStream.play(id);
      console.log('stream-subscribed ID: ', id);
      Toast.info('遠端流訂閱成功 - ' + remoteStream.getUserId());
    });

    // 處理遠端流被刪除事件
    this.client_.on('stream-removed', evt => {
      const remoteStream = evt.stream;
      const id = remoteStream.getId();
      // 關閉遠端流內部的音視頻播放器
      remoteStream.stop();
      this.remoteStreams_ = this.remoteStreams_.filter(stream => {
        return stream.getId() !== id;
      });
      removeView(id);
      console.log(`stream-removed ID: ${id}  type: ${remoteStream.getType()}`);
      Toast.info('遠端流刪除 - ' + remoteStream.getUserId());
    });

    // 處理遠端流更新事件,在音視頻通話過程中,遠端流音頻或視頻可能會有更新
    this.client_.on('stream-updated', evt => {
      const remoteStream = evt.stream;
      console.log(
        'type: ' +
          remoteStream.getType() +
          ' stream-updated hasAudio: ' +
          remoteStream.hasAudio() +
          ' hasVideo: ' +
          remoteStream.hasVideo()
      );
      Toast.info('遠端流更新!');
    });

    // 遠端流音頻或視頻mute狀態通知
    this.client_.on('mute-audio', evt => {
      console.log(evt.userId + ' mute audio');
    });
    this.client_.on('unmute-audio', evt => {
      console.log(evt.userId + ' unmute audio');
    });
    this.client_.on('mute-video', evt => {
      console.log(evt.userId + ' mute video');
    });
    this.client_.on('unmute-video', evt => {
      console.log(evt.userId + ' unmute video');
    });

    // 信令通道連接狀態通知
    this.client_.on('connection-state-changed', evt => {
      console.log(`RtcClient state changed to ${evt.state} from ${evt.prevState}`);
    });
  }
}
View Code

看到這里,我在本地寫了demo,有個報錯,先到這里,推薦大家看文檔:

文檔地址:https://cloud.tencent.com/document/product/647/16863

 

 

 可以現充基礎音視頻通話來做,慢慢的我那個下看文檔

 也可以看一下最佳實踐里的小例子,很簡短,可以照着實現一個web端的音視頻通話;

文檔地址:https://cloud.tencent.com/document/product/647/32225

 

 

下面是基於demo中的一些js文件自己實現的一個實時音視頻的,進入房間、退出房間、開始推流、停止推流的小例子,就提的代碼還沒研究;先貼上代碼:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>實時音視頻demo</title>
  <link href="https://cdn.bootcss.com/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
  <link rel="stylesheet" href="./css/toastify.min.css">
  <style>
    #local_stream{
      height:500px;
    }
  </style>
</head>
<body>
  <div class="row">
    <div class="col col-xs-3">
      <button class="btn btn-primary" id="join">加入房間</button>
    </div>
    <div class="col col-xs-3">
      <button class="btn btn-primary" id="leave">退出房間</button>
    </div>
    <div class="col col-xs-3">
      <button class="btn btn-primary" id="publish">開始推流</button>
    </div>
    <div class="col col-xs-3">
      <button class="btn btn-primary" id="unpublish">停止推流</button>
    </div>
  </div>
  <div class="row">
    <div class="col col-xs-12">
      <div id="local_stream" class="video-placeholder">
        <div id="local_video_info" class="video-info"></div>
      </div>
    </div>
  </div>



  <!-- jquery -->
  <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
  <!-- 提示框插件 -->
  <script src="./js/toastify.js"></script>
  <!-- bootstrap -->
  <script src="https://cdn.bootcss.com/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script>
  <!-- 引入 TRTC WEB SDK 腳本 -->
  <script src="./js/trtc.js"></script>
  <!-- 下面是demo相關腳本 -->
  <!-- 生成簽名 -->
  <script src="./js/lib-generate-test-usersig.min.js"></script>
  <script src="./js/GenerateTestUserSig.js"></script>
  <script src="./js/utils.js"></script>
  <script src="./js/rtc-client.js"></script>
  

<script>
  let rtc = null;
  // 加入房間
$('#join').on('click', function(e) {
  e.preventDefault();
  console.log('join');
  if (rtc) return;
  let userId = 'user_' + parseInt(Math.random() * 100000000);
  const config = genTestUserSig(userId);//根據userId生成一個對象含:sdkAppId、userSig,具體代碼見GenerateTestUserSig.js
  rtc = new RtcClient({//創建一個rtc實例  傳入userid、房間號、sdkAppId、userSig
    userId,
    roomId:'889988',
    sdkAppId: config.sdkAppId,
    userSig: config.userSig
  });
  rtc.join();//調用join方法
});
// 退出房間
$('#leave').on('click', function(e) {
  e.preventDefault();
  console.log('leave');
  if (!rtc) {
    Toast.error('請先加入房間!');
    return;
  }
  rtc.leave();
  rtc = null;
});
// 開始推流
$('#publish').on('click', function(e) {
  e.preventDefault();
  console.log('publish');
  if (!rtc) {
    Toast.error('請先加入房間!');
    return;
  }
  rtc.publish();
});
// 停止推流
$('#unpublish').on('click', function(e) {
  e.preventDefault();
  console.log('unpublish');
  if (!rtc) {
    Toast.error('請先加入房間!');
    return;
  }
  rtc.unpublish();
});

</script>
</body>
</html>

效果:

 

 

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM