From 6ded050b516d91bb00a536d4540fabf6adf0151f Mon Sep 17 00:00:00 2001 From: isymchych Date: Mon, 14 Dec 2015 14:26:50 +0200 Subject: [PATCH] do not use RTC/xmpp in UI module --- app.js | 78 +- lib-jitsi-meet.js | 1529 ++++++++++------- modules/UI/UI.js | 44 +- modules/UI/audio_levels/AudioLevels.js | 3 +- modules/UI/prezi/Prezi.js | 7 +- modules/UI/util/UIUtil.js | 8 +- modules/UI/videolayout/ConnectionIndicator.js | 16 +- modules/UI/videolayout/LargeVideo.js | 155 +- modules/UI/videolayout/LocalVideo.js | 54 +- modules/UI/videolayout/RemoteVideo.js | 115 +- modules/UI/videolayout/SmallVideo.js | 49 +- modules/UI/videolayout/VideoLayout.js | 528 +++--- service/UI/UIEvents.js | 1 + 13 files changed, 1467 insertions(+), 1120 deletions(-) diff --git a/app.js b/app.js index c4961b4b8..161f92b59 100644 --- a/app.js +++ b/app.js @@ -13,6 +13,7 @@ import "jQuery-Impromptu"; import "autosize"; window.toastr = require("toastr"); +import URLProcessor from "./modules/config/URLProcessor"; import RoomnameGenerator from './modules/util/RoomnameGenerator'; import CQEvents from './service/connectionquality/CQEvents'; import UIEvents from './service/UI/UIEvents'; @@ -101,23 +102,23 @@ const APP = { }; -var ConnectionEvents = JitsiMeetJS.events.connection; -var ConnectionErrors = JitsiMeetJS.errors.connection; +const ConnectionEvents = JitsiMeetJS.events.connection; +const ConnectionErrors = JitsiMeetJS.errors.connection; function connect() { - var connection = new JitsiMeetJS.JitsiConnection(null, null, { + let connection = new JitsiMeetJS.JitsiConnection(null, null, { hosts: config.hosts, bosh: config.bosh, clientNode: config.clientNode }); return new Promise(function (resolve, reject) { - var handlers = {}; + let handlers = {}; - var unsubscribe = function () { + function unsubscribe () { Object.keys(handlers).forEach(function (event) { connection.removeEventListener(event, handlers[event]); }); - }; + } handlers[ConnectionEvents.CONNECTION_ESTABLISHED] = function () { console.log('CONNECTED'); @@ -125,14 +126,14 @@ function connect() { resolve(connection); }; - var listenForFailure = function (event) { + function listenForFailure (event) { handlers[event] = function (...args) { console.error(`CONNECTION FAILED: ${event}`, ...args); unsubscribe(); reject([event, ...args]); }; - }; + } listenForFailure(ConnectionEvents.CONNECTION_FAILED); listenForFailure(ConnectionErrors.PASSWORD_REQUIRED); @@ -172,6 +173,13 @@ function initConference(localTracks, connection) { } }); + APP.conference.listMembers = function () { + return room.getParticipants(); + }; + APP.conference.listMembersIds = function () { + return room.getParticipants().map(p => p.getId()); + }; + function getDisplayName(id) { if (APP.conference.isLocalId(id)) { return APP.settings.getDisplayName(); @@ -187,17 +195,22 @@ function initConference(localTracks, connection) { room.on(ConferenceEvents.CONFERENCE_JOINED, function () { localTracks.forEach(function (track) { room.addTrack(track); - //APP.UI.addLocalStream(track); + APP.UI.addLocalStream(track); }); }); - room.on(ConferenceEvents.USER_JOINED, function (id) { + room.on(ConferenceEvents.USER_JOINED, function (id, user) { + if (APP.conference.isLocalId(id)) { + return; + } + console.error('USER %s connnected', id); // FIXME email??? - //APP.UI.addUser(id); + APP.UI.addUser(id, user.getDisplayName()); }); - room.on(ConferenceEvents.USER_LEFT, function (id) { - APP.UI.removeUser(id); + room.on(ConferenceEvents.USER_LEFT, function (id, user) { + console.error('USER LEFT', id); + APP.UI.removeUser(id, user.getDisplayName()); }); @@ -230,6 +243,26 @@ function initConference(localTracks, connection) { }); + room.on(ConferenceEvents.TRACK_ADDED, function (track) { + if (!track.getParticipantId) { // skip local tracks + return; + } + console.error( + 'REMOTE %s TRACK', track.getType(), track.getParticipantId() + ); + APP.UI.addRemoteStream(track); + }); + room.on(ConferenceEvents.TRACK_REMOVED, function (track) { + if (!track.getParticipantId) { // skip local tracks + return; + } + + console.error( + 'REMOTE %s TRACK REMOVED', track.getType(), track.getParticipantId() + ); + + // FIXME handle + }); room.on(ConferenceEvents.TRACK_MUTE_CHANGED, function (track) { // FIXME handle mute }); @@ -421,10 +454,23 @@ function initConference(localTracks, connection) { // on SUBJECT_CHANGED UI.setSubject(topic); }); + APP.UI.addListener(UIEvents.USER_KICKED, function (id) { + // FIXME handle + // APP.xmpp.eject(self.id); + }); + + APP.UI.addListener(UIEvents.SELECTED_ENDPOINT, function (id) { + room.selectParticipant(id); + }); + room.on(ConferenceEvents.DTMF_SUPPORT_CHANGED, function (isDTMFSupported) { APP.UI.updateDTMFSupport(isDTMFSupported); }); + $(window).bind('beforeunload', function () { + room.leave(); + }); + return new Promise(function (resolve, reject) { room.on(ConferenceEvents.CONFERENCE_JOINED, resolve); @@ -456,6 +502,7 @@ function createLocalTracks () { } function init() { + APP.UI.start(); JitsiMeetJS.setLogLevel(JitsiMeetJS.logLevels.TRACE); JitsiMeetJS.init().then(function () { return Promise.all([createLocalTracks(), connect()]); @@ -463,8 +510,6 @@ function init() { console.log('initialized with %s local tracks', tracks.length); return initConference(tracks, connection); }).then(function () { - APP.UI.start(); - APP.UI.initConference(); APP.UI.addListener(UIEvents.LANG_CHANGED, function (language) { @@ -518,7 +563,6 @@ function obtainConfigAndInit() { $(document).ready(function () { console.log("(TIME) document ready:\t", window.performance.now()); - var URLProcessor = require("./modules/config/URLProcessor"); URLProcessor.setConfigParametersFromUrl(); APP.init(); @@ -537,4 +581,4 @@ $(window).bind('beforeunload', function () { } }); -export default APP; +module.exports = APP; diff --git a/lib-jitsi-meet.js b/lib-jitsi-meet.js index e2b9eb854..19badfd09 100644 --- a/lib-jitsi-meet.js +++ b/lib-jitsi-meet.js @@ -8,6 +8,7 @@ var XMPPEvents = require("./service/xmpp/XMPPEvents"); var RTCEvents = require("./service/RTC/RTCEvents"); var EventEmitter = require("events"); var JitsiConferenceEvents = require("./JitsiConferenceEvents"); +var JitsiConferenceErrors = require("./JitsiConferenceErrors"); var JitsiParticipant = require("./JitsiParticipant"); var Statistics = require("./modules/statistics/statistics"); var JitsiDTMFManager = require('./modules/DTMF/JitsiDTMFManager'); @@ -246,6 +247,36 @@ JitsiConference.prototype.isModerator = function () { return this.room.isModerator(); }; +/** + * Set password for the room. + * @param {string} password new password for the room. + * @returns {Promise} + */ +JitsiConference.prototype.lock = function (password) { + if (!this.isModerator()) { + return Promise.reject(); + } + + var conference = this; + return new Promise(function (resolve, reject) { + conference.xmpp.lockRoom(password, function () { + resolve(); + }, function (err) { + reject(err); + }, function () { + reject(JitsiConferenceErrors.PASSWORD_REQUIRED); + }); + }); +}; + +/** + * Remove password from the room. + * @returns {Promise} + */ +JitsiConference.prototype.unlock = function () { + return this.lock(undefined); +}; + /** * Elects the participant with the given id to be the selected participant or the speaker. * @param id the identifier of the participant @@ -287,10 +318,13 @@ JitsiConference.prototype.getParticipantById = function(id) { JitsiConference.prototype.onMemberJoined = function (jid, email, nick) { var id = Strophe.getResourceFromJid(jid); + if (id === 'focus') { + return; + } var participant = new JitsiParticipant(id, this, nick); - this.eventEmitter.emit(JitsiConferenceEvents.USER_JOINED, id); this.participants[id] = participant; - this.connection.xmpp.connection.disco.info( + this.eventEmitter.emit(JitsiConferenceEvents.USER_JOINED, id, participant); + this.xmpp.connection.disco.info( jid, "node", function(iq) { participant._supportsDTMF = $(iq).find( '>query>feature[var="urn:xmpp:jingle:dtmf:0"]').length > 0; @@ -301,8 +335,9 @@ JitsiConference.prototype.onMemberJoined = function (jid, email, nick) { JitsiConference.prototype.onMemberLeft = function (jid) { var id = Strophe.getResourceFromJid(jid); + var participant = this.participants[id]; delete this.participants[id]; - this.eventEmitter.emit(JitsiConferenceEvents.USER_LEFT, id); + this.eventEmitter.emit(JitsiConferenceEvents.USER_LEFT, id, participant); }; JitsiConference.prototype.onUserRoleChanged = function (jid, role) { @@ -398,7 +433,7 @@ JitsiConference.prototype.myUserId = function () { JitsiConference.prototype.sendTones = function (tones, duration, pause) { if (!this.dtmfManager) { - var connection = this.connection.xmpp.connection.jingle.activecall.peerconnection; + var connection = this.xmpp.connection.jingle.activecall.peerconnection; if (!connection) { logger.warn("cannot sendTones: no conneciton"); return; @@ -482,6 +517,9 @@ function setupListeners(conference) { conference.eventEmitter.emit(JitsiConferenceEvents.LAST_N_ENDPOINTS_CHANGED, lastNEndpoints, endpointsEnteringLastN); }); + conference.xmpp.addListener(XMPPEvents.PASSWORD_REQUIRED, function () { + conference.eventEmitter.emit(JitsiConferenceErrors.PASSWORD_REQUIRED); + }); if(conference.statistics) { //FIXME: Maybe remove event should not be associated with the conference. @@ -508,7 +546,7 @@ function setupListeners(conference) { module.exports = JitsiConference; }).call(this,"/JitsiConference.js") -},{"./JitsiConferenceEvents":3,"./JitsiParticipant":8,"./JitsiTrackEvents":10,"./modules/DTMF/JitsiDTMFManager":11,"./modules/RTC/RTC":16,"./modules/statistics/statistics":24,"./service/RTC/RTCEvents":83,"./service/xmpp/XMPPEvents":89,"events":43,"jitsi-meet-logger":47}],2:[function(require,module,exports){ +},{"./JitsiConferenceErrors":2,"./JitsiConferenceEvents":3,"./JitsiParticipant":8,"./JitsiTrackEvents":10,"./modules/DTMF/JitsiDTMFManager":11,"./modules/RTC/RTC":16,"./modules/statistics/statistics":24,"./service/RTC/RTCEvents":79,"./service/xmpp/XMPPEvents":85,"events":43,"jitsi-meet-logger":47}],2:[function(require,module,exports){ /** * Enumeration with the errors for the conference. * @type {{string: string}} @@ -518,6 +556,10 @@ var JitsiConferenceErrors = { * Indicates that a password is required in order to join the conference. */ PASSWORD_REQUIRED: "conference.passwordRequired", + /** + * Indicates that password cannot be set for this conference. + */ + PASSWORD_NOT_SUPPORTED: "conference.passwordNotSupported", /** * Indicates that a connection error occurred when trying to join a * conference. @@ -1282,7 +1324,7 @@ module.exports = DataChannels; }).call(this,"/modules/RTC/DataChannels.js") -},{"../../service/RTC/RTCEvents":83,"jitsi-meet-logger":47}],13:[function(require,module,exports){ +},{"../../service/RTC/RTCEvents":79,"jitsi-meet-logger":47}],13:[function(require,module,exports){ var JitsiTrack = require("./JitsiTrack"); var RTCBrowserType = require("./RTCBrowserType"); var JitsiTrackEvents = require('../../JitsiTrackEvents'); @@ -1342,7 +1384,7 @@ JitsiLocalTrack.prototype._setMute = function (mute) { } else { if (mute) { this.dontFireRemoveEvent = true; - this.rtc.room.removeStream(this.stream); + this.rtc.room.removeStream(this.stream, function () {}); RTC.stopMediaStream(this.stream); if(isAudio) this.rtc.room.setAudioMute(mute); @@ -1982,7 +2024,7 @@ RTC.prototype.setAudioLevel = function (jid, audioLevel) { } module.exports = RTC; -},{"../../service/RTC/MediaStreamTypes":82,"../../service/RTC/RTCEvents.js":83,"../../service/desktopsharing/DesktopSharingEventTypes":86,"./DataChannels":12,"./JitsiLocalTrack.js":13,"./JitsiRemoteTrack.js":14,"./JitsiTrack":15,"./RTCBrowserType":17,"./RTCUtils.js":18,"events":43}],17:[function(require,module,exports){ +},{"../../service/RTC/MediaStreamTypes":78,"../../service/RTC/RTCEvents.js":79,"../../service/desktopsharing/DesktopSharingEventTypes":82,"./DataChannels":12,"./JitsiLocalTrack.js":13,"./JitsiRemoteTrack.js":14,"./JitsiTrack":15,"./RTCBrowserType":17,"./RTCUtils.js":18,"events":43}],17:[function(require,module,exports){ var currentBrowser; @@ -2918,7 +2960,7 @@ var RTCUtils = { module.exports = RTCUtils; }).call(this,"/modules/RTC/RTCUtils.js") -},{"../../JitsiTrackErrors":9,"../../service/RTC/RTCEvents":83,"../../service/RTC/Resolutions":84,"../xmpp/SDPUtil":32,"./RTCBrowserType":17,"./ScreenObtainer":19,"./adapter.screenshare":20,"events":43,"jitsi-meet-logger":47}],19:[function(require,module,exports){ +},{"../../JitsiTrackErrors":9,"../../service/RTC/RTCEvents":79,"../../service/RTC/Resolutions":80,"../xmpp/SDPUtil":32,"./RTCBrowserType":17,"./ScreenObtainer":19,"./adapter.screenshare":20,"events":43,"jitsi-meet-logger":47}],19:[function(require,module,exports){ (function (__filename){ /* global chrome, $, alert */ /* jshint -W003 */ @@ -3340,7 +3382,7 @@ function initFirefoxExtensionDetection(options) { module.exports = ScreenObtainer; }).call(this,"/modules/RTC/ScreenObtainer.js") -},{"../../service/desktopsharing/DesktopSharingEventTypes":86,"./RTCBrowserType":17,"./adapter.screenshare":20,"jitsi-meet-logger":47}],20:[function(require,module,exports){ +},{"../../service/desktopsharing/DesktopSharingEventTypes":82,"./RTCBrowserType":17,"./adapter.screenshare":20,"jitsi-meet-logger":47}],20:[function(require,module,exports){ (function (__filename){ /*! adapterjs - v0.12.0 - 2015-09-04 */ var console = require("jitsi-meet-logger").getLogger(__filename); @@ -5451,7 +5493,7 @@ StatsCollector.prototype.processAudioLevelReport = function () { }; }).call(this,"/modules/statistics/RTPStatsCollector.js") -},{"../../service/statistics/Events":87,"../RTC/RTCBrowserType":17,"jitsi-meet-logger":47}],24:[function(require,module,exports){ +},{"../../service/statistics/Events":83,"../RTC/RTCBrowserType":17,"jitsi-meet-logger":47}],24:[function(require,module,exports){ /* global require, APP */ var LocalStats = require("./LocalStatsCollector.js"); var RTPStats = require("./RTPStatsCollector.js"); @@ -5626,7 +5668,7 @@ Statistics.LOCAL_JID = require("../../service/statistics/constants").LOCAL_JID; module.exports = Statistics; -},{"../../service/statistics/Events":87,"../../service/statistics/constants":88,"./LocalStatsCollector.js":22,"./RTPStatsCollector.js":23,"events":43}],25:[function(require,module,exports){ +},{"../../service/statistics/Events":83,"../../service/statistics/constants":84,"./LocalStatsCollector.js":22,"./RTPStatsCollector.js":23,"events":43}],25:[function(require,module,exports){ /** /** * @const @@ -6347,7 +6389,7 @@ ChatRoom.prototype.getJidBySSRC = function (ssrc) { module.exports = ChatRoom; }).call(this,"/modules/xmpp/ChatRoom.js") -},{"../../service/xmpp/XMPPEvents":89,"./moderator":34,"events":43,"jitsi-meet-logger":47}],27:[function(require,module,exports){ +},{"../../service/xmpp/XMPPEvents":85,"./moderator":34,"events":43,"jitsi-meet-logger":47}],27:[function(require,module,exports){ (function (__filename){ /* * JingleSession provides an API to manage a single Jingle session. We will @@ -8116,7 +8158,7 @@ JingleSessionPC.prototype.remoteStreamAdded = function (data, times) { module.exports = JingleSessionPC; }).call(this,"/modules/xmpp/JingleSessionPC.js") -},{"../../service/xmpp/XMPPEvents":89,"../RTC/RTC":16,"../RTC/RTCBrowserType":17,"./JingleSession":27,"./LocalSSRCReplacement":29,"./SDP":30,"./SDPDiffer":31,"./SDPUtil":32,"./TraceablePeerConnection":33,"async":42,"jitsi-meet-logger":47,"sdp-transform":79}],29:[function(require,module,exports){ +},{"../../service/xmpp/XMPPEvents":85,"../RTC/RTC":16,"../RTC/RTCBrowserType":17,"./JingleSession":27,"./LocalSSRCReplacement":29,"./SDP":30,"./SDPDiffer":31,"./SDPUtil":32,"./TraceablePeerConnection":33,"async":42,"jitsi-meet-logger":47,"sdp-transform":75}],29:[function(require,module,exports){ (function (__filename){ /* global $ */ var logger = require("jitsi-meet-logger").getLogger(__filename); @@ -8171,11 +8213,11 @@ var isEnabled = !RTCBrowserType.isFirefox(); var localVideoSSRC; /** - * SSRC, msid, mslabel, label used for recvonly video stream when we have no local camera. + * SSRC used for recvonly video stream when we have no local camera. * This is in order to tell Chrome what SSRC should be used in RTCP requests * instead of 1. */ -var localRecvOnlySSRC, localRecvOnlyMSID, localRecvOnlyMSLabel, localRecvOnlyLabel; +var localRecvOnlySSRC; /** * cname for localRecvOnlySSRC @@ -8248,30 +8290,19 @@ var storeLocalVideoSSRC = function (jingleIq) { }; /** - * Generates new label/mslabel attribute - * @returns {string} label/mslabel attribute - */ -function generateLabel() { - return RandomUtil.randomHexString(8) + "-" + RandomUtil.randomHexString(4) + - "-" + RandomUtil.randomHexString(4) + "-" + - RandomUtil.randomHexString(4) + "-" + RandomUtil.randomHexString(12); -} - -/** - * Generates new SSRC, CNAME, mslabel, label and msid for local video recvonly stream. + * Generates new SSRC for local video recvonly stream. * FIXME what about eventual SSRC collision ? */ function generateRecvonlySSRC() { + localRecvOnlySSRC = - Math.random().toString(10).substring(2, 11); + localVideoSSRC ? + localVideoSSRC : Math.random().toString(10).substring(2, 11); + localRecvOnlyCName = Math.random().toString(36).substring(2); - localRecvOnlyMSLabel = generateLabel(); - localRecvOnlyLabel = generateLabel(); - localRecvOnlyMSID = localRecvOnlyMSLabel + " " + localRecvOnlyLabel; - - logger.info( + logger.info( "Generated local recvonly SSRC: " + localRecvOnlySSRC + ", cname: " + localRecvOnlyCName); } @@ -8313,51 +8344,41 @@ var LocalSSRCReplacement = { // IF we have local video SSRC stored make sure it is replaced // with old SSRC - if (localVideoSSRC) { - var newSdp = new SDP(localDescription.sdp); - if (newSdp.media[1].indexOf("a=ssrc:") !== -1 && - !newSdp.containsSSRC(localVideoSSRC)) { - // Get new video SSRC - var map = newSdp.getMediaSsrcMap(); - var videoPart = map[1]; - var videoSSRCs = videoPart.ssrcs; - var newSSRC = Object.keys(videoSSRCs)[0]; + var sdp = new SDP(localDescription.sdp); + if (sdp.media.length < 2) + return; - logger.info( - "Replacing new video SSRC: " + newSSRC + - " with " + localVideoSSRC); + if (localVideoSSRC && sdp.media[1].indexOf("a=ssrc:") !== -1 && + !sdp.containsSSRC(localVideoSSRC)) { + // Get new video SSRC + var map = sdp.getMediaSsrcMap(); + var videoPart = map[1]; + var videoSSRCs = videoPart.ssrcs; + var newSSRC = Object.keys(videoSSRCs)[0]; - localDescription.sdp = - newSdp.raw.replace( - new RegExp('a=ssrc:' + newSSRC, 'g'), - 'a=ssrc:' + localVideoSSRC); - } - } else { + logger.info( + "Replacing new video SSRC: " + newSSRC + + " with " + localVideoSSRC); + + localDescription.sdp = + sdp.raw.replace( + new RegExp('a=ssrc:' + newSSRC, 'g'), + 'a=ssrc:' + localVideoSSRC); + } + else if (sdp.media[1].indexOf('a=ssrc:') === -1 && + sdp.media[1].indexOf('a=recvonly') !== -1) { // Make sure we have any SSRC for recvonly video stream - var sdp = new SDP(localDescription.sdp); - - if (sdp.media[1] && sdp.media[1].indexOf('a=ssrc:') === -1 && - sdp.media[1].indexOf('a=recvonly') !== -1) { - - if (!localRecvOnlySSRC) { - generateRecvonlySSRC(); - } - localVideoSSRC = localRecvOnlySSRC; - - logger.info('No SSRC in video recvonly stream' + - ' - adding SSRC: ' + localRecvOnlySSRC); - - sdp.media[1] += 'a=ssrc:' + localRecvOnlySSRC + - ' cname:' + localRecvOnlyCName + '\r\n' + - 'a=ssrc:' + localRecvOnlySSRC + - ' msid:' + localRecvOnlyMSID + '\r\n' + - 'a=ssrc:' + localRecvOnlySSRC + - ' mslabel:' + localRecvOnlyMSLabel + '\r\n' + - 'a=ssrc:' + localRecvOnlySSRC + - ' label:' + localRecvOnlyLabel + '\r\n'; - - localDescription.sdp = sdp.session + sdp.media.join(''); + if (!localRecvOnlySSRC) { + generateRecvonlySSRC(); } + + logger.info('No SSRC in video recvonly stream' + + ' - adding SSRC: ' + localRecvOnlySSRC); + + sdp.media[1] += 'a=ssrc:' + localRecvOnlySSRC + + ' cname:' + localRecvOnlyCName + '\r\n'; + + localDescription.sdp = sdp.session + sdp.media.join(''); } return localDescription; }, @@ -8493,16 +8514,18 @@ SDP.prototype.getMediaSsrcMap = function() { * @param ssrc the ssrc to check. * @returns {boolean} true if this SDP contains given SSRC. */ -SDP.prototype.containsSSRC = function(ssrc) { +SDP.prototype.containsSSRC = function (ssrc) { + // FIXME this code is really strange - improve it if you can var medias = this.getMediaSsrcMap(); - Object.keys(medias).forEach(function(mediaindex){ - var media = medias[mediaindex]; - //logger.log("Check", channel, ssrc); - if(Object.keys(media.ssrcs).indexOf(ssrc) != -1){ - return true; + var result = false; + Object.keys(medias).forEach(function (mediaindex) { + if (result) + return; + if (medias[mediaindex].ssrcs[ssrc]) { + result = true; } }); - return false; + return result; }; // remove iSAC and CN from SDP @@ -10053,7 +10076,7 @@ TraceablePeerConnection.prototype.getStats = function(callback, errback) { module.exports = TraceablePeerConnection; }).call(this,"/modules/xmpp/TraceablePeerConnection.js") -},{"../../service/xmpp/XMPPEvents":89,"../RTC/RTC":16,"../RTC/RTCBrowserType.js":17,"./LocalSSRCReplacement":29,"jitsi-meet-logger":47,"sdp-interop":65,"sdp-simulcast":72,"sdp-transform":79}],34:[function(require,module,exports){ +},{"../../service/xmpp/XMPPEvents":85,"../RTC/RTC":16,"../RTC/RTCBrowserType.js":17,"./LocalSSRCReplacement":29,"jitsi-meet-logger":47,"sdp-interop":65,"sdp-simulcast":68,"sdp-transform":75}],34:[function(require,module,exports){ (function (__filename){ /* global $, $iq, APP, config, messageHandler, roomName, sessionTerminated, Strophe, Util */ @@ -10497,7 +10520,7 @@ module.exports = Moderator; }).call(this,"/modules/xmpp/moderator.js") -},{"../../service/authentication/AuthenticationEvents":85,"../../service/xmpp/XMPPEvents":89,"../settings/Settings":21,"jitsi-meet-logger":47}],35:[function(require,module,exports){ +},{"../../service/authentication/AuthenticationEvents":81,"../../service/xmpp/XMPPEvents":85,"../settings/Settings":21,"jitsi-meet-logger":47}],35:[function(require,module,exports){ (function (__filename){ /* jshint -W117 */ /* a simple MUC connection plugin @@ -10891,7 +10914,7 @@ module.exports = function(XMPP, eventEmitter) { }).call(this,"/modules/xmpp/strophe.jingle.js") -},{"../../service/xmpp/XMPPEvents":89,"../RTC/RTCBrowserType":17,"./JingleSessionPC":28,"jitsi-meet-logger":47}],37:[function(require,module,exports){ +},{"../../service/xmpp/XMPPEvents":85,"../RTC/RTCBrowserType":17,"./JingleSessionPC":28,"jitsi-meet-logger":47}],37:[function(require,module,exports){ /* global Strophe */ module.exports = function () { @@ -11039,7 +11062,7 @@ module.exports = function (XMPP, eventEmitter) { }; }).call(this,"/modules/xmpp/strophe.ping.js") -},{"../../service/xmpp/XMPPEvents":89,"jitsi-meet-logger":47}],39:[function(require,module,exports){ +},{"../../service/xmpp/XMPPEvents":85,"jitsi-meet-logger":47}],39:[function(require,module,exports){ (function (__filename){ /* jshint -W117 */ var logger = require("jitsi-meet-logger").getLogger(__filename); @@ -11512,7 +11535,7 @@ XMPP.prototype.getLocalSSRC = function (mediaType) { module.exports = XMPP; }).call(this,"/modules/xmpp/xmpp.js") -},{"../../JitsiConnectionErrors":5,"../../JitsiConnectionEvents":6,"../../service/RTC/RTCEvents":83,"../../service/xmpp/XMPPEvents":89,"../RTC/RTC":16,"./strophe.emuc":35,"./strophe.jingle":36,"./strophe.logger":37,"./strophe.ping":38,"./strophe.rayo":39,"./strophe.util":40,"events":43,"jitsi-meet-logger":47,"pako":48}],42:[function(require,module,exports){ +},{"../../JitsiConnectionErrors":5,"../../JitsiConnectionEvents":6,"../../service/RTC/RTCEvents":79,"../../service/xmpp/XMPPEvents":85,"../RTC/RTC":16,"./strophe.emuc":35,"./strophe.jingle":36,"./strophe.logger":37,"./strophe.ping":38,"./strophe.rayo":39,"./strophe.util":40,"events":43,"jitsi-meet-logger":47,"pako":48}],42:[function(require,module,exports){ (function (process){ /*! * async @@ -21562,487 +21585,7 @@ exports.parse = function(sdp) { }; -},{"sdp-transform":69}],68:[function(require,module,exports){ -var grammar = module.exports = { - v: [{ - name: 'version', - reg: /^(\d*)$/ - }], - o: [{ //o=- 20518 0 IN IP4 203.0.113.1 - // NB: sessionId will be a String in most cases because it is huge - name: 'origin', - reg: /^(\S*) (\d*) (\d*) (\S*) IP(\d) (\S*)/, - names: ['username', 'sessionId', 'sessionVersion', 'netType', 'ipVer', 'address'], - format: "%s %s %d %s IP%d %s" - }], - // default parsing of these only (though some of these feel outdated) - s: [{ name: 'name' }], - i: [{ name: 'description' }], - u: [{ name: 'uri' }], - e: [{ name: 'email' }], - p: [{ name: 'phone' }], - z: [{ name: 'timezones' }], // TODO: this one can actually be parsed properly.. - r: [{ name: 'repeats' }], // TODO: this one can also be parsed properly - //k: [{}], // outdated thing ignored - t: [{ //t=0 0 - name: 'timing', - reg: /^(\d*) (\d*)/, - names: ['start', 'stop'], - format: "%d %d" - }], - c: [{ //c=IN IP4 10.47.197.26 - name: 'connection', - reg: /^IN IP(\d) (\S*)/, - names: ['version', 'ip'], - format: "IN IP%d %s" - }], - b: [{ //b=AS:4000 - push: 'bandwidth', - reg: /^(TIAS|AS|CT|RR|RS):(\d*)/, - names: ['type', 'limit'], - format: "%s:%s" - }], - m: [{ //m=video 51744 RTP/AVP 126 97 98 34 31 - // NB: special - pushes to session - // TODO: rtp/fmtp should be filtered by the payloads found here? - reg: /^(\w*) (\d*) ([\w\/]*)(?: (.*))?/, - names: ['type', 'port', 'protocol', 'payloads'], - format: "%s %d %s %s" - }], - a: [ - { //a=rtpmap:110 opus/48000/2 - push: 'rtp', - reg: /^rtpmap:(\d*) ([\w\-]*)(?:\s*\/(\d*)(?:\s*\/(\S*))?)?/, - names: ['payload', 'codec', 'rate', 'encoding'], - format: function (o) { - return (o.encoding) ? - "rtpmap:%d %s/%s/%s": - o.rate ? - "rtpmap:%d %s/%s": - "rtpmap:%d %s"; - } - }, - { - //a=fmtp:108 profile-level-id=24;object=23;bitrate=64000 - //a=fmtp:111 minptime=10; useinbandfec=1 - push: 'fmtp', - reg: /^fmtp:(\d*) ([\S| ]*)/, - names: ['payload', 'config'], - format: "fmtp:%d %s" - }, - { //a=control:streamid=0 - name: 'control', - reg: /^control:(.*)/, - format: "control:%s" - }, - { //a=rtcp:65179 IN IP4 193.84.77.194 - name: 'rtcp', - reg: /^rtcp:(\d*)(?: (\S*) IP(\d) (\S*))?/, - names: ['port', 'netType', 'ipVer', 'address'], - format: function (o) { - return (o.address != null) ? - "rtcp:%d %s IP%d %s": - "rtcp:%d"; - } - }, - { //a=rtcp-fb:98 trr-int 100 - push: 'rtcpFbTrrInt', - reg: /^rtcp-fb:(\*|\d*) trr-int (\d*)/, - names: ['payload', 'value'], - format: "rtcp-fb:%d trr-int %d" - }, - { //a=rtcp-fb:98 nack rpsi - push: 'rtcpFb', - reg: /^rtcp-fb:(\*|\d*) ([\w-_]*)(?: ([\w-_]*))?/, - names: ['payload', 'type', 'subtype'], - format: function (o) { - return (o.subtype != null) ? - "rtcp-fb:%s %s %s": - "rtcp-fb:%s %s"; - } - }, - { //a=extmap:2 urn:ietf:params:rtp-hdrext:toffset - //a=extmap:1/recvonly URI-gps-string - push: 'ext', - reg: /^extmap:([\w_\/]*) (\S*)(?: (\S*))?/, - names: ['value', 'uri', 'config'], // value may include "/direction" suffix - format: function (o) { - return (o.config != null) ? - "extmap:%s %s %s": - "extmap:%s %s"; - } - }, - { - //a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:PS1uQCVeeCFCanVmcjkpPywjNWhcYD0mXXtxaVBR|2^20|1:32 - push: 'crypto', - reg: /^crypto:(\d*) ([\w_]*) (\S*)(?: (\S*))?/, - names: ['id', 'suite', 'config', 'sessionConfig'], - format: function (o) { - return (o.sessionConfig != null) ? - "crypto:%d %s %s %s": - "crypto:%d %s %s"; - } - }, - { //a=setup:actpass - name: 'setup', - reg: /^setup:(\w*)/, - format: "setup:%s" - }, - { //a=mid:1 - name: 'mid', - reg: /^mid:([^\s]*)/, - format: "mid:%s" - }, - { //a=msid:0c8b064d-d807-43b4-b434-f92a889d8587 98178685-d409-46e0-8e16-7ef0db0db64a - name: 'msid', - reg: /^msid:(.*)/, - format: "msid:%s" - }, - { //a=ptime:20 - name: 'ptime', - reg: /^ptime:(\d*)/, - format: "ptime:%d" - }, - { //a=maxptime:60 - name: 'maxptime', - reg: /^maxptime:(\d*)/, - format: "maxptime:%d" - }, - { //a=sendrecv - name: 'direction', - reg: /^(sendrecv|recvonly|sendonly|inactive)/ - }, - { //a=ice-lite - name: 'icelite', - reg: /^(ice-lite)/ - }, - { //a=ice-ufrag:F7gI - name: 'iceUfrag', - reg: /^ice-ufrag:(\S*)/, - format: "ice-ufrag:%s" - }, - { //a=ice-pwd:x9cml/YzichV2+XlhiMu8g - name: 'icePwd', - reg: /^ice-pwd:(\S*)/, - format: "ice-pwd:%s" - }, - { //a=fingerprint:SHA-1 00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33 - name: 'fingerprint', - reg: /^fingerprint:(\S*) (\S*)/, - names: ['type', 'hash'], - format: "fingerprint:%s %s" - }, - { - //a=candidate:0 1 UDP 2113667327 203.0.113.1 54400 typ host - //a=candidate:1162875081 1 udp 2113937151 192.168.34.75 60017 typ host generation 0 - //a=candidate:3289912957 2 udp 1845501695 193.84.77.194 60017 typ srflx raddr 192.168.34.75 rport 60017 generation 0 - //a=candidate:229815620 1 tcp 1518280447 192.168.150.19 60017 typ host tcptype active generation 0 - //a=candidate:3289912957 2 tcp 1845501695 193.84.77.194 60017 typ srflx raddr 192.168.34.75 rport 60017 tcptype passive generation 0 - push:'candidates', - reg: /^candidate:(\S*) (\d*) (\S*) (\d*) (\S*) (\d*) typ (\S*)(?: raddr (\S*) rport (\d*))?(?: tcptype (\S*))?(?: generation (\d*))?/, - names: ['foundation', 'component', 'transport', 'priority', 'ip', 'port', 'type', 'raddr', 'rport', 'tcptype', 'generation'], - format: function (o) { - var str = "candidate:%s %d %s %d %s %d typ %s"; - - str += (o.raddr != null) ? " raddr %s rport %d" : "%v%v"; - - // NB: candidate has three optional chunks, so %void middles one if it's missing - str += (o.tcptype != null) ? " tcptype %s" : "%v"; - - if (o.generation != null) { - str += " generation %d"; - } - return str; - } - }, - { //a=end-of-candidates (keep after the candidates line for readability) - name: 'endOfCandidates', - reg: /^(end-of-candidates)/ - }, - { //a=remote-candidates:1 203.0.113.1 54400 2 203.0.113.1 54401 ... - name: 'remoteCandidates', - reg: /^remote-candidates:(.*)/, - format: "remote-candidates:%s" - }, - { //a=ice-options:google-ice - name: 'iceOptions', - reg: /^ice-options:(\S*)/, - format: "ice-options:%s" - }, - { //a=ssrc:2566107569 cname:t9YU8M1UxTF8Y1A1 - push: "ssrcs", - reg: /^ssrc:(\d*) ([\w_]*):(.*)/, - names: ['id', 'attribute', 'value'], - format: "ssrc:%d %s:%s" - }, - { //a=ssrc-group:FEC 1 2 - push: "ssrcGroups", - reg: /^ssrc-group:(\w*) (.*)/, - names: ['semantics', 'ssrcs'], - format: "ssrc-group:%s %s" - }, - { //a=msid-semantic: WMS Jvlam5X3SX1OP6pn20zWogvaKJz5Hjf9OnlV - name: "msidSemantic", - reg: /^msid-semantic:\s?(\w*) (\S*)/, - names: ['semantic', 'token'], - format: "msid-semantic: %s %s" // space after ":" is not accidental - }, - { //a=group:BUNDLE audio video - push: 'groups', - reg: /^group:(\w*) (.*)/, - names: ['type', 'mids'], - format: "group:%s %s" - }, - { //a=rtcp-mux - name: 'rtcpMux', - reg: /^(rtcp-mux)/ - }, - { //a=rtcp-rsize - name: 'rtcpRsize', - reg: /^(rtcp-rsize)/ - }, - { // any a= that we don't understand is kepts verbatim on media.invalid - push: 'invalid', - names: ["value"] - } - ] -}; - -// set sensible defaults to avoid polluting the grammar with boring details -Object.keys(grammar).forEach(function (key) { - var objs = grammar[key]; - objs.forEach(function (obj) { - if (!obj.reg) { - obj.reg = /(.*)/; - } - if (!obj.format) { - obj.format = "%s"; - } - }); -}); - -},{}],69:[function(require,module,exports){ -var parser = require('./parser'); -var writer = require('./writer'); - -exports.write = writer; -exports.parse = parser.parse; -exports.parseFmtpConfig = parser.parseFmtpConfig; -exports.parsePayloads = parser.parsePayloads; -exports.parseRemoteCandidates = parser.parseRemoteCandidates; - -},{"./parser":70,"./writer":71}],70:[function(require,module,exports){ -var toIntIfInt = function (v) { - return String(Number(v)) === v ? Number(v) : v; -}; - -var attachProperties = function (match, location, names, rawName) { - if (rawName && !names) { - location[rawName] = toIntIfInt(match[1]); - } - else { - for (var i = 0; i < names.length; i += 1) { - if (match[i+1] != null) { - location[names[i]] = toIntIfInt(match[i+1]); - } - } - } -}; - -var parseReg = function (obj, location, content) { - var needsBlank = obj.name && obj.names; - if (obj.push && !location[obj.push]) { - location[obj.push] = []; - } - else if (needsBlank && !location[obj.name]) { - location[obj.name] = {}; - } - var keyLocation = obj.push ? - {} : // blank object that will be pushed - needsBlank ? location[obj.name] : location; // otherwise, named location or root - - attachProperties(content.match(obj.reg), keyLocation, obj.names, obj.name); - - if (obj.push) { - location[obj.push].push(keyLocation); - } -}; - -var grammar = require('./grammar'); -var validLine = RegExp.prototype.test.bind(/^([a-z])=(.*)/); - -exports.parse = function (sdp) { - var session = {} - , media = [] - , location = session; // points at where properties go under (one of the above) - - // parse lines we understand - sdp.split(/(\r\n|\r|\n)/).filter(validLine).forEach(function (l) { - var type = l[0]; - var content = l.slice(2); - if (type === 'm') { - media.push({rtp: [], fmtp: []}); - location = media[media.length-1]; // point at latest media line - } - - for (var j = 0; j < (grammar[type] || []).length; j += 1) { - var obj = grammar[type][j]; - if (obj.reg.test(content)) { - return parseReg(obj, location, content); - } - } - }); - - session.media = media; // link it up - return session; -}; - -var fmtpReducer = function (acc, expr) { - var s = expr.split('='); - if (s.length === 2) { - acc[s[0]] = toIntIfInt(s[1]); - } - return acc; -}; - -exports.parseFmtpConfig = function (str) { - return str.split(/\;\s?/).reduce(fmtpReducer, {}); -}; - -exports.parsePayloads = function (str) { - return str.split(' ').map(Number); -}; - -exports.parseRemoteCandidates = function (str) { - var candidates = []; - var parts = str.split(' ').map(toIntIfInt); - for (var i = 0; i < parts.length; i += 3) { - candidates.push({ - component: parts[i], - ip: parts[i + 1], - port: parts[i + 2] - }); - } - return candidates; -}; - -},{"./grammar":68}],71:[function(require,module,exports){ -var grammar = require('./grammar'); - -// customized util.format - discards excess arguments and can void middle ones -var formatRegExp = /%[sdv%]/g; -var format = function (formatStr) { - var i = 1; - var args = arguments; - var len = args.length; - return formatStr.replace(formatRegExp, function (x) { - if (i >= len) { - return x; // missing argument - } - var arg = args[i]; - i += 1; - switch (x) { - case '%%': - return '%'; - case '%s': - return String(arg); - case '%d': - return Number(arg); - case '%v': - return ''; - } - }); - // NB: we discard excess arguments - they are typically undefined from makeLine -}; - -var makeLine = function (type, obj, location) { - var str = obj.format instanceof Function ? - (obj.format(obj.push ? location : location[obj.name])) : - obj.format; - - var args = [type + '=' + str]; - if (obj.names) { - for (var i = 0; i < obj.names.length; i += 1) { - var n = obj.names[i]; - if (obj.name) { - args.push(location[obj.name][n]); - } - else { // for mLine and push attributes - args.push(location[obj.names[i]]); - } - } - } - else { - args.push(location[obj.name]); - } - return format.apply(null, args); -}; - -// RFC specified order -// TODO: extend this with all the rest -var defaultOuterOrder = [ - 'v', 'o', 's', 'i', - 'u', 'e', 'p', 'c', - 'b', 't', 'r', 'z', 'a' -]; -var defaultInnerOrder = ['i', 'c', 'b', 'a']; - - -module.exports = function (session, opts) { - opts = opts || {}; - // ensure certain properties exist - if (session.version == null) { - session.version = 0; // "v=0" must be there (only defined version atm) - } - if (session.name == null) { - session.name = " "; // "s= " must be there if no meaningful name set - } - session.media.forEach(function (mLine) { - if (mLine.payloads == null) { - mLine.payloads = ""; - } - }); - - var outerOrder = opts.outerOrder || defaultOuterOrder; - var innerOrder = opts.innerOrder || defaultInnerOrder; - var sdp = []; - - // loop through outerOrder for matching properties on session - outerOrder.forEach(function (type) { - grammar[type].forEach(function (obj) { - if (obj.name in session && session[obj.name] != null) { - sdp.push(makeLine(type, obj, session)); - } - else if (obj.push in session && session[obj.push] != null) { - session[obj.push].forEach(function (el) { - sdp.push(makeLine(type, obj, el)); - }); - } - }); - }); - - // then for each media line, follow the innerOrder - session.media.forEach(function (mLine) { - sdp.push(makeLine('m', grammar.m[0], mLine)); - - innerOrder.forEach(function (type) { - grammar[type].forEach(function (obj) { - if (obj.name in mLine && mLine[obj.name] != null) { - sdp.push(makeLine(type, obj, mLine)); - } - else if (obj.push in mLine && mLine[obj.push] != null) { - mLine[obj.push].forEach(function (el) { - sdp.push(makeLine(type, obj, el)); - }); - } - }); - }); - }); - - return sdp.join('\r\n') + '\r\n'; -}; - -},{"./grammar":68}],72:[function(require,module,exports){ +},{"sdp-transform":75}],68:[function(require,module,exports){ /* Copyright @ 2015 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -22462,7 +22005,7 @@ Simulcast.prototype.mungeLocalDescription = function (desc) { module.exports = Simulcast; -},{"./transform-utils":73,"sdp-transform":75}],73:[function(require,module,exports){ +},{"./transform-utils":69,"sdp-transform":71}],69:[function(require,module,exports){ /* Copyright @ 2015 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -22528,30 +22071,834 @@ exports.parseSsrcs = function (mLine) { }; -},{}],74:[function(require,module,exports){ -arguments[4][68][0].apply(exports,arguments) -},{"dup":68}],75:[function(require,module,exports){ -arguments[4][69][0].apply(exports,arguments) -},{"./parser":76,"./writer":77,"dup":69}],76:[function(require,module,exports){ -arguments[4][70][0].apply(exports,arguments) -},{"./grammar":74,"dup":70}],77:[function(require,module,exports){ +},{}],70:[function(require,module,exports){ +var grammar = module.exports = { + v: [{ + name: 'version', + reg: /^(\d*)$/ + }], + o: [{ //o=- 20518 0 IN IP4 203.0.113.1 + // NB: sessionId will be a String in most cases because it is huge + name: 'origin', + reg: /^(\S*) (\d*) (\d*) (\S*) IP(\d) (\S*)/, + names: ['username', 'sessionId', 'sessionVersion', 'netType', 'ipVer', 'address'], + format: "%s %s %d %s IP%d %s" + }], + // default parsing of these only (though some of these feel outdated) + s: [{ name: 'name' }], + i: [{ name: 'description' }], + u: [{ name: 'uri' }], + e: [{ name: 'email' }], + p: [{ name: 'phone' }], + z: [{ name: 'timezones' }], // TODO: this one can actually be parsed properly.. + r: [{ name: 'repeats' }], // TODO: this one can also be parsed properly + //k: [{}], // outdated thing ignored + t: [{ //t=0 0 + name: 'timing', + reg: /^(\d*) (\d*)/, + names: ['start', 'stop'], + format: "%d %d" + }], + c: [{ //c=IN IP4 10.47.197.26 + name: 'connection', + reg: /^IN IP(\d) (\S*)/, + names: ['version', 'ip'], + format: "IN IP%d %s" + }], + b: [{ //b=AS:4000 + push: 'bandwidth', + reg: /^(TIAS|AS|CT|RR|RS):(\d*)/, + names: ['type', 'limit'], + format: "%s:%s" + }], + m: [{ //m=video 51744 RTP/AVP 126 97 98 34 31 + // NB: special - pushes to session + // TODO: rtp/fmtp should be filtered by the payloads found here? + reg: /^(\w*) (\d*) ([\w\/]*)(?: (.*))?/, + names: ['type', 'port', 'protocol', 'payloads'], + format: "%s %d %s %s" + }], + a: [ + { //a=rtpmap:110 opus/48000/2 + push: 'rtp', + reg: /^rtpmap:(\d*) ([\w\-]*)\/(\d*)(?:\s*\/(\S*))?/, + names: ['payload', 'codec', 'rate', 'encoding'], + format: function (o) { + return (o.encoding) ? + "rtpmap:%d %s/%s/%s": + "rtpmap:%d %s/%s"; + } + }, + { //a=fmtp:108 profile-level-id=24;object=23;bitrate=64000 + push: 'fmtp', + reg: /^fmtp:(\d*) (\S*)/, + names: ['payload', 'config'], + format: "fmtp:%d %s" + }, + { //a=control:streamid=0 + name: 'control', + reg: /^control:(.*)/, + format: "control:%s" + }, + { //a=rtcp:65179 IN IP4 193.84.77.194 + name: 'rtcp', + reg: /^rtcp:(\d*)(?: (\S*) IP(\d) (\S*))?/, + names: ['port', 'netType', 'ipVer', 'address'], + format: function (o) { + return (o.address != null) ? + "rtcp:%d %s IP%d %s": + "rtcp:%d"; + } + }, + { //a=rtcp-fb:98 trr-int 100 + push: 'rtcpFbTrrInt', + reg: /^rtcp-fb:(\*|\d*) trr-int (\d*)/, + names: ['payload', 'value'], + format: "rtcp-fb:%d trr-int %d" + }, + { //a=rtcp-fb:98 nack rpsi + push: 'rtcpFb', + reg: /^rtcp-fb:(\*|\d*) ([\w-_]*)(?: ([\w-_]*))?/, + names: ['payload', 'type', 'subtype'], + format: function (o) { + return (o.subtype != null) ? + "rtcp-fb:%s %s %s": + "rtcp-fb:%s %s"; + } + }, + { //a=extmap:2 urn:ietf:params:rtp-hdrext:toffset + //a=extmap:1/recvonly URI-gps-string + push: 'ext', + reg: /^extmap:([\w_\/]*) (\S*)(?: (\S*))?/, + names: ['value', 'uri', 'config'], // value may include "/direction" suffix + format: function (o) { + return (o.config != null) ? + "extmap:%s %s %s": + "extmap:%s %s"; + } + }, + { + //a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:PS1uQCVeeCFCanVmcjkpPywjNWhcYD0mXXtxaVBR|2^20|1:32 + push: 'crypto', + reg: /^crypto:(\d*) ([\w_]*) (\S*)(?: (\S*))?/, + names: ['id', 'suite', 'config', 'sessionConfig'], + format: function (o) { + return (o.sessionConfig != null) ? + "crypto:%d %s %s %s": + "crypto:%d %s %s"; + } + }, + { //a=setup:actpass + name: 'setup', + reg: /^setup:(\w*)/, + format: "setup:%s" + }, + { //a=mid:1 + name: 'mid', + reg: /^mid:([^\s]*)/, + format: "mid:%s" + }, + { //a=msid:0c8b064d-d807-43b4-b434-f92a889d8587 98178685-d409-46e0-8e16-7ef0db0db64a + name: 'msid', + reg: /^msid:(.*)/, + format: "msid:%s" + }, + { //a=ptime:20 + name: 'ptime', + reg: /^ptime:(\d*)/, + format: "ptime:%d" + }, + { //a=maxptime:60 + name: 'maxptime', + reg: /^maxptime:(\d*)/, + format: "maxptime:%d" + }, + { //a=sendrecv + name: 'direction', + reg: /^(sendrecv|recvonly|sendonly|inactive)/ + }, + { //a=ice-lite + name: 'icelite', + reg: /^(ice-lite)/ + }, + { //a=ice-ufrag:F7gI + name: 'iceUfrag', + reg: /^ice-ufrag:(\S*)/, + format: "ice-ufrag:%s" + }, + { //a=ice-pwd:x9cml/YzichV2+XlhiMu8g + name: 'icePwd', + reg: /^ice-pwd:(\S*)/, + format: "ice-pwd:%s" + }, + { //a=fingerprint:SHA-1 00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33 + name: 'fingerprint', + reg: /^fingerprint:(\S*) (\S*)/, + names: ['type', 'hash'], + format: "fingerprint:%s %s" + }, + { + //a=candidate:0 1 UDP 2113667327 203.0.113.1 54400 typ host + //a=candidate:1162875081 1 udp 2113937151 192.168.34.75 60017 typ host generation 0 + //a=candidate:3289912957 2 udp 1845501695 193.84.77.194 60017 typ srflx raddr 192.168.34.75 rport 60017 generation 0 + push:'candidates', + reg: /^candidate:(\S*) (\d*) (\S*) (\d*) (\S*) (\d*) typ (\S*)(?: raddr (\S*) rport (\d*))?(?: generation (\d*))?/, + names: ['foundation', 'component', 'transport', 'priority', 'ip', 'port', 'type', 'raddr', 'rport', 'generation'], + format: function (o) { + var str = "candidate:%s %d %s %d %s %d typ %s"; + // NB: candidate has two optional chunks, so %void middle one if it's missing + str += (o.raddr != null) ? " raddr %s rport %d" : "%v%v"; + if (o.generation != null) { + str += " generation %d"; + } + return str; + } + }, + { //a=end-of-candidates (keep after the candidates line for readability) + name: 'endOfCandidates', + reg: /^(end-of-candidates)/ + }, + { //a=remote-candidates:1 203.0.113.1 54400 2 203.0.113.1 54401 ... + name: 'remoteCandidates', + reg: /^remote-candidates:(.*)/, + format: "remote-candidates:%s" + }, + { //a=ice-options:google-ice + name: 'iceOptions', + reg: /^ice-options:(\S*)/, + format: "ice-options:%s" + }, + { //a=ssrc:2566107569 cname:t9YU8M1UxTF8Y1A1 + push: "ssrcs", + reg: /^ssrc:(\d*) ([\w_]*):(.*)/, + names: ['id', 'attribute', 'value'], + format: "ssrc:%d %s:%s" + }, + { //a=ssrc-group:FEC 1 2 + push: "ssrcGroups", + reg: /^ssrc-group:(\w*) (.*)/, + names: ['semantics', 'ssrcs'], + format: "ssrc-group:%s %s" + }, + { //a=msid-semantic: WMS Jvlam5X3SX1OP6pn20zWogvaKJz5Hjf9OnlV + name: "msidSemantic", + reg: /^msid-semantic:\s?(\w*) (\S*)/, + names: ['semantic', 'token'], + format: "msid-semantic: %s %s" // space after ":" is not accidental + }, + { //a=group:BUNDLE audio video + push: 'groups', + reg: /^group:(\w*) (.*)/, + names: ['type', 'mids'], + format: "group:%s %s" + }, + { //a=rtcp-mux + name: 'rtcpMux', + reg: /^(rtcp-mux)/ + }, + { //a=rtcp-rsize + name: 'rtcpRsize', + reg: /^(rtcp-rsize)/ + }, + { // any a= that we don't understand is kepts verbatim on media.invalid + push: 'invalid', + names: ["value"] + } + ] +}; + +// set sensible defaults to avoid polluting the grammar with boring details +Object.keys(grammar).forEach(function (key) { + var objs = grammar[key]; + objs.forEach(function (obj) { + if (!obj.reg) { + obj.reg = /(.*)/; + } + if (!obj.format) { + obj.format = "%s"; + } + }); +}); + +},{}],71:[function(require,module,exports){ +var parser = require('./parser'); +var writer = require('./writer'); + +exports.write = writer; +exports.parse = parser.parse; +exports.parseFmtpConfig = parser.parseFmtpConfig; +exports.parsePayloads = parser.parsePayloads; +exports.parseRemoteCandidates = parser.parseRemoteCandidates; + +},{"./parser":72,"./writer":73}],72:[function(require,module,exports){ +var toIntIfInt = function (v) { + return String(Number(v)) === v ? Number(v) : v; +}; + +var attachProperties = function (match, location, names, rawName) { + if (rawName && !names) { + location[rawName] = toIntIfInt(match[1]); + } + else { + for (var i = 0; i < names.length; i += 1) { + if (match[i+1] != null) { + location[names[i]] = toIntIfInt(match[i+1]); + } + } + } +}; + +var parseReg = function (obj, location, content) { + var needsBlank = obj.name && obj.names; + if (obj.push && !location[obj.push]) { + location[obj.push] = []; + } + else if (needsBlank && !location[obj.name]) { + location[obj.name] = {}; + } + var keyLocation = obj.push ? + {} : // blank object that will be pushed + needsBlank ? location[obj.name] : location; // otherwise, named location or root + + attachProperties(content.match(obj.reg), keyLocation, obj.names, obj.name); + + if (obj.push) { + location[obj.push].push(keyLocation); + } +}; + +var grammar = require('./grammar'); +var validLine = RegExp.prototype.test.bind(/^([a-z])=(.*)/); + +exports.parse = function (sdp) { + var session = {} + , media = [] + , location = session; // points at where properties go under (one of the above) + + // parse lines we understand + sdp.split(/(\r\n|\r|\n)/).filter(validLine).forEach(function (l) { + var type = l[0]; + var content = l.slice(2); + if (type === 'm') { + media.push({rtp: [], fmtp: []}); + location = media[media.length-1]; // point at latest media line + } + + for (var j = 0; j < (grammar[type] || []).length; j += 1) { + var obj = grammar[type][j]; + if (obj.reg.test(content)) { + return parseReg(obj, location, content); + } + } + }); + + session.media = media; // link it up + return session; +}; + +var fmtpReducer = function (acc, expr) { + var s = expr.split('='); + if (s.length === 2) { + acc[s[0]] = toIntIfInt(s[1]); + } + return acc; +}; + +exports.parseFmtpConfig = function (str) { + return str.split(';').reduce(fmtpReducer, {}); +}; + +exports.parsePayloads = function (str) { + return str.split(' ').map(Number); +}; + +exports.parseRemoteCandidates = function (str) { + var candidates = []; + var parts = str.split(' ').map(toIntIfInt); + for (var i = 0; i < parts.length; i += 3) { + candidates.push({ + component: parts[i], + ip: parts[i + 1], + port: parts[i + 2] + }); + } + return candidates; +}; + +},{"./grammar":70}],73:[function(require,module,exports){ +var grammar = require('./grammar'); + +// customized util.format - discards excess arguments and can void middle ones +var formatRegExp = /%[sdv%]/g; +var format = function (formatStr) { + var i = 1; + var args = arguments; + var len = args.length; + return formatStr.replace(formatRegExp, function (x) { + if (i >= len) { + return x; // missing argument + } + var arg = args[i]; + i += 1; + switch (x) { + case '%%': + return '%'; + case '%s': + return String(arg); + case '%d': + return Number(arg); + case '%v': + return ''; + } + }); + // NB: we discard excess arguments - they are typically undefined from makeLine +}; + +var makeLine = function (type, obj, location) { + var str = obj.format instanceof Function ? + (obj.format(obj.push ? location : location[obj.name])) : + obj.format; + + var args = [type + '=' + str]; + if (obj.names) { + for (var i = 0; i < obj.names.length; i += 1) { + var n = obj.names[i]; + if (obj.name) { + args.push(location[obj.name][n]); + } + else { // for mLine and push attributes + args.push(location[obj.names[i]]); + } + } + } + else { + args.push(location[obj.name]); + } + return format.apply(null, args); +}; + +// RFC specified order +// TODO: extend this with all the rest +var defaultOuterOrder = [ + 'v', 'o', 's', 'i', + 'u', 'e', 'p', 'c', + 'b', 't', 'r', 'z', 'a' +]; +var defaultInnerOrder = ['i', 'c', 'b', 'a']; + + +module.exports = function (session, opts) { + opts = opts || {}; + // ensure certain properties exist + if (session.version == null) { + session.version = 0; // "v=0" must be there (only defined version atm) + } + if (session.name == null) { + session.name = " "; // "s= " must be there if no meaningful name set + } + session.media.forEach(function (mLine) { + if (mLine.payloads == null) { + mLine.payloads = ""; + } + }); + + var outerOrder = opts.outerOrder || defaultOuterOrder; + var innerOrder = opts.innerOrder || defaultInnerOrder; + var sdp = []; + + // loop through outerOrder for matching properties on session + outerOrder.forEach(function (type) { + grammar[type].forEach(function (obj) { + if (obj.name in session && session[obj.name] != null) { + sdp.push(makeLine(type, obj, session)); + } + else if (obj.push in session && session[obj.push] != null) { + session[obj.push].forEach(function (el) { + sdp.push(makeLine(type, obj, el)); + }); + } + }); + }); + + // then for each media line, follow the innerOrder + session.media.forEach(function (mLine) { + sdp.push(makeLine('m', grammar.m[0], mLine)); + + innerOrder.forEach(function (type) { + grammar[type].forEach(function (obj) { + if (obj.name in mLine && mLine[obj.name] != null) { + sdp.push(makeLine(type, obj, mLine)); + } + else if (obj.push in mLine && mLine[obj.push] != null) { + mLine[obj.push].forEach(function (el) { + sdp.push(makeLine(type, obj, el)); + }); + } + }); + }); + }); + + return sdp.join('\r\n') + '\r\n'; +}; + +},{"./grammar":70}],74:[function(require,module,exports){ +var grammar = module.exports = { + v: [{ + name: 'version', + reg: /^(\d*)$/ + }], + o: [{ //o=- 20518 0 IN IP4 203.0.113.1 + // NB: sessionId will be a String in most cases because it is huge + name: 'origin', + reg: /^(\S*) (\d*) (\d*) (\S*) IP(\d) (\S*)/, + names: ['username', 'sessionId', 'sessionVersion', 'netType', 'ipVer', 'address'], + format: "%s %s %d %s IP%d %s" + }], + // default parsing of these only (though some of these feel outdated) + s: [{ name: 'name' }], + i: [{ name: 'description' }], + u: [{ name: 'uri' }], + e: [{ name: 'email' }], + p: [{ name: 'phone' }], + z: [{ name: 'timezones' }], // TODO: this one can actually be parsed properly.. + r: [{ name: 'repeats' }], // TODO: this one can also be parsed properly + //k: [{}], // outdated thing ignored + t: [{ //t=0 0 + name: 'timing', + reg: /^(\d*) (\d*)/, + names: ['start', 'stop'], + format: "%d %d" + }], + c: [{ //c=IN IP4 10.47.197.26 + name: 'connection', + reg: /^IN IP(\d) (\S*)/, + names: ['version', 'ip'], + format: "IN IP%d %s" + }], + b: [{ //b=AS:4000 + push: 'bandwidth', + reg: /^(TIAS|AS|CT|RR|RS):(\d*)/, + names: ['type', 'limit'], + format: "%s:%s" + }], + m: [{ //m=video 51744 RTP/AVP 126 97 98 34 31 + // NB: special - pushes to session + // TODO: rtp/fmtp should be filtered by the payloads found here? + reg: /^(\w*) (\d*) ([\w\/]*)(?: (.*))?/, + names: ['type', 'port', 'protocol', 'payloads'], + format: "%s %d %s %s" + }], + a: [ + { //a=rtpmap:110 opus/48000/2 + push: 'rtp', + reg: /^rtpmap:(\d*) ([\w\-]*)\/(\d*)(?:\s*\/(\S*))?/, + names: ['payload', 'codec', 'rate', 'encoding'], + format: function (o) { + return (o.encoding) ? + "rtpmap:%d %s/%s/%s": + "rtpmap:%d %s/%s"; + } + }, + { + //a=fmtp:108 profile-level-id=24;object=23;bitrate=64000 + //a=fmtp:111 minptime=10; useinbandfec=1 + push: 'fmtp', + reg: /^fmtp:(\d*) ([\S| ]*)/, + names: ['payload', 'config'], + format: "fmtp:%d %s" + }, + { //a=control:streamid=0 + name: 'control', + reg: /^control:(.*)/, + format: "control:%s" + }, + { //a=rtcp:65179 IN IP4 193.84.77.194 + name: 'rtcp', + reg: /^rtcp:(\d*)(?: (\S*) IP(\d) (\S*))?/, + names: ['port', 'netType', 'ipVer', 'address'], + format: function (o) { + return (o.address != null) ? + "rtcp:%d %s IP%d %s": + "rtcp:%d"; + } + }, + { //a=rtcp-fb:98 trr-int 100 + push: 'rtcpFbTrrInt', + reg: /^rtcp-fb:(\*|\d*) trr-int (\d*)/, + names: ['payload', 'value'], + format: "rtcp-fb:%d trr-int %d" + }, + { //a=rtcp-fb:98 nack rpsi + push: 'rtcpFb', + reg: /^rtcp-fb:(\*|\d*) ([\w-_]*)(?: ([\w-_]*))?/, + names: ['payload', 'type', 'subtype'], + format: function (o) { + return (o.subtype != null) ? + "rtcp-fb:%s %s %s": + "rtcp-fb:%s %s"; + } + }, + { //a=extmap:2 urn:ietf:params:rtp-hdrext:toffset + //a=extmap:1/recvonly URI-gps-string + push: 'ext', + reg: /^extmap:([\w_\/]*) (\S*)(?: (\S*))?/, + names: ['value', 'uri', 'config'], // value may include "/direction" suffix + format: function (o) { + return (o.config != null) ? + "extmap:%s %s %s": + "extmap:%s %s"; + } + }, + { + //a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:PS1uQCVeeCFCanVmcjkpPywjNWhcYD0mXXtxaVBR|2^20|1:32 + push: 'crypto', + reg: /^crypto:(\d*) ([\w_]*) (\S*)(?: (\S*))?/, + names: ['id', 'suite', 'config', 'sessionConfig'], + format: function (o) { + return (o.sessionConfig != null) ? + "crypto:%d %s %s %s": + "crypto:%d %s %s"; + } + }, + { //a=setup:actpass + name: 'setup', + reg: /^setup:(\w*)/, + format: "setup:%s" + }, + { //a=mid:1 + name: 'mid', + reg: /^mid:([^\s]*)/, + format: "mid:%s" + }, + { //a=msid:0c8b064d-d807-43b4-b434-f92a889d8587 98178685-d409-46e0-8e16-7ef0db0db64a + name: 'msid', + reg: /^msid:(.*)/, + format: "msid:%s" + }, + { //a=ptime:20 + name: 'ptime', + reg: /^ptime:(\d*)/, + format: "ptime:%d" + }, + { //a=maxptime:60 + name: 'maxptime', + reg: /^maxptime:(\d*)/, + format: "maxptime:%d" + }, + { //a=sendrecv + name: 'direction', + reg: /^(sendrecv|recvonly|sendonly|inactive)/ + }, + { //a=ice-lite + name: 'icelite', + reg: /^(ice-lite)/ + }, + { //a=ice-ufrag:F7gI + name: 'iceUfrag', + reg: /^ice-ufrag:(\S*)/, + format: "ice-ufrag:%s" + }, + { //a=ice-pwd:x9cml/YzichV2+XlhiMu8g + name: 'icePwd', + reg: /^ice-pwd:(\S*)/, + format: "ice-pwd:%s" + }, + { //a=fingerprint:SHA-1 00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33 + name: 'fingerprint', + reg: /^fingerprint:(\S*) (\S*)/, + names: ['type', 'hash'], + format: "fingerprint:%s %s" + }, + { + //a=candidate:0 1 UDP 2113667327 203.0.113.1 54400 typ host + //a=candidate:1162875081 1 udp 2113937151 192.168.34.75 60017 typ host generation 0 + //a=candidate:3289912957 2 udp 1845501695 193.84.77.194 60017 typ srflx raddr 192.168.34.75 rport 60017 generation 0 + push:'candidates', + reg: /^candidate:(\S*) (\d*) (\S*) (\d*) (\S*) (\d*) typ (\S*)(?: raddr (\S*) rport (\d*))?(?: generation (\d*))?/, + names: ['foundation', 'component', 'transport', 'priority', 'ip', 'port', 'type', 'raddr', 'rport', 'generation'], + format: function (o) { + var str = "candidate:%s %d %s %d %s %d typ %s"; + // NB: candidate has two optional chunks, so %void middle one if it's missing + str += (o.raddr != null) ? " raddr %s rport %d" : "%v%v"; + if (o.generation != null) { + str += " generation %d"; + } + return str; + } + }, + { //a=end-of-candidates (keep after the candidates line for readability) + name: 'endOfCandidates', + reg: /^(end-of-candidates)/ + }, + { //a=remote-candidates:1 203.0.113.1 54400 2 203.0.113.1 54401 ... + name: 'remoteCandidates', + reg: /^remote-candidates:(.*)/, + format: "remote-candidates:%s" + }, + { //a=ice-options:google-ice + name: 'iceOptions', + reg: /^ice-options:(\S*)/, + format: "ice-options:%s" + }, + { //a=ssrc:2566107569 cname:t9YU8M1UxTF8Y1A1 + push: "ssrcs", + reg: /^ssrc:(\d*) ([\w_]*):(.*)/, + names: ['id', 'attribute', 'value'], + format: "ssrc:%d %s:%s" + }, + { //a=ssrc-group:FEC 1 2 + push: "ssrcGroups", + reg: /^ssrc-group:(\w*) (.*)/, + names: ['semantics', 'ssrcs'], + format: "ssrc-group:%s %s" + }, + { //a=msid-semantic: WMS Jvlam5X3SX1OP6pn20zWogvaKJz5Hjf9OnlV + name: "msidSemantic", + reg: /^msid-semantic:\s?(\w*) (\S*)/, + names: ['semantic', 'token'], + format: "msid-semantic: %s %s" // space after ":" is not accidental + }, + { //a=group:BUNDLE audio video + push: 'groups', + reg: /^group:(\w*) (.*)/, + names: ['type', 'mids'], + format: "group:%s %s" + }, + { //a=rtcp-mux + name: 'rtcpMux', + reg: /^(rtcp-mux)/ + }, + { //a=rtcp-rsize + name: 'rtcpRsize', + reg: /^(rtcp-rsize)/ + }, + { // any a= that we don't understand is kepts verbatim on media.invalid + push: 'invalid', + names: ["value"] + } + ] +}; + +// set sensible defaults to avoid polluting the grammar with boring details +Object.keys(grammar).forEach(function (key) { + var objs = grammar[key]; + objs.forEach(function (obj) { + if (!obj.reg) { + obj.reg = /(.*)/; + } + if (!obj.format) { + obj.format = "%s"; + } + }); +}); + +},{}],75:[function(require,module,exports){ arguments[4][71][0].apply(exports,arguments) -},{"./grammar":74,"dup":71}],78:[function(require,module,exports){ -arguments[4][68][0].apply(exports,arguments) -},{"dup":68}],79:[function(require,module,exports){ -arguments[4][69][0].apply(exports,arguments) -},{"./parser":80,"./writer":81,"dup":69}],80:[function(require,module,exports){ -arguments[4][70][0].apply(exports,arguments) -},{"./grammar":78,"dup":70}],81:[function(require,module,exports){ -arguments[4][71][0].apply(exports,arguments) -},{"./grammar":78,"dup":71}],82:[function(require,module,exports){ +},{"./parser":76,"./writer":77,"dup":71}],76:[function(require,module,exports){ +var toIntIfInt = function (v) { + return String(Number(v)) === v ? Number(v) : v; +}; + +var attachProperties = function (match, location, names, rawName) { + if (rawName && !names) { + location[rawName] = toIntIfInt(match[1]); + } + else { + for (var i = 0; i < names.length; i += 1) { + if (match[i+1] != null) { + location[names[i]] = toIntIfInt(match[i+1]); + } + } + } +}; + +var parseReg = function (obj, location, content) { + var needsBlank = obj.name && obj.names; + if (obj.push && !location[obj.push]) { + location[obj.push] = []; + } + else if (needsBlank && !location[obj.name]) { + location[obj.name] = {}; + } + var keyLocation = obj.push ? + {} : // blank object that will be pushed + needsBlank ? location[obj.name] : location; // otherwise, named location or root + + attachProperties(content.match(obj.reg), keyLocation, obj.names, obj.name); + + if (obj.push) { + location[obj.push].push(keyLocation); + } +}; + +var grammar = require('./grammar'); +var validLine = RegExp.prototype.test.bind(/^([a-z])=(.*)/); + +exports.parse = function (sdp) { + var session = {} + , media = [] + , location = session; // points at where properties go under (one of the above) + + // parse lines we understand + sdp.split(/(\r\n|\r|\n)/).filter(validLine).forEach(function (l) { + var type = l[0]; + var content = l.slice(2); + if (type === 'm') { + media.push({rtp: [], fmtp: []}); + location = media[media.length-1]; // point at latest media line + } + + for (var j = 0; j < (grammar[type] || []).length; j += 1) { + var obj = grammar[type][j]; + if (obj.reg.test(content)) { + return parseReg(obj, location, content); + } + } + }); + + session.media = media; // link it up + return session; +}; + +var fmtpReducer = function (acc, expr) { + var s = expr.split('='); + if (s.length === 2) { + acc[s[0]] = toIntIfInt(s[1]); + } + return acc; +}; + +exports.parseFmtpConfig = function (str) { + return str.split(/\;\s?/).reduce(fmtpReducer, {}); +}; + +exports.parsePayloads = function (str) { + return str.split(' ').map(Number); +}; + +exports.parseRemoteCandidates = function (str) { + var candidates = []; + var parts = str.split(' ').map(toIntIfInt); + for (var i = 0; i < parts.length; i += 3) { + candidates.push({ + component: parts[i], + ip: parts[i + 1], + port: parts[i + 2] + }); + } + return candidates; +}; + +},{"./grammar":74}],77:[function(require,module,exports){ +arguments[4][73][0].apply(exports,arguments) +},{"./grammar":74,"dup":73}],78:[function(require,module,exports){ var MediaStreamType = { VIDEO_TYPE: "Video", AUDIO_TYPE: "Audio" }; module.exports = MediaStreamType; -},{}],83:[function(require,module,exports){ +},{}],79:[function(require,module,exports){ var RTCEvents = { RTC_READY: "rtc.ready", DATA_CHANNEL_OPEN: "rtc.data_channel_open", @@ -22562,7 +22909,7 @@ var RTCEvents = { }; module.exports = RTCEvents; -},{}],84:[function(require,module,exports){ +},{}],80:[function(require,module,exports){ var Resolutions = { "1080": { width: 1920, @@ -22616,7 +22963,7 @@ var Resolutions = { } }; module.exports = Resolutions; -},{}],85:[function(require,module,exports){ +},{}],81:[function(require,module,exports){ var AuthenticationEvents = { /** * Event callback arguments: @@ -22630,7 +22977,7 @@ var AuthenticationEvents = { }; module.exports = AuthenticationEvents; -},{}],86:[function(require,module,exports){ +},{}],82:[function(require,module,exports){ var DesktopSharingEventTypes = { INIT: "ds.init", @@ -22646,7 +22993,7 @@ var DesktopSharingEventTypes = { module.exports = DesktopSharingEventTypes; -},{}],87:[function(require,module,exports){ +},{}],83:[function(require,module,exports){ module.exports = { /** * An event carrying connection statistics. @@ -22662,12 +23009,12 @@ module.exports = { STOP: "statistics.stop" }; -},{}],88:[function(require,module,exports){ +},{}],84:[function(require,module,exports){ var Constants = { LOCAL_JID: 'local' }; module.exports = Constants; -},{}],89:[function(require,module,exports){ +},{}],85:[function(require,module,exports){ var XMPPEvents = { // Designates an event indicating that the connection to the XMPP server // failed. diff --git a/modules/UI/UI.js b/modules/UI/UI.js index 96e3b96b7..f2936a1ad 100644 --- a/modules/UI/UI.js +++ b/modules/UI/UI.js @@ -1,31 +1,33 @@ -/* global Strophe, APP, $, config, interfaceConfig, toastr */ +/* global APP, $, config, interfaceConfig, toastr */ /* jshint -W101 */ var UI = {}; -var VideoLayout = require("./videolayout/VideoLayout"); -var AudioLevels = require("./audio_levels/AudioLevels"); +import AudioLevels from './audio_levels/AudioLevels'; +import Chat from "./side_pannels/chat/Chat"; +import Toolbar from "./toolbars/Toolbar"; +import ToolbarToggler from "./toolbars/ToolbarToggler"; +import BottomToolbar from "./toolbars/BottomToolbar"; +import ContactList from "./side_pannels/contactlist/ContactList"; +import Avatar from "./avatar/Avatar"; +import PanelToggler from "./side_pannels/SidePanelToggler"; +import UIUtil from "./util/UIUtil"; +import UIEvents from "../../service/UI/UIEvents"; + +import VideoLayout from "./videolayout/VideoLayout"; + var Prezi = require("./prezi/Prezi"); var Etherpad = require("./etherpad/Etherpad"); -var Chat = require("./side_pannels/chat/Chat"); -var Toolbar = require("./toolbars/Toolbar"); -var ToolbarToggler = require("./toolbars/ToolbarToggler"); -var BottomToolbar = require("./toolbars/BottomToolbar"); -var ContactList = require("./side_pannels/contactlist/ContactList"); -var Avatar = require("./avatar/Avatar"); var EventEmitter = require("events"); var SettingsMenu = require("./side_pannels/settings/SettingsMenu"); var Settings = require("./../settings/Settings"); -var PanelToggler = require("./side_pannels/SidePanelToggler"); UI.messageHandler = require("./util/MessageHandler"); var messageHandler = UI.messageHandler; var Authentication = require("./authentication/Authentication"); -var UIUtil = require("./util/UIUtil"); var JitsiPopover = require("./util/JitsiPopover"); var CQEvents = require("../../service/connectionquality/CQEvents"); var DesktopSharingEventTypes = require("../../service/desktopsharing/DesktopSharingEventTypes"); var StatisticsEvents = require("../../service/statistics/Events"); -var UIEvents = require("../../service/UI/UIEvents"); var Feedback = require("./Feedback"); var eventEmitter = new EventEmitter(); @@ -352,7 +354,7 @@ function initEtherpad(name) { Etherpad.init(name); } -UI.addUser = function (jid, id, displayName) { +UI.addUser = function (id, displayName) { messageHandler.notify( displayName,'notify.somebody', 'connected', 'notify.connected' ); @@ -362,16 +364,14 @@ UI.addUser = function (jid, id, displayName) { UIUtil.playSoundNotification('userJoined'); // Configure avatar - UI.setUserAvatar(jid, id); + UI.setUserAvatar(id, displayName); // Add Peer's container - VideoLayout.ensurePeerContainerExists(jid); + VideoLayout.ensurePeerContainerExists(id); }; -UI.removeUser = function (jid) { - console.log('left.muc', jid); - var displayName = $('#participant_' + Strophe.getResourceFromJid(jid) + - '>.displayname').html(); +UI.removeUser = function (id, displayName) { + console.log('left.muc', id); messageHandler.notify(displayName,'notify.somebody', 'disconnected', 'notify.disconnected'); @@ -380,9 +380,9 @@ UI.removeUser = function (jid) { UIUtil.playSoundNotification('userLeft'); } - ContactList.removeContact(jid); + ContactList.removeContact(id); - VideoLayout.participantLeft(jid); + VideoLayout.participantLeft(id); }; function onMucPresenceStatus(jid, info) { @@ -601,7 +601,7 @@ UI.handleLastNEndpoints = function (ids) { UI.setAudioLevel = function (id, lvl) { AudioLevels.updateAudioLevel( - id, lvl, VideoLayout.getLargeVideoResource() + id, lvl, VideoLayout.getLargeVideoId() ); }; diff --git a/modules/UI/audio_levels/AudioLevels.js b/modules/UI/audio_levels/AudioLevels.js index a158cd79a..2e99fe94d 100644 --- a/modules/UI/audio_levels/AudioLevels.js +++ b/modules/UI/audio_levels/AudioLevels.js @@ -248,8 +248,7 @@ const AudioLevels = { // Fill the shape. ASDrawContext.fill(); - }, - + } }; export default AudioLevels; diff --git a/modules/UI/prezi/Prezi.js b/modules/UI/prezi/Prezi.js index cd5ce88cf..4c2b6c61b 100644 --- a/modules/UI/prezi/Prezi.js +++ b/modules/UI/prezi/Prezi.js @@ -1,5 +1,8 @@ -var UIUtil = require("../util/UIUtil"); -var VideoLayout = require("../videolayout/VideoLayout"); +/* global $, APP */ +/* jshint -W101 */ +import UIUtil from "../util/UIUtil"; +import VideoLayout from "../videolayout/VideoLayout"; + var messageHandler = require("../util/MessageHandler"); var PreziPlayer = require("./PreziPlayer"); diff --git a/modules/UI/util/UIUtil.js b/modules/UI/util/UIUtil.js index 0371a896e..fec900d2a 100644 --- a/modules/UI/util/UIUtil.js +++ b/modules/UI/util/UIUtil.js @@ -1,13 +1,15 @@ /* global $, config, interfaceConfig */ + +import PanelToggler from "../side_pannels/SidePanelToggler"; + /** * Created by hristo on 12/22/14. */ -var UIUtil = module.exports = { + var UIUtil = { /** * Returns the available video width. */ getAvailableVideoWidth: function (isVisible) { - var PanelToggler = require("../side_pannels/SidePanelToggler"); if(typeof isVisible === "undefined" || isVisible === null) isVisible = PanelToggler.isVisible(); var rightPanelWidth @@ -114,3 +116,5 @@ var UIUtil = module.exports = { $(selector).hide(); } }; + +export default UIUtil; diff --git a/modules/UI/videolayout/ConnectionIndicator.js b/modules/UI/videolayout/ConnectionIndicator.js index 4ee8ae5de..80b5a2fd6 100644 --- a/modules/UI/videolayout/ConnectionIndicator.js +++ b/modules/UI/videolayout/ConnectionIndicator.js @@ -1,13 +1,13 @@ /* global APP, $ */ /* jshint -W101 */ -var JitsiPopover = require("../util/JitsiPopover"); +import JitsiPopover from "../util/JitsiPopover"; /** * Constructs new connection indicator. * @param videoContainer the video container associated with the indicator. * @constructor */ -function ConnectionIndicator(videoContainer, jid) { +function ConnectionIndicator(videoContainer, id) { this.videoContainer = videoContainer; this.bandwidth = null; this.packetLoss = null; @@ -16,7 +16,7 @@ function ConnectionIndicator(videoContainer, jid) { this.resolution = null; this.transport = []; this.popover = null; - this.jid = jid; + this.id = id; this.create(); } @@ -87,7 +87,7 @@ ConnectionIndicator.prototype.generateText = function () { } var resolutionValue = null; - if(this.resolution && this.jid) { + if(this.resolution && this.id) { var keys = Object.keys(this.resolution); for(var ssrc in this.resolution) { // skip resolutions for ssrc that don't have this info @@ -99,7 +99,7 @@ ConnectionIndicator.prototype.generateText = function () { } } - if(this.jid === null) { + if(this.id === null) { resolution = ""; if(this.resolution === null || !Object.keys(this.resolution) || Object.keys(this.resolution).length === 0) { @@ -144,8 +144,8 @@ ConnectionIndicator.prototype.generateText = function () { if(this.videoContainer.videoSpanId == "localVideoContainer") { result += "
" + translate("connectionindicator." + (this.showMoreValue ? "less" : "more")) + @@ -385,4 +385,4 @@ ConnectionIndicator.prototype.hideIndicator = function () { this.popover.forceHide(); }; -module.exports = ConnectionIndicator; \ No newline at end of file +export default ConnectionIndicator; diff --git a/modules/UI/videolayout/LargeVideo.js b/modules/UI/videolayout/LargeVideo.js index b32569e48..dafb921ea 100644 --- a/modules/UI/videolayout/LargeVideo.js +++ b/modules/UI/videolayout/LargeVideo.js @@ -1,11 +1,11 @@ -/* global $, APP, Strophe, interfaceConfig */ +/* global $, APP, interfaceConfig */ /* jshint -W101 */ -var Avatar = require("../avatar/Avatar"); +import Avatar from "../avatar/Avatar"; +import ToolbarToggler from "../toolbars/ToolbarToggler"; +import UIUtil from "../util/UIUtil"; +import UIEvents from "../../../service/UI/UIEvents"; + var RTCBrowserType = require("../../RTC/RTCBrowserType"); -var UIUtil = require("../util/UIUtil"); -var UIEvents = require("../../../service/UI/UIEvents"); -var xmpp = require("../../xmpp/xmpp"); -var ToolbarToggler = require("../toolbars/ToolbarToggler"); // FIXME: With Temasys we have to re-select everytime //var video = $('#largeVideo'); @@ -37,22 +37,22 @@ var state = "video"; * @param state the state. * @returns {JQuery|*|jQuery|HTMLElement} the container. */ -function getContainerByState(state) -{ +function getContainerByState(state) { var selector = null; - switch (state) - { - case "video": - selector = "#largeVideoWrapper"; - break; - case "etherpad": - selector = "#etherpad>iframe"; - break; - case "prezi": - selector = "#presentation>iframe"; - break; + switch (state) { + case "video": + selector = "#largeVideoWrapper"; + break; + case "etherpad": + selector = "#etherpad>iframe"; + break; + case "prezi": + selector = "#presentation>iframe"; + break; + default: + return null; } - return (selector !== null)? $(selector) : null; + return $(selector); } /** @@ -72,24 +72,25 @@ function positionVideo(video, animate) { if (animate) { video.animate({ - width: width, - height: height, - top: verticalIndent, - bottom: verticalIndent, - left: horizontalIndent, - right: horizontalIndent - }, - { - queue: false, - duration: 500 - }); + width: width, + height: height, + top: verticalIndent, + bottom: verticalIndent, + left: horizontalIndent, + right: horizontalIndent + }, { + queue: false, + duration: 500 + }); } else { video.width(width); video.height(height); - video.css({ top: verticalIndent + 'px', - bottom: verticalIndent + 'px', - left: horizontalIndent + 'px', - right: horizontalIndent + 'px'}); + video.css({ + top: verticalIndent, + bottom: verticalIndent, + left: horizontalIndent, + right: horizontalIndent + }); } } @@ -237,16 +238,13 @@ function getCameraVideoSize(videoWidth, /** * Updates the src of the active speaker avatar - * @param jid of the current active speaker */ function updateActiveSpeakerAvatarSrc() { - var avatar = $("#activeSpeakerAvatar")[0]; - var jid = currentSmallVideo.peerJid; - var url = Avatar.getActiveSpeakerUrl(jid); - if (avatar.src === url) - return; - if (jid) { - avatar.src = url; + let avatar = $("#activeSpeakerAvatar"); + let id = currentSmallVideo.id; + let url = Avatar.getActiveSpeakerUrl(id); + if (id && avatar.attr('src') !== url) { + avatar.attr('src', url); currentSmallVideo.showAvatar(); } } @@ -263,13 +261,15 @@ function changeVideo(isVisible) { } updateActiveSpeakerAvatarSrc(); - var largeVideoElement = $('#largeVideo')[0]; + let largeVideoElement = $('#largeVideo'); - APP.RTC.setVideoSrc(largeVideoElement, currentSmallVideo.getSrc()); + currentSmallVideo.stream.attach(largeVideoElement); - var flipX = currentSmallVideo.flipX; + let flipX = currentSmallVideo.flipX; - largeVideoElement.style.transform = flipX ? "scaleX(-1)" : "none"; + largeVideoElement.css({ + transform: flipX ? "scaleX(-1)" : "none" + }); LargeVideo.updateVideoSizeAndPosition(currentSmallVideo.getVideoType()); @@ -369,40 +369,35 @@ var LargeVideo = { /** * Returns true if the user is currently displayed on large video. */ - isCurrentlyOnLarge: function (resourceJid) { - return currentSmallVideo && resourceJid && - currentSmallVideo.getResourceJid() === resourceJid; + isCurrentlyOnLarge: function (id) { + return id && id === this.getId(); }, /** * Updates the large video with the given new video source. */ - updateLargeVideo: function (resourceJid, forceUpdate) { - if(!isEnabled) + updateLargeVideo: function (id, forceUpdate) { + if(!isEnabled) { return; - var newSmallVideo = this.VideoLayout.getSmallVideo(resourceJid); - console.info('hover in ' + resourceJid + ', video: ', newSmallVideo); + } + let newSmallVideo = this.VideoLayout.getSmallVideo(id); + console.info(`hover in ${id} , video: `, newSmallVideo); if (!newSmallVideo) { - console.error("Small video not found for: " + resourceJid); + console.error("Small video not found for: " + id); return; } - if (!LargeVideo.isCurrentlyOnLarge(resourceJid) || forceUpdate) { + if (!LargeVideo.isCurrentlyOnLarge(id) || forceUpdate) { $('#activeSpeaker').css('visibility', 'hidden'); - var oldSmallVideo = null; - if (currentSmallVideo) { - oldSmallVideo = currentSmallVideo; - } + let oldId = this.getId(); + currentSmallVideo = newSmallVideo; - var oldJid = null; - if (oldSmallVideo) - oldJid = oldSmallVideo.peerJid; - if (oldJid !== resourceJid) { - // we want the notification to trigger even if userJid is undefined, + if (oldId !== id) { + // we want the notification to trigger even if id is undefined, // or null. - this.eventEmitter.emit(UIEvents.SELECTED_ENDPOINT, resourceJid); + this.eventEmitter.emit(UIEvents.SELECTED_ENDPOINT, id); } // We are doing fadeOut/fadeIn animations on parent div which wraps // largeVideo, because when Temasys plugin is in use it replaces @@ -443,11 +438,10 @@ var LargeVideo = { currentSmallVideo.enableDominantSpeaker(false); } }, - onVideoTypeChanged: function (resourceJid, newVideoType) { + onVideoTypeChanged: function (id, newVideoType) { if (!isEnabled) return; - if (LargeVideo.isCurrentlyOnLarge(resourceJid)) - { + if (LargeVideo.isCurrentlyOnLarge(id)) { LargeVideo.updateVideoSizeAndPosition(newVideoType); this.position(null, null, null, null, true); @@ -562,22 +556,23 @@ var LargeVideo = { getVideoPosition = isDesktop ? getDesktopVideoPosition : getCameraVideoPosition; }, - getResourceJid: function () { - return currentSmallVideo ? currentSmallVideo.getResourceJid() : null; + getId: function () { + return currentSmallVideo ? currentSmallVideo.id : null; }, - updateAvatar: function (resourceJid) { - if(!isEnabled) + updateAvatar: function (id) { + if (!isEnabled) { return; - if (resourceJid === this.getResourceJid()) { + } + if (id === this.getId()) { updateActiveSpeakerAvatarSrc(); } }, - showAvatar: function (resourceJid, show) { - if (!isEnabled) + showAvatar: function (id, show) { + if (!isEnabled) { return; - if (this.getResourceJid() === resourceJid && state === "video") { - $("#largeVideoWrapper") - .css("visibility", show ? "hidden" : "visible"); + } + if (this.getId() === id && state === "video") { + $("#largeVideoWrapper").css("visibility", show ? "hidden" : "visible"); $('#activeSpeaker').css("visibility", show ? "visible" : "hidden"); return true; } @@ -721,4 +716,4 @@ var LargeVideo = { } }; -module.exports = LargeVideo; \ No newline at end of file +export default LargeVideo; diff --git a/modules/UI/videolayout/LocalVideo.js b/modules/UI/videolayout/LocalVideo.js index dfc97bb6e..0dc8f95bf 100644 --- a/modules/UI/videolayout/LocalVideo.js +++ b/modules/UI/videolayout/LocalVideo.js @@ -1,8 +1,9 @@ /* global $, interfaceConfig, APP */ -var SmallVideo = require("./SmallVideo"); -var ConnectionIndicator = require("./ConnectionIndicator"); -var UIUtil = require("../util/UIUtil"); -var UIEvents = require("../../../service/UI/UIEvents"); +import ConnectionIndicator from "./ConnectionIndicator"; +import UIUtil from "../util/UIUtil"; +import UIEvents from "../../../service/UI/UIEvents"; +import SmallVideo from "./SmallVideo"; + var LargeVideo = require("./LargeVideo"); var RTCBrowserType = require("../../RTC/RTCBrowserType"); @@ -13,7 +14,6 @@ function LocalVideo(VideoLayout, emitter) { this.VideoLayout = VideoLayout; this.flipX = true; this.isLocal = true; - this.peerJid = null; this.emitter = emitter; } @@ -143,32 +143,25 @@ LocalVideo.prototype.createConnectionIndicator = function() { this.connectionIndicator = new ConnectionIndicator(this, null); }; -LocalVideo.prototype.changeVideo = function (stream, isMuted) { - var self = this; +LocalVideo.prototype.changeVideo = function (stream) { + this.stream = stream; - function localVideoClick(event) { + let localVideoClick = (event) => { // FIXME: with Temasys plugin event arg is not an event, but // the clicked object itself, so we have to skip this call if (event.stopPropagation) { event.stopPropagation(); } - self.VideoLayout.handleVideoThumbClicked( - true, - APP.xmpp.myResource()); - } + this.VideoLayout.handleVideoThumbClicked(true, this.id); + }; - var localVideoContainerSelector = $('#localVideoContainer'); + let localVideoContainerSelector = $('#localVideoContainer'); localVideoContainerSelector.off('click'); localVideoContainerSelector.on('click', localVideoClick); - if(isMuted) { - APP.UI.setVideoMute(true); - return; - } this.flipX = stream.videoType != "screen"; - var localVideo = document.createElement('video'); - localVideo.id = 'localVideo_' + - APP.RTC.getStreamID(stream.getOriginalStream()); + let localVideo = document.createElement('video'); + localVideo.id = 'localVideo_' + stream.getId(); if (!RTCBrowserType.isIExplorer()) { localVideo.autoplay = true; localVideo.volume = 0; // is it required if audio is separated ? @@ -192,7 +185,10 @@ LocalVideo.prototype.changeVideo = function (stream, isMuted) { } // Attach WebRTC stream - APP.RTC.attachMediaStream(localVideoSelector, stream.getOriginalStream()); + stream.attach(localVideoSelector); + + // FIXME handle + return; // Add stream ended handler APP.RTC.addMediaStreamInactiveHandler( @@ -201,20 +197,12 @@ LocalVideo.prototype.changeVideo = function (stream, isMuted) { // because