From 60d6277f56612d33c66961249dc3fd5e97a029b8 Mon Sep 17 00:00:00 2001 From: isymchych Date: Thu, 3 Dec 2015 18:32:40 +0200 Subject: [PATCH 1/8] support DTMF --- .editorconfig | 10 +++ JitsiConference.js | 127 +++++++++++++++++++++--------- JitsiConferenceEvents.js | 3 +- JitsiParticipant.js | 41 +++++----- modules/DTMF/DTMF.js | 47 ------------ modules/DTMF/JitsiDTMFManager.js | 15 ++++ modules/RTC/JitsiTrack.js | 14 ++++ modules/members/MemberList.js | 128 ------------------------------- service/members/Events.js | 5 -- 9 files changed, 155 insertions(+), 235 deletions(-) create mode 100644 .editorconfig delete mode 100644 modules/DTMF/DTMF.js create mode 100644 modules/DTMF/JitsiDTMFManager.js delete mode 100644 modules/members/MemberList.js delete mode 100644 service/members/Events.js diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..7eeae4728 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,10 @@ +# http://editorconfig.org +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 4 +indent_style = space +max_line_length = 80 +trim_trailing_whitespace = true diff --git a/JitsiConference.js b/JitsiConference.js index ed3978b72..488d78db6 100644 --- a/JitsiConference.js +++ b/JitsiConference.js @@ -1,4 +1,5 @@ - +/* global Strophe */ +/* jshint -W101 */ var logger = require("jitsi-meet-logger").getLogger(__filename); var RTC = require("./modules/RTC/RTC"); var XMPPEvents = require("./service/xmpp/XMPPEvents"); @@ -8,6 +9,7 @@ var EventEmitter = require("events"); var JitsiConferenceEvents = require("./JitsiConferenceEvents"); var JitsiParticipant = require("./JitsiParticipant"); var Statistics = require("./modules/statistics/statistics"); +var JitsiDTMFManager = require('./modules/DTMF/JitsiDTMFManager'); /** * Creates a JitsiConference object with the given name and properties. @@ -18,7 +20,6 @@ var Statistics = require("./modules/statistics/statistics"); * @param options.connection the JitsiConnection object for this JitsiConference. * @constructor */ - function JitsiConference(options) { if(!options.name || options.name.toLowerCase() !== options.name) { logger.error("Invalid conference name (no conference name passed or it" @@ -37,6 +38,8 @@ function JitsiConference(options) { setupListeners(this); this.participants = {}; this.lastActiveSpeaker = null; + this.dtmfManager = null; + this.somebodySupportsDTMF = false; } /** @@ -46,7 +49,7 @@ function JitsiConference(options) { JitsiConference.prototype.join = function (password) { if(this.room) this.room.join(password, this.connection.tokenPassword); -} +}; /** * Leaves the conference. @@ -55,14 +58,17 @@ JitsiConference.prototype.leave = function () { if(this.xmpp) this.xmpp.leaveRoom(this.room.roomjid); this.room = null; -} +}; /** * Returns the local tracks. */ JitsiConference.prototype.getLocalTracks = function () { - if(this.rtc) + if (this.rtc) { return this.rtc.localStreams; + } else { + return []; + } }; @@ -77,7 +83,7 @@ JitsiConference.prototype.getLocalTracks = function () { JitsiConference.prototype.on = function (eventId, handler) { if(this.eventEmitter) this.eventEmitter.on(eventId, handler); -} +}; /** * Removes event listener @@ -88,12 +94,12 @@ JitsiConference.prototype.on = function (eventId, handler) { */ JitsiConference.prototype.off = function (eventId, handler) { if(this.eventEmitter) - this.eventEmitter.removeListener(eventId, listener); -} + this.eventEmitter.removeListener(eventId, handler); +}; // Common aliases for event emitter -JitsiConference.prototype.addEventListener = JitsiConference.prototype.on -JitsiConference.prototype.removeEventListener = JitsiConference.prototype.off +JitsiConference.prototype.addEventListener = JitsiConference.prototype.on; +JitsiConference.prototype.removeEventListener = JitsiConference.prototype.off; /** * Receives notifications from another participants for commands / custom events(send by sendPresenceCommand method). @@ -103,7 +109,7 @@ JitsiConference.prototype.removeEventListener = JitsiConference.prototype.off JitsiConference.prototype.addCommandListener = function (command, handler) { if(this.room) this.room.addPresenceListener(command, handler); - } + }; /** * Removes command listener @@ -112,7 +118,7 @@ JitsiConference.prototype.removeEventListener = JitsiConference.prototype.off JitsiConference.prototype.removeCommandListener = function (command) { if(this.room) this.room.removePresenceListener(command); - } + }; /** * Sends text message to the other participants in the conference @@ -121,7 +127,7 @@ JitsiConference.prototype.removeEventListener = JitsiConference.prototype.off JitsiConference.prototype.sendTextMessage = function (message) { if(this.room) this.room.sendMessage(message); -} +}; /** * Send presence command. @@ -133,7 +139,7 @@ JitsiConference.prototype.sendCommand = function (name, values) { this.room.addToPresence(name, values); this.room.sendPresence(); } -} +}; /** * Send presence command one time. @@ -143,7 +149,7 @@ JitsiConference.prototype.sendCommand = function (name, values) { JitsiConference.prototype.sendCommandOnce = function (name, values) { this.sendCommand(name, values); this.removeCommand(name); -} +}; /** * Send presence command. @@ -154,7 +160,7 @@ JitsiConference.prototype.sendCommandOnce = function (name, values) { JitsiConference.prototype.removeCommand = function (name) { if(this.room) this.room.removeFromPresence(name); -} +}; /** * Sets the display name for this conference. @@ -162,10 +168,14 @@ JitsiConference.prototype.removeCommand = function (name) { */ JitsiConference.prototype.setDisplayName = function(name) { if(this.room){ - this.room.addToPresence("nick", {attributes: {xmlns: 'http://jabber.org/protocol/nick'}, value: name}); + this.room.addToPresence("nick", { + attributes: { + xmlns: 'http://jabber.org/protocol/nick' + }, value: name + }); this.room.sendPresence(); } -} +}; /** * Adds JitsiLocalTrack object to the conference. @@ -174,7 +184,7 @@ JitsiConference.prototype.setDisplayName = function(name) { JitsiConference.prototype.addTrack = function (track) { this.rtc.addLocalStream(track); this.room.addStream(track.getOriginalStream(), function () {}); -} +}; /** * Removes JitsiLocalTrack object to the conference. @@ -183,7 +193,7 @@ JitsiConference.prototype.addTrack = function (track) { JitsiConference.prototype.removeTrack = function (track) { this.room.removeStream(track.getOriginalStream()); this.rtc.removeLocalStream(track); -} +}; /** * Elects the participant with the given id to be the selected participant or the speaker. @@ -193,41 +203,65 @@ JitsiConference.prototype.selectParticipant = function(participantId) { if (this.rtc) { this.rtc.selectedEndpoint(participantId); } -} +}; /** * * @param id the identifier of the participant */ JitsiConference.prototype.pinParticipant = function(participantId) { - if(this.rtc) + if (this.rtc) { this.rtc.pinEndpoint(participantId); -} + } +}; /** * Returns the list of participants for this conference. - * @return Object a list of participant identifiers containing all conference participants. + * @return Array a list of participant identifiers containing all conference participants. */ JitsiConference.prototype.getParticipants = function() { - return this.participants; -} + return Object.keys(this.participants).map(function (key) { + return this.participants[key]; + }, this); +}; /** * @returns {JitsiParticipant} the participant in this conference with the specified id (or - * null if there isn't one). + * undefined if there isn't one). * @param id the id of the participant. */ JitsiConference.prototype.getParticipantById = function(id) { - if(this.participants) - return this.participants[id]; - return null; -} + return this.participants[id]; +}; JitsiConference.prototype.onMemberJoined = function (jid, email, nick) { - if(this.eventEmitter) - this.eventEmitter.emit(JitsiConferenceEvents.USER_JOINED, Strophe.getResourceFromJid(jid)); -// this.participants[jid] = new JitsiParticipant(); -} + var id = Strophe.getResourceFromJid(jid); + var participant = new JitsiParticipant(id, this, nick); + this.eventEmitter.emit(JitsiConferenceEvents.USER_JOINED, id); + this.participants[jid] = participant; + this.xmpp.getConnection().disco.info( + jid, "" /* node */, function(iq) { + participant._supportsDTMF = $(iq).find('>query>feature[var="urn:xmpp:jingle:dtmf:0"]').length > 0; + this.updateDTMFSupport(); + }.bind(this) + ); + +}; + +JitsiConference.prototype.updateDTMFSupport = function () { + var somebodySupportsDTMF = false; + var participants = this.getParticipants(); + for (var i = 0; i < participants.length; i += 1) { + if (participants[i].supportsDTMF()) { + somebodySupportsDTMF = true; + break; + } + } + if (somebodySupportsDTMF !== this.somebodySupportsDTMF) { + this.somebodySupportsDTMF = somebodySupportsDTMF; + this.eventEmitter.emit(JitsiConferenceEvents.DTMF_SUPPORT_CHANGED, somebodySupportsDTMF); + } +}; /** * Returns the local user's ID @@ -235,7 +269,28 @@ JitsiConference.prototype.onMemberJoined = function (jid, email, nick) { */ JitsiConference.prototype.myUserId = function () { return (this.room && this.room.myroomjid)? Strophe.getResourceFromJid(this.room.myroomjid) : null; -} +}; + +JitsiConference.prototype.sendTones = function (tones, duration, pause) { + if (!this.dtmfManager) { + var connection = this.xmpp.getConnection().jingle.activecall.peerconnection; + if (!connection) { + logger.warn("cannot sendTones: no conneciton"); + return; + } + + var tracks = this.getLocalTracks().filter(function (track) { + return track.isAudioTrack(); + }); + if (!tracks.length) { + logger.warn("cannot sendTones: no local audio stream"); + return; + } + this.dtmfManager = new JitsiDTMFManager(tracks[0], connection); + } + + this.dtmfManager.sendTones(tones, duration, pause); +}; /** * Setups the listeners needed for the conference. diff --git a/JitsiConferenceEvents.js b/JitsiConferenceEvents.js index 91808064c..51d07dff5 100644 --- a/JitsiConferenceEvents.js +++ b/JitsiConferenceEvents.js @@ -75,7 +75,8 @@ var JitsiConferenceEvents = { /** * Indicates that conference has been left. */ - CONFERENCE_LEFT: "conference.left" + CONFERENCE_LEFT: "conference.left", + DTMF_SUPPORT_CHANGED: "conference.dtmf_support_changed" }; module.exports = JitsiConferenceEvents; diff --git a/JitsiParticipant.js b/JitsiParticipant.js index 58104f977..c45e2aa93 100644 --- a/JitsiParticipant.js +++ b/JitsiParticipant.js @@ -5,6 +5,7 @@ function JitsiParticipant(id, conference, displayName){ this._id = id; this._conference = conference; this._displayName = displayName; + this._supportsDTMF = false; } /** @@ -12,34 +13,34 @@ function JitsiParticipant(id, conference, displayName){ */ JitsiParticipant.prototype.getConference = function() { return this._conference; -} +}; /** * @returns {Array.} The list of media tracks for this participant. */ JitsiParticipant.prototype.getTracks = function() { -} +}; /** * @returns {String} The ID (i.e. JID) of this participant. */ JitsiParticipant.prototype.getId = function() { return this._id; -} +}; /** * @returns {String} The human-readable display name of this participant. */ JitsiParticipant.prototype.getDisplayName = function() { return this._displayName; -} +}; /** * @returns {Boolean} Whether this participant is a moderator or not. */ JitsiParticipant.prototype.isModerator = function() { -} +}; // Gets a link to an etherpad instance advertised by the participant? //JitsiParticipant.prototype.getEtherpad = function() { @@ -52,14 +53,14 @@ JitsiParticipant.prototype.isModerator = function() { */ JitsiParticipant.prototype.isAudioMuted = function() { -} +}; /* * @returns {Boolean} Whether this participant has muted their video. */ JitsiParticipant.prototype.isVideoMuted = function() { -} +}; /* * @returns {???} The latest statistics reported by this participant (i.e. info used to populate the GSM bars) @@ -67,70 +68,74 @@ JitsiParticipant.prototype.isVideoMuted = function() { */ JitsiParticipant.prototype.getLatestStats = function() { -} +}; /** * @returns {String} The role of this participant. */ JitsiParticipant.prototype.getRole = function() { -} +}; /* * @returns {Boolean} Whether this participant is the conference focus (i.e. jicofo). */ JitsiParticipant.prototype.isFocus = function() { -} +}; /* * @returns {Boolean} Whether this participant is a conference recorder (i.e. jirecon). */ JitsiParticipant.prototype.isRecorder = function() { -} +}; /* * @returns {Boolean} Whether this participant is a SIP gateway (i.e. jigasi). */ JitsiParticipant.prototype.isSipGateway = function() { -} +}; /** * @returns {String} The ID for this participant's avatar. */ JitsiParticipant.prototype.getAvatarId = function() { -} +}; /** * @returns {Boolean} Whether this participant is currently sharing their screen. */ JitsiParticipant.prototype.isScreenSharing = function() { -} +}; /** * @returns {String} The user agent of this participant (i.e. browser userAgent string). */ JitsiParticipant.prototype.getUserAgent = function() { -} +}; /** * Kicks the participant from the conference (requires certain privileges). */ JitsiParticipant.prototype.kick = function() { -} +}; /** * Asks this participant to mute themselves. */ JitsiParticipant.prototype.askToMute = function() { -} +}; + +JitsiParticipant.prototype.supportsDTMF = function () { + return this._supportsDTMF; +}; -module.exports = JitsiParticipant(); +module.exports = JitsiParticipant; diff --git a/modules/DTMF/DTMF.js b/modules/DTMF/DTMF.js deleted file mode 100644 index e4ce2057c..000000000 --- a/modules/DTMF/DTMF.js +++ /dev/null @@ -1,47 +0,0 @@ -/* global APP */ - -/** - * A module for sending DTMF tones. - */ -var DTMFSender; -var initDtmfSender = function() { - // TODO: This needs to reset this if the peerconnection changes - // (e.g. the call is re-made) - if (DTMFSender) - return; - - var localAudio = APP.RTC.localAudio; - if (localAudio && localAudio.getTracks().length > 0) - { - var peerconnection - = APP.xmpp.getConnection().jingle.activecall.peerconnection; - if (peerconnection) { - DTMFSender = - peerconnection.peerconnection - .createDTMFSender(localAudio.getTracks()[0]); - console.log("Initialized DTMFSender"); - } - else { - console.log("Failed to initialize DTMFSender: no PeerConnection."); - } - } - else { - console.log("Failed to initialize DTMFSender: no audio track."); - } -}; - -var DTMF = { - sendTones: function (tones, duration, pause) { - if (!DTMFSender) - initDtmfSender(); - - if (DTMFSender){ - DTMFSender.insertDTMF(tones, - (duration || 200), - (pause || 200)); - } - } -}; - -module.exports = DTMF; - diff --git a/modules/DTMF/JitsiDTMFManager.js b/modules/DTMF/JitsiDTMFManager.js new file mode 100644 index 000000000..3a046c793 --- /dev/null +++ b/modules/DTMF/JitsiDTMFManager.js @@ -0,0 +1,15 @@ +var logger = require("jitsi-meet-logger").getLogger(__filename); + +function JitsiDTMFManager (localAudio, peerConnection) { + var tracks = localAudio._getTracks(); + if (!tracks.length) { + throw new Error("Failed to initialize DTMFSender: no audio track."); + } + this.dtmfSender = peerConnection.peerconnection.createDTMFSender(tracks[0]); + logger.debug("Initialized DTMFSender"); +} + + +JitsiDTMFManager.prototype.sendTones = function (tones, duration, pause) { + this.dtmfSender.insertDTMF(tones, (duration || 200), (pause || 200)); +}; diff --git a/modules/RTC/JitsiTrack.js b/modules/RTC/JitsiTrack.js index 2042d2415..dae9a033c 100644 --- a/modules/RTC/JitsiTrack.js +++ b/modules/RTC/JitsiTrack.js @@ -92,6 +92,20 @@ JitsiTrack.prototype.getType = function() { return this.type; }; +/** + * Check if this is audiotrack. + */ +JitsiTrack.prototype.isAudioTrack = function () { + return this.getType() === JitsiTrack.AUDIO; +}; + +/** + * Check if this is videotrack. + */ +JitsiTrack.prototype.isVideoTrack = function () { + return this.getType() === JitsiTrack.VIDEO; +}; + /** * Returns the RTCMediaStream from the browser (?). */ diff --git a/modules/members/MemberList.js b/modules/members/MemberList.js deleted file mode 100644 index 95bb0d29c..000000000 --- a/modules/members/MemberList.js +++ /dev/null @@ -1,128 +0,0 @@ -/* global APP, require, $ */ - -/** - * This module is meant to (eventually) contain and manage all information - * about members/participants of the conference, so that other modules don't - * have to do it on their own, and so that other modules can access members' - * information from a single place. - * - * Currently this module only manages information about the support of jingle - * DTMF of the members. Other fields, as well as accessor methods are meant to - * be added as needed. - */ - -var XMPPEvents = require("../../service/xmpp/XMPPEvents"); -var Events = require("../../service/members/Events"); -var EventEmitter = require("events"); - -var eventEmitter = new EventEmitter(); - -/** - * The actual container. - */ -var members = {}; - -/** - * There is at least one member that supports DTMF (i.e. is jigasi). - */ -var atLeastOneDtmf = false; - - -function registerListeners() { - APP.xmpp.addListener(XMPPEvents.MUC_MEMBER_JOINED, onMucMemberJoined); - APP.xmpp.addListener(XMPPEvents.MUC_MEMBER_LEFT, onMucMemberLeft); -} - -/** - * Handles a new member joining the MUC. - */ -function onMucMemberJoined(jid, id, displayName) { - var member = { - displayName: displayName - }; - - APP.xmpp.getConnection().disco.info( - jid, "" /* node */, function(iq) { onDiscoInfoReceived(jid, iq); }); - - members[jid] = member; -} - -/** - * Handles a member leaving the MUC. - */ -function onMucMemberLeft(jid) { - delete members[jid]; - updateAtLeastOneDtmf(); -} - -/** - * Handles the reception of a disco#info packet from a particular JID. - * @param jid the JID sending the packet. - * @param iq the packet. - */ -function onDiscoInfoReceived(jid, iq) { - if (!members[jid]) - return; - - var supportsDtmf - = $(iq).find('>query>feature[var="urn:xmpp:jingle:dtmf:0"]').length > 0; - updateDtmf(jid, supportsDtmf); -} - -/** - * Updates the 'supportsDtmf' field for a member. - * @param jid the jid of the member. - * @param newValue the new value for the 'supportsDtmf' field. - */ -function updateDtmf(jid, newValue) { - var oldValue = members[jid].supportsDtmf; - members[jid].supportsDtmf = newValue; - - if (newValue != oldValue) { - updateAtLeastOneDtmf(); - } -} - -/** - * Checks each member's 'supportsDtmf' field and updates - * 'atLastOneSupportsDtmf'. - */ -function updateAtLeastOneDtmf() { - var newAtLeastOneDtmf = false; - for (var key in members) { - if (typeof members[key].supportsDtmf !== 'undefined' - && members[key].supportsDtmf) { - newAtLeastOneDtmf= true; - break; - } - } - - if (atLeastOneDtmf != newAtLeastOneDtmf) { - atLeastOneDtmf = newAtLeastOneDtmf; - eventEmitter.emit(Events.DTMF_SUPPORT_CHANGED, atLeastOneDtmf); - } -} - - -/** - * Exported interface. - */ -var Members = { - start: function() { - registerListeners(); - }, - addListener: function(type, listener) { - eventEmitter.on(type, listener); - }, - removeListener: function (type, listener) { - eventEmitter.removeListener(type, listener); - }, - size: function () { - return Object.keys(members).length; - }, - getMembers: function () { - return members; - } -}; - -module.exports = Members; diff --git a/service/members/Events.js b/service/members/Events.js deleted file mode 100644 index 02b006b7f..000000000 --- a/service/members/Events.js +++ /dev/null @@ -1,5 +0,0 @@ -var Events = { - DTMF_SUPPORT_CHANGED: "members.dtmf_support_changed" -}; - -module.exports = Events; From 3b633c014a1b824cd061a5e7e717ca7925fac341 Mon Sep 17 00:00:00 2001 From: isymchych Date: Fri, 4 Dec 2015 14:21:03 +0200 Subject: [PATCH 2/8] use JitsiParticipant; add some method implementations --- JitsiConference.js | 115 +- JitsiParticipant.js | 20 +- doc/example/example.js | 2 +- lib-jitsi-meet.js | 2331 ++++++++++++++++++------------- modules/RTC/JitsiRemoteTrack.js | 2 +- modules/xmpp/SDPUtil.js | 2 +- 6 files changed, 1444 insertions(+), 1028 deletions(-) diff --git a/JitsiConference.js b/JitsiConference.js index 488d78db6..89dc34965 100644 --- a/JitsiConference.js +++ b/JitsiConference.js @@ -1,4 +1,4 @@ -/* global Strophe */ +/* global Strophe, $ */ /* jshint -W101 */ var logger = require("jitsi-meet-logger").getLogger(__filename); var RTC = require("./modules/RTC/RTC"); @@ -238,19 +238,44 @@ JitsiConference.prototype.onMemberJoined = function (jid, email, nick) { var id = Strophe.getResourceFromJid(jid); var participant = new JitsiParticipant(id, this, nick); this.eventEmitter.emit(JitsiConferenceEvents.USER_JOINED, id); - this.participants[jid] = participant; - this.xmpp.getConnection().disco.info( + this.participants[id] = participant; + this.connection.xmpp.connection.disco.info( jid, "" /* node */, function(iq) { participant._supportsDTMF = $(iq).find('>query>feature[var="urn:xmpp:jingle:dtmf:0"]').length > 0; this.updateDTMFSupport(); }.bind(this) ); +}; +JitsiConference.prototype.onMemberLeft = function (jid) { + var id = Strophe.getResourceFromJid(jid); + delete this.participants[id]; + this.eventEmitter.emit(JitsiConferenceEvents.USER_LEFT, id); +}; + + +JitsiConference.prototype.onTrackAdded = function (track) { + var id = track.getParticipantId(); + var participant = this.getParticipantById(id); + participant._tracks.push(track); + this.eventEmitter.emit(JitsiConferenceEvents.TRACK_ADDED, track); +}; + +JitsiConference.prototype.onTrackRemoved = function (track) { + var id = track.getParticipantId(); + var participant = this.getParticipantById(id); + var pos = participant._tracks.indexOf(track); + if (pos > -1) { + participant._tracks.splice(pos, 1); + } + this.eventEmitter.emit(JitsiConferenceEvents.TRACK_REMOVED, track); }; JitsiConference.prototype.updateDTMFSupport = function () { var somebodySupportsDTMF = false; var participants = this.getParticipants(); + + // check if at least 1 participant supports DTMF for (var i = 0; i < participants.length; i += 1) { if (participants[i].supportsDTMF()) { somebodySupportsDTMF = true; @@ -273,7 +298,7 @@ JitsiConference.prototype.myUserId = function () { JitsiConference.prototype.sendTones = function (tones, duration, pause) { if (!this.dtmfManager) { - var connection = this.xmpp.getConnection().jingle.activecall.peerconnection; + var connection = this.connection.xmpp.connection.jingle.activecall.peerconnection; if (!connection) { logger.warn("cannot sendTones: no conneciton"); return; @@ -304,7 +329,21 @@ function setupListeners(conference) { }); conference.room.addListener(XMPPEvents.REMOTE_STREAM_RECEIVED, conference.rtc.createRemoteStream.bind(conference.rtc)); - + //FIXME: Maybe remove event should not be associated with the conference. + conference.rtc.addListener( + StreamEventTypes.EVENT_TYPE_REMOTE_CREATED, conference.onTrackAdded.bind(conference) + ); + //FIXME: Maybe remove event should not be associated with the conference. + conference.rtc.addListener( + StreamEventTypes.EVENT_TYPE_REMOTE_ENDED, conference.onTrackRemoved.bind(conference) + ); + conference.rtc.addListener(StreamEventTypes.EVENT_TYPE_LOCAL_ENDED, function (stream) { + conference.removeTrack(stream); + conference.eventEmitter.emit(JitsiConferenceEvents.TRACK_REMOVED, stream); + }); + conference.rtc.addListener(StreamEventTypes.TRACK_MUTE_CHANGED, function (track) { + conference.eventEmitter.emit(JitsiConferenceEvents.TRACK_MUTE_CHANGED, track); + }); conference.room.addListener(XMPPEvents.MUC_JOINED, function () { conference.eventEmitter.emit(JitsiConferenceEvents.CONFERENCE_JOINED); }); @@ -312,53 +351,6 @@ function setupListeners(conference) { // conference.room.addListener(XMPPEvents.MUC_JOINED, function () { // conference.eventEmitter.emit(JitsiConferenceEvents.CONFERENCE_LEFT); // }); - - conference.room.addListener(XMPPEvents.MUC_MEMBER_JOINED, - conference.onMemberJoined.bind(conference)); - conference.room.addListener(XMPPEvents.MUC_MEMBER_LEFT,function (jid) { - conference.eventEmitter.emit(JitsiConferenceEvents.USER_LEFT, - Strophe.getResourceFromJid(jid)); - }); - - conference.room.addListener(XMPPEvents.DISPLAY_NAME_CHANGED, - function (from, displayName) { - conference.eventEmitter.emit( - JitsiConferenceEvents.DISPLAY_NAME_CHANGED, - Strophe.getResourceFromJid(from), displayName); - }); - - conference.room.addListener(XMPPEvents.CONNECTION_INTERRUPTED, - function () { - conference.eventEmitter.emit( - JitsiConferenceEvents.CONNECTION_INTERRUPTED); - }); - - conference.room.addListener(XMPPEvents.CONNECTION_RESTORED, function () { - conference.eventEmitter.emit(JitsiConferenceEvents.CONNECTION_RESTORED); - }); - conference.room.addListener(XMPPEvents.CONFERENCE_SETUP_FAILED, function() { - conference.eventEmitter.emit(JitsiConferenceEvents.SETUP_FAILED); - }); - - conference.rtc.addListener(StreamEventTypes.EVENT_TYPE_REMOTE_CREATED, - function (stream) { - conference.eventEmitter.emit(JitsiConferenceEvents.TRACK_ADDED, stream); - }); - -//FIXME: Maybe remove event should not be associated with the conference. - conference.rtc.addListener(StreamEventTypes.EVENT_TYPE_REMOTE_ENDED, function (stream) { - conference.eventEmitter.emit(JitsiConferenceEvents.TRACK_REMOVED, stream); - }); -//FIXME: Maybe remove event should not be associated with the conference. - conference.rtc.addListener(StreamEventTypes.EVENT_TYPE_LOCAL_ENDED, function (stream) { - conference.eventEmitter.emit(JitsiConferenceEvents.TRACK_REMOVED, stream); - conference.removeTrack(stream); - }); - - conference.rtc.addListener(StreamEventTypes.TRACK_MUTE_CHANGED, function (track) { - conference.eventEmitter.emit(JitsiConferenceEvents.TRACK_MUTE_CHANGED, track); - }); - conference.rtc.addListener(RTCEvents.DOMINANTSPEAKER_CHANGED, function (id) { if(conference.lastActiveSpeaker !== id && conference.room) { conference.lastActiveSpeaker = id; @@ -376,6 +368,25 @@ function setupListeners(conference) { lastNEndpoints, endpointsEnteringLastN); }); + conference.room.addListener(XMPPEvents.MUC_MEMBER_JOINED, conference.onMemberJoined.bind(conference)); + conference.room.addListener(XMPPEvents.MUC_MEMBER_LEFT, conference.onMemberLeft.bind(conference)); + + conference.room.addListener(XMPPEvents.DISPLAY_NAME_CHANGED, function (from, displayName) { + conference.eventEmitter.emit(JitsiConferenceEvents.DISPLAY_NAME_CHANGED, + Strophe.getResourceFromJid(from), displayName); + }); + + conference.room.addListener(XMPPEvents.CONNECTION_INTERRUPTED, function () { + conference.eventEmitter.emit(JitsiConferenceEvents.CONNECTION_INTERRUPTED); + }); + + conference.room.addListener(XMPPEvents.CONNECTION_RESTORED, function () { + conference.eventEmitter.emit(JitsiConferenceEvents.CONNECTION_RESTORED); + }); + conference.room.addListener(XMPPEvents.CONFERENCE_SETUP_FAILED, function () { + conference.eventEmitter.emit(JitsiConferenceEvents.SETUP_FAILED); + }); + if(conference.statistics) { //FIXME: Maybe remove event should not be associated with the conference. conference.statistics.addAudioLevelListener(function (ssrc, level) { diff --git a/JitsiParticipant.js b/JitsiParticipant.js index c45e2aa93..7e647a5e6 100644 --- a/JitsiParticipant.js +++ b/JitsiParticipant.js @@ -6,6 +6,8 @@ function JitsiParticipant(id, conference, displayName){ this._conference = conference; this._displayName = displayName; this._supportsDTMF = false; + this._tracks = []; + this._isModerator = false; } /** @@ -19,7 +21,7 @@ JitsiParticipant.prototype.getConference = function() { * @returns {Array.} The list of media tracks for this participant. */ JitsiParticipant.prototype.getTracks = function() { - + return this._tracks; }; /** @@ -40,6 +42,7 @@ JitsiParticipant.prototype.getDisplayName = function() { * @returns {Boolean} Whether this participant is a moderator or not. */ JitsiParticipant.prototype.isModerator = function() { + return this._isModerator; }; // Gets a link to an etherpad instance advertised by the participant? @@ -52,14 +55,18 @@ JitsiParticipant.prototype.isModerator = function() { * @returns {Boolean} Whether this participant has muted their audio. */ JitsiParticipant.prototype.isAudioMuted = function() { - + return this.getTracks().reduce(function (track, isAudioMuted) { + return isAudioMuted && (track.isVideoTrack() || track.isMuted()); + }, true); }; /* * @returns {Boolean} Whether this participant has muted their video. */ JitsiParticipant.prototype.isVideoMuted = function() { - + return this.getTracks().reduce(function (track, isVideoMuted) { + return isVideoMuted && (track.isAudioTrack() || track.isMuted()); + }, true); }; /* @@ -98,13 +105,6 @@ JitsiParticipant.prototype.isSipGateway = function() { }; -/** - * @returns {String} The ID for this participant's avatar. - */ -JitsiParticipant.prototype.getAvatarId = function() { - -}; - /** * @returns {Boolean} Whether this participant is currently sharing their screen. */ diff --git a/doc/example/example.js b/doc/example/example.js index 6a775bfc7..9c165513c 100644 --- a/doc/example/example.js +++ b/doc/example/example.js @@ -38,7 +38,7 @@ function onLocalTracks(tracks) * @param track JitsiTrack object */ function onRemoteTrack(track) { - var participant = track.getParitcipantId(); + var participant = track.getParticipantId(); if(!remoteTracks[participant]) remoteTracks[participant] = []; var idx = remoteTracks[participant].push(track); diff --git a/lib-jitsi-meet.js b/lib-jitsi-meet.js index 8e195034b..52598cc14 100644 --- a/lib-jitsi-meet.js +++ b/lib-jitsi-meet.js @@ -1,6 +1,7 @@ (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.JitsiMeetJS = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o a list of participant identifiers containing all conference participants. */ JitsiConference.prototype.getParticipants = function() { - return this.participants; -} + return Object.keys(this.participants).map(function (key) { + return this.participants[key]; + }, this); +}; /** * @returns {JitsiParticipant} the participant in this conference with the specified id (or - * null if there isn't one). + * undefined if there isn't one). * @param id the id of the participant. */ JitsiConference.prototype.getParticipantById = function(id) { - if(this.participants) - return this.participants[id]; - return null; -} + return this.participants[id]; +}; JitsiConference.prototype.onMemberJoined = function (jid, email, nick) { - if(this.eventEmitter) - this.eventEmitter.emit(JitsiConferenceEvents.USER_JOINED, Strophe.getResourceFromJid(jid)); -// this.participants[jid] = new JitsiParticipant(); -} + var id = Strophe.getResourceFromJid(jid); + var participant = new JitsiParticipant(id, this, nick); + this.eventEmitter.emit(JitsiConferenceEvents.USER_JOINED, id); + this.participants[id] = participant; + this.xmpp.getConnection().disco.info( + jid, "" /* node */, function(iq) { + participant._supportsDTMF = $(iq).find('>query>feature[var="urn:xmpp:jingle:dtmf:0"]').length > 0; + this.updateDTMFSupport(); + }.bind(this) + ); +}; + +JitsiConference.prototype.onMemberLeft = function (jid) { + var id = Strophe.getResourceFromJid(jid); + delete this.participants[id]; + this.eventEmitter.emit(JitsiConferenceEvents.USER_LEFT, id); +}; + + +JitsiConference.prototype.onTrackAdded = function (track) { + var id = track.getParticipantId(); + var participant = this.getParticipantById(id); + participant._tracks.push(track); + this.eventEmitter.emit(JitsiConferenceEvents.TRACK_ADDED, track); +}; + +JitsiConference.prototype.onTrackRemoved = function (track) { + var id = track.getParticipantId(); + var participant = this.getParticipantById(id); + var pos = participant._tracks.indexOf(track); + if (pos > -1) { + participant._tracks.splice(pos, 1); + } + this.eventEmitter.emit(JitsiConferenceEvents.TRACK_REMOVED, track); +}; + +JitsiConference.prototype.updateDTMFSupport = function () { + var somebodySupportsDTMF = false; + var participants = this.getParticipants(); + + // check if at least 1 participant supports DTMF + for (var i = 0; i < participants.length; i += 1) { + if (participants[i].supportsDTMF()) { + somebodySupportsDTMF = true; + break; + } + } + if (somebodySupportsDTMF !== this.somebodySupportsDTMF) { + this.somebodySupportsDTMF = somebodySupportsDTMF; + this.eventEmitter.emit(JitsiConferenceEvents.DTMF_SUPPORT_CHANGED, somebodySupportsDTMF); + } +}; /** * Returns the local user's ID @@ -237,7 +296,28 @@ JitsiConference.prototype.onMemberJoined = function (jid, email, nick) { */ JitsiConference.prototype.myUserId = function () { return (this.room && this.room.myroomjid)? Strophe.getResourceFromJid(this.room.myroomjid) : null; -} +}; + +JitsiConference.prototype.sendTones = function (tones, duration, pause) { + if (!this.dtmfManager) { + var connection = this.xmpp.getConnection().jingle.activecall.peerconnection; + if (!connection) { + logger.warn("cannot sendTones: no conneciton"); + return; + } + + var tracks = this.getLocalTracks().filter(function (track) { + return track.isAudioTrack(); + }); + if (!tracks.length) { + logger.warn("cannot sendTones: no local audio stream"); + return; + } + this.dtmfManager = new JitsiDTMFManager(tracks[0], connection); + } + + this.dtmfManager.sendTones(tones, duration, pause); +}; /** * Setups the listeners needed for the conference. @@ -251,7 +331,19 @@ function setupListeners(conference) { }); conference.room.addListener(XMPPEvents.REMOTE_STREAM_RECEIVED, conference.rtc.createRemoteStream.bind(conference.rtc)); - + conference.rtc.addListener( + StreamEventTypes.EVENT_TYPE_REMOTE_CREATED, conference.onTrackAdded.bind(conference) + ); + conference.rtc.addListener( + StreamEventTypes.EVENT_TYPE_REMOTE_ENDED, conference.onTrackRemoved.bind(conference) + ); + conference.rtc.addListener(StreamEventTypes.EVENT_TYPE_LOCAL_ENDED, function (stream) { + conference.removeTrack(stream); + conference.eventEmitter.emit(JitsiConferenceEvents.TRACK_REMOVED, stream); + }); + conference.rtc.addListener(StreamEventTypes.TRACK_MUTE_CHANGED, function (track) { + conference.eventEmitter.emit(JitsiConferenceEvents.TRACK_MUTE_CHANGED, track); + }); conference.room.addListener(XMPPEvents.MUC_JOINED, function () { conference.eventEmitter.emit(JitsiConferenceEvents.CONFERENCE_JOINED); }); @@ -259,53 +351,6 @@ function setupListeners(conference) { // conference.room.addListener(XMPPEvents.MUC_JOINED, function () { // conference.eventEmitter.emit(JitsiConferenceEvents.CONFERENCE_LEFT); // }); - - conference.room.addListener(XMPPEvents.MUC_MEMBER_JOINED, - conference.onMemberJoined.bind(conference)); - conference.room.addListener(XMPPEvents.MUC_MEMBER_LEFT,function (jid) { - conference.eventEmitter.emit(JitsiConferenceEvents.USER_LEFT, - Strophe.getResourceFromJid(jid)); - }); - - conference.room.addListener(XMPPEvents.DISPLAY_NAME_CHANGED, - function (from, displayName) { - conference.eventEmitter.emit( - JitsiConferenceEvents.DISPLAY_NAME_CHANGED, - Strophe.getResourceFromJid(from), displayName); - }); - - conference.room.addListener(XMPPEvents.CONNECTION_INTERRUPTED, - function () { - conference.eventEmitter.emit( - JitsiConferenceEvents.CONNECTION_INTERRUPTED); - }); - - conference.room.addListener(XMPPEvents.CONNECTION_RESTORED, function () { - conference.eventEmitter.emit(JitsiConferenceEvents.CONNECTION_RESTORED); - }); - conference.room.addListener(XMPPEvents.CONFERENCE_SETUP_FAILED, function() { - conference.eventEmitter.emit(JitsiConferenceEvents.SETUP_FAILED); - }); - - conference.rtc.addListener(StreamEventTypes.EVENT_TYPE_REMOTE_CREATED, - function (stream) { - conference.eventEmitter.emit(JitsiConferenceEvents.TRACK_ADDED, stream); - }); - -//FIXME: Maybe remove event should not be associated with the conference. - conference.rtc.addListener(StreamEventTypes.EVENT_TYPE_REMOTE_ENDED, function (stream) { - conference.eventEmitter.emit(JitsiConferenceEvents.TRACK_REMOVED, stream); - }); -//FIXME: Maybe remove event should not be associated with the conference. - conference.rtc.addListener(StreamEventTypes.EVENT_TYPE_LOCAL_ENDED, function (stream) { - conference.eventEmitter.emit(JitsiConferenceEvents.TRACK_REMOVED, stream); - conference.removeTrack(stream); - }); - - conference.rtc.addListener(StreamEventTypes.TRACK_MUTE_CHANGED, function (track) { - conference.eventEmitter.emit(JitsiConferenceEvents.TRACK_MUTE_CHANGED, track); - }); - conference.rtc.addListener(RTCEvents.DOMINANTSPEAKER_CHANGED, function (id) { if(conference.lastActiveSpeaker !== id && conference.room) { conference.lastActiveSpeaker = id; @@ -323,8 +368,26 @@ function setupListeners(conference) { lastNEndpoints, endpointsEnteringLastN); }); + conference.room.addListener(XMPPEvents.MUC_MEMBER_JOINED, conference.onMemberJoined.bind(conference)); + conference.room.addListener(XMPPEvents.MUC_MEMBER_LEFT, conference.onMemberLeft.bind(conference)); + + conference.room.addListener(XMPPEvents.DISPLAY_NAME_CHANGED, function (from, displayName) { + conference.eventEmitter.emit(JitsiConferenceEvents.DISPLAY_NAME_CHANGED, + Strophe.getResourceFromJid(from), displayName); + }); + + conference.room.addListener(XMPPEvents.CONNECTION_INTERRUPTED, function () { + conference.eventEmitter.emit(JitsiConferenceEvents.CONNECTION_INTERRUPTED); + }); + + conference.room.addListener(XMPPEvents.CONNECTION_RESTORED, function () { + conference.eventEmitter.emit(JitsiConferenceEvents.CONNECTION_RESTORED); + }); + conference.room.addListener(XMPPEvents.CONFERENCE_SETUP_FAILED, function () { + conference.eventEmitter.emit(JitsiConferenceEvents.SETUP_FAILED); + }); + if(conference.statistics) { - //FIXME: Maybe remove event should not be associated with the conference. conference.statistics.addAudioLevelListener(function (ssrc, level) { var userId = null; if (ssrc === Statistics.LOCAL_JID) { @@ -346,10 +409,12 @@ function setupListeners(conference) { function () { conference.statistics.dispose(); }); - // FIXME: Maybe we should move this. - // RTC.addListener(RTCEvents.AVAILABLE_DEVICES_CHANGED, function (devices) { - // conference.room.updateDeviceAvailability(devices); - // }); + RTC.addListener(RTCEvents.AVAILABLE_DEVICES_CHANGED, function (devices) { + conference.room.updateDeviceAvailability(devices); + }); + RTC.addListener(StreamEventTypes.TRACK_MUTE_CHANGED, function (track) { + conference.eventEmitter.emit(JitsiConferenceEvents.TRACK_MUTE_CHANGED, track); + }); } } @@ -357,7 +422,7 @@ function setupListeners(conference) { module.exports = JitsiConference; }).call(this,"/JitsiConference.js") -},{"./JitsiConferenceEvents":3,"./JitsiParticipant":8,"./modules/RTC/RTC":15,"./modules/statistics/statistics":23,"./service/RTC/RTCEvents":82,"./service/RTC/StreamEventTypes":84,"./service/xmpp/XMPPEvents":89,"events":42,"jitsi-meet-logger":46}],2:[function(require,module,exports){ +},{"./JitsiConferenceEvents":3,"./JitsiParticipant":9,"./modules/DTMF/JitsiDTMFManager":10,"./modules/RTC/RTC":15,"./modules/statistics/statistics":23,"./service/RTC/RTCEvents":78,"./service/RTC/StreamEventTypes":80,"./service/xmpp/XMPPEvents":85,"events":42,"jitsi-meet-logger":46}],2:[function(require,module,exports){ /** * Enumeration with the errors for the conference. * @type {{string: string}} @@ -394,7 +459,7 @@ var JitsiConferenceEvents = { */ TRACK_ADDED: "conference.trackAdded", /** - * The media track was removed from the conference. + * The media track was removed to the conference. */ TRACK_REMOVED: "conference.trackRemoved", /** @@ -434,11 +499,11 @@ var JitsiConferenceEvents = { */ IN_LAST_N_CHANGED: "conference.lastNEndpointsChanged", /** - * A media track ( attached to the conference) mute status was changed. + * A media track mute status was changed. */ TRACK_MUTE_CHANGED: "conference.trackMuteChanged", /** - * Audio levels of a media track ( attached to the conference) was changed. + * Audio levels of a media track was changed. */ TRACK_AUDIO_LEVEL_CHANGED: "conference.audioLevelsChanged", /** @@ -461,7 +526,8 @@ var JitsiConferenceEvents = { /** * Indicates that conference has been left. */ - CONFERENCE_LEFT: "conference.left" + CONFERENCE_LEFT: "conference.left", + DTMF_SUPPORT_CHANGED: "conference.dtmf_support_changed" }; module.exports = JitsiConferenceEvents; @@ -623,8 +689,6 @@ var JitsiConferenceEvents = require("./JitsiConferenceEvents"); var JitsiConnectionEvents = require("./JitsiConnectionEvents"); var JitsiConnectionErrors = require("./JitsiConnectionErrors"); var JitsiConferenceErrors = require("./JitsiConferenceErrors"); -var JitsiTrackEvents = require("./JitsiTrackEvents"); -var JitsiTrackErrors = require("./JitsiTrackErrors"); var Logger = require("jitsi-meet-logger"); var RTC = require("./modules/RTC/RTC"); @@ -636,17 +700,15 @@ var LibJitsiMeet = { JitsiConnection: JitsiConnection, events: { conference: JitsiConferenceEvents, - connection: JitsiConnectionEvents, - track: JitsiTrackEvents + connection: JitsiConnectionEvents }, errors: { conference: JitsiConferenceErrors, - connection: JitsiConnectionErrors, - track: JitsiTrackErrors + connection: JitsiConnectionErrors }, logLevels: Logger.levels, init: function (options) { - return RTC.init(options || {}); + RTC.init(options || {}); }, setLogLevel: function (level) { Logger.setLogLevel(level); @@ -682,148 +744,11 @@ 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":15,"es6-promise":44,"jitsi-meet-logger":46}],8:[function(require,module,exports){ -/** - * Represents a participant in (a member of) a conference. - */ -function JitsiParticipant(id, conference, displayName){ - this._id = id; - this._conference = conference; - this._displayName = displayName; -} - -/** - * @returns {JitsiConference} The conference that this participant belongs to. - */ -JitsiParticipant.prototype.getConference = function() { - return this._conference; -} - -/** - * @returns {Array.} The list of media tracks for this participant. - */ -JitsiParticipant.prototype.getTracks = function() { - -} - -/** - * @returns {String} The ID (i.e. JID) of this participant. - */ -JitsiParticipant.prototype.getId = function() { - return this._id; -} - -/** - * @returns {String} The human-readable display name of this participant. - */ -JitsiParticipant.prototype.getDisplayName = function() { - return this._displayName; -} - -/** - * @returns {Boolean} Whether this participant is a moderator or not. - */ -JitsiParticipant.prototype.isModerator = function() { -} - -// Gets a link to an etherpad instance advertised by the participant? -//JitsiParticipant.prototype.getEtherpad = function() { -// -//} - - -/* - * @returns {Boolean} Whether this participant has muted their audio. - */ -JitsiParticipant.prototype.isAudioMuted = function() { - -} - -/* - * @returns {Boolean} Whether this participant has muted their video. - */ -JitsiParticipant.prototype.isVideoMuted = function() { - -} - -/* - * @returns {???} The latest statistics reported by this participant (i.e. info used to populate the GSM bars) - * TODO: do we expose this or handle it internally? - */ -JitsiParticipant.prototype.getLatestStats = function() { - -} - -/** - * @returns {String} The role of this participant. - */ -JitsiParticipant.prototype.getRole = function() { - -} - -/* - * @returns {Boolean} Whether this participant is the conference focus (i.e. jicofo). - */ -JitsiParticipant.prototype.isFocus = function() { - -} - -/* - * @returns {Boolean} Whether this participant is a conference recorder (i.e. jirecon). - */ -JitsiParticipant.prototype.isRecorder = function() { - -} - -/* - * @returns {Boolean} Whether this participant is a SIP gateway (i.e. jigasi). - */ -JitsiParticipant.prototype.isSipGateway = function() { - -} - -/** - * @returns {String} The ID for this participant's avatar. - */ -JitsiParticipant.prototype.getAvatarId = function() { - -} - -/** - * @returns {Boolean} Whether this participant is currently sharing their screen. - */ -JitsiParticipant.prototype.isScreenSharing = function() { - -} - -/** - * @returns {String} The user agent of this participant (i.e. browser userAgent string). - */ -JitsiParticipant.prototype.getUserAgent = function() { - -} - -/** - * Kicks the participant from the conference (requires certain privileges). - */ -JitsiParticipant.prototype.kick = function() { - -} - -/** - * Asks this participant to mute themselves. - */ -JitsiParticipant.prototype.askToMute = function() { - -} - - -module.exports = JitsiParticipant(); - -},{}],9:[function(require,module,exports){ +},{"./JitsiConferenceErrors":2,"./JitsiConferenceEvents":3,"./JitsiConnection":4,"./JitsiConnectionErrors":5,"./JitsiConnectionEvents":6,"./modules/RTC/RTC":15,"es6-promise":44,"jitsi-meet-logger":46}],8:[function(require,module,exports){ module.exports = { /** - * Returns JitsiTrackErrors based on the error object passed by GUM * @param error the error + * Returns JitsiMeetJSError based on the error object passed by GUM + * @param error the error * @param {Object} options the options object given to GUM. */ parseError: function (error, options) { @@ -836,34 +761,178 @@ module.exports = { error.constraintName == "minHeight" || error.constraintName == "maxHeight") && options.devices.indexOf("video") !== -1) { - return this.UNSUPPORTED_RESOLUTION; + return this.GET_TRACKS_RESOLUTION; } else { - return this.GENERAL; + return this.GET_TRACKS_GENERAL; } }, - UNSUPPORTED_RESOLUTION: "gum.unsupported_resolution", - GENERAL: "gum.general" + GET_TRACKS_RESOLUTION: "gum.get_tracks_resolution", + GET_TRACKS_GENERAL: "gum.get_tracks_general" }; +},{}],9:[function(require,module,exports){ +/** + * Represents a participant in (a member of) a conference. + */ +function JitsiParticipant(id, conference, displayName){ + this._id = id; + this._conference = conference; + this._displayName = displayName; + this._supportsDTMF = false; + this._tracks = []; + this._isModerator = false; +} + +/** + * @returns {JitsiConference} The conference that this participant belongs to. + */ +JitsiParticipant.prototype.getConference = function() { + return this._conference; +}; + +/** + * @returns {Array.} The list of media tracks for this participant. + */ +JitsiParticipant.prototype.getTracks = function() { + return this._tracks; +}; + +/** + * @returns {String} The ID (i.e. JID) of this participant. + */ +JitsiParticipant.prototype.getId = function() { + return this._id; +}; + +/** + * @returns {String} The human-readable display name of this participant. + */ +JitsiParticipant.prototype.getDisplayName = function() { + return this._displayName; +}; + +/** + * @returns {Boolean} Whether this participant is a moderator or not. + */ +JitsiParticipant.prototype.isModerator = function() { + return this._isModerator; +}; + +// Gets a link to an etherpad instance advertised by the participant? +//JitsiParticipant.prototype.getEtherpad = function() { +// +//} + + +/* + * @returns {Boolean} Whether this participant has muted their audio. + */ +JitsiParticipant.prototype.isAudioMuted = function() { + return this.getTracks().reduce(function (track, isAudioMuted) { + return isAudioMuted && (track.isVideoTrack() || track.isMuted()); + }, true); +}; + +/* + * @returns {Boolean} Whether this participant has muted their video. + */ +JitsiParticipant.prototype.isVideoMuted = function() { + return this.getTracks().reduce(function (track, isVideoMuted) { + return isVideoMuted && (track.isAudioTrack() || track.isMuted()); + }, true); +}; + +/* + * @returns {???} The latest statistics reported by this participant (i.e. info used to populate the GSM bars) + * TODO: do we expose this or handle it internally? + */ +JitsiParticipant.prototype.getLatestStats = function() { + +}; + +/** + * @returns {String} The role of this participant. + */ +JitsiParticipant.prototype.getRole = function() { + +}; + +/* + * @returns {Boolean} Whether this participant is the conference focus (i.e. jicofo). + */ +JitsiParticipant.prototype.isFocus = function() { + +}; + +/* + * @returns {Boolean} Whether this participant is a conference recorder (i.e. jirecon). + */ +JitsiParticipant.prototype.isRecorder = function() { + +}; + +/* + * @returns {Boolean} Whether this participant is a SIP gateway (i.e. jigasi). + */ +JitsiParticipant.prototype.isSipGateway = function() { + +}; + +/** + * @returns {Boolean} Whether this participant is currently sharing their screen. + */ +JitsiParticipant.prototype.isScreenSharing = function() { + +}; + +/** + * @returns {String} The user agent of this participant (i.e. browser userAgent string). + */ +JitsiParticipant.prototype.getUserAgent = function() { + +}; + +/** + * Kicks the participant from the conference (requires certain privileges). + */ +JitsiParticipant.prototype.kick = function() { + +}; + +/** + * Asks this participant to mute themselves. + */ +JitsiParticipant.prototype.askToMute = function() { + +}; + +JitsiParticipant.prototype.supportsDTMF = function () { + return this._supportsDTMF; +}; + + +module.exports = JitsiParticipant; + },{}],10:[function(require,module,exports){ -var JitsiTrackEvents = { - /** - * A media track mute status was changed. - */ - TRACK_MUTE_CHANGED: "track.trackMuteChanged", - /** - * Audio levels of a this track was changed. - */ - TRACK_AUDIO_LEVEL_CHANGED: "track.audioLevelsChanged", - /** - * The media track was removed to the conference. - */ - TRACK_STOPPED: "track.TRACK_STOPPED", +(function (__filename){ +var logger = require("jitsi-meet-logger").getLogger(__filename); + +function JitsiDTMFManager (localAudio, peerConnection) { + var tracks = localAudio._getTracks(); + if (!tracks.length) { + throw new Error("Failed to initialize DTMFSender: no audio track."); + } + this.dtmfSender = peerConnection.peerconnection.createDTMFSender(tracks[0]); + logger.debug("Initialized DTMFSender"); +} + + +JitsiDTMFManager.prototype.sendTones = function (tones, duration, pause) { + this.dtmfSender.insertDTMF(tones, (duration || 200), (pause || 200)); }; -module.exports = JitsiTrackEvents; - -},{}],11:[function(require,module,exports){ +}).call(this,"/modules/DTMF/JitsiDTMFManager.js") +},{"jitsi-meet-logger":46}],11:[function(require,module,exports){ (function (__filename){ /* global config, APP, Strophe */ @@ -1079,31 +1148,30 @@ module.exports = DataChannels; }).call(this,"/modules/RTC/DataChannels.js") -},{"../../service/RTC/RTCEvents":82,"jitsi-meet-logger":46}],12:[function(require,module,exports){ +},{"../../service/RTC/RTCEvents":78,"jitsi-meet-logger":46}],12:[function(require,module,exports){ var JitsiTrack = require("./JitsiTrack"); var StreamEventTypes = require("../../service/RTC/StreamEventTypes"); var RTCBrowserType = require("./RTCBrowserType"); -var RTC = require("./RTCUtils"); /** * Represents a single media track (either audio or video). * @constructor */ -function JitsiLocalTrack(stream, videoType, +function JitsiLocalTrack(RTC, stream, eventEmitter, videoType, resolution) { + JitsiTrack.call(this, RTC, stream, + function () { + if(!self.dontFireRemoveEvent) + self.eventEmitter.emit( + StreamEventTypes.EVENT_TYPE_LOCAL_ENDED, self); + self.dontFireRemoveEvent = false; + }); + this.eventEmitter = eventEmitter; this.videoType = videoType; this.dontFireRemoveEvent = false; this.resolution = resolution; var self = this; - JitsiTrack.call(this, null, stream, - function () { - if(!self.dontFireRemoveEvent && self.rtc) - self.rtc.eventEmitter.emit( - StreamEventTypes.EVENT_TYPE_LOCAL_ENDED, self); - self.dontFireRemoveEvent = false; - }); - } JitsiLocalTrack.prototype = Object.create(JitsiTrack.prototype); @@ -1134,24 +1202,26 @@ JitsiLocalTrack.prototype._setMute = function (mute) { this.rtc.room.setAudioMute(mute); else this.rtc.room.setVideoMute(mute); - this.rtc.eventEmitter.emit(StreamEventTypes.TRACK_MUTE_CHANGED, this); + this.eventEmitter.emit(StreamEventTypes.TRACK_MUTE_CHANGED, this); } else { if (mute) { this.dontFireRemoveEvent = true; this.rtc.room.removeStream(this.stream); - RTC.stopMediaStream(this.stream); + this.rtc.stopMediaStream(this.stream); if(isAudio) this.rtc.room.setAudioMute(mute); else this.rtc.room.setVideoMute(mute); this.stream = null; - this.rtc.eventEmitter.emit(StreamEventTypes.TRACK_MUTE_CHANGED, this); + this.eventEmitter.emit(StreamEventTypes.TRACK_MUTE_CHANGED, this); //FIXME: Maybe here we should set the SRC for the containers to something } else { var self = this; + var RTC = require("./RTCUtils"); RTC.obtainAudioAndVideoPermissions({ devices: (isAudio ? ["audio"] : ["video"]), - resolution: self.resolution}) + resolution: self.resolution, + dontCreateJitsiTrack: true}) .then(function (streams) { var stream = null; for(var i = 0; i < streams.length; i++) { @@ -1177,7 +1247,7 @@ JitsiLocalTrack.prototype._setMute = function (mute) { self.rtc.room.setAudioMute(mute); else self.rtc.room.setVideoMute(mute); - self.rtc.eventEmitter.emit(StreamEventTypes.TRACK_MUTE_CHANGED, self); + self.eventEmitter.emit(StreamEventTypes.TRACK_MUTE_CHANGED, self); }); }); } @@ -1232,7 +1302,7 @@ JitsiLocalTrack.prototype._setRTC = function (rtc) { module.exports = JitsiLocalTrack; -},{"../../service/RTC/StreamEventTypes":84,"./JitsiTrack":14,"./RTCBrowserType":16,"./RTCUtils":17}],13:[function(require,module,exports){ +},{"../../service/RTC/StreamEventTypes":80,"./JitsiTrack":14,"./RTCBrowserType":16,"./RTCUtils":17}],13:[function(require,module,exports){ var JitsiTrack = require("./JitsiTrack"); var StreamEventTypes = require("../../service/RTC/StreamEventTypes"); @@ -1290,7 +1360,7 @@ JitsiRemoteTrack.prototype.isMuted = function () { * Returns the participant id which owns the track. * @returns {string} the id of the participants. */ -JitsiRemoteTrack.prototype.getParitcipantId = function() { +JitsiRemoteTrack.prototype.getParticipantId = function() { return Strophe.getResourceFromJid(this.peerjid); }; @@ -1300,7 +1370,7 @@ delete JitsiRemoteTrack.prototype.start; module.exports = JitsiRemoteTrack; -},{"../../service/RTC/StreamEventTypes":84,"./JitsiTrack":14}],14:[function(require,module,exports){ +},{"../../service/RTC/StreamEventTypes":80,"./JitsiTrack":14}],14:[function(require,module,exports){ var RTCBrowserType = require("./RTCBrowserType"); /** @@ -1326,17 +1396,17 @@ function implementOnEndedHandling(jitsiTrack) { * @param handler the handler */ function addMediaStreamInactiveHandler(mediaStream, handler) { - if(RTCBrowserType.isTemasysPluginUsed()) { - // themasys - mediaStream.attachEvent('ended', function () { - handler(mediaStream); - }); - } - else { + if (mediaStream.addEventListener) { + // chrome if(typeof mediaStream.active !== "undefined") mediaStream.oninactive = handler; else mediaStream.onended = handler; + } else { + // themasys + mediaStream.attachEvent('ended', function () { + handler(mediaStream); + }); } } @@ -1395,6 +1465,20 @@ JitsiTrack.prototype.getType = function() { return this.type; }; +/** + * Check if this is audiotrack. + */ +JitsiTrack.prototype.isAudioTrack = function () { + return this.getType() === JitsiTrack.AUDIO; +}; + +/** + * Check if this is videotrack. + */ +JitsiTrack.prototype.isVideoTrack = function () { + return this.getType() === JitsiTrack.VIDEO; +}; + /** * Returns the RTCMediaStream from the browser (?). */ @@ -1505,22 +1589,6 @@ var MediaStreamType = require("../../service/RTC/MediaStreamTypes"); var StreamEventTypes = require("../../service/RTC/StreamEventTypes.js"); var RTCEvents = require("../../service/RTC/RTCEvents.js"); -function createLocalTracks(streams) { - var newStreams = [] - for (var i = 0; i < streams.length; i++) { - var localStream = new JitsiLocalTrack(streams[i].stream, - streams[i].videoType, streams[i].resolution); - newStreams.push(localStream); - if (streams[i].isMuted === true) - localStream.setMute(true); - //FIXME: - // var eventType = StreamEventTypes.EVENT_TYPE_LOCAL_CREATED; - // - // eventEmitter.emit(eventType, localStream); - } - return newStreams; -} - function RTC(room, options) { this.room = room; this.localStreams = []; @@ -1553,7 +1621,7 @@ function RTC(room, options) { * @returns {*} Promise object that will receive the new JitsiTracks */ RTC.obtainAudioAndVideoPermissions = function (options) { - return RTCUtils.obtainAudioAndVideoPermissions(options).then(createLocalTracks); + return RTCUtils.obtainAudioAndVideoPermissions(options); } RTC.prototype.onIncommingCall = function(event) { @@ -1750,7 +1818,7 @@ RTC.prototype.setVideoMute = function (mute, callback, options) { module.exports = RTC; -},{"../../service/RTC/MediaStreamTypes":81,"../../service/RTC/RTCEvents.js":82,"../../service/RTC/StreamEventTypes.js":84,"../../service/desktopsharing/DesktopSharingEventTypes":86,"./DataChannels":11,"./JitsiLocalTrack.js":12,"./JitsiRemoteTrack.js":13,"./JitsiTrack":14,"./RTCBrowserType":16,"./RTCUtils.js":17,"events":42}],16:[function(require,module,exports){ +},{"../../service/RTC/MediaStreamTypes":77,"../../service/RTC/RTCEvents.js":78,"../../service/RTC/StreamEventTypes.js":80,"../../service/desktopsharing/DesktopSharingEventTypes":82,"./DataChannels":11,"./JitsiLocalTrack.js":12,"./JitsiRemoteTrack.js":13,"./JitsiTrack":14,"./RTCBrowserType":16,"./RTCUtils.js":17,"events":42}],16:[function(require,module,exports){ var currentBrowser; @@ -1933,8 +2001,10 @@ var RTCEvents = require("../../service/RTC/RTCEvents"); var AdapterJS = require("./adapter.screenshare"); var SDPUtil = require("../xmpp/SDPUtil"); var EventEmitter = require("events"); +var JitsiLocalTrack = require("./JitsiLocalTrack"); +var StreamEventTypes = require("../../service/RTC/StreamEventTypes.js"); var screenObtainer = require("./ScreenObtainer"); -var JitsiTrackErrors = require("../../JitsiTrackErrors"); +var JitsiMeetJSError = require("../../JitsiMeetJSErrors"); var eventEmitter = new EventEmitter(); @@ -2261,11 +2331,27 @@ function obtainDevices(options) { function (error) { logger.error( "failed to obtain " + device + " stream - stop", error); - options.errorCallback(JitsiTrackErrors.parseError(error)); + options.errorCallback(JitsiMeetJSError.parseError(error)); }); } +function createLocalTracks(streams) { + var newStreams = [] + for (var i = 0; i < streams.length; i++) { + var localStream = new JitsiLocalTrack(null, streams[i].stream, + eventEmitter, streams[i].videoType, streams[i].resolution); + newStreams.push(localStream); + if (streams[i].isMuted === true) + localStream.setMute(true); + + var eventType = StreamEventTypes.EVENT_TYPE_LOCAL_CREATED; + + eventEmitter.emit(eventType, localStream); + } + return newStreams; +} + /** * Handles the newly created Media Streams. * @param streams the new Media Streams @@ -2288,7 +2374,7 @@ function handleLocalStream(streams, resolution) { } var videoTracks = audioVideo.getVideoTracks(); - if(videoTracks.length) { + if(audioTracks.length) { videoStream = new webkitMediaStream(); for (i = 0; i < videoTracks.length; i++) { videoStream.addTrack(videoTracks[i]); @@ -2322,17 +2408,10 @@ function handleLocalStream(streams, resolution) { //Options parameter is to pass config options. Currently uses only "useIPv6". var RTCUtils = { init: function (options) { - return new Promise(function(resolve, reject) { - if (RTCBrowserType.isFirefox()) { - var FFversion = RTCBrowserType.getFirefoxVersion(); - if (FFversion < 40) { - logger.error( - "Firefox version too old: " + FFversion + - ". Required >= 40."); - reject(new Error("Firefox version too old: " + FFversion + - ". Required >= 40.")); - return; - } + var self = this; + if (RTCBrowserType.isFirefox()) { + var FFversion = RTCBrowserType.getFirefoxVersion(); + if (FFversion >= 40) { this.peerconnection = mozRTCPeerConnection; this.getUserMedia = wrapGetUserMedia(navigator.mozGetUserMedia.bind(navigator)); this.enumerateDevices = wrapEnumerateDevices( @@ -2374,124 +2453,128 @@ var RTCUtils = { }; RTCSessionDescription = mozRTCSessionDescription; RTCIceCandidate = mozRTCIceCandidate; - } else if (RTCBrowserType.isChrome() || RTCBrowserType.isOpera()) { - this.peerconnection = webkitRTCPeerConnection; - var getUserMedia = navigator.webkitGetUserMedia.bind(navigator); - if (navigator.mediaDevices) { - this.getUserMedia = wrapGetUserMedia(getUserMedia); - this.enumerateDevices = wrapEnumerateDevices( - navigator.mediaDevices.enumerateDevices.bind(navigator.mediaDevices) - ); - } else { - this.getUserMedia = getUserMedia; - this.enumerateDevices = enumerateDevicesThroughMediaStreamTrack; - } - this.attachMediaStream = function (element, stream) { - element.attr('src', webkitURL.createObjectURL(stream)); - }; - this.getStreamID = function (stream) { - // streams from FF endpoints have the characters '{' and '}' - // that make jQuery choke. - return SDPUtil.filter_special_chars(stream.id); - }; - this.getVideoSrc = function (element) { - if (!element) - return null; - return element.getAttribute("src"); - }; - this.setVideoSrc = function (element, src) { - if (element) - element.setAttribute("src", src); - }; - // DTLS should now be enabled by default but.. - this.pc_constraints = {'optional': [ - {'DtlsSrtpKeyAgreement': 'true'} - ]}; - if (options.useIPv6) { - // https://code.google.com/p/webrtc/issues/detail?id=2828 - this.pc_constraints.optional.push({googIPv6: true}); - } - if (RTCBrowserType.isAndroid()) { - this.pc_constraints = {}; // disable DTLS on Android - } - if (!webkitMediaStream.prototype.getVideoTracks) { - webkitMediaStream.prototype.getVideoTracks = function () { - return this.videoTracks; - }; - } - if (!webkitMediaStream.prototype.getAudioTracks) { - webkitMediaStream.prototype.getAudioTracks = function () { - return this.audioTracks; - }; - } - } - // Detect IE/Safari - else if (RTCBrowserType.isTemasysPluginUsed()) { - - //AdapterJS.WebRTCPlugin.setLogLevel( - // AdapterJS.WebRTCPlugin.PLUGIN_LOG_LEVELS.VERBOSE); - - AdapterJS.webRTCReady(function (isPlugin) { - - self.peerconnection = RTCPeerConnection; - self.getUserMedia = window.getUserMedia; - self.enumerateDevices = enumerateDevicesThroughMediaStreamTrack; - self.attachMediaStream = function (elSel, stream) { - - if (stream.id === "dummyAudio" || stream.id === "dummyVideo") { - return; - } - - attachMediaStream(elSel[0], stream); - }; - self.getStreamID = function (stream) { - var id = SDPUtil.filter_special_chars(stream.label); - return id; - }; - self.getVideoSrc = function (element) { - if (!element) { - logger.warn("Attempt to get video SRC of null element"); - return null; - } - var children = element.children; - for (var i = 0; i !== children.length; ++i) { - if (children[i].name === 'streamId') { - return children[i].value; - } - } - //logger.info(element.id + " SRC: " + src); - return null; - }; - self.setVideoSrc = function (element, src) { - //logger.info("Set video src: ", element, src); - if (!src) { - logger.warn("Not attaching video stream, 'src' is null"); - return; - } - AdapterJS.WebRTCPlugin.WaitForPluginReady(); - var stream = AdapterJS.WebRTCPlugin.plugin - .getStreamWithId(AdapterJS.WebRTCPlugin.pageId, src); - attachMediaStream(element, stream); - }; - - onReady(options, self.getUserMediaWithConstraints); - resolve(); - }); } else { - try { - logger.error('Browser does not appear to be WebRTC-capable'); - } catch (e) { - } - reject('Browser does not appear to be WebRTC-capable'); + logger.error( + "Firefox version too old: " + FFversion + ". Required >= 40."); + window.location.href = 'unsupported_browser.html'; return; } - // Call onReady() if Temasys plugin is not used - if (!RTCBrowserType.isTemasysPluginUsed()) { - onReady(options, self.getUserMediaWithConstraints); - resolve(); + } else if (RTCBrowserType.isChrome() || RTCBrowserType.isOpera()) { + this.peerconnection = webkitRTCPeerConnection; + var getUserMedia = navigator.webkitGetUserMedia.bind(navigator); + if (navigator.mediaDevices) { + this.getUserMedia = wrapGetUserMedia(getUserMedia); + this.enumerateDevices = wrapEnumerateDevices( + navigator.mediaDevices.enumerateDevices.bind(navigator.mediaDevices) + ); + } else { + this.getUserMedia = getUserMedia; + this.enumerateDevices = enumerateDevicesThroughMediaStreamTrack; } - }.bind(this)); + this.attachMediaStream = function (element, stream) { + element.attr('src', webkitURL.createObjectURL(stream)); + }; + this.getStreamID = function (stream) { + // streams from FF endpoints have the characters '{' and '}' + // that make jQuery choke. + return SDPUtil.filter_special_chars(stream.id); + }; + this.getVideoSrc = function (element) { + if (!element) + return null; + return element.getAttribute("src"); + }; + this.setVideoSrc = function (element, src) { + if (element) + element.setAttribute("src", src); + }; + // DTLS should now be enabled by default but.. + this.pc_constraints = {'optional': [ + {'DtlsSrtpKeyAgreement': 'true'} + ]}; + if (options.useIPv6) { + // https://code.google.com/p/webrtc/issues/detail?id=2828 + this.pc_constraints.optional.push({googIPv6: true}); + } + if (RTCBrowserType.isAndroid()) { + this.pc_constraints = {}; // disable DTLS on Android + } + if (!webkitMediaStream.prototype.getVideoTracks) { + webkitMediaStream.prototype.getVideoTracks = function () { + return this.videoTracks; + }; + } + if (!webkitMediaStream.prototype.getAudioTracks) { + webkitMediaStream.prototype.getAudioTracks = function () { + return this.audioTracks; + }; + } + } + // Detect IE/Safari + else if (RTCBrowserType.isTemasysPluginUsed()) { + + //AdapterJS.WebRTCPlugin.setLogLevel( + // AdapterJS.WebRTCPlugin.PLUGIN_LOG_LEVELS.VERBOSE); + + AdapterJS.webRTCReady(function (isPlugin) { + + self.peerconnection = RTCPeerConnection; + self.getUserMedia = window.getUserMedia; + self.enumerateDevices = enumerateDevicesThroughMediaStreamTrack; + self.attachMediaStream = function (elSel, stream) { + + if (stream.id === "dummyAudio" || stream.id === "dummyVideo") { + return; + } + + attachMediaStream(elSel[0], stream); + }; + self.getStreamID = function (stream) { + var id = SDPUtil.filter_special_chars(stream.label); + return id; + }; + self.getVideoSrc = function (element) { + if (!element) { + logger.warn("Attempt to get video SRC of null element"); + return null; + } + var children = element.children; + for (var i = 0; i !== children.length; ++i) { + if (children[i].name === 'streamId') { + return children[i].value; + } + } + //logger.info(element.id + " SRC: " + src); + return null; + }; + self.setVideoSrc = function (element, src) { + //logger.info("Set video src: ", element, src); + if (!src) { + logger.warn("Not attaching video stream, 'src' is null"); + return; + } + AdapterJS.WebRTCPlugin.WaitForPluginReady(); + var stream = AdapterJS.WebRTCPlugin.plugin + .getStreamWithId(AdapterJS.WebRTCPlugin.pageId, src); + attachMediaStream(element, stream); + }; + + onReady(options, self.getUserMediaWithConstraints); + }); + } else { + try { + logger.error('Browser does not appear to be WebRTC-capable'); + } catch (e) { + } + return; + } + + // Call onReady() if Temasys plugin is not used + if (!RTCBrowserType.isTemasysPluginUsed()) { + onReady(options, self.getUserMediaWithConstraints); + } + }, /** * @param {string[]} um required user media types @@ -2554,13 +2637,15 @@ var RTCUtils = { options = options || {}; return new Promise(function (resolve, reject) { var successCallback = function (stream) { - resolve(handleLocalStream(stream, options.resolution)); + var streams = handleLocalStream(stream, options.resolution); + resolve(options.dontCreateJitsiTracks? + streams: createLocalTracks(streams)); }; options.devices = options.devices || ['audio', 'video']; if(!screenObtainer.isSupported() && options.devices.indexOf("desktop") !== -1){ - reject(new Error("Desktop sharing is not supported!")); + options.devices.splice(options.devices.indexOf("desktop"), 1); } if (RTCBrowserType.isFirefox() || RTCBrowserType.isTemasysPluginUsed()) { @@ -2603,14 +2688,14 @@ var RTCUtils = { desktopStream: desktopStream}); }, function (error) { reject( - JitsiTrackErrors.parseError(error)); + JitsiMeetJSError.parseError(error)); }); } else { successCallback({audioVideo: stream}); } }, function (error) { - reject(JitsiTrackErrors.parseError(error)); + reject(JitsiMeetJSError.parseError(error)); }, options); } else if (hasDesktop) { @@ -2619,7 +2704,7 @@ var RTCUtils = { successCallback({desktopStream: stream}); }, function (error) { reject( - JitsiTrackErrors.parseError(error)); + JitsiMeetJSError.parseError(error)); }); } } @@ -2672,7 +2757,7 @@ var RTCUtils = { module.exports = RTCUtils; }).call(this,"/modules/RTC/RTCUtils.js") -},{"../../JitsiTrackErrors":9,"../../service/RTC/RTCEvents":82,"../../service/RTC/Resolutions":83,"../xmpp/SDPUtil":31,"./RTCBrowserType":16,"./ScreenObtainer":18,"./adapter.screenshare":19,"events":42,"jitsi-meet-logger":46}],18:[function(require,module,exports){ +},{"../../JitsiMeetJSErrors":8,"../../service/RTC/RTCEvents":78,"../../service/RTC/Resolutions":79,"../../service/RTC/StreamEventTypes.js":80,"../xmpp/SDPUtil":31,"./JitsiLocalTrack":12,"./RTCBrowserType":16,"./ScreenObtainer":18,"./adapter.screenshare":19,"events":42,"jitsi-meet-logger":46}],18:[function(require,module,exports){ (function (__filename){ /* global chrome, $, alert */ /* jshint -W003 */ @@ -3094,7 +3179,7 @@ function initFirefoxExtensionDetection(options) { module.exports = ScreenObtainer; }).call(this,"/modules/RTC/ScreenObtainer.js") -},{"../../service/desktopsharing/DesktopSharingEventTypes":86,"./RTCBrowserType":16,"./adapter.screenshare":19,"jitsi-meet-logger":46}],19:[function(require,module,exports){ +},{"../../service/desktopsharing/DesktopSharingEventTypes":82,"./RTCBrowserType":16,"./adapter.screenshare":19,"jitsi-meet-logger":46}],19:[function(require,module,exports){ (function (__filename){ /*! adapterjs - v0.12.0 - 2015-09-04 */ var console = require("jitsi-meet-logger").getLogger(__filename); @@ -4483,7 +4568,7 @@ LocalStatsCollector.prototype.stop = function () { }; module.exports = LocalStatsCollector; -},{"../../service/statistics/Events":87,"../../service/statistics/constants":88,"../RTC/RTCBrowserType":16}],22:[function(require,module,exports){ +},{"../../service/statistics/Events":83,"../../service/statistics/constants":84,"../RTC/RTCBrowserType":16}],22:[function(require,module,exports){ (function (__filename){ /* global require, ssrc2jid */ /* jshint -W117 */ @@ -5210,7 +5295,7 @@ StatsCollector.prototype.processAudioLevelReport = function () { }; }).call(this,"/modules/statistics/RTPStatsCollector.js") -},{"../../service/statistics/Events":87,"../RTC/RTCBrowserType":16,"jitsi-meet-logger":46}],23:[function(require,module,exports){ +},{"../../service/statistics/Events":83,"../RTC/RTCBrowserType":16,"jitsi-meet-logger":46}],23:[function(require,module,exports){ /* global require, APP */ var LocalStats = require("./LocalStatsCollector.js"); var RTPStats = require("./RTPStatsCollector.js"); @@ -5358,7 +5443,7 @@ Statistics.LOCAL_JID = require("../../service/statistics/constants").LOCAL_JID; module.exports = Statistics; -},{"../../service/statistics/Events":87,"../../service/statistics/constants":88,"./LocalStatsCollector.js":21,"./RTPStatsCollector.js":22,"events":42}],24:[function(require,module,exports){ +},{"../../service/statistics/Events":83,"../../service/statistics/constants":84,"./LocalStatsCollector.js":21,"./RTPStatsCollector.js":22,"events":42}],24:[function(require,module,exports){ /** /** * @const @@ -6079,7 +6164,7 @@ ChatRoom.prototype.getJidBySSRC = function (ssrc) { module.exports = ChatRoom; }).call(this,"/modules/xmpp/ChatRoom.js") -},{"../../service/xmpp/XMPPEvents":89,"./moderator":33,"events":42,"jitsi-meet-logger":46}],26:[function(require,module,exports){ +},{"../../service/xmpp/XMPPEvents":85,"./moderator":33,"events":42,"jitsi-meet-logger":46}],26:[function(require,module,exports){ (function (__filename){ /* * JingleSession provides an API to manage a single Jingle session. We will @@ -7805,7 +7890,7 @@ JingleSessionPC.prototype.remoteStreamAdded = function (data, times) { module.exports = JingleSessionPC; }).call(this,"/modules/xmpp/JingleSessionPC.js") -},{"../../service/xmpp/XMPPEvents":89,"../RTC/RTC":15,"../RTC/RTCBrowserType":16,"./JingleSession":26,"./LocalSSRCReplacement":28,"./SDP":29,"./SDPDiffer":30,"./SDPUtil":31,"./TraceablePeerConnection":32,"async":41,"jitsi-meet-logger":46,"sdp-transform":78}],28:[function(require,module,exports){ +},{"../../service/xmpp/XMPPEvents":85,"../RTC/RTC":15,"../RTC/RTCBrowserType":16,"./JingleSession":26,"./LocalSSRCReplacement":28,"./SDP":29,"./SDPDiffer":30,"./SDPUtil":31,"./TraceablePeerConnection":32,"async":41,"jitsi-meet-logger":46,"sdp-transform":74}],28:[function(require,module,exports){ (function (__filename){ /* global $ */ var logger = require("jitsi-meet-logger").getLogger(__filename); @@ -8929,9 +9014,6 @@ module.exports = SDPDiffer; (function (__filename){ var logger = require("jitsi-meet-logger").getLogger(__filename); -var RTCBrowserType = require("../RTC/RTCBrowserType"); - - SDPUtil = { filter_special_chars: function (text) { return text.replace(/[\\\/\{,\}\+]/g, ""); @@ -9251,7 +9333,6 @@ SDPUtil = { protocol = 'tcp'; } - line += protocol; //.toUpperCase(); // chrome M23 doesn't like this line += ' '; line += cand.getAttribute('priority'); line += ' '; @@ -9291,9 +9372,8 @@ SDPUtil = { } }; module.exports = SDPUtil; - }).call(this,"/modules/xmpp/SDPUtil.js") -},{"../RTC/RTCBrowserType":16,"jitsi-meet-logger":46}],32:[function(require,module,exports){ +},{"jitsi-meet-logger":46}],32:[function(require,module,exports){ (function (__filename){ /* global $ */ var RTC = require('../RTC/RTC'); @@ -9322,7 +9402,7 @@ function TraceablePeerConnection(ice_config, constraints, session) { var Interop = require('sdp-interop').Interop; this.interop = new Interop(); var Simulcast = require('sdp-simulcast'); - this.simulcast = new Simulcast({numOfLayers: 2, explodeRemoteSimulcast: false}); + this.simulcast = new Simulcast({numOfLayers: 3, explodeRemoteSimulcast: false}); // override as desired this.trace = function (what, info) { @@ -9518,7 +9598,7 @@ if (TraceablePeerConnection.prototype.__defineGetter__ !== undefined) { // FIXME this should probably be after the Unified Plan -> Plan B // transformation. desc = SSRCReplacement.mungeLocalVideoSSRC(desc); - + this.trace('getLocalDescription::preTransform', dumpSDP(desc)); // if we're running on FF, transform to Plan B first. @@ -9741,8 +9821,9 @@ TraceablePeerConnection.prototype.getStats = function(callback, errback) { module.exports = TraceablePeerConnection; + }).call(this,"/modules/xmpp/TraceablePeerConnection.js") -},{"../../service/xmpp/XMPPEvents":89,"../RTC/RTC":15,"../RTC/RTCBrowserType.js":16,"./LocalSSRCReplacement":28,"jitsi-meet-logger":46,"sdp-interop":64,"sdp-simulcast":71,"sdp-transform":78}],33:[function(require,module,exports){ +},{"../../service/xmpp/XMPPEvents":85,"../RTC/RTC":15,"../RTC/RTCBrowserType.js":16,"./LocalSSRCReplacement":28,"jitsi-meet-logger":46,"sdp-interop":64,"sdp-simulcast":67,"sdp-transform":74}],33:[function(require,module,exports){ (function (__filename){ /* global $, $iq, APP, config, messageHandler, roomName, sessionTerminated, Strophe, Util */ @@ -10179,7 +10260,7 @@ module.exports = Moderator; }).call(this,"/modules/xmpp/moderator.js") -},{"../../service/authentication/AuthenticationEvents":85,"../../service/xmpp/XMPPEvents":89,"../settings/Settings":20,"jitsi-meet-logger":46}],34:[function(require,module,exports){ +},{"../../service/authentication/AuthenticationEvents":81,"../../service/xmpp/XMPPEvents":85,"../settings/Settings":20,"jitsi-meet-logger":46}],34:[function(require,module,exports){ (function (__filename){ /* jshint -W117 */ /* a simple MUC connection plugin @@ -10573,7 +10654,7 @@ module.exports = function(XMPP, eventEmitter) { }).call(this,"/modules/xmpp/strophe.jingle.js") -},{"../../service/xmpp/XMPPEvents":89,"../RTC/RTCBrowserType":16,"./JingleSessionPC":27,"jitsi-meet-logger":46}],36:[function(require,module,exports){ +},{"../../service/xmpp/XMPPEvents":85,"../RTC/RTCBrowserType":16,"./JingleSessionPC":27,"jitsi-meet-logger":46}],36:[function(require,module,exports){ /* global Strophe */ module.exports = function () { @@ -10721,7 +10802,7 @@ module.exports = function (XMPP, eventEmitter) { }; }).call(this,"/modules/xmpp/strophe.ping.js") -},{"../../service/xmpp/XMPPEvents":89,"jitsi-meet-logger":46}],38:[function(require,module,exports){ +},{"../../service/xmpp/XMPPEvents":85,"jitsi-meet-logger":46}],38:[function(require,module,exports){ (function (__filename){ /* jshint -W117 */ var logger = require("jitsi-meet-logger").getLogger(__filename); @@ -11180,7 +11261,7 @@ XMPP.prototype.disconnect = function () { module.exports = XMPP; }).call(this,"/modules/xmpp/xmpp.js") -},{"../../JitsiConnectionErrors":5,"../../JitsiConnectionEvents":6,"../../service/RTC/RTCEvents":82,"../../service/RTC/StreamEventTypes":84,"../../service/xmpp/XMPPEvents":89,"../RTC/RTC":15,"./strophe.emuc":34,"./strophe.jingle":35,"./strophe.logger":36,"./strophe.ping":37,"./strophe.rayo":38,"./strophe.util":39,"events":42,"jitsi-meet-logger":46,"pako":47}],41:[function(require,module,exports){ +},{"../../JitsiConnectionErrors":5,"../../JitsiConnectionEvents":6,"../../service/RTC/RTCEvents":78,"../../service/RTC/StreamEventTypes":80,"../../service/xmpp/XMPPEvents":85,"../RTC/RTC":15,"./strophe.emuc":34,"./strophe.jingle":35,"./strophe.logger":36,"./strophe.ping":37,"./strophe.rayo":38,"./strophe.util":39,"events":42,"jitsi-meet-logger":46,"pako":47}],41:[function(require,module,exports){ (function (process){ /*! * async @@ -21230,487 +21311,7 @@ exports.parse = function(sdp) { }; -},{"sdp-transform":68}],67:[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"; - } - }); -}); - -},{}],68:[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":69,"./writer":70}],69:[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":67}],70:[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":67}],71:[function(require,module,exports){ +},{"sdp-transform":74}],67:[function(require,module,exports){ /* Copyright @ 2015 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -22130,7 +21731,7 @@ Simulcast.prototype.mungeLocalDescription = function (desc) { module.exports = Simulcast; -},{"./transform-utils":72,"sdp-transform":74}],72:[function(require,module,exports){ +},{"./transform-utils":68,"sdp-transform":70}],68:[function(require,module,exports){ /* Copyright @ 2015 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -22196,30 +21797,834 @@ exports.parseSsrcs = function (mLine) { }; -},{}],73:[function(require,module,exports){ -arguments[4][67][0].apply(exports,arguments) -},{"dup":67}],74:[function(require,module,exports){ -arguments[4][68][0].apply(exports,arguments) -},{"./parser":75,"./writer":76,"dup":68}],75:[function(require,module,exports){ -arguments[4][69][0].apply(exports,arguments) -},{"./grammar":73,"dup":69}],76:[function(require,module,exports){ +},{}],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\-]*)\/(\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"; + } + }); +}); + +},{}],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(';').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){ +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"; + } + }); +}); + +},{}],74:[function(require,module,exports){ arguments[4][70][0].apply(exports,arguments) -},{"./grammar":73,"dup":70}],77:[function(require,module,exports){ -arguments[4][67][0].apply(exports,arguments) -},{"dup":67}],78:[function(require,module,exports){ -arguments[4][68][0].apply(exports,arguments) -},{"./parser":79,"./writer":80,"dup":68}],79:[function(require,module,exports){ -arguments[4][69][0].apply(exports,arguments) -},{"./grammar":77,"dup":69}],80:[function(require,module,exports){ -arguments[4][70][0].apply(exports,arguments) -},{"./grammar":77,"dup":70}],81:[function(require,module,exports){ +},{"./parser":75,"./writer":76,"dup":70}],75:[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":73}],76:[function(require,module,exports){ +arguments[4][72][0].apply(exports,arguments) +},{"./grammar":73,"dup":72}],77:[function(require,module,exports){ var MediaStreamType = { VIDEO_TYPE: "Video", AUDIO_TYPE: "Audio" }; module.exports = MediaStreamType; -},{}],82:[function(require,module,exports){ +},{}],78:[function(require,module,exports){ var RTCEvents = { RTC_READY: "rtc.ready", DATA_CHANNEL_OPEN: "rtc.data_channel_open", @@ -22230,7 +22635,7 @@ var RTCEvents = { }; module.exports = RTCEvents; -},{}],83:[function(require,module,exports){ +},{}],79:[function(require,module,exports){ var Resolutions = { "1080": { width: 1920, @@ -22284,7 +22689,7 @@ var Resolutions = { } }; module.exports = Resolutions; -},{}],84:[function(require,module,exports){ +},{}],80:[function(require,module,exports){ var StreamEventTypes = { EVENT_TYPE_LOCAL_CREATED: "stream.local_created", @@ -22299,7 +22704,7 @@ var StreamEventTypes = { }; module.exports = StreamEventTypes; -},{}],85:[function(require,module,exports){ +},{}],81:[function(require,module,exports){ var AuthenticationEvents = { /** * Event callback arguments: @@ -22313,7 +22718,7 @@ var AuthenticationEvents = { }; module.exports = AuthenticationEvents; -},{}],86:[function(require,module,exports){ +},{}],82:[function(require,module,exports){ var DesktopSharingEventTypes = { INIT: "ds.init", @@ -22329,7 +22734,7 @@ var DesktopSharingEventTypes = { module.exports = DesktopSharingEventTypes; -},{}],87:[function(require,module,exports){ +},{}],83:[function(require,module,exports){ module.exports = { /** * An event carrying connection statistics. @@ -22345,12 +22750,12 @@ module.exports = { STOP: "statistics.stop" }; -},{}],88:[function(require,module,exports){ +},{}],84:[function(require,module,exports){ var Constants = { LOCAL_JID: 'local' }; module.exports = Constants; -},{}],89:[function(require,module,exports){ +},{}],85:[function(require,module,exports){ var XMPPEvents = { // Designates an event indicating that the connection to the XMPP server // failed. diff --git a/modules/RTC/JitsiRemoteTrack.js b/modules/RTC/JitsiRemoteTrack.js index aa7db5ee9..a196e02ef 100644 --- a/modules/RTC/JitsiRemoteTrack.js +++ b/modules/RTC/JitsiRemoteTrack.js @@ -55,7 +55,7 @@ JitsiRemoteTrack.prototype.isMuted = function () { * Returns the participant id which owns the track. * @returns {string} the id of the participants. */ -JitsiRemoteTrack.prototype.getParitcipantId = function() { +JitsiRemoteTrack.prototype.getParticipantId = function() { return Strophe.getResourceFromJid(this.peerjid); }; diff --git a/modules/xmpp/SDPUtil.js b/modules/xmpp/SDPUtil.js index 92d2aaf79..830c96622 100644 --- a/modules/xmpp/SDPUtil.js +++ b/modules/xmpp/SDPUtil.js @@ -1,4 +1,3 @@ - var logger = require("jitsi-meet-logger").getLogger(__filename); var RTCBrowserType = require("../RTC/RTCBrowserType"); @@ -361,4 +360,5 @@ SDPUtil = { return line + '\r\n'; } }; + module.exports = SDPUtil; From 45f7ce99960aec259c02df9d94aa1eddc815f20e Mon Sep 17 00:00:00 2001 From: isymchych Date: Fri, 4 Dec 2015 14:34:24 +0200 Subject: [PATCH 3/8] adjusted JitsiTrack API doc --- doc/API.md | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/doc/API.md b/doc/API.md index 9bddf1e52..c3a236df3 100644 --- a/doc/API.md +++ b/doc/API.md @@ -213,35 +213,42 @@ The object represents a conference. We have the following methods to control the 17. addTrack(track) - Adds JitsiLocalTrack object to the conference. - track - the JitsiLocalTrack -17. removeTrack(track) - Removes JitsiLocalTrack object to the conference. +18. removeTrack(track) - Removes JitsiLocalTrack object to the conference. - track - the JitsiLocalTrack + + JitsiTrack ====== The object represents single track - video or audio. They can be remote tracks ( from the other participants in the call) or local tracks (from the devices of the local participant). We have the following methods for controling the tracks: -1.getType() - returns string with the type of the track( "video" for the video tracks and "audio" for the audio tracks) +1. getType() - returns string with the type of the track( "video" for the video tracks and "audio" for the audio tracks) -2.mute() - mutes the track. +2. mute() - mutes the track. -Note: This method is implemented only for the local tracks. + Note: This method is implemented only for the local tracks. -3.unmute() - unmutes the track. +3. unmute() - unmutes the track. -Note: This method is implemented only for the local tracks. + Note: This method is implemented only for the local tracks. +4. isMuted() - check if track is muted -4. attach(container) - attaches the track to the given container. +5. attach(container) - attaches the track to the given container. -5. detach(container) - removes the track from the container. +6. detach(container) - removes the track from the container. -6. stop() - stop sending the track to the other participants in the conference. +7. stop() - stop sending the track to the other participants in the conference. -Note: This method is implemented only for the local tracks. + Note: This method is implemented only for the local tracks. -7. getId() - returns unique string for the track. +8. getId() - returns unique string for the track. + +9. getParticipantId() - returns id(string) of the track owner + + Note: This method is implemented only for the remote tracks. Getting Started From b7ac3e9a06a1b08c105b44b8dceccdd9eff99b11 Mon Sep 17 00:00:00 2001 From: isymchych Date: Fri, 4 Dec 2015 15:04:36 +0200 Subject: [PATCH 4/8] add jsdocs, update API description --- JitsiConference.js | 9 +++++++++ JitsiConferenceEvents.js | 3 +++ doc/API.md | 3 +++ lib-jitsi-meet.js | 25 ++++++++++++++++++++----- 4 files changed, 35 insertions(+), 5 deletions(-) diff --git a/JitsiConference.js b/JitsiConference.js index 89dc34965..6955ecac2 100644 --- a/JitsiConference.js +++ b/JitsiConference.js @@ -288,6 +288,15 @@ JitsiConference.prototype.updateDTMFSupport = function () { } }; +/** + * Allows to check if there is at least one user in the conference + * that supports DTMF. + * @returns {boolean} true if somebody supports DTMF, false otherwise + */ +JitsiConference.prototype.isDTMFSupported = function () { + return this.somebodySupportsDTMF; +}; + /** * Returns the local user's ID * @return {string} local user's ID diff --git a/JitsiConferenceEvents.js b/JitsiConferenceEvents.js index 51d07dff5..59f1f6797 100644 --- a/JitsiConferenceEvents.js +++ b/JitsiConferenceEvents.js @@ -76,6 +76,9 @@ var JitsiConferenceEvents = { * Indicates that conference has been left. */ CONFERENCE_LEFT: "conference.left", + /** + * Indicates that DTMF support changed. + */ DTMF_SUPPORT_CHANGED: "conference.dtmf_support_changed" }; diff --git a/doc/API.md b/doc/API.md index c3a236df3..1aa2d0eef 100644 --- a/doc/API.md +++ b/doc/API.md @@ -77,6 +77,7 @@ JitsiMeetJS.setLogLevel(JitsiMeetJS.logLevels.ERROR); - IN_LAST_N_CHANGED - passes boolean property that shows whether the local user is included in last n set of any other user or not. (parameters - boolean) - CONFERENCE_JOINED - notifies the local user that he joined the conference successfully. (no parameters) - CONFERENCE_LEFT - notifies the local user that he left the conference successfully. (no parameters) + - DTMF_SUPPORT_CHANGED - notifies if at least one user supports DTMF. (parameters - supports(boolean)) 2. connection - CONNECTION_FAILED - indicates that the server connection failed. @@ -213,9 +214,11 @@ The object represents a conference. We have the following methods to control the 17. addTrack(track) - Adds JitsiLocalTrack object to the conference. - track - the JitsiLocalTrack + 18. removeTrack(track) - Removes JitsiLocalTrack object to the conference. - track - the JitsiLocalTrack +19. isDTMFSupported() - Check if at least one user supports DTMF. JitsiTrack ====== diff --git a/lib-jitsi-meet.js b/lib-jitsi-meet.js index 52598cc14..7bb96f869 100644 --- a/lib-jitsi-meet.js +++ b/lib-jitsi-meet.js @@ -241,7 +241,7 @@ JitsiConference.prototype.onMemberJoined = function (jid, email, nick) { var participant = new JitsiParticipant(id, this, nick); this.eventEmitter.emit(JitsiConferenceEvents.USER_JOINED, id); this.participants[id] = participant; - this.xmpp.getConnection().disco.info( + this.connection.xmpp.connection.disco.info( jid, "" /* node */, function(iq) { participant._supportsDTMF = $(iq).find('>query>feature[var="urn:xmpp:jingle:dtmf:0"]').length > 0; this.updateDTMFSupport(); @@ -290,6 +290,15 @@ JitsiConference.prototype.updateDTMFSupport = function () { } }; +/** + * Allows to check if there is at least one user in the conference + * that supports DTMF. + * @returns {boolean} true if somebody supports DTMF, false otherwise + */ +JitsiConference.prototype.isDTMFSupported = function () { + return this.somebodySupportsDTMF; +}; + /** * Returns the local user's ID * @return {string} local user's ID @@ -300,7 +309,7 @@ JitsiConference.prototype.myUserId = function () { JitsiConference.prototype.sendTones = function (tones, duration, pause) { if (!this.dtmfManager) { - var connection = this.xmpp.getConnection().jingle.activecall.peerconnection; + var connection = this.connection.xmpp.connection.jingle.activecall.peerconnection; if (!connection) { logger.warn("cannot sendTones: no conneciton"); return; @@ -527,6 +536,9 @@ var JitsiConferenceEvents = { * Indicates that conference has been left. */ CONFERENCE_LEFT: "conference.left", + /** + * Indicates that DTMF support changed. + */ DTMF_SUPPORT_CHANGED: "conference.dtmf_support_changed" }; @@ -9012,9 +9024,10 @@ SDPDiffer.prototype.toJingle = function(modify) { module.exports = SDPDiffer; },{"./SDPUtil":31}],31:[function(require,module,exports){ (function (__filename){ - var logger = require("jitsi-meet-logger").getLogger(__filename); -SDPUtil = { +var RTCBrowserType = require('../RTC/RTCBrowserType'); + +var SDPUtil = { filter_special_chars: function (text) { return text.replace(/[\\\/\{,\}\+]/g, ""); }, @@ -9371,9 +9384,11 @@ SDPUtil = { return line + '\r\n'; } }; + module.exports = SDPUtil; + }).call(this,"/modules/xmpp/SDPUtil.js") -},{"jitsi-meet-logger":46}],32:[function(require,module,exports){ +},{"../RTC/RTCBrowserType":16,"jitsi-meet-logger":46}],32:[function(require,module,exports){ (function (__filename){ /* global $ */ var RTC = require('../RTC/RTC'); From 9a00401b92b023de15774544eeb067a5ce13df3b Mon Sep 17 00:00:00 2001 From: isymchych Date: Mon, 7 Dec 2015 12:22:11 +0200 Subject: [PATCH 5/8] fix issues after rebase --- JitsiConference.js | 80 +++--- JitsiTrackErrors.js | 3 +- lib-jitsi-meet.js | 688 +++++++++++++++++++++++--------------------- 3 files changed, 409 insertions(+), 362 deletions(-) diff --git a/JitsiConference.js b/JitsiConference.js index 6955ecac2..01b4b7ef8 100644 --- a/JitsiConference.js +++ b/JitsiConference.js @@ -253,6 +253,16 @@ JitsiConference.prototype.onMemberLeft = function (jid) { this.eventEmitter.emit(JitsiConferenceEvents.USER_LEFT, id); }; +JitsiConference.prototype.onDisplayNameChanged = function (jid, displayName) { + var id = Strophe.getResourceFromJid(jid); + var participant = this.getParticipantById(id); + if (!participant) { + return; + } + participant._displayName = displayName; + this.eventEmitter.emit(JitsiConferenceEvents.DISPLAY_NAME_CHANGED, id, displayName); +}; + JitsiConference.prototype.onTrackAdded = function (track) { var id = track.getParticipantId(); @@ -338,21 +348,7 @@ function setupListeners(conference) { }); conference.room.addListener(XMPPEvents.REMOTE_STREAM_RECEIVED, conference.rtc.createRemoteStream.bind(conference.rtc)); - //FIXME: Maybe remove event should not be associated with the conference. - conference.rtc.addListener( - StreamEventTypes.EVENT_TYPE_REMOTE_CREATED, conference.onTrackAdded.bind(conference) - ); - //FIXME: Maybe remove event should not be associated with the conference. - conference.rtc.addListener( - StreamEventTypes.EVENT_TYPE_REMOTE_ENDED, conference.onTrackRemoved.bind(conference) - ); - conference.rtc.addListener(StreamEventTypes.EVENT_TYPE_LOCAL_ENDED, function (stream) { - conference.removeTrack(stream); - conference.eventEmitter.emit(JitsiConferenceEvents.TRACK_REMOVED, stream); - }); - conference.rtc.addListener(StreamEventTypes.TRACK_MUTE_CHANGED, function (track) { - conference.eventEmitter.emit(JitsiConferenceEvents.TRACK_MUTE_CHANGED, track); - }); + conference.room.addListener(XMPPEvents.MUC_JOINED, function () { conference.eventEmitter.emit(JitsiConferenceEvents.CONFERENCE_JOINED); }); @@ -360,6 +356,41 @@ function setupListeners(conference) { // conference.room.addListener(XMPPEvents.MUC_JOINED, function () { // conference.eventEmitter.emit(JitsiConferenceEvents.CONFERENCE_LEFT); // }); + + conference.room.addListener(XMPPEvents.MUC_MEMBER_JOINED, conference.onMemberJoined.bind(conference)); + conference.room.addListener(XMPPEvents.MUC_MEMBER_LEFT, conference.onMemberLeft.bind(conference)); + + conference.room.addListener(XMPPEvents.DISPLAY_NAME_CHANGED, conference.onDisplayNameChanged.bind(conference)); + + conference.room.addListener(XMPPEvents.CONNECTION_INTERRUPTED, function () { + conference.eventEmitter.emit(JitsiConferenceEvents.CONNECTION_INTERRUPTED); + }); + + conference.room.addListener(XMPPEvents.CONNECTION_RESTORED, function () { + conference.eventEmitter.emit(JitsiConferenceEvents.CONNECTION_RESTORED); + }); + conference.room.addListener(XMPPEvents.CONFERENCE_SETUP_FAILED, function () { + conference.eventEmitter.emit(JitsiConferenceEvents.SETUP_FAILED); + }); + + conference.rtc.addListener( + StreamEventTypes.EVENT_TYPE_REMOTE_CREATED, conference.onTrackAdded.bind(conference) + ); + +//FIXME: Maybe remove event should not be associated with the conference. + conference.rtc.addListener( + StreamEventTypes.EVENT_TYPE_REMOTE_ENDED, conference.onTrackRemoved.bind(conference) + ); +//FIXME: Maybe remove event should not be associated with the conference. + conference.rtc.addListener(StreamEventTypes.EVENT_TYPE_LOCAL_ENDED, function (stream) { + conference.removeTrack(stream); + conference.eventEmitter.emit(JitsiConferenceEvents.TRACK_REMOVED, stream); + }); + + conference.rtc.addListener(StreamEventTypes.TRACK_MUTE_CHANGED, function (track) { + conference.eventEmitter.emit(JitsiConferenceEvents.TRACK_MUTE_CHANGED, track); + }); + conference.rtc.addListener(RTCEvents.DOMINANTSPEAKER_CHANGED, function (id) { if(conference.lastActiveSpeaker !== id && conference.room) { conference.lastActiveSpeaker = id; @@ -377,25 +408,6 @@ function setupListeners(conference) { lastNEndpoints, endpointsEnteringLastN); }); - conference.room.addListener(XMPPEvents.MUC_MEMBER_JOINED, conference.onMemberJoined.bind(conference)); - conference.room.addListener(XMPPEvents.MUC_MEMBER_LEFT, conference.onMemberLeft.bind(conference)); - - conference.room.addListener(XMPPEvents.DISPLAY_NAME_CHANGED, function (from, displayName) { - conference.eventEmitter.emit(JitsiConferenceEvents.DISPLAY_NAME_CHANGED, - Strophe.getResourceFromJid(from), displayName); - }); - - conference.room.addListener(XMPPEvents.CONNECTION_INTERRUPTED, function () { - conference.eventEmitter.emit(JitsiConferenceEvents.CONNECTION_INTERRUPTED); - }); - - conference.room.addListener(XMPPEvents.CONNECTION_RESTORED, function () { - conference.eventEmitter.emit(JitsiConferenceEvents.CONNECTION_RESTORED); - }); - conference.room.addListener(XMPPEvents.CONFERENCE_SETUP_FAILED, function () { - conference.eventEmitter.emit(JitsiConferenceEvents.SETUP_FAILED); - }); - if(conference.statistics) { //FIXME: Maybe remove event should not be associated with the conference. conference.statistics.addAudioLevelListener(function (ssrc, level) { diff --git a/JitsiTrackErrors.js b/JitsiTrackErrors.js index 3a7cc3a66..9239bc6be 100644 --- a/JitsiTrackErrors.js +++ b/JitsiTrackErrors.js @@ -1,6 +1,7 @@ module.exports = { /** - * Returns JitsiTrackErrors based on the error object passed by GUM * @param error the error + * Returns JitsiTrackErrors based on the error object passed by GUM + * @param error the error * @param {Object} options the options object given to GUM. */ parseError: function (error, options) { diff --git a/lib-jitsi-meet.js b/lib-jitsi-meet.js index 7bb96f869..d224ff7b5 100644 --- a/lib-jitsi-meet.js +++ b/lib-jitsi-meet.js @@ -255,6 +255,16 @@ JitsiConference.prototype.onMemberLeft = function (jid) { this.eventEmitter.emit(JitsiConferenceEvents.USER_LEFT, id); }; +JitsiConference.prototype.onDisplayNameChanged = function (jid, displayName) { + var id = Strophe.getResourceFromJid(jid); + var participant = this.getParticipantById(id); + if (!participant) { + return; + } + participant._displayName = displayName; + this.eventEmitter.emit(JitsiConferenceEvents.DISPLAY_NAME_CHANGED, id, displayName); +}; + JitsiConference.prototype.onTrackAdded = function (track) { var id = track.getParticipantId(); @@ -340,19 +350,7 @@ function setupListeners(conference) { }); conference.room.addListener(XMPPEvents.REMOTE_STREAM_RECEIVED, conference.rtc.createRemoteStream.bind(conference.rtc)); - conference.rtc.addListener( - StreamEventTypes.EVENT_TYPE_REMOTE_CREATED, conference.onTrackAdded.bind(conference) - ); - conference.rtc.addListener( - StreamEventTypes.EVENT_TYPE_REMOTE_ENDED, conference.onTrackRemoved.bind(conference) - ); - conference.rtc.addListener(StreamEventTypes.EVENT_TYPE_LOCAL_ENDED, function (stream) { - conference.removeTrack(stream); - conference.eventEmitter.emit(JitsiConferenceEvents.TRACK_REMOVED, stream); - }); - conference.rtc.addListener(StreamEventTypes.TRACK_MUTE_CHANGED, function (track) { - conference.eventEmitter.emit(JitsiConferenceEvents.TRACK_MUTE_CHANGED, track); - }); + conference.room.addListener(XMPPEvents.MUC_JOINED, function () { conference.eventEmitter.emit(JitsiConferenceEvents.CONFERENCE_JOINED); }); @@ -360,6 +358,41 @@ function setupListeners(conference) { // conference.room.addListener(XMPPEvents.MUC_JOINED, function () { // conference.eventEmitter.emit(JitsiConferenceEvents.CONFERENCE_LEFT); // }); + + conference.room.addListener(XMPPEvents.MUC_MEMBER_JOINED, conference.onMemberJoined.bind(conference)); + conference.room.addListener(XMPPEvents.MUC_MEMBER_LEFT, conference.onMemberLeft.bind(conference)); + + conference.room.addListener(XMPPEvents.DISPLAY_NAME_CHANGED, conference.onDisplayNameChanged.bind(conference)); + + conference.room.addListener(XMPPEvents.CONNECTION_INTERRUPTED, function () { + conference.eventEmitter.emit(JitsiConferenceEvents.CONNECTION_INTERRUPTED); + }); + + conference.room.addListener(XMPPEvents.CONNECTION_RESTORED, function () { + conference.eventEmitter.emit(JitsiConferenceEvents.CONNECTION_RESTORED); + }); + conference.room.addListener(XMPPEvents.CONFERENCE_SETUP_FAILED, function () { + conference.eventEmitter.emit(JitsiConferenceEvents.SETUP_FAILED); + }); + + conference.rtc.addListener( + StreamEventTypes.EVENT_TYPE_REMOTE_CREATED, conference.onTrackAdded.bind(conference) + ); + +//FIXME: Maybe remove event should not be associated with the conference. + conference.rtc.addListener( + StreamEventTypes.EVENT_TYPE_REMOTE_ENDED, conference.onTrackRemoved.bind(conference) + ); +//FIXME: Maybe remove event should not be associated with the conference. + conference.rtc.addListener(StreamEventTypes.EVENT_TYPE_LOCAL_ENDED, function (stream) { + conference.removeTrack(stream); + conference.eventEmitter.emit(JitsiConferenceEvents.TRACK_REMOVED, stream); + }); + + conference.rtc.addListener(StreamEventTypes.TRACK_MUTE_CHANGED, function (track) { + conference.eventEmitter.emit(JitsiConferenceEvents.TRACK_MUTE_CHANGED, track); + }); + conference.rtc.addListener(RTCEvents.DOMINANTSPEAKER_CHANGED, function (id) { if(conference.lastActiveSpeaker !== id && conference.room) { conference.lastActiveSpeaker = id; @@ -377,26 +410,8 @@ function setupListeners(conference) { lastNEndpoints, endpointsEnteringLastN); }); - conference.room.addListener(XMPPEvents.MUC_MEMBER_JOINED, conference.onMemberJoined.bind(conference)); - conference.room.addListener(XMPPEvents.MUC_MEMBER_LEFT, conference.onMemberLeft.bind(conference)); - - conference.room.addListener(XMPPEvents.DISPLAY_NAME_CHANGED, function (from, displayName) { - conference.eventEmitter.emit(JitsiConferenceEvents.DISPLAY_NAME_CHANGED, - Strophe.getResourceFromJid(from), displayName); - }); - - conference.room.addListener(XMPPEvents.CONNECTION_INTERRUPTED, function () { - conference.eventEmitter.emit(JitsiConferenceEvents.CONNECTION_INTERRUPTED); - }); - - conference.room.addListener(XMPPEvents.CONNECTION_RESTORED, function () { - conference.eventEmitter.emit(JitsiConferenceEvents.CONNECTION_RESTORED); - }); - conference.room.addListener(XMPPEvents.CONFERENCE_SETUP_FAILED, function () { - conference.eventEmitter.emit(JitsiConferenceEvents.SETUP_FAILED); - }); - if(conference.statistics) { + //FIXME: Maybe remove event should not be associated with the conference. conference.statistics.addAudioLevelListener(function (ssrc, level) { var userId = null; if (ssrc === Statistics.LOCAL_JID) { @@ -418,12 +433,10 @@ function setupListeners(conference) { function () { conference.statistics.dispose(); }); - RTC.addListener(RTCEvents.AVAILABLE_DEVICES_CHANGED, function (devices) { - conference.room.updateDeviceAvailability(devices); - }); - RTC.addListener(StreamEventTypes.TRACK_MUTE_CHANGED, function (track) { - conference.eventEmitter.emit(JitsiConferenceEvents.TRACK_MUTE_CHANGED, track); - }); + // FIXME: Maybe we should move this. + // RTC.addListener(RTCEvents.AVAILABLE_DEVICES_CHANGED, function (devices) { + // conference.room.updateDeviceAvailability(devices); + // }); } } @@ -431,7 +444,7 @@ function setupListeners(conference) { module.exports = JitsiConference; }).call(this,"/JitsiConference.js") -},{"./JitsiConferenceEvents":3,"./JitsiParticipant":9,"./modules/DTMF/JitsiDTMFManager":10,"./modules/RTC/RTC":15,"./modules/statistics/statistics":23,"./service/RTC/RTCEvents":78,"./service/RTC/StreamEventTypes":80,"./service/xmpp/XMPPEvents":85,"events":42,"jitsi-meet-logger":46}],2:[function(require,module,exports){ +},{"./JitsiConferenceEvents":3,"./JitsiParticipant":8,"./modules/DTMF/JitsiDTMFManager":11,"./modules/RTC/RTC":16,"./modules/statistics/statistics":24,"./service/RTC/RTCEvents":79,"./service/RTC/StreamEventTypes":81,"./service/xmpp/XMPPEvents":86,"events":43,"jitsi-meet-logger":47}],2:[function(require,module,exports){ /** * Enumeration with the errors for the conference. * @type {{string: string}} @@ -468,7 +481,7 @@ var JitsiConferenceEvents = { */ TRACK_ADDED: "conference.trackAdded", /** - * The media track was removed to the conference. + * The media track was removed from the conference. */ TRACK_REMOVED: "conference.trackRemoved", /** @@ -508,11 +521,11 @@ var JitsiConferenceEvents = { */ IN_LAST_N_CHANGED: "conference.lastNEndpointsChanged", /** - * A media track mute status was changed. + * A media track ( attached to the conference) mute status was changed. */ TRACK_MUTE_CHANGED: "conference.trackMuteChanged", /** - * Audio levels of a media track was changed. + * Audio levels of a media track ( attached to the conference) was changed. */ TRACK_AUDIO_LEVEL_CHANGED: "conference.audioLevelsChanged", /** @@ -645,7 +658,7 @@ JitsiConnection.prototype.removeEventListener = function (event, listener) { module.exports = JitsiConnection; -},{"./JitsiConference":1,"./modules/util/RandomUtil":24,"./modules/xmpp/xmpp":40}],5:[function(require,module,exports){ +},{"./JitsiConference":1,"./modules/util/RandomUtil":25,"./modules/xmpp/xmpp":41}],5:[function(require,module,exports){ /** * Enumeration with the errors for the connection. * @type {{string: string}} @@ -701,6 +714,8 @@ var JitsiConferenceEvents = require("./JitsiConferenceEvents"); var JitsiConnectionEvents = require("./JitsiConnectionEvents"); var JitsiConnectionErrors = require("./JitsiConnectionErrors"); var JitsiConferenceErrors = require("./JitsiConferenceErrors"); +var JitsiTrackEvents = require("./JitsiTrackEvents"); +var JitsiTrackErrors = require("./JitsiTrackErrors"); var Logger = require("jitsi-meet-logger"); var RTC = require("./modules/RTC/RTC"); @@ -712,15 +727,17 @@ var LibJitsiMeet = { JitsiConnection: JitsiConnection, events: { conference: JitsiConferenceEvents, - connection: JitsiConnectionEvents + connection: JitsiConnectionEvents, + track: JitsiTrackEvents }, errors: { conference: JitsiConferenceErrors, - connection: JitsiConnectionErrors + connection: JitsiConnectionErrors, + track: JitsiTrackErrors }, logLevels: Logger.levels, init: function (options) { - RTC.init(options || {}); + return RTC.init(options || {}); }, setLogLevel: function (level) { Logger.setLogLevel(level); @@ -756,33 +773,7 @@ window.Promise = window.Promise || require("es6-promise").Promise; module.exports = LibJitsiMeet; -},{"./JitsiConferenceErrors":2,"./JitsiConferenceEvents":3,"./JitsiConnection":4,"./JitsiConnectionErrors":5,"./JitsiConnectionEvents":6,"./modules/RTC/RTC":15,"es6-promise":44,"jitsi-meet-logger":46}],8:[function(require,module,exports){ -module.exports = { - /** - * Returns JitsiMeetJSError based on the error object passed by GUM - * @param error the error - * @param {Object} options the options object given to GUM. - */ - parseError: function (error, options) { - options = options || {}; - if (typeof error == "object" && error.constraintName && error.name - && (error.name == "ConstraintNotSatisfiedError" || - error.name == "OverconstrainedError") && - (error.constraintName == "minWidth" || - error.constraintName == "maxWidth" || - error.constraintName == "minHeight" || - error.constraintName == "maxHeight") && - options.devices.indexOf("video") !== -1) { - return this.GET_TRACKS_RESOLUTION; - } else { - return this.GET_TRACKS_GENERAL; - } - }, - GET_TRACKS_RESOLUTION: "gum.get_tracks_resolution", - GET_TRACKS_GENERAL: "gum.get_tracks_general" -}; - -},{}],9:[function(require,module,exports){ +},{"./JitsiConferenceErrors":2,"./JitsiConferenceEvents":3,"./JitsiConnection":4,"./JitsiConnectionErrors":5,"./JitsiConnectionEvents":6,"./JitsiTrackErrors":9,"./JitsiTrackEvents":10,"./modules/RTC/RTC":16,"es6-promise":45,"jitsi-meet-logger":47}],8:[function(require,module,exports){ /** * Represents a participant in (a member of) a conference. */ @@ -925,7 +916,51 @@ JitsiParticipant.prototype.supportsDTMF = function () { module.exports = JitsiParticipant; +},{}],9:[function(require,module,exports){ +module.exports = { + /** + * Returns JitsiTrackErrors based on the error object passed by GUM + * @param error the error + * @param {Object} options the options object given to GUM. + */ + parseError: function (error, options) { + options = options || {}; + if (typeof error == "object" && error.constraintName && error.name + && (error.name == "ConstraintNotSatisfiedError" || + error.name == "OverconstrainedError") && + (error.constraintName == "minWidth" || + error.constraintName == "maxWidth" || + error.constraintName == "minHeight" || + error.constraintName == "maxHeight") && + options.devices.indexOf("video") !== -1) { + return this.UNSUPPORTED_RESOLUTION; + } else { + return this.GENERAL; + } + }, + UNSUPPORTED_RESOLUTION: "gum.unsupported_resolution", + GENERAL: "gum.general" +}; + },{}],10:[function(require,module,exports){ +var JitsiTrackEvents = { + /** + * A media track mute status was changed. + */ + TRACK_MUTE_CHANGED: "track.trackMuteChanged", + /** + * Audio levels of a this track was changed. + */ + TRACK_AUDIO_LEVEL_CHANGED: "track.audioLevelsChanged", + /** + * The media track was removed to the conference. + */ + TRACK_STOPPED: "track.TRACK_STOPPED", +}; + +module.exports = JitsiTrackEvents; + +},{}],11:[function(require,module,exports){ (function (__filename){ var logger = require("jitsi-meet-logger").getLogger(__filename); @@ -944,7 +979,7 @@ JitsiDTMFManager.prototype.sendTones = function (tones, duration, pause) { }; }).call(this,"/modules/DTMF/JitsiDTMFManager.js") -},{"jitsi-meet-logger":46}],11:[function(require,module,exports){ +},{"jitsi-meet-logger":47}],12:[function(require,module,exports){ (function (__filename){ /* global config, APP, Strophe */ @@ -1160,30 +1195,31 @@ module.exports = DataChannels; }).call(this,"/modules/RTC/DataChannels.js") -},{"../../service/RTC/RTCEvents":78,"jitsi-meet-logger":46}],12:[function(require,module,exports){ +},{"../../service/RTC/RTCEvents":79,"jitsi-meet-logger":47}],13:[function(require,module,exports){ var JitsiTrack = require("./JitsiTrack"); var StreamEventTypes = require("../../service/RTC/StreamEventTypes"); var RTCBrowserType = require("./RTCBrowserType"); +var RTC = require("./RTCUtils"); /** * Represents a single media track (either audio or video). * @constructor */ -function JitsiLocalTrack(RTC, stream, eventEmitter, videoType, +function JitsiLocalTrack(stream, videoType, resolution) { - JitsiTrack.call(this, RTC, stream, - function () { - if(!self.dontFireRemoveEvent) - self.eventEmitter.emit( - StreamEventTypes.EVENT_TYPE_LOCAL_ENDED, self); - self.dontFireRemoveEvent = false; - }); - this.eventEmitter = eventEmitter; this.videoType = videoType; this.dontFireRemoveEvent = false; this.resolution = resolution; var self = this; + JitsiTrack.call(this, null, stream, + function () { + if(!self.dontFireRemoveEvent && self.rtc) + self.rtc.eventEmitter.emit( + StreamEventTypes.EVENT_TYPE_LOCAL_ENDED, self); + self.dontFireRemoveEvent = false; + }); + } JitsiLocalTrack.prototype = Object.create(JitsiTrack.prototype); @@ -1214,26 +1250,24 @@ JitsiLocalTrack.prototype._setMute = function (mute) { this.rtc.room.setAudioMute(mute); else this.rtc.room.setVideoMute(mute); - this.eventEmitter.emit(StreamEventTypes.TRACK_MUTE_CHANGED, this); + this.rtc.eventEmitter.emit(StreamEventTypes.TRACK_MUTE_CHANGED, this); } else { if (mute) { this.dontFireRemoveEvent = true; this.rtc.room.removeStream(this.stream); - this.rtc.stopMediaStream(this.stream); + RTC.stopMediaStream(this.stream); if(isAudio) this.rtc.room.setAudioMute(mute); else this.rtc.room.setVideoMute(mute); this.stream = null; - this.eventEmitter.emit(StreamEventTypes.TRACK_MUTE_CHANGED, this); + this.rtc.eventEmitter.emit(StreamEventTypes.TRACK_MUTE_CHANGED, this); //FIXME: Maybe here we should set the SRC for the containers to something } else { var self = this; - var RTC = require("./RTCUtils"); RTC.obtainAudioAndVideoPermissions({ devices: (isAudio ? ["audio"] : ["video"]), - resolution: self.resolution, - dontCreateJitsiTrack: true}) + resolution: self.resolution}) .then(function (streams) { var stream = null; for(var i = 0; i < streams.length; i++) { @@ -1259,7 +1293,7 @@ JitsiLocalTrack.prototype._setMute = function (mute) { self.rtc.room.setAudioMute(mute); else self.rtc.room.setVideoMute(mute); - self.eventEmitter.emit(StreamEventTypes.TRACK_MUTE_CHANGED, self); + self.rtc.eventEmitter.emit(StreamEventTypes.TRACK_MUTE_CHANGED, self); }); }); } @@ -1314,7 +1348,7 @@ JitsiLocalTrack.prototype._setRTC = function (rtc) { module.exports = JitsiLocalTrack; -},{"../../service/RTC/StreamEventTypes":80,"./JitsiTrack":14,"./RTCBrowserType":16,"./RTCUtils":17}],13:[function(require,module,exports){ +},{"../../service/RTC/StreamEventTypes":81,"./JitsiTrack":15,"./RTCBrowserType":17,"./RTCUtils":18}],14:[function(require,module,exports){ var JitsiTrack = require("./JitsiTrack"); var StreamEventTypes = require("../../service/RTC/StreamEventTypes"); @@ -1382,7 +1416,7 @@ delete JitsiRemoteTrack.prototype.start; module.exports = JitsiRemoteTrack; -},{"../../service/RTC/StreamEventTypes":80,"./JitsiTrack":14}],14:[function(require,module,exports){ +},{"../../service/RTC/StreamEventTypes":81,"./JitsiTrack":15}],15:[function(require,module,exports){ var RTCBrowserType = require("./RTCBrowserType"); /** @@ -1408,18 +1442,18 @@ function implementOnEndedHandling(jitsiTrack) { * @param handler the handler */ function addMediaStreamInactiveHandler(mediaStream, handler) { - if (mediaStream.addEventListener) { - // chrome - if(typeof mediaStream.active !== "undefined") - mediaStream.oninactive = handler; - else - mediaStream.onended = handler; - } else { + if(RTCBrowserType.isTemasysPluginUsed()) { // themasys mediaStream.attachEvent('ended', function () { handler(mediaStream); }); } + else { + if(typeof mediaStream.active !== "undefined") + mediaStream.oninactive = handler; + else + mediaStream.onended = handler; + } } /** @@ -1586,7 +1620,7 @@ JitsiTrack.prototype.isActive = function () { module.exports = JitsiTrack; -},{"./RTCBrowserType":16,"./RTCUtils":17}],15:[function(require,module,exports){ +},{"./RTCBrowserType":17,"./RTCUtils":18}],16:[function(require,module,exports){ /* global APP */ var EventEmitter = require("events"); var RTCBrowserType = require("./RTCBrowserType"); @@ -1601,6 +1635,22 @@ var MediaStreamType = require("../../service/RTC/MediaStreamTypes"); var StreamEventTypes = require("../../service/RTC/StreamEventTypes.js"); var RTCEvents = require("../../service/RTC/RTCEvents.js"); +function createLocalTracks(streams) { + var newStreams = [] + for (var i = 0; i < streams.length; i++) { + var localStream = new JitsiLocalTrack(streams[i].stream, + streams[i].videoType, streams[i].resolution); + newStreams.push(localStream); + if (streams[i].isMuted === true) + localStream.setMute(true); + //FIXME: + // var eventType = StreamEventTypes.EVENT_TYPE_LOCAL_CREATED; + // + // eventEmitter.emit(eventType, localStream); + } + return newStreams; +} + function RTC(room, options) { this.room = room; this.localStreams = []; @@ -1633,7 +1683,7 @@ function RTC(room, options) { * @returns {*} Promise object that will receive the new JitsiTracks */ RTC.obtainAudioAndVideoPermissions = function (options) { - return RTCUtils.obtainAudioAndVideoPermissions(options); + return RTCUtils.obtainAudioAndVideoPermissions(options).then(createLocalTracks); } RTC.prototype.onIncommingCall = function(event) { @@ -1830,7 +1880,7 @@ RTC.prototype.setVideoMute = function (mute, callback, options) { module.exports = RTC; -},{"../../service/RTC/MediaStreamTypes":77,"../../service/RTC/RTCEvents.js":78,"../../service/RTC/StreamEventTypes.js":80,"../../service/desktopsharing/DesktopSharingEventTypes":82,"./DataChannels":11,"./JitsiLocalTrack.js":12,"./JitsiRemoteTrack.js":13,"./JitsiTrack":14,"./RTCBrowserType":16,"./RTCUtils.js":17,"events":42}],16:[function(require,module,exports){ +},{"../../service/RTC/MediaStreamTypes":78,"../../service/RTC/RTCEvents.js":79,"../../service/RTC/StreamEventTypes.js":81,"../../service/desktopsharing/DesktopSharingEventTypes":83,"./DataChannels":12,"./JitsiLocalTrack.js":13,"./JitsiRemoteTrack.js":14,"./JitsiTrack":15,"./RTCBrowserType":17,"./RTCUtils.js":18,"events":43}],17:[function(require,module,exports){ var currentBrowser; @@ -2002,7 +2052,7 @@ browserVersion = detectBrowser(); isAndroid = navigator.userAgent.indexOf('Android') != -1; module.exports = RTCBrowserType; -},{}],17:[function(require,module,exports){ +},{}],18:[function(require,module,exports){ (function (__filename){ /* global config, require, attachMediaStream, getUserMedia */ @@ -2013,10 +2063,8 @@ var RTCEvents = require("../../service/RTC/RTCEvents"); var AdapterJS = require("./adapter.screenshare"); var SDPUtil = require("../xmpp/SDPUtil"); var EventEmitter = require("events"); -var JitsiLocalTrack = require("./JitsiLocalTrack"); -var StreamEventTypes = require("../../service/RTC/StreamEventTypes.js"); var screenObtainer = require("./ScreenObtainer"); -var JitsiMeetJSError = require("../../JitsiMeetJSErrors"); +var JitsiTrackErrors = require("../../JitsiTrackErrors"); var eventEmitter = new EventEmitter(); @@ -2343,27 +2391,11 @@ function obtainDevices(options) { function (error) { logger.error( "failed to obtain " + device + " stream - stop", error); - options.errorCallback(JitsiMeetJSError.parseError(error)); + options.errorCallback(JitsiTrackErrors.parseError(error)); }); } -function createLocalTracks(streams) { - var newStreams = [] - for (var i = 0; i < streams.length; i++) { - var localStream = new JitsiLocalTrack(null, streams[i].stream, - eventEmitter, streams[i].videoType, streams[i].resolution); - newStreams.push(localStream); - if (streams[i].isMuted === true) - localStream.setMute(true); - - var eventType = StreamEventTypes.EVENT_TYPE_LOCAL_CREATED; - - eventEmitter.emit(eventType, localStream); - } - return newStreams; -} - /** * Handles the newly created Media Streams. * @param streams the new Media Streams @@ -2386,7 +2418,7 @@ function handleLocalStream(streams, resolution) { } var videoTracks = audioVideo.getVideoTracks(); - if(audioTracks.length) { + if(videoTracks.length) { videoStream = new webkitMediaStream(); for (i = 0; i < videoTracks.length; i++) { videoStream.addTrack(videoTracks[i]); @@ -2420,10 +2452,17 @@ function handleLocalStream(streams, resolution) { //Options parameter is to pass config options. Currently uses only "useIPv6". var RTCUtils = { init: function (options) { - var self = this; - if (RTCBrowserType.isFirefox()) { - var FFversion = RTCBrowserType.getFirefoxVersion(); - if (FFversion >= 40) { + return new Promise(function(resolve, reject) { + if (RTCBrowserType.isFirefox()) { + var FFversion = RTCBrowserType.getFirefoxVersion(); + if (FFversion < 40) { + logger.error( + "Firefox version too old: " + FFversion + + ". Required >= 40."); + reject(new Error("Firefox version too old: " + FFversion + + ". Required >= 40.")); + return; + } this.peerconnection = mozRTCPeerConnection; this.getUserMedia = wrapGetUserMedia(navigator.mozGetUserMedia.bind(navigator)); this.enumerateDevices = wrapEnumerateDevices( @@ -2465,128 +2504,124 @@ var RTCUtils = { }; RTCSessionDescription = mozRTCSessionDescription; RTCIceCandidate = mozRTCIceCandidate; + } else if (RTCBrowserType.isChrome() || RTCBrowserType.isOpera()) { + this.peerconnection = webkitRTCPeerConnection; + var getUserMedia = navigator.webkitGetUserMedia.bind(navigator); + if (navigator.mediaDevices) { + this.getUserMedia = wrapGetUserMedia(getUserMedia); + this.enumerateDevices = wrapEnumerateDevices( + navigator.mediaDevices.enumerateDevices.bind(navigator.mediaDevices) + ); + } else { + this.getUserMedia = getUserMedia; + this.enumerateDevices = enumerateDevicesThroughMediaStreamTrack; + } + this.attachMediaStream = function (element, stream) { + element.attr('src', webkitURL.createObjectURL(stream)); + }; + this.getStreamID = function (stream) { + // streams from FF endpoints have the characters '{' and '}' + // that make jQuery choke. + return SDPUtil.filter_special_chars(stream.id); + }; + this.getVideoSrc = function (element) { + if (!element) + return null; + return element.getAttribute("src"); + }; + this.setVideoSrc = function (element, src) { + if (element) + element.setAttribute("src", src); + }; + // DTLS should now be enabled by default but.. + this.pc_constraints = {'optional': [ + {'DtlsSrtpKeyAgreement': 'true'} + ]}; + if (options.useIPv6) { + // https://code.google.com/p/webrtc/issues/detail?id=2828 + this.pc_constraints.optional.push({googIPv6: true}); + } + if (RTCBrowserType.isAndroid()) { + this.pc_constraints = {}; // disable DTLS on Android + } + if (!webkitMediaStream.prototype.getVideoTracks) { + webkitMediaStream.prototype.getVideoTracks = function () { + return this.videoTracks; + }; + } + if (!webkitMediaStream.prototype.getAudioTracks) { + webkitMediaStream.prototype.getAudioTracks = function () { + return this.audioTracks; + }; + } + } + // Detect IE/Safari + else if (RTCBrowserType.isTemasysPluginUsed()) { + + //AdapterJS.WebRTCPlugin.setLogLevel( + // AdapterJS.WebRTCPlugin.PLUGIN_LOG_LEVELS.VERBOSE); + + AdapterJS.webRTCReady(function (isPlugin) { + + self.peerconnection = RTCPeerConnection; + self.getUserMedia = window.getUserMedia; + self.enumerateDevices = enumerateDevicesThroughMediaStreamTrack; + self.attachMediaStream = function (elSel, stream) { + + if (stream.id === "dummyAudio" || stream.id === "dummyVideo") { + return; + } + + attachMediaStream(elSel[0], stream); + }; + self.getStreamID = function (stream) { + var id = SDPUtil.filter_special_chars(stream.label); + return id; + }; + self.getVideoSrc = function (element) { + if (!element) { + logger.warn("Attempt to get video SRC of null element"); + return null; + } + var children = element.children; + for (var i = 0; i !== children.length; ++i) { + if (children[i].name === 'streamId') { + return children[i].value; + } + } + //logger.info(element.id + " SRC: " + src); + return null; + }; + self.setVideoSrc = function (element, src) { + //logger.info("Set video src: ", element, src); + if (!src) { + logger.warn("Not attaching video stream, 'src' is null"); + return; + } + AdapterJS.WebRTCPlugin.WaitForPluginReady(); + var stream = AdapterJS.WebRTCPlugin.plugin + .getStreamWithId(AdapterJS.WebRTCPlugin.pageId, src); + attachMediaStream(element, stream); + }; + + onReady(options, self.getUserMediaWithConstraints); + resolve(); + }); } else { - logger.error( - "Firefox version too old: " + FFversion + ". Required >= 40."); - window.location.href = 'unsupported_browser.html'; + try { + logger.error('Browser does not appear to be WebRTC-capable'); + } catch (e) { + } + reject('Browser does not appear to be WebRTC-capable'); return; } - } else if (RTCBrowserType.isChrome() || RTCBrowserType.isOpera()) { - this.peerconnection = webkitRTCPeerConnection; - var getUserMedia = navigator.webkitGetUserMedia.bind(navigator); - if (navigator.mediaDevices) { - this.getUserMedia = wrapGetUserMedia(getUserMedia); - this.enumerateDevices = wrapEnumerateDevices( - navigator.mediaDevices.enumerateDevices.bind(navigator.mediaDevices) - ); - } else { - this.getUserMedia = getUserMedia; - this.enumerateDevices = enumerateDevicesThroughMediaStreamTrack; - } - this.attachMediaStream = function (element, stream) { - element.attr('src', webkitURL.createObjectURL(stream)); - }; - this.getStreamID = function (stream) { - // streams from FF endpoints have the characters '{' and '}' - // that make jQuery choke. - return SDPUtil.filter_special_chars(stream.id); - }; - this.getVideoSrc = function (element) { - if (!element) - return null; - return element.getAttribute("src"); - }; - this.setVideoSrc = function (element, src) { - if (element) - element.setAttribute("src", src); - }; - // DTLS should now be enabled by default but.. - this.pc_constraints = {'optional': [ - {'DtlsSrtpKeyAgreement': 'true'} - ]}; - if (options.useIPv6) { - // https://code.google.com/p/webrtc/issues/detail?id=2828 - this.pc_constraints.optional.push({googIPv6: true}); - } - if (RTCBrowserType.isAndroid()) { - this.pc_constraints = {}; // disable DTLS on Android - } - if (!webkitMediaStream.prototype.getVideoTracks) { - webkitMediaStream.prototype.getVideoTracks = function () { - return this.videoTracks; - }; - } - if (!webkitMediaStream.prototype.getAudioTracks) { - webkitMediaStream.prototype.getAudioTracks = function () { - return this.audioTracks; - }; - } - } - // Detect IE/Safari - else if (RTCBrowserType.isTemasysPluginUsed()) { - - //AdapterJS.WebRTCPlugin.setLogLevel( - // AdapterJS.WebRTCPlugin.PLUGIN_LOG_LEVELS.VERBOSE); - - AdapterJS.webRTCReady(function (isPlugin) { - - self.peerconnection = RTCPeerConnection; - self.getUserMedia = window.getUserMedia; - self.enumerateDevices = enumerateDevicesThroughMediaStreamTrack; - self.attachMediaStream = function (elSel, stream) { - - if (stream.id === "dummyAudio" || stream.id === "dummyVideo") { - return; - } - - attachMediaStream(elSel[0], stream); - }; - self.getStreamID = function (stream) { - var id = SDPUtil.filter_special_chars(stream.label); - return id; - }; - self.getVideoSrc = function (element) { - if (!element) { - logger.warn("Attempt to get video SRC of null element"); - return null; - } - var children = element.children; - for (var i = 0; i !== children.length; ++i) { - if (children[i].name === 'streamId') { - return children[i].value; - } - } - //logger.info(element.id + " SRC: " + src); - return null; - }; - self.setVideoSrc = function (element, src) { - //logger.info("Set video src: ", element, src); - if (!src) { - logger.warn("Not attaching video stream, 'src' is null"); - return; - } - AdapterJS.WebRTCPlugin.WaitForPluginReady(); - var stream = AdapterJS.WebRTCPlugin.plugin - .getStreamWithId(AdapterJS.WebRTCPlugin.pageId, src); - attachMediaStream(element, stream); - }; - + // Call onReady() if Temasys plugin is not used + if (!RTCBrowserType.isTemasysPluginUsed()) { onReady(options, self.getUserMediaWithConstraints); - }); - } else { - try { - logger.error('Browser does not appear to be WebRTC-capable'); - } catch (e) { + resolve(); } - return; - } - - // Call onReady() if Temasys plugin is not used - if (!RTCBrowserType.isTemasysPluginUsed()) { - onReady(options, self.getUserMediaWithConstraints); - } - + }.bind(this)); }, /** * @param {string[]} um required user media types @@ -2649,15 +2684,13 @@ var RTCUtils = { options = options || {}; return new Promise(function (resolve, reject) { var successCallback = function (stream) { - var streams = handleLocalStream(stream, options.resolution); - resolve(options.dontCreateJitsiTracks? - streams: createLocalTracks(streams)); + resolve(handleLocalStream(stream, options.resolution)); }; options.devices = options.devices || ['audio', 'video']; if(!screenObtainer.isSupported() && options.devices.indexOf("desktop") !== -1){ - options.devices.splice(options.devices.indexOf("desktop"), 1); + reject(new Error("Desktop sharing is not supported!")); } if (RTCBrowserType.isFirefox() || RTCBrowserType.isTemasysPluginUsed()) { @@ -2700,14 +2733,14 @@ var RTCUtils = { desktopStream: desktopStream}); }, function (error) { reject( - JitsiMeetJSError.parseError(error)); + JitsiTrackErrors.parseError(error)); }); } else { successCallback({audioVideo: stream}); } }, function (error) { - reject(JitsiMeetJSError.parseError(error)); + reject(JitsiTrackErrors.parseError(error)); }, options); } else if (hasDesktop) { @@ -2716,7 +2749,7 @@ var RTCUtils = { successCallback({desktopStream: stream}); }, function (error) { reject( - JitsiMeetJSError.parseError(error)); + JitsiTrackErrors.parseError(error)); }); } } @@ -2769,7 +2802,7 @@ var RTCUtils = { module.exports = RTCUtils; }).call(this,"/modules/RTC/RTCUtils.js") -},{"../../JitsiMeetJSErrors":8,"../../service/RTC/RTCEvents":78,"../../service/RTC/Resolutions":79,"../../service/RTC/StreamEventTypes.js":80,"../xmpp/SDPUtil":31,"./JitsiLocalTrack":12,"./RTCBrowserType":16,"./ScreenObtainer":18,"./adapter.screenshare":19,"events":42,"jitsi-meet-logger":46}],18:[function(require,module,exports){ +},{"../../JitsiTrackErrors":9,"../../service/RTC/RTCEvents":79,"../../service/RTC/Resolutions":80,"../xmpp/SDPUtil":32,"./RTCBrowserType":17,"./ScreenObtainer":19,"./adapter.screenshare":20,"events":43,"jitsi-meet-logger":47}],19:[function(require,module,exports){ (function (__filename){ /* global chrome, $, alert */ /* jshint -W003 */ @@ -3191,7 +3224,7 @@ function initFirefoxExtensionDetection(options) { module.exports = ScreenObtainer; }).call(this,"/modules/RTC/ScreenObtainer.js") -},{"../../service/desktopsharing/DesktopSharingEventTypes":82,"./RTCBrowserType":16,"./adapter.screenshare":19,"jitsi-meet-logger":46}],19:[function(require,module,exports){ +},{"../../service/desktopsharing/DesktopSharingEventTypes":83,"./RTCBrowserType":17,"./adapter.screenshare":20,"jitsi-meet-logger":47}],20:[function(require,module,exports){ (function (__filename){ /*! adapterjs - v0.12.0 - 2015-09-04 */ var console = require("jitsi-meet-logger").getLogger(__filename); @@ -4364,7 +4397,7 @@ if (navigator.mozGetUserMedia) { } }).call(this,"/modules/RTC/adapter.screenshare.js") -},{"jitsi-meet-logger":46}],20:[function(require,module,exports){ +},{"jitsi-meet-logger":47}],21:[function(require,module,exports){ (function (__filename){ var logger = require("jitsi-meet-logger").getLogger(__filename); @@ -4450,7 +4483,7 @@ Settings.prototype.setLanguage = function (lang) { module.exports = Settings; }).call(this,"/modules/settings/Settings.js") -},{"jitsi-meet-logger":46}],21:[function(require,module,exports){ +},{"jitsi-meet-logger":47}],22:[function(require,module,exports){ /* global config */ /** * Provides statistics for the local stream. @@ -4580,7 +4613,7 @@ LocalStatsCollector.prototype.stop = function () { }; module.exports = LocalStatsCollector; -},{"../../service/statistics/Events":83,"../../service/statistics/constants":84,"../RTC/RTCBrowserType":16}],22:[function(require,module,exports){ +},{"../../service/statistics/Events":84,"../../service/statistics/constants":85,"../RTC/RTCBrowserType":17}],23:[function(require,module,exports){ (function (__filename){ /* global require, ssrc2jid */ /* jshint -W117 */ @@ -5307,7 +5340,7 @@ StatsCollector.prototype.processAudioLevelReport = function () { }; }).call(this,"/modules/statistics/RTPStatsCollector.js") -},{"../../service/statistics/Events":83,"../RTC/RTCBrowserType":16,"jitsi-meet-logger":46}],23:[function(require,module,exports){ +},{"../../service/statistics/Events":84,"../RTC/RTCBrowserType":17,"jitsi-meet-logger":47}],24:[function(require,module,exports){ /* global require, APP */ var LocalStats = require("./LocalStatsCollector.js"); var RTPStats = require("./RTPStatsCollector.js"); @@ -5455,7 +5488,7 @@ Statistics.LOCAL_JID = require("../../service/statistics/constants").LOCAL_JID; module.exports = Statistics; -},{"../../service/statistics/Events":83,"../../service/statistics/constants":84,"./LocalStatsCollector.js":21,"./RTPStatsCollector.js":22,"events":42}],24:[function(require,module,exports){ +},{"../../service/statistics/Events":84,"../../service/statistics/constants":85,"./LocalStatsCollector.js":22,"./RTPStatsCollector.js":23,"events":43}],25:[function(require,module,exports){ /** /** * @const @@ -5530,7 +5563,7 @@ var RandomUtil = { module.exports = RandomUtil; -},{}],25:[function(require,module,exports){ +},{}],26:[function(require,module,exports){ (function (__filename){ var logger = require("jitsi-meet-logger").getLogger(__filename); @@ -6176,7 +6209,7 @@ ChatRoom.prototype.getJidBySSRC = function (ssrc) { module.exports = ChatRoom; }).call(this,"/modules/xmpp/ChatRoom.js") -},{"../../service/xmpp/XMPPEvents":85,"./moderator":33,"events":42,"jitsi-meet-logger":46}],26:[function(require,module,exports){ +},{"../../service/xmpp/XMPPEvents":86,"./moderator":34,"events":43,"jitsi-meet-logger":47}],27:[function(require,module,exports){ (function (__filename){ /* * JingleSession provides an API to manage a single Jingle session. We will @@ -6312,7 +6345,7 @@ JingleSession.prototype.setAnswer = function(jingle) {}; module.exports = JingleSession; }).call(this,"/modules/xmpp/JingleSession.js") -},{"jitsi-meet-logger":46}],27:[function(require,module,exports){ +},{"jitsi-meet-logger":47}],28:[function(require,module,exports){ (function (__filename){ /* jshint -W117 */ @@ -7902,7 +7935,7 @@ JingleSessionPC.prototype.remoteStreamAdded = function (data, times) { module.exports = JingleSessionPC; }).call(this,"/modules/xmpp/JingleSessionPC.js") -},{"../../service/xmpp/XMPPEvents":85,"../RTC/RTC":15,"../RTC/RTCBrowserType":16,"./JingleSession":26,"./LocalSSRCReplacement":28,"./SDP":29,"./SDPDiffer":30,"./SDPUtil":31,"./TraceablePeerConnection":32,"async":41,"jitsi-meet-logger":46,"sdp-transform":74}],28:[function(require,module,exports){ +},{"../../service/xmpp/XMPPEvents":86,"../RTC/RTC":16,"../RTC/RTCBrowserType":17,"./JingleSession":27,"./LocalSSRCReplacement":29,"./SDP":30,"./SDPDiffer":31,"./SDPUtil":32,"./TraceablePeerConnection":33,"async":42,"jitsi-meet-logger":47,"sdp-transform":75}],29:[function(require,module,exports){ (function (__filename){ /* global $ */ var logger = require("jitsi-meet-logger").getLogger(__filename); @@ -8199,7 +8232,7 @@ var LocalSSRCReplacement = { module.exports = LocalSSRCReplacement; }).call(this,"/modules/xmpp/LocalSSRCReplacement.js") -},{"../RTC/RTCBrowserType":16,"../util/RandomUtil":24,"./SDP":29,"jitsi-meet-logger":46}],29:[function(require,module,exports){ +},{"../RTC/RTCBrowserType":17,"../util/RandomUtil":25,"./SDP":30,"jitsi-meet-logger":47}],30:[function(require,module,exports){ (function (__filename){ /* jshint -W117 */ @@ -8853,7 +8886,7 @@ module.exports = SDP; }).call(this,"/modules/xmpp/SDP.js") -},{"./SDPUtil":31,"jitsi-meet-logger":46}],30:[function(require,module,exports){ +},{"./SDPUtil":32,"jitsi-meet-logger":47}],31:[function(require,module,exports){ var SDPUtil = require("./SDPUtil"); function SDPDiffer(mySDP, otherSDP) @@ -9022,12 +9055,13 @@ SDPDiffer.prototype.toJingle = function(modify) { }; module.exports = SDPDiffer; -},{"./SDPUtil":31}],31:[function(require,module,exports){ +},{"./SDPUtil":32}],32:[function(require,module,exports){ (function (__filename){ var logger = require("jitsi-meet-logger").getLogger(__filename); -var RTCBrowserType = require('../RTC/RTCBrowserType'); +var RTCBrowserType = require("../RTC/RTCBrowserType"); -var SDPUtil = { + +SDPUtil = { filter_special_chars: function (text) { return text.replace(/[\\\/\{,\}\+]/g, ""); }, @@ -9346,6 +9380,7 @@ var SDPUtil = { protocol = 'tcp'; } + line += protocol; //.toUpperCase(); // chrome M23 doesn't like this line += ' '; line += cand.getAttribute('priority'); line += ' '; @@ -9388,7 +9423,7 @@ var SDPUtil = { module.exports = SDPUtil; }).call(this,"/modules/xmpp/SDPUtil.js") -},{"../RTC/RTCBrowserType":16,"jitsi-meet-logger":46}],32:[function(require,module,exports){ +},{"../RTC/RTCBrowserType":17,"jitsi-meet-logger":47}],33:[function(require,module,exports){ (function (__filename){ /* global $ */ var RTC = require('../RTC/RTC'); @@ -9417,7 +9452,7 @@ function TraceablePeerConnection(ice_config, constraints, session) { var Interop = require('sdp-interop').Interop; this.interop = new Interop(); var Simulcast = require('sdp-simulcast'); - this.simulcast = new Simulcast({numOfLayers: 3, explodeRemoteSimulcast: false}); + this.simulcast = new Simulcast({numOfLayers: 2, explodeRemoteSimulcast: false}); // override as desired this.trace = function (what, info) { @@ -9613,7 +9648,7 @@ if (TraceablePeerConnection.prototype.__defineGetter__ !== undefined) { // FIXME this should probably be after the Unified Plan -> Plan B // transformation. desc = SSRCReplacement.mungeLocalVideoSSRC(desc); - + this.trace('getLocalDescription::preTransform', dumpSDP(desc)); // if we're running on FF, transform to Plan B first. @@ -9836,9 +9871,8 @@ TraceablePeerConnection.prototype.getStats = function(callback, errback) { module.exports = TraceablePeerConnection; - }).call(this,"/modules/xmpp/TraceablePeerConnection.js") -},{"../../service/xmpp/XMPPEvents":85,"../RTC/RTC":15,"../RTC/RTCBrowserType.js":16,"./LocalSSRCReplacement":28,"jitsi-meet-logger":46,"sdp-interop":64,"sdp-simulcast":67,"sdp-transform":74}],33:[function(require,module,exports){ +},{"../../service/xmpp/XMPPEvents":86,"../RTC/RTC":16,"../RTC/RTCBrowserType.js":17,"./LocalSSRCReplacement":29,"jitsi-meet-logger":47,"sdp-interop":65,"sdp-simulcast":68,"sdp-transform":75}],34:[function(require,module,exports){ (function (__filename){ /* global $, $iq, APP, config, messageHandler, roomName, sessionTerminated, Strophe, Util */ @@ -10275,7 +10309,7 @@ module.exports = Moderator; }).call(this,"/modules/xmpp/moderator.js") -},{"../../service/authentication/AuthenticationEvents":81,"../../service/xmpp/XMPPEvents":85,"../settings/Settings":20,"jitsi-meet-logger":46}],34:[function(require,module,exports){ +},{"../../service/authentication/AuthenticationEvents":82,"../../service/xmpp/XMPPEvents":86,"../settings/Settings":21,"jitsi-meet-logger":47}],35:[function(require,module,exports){ (function (__filename){ /* jshint -W117 */ /* a simple MUC connection plugin @@ -10372,7 +10406,7 @@ module.exports = function(XMPP) { }).call(this,"/modules/xmpp/strophe.emuc.js") -},{"./ChatRoom":25,"jitsi-meet-logger":46}],35:[function(require,module,exports){ +},{"./ChatRoom":26,"jitsi-meet-logger":47}],36:[function(require,module,exports){ (function (__filename){ /* jshint -W117 */ @@ -10669,7 +10703,7 @@ module.exports = function(XMPP, eventEmitter) { }).call(this,"/modules/xmpp/strophe.jingle.js") -},{"../../service/xmpp/XMPPEvents":85,"../RTC/RTCBrowserType":16,"./JingleSessionPC":27,"jitsi-meet-logger":46}],36:[function(require,module,exports){ +},{"../../service/xmpp/XMPPEvents":86,"../RTC/RTCBrowserType":17,"./JingleSessionPC":28,"jitsi-meet-logger":47}],37:[function(require,module,exports){ /* global Strophe */ module.exports = function () { @@ -10690,7 +10724,7 @@ module.exports = function () { } }); }; -},{}],37:[function(require,module,exports){ +},{}],38:[function(require,module,exports){ (function (__filename){ /* global $, $iq, Strophe */ @@ -10817,7 +10851,7 @@ module.exports = function (XMPP, eventEmitter) { }; }).call(this,"/modules/xmpp/strophe.ping.js") -},{"../../service/xmpp/XMPPEvents":85,"jitsi-meet-logger":46}],38:[function(require,module,exports){ +},{"../../service/xmpp/XMPPEvents":86,"jitsi-meet-logger":47}],39:[function(require,module,exports){ (function (__filename){ /* jshint -W117 */ var logger = require("jitsi-meet-logger").getLogger(__filename); @@ -10919,7 +10953,7 @@ module.exports = function() { }; }).call(this,"/modules/xmpp/strophe.rayo.js") -},{"jitsi-meet-logger":46}],39:[function(require,module,exports){ +},{"jitsi-meet-logger":47}],40:[function(require,module,exports){ (function (__filename){ /* global Strophe */ /** @@ -10968,7 +11002,7 @@ module.exports = function () { }; }).call(this,"/modules/xmpp/strophe.util.js") -},{"jitsi-meet-logger":46}],40:[function(require,module,exports){ +},{"jitsi-meet-logger":47}],41:[function(require,module,exports){ (function (__filename){ /* global $, APP, config, Strophe*/ @@ -11276,7 +11310,7 @@ XMPP.prototype.disconnect = function () { module.exports = XMPP; }).call(this,"/modules/xmpp/xmpp.js") -},{"../../JitsiConnectionErrors":5,"../../JitsiConnectionEvents":6,"../../service/RTC/RTCEvents":78,"../../service/RTC/StreamEventTypes":80,"../../service/xmpp/XMPPEvents":85,"../RTC/RTC":15,"./strophe.emuc":34,"./strophe.jingle":35,"./strophe.logger":36,"./strophe.ping":37,"./strophe.rayo":38,"./strophe.util":39,"events":42,"jitsi-meet-logger":46,"pako":47}],41:[function(require,module,exports){ +},{"../../JitsiConnectionErrors":5,"../../JitsiConnectionEvents":6,"../../service/RTC/RTCEvents":79,"../../service/RTC/StreamEventTypes":81,"../../service/xmpp/XMPPEvents":86,"../RTC/RTC":16,"./strophe.emuc":35,"./strophe.jingle":36,"./strophe.logger":37,"./strophe.ping":38,"./strophe.rayo":39,"./strophe.util":40,"events":43,"jitsi-meet-logger":47,"pako":48}],42:[function(require,module,exports){ (function (process){ /*! * async @@ -12403,7 +12437,7 @@ module.exports = XMPP; }()); }).call(this,require('_process')) -},{"_process":43}],42:[function(require,module,exports){ +},{"_process":44}],43:[function(require,module,exports){ // Copyright Joyent, Inc. and other Node contributors. // // Permission is hereby granted, free of charge, to any person obtaining a @@ -12706,7 +12740,7 @@ function isUndefined(arg) { return arg === void 0; } -},{}],43:[function(require,module,exports){ +},{}],44:[function(require,module,exports){ // shim for using process in browser var process = module.exports = {}; @@ -12799,7 +12833,7 @@ process.chdir = function (dir) { }; process.umask = function() { return 0; }; -},{}],44:[function(require,module,exports){ +},{}],45:[function(require,module,exports){ (function (process,global){ /*! * @overview es6-promise - a tiny implementation of Promises/A+. @@ -13770,7 +13804,7 @@ process.umask = function() { return 0; }; }).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{"_process":43}],45:[function(require,module,exports){ +},{"_process":44}],46:[function(require,module,exports){ /* Copyright @ 2015 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -13914,7 +13948,7 @@ Logger.levels = { ERROR: "error" }; -},{}],46:[function(require,module,exports){ +},{}],47:[function(require,module,exports){ /* Copyright @ 2015 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -13999,7 +14033,7 @@ module.exports = { levels: Logger.levels }; -},{"./Logger":45}],47:[function(require,module,exports){ +},{"./Logger":46}],48:[function(require,module,exports){ // Top level file is just a mixin of submodules & constants 'use strict'; @@ -14015,7 +14049,7 @@ assign(pako, deflate, inflate, constants); module.exports = pako; -},{"./lib/deflate":48,"./lib/inflate":49,"./lib/utils/common":50,"./lib/zlib/constants":53}],48:[function(require,module,exports){ +},{"./lib/deflate":49,"./lib/inflate":50,"./lib/utils/common":51,"./lib/zlib/constants":54}],49:[function(require,module,exports){ 'use strict'; @@ -14393,7 +14427,7 @@ exports.deflate = deflate; exports.deflateRaw = deflateRaw; exports.gzip = gzip; -},{"./utils/common":50,"./utils/strings":51,"./zlib/deflate.js":55,"./zlib/messages":60,"./zlib/zstream":62}],49:[function(require,module,exports){ +},{"./utils/common":51,"./utils/strings":52,"./zlib/deflate.js":56,"./zlib/messages":61,"./zlib/zstream":63}],50:[function(require,module,exports){ 'use strict'; @@ -14795,7 +14829,7 @@ exports.inflate = inflate; exports.inflateRaw = inflateRaw; exports.ungzip = inflate; -},{"./utils/common":50,"./utils/strings":51,"./zlib/constants":53,"./zlib/gzheader":56,"./zlib/inflate.js":58,"./zlib/messages":60,"./zlib/zstream":62}],50:[function(require,module,exports){ +},{"./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){ 'use strict'; @@ -14899,7 +14933,7 @@ exports.setTyped = function (on) { exports.setTyped(TYPED_OK); -},{}],51:[function(require,module,exports){ +},{}],52:[function(require,module,exports){ // String encode/decode helpers 'use strict'; @@ -15086,7 +15120,7 @@ exports.utf8border = function(buf, max) { return (pos + _utf8len[buf[pos]] > max) ? pos : max; }; -},{"./common":50}],52:[function(require,module,exports){ +},{"./common":51}],53:[function(require,module,exports){ 'use strict'; // Note: adler32 takes 12% for level 0 and 2% for level 6. @@ -15120,7 +15154,7 @@ function adler32(adler, buf, len, pos) { module.exports = adler32; -},{}],53:[function(require,module,exports){ +},{}],54:[function(require,module,exports){ module.exports = { /* Allowed flush values; see deflate() and inflate() below for details */ @@ -15169,7 +15203,7 @@ module.exports = { //Z_NULL: null // Use -1 or null inline, depending on var type }; -},{}],54:[function(require,module,exports){ +},{}],55:[function(require,module,exports){ 'use strict'; // Note: we can't get significant speed boost here. @@ -15212,7 +15246,7 @@ function crc32(crc, buf, len, pos) { module.exports = crc32; -},{}],55:[function(require,module,exports){ +},{}],56:[function(require,module,exports){ 'use strict'; var utils = require('../utils/common'); @@ -16979,7 +17013,7 @@ exports.deflatePrime = deflatePrime; exports.deflateTune = deflateTune; */ -},{"../utils/common":50,"./adler32":52,"./crc32":54,"./messages":60,"./trees":61}],56:[function(require,module,exports){ +},{"../utils/common":51,"./adler32":53,"./crc32":55,"./messages":61,"./trees":62}],57:[function(require,module,exports){ 'use strict'; @@ -17021,7 +17055,7 @@ function GZheader() { module.exports = GZheader; -},{}],57:[function(require,module,exports){ +},{}],58:[function(require,module,exports){ 'use strict'; // See state defs from inflate.js @@ -17349,7 +17383,7 @@ module.exports = function inflate_fast(strm, start) { return; }; -},{}],58:[function(require,module,exports){ +},{}],59:[function(require,module,exports){ 'use strict'; @@ -18854,7 +18888,7 @@ exports.inflateSyncPoint = inflateSyncPoint; exports.inflateUndermine = inflateUndermine; */ -},{"../utils/common":50,"./adler32":52,"./crc32":54,"./inffast":57,"./inftrees":59}],59:[function(require,module,exports){ +},{"../utils/common":51,"./adler32":53,"./crc32":55,"./inffast":58,"./inftrees":60}],60:[function(require,module,exports){ 'use strict'; @@ -19183,7 +19217,7 @@ module.exports = function inflate_table(type, lens, lens_index, codes, table, ta return 0; }; -},{"../utils/common":50}],60:[function(require,module,exports){ +},{"../utils/common":51}],61:[function(require,module,exports){ 'use strict'; module.exports = { @@ -19198,7 +19232,7 @@ module.exports = { '-6': 'incompatible version' /* Z_VERSION_ERROR (-6) */ }; -},{}],61:[function(require,module,exports){ +},{}],62:[function(require,module,exports){ 'use strict'; @@ -20399,7 +20433,7 @@ exports._tr_flush_block = _tr_flush_block; exports._tr_tally = _tr_tally; exports._tr_align = _tr_align; -},{"../utils/common":50}],62:[function(require,module,exports){ +},{"../utils/common":51}],63:[function(require,module,exports){ 'use strict'; @@ -20430,7 +20464,7 @@ function ZStream() { module.exports = ZStream; -},{}],63:[function(require,module,exports){ +},{}],64:[function(require,module,exports){ /* Copyright @ 2015 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -20471,7 +20505,7 @@ module.exports = function arrayEquals(array) { }; -},{}],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"); @@ -20489,7 +20523,7 @@ module.exports = function arrayEquals(array) { exports.Interop = require('./interop'); -},{"./interop":65}],65:[function(require,module,exports){ +},{"./interop":66}],66:[function(require,module,exports){ /* Copyright @ 2015 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -21212,7 +21246,7 @@ Interop.prototype.toUnifiedPlan = function(desc) { //#endregion }; -},{"./array-equals":63,"./transform":66}],66:[function(require,module,exports){ +},{"./array-equals":64,"./transform":67}],67:[function(require,module,exports){ /* Copyright @ 2015 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -21326,7 +21360,7 @@ exports.parse = function(sdp) { }; -},{"sdp-transform":74}],67:[function(require,module,exports){ +},{"sdp-transform":75}],68:[function(require,module,exports){ /* Copyright @ 2015 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -21746,7 +21780,7 @@ Simulcast.prototype.mungeLocalDescription = function (desc) { module.exports = Simulcast; -},{"./transform-utils":68,"sdp-transform":70}],68:[function(require,module,exports){ +},{"./transform-utils":69,"sdp-transform":71}],69:[function(require,module,exports){ /* Copyright @ 2015 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -21812,7 +21846,7 @@ exports.parseSsrcs = function (mLine) { }; -},{}],69:[function(require,module,exports){ +},{}],70:[function(require,module,exports){ var grammar = module.exports = { v: [{ name: 'version', @@ -22061,7 +22095,7 @@ Object.keys(grammar).forEach(function (key) { }); }); -},{}],70:[function(require,module,exports){ +},{}],71:[function(require,module,exports){ var parser = require('./parser'); var writer = require('./writer'); @@ -22071,7 +22105,7 @@ exports.parseFmtpConfig = parser.parseFmtpConfig; exports.parsePayloads = parser.parsePayloads; exports.parseRemoteCandidates = parser.parseRemoteCandidates; -},{"./parser":71,"./writer":72}],71:[function(require,module,exports){ +},{"./parser":72,"./writer":73}],72:[function(require,module,exports){ var toIntIfInt = function (v) { return String(Number(v)) === v ? Number(v) : v; }; @@ -22166,7 +22200,7 @@ exports.parseRemoteCandidates = function (str) { return candidates; }; -},{"./grammar":69}],72:[function(require,module,exports){ +},{"./grammar":70}],73:[function(require,module,exports){ var grammar = require('./grammar'); // customized util.format - discards excess arguments and can void middle ones @@ -22282,7 +22316,7 @@ module.exports = function (session, opts) { return sdp.join('\r\n') + '\r\n'; }; -},{"./grammar":69}],73:[function(require,module,exports){ +},{"./grammar":70}],74:[function(require,module,exports){ var grammar = module.exports = { v: [{ name: 'version', @@ -22533,9 +22567,9 @@ Object.keys(grammar).forEach(function (key) { }); }); -},{}],74:[function(require,module,exports){ -arguments[4][70][0].apply(exports,arguments) -},{"./parser":75,"./writer":76,"dup":70}],75:[function(require,module,exports){ +},{}],75:[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; }; @@ -22630,16 +22664,16 @@ exports.parseRemoteCandidates = function (str) { return candidates; }; -},{"./grammar":73}],76:[function(require,module,exports){ -arguments[4][72][0].apply(exports,arguments) -},{"./grammar":73,"dup":72}],77:[function(require,module,exports){ +},{"./grammar":74}],77:[function(require,module,exports){ +arguments[4][73][0].apply(exports,arguments) +},{"./grammar":74,"dup":73}],78:[function(require,module,exports){ var MediaStreamType = { VIDEO_TYPE: "Video", AUDIO_TYPE: "Audio" }; module.exports = MediaStreamType; -},{}],78:[function(require,module,exports){ +},{}],79:[function(require,module,exports){ var RTCEvents = { RTC_READY: "rtc.ready", DATA_CHANNEL_OPEN: "rtc.data_channel_open", @@ -22650,7 +22684,7 @@ var RTCEvents = { }; module.exports = RTCEvents; -},{}],79:[function(require,module,exports){ +},{}],80:[function(require,module,exports){ var Resolutions = { "1080": { width: 1920, @@ -22704,7 +22738,7 @@ var Resolutions = { } }; module.exports = Resolutions; -},{}],80:[function(require,module,exports){ +},{}],81:[function(require,module,exports){ var StreamEventTypes = { EVENT_TYPE_LOCAL_CREATED: "stream.local_created", @@ -22719,7 +22753,7 @@ var StreamEventTypes = { }; module.exports = StreamEventTypes; -},{}],81:[function(require,module,exports){ +},{}],82:[function(require,module,exports){ var AuthenticationEvents = { /** * Event callback arguments: @@ -22733,7 +22767,7 @@ var AuthenticationEvents = { }; module.exports = AuthenticationEvents; -},{}],82:[function(require,module,exports){ +},{}],83:[function(require,module,exports){ var DesktopSharingEventTypes = { INIT: "ds.init", @@ -22749,7 +22783,7 @@ var DesktopSharingEventTypes = { module.exports = DesktopSharingEventTypes; -},{}],83:[function(require,module,exports){ +},{}],84:[function(require,module,exports){ module.exports = { /** * An event carrying connection statistics. @@ -22765,12 +22799,12 @@ module.exports = { STOP: "statistics.stop" }; -},{}],84:[function(require,module,exports){ +},{}],85:[function(require,module,exports){ var Constants = { LOCAL_JID: 'local' }; module.exports = Constants; -},{}],85:[function(require,module,exports){ +},{}],86:[function(require,module,exports){ var XMPPEvents = { // Designates an event indicating that the connection to the XMPP server // failed. From 9e34ae7f5d22f806dd21c5ad67745bb38f41eb1b Mon Sep 17 00:00:00 2001 From: isymchych Date: Mon, 7 Dec 2015 14:26:39 +0200 Subject: [PATCH 6/8] fixed jshint errors in CharRoom --- modules/xmpp/ChatRoom.js | 46 +++++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/modules/xmpp/ChatRoom.js b/modules/xmpp/ChatRoom.js index 5e943bd3d..48786766b 100644 --- a/modules/xmpp/ChatRoom.js +++ b/modules/xmpp/ChatRoom.js @@ -1,4 +1,5 @@ - +/* global Strophe, $, $pres, $iq, $msg */ +/* jshint -W101,-W069 */ var logger = require("jitsi-meet-logger").getLogger(__filename); var XMPPEvents = require("../../service/xmpp/XMPPEvents"); var Moderator = require("./moderator"); @@ -9,23 +10,24 @@ var parser = { var self = this; $(packet).children().each(function (index) { var tagName = $(this).prop("tagName"); - var node = {} - node["tagName"] = tagName; + var node = { + tagName: tagName + }; node.attributes = {}; $($(this)[0].attributes).each(function( index, attr ) { node.attributes[ attr.name ] = attr.value; - } ); + }); var text = Strophe.getText($(this)[0]); - if(text) + if (text) { node.value = text; + } node.children = []; nodes.push(node); self.packet2JSON($(this), node.children); - }) + }); }, JSON2packet: function (nodes, packet) { - for(var i = 0; i < nodes.length; i++) - { + for(var i = 0; i < nodes.length; i++) { var node = nodes[i]; if(!node || node === null){ continue; @@ -104,7 +106,7 @@ ChatRoom.prototype.updateDeviceAvailability = function (devices) { } ] }); -} +}; ChatRoom.prototype.join = function (password, tokenPassword) { if(password) @@ -206,7 +208,7 @@ ChatRoom.prototype.onPresence = function (pres) { member.jid = tmp.attr('jid'); member.isFocus = false; if (member.jid - && member.jid.indexOf(this.moderator.getFocusUserJid() + "/") == 0) { + && member.jid.indexOf(this.moderator.getFocusUserJid() + "/") === 0) { member.isFocus = true; } @@ -345,7 +347,7 @@ ChatRoom.prototype.onPresenceUnavailable = function (pres, from) { this.xmpp.disposeConference(false); this.eventEmitter.emit(XMPPEvents.MUC_DESTROYED, reason); - delete this.connection.emuc.rooms[Strophe.getBareJidFromJid(jid)]; + delete this.connection.emuc.rooms[Strophe.getBareJidFromJid(from)]; return true; } @@ -388,7 +390,7 @@ ChatRoom.prototype.onMessage = function (msg, from) { var subject = $(msg).find('>subject'); if (subject.length) { var subjectText = subject.text(); - if (subjectText || subjectText == "") { + if (subjectText || subjectText === "") { this.eventEmitter.emit(XMPPEvents.SUBJECT_CHANGED, subjectText); logger.log("Subject is changed to " + subjectText); } @@ -413,7 +415,7 @@ ChatRoom.prototype.onMessage = function (msg, from) { this.eventEmitter.emit(XMPPEvents.MESSAGE_RECEIVED, from, nick, txt, this.myroomjid, stamp); } -} +}; ChatRoom.prototype.onPresenceError = function (pres, from) { if ($(pres).find('>error[type="auth"]>not-authorized[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]').length) { @@ -492,11 +494,11 @@ ChatRoom.prototype.removeFromPresence = function (key) { ChatRoom.prototype.addPresenceListener = function (name, handler) { this.presHandlers[name] = handler; -} +}; ChatRoom.prototype.removePresenceListener = function (name) { delete this.presHandlers[name]; -} +}; ChatRoom.prototype.isModerator = function (jid) { return this.role === 'moderator'; @@ -518,8 +520,8 @@ ChatRoom.prototype.setJingleSession = function(session){ ChatRoom.prototype.removeStream = function (stream) { if(!this.session) return; - this.session.peerconnection.removeStream(stream) -} + this.session.peerconnection.removeStream(stream); +}; ChatRoom.prototype.switchStreams = function (stream, oldStream, callback, isAudio) { if(this.session) { @@ -541,14 +543,14 @@ ChatRoom.prototype.addStream = function (stream, callback) { logger.warn("No conference handler or conference not started yet"); callback(); } -} +}; ChatRoom.prototype.setVideoMute = function (mute, callback, options) { var self = this; var localCallback = function (mute) { self.sendVideoInfoPresence(mute); if(callback) - callback(mute) + callback(mute); }; if(this.session) @@ -581,7 +583,7 @@ ChatRoom.prototype.addAudioInfoToPresence = function (mute) { {attributes: {"audions": "http://jitsi.org/jitmeet/audio"}, value: mute.toString()}); -} +}; ChatRoom.prototype.sendAudioInfoPresence = function(mute, callback) { this.addAudioInfoToPresence(mute); @@ -598,7 +600,7 @@ ChatRoom.prototype.addVideoInfoToPresence = function (mute) { {attributes: {"videons": "http://jitsi.org/jitmeet/video"}, value: mute.toString()}); -} +}; ChatRoom.prototype.sendVideoInfoPresence = function (mute) { @@ -631,7 +633,7 @@ ChatRoom.prototype.remoteStreamAdded = function(data, sid, thessrc) { } this.eventEmitter.emit(XMPPEvents.REMOTE_STREAM_RECEIVED, data, sid, thessrc); -} +}; ChatRoom.prototype.getJidBySSRC = function (ssrc) { if (!this.session) From e25f831ceb3b7ee4dca4258ff43f3f1f2a443dfb Mon Sep 17 00:00:00 2001 From: isymchych Date: Mon, 7 Dec 2015 15:54:16 +0200 Subject: [PATCH 7/8] add info about user role to JitsiConference --- JitsiConference.js | 31 +++++++++++++ JitsiConferenceEvents.js | 6 ++- JitsiParticipant.js | 6 +-- doc/API.md | 6 +++ lib-jitsi-meet.js | 99 +++++++++++++++++++++++++++------------- modules/xmpp/ChatRoom.js | 10 ++-- 6 files changed, 116 insertions(+), 42 deletions(-) diff --git a/JitsiConference.js b/JitsiConference.js index 01b4b7ef8..f05f315b8 100644 --- a/JitsiConference.js +++ b/JitsiConference.js @@ -195,6 +195,22 @@ JitsiConference.prototype.removeTrack = function (track) { this.rtc.removeLocalStream(track); }; +/** + * Get role of the local user. + * @returns {string} user role: 'moderator' or 'none' + */ +JitsiConference.prototype.getRole = function () { + return this.room.role; +}; + +/** + * Check if local user is moderator. + * @returns {boolean} true if local user is moderator, false otherwise. + */ +JitsiConference.prototype.isModerator = function () { + return this.room.isModerator(); +}; + /** * Elects the participant with the given id to be the selected participant or the speaker. * @param id the identifier of the participant @@ -253,6 +269,16 @@ JitsiConference.prototype.onMemberLeft = function (jid) { this.eventEmitter.emit(JitsiConferenceEvents.USER_LEFT, id); }; +JitsiConference.prototype.onUserRoleChanged = function (jid, role) { + var id = Strophe.getResourceFromJid(jid); + var participant = this.getParticipantById(id); + if (!participant) { + return; + } + participant._role = role; + this.eventEmitter.emit(JitsiConferenceEvents.USER_ROLE_CHANGED, id, role); +}; + JitsiConference.prototype.onDisplayNameChanged = function (jid, displayName) { var id = Strophe.getResourceFromJid(jid); var participant = this.getParticipantById(id); @@ -362,6 +388,11 @@ function setupListeners(conference) { conference.room.addListener(XMPPEvents.DISPLAY_NAME_CHANGED, conference.onDisplayNameChanged.bind(conference)); + conference.room.addListener(XMPPEvents.LOCAL_ROLE_CHANGED, function (role) { + conference.eventEmitter.emit(JitsiConferenceEvents.USER_ROLE_CHANGED, conference.myUserId(), role); + }); + conference.room.addListener(XMPPEvents.MUC_ROLE_CHANGED, conference.onUserRoleChanged.bind(conference)); + conference.room.addListener(XMPPEvents.CONNECTION_INTERRUPTED, function () { conference.eventEmitter.emit(JitsiConferenceEvents.CONNECTION_INTERRUPTED); }); diff --git a/JitsiConferenceEvents.js b/JitsiConferenceEvents.js index 59f1f6797..5c3273873 100644 --- a/JitsiConferenceEvents.js +++ b/JitsiConferenceEvents.js @@ -23,6 +23,10 @@ var JitsiConferenceEvents = { * A user has left the conference. */ USER_LEFT: "conference.userLeft", + /** + * User role changed. + */ + USER_ROLE_CHANGED: "conference.roleChanged", /** * New text message was received. */ @@ -79,7 +83,7 @@ var JitsiConferenceEvents = { /** * Indicates that DTMF support changed. */ - DTMF_SUPPORT_CHANGED: "conference.dtmf_support_changed" + DTMF_SUPPORT_CHANGED: "conference.dtmfSupportChanged" }; module.exports = JitsiConferenceEvents; diff --git a/JitsiParticipant.js b/JitsiParticipant.js index 7e647a5e6..e7c2f34fd 100644 --- a/JitsiParticipant.js +++ b/JitsiParticipant.js @@ -7,7 +7,7 @@ function JitsiParticipant(id, conference, displayName){ this._displayName = displayName; this._supportsDTMF = false; this._tracks = []; - this._isModerator = false; + this._role = 'none'; } /** @@ -42,7 +42,7 @@ JitsiParticipant.prototype.getDisplayName = function() { * @returns {Boolean} Whether this participant is a moderator or not. */ JitsiParticipant.prototype.isModerator = function() { - return this._isModerator; + return this._role === 'moderator'; }; // Gets a link to an etherpad instance advertised by the participant? @@ -81,7 +81,7 @@ JitsiParticipant.prototype.getLatestStats = function() { * @returns {String} The role of this participant. */ JitsiParticipant.prototype.getRole = function() { - + return this._role; }; /* diff --git a/doc/API.md b/doc/API.md index 1aa2d0eef..22e954c01 100644 --- a/doc/API.md +++ b/doc/API.md @@ -78,6 +78,7 @@ JitsiMeetJS.setLogLevel(JitsiMeetJS.logLevels.ERROR); - CONFERENCE_JOINED - notifies the local user that he joined the conference successfully. (no parameters) - CONFERENCE_LEFT - notifies the local user that he left the conference successfully. (no parameters) - DTMF_SUPPORT_CHANGED - notifies if at least one user supports DTMF. (parameters - supports(boolean)) + - USER_ROLE_CHANGED - notifies that role of some user changed. (parameters - id(string), role(string)) 2. connection - CONNECTION_FAILED - indicates that the server connection failed. @@ -220,6 +221,11 @@ The object represents a conference. We have the following methods to control the 19. isDTMFSupported() - Check if at least one user supports DTMF. +20. getRole() - returns string with the local user role ("moderator" or "none") + +21. isModerator() - checks if local user has "moderator" role + + JitsiTrack ====== The object represents single track - video or audio. They can be remote tracks ( from the other participants in the call) or local tracks (from the devices of the local participant). diff --git a/lib-jitsi-meet.js b/lib-jitsi-meet.js index d224ff7b5..9a2f1587f 100644 --- a/lib-jitsi-meet.js +++ b/lib-jitsi-meet.js @@ -197,6 +197,22 @@ JitsiConference.prototype.removeTrack = function (track) { this.rtc.removeLocalStream(track); }; +/** + * Get role of the local user. + * @returns {string} user role: 'moderator' or 'none' + */ +JitsiConference.prototype.getRole = function () { + return this.room.role; +}; + +/** + * Check if local user is moderator. + * @returns {boolean} true if local user is moderator, false otherwise. + */ +JitsiConference.prototype.isModerator = function () { + return this.room.isModerator(); +}; + /** * Elects the participant with the given id to be the selected participant or the speaker. * @param id the identifier of the participant @@ -255,6 +271,16 @@ JitsiConference.prototype.onMemberLeft = function (jid) { this.eventEmitter.emit(JitsiConferenceEvents.USER_LEFT, id); }; +JitsiConference.prototype.onUserRoleChanged = function (jid, role) { + var id = Strophe.getResourceFromJid(jid); + var participant = this.getParticipantById(id); + if (!participant) { + return; + } + participant._role = role; + this.eventEmitter.emit(JitsiConferenceEvents.USER_ROLE_CHANGED, id, role); +}; + JitsiConference.prototype.onDisplayNameChanged = function (jid, displayName) { var id = Strophe.getResourceFromJid(jid); var participant = this.getParticipantById(id); @@ -364,6 +390,11 @@ function setupListeners(conference) { conference.room.addListener(XMPPEvents.DISPLAY_NAME_CHANGED, conference.onDisplayNameChanged.bind(conference)); + conference.room.addListener(XMPPEvents.LOCAL_ROLE_CHANGED, function (role) { + conference.eventEmitter.emit(JitsiConferenceEvents.USER_ROLE_CHANGED, conference.myUserId(), role); + }); + conference.room.addListener(XMPPEvents.MUC_ROLE_CHANGED, conference.onUserRoleChanged.bind(conference)); + conference.room.addListener(XMPPEvents.CONNECTION_INTERRUPTED, function () { conference.eventEmitter.emit(JitsiConferenceEvents.CONNECTION_INTERRUPTED); }); @@ -496,6 +527,10 @@ var JitsiConferenceEvents = { * A user has left the conference. */ USER_LEFT: "conference.userLeft", + /** + * User role changed. + */ + USER_ROLE_CHANGED: "conference.roleChanged", /** * New text message was received. */ @@ -552,7 +587,7 @@ var JitsiConferenceEvents = { /** * Indicates that DTMF support changed. */ - DTMF_SUPPORT_CHANGED: "conference.dtmf_support_changed" + DTMF_SUPPORT_CHANGED: "conference.dtmfSupportChanged" }; module.exports = JitsiConferenceEvents; @@ -783,7 +818,7 @@ function JitsiParticipant(id, conference, displayName){ this._displayName = displayName; this._supportsDTMF = false; this._tracks = []; - this._isModerator = false; + this._role = 'none'; } /** @@ -818,7 +853,7 @@ JitsiParticipant.prototype.getDisplayName = function() { * @returns {Boolean} Whether this participant is a moderator or not. */ JitsiParticipant.prototype.isModerator = function() { - return this._isModerator; + return this._role === 'moderator'; }; // Gets a link to an etherpad instance advertised by the participant? @@ -857,7 +892,7 @@ JitsiParticipant.prototype.getLatestStats = function() { * @returns {String} The role of this participant. */ JitsiParticipant.prototype.getRole = function() { - + return this._role; }; /* @@ -5565,7 +5600,8 @@ module.exports = RandomUtil; },{}],26:[function(require,module,exports){ (function (__filename){ - +/* global Strophe, $, $pres, $iq, $msg */ +/* jshint -W101,-W069 */ var logger = require("jitsi-meet-logger").getLogger(__filename); var XMPPEvents = require("../../service/xmpp/XMPPEvents"); var Moderator = require("./moderator"); @@ -5576,23 +5612,24 @@ var parser = { var self = this; $(packet).children().each(function (index) { var tagName = $(this).prop("tagName"); - var node = {} - node["tagName"] = tagName; + var node = { + tagName: tagName + }; node.attributes = {}; $($(this)[0].attributes).each(function( index, attr ) { node.attributes[ attr.name ] = attr.value; - } ); + }); var text = Strophe.getText($(this)[0]); - if(text) + if (text) { node.value = text; + } node.children = []; nodes.push(node); self.packet2JSON($(this), node.children); - }) + }); }, JSON2packet: function (nodes, packet) { - for(var i = 0; i < nodes.length; i++) - { + for(var i = 0; i < nodes.length; i++) { var node = nodes[i]; if(!node || node === null){ continue; @@ -5634,7 +5671,7 @@ function ChatRoom(connection, jid, password, XMPP, options) { this.presMap = {}; this.presHandlers = {}; this.joined = false; - this.role = null; + this.role = 'none'; this.focusMucJid = null; this.bridgeIsDown = false; this.options = options || {}; @@ -5671,7 +5708,7 @@ ChatRoom.prototype.updateDeviceAvailability = function (devices) { } ] }); -} +}; ChatRoom.prototype.join = function (password, tokenPassword) { if(password) @@ -5773,7 +5810,7 @@ ChatRoom.prototype.onPresence = function (pres) { member.jid = tmp.attr('jid'); member.isFocus = false; if (member.jid - && member.jid.indexOf(this.moderator.getFocusUserJid() + "/") == 0) { + && member.jid.indexOf(this.moderator.getFocusUserJid() + "/") === 0) { member.isFocus = true; } @@ -5822,8 +5859,7 @@ ChatRoom.prototype.onPresence = function (pres) { if (this.role !== member.role) { this.role = member.role; - this.eventEmitter.emit(XMPPEvents.LOCAL_ROLE_CHANGED, - member, this.isModerator()); + this.eventEmitter.emit(XMPPEvents.LOCAL_ROLE_CHANGED, this.role); } if (!this.joined) { this.joined = true; @@ -5846,8 +5882,7 @@ ChatRoom.prototype.onPresence = function (pres) { // Watch role change: if (this.members[from].role != member.role) { this.members[from].role = member.role; - this.eventEmitter.emit(XMPPEvents.MUC_ROLE_CHANGED, - member.role, member.nick); + this.eventEmitter.emit(XMPPEvents.MUC_ROLE_CHANGED, from, member.role); } // store the new display name @@ -5912,7 +5947,7 @@ ChatRoom.prototype.onPresenceUnavailable = function (pres, from) { this.xmpp.disposeConference(false); this.eventEmitter.emit(XMPPEvents.MUC_DESTROYED, reason); - delete this.connection.emuc.rooms[Strophe.getBareJidFromJid(jid)]; + delete this.connection.emuc.rooms[Strophe.getBareJidFromJid(from)]; return true; } @@ -5955,7 +5990,7 @@ ChatRoom.prototype.onMessage = function (msg, from) { var subject = $(msg).find('>subject'); if (subject.length) { var subjectText = subject.text(); - if (subjectText || subjectText == "") { + if (subjectText || subjectText === "") { this.eventEmitter.emit(XMPPEvents.SUBJECT_CHANGED, subjectText); logger.log("Subject is changed to " + subjectText); } @@ -5980,7 +6015,7 @@ ChatRoom.prototype.onMessage = function (msg, from) { this.eventEmitter.emit(XMPPEvents.MESSAGE_RECEIVED, from, nick, txt, this.myroomjid, stamp); } -} +}; ChatRoom.prototype.onPresenceError = function (pres, from) { if ($(pres).find('>error[type="auth"]>not-authorized[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]').length) { @@ -6059,13 +6094,13 @@ ChatRoom.prototype.removeFromPresence = function (key) { ChatRoom.prototype.addPresenceListener = function (name, handler) { this.presHandlers[name] = handler; -} +}; ChatRoom.prototype.removePresenceListener = function (name) { delete this.presHandlers[name]; -} +}; -ChatRoom.prototype.isModerator = function (jid) { +ChatRoom.prototype.isModerator = function () { return this.role === 'moderator'; }; @@ -6085,8 +6120,8 @@ ChatRoom.prototype.setJingleSession = function(session){ ChatRoom.prototype.removeStream = function (stream) { if(!this.session) return; - this.session.peerconnection.removeStream(stream) -} + this.session.peerconnection.removeStream(stream); +}; ChatRoom.prototype.switchStreams = function (stream, oldStream, callback, isAudio) { if(this.session) { @@ -6108,14 +6143,14 @@ ChatRoom.prototype.addStream = function (stream, callback) { logger.warn("No conference handler or conference not started yet"); callback(); } -} +}; ChatRoom.prototype.setVideoMute = function (mute, callback, options) { var self = this; var localCallback = function (mute) { self.sendVideoInfoPresence(mute); if(callback) - callback(mute) + callback(mute); }; if(this.session) @@ -6148,7 +6183,7 @@ ChatRoom.prototype.addAudioInfoToPresence = function (mute) { {attributes: {"audions": "http://jitsi.org/jitmeet/audio"}, value: mute.toString()}); -} +}; ChatRoom.prototype.sendAudioInfoPresence = function(mute, callback) { this.addAudioInfoToPresence(mute); @@ -6165,7 +6200,7 @@ ChatRoom.prototype.addVideoInfoToPresence = function (mute) { {attributes: {"videons": "http://jitsi.org/jitmeet/video"}, value: mute.toString()}); -} +}; ChatRoom.prototype.sendVideoInfoPresence = function (mute) { @@ -6198,7 +6233,7 @@ ChatRoom.prototype.remoteStreamAdded = function(data, sid, thessrc) { } this.eventEmitter.emit(XMPPEvents.REMOTE_STREAM_RECEIVED, data, sid, thessrc); -} +}; ChatRoom.prototype.getJidBySSRC = function (ssrc) { if (!this.session) diff --git a/modules/xmpp/ChatRoom.js b/modules/xmpp/ChatRoom.js index 48786766b..2c84b6902 100644 --- a/modules/xmpp/ChatRoom.js +++ b/modules/xmpp/ChatRoom.js @@ -69,7 +69,7 @@ function ChatRoom(connection, jid, password, XMPP, options) { this.presMap = {}; this.presHandlers = {}; this.joined = false; - this.role = null; + this.role = 'none'; this.focusMucJid = null; this.bridgeIsDown = false; this.options = options || {}; @@ -257,8 +257,7 @@ ChatRoom.prototype.onPresence = function (pres) { if (this.role !== member.role) { this.role = member.role; - this.eventEmitter.emit(XMPPEvents.LOCAL_ROLE_CHANGED, - member, this.isModerator()); + this.eventEmitter.emit(XMPPEvents.LOCAL_ROLE_CHANGED, this.role); } if (!this.joined) { this.joined = true; @@ -281,8 +280,7 @@ ChatRoom.prototype.onPresence = function (pres) { // Watch role change: if (this.members[from].role != member.role) { this.members[from].role = member.role; - this.eventEmitter.emit(XMPPEvents.MUC_ROLE_CHANGED, - member.role, member.nick); + this.eventEmitter.emit(XMPPEvents.MUC_ROLE_CHANGED, from, member.role); } // store the new display name @@ -500,7 +498,7 @@ ChatRoom.prototype.removePresenceListener = function (name) { delete this.presHandlers[name]; }; -ChatRoom.prototype.isModerator = function (jid) { +ChatRoom.prototype.isModerator = function () { return this.role === 'moderator'; }; From 6de7caa9ce6547af0873d1c383da74a864016c46 Mon Sep 17 00:00:00 2001 From: isymchych Date: Tue, 8 Dec 2015 16:32:01 +0200 Subject: [PATCH 8/8] fix issues in RTCUtils --- lib-jitsi-meet.js | 37 +++++++++++++++++++++---------------- modules/RTC/RTCUtils.js | 37 +++++++++++++++++++++---------------- 2 files changed, 42 insertions(+), 32 deletions(-) diff --git a/lib-jitsi-meet.js b/lib-jitsi-meet.js index e1da6a8aa..7919352d8 100644 --- a/lib-jitsi-meet.js +++ b/lib-jitsi-meet.js @@ -2146,7 +2146,12 @@ isAndroid = navigator.userAgent.indexOf('Android') != -1; module.exports = RTCBrowserType; },{}],18:[function(require,module,exports){ (function (__filename){ -/* global config, require, attachMediaStream, getUserMedia */ +/* global config, require, attachMediaStream, getUserMedia, + RTCPeerConnection, RTCSessionDescription, RTCIceCandidate, MediaStreamTrack, + mozRTCPeerConnection, mozRTCSessionDescription, mozRTCIceCandidate, + webkitRTCPeerConnection, webkitMediaStream, webkitURL +*/ +/* jshint -W101 */ var logger = require("jitsi-meet-logger").getLogger(__filename); var RTCBrowserType = require("./RTCBrowserType"); @@ -2163,7 +2168,7 @@ var eventEmitter = new EventEmitter(); var devices = { audio: true, video: true -} +}; var rtcReady = false; @@ -2343,7 +2348,7 @@ function onReady (options, GUM) { rtcReady = true; eventEmitter.emit(RTCEvents.RTC_READY, true); screenObtainer.init(eventEmitter, options, GUM); -}; +} /** * Apply function with arguments if function exists. @@ -2471,8 +2476,8 @@ function enumerateDevicesThroughMediaStreamTrack (callback) { } function obtainDevices(options) { - if(!options.devices || options.devices.length === 0) { - return options.successCallback(streams); + if (!options.devices || options.devices.length === 0) { + return options.successCallback(options.streams); } var device = options.devices.splice(0, 1); @@ -2512,8 +2517,8 @@ function handleLocalStream(streams, resolution) { var videoTracks = audioVideo.getVideoTracks(); if(videoTracks.length) { videoStream = new webkitMediaStream(); - for (i = 0; i < videoTracks.length; i++) { - videoStream.addTrack(videoTracks[i]); + for (var j = 0; j < videoTracks.length; j++) { + videoStream.addTrack(videoTracks[j]); } } } @@ -2652,7 +2657,7 @@ var RTCUtils = { //AdapterJS.WebRTCPlugin.setLogLevel( // AdapterJS.WebRTCPlugin.PLUGIN_LOG_LEVELS.VERBOSE); - + var self = this; AdapterJS.webRTCReady(function (isPlugin) { self.peerconnection = RTCPeerConnection; @@ -2710,7 +2715,7 @@ var RTCUtils = { // Call onReady() if Temasys plugin is not used if (!RTCBrowserType.isTemasysPluginUsed()) { - onReady(options, self.getUserMediaWithConstraints); + onReady(options, this.getUserMediaWithConstraints); resolve(); } }.bind(this)); @@ -2729,9 +2734,8 @@ var RTCUtils = { **/ getUserMediaWithConstraints: function ( um, success_callback, failure_callback, options) { options = options || {}; - resolution = options.resolution; - var constraints = getConstraints( - um, options); + var resolution = options.resolution; + var constraints = getConstraints(um, options); logger.info("Get media constraints", constraints); @@ -2788,12 +2792,12 @@ var RTCUtils = { RTCBrowserType.isTemasysPluginUsed()) { var GUM = function (device, s, e) { this.getUserMediaWithConstraints(device, s, e, options); - } + }; var deviceGUM = { "audio": GUM.bind(self, ["audio"]), "video": GUM.bind(self, ["video"]), "desktop": screenObtainer.obtainStream - } + }; // 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 // audio stream separately from the video stream using two distinct GUM @@ -2804,13 +2808,14 @@ var RTCUtils = { // the successCallback method. obtainDevices({ devices: options.devices, + streams: [], successCallback: successCallback, errorCallback: reject, deviceGUM: deviceGUM }); } else { - var hasDesktop = false; - if(hasDesktop = options.devices.indexOf("desktop") !== -1) { + var hasDesktop = options.devices.indexOf('desktop') > -1; + if (hasDesktop) { options.devices.splice(options.devices.indexOf("desktop"), 1); } options.resolution = options.resolution || '360'; diff --git a/modules/RTC/RTCUtils.js b/modules/RTC/RTCUtils.js index 41d11539e..9a3907db8 100644 --- a/modules/RTC/RTCUtils.js +++ b/modules/RTC/RTCUtils.js @@ -1,4 +1,9 @@ -/* global config, require, attachMediaStream, getUserMedia */ +/* global config, require, attachMediaStream, getUserMedia, + RTCPeerConnection, RTCSessionDescription, RTCIceCandidate, MediaStreamTrack, + mozRTCPeerConnection, mozRTCSessionDescription, mozRTCIceCandidate, + webkitRTCPeerConnection, webkitMediaStream, webkitURL +*/ +/* jshint -W101 */ var logger = require("jitsi-meet-logger").getLogger(__filename); var RTCBrowserType = require("./RTCBrowserType"); @@ -15,7 +20,7 @@ var eventEmitter = new EventEmitter(); var devices = { audio: true, video: true -} +}; var rtcReady = false; @@ -195,7 +200,7 @@ function onReady (options, GUM) { rtcReady = true; eventEmitter.emit(RTCEvents.RTC_READY, true); screenObtainer.init(eventEmitter, options, GUM); -}; +} /** * Apply function with arguments if function exists. @@ -323,8 +328,8 @@ function enumerateDevicesThroughMediaStreamTrack (callback) { } function obtainDevices(options) { - if(!options.devices || options.devices.length === 0) { - return options.successCallback(streams); + if (!options.devices || options.devices.length === 0) { + return options.successCallback(options.streams); } var device = options.devices.splice(0, 1); @@ -364,8 +369,8 @@ function handleLocalStream(streams, resolution) { var videoTracks = audioVideo.getVideoTracks(); if(videoTracks.length) { videoStream = new webkitMediaStream(); - for (i = 0; i < videoTracks.length; i++) { - videoStream.addTrack(videoTracks[i]); + for (var j = 0; j < videoTracks.length; j++) { + videoStream.addTrack(videoTracks[j]); } } } @@ -504,7 +509,7 @@ var RTCUtils = { //AdapterJS.WebRTCPlugin.setLogLevel( // AdapterJS.WebRTCPlugin.PLUGIN_LOG_LEVELS.VERBOSE); - + var self = this; AdapterJS.webRTCReady(function (isPlugin) { self.peerconnection = RTCPeerConnection; @@ -562,7 +567,7 @@ var RTCUtils = { // Call onReady() if Temasys plugin is not used if (!RTCBrowserType.isTemasysPluginUsed()) { - onReady(options, self.getUserMediaWithConstraints); + onReady(options, this.getUserMediaWithConstraints); resolve(); } }.bind(this)); @@ -581,9 +586,8 @@ var RTCUtils = { **/ getUserMediaWithConstraints: function ( um, success_callback, failure_callback, options) { options = options || {}; - resolution = options.resolution; - var constraints = getConstraints( - um, options); + var resolution = options.resolution; + var constraints = getConstraints(um, options); logger.info("Get media constraints", constraints); @@ -640,12 +644,12 @@ var RTCUtils = { RTCBrowserType.isTemasysPluginUsed()) { var GUM = function (device, s, e) { this.getUserMediaWithConstraints(device, s, e, options); - } + }; var deviceGUM = { "audio": GUM.bind(self, ["audio"]), "video": GUM.bind(self, ["video"]), "desktop": screenObtainer.obtainStream - } + }; // 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 // audio stream separately from the video stream using two distinct GUM @@ -656,13 +660,14 @@ var RTCUtils = { // the successCallback method. obtainDevices({ devices: options.devices, + streams: [], successCallback: successCallback, errorCallback: reject, deviceGUM: deviceGUM }); } else { - var hasDesktop = false; - if(hasDesktop = options.devices.indexOf("desktop") !== -1) { + var hasDesktop = options.devices.indexOf('desktop') > -1; + if (hasDesktop) { options.devices.splice(options.devices.indexOf("desktop"), 1); } options.resolution = options.resolution || '360';