diff --git a/config.js b/config.js index 1152fe881..ebf7d2698 100644 --- a/config.js +++ b/config.js @@ -35,6 +35,9 @@ var config = { // startAudioMuted: 10, //every participant after the Nth will start audio muted // startVideoMuted: 10, //every participant after the Nth will start video muted // defaultLanguage: "en", +// To enable sending statistics to callstats.io you should provide Applicaiton ID and Secret. +// callStatsID: "",//Application ID for callstats.io API +// callStatsSecret: ""//Secret for callstats.io API /*noticeMessage: 'Service update is scheduled for 16th March 2015. ' + 'During that time service will not be available. ' + 'Apologise for inconvenience.'*/ diff --git a/index.html b/index.html index dfd54f6fe..3f8797030 100644 --- a/index.html +++ b/index.html @@ -9,8 +9,11 @@ + - + + + @@ -19,7 +22,7 @@ - + diff --git a/libs/app.bundle.js b/libs/app.bundle.js index 6fc48b6d3..75dcab45a 100644 --- a/libs/app.bundle.js +++ b/libs/app.bundle.js @@ -58,7 +58,7 @@ $(window).bind('beforeunload', function () { module.exports = APP; -},{"./modules/API/API":2,"./modules/DTMF/DTMF":3,"./modules/RTC/RTC":7,"./modules/UI/UI":9,"./modules/URLProcessor/URLProcessor":40,"./modules/connectionquality/connectionquality":41,"./modules/desktopsharing/desktopsharing":42,"./modules/keyboardshortcut/keyboardshortcut":43,"./modules/members/MemberList":44,"./modules/settings/Settings":45,"./modules/statistics/statistics":48,"./modules/translation/translation":49,"./modules/xmpp/xmpp":63}],2:[function(require,module,exports){ +},{"./modules/API/API":2,"./modules/DTMF/DTMF":3,"./modules/RTC/RTC":7,"./modules/UI/UI":9,"./modules/URLProcessor/URLProcessor":40,"./modules/connectionquality/connectionquality":41,"./modules/desktopsharing/desktopsharing":42,"./modules/keyboardshortcut/keyboardshortcut":43,"./modules/members/MemberList":44,"./modules/settings/Settings":45,"./modules/statistics/statistics":49,"./modules/translation/translation":50,"./modules/xmpp/xmpp":64}],2:[function(require,module,exports){ /** * Implements API class that communicates with external api class * and provides interface to access Jitsi Meet features by external @@ -290,7 +290,7 @@ var API = { }; module.exports = API; -},{"../../service/xmpp/XMPPEvents":106}],3:[function(require,module,exports){ +},{"../../service/xmpp/XMPPEvents":111}],3:[function(require,module,exports){ /* global APP */ /** @@ -557,8 +557,9 @@ function onPinnedEndpointChanged(userResource) module.exports = DataChannels; -},{"../../service/RTC/RTCEvents":97}],5:[function(require,module,exports){ +},{"../../service/RTC/RTCEvents":102}],5:[function(require,module,exports){ var StreamEventTypes = require("../../service/RTC/StreamEventTypes.js"); +var RTCEvents = require("../../service/RTC/RTCEvents"); function LocalStream(stream, type, eventEmitter, videoType, isGUMStream) @@ -614,12 +615,18 @@ LocalStream.prototype.setMute = function(mute) for (var idx = 0; idx < tracks.length; idx++) { tracks[idx].enabled = mute; } + this.eventEmitter.emit( + (this.type == "audio"? RTCEvents.AUDIO_MUTE : RTCEvents.VIDEO_MUTE), + !mute); } else { if(mute === false) { APP.xmpp.removeStream(this.stream); this.stream.stop(); + this.eventEmitter.emit( + (this.type == "audio"? RTCEvents.AUDIO_MUTE : RTCEvents.VIDEO_MUTE), + true); } else { @@ -629,11 +636,21 @@ LocalStream.prototype.setMute = function(mute) function (stream) { if(self.isAudioStream()) { - APP.RTC.changeLocalAudio(stream, function () {}); + APP.RTC.changeLocalAudio(stream, + function () { + self.eventEmitter.emit( + (self.type == "audio"? RTCEvents.AUDIO_MUTE : RTCEvents.VIDEO_MUTE), + true); + }); } else { - APP.RTC.changeLocalVideo(stream, false, function () {}); + APP.RTC.changeLocalVideo(stream, false, + function () { + self.eventEmitter.emit( + (self.type == "audio"? RTCEvents.AUDIO_MUTE : RTCEvents.VIDEO_MUTE), + true); + }); } }); } @@ -665,7 +682,7 @@ LocalStream.prototype.getId = function () { module.exports = LocalStream; -},{"../../service/RTC/StreamEventTypes.js":99}],6:[function(require,module,exports){ +},{"../../service/RTC/RTCEvents":102,"../../service/RTC/StreamEventTypes.js":104}],6:[function(require,module,exports){ ////These lines should be uncommented when require works in app.js var MediaStreamType = require("../../service/RTC/MediaStreamTypes"); var StreamEventType = require("../../service/RTC/StreamEventTypes"); @@ -727,7 +744,7 @@ MediaStream.prototype.setVideoType = function (value) { module.exports = MediaStream; -},{"../../service/RTC/MediaStreamTypes":95,"../../service/RTC/StreamEventTypes":99}],7:[function(require,module,exports){ +},{"../../service/RTC/MediaStreamTypes":100,"../../service/RTC/StreamEventTypes":104}],7:[function(require,module,exports){ var EventEmitter = require("events"); var RTCUtils = require("./RTCUtils.js"); var LocalStream = require("./LocalStream.js"); @@ -1013,7 +1030,7 @@ var RTC = { module.exports = RTC; -},{"../../service/RTC/MediaStreamTypes":95,"../../service/RTC/RTCEvents.js":97,"../../service/RTC/StreamEventTypes.js":99,"../../service/UI/UIEvents":100,"../../service/desktopsharing/DesktopSharingEventTypes":103,"../../service/xmpp/XMPPEvents":106,"./DataChannels":4,"./LocalStream.js":5,"./MediaStream.js":6,"./RTCUtils.js":8,"events":107}],8:[function(require,module,exports){ +},{"../../service/RTC/MediaStreamTypes":100,"../../service/RTC/RTCEvents.js":102,"../../service/RTC/StreamEventTypes.js":104,"../../service/UI/UIEvents":105,"../../service/desktopsharing/DesktopSharingEventTypes":108,"../../service/xmpp/XMPPEvents":111,"./DataChannels":4,"./LocalStream.js":5,"./MediaStream.js":6,"./RTCUtils.js":8,"events":112}],8:[function(require,module,exports){ var RTCBrowserType = require("../../service/RTC/RTCBrowserType.js"); var Resolutions = require("../../service/RTC/Resolutions"); @@ -1487,7 +1504,7 @@ RTCUtils.prototype.createStream = function(stream, isVideo) module.exports = RTCUtils; -},{"../../service/RTC/RTCBrowserType.js":96,"../../service/RTC/Resolutions":98}],9:[function(require,module,exports){ +},{"../../service/RTC/RTCBrowserType.js":101,"../../service/RTC/Resolutions":103}],9:[function(require,module,exports){ var UI = {}; var VideoLayout = require("./videolayout/VideoLayout.js"); @@ -2294,7 +2311,7 @@ UI.setVideoMute = setVideoMute; module.exports = UI; -},{"../../service/RTC/RTCEvents":97,"../../service/RTC/StreamEventTypes":99,"../../service/connectionquality/CQEvents":102,"../../service/desktopsharing/DesktopSharingEventTypes":103,"../../service/members/Events":104,"../../service/xmpp/XMPPEvents":106,"./../settings/Settings":45,"./audio_levels/AudioLevels.js":10,"./authentication/Authentication":12,"./avatar/Avatar":14,"./etherpad/Etherpad.js":15,"./prezi/Prezi.js":16,"./side_pannels/SidePanelToggler":18,"./side_pannels/chat/Chat.js":19,"./side_pannels/contactlist/ContactList":23,"./side_pannels/settings/SettingsMenu":24,"./toolbars/BottomToolbar":25,"./toolbars/Toolbar":26,"./toolbars/ToolbarToggler":27,"./util/MessageHandler":29,"./util/NicknameHandler":30,"./util/UIUtil":31,"./videolayout/VideoLayout.js":37,"./welcome_page/RoomnameGenerator":38,"./welcome_page/WelcomePage":39,"events":107}],10:[function(require,module,exports){ +},{"../../service/RTC/RTCEvents":102,"../../service/RTC/StreamEventTypes":104,"../../service/connectionquality/CQEvents":107,"../../service/desktopsharing/DesktopSharingEventTypes":108,"../../service/members/Events":109,"../../service/xmpp/XMPPEvents":111,"./../settings/Settings":45,"./audio_levels/AudioLevels.js":10,"./authentication/Authentication":12,"./avatar/Avatar":14,"./etherpad/Etherpad.js":15,"./prezi/Prezi.js":16,"./side_pannels/SidePanelToggler":18,"./side_pannels/chat/Chat.js":19,"./side_pannels/contactlist/ContactList":23,"./side_pannels/settings/SettingsMenu":24,"./toolbars/BottomToolbar":25,"./toolbars/Toolbar":26,"./toolbars/ToolbarToggler":27,"./util/MessageHandler":29,"./util/NicknameHandler":30,"./util/UIUtil":31,"./videolayout/VideoLayout.js":37,"./welcome_page/RoomnameGenerator":38,"./welcome_page/WelcomePage":39,"events":112}],10:[function(require,module,exports){ var CanvasUtil = require("./CanvasUtils"); var ASDrawContext = $('#activeSpeakerAudioLevel')[0].getContext('2d'); @@ -2797,7 +2814,7 @@ var Authentication = { }; module.exports = Authentication; -},{"../../xmpp/moderator":55,"./LoginDialog":13}],13:[function(require,module,exports){ +},{"../../xmpp/moderator":56,"./LoginDialog":13}],13:[function(require,module,exports){ /* global $, APP, config*/ var XMPP = require('../../xmpp/xmpp'); @@ -3026,7 +3043,7 @@ var LoginDialog = { }; module.exports = LoginDialog; -},{"../../xmpp/moderator":55,"../../xmpp/xmpp":63}],14:[function(require,module,exports){ +},{"../../xmpp/moderator":56,"../../xmpp/xmpp":64}],14:[function(require,module,exports){ var Settings = require("../../settings/Settings"); var MediaStreamType = require("../../../service/RTC/MediaStreamTypes"); @@ -3184,7 +3201,7 @@ var Avatar = { module.exports = Avatar; -},{"../../../service/RTC/MediaStreamTypes":95,"../../settings/Settings":45,"../videolayout/LargeVideo":33,"../videolayout/VideoLayout":37}],15:[function(require,module,exports){ +},{"../../../service/RTC/MediaStreamTypes":100,"../../settings/Settings":45,"../videolayout/LargeVideo":33,"../videolayout/VideoLayout":37}],15:[function(require,module,exports){ /* global $, config, setLargeVideoVisible, Util */ @@ -4570,7 +4587,7 @@ var Chat = (function (my) { return my; }(Chat || {})); module.exports = Chat; -},{"../../../../service/UI/UIEvents":100,"../../toolbars/ToolbarToggler":27,"../../util/NicknameHandler":30,"../../util/UIUtil":31,"../SidePanelToggler":18,"./Commands":20,"./Replacement":21,"./smileys.json":22}],20:[function(require,module,exports){ +},{"../../../../service/UI/UIEvents":105,"../../toolbars/ToolbarToggler":27,"../../util/NicknameHandler":30,"../../util/UIUtil":31,"../SidePanelToggler":18,"./Commands":20,"./Replacement":21,"./smileys.json":22}],20:[function(require,module,exports){ var UIUtil = require("../../util/UIUtil"); /** @@ -5078,7 +5095,7 @@ var SettingsMenu = { module.exports = SettingsMenu; -},{"../../../../service/translation/languages":105,"../../avatar/Avatar":14,"../../util/UIUtil":31,"./../../../settings/Settings":45}],25:[function(require,module,exports){ +},{"../../../../service/translation/languages":110,"../../avatar/Avatar":14,"../../util/UIUtil":31,"./../../../settings/Settings":45}],25:[function(require,module,exports){ var PanelToggler = require("../side_pannels/SidePanelToggler"); var buttonHandlers = { @@ -5772,7 +5789,7 @@ var Toolbar = (function (my) { }(Toolbar || {})); module.exports = Toolbar; -},{"../../../service/authentication/AuthenticationEvents":101,"../authentication/Authentication":12,"../etherpad/Etherpad":15,"../prezi/Prezi":16,"../side_pannels/SidePanelToggler":18,"../util/MessageHandler":29,"../util/UIUtil":31,"./BottomToolbar":25}],27:[function(require,module,exports){ +},{"../../../service/authentication/AuthenticationEvents":106,"../authentication/Authentication":12,"../etherpad/Etherpad":15,"../prezi/Prezi":16,"../side_pannels/SidePanelToggler":18,"../util/MessageHandler":29,"../util/UIUtil":31,"./BottomToolbar":25}],27:[function(require,module,exports){ /* global $, interfaceConfig, Moderator, DesktopStreaming.showDesktopSharingButton */ var toolbarTimeoutObject, @@ -6253,7 +6270,7 @@ var NickanameHandler = { }; module.exports = NickanameHandler; -},{"../../../service/UI/UIEvents":100}],31:[function(require,module,exports){ +},{"../../../service/UI/UIEvents":105}],31:[function(require,module,exports){ /** * Created by hristo on 12/22/14. */ @@ -7177,7 +7194,7 @@ var LargeVideo = { module.exports = LargeVideo; -},{"../../../service/UI/UIEvents":100,"../../xmpp/xmpp":63,"../avatar/Avatar":14,"../etherpad/Etherpad":15,"../prezi/Prezi":16,"../util/UIUtil":31}],34:[function(require,module,exports){ +},{"../../../service/UI/UIEvents":105,"../../xmpp/xmpp":64,"../avatar/Avatar":14,"../etherpad/Etherpad":15,"../prezi/Prezi":16,"../util/UIUtil":31}],34:[function(require,module,exports){ var SmallVideo = require("./SmallVideo"); var ConnectionIndicator = require("./ConnectionIndicator"); var NicknameHandler = require("../util/NicknameHandler"); @@ -8901,7 +8918,7 @@ var VideoLayout = (function (my) { my.onStatsStop = function () { for(var video in remoteVideos) { - video.hideIndicator(); + remoteVideos[video].hideIndicator(); } localVideoThumbnail.hideIndicator(); }; @@ -8977,7 +8994,7 @@ var VideoLayout = (function (my) { }(VideoLayout || {})); module.exports = VideoLayout; -},{"../../../service/RTC/MediaStreamTypes":95,"../../../service/UI/UIEvents":100,"../audio_levels/AudioLevels":10,"../avatar/Avatar":14,"../prezi/Prezi":16,"../side_pannels/contactlist/ContactList":23,"./LargeVideo":33,"./LocalVideo":34,"./RemoteVideo":35}],38:[function(require,module,exports){ +},{"../../../service/RTC/MediaStreamTypes":100,"../../../service/UI/UIEvents":105,"../audio_levels/AudioLevels":10,"../avatar/Avatar":14,"../prezi/Prezi":16,"../side_pannels/contactlist/ContactList":23,"./LargeVideo":33,"./LocalVideo":34,"./RemoteVideo":35}],38:[function(require,module,exports){ //var nouns = [ //]; var pluralNouns = [ @@ -9436,7 +9453,7 @@ var ConnectionQuality = { }; module.exports = ConnectionQuality; -},{"../../service/connectionquality/CQEvents":102,"../../service/xmpp/XMPPEvents":106,"events":107}],42:[function(require,module,exports){ +},{"../../service/connectionquality/CQEvents":107,"../../service/xmpp/XMPPEvents":111,"events":112}],42:[function(require,module,exports){ /* global $, alert, APP, changeLocalVideo, chrome, config, getConferenceHandler, getUserMediaWithConstraints */ /** @@ -9806,7 +9823,7 @@ module.exports = { }; -},{"../../service/desktopsharing/DesktopSharingEventTypes":103,"events":107}],43:[function(require,module,exports){ +},{"../../service/desktopsharing/DesktopSharingEventTypes":108,"events":112}],43:[function(require,module,exports){ //maps keycode to character, id of popover for given function and function var shortcuts = { 67: { @@ -10030,7 +10047,7 @@ var Members = { module.exports = Members; -},{"../../service/members/Events":104,"../../service/xmpp/XMPPEvents":106,"events":107}],45:[function(require,module,exports){ +},{"../../service/members/Events":109,"../../service/xmpp/XMPPEvents":111,"events":112}],45:[function(require,module,exports){ var email = ''; var displayName = ''; var userId; @@ -10970,7 +10987,84 @@ StatsCollector.prototype.processAudioLevelReport = function () }; -},{"../../service/RTC/RTCBrowserType":96}],48:[function(require,module,exports){ +},{"../../service/RTC/RTCBrowserType":101}],48:[function(require,module,exports){ +var callStats = null; + +function initCallback (err, msg) { + console.log("Initializing Status: err="+err+" msg="+msg); +} + +var CallStats = { + init: function (jingleSession) { + + if(!config.callStatsID || !config.callStatsSecret || callStats !== null) + return; + + callStats = new callstats($,io,jsSHA); + + this.session = jingleSession; + this.peerconnection = jingleSession.peerconnection.peerconnection; + + this.userID = APP.xmpp.myResource(); + + var roomJid = APP.UI.getRoomName(); + this.confID = roomJid? Strophe.getNodeFromJid(roomJid) : null; + + //userID is generated or given by the origin server + callStats.initialize(config.callStatsID, + config.callStatsSecret, + this.userID, + initCallback); + + var usage = callStats.fabricUsage.unbundled; + if(config.useBundle) + usage = callStats.fabricUsage.multiplex; + + callStats.addNewFabric(this.peerconnection, + Strophe.getResourceFromJid(jingleSession.peerjid), + usage, + this.confID, + this.pcCallback.bind(this)); + }, + pcCallback: function (err, msg) { + if(!callStats) + return; + console.log("Monitoring status: "+ err + " msg: " + msg); + callStats.sendFabricEvent(this.peerconnection, + callStats.fabricEvent.fabricSetup, this.confID); + }, + sendMuteEvent: function (mute, type) { + if(!callStats) + return; + var event = null; + if(type === "video") + { + event = (mute? callStats.fabricEvent.videoPause : + callStats.fabricEvent.videoResume); + } + else + { + event = (mute? callStats.fabricEvent.audioMute : + callStats.fabricEvent.audioUnmute); + } + callStats.sendFabricEvent(this.peerconnection, event, this.confID); + }, + sendTerminateEvent: function () { + if(!callStats) + return; + callStats.sendFabricEvent(this.peerconnection, + callStats.fabricEvent.fabricTerminated, this.confID); + }, + sendSetupFailedEvent: function () { + if(!callStats) + return; + callStats.sendFabricEvent(this.peerconnection, + callStats.fabricEvent.fabricSetupFailed, this.confID); + } + +}; +module.exports = CallStats; +},{}],49:[function(require,module,exports){ /** * Created by hristo on 8/4/14. */ @@ -10979,6 +11073,8 @@ var RTPStats = require("./RTPStatsCollector.js"); var EventEmitter = require("events"); var StreamEventTypes = require("../../service/RTC/StreamEventTypes.js"); var XMPPEvents = require("../../service/xmpp/XMPPEvents"); +var CallStats = require("./callstats"); +var RTCEvents = require("../../service/RTC/RTCEvents"); var eventEmitter = new EventEmitter(); @@ -11027,6 +11123,7 @@ function onStreamCreated(stream) } function onDisposeConference(onUnload) { + CallStats.sendTerminateEvent(); stopRemote(); if(onUnload) { stopLocal(); @@ -11092,8 +11189,22 @@ var statistics = APP.RTC.addStreamListener(onStreamCreated, StreamEventTypes.EVENT_TYPE_LOCAL_CREATED); APP.xmpp.addListener(XMPPEvents.DISPOSE_CONFERENCE, onDisposeConference); + //FIXME: we may want to change CALL INCOMING event to onnegotiationneeded APP.xmpp.addListener(XMPPEvents.CALL_INCOMING, function (event) { startRemoteStats(event.peerconnection); +// CallStats.init(event); + }); + APP.xmpp.addListener(XMPPEvents.PEERCONNECTION_READY, function (session) { + CallStats.init(session); + }) + APP.RTC.addListener(RTCEvents.AUDIO_MUTE, function (mute) { + CallStats.sendMuteEvent(mute, "audio"); + }); + APP.xmpp.addListener(XMPPEvents.CONFERENCE_SETUP_FAILED, function () { + CallStats.sendSetupFailedEvent(); + }) + APP.RTC.addListener(RTCEvents.VIDEO_MUTE, function (mute) { + CallStats.sendMuteEvent(mute, "video"); }); } @@ -11103,7 +11214,7 @@ var statistics = module.exports = statistics; -},{"../../service/RTC/StreamEventTypes.js":99,"../../service/xmpp/XMPPEvents":106,"./LocalStatsCollector.js":46,"./RTPStatsCollector.js":47,"events":107}],49:[function(require,module,exports){ +},{"../../service/RTC/RTCEvents":102,"../../service/RTC/StreamEventTypes.js":104,"../../service/xmpp/XMPPEvents":111,"./LocalStatsCollector.js":46,"./RTPStatsCollector.js":47,"./callstats":48,"events":112}],50:[function(require,module,exports){ var i18n = require("i18next-client"); var languages = require("../../service/translation/languages"); var Settings = require("../settings/Settings"); @@ -11242,18 +11353,18 @@ module.exports = { } }; -},{"../../service/translation/languages":105,"../settings/Settings":45,"i18next-client":65}],50:[function(require,module,exports){ +},{"../../service/translation/languages":110,"../settings/Settings":45,"i18next-client":66}],51:[function(require,module,exports){ /* jshint -W117 */ var TraceablePeerConnection = require("./TraceablePeerConnection"); var SDPDiffer = require("./SDPDiffer"); var SDPUtil = require("./SDPUtil"); var SDP = require("./SDP"); -var RTCBrowserType = require("../../service/RTC/RTCBrowserType"); var async = require("async"); var transform = require("sdp-transform"); +var XMPPEvents = require("../../service/xmpp/XMPPEvents"); // Jingle stuff -function JingleSession(me, sid, connection, service) { +function JingleSession(me, sid, connection, service, eventEmitter) { this.me = me; this.sid = sid; this.connection = connection; @@ -11292,6 +11403,7 @@ function JingleSession(me, sid, connection, service) { this.wait = true; this.localStreamsSSRC = null; + this.eventEmitter = eventEmitter; /** * The indicator which determines whether the (local) video has been muted @@ -11334,7 +11446,8 @@ JingleSession.prototype.initiate = function (peerjid, isInitiator) { this.peerconnection = new TraceablePeerConnection( this.connection.jingle.ice_config, - this.connection.jingle.pc_constraints ); + this.connection.jingle.pc_constraints, + this); this.peerconnection.onicecandidate = function (event) { self.sendIceCandidate(event.candidate); @@ -11370,9 +11483,15 @@ JingleSession.prototype.initiate = function (peerjid, isInitiator) { case 'disconnected': this.stopTime = new Date(); break; + case 'failed': + self.eventEmitter(XMPPEvents.CONFERENCE_SETUP_FAILED); + break; } onIceConnectionStateChange(self.sid, self); }; + this.peerconnection.onnegotiationneeded = function (event) { + self.eventEmitter.emit(XMPPEvents.PEERCONNECTION_READY, self); + }; // add any local and relayed stream APP.RTC.localStreams.forEach(function(stream) { self.peerconnection.addStream(stream.getOriginalStream()); @@ -11483,6 +11602,7 @@ JingleSession.prototype.accept = function () { }, function (e) { console.error('setLocalDescription failed', e); + self.eventEmitter.emit(XMPPEvents.CONFERENCE_SETUP_FAILED); } ); }; @@ -11693,6 +11813,7 @@ JingleSession.prototype.createdOffer = function (sdp) { }, function (e) { console.error('setLocalDescription failed', e); + self.eventEmitter.emit(XMPPEvents.CONFERENCE_SETUP_FAILED); } ); var cands = SDPUtil.find_lines(this.localSDP.raw, 'a=candidate:'); @@ -11867,6 +11988,7 @@ JingleSession.prototype.sendAnswer = function (provisional) { }, function (e) { console.error('createAnswer failed', e); + self.eventEmitter.emit(XMPPEvents.CONFERENCE_SETUP_FAILED); }, this.media_constraints ); @@ -11926,6 +12048,7 @@ JingleSession.prototype.createdAnswer = function (sdp, provisional) { }, function (e) { console.error('setLocalDescription failed', e); + self.eventEmitter.emit(XMPPEvents.CONFERENCE_SETUP_FAILED); } ); var cands = SDPUtil.find_lines(this.localSDP.raw, 'a=candidate:'); @@ -12477,6 +12600,7 @@ JingleSession.onJingleFatalError = function (session, error) this.connection.emuc.doLeave(); APP.UI.messageHandler.showError("dialog.sorry", "dialog.internalError"); + this.eventEmitter.emit(XMPPEvents.CONFERENCE_SETUP_FAILED); } JingleSession.prototype.setLocalDescription = function () { @@ -12532,6 +12656,7 @@ JingleSession.prototype.setLocalDescription = function () { function sendKeyframe(pc) { console.log('sendkeyframe', pc.iceConnectionState); if (pc.iceConnectionState !== 'connected') return; // safe... + var self = this; pc.setRemoteDescription( pc.remoteDescription, function () { @@ -12636,7 +12761,7 @@ JingleSession.prototype.remoteStreamAdded = function (data, times) { module.exports = JingleSession; -},{"../../service/RTC/RTCBrowserType":96,"./SDP":51,"./SDPDiffer":52,"./SDPUtil":53,"./TraceablePeerConnection":54,"async":64,"sdp-transform":92}],51:[function(require,module,exports){ +},{"../../service/xmpp/XMPPEvents":111,"./SDP":52,"./SDPDiffer":53,"./SDPUtil":54,"./TraceablePeerConnection":55,"async":65,"sdp-transform":97}],52:[function(require,module,exports){ /* jshint -W117 */ var SDPUtil = require("./SDPUtil"); @@ -13258,7 +13383,7 @@ SDP.prototype.jingle2media = function (content) { module.exports = SDP; -},{"./SDPUtil":53}],52:[function(require,module,exports){ +},{"./SDPUtil":54}],53:[function(require,module,exports){ function SDPDiffer(mySDP, otherSDP) { this.mySDP = mySDP; this.otherSDP = otherSDP; @@ -13424,7 +13549,7 @@ SDPDiffer.prototype.toJingle = function(modify) { }; module.exports = SDPDiffer; -},{}],53:[function(require,module,exports){ +},{}],54:[function(require,module,exports){ SDPUtil = { iceparams: function (mediadesc, sessiondesc) { var data = null; @@ -13774,8 +13899,10 @@ SDPUtil = { } }; module.exports = SDPUtil; -},{}],54:[function(require,module,exports){ -function TraceablePeerConnection(ice_config, constraints) { +},{}],55:[function(require,module,exports){ +var XMPPEvents = require("../../service/xmpp/XMPPEvents"); + +function TraceablePeerConnection(ice_config, constraints, session) { var self = this; var RTCPeerconnection = navigator.mozGetUserMedia ? mozRTCPeerConnection : webkitRTCPeerConnection; this.peerconnection = new RTCPeerconnection(ice_config, constraints); @@ -14101,7 +14228,7 @@ TraceablePeerConnection.prototype.getStats = function(callback, errback) { module.exports = TraceablePeerConnection; -},{"sdp-interop":86,"sdp-simulcast":89}],55:[function(require,module,exports){ +},{"../../service/xmpp/XMPPEvents":111,"sdp-interop":87,"sdp-simulcast":94}],56:[function(require,module,exports){ /* global $, $iq, APP, config, connection, UI, messageHandler, roomName, sessionTerminated, Strophe, Util */ var XMPPEvents = require("../../service/xmpp/XMPPEvents"); @@ -14537,7 +14664,7 @@ module.exports = Moderator; -},{"../../service/authentication/AuthenticationEvents":101,"../../service/xmpp/XMPPEvents":106,"../settings/Settings":45}],56:[function(require,module,exports){ +},{"../../service/authentication/AuthenticationEvents":106,"../../service/xmpp/XMPPEvents":111,"../settings/Settings":45}],57:[function(require,module,exports){ /* global $, $iq, config, connection, focusMucJid, messageHandler, Moderator, Toolbar, Util */ var Moderator = require("./moderator"); @@ -14693,7 +14820,7 @@ var Recording = { } module.exports = Recording; -},{"./moderator":55}],57:[function(require,module,exports){ +},{"./moderator":56}],58:[function(require,module,exports){ /* jshint -W117 */ /* a simple MUC connection plugin * can only handle a single MUC room @@ -15390,7 +15517,7 @@ module.exports = function(XMPP, eventEmitter) { }; -},{"../../service/xmpp/XMPPEvents":106,"./JingleSession":50,"./moderator":55}],58:[function(require,module,exports){ +},{"../../service/xmpp/XMPPEvents":111,"./JingleSession":51,"./moderator":56}],59:[function(require,module,exports){ /* jshint -W117 */ var JingleSession = require("./JingleSession"); @@ -15512,7 +15639,7 @@ module.exports = function(XMPP, eventEmitter) } sess = new JingleSession( $(iq).attr('to'), $(iq).find('jingle').attr('sid'), - this.connection, XMPP); + this.connection, XMPP, eventEmitter); // configure session sess.media_constraints = this.media_constraints; @@ -15592,7 +15719,7 @@ module.exports = function(XMPP, eventEmitter) initiate: function (peerjid, myjid) { // initiate a new jinglesession to peerjid var sess = new JingleSession(myjid || this.connection.jid, Math.random().toString(36).substr(2, 12), // random string - this.connection, XMPP); + this.connection, XMPP, eventEmitter); // configure session sess.media_constraints = this.media_constraints; @@ -15739,7 +15866,7 @@ module.exports = function(XMPP, eventEmitter) }; -},{"../../service/xmpp/XMPPEvents":106,"./JingleSession":50}],59:[function(require,module,exports){ +},{"../../service/xmpp/XMPPEvents":111,"./JingleSession":51}],60:[function(require,module,exports){ /* global Strophe */ module.exports = function () { @@ -15760,7 +15887,7 @@ module.exports = function () { } }); }; -},{}],60:[function(require,module,exports){ +},{}],61:[function(require,module,exports){ /* global $, $iq, config, connection, focusMucJid, forceMuted, setAudioMuted, Strophe */ /** @@ -15819,7 +15946,7 @@ module.exports = function (XMPP) { } }); } -},{}],61:[function(require,module,exports){ +},{}],62:[function(require,module,exports){ /* jshint -W117 */ module.exports = function() { Strophe.addConnectionPlugin('rayo', @@ -15916,7 +16043,7 @@ module.exports = function() { ); }; -},{}],62:[function(require,module,exports){ +},{}],63:[function(require,module,exports){ /** * Strophe logger implementation. Logs from level WARN and above. */ @@ -15960,7 +16087,7 @@ module.exports = function () { }; }; -},{}],63:[function(require,module,exports){ +},{}],64:[function(require,module,exports){ /* global $, APP, config, Strophe*/ var Moderator = require("./moderator"); var EventEmitter = require("events"); @@ -16278,7 +16405,6 @@ var XMPP = { return Strophe.getResourceFromJid(connection.emuc.myroomjid); }, disposeConference: function (onUnload) { - eventEmitter.emit(XMPPEvents.DISPOSE_CONFERENCE, onUnload); var handler = connection.jingle.activecall; if (handler && handler.peerconnection) { // FIXME: probably removing streams is not required and close() should @@ -16293,6 +16419,7 @@ var XMPP = { } handler.peerconnection.close(); } + eventEmitter.emit(XMPPEvents.DISPOSE_CONFERENCE, onUnload); connection.jingle.activecall = null; if(!onUnload) { @@ -16573,7 +16700,7 @@ var XMPP = { module.exports = XMPP; -},{"../../service/RTC/RTCEvents":97,"../../service/RTC/StreamEventTypes":99,"../../service/UI/UIEvents":100,"../../service/xmpp/XMPPEvents":106,"../settings/Settings":45,"./SDP":51,"./moderator":55,"./recording":56,"./strophe.emuc":57,"./strophe.jingle":58,"./strophe.logger":59,"./strophe.moderate":60,"./strophe.rayo":61,"./strophe.util":62,"events":107,"pako":66,"retry":82}],64:[function(require,module,exports){ +},{"../../service/RTC/RTCEvents":102,"../../service/RTC/StreamEventTypes":104,"../../service/UI/UIEvents":105,"../../service/xmpp/XMPPEvents":111,"../settings/Settings":45,"./SDP":52,"./moderator":56,"./recording":57,"./strophe.emuc":58,"./strophe.jingle":59,"./strophe.logger":60,"./strophe.moderate":61,"./strophe.rayo":62,"./strophe.util":63,"events":112,"pako":67,"retry":83}],65:[function(require,module,exports){ (function (process){ /*! * async @@ -17700,7 +17827,7 @@ module.exports = XMPP; }()); }).call(this,require('_process')) -},{"_process":108}],65:[function(require,module,exports){ +},{"_process":113}],66:[function(require,module,exports){ // i18next, v1.7.7 // Copyright (c)2014 Jan Mühlemann (jamuhl). // Distributed under MIT license @@ -19823,7 +19950,7 @@ module.exports = XMPP; i18n.options = o; })(); -},{"jquery":"jquery"}],66:[function(require,module,exports){ +},{"jquery":"jquery"}],67:[function(require,module,exports){ // Top level file is just a mixin of submodules & constants 'use strict'; @@ -19839,7 +19966,7 @@ assign(pako, deflate, inflate, constants); module.exports = pako; -},{"./lib/deflate":67,"./lib/inflate":68,"./lib/utils/common":69,"./lib/zlib/constants":72}],67:[function(require,module,exports){ +},{"./lib/deflate":68,"./lib/inflate":69,"./lib/utils/common":70,"./lib/zlib/constants":73}],68:[function(require,module,exports){ 'use strict'; @@ -20217,7 +20344,7 @@ exports.deflate = deflate; exports.deflateRaw = deflateRaw; exports.gzip = gzip; -},{"./utils/common":69,"./utils/strings":70,"./zlib/deflate.js":74,"./zlib/messages":79,"./zlib/zstream":81}],68:[function(require,module,exports){ +},{"./utils/common":70,"./utils/strings":71,"./zlib/deflate.js":75,"./zlib/messages":80,"./zlib/zstream":82}],69:[function(require,module,exports){ 'use strict'; @@ -20598,7 +20725,7 @@ exports.inflate = inflate; exports.inflateRaw = inflateRaw; exports.ungzip = inflate; -},{"./utils/common":69,"./utils/strings":70,"./zlib/constants":72,"./zlib/gzheader":75,"./zlib/inflate.js":77,"./zlib/messages":79,"./zlib/zstream":81}],69:[function(require,module,exports){ +},{"./utils/common":70,"./utils/strings":71,"./zlib/constants":73,"./zlib/gzheader":76,"./zlib/inflate.js":78,"./zlib/messages":80,"./zlib/zstream":82}],70:[function(require,module,exports){ 'use strict'; @@ -20702,7 +20829,7 @@ exports.setTyped = function (on) { exports.setTyped(TYPED_OK); -},{}],70:[function(require,module,exports){ +},{}],71:[function(require,module,exports){ // String encode/decode helpers 'use strict'; @@ -20889,7 +21016,7 @@ exports.utf8border = function(buf, max) { return (pos + _utf8len[buf[pos]] > max) ? pos : max; }; -},{"./common":69}],71:[function(require,module,exports){ +},{"./common":70}],72:[function(require,module,exports){ 'use strict'; // Note: adler32 takes 12% for level 0 and 2% for level 6. @@ -20923,7 +21050,7 @@ function adler32(adler, buf, len, pos) { module.exports = adler32; -},{}],72:[function(require,module,exports){ +},{}],73:[function(require,module,exports){ module.exports = { /* Allowed flush values; see deflate() and inflate() below for details */ @@ -20972,7 +21099,7 @@ module.exports = { //Z_NULL: null // Use -1 or null inline, depending on var type }; -},{}],73:[function(require,module,exports){ +},{}],74:[function(require,module,exports){ 'use strict'; // Note: we can't get significant speed boost here. @@ -21015,7 +21142,7 @@ function crc32(crc, buf, len, pos) { module.exports = crc32; -},{}],74:[function(require,module,exports){ +},{}],75:[function(require,module,exports){ 'use strict'; var utils = require('../utils/common'); @@ -22782,7 +22909,7 @@ exports.deflatePrime = deflatePrime; exports.deflateTune = deflateTune; */ -},{"../utils/common":69,"./adler32":71,"./crc32":73,"./messages":79,"./trees":80}],75:[function(require,module,exports){ +},{"../utils/common":70,"./adler32":72,"./crc32":74,"./messages":80,"./trees":81}],76:[function(require,module,exports){ 'use strict'; @@ -22824,7 +22951,7 @@ function GZheader() { module.exports = GZheader; -},{}],76:[function(require,module,exports){ +},{}],77:[function(require,module,exports){ 'use strict'; // See state defs from inflate.js @@ -23151,7 +23278,7 @@ module.exports = function inflate_fast(strm, start) { return; }; -},{}],77:[function(require,module,exports){ +},{}],78:[function(require,module,exports){ 'use strict'; @@ -24656,7 +24783,7 @@ exports.inflateSyncPoint = inflateSyncPoint; exports.inflateUndermine = inflateUndermine; */ -},{"../utils/common":69,"./adler32":71,"./crc32":73,"./inffast":76,"./inftrees":78}],78:[function(require,module,exports){ +},{"../utils/common":70,"./adler32":72,"./crc32":74,"./inffast":77,"./inftrees":79}],79:[function(require,module,exports){ 'use strict'; @@ -24985,7 +25112,7 @@ module.exports = function inflate_table(type, lens, lens_index, codes, table, ta return 0; }; -},{"../utils/common":69}],79:[function(require,module,exports){ +},{"../utils/common":70}],80:[function(require,module,exports){ 'use strict'; module.exports = { @@ -25000,7 +25127,7 @@ module.exports = { '-6': 'incompatible version' /* Z_VERSION_ERROR (-6) */ }; -},{}],80:[function(require,module,exports){ +},{}],81:[function(require,module,exports){ 'use strict'; @@ -26201,7 +26328,7 @@ exports._tr_flush_block = _tr_flush_block; exports._tr_tally = _tr_tally; exports._tr_align = _tr_align; -},{"../utils/common":69}],81:[function(require,module,exports){ +},{"../utils/common":70}],82:[function(require,module,exports){ 'use strict'; @@ -26232,9 +26359,9 @@ function ZStream() { module.exports = ZStream; -},{}],82:[function(require,module,exports){ +},{}],83:[function(require,module,exports){ module.exports = require('./lib/retry'); -},{"./lib/retry":83}],83:[function(require,module,exports){ +},{"./lib/retry":84}],84:[function(require,module,exports){ var RetryOperation = require('./retry_operation'); exports.operation = function(options) { @@ -26285,7 +26412,7 @@ exports._createTimeout = function(attempt, opts) { return timeout; }; -},{"./retry_operation":84}],84:[function(require,module,exports){ +},{"./retry_operation":85}],85:[function(require,module,exports){ function RetryOperation(timeouts) { this._timeouts = timeouts; this._fn = null; @@ -26395,7 +26522,7 @@ RetryOperation.prototype.mainError = function() { return mainError; }; -},{}],85:[function(require,module,exports){ +},{}],86:[function(require,module,exports){ module.exports = function arrayEquals(array) { // if the other array is a falsy value, return if (!array) @@ -26421,10 +26548,10 @@ module.exports = function arrayEquals(array) { } -},{}],86:[function(require,module,exports){ +},{}],87:[function(require,module,exports){ exports.Interop = require('./interop'); -},{"./interop":87}],87:[function(require,module,exports){ +},{"./interop":88}],88:[function(require,module,exports){ "use strict"; var transform = require('./transform'); @@ -27006,7 +27133,7 @@ Interop.prototype.toUnifiedPlan = function(desc) { //#endregion }; -},{"./array-equals":85,"./transform":88}],88:[function(require,module,exports){ +},{"./array-equals":86,"./transform":89}],89:[function(require,module,exports){ var transform = require('sdp-transform'); exports.write = function(session, opts) { @@ -27105,7 +27232,477 @@ exports.parse = function(sdp) { }; -},{"sdp-transform":92}],89:[function(require,module,exports){ +},{"sdp-transform":91}],90:[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"; + } + }); +}); + +},{}],91:[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":92,"./writer":93}],92:[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":90}],93:[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":90}],94:[function(require,module,exports){ var transform = require('sdp-transform'); var transformUtils = require('./transform-utils'); var parseSsrcs = transformUtils.parseSsrcs; @@ -27507,7 +28104,7 @@ Simulcast.prototype.mungeLocalDescription = function (desc) { module.exports = Simulcast; -},{"./transform-utils":90,"sdp-transform":92}],90:[function(require,module,exports){ +},{"./transform-utils":95,"sdp-transform":97}],95:[function(require,module,exports){ exports.writeSsrcs = function(sources, order) { var ssrcs = []; @@ -27558,484 +28155,22 @@ exports.parseSsrcs = function (mLine) { }; -},{}],91:[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"; - } - }); -}); - -},{}],92:[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":93,"./writer":94}],93:[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":91}],94:[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":91}],95:[function(require,module,exports){ +},{}],96:[function(require,module,exports){ +arguments[4][90][0].apply(exports,arguments) +},{"dup":90}],97:[function(require,module,exports){ +arguments[4][91][0].apply(exports,arguments) +},{"./parser":98,"./writer":99,"dup":91}],98:[function(require,module,exports){ +arguments[4][92][0].apply(exports,arguments) +},{"./grammar":96,"dup":92}],99:[function(require,module,exports){ +arguments[4][93][0].apply(exports,arguments) +},{"./grammar":96,"dup":93}],100:[function(require,module,exports){ var MediaStreamType = { VIDEO_TYPE: "Video", AUDIO_TYPE: "Audio" }; module.exports = MediaStreamType; -},{}],96:[function(require,module,exports){ +},{}],101:[function(require,module,exports){ var RTCBrowserType = { RTC_BROWSER_CHROME: "rtc_browser.chrome", @@ -28043,16 +28178,18 @@ var RTCBrowserType = { }; module.exports = RTCBrowserType; -},{}],97:[function(require,module,exports){ +},{}],102:[function(require,module,exports){ var RTCEvents = { LASTN_CHANGED: "rtc.lastn_changed", DOMINANTSPEAKER_CHANGED: "rtc.dominantspeaker_changed", LASTN_ENDPOINT_CHANGED: "rtc.lastn_endpoint_changed", - AVAILABLE_DEVICES_CHANGED: "rtc.available_devices_changed" + AVAILABLE_DEVICES_CHANGED: "rtc.available_devices_changed", + AUDIO_MUTE: "rtc.audio_mute", + VIDEO_MUTE: "rtc.video_mute" }; module.exports = RTCEvents; -},{}],98:[function(require,module,exports){ +},{}],103:[function(require,module,exports){ var Resolutions = { "1080": { width: 1920, @@ -28106,7 +28243,7 @@ var Resolutions = { } }; module.exports = Resolutions; -},{}],99:[function(require,module,exports){ +},{}],104:[function(require,module,exports){ var StreamEventTypes = { EVENT_TYPE_LOCAL_CREATED: "stream.local_created", @@ -28122,14 +28259,14 @@ var StreamEventTypes = { }; module.exports = StreamEventTypes; -},{}],100:[function(require,module,exports){ +},{}],105:[function(require,module,exports){ var UIEvents = { NICKNAME_CHANGED: "UI.nickname_changed", SELECTED_ENDPOINT: "UI.selected_endpoint", PINNED_ENDPOINT: "UI.pinned_endpoint" }; module.exports = UIEvents; -},{}],101:[function(require,module,exports){ +},{}],106:[function(require,module,exports){ var AuthenticationEvents = { /** * Event callback arguments: @@ -28143,7 +28280,7 @@ var AuthenticationEvents = { }; module.exports = AuthenticationEvents; -},{}],102:[function(require,module,exports){ +},{}],107:[function(require,module,exports){ var CQEvents = { LOCALSTATS_UPDATED: "cq.localstats_updated", REMOTESTATS_UPDATED: "cq.remotestats_updated", @@ -28151,7 +28288,7 @@ var CQEvents = { }; module.exports = CQEvents; -},{}],103:[function(require,module,exports){ +},{}],108:[function(require,module,exports){ var DesktopSharingEventTypes = { INIT: "ds.init", @@ -28161,14 +28298,14 @@ var DesktopSharingEventTypes = { }; module.exports = DesktopSharingEventTypes; -},{}],104:[function(require,module,exports){ +},{}],109:[function(require,module,exports){ var Events = { DTMF_SUPPORT_CHANGED: "members.dtmf_support_changed" }; module.exports = Events; -},{}],105:[function(require,module,exports){ +},{}],110:[function(require,module,exports){ module.exports = { getLanguages : function () { var languages = []; @@ -28185,11 +28322,10 @@ module.exports = { TR: "tr", FR: "fr" } -},{}],106:[function(require,module,exports){ +},{}],111:[function(require,module,exports){ var XMPPEvents = { CONNECTION_FAILED: "xmpp.connection.failed", CONFERENCE_CREATED: "xmpp.conferenceCreated.jingle", - CALL_TERMINATED: "xmpp.callterminated.jingle", CALL_INCOMING: "xmpp.callincoming.jingle", DISPOSE_CONFERENCE: "xmpp.dispose_conference", GRACEFUL_SHUTDOWN: "xmpp.graceful_shutdown", @@ -28218,10 +28354,12 @@ var XMPPEvents = { CHAT_ERROR_RECEIVED: "xmpp.chat_error_received", ETHERPAD: "xmpp.etherpad", DEVICE_AVAILABLE: "xmpp.device_available", - START_MUTED: "xmpp.start_muted" + START_MUTED: "xmpp.start_muted", + PEERCONNECTION_READY: "xmpp.peerconnection_ready", + CONFERENCE_SETUP_FAILED: "xmpp.conference_setup_failed" }; module.exports = XMPPEvents; -},{}],107:[function(require,module,exports){ +},{}],112:[function(require,module,exports){ // Copyright Joyent, Inc. and other Node contributors. // // Permission is hereby granted, free of charge, to any person obtaining a @@ -28524,73 +28662,43 @@ function isUndefined(arg) { return arg === void 0; } -},{}],108:[function(require,module,exports){ +},{}],113:[function(require,module,exports){ // shim for using process in browser var process = module.exports = {}; +var queue = []; +var draining = false; -process.nextTick = (function () { - var canSetImmediate = typeof window !== 'undefined' - && window.setImmediate; - var canMutationObserver = typeof window !== 'undefined' - && window.MutationObserver; - var canPost = typeof window !== 'undefined' - && window.postMessage && window.addEventListener - ; - - if (canSetImmediate) { - return function (f) { return window.setImmediate(f) }; +function drainQueue() { + if (draining) { + return; } - - var queue = []; - - if (canMutationObserver) { - var hiddenDiv = document.createElement("div"); - var observer = new MutationObserver(function () { - var queueList = queue.slice(); - queue.length = 0; - queueList.forEach(function (fn) { - fn(); - }); - }); - - observer.observe(hiddenDiv, { attributes: true }); - - return function nextTick(fn) { - if (!queue.length) { - hiddenDiv.setAttribute('yes', 'no'); - } - queue.push(fn); - }; + draining = true; + var currentQueue; + var len = queue.length; + while(len) { + currentQueue = queue; + queue = []; + var i = -1; + while (++i < len) { + currentQueue[i](); + } + len = queue.length; } - - if (canPost) { - window.addEventListener('message', function (ev) { - var source = ev.source; - if ((source === window || source === null) && ev.data === 'process-tick') { - ev.stopPropagation(); - if (queue.length > 0) { - var fn = queue.shift(); - fn(); - } - } - }, true); - - return function nextTick(fn) { - queue.push(fn); - window.postMessage('process-tick', '*'); - }; + draining = false; +} +process.nextTick = function (fun) { + queue.push(fun); + if (!draining) { + setTimeout(drainQueue, 0); } - - return function nextTick(fn) { - setTimeout(fn, 0); - }; -})(); +}; process.title = 'browser'; process.browser = true; process.env = {}; process.argv = []; +process.version = ''; // empty string to avoid regexp issues function noop() {} @@ -28611,6 +28719,7 @@ process.cwd = function () { return '/' }; process.chdir = function (dir) { throw new Error('process.chdir is not supported'); }; +process.umask = function() { return 0; }; },{}]},{},[1])(1) }); \ No newline at end of file diff --git a/modules/RTC/LocalStream.js b/modules/RTC/LocalStream.js index 69d8efb6f..342fbb57c 100644 --- a/modules/RTC/LocalStream.js +++ b/modules/RTC/LocalStream.js @@ -1,4 +1,5 @@ var StreamEventTypes = require("../../service/RTC/StreamEventTypes.js"); +var RTCEvents = require("../../service/RTC/RTCEvents"); function LocalStream(stream, type, eventEmitter, videoType, isGUMStream) @@ -54,12 +55,18 @@ LocalStream.prototype.setMute = function(mute) for (var idx = 0; idx < tracks.length; idx++) { tracks[idx].enabled = mute; } + this.eventEmitter.emit( + (this.type == "audio"? RTCEvents.AUDIO_MUTE : RTCEvents.VIDEO_MUTE), + !mute); } else { if(mute === false) { APP.xmpp.removeStream(this.stream); this.stream.stop(); + this.eventEmitter.emit( + (this.type == "audio"? RTCEvents.AUDIO_MUTE : RTCEvents.VIDEO_MUTE), + true); } else { @@ -69,11 +76,21 @@ LocalStream.prototype.setMute = function(mute) function (stream) { if(self.isAudioStream()) { - APP.RTC.changeLocalAudio(stream, function () {}); + APP.RTC.changeLocalAudio(stream, + function () { + self.eventEmitter.emit( + (self.type == "audio"? RTCEvents.AUDIO_MUTE : RTCEvents.VIDEO_MUTE), + true); + }); } else { - APP.RTC.changeLocalVideo(stream, false, function () {}); + APP.RTC.changeLocalVideo(stream, false, + function () { + self.eventEmitter.emit( + (self.type == "audio"? RTCEvents.AUDIO_MUTE : RTCEvents.VIDEO_MUTE), + true); + }); } }); } diff --git a/modules/UI/videolayout/VideoLayout.js b/modules/UI/videolayout/VideoLayout.js index 85d5ce1a3..a7bcdda56 100644 --- a/modules/UI/videolayout/VideoLayout.js +++ b/modules/UI/videolayout/VideoLayout.js @@ -808,7 +808,7 @@ var VideoLayout = (function (my) { my.onStatsStop = function () { for(var video in remoteVideos) { - video.hideIndicator(); + remoteVideos[video].hideIndicator(); } localVideoThumbnail.hideIndicator(); }; diff --git a/modules/statistics/CallStats.js b/modules/statistics/CallStats.js new file mode 100644 index 000000000..7453d2aa7 --- /dev/null +++ b/modules/statistics/CallStats.js @@ -0,0 +1,76 @@ +var callStats = null; + +function initCallback (err, msg) { + console.log("Initializing Status: err="+err+" msg="+msg); +} + +var CallStats = { + init: function (jingleSession) { + + if(!config.callStatsID || !config.callStatsSecret || callStats !== null) + return; + + callStats = new callstats($,io,jsSHA); + + this.session = jingleSession; + this.peerconnection = jingleSession.peerconnection.peerconnection; + + this.userID = APP.xmpp.myResource(); + + var roomJid = APP.UI.getRoomName(); + this.confID = roomJid? Strophe.getNodeFromJid(roomJid) : null; + + //userID is generated or given by the origin server + callStats.initialize(config.callStatsID, + config.callStatsSecret, + this.userID, + initCallback); + + var usage = callStats.fabricUsage.unbundled; + if(config.useBundle) + usage = callStats.fabricUsage.multiplex; + + callStats.addNewFabric(this.peerconnection, + Strophe.getResourceFromJid(jingleSession.peerjid), + usage, + this.confID, + this.pcCallback.bind(this)); + }, + pcCallback: function (err, msg) { + if(!callStats) + return; + console.log("Monitoring status: "+ err + " msg: " + msg); + callStats.sendFabricEvent(this.peerconnection, + callStats.fabricEvent.fabricSetup, this.confID); + }, + sendMuteEvent: function (mute, type) { + if(!callStats) + return; + var event = null; + if(type === "video") + { + event = (mute? callStats.fabricEvent.videoPause : + callStats.fabricEvent.videoResume); + } + else + { + event = (mute? callStats.fabricEvent.audioMute : + callStats.fabricEvent.audioUnmute); + } + callStats.sendFabricEvent(this.peerconnection, event, this.confID); + }, + sendTerminateEvent: function () { + if(!callStats) + return; + callStats.sendFabricEvent(this.peerconnection, + callStats.fabricEvent.fabricTerminated, this.confID); + }, + sendSetupFailedEvent: function () { + if(!callStats) + return; + callStats.sendFabricEvent(this.peerconnection, + callStats.fabricEvent.fabricSetupFailed, this.confID); + } + +}; +module.exports = CallStats; \ No newline at end of file diff --git a/modules/statistics/statistics.js b/modules/statistics/statistics.js index 1031ec5ed..9889cc69a 100644 --- a/modules/statistics/statistics.js +++ b/modules/statistics/statistics.js @@ -6,6 +6,8 @@ var RTPStats = require("./RTPStatsCollector.js"); var EventEmitter = require("events"); var StreamEventTypes = require("../../service/RTC/StreamEventTypes.js"); var XMPPEvents = require("../../service/xmpp/XMPPEvents"); +var CallStats = require("./callstats"); +var RTCEvents = require("../../service/RTC/RTCEvents"); var eventEmitter = new EventEmitter(); @@ -54,6 +56,7 @@ function onStreamCreated(stream) } function onDisposeConference(onUnload) { + CallStats.sendTerminateEvent(); stopRemote(); if(onUnload) { stopLocal(); @@ -119,8 +122,22 @@ var statistics = APP.RTC.addStreamListener(onStreamCreated, StreamEventTypes.EVENT_TYPE_LOCAL_CREATED); APP.xmpp.addListener(XMPPEvents.DISPOSE_CONFERENCE, onDisposeConference); + //FIXME: we may want to change CALL INCOMING event to onnegotiationneeded APP.xmpp.addListener(XMPPEvents.CALL_INCOMING, function (event) { startRemoteStats(event.peerconnection); +// CallStats.init(event); + }); + APP.xmpp.addListener(XMPPEvents.PEERCONNECTION_READY, function (session) { + CallStats.init(session); + }) + APP.RTC.addListener(RTCEvents.AUDIO_MUTE, function (mute) { + CallStats.sendMuteEvent(mute, "audio"); + }); + APP.xmpp.addListener(XMPPEvents.CONFERENCE_SETUP_FAILED, function () { + CallStats.sendSetupFailedEvent(); + }) + APP.RTC.addListener(RTCEvents.VIDEO_MUTE, function (mute) { + CallStats.sendMuteEvent(mute, "video"); }); } diff --git a/modules/xmpp/JingleSession.js b/modules/xmpp/JingleSession.js index 8a91706ef..99253cf37 100644 --- a/modules/xmpp/JingleSession.js +++ b/modules/xmpp/JingleSession.js @@ -3,12 +3,12 @@ var TraceablePeerConnection = require("./TraceablePeerConnection"); var SDPDiffer = require("./SDPDiffer"); var SDPUtil = require("./SDPUtil"); var SDP = require("./SDP"); -var RTCBrowserType = require("../../service/RTC/RTCBrowserType"); var async = require("async"); var transform = require("sdp-transform"); +var XMPPEvents = require("../../service/xmpp/XMPPEvents"); // Jingle stuff -function JingleSession(me, sid, connection, service) { +function JingleSession(me, sid, connection, service, eventEmitter) { this.me = me; this.sid = sid; this.connection = connection; @@ -47,6 +47,7 @@ function JingleSession(me, sid, connection, service) { this.wait = true; this.localStreamsSSRC = null; + this.eventEmitter = eventEmitter; /** * The indicator which determines whether the (local) video has been muted @@ -89,7 +90,8 @@ JingleSession.prototype.initiate = function (peerjid, isInitiator) { this.peerconnection = new TraceablePeerConnection( this.connection.jingle.ice_config, - this.connection.jingle.pc_constraints ); + this.connection.jingle.pc_constraints, + this); this.peerconnection.onicecandidate = function (event) { self.sendIceCandidate(event.candidate); @@ -125,9 +127,15 @@ JingleSession.prototype.initiate = function (peerjid, isInitiator) { case 'disconnected': this.stopTime = new Date(); break; + case 'failed': + self.eventEmitter(XMPPEvents.CONFERENCE_SETUP_FAILED); + break; } onIceConnectionStateChange(self.sid, self); }; + this.peerconnection.onnegotiationneeded = function (event) { + self.eventEmitter.emit(XMPPEvents.PEERCONNECTION_READY, self); + }; // add any local and relayed stream APP.RTC.localStreams.forEach(function(stream) { self.peerconnection.addStream(stream.getOriginalStream()); @@ -238,6 +246,7 @@ JingleSession.prototype.accept = function () { }, function (e) { console.error('setLocalDescription failed', e); + self.eventEmitter.emit(XMPPEvents.CONFERENCE_SETUP_FAILED); } ); }; @@ -448,6 +457,7 @@ JingleSession.prototype.createdOffer = function (sdp) { }, function (e) { console.error('setLocalDescription failed', e); + self.eventEmitter.emit(XMPPEvents.CONFERENCE_SETUP_FAILED); } ); var cands = SDPUtil.find_lines(this.localSDP.raw, 'a=candidate:'); @@ -622,6 +632,7 @@ JingleSession.prototype.sendAnswer = function (provisional) { }, function (e) { console.error('createAnswer failed', e); + self.eventEmitter.emit(XMPPEvents.CONFERENCE_SETUP_FAILED); }, this.media_constraints ); @@ -681,6 +692,7 @@ JingleSession.prototype.createdAnswer = function (sdp, provisional) { }, function (e) { console.error('setLocalDescription failed', e); + self.eventEmitter.emit(XMPPEvents.CONFERENCE_SETUP_FAILED); } ); var cands = SDPUtil.find_lines(this.localSDP.raw, 'a=candidate:'); @@ -1232,6 +1244,7 @@ JingleSession.onJingleFatalError = function (session, error) this.connection.emuc.doLeave(); APP.UI.messageHandler.showError("dialog.sorry", "dialog.internalError"); + this.eventEmitter.emit(XMPPEvents.CONFERENCE_SETUP_FAILED); } JingleSession.prototype.setLocalDescription = function () { @@ -1287,6 +1300,7 @@ JingleSession.prototype.setLocalDescription = function () { function sendKeyframe(pc) { console.log('sendkeyframe', pc.iceConnectionState); if (pc.iceConnectionState !== 'connected') return; // safe... + var self = this; pc.setRemoteDescription( pc.remoteDescription, function () { diff --git a/modules/xmpp/TraceablePeerConnection.js b/modules/xmpp/TraceablePeerConnection.js index 50d2e4a18..519097438 100644 --- a/modules/xmpp/TraceablePeerConnection.js +++ b/modules/xmpp/TraceablePeerConnection.js @@ -1,4 +1,6 @@ -function TraceablePeerConnection(ice_config, constraints) { +var XMPPEvents = require("../../service/xmpp/XMPPEvents"); + +function TraceablePeerConnection(ice_config, constraints, session) { var self = this; var RTCPeerconnection = navigator.mozGetUserMedia ? mozRTCPeerConnection : webkitRTCPeerConnection; this.peerconnection = new RTCPeerconnection(ice_config, constraints); diff --git a/modules/xmpp/strophe.jingle.js b/modules/xmpp/strophe.jingle.js index 491c54c19..e72fba575 100644 --- a/modules/xmpp/strophe.jingle.js +++ b/modules/xmpp/strophe.jingle.js @@ -119,7 +119,7 @@ module.exports = function(XMPP, eventEmitter) } sess = new JingleSession( $(iq).attr('to'), $(iq).find('jingle').attr('sid'), - this.connection, XMPP); + this.connection, XMPP, eventEmitter); // configure session sess.media_constraints = this.media_constraints; @@ -199,7 +199,7 @@ module.exports = function(XMPP, eventEmitter) initiate: function (peerjid, myjid) { // initiate a new jinglesession to peerjid var sess = new JingleSession(myjid || this.connection.jid, Math.random().toString(36).substr(2, 12), // random string - this.connection, XMPP); + this.connection, XMPP, eventEmitter); // configure session sess.media_constraints = this.media_constraints; diff --git a/modules/xmpp/xmpp.js b/modules/xmpp/xmpp.js index c9f29d01e..6587bf524 100644 --- a/modules/xmpp/xmpp.js +++ b/modules/xmpp/xmpp.js @@ -315,7 +315,6 @@ var XMPP = { return Strophe.getResourceFromJid(connection.emuc.myroomjid); }, disposeConference: function (onUnload) { - eventEmitter.emit(XMPPEvents.DISPOSE_CONFERENCE, onUnload); var handler = connection.jingle.activecall; if (handler && handler.peerconnection) { // FIXME: probably removing streams is not required and close() should @@ -330,6 +329,7 @@ var XMPP = { } handler.peerconnection.close(); } + eventEmitter.emit(XMPPEvents.DISPOSE_CONFERENCE, onUnload); connection.jingle.activecall = null; if(!onUnload) { diff --git a/service/RTC/RTCEvents.js b/service/RTC/RTCEvents.js index a53115712..3944c706f 100644 --- a/service/RTC/RTCEvents.js +++ b/service/RTC/RTCEvents.js @@ -2,7 +2,9 @@ var RTCEvents = { LASTN_CHANGED: "rtc.lastn_changed", DOMINANTSPEAKER_CHANGED: "rtc.dominantspeaker_changed", LASTN_ENDPOINT_CHANGED: "rtc.lastn_endpoint_changed", - AVAILABLE_DEVICES_CHANGED: "rtc.available_devices_changed" + AVAILABLE_DEVICES_CHANGED: "rtc.available_devices_changed", + AUDIO_MUTE: "rtc.audio_mute", + VIDEO_MUTE: "rtc.video_mute" }; module.exports = RTCEvents; \ No newline at end of file diff --git a/service/xmpp/XMPPEvents.js b/service/xmpp/XMPPEvents.js index 66047ff1c..68b6ed8a7 100644 --- a/service/xmpp/XMPPEvents.js +++ b/service/xmpp/XMPPEvents.js @@ -1,7 +1,6 @@ var XMPPEvents = { CONNECTION_FAILED: "xmpp.connection.failed", CONFERENCE_CREATED: "xmpp.conferenceCreated.jingle", - CALL_TERMINATED: "xmpp.callterminated.jingle", CALL_INCOMING: "xmpp.callincoming.jingle", DISPOSE_CONFERENCE: "xmpp.dispose_conference", GRACEFUL_SHUTDOWN: "xmpp.graceful_shutdown", @@ -30,6 +29,8 @@ var XMPPEvents = { CHAT_ERROR_RECEIVED: "xmpp.chat_error_received", ETHERPAD: "xmpp.etherpad", DEVICE_AVAILABLE: "xmpp.device_available", - START_MUTED: "xmpp.start_muted" + START_MUTED: "xmpp.start_muted", + PEERCONNECTION_READY: "xmpp.peerconnection_ready", + CONFERENCE_SETUP_FAILED: "xmpp.conference_setup_failed" }; module.exports = XMPPEvents; \ No newline at end of file