From 3ec04d5a954bcb3f9a1630dfa757af0f85f6807c Mon Sep 17 00:00:00 2001 From: hristoterezov Date: Tue, 29 Dec 2015 16:30:50 -0600 Subject: [PATCH] Implements desktop sharing. --- .jshintignore | 2 + app.js | 92 +- lib-jitsi-meet.js => libs/lib-jitsi-meet.js | 2153 ++++++++++--------- modules/UI/UI.js | 11 +- modules/UI/util/UIUtil.js | 2 +- modules/UI/videolayout/LargeVideo.js | 2 +- modules/UI/videolayout/LocalVideo.js | 6 +- modules/UI/videolayout/RemoteVideo.js | 3 +- modules/UI/videolayout/SmallVideo.js | 7 +- modules/desktopsharing/ScreenObtainer.js | 407 ---- modules/desktopsharing/desktopsharing.js | 74 +- 11 files changed, 1235 insertions(+), 1524 deletions(-) rename lib-jitsi-meet.js => libs/lib-jitsi-meet.js (97%) delete mode 100644 modules/desktopsharing/ScreenObtainer.js diff --git a/.jshintignore b/.jshintignore index 3d4784468..6224afaa9 100644 --- a/.jshintignore +++ b/.jshintignore @@ -7,3 +7,5 @@ lib-jitsi-meet.js modules/xmpp/strophe.emuc.js modules/UI/prezi/Prezi.js modules/RTC/adapter.screenshare.js +modules/statistics/* +modules/UI/videolayout/* diff --git a/app.js b/app.js index 9c30317b6..3d1f7e687 100644 --- a/app.js +++ b/app.js @@ -23,9 +23,24 @@ import AuthHandler from './modules/AuthHandler'; import createRoomLocker from './modules/RoomLocker'; +const DesktopSharingEventTypes = + require("./service/desktopsharing/DesktopSharingEventTypes"); + +const ConnectionEvents = JitsiMeetJS.events.connection; +const ConnectionErrors = JitsiMeetJS.errors.connection; + +const ConferenceEvents = JitsiMeetJS.events.conference; +const ConferenceErrors = JitsiMeetJS.errors.conference; + +const TrackEvents = JitsiMeetJS.events.track; +const TrackErrors = JitsiMeetJS.errors.track; + +let localVideo, localAudio; + const Commands = { CONNECTION_QUALITY: "connectionQuality", - EMAIL: "email" + EMAIL: "email", + VIDEO_TYPE: "videoType" }; function buildRoomName () { @@ -104,18 +119,25 @@ const APP = { } }; - -const ConnectionEvents = JitsiMeetJS.events.connection; -const ConnectionErrors = JitsiMeetJS.errors.connection; - -const ConferenceEvents = JitsiMeetJS.events.conference; -const ConferenceErrors = JitsiMeetJS.errors.conference; function initConference(localTracks, connection) { let room = connection.initJitsiConference(APP.conference.roomName, { openSctp: config.openSctp, disableAudioLevels: config.disableAudioLevels }); + const addTrack = (track) => { + room.addTrack(track); + if(track.getType() === "audio") + return; + room.removeCommand(Commands.VIDEO_TYPE); + room.sendCommand(Commands.VIDEO_TYPE, { + value: track.videoType, + attributes: { + xmlns: 'http://jitsi.org/jitmeet/video' + } + }); + }; + APP.conference.localId = room.myUserId(); Object.defineProperty(APP.conference, "membersCount", { get: function () { @@ -129,6 +151,40 @@ function initConference(localTracks, connection) { APP.conference.listMembersIds = function () { return room.getParticipants().map(p => p.getId()); }; + /** + * Creates video track (desktop or camera). + * @param type "camera" or "video" + * @param endedHandler onended function + * @returns Promise + */ + APP.conference.createVideoTrack = (type, endedHandler) => { + return JitsiMeetJS.createLocalTracks({ + devices: [type], resolution: config.resolution + }).then((tracks) => { + tracks[0].on(TrackEvents.TRACK_STOPPED, endedHandler); + return tracks; + }); + }; + + APP.conference.changeLocalVideo = (track, callback) => { + const localCallback = (newTrack) => { + if (newTrack.isLocal() && newTrack === localVideo) { + if(localVideo.isMuted() && + localVideo.videoType !== track.videoType) { + localVideo.mute(); + } + callback(); + room.off(ConferenceEvents.TRACK_ADDED, localCallback); + } + }; + + room.on(ConferenceEvents.TRACK_ADDED, localCallback); + + localVideo.stop(); + localVideo = track; + addTrack(track); + APP.UI.addLocalStream(track); + }; function getDisplayName(id) { if (APP.conference.isLocalId(id)) { @@ -144,7 +200,13 @@ function initConference(localTracks, connection) { // add local streams when joined to the conference room.on(ConferenceEvents.CONFERENCE_JOINED, function () { localTracks.forEach(function (track) { - room.addTrack(track); + if(track.getType() === "audio") { + localAudio = track; + } + else if (track.getType() === "video") { + localVideo = track; + } + addTrack(track); APP.UI.addLocalStream(track); }); @@ -293,6 +355,10 @@ function initConference(localTracks, connection) { } ); + room.addCommandListener(Commands.VIDEO_TYPE, (data, from) => { + APP.UI.onPeerVideoTypeChanged(from, data.value); + }); + // share email with other users function sendEmail(email) { @@ -520,7 +586,7 @@ function init() { JitsiMeetJS.setLogLevel(JitsiMeetJS.logLevels.TRACE); - JitsiMeetJS.init().then(function () { + JitsiMeetJS.init(config).then(function () { return Promise.all([createLocalTracks(), connect()]); }).then(function ([tracks, connection]) { console.log('initialized with %s local tracks', tracks.length); @@ -533,7 +599,13 @@ function init() { APP.settings.setLanguage(language); }); - APP.desktopsharing.init(); + APP.desktopsharing.addListener( + DesktopSharingEventTypes.NEW_STREAM_CREATED, + (stream, callback) => { + APP.conference.changeLocalVideo(stream, + callback); + }); + APP.desktopsharing.init(JitsiMeetJS.isDesktopSharingEnabled()); APP.statistics.start(); APP.connectionquality.init(); APP.keyboardshortcut.init(); diff --git a/lib-jitsi-meet.js b/libs/lib-jitsi-meet.js similarity index 97% rename from lib-jitsi-meet.js rename to libs/lib-jitsi-meet.js index a0a1e0150..251e21076 100644 --- a/lib-jitsi-meet.js +++ b/libs/lib-jitsi-meet.js @@ -54,7 +54,7 @@ function JitsiConference(options) { */ JitsiConference.prototype.join = function (password) { if(this.room) - this.room.join(password, this.connection.tokenPassword); + this.room.join(password); }; /** @@ -540,6 +540,94 @@ JitsiConference.prototype.sendTones = function (tones, duration, pause) { this.dtmfManager.sendTones(tones, duration, pause); }; +/** + * Returns true if the recording is supproted and false if not. + */ +JitsiConference.prototype.isRecordingSupported = function () { + if(this.room) + return this.room.isRecordingSupported(); + return false; +}; + +/** + * Returns null if the recording is not supported, "on" if the recording started + * and "off" if the recording is not started. + */ +JitsiConference.prototype.getRecordingState = function () { + if(this.room) + return this.room.getRecordingState(); + return "off"; +} + +/** + * Returns the url of the recorded video. + */ +JitsiConference.prototype.getRecordingURL = function () { + if(this.room) + return this.room.getRecordingURL(); + return null; +} + +/** + * Starts/stops the recording + * @param token a token for authentication. + */ +JitsiConference.prototype.toggleRecording = function (token, followEntity) { + if(this.room) + return this.room.toggleRecording(token, followEntity); + return new Promise(function(resolve, reject){ + reject(new Error("The conference is not created yet!"))}); +} + +/** + * Dials a number. + * @param number the number + */ +JitsiConference.prototype.dial = function (number) { + if(this.room) + return this.room.dial(number); + return new Promise(function(resolve, reject){ + reject(new Error("The conference is not created yet!"))}); +} + +/** + * Hangup an existing call + */ +JitsiConference.prototype.hangup = function () { + if(this.room) + return this.room.hangup(); + return new Promise(function(resolve, reject){ + reject(new Error("The conference is not created yet!"))}); +} + +/** + * Returns the phone number for joining the conference. + */ +JitsiConference.prototype.getPhoneNumber = function () { + if(this.room) + return this.room.getPhoneNumber(); + return null; +} + +/** + * Returns the pin for joining the conference with phone. + */ +JitsiConference.prototype.getPhonePin = function () { + if(this.room) + return this.room.getPhonePin(); + return null; +} + +/** + * Returns the connection state for the current room. Its ice connection state + * for its session. + */ +JitsiConference.prototype.getConnectionState = function () { + if(this.room) + return this.room.getConnectionState(); + return null; +} + /** * Setups the listeners needed for the conference. * @param conference the conference @@ -598,6 +686,17 @@ function setupListeners(conference) { conference.eventEmitter.emit(JitsiConferenceEvents.CONNECTION_INTERRUPTED); }); + conference.room.addListener(XMPPEvents.RECORDING_STATE_CHANGED, + function () { + conference.eventEmitter.emit( + JitsiConferenceEvents.RECORDING_STATE_CHANGED); + }); + + conference.room.addListener(XMPPEvents.PHONE_NUMBER_CHANGED, function () { + conference.eventEmitter.emit( + JitsiConferenceEvents.PHONE_NUMBER_CHANGED); + }); + conference.room.addListener(XMPPEvents.CONNECTION_RESTORED, function () { conference.eventEmitter.emit(JitsiConferenceEvents.CONNECTION_RESTORED); }); @@ -660,7 +759,7 @@ function setupListeners(conference) { module.exports = JitsiConference; }).call(this,"/JitsiConference.js") -},{"./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/authentication/AuthenticationEvents":81,"./service/xmpp/XMPPEvents":85,"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":80,"./service/authentication/AuthenticationEvents":83,"./service/xmpp/XMPPEvents":87,"events":44,"jitsi-meet-logger":48}],2:[function(require,module,exports){ /** * Enumeration with the errors for the conference. * @type {{string: string}} @@ -788,7 +887,15 @@ var JitsiConferenceEvents = { /** * Indicates that DTMF support changed. */ - DTMF_SUPPORT_CHANGED: "conference.dtmfSupportChanged" + DTMF_SUPPORT_CHANGED: "conference.dtmfSupportChanged", + /** + * Indicates that recording state changed. + */ + RECORDING_STATE_CHANGED: "conference.recordingStateChanged", + /** + * Indicates that phone number changed. + */ + PHONE_NUMBER_CHANGED: "conference.phoneNumberChanged" }; module.exports = JitsiConferenceEvents; @@ -796,34 +903,18 @@ module.exports = JitsiConferenceEvents; },{}],4:[function(require,module,exports){ var JitsiConference = require("./JitsiConference"); var XMPP = require("./modules/xmpp/xmpp"); -var RandomUtil = require("./modules/util/RandomUtil"); - -/** - * Utility method that generates user name based on random hex values. - * Eg. 12345678-1234-1234-12345678 - * @returns {string} - */ -function generateUserName() { - return RandomUtil.randomHexString(8) + "-" + RandomUtil.randomHexString(4) + - "-" + RandomUtil.randomHexString(4) + "-" + - RandomUtil.randomHexDigit(8); -} /** * Creates new connection object for the Jitsi Meet server side video conferencing service. Provides access to the * JitsiConference interface. * @param appID identification for the provider of Jitsi Meet video conferencing services. - * @param tokenPassword secret generated by the provider of Jitsi Meet video conferencing services. - * The token will be send to the provider from the Jitsi Meet server deployment for authorization of the current client. - * The format is: - * passwordToken = token + "_" + roomName + "_" + ts - * See doc/tokens.md for more info on how tokens are generated. + * @param token the JWT token used to authenticate with the server(optional) * @param options Object with properties / settings related to connection with the server. * @constructor */ -function JitsiConnection(appID, tokenPassword, options) { +function JitsiConnection(appID, token, options) { this.appID = appID; - this.tokenPassword = tokenPassword; + this.token = token; this.options = options; this.xmpp = new XMPP(options); this.conferences = {}; @@ -837,14 +928,6 @@ JitsiConnection.prototype.connect = function (options) { if(!options) options = {}; - // If we have token provided use it as a password and generate random username - if (this.tokenPassword) { - options.password = this.tokenPassword; - if (!options.id) { - options.id = generateUserName() + "@" + this.options.hosts.domain; - } - } - this.xmpp.connect(options.id, options.password); } @@ -894,7 +977,7 @@ JitsiConnection.prototype.removeEventListener = function (event, listener) { module.exports = JitsiConnection; -},{"./JitsiConference":1,"./modules/util/RandomUtil":25,"./modules/xmpp/xmpp":41}],5:[function(require,module,exports){ +},{"./JitsiConference":1,"./modules/xmpp/xmpp":42}],5:[function(require,module,exports){ /** * Enumeration with the errors for the connection. * @type {{string: string}} @@ -976,6 +1059,13 @@ var LibJitsiMeet = { init: function (options) { return RTC.init(options || {}); }, + /** + * Returns whether the desktop sharing is enabled or not. + * @returns {boolean} + */ + isDesktopSharingEnabled: function () { + return RTC.isDesktopSharingEnabled(); + }, setLogLevel: function (level) { Logger.setLogLevel(level); }, @@ -1040,7 +1130,7 @@ window.Promise = window.Promise || require("es6-promise").Promise; module.exports = LibJitsiMeet; -},{"./JitsiConferenceErrors":2,"./JitsiConferenceEvents":3,"./JitsiConnection":4,"./JitsiConnectionErrors":5,"./JitsiConnectionEvents":6,"./JitsiTrackErrors":9,"./JitsiTrackEvents":10,"./modules/RTC/RTC":16,"./modules/statistics/statistics":24,"es6-promise":45,"jitsi-meet-logger":47}],8:[function(require,module,exports){ +},{"./JitsiConferenceErrors":2,"./JitsiConferenceEvents":3,"./JitsiConnection":4,"./JitsiConnectionErrors":5,"./JitsiConnectionEvents":6,"./JitsiTrackErrors":9,"./JitsiTrackEvents":10,"./modules/RTC/RTC":16,"./modules/statistics/statistics":24,"es6-promise":46,"jitsi-meet-logger":48}],8:[function(require,module,exports){ /* global Strophe */ /** @@ -1261,7 +1351,7 @@ JitsiDTMFManager.prototype.sendTones = function (tones, duration, pause) { }; }).call(this,"/modules/DTMF/JitsiDTMFManager.js") -},{"jitsi-meet-logger":47}],12:[function(require,module,exports){ +},{"jitsi-meet-logger":48}],12:[function(require,module,exports){ (function (__filename){ /* global config, APP, Strophe */ @@ -1477,7 +1567,7 @@ module.exports = DataChannels; }).call(this,"/modules/RTC/DataChannels.js") -},{"../../service/RTC/RTCEvents":79,"jitsi-meet-logger":47}],13:[function(require,module,exports){ +},{"../../service/RTC/RTCEvents":80,"jitsi-meet-logger":48}],13:[function(require,module,exports){ var JitsiTrack = require("./JitsiTrack"); var RTCBrowserType = require("./RTCBrowserType"); var JitsiTrackEvents = require('../../JitsiTrackEvents'); @@ -1716,6 +1806,7 @@ module.exports = JitsiRemoteTrack; var RTCBrowserType = require("./RTCBrowserType"); var JitsiTrackEvents = require("../../JitsiTrackEvents"); var EventEmitter = require("events"); +var RTC = require("./RTCUtils"); /** * This implements 'onended' callback normally fired by WebRTC after the stream @@ -1906,13 +1997,21 @@ JitsiTrack.prototype.isScreenSharing = function(){ * Returns id of the track. * @returns {string} id of the track or null if this is fake track. */ -JitsiTrack.prototype.getId = function () { +JitsiTrack.prototype._getId = function () { var tracks = this.stream.getTracks(); if(!tracks || tracks.length === 0) return null; return tracks[0].id; }; +/** + * Returns id of the track. + * @returns {string} id of the track or null if this is fake track. + */ +JitsiTrack.prototype.getId = function () { + return RTC.getStreamID(this.stream); +}; + /** * Checks whether the MediaStream is avtive/not ended. * When there is no check for active we don't have information and so @@ -1966,7 +2065,7 @@ JitsiTrack.prototype.setAudioLevel = function (audioLevel) { module.exports = JitsiTrack; -},{"../../JitsiTrackEvents":10,"./RTCBrowserType":17,"./RTCUtils":18,"events":43}],16:[function(require,module,exports){ +},{"../../JitsiTrackEvents":10,"./RTCBrowserType":17,"./RTCUtils":18,"events":44}],16:[function(require,module,exports){ /* global APP */ var EventEmitter = require("events"); var RTCBrowserType = require("./RTCBrowserType"); @@ -2164,6 +2263,14 @@ RTC.stopMediaStream = function (mediaStream) { RTCUtils.stopMediaStream(mediaStream); }; +/** + * Returns whether the desktop sharing is enabled or not. + * @returns {boolean} + */ +RTC.isDesktopSharingEnabled = function () { + return RTCUtils.isDesktopSharingEnabled(); +} + RTC.prototype.getVideoElementName = function () { return RTCBrowserType.isTemasysPluginUsed() ? 'object' : 'video'; }; @@ -2188,7 +2295,7 @@ RTC.prototype.setAudioLevel = function (jid, audioLevel) { } module.exports = RTC; -},{"../../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){ +},{"../../service/RTC/MediaStreamTypes":79,"../../service/RTC/RTCEvents.js":80,"../../service/desktopsharing/DesktopSharingEventTypes":84,"./DataChannels":12,"./JitsiLocalTrack.js":13,"./JitsiRemoteTrack.js":14,"./JitsiTrack":15,"./RTCBrowserType":17,"./RTCUtils.js":18,"events":44}],17:[function(require,module,exports){ var currentBrowser; @@ -2352,8 +2459,9 @@ function detectBrowser() { if (version) return version; } - console.error("Failed to detect browser type"); - return undefined; + console.warn("Browser type defaults to Safari ver 1"); + currentBrowser = RTCBrowserType.RTC_BROWSER_SAFARI; + return 1; } browserVersion = detectBrowser(); @@ -3021,7 +3129,7 @@ var RTCUtils = { var deviceGUM = { "audio": GUM.bind(self, ["audio"]), "video": GUM.bind(self, ["video"]), - "desktop": screenObtainer.obtainStream + "desktop": screenObtainer.obtainStream.bind(screenObtainer) }; // With FF/IE we can't split the stream into audio and video because FF // doesn't support media stream constructors. So, we need to get the @@ -3127,6 +3235,13 @@ var RTCUtils = { if (mediaStream.stop) { mediaStream.stop(); } + }, + /** + * Returns whether the desktop sharing is enabled or not. + * @returns {boolean} + */ + isDesktopSharingEnabled: function () { + return screenObtainer.isSupported(); } }; @@ -3134,7 +3249,7 @@ var RTCUtils = { module.exports = RTCUtils; }).call(this,"/modules/RTC/RTCUtils.js") -},{"../../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){ +},{"../../JitsiTrackErrors":9,"../../service/RTC/RTCEvents":80,"../../service/RTC/Resolutions":81,"../xmpp/SDPUtil":32,"./RTCBrowserType":17,"./ScreenObtainer":19,"./adapter.screenshare":20,"events":44,"jitsi-meet-logger":48}],19:[function(require,module,exports){ (function (__filename){ /* global chrome, $, alert */ /* jshint -W003 */ @@ -3556,7 +3671,7 @@ function initFirefoxExtensionDetection(options) { module.exports = ScreenObtainer; }).call(this,"/modules/RTC/ScreenObtainer.js") -},{"../../service/desktopsharing/DesktopSharingEventTypes":82,"./RTCBrowserType":17,"./adapter.screenshare":20,"jitsi-meet-logger":47}],20:[function(require,module,exports){ +},{"../../service/desktopsharing/DesktopSharingEventTypes":84,"./RTCBrowserType":17,"./adapter.screenshare":20,"jitsi-meet-logger":48}],20:[function(require,module,exports){ (function (__filename){ /*! adapterjs - v0.12.3 - 2015-11-16 */ var console = require("jitsi-meet-logger").getLogger(__filename); @@ -4740,7 +4855,7 @@ if (navigator.mozGetUserMedia) { } }).call(this,"/modules/RTC/adapter.screenshare.js") -},{"jitsi-meet-logger":47}],21:[function(require,module,exports){ +},{"jitsi-meet-logger":48}],21:[function(require,module,exports){ (function (__filename){ var logger = require("jitsi-meet-logger").getLogger(__filename); @@ -4826,7 +4941,7 @@ Settings.prototype.setLanguage = function (lang) { module.exports = Settings; }).call(this,"/modules/settings/Settings.js") -},{"jitsi-meet-logger":47}],22:[function(require,module,exports){ +},{"jitsi-meet-logger":48}],22:[function(require,module,exports){ /* global config */ /** * Provides statistics for the local stream. @@ -5678,7 +5793,7 @@ StatsCollector.prototype.processAudioLevelReport = function () { }; }).call(this,"/modules/statistics/RTPStatsCollector.js") -},{"../../service/statistics/Events":83,"../RTC/RTCBrowserType":17,"jitsi-meet-logger":47}],24:[function(require,module,exports){ +},{"../../service/statistics/Events":85,"../RTC/RTCBrowserType":17,"jitsi-meet-logger":48}],24:[function(require,module,exports){ /* global require, APP */ var LocalStats = require("./LocalStatsCollector.js"); var RTPStats = require("./RTPStatsCollector.js"); @@ -5853,7 +5968,7 @@ Statistics.LOCAL_JID = require("../../service/statistics/constants").LOCAL_JID; module.exports = Statistics; -},{"../../service/statistics/Events":83,"../../service/statistics/constants":84,"./LocalStatsCollector.js":22,"./RTPStatsCollector.js":23,"events":43}],25:[function(require,module,exports){ +},{"../../service/statistics/Events":85,"../../service/statistics/constants":86,"./LocalStatsCollector.js":22,"./RTPStatsCollector.js":23,"events":44}],25:[function(require,module,exports){ /** /** * @const @@ -5936,6 +6051,8 @@ var logger = require("jitsi-meet-logger").getLogger(__filename); var XMPPEvents = require("../../service/xmpp/XMPPEvents"); var Moderator = require("./moderator"); var EventEmitter = require("events"); +var Recorder = require("./recording"); +var JIBRI_XMLNS = 'http://jitsi.org/protocol/jibri'; var parser = { packet2JSON: function (packet, nodes) { @@ -6010,6 +6127,8 @@ function ChatRoom(connection, jid, password, XMPP, options) { this.session = null; var self = this; this.lastPresences = {}; + this.phoneNumber = null; + this.phonePin = null; } ChatRoom.prototype.initPresenceMap = function () { @@ -6040,17 +6159,17 @@ ChatRoom.prototype.updateDeviceAvailability = function (devices) { }); }; -ChatRoom.prototype.join = function (password, tokenPassword) { +ChatRoom.prototype.join = function (password) { if(password) this.password = password; var self = this; this.moderator.allocateConferenceFocus(function() { - self.sendPresence(tokenPassword); + self.sendPresence(); }.bind(this)); }; -ChatRoom.prototype.sendPresence = function (tokenPassword) { +ChatRoom.prototype.sendPresence = function () { if (!this.presMap['to']) { // Too early to send presence - not initialized return; @@ -6070,11 +6189,6 @@ ChatRoom.prototype.sendPresence = function (tokenPassword) { pres.c('c', this.connection.caps.generateCapsAttrs()).up(); } - if (tokenPassword) { - pres.c('token', { xmlns: 'http://jitsi.org/jitmeet/auth-token'}) - .t(tokenPassword).up(); - } - parser.JSON2packet(this.presMap.nodes, pres); this.connection.send(pres); }; @@ -6127,6 +6241,7 @@ ChatRoom.prototype.createNonAnonymousRoom = function () { }; ChatRoom.prototype.onPresence = function (pres) { + console.log(pres); var from = pres.getAttribute('from'); // Parse roles. var member = {}; @@ -6148,6 +6263,7 @@ ChatRoom.prototype.onPresence = function (pres) { var nodes = []; parser.packet2JSON(pres, nodes); this.lastPresences[from] = nodes; + var jibri = null; for(var i = 0; i < nodes.length; i++) { var node = nodes[i]; @@ -6177,6 +6293,18 @@ ChatRoom.prototype.onPresence = function (pres) { this.eventEmitter.emit(XMPPEvents.BRIDGE_DOWN); } break; + case "jibri-recording-status": + var jibri = node; + break; + case "call-control": + console.log(pres); + var att = node.attributes; + if(!att) + break; + this.phoneNumber = att.phone || null; + this.phonePin = att.pin || null; + this.eventEmitter.emit(XMPPEvents.PHONE_NUMBER_CHANGED); + break; default : this.processNode(node, from); } @@ -6202,6 +6330,12 @@ ChatRoom.prototype.onPresence = function (pres) { logger.log('entered', from, member); if (member.isFocus) { this.focusMucJid = from; + if(!this.recording) { + this.recording = new Recorder(this.eventEmitter, this.connection, + this.focusMucJid); + if(this.lastJibri) + this.recording.handleJibriPresence(this.lastJibri); + } logger.info("Ignore focus: " + from + ", real JID: " + member.jid); } else { @@ -6230,11 +6364,18 @@ ChatRoom.prototype.onPresence = function (pres) { this.eventEmitter.emit(XMPPEvents.PRESENCE_STATUS, from, member); } + if(jibri) + { + this.lastJibri = jibri; + if(this.recording) + this.recording.handleJibriPresence(jibri); + } + }; ChatRoom.prototype.processNode = function (node, from) { if(this.presHandlers[node.tagName]) - this.presHandlers[node.tagName](node, from); + this.presHandlers[node.tagName](node, Strophe.getResourceFromJid(from)); }; ChatRoom.prototype.sendMessage = function (body, nickname) { @@ -6572,10 +6713,90 @@ ChatRoom.prototype.getJidBySSRC = function (ssrc) { return this.session.getSsrcOwner(ssrc); }; +/** + * Returns true if the recording is supproted and false if not. + */ +ChatRoom.prototype.isRecordingSupported = function () { + if(this.recording) + return this.recording.isSupported(); + return false; +}; + +/** + * Returns null if the recording is not supported, "on" if the recording started + * and "off" if the recording is not started. + */ +ChatRoom.prototype.getRecordingState = function () { + if(this.recording) + return this.recording.getState(); + return "off"; +} + +/** + * Returns the url of the recorded video. + */ +ChatRoom.prototype.getRecordingURL = function () { + if(this.recording) + return this.recording.getURL(); + return null; +} + +/** + * Starts/stops the recording + * @param token token for authentication + */ +ChatRoom.prototype.toggleRecording = function (token, followEntity) { + if(this.recording) + return this.recording.toggleRecording(token, followEntity); + + return new Promise(function(resolve, reject){ + reject(new Error("The conference is not created yet!"))}); +} + +/** + * Dials a number. + * @param number the number + */ +ChatRoom.prototype.dial = function (number) { + return this.connection.rayo.dial(number, "fromnumber", + Strophe.getNodeFromJid(this.myroomjid), this.password, + this.focusMucJid); +} + +/** + * Hangup an existing call + */ +ChatRoom.prototype.hangup = function () { + return this.connection.rayo.hangup(); +} + +/** + * Returns the phone number for joining the conference. + */ +ChatRoom.prototype.getPhoneNumber = function () { + return this.phoneNumber; +} + +/** + * Returns the pin for joining the conference with phone. + */ +ChatRoom.prototype.getPhonePin = function () { + return this.phonePin; +} + +/** + * Returns the connection state for the current session. + */ +ChatRoom.prototype.getConnectionState = function () { + if(!this.session) + return null; + return this.session.getIceConnectionState(); +} + module.exports = ChatRoom; }).call(this,"/modules/xmpp/ChatRoom.js") -},{"../../service/xmpp/XMPPEvents":85,"./moderator":34,"events":43,"jitsi-meet-logger":47}],27:[function(require,module,exports){ +},{"../../service/xmpp/XMPPEvents":87,"./moderator":34,"./recording":35,"events":44,"jitsi-meet-logger":48}],27:[function(require,module,exports){ (function (__filename){ /* * JingleSession provides an API to manage a single Jingle session. We will @@ -6711,7 +6932,7 @@ JingleSession.prototype.setAnswer = function(jingle) {}; module.exports = JingleSession; }).call(this,"/modules/xmpp/JingleSession.js") -},{"jitsi-meet-logger":47}],28:[function(require,module,exports){ +},{"jitsi-meet-logger":48}],28:[function(require,module,exports){ (function (__filename){ /* jshint -W117 */ @@ -8341,10 +8562,18 @@ JingleSessionPC.prototype.remoteStreamAdded = function (data, times) { } } +/** + * Returns the ice connection state for the peer connection. + * @returns the ice connection state for the peer connection. + */ +JingleSessionPC.prototype.getIceConnectionState = function () { + return this.peerconnection.iceConnectionState; +} + module.exports = JingleSessionPC; }).call(this,"/modules/xmpp/JingleSessionPC.js") -},{"../../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){ +},{"../../service/xmpp/XMPPEvents":87,"../RTC/RTC":16,"../RTC/RTCBrowserType":17,"./JingleSession":27,"./LocalSSRCReplacement":29,"./SDP":30,"./SDPDiffer":31,"./SDPUtil":32,"./TraceablePeerConnection":33,"async":43,"jitsi-meet-logger":48,"sdp-transform":76}],29:[function(require,module,exports){ (function (__filename){ /* global $ */ var logger = require("jitsi-meet-logger").getLogger(__filename); @@ -8403,7 +8632,7 @@ var localVideoSSRC; * This is in order to tell Chrome what SSRC should be used in RTCP requests * instead of 1. */ -var localRecvOnlySSRC; +var localRecvOnlySSRC, localRecvOnlyMSID, localRecvOnlyMSLabel, localRecvOnlyLabel; /** * cname for localRecvOnlySSRC @@ -8475,19 +8704,33 @@ 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 for local video recvonly stream. * FIXME what about eventual SSRC collision ? */ function generateRecvonlySSRC() { - localRecvOnlySSRC = + localVideoSSRC = localRecvOnlySSRC = 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( "Generated local recvonly SSRC: " + localRecvOnlySSRC + ", cname: " + localRecvOnlyCName); @@ -8562,7 +8805,13 @@ var LocalSSRCReplacement = { ' - adding SSRC: ' + localRecvOnlySSRC); sdp.media[1] += 'a=ssrc:' + localRecvOnlySSRC + - ' cname:' + localRecvOnlyCName + '\r\n'; + ' 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(''); } @@ -8620,7 +8869,7 @@ var LocalSSRCReplacement = { module.exports = LocalSSRCReplacement; }).call(this,"/modules/xmpp/LocalSSRCReplacement.js") -},{"../RTC/RTCBrowserType":17,"../util/RandomUtil":25,"./SDP":30,"jitsi-meet-logger":47}],30:[function(require,module,exports){ +},{"../RTC/RTCBrowserType":17,"../util/RandomUtil":25,"./SDP":30,"jitsi-meet-logger":48}],30:[function(require,module,exports){ (function (__filename){ /* jshint -W117 */ @@ -8870,11 +9119,11 @@ SDP.prototype.toJingle = function (elem, thecreator) { var msid = null; if(mline.media == "audio") { - msid = APP.RTC.localAudio.getId(); + msid = APP.RTC.localAudio._getId(); } else { - msid = APP.RTC.localVideo.getId(); + msid = APP.RTC.localVideo._getId(); } if(msid != null) { @@ -9270,9 +9519,8 @@ SDP.prototype.jingle2media = function (content) { module.exports = SDP; - }).call(this,"/modules/xmpp/SDP.js") -},{"./SDPUtil":32,"jitsi-meet-logger":47}],31:[function(require,module,exports){ +},{"./SDPUtil":32,"jitsi-meet-logger":48}],31:[function(require,module,exports){ var SDPUtil = require("./SDPUtil"); function SDPDiffer(mySDP, otherSDP) @@ -9809,7 +10057,7 @@ SDPUtil = { module.exports = SDPUtil; }).call(this,"/modules/xmpp/SDPUtil.js") -},{"../RTC/RTCBrowserType":17,"jitsi-meet-logger":47}],33:[function(require,module,exports){ +},{"../RTC/RTCBrowserType":17,"jitsi-meet-logger":48}],33:[function(require,module,exports){ (function (__filename){ /* global $ */ var RTC = require('../RTC/RTC'); @@ -10262,7 +10510,7 @@ TraceablePeerConnection.prototype.getStats = function(callback, errback) { module.exports = TraceablePeerConnection; }).call(this,"/modules/xmpp/TraceablePeerConnection.js") -},{"../../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){ +},{"../../service/xmpp/XMPPEvents":87,"../RTC/RTC":16,"../RTC/RTCBrowserType.js":17,"./LocalSSRCReplacement":29,"jitsi-meet-logger":48,"sdp-interop":66,"sdp-simulcast":73,"sdp-transform":76}],34:[function(require,module,exports){ (function (__filename){ /* global $, $iq, APP, config, messageHandler, roomName, sessionTerminated, Strophe, Util */ @@ -10708,7 +10956,128 @@ Moderator.prototype.logout = function (callback) { module.exports = Moderator; }).call(this,"/modules/xmpp/moderator.js") -},{"../../service/authentication/AuthenticationEvents":81,"../../service/xmpp/XMPPEvents":85,"../settings/Settings":21,"jitsi-meet-logger":47}],35:[function(require,module,exports){ +},{"../../service/authentication/AuthenticationEvents":83,"../../service/xmpp/XMPPEvents":87,"../settings/Settings":21,"jitsi-meet-logger":48}],35:[function(require,module,exports){ +(function (__filename){ +/* global $, $iq, config, connection, focusMucJid, messageHandler, + Toolbar, Util, Promise */ +var XMPPEvents = require("../../service/XMPP/XMPPEvents"); +var logger = require("jitsi-meet-logger").getLogger(__filename); + +function Recording(ee, connection, focusMucJid) { + this.eventEmitter = ee; + this.connection = connection; + this.state = "off"; + this.focusMucJid = focusMucJid; + this.url = null; + this._isSupported = false; +} + +Recording.prototype.handleJibriPresence = function (jibri) { + var attributes = jibri.attributes; + if(!attributes) + return; + + this._isSupported = + (attributes.status && attributes.status !== "undefined"); + if(this._isSupported) { + this.url = attributes.url || null; + this.state = attributes.status || "off"; + } + this.eventEmitter.emit(XMPPEvents.RECORDING_STATE_CHANGED); +}; + +Recording.prototype.setRecording = function (state, streamId, followEntity, + callback, errCallback){ + if (state == this.state){ + return; + } + + // FIXME jibri does not accept IQ without 'url' attribute set ? + + var iq = $iq({to: this.focusMucJid, type: 'set'}) + .c('jibri', { + "xmlns": 'http://jitsi.org/protocol/jibri', + "action": (state === 'on') ? 'start' : 'stop', + "streamid": streamId, + "follow-entity": followEntity + }).up(); + + logger.log('Set jibri recording: '+state, iq.nodeTree); + console.log(iq.nodeTree); + this.connection.sendIQ( + iq, + function (result) { + callback($(result).find('jibri').attr('state'), + $(result).find('jibri').attr('url')); + }, + function (error) { + logger.log('Failed to start recording, error: ', error); + errCallback(error); + }); +}; + +Recording.prototype.toggleRecording = function (token, followEntity) { + var self = this; + return new Promise(function(resolve, reject) { + if (!token) { + reject(new Error("No token passed!")); + logger.error("No token passed!"); + return; + } + if(self.state === "on") { + reject(new Error("Recording is already started!")); + logger.error("Recording is already started!"); + return; + } + + var oldState = self.state; + var newState = (oldState === 'off' || !oldState) ? 'on' : 'off'; + + self.setRecording(newState, + token, followEntity, + function (state, url) { + logger.log("New recording state: ", state); + if (state && state !== oldState) { + self.state = state; + self.url = url; + resolve(); + } else { + reject(new Error("State not changed!")); + } + }, + function (error) { + reject(error); + } + ); + }); +}; + +/** + * Returns true if the recording is supproted and false if not. + */ +Recording.prototype.isSupported = function () { + return this._isSupported; +}; + +/** + * Returns null if the recording is not supported, "on" if the recording started + * and "off" if the recording is not started. + */ +Recording.prototype.getState = function () { + return this.state; +}; + +/** + * Returns the url of the recorded video. + */ +Recording.prototype.getURL = function () { + return this.url; +}; + +module.exports = Recording; + +}).call(this,"/modules/xmpp/recording.js") +},{"../../service/XMPP/XMPPEvents":82,"jitsi-meet-logger":48}],36:[function(require,module,exports){ (function (__filename){ /* jshint -W117 */ /* a simple MUC connection plugin @@ -10805,7 +11174,7 @@ module.exports = function(XMPP) { }).call(this,"/modules/xmpp/strophe.emuc.js") -},{"./ChatRoom":26,"jitsi-meet-logger":47}],36:[function(require,module,exports){ +},{"./ChatRoom":26,"jitsi-meet-logger":48}],37:[function(require,module,exports){ (function (__filename){ /* jshint -W117 */ @@ -11102,7 +11471,7 @@ module.exports = function(XMPP, eventEmitter) { }).call(this,"/modules/xmpp/strophe.jingle.js") -},{"../../service/xmpp/XMPPEvents":85,"../RTC/RTCBrowserType":17,"./JingleSessionPC":28,"jitsi-meet-logger":47}],37:[function(require,module,exports){ +},{"../../service/xmpp/XMPPEvents":87,"../RTC/RTCBrowserType":17,"./JingleSessionPC":28,"jitsi-meet-logger":48}],38:[function(require,module,exports){ /* global Strophe */ module.exports = function () { @@ -11123,7 +11492,7 @@ module.exports = function () { } }); }; -},{}],38:[function(require,module,exports){ +},{}],39:[function(require,module,exports){ (function (__filename){ /* global $, $iq, Strophe */ @@ -11250,7 +11619,7 @@ module.exports = function (XMPP, eventEmitter) { }; }).call(this,"/modules/xmpp/strophe.ping.js") -},{"../../service/xmpp/XMPPEvents":85,"jitsi-meet-logger":47}],39:[function(require,module,exports){ +},{"../../service/xmpp/XMPPEvents":87,"jitsi-meet-logger":48}],40:[function(require,module,exports){ (function (__filename){ /* jshint -W117 */ var logger = require("jitsi-meet-logger").getLogger(__filename); @@ -11267,92 +11636,107 @@ module.exports = function() { } this.connection.addHandler( - this.onRayo.bind(this), - this.RAYO_XMLNS, 'iq', 'set', null, null); + this.onRayo.bind(this), this.RAYO_XMLNS, 'iq', 'set', + null, null); }, onRayo: function (iq) { logger.info("Rayo IQ", iq); }, - dial: function (to, from, roomName, roomPass) { + dial: function (to, from, roomName, roomPass, focusMucJid) { var self = this; - var req = $iq( - { - type: 'set', - to: this.connection.emuc.focusMucJid + return new Promise(function (resolve, reject) { + if(!focusMucJid) { + reject(new Error("Internal error!")); + return; } - ); - req.c('dial', - { - xmlns: this.RAYO_XMLNS, - to: to, - from: from - }); - req.c('header', - { - name: 'JvbRoomName', - value: roomName - }).up(); - - if (roomPass !== null && roomPass.length) { - + var req = $iq( + { + type: 'set', + to: focusMucJid + } + ); + req.c('dial', + { + xmlns: self.RAYO_XMLNS, + to: to, + from: from + }); req.c('header', { - name: 'JvbRoomPassword', - value: roomPass + name: 'JvbRoomName', + value: roomName }).up(); - } - this.connection.sendIQ( - req, - function (result) { - logger.info('Dial result ', result); + if (roomPass !== null && roomPass.length) { - var resource = $(result).find('ref').attr('uri'); - this.call_resource = resource.substr('xmpp:'.length); - logger.info( - "Received call resource: " + this.call_resource); - }, - function (error) { - logger.info('Dial error ', error); + req.c('header', + { + name: 'JvbRoomPassword', + value: roomPass + }).up(); } - ); + + self.connection.sendIQ( + req, + function (result) { + logger.info('Dial result ', result); + + var resource = $(result).find('ref').attr('uri'); + self.call_resource = + resource.substr('xmpp:'.length); + logger.info( + "Received call resource: " + + self.call_resource); + resolve(); + }, + function (error) { + logger.info('Dial error ', error); + reject(error); + } + ); + }); }, - hang_up: function () { - if (!this.call_resource) { - logger.warn("No call in progress"); - return; - } - + hangup: function () { var self = this; - var req = $iq( - { - type: 'set', - to: this.call_resource + return new Promise(function (resolve, reject) { + if (!self.call_resource) { + reject(new Error("No call in progress")); + logger.warn("No call in progress"); + return; } - ); - req.c('hangup', - { - xmlns: this.RAYO_XMLNS - }); - this.connection.sendIQ( - req, - function (result) { - logger.info('Hangup result ', result); - self.call_resource = null; - }, - function (error) { - logger.info('Hangup error ', error); - self.call_resource = null; - } - ); + var req = $iq( + { + type: 'set', + to: self.call_resource + } + ); + req.c('hangup', + { + xmlns: self.RAYO_XMLNS + }); + + self.connection.sendIQ( + req, + function (result) { + logger.info('Hangup result ', result); + self.call_resource = null; + resolve(); + }, + function (error) { + logger.info('Hangup error ', error); + self.call_resource = null; + reject(new Error('Hangup error ')); + } + ); + }); } } ); }; }).call(this,"/modules/xmpp/strophe.rayo.js") -},{"jitsi-meet-logger":47}],40:[function(require,module,exports){ +},{"jitsi-meet-logger":48}],41:[function(require,module,exports){ (function (__filename){ /* global Strophe */ /** @@ -11401,7 +11785,7 @@ module.exports = function () { }; }).call(this,"/modules/xmpp/strophe.util.js") -},{"jitsi-meet-logger":47}],41:[function(require,module,exports){ +},{"jitsi-meet-logger":48}],42:[function(require,module,exports){ (function (__filename){ /* global $, APP, config, Strophe*/ @@ -11419,6 +11803,12 @@ var authenticatedUser = false; function createConnection(bosh) { bosh = bosh || '/http-bind'; + // Append token as URL param + if (this.token) { + bosh += bosh.indexOf('?') == -1 ? + '?token=' + this.token : '&token=' + this.token; + } + return new Strophe.Connection(bosh); }; @@ -11723,7 +12113,7 @@ XMPP.prototype.getLocalSSRC = function (mediaType) { module.exports = XMPP; }).call(this,"/modules/xmpp/xmpp.js") -},{"../../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){ +},{"../../JitsiConnectionErrors":5,"../../JitsiConnectionEvents":6,"../../service/RTC/RTCEvents":80,"../../service/xmpp/XMPPEvents":87,"../RTC/RTC":16,"./strophe.emuc":36,"./strophe.jingle":37,"./strophe.logger":38,"./strophe.ping":39,"./strophe.rayo":40,"./strophe.util":41,"events":44,"jitsi-meet-logger":48,"pako":49}],43:[function(require,module,exports){ (function (process){ /*! * async @@ -12850,7 +13240,7 @@ module.exports = XMPP; }()); }).call(this,require('_process')) -},{"_process":44}],43:[function(require,module,exports){ +},{"_process":45}],44:[function(require,module,exports){ // Copyright Joyent, Inc. and other Node contributors. // // Permission is hereby granted, free of charge, to any person obtaining a @@ -13153,7 +13543,7 @@ function isUndefined(arg) { return arg === void 0; } -},{}],44:[function(require,module,exports){ +},{}],45:[function(require,module,exports){ // shim for using process in browser var process = module.exports = {}; @@ -13246,7 +13636,7 @@ process.chdir = function (dir) { }; process.umask = function() { return 0; }; -},{}],45:[function(require,module,exports){ +},{}],46:[function(require,module,exports){ (function (process,global){ /*! * @overview es6-promise - a tiny implementation of Promises/A+. @@ -14217,7 +14607,7 @@ process.umask = function() { return 0; }; }).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{"_process":44}],46:[function(require,module,exports){ +},{"_process":45}],47:[function(require,module,exports){ /* Copyright @ 2015 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -14361,7 +14751,7 @@ Logger.levels = { ERROR: "error" }; -},{}],47:[function(require,module,exports){ +},{}],48:[function(require,module,exports){ /* Copyright @ 2015 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -14446,7 +14836,7 @@ module.exports = { levels: Logger.levels }; -},{"./Logger":46}],48:[function(require,module,exports){ +},{"./Logger":47}],49:[function(require,module,exports){ // Top level file is just a mixin of submodules & constants 'use strict'; @@ -14462,7 +14852,7 @@ assign(pako, deflate, inflate, constants); module.exports = pako; -},{"./lib/deflate":49,"./lib/inflate":50,"./lib/utils/common":51,"./lib/zlib/constants":54}],49:[function(require,module,exports){ +},{"./lib/deflate":50,"./lib/inflate":51,"./lib/utils/common":52,"./lib/zlib/constants":55}],50:[function(require,module,exports){ 'use strict'; @@ -14840,7 +15230,7 @@ exports.deflate = deflate; exports.deflateRaw = deflateRaw; exports.gzip = gzip; -},{"./utils/common":51,"./utils/strings":52,"./zlib/deflate.js":56,"./zlib/messages":61,"./zlib/zstream":63}],50:[function(require,module,exports){ +},{"./utils/common":52,"./utils/strings":53,"./zlib/deflate.js":57,"./zlib/messages":62,"./zlib/zstream":64}],51:[function(require,module,exports){ 'use strict'; @@ -15242,7 +15632,7 @@ exports.inflate = inflate; exports.inflateRaw = inflateRaw; exports.ungzip = inflate; -},{"./utils/common":51,"./utils/strings":52,"./zlib/constants":54,"./zlib/gzheader":57,"./zlib/inflate.js":59,"./zlib/messages":61,"./zlib/zstream":63}],51:[function(require,module,exports){ +},{"./utils/common":52,"./utils/strings":53,"./zlib/constants":55,"./zlib/gzheader":58,"./zlib/inflate.js":60,"./zlib/messages":62,"./zlib/zstream":64}],52:[function(require,module,exports){ 'use strict'; @@ -15346,7 +15736,7 @@ exports.setTyped = function (on) { exports.setTyped(TYPED_OK); -},{}],52:[function(require,module,exports){ +},{}],53:[function(require,module,exports){ // String encode/decode helpers 'use strict'; @@ -15533,7 +15923,7 @@ exports.utf8border = function(buf, max) { return (pos + _utf8len[buf[pos]] > max) ? pos : max; }; -},{"./common":51}],53:[function(require,module,exports){ +},{"./common":52}],54:[function(require,module,exports){ 'use strict'; // Note: adler32 takes 12% for level 0 and 2% for level 6. @@ -15567,7 +15957,7 @@ function adler32(adler, buf, len, pos) { module.exports = adler32; -},{}],54:[function(require,module,exports){ +},{}],55:[function(require,module,exports){ module.exports = { /* Allowed flush values; see deflate() and inflate() below for details */ @@ -15616,7 +16006,7 @@ module.exports = { //Z_NULL: null // Use -1 or null inline, depending on var type }; -},{}],55:[function(require,module,exports){ +},{}],56:[function(require,module,exports){ 'use strict'; // Note: we can't get significant speed boost here. @@ -15659,7 +16049,7 @@ function crc32(crc, buf, len, pos) { module.exports = crc32; -},{}],56:[function(require,module,exports){ +},{}],57:[function(require,module,exports){ 'use strict'; var utils = require('../utils/common'); @@ -17426,7 +17816,7 @@ exports.deflatePrime = deflatePrime; exports.deflateTune = deflateTune; */ -},{"../utils/common":51,"./adler32":53,"./crc32":55,"./messages":61,"./trees":62}],57:[function(require,module,exports){ +},{"../utils/common":52,"./adler32":54,"./crc32":56,"./messages":62,"./trees":63}],58:[function(require,module,exports){ 'use strict'; @@ -17468,7 +17858,7 @@ function GZheader() { module.exports = GZheader; -},{}],58:[function(require,module,exports){ +},{}],59:[function(require,module,exports){ 'use strict'; // See state defs from inflate.js @@ -17796,7 +18186,7 @@ module.exports = function inflate_fast(strm, start) { return; }; -},{}],59:[function(require,module,exports){ +},{}],60:[function(require,module,exports){ 'use strict'; @@ -19301,7 +19691,7 @@ exports.inflateSyncPoint = inflateSyncPoint; exports.inflateUndermine = inflateUndermine; */ -},{"../utils/common":51,"./adler32":53,"./crc32":55,"./inffast":58,"./inftrees":60}],60:[function(require,module,exports){ +},{"../utils/common":52,"./adler32":54,"./crc32":56,"./inffast":59,"./inftrees":61}],61:[function(require,module,exports){ 'use strict'; @@ -19630,7 +20020,7 @@ module.exports = function inflate_table(type, lens, lens_index, codes, table, ta return 0; }; -},{"../utils/common":51}],61:[function(require,module,exports){ +},{"../utils/common":52}],62:[function(require,module,exports){ 'use strict'; module.exports = { @@ -19645,7 +20035,7 @@ module.exports = { '-6': 'incompatible version' /* Z_VERSION_ERROR (-6) */ }; -},{}],62:[function(require,module,exports){ +},{}],63:[function(require,module,exports){ 'use strict'; @@ -20846,7 +21236,7 @@ exports._tr_flush_block = _tr_flush_block; exports._tr_tally = _tr_tally; exports._tr_align = _tr_align; -},{"../utils/common":51}],63:[function(require,module,exports){ +},{"../utils/common":52}],64:[function(require,module,exports){ 'use strict'; @@ -20877,7 +21267,7 @@ function ZStream() { module.exports = ZStream; -},{}],64:[function(require,module,exports){ +},{}],65:[function(require,module,exports){ /* Copyright @ 2015 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -20918,7 +21308,7 @@ module.exports = function arrayEquals(array) { }; -},{}],65:[function(require,module,exports){ +},{}],66:[function(require,module,exports){ /* Copyright @ 2015 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -20936,7 +21326,7 @@ module.exports = function arrayEquals(array) { exports.Interop = require('./interop'); -},{"./interop":66}],66:[function(require,module,exports){ +},{"./interop":67}],67:[function(require,module,exports){ /* Copyright @ 2015 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -21659,7 +22049,7 @@ Interop.prototype.toUnifiedPlan = function(desc) { //#endregion }; -},{"./array-equals":64,"./transform":67}],67:[function(require,module,exports){ +},{"./array-equals":65,"./transform":68}],68:[function(require,module,exports){ /* Copyright @ 2015 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -21773,7 +22163,487 @@ exports.parse = function(sdp) { }; -},{"sdp-transform":75}],68:[function(require,module,exports){ +},{"sdp-transform":70}],69:[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"; + } + }); +}); + +},{}],70:[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":71,"./writer":72}],71:[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":69}],72:[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":69}],73:[function(require,module,exports){ /* Copyright @ 2015 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -21906,7 +22776,7 @@ function explodeRemoteSimulcast(mLine) { function implodeRemoteSimulcast(mLine) { if (!mLine || !Array.isArray(mLine.ssrcGroups)) { - console.debug('Halt: There are no SSRC groups in the remote ' + + console.info('Halt: There are no SSRC groups in the remote ' + 'description.'); return; } @@ -21919,7 +22789,7 @@ function implodeRemoteSimulcast(mLine) { return; } - console.debug("Imploding SIM group: " + simulcastGroup.ssrcs); + console.info("Imploding SIM group: " + simulcastGroup.ssrcs); // Schedule the SIM group for nuking. simulcastGroup.nuke = true; @@ -22193,7 +23063,7 @@ Simulcast.prototype.mungeLocalDescription = function (desc) { module.exports = Simulcast; -},{"./transform-utils":69,"sdp-transform":71}],69:[function(require,module,exports){ +},{"./transform-utils":74,"sdp-transform":76}],74:[function(require,module,exports){ /* Copyright @ 2015 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -22259,834 +23129,22 @@ exports.parseSsrcs = function (mLine) { }; -},{}],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][69][0].apply(exports,arguments) +},{"dup":69}],76:[function(require,module,exports){ +arguments[4][70][0].apply(exports,arguments) +},{"./parser":77,"./writer":78,"dup":70}],77:[function(require,module,exports){ arguments[4][71][0].apply(exports,arguments) -},{"./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){ +},{"./grammar":75,"dup":71}],78:[function(require,module,exports){ +arguments[4][72][0].apply(exports,arguments) +},{"./grammar":75,"dup":72}],79:[function(require,module,exports){ var MediaStreamType = { VIDEO_TYPE: "Video", AUDIO_TYPE: "Audio" }; module.exports = MediaStreamType; -},{}],79:[function(require,module,exports){ +},{}],80:[function(require,module,exports){ var RTCEvents = { RTC_READY: "rtc.ready", DATA_CHANNEL_OPEN: "rtc.data_channel_open", @@ -23097,7 +23155,7 @@ var RTCEvents = { }; module.exports = RTCEvents; -},{}],80:[function(require,module,exports){ +},{}],81:[function(require,module,exports){ var Resolutions = { "1080": { width: 1920, @@ -23151,58 +23209,7 @@ var Resolutions = { } }; module.exports = Resolutions; -},{}],81:[function(require,module,exports){ -var AuthenticationEvents = { - /** - * Event callback arguments: - * function(authenticationEnabled, userIdentity) - * authenticationEnabled - indicates whether authentication has been enabled - * in this session - * userIdentity - if user has been logged in then it contains user name. If - * contains 'null' or 'undefined' then user is not logged in. - */ - IDENTITY_UPDATED: "authentication.identity_updated" -}; -module.exports = AuthenticationEvents; - },{}],82:[function(require,module,exports){ -var DesktopSharingEventTypes = { - INIT: "ds.init", - - SWITCHING_DONE: "ds.switching_done", - - NEW_STREAM_CREATED: "ds.new_stream_created", - /** - * An event which indicates that the jidesha extension for Firefox is - * needed to proceed with screen sharing, and that it is not installed. - */ - FIREFOX_EXTENSION_NEEDED: "ds.firefox_extension_needed" -}; - -module.exports = DesktopSharingEventTypes; - -},{}],83:[function(require,module,exports){ -module.exports = { - /** - * An event carrying connection statistics. - */ - CONNECTION_STATS: "statistics.connectionstats", - /** - * FIXME: needs documentation. - */ - AUDIO_LEVEL: "statistics.audioLevel", - /** - * FIXME: needs documentation. - */ - STOP: "statistics.stop" -}; - -},{}],84:[function(require,module,exports){ -var Constants = { - LOCAL_JID: 'local' -}; -module.exports = Constants; -},{}],85:[function(require,module,exports){ var XMPPEvents = { // Designates an event indicating that the connection to the XMPP server // failed. @@ -23302,8 +23309,70 @@ var XMPPEvents = { // xmpp is connected and obtained user media READY_TO_JOIN: 'xmpp.ready_to_join', FOCUS_LEFT: "xmpp.focus_left", - REMOTE_STREAM_RECEIVED: "xmpp.remote_stream_received" + REMOTE_STREAM_RECEIVED: "xmpp.remote_stream_received", + /** + * Indicates that recording state changed. + */ + RECORDING_STATE_CHANGED: "xmpp.recordingStateChanged", + /** + * Indicates that phone number changed. + */ + PHONE_NUMBER_CHANGED: "conference.phoneNumberChanged" }; module.exports = XMPPEvents; -},{}]},{},[7])(7) + +},{}],83:[function(require,module,exports){ +var AuthenticationEvents = { + /** + * Event callback arguments: + * function(authenticationEnabled, userIdentity) + * authenticationEnabled - indicates whether authentication has been enabled + * in this session + * userIdentity - if user has been logged in then it contains user name. If + * contains 'null' or 'undefined' then user is not logged in. + */ + IDENTITY_UPDATED: "authentication.identity_updated" +}; +module.exports = AuthenticationEvents; + +},{}],84:[function(require,module,exports){ +var DesktopSharingEventTypes = { + INIT: "ds.init", + + SWITCHING_DONE: "ds.switching_done", + + NEW_STREAM_CREATED: "ds.new_stream_created", + /** + * An event which indicates that the jidesha extension for Firefox is + * needed to proceed with screen sharing, and that it is not installed. + */ + FIREFOX_EXTENSION_NEEDED: "ds.firefox_extension_needed" +}; + +module.exports = DesktopSharingEventTypes; + +},{}],85:[function(require,module,exports){ +module.exports = { + /** + * An event carrying connection statistics. + */ + CONNECTION_STATS: "statistics.connectionstats", + /** + * FIXME: needs documentation. + */ + AUDIO_LEVEL: "statistics.audioLevel", + /** + * FIXME: needs documentation. + */ + STOP: "statistics.stop" +}; + +},{}],86:[function(require,module,exports){ +var Constants = { + LOCAL_JID: 'local' +}; +module.exports = Constants; +},{}],87:[function(require,module,exports){ +arguments[4][82][0].apply(exports,arguments) +},{"dup":82}]},{},[7])(7) }); \ No newline at end of file diff --git a/modules/UI/UI.js b/modules/UI/UI.js index 09b627a8c..fcb4fae34 100644 --- a/modules/UI/UI.js +++ b/modules/UI/UI.js @@ -386,13 +386,14 @@ UI.removeUser = function (id, displayName) { VideoLayout.removeParticipantContainer(id); }; -function onMucPresenceStatus(jid, info) { - VideoLayout.setPresenceStatus(Strophe.getResourceFromJid(jid), info.status); -} +//FIXME: NOT USED. Should start using the lib +// function onMucPresenceStatus(jid, info) { +// VideoLayout.setPresenceStatus(Strophe.getResourceFromJid(jid), info.status); +// } -function onPeerVideoTypeChanged(resourceJid, newVideoType) { +UI.onPeerVideoTypeChanged = (resourceJid, newVideoType) => { VideoLayout.onVideoTypeChanged(resourceJid, newVideoType); -} +}; UI.updateLocalRole = function (isModerator) { VideoLayout.showModeratorIndicator(); diff --git a/modules/UI/util/UIUtil.js b/modules/UI/util/UIUtil.js index bee0149a5..d1dc7a21c 100644 --- a/modules/UI/util/UIUtil.js +++ b/modules/UI/util/UIUtil.js @@ -116,7 +116,7 @@ import PanelToggler from "../side_pannels/SidePanelToggler"; $(selector).hide(); }, - redirect (url) { + redirect (url) { window.location.href = url; } }; diff --git a/modules/UI/videolayout/LargeVideo.js b/modules/UI/videolayout/LargeVideo.js index dafb921ea..921398770 100644 --- a/modules/UI/videolayout/LargeVideo.js +++ b/modules/UI/videolayout/LargeVideo.js @@ -549,7 +549,7 @@ var LargeVideo = { if (!videoType) videoType = currentSmallVideo.getVideoType(); - var isDesktop = videoType === 'screen'; + var isDesktop = videoType === 'desktop'; // Change the way we'll be measuring and positioning large video getVideoSize = isDesktop ? getDesktopVideoSize : getCameraVideoSize; diff --git a/modules/UI/videolayout/LocalVideo.js b/modules/UI/videolayout/LocalVideo.js index b23da4845..a00d66545 100644 --- a/modules/UI/videolayout/LocalVideo.js +++ b/modules/UI/videolayout/LocalVideo.js @@ -159,7 +159,7 @@ LocalVideo.prototype.changeVideo = function (stream) { localVideoContainerSelector.off('click'); localVideoContainerSelector.on('click', localVideoClick); - this.flipX = stream.videoType != "screen"; + this.flipX = stream.videoType != "desktop"; let localVideo = document.createElement('video'); localVideo.id = 'localVideo_' + stream.getId(); if (!RTCBrowserType.isIExplorer()) { @@ -191,14 +191,14 @@ LocalVideo.prototype.changeVideo = function (stream) { return; // Add stream ended handler - APP.RTC.addMediaStreamInactiveHandler( + /**APP.RTC.addMediaStreamInactiveHandler( stream.getOriginalStream(), function () { // We have to re-select after attach when Temasys plugin is used, // because