init live
This commit is contained in:
6939
public/js/bootstrap-material-design.js
vendored
Normal file
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
660
public/js/common.js
Normal 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();
|
||||
}
|
||||
}
|
||||
63
public/js/debug/GenerateTestUserSig.js
Normal file
63
public/js/debug/GenerateTestUserSig.js
Normal file
@@ -0,0 +1,63 @@
|
||||
/* 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 = 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
838
public/js/device-testing.js
Normal 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
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
51
public/js/index.js
Normal 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
4
public/js/jquery-3.2.1.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
2
public/js/lib-generate-test-usersig.min.js
vendored
Normal file
2
public/js/lib-generate-test-usersig.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
2442
public/js/popper.js
Normal file
2442
public/js/popper.js
Normal file
File diff suppressed because it is too large
Load Diff
45
public/js/presetting.js
Normal file
45
public/js/presetting.js
Normal 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
341
public/js/rtc-client.js
Normal 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
163
public/js/share-client.js
Normal 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
1
public/js/trtc.js
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user