init live

This commit is contained in:
2020-11-12 17:27:59 +08:00
parent 0d29e223e6
commit bc46dde11c
41 changed files with 13590 additions and 575 deletions

6939
public/js/bootstrap-material-design.js vendored Normal file

File diff suppressed because it is too large Load Diff

660
public/js/common.js Normal file
View File

@@ -0,0 +1,660 @@
/* eslint-disable no-cond-assign */
/* global $ TRTC presetting RtcClient ShareClient */
/* eslint-disable require-jsdoc */
let isCamOn = true;
let isMicOn = true;
let isScreenOn = false;
let isJoined = true;
let rtc = null;
let share = null;
let shareUserId = '';
let cameraId = '';
let micId = '';
function login() {
if ($('#userId').val() == '') {
alert('用户名不能为空!');
return;
}
if ($('#roomId').val() == '') {
alert('房间号不能为空!');
return;
}
presetting.login(false, options => {
rtc = new RtcClient(options);
join();
});
presetting.login(true, options => {
shareUserId = options.userId;
share = new ShareClient(options);
});
}
function join() {
rtc.join();
$('#login-root').hide();
$('#room-root').show();
$('#header-roomId').html('房间号: ' + $('#roomId').val());
$('#member-me')
.find('.member-id')
.html($('#userId').val() + '(我)');
}
function leave() {
$('#mask_main').appendTo($('#main-video'));
rtc.leave();
share.leave();
}
function publish() {
rtc.publish();
}
function unpublish() {
rtc.unpublish();
}
function muteAudio() {
rtc.muteLocalAudio();
}
function unmuteAudio() {
rtc.unmuteLocalAudio();
}
function muteVideo() {
$('#mask_main').show();
rtc.muteLocalVideo();
}
function unmuteVideo() {
rtc.unmuteLocalVideo();
$('#mask_main').hide();
}
function startSharing() {
share.join();
}
function stopSharing() {
share.leave();
}
function setBtnClickFuc() {
//userid roomid规格
//$('#userId').on('input', function(e) {
// e.preventDefault();
// console.log('userId input ' + e.target.value);
// let val = $('#userId').val().slice(5);
// $('#userId').val('user_'+val.replace(/[^\d]/g,''));
//});
$('#roomId').on('input', function(e) {
e.preventDefault();
console.log('roomId input ' + e.target.value);
let val = $('#roomId').val();
$('#roomId').val(val.replace(/[^\d]/g, ''));
});
//login
$('#login-btn').click(() => {
login();
});
//open or close camera
$('#video-btn').on('click', () => {
if (isCamOn) {
$('#video-btn').attr('src', './img/big-camera-off.png');
$('#video-btn').attr('title', '打开摄像头');
$('#member-me')
.find('.member-video-btn')
.attr('src', 'img/camera-off.png');
isCamOn = false;
muteVideo();
} else {
$('#video-btn').attr('src', './img/big-camera-on.png');
$('#video-btn').attr('title', '关闭摄像头');
$('#member-me')
.find('.member-video-btn')
.attr('src', 'img/camera-on.png');
isCamOn = true;
unmuteVideo();
}
});
//open or close microphone
$('#mic-btn').on('click', () => {
if (isMicOn) {
$('#mic-btn').attr('src', './img/big-mic-off.png');
$('#mic-btn').attr('title', '打开麦克风');
$('#member-me')
.find('.member-audio-btn')
.attr('src', 'img/mic-off.png');
isMicOn = false;
muteAudio();
} else {
$('#mic-btn').attr('src', './img/big-mic-on.png');
$('#mic-btn').attr('title', '关闭麦克风');
$('#member-me')
.find('.member-audio-btn')
.attr('src', 'img/mic-on.png');
isMicOn = true;
unmuteAudio();
}
});
//share screen or not
$('#screen-btn').on(
'click',
throttle(() => {
if (!TRTC.isScreenShareSupported()) {
alert('当前浏览器不支持屏幕分享!');
return;
}
if ($('#screen-btn').attr('src') == './img/screen-on.png') {
$('#screen-btn').attr('src', './img/screen-off.png');
stopSharing();
isScreenOn = false;
} else {
$('#screen-btn').attr('src', './img/screen-on.png');
startSharing();
isScreenOn = true;
}
}, 2000)
);
//logout
$('#logout-btn').on('click', () => {
leave();
$('#room-root').hide();
$('#login-root').show();
});
//switch main video
$('#main-video').on('click', () => {
let mainVideo = $('.video-box').first();
if ($('#main-video').is(mainVideo)) {
return;
}
//释放main-video grid-area
mainVideo.css('grid-area', 'auto/auto/auto/auto');
exchangeView($('#main-video'), mainVideo);
//将video-grid中第一个div设为main-video
$('.video-box')
.first()
.css('grid-area', '1/1/3/4');
//chromeM71以下会自动暂停手动唤醒
if (getBroswer().broswer == 'Chrome' && getBroswer().version < '72') {
rtc.resumeStreams();
}
});
//chrome60以下不支持popover防止error
if (getBroswer().broswer == 'Chrome' && getBroswer().version < '60') return;
//开启popover
$(function() {
$('[data-toggle="popover"]').popover();
});
$('#camera').popover({
html: true,
content: () => {
return $('#camera-option').html();
}
});
$('#microphone').popover({
html: true,
content: () => {
return $('#mic-option').html();
}
});
$('#camera').on('click', () => {
$('#microphone').popover('hide');
$('.popover-body')
.find('div')
.attr('onclick', 'setCameraId(this)');
});
$('#microphone').on('click', () => {
$('#camera').popover('hide');
$('.popover-body')
.find('div')
.attr('onclick', 'setMicId(this)');
});
//点击body关闭popover
$('body').click(() => {
$('#camera').popover('hide');
$('#microphone').popover('hide');
});
//popover事件
$('#camera').on('show.bs.popover', () => {
$('#camera').attr('src', './img/camera-on.png');
});
$('#camera').on('hide.bs.popover', () => {
$('#camera').attr('src', './img/camera.png');
});
$('#microphone').on('show.bs.popover', () => {
$('#microphone').attr('src', './img/mic-on.png');
});
$('#microphone').on('hide.bs.popover', () => {
$('#microphone').attr('src', './img/mic.png');
});
}
function setCameraId(thisDiv) {
cameraId = $(thisDiv).attr('id');
console.log('setCameraId: ' + cameraId);
}
function setMicId(thisDiv) {
micId = $(thisDiv).attr('id');
console.log('setMicId: ' + micId);
}
function addVideoView(id, isLocal = false) {
let div = $('<div/>', {
id: id,
class: 'video-box',
style: 'justify-content: center'
});
div.appendTo('#video-grid');
//设置监听
div.click(() => {
let mainVideo = $('.video-box').first();
if (div.is(mainVideo)) {
return;
}
//释放main-video grid-area
mainVideo.css('grid-area', 'auto/auto/auto/auto');
exchangeView(div, mainVideo);
//将video-grid中第一个div设为main-video
$('.video-box')
.first()
.css('grid-area', '1/1/3/4');
//chromeM71以下会自动暂停手动唤醒
if (getBroswer().broswer == 'Chrome' && getBroswer().version < '72') {
rtc.resumeStreams();
}
});
}
function addMemberView(id) {
let memberElm = $('#member-me').clone();
memberElm.attr('id', id);
memberElm.find('div.member-id').html(id);
memberElm.css('display', 'flex');
memberElm.appendTo($('#member-list'));
}
function removeView(id) {
if ($('#' + id)[0]) {
$('#' + id).remove();
//将video-grid中第一个div设为main-video
$('.video-box')
.first()
.css('grid-area', '1/1/3/4');
}
}
function exchangeView(a, b) {
var $div1 = $(a);
var $div3 = $(b);
var $temobj1 = $('<div></div>');
var $temobj2 = $('<div></div>');
$temobj1.insertBefore($div1);
$temobj2.insertBefore($div3);
$div1.insertAfter($temobj2);
$div3.insertAfter($temobj1);
$temobj1.remove();
$temobj2.remove();
}
function isPC() {
var userAgentInfo = navigator.userAgent;
var Agents = new Array('Android', 'iPhone', 'SymbianOS', 'Windows Phone', 'iPad', 'iPod');
var flag = true;
for (var v = 0; v < Agents.length; v++) {
if (userAgentInfo.indexOf(Agents[v]) > 0) {
flag = false;
break;
}
}
return flag;
}
function getCameraId() {
console.log('selected cameraId: ' + cameraId);
return cameraId;
}
function getMicrophoneId() {
console.log('selected microphoneId: ' + micId);
return micId;
}
function throttle(func, delay) {
var timer = null;
var startTime = Date.now();
return function() {
var curTime = Date.now();
var remaining = delay - (curTime - startTime);
var context = this;
var args = arguments;
clearTimeout(timer);
if (remaining <= 0) {
func.apply(context, args);
startTime = Date.now();
} else {
timer = setTimeout(() => {
console.log('duplicate click');
}, remaining);
}
};
}
function resetView() {
isCamOn = true;
isMicOn = true;
isScreenOn = false;
isJoined = true;
$('#main-video-btns').hide();
$('#video-btn').attr('src', './img/big-camera-on.png');
$('#mic-btn').attr('src', './img/big-mic-on.png');
$('#screen-btn').attr('src', './img/screen-off.png');
$('#member-me')
.find('.member-video-btn')
.attr('src', 'img/camera-on.png');
$('#member-me')
.find('.member-audio-btn')
.attr('src', 'img/mic-on.png');
$('.mask').hide();
//清空member-list
if ($('#member-list')) {
$('#member-list')
.find('.member')
.each((index, element) => {
if (
$(element)
.parent()
.attr('id') != 'member-me'
) {
$(element)
.parent()
.remove();
}
});
}
}
function getBroswer() {
var sys = {};
var ua = navigator.userAgent.toLowerCase();
var s;
(s = ua.match(/edge\/([\d.]+)/))
? (sys.edge = s[1])
: (s = ua.match(/rv:([\d.]+)\) like gecko/))
? (sys.ie = s[1])
: (s = ua.match(/msie ([\d.]+)/))
? (sys.ie = s[1])
: (s = ua.match(/firefox\/([\d.]+)/))
? (sys.firefox = s[1])
: (s = ua.match(/chrome\/([\d.]+)/))
? (sys.chrome = s[1])
: (s = ua.match(/opera.([\d.]+)/))
? (sys.opera = s[1])
: (s = ua.match(/version\/([\d.]+).*safari/))
? (sys.safari = s[1])
: 0;
if (sys.edge) return { broswer: 'Edge', version: sys.edge };
if (sys.ie) return { broswer: 'IE', version: sys.ie };
if (sys.firefox) return { broswer: 'Firefox', version: sys.firefox };
if (sys.chrome) return { broswer: 'Chrome', version: sys.chrome };
if (sys.opera) return { broswer: 'Opera', version: sys.opera };
if (sys.safari) return { broswer: 'Safari', version: sys.safari };
return { broswer: '', version: '0' };
}
function isHidden() {
var hidden, visibilityChange;
if (typeof document.hidden !== 'undefined') {
hidden = 'hidden';
visibilityChange = 'visibilitychange';
} else if (typeof document.msHidden !== 'undefined') {
hidden = 'msHidden';
visibilityChange = 'msvisibilitychange';
} else if (typeof document.webkitHidden !== 'undefined') {
hidden = 'webkitHidden';
visibilityChange = 'webkitvisibilitychange';
}
return document[hidden];
}
function getIPAddress() {
return new Promise(resolve => {
window.RTCPeerConnection =
window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection; //compatibility for firefox and chrome
let pc = new RTCPeerConnection({ iceServers: [] });
let noop = function() {};
let IPAddress = '';
let ipRegex = /([0-9]{1,3}(\.[0-9]{1,3}){3}|[a-f0-9]{1,4}(:[a-f0-9]{1,4}){7})/;
pc.createDataChannel(''); //create a bogus data channel
pc.createOffer(pc.setLocalDescription.bind(pc), noop); // create offer and set local description
//listen for candidate events
pc.onicecandidate = function(ice) {
if (
!ice ||
!ice.candidate ||
!ice.candidate.candidate ||
!ipRegex.exec(ice.candidate.candidate)
) {
return;
}
IPAddress = ipRegex.exec(ice.candidate.candidate)[1];
pc.onicecandidate = noop;
resolve(IPAddress);
};
});
}
let isMobile = {
Android: function() {
return navigator.userAgent.match(/Android/i);
},
BlackBerry: function() {
return navigator.userAgent.match(/BlackBerry|BB10/i);
},
iOS: function() {
return navigator.userAgent.match(/iPhone|iPad|iPod/i);
},
Opera: function() {
return navigator.userAgent.match(/Opera Mini/i);
},
Windows: function() {
return navigator.userAgent.match(/IEMobile/i);
},
any: function() {
return (
isMobile.Android() ||
isMobile.BlackBerry() ||
isMobile.iOS() ||
isMobile.Opera() ||
isMobile.Windows()
);
},
getOsName: function() {
var osName = 'Unknown OS';
if (isMobile.Android()) {
osName = 'Android';
}
if (isMobile.BlackBerry()) {
osName = 'BlackBerry';
}
if (isMobile.iOS()) {
osName = 'iOS';
}
if (isMobile.Opera()) {
osName = 'Opera Mini';
}
if (isMobile.Windows()) {
osName = 'Windows';
}
return osName;
}
};
function detectDesktopOS() {
var unknown = '-';
var nVer = navigator.appVersion;
var nAgt = navigator.userAgent;
var os = unknown;
var clientStrings = [
{
s: 'Chrome OS',
r: /CrOS/
},
{
s: 'Windows 10',
r: /(Windows 10.0|Windows NT 10.0)/
},
{
s: 'Windows 8.1',
r: /(Windows 8.1|Windows NT 6.3)/
},
{
s: 'Windows 8',
r: /(Windows 8|Windows NT 6.2)/
},
{
s: 'Windows 7',
r: /(Windows 7|Windows NT 6.1)/
},
{
s: 'Windows Vista',
r: /Windows NT 6.0/
},
{
s: 'Windows Server 2003',
r: /Windows NT 5.2/
},
{
s: 'Windows XP',
r: /(Windows NT 5.1|Windows XP)/
},
{
s: 'Windows 2000',
r: /(Windows NT 5.0|Windows 2000)/
},
{
s: 'Windows ME',
r: /(Win 9x 4.90|Windows ME)/
},
{
s: 'Windows 98',
r: /(Windows 98|Win98)/
},
{
s: 'Windows 95',
r: /(Windows 95|Win95|Windows_95)/
},
{
s: 'Windows NT 4.0',
r: /(Windows NT 4.0|WinNT4.0|WinNT|Windows NT)/
},
{
s: 'Windows CE',
r: /Windows CE/
},
{
s: 'Windows 3.11',
r: /Win16/
},
{
s: 'Android',
r: /Android/
},
{
s: 'Open BSD',
r: /OpenBSD/
},
{
s: 'Sun OS',
r: /SunOS/
},
{
s: 'Linux',
r: /(Linux|X11)/
},
{
s: 'iOS',
r: /(iPhone|iPad|iPod)/
},
{
s: 'Mac OS X',
r: /Mac OS X/
},
{
s: 'Mac OS',
r: /(MacPPC|MacIntel|Mac_PowerPC|Macintosh)/
},
{
s: 'QNX',
r: /QNX/
},
{
s: 'UNIX',
r: /UNIX/
},
{
s: 'BeOS',
r: /BeOS/
},
{
s: 'OS/2',
r: /OS\/2/
},
{
s: 'Search Bot',
r: /(nuhk|Googlebot|Yammybot|Openbot|Slurp|MSNBot|Ask Jeeves\/Teoma|ia_archiver)/
}
];
for (var i = 0, cs; (cs = clientStrings[i]); i++) {
if (cs.r.test(nAgt)) {
os = cs.s;
break;
}
}
var osVersion = unknown;
if (/Windows/.test(os)) {
if (/Windows (.*)/.test(os)) {
osVersion = /Windows (.*)/.exec(os)[1];
}
os = 'Windows';
}
switch (os) {
case 'Mac OS X':
if (/Mac OS X (10[/._\d]+)/.test(nAgt)) {
// eslint-disable-next-line no-useless-escape
osVersion = /Mac OS X (10[\.\_\d]+)/.exec(nAgt)[1];
}
break;
case 'Android':
// eslint-disable-next-line no-useless-escape
if (/Android ([\.\_\d]+)/.test(nAgt)) {
// eslint-disable-next-line no-useless-escape
osVersion = /Android ([\.\_\d]+)/.exec(nAgt)[1];
}
break;
case 'iOS':
if (/OS (\d+)_(\d+)_?(\d+)?/.test(nAgt)) {
osVersion = /OS (\d+)_(\d+)_?(\d+)?/.exec(nVer);
osVersion = osVersion[1] + '.' + osVersion[2] + '.' + (osVersion[3] | 0);
}
break;
}
return {
osName: os + osVersion
};
}
function getOS() {
if (isMobile.any()) {
return isMobile.getOsName();
} else {
return detectDesktopOS();
}
}

View File

@@ -0,0 +1,63 @@
/* eslint-disable require-jsdoc */
/*
* Module: GenerateTestUserSig
*
* Function: 用于生成测试用的 UserSigUserSig 是腾讯云为其云服务设计的一种安全保护签名。
* 其计算方法是对 SDKAppID、UserID 和 EXPIRETIME 进行加密,加密算法为 HMAC-SHA256。
*
* Attention: 请不要将如下代码发布到您的线上正式版本的 App 中,原因如下:
*
* 本文件中的代码虽然能够正确计算出 UserSig但仅适合快速调通 SDK 的基本功能,不适合线上产品,
* 这是因为客户端代码中的 SECRETKEY 很容易被反编译逆向破解,尤其是 Web 端的代码被破解的难度几乎为零。
* 一旦您的密钥泄露,攻击者就可以计算出正确的 UserSig 来盗用您的腾讯云流量。
*
* 正确的做法是将 UserSig 的计算代码和加密密钥放在您的业务服务器上,然后由 App 按需向您的服务器获取实时算出的 UserSig。
* 由于破解服务器的成本要高于破解客户端 App所以服务器计算的方案能够更好地保护您的加密密钥。
*
* Referencehttps://cloud.tencent.com/document/product/647/17275#Server
*/
function genTestUserSig(userID) {
/**
* 腾讯云 SDKAppId需要替换为您自己账号下的 SDKAppId。
*
* 进入腾讯云实时音视频[控制台](https://console.cloud.tencent.com/rav ) 创建应用,即可看到 SDKAppId
* 它是腾讯云用于区分客户的唯一标识。
*/
const SDKAPPID = 1400435767;
/**
* 签名过期时间,建议不要设置的过短
* <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 = 'dzrUpsgeMo0ygiSmqeDVqxnLbdT3Lbbh';
// 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 = window.istow ? "eJwtzdEKgjAUBuB32XXYdJubQhcV2IWBkGYEgohOO1SypoYQvXtLvTzff85-Pig5xtZbauQjx8JoNc1QybaHGibuboWW*dBJnRNMMcYeX9a66l4oBRXybeOUMO7yOZGjAi2NM8YcczJrD8*-uZhhYRPBlhZozJfW4*f9y7EPafmgTXjdnYQagjIRY7Ye4qim20sNaRsGEWk26PsDMPg1Xg__" : "eJwtzE8LgkAQBfDvstdCJt3RTeigl6gMw-7oLUK3GCNZ1Foj*u5t6vH93sz7sEO0t16yZj6zLWDTPlMhq5au1POzkfXZAQ4Ac288aIr7RSkqmD8zzh30XG9oZKeolsYR0TYvg7b0*JsLCAIcjuMK3cx*LmyV5GFXBWGZ6m4VVaejzjax3K3fehsTpk2QJKVeTsSCfX94djN6";
console.log(userSig)
window.istow = true
return {
sdkAppId: SDKAPPID,
userSig:userSig
};
}

838
public/js/device-testing.js Normal file
View File

@@ -0,0 +1,838 @@
/**
* 设备检测demo
*/
/* global $ TRTC presetting getOS getBroswer cameraId micId */
// 用于记录检测结果,生成检测报告
let hasCameraDevice = false,
hasMicAndVoiceDevice = false,
hasCameraConnect,
hasVoiceConnect,
hasMicConnect,
hasNetworkConnect;
let cameraTestingResult = {};
let voiceTestingResult = {};
let micTestingResult = {};
let networkTestingResult = {};
// 记录检测步骤,用于关闭时清空弹窗
let completedTestingPageIdList = [];
let curTestingPageId = '';
let localStream = null;
let client = null;
let timeout = null;
// 监听到network-quality事件的次数
let networkQualityNum = 0;
const deviceFailAttention =
'1. 若浏览器弹出提示,请选择“允许”<br>' +
'2. 若杀毒软件弹出提示,请选择“允许”<br>' +
'3. 检查浏览器设置,允许网页访问摄像头及麦克风<br>' +
'4. 检查摄像头/麦克风是否正确连接并开启<br>' +
'5. 尝试重新连接摄像头/麦克风<br>' +
'6. 尝试重启电脑后重新检测';
const networkFailAttention =
'1. 请检查设备是否联网<br>' + '2. 请刷新网页后再次检测<br>' + '3. 请尝试更换网络后再次检测';
// 网络参数对照表
const NETWORK_QUALITY = {
'0': '未知',
'1': '极佳',
'2': '较好',
'3': '一般',
'4': '差',
'5': '极差',
'6': '断开'
};
// 设备检测tab页签对应的执行方法
const pageCallbackConfig = {
'camera-testing-body': 'startCameraTesting',
'voice-testing-body': 'startVoiceTesting',
'mic-testing-body': 'startMicTesting',
'network-testing-body': 'startNetworkTesting'
};
// 判断是否为safari浏览器
let isSafari = /Safari/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgent);
hideVoiceForSafari();
/**
* safari浏览器中隐藏扬声器相关检测
*/
function hideVoiceForSafari() {
if (!isSafari) return;
$('#connect-voice').hide();
$('#device-voice').hide();
$('#voice-testing').hide();
$('#voice-report').hide();
$('#device-mic').addClass('safari');
$('#device-network').addClass('safari');
$('#mic-testing').addClass('safari');
$('#network-testing').addClass('safari');
}
// 是否是本地路径打开
let isFilePath = location.href.indexOf('file://') > -1;
/**
* 设备检测初始化
*/
async function deviceTestingInit() {
// 点击【设备检测】文字, 点击 【重新连接】按钮
$('#device-testing-btn, #connect-again-btn').on('click', () => {
startDeviceConnect();
});
// 连接设备错误icon
$('#connect-attention-icon').on('mouseover', () => {
$('#connect-attention-info').show();
});
// 连接设备错误icon
$('#connect-attention-icon').on('mouseout', () => {
$('#connect-attention-info').hide();
});
// 【开始检测】开始设备检测按钮
$('#start-test-btn').on('click', function() {
if ($(this).hasClass('start-gray')) return;
$('#device-testing-prepare').hide();
$('#device-testing').show();
startCameraTesting();
});
// 摄像头检测失败/成功
$('#camera-fail, #camera-success').on('click', function() {
cameraTestingResult.statusResult = $(this).attr('id') === 'camera-success';
$('#camera-testing-body').hide();
localStream.close();
// safari浏览器跳过扬声器检测
isSafari ? startMicTesting() : startVoiceTesting();
});
// 播放器检测失败/成功
$('#voice-fail, #voice-success').on('click', function() {
voiceTestingResult.statusResult = $(this).attr('id') === 'voice-success';
$('#voice-testing-body').hide();
let audioPlayer = document.querySelector('#audio-player');
if (!audioPlayer.paused) {
audioPlayer.pause();
}
startMicTesting();
});
// 麦克风测试失败/成功
$('#mic-fail, #mic-success').on('click', function() {
micTestingResult.statusResult = $(this).attr('id') === 'mic-success';
$('#mic-testing-body').hide();
localStream.close();
startNetworkTesting();
});
// 点击【查看检测报告】按钮
$('#testing-report-btn').on('click', () => {
showTestingReport();
localStream.close();
client && client.leave();
client && client.off('network-quality');
});
// 点击【重新测试】按钮
$('#testing-again').on('click', () => {
$('#device-testing-report').hide();
startDeviceConnect();
completedTestingPageIdList = [];
});
// 点击【测试完成】按钮 / 点击关闭图标
$('#testing-finish, #device-testing-close-btn').on('click', () => {
finishDeviceTesting();
});
// 测试tab页切换
$('#camera-testing, #voice-testing, #mic-testing, #network-testing').on('click', function() {
let targetPageId = $(this).attr('id') + '-body';
if (
targetPageId !== curTestingPageId &&
completedTestingPageIdList.indexOf(targetPageId) > -1
) {
$(`#${curTestingPageId}`).hide();
localStream && localStream.close();
client && client.leave();
client && client.off('network-quality');
// 停止播放器的音乐
let audioPlayer = document.querySelector('#audio-player');
if (!audioPlayer.paused) {
audioPlayer.pause();
}
// 展示要切换的设备检测tab页面
$(`#${targetPageId}`).show();
window[pageCallbackConfig[targetPageId]] && window[pageCallbackConfig[targetPageId]]();
}
});
// 摄像头设备切换
$('#camera-select').change(async function() {
let newCameraId = $(this)
.children('option:selected')
.val();
localStorage.setItem('txy_webRTC_cameraId', newCameraId);
cameraTestingResult.device = {
label: $(this)
.children('option:selected')
.text(),
deviceId: $(this)
.children('option:selected')
.val(),
kind: 'videoinput'
};
await localStream.switchDevice('video', newCameraId);
});
// 扬声器设备切换
$('#voice-select').change(async function() {
let newVoiceId = $(this)
.children('option:selected')
.val();
localStorage.setItem('txy_webRTC_voiceId', newVoiceId);
voiceTestingResult.device = {
label: $(this)
.children('option:selected')
.text(),
deviceId: $(this)
.children('option:selected')
.val(),
kind: 'audiooutput'
};
let audioPlayer = document.querySelector('#audio-player');
await audioPlayer.setSinkId(newVoiceId);
});
// 麦克风设备切换
$('#mic-select').change(async function() {
let newMicID = $(this)
.children('option:selected')
.val();
localStorage.setItem('txy_webRTC_micId', newMicID);
micTestingResult.device = {
label: $(this)
.children('option:selected')
.text(),
deviceId: $(this)
.children('option:selected')
.val(),
kind: 'audioinput'
};
await localStream.switchDevice('audio', newMicID);
});
$('body').on('click', function() {
$('#device-connect-list').hide();
});
// 获取设备信息
await getDevicesInfo();
// 初始化设备弹窗信息
deviceDialogInit();
}
/**
* 获取设备信息及网络连接信息
*/
async function getDevicesInfo() {
let micList = await TRTC.getMicrophones();
let voiceList = await TRTC.getSpeakers();
let cameraList = await TRTC.getCameras();
let index = isFilePath ? 'label' : 'deviceId';
if (cameraList.length > 0) {
hasCameraDevice = true;
}
if (micList.length > 0) {
hasMicAndVoiceDevice = true;
}
cameraList.forEach(camera => {
if (camera[index].length > 0) {
hasCameraConnect = true;
}
});
micList.forEach(mic => {
if (mic[index].length > 0) {
hasMicConnect = true;
}
});
if (isSafari) {
hasVoiceConnect = true;
} else {
voiceList.forEach(voice => {
if (voice[index].length > 0) {
hasVoiceConnect = true;
}
});
}
hasNetworkConnect = !!navigator.onLine;
}
/**
* 判断是否展示弹窗
*/
function deviceDialogInit() {
if (!localStorage.getItem('txy_device_testing')) {
localStorage.setItem('txy_device_testing', Date.now());
startDeviceConnect();
} else {
// 在首页展示设备连接结果
let showDeviceStatus = function() {
$('#device-connect-list').show();
timeout = setTimeout(() => {
$('#device-connect-list').hide();
}, 3000);
$('#connect-camera').css('color', `${hasCameraConnect ? 'green' : 'red'}`);
$('#connect-voice').css('color', `${hasVoiceConnect ? 'green' : 'red'}`);
$('#connect-mic').css('color', `${hasMicConnect ? 'green' : 'red'}`);
$('#connect-network').css('color', `${hasNetworkConnect ? 'green' : 'red'}`);
if (!(hasCameraConnect && hasVoiceConnect && hasMicConnect && hasNetworkConnect)) {
$('#device-testing-btn').css('color', 'red');
} else {
$('#device-testing-btn').css('color', 'green');
}
};
showDeviceStatus();
if (!(hasCameraConnect && hasVoiceConnect && hasMicConnect)) {
navigator.mediaDevices
.getUserMedia({ video: hasCameraDevice, audio: hasMicAndVoiceDevice })
.then(async () => {
// 重新获取设备信息
await getDevicesInfo();
// 更新首页popover的option list
getDevicesList();
// 展示连接结果
showDeviceStatus();
})
.catch(err => {});
}
}
}
/**
* 弹窗-设备连接检查
*/
function startDeviceConnect() {
// 显示设备检测弹窗
$('#device-testing-root').show();
// 设备检测弹窗-设备连接页
$('#device-testing-prepare').show();
curTestingPageId = 'device-testing-prepare';
initTestingTabTitle();
// 在设备检测弹窗显示设备连接信息
let showDeviceConnectInfo = function() {
if (!(hasCameraConnect && hasVoiceConnect && hasMicConnect && hasNetworkConnect)) {
$('#device-testing-btn').css('color', 'red');
} else {
$('#device-testing-btn').css('color', 'green');
}
// 隐藏设备连接失败提示
$('#connect-attention-container').hide();
// 设备连接中
$('#device-loading').show();
$('#connect-info')
.text('设备正在连接中,请稍等')
.css('color', '#cccccc');
$('#device-camera, #device-voice, #device-mic, #device-network').removeClass(
'connect-success connect-fail'
);
$('#connect-again-btn').hide();
$('#start-test-btn')
.addClass('start-gray')
.show();
// 设备连接结束,展示连接结果
setTimeout(() => {
$('#device-loading').hide();
$('#device-camera')
.removeClass('connect-success connect-fail')
.addClass(`${hasCameraConnect ? 'connect-success' : 'connect-fail'}`);
$('#device-voice')
.removeClass('connect-success connect-fail')
.addClass(`${hasVoiceConnect ? 'connect-success' : 'connect-fail'}`);
$('#device-mic')
.removeClass('connect-success connect-fail')
.addClass(`${hasMicConnect ? 'connect-success' : 'connect-fail'}`);
$('#device-network')
.removeClass('connect-success connect-fail')
.addClass(`${hasNetworkConnect ? 'connect-success' : 'connect-fail'}`);
if (!(hasCameraConnect && hasVoiceConnect && hasMicConnect)) {
let connectInfo = hasNetworkConnect
? '设备连接失败,请允许网页访问摄像头及麦克风'
: '设备及网络连接失败,请允许网页访问摄像头及麦克风并检查网络连接';
$('#connect-info')
.text(connectInfo)
.css('color', 'red');
// 显示设备连接失败引导
$('#connect-attention-container').show();
$('#connect-attention-info').html(deviceFailAttention);
// 切换按钮状态
$('#start-test-btn').hide();
$('#connect-again-btn').show();
}
if (hasCameraConnect && hasVoiceConnect && hasMicConnect && !hasNetworkConnect) {
$('#connect-info')
.text('网络连接失败,请检查网络连接')
.css('color', 'red');
// 显示网络连接失败引导
$('#connect-attention-container').show();
$('#connect-attention-info').html(networkFailAttention);
// 切换按钮状态
$('#start-test-btn').hide();
$('#connect-again-btn').show();
}
if (hasCameraConnect && hasVoiceConnect && hasMicConnect && hasNetworkConnect) {
$('#connect-info')
.text('设备及网络连接成功,请开始设备检测')
.css('color', '#32CD32');
$('#connect-again-btn').hide();
$('#start-test-btn')
.removeClass('start-gray')
.show();
}
}, 2000);
};
showDeviceConnectInfo();
// 如果有设备未连接,唤起请求弹窗
if (!(hasCameraConnect && hasVoiceConnect && hasMicConnect)) {
navigator.mediaDevices
.getUserMedia({ video: hasCameraDevice, audio: hasMicAndVoiceDevice })
.then(async () => {
// 重新获取设备信息
await getDevicesInfo();
// 更新首页popover的option list
getDevicesList();
// 显示设备连接信息
showDeviceConnectInfo();
})
.catch(err => {
console.log('err', err.name);
});
}
}
/**
* 更新首页popover的option list
*/
function getDevicesList() {
// populate camera options
TRTC.getCameras().then(devices => {
$('#camera-option').empty();
devices.forEach(device => {
if (!cameraId) {
// eslint-disable-next-line no-global-assign
cameraId = device.deviceId;
}
let div = $('<div></div>');
div.attr('id', device.deviceId);
div.html(device.label);
div.appendTo('#camera-option');
});
});
// populate microphone options
TRTC.getMicrophones().then(devices => {
$('#mic-option').empty();
devices.forEach(device => {
if (!micId) {
// eslint-disable-next-line no-global-assign
micId = device.deviceId;
}
let div = $('<div></div>');
div.attr('id', device.deviceId);
div.html(device.label);
div.appendTo('#mic-option');
});
});
}
/**
* 摄像头检测页-检测展示摄像头设备选择列表
*/
async function updateCameraDeviceList() {
let cameraDevices = await TRTC.getCameras();
cameraDevices.filter(camera => camera.deviceId !== 'default');
$('#camera-select').empty();
cameraDevices.forEach(camera => {
let option = $('<option></option>');
option.attr('value', camera.deviceId);
option.html(camera.label);
option.appendTo('#camera-select');
});
// 如果有用户设备选择缓存优先使用缓存的deviceId
let cacheCameraDevice = cameraDevices.filter(
camera => camera.deviceId === localStorage.getItem('txy_webRTC_cameraId')
);
if (cacheCameraDevice.length > 0) {
$('#camera-select').val(localStorage.getItem('txy_webRTC_cameraId'));
cameraTestingResult.device = cacheCameraDevice[0];
} else {
$('#camera-select').val(cameraDevices[0].deviceId);
cameraTestingResult.device = cameraDevices[0];
}
}
/**
* 摄像头设备测试
*/
async function startCameraTesting() {
$('#camera-testing-body').show();
curTestingPageId = 'camera-testing-body';
$('#camera-testing')
.removeClass('icon-normal')
.addClass('icon-blue complete');
completedTestingPageIdList.push('camera-testing-body');
completedTestingPageIdList = [...new Set(completedTestingPageIdList)];
await updateCameraDeviceList();
// 创建本地视频流
await createLocalStream(
{
audio: false,
video: true,
cameraId: cameraTestingResult.device.deviceId
},
'camera-video'
);
}
/**
* 初始化/更新扬声器设备数组
*/
async function updateVoiceDeviceList() {
// 获取扬声器设备并展示在界面中
let voiceDevices = await TRTC.getSpeakers();
voiceDevices = voiceDevices.filter(voice => voice.deviceId !== 'default');
$('#voice-select').empty();
voiceDevices.forEach(voice => {
let option = $('<option></option>');
option.attr('value', voice.deviceId);
option.html(voice.label);
option.appendTo('#voice-select');
});
// 如果有用户设备选择缓存优先使用缓存的deviceId
let cacheVoiceDevice = voiceDevices.filter(
mic => mic.deviceId === localStorage.getItem('txy_webRTC_voiceId')
);
if (cacheVoiceDevice.length > 0) {
$('#voice-select').val(localStorage.getItem('txy_webRTC_voiceId'));
voiceTestingResult.device = cacheVoiceDevice[0];
} else {
$('#voice-select').val(voiceDevices[0].deviceId);
voiceTestingResult.device = voiceDevices[0];
}
}
/**
* 播放器设备测试
*/
async function startVoiceTesting() {
$('#voice-testing-body').show();
curTestingPageId = 'voice-testing-body';
$('#voice-testing')
.removeClass('icon-gray')
.addClass('icon-blue complete');
completedTestingPageIdList.push('voice-testing-body');
completedTestingPageIdList = [...new Set(completedTestingPageIdList)];
await updateVoiceDeviceList();
}
/**
* 更新/初始化麦克风设备
*/
async function updateMicDeviceList() {
// 展示麦克风设备选择
let micDevices = await TRTC.getMicrophones();
micDevices = micDevices.filter(mic => mic.deviceId !== 'default');
$('#mic-select').empty();
micDevices.forEach(mic => {
let option = $('<option></option>');
option.attr('value', mic.deviceId);
option.html(mic.label);
option.appendTo('#mic-select');
});
// 如果有用户设备选择缓存优先使用缓存的deviceId
let cacheMicDevice = micDevices.filter(
mic => mic.deviceId === localStorage.getItem('txy_webRTC_micId')
);
if (cacheMicDevice.length > 0) {
$('#mic-select').val(localStorage.getItem('txy_webRTC_micId'));
micTestingResult.device = cacheMicDevice[0];
} else {
$('#mic-select').val(micDevices[0].deviceId);
micTestingResult.device = micDevices[0];
}
}
/**
* 麦克风设备测试
*/
async function startMicTesting() {
$('#mic-testing-body').show();
curTestingPageId = 'mic-testing-body';
$('#mic-testing')
.removeClass('icon-gray')
.addClass('icon-blue complete');
completedTestingPageIdList.push('mic-testing-body');
completedTestingPageIdList = [...new Set(completedTestingPageIdList)];
await updateMicDeviceList();
// 展示麦克风的声音大小显示
if ($('#mic-bar-container').children().length === 0) {
for (let index = 0; index < 28; index++) {
$('<div></div>')
.addClass('mic-bar')
.appendTo('#mic-bar-container');
}
}
// 创建本地音频流
await createLocalStream(
{
audio: true,
microphoneId: micTestingResult.device.deviceId,
video: false
},
'audio-container'
);
// 监听音量,并量化显示出来
setInterval(() => {
let volume = localStream.getAudioLevel();
let num = Math.ceil(28 * volume);
$('#mic-bar-container')
.children('.active')
.removeClass('active');
for (let i = 0; i < num; i++) {
$('#mic-bar-container')
.children()
.slice(0, i)
.addClass('active');
}
}, 100);
}
/**
* 系统信息展示
*/
async function startNetworkTesting() {
$('#network-testing-body').show();
$('#testing-report-btn').hide();
curTestingPageId = 'network-testing-body';
$('#network-testing')
.removeClass('icon-gray')
.addClass('icon-blue complete');
completedTestingPageIdList.push('network-testing-body');
completedTestingPageIdList = [...new Set(completedTestingPageIdList)];
networkQualityNum = 0;
$('#uplink-network')
.addClass('network-loading')
.text('');
// 获取系统信息
$('#system').empty();
let OSInfo = getOS();
$('<div></div>')
.text(OSInfo.osName)
.appendTo('#system');
// 获取浏览器及版本信息
$('#browser').empty();
let browser = getBroswer();
$('<div></div>')
.text(`${browser.broswer} ${browser.version}`)
.appendTo('#browser');
// 获取ip地址信息
// $('#ip').empty();
// let IPAddress = await getIPAddress();
// $('<div></div>').text(IPAddress).appendTo('#ip');
// networkTestingResult.IPAddress = IPAddress;
// 是否支持屏幕分享能力
$('#screen-share').empty();
let isScreenShareSupported = TRTC.isScreenShareSupported();
$('<div></div>')
.text(isScreenShareSupported ? '支持' : '不支持')
.appendTo('#screen-share');
// 上下行网络质量
presetting.login(false, async options => {
client = TRTC.createClient({ mode: 'rtc', ...options });
client.on('network-quality', event => {
networkQualityNum++;
// 收到3次'network-quality'事件的时候认为拿到了网络实际质量
if (networkQualityNum === 3) {
networkTestingResult.upLinkNetwork = event.uplinkNetworkQuality;
networkTestingResult.downLinkNetwork = event.downlinkNetworkQuality;
$('#uplink-network')
.removeClass('network-loading')
.text(NETWORK_QUALITY[String(networkTestingResult.upLinkNetwork)]);
$('#testing-report-btn').show();
client && client.leave();
client && client.off('network-quality');
}
});
await client.join({
roomId: options.roomId
});
await createLocalStream(
{
audio: true,
video: false
},
'audio-container'
);
await client.publish(localStream);
// 音频轨道静音
localStream.muteAudio();
});
}
/**
* 展示检测报告
*/
function showTestingReport() {
$('#device-testing').hide();
$('#network-testing-body').hide();
$('#device-testing-report').show();
curTestingPageId = 'device-testing-report';
// 摄像头检测结果
$('#camera-name').text(cameraTestingResult.device.label);
if (cameraTestingResult.statusResult) {
$('#camera-testing-result')
.text('正常')
.css('color', 'green');
} else {
$('#camera-testing-result')
.text('异常')
.css('color', 'red');
}
// 扬声器检测结果(safari浏览器不显示扬声器检测结果)
if (!isSafari) {
$('#voice-name').text(voiceTestingResult.device.label);
if (voiceTestingResult.statusResult) {
$('#voice-testing-result')
.text('正常')
.css('color', 'green');
} else {
$('#voice-testing-result')
.text('异常')
.css('color', 'red');
}
}
// 麦克风检测结果
$('#mic-name').text(micTestingResult.device.label);
if (micTestingResult.statusResult) {
$('#mic-testing-result')
.text('正常')
.css('color', 'green');
} else {
$('#mic-testing-result')
.text('异常')
.css('color', 'red');
}
// 网络检测结果
// $('#network-name').text(networkTestingResult.IPAddress);
$('#network-name').text('网络质量');
$('#network-testing-result')
.html(`${NETWORK_QUALITY[String(networkTestingResult.upLinkNetwork)]}`)
.css('color', `${Number(networkTestingResult.upLinkNetwork) > 3 ? 'red' : 'green'}`);
}
/**
* 结束设备检测,隐藏设备检测弹窗
*/
function finishDeviceTesting() {
$('#device-testing-root').hide();
$('#device-testing').hide();
$(`#${curTestingPageId}`).hide();
curTestingPageId = '';
completedTestingPageIdList = [];
// 停止摄像头/麦克风的流采集并释放摄像头/麦克风设备
localStream && localStream.close();
client && client.leave();
client && client.off('network-quality');
// 停止播放器的音乐
let audioPlayer = document.querySelector('#audio-player');
if (!audioPlayer.paused) {
audioPlayer.pause();
}
audioPlayer.currentTime = 0;
}
/**
* 恢复检测页面头部图标的状态
*/
function initTestingTabTitle() {
['camera', 'voice', 'mic', 'network'].forEach(item => {
$(`#${item}-testing`)
.removeClass('icon-blue complete')
.addClass('icon-gray');
});
}
/**
* 监听设备变化
*/
navigator.mediaDevices.ondevicechange = async function(event) {
// 当前在摄像头检测页
if (curTestingPageId === 'camera-testing-body') {
await updateCameraDeviceList();
return;
}
// 当前在扬声器检测页
if (curTestingPageId === 'voice-testing-body') {
await updateVoiceDeviceList();
return;
}
// 当前在麦克风检测页
if (curTestingPageId === 'mic-testing-body') {
await updateMicDeviceList();
return;
}
};
/**
* 抽离createStream的公共处理函数
*/
async function createLocalStream(constraints, container) {
localStream = TRTC.createStream(constraints);
try {
await localStream.initialize();
} catch (error) {
switch (error.name) {
case 'NotReadableError':
// 当系统或浏览器异常的时候,可能会出现此错误,您可能需要引导用户重启电脑/浏览器来尝试恢复。
alert('暂时无法访问摄像头/麦克风,请确保当前没有其他应用请求访问摄像头/麦克风,并重试');
return;
case 'NotAllowedError':
// 用户拒绝授权访问摄像头或麦克风 | 屏幕分享,您需要引导客户来授权访问
alert('用户已拒绝授权访问摄像头或麦克风');
return;
case 'NotFoundError':
// 找不到摄像头或麦克风设备
alert('找不到摄像头或麦克风设备');
return;
case 'OverConstrainedError':
alert(
'采集属性设置错误,如果您指定了 cameraId/microphoneId请确保它们是一个有效的非空字符串'
);
return;
default:
alert('未知错误');
return;
}
}
container && localStream.play(container);
}

1
public/js/iconfont.js Normal file

File diff suppressed because one or more lines are too long

51
public/js/index.js Normal file
View File

@@ -0,0 +1,51 @@
/* eslint-disable no-global-assign */
/* global $ TRTC Presetting deviceTestingInit cameraId micId */
const presetting = new Presetting();
presetting.init();
deviceTestingInit();
// check if browser is compatible with TRTC
TRTC.checkSystemRequirements().then(result => {
if (!result) {
alert('您的浏览器不兼容此应用!\n建议下载最新版Chrome浏览器');
window.location.href = 'http://www.google.cn/chrome/';
}
});
// setup logging stuffs
TRTC.Logger.setLogLevel(TRTC.Logger.LogLevel.DEBUG);
TRTC.Logger.enableUploadLog();
TRTC.getDevices()
.then(devices => {
devices.forEach(item => {
console.log('device: ' + item.kind + ' ' + item.label + ' ' + item.deviceId);
});
})
.catch(error => console.error('getDevices error observed ' + error));
// populate camera options
TRTC.getCameras().then(devices => {
devices.forEach(device => {
if (!cameraId) {
cameraId = device.deviceId;
}
let div = $('<div></div>');
div.attr('id', device.deviceId);
div.html(device.label);
div.appendTo('#camera-option');
});
});
// populate microphone options
TRTC.getMicrophones().then(devices => {
devices.forEach(device => {
if (!micId) {
micId = device.deviceId;
}
let div = $('<div></div>');
div.attr('id', device.deviceId);
div.html(device.label);
div.appendTo('#mic-option');
});
});

4
public/js/jquery-3.2.1.min.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

2442
public/js/popper.js Normal file

File diff suppressed because it is too large Load Diff

45
public/js/presetting.js Normal file
View File

@@ -0,0 +1,45 @@
/* global $ setBtnClickFuc genTestUserSig */
// preset before starting RTC
class Presetting {
init() {
// populate userId/roomId
$('#userId').val('user_30400097');
$('#roomId').val(parseInt(Math.random() * 100000));
const roomId = this.query('roomId');
const userId = this.query('userId');
if (roomId) {
$('#roomId').val(roomId);
}
if (userId) {
$('#userId').val(userId);
}
$('#main-video-btns').hide();
$('.mask').hide();
setBtnClickFuc();
}
query(name) {
const match = window.location.search.match(new RegExp('(\\?|&)' + name + '=([^&]*)(&|$)'));
return !match ? '' : decodeURIComponent(match[2]);
}
login(share, callback) {
let userId = $('#userId').val();
if (share) {
userId = 'share_' + userId;
}
console.log(userId)
const config = genTestUserSig(userId);
const sdkAppId = config.sdkAppId;
const userSig = config.userSig;
const roomId = $('#roomId').val();
callback({
sdkAppId,
userId,
userSig,
roomId
});
}
}

341
public/js/rtc-client.js Normal file
View File

@@ -0,0 +1,341 @@
/* global $ TRTC getCameraId getMicrophoneId resetView isHidden shareUserId addMemberView removeView addVideoView */
class RtcClient {
constructor(options) {
this.sdkAppId_ = options.sdkAppId;
this.userId_ = options.userId;
this.userSig_ = options.userSig;
this.roomId_ = options.roomId;
this.isJoined_ = false;
this.isPublished_ = false;
this.isAudioMuted = false;
this.isVideoMuted = false;
this.localStream_ = null;
this.remoteStreams_ = [];
this.members_ = new Map();
// create a client for RtcClient
this.client_ = TRTC.createClient({
mode: 'rtc',
sdkAppId: this.sdkAppId_,
userId: this.userId_,
userSig: this.userSig_
});
this.handleEvents();
}
async join() {
if (this.isJoined_) {
console.warn('duplicate RtcClient.join() observed');
return;
}
try {
// join the room
await this.client_.join({
roomId: this.roomId_
});
console.log('join room success');
this.isJoined_ = true;
// create a local stream with audio/video from microphone/camera
if (getCameraId() && getMicrophoneId()) {
this.localStream_ = TRTC.createStream({
audio: true,
video: true,
userId: this.userId_,
cameraId: getCameraId(),
microphoneId: getMicrophoneId(),
mirror: true
});
} else {
// not to specify cameraId/microphoneId to avoid OverConstrainedError
this.localStream_ = TRTC.createStream({
audio: true,
video: true,
userId: this.userId_,
mirror: true
});
}
try {
// initialize the local stream and the stream will be populated with audio/video
await this.localStream_.initialize();
console.log('initialize local stream success');
this.localStream_.on('player-state-changed', event => {
console.log(`local stream ${event.type} player is ${event.state}`);
});
// publish the local stream
await this.publish();
this.localStream_.play('main-video');
$('#main-video-btns').show();
$('#mask_main').appendTo($('#player_' + this.localStream_.getId()));
} catch (e) {
console.error('failed to initialize local stream - ' + e);
}
} catch (e) {
console.error('join room failed! ' + e);
}
//更新成员状态
let states = this.client_.getRemoteMutedState();
for (let state of states) {
if (state.audioMuted) {
$('#' + state.userId)
.find('.member-audio-btn')
.attr('src', './img/mic-off.png');
}
if (state.videoMuted) {
$('#' + state.userId)
.find('.member-video-btn')
.attr('src', './img/camera-off.png');
$('#mask_' + this.members_.get(state.userId).getId()).show();
}
}
}
async leave() {
if (!this.isJoined_) {
console.warn('leave() - please join() firstly');
return;
}
// ensure the local stream is unpublished before leaving.
await this.unpublish();
// leave the room
await this.client_.leave();
this.localStream_.stop();
this.localStream_.close();
this.localStream_ = null;
this.isJoined_ = false;
resetView();
}
async publish() {
if (!this.isJoined_) {
console.warn('publish() - please join() firstly');
return;
}
if (this.isPublished_) {
console.warn('duplicate RtcClient.publish() observed');
return;
}
try {
await this.client_.publish(this.localStream_);
} catch (e) {
console.error('failed to publish local stream ' + e);
this.isPublished_ = false;
}
this.isPublished_ = true;
}
async unpublish() {
if (!this.isJoined_) {
console.warn('unpublish() - please join() firstly');
return;
}
if (!this.isPublished_) {
console.warn('RtcClient.unpublish() called but not published yet');
return;
}
await this.client_.unpublish(this.localStream_);
this.isPublished_ = false;
}
muteLocalAudio() {
this.localStream_.muteAudio();
}
unmuteLocalAudio() {
this.localStream_.unmuteAudio();
}
muteLocalVideo() {
this.localStream_.muteVideo();
}
unmuteLocalVideo() {
this.localStream_.unmuteVideo();
}
resumeStreams() {
this.localStream_.resume();
for (let stream of this.remoteStreams_) {
stream.resume();
}
}
handleEvents() {
this.client_.on('error', err => {
console.error(err);
alert(err);
location.reload();
});
this.client_.on('client-banned', err => {
console.error('client has been banned for ' + err);
if (!isHidden()) {
alert('您已被踢出房间');
location.reload();
} else {
document.addEventListener(
'visibilitychange',
() => {
if (!isHidden()) {
alert('您已被踢出房间');
location.reload();
}
},
false
);
}
});
// fired when a remote peer is joining the room
this.client_.on('peer-join', evt => {
const userId = evt.userId;
console.log('peer-join ' + userId);
if (userId !== shareUserId) {
addMemberView(userId);
}
});
// fired when a remote peer is leaving the room
this.client_.on('peer-leave', evt => {
const userId = evt.userId;
removeView(userId);
console.log('peer-leave ' + userId);
});
// fired when a remote stream is added
this.client_.on('stream-added', evt => {
const remoteStream = evt.stream;
const id = remoteStream.getId();
const userId = remoteStream.getUserId();
this.members_.set(userId, remoteStream);
console.log(`remote stream added: [${userId}] ID: ${id} type: ${remoteStream.getType()}`);
if (remoteStream.getUserId() === shareUserId) {
// don't need screen shared by us
this.client_.unsubscribe(remoteStream);
} else {
console.log('subscribe to this remote stream');
this.client_.subscribe(remoteStream);
}
});
// fired when a remote stream has been subscribed
this.client_.on('stream-subscribed', evt => {
const uid = evt.userId;
const remoteStream = evt.stream;
const id = remoteStream.getId();
this.remoteStreams_.push(remoteStream);
remoteStream.on('player-state-changed', event => {
console.log(`${event.type} player is ${event.state}`);
if (event.type == 'video' && event.state == 'STOPPED') {
$('#mask_' + remoteStream.getId()).show();
$('#' + remoteStream.getUserId())
.find('.member-video-btn')
.attr('src', 'img/camera-off.png');
}
if (event.type == 'video' && event.state == 'PLAYING') {
$('#mask_' + remoteStream.getId()).hide();
$('#' + remoteStream.getUserId())
.find('.member-video-btn')
.attr('src', 'img/camera-on.png');
}
});
addVideoView(id);
// objectFit 为播放的填充模式详细参考https://trtc-1252463788.file.myqcloud.com/web/docs/Stream.html#play
remoteStream.play(id, { objectFit: 'contain' });
//添加“摄像头未打开”遮罩
let mask = $('#mask_main').clone();
mask.attr('id', 'mask_' + id);
mask.appendTo($('#player_' + id));
mask.hide();
if (!remoteStream.hasVideo()) {
mask.show();
$('#' + remoteStream.getUserId())
.find('.member-video-btn')
.attr('src', 'img/camera-off.png');
}
console.log('stream-subscribed ID: ', id);
});
// fired when the remote stream is removed, e.g. the remote user called Client.unpublish()
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()}`);
});
this.client_.on('stream-updated', evt => {
const remoteStream = evt.stream;
let uid = this.getUidByStreamId(remoteStream.getId());
if (!remoteStream.hasVideo()) {
$('#' + uid)
.find('.member-video-btn')
.attr('src', 'img/camera-off.png');
}
console.log(
'type: ' +
remoteStream.getType() +
' stream-updated hasAudio: ' +
remoteStream.hasAudio() +
' hasVideo: ' +
remoteStream.hasVideo() +
' uid: ' +
uid
);
});
this.client_.on('mute-audio', evt => {
console.log(evt.userId + ' mute audio');
$('#' + evt.userId)
.find('.member-audio-btn')
.attr('src', 'img/mic-off.png');
});
this.client_.on('unmute-audio', evt => {
console.log(evt.userId + ' unmute audio');
$('#' + evt.userId)
.find('.member-audio-btn')
.attr('src', 'img/mic-on.png');
});
this.client_.on('mute-video', evt => {
console.log(evt.userId + ' mute video');
$('#' + evt.userId)
.find('.member-video-btn')
.attr('src', 'img/camera-off.png');
let streamId = this.members_.get(evt.userId).getId();
if (streamId) {
$('#mask_' + streamId).show();
}
});
this.client_.on('unmute-video', evt => {
console.log(evt.userId + ' unmute video');
$('#' + evt.userId)
.find('.member-video-btn')
.attr('src', 'img/camera-on.png');
const stream = this.members_.get(evt.userId);
if (stream) {
let streamId = stream.getId();
if (streamId) {
$('#mask_' + streamId).hide();
}
}
});
}
showStreamState(stream) {
console.log('has audio: ' + stream.hasAudio() + ' has video: ' + stream.hasVideo());
}
getUidByStreamId(streamId) {
for (let [uid, stream] of this.members_) {
if (stream.getId() == streamId) {
return uid;
}
}
}
}

163
public/js/share-client.js Normal file
View File

@@ -0,0 +1,163 @@
/* global $ TRTC */
class ShareClient {
constructor(options) {
this.sdkAppId_ = options.sdkAppId;
this.userId_ = options.userId;
this.userSig_ = options.userSig;
this.roomId_ = options.roomId;
this.isJoined_ = false;
this.isPublished_ = false;
this.localStream_ = null;
this.client_ = TRTC.createClient({
mode: 'rtc',
sdkAppId: this.sdkAppId_,
userId: this.userId_,
userSig: this.userSig_,
/**
* disable receivers to avoid receiving remote streams as we only want to
* publish the screen stream
*/
disableReceiver: true
});
this.client_.setDefaultMuteRemoteStreams(true);
this.handleEvents();
}
async join() {
if (this.isJoined_) {
console.warn('duplicate RtcClient.join() observed');
return;
}
try {
await this.client_.join({
roomId: this.roomId_
});
console.log('ShareClient join room success');
this.isJoined_ = true;
// create a local stream for screen share
this.localStream_ = TRTC.createStream({
// disable audio as RtcClient already enable audio
audio: false,
// enable screen share
screen: true,
userId: this.userId_
});
try {
// initialize the local stream to populate the screen stream
await this.localStream_.initialize();
console.log('ShareClient initialize local stream for screen share success');
this.localStream_.on('player-state-changed', event => {
console.log(`local stream ${event.type} player is ${event.state}`);
});
this.localStream_.on('screen-sharing-stopped', event => {
console.log('share stream video track enned');
this.leave();
$('#screen-btn').attr('src', './img/screen-off.png');
});
// publish the screen share stream
await this.client_.publish(this.localStream_);
} catch (e) {
console.error('ShareClient failed to initialize local stream - ' + e);
//用户取消分享屏幕导致推流失败
await this.client_.leave();
this.isJoined_ = false;
$('#screen-btn').attr('src', 'img/screen-off.png');
}
} catch (e) {
console.error('ShareClient join room failed! ' + e);
}
}
async leave() {
if (!this.isJoined_) {
console.warn('leave() - please join() firstly');
return;
}
if (this.isPublished_) {
await this.client_.unpublish(this.localStream_);
this.isPublished_ = false;
}
await this.client_.leave();
if (this.localStream_) {
this.localStream_.close();
this.localStream_ = null;
}
this.isJoined_ = false;
}
handleEvents() {
this.client_.on('error', err => {
console.error(err);
alert(err);
});
this.client_.on('client-banned', err => {
console.error('client has been banned for ' + err);
});
// fired when a remote peer is joining the room
this.client_.on('peer-join', evt => {
const userId = evt.userId;
console.log('peer-join ' + userId);
});
// fired when a remote peer is leaving the room
this.client_.on('peer-leave', evt => {
const userId = evt.userId;
console.log('peer-leave ' + userId);
});
// fired when a remote stream is added
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()}`);
console.log('subscribe to this remote stream');
});
// fired when a remote stream has been subscribed
this.client_.on('stream-subscribed', evt => {
const remoteStream = evt.stream;
const id = remoteStream.getId();
remoteStream.on('player-state-changed', event => {
console.log(`${event.type} player is ${event.state}`);
});
console.log('stream-subscribed ID: ', id);
});
// fired when the remote stream is removed, e.g. the remote user called Client.unpublish()
this.client_.on('stream-removed', evt => {
const remoteStream = evt.stream;
const id = remoteStream.getId();
console.log(`stream-removed ID: ${id} type: ${remoteStream.getType()}`);
});
this.client_.on('stream-updated', evt => {
const remoteStream = evt.stream;
console.log(
'type: ' +
remoteStream.getType() +
' stream-updated hasAudio: ' +
remoteStream.hasAudio() +
' hasVideo: ' +
remoteStream.hasVideo() +
' uid: ' +
remoteStream.getUserId()
);
});
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');
});
}
}

1
public/js/trtc.js Normal file

File diff suppressed because one or more lines are too long