EVOLUTION-MANAGER
Edit File: room.js
/* Licensed under the Apache License, Version 2.0 (the "License") http://www.apache.org/licenses/LICENSE-2.0 */ var Video = (function() { const self = {} , AudioCtx = window.AudioContext || window.webkitAudioContext; let sd, v, vc, t, footer, size, vol, video, iceServers , lm, level, userSpeaks = false, muteOthers, hasVideo; function _resizeDlgArea(_w, _h) { if (Room.getOptions().interview) { VideoUtil.setPos(v, VideoUtil.getPos()); } else if (v.dialog('instance')) { v.dialog('option', 'width', _w).dialog('option', 'height', _h); } } function _micActivity(speaks) { if (speaks !== userSpeaks) { userSpeaks = speaks; OmUtil.sendMessage({type: 'mic', id: 'activity', active: speaks}); } } function _getScreenStream(msg, callback) { function __handleScreenError(err) { VideoManager.sendMessage({id: 'errorSharing'}); Sharer.setShareState(SHARE_STOPED); Sharer.setRecState(SHARE_STOPED); OmUtil.error(err); } const b = kurentoUtils.WebRtcPeer.browser; let promise, cnts; if (VideoUtil.isEdge(b) && b.major > 16) { cnts = { video: true }; promise = navigator.getDisplayMedia(cnts); } else if (b.name === 'Firefox') { // https://mozilla.github.io/webrtc-landing/gum_test.html cnts = Sharer.baseConstraints(sd); cnts.video.mediaSource = sd.shareType; promise = navigator.mediaDevices.getUserMedia(cnts); } else if (VideoUtil.isChrome(b)) { cnts = { video: true }; promise = navigator.mediaDevices.getDisplayMedia(cnts); } else { promise = new Promise(() => { Sharer.close(); throw 'Screen-sharing is not supported in ' + b.name + '[' + b.major + ']'; }); } promise.then(function(stream) { __createVideo(); callback(msg, cnts, stream); }).catch(__handleScreenError); } function _getVideoStream(msg, callback) { VideoSettings.constraints(sd, function(cnts) { if ((VideoUtil.hasCam(sd) && !cnts.video) || (VideoUtil.hasMic(sd) && !cnts.audio)) { VideoManager.sendMessage({ id : 'devicesAltered' , uid: sd.uid , audio: !!cnts.audio , video: !!cnts.video }); } if (!cnts.audio && !cnts.video) { OmUtil.error('Requested devices are not available'); VideoManager.close(sd.uid) return; } navigator.mediaDevices.getUserMedia(cnts) .then(function(stream) { let _stream = stream; const data = {}; if (stream.getAudioTracks().length !== 0) { lm = vc.find('.level-meter'); lm.show(); data.aCtx = new AudioCtx(); data.gainNode = data.aCtx.createGain(); data.analyser = data.aCtx.createAnalyser(); data.aSrc = data.aCtx.createMediaStreamSource(stream); data.aSrc.connect(data.gainNode); data.gainNode.connect(data.analyser); if (VideoUtil.isEdge()) { data.analyser.connect(data.aCtx.destination); } else { data.aDest = data.aCtx.createMediaStreamDestination(); data.analyser.connect(data.aDest); data.aSrc.origStream = stream; _stream = data.aDest.stream; stream.getVideoTracks().forEach(function(track) { _stream.addTrack(track); }); } } __createVideo(data); callback(msg, cnts, _stream); }) .catch(function(err) { VideoManager.sendMessage({ id : 'devicesAltered' , uid: sd.uid , audio: false , video: false }); VideoManager.close(sd.uid); if ('NotReadableError' === err.name) { OmUtil.error('Camera/Microphone is busy and can\'t be used'); } else { OmUtil.error(err); } }); }); } function __attachListener(rtcPeer) { if (rtcPeer) { const pc = rtcPeer.peerConnection; pc.onconnectionstatechange = function(event) { console.warn(`!!RTCPeerConnection state changed: ${pc.connectionState}, user: ${sd.user.displayName}, uid: ${sd.uid}`); switch(pc.connectionState) { case "connected": if (sd.self) { // The connection has become fully connected OmUtil.alert('info', `Connection to Media server has been established`, 3000);//notify user } break; case "disconnected": case "failed": //connection has been dropped OmUtil.alert('warning', `Media server connection for user ${sd.user.displayName} is ${pc.connectionState}, will try to re-connect`, 3000);//notify user _refresh(); break; case "closed": // The connection has been closed break; } } } } function __createSendPeer(msg, cnts, stream) { const options = { videoStream: stream , mediaConstraints: cnts , onicecandidate: self.onIceCandidate }; if (!VideoUtil.isSharing(sd)) { options.localVideo = video[0]; } const data = video.data(); data.rtcPeer = new kurentoUtils.WebRtcPeer.WebRtcPeerSendonly( VideoUtil.addIceServers(options, msg) , function (error) { if (error) { if (true === this.cleaned) { return; } return OmUtil.error(error); } if (data.analyser) { level = MicLevel(); level.meter(data.analyser, lm, _micActivity, OmUtil.error); } this.generateOffer(function(error, offerSdp) { if (error) { if (true === this.cleaned) { return; } return OmUtil.error('Sender sdp offer error ' + error); } OmUtil.log('Invoking Sender SDP offer callback function'); VideoManager.sendMessage({ id : 'broadcastStarted' , uid: sd.uid , sdpOffer: offerSdp }); if (VideoUtil.isSharing(sd)) { Sharer.setShareState(SHARE_STARTED); } if (VideoUtil.isRecording(sd)) { Sharer.setRecState(SHARE_STARTED); } }); }); __attachListener(data.rtcPeer); } function _createSendPeer(msg) { if (VideoUtil.isSharing(sd) || VideoUtil.isRecording(sd)) { _getScreenStream(msg, __createSendPeer); } else { _getVideoStream(msg, __createSendPeer); } } function _createResvPeer(msg) { __createVideo(); const options = VideoUtil.addIceServers({ remoteVideo : video[0] , onicecandidate : self.onIceCandidate }, msg); const data = video.data(); data.rtcPeer = new kurentoUtils.WebRtcPeer.WebRtcPeerRecvonly( options , function(error) { if (error) { if (true === this.cleaned) { return; } return OmUtil.error(error); } this.generateOffer(function(error, offerSdp) { if (error) { if (true === this.cleaned) { return; } return OmUtil.error('Receiver sdp offer error ' + error); } OmUtil.log('Invoking Receiver SDP offer callback function'); VideoManager.sendMessage({ id : 'addListener' , sender: sd.uid , sdpOffer: offerSdp }); }); }); __attachListener(data.rtcPeer); } function _handleMicStatus(state) { if (!footer || !footer.is(':visible')) { return; } if (state) { footer.text(footer.data('on')); footer.addClass('mic-on'); t.addClass('mic-on'); } else { footer.text(footer.data('off')); footer.removeClass('mic-on'); t.removeClass('mic-on'); } } function _initContainer(_id, name, opts) { let contSel = '#user' + sd.cuid; hasVideo = VideoUtil.hasVideo(sd) || $(contSel).length < 1; if (hasVideo) { if (opts.interview) { const area = $('.pod-area'); const contId = uuidv4(); contSel = '#' + contId; area.append($('<div class="pod"></div>').attr('id', contId)); WbArea.updateAreaClass(); } else { contSel = '.room-block .room-container'; } } $(contSel).append(OmUtil.tmpl('#user-video', _id) .attr('title', name) .attr('data-client-uid', sd.cuid) .attr('data-client-type', sd.type) .data(self)); v = $('#' + _id); vc = v.find('.video'); muteOthers = vc.find('.mute-others'); _setRights(); return contSel; } function _initDialog(v, opts) { if (opts.interview) { v.dialog('option', 'draggable', false); v.dialog('option', 'resizable', false); $('.pod-area').sortable('refresh'); } else { v.dialog('option', 'draggable', true); v.dialog('option', 'resizable', true); if (VideoUtil.isSharing(sd)) { v.on('dialogclose', function() { VideoManager.close(sd.uid, true); }); } } _initDialogBtns(opts); } function _initDialogBtns(opts) { function noDblClick(e) { e.dblclick(function(e) { e.stopImmediatePropagation(); return false; }); } v.parent().find('.ui-dialog-titlebar-close').remove(); v.parent().find('.ui-dialog-titlebar').append(OmUtil.tmpl('#video-button-bar')); const refresh = v.parent().find('.btn-refresh') , tgl = v.parent().find('.btn-toggle') , cls = v.parent().find('.btn-wclose'); if (VideoUtil.isSharing(sd)) { cls.click(function (e) { v.dialog('close'); return false; }); noDblClick(cls); refresh.remove(); } else { cls.remove(); refresh.click(function(e) { e.stopImmediatePropagation(); _refresh(); return false; }); } if (opts.interview) { tgl.remove(); } else { tgl.click(function (e) { e.stopImmediatePropagation(); $(this).toggleClass('minimized'); v.toggle(); return false; }); noDblClick(tgl); } } function _init(msg) { sd = msg.stream; if (!vol) { vol = Volume(); } iceServers = msg.iceServers; sd.activities = sd.activities.sort(); size = {width: sd.width, height: sd.height}; const _id = VideoUtil.getVid(sd.uid) , name = sd.user.displayName , _w = sd.width , _h = sd.height , isSharing = VideoUtil.isSharing(sd) , isRecording = VideoUtil.isRecording(sd) , opts = Room.getOptions(); sd.self = sd.cuid === opts.uid; const contSel = _initContainer(_id, name, opts); footer = v.find('.footer'); if (!opts.showMicStatus) { footer.hide(); } if (!sd.self && isSharing) { Sharer.close(); } if (sd.self && (isSharing || isRecording)) { v.hide(); } else if (hasVideo) { v.dialog({ classes: { 'ui-dialog': 'video user-video' + (opts.showMicStatus ? ' mic-status' : '') , 'ui-dialog-titlebar': '' + (opts.showMicStatus ? ' ui-state-highlight' : '') } , width: _w , minWidth: 40 , minHeight: 50 , autoOpen: true , modal: false , appendTo: contSel }); _initDialog(v, opts); } t = v.parent().find('.ui-dialog-titlebar').attr('title', name); v.on('remove', _cleanup); if (hasVideo) { vc.width(_w).height(_h); } _refresh(msg); if (!isSharing && !isRecording) { VideoUtil.setPos(v, VideoUtil.getPos(VideoUtil.getRects(VIDWIN_SEL), sd.width, sd.height + 25)); } return v; } function _update(_c) { const prevA = sd.activities; sd.activities = _c.activities.sort(); sd.level = _c.level; sd.user.firstName = _c.user.firstName; sd.user.lastName = _c.user.lastName; sd.user.displayName = _c.user.displayName; const name = sd.user.displayName; if (hasVideo) { v.dialog('option', 'title', name).parent().find('.ui-dialog-titlebar').attr('title', name); } const same = prevA.length === sd.activities.length && prevA.every(function(value, index) { return value === sd.activities[index]}) if (sd.self && !same) { _cleanup(); v.remove(); _init({stream: sd, iceServers: iceServers}); } } function __createVideo(data) { const _id = VideoUtil.getVid(sd.uid); _resizeDlgArea(hasVideo ? size.width : 120 , hasVideo ? size.height : 90); video = $(hasVideo ? '<video>' : '<audio>').attr('id', 'vid' + _id) .width(vc.width()).height(vc.height()) .prop('autoplay', true).prop('controls', false); if (data) { video.data(data); } if (hasVideo) { vc.removeClass('audio-only').css('background-image', '');; vc.parents('.ui-dialog').removeClass('audio-only'); video.attr('poster', sd.user.pictureUri); } else { vc.addClass('audio-only'); } vc.append(video); if (VideoUtil.hasMic(sd)) { const volIco = vol.create(self) if (hasVideo) { v.parent().find('.buttonpane').append(volIco); } else { volIco.addClass('ulist-small'); volIco.insertAfter('#user' + sd.cuid + ' .typing-activity'); } } else { vol.destroy(); } } function _refresh(_msg) { const msg = _msg || {iceServers: iceServers}; _cleanup(); const hasAudio = VideoUtil.hasMic(sd); if (sd.self) { _createSendPeer(msg); _handleMicStatus(hasAudio); } else { _createResvPeer(msg); } } function _setRights() { if (Room.hasRight(['MUTE_OTHERS']) && VideoUtil.hasMic(sd)) { muteOthers.addClass('enabled').click(function() { VideoManager.clickMuteOthers(sd.uid); }); } else { muteOthers.removeClass('enabled').off(); } } function _cleanup() { OmUtil.log('Disposing participant ' + sd.uid); if (video && video.length > 0) { const data = video.data(); if (data.analyser) { VideoUtil.disconnect(data.analyser); data.analyser = null; } if (data.gainNode) { VideoUtil.disconnect(data.gainNode); data.gainNode = null; } if (data.aSrc) { VideoUtil.cleanStream(data.aSrc.mediaStream); VideoUtil.cleanStream(data.aSrc.origStream); VideoUtil.disconnect(data.aSrc); data.aSrc = null; } if (data.aDest) { VideoUtil.disconnect(data.aDest); data.aDest = null; } if (data.aCtx) { if (data.aCtx.destination) { VideoUtil.disconnect(data.aCtx.destination); } data.aCtx.close(); data.aCtx = null; } video.attr('id', 'dummy'); const vidNode = video[0]; VideoUtil.cleanStream(vidNode.srcObject); vidNode.srcObject = null; vidNode.parentNode.removeChild(vidNode); VideoUtil.cleanPeer(data.rtcPeer); video = null; } if (lm && lm.length > 0) { _micActivity(false); lm.hide(); muteOthers.removeClass('enabled').off(); } if (level) { level.dispose(); level = null; } vc.find('audio,video').remove(); vol.destroy(); } function _reattachStream() { if (video && video.length > 0) { const data = video.data(); if (data.rtcPeer) { video[0].srcObject = sd.self ? data.rtcPeer.getLocalStream() : data.rtcPeer.getRemoteStream(); } } } self.update = _update; self.refresh = _refresh; self.mute = function(_mute) { vol.mute(_mute); }; self.isMuted = function() { return vol.muted(); }; self.init = _init; self.stream = function() { return sd; }; self.setRights = _setRights; self.getPeer = function() { return video ? video.data().rtcPeer : null; }; self.onIceCandidate = function(candidate) { const opts = Room.getOptions(); OmUtil.log('Local candidate ' + JSON.stringify(candidate)); VideoManager.sendMessage({ id: 'onIceCandidate' , candidate: candidate , uid: sd.uid , luid: sd.self ? sd.uid : opts.uid }); }; self.reattachStream = _reattachStream; self.video = function() { return video; }; self.handleMicStatus = _handleMicStatus; return self; }); /* Licensed under the Apache License, Version 2.0 (the "License") http://www.apache.org/licenses/LICENSE-2.0 */ var VideoManager = (function() { const self = {}; let share, inited = false; function _onVideoResponse(m) { const w = $('#' + VideoUtil.getVid(m.uid)) , v = w.data() , peer = v.getPeer(); if (peer) { peer.processAnswer(m.sdpAnswer, function (error) { if (error) { if (true === this.cleaned) { return; } return OmUtil.error(error); } const vidEls = w.find('audio, video') , vidEl = vidEls.length === 1 ? vidEls[0] : null; if (vidEl && vidEl.paused) { vidEl.play().catch(function (err) { if ('NotAllowedError' === err.name) { VideoUtil.askPermission(function () { vidEl.play(); }); } }); } }); } } function _onBroadcast(msg) { const sd = msg.stream , uid = sd.uid; if (Array.isArray(msg.cleanup)) { msg.cleanup.forEach(function(_cuid) { _close(_cuid); }); } $('#' + VideoUtil.getVid(uid)).remove(); Video().init(msg); OmUtil.log(uid + ' registered in room'); } function _onShareUpdated(msg) { const sd = msg.stream , uid = sd.uid , w = $('#' + VideoUtil.getVid(uid)) , v = w.data(); if (!VideoUtil.isSharing(sd) && !VideoUtil.isRecording(sd)) { VideoManager.close(uid, false); } else { v.stream().activities = sd.activities; } Sharer.setShareState(VideoUtil.isSharing(sd) ? SHARE_STARTED : SHARE_STOPED); Sharer.setRecState(VideoUtil.isRecording(sd) ? SHARE_STARTED : SHARE_STOPED); } function _onReceive(msg) { const uid = msg.stream.uid; $('#' + VideoUtil.getVid(uid)).remove(); Video().init(msg); OmUtil.log(uid + ' receiving video'); } function _onKMessage(m) { switch (m.id) { case 'clientLeave': $(VID_SEL + '[data-client-uid="' + m.uid + '"]').each(function() { _closeV($(this)); }); if (share.data('cuid') === m.uid) { share.off().hide(); } break; case 'broadcastStopped': _close(m.uid, false); break; case 'broadcast': _onBroadcast(m); break; case 'videoResponse': _onVideoResponse(m); break; case 'iceCandidate': { const w = $('#' + VideoUtil.getVid(m.uid)) , v = w.data() , peer = v.getPeer(); if (peer) { peer.addIceCandidate(m.candidate, function (error) { if (error) { if (true === this.cleaned) { return; } OmUtil.error('Error adding candidate: ' + error); return; } }); } } break; case 'newStream': _play([m.stream], m.iceServers); break; case 'shareUpdated': _onShareUpdated(m); break; case 'error': OmUtil.error(m.message); break; default: //no-op } } function _onWsMessage(jqEvent, msg) { try { if (msg instanceof Blob) { return; //ping } const m = JSON.parse(msg); if (!m) { return; } if ('kurento' === m.type && 'test' !== m.mode) { OmUtil.info('Received message: ' + msg); _onKMessage(m); } else if ('mic' === m.type) { switch (m.id) { case 'activity': _userSpeaks(m.uid, m.active); break; default: //no-op } } } catch (err) { OmUtil.error(err); } } function _init() { Wicket.Event.subscribe('/websocket/message', _onWsMessage); VideoSettings.init(Room.getOptions()); share = $('.room-block .room-container').find('.btn.shared'); inited = true; } function _update(c) { if (!inited) { return; } const streamMap = {}; c.streams.forEach(function(sd) { streamMap[sd.uid] = sd.uid; sd.self = c.self; if (VideoUtil.isSharing(sd) || VideoUtil.isRecording(sd)) { return; } const _id = VideoUtil.getVid(sd.uid) , av = VideoUtil.hasMic(sd) || VideoUtil.hasCam(sd) , v = $('#' + _id); if (av && v.length === 1) { v.data().update(sd); } else if (!av && v.length === 1) { _closeV(v); } }); if (c.uid === Room.getOptions().uid) { $(VID_SEL).each(function() { $(this).data().setRights(c.rights); }); } $('[data-client-uid="' + c.cuid + '"]').each(function() { const sd = $(this).data().stream(); if (!streamMap[sd.uid]) { //not-inited/invalid video window _closeV($(this)); } }); } function _closeV(v) { if (v.dialog('instance') !== undefined) { v.dialog('destroy'); } v.parents('.pod').remove(); v.remove(); WbArea.updateAreaClass(); } function _playSharing(sd, iceServers) { const m = {stream: sd, iceServers: iceServers}; let v = $('#' + VideoUtil.getVid(sd.uid)) if (v.length === 1) { v.remove(); } v = Video().init(m); VideoUtil.setPos(v, {left: 0, top: 35}); } function _play(streams, iceServers) { if (!inited) { return; } streams.forEach(function(sd) { const m = {stream: sd, iceServers: iceServers}; if (VideoUtil.isSharing(sd)) { VideoUtil.highlight(share .attr('title', share.data('user') + ' ' + sd.user.firstName + ' ' + sd.user.lastName + ' ' + share.data('text')) .data('uid', sd.uid) .data('cuid', sd.cuid) .show() , 'btn-outline-warning', 10); share.tooltip().off().click(function() { _playSharing(sd, iceServers); }); if (Room.getOptions().autoOpenSharing === true) { _playSharing(sd, iceServers); } } else if (VideoUtil.isRecording(sd)) { return; } else { _onReceive(m); } }); } function _close(uid, showShareBtn) { const v = $('#' + VideoUtil.getVid(uid)); if (v.length === 1) { _closeV(v); } if (!showShareBtn && uid === share.data('uid')) { share.off().hide(); } } function _find(uid) { return $(VID_SEL + '[data-client-uid="' + uid + '"][data-client-type="WEBCAM"]'); } function _userSpeaks(uid, active) { const u = $('#user' + uid + ' .audio-activity') , v = _find(uid).parent(); if (active) { u.addClass('speaking'); v.addClass('user-speaks') } else { u.removeClass('speaking'); v.removeClass('user-speaks') } } function _refresh(uid, opts) { const v = _find(uid); if (v.length > 0) { v.data().refresh(opts); } } function _mute(uid, mute) { const v = _find(uid); if (v.length > 0) { v.data().mute(mute); } } function _clickMuteOthers(uid) { const s = VideoSettings.load(); if (false !== s.video.confirmMuteOthers) { const dlg = $('#muteothers-confirm'); dlg.dialog({ buttons: [ { text: dlg.data('btn-ok') , click: function() { s.video.confirmMuteOthers = !$('#muteothers-confirm-dont-show').prop('checked'); VideoSettings.save(); OmUtil.roomAction({action: 'muteOthers', uid: uid}); $(this).dialog('close'); } } , { text: dlg.data('btn-cancel') , click: function() { $(this).dialog('close'); } } ] }) } } function _muteOthers(uid) { $(VID_SEL).each(function() { const w= $(this); w.data().mute('room' + uid !== w.data('client-uid')); }); } function _toggleActivity(activity) { self.sendMessage({ id: 'toggleActivity' , activity: activity }); } self.init = _init; self.update = _update; self.play = _play; self.close = _close; self.refresh = _refresh; self.mute = _mute; self.clickMuteOthers = _clickMuteOthers; self.muteOthers = _muteOthers; self.toggleActivity = _toggleActivity; self.sendMessage = function(_m) { OmUtil.sendMessage(_m, {type: 'kurento'}); } self.destroy = function() { Wicket.Event.unsubscribe('/websocket/message', _onWsMessage); } return self; })(); /* Licensed under the Apache License, Version 2.0 (the "License") http://www.apache.org/licenses/LICENSE-2.0 */ var SHARE_STARTING = 'starting'; var SHARE_STARTED = 'started'; var SHARE_STOPED = 'stoped'; var Sharer = (function() { const self = {}; let sharer, type, fps, sbtn, rbtn, width, height , shareState = SHARE_STOPED, recState = SHARE_STOPED; function _init() { sharer = $('#sharer').dialog({ classes: { 'ui-dialog': 'sharer' , 'ui-dialog-titlebar': '' } , width: 450 , autoOpen: false , resizable: false }); if (!VideoUtil.sharingSupported()) { sharer.find('.container').remove(); sharer.find('.alert').show(); } else { type = sharer.find('select.type'); const b = kurentoUtils.WebRtcPeer.browser; fps = sharer.find('select.fps'); _disable(fps, VideoUtil.isEdge(b)); sbtn = sharer.find('.share-start-stop').off().click(function() { if (shareState === SHARE_STOPED) { _setShareState(SHARE_STARTING); VideoManager.sendMessage({ id: 'wannaShare' , shareType: type.val() , fps: fps.val() , width: width.val() , height: height.val() }); } else { VideoManager.sendMessage({ id: 'pauseSharing' , uid: _getShareUid() }); } }); width = sharer.find('.width'); height = sharer.find('.height'); rbtn = sharer.find('.record-start-stop').off(); if (Room.getOptions().allowRecording) { rbtn.show().click(function() { if (recState === SHARE_STOPED) { _setRecState(SHARE_STARTING); VideoManager.sendMessage({ id: 'wannaRecord' , shareType: type.val() , fps: fps.val() , width: width.val() , height: height.val() }); } else { VideoManager.sendMessage({ id: 'stopRecord' , uid: _getShareUid() }); } }); } else { rbtn.hide(); } } } function _disable(e, state) { e.prop('disabled', state); if (state) { e.addClass('disabled'); } else { e.removeClass('disabled'); } } function _typeDisabled(_b) { const b = _b || kurentoUtils.WebRtcPeer.browser; return VideoUtil.isEdge(b) || VideoUtil.isChrome(b); } function _setBtnState(btn, state) { const dis = SHARE_STOPED !== state , typeDis = _typeDisabled(); _disable(type, dis); _disable(fps, dis || typeDis); _disable(width, dis); _disable(height, dis); btn.find('span').text(btn.data(dis ? 'stop' : 'start')); if (dis) { btn.addClass('stop'); } else { btn.removeClass('stop'); } _disable(btn, state === SHARE_STARTING); _disable(btn, state === SHARE_STARTING); } function _setShareState(state) { shareState = state; _setBtnState(sbtn, state); } function _setRecState(state) { recState = state; _setBtnState(rbtn, state); } function _getShareUid() { const v = $('div[data-client-uid="' + Room.getOptions().uid + '"][data-client-type="SCREEN"]'); return v && v.data() && v.data().stream() ? v.data().stream().uid : ''; } self.init = _init; self.open = function() { if (sharer && sharer.dialog('instance')) { sharer.dialog('open'); } }; self.close = function() { if (sharer && sharer.dialog('instance')) { sharer.dialog('close'); } }; self.setShareState = _setShareState; self.setRecState = _setRecState; self.baseConstraints = function(sd) { return { video: { frameRate: { ideal: sd.fps } } , audio: false }; }; return self; })(); /* Licensed under the Apache License, Version 2.0 (the "License") http://www.apache.org/licenses/LICENSE-2.0 */ var Room = (function() { const self = {}, sbSide = Settings.isRtl ? 'right' : 'left'; let options, menuHeight, sb, dock, activities; function _init(_options) { options = _options; window.WbArea = options.interview ? InterviewWbArea() : DrawWbArea(); const menu = $('.room-block .room-container .menu'); activities = $('#activities'); sb = $('.room-block .sidebar'); sb.width(sb.width()); // this is required to 'fix' the width so it will not depend on CSS var dock = sb.find('.btn-dock').click(function() { const offset = parseInt(sb.css(sbSide)); if (offset < 0) { sb.removeClass('closed'); } dock.prop('disabled', true); const props = {}; props[sbSide] = offset < 0 ? '0px' : (-sb.width() + 45) + 'px'; sb.animate(props, 1500 , function() { dock.prop('disabled', false); __dockSetMode(offset < 0); _setSize(); }); }); __dockSetMode(true); const header = $('#room-sidebar-tab-users .header'); header.find('.om-icon.settings').off().click(VideoSettings.open); header.find('.om-icon.activity.cam').off().click(function() { VideoManager.toggleActivity('VIDEO'); }); header.find('.om-icon.activity.mic').off().click(function() { VideoManager.toggleActivity('AUDIO'); }); menuHeight = menu.length === 0 ? 0 : menu.height(); VideoManager.init(); if (typeof(Activities) !== 'undefined') { Activities.init(); } Sharer.init(); _setSize(); } function __dockSetMode(mode) { const icon = dock.find('i').removeClass('icon-dock icon-undock'); if (mode) { icon.addClass('icon-undock'); dock.attr('title', dock.data('ttl-undock')) .find('.sr-only').text(dock.data('ttl-undock')); _sbAddResizable(); } else { icon.addClass('icon-dock'); dock.attr('title', dock.data('ttl-dock')) .find('.sr-only').text(dock.data('ttl-dock')); sb.addClass('closed').resizable('destroy'); } } function _getSelfAudioClient() { const vw = $('.video-container[data-client-type=WEBCAM][data-client-uid=' + Room.getOptions().uid + ']'); if (vw.length > 0) { const v = vw.first().data(); if (VideoUtil.hasMic(v.stream())) { return v; } } return null; } function _preventKeydown(e) { const base = $(e.target); if (e.target.isContentEditable === true || base.is('textarea, input:not([readonly]):not([type=radio]):not([type=checkbox])')) { return; } if (e.which === 8) { // backspace e.preventDefault(); e.stopImmediatePropagation(); return false; } } function __keyPressed(hotkey, e) { const code = OmUtil.getKeyCode(e); return hotkey.alt === e.altKey && hotkey.ctrl === e.ctrlKey && hotkey.shift === e.shiftKey && hotkey.code.toUpperCase() === (code ? code.toUpperCase() : ''); } function _keyHandler(e) { if (__keyPressed(options.keycode.arrange, e)) { VideoUtil.arrange(); } else if (__keyPressed(options.keycode.arrangeresize, e)) { VideoUtil.arrangeResize(); } else if (__keyPressed(options.keycode.muteothers, e)) { const v = _getSelfAudioClient(); if (v !== null) { VideoManager.clickMuteOthers(Room.getOptions().uid); } } else if (__keyPressed(options.keycode.mute, e)) { const v = _getSelfAudioClient(); if (v !== null) { v.mute(!v.isMuted()); } } else if (__keyPressed(options.keycode.quickpoll, e)) { quickPollAction('open'); } if (e.which === 27) { $('#wb-rename-menu').hide(); } } function _mouseHandler(e) { if (e.which === 1) { $('#wb-rename-menu').hide(); } } function _sbWidth() { if (sb === undefined) { sb = $('.room-block .sidebar'); } return sb === undefined ? 0 : sb.width() + parseInt(sb.css(sbSide)); } function _setSize() { const sbW = _sbWidth() , holder = $('.room-block'); ($('.main.room')[0]).style.setProperty('--room-sidebar-width', sbW + 'px'); if (sbW > 285) { holder.addClass('big').removeClass('narrow'); } else { holder.removeClass('big').addClass('narrow'); } } function _reload() { if (!!options && !!options.reloadUrl) { window.location.href = options.reloadUrl; } else { window.location.reload(); } } function _close() { _unload(); $(".room-block").remove(); $("#chatPanel").remove(); $('#disconnected-dlg') .modal('show') .off('hide.bs.modal') .on('hide.bs.modal', _reload); } function _sbAddResizable() { sb.resizable({ handles: Settings.isRtl ? 'w' : 'e' , stop: function() { _setSize(); } }); } function _load() { $('body').addClass('no-header'); Wicket.Event.subscribe("/websocket/closed", _close); Wicket.Event.subscribe("/websocket/error", _close); $(window).on('keydown.openmeetings', _preventKeydown); $(window).on('keyup.openmeetings', _keyHandler); $(document).click(_mouseHandler); } function _unload() { $('body').removeClass('no-header'); Wicket.Event.unsubscribe("/websocket/closed", _close); Wicket.Event.unsubscribe("/websocket/error", _close); if (typeof(WbArea) === 'object') { WbArea.destroy(); window.WbArea = undefined; } if (typeof(VideoSettings) === 'object') { VideoSettings.close(); } if (typeof(VideoManager) === 'object') { VideoManager.destroy(); } $('.ui-dialog.user-video').remove(); $(window).off('keyup.openmeetings'); $(window).off('keydown.openmeetings'); $(document).off('click', _mouseHandler); sb = undefined; Sharer.close(); } function _showClipboard(txt) { const dlg = $('#clipboard-dialog'); dlg.find('p .text').text(txt); dlg.dialog({ resizable: false , height: "auto" , width: 400 , modal: true , buttons: [ { text: dlg.data('btn-ok') , click: function() { $(this).dialog('close'); } } ] }); } function _hasRight(_inRights, _ref) { const ref = _ref || options.rights; let _rights; if (Array.isArray(_inRights)) { _rights = _inRights; } else { if ('SUPER_MODERATOR' === _inRights) { return ref.includes(_inRights); } _rights = [_inRights]; } const rights = ['SUPER_MODERATOR', 'MODERATOR', ..._rights]; for (let i = 0; i < rights.length; ++i) { if (ref.includes(rights[i])) { return true; } } return false; } function _setQuickPollRights() { const close = $('#quick-vote .close-btn'); if (close.length === 1) { if (_hasRight(['PRESENTER'])) { close.show(); if (typeof(close.data('bs.confirmation')) === 'object') { return; } close .confirmation({ confirmationEvent: 'bla' , onConfirm: function() { quickPollAction('close'); } }); } else { close.hide(); } } } function _quickPoll(obj) { if (obj.started) { let qv = $('#quick-vote'); if (qv.length === 0) { const wbArea = $('.room-block .wb-block'); qv = OmUtil.tmpl('#quick-vote-template', 'quick-vote'); wbArea.append(qv); } const pro = qv.find('.control.pro') , con = qv.find('.control.con'); if (obj.voted) { pro.removeClass('clickable').off(); con.removeClass('clickable').off(); } else { pro.addClass('clickable').off().click(function() { quickPollAction('vote', true); }); con.addClass('clickable').off().click(function() { quickPollAction('vote', false); }); } pro.find('.badge').text(obj.pros); con.find('.badge').text(obj.cons); _setQuickPollRights(); } else { const qv = $('#quick-vote'); if (qv.length === 1) { qv.remove(); } } OmUtil.tmpl('#quick-vote-template', 'quick-vote'); } function __activityAVIcon(elem, selector, predicate, onfunc, disabledfunc) { let icon = elem.find(selector); if (predicate()) { icon.show(); const on = onfunc() , disabled = disabledfunc();; if (disabled) { icon.addClass('disabled'); } else { icon.removeClass('disabled'); if (on) { icon.addClass('enabled'); } else { icon.removeClass('enabled'); } } icon.attr('title', icon.data(on ? 'on' :'off')); } else { icon.hide(); } } function __activityIcon(elem, selector, predicate, action) { let icon = elem.find(selector); if (predicate()) { if (icon.length === 0) { icon = OmUtil.tmpl('#user-actions-stub ' + selector); elem.append(icon); } icon.off().click(action); } else { icon.hide(); } } function __rightIcon(c, elem, rights, selector, predicate) { const self = c.uid === options.uid , hasRight = _hasRight(rights, c.rights); let icon = elem.find(selector); if (predicate() && !_hasRight('SUPER_MODERATOR', c.rights) && ( (self && options.questions && !hasRight) || (!self && _hasRight('MODERATOR')) )) { if (icon.length === 0) { icon = OmUtil.tmpl('#user-actions-stub ' + selector); elem.append(icon); } if (hasRight) { icon.addClass('granted'); } else { icon.removeClass('granted') } icon.attr('title', icon.data(self ? 'request' : (hasRight ? 'revoke' : 'grant'))); icon.off().click(function() { OmUtil.roomAction({action: 'toggleRight', right: rights[0], uid: c.uid}); }); } else { icon.remove(); } } function __rightAudioIcon(c, elem) { __rightIcon(c, elem, ['AUDIO'], '.right.audio', () => true); } function __rightVideoIcon(c, elem) { __rightIcon(c, elem, ['VIDEO'], '.right.camera', () => !options.audioOnly); } function __rightOtherIcons(c, elem) { __rightIcon(c, elem, ['PRESENTER'], '.right.presenter', () => !options.interview && $('.wb-area').is(':visible')); __rightIcon(c, elem, ['WHITEBOARD', 'PRESENTER'], '.right.wb', () => !options.interview && $('.wb-area').is(':visible')); __rightIcon(c, elem, ['SHARE'], '.right.screen-share', () => true); //FIXME TODO getRoomPanel().screenShareAllowed() __rightIcon(c, elem, ['REMOTE_CONTROL'], '.right.remote-control', () => true); //FIXME TODO getRoomPanel().screenShareAllowed() __rightIcon(c, elem, ['MODERATOR'], '.right.moderator', () => true); } function __setStatus(c, le) { const status = le.find('.user-status') , mode = c.level == 5 ? 'mod' : (c.level == 3 ? 'wb' : 'user'); status.removeClass('mod wb user'); status.attr('title', status.data(mode)).addClass(mode); le.data('level', c.level); } function __updateCount() { $('#room-sidebar-users-tab .user-count').text($('#room-sidebar-tab-users .user-list .users .user.entry').length); } function __sortUserList() { const container = $('#room-sidebar-tab-users .user-list .users'); container.find('.user.entry').sort((_u1, _u2) => { const u1 = $(_u1) , u2 = $(_u2); return u2.data('level') - u1.data('level') || u1.attr('title').localeCompare(u2.attr('title')); }).appendTo(container); } function _addClient(_clients) { const clients = Array.isArray(_clients) ? _clients : [_clients]; clients.forEach(c => { const self = c.uid === options.uid; let le = Room.getClient(c.uid); if (le.length === 0) { le = OmUtil.tmpl('#user-entry-stub', 'user' + c.uid); le.attr('id', 'user' + c.uid) .attr('data-userid', c.user.id) .attr('data-uid', c.uid); if (self) { le.addClass('current'); } $('#room-sidebar-tab-users .user-list .users').append(le); } _updateClient(c); }); __updateCount(); __sortUserList(); } function _updateClient(c) { const self = c.uid === options.uid , le = Room.getClient(c.uid) , hasAudio = VideoUtil.hasMic(c) , hasVideo = VideoUtil.hasCam(c) , speaks = le.find('.audio-activity'); if (le.length === 0) { return; } __setStatus(c, le); if (hasVideo || hasAudio) { if (le.find('.restart').length === 0) { le.prepend(OmUtil.tmpl('#user-av-restart').click(function () { VideoManager.refresh(c.uid); })); } } else { le.find('.restart').remove(); } speaks.hide().removeClass('clickable').attr('title', speaks.data('speaks')).off(); if (hasAudio) { speaks.show(); if(_hasRight('MUTE_OTHERS')) { speaks.addClass('clickable').click(function () { VideoManager.clickMuteOthers(c.uid); }).attr('title', speaks.attr('title') + speaks.data('mute')); } } le.attr('title', c.user.displayName) .css('background-image', 'url(' + c.user.pictureUri + ')') .find('.user.name').text(c.user.displayName); const actions = le.find('.user.actions'); __rightVideoIcon(c, actions); __rightAudioIcon(c, actions); __rightOtherIcons(c, actions); __activityIcon(actions, '.kick' , () => !self && _hasRight('MODERATOR') && !_hasRight('SUPER_MODERATOR', c.rights) , function() { OmUtil.roomAction({action: 'kick', uid: c.uid}); }); __activityIcon(actions, '.private-chat' , () => options.userId !== c.user.id && $('#chatPanel').is(':visible') , function() { Chat.addTab('chatTab-u' + c.user.id, c.user.displayName); Chat.open(); $('#chatMessage .wysiwyg-editor').click(); }); if (self) { options.rights = c.rights; _setQuickPollRights(); options.activities = c.activities; const header = $('#room-sidebar-tab-users .header'); __rightVideoIcon(c, header); __activityAVIcon(header, '.activity.cam', () => !options.audioOnly && _hasRight('VIDEO') , () => hasVideo , () => Settings.load().video.cam < 0); __rightAudioIcon(c, header); __activityAVIcon(header, '.activity.mic', () => _hasRight('AUDIO') , () => hasAudio , () => Settings.load().video.mic < 0); __rightOtherIcons(c, header); } VideoManager.update(c) } self.init = _init; self.getMenuHeight = function() { return menuHeight; }; self.getOptions = function() { return typeof(options) === 'object' ? JSON.parse(JSON.stringify(options)) : {}; }; self.load = _load; self.unload = _unload; self.showClipboard = _showClipboard; self.quickPoll = _quickPoll; self.hasRight = _hasRight; self.setCssVar = function(key, val) { ($('.main.room')[0]).style.setProperty(key, val); }; self.addClient = _addClient; self.updateClient = function(c) { _updateClient(c); __sortUserList(); }; self.removeClient = function(uid) { Room.getClient(uid).remove(); __updateCount(); }; self.removeOthers = function() { const selfUid = Room.getOptions().uid; $('.user-list .user.entry').each(function() { const c = $(this); if (c.data('uid') !== selfUid) { c.remove(); } }); __updateCount(); }; self.getClient = function(uid) { return $('#user' + uid); }; return self; })(); /***** functions required by SIP ******/ function sipBtnClick() { const txt = $('.sip-number'); txt.val(txt.val() + $(this).data('value')); } function sipBtnEraseClick() { const txt = $('.sip-number') , t = txt.val(); if (!!t) { txt.val(t.substring(0, t.length - 1)); } } function sipGetKey(evt) { let k = -1; if (evt.keyCode > 47 && evt.keyCode < 58) { k = evt.keyCode - 48; } if (evt.keyCode > 95 && evt.keyCode < 106) { k = evt.keyCode - 96; } return k; } function sipKeyDown(evt) { const k = sipGetKey(evt); if (k > 0) { $('#sip-dialer-btn-' + k).addClass('ui-state-active'); } } function sipKeyUp(evt) { const k = sipGetKey(evt); if (k > 0) { $('#sip-dialer-btn-' + k).removeClass('ui-state-active'); } } function typingActivity(uid, active) { const u = Room.getClient(uid).find('.typing-activity'); if (active) { u.addClass("typing"); } else { u.removeClass("typing"); } } $(function() { $('.sip').on('keydown', sipKeyDown).on('keyup', sipKeyUp); $('.sip .button-row button').button().click(sipBtnClick); $('#sip-dialer-btn-erase').button().click(sipBtnEraseClick); });