分析 腾讯的 实时音视频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>
里面有:输入用户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(); });
可以看出。先让给用户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。 * * 进入腾讯云实时音视频[控制台](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 }; }
我们还发现加入房间的代码里 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}`); }); } }
看到这里,我在本地写了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>
效果:
。