分析 腾讯的 实时音视频web端demo代码

首先我们要会引入TRTC Web SDK

下载地址:<https://cloud.tencent.com/document/product/647/16863#script-.E9.9B.86.E6.88.90&gt;

![]()

![]()

![]()

![]()

目前还不知道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&quot;&gt; -->

<!-- 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 old-src="./js/jquery-3.2.1.min.js"></script>
<script old-src="./js/popper.js"></script>
<script old-src="./js/toastify.js"></script>
<script old-src="./js/bootstrap-material-design.min.js"></script>
<script>$(document).ready(function () { $('body').bootstrapMaterialDesign(); });</script>
<!-- 引入 TRTC WEB SDK 脚本 -->
<script old-src="./js/trtc.js"></script>
<!-- Demo 相关脚本 -->
<script old-src="./js/lib-generate-test-usersig.min.js"></script>
<script old-src="./js/debug/GenerateTestUserSig.js"></script>
<script old-src="./js/utils.js"></script>
<script old-src="./js/rtc-client.js"></script>
<script old-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:根据SDKAPPID, SECRETKEY(秘钥), 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。
    • 进入腾讯云实时音视频控制台 创建应用,即可看到 SDKAppId,
    • 它是腾讯云用于区分客户的唯一标识。
      */
      const SDKAPPID = 1400324973;

    /**

    • 签名过期时间,建议不要设置的过短
    • <p>
    • 时间单位:秒
    • 默认时间:7 x 24 x 60 x 60 = 604800 = 7 天
      */
      const EXPIRETIME = 604800;

    /**

    • 计算签名用的加密密钥,获取步骤如下:
    • step1. 进入腾讯云实时音视频控制台,如果还没有应用就创建一个,
    • 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 =&gt; {//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 =&gt; {
  console.log(`local stream ${event.type} player is ${event.state}`);
  if (event.type === 'video' &amp;&amp; event.state === 'PLAYING') {
    // dismiss the remote user UI placeholder
  } else if (event.type === 'video' &amp;&amp; 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 =&gt; {
  console.error('client has been banned for ' + err);
  Toast.error('用户被踢出房间!');
  // location.reload();
});

// 远端用户进房通知 - 仅限主动推流用户
this.client_.on('peer-join', evt =&gt; {
  const userId = evt.userId;
  console.log('peer-join ' + userId);
  Toast.notify('远端用户进房 - ' + userId);
});
// 远端用户退房通知 - 仅限主动推流用户
this.client_.on('peer-leave', evt =&gt; {
  const userId = evt.userId;
  console.log('peer-leave ' + userId);
  Toast.notify('远端用户退房 - ' + userId);
});

// 处理远端流增加事件
this.client_.on('stream-added', evt =&gt; {
  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 =&gt; {
  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 =&gt; {
  const remoteStream = evt.stream;
  const id = remoteStream.getId();
  // 关闭远端流内部的音视频播放器
  remoteStream.stop();
  this.remoteStreams_ = this.remoteStreams_.filter(stream =&gt; {
    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 =&gt; {
  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 =&gt; {
  console.log(evt.userId + ' mute audio');
});
this.client_.on('unmute-audio', evt =&gt; {
  console.log(evt.userId + ' unmute audio');
});
this.client_.on('mute-video', evt =&gt; {
  console.log(evt.userId + ' mute video');
});
this.client_.on('unmute-video', evt =&gt; {
  console.log(evt.userId + ' unmute video');
});

// 信令通道连接状态通知
this.client_.on('connection-state-changed', evt =&gt; {
  console.log(`RtcClient state changed to ${evt.state} from ${evt.prevState}`);
});

}
}


View Code

看到这里,我在本地写了demo,有个报错,先到这里,推荐大家看文档:

文档地址:&lt;https://cloud.tencent.com/document/product/647/16863&gt;

 ![]()

 可以现充基础音视频通话来做,慢慢的我那个下看文档

 也可以看一下最佳实践里的小例子,很简短,可以照着实现一个web端的音视频通话;

文档地址:&lt;https://cloud.tencent.com/document/product/647/32225&gt;

 ![]()

下面是基于demo中的一些js文件自己实现的一个实时音视频的,进入房间、退出房间、开始推流、停止推流的小例子,就提的代码还没研究;先贴上代码:

<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&quot; 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 old-src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js&quot;&gt;&lt;/script&gt;
<!-- 提示框插件 -->
<script old-src="./js/toastify.js"></script>
<!-- bootstrap -->
<script old-src="https://cdn.bootcss.com/twitter-bootstrap/3.3.7/js/bootstrap.min.js&quot;&gt;&lt;/script&gt;
<!-- 引入 TRTC WEB SDK 脚本 -->
<script old-src="./js/trtc.js"></script>
<!-- 下面是demo相关脚本 -->
<!-- 生成签名 -->
<script old-src="./js/lib-generate-test-usersig.min.js"></script>
<script old-src="./js/GenerateTestUserSig.js"></script>
<script old-src="./js/utils.js"></script>
<script old-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>



效果:

![]()

。

声明:该文章系转载,转载该文章的目的在于更广泛的传递信息,并不代表本网站赞同其观点,文章内容仅供参考。

本站是一个个人学习和交流平台,网站上部分文章为网站管理员和网友从相关媒体转载而来,并不用于任何商业目的,内容为作者个人观点, 并不代表本网站赞同其观点和对其真实性负责。

我们已经尽可能的对作者和来源进行了通告,但是可能由于能力有限或疏忽,导致作者和来源有误,亦可能您并不期望您的作品在我们的网站上发布。我们为这些问题向您致歉,如果您在我站上发现此类问题,请及时联系我们,我们将根据您的要求,立即更正或者删除有关内容。本站拥有对此声明的最终解释权。