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 87e8a3f94..662c27f50 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"); @@ -7,6 +8,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'); var JitsiTrackEvents = require("./JitsiTrackEvents"); /** @@ -18,7 +20,6 @@ var JitsiTrackEvents = require("./JitsiTrackEvents"); * @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 @@ -89,7 +95,7 @@ JitsiConference.prototype.on = function (eventId, handler) { JitsiConference.prototype.off = function (eventId, handler) { if(this.eventEmitter) this.eventEmitter.removeListener(eventId, handler); -} +}; // Common aliases for event emitter JitsiConference.prototype.addEventListener = JitsiConference.prototype.on; @@ -104,7 +110,7 @@ JitsiConference.prototype.removeEventListener = JitsiConference.prototype.off; JitsiConference.prototype.addCommandListener = function (command, handler) { if(this.room) this.room.addPresenceListener(command, handler); - } + }; /** * Removes command listener @@ -113,7 +119,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 @@ -122,7 +128,7 @@ JitsiConference.prototype.removeEventListener = JitsiConference.prototype.off; JitsiConference.prototype.sendTextMessage = function (message) { if(this.room) this.room.sendMessage(message); -} +}; /** * Send presence command. @@ -134,7 +140,7 @@ JitsiConference.prototype.sendCommand = function (name, values) { this.room.addToPresence(name, values); this.room.sendPresence(); } -} +}; /** * Send presence command one time. @@ -144,7 +150,7 @@ JitsiConference.prototype.sendCommand = function (name, values) { JitsiConference.prototype.sendCommandOnce = function (name, values) { this.sendCommand(name, values); this.removeCommand(name); -} +}; /** * Send presence command. @@ -155,7 +161,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. @@ -166,7 +172,7 @@ JitsiConference.prototype.setDisplayName = function(name) { this.room.addToPresence("nick", {attributes: {xmlns: 'http://jabber.org/protocol/nick'}, value: name}); this.room.sendPresence(); } -} +}; /** * Adds JitsiLocalTrack object to the conference. @@ -180,20 +186,18 @@ JitsiConference.prototype.addTrack = function (track) { var audioLevelHandler = this._fireAudioLevelChangeEvent.bind(this); track.addEventListener(JitsiTrackEvents.TRACK_MUTE_CHANGED, muteHandler); track.addEventListener(JitsiTrackEvents.TRACK_STOPPED, stopHandler); - track.addEventListener(JitsiTrackEvents.TRACK_AUDIO_LEVEL_CHANGED, - audioLevelHandler); - this.addEventListener(JitsiConferenceEvents.TRACK_REMOVED, function (track) { - track.removeEventListener(JitsiTrackEvents.TRACK_MUTE_CHANGED, - muteHandler); - track.removeEventListener(JitsiTrackEvents.TRACK_STOPPED, - stopHandler); - track.removeEventListener( - JitsiTrackEvents.TRACK_AUDIO_LEVEL_CHANGED, audioLevelHandler); - }) + track.addEventListener(JitsiTrackEvents.TRACK_AUDIO_LEVEL_CHANGED, audioLevelHandler); + this.addEventListener(JitsiConferenceEvents.TRACK_REMOVED, function (someTrack) { + if (someTrack !== track) { + return; + } + track.removeEventListener(JitsiTrackEvents.TRACK_MUTE_CHANGED, muteHandler); + track.removeEventListener(JitsiTrackEvents.TRACK_STOPPED, stopHandler); + track.removeEventListener(JitsiTrackEvents.TRACK_AUDIO_LEVEL_CHANGED, audioLevelHandler); + }); this.eventEmitter.emit(JitsiConferenceEvents.TRACK_ADDED, track); }.bind(this)); - -} +}; /** * Fires TRACK_AUDIO_LEVEL_CHANGED change conference event. @@ -203,7 +207,7 @@ JitsiConference.prototype._fireAudioLevelChangeEvent = function (audioLevel) { this.eventEmitter.emit( JitsiConferenceEvents.TRACK_AUDIO_LEVEL_CHANGED, this.myUserId(), audioLevel); -} +}; /** * Fires TRACK_MUTE_CHANGED change conference event. @@ -222,7 +226,23 @@ JitsiConference.prototype.removeTrack = function (track) { this.rtc.removeLocalStream(track); this.eventEmitter.emit(JitsiConferenceEvents.TRACK_REMOVED, track); }.bind(this)); -} +}; + +/** + * 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. @@ -232,41 +252,138 @@ 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[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.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); + if (!participant) { + return; + } + participant._displayName = displayName; + this.eventEmitter.emit(JitsiConferenceEvents.DISPLAY_NAME_CHANGED, id, displayName); +}; + +JitsiConference.prototype.onTrackAdded = function (track) { + var id = track.getParticipantId(); + var participant = this.getParticipantById(id); + if (!participant) { + return; + } + // add track to JitsiParticipant + participant._tracks.push(track); + + var emitter = this.eventEmitter; + track.addEventListener( + JitsiTrackEvents.TRACK_STOPPED, + function () { + // remove track from JitsiParticipant + var pos = participant._tracks.indexOf(track); + if (pos > -1) { + participant._tracks.splice(pos, 1); + } + emitter.emit(JitsiConferenceEvents.TRACK_REMOVED, track); + } + ); + track.addEventListener( + JitsiTrackEvents.TRACK_MUTE_CHANGED, + function () { + emitter.emit(JitsiConferenceEvents.TRACK_MUTE_CHANGED, track); + } + ); + track.addEventListener( + JitsiTrackEvents.TRACK_AUDIO_LEVEL_CHANGED, + function (audioLevel) { + emitter.emit(JitsiConferenceEvents.TRACK_AUDIO_LEVEL_CHANGED, id, audioLevel); + } + ); + + this.eventEmitter.emit(JitsiConferenceEvents.TRACK_ADDED, 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); + } +}; + +/** + * 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 @@ -274,7 +391,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.connection.xmpp.connection.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. @@ -290,27 +428,9 @@ function setupListeners(conference) { conference.room.addListener(XMPPEvents.REMOTE_STREAM_RECEIVED, function (data, sid, thessrc) { var track = conference.rtc.createRemoteStream(data, sid, thessrc); - if(!track) - return; - conference.eventEmitter.emit(JitsiConferenceEvents.TRACK_ADDED, - track); - track.addEventListener(JitsiTrackEvents.TRACK_STOPPED, - function () { - conference.eventEmitter.emit( - JitsiConferenceEvents.TRACK_REMOVED, track); - }); - track.addEventListener(JitsiTrackEvents.TRACK_MUTE_CHANGED, - function () { - conference.eventEmitter.emit( - JitsiConferenceEvents.TRACK_MUTE_CHANGED, track); - }); - var userId = track.getParitcipantId(); - track.addEventListener(JitsiTrackEvents.TRACK_AUDIO_LEVEL_CHANGED, - function (audioLevel) { - conference.eventEmitter.emit( - JitsiConferenceEvents.TRACK_AUDIO_LEVEL_CHANGED, - userId, audioLevel); - }); + if (track) { + conference.onTrackAdded(track); + } } ); @@ -322,30 +442,24 @@ function setupListeners(conference) { // 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.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.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.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_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.room.addListener(XMPPEvents.CONFERENCE_SETUP_FAILED, function () { conference.eventEmitter.emit(JitsiConferenceEvents.SETUP_FAILED); }); diff --git a/JitsiConferenceEvents.js b/JitsiConferenceEvents.js index 91808064c..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. */ @@ -75,7 +79,11 @@ var JitsiConferenceEvents = { /** * Indicates that conference has been left. */ - CONFERENCE_LEFT: "conference.left" + CONFERENCE_LEFT: "conference.left", + /** + * Indicates that DTMF support changed. + */ + DTMF_SUPPORT_CHANGED: "conference.dtmfSupportChanged" }; module.exports = JitsiConferenceEvents; diff --git a/JitsiParticipant.js b/JitsiParticipant.js index 58104f977..e7c2f34fd 100644 --- a/JitsiParticipant.js +++ b/JitsiParticipant.js @@ -5,6 +5,9 @@ function JitsiParticipant(id, conference, displayName){ this._id = id; this._conference = conference; this._displayName = displayName; + this._supportsDTMF = false; + this._tracks = []; + this._role = 'none'; } /** @@ -12,34 +15,35 @@ 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() { - -} + 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._role === 'moderator'; +}; // Gets a link to an etherpad instance advertised by the participant? //JitsiParticipant.prototype.getEtherpad = function() { @@ -51,15 +55,19 @@ 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); +}; /* * @returns {???} The latest statistics reported by this participant (i.e. info used to populate the GSM bars) @@ -67,70 +75,67 @@ JitsiParticipant.prototype.isVideoMuted = function() { */ JitsiParticipant.prototype.getLatestStats = function() { -} +}; /** * @returns {String} The role of this participant. */ JitsiParticipant.prototype.getRole = function() { - -} + return this._role; +}; /* * @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/doc/API.md b/doc/API.md index 9bddf1e52..22e954c01 100644 --- a/doc/API.md +++ b/doc/API.md @@ -77,6 +77,8 @@ 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)) + - 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. @@ -213,35 +215,49 @@ 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 + +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). 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 diff --git a/doc/example/example.js b/doc/example/example.js index 9f91d4a02..06f82f389 100644 --- a/doc/example/example.js +++ b/doc/example/example.js @@ -50,7 +50,7 @@ function onLocalTracks(tracks) function onRemoteTrack(track) { if(track.isLocal()) return; - 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 35a2ab998..7919352d8 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.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.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); + if (!participant) { + return; + } + participant._displayName = displayName; + this.eventEmitter.emit(JitsiConferenceEvents.DISPLAY_NAME_CHANGED, id, displayName); +}; + +JitsiConference.prototype.onTrackAdded = function (track) { + var id = track.getParticipantId(); + var participant = this.getParticipantById(id); + if (!participant) { + return; + } + // add track to JitsiParticipant + participant._tracks.push(track); + + var emitter = this.eventEmitter; + track.addEventListener( + JitsiTrackEvents.TRACK_STOPPED, + function () { + // remove track from JitsiParticipant + var pos = participant._tracks.indexOf(track); + if (pos > -1) { + participant._tracks.splice(pos, 1); + } + emitter.emit(JitsiConferenceEvents.TRACK_REMOVED, track); + } + ); + track.addEventListener( + JitsiTrackEvents.TRACK_MUTE_CHANGED, + function () { + emitter.emit(JitsiConferenceEvents.TRACK_MUTE_CHANGED, track); + } + ); + track.addEventListener( + JitsiTrackEvents.TRACK_AUDIO_LEVEL_CHANGED, + function (audioLevel) { + emitter.emit(JitsiConferenceEvents.TRACK_AUDIO_LEVEL_CHANGED, id, audioLevel); + } + ); + + this.eventEmitter.emit(JitsiConferenceEvents.TRACK_ADDED, 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); + } +}; + +/** + * 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 @@ -276,7 +393,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.connection.xmpp.connection.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. @@ -292,27 +430,9 @@ function setupListeners(conference) { conference.room.addListener(XMPPEvents.REMOTE_STREAM_RECEIVED, function (data, sid, thessrc) { var track = conference.rtc.createRemoteStream(data, sid, thessrc); - if(!track) - return; - conference.eventEmitter.emit(JitsiConferenceEvents.TRACK_ADDED, - track); - track.addEventListener(JitsiTrackEvents.TRACK_STOPPED, - function () { - conference.eventEmitter.emit( - JitsiConferenceEvents.TRACK_REMOVED, track); - }); - track.addEventListener(JitsiTrackEvents.TRACK_MUTE_CHANGED, - function () { - conference.eventEmitter.emit( - JitsiConferenceEvents.TRACK_MUTE_CHANGED, track); - }); - var userId = track.getParitcipantId(); - track.addEventListener(JitsiTrackEvents.TRACK_AUDIO_LEVEL_CHANGED, - function (audioLevel) { - conference.eventEmitter.emit( - JitsiConferenceEvents.TRACK_AUDIO_LEVEL_CHANGED, - userId, audioLevel); - }); + if (track) { + conference.onTrackAdded(track); + } } ); @@ -324,30 +444,24 @@ function setupListeners(conference) { // 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.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.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.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_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.room.addListener(XMPPEvents.CONFERENCE_SETUP_FAILED, function () { conference.eventEmitter.emit(JitsiConferenceEvents.SETUP_FAILED); }); @@ -393,7 +507,7 @@ function setupListeners(conference) { module.exports = JitsiConference; }).call(this,"/JitsiConference.js") -},{"./JitsiConferenceEvents":3,"./JitsiParticipant":8,"./JitsiTrackEvents":10,"./modules/RTC/RTC":15,"./modules/statistics/statistics":23,"./service/RTC/RTCEvents":82,"./service/xmpp/XMPPEvents":88,"events":42,"jitsi-meet-logger":46}],2:[function(require,module,exports){ +},{"./JitsiConferenceEvents":3,"./JitsiParticipant":8,"./JitsiTrackEvents":10,"./modules/DTMF/JitsiDTMFManager":11,"./modules/RTC/RTC":16,"./modules/statistics/statistics":24,"./service/RTC/RTCEvents":79,"./service/xmpp/XMPPEvents":85,"events":43,"jitsi-meet-logger":47}],2:[function(require,module,exports){ /** * Enumeration with the errors for the conference. * @type {{string: string}} @@ -445,6 +559,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. */ @@ -497,7 +615,11 @@ var JitsiConferenceEvents = { /** * Indicates that conference has been left. */ - CONFERENCE_LEFT: "conference.left" + CONFERENCE_LEFT: "conference.left", + /** + * Indicates that DTMF support changed. + */ + DTMF_SUPPORT_CHANGED: "conference.dtmfSupportChanged" }; module.exports = JitsiConferenceEvents; @@ -603,7 +725,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}} @@ -737,7 +859,7 @@ window.Promise = window.Promise || require("es6-promise").Promise; module.exports = LibJitsiMeet; -},{"./JitsiConferenceErrors":2,"./JitsiConferenceEvents":3,"./JitsiConnection":4,"./JitsiConnectionErrors":5,"./JitsiConnectionEvents":6,"./JitsiTrackErrors":9,"./JitsiTrackEvents":10,"./modules/RTC/RTC":15,"./modules/statistics/statistics":23,"es6-promise":44,"jitsi-meet-logger":46}],8:[function(require,module,exports){ +},{"./JitsiConferenceErrors":2,"./JitsiConferenceEvents":3,"./JitsiConnection":4,"./JitsiConnectionErrors":5,"./JitsiConnectionEvents":6,"./JitsiTrackErrors":9,"./JitsiTrackEvents":10,"./modules/RTC/RTC":16,"./modules/statistics/statistics":24,"es6-promise":45,"jitsi-meet-logger":47}],8:[function(require,module,exports){ /** * Represents a participant in (a member of) a conference. */ @@ -745,6 +867,9 @@ function JitsiParticipant(id, conference, displayName){ this._id = id; this._conference = conference; this._displayName = displayName; + this._supportsDTMF = false; + this._tracks = []; + this._role = 'none'; } /** @@ -752,34 +877,35 @@ 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() { - -} + 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._role === 'moderator'; +}; // Gets a link to an etherpad instance advertised by the participant? //JitsiParticipant.prototype.getEtherpad = function() { @@ -791,15 +917,19 @@ 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); +}; /* * @returns {???} The latest statistics reported by this participant (i.e. info used to populate the GSM bars) @@ -807,73 +937,70 @@ JitsiParticipant.prototype.isVideoMuted = function() { */ JitsiParticipant.prototype.getLatestStats = function() { -} +}; /** * @returns {String} The role of this participant. */ JitsiParticipant.prototype.getRole = function() { - -} + return this._role; +}; /* * @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; },{}],9:[function(require,module,exports){ module.exports = { @@ -921,6 +1048,25 @@ module.exports = JitsiTrackEvents; },{}],11:[function(require,module,exports){ (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)); +}; + +}).call(this,"/modules/DTMF/JitsiDTMFManager.js") +},{"jitsi-meet-logger":47}],12:[function(require,module,exports){ +(function (__filename){ /* global config, APP, Strophe */ // cache datachannels to avoid garbage collection @@ -1135,7 +1281,7 @@ 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":79,"jitsi-meet-logger":47}],13:[function(require,module,exports){ var JitsiTrack = require("./JitsiTrack"); var RTCBrowserType = require("./RTCBrowserType"); var JitsiTrackEvents = require('../../JitsiTrackEvents'); @@ -1297,7 +1443,7 @@ JitsiLocalTrack.prototype.isLocal = function () { module.exports = JitsiLocalTrack; -},{"../../JitsiTrackEvents":10,"./JitsiTrack":14,"./RTCBrowserType":16,"./RTCUtils":17}],13:[function(require,module,exports){ +},{"../../JitsiTrackEvents":10,"./JitsiTrack":15,"./RTCBrowserType":17,"./RTCUtils":18}],14:[function(require,module,exports){ var JitsiTrack = require("./JitsiTrack"); var JitsiTrackEvents = require("../../JitsiTrackEvents"); @@ -1353,7 +1499,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); }; @@ -1370,7 +1516,7 @@ delete JitsiRemoteTrack.prototype.start; module.exports = JitsiRemoteTrack; -},{"../../JitsiTrackEvents":10,"./JitsiTrack":14}],14:[function(require,module,exports){ +},{"../../JitsiTrackEvents":10,"./JitsiTrack":15}],15:[function(require,module,exports){ var RTCBrowserType = require("./RTCBrowserType"); var JitsiTrackEvents = require("../../JitsiTrackEvents"); var EventEmitter = require("events"); @@ -1469,6 +1615,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 (?). */ @@ -1601,7 +1761,7 @@ JitsiTrack.prototype.setAudioLevel = function (audioLevel) { module.exports = JitsiTrack; -},{"../../JitsiTrackEvents":10,"./RTCBrowserType":16,"./RTCUtils":17,"events":42}],15:[function(require,module,exports){ +},{"../../JitsiTrackEvents":10,"./RTCBrowserType":17,"./RTCUtils":18,"events":43}],16:[function(require,module,exports){ /* global APP */ var EventEmitter = require("events"); var RTCBrowserType = require("./RTCBrowserType"); @@ -1812,7 +1972,7 @@ RTC.prototype.setAudioLevel = function (jid, audioLevel) { } module.exports = RTC; -},{"../../service/RTC/MediaStreamTypes":81,"../../service/RTC/RTCEvents.js":82,"../../service/desktopsharing/DesktopSharingEventTypes":85,"./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/desktopsharing/DesktopSharingEventTypes":82,"./DataChannels":12,"./JitsiLocalTrack.js":13,"./JitsiRemoteTrack.js":14,"./JitsiTrack":15,"./RTCBrowserType":17,"./RTCUtils.js":18,"events":43}],17:[function(require,module,exports){ var currentBrowser; @@ -1984,9 +2144,14 @@ 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 */ +/* 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"); @@ -2003,7 +2168,7 @@ var eventEmitter = new EventEmitter(); var devices = { audio: true, video: true -} +}; var rtcReady = false; @@ -2183,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. @@ -2311,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); @@ -2352,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]); } } } @@ -2492,7 +2657,7 @@ var RTCUtils = { //AdapterJS.WebRTCPlugin.setLogLevel( // AdapterJS.WebRTCPlugin.PLUGIN_LOG_LEVELS.VERBOSE); - + var self = this; AdapterJS.webRTCReady(function (isPlugin) { self.peerconnection = RTCPeerConnection; @@ -2550,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)); @@ -2569,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); @@ -2628,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 @@ -2644,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'; @@ -2734,7 +2899,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){ +},{"../../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 */ @@ -3156,7 +3321,7 @@ function initFirefoxExtensionDetection(options) { module.exports = ScreenObtainer; }).call(this,"/modules/RTC/ScreenObtainer.js") -},{"../../service/desktopsharing/DesktopSharingEventTypes":85,"./RTCBrowserType":16,"./adapter.screenshare":19,"jitsi-meet-logger":46}],19:[function(require,module,exports){ +},{"../../service/desktopsharing/DesktopSharingEventTypes":82,"./RTCBrowserType":17,"./adapter.screenshare":20,"jitsi-meet-logger":47}],20:[function(require,module,exports){ (function (__filename){ /*! adapterjs - v0.12.0 - 2015-09-04 */ var console = require("jitsi-meet-logger").getLogger(__filename); @@ -4329,7 +4494,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); @@ -4415,7 +4580,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. @@ -4540,7 +4705,7 @@ LocalStatsCollector.prototype.stop = function () { module.exports = LocalStatsCollector; -},{"../RTC/RTCBrowserType":16}],22:[function(require,module,exports){ +},{"../RTC/RTCBrowserType":17}],23:[function(require,module,exports){ (function (__filename){ /* global require, ssrc2jid */ /* jshint -W117 */ @@ -5267,7 +5432,7 @@ StatsCollector.prototype.processAudioLevelReport = function () { }; }).call(this,"/modules/statistics/RTPStatsCollector.js") -},{"../../service/statistics/Events":86,"../RTC/RTCBrowserType":16,"jitsi-meet-logger":46}],23:[function(require,module,exports){ +},{"../../service/statistics/Events":83,"../RTC/RTCBrowserType":17,"jitsi-meet-logger":47}],24:[function(require,module,exports){ /* global require, APP */ var LocalStats = require("./LocalStatsCollector.js"); var RTPStats = require("./RTPStatsCollector.js"); @@ -5425,7 +5590,7 @@ Statistics.LOCAL_JID = require("../../service/statistics/constants").LOCAL_JID; module.exports = Statistics; -},{"../../service/statistics/Events":86,"../../service/statistics/constants":87,"./LocalStatsCollector.js":21,"./RTPStatsCollector.js":22,"events":42}],24:[function(require,module,exports){ +},{"../../service/statistics/Events":83,"../../service/statistics/constants":84,"./LocalStatsCollector.js":22,"./RTPStatsCollector.js":23,"events":43}],25:[function(require,module,exports){ /** /** * @const @@ -5500,9 +5665,10 @@ var RandomUtil = { module.exports = RandomUtil; -},{}],25:[function(require,module,exports){ +},{}],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"); @@ -5513,23 +5679,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; @@ -5571,7 +5738,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 || {}; @@ -5608,7 +5775,7 @@ ChatRoom.prototype.updateDeviceAvailability = function (devices) { } ] }); -} +}; ChatRoom.prototype.join = function (password, tokenPassword) { if(password) @@ -5710,7 +5877,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; } @@ -5759,8 +5926,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; @@ -5783,8 +5949,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 @@ -5849,7 +6014,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; } @@ -5892,7 +6057,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); } @@ -5917,7 +6082,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) { @@ -5996,13 +6161,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'; }; @@ -6023,7 +6188,7 @@ ChatRoom.prototype.removeStream = function (stream, callback) { if(!this.session) return; this.session.removeStream(stream, callback); -} +}; ChatRoom.prototype.switchStreams = function (stream, oldStream, callback, isAudio) { if(this.session) { @@ -6045,14 +6210,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) @@ -6085,7 +6250,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); @@ -6102,7 +6267,7 @@ ChatRoom.prototype.addVideoInfoToPresence = function (mute) { {attributes: {"videons": "http://jitsi.org/jitmeet/video"}, value: mute.toString()}); -} +}; ChatRoom.prototype.sendVideoInfoPresence = function (mute) { @@ -6135,7 +6300,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) @@ -6146,7 +6311,7 @@ ChatRoom.prototype.getJidBySSRC = function (ssrc) { module.exports = ChatRoom; }).call(this,"/modules/xmpp/ChatRoom.js") -},{"../../service/xmpp/XMPPEvents":88,"./moderator":33,"events":42,"jitsi-meet-logger":46}],26:[function(require,module,exports){ +},{"../../service/xmpp/XMPPEvents":85,"./moderator":34,"events":43,"jitsi-meet-logger":47}],27:[function(require,module,exports){ (function (__filename){ /* * JingleSession provides an API to manage a single Jingle session. We will @@ -6282,7 +6447,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 */ @@ -7909,7 +8074,7 @@ JingleSessionPC.prototype.remoteStreamAdded = function (data, times) { module.exports = JingleSessionPC; }).call(this,"/modules/xmpp/JingleSessionPC.js") -},{"../../service/xmpp/XMPPEvents":88,"../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":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); @@ -8206,7 +8371,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 */ @@ -8860,7 +9025,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) @@ -9029,9 +9194,8 @@ 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"); @@ -9394,10 +9558,11 @@ SDPUtil = { return line + '\r\n'; } }; + 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'); @@ -9607,45 +9772,49 @@ var normalizePlanB = function(desc) { }); }; -if (TraceablePeerConnection.prototype.__defineGetter__ !== undefined) { - TraceablePeerConnection.prototype.__defineGetter__( - 'signalingState', - function() { return this.peerconnection.signalingState; }); - TraceablePeerConnection.prototype.__defineGetter__( - 'iceConnectionState', - function() { return this.peerconnection.iceConnectionState; }); - TraceablePeerConnection.prototype.__defineGetter__( - 'localDescription', - function() { - var desc = this.peerconnection.localDescription; +var getters = { + signalingState: function () { + return this.peerconnection.signalingState; + }, + iceConnectionState: function () { + return this.peerconnection.iceConnectionState; + }, + localDescription: function() { + var desc = this.peerconnection.localDescription; // FIXME this should probably be after the Unified Plan -> Plan B // transformation. - desc = SSRCReplacement.mungeLocalVideoSSRC(desc); + desc = SSRCReplacement.mungeLocalVideoSSRC(desc); - this.trace('getLocalDescription::preTransform', dumpSDP(desc)); + this.trace('getLocalDescription::preTransform', dumpSDP(desc)); - // if we're running on FF, transform to Plan B first. - if (RTCBrowserType.usesUnifiedPlan()) { - desc = this.interop.toPlanB(desc); - this.trace('getLocalDescription::postTransform (Plan B)', dumpSDP(desc)); - } - return desc; - }); - TraceablePeerConnection.prototype.__defineGetter__( - 'remoteDescription', - function() { - var desc = this.peerconnection.remoteDescription; - this.trace('getRemoteDescription::preTransform', dumpSDP(desc)); + // if we're running on FF, transform to Plan B first. + if (RTCBrowserType.usesUnifiedPlan()) { + desc = this.interop.toPlanB(desc); + this.trace('getLocalDescription::postTransform (Plan B)', dumpSDP(desc)); + } + return desc; + }, + remoteDescription: function() { + var desc = this.peerconnection.remoteDescription; + this.trace('getRemoteDescription::preTransform', dumpSDP(desc)); - // if we're running on FF, transform to Plan B first. - if (RTCBrowserType.usesUnifiedPlan()) { - desc = this.interop.toPlanB(desc); - this.trace('getRemoteDescription::postTransform (Plan B)', dumpSDP(desc)); - } - return desc; - }); -} + // if we're running on FF, transform to Plan B first. + if (RTCBrowserType.usesUnifiedPlan()) { + desc = this.interop.toPlanB(desc); + this.trace('getRemoteDescription::postTransform (Plan B)', dumpSDP(desc)); + } + return desc; + } +}; +Object.keys(getters).forEach(function (prop) { + Object.defineProperty( + TraceablePeerConnection.prototype, + prop, { + get: getters[prop] + } + ); +}); TraceablePeerConnection.prototype.addStream = function (stream) { this.trace('addStream', stream.id); @@ -9846,7 +10015,7 @@ TraceablePeerConnection.prototype.getStats = function(callback, errback) { module.exports = TraceablePeerConnection; }).call(this,"/modules/xmpp/TraceablePeerConnection.js") -},{"../../service/xmpp/XMPPEvents":88,"../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":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 */ @@ -10283,7 +10452,7 @@ module.exports = Moderator; }).call(this,"/modules/xmpp/moderator.js") -},{"../../service/authentication/AuthenticationEvents":84,"../../service/xmpp/XMPPEvents":88,"../settings/Settings":20,"jitsi-meet-logger":46}],34:[function(require,module,exports){ +},{"../../service/authentication/AuthenticationEvents":81,"../../service/xmpp/XMPPEvents":85,"../settings/Settings":21,"jitsi-meet-logger":47}],35:[function(require,module,exports){ (function (__filename){ /* jshint -W117 */ /* a simple MUC connection plugin @@ -10380,7 +10549,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 */ @@ -10677,7 +10846,7 @@ module.exports = function(XMPP, eventEmitter) { }).call(this,"/modules/xmpp/strophe.jingle.js") -},{"../../service/xmpp/XMPPEvents":88,"../RTC/RTCBrowserType":16,"./JingleSessionPC":27,"jitsi-meet-logger":46}],36:[function(require,module,exports){ +},{"../../service/xmpp/XMPPEvents":85,"../RTC/RTCBrowserType":17,"./JingleSessionPC":28,"jitsi-meet-logger":47}],37:[function(require,module,exports){ /* global Strophe */ module.exports = function () { @@ -10698,7 +10867,7 @@ module.exports = function () { } }); }; -},{}],37:[function(require,module,exports){ +},{}],38:[function(require,module,exports){ (function (__filename){ /* global $, $iq, Strophe */ @@ -10825,7 +10994,7 @@ module.exports = function (XMPP, eventEmitter) { }; }).call(this,"/modules/xmpp/strophe.ping.js") -},{"../../service/xmpp/XMPPEvents":88,"jitsi-meet-logger":46}],38:[function(require,module,exports){ +},{"../../service/xmpp/XMPPEvents":85,"jitsi-meet-logger":47}],39:[function(require,module,exports){ (function (__filename){ /* jshint -W117 */ var logger = require("jitsi-meet-logger").getLogger(__filename); @@ -10927,7 +11096,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 */ /** @@ -10976,7 +11145,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*/ @@ -11283,7 +11452,7 @@ XMPP.prototype.disconnect = function () { module.exports = XMPP; }).call(this,"/modules/xmpp/xmpp.js") -},{"../../JitsiConnectionErrors":5,"../../JitsiConnectionEvents":6,"../../service/RTC/RTCEvents":82,"../../service/xmpp/XMPPEvents":88,"../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/xmpp/XMPPEvents":85,"../RTC/RTC":16,"./strophe.emuc":35,"./strophe.jingle":36,"./strophe.logger":37,"./strophe.ping":38,"./strophe.rayo":39,"./strophe.util":40,"events":43,"jitsi-meet-logger":47,"pako":48}],42:[function(require,module,exports){ (function (process){ /*! * async @@ -12410,7 +12579,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 @@ -12713,7 +12882,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 = {}; @@ -12806,7 +12975,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+. @@ -13777,7 +13946,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"); @@ -13921,7 +14090,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"); @@ -14006,7 +14175,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'; @@ -14022,7 +14191,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'; @@ -14400,7 +14569,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'; @@ -14802,7 +14971,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'; @@ -14906,7 +15075,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'; @@ -15093,7 +15262,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. @@ -15127,7 +15296,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 */ @@ -15176,7 +15345,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. @@ -15219,7 +15388,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'); @@ -16986,7 +17155,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'; @@ -17028,7 +17197,7 @@ function GZheader() { module.exports = GZheader; -},{}],57:[function(require,module,exports){ +},{}],58:[function(require,module,exports){ 'use strict'; // See state defs from inflate.js @@ -17356,7 +17525,7 @@ module.exports = function inflate_fast(strm, start) { return; }; -},{}],58:[function(require,module,exports){ +},{}],59:[function(require,module,exports){ 'use strict'; @@ -18861,7 +19030,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'; @@ -19190,7 +19359,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 = { @@ -19205,7 +19374,7 @@ module.exports = { '-6': 'incompatible version' /* Z_VERSION_ERROR (-6) */ }; -},{}],61:[function(require,module,exports){ +},{}],62:[function(require,module,exports){ 'use strict'; @@ -20406,7 +20575,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'; @@ -20437,7 +20606,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"); @@ -20478,7 +20647,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"); @@ -20496,7 +20665,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"); @@ -21219,7 +21388,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"); @@ -21333,487 +21502,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":75}],68:[function(require,module,exports){ /* Copyright @ 2015 Atlassian Pty Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -22233,7 +21922,7 @@ Simulcast.prototype.mungeLocalDescription = function (desc) { module.exports = Simulcast; -},{"./transform-utils":72,"sdp-transform":74}],72:[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"); @@ -22299,30 +21988,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){ -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){ +},{}],70:[function(require,module,exports){ +var grammar = module.exports = { + v: [{ + name: 'version', + reg: /^(\d*)$/ + }], + o: [{ //o=- 20518 0 IN IP4 203.0.113.1 + // NB: sessionId will be a String in most cases because it is huge + name: 'origin', + reg: /^(\S*) (\d*) (\d*) (\S*) IP(\d) (\S*)/, + names: ['username', 'sessionId', 'sessionVersion', 'netType', 'ipVer', 'address'], + format: "%s %s %d %s IP%d %s" + }], + // default parsing of these only (though some of these feel outdated) + s: [{ name: 'name' }], + i: [{ name: 'description' }], + u: [{ name: 'uri' }], + e: [{ name: 'email' }], + p: [{ name: 'phone' }], + z: [{ name: 'timezones' }], // TODO: this one can actually be parsed properly.. + r: [{ name: 'repeats' }], // TODO: this one can also be parsed properly + //k: [{}], // outdated thing ignored + t: [{ //t=0 0 + name: 'timing', + reg: /^(\d*) (\d*)/, + names: ['start', 'stop'], + format: "%d %d" + }], + c: [{ //c=IN IP4 10.47.197.26 + name: 'connection', + reg: /^IN IP(\d) (\S*)/, + names: ['version', 'ip'], + format: "IN IP%d %s" + }], + b: [{ //b=AS:4000 + push: 'bandwidth', + reg: /^(TIAS|AS|CT|RR|RS):(\d*)/, + names: ['type', 'limit'], + format: "%s:%s" + }], + m: [{ //m=video 51744 RTP/AVP 126 97 98 34 31 + // NB: special - pushes to session + // TODO: rtp/fmtp should be filtered by the payloads found here? + reg: /^(\w*) (\d*) ([\w\/]*)(?: (.*))?/, + names: ['type', 'port', 'protocol', 'payloads'], + format: "%s %d %s %s" + }], + a: [ + { //a=rtpmap:110 opus/48000/2 + push: 'rtp', + reg: /^rtpmap:(\d*) ([\w\-]*)\/(\d*)(?:\s*\/(\S*))?/, + names: ['payload', 'codec', 'rate', 'encoding'], + format: function (o) { + return (o.encoding) ? + "rtpmap:%d %s/%s/%s": + "rtpmap:%d %s/%s"; + } + }, + { //a=fmtp:108 profile-level-id=24;object=23;bitrate=64000 + push: 'fmtp', + reg: /^fmtp:(\d*) (\S*)/, + names: ['payload', 'config'], + format: "fmtp:%d %s" + }, + { //a=control:streamid=0 + name: 'control', + reg: /^control:(.*)/, + format: "control:%s" + }, + { //a=rtcp:65179 IN IP4 193.84.77.194 + name: 'rtcp', + reg: /^rtcp:(\d*)(?: (\S*) IP(\d) (\S*))?/, + names: ['port', 'netType', 'ipVer', 'address'], + format: function (o) { + return (o.address != null) ? + "rtcp:%d %s IP%d %s": + "rtcp:%d"; + } + }, + { //a=rtcp-fb:98 trr-int 100 + push: 'rtcpFbTrrInt', + reg: /^rtcp-fb:(\*|\d*) trr-int (\d*)/, + names: ['payload', 'value'], + format: "rtcp-fb:%d trr-int %d" + }, + { //a=rtcp-fb:98 nack rpsi + push: 'rtcpFb', + reg: /^rtcp-fb:(\*|\d*) ([\w-_]*)(?: ([\w-_]*))?/, + names: ['payload', 'type', 'subtype'], + format: function (o) { + return (o.subtype != null) ? + "rtcp-fb:%s %s %s": + "rtcp-fb:%s %s"; + } + }, + { //a=extmap:2 urn:ietf:params:rtp-hdrext:toffset + //a=extmap:1/recvonly URI-gps-string + push: 'ext', + reg: /^extmap:([\w_\/]*) (\S*)(?: (\S*))?/, + names: ['value', 'uri', 'config'], // value may include "/direction" suffix + format: function (o) { + return (o.config != null) ? + "extmap:%s %s %s": + "extmap:%s %s"; + } + }, + { + //a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:PS1uQCVeeCFCanVmcjkpPywjNWhcYD0mXXtxaVBR|2^20|1:32 + push: 'crypto', + reg: /^crypto:(\d*) ([\w_]*) (\S*)(?: (\S*))?/, + names: ['id', 'suite', 'config', 'sessionConfig'], + format: function (o) { + return (o.sessionConfig != null) ? + "crypto:%d %s %s %s": + "crypto:%d %s %s"; + } + }, + { //a=setup:actpass + name: 'setup', + reg: /^setup:(\w*)/, + format: "setup:%s" + }, + { //a=mid:1 + name: 'mid', + reg: /^mid:([^\s]*)/, + format: "mid:%s" + }, + { //a=msid:0c8b064d-d807-43b4-b434-f92a889d8587 98178685-d409-46e0-8e16-7ef0db0db64a + name: 'msid', + reg: /^msid:(.*)/, + format: "msid:%s" + }, + { //a=ptime:20 + name: 'ptime', + reg: /^ptime:(\d*)/, + format: "ptime:%d" + }, + { //a=maxptime:60 + name: 'maxptime', + reg: /^maxptime:(\d*)/, + format: "maxptime:%d" + }, + { //a=sendrecv + name: 'direction', + reg: /^(sendrecv|recvonly|sendonly|inactive)/ + }, + { //a=ice-lite + name: 'icelite', + reg: /^(ice-lite)/ + }, + { //a=ice-ufrag:F7gI + name: 'iceUfrag', + reg: /^ice-ufrag:(\S*)/, + format: "ice-ufrag:%s" + }, + { //a=ice-pwd:x9cml/YzichV2+XlhiMu8g + name: 'icePwd', + reg: /^ice-pwd:(\S*)/, + format: "ice-pwd:%s" + }, + { //a=fingerprint:SHA-1 00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33 + name: 'fingerprint', + reg: /^fingerprint:(\S*) (\S*)/, + names: ['type', 'hash'], + format: "fingerprint:%s %s" + }, + { + //a=candidate:0 1 UDP 2113667327 203.0.113.1 54400 typ host + //a=candidate:1162875081 1 udp 2113937151 192.168.34.75 60017 typ host generation 0 + //a=candidate:3289912957 2 udp 1845501695 193.84.77.194 60017 typ srflx raddr 192.168.34.75 rport 60017 generation 0 + push:'candidates', + reg: /^candidate:(\S*) (\d*) (\S*) (\d*) (\S*) (\d*) typ (\S*)(?: raddr (\S*) rport (\d*))?(?: generation (\d*))?/, + names: ['foundation', 'component', 'transport', 'priority', 'ip', 'port', 'type', 'raddr', 'rport', 'generation'], + format: function (o) { + var str = "candidate:%s %d %s %d %s %d typ %s"; + // NB: candidate has two optional chunks, so %void middle one if it's missing + str += (o.raddr != null) ? " raddr %s rport %d" : "%v%v"; + if (o.generation != null) { + str += " generation %d"; + } + return str; + } + }, + { //a=end-of-candidates (keep after the candidates line for readability) + name: 'endOfCandidates', + reg: /^(end-of-candidates)/ + }, + { //a=remote-candidates:1 203.0.113.1 54400 2 203.0.113.1 54401 ... + name: 'remoteCandidates', + reg: /^remote-candidates:(.*)/, + format: "remote-candidates:%s" + }, + { //a=ice-options:google-ice + name: 'iceOptions', + reg: /^ice-options:(\S*)/, + format: "ice-options:%s" + }, + { //a=ssrc:2566107569 cname:t9YU8M1UxTF8Y1A1 + push: "ssrcs", + reg: /^ssrc:(\d*) ([\w_]*):(.*)/, + names: ['id', 'attribute', 'value'], + format: "ssrc:%d %s:%s" + }, + { //a=ssrc-group:FEC 1 2 + push: "ssrcGroups", + reg: /^ssrc-group:(\w*) (.*)/, + names: ['semantics', 'ssrcs'], + format: "ssrc-group:%s %s" + }, + { //a=msid-semantic: WMS Jvlam5X3SX1OP6pn20zWogvaKJz5Hjf9OnlV + name: "msidSemantic", + reg: /^msid-semantic:\s?(\w*) (\S*)/, + names: ['semantic', 'token'], + format: "msid-semantic: %s %s" // space after ":" is not accidental + }, + { //a=group:BUNDLE audio video + push: 'groups', + reg: /^group:(\w*) (.*)/, + names: ['type', 'mids'], + format: "group:%s %s" + }, + { //a=rtcp-mux + name: 'rtcpMux', + reg: /^(rtcp-mux)/ + }, + { //a=rtcp-rsize + name: 'rtcpRsize', + reg: /^(rtcp-rsize)/ + }, + { // any a= that we don't understand is kepts verbatim on media.invalid + push: 'invalid', + names: ["value"] + } + ] +}; + +// set sensible defaults to avoid polluting the grammar with boring details +Object.keys(grammar).forEach(function (key) { + var objs = grammar[key]; + objs.forEach(function (obj) { + if (!obj.reg) { + obj.reg = /(.*)/; + } + if (!obj.format) { + obj.format = "%s"; + } + }); +}); + +},{}],71:[function(require,module,exports){ +var parser = require('./parser'); +var writer = require('./writer'); + +exports.write = writer; +exports.parse = parser.parse; +exports.parseFmtpConfig = parser.parseFmtpConfig; +exports.parsePayloads = parser.parsePayloads; +exports.parseRemoteCandidates = parser.parseRemoteCandidates; + +},{"./parser":72,"./writer":73}],72:[function(require,module,exports){ +var toIntIfInt = function (v) { + return String(Number(v)) === v ? Number(v) : v; +}; + +var attachProperties = function (match, location, names, rawName) { + if (rawName && !names) { + location[rawName] = toIntIfInt(match[1]); + } + else { + for (var i = 0; i < names.length; i += 1) { + if (match[i+1] != null) { + location[names[i]] = toIntIfInt(match[i+1]); + } + } + } +}; + +var parseReg = function (obj, location, content) { + var needsBlank = obj.name && obj.names; + if (obj.push && !location[obj.push]) { + location[obj.push] = []; + } + else if (needsBlank && !location[obj.name]) { + location[obj.name] = {}; + } + var keyLocation = obj.push ? + {} : // blank object that will be pushed + needsBlank ? location[obj.name] : location; // otherwise, named location or root + + attachProperties(content.match(obj.reg), keyLocation, obj.names, obj.name); + + if (obj.push) { + location[obj.push].push(keyLocation); + } +}; + +var grammar = require('./grammar'); +var validLine = RegExp.prototype.test.bind(/^([a-z])=(.*)/); + +exports.parse = function (sdp) { + var session = {} + , media = [] + , location = session; // points at where properties go under (one of the above) + + // parse lines we understand + sdp.split(/(\r\n|\r|\n)/).filter(validLine).forEach(function (l) { + var type = l[0]; + var content = l.slice(2); + if (type === 'm') { + media.push({rtp: [], fmtp: []}); + location = media[media.length-1]; // point at latest media line + } + + for (var j = 0; j < (grammar[type] || []).length; j += 1) { + var obj = grammar[type][j]; + if (obj.reg.test(content)) { + return parseReg(obj, location, content); + } + } + }); + + session.media = media; // link it up + return session; +}; + +var fmtpReducer = function (acc, expr) { + var s = expr.split('='); + if (s.length === 2) { + acc[s[0]] = toIntIfInt(s[1]); + } + return acc; +}; + +exports.parseFmtpConfig = function (str) { + return str.split(';').reduce(fmtpReducer, {}); +}; + +exports.parsePayloads = function (str) { + return str.split(' ').map(Number); +}; + +exports.parseRemoteCandidates = function (str) { + var candidates = []; + var parts = str.split(' ').map(toIntIfInt); + for (var i = 0; i < parts.length; i += 3) { + candidates.push({ + component: parts[i], + ip: parts[i + 1], + port: parts[i + 2] + }); + } + return candidates; +}; + +},{"./grammar":70}],73:[function(require,module,exports){ +var grammar = require('./grammar'); + +// customized util.format - discards excess arguments and can void middle ones +var formatRegExp = /%[sdv%]/g; +var format = function (formatStr) { + var i = 1; + var args = arguments; + var len = args.length; + return formatStr.replace(formatRegExp, function (x) { + if (i >= len) { + return x; // missing argument + } + var arg = args[i]; + i += 1; + switch (x) { + case '%%': + return '%'; + case '%s': + return String(arg); + case '%d': + return Number(arg); + case '%v': + return ''; + } + }); + // NB: we discard excess arguments - they are typically undefined from makeLine +}; + +var makeLine = function (type, obj, location) { + var str = obj.format instanceof Function ? + (obj.format(obj.push ? location : location[obj.name])) : + obj.format; + + var args = [type + '=' + str]; + if (obj.names) { + for (var i = 0; i < obj.names.length; i += 1) { + var n = obj.names[i]; + if (obj.name) { + args.push(location[obj.name][n]); + } + else { // for mLine and push attributes + args.push(location[obj.names[i]]); + } + } + } + else { + args.push(location[obj.name]); + } + return format.apply(null, args); +}; + +// RFC specified order +// TODO: extend this with all the rest +var defaultOuterOrder = [ + 'v', 'o', 's', 'i', + 'u', 'e', 'p', 'c', + 'b', 't', 'r', 'z', 'a' +]; +var defaultInnerOrder = ['i', 'c', 'b', 'a']; + + +module.exports = function (session, opts) { + opts = opts || {}; + // ensure certain properties exist + if (session.version == null) { + session.version = 0; // "v=0" must be there (only defined version atm) + } + if (session.name == null) { + session.name = " "; // "s= " must be there if no meaningful name set + } + session.media.forEach(function (mLine) { + if (mLine.payloads == null) { + mLine.payloads = ""; + } + }); + + var outerOrder = opts.outerOrder || defaultOuterOrder; + var innerOrder = opts.innerOrder || defaultInnerOrder; + var sdp = []; + + // loop through outerOrder for matching properties on session + outerOrder.forEach(function (type) { + grammar[type].forEach(function (obj) { + if (obj.name in session && session[obj.name] != null) { + sdp.push(makeLine(type, obj, session)); + } + else if (obj.push in session && session[obj.push] != null) { + session[obj.push].forEach(function (el) { + sdp.push(makeLine(type, obj, el)); + }); + } + }); + }); + + // then for each media line, follow the innerOrder + session.media.forEach(function (mLine) { + sdp.push(makeLine('m', grammar.m[0], mLine)); + + innerOrder.forEach(function (type) { + grammar[type].forEach(function (obj) { + if (obj.name in mLine && mLine[obj.name] != null) { + sdp.push(makeLine(type, obj, mLine)); + } + else if (obj.push in mLine && mLine[obj.push] != null) { + mLine[obj.push].forEach(function (el) { + sdp.push(makeLine(type, obj, el)); + }); + } + }); + }); + }); + + return sdp.join('\r\n') + '\r\n'; +}; + +},{"./grammar":70}],74:[function(require,module,exports){ +var grammar = module.exports = { + v: [{ + name: 'version', + reg: /^(\d*)$/ + }], + o: [{ //o=- 20518 0 IN IP4 203.0.113.1 + // NB: sessionId will be a String in most cases because it is huge + name: 'origin', + reg: /^(\S*) (\d*) (\d*) (\S*) IP(\d) (\S*)/, + names: ['username', 'sessionId', 'sessionVersion', 'netType', 'ipVer', 'address'], + format: "%s %s %d %s IP%d %s" + }], + // default parsing of these only (though some of these feel outdated) + s: [{ name: 'name' }], + i: [{ name: 'description' }], + u: [{ name: 'uri' }], + e: [{ name: 'email' }], + p: [{ name: 'phone' }], + z: [{ name: 'timezones' }], // TODO: this one can actually be parsed properly.. + r: [{ name: 'repeats' }], // TODO: this one can also be parsed properly + //k: [{}], // outdated thing ignored + t: [{ //t=0 0 + name: 'timing', + reg: /^(\d*) (\d*)/, + names: ['start', 'stop'], + format: "%d %d" + }], + c: [{ //c=IN IP4 10.47.197.26 + name: 'connection', + reg: /^IN IP(\d) (\S*)/, + names: ['version', 'ip'], + format: "IN IP%d %s" + }], + b: [{ //b=AS:4000 + push: 'bandwidth', + reg: /^(TIAS|AS|CT|RR|RS):(\d*)/, + names: ['type', 'limit'], + format: "%s:%s" + }], + m: [{ //m=video 51744 RTP/AVP 126 97 98 34 31 + // NB: special - pushes to session + // TODO: rtp/fmtp should be filtered by the payloads found here? + reg: /^(\w*) (\d*) ([\w\/]*)(?: (.*))?/, + names: ['type', 'port', 'protocol', 'payloads'], + format: "%s %d %s %s" + }], + a: [ + { //a=rtpmap:110 opus/48000/2 + push: 'rtp', + reg: /^rtpmap:(\d*) ([\w\-]*)\/(\d*)(?:\s*\/(\S*))?/, + names: ['payload', 'codec', 'rate', 'encoding'], + format: function (o) { + return (o.encoding) ? + "rtpmap:%d %s/%s/%s": + "rtpmap:%d %s/%s"; + } + }, + { + //a=fmtp:108 profile-level-id=24;object=23;bitrate=64000 + //a=fmtp:111 minptime=10; useinbandfec=1 + push: 'fmtp', + reg: /^fmtp:(\d*) ([\S| ]*)/, + names: ['payload', 'config'], + format: "fmtp:%d %s" + }, + { //a=control:streamid=0 + name: 'control', + reg: /^control:(.*)/, + format: "control:%s" + }, + { //a=rtcp:65179 IN IP4 193.84.77.194 + name: 'rtcp', + reg: /^rtcp:(\d*)(?: (\S*) IP(\d) (\S*))?/, + names: ['port', 'netType', 'ipVer', 'address'], + format: function (o) { + return (o.address != null) ? + "rtcp:%d %s IP%d %s": + "rtcp:%d"; + } + }, + { //a=rtcp-fb:98 trr-int 100 + push: 'rtcpFbTrrInt', + reg: /^rtcp-fb:(\*|\d*) trr-int (\d*)/, + names: ['payload', 'value'], + format: "rtcp-fb:%d trr-int %d" + }, + { //a=rtcp-fb:98 nack rpsi + push: 'rtcpFb', + reg: /^rtcp-fb:(\*|\d*) ([\w-_]*)(?: ([\w-_]*))?/, + names: ['payload', 'type', 'subtype'], + format: function (o) { + return (o.subtype != null) ? + "rtcp-fb:%s %s %s": + "rtcp-fb:%s %s"; + } + }, + { //a=extmap:2 urn:ietf:params:rtp-hdrext:toffset + //a=extmap:1/recvonly URI-gps-string + push: 'ext', + reg: /^extmap:([\w_\/]*) (\S*)(?: (\S*))?/, + names: ['value', 'uri', 'config'], // value may include "/direction" suffix + format: function (o) { + return (o.config != null) ? + "extmap:%s %s %s": + "extmap:%s %s"; + } + }, + { + //a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:PS1uQCVeeCFCanVmcjkpPywjNWhcYD0mXXtxaVBR|2^20|1:32 + push: 'crypto', + reg: /^crypto:(\d*) ([\w_]*) (\S*)(?: (\S*))?/, + names: ['id', 'suite', 'config', 'sessionConfig'], + format: function (o) { + return (o.sessionConfig != null) ? + "crypto:%d %s %s %s": + "crypto:%d %s %s"; + } + }, + { //a=setup:actpass + name: 'setup', + reg: /^setup:(\w*)/, + format: "setup:%s" + }, + { //a=mid:1 + name: 'mid', + reg: /^mid:([^\s]*)/, + format: "mid:%s" + }, + { //a=msid:0c8b064d-d807-43b4-b434-f92a889d8587 98178685-d409-46e0-8e16-7ef0db0db64a + name: 'msid', + reg: /^msid:(.*)/, + format: "msid:%s" + }, + { //a=ptime:20 + name: 'ptime', + reg: /^ptime:(\d*)/, + format: "ptime:%d" + }, + { //a=maxptime:60 + name: 'maxptime', + reg: /^maxptime:(\d*)/, + format: "maxptime:%d" + }, + { //a=sendrecv + name: 'direction', + reg: /^(sendrecv|recvonly|sendonly|inactive)/ + }, + { //a=ice-lite + name: 'icelite', + reg: /^(ice-lite)/ + }, + { //a=ice-ufrag:F7gI + name: 'iceUfrag', + reg: /^ice-ufrag:(\S*)/, + format: "ice-ufrag:%s" + }, + { //a=ice-pwd:x9cml/YzichV2+XlhiMu8g + name: 'icePwd', + reg: /^ice-pwd:(\S*)/, + format: "ice-pwd:%s" + }, + { //a=fingerprint:SHA-1 00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33 + name: 'fingerprint', + reg: /^fingerprint:(\S*) (\S*)/, + names: ['type', 'hash'], + format: "fingerprint:%s %s" + }, + { + //a=candidate:0 1 UDP 2113667327 203.0.113.1 54400 typ host + //a=candidate:1162875081 1 udp 2113937151 192.168.34.75 60017 typ host generation 0 + //a=candidate:3289912957 2 udp 1845501695 193.84.77.194 60017 typ srflx raddr 192.168.34.75 rport 60017 generation 0 + push:'candidates', + reg: /^candidate:(\S*) (\d*) (\S*) (\d*) (\S*) (\d*) typ (\S*)(?: raddr (\S*) rport (\d*))?(?: generation (\d*))?/, + names: ['foundation', 'component', 'transport', 'priority', 'ip', 'port', 'type', 'raddr', 'rport', 'generation'], + format: function (o) { + var str = "candidate:%s %d %s %d %s %d typ %s"; + // NB: candidate has two optional chunks, so %void middle one if it's missing + str += (o.raddr != null) ? " raddr %s rport %d" : "%v%v"; + if (o.generation != null) { + str += " generation %d"; + } + return str; + } + }, + { //a=end-of-candidates (keep after the candidates line for readability) + name: 'endOfCandidates', + reg: /^(end-of-candidates)/ + }, + { //a=remote-candidates:1 203.0.113.1 54400 2 203.0.113.1 54401 ... + name: 'remoteCandidates', + reg: /^remote-candidates:(.*)/, + format: "remote-candidates:%s" + }, + { //a=ice-options:google-ice + name: 'iceOptions', + reg: /^ice-options:(\S*)/, + format: "ice-options:%s" + }, + { //a=ssrc:2566107569 cname:t9YU8M1UxTF8Y1A1 + push: "ssrcs", + reg: /^ssrc:(\d*) ([\w_]*):(.*)/, + names: ['id', 'attribute', 'value'], + format: "ssrc:%d %s:%s" + }, + { //a=ssrc-group:FEC 1 2 + push: "ssrcGroups", + reg: /^ssrc-group:(\w*) (.*)/, + names: ['semantics', 'ssrcs'], + format: "ssrc-group:%s %s" + }, + { //a=msid-semantic: WMS Jvlam5X3SX1OP6pn20zWogvaKJz5Hjf9OnlV + name: "msidSemantic", + reg: /^msid-semantic:\s?(\w*) (\S*)/, + names: ['semantic', 'token'], + format: "msid-semantic: %s %s" // space after ":" is not accidental + }, + { //a=group:BUNDLE audio video + push: 'groups', + reg: /^group:(\w*) (.*)/, + names: ['type', 'mids'], + format: "group:%s %s" + }, + { //a=rtcp-mux + name: 'rtcpMux', + reg: /^(rtcp-mux)/ + }, + { //a=rtcp-rsize + name: 'rtcpRsize', + reg: /^(rtcp-rsize)/ + }, + { // any a= that we don't understand is kepts verbatim on media.invalid + push: 'invalid', + names: ["value"] + } + ] +}; + +// set sensible defaults to avoid polluting the grammar with boring details +Object.keys(grammar).forEach(function (key) { + var objs = grammar[key]; + objs.forEach(function (obj) { + if (!obj.reg) { + obj.reg = /(.*)/; + } + if (!obj.format) { + obj.format = "%s"; + } + }); +}); + +},{}],75:[function(require,module,exports){ +arguments[4][71][0].apply(exports,arguments) +},{"./parser":76,"./writer":77,"dup":71}],76:[function(require,module,exports){ +var toIntIfInt = function (v) { + return String(Number(v)) === v ? Number(v) : v; +}; + +var attachProperties = function (match, location, names, rawName) { + if (rawName && !names) { + location[rawName] = toIntIfInt(match[1]); + } + else { + for (var i = 0; i < names.length; i += 1) { + if (match[i+1] != null) { + location[names[i]] = toIntIfInt(match[i+1]); + } + } + } +}; + +var parseReg = function (obj, location, content) { + var needsBlank = obj.name && obj.names; + if (obj.push && !location[obj.push]) { + location[obj.push] = []; + } + else if (needsBlank && !location[obj.name]) { + location[obj.name] = {}; + } + var keyLocation = obj.push ? + {} : // blank object that will be pushed + needsBlank ? location[obj.name] : location; // otherwise, named location or root + + attachProperties(content.match(obj.reg), keyLocation, obj.names, obj.name); + + if (obj.push) { + location[obj.push].push(keyLocation); + } +}; + +var grammar = require('./grammar'); +var validLine = RegExp.prototype.test.bind(/^([a-z])=(.*)/); + +exports.parse = function (sdp) { + var session = {} + , media = [] + , location = session; // points at where properties go under (one of the above) + + // parse lines we understand + sdp.split(/(\r\n|\r|\n)/).filter(validLine).forEach(function (l) { + var type = l[0]; + var content = l.slice(2); + if (type === 'm') { + media.push({rtp: [], fmtp: []}); + location = media[media.length-1]; // point at latest media line + } + + for (var j = 0; j < (grammar[type] || []).length; j += 1) { + var obj = grammar[type][j]; + if (obj.reg.test(content)) { + return parseReg(obj, location, content); + } + } + }); + + session.media = media; // link it up + return session; +}; + +var fmtpReducer = function (acc, expr) { + var s = expr.split('='); + if (s.length === 2) { + acc[s[0]] = toIntIfInt(s[1]); + } + return acc; +}; + +exports.parseFmtpConfig = function (str) { + return str.split(/\;\s?/).reduce(fmtpReducer, {}); +}; + +exports.parsePayloads = function (str) { + return str.split(' ').map(Number); +}; + +exports.parseRemoteCandidates = function (str) { + var candidates = []; + var parts = str.split(' ').map(toIntIfInt); + for (var i = 0; i < parts.length; i += 3) { + candidates.push({ + component: parts[i], + ip: parts[i + 1], + port: parts[i + 2] + }); + } + return candidates; +}; + +},{"./grammar":74}],77:[function(require,module,exports){ +arguments[4][73][0].apply(exports,arguments) +},{"./grammar":74,"dup":73}],78:[function(require,module,exports){ var MediaStreamType = { VIDEO_TYPE: "Video", AUDIO_TYPE: "Audio" }; module.exports = MediaStreamType; -},{}],82:[function(require,module,exports){ +},{}],79:[function(require,module,exports){ var RTCEvents = { RTC_READY: "rtc.ready", DATA_CHANNEL_OPEN: "rtc.data_channel_open", @@ -22333,7 +22826,7 @@ var RTCEvents = { }; module.exports = RTCEvents; -},{}],83:[function(require,module,exports){ +},{}],80:[function(require,module,exports){ var Resolutions = { "1080": { width: 1920, @@ -22387,7 +22880,7 @@ var Resolutions = { } }; module.exports = Resolutions; -},{}],84:[function(require,module,exports){ +},{}],81:[function(require,module,exports){ var AuthenticationEvents = { /** * Event callback arguments: @@ -22401,7 +22894,7 @@ var AuthenticationEvents = { }; module.exports = AuthenticationEvents; -},{}],85:[function(require,module,exports){ +},{}],82:[function(require,module,exports){ var DesktopSharingEventTypes = { INIT: "ds.init", @@ -22417,7 +22910,7 @@ var DesktopSharingEventTypes = { module.exports = DesktopSharingEventTypes; -},{}],86:[function(require,module,exports){ +},{}],83:[function(require,module,exports){ module.exports = { /** * An event carrying connection statistics. @@ -22433,12 +22926,12 @@ module.exports = { STOP: "statistics.stop" }; -},{}],87:[function(require,module,exports){ +},{}],84:[function(require,module,exports){ var Constants = { LOCAL_JID: 'local' }; module.exports = Constants; -},{}],88:[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/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/JitsiRemoteTrack.js b/modules/RTC/JitsiRemoteTrack.js index 4d44d4191..0558a4b1d 100644 --- a/modules/RTC/JitsiRemoteTrack.js +++ b/modules/RTC/JitsiRemoteTrack.js @@ -53,7 +53,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/RTC/JitsiTrack.js b/modules/RTC/JitsiTrack.js index 82d41d51e..a0b958bfc 100644 --- a/modules/RTC/JitsiTrack.js +++ b/modules/RTC/JitsiTrack.js @@ -96,6 +96,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/RTC/RTCUtils.js b/modules/RTC/RTCUtils.js index 98f013db8..b456f2e7a 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; @@ -197,7 +202,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. @@ -325,8 +330,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); @@ -366,8 +371,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]); } } } @@ -506,7 +511,7 @@ var RTCUtils = { //AdapterJS.WebRTCPlugin.setLogLevel( // AdapterJS.WebRTCPlugin.PLUGIN_LOG_LEVELS.VERBOSE); - + var self = this; AdapterJS.webRTCReady(function (isPlugin) { self.peerconnection = RTCPeerConnection; @@ -564,7 +569,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)); @@ -583,9 +588,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); @@ -642,12 +646,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 @@ -658,13 +662,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/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/modules/xmpp/ChatRoom.js b/modules/xmpp/ChatRoom.js index 3e4645102..5484e307d 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; @@ -67,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 || {}; @@ -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; } @@ -255,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; @@ -279,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 @@ -345,7 +345,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 +388,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 +413,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,13 +492,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'; }; @@ -519,7 +519,7 @@ ChatRoom.prototype.removeStream = function (stream, callback) { if(!this.session) return; this.session.removeStream(stream, callback); -} +}; ChatRoom.prototype.switchStreams = function (stream, oldStream, callback, isAudio) { if(this.session) { @@ -541,14 +541,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 +581,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 +598,7 @@ ChatRoom.prototype.addVideoInfoToPresence = function (mute) { {attributes: {"videons": "http://jitsi.org/jitmeet/video"}, value: mute.toString()}); -} +}; ChatRoom.prototype.sendVideoInfoPresence = function (mute) { @@ -631,7 +631,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/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; 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;