0 &&
+ obj.values.animationCountOnSteps &&
+ obj.values.animationCountOnSteps[step] <= animation_step) {
+ animation_step = obj.values.animationCountOnSteps[step];
+ }
+ // jump to animation steps by calling flyToNextStep()
+ function doAnimationSteps() {
+ if (obj.values.isMoving == true) {
+ setTimeout(doAnimationSteps, 100); // wait until the flight ends
+ return;
+ }
+ while (animation_step-- > 0) {
+ obj.flyToNextStep(); // do the animation steps
+ }
+ }
+ setTimeout(doAnimationSteps, 200); // 200ms is the internal "reporting" time
+ // jump to the step
+ return this.sendMessage({
+ 'action': 'present',
+ 'data': ['moveToStep', step]
+ });
+ };
+
+ PreziPlayer.prototype.toObject = /* toObject is DEPRECATED */
+ PreziPlayer.prototype.flyToObject = function(objectId) {
+ return this.sendMessage({
+ 'action': 'present',
+ 'data': ['moveToObject', objectId]
+ });
+ };
+
+ PreziPlayer.prototype.play = function(defaultDelay) {
+ return this.sendMessage({
+ 'action': 'present',
+ 'data': ['startAutoPlay', defaultDelay]
+ });
+ };
+
+ PreziPlayer.prototype.stop = function() {
+ return this.sendMessage({
+ 'action': 'present',
+ 'data': ['stopAutoPlay']
+ });
+ };
+
+ PreziPlayer.prototype.pause = function(defaultDelay) {
+ return this.sendMessage({
+ 'action': 'present',
+ 'data': ['pauseAutoPlay', defaultDelay]
+ });
+ };
+
+ PreziPlayer.prototype.getCurrentStep = function() {
+ return this.values.currentStep;
+ };
+
+ PreziPlayer.prototype.getCurrentAnimationStep = function() {
+ return this.values.currentAnimationStep;
+ };
+
+ PreziPlayer.prototype.getCurrentObject = function() {
+ return this.values.currentObject;
+ };
+
+ PreziPlayer.prototype.getStatus = function() {
+ return this.values.status;
+ };
+
+ PreziPlayer.prototype.isPlaying = function() {
+ return this.values.isAutoPlaying;
+ };
+
+ PreziPlayer.prototype.getStepCount = function() {
+ return this.values.stepCount;
+ };
+
+ PreziPlayer.prototype.getAnimationCountOnSteps = function() {
+ return this.values.animationCountOnSteps;
+ };
+
+ PreziPlayer.prototype.getTitle = function() {
+ return this.values.title;
+ };
+
+ PreziPlayer.prototype.setDimensions = function(dims) {
+ for (var parameter in dims) {
+ this.iframe[parameter] = dims[parameter];
+ }
+ }
+
+ PreziPlayer.prototype.getDimensions = function() {
+ return {
+ width: parseInt(this.iframe.width, 10),
+ height: parseInt(this.iframe.height, 10)
+ }
+ }
+
+ PreziPlayer.prototype.on = function(event, callback) {
+ this.callbacks.push({
+ event: event,
+ callback: callback
+ });
+ };
+
+ PreziPlayer.prototype.off = function(event, callback) {
+ var j, item;
+ if (event === undefined) {
+ this.callbacks = [];
+ }
+ j = this.callbacks.length;
+ while (j--) {
+ item = this.callbacks[j];
+ if (item && item.event === event && (callback === undefined || item.callback === callback)){
+ this.callbacks.splice(j, 1);
+ }
+ }
+ };
+
+ if (window.addEventListener) {
+ window.addEventListener('message', PreziPlayer.messageReceived, false);
+ } else {
+ window.attachEvent('onmessage', PreziPlayer.messageReceived);
+ }
+
+ return PreziPlayer;
+
+ })();
+
+})();
+
+module.exports = PreziPlayer;
+
+},{}],20:[function(require,module,exports){
+var Chat = require("./chat/Chat");
+var ContactList = require("./contactlist/ContactList");
+var Settings = require("./../../settings/Settings");
+var SettingsMenu = require("./settings/SettingsMenu");
+var VideoLayout = require("../videolayout/VideoLayout");
+var ToolbarToggler = require("../toolbars/ToolbarToggler");
+var UIUtil = require("../util/UIUtil");
+var LargeVideo = require("../videolayout/LargeVideo");
+
+/**
+ * Toggler for the chat, contact list, settings menu, etc..
+ */
+var PanelToggler = (function(my) {
+
+ var currentlyOpen = null;
+ var buttons = {
+ '#chatspace': '#chatBottomButton',
+ '#contactlist': '#contactListButton',
+ '#settingsmenu': '#settingsButton'
+ };
+
+ /**
+ * Toggles the windows in the side panel
+ * @param object the window that should be shown
+ * @param selector the selector for the element containing the panel
+ * @param onOpenComplete function to be called when the panel is opened
+ * @param onOpen function to be called if the window is going to be opened
+ * @param onClose function to be called if the window is going to be closed
+ */
+ var toggle = function(object, selector, onOpenComplete, onOpen, onClose) {
+ UIUtil.buttonClick(buttons[selector], "active");
+
+ if (object.isVisible()) {
+ $("#toast-container").animate({
+ right: '5px'
+ },
+ {
+ queue: false,
+ duration: 500
+ });
+ $(selector).hide("slide", {
+ direction: "right",
+ queue: false,
+ duration: 500
+ });
+ if(typeof onClose === "function") {
+ onClose();
+ }
+
+ currentlyOpen = null;
+ }
+ else {
+ // Undock the toolbar when the chat is shown and if we're in a
+ // video mode.
+ if (LargeVideo.isLargeVideoVisible()) {
+ ToolbarToggler.dockToolbar(false);
+ }
+
+ if(currentlyOpen) {
+ var current = $(currentlyOpen);
+ UIUtil.buttonClick(buttons[currentlyOpen], "active");
+ current.css('z-index', 4);
+ setTimeout(function () {
+ current.css('display', 'none');
+ current.css('z-index', 5);
+ }, 500);
+ }
+
+ $("#toast-container").animate({
+ right: (PanelToggler.getPanelSize()[0] + 5) + 'px'
+ },
+ {
+ queue: false,
+ duration: 500
+ });
+ $(selector).show("slide", {
+ direction: "right",
+ queue: false,
+ duration: 500,
+ complete: onOpenComplete
+ });
+ if(typeof onOpen === "function") {
+ onOpen();
+ }
+
+ currentlyOpen = selector;
+ }
+ };
+
+ /**
+ * Opens / closes the chat area.
+ */
+ my.toggleChat = function() {
+ var chatCompleteFunction = Chat.isVisible() ?
+ function() {} : function () {
+ Chat.scrollChatToBottom();
+ $('#chatspace').trigger('shown');
+ };
+
+ VideoLayout.resizeVideoArea(!Chat.isVisible(), chatCompleteFunction);
+
+ toggle(Chat,
+ '#chatspace',
+ function () {
+ // Request the focus in the nickname field or the chat input field.
+ if ($('#nickname').css('visibility') === 'visible') {
+ $('#nickinput').focus();
+ } else {
+ $('#usermsg').focus();
+ }
+ },
+ null,
+ Chat.resizeChat,
+ null);
+ };
+
+ /**
+ * Opens / closes the contact list area.
+ */
+ my.toggleContactList = function () {
+ var completeFunction = ContactList.isVisible() ?
+ function() {} : function () { $('#contactlist').trigger('shown');};
+ VideoLayout.resizeVideoArea(!ContactList.isVisible(), completeFunction);
+
+ toggle(ContactList,
+ '#contactlist',
+ null,
+ function() {
+ ContactList.setVisualNotification(false);
+ },
+ null);
+ };
+
+ /**
+ * Opens / closes the settings menu
+ */
+ my.toggleSettingsMenu = function() {
+ VideoLayout.resizeVideoArea(!SettingsMenu.isVisible(), function (){});
+ toggle(SettingsMenu,
+ '#settingsmenu',
+ null,
+ function() {
+ var settings = Settings.getSettings();
+ $('#setDisplayName').get(0).value = settings.displayName;
+ $('#setEmail').get(0).value = settings.email;
+ },
+ null);
+ };
+
+ /**
+ * Returns the size of the side panel.
+ */
+ my.getPanelSize = function () {
+ var availableHeight = window.innerHeight;
+ var availableWidth = window.innerWidth;
+
+ var panelWidth = 200;
+ if (availableWidth * 0.2 < 200) {
+ panelWidth = availableWidth * 0.2;
+ }
+
+ return [panelWidth, availableHeight];
+ };
+
+ my.isVisible = function() {
+ return (Chat.isVisible() || ContactList.isVisible() || SettingsMenu.isVisible());
+ };
+
+ return my;
+
+}(PanelToggler || {}));
+
module.exports = PanelToggler;
-},{"../toolbars/ToolbarToggler":27,"../util/UIUtil":31,"../videolayout/LargeVideo":33,"../videolayout/VideoLayout":37,"./../../settings/Settings":45,"./chat/Chat":19,"./contactlist/ContactList":23,"./settings/SettingsMenu":24}],19:[function(require,module,exports){
-/* global $, Util, nickname:true */
-var Replacement = require("./Replacement");
-var CommandsProcessor = require("./Commands");
-var ToolbarToggler = require("../../toolbars/ToolbarToggler");
-var smileys = require("./smileys.json").smileys;
-var NicknameHandler = require("../../util/NicknameHandler");
-var UIUtil = require("../../util/UIUtil");
-var UIEvents = require("../../../../service/UI/UIEvents");
-
-var notificationInterval = false;
-var unreadMessages = 0;
-
-
-/**
- * Shows/hides a visual notification, indicating that a message has arrived.
- */
-function setVisualNotification(show) {
- var unreadMsgElement = document.getElementById('unreadMessages');
- var unreadMsgBottomElement
- = document.getElementById('bottomUnreadMessages');
-
- var glower = $('#chatButton');
- var bottomGlower = $('#chatBottomButton');
-
- if (unreadMessages) {
- unreadMsgElement.innerHTML = unreadMessages.toString();
- unreadMsgBottomElement.innerHTML = unreadMessages.toString();
-
- ToolbarToggler.dockToolbar(true);
-
- var chatButtonElement
- = document.getElementById('chatButton').parentNode;
- var leftIndent = (UIUtil.getTextWidth(chatButtonElement) -
- UIUtil.getTextWidth(unreadMsgElement)) / 2;
- var topIndent = (UIUtil.getTextHeight(chatButtonElement) -
- UIUtil.getTextHeight(unreadMsgElement)) / 2 - 3;
-
- unreadMsgElement.setAttribute(
- 'style',
- 'top:' + topIndent +
- '; left:' + leftIndent + ';');
-
- var chatBottomButtonElement
- = document.getElementById('chatBottomButton').parentNode;
- var bottomLeftIndent = (UIUtil.getTextWidth(chatBottomButtonElement) -
- UIUtil.getTextWidth(unreadMsgBottomElement)) / 2;
- var bottomTopIndent = (UIUtil.getTextHeight(chatBottomButtonElement) -
- UIUtil.getTextHeight(unreadMsgBottomElement)) / 2 - 2;
-
- unreadMsgBottomElement.setAttribute(
- 'style',
- 'top:' + bottomTopIndent +
- '; left:' + bottomLeftIndent + ';');
-
-
- if (!glower.hasClass('icon-chat-simple')) {
- glower.removeClass('icon-chat');
- glower.addClass('icon-chat-simple');
- }
- }
- else {
- unreadMsgElement.innerHTML = '';
- unreadMsgBottomElement.innerHTML = '';
- glower.removeClass('icon-chat-simple');
- glower.addClass('icon-chat');
- }
-
- if (show && !notificationInterval) {
- notificationInterval = window.setInterval(function () {
- glower.toggleClass('active');
- bottomGlower.toggleClass('active glowing');
- }, 800);
- }
- else if (!show && notificationInterval) {
- window.clearInterval(notificationInterval);
- notificationInterval = false;
- glower.removeClass('active');
- bottomGlower.removeClass('glowing');
- bottomGlower.addClass('active');
- }
-}
-
-
-/**
- * Returns the current time in the format it is shown to the user
- * @returns {string}
- */
-function getCurrentTime(stamp) {
- var now = (stamp? new Date(stamp): new Date());
- var hour = now.getHours();
- var minute = now.getMinutes();
- var second = now.getSeconds();
- if(hour.toString().length === 1) {
- hour = '0'+hour;
- }
- if(minute.toString().length === 1) {
- minute = '0'+minute;
- }
- if(second.toString().length === 1) {
- second = '0'+second;
- }
- return hour+':'+minute+':'+second;
-}
-
-function toggleSmileys()
-{
- var smileys = $('#smileysContainer');
- if(!smileys.is(':visible')) {
- smileys.show("slide", { direction: "down", duration: 300});
- } else {
- smileys.hide("slide", { direction: "down", duration: 300});
- }
- $('#usermsg').focus();
-}
-
-function addClickFunction(smiley, number) {
- smiley.onclick = function addSmileyToMessage() {
- var usermsg = $('#usermsg');
- var message = usermsg.val();
- message += smileys['smiley' + number];
- usermsg.val(message);
- usermsg.get(0).setSelectionRange(message.length, message.length);
- toggleSmileys();
- usermsg.focus();
- };
-}
-
-/**
- * Adds the smileys container to the chat
- */
-function addSmileys() {
- var smileysContainer = document.createElement('div');
- smileysContainer.id = 'smileysContainer';
- for(var i = 1; i <= 21; i++) {
- var smileyContainer = document.createElement('div');
- smileyContainer.id = 'smiley' + i;
- smileyContainer.className = 'smileyContainer';
- var smiley = document.createElement('img');
- smiley.src = 'images/smileys/smiley' + i + '.svg';
- smiley.className = 'smiley';
- addClickFunction(smiley, i);
- smileyContainer.appendChild(smiley);
- smileysContainer.appendChild(smileyContainer);
- }
-
- $("#chatspace").append(smileysContainer);
-}
-
-/**
- * Resizes the chat conversation.
- */
-function resizeChatConversation() {
- var msgareaHeight = $('#usermsg').outerHeight();
- var chatspace = $('#chatspace');
- var width = chatspace.width();
- var chat = $('#chatconversation');
- var smileys = $('#smileysarea');
-
- smileys.height(msgareaHeight);
- $("#smileys").css('bottom', (msgareaHeight - 26) / 2);
- $('#smileysContainer').css('bottom', msgareaHeight);
- chat.width(width - 10);
- chat.height(window.innerHeight - 15 - msgareaHeight);
-}
-
-/**
- * Chat related user interface.
- */
-var Chat = (function (my) {
- /**
- * Initializes chat related interface.
- */
- my.init = function () {
- if(NicknameHandler.getNickname())
- Chat.setChatConversationMode(true);
- NicknameHandler.addListener(UIEvents.NICKNAME_CHANGED,
- function (nickname) {
- Chat.setChatConversationMode(true);
- });
-
- $('#nickinput').keydown(function (event) {
- if (event.keyCode === 13) {
- event.preventDefault();
- var val = UIUtil.escapeHtml(this.value);
- this.value = '';
- if (!NicknameHandler.getNickname()) {
- NicknameHandler.setNickname(val);
-
- return;
- }
- }
- });
-
- $('#usermsg').keydown(function (event) {
- if (event.keyCode === 13) {
- event.preventDefault();
- var value = this.value;
- $('#usermsg').val('').trigger('autosize.resize');
- this.focus();
- var command = new CommandsProcessor(value);
- if(command.isCommand())
- {
- command.processCommand();
- }
- else
- {
- var message = UIUtil.escapeHtml(value);
- APP.xmpp.sendChatMessage(message, NicknameHandler.getNickname());
- }
- }
- });
-
- var onTextAreaResize = function () {
- resizeChatConversation();
- Chat.scrollChatToBottom();
- };
- $('#usermsg').autosize({callback: onTextAreaResize});
-
- $("#chatspace").bind("shown",
- function () {
- unreadMessages = 0;
- setVisualNotification(false);
- });
-
- addSmileys();
- };
-
- /**
- * Appends the given message to the chat conversation.
- */
- my.updateChatConversation = function (from, displayName, message, myjid, stamp) {
- var divClassName = '';
-
- if (APP.xmpp.myJid() === from) {
- divClassName = "localuser";
- }
- else {
- divClassName = "remoteuser";
-
- if (!Chat.isVisible()) {
- unreadMessages++;
- UIUtil.playSoundNotification('chatNotification');
- setVisualNotification(true);
- }
- }
-
- // replace links and smileys
- // Strophe already escapes special symbols on sending,
- // so we escape here only tags to avoid double &
- var escMessage = message.replace(//g, '>').replace(/\n/g, ' ');
- var escDisplayName = UIUtil.escapeHtml(displayName);
- message = Replacement.processReplacements(escMessage);
-
- var messageContainer =
- ''+
- '
' +
- '
' + escDisplayName +
- '
' + '
' + getCurrentTime(stamp) +
- '
' + '
' + message + '
' +
- '
';
-
- $('#chatconversation').append(messageContainer);
- $('#chatconversation').animate(
- { scrollTop: $('#chatconversation')[0].scrollHeight}, 1000);
- };
-
- /**
- * Appends error message to the conversation
- * @param errorMessage the received error message.
- * @param originalText the original message.
- */
- my.chatAddError = function(errorMessage, originalText)
- {
- errorMessage = UIUtil.escapeHtml(errorMessage);
- originalText = UIUtil.escapeHtml(originalText);
-
- $('#chatconversation').append(
- 'Error: ' + 'Your message' +
- (originalText? (' \"'+ originalText + '\"') : "") +
- ' was not sent.' +
- (errorMessage? (' Reason: ' + errorMessage) : '') + '
');
- $('#chatconversation').animate(
- { scrollTop: $('#chatconversation')[0].scrollHeight}, 1000);
- };
-
- /**
- * Sets the subject to the UI
- * @param subject the subject
- */
- my.chatSetSubject = function(subject)
- {
- if(subject)
- subject = subject.trim();
- $('#subject').html(Replacement.linkify(UIUtil.escapeHtml(subject)));
- if(subject === "")
- {
- $("#subject").css({display: "none"});
- }
- else
- {
- $("#subject").css({display: "block"});
- }
- };
-
-
-
- /**
- * Sets the chat conversation mode.
- */
- my.setChatConversationMode = function (isConversationMode) {
- if (isConversationMode) {
- $('#nickname').css({visibility: 'hidden'});
- $('#chatconversation').css({visibility: 'visible'});
- $('#usermsg').css({visibility: 'visible'});
- $('#smileysarea').css({visibility: 'visible'});
- $('#usermsg').focus();
- }
- };
-
- /**
- * Resizes the chat area.
- */
- my.resizeChat = function () {
- var chatSize = require("../SidePanelToggler").getPanelSize();
-
- $('#chatspace').width(chatSize[0]);
- $('#chatspace').height(chatSize[1]);
-
- resizeChatConversation();
- };
-
- /**
- * Indicates if the chat is currently visible.
- */
- my.isVisible = function () {
- return $('#chatspace').is(":visible");
- };
- /**
- * Shows and hides the window with the smileys
- */
- my.toggleSmileys = toggleSmileys;
-
- /**
- * Scrolls chat to the bottom.
- */
- my.scrollChatToBottom = function() {
- setTimeout(function () {
- $('#chatconversation').scrollTop(
- $('#chatconversation')[0].scrollHeight);
- }, 5);
- };
-
-
- return my;
-}(Chat || {}));
+},{"../toolbars/ToolbarToggler":29,"../util/UIUtil":33,"../videolayout/LargeVideo":35,"../videolayout/VideoLayout":39,"./../../settings/Settings":47,"./chat/Chat":21,"./contactlist/ContactList":25,"./settings/SettingsMenu":26}],21:[function(require,module,exports){
+/* global $, Util, nickname:true */
+var Replacement = require("./Replacement");
+var CommandsProcessor = require("./Commands");
+var ToolbarToggler = require("../../toolbars/ToolbarToggler");
+var smileys = require("./smileys.json").smileys;
+var NicknameHandler = require("../../util/NicknameHandler");
+var UIUtil = require("../../util/UIUtil");
+var UIEvents = require("../../../../service/UI/UIEvents");
+
+var notificationInterval = false;
+var unreadMessages = 0;
+
+
+/**
+ * Shows/hides a visual notification, indicating that a message has arrived.
+ */
+function setVisualNotification(show) {
+ var unreadMsgElement = document.getElementById('unreadMessages');
+ var unreadMsgBottomElement
+ = document.getElementById('bottomUnreadMessages');
+
+ var glower = $('#chatButton');
+ var bottomGlower = $('#chatBottomButton');
+
+ if (unreadMessages) {
+ unreadMsgElement.innerHTML = unreadMessages.toString();
+ unreadMsgBottomElement.innerHTML = unreadMessages.toString();
+
+ ToolbarToggler.dockToolbar(true);
+
+ var chatButtonElement
+ = document.getElementById('chatButton').parentNode;
+ var leftIndent = (UIUtil.getTextWidth(chatButtonElement) -
+ UIUtil.getTextWidth(unreadMsgElement)) / 2;
+ var topIndent = (UIUtil.getTextHeight(chatButtonElement) -
+ UIUtil.getTextHeight(unreadMsgElement)) / 2 - 3;
+
+ unreadMsgElement.setAttribute(
+ 'style',
+ 'top:' + topIndent +
+ '; left:' + leftIndent + ';');
+
+ var chatBottomButtonElement
+ = document.getElementById('chatBottomButton').parentNode;
+ var bottomLeftIndent = (UIUtil.getTextWidth(chatBottomButtonElement) -
+ UIUtil.getTextWidth(unreadMsgBottomElement)) / 2;
+ var bottomTopIndent = (UIUtil.getTextHeight(chatBottomButtonElement) -
+ UIUtil.getTextHeight(unreadMsgBottomElement)) / 2 - 2;
+
+ unreadMsgBottomElement.setAttribute(
+ 'style',
+ 'top:' + bottomTopIndent +
+ '; left:' + bottomLeftIndent + ';');
+
+
+ if (!glower.hasClass('icon-chat-simple')) {
+ glower.removeClass('icon-chat');
+ glower.addClass('icon-chat-simple');
+ }
+ }
+ else {
+ unreadMsgElement.innerHTML = '';
+ unreadMsgBottomElement.innerHTML = '';
+ glower.removeClass('icon-chat-simple');
+ glower.addClass('icon-chat');
+ }
+
+ if (show && !notificationInterval) {
+ notificationInterval = window.setInterval(function () {
+ glower.toggleClass('active');
+ bottomGlower.toggleClass('active glowing');
+ }, 800);
+ }
+ else if (!show && notificationInterval) {
+ window.clearInterval(notificationInterval);
+ notificationInterval = false;
+ glower.removeClass('active');
+ bottomGlower.removeClass('glowing');
+ bottomGlower.addClass('active');
+ }
+}
+
+
+/**
+ * Returns the current time in the format it is shown to the user
+ * @returns {string}
+ */
+function getCurrentTime(stamp) {
+ var now = (stamp? new Date(stamp): new Date());
+ var hour = now.getHours();
+ var minute = now.getMinutes();
+ var second = now.getSeconds();
+ if(hour.toString().length === 1) {
+ hour = '0'+hour;
+ }
+ if(minute.toString().length === 1) {
+ minute = '0'+minute;
+ }
+ if(second.toString().length === 1) {
+ second = '0'+second;
+ }
+ return hour+':'+minute+':'+second;
+}
+
+function toggleSmileys()
+{
+ var smileys = $('#smileysContainer');
+ if(!smileys.is(':visible')) {
+ smileys.show("slide", { direction: "down", duration: 300});
+ } else {
+ smileys.hide("slide", { direction: "down", duration: 300});
+ }
+ $('#usermsg').focus();
+}
+
+function addClickFunction(smiley, number) {
+ smiley.onclick = function addSmileyToMessage() {
+ var usermsg = $('#usermsg');
+ var message = usermsg.val();
+ message += smileys['smiley' + number];
+ usermsg.val(message);
+ usermsg.get(0).setSelectionRange(message.length, message.length);
+ toggleSmileys();
+ usermsg.focus();
+ };
+}
+
+/**
+ * Adds the smileys container to the chat
+ */
+function addSmileys() {
+ var smileysContainer = document.createElement('div');
+ smileysContainer.id = 'smileysContainer';
+ for(var i = 1; i <= 21; i++) {
+ var smileyContainer = document.createElement('div');
+ smileyContainer.id = 'smiley' + i;
+ smileyContainer.className = 'smileyContainer';
+ var smiley = document.createElement('img');
+ smiley.src = 'images/smileys/smiley' + i + '.svg';
+ smiley.className = 'smiley';
+ addClickFunction(smiley, i);
+ smileyContainer.appendChild(smiley);
+ smileysContainer.appendChild(smileyContainer);
+ }
+
+ $("#chatspace").append(smileysContainer);
+}
+
+/**
+ * Resizes the chat conversation.
+ */
+function resizeChatConversation() {
+ var msgareaHeight = $('#usermsg').outerHeight();
+ var chatspace = $('#chatspace');
+ var width = chatspace.width();
+ var chat = $('#chatconversation');
+ var smileys = $('#smileysarea');
+
+ smileys.height(msgareaHeight);
+ $("#smileys").css('bottom', (msgareaHeight - 26) / 2);
+ $('#smileysContainer').css('bottom', msgareaHeight);
+ chat.width(width - 10);
+ chat.height(window.innerHeight - 15 - msgareaHeight);
+}
+
+/**
+ * Chat related user interface.
+ */
+var Chat = (function (my) {
+ /**
+ * Initializes chat related interface.
+ */
+ my.init = function () {
+ if(NicknameHandler.getNickname())
+ Chat.setChatConversationMode(true);
+ NicknameHandler.addListener(UIEvents.NICKNAME_CHANGED,
+ function (nickname) {
+ Chat.setChatConversationMode(true);
+ });
+
+ $('#nickinput').keydown(function (event) {
+ if (event.keyCode === 13) {
+ event.preventDefault();
+ var val = UIUtil.escapeHtml(this.value);
+ this.value = '';
+ if (!NicknameHandler.getNickname()) {
+ NicknameHandler.setNickname(val);
+
+ return;
+ }
+ }
+ });
+
+ $('#usermsg').keydown(function (event) {
+ if (event.keyCode === 13) {
+ event.preventDefault();
+ var value = this.value;
+ $('#usermsg').val('').trigger('autosize.resize');
+ this.focus();
+ var command = new CommandsProcessor(value);
+ if(command.isCommand())
+ {
+ command.processCommand();
+ }
+ else
+ {
+ var message = UIUtil.escapeHtml(value);
+ APP.xmpp.sendChatMessage(message, NicknameHandler.getNickname());
+ }
+ }
+ });
+
+ var onTextAreaResize = function () {
+ resizeChatConversation();
+ Chat.scrollChatToBottom();
+ };
+ $('#usermsg').autosize({callback: onTextAreaResize});
+
+ $("#chatspace").bind("shown",
+ function () {
+ unreadMessages = 0;
+ setVisualNotification(false);
+ });
+
+ addSmileys();
+ };
+
+ /**
+ * Appends the given message to the chat conversation.
+ */
+ my.updateChatConversation = function (from, displayName, message, myjid, stamp) {
+ var divClassName = '';
+
+ if (APP.xmpp.myJid() === from) {
+ divClassName = "localuser";
+ }
+ else {
+ divClassName = "remoteuser";
+
+ if (!Chat.isVisible()) {
+ unreadMessages++;
+ UIUtil.playSoundNotification('chatNotification');
+ setVisualNotification(true);
+ }
+ }
+
+ // replace links and smileys
+ // Strophe already escapes special symbols on sending,
+ // so we escape here only tags to avoid double &
+ var escMessage = message.replace(//g, '>').replace(/\n/g, ' ');
+ var escDisplayName = UIUtil.escapeHtml(displayName);
+ message = Replacement.processReplacements(escMessage);
+
+ var messageContainer =
+ ''+
+ '
' +
+ '
' + escDisplayName +
+ '
' + '
' + getCurrentTime(stamp) +
+ '
' + '
' + message + '
' +
+ '
';
+
+ $('#chatconversation').append(messageContainer);
+ $('#chatconversation').animate(
+ { scrollTop: $('#chatconversation')[0].scrollHeight}, 1000);
+ };
+
+ /**
+ * Appends error message to the conversation
+ * @param errorMessage the received error message.
+ * @param originalText the original message.
+ */
+ my.chatAddError = function(errorMessage, originalText)
+ {
+ errorMessage = UIUtil.escapeHtml(errorMessage);
+ originalText = UIUtil.escapeHtml(originalText);
+
+ $('#chatconversation').append(
+ 'Error: ' + 'Your message' +
+ (originalText? (' \"'+ originalText + '\"') : "") +
+ ' was not sent.' +
+ (errorMessage? (' Reason: ' + errorMessage) : '') + '
');
+ $('#chatconversation').animate(
+ { scrollTop: $('#chatconversation')[0].scrollHeight}, 1000);
+ };
+
+ /**
+ * Sets the subject to the UI
+ * @param subject the subject
+ */
+ my.chatSetSubject = function(subject)
+ {
+ if(subject)
+ subject = subject.trim();
+ $('#subject').html(Replacement.linkify(UIUtil.escapeHtml(subject)));
+ if(subject === "")
+ {
+ $("#subject").css({display: "none"});
+ }
+ else
+ {
+ $("#subject").css({display: "block"});
+ }
+ };
+
+
+
+ /**
+ * Sets the chat conversation mode.
+ */
+ my.setChatConversationMode = function (isConversationMode) {
+ if (isConversationMode) {
+ $('#nickname').css({visibility: 'hidden'});
+ $('#chatconversation').css({visibility: 'visible'});
+ $('#usermsg').css({visibility: 'visible'});
+ $('#smileysarea').css({visibility: 'visible'});
+ $('#usermsg').focus();
+ }
+ };
+
+ /**
+ * Resizes the chat area.
+ */
+ my.resizeChat = function () {
+ var chatSize = require("../SidePanelToggler").getPanelSize();
+
+ $('#chatspace').width(chatSize[0]);
+ $('#chatspace').height(chatSize[1]);
+
+ resizeChatConversation();
+ };
+
+ /**
+ * Indicates if the chat is currently visible.
+ */
+ my.isVisible = function () {
+ return $('#chatspace').is(":visible");
+ };
+ /**
+ * Shows and hides the window with the smileys
+ */
+ my.toggleSmileys = toggleSmileys;
+
+ /**
+ * Scrolls chat to the bottom.
+ */
+ my.scrollChatToBottom = function() {
+ setTimeout(function () {
+ $('#chatconversation').scrollTop(
+ $('#chatconversation')[0].scrollHeight);
+ }, 5);
+ };
+
+
+ return my;
+}(Chat || {}));
module.exports = Chat;
-},{"../../../../service/UI/UIEvents":105,"../../toolbars/ToolbarToggler":27,"../../util/NicknameHandler":30,"../../util/UIUtil":31,"../SidePanelToggler":18,"./Commands":20,"./Replacement":21,"./smileys.json":22}],20:[function(require,module,exports){
-var UIUtil = require("../../util/UIUtil");
-
-/**
- * List with supported commands. The keys are the names of the commands and
- * the value is the function that processes the message.
- * @type {{String: function}}
- */
-var commands = {
- "topic" : processTopic
-};
-
-/**
- * Extracts the command from the message.
- * @param message the received message
- * @returns {string} the command
- */
-function getCommand(message)
-{
- if(message)
- {
- for(var command in commands)
- {
- if(message.indexOf("/" + command) == 0)
- return command;
- }
- }
- return "";
-};
-
-/**
- * Processes the data for topic command.
- * @param commandArguments the arguments of the topic command.
- */
-function processTopic(commandArguments)
-{
- var topic = UIUtil.escapeHtml(commandArguments);
- APP.xmpp.setSubject(topic);
-}
-
-/**
- * Constructs new CommandProccessor instance from a message that
- * handles commands received via chat messages.
- * @param message the message
- * @constructor
- */
-function CommandsProcessor(message)
-{
-
-
- var command = getCommand(message);
-
- /**
- * Returns the name of the command.
- * @returns {String} the command
- */
- this.getCommand = function()
- {
- return command;
- };
-
-
- var messageArgument = message.substr(command.length + 2);
-
- /**
- * Returns the arguments of the command.
- * @returns {string}
- */
- this.getArgument = function()
- {
- return messageArgument;
- };
-}
-
-/**
- * Checks whether this instance is valid command or not.
- * @returns {boolean}
- */
-CommandsProcessor.prototype.isCommand = function()
-{
- if(this.getCommand())
- return true;
- return false;
-};
-
-/**
- * Processes the command.
- */
-CommandsProcessor.prototype.processCommand = function()
-{
- if(!this.isCommand())
- return;
-
- commands[this.getCommand()](this.getArgument());
-
-};
-
+},{"../../../../service/UI/UIEvents":103,"../../toolbars/ToolbarToggler":29,"../../util/NicknameHandler":32,"../../util/UIUtil":33,"../SidePanelToggler":20,"./Commands":22,"./Replacement":23,"./smileys.json":24}],22:[function(require,module,exports){
+var UIUtil = require("../../util/UIUtil");
+
+/**
+ * List with supported commands. The keys are the names of the commands and
+ * the value is the function that processes the message.
+ * @type {{String: function}}
+ */
+var commands = {
+ "topic" : processTopic
+};
+
+/**
+ * Extracts the command from the message.
+ * @param message the received message
+ * @returns {string} the command
+ */
+function getCommand(message)
+{
+ if(message)
+ {
+ for(var command in commands)
+ {
+ if(message.indexOf("/" + command) == 0)
+ return command;
+ }
+ }
+ return "";
+};
+
+/**
+ * Processes the data for topic command.
+ * @param commandArguments the arguments of the topic command.
+ */
+function processTopic(commandArguments)
+{
+ var topic = UIUtil.escapeHtml(commandArguments);
+ APP.xmpp.setSubject(topic);
+}
+
+/**
+ * Constructs new CommandProccessor instance from a message that
+ * handles commands received via chat messages.
+ * @param message the message
+ * @constructor
+ */
+function CommandsProcessor(message)
+{
+
+
+ var command = getCommand(message);
+
+ /**
+ * Returns the name of the command.
+ * @returns {String} the command
+ */
+ this.getCommand = function()
+ {
+ return command;
+ };
+
+
+ var messageArgument = message.substr(command.length + 2);
+
+ /**
+ * Returns the arguments of the command.
+ * @returns {string}
+ */
+ this.getArgument = function()
+ {
+ return messageArgument;
+ };
+}
+
+/**
+ * Checks whether this instance is valid command or not.
+ * @returns {boolean}
+ */
+CommandsProcessor.prototype.isCommand = function()
+{
+ if(this.getCommand())
+ return true;
+ return false;
+};
+
+/**
+ * Processes the command.
+ */
+CommandsProcessor.prototype.processCommand = function()
+{
+ if(!this.isCommand())
+ return;
+
+ commands[this.getCommand()](this.getArgument());
+
+};
+
module.exports = CommandsProcessor;
-},{"../../util/UIUtil":31}],21:[function(require,module,exports){
-var Smileys = require("./smileys.json");
-/**
- * Processes links and smileys in "body"
- */
-function processReplacements(body)
-{
- //make links clickable
- body = linkify(body);
+},{"../../util/UIUtil":33}],23:[function(require,module,exports){
+var Smileys = require("./smileys.json");
+/**
+ * Processes links and smileys in "body"
+ */
+function processReplacements(body)
+{
+ //make links clickable
+ body = linkify(body);
+
+ //add smileys
+ body = smilify(body);
+
+ return body;
+}
+
+/**
+ * Finds and replaces all links in the links in "body"
+ * with their
+ */
+function linkify(inputText)
+{
+ var replacedText, replacePattern1, replacePattern2, replacePattern3;
+
+ //URLs starting with http://, https://, or ftp://
+ replacePattern1 = /(\b(https?|ftp):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gim;
+ replacedText = inputText.replace(replacePattern1, '$1 ');
+
+ //URLs starting with "www." (without // before it, or it'd re-link the ones done above).
+ replacePattern2 = /(^|[^\/])(www\.[\S]+(\b|$))/gim;
+ replacedText = replacedText.replace(replacePattern2, '$1$2 ');
+
+ //Change email addresses to mailto:: links.
+ replacePattern3 = /(([a-zA-Z0-9\-\_\.])+@[a-zA-Z\_]+?(\.[a-zA-Z]{2,6})+)/gim;
+ replacedText = replacedText.replace(replacePattern3, '$1 ');
+
+ return replacedText;
+}
+
+/**
+ * Replaces common smiley strings with images
+ */
+function smilify(body)
+{
+ if(!body) {
+ return body;
+ }
+
+ var regexs = Smileys["regexs"];
+ for(var smiley in regexs) {
+ if(regexs.hasOwnProperty(smiley)) {
+ body = body.replace(regexs[smiley],
+ ' ');
+ }
+ }
+
+ return body;
+}
+
+module.exports = {
+ processReplacements: processReplacements,
+ linkify: linkify
+};
- //add smileys
- body = smilify(body);
-
- return body;
-}
-
-/**
- * Finds and replaces all links in the links in "body"
- * with their
- */
-function linkify(inputText)
-{
- var replacedText, replacePattern1, replacePattern2, replacePattern3;
-
- //URLs starting with http://, https://, or ftp://
- replacePattern1 = /(\b(https?|ftp):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gim;
- replacedText = inputText.replace(replacePattern1, '$1 ');
-
- //URLs starting with "www." (without // before it, or it'd re-link the ones done above).
- replacePattern2 = /(^|[^\/])(www\.[\S]+(\b|$))/gim;
- replacedText = replacedText.replace(replacePattern2, '$1$2 ');
-
- //Change email addresses to mailto:: links.
- replacePattern3 = /(([a-zA-Z0-9\-\_\.])+@[a-zA-Z\_]+?(\.[a-zA-Z]{2,6})+)/gim;
- replacedText = replacedText.replace(replacePattern3, '$1 ');
-
- return replacedText;
-}
-
-/**
- * Replaces common smiley strings with images
- */
-function smilify(body)
-{
- if(!body) {
- return body;
- }
-
- var regexs = Smileys["regexs"];
- for(var smiley in regexs) {
- if(regexs.hasOwnProperty(smiley)) {
- body = body.replace(regexs[smiley],
- ' ');
- }
- }
-
- return body;
-}
-
-module.exports = {
- processReplacements: processReplacements,
- linkify: linkify
-};
-
-},{"./smileys.json":22}],22:[function(require,module,exports){
-module.exports={
- "smileys": {
- "smiley1": ":)",
- "smiley2": ":(",
- "smiley3": ":D",
- "smiley4": "(y)",
- "smiley5": " :P",
- "smiley6": "(wave)",
- "smiley7": "(blush)",
- "smiley8": "(chuckle)",
- "smiley9": "(shocked)",
- "smiley10": ":*",
- "smiley11": "(n)",
- "smiley12": "(search)",
- "smiley13": " <3",
- "smiley14": "(oops)",
- "smiley15": "(angry)",
- "smiley16": "(angel)",
- "smiley17": "(sick)",
- "smiley18": ";(",
- "smiley19": "(bomb)",
- "smiley20": "(clap)",
- "smiley21": " ;)"
- },
- "regexs": {
- "smiley2": /(:-\(\(|:-\(|:\(\(|:\(|\(sad\))/gi,
- "smiley3": /(:-\)\)|:\)\)|\(lol\)|:-D|:D)/gi,
- "smiley1": /(:-\)|:\))/gi,
- "smiley4": /(\(y\)|\(Y\)|\(ok\))/gi,
- "smiley5": /(:-P|:P|:-p|:p)/gi,
- "smiley6": /(\(wave\))/gi,
- "smiley7": /(\(blush\))/gi,
- "smiley8": /(\(chuckle\))/gi,
- "smiley9": /(:-0|\(shocked\))/gi,
- "smiley10": /(:-\*|:\*|\(kiss\))/gi,
- "smiley11": /(\(n\))/gi,
- "smiley12": /(\(search\))/g,
- "smiley13": /(<3|<3|<3|\(L\)|\(l\)|\(H\)|\(h\))/gi,
- "smiley14": /(\(oops\))/gi,
- "smiley15": /(\(angry\))/gi,
- "smiley16": /(\(angel\))/gi,
- "smiley17": /(\(sick\))/gi,
- "smiley18": /(;-\(\(|;\(\(|;-\(|;\(|:"\(|:"-\(|:~-\(|:~\(|\(upset\))/gi,
- "smiley19": /(\(bomb\))/gi,
- "smiley20": /(\(clap\))/gi,
- "smiley21": /(;-\)|;\)|;-\)\)|;\)\)|;-D|;D|\(wink\))/gi
- }
-}
-
-},{}],23:[function(require,module,exports){
-
-var numberOfContacts = 0;
-var notificationInterval;
-
-/**
- * Updates the number of participants in the contact list button and sets
- * the glow
- * @param delta indicates whether a new user has joined (1) or someone has
- * left(-1)
- */
-function updateNumberOfParticipants(delta) {
- //when the user is alone we don't show the number of participants
- if(numberOfContacts === 0) {
- $("#numberOfParticipants").text('');
- numberOfContacts += delta;
- } else if(numberOfContacts !== 0 && !ContactList.isVisible()) {
- ContactList.setVisualNotification(true);
- numberOfContacts += delta;
- $("#numberOfParticipants").text(numberOfContacts);
- }
-}
-
-/**
- * Creates the avatar element.
- *
- * @return the newly created avatar element
- */
-function createAvatar(id) {
- var avatar = document.createElement('img');
- avatar.className = "icon-avatar avatar";
- avatar.src = "https://www.gravatar.com/avatar/" + id + "?d=wavatar&size=30";
-
- return avatar;
-}
-
-/**
- * Creates the display name paragraph.
- *
- * @param displayName the display name to set
- */
-function createDisplayNameParagraph(key, displayName) {
- var p = document.createElement('p');
- if(displayName)
- p.innerText = displayName;
- else if(key)
- {
- p.setAttribute("data-i18n",key);
- p.innerText = APP.translation.translateString(key);
- }
-
- return p;
-}
-
-
-function stopGlowing(glower) {
- window.clearInterval(notificationInterval);
- notificationInterval = false;
- glower.removeClass('glowing');
- if (!ContactList.isVisible()) {
- glower.removeClass('active');
- }
-}
-
-
-/**
- * Contact list.
- */
-var ContactList = {
- /**
- * Indicates if the chat is currently visible.
- *
- * @return true if the chat is currently visible, false -
- * otherwise
- */
- isVisible: function () {
- return $('#contactlist').is(":visible");
- },
-
- /**
- * Adds a contact for the given peerJid if such doesn't yet exist.
- *
- * @param peerJid the peerJid corresponding to the contact
- * @param id the user's email or userId used to get the user's avatar
- */
- ensureAddContact: function (peerJid, id) {
- var resourceJid = Strophe.getResourceFromJid(peerJid);
-
- var contact = $('#contacts>li[id="' + resourceJid + '"]');
-
- if (!contact || contact.length <= 0)
- ContactList.addContact(peerJid, id);
- },
-
- /**
- * Adds a contact for the given peer jid.
- *
- * @param peerJid the jid of the contact to add
- * @param id the email or userId of the user
- */
- addContact: function (peerJid, id) {
- var resourceJid = Strophe.getResourceFromJid(peerJid);
-
- var contactlist = $('#contacts');
-
- var newContact = document.createElement('li');
- newContact.id = resourceJid;
- newContact.className = "clickable";
- newContact.onclick = function (event) {
- if (event.currentTarget.className === "clickable") {
- $(ContactList).trigger('contactclicked', [peerJid]);
- }
- };
-
- newContact.appendChild(createAvatar(id));
- newContact.appendChild(createDisplayNameParagraph("participant"));
-
- if (resourceJid === APP.xmpp.myResource()) {
- contactlist.prepend(newContact);
- }
- else {
- contactlist.append(newContact);
- }
- updateNumberOfParticipants(1);
- },
-
- /**
- * Removes a contact for the given peer jid.
- *
- * @param peerJid the peerJid corresponding to the contact to remove
- */
- removeContact: function (peerJid) {
- var resourceJid = Strophe.getResourceFromJid(peerJid);
-
- var contact = $('#contacts>li[id="' + resourceJid + '"]');
-
- if (contact && contact.length > 0) {
- var contactlist = $('#contactlist>ul');
-
- contactlist.get(0).removeChild(contact.get(0));
-
- updateNumberOfParticipants(-1);
- }
- },
-
- setVisualNotification: function (show, stopGlowingIn) {
- var glower = $('#contactListButton');
-
- if (show && !notificationInterval) {
- notificationInterval = window.setInterval(function () {
- glower.toggleClass('active glowing');
- }, 800);
- }
- else if (!show && notificationInterval) {
- stopGlowing(glower);
- }
- if (stopGlowingIn) {
- setTimeout(function () {
- stopGlowing(glower);
- }, stopGlowingIn);
- }
- },
-
- setClickable: function (resourceJid, isClickable) {
- var contact = $('#contacts>li[id="' + resourceJid + '"]');
- if (isClickable) {
- contact.addClass('clickable');
- } else {
- contact.removeClass('clickable');
- }
- },
-
- onDisplayNameChange: function (peerJid, displayName) {
- if (peerJid === 'localVideoContainer')
- peerJid = APP.xmpp.myJid();
-
- var resourceJid = Strophe.getResourceFromJid(peerJid);
-
- var contactName = $('#contacts #' + resourceJid + '>p');
-
- if (contactName && displayName && displayName.length > 0)
- contactName.html(displayName);
- },
-
- userAvatarChanged: function (resourceJid, contactListUrl) {
- // set the avatar in the contact list
- var contact = $('#' + resourceJid + '>img');
- if (contact && contact.length > 0) {
- contact.get(0).src = contactListUrl;
- }
-
- }
-};
+},{"./smileys.json":24}],24:[function(require,module,exports){
+module.exports={
+ "smileys": {
+ "smiley1": ":)",
+ "smiley2": ":(",
+ "smiley3": ":D",
+ "smiley4": "(y)",
+ "smiley5": " :P",
+ "smiley6": "(wave)",
+ "smiley7": "(blush)",
+ "smiley8": "(chuckle)",
+ "smiley9": "(shocked)",
+ "smiley10": ":*",
+ "smiley11": "(n)",
+ "smiley12": "(search)",
+ "smiley13": " <3",
+ "smiley14": "(oops)",
+ "smiley15": "(angry)",
+ "smiley16": "(angel)",
+ "smiley17": "(sick)",
+ "smiley18": ";(",
+ "smiley19": "(bomb)",
+ "smiley20": "(clap)",
+ "smiley21": " ;)"
+ },
+ "regexs": {
+ "smiley2": /(:-\(\(|:-\(|:\(\(|:\(|\(sad\))/gi,
+ "smiley3": /(:-\)\)|:\)\)|\(lol\)|:-D|:D)/gi,
+ "smiley1": /(:-\)|:\))/gi,
+ "smiley4": /(\(y\)|\(Y\)|\(ok\))/gi,
+ "smiley5": /(:-P|:P|:-p|:p)/gi,
+ "smiley6": /(\(wave\))/gi,
+ "smiley7": /(\(blush\))/gi,
+ "smiley8": /(\(chuckle\))/gi,
+ "smiley9": /(:-0|\(shocked\))/gi,
+ "smiley10": /(:-\*|:\*|\(kiss\))/gi,
+ "smiley11": /(\(n\))/gi,
+ "smiley12": /(\(search\))/g,
+ "smiley13": /(<3|<3|<3|\(L\)|\(l\)|\(H\)|\(h\))/gi,
+ "smiley14": /(\(oops\))/gi,
+ "smiley15": /(\(angry\))/gi,
+ "smiley16": /(\(angel\))/gi,
+ "smiley17": /(\(sick\))/gi,
+ "smiley18": /(;-\(\(|;\(\(|;-\(|;\(|:"\(|:"-\(|:~-\(|:~\(|\(upset\))/gi,
+ "smiley19": /(\(bomb\))/gi,
+ "smiley20": /(\(clap\))/gi,
+ "smiley21": /(;-\)|;\)|;-\)\)|;\)\)|;-D|;D|\(wink\))/gi
+ }
+}
+},{}],25:[function(require,module,exports){
+
+var numberOfContacts = 0;
+var notificationInterval;
+
+/**
+ * Updates the number of participants in the contact list button and sets
+ * the glow
+ * @param delta indicates whether a new user has joined (1) or someone has
+ * left(-1)
+ */
+function updateNumberOfParticipants(delta) {
+ numberOfContacts += delta;
+ if (numberOfContacts === 1) {
+ // when the user is alone we don't show the number of participants
+ $("#numberOfParticipants").text('');
+ ContactList.setVisualNotification(false);
+ } else if (numberOfContacts > 1) {
+ ContactList.setVisualNotification(!ContactList.isVisible());
+ $("#numberOfParticipants").text(numberOfContacts);
+ } else {
+ console.error("Invalid number of participants: " + numberOfContacts);
+ }
+}
+
+/**
+ * Creates the avatar element.
+ *
+ * @return the newly created avatar element
+ */
+function createAvatar(id) {
+ var avatar = document.createElement('img');
+ avatar.className = "icon-avatar avatar";
+ avatar.src = "https://www.gravatar.com/avatar/" + id + "?d=wavatar&size=30";
+
+ return avatar;
+}
+
+/**
+ * Creates the display name paragraph.
+ *
+ * @param displayName the display name to set
+ */
+function createDisplayNameParagraph(key, displayName) {
+ var p = document.createElement('p');
+ if(displayName)
+ p.innerText = displayName;
+ else if(key)
+ {
+ p.setAttribute("data-i18n",key);
+ p.innerText = APP.translation.translateString(key);
+ }
+
+ return p;
+}
+
+
+function stopGlowing(glower) {
+ window.clearInterval(notificationInterval);
+ notificationInterval = false;
+ glower.removeClass('glowing');
+ if (!ContactList.isVisible()) {
+ glower.removeClass('active');
+ }
+}
+
+
+/**
+ * Contact list.
+ */
+var ContactList = {
+ /**
+ * Indicates if the chat is currently visible.
+ *
+ * @return true if the chat is currently visible, false -
+ * otherwise
+ */
+ isVisible: function () {
+ return $('#contactlist').is(":visible");
+ },
+
+ /**
+ * Adds a contact for the given peerJid if such doesn't yet exist.
+ *
+ * @param peerJid the peerJid corresponding to the contact
+ * @param id the user's email or userId used to get the user's avatar
+ */
+ ensureAddContact: function (peerJid, id) {
+ var resourceJid = Strophe.getResourceFromJid(peerJid);
+
+ var contact = $('#contacts>li[id="' + resourceJid + '"]');
+
+ if (!contact || contact.length <= 0)
+ ContactList.addContact(peerJid, id);
+ },
+
+ /**
+ * Adds a contact for the given peer jid.
+ *
+ * @param peerJid the jid of the contact to add
+ * @param id the email or userId of the user
+ */
+ addContact: function (peerJid, id) {
+ var resourceJid = Strophe.getResourceFromJid(peerJid);
+
+ var contactlist = $('#contacts');
+
+ var newContact = document.createElement('li');
+ newContact.id = resourceJid;
+ newContact.className = "clickable";
+ newContact.onclick = function (event) {
+ if (event.currentTarget.className === "clickable") {
+ $(ContactList).trigger('contactclicked', [peerJid]);
+ }
+ };
+
+ newContact.appendChild(createAvatar(id));
+ newContact.appendChild(createDisplayNameParagraph("participant"));
+
+ if (resourceJid === APP.xmpp.myResource()) {
+ contactlist.prepend(newContact);
+ }
+ else {
+ contactlist.append(newContact);
+ }
+ updateNumberOfParticipants(1);
+ },
+
+ /**
+ * Removes a contact for the given peer jid.
+ *
+ * @param peerJid the peerJid corresponding to the contact to remove
+ */
+ removeContact: function (peerJid) {
+ var resourceJid = Strophe.getResourceFromJid(peerJid);
+
+ var contact = $('#contacts>li[id="' + resourceJid + '"]');
+
+ if (contact && contact.length > 0) {
+ var contactlist = $('#contactlist>ul');
+
+ contactlist.get(0).removeChild(contact.get(0));
+
+ updateNumberOfParticipants(-1);
+ }
+ },
+
+ setVisualNotification: function (show, stopGlowingIn) {
+ var glower = $('#contactListButton');
+
+ if (show && !notificationInterval) {
+ notificationInterval = window.setInterval(function () {
+ glower.toggleClass('active glowing');
+ }, 800);
+ }
+ else if (!show && notificationInterval) {
+ stopGlowing(glower);
+ }
+ if (stopGlowingIn) {
+ setTimeout(function () {
+ stopGlowing(glower);
+ }, stopGlowingIn);
+ }
+ },
+
+ setClickable: function (resourceJid, isClickable) {
+ var contact = $('#contacts>li[id="' + resourceJid + '"]');
+ if (isClickable) {
+ contact.addClass('clickable');
+ } else {
+ contact.removeClass('clickable');
+ }
+ },
+
+ onDisplayNameChange: function (peerJid, displayName) {
+ if (peerJid === 'localVideoContainer')
+ peerJid = APP.xmpp.myJid();
+
+ var resourceJid = Strophe.getResourceFromJid(peerJid);
+
+ var contactName = $('#contacts #' + resourceJid + '>p');
+
+ if (contactName && displayName && displayName.length > 0)
+ contactName.html(displayName);
+ },
+
+ userAvatarChanged: function (resourceJid, contactListUrl) {
+ // set the avatar in the contact list
+ var contact = $('#' + resourceJid + '>img');
+ if (contact && contact.length > 0) {
+ contact.get(0).src = contactListUrl;
+ }
+
+ }
+};
+
module.exports = ContactList;
-},{}],24:[function(require,module,exports){
-var Avatar = require("../../avatar/Avatar");
-var Settings = require("./../../../settings/Settings");
-var UIUtil = require("../../util/UIUtil");
-var languages = require("../../../../service/translation/languages");
-
-function generateLanguagesSelectBox()
-{
- var currentLang = APP.translation.getCurrentLanguage();
- var html = "";
- var langArray = languages.getLanguages();
- for(var i = 0; i < langArray.length; i++)
- {
- var lang = langArray[i];
- html += "";
- html += " ";
-
- }
-
- return html + " ";
-}
-
-
-var SettingsMenu = {
-
- init: function () {
- $("#startMutedOptions").before(generateLanguagesSelectBox());
- APP.translation.translateElement($("#languages_selectbox"));
- $('#settingsmenu>input').keyup(function(event){
- if(event.keyCode === 13) {//enter
- SettingsMenu.update();
- }
- });
-
- if(APP.xmpp.isModerator())
- {
- $("#startMutedOptions").css("display", "block");
- }
- else
- {
- $("#startMutedOptions").css("display", "none");
- }
-
- $("#updateSettings").click(function () {
- SettingsMenu.update();
- });
- },
-
- onRoleChanged: function () {
- if(APP.xmpp.isModerator())
- {
- $("#startMutedOptions").css("display", "block");
- }
- else
- {
- $("#startMutedOptions").css("display", "none");
- }
- },
-
- setStartMuted: function (audio, video) {
- $("#startAudioMuted").attr("checked", audio);
- $("#startVideoMuted").attr("checked", video);
- },
-
- update: function() {
- var newDisplayName = UIUtil.escapeHtml($('#setDisplayName').get(0).value);
- var newEmail = UIUtil.escapeHtml($('#setEmail').get(0).value);
-
- if(newDisplayName) {
- var displayName = Settings.setDisplayName(newDisplayName);
- APP.xmpp.addToPresence("displayName", displayName, true);
- }
-
- var language = $("#languages_selectbox").val();
- APP.translation.setLanguage(language);
- Settings.setLanguage(language);
-
- APP.xmpp.addToPresence("email", newEmail);
- var email = Settings.setEmail(newEmail);
-
- var startAudioMuted = ($("#startAudioMuted").is(":checked"));
- var startVideoMuted = ($("#startVideoMuted").is(":checked"));
- APP.xmpp.addToPresence("startMuted",
- [startAudioMuted, startVideoMuted]);
-
- Avatar.setUserAvatar(APP.xmpp.myJid(), email);
- },
-
- isVisible: function() {
- return $('#settingsmenu').is(':visible');
- },
-
- setDisplayName: function(newDisplayName) {
- var displayName = Settings.setDisplayName(newDisplayName);
- $('#setDisplayName').get(0).value = displayName;
- },
-
- onDisplayNameChange: function(peerJid, newDisplayName) {
- if(peerJid === 'localVideoContainer' ||
- peerJid === APP.xmpp.myJid()) {
- this.setDisplayName(newDisplayName);
- }
- },
- changeAvatar: function (thumbUrl) {
- $('#avatar').get(0).src = thumbUrl;
- }
-};
-
-
+},{}],26:[function(require,module,exports){
+var Avatar = require("../../avatar/Avatar");
+var Settings = require("./../../../settings/Settings");
+var UIUtil = require("../../util/UIUtil");
+var languages = require("../../../../service/translation/languages");
+
+function generateLanguagesSelectBox()
+{
+ var currentLang = APP.translation.getCurrentLanguage();
+ var html = "";
+ var langArray = languages.getLanguages();
+ for(var i = 0; i < langArray.length; i++)
+ {
+ var lang = langArray[i];
+ html += "";
+ html += " ";
+
+ }
+
+ return html + " ";
+}
+
+
+var SettingsMenu = {
+
+ init: function () {
+ $("#startMutedOptions").before(generateLanguagesSelectBox());
+ APP.translation.translateElement($("#languages_selectbox"));
+ $('#settingsmenu>input').keyup(function(event){
+ if(event.keyCode === 13) {//enter
+ SettingsMenu.update();
+ }
+ });
+
+ if(APP.xmpp.isModerator())
+ {
+ $("#startMutedOptions").css("display", "block");
+ }
+ else
+ {
+ $("#startMutedOptions").css("display", "none");
+ }
+
+ $("#updateSettings").click(function () {
+ SettingsMenu.update();
+ });
+ },
+
+ onRoleChanged: function () {
+ if(APP.xmpp.isModerator())
+ {
+ $("#startMutedOptions").css("display", "block");
+ }
+ else
+ {
+ $("#startMutedOptions").css("display", "none");
+ }
+ },
+
+ setStartMuted: function (audio, video) {
+ $("#startAudioMuted").attr("checked", audio);
+ $("#startVideoMuted").attr("checked", video);
+ },
+
+ update: function() {
+ var newDisplayName = UIUtil.escapeHtml($('#setDisplayName').get(0).value);
+ var newEmail = UIUtil.escapeHtml($('#setEmail').get(0).value);
+
+ if(newDisplayName) {
+ var displayName = Settings.setDisplayName(newDisplayName);
+ APP.xmpp.addToPresence("displayName", displayName, true);
+ }
+
+ var language = $("#languages_selectbox").val();
+ APP.translation.setLanguage(language);
+ Settings.setLanguage(language);
+
+ APP.xmpp.addToPresence("email", newEmail);
+ var email = Settings.setEmail(newEmail);
+
+ var startAudioMuted = ($("#startAudioMuted").is(":checked"));
+ var startVideoMuted = ($("#startVideoMuted").is(":checked"));
+ APP.xmpp.addToPresence("startMuted",
+ [startAudioMuted, startVideoMuted]);
+
+ Avatar.setUserAvatar(APP.xmpp.myJid(), email);
+ },
+
+ isVisible: function() {
+ return $('#settingsmenu').is(':visible');
+ },
+
+ setDisplayName: function(newDisplayName) {
+ var displayName = Settings.setDisplayName(newDisplayName);
+ $('#setDisplayName').get(0).value = displayName;
+ },
+
+ onDisplayNameChange: function(peerJid, newDisplayName) {
+ if(peerJid === 'localVideoContainer' ||
+ peerJid === APP.xmpp.myJid()) {
+ this.setDisplayName(newDisplayName);
+ }
+ },
+ changeAvatar: function (thumbUrl) {
+ $('#avatar').get(0).src = thumbUrl;
+ }
+};
+
+
module.exports = SettingsMenu;
-},{"../../../../service/translation/languages":110,"../../avatar/Avatar":14,"../../util/UIUtil":31,"./../../../settings/Settings":45}],25:[function(require,module,exports){
-var PanelToggler = require("../side_pannels/SidePanelToggler");
-
-var buttonHandlers = {
- "bottom_toolbar_contact_list": function () {
- BottomToolbar.toggleContactList();
- },
- "bottom_toolbar_film_strip": function () {
- BottomToolbar.toggleFilmStrip();
- },
- "bottom_toolbar_chat": function () {
- BottomToolbar.toggleChat();
- }
-};
-
-var BottomToolbar = (function (my) {
- my.init = function () {
- for(var k in buttonHandlers)
- $("#" + k).click(buttonHandlers[k]);
- };
-
- my.toggleChat = function() {
- PanelToggler.toggleChat();
- };
-
- my.toggleContactList = function() {
- PanelToggler.toggleContactList();
- };
-
- my.toggleFilmStrip = function() {
- var filmstrip = $("#remoteVideos");
- filmstrip.toggleClass("hidden");
- };
-
- $(document).bind("remotevideo.resized", function (event, width, height) {
- var bottom = (height - $('#bottomToolbar').outerHeight())/2 + 18;
-
- $('#bottomToolbar').css({bottom: bottom + 'px'});
- });
-
- return my;
-}(BottomToolbar || {}));
-
-module.exports = BottomToolbar;
-
-},{"../side_pannels/SidePanelToggler":18}],26:[function(require,module,exports){
-/* global APP,$, buttonClick, config, lockRoom,
- setSharedKey, Util */
-var messageHandler = require("../util/MessageHandler");
-var BottomToolbar = require("./BottomToolbar");
-var Prezi = require("../prezi/Prezi");
-var Etherpad = require("../etherpad/Etherpad");
-var PanelToggler = require("../side_pannels/SidePanelToggler");
-var Authentication = require("../authentication/Authentication");
-var UIUtil = require("../util/UIUtil");
-var AuthenticationEvents
- = require("../../../service/authentication/AuthenticationEvents");
-
-var roomUrl = null;
-var sharedKey = '';
-var UI = null;
-
-var buttonHandlers =
-{
- "toolbar_button_mute": function () {
- return APP.UI.toggleAudio();
- },
- "toolbar_button_camera": function () {
- return APP.UI.toggleVideo();
- },
- /*"toolbar_button_authentication": function () {
- return Toolbar.authenticateClicked();
- },*/
- "toolbar_button_record": function () {
- return toggleRecording();
- },
- "toolbar_button_security": function () {
- return Toolbar.openLockDialog();
- },
- "toolbar_button_link": function () {
- return Toolbar.openLinkDialog();
- },
- "toolbar_button_chat": function () {
- return BottomToolbar.toggleChat();
- },
- "toolbar_button_prezi": function () {
- return Prezi.openPreziDialog();
- },
- "toolbar_button_etherpad": function () {
- return Etherpad.toggleEtherpad(0);
- },
- "toolbar_button_desktopsharing": function () {
- return APP.desktopsharing.toggleScreenSharing();
- },
- "toolbar_button_fullScreen": function()
- {
- UIUtil.buttonClick("#fullScreen", "icon-full-screen icon-exit-full-screen");
- return Toolbar.toggleFullScreen();
- },
- "toolbar_button_sip": function () {
- return callSipButtonClicked();
- },
- "toolbar_button_dialpad": function () {
- return dialpadButtonClicked();
- },
- "toolbar_button_settings": function () {
- PanelToggler.toggleSettingsMenu();
- },
- "toolbar_button_hangup": function () {
- return hangup();
- },
- "toolbar_button_login": function () {
- Toolbar.authenticateClicked();
- },
- "toolbar_button_logout": function () {
- // Ask for confirmation
- messageHandler.openTwoButtonDialog(
- "dialog.logoutTitle",
- null,
- "dialog.logoutQuestion",
- null,
- false,
- "dialog.Yes",
- function (evt, yes) {
- if (yes) {
- APP.xmpp.logout(function (url) {
- if (url) {
- window.location.href = url;
- } else {
- hangup();
- }
- });
- }
- });
- }
-};
-
-function hangup() {
- APP.xmpp.disposeConference();
- if(config.enableWelcomePage)
- {
- setTimeout(function()
- {
- window.localStorage.welcomePageDisabled = false;
- window.location.pathname = "/";
- }, 10000);
-
- }
-
- var title = APP.translation.generateTranslationHTML(
- "dialog.sessTerminated");
- var msg = APP.translation.generateTranslationHTML(
- "dialog.hungUp");
- var button = APP.translation.generateTranslationHTML(
- "dialog.joinAgain");
- var buttons = [];
- buttons.push({title: button, value: true});
-
- UI.messageHandler.openDialog(
- title,
- msg,
- true,
- buttons,
- function(event, value, message, formVals)
- {
- window.location.reload();
- return false;
- }
- );
-}
-
-/**
- * Starts or stops the recording for the conference.
- */
-
-function toggleRecording() {
- APP.xmpp.toggleRecording(function (callback) {
- var msg = APP.translation.generateTranslationHTML(
- "dialog.recordingToken");
- var token = APP.translation.translateString("dialog.token");
- APP.UI.messageHandler.openTwoButtonDialog(null, null, null,
- '' + msg + ' ' +
- ' ',
- false,
- "dialog.Save",
- function (e, v, m, f) {
- if (v) {
- var token = f.recordingToken;
-
- if (token) {
- callback(UIUtil.escapeHtml(token));
- }
- }
- },
- null,
- function () { },
- ':input:first'
- );
- }, Toolbar.setRecordingButtonState, Toolbar.setRecordingButtonState);
-}
-
-/**
- * Locks / unlocks the room.
- */
-function lockRoom(lock) {
- var currentSharedKey = '';
- if (lock)
- currentSharedKey = sharedKey;
-
- APP.xmpp.lockRoom(currentSharedKey, function (res) {
- // password is required
- if (sharedKey)
- {
- console.log('set room password');
- Toolbar.lockLockButton();
- }
- else
- {
- console.log('removed room password');
- Toolbar.unlockLockButton();
- }
- }, function (err) {
- console.warn('setting password failed', err);
- messageHandler.showError("dialog.lockTitle",
- "dialog.lockMessage");
- Toolbar.setSharedKey('');
- }, function () {
- console.warn('room passwords not supported');
- messageHandler.showError("dialog.warning",
- "dialog.passwordNotSupported");
- Toolbar.setSharedKey('');
- });
-};
-
-/**
- * Invite participants to conference.
- */
-function inviteParticipants() {
- if (roomUrl === null)
- return;
-
- var sharedKeyText = "";
- if (sharedKey && sharedKey.length > 0) {
- sharedKeyText =
- APP.translation.translateString("email.sharedKey",
- {sharedKey: sharedKey});
- sharedKeyText = sharedKeyText.replace(/\n/g, "%0D%0A");
- }
-
- var supportedBrowsers = "Chromium, Google Chrome " +
- APP.translation.translateString("email.and") + " Opera";
- var conferenceName = roomUrl.substring(roomUrl.lastIndexOf('/') + 1);
- var subject = APP.translation.translateString("email.subject",
- {appName:interfaceConfig.APP_NAME, conferenceName: conferenceName});
- var body = APP.translation.translateString("email.body",
- {appName:interfaceConfig.APP_NAME, sharedKeyText: sharedKeyText,
- roomUrl: roomUrl, supportedBrowsers: supportedBrowsers});
- body = body.replace(/\n/g, "%0D%0A");
-
- if (window.localStorage.displayname) {
- body += "%0D%0A%0D%0A" + window.localStorage.displayname;
- }
-
- if (interfaceConfig.INVITATION_POWERED_BY) {
- body += "%0D%0A%0D%0A--%0D%0Apowered by jitsi.org";
- }
-
- window.open("mailto:?subject=" + subject + "&body=" + body, '_blank');
-}
-
-function dialpadButtonClicked()
-{
- //TODO show the dialpad window
-}
-
-function callSipButtonClicked()
-{
- var defaultNumber
- = config.defaultSipNumber ? config.defaultSipNumber : '';
-
- var sipMsg = APP.translation.generateTranslationHTML(
- "dialog.sipMsg");
- messageHandler.openTwoButtonDialog(null, null, null,
- '' + sipMsg + ' ' +
- ' ',
- false,
- "dialog.Dial",
- function (e, v, m, f) {
- if (v) {
- var numberInput = f.sipNumber;
- if (numberInput) {
- APP.xmpp.dial(
- numberInput, 'fromnumber', UI.getRoomName(), sharedKey);
- }
- }
- },
- null, null, ':input:first'
- );
-}
-
-var Toolbar = (function (my) {
-
- my.init = function (ui) {
- for(var k in buttonHandlers)
- $("#" + k).click(buttonHandlers[k]);
- UI = ui;
- // Update login info
- APP.xmpp.addListener(
- AuthenticationEvents.IDENTITY_UPDATED,
- function (authenticationEnabled, userIdentity) {
-
- var loggedIn = false;
- if (userIdentity) {
- loggedIn = true;
- }
-
- Toolbar.showAuthenticateButton(authenticationEnabled);
-
- if (authenticationEnabled) {
- Toolbar.setAuthenticatedIdentity(userIdentity);
-
- Toolbar.showLoginButton(!loggedIn);
- Toolbar.showLogoutButton(loggedIn);
- }
- }
- );
- },
-
- /**
- * Sets shared key
- * @param sKey the shared key
- */
- my.setSharedKey = function (sKey) {
- sharedKey = sKey;
- };
-
- my.authenticateClicked = function () {
- Authentication.focusAuthenticationWindow();
- if (!APP.xmpp.isExternalAuthEnabled()) {
- Authentication.xmppAuthenticate();
- return;
- }
- // Get authentication URL
- if (!APP.xmpp.getMUCJoined()) {
- APP.xmpp.getLoginUrl(UI.getRoomName(), function (url) {
- // If conference has not been started yet - redirect to login page
- window.location.href = url;
- });
- } else {
- APP.xmpp.getPopupLoginUrl(UI.getRoomName(), function (url) {
- // Otherwise - open popup with authentication URL
- var authenticationWindow = Authentication.createAuthenticationWindow(
- function () {
- // On popup closed - retry room allocation
- APP.xmpp.allocateConferenceFocus(
- APP.UI.getRoomName(),
- function () { console.info("AUTH DONE"); }
- );
- }, url);
- if (!authenticationWindow) {
- messageHandler.openMessageDialog(
- null, "dialog.popupError");
- }
- });
- }
- };
-
- /**
- * Updates the room invite url.
- */
- my.updateRoomUrl = function (newRoomUrl) {
- roomUrl = newRoomUrl;
-
- // If the invite dialog has been already opened we update the information.
- var inviteLink = document.getElementById('inviteLinkRef');
- if (inviteLink) {
- inviteLink.value = roomUrl;
- inviteLink.select();
- $('#inviteLinkRef').parent()
- .find('button[value=true]').prop('disabled', false);
- }
- };
-
- /**
- * Disables and enables some of the buttons.
- */
- my.setupButtonsFromConfig = function () {
- if (config.disablePrezi)
- {
- $("#prezi_button").css({display: "none"});
- }
- };
-
- /**
- * Opens the lock room dialog.
- */
- my.openLockDialog = function () {
- // Only the focus is able to set a shared key.
- if (!APP.xmpp.isModerator()) {
- if (sharedKey) {
- messageHandler.openMessageDialog(null,
- "dialog.passwordError");
- } else {
- messageHandler.openMessageDialog(null, "dialog.passwordError2");
- }
- } else {
- if (sharedKey) {
- messageHandler.openTwoButtonDialog(null, null,
- "dialog.passwordCheck",
- null,
- false,
- "dialog.Remove",
- function (e, v) {
- if (v) {
- Toolbar.setSharedKey('');
- lockRoom(false);
- }
- });
- } else {
- var msg = APP.translation.generateTranslationHTML(
- "dialog.passwordMsg");
- var yourPassword = APP.translation.translateString(
- "dialog.yourPassword");
- messageHandler.openTwoButtonDialog(null, null, null,
- '' + msg + ' ' +
- ' ',
- false,
- "dialog.Save",
- function (e, v, m, f) {
- if (v) {
- var lockKey = f.lockKey;
-
- if (lockKey) {
- Toolbar.setSharedKey(
- UIUtil.escapeHtml(lockKey));
- lockRoom(true);
- }
- }
- },
- null, null, 'input:first'
- );
- }
- }
- };
-
- /**
- * Opens the invite link dialog.
- */
- my.openLinkDialog = function () {
- var inviteAttreibutes;
-
- if (roomUrl === null) {
- inviteAttreibutes = 'data-i18n="[value]roomUrlDefaultMsg" value="' +
- APP.translation.translateString("roomUrlDefaultMsg") + '"';
- } else {
- inviteAttreibutes = "value=\"" + encodeURI(roomUrl) + "\"";
- }
- messageHandler.openTwoButtonDialog("dialog.shareLink",
- null, null,
- ' ',
- false,
- "dialog.Invite",
- function (e, v) {
- if (v) {
- if (roomUrl) {
- inviteParticipants();
- }
- }
- },
- function (event) {
- if (roomUrl) {
- document.getElementById('inviteLinkRef').select();
- } else {
- if (event && event.target)
- $(event.target)
- .find('button[value=true]').prop('disabled', true);
- }
- }
- );
- };
-
- /**
- * Opens the settings dialog.
- * FIXME: not used ?
- */
- my.openSettingsDialog = function () {
- var settings1 = APP.translation.generateTranslationHTML(
- "dialog.settings1");
- var settings2 = APP.translation.generateTranslationHTML(
- "dialog.settings2");
- var settings3 = APP.translation.generateTranslationHTML(
- "dialog.settings3");
-
- var yourPassword = APP.translation.translateString(
- "dialog.yourPassword");
-
- messageHandler.openTwoButtonDialog(null,
- '' + settings1 + ' ' +
- ' ' +
- settings2 + ' ' +
- ' ' +
- settings3 +
- ' ',
- null,
- null,
- false,
- "dialog.Save",
- function () {
- document.getElementById('lockKey').focus();
- },
- function (e, v) {
- if (v) {
- if ($('#initMuted').is(":checked")) {
- // it is checked
- }
-
- if ($('#requireNicknames').is(":checked")) {
- // it is checked
- }
- /*
- var lockKey = document.getElementById('lockKey');
-
- if (lockKey.value) {
- setSharedKey(lockKey.value);
- lockRoom(true);
- }
- */
- }
- }
- );
- };
-
- /**
- * Toggles the application in and out of full screen mode
- * (a.k.a. presentation mode in Chrome).
- */
- my.toggleFullScreen = function () {
- var fsElement = document.documentElement;
-
- if (!document.mozFullScreen && !document.webkitIsFullScreen) {
- //Enter Full Screen
- if (fsElement.mozRequestFullScreen) {
- fsElement.mozRequestFullScreen();
- }
- else {
- fsElement.webkitRequestFullScreen(Element.ALLOW_KEYBOARD_INPUT);
- }
- } else {
- //Exit Full Screen
- if (document.mozCancelFullScreen) {
- document.mozCancelFullScreen();
- } else {
- document.webkitCancelFullScreen();
- }
- }
- };
- /**
- * Unlocks the lock button state.
- */
- my.unlockLockButton = function () {
- if ($("#lockIcon").hasClass("icon-security-locked"))
- UIUtil.buttonClick("#lockIcon", "icon-security icon-security-locked");
- };
- /**
- * Updates the lock button state to locked.
- */
- my.lockLockButton = function () {
- if ($("#lockIcon").hasClass("icon-security"))
- UIUtil.buttonClick("#lockIcon", "icon-security icon-security-locked");
- };
-
- /**
- * Shows or hides authentication button
- * @param show true to show or false to hide
- */
- my.showAuthenticateButton = function (show) {
- if (show) {
- $('#authentication').css({display: "inline"});
- }
- else {
- $('#authentication').css({display: "none"});
- }
- };
-
- // Shows or hides the 'recording' button.
- my.showRecordingButton = function (show) {
- if (!config.enableRecording) {
- return;
- }
-
- if (show) {
- $('#recording').css({display: "inline"});
- }
- else {
- $('#recording').css({display: "none"});
- }
- };
-
- // Sets the state of the recording button
- my.setRecordingButtonState = function (isRecording) {
- var selector = $('#recordButton');
- if (isRecording) {
- selector.removeClass("icon-recEnable");
- selector.addClass("icon-recEnable active");
- } else {
- selector.removeClass("icon-recEnable active");
- selector.addClass("icon-recEnable");
- }
- };
-
- // Shows or hides SIP calls button
- my.showSipCallButton = function (show) {
- if (APP.xmpp.isSipGatewayEnabled() && show) {
- $('#sipCallButton').css({display: "inline-block"});
- } else {
- $('#sipCallButton').css({display: "none"});
- }
- };
-
- // Shows or hides the dialpad button
- my.showDialPadButton = function (show) {
- if (show) {
- $('#dialPadButton').css({display: "inline-block"});
- } else {
- $('#dialPadButton').css({display: "none"});
- }
- };
-
- /**
- * Displays user authenticated identity name(login).
- * @param authIdentity identity name to be displayed.
- */
- my.setAuthenticatedIdentity = function (authIdentity) {
- if (authIdentity) {
- var selector = $('#toolbar_auth_identity');
- selector.css({display: "list-item"});
- selector.text(authIdentity);
- } else {
- $('#toolbar_auth_identity').css({display: "none"});
- }
- };
-
- /**
- * Shows/hides login button.
- * @param show true to show
- */
- my.showLoginButton = function (show) {
- if (show) {
- $('#toolbar_button_login').css({display: "list-item"});
- } else {
- $('#toolbar_button_login').css({display: "none"});
- }
- };
-
- /**
- * Shows/hides logout button.
- * @param show true to show
- */
- my.showLogoutButton = function (show) {
- if (show) {
- $('#toolbar_button_logout').css({display: "list-item"});
- } else {
- $('#toolbar_button_logout').css({display: "none"});
- }
- };
-
- /**
- * Sets the state of the button. The button has blue glow if desktop
- * streaming is active.
- * @param active the state of the desktop streaming.
- */
- my.changeDesktopSharingButtonState = function (active) {
- var button = $("#desktopsharing > a");
- if (active)
- {
- button.addClass("glow");
- }
- else
- {
- button.removeClass("glow");
- }
- };
-
- return my;
-}(Toolbar || {}));
+},{"../../../../service/translation/languages":108,"../../avatar/Avatar":16,"../../util/UIUtil":33,"./../../../settings/Settings":47}],27:[function(require,module,exports){
+var PanelToggler = require("../side_pannels/SidePanelToggler");
+
+var buttonHandlers = {
+ "bottom_toolbar_contact_list": function () {
+ BottomToolbar.toggleContactList();
+ },
+ "bottom_toolbar_film_strip": function () {
+ BottomToolbar.toggleFilmStrip();
+ },
+ "bottom_toolbar_chat": function () {
+ BottomToolbar.toggleChat();
+ }
+};
+
+var BottomToolbar = (function (my) {
+ my.init = function () {
+ for(var k in buttonHandlers)
+ $("#" + k).click(buttonHandlers[k]);
+ };
+
+ my.toggleChat = function() {
+ PanelToggler.toggleChat();
+ };
+
+ my.toggleContactList = function() {
+ PanelToggler.toggleContactList();
+ };
+
+ my.toggleFilmStrip = function() {
+ var filmstrip = $("#remoteVideos");
+ filmstrip.toggleClass("hidden");
+ };
+
+ $(document).bind("remotevideo.resized", function (event, width, height) {
+ var bottom = (height - $('#bottomToolbar').outerHeight())/2 + 18;
+
+ $('#bottomToolbar').css({bottom: bottom + 'px'});
+ });
+
+ return my;
+}(BottomToolbar || {}));
+
+module.exports = BottomToolbar;
+},{"../side_pannels/SidePanelToggler":20}],28:[function(require,module,exports){
+/* global APP,$, buttonClick, config, lockRoom,
+ setSharedKey, Util */
+var messageHandler = require("../util/MessageHandler");
+var BottomToolbar = require("./BottomToolbar");
+var Prezi = require("../prezi/Prezi");
+var Etherpad = require("../etherpad/Etherpad");
+var PanelToggler = require("../side_pannels/SidePanelToggler");
+var Authentication = require("../authentication/Authentication");
+var UIUtil = require("../util/UIUtil");
+var AuthenticationEvents
+ = require("../../../service/authentication/AuthenticationEvents");
+
+var roomUrl = null;
+var sharedKey = '';
+var UI = null;
+
+var buttonHandlers =
+{
+ "toolbar_button_mute": function () {
+ return APP.UI.toggleAudio();
+ },
+ "toolbar_button_camera": function () {
+ return APP.UI.toggleVideo();
+ },
+ /*"toolbar_button_authentication": function () {
+ return Toolbar.authenticateClicked();
+ },*/
+ "toolbar_button_record": function () {
+ return toggleRecording();
+ },
+ "toolbar_button_security": function () {
+ return Toolbar.openLockDialog();
+ },
+ "toolbar_button_link": function () {
+ return Toolbar.openLinkDialog();
+ },
+ "toolbar_button_chat": function () {
+ return BottomToolbar.toggleChat();
+ },
+ "toolbar_button_prezi": function () {
+ return Prezi.openPreziDialog();
+ },
+ "toolbar_button_etherpad": function () {
+ return Etherpad.toggleEtherpad(0);
+ },
+ "toolbar_button_desktopsharing": function () {
+ return APP.desktopsharing.toggleScreenSharing();
+ },
+ "toolbar_button_fullScreen": function()
+ {
+ UIUtil.buttonClick("#fullScreen", "icon-full-screen icon-exit-full-screen");
+ return Toolbar.toggleFullScreen();
+ },
+ "toolbar_button_sip": function () {
+ return callSipButtonClicked();
+ },
+ "toolbar_button_dialpad": function () {
+ return dialpadButtonClicked();
+ },
+ "toolbar_button_settings": function () {
+ PanelToggler.toggleSettingsMenu();
+ },
+ "toolbar_button_hangup": function () {
+ return hangup();
+ },
+ "toolbar_button_login": function () {
+ Toolbar.authenticateClicked();
+ },
+ "toolbar_button_logout": function () {
+ // Ask for confirmation
+ messageHandler.openTwoButtonDialog(
+ "dialog.logoutTitle",
+ null,
+ "dialog.logoutQuestion",
+ null,
+ false,
+ "dialog.Yes",
+ function (evt, yes) {
+ if (yes) {
+ APP.xmpp.logout(function (url) {
+ if (url) {
+ window.location.href = url;
+ } else {
+ hangup();
+ }
+ });
+ }
+ });
+ }
+};
+
+function hangup() {
+ APP.xmpp.disposeConference();
+ if(config.enableWelcomePage)
+ {
+ setTimeout(function()
+ {
+ window.localStorage.welcomePageDisabled = false;
+ window.location.pathname = "/";
+ }, 10000);
+
+ }
+
+ var title = APP.translation.generateTranslationHTML(
+ "dialog.sessTerminated");
+ var msg = APP.translation.generateTranslationHTML(
+ "dialog.hungUp");
+ var button = APP.translation.generateTranslationHTML(
+ "dialog.joinAgain");
+ var buttons = [];
+ buttons.push({title: button, value: true});
+
+ UI.messageHandler.openDialog(
+ title,
+ msg,
+ true,
+ buttons,
+ function(event, value, message, formVals)
+ {
+ window.location.reload();
+ return false;
+ }
+ );
+}
+
+/**
+ * Starts or stops the recording for the conference.
+ */
+
+function toggleRecording() {
+ APP.xmpp.toggleRecording(function (callback) {
+ var msg = APP.translation.generateTranslationHTML(
+ "dialog.recordingToken");
+ var token = APP.translation.translateString("dialog.token");
+ APP.UI.messageHandler.openTwoButtonDialog(null, null, null,
+ '' + msg + ' ' +
+ ' ',
+ false,
+ "dialog.Save",
+ function (e, v, m, f) {
+ if (v) {
+ var token = f.recordingToken;
+
+ if (token) {
+ callback(UIUtil.escapeHtml(token));
+ }
+ }
+ },
+ null,
+ function () { },
+ ':input:first'
+ );
+ }, Toolbar.setRecordingButtonState, Toolbar.setRecordingButtonState);
+}
+
+/**
+ * Locks / unlocks the room.
+ */
+function lockRoom(lock) {
+ var currentSharedKey = '';
+ if (lock)
+ currentSharedKey = sharedKey;
+
+ APP.xmpp.lockRoom(currentSharedKey, function (res) {
+ // password is required
+ if (sharedKey)
+ {
+ console.log('set room password');
+ Toolbar.lockLockButton();
+ }
+ else
+ {
+ console.log('removed room password');
+ Toolbar.unlockLockButton();
+ }
+ }, function (err) {
+ console.warn('setting password failed', err);
+ messageHandler.showError("dialog.lockTitle",
+ "dialog.lockMessage");
+ Toolbar.setSharedKey('');
+ }, function () {
+ console.warn('room passwords not supported');
+ messageHandler.showError("dialog.warning",
+ "dialog.passwordNotSupported");
+ Toolbar.setSharedKey('');
+ });
+};
+
+/**
+ * Invite participants to conference.
+ */
+function inviteParticipants() {
+ if (roomUrl === null)
+ return;
+
+ var sharedKeyText = "";
+ if (sharedKey && sharedKey.length > 0) {
+ sharedKeyText =
+ APP.translation.translateString("email.sharedKey",
+ {sharedKey: sharedKey});
+ sharedKeyText = sharedKeyText.replace(/\n/g, "%0D%0A");
+ }
+
+ var supportedBrowsers = "Chromium, Google Chrome " +
+ APP.translation.translateString("email.and") + " Opera";
+ var conferenceName = roomUrl.substring(roomUrl.lastIndexOf('/') + 1);
+ var subject = APP.translation.translateString("email.subject",
+ {appName:interfaceConfig.APP_NAME, conferenceName: conferenceName});
+ var body = APP.translation.translateString("email.body",
+ {appName:interfaceConfig.APP_NAME, sharedKeyText: sharedKeyText,
+ roomUrl: roomUrl, supportedBrowsers: supportedBrowsers});
+ body = body.replace(/\n/g, "%0D%0A");
+
+ if (window.localStorage.displayname) {
+ body += "%0D%0A%0D%0A" + window.localStorage.displayname;
+ }
+
+ if (interfaceConfig.INVITATION_POWERED_BY) {
+ body += "%0D%0A%0D%0A--%0D%0Apowered by jitsi.org";
+ }
+
+ window.open("mailto:?subject=" + subject + "&body=" + body, '_blank');
+}
+
+function dialpadButtonClicked()
+{
+ //TODO show the dialpad window
+}
+
+function callSipButtonClicked()
+{
+ var defaultNumber
+ = config.defaultSipNumber ? config.defaultSipNumber : '';
+
+ var sipMsg = APP.translation.generateTranslationHTML(
+ "dialog.sipMsg");
+ messageHandler.openTwoButtonDialog(null, null, null,
+ '' + sipMsg + ' ' +
+ ' ',
+ false,
+ "dialog.Dial",
+ function (e, v, m, f) {
+ if (v) {
+ var numberInput = f.sipNumber;
+ if (numberInput) {
+ APP.xmpp.dial(
+ numberInput, 'fromnumber', UI.getRoomName(), sharedKey);
+ }
+ }
+ },
+ null, null, ':input:first'
+ );
+}
+
+var Toolbar = (function (my) {
+
+ my.init = function (ui) {
+ for(var k in buttonHandlers)
+ $("#" + k).click(buttonHandlers[k]);
+ UI = ui;
+ // Update login info
+ APP.xmpp.addListener(
+ AuthenticationEvents.IDENTITY_UPDATED,
+ function (authenticationEnabled, userIdentity) {
+
+ var loggedIn = false;
+ if (userIdentity) {
+ loggedIn = true;
+ }
+
+ Toolbar.showAuthenticateButton(authenticationEnabled);
+
+ if (authenticationEnabled) {
+ Toolbar.setAuthenticatedIdentity(userIdentity);
+
+ Toolbar.showLoginButton(!loggedIn);
+ Toolbar.showLogoutButton(loggedIn);
+ }
+ }
+ );
+ },
+
+ /**
+ * Sets shared key
+ * @param sKey the shared key
+ */
+ my.setSharedKey = function (sKey) {
+ sharedKey = sKey;
+ };
+
+ my.authenticateClicked = function () {
+ Authentication.focusAuthenticationWindow();
+ if (!APP.xmpp.isExternalAuthEnabled()) {
+ Authentication.xmppAuthenticate();
+ return;
+ }
+ // Get authentication URL
+ if (!APP.xmpp.getMUCJoined()) {
+ APP.xmpp.getLoginUrl(UI.getRoomName(), function (url) {
+ // If conference has not been started yet - redirect to login page
+ window.location.href = url;
+ });
+ } else {
+ APP.xmpp.getPopupLoginUrl(UI.getRoomName(), function (url) {
+ // Otherwise - open popup with authentication URL
+ var authenticationWindow = Authentication.createAuthenticationWindow(
+ function () {
+ // On popup closed - retry room allocation
+ APP.xmpp.allocateConferenceFocus(
+ APP.UI.getRoomName(),
+ function () { console.info("AUTH DONE"); }
+ );
+ }, url);
+ if (!authenticationWindow) {
+ messageHandler.openMessageDialog(
+ null, "dialog.popupError");
+ }
+ });
+ }
+ };
+
+ /**
+ * Updates the room invite url.
+ */
+ my.updateRoomUrl = function (newRoomUrl) {
+ roomUrl = newRoomUrl;
+
+ // If the invite dialog has been already opened we update the information.
+ var inviteLink = document.getElementById('inviteLinkRef');
+ if (inviteLink) {
+ inviteLink.value = roomUrl;
+ inviteLink.select();
+ $('#inviteLinkRef').parent()
+ .find('button[value=true]').prop('disabled', false);
+ }
+ };
+
+ /**
+ * Disables and enables some of the buttons.
+ */
+ my.setupButtonsFromConfig = function () {
+ if (config.disablePrezi)
+ {
+ $("#prezi_button").css({display: "none"});
+ }
+ };
+
+ /**
+ * Opens the lock room dialog.
+ */
+ my.openLockDialog = function () {
+ // Only the focus is able to set a shared key.
+ if (!APP.xmpp.isModerator()) {
+ if (sharedKey) {
+ messageHandler.openMessageDialog(null,
+ "dialog.passwordError");
+ } else {
+ messageHandler.openMessageDialog(null, "dialog.passwordError2");
+ }
+ } else {
+ if (sharedKey) {
+ messageHandler.openTwoButtonDialog(null, null,
+ "dialog.passwordCheck",
+ null,
+ false,
+ "dialog.Remove",
+ function (e, v) {
+ if (v) {
+ Toolbar.setSharedKey('');
+ lockRoom(false);
+ }
+ });
+ } else {
+ var msg = APP.translation.generateTranslationHTML(
+ "dialog.passwordMsg");
+ var yourPassword = APP.translation.translateString(
+ "dialog.yourPassword");
+ messageHandler.openTwoButtonDialog(null, null, null,
+ '' + msg + ' ' +
+ ' ',
+ false,
+ "dialog.Save",
+ function (e, v, m, f) {
+ if (v) {
+ var lockKey = f.lockKey;
+
+ if (lockKey) {
+ Toolbar.setSharedKey(
+ UIUtil.escapeHtml(lockKey));
+ lockRoom(true);
+ }
+ }
+ },
+ null, null, 'input:first'
+ );
+ }
+ }
+ };
+
+ /**
+ * Opens the invite link dialog.
+ */
+ my.openLinkDialog = function () {
+ var inviteAttreibutes;
+
+ if (roomUrl === null) {
+ inviteAttreibutes = 'data-i18n="[value]roomUrlDefaultMsg" value="' +
+ APP.translation.translateString("roomUrlDefaultMsg") + '"';
+ } else {
+ inviteAttreibutes = "value=\"" + encodeURI(roomUrl) + "\"";
+ }
+ messageHandler.openTwoButtonDialog("dialog.shareLink",
+ null, null,
+ ' ',
+ false,
+ "dialog.Invite",
+ function (e, v) {
+ if (v) {
+ if (roomUrl) {
+ inviteParticipants();
+ }
+ }
+ },
+ function (event) {
+ if (roomUrl) {
+ document.getElementById('inviteLinkRef').select();
+ } else {
+ if (event && event.target)
+ $(event.target)
+ .find('button[value=true]').prop('disabled', true);
+ }
+ }
+ );
+ };
+
+ /**
+ * Opens the settings dialog.
+ * FIXME: not used ?
+ */
+ my.openSettingsDialog = function () {
+ var settings1 = APP.translation.generateTranslationHTML(
+ "dialog.settings1");
+ var settings2 = APP.translation.generateTranslationHTML(
+ "dialog.settings2");
+ var settings3 = APP.translation.generateTranslationHTML(
+ "dialog.settings3");
+
+ var yourPassword = APP.translation.translateString(
+ "dialog.yourPassword");
+
+ messageHandler.openTwoButtonDialog(null,
+ '' + settings1 + ' ' +
+ ' ' +
+ settings2 + ' ' +
+ ' ' +
+ settings3 +
+ ' ',
+ null,
+ null,
+ false,
+ "dialog.Save",
+ function () {
+ document.getElementById('lockKey').focus();
+ },
+ function (e, v) {
+ if (v) {
+ if ($('#initMuted').is(":checked")) {
+ // it is checked
+ }
+
+ if ($('#requireNicknames').is(":checked")) {
+ // it is checked
+ }
+ /*
+ var lockKey = document.getElementById('lockKey');
+
+ if (lockKey.value) {
+ setSharedKey(lockKey.value);
+ lockRoom(true);
+ }
+ */
+ }
+ }
+ );
+ };
+
+ /**
+ * Toggles the application in and out of full screen mode
+ * (a.k.a. presentation mode in Chrome).
+ */
+ my.toggleFullScreen = function () {
+ var fsElement = document.documentElement;
+
+ if (!document.mozFullScreen && !document.webkitIsFullScreen) {
+ //Enter Full Screen
+ if (fsElement.mozRequestFullScreen) {
+ fsElement.mozRequestFullScreen();
+ }
+ else {
+ fsElement.webkitRequestFullScreen(Element.ALLOW_KEYBOARD_INPUT);
+ }
+ } else {
+ //Exit Full Screen
+ if (document.mozCancelFullScreen) {
+ document.mozCancelFullScreen();
+ } else {
+ document.webkitCancelFullScreen();
+ }
+ }
+ };
+ /**
+ * Unlocks the lock button state.
+ */
+ my.unlockLockButton = function () {
+ if ($("#lockIcon").hasClass("icon-security-locked"))
+ UIUtil.buttonClick("#lockIcon", "icon-security icon-security-locked");
+ };
+ /**
+ * Updates the lock button state to locked.
+ */
+ my.lockLockButton = function () {
+ if ($("#lockIcon").hasClass("icon-security"))
+ UIUtil.buttonClick("#lockIcon", "icon-security icon-security-locked");
+ };
+
+ /**
+ * Shows or hides authentication button
+ * @param show true to show or false to hide
+ */
+ my.showAuthenticateButton = function (show) {
+ if (show) {
+ $('#authentication').css({display: "inline"});
+ }
+ else {
+ $('#authentication').css({display: "none"});
+ }
+ };
+
+ // Shows or hides the 'recording' button.
+ my.showRecordingButton = function (show) {
+ if (!config.enableRecording) {
+ return;
+ }
+
+ if (show) {
+ $('#recording').css({display: "inline"});
+ }
+ else {
+ $('#recording').css({display: "none"});
+ }
+ };
+
+ // Sets the state of the recording button
+ my.setRecordingButtonState = function (isRecording) {
+ var selector = $('#recordButton');
+ if (isRecording) {
+ selector.removeClass("icon-recEnable");
+ selector.addClass("icon-recEnable active");
+ } else {
+ selector.removeClass("icon-recEnable active");
+ selector.addClass("icon-recEnable");
+ }
+ };
+
+ // Shows or hides SIP calls button
+ my.showSipCallButton = function (show) {
+ if (APP.xmpp.isSipGatewayEnabled() && show) {
+ $('#sipCallButton').css({display: "inline-block"});
+ } else {
+ $('#sipCallButton').css({display: "none"});
+ }
+ };
+
+ // Shows or hides the dialpad button
+ my.showDialPadButton = function (show) {
+ if (show) {
+ $('#dialPadButton').css({display: "inline-block"});
+ } else {
+ $('#dialPadButton').css({display: "none"});
+ }
+ };
+
+ /**
+ * Displays user authenticated identity name(login).
+ * @param authIdentity identity name to be displayed.
+ */
+ my.setAuthenticatedIdentity = function (authIdentity) {
+ if (authIdentity) {
+ var selector = $('#toolbar_auth_identity');
+ selector.css({display: "list-item"});
+ selector.text(authIdentity);
+ } else {
+ $('#toolbar_auth_identity').css({display: "none"});
+ }
+ };
+
+ /**
+ * Shows/hides login button.
+ * @param show true to show
+ */
+ my.showLoginButton = function (show) {
+ if (show) {
+ $('#toolbar_button_login').css({display: "list-item"});
+ } else {
+ $('#toolbar_button_login').css({display: "none"});
+ }
+ };
+
+ /**
+ * Shows/hides logout button.
+ * @param show true to show
+ */
+ my.showLogoutButton = function (show) {
+ if (show) {
+ $('#toolbar_button_logout').css({display: "list-item"});
+ } else {
+ $('#toolbar_button_logout').css({display: "none"});
+ }
+ };
+
+ /**
+ * Sets the state of the button. The button has blue glow if desktop
+ * streaming is active.
+ * @param active the state of the desktop streaming.
+ */
+ my.changeDesktopSharingButtonState = function (active) {
+ var button = $("#desktopsharing > a");
+ if (active)
+ {
+ button.addClass("glow");
+ }
+ else
+ {
+ button.removeClass("glow");
+ }
+ };
+
+ return my;
+}(Toolbar || {}));
+
module.exports = Toolbar;
-},{"../../../service/authentication/AuthenticationEvents":106,"../authentication/Authentication":12,"../etherpad/Etherpad":15,"../prezi/Prezi":16,"../side_pannels/SidePanelToggler":18,"../util/MessageHandler":29,"../util/UIUtil":31,"./BottomToolbar":25}],27:[function(require,module,exports){
-/* global $, interfaceConfig, Moderator, DesktopStreaming.showDesktopSharingButton */
-
-var toolbarTimeoutObject,
- toolbarTimeout = interfaceConfig.INITIAL_TOOLBAR_TIMEOUT;
-
-function showDesktopSharingButton() {
- if (APP.desktopsharing.isDesktopSharingEnabled()) {
- $('#desktopsharing').css({display: "inline"});
- } else {
- $('#desktopsharing').css({display: "none"});
- }
-}
-
-/**
- * Hides the toolbar.
- */
-function hideToolbar() {
- if(config.alwaysVisibleToolbar)
- return;
-
- var header = $("#header"),
- bottomToolbar = $("#bottomToolbar");
- var isToolbarHover = false;
- header.find('*').each(function () {
- var id = $(this).attr('id');
- if ($("#" + id + ":hover").length > 0) {
- isToolbarHover = true;
- }
- });
- if ($("#bottomToolbar:hover").length > 0) {
- isToolbarHover = true;
- }
-
- clearTimeout(toolbarTimeoutObject);
- toolbarTimeoutObject = null;
-
- if (!isToolbarHover) {
- header.hide("slide", { direction: "up", duration: 300});
- $('#subject').animate({top: "-=40"}, 300);
- if ($("#remoteVideos").hasClass("hidden")) {
- bottomToolbar.hide(
- "slide", {direction: "right", duration: 300});
- }
- }
- else {
- toolbarTimeoutObject = setTimeout(hideToolbar, toolbarTimeout);
- }
-}
-
-var ToolbarToggler = {
- /**
- * Shows the main toolbar.
- */
- showToolbar: function () {
- var header = $("#header"),
- bottomToolbar = $("#bottomToolbar");
- if (!header.is(':visible') || !bottomToolbar.is(":visible")) {
- header.show("slide", { direction: "up", duration: 300});
- $('#subject').animate({top: "+=40"}, 300);
- if (!bottomToolbar.is(":visible")) {
- bottomToolbar.show(
- "slide", {direction: "right", duration: 300});
- }
-
- if (toolbarTimeoutObject) {
- clearTimeout(toolbarTimeoutObject);
- toolbarTimeoutObject = null;
- }
- toolbarTimeoutObject = setTimeout(hideToolbar, toolbarTimeout);
- toolbarTimeout = interfaceConfig.TOOLBAR_TIMEOUT;
- }
-
- if (APP.xmpp.isModerator())
- {
-// TODO: Enable settings functionality.
-// Need to uncomment the settings button in index.html.
-// $('#settingsButton').css({visibility:"visible"});
- }
-
- // Show/hide desktop sharing button
- showDesktopSharingButton();
- },
-
-
- /**
- * Docks/undocks the toolbar.
- *
- * @param isDock indicates what operation to perform
- */
- dockToolbar: function (isDock) {
- if (isDock) {
- // First make sure the toolbar is shown.
- if (!$('#header').is(':visible')) {
- this.showToolbar();
- }
-
- // Then clear the time out, to dock the toolbar.
- if (toolbarTimeoutObject) {
- clearTimeout(toolbarTimeoutObject);
- toolbarTimeoutObject = null;
- }
- }
- else {
- if (!$('#header').is(':visible')) {
- this.showToolbar();
- }
- else {
- toolbarTimeoutObject = setTimeout(hideToolbar, toolbarTimeout);
- }
- }
- },
-
- showDesktopSharingButton: showDesktopSharingButton
-
-};
-
+},{"../../../service/authentication/AuthenticationEvents":104,"../authentication/Authentication":14,"../etherpad/Etherpad":17,"../prezi/Prezi":18,"../side_pannels/SidePanelToggler":20,"../util/MessageHandler":31,"../util/UIUtil":33,"./BottomToolbar":27}],29:[function(require,module,exports){
+/* global $, interfaceConfig, Moderator, DesktopStreaming.showDesktopSharingButton */
+
+var toolbarTimeoutObject,
+ toolbarTimeout = interfaceConfig.INITIAL_TOOLBAR_TIMEOUT;
+
+function showDesktopSharingButton() {
+ if (APP.desktopsharing.isDesktopSharingEnabled()) {
+ $('#desktopsharing').css({display: "inline"});
+ } else {
+ $('#desktopsharing').css({display: "none"});
+ }
+}
+
+/**
+ * Hides the toolbar.
+ */
+function hideToolbar() {
+ if(config.alwaysVisibleToolbar)
+ return;
+
+ var header = $("#header"),
+ bottomToolbar = $("#bottomToolbar");
+ var isToolbarHover = false;
+ header.find('*').each(function () {
+ var id = $(this).attr('id');
+ if ($("#" + id + ":hover").length > 0) {
+ isToolbarHover = true;
+ }
+ });
+ if ($("#bottomToolbar:hover").length > 0) {
+ isToolbarHover = true;
+ }
+
+ clearTimeout(toolbarTimeoutObject);
+ toolbarTimeoutObject = null;
+
+ if (!isToolbarHover) {
+ header.hide("slide", { direction: "up", duration: 300});
+ $('#subject').animate({top: "-=40"}, 300);
+ if ($("#remoteVideos").hasClass("hidden")) {
+ bottomToolbar.hide(
+ "slide", {direction: "right", duration: 300});
+ }
+ }
+ else {
+ toolbarTimeoutObject = setTimeout(hideToolbar, toolbarTimeout);
+ }
+}
+
+var ToolbarToggler = {
+ /**
+ * Shows the main toolbar.
+ */
+ showToolbar: function () {
+ var header = $("#header"),
+ bottomToolbar = $("#bottomToolbar");
+ if (!header.is(':visible') || !bottomToolbar.is(":visible")) {
+ header.show("slide", { direction: "up", duration: 300});
+ $('#subject').animate({top: "+=40"}, 300);
+ if (!bottomToolbar.is(":visible")) {
+ bottomToolbar.show(
+ "slide", {direction: "right", duration: 300});
+ }
+
+ if (toolbarTimeoutObject) {
+ clearTimeout(toolbarTimeoutObject);
+ toolbarTimeoutObject = null;
+ }
+ toolbarTimeoutObject = setTimeout(hideToolbar, toolbarTimeout);
+ toolbarTimeout = interfaceConfig.TOOLBAR_TIMEOUT;
+ }
+
+ if (APP.xmpp.isModerator())
+ {
+// TODO: Enable settings functionality.
+// Need to uncomment the settings button in index.html.
+// $('#settingsButton').css({visibility:"visible"});
+ }
+
+ // Show/hide desktop sharing button
+ showDesktopSharingButton();
+ },
+
+
+ /**
+ * Docks/undocks the toolbar.
+ *
+ * @param isDock indicates what operation to perform
+ */
+ dockToolbar: function (isDock) {
+ if (isDock) {
+ // First make sure the toolbar is shown.
+ if (!$('#header').is(':visible')) {
+ this.showToolbar();
+ }
+
+ // Then clear the time out, to dock the toolbar.
+ if (toolbarTimeoutObject) {
+ clearTimeout(toolbarTimeoutObject);
+ toolbarTimeoutObject = null;
+ }
+ }
+ else {
+ if (!$('#header').is(':visible')) {
+ this.showToolbar();
+ }
+ else {
+ toolbarTimeoutObject = setTimeout(hideToolbar, toolbarTimeout);
+ }
+ }
+ },
+
+ showDesktopSharingButton: showDesktopSharingButton
+
+};
+
module.exports = ToolbarToggler;
-},{}],28:[function(require,module,exports){
-var JitsiPopover = (function () {
- /**
- * Constructs new JitsiPopover and attaches it to the element
- * @param element jquery selector
- * @param options the options for the popover.
- * @constructor
- */
- function JitsiPopover(element, options)
- {
- this.options = {
- skin: "white",
- content: ""
- };
- if(options)
- {
- if(options.skin)
- this.options.skin = options.skin;
-
- if(options.content)
- this.options.content = options.content;
- }
-
- this.elementIsHovered = false;
- this.popoverIsHovered = false;
- this.popoverShown = false;
-
- element.data("jitsi_popover", this);
- this.element = element;
- this.template = ' ';
- var self = this;
- this.element.on("mouseenter", function () {
- self.elementIsHovered = true;
- self.show();
- }).on("mouseleave", function () {
- self.elementIsHovered = false;
- setTimeout(function () {
- self.hide();
- }, 10);
- });
- }
-
- /**
- * Shows the popover
- */
- JitsiPopover.prototype.show = function () {
- this.createPopover();
- this.popoverShown = true;
-
- };
-
- /**
- * Hides the popover
- */
- JitsiPopover.prototype.hide = function () {
- if(!this.elementIsHovered && !this.popoverIsHovered && this.popoverShown)
- {
- this.forceHide();
- }
- };
-
- /**
- * Hides the popover
- */
- JitsiPopover.prototype.forceHide = function () {
- $(".jitsipopover").remove();
- this.popoverShown = false;
- };
-
- /**
- * Creates the popover html
- */
- JitsiPopover.prototype.createPopover = function () {
- $("body").append(this.template);
- $(".jitsipopover > .jitsipopover-content").html(this.options.content);
- var self = this;
- $(".jitsipopover").on("mouseenter", function () {
- self.popoverIsHovered = true;
- }).on("mouseleave", function () {
- self.popoverIsHovered = false;
- self.hide();
- });
-
- this.refreshPosition();
- };
-
- /**
- * Refreshes the position of the popover
- */
- JitsiPopover.prototype.refreshPosition = function () {
- $(".jitsipopover").position({
- my: "bottom",
- at: "top",
- collision: "fit",
- of: this.element,
- using: function (position, elements) {
- var calcLeft = elements.target.left - elements.element.left + elements.target.width/2;
- $(".jitsipopover").css({top: position.top, left: position.left, display: "table"});
- $(".jitsipopover > .arrow").css({left: calcLeft});
- $(".jitsipopover > .jitsiPopupmenuPadding").css({left: calcLeft - 50});
- }
- });
- };
-
- /**
- * Updates the content of popover.
- * @param content new content
- */
- JitsiPopover.prototype.updateContent = function (content) {
- this.options.content = content;
- if(!this.popoverShown)
- return;
- $(".jitsipopover").remove();
- this.createPopover();
- };
-
- return JitsiPopover;
-
-
-})();
-
-module.exports = JitsiPopover;
-},{}],29:[function(require,module,exports){
-/* global $, APP, jQuery, toastr */
-var messageHandler = (function(my) {
-
- /**
- * Shows a message to the user.
- *
- * @param titleString the title of the message
- * @param messageString the text of the message
- */
- my.openMessageDialog = function(titleKey, messageKey) {
- var title = null;
- if(titleKey)
- {
- title = APP.translation.generateTranslationHTML(titleKey);
- }
- var message = APP.translation.generateTranslationHTML(messageKey);
- $.prompt(message,
- {
- title: title,
- persistent: false
- }
- );
- };
-
- /**
- * Shows a message to the user with two buttons: first is given as a parameter and the second is Cancel.
- *
- * @param titleString the title of the message
- * @param msgString the text of the message
- * @param persistent boolean value which determines whether the message is persistent or not
- * @param leftButton the fist button's text
- * @param submitFunction function to be called on submit
- * @param loadedFunction function to be called after the prompt is fully loaded
- * @param closeFunction function to be called after the prompt is closed
- * @param focus optional focus selector or button index to be focused after
- * the dialog is opened
- * @param defaultButton index of default button which will be activated when
- * the user press 'enter'. Indexed from 0.
- */
- my.openTwoButtonDialog = function(titleKey, titleString, msgKey, msgString,
- persistent, leftButtonKey, submitFunction, loadedFunction,
- closeFunction, focus, defaultButton)
- {
- var buttons = [];
-
- var leftButton = APP.translation.generateTranslationHTML(leftButtonKey);
- buttons.push({ title: leftButton, value: true});
-
- var cancelButton
- = APP.translation.generateTranslationHTML("dialog.Cancel");
- buttons.push({title: cancelButton, value: false});
-
- var message = msgString, title = titleString;
- if (titleKey)
- {
- title = APP.translation.generateTranslationHTML(titleKey);
- }
- if (msgKey) {
- message = APP.translation.generateTranslationHTML(msgKey);
- }
- $.prompt(message, {
- title: title,
- persistent: false,
- buttons: buttons,
- defaultButton: defaultButton,
- focus: focus,
- loaded: loadedFunction,
- submit: submitFunction,
- close: closeFunction
- });
- };
-
- /**
- * Shows a message to the user with two buttons: first is given as a parameter and the second is Cancel.
- *
- * @param titleString the title of the message
- * @param msgString the text of the message
- * @param persistent boolean value which determines whether the message is persistent or not
- * @param buttons object with the buttons. The keys must be the name of the button and value is the value
- * that will be passed to submitFunction
- * @param submitFunction function to be called on submit
- * @param loadedFunction function to be called after the prompt is fully loaded
- */
- my.openDialog = function (titleString, msgString, persistent, buttons,
- submitFunction, loadedFunction) {
- var args = {
- title: titleString,
- persistent: persistent,
- buttons: buttons,
- defaultButton: 1,
- loaded: loadedFunction,
- submit: submitFunction
- };
- if (persistent) {
- args.closeText = '';
- }
- return new Impromptu(msgString, args);
- };
-
- /**
- * Closes currently opened dialog.
- */
- my.closeDialog = function () {
- $.prompt.close();
- };
-
- /**
- * Shows a dialog with different states to the user.
- *
- * @param statesObject object containing all the states of the dialog
- */
- my.openDialogWithStates = function (statesObject, options) {
-
- return new Impromptu(statesObject, options);
- };
-
- /**
- * Opens new popup window for given url centered over current
- * window.
- *
- * @param url the URL to be displayed in the popup window
- * @param w the width of the popup window
- * @param h the height of the popup window
- * @param onPopupClosed optional callback function called when popup window
- * has been closed.
- *
- * @returns popup window object if opened successfully or undefined
- * in case we failed to open it(popup blocked)
- */
- my.openCenteredPopup = function (url, w, h, onPopupClosed) {
- var l = window.screenX + (window.innerWidth / 2) - (w / 2);
- var t = window.screenY + (window.innerHeight / 2) - (h / 2);
- var popup = window.open(
- url, '_blank',
- 'top=' + t + ', left=' + l + ', width=' + w + ', height=' + h + '');
- if (popup && onPopupClosed) {
- var pollTimer = window.setInterval(function () {
- if (popup.closed !== false) {
- window.clearInterval(pollTimer);
- onPopupClosed();
- }
- }, 200);
- }
- return popup;
- };
-
- /**
- * Shows a dialog prompting the user to send an error report.
- *
- * @param titleString the title of the message
- * @param msgString the text of the message
- * @param error the error that is being reported
- */
- my.openReportDialog = function(titleKey, msgKey, error) {
- my.openMessageDialog(titleKey, msgKey);
- console.log(error);
- //FIXME send the error to the server
- };
-
- /**
- * Shows an error dialog to the user.
- * @param title the title of the message
- * @param message the text of the messafe
- */
- my.showError = function(titleKey, msgKey) {
-
- if(!titleKey) {
- titleKey = "dialog.oops";
- }
- if(!msgKey)
- {
- msgKey = "dialog.defaultError";
- }
- messageHandler.openMessageDialog(titleKey, msgKey);
- };
-
- my.notify = function(displayName, displayNameKey,
- cls, messageKey, messageArguments, options) {
- var displayNameSpan = '" + displayName;
- }
- else
- {
- displayNameSpan += "data-i18n='" + displayNameKey +
- "'>" + APP.translation.translateString(displayNameKey);
- }
- displayNameSpan += " ";
- toastr.info(
- displayNameSpan + ' ' +
- '" +
- APP.translation.translateString(messageKey,
- messageArguments) +
- ' ', null, options);
- };
-
- return my;
-}(messageHandler || {}));
-
-module.exports = messageHandler;
-
-
-
},{}],30:[function(require,module,exports){
-var UIEvents = require("../../../service/UI/UIEvents");
-
-var nickname = null;
-var eventEmitter = null;
-
-var NickanameHandler = {
- init: function (emitter) {
- eventEmitter = emitter;
- var storedDisplayName = window.localStorage.displayname;
- if (storedDisplayName) {
- nickname = storedDisplayName;
- }
- },
- setNickname: function (newNickname) {
- if (!newNickname || nickname === newNickname)
- return;
-
- nickname = newNickname;
- window.localStorage.displayname = nickname;
- eventEmitter.emit(UIEvents.NICKNAME_CHANGED, newNickname);
- },
- getNickname: function () {
- return nickname;
- },
- addListener: function (type, listener) {
- eventEmitter.on(type, listener);
- }
-};
+var JitsiPopover = (function () {
+ /**
+ * Constructs new JitsiPopover and attaches it to the element
+ * @param element jquery selector
+ * @param options the options for the popover.
+ * @constructor
+ */
+ function JitsiPopover(element, options)
+ {
+ this.options = {
+ skin: "white",
+ content: ""
+ };
+ if(options)
+ {
+ if(options.skin)
+ this.options.skin = options.skin;
+
+ if(options.content)
+ this.options.content = options.content;
+ }
+
+ this.elementIsHovered = false;
+ this.popoverIsHovered = false;
+ this.popoverShown = false;
+
+ element.data("jitsi_popover", this);
+ this.element = element;
+ this.template = ' ';
+ var self = this;
+ this.element.on("mouseenter", function () {
+ self.elementIsHovered = true;
+ self.show();
+ }).on("mouseleave", function () {
+ self.elementIsHovered = false;
+ setTimeout(function () {
+ self.hide();
+ }, 10);
+ });
+ }
+
+ /**
+ * Shows the popover
+ */
+ JitsiPopover.prototype.show = function () {
+ this.createPopover();
+ this.popoverShown = true;
+
+ };
+
+ /**
+ * Hides the popover
+ */
+ JitsiPopover.prototype.hide = function () {
+ if(!this.elementIsHovered && !this.popoverIsHovered && this.popoverShown)
+ {
+ this.forceHide();
+ }
+ };
+
+ /**
+ * Hides the popover
+ */
+ JitsiPopover.prototype.forceHide = function () {
+ $(".jitsipopover").remove();
+ this.popoverShown = false;
+ };
+
+ /**
+ * Creates the popover html
+ */
+ JitsiPopover.prototype.createPopover = function () {
+ $("body").append(this.template);
+ $(".jitsipopover > .jitsipopover-content").html(this.options.content);
+ var self = this;
+ $(".jitsipopover").on("mouseenter", function () {
+ self.popoverIsHovered = true;
+ }).on("mouseleave", function () {
+ self.popoverIsHovered = false;
+ self.hide();
+ });
+
+ this.refreshPosition();
+ };
+
+ /**
+ * Refreshes the position of the popover
+ */
+ JitsiPopover.prototype.refreshPosition = function () {
+ $(".jitsipopover").position({
+ my: "bottom",
+ at: "top",
+ collision: "fit",
+ of: this.element,
+ using: function (position, elements) {
+ var calcLeft = elements.target.left - elements.element.left + elements.target.width/2;
+ $(".jitsipopover").css({top: position.top, left: position.left, display: "table"});
+ $(".jitsipopover > .arrow").css({left: calcLeft});
+ $(".jitsipopover > .jitsiPopupmenuPadding").css({left: calcLeft - 50});
+ }
+ });
+ };
+
+ /**
+ * Updates the content of popover.
+ * @param content new content
+ */
+ JitsiPopover.prototype.updateContent = function (content) {
+ this.options.content = content;
+ if(!this.popoverShown)
+ return;
+ $(".jitsipopover").remove();
+ this.createPopover();
+ };
+
+ return JitsiPopover;
+
+
+})();
+
+module.exports = JitsiPopover;
+},{}],31:[function(require,module,exports){
+/* global $, APP, jQuery, toastr */
+var messageHandler = (function(my) {
+
+ /**
+ * Shows a message to the user.
+ *
+ * @param titleString the title of the message
+ * @param messageString the text of the message
+ */
+ my.openMessageDialog = function(titleKey, messageKey) {
+ var title = null;
+ if(titleKey)
+ {
+ title = APP.translation.generateTranslationHTML(titleKey);
+ }
+ var message = APP.translation.generateTranslationHTML(messageKey);
+ $.prompt(message,
+ {
+ title: title,
+ persistent: false
+ }
+ );
+ };
+
+ /**
+ * Shows a message to the user with two buttons: first is given as a parameter and the second is Cancel.
+ *
+ * @param titleString the title of the message
+ * @param msgString the text of the message
+ * @param persistent boolean value which determines whether the message is persistent or not
+ * @param leftButton the fist button's text
+ * @param submitFunction function to be called on submit
+ * @param loadedFunction function to be called after the prompt is fully loaded
+ * @param closeFunction function to be called after the prompt is closed
+ * @param focus optional focus selector or button index to be focused after
+ * the dialog is opened
+ * @param defaultButton index of default button which will be activated when
+ * the user press 'enter'. Indexed from 0.
+ */
+ my.openTwoButtonDialog = function(titleKey, titleString, msgKey, msgString,
+ persistent, leftButtonKey, submitFunction, loadedFunction,
+ closeFunction, focus, defaultButton)
+ {
+ var buttons = [];
+
+ var leftButton = APP.translation.generateTranslationHTML(leftButtonKey);
+ buttons.push({ title: leftButton, value: true});
+
+ var cancelButton
+ = APP.translation.generateTranslationHTML("dialog.Cancel");
+ buttons.push({title: cancelButton, value: false});
+
+ var message = msgString, title = titleString;
+ if (titleKey)
+ {
+ title = APP.translation.generateTranslationHTML(titleKey);
+ }
+ if (msgKey) {
+ message = APP.translation.generateTranslationHTML(msgKey);
+ }
+ $.prompt(message, {
+ title: title,
+ persistent: false,
+ buttons: buttons,
+ defaultButton: defaultButton,
+ focus: focus,
+ loaded: loadedFunction,
+ submit: submitFunction,
+ close: closeFunction
+ });
+ };
+
+ /**
+ * Shows a message to the user with two buttons: first is given as a parameter and the second is Cancel.
+ *
+ * @param titleString the title of the message
+ * @param msgString the text of the message
+ * @param persistent boolean value which determines whether the message is persistent or not
+ * @param buttons object with the buttons. The keys must be the name of the button and value is the value
+ * that will be passed to submitFunction
+ * @param submitFunction function to be called on submit
+ * @param loadedFunction function to be called after the prompt is fully loaded
+ */
+ my.openDialog = function (titleString, msgString, persistent, buttons,
+ submitFunction, loadedFunction) {
+ var args = {
+ title: titleString,
+ persistent: persistent,
+ buttons: buttons,
+ defaultButton: 1,
+ loaded: loadedFunction,
+ submit: submitFunction
+ };
+ if (persistent) {
+ args.closeText = '';
+ }
+ return new Impromptu(msgString, args);
+ };
+
+ /**
+ * Closes currently opened dialog.
+ */
+ my.closeDialog = function () {
+ $.prompt.close();
+ };
+
+ /**
+ * Shows a dialog with different states to the user.
+ *
+ * @param statesObject object containing all the states of the dialog
+ */
+ my.openDialogWithStates = function (statesObject, options) {
+
+ return new Impromptu(statesObject, options);
+ };
+
+ /**
+ * Opens new popup window for given url centered over current
+ * window.
+ *
+ * @param url the URL to be displayed in the popup window
+ * @param w the width of the popup window
+ * @param h the height of the popup window
+ * @param onPopupClosed optional callback function called when popup window
+ * has been closed.
+ *
+ * @returns popup window object if opened successfully or undefined
+ * in case we failed to open it(popup blocked)
+ */
+ my.openCenteredPopup = function (url, w, h, onPopupClosed) {
+ var l = window.screenX + (window.innerWidth / 2) - (w / 2);
+ var t = window.screenY + (window.innerHeight / 2) - (h / 2);
+ var popup = window.open(
+ url, '_blank',
+ 'top=' + t + ', left=' + l + ', width=' + w + ', height=' + h + '');
+ if (popup && onPopupClosed) {
+ var pollTimer = window.setInterval(function () {
+ if (popup.closed !== false) {
+ window.clearInterval(pollTimer);
+ onPopupClosed();
+ }
+ }, 200);
+ }
+ return popup;
+ };
+
+ /**
+ * Shows a dialog prompting the user to send an error report.
+ *
+ * @param titleString the title of the message
+ * @param msgString the text of the message
+ * @param error the error that is being reported
+ */
+ my.openReportDialog = function(titleKey, msgKey, error) {
+ my.openMessageDialog(titleKey, msgKey);
+ console.log(error);
+ //FIXME send the error to the server
+ };
+
+ /**
+ * Shows an error dialog to the user.
+ * @param title the title of the message
+ * @param message the text of the messafe
+ */
+ my.showError = function(titleKey, msgKey) {
+
+ if(!titleKey) {
+ titleKey = "dialog.oops";
+ }
+ if(!msgKey)
+ {
+ msgKey = "dialog.defaultError";
+ }
+ messageHandler.openMessageDialog(titleKey, msgKey);
+ };
+
+ my.notify = function(displayName, displayNameKey,
+ cls, messageKey, messageArguments, options) {
+ var displayNameSpan = '" + displayName;
+ }
+ else
+ {
+ displayNameSpan += "data-i18n='" + displayNameKey +
+ "'>" + APP.translation.translateString(displayNameKey);
+ }
+ displayNameSpan += " ";
+ toastr.info(
+ displayNameSpan + ' ' +
+ '" +
+ APP.translation.translateString(messageKey,
+ messageArguments) +
+ ' ', null, options);
+ };
+
+ return my;
+}(messageHandler || {}));
+
+module.exports = messageHandler;
+
+
+},{}],32:[function(require,module,exports){
+var UIEvents = require("../../../service/UI/UIEvents");
+
+var nickname = null;
+var eventEmitter = null;
+
+var NickanameHandler = {
+ init: function (emitter) {
+ eventEmitter = emitter;
+ var storedDisplayName = window.localStorage.displayname;
+ if (storedDisplayName) {
+ nickname = storedDisplayName;
+ }
+ },
+ setNickname: function (newNickname) {
+ if (!newNickname || nickname === newNickname)
+ return;
+
+ nickname = newNickname;
+ window.localStorage.displayname = nickname;
+ eventEmitter.emit(UIEvents.NICKNAME_CHANGED, newNickname);
+ },
+ getNickname: function () {
+ return nickname;
+ },
+ addListener: function (type, listener) {
+ eventEmitter.on(type, listener);
+ }
+};
+
module.exports = NickanameHandler;
-},{"../../../service/UI/UIEvents":105}],31:[function(require,module,exports){
-/**
- * Created by hristo on 12/22/14.
- */
-module.exports = {
- /**
- * Returns the available video width.
- */
- getAvailableVideoWidth: function (isVisible) {
- var PanelToggler = require("../side_pannels/SidePanelToggler");
- if(typeof isVisible === "undefined" || isVisible === null)
- isVisible = PanelToggler.isVisible();
- var rightPanelWidth
- = isVisible ? PanelToggler.getPanelSize()[0] : 0;
-
- return window.innerWidth - rightPanelWidth;
- },
- /**
- * Changes the style class of the element given by id.
- */
- buttonClick: function(id, classname) {
- $(id).toggleClass(classname); // add the class to the clicked element
- },
- /**
- * Returns the text width for the given element.
- *
- * @param el the element
- */
- getTextWidth: function (el) {
- return (el.clientWidth + 1);
- },
-
- /**
- * Returns the text height for the given element.
- *
- * @param el the element
- */
- getTextHeight: function (el) {
- return (el.clientHeight + 1);
- },
-
- /**
- * Plays the sound given by id.
- *
- * @param id the identifier of the audio element.
- */
- playSoundNotification: function (id) {
- document.getElementById(id).play();
- },
-
- /**
- * Escapes the given text.
- */
- escapeHtml: function (unsafeText) {
- return $('
').text(unsafeText).html();
- },
-
- imageToGrayScale: function (canvas) {
- var context = canvas.getContext('2d');
- var imgData = context.getImageData(0, 0, canvas.width, canvas.height);
- var pixels = imgData.data;
-
- for (var i = 0, n = pixels.length; i < n; i += 4) {
- var grayscale
- = pixels[i] * .3 + pixels[i+1] * .59 + pixels[i+2] * .11;
- pixels[i ] = grayscale; // red
- pixels[i+1] = grayscale; // green
- pixels[i+2] = grayscale; // blue
- // pixels[i+3] is alpha
- }
- // redraw the image in black & white
- context.putImageData(imgData, 0, 0);
- },
-
- setTooltip: function (element, key, position) {
- element.setAttribute("data-i18n", "[data-content]" + key);
- element.setAttribute("data-toggle", "popover");
- element.setAttribute("data-placement", position);
- element.setAttribute("data-html", true);
- element.setAttribute("data-container", "body");
- }
-
-
+},{"../../../service/UI/UIEvents":103}],33:[function(require,module,exports){
+/**
+ * Created by hristo on 12/22/14.
+ */
+module.exports = {
+ /**
+ * Returns the available video width.
+ */
+ getAvailableVideoWidth: function (isVisible) {
+ var PanelToggler = require("../side_pannels/SidePanelToggler");
+ if(typeof isVisible === "undefined" || isVisible === null)
+ isVisible = PanelToggler.isVisible();
+ var rightPanelWidth
+ = isVisible ? PanelToggler.getPanelSize()[0] : 0;
+
+ return window.innerWidth - rightPanelWidth;
+ },
+ /**
+ * Changes the style class of the element given by id.
+ */
+ buttonClick: function(id, classname) {
+ $(id).toggleClass(classname); // add the class to the clicked element
+ },
+ /**
+ * Returns the text width for the given element.
+ *
+ * @param el the element
+ */
+ getTextWidth: function (el) {
+ return (el.clientWidth + 1);
+ },
+
+ /**
+ * Returns the text height for the given element.
+ *
+ * @param el the element
+ */
+ getTextHeight: function (el) {
+ return (el.clientHeight + 1);
+ },
+
+ /**
+ * Plays the sound given by id.
+ *
+ * @param id the identifier of the audio element.
+ */
+ playSoundNotification: function (id) {
+ document.getElementById(id).play();
+ },
+
+ /**
+ * Escapes the given text.
+ */
+ escapeHtml: function (unsafeText) {
+ return $('
').text(unsafeText).html();
+ },
+
+ imageToGrayScale: function (canvas) {
+ var context = canvas.getContext('2d');
+ var imgData = context.getImageData(0, 0, canvas.width, canvas.height);
+ var pixels = imgData.data;
+
+ for (var i = 0, n = pixels.length; i < n; i += 4) {
+ var grayscale
+ = pixels[i] * .3 + pixels[i+1] * .59 + pixels[i+2] * .11;
+ pixels[i ] = grayscale; // red
+ pixels[i+1] = grayscale; // green
+ pixels[i+2] = grayscale; // blue
+ // pixels[i+3] is alpha
+ }
+ // redraw the image in black & white
+ context.putImageData(imgData, 0, 0);
+ },
+
+ setTooltip: function (element, key, position) {
+ element.setAttribute("data-i18n", "[data-content]" + key);
+ element.setAttribute("data-toggle", "popover");
+ element.setAttribute("data-placement", position);
+ element.setAttribute("data-html", true);
+ element.setAttribute("data-container", "body");
+ }
+
+
};
-},{"../side_pannels/SidePanelToggler":18}],32:[function(require,module,exports){
-var JitsiPopover = require("../util/JitsiPopover");
-
-/**
- * Constructs new connection indicator.
- * @param videoContainer the video container associated with the indicator.
- * @constructor
- */
-function ConnectionIndicator(videoContainer, jid)
-{
- this.videoContainer = videoContainer;
- this.bandwidth = null;
- this.packetLoss = null;
- this.bitrate = null;
- this.showMoreValue = false;
- this.resolution = null;
- this.transport = [];
- this.popover = null;
- this.jid = jid;
- this.create();
-}
-
-/**
- * Values for the connection quality
- * @type {{98: string,
- * 81: string,
- * 64: string,
- * 47: string,
- * 30: string,
- * 0: string}}
- */
-ConnectionIndicator.connectionQualityValues = {
- 98: "18px", //full
- 81: "15px",//4 bars
- 64: "11px",//3 bars
- 47: "7px",//2 bars
- 30: "3px",//1 bar
- 0: "0px"//empty
-};
-
-ConnectionIndicator.getIP = function(value)
-{
- return value.substring(0, value.lastIndexOf(":"));
-};
-
-ConnectionIndicator.getPort = function(value)
-{
- return value.substring(value.lastIndexOf(":") + 1, value.length);
-};
-
-ConnectionIndicator.getStringFromArray = function (array) {
- var res = "";
- for(var i = 0; i < array.length; i++)
- {
- res += (i === 0? "" : ", ") + array[i];
- }
- return res;
-};
-
-/**
- * Generates the html content.
- * @returns {string} the html content.
- */
-ConnectionIndicator.prototype.generateText = function () {
- var downloadBitrate, uploadBitrate, packetLoss, resolution, i;
-
- var translate = APP.translation.translateString;
-
- if(this.bitrate === null)
- {
- downloadBitrate = "N/A";
- uploadBitrate = "N/A";
- }
- else
- {
- downloadBitrate =
- this.bitrate.download? this.bitrate.download + " Kbps" : "N/A";
- uploadBitrate =
- this.bitrate.upload? this.bitrate.upload + " Kbps" : "N/A";
- }
-
- if(this.packetLoss === null)
- {
- packetLoss = "N/A";
- }
- else
- {
-
- packetLoss = "↓ " +
- (this.packetLoss.download !== null? this.packetLoss.download : "N/A") +
- "% ↑ " +
- (this.packetLoss.upload !== null? this.packetLoss.upload : "N/A") + "%";
- }
-
- var resolutionValue = null;
- if(this.resolution && this.jid != null)
- {
- var keys = Object.keys(this.resolution);
- for(var ssrc in this.resolution)
- {
- resolutionValue = this.resolution[ssrc];
- }
- }
-
- if(this.jid === null)
- {
- resolution = "";
- if(this.resolution === null || !Object.keys(this.resolution) ||
- Object.keys(this.resolution).length === 0)
- {
- resolution = "N/A";
- }
- else
- for(i in this.resolution)
- {
- resolutionValue = this.resolution[i];
- if(resolutionValue)
- {
- if(resolutionValue.height &&
- resolutionValue.width)
- {
- resolution += (resolution === ""? "" : ", ") +
- resolutionValue.width + "x" +
- resolutionValue.height;
- }
- }
- }
- }
- else if(!resolutionValue ||
- !resolutionValue.height ||
- !resolutionValue.width)
- {
- resolution = "N/A";
- }
- else
- {
- resolution = resolutionValue.width + "x" + resolutionValue.height;
- }
-
- var result = "" +
- "" +
- "" +
- translate("connectionindicator.bitrate") + " " +
- "↓ " +
- downloadBitrate + " ↑ " +
- uploadBitrate + " " +
- " " +
- "" +
- translate("connectionindicator.packetloss") + " " +
- "" + packetLoss + " " +
- " " +
- "" +
- translate("connectionindicator.resolution") + " " +
- "" + resolution + "
";
-
- if(this.videoContainer.videoSpanId == "localVideoContainer") {
- result += "" +
- translate("connectionindicator." + (this.showMoreValue ? "less" : "more")) +
- "
";
- }
-
- if(this.showMoreValue)
- {
- var downloadBandwidth, uploadBandwidth, transport;
- if(this.bandwidth === null)
- {
- downloadBandwidth = "N/A";
- uploadBandwidth = "N/A";
- }
- else
- {
- downloadBandwidth = this.bandwidth.download?
- this.bandwidth.download + " Kbps" :
- "N/A";
- uploadBandwidth = this.bandwidth.upload?
- this.bandwidth.upload + " Kbps" :
- "N/A";
- }
-
- if(!this.transport || this.transport.length === 0)
- {
- transport = "" +
- "" +
- translate("connectionindicator.address") + " " +
- " N/A ";
- }
- else
- {
- var data = {remoteIP: [], localIP:[], remotePort:[], localPort:[]};
- for(i = 0; i < this.transport.length; i++)
- {
- var ip = ConnectionIndicator.getIP(this.transport[i].ip);
- var port = ConnectionIndicator.getPort(this.transport[i].ip);
- var localIP =
- ConnectionIndicator.getIP(this.transport[i].localip);
- var localPort =
- ConnectionIndicator.getPort(this.transport[i].localip);
- if(data.remoteIP.indexOf(ip) == -1)
- {
- data.remoteIP.push(ip);
- }
-
- if(data.remotePort.indexOf(port) == -1)
- {
- data.remotePort.push(port);
- }
-
- if(data.localIP.indexOf(localIP) == -1)
- {
- data.localIP.push(localIP);
- }
-
- if(data.localPort.indexOf(localPort) == -1)
- {
- data.localPort.push(localPort);
- }
-
- }
-
- var local_address_key = "connectionindicator.localaddress";
- var remote_address_key = "connectionindicator.remoteaddress";
- var localTransport =
- "" +
- translate(local_address_key, {count: data.localIP.length}) +
- " " +
- ConnectionIndicator.getStringFromArray(data.localIP) +
- " ";
- transport =
- "" +
- translate(remote_address_key,
- {count: data.remoteIP.length}) +
- " " +
- ConnectionIndicator.getStringFromArray(data.remoteIP) +
- " ";
-
- var key_remote = "connectionindicator.remoteport",
- key_local = "connectionindicator.localport";
-
- transport += "" +
- "" +
- "" +
- translate(key_remote, {count: this.transport.length}) +
- " ";
- localTransport += " " +
- "" +
- "" +
- translate(key_local, {count: this.transport.length}) +
- " ";
-
- transport +=
- ConnectionIndicator.getStringFromArray(data.remotePort);
- localTransport +=
- ConnectionIndicator.getStringFromArray(data.localPort);
- transport += " ";
- transport += localTransport + "";
- transport +="" +
- "" +
- translate("connectionindicator.transport") + " " +
- "" + this.transport[0].type + " ";
-
- }
-
- result += "" +
- "" +
- "" +
- "" +
- translate("connectionindicator.bandwidth") + " " +
- " " +
- "↓ " +
- downloadBandwidth +
- " ↑ " +
- uploadBandwidth + " ";
-
- result += transport + "
";
-
- }
-
- return result;
-};
-
-/**
- * Shows or hide the additional information.
- */
-ConnectionIndicator.prototype.showMore = function () {
- this.showMoreValue = !this.showMoreValue;
- this.updatePopoverData();
-};
-
-
-function createIcon(classes)
-{
- var icon = document.createElement("span");
- for(var i in classes)
- {
- icon.classList.add(classes[i]);
- }
- icon.appendChild(
- document.createElement("i")).classList.add("icon-connection");
- return icon;
-}
-
-/**
- * Creates the indicator
- */
-ConnectionIndicator.prototype.create = function () {
- this.connectionIndicatorContainer = document.createElement("div");
- this.connectionIndicatorContainer.className = "connectionindicator";
- this.connectionIndicatorContainer.style.display = "none";
- this.videoContainer.container.appendChild(this.connectionIndicatorContainer);
- this.popover = new JitsiPopover(
- $("#" + this.videoContainer.videoSpanId + " > .connectionindicator"),
- {content: "" +
- APP.translation.translateString("connectionindicator.na") + "
",
- skin: "black"});
-
- this.emptyIcon = this.connectionIndicatorContainer.appendChild(
- createIcon(["connection", "connection_empty"]));
- this.fullIcon = this.connectionIndicatorContainer.appendChild(
- createIcon(["connection", "connection_full"]));
-
-};
-
-/**
- * Removes the indicator
- */
-ConnectionIndicator.prototype.remove = function()
-{
- this.connectionIndicatorContainer.remove();
- this.popover.forceHide();
-
-};
-
-/**
- * Updates the data of the indicator
- * @param percent the percent of connection quality
- * @param object the statistics data.
- */
-ConnectionIndicator.prototype.updateConnectionQuality =
-function (percent, object) {
-
- if(percent === null)
- {
- this.connectionIndicatorContainer.style.display = "none";
- this.popover.forceHide();
- return;
- }
- else
- {
- if(this.connectionIndicatorContainer.style.display == "none") {
- this.connectionIndicatorContainer.style.display = "block";
- this.videoContainer.updateIconPositions();
- }
- }
- this.bandwidth = object.bandwidth;
- this.bitrate = object.bitrate;
- this.packetLoss = object.packetLoss;
- this.transport = object.transport;
- if(object.resolution)
- {
- this.resolution = object.resolution;
- }
- for(var quality in ConnectionIndicator.connectionQualityValues)
- {
- if(percent >= quality)
- {
- this.fullIcon.style.width =
- ConnectionIndicator.connectionQualityValues[quality];
- }
- }
- this.updatePopoverData();
-};
-
-/**
- * Updates the resolution
- * @param resolution the new resolution
- */
-ConnectionIndicator.prototype.updateResolution = function (resolution) {
- this.resolution = resolution;
- this.updatePopoverData();
-};
-
-/**
- * Updates the content of the popover
- */
-ConnectionIndicator.prototype.updatePopoverData = function () {
- this.popover.updateContent(
- "" + this.generateText() + "
");
- APP.translation.translateElement($(".connection_info"));
-};
-
-/**
- * Hides the popover
- */
-ConnectionIndicator.prototype.hide = function () {
- this.popover.forceHide();
-};
-
-/**
- * Hides the indicator
- */
-ConnectionIndicator.prototype.hideIndicator = function () {
- this.connectionIndicatorContainer.style.display = "none";
- if(this.popover)
- this.popover.forceHide();
-};
-
+},{"../side_pannels/SidePanelToggler":20}],34:[function(require,module,exports){
+var JitsiPopover = require("../util/JitsiPopover");
+
+/**
+ * Constructs new connection indicator.
+ * @param videoContainer the video container associated with the indicator.
+ * @constructor
+ */
+function ConnectionIndicator(videoContainer, jid)
+{
+ this.videoContainer = videoContainer;
+ this.bandwidth = null;
+ this.packetLoss = null;
+ this.bitrate = null;
+ this.showMoreValue = false;
+ this.resolution = null;
+ this.transport = [];
+ this.popover = null;
+ this.jid = jid;
+ this.create();
+}
+
+/**
+ * Values for the connection quality
+ * @type {{98: string,
+ * 81: string,
+ * 64: string,
+ * 47: string,
+ * 30: string,
+ * 0: string}}
+ */
+ConnectionIndicator.connectionQualityValues = {
+ 98: "18px", //full
+ 81: "15px",//4 bars
+ 64: "11px",//3 bars
+ 47: "7px",//2 bars
+ 30: "3px",//1 bar
+ 0: "0px"//empty
+};
+
+ConnectionIndicator.getIP = function(value)
+{
+ return value.substring(0, value.lastIndexOf(":"));
+};
+
+ConnectionIndicator.getPort = function(value)
+{
+ return value.substring(value.lastIndexOf(":") + 1, value.length);
+};
+
+ConnectionIndicator.getStringFromArray = function (array) {
+ var res = "";
+ for(var i = 0; i < array.length; i++)
+ {
+ res += (i === 0? "" : ", ") + array[i];
+ }
+ return res;
+};
+
+/**
+ * Generates the html content.
+ * @returns {string} the html content.
+ */
+ConnectionIndicator.prototype.generateText = function () {
+ var downloadBitrate, uploadBitrate, packetLoss, resolution, i;
+
+ var translate = APP.translation.translateString;
+
+ if(this.bitrate === null)
+ {
+ downloadBitrate = "N/A";
+ uploadBitrate = "N/A";
+ }
+ else
+ {
+ downloadBitrate =
+ this.bitrate.download? this.bitrate.download + " Kbps" : "N/A";
+ uploadBitrate =
+ this.bitrate.upload? this.bitrate.upload + " Kbps" : "N/A";
+ }
+
+ if(this.packetLoss === null)
+ {
+ packetLoss = "N/A";
+ }
+ else
+ {
+
+ packetLoss = "↓ " +
+ (this.packetLoss.download !== null? this.packetLoss.download : "N/A") +
+ "% ↑ " +
+ (this.packetLoss.upload !== null? this.packetLoss.upload : "N/A") + "%";
+ }
+
+ var resolutionValue = null;
+ if(this.resolution && this.jid != null)
+ {
+ var keys = Object.keys(this.resolution);
+ for(var ssrc in this.resolution)
+ {
+ resolutionValue = this.resolution[ssrc];
+ }
+ }
+
+ if(this.jid === null)
+ {
+ resolution = "";
+ if(this.resolution === null || !Object.keys(this.resolution) ||
+ Object.keys(this.resolution).length === 0)
+ {
+ resolution = "N/A";
+ }
+ else
+ for(i in this.resolution)
+ {
+ resolutionValue = this.resolution[i];
+ if(resolutionValue)
+ {
+ if(resolutionValue.height &&
+ resolutionValue.width)
+ {
+ resolution += (resolution === ""? "" : ", ") +
+ resolutionValue.width + "x" +
+ resolutionValue.height;
+ }
+ }
+ }
+ }
+ else if(!resolutionValue ||
+ !resolutionValue.height ||
+ !resolutionValue.width)
+ {
+ resolution = "N/A";
+ }
+ else
+ {
+ resolution = resolutionValue.width + "x" + resolutionValue.height;
+ }
+
+ var result = "" +
+ "" +
+ "" +
+ translate("connectionindicator.bitrate") + " " +
+ "↓ " +
+ downloadBitrate + " ↑ " +
+ uploadBitrate + " " +
+ " " +
+ "" +
+ translate("connectionindicator.packetloss") + " " +
+ "" + packetLoss + " " +
+ " " +
+ "" +
+ translate("connectionindicator.resolution") + " " +
+ "" + resolution + "
";
+
+ if(this.videoContainer.videoSpanId == "localVideoContainer") {
+ result += "" +
+ translate("connectionindicator." + (this.showMoreValue ? "less" : "more")) +
+ "
";
+ }
+
+ if(this.showMoreValue)
+ {
+ var downloadBandwidth, uploadBandwidth, transport;
+ if(this.bandwidth === null)
+ {
+ downloadBandwidth = "N/A";
+ uploadBandwidth = "N/A";
+ }
+ else
+ {
+ downloadBandwidth = this.bandwidth.download?
+ this.bandwidth.download + " Kbps" :
+ "N/A";
+ uploadBandwidth = this.bandwidth.upload?
+ this.bandwidth.upload + " Kbps" :
+ "N/A";
+ }
+
+ if(!this.transport || this.transport.length === 0)
+ {
+ transport = "" +
+ "" +
+ translate("connectionindicator.address") + " " +
+ " N/A ";
+ }
+ else
+ {
+ var data = {remoteIP: [], localIP:[], remotePort:[], localPort:[]};
+ for(i = 0; i < this.transport.length; i++)
+ {
+ var ip = ConnectionIndicator.getIP(this.transport[i].ip);
+ var port = ConnectionIndicator.getPort(this.transport[i].ip);
+ var localIP =
+ ConnectionIndicator.getIP(this.transport[i].localip);
+ var localPort =
+ ConnectionIndicator.getPort(this.transport[i].localip);
+ if(data.remoteIP.indexOf(ip) == -1)
+ {
+ data.remoteIP.push(ip);
+ }
+
+ if(data.remotePort.indexOf(port) == -1)
+ {
+ data.remotePort.push(port);
+ }
+
+ if(data.localIP.indexOf(localIP) == -1)
+ {
+ data.localIP.push(localIP);
+ }
+
+ if(data.localPort.indexOf(localPort) == -1)
+ {
+ data.localPort.push(localPort);
+ }
+
+ }
+
+ var local_address_key = "connectionindicator.localaddress";
+ var remote_address_key = "connectionindicator.remoteaddress";
+ var localTransport =
+ "" +
+ translate(local_address_key, {count: data.localIP.length}) +
+ " " +
+ ConnectionIndicator.getStringFromArray(data.localIP) +
+ " ";
+ transport =
+ "" +
+ translate(remote_address_key,
+ {count: data.remoteIP.length}) +
+ " " +
+ ConnectionIndicator.getStringFromArray(data.remoteIP) +
+ " ";
+
+ var key_remote = "connectionindicator.remoteport",
+ key_local = "connectionindicator.localport";
+
+ transport += "" +
+ "" +
+ "" +
+ translate(key_remote, {count: this.transport.length}) +
+ " ";
+ localTransport += " " +
+ "" +
+ "" +
+ translate(key_local, {count: this.transport.length}) +
+ " ";
+
+ transport +=
+ ConnectionIndicator.getStringFromArray(data.remotePort);
+ localTransport +=
+ ConnectionIndicator.getStringFromArray(data.localPort);
+ transport += " ";
+ transport += localTransport + "";
+ transport +="" +
+ "" +
+ translate("connectionindicator.transport") + " " +
+ "" + this.transport[0].type + " ";
+
+ }
+
+ result += "" +
+ "" +
+ "" +
+ "" +
+ translate("connectionindicator.bandwidth") + " " +
+ " " +
+ "↓ " +
+ downloadBandwidth +
+ " ↑ " +
+ uploadBandwidth + " ";
+
+ result += transport + "
";
+
+ }
+
+ return result;
+};
+
+/**
+ * Shows or hide the additional information.
+ */
+ConnectionIndicator.prototype.showMore = function () {
+ this.showMoreValue = !this.showMoreValue;
+ this.updatePopoverData();
+};
+
+
+function createIcon(classes)
+{
+ var icon = document.createElement("span");
+ for(var i in classes)
+ {
+ icon.classList.add(classes[i]);
+ }
+ icon.appendChild(
+ document.createElement("i")).classList.add("icon-connection");
+ return icon;
+}
+
+/**
+ * Creates the indicator
+ */
+ConnectionIndicator.prototype.create = function () {
+ this.connectionIndicatorContainer = document.createElement("div");
+ this.connectionIndicatorContainer.className = "connectionindicator";
+ this.connectionIndicatorContainer.style.display = "none";
+ this.videoContainer.container.appendChild(this.connectionIndicatorContainer);
+ this.popover = new JitsiPopover(
+ $("#" + this.videoContainer.videoSpanId + " > .connectionindicator"),
+ {content: "" +
+ APP.translation.translateString("connectionindicator.na") + "
",
+ skin: "black"});
+
+ this.emptyIcon = this.connectionIndicatorContainer.appendChild(
+ createIcon(["connection", "connection_empty"]));
+ this.fullIcon = this.connectionIndicatorContainer.appendChild(
+ createIcon(["connection", "connection_full"]));
+
+};
+
+/**
+ * Removes the indicator
+ */
+ConnectionIndicator.prototype.remove = function()
+{
+ this.connectionIndicatorContainer.remove();
+ this.popover.forceHide();
+
+};
+
+/**
+ * Updates the data of the indicator
+ * @param percent the percent of connection quality
+ * @param object the statistics data.
+ */
+ConnectionIndicator.prototype.updateConnectionQuality =
+function (percent, object) {
+
+ if(percent === null)
+ {
+ this.connectionIndicatorContainer.style.display = "none";
+ this.popover.forceHide();
+ return;
+ }
+ else
+ {
+ if(this.connectionIndicatorContainer.style.display == "none") {
+ this.connectionIndicatorContainer.style.display = "block";
+ this.videoContainer.updateIconPositions();
+ }
+ }
+ this.bandwidth = object.bandwidth;
+ this.bitrate = object.bitrate;
+ this.packetLoss = object.packetLoss;
+ this.transport = object.transport;
+ if(object.resolution)
+ {
+ this.resolution = object.resolution;
+ }
+ for(var quality in ConnectionIndicator.connectionQualityValues)
+ {
+ if(percent >= quality)
+ {
+ this.fullIcon.style.width =
+ ConnectionIndicator.connectionQualityValues[quality];
+ }
+ }
+ this.updatePopoverData();
+};
+
+/**
+ * Updates the resolution
+ * @param resolution the new resolution
+ */
+ConnectionIndicator.prototype.updateResolution = function (resolution) {
+ this.resolution = resolution;
+ this.updatePopoverData();
+};
+
+/**
+ * Updates the content of the popover
+ */
+ConnectionIndicator.prototype.updatePopoverData = function () {
+ this.popover.updateContent(
+ "" + this.generateText() + "
");
+ APP.translation.translateElement($(".connection_info"));
+};
+
+/**
+ * Hides the popover
+ */
+ConnectionIndicator.prototype.hide = function () {
+ this.popover.forceHide();
+};
+
+/**
+ * Hides the indicator
+ */
+ConnectionIndicator.prototype.hideIndicator = function () {
+ this.connectionIndicatorContainer.style.display = "none";
+ if(this.popover)
+ this.popover.forceHide();
+};
+
module.exports = ConnectionIndicator;
-},{"../util/JitsiPopover":28}],33:[function(require,module,exports){
-var Avatar = require("../avatar/Avatar");
-var UIUtil = require("../util/UIUtil");
-var UIEvents = require("../../../service/UI/UIEvents");
-var xmpp = require("../../xmpp/xmpp");
-
-var video = $('#largeVideo');
-
-var currentVideoWidth = null;
-var currentVideoHeight = null;
-// By default we use camera
-var getVideoSize = getCameraVideoSize;
-var getVideoPosition = getCameraVideoPosition;
-var currentSmallVideo = null;
-var oldSmallVideo = null;
-
-
-
-/**
- * Sets the size and position of the given video element.
- *
- * @param video the video element to position
- * @param width the desired video width
- * @param height the desired video height
- * @param horizontalIndent the left and right indent
- * @param verticalIndent the top and bottom indent
- */
-function positionVideo(video,
- width,
- height,
- horizontalIndent,
- verticalIndent,
- animate) {
- if(animate)
- {
- video.animate({
- width: width,
- height: height,
- top: verticalIndent,
- bottom: verticalIndent,
- left: horizontalIndent,
- right: horizontalIndent
- },
- {
- queue: false,
- duration: 500
- });
- }
- else
- {
- video.width(width);
- video.height(height);
- video.css({ top: verticalIndent + 'px',
- bottom: verticalIndent + 'px',
- left: horizontalIndent + 'px',
- right: horizontalIndent + 'px'});
- }
-
-}
-
-
-/**
- * Returns an array of the video dimensions, so that it keeps it's aspect
- * ratio and fits available area with it's larger dimension. This method
- * ensures that whole video will be visible and can leave empty areas.
- *
- * @return an array with 2 elements, the video width and the video height
- */
-function getDesktopVideoSize(videoWidth,
- videoHeight,
- videoSpaceWidth,
- videoSpaceHeight) {
- if (!videoWidth)
- videoWidth = currentVideoWidth;
- if (!videoHeight)
- videoHeight = currentVideoHeight;
-
- var aspectRatio = videoWidth / videoHeight;
-
- var availableWidth = Math.max(videoWidth, videoSpaceWidth);
- var availableHeight = Math.max(videoHeight, videoSpaceHeight);
-
- videoSpaceHeight -= $('#remoteVideos').outerHeight();
-
- if (availableWidth / aspectRatio >= videoSpaceHeight)
- {
- availableHeight = videoSpaceHeight;
- availableWidth = availableHeight * aspectRatio;
- }
-
- if (availableHeight * aspectRatio >= videoSpaceWidth)
- {
- availableWidth = videoSpaceWidth;
- availableHeight = availableWidth / aspectRatio;
- }
-
- return [availableWidth, availableHeight];
-}
-
-
-/**
- * Returns an array of the video horizontal and vertical indents,
- * so that if fits its parent.
- *
- * @return an array with 2 elements, the horizontal indent and the vertical
- * indent
- */
-function getCameraVideoPosition(videoWidth,
- videoHeight,
- videoSpaceWidth,
- videoSpaceHeight) {
- // Parent height isn't completely calculated when we position the video in
- // full screen mode and this is why we use the screen height in this case.
- // Need to think it further at some point and implement it properly.
- var isFullScreen = document.fullScreen ||
- document.mozFullScreen ||
- document.webkitIsFullScreen;
- if (isFullScreen)
- videoSpaceHeight = window.innerHeight;
-
- var horizontalIndent = (videoSpaceWidth - videoWidth) / 2;
- var verticalIndent = (videoSpaceHeight - videoHeight) / 2;
-
- return [horizontalIndent, verticalIndent];
-}
-
-/**
- * Returns an array of the video horizontal and vertical indents.
- * Centers horizontally and top aligns vertically.
- *
- * @return an array with 2 elements, the horizontal indent and the vertical
- * indent
- */
-function getDesktopVideoPosition(videoWidth,
- videoHeight,
- videoSpaceWidth,
- videoSpaceHeight) {
-
- var horizontalIndent = (videoSpaceWidth - videoWidth) / 2;
-
- var verticalIndent = 0;// Top aligned
-
- return [horizontalIndent, verticalIndent];
-}
-
-
-/**
- * Returns an array of the video dimensions, so that it covers the screen.
- * It leaves no empty areas, but some parts of the video might not be visible.
- *
- * @return an array with 2 elements, the video width and the video height
- */
-function getCameraVideoSize(videoWidth,
- videoHeight,
- videoSpaceWidth,
- videoSpaceHeight) {
- if (!videoWidth)
- videoWidth = currentVideoWidth;
- if (!videoHeight)
- videoHeight = currentVideoHeight;
-
- var aspectRatio = videoWidth / videoHeight;
-
- var availableWidth = Math.max(videoWidth, videoSpaceWidth);
- var availableHeight = Math.max(videoHeight, videoSpaceHeight);
-
- if (availableWidth / aspectRatio < videoSpaceHeight) {
- availableHeight = videoSpaceHeight;
- availableWidth = availableHeight * aspectRatio;
- }
-
- if (availableHeight * aspectRatio < videoSpaceWidth) {
- availableWidth = videoSpaceWidth;
- availableHeight = availableWidth / aspectRatio;
- }
-
- return [availableWidth, availableHeight];
-}
-
-/**
- * Updates the src of the active speaker avatar
- * @param jid of the current active speaker
- */
-function updateActiveSpeakerAvatarSrc() {
- var avatar = $("#activeSpeakerAvatar")[0];
- var jid = currentSmallVideo.peerJid;
- var url = Avatar.getGravatarUrl(jid);
- if(avatar.src === url)
- return;
- var isMuted = null;
- if(!LargeVideo.VideoLayout.isInLastN(currentSmallVideo.resourceJid)) {
- isMuted = true;
- }
- else
- {
- isMuted = APP.RTC.isVideoMuted(jid);
- }
-
- if (jid && isMuted !== null) {
- avatar.src = url;
- $("#largeVideo").css("visibility", isMuted ? "hidden" : "visible");
- currentSmallVideo.showAvatar(isMuted);
- }
-}
-
-function changeVideo(isVisible) {
- updateActiveSpeakerAvatarSrc();
-
- APP.RTC.setVideoSrc($('#largeVideo')[0], currentSmallVideo.getSrc());
-
- var videoTransform = document.getElementById('largeVideo')
- .style.webkitTransform;
-
- var flipX = currentSmallVideo.flipX;
-
- if (flipX && videoTransform !== 'scaleX(-1)') {
- document.getElementById('largeVideo').style.webkitTransform
- = "scaleX(-1)";
- }
- else if (!flipX && videoTransform === 'scaleX(-1)') {
- document.getElementById('largeVideo').style.webkitTransform
- = "none";
- }
-
- var isDesktop = APP.RTC.isVideoSrcDesktop(currentSmallVideo.peerJid);
- // Change the way we'll be measuring and positioning large video
-
- getVideoSize = isDesktop
- ? getDesktopVideoSize
- : getCameraVideoSize;
- getVideoPosition = isDesktop
- ? getDesktopVideoPosition
- : getCameraVideoPosition;
-
-
- // Only if the large video is currently visible.
- // Disable previous dominant speaker video.
- if (oldSmallVideo) {
- oldSmallVideo.enableDominantSpeaker(false);
- }
-
- // Enable new dominant speaker in the remote videos section.
- if (currentSmallVideo) {
- currentSmallVideo.enableDominantSpeaker(true);
- }
-
- if (isVisible) {
- // using "this" should be ok because we're called
- // from within the fadeOut event.
- $(this).fadeIn(300);
- }
-
- if(oldSmallVideo)
- oldSmallVideo.showAvatar();
-}
-
-var LargeVideo = {
-
- init: function (VideoLayout, emitter) {
- this.VideoLayout = VideoLayout;
- this.eventEmitter = emitter;
- var self = this;
- // Listen for large video size updates
- document.getElementById('largeVideo')
- .addEventListener('loadedmetadata', function (e) {
- currentVideoWidth = this.videoWidth;
- currentVideoHeight = this.videoHeight;
- self.position(currentVideoWidth, currentVideoHeight);
- });
- },
- /**
- * Indicates if the large video is currently visible.
- *
- * @return true if visible, false - otherwise
- */
- isLargeVideoVisible: function() {
- return video.is(':visible');
- },
- /**
- * Updates the large video with the given new video source.
- */
- updateLargeVideo: function(resourceJid, forceUpdate) {
- console.log('hover in', resourceJid);
- var newSmallVideo = this.VideoLayout.getSmallVideo(resourceJid);
-
- if ((currentSmallVideo && currentSmallVideo.resourceJid !== resourceJid)
- || forceUpdate) {
- $('#activeSpeaker').css('visibility', 'hidden');
-
- if(currentSmallVideo) {
- oldSmallVideo = currentSmallVideo;
- } else {
- oldSmallVideo = null;
- }
-
- currentSmallVideo = newSmallVideo;
- var oldJid = null;
- if(oldSmallVideo)
- oldJid = oldSmallVideo.peerJid;
-
- if (oldJid !== resourceJid) {
- // we want the notification to trigger even if userJid is undefined,
- // or null.
- this.eventEmitter.emit(UIEvents.SELECTED_ENDPOINT,
- resourceJid);
- }
-
- video.fadeOut(300, changeVideo.bind(video, this.isLargeVideoVisible()));
- } else {
- if(currentSmallVideo) {
- currentSmallVideo.showAvatar();
- }
- }
-
- },
-
- /**
- * Shows/hides the large video.
- */
- setLargeVideoVisible: function(isVisible) {
- if (isVisible) {
- $('#largeVideo').css({visibility: 'visible'});
- $('.watermark').css({visibility: 'visible'});
- if(currentSmallVideo)
- currentSmallVideo.enableDominantSpeaker(true);
- }
- else {
- $('#largeVideo').css({visibility: 'hidden'});
- $('#activeSpeaker').css('visibility', 'hidden');
- $('.watermark').css({visibility: 'hidden'});
- if(currentSmallVideo)
- currentSmallVideo.enableDominantSpeaker(false);
- }
- },
- onVideoTypeChanged: function (jid) {
- if(jid && currentSmallVideo && jid === currentSmallVideo.peerJid)
- {
- var isDesktop = APP.RTC.isVideoSrcDesktop(jid);
- getVideoSize = isDesktop
- ? getDesktopVideoSize
- : getCameraVideoSize;
- getVideoPosition = isDesktop
- ? getDesktopVideoPosition
- : getCameraVideoPosition;
- this.position(null, null);
- }
- },
- /**
- * Positions the large video.
- *
- * @param videoWidth the stream video width
- * @param videoHeight the stream video height
- */
- position: function (videoWidth, videoHeight,
- videoSpaceWidth, videoSpaceHeight, animate) {
- if(!videoSpaceWidth)
- videoSpaceWidth = $('#videospace').width();
- if(!videoSpaceHeight)
- videoSpaceHeight = window.innerHeight;
-
- var videoSize = getVideoSize(videoWidth,
- videoHeight,
- videoSpaceWidth,
- videoSpaceHeight);
-
- var largeVideoWidth = videoSize[0];
- var largeVideoHeight = videoSize[1];
-
- var videoPosition = getVideoPosition(largeVideoWidth,
- largeVideoHeight,
- videoSpaceWidth,
- videoSpaceHeight);
-
- var horizontalIndent = videoPosition[0];
- var verticalIndent = videoPosition[1];
-
- positionVideo($('#largeVideo'),
- largeVideoWidth,
- largeVideoHeight,
- horizontalIndent, verticalIndent, animate);
- },
-
- isLargeVideoOnTop: function () {
- var Etherpad = require("../etherpad/Etherpad");
- var Prezi = require("../prezi/Prezi");
- return !Prezi.isPresentationVisible() && !Etherpad.isVisible();
- },
- resize: function (animate, isVisible, completeFunction) {
- var availableHeight = window.innerHeight;
- var availableWidth = UIUtil.getAvailableVideoWidth(isVisible);
-
- if (availableWidth < 0 || availableHeight < 0) return;
-
- var avatarSize = interfaceConfig.ACTIVE_SPEAKER_AVATAR_SIZE;
- var top = availableHeight / 2 - avatarSize / 4 * 3;
- $('#activeSpeaker').css('top', top);
-
- if(animate)
- {
- $('#videospace').animate({
- right: window.innerWidth - availableWidth,
- width: availableWidth,
- height: availableHeight
- },
- {
- queue: false,
- duration: 500,
- complete: completeFunction
- });
-
- $('#largeVideoContainer').animate({
- width: availableWidth,
- height: availableHeight
- },
- {
- queue: false,
- duration: 500
- });
-
-
- }
- else
- {
- $('#videospace').width(availableWidth);
- $('#videospace').height(availableHeight);
- $('#largeVideoContainer').width(availableWidth);
- $('#largeVideoContainer').height(availableHeight);
- }
- return [availableWidth, availableHeight];
-
- },
- resizeVideoAreaAnimated: function (isVisible, completeFunction) {
- var size = this.resize(true, isVisible, completeFunction);
- this.position(null, null, size[0], size[1], true);
- },
- getResourceJid: function () {
- if(!currentSmallVideo)
- return null;
- return currentSmallVideo.resourceJid;
- },
- updateAvatar: function (resourceJid) {
- if (resourceJid === this.getResourceJid()) {
- updateActiveSpeakerAvatarSrc();
- }
- },
- showAvatar: function (resourceJid, show) {
- if(this.getResourceJid() === resourceJid
- && LargeVideo.isLargeVideoOnTop())
- {
- $("#largeVideo").css("visibility", show ? "hidden" : "visible");
- $('#activeSpeaker').css("visibility", show ? "visible" : "hidden");
- return true;
- }
- return false;
- }
-
-}
-
-
+},{"../util/JitsiPopover":30}],35:[function(require,module,exports){
+var Avatar = require("../avatar/Avatar");
+var UIUtil = require("../util/UIUtil");
+var UIEvents = require("../../../service/UI/UIEvents");
+var xmpp = require("../../xmpp/xmpp");
+
+var video = $('#largeVideo');
+
+var currentVideoWidth = null;
+var currentVideoHeight = null;
+// By default we use camera
+var getVideoSize = getCameraVideoSize;
+var getVideoPosition = getCameraVideoPosition;
+var currentSmallVideo = null;
+var oldSmallVideo = null;
+
+
+
+/**
+ * Sets the size and position of the given video element.
+ *
+ * @param video the video element to position
+ * @param width the desired video width
+ * @param height the desired video height
+ * @param horizontalIndent the left and right indent
+ * @param verticalIndent the top and bottom indent
+ */
+function positionVideo(video,
+ width,
+ height,
+ horizontalIndent,
+ verticalIndent,
+ animate) {
+ if(animate)
+ {
+ video.animate({
+ width: width,
+ height: height,
+ top: verticalIndent,
+ bottom: verticalIndent,
+ left: horizontalIndent,
+ right: horizontalIndent
+ },
+ {
+ queue: false,
+ duration: 500
+ });
+ }
+ else
+ {
+ video.width(width);
+ video.height(height);
+ video.css({ top: verticalIndent + 'px',
+ bottom: verticalIndent + 'px',
+ left: horizontalIndent + 'px',
+ right: horizontalIndent + 'px'});
+ }
+
+}
+
+
+/**
+ * Returns an array of the video dimensions, so that it keeps it's aspect
+ * ratio and fits available area with it's larger dimension. This method
+ * ensures that whole video will be visible and can leave empty areas.
+ *
+ * @return an array with 2 elements, the video width and the video height
+ */
+function getDesktopVideoSize(videoWidth,
+ videoHeight,
+ videoSpaceWidth,
+ videoSpaceHeight) {
+ if (!videoWidth)
+ videoWidth = currentVideoWidth;
+ if (!videoHeight)
+ videoHeight = currentVideoHeight;
+
+ var aspectRatio = videoWidth / videoHeight;
+
+ var availableWidth = Math.max(videoWidth, videoSpaceWidth);
+ var availableHeight = Math.max(videoHeight, videoSpaceHeight);
+
+ videoSpaceHeight -= $('#remoteVideos').outerHeight();
+
+ if (availableWidth / aspectRatio >= videoSpaceHeight)
+ {
+ availableHeight = videoSpaceHeight;
+ availableWidth = availableHeight * aspectRatio;
+ }
+
+ if (availableHeight * aspectRatio >= videoSpaceWidth)
+ {
+ availableWidth = videoSpaceWidth;
+ availableHeight = availableWidth / aspectRatio;
+ }
+
+ return [availableWidth, availableHeight];
+}
+
+
+/**
+ * Returns an array of the video horizontal and vertical indents,
+ * so that if fits its parent.
+ *
+ * @return an array with 2 elements, the horizontal indent and the vertical
+ * indent
+ */
+function getCameraVideoPosition(videoWidth,
+ videoHeight,
+ videoSpaceWidth,
+ videoSpaceHeight) {
+ // Parent height isn't completely calculated when we position the video in
+ // full screen mode and this is why we use the screen height in this case.
+ // Need to think it further at some point and implement it properly.
+ var isFullScreen = document.fullScreen ||
+ document.mozFullScreen ||
+ document.webkitIsFullScreen;
+ if (isFullScreen)
+ videoSpaceHeight = window.innerHeight;
+
+ var horizontalIndent = (videoSpaceWidth - videoWidth) / 2;
+ var verticalIndent = (videoSpaceHeight - videoHeight) / 2;
+
+ return [horizontalIndent, verticalIndent];
+}
+
+/**
+ * Returns an array of the video horizontal and vertical indents.
+ * Centers horizontally and top aligns vertically.
+ *
+ * @return an array with 2 elements, the horizontal indent and the vertical
+ * indent
+ */
+function getDesktopVideoPosition(videoWidth,
+ videoHeight,
+ videoSpaceWidth,
+ videoSpaceHeight) {
+
+ var horizontalIndent = (videoSpaceWidth - videoWidth) / 2;
+
+ var verticalIndent = 0;// Top aligned
+
+ return [horizontalIndent, verticalIndent];
+}
+
+
+/**
+ * Returns an array of the video dimensions, so that it covers the screen.
+ * It leaves no empty areas, but some parts of the video might not be visible.
+ *
+ * @return an array with 2 elements, the video width and the video height
+ */
+function getCameraVideoSize(videoWidth,
+ videoHeight,
+ videoSpaceWidth,
+ videoSpaceHeight) {
+ if (!videoWidth)
+ videoWidth = currentVideoWidth;
+ if (!videoHeight)
+ videoHeight = currentVideoHeight;
+
+ var aspectRatio = videoWidth / videoHeight;
+
+ var availableWidth = Math.max(videoWidth, videoSpaceWidth);
+ var availableHeight = Math.max(videoHeight, videoSpaceHeight);
+
+ if (availableWidth / aspectRatio < videoSpaceHeight) {
+ availableHeight = videoSpaceHeight;
+ availableWidth = availableHeight * aspectRatio;
+ }
+
+ if (availableHeight * aspectRatio < videoSpaceWidth) {
+ availableWidth = videoSpaceWidth;
+ availableHeight = availableWidth / aspectRatio;
+ }
+
+ return [availableWidth, availableHeight];
+}
+
+/**
+ * Updates the src of the active speaker avatar
+ * @param jid of the current active speaker
+ */
+function updateActiveSpeakerAvatarSrc() {
+ var avatar = $("#activeSpeakerAvatar")[0];
+ var jid = currentSmallVideo.peerJid;
+ var url = Avatar.getGravatarUrl(jid);
+ if(avatar.src === url)
+ return;
+ var isMuted = null;
+ if(!LargeVideo.VideoLayout.isInLastN(currentSmallVideo.resourceJid)) {
+ isMuted = true;
+ }
+ else
+ {
+ isMuted = APP.RTC.isVideoMuted(jid);
+ }
+
+ if (jid && isMuted !== null) {
+ avatar.src = url;
+ $("#largeVideo").css("visibility", isMuted ? "hidden" : "visible");
+ currentSmallVideo.showAvatar(isMuted);
+ }
+}
+
+function changeVideo(isVisible) {
+ updateActiveSpeakerAvatarSrc();
+
+ APP.RTC.setVideoSrc($('#largeVideo')[0], currentSmallVideo.getSrc());
+
+ var videoTransform = document.getElementById('largeVideo')
+ .style.webkitTransform;
+
+ var flipX = currentSmallVideo.flipX;
+
+ if (flipX && videoTransform !== 'scaleX(-1)') {
+ document.getElementById('largeVideo').style.webkitTransform
+ = "scaleX(-1)";
+ }
+ else if (!flipX && videoTransform === 'scaleX(-1)') {
+ document.getElementById('largeVideo').style.webkitTransform
+ = "none";
+ }
+
+ var isDesktop = APP.RTC.isVideoSrcDesktop(currentSmallVideo.peerJid);
+ // Change the way we'll be measuring and positioning large video
+
+ getVideoSize = isDesktop
+ ? getDesktopVideoSize
+ : getCameraVideoSize;
+ getVideoPosition = isDesktop
+ ? getDesktopVideoPosition
+ : getCameraVideoPosition;
+
+
+ // Only if the large video is currently visible.
+ // Disable previous dominant speaker video.
+ if (oldSmallVideo) {
+ oldSmallVideo.enableDominantSpeaker(false);
+ }
+
+ // Enable new dominant speaker in the remote videos section.
+ if (currentSmallVideo) {
+ currentSmallVideo.enableDominantSpeaker(true);
+ }
+
+ if (isVisible) {
+ // using "this" should be ok because we're called
+ // from within the fadeOut event.
+ $(this).fadeIn(300);
+ }
+
+ if(oldSmallVideo)
+ oldSmallVideo.showAvatar();
+}
+
+var LargeVideo = {
+
+ init: function (VideoLayout, emitter) {
+ this.VideoLayout = VideoLayout;
+ this.eventEmitter = emitter;
+ var self = this;
+ // Listen for large video size updates
+ document.getElementById('largeVideo')
+ .addEventListener('loadedmetadata', function (e) {
+ currentVideoWidth = this.videoWidth;
+ currentVideoHeight = this.videoHeight;
+ self.position(currentVideoWidth, currentVideoHeight);
+ });
+ },
+ /**
+ * Indicates if the large video is currently visible.
+ *
+ * @return true if visible, false - otherwise
+ */
+ isLargeVideoVisible: function() {
+ return video.is(':visible');
+ },
+ /**
+ * Updates the large video with the given new video source.
+ */
+ updateLargeVideo: function(resourceJid, forceUpdate) {
+ console.log('hover in', resourceJid);
+ var newSmallVideo = this.VideoLayout.getSmallVideo(resourceJid);
+
+ if ((currentSmallVideo && currentSmallVideo.resourceJid !== resourceJid)
+ || forceUpdate) {
+ $('#activeSpeaker').css('visibility', 'hidden');
+
+ if(currentSmallVideo) {
+ oldSmallVideo = currentSmallVideo;
+ } else {
+ oldSmallVideo = null;
+ }
+
+ currentSmallVideo = newSmallVideo;
+ var oldJid = null;
+ if(oldSmallVideo)
+ oldJid = oldSmallVideo.peerJid;
+
+ if (oldJid !== resourceJid) {
+ // we want the notification to trigger even if userJid is undefined,
+ // or null.
+ this.eventEmitter.emit(UIEvents.SELECTED_ENDPOINT,
+ resourceJid);
+ }
+
+ video.fadeOut(300, changeVideo.bind(video, this.isLargeVideoVisible()));
+ } else {
+ if(currentSmallVideo) {
+ currentSmallVideo.showAvatar();
+ }
+ }
+
+ },
+
+ /**
+ * Shows/hides the large video.
+ */
+ setLargeVideoVisible: function(isVisible) {
+ if (isVisible) {
+ $('#largeVideo').css({visibility: 'visible'});
+ $('.watermark').css({visibility: 'visible'});
+ if(currentSmallVideo)
+ currentSmallVideo.enableDominantSpeaker(true);
+ }
+ else {
+ $('#largeVideo').css({visibility: 'hidden'});
+ $('#activeSpeaker').css('visibility', 'hidden');
+ $('.watermark').css({visibility: 'hidden'});
+ if(currentSmallVideo)
+ currentSmallVideo.enableDominantSpeaker(false);
+ }
+ },
+ onVideoTypeChanged: function (jid) {
+ if(jid && currentSmallVideo && jid === currentSmallVideo.peerJid)
+ {
+ var isDesktop = APP.RTC.isVideoSrcDesktop(jid);
+ getVideoSize = isDesktop
+ ? getDesktopVideoSize
+ : getCameraVideoSize;
+ getVideoPosition = isDesktop
+ ? getDesktopVideoPosition
+ : getCameraVideoPosition;
+ this.position(null, null);
+ }
+ },
+ /**
+ * Positions the large video.
+ *
+ * @param videoWidth the stream video width
+ * @param videoHeight the stream video height
+ */
+ position: function (videoWidth, videoHeight,
+ videoSpaceWidth, videoSpaceHeight, animate) {
+ if(!videoSpaceWidth)
+ videoSpaceWidth = $('#videospace').width();
+ if(!videoSpaceHeight)
+ videoSpaceHeight = window.innerHeight;
+
+ var videoSize = getVideoSize(videoWidth,
+ videoHeight,
+ videoSpaceWidth,
+ videoSpaceHeight);
+
+ var largeVideoWidth = videoSize[0];
+ var largeVideoHeight = videoSize[1];
+
+ var videoPosition = getVideoPosition(largeVideoWidth,
+ largeVideoHeight,
+ videoSpaceWidth,
+ videoSpaceHeight);
+
+ var horizontalIndent = videoPosition[0];
+ var verticalIndent = videoPosition[1];
+
+ positionVideo($('#largeVideo'),
+ largeVideoWidth,
+ largeVideoHeight,
+ horizontalIndent, verticalIndent, animate);
+ },
+
+ isLargeVideoOnTop: function () {
+ var Etherpad = require("../etherpad/Etherpad");
+ var Prezi = require("../prezi/Prezi");
+ return !Prezi.isPresentationVisible() && !Etherpad.isVisible();
+ },
+ resize: function (animate, isVisible, completeFunction) {
+ var availableHeight = window.innerHeight;
+ var availableWidth = UIUtil.getAvailableVideoWidth(isVisible);
+
+ if (availableWidth < 0 || availableHeight < 0) return;
+
+ var avatarSize = interfaceConfig.ACTIVE_SPEAKER_AVATAR_SIZE;
+ var top = availableHeight / 2 - avatarSize / 4 * 3;
+ $('#activeSpeaker').css('top', top);
+
+ if(animate)
+ {
+ $('#videospace').animate({
+ right: window.innerWidth - availableWidth,
+ width: availableWidth,
+ height: availableHeight
+ },
+ {
+ queue: false,
+ duration: 500,
+ complete: completeFunction
+ });
+
+ $('#largeVideoContainer').animate({
+ width: availableWidth,
+ height: availableHeight
+ },
+ {
+ queue: false,
+ duration: 500
+ });
+
+
+ }
+ else
+ {
+ $('#videospace').width(availableWidth);
+ $('#videospace').height(availableHeight);
+ $('#largeVideoContainer').width(availableWidth);
+ $('#largeVideoContainer').height(availableHeight);
+ }
+ return [availableWidth, availableHeight];
+
+ },
+ resizeVideoAreaAnimated: function (isVisible, completeFunction) {
+ var size = this.resize(true, isVisible, completeFunction);
+ this.position(null, null, size[0], size[1], true);
+ },
+ getResourceJid: function () {
+ if(!currentSmallVideo)
+ return null;
+ return currentSmallVideo.resourceJid;
+ },
+ updateAvatar: function (resourceJid) {
+ if (resourceJid === this.getResourceJid()) {
+ updateActiveSpeakerAvatarSrc();
+ }
+ },
+ showAvatar: function (resourceJid, show) {
+ if(this.getResourceJid() === resourceJid
+ && LargeVideo.isLargeVideoOnTop())
+ {
+ $("#largeVideo").css("visibility", show ? "hidden" : "visible");
+ $('#activeSpeaker').css("visibility", show ? "visible" : "hidden");
+ return true;
+ }
+ return false;
+ }
+
+}
+
+
module.exports = LargeVideo;
-},{"../../../service/UI/UIEvents":105,"../../xmpp/xmpp":64,"../avatar/Avatar":14,"../etherpad/Etherpad":15,"../prezi/Prezi":16,"../util/UIUtil":31}],34:[function(require,module,exports){
-var SmallVideo = require("./SmallVideo");
-var ConnectionIndicator = require("./ConnectionIndicator");
-var NicknameHandler = require("../util/NicknameHandler");
-var UIUtil = require("../util/UIUtil");
-var LargeVideo = require("./LargeVideo");
-
-function LocalVideo(VideoLayout)
-{
- this.videoSpanId = "localVideoContainer";
- this.container = $("#localVideoContainer").get(0);
- this.VideoLayout = VideoLayout;
- this.flipX = true;
- this.peerJid = null;
- this.resourceJid = null;
-}
-
-LocalVideo.prototype = Object.create(SmallVideo.prototype);
-LocalVideo.prototype.constructor = LocalVideo;
-
-/**
- * Creates the edit display name button.
- *
- * @returns the edit button
- */
-function createEditDisplayNameButton() {
- var editButton = document.createElement('a');
- editButton.className = 'displayname';
- UIUtil.setTooltip(editButton,
- "videothumbnail.editnickname",
- "top");
- editButton.innerHTML = ' ';
-
- return editButton;
-}
-
-
-/**
- * Sets the display name for the given video span id.
- */
-LocalVideo.prototype.setDisplayName = function(displayName, key) {
-
- if (!this.container) {
- console.warn(
- "Unable to set displayName - " + this.videoSpanId + " does not exist");
- return;
- }
-
- var nameSpan = $('#' + this.videoSpanId + '>span.displayname');
- var defaultLocalDisplayName = APP.translation.generateTranslationHTML(
- interfaceConfig.DEFAULT_LOCAL_DISPLAY_NAME);
-
- // If we already have a display name for this video.
- if (nameSpan.length > 0) {
-
- if (nameSpan.text() !== displayName) {
- if (displayName && displayName.length > 0)
- {
- var meHTML = APP.translation.generateTranslationHTML("me");
- $('#localDisplayName').html(displayName + ' (' + meHTML + ')');
- }
- else
- $('#localDisplayName').html(defaultLocalDisplayName);
- }
- } else {
- var editButton = createEditDisplayNameButton();
-
- nameSpan = document.createElement('span');
- nameSpan.className = 'displayname';
- $('#' + this.videoSpanId)[0].appendChild(nameSpan);
-
-
- if (displayName && displayName.length > 0) {
- var meHTML = APP.translation.generateTranslationHTML("me");
- nameSpan.innerHTML = displayName + meHTML;
- }
- else {
- nameSpan.innerHTML = defaultLocalDisplayName;
- }
-
-
- nameSpan.id = 'localDisplayName';
- this.container.appendChild(editButton);
- //translates popover of edit button
- APP.translation.translateElement($("a.displayname"));
-
- var editableText = document.createElement('input');
- editableText.className = 'displayname';
- editableText.type = 'text';
- editableText.id = 'editDisplayName';
-
- if (displayName && displayName.length) {
- editableText.value = displayName;
- }
-
- var defaultNickname = APP.translation.translateString(
- "defaultNickname", {name: "Jane Pink"});
- editableText.setAttribute('style', 'display:none;');
- editableText.setAttribute('data-18n',
- '[placeholder]defaultNickname');
- editableText.setAttribute("data-i18n-options",
- JSON.stringify({name: "Jane Pink"}));
- editableText.setAttribute("placeholder", defaultNickname);
-
- this.container.appendChild(editableText);
-
- var self = this;
- $('#localVideoContainer .displayname')
- .bind("click", function (e) {
-
- e.preventDefault();
- e.stopPropagation();
- $('#localDisplayName').hide();
- $('#editDisplayName').show();
- $('#editDisplayName').focus();
- $('#editDisplayName').select();
-
- $('#editDisplayName').one("focusout", function (e) {
- self.VideoLayout.inputDisplayNameHandler(this.value);
- });
-
- $('#editDisplayName').on('keydown', function (e) {
- if (e.keyCode === 13) {
- e.preventDefault();
- self.VideoLayout.inputDisplayNameHandler(this.value);
- }
- });
- });
- }
-}
-
-LocalVideo.prototype.inputDisplayNameHandler = function (name) {
- NicknameHandler.setNickname(name);
-
- if (!$('#localDisplayName').is(":visible")) {
- if (NicknameHandler.getNickname())
- {
- var meHTML = APP.translation.generateTranslationHTML("me");
- $('#localDisplayName').html(NicknameHandler.getNickname() + " (" + meHTML + ")");
- }
- else
- {
- var defaultHTML = APP.translation.generateTranslationHTML(
- interfaceConfig.DEFAULT_LOCAL_DISPLAY_NAME);
- $('#localDisplayName')
- .html(defaultHTML);
- }
- $('#localDisplayName').show();
- }
-
- $('#editDisplayName').hide();
-}
-
-LocalVideo.prototype.createConnectionIndicator = function()
-{
- if(this.connectionIndicator)
- return;
-
- this.connectionIndicator
- = new ConnectionIndicator(this, null);
-}
-
-LocalVideo.prototype.changeVideo = function (stream, isMuted) {
- var self = this;
-
- function localVideoClick(event) {
- event.stopPropagation();
- self.VideoLayout.handleVideoThumbClicked(
- false,
- APP.xmpp.myResource());
- }
-
- $('#localVideoContainer').click(localVideoClick);
-
- // Add hover handler
- $('#localVideoContainer').hover(
- function() {
- self.showDisplayName(true);
- },
- function() {
- if (!LargeVideo.isLargeVideoVisible()
- || APP.xmpp.myResource() !== LargeVideo.getResourceJid())
- self.showDisplayName(false);
- }
- );
-
- if(isMuted)
- {
- APP.UI.setVideoMute(true);
- return;
- }
- this.flipX = (stream.videoType == "screen")? false : true;
- var localVideo = document.createElement('video');
- localVideo.id = 'localVideo_' +
- APP.RTC.getStreamID(stream.getOriginalStream());
- localVideo.autoplay = true;
- localVideo.volume = 0; // is it required if audio is separated ?
- localVideo.oncontextmenu = function () { return false; };
-
- var localVideoContainer = document.getElementById('localVideoWrapper');
- localVideoContainer.appendChild(localVideo);
-
- var localVideoSelector = $('#' + localVideo.id);
-
- // Add click handler to both video and video wrapper elements in case
- // there's no video.
- localVideoSelector.click(localVideoClick);
-
- if (this.flipX) {
- localVideoSelector.addClass("flipVideoX");
- }
-
- // Attach WebRTC stream
- APP.RTC.attachMediaStream(localVideoSelector, stream.getOriginalStream());
-
- // Add stream ended handler
- stream.getOriginalStream().onended = function () {
- localVideoContainer.removeChild(localVideo);
- self.VideoLayout.updateRemovedVideo(APP.xmpp.myResource());
- };
-}
-
-LocalVideo.prototype.joined = function (jid) {
- this.peerJid = jid;
- this.resourceJid = Strophe.getResourceFromJid(jid);
-}
-
+},{"../../../service/UI/UIEvents":103,"../../xmpp/xmpp":66,"../avatar/Avatar":16,"../etherpad/Etherpad":17,"../prezi/Prezi":18,"../util/UIUtil":33}],36:[function(require,module,exports){
+var SmallVideo = require("./SmallVideo");
+var ConnectionIndicator = require("./ConnectionIndicator");
+var NicknameHandler = require("../util/NicknameHandler");
+var UIUtil = require("../util/UIUtil");
+var LargeVideo = require("./LargeVideo");
+
+function LocalVideo(VideoLayout)
+{
+ this.videoSpanId = "localVideoContainer";
+ this.container = $("#localVideoContainer").get(0);
+ this.VideoLayout = VideoLayout;
+ this.flipX = true;
+ this.peerJid = null;
+ this.resourceJid = null;
+}
+
+LocalVideo.prototype = Object.create(SmallVideo.prototype);
+LocalVideo.prototype.constructor = LocalVideo;
+
+/**
+ * Creates the edit display name button.
+ *
+ * @returns the edit button
+ */
+function createEditDisplayNameButton() {
+ var editButton = document.createElement('a');
+ editButton.className = 'displayname';
+ UIUtil.setTooltip(editButton,
+ "videothumbnail.editnickname",
+ "top");
+ editButton.innerHTML = ' ';
+
+ return editButton;
+}
+
+
+/**
+ * Sets the display name for the given video span id.
+ */
+LocalVideo.prototype.setDisplayName = function(displayName, key) {
+
+ if (!this.container) {
+ console.warn(
+ "Unable to set displayName - " + this.videoSpanId + " does not exist");
+ return;
+ }
+
+ var nameSpan = $('#' + this.videoSpanId + '>span.displayname');
+ var defaultLocalDisplayName = APP.translation.generateTranslationHTML(
+ interfaceConfig.DEFAULT_LOCAL_DISPLAY_NAME);
+
+ // If we already have a display name for this video.
+ if (nameSpan.length > 0) {
+
+ if (nameSpan.text() !== displayName) {
+ if (displayName && displayName.length > 0)
+ {
+ var meHTML = APP.translation.generateTranslationHTML("me");
+ $('#localDisplayName').html(displayName + ' (' + meHTML + ')');
+ }
+ else
+ $('#localDisplayName').html(defaultLocalDisplayName);
+ }
+ } else {
+ var editButton = createEditDisplayNameButton();
+
+ nameSpan = document.createElement('span');
+ nameSpan.className = 'displayname';
+ $('#' + this.videoSpanId)[0].appendChild(nameSpan);
+
+
+ if (displayName && displayName.length > 0) {
+ var meHTML = APP.translation.generateTranslationHTML("me");
+ nameSpan.innerHTML = displayName + meHTML;
+ }
+ else {
+ nameSpan.innerHTML = defaultLocalDisplayName;
+ }
+
+
+ nameSpan.id = 'localDisplayName';
+ this.container.appendChild(editButton);
+ //translates popover of edit button
+ APP.translation.translateElement($("a.displayname"));
+
+ var editableText = document.createElement('input');
+ editableText.className = 'displayname';
+ editableText.type = 'text';
+ editableText.id = 'editDisplayName';
+
+ if (displayName && displayName.length) {
+ editableText.value = displayName;
+ }
+
+ var defaultNickname = APP.translation.translateString(
+ "defaultNickname", {name: "Jane Pink"});
+ editableText.setAttribute('style', 'display:none;');
+ editableText.setAttribute('data-18n',
+ '[placeholder]defaultNickname');
+ editableText.setAttribute("data-i18n-options",
+ JSON.stringify({name: "Jane Pink"}));
+ editableText.setAttribute("placeholder", defaultNickname);
+
+ this.container.appendChild(editableText);
+
+ var self = this;
+ $('#localVideoContainer .displayname')
+ .bind("click", function (e) {
+
+ e.preventDefault();
+ e.stopPropagation();
+ $('#localDisplayName').hide();
+ $('#editDisplayName').show();
+ $('#editDisplayName').focus();
+ $('#editDisplayName').select();
+
+ $('#editDisplayName').one("focusout", function (e) {
+ self.VideoLayout.inputDisplayNameHandler(this.value);
+ });
+
+ $('#editDisplayName').on('keydown', function (e) {
+ if (e.keyCode === 13) {
+ e.preventDefault();
+ self.VideoLayout.inputDisplayNameHandler(this.value);
+ }
+ });
+ });
+ }
+}
+
+LocalVideo.prototype.inputDisplayNameHandler = function (name) {
+ NicknameHandler.setNickname(name);
+
+ if (!$('#localDisplayName').is(":visible")) {
+ if (NicknameHandler.getNickname())
+ {
+ var meHTML = APP.translation.generateTranslationHTML("me");
+ $('#localDisplayName').html(NicknameHandler.getNickname() + " (" + meHTML + ")");
+ }
+ else
+ {
+ var defaultHTML = APP.translation.generateTranslationHTML(
+ interfaceConfig.DEFAULT_LOCAL_DISPLAY_NAME);
+ $('#localDisplayName')
+ .html(defaultHTML);
+ }
+ $('#localDisplayName').show();
+ }
+
+ $('#editDisplayName').hide();
+}
+
+LocalVideo.prototype.createConnectionIndicator = function()
+{
+ if(this.connectionIndicator)
+ return;
+
+ this.connectionIndicator
+ = new ConnectionIndicator(this, null);
+}
+
+LocalVideo.prototype.changeVideo = function (stream, isMuted) {
+ var self = this;
+
+ function localVideoClick(event) {
+ event.stopPropagation();
+ self.VideoLayout.handleVideoThumbClicked(
+ false,
+ APP.xmpp.myResource());
+ }
+
+ $('#localVideoContainer').click(localVideoClick);
+
+ // Add hover handler
+ $('#localVideoContainer').hover(
+ function() {
+ self.showDisplayName(true);
+ },
+ function() {
+ if (!LargeVideo.isLargeVideoVisible()
+ || APP.xmpp.myResource() !== LargeVideo.getResourceJid())
+ self.showDisplayName(false);
+ }
+ );
+
+ if(isMuted)
+ {
+ APP.UI.setVideoMute(true);
+ return;
+ }
+ this.flipX = (stream.videoType == "screen")? false : true;
+ var localVideo = document.createElement('video');
+ localVideo.id = 'localVideo_' +
+ APP.RTC.getStreamID(stream.getOriginalStream());
+ localVideo.autoplay = true;
+ localVideo.volume = 0; // is it required if audio is separated ?
+ localVideo.oncontextmenu = function () { return false; };
+
+ var localVideoContainer = document.getElementById('localVideoWrapper');
+ localVideoContainer.appendChild(localVideo);
+
+ var localVideoSelector = $('#' + localVideo.id);
+
+ // Add click handler to both video and video wrapper elements in case
+ // there's no video.
+ localVideoSelector.click(localVideoClick);
+
+ if (this.flipX) {
+ localVideoSelector.addClass("flipVideoX");
+ }
+
+ // Attach WebRTC stream
+ APP.RTC.attachMediaStream(localVideoSelector, stream.getOriginalStream());
+
+ // Add stream ended handler
+ stream.getOriginalStream().onended = function () {
+ localVideoContainer.removeChild(localVideo);
+ self.VideoLayout.updateRemovedVideo(APP.xmpp.myResource());
+ };
+}
+
+LocalVideo.prototype.joined = function (jid) {
+ this.peerJid = jid;
+ this.resourceJid = Strophe.getResourceFromJid(jid);
+}
+
module.exports = LocalVideo;
-},{"../util/NicknameHandler":30,"../util/UIUtil":31,"./ConnectionIndicator":32,"./LargeVideo":33,"./SmallVideo":36}],35:[function(require,module,exports){
-var ConnectionIndicator = require("./ConnectionIndicator");
-var SmallVideo = require("./SmallVideo");
-var AudioLevels = require("../audio_levels/AudioLevels");
-var LargeVideo = require("./LargeVideo");
-var Avatar = require("../avatar/Avatar");
-
-function RemoteVideo(peerJid, VideoLayout)
-{
- this.peerJid = peerJid;
- this.resourceJid = Strophe.getResourceFromJid(peerJid);
- this.videoSpanId = 'participant_' + this.resourceJid;
- this.VideoLayout = VideoLayout;
- this.addRemoteVideoContainer();
- this.connectionIndicator = new ConnectionIndicator(
- this, this.peerJid);
- this.setDisplayName();
- var nickfield = document.createElement('span');
- nickfield.className = "nick";
- nickfield.appendChild(document.createTextNode(this.resourceJid));
- this.container.appendChild(nickfield);
- this.flipX = false;
-}
-
-RemoteVideo.prototype = Object.create(SmallVideo.prototype);
-RemoteVideo.prototype.constructor = RemoteVideo;
-
-RemoteVideo.prototype.addRemoteVideoContainer = function() {
- this.container = RemoteVideo.createContainer(this.videoSpanId);
- if (APP.xmpp.isModerator())
- this.addRemoteVideoMenu();
- AudioLevels.updateAudioLevelCanvas(this.peerJid, this.VideoLayout);
-
- return this.container;
-};
-
-/**
- * Adds the remote video menu element for the given jid in the
- * given parentElement .
- *
- * @param jid the jid indicating the video for which we're adding a menu.
- * @param parentElement the parent element where this menu will be added
- */
-RemoteVideo.prototype.addRemoteVideoMenu = function () {
- var spanElement = document.createElement('span');
- spanElement.className = 'remotevideomenu';
-
- this.container.appendChild(spanElement);
-
- var menuElement = document.createElement('i');
- menuElement.className = 'fa fa-angle-down';
- menuElement.title = 'Remote user controls';
- spanElement.appendChild(menuElement);
-
-
- var popupmenuElement = document.createElement('ul');
- popupmenuElement.className = 'popupmenu';
- popupmenuElement.id
- = 'remote_popupmenu_' + this.resourceJid;
- spanElement.appendChild(popupmenuElement);
-
- var muteMenuItem = document.createElement('li');
- var muteLinkItem = document.createElement('a');
-
- var mutedIndicator = " ";
-
- if (!this.isMuted) {
- muteLinkItem.innerHTML = mutedIndicator +
- "
";
- muteLinkItem.className = 'mutelink';
- }
- else {
- muteLinkItem.innerHTML = mutedIndicator +
- "
";
- muteLinkItem.className = 'mutelink disabled';
- }
-
- var self = this;
- muteLinkItem.onclick = function(){
- if ($(this).attr('disabled') != undefined) {
- event.preventDefault();
- }
- var isMute = self.isMuted == true;
- APP.xmpp.setMute(self.peerJid, !isMute);
-
- popupmenuElement.setAttribute('style', 'display:none;');
-
- if (isMute) {
- this.innerHTML = mutedIndicator +
- "
";
- this.className = 'mutelink disabled';
- }
- else {
- this.innerHTML = mutedIndicator +
- "
";
- this.className = 'mutelink';
- }
- };
-
- muteMenuItem.appendChild(muteLinkItem);
- popupmenuElement.appendChild(muteMenuItem);
-
- var ejectIndicator = " ";
-
- var ejectMenuItem = document.createElement('li');
- var ejectLinkItem = document.createElement('a');
- var ejectText = "
";
- ejectLinkItem.innerHTML = ejectIndicator + ' ' + ejectText;
- ejectLinkItem.onclick = function(){
- APP.xmpp.eject(self.peerJid);
- popupmenuElement.setAttribute('style', 'display:none;');
- };
-
- ejectMenuItem.appendChild(ejectLinkItem);
- popupmenuElement.appendChild(ejectMenuItem);
-
- var paddingSpan = document.createElement('span');
- paddingSpan.className = 'popupmenuPadding';
- popupmenuElement.appendChild(paddingSpan);
- APP.translation.translateElement($("#" + popupmenuElement.id + " > li > a > div"));
-}
-
-
-/**
- * Removes the remote stream element corresponding to the given stream and
- * parent container.
- *
- * @param stream the stream
- * @param isVideo true if given stream is a video one.
- * @param container
- */
-RemoteVideo.prototype.removeRemoteStreamElement = function (stream, isVideo, id) {
- if (!this.container)
- return false;
-
- var select = null;
- if (isVideo) {
- select = $('#' + id);
- }
- else
- select = $('#' + this.videoSpanId + '>audio');
-
-
- // Mark video as removed to cancel waiting loop(if video is removed
- // before has started)
- select.removed = true;
- select.remove();
-
- var audioCount = $('#' + this.videoSpanId + '>audio').length;
- var videoCount = $('#' + this.videoSpanId + '>video').length;
-
- if (!audioCount && !videoCount) {
- console.log("Remove whole user", this.videoSpanId);
- if(this.connectionIndicator)
- this.connectionIndicator.remove();
- // Remove whole container
- this.container.remove();
-
- this.VideoLayout.resizeThumbnails();
- }
-
- if (isVideo)
- this.VideoLayout.updateRemovedVideo(this.resourceJid);
-};
-
-RemoteVideo.prototype.addRemoteStreamElement = function (sid, stream, thessrc) {
- var isVideo = stream.getVideoTracks().length > 0;
- if(!this.container)
- return;
-
- var streamElement = SmallVideo.createStreamElement(sid, stream);
- var newElementId = streamElement.id;
-
- this.container.appendChild(streamElement);
-
- var sel = $('#' + newElementId);
- sel.hide();
-
- // If the container is currently visible we attach the stream.
- if (!isVideo
- || (this.container.offsetParent !== null && isVideo)) {
- APP.RTC.attachMediaStream(sel, stream);
-
- if (isVideo)
- this.waitForRemoteVideo(sel, thessrc, stream);
- }
-
- var self = this;
- stream.onended = function () {
- console.log('stream ended', this);
-
- self.removeRemoteStreamElement(stream, isVideo, newElementId);
-
- };
-
- // Add click handler.
- this.container.onclick = function (event) {
- /*
- * FIXME It turns out that videoThumb may not exist (if there is
- * no actual video).
- */
- var videoThumb = $('#' + self.videoSpanId + '>video').get(0);
- if (videoThumb) {
- self.VideoLayout.handleVideoThumbClicked(
- false,
- self.resourceJid);
- }
-
- event.stopPropagation();
- event.preventDefault();
- return false;
- };
-
- //FIXME
- // Add hover handler
- $(this.container).hover(
- function() {
- self.showDisplayName(true);
- },
- function() {
- var videoSrc = null;
- if ($('#' + self.videoSpanId + '>video')
- && $('#' + self.videoSpanId + '>video').length > 0) {
- videoSrc = APP.RTC.getVideoSrc($('#' + self.videoSpanId + '>video').get(0));
- }
-
- // If the video has been "pinned" by the user we want to
- // keep the display name on place.
- if (!LargeVideo.isLargeVideoVisible()
- || videoSrc !== APP.RTC.getVideoSrc($('#largeVideo')[0]))
- self.showDisplayName(false);
- }
- );
-}
-
-
-RemoteVideo.prototype.waitForRemoteVideo = function(selector, ssrc, stream) {
- if (selector.removed || !selector.parent().is(":visible")) {
- console.warn("Media removed before had started", selector);
- return;
- }
-
- if (stream.id === 'mixedmslabel') return;
-
- if (selector[0].currentTime > 0) {
- APP.RTC.attachMediaStream(selector, stream); // FIXME: why do i have to do this for FF?
- this.VideoLayout.videoactive(selector, this.resourceJid);
- } else {
- var self = this;
- setTimeout(function () {
- self.waitForRemoteVideo(selector, ssrc, stream);
- }, 250);
- }
-}
-
-/**
- * Show/hide peer container for the given resourceJid.
- */
-RemoteVideo.prototype.showPeerContainer = function (state) {
- if (!this.container)
- return;
-
- var isHide = state === 'hide';
- var resizeThumbnails = false;
-
- if (!isHide) {
- if (!$(this.container).is(':visible')) {
- resizeThumbnails = true;
- $(this.container).show();
- }
-
- this.showAvatar(state !== 'show');
- }
- else if ($(this.container).is(':visible') && isHide)
- {
- resizeThumbnails = true;
- $(this.container).hide();
- if(this.connectionIndicator)
- this.connectionIndicator.hide();
- }
-
- if (resizeThumbnails) {
- this.VideoLayout.resizeThumbnails();
- }
-
- // We want to be able to pin a participant from the contact list, even
- // if he's not in the lastN set!
- // ContactList.setClickable(resourceJid, !isHide);
-
-};
-
-RemoteVideo.prototype.removeConnectionIndicator = function () {
- if(this.connectionIndicator)
- this.connectionIndicator.remove();
-}
-
-RemoteVideo.prototype.hideConnectionIndicator = function () {
- if(this.connectionIndicator)
- this.connectionIndicator.hide();
-}
-
-/**
- * Updates the remote video menu.
- *
- * @param jid the jid indicating the video for which we're adding a menu.
- * @param isMuted indicates the current mute state
- */
-RemoteVideo.prototype.updateRemoteVideoMenu = function (isMuted) {
- var muteMenuItem
- = $('#remote_popupmenu_'
- + this.resourceJid
- + '>li>a.mutelink');
-
- var mutedIndicator = " ";
-
- if (muteMenuItem.length) {
- var muteLink = muteMenuItem.get(0);
-
- if (isMuted === 'true') {
- muteLink.innerHTML = mutedIndicator + ' Muted';
- muteLink.className = 'mutelink disabled';
- }
- else {
- muteLink.innerHTML = mutedIndicator + ' Mute';
- muteLink.className = 'mutelink';
- }
- }
-}
-
-/**
- * Sets the display name for the given video span id.
- */
-RemoteVideo.prototype.setDisplayName = function(displayName, key) {
-
- if (!this.container) {
- console.warn(
- "Unable to set displayName - " + this.videoSpanId + " does not exist");
- return;
- }
-
- var nameSpan = $('#' + this.videoSpanId + '>span.displayname');
-
- // If we already have a display name for this video.
- if (nameSpan.length > 0) {
- if (displayName && displayName.length > 0)
- {
- $('#' + this.videoSpanId + '_name').html(displayName);
- }
- else if (key && key.length > 0)
- {
- var nameHtml = APP.translation.generateTranslationHTML(key);
- $('#' + this.videoSpanId + '_name').html(nameHtml);
- }
- else
- $('#' + this.videoSpanId + '_name').text(
- interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME);
- } else {
- nameSpan = document.createElement('span');
- nameSpan.className = 'displayname';
- $('#' + this.videoSpanId)[0].appendChild(nameSpan);
-
-
- if (displayName && displayName.length > 0) {
-
- nameSpan.innerText = displayName;
- }
- else
- nameSpan.innerText = interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME;
-
-
- nameSpan.id = this.videoSpanId + '_name';
-
- }
-}
-
-/**
- * Removes remote video menu element from video element identified by
- * given videoElementId .
- *
- * @param videoElementId the id of local or remote video element.
- */
-RemoteVideo.prototype.removeRemoteVideoMenu = function() {
- var menuSpan = $('#' + this.videoSpanId + '>span.remotevideomenu');
- if (menuSpan.length) {
- menuSpan.remove();
- }
-}
-
-RemoteVideo.createContainer = function (spanId) {
- var container = document.createElement('span');
- container.id = spanId;
- container.className = 'videocontainer';
- var remotes = document.getElementById('remoteVideos');
- return remotes.appendChild(container);
-};
-
-
+},{"../util/NicknameHandler":32,"../util/UIUtil":33,"./ConnectionIndicator":34,"./LargeVideo":35,"./SmallVideo":38}],37:[function(require,module,exports){
+var ConnectionIndicator = require("./ConnectionIndicator");
+var SmallVideo = require("./SmallVideo");
+var AudioLevels = require("../audio_levels/AudioLevels");
+var LargeVideo = require("./LargeVideo");
+var Avatar = require("../avatar/Avatar");
+
+function RemoteVideo(peerJid, VideoLayout)
+{
+ this.peerJid = peerJid;
+ this.resourceJid = Strophe.getResourceFromJid(peerJid);
+ this.videoSpanId = 'participant_' + this.resourceJid;
+ this.VideoLayout = VideoLayout;
+ this.addRemoteVideoContainer();
+ this.connectionIndicator = new ConnectionIndicator(
+ this, this.peerJid);
+ this.setDisplayName();
+ var nickfield = document.createElement('span');
+ nickfield.className = "nick";
+ nickfield.appendChild(document.createTextNode(this.resourceJid));
+ this.container.appendChild(nickfield);
+ this.flipX = false;
+}
+
+RemoteVideo.prototype = Object.create(SmallVideo.prototype);
+RemoteVideo.prototype.constructor = RemoteVideo;
+
+RemoteVideo.prototype.addRemoteVideoContainer = function() {
+ this.container = RemoteVideo.createContainer(this.videoSpanId);
+ if (APP.xmpp.isModerator())
+ this.addRemoteVideoMenu();
+ AudioLevels.updateAudioLevelCanvas(this.peerJid, this.VideoLayout);
+
+ return this.container;
+};
+
+/**
+ * Adds the remote video menu element for the given jid in the
+ * given parentElement .
+ *
+ * @param jid the jid indicating the video for which we're adding a menu.
+ * @param parentElement the parent element where this menu will be added
+ */
+RemoteVideo.prototype.addRemoteVideoMenu = function () {
+ var spanElement = document.createElement('span');
+ spanElement.className = 'remotevideomenu';
+
+ this.container.appendChild(spanElement);
+
+ var menuElement = document.createElement('i');
+ menuElement.className = 'fa fa-angle-down';
+ menuElement.title = 'Remote user controls';
+ spanElement.appendChild(menuElement);
+
+
+ var popupmenuElement = document.createElement('ul');
+ popupmenuElement.className = 'popupmenu';
+ popupmenuElement.id
+ = 'remote_popupmenu_' + this.resourceJid;
+ spanElement.appendChild(popupmenuElement);
+
+ var muteMenuItem = document.createElement('li');
+ var muteLinkItem = document.createElement('a');
+
+ var mutedIndicator = " ";
+
+ if (!this.isMuted) {
+ muteLinkItem.innerHTML = mutedIndicator +
+ "
";
+ muteLinkItem.className = 'mutelink';
+ }
+ else {
+ muteLinkItem.innerHTML = mutedIndicator +
+ "
";
+ muteLinkItem.className = 'mutelink disabled';
+ }
+
+ var self = this;
+ muteLinkItem.onclick = function(){
+ if ($(this).attr('disabled') != undefined) {
+ event.preventDefault();
+ }
+ var isMute = self.isMuted == true;
+ APP.xmpp.setMute(self.peerJid, !isMute);
+
+ popupmenuElement.setAttribute('style', 'display:none;');
+
+ if (isMute) {
+ this.innerHTML = mutedIndicator +
+ "
";
+ this.className = 'mutelink disabled';
+ }
+ else {
+ this.innerHTML = mutedIndicator +
+ "
";
+ this.className = 'mutelink';
+ }
+ };
+
+ muteMenuItem.appendChild(muteLinkItem);
+ popupmenuElement.appendChild(muteMenuItem);
+
+ var ejectIndicator = " ";
+
+ var ejectMenuItem = document.createElement('li');
+ var ejectLinkItem = document.createElement('a');
+ var ejectText = "
";
+ ejectLinkItem.innerHTML = ejectIndicator + ' ' + ejectText;
+ ejectLinkItem.onclick = function(){
+ APP.xmpp.eject(self.peerJid);
+ popupmenuElement.setAttribute('style', 'display:none;');
+ };
+
+ ejectMenuItem.appendChild(ejectLinkItem);
+ popupmenuElement.appendChild(ejectMenuItem);
+
+ var paddingSpan = document.createElement('span');
+ paddingSpan.className = 'popupmenuPadding';
+ popupmenuElement.appendChild(paddingSpan);
+ APP.translation.translateElement($("#" + popupmenuElement.id + " > li > a > div"));
+}
+
+
+/**
+ * Removes the remote stream element corresponding to the given stream and
+ * parent container.
+ *
+ * @param stream the stream
+ * @param isVideo true if given stream is a video one.
+ * @param container
+ */
+RemoteVideo.prototype.removeRemoteStreamElement = function (stream, isVideo, id) {
+ if (!this.container)
+ return false;
+
+ var select = null;
+ if (isVideo) {
+ select = $('#' + id);
+ }
+ else
+ select = $('#' + this.videoSpanId + '>audio');
+
+
+ // Mark video as removed to cancel waiting loop(if video is removed
+ // before has started)
+ select.removed = true;
+ select.remove();
+
+ var audioCount = $('#' + this.videoSpanId + '>audio').length;
+ var videoCount = $('#' + this.videoSpanId + '>video').length;
+
+ if (!audioCount && !videoCount) {
+ console.log("Remove whole user", this.videoSpanId);
+ if(this.connectionIndicator)
+ this.connectionIndicator.remove();
+ // Remove whole container
+ this.container.remove();
+
+ this.VideoLayout.resizeThumbnails();
+ }
+
+ if (isVideo)
+ this.VideoLayout.updateRemovedVideo(this.resourceJid);
+};
+
+RemoteVideo.prototype.addRemoteStreamElement = function (sid, stream, thessrc) {
+ if (!this.container)
+ return;
+
+ var self = this;
+ var isVideo = stream.getVideoTracks().length > 0;
+ var streamElement = SmallVideo.createStreamElement(sid, stream);
+ var newElementId = streamElement.id;
+
+ this.container.appendChild(streamElement);
+
+ var sel = $('#' + newElementId);
+ sel.hide();
+
+ // If the container is currently visible we attach the stream.
+ if (!isVideo
+ || (this.container.offsetParent !== null && isVideo)) {
+
+ // Register 'onplaying' listener to trigger 'videoactive' on VideoLayout
+ // when video playback starts
+ if (isVideo && stream.id !== 'mixedmslabel') {
+ var onPlayingHandler = function () {
+ // FIXME: why do i have to do this for FF?
+ APP.RTC.attachMediaStream(sel, stream);
+ self.VideoLayout.videoactive(sel, self.resourceJid);
+ sel.off("playing", onPlayingHandler);
+ };
+ sel.on("playing", onPlayingHandler);
+ }
+
+ APP.RTC.attachMediaStream(sel, stream);
+ }
+
+ var self = this;
+ stream.onended = function () {
+ console.log('stream ended', this);
+
+ self.removeRemoteStreamElement(stream, isVideo, newElementId);
+
+ };
+
+ // Add click handler.
+ this.container.onclick = function (event) {
+ /*
+ * FIXME It turns out that videoThumb may not exist (if there is
+ * no actual video).
+ */
+ var videoThumb = $('#' + self.videoSpanId + '>video').get(0);
+ if (videoThumb) {
+ self.VideoLayout.handleVideoThumbClicked(
+ false,
+ self.resourceJid);
+ }
+
+ event.stopPropagation();
+ event.preventDefault();
+ return false;
+ };
+
+ //FIXME
+ // Add hover handler
+ $(this.container).hover(
+ function() {
+ self.showDisplayName(true);
+ },
+ function() {
+ var videoSrc = null;
+ if ($('#' + self.videoSpanId + '>video')
+ && $('#' + self.videoSpanId + '>video').length > 0) {
+ videoSrc = APP.RTC.getVideoSrc($('#' + self.videoSpanId + '>video').get(0));
+ }
+
+ // If the video has been "pinned" by the user we want to
+ // keep the display name on place.
+ if (!LargeVideo.isLargeVideoVisible()
+ || videoSrc !== APP.RTC.getVideoSrc($('#largeVideo')[0]))
+ self.showDisplayName(false);
+ }
+ );
+}
+
+/**
+ * Show/hide peer container for the given resourceJid.
+ */
+RemoteVideo.prototype.showPeerContainer = function (state) {
+ if (!this.container)
+ return;
+
+ var isHide = state === 'hide';
+ var resizeThumbnails = false;
+
+ if (!isHide) {
+ if (!$(this.container).is(':visible')) {
+ resizeThumbnails = true;
+ $(this.container).show();
+ }
+
+ this.showAvatar(state !== 'show');
+ }
+ else if ($(this.container).is(':visible') && isHide)
+ {
+ resizeThumbnails = true;
+ $(this.container).hide();
+ if(this.connectionIndicator)
+ this.connectionIndicator.hide();
+ }
+
+ if (resizeThumbnails) {
+ this.VideoLayout.resizeThumbnails();
+ }
+
+ // We want to be able to pin a participant from the contact list, even
+ // if he's not in the lastN set!
+ // ContactList.setClickable(resourceJid, !isHide);
+
+};
+
+RemoteVideo.prototype.removeConnectionIndicator = function () {
+ if(this.connectionIndicator)
+ this.connectionIndicator.remove();
+}
+
+RemoteVideo.prototype.hideConnectionIndicator = function () {
+ if(this.connectionIndicator)
+ this.connectionIndicator.hide();
+}
+
+/**
+ * Updates the remote video menu.
+ *
+ * @param jid the jid indicating the video for which we're adding a menu.
+ * @param isMuted indicates the current mute state
+ */
+RemoteVideo.prototype.updateRemoteVideoMenu = function (isMuted) {
+ var muteMenuItem
+ = $('#remote_popupmenu_'
+ + this.resourceJid
+ + '>li>a.mutelink');
+
+ var mutedIndicator = " ";
+
+ if (muteMenuItem.length) {
+ var muteLink = muteMenuItem.get(0);
+
+ if (isMuted === 'true') {
+ muteLink.innerHTML = mutedIndicator + ' Muted';
+ muteLink.className = 'mutelink disabled';
+ }
+ else {
+ muteLink.innerHTML = mutedIndicator + ' Mute';
+ muteLink.className = 'mutelink';
+ }
+ }
+}
+
+/**
+ * Sets the display name for the given video span id.
+ */
+RemoteVideo.prototype.setDisplayName = function(displayName, key) {
+
+ if (!this.container) {
+ console.warn(
+ "Unable to set displayName - " + this.videoSpanId + " does not exist");
+ return;
+ }
+
+ var nameSpan = $('#' + this.videoSpanId + '>span.displayname');
+
+ // If we already have a display name for this video.
+ if (nameSpan.length > 0) {
+ if (displayName && displayName.length > 0)
+ {
+ $('#' + this.videoSpanId + '_name').html(displayName);
+ }
+ else if (key && key.length > 0)
+ {
+ var nameHtml = APP.translation.generateTranslationHTML(key);
+ $('#' + this.videoSpanId + '_name').html(nameHtml);
+ }
+ else
+ $('#' + this.videoSpanId + '_name').text(
+ interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME);
+ } else {
+ nameSpan = document.createElement('span');
+ nameSpan.className = 'displayname';
+ $('#' + this.videoSpanId)[0].appendChild(nameSpan);
+
+
+ if (displayName && displayName.length > 0) {
+
+ nameSpan.innerText = displayName;
+ }
+ else
+ nameSpan.innerText = interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME;
+
+
+ nameSpan.id = this.videoSpanId + '_name';
+
+ }
+}
+
+/**
+ * Removes remote video menu element from video element identified by
+ * given videoElementId .
+ *
+ * @param videoElementId the id of local or remote video element.
+ */
+RemoteVideo.prototype.removeRemoteVideoMenu = function() {
+ var menuSpan = $('#' + this.videoSpanId + '>span.remotevideomenu');
+ if (menuSpan.length) {
+ menuSpan.remove();
+ }
+}
+
+RemoteVideo.createContainer = function (spanId) {
+ var container = document.createElement('span');
+ container.id = spanId;
+ container.className = 'videocontainer';
+ var remotes = document.getElementById('remoteVideos');
+ return remotes.appendChild(container);
+};
+
+
module.exports = RemoteVideo;
-},{"../audio_levels/AudioLevels":10,"../avatar/Avatar":14,"./ConnectionIndicator":32,"./LargeVideo":33,"./SmallVideo":36}],36:[function(require,module,exports){
-var UIUtil = require("../util/UIUtil");
-var LargeVideo = require("./LargeVideo");
-
-function SmallVideo(){
- this.isMuted = false;
- this.hasAvatar = false;
-}
-
-function setVisibility(selector, show) {
- if (selector && selector.length > 0) {
- selector.css("visibility", show ? "visible" : "hidden");
- }
-}
-
-SmallVideo.prototype.showDisplayName = function(isShow) {
- var nameSpan = $('#' + this.videoSpanId + '>span.displayname').get(0);
- if (isShow) {
- if (nameSpan && nameSpan.innerHTML && nameSpan.innerHTML.length)
- nameSpan.setAttribute("style", "display:inline-block;");
- }
- else {
- if (nameSpan)
- nameSpan.setAttribute("style", "display:none;");
- }
-};
-
-SmallVideo.prototype.setDeviceAvailabilityIcons = function (devices) {
- if(!this.container)
- return;
-
- $("#" + this.videoSpanId + " > .noMic").remove();
- $("#" + this.videoSpanId + " > .noVideo").remove();
- if(!devices.audio)
- {
- this.container.appendChild(
- document.createElement("div")).setAttribute("class","noMic");
- }
-
- if(!devices.video)
- {
- this.container.appendChild(
- document.createElement("div")).setAttribute("class","noVideo");
- }
-
- if(!devices.audio && !devices.video)
- {
- $("#" + this.videoSpanId + " > .noMic").css("background-position", "75%");
- $("#" + this.videoSpanId + " > .noVideo").css("background-position", "25%");
- $("#" + this.videoSpanId + " > .noVideo").css("background-color", "transparent");
- }
-}
-
-/**
- * Shows the presence status message for the given video.
- */
-SmallVideo.prototype.setPresenceStatus = function (statusMsg) {
-
- if (!this.container) {
- // No container
- return;
- }
-
- var statusSpan = $('#' + this.videoSpanId + '>span.status');
- if (!statusSpan.length) {
- //Add status span
- statusSpan = document.createElement('span');
- statusSpan.className = 'status';
- statusSpan.id = this.videoSpanId + '_status';
- $('#' + this.videoSpanId)[0].appendChild(statusSpan);
-
- statusSpan = $('#' + this.videoSpanId + '>span.status');
- }
-
- // Display status
- if (statusMsg && statusMsg.length) {
- $('#' + this.videoSpanId + '_status').text(statusMsg);
- statusSpan.get(0).setAttribute("style", "display:inline-block;");
- }
- else {
- // Hide
- statusSpan.get(0).setAttribute("style", "display:none;");
- }
-};
-
-/**
- * Creates an audio or video stream element.
- */
-
-SmallVideo.createStreamElement = function (sid, stream) {
- var isVideo = stream.getVideoTracks().length > 0;
-
- var element = isVideo
- ? document.createElement('video')
- : document.createElement('audio');
- var id = (isVideo ? 'remoteVideo_' : 'remoteAudio_')
- + sid + '_' + APP.RTC.getStreamID(stream);
-
- element.id = id;
- element.autoplay = true;
- element.oncontextmenu = function () { return false; };
-
- return element;
-};
-
-/**
- * Updates the data for the indicator
- * @param id the id of the indicator
- * @param percent the percent for connection quality
- * @param object the data
- */
-SmallVideo.prototype.updateStatsIndicator = function (percent, object) {
- if(this.connectionIndicator)
- this.connectionIndicator.updateConnectionQuality(percent, object);
-}
-
-SmallVideo.prototype.hideIndicator = function () {
- if(this.connectionIndicator)
- this.connectionIndicator.hideIndicator();
-}
-
-
-/**
- * Shows audio muted indicator over small videos.
- * @param {string} isMuted
- */
-SmallVideo.prototype.showAudioIndicator = function(isMuted) {
- var audioMutedSpan = $('#' + this.videoSpanId + '>span.audioMuted');
-
- if (!isMuted) {
- if (audioMutedSpan.length > 0) {
- audioMutedSpan.popover('hide');
- audioMutedSpan.remove();
- }
- }
- else {
- if(audioMutedSpan.length == 0 ) {
- audioMutedSpan = document.createElement('span');
- audioMutedSpan.className = 'audioMuted';
- UIUtil.setTooltip(audioMutedSpan,
- "videothumbnail.mute",
- "top");
-
- this.container.appendChild(audioMutedSpan);
- APP.translation.translateElement($('#' + this.videoSpanId + " > span"));
- var mutedIndicator = document.createElement('i');
- mutedIndicator.className = 'icon-mic-disabled';
- audioMutedSpan.appendChild(mutedIndicator);
-
- }
- this.updateIconPositions();
- }
- this.isMuted = isMuted;
-};
-
-/**
- * Shows video muted indicator over small videos.
- */
-SmallVideo.prototype.showVideoIndicator = function(isMuted) {
- this.showAvatar(isMuted);
-
- var videoMutedSpan = $('#' + this.videoSpanId + '>span.videoMuted');
-
- if (isMuted === false) {
- if (videoMutedSpan.length > 0) {
- videoMutedSpan.remove();
- }
- }
- else {
- if(videoMutedSpan.length == 0) {
- videoMutedSpan = document.createElement('span');
- videoMutedSpan.className = 'videoMuted';
-
- this.container.appendChild(videoMutedSpan);
-
- var mutedIndicator = document.createElement('i');
- mutedIndicator.className = 'icon-camera-disabled';
- UIUtil.setTooltip(mutedIndicator,
- "videothumbnail.videomute",
- "top");
- videoMutedSpan.appendChild(mutedIndicator);
- //translate texts for muted indicator
- APP.translation.translateElement($('#' + this.videoSpanId + " > span > i"));
- }
-
- this.updateIconPositions();
-
- }
-};
-
-SmallVideo.prototype.enableDominantSpeaker = function (isEnable)
-{
- var displayName = this.resourceJid;
- var nameSpan = $('#' + this.videoSpanId + '>span.displayname');
- if (nameSpan.length > 0)
- displayName = nameSpan.html();
-
- console.log("UI enable dominant speaker",
- displayName,
- this.resourceJid,
- isEnable);
-
-
- if (!this.container) {
- return;
- }
-
- var video = $('#' + this.videoSpanId + '>video');
-
- if (video && video.length > 0) {
- if (isEnable) {
- this.showDisplayName(LargeVideo.isLargeVideoOnTop());
-
- if (!this.container.classList.contains("dominantspeaker"))
- this.container.classList.add("dominantspeaker");
- }
- else {
- this.showDisplayName(false);
-
- if (this.container.classList.contains("dominantspeaker"))
- this.container.classList.remove("dominantspeaker");
- }
- }
-
- this.showAvatar();
-};
-
-SmallVideo.prototype.updateIconPositions = function () {
- var audioMutedSpan = $('#' + this.videoSpanId + '>span.audioMuted');
- var connectionIndicator = $('#' + this.videoSpanId + '>div.connectionindicator');
- var videoMutedSpan = $('#' + this.videoSpanId + '>span.videoMuted');
- if(connectionIndicator.length > 0
- && connectionIndicator[0].style.display != "none") {
- audioMutedSpan.css({right: "23px"});
- videoMutedSpan.css({right: ((audioMutedSpan.length > 0? 23 : 0) + 30) + "px"});
- }
- else
- {
- audioMutedSpan.css({right: "0px"});
- videoMutedSpan.css({right: (audioMutedSpan.length > 0? 30 : 0) + "px"});
- }
-}
-
-
-/**
- * Creates the element indicating the moderator(owner) of the conference.
- *
- * @param parentElement the parent element where the owner indicator will
- * be added
- */
-SmallVideo.prototype.createModeratorIndicatorElement = function () {
- // Show moderator indicator
- var indicatorSpan = $('#' + this.videoSpanId + ' .focusindicator');
-
- if (!indicatorSpan || indicatorSpan.length === 0) {
- indicatorSpan = document.createElement('span');
- indicatorSpan.className = 'focusindicator';
-
- this.container.appendChild(indicatorSpan);
- indicatorSpan = $('#' + this.videoSpanId + ' .focusindicator');
- }
-
- if (indicatorSpan.children().length !== 0)
- return;
- var moderatorIndicator = document.createElement('i');
- moderatorIndicator.className = 'fa fa-star';
- indicatorSpan[0].appendChild(moderatorIndicator);
-
- UIUtil.setTooltip(indicatorSpan[0],
- "videothumbnail.moderator",
- "top");
-
- //translates text in focus indicators
- APP.translation.translateElement($('#' + this.videoSpanId + ' .focusindicator'));
-}
-
-
-SmallVideo.prototype.getSrc = function () {
- return APP.RTC.getVideoSrc($('#' + this.videoSpanId).find("video").get(0));
-}
-
-SmallVideo.prototype.focus = function(isFocused)
-{
- if(!isFocused) {
- this.container.classList.remove("videoContainerFocused");
- }
- else
- {
- this.container.classList.add("videoContainerFocused");
- }
-}
-
-SmallVideo.prototype.hasVideo = function () {
- return $("#" + this.videoSpanId).find("video").length !== 0;
-}
-
-/**
- * Hides or shows the user's avatar
- * @param show whether we should show the avatar or not
- * video because there is no dominant speaker and no focused speaker
- */
-SmallVideo.prototype.showAvatar = function (show) {
- if(!this.hasAvatar)
- return;
-
- var video = $('#' + this.videoSpanId).find("video");
- var avatar = $('#avatar_' + this.resourceJid);
-
- if (show === undefined || show === null) {
- if(!this.VideoLayout.isInLastN(this.resourceJid)) {
- show = true;
- }
- else
- {
- show = APP.RTC.isVideoMuted(this.peerJid);
- }
-
- }
-
- if (LargeVideo.showAvatar(this.resourceJid, show))
- {
- setVisibility(avatar, false);
- setVisibility(video, false);
- } else {
- if (video && video.length > 0) {
- setVisibility(video, !show);
- }
- setVisibility(avatar, show);
-
- }
-}
-
-SmallVideo.prototype.avatarChanged = function (thumbUrl) {
- var thumbnail = $('#' + this.videoSpanId);
- var avatar = $('#avatar_' + this.resourceJid);
- this.hasAvatar = true;
-
- // set the avatar in the thumbnail
- if (avatar && avatar.length > 0) {
- avatar[0].src = thumbUrl;
- } else {
- if (thumbnail && thumbnail.length > 0) {
- avatar = document.createElement('img');
- avatar.id = 'avatar_' + this.resourceJid;
- avatar.className = 'userAvatar';
- avatar.src = thumbUrl;
- thumbnail.append(avatar);
- }
- }
-}
-
+},{"../audio_levels/AudioLevels":12,"../avatar/Avatar":16,"./ConnectionIndicator":34,"./LargeVideo":35,"./SmallVideo":38}],38:[function(require,module,exports){
+var UIUtil = require("../util/UIUtil");
+var LargeVideo = require("./LargeVideo");
+
+function SmallVideo(){
+ this.isMuted = false;
+ this.hasAvatar = false;
+}
+
+function setVisibility(selector, show) {
+ if (selector && selector.length > 0) {
+ selector.css("visibility", show ? "visible" : "hidden");
+ }
+}
+
+SmallVideo.prototype.showDisplayName = function(isShow) {
+ var nameSpan = $('#' + this.videoSpanId + '>span.displayname').get(0);
+ if (isShow) {
+ if (nameSpan && nameSpan.innerHTML && nameSpan.innerHTML.length)
+ nameSpan.setAttribute("style", "display:inline-block;");
+ }
+ else {
+ if (nameSpan)
+ nameSpan.setAttribute("style", "display:none;");
+ }
+};
+
+SmallVideo.prototype.setDeviceAvailabilityIcons = function (devices) {
+ if(!this.container)
+ return;
+
+ $("#" + this.videoSpanId + " > .noMic").remove();
+ $("#" + this.videoSpanId + " > .noVideo").remove();
+ if(!devices.audio)
+ {
+ this.container.appendChild(
+ document.createElement("div")).setAttribute("class","noMic");
+ }
+
+ if(!devices.video)
+ {
+ this.container.appendChild(
+ document.createElement("div")).setAttribute("class","noVideo");
+ }
+
+ if(!devices.audio && !devices.video)
+ {
+ $("#" + this.videoSpanId + " > .noMic").css("background-position", "75%");
+ $("#" + this.videoSpanId + " > .noVideo").css("background-position", "25%");
+ $("#" + this.videoSpanId + " > .noVideo").css("background-color", "transparent");
+ }
+}
+
+/**
+ * Shows the presence status message for the given video.
+ */
+SmallVideo.prototype.setPresenceStatus = function (statusMsg) {
+
+ if (!this.container) {
+ // No container
+ return;
+ }
+
+ var statusSpan = $('#' + this.videoSpanId + '>span.status');
+ if (!statusSpan.length) {
+ //Add status span
+ statusSpan = document.createElement('span');
+ statusSpan.className = 'status';
+ statusSpan.id = this.videoSpanId + '_status';
+ $('#' + this.videoSpanId)[0].appendChild(statusSpan);
+
+ statusSpan = $('#' + this.videoSpanId + '>span.status');
+ }
+
+ // Display status
+ if (statusMsg && statusMsg.length) {
+ $('#' + this.videoSpanId + '_status').text(statusMsg);
+ statusSpan.get(0).setAttribute("style", "display:inline-block;");
+ }
+ else {
+ // Hide
+ statusSpan.get(0).setAttribute("style", "display:none;");
+ }
+};
+
+/**
+ * Creates an audio or video stream element.
+ */
+
+SmallVideo.createStreamElement = function (sid, stream) {
+ var isVideo = stream.getVideoTracks().length > 0;
+
+ var element = isVideo
+ ? document.createElement('video')
+ : document.createElement('audio');
+ var id = (isVideo ? 'remoteVideo_' : 'remoteAudio_')
+ + sid + '_' + APP.RTC.getStreamID(stream);
+
+ element.id = id;
+ element.autoplay = true;
+ element.oncontextmenu = function () { return false; };
+
+ return element;
+};
+
+/**
+ * Updates the data for the indicator
+ * @param id the id of the indicator
+ * @param percent the percent for connection quality
+ * @param object the data
+ */
+SmallVideo.prototype.updateStatsIndicator = function (percent, object) {
+ if(this.connectionIndicator)
+ this.connectionIndicator.updateConnectionQuality(percent, object);
+}
+
+SmallVideo.prototype.hideIndicator = function () {
+ if(this.connectionIndicator)
+ this.connectionIndicator.hideIndicator();
+}
+
+
+/**
+ * Shows audio muted indicator over small videos.
+ * @param {string} isMuted
+ */
+SmallVideo.prototype.showAudioIndicator = function(isMuted) {
+ var audioMutedSpan = $('#' + this.videoSpanId + '>span.audioMuted');
+
+ if (!isMuted) {
+ if (audioMutedSpan.length > 0) {
+ audioMutedSpan.popover('hide');
+ audioMutedSpan.remove();
+ }
+ }
+ else {
+ if(audioMutedSpan.length == 0 ) {
+ audioMutedSpan = document.createElement('span');
+ audioMutedSpan.className = 'audioMuted';
+ UIUtil.setTooltip(audioMutedSpan,
+ "videothumbnail.mute",
+ "top");
+
+ this.container.appendChild(audioMutedSpan);
+ APP.translation.translateElement($('#' + this.videoSpanId + " > span"));
+ var mutedIndicator = document.createElement('i');
+ mutedIndicator.className = 'icon-mic-disabled';
+ audioMutedSpan.appendChild(mutedIndicator);
+
+ }
+ this.updateIconPositions();
+ }
+ this.isMuted = isMuted;
+};
+
+/**
+ * Shows video muted indicator over small videos.
+ */
+SmallVideo.prototype.showVideoIndicator = function(isMuted) {
+ this.showAvatar(isMuted);
+
+ var videoMutedSpan = $('#' + this.videoSpanId + '>span.videoMuted');
+
+ if (isMuted === false) {
+ if (videoMutedSpan.length > 0) {
+ videoMutedSpan.remove();
+ }
+ }
+ else {
+ if(videoMutedSpan.length == 0) {
+ videoMutedSpan = document.createElement('span');
+ videoMutedSpan.className = 'videoMuted';
+
+ this.container.appendChild(videoMutedSpan);
+
+ var mutedIndicator = document.createElement('i');
+ mutedIndicator.className = 'icon-camera-disabled';
+ UIUtil.setTooltip(mutedIndicator,
+ "videothumbnail.videomute",
+ "top");
+ videoMutedSpan.appendChild(mutedIndicator);
+ //translate texts for muted indicator
+ APP.translation.translateElement($('#' + this.videoSpanId + " > span > i"));
+ }
+
+ this.updateIconPositions();
+
+ }
+};
+
+SmallVideo.prototype.enableDominantSpeaker = function (isEnable)
+{
+ var displayName = this.resourceJid;
+ var nameSpan = $('#' + this.videoSpanId + '>span.displayname');
+ if (nameSpan.length > 0)
+ displayName = nameSpan.html();
+
+ console.log("UI enable dominant speaker",
+ displayName,
+ this.resourceJid,
+ isEnable);
+
+
+ if (!this.container) {
+ return;
+ }
+
+ var video = $('#' + this.videoSpanId + '>video');
+
+ if (video && video.length > 0) {
+ if (isEnable) {
+ this.showDisplayName(LargeVideo.isLargeVideoOnTop());
+
+ if (!this.container.classList.contains("dominantspeaker"))
+ this.container.classList.add("dominantspeaker");
+ }
+ else {
+ this.showDisplayName(false);
+
+ if (this.container.classList.contains("dominantspeaker"))
+ this.container.classList.remove("dominantspeaker");
+ }
+ }
+
+ this.showAvatar();
+};
+
+SmallVideo.prototype.updateIconPositions = function () {
+ var audioMutedSpan = $('#' + this.videoSpanId + '>span.audioMuted');
+ var connectionIndicator = $('#' + this.videoSpanId + '>div.connectionindicator');
+ var videoMutedSpan = $('#' + this.videoSpanId + '>span.videoMuted');
+ if(connectionIndicator.length > 0
+ && connectionIndicator[0].style.display != "none") {
+ audioMutedSpan.css({right: "23px"});
+ videoMutedSpan.css({right: ((audioMutedSpan.length > 0? 23 : 0) + 30) + "px"});
+ }
+ else
+ {
+ audioMutedSpan.css({right: "0px"});
+ videoMutedSpan.css({right: (audioMutedSpan.length > 0? 30 : 0) + "px"});
+ }
+}
+
+
+/**
+ * Creates the element indicating the moderator(owner) of the conference.
+ *
+ * @param parentElement the parent element where the owner indicator will
+ * be added
+ */
+SmallVideo.prototype.createModeratorIndicatorElement = function () {
+ // Show moderator indicator
+ var indicatorSpan = $('#' + this.videoSpanId + ' .focusindicator');
+
+ if (!indicatorSpan || indicatorSpan.length === 0) {
+ indicatorSpan = document.createElement('span');
+ indicatorSpan.className = 'focusindicator';
+
+ this.container.appendChild(indicatorSpan);
+ indicatorSpan = $('#' + this.videoSpanId + ' .focusindicator');
+ }
+
+ if (indicatorSpan.children().length !== 0)
+ return;
+ var moderatorIndicator = document.createElement('i');
+ moderatorIndicator.className = 'fa fa-star';
+ indicatorSpan[0].appendChild(moderatorIndicator);
+
+ UIUtil.setTooltip(indicatorSpan[0],
+ "videothumbnail.moderator",
+ "top");
+
+ //translates text in focus indicators
+ APP.translation.translateElement($('#' + this.videoSpanId + ' .focusindicator'));
+}
+
+
+SmallVideo.prototype.getSrc = function () {
+ return APP.RTC.getVideoSrc($('#' + this.videoSpanId).find("video").get(0));
+}
+
+SmallVideo.prototype.focus = function(isFocused)
+{
+ if(!isFocused) {
+ this.container.classList.remove("videoContainerFocused");
+ }
+ else
+ {
+ this.container.classList.add("videoContainerFocused");
+ }
+}
+
+SmallVideo.prototype.hasVideo = function () {
+ return $("#" + this.videoSpanId).find("video").length !== 0;
+}
+
+/**
+ * Hides or shows the user's avatar
+ * @param show whether we should show the avatar or not
+ * video because there is no dominant speaker and no focused speaker
+ */
+SmallVideo.prototype.showAvatar = function (show) {
+ if(!this.hasAvatar)
+ return;
+
+ var video = $('#' + this.videoSpanId).find("video");
+ var avatar = $('#avatar_' + this.resourceJid);
+
+ if (show === undefined || show === null) {
+ if(!this.VideoLayout.isInLastN(this.resourceJid)) {
+ show = true;
+ }
+ else
+ {
+ show = APP.RTC.isVideoMuted(this.peerJid);
+ }
+
+ }
+
+ if (LargeVideo.showAvatar(this.resourceJid, show))
+ {
+ setVisibility(avatar, false);
+ setVisibility(video, false);
+ } else {
+ if (video && video.length > 0) {
+ setVisibility(video, !show);
+ }
+ setVisibility(avatar, show);
+
+ }
+}
+
+SmallVideo.prototype.avatarChanged = function (thumbUrl) {
+ var thumbnail = $('#' + this.videoSpanId);
+ var avatar = $('#avatar_' + this.resourceJid);
+ this.hasAvatar = true;
+
+ // set the avatar in the thumbnail
+ if (avatar && avatar.length > 0) {
+ avatar[0].src = thumbUrl;
+ } else {
+ if (thumbnail && thumbnail.length > 0) {
+ avatar = document.createElement('img');
+ avatar.id = 'avatar_' + this.resourceJid;
+ avatar.className = 'userAvatar';
+ avatar.src = thumbUrl;
+ thumbnail.append(avatar);
+ }
+ }
+}
+
module.exports = SmallVideo;
-},{"../util/UIUtil":31,"./LargeVideo":33}],37:[function(require,module,exports){
-var AudioLevels = require("../audio_levels/AudioLevels");
-var Avatar = require("../avatar/Avatar");
-var ContactList = require("../side_pannels/contactlist/ContactList");
-var MediaStreamType = require("../../../service/RTC/MediaStreamTypes");
-var UIEvents = require("../../../service/UI/UIEvents");
-var RemoteVideo = require("./RemoteVideo");
-var LargeVideo = require("./LargeVideo");
-var LocalVideo = require("./LocalVideo");
-
-var remoteVideos = {};
-var localVideoThumbnail = null;
-
-
-
-
-var currentDominantSpeaker = null;
-var lastNCount = config.channelLastN;
-var localLastNCount = config.channelLastN;
-var localLastNSet = [];
-var lastNEndpointsCache = [];
-var lastNPickupJid = null;
-
-var eventEmitter = null;
-
-
-/**
- * Currently focused video jid
- * @type {String}
- */
-var focusedVideoResourceJid = null;
-
-var VideoLayout = (function (my) {
- my.init = function (emitter) {
- eventEmitter = emitter;
- localVideoThumbnail = new LocalVideo(VideoLayout);
- VideoLayout.resizeLargeVideoContainer();
- LargeVideo.init(VideoLayout, emitter);
- };
-
- my.isInLastN = function(resource) {
- return lastNCount < 0 // lastN is disabled, return true
- || (lastNCount > 0 && lastNEndpointsCache.length == 0) // lastNEndpoints cache not built yet, return true
- || (lastNEndpointsCache && lastNEndpointsCache.indexOf(resource) !== -1);
- };
-
- my.changeLocalStream = function (stream, isMuted) {
- VideoLayout.changeLocalVideo(stream, isMuted);
- };
-
- my.changeLocalAudio = function(stream, isMuted) {
- if(isMuted)
- APP.UI.setAudioMuted(true, true);
- APP.RTC.attachMediaStream($('#localAudio'), stream.getOriginalStream());
- document.getElementById('localAudio').autoplay = true;
- document.getElementById('localAudio').volume = 0;
- };
-
- my.changeLocalVideo = function(stream, isMuted) {
- // Set default display name.
- localVideoThumbnail.setDisplayName();
- localVideoThumbnail.createConnectionIndicator();
-
- AudioLevels.updateAudioLevelCanvas(null, VideoLayout);
-
- localVideoThumbnail.changeVideo(stream, isMuted);
-
- LargeVideo.updateLargeVideo(APP.xmpp.myResource());
-
- };
-
- my.mucJoined = function () {
- var myResourceJid = APP.xmpp.myResource();
- localVideoThumbnail.joined(APP.xmpp.myJid());
-
- if (!LargeVideo.getResourceJid())
- LargeVideo.updateLargeVideo(myResourceJid, true);
- };
-
- /**
- * Adds or removes icons for not available camera and microphone.
- * @param resourceJid the jid of user
- * @param devices available devices
- */
- my.setDeviceAvailabilityIcons = function (resourceJid, devices) {
- if(!devices)
- return;
-
- if(!resourceJid)
- {
- localVideoThumbnail.setDeviceAvailabilityIcons(devices);
- }
- else
- {
- if(remoteVideos[resourceJid])
- remoteVideos[resourceJid].setDeviceAvailabilityIcons(devices);
- }
-
-
- }
-
- /**
- * Checks if removed video is currently displayed and tries to display
- * another one instead.
- * @param removedVideoSrc src stream identifier of the video.
- */
- my.updateRemovedVideo = function(resourceJid) {
- if (resourceJid === LargeVideo.getResourceJid()) {
- // this is currently displayed as large
- // pick the last visible video in the row
- // if nobody else is left, this picks the local video
- var pick
- = $('#remoteVideos>span[id!="mixedstream"]:visible:last>video')
- .get(0);
-
- if (!pick) {
- console.info("Last visible video no longer exists");
- pick = $('#remoteVideos>span[id!="mixedstream"]>video').get(0);
-
- if (!pick || !APP.RTC.getVideoSrc(pick)) {
- // Try local video
- console.info("Fallback to local video...");
- pick = $('#remoteVideos>span>span>video').get(0);
- }
- }
-
- // mute if localvideo
- if (pick) {
- var container = pick.parentNode;
- } else {
- console.warn("Failed to elect large video");
- container = $('#remoteVideos>span[id!="mixedstream"]:visible:last').get(0);
-
- }
-
- var jid = null;
- if(container)
- {
- if(container.id == "localVideoWrapper")
- {
- jid = APP.xmpp.myResource();
- }
- else
- {
- jid = VideoLayout.getPeerContainerResourceJid(container);
- }
- }
- else
- return;
-
- LargeVideo.updateLargeVideo(jid);
- }
- };
-
- my.onRemoteStreamAdded = function (stream) {
- if (stream.peerjid) {
- VideoLayout.ensurePeerContainerExists(stream.peerjid);
-
- var resourceJid = Strophe.getResourceFromJid(stream.peerjid);
- remoteVideos[resourceJid].addRemoteStreamElement(
- stream.sid,
- stream.getOriginalStream(),
- stream.ssrc);
- }
- };
-
- my.getLargeVideoJid = function () {
- return LargeVideo.getResourceJid();
- };
-
- my.handleVideoThumbClicked = function(noPinnedEndpointChangedEvent,
- resourceJid) {
- if(focusedVideoResourceJid) {
- var oldSmallVideo = VideoLayout.getSmallVideo(focusedVideoResourceJid);
- if(oldSmallVideo)
- oldSmallVideo.focus(false);
- }
-
- var smallVideo = VideoLayout.getSmallVideo(resourceJid);
- // Unlock current focused.
- if (focusedVideoResourceJid === resourceJid)
- {
- focusedVideoResourceJid = null;
- // Enable the currently set dominant speaker.
- if (currentDominantSpeaker) {
- if(smallVideo && smallVideo.hasVideo())
- {
- LargeVideo.updateLargeVideo(currentDominantSpeaker);
- }
- }
-
- if (!noPinnedEndpointChangedEvent) {
- eventEmitter.emit(UIEvents.PINNED_ENDPOINT);
- }
- return;
- }
-
-
- // Lock new video
- focusedVideoResourceJid = resourceJid;
-
- // Update focused/pinned interface.
- if (resourceJid)
- {
- if(smallVideo)
- smallVideo.focus(true);
-
- if (!noPinnedEndpointChangedEvent) {
- eventEmitter.emit(UIEvents.PINNED_ENDPOINT, resourceJid);
- }
- }
-
- if (LargeVideo.getResourceJid() === resourceJid &&
- LargeVideo.isLargeVideoOnTop()) {
- return;
- }
-
- // Triggers a "video.selected" event. The "false" parameter indicates
- // this isn't a prezi.
- $(document).trigger("video.selected", [false]);
-
- LargeVideo.updateLargeVideo(resourceJid);
-
- $('audio').each(function (idx, el) {
- el.volume = 0;
- el.volume = 1;
- });
- };
-
-
- /**
- * Checks if container for participant identified by given peerJid exists
- * in the document and creates it eventually.
- *
- * @param peerJid peer Jid to check.
- * @param userId user email or id for setting the avatar
- *
- * @return Returns true if the peer container exists,
- * false - otherwise
- */
- my.ensurePeerContainerExists = function(peerJid, userId) {
- ContactList.ensureAddContact(peerJid, userId);
-
- var resourceJid = Strophe.getResourceFromJid(peerJid);
-
- if(!remoteVideos[resourceJid])
- {
- remoteVideos[resourceJid] = new RemoteVideo(peerJid, VideoLayout);
- Avatar.setUserAvatar(peerJid, userId);
-
- // In case this is not currently in the last n we don't show it.
- if (localLastNCount
- && localLastNCount > 0
- && $('#remoteVideos>span').length >= localLastNCount + 2) {
- remoteVideos[resourceJid].showPeerContainer('hide');
- }
- else
- VideoLayout.resizeThumbnails();
- }
- };
-
-
- my.inputDisplayNameHandler = function (name) {
- localVideoThumbnail.inputDisplayNameHandler(name);
- };
-
- my.videoactive = function (videoelem, resourceJid) {
- videoelem.show();
- VideoLayout.resizeThumbnails();
-
- // Update the large video to the last added video only if there's no
- // current dominant, focused speaker or prezi playing or update it to
- // the current dominant speaker.
- if ((!focusedVideoResourceJid &&
- !currentDominantSpeaker &&
- !require("../prezi/Prezi").isPresentationVisible()) ||
- (resourceJid &&
- currentDominantSpeaker === resourceJid)) {
- LargeVideo.updateLargeVideo(resourceJid, true);
- }
- }
-
- /**
- * Shows the presence status message for the given video.
- */
- my.setPresenceStatus = function (resourceJid, statusMsg) {
- remoteVideos[resourceJid].setPresenceStatus(statusMsg);
- };
-
- /**
- * Shows a visual indicator for the moderator of the conference.
- */
- my.showModeratorIndicator = function () {
-
- var isModerator = APP.xmpp.isModerator();
- if (isModerator) {
- localVideoThumbnail.createModeratorIndicatorElement();
- }
-
- var members = APP.xmpp.getMembers();
-
- Object.keys(members).forEach(function (jid) {
-
- if (Strophe.getResourceFromJid(jid) === 'focus') {
- // Skip server side focus
- return;
- }
-
- var resourceJid = Strophe.getResourceFromJid(jid);
-
- var member = members[jid];
-
- if (member.role === 'moderator') {
-
- remoteVideos[resourceJid].removeRemoteVideoMenu();
-
- remoteVideos[resourceJid].createModeratorIndicatorElement();
- } else if (isModerator) {
- // We are moderator, but user is not - add menu
- if ($('#remote_popupmenu_' + resourceJid).length <= 0) {
- remoteVideos[resourceJid].addRemoteVideoMenu();
- }
- }
- });
- };
-
- /*
- * Shows or hides the audio muted indicator over the local thumbnail video.
- * @param {boolean} isMuted
- */
- my.showLocalAudioIndicator = function(isMuted) {
- localVideoThumbnail.showAudioIndicator(isMuted);
- };
-
- /**
- * Resizes the large video container.
- */
- my.resizeLargeVideoContainer = function () {
- LargeVideo.resize();
- VideoLayout.resizeThumbnails();
- LargeVideo.position();
- };
-
- /**
- * Resizes thumbnails.
- */
- my.resizeThumbnails = function(animate) {
- var videoSpaceWidth = $('#remoteVideos').width();
-
- var thumbnailSize = VideoLayout.calculateThumbnailSize(videoSpaceWidth);
- var width = thumbnailSize[0];
- var height = thumbnailSize[1];
-
- $('.userAvatar').css('left', (width - height) / 2);
-
- if(animate)
- {
- $('#remoteVideos').animate({
- height: height
- },
- {
- queue: false,
- duration: 500
- });
-
- $('#remoteVideos>span').animate({
- height: height,
- width: width
- },
- {
- queue: false,
- duration: 500,
- complete: function () {
- $(document).trigger(
- "remotevideo.resized",
- [width,
- height]);
- }
- });
-
- }
- else
- {
- // size videos so that while keeping AR and max height, we have a
- // nice fit
- $('#remoteVideos').height(height);
- $('#remoteVideos>span').width(width);
- $('#remoteVideos>span').height(height);
-
-
- $(document).trigger("remotevideo.resized", [width, height]);
- }
- };
-
- /**
- * Calculates the thumbnail size.
- *
- * @param videoSpaceWidth the width of the video space
- */
- my.calculateThumbnailSize = function (videoSpaceWidth) {
- // Calculate the available height, which is the inner window height minus
- // 39px for the header minus 2px for the delimiter lines on the top and
- // bottom of the large video, minus the 36px space inside the remoteVideos
- // container used for highlighting shadow.
- var availableHeight = 100;
-
- var numvids = $('#remoteVideos>span:visible').length;
- if (localLastNCount && localLastNCount > 0) {
- numvids = Math.min(localLastNCount + 1, numvids);
- }
-
- // Remove the 3px borders arround videos and border around the remote
- // videos area and the 4 pixels between the local video and the others
- //TODO: Find out where the 4 pixels come from and remove them
- var availableWinWidth = videoSpaceWidth - 2 * 3 * numvids - 70 - 4;
-
- var availableWidth = availableWinWidth / numvids;
- var aspectRatio = 16.0 / 9.0;
- var maxHeight = Math.min(160, availableHeight);
- availableHeight = Math.min(maxHeight, availableWidth / aspectRatio);
- if (availableHeight < availableWidth / aspectRatio) {
- availableWidth = Math.floor(availableHeight * aspectRatio);
- }
-
- return [availableWidth, availableHeight];
- };
-
- /**
- * Returns the corresponding resource jid to the given peer container
- * DOM element.
- *
- * @return the corresponding resource jid to the given peer container
- * DOM element
- */
- my.getPeerContainerResourceJid = function (containerElement) {
- var i = containerElement.id.indexOf('participant_');
-
- if (i >= 0)
- return containerElement.id.substring(i + 12);
- };
-
- /**
- * On contact list item clicked.
- */
- $(ContactList).bind('contactclicked', function(event, jid) {
- if (!jid) {
- return;
- }
-
- var resource = Strophe.getResourceFromJid(jid);
- var videoContainer = $("#participant_" + resource);
- if (videoContainer.length > 0) {
- var videoThumb = $('video', videoContainer).get(0);
- // It is not always the case that a videoThumb exists (if there is
- // no actual video).
- if (videoThumb) {
- if (videoThumb.src && videoThumb.src != '') {
-
- // We have a video src, great! Let's update the large video
- // now.
-
- VideoLayout.handleVideoThumbClicked(
- false,
- Strophe.getResourceFromJid(jid));
- } else {
-
- // If we don't have a video src for jid, there's absolutely
- // no point in calling handleVideoThumbClicked; Quite
- // simply, it won't work because it needs an src to attach
- // to the large video.
- //
- // Instead, we trigger the pinned endpoint changed event to
- // let the bridge adjust its lastN set for myjid and store
- // the pinned user in the lastNPickupJid variable to be
- // picked up later by the lastN changed event handler.
-
- lastNPickupJid = jid;
- eventEmitter.emit(UIEvents.PINNED_ENDPOINT,
- Strophe.getResourceFromJid(jid));
- }
- } else if (jid == APP.xmpp.myJid()) {
- $("#localVideoContainer").click();
- }
- }
- });
-
- /**
- * On audio muted event.
- */
- my.onAudioMute = function (jid, isMuted) {
- var resourceJid = Strophe.getResourceFromJid(jid);
- if (resourceJid === APP.xmpp.myResource()) {
- localVideoThumbnail.showAudioIndicator(isMuted);
- } else {
- VideoLayout.ensurePeerContainerExists(jid);
- remoteVideos[resourceJid].showAudioIndicator(isMuted);
- if (APP.xmpp.isModerator()) {
- remoteVideos[resourceJid].updateRemoteVideoMenu(isMuted);
- }
- }
-
-
- };
-
- /**
- * On video muted event.
- */
- my.onVideoMute = function (jid, value) {
- if(jid !== APP.xmpp.myJid() && !APP.RTC.muteRemoteVideoStream(jid, value))
- return;
-
- if (jid === APP.xmpp.myJid()) {
- localVideoThumbnail.showVideoIndicator(value);
- } else {
- VideoLayout.ensurePeerContainerExists(jid);
- remoteVideos[Strophe.getResourceFromJid(jid)].showVideoIndicator(value);
- }
- };
-
- /**
- * Display name changed.
- */
- my.onDisplayNameChanged =
- function (jid, displayName, status) {
- if (jid === 'localVideoContainer'
- || jid === APP.xmpp.myJid()) {
- localVideoThumbnail.setDisplayName(displayName);
- } else {
- VideoLayout.ensurePeerContainerExists(jid);
- remoteVideos[Strophe.getResourceFromJid(jid)].setDisplayName(
- displayName,
- status);
- }
-
- };
-
- /**
- * On dominant speaker changed event.
- */
- my.onDominantSpeakerChanged = function (resourceJid) {
- // We ignore local user events.
- if (resourceJid === APP.xmpp.myResource())
- return;
-
- var members = APP.xmpp.getMembers();
- // Update the current dominant speaker.
- if (resourceJid !== currentDominantSpeaker) {
- var currentJID = APP.xmpp.findJidFromResource(currentDominantSpeaker);
- var newJID = APP.xmpp.findJidFromResource(resourceJid);
- if(currentDominantSpeaker && (!members || !members[currentJID] ||
- !members[currentJID].displayName)) {
- remoteVideos[resourceJid].setDisplayName(null);
- }
- if(resourceJid && (!members || !members[newJID] ||
- !members[newJID].displayName)) {
- remoteVideos[resourceJid].setDisplayName(null,
- interfaceConfig.DEFAULT_DOMINANT_SPEAKER_DISPLAY_NAME);
- }
- currentDominantSpeaker = resourceJid;
- } else {
- return;
- }
-
- // Obtain container for new dominant speaker.
- var container = document.getElementById(
- 'participant_' + resourceJid);
-
- // Local video will not have container found, but that's ok
- // since we don't want to switch to local video.
- if (container && !focusedVideoResourceJid)
- {
- var video = container.getElementsByTagName("video");
-
- // Update the large video if the video source is already available,
- // otherwise wait for the "videoactive.jingle" event.
- if (video.length && video[0].currentTime > 0) {
- LargeVideo.updateLargeVideo(resourceJid);
- }
- }
- };
-
- /**
- * On last N change event.
- *
- * @param lastNEndpoints the list of last N endpoints
- * @param endpointsEnteringLastN the list currently entering last N
- * endpoints
- */
- my.onLastNEndpointsChanged = function ( lastNEndpoints,
- endpointsEnteringLastN,
- stream) {
- if (lastNCount !== lastNEndpoints.length)
- lastNCount = lastNEndpoints.length;
-
- lastNEndpointsCache = lastNEndpoints;
-
- // Say A, B, C, D, E, and F are in a conference and LastN = 3.
- //
- // If LastN drops to, say, 2, because of adaptivity, then E should see
- // thumbnails for A, B and C. A and B are in E's server side LastN set,
- // so E sees them. C is only in E's local LastN set.
- //
- // If F starts talking and LastN = 3, then E should see thumbnails for
- // F, A, B. B gets "ejected" from E's server side LastN set, but it
- // enters E's local LastN ejecting C.
-
- // Increase the local LastN set size, if necessary.
- if (lastNCount > localLastNCount) {
- localLastNCount = lastNCount;
- }
-
- // Update the local LastN set preserving the order in which the
- // endpoints appeared in the LastN/local LastN set.
-
- var nextLocalLastNSet = lastNEndpoints.slice(0);
- for (var i = 0; i < localLastNSet.length; i++) {
- if (nextLocalLastNSet.length >= localLastNCount) {
- break;
- }
-
- var resourceJid = localLastNSet[i];
- if (nextLocalLastNSet.indexOf(resourceJid) === -1) {
- nextLocalLastNSet.push(resourceJid);
- }
- }
-
- localLastNSet = nextLocalLastNSet;
-
- var updateLargeVideo = false;
-
- // Handle LastN/local LastN changes.
- $('#remoteVideos>span').each(function( index, element ) {
- var resourceJid = VideoLayout.getPeerContainerResourceJid(element);
-
- var isReceived = true;
- if (resourceJid
- && lastNEndpoints.indexOf(resourceJid) < 0
- && localLastNSet.indexOf(resourceJid) < 0) {
- console.log("Remove from last N", resourceJid);
- remoteVideos[resourceJid].showPeerContainer('hide');
- isReceived = false;
- } else if (resourceJid
- && $('#participant_' + resourceJid).is(':visible')
- && lastNEndpoints.indexOf(resourceJid) < 0
- && localLastNSet.indexOf(resourceJid) >= 0) {
- remoteVideos[resourceJid].showPeerContainer('avatar');
- isReceived = false;
- }
-
- if (!isReceived) {
- // resourceJid has dropped out of the server side lastN set, so
- // it is no longer being received. If resourceJid was being
- // displayed in the large video we have to switch to another
- // user.
- if (!updateLargeVideo && resourceJid === LargeVideo.getResourceJid()) {
- updateLargeVideo = true;
- }
- }
- });
-
- if (!endpointsEnteringLastN || endpointsEnteringLastN.length < 0)
- endpointsEnteringLastN = lastNEndpoints;
-
- if (endpointsEnteringLastN && endpointsEnteringLastN.length > 0) {
- endpointsEnteringLastN.forEach(function (resourceJid) {
-
- var isVisible = $('#participant_' + resourceJid).is(':visible');
- remoteVideos[resourceJid].showPeerContainer('show');
- if (!isVisible) {
- console.log("Add to last N", resourceJid);
-
- var jid = APP.xmpp.findJidFromResource(resourceJid);
- var mediaStream = APP.RTC.remoteStreams[jid][MediaStreamType.VIDEO_TYPE];
- var sel = $('#participant_' + resourceJid + '>video');
-
- APP.RTC.attachMediaStream(sel, mediaStream.stream);
- if (lastNPickupJid == mediaStream.peerjid) {
- // Clean up the lastN pickup jid.
- lastNPickupJid = null;
-
- // Don't fire the events again, they've already
- // been fired in the contact list click handler.
- VideoLayout.handleVideoThumbClicked(
- false,
- Strophe.getResourceFromJid(mediaStream.peerjid));
-
- updateLargeVideo = false;
- }
- remoteVideos[resourceJid].waitForRemoteVideo(sel, mediaStream.ssrc, mediaStream.stream);
- }
- });
- }
-
- // The endpoint that was being shown in the large video has dropped out
- // of the lastN set and there was no lastN pickup jid. We need to update
- // the large video now.
-
- if (updateLargeVideo) {
-
- var resource, container, src;
- var myResource
- = APP.xmpp.myResource();
-
- // Find out which endpoint to show in the large video.
- for (var i = 0; i < lastNEndpoints.length; i++) {
- resource = lastNEndpoints[i];
- if (!resource || resource === myResource)
- continue;
-
- // videoSrcToSsrc needs to be update for this call to succeed.
- LargeVideo.updateLargeVideo(resource);
- break;
-
- }
- }
- };
-
- /**
- * Updates local stats
- * @param percent
- * @param object
- */
- my.updateLocalConnectionStats = function (percent, object) {
- var resolution = null;
- if(object.resolution !== null)
- {
- resolution = object.resolution;
- object.resolution = resolution[APP.xmpp.myJid()];
- delete resolution[APP.xmpp.myJid()];
- }
- localVideoThumbnail.updateStatsIndicator(percent, object);
- for(var jid in resolution)
- {
- if(resolution[jid] === null)
- continue;
- var resourceJid = Strophe.getResourceFromJid(jid);
- if(remoteVideos[resourceJid] && remoteVideos[resourceJid].connectionIndicator)
- {
- remoteVideos[resourceJid].connectionIndicator.updateResolution(resolution[jid]);
- }
- }
-
- };
-
- /**
- * Updates remote stats.
- * @param jid the jid associated with the stats
- * @param percent the connection quality percent
- * @param object the stats data
- */
- my.updateConnectionStats = function (jid, percent, object) {
- var resourceJid = Strophe.getResourceFromJid(jid);
-
- if(remoteVideos[resourceJid])
- remoteVideos[resourceJid].updateStatsIndicator(percent, object);
- };
-
- /**
- * Removes the connection
- * @param jid
- */
- my.removeConnectionIndicator = function (jid) {
- remoteVideos[Strophe.getResourceFromJid(jid)].removeConnectionIndicator();
- };
-
- /**
- * Hides the connection indicator
- * @param jid
- */
- my.hideConnectionIndicator = function (jid) {
- remoteVideos[Strophe.getResourceFromJid(jid)].hideConnectionIndicator();
- };
-
- /**
- * Hides all the indicators
- */
- my.onStatsStop = function () {
- for(var video in remoteVideos)
- {
- remoteVideos[video].hideIndicator();
- }
- localVideoThumbnail.hideIndicator();
- };
-
- my.participantLeft = function (jid) {
- // Unlock large video
- var resourceJid = Strophe.getResourceFromJid(jid);
- if (focusedVideoResourceJid === resourceJid)
- {
- console.info("Focused video owner has left the conference");
- focusedVideoResourceJid = null;
- }
- };
-
- my.onVideoTypeChanged = function (jid) {
- LargeVideo.onVideoTypeChanged(jid);
- };
-
- my.showMore = function (jid) {
- if(APP.xmpp.myJid = jid)
- {
- localVideoThumbnail.connectionIndicator.showMore();
- }
- else
- {
- remoteVideos[Strophe.getResourceFromJid(jid)].connectionIndicator.showMore();
- }
-
- };
-
- my.addPreziContainer = function (id) {
- return RemoteVideo.createContainer(id);
- };
-
- my.setLargeVideoVisible = function (isVisible) {
- LargeVideo.setLargeVideoVisible(isVisible);
- if(!isVisible && focusedVideoResourceJid)
- {
- var smallVideo = VideoLayout.getSmallVideo(focusedVideoResourceJid);
- if(smallVideo)
- smallVideo.focus(false);
- smallVideo.showAvatar();
- focusedVideoResourceJid = null;
- }
- };
-
-
- /**
- * Resizes the video area
- * @param completeFunction a function to be called when the video space is resized
- */
- my.resizeVideoArea = function(isVisible, completeFunction) {
- LargeVideo.resizeVideoAreaAnimated(isVisible, completeFunction);
- VideoLayout.resizeThumbnails(true);
-
- };
-
- my.getSmallVideo = function (resourceJid) {
- if(resourceJid == APP.xmpp.myResource())
- {
- return localVideoThumbnail;
- }
- else
- {
- if(!remoteVideos[resourceJid])
- return null;
- return remoteVideos[resourceJid];
- }
- };
-
- my.userAvatarChanged = function(resourceJid, thumbUrl)
- {
- var smallVideo = VideoLayout.getSmallVideo(resourceJid);
- if(smallVideo)
- smallVideo.avatarChanged(thumbUrl);
- LargeVideo.updateAvatar(resourceJid, thumbUrl);
- };
-
- return my;
-}(VideoLayout || {}));
-
+},{"../util/UIUtil":33,"./LargeVideo":35}],39:[function(require,module,exports){
+var AudioLevels = require("../audio_levels/AudioLevels");
+var Avatar = require("../avatar/Avatar");
+var ContactList = require("../side_pannels/contactlist/ContactList");
+var MediaStreamType = require("../../../service/RTC/MediaStreamTypes");
+var UIEvents = require("../../../service/UI/UIEvents");
+var RemoteVideo = require("./RemoteVideo");
+var LargeVideo = require("./LargeVideo");
+var LocalVideo = require("./LocalVideo");
+
+var remoteVideos = {};
+var localVideoThumbnail = null;
+
+
+
+
+var currentDominantSpeaker = null;
+var lastNCount = config.channelLastN;
+var localLastNCount = config.channelLastN;
+var localLastNSet = [];
+var lastNEndpointsCache = [];
+var lastNPickupJid = null;
+
+var eventEmitter = null;
+
+
+/**
+ * Currently focused video jid
+ * @type {String}
+ */
+var focusedVideoResourceJid = null;
+
+var VideoLayout = (function (my) {
+ my.init = function (emitter) {
+ eventEmitter = emitter;
+ localVideoThumbnail = new LocalVideo(VideoLayout);
+ VideoLayout.resizeLargeVideoContainer();
+ LargeVideo.init(VideoLayout, emitter);
+ };
+
+ my.isInLastN = function(resource) {
+ return lastNCount < 0 // lastN is disabled, return true
+ || (lastNCount > 0 && lastNEndpointsCache.length == 0) // lastNEndpoints cache not built yet, return true
+ || (lastNEndpointsCache && lastNEndpointsCache.indexOf(resource) !== -1);
+ };
+
+ my.changeLocalStream = function (stream, isMuted) {
+ VideoLayout.changeLocalVideo(stream, isMuted);
+ };
+
+ my.changeLocalAudio = function(stream, isMuted) {
+ if(isMuted)
+ APP.UI.setAudioMuted(true, true);
+ APP.RTC.attachMediaStream($('#localAudio'), stream.getOriginalStream());
+ document.getElementById('localAudio').autoplay = true;
+ document.getElementById('localAudio').volume = 0;
+ };
+
+ my.changeLocalVideo = function(stream, isMuted) {
+ // Set default display name.
+ localVideoThumbnail.setDisplayName();
+ localVideoThumbnail.createConnectionIndicator();
+
+ AudioLevels.updateAudioLevelCanvas(null, VideoLayout);
+
+ localVideoThumbnail.changeVideo(stream, isMuted);
+
+ LargeVideo.updateLargeVideo(APP.xmpp.myResource());
+
+ };
+
+ my.mucJoined = function () {
+ var myResourceJid = APP.xmpp.myResource();
+ localVideoThumbnail.joined(APP.xmpp.myJid());
+
+ if (!LargeVideo.getResourceJid())
+ LargeVideo.updateLargeVideo(myResourceJid, true);
+ };
+
+ /**
+ * Adds or removes icons for not available camera and microphone.
+ * @param resourceJid the jid of user
+ * @param devices available devices
+ */
+ my.setDeviceAvailabilityIcons = function (resourceJid, devices) {
+ if(!devices)
+ return;
+
+ if(!resourceJid)
+ {
+ localVideoThumbnail.setDeviceAvailabilityIcons(devices);
+ }
+ else
+ {
+ if(remoteVideos[resourceJid])
+ remoteVideos[resourceJid].setDeviceAvailabilityIcons(devices);
+ }
+
+
+ }
+
+ /**
+ * Checks if removed video is currently displayed and tries to display
+ * another one instead.
+ * @param removedVideoSrc src stream identifier of the video.
+ */
+ my.updateRemovedVideo = function(resourceJid) {
+ if (resourceJid === LargeVideo.getResourceJid()) {
+ // this is currently displayed as large
+ // pick the last visible video in the row
+ // if nobody else is left, this picks the local video
+ var pick
+ = $('#remoteVideos>span[id!="mixedstream"]:visible:last>video')
+ .get(0);
+
+ if (!pick) {
+ console.info("Last visible video no longer exists");
+ pick = $('#remoteVideos>span[id!="mixedstream"]>video').get(0);
+
+ if (!pick || !APP.RTC.getVideoSrc(pick)) {
+ // Try local video
+ console.info("Fallback to local video...");
+ pick = $('#remoteVideos>span>span>video').get(0);
+ }
+ }
+
+ // mute if localvideo
+ if (pick) {
+ var container = pick.parentNode;
+ } else {
+ console.warn("Failed to elect large video");
+ container = $('#remoteVideos>span[id!="mixedstream"]:visible:last').get(0);
+
+ }
+
+ var jid = null;
+ if(container)
+ {
+ if(container.id == "localVideoWrapper")
+ {
+ jid = APP.xmpp.myResource();
+ }
+ else
+ {
+ jid = VideoLayout.getPeerContainerResourceJid(container);
+ }
+ }
+ else
+ return;
+
+ LargeVideo.updateLargeVideo(jid);
+ }
+ };
+
+ my.onRemoteStreamAdded = function (stream) {
+ if (stream.peerjid) {
+ VideoLayout.ensurePeerContainerExists(stream.peerjid);
+
+ var resourceJid = Strophe.getResourceFromJid(stream.peerjid);
+ remoteVideos[resourceJid].addRemoteStreamElement(
+ stream.sid,
+ stream.getOriginalStream(),
+ stream.ssrc);
+ }
+ };
+
+ my.getLargeVideoJid = function () {
+ return LargeVideo.getResourceJid();
+ };
+
+ my.handleVideoThumbClicked = function(noPinnedEndpointChangedEvent,
+ resourceJid) {
+ if(focusedVideoResourceJid) {
+ var oldSmallVideo = VideoLayout.getSmallVideo(focusedVideoResourceJid);
+ if(oldSmallVideo)
+ oldSmallVideo.focus(false);
+ }
+
+ var smallVideo = VideoLayout.getSmallVideo(resourceJid);
+ // Unlock current focused.
+ if (focusedVideoResourceJid === resourceJid)
+ {
+ focusedVideoResourceJid = null;
+ // Enable the currently set dominant speaker.
+ if (currentDominantSpeaker) {
+ if(smallVideo && smallVideo.hasVideo())
+ {
+ LargeVideo.updateLargeVideo(currentDominantSpeaker);
+ }
+ }
+
+ if (!noPinnedEndpointChangedEvent) {
+ eventEmitter.emit(UIEvents.PINNED_ENDPOINT);
+ }
+ return;
+ }
+
+
+ // Lock new video
+ focusedVideoResourceJid = resourceJid;
+
+ // Update focused/pinned interface.
+ if (resourceJid)
+ {
+ if(smallVideo)
+ smallVideo.focus(true);
+
+ if (!noPinnedEndpointChangedEvent) {
+ eventEmitter.emit(UIEvents.PINNED_ENDPOINT, resourceJid);
+ }
+ }
+
+ if (LargeVideo.getResourceJid() === resourceJid &&
+ LargeVideo.isLargeVideoOnTop()) {
+ return;
+ }
+
+ // Triggers a "video.selected" event. The "false" parameter indicates
+ // this isn't a prezi.
+ $(document).trigger("video.selected", [false]);
+
+ LargeVideo.updateLargeVideo(resourceJid);
+
+ $('audio').each(function (idx, el) {
+ el.volume = 0;
+ el.volume = 1;
+ });
+ };
+
+
+ /**
+ * Checks if container for participant identified by given peerJid exists
+ * in the document and creates it eventually.
+ *
+ * @param peerJid peer Jid to check.
+ * @param userId user email or id for setting the avatar
+ *
+ * @return Returns true if the peer container exists,
+ * false - otherwise
+ */
+ my.ensurePeerContainerExists = function(peerJid, userId) {
+ ContactList.ensureAddContact(peerJid, userId);
+
+ var resourceJid = Strophe.getResourceFromJid(peerJid);
+
+ if(!remoteVideos[resourceJid])
+ {
+ remoteVideos[resourceJid] = new RemoteVideo(peerJid, VideoLayout);
+ Avatar.setUserAvatar(peerJid, userId);
+
+ // In case this is not currently in the last n we don't show it.
+ if (localLastNCount
+ && localLastNCount > 0
+ && $('#remoteVideos>span').length >= localLastNCount + 2) {
+ remoteVideos[resourceJid].showPeerContainer('hide');
+ }
+ else
+ VideoLayout.resizeThumbnails();
+ }
+ };
+
+
+ my.inputDisplayNameHandler = function (name) {
+ localVideoThumbnail.inputDisplayNameHandler(name);
+ };
+
+ my.videoactive = function (videoelem, resourceJid) {
+ videoelem.show();
+ VideoLayout.resizeThumbnails();
+
+ // Update the large video to the last added video only if there's no
+ // current dominant, focused speaker or prezi playing or update it to
+ // the current dominant speaker.
+ if ((!focusedVideoResourceJid &&
+ !currentDominantSpeaker &&
+ !require("../prezi/Prezi").isPresentationVisible()) ||
+ (resourceJid &&
+ currentDominantSpeaker === resourceJid)) {
+ LargeVideo.updateLargeVideo(resourceJid, true);
+ }
+ }
+
+ /**
+ * Shows the presence status message for the given video.
+ */
+ my.setPresenceStatus = function (resourceJid, statusMsg) {
+ remoteVideos[resourceJid].setPresenceStatus(statusMsg);
+ };
+
+ /**
+ * Shows a visual indicator for the moderator of the conference.
+ */
+ my.showModeratorIndicator = function () {
+
+ var isModerator = APP.xmpp.isModerator();
+ if (isModerator) {
+ localVideoThumbnail.createModeratorIndicatorElement();
+ }
+
+ var members = APP.xmpp.getMembers();
+
+ Object.keys(members).forEach(function (jid) {
+
+ if (Strophe.getResourceFromJid(jid) === 'focus') {
+ // Skip server side focus
+ return;
+ }
+
+ var resourceJid = Strophe.getResourceFromJid(jid);
+
+ var member = members[jid];
+
+ if (member.role === 'moderator') {
+
+ remoteVideos[resourceJid].removeRemoteVideoMenu();
+
+ remoteVideos[resourceJid].createModeratorIndicatorElement();
+ } else if (isModerator) {
+ // We are moderator, but user is not - add menu
+ if ($('#remote_popupmenu_' + resourceJid).length <= 0) {
+ remoteVideos[resourceJid].addRemoteVideoMenu();
+ }
+ }
+ });
+ };
+
+ /*
+ * Shows or hides the audio muted indicator over the local thumbnail video.
+ * @param {boolean} isMuted
+ */
+ my.showLocalAudioIndicator = function(isMuted) {
+ localVideoThumbnail.showAudioIndicator(isMuted);
+ };
+
+ /**
+ * Resizes the large video container.
+ */
+ my.resizeLargeVideoContainer = function () {
+ LargeVideo.resize();
+ VideoLayout.resizeThumbnails();
+ LargeVideo.position();
+ };
+
+ /**
+ * Resizes thumbnails.
+ */
+ my.resizeThumbnails = function(animate) {
+ var videoSpaceWidth = $('#remoteVideos').width();
+
+ var thumbnailSize = VideoLayout.calculateThumbnailSize(videoSpaceWidth);
+ var width = thumbnailSize[0];
+ var height = thumbnailSize[1];
+
+ $('.userAvatar').css('left', (width - height) / 2);
+
+ if(animate)
+ {
+ $('#remoteVideos').animate({
+ height: height
+ },
+ {
+ queue: false,
+ duration: 500
+ });
+
+ $('#remoteVideos>span').animate({
+ height: height,
+ width: width
+ },
+ {
+ queue: false,
+ duration: 500,
+ complete: function () {
+ $(document).trigger(
+ "remotevideo.resized",
+ [width,
+ height]);
+ }
+ });
+
+ }
+ else
+ {
+ // size videos so that while keeping AR and max height, we have a
+ // nice fit
+ $('#remoteVideos').height(height);
+ $('#remoteVideos>span').width(width);
+ $('#remoteVideos>span').height(height);
+
+
+ $(document).trigger("remotevideo.resized", [width, height]);
+ }
+ };
+
+ /**
+ * Calculates the thumbnail size.
+ *
+ * @param videoSpaceWidth the width of the video space
+ */
+ my.calculateThumbnailSize = function (videoSpaceWidth) {
+ // Calculate the available height, which is the inner window height minus
+ // 39px for the header minus 2px for the delimiter lines on the top and
+ // bottom of the large video, minus the 36px space inside the remoteVideos
+ // container used for highlighting shadow.
+ var availableHeight = 100;
+
+ var numvids = $('#remoteVideos>span:visible').length;
+ if (localLastNCount && localLastNCount > 0) {
+ numvids = Math.min(localLastNCount + 1, numvids);
+ }
+
+ // Remove the 3px borders arround videos and border around the remote
+ // videos area and the 4 pixels between the local video and the others
+ //TODO: Find out where the 4 pixels come from and remove them
+ var availableWinWidth = videoSpaceWidth - 2 * 3 * numvids - 70 - 4;
+
+ var availableWidth = availableWinWidth / numvids;
+ var aspectRatio = 16.0 / 9.0;
+ var maxHeight = Math.min(160, availableHeight);
+ availableHeight = Math.min(maxHeight, availableWidth / aspectRatio);
+ if (availableHeight < availableWidth / aspectRatio) {
+ availableWidth = Math.floor(availableHeight * aspectRatio);
+ }
+
+ return [availableWidth, availableHeight];
+ };
+
+ /**
+ * Returns the corresponding resource jid to the given peer container
+ * DOM element.
+ *
+ * @return the corresponding resource jid to the given peer container
+ * DOM element
+ */
+ my.getPeerContainerResourceJid = function (containerElement) {
+ var i = containerElement.id.indexOf('participant_');
+
+ if (i >= 0)
+ return containerElement.id.substring(i + 12);
+ };
+
+ /**
+ * On contact list item clicked.
+ */
+ $(ContactList).bind('contactclicked', function(event, jid) {
+ if (!jid) {
+ return;
+ }
+
+ var resource = Strophe.getResourceFromJid(jid);
+ var videoContainer = $("#participant_" + resource);
+ if (videoContainer.length > 0) {
+ var videoThumb = $('video', videoContainer).get(0);
+ // It is not always the case that a videoThumb exists (if there is
+ // no actual video).
+ if (videoThumb) {
+ if (videoThumb.src && videoThumb.src != '') {
+
+ // We have a video src, great! Let's update the large video
+ // now.
+
+ VideoLayout.handleVideoThumbClicked(
+ false,
+ Strophe.getResourceFromJid(jid));
+ } else {
+
+ // If we don't have a video src for jid, there's absolutely
+ // no point in calling handleVideoThumbClicked; Quite
+ // simply, it won't work because it needs an src to attach
+ // to the large video.
+ //
+ // Instead, we trigger the pinned endpoint changed event to
+ // let the bridge adjust its lastN set for myjid and store
+ // the pinned user in the lastNPickupJid variable to be
+ // picked up later by the lastN changed event handler.
+
+ lastNPickupJid = jid;
+ eventEmitter.emit(UIEvents.PINNED_ENDPOINT,
+ Strophe.getResourceFromJid(jid));
+ }
+ } else if (jid == APP.xmpp.myJid()) {
+ $("#localVideoContainer").click();
+ }
+ }
+ });
+
+ /**
+ * On audio muted event.
+ */
+ my.onAudioMute = function (jid, isMuted) {
+ var resourceJid = Strophe.getResourceFromJid(jid);
+ if (resourceJid === APP.xmpp.myResource()) {
+ localVideoThumbnail.showAudioIndicator(isMuted);
+ } else {
+ VideoLayout.ensurePeerContainerExists(jid);
+ remoteVideos[resourceJid].showAudioIndicator(isMuted);
+ if (APP.xmpp.isModerator()) {
+ remoteVideos[resourceJid].updateRemoteVideoMenu(isMuted);
+ }
+ }
+
+
+ };
+
+ /**
+ * On video muted event.
+ */
+ my.onVideoMute = function (jid, value) {
+ if(jid !== APP.xmpp.myJid() && !APP.RTC.muteRemoteVideoStream(jid, value))
+ return;
+
+ if (jid === APP.xmpp.myJid()) {
+ localVideoThumbnail.showVideoIndicator(value);
+ } else {
+ VideoLayout.ensurePeerContainerExists(jid);
+ remoteVideos[Strophe.getResourceFromJid(jid)].showVideoIndicator(value);
+ }
+ };
+
+ /**
+ * Display name changed.
+ */
+ my.onDisplayNameChanged =
+ function (jid, displayName, status) {
+ if (jid === 'localVideoContainer'
+ || jid === APP.xmpp.myJid()) {
+ localVideoThumbnail.setDisplayName(displayName);
+ } else {
+ VideoLayout.ensurePeerContainerExists(jid);
+ remoteVideos[Strophe.getResourceFromJid(jid)].setDisplayName(
+ displayName,
+ status);
+ }
+
+ };
+
+ /**
+ * On dominant speaker changed event.
+ */
+ my.onDominantSpeakerChanged = function (resourceJid) {
+ // We ignore local user events.
+ if (resourceJid === APP.xmpp.myResource())
+ return;
+
+ var members = APP.xmpp.getMembers();
+ // Update the current dominant speaker.
+ if (resourceJid !== currentDominantSpeaker) {
+ var currentJID = APP.xmpp.findJidFromResource(currentDominantSpeaker);
+ var newJID = APP.xmpp.findJidFromResource(resourceJid);
+ if(currentDominantSpeaker && (!members || !members[currentJID] ||
+ !members[currentJID].displayName)) {
+ remoteVideos[resourceJid].setDisplayName(null);
+ }
+ if(resourceJid && (!members || !members[newJID] ||
+ !members[newJID].displayName)) {
+ remoteVideos[resourceJid].setDisplayName(null,
+ interfaceConfig.DEFAULT_DOMINANT_SPEAKER_DISPLAY_NAME);
+ }
+ currentDominantSpeaker = resourceJid;
+ } else {
+ return;
+ }
+
+ // Obtain container for new dominant speaker.
+ var container = document.getElementById(
+ 'participant_' + resourceJid);
+
+ // Local video will not have container found, but that's ok
+ // since we don't want to switch to local video.
+ if (container && !focusedVideoResourceJid)
+ {
+ var video = container.getElementsByTagName("video");
+
+ // Update the large video if the video source is already available,
+ // otherwise wait for the "videoactive.jingle" event.
+ if (video.length && video[0].currentTime > 0) {
+ LargeVideo.updateLargeVideo(resourceJid);
+ }
+ }
+ };
+
+ /**
+ * On last N change event.
+ *
+ * @param lastNEndpoints the list of last N endpoints
+ * @param endpointsEnteringLastN the list currently entering last N
+ * endpoints
+ */
+ my.onLastNEndpointsChanged = function ( lastNEndpoints,
+ endpointsEnteringLastN,
+ stream) {
+ if (lastNCount !== lastNEndpoints.length)
+ lastNCount = lastNEndpoints.length;
+
+ lastNEndpointsCache = lastNEndpoints;
+
+ // Say A, B, C, D, E, and F are in a conference and LastN = 3.
+ //
+ // If LastN drops to, say, 2, because of adaptivity, then E should see
+ // thumbnails for A, B and C. A and B are in E's server side LastN set,
+ // so E sees them. C is only in E's local LastN set.
+ //
+ // If F starts talking and LastN = 3, then E should see thumbnails for
+ // F, A, B. B gets "ejected" from E's server side LastN set, but it
+ // enters E's local LastN ejecting C.
+
+ // Increase the local LastN set size, if necessary.
+ if (lastNCount > localLastNCount) {
+ localLastNCount = lastNCount;
+ }
+
+ // Update the local LastN set preserving the order in which the
+ // endpoints appeared in the LastN/local LastN set.
+
+ var nextLocalLastNSet = lastNEndpoints.slice(0);
+ for (var i = 0; i < localLastNSet.length; i++) {
+ if (nextLocalLastNSet.length >= localLastNCount) {
+ break;
+ }
+
+ var resourceJid = localLastNSet[i];
+ if (nextLocalLastNSet.indexOf(resourceJid) === -1) {
+ nextLocalLastNSet.push(resourceJid);
+ }
+ }
+
+ localLastNSet = nextLocalLastNSet;
+
+ var updateLargeVideo = false;
+
+ // Handle LastN/local LastN changes.
+ $('#remoteVideos>span').each(function( index, element ) {
+ var resourceJid = VideoLayout.getPeerContainerResourceJid(element);
+
+ var isReceived = true;
+ if (resourceJid
+ && lastNEndpoints.indexOf(resourceJid) < 0
+ && localLastNSet.indexOf(resourceJid) < 0) {
+ console.log("Remove from last N", resourceJid);
+ remoteVideos[resourceJid].showPeerContainer('hide');
+ isReceived = false;
+ } else if (resourceJid
+ && $('#participant_' + resourceJid).is(':visible')
+ && lastNEndpoints.indexOf(resourceJid) < 0
+ && localLastNSet.indexOf(resourceJid) >= 0) {
+ remoteVideos[resourceJid].showPeerContainer('avatar');
+ isReceived = false;
+ }
+
+ if (!isReceived) {
+ // resourceJid has dropped out of the server side lastN set, so
+ // it is no longer being received. If resourceJid was being
+ // displayed in the large video we have to switch to another
+ // user.
+ if (!updateLargeVideo && resourceJid === LargeVideo.getResourceJid()) {
+ updateLargeVideo = true;
+ }
+ }
+ });
+
+ if (!endpointsEnteringLastN || endpointsEnteringLastN.length < 0)
+ endpointsEnteringLastN = lastNEndpoints;
+
+ if (endpointsEnteringLastN && endpointsEnteringLastN.length > 0) {
+ endpointsEnteringLastN.forEach(function (resourceJid) {
+
+ var isVisible = $('#participant_' + resourceJid).is(':visible');
+ remoteVideos[resourceJid].showPeerContainer('show');
+ if (!isVisible) {
+ console.log("Add to last N", resourceJid);
+
+ var jid = APP.xmpp.findJidFromResource(resourceJid);
+ var mediaStream = APP.RTC.remoteStreams[jid][MediaStreamType.VIDEO_TYPE];
+ var sel = $('#participant_' + resourceJid + '>video');
+
+ APP.RTC.attachMediaStream(sel, mediaStream.stream);
+ if (lastNPickupJid == mediaStream.peerjid) {
+ // Clean up the lastN pickup jid.
+ lastNPickupJid = null;
+
+ // Don't fire the events again, they've already
+ // been fired in the contact list click handler.
+ VideoLayout.handleVideoThumbClicked(
+ false,
+ Strophe.getResourceFromJid(mediaStream.peerjid));
+
+ updateLargeVideo = false;
+ }
+ remoteVideos[resourceJid].waitForRemoteVideo(sel, mediaStream.ssrc, mediaStream.stream);
+ }
+ });
+ }
+
+ // The endpoint that was being shown in the large video has dropped out
+ // of the lastN set and there was no lastN pickup jid. We need to update
+ // the large video now.
+
+ if (updateLargeVideo) {
+
+ var resource, container, src;
+ var myResource
+ = APP.xmpp.myResource();
+
+ // Find out which endpoint to show in the large video.
+ for (var i = 0; i < lastNEndpoints.length; i++) {
+ resource = lastNEndpoints[i];
+ if (!resource || resource === myResource)
+ continue;
+
+ // videoSrcToSsrc needs to be update for this call to succeed.
+ LargeVideo.updateLargeVideo(resource);
+ break;
+
+ }
+ }
+ };
+
+ /**
+ * Updates local stats
+ * @param percent
+ * @param object
+ */
+ my.updateLocalConnectionStats = function (percent, object) {
+ var resolution = null;
+ if(object.resolution !== null)
+ {
+ resolution = object.resolution;
+ object.resolution = resolution[APP.xmpp.myJid()];
+ delete resolution[APP.xmpp.myJid()];
+ }
+ localVideoThumbnail.updateStatsIndicator(percent, object);
+ for(var jid in resolution)
+ {
+ if(resolution[jid] === null)
+ continue;
+ var resourceJid = Strophe.getResourceFromJid(jid);
+ if(remoteVideos[resourceJid] && remoteVideos[resourceJid].connectionIndicator)
+ {
+ remoteVideos[resourceJid].connectionIndicator.updateResolution(resolution[jid]);
+ }
+ }
+
+ };
+
+ /**
+ * Updates remote stats.
+ * @param jid the jid associated with the stats
+ * @param percent the connection quality percent
+ * @param object the stats data
+ */
+ my.updateConnectionStats = function (jid, percent, object) {
+ var resourceJid = Strophe.getResourceFromJid(jid);
+
+ if(remoteVideos[resourceJid])
+ remoteVideos[resourceJid].updateStatsIndicator(percent, object);
+ };
+
+ /**
+ * Removes the connection
+ * @param jid
+ */
+ my.removeConnectionIndicator = function (jid) {
+ remoteVideos[Strophe.getResourceFromJid(jid)].removeConnectionIndicator();
+ };
+
+ /**
+ * Hides the connection indicator
+ * @param jid
+ */
+ my.hideConnectionIndicator = function (jid) {
+ remoteVideos[Strophe.getResourceFromJid(jid)].hideConnectionIndicator();
+ };
+
+ /**
+ * Hides all the indicators
+ */
+ my.onStatsStop = function () {
+ for(var video in remoteVideos)
+ {
+ remoteVideos[video].hideIndicator();
+ }
+ localVideoThumbnail.hideIndicator();
+ };
+
+ my.participantLeft = function (jid) {
+ // Unlock large video
+ var resourceJid = Strophe.getResourceFromJid(jid);
+ if (focusedVideoResourceJid === resourceJid)
+ {
+ console.info("Focused video owner has left the conference");
+ focusedVideoResourceJid = null;
+ }
+ };
+
+ my.onVideoTypeChanged = function (jid) {
+ LargeVideo.onVideoTypeChanged(jid);
+ };
+
+ my.showMore = function (jid) {
+ if(APP.xmpp.myJid = jid)
+ {
+ localVideoThumbnail.connectionIndicator.showMore();
+ }
+ else
+ {
+ remoteVideos[Strophe.getResourceFromJid(jid)].connectionIndicator.showMore();
+ }
+
+ };
+
+ my.addPreziContainer = function (id) {
+ return RemoteVideo.createContainer(id);
+ };
+
+ my.setLargeVideoVisible = function (isVisible) {
+ LargeVideo.setLargeVideoVisible(isVisible);
+ if(!isVisible && focusedVideoResourceJid)
+ {
+ var smallVideo = VideoLayout.getSmallVideo(focusedVideoResourceJid);
+ if(smallVideo)
+ smallVideo.focus(false);
+ smallVideo.showAvatar();
+ focusedVideoResourceJid = null;
+ }
+ };
+
+
+ /**
+ * Resizes the video area
+ * @param completeFunction a function to be called when the video space is resized
+ */
+ my.resizeVideoArea = function(isVisible, completeFunction) {
+ LargeVideo.resizeVideoAreaAnimated(isVisible, completeFunction);
+ VideoLayout.resizeThumbnails(true);
+
+ };
+
+ my.getSmallVideo = function (resourceJid) {
+ if(resourceJid == APP.xmpp.myResource())
+ {
+ return localVideoThumbnail;
+ }
+ else
+ {
+ if(!remoteVideos[resourceJid])
+ return null;
+ return remoteVideos[resourceJid];
+ }
+ };
+
+ my.userAvatarChanged = function(resourceJid, thumbUrl)
+ {
+ var smallVideo = VideoLayout.getSmallVideo(resourceJid);
+ if(smallVideo)
+ smallVideo.avatarChanged(thumbUrl);
+ LargeVideo.updateAvatar(resourceJid, thumbUrl);
+ };
+
+ return my;
+}(VideoLayout || {}));
+
module.exports = VideoLayout;
-},{"../../../service/RTC/MediaStreamTypes":100,"../../../service/UI/UIEvents":105,"../audio_levels/AudioLevels":10,"../avatar/Avatar":14,"../prezi/Prezi":16,"../side_pannels/contactlist/ContactList":23,"./LargeVideo":33,"./LocalVideo":34,"./RemoteVideo":35}],38:[function(require,module,exports){
-//var nouns = [
-//];
-var pluralNouns = [
- "Aliens", "Animals", "Antelopes", "Ants", "Apes", "Apples", "Baboons", "Bacteria", "Badgers", "Bananas", "Bats",
- "Bears", "Birds", "Bonobos", "Brides", "Bugs", "Bulls", "Butterflies", "Cheetahs",
- "Cherries", "Chicken", "Children", "Chimps", "Clowns", "Cows", "Creatures", "Dinosaurs", "Dogs", "Dolphins",
- "Donkeys", "Dragons", "Ducks", "Dwarfs", "Eagles", "Elephants", "Elves", "FAIL", "Fathers",
- "Fish", "Flowers", "Frogs", "Fruit", "Fungi", "Galaxies", "Geese", "Goats",
- "Gorillas", "Hedgehogs", "Hippos", "Horses", "Hunters", "Insects", "Kids", "Knights",
- "Lemons", "Lemurs", "Leopards", "LifeForms", "Lions", "Lizards", "Mice", "Monkeys", "Monsters",
- "Mushrooms", "Octopodes", "Oranges", "Orangutans", "Organisms", "Pants", "Parrots", "Penguins",
- "People", "Pigeons", "Pigs", "Pineapples", "Plants", "Potatoes", "Priests", "Rats", "Reptiles", "Reptilians",
- "Rhinos", "Seagulls", "Sheep", "Siblings", "Snakes", "Spaghetti", "Spiders", "Squid", "Squirrels",
- "Stars", "Students", "Teachers", "Tigers", "Tomatoes", "Trees", "Vampires", "Vegetables", "Viruses", "Vulcans",
- "Warewolves", "Weasels", "Whales", "Witches", "Wizards", "Wolves", "Workers", "Worms", "Zebras"
-];
-//var places = [
-//"Pub", "University", "Airport", "Library", "Mall", "Theater", "Stadium", "Office", "Show", "Gallows", "Beach",
-// "Cemetery", "Hospital", "Reception", "Restaurant", "Bar", "Church", "House", "School", "Square", "Village",
-// "Cinema", "Movies", "Party", "Restroom", "End", "Jail", "PostOffice", "Station", "Circus", "Gates", "Entrance",
-// "Bridge"
-//];
-var verbs = [
- "Abandon", "Adapt", "Advertise", "Answer", "Anticipate", "Appreciate",
- "Approach", "Argue", "Ask", "Bite", "Blossom", "Blush", "Breathe", "Breed", "Bribe", "Burn", "Calculate",
- "Clean", "Code", "Communicate", "Compute", "Confess", "Confiscate", "Conjugate", "Conjure", "Consume",
- "Contemplate", "Crawl", "Dance", "Delegate", "Devour", "Develop", "Differ", "Discuss",
- "Dissolve", "Drink", "Eat", "Elaborate", "Emancipate", "Estimate", "Expire", "Extinguish",
- "Extract", "FAIL", "Facilitate", "Fall", "Feed", "Finish", "Floss", "Fly", "Follow", "Fragment", "Freeze",
- "Gather", "Glow", "Grow", "Hex", "Hide", "Hug", "Hurry", "Improve", "Intersect", "Investigate", "Jinx",
- "Joke", "Jubilate", "Kiss", "Laugh", "Manage", "Meet", "Merge", "Move", "Object", "Observe", "Offer",
- "Paint", "Participate", "Party", "Perform", "Plan", "Pursue", "Pierce", "Play", "Postpone", "Pray", "Proclaim",
- "Question", "Read", "Reckon", "Rejoice", "Represent", "Resize", "Rhyme", "Scream", "Search", "Select", "Share", "Shoot",
- "Shout", "Signal", "Sing", "Skate", "Sleep", "Smile", "Smoke", "Solve", "Spell", "Steer", "Stink",
- "Substitute", "Swim", "Taste", "Teach", "Terminate", "Think", "Type", "Unite", "Vanish", "Worship"
-];
-var adverbs = [
- "Absently", "Accurately", "Accusingly", "Adorably", "AllTheTime", "Alone", "Always", "Amazingly", "Angrily",
- "Anxiously", "Anywhere", "Appallingly", "Apparently", "Articulately", "Astonishingly", "Badly", "Barely",
- "Beautifully", "Blindly", "Bravely", "Brightly", "Briskly", "Brutally", "Calmly", "Carefully", "Casually",
- "Cautiously", "Cleverly", "Constantly", "Correctly", "Crazily", "Curiously", "Cynically", "Daily",
- "Dangerously", "Deliberately", "Delicately", "Desperately", "Discreetly", "Eagerly", "Easily", "Euphoricly",
- "Evenly", "Everywhere", "Exactly", "Expectantly", "Extensively", "FAIL", "Ferociously", "Fiercely", "Finely",
- "Flatly", "Frequently", "Frighteningly", "Gently", "Gloriously", "Grimly", "Guiltily", "Happily",
- "Hard", "Hastily", "Heroically", "High", "Highly", "Hourly", "Humbly", "Hysterically", "Immensely",
- "Impartially", "Impolitely", "Indifferently", "Intensely", "Jealously", "Jovially", "Kindly", "Lazily",
- "Lightly", "Loudly", "Lovingly", "Loyally", "Magnificently", "Malevolently", "Merrily", "Mightily", "Miserably",
- "Mysteriously", "NOT", "Nervously", "Nicely", "Nowhere", "Objectively", "Obnoxiously", "Obsessively",
- "Obviously", "Often", "Painfully", "Patiently", "Playfully", "Politely", "Poorly", "Precisely", "Promptly",
- "Quickly", "Quietly", "Randomly", "Rapidly", "Rarely", "Recklessly", "Regularly", "Remorsefully", "Responsibly",
- "Rudely", "Ruthlessly", "Sadly", "Scornfully", "Seamlessly", "Seldom", "Selfishly", "Seriously", "Shakily",
- "Sharply", "Sideways", "Silently", "Sleepily", "Slightly", "Slowly", "Slyly", "Smoothly", "Softly", "Solemnly", "Steadily", "Sternly", "Strangely", "Strongly", "Stunningly", "Surely", "Tenderly", "Thoughtfully",
- "Tightly", "Uneasily", "Vanishingly", "Violently", "Warmly", "Weakly", "Wearily", "Weekly", "Weirdly", "Well",
- "Well", "Wickedly", "Wildly", "Wisely", "Wonderfully", "Yearly"
-];
-var adjectives = [
- "Abominable", "Accurate", "Adorable", "All", "Alleged", "Ancient", "Angry", "Angry", "Anxious", "Appalling",
- "Apparent", "Astonishing", "Attractive", "Awesome", "Baby", "Bad", "Beautiful", "Benign", "Big", "Bitter",
- "Blind", "Blue", "Bold", "Brave", "Bright", "Brisk", "Calm", "Camouflaged", "Casual", "Cautious",
- "Choppy", "Chosen", "Clever", "Cold", "Cool", "Crawly", "Crazy", "Creepy", "Cruel", "Curious", "Cynical",
- "Dangerous", "Dark", "Delicate", "Desperate", "Difficult", "Discreet", "Disguised", "Dizzy",
- "Dumb", "Eager", "Easy", "Edgy", "Electric", "Elegant", "Emancipated", "Enormous", "Euphoric", "Evil",
- "FAIL", "Fast", "Ferocious", "Fierce", "Fine", "Flawed", "Flying", "Foolish", "Foxy",
- "Freezing", "Funny", "Furious", "Gentle", "Glorious", "Golden", "Good", "Green", "Green", "Guilty",
- "Hairy", "Happy", "Hard", "Hasty", "Hazy", "Heroic", "Hostile", "Hot", "Humble", "Humongous",
- "Humorous", "Hysterical", "Idealistic", "Ignorant", "Immense", "Impartial", "Impolite", "Indifferent",
- "Infuriated", "Insightful", "Intense", "Interesting", "Intimidated", "Intriguing", "Jealous", "Jolly", "Jovial",
- "Jumpy", "Kind", "Laughing", "Lazy", "Liquid", "Lonely", "Longing", "Loud", "Loving", "Loyal", "Macabre", "Mad",
- "Magical", "Magnificent", "Malevolent", "Medieval", "Memorable", "Mere", "Merry", "Mighty",
- "Mischievous", "Miserable", "Modified", "Moody", "Most", "Mysterious", "Mystical", "Needy",
- "Nervous", "Nice", "Objective", "Obnoxious", "Obsessive", "Obvious", "Opinionated", "Orange",
- "Painful", "Passionate", "Perfect", "Pink", "Playful", "Poisonous", "Polite", "Poor", "Popular", "Powerful",
- "Precise", "Preserved", "Pretty", "Purple", "Quick", "Quiet", "Random", "Rapid", "Rare", "Real",
- "Reassuring", "Reckless", "Red", "Regular", "Remorseful", "Responsible", "Rich", "Rude", "Ruthless",
- "Sad", "Scared", "Scary", "Scornful", "Screaming", "Selfish", "Serious", "Shady", "Shaky", "Sharp",
- "Shiny", "Shy", "Simple", "Sleepy", "Slow", "Sly", "Small", "Smart", "Smelly", "Smiling", "Smooth",
- "Smug", "Sober", "Soft", "Solemn", "Square", "Square", "Steady", "Strange", "Strong",
- "Stunning", "Subjective", "Successful", "Surly", "Sweet", "Tactful", "Tense",
- "Thoughtful", "Tight", "Tiny", "Tolerant", "Uneasy", "Unique", "Unseen", "Warm", "Weak",
- "Weird", "WellCooked", "Wild", "Wise", "Witty", "Wonderful", "Worried", "Yellow", "Young",
- "Zealous"
- ];
-//var pronouns = [
-//];
-//var conjunctions = [
-//"And", "Or", "For", "Above", "Before", "Against", "Between"
-//];
+},{"../../../service/RTC/MediaStreamTypes":98,"../../../service/UI/UIEvents":103,"../audio_levels/AudioLevels":12,"../avatar/Avatar":16,"../prezi/Prezi":18,"../side_pannels/contactlist/ContactList":25,"./LargeVideo":35,"./LocalVideo":36,"./RemoteVideo":37}],40:[function(require,module,exports){
+//var nouns = [
+//];
+var pluralNouns = [
+ "Aliens", "Animals", "Antelopes", "Ants", "Apes", "Apples", "Baboons", "Bacteria", "Badgers", "Bananas", "Bats",
+ "Bears", "Birds", "Bonobos", "Brides", "Bugs", "Bulls", "Butterflies", "Cheetahs",
+ "Cherries", "Chicken", "Children", "Chimps", "Clowns", "Cows", "Creatures", "Dinosaurs", "Dogs", "Dolphins",
+ "Donkeys", "Dragons", "Ducks", "Dwarfs", "Eagles", "Elephants", "Elves", "FAIL", "Fathers",
+ "Fish", "Flowers", "Frogs", "Fruit", "Fungi", "Galaxies", "Geese", "Goats",
+ "Gorillas", "Hedgehogs", "Hippos", "Horses", "Hunters", "Insects", "Kids", "Knights",
+ "Lemons", "Lemurs", "Leopards", "LifeForms", "Lions", "Lizards", "Mice", "Monkeys", "Monsters",
+ "Mushrooms", "Octopodes", "Oranges", "Orangutans", "Organisms", "Pants", "Parrots", "Penguins",
+ "People", "Pigeons", "Pigs", "Pineapples", "Plants", "Potatoes", "Priests", "Rats", "Reptiles", "Reptilians",
+ "Rhinos", "Seagulls", "Sheep", "Siblings", "Snakes", "Spaghetti", "Spiders", "Squid", "Squirrels",
+ "Stars", "Students", "Teachers", "Tigers", "Tomatoes", "Trees", "Vampires", "Vegetables", "Viruses", "Vulcans",
+ "Warewolves", "Weasels", "Whales", "Witches", "Wizards", "Wolves", "Workers", "Worms", "Zebras"
+];
+//var places = [
+//"Pub", "University", "Airport", "Library", "Mall", "Theater", "Stadium", "Office", "Show", "Gallows", "Beach",
+// "Cemetery", "Hospital", "Reception", "Restaurant", "Bar", "Church", "House", "School", "Square", "Village",
+// "Cinema", "Movies", "Party", "Restroom", "End", "Jail", "PostOffice", "Station", "Circus", "Gates", "Entrance",
+// "Bridge"
+//];
+var verbs = [
+ "Abandon", "Adapt", "Advertise", "Answer", "Anticipate", "Appreciate",
+ "Approach", "Argue", "Ask", "Bite", "Blossom", "Blush", "Breathe", "Breed", "Bribe", "Burn", "Calculate",
+ "Clean", "Code", "Communicate", "Compute", "Confess", "Confiscate", "Conjugate", "Conjure", "Consume",
+ "Contemplate", "Crawl", "Dance", "Delegate", "Devour", "Develop", "Differ", "Discuss",
+ "Dissolve", "Drink", "Eat", "Elaborate", "Emancipate", "Estimate", "Expire", "Extinguish",
+ "Extract", "FAIL", "Facilitate", "Fall", "Feed", "Finish", "Floss", "Fly", "Follow", "Fragment", "Freeze",
+ "Gather", "Glow", "Grow", "Hex", "Hide", "Hug", "Hurry", "Improve", "Intersect", "Investigate", "Jinx",
+ "Joke", "Jubilate", "Kiss", "Laugh", "Manage", "Meet", "Merge", "Move", "Object", "Observe", "Offer",
+ "Paint", "Participate", "Party", "Perform", "Plan", "Pursue", "Pierce", "Play", "Postpone", "Pray", "Proclaim",
+ "Question", "Read", "Reckon", "Rejoice", "Represent", "Resize", "Rhyme", "Scream", "Search", "Select", "Share", "Shoot",
+ "Shout", "Signal", "Sing", "Skate", "Sleep", "Smile", "Smoke", "Solve", "Spell", "Steer", "Stink",
+ "Substitute", "Swim", "Taste", "Teach", "Terminate", "Think", "Type", "Unite", "Vanish", "Worship"
+];
+var adverbs = [
+ "Absently", "Accurately", "Accusingly", "Adorably", "AllTheTime", "Alone", "Always", "Amazingly", "Angrily",
+ "Anxiously", "Anywhere", "Appallingly", "Apparently", "Articulately", "Astonishingly", "Badly", "Barely",
+ "Beautifully", "Blindly", "Bravely", "Brightly", "Briskly", "Brutally", "Calmly", "Carefully", "Casually",
+ "Cautiously", "Cleverly", "Constantly", "Correctly", "Crazily", "Curiously", "Cynically", "Daily",
+ "Dangerously", "Deliberately", "Delicately", "Desperately", "Discreetly", "Eagerly", "Easily", "Euphoricly",
+ "Evenly", "Everywhere", "Exactly", "Expectantly", "Extensively", "FAIL", "Ferociously", "Fiercely", "Finely",
+ "Flatly", "Frequently", "Frighteningly", "Gently", "Gloriously", "Grimly", "Guiltily", "Happily",
+ "Hard", "Hastily", "Heroically", "High", "Highly", "Hourly", "Humbly", "Hysterically", "Immensely",
+ "Impartially", "Impolitely", "Indifferently", "Intensely", "Jealously", "Jovially", "Kindly", "Lazily",
+ "Lightly", "Loudly", "Lovingly", "Loyally", "Magnificently", "Malevolently", "Merrily", "Mightily", "Miserably",
+ "Mysteriously", "NOT", "Nervously", "Nicely", "Nowhere", "Objectively", "Obnoxiously", "Obsessively",
+ "Obviously", "Often", "Painfully", "Patiently", "Playfully", "Politely", "Poorly", "Precisely", "Promptly",
+ "Quickly", "Quietly", "Randomly", "Rapidly", "Rarely", "Recklessly", "Regularly", "Remorsefully", "Responsibly",
+ "Rudely", "Ruthlessly", "Sadly", "Scornfully", "Seamlessly", "Seldom", "Selfishly", "Seriously", "Shakily",
+ "Sharply", "Sideways", "Silently", "Sleepily", "Slightly", "Slowly", "Slyly", "Smoothly", "Softly", "Solemnly", "Steadily", "Sternly", "Strangely", "Strongly", "Stunningly", "Surely", "Tenderly", "Thoughtfully",
+ "Tightly", "Uneasily", "Vanishingly", "Violently", "Warmly", "Weakly", "Wearily", "Weekly", "Weirdly", "Well",
+ "Well", "Wickedly", "Wildly", "Wisely", "Wonderfully", "Yearly"
+];
+var adjectives = [
+ "Abominable", "Accurate", "Adorable", "All", "Alleged", "Ancient", "Angry", "Angry", "Anxious", "Appalling",
+ "Apparent", "Astonishing", "Attractive", "Awesome", "Baby", "Bad", "Beautiful", "Benign", "Big", "Bitter",
+ "Blind", "Blue", "Bold", "Brave", "Bright", "Brisk", "Calm", "Camouflaged", "Casual", "Cautious",
+ "Choppy", "Chosen", "Clever", "Cold", "Cool", "Crawly", "Crazy", "Creepy", "Cruel", "Curious", "Cynical",
+ "Dangerous", "Dark", "Delicate", "Desperate", "Difficult", "Discreet", "Disguised", "Dizzy",
+ "Dumb", "Eager", "Easy", "Edgy", "Electric", "Elegant", "Emancipated", "Enormous", "Euphoric", "Evil",
+ "FAIL", "Fast", "Ferocious", "Fierce", "Fine", "Flawed", "Flying", "Foolish", "Foxy",
+ "Freezing", "Funny", "Furious", "Gentle", "Glorious", "Golden", "Good", "Green", "Green", "Guilty",
+ "Hairy", "Happy", "Hard", "Hasty", "Hazy", "Heroic", "Hostile", "Hot", "Humble", "Humongous",
+ "Humorous", "Hysterical", "Idealistic", "Ignorant", "Immense", "Impartial", "Impolite", "Indifferent",
+ "Infuriated", "Insightful", "Intense", "Interesting", "Intimidated", "Intriguing", "Jealous", "Jolly", "Jovial",
+ "Jumpy", "Kind", "Laughing", "Lazy", "Liquid", "Lonely", "Longing", "Loud", "Loving", "Loyal", "Macabre", "Mad",
+ "Magical", "Magnificent", "Malevolent", "Medieval", "Memorable", "Mere", "Merry", "Mighty",
+ "Mischievous", "Miserable", "Modified", "Moody", "Most", "Mysterious", "Mystical", "Needy",
+ "Nervous", "Nice", "Objective", "Obnoxious", "Obsessive", "Obvious", "Opinionated", "Orange",
+ "Painful", "Passionate", "Perfect", "Pink", "Playful", "Poisonous", "Polite", "Poor", "Popular", "Powerful",
+ "Precise", "Preserved", "Pretty", "Purple", "Quick", "Quiet", "Random", "Rapid", "Rare", "Real",
+ "Reassuring", "Reckless", "Red", "Regular", "Remorseful", "Responsible", "Rich", "Rude", "Ruthless",
+ "Sad", "Scared", "Scary", "Scornful", "Screaming", "Selfish", "Serious", "Shady", "Shaky", "Sharp",
+ "Shiny", "Shy", "Simple", "Sleepy", "Slow", "Sly", "Small", "Smart", "Smelly", "Smiling", "Smooth",
+ "Smug", "Sober", "Soft", "Solemn", "Square", "Square", "Steady", "Strange", "Strong",
+ "Stunning", "Subjective", "Successful", "Surly", "Sweet", "Tactful", "Tense",
+ "Thoughtful", "Tight", "Tiny", "Tolerant", "Uneasy", "Unique", "Unseen", "Warm", "Weak",
+ "Weird", "WellCooked", "Wild", "Wise", "Witty", "Wonderful", "Worried", "Yellow", "Young",
+ "Zealous"
+ ];
+//var pronouns = [
+//];
+//var conjunctions = [
+//"And", "Or", "For", "Above", "Before", "Against", "Between"
+//];
+
+/*
+ * Maps a string (category name) to the array of words from that category.
+ */
+var CATEGORIES =
+{
+ //"_NOUN_": nouns,
+ "_PLURALNOUN_": pluralNouns,
+ //"_PLACE_": places,
+ "_VERB_": verbs,
+ "_ADVERB_": adverbs,
+ "_ADJECTIVE_": adjectives
+ //"_PRONOUN_": pronouns,
+ //"_CONJUNCTION_": conjunctions,
+};
+
+var PATTERNS = [
+ "_ADJECTIVE__PLURALNOUN__VERB__ADVERB_"
+
+ // BeautifulFungiOrSpaghetti
+ //"_ADJECTIVE__PLURALNOUN__CONJUNCTION__PLURALNOUN_",
+
+ // AmazinglyScaryToy
+ //"_ADVERB__ADJECTIVE__NOUN_",
+
+ // NeitherTrashNorRifle
+ //"Neither_NOUN_Nor_NOUN_",
+ //"Either_NOUN_Or_NOUN_",
+
+ // EitherCopulateOrInvestigate
+ //"Either_VERB_Or_VERB_",
+ //"Neither_VERB_Nor_VERB_",
+
+ //"The_ADJECTIVE__ADJECTIVE__NOUN_",
+ //"The_ADVERB__ADJECTIVE__NOUN_",
+ //"The_ADVERB__ADJECTIVE__NOUN_s",
+ //"The_ADVERB__ADJECTIVE__PLURALNOUN__VERB_",
+
+ // WolvesComputeBadly
+ //"_PLURALNOUN__VERB__ADVERB_",
+
+ // UniteFacilitateAndMerge
+ //"_VERB__VERB_And_VERB_",
+
+ //NastyWitchesAtThePub
+ //"_ADJECTIVE__PLURALNOUN_AtThe_PLACE_",
+];
+
+
+/*
+ * Returns a random element from the array 'arr'
+ */
+function randomElement(arr)
+{
+ return arr[Math.floor(Math.random() * arr.length)];
+}
+
+/*
+ * Returns true if the string 's' contains one of the
+ * template strings.
+ */
+function hasTemplate(s)
+{
+ for (var template in CATEGORIES){
+ if (s.indexOf(template) >= 0){
+ return true;
+ }
+ }
+}
+
+/**
+ * Generates new room name.
+ */
+var RoomNameGenerator = {
+ generateRoomWithoutSeparator: function()
+ {
+ // Note that if more than one pattern is available, the choice of 'name' won't be random (names from patterns
+ // with fewer options will have higher probability of being chosen that names from patterns with more options).
+ var name = randomElement(PATTERNS);
+ var word;
+ while (hasTemplate(name)){
+ for (var template in CATEGORIES){
+ word = randomElement(CATEGORIES[template]);
+ name = name.replace(template, word);
+ }
+ }
+
+ return name;
+ }
+}
+
+module.exports = RoomNameGenerator;
-/*
- * Maps a string (category name) to the array of words from that category.
- */
-var CATEGORIES =
-{
- //"_NOUN_": nouns,
- "_PLURALNOUN_": pluralNouns,
- //"_PLACE_": places,
- "_VERB_": verbs,
- "_ADVERB_": adverbs,
- "_ADJECTIVE_": adjectives
- //"_PRONOUN_": pronouns,
- //"_CONJUNCTION_": conjunctions,
-};
-
-var PATTERNS = [
- "_ADJECTIVE__PLURALNOUN__VERB__ADVERB_"
-
- // BeautifulFungiOrSpaghetti
- //"_ADJECTIVE__PLURALNOUN__CONJUNCTION__PLURALNOUN_",
-
- // AmazinglyScaryToy
- //"_ADVERB__ADJECTIVE__NOUN_",
-
- // NeitherTrashNorRifle
- //"Neither_NOUN_Nor_NOUN_",
- //"Either_NOUN_Or_NOUN_",
-
- // EitherCopulateOrInvestigate
- //"Either_VERB_Or_VERB_",
- //"Neither_VERB_Nor_VERB_",
-
- //"The_ADJECTIVE__ADJECTIVE__NOUN_",
- //"The_ADVERB__ADJECTIVE__NOUN_",
- //"The_ADVERB__ADJECTIVE__NOUN_s",
- //"The_ADVERB__ADJECTIVE__PLURALNOUN__VERB_",
-
- // WolvesComputeBadly
- //"_PLURALNOUN__VERB__ADVERB_",
-
- // UniteFacilitateAndMerge
- //"_VERB__VERB_And_VERB_",
-
- //NastyWitchesAtThePub
- //"_ADJECTIVE__PLURALNOUN_AtThe_PLACE_",
-];
-
-
-/*
- * Returns a random element from the array 'arr'
- */
-function randomElement(arr)
-{
- return arr[Math.floor(Math.random() * arr.length)];
-}
-
-/*
- * Returns true if the string 's' contains one of the
- * template strings.
- */
-function hasTemplate(s)
-{
- for (var template in CATEGORIES){
- if (s.indexOf(template) >= 0){
- return true;
- }
- }
-}
-
-/**
- * Generates new room name.
- */
-var RoomNameGenerator = {
- generateRoomWithoutSeparator: function()
- {
- // Note that if more than one pattern is available, the choice of 'name' won't be random (names from patterns
- // with fewer options will have higher probability of being chosen that names from patterns with more options).
- var name = randomElement(PATTERNS);
- var word;
- while (hasTemplate(name)){
- for (var template in CATEGORIES){
- word = randomElement(CATEGORIES[template]);
- name = name.replace(template, word);
- }
- }
-
- return name;
- }
-}
-
-module.exports = RoomNameGenerator;
-
-},{}],39:[function(require,module,exports){
-var animateTimeout, updateTimeout;
-
-var RoomNameGenerator = require("./RoomnameGenerator");
-
-function enter_room()
-{
- var val = $("#enter_room_field").val();
- if(!val) {
- val = $("#enter_room_field").attr("room_name");
- }
- if (val) {
- window.location.pathname = "/" + val;
- }
-}
-
-function animate(word) {
- var currentVal = $("#enter_room_field").attr("placeholder");
- $("#enter_room_field").attr("placeholder", currentVal + word.substr(0, 1));
- animateTimeout = setTimeout(function() {
- animate(word.substring(1, word.length))
- }, 70);
-}
-
-function update_roomname()
-{
- var word = RoomNameGenerator.generateRoomWithoutSeparator();
- $("#enter_room_field").attr("room_name", word);
- $("#enter_room_field").attr("placeholder", "");
- clearTimeout(animateTimeout);
- animate(word);
- updateTimeout = setTimeout(update_roomname, 10000);
-}
-
-
-function setupWelcomePage()
-{
- $("#videoconference_page").hide();
- $("#domain_name").text(
- window.location.protocol + "//" + window.location.host + "/");
- if (interfaceConfig.SHOW_JITSI_WATERMARK) {
- var leftWatermarkDiv
- = $("#welcome_page_header div[class='watermark leftwatermark']");
- if(leftWatermarkDiv && leftWatermarkDiv.length > 0)
- {
- leftWatermarkDiv.css({display: 'block'});
- leftWatermarkDiv.parent().get(0).href
- = interfaceConfig.JITSI_WATERMARK_LINK;
- }
-
- }
-
- if (interfaceConfig.SHOW_BRAND_WATERMARK) {
- var rightWatermarkDiv
- = $("#welcome_page_header div[class='watermark rightwatermark']");
- if(rightWatermarkDiv && rightWatermarkDiv.length > 0) {
- rightWatermarkDiv.css({display: 'block'});
- rightWatermarkDiv.parent().get(0).href
- = interfaceConfig.BRAND_WATERMARK_LINK;
- rightWatermarkDiv.get(0).style.backgroundImage
- = "url(images/rightwatermark.png)";
- }
- }
-
- if (interfaceConfig.SHOW_POWERED_BY) {
- $("#welcome_page_header>a[class='poweredby']")
- .css({display: 'block'});
- }
-
- $("#enter_room_button").click(function()
- {
- enter_room();
- });
-
- $("#enter_room_field").keydown(function (event) {
- if (event.keyCode === 13 /* enter */) {
- enter_room();
- }
- });
-
- if (!(interfaceConfig.GENERATE_ROOMNAMES_ON_WELCOME_PAGE === false)){
- var updateTimeout;
- var animateTimeout;
- $("#reload_roomname").click(function () {
- clearTimeout(updateTimeout);
- clearTimeout(animateTimeout);
- update_roomname();
- });
- $("#reload_roomname").show();
-
-
- update_roomname();
- }
-
- $("#disable_welcome").click(function () {
- window.localStorage.welcomePageDisabled
- = $("#disable_welcome").is(":checked");
- });
-
-}
-
-module.exports = setupWelcomePage;
-},{"./RoomnameGenerator":38}],40:[function(require,module,exports){
-var params = {};
-function getConfigParamsFromUrl() {
- if(!location.hash)
- return {};
- var hash = location.hash.substr(1);
- var result = {};
- hash.split("&").forEach(function(part) {
- var item = part.split("=");
- result[item[0]] = JSON.parse(
- decodeURIComponent(item[1]).replace(/\\&/, "&"));
- });
- return result;
-}
-
-params = getConfigParamsFromUrl();
-
-var URLProcessor = {
- setConfigParametersFromUrl: function () {
- for(var k in params)
- {
- if(typeof k !== "string" || k.indexOf("config.") === -1)
- continue;
-
- var v = params[k];
- var confKey = k.substr(7);
- if(config[confKey] && typeof config[confKey] !== typeof v)
- {
- console.warn("The type of " + k +
- " is wrong. That parameter won't be updated in config.js.");
- continue;
- }
-
- config[confKey] = v;
-
- }
-
- }
-};
-
-module.exports = URLProcessor;
},{}],41:[function(require,module,exports){
-var EventEmitter = require("events");
-var eventEmitter = new EventEmitter();
-var CQEvents = require("../../service/connectionquality/CQEvents");
-var XMPPEvents = require("../../service/xmpp/XMPPEvents");
-
-/**
- * local stats
- * @type {{}}
- */
-var stats = {};
-
-/**
- * remote stats
- * @type {{}}
- */
-var remoteStats = {};
-
-/**
- * Interval for sending statistics to other participants
- * @type {null}
- */
-var sendIntervalId = null;
-
-
-/**
- * Start statistics sending.
- */
-function startSendingStats() {
- sendStats();
- sendIntervalId = setInterval(sendStats, 10000);
-}
-
-/**
- * Sends statistics to other participants
- */
-function sendStats() {
- APP.xmpp.addToPresence("connectionQuality", convertToMUCStats(stats));
-}
-
-/**
- * Converts statistics to format for sending through XMPP
- * @param stats the statistics
- * @returns {{bitrate_donwload: *, bitrate_uplpoad: *, packetLoss_total: *, packetLoss_download: *, packetLoss_upload: *}}
- */
-function convertToMUCStats(stats) {
- return {
- "bitrate_download": stats.bitrate.download,
- "bitrate_upload": stats.bitrate.upload,
- "packetLoss_total": stats.packetLoss.total,
- "packetLoss_download": stats.packetLoss.download,
- "packetLoss_upload": stats.packetLoss.upload
- };
-}
-
-/**
- * Converts statitistics to format used by VideoLayout
- * @param stats
- * @returns {{bitrate: {download: *, upload: *}, packetLoss: {total: *, download: *, upload: *}}}
- */
-function parseMUCStats(stats) {
- return {
- bitrate: {
- download: stats.bitrate_download,
- upload: stats.bitrate_upload
- },
- packetLoss: {
- total: stats.packetLoss_total,
- download: stats.packetLoss_download,
- upload: stats.packetLoss_upload
- }
- };
-}
-
-
-var ConnectionQuality = {
- init: function () {
- APP.xmpp.addListener(XMPPEvents.REMOTE_STATS, this.updateRemoteStats);
- APP.statistics.addConnectionStatsListener(this.updateLocalStats);
- APP.statistics.addRemoteStatsStopListener(this.stopSendingStats);
-
- },
-
- /**
- * Updates the local statistics
- * @param data new statistics
- */
- updateLocalStats: function (data) {
- stats = data;
- eventEmitter.emit(CQEvents.LOCALSTATS_UPDATED, 100 - stats.packetLoss.total, stats);
- if (sendIntervalId == null) {
- startSendingStats();
- }
- },
-
- /**
- * Updates remote statistics
- * @param jid the jid associated with the statistics
- * @param data the statistics
- */
- updateRemoteStats: function (jid, data) {
- if (data == null || data.packetLoss_total == null) {
- eventEmitter.emit(CQEvents.REMOTESTATS_UPDATED, jid, null, null);
- return;
- }
- remoteStats[jid] = parseMUCStats(data);
-
- eventEmitter.emit(CQEvents.REMOTESTATS_UPDATED,
- jid, 100 - data.packetLoss_total, remoteStats[jid]);
- },
-
- /**
- * Stops statistics sending.
- */
- stopSendingStats: function () {
- clearInterval(sendIntervalId);
- sendIntervalId = null;
- //notify UI about stopping statistics gathering
- eventEmitter.emit(CQEvents.STOP);
- },
-
- /**
- * Returns the local statistics.
- */
- getStats: function () {
- return stats;
- },
-
- addListener: function (type, listener) {
- eventEmitter.on(type, listener);
- }
-
-};
-
+var animateTimeout, updateTimeout;
+
+var RoomNameGenerator = require("./RoomnameGenerator");
+
+function enter_room()
+{
+ var val = $("#enter_room_field").val();
+ if(!val) {
+ val = $("#enter_room_field").attr("room_name");
+ }
+ if (val) {
+ window.location.pathname = "/" + val;
+ }
+}
+
+function animate(word) {
+ var currentVal = $("#enter_room_field").attr("placeholder");
+ $("#enter_room_field").attr("placeholder", currentVal + word.substr(0, 1));
+ animateTimeout = setTimeout(function() {
+ animate(word.substring(1, word.length))
+ }, 70);
+}
+
+function update_roomname()
+{
+ var word = RoomNameGenerator.generateRoomWithoutSeparator();
+ $("#enter_room_field").attr("room_name", word);
+ $("#enter_room_field").attr("placeholder", "");
+ clearTimeout(animateTimeout);
+ animate(word);
+ updateTimeout = setTimeout(update_roomname, 10000);
+}
+
+
+function setupWelcomePage()
+{
+ $("#videoconference_page").hide();
+ $("#domain_name").text(
+ window.location.protocol + "//" + window.location.host + "/");
+ if (interfaceConfig.SHOW_JITSI_WATERMARK) {
+ var leftWatermarkDiv
+ = $("#welcome_page_header div[class='watermark leftwatermark']");
+ if(leftWatermarkDiv && leftWatermarkDiv.length > 0)
+ {
+ leftWatermarkDiv.css({display: 'block'});
+ leftWatermarkDiv.parent().get(0).href
+ = interfaceConfig.JITSI_WATERMARK_LINK;
+ }
+
+ }
+
+ if (interfaceConfig.SHOW_BRAND_WATERMARK) {
+ var rightWatermarkDiv
+ = $("#welcome_page_header div[class='watermark rightwatermark']");
+ if(rightWatermarkDiv && rightWatermarkDiv.length > 0) {
+ rightWatermarkDiv.css({display: 'block'});
+ rightWatermarkDiv.parent().get(0).href
+ = interfaceConfig.BRAND_WATERMARK_LINK;
+ rightWatermarkDiv.get(0).style.backgroundImage
+ = "url(images/rightwatermark.png)";
+ }
+ }
+
+ if (interfaceConfig.SHOW_POWERED_BY) {
+ $("#welcome_page_header>a[class='poweredby']")
+ .css({display: 'block'});
+ }
+
+ $("#enter_room_button").click(function()
+ {
+ enter_room();
+ });
+
+ $("#enter_room_field").keydown(function (event) {
+ if (event.keyCode === 13 /* enter */) {
+ enter_room();
+ }
+ });
+
+ if (!(interfaceConfig.GENERATE_ROOMNAMES_ON_WELCOME_PAGE === false)){
+ var updateTimeout;
+ var animateTimeout;
+ $("#reload_roomname").click(function () {
+ clearTimeout(updateTimeout);
+ clearTimeout(animateTimeout);
+ update_roomname();
+ });
+ $("#reload_roomname").show();
+
+
+ update_roomname();
+ }
+
+ $("#disable_welcome").click(function () {
+ window.localStorage.welcomePageDisabled
+ = $("#disable_welcome").is(":checked");
+ });
+
+}
+
+module.exports = setupWelcomePage;
+},{"./RoomnameGenerator":40}],42:[function(require,module,exports){
+var params = {};
+function getConfigParamsFromUrl() {
+ if(!location.hash)
+ return {};
+ var hash = location.hash.substr(1);
+ var result = {};
+ hash.split("&").forEach(function(part) {
+ var item = part.split("=");
+ result[item[0]] = JSON.parse(
+ decodeURIComponent(item[1]).replace(/\\&/, "&"));
+ });
+ return result;
+}
+
+params = getConfigParamsFromUrl();
+
+var URLProcessor = {
+ setConfigParametersFromUrl: function () {
+ for(var k in params)
+ {
+ if(typeof k !== "string" || k.indexOf("config.") === -1)
+ continue;
+
+ var v = params[k];
+ var confKey = k.substr(7);
+ if(config[confKey] && typeof config[confKey] !== typeof v)
+ {
+ console.warn("The type of " + k +
+ " is wrong. That parameter won't be updated in config.js.");
+ continue;
+ }
+
+ config[confKey] = v;
+
+ }
+
+ }
+};
+
+module.exports = URLProcessor;
+},{}],43:[function(require,module,exports){
+var EventEmitter = require("events");
+var eventEmitter = new EventEmitter();
+var CQEvents = require("../../service/connectionquality/CQEvents");
+var XMPPEvents = require("../../service/xmpp/XMPPEvents");
+
+/**
+ * local stats
+ * @type {{}}
+ */
+var stats = {};
+
+/**
+ * remote stats
+ * @type {{}}
+ */
+var remoteStats = {};
+
+/**
+ * Interval for sending statistics to other participants
+ * @type {null}
+ */
+var sendIntervalId = null;
+
+
+/**
+ * Start statistics sending.
+ */
+function startSendingStats() {
+ sendStats();
+ sendIntervalId = setInterval(sendStats, 10000);
+}
+
+/**
+ * Sends statistics to other participants
+ */
+function sendStats() {
+ APP.xmpp.addToPresence("connectionQuality", convertToMUCStats(stats));
+}
+
+/**
+ * Converts statistics to format for sending through XMPP
+ * @param stats the statistics
+ * @returns {{bitrate_donwload: *, bitrate_uplpoad: *, packetLoss_total: *, packetLoss_download: *, packetLoss_upload: *}}
+ */
+function convertToMUCStats(stats) {
+ return {
+ "bitrate_download": stats.bitrate.download,
+ "bitrate_upload": stats.bitrate.upload,
+ "packetLoss_total": stats.packetLoss.total,
+ "packetLoss_download": stats.packetLoss.download,
+ "packetLoss_upload": stats.packetLoss.upload
+ };
+}
+
+/**
+ * Converts statitistics to format used by VideoLayout
+ * @param stats
+ * @returns {{bitrate: {download: *, upload: *}, packetLoss: {total: *, download: *, upload: *}}}
+ */
+function parseMUCStats(stats) {
+ return {
+ bitrate: {
+ download: stats.bitrate_download,
+ upload: stats.bitrate_upload
+ },
+ packetLoss: {
+ total: stats.packetLoss_total,
+ download: stats.packetLoss_download,
+ upload: stats.packetLoss_upload
+ }
+ };
+}
+
+
+var ConnectionQuality = {
+ init: function () {
+ APP.xmpp.addListener(XMPPEvents.REMOTE_STATS, this.updateRemoteStats);
+ APP.statistics.addConnectionStatsListener(this.updateLocalStats);
+ APP.statistics.addRemoteStatsStopListener(this.stopSendingStats);
+
+ },
+
+ /**
+ * Updates the local statistics
+ * @param data new statistics
+ */
+ updateLocalStats: function (data) {
+ stats = data;
+ eventEmitter.emit(CQEvents.LOCALSTATS_UPDATED, 100 - stats.packetLoss.total, stats);
+ if (sendIntervalId == null) {
+ startSendingStats();
+ }
+ },
+
+ /**
+ * Updates remote statistics
+ * @param jid the jid associated with the statistics
+ * @param data the statistics
+ */
+ updateRemoteStats: function (jid, data) {
+ if (data == null || data.packetLoss_total == null) {
+ eventEmitter.emit(CQEvents.REMOTESTATS_UPDATED, jid, null, null);
+ return;
+ }
+ remoteStats[jid] = parseMUCStats(data);
+
+ eventEmitter.emit(CQEvents.REMOTESTATS_UPDATED,
+ jid, 100 - data.packetLoss_total, remoteStats[jid]);
+ },
+
+ /**
+ * Stops statistics sending.
+ */
+ stopSendingStats: function () {
+ clearInterval(sendIntervalId);
+ sendIntervalId = null;
+ //notify UI about stopping statistics gathering
+ eventEmitter.emit(CQEvents.STOP);
+ },
+
+ /**
+ * Returns the local statistics.
+ */
+ getStats: function () {
+ return stats;
+ },
+
+ addListener: function (type, listener) {
+ eventEmitter.on(type, listener);
+ }
+
+};
+
module.exports = ConnectionQuality;
-},{"../../service/connectionquality/CQEvents":107,"../../service/xmpp/XMPPEvents":111,"events":112}],42:[function(require,module,exports){
-/* global $, alert, APP, changeLocalVideo, chrome, config, getConferenceHandler,
- getUserMediaWithConstraints */
-/**
- * Indicates that desktop stream is currently in use(for toggle purpose).
- * @type {boolean}
- */
-var isUsingScreenStream = false;
-/**
- * Indicates that switch stream operation is in progress and prevent from
- * triggering new events.
- * @type {boolean}
- */
-var switchInProgress = false;
-
-/**
- * Method used to get screen sharing stream.
- *
- * @type {function (stream_callback, failure_callback}
- */
-var obtainDesktopStream = null;
-
-/**
- * Indicates whether desktop sharing extension is installed.
- * @type {boolean}
- */
-var extInstalled = false;
-
-/**
- * Indicates whether update of desktop sharing extension is required.
- * @type {boolean}
- */
-var extUpdateRequired = false;
-
-/**
- * Flag used to cache desktop sharing enabled state. Do not use directly as
- * it can be null .
- *
- * @type {null|boolean}
- */
-var _desktopSharingEnabled = null;
-
-var EventEmitter = require("events");
-
-var eventEmitter = new EventEmitter();
-
-var DesktopSharingEventTypes
- = require("../../service/desktopsharing/DesktopSharingEventTypes");
-
-/**
- * Method obtains desktop stream from WebRTC 'screen' source.
- * Flag 'chrome://flags/#enable-usermedia-screen-capture' must be enabled.
- */
-function obtainWebRTCScreen(streamCallback, failCallback) {
- APP.RTC.getUserMediaWithConstraints(
- ['screen'],
- streamCallback,
- failCallback
- );
-}
-
-/**
- * Constructs inline install URL for Chrome desktop streaming extension.
- * The 'chromeExtensionId' must be defined in config.js.
- * @returns {string}
- */
-function getWebStoreInstallUrl()
-{
- return "https://chrome.google.com/webstore/detail/" +
- config.chromeExtensionId;
-}
-
-/**
- * Checks whether extension update is required.
- * @param minVersion minimal required version
- * @param extVersion current extension version
- * @returns {boolean}
- */
-function isUpdateRequired(minVersion, extVersion)
-{
- try
- {
- var s1 = minVersion.split('.');
- var s2 = extVersion.split('.');
-
- var len = Math.max(s1.length, s2.length);
- for (var i = 0; i < len; i++)
- {
- var n1 = 0,
- n2 = 0;
-
- if (i < s1.length)
- n1 = parseInt(s1[i]);
- if (i < s2.length)
- n2 = parseInt(s2[i]);
-
- if (isNaN(n1) || isNaN(n2))
- {
- return true;
- }
- else if (n1 !== n2)
- {
- return n1 > n2;
- }
- }
-
- // will happen if boths version has identical numbers in
- // their components (even if one of them is longer, has more components)
- return false;
- }
- catch (e)
- {
- console.error("Failed to parse extension version", e);
- APP.UI.messageHandler.showError("dialog.error",
- "dialog.detectext");
- return true;
- }
-}
-
-function checkExtInstalled(callback) {
- if (!chrome.runtime) {
- // No API, so no extension for sure
- callback(false, false);
- return;
- }
- chrome.runtime.sendMessage(
- config.chromeExtensionId,
- { getVersion: true },
- function (response) {
- if (!response || !response.version) {
- // Communication failure - assume that no endpoint exists
- console.warn(
- "Extension not installed?: ", chrome.runtime.lastError);
- callback(false, false);
- return;
- }
- // Check installed extension version
- var extVersion = response.version;
- console.log('Extension version is: ' + extVersion);
- var updateRequired
- = isUpdateRequired(config.minChromeExtVersion, extVersion);
- callback(!updateRequired, updateRequired);
- }
- );
-}
-
-function doGetStreamFromExtension(streamCallback, failCallback) {
- // Sends 'getStream' msg to the extension.
- // Extension id must be defined in the config.
- chrome.runtime.sendMessage(
- config.chromeExtensionId,
- { getStream: true, sources: config.desktopSharingSources },
- function (response) {
- if (!response) {
- failCallback(chrome.runtime.lastError);
- return;
- }
- console.log("Response from extension: " + response);
- if (response.streamId) {
- APP.RTC.getUserMediaWithConstraints(
- ['desktop'],
- function (stream) {
- streamCallback(stream);
- },
- failCallback,
- null, null, null,
- response.streamId);
- } else {
- failCallback("Extension failed to get the stream");
- }
- }
- );
-}
-/**
- * Asks Chrome extension to call chooseDesktopMedia and gets chrome 'desktop'
- * stream for returned stream token.
- */
-function obtainScreenFromExtension(streamCallback, failCallback) {
- if (extInstalled) {
- doGetStreamFromExtension(streamCallback, failCallback);
- } else {
- if (extUpdateRequired) {
- alert(
- 'Jitsi Desktop Streamer requires update. ' +
- 'Changes will take effect after next Chrome restart.');
- }
-
- chrome.webstore.install(
- getWebStoreInstallUrl(),
- function (arg) {
- console.log("Extension installed successfully", arg);
- extInstalled = true;
- // We need to give a moment for the endpoint to become available
- window.setTimeout(function () {
- doGetStreamFromExtension(streamCallback, failCallback);
- }, 500);
- },
- function (arg) {
- console.log("Failed to install the extension", arg);
- failCallback(arg);
- APP.UI.messageHandler.showError("dialog.error",
- "dialog.failtoinstall");
- }
- );
- }
-}
-
-/**
- * Call this method to toggle desktop sharing feature.
- * @param method pass "ext" to use chrome extension for desktop capture(chrome
- * extension required), pass "webrtc" to use WebRTC "screen" desktop
- * source('chrome://flags/#enable-usermedia-screen-capture' must be
- * enabled), pass any other string or nothing in order to disable this
- * feature completely.
- */
-function setDesktopSharing(method) {
- // Check if we are running chrome
- if (!navigator.webkitGetUserMedia) {
- obtainDesktopStream = null;
- console.info("Desktop sharing disabled");
- } else if (method == "ext") {
- obtainDesktopStream = obtainScreenFromExtension;
- console.info("Using Chrome extension for desktop sharing");
- } else if (method == "webrtc") {
- obtainDesktopStream = obtainWebRTCScreen;
- console.info("Using Chrome WebRTC for desktop sharing");
- }
-
- // Reset enabled cache
- _desktopSharingEnabled = null;
-}
-
-/**
- * Initializes with extension id set in
- * config.js to support inline installs. Host site must be selected as main
- * website of published extension.
- */
-function initInlineInstalls()
-{
- $("link[rel=chrome-webstore-item]").attr("href", getWebStoreInstallUrl());
-}
-
-function getVideoStreamFailed(error) {
- console.error("Failed to obtain the stream to switch to", error);
- switchInProgress = false;
- isUsingScreenStream = false;
- newStreamCreated(null);
-}
-
-function getDesktopStreamFailed(error) {
- console.error("Failed to obtain the stream to switch to", error);
- switchInProgress = false;
-}
-
-function streamSwitchDone() {
- switchInProgress = false;
- eventEmitter.emit(
- DesktopSharingEventTypes.SWITCHING_DONE,
- isUsingScreenStream);
-}
-
-function newStreamCreated(stream)
-{
- eventEmitter.emit(DesktopSharingEventTypes.NEW_STREAM_CREATED,
- stream, isUsingScreenStream, streamSwitchDone);
-}
-
-
-module.exports = {
- isUsingScreenStream: function () {
- return isUsingScreenStream;
- },
-
- /**
- * @returns {boolean} true if desktop sharing feature is available
- * and enabled.
- */
- isDesktopSharingEnabled: function () {
- if (_desktopSharingEnabled === null) {
- if (obtainDesktopStream === obtainScreenFromExtension) {
- // Parse chrome version
- var userAgent = navigator.userAgent.toLowerCase();
- // We can assume that user agent is chrome, because it's
- // enforced when 'ext' streaming method is set
- var ver = parseInt(userAgent.match(/chrome\/(\d+)\./)[1], 10);
- console.log("Chrome version" + userAgent, ver);
- _desktopSharingEnabled = ver >= 34;
- } else {
- _desktopSharingEnabled =
- obtainDesktopStream === obtainWebRTCScreen;
- }
- }
- return _desktopSharingEnabled;
- },
-
- init: function () {
- setDesktopSharing(config.desktopSharing);
-
- // Initialize Chrome extension inline installs
- if (config.chromeExtensionId) {
-
- initInlineInstalls();
-
- // Check if extension is installed
- checkExtInstalled(function (installed, updateRequired) {
- extInstalled = installed;
- extUpdateRequired = updateRequired;
- console.info(
- "Chrome extension installed: " + extInstalled +
- " updateRequired: " + extUpdateRequired);
- });
- }
-
- eventEmitter.emit(DesktopSharingEventTypes.INIT);
- },
-
- addListener: function (listener, type)
- {
- eventEmitter.on(type, listener);
- },
-
- removeListener: function (listener, type) {
- eventEmitter.removeListener(type, listener);
- },
-
- /*
- * Toggles screen sharing.
- */
- toggleScreenSharing: function () {
- if (switchInProgress || !obtainDesktopStream) {
- console.warn("Switch in progress or no method defined");
- return;
- }
- switchInProgress = true;
-
- if (!isUsingScreenStream)
- {
- // Switch to desktop stream
- obtainDesktopStream(
- function (stream) {
- // We now use screen stream
- isUsingScreenStream = true;
- // Hook 'ended' event to restore camera
- // when screen stream stops
- stream.addEventListener('ended',
- function (e) {
- if (!switchInProgress && isUsingScreenStream) {
- APP.desktopsharing.toggleScreenSharing();
- }
- }
- );
- newStreamCreated(stream);
- },
- getDesktopStreamFailed);
- } else {
- // Disable screen stream
- APP.RTC.getUserMediaWithConstraints(
- ['video'],
- function (stream) {
- // We are now using camera stream
- isUsingScreenStream = false;
- newStreamCreated(stream);
- },
- getVideoStreamFailed, config.resolution || '360'
- );
- }
- }
-};
-
-
-},{"../../service/desktopsharing/DesktopSharingEventTypes":108,"events":112}],43:[function(require,module,exports){
-//maps keycode to character, id of popover for given function and function
-var shortcuts = {
- 67: {
- character: "C",
- id: "toggleChatPopover",
- function: APP.UI.toggleChat
- },
- 70: {
- character: "F",
- id: "filmstripPopover",
- function: APP.UI.toggleFilmStrip
- },
- 77: {
- character: "M",
- id: "mutePopover",
- function: APP.UI.toggleAudio
- },
- 84: {
- character: "T",
- function: function() {
- if(!APP.RTC.localAudio.isMuted()) {
- APP.UI.toggleAudio();
- }
- }
- },
- 86: {
- character: "V",
- id: "toggleVideoPopover",
- function: APP.UI.toggleVideo
- }
-};
-
-
-var KeyboardShortcut = {
- init: function () {
- window.onkeyup = function(e) {
- var keycode = e.which;
- if(!($(":focus").is("input[type=text]") ||
- $(":focus").is("input[type=password]") ||
- $(":focus").is("textarea"))) {
- if (typeof shortcuts[keycode] === "object") {
- shortcuts[keycode].function();
- }
- else if (keycode >= "0".charCodeAt(0) &&
- keycode <= "9".charCodeAt(0)) {
- APP.UI.clickOnVideo(keycode - "0".charCodeAt(0) + 1);
- }
- //esc while the smileys are visible hides them
- } else if (keycode === 27 && $('#smileysContainer').is(':visible')) {
- APP.UI.toggleSmileys();
- }
- };
-
- window.onkeydown = function(e) {
- if(!($(":focus").is("input[type=text]") ||
- $(":focus").is("input[type=password]") ||
- $(":focus").is("textarea"))) {
- if(e.which === "T".charCodeAt(0)) {
- if(APP.RTC.localAudio.isMuted()) {
- APP.UI.toggleAudio();
- }
- }
- }
- };
- var self = this;
- $('body').popover({ selector: '[data-toggle=popover]',
- trigger: 'click hover',
- content: function() {
- return this.getAttribute("content") +
- self.getShortcut(this.getAttribute("shortcut"));
- }
- });
- },
- /**
- *
- * @param id indicates the popover associated with the shortcut
- * @returns {string} the keyboard shortcut used for the id given
- */
- getShortcut: function (id) {
- for (var keycode in shortcuts) {
- if (shortcuts.hasOwnProperty(keycode)) {
- if (shortcuts[keycode].id === id) {
- return " (" + shortcuts[keycode].character + ")";
- }
- }
- }
- return "";
- }
-};
-
-module.exports = KeyboardShortcut;
-
-},{}],44:[function(require,module,exports){
-/* global APP */
-
-/**
- * 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;
-
-},{"../../service/members/Events":109,"../../service/xmpp/XMPPEvents":111,"events":112}],45:[function(require,module,exports){
-var email = '';
-var displayName = '';
-var userId;
-var language = null;
-
-
-function supportsLocalStorage() {
- try {
- return 'localStorage' in window && window.localStorage !== null;
- } catch (e) {
- console.log("localstorage is not supported");
- return false;
- }
-}
-
-
-function generateUniqueId() {
- function _p8() {
- return (Math.random().toString(16) + "000000000").substr(2, 8);
- }
- return _p8() + _p8() + _p8() + _p8();
-}
-
-if (supportsLocalStorage()) {
- if (!window.localStorage.jitsiMeetId) {
- window.localStorage.jitsiMeetId = generateUniqueId();
- console.log("generated id", window.localStorage.jitsiMeetId);
- }
- userId = window.localStorage.jitsiMeetId || '';
- email = window.localStorage.email || '';
- displayName = window.localStorage.displayname || '';
- language = window.localStorage.language;
-} else {
- console.log("local storage is not supported");
- userId = generateUniqueId();
-}
-
-var Settings =
-{
- setDisplayName: function (newDisplayName) {
- displayName = newDisplayName;
- window.localStorage.displayname = displayName;
- return displayName;
- },
- setEmail: function (newEmail)
- {
- email = newEmail;
- window.localStorage.email = newEmail;
- return email;
- },
- getSettings: function () {
- return {
- email: email,
- displayName: displayName,
- uid: userId,
- language: language
- };
- },
- setLanguage: function (lang) {
- language = lang;
- window.localStorage.language = lang;
- }
-};
-
-module.exports = Settings;
+},{"../../service/connectionquality/CQEvents":105,"../../service/xmpp/XMPPEvents":109,"events":1}],44:[function(require,module,exports){
+/* global $, alert, APP, changeLocalVideo, chrome, config, getConferenceHandler,
+ getUserMediaWithConstraints */
+/**
+ * Indicates that desktop stream is currently in use(for toggle purpose).
+ * @type {boolean}
+ */
+var isUsingScreenStream = false;
+/**
+ * Indicates that switch stream operation is in progress and prevent from
+ * triggering new events.
+ * @type {boolean}
+ */
+var switchInProgress = false;
+
+/**
+ * Method used to get screen sharing stream.
+ *
+ * @type {function (stream_callback, failure_callback}
+ */
+var obtainDesktopStream = null;
+
+/**
+ * Indicates whether desktop sharing extension is installed.
+ * @type {boolean}
+ */
+var extInstalled = false;
+
+/**
+ * Indicates whether update of desktop sharing extension is required.
+ * @type {boolean}
+ */
+var extUpdateRequired = false;
+
+/**
+ * Flag used to cache desktop sharing enabled state. Do not use directly as
+ * it can be null .
+ *
+ * @type {null|boolean}
+ */
+var _desktopSharingEnabled = null;
+
+var EventEmitter = require("events");
+
+var eventEmitter = new EventEmitter();
+
+var DesktopSharingEventTypes
+ = require("../../service/desktopsharing/DesktopSharingEventTypes");
+
+/**
+ * Method obtains desktop stream from WebRTC 'screen' source.
+ * Flag 'chrome://flags/#enable-usermedia-screen-capture' must be enabled.
+ */
+function obtainWebRTCScreen(streamCallback, failCallback) {
+ APP.RTC.getUserMediaWithConstraints(
+ ['screen'],
+ streamCallback,
+ failCallback
+ );
+}
+
+/**
+ * Constructs inline install URL for Chrome desktop streaming extension.
+ * The 'chromeExtensionId' must be defined in config.js.
+ * @returns {string}
+ */
+function getWebStoreInstallUrl()
+{
+ return "https://chrome.google.com/webstore/detail/" +
+ config.chromeExtensionId;
+}
+
+/**
+ * Checks whether extension update is required.
+ * @param minVersion minimal required version
+ * @param extVersion current extension version
+ * @returns {boolean}
+ */
+function isUpdateRequired(minVersion, extVersion)
+{
+ try
+ {
+ var s1 = minVersion.split('.');
+ var s2 = extVersion.split('.');
+
+ var len = Math.max(s1.length, s2.length);
+ for (var i = 0; i < len; i++)
+ {
+ var n1 = 0,
+ n2 = 0;
+
+ if (i < s1.length)
+ n1 = parseInt(s1[i]);
+ if (i < s2.length)
+ n2 = parseInt(s2[i]);
+
+ if (isNaN(n1) || isNaN(n2))
+ {
+ return true;
+ }
+ else if (n1 !== n2)
+ {
+ return n1 > n2;
+ }
+ }
+
+ // will happen if boths version has identical numbers in
+ // their components (even if one of them is longer, has more components)
+ return false;
+ }
+ catch (e)
+ {
+ console.error("Failed to parse extension version", e);
+ APP.UI.messageHandler.showError("dialog.error",
+ "dialog.detectext");
+ return true;
+ }
+}
+
+function checkExtInstalled(callback) {
+ if (!chrome.runtime) {
+ // No API, so no extension for sure
+ callback(false, false);
+ return;
+ }
+ chrome.runtime.sendMessage(
+ config.chromeExtensionId,
+ { getVersion: true },
+ function (response) {
+ if (!response || !response.version) {
+ // Communication failure - assume that no endpoint exists
+ console.warn(
+ "Extension not installed?: ", chrome.runtime.lastError);
+ callback(false, false);
+ return;
+ }
+ // Check installed extension version
+ var extVersion = response.version;
+ console.log('Extension version is: ' + extVersion);
+ var updateRequired
+ = isUpdateRequired(config.minChromeExtVersion, extVersion);
+ callback(!updateRequired, updateRequired);
+ }
+ );
+}
+
+function doGetStreamFromExtension(streamCallback, failCallback) {
+ // Sends 'getStream' msg to the extension.
+ // Extension id must be defined in the config.
+ chrome.runtime.sendMessage(
+ config.chromeExtensionId,
+ { getStream: true, sources: config.desktopSharingSources },
+ function (response) {
+ if (!response) {
+ failCallback(chrome.runtime.lastError);
+ return;
+ }
+ console.log("Response from extension: " + response);
+ if (response.streamId) {
+ APP.RTC.getUserMediaWithConstraints(
+ ['desktop'],
+ function (stream) {
+ streamCallback(stream);
+ },
+ failCallback,
+ null, null, null,
+ response.streamId);
+ } else {
+ failCallback("Extension failed to get the stream");
+ }
+ }
+ );
+}
+/**
+ * Asks Chrome extension to call chooseDesktopMedia and gets chrome 'desktop'
+ * stream for returned stream token.
+ */
+function obtainScreenFromExtension(streamCallback, failCallback) {
+ if (extInstalled) {
+ doGetStreamFromExtension(streamCallback, failCallback);
+ } else {
+ if (extUpdateRequired) {
+ alert(
+ 'Jitsi Desktop Streamer requires update. ' +
+ 'Changes will take effect after next Chrome restart.');
+ }
+
+ chrome.webstore.install(
+ getWebStoreInstallUrl(),
+ function (arg) {
+ console.log("Extension installed successfully", arg);
+ extInstalled = true;
+ // We need to give a moment for the endpoint to become available
+ window.setTimeout(function () {
+ doGetStreamFromExtension(streamCallback, failCallback);
+ }, 500);
+ },
+ function (arg) {
+ console.log("Failed to install the extension", arg);
+ failCallback(arg);
+ APP.UI.messageHandler.showError("dialog.error",
+ "dialog.failtoinstall");
+ }
+ );
+ }
+}
+
+/**
+ * Call this method to toggle desktop sharing feature.
+ * @param method pass "ext" to use chrome extension for desktop capture(chrome
+ * extension required), pass "webrtc" to use WebRTC "screen" desktop
+ * source('chrome://flags/#enable-usermedia-screen-capture' must be
+ * enabled), pass any other string or nothing in order to disable this
+ * feature completely.
+ */
+function setDesktopSharing(method) {
+ // Check if we are running chrome
+ if (!navigator.webkitGetUserMedia) {
+ obtainDesktopStream = null;
+ console.info("Desktop sharing disabled");
+ } else if (method == "ext") {
+ obtainDesktopStream = obtainScreenFromExtension;
+ console.info("Using Chrome extension for desktop sharing");
+ } else if (method == "webrtc") {
+ obtainDesktopStream = obtainWebRTCScreen;
+ console.info("Using Chrome WebRTC for desktop sharing");
+ }
+
+ // Reset enabled cache
+ _desktopSharingEnabled = null;
+}
+
+/**
+ * Initializes with extension id set in
+ * config.js to support inline installs. Host site must be selected as main
+ * website of published extension.
+ */
+function initInlineInstalls()
+{
+ $("link[rel=chrome-webstore-item]").attr("href", getWebStoreInstallUrl());
+}
+
+function getVideoStreamFailed(error) {
+ console.error("Failed to obtain the stream to switch to", error);
+ switchInProgress = false;
+ isUsingScreenStream = false;
+ newStreamCreated(null);
+}
+
+function getDesktopStreamFailed(error) {
+ console.error("Failed to obtain the stream to switch to", error);
+ switchInProgress = false;
+}
+
+function streamSwitchDone() {
+ switchInProgress = false;
+ eventEmitter.emit(
+ DesktopSharingEventTypes.SWITCHING_DONE,
+ isUsingScreenStream);
+}
+
+function newStreamCreated(stream)
+{
+ eventEmitter.emit(DesktopSharingEventTypes.NEW_STREAM_CREATED,
+ stream, isUsingScreenStream, streamSwitchDone);
+}
+
+
+module.exports = {
+ isUsingScreenStream: function () {
+ return isUsingScreenStream;
+ },
+
+ /**
+ * @returns {boolean} true if desktop sharing feature is available
+ * and enabled.
+ */
+ isDesktopSharingEnabled: function () {
+ if (_desktopSharingEnabled === null) {
+ if (obtainDesktopStream === obtainScreenFromExtension) {
+ // Parse chrome version
+ var userAgent = navigator.userAgent.toLowerCase();
+ // We can assume that user agent is chrome, because it's
+ // enforced when 'ext' streaming method is set
+ var ver = parseInt(userAgent.match(/chrome\/(\d+)\./)[1], 10);
+ console.log("Chrome version" + userAgent, ver);
+ _desktopSharingEnabled = ver >= 34;
+ } else {
+ _desktopSharingEnabled =
+ obtainDesktopStream === obtainWebRTCScreen;
+ }
+ }
+ return _desktopSharingEnabled;
+ },
+
+ init: function () {
+ setDesktopSharing(config.desktopSharing);
+
+ // Initialize Chrome extension inline installs
+ if (config.chromeExtensionId) {
+
+ initInlineInstalls();
+
+ // Check if extension is installed
+ checkExtInstalled(function (installed, updateRequired) {
+ extInstalled = installed;
+ extUpdateRequired = updateRequired;
+ console.info(
+ "Chrome extension installed: " + extInstalled +
+ " updateRequired: " + extUpdateRequired);
+ });
+ }
+
+ eventEmitter.emit(DesktopSharingEventTypes.INIT);
+ },
+
+ addListener: function (listener, type)
+ {
+ eventEmitter.on(type, listener);
+ },
+
+ removeListener: function (listener, type) {
+ eventEmitter.removeListener(type, listener);
+ },
+
+ /*
+ * Toggles screen sharing.
+ */
+ toggleScreenSharing: function () {
+ if (switchInProgress || !obtainDesktopStream) {
+ console.warn("Switch in progress or no method defined");
+ return;
+ }
+ switchInProgress = true;
+
+ if (!isUsingScreenStream)
+ {
+ // Switch to desktop stream
+ obtainDesktopStream(
+ function (stream) {
+ // We now use screen stream
+ isUsingScreenStream = true;
+ // Hook 'ended' event to restore camera
+ // when screen stream stops
+ stream.addEventListener('ended',
+ function (e) {
+ if (!switchInProgress && isUsingScreenStream) {
+ APP.desktopsharing.toggleScreenSharing();
+ }
+ }
+ );
+ newStreamCreated(stream);
+ },
+ getDesktopStreamFailed);
+ } else {
+ // Disable screen stream
+ APP.RTC.getUserMediaWithConstraints(
+ ['video'],
+ function (stream) {
+ // We are now using camera stream
+ isUsingScreenStream = false;
+ newStreamCreated(stream);
+ },
+ getVideoStreamFailed, config.resolution || '360'
+ );
+ }
+ }
+};
+
+
+},{"../../service/desktopsharing/DesktopSharingEventTypes":106,"events":1}],45:[function(require,module,exports){
+//maps keycode to character, id of popover for given function and function
+var shortcuts = {
+ 67: {
+ character: "C",
+ id: "toggleChatPopover",
+ function: APP.UI.toggleChat
+ },
+ 70: {
+ character: "F",
+ id: "filmstripPopover",
+ function: APP.UI.toggleFilmStrip
+ },
+ 77: {
+ character: "M",
+ id: "mutePopover",
+ function: APP.UI.toggleAudio
+ },
+ 84: {
+ character: "T",
+ function: function() {
+ if(!APP.RTC.localAudio.isMuted()) {
+ APP.UI.toggleAudio();
+ }
+ }
+ },
+ 86: {
+ character: "V",
+ id: "toggleVideoPopover",
+ function: APP.UI.toggleVideo
+ }
+};
+
+
+var KeyboardShortcut = {
+ init: function () {
+ window.onkeyup = function(e) {
+ var keycode = e.which;
+ if(!($(":focus").is("input[type=text]") ||
+ $(":focus").is("input[type=password]") ||
+ $(":focus").is("textarea"))) {
+ if (typeof shortcuts[keycode] === "object") {
+ shortcuts[keycode].function();
+ }
+ else if (keycode >= "0".charCodeAt(0) &&
+ keycode <= "9".charCodeAt(0)) {
+ APP.UI.clickOnVideo(keycode - "0".charCodeAt(0) + 1);
+ }
+ //esc while the smileys are visible hides them
+ } else if (keycode === 27 && $('#smileysContainer').is(':visible')) {
+ APP.UI.toggleSmileys();
+ }
+ };
+
+ window.onkeydown = function(e) {
+ if(!($(":focus").is("input[type=text]") ||
+ $(":focus").is("input[type=password]") ||
+ $(":focus").is("textarea"))) {
+ if(e.which === "T".charCodeAt(0)) {
+ if(APP.RTC.localAudio.isMuted()) {
+ APP.UI.toggleAudio();
+ }
+ }
+ }
+ };
+ var self = this;
+ $('body').popover({ selector: '[data-toggle=popover]',
+ trigger: 'click hover',
+ content: function() {
+ return this.getAttribute("content") +
+ self.getShortcut(this.getAttribute("shortcut"));
+ }
+ });
+ },
+ /**
+ *
+ * @param id indicates the popover associated with the shortcut
+ * @returns {string} the keyboard shortcut used for the id given
+ */
+ getShortcut: function (id) {
+ for (var keycode in shortcuts) {
+ if (shortcuts.hasOwnProperty(keycode)) {
+ if (shortcuts[keycode].id === id) {
+ return " (" + shortcuts[keycode].character + ")";
+ }
+ }
+ }
+ return "";
+ }
+};
+
+module.exports = KeyboardShortcut;
},{}],46:[function(require,module,exports){
-/**
- * Provides statistics for the local stream.
- */
+/* global APP */
+
+/**
+ * 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;
+},{"../../service/members/Events":107,"../../service/xmpp/XMPPEvents":109,"events":1}],47:[function(require,module,exports){
+var email = '';
+var displayName = '';
+var userId;
+var language = null;
+
+
+function supportsLocalStorage() {
+ try {
+ return 'localStorage' in window && window.localStorage !== null;
+ } catch (e) {
+ console.log("localstorage is not supported");
+ return false;
+ }
+}
+
+
+function generateUniqueId() {
+ function _p8() {
+ return (Math.random().toString(16) + "000000000").substr(2, 8);
+ }
+ return _p8() + _p8() + _p8() + _p8();
+}
+
+if (supportsLocalStorage()) {
+ if (!window.localStorage.jitsiMeetId) {
+ window.localStorage.jitsiMeetId = generateUniqueId();
+ console.log("generated id", window.localStorage.jitsiMeetId);
+ }
+ userId = window.localStorage.jitsiMeetId || '';
+ email = window.localStorage.email || '';
+ displayName = window.localStorage.displayname || '';
+ language = window.localStorage.language;
+} else {
+ console.log("local storage is not supported");
+ userId = generateUniqueId();
+}
+
+var Settings =
+{
+ setDisplayName: function (newDisplayName) {
+ displayName = newDisplayName;
+ window.localStorage.displayname = displayName;
+ return displayName;
+ },
+ setEmail: function (newEmail)
+ {
+ email = newEmail;
+ window.localStorage.email = newEmail;
+ return email;
+ },
+ getSettings: function () {
+ return {
+ email: email,
+ displayName: displayName,
+ uid: userId,
+ language: language
+ };
+ },
+ setLanguage: function (lang) {
+ language = lang;
+ window.localStorage.language = lang;
+ }
+};
+
+module.exports = Settings;
-/**
- * Size of the webaudio analizer buffer.
- * @type {number}
- */
-var WEBAUDIO_ANALIZER_FFT_SIZE = 2048;
-
-/**
- * Value of the webaudio analizer smoothing time parameter.
- * @type {number}
- */
-var WEBAUDIO_ANALIZER_SMOOTING_TIME = 0.8;
-
-/**
- * Converts time domain data array to audio level.
- * @param array the time domain data array.
- * @returns {number} the audio level
- */
-function timeDomainDataToAudioLevel(samples) {
-
- var maxVolume = 0;
-
- var length = samples.length;
-
- for (var i = 0; i < length; i++) {
- if (maxVolume < samples[i])
- maxVolume = samples[i];
- }
-
- return parseFloat(((maxVolume - 127) / 128).toFixed(3));
-};
-
-/**
- * Animates audio level change
- * @param newLevel the new audio level
- * @param lastLevel the last audio level
- * @returns {Number} the audio level to be set
- */
-function animateLevel(newLevel, lastLevel)
-{
- var value = 0;
- var diff = lastLevel - newLevel;
- if(diff > 0.2)
- {
- value = lastLevel - 0.2;
- }
- else if(diff < -0.4)
- {
- value = lastLevel + 0.4;
- }
- else
- {
- value = newLevel;
- }
-
- return parseFloat(value.toFixed(3));
-}
-
-
-/**
- * LocalStatsCollector calculates statistics for the local stream.
- *
- * @param stream the local stream
- * @param interval stats refresh interval given in ms.
- * @param {function(LocalStatsCollector)} updateCallback the callback called on stats
- * update.
- * @constructor
- */
-function LocalStatsCollector(stream, interval, statisticsService, eventEmitter) {
- window.AudioContext = window.AudioContext || window.webkitAudioContext;
- this.stream = stream;
- this.intervalId = null;
- this.intervalMilis = interval;
- this.eventEmitter = eventEmitter;
- this.audioLevel = 0;
- this.statisticsService = statisticsService;
-}
-
-/**
- * Starts the collecting the statistics.
- */
-LocalStatsCollector.prototype.start = function () {
- if (config.disableAudioLevels || !window.AudioContext)
- return;
-
- var context = new AudioContext();
- var analyser = context.createAnalyser();
- analyser.smoothingTimeConstant = WEBAUDIO_ANALIZER_SMOOTING_TIME;
- analyser.fftSize = WEBAUDIO_ANALIZER_FFT_SIZE;
-
-
- var source = context.createMediaStreamSource(this.stream);
- source.connect(analyser);
-
-
- var self = this;
-
- this.intervalId = setInterval(
- function () {
- var array = new Uint8Array(analyser.frequencyBinCount);
- analyser.getByteTimeDomainData(array);
- var audioLevel = timeDomainDataToAudioLevel(array);
- if(audioLevel != self.audioLevel) {
- self.audioLevel = animateLevel(audioLevel, self.audioLevel);
- self.eventEmitter.emit(
- "statistics.audioLevel",
- self.statisticsService.LOCAL_JID,
- self.audioLevel);
- }
- },
- this.intervalMilis
- );
-
-};
-
-/**
- * Stops collecting the statistics.
- */
-LocalStatsCollector.prototype.stop = function () {
- if (this.intervalId) {
- clearInterval(this.intervalId);
- this.intervalId = null;
- }
-};
-
+},{}],48:[function(require,module,exports){
+/**
+ * Provides statistics for the local stream.
+ */
+
+
+/**
+ * Size of the webaudio analizer buffer.
+ * @type {number}
+ */
+var WEBAUDIO_ANALIZER_FFT_SIZE = 2048;
+
+/**
+ * Value of the webaudio analizer smoothing time parameter.
+ * @type {number}
+ */
+var WEBAUDIO_ANALIZER_SMOOTING_TIME = 0.8;
+
+/**
+ * Converts time domain data array to audio level.
+ * @param array the time domain data array.
+ * @returns {number} the audio level
+ */
+function timeDomainDataToAudioLevel(samples) {
+
+ var maxVolume = 0;
+
+ var length = samples.length;
+
+ for (var i = 0; i < length; i++) {
+ if (maxVolume < samples[i])
+ maxVolume = samples[i];
+ }
+
+ return parseFloat(((maxVolume - 127) / 128).toFixed(3));
+};
+
+/**
+ * Animates audio level change
+ * @param newLevel the new audio level
+ * @param lastLevel the last audio level
+ * @returns {Number} the audio level to be set
+ */
+function animateLevel(newLevel, lastLevel)
+{
+ var value = 0;
+ var diff = lastLevel - newLevel;
+ if(diff > 0.2)
+ {
+ value = lastLevel - 0.2;
+ }
+ else if(diff < -0.4)
+ {
+ value = lastLevel + 0.4;
+ }
+ else
+ {
+ value = newLevel;
+ }
+
+ return parseFloat(value.toFixed(3));
+}
+
+
+/**
+ * LocalStatsCollector calculates statistics for the local stream.
+ *
+ * @param stream the local stream
+ * @param interval stats refresh interval given in ms.
+ * @param {function(LocalStatsCollector)} updateCallback the callback called on stats
+ * update.
+ * @constructor
+ */
+function LocalStatsCollector(stream, interval, statisticsService, eventEmitter) {
+ window.AudioContext = window.AudioContext || window.webkitAudioContext;
+ this.stream = stream;
+ this.intervalId = null;
+ this.intervalMilis = interval;
+ this.eventEmitter = eventEmitter;
+ this.audioLevel = 0;
+ this.statisticsService = statisticsService;
+}
+
+/**
+ * Starts the collecting the statistics.
+ */
+LocalStatsCollector.prototype.start = function () {
+ if (config.disableAudioLevels || !window.AudioContext)
+ return;
+
+ var context = new AudioContext();
+ var analyser = context.createAnalyser();
+ analyser.smoothingTimeConstant = WEBAUDIO_ANALIZER_SMOOTING_TIME;
+ analyser.fftSize = WEBAUDIO_ANALIZER_FFT_SIZE;
+
+
+ var source = context.createMediaStreamSource(this.stream);
+ source.connect(analyser);
+
+
+ var self = this;
+
+ this.intervalId = setInterval(
+ function () {
+ var array = new Uint8Array(analyser.frequencyBinCount);
+ analyser.getByteTimeDomainData(array);
+ var audioLevel = timeDomainDataToAudioLevel(array);
+ if(audioLevel != self.audioLevel) {
+ self.audioLevel = animateLevel(audioLevel, self.audioLevel);
+ self.eventEmitter.emit(
+ "statistics.audioLevel",
+ self.statisticsService.LOCAL_JID,
+ self.audioLevel);
+ }
+ },
+ this.intervalMilis
+ );
+
+};
+
+/**
+ * Stops collecting the statistics.
+ */
+LocalStatsCollector.prototype.stop = function () {
+ if (this.intervalId) {
+ clearInterval(this.intervalId);
+ this.intervalId = null;
+ }
+};
+
module.exports = LocalStatsCollector;
-},{}],47:[function(require,module,exports){
-/* global ssrc2jid */
-/* jshint -W117 */
-var RTCBrowserType = require("../../service/RTC/RTCBrowserType");
-
-
-/**
- * Calculates packet lost percent using the number of lost packets and the
- * number of all packet.
- * @param lostPackets the number of lost packets
- * @param totalPackets the number of all packets.
- * @returns {number} packet loss percent
- */
-function calculatePacketLoss(lostPackets, totalPackets) {
- if(!totalPackets || totalPackets <= 0 || !lostPackets || lostPackets <= 0)
- return 0;
- return Math.round((lostPackets/totalPackets)*100);
-}
-
-function getStatValue(item, name) {
- if(!keyMap[APP.RTC.getBrowserType()][name])
- throw "The property isn't supported!";
- var key = keyMap[APP.RTC.getBrowserType()][name];
- return APP.RTC.getBrowserType() == RTCBrowserType.RTC_BROWSER_CHROME? item.stat(key) : item[key];
-}
-
-/**
- * Peer statistics data holder.
- * @constructor
- */
-function PeerStats()
-{
- this.ssrc2Loss = {};
- this.ssrc2AudioLevel = {};
- this.ssrc2bitrate = {};
- this.ssrc2resolution = {};
-}
-
-/**
- * The bandwidth
- * @type {{}}
- */
-PeerStats.bandwidth = {};
-
-/**
- * The bit rate
- * @type {{}}
- */
-PeerStats.bitrate = {};
-
-
-
-/**
- * The packet loss rate
- * @type {{}}
- */
-PeerStats.packetLoss = null;
-
-/**
- * Sets packets loss rate for given ssrc that blong to the peer
- * represented by this instance.
- * @param ssrc audio or video RTP stream SSRC.
- * @param lossRate new packet loss rate value to be set.
- */
-PeerStats.prototype.setSsrcLoss = function (ssrc, lossRate)
-{
- this.ssrc2Loss[ssrc] = lossRate;
-};
-
-/**
- * Sets resolution for given ssrc that belong to the peer
- * represented by this instance.
- * @param ssrc audio or video RTP stream SSRC.
- * @param resolution new resolution value to be set.
- */
-PeerStats.prototype.setSsrcResolution = function (ssrc, resolution)
-{
- if(resolution === null && this.ssrc2resolution[ssrc])
- {
- delete this.ssrc2resolution[ssrc];
- }
- else if(resolution !== null)
- this.ssrc2resolution[ssrc] = resolution;
-};
-
-/**
- * Sets the bit rate for given ssrc that blong to the peer
- * represented by this instance.
- * @param ssrc audio or video RTP stream SSRC.
- * @param bitrate new bitrate value to be set.
- */
-PeerStats.prototype.setSsrcBitrate = function (ssrc, bitrate)
-{
- if(this.ssrc2bitrate[ssrc])
- {
- this.ssrc2bitrate[ssrc].download += bitrate.download;
- this.ssrc2bitrate[ssrc].upload += bitrate.upload;
- }
- else {
- this.ssrc2bitrate[ssrc] = bitrate;
- }
-};
-
-/**
- * Sets new audio level(input or output) for given ssrc that identifies
- * the stream which belongs to the peer represented by this instance.
- * @param ssrc RTP stream SSRC for which current audio level value will be
- * updated.
- * @param audioLevel the new audio level value to be set. Value is truncated to
- * fit the range from 0 to 1.
- */
-PeerStats.prototype.setSsrcAudioLevel = function (ssrc, audioLevel)
-{
- // Range limit 0 - 1
- this.ssrc2AudioLevel[ssrc] = formatAudioLevel(audioLevel);
-};
-
-function formatAudioLevel(audioLevel) {
- return Math.min(Math.max(audioLevel, 0), 1);
-}
-
-/**
- * Array with the transport information.
- * @type {Array}
- */
-PeerStats.transport = [];
-
-
-/**
- * StatsCollector registers for stats updates of given
- * peerconnection in given interval . On each update particular
- * stats are extracted and put in {@link PeerStats} objects. Once the processing
- * is done audioLevelsUpdateCallback is called with this
- * instance as an event source.
- *
- * @param peerconnection webRTC peer connection object.
- * @param interval stats refresh interval given in ms.
- * @param {function(StatsCollector)} audioLevelsUpdateCallback the callback
- * called on stats update.
- * @constructor
- */
-function StatsCollector(peerconnection, audioLevelsInterval, statsInterval, eventEmitter)
-{
- this.peerconnection = peerconnection;
- this.baselineAudioLevelsReport = null;
- this.currentAudioLevelsReport = null;
- this.currentStatsReport = null;
- this.baselineStatsReport = null;
- this.audioLevelsIntervalId = null;
- this.eventEmitter = eventEmitter;
-
- /**
- * Gather PeerConnection stats once every this many milliseconds.
- */
- this.GATHER_INTERVAL = 15000;
-
- /**
- * Log stats via the focus once every this many milliseconds.
- */
- this.LOG_INTERVAL = 60000;
-
- /**
- * Gather stats and store them in this.statsToBeLogged.
- */
- this.gatherStatsIntervalId = null;
-
- /**
- * Send the stats already saved in this.statsToBeLogged to be logged via
- * the focus.
- */
- this.logStatsIntervalId = null;
-
- /**
- * Stores the statistics which will be send to the focus to be logged.
- */
- this.statsToBeLogged =
- {
- timestamps: [],
- stats: {}
- };
-
- // Updates stats interval
- this.audioLevelsIntervalMilis = audioLevelsInterval;
-
- this.statsIntervalId = null;
- this.statsIntervalMilis = statsInterval;
- // Map of jids to PeerStats
- this.jid2stats = {};
-}
-
-module.exports = StatsCollector;
-
-/**
- * Stops stats updates.
- */
-StatsCollector.prototype.stop = function () {
- if (this.audioLevelsIntervalId) {
- clearInterval(this.audioLevelsIntervalId);
- this.audioLevelsIntervalId = null;
- }
-
- if (this.statsIntervalId)
- {
- clearInterval(this.statsIntervalId);
- this.statsIntervalId = null;
- }
-
- if(this.logStatsIntervalId)
- {
- clearInterval(this.logStatsIntervalId);
- this.logStatsIntervalId = null;
- }
-
- if(this.gatherStatsIntervalId)
- {
- clearInterval(this.gatherStatsIntervalId);
- this.gatherStatsIntervalId = null;
- }
-};
-
-/**
- * Callback passed to getStats method.
- * @param error an error that occurred on getStats call.
- */
-StatsCollector.prototype.errorCallback = function (error)
-{
- console.error("Get stats error", error);
- this.stop();
-};
-
-/**
- * Starts stats updates.
- */
-StatsCollector.prototype.start = function ()
-{
- var self = this;
- if(!config.disableAudioLevels) {
- this.audioLevelsIntervalId = setInterval(
- function () {
- // Interval updates
- self.peerconnection.getStats(
- function (report) {
- var results = null;
- if (!report || !report.result ||
- typeof report.result != 'function') {
- results = report;
- }
- else {
- results = report.result();
- }
- //console.error("Got interval report", results);
- self.currentAudioLevelsReport = results;
- self.processAudioLevelReport();
- self.baselineAudioLevelsReport =
- self.currentAudioLevelsReport;
- },
- self.errorCallback
- );
- },
- self.audioLevelsIntervalMilis
- );
- }
-
- if(!config.disableStats && !navigator.mozGetUserMedia) {
- this.statsIntervalId = setInterval(
- function () {
- // Interval updates
- self.peerconnection.getStats(
- function (report) {
- var results = null;
- if (!report || !report.result ||
- typeof report.result != 'function') {
- //firefox
- results = report;
- }
- else {
- //chrome
- results = report.result();
- }
- //console.error("Got interval report", results);
- self.currentStatsReport = results;
- try {
- self.processStatsReport();
- }
- catch (e) {
- console.error("Unsupported key:" + e, e);
- }
-
- self.baselineStatsReport = self.currentStatsReport;
- },
- self.errorCallback
- );
- },
- self.statsIntervalMilis
- );
- }
-
- if (config.logStats && !navigator.mozGetUserMedia) {
- this.gatherStatsIntervalId = setInterval(
- function () {
- self.peerconnection.getStats(
- function (report) {
- self.addStatsToBeLogged(report.result());
- },
- function () {
- }
- );
- },
- this.GATHER_INTERVAL
- );
-
- this.logStatsIntervalId = setInterval(
- function() { self.logStats(); },
- this.LOG_INTERVAL);
- }
-};
-
-/**
- * Checks whether a certain record should be included in the logged statistics.
- */
-function acceptStat(reportId, reportType, statName) {
- if (reportType == "googCandidatePair" && statName == "googChannelId")
- return false;
-
- if (reportType == "ssrc") {
- if (statName == "googTrackId" ||
- statName == "transportId" ||
- statName == "ssrc")
- return false;
- }
-
- return true;
-}
-
-/**
- * Checks whether a certain record should be included in the logged statistics.
- */
-function acceptReport(id, type) {
- if (id.substring(0, 15) == "googCertificate" ||
- id.substring(0, 9) == "googTrack" ||
- id.substring(0, 20) == "googLibjingleSession")
- return false;
-
- if (type == "googComponent")
- return false;
-
- return true;
-}
-
-/**
- * Converts the stats to the format used for logging, and saves the data in
- * this.statsToBeLogged.
- * @param reports Reports as given by webkitRTCPerConnection.getStats.
- */
-StatsCollector.prototype.addStatsToBeLogged = function (reports) {
- var self = this;
- var num_records = this.statsToBeLogged.timestamps.length;
- this.statsToBeLogged.timestamps.push(new Date().getTime());
- reports.map(function (report) {
- if (!acceptReport(report.id, report.type))
- return;
- var stat = self.statsToBeLogged.stats[report.id];
- if (!stat) {
- stat = self.statsToBeLogged.stats[report.id] = {};
- }
- stat.type = report.type;
- report.names().map(function (name) {
- if (!acceptStat(report.id, report.type, name))
- return;
- var values = stat[name];
- if (!values) {
- values = stat[name] = [];
- }
- while (values.length < num_records) {
- values.push(null);
- }
- values.push(report.stat(name));
- });
- });
-};
-
-StatsCollector.prototype.logStats = function () {
-
- if(!APP.xmpp.sendLogs(this.statsToBeLogged))
- return;
- // Reset the stats
- this.statsToBeLogged.stats = {};
- this.statsToBeLogged.timestamps = [];
-};
-var keyMap = {};
-keyMap[RTCBrowserType.RTC_BROWSER_FIREFOX] = {
- "ssrc": "ssrc",
- "packetsReceived": "packetsReceived",
- "packetsLost": "packetsLost",
- "packetsSent": "packetsSent",
- "bytesReceived": "bytesReceived",
- "bytesSent": "bytesSent"
-};
-keyMap[RTCBrowserType.RTC_BROWSER_CHROME] = {
- "receiveBandwidth": "googAvailableReceiveBandwidth",
- "sendBandwidth": "googAvailableSendBandwidth",
- "remoteAddress": "googRemoteAddress",
- "transportType": "googTransportType",
- "localAddress": "googLocalAddress",
- "activeConnection": "googActiveConnection",
- "ssrc": "ssrc",
- "packetsReceived": "packetsReceived",
- "packetsSent": "packetsSent",
- "packetsLost": "packetsLost",
- "bytesReceived": "bytesReceived",
- "bytesSent": "bytesSent",
- "googFrameHeightReceived": "googFrameHeightReceived",
- "googFrameWidthReceived": "googFrameWidthReceived",
- "googFrameHeightSent": "googFrameHeightSent",
- "googFrameWidthSent": "googFrameWidthSent",
- "audioInputLevel": "audioInputLevel",
- "audioOutputLevel": "audioOutputLevel"
-};
-
-
-/**
- * Stats processing logic.
- */
-StatsCollector.prototype.processStatsReport = function () {
- if (!this.baselineStatsReport) {
- return;
- }
-
- for (var idx in this.currentStatsReport) {
- var now = this.currentStatsReport[idx];
- try {
- if (getStatValue(now, 'receiveBandwidth') ||
- getStatValue(now, 'sendBandwidth')) {
- PeerStats.bandwidth = {
- "download": Math.round(
- (getStatValue(now, 'receiveBandwidth')) / 1000),
- "upload": Math.round(
- (getStatValue(now, 'sendBandwidth')) / 1000)
- };
- }
- }
- catch(e){/*not supported*/}
-
- if(now.type == 'googCandidatePair')
- {
- var ip, type, localIP, active;
- try {
- ip = getStatValue(now, 'remoteAddress');
- type = getStatValue(now, "transportType");
- localIP = getStatValue(now, "localAddress");
- active = getStatValue(now, "activeConnection");
- }
- catch(e){/*not supported*/}
- if(!ip || !type || !localIP || active != "true")
- continue;
- var addressSaved = false;
- for(var i = 0; i < PeerStats.transport.length; i++)
- {
- if(PeerStats.transport[i].ip == ip &&
- PeerStats.transport[i].type == type &&
- PeerStats.transport[i].localip == localIP)
- {
- addressSaved = true;
- }
- }
- if(addressSaved)
- continue;
- PeerStats.transport.push({localip: localIP, ip: ip, type: type});
- continue;
- }
-
- if(now.type == "candidatepair")
- {
- if(now.state == "succeeded")
- continue;
-
- var local = this.currentStatsReport[now.localCandidateId];
- var remote = this.currentStatsReport[now.remoteCandidateId];
- PeerStats.transport.push({localip: local.ipAddress + ":" + local.portNumber,
- ip: remote.ipAddress + ":" + remote.portNumber, type: local.transport});
-
- }
-
- if (now.type != 'ssrc' && now.type != "outboundrtp" &&
- now.type != "inboundrtp") {
- continue;
- }
-
- var before = this.baselineStatsReport[idx];
- if (!before) {
- console.warn(getStatValue(now, 'ssrc') + ' not enough data');
- continue;
- }
-
- var ssrc = getStatValue(now, 'ssrc');
- if(!ssrc)
- continue;
- var jid = APP.xmpp.getJidFromSSRC(ssrc);
- if (!jid && (Date.now() - now.timestamp) < 3000) {
- console.warn("No jid for ssrc: " + ssrc);
- continue;
- }
-
- var jidStats = this.jid2stats[jid];
- if (!jidStats) {
- jidStats = new PeerStats();
- this.jid2stats[jid] = jidStats;
- }
-
-
- var isDownloadStream = true;
- var key = 'packetsReceived';
- if (!getStatValue(now, key))
- {
- isDownloadStream = false;
- key = 'packetsSent';
- if (!getStatValue(now, key))
- {
- console.warn("No packetsReceived nor packetSent stat found");
- continue;
- }
- }
- var packetsNow = getStatValue(now, key);
- if(!packetsNow || packetsNow < 0)
- packetsNow = 0;
-
- var packetsBefore = getStatValue(before, key);
- if(!packetsBefore || packetsBefore < 0)
- packetsBefore = 0;
- var packetRate = packetsNow - packetsBefore;
- if(!packetRate || packetRate < 0)
- packetRate = 0;
- var currentLoss = getStatValue(now, 'packetsLost');
- if(!currentLoss || currentLoss < 0)
- currentLoss = 0;
- var previousLoss = getStatValue(before, 'packetsLost');
- if(!previousLoss || previousLoss < 0)
- previousLoss = 0;
- var lossRate = currentLoss - previousLoss;
- if(!lossRate || lossRate < 0)
- lossRate = 0;
- var packetsTotal = (packetRate + lossRate);
-
- jidStats.setSsrcLoss(ssrc,
- {"packetsTotal": packetsTotal,
- "packetsLost": lossRate,
- "isDownloadStream": isDownloadStream});
-
-
- var bytesReceived = 0, bytesSent = 0;
- if(getStatValue(now, "bytesReceived"))
- {
- bytesReceived = getStatValue(now, "bytesReceived") -
- getStatValue(before, "bytesReceived");
- }
-
- if(getStatValue(now, "bytesSent"))
- {
- bytesSent = getStatValue(now, "bytesSent") -
- getStatValue(before, "bytesSent");
- }
-
- var time = Math.round((now.timestamp - before.timestamp) / 1000);
- if(bytesReceived <= 0 || time <= 0)
- {
- bytesReceived = 0;
- }
- else
- {
- bytesReceived = Math.round(((bytesReceived * 8) / time) / 1000);
- }
-
- if(bytesSent <= 0 || time <= 0)
- {
- bytesSent = 0;
- }
- else
- {
- bytesSent = Math.round(((bytesSent * 8) / time) / 1000);
- }
-
- jidStats.setSsrcBitrate(ssrc, {
- "download": bytesReceived,
- "upload": bytesSent});
-
- var resolution = {height: null, width: null};
- try {
- if (getStatValue(now, "googFrameHeightReceived") &&
- getStatValue(now, "googFrameWidthReceived")) {
- resolution.height = getStatValue(now, "googFrameHeightReceived");
- resolution.width = getStatValue(now, "googFrameWidthReceived");
- }
- else if (getStatValue(now, "googFrameHeightSent") &&
- getStatValue(now, "googFrameWidthSent")) {
- resolution.height = getStatValue(now, "googFrameHeightSent");
- resolution.width = getStatValue(now, "googFrameWidthSent");
- }
- }
- catch(e){/*not supported*/}
-
- if(resolution.height && resolution.width)
- {
- jidStats.setSsrcResolution(ssrc, resolution);
- }
- else
- {
- jidStats.setSsrcResolution(ssrc, null);
- }
-
-
- }
-
- var self = this;
- // Jid stats
- var totalPackets = {download: 0, upload: 0};
- var lostPackets = {download: 0, upload: 0};
- var bitrateDownload = 0;
- var bitrateUpload = 0;
- var resolutions = {};
- Object.keys(this.jid2stats).forEach(
- function (jid)
- {
- Object.keys(self.jid2stats[jid].ssrc2Loss).forEach(
- function (ssrc)
- {
- var type = "upload";
- if(self.jid2stats[jid].ssrc2Loss[ssrc].isDownloadStream)
- type = "download";
- totalPackets[type] +=
- self.jid2stats[jid].ssrc2Loss[ssrc].packetsTotal;
- lostPackets[type] +=
- self.jid2stats[jid].ssrc2Loss[ssrc].packetsLost;
- }
- );
- Object.keys(self.jid2stats[jid].ssrc2bitrate).forEach(
- function (ssrc) {
- bitrateDownload +=
- self.jid2stats[jid].ssrc2bitrate[ssrc].download;
- bitrateUpload +=
- self.jid2stats[jid].ssrc2bitrate[ssrc].upload;
-
- delete self.jid2stats[jid].ssrc2bitrate[ssrc];
- }
- );
- resolutions[jid] = self.jid2stats[jid].ssrc2resolution;
- }
- );
-
- PeerStats.bitrate = {"upload": bitrateUpload, "download": bitrateDownload};
-
- PeerStats.packetLoss = {
- total:
- calculatePacketLoss(lostPackets.download + lostPackets.upload,
- totalPackets.download + totalPackets.upload),
- download:
- calculatePacketLoss(lostPackets.download, totalPackets.download),
- upload:
- calculatePacketLoss(lostPackets.upload, totalPackets.upload)
- };
- this.eventEmitter.emit("statistics.connectionstats",
- {
- "bitrate": PeerStats.bitrate,
- "packetLoss": PeerStats.packetLoss,
- "bandwidth": PeerStats.bandwidth,
- "resolution": resolutions,
- "transport": PeerStats.transport
- });
- PeerStats.transport = [];
-
-};
-
-/**
- * Stats processing logic.
- */
-StatsCollector.prototype.processAudioLevelReport = function ()
-{
- if (!this.baselineAudioLevelsReport)
- {
- return;
- }
-
- for (var idx in this.currentAudioLevelsReport)
- {
- var now = this.currentAudioLevelsReport[idx];
-
- if (now.type != 'ssrc')
- {
- continue;
- }
-
- var before = this.baselineAudioLevelsReport[idx];
- if (!before)
- {
- console.warn(getStatValue(now, 'ssrc') + ' not enough data');
- continue;
- }
-
- var ssrc = getStatValue(now, 'ssrc');
- var jid = APP.xmpp.getJidFromSSRC(ssrc);
- if (!jid)
- {
- if((Date.now() - now.timestamp) < 3000)
- console.warn("No jid for ssrc: " + ssrc);
- continue;
- }
-
- var jidStats = this.jid2stats[jid];
- if (!jidStats)
- {
- jidStats = new PeerStats();
- this.jid2stats[jid] = jidStats;
- }
-
- // Audio level
- var audioLevel = null;
-
- try {
- audioLevel = getStatValue(now, 'audioInputLevel');
- if (!audioLevel)
- audioLevel = getStatValue(now, 'audioOutputLevel');
- }
- catch(e) {/*not supported*/
- console.warn("Audio Levels are not available in the statistics.");
- clearInterval(this.audioLevelsIntervalId);
- return;
- }
-
- if (audioLevel)
- {
- // TODO: can't find specs about what this value really is,
- // but it seems to vary between 0 and around 32k.
- audioLevel = audioLevel / 32767;
- jidStats.setSsrcAudioLevel(ssrc, audioLevel);
- if(jid != APP.xmpp.myJid())
- this.eventEmitter.emit("statistics.audioLevel", jid, audioLevel);
- }
-
- }
-
-
-};
-
-},{"../../service/RTC/RTCBrowserType":101}],48:[function(require,module,exports){
-var callStats = null;
-
-function initCallback (err, msg) {
- console.log("Initializing Status: err="+err+" msg="+msg);
-}
-
-var CallStats = {
- init: function (jingleSession) {
-
- if(!config.callStatsID || !config.callStatsSecret || callStats !== null)
- return;
-
- callStats = new callstats($,io,jsSHA);
-
- this.session = jingleSession;
- this.peerconnection = jingleSession.peerconnection.peerconnection;
-
- this.userID = APP.xmpp.myResource();
-
- var roomJid = APP.UI.getRoomName();
- this.confID = roomJid? Strophe.getNodeFromJid(roomJid) : null;
-
- //userID is generated or given by the origin server
- callStats.initialize(config.callStatsID,
- config.callStatsSecret,
- this.userID,
- initCallback);
-
- var usage = callStats.fabricUsage.unbundled;
- if(config.useBundle)
- usage = callStats.fabricUsage.multiplex;
-
- callStats.addNewFabric(this.peerconnection,
- Strophe.getResourceFromJid(jingleSession.peerjid),
- usage,
- this.confID,
- this.pcCallback.bind(this));
- },
- pcCallback: function (err, msg) {
- if(!callStats)
- return;
- console.log("Monitoring status: "+ err + " msg: " + msg);
- callStats.sendFabricEvent(this.peerconnection,
- callStats.fabricEvent.fabricSetup, this.confID);
- },
- sendMuteEvent: function (mute, type) {
- if(!callStats)
- return;
- var event = null;
- if(type === "video")
- {
- event = (mute? callStats.fabricEvent.videoPause :
- callStats.fabricEvent.videoResume);
- }
- else
- {
- event = (mute? callStats.fabricEvent.audioMute :
- callStats.fabricEvent.audioUnmute);
- }
- callStats.sendFabricEvent(this.peerconnection, event, this.confID);
- },
- sendTerminateEvent: function () {
- if(!callStats)
- return;
- callStats.sendFabricEvent(this.peerconnection,
- callStats.fabricEvent.fabricTerminated, this.confID);
- },
- sendSetupFailedEvent: function () {
- if(!callStats)
- return;
- callStats.sendFabricEvent(this.peerconnection,
- callStats.fabricEvent.fabricSetupFailed, this.confID);
- }
-
-};
-module.exports = CallStats;
},{}],49:[function(require,module,exports){
-/**
- * Created by hristo on 8/4/14.
- */
-var LocalStats = require("./LocalStatsCollector.js");
-var RTPStats = require("./RTPStatsCollector.js");
-var EventEmitter = require("events");
-var StreamEventTypes = require("../../service/RTC/StreamEventTypes.js");
-var XMPPEvents = require("../../service/xmpp/XMPPEvents");
-var CallStats = require("./callstats");
-var RTCEvents = require("../../service/RTC/RTCEvents");
-
-var eventEmitter = new EventEmitter();
-
-var localStats = null;
-
-var rtpStats = null;
-
-function stopLocal()
-{
- if(localStats)
- {
- localStats.stop();
- localStats = null;
- }
-}
-
-function stopRemote()
-{
- if(rtpStats)
- {
- rtpStats.stop();
- eventEmitter.emit("statistics.stop");
- rtpStats = null;
- }
-}
-
-function startRemoteStats (peerconnection) {
- if(rtpStats)
- {
- rtpStats.stop();
- rtpStats = null;
- }
-
- rtpStats = new RTPStats(peerconnection, 200, 2000, eventEmitter);
- rtpStats.start();
-}
-
-function onStreamCreated(stream)
-{
- if(stream.getOriginalStream().getAudioTracks().length === 0)
- return;
-
- localStats = new LocalStats(stream.getOriginalStream(), 200, statistics,
- eventEmitter);
- localStats.start();
-}
-
-function onDisposeConference(onUnload) {
- CallStats.sendTerminateEvent();
- stopRemote();
- if(onUnload) {
- stopLocal();
- eventEmitter.removeAllListeners();
- }
-}
-
-
-var statistics =
-{
- /**
- * Indicates that this audio level is for local jid.
- * @type {string}
- */
- LOCAL_JID: 'local',
-
- addAudioLevelListener: function(listener)
- {
- eventEmitter.on("statistics.audioLevel", listener);
- },
-
- removeAudioLevelListener: function(listener)
- {
- eventEmitter.removeListener("statistics.audioLevel", listener);
- },
-
- addConnectionStatsListener: function(listener)
- {
- eventEmitter.on("statistics.connectionstats", listener);
- },
-
- removeConnectionStatsListener: function(listener)
- {
- eventEmitter.removeListener("statistics.connectionstats", listener);
- },
-
-
- addRemoteStatsStopListener: function(listener)
- {
- eventEmitter.on("statistics.stop", listener);
- },
-
- removeRemoteStatsStopListener: function(listener)
- {
- eventEmitter.removeListener("statistics.stop", listener);
- },
-
- stop: function () {
- stopLocal();
- stopRemote();
- if(eventEmitter)
- {
- eventEmitter.removeAllListeners();
- }
- },
-
- stopRemoteStatistics: function()
- {
- stopRemote();
- },
-
- start: function () {
- APP.RTC.addStreamListener(onStreamCreated,
- StreamEventTypes.EVENT_TYPE_LOCAL_CREATED);
- APP.xmpp.addListener(XMPPEvents.DISPOSE_CONFERENCE, onDisposeConference);
- //FIXME: we may want to change CALL INCOMING event to onnegotiationneeded
- APP.xmpp.addListener(XMPPEvents.CALL_INCOMING, function (event) {
- startRemoteStats(event.peerconnection);
-// CallStats.init(event);
- });
- APP.xmpp.addListener(XMPPEvents.PEERCONNECTION_READY, function (session) {
- CallStats.init(session);
- })
- APP.RTC.addListener(RTCEvents.AUDIO_MUTE, function (mute) {
- CallStats.sendMuteEvent(mute, "audio");
- });
- APP.xmpp.addListener(XMPPEvents.CONFERENCE_SETUP_FAILED, function () {
- CallStats.sendSetupFailedEvent();
- })
- APP.RTC.addListener(RTCEvents.VIDEO_MUTE, function (mute) {
- CallStats.sendMuteEvent(mute, "video");
- });
- }
-
-};
-
-
-
+/* global ssrc2jid */
+/* jshint -W117 */
+var RTCBrowserType = require("../../service/RTC/RTCBrowserType");
+
+
+/**
+ * Calculates packet lost percent using the number of lost packets and the
+ * number of all packet.
+ * @param lostPackets the number of lost packets
+ * @param totalPackets the number of all packets.
+ * @returns {number} packet loss percent
+ */
+function calculatePacketLoss(lostPackets, totalPackets) {
+ if(!totalPackets || totalPackets <= 0 || !lostPackets || lostPackets <= 0)
+ return 0;
+ return Math.round((lostPackets/totalPackets)*100);
+}
+
+function getStatValue(item, name) {
+ if(!keyMap[APP.RTC.getBrowserType()][name])
+ throw "The property isn't supported!";
+ var key = keyMap[APP.RTC.getBrowserType()][name];
+ return APP.RTC.getBrowserType() == RTCBrowserType.RTC_BROWSER_CHROME? item.stat(key) : item[key];
+}
+
+/**
+ * Peer statistics data holder.
+ * @constructor
+ */
+function PeerStats()
+{
+ this.ssrc2Loss = {};
+ this.ssrc2AudioLevel = {};
+ this.ssrc2bitrate = {};
+ this.ssrc2resolution = {};
+}
+
+/**
+ * The bandwidth
+ * @type {{}}
+ */
+PeerStats.bandwidth = {};
+
+/**
+ * The bit rate
+ * @type {{}}
+ */
+PeerStats.bitrate = {};
+
+
+
+/**
+ * The packet loss rate
+ * @type {{}}
+ */
+PeerStats.packetLoss = null;
+
+/**
+ * Sets packets loss rate for given ssrc that blong to the peer
+ * represented by this instance.
+ * @param ssrc audio or video RTP stream SSRC.
+ * @param lossRate new packet loss rate value to be set.
+ */
+PeerStats.prototype.setSsrcLoss = function (ssrc, lossRate)
+{
+ this.ssrc2Loss[ssrc] = lossRate;
+};
+
+/**
+ * Sets resolution for given ssrc that belong to the peer
+ * represented by this instance.
+ * @param ssrc audio or video RTP stream SSRC.
+ * @param resolution new resolution value to be set.
+ */
+PeerStats.prototype.setSsrcResolution = function (ssrc, resolution)
+{
+ if(resolution === null && this.ssrc2resolution[ssrc])
+ {
+ delete this.ssrc2resolution[ssrc];
+ }
+ else if(resolution !== null)
+ this.ssrc2resolution[ssrc] = resolution;
+};
+
+/**
+ * Sets the bit rate for given ssrc that blong to the peer
+ * represented by this instance.
+ * @param ssrc audio or video RTP stream SSRC.
+ * @param bitrate new bitrate value to be set.
+ */
+PeerStats.prototype.setSsrcBitrate = function (ssrc, bitrate)
+{
+ if(this.ssrc2bitrate[ssrc])
+ {
+ this.ssrc2bitrate[ssrc].download += bitrate.download;
+ this.ssrc2bitrate[ssrc].upload += bitrate.upload;
+ }
+ else {
+ this.ssrc2bitrate[ssrc] = bitrate;
+ }
+};
+
+/**
+ * Sets new audio level(input or output) for given ssrc that identifies
+ * the stream which belongs to the peer represented by this instance.
+ * @param ssrc RTP stream SSRC for which current audio level value will be
+ * updated.
+ * @param audioLevel the new audio level value to be set. Value is truncated to
+ * fit the range from 0 to 1.
+ */
+PeerStats.prototype.setSsrcAudioLevel = function (ssrc, audioLevel)
+{
+ // Range limit 0 - 1
+ this.ssrc2AudioLevel[ssrc] = formatAudioLevel(audioLevel);
+};
+
+function formatAudioLevel(audioLevel) {
+ return Math.min(Math.max(audioLevel, 0), 1);
+}
+
+/**
+ * Array with the transport information.
+ * @type {Array}
+ */
+PeerStats.transport = [];
+
+
+/**
+ * StatsCollector registers for stats updates of given
+ * peerconnection in given interval . On each update particular
+ * stats are extracted and put in {@link PeerStats} objects. Once the processing
+ * is done audioLevelsUpdateCallback is called with this
+ * instance as an event source.
+ *
+ * @param peerconnection webRTC peer connection object.
+ * @param interval stats refresh interval given in ms.
+ * @param {function(StatsCollector)} audioLevelsUpdateCallback the callback
+ * called on stats update.
+ * @constructor
+ */
+function StatsCollector(peerconnection, audioLevelsInterval, statsInterval, eventEmitter)
+{
+ this.peerconnection = peerconnection;
+ this.baselineAudioLevelsReport = null;
+ this.currentAudioLevelsReport = null;
+ this.currentStatsReport = null;
+ this.baselineStatsReport = null;
+ this.audioLevelsIntervalId = null;
+ this.eventEmitter = eventEmitter;
+
+ /**
+ * Gather PeerConnection stats once every this many milliseconds.
+ */
+ this.GATHER_INTERVAL = 15000;
+
+ /**
+ * Log stats via the focus once every this many milliseconds.
+ */
+ this.LOG_INTERVAL = 60000;
+
+ /**
+ * Gather stats and store them in this.statsToBeLogged.
+ */
+ this.gatherStatsIntervalId = null;
+
+ /**
+ * Send the stats already saved in this.statsToBeLogged to be logged via
+ * the focus.
+ */
+ this.logStatsIntervalId = null;
+
+ /**
+ * Stores the statistics which will be send to the focus to be logged.
+ */
+ this.statsToBeLogged =
+ {
+ timestamps: [],
+ stats: {}
+ };
+
+ // Updates stats interval
+ this.audioLevelsIntervalMilis = audioLevelsInterval;
+
+ this.statsIntervalId = null;
+ this.statsIntervalMilis = statsInterval;
+ // Map of jids to PeerStats
+ this.jid2stats = {};
+}
+
+module.exports = StatsCollector;
+
+/**
+ * Stops stats updates.
+ */
+StatsCollector.prototype.stop = function () {
+ if (this.audioLevelsIntervalId) {
+ clearInterval(this.audioLevelsIntervalId);
+ this.audioLevelsIntervalId = null;
+ }
+
+ if (this.statsIntervalId)
+ {
+ clearInterval(this.statsIntervalId);
+ this.statsIntervalId = null;
+ }
+
+ if(this.logStatsIntervalId)
+ {
+ clearInterval(this.logStatsIntervalId);
+ this.logStatsIntervalId = null;
+ }
+
+ if(this.gatherStatsIntervalId)
+ {
+ clearInterval(this.gatherStatsIntervalId);
+ this.gatherStatsIntervalId = null;
+ }
+};
+
+/**
+ * Callback passed to getStats method.
+ * @param error an error that occurred on getStats call.
+ */
+StatsCollector.prototype.errorCallback = function (error)
+{
+ console.error("Get stats error", error);
+ this.stop();
+};
+
+/**
+ * Starts stats updates.
+ */
+StatsCollector.prototype.start = function ()
+{
+ var self = this;
+ if(!config.disableAudioLevels) {
+ this.audioLevelsIntervalId = setInterval(
+ function () {
+ // Interval updates
+ self.peerconnection.getStats(
+ function (report) {
+ var results = null;
+ if (!report || !report.result ||
+ typeof report.result != 'function') {
+ results = report;
+ }
+ else {
+ results = report.result();
+ }
+ //console.error("Got interval report", results);
+ self.currentAudioLevelsReport = results;
+ self.processAudioLevelReport();
+ self.baselineAudioLevelsReport =
+ self.currentAudioLevelsReport;
+ },
+ self.errorCallback
+ );
+ },
+ self.audioLevelsIntervalMilis
+ );
+ }
+
+ if(!config.disableStats && !navigator.mozGetUserMedia) {
+ this.statsIntervalId = setInterval(
+ function () {
+ // Interval updates
+ self.peerconnection.getStats(
+ function (report) {
+ var results = null;
+ if (!report || !report.result ||
+ typeof report.result != 'function') {
+ //firefox
+ results = report;
+ }
+ else {
+ //chrome
+ results = report.result();
+ }
+ //console.error("Got interval report", results);
+ self.currentStatsReport = results;
+ try {
+ self.processStatsReport();
+ }
+ catch (e) {
+ console.error("Unsupported key:" + e, e);
+ }
+
+ self.baselineStatsReport = self.currentStatsReport;
+ },
+ self.errorCallback
+ );
+ },
+ self.statsIntervalMilis
+ );
+ }
+
+ if (config.logStats && !navigator.mozGetUserMedia) {
+ this.gatherStatsIntervalId = setInterval(
+ function () {
+ self.peerconnection.getStats(
+ function (report) {
+ self.addStatsToBeLogged(report.result());
+ },
+ function () {
+ }
+ );
+ },
+ this.GATHER_INTERVAL
+ );
+
+ this.logStatsIntervalId = setInterval(
+ function() { self.logStats(); },
+ this.LOG_INTERVAL);
+ }
+};
+
+/**
+ * Checks whether a certain record should be included in the logged statistics.
+ */
+function acceptStat(reportId, reportType, statName) {
+ if (reportType == "googCandidatePair" && statName == "googChannelId")
+ return false;
+
+ if (reportType == "ssrc") {
+ if (statName == "googTrackId" ||
+ statName == "transportId" ||
+ statName == "ssrc")
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Checks whether a certain record should be included in the logged statistics.
+ */
+function acceptReport(id, type) {
+ if (id.substring(0, 15) == "googCertificate" ||
+ id.substring(0, 9) == "googTrack" ||
+ id.substring(0, 20) == "googLibjingleSession")
+ return false;
+
+ if (type == "googComponent")
+ return false;
+
+ return true;
+}
+
+/**
+ * Converts the stats to the format used for logging, and saves the data in
+ * this.statsToBeLogged.
+ * @param reports Reports as given by webkitRTCPerConnection.getStats.
+ */
+StatsCollector.prototype.addStatsToBeLogged = function (reports) {
+ var self = this;
+ var num_records = this.statsToBeLogged.timestamps.length;
+ this.statsToBeLogged.timestamps.push(new Date().getTime());
+ reports.map(function (report) {
+ if (!acceptReport(report.id, report.type))
+ return;
+ var stat = self.statsToBeLogged.stats[report.id];
+ if (!stat) {
+ stat = self.statsToBeLogged.stats[report.id] = {};
+ }
+ stat.type = report.type;
+ report.names().map(function (name) {
+ if (!acceptStat(report.id, report.type, name))
+ return;
+ var values = stat[name];
+ if (!values) {
+ values = stat[name] = [];
+ }
+ while (values.length < num_records) {
+ values.push(null);
+ }
+ values.push(report.stat(name));
+ });
+ });
+};
+
+StatsCollector.prototype.logStats = function () {
+
+ if(!APP.xmpp.sendLogs(this.statsToBeLogged))
+ return;
+ // Reset the stats
+ this.statsToBeLogged.stats = {};
+ this.statsToBeLogged.timestamps = [];
+};
+var keyMap = {};
+keyMap[RTCBrowserType.RTC_BROWSER_FIREFOX] = {
+ "ssrc": "ssrc",
+ "packetsReceived": "packetsReceived",
+ "packetsLost": "packetsLost",
+ "packetsSent": "packetsSent",
+ "bytesReceived": "bytesReceived",
+ "bytesSent": "bytesSent"
+};
+keyMap[RTCBrowserType.RTC_BROWSER_CHROME] = {
+ "receiveBandwidth": "googAvailableReceiveBandwidth",
+ "sendBandwidth": "googAvailableSendBandwidth",
+ "remoteAddress": "googRemoteAddress",
+ "transportType": "googTransportType",
+ "localAddress": "googLocalAddress",
+ "activeConnection": "googActiveConnection",
+ "ssrc": "ssrc",
+ "packetsReceived": "packetsReceived",
+ "packetsSent": "packetsSent",
+ "packetsLost": "packetsLost",
+ "bytesReceived": "bytesReceived",
+ "bytesSent": "bytesSent",
+ "googFrameHeightReceived": "googFrameHeightReceived",
+ "googFrameWidthReceived": "googFrameWidthReceived",
+ "googFrameHeightSent": "googFrameHeightSent",
+ "googFrameWidthSent": "googFrameWidthSent",
+ "audioInputLevel": "audioInputLevel",
+ "audioOutputLevel": "audioOutputLevel"
+};
+
+
+/**
+ * Stats processing logic.
+ */
+StatsCollector.prototype.processStatsReport = function () {
+ if (!this.baselineStatsReport) {
+ return;
+ }
+
+ for (var idx in this.currentStatsReport) {
+ var now = this.currentStatsReport[idx];
+ try {
+ if (getStatValue(now, 'receiveBandwidth') ||
+ getStatValue(now, 'sendBandwidth')) {
+ PeerStats.bandwidth = {
+ "download": Math.round(
+ (getStatValue(now, 'receiveBandwidth')) / 1000),
+ "upload": Math.round(
+ (getStatValue(now, 'sendBandwidth')) / 1000)
+ };
+ }
+ }
+ catch(e){/*not supported*/}
+
+ if(now.type == 'googCandidatePair')
+ {
+ var ip, type, localIP, active;
+ try {
+ ip = getStatValue(now, 'remoteAddress');
+ type = getStatValue(now, "transportType");
+ localIP = getStatValue(now, "localAddress");
+ active = getStatValue(now, "activeConnection");
+ }
+ catch(e){/*not supported*/}
+ if(!ip || !type || !localIP || active != "true")
+ continue;
+ var addressSaved = false;
+ for(var i = 0; i < PeerStats.transport.length; i++)
+ {
+ if(PeerStats.transport[i].ip == ip &&
+ PeerStats.transport[i].type == type &&
+ PeerStats.transport[i].localip == localIP)
+ {
+ addressSaved = true;
+ }
+ }
+ if(addressSaved)
+ continue;
+ PeerStats.transport.push({localip: localIP, ip: ip, type: type});
+ continue;
+ }
+
+ if(now.type == "candidatepair")
+ {
+ if(now.state == "succeeded")
+ continue;
+
+ var local = this.currentStatsReport[now.localCandidateId];
+ var remote = this.currentStatsReport[now.remoteCandidateId];
+ PeerStats.transport.push({localip: local.ipAddress + ":" + local.portNumber,
+ ip: remote.ipAddress + ":" + remote.portNumber, type: local.transport});
+
+ }
+
+ if (now.type != 'ssrc' && now.type != "outboundrtp" &&
+ now.type != "inboundrtp") {
+ continue;
+ }
+
+ var before = this.baselineStatsReport[idx];
+ if (!before) {
+ console.warn(getStatValue(now, 'ssrc') + ' not enough data');
+ continue;
+ }
+
+ var ssrc = getStatValue(now, 'ssrc');
+ if(!ssrc)
+ continue;
+ var jid = APP.xmpp.getJidFromSSRC(ssrc);
+ if (!jid && (Date.now() - now.timestamp) < 3000) {
+ console.warn("No jid for ssrc: " + ssrc);
+ continue;
+ }
+
+ var jidStats = this.jid2stats[jid];
+ if (!jidStats) {
+ jidStats = new PeerStats();
+ this.jid2stats[jid] = jidStats;
+ }
+
+
+ var isDownloadStream = true;
+ var key = 'packetsReceived';
+ if (!getStatValue(now, key))
+ {
+ isDownloadStream = false;
+ key = 'packetsSent';
+ if (!getStatValue(now, key))
+ {
+ console.warn("No packetsReceived nor packetSent stat found");
+ continue;
+ }
+ }
+ var packetsNow = getStatValue(now, key);
+ if(!packetsNow || packetsNow < 0)
+ packetsNow = 0;
+
+ var packetsBefore = getStatValue(before, key);
+ if(!packetsBefore || packetsBefore < 0)
+ packetsBefore = 0;
+ var packetRate = packetsNow - packetsBefore;
+ if(!packetRate || packetRate < 0)
+ packetRate = 0;
+ var currentLoss = getStatValue(now, 'packetsLost');
+ if(!currentLoss || currentLoss < 0)
+ currentLoss = 0;
+ var previousLoss = getStatValue(before, 'packetsLost');
+ if(!previousLoss || previousLoss < 0)
+ previousLoss = 0;
+ var lossRate = currentLoss - previousLoss;
+ if(!lossRate || lossRate < 0)
+ lossRate = 0;
+ var packetsTotal = (packetRate + lossRate);
+
+ jidStats.setSsrcLoss(ssrc,
+ {"packetsTotal": packetsTotal,
+ "packetsLost": lossRate,
+ "isDownloadStream": isDownloadStream});
+
+
+ var bytesReceived = 0, bytesSent = 0;
+ if(getStatValue(now, "bytesReceived"))
+ {
+ bytesReceived = getStatValue(now, "bytesReceived") -
+ getStatValue(before, "bytesReceived");
+ }
+
+ if(getStatValue(now, "bytesSent"))
+ {
+ bytesSent = getStatValue(now, "bytesSent") -
+ getStatValue(before, "bytesSent");
+ }
+
+ var time = Math.round((now.timestamp - before.timestamp) / 1000);
+ if(bytesReceived <= 0 || time <= 0)
+ {
+ bytesReceived = 0;
+ }
+ else
+ {
+ bytesReceived = Math.round(((bytesReceived * 8) / time) / 1000);
+ }
+
+ if(bytesSent <= 0 || time <= 0)
+ {
+ bytesSent = 0;
+ }
+ else
+ {
+ bytesSent = Math.round(((bytesSent * 8) / time) / 1000);
+ }
+
+ jidStats.setSsrcBitrate(ssrc, {
+ "download": bytesReceived,
+ "upload": bytesSent});
+
+ var resolution = {height: null, width: null};
+ try {
+ if (getStatValue(now, "googFrameHeightReceived") &&
+ getStatValue(now, "googFrameWidthReceived")) {
+ resolution.height = getStatValue(now, "googFrameHeightReceived");
+ resolution.width = getStatValue(now, "googFrameWidthReceived");
+ }
+ else if (getStatValue(now, "googFrameHeightSent") &&
+ getStatValue(now, "googFrameWidthSent")) {
+ resolution.height = getStatValue(now, "googFrameHeightSent");
+ resolution.width = getStatValue(now, "googFrameWidthSent");
+ }
+ }
+ catch(e){/*not supported*/}
+
+ if(resolution.height && resolution.width)
+ {
+ jidStats.setSsrcResolution(ssrc, resolution);
+ }
+ else
+ {
+ jidStats.setSsrcResolution(ssrc, null);
+ }
+
+
+ }
+
+ var self = this;
+ // Jid stats
+ var totalPackets = {download: 0, upload: 0};
+ var lostPackets = {download: 0, upload: 0};
+ var bitrateDownload = 0;
+ var bitrateUpload = 0;
+ var resolutions = {};
+ Object.keys(this.jid2stats).forEach(
+ function (jid)
+ {
+ Object.keys(self.jid2stats[jid].ssrc2Loss).forEach(
+ function (ssrc)
+ {
+ var type = "upload";
+ if(self.jid2stats[jid].ssrc2Loss[ssrc].isDownloadStream)
+ type = "download";
+ totalPackets[type] +=
+ self.jid2stats[jid].ssrc2Loss[ssrc].packetsTotal;
+ lostPackets[type] +=
+ self.jid2stats[jid].ssrc2Loss[ssrc].packetsLost;
+ }
+ );
+ Object.keys(self.jid2stats[jid].ssrc2bitrate).forEach(
+ function (ssrc) {
+ bitrateDownload +=
+ self.jid2stats[jid].ssrc2bitrate[ssrc].download;
+ bitrateUpload +=
+ self.jid2stats[jid].ssrc2bitrate[ssrc].upload;
+
+ delete self.jid2stats[jid].ssrc2bitrate[ssrc];
+ }
+ );
+ resolutions[jid] = self.jid2stats[jid].ssrc2resolution;
+ }
+ );
+
+ PeerStats.bitrate = {"upload": bitrateUpload, "download": bitrateDownload};
+
+ PeerStats.packetLoss = {
+ total:
+ calculatePacketLoss(lostPackets.download + lostPackets.upload,
+ totalPackets.download + totalPackets.upload),
+ download:
+ calculatePacketLoss(lostPackets.download, totalPackets.download),
+ upload:
+ calculatePacketLoss(lostPackets.upload, totalPackets.upload)
+ };
+ this.eventEmitter.emit("statistics.connectionstats",
+ {
+ "bitrate": PeerStats.bitrate,
+ "packetLoss": PeerStats.packetLoss,
+ "bandwidth": PeerStats.bandwidth,
+ "resolution": resolutions,
+ "transport": PeerStats.transport
+ });
+ PeerStats.transport = [];
+
+};
+
+/**
+ * Stats processing logic.
+ */
+StatsCollector.prototype.processAudioLevelReport = function ()
+{
+ if (!this.baselineAudioLevelsReport)
+ {
+ return;
+ }
+
+ for (var idx in this.currentAudioLevelsReport)
+ {
+ var now = this.currentAudioLevelsReport[idx];
+
+ if (now.type != 'ssrc')
+ {
+ continue;
+ }
+
+ var before = this.baselineAudioLevelsReport[idx];
+ if (!before)
+ {
+ console.warn(getStatValue(now, 'ssrc') + ' not enough data');
+ continue;
+ }
+
+ var ssrc = getStatValue(now, 'ssrc');
+ var jid = APP.xmpp.getJidFromSSRC(ssrc);
+ if (!jid)
+ {
+ if((Date.now() - now.timestamp) < 3000)
+ console.warn("No jid for ssrc: " + ssrc);
+ continue;
+ }
+
+ var jidStats = this.jid2stats[jid];
+ if (!jidStats)
+ {
+ jidStats = new PeerStats();
+ this.jid2stats[jid] = jidStats;
+ }
+
+ // Audio level
+ var audioLevel = null;
+
+ try {
+ audioLevel = getStatValue(now, 'audioInputLevel');
+ if (!audioLevel)
+ audioLevel = getStatValue(now, 'audioOutputLevel');
+ }
+ catch(e) {/*not supported*/
+ console.warn("Audio Levels are not available in the statistics.");
+ clearInterval(this.audioLevelsIntervalId);
+ return;
+ }
+
+ if (audioLevel)
+ {
+ // TODO: can't find specs about what this value really is,
+ // but it seems to vary between 0 and around 32k.
+ audioLevel = audioLevel / 32767;
+ jidStats.setSsrcAudioLevel(ssrc, audioLevel);
+ if(jid != APP.xmpp.myJid())
+ this.eventEmitter.emit("statistics.audioLevel", jid, audioLevel);
+ }
+
+ }
+
+
+};
+},{"../../service/RTC/RTCBrowserType":99}],50:[function(require,module,exports){
+var callStats = null;
+
+function initCallback (err, msg) {
+ console.log("Initializing Status: err="+err+" msg="+msg);
+}
+
+var CallStats = {
+ init: function (jingleSession) {
+
+ if(!config.callStatsID || !config.callStatsSecret || callStats !== null)
+ return;
+
+ callStats = new callstats($,io,jsSHA);
+
+ this.session = jingleSession;
+ this.peerconnection = jingleSession.peerconnection.peerconnection;
+
+ this.userID = APP.xmpp.myResource();
+
+ var roomJid = APP.UI.getRoomName();
+ this.confID = roomJid? Strophe.getNodeFromJid(roomJid) : null;
+
+ //userID is generated or given by the origin server
+ callStats.initialize(config.callStatsID,
+ config.callStatsSecret,
+ this.userID,
+ initCallback);
+
+ var usage = callStats.fabricUsage.unbundled;
+ if(config.useBundle)
+ usage = callStats.fabricUsage.multiplex;
+
+ callStats.addNewFabric(this.peerconnection,
+ Strophe.getResourceFromJid(jingleSession.peerjid),
+ usage,
+ this.confID,
+ this.pcCallback.bind(this));
+ },
+ pcCallback: function (err, msg) {
+ if(!callStats)
+ return;
+ console.log("Monitoring status: "+ err + " msg: " + msg);
+ callStats.sendFabricEvent(this.peerconnection,
+ callStats.fabricEvent.fabricSetup, this.confID);
+ },
+ sendMuteEvent: function (mute, type) {
+ if(!callStats)
+ return;
+ var event = null;
+ if(type === "video")
+ {
+ event = (mute? callStats.fabricEvent.videoPause :
+ callStats.fabricEvent.videoResume);
+ }
+ else
+ {
+ event = (mute? callStats.fabricEvent.audioMute :
+ callStats.fabricEvent.audioUnmute);
+ }
+ callStats.sendFabricEvent(this.peerconnection, event, this.confID);
+ },
+ sendTerminateEvent: function () {
+ if(!callStats)
+ return;
+ callStats.sendFabricEvent(this.peerconnection,
+ callStats.fabricEvent.fabricTerminated, this.confID);
+ },
+ sendSetupFailedEvent: function () {
+ if(!callStats)
+ return;
+ callStats.sendFabricEvent(this.peerconnection,
+ callStats.fabricEvent.fabricSetupFailed, this.confID);
+ }
+
+};
+module.exports = CallStats;
+},{}],51:[function(require,module,exports){
+/**
+ * Created by hristo on 8/4/14.
+ */
+var LocalStats = require("./LocalStatsCollector.js");
+var RTPStats = require("./RTPStatsCollector.js");
+var EventEmitter = require("events");
+var StreamEventTypes = require("../../service/RTC/StreamEventTypes.js");
+var XMPPEvents = require("../../service/xmpp/XMPPEvents");
+var CallStats = require("./callstats");
+var RTCEvents = require("../../service/RTC/RTCEvents");
+
+var eventEmitter = new EventEmitter();
+
+var localStats = null;
+
+var rtpStats = null;
+
+function stopLocal()
+{
+ if(localStats)
+ {
+ localStats.stop();
+ localStats = null;
+ }
+}
+
+function stopRemote()
+{
+ if(rtpStats)
+ {
+ rtpStats.stop();
+ eventEmitter.emit("statistics.stop");
+ rtpStats = null;
+ }
+}
+
+function startRemoteStats (peerconnection) {
+ if(rtpStats)
+ {
+ rtpStats.stop();
+ rtpStats = null;
+ }
+
+ rtpStats = new RTPStats(peerconnection, 200, 2000, eventEmitter);
+ rtpStats.start();
+}
+
+function onStreamCreated(stream)
+{
+ if(stream.getOriginalStream().getAudioTracks().length === 0)
+ return;
+
+ localStats = new LocalStats(stream.getOriginalStream(), 200, statistics,
+ eventEmitter);
+ localStats.start();
+}
+
+function onDisposeConference(onUnload) {
+ CallStats.sendTerminateEvent();
+ stopRemote();
+ if(onUnload) {
+ stopLocal();
+ eventEmitter.removeAllListeners();
+ }
+}
+
+
+var statistics =
+{
+ /**
+ * Indicates that this audio level is for local jid.
+ * @type {string}
+ */
+ LOCAL_JID: 'local',
+
+ addAudioLevelListener: function(listener)
+ {
+ eventEmitter.on("statistics.audioLevel", listener);
+ },
+
+ removeAudioLevelListener: function(listener)
+ {
+ eventEmitter.removeListener("statistics.audioLevel", listener);
+ },
+
+ addConnectionStatsListener: function(listener)
+ {
+ eventEmitter.on("statistics.connectionstats", listener);
+ },
+
+ removeConnectionStatsListener: function(listener)
+ {
+ eventEmitter.removeListener("statistics.connectionstats", listener);
+ },
+
+
+ addRemoteStatsStopListener: function(listener)
+ {
+ eventEmitter.on("statistics.stop", listener);
+ },
+
+ removeRemoteStatsStopListener: function(listener)
+ {
+ eventEmitter.removeListener("statistics.stop", listener);
+ },
+
+ stop: function () {
+ stopLocal();
+ stopRemote();
+ if(eventEmitter)
+ {
+ eventEmitter.removeAllListeners();
+ }
+ },
+
+ stopRemoteStatistics: function()
+ {
+ stopRemote();
+ },
+
+ start: function () {
+ APP.RTC.addStreamListener(onStreamCreated,
+ StreamEventTypes.EVENT_TYPE_LOCAL_CREATED);
+ APP.xmpp.addListener(XMPPEvents.DISPOSE_CONFERENCE, onDisposeConference);
+ //FIXME: we may want to change CALL INCOMING event to onnegotiationneeded
+ APP.xmpp.addListener(XMPPEvents.CALL_INCOMING, function (event) {
+ startRemoteStats(event.peerconnection);
+// CallStats.init(event);
+ });
+ APP.xmpp.addListener(XMPPEvents.PEERCONNECTION_READY, function (session) {
+ CallStats.init(session);
+ })
+ APP.RTC.addListener(RTCEvents.AUDIO_MUTE, function (mute) {
+ CallStats.sendMuteEvent(mute, "audio");
+ });
+ APP.xmpp.addListener(XMPPEvents.CONFERENCE_SETUP_FAILED, function () {
+ CallStats.sendSetupFailedEvent();
+ })
+ APP.RTC.addListener(RTCEvents.VIDEO_MUTE, function (mute) {
+ CallStats.sendMuteEvent(mute, "video");
+ });
+ }
+
+};
+
+
+
+
module.exports = statistics;
-},{"../../service/RTC/RTCEvents":102,"../../service/RTC/StreamEventTypes.js":104,"../../service/xmpp/XMPPEvents":111,"./LocalStatsCollector.js":46,"./RTPStatsCollector.js":47,"./callstats":48,"events":112}],50:[function(require,module,exports){
-var i18n = require("i18next-client");
-var languages = require("../../service/translation/languages");
-var Settings = require("../settings/Settings");
-var DEFAULT_LANG = languages.EN;
-
-i18n.addPostProcessor("resolveAppName", function(value, key, options) {
- return value.replace("__app__", interfaceConfig.APP_NAME);
-});
-
-
-
-var defaultOptions = {
- detectLngQS: "lang",
- useCookie: false,
- fallbackLng: DEFAULT_LANG,
- load: "unspecific",
- resGetPath: 'lang/__ns__-__lng__.json',
- ns: {
- namespaces: ['main', 'languages'],
- defaultNs: 'main'
- },
- lngWhitelist : languages.getLanguages(),
- fallbackOnNull: true,
- fallbackOnEmpty: true,
- useDataAttrOptions: true,
- defaultValueFromContent: false,
- app: interfaceConfig.APP_NAME,
- getAsync: false,
- defaultValueFromContent: false,
- customLoad: function(lng, ns, options, done) {
- var resPath = "lang/__ns__-__lng__.json";
- if(lng === languages.EN)
- resPath = "lang/__ns__.json";
- var url = i18n.functions.applyReplacement(resPath, { lng: lng, ns: ns });
- i18n.functions.ajax({
- url: url,
- success: function(data, status, xhr) {
- i18n.functions.log('loaded: ' + url);
- done(null, data);
- },
- error : function(xhr, status, error) {
- if ((status && status == 200) ||
- (xhr && xhr.status && xhr.status == 200)) {
- // file loaded but invalid json, stop waste time !
- i18n.functions.error('There is a typo in: ' + url);
- } else if ((status && status == 404) ||
- (xhr && xhr.status && xhr.status == 404)) {
- i18n.functions.log('Does not exist: ' + url);
- } else {
- var theStatus = status ? status :
- ((xhr && xhr.status) ? xhr.status : null);
- i18n.functions.log(theStatus + ' when loading ' + url);
- }
-
- done(error, {});
- },
- dataType: "json",
- async : options.getAsync
- });
- }
- // options for caching
-// useLocalStorage: true,
-// localStorageExpirationTime: 86400000 // in ms, default 1 week
-};
-
-function initCompleted(t)
-{
- $("[data-i18n]").i18n();
-}
-
-function checkForParameter() {
- var query = window.location.search.substring(1);
- var vars = query.split("&");
- for (var i=0;i";
- str += this.translateString(key, options);
- str += "";
- return str;
-
- }
-};
-
-},{"../../service/translation/languages":110,"../settings/Settings":45,"i18next-client":66}],51:[function(require,module,exports){
-/* jshint -W117 */
-var TraceablePeerConnection = require("./TraceablePeerConnection");
-var SDPDiffer = require("./SDPDiffer");
-var SDPUtil = require("./SDPUtil");
-var SDP = require("./SDP");
-var async = require("async");
-var transform = require("sdp-transform");
-var XMPPEvents = require("../../service/xmpp/XMPPEvents");
-
-// Jingle stuff
-function JingleSession(me, sid, connection, service, eventEmitter) {
- this.me = me;
- this.sid = sid;
- this.connection = connection;
- this.initiator = null;
- this.responder = null;
- this.isInitiator = null;
- this.peerjid = null;
- this.state = null;
- this.localSDP = null;
- this.remoteSDP = null;
- this.relayedStreams = [];
- this.startTime = null;
- this.stopTime = null;
- this.media_constraints = null;
- this.pc_constraints = null;
- this.ice_config = {};
- this.drip_container = [];
- this.service = service;
-
- this.usetrickle = true;
- this.usepranswer = false; // early transport warmup -- mind you, this might fail. depends on webrtc issue 1718
- this.usedrip = false; // dripping is sending trickle candidates not one-by-one
-
- this.hadstuncandidate = false;
- this.hadturncandidate = false;
- this.lasticecandidate = false;
-
- this.statsinterval = null;
-
- this.reason = null;
-
- this.addssrc = [];
- this.removessrc = [];
- this.pendingop = null;
- this.switchstreams = false;
-
- this.wait = true;
- this.localStreamsSSRC = null;
- this.eventEmitter = eventEmitter;
-
- /**
- * The indicator which determines whether the (local) video has been muted
- * in response to a user command in contrast to an automatic decision made
- * by the application logic.
- */
- this.videoMuteByUser = false;
- this.modifySourcesQueue = async.queue(this._modifySources.bind(this), 1);
- // We start with the queue paused. We resume it when the signaling state is
- // stable and the ice connection state is connected.
- this.modifySourcesQueue.pause();
-}
-
-JingleSession.prototype.updateModifySourcesQueue = function() {
- var signalingState = this.peerconnection.signalingState;
- var iceConnectionState = this.peerconnection.iceConnectionState;
- if (signalingState === 'stable' && iceConnectionState === 'connected') {
- this.modifySourcesQueue.resume();
- } else {
- this.modifySourcesQueue.pause();
- }
-};
-
-JingleSession.prototype.initiate = function (peerjid, isInitiator) {
- var self = this;
- if (this.state !== null) {
- console.error('attempt to initiate on session ' + this.sid +
- 'in state ' + this.state);
- return;
- }
- this.isInitiator = isInitiator;
- this.state = 'pending';
- this.initiator = isInitiator ? this.me : peerjid;
- this.responder = !isInitiator ? this.me : peerjid;
- this.peerjid = peerjid;
- this.hadstuncandidate = false;
- this.hadturncandidate = false;
- this.lasticecandidate = false;
-
- this.peerconnection
- = new TraceablePeerConnection(
- this.connection.jingle.ice_config,
- this.connection.jingle.pc_constraints,
- this);
-
- this.peerconnection.onicecandidate = function (event) {
- self.sendIceCandidate(event.candidate);
- };
- this.peerconnection.onaddstream = function (event) {
- if (event.stream.id !== 'default') {
- console.log("REMOTE STREAM ADDED: " + event.stream + " - " + event.stream.id);
- self.remoteStreamAdded(event);
- } else {
- // This is a recvonly stream. Clients that implement Unified Plan,
- // such as Firefox use recvonly "streams/channels/tracks" for
- // receiving remote stream/tracks, as opposed to Plan B where there
- // are only 3 channels: audio, video and data.
- console.log("RECVONLY REMOTE STREAM IGNORED: " + event.stream + " - " + event.stream.id);
- }
- };
- this.peerconnection.onremovestream = function (event) {
- // Remove the stream from remoteStreams
- // FIXME: remotestreamremoved.jingle not defined anywhere(unused)
- $(document).trigger('remotestreamremoved.jingle', [event, self.sid]);
- };
- this.peerconnection.onsignalingstatechange = function (event) {
- if (!(self && self.peerconnection)) return;
- self.updateModifySourcesQueue();
- };
- this.peerconnection.oniceconnectionstatechange = function (event) {
- if (!(self && self.peerconnection)) return;
- self.updateModifySourcesQueue();
- switch (self.peerconnection.iceConnectionState) {
- case 'connected':
- this.startTime = new Date();
- break;
- case 'disconnected':
- this.stopTime = new Date();
- break;
- case 'failed':
- self.eventEmitter(XMPPEvents.CONFERENCE_SETUP_FAILED);
- break;
- }
- onIceConnectionStateChange(self.sid, self);
- };
- this.peerconnection.onnegotiationneeded = function (event) {
- self.eventEmitter.emit(XMPPEvents.PEERCONNECTION_READY, self);
- };
- // add any local and relayed stream
- APP.RTC.localStreams.forEach(function(stream) {
- self.peerconnection.addStream(stream.getOriginalStream());
- });
- this.relayedStreams.forEach(function(stream) {
- self.peerconnection.addStream(stream);
- });
-};
-
-function onIceConnectionStateChange(sid, session) {
- switch (session.peerconnection.iceConnectionState) {
- case 'checking':
- session.timeChecking = (new Date()).getTime();
- session.firstconnect = true;
- break;
- case 'completed': // on caller side
- case 'connected':
- if (session.firstconnect) {
- session.firstconnect = false;
- var metadata = {};
- metadata.setupTime
- = (new Date()).getTime() - session.timeChecking;
- session.peerconnection.getStats(function (res) {
- if(res && res.result) {
- res.result().forEach(function (report) {
- if (report.type == 'googCandidatePair' &&
- report.stat('googActiveConnection') == 'true') {
- metadata.localCandidateType
- = report.stat('googLocalCandidateType');
- metadata.remoteCandidateType
- = report.stat('googRemoteCandidateType');
-
- // log pair as well so we can get nice pie
- // charts
- metadata.candidatePair
- = report.stat('googLocalCandidateType') +
- ';' +
- report.stat('googRemoteCandidateType');
-
- if (report.stat('googRemoteAddress').indexOf('[') === 0)
- {
- metadata.ipv6 = true;
- }
- }
- });
- }
- });
- }
- break;
- }
-}
-
-JingleSession.prototype.accept = function () {
- var self = this;
- this.state = 'active';
-
- var pranswer = this.peerconnection.localDescription;
- if (!pranswer || pranswer.type != 'pranswer') {
- return;
- }
- console.log('going from pranswer to answer');
- if (this.usetrickle) {
- // remove candidates already sent from session-accept
- var lines = SDPUtil.find_lines(pranswer.sdp, 'a=candidate:');
- for (var i = 0; i < lines.length; i++) {
- pranswer.sdp = pranswer.sdp.replace(lines[i] + '\r\n', '');
- }
- }
- while (SDPUtil.find_line(pranswer.sdp, 'a=inactive')) {
- // FIXME: change any inactive to sendrecv or whatever they were originally
- pranswer.sdp = pranswer.sdp.replace('a=inactive', 'a=sendrecv');
- }
- var prsdp = new SDP(pranswer.sdp);
- var accept = $iq({to: this.peerjid,
- type: 'set'})
- .c('jingle', {xmlns: 'urn:xmpp:jingle:1',
- action: 'session-accept',
- initiator: this.initiator,
- responder: this.responder,
- sid: this.sid });
- prsdp.toJingle(accept, this.initiator == this.me ? 'initiator' : 'responder', this.localStreamsSSRC);
- var sdp = this.peerconnection.localDescription.sdp;
- while (SDPUtil.find_line(sdp, 'a=inactive')) {
- // FIXME: change any inactive to sendrecv or whatever they were originally
- sdp = sdp.replace('a=inactive', 'a=sendrecv');
- }
- var self = this;
- this.peerconnection.setLocalDescription(new RTCSessionDescription({type: 'answer', sdp: sdp}),
- function () {
- //console.log('setLocalDescription success');
- self.setLocalDescription();
-
- self.connection.sendIQ(accept,
- function () {
- var ack = {};
- ack.source = 'answer';
- $(document).trigger('ack.jingle', [self.sid, ack]);
- },
- function (stanza) {
- var error = ($(stanza).find('error').length) ? {
- code: $(stanza).find('error').attr('code'),
- reason: $(stanza).find('error :first')[0].tagName
- }:{};
- error.source = 'answer';
- JingleSession.onJingleError(self.sid, error);
- },
- 10000);
- },
- function (e) {
- console.error('setLocalDescription failed', e);
- self.eventEmitter.emit(XMPPEvents.CONFERENCE_SETUP_FAILED);
- }
- );
-};
-
-JingleSession.prototype.terminate = function (reason) {
- this.state = 'ended';
- this.reason = reason;
- this.peerconnection.close();
- if (this.statsinterval !== null) {
- window.clearInterval(this.statsinterval);
- this.statsinterval = null;
- }
-};
-
-JingleSession.prototype.active = function () {
- return this.state == 'active';
-};
-
-JingleSession.prototype.sendIceCandidate = function (candidate) {
- var self = this;
- if (candidate && !this.lasticecandidate) {
- var ice = SDPUtil.iceparams(this.localSDP.media[candidate.sdpMLineIndex], this.localSDP.session);
- var jcand = SDPUtil.candidateToJingle(candidate.candidate);
- if (!(ice && jcand)) {
- console.error('failed to get ice && jcand');
- return;
- }
- ice.xmlns = 'urn:xmpp:jingle:transports:ice-udp:1';
-
- if (jcand.type === 'srflx') {
- this.hadstuncandidate = true;
- } else if (jcand.type === 'relay') {
- this.hadturncandidate = true;
- }
-
- if (this.usetrickle) {
- if (this.usedrip) {
- if (this.drip_container.length === 0) {
- // start 20ms callout
- window.setTimeout(function () {
- if (self.drip_container.length === 0) return;
- self.sendIceCandidates(self.drip_container);
- self.drip_container = [];
- }, 20);
-
- }
- this.drip_container.push(candidate);
- return;
- } else {
- self.sendIceCandidate([candidate]);
- }
- }
- } else {
- //console.log('sendIceCandidate: last candidate.');
- if (!this.usetrickle) {
- //console.log('should send full offer now...');
- var init = $iq({to: this.peerjid,
- type: 'set'})
- .c('jingle', {xmlns: 'urn:xmpp:jingle:1',
- action: this.peerconnection.localDescription.type == 'offer' ? 'session-initiate' : 'session-accept',
- initiator: this.initiator,
- sid: this.sid});
- this.localSDP = new SDP(this.peerconnection.localDescription.sdp);
- var self = this;
- var sendJingle = function (ssrc) {
- if(!ssrc)
- ssrc = {};
- self.localSDP.toJingle(init, self.initiator == self.me ? 'initiator' : 'responder', ssrc);
- self.connection.sendIQ(init,
- function () {
- //console.log('session initiate ack');
- var ack = {};
- ack.source = 'offer';
- $(document).trigger('ack.jingle', [self.sid, ack]);
- },
- function (stanza) {
- self.state = 'error';
- self.peerconnection.close();
- var error = ($(stanza).find('error').length) ? {
- code: $(stanza).find('error').attr('code'),
- reason: $(stanza).find('error :first')[0].tagName,
- }:{};
- error.source = 'offer';
- JingleSession.onJingleError(self.sid, error);
- },
- 10000);
- }
- sendJingle();
- }
- this.lasticecandidate = true;
- console.log('Have we encountered any srflx candidates? ' + this.hadstuncandidate);
- console.log('Have we encountered any relay candidates? ' + this.hadturncandidate);
-
- if (!(this.hadstuncandidate || this.hadturncandidate) && this.peerconnection.signalingState != 'closed') {
- $(document).trigger('nostuncandidates.jingle', [this.sid]);
- }
- }
-};
-
-JingleSession.prototype.sendIceCandidates = function (candidates) {
- console.log('sendIceCandidates', candidates);
- var cand = $iq({to: this.peerjid, type: 'set'})
- .c('jingle', {xmlns: 'urn:xmpp:jingle:1',
- action: 'transport-info',
- initiator: this.initiator,
- sid: this.sid});
- for (var mid = 0; mid < this.localSDP.media.length; mid++) {
- var cands = candidates.filter(function (el) { return el.sdpMLineIndex == mid; });
- var mline = SDPUtil.parse_mline(this.localSDP.media[mid].split('\r\n')[0]);
- if (cands.length > 0) {
- var ice = SDPUtil.iceparams(this.localSDP.media[mid], this.localSDP.session);
- ice.xmlns = 'urn:xmpp:jingle:transports:ice-udp:1';
- cand.c('content', {creator: this.initiator == this.me ? 'initiator' : 'responder',
- name: (cands[0].sdpMid? cands[0].sdpMid : mline.media)
- }).c('transport', ice);
- for (var i = 0; i < cands.length; i++) {
- cand.c('candidate', SDPUtil.candidateToJingle(cands[i].candidate)).up();
- }
- // add fingerprint
- if (SDPUtil.find_line(this.localSDP.media[mid], 'a=fingerprint:', this.localSDP.session)) {
- var tmp = SDPUtil.parse_fingerprint(SDPUtil.find_line(this.localSDP.media[mid], 'a=fingerprint:', this.localSDP.session));
- tmp.required = true;
- cand.c(
- 'fingerprint',
- {xmlns: 'urn:xmpp:jingle:apps:dtls:0'})
- .t(tmp.fingerprint);
- delete tmp.fingerprint;
- cand.attrs(tmp);
- cand.up();
- }
- cand.up(); // transport
- cand.up(); // content
- }
- }
- // might merge last-candidate notification into this, but it is called alot later. See webrtc issue #2340
- //console.log('was this the last candidate', this.lasticecandidate);
- this.connection.sendIQ(cand,
- function () {
- var ack = {};
- ack.source = 'transportinfo';
- $(document).trigger('ack.jingle', [this.sid, ack]);
- },
- function (stanza) {
- var error = ($(stanza).find('error').length) ? {
- code: $(stanza).find('error').attr('code'),
- reason: $(stanza).find('error :first')[0].tagName,
- }:{};
- error.source = 'transportinfo';
- JingleSession.onJingleError(this.sid, error);
- },
- 10000);
-};
-
-
-JingleSession.prototype.sendOffer = function () {
- //console.log('sendOffer...');
- var self = this;
- this.peerconnection.createOffer(function (sdp) {
- self.createdOffer(sdp);
- },
- function (e) {
- console.error('createOffer failed', e);
- },
- this.media_constraints
- );
-};
-
-JingleSession.prototype.createdOffer = function (sdp) {
- //console.log('createdOffer', sdp);
- var self = this;
- this.localSDP = new SDP(sdp.sdp);
- //this.localSDP.mangle();
- var sendJingle = function () {
- var init = $iq({to: this.peerjid,
- type: 'set'})
- .c('jingle', {xmlns: 'urn:xmpp:jingle:1',
- action: 'session-initiate',
- initiator: this.initiator,
- sid: this.sid});
- self.localSDP.toJingle(init, this.initiator == this.me ? 'initiator' : 'responder', this.localStreamsSSRC);
- self.connection.sendIQ(init,
- function () {
- var ack = {};
- ack.source = 'offer';
- $(document).trigger('ack.jingle', [self.sid, ack]);
- },
- function (stanza) {
- self.state = 'error';
- self.peerconnection.close();
- var error = ($(stanza).find('error').length) ? {
- code: $(stanza).find('error').attr('code'),
- reason: $(stanza).find('error :first')[0].tagName,
- }:{};
- error.source = 'offer';
- JingleSession.onJingleError(self.sid, error);
- },
- 10000);
- }
- sdp.sdp = this.localSDP.raw;
- this.peerconnection.setLocalDescription(sdp,
- function () {
- if(self.usetrickle)
- {
- sendJingle();
- }
- self.setLocalDescription();
- //console.log('setLocalDescription success');
- },
- function (e) {
- console.error('setLocalDescription failed', e);
- self.eventEmitter.emit(XMPPEvents.CONFERENCE_SETUP_FAILED);
- }
- );
- var cands = SDPUtil.find_lines(this.localSDP.raw, 'a=candidate:');
- for (var i = 0; i < cands.length; i++) {
- var cand = SDPUtil.parse_icecandidate(cands[i]);
- if (cand.type == 'srflx') {
- this.hadstuncandidate = true;
- } else if (cand.type == 'relay') {
- this.hadturncandidate = true;
- }
- }
-};
-
-JingleSession.prototype.setRemoteDescription = function (elem, desctype) {
- //console.log('setting remote description... ', desctype);
- this.remoteSDP = new SDP('');
- this.remoteSDP.fromJingle(elem);
- if (this.peerconnection.remoteDescription !== null) {
- console.log('setRemoteDescription when remote description is not null, should be pranswer', this.peerconnection.remoteDescription);
- if (this.peerconnection.remoteDescription.type == 'pranswer') {
- var pranswer = new SDP(this.peerconnection.remoteDescription.sdp);
- for (var i = 0; i < pranswer.media.length; i++) {
- // make sure we have ice ufrag and pwd
- if (!SDPUtil.find_line(this.remoteSDP.media[i], 'a=ice-ufrag:', this.remoteSDP.session)) {
- if (SDPUtil.find_line(pranswer.media[i], 'a=ice-ufrag:', pranswer.session)) {
- this.remoteSDP.media[i] += SDPUtil.find_line(pranswer.media[i], 'a=ice-ufrag:', pranswer.session) + '\r\n';
- } else {
- console.warn('no ice ufrag?');
- }
- if (SDPUtil.find_line(pranswer.media[i], 'a=ice-pwd:', pranswer.session)) {
- this.remoteSDP.media[i] += SDPUtil.find_line(pranswer.media[i], 'a=ice-pwd:', pranswer.session) + '\r\n';
- } else {
- console.warn('no ice pwd?');
- }
- }
- // copy over candidates
- var lines = SDPUtil.find_lines(pranswer.media[i], 'a=candidate:');
- for (var j = 0; j < lines.length; j++) {
- this.remoteSDP.media[i] += lines[j] + '\r\n';
- }
- }
- this.remoteSDP.raw = this.remoteSDP.session + this.remoteSDP.media.join('');
- }
- }
- var remotedesc = new RTCSessionDescription({type: desctype, sdp: this.remoteSDP.raw});
-
- this.peerconnection.setRemoteDescription(remotedesc,
- function () {
- //console.log('setRemoteDescription success');
- },
- function (e) {
- console.error('setRemoteDescription error', e);
- JingleSession.onJingleFatalError(self, e);
- }
- );
-};
-
-JingleSession.prototype.addIceCandidate = function (elem) {
- var self = this;
- if (this.peerconnection.signalingState == 'closed') {
- return;
- }
- if (!this.peerconnection.remoteDescription && this.peerconnection.signalingState == 'have-local-offer') {
- console.log('trickle ice candidate arriving before session accept...');
- // create a PRANSWER for setRemoteDescription
- if (!this.remoteSDP) {
- var cobbled = 'v=0\r\n' +
- 'o=- ' + '1923518516' + ' 2 IN IP4 0.0.0.0\r\n' +// FIXME
- 's=-\r\n' +
- 't=0 0\r\n';
- // first, take some things from the local description
- for (var i = 0; i < this.localSDP.media.length; i++) {
- cobbled += SDPUtil.find_line(this.localSDP.media[i], 'm=') + '\r\n';
- cobbled += SDPUtil.find_lines(this.localSDP.media[i], 'a=rtpmap:').join('\r\n') + '\r\n';
- if (SDPUtil.find_line(this.localSDP.media[i], 'a=mid:')) {
- cobbled += SDPUtil.find_line(this.localSDP.media[i], 'a=mid:') + '\r\n';
- }
- cobbled += 'a=inactive\r\n';
- }
- this.remoteSDP = new SDP(cobbled);
- }
- // then add things like ice and dtls from remote candidate
- elem.each(function () {
- for (var i = 0; i < self.remoteSDP.media.length; i++) {
- if (SDPUtil.find_line(self.remoteSDP.media[i], 'a=mid:' + $(this).attr('name')) ||
- self.remoteSDP.media[i].indexOf('m=' + $(this).attr('name')) === 0) {
- if (!SDPUtil.find_line(self.remoteSDP.media[i], 'a=ice-ufrag:')) {
- var tmp = $(this).find('transport');
- self.remoteSDP.media[i] += 'a=ice-ufrag:' + tmp.attr('ufrag') + '\r\n';
- self.remoteSDP.media[i] += 'a=ice-pwd:' + tmp.attr('pwd') + '\r\n';
- tmp = $(this).find('transport>fingerprint');
- if (tmp.length) {
- self.remoteSDP.media[i] += 'a=fingerprint:' + tmp.attr('hash') + ' ' + tmp.text() + '\r\n';
- } else {
- console.log('no dtls fingerprint (webrtc issue #1718?)');
- self.remoteSDP.media[i] += 'a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:BAADBAADBAADBAADBAADBAADBAADBAADBAADBAAD\r\n';
- }
- break;
- }
- }
- }
- });
- this.remoteSDP.raw = this.remoteSDP.session + this.remoteSDP.media.join('');
-
- // we need a complete SDP with ice-ufrag/ice-pwd in all parts
- // this makes the assumption that the PRANSWER is constructed such that the ice-ufrag is in all mediaparts
- // but it could be in the session part as well. since the code above constructs this sdp this can't happen however
- var iscomplete = this.remoteSDP.media.filter(function (mediapart) {
- return SDPUtil.find_line(mediapart, 'a=ice-ufrag:');
- }).length == this.remoteSDP.media.length;
-
- if (iscomplete) {
- console.log('setting pranswer');
- try {
- this.peerconnection.setRemoteDescription(new RTCSessionDescription({type: 'pranswer', sdp: this.remoteSDP.raw }),
- function() {
- },
- function(e) {
- console.log('setRemoteDescription pranswer failed', e.toString());
- });
- } catch (e) {
- console.error('setting pranswer failed', e);
- }
- } else {
- //console.log('not yet setting pranswer');
- }
- }
- // operate on each content element
- elem.each(function () {
- // would love to deactivate this, but firefox still requires it
- var idx = -1;
- var i;
- for (i = 0; i < self.remoteSDP.media.length; i++) {
- if (SDPUtil.find_line(self.remoteSDP.media[i], 'a=mid:' + $(this).attr('name')) ||
- self.remoteSDP.media[i].indexOf('m=' + $(this).attr('name')) === 0) {
- idx = i;
- break;
- }
- }
- if (idx == -1) { // fall back to localdescription
- for (i = 0; i < self.localSDP.media.length; i++) {
- if (SDPUtil.find_line(self.localSDP.media[i], 'a=mid:' + $(this).attr('name')) ||
- self.localSDP.media[i].indexOf('m=' + $(this).attr('name')) === 0) {
- idx = i;
- break;
- }
- }
- }
- var name = $(this).attr('name');
- // TODO: check ice-pwd and ice-ufrag?
- $(this).find('transport>candidate').each(function () {
- var line, candidate;
- line = SDPUtil.candidateFromJingle(this);
- candidate = new RTCIceCandidate({sdpMLineIndex: idx,
- sdpMid: name,
- candidate: line});
- try {
- self.peerconnection.addIceCandidate(candidate);
- } catch (e) {
- console.error('addIceCandidate failed', e.toString(), line);
- }
- });
- });
-};
-
-JingleSession.prototype.sendAnswer = function (provisional) {
- //console.log('createAnswer', provisional);
- var self = this;
- this.peerconnection.createAnswer(
- function (sdp) {
- self.createdAnswer(sdp, provisional);
- },
- function (e) {
- console.error('createAnswer failed', e);
- self.eventEmitter.emit(XMPPEvents.CONFERENCE_SETUP_FAILED);
- },
- this.media_constraints
- );
-};
-
-JingleSession.prototype.createdAnswer = function (sdp, provisional) {
- //console.log('createAnswer callback');
- var self = this;
- this.localSDP = new SDP(sdp.sdp);
- //this.localSDP.mangle();
- this.usepranswer = provisional === true;
- if (this.usetrickle) {
- if (this.usepranswer) {
- sdp.type = 'pranswer';
- for (var i = 0; i < this.localSDP.media.length; i++) {
- this.localSDP.media[i] = this.localSDP.media[i].replace('a=sendrecv\r\n', 'a=inactive\r\n');
- }
- this.localSDP.raw = this.localSDP.session + '\r\n' + this.localSDP.media.join('');
- }
- }
- var self = this;
- var sendJingle = function (ssrcs) {
-
- var accept = $iq({to: self.peerjid,
- type: 'set'})
- .c('jingle', {xmlns: 'urn:xmpp:jingle:1',
- action: 'session-accept',
- initiator: self.initiator,
- responder: self.responder,
- sid: self.sid });
- self.localSDP.toJingle(accept, self.initiator == self.me ? 'initiator' : 'responder', ssrcs);
- self.connection.sendIQ(accept,
- function () {
- var ack = {};
- ack.source = 'answer';
- $(document).trigger('ack.jingle', [self.sid, ack]);
- },
- function (stanza) {
- var error = ($(stanza).find('error').length) ? {
- code: $(stanza).find('error').attr('code'),
- reason: $(stanza).find('error :first')[0].tagName,
- }:{};
- error.source = 'answer';
- JingleSession.onJingleError(self.sid, error);
- },
- 10000);
- }
- sdp.sdp = this.localSDP.raw;
- this.peerconnection.setLocalDescription(sdp,
- function () {
-
- //console.log('setLocalDescription success');
- if (self.usetrickle && !self.usepranswer) {
- sendJingle();
- }
- self.setLocalDescription();
- },
- function (e) {
- console.error('setLocalDescription failed', e);
- self.eventEmitter.emit(XMPPEvents.CONFERENCE_SETUP_FAILED);
- }
- );
- var cands = SDPUtil.find_lines(this.localSDP.raw, 'a=candidate:');
- for (var j = 0; j < cands.length; j++) {
- var cand = SDPUtil.parse_icecandidate(cands[j]);
- if (cand.type == 'srflx') {
- this.hadstuncandidate = true;
- } else if (cand.type == 'relay') {
- this.hadturncandidate = true;
- }
- }
-};
-
-JingleSession.prototype.sendTerminate = function (reason, text) {
- var self = this,
- term = $iq({to: this.peerjid,
- type: 'set'})
- .c('jingle', {xmlns: 'urn:xmpp:jingle:1',
- action: 'session-terminate',
- initiator: this.initiator,
- sid: this.sid})
- .c('reason')
- .c(reason || 'success');
-
- if (text) {
- term.up().c('text').t(text);
- }
-
- this.connection.sendIQ(term,
- function () {
- self.peerconnection.close();
- self.peerconnection = null;
- self.terminate();
- var ack = {};
- ack.source = 'terminate';
- $(document).trigger('ack.jingle', [self.sid, ack]);
- },
- function (stanza) {
- var error = ($(stanza).find('error').length) ? {
- code: $(stanza).find('error').attr('code'),
- reason: $(stanza).find('error :first')[0].tagName,
- }:{};
- $(document).trigger('ack.jingle', [self.sid, error]);
- },
- 10000);
- if (this.statsinterval !== null) {
- window.clearInterval(this.statsinterval);
- this.statsinterval = null;
- }
-};
-
-JingleSession.prototype.addSource = function (elem, fromJid) {
-
- var self = this;
- // FIXME: dirty waiting
- if (!this.peerconnection.localDescription)
- {
- console.warn("addSource - localDescription not ready yet")
- setTimeout(function()
- {
- self.addSource(elem, fromJid);
- },
- 200
- );
- return;
- }
-
- console.log('addssrc', new Date().getTime());
- console.log('ice', this.peerconnection.iceConnectionState);
- var sdp = new SDP(this.peerconnection.remoteDescription.sdp);
- var mySdp = new SDP(this.peerconnection.localDescription.sdp);
-
- $(elem).each(function (idx, content) {
- var name = $(content).attr('name');
- var lines = '';
- $(content).find('ssrc-group[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]').each(function() {
- var semantics = this.getAttribute('semantics');
- var ssrcs = $(this).find('>source').map(function () {
- return this.getAttribute('ssrc');
- }).get();
-
- if (ssrcs.length != 0) {
- lines += 'a=ssrc-group:' + semantics + ' ' + ssrcs.join(' ') + '\r\n';
- }
- });
- var tmp = $(content).find('source[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]'); // can handle both >source and >description>source
- tmp.each(function () {
- var ssrc = $(this).attr('ssrc');
- if(mySdp.containsSSRC(ssrc)){
- /**
- * This happens when multiple participants change their streams at the same time and
- * ColibriFocus.modifySources have to wait for stable state. In the meantime multiple
- * addssrc are scheduled for update IQ. See
- */
- console.warn("Got add stream request for my own ssrc: "+ssrc);
- return;
- }
- $(this).find('>parameter').each(function () {
- lines += 'a=ssrc:' + ssrc + ' ' + $(this).attr('name');
- if ($(this).attr('value') && $(this).attr('value').length)
- lines += ':' + $(this).attr('value');
- lines += '\r\n';
- });
- });
- sdp.media.forEach(function(media, idx) {
- if (!SDPUtil.find_line(media, 'a=mid:' + name))
- return;
- sdp.media[idx] += lines;
- if (!self.addssrc[idx]) self.addssrc[idx] = '';
- self.addssrc[idx] += lines;
- });
- sdp.raw = sdp.session + sdp.media.join('');
- });
-
- this.modifySourcesQueue.push(function() {
- // When a source is added and if this is FF, a new channel is allocated
- // for receiving the added source. We need to diffuse the SSRC of this
- // new recvonly channel to the rest of the peers.
- console.log('modify sources done');
-
- var newSdp = new SDP(self.peerconnection.localDescription.sdp);
- console.log("SDPs", mySdp, newSdp);
- self.notifyMySSRCUpdate(mySdp, newSdp);
- });
-};
-
-JingleSession.prototype.removeSource = function (elem, fromJid) {
-
- var self = this;
- // FIXME: dirty waiting
- if (!this.peerconnection.localDescription)
- {
- console.warn("removeSource - localDescription not ready yet")
- setTimeout(function()
- {
- self.removeSource(elem, fromJid);
- },
- 200
- );
- return;
- }
-
- console.log('removessrc', new Date().getTime());
- console.log('ice', this.peerconnection.iceConnectionState);
- var sdp = new SDP(this.peerconnection.remoteDescription.sdp);
- var mySdp = new SDP(this.peerconnection.localDescription.sdp);
-
- $(elem).each(function (idx, content) {
- var name = $(content).attr('name');
- var lines = '';
- $(content).find('ssrc-group[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]').each(function() {
- var semantics = this.getAttribute('semantics');
- var ssrcs = $(this).find('>source').map(function () {
- return this.getAttribute('ssrc');
- }).get();
-
- if (ssrcs.length != 0) {
- lines += 'a=ssrc-group:' + semantics + ' ' + ssrcs.join(' ') + '\r\n';
- }
- });
- var tmp = $(content).find('source[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]'); // can handle both >source and >description>source
- tmp.each(function () {
- var ssrc = $(this).attr('ssrc');
- // This should never happen, but can be useful for bug detection
- if(mySdp.containsSSRC(ssrc)){
- console.error("Got remove stream request for my own ssrc: "+ssrc);
- return;
- }
- $(this).find('>parameter').each(function () {
- lines += 'a=ssrc:' + ssrc + ' ' + $(this).attr('name');
- if ($(this).attr('value') && $(this).attr('value').length)
- lines += ':' + $(this).attr('value');
- lines += '\r\n';
- });
- });
- sdp.media.forEach(function(media, idx) {
- if (!SDPUtil.find_line(media, 'a=mid:' + name))
- return;
- sdp.media[idx] += lines;
- if (!self.removessrc[idx]) self.removessrc[idx] = '';
- self.removessrc[idx] += lines;
- });
- sdp.raw = sdp.session + sdp.media.join('');
- });
-
- this.modifySourcesQueue.push(function() {
- // When a source is removed and if this is FF, the recvonly channel that
- // receives the remote stream is deactivated . We need to diffuse the
- // recvonly SSRC removal to the rest of the peers.
- console.log('modify sources done');
-
- var newSdp = new SDP(self.peerconnection.localDescription.sdp);
- console.log("SDPs", mySdp, newSdp);
- self.notifyMySSRCUpdate(mySdp, newSdp);
- });
-};
-
-JingleSession.prototype._modifySources = function (successCallback, queueCallback) {
- var self = this;
-
- if (this.peerconnection.signalingState == 'closed') return;
- if (!(this.addssrc.length || this.removessrc.length || this.pendingop !== null || this.switchstreams)){
- // There is nothing to do since scheduled job might have been executed by another succeeding call
- this.setLocalDescription();
- if(successCallback){
- successCallback();
- }
- queueCallback();
- return;
- }
-
- // Reset switch streams flag
- this.switchstreams = false;
-
- var sdp = new SDP(this.peerconnection.remoteDescription.sdp);
-
- // add sources
- this.addssrc.forEach(function(lines, idx) {
- sdp.media[idx] += lines;
- });
- this.addssrc = [];
-
- // remove sources
- this.removessrc.forEach(function(lines, idx) {
- lines = lines.split('\r\n');
- lines.pop(); // remove empty last element;
- lines.forEach(function(line) {
- sdp.media[idx] = sdp.media[idx].replace(line + '\r\n', '');
- });
- });
- this.removessrc = [];
-
- // FIXME:
- // this was a hack for the situation when only one peer exists
- // in the conference.
- // check if still required and remove
- if (sdp.media[0])
- sdp.media[0] = sdp.media[0].replace('a=recvonly', 'a=sendrecv');
- if (sdp.media[1])
- sdp.media[1] = sdp.media[1].replace('a=recvonly', 'a=sendrecv');
-
- sdp.raw = sdp.session + sdp.media.join('');
- this.peerconnection.setRemoteDescription(new RTCSessionDescription({type: 'offer', sdp: sdp.raw}),
- function() {
-
- if(self.signalingState == 'closed') {
- console.error("createAnswer attempt on closed state");
- queueCallback("createAnswer attempt on closed state");
- return;
- }
-
- self.peerconnection.createAnswer(
- function(modifiedAnswer) {
- // change video direction, see https://github.com/jitsi/jitmeet/issues/41
- if (self.pendingop !== null) {
- var sdp = new SDP(modifiedAnswer.sdp);
- if (sdp.media.length > 1) {
- switch(self.pendingop) {
- case 'mute':
- sdp.media[1] = sdp.media[1].replace('a=sendrecv', 'a=recvonly');
- break;
- case 'unmute':
- sdp.media[1] = sdp.media[1].replace('a=recvonly', 'a=sendrecv');
- break;
- }
- sdp.raw = sdp.session + sdp.media.join('');
- modifiedAnswer.sdp = sdp.raw;
- }
- self.pendingop = null;
- }
-
- // FIXME: pushing down an answer while ice connection state
- // is still checking is bad...
- //console.log(self.peerconnection.iceConnectionState);
-
- // trying to work around another chrome bug
- //modifiedAnswer.sdp = modifiedAnswer.sdp.replace(/a=setup:active/g, 'a=setup:actpass');
- self.peerconnection.setLocalDescription(modifiedAnswer,
- function() {
- //console.log('modified setLocalDescription ok');
- self.setLocalDescription();
- if(successCallback){
- successCallback();
- }
- queueCallback();
- },
- function(error) {
- console.error('modified setLocalDescription failed', error);
- queueCallback(error);
- }
- );
- },
- function(error) {
- console.error('modified answer failed', error);
- queueCallback(error);
- }
- );
- },
- function(error) {
- console.error('modify failed', error);
- queueCallback(error);
- }
- );
-};
-
-
-/**
- * Switches video streams.
- * @param new_stream new stream that will be used as video of this session.
- * @param oldStream old video stream of this session.
- * @param success_callback callback executed after successful stream switch.
- */
-JingleSession.prototype.switchStreams = function (new_stream, oldStream, success_callback, isAudio) {
-
- var self = this;
-
- // Remember SDP to figure out added/removed SSRCs
- var oldSdp = null;
- if(self.peerconnection) {
- if(self.peerconnection.localDescription) {
- oldSdp = new SDP(self.peerconnection.localDescription.sdp);
- }
- self.peerconnection.removeStream(oldStream, true);
- if(new_stream)
- self.peerconnection.addStream(new_stream);
- }
-
- if(!isAudio)
- APP.RTC.switchVideoStreams(new_stream, oldStream);
-
- // Conference is not active
- if(!oldSdp || !self.peerconnection) {
- success_callback();
- return;
- }
-
- self.switchstreams = true;
- self.modifySourcesQueue.push(function() {
- console.log('modify sources done');
-
- success_callback();
-
- var newSdp = new SDP(self.peerconnection.localDescription.sdp);
- console.log("SDPs", oldSdp, newSdp);
- self.notifyMySSRCUpdate(oldSdp, newSdp);
- });
-};
-
-/**
- * Figures out added/removed ssrcs and send update IQs.
- * @param old_sdp SDP object for old description.
- * @param new_sdp SDP object for new description.
- */
-JingleSession.prototype.notifyMySSRCUpdate = function (old_sdp, new_sdp) {
-
- if (!(this.peerconnection.signalingState == 'stable' &&
- this.peerconnection.iceConnectionState == 'connected')){
- console.log("Too early to send updates");
- return;
- }
-
- // send source-remove IQ.
- sdpDiffer = new SDPDiffer(new_sdp, old_sdp);
- var remove = $iq({to: this.peerjid, type: 'set'})
- .c('jingle', {
- xmlns: 'urn:xmpp:jingle:1',
- action: 'source-remove',
- initiator: this.initiator,
- sid: this.sid
- }
- );
- var removed = sdpDiffer.toJingle(remove);
- if (removed) {
- this.connection.sendIQ(remove,
- function (res) {
- console.info('got remove result', res);
- },
- function (err) {
- console.error('got remove error', err);
- }
- );
- } else {
- console.log('removal not necessary');
- }
-
- // send source-add IQ.
- var sdpDiffer = new SDPDiffer(old_sdp, new_sdp);
- var add = $iq({to: this.peerjid, type: 'set'})
- .c('jingle', {
- xmlns: 'urn:xmpp:jingle:1',
- action: 'source-add',
- initiator: this.initiator,
- sid: this.sid
- }
- );
- var added = sdpDiffer.toJingle(add);
- if (added) {
- this.connection.sendIQ(add,
- function (res) {
- console.info('got add result', res);
- },
- function (err) {
- console.error('got add error', err);
- }
- );
- } else {
- console.log('addition not necessary');
- }
-};
-
-/**
- * Mutes/unmutes the (local) video i.e. enables/disables all video tracks.
- *
- * @param mute true to mute the (local) video i.e. to disable all video
- * tracks; otherwise, false
- * @param callback a function to be invoked with mute after all video
- * tracks have been enabled/disabled. The function may, optionally, return
- * another function which is to be invoked after the whole mute/unmute operation
- * has completed successfully.
- * @param options an object which specifies optional arguments such as the
- * boolean key byUser with default value true which
- * specifies whether the method was initiated in response to a user command (in
- * contrast to an automatic decision made by the application logic)
- */
-JingleSession.prototype.setVideoMute = function (mute, callback, options) {
- var byUser;
-
- if (options) {
- byUser = options.byUser;
- if (typeof byUser === 'undefined') {
- byUser = true;
- }
- } else {
- byUser = true;
- }
- // The user's command to mute the (local) video takes precedence over any
- // automatic decision made by the application logic.
- if (byUser) {
- this.videoMuteByUser = mute;
- } else if (this.videoMuteByUser) {
- return;
- }
-
- this.hardMuteVideo(mute);
-
- var self = this;
- var oldSdp = null;
- if(self.peerconnection) {
- if(self.peerconnection.localDescription) {
- oldSdp = new SDP(self.peerconnection.localDescription.sdp);
- }
- }
-
- this.modifySourcesQueue.push(function() {
- console.log('modify sources done');
-
- callback(mute);
-
- var newSdp = new SDP(self.peerconnection.localDescription.sdp);
- console.log("SDPs", oldSdp, newSdp);
- self.notifyMySSRCUpdate(oldSdp, newSdp);
- });
-};
-
-JingleSession.prototype.hardMuteVideo = function (muted) {
- this.pendingop = muted ? 'mute' : 'unmute';
-};
-
-JingleSession.prototype.sendMute = function (muted, content) {
- var info = $iq({to: this.peerjid,
- type: 'set'})
- .c('jingle', {xmlns: 'urn:xmpp:jingle:1',
- action: 'session-info',
- initiator: this.initiator,
- sid: this.sid });
- info.c(muted ? 'mute' : 'unmute', {xmlns: 'urn:xmpp:jingle:apps:rtp:info:1'});
- info.attrs({'creator': this.me == this.initiator ? 'creator' : 'responder'});
- if (content) {
- info.attrs({'name': content});
- }
- this.connection.send(info);
-};
-
-JingleSession.prototype.sendRinging = function () {
- var info = $iq({to: this.peerjid,
- type: 'set'})
- .c('jingle', {xmlns: 'urn:xmpp:jingle:1',
- action: 'session-info',
- initiator: this.initiator,
- sid: this.sid });
- info.c('ringing', {xmlns: 'urn:xmpp:jingle:apps:rtp:info:1'});
- this.connection.send(info);
-};
-
-JingleSession.prototype.getStats = function (interval) {
- var self = this;
- var recv = {audio: 0, video: 0};
- var lost = {audio: 0, video: 0};
- var lastrecv = {audio: 0, video: 0};
- var lastlost = {audio: 0, video: 0};
- var loss = {audio: 0, video: 0};
- var delta = {audio: 0, video: 0};
- this.statsinterval = window.setInterval(function () {
- if (self && self.peerconnection && self.peerconnection.getStats) {
- self.peerconnection.getStats(function (stats) {
- var results = stats.result();
- // TODO: there are so much statistics you can get from this..
- for (var i = 0; i < results.length; ++i) {
- if (results[i].type == 'ssrc') {
- var packetsrecv = results[i].stat('packetsReceived');
- var packetslost = results[i].stat('packetsLost');
- if (packetsrecv && packetslost) {
- packetsrecv = parseInt(packetsrecv, 10);
- packetslost = parseInt(packetslost, 10);
-
- if (results[i].stat('googFrameRateReceived')) {
- lastlost.video = lost.video;
- lastrecv.video = recv.video;
- recv.video = packetsrecv;
- lost.video = packetslost;
- } else {
- lastlost.audio = lost.audio;
- lastrecv.audio = recv.audio;
- recv.audio = packetsrecv;
- lost.audio = packetslost;
- }
- }
- }
- }
- delta.audio = recv.audio - lastrecv.audio;
- delta.video = recv.video - lastrecv.video;
- loss.audio = (delta.audio > 0) ? Math.ceil(100 * (lost.audio - lastlost.audio) / delta.audio) : 0;
- loss.video = (delta.video > 0) ? Math.ceil(100 * (lost.video - lastlost.video) / delta.video) : 0;
- $(document).trigger('packetloss.jingle', [self.sid, loss]);
- });
- }
- }, interval || 3000);
- return this.statsinterval;
-};
-
-JingleSession.onJingleError = function (session, error)
-{
- console.error("Jingle error", error);
-}
-
-JingleSession.onJingleFatalError = function (session, error)
-{
- this.service.sessionTerminated = true;
- this.connection.emuc.doLeave();
- APP.UI.messageHandler.showError("dialog.sorry",
- "dialog.internalError");
- this.eventEmitter.emit(XMPPEvents.CONFERENCE_SETUP_FAILED);
-}
-
-JingleSession.prototype.setLocalDescription = function () {
- // put our ssrcs into presence so other clients can identify our stream
- var newssrcs = [];
- var session = transform.parse(this.peerconnection.localDescription.sdp);
- session.media.forEach(function (media) {
-
- if (media.ssrcs != null && media.ssrcs.length > 0) {
- // TODO(gp) maybe exclude FID streams?
- media.ssrcs.forEach(function (ssrc) {
- if (ssrc.attribute !== 'cname') {
- return;
- }
- newssrcs.push({
- 'ssrc': ssrc.id,
- 'type': media.type,
- 'direction': media.direction
- });
- });
- }
- else if(this.localStreamsSSRC && this.localStreamsSSRC[media.type])
- {
- newssrcs.push({
- 'ssrc': this.localStreamsSSRC[media.type],
- 'type': media.type,
- 'direction': media.direction
- });
- }
-
- });
-
- console.log('new ssrcs', newssrcs);
-
- // Have to clear presence map to get rid of removed streams
- this.connection.emuc.clearPresenceMedia();
-
- if (newssrcs.length > 0) {
- for (var i = 1; i <= newssrcs.length; i ++) {
- // Change video type to screen
- if (newssrcs[i-1].type === 'video' && APP.desktopsharing.isUsingScreenStream()) {
- newssrcs[i-1].type = 'screen';
- }
- this.connection.emuc.addMediaToPresence(i,
- newssrcs[i-1].type, newssrcs[i-1].ssrc, newssrcs[i-1].direction);
- }
-
- this.connection.emuc.sendPresence();
- }
-}
-
-// an attempt to work around https://github.com/jitsi/jitmeet/issues/32
-function sendKeyframe(pc) {
- console.log('sendkeyframe', pc.iceConnectionState);
- if (pc.iceConnectionState !== 'connected') return; // safe...
- var self = this;
- pc.setRemoteDescription(
- pc.remoteDescription,
- function () {
- pc.createAnswer(
- function (modifiedAnswer) {
- pc.setLocalDescription(
- modifiedAnswer,
- function () {
- // noop
- },
- function (error) {
- console.log('triggerKeyframe setLocalDescription failed', error);
- APP.UI.messageHandler.showError();
- }
- );
- },
- function (error) {
- console.log('triggerKeyframe createAnswer failed', error);
- APP.UI.messageHandler.showError();
- }
- );
- },
- function (error) {
- console.log('triggerKeyframe setRemoteDescription failed', error);
- APP.UI.messageHandler.showError();
- }
- );
-}
-
-
-JingleSession.prototype.remoteStreamAdded = function (data, times) {
- var self = this;
- var thessrc;
- var ssrc2jid = this.connection.emuc.ssrc2jid;
-
- // look up an associated JID for a stream id
- if (data.stream.id && data.stream.id.indexOf('mixedmslabel') === -1) {
- // look only at a=ssrc: and _not_ at a=ssrc-group: lines
-
- var ssrclines
- = SDPUtil.find_lines(this.peerconnection.remoteDescription.sdp, 'a=ssrc:');
- ssrclines = ssrclines.filter(function (line) {
- // NOTE(gp) previously we filtered on the mslabel, but that property
- // is not always present.
- // return line.indexOf('mslabel:' + data.stream.label) !== -1;
-
- return ((line.indexOf('msid:' + data.stream.id) !== -1));
- });
- if (ssrclines.length) {
- thessrc = ssrclines[0].substring(7).split(' ')[0];
-
- // We signal our streams (through Jingle to the focus) before we set
- // our presence (through which peers associate remote streams to
- // jids). So, it might arrive that a remote stream is added but
- // ssrc2jid is not yet updated and thus data.peerjid cannot be
- // successfully set. Here we wait for up to a second for the
- // presence to arrive.
-
- if (!ssrc2jid[thessrc]) {
-
- if (typeof times === 'undefined')
- {
- times = 0;
- }
-
- if (times > 10)
- {
- console.warning('Waiting for jid timed out', thessrc);
- }
- else
- {
- setTimeout(function(d) {
- return function() {
- self.remoteStreamAdded(d, times++);
- }
- }(data), 250);
- }
- return;
- }
-
- // ok to overwrite the one from focus? might save work in colibri.js
- console.log('associated jid', ssrc2jid[thessrc], data.peerjid);
- if (ssrc2jid[thessrc]) {
- data.peerjid = ssrc2jid[thessrc];
- }
- }
- }
-
- APP.RTC.createRemoteStream(data, this.sid, thessrc);
-
- var isVideo = data.stream.getVideoTracks().length > 0;
- // an attempt to work around https://github.com/jitsi/jitmeet/issues/32
- if (isVideo &&
- data.peerjid && this.peerjid === data.peerjid &&
- data.stream.getVideoTracks().length === 0 &&
- APP.RTC.localVideo.getTracks().length > 0) {
- window.setTimeout(function () {
- sendKeyframe(self.peerconnection);
- }, 3000);
- }
-}
-
-module.exports = JingleSession;
-
-},{"../../service/xmpp/XMPPEvents":111,"./SDP":52,"./SDPDiffer":53,"./SDPUtil":54,"./TraceablePeerConnection":55,"async":65,"sdp-transform":97}],52:[function(require,module,exports){
-/* jshint -W117 */
-var SDPUtil = require("./SDPUtil");
-
-// SDP STUFF
-function SDP(sdp) {
- this.media = sdp.split('\r\nm=');
- for (var i = 1; i < this.media.length; i++) {
- this.media[i] = 'm=' + this.media[i];
- if (i != this.media.length - 1) {
- this.media[i] += '\r\n';
- }
- }
- this.session = this.media.shift() + '\r\n';
- this.raw = this.session + this.media.join('');
-}
-/**
- * Returns map of MediaChannel mapped per channel idx.
- */
-SDP.prototype.getMediaSsrcMap = function() {
- var self = this;
- var media_ssrcs = {};
- var tmp;
- for (var mediaindex = 0; mediaindex < self.media.length; mediaindex++) {
- tmp = SDPUtil.find_lines(self.media[mediaindex], 'a=ssrc:');
- var mid = SDPUtil.parse_mid(SDPUtil.find_line(self.media[mediaindex], 'a=mid:'));
- var media = {
- mediaindex: mediaindex,
- mid: mid,
- ssrcs: {},
- ssrcGroups: []
- };
- media_ssrcs[mediaindex] = media;
- tmp.forEach(function (line) {
- var linessrc = line.substring(7).split(' ')[0];
- // allocate new ChannelSsrc
- if(!media.ssrcs[linessrc]) {
- media.ssrcs[linessrc] = {
- ssrc: linessrc,
- lines: []
- };
- }
- media.ssrcs[linessrc].lines.push(line);
- });
- tmp = SDPUtil.find_lines(self.media[mediaindex], 'a=ssrc-group:');
- tmp.forEach(function(line){
- var semantics = line.substr(0, idx).substr(13);
- var ssrcs = line.substr(14 + semantics.length).split(' ');
- if (ssrcs.length != 0) {
- media.ssrcGroups.push({
- semantics: semantics,
- ssrcs: ssrcs
- });
- }
- });
- }
- return media_ssrcs;
-};
-/**
- * Returns true if this SDP contains given SSRC.
- * @param ssrc the ssrc to check.
- * @returns {boolean} true if this SDP contains given SSRC.
- */
-SDP.prototype.containsSSRC = function(ssrc) {
- var medias = this.getMediaSsrcMap();
- var contains = false;
- Object.keys(medias).forEach(function(mediaindex){
- var media = medias[mediaindex];
- //console.log("Check", channel, ssrc);
- if(Object.keys(media.ssrcs).indexOf(ssrc) != -1){
- contains = true;
- }
- });
- return contains;
-};
-
-
-// remove iSAC and CN from SDP
-SDP.prototype.mangle = function () {
- var i, j, mline, lines, rtpmap, newdesc;
- for (i = 0; i < this.media.length; i++) {
- lines = this.media[i].split('\r\n');
- lines.pop(); // remove empty last element
- mline = SDPUtil.parse_mline(lines.shift());
- if (mline.media != 'audio')
- continue;
- newdesc = '';
- mline.fmt.length = 0;
- for (j = 0; j < lines.length; j++) {
- if (lines[j].substr(0, 9) == 'a=rtpmap:') {
- rtpmap = SDPUtil.parse_rtpmap(lines[j]);
- if (rtpmap.name == 'CN' || rtpmap.name == 'ISAC')
- continue;
- mline.fmt.push(rtpmap.id);
- newdesc += lines[j] + '\r\n';
- } else {
- newdesc += lines[j] + '\r\n';
- }
- }
- this.media[i] = SDPUtil.build_mline(mline) + '\r\n';
- this.media[i] += newdesc;
- }
- this.raw = this.session + this.media.join('');
-};
-
-// remove lines matching prefix from session section
-SDP.prototype.removeSessionLines = function(prefix) {
- var self = this;
- var lines = SDPUtil.find_lines(this.session, prefix);
- lines.forEach(function(line) {
- self.session = self.session.replace(line + '\r\n', '');
- });
- this.raw = this.session + this.media.join('');
- return lines;
-}
-// remove lines matching prefix from a media section specified by mediaindex
-// TODO: non-numeric mediaindex could match mid
-SDP.prototype.removeMediaLines = function(mediaindex, prefix) {
- var self = this;
- var lines = SDPUtil.find_lines(this.media[mediaindex], prefix);
- lines.forEach(function(line) {
- self.media[mediaindex] = self.media[mediaindex].replace(line + '\r\n', '');
- });
- this.raw = this.session + this.media.join('');
- return lines;
-}
-
-// add content's to a jingle element
-SDP.prototype.toJingle = function (elem, thecreator, ssrcs) {
-// console.log("SSRC" + ssrcs["audio"] + " - " + ssrcs["video"]);
- var i, j, k, mline, ssrc, rtpmap, tmp, line, lines;
- var self = this;
- // new bundle plan
- if (SDPUtil.find_line(this.session, 'a=group:')) {
- lines = SDPUtil.find_lines(this.session, 'a=group:');
- for (i = 0; i < lines.length; i++) {
- tmp = lines[i].split(' ');
- var semantics = tmp.shift().substr(8);
- elem.c('group', {xmlns: 'urn:xmpp:jingle:apps:grouping:0', semantics:semantics});
- for (j = 0; j < tmp.length; j++) {
- elem.c('content', {name: tmp[j]}).up();
- }
- elem.up();
- }
- }
- for (i = 0; i < this.media.length; i++) {
- mline = SDPUtil.parse_mline(this.media[i].split('\r\n')[0]);
- if (!(mline.media === 'audio' ||
- mline.media === 'video' ||
- mline.media === 'application'))
- {
- continue;
- }
- if (SDPUtil.find_line(this.media[i], 'a=ssrc:')) {
- ssrc = SDPUtil.find_line(this.media[i], 'a=ssrc:').substring(7).split(' ')[0]; // take the first
- } else {
- if(ssrcs && ssrcs[mline.media])
- {
- ssrc = ssrcs[mline.media];
- }
- else
- ssrc = false;
- }
-
- elem.c('content', {creator: thecreator, name: mline.media});
- if (SDPUtil.find_line(this.media[i], 'a=mid:')) {
- // prefer identifier from a=mid if present
- var mid = SDPUtil.parse_mid(SDPUtil.find_line(this.media[i], 'a=mid:'));
- elem.attrs({ name: mid });
- }
-
- if (SDPUtil.find_line(this.media[i], 'a=rtpmap:').length)
- {
- elem.c('description',
- {xmlns: 'urn:xmpp:jingle:apps:rtp:1',
- media: mline.media });
- if (ssrc) {
- elem.attrs({ssrc: ssrc});
- }
- for (j = 0; j < mline.fmt.length; j++) {
- rtpmap = SDPUtil.find_line(this.media[i], 'a=rtpmap:' + mline.fmt[j]);
- elem.c('payload-type', SDPUtil.parse_rtpmap(rtpmap));
- // put any 'a=fmtp:' + mline.fmt[j] lines into
- if (SDPUtil.find_line(this.media[i], 'a=fmtp:' + mline.fmt[j])) {
- tmp = SDPUtil.parse_fmtp(SDPUtil.find_line(this.media[i], 'a=fmtp:' + mline.fmt[j]));
- for (k = 0; k < tmp.length; k++) {
- elem.c('parameter', tmp[k]).up();
- }
- }
- this.RtcpFbToJingle(i, elem, mline.fmt[j]); // XEP-0293 -- map a=rtcp-fb
-
- elem.up();
- }
- if (SDPUtil.find_line(this.media[i], 'a=crypto:', this.session)) {
- elem.c('encryption', {required: 1});
- var crypto = SDPUtil.find_lines(this.media[i], 'a=crypto:', this.session);
- crypto.forEach(function(line) {
- elem.c('crypto', SDPUtil.parse_crypto(line)).up();
- });
- elem.up(); // end of encryption
- }
-
- if (ssrc) {
- // new style mapping
- elem.c('source', { ssrc: ssrc, xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' });
- // FIXME: group by ssrc and support multiple different ssrcs
- var ssrclines = SDPUtil.find_lines(this.media[i], 'a=ssrc:');
- if(ssrclines.length > 0) {
- ssrclines.forEach(function (line) {
- idx = line.indexOf(' ');
- var linessrc = line.substr(0, idx).substr(7);
- if (linessrc != ssrc) {
- elem.up();
- ssrc = linessrc;
- elem.c('source', { ssrc: ssrc, xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' });
- }
- var kv = line.substr(idx + 1);
- elem.c('parameter');
- if (kv.indexOf(':') == -1) {
- elem.attrs({ name: kv });
- } else {
- elem.attrs({ name: kv.split(':', 2)[0] });
- elem.attrs({ value: kv.split(':', 2)[1] });
- }
- elem.up();
- });
- elem.up();
- }
- else
- {
- elem.up();
- elem.c('source', { ssrc: ssrc, xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' });
- elem.c('parameter');
- elem.attrs({name: "cname", value:Math.random().toString(36).substring(7)});
- elem.up();
- var msid = null;
- if(mline.media == "audio")
- {
- msid = APP.RTC.localAudio.getId();
- }
- else
- {
- msid = APP.RTC.localVideo.getId();
- }
- if(msid != null)
- {
- msid = msid.replace(/[\{,\}]/g,"");
- elem.c('parameter');
- elem.attrs({name: "msid", value:msid});
- elem.up();
- elem.c('parameter');
- elem.attrs({name: "mslabel", value:msid});
- elem.up();
- elem.c('parameter');
- elem.attrs({name: "label", value:msid});
- elem.up();
- elem.up();
- }
-
-
- }
-
- // XEP-0339 handle ssrc-group attributes
- var ssrc_group_lines = SDPUtil.find_lines(this.media[i], 'a=ssrc-group:');
- ssrc_group_lines.forEach(function(line) {
- idx = line.indexOf(' ');
- var semantics = line.substr(0, idx).substr(13);
- var ssrcs = line.substr(14 + semantics.length).split(' ');
- if (ssrcs.length != 0) {
- elem.c('ssrc-group', { semantics: semantics, xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' });
- ssrcs.forEach(function(ssrc) {
- elem.c('source', { ssrc: ssrc })
- .up();
- });
- elem.up();
- }
- });
- }
-
- if (SDPUtil.find_line(this.media[i], 'a=rtcp-mux')) {
- elem.c('rtcp-mux').up();
- }
-
- // XEP-0293 -- map a=rtcp-fb:*
- this.RtcpFbToJingle(i, elem, '*');
-
- // XEP-0294
- if (SDPUtil.find_line(this.media[i], 'a=extmap:')) {
- lines = SDPUtil.find_lines(this.media[i], 'a=extmap:');
- for (j = 0; j < lines.length; j++) {
- tmp = SDPUtil.parse_extmap(lines[j]);
- elem.c('rtp-hdrext', { xmlns: 'urn:xmpp:jingle:apps:rtp:rtp-hdrext:0',
- uri: tmp.uri,
- id: tmp.value });
- if (tmp.hasOwnProperty('direction')) {
- switch (tmp.direction) {
- case 'sendonly':
- elem.attrs({senders: 'responder'});
- break;
- case 'recvonly':
- elem.attrs({senders: 'initiator'});
- break;
- case 'sendrecv':
- elem.attrs({senders: 'both'});
- break;
- case 'inactive':
- elem.attrs({senders: 'none'});
- break;
- }
- }
- // TODO: handle params
- elem.up();
- }
- }
- elem.up(); // end of description
- }
-
- // map ice-ufrag/pwd, dtls fingerprint, candidates
- this.TransportToJingle(i, elem);
-
- if (SDPUtil.find_line(this.media[i], 'a=sendrecv', this.session)) {
- elem.attrs({senders: 'both'});
- } else if (SDPUtil.find_line(this.media[i], 'a=sendonly', this.session)) {
- elem.attrs({senders: 'initiator'});
- } else if (SDPUtil.find_line(this.media[i], 'a=recvonly', this.session)) {
- elem.attrs({senders: 'responder'});
- } else if (SDPUtil.find_line(this.media[i], 'a=inactive', this.session)) {
- elem.attrs({senders: 'none'});
- }
- if (mline.port == '0') {
- // estos hack to reject an m-line
- elem.attrs({senders: 'rejected'});
- }
- elem.up(); // end of content
- }
- elem.up();
- return elem;
-};
-
-SDP.prototype.TransportToJingle = function (mediaindex, elem) {
- var i = mediaindex;
- var tmp;
- var self = this;
- elem.c('transport');
-
- // XEP-0343 DTLS/SCTP
- if (SDPUtil.find_line(this.media[mediaindex], 'a=sctpmap:').length)
- {
- var sctpmap = SDPUtil.find_line(
- this.media[i], 'a=sctpmap:', self.session);
- if (sctpmap)
- {
- var sctpAttrs = SDPUtil.parse_sctpmap(sctpmap);
- elem.c('sctpmap',
- {
- xmlns: 'urn:xmpp:jingle:transports:dtls-sctp:1',
- number: sctpAttrs[0], /* SCTP port */
- protocol: sctpAttrs[1], /* protocol */
- });
- // Optional stream count attribute
- if (sctpAttrs.length > 2)
- elem.attrs({ streams: sctpAttrs[2]});
- elem.up();
- }
- }
- // XEP-0320
- var fingerprints = SDPUtil.find_lines(this.media[mediaindex], 'a=fingerprint:', this.session);
- fingerprints.forEach(function(line) {
- tmp = SDPUtil.parse_fingerprint(line);
- tmp.xmlns = 'urn:xmpp:jingle:apps:dtls:0';
- elem.c('fingerprint').t(tmp.fingerprint);
- delete tmp.fingerprint;
- line = SDPUtil.find_line(self.media[mediaindex], 'a=setup:', self.session);
- if (line) {
- tmp.setup = line.substr(8);
- }
- elem.attrs(tmp);
- elem.up(); // end of fingerprint
- });
- tmp = SDPUtil.iceparams(this.media[mediaindex], this.session);
- if (tmp) {
- tmp.xmlns = 'urn:xmpp:jingle:transports:ice-udp:1';
- elem.attrs(tmp);
- // XEP-0176
- if (SDPUtil.find_line(this.media[mediaindex], 'a=candidate:', this.session)) { // add any a=candidate lines
- var lines = SDPUtil.find_lines(this.media[mediaindex], 'a=candidate:', this.session);
- lines.forEach(function (line) {
- elem.c('candidate', SDPUtil.candidateToJingle(line)).up();
- });
- }
- }
- elem.up(); // end of transport
-}
-
-SDP.prototype.RtcpFbToJingle = function (mediaindex, elem, payloadtype) { // XEP-0293
- var lines = SDPUtil.find_lines(this.media[mediaindex], 'a=rtcp-fb:' + payloadtype);
- lines.forEach(function (line) {
- var tmp = SDPUtil.parse_rtcpfb(line);
- if (tmp.type == 'trr-int') {
- elem.c('rtcp-fb-trr-int', {xmlns: 'urn:xmpp:jingle:apps:rtp:rtcp-fb:0', value: tmp.params[0]});
- elem.up();
- } else {
- elem.c('rtcp-fb', {xmlns: 'urn:xmpp:jingle:apps:rtp:rtcp-fb:0', type: tmp.type});
- if (tmp.params.length > 0) {
- elem.attrs({'subtype': tmp.params[0]});
- }
- elem.up();
- }
- });
-};
-
-SDP.prototype.RtcpFbFromJingle = function (elem, payloadtype) { // XEP-0293
- var media = '';
- var tmp = elem.find('>rtcp-fb-trr-int[xmlns="urn:xmpp:jingle:apps:rtp:rtcp-fb:0"]');
- if (tmp.length) {
- media += 'a=rtcp-fb:' + '*' + ' ' + 'trr-int' + ' ';
- if (tmp.attr('value')) {
- media += tmp.attr('value');
- } else {
- media += '0';
- }
- media += '\r\n';
- }
- tmp = elem.find('>rtcp-fb[xmlns="urn:xmpp:jingle:apps:rtp:rtcp-fb:0"]');
- tmp.each(function () {
- media += 'a=rtcp-fb:' + payloadtype + ' ' + $(this).attr('type');
- if ($(this).attr('subtype')) {
- media += ' ' + $(this).attr('subtype');
- }
- media += '\r\n';
- });
- return media;
-};
-
-// construct an SDP from a jingle stanza
-SDP.prototype.fromJingle = function (jingle) {
- var self = this;
- this.raw = 'v=0\r\n' +
- 'o=- ' + '1923518516' + ' 2 IN IP4 0.0.0.0\r\n' +// FIXME
- 's=-\r\n' +
- 't=0 0\r\n';
- // http://tools.ietf.org/html/draft-ietf-mmusic-sdp-bundle-negotiation-04#section-8
- if ($(jingle).find('>group[xmlns="urn:xmpp:jingle:apps:grouping:0"]').length) {
- $(jingle).find('>group[xmlns="urn:xmpp:jingle:apps:grouping:0"]').each(function (idx, group) {
- var contents = $(group).find('>content').map(function (idx, content) {
- return content.getAttribute('name');
- }).get();
- if (contents.length > 0) {
- self.raw += 'a=group:' + (group.getAttribute('semantics') || group.getAttribute('type')) + ' ' + contents.join(' ') + '\r\n';
- }
- });
- }
-
- this.session = this.raw;
- jingle.find('>content').each(function () {
- var m = self.jingle2media($(this));
- self.media.push(m);
- });
-
- // reconstruct msid-semantic -- apparently not necessary
- /*
- var msid = SDPUtil.parse_ssrc(this.raw);
- if (msid.hasOwnProperty('mslabel')) {
- this.session += "a=msid-semantic: WMS " + msid.mslabel + "\r\n";
- }
- */
-
- this.raw = this.session + this.media.join('');
-};
-
-// translate a jingle content element into an an SDP media part
-SDP.prototype.jingle2media = function (content) {
- var media = '',
- desc = content.find('description'),
- ssrc = desc.attr('ssrc'),
- self = this,
- tmp;
- var sctp = content.find(
- '>transport>sctpmap[xmlns="urn:xmpp:jingle:transports:dtls-sctp:1"]');
-
- tmp = { media: desc.attr('media') };
- tmp.port = '1';
- if (content.attr('senders') == 'rejected') {
- // estos hack to reject an m-line.
- tmp.port = '0';
- }
- if (content.find('>transport>fingerprint').length || desc.find('encryption').length) {
- if (sctp.length)
- tmp.proto = 'DTLS/SCTP';
- else
- tmp.proto = 'RTP/SAVPF';
- } else {
- tmp.proto = 'RTP/AVPF';
- }
- if (!sctp.length)
- {
- tmp.fmt = desc.find('payload-type').map(
- function () { return this.getAttribute('id'); }).get();
- media += SDPUtil.build_mline(tmp) + '\r\n';
- }
- else
- {
- media += 'm=application 1 DTLS/SCTP ' + sctp.attr('number') + '\r\n';
- media += 'a=sctpmap:' + sctp.attr('number') +
- ' ' + sctp.attr('protocol');
-
- var streamCount = sctp.attr('streams');
- if (streamCount)
- media += ' ' + streamCount + '\r\n';
- else
- media += '\r\n';
- }
-
- media += 'c=IN IP4 0.0.0.0\r\n';
- if (!sctp.length)
- media += 'a=rtcp:1 IN IP4 0.0.0.0\r\n';
- tmp = content.find('>transport[xmlns="urn:xmpp:jingle:transports:ice-udp:1"]');
- if (tmp.length) {
- if (tmp.attr('ufrag')) {
- media += SDPUtil.build_iceufrag(tmp.attr('ufrag')) + '\r\n';
- }
- if (tmp.attr('pwd')) {
- media += SDPUtil.build_icepwd(tmp.attr('pwd')) + '\r\n';
- }
- tmp.find('>fingerprint').each(function () {
- // FIXME: check namespace at some point
- media += 'a=fingerprint:' + this.getAttribute('hash');
- media += ' ' + $(this).text();
- media += '\r\n';
- if (this.getAttribute('setup')) {
- media += 'a=setup:' + this.getAttribute('setup') + '\r\n';
- }
- });
- }
- switch (content.attr('senders')) {
- case 'initiator':
- media += 'a=sendonly\r\n';
- break;
- case 'responder':
- media += 'a=recvonly\r\n';
- break;
- case 'none':
- media += 'a=inactive\r\n';
- break;
- case 'both':
- media += 'a=sendrecv\r\n';
- break;
- }
- media += 'a=mid:' + content.attr('name') + '\r\n';
-
- //
- // see http://code.google.com/p/libjingle/issues/detail?id=309 -- no spec though
- // and http://mail.jabber.org/pipermail/jingle/2011-December/001761.html
- if (desc.find('rtcp-mux').length) {
- media += 'a=rtcp-mux\r\n';
- }
-
- if (desc.find('encryption').length) {
- desc.find('encryption>crypto').each(function () {
- media += 'a=crypto:' + this.getAttribute('tag');
- media += ' ' + this.getAttribute('crypto-suite');
- media += ' ' + this.getAttribute('key-params');
- if (this.getAttribute('session-params')) {
- media += ' ' + this.getAttribute('session-params');
- }
- media += '\r\n';
- });
- }
- desc.find('payload-type').each(function () {
- media += SDPUtil.build_rtpmap(this) + '\r\n';
- if ($(this).find('>parameter').length) {
- media += 'a=fmtp:' + this.getAttribute('id') + ' ';
- media += $(this).find('parameter').map(function () { return (this.getAttribute('name') ? (this.getAttribute('name') + '=') : '') + this.getAttribute('value'); }).get().join('; ');
- media += '\r\n';
- }
- // xep-0293
- media += self.RtcpFbFromJingle($(this), this.getAttribute('id'));
- });
-
- // xep-0293
- media += self.RtcpFbFromJingle(desc, '*');
-
- // xep-0294
- tmp = desc.find('>rtp-hdrext[xmlns="urn:xmpp:jingle:apps:rtp:rtp-hdrext:0"]');
- tmp.each(function () {
- media += 'a=extmap:' + this.getAttribute('id') + ' ' + this.getAttribute('uri') + '\r\n';
- });
-
- content.find('>transport[xmlns="urn:xmpp:jingle:transports:ice-udp:1"]>candidate').each(function () {
- media += SDPUtil.candidateFromJingle(this);
- });
-
- // XEP-0339 handle ssrc-group attributes
- tmp = content.find('description>ssrc-group[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]').each(function() {
- var semantics = this.getAttribute('semantics');
- var ssrcs = $(this).find('>source').map(function() {
- return this.getAttribute('ssrc');
- }).get();
-
- if (ssrcs.length != 0) {
- media += 'a=ssrc-group:' + semantics + ' ' + ssrcs.join(' ') + '\r\n';
- }
- });
-
- tmp = content.find('description>source[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]');
- tmp.each(function () {
- var ssrc = this.getAttribute('ssrc');
- $(this).find('>parameter').each(function () {
- media += 'a=ssrc:' + ssrc + ' ' + this.getAttribute('name');
- if (this.getAttribute('value') && this.getAttribute('value').length)
- media += ':' + this.getAttribute('value');
- media += '\r\n';
- });
- });
-
- return media;
-};
-
-
-module.exports = SDP;
-
-
-},{"./SDPUtil":54}],53:[function(require,module,exports){
-function SDPDiffer(mySDP, otherSDP) {
- this.mySDP = mySDP;
- this.otherSDP = otherSDP;
-}
-
-/**
- * Returns map of MediaChannel that contains only media not contained in otherSdp . Mapped by channel idx.
- * @param otherSdp the other SDP to check ssrc with.
- */
-SDPDiffer.prototype.getNewMedia = function() {
-
- // this could be useful in Array.prototype.
- function arrayEquals(array) {
- // if the other array is a falsy value, return
- if (!array)
- return false;
-
- // compare lengths - can save a lot of time
- if (this.length != array.length)
- return false;
-
- for (var i = 0, l=this.length; i < l; i++) {
- // Check if we have nested arrays
- if (this[i] instanceof Array && array[i] instanceof Array) {
- // recurse into the nested arrays
- if (!this[i].equals(array[i]))
- return false;
- }
- else if (this[i] != array[i]) {
- // Warning - two different object instances will never be equal: {x:20} != {x:20}
- return false;
- }
- }
- return true;
- }
-
- var myMedias = this.mySDP.getMediaSsrcMap();
- var othersMedias = this.otherSDP.getMediaSsrcMap();
- var newMedia = {};
- Object.keys(othersMedias).forEach(function(othersMediaIdx) {
- var myMedia = myMedias[othersMediaIdx];
- var othersMedia = othersMedias[othersMediaIdx];
- if(!myMedia && othersMedia) {
- // Add whole channel
- newMedia[othersMediaIdx] = othersMedia;
- return;
- }
- // Look for new ssrcs accross the channel
- Object.keys(othersMedia.ssrcs).forEach(function(ssrc) {
- if(Object.keys(myMedia.ssrcs).indexOf(ssrc) === -1) {
- // Allocate channel if we've found ssrc that doesn't exist in our channel
- if(!newMedia[othersMediaIdx]){
- newMedia[othersMediaIdx] = {
- mediaindex: othersMedia.mediaindex,
- mid: othersMedia.mid,
- ssrcs: {},
- ssrcGroups: []
- };
- }
- newMedia[othersMediaIdx].ssrcs[ssrc] = othersMedia.ssrcs[ssrc];
- }
- });
-
- // Look for new ssrc groups across the channels
- othersMedia.ssrcGroups.forEach(function(otherSsrcGroup){
-
- // try to match the other ssrc-group with an ssrc-group of ours
- var matched = false;
- for (var i = 0; i < myMedia.ssrcGroups.length; i++) {
- var mySsrcGroup = myMedia.ssrcGroups[i];
- if (otherSsrcGroup.semantics == mySsrcGroup.semantics
- && arrayEquals.apply(otherSsrcGroup.ssrcs, [mySsrcGroup.ssrcs])) {
-
- matched = true;
- break;
- }
- }
-
- if (!matched) {
- // Allocate channel if we've found an ssrc-group that doesn't
- // exist in our channel
-
- if(!newMedia[othersMediaIdx]){
- newMedia[othersMediaIdx] = {
- mediaindex: othersMedia.mediaindex,
- mid: othersMedia.mid,
- ssrcs: {},
- ssrcGroups: []
- };
- }
- newMedia[othersMediaIdx].ssrcGroups.push(otherSsrcGroup);
- }
- });
- });
- return newMedia;
-};
-
-/**
- * Sends SSRC update IQ.
- * @param sdpMediaSsrcs SSRCs map obtained from SDP.getNewMedia. Cntains SSRCs to add/remove.
- * @param sid session identifier that will be put into the IQ.
- * @param initiator initiator identifier.
- * @param toJid destination Jid
- * @param isAdd indicates if this is remove or add operation.
- */
-SDPDiffer.prototype.toJingle = function(modify) {
- var sdpMediaSsrcs = this.getNewMedia();
- var self = this;
-
- // FIXME: only announce video ssrcs since we mix audio and dont need
- // the audio ssrcs therefore
- var modified = false;
- Object.keys(sdpMediaSsrcs).forEach(function(mediaindex){
- modified = true;
- var media = sdpMediaSsrcs[mediaindex];
- modify.c('content', {name: media.mid});
-
- modify.c('description', {xmlns:'urn:xmpp:jingle:apps:rtp:1', media: media.mid});
- // FIXME: not completly sure this operates on blocks and / or handles different ssrcs correctly
- // generate sources from lines
- Object.keys(media.ssrcs).forEach(function(ssrcNum) {
- var mediaSsrc = media.ssrcs[ssrcNum];
- modify.c('source', { xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' });
- modify.attrs({ssrc: mediaSsrc.ssrc});
- // iterate over ssrc lines
- mediaSsrc.lines.forEach(function (line) {
- var idx = line.indexOf(' ');
- var kv = line.substr(idx + 1);
- modify.c('parameter');
- if (kv.indexOf(':') == -1) {
- modify.attrs({ name: kv });
- } else {
- modify.attrs({ name: kv.split(':', 2)[0] });
- modify.attrs({ value: kv.split(':', 2)[1] });
- }
- modify.up(); // end of parameter
- });
- modify.up(); // end of source
- });
-
- // generate source groups from lines
- media.ssrcGroups.forEach(function(ssrcGroup) {
- if (ssrcGroup.ssrcs.length != 0) {
-
- modify.c('ssrc-group', {
- semantics: ssrcGroup.semantics,
- xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0'
- });
-
- ssrcGroup.ssrcs.forEach(function (ssrc) {
- modify.c('source', { ssrc: ssrc })
- .up(); // end of source
- });
- modify.up(); // end of ssrc-group
- }
- });
-
- modify.up(); // end of description
- modify.up(); // end of content
- });
-
- return modified;
-};
-
+},{"../../service/RTC/RTCEvents":100,"../../service/RTC/StreamEventTypes.js":102,"../../service/xmpp/XMPPEvents":109,"./LocalStatsCollector.js":48,"./RTPStatsCollector.js":49,"./callstats":50,"events":1}],52:[function(require,module,exports){
+var i18n = require("i18next-client");
+var languages = require("../../service/translation/languages");
+var Settings = require("../settings/Settings");
+var DEFAULT_LANG = languages.EN;
+
+i18n.addPostProcessor("resolveAppName", function(value, key, options) {
+ return value.replace("__app__", interfaceConfig.APP_NAME);
+});
+
+
+
+var defaultOptions = {
+ detectLngQS: "lang",
+ useCookie: false,
+ fallbackLng: DEFAULT_LANG,
+ load: "unspecific",
+ resGetPath: 'lang/__ns__-__lng__.json',
+ ns: {
+ namespaces: ['main', 'languages'],
+ defaultNs: 'main'
+ },
+ lngWhitelist : languages.getLanguages(),
+ fallbackOnNull: true,
+ fallbackOnEmpty: true,
+ useDataAttrOptions: true,
+ defaultValueFromContent: false,
+ app: interfaceConfig.APP_NAME,
+ getAsync: false,
+ defaultValueFromContent: false,
+ customLoad: function(lng, ns, options, done) {
+ var resPath = "lang/__ns__-__lng__.json";
+ if(lng === languages.EN)
+ resPath = "lang/__ns__.json";
+ var url = i18n.functions.applyReplacement(resPath, { lng: lng, ns: ns });
+ i18n.functions.ajax({
+ url: url,
+ success: function(data, status, xhr) {
+ i18n.functions.log('loaded: ' + url);
+ done(null, data);
+ },
+ error : function(xhr, status, error) {
+ if ((status && status == 200) ||
+ (xhr && xhr.status && xhr.status == 200)) {
+ // file loaded but invalid json, stop waste time !
+ i18n.functions.error('There is a typo in: ' + url);
+ } else if ((status && status == 404) ||
+ (xhr && xhr.status && xhr.status == 404)) {
+ i18n.functions.log('Does not exist: ' + url);
+ } else {
+ var theStatus = status ? status :
+ ((xhr && xhr.status) ? xhr.status : null);
+ i18n.functions.log(theStatus + ' when loading ' + url);
+ }
+
+ done(error, {});
+ },
+ dataType: "json",
+ async : options.getAsync
+ });
+ }
+ // options for caching
+// useLocalStorage: true,
+// localStorageExpirationTime: 86400000 // in ms, default 1 week
+};
+
+function initCompleted(t)
+{
+ $("[data-i18n]").i18n();
+}
+
+function checkForParameter() {
+ var query = window.location.search.substring(1);
+ var vars = query.split("&");
+ for (var i=0;i";
+ str += this.translateString(key, options);
+ str += "";
+ return str;
+
+ }
+};
+
+},{"../../service/translation/languages":108,"../settings/Settings":47,"i18next-client":68}],53:[function(require,module,exports){
+/* jshint -W117 */
+var TraceablePeerConnection = require("./TraceablePeerConnection");
+var SDPDiffer = require("./SDPDiffer");
+var SDPUtil = require("./SDPUtil");
+var SDP = require("./SDP");
+var async = require("async");
+var transform = require("sdp-transform");
+var XMPPEvents = require("../../service/xmpp/XMPPEvents");
+
+// Jingle stuff
+function JingleSession(me, sid, connection, service, eventEmitter) {
+ this.me = me;
+ this.sid = sid;
+ this.connection = connection;
+ this.initiator = null;
+ this.responder = null;
+ this.isInitiator = null;
+ this.peerjid = null;
+ this.state = null;
+ this.localSDP = null;
+ this.remoteSDP = null;
+ this.relayedStreams = [];
+ this.startTime = null;
+ this.stopTime = null;
+ this.media_constraints = null;
+ this.pc_constraints = null;
+ this.ice_config = {};
+ this.drip_container = [];
+ this.service = service;
+
+ this.usetrickle = true;
+ this.usepranswer = false; // early transport warmup -- mind you, this might fail. depends on webrtc issue 1718
+ this.usedrip = false; // dripping is sending trickle candidates not one-by-one
+
+ this.hadstuncandidate = false;
+ this.hadturncandidate = false;
+ this.lasticecandidate = false;
+
+ this.statsinterval = null;
+
+ this.reason = null;
+
+ this.addssrc = [];
+ this.removessrc = [];
+ this.pendingop = null;
+ this.switchstreams = false;
+
+ this.wait = true;
+ this.localStreamsSSRC = null;
+ this.eventEmitter = eventEmitter;
+
+ /**
+ * The indicator which determines whether the (local) video has been muted
+ * in response to a user command in contrast to an automatic decision made
+ * by the application logic.
+ */
+ this.videoMuteByUser = false;
+ this.modifySourcesQueue = async.queue(this._modifySources.bind(this), 1);
+ // We start with the queue paused. We resume it when the signaling state is
+ // stable and the ice connection state is connected.
+ this.modifySourcesQueue.pause();
+}
+
+JingleSession.prototype.updateModifySourcesQueue = function() {
+ var signalingState = this.peerconnection.signalingState;
+ var iceConnectionState = this.peerconnection.iceConnectionState;
+ if (signalingState === 'stable' && iceConnectionState === 'connected') {
+ this.modifySourcesQueue.resume();
+ } else {
+ this.modifySourcesQueue.pause();
+ }
+};
+
+JingleSession.prototype.initiate = function (peerjid, isInitiator) {
+ var self = this;
+ if (this.state !== null) {
+ console.error('attempt to initiate on session ' + this.sid +
+ 'in state ' + this.state);
+ return;
+ }
+ this.isInitiator = isInitiator;
+ this.state = 'pending';
+ this.initiator = isInitiator ? this.me : peerjid;
+ this.responder = !isInitiator ? this.me : peerjid;
+ this.peerjid = peerjid;
+ this.hadstuncandidate = false;
+ this.hadturncandidate = false;
+ this.lasticecandidate = false;
+
+ this.peerconnection
+ = new TraceablePeerConnection(
+ this.connection.jingle.ice_config,
+ this.connection.jingle.pc_constraints,
+ this);
+
+ this.peerconnection.onicecandidate = function (event) {
+ self.sendIceCandidate(event.candidate);
+ };
+ this.peerconnection.onaddstream = function (event) {
+ if (event.stream.id !== 'default') {
+ console.log("REMOTE STREAM ADDED: " + event.stream + " - " + event.stream.id);
+ self.remoteStreamAdded(event);
+ } else {
+ // This is a recvonly stream. Clients that implement Unified Plan,
+ // such as Firefox use recvonly "streams/channels/tracks" for
+ // receiving remote stream/tracks, as opposed to Plan B where there
+ // are only 3 channels: audio, video and data.
+ console.log("RECVONLY REMOTE STREAM IGNORED: " + event.stream + " - " + event.stream.id);
+ }
+ };
+ this.peerconnection.onremovestream = function (event) {
+ // Remove the stream from remoteStreams
+ // FIXME: remotestreamremoved.jingle not defined anywhere(unused)
+ $(document).trigger('remotestreamremoved.jingle', [event, self.sid]);
+ };
+ this.peerconnection.onsignalingstatechange = function (event) {
+ if (!(self && self.peerconnection)) return;
+ self.updateModifySourcesQueue();
+ };
+ this.peerconnection.oniceconnectionstatechange = function (event) {
+ if (!(self && self.peerconnection)) return;
+ self.updateModifySourcesQueue();
+ switch (self.peerconnection.iceConnectionState) {
+ case 'connected':
+ this.startTime = new Date();
+ break;
+ case 'disconnected':
+ this.stopTime = new Date();
+ break;
+ case 'failed':
+ self.eventEmitter(XMPPEvents.CONFERENCE_SETUP_FAILED);
+ break;
+ }
+ onIceConnectionStateChange(self.sid, self);
+ };
+ this.peerconnection.onnegotiationneeded = function (event) {
+ self.eventEmitter.emit(XMPPEvents.PEERCONNECTION_READY, self);
+ };
+ // add any local and relayed stream
+ APP.RTC.localStreams.forEach(function(stream) {
+ self.peerconnection.addStream(stream.getOriginalStream());
+ });
+ this.relayedStreams.forEach(function(stream) {
+ self.peerconnection.addStream(stream);
+ });
+};
+
+function onIceConnectionStateChange(sid, session) {
+ switch (session.peerconnection.iceConnectionState) {
+ case 'checking':
+ session.timeChecking = (new Date()).getTime();
+ session.firstconnect = true;
+ break;
+ case 'completed': // on caller side
+ case 'connected':
+ if (session.firstconnect) {
+ session.firstconnect = false;
+ var metadata = {};
+ metadata.setupTime
+ = (new Date()).getTime() - session.timeChecking;
+ session.peerconnection.getStats(function (res) {
+ if(res && res.result) {
+ res.result().forEach(function (report) {
+ if (report.type == 'googCandidatePair' &&
+ report.stat('googActiveConnection') == 'true') {
+ metadata.localCandidateType
+ = report.stat('googLocalCandidateType');
+ metadata.remoteCandidateType
+ = report.stat('googRemoteCandidateType');
+
+ // log pair as well so we can get nice pie
+ // charts
+ metadata.candidatePair
+ = report.stat('googLocalCandidateType') +
+ ';' +
+ report.stat('googRemoteCandidateType');
+
+ if (report.stat('googRemoteAddress').indexOf('[') === 0)
+ {
+ metadata.ipv6 = true;
+ }
+ }
+ });
+ }
+ });
+ }
+ break;
+ }
+}
+
+JingleSession.prototype.accept = function () {
+ var self = this;
+ this.state = 'active';
+
+ var pranswer = this.peerconnection.localDescription;
+ if (!pranswer || pranswer.type != 'pranswer') {
+ return;
+ }
+ console.log('going from pranswer to answer');
+ if (this.usetrickle) {
+ // remove candidates already sent from session-accept
+ var lines = SDPUtil.find_lines(pranswer.sdp, 'a=candidate:');
+ for (var i = 0; i < lines.length; i++) {
+ pranswer.sdp = pranswer.sdp.replace(lines[i] + '\r\n', '');
+ }
+ }
+ while (SDPUtil.find_line(pranswer.sdp, 'a=inactive')) {
+ // FIXME: change any inactive to sendrecv or whatever they were originally
+ pranswer.sdp = pranswer.sdp.replace('a=inactive', 'a=sendrecv');
+ }
+ var prsdp = new SDP(pranswer.sdp);
+ var accept = $iq({to: this.peerjid,
+ type: 'set'})
+ .c('jingle', {xmlns: 'urn:xmpp:jingle:1',
+ action: 'session-accept',
+ initiator: this.initiator,
+ responder: this.responder,
+ sid: this.sid });
+ prsdp.toJingle(accept, this.initiator == this.me ? 'initiator' : 'responder', this.localStreamsSSRC);
+ var sdp = this.peerconnection.localDescription.sdp;
+ while (SDPUtil.find_line(sdp, 'a=inactive')) {
+ // FIXME: change any inactive to sendrecv or whatever they were originally
+ sdp = sdp.replace('a=inactive', 'a=sendrecv');
+ }
+ var self = this;
+ this.peerconnection.setLocalDescription(new RTCSessionDescription({type: 'answer', sdp: sdp}),
+ function () {
+ //console.log('setLocalDescription success');
+ self.setLocalDescription();
+
+ self.connection.sendIQ(accept,
+ function () {
+ var ack = {};
+ ack.source = 'answer';
+ $(document).trigger('ack.jingle', [self.sid, ack]);
+ },
+ function (stanza) {
+ var error = ($(stanza).find('error').length) ? {
+ code: $(stanza).find('error').attr('code'),
+ reason: $(stanza).find('error :first')[0].tagName
+ }:{};
+ error.source = 'answer';
+ JingleSession.onJingleError(self.sid, error);
+ },
+ 10000);
+ },
+ function (e) {
+ console.error('setLocalDescription failed', e);
+ self.eventEmitter.emit(XMPPEvents.CONFERENCE_SETUP_FAILED);
+ }
+ );
+};
+
+JingleSession.prototype.terminate = function (reason) {
+ this.state = 'ended';
+ this.reason = reason;
+ this.peerconnection.close();
+ if (this.statsinterval !== null) {
+ window.clearInterval(this.statsinterval);
+ this.statsinterval = null;
+ }
+};
+
+JingleSession.prototype.active = function () {
+ return this.state == 'active';
+};
+
+JingleSession.prototype.sendIceCandidate = function (candidate) {
+ var self = this;
+ if (candidate && !this.lasticecandidate) {
+ var ice = SDPUtil.iceparams(this.localSDP.media[candidate.sdpMLineIndex], this.localSDP.session);
+ var jcand = SDPUtil.candidateToJingle(candidate.candidate);
+ if (!(ice && jcand)) {
+ console.error('failed to get ice && jcand');
+ return;
+ }
+ ice.xmlns = 'urn:xmpp:jingle:transports:ice-udp:1';
+
+ if (jcand.type === 'srflx') {
+ this.hadstuncandidate = true;
+ } else if (jcand.type === 'relay') {
+ this.hadturncandidate = true;
+ }
+
+ if (this.usetrickle) {
+ if (this.usedrip) {
+ if (this.drip_container.length === 0) {
+ // start 20ms callout
+ window.setTimeout(function () {
+ if (self.drip_container.length === 0) return;
+ self.sendIceCandidates(self.drip_container);
+ self.drip_container = [];
+ }, 20);
+
+ }
+ this.drip_container.push(candidate);
+ return;
+ } else {
+ self.sendIceCandidate([candidate]);
+ }
+ }
+ } else {
+ //console.log('sendIceCandidate: last candidate.');
+ if (!this.usetrickle) {
+ //console.log('should send full offer now...');
+ var init = $iq({to: this.peerjid,
+ type: 'set'})
+ .c('jingle', {xmlns: 'urn:xmpp:jingle:1',
+ action: this.peerconnection.localDescription.type == 'offer' ? 'session-initiate' : 'session-accept',
+ initiator: this.initiator,
+ sid: this.sid});
+ this.localSDP = new SDP(this.peerconnection.localDescription.sdp);
+ var self = this;
+ var sendJingle = function (ssrc) {
+ if(!ssrc)
+ ssrc = {};
+ self.localSDP.toJingle(init, self.initiator == self.me ? 'initiator' : 'responder', ssrc);
+ self.connection.sendIQ(init,
+ function () {
+ //console.log('session initiate ack');
+ var ack = {};
+ ack.source = 'offer';
+ $(document).trigger('ack.jingle', [self.sid, ack]);
+ },
+ function (stanza) {
+ self.state = 'error';
+ self.peerconnection.close();
+ var error = ($(stanza).find('error').length) ? {
+ code: $(stanza).find('error').attr('code'),
+ reason: $(stanza).find('error :first')[0].tagName,
+ }:{};
+ error.source = 'offer';
+ JingleSession.onJingleError(self.sid, error);
+ },
+ 10000);
+ }
+ sendJingle();
+ }
+ this.lasticecandidate = true;
+ console.log('Have we encountered any srflx candidates? ' + this.hadstuncandidate);
+ console.log('Have we encountered any relay candidates? ' + this.hadturncandidate);
+
+ if (!(this.hadstuncandidate || this.hadturncandidate) && this.peerconnection.signalingState != 'closed') {
+ $(document).trigger('nostuncandidates.jingle', [this.sid]);
+ }
+ }
+};
+
+JingleSession.prototype.sendIceCandidates = function (candidates) {
+ console.log('sendIceCandidates', candidates);
+ var cand = $iq({to: this.peerjid, type: 'set'})
+ .c('jingle', {xmlns: 'urn:xmpp:jingle:1',
+ action: 'transport-info',
+ initiator: this.initiator,
+ sid: this.sid});
+ for (var mid = 0; mid < this.localSDP.media.length; mid++) {
+ var cands = candidates.filter(function (el) { return el.sdpMLineIndex == mid; });
+ var mline = SDPUtil.parse_mline(this.localSDP.media[mid].split('\r\n')[0]);
+ if (cands.length > 0) {
+ var ice = SDPUtil.iceparams(this.localSDP.media[mid], this.localSDP.session);
+ ice.xmlns = 'urn:xmpp:jingle:transports:ice-udp:1';
+ cand.c('content', {creator: this.initiator == this.me ? 'initiator' : 'responder',
+ name: (cands[0].sdpMid? cands[0].sdpMid : mline.media)
+ }).c('transport', ice);
+ for (var i = 0; i < cands.length; i++) {
+ cand.c('candidate', SDPUtil.candidateToJingle(cands[i].candidate)).up();
+ }
+ // add fingerprint
+ if (SDPUtil.find_line(this.localSDP.media[mid], 'a=fingerprint:', this.localSDP.session)) {
+ var tmp = SDPUtil.parse_fingerprint(SDPUtil.find_line(this.localSDP.media[mid], 'a=fingerprint:', this.localSDP.session));
+ tmp.required = true;
+ cand.c(
+ 'fingerprint',
+ {xmlns: 'urn:xmpp:jingle:apps:dtls:0'})
+ .t(tmp.fingerprint);
+ delete tmp.fingerprint;
+ cand.attrs(tmp);
+ cand.up();
+ }
+ cand.up(); // transport
+ cand.up(); // content
+ }
+ }
+ // might merge last-candidate notification into this, but it is called alot later. See webrtc issue #2340
+ //console.log('was this the last candidate', this.lasticecandidate);
+ this.connection.sendIQ(cand,
+ function () {
+ var ack = {};
+ ack.source = 'transportinfo';
+ $(document).trigger('ack.jingle', [this.sid, ack]);
+ },
+ function (stanza) {
+ var error = ($(stanza).find('error').length) ? {
+ code: $(stanza).find('error').attr('code'),
+ reason: $(stanza).find('error :first')[0].tagName,
+ }:{};
+ error.source = 'transportinfo';
+ JingleSession.onJingleError(this.sid, error);
+ },
+ 10000);
+};
+
+
+JingleSession.prototype.sendOffer = function () {
+ //console.log('sendOffer...');
+ var self = this;
+ this.peerconnection.createOffer(function (sdp) {
+ self.createdOffer(sdp);
+ },
+ function (e) {
+ console.error('createOffer failed', e);
+ },
+ this.media_constraints
+ );
+};
+
+JingleSession.prototype.createdOffer = function (sdp) {
+ //console.log('createdOffer', sdp);
+ var self = this;
+ this.localSDP = new SDP(sdp.sdp);
+ //this.localSDP.mangle();
+ var sendJingle = function () {
+ var init = $iq({to: this.peerjid,
+ type: 'set'})
+ .c('jingle', {xmlns: 'urn:xmpp:jingle:1',
+ action: 'session-initiate',
+ initiator: this.initiator,
+ sid: this.sid});
+ self.localSDP.toJingle(init, this.initiator == this.me ? 'initiator' : 'responder', this.localStreamsSSRC);
+ self.connection.sendIQ(init,
+ function () {
+ var ack = {};
+ ack.source = 'offer';
+ $(document).trigger('ack.jingle', [self.sid, ack]);
+ },
+ function (stanza) {
+ self.state = 'error';
+ self.peerconnection.close();
+ var error = ($(stanza).find('error').length) ? {
+ code: $(stanza).find('error').attr('code'),
+ reason: $(stanza).find('error :first')[0].tagName,
+ }:{};
+ error.source = 'offer';
+ JingleSession.onJingleError(self.sid, error);
+ },
+ 10000);
+ }
+ sdp.sdp = this.localSDP.raw;
+ this.peerconnection.setLocalDescription(sdp,
+ function () {
+ if(self.usetrickle)
+ {
+ sendJingle();
+ }
+ self.setLocalDescription();
+ //console.log('setLocalDescription success');
+ },
+ function (e) {
+ console.error('setLocalDescription failed', e);
+ self.eventEmitter.emit(XMPPEvents.CONFERENCE_SETUP_FAILED);
+ }
+ );
+ var cands = SDPUtil.find_lines(this.localSDP.raw, 'a=candidate:');
+ for (var i = 0; i < cands.length; i++) {
+ var cand = SDPUtil.parse_icecandidate(cands[i]);
+ if (cand.type == 'srflx') {
+ this.hadstuncandidate = true;
+ } else if (cand.type == 'relay') {
+ this.hadturncandidate = true;
+ }
+ }
+};
+
+JingleSession.prototype.setRemoteDescription = function (elem, desctype) {
+ //console.log('setting remote description... ', desctype);
+ this.remoteSDP = new SDP('');
+ this.remoteSDP.fromJingle(elem);
+ if (this.peerconnection.remoteDescription !== null) {
+ console.log('setRemoteDescription when remote description is not null, should be pranswer', this.peerconnection.remoteDescription);
+ if (this.peerconnection.remoteDescription.type == 'pranswer') {
+ var pranswer = new SDP(this.peerconnection.remoteDescription.sdp);
+ for (var i = 0; i < pranswer.media.length; i++) {
+ // make sure we have ice ufrag and pwd
+ if (!SDPUtil.find_line(this.remoteSDP.media[i], 'a=ice-ufrag:', this.remoteSDP.session)) {
+ if (SDPUtil.find_line(pranswer.media[i], 'a=ice-ufrag:', pranswer.session)) {
+ this.remoteSDP.media[i] += SDPUtil.find_line(pranswer.media[i], 'a=ice-ufrag:', pranswer.session) + '\r\n';
+ } else {
+ console.warn('no ice ufrag?');
+ }
+ if (SDPUtil.find_line(pranswer.media[i], 'a=ice-pwd:', pranswer.session)) {
+ this.remoteSDP.media[i] += SDPUtil.find_line(pranswer.media[i], 'a=ice-pwd:', pranswer.session) + '\r\n';
+ } else {
+ console.warn('no ice pwd?');
+ }
+ }
+ // copy over candidates
+ var lines = SDPUtil.find_lines(pranswer.media[i], 'a=candidate:');
+ for (var j = 0; j < lines.length; j++) {
+ this.remoteSDP.media[i] += lines[j] + '\r\n';
+ }
+ }
+ this.remoteSDP.raw = this.remoteSDP.session + this.remoteSDP.media.join('');
+ }
+ }
+ var remotedesc = new RTCSessionDescription({type: desctype, sdp: this.remoteSDP.raw});
+
+ this.peerconnection.setRemoteDescription(remotedesc,
+ function () {
+ //console.log('setRemoteDescription success');
+ },
+ function (e) {
+ console.error('setRemoteDescription error', e);
+ JingleSession.onJingleFatalError(self, e);
+ }
+ );
+};
+
+JingleSession.prototype.addIceCandidate = function (elem) {
+ var self = this;
+ if (this.peerconnection.signalingState == 'closed') {
+ return;
+ }
+ if (!this.peerconnection.remoteDescription && this.peerconnection.signalingState == 'have-local-offer') {
+ console.log('trickle ice candidate arriving before session accept...');
+ // create a PRANSWER for setRemoteDescription
+ if (!this.remoteSDP) {
+ var cobbled = 'v=0\r\n' +
+ 'o=- ' + '1923518516' + ' 2 IN IP4 0.0.0.0\r\n' +// FIXME
+ 's=-\r\n' +
+ 't=0 0\r\n';
+ // first, take some things from the local description
+ for (var i = 0; i < this.localSDP.media.length; i++) {
+ cobbled += SDPUtil.find_line(this.localSDP.media[i], 'm=') + '\r\n';
+ cobbled += SDPUtil.find_lines(this.localSDP.media[i], 'a=rtpmap:').join('\r\n') + '\r\n';
+ if (SDPUtil.find_line(this.localSDP.media[i], 'a=mid:')) {
+ cobbled += SDPUtil.find_line(this.localSDP.media[i], 'a=mid:') + '\r\n';
+ }
+ cobbled += 'a=inactive\r\n';
+ }
+ this.remoteSDP = new SDP(cobbled);
+ }
+ // then add things like ice and dtls from remote candidate
+ elem.each(function () {
+ for (var i = 0; i < self.remoteSDP.media.length; i++) {
+ if (SDPUtil.find_line(self.remoteSDP.media[i], 'a=mid:' + $(this).attr('name')) ||
+ self.remoteSDP.media[i].indexOf('m=' + $(this).attr('name')) === 0) {
+ if (!SDPUtil.find_line(self.remoteSDP.media[i], 'a=ice-ufrag:')) {
+ var tmp = $(this).find('transport');
+ self.remoteSDP.media[i] += 'a=ice-ufrag:' + tmp.attr('ufrag') + '\r\n';
+ self.remoteSDP.media[i] += 'a=ice-pwd:' + tmp.attr('pwd') + '\r\n';
+ tmp = $(this).find('transport>fingerprint');
+ if (tmp.length) {
+ self.remoteSDP.media[i] += 'a=fingerprint:' + tmp.attr('hash') + ' ' + tmp.text() + '\r\n';
+ } else {
+ console.log('no dtls fingerprint (webrtc issue #1718?)');
+ self.remoteSDP.media[i] += 'a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:BAADBAADBAADBAADBAADBAADBAADBAADBAADBAAD\r\n';
+ }
+ break;
+ }
+ }
+ }
+ });
+ this.remoteSDP.raw = this.remoteSDP.session + this.remoteSDP.media.join('');
+
+ // we need a complete SDP with ice-ufrag/ice-pwd in all parts
+ // this makes the assumption that the PRANSWER is constructed such that the ice-ufrag is in all mediaparts
+ // but it could be in the session part as well. since the code above constructs this sdp this can't happen however
+ var iscomplete = this.remoteSDP.media.filter(function (mediapart) {
+ return SDPUtil.find_line(mediapart, 'a=ice-ufrag:');
+ }).length == this.remoteSDP.media.length;
+
+ if (iscomplete) {
+ console.log('setting pranswer');
+ try {
+ this.peerconnection.setRemoteDescription(new RTCSessionDescription({type: 'pranswer', sdp: this.remoteSDP.raw }),
+ function() {
+ },
+ function(e) {
+ console.log('setRemoteDescription pranswer failed', e.toString());
+ });
+ } catch (e) {
+ console.error('setting pranswer failed', e);
+ }
+ } else {
+ //console.log('not yet setting pranswer');
+ }
+ }
+ // operate on each content element
+ elem.each(function () {
+ // would love to deactivate this, but firefox still requires it
+ var idx = -1;
+ var i;
+ for (i = 0; i < self.remoteSDP.media.length; i++) {
+ if (SDPUtil.find_line(self.remoteSDP.media[i], 'a=mid:' + $(this).attr('name')) ||
+ self.remoteSDP.media[i].indexOf('m=' + $(this).attr('name')) === 0) {
+ idx = i;
+ break;
+ }
+ }
+ if (idx == -1) { // fall back to localdescription
+ for (i = 0; i < self.localSDP.media.length; i++) {
+ if (SDPUtil.find_line(self.localSDP.media[i], 'a=mid:' + $(this).attr('name')) ||
+ self.localSDP.media[i].indexOf('m=' + $(this).attr('name')) === 0) {
+ idx = i;
+ break;
+ }
+ }
+ }
+ var name = $(this).attr('name');
+ // TODO: check ice-pwd and ice-ufrag?
+ $(this).find('transport>candidate').each(function () {
+ var line, candidate;
+ line = SDPUtil.candidateFromJingle(this);
+ candidate = new RTCIceCandidate({sdpMLineIndex: idx,
+ sdpMid: name,
+ candidate: line});
+ try {
+ self.peerconnection.addIceCandidate(candidate);
+ } catch (e) {
+ console.error('addIceCandidate failed', e.toString(), line);
+ }
+ });
+ });
+};
+
+JingleSession.prototype.sendAnswer = function (provisional) {
+ //console.log('createAnswer', provisional);
+ var self = this;
+ this.peerconnection.createAnswer(
+ function (sdp) {
+ self.createdAnswer(sdp, provisional);
+ },
+ function (e) {
+ console.error('createAnswer failed', e);
+ self.eventEmitter.emit(XMPPEvents.CONFERENCE_SETUP_FAILED);
+ },
+ this.media_constraints
+ );
+};
+
+JingleSession.prototype.createdAnswer = function (sdp, provisional) {
+ //console.log('createAnswer callback');
+ var self = this;
+ this.localSDP = new SDP(sdp.sdp);
+ //this.localSDP.mangle();
+ this.usepranswer = provisional === true;
+ if (this.usetrickle) {
+ if (this.usepranswer) {
+ sdp.type = 'pranswer';
+ for (var i = 0; i < this.localSDP.media.length; i++) {
+ this.localSDP.media[i] = this.localSDP.media[i].replace('a=sendrecv\r\n', 'a=inactive\r\n');
+ }
+ this.localSDP.raw = this.localSDP.session + '\r\n' + this.localSDP.media.join('');
+ }
+ }
+ var self = this;
+ var sendJingle = function (ssrcs) {
+
+ var accept = $iq({to: self.peerjid,
+ type: 'set'})
+ .c('jingle', {xmlns: 'urn:xmpp:jingle:1',
+ action: 'session-accept',
+ initiator: self.initiator,
+ responder: self.responder,
+ sid: self.sid });
+ self.localSDP.toJingle(accept, self.initiator == self.me ? 'initiator' : 'responder', ssrcs);
+ self.connection.sendIQ(accept,
+ function () {
+ var ack = {};
+ ack.source = 'answer';
+ $(document).trigger('ack.jingle', [self.sid, ack]);
+ },
+ function (stanza) {
+ var error = ($(stanza).find('error').length) ? {
+ code: $(stanza).find('error').attr('code'),
+ reason: $(stanza).find('error :first')[0].tagName,
+ }:{};
+ error.source = 'answer';
+ JingleSession.onJingleError(self.sid, error);
+ },
+ 10000);
+ }
+ sdp.sdp = this.localSDP.raw;
+ this.peerconnection.setLocalDescription(sdp,
+ function () {
+
+ //console.log('setLocalDescription success');
+ if (self.usetrickle && !self.usepranswer) {
+ sendJingle();
+ }
+ self.setLocalDescription();
+ },
+ function (e) {
+ console.error('setLocalDescription failed', e);
+ self.eventEmitter.emit(XMPPEvents.CONFERENCE_SETUP_FAILED);
+ }
+ );
+ var cands = SDPUtil.find_lines(this.localSDP.raw, 'a=candidate:');
+ for (var j = 0; j < cands.length; j++) {
+ var cand = SDPUtil.parse_icecandidate(cands[j]);
+ if (cand.type == 'srflx') {
+ this.hadstuncandidate = true;
+ } else if (cand.type == 'relay') {
+ this.hadturncandidate = true;
+ }
+ }
+};
+
+JingleSession.prototype.sendTerminate = function (reason, text) {
+ var self = this,
+ term = $iq({to: this.peerjid,
+ type: 'set'})
+ .c('jingle', {xmlns: 'urn:xmpp:jingle:1',
+ action: 'session-terminate',
+ initiator: this.initiator,
+ sid: this.sid})
+ .c('reason')
+ .c(reason || 'success');
+
+ if (text) {
+ term.up().c('text').t(text);
+ }
+
+ this.connection.sendIQ(term,
+ function () {
+ self.peerconnection.close();
+ self.peerconnection = null;
+ self.terminate();
+ var ack = {};
+ ack.source = 'terminate';
+ $(document).trigger('ack.jingle', [self.sid, ack]);
+ },
+ function (stanza) {
+ var error = ($(stanza).find('error').length) ? {
+ code: $(stanza).find('error').attr('code'),
+ reason: $(stanza).find('error :first')[0].tagName,
+ }:{};
+ $(document).trigger('ack.jingle', [self.sid, error]);
+ },
+ 10000);
+ if (this.statsinterval !== null) {
+ window.clearInterval(this.statsinterval);
+ this.statsinterval = null;
+ }
+};
+
+JingleSession.prototype.addSource = function (elem, fromJid) {
+
+ var self = this;
+ // FIXME: dirty waiting
+ if (!this.peerconnection.localDescription)
+ {
+ console.warn("addSource - localDescription not ready yet")
+ setTimeout(function()
+ {
+ self.addSource(elem, fromJid);
+ },
+ 200
+ );
+ return;
+ }
+
+ console.log('addssrc', new Date().getTime());
+ console.log('ice', this.peerconnection.iceConnectionState);
+ var sdp = new SDP(this.peerconnection.remoteDescription.sdp);
+ var mySdp = new SDP(this.peerconnection.localDescription.sdp);
+
+ $(elem).each(function (idx, content) {
+ var name = $(content).attr('name');
+ var lines = '';
+ $(content).find('ssrc-group[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]').each(function() {
+ var semantics = this.getAttribute('semantics');
+ var ssrcs = $(this).find('>source').map(function () {
+ return this.getAttribute('ssrc');
+ }).get();
+
+ if (ssrcs.length != 0) {
+ lines += 'a=ssrc-group:' + semantics + ' ' + ssrcs.join(' ') + '\r\n';
+ }
+ });
+ var tmp = $(content).find('source[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]'); // can handle both >source and >description>source
+ tmp.each(function () {
+ var ssrc = $(this).attr('ssrc');
+ if(mySdp.containsSSRC(ssrc)){
+ /**
+ * This happens when multiple participants change their streams at the same time and
+ * ColibriFocus.modifySources have to wait for stable state. In the meantime multiple
+ * addssrc are scheduled for update IQ. See
+ */
+ console.warn("Got add stream request for my own ssrc: "+ssrc);
+ return;
+ }
+ $(this).find('>parameter').each(function () {
+ lines += 'a=ssrc:' + ssrc + ' ' + $(this).attr('name');
+ if ($(this).attr('value') && $(this).attr('value').length)
+ lines += ':' + $(this).attr('value');
+ lines += '\r\n';
+ });
+ });
+ sdp.media.forEach(function(media, idx) {
+ if (!SDPUtil.find_line(media, 'a=mid:' + name))
+ return;
+ sdp.media[idx] += lines;
+ if (!self.addssrc[idx]) self.addssrc[idx] = '';
+ self.addssrc[idx] += lines;
+ });
+ sdp.raw = sdp.session + sdp.media.join('');
+ });
+
+ this.modifySourcesQueue.push(function() {
+ // When a source is added and if this is FF, a new channel is allocated
+ // for receiving the added source. We need to diffuse the SSRC of this
+ // new recvonly channel to the rest of the peers.
+ console.log('modify sources done');
+
+ var newSdp = new SDP(self.peerconnection.localDescription.sdp);
+ console.log("SDPs", mySdp, newSdp);
+ self.notifyMySSRCUpdate(mySdp, newSdp);
+ });
+};
+
+JingleSession.prototype.removeSource = function (elem, fromJid) {
+
+ var self = this;
+ // FIXME: dirty waiting
+ if (!this.peerconnection.localDescription)
+ {
+ console.warn("removeSource - localDescription not ready yet")
+ setTimeout(function()
+ {
+ self.removeSource(elem, fromJid);
+ },
+ 200
+ );
+ return;
+ }
+
+ console.log('removessrc', new Date().getTime());
+ console.log('ice', this.peerconnection.iceConnectionState);
+ var sdp = new SDP(this.peerconnection.remoteDescription.sdp);
+ var mySdp = new SDP(this.peerconnection.localDescription.sdp);
+
+ $(elem).each(function (idx, content) {
+ var name = $(content).attr('name');
+ var lines = '';
+ $(content).find('ssrc-group[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]').each(function() {
+ var semantics = this.getAttribute('semantics');
+ var ssrcs = $(this).find('>source').map(function () {
+ return this.getAttribute('ssrc');
+ }).get();
+
+ if (ssrcs.length != 0) {
+ lines += 'a=ssrc-group:' + semantics + ' ' + ssrcs.join(' ') + '\r\n';
+ }
+ });
+ var tmp = $(content).find('source[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]'); // can handle both >source and >description>source
+ tmp.each(function () {
+ var ssrc = $(this).attr('ssrc');
+ // This should never happen, but can be useful for bug detection
+ if(mySdp.containsSSRC(ssrc)){
+ console.error("Got remove stream request for my own ssrc: "+ssrc);
+ return;
+ }
+ $(this).find('>parameter').each(function () {
+ lines += 'a=ssrc:' + ssrc + ' ' + $(this).attr('name');
+ if ($(this).attr('value') && $(this).attr('value').length)
+ lines += ':' + $(this).attr('value');
+ lines += '\r\n';
+ });
+ });
+ sdp.media.forEach(function(media, idx) {
+ if (!SDPUtil.find_line(media, 'a=mid:' + name))
+ return;
+ sdp.media[idx] += lines;
+ if (!self.removessrc[idx]) self.removessrc[idx] = '';
+ self.removessrc[idx] += lines;
+ });
+ sdp.raw = sdp.session + sdp.media.join('');
+ });
+
+ this.modifySourcesQueue.push(function() {
+ // When a source is removed and if this is FF, the recvonly channel that
+ // receives the remote stream is deactivated . We need to diffuse the
+ // recvonly SSRC removal to the rest of the peers.
+ console.log('modify sources done');
+
+ var newSdp = new SDP(self.peerconnection.localDescription.sdp);
+ console.log("SDPs", mySdp, newSdp);
+ self.notifyMySSRCUpdate(mySdp, newSdp);
+ });
+};
+
+JingleSession.prototype._modifySources = function (successCallback, queueCallback) {
+ var self = this;
+
+ if (this.peerconnection.signalingState == 'closed') return;
+ if (!(this.addssrc.length || this.removessrc.length || this.pendingop !== null || this.switchstreams)){
+ // There is nothing to do since scheduled job might have been executed by another succeeding call
+ this.setLocalDescription();
+ if(successCallback){
+ successCallback();
+ }
+ queueCallback();
+ return;
+ }
+
+ // Reset switch streams flag
+ this.switchstreams = false;
+
+ var sdp = new SDP(this.peerconnection.remoteDescription.sdp);
+
+ // add sources
+ this.addssrc.forEach(function(lines, idx) {
+ sdp.media[idx] += lines;
+ });
+ this.addssrc = [];
+
+ // remove sources
+ this.removessrc.forEach(function(lines, idx) {
+ lines = lines.split('\r\n');
+ lines.pop(); // remove empty last element;
+ lines.forEach(function(line) {
+ sdp.media[idx] = sdp.media[idx].replace(line + '\r\n', '');
+ });
+ });
+ this.removessrc = [];
+
+ // FIXME:
+ // this was a hack for the situation when only one peer exists
+ // in the conference.
+ // check if still required and remove
+ if (sdp.media[0])
+ sdp.media[0] = sdp.media[0].replace('a=recvonly', 'a=sendrecv');
+ if (sdp.media[1])
+ sdp.media[1] = sdp.media[1].replace('a=recvonly', 'a=sendrecv');
+
+ sdp.raw = sdp.session + sdp.media.join('');
+ this.peerconnection.setRemoteDescription(new RTCSessionDescription({type: 'offer', sdp: sdp.raw}),
+ function() {
+
+ if(self.signalingState == 'closed') {
+ console.error("createAnswer attempt on closed state");
+ queueCallback("createAnswer attempt on closed state");
+ return;
+ }
+
+ self.peerconnection.createAnswer(
+ function(modifiedAnswer) {
+ // change video direction, see https://github.com/jitsi/jitmeet/issues/41
+ if (self.pendingop !== null) {
+ var sdp = new SDP(modifiedAnswer.sdp);
+ if (sdp.media.length > 1) {
+ switch(self.pendingop) {
+ case 'mute':
+ sdp.media[1] = sdp.media[1].replace('a=sendrecv', 'a=recvonly');
+ break;
+ case 'unmute':
+ sdp.media[1] = sdp.media[1].replace('a=recvonly', 'a=sendrecv');
+ break;
+ }
+ sdp.raw = sdp.session + sdp.media.join('');
+ modifiedAnswer.sdp = sdp.raw;
+ }
+ self.pendingop = null;
+ }
+
+ // FIXME: pushing down an answer while ice connection state
+ // is still checking is bad...
+ //console.log(self.peerconnection.iceConnectionState);
+
+ // trying to work around another chrome bug
+ //modifiedAnswer.sdp = modifiedAnswer.sdp.replace(/a=setup:active/g, 'a=setup:actpass');
+ self.peerconnection.setLocalDescription(modifiedAnswer,
+ function() {
+ //console.log('modified setLocalDescription ok');
+ self.setLocalDescription();
+ if(successCallback){
+ successCallback();
+ }
+ queueCallback();
+ },
+ function(error) {
+ console.error('modified setLocalDescription failed', error);
+ queueCallback(error);
+ }
+ );
+ },
+ function(error) {
+ console.error('modified answer failed', error);
+ queueCallback(error);
+ }
+ );
+ },
+ function(error) {
+ console.error('modify failed', error);
+ queueCallback(error);
+ }
+ );
+};
+
+
+/**
+ * Switches video streams.
+ * @param new_stream new stream that will be used as video of this session.
+ * @param oldStream old video stream of this session.
+ * @param success_callback callback executed after successful stream switch.
+ */
+JingleSession.prototype.switchStreams = function (new_stream, oldStream, success_callback, isAudio) {
+
+ var self = this;
+
+ // Remember SDP to figure out added/removed SSRCs
+ var oldSdp = null;
+ if(self.peerconnection) {
+ if(self.peerconnection.localDescription) {
+ oldSdp = new SDP(self.peerconnection.localDescription.sdp);
+ }
+ self.peerconnection.removeStream(oldStream, true);
+ if(new_stream)
+ self.peerconnection.addStream(new_stream);
+ }
+
+ if(!isAudio)
+ APP.RTC.switchVideoStreams(new_stream, oldStream);
+
+ // Conference is not active
+ if(!oldSdp || !self.peerconnection) {
+ success_callback();
+ return;
+ }
+
+ self.switchstreams = true;
+ self.modifySourcesQueue.push(function() {
+ console.log('modify sources done');
+
+ success_callback();
+
+ var newSdp = new SDP(self.peerconnection.localDescription.sdp);
+ console.log("SDPs", oldSdp, newSdp);
+ self.notifyMySSRCUpdate(oldSdp, newSdp);
+ });
+};
+
+/**
+ * Figures out added/removed ssrcs and send update IQs.
+ * @param old_sdp SDP object for old description.
+ * @param new_sdp SDP object for new description.
+ */
+JingleSession.prototype.notifyMySSRCUpdate = function (old_sdp, new_sdp) {
+
+ if (!(this.peerconnection.signalingState == 'stable' &&
+ this.peerconnection.iceConnectionState == 'connected')){
+ console.log("Too early to send updates");
+ return;
+ }
+
+ // send source-remove IQ.
+ sdpDiffer = new SDPDiffer(new_sdp, old_sdp);
+ var remove = $iq({to: this.peerjid, type: 'set'})
+ .c('jingle', {
+ xmlns: 'urn:xmpp:jingle:1',
+ action: 'source-remove',
+ initiator: this.initiator,
+ sid: this.sid
+ }
+ );
+ var removed = sdpDiffer.toJingle(remove);
+ if (removed) {
+ this.connection.sendIQ(remove,
+ function (res) {
+ console.info('got remove result', res);
+ },
+ function (err) {
+ console.error('got remove error', err);
+ }
+ );
+ } else {
+ console.log('removal not necessary');
+ }
+
+ // send source-add IQ.
+ var sdpDiffer = new SDPDiffer(old_sdp, new_sdp);
+ var add = $iq({to: this.peerjid, type: 'set'})
+ .c('jingle', {
+ xmlns: 'urn:xmpp:jingle:1',
+ action: 'source-add',
+ initiator: this.initiator,
+ sid: this.sid
+ }
+ );
+ var added = sdpDiffer.toJingle(add);
+ if (added) {
+ this.connection.sendIQ(add,
+ function (res) {
+ console.info('got add result', res);
+ },
+ function (err) {
+ console.error('got add error', err);
+ }
+ );
+ } else {
+ console.log('addition not necessary');
+ }
+};
+
+/**
+ * Mutes/unmutes the (local) video i.e. enables/disables all video tracks.
+ *
+ * @param mute true to mute the (local) video i.e. to disable all video
+ * tracks; otherwise, false
+ * @param callback a function to be invoked with mute after all video
+ * tracks have been enabled/disabled. The function may, optionally, return
+ * another function which is to be invoked after the whole mute/unmute operation
+ * has completed successfully.
+ * @param options an object which specifies optional arguments such as the
+ * boolean key byUser with default value true which
+ * specifies whether the method was initiated in response to a user command (in
+ * contrast to an automatic decision made by the application logic)
+ */
+JingleSession.prototype.setVideoMute = function (mute, callback, options) {
+ var byUser;
+
+ if (options) {
+ byUser = options.byUser;
+ if (typeof byUser === 'undefined') {
+ byUser = true;
+ }
+ } else {
+ byUser = true;
+ }
+ // The user's command to mute the (local) video takes precedence over any
+ // automatic decision made by the application logic.
+ if (byUser) {
+ this.videoMuteByUser = mute;
+ } else if (this.videoMuteByUser) {
+ return;
+ }
+
+ this.hardMuteVideo(mute);
+
+ var self = this;
+ var oldSdp = null;
+ if(self.peerconnection) {
+ if(self.peerconnection.localDescription) {
+ oldSdp = new SDP(self.peerconnection.localDescription.sdp);
+ }
+ }
+
+ this.modifySourcesQueue.push(function() {
+ console.log('modify sources done');
+
+ callback(mute);
+
+ var newSdp = new SDP(self.peerconnection.localDescription.sdp);
+ console.log("SDPs", oldSdp, newSdp);
+ self.notifyMySSRCUpdate(oldSdp, newSdp);
+ });
+};
+
+JingleSession.prototype.hardMuteVideo = function (muted) {
+ this.pendingop = muted ? 'mute' : 'unmute';
+};
+
+JingleSession.prototype.sendMute = function (muted, content) {
+ var info = $iq({to: this.peerjid,
+ type: 'set'})
+ .c('jingle', {xmlns: 'urn:xmpp:jingle:1',
+ action: 'session-info',
+ initiator: this.initiator,
+ sid: this.sid });
+ info.c(muted ? 'mute' : 'unmute', {xmlns: 'urn:xmpp:jingle:apps:rtp:info:1'});
+ info.attrs({'creator': this.me == this.initiator ? 'creator' : 'responder'});
+ if (content) {
+ info.attrs({'name': content});
+ }
+ this.connection.send(info);
+};
+
+JingleSession.prototype.sendRinging = function () {
+ var info = $iq({to: this.peerjid,
+ type: 'set'})
+ .c('jingle', {xmlns: 'urn:xmpp:jingle:1',
+ action: 'session-info',
+ initiator: this.initiator,
+ sid: this.sid });
+ info.c('ringing', {xmlns: 'urn:xmpp:jingle:apps:rtp:info:1'});
+ this.connection.send(info);
+};
+
+JingleSession.prototype.getStats = function (interval) {
+ var self = this;
+ var recv = {audio: 0, video: 0};
+ var lost = {audio: 0, video: 0};
+ var lastrecv = {audio: 0, video: 0};
+ var lastlost = {audio: 0, video: 0};
+ var loss = {audio: 0, video: 0};
+ var delta = {audio: 0, video: 0};
+ this.statsinterval = window.setInterval(function () {
+ if (self && self.peerconnection && self.peerconnection.getStats) {
+ self.peerconnection.getStats(function (stats) {
+ var results = stats.result();
+ // TODO: there are so much statistics you can get from this..
+ for (var i = 0; i < results.length; ++i) {
+ if (results[i].type == 'ssrc') {
+ var packetsrecv = results[i].stat('packetsReceived');
+ var packetslost = results[i].stat('packetsLost');
+ if (packetsrecv && packetslost) {
+ packetsrecv = parseInt(packetsrecv, 10);
+ packetslost = parseInt(packetslost, 10);
+
+ if (results[i].stat('googFrameRateReceived')) {
+ lastlost.video = lost.video;
+ lastrecv.video = recv.video;
+ recv.video = packetsrecv;
+ lost.video = packetslost;
+ } else {
+ lastlost.audio = lost.audio;
+ lastrecv.audio = recv.audio;
+ recv.audio = packetsrecv;
+ lost.audio = packetslost;
+ }
+ }
+ }
+ }
+ delta.audio = recv.audio - lastrecv.audio;
+ delta.video = recv.video - lastrecv.video;
+ loss.audio = (delta.audio > 0) ? Math.ceil(100 * (lost.audio - lastlost.audio) / delta.audio) : 0;
+ loss.video = (delta.video > 0) ? Math.ceil(100 * (lost.video - lastlost.video) / delta.video) : 0;
+ $(document).trigger('packetloss.jingle', [self.sid, loss]);
+ });
+ }
+ }, interval || 3000);
+ return this.statsinterval;
+};
+
+JingleSession.onJingleError = function (session, error)
+{
+ console.error("Jingle error", error);
+}
+
+JingleSession.onJingleFatalError = function (session, error)
+{
+ this.service.sessionTerminated = true;
+ this.connection.emuc.doLeave();
+ APP.UI.messageHandler.showError("dialog.sorry",
+ "dialog.internalError");
+ this.eventEmitter.emit(XMPPEvents.CONFERENCE_SETUP_FAILED);
+}
+
+JingleSession.prototype.setLocalDescription = function () {
+ // put our ssrcs into presence so other clients can identify our stream
+ var newssrcs = [];
+ var session = transform.parse(this.peerconnection.localDescription.sdp);
+ session.media.forEach(function (media) {
+
+ if (media.ssrcs != null && media.ssrcs.length > 0) {
+ // TODO(gp) maybe exclude FID streams?
+ media.ssrcs.forEach(function (ssrc) {
+ if (ssrc.attribute !== 'cname') {
+ return;
+ }
+ newssrcs.push({
+ 'ssrc': ssrc.id,
+ 'type': media.type,
+ 'direction': media.direction
+ });
+ });
+ }
+ else if(this.localStreamsSSRC && this.localStreamsSSRC[media.type])
+ {
+ newssrcs.push({
+ 'ssrc': this.localStreamsSSRC[media.type],
+ 'type': media.type,
+ 'direction': media.direction
+ });
+ }
+
+ });
+
+ console.log('new ssrcs', newssrcs);
+
+ // Have to clear presence map to get rid of removed streams
+ this.connection.emuc.clearPresenceMedia();
+
+ if (newssrcs.length > 0) {
+ for (var i = 1; i <= newssrcs.length; i ++) {
+ // Change video type to screen
+ if (newssrcs[i-1].type === 'video' && APP.desktopsharing.isUsingScreenStream()) {
+ newssrcs[i-1].type = 'screen';
+ }
+ this.connection.emuc.addMediaToPresence(i,
+ newssrcs[i-1].type, newssrcs[i-1].ssrc, newssrcs[i-1].direction);
+ }
+
+ this.connection.emuc.sendPresence();
+ }
+}
+
+// an attempt to work around https://github.com/jitsi/jitmeet/issues/32
+function sendKeyframe(pc) {
+ console.log('sendkeyframe', pc.iceConnectionState);
+ if (pc.iceConnectionState !== 'connected') return; // safe...
+ var self = this;
+ pc.setRemoteDescription(
+ pc.remoteDescription,
+ function () {
+ pc.createAnswer(
+ function (modifiedAnswer) {
+ pc.setLocalDescription(
+ modifiedAnswer,
+ function () {
+ // noop
+ },
+ function (error) {
+ console.log('triggerKeyframe setLocalDescription failed', error);
+ APP.UI.messageHandler.showError();
+ }
+ );
+ },
+ function (error) {
+ console.log('triggerKeyframe createAnswer failed', error);
+ APP.UI.messageHandler.showError();
+ }
+ );
+ },
+ function (error) {
+ console.log('triggerKeyframe setRemoteDescription failed', error);
+ APP.UI.messageHandler.showError();
+ }
+ );
+}
+
+
+JingleSession.prototype.remoteStreamAdded = function (data, times) {
+ var self = this;
+ var thessrc;
+ var ssrc2jid = this.connection.emuc.ssrc2jid;
+
+ // look up an associated JID for a stream id
+ if (data.stream.id && data.stream.id.indexOf('mixedmslabel') === -1) {
+ // look only at a=ssrc: and _not_ at a=ssrc-group: lines
+
+ var ssrclines
+ = SDPUtil.find_lines(this.peerconnection.remoteDescription.sdp, 'a=ssrc:');
+ ssrclines = ssrclines.filter(function (line) {
+ // NOTE(gp) previously we filtered on the mslabel, but that property
+ // is not always present.
+ // return line.indexOf('mslabel:' + data.stream.label) !== -1;
+
+ return ((line.indexOf('msid:' + data.stream.id) !== -1));
+ });
+ if (ssrclines.length) {
+ thessrc = ssrclines[0].substring(7).split(' ')[0];
+
+ // We signal our streams (through Jingle to the focus) before we set
+ // our presence (through which peers associate remote streams to
+ // jids). So, it might arrive that a remote stream is added but
+ // ssrc2jid is not yet updated and thus data.peerjid cannot be
+ // successfully set. Here we wait for up to a second for the
+ // presence to arrive.
+
+ if (!ssrc2jid[thessrc]) {
+
+ if (typeof times === 'undefined')
+ {
+ times = 0;
+ }
+
+ if (times > 10)
+ {
+ console.warning('Waiting for jid timed out', thessrc);
+ }
+ else
+ {
+ setTimeout(function(d) {
+ return function() {
+ self.remoteStreamAdded(d, times++);
+ }
+ }(data), 250);
+ }
+ return;
+ }
+
+ // ok to overwrite the one from focus? might save work in colibri.js
+ console.log('associated jid', ssrc2jid[thessrc], data.peerjid);
+ if (ssrc2jid[thessrc]) {
+ data.peerjid = ssrc2jid[thessrc];
+ }
+ }
+ }
+
+ APP.RTC.createRemoteStream(data, this.sid, thessrc);
+
+ var isVideo = data.stream.getVideoTracks().length > 0;
+ // an attempt to work around https://github.com/jitsi/jitmeet/issues/32
+ if (isVideo &&
+ data.peerjid && this.peerjid === data.peerjid &&
+ data.stream.getVideoTracks().length === 0 &&
+ APP.RTC.localVideo.getTracks().length > 0) {
+ window.setTimeout(function () {
+ sendKeyframe(self.peerconnection);
+ }, 3000);
+ }
+}
+
+module.exports = JingleSession;
+
+},{"../../service/xmpp/XMPPEvents":109,"./SDP":54,"./SDPDiffer":55,"./SDPUtil":56,"./TraceablePeerConnection":57,"async":67,"sdp-transform":95}],54:[function(require,module,exports){
+/* jshint -W117 */
+var SDPUtil = require("./SDPUtil");
+
+// SDP STUFF
+function SDP(sdp) {
+ this.media = sdp.split('\r\nm=');
+ for (var i = 1; i < this.media.length; i++) {
+ this.media[i] = 'm=' + this.media[i];
+ if (i != this.media.length - 1) {
+ this.media[i] += '\r\n';
+ }
+ }
+ this.session = this.media.shift() + '\r\n';
+ this.raw = this.session + this.media.join('');
+}
+/**
+ * Returns map of MediaChannel mapped per channel idx.
+ */
+SDP.prototype.getMediaSsrcMap = function() {
+ var self = this;
+ var media_ssrcs = {};
+ var tmp;
+ for (var mediaindex = 0; mediaindex < self.media.length; mediaindex++) {
+ tmp = SDPUtil.find_lines(self.media[mediaindex], 'a=ssrc:');
+ var mid = SDPUtil.parse_mid(SDPUtil.find_line(self.media[mediaindex], 'a=mid:'));
+ var media = {
+ mediaindex: mediaindex,
+ mid: mid,
+ ssrcs: {},
+ ssrcGroups: []
+ };
+ media_ssrcs[mediaindex] = media;
+ tmp.forEach(function (line) {
+ var linessrc = line.substring(7).split(' ')[0];
+ // allocate new ChannelSsrc
+ if(!media.ssrcs[linessrc]) {
+ media.ssrcs[linessrc] = {
+ ssrc: linessrc,
+ lines: []
+ };
+ }
+ media.ssrcs[linessrc].lines.push(line);
+ });
+ tmp = SDPUtil.find_lines(self.media[mediaindex], 'a=ssrc-group:');
+ tmp.forEach(function(line){
+ var semantics = line.substr(0, idx).substr(13);
+ var ssrcs = line.substr(14 + semantics.length).split(' ');
+ if (ssrcs.length != 0) {
+ media.ssrcGroups.push({
+ semantics: semantics,
+ ssrcs: ssrcs
+ });
+ }
+ });
+ }
+ return media_ssrcs;
+};
+/**
+ * Returns true if this SDP contains given SSRC.
+ * @param ssrc the ssrc to check.
+ * @returns {boolean} true if this SDP contains given SSRC.
+ */
+SDP.prototype.containsSSRC = function(ssrc) {
+ var medias = this.getMediaSsrcMap();
+ var contains = false;
+ Object.keys(medias).forEach(function(mediaindex){
+ var media = medias[mediaindex];
+ //console.log("Check", channel, ssrc);
+ if(Object.keys(media.ssrcs).indexOf(ssrc) != -1){
+ contains = true;
+ }
+ });
+ return contains;
+};
+
+
+// remove iSAC and CN from SDP
+SDP.prototype.mangle = function () {
+ var i, j, mline, lines, rtpmap, newdesc;
+ for (i = 0; i < this.media.length; i++) {
+ lines = this.media[i].split('\r\n');
+ lines.pop(); // remove empty last element
+ mline = SDPUtil.parse_mline(lines.shift());
+ if (mline.media != 'audio')
+ continue;
+ newdesc = '';
+ mline.fmt.length = 0;
+ for (j = 0; j < lines.length; j++) {
+ if (lines[j].substr(0, 9) == 'a=rtpmap:') {
+ rtpmap = SDPUtil.parse_rtpmap(lines[j]);
+ if (rtpmap.name == 'CN' || rtpmap.name == 'ISAC')
+ continue;
+ mline.fmt.push(rtpmap.id);
+ newdesc += lines[j] + '\r\n';
+ } else {
+ newdesc += lines[j] + '\r\n';
+ }
+ }
+ this.media[i] = SDPUtil.build_mline(mline) + '\r\n';
+ this.media[i] += newdesc;
+ }
+ this.raw = this.session + this.media.join('');
+};
+
+// remove lines matching prefix from session section
+SDP.prototype.removeSessionLines = function(prefix) {
+ var self = this;
+ var lines = SDPUtil.find_lines(this.session, prefix);
+ lines.forEach(function(line) {
+ self.session = self.session.replace(line + '\r\n', '');
+ });
+ this.raw = this.session + this.media.join('');
+ return lines;
+}
+// remove lines matching prefix from a media section specified by mediaindex
+// TODO: non-numeric mediaindex could match mid
+SDP.prototype.removeMediaLines = function(mediaindex, prefix) {
+ var self = this;
+ var lines = SDPUtil.find_lines(this.media[mediaindex], prefix);
+ lines.forEach(function(line) {
+ self.media[mediaindex] = self.media[mediaindex].replace(line + '\r\n', '');
+ });
+ this.raw = this.session + this.media.join('');
+ return lines;
+}
+
+// add content's to a jingle element
+SDP.prototype.toJingle = function (elem, thecreator, ssrcs) {
+// console.log("SSRC" + ssrcs["audio"] + " - " + ssrcs["video"]);
+ var i, j, k, mline, ssrc, rtpmap, tmp, line, lines;
+ var self = this;
+ // new bundle plan
+ if (SDPUtil.find_line(this.session, 'a=group:')) {
+ lines = SDPUtil.find_lines(this.session, 'a=group:');
+ for (i = 0; i < lines.length; i++) {
+ tmp = lines[i].split(' ');
+ var semantics = tmp.shift().substr(8);
+ elem.c('group', {xmlns: 'urn:xmpp:jingle:apps:grouping:0', semantics:semantics});
+ for (j = 0; j < tmp.length; j++) {
+ elem.c('content', {name: tmp[j]}).up();
+ }
+ elem.up();
+ }
+ }
+ for (i = 0; i < this.media.length; i++) {
+ mline = SDPUtil.parse_mline(this.media[i].split('\r\n')[0]);
+ if (!(mline.media === 'audio' ||
+ mline.media === 'video' ||
+ mline.media === 'application'))
+ {
+ continue;
+ }
+ if (SDPUtil.find_line(this.media[i], 'a=ssrc:')) {
+ ssrc = SDPUtil.find_line(this.media[i], 'a=ssrc:').substring(7).split(' ')[0]; // take the first
+ } else {
+ if(ssrcs && ssrcs[mline.media])
+ {
+ ssrc = ssrcs[mline.media];
+ }
+ else
+ ssrc = false;
+ }
+
+ elem.c('content', {creator: thecreator, name: mline.media});
+ if (SDPUtil.find_line(this.media[i], 'a=mid:')) {
+ // prefer identifier from a=mid if present
+ var mid = SDPUtil.parse_mid(SDPUtil.find_line(this.media[i], 'a=mid:'));
+ elem.attrs({ name: mid });
+ }
+
+ if (SDPUtil.find_line(this.media[i], 'a=rtpmap:').length)
+ {
+ elem.c('description',
+ {xmlns: 'urn:xmpp:jingle:apps:rtp:1',
+ media: mline.media });
+ if (ssrc) {
+ elem.attrs({ssrc: ssrc});
+ }
+ for (j = 0; j < mline.fmt.length; j++) {
+ rtpmap = SDPUtil.find_line(this.media[i], 'a=rtpmap:' + mline.fmt[j]);
+ elem.c('payload-type', SDPUtil.parse_rtpmap(rtpmap));
+ // put any 'a=fmtp:' + mline.fmt[j] lines into
+ if (SDPUtil.find_line(this.media[i], 'a=fmtp:' + mline.fmt[j])) {
+ tmp = SDPUtil.parse_fmtp(SDPUtil.find_line(this.media[i], 'a=fmtp:' + mline.fmt[j]));
+ for (k = 0; k < tmp.length; k++) {
+ elem.c('parameter', tmp[k]).up();
+ }
+ }
+ this.RtcpFbToJingle(i, elem, mline.fmt[j]); // XEP-0293 -- map a=rtcp-fb
+
+ elem.up();
+ }
+ if (SDPUtil.find_line(this.media[i], 'a=crypto:', this.session)) {
+ elem.c('encryption', {required: 1});
+ var crypto = SDPUtil.find_lines(this.media[i], 'a=crypto:', this.session);
+ crypto.forEach(function(line) {
+ elem.c('crypto', SDPUtil.parse_crypto(line)).up();
+ });
+ elem.up(); // end of encryption
+ }
+
+ if (ssrc) {
+ // new style mapping
+ elem.c('source', { ssrc: ssrc, xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' });
+ // FIXME: group by ssrc and support multiple different ssrcs
+ var ssrclines = SDPUtil.find_lines(this.media[i], 'a=ssrc:');
+ if(ssrclines.length > 0) {
+ ssrclines.forEach(function (line) {
+ idx = line.indexOf(' ');
+ var linessrc = line.substr(0, idx).substr(7);
+ if (linessrc != ssrc) {
+ elem.up();
+ ssrc = linessrc;
+ elem.c('source', { ssrc: ssrc, xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' });
+ }
+ var kv = line.substr(idx + 1);
+ elem.c('parameter');
+ if (kv.indexOf(':') == -1) {
+ elem.attrs({ name: kv });
+ } else {
+ elem.attrs({ name: kv.split(':', 2)[0] });
+ elem.attrs({ value: kv.split(':', 2)[1] });
+ }
+ elem.up();
+ });
+ elem.up();
+ }
+ else
+ {
+ elem.up();
+ elem.c('source', { ssrc: ssrc, xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' });
+ elem.c('parameter');
+ elem.attrs({name: "cname", value:Math.random().toString(36).substring(7)});
+ elem.up();
+ var msid = null;
+ if(mline.media == "audio")
+ {
+ msid = APP.RTC.localAudio.getId();
+ }
+ else
+ {
+ msid = APP.RTC.localVideo.getId();
+ }
+ if(msid != null)
+ {
+ msid = msid.replace(/[\{,\}]/g,"");
+ elem.c('parameter');
+ elem.attrs({name: "msid", value:msid});
+ elem.up();
+ elem.c('parameter');
+ elem.attrs({name: "mslabel", value:msid});
+ elem.up();
+ elem.c('parameter');
+ elem.attrs({name: "label", value:msid});
+ elem.up();
+ elem.up();
+ }
+
+
+ }
+
+ // XEP-0339 handle ssrc-group attributes
+ var ssrc_group_lines = SDPUtil.find_lines(this.media[i], 'a=ssrc-group:');
+ ssrc_group_lines.forEach(function(line) {
+ idx = line.indexOf(' ');
+ var semantics = line.substr(0, idx).substr(13);
+ var ssrcs = line.substr(14 + semantics.length).split(' ');
+ if (ssrcs.length != 0) {
+ elem.c('ssrc-group', { semantics: semantics, xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' });
+ ssrcs.forEach(function(ssrc) {
+ elem.c('source', { ssrc: ssrc })
+ .up();
+ });
+ elem.up();
+ }
+ });
+ }
+
+ if (SDPUtil.find_line(this.media[i], 'a=rtcp-mux')) {
+ elem.c('rtcp-mux').up();
+ }
+
+ // XEP-0293 -- map a=rtcp-fb:*
+ this.RtcpFbToJingle(i, elem, '*');
+
+ // XEP-0294
+ if (SDPUtil.find_line(this.media[i], 'a=extmap:')) {
+ lines = SDPUtil.find_lines(this.media[i], 'a=extmap:');
+ for (j = 0; j < lines.length; j++) {
+ tmp = SDPUtil.parse_extmap(lines[j]);
+ elem.c('rtp-hdrext', { xmlns: 'urn:xmpp:jingle:apps:rtp:rtp-hdrext:0',
+ uri: tmp.uri,
+ id: tmp.value });
+ if (tmp.hasOwnProperty('direction')) {
+ switch (tmp.direction) {
+ case 'sendonly':
+ elem.attrs({senders: 'responder'});
+ break;
+ case 'recvonly':
+ elem.attrs({senders: 'initiator'});
+ break;
+ case 'sendrecv':
+ elem.attrs({senders: 'both'});
+ break;
+ case 'inactive':
+ elem.attrs({senders: 'none'});
+ break;
+ }
+ }
+ // TODO: handle params
+ elem.up();
+ }
+ }
+ elem.up(); // end of description
+ }
+
+ // map ice-ufrag/pwd, dtls fingerprint, candidates
+ this.TransportToJingle(i, elem);
+
+ if (SDPUtil.find_line(this.media[i], 'a=sendrecv', this.session)) {
+ elem.attrs({senders: 'both'});
+ } else if (SDPUtil.find_line(this.media[i], 'a=sendonly', this.session)) {
+ elem.attrs({senders: 'initiator'});
+ } else if (SDPUtil.find_line(this.media[i], 'a=recvonly', this.session)) {
+ elem.attrs({senders: 'responder'});
+ } else if (SDPUtil.find_line(this.media[i], 'a=inactive', this.session)) {
+ elem.attrs({senders: 'none'});
+ }
+ if (mline.port == '0') {
+ // estos hack to reject an m-line
+ elem.attrs({senders: 'rejected'});
+ }
+ elem.up(); // end of content
+ }
+ elem.up();
+ return elem;
+};
+
+SDP.prototype.TransportToJingle = function (mediaindex, elem) {
+ var i = mediaindex;
+ var tmp;
+ var self = this;
+ elem.c('transport');
+
+ // XEP-0343 DTLS/SCTP
+ if (SDPUtil.find_line(this.media[mediaindex], 'a=sctpmap:').length)
+ {
+ var sctpmap = SDPUtil.find_line(
+ this.media[i], 'a=sctpmap:', self.session);
+ if (sctpmap)
+ {
+ var sctpAttrs = SDPUtil.parse_sctpmap(sctpmap);
+ elem.c('sctpmap',
+ {
+ xmlns: 'urn:xmpp:jingle:transports:dtls-sctp:1',
+ number: sctpAttrs[0], /* SCTP port */
+ protocol: sctpAttrs[1], /* protocol */
+ });
+ // Optional stream count attribute
+ if (sctpAttrs.length > 2)
+ elem.attrs({ streams: sctpAttrs[2]});
+ elem.up();
+ }
+ }
+ // XEP-0320
+ var fingerprints = SDPUtil.find_lines(this.media[mediaindex], 'a=fingerprint:', this.session);
+ fingerprints.forEach(function(line) {
+ tmp = SDPUtil.parse_fingerprint(line);
+ tmp.xmlns = 'urn:xmpp:jingle:apps:dtls:0';
+ elem.c('fingerprint').t(tmp.fingerprint);
+ delete tmp.fingerprint;
+ line = SDPUtil.find_line(self.media[mediaindex], 'a=setup:', self.session);
+ if (line) {
+ tmp.setup = line.substr(8);
+ }
+ elem.attrs(tmp);
+ elem.up(); // end of fingerprint
+ });
+ tmp = SDPUtil.iceparams(this.media[mediaindex], this.session);
+ if (tmp) {
+ tmp.xmlns = 'urn:xmpp:jingle:transports:ice-udp:1';
+ elem.attrs(tmp);
+ // XEP-0176
+ if (SDPUtil.find_line(this.media[mediaindex], 'a=candidate:', this.session)) { // add any a=candidate lines
+ var lines = SDPUtil.find_lines(this.media[mediaindex], 'a=candidate:', this.session);
+ lines.forEach(function (line) {
+ elem.c('candidate', SDPUtil.candidateToJingle(line)).up();
+ });
+ }
+ }
+ elem.up(); // end of transport
+}
+
+SDP.prototype.RtcpFbToJingle = function (mediaindex, elem, payloadtype) { // XEP-0293
+ var lines = SDPUtil.find_lines(this.media[mediaindex], 'a=rtcp-fb:' + payloadtype);
+ lines.forEach(function (line) {
+ var tmp = SDPUtil.parse_rtcpfb(line);
+ if (tmp.type == 'trr-int') {
+ elem.c('rtcp-fb-trr-int', {xmlns: 'urn:xmpp:jingle:apps:rtp:rtcp-fb:0', value: tmp.params[0]});
+ elem.up();
+ } else {
+ elem.c('rtcp-fb', {xmlns: 'urn:xmpp:jingle:apps:rtp:rtcp-fb:0', type: tmp.type});
+ if (tmp.params.length > 0) {
+ elem.attrs({'subtype': tmp.params[0]});
+ }
+ elem.up();
+ }
+ });
+};
+
+SDP.prototype.RtcpFbFromJingle = function (elem, payloadtype) { // XEP-0293
+ var media = '';
+ var tmp = elem.find('>rtcp-fb-trr-int[xmlns="urn:xmpp:jingle:apps:rtp:rtcp-fb:0"]');
+ if (tmp.length) {
+ media += 'a=rtcp-fb:' + '*' + ' ' + 'trr-int' + ' ';
+ if (tmp.attr('value')) {
+ media += tmp.attr('value');
+ } else {
+ media += '0';
+ }
+ media += '\r\n';
+ }
+ tmp = elem.find('>rtcp-fb[xmlns="urn:xmpp:jingle:apps:rtp:rtcp-fb:0"]');
+ tmp.each(function () {
+ media += 'a=rtcp-fb:' + payloadtype + ' ' + $(this).attr('type');
+ if ($(this).attr('subtype')) {
+ media += ' ' + $(this).attr('subtype');
+ }
+ media += '\r\n';
+ });
+ return media;
+};
+
+// construct an SDP from a jingle stanza
+SDP.prototype.fromJingle = function (jingle) {
+ var self = this;
+ this.raw = 'v=0\r\n' +
+ 'o=- ' + '1923518516' + ' 2 IN IP4 0.0.0.0\r\n' +// FIXME
+ 's=-\r\n' +
+ 't=0 0\r\n';
+ // http://tools.ietf.org/html/draft-ietf-mmusic-sdp-bundle-negotiation-04#section-8
+ if ($(jingle).find('>group[xmlns="urn:xmpp:jingle:apps:grouping:0"]').length) {
+ $(jingle).find('>group[xmlns="urn:xmpp:jingle:apps:grouping:0"]').each(function (idx, group) {
+ var contents = $(group).find('>content').map(function (idx, content) {
+ return content.getAttribute('name');
+ }).get();
+ if (contents.length > 0) {
+ self.raw += 'a=group:' + (group.getAttribute('semantics') || group.getAttribute('type')) + ' ' + contents.join(' ') + '\r\n';
+ }
+ });
+ }
+
+ this.session = this.raw;
+ jingle.find('>content').each(function () {
+ var m = self.jingle2media($(this));
+ self.media.push(m);
+ });
+
+ // reconstruct msid-semantic -- apparently not necessary
+ /*
+ var msid = SDPUtil.parse_ssrc(this.raw);
+ if (msid.hasOwnProperty('mslabel')) {
+ this.session += "a=msid-semantic: WMS " + msid.mslabel + "\r\n";
+ }
+ */
+
+ this.raw = this.session + this.media.join('');
+};
+
+// translate a jingle content element into an an SDP media part
+SDP.prototype.jingle2media = function (content) {
+ var media = '',
+ desc = content.find('description'),
+ ssrc = desc.attr('ssrc'),
+ self = this,
+ tmp;
+ var sctp = content.find(
+ '>transport>sctpmap[xmlns="urn:xmpp:jingle:transports:dtls-sctp:1"]');
+
+ tmp = { media: desc.attr('media') };
+ tmp.port = '1';
+ if (content.attr('senders') == 'rejected') {
+ // estos hack to reject an m-line.
+ tmp.port = '0';
+ }
+ if (content.find('>transport>fingerprint').length || desc.find('encryption').length) {
+ if (sctp.length)
+ tmp.proto = 'DTLS/SCTP';
+ else
+ tmp.proto = 'RTP/SAVPF';
+ } else {
+ tmp.proto = 'RTP/AVPF';
+ }
+ if (!sctp.length)
+ {
+ tmp.fmt = desc.find('payload-type').map(
+ function () { return this.getAttribute('id'); }).get();
+ media += SDPUtil.build_mline(tmp) + '\r\n';
+ }
+ else
+ {
+ media += 'm=application 1 DTLS/SCTP ' + sctp.attr('number') + '\r\n';
+ media += 'a=sctpmap:' + sctp.attr('number') +
+ ' ' + sctp.attr('protocol');
+
+ var streamCount = sctp.attr('streams');
+ if (streamCount)
+ media += ' ' + streamCount + '\r\n';
+ else
+ media += '\r\n';
+ }
+
+ media += 'c=IN IP4 0.0.0.0\r\n';
+ if (!sctp.length)
+ media += 'a=rtcp:1 IN IP4 0.0.0.0\r\n';
+ tmp = content.find('>transport[xmlns="urn:xmpp:jingle:transports:ice-udp:1"]');
+ if (tmp.length) {
+ if (tmp.attr('ufrag')) {
+ media += SDPUtil.build_iceufrag(tmp.attr('ufrag')) + '\r\n';
+ }
+ if (tmp.attr('pwd')) {
+ media += SDPUtil.build_icepwd(tmp.attr('pwd')) + '\r\n';
+ }
+ tmp.find('>fingerprint').each(function () {
+ // FIXME: check namespace at some point
+ media += 'a=fingerprint:' + this.getAttribute('hash');
+ media += ' ' + $(this).text();
+ media += '\r\n';
+ if (this.getAttribute('setup')) {
+ media += 'a=setup:' + this.getAttribute('setup') + '\r\n';
+ }
+ });
+ }
+ switch (content.attr('senders')) {
+ case 'initiator':
+ media += 'a=sendonly\r\n';
+ break;
+ case 'responder':
+ media += 'a=recvonly\r\n';
+ break;
+ case 'none':
+ media += 'a=inactive\r\n';
+ break;
+ case 'both':
+ media += 'a=sendrecv\r\n';
+ break;
+ }
+ media += 'a=mid:' + content.attr('name') + '\r\n';
+
+ //
+ // see http://code.google.com/p/libjingle/issues/detail?id=309 -- no spec though
+ // and http://mail.jabber.org/pipermail/jingle/2011-December/001761.html
+ if (desc.find('rtcp-mux').length) {
+ media += 'a=rtcp-mux\r\n';
+ }
+
+ if (desc.find('encryption').length) {
+ desc.find('encryption>crypto').each(function () {
+ media += 'a=crypto:' + this.getAttribute('tag');
+ media += ' ' + this.getAttribute('crypto-suite');
+ media += ' ' + this.getAttribute('key-params');
+ if (this.getAttribute('session-params')) {
+ media += ' ' + this.getAttribute('session-params');
+ }
+ media += '\r\n';
+ });
+ }
+ desc.find('payload-type').each(function () {
+ media += SDPUtil.build_rtpmap(this) + '\r\n';
+ if ($(this).find('>parameter').length) {
+ media += 'a=fmtp:' + this.getAttribute('id') + ' ';
+ media += $(this).find('parameter').map(function () { return (this.getAttribute('name') ? (this.getAttribute('name') + '=') : '') + this.getAttribute('value'); }).get().join('; ');
+ media += '\r\n';
+ }
+ // xep-0293
+ media += self.RtcpFbFromJingle($(this), this.getAttribute('id'));
+ });
+
+ // xep-0293
+ media += self.RtcpFbFromJingle(desc, '*');
+
+ // xep-0294
+ tmp = desc.find('>rtp-hdrext[xmlns="urn:xmpp:jingle:apps:rtp:rtp-hdrext:0"]');
+ tmp.each(function () {
+ media += 'a=extmap:' + this.getAttribute('id') + ' ' + this.getAttribute('uri') + '\r\n';
+ });
+
+ content.find('>transport[xmlns="urn:xmpp:jingle:transports:ice-udp:1"]>candidate').each(function () {
+ media += SDPUtil.candidateFromJingle(this);
+ });
+
+ // XEP-0339 handle ssrc-group attributes
+ tmp = content.find('description>ssrc-group[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]').each(function() {
+ var semantics = this.getAttribute('semantics');
+ var ssrcs = $(this).find('>source').map(function() {
+ return this.getAttribute('ssrc');
+ }).get();
+
+ if (ssrcs.length != 0) {
+ media += 'a=ssrc-group:' + semantics + ' ' + ssrcs.join(' ') + '\r\n';
+ }
+ });
+
+ tmp = content.find('description>source[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]');
+ tmp.each(function () {
+ var ssrc = this.getAttribute('ssrc');
+ $(this).find('>parameter').each(function () {
+ media += 'a=ssrc:' + ssrc + ' ' + this.getAttribute('name');
+ if (this.getAttribute('value') && this.getAttribute('value').length)
+ media += ':' + this.getAttribute('value');
+ media += '\r\n';
+ });
+ });
+
+ return media;
+};
+
+
+module.exports = SDP;
+
+
+},{"./SDPUtil":56}],55:[function(require,module,exports){
+function SDPDiffer(mySDP, otherSDP) {
+ this.mySDP = mySDP;
+ this.otherSDP = otherSDP;
+}
+
+/**
+ * Returns map of MediaChannel that contains only media not contained in otherSdp . Mapped by channel idx.
+ * @param otherSdp the other SDP to check ssrc with.
+ */
+SDPDiffer.prototype.getNewMedia = function() {
+
+ // this could be useful in Array.prototype.
+ function arrayEquals(array) {
+ // if the other array is a falsy value, return
+ if (!array)
+ return false;
+
+ // compare lengths - can save a lot of time
+ if (this.length != array.length)
+ return false;
+
+ for (var i = 0, l=this.length; i < l; i++) {
+ // Check if we have nested arrays
+ if (this[i] instanceof Array && array[i] instanceof Array) {
+ // recurse into the nested arrays
+ if (!this[i].equals(array[i]))
+ return false;
+ }
+ else if (this[i] != array[i]) {
+ // Warning - two different object instances will never be equal: {x:20} != {x:20}
+ return false;
+ }
+ }
+ return true;
+ }
+
+ var myMedias = this.mySDP.getMediaSsrcMap();
+ var othersMedias = this.otherSDP.getMediaSsrcMap();
+ var newMedia = {};
+ Object.keys(othersMedias).forEach(function(othersMediaIdx) {
+ var myMedia = myMedias[othersMediaIdx];
+ var othersMedia = othersMedias[othersMediaIdx];
+ if(!myMedia && othersMedia) {
+ // Add whole channel
+ newMedia[othersMediaIdx] = othersMedia;
+ return;
+ }
+ // Look for new ssrcs accross the channel
+ Object.keys(othersMedia.ssrcs).forEach(function(ssrc) {
+ if(Object.keys(myMedia.ssrcs).indexOf(ssrc) === -1) {
+ // Allocate channel if we've found ssrc that doesn't exist in our channel
+ if(!newMedia[othersMediaIdx]){
+ newMedia[othersMediaIdx] = {
+ mediaindex: othersMedia.mediaindex,
+ mid: othersMedia.mid,
+ ssrcs: {},
+ ssrcGroups: []
+ };
+ }
+ newMedia[othersMediaIdx].ssrcs[ssrc] = othersMedia.ssrcs[ssrc];
+ }
+ });
+
+ // Look for new ssrc groups across the channels
+ othersMedia.ssrcGroups.forEach(function(otherSsrcGroup){
+
+ // try to match the other ssrc-group with an ssrc-group of ours
+ var matched = false;
+ for (var i = 0; i < myMedia.ssrcGroups.length; i++) {
+ var mySsrcGroup = myMedia.ssrcGroups[i];
+ if (otherSsrcGroup.semantics == mySsrcGroup.semantics
+ && arrayEquals.apply(otherSsrcGroup.ssrcs, [mySsrcGroup.ssrcs])) {
+
+ matched = true;
+ break;
+ }
+ }
+
+ if (!matched) {
+ // Allocate channel if we've found an ssrc-group that doesn't
+ // exist in our channel
+
+ if(!newMedia[othersMediaIdx]){
+ newMedia[othersMediaIdx] = {
+ mediaindex: othersMedia.mediaindex,
+ mid: othersMedia.mid,
+ ssrcs: {},
+ ssrcGroups: []
+ };
+ }
+ newMedia[othersMediaIdx].ssrcGroups.push(otherSsrcGroup);
+ }
+ });
+ });
+ return newMedia;
+};
+
+/**
+ * Sends SSRC update IQ.
+ * @param sdpMediaSsrcs SSRCs map obtained from SDP.getNewMedia. Cntains SSRCs to add/remove.
+ * @param sid session identifier that will be put into the IQ.
+ * @param initiator initiator identifier.
+ * @param toJid destination Jid
+ * @param isAdd indicates if this is remove or add operation.
+ */
+SDPDiffer.prototype.toJingle = function(modify) {
+ var sdpMediaSsrcs = this.getNewMedia();
+ var self = this;
+
+ // FIXME: only announce video ssrcs since we mix audio and dont need
+ // the audio ssrcs therefore
+ var modified = false;
+ Object.keys(sdpMediaSsrcs).forEach(function(mediaindex){
+ modified = true;
+ var media = sdpMediaSsrcs[mediaindex];
+ modify.c('content', {name: media.mid});
+
+ modify.c('description', {xmlns:'urn:xmpp:jingle:apps:rtp:1', media: media.mid});
+ // FIXME: not completly sure this operates on blocks and / or handles different ssrcs correctly
+ // generate sources from lines
+ Object.keys(media.ssrcs).forEach(function(ssrcNum) {
+ var mediaSsrc = media.ssrcs[ssrcNum];
+ modify.c('source', { xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' });
+ modify.attrs({ssrc: mediaSsrc.ssrc});
+ // iterate over ssrc lines
+ mediaSsrc.lines.forEach(function (line) {
+ var idx = line.indexOf(' ');
+ var kv = line.substr(idx + 1);
+ modify.c('parameter');
+ if (kv.indexOf(':') == -1) {
+ modify.attrs({ name: kv });
+ } else {
+ modify.attrs({ name: kv.split(':', 2)[0] });
+ modify.attrs({ value: kv.split(':', 2)[1] });
+ }
+ modify.up(); // end of parameter
+ });
+ modify.up(); // end of source
+ });
+
+ // generate source groups from lines
+ media.ssrcGroups.forEach(function(ssrcGroup) {
+ if (ssrcGroup.ssrcs.length != 0) {
+
+ modify.c('ssrc-group', {
+ semantics: ssrcGroup.semantics,
+ xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0'
+ });
+
+ ssrcGroup.ssrcs.forEach(function (ssrc) {
+ modify.c('source', { ssrc: ssrc })
+ .up(); // end of source
+ });
+ modify.up(); // end of ssrc-group
+ }
+ });
+
+ modify.up(); // end of description
+ modify.up(); // end of content
+ });
+
+ return modified;
+};
+
module.exports = SDPDiffer;
-},{}],54:[function(require,module,exports){
-SDPUtil = {
- iceparams: function (mediadesc, sessiondesc) {
- var data = null;
- if (SDPUtil.find_line(mediadesc, 'a=ice-ufrag:', sessiondesc) &&
- SDPUtil.find_line(mediadesc, 'a=ice-pwd:', sessiondesc)) {
- data = {
- ufrag: SDPUtil.parse_iceufrag(SDPUtil.find_line(mediadesc, 'a=ice-ufrag:', sessiondesc)),
- pwd: SDPUtil.parse_icepwd(SDPUtil.find_line(mediadesc, 'a=ice-pwd:', sessiondesc))
- };
- }
- return data;
- },
- parse_iceufrag: function (line) {
- return line.substring(12);
- },
- build_iceufrag: function (frag) {
- return 'a=ice-ufrag:' + frag;
- },
- parse_icepwd: function (line) {
- return line.substring(10);
- },
- build_icepwd: function (pwd) {
- return 'a=ice-pwd:' + pwd;
- },
- parse_mid: function (line) {
- return line.substring(6);
- },
- parse_mline: function (line) {
- var parts = line.substring(2).split(' '),
- data = {};
- data.media = parts.shift();
- data.port = parts.shift();
- data.proto = parts.shift();
- if (parts[parts.length - 1] === '') { // trailing whitespace
- parts.pop();
- }
- data.fmt = parts;
- return data;
- },
- build_mline: function (mline) {
- return 'm=' + mline.media + ' ' + mline.port + ' ' + mline.proto + ' ' + mline.fmt.join(' ');
- },
- parse_rtpmap: function (line) {
- var parts = line.substring(9).split(' '),
- data = {};
- data.id = parts.shift();
- parts = parts[0].split('/');
- data.name = parts.shift();
- data.clockrate = parts.shift();
- data.channels = parts.length ? parts.shift() : '1';
- return data;
- },
- /**
- * Parses SDP line "a=sctpmap:..." and extracts SCTP port from it.
- * @param line eg. "a=sctpmap:5000 webrtc-datachannel"
- * @returns [SCTP port number, protocol, streams]
- */
- parse_sctpmap: function (line)
- {
- var parts = line.substring(10).split(' ');
- var sctpPort = parts[0];
- var protocol = parts[1];
- // Stream count is optional
- var streamCount = parts.length > 2 ? parts[2] : null;
- return [sctpPort, protocol, streamCount];// SCTP port
- },
- build_rtpmap: function (el) {
- var line = 'a=rtpmap:' + el.getAttribute('id') + ' ' + el.getAttribute('name') + '/' + el.getAttribute('clockrate');
- if (el.getAttribute('channels') && el.getAttribute('channels') != '1') {
- line += '/' + el.getAttribute('channels');
- }
- return line;
- },
- parse_crypto: function (line) {
- var parts = line.substring(9).split(' '),
- data = {};
- data.tag = parts.shift();
- data['crypto-suite'] = parts.shift();
- data['key-params'] = parts.shift();
- if (parts.length) {
- data['session-params'] = parts.join(' ');
- }
- return data;
- },
- parse_fingerprint: function (line) { // RFC 4572
- var parts = line.substring(14).split(' '),
- data = {};
- data.hash = parts.shift();
- data.fingerprint = parts.shift();
- // TODO assert that fingerprint satisfies 2UHEX *(":" 2UHEX) ?
- return data;
- },
- parse_fmtp: function (line) {
- var parts = line.split(' '),
- i, key, value,
- data = [];
- parts.shift();
- parts = parts.join(' ').split(';');
- for (i = 0; i < parts.length; i++) {
- key = parts[i].split('=')[0];
- while (key.length && key[0] == ' ') {
- key = key.substring(1);
- }
- value = parts[i].split('=')[1];
- if (key && value) {
- data.push({name: key, value: value});
- } else if (key) {
- // rfc 4733 (DTMF) style stuff
- data.push({name: '', value: key});
- }
- }
- return data;
- },
- parse_icecandidate: function (line) {
- var candidate = {},
- elems = line.split(' ');
- candidate.foundation = elems[0].substring(12);
- candidate.component = elems[1];
- candidate.protocol = elems[2].toLowerCase();
- candidate.priority = elems[3];
- candidate.ip = elems[4];
- candidate.port = elems[5];
- // elems[6] => "typ"
- candidate.type = elems[7];
- candidate.generation = 0; // default value, may be overwritten below
- for (var i = 8; i < elems.length; i += 2) {
- switch (elems[i]) {
- case 'raddr':
- candidate['rel-addr'] = elems[i + 1];
- break;
- case 'rport':
- candidate['rel-port'] = elems[i + 1];
- break;
- case 'generation':
- candidate.generation = elems[i + 1];
- break;
- case 'tcptype':
- candidate.tcptype = elems[i + 1];
- break;
- default: // TODO
- console.log('parse_icecandidate not translating "' + elems[i] + '" = "' + elems[i + 1] + '"');
- }
- }
- candidate.network = '1';
- candidate.id = Math.random().toString(36).substr(2, 10); // not applicable to SDP -- FIXME: should be unique, not just random
- return candidate;
- },
- build_icecandidate: function (cand) {
- var line = ['a=candidate:' + cand.foundation, cand.component, cand.protocol, cand.priority, cand.ip, cand.port, 'typ', cand.type].join(' ');
- line += ' ';
- switch (cand.type) {
- case 'srflx':
- case 'prflx':
- case 'relay':
- if (cand.hasOwnAttribute('rel-addr') && cand.hasOwnAttribute('rel-port')) {
- line += 'raddr';
- line += ' ';
- line += cand['rel-addr'];
- line += ' ';
- line += 'rport';
- line += ' ';
- line += cand['rel-port'];
- line += ' ';
- }
- break;
- }
- if (cand.hasOwnAttribute('tcptype')) {
- line += 'tcptype';
- line += ' ';
- line += cand.tcptype;
- line += ' ';
- }
- line += 'generation';
- line += ' ';
- line += cand.hasOwnAttribute('generation') ? cand.generation : '0';
- return line;
- },
- parse_ssrc: function (desc) {
- // proprietary mapping of a=ssrc lines
- // TODO: see "Jingle RTP Source Description" by Juberti and P. Thatcher on google docs
- // and parse according to that
- var lines = desc.split('\r\n'),
- data = {};
- for (var i = 0; i < lines.length; i++) {
- if (lines[i].substring(0, 7) == 'a=ssrc:') {
- var idx = lines[i].indexOf(' ');
- data[lines[i].substr(idx + 1).split(':', 2)[0]] = lines[i].substr(idx + 1).split(':', 2)[1];
- }
- }
- return data;
- },
- parse_rtcpfb: function (line) {
- var parts = line.substr(10).split(' ');
- var data = {};
- data.pt = parts.shift();
- data.type = parts.shift();
- data.params = parts;
- return data;
- },
- parse_extmap: function (line) {
- var parts = line.substr(9).split(' ');
- var data = {};
- data.value = parts.shift();
- if (data.value.indexOf('/') != -1) {
- data.direction = data.value.substr(data.value.indexOf('/') + 1);
- data.value = data.value.substr(0, data.value.indexOf('/'));
- } else {
- data.direction = 'both';
- }
- data.uri = parts.shift();
- data.params = parts;
- return data;
- },
- find_line: function (haystack, needle, sessionpart) {
- var lines = haystack.split('\r\n');
- for (var i = 0; i < lines.length; i++) {
- if (lines[i].substring(0, needle.length) == needle) {
- return lines[i];
- }
- }
- if (!sessionpart) {
- return false;
- }
- // search session part
- lines = sessionpart.split('\r\n');
- for (var j = 0; j < lines.length; j++) {
- if (lines[j].substring(0, needle.length) == needle) {
- return lines[j];
- }
- }
- return false;
- },
- find_lines: function (haystack, needle, sessionpart) {
- var lines = haystack.split('\r\n'),
- needles = [];
- for (var i = 0; i < lines.length; i++) {
- if (lines[i].substring(0, needle.length) == needle)
- needles.push(lines[i]);
- }
- if (needles.length || !sessionpart) {
- return needles;
- }
- // search session part
- lines = sessionpart.split('\r\n');
- for (var j = 0; j < lines.length; j++) {
- if (lines[j].substring(0, needle.length) == needle) {
- needles.push(lines[j]);
- }
- }
- return needles;
- },
- candidateToJingle: function (line) {
- // a=candidate:2979166662 1 udp 2113937151 192.168.2.100 57698 typ host generation 0
- //
- if (line.indexOf('candidate:') === 0) {
- line = 'a=' + line;
- } else if (line.substring(0, 12) != 'a=candidate:') {
- console.log('parseCandidate called with a line that is not a candidate line');
- console.log(line);
- return null;
- }
- if (line.substring(line.length - 2) == '\r\n') // chomp it
- line = line.substring(0, line.length - 2);
- var candidate = {},
- elems = line.split(' '),
- i;
- if (elems[6] != 'typ') {
- console.log('did not find typ in the right place');
- console.log(line);
- return null;
- }
- candidate.foundation = elems[0].substring(12);
- candidate.component = elems[1];
- candidate.protocol = elems[2].toLowerCase();
- candidate.priority = elems[3];
- candidate.ip = elems[4];
- candidate.port = elems[5];
- // elems[6] => "typ"
- candidate.type = elems[7];
-
- candidate.generation = '0'; // default, may be overwritten below
- for (i = 8; i < elems.length; i += 2) {
- switch (elems[i]) {
- case 'raddr':
- candidate['rel-addr'] = elems[i + 1];
- break;
- case 'rport':
- candidate['rel-port'] = elems[i + 1];
- break;
- case 'generation':
- candidate.generation = elems[i + 1];
- break;
- case 'tcptype':
- candidate.tcptype = elems[i + 1];
- break;
- default: // TODO
- console.log('not translating "' + elems[i] + '" = "' + elems[i + 1] + '"');
- }
- }
- candidate.network = '1';
- candidate.id = Math.random().toString(36).substr(2, 10); // not applicable to SDP -- FIXME: should be unique, not just random
- return candidate;
- },
- candidateFromJingle: function (cand) {
- var line = 'a=candidate:';
- line += cand.getAttribute('foundation');
- line += ' ';
- line += cand.getAttribute('component');
- line += ' ';
- line += cand.getAttribute('protocol'); //.toUpperCase(); // chrome M23 doesn't like this
- line += ' ';
- line += cand.getAttribute('priority');
- line += ' ';
- line += cand.getAttribute('ip');
- line += ' ';
- line += cand.getAttribute('port');
- line += ' ';
- line += 'typ';
- line += ' ' + cand.getAttribute('type');
- line += ' ';
- switch (cand.getAttribute('type')) {
- case 'srflx':
- case 'prflx':
- case 'relay':
- if (cand.getAttribute('rel-addr') && cand.getAttribute('rel-port')) {
- line += 'raddr';
- line += ' ';
- line += cand.getAttribute('rel-addr');
- line += ' ';
- line += 'rport';
- line += ' ';
- line += cand.getAttribute('rel-port');
- line += ' ';
- }
- break;
- }
- if (cand.getAttribute('protocol').toLowerCase() == 'tcp') {
- line += 'tcptype';
- line += ' ';
- line += cand.getAttribute('tcptype');
- line += ' ';
- }
- line += 'generation';
- line += ' ';
- line += cand.getAttribute('generation') || '0';
- return line + '\r\n';
- }
-};
+},{}],56:[function(require,module,exports){
+SDPUtil = {
+ iceparams: function (mediadesc, sessiondesc) {
+ var data = null;
+ if (SDPUtil.find_line(mediadesc, 'a=ice-ufrag:', sessiondesc) &&
+ SDPUtil.find_line(mediadesc, 'a=ice-pwd:', sessiondesc)) {
+ data = {
+ ufrag: SDPUtil.parse_iceufrag(SDPUtil.find_line(mediadesc, 'a=ice-ufrag:', sessiondesc)),
+ pwd: SDPUtil.parse_icepwd(SDPUtil.find_line(mediadesc, 'a=ice-pwd:', sessiondesc))
+ };
+ }
+ return data;
+ },
+ parse_iceufrag: function (line) {
+ return line.substring(12);
+ },
+ build_iceufrag: function (frag) {
+ return 'a=ice-ufrag:' + frag;
+ },
+ parse_icepwd: function (line) {
+ return line.substring(10);
+ },
+ build_icepwd: function (pwd) {
+ return 'a=ice-pwd:' + pwd;
+ },
+ parse_mid: function (line) {
+ return line.substring(6);
+ },
+ parse_mline: function (line) {
+ var parts = line.substring(2).split(' '),
+ data = {};
+ data.media = parts.shift();
+ data.port = parts.shift();
+ data.proto = parts.shift();
+ if (parts[parts.length - 1] === '') { // trailing whitespace
+ parts.pop();
+ }
+ data.fmt = parts;
+ return data;
+ },
+ build_mline: function (mline) {
+ return 'm=' + mline.media + ' ' + mline.port + ' ' + mline.proto + ' ' + mline.fmt.join(' ');
+ },
+ parse_rtpmap: function (line) {
+ var parts = line.substring(9).split(' '),
+ data = {};
+ data.id = parts.shift();
+ parts = parts[0].split('/');
+ data.name = parts.shift();
+ data.clockrate = parts.shift();
+ data.channels = parts.length ? parts.shift() : '1';
+ return data;
+ },
+ /**
+ * Parses SDP line "a=sctpmap:..." and extracts SCTP port from it.
+ * @param line eg. "a=sctpmap:5000 webrtc-datachannel"
+ * @returns [SCTP port number, protocol, streams]
+ */
+ parse_sctpmap: function (line)
+ {
+ var parts = line.substring(10).split(' ');
+ var sctpPort = parts[0];
+ var protocol = parts[1];
+ // Stream count is optional
+ var streamCount = parts.length > 2 ? parts[2] : null;
+ return [sctpPort, protocol, streamCount];// SCTP port
+ },
+ build_rtpmap: function (el) {
+ var line = 'a=rtpmap:' + el.getAttribute('id') + ' ' + el.getAttribute('name') + '/' + el.getAttribute('clockrate');
+ if (el.getAttribute('channels') && el.getAttribute('channels') != '1') {
+ line += '/' + el.getAttribute('channels');
+ }
+ return line;
+ },
+ parse_crypto: function (line) {
+ var parts = line.substring(9).split(' '),
+ data = {};
+ data.tag = parts.shift();
+ data['crypto-suite'] = parts.shift();
+ data['key-params'] = parts.shift();
+ if (parts.length) {
+ data['session-params'] = parts.join(' ');
+ }
+ return data;
+ },
+ parse_fingerprint: function (line) { // RFC 4572
+ var parts = line.substring(14).split(' '),
+ data = {};
+ data.hash = parts.shift();
+ data.fingerprint = parts.shift();
+ // TODO assert that fingerprint satisfies 2UHEX *(":" 2UHEX) ?
+ return data;
+ },
+ parse_fmtp: function (line) {
+ var parts = line.split(' '),
+ i, key, value,
+ data = [];
+ parts.shift();
+ parts = parts.join(' ').split(';');
+ for (i = 0; i < parts.length; i++) {
+ key = parts[i].split('=')[0];
+ while (key.length && key[0] == ' ') {
+ key = key.substring(1);
+ }
+ value = parts[i].split('=')[1];
+ if (key && value) {
+ data.push({name: key, value: value});
+ } else if (key) {
+ // rfc 4733 (DTMF) style stuff
+ data.push({name: '', value: key});
+ }
+ }
+ return data;
+ },
+ parse_icecandidate: function (line) {
+ var candidate = {},
+ elems = line.split(' ');
+ candidate.foundation = elems[0].substring(12);
+ candidate.component = elems[1];
+ candidate.protocol = elems[2].toLowerCase();
+ candidate.priority = elems[3];
+ candidate.ip = elems[4];
+ candidate.port = elems[5];
+ // elems[6] => "typ"
+ candidate.type = elems[7];
+ candidate.generation = 0; // default value, may be overwritten below
+ for (var i = 8; i < elems.length; i += 2) {
+ switch (elems[i]) {
+ case 'raddr':
+ candidate['rel-addr'] = elems[i + 1];
+ break;
+ case 'rport':
+ candidate['rel-port'] = elems[i + 1];
+ break;
+ case 'generation':
+ candidate.generation = elems[i + 1];
+ break;
+ case 'tcptype':
+ candidate.tcptype = elems[i + 1];
+ break;
+ default: // TODO
+ console.log('parse_icecandidate not translating "' + elems[i] + '" = "' + elems[i + 1] + '"');
+ }
+ }
+ candidate.network = '1';
+ candidate.id = Math.random().toString(36).substr(2, 10); // not applicable to SDP -- FIXME: should be unique, not just random
+ return candidate;
+ },
+ build_icecandidate: function (cand) {
+ var line = ['a=candidate:' + cand.foundation, cand.component, cand.protocol, cand.priority, cand.ip, cand.port, 'typ', cand.type].join(' ');
+ line += ' ';
+ switch (cand.type) {
+ case 'srflx':
+ case 'prflx':
+ case 'relay':
+ if (cand.hasOwnAttribute('rel-addr') && cand.hasOwnAttribute('rel-port')) {
+ line += 'raddr';
+ line += ' ';
+ line += cand['rel-addr'];
+ line += ' ';
+ line += 'rport';
+ line += ' ';
+ line += cand['rel-port'];
+ line += ' ';
+ }
+ break;
+ }
+ if (cand.hasOwnAttribute('tcptype')) {
+ line += 'tcptype';
+ line += ' ';
+ line += cand.tcptype;
+ line += ' ';
+ }
+ line += 'generation';
+ line += ' ';
+ line += cand.hasOwnAttribute('generation') ? cand.generation : '0';
+ return line;
+ },
+ parse_ssrc: function (desc) {
+ // proprietary mapping of a=ssrc lines
+ // TODO: see "Jingle RTP Source Description" by Juberti and P. Thatcher on google docs
+ // and parse according to that
+ var lines = desc.split('\r\n'),
+ data = {};
+ for (var i = 0; i < lines.length; i++) {
+ if (lines[i].substring(0, 7) == 'a=ssrc:') {
+ var idx = lines[i].indexOf(' ');
+ data[lines[i].substr(idx + 1).split(':', 2)[0]] = lines[i].substr(idx + 1).split(':', 2)[1];
+ }
+ }
+ return data;
+ },
+ parse_rtcpfb: function (line) {
+ var parts = line.substr(10).split(' ');
+ var data = {};
+ data.pt = parts.shift();
+ data.type = parts.shift();
+ data.params = parts;
+ return data;
+ },
+ parse_extmap: function (line) {
+ var parts = line.substr(9).split(' ');
+ var data = {};
+ data.value = parts.shift();
+ if (data.value.indexOf('/') != -1) {
+ data.direction = data.value.substr(data.value.indexOf('/') + 1);
+ data.value = data.value.substr(0, data.value.indexOf('/'));
+ } else {
+ data.direction = 'both';
+ }
+ data.uri = parts.shift();
+ data.params = parts;
+ return data;
+ },
+ find_line: function (haystack, needle, sessionpart) {
+ var lines = haystack.split('\r\n');
+ for (var i = 0; i < lines.length; i++) {
+ if (lines[i].substring(0, needle.length) == needle) {
+ return lines[i];
+ }
+ }
+ if (!sessionpart) {
+ return false;
+ }
+ // search session part
+ lines = sessionpart.split('\r\n');
+ for (var j = 0; j < lines.length; j++) {
+ if (lines[j].substring(0, needle.length) == needle) {
+ return lines[j];
+ }
+ }
+ return false;
+ },
+ find_lines: function (haystack, needle, sessionpart) {
+ var lines = haystack.split('\r\n'),
+ needles = [];
+ for (var i = 0; i < lines.length; i++) {
+ if (lines[i].substring(0, needle.length) == needle)
+ needles.push(lines[i]);
+ }
+ if (needles.length || !sessionpart) {
+ return needles;
+ }
+ // search session part
+ lines = sessionpart.split('\r\n');
+ for (var j = 0; j < lines.length; j++) {
+ if (lines[j].substring(0, needle.length) == needle) {
+ needles.push(lines[j]);
+ }
+ }
+ return needles;
+ },
+ candidateToJingle: function (line) {
+ // a=candidate:2979166662 1 udp 2113937151 192.168.2.100 57698 typ host generation 0
+ //
+ if (line.indexOf('candidate:') === 0) {
+ line = 'a=' + line;
+ } else if (line.substring(0, 12) != 'a=candidate:') {
+ console.log('parseCandidate called with a line that is not a candidate line');
+ console.log(line);
+ return null;
+ }
+ if (line.substring(line.length - 2) == '\r\n') // chomp it
+ line = line.substring(0, line.length - 2);
+ var candidate = {},
+ elems = line.split(' '),
+ i;
+ if (elems[6] != 'typ') {
+ console.log('did not find typ in the right place');
+ console.log(line);
+ return null;
+ }
+ candidate.foundation = elems[0].substring(12);
+ candidate.component = elems[1];
+ candidate.protocol = elems[2].toLowerCase();
+ candidate.priority = elems[3];
+ candidate.ip = elems[4];
+ candidate.port = elems[5];
+ // elems[6] => "typ"
+ candidate.type = elems[7];
+
+ candidate.generation = '0'; // default, may be overwritten below
+ for (i = 8; i < elems.length; i += 2) {
+ switch (elems[i]) {
+ case 'raddr':
+ candidate['rel-addr'] = elems[i + 1];
+ break;
+ case 'rport':
+ candidate['rel-port'] = elems[i + 1];
+ break;
+ case 'generation':
+ candidate.generation = elems[i + 1];
+ break;
+ case 'tcptype':
+ candidate.tcptype = elems[i + 1];
+ break;
+ default: // TODO
+ console.log('not translating "' + elems[i] + '" = "' + elems[i + 1] + '"');
+ }
+ }
+ candidate.network = '1';
+ candidate.id = Math.random().toString(36).substr(2, 10); // not applicable to SDP -- FIXME: should be unique, not just random
+ return candidate;
+ },
+ candidateFromJingle: function (cand) {
+ var line = 'a=candidate:';
+ line += cand.getAttribute('foundation');
+ line += ' ';
+ line += cand.getAttribute('component');
+ line += ' ';
+ line += cand.getAttribute('protocol'); //.toUpperCase(); // chrome M23 doesn't like this
+ line += ' ';
+ line += cand.getAttribute('priority');
+ line += ' ';
+ line += cand.getAttribute('ip');
+ line += ' ';
+ line += cand.getAttribute('port');
+ line += ' ';
+ line += 'typ';
+ line += ' ' + cand.getAttribute('type');
+ line += ' ';
+ switch (cand.getAttribute('type')) {
+ case 'srflx':
+ case 'prflx':
+ case 'relay':
+ if (cand.getAttribute('rel-addr') && cand.getAttribute('rel-port')) {
+ line += 'raddr';
+ line += ' ';
+ line += cand.getAttribute('rel-addr');
+ line += ' ';
+ line += 'rport';
+ line += ' ';
+ line += cand.getAttribute('rel-port');
+ line += ' ';
+ }
+ break;
+ }
+ if (cand.getAttribute('protocol').toLowerCase() == 'tcp') {
+ line += 'tcptype';
+ line += ' ';
+ line += cand.getAttribute('tcptype');
+ line += ' ';
+ }
+ line += 'generation';
+ line += ' ';
+ line += cand.getAttribute('generation') || '0';
+ return line + '\r\n';
+ }
+};
module.exports = SDPUtil;
-},{}],55:[function(require,module,exports){
-var XMPPEvents = require("../../service/xmpp/XMPPEvents");
-
-function TraceablePeerConnection(ice_config, constraints, session) {
- var self = this;
- var RTCPeerconnection = navigator.mozGetUserMedia ? mozRTCPeerConnection : webkitRTCPeerConnection;
- this.peerconnection = new RTCPeerconnection(ice_config, constraints);
- this.updateLog = [];
- this.stats = {};
- this.statsinterval = null;
- this.maxstats = 0; // limit to 300 values, i.e. 5 minutes; set to 0 to disable
- var Interop = require('sdp-interop').Interop;
- this.interop = new Interop();
- var Simulcast = require('sdp-simulcast');
- this.simulcast = new Simulcast({numOfLayers: 3, explodeRemoteSimulcast: false});
-
- // override as desired
- this.trace = function (what, info) {
- //console.warn('WTRACE', what, info);
- self.updateLog.push({
- time: new Date(),
- type: what,
- value: info || ""
- });
- };
- this.onicecandidate = null;
- this.peerconnection.onicecandidate = function (event) {
- self.trace('onicecandidate', JSON.stringify(event.candidate, null, ' '));
- if (self.onicecandidate !== null) {
- self.onicecandidate(event);
- }
- };
- this.onaddstream = null;
- this.peerconnection.onaddstream = function (event) {
- self.trace('onaddstream', event.stream.id);
- if (self.onaddstream !== null) {
- self.onaddstream(event);
- }
- };
- this.onremovestream = null;
- this.peerconnection.onremovestream = function (event) {
- self.trace('onremovestream', event.stream.id);
- if (self.onremovestream !== null) {
- self.onremovestream(event);
- }
- };
- this.onsignalingstatechange = null;
- this.peerconnection.onsignalingstatechange = function (event) {
- self.trace('onsignalingstatechange', self.signalingState);
- if (self.onsignalingstatechange !== null) {
- self.onsignalingstatechange(event);
- }
- };
- this.oniceconnectionstatechange = null;
- this.peerconnection.oniceconnectionstatechange = function (event) {
- self.trace('oniceconnectionstatechange', self.iceConnectionState);
- if (self.oniceconnectionstatechange !== null) {
- self.oniceconnectionstatechange(event);
- }
- };
- this.onnegotiationneeded = null;
- this.peerconnection.onnegotiationneeded = function (event) {
- self.trace('onnegotiationneeded');
- if (self.onnegotiationneeded !== null) {
- self.onnegotiationneeded(event);
- }
- };
- self.ondatachannel = null;
- this.peerconnection.ondatachannel = function (event) {
- self.trace('ondatachannel', event);
- if (self.ondatachannel !== null) {
- self.ondatachannel(event);
- }
- };
- if (!navigator.mozGetUserMedia && this.maxstats) {
- this.statsinterval = window.setInterval(function() {
- self.peerconnection.getStats(function(stats) {
- var results = stats.result();
- for (var i = 0; i < results.length; ++i) {
- //console.log(results[i].type, results[i].id, results[i].names())
- var now = new Date();
- results[i].names().forEach(function (name) {
- var id = results[i].id + '-' + name;
- if (!self.stats[id]) {
- self.stats[id] = {
- startTime: now,
- endTime: now,
- values: [],
- times: []
- };
- }
- self.stats[id].values.push(results[i].stat(name));
- self.stats[id].times.push(now.getTime());
- if (self.stats[id].values.length > self.maxstats) {
- self.stats[id].values.shift();
- self.stats[id].times.shift();
- }
- self.stats[id].endTime = now;
- });
- }
- });
-
- }, 1000);
- }
-};
-
-dumpSDP = function(description) {
- if (typeof description === 'undefined' || description == null) {
- return '';
- }
-
- return 'type: ' + description.type + '\r\n' + description.sdp;
-};
-
-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;
- this.trace('getLocalDescription::preTransform', dumpSDP(desc));
-
- // if we're running on FF, transform to Plan B first.
- if (navigator.mozGetUserMedia) {
- 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 (navigator.mozGetUserMedia) {
- desc = this.interop.toPlanB(desc);
- this.trace('getRemoteDescription::postTransform (Plan B)', dumpSDP(desc));
- }
- return desc;
- });
-}
-
-TraceablePeerConnection.prototype.addStream = function (stream) {
- this.trace('addStream', stream.id);
- try
- {
- this.peerconnection.addStream(stream);
- }
- catch (e)
- {
- console.error(e);
- return;
- }
-};
-
-TraceablePeerConnection.prototype.removeStream = function (stream, stopStreams) {
- this.trace('removeStream', stream.id);
- if(stopStreams) {
- stream.getAudioTracks().forEach(function (track) {
- track.stop();
- });
- stream.getVideoTracks().forEach(function (track) {
- track.stop();
- });
- }
-
- try {
- // FF doesn't support this yet.
- this.peerconnection.removeStream(stream);
- } catch (e) {
- console.error(e);
- }
-};
-
-TraceablePeerConnection.prototype.createDataChannel = function (label, opts) {
- this.trace('createDataChannel', label, opts);
- return this.peerconnection.createDataChannel(label, opts);
-};
-
-TraceablePeerConnection.prototype.setLocalDescription = function (description, successCallback, failureCallback) {
- this.trace('setLocalDescription::preTransform', dumpSDP(description));
- // if we're running on FF, transform to Plan A first.
- if (navigator.mozGetUserMedia) {
- description = this.interop.toUnifiedPlan(description);
- this.trace('setLocalDescription::postTransform (Plan A)', dumpSDP(description));
- }
-
- var self = this;
- this.peerconnection.setLocalDescription(description,
- function () {
- self.trace('setLocalDescriptionOnSuccess');
- successCallback();
- },
- function (err) {
- self.trace('setLocalDescriptionOnFailure', err);
- failureCallback(err);
- }
- );
- /*
- if (this.statsinterval === null && this.maxstats > 0) {
- // start gathering stats
- }
- */
-};
-
-TraceablePeerConnection.prototype.setRemoteDescription = function (description, successCallback, failureCallback) {
- this.trace('setRemoteDescription::preTransform', dumpSDP(description));
- // TODO the focus should squeze or explode the remote simulcast
- description = this.simulcast.mungeRemoteDescription(description);
- this.trace('setRemoteDescription::postTransform (simulcast)', dumpSDP(description));
-
- // if we're running on FF, transform to Plan A first.
- if (navigator.mozGetUserMedia) {
- description = this.interop.toUnifiedPlan(description);
- this.trace('setRemoteDescription::postTransform (Plan A)', dumpSDP(description));
- }
- var self = this;
- this.peerconnection.setRemoteDescription(description,
- function () {
- self.trace('setRemoteDescriptionOnSuccess');
- successCallback();
- },
- function (err) {
- self.trace('setRemoteDescriptionOnFailure', err);
- failureCallback(err);
- }
- );
- /*
- if (this.statsinterval === null && this.maxstats > 0) {
- // start gathering stats
- }
- */
-};
-
-TraceablePeerConnection.prototype.close = function () {
- this.trace('stop');
- if (this.statsinterval !== null) {
- window.clearInterval(this.statsinterval);
- this.statsinterval = null;
- }
- this.peerconnection.close();
-};
-
-TraceablePeerConnection.prototype.createOffer = function (successCallback, failureCallback, constraints) {
- var self = this;
- this.trace('createOffer', JSON.stringify(constraints, null, ' '));
- this.peerconnection.createOffer(
- function (offer) {
- self.trace('createOfferOnSuccess::preTransform', dumpSDP(offer));
- // if we're running on FF, transform to Plan B first.
- // NOTE this is not tested because in meet the focus generates the
- // offer.
- if (navigator.mozGetUserMedia) {
- offer = self.interop.toPlanB(offer);
- self.trace('createOfferOnSuccess::postTransform (Plan B)', dumpSDP(offer));
- }
-
- if (config.enableSimulcast && self.simulcast.isSupported()) {
- offer = self.simulcast.mungeLocalDescription(offer);
- self.trace('createOfferOnSuccess::postTransform (simulcast)', dumpSDP(offer));
- }
- successCallback(offer);
- },
- function(err) {
- self.trace('createOfferOnFailure', err);
- failureCallback(err);
- },
- constraints
- );
-};
-
-TraceablePeerConnection.prototype.createAnswer = function (successCallback, failureCallback, constraints) {
- var self = this;
- this.trace('createAnswer', JSON.stringify(constraints, null, ' '));
- this.peerconnection.createAnswer(
- function (answer) {
- self.trace('createAnswerOnSuccess::preTransfom', dumpSDP(answer));
- // if we're running on FF, transform to Plan A first.
- if (navigator.mozGetUserMedia) {
- answer = self.interop.toPlanB(answer);
- self.trace('createAnswerOnSuccess::postTransfom (Plan B)', dumpSDP(answer));
- }
- if (config.enableSimulcast && self.simulcast.isSupported()) {
- answer = self.simulcast.mungeLocalDescription(answer);
- self.trace('createAnswerOnSuccess::postTransfom (simulcast)', dumpSDP(answer));
- }
- successCallback(answer);
- },
- function(err) {
- self.trace('createAnswerOnFailure', err);
- failureCallback(err);
- },
- constraints
- );
-};
-
-TraceablePeerConnection.prototype.addIceCandidate = function (candidate, successCallback, failureCallback) {
- var self = this;
- this.trace('addIceCandidate', JSON.stringify(candidate, null, ' '));
- this.peerconnection.addIceCandidate(candidate);
- /* maybe later
- this.peerconnection.addIceCandidate(candidate,
- function () {
- self.trace('addIceCandidateOnSuccess');
- successCallback();
- },
- function (err) {
- self.trace('addIceCandidateOnFailure', err);
- failureCallback(err);
- }
- );
- */
-};
-
-TraceablePeerConnection.prototype.getStats = function(callback, errback) {
- if (navigator.mozGetUserMedia) {
- // ignore for now...
- if(!errback)
- errback = function () {
-
- }
- this.peerconnection.getStats(null,callback,errback);
- } else {
- this.peerconnection.getStats(callback);
- }
-};
-
-module.exports = TraceablePeerConnection;
-
-
-},{"../../service/xmpp/XMPPEvents":111,"sdp-interop":87,"sdp-simulcast":94}],56:[function(require,module,exports){
-/* global $, $iq, APP, config, connection, UI, messageHandler,
- roomName, sessionTerminated, Strophe, Util */
-var XMPPEvents = require("../../service/xmpp/XMPPEvents");
-var Settings = require("../settings/Settings");
-
-var AuthenticationEvents
- = require("../../service/authentication/AuthenticationEvents");
-
-/**
- * Contains logic responsible for enabling/disabling functionality available
- * only to moderator users.
- */
-var connection = null;
-var focusUserJid;
-
-function createExpBackoffTimer(step) {
- var count = 1;
- return function (reset) {
- // Reset call
- if (reset) {
- count = 1;
- return;
- }
- // Calculate next timeout
- var timeout = Math.pow(2, count - 1);
- count += 1;
- return timeout * step;
- };
-}
-
-var getNextTimeout = createExpBackoffTimer(1000);
-var getNextErrorTimeout = createExpBackoffTimer(1000);
-// External authentication stuff
-var externalAuthEnabled = false;
-// Sip gateway can be enabled by configuring Jigasi host in config.js or
-// it will be enabled automatically if focus detects the component through
-// service discovery.
-var sipGatewayEnabled = config.hosts.call_control !== undefined;
-
-var eventEmitter = null;
-
-var Moderator = {
- isModerator: function () {
- return connection && connection.emuc.isModerator();
- },
-
- isPeerModerator: function (peerJid) {
- return connection &&
- connection.emuc.getMemberRole(peerJid) === 'moderator';
- },
-
- isExternalAuthEnabled: function () {
- return externalAuthEnabled;
- },
-
- isSipGatewayEnabled: function () {
- return sipGatewayEnabled;
- },
-
- setConnection: function (con) {
- connection = con;
- },
-
- init: function (xmpp, emitter) {
- this.xmppService = xmpp;
- eventEmitter = emitter;
-
- // Message listener that talks to POPUP window
- function listener(event) {
- if (event.data && event.data.sessionId) {
- if (event.origin !== window.location.origin) {
- console.warn(
- "Ignoring sessionId from different origin: " + event.origin);
- return;
- }
- localStorage.setItem('sessionId', event.data.sessionId);
- // After popup is closed we will authenticate
- }
- }
- // Register
- if (window.addEventListener) {
- window.addEventListener("message", listener, false);
- } else {
- window.attachEvent("onmessage", listener);
- }
- },
-
- onMucMemberLeft: function (jid) {
- console.info("Someone left is it focus ? " + jid);
- var resource = Strophe.getResourceFromJid(jid);
- if (resource === 'focus' && !this.xmppService.sessionTerminated) {
- console.info(
- "Focus has left the room - leaving conference");
- //hangUp();
- // We'd rather reload to have everything re-initialized
- // FIXME: show some message before reload
- location.reload();
- }
- },
-
- setFocusUserJid: function (focusJid) {
- if (!focusUserJid) {
- focusUserJid = focusJid;
- console.info("Focus jid set to: " + focusUserJid);
- }
- },
-
- getFocusUserJid: function () {
- return focusUserJid;
- },
-
- getFocusComponent: function () {
- // Get focus component address
- var focusComponent = config.hosts.focus;
- // If not specified use default: 'focus.domain'
- if (!focusComponent) {
- focusComponent = 'focus.' + config.hosts.domain;
- }
- return focusComponent;
- },
-
- createConferenceIq: function (roomName) {
- // Generate create conference IQ
- var elem = $iq({to: Moderator.getFocusComponent(), type: 'set'});
-
- // Session Id used for authentication
- var sessionId = localStorage.getItem('sessionId');
- var machineUID = Settings.getSettings().uid;
-
- console.info(
- "Session ID: " + sessionId + " machine UID: " + machineUID);
-
- elem.c('conference', {
- xmlns: 'http://jitsi.org/protocol/focus',
- room: roomName,
- 'machine-uid': machineUID
- });
-
- if (sessionId) {
- elem.attrs({ 'session-id': sessionId});
- }
-
- if (config.hosts.bridge !== undefined) {
- elem.c(
- 'property',
- { name: 'bridge', value: config.hosts.bridge})
- .up();
- }
- // Tell the focus we have Jigasi configured
- if (config.hosts.call_control !== undefined) {
- elem.c(
- 'property',
- { name: 'call_control', value: config.hosts.call_control})
- .up();
- }
- if (config.channelLastN !== undefined) {
- elem.c(
- 'property',
- { name: 'channelLastN', value: config.channelLastN})
- .up();
- }
- if (config.adaptiveLastN !== undefined) {
- elem.c(
- 'property',
- { name: 'adaptiveLastN', value: config.adaptiveLastN})
- .up();
- }
- if (config.adaptiveSimulcast !== undefined) {
- elem.c(
- 'property',
- { name: 'adaptiveSimulcast', value: config.adaptiveSimulcast})
- .up();
- }
- if (config.openSctp !== undefined) {
- elem.c(
- 'property',
- { name: 'openSctp', value: config.openSctp})
- .up();
- }
- if(config.startAudioMuted !== undefined)
- {
- elem.c(
- 'property',
- { name: 'startAudioMuted', value: config.startAudioMuted})
- .up();
- }
- if(config.startVideoMuted !== undefined)
- {
- elem.c(
- 'property',
- { name: 'startVideoMuted', value: config.startVideoMuted})
- .up();
- }
- elem.c(
- 'property',
- { name: 'simulcastMode', value: 'rewriting'})
- .up();
- elem.up();
- return elem;
- },
-
- parseSessionId: function (resultIq) {
- var sessionId = $(resultIq).find('conference').attr('session-id');
- if (sessionId) {
- console.info('Received sessionId: ' + sessionId);
- localStorage.setItem('sessionId', sessionId);
- }
- },
-
- parseConfigOptions: function (resultIq) {
-
- Moderator.setFocusUserJid(
- $(resultIq).find('conference').attr('focusjid'));
-
- var authenticationEnabled
- = $(resultIq).find(
- '>conference>property' +
- '[name=\'authentication\'][value=\'true\']').length > 0;
-
- console.info("Authentication enabled: " + authenticationEnabled);
-
- externalAuthEnabled
- = $(resultIq).find(
- '>conference>property' +
- '[name=\'externalAuth\'][value=\'true\']').length > 0;
-
- console.info('External authentication enabled: ' + externalAuthEnabled);
-
- if (!externalAuthEnabled) {
- // We expect to receive sessionId in 'internal' authentication mode
- Moderator.parseSessionId(resultIq);
- }
-
- var authIdentity = $(resultIq).find('>conference').attr('identity');
-
- eventEmitter.emit(AuthenticationEvents.IDENTITY_UPDATED,
- authenticationEnabled, authIdentity);
-
- // Check if focus has auto-detected Jigasi component(this will be also
- // included if we have passed our host from the config)
- if ($(resultIq).find(
- '>conference>property' +
- '[name=\'sipGatewayEnabled\'][value=\'true\']').length) {
- sipGatewayEnabled = true;
- }
-
- console.info("Sip gateway enabled: " + sipGatewayEnabled);
- },
-
- // FIXME: we need to show the fact that we're waiting for the focus
- // to the user(or that focus is not available)
- allocateConferenceFocus: function (roomName, callback) {
- // Try to use focus user JID from the config
- Moderator.setFocusUserJid(config.focusUserJid);
- // Send create conference IQ
- var iq = Moderator.createConferenceIq(roomName);
- var self = this;
- connection.sendIQ(
- iq,
- function (result) {
-
- // Setup config options
- Moderator.parseConfigOptions(result);
-
- if ('true' === $(result).find('conference').attr('ready')) {
- // Reset both timers
- getNextTimeout(true);
- getNextErrorTimeout(true);
- // Exec callback
- callback();
- } else {
- var waitMs = getNextTimeout();
- console.info("Waiting for the focus... " + waitMs);
- // Reset error timeout
- getNextErrorTimeout(true);
- window.setTimeout(
- function () {
- Moderator.allocateConferenceFocus(
- roomName, callback);
- }, waitMs);
- }
- },
- function (error) {
- // Invalid session ? remove and try again
- // without session ID to get a new one
- var invalidSession
- = $(error).find('>error>session-invalid').length;
- if (invalidSession) {
- console.info("Session expired! - removing");
- localStorage.removeItem("sessionId");
- }
- if ($(error).find('>error>graceful-shutdown').length) {
- eventEmitter.emit(XMPPEvents.GRACEFUL_SHUTDOWN);
- return;
- }
- // Check for error returned by the reservation system
- var reservationErr = $(error).find('>error>reservation-error');
- if (reservationErr.length) {
- // Trigger error event
- var errorCode = reservationErr.attr('error-code');
- var errorMsg;
- if ($(error).find('>error>text')) {
- errorMsg = $(error).find('>error>text').text();
- }
- eventEmitter.emit(
- XMPPEvents.RESERVATION_ERROR, errorCode, errorMsg);
- return;
- }
- // Not authorized to create new room
- if ($(error).find('>error>not-authorized').length) {
- console.warn("Unauthorized to start the conference", error);
- var toDomain
- = Strophe.getDomainFromJid(error.getAttribute('to'));
- if (toDomain !== config.hosts.anonymousdomain) {
- // FIXME: "is external" should come either from
- // the focus or config.js
- externalAuthEnabled = true;
- }
- eventEmitter.emit(
- XMPPEvents.AUTHENTICATION_REQUIRED,
- function () {
- Moderator.allocateConferenceFocus(
- roomName, callback);
- });
- return;
- }
- var waitMs = getNextErrorTimeout();
- console.error("Focus error, retry after " + waitMs, error);
- // Show message
- var focusComponent = Moderator.getFocusComponent();
- var retrySec = waitMs / 1000;
- // FIXME: message is duplicated ?
- // Do not show in case of session invalid
- // which means just a retry
- if (!invalidSession) {
- APP.UI.messageHandler.notify(
- null, "notify.focus",
- 'disconnected', "notify.focusFail",
- {component: focusComponent, ms: retrySec});
- }
- // Reset response timeout
- getNextTimeout(true);
- window.setTimeout(
- function () {
- Moderator.allocateConferenceFocus(roomName, callback);
- }, waitMs);
- }
- );
- },
-
- getLoginUrl: function (roomName, urlCallback) {
- var iq = $iq({to: Moderator.getFocusComponent(), type: 'get'});
- iq.c('login-url', {
- xmlns: 'http://jitsi.org/protocol/focus',
- room: roomName,
- 'machine-uid': Settings.getSettings().uid
- });
- connection.sendIQ(
- iq,
- function (result) {
- var url = $(result).find('login-url').attr('url');
- url = url = decodeURIComponent(url);
- if (url) {
- console.info("Got auth url: " + url);
- urlCallback(url);
- } else {
- console.error(
- "Failed to get auth url from the focus", result);
- }
- },
- function (error) {
- console.error("Get auth url error", error);
- }
- );
- },
- getPopupLoginUrl: function (roomName, urlCallback) {
- var iq = $iq({to: Moderator.getFocusComponent(), type: 'get'});
- iq.c('login-url', {
- xmlns: 'http://jitsi.org/protocol/focus',
- room: roomName,
- 'machine-uid': Settings.getSettings().uid,
- popup: true
- });
- connection.sendIQ(
- iq,
- function (result) {
- var url = $(result).find('login-url').attr('url');
- url = url = decodeURIComponent(url);
- if (url) {
- console.info("Got POPUP auth url: " + url);
- urlCallback(url);
- } else {
- console.error(
- "Failed to get POPUP auth url from the focus", result);
- }
- },
- function (error) {
- console.error('Get POPUP auth url error', error);
- }
- );
- },
- logout: function (callback) {
- var iq = $iq({to: Moderator.getFocusComponent(), type: 'set'});
- var sessionId = localStorage.getItem('sessionId');
- if (!sessionId) {
- callback();
- return;
- }
- iq.c('logout', {
- xmlns: 'http://jitsi.org/protocol/focus',
- 'session-id': sessionId
- });
- connection.sendIQ(
- iq,
- function (result) {
- var logoutUrl = $(result).find('logout').attr('logout-url');
- if (logoutUrl) {
- logoutUrl = decodeURIComponent(logoutUrl);
- }
- console.info("Log out OK, url: " + logoutUrl, result);
- localStorage.removeItem('sessionId');
- callback(logoutUrl);
- },
- function (error) {
- console.error("Logout error", error);
- }
- );
- }
-};
-
-module.exports = Moderator;
-
-
-
-
-},{"../../service/authentication/AuthenticationEvents":106,"../../service/xmpp/XMPPEvents":111,"../settings/Settings":45}],57:[function(require,module,exports){
-/* global $, $iq, config, connection, focusMucJid, messageHandler, Moderator,
- Toolbar, Util */
-var Moderator = require("./moderator");
-
-
-var recordingToken = null;
-var recordingEnabled;
-
-/**
- * Whether to use a jirecon component for recording, or use the videobridge
- * through COLIBRI.
- */
-var useJirecon = (typeof config.hosts.jirecon != "undefined");
-
-/**
- * The ID of the jirecon recording session. Jirecon generates it when we
- * initially start recording, and it needs to be used in subsequent requests
- * to jirecon.
- */
-var jireconRid = null;
-
-function setRecordingToken(token) {
- recordingToken = token;
-}
-
-function setRecording(state, token, callback, connection) {
- if (useJirecon){
- setRecordingJirecon(state, token, callback, connection);
- } else {
- setRecordingColibri(state, token, callback, connection);
- }
-}
-
-function setRecordingJirecon(state, token, callback, connection) {
- if (state == recordingEnabled){
- return;
- }
-
- var iq = $iq({to: config.hosts.jirecon, type: 'set'})
- .c('recording', {xmlns: 'http://jitsi.org/protocol/jirecon',
- action: state ? 'start' : 'stop',
- mucjid: connection.emuc.roomjid});
- if (!state){
- iq.attrs({rid: jireconRid});
- }
-
- console.log('Start recording');
-
- connection.sendIQ(
- iq,
- function (result) {
- // TODO wait for an IQ with the real status, since this is
- // provisional?
- jireconRid = $(result).find('recording').attr('rid');
- console.log('Recording ' + (state ? 'started' : 'stopped') +
- '(jirecon)' + result);
- recordingEnabled = state;
- if (!state){
- jireconRid = null;
- }
-
- callback(state);
- },
- function (error) {
- console.log('Failed to start recording, error: ', error);
- callback(recordingEnabled);
- });
-}
-
-// Sends a COLIBRI message which enables or disables (according to 'state')
-// the recording on the bridge. Waits for the result IQ and calls 'callback'
-// with the new recording state, according to the IQ.
-function setRecordingColibri(state, token, callback, connection) {
- var elem = $iq({to: connection.emuc.focusMucJid, type: 'set'});
- elem.c('conference', {
- xmlns: 'http://jitsi.org/protocol/colibri'
- });
- elem.c('recording', {state: state, token: token});
-
- connection.sendIQ(elem,
- function (result) {
- console.log('Set recording "', state, '". Result:', result);
- var recordingElem = $(result).find('>conference>recording');
- var newState = ('true' === recordingElem.attr('state'));
-
- recordingEnabled = newState;
- callback(newState);
- },
- function (error) {
- console.warn(error);
- callback(recordingEnabled);
- }
- );
-}
-
-var Recording = {
- toggleRecording: function (tokenEmptyCallback,
- startingCallback, startedCallback, connection) {
- if (!Moderator.isModerator()) {
- console.log(
- 'non-focus, or conference not yet organized:' +
- ' not enabling recording');
- return;
- }
-
- var self = this;
- // Jirecon does not (currently) support a token.
- if (!recordingToken && !useJirecon) {
- tokenEmptyCallback(function (value) {
- setRecordingToken(value);
- self.toggleRecording(tokenEmptyCallback,
- startingCallback, startedCallback, connection);
- });
-
- return;
- }
-
- var oldState = recordingEnabled;
- startingCallback(!oldState);
- setRecording(!oldState,
- recordingToken,
- function (state) {
- console.log("New recording state: ", state);
- if (state === oldState) {
- // FIXME: new focus:
- // this will not work when moderator changes
- // during active session. Then it will assume that
- // recording status has changed to true, but it might have
- // been already true(and we only received actual status from
- // the focus).
- //
- // SO we start with status null, so that it is initialized
- // here and will fail only after second click, so if invalid
- // token was used we have to press the button twice before
- // current status will be fetched and token will be reset.
- //
- // Reliable way would be to return authentication error.
- // Or status update when moderator connects.
- // Or we have to stop recording session when current
- // moderator leaves the room.
-
- // Failed to change, reset the token because it might
- // have been wrong
- setRecordingToken(null);
- }
- startedCallback(state);
-
- },
- connection
- );
- }
-
-}
-
+},{}],57:[function(require,module,exports){
+var XMPPEvents = require("../../service/xmpp/XMPPEvents");
+
+function TraceablePeerConnection(ice_config, constraints, session) {
+ var self = this;
+ var RTCPeerconnection = navigator.mozGetUserMedia ? mozRTCPeerConnection : webkitRTCPeerConnection;
+ this.peerconnection = new RTCPeerconnection(ice_config, constraints);
+ this.updateLog = [];
+ this.stats = {};
+ this.statsinterval = null;
+ this.maxstats = 0; // limit to 300 values, i.e. 5 minutes; set to 0 to disable
+ var Interop = require('sdp-interop').Interop;
+ this.interop = new Interop();
+ var Simulcast = require('sdp-simulcast');
+ this.simulcast = new Simulcast({numOfLayers: 3, explodeRemoteSimulcast: false});
+
+ // override as desired
+ this.trace = function (what, info) {
+ //console.warn('WTRACE', what, info);
+ self.updateLog.push({
+ time: new Date(),
+ type: what,
+ value: info || ""
+ });
+ };
+ this.onicecandidate = null;
+ this.peerconnection.onicecandidate = function (event) {
+ self.trace('onicecandidate', JSON.stringify(event.candidate, null, ' '));
+ if (self.onicecandidate !== null) {
+ self.onicecandidate(event);
+ }
+ };
+ this.onaddstream = null;
+ this.peerconnection.onaddstream = function (event) {
+ self.trace('onaddstream', event.stream.id);
+ if (self.onaddstream !== null) {
+ self.onaddstream(event);
+ }
+ };
+ this.onremovestream = null;
+ this.peerconnection.onremovestream = function (event) {
+ self.trace('onremovestream', event.stream.id);
+ if (self.onremovestream !== null) {
+ self.onremovestream(event);
+ }
+ };
+ this.onsignalingstatechange = null;
+ this.peerconnection.onsignalingstatechange = function (event) {
+ self.trace('onsignalingstatechange', self.signalingState);
+ if (self.onsignalingstatechange !== null) {
+ self.onsignalingstatechange(event);
+ }
+ };
+ this.oniceconnectionstatechange = null;
+ this.peerconnection.oniceconnectionstatechange = function (event) {
+ self.trace('oniceconnectionstatechange', self.iceConnectionState);
+ if (self.oniceconnectionstatechange !== null) {
+ self.oniceconnectionstatechange(event);
+ }
+ };
+ this.onnegotiationneeded = null;
+ this.peerconnection.onnegotiationneeded = function (event) {
+ self.trace('onnegotiationneeded');
+ if (self.onnegotiationneeded !== null) {
+ self.onnegotiationneeded(event);
+ }
+ };
+ self.ondatachannel = null;
+ this.peerconnection.ondatachannel = function (event) {
+ self.trace('ondatachannel', event);
+ if (self.ondatachannel !== null) {
+ self.ondatachannel(event);
+ }
+ };
+ if (!navigator.mozGetUserMedia && this.maxstats) {
+ this.statsinterval = window.setInterval(function() {
+ self.peerconnection.getStats(function(stats) {
+ var results = stats.result();
+ for (var i = 0; i < results.length; ++i) {
+ //console.log(results[i].type, results[i].id, results[i].names())
+ var now = new Date();
+ results[i].names().forEach(function (name) {
+ var id = results[i].id + '-' + name;
+ if (!self.stats[id]) {
+ self.stats[id] = {
+ startTime: now,
+ endTime: now,
+ values: [],
+ times: []
+ };
+ }
+ self.stats[id].values.push(results[i].stat(name));
+ self.stats[id].times.push(now.getTime());
+ if (self.stats[id].values.length > self.maxstats) {
+ self.stats[id].values.shift();
+ self.stats[id].times.shift();
+ }
+ self.stats[id].endTime = now;
+ });
+ }
+ });
+
+ }, 1000);
+ }
+};
+
+dumpSDP = function(description) {
+ if (typeof description === 'undefined' || description == null) {
+ return '';
+ }
+
+ return 'type: ' + description.type + '\r\n' + description.sdp;
+};
+
+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;
+ this.trace('getLocalDescription::preTransform', dumpSDP(desc));
+
+ // if we're running on FF, transform to Plan B first.
+ if (navigator.mozGetUserMedia) {
+ 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 (navigator.mozGetUserMedia) {
+ desc = this.interop.toPlanB(desc);
+ this.trace('getRemoteDescription::postTransform (Plan B)', dumpSDP(desc));
+ }
+ return desc;
+ });
+}
+
+TraceablePeerConnection.prototype.addStream = function (stream) {
+ this.trace('addStream', stream.id);
+ try
+ {
+ this.peerconnection.addStream(stream);
+ }
+ catch (e)
+ {
+ console.error(e);
+ return;
+ }
+};
+
+TraceablePeerConnection.prototype.removeStream = function (stream, stopStreams) {
+ this.trace('removeStream', stream.id);
+ if(stopStreams) {
+ stream.getAudioTracks().forEach(function (track) {
+ track.stop();
+ });
+ stream.getVideoTracks().forEach(function (track) {
+ track.stop();
+ });
+ }
+
+ try {
+ // FF doesn't support this yet.
+ this.peerconnection.removeStream(stream);
+ } catch (e) {
+ console.error(e);
+ }
+};
+
+TraceablePeerConnection.prototype.createDataChannel = function (label, opts) {
+ this.trace('createDataChannel', label, opts);
+ return this.peerconnection.createDataChannel(label, opts);
+};
+
+TraceablePeerConnection.prototype.setLocalDescription = function (description, successCallback, failureCallback) {
+ this.trace('setLocalDescription::preTransform', dumpSDP(description));
+ // if we're running on FF, transform to Plan A first.
+ if (navigator.mozGetUserMedia) {
+ description = this.interop.toUnifiedPlan(description);
+ this.trace('setLocalDescription::postTransform (Plan A)', dumpSDP(description));
+ }
+
+ var self = this;
+ this.peerconnection.setLocalDescription(description,
+ function () {
+ self.trace('setLocalDescriptionOnSuccess');
+ successCallback();
+ },
+ function (err) {
+ self.trace('setLocalDescriptionOnFailure', err);
+ failureCallback(err);
+ }
+ );
+ /*
+ if (this.statsinterval === null && this.maxstats > 0) {
+ // start gathering stats
+ }
+ */
+};
+
+TraceablePeerConnection.prototype.setRemoteDescription = function (description, successCallback, failureCallback) {
+ this.trace('setRemoteDescription::preTransform', dumpSDP(description));
+ // TODO the focus should squeze or explode the remote simulcast
+ description = this.simulcast.mungeRemoteDescription(description);
+ this.trace('setRemoteDescription::postTransform (simulcast)', dumpSDP(description));
+
+ // if we're running on FF, transform to Plan A first.
+ if (navigator.mozGetUserMedia) {
+ description = this.interop.toUnifiedPlan(description);
+ this.trace('setRemoteDescription::postTransform (Plan A)', dumpSDP(description));
+ }
+ var self = this;
+ this.peerconnection.setRemoteDescription(description,
+ function () {
+ self.trace('setRemoteDescriptionOnSuccess');
+ successCallback();
+ },
+ function (err) {
+ self.trace('setRemoteDescriptionOnFailure', err);
+ failureCallback(err);
+ }
+ );
+ /*
+ if (this.statsinterval === null && this.maxstats > 0) {
+ // start gathering stats
+ }
+ */
+};
+
+TraceablePeerConnection.prototype.close = function () {
+ this.trace('stop');
+ if (this.statsinterval !== null) {
+ window.clearInterval(this.statsinterval);
+ this.statsinterval = null;
+ }
+ this.peerconnection.close();
+};
+
+TraceablePeerConnection.prototype.createOffer = function (successCallback, failureCallback, constraints) {
+ var self = this;
+ this.trace('createOffer', JSON.stringify(constraints, null, ' '));
+ this.peerconnection.createOffer(
+ function (offer) {
+ self.trace('createOfferOnSuccess::preTransform', dumpSDP(offer));
+ // if we're running on FF, transform to Plan B first.
+ // NOTE this is not tested because in meet the focus generates the
+ // offer.
+ if (navigator.mozGetUserMedia) {
+ offer = self.interop.toPlanB(offer);
+ self.trace('createOfferOnSuccess::postTransform (Plan B)', dumpSDP(offer));
+ }
+
+ if (config.enableSimulcast && self.simulcast.isSupported()) {
+ offer = self.simulcast.mungeLocalDescription(offer);
+ self.trace('createOfferOnSuccess::postTransform (simulcast)', dumpSDP(offer));
+ }
+ successCallback(offer);
+ },
+ function(err) {
+ self.trace('createOfferOnFailure', err);
+ failureCallback(err);
+ },
+ constraints
+ );
+};
+
+TraceablePeerConnection.prototype.createAnswer = function (successCallback, failureCallback, constraints) {
+ var self = this;
+ this.trace('createAnswer', JSON.stringify(constraints, null, ' '));
+ this.peerconnection.createAnswer(
+ function (answer) {
+ self.trace('createAnswerOnSuccess::preTransfom', dumpSDP(answer));
+ // if we're running on FF, transform to Plan A first.
+ if (navigator.mozGetUserMedia) {
+ answer = self.interop.toPlanB(answer);
+ self.trace('createAnswerOnSuccess::postTransfom (Plan B)', dumpSDP(answer));
+ }
+ if (config.enableSimulcast && self.simulcast.isSupported()) {
+ answer = self.simulcast.mungeLocalDescription(answer);
+ self.trace('createAnswerOnSuccess::postTransfom (simulcast)', dumpSDP(answer));
+ }
+ successCallback(answer);
+ },
+ function(err) {
+ self.trace('createAnswerOnFailure', err);
+ failureCallback(err);
+ },
+ constraints
+ );
+};
+
+TraceablePeerConnection.prototype.addIceCandidate = function (candidate, successCallback, failureCallback) {
+ var self = this;
+ this.trace('addIceCandidate', JSON.stringify(candidate, null, ' '));
+ this.peerconnection.addIceCandidate(candidate);
+ /* maybe later
+ this.peerconnection.addIceCandidate(candidate,
+ function () {
+ self.trace('addIceCandidateOnSuccess');
+ successCallback();
+ },
+ function (err) {
+ self.trace('addIceCandidateOnFailure', err);
+ failureCallback(err);
+ }
+ );
+ */
+};
+
+TraceablePeerConnection.prototype.getStats = function(callback, errback) {
+ if (navigator.mozGetUserMedia) {
+ // ignore for now...
+ if(!errback)
+ errback = function () {
+
+ }
+ this.peerconnection.getStats(null,callback,errback);
+ } else {
+ this.peerconnection.getStats(callback);
+ }
+};
+
+module.exports = TraceablePeerConnection;
+
+
+},{"../../service/xmpp/XMPPEvents":109,"sdp-interop":89,"sdp-simulcast":92}],58:[function(require,module,exports){
+/* global $, $iq, APP, config, connection, UI, messageHandler,
+ roomName, sessionTerminated, Strophe, Util */
+var XMPPEvents = require("../../service/xmpp/XMPPEvents");
+var Settings = require("../settings/Settings");
+
+var AuthenticationEvents
+ = require("../../service/authentication/AuthenticationEvents");
+
+/**
+ * Contains logic responsible for enabling/disabling functionality available
+ * only to moderator users.
+ */
+var connection = null;
+var focusUserJid;
+
+function createExpBackoffTimer(step) {
+ var count = 1;
+ return function (reset) {
+ // Reset call
+ if (reset) {
+ count = 1;
+ return;
+ }
+ // Calculate next timeout
+ var timeout = Math.pow(2, count - 1);
+ count += 1;
+ return timeout * step;
+ };
+}
+
+var getNextTimeout = createExpBackoffTimer(1000);
+var getNextErrorTimeout = createExpBackoffTimer(1000);
+// External authentication stuff
+var externalAuthEnabled = false;
+// Sip gateway can be enabled by configuring Jigasi host in config.js or
+// it will be enabled automatically if focus detects the component through
+// service discovery.
+var sipGatewayEnabled = config.hosts.call_control !== undefined;
+
+var eventEmitter = null;
+
+var Moderator = {
+ isModerator: function () {
+ return connection && connection.emuc.isModerator();
+ },
+
+ isPeerModerator: function (peerJid) {
+ return connection &&
+ connection.emuc.getMemberRole(peerJid) === 'moderator';
+ },
+
+ isExternalAuthEnabled: function () {
+ return externalAuthEnabled;
+ },
+
+ isSipGatewayEnabled: function () {
+ return sipGatewayEnabled;
+ },
+
+ setConnection: function (con) {
+ connection = con;
+ },
+
+ init: function (xmpp, emitter) {
+ this.xmppService = xmpp;
+ eventEmitter = emitter;
+
+ // Message listener that talks to POPUP window
+ function listener(event) {
+ if (event.data && event.data.sessionId) {
+ if (event.origin !== window.location.origin) {
+ console.warn(
+ "Ignoring sessionId from different origin: " + event.origin);
+ return;
+ }
+ localStorage.setItem('sessionId', event.data.sessionId);
+ // After popup is closed we will authenticate
+ }
+ }
+ // Register
+ if (window.addEventListener) {
+ window.addEventListener("message", listener, false);
+ } else {
+ window.attachEvent("onmessage", listener);
+ }
+ },
+
+ onMucMemberLeft: function (jid) {
+ console.info("Someone left is it focus ? " + jid);
+ var resource = Strophe.getResourceFromJid(jid);
+ if (resource === 'focus' && !this.xmppService.sessionTerminated) {
+ console.info(
+ "Focus has left the room - leaving conference");
+ //hangUp();
+ // We'd rather reload to have everything re-initialized
+ // FIXME: show some message before reload
+ location.reload();
+ }
+ },
+
+ setFocusUserJid: function (focusJid) {
+ if (!focusUserJid) {
+ focusUserJid = focusJid;
+ console.info("Focus jid set to: " + focusUserJid);
+ }
+ },
+
+ getFocusUserJid: function () {
+ return focusUserJid;
+ },
+
+ getFocusComponent: function () {
+ // Get focus component address
+ var focusComponent = config.hosts.focus;
+ // If not specified use default: 'focus.domain'
+ if (!focusComponent) {
+ focusComponent = 'focus.' + config.hosts.domain;
+ }
+ return focusComponent;
+ },
+
+ createConferenceIq: function (roomName) {
+ // Generate create conference IQ
+ var elem = $iq({to: Moderator.getFocusComponent(), type: 'set'});
+
+ // Session Id used for authentication
+ var sessionId = localStorage.getItem('sessionId');
+ var machineUID = Settings.getSettings().uid;
+
+ console.info(
+ "Session ID: " + sessionId + " machine UID: " + machineUID);
+
+ elem.c('conference', {
+ xmlns: 'http://jitsi.org/protocol/focus',
+ room: roomName,
+ 'machine-uid': machineUID
+ });
+
+ if (sessionId) {
+ elem.attrs({ 'session-id': sessionId});
+ }
+
+ if (config.hosts.bridge !== undefined) {
+ elem.c(
+ 'property',
+ { name: 'bridge', value: config.hosts.bridge})
+ .up();
+ }
+ // Tell the focus we have Jigasi configured
+ if (config.hosts.call_control !== undefined) {
+ elem.c(
+ 'property',
+ { name: 'call_control', value: config.hosts.call_control})
+ .up();
+ }
+ if (config.channelLastN !== undefined) {
+ elem.c(
+ 'property',
+ { name: 'channelLastN', value: config.channelLastN})
+ .up();
+ }
+ if (config.adaptiveLastN !== undefined) {
+ elem.c(
+ 'property',
+ { name: 'adaptiveLastN', value: config.adaptiveLastN})
+ .up();
+ }
+ if (config.adaptiveSimulcast !== undefined) {
+ elem.c(
+ 'property',
+ { name: 'adaptiveSimulcast', value: config.adaptiveSimulcast})
+ .up();
+ }
+ if (config.openSctp !== undefined) {
+ elem.c(
+ 'property',
+ { name: 'openSctp', value: config.openSctp})
+ .up();
+ }
+ if(config.startAudioMuted !== undefined)
+ {
+ elem.c(
+ 'property',
+ { name: 'startAudioMuted', value: config.startAudioMuted})
+ .up();
+ }
+ if(config.startVideoMuted !== undefined)
+ {
+ elem.c(
+ 'property',
+ { name: 'startVideoMuted', value: config.startVideoMuted})
+ .up();
+ }
+ elem.c(
+ 'property',
+ { name: 'simulcastMode', value: 'rewriting'})
+ .up();
+ elem.up();
+ return elem;
+ },
+
+ parseSessionId: function (resultIq) {
+ var sessionId = $(resultIq).find('conference').attr('session-id');
+ if (sessionId) {
+ console.info('Received sessionId: ' + sessionId);
+ localStorage.setItem('sessionId', sessionId);
+ }
+ },
+
+ parseConfigOptions: function (resultIq) {
+
+ Moderator.setFocusUserJid(
+ $(resultIq).find('conference').attr('focusjid'));
+
+ var authenticationEnabled
+ = $(resultIq).find(
+ '>conference>property' +
+ '[name=\'authentication\'][value=\'true\']').length > 0;
+
+ console.info("Authentication enabled: " + authenticationEnabled);
+
+ externalAuthEnabled
+ = $(resultIq).find(
+ '>conference>property' +
+ '[name=\'externalAuth\'][value=\'true\']').length > 0;
+
+ console.info('External authentication enabled: ' + externalAuthEnabled);
+
+ if (!externalAuthEnabled) {
+ // We expect to receive sessionId in 'internal' authentication mode
+ Moderator.parseSessionId(resultIq);
+ }
+
+ var authIdentity = $(resultIq).find('>conference').attr('identity');
+
+ eventEmitter.emit(AuthenticationEvents.IDENTITY_UPDATED,
+ authenticationEnabled, authIdentity);
+
+ // Check if focus has auto-detected Jigasi component(this will be also
+ // included if we have passed our host from the config)
+ if ($(resultIq).find(
+ '>conference>property' +
+ '[name=\'sipGatewayEnabled\'][value=\'true\']').length) {
+ sipGatewayEnabled = true;
+ }
+
+ console.info("Sip gateway enabled: " + sipGatewayEnabled);
+ },
+
+ // FIXME: we need to show the fact that we're waiting for the focus
+ // to the user(or that focus is not available)
+ allocateConferenceFocus: function (roomName, callback) {
+ // Try to use focus user JID from the config
+ Moderator.setFocusUserJid(config.focusUserJid);
+ // Send create conference IQ
+ var iq = Moderator.createConferenceIq(roomName);
+ var self = this;
+ connection.sendIQ(
+ iq,
+ function (result) {
+
+ // Setup config options
+ Moderator.parseConfigOptions(result);
+
+ if ('true' === $(result).find('conference').attr('ready')) {
+ // Reset both timers
+ getNextTimeout(true);
+ getNextErrorTimeout(true);
+ // Exec callback
+ callback();
+ } else {
+ var waitMs = getNextTimeout();
+ console.info("Waiting for the focus... " + waitMs);
+ // Reset error timeout
+ getNextErrorTimeout(true);
+ window.setTimeout(
+ function () {
+ Moderator.allocateConferenceFocus(
+ roomName, callback);
+ }, waitMs);
+ }
+ },
+ function (error) {
+ // Invalid session ? remove and try again
+ // without session ID to get a new one
+ var invalidSession
+ = $(error).find('>error>session-invalid').length;
+ if (invalidSession) {
+ console.info("Session expired! - removing");
+ localStorage.removeItem("sessionId");
+ }
+ if ($(error).find('>error>graceful-shutdown').length) {
+ eventEmitter.emit(XMPPEvents.GRACEFUL_SHUTDOWN);
+ return;
+ }
+ // Check for error returned by the reservation system
+ var reservationErr = $(error).find('>error>reservation-error');
+ if (reservationErr.length) {
+ // Trigger error event
+ var errorCode = reservationErr.attr('error-code');
+ var errorMsg;
+ if ($(error).find('>error>text')) {
+ errorMsg = $(error).find('>error>text').text();
+ }
+ eventEmitter.emit(
+ XMPPEvents.RESERVATION_ERROR, errorCode, errorMsg);
+ return;
+ }
+ // Not authorized to create new room
+ if ($(error).find('>error>not-authorized').length) {
+ console.warn("Unauthorized to start the conference", error);
+ var toDomain
+ = Strophe.getDomainFromJid(error.getAttribute('to'));
+ if (toDomain !== config.hosts.anonymousdomain) {
+ // FIXME: "is external" should come either from
+ // the focus or config.js
+ externalAuthEnabled = true;
+ }
+ eventEmitter.emit(
+ XMPPEvents.AUTHENTICATION_REQUIRED,
+ function () {
+ Moderator.allocateConferenceFocus(
+ roomName, callback);
+ });
+ return;
+ }
+ var waitMs = getNextErrorTimeout();
+ console.error("Focus error, retry after " + waitMs, error);
+ // Show message
+ var focusComponent = Moderator.getFocusComponent();
+ var retrySec = waitMs / 1000;
+ // FIXME: message is duplicated ?
+ // Do not show in case of session invalid
+ // which means just a retry
+ if (!invalidSession) {
+ APP.UI.messageHandler.notify(
+ null, "notify.focus",
+ 'disconnected', "notify.focusFail",
+ {component: focusComponent, ms: retrySec});
+ }
+ // Reset response timeout
+ getNextTimeout(true);
+ window.setTimeout(
+ function () {
+ Moderator.allocateConferenceFocus(roomName, callback);
+ }, waitMs);
+ }
+ );
+ },
+
+ getLoginUrl: function (roomName, urlCallback) {
+ var iq = $iq({to: Moderator.getFocusComponent(), type: 'get'});
+ iq.c('login-url', {
+ xmlns: 'http://jitsi.org/protocol/focus',
+ room: roomName,
+ 'machine-uid': Settings.getSettings().uid
+ });
+ connection.sendIQ(
+ iq,
+ function (result) {
+ var url = $(result).find('login-url').attr('url');
+ url = url = decodeURIComponent(url);
+ if (url) {
+ console.info("Got auth url: " + url);
+ urlCallback(url);
+ } else {
+ console.error(
+ "Failed to get auth url from the focus", result);
+ }
+ },
+ function (error) {
+ console.error("Get auth url error", error);
+ }
+ );
+ },
+ getPopupLoginUrl: function (roomName, urlCallback) {
+ var iq = $iq({to: Moderator.getFocusComponent(), type: 'get'});
+ iq.c('login-url', {
+ xmlns: 'http://jitsi.org/protocol/focus',
+ room: roomName,
+ 'machine-uid': Settings.getSettings().uid,
+ popup: true
+ });
+ connection.sendIQ(
+ iq,
+ function (result) {
+ var url = $(result).find('login-url').attr('url');
+ url = url = decodeURIComponent(url);
+ if (url) {
+ console.info("Got POPUP auth url: " + url);
+ urlCallback(url);
+ } else {
+ console.error(
+ "Failed to get POPUP auth url from the focus", result);
+ }
+ },
+ function (error) {
+ console.error('Get POPUP auth url error', error);
+ }
+ );
+ },
+ logout: function (callback) {
+ var iq = $iq({to: Moderator.getFocusComponent(), type: 'set'});
+ var sessionId = localStorage.getItem('sessionId');
+ if (!sessionId) {
+ callback();
+ return;
+ }
+ iq.c('logout', {
+ xmlns: 'http://jitsi.org/protocol/focus',
+ 'session-id': sessionId
+ });
+ connection.sendIQ(
+ iq,
+ function (result) {
+ var logoutUrl = $(result).find('logout').attr('logout-url');
+ if (logoutUrl) {
+ logoutUrl = decodeURIComponent(logoutUrl);
+ }
+ console.info("Log out OK, url: " + logoutUrl, result);
+ localStorage.removeItem('sessionId');
+ callback(logoutUrl);
+ },
+ function (error) {
+ console.error("Logout error", error);
+ }
+ );
+ }
+};
+
+module.exports = Moderator;
+
+
+
+
+},{"../../service/authentication/AuthenticationEvents":104,"../../service/xmpp/XMPPEvents":109,"../settings/Settings":47}],59:[function(require,module,exports){
+/* global $, $iq, config, connection, focusMucJid, messageHandler, Moderator,
+ Toolbar, Util */
+var Moderator = require("./moderator");
+
+
+var recordingToken = null;
+var recordingEnabled;
+
+/**
+ * Whether to use a jirecon component for recording, or use the videobridge
+ * through COLIBRI.
+ */
+var useJirecon = (typeof config.hosts.jirecon != "undefined");
+
+/**
+ * The ID of the jirecon recording session. Jirecon generates it when we
+ * initially start recording, and it needs to be used in subsequent requests
+ * to jirecon.
+ */
+var jireconRid = null;
+
+function setRecordingToken(token) {
+ recordingToken = token;
+}
+
+function setRecording(state, token, callback, connection) {
+ if (useJirecon){
+ setRecordingJirecon(state, token, callback, connection);
+ } else {
+ setRecordingColibri(state, token, callback, connection);
+ }
+}
+
+function setRecordingJirecon(state, token, callback, connection) {
+ if (state == recordingEnabled){
+ return;
+ }
+
+ var iq = $iq({to: config.hosts.jirecon, type: 'set'})
+ .c('recording', {xmlns: 'http://jitsi.org/protocol/jirecon',
+ action: state ? 'start' : 'stop',
+ mucjid: connection.emuc.roomjid});
+ if (!state){
+ iq.attrs({rid: jireconRid});
+ }
+
+ console.log('Start recording');
+
+ connection.sendIQ(
+ iq,
+ function (result) {
+ // TODO wait for an IQ with the real status, since this is
+ // provisional?
+ jireconRid = $(result).find('recording').attr('rid');
+ console.log('Recording ' + (state ? 'started' : 'stopped') +
+ '(jirecon)' + result);
+ recordingEnabled = state;
+ if (!state){
+ jireconRid = null;
+ }
+
+ callback(state);
+ },
+ function (error) {
+ console.log('Failed to start recording, error: ', error);
+ callback(recordingEnabled);
+ });
+}
+
+// Sends a COLIBRI message which enables or disables (according to 'state')
+// the recording on the bridge. Waits for the result IQ and calls 'callback'
+// with the new recording state, according to the IQ.
+function setRecordingColibri(state, token, callback, connection) {
+ var elem = $iq({to: connection.emuc.focusMucJid, type: 'set'});
+ elem.c('conference', {
+ xmlns: 'http://jitsi.org/protocol/colibri'
+ });
+ elem.c('recording', {state: state, token: token});
+
+ connection.sendIQ(elem,
+ function (result) {
+ console.log('Set recording "', state, '". Result:', result);
+ var recordingElem = $(result).find('>conference>recording');
+ var newState = ('true' === recordingElem.attr('state'));
+
+ recordingEnabled = newState;
+ callback(newState);
+ },
+ function (error) {
+ console.warn(error);
+ callback(recordingEnabled);
+ }
+ );
+}
+
+var Recording = {
+ toggleRecording: function (tokenEmptyCallback,
+ startingCallback, startedCallback, connection) {
+ if (!Moderator.isModerator()) {
+ console.log(
+ 'non-focus, or conference not yet organized:' +
+ ' not enabling recording');
+ return;
+ }
+
+ var self = this;
+ // Jirecon does not (currently) support a token.
+ if (!recordingToken && !useJirecon) {
+ tokenEmptyCallback(function (value) {
+ setRecordingToken(value);
+ self.toggleRecording(tokenEmptyCallback,
+ startingCallback, startedCallback, connection);
+ });
+
+ return;
+ }
+
+ var oldState = recordingEnabled;
+ startingCallback(!oldState);
+ setRecording(!oldState,
+ recordingToken,
+ function (state) {
+ console.log("New recording state: ", state);
+ if (state === oldState) {
+ // FIXME: new focus:
+ // this will not work when moderator changes
+ // during active session. Then it will assume that
+ // recording status has changed to true, but it might have
+ // been already true(and we only received actual status from
+ // the focus).
+ //
+ // SO we start with status null, so that it is initialized
+ // here and will fail only after second click, so if invalid
+ // token was used we have to press the button twice before
+ // current status will be fetched and token will be reset.
+ //
+ // Reliable way would be to return authentication error.
+ // Or status update when moderator connects.
+ // Or we have to stop recording session when current
+ // moderator leaves the room.
+
+ // Failed to change, reset the token because it might
+ // have been wrong
+ setRecordingToken(null);
+ }
+ startedCallback(state);
+
+ },
+ connection
+ );
+ }
+
+}
+
module.exports = Recording;
-},{"./moderator":56}],58:[function(require,module,exports){
-/* jshint -W117 */
-/* a simple MUC connection plugin
- * can only handle a single MUC room
- */
-var XMPPEvents = require("../../service/xmpp/XMPPEvents");
-var Moderator = require("./moderator");
-var JingleSession = require("./JingleSession");
-
-var bridgeIsDown = false;
-
-module.exports = function(XMPP, eventEmitter) {
- Strophe.addConnectionPlugin('emuc', {
- connection: null,
- roomjid: null,
- myroomjid: null,
- members: {},
- list_members: [], // so we can elect a new focus
- presMap: {},
- preziMap: {},
- joined: false,
- isOwner: false,
- role: null,
- focusMucJid: null,
- ssrc2jid: {},
- init: function (conn) {
- this.connection = conn;
- },
- initPresenceMap: function (myroomjid) {
- this.presMap['to'] = myroomjid;
- this.presMap['xns'] = 'http://jabber.org/protocol/muc';
- if(APP.RTC.localAudio.isMuted())
- {
- this.addAudioInfoToPresence(true);
- }
-
- if(APP.RTC.localVideo.isMuted())
- {
- this.addVideoInfoToPresence(true);
- }
- },
- doJoin: function (jid, password) {
- this.myroomjid = jid;
-
- console.info("Joined MUC as " + this.myroomjid);
-
- this.initPresenceMap(this.myroomjid);
-
- if (!this.roomjid) {
- this.roomjid = Strophe.getBareJidFromJid(jid);
- // add handlers (just once)
- this.connection.addHandler(this.onPresence.bind(this), null, 'presence', null, null, this.roomjid, {matchBare: true});
- this.connection.addHandler(this.onPresenceUnavailable.bind(this), null, 'presence', 'unavailable', null, this.roomjid, {matchBare: true});
- this.connection.addHandler(this.onPresenceError.bind(this), null, 'presence', 'error', null, this.roomjid, {matchBare: true});
- this.connection.addHandler(this.onMessage.bind(this), null, 'message', null, null, this.roomjid, {matchBare: true});
- }
- if (password !== undefined) {
- this.presMap['password'] = password;
- }
- this.sendPresence();
- },
- doLeave: function () {
- console.log("do leave", this.myroomjid);
- var pres = $pres({to: this.myroomjid, type: 'unavailable' });
- this.presMap.length = 0;
- this.connection.send(pres);
- },
- createNonAnonymousRoom: function () {
- // http://xmpp.org/extensions/xep-0045.html#createroom-reserved
-
- var getForm = $iq({type: 'get', to: this.roomjid})
- .c('query', {xmlns: 'http://jabber.org/protocol/muc#owner'})
- .c('x', {xmlns: 'jabber:x:data', type: 'submit'});
-
- var self = this;
-
- this.connection.sendIQ(getForm, function (form) {
-
- if (!$(form).find(
- '>query>x[xmlns="jabber:x:data"]' +
- '>field[var="muc#roomconfig_whois"]').length) {
-
- console.error('non-anonymous rooms not supported');
- return;
- }
-
- var formSubmit = $iq({to: this.roomjid, type: 'set'})
- .c('query', {xmlns: 'http://jabber.org/protocol/muc#owner'});
-
- formSubmit.c('x', {xmlns: 'jabber:x:data', type: 'submit'});
-
- formSubmit.c('field', {'var': 'FORM_TYPE'})
- .c('value')
- .t('http://jabber.org/protocol/muc#roomconfig').up().up();
-
- formSubmit.c('field', {'var': 'muc#roomconfig_whois'})
- .c('value').t('anyone').up().up();
-
- self.connection.sendIQ(formSubmit);
-
- }, function (error) {
- console.error("Error getting room configuration form");
- });
- },
- onPresence: function (pres) {
- var from = pres.getAttribute('from');
-
- // What is this for? A workaround for something?
- if (pres.getAttribute('type')) {
- return true;
- }
-
- // Parse etherpad tag.
- var etherpad = $(pres).find('>etherpad');
- if (etherpad.length) {
- if (config.etherpad_base) {
- eventEmitter.emit(XMPPEvents.ETHERPAD, etherpad.text());
- }
- }
-
- // Parse prezi tag.
- var presentation = $(pres).find('>prezi');
- if (presentation.length) {
- var url = presentation.attr('url');
- var current = presentation.find('>current').text();
-
- console.log('presentation info received from', from, url);
-
- if (this.preziMap[from] == null) {
- this.preziMap[from] = url;
-
- $(document).trigger('presentationadded.muc', [from, url, current]);
- }
- else {
- $(document).trigger('gotoslide.muc', [from, url, current]);
- }
- }
- else if (this.preziMap[from] != null) {
- var url = this.preziMap[from];
- delete this.preziMap[from];
- $(document).trigger('presentationremoved.muc', [from, url]);
- }
-
- // Parse audio info tag.
- var audioMuted = $(pres).find('>audiomuted');
- if (audioMuted.length) {
- eventEmitter.emit(XMPPEvents.AUDIO_MUTED,
- from, (audioMuted.text() === "true"));
- }
-
- // Parse video info tag.
- var videoMuted = $(pres).find('>videomuted');
- if (videoMuted.length) {
- eventEmitter.emit(XMPPEvents.VIDEO_MUTED,
- from, (videoMuted.text() === "true"));
- }
-
- var startMuted = $(pres).find('>startmuted');
- if (startMuted.length)
- {
- eventEmitter.emit(XMPPEvents.START_MUTED,
- startMuted.attr("audio") === "true", startMuted.attr("video") === "true");
- }
-
- var devices = $(pres).find('>devices');
- if(devices.length)
- {
- var audio = devices.find('>audio');
- var video = devices.find('>video');
- var devicesValues = {audio: false, video: false};
- if(audio.length && audio.text() === "true")
- {
- devicesValues.audio = true;
- }
-
- if(video.length && video.text() === "true")
- {
- devicesValues.video = true;
- }
- eventEmitter.emit(XMPPEvents.DEVICE_AVAILABLE,
- Strophe.getResourceFromJid(from), devicesValues);
- }
-
- var stats = $(pres).find('>stats');
- if (stats.length) {
- var statsObj = {};
- Strophe.forEachChild(stats[0], "stat", function (el) {
- statsObj[el.getAttribute("name")] = el.getAttribute("value");
- });
- eventEmitter.emit(XMPPEvents.REMOTE_STATS, from, statsObj);
- }
-
- // Parse status.
- if ($(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]>status[code="201"]').length) {
- this.isOwner = true;
- this.createNonAnonymousRoom();
- }
-
- // Parse roles.
- var member = {};
- member.show = $(pres).find('>show').text();
- member.status = $(pres).find('>status').text();
- var tmp = $(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]>item');
- member.affiliation = tmp.attr('affiliation');
- member.role = tmp.attr('role');
-
- // Focus recognition
- member.jid = tmp.attr('jid');
- member.isFocus = false;
- if (member.jid
- && member.jid.indexOf(Moderator.getFocusUserJid() + "/") == 0) {
- member.isFocus = true;
- }
-
- var nicktag = $(pres).find('>nick[xmlns="http://jabber.org/protocol/nick"]');
- member.displayName = (nicktag.length > 0 ? nicktag.html() : null);
-
- if (from == this.myroomjid) {
- if (member.affiliation == 'owner') this.isOwner = true;
- if (this.role !== member.role) {
- this.role = member.role;
-
- eventEmitter.emit(XMPPEvents.LOCAL_ROLE_CHANGED,
- from, member, pres, Moderator.isModerator());
- }
- if (!this.joined) {
- this.joined = true;
- eventEmitter.emit(XMPPEvents.MUC_JOINED, from, member);
- this.list_members.push(from);
- }
- } else if (this.members[from] === undefined) {
- // new participant
- this.members[from] = member;
- this.list_members.push(from);
- console.log('entered', from, member);
- if (member.isFocus) {
- this.focusMucJid = from;
- console.info("Ignore focus: " + from + ", real JID: " + member.jid);
- }
- else {
- var id = $(pres).find('>userId').text();
- var email = $(pres).find('>email');
- if (email.length > 0) {
- id = email.text();
- }
- eventEmitter.emit(XMPPEvents.MUC_MEMBER_JOINED, from, id, member.displayName);
- }
- } else {
- // Presence update for existing participant
- // Watch role change:
- if (this.members[from].role != member.role) {
- this.members[from].role = member.role;
- eventEmitter.emit(XMPPEvents.MUC_ROLE_CHANGED,
- member.role, member.displayName);
- }
- }
-
- // Always trigger presence to update bindings
- this.parsePresence(from, member, pres);
-
- // Trigger status message update
- if (member.status) {
- eventEmitter.emit(XMPPEvents.PRESENCE_STATUS, from, member);
- }
-
- return true;
- },
- onPresenceUnavailable: function (pres) {
- var from = pres.getAttribute('from');
- // room destroyed ?
- if ($(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]' +
- '>destroy').length) {
- var reason;
- var reasonSelect = $(pres).find(
- '>x[xmlns="http://jabber.org/protocol/muc#user"]' +
- '>destroy>reason');
- if (reasonSelect.length) {
- reason = reasonSelect.text();
- }
- XMPP.disposeConference(false);
- eventEmitter.emit(XMPPEvents.MUC_DESTROYED, reason);
- return true;
- }
-
- var self = this;
- // Remove old ssrcs coming from the jid
- Object.keys(this.ssrc2jid).forEach(function (ssrc) {
- if (self.ssrc2jid[ssrc] == from) {
- delete self.ssrc2jid[ssrc];
- }
- });
-
- // Status code 110 indicates that this notification is "self-presence".
- if (!$(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]>status[code="110"]').length) {
- delete this.members[from];
- this.list_members.splice(this.list_members.indexOf(from), 1);
- this.onParticipantLeft(from);
- }
- // If the status code is 110 this means we're leaving and we would like
- // to remove everyone else from our view, so we trigger the event.
- else if (this.list_members.length > 1) {
- for (var i = 0; i < this.list_members.length; i++) {
- var member = this.list_members[i];
- delete this.members[i];
- this.list_members.splice(i, 1);
- this.onParticipantLeft(member);
- }
- }
- if ($(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]>status[code="307"]').length) {
- $(document).trigger('kicked.muc', [from]);
- if (this.myroomjid === from) {
- XMPP.disposeConference(false);
- eventEmitter.emit(XMPPEvents.KICKED);
- }
- }
- return true;
- },
- onPresenceError: function (pres) {
- var from = pres.getAttribute('from');
- if ($(pres).find('>error[type="auth"]>not-authorized[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]').length) {
- console.log('on password required', from);
- var self = this;
- eventEmitter.emit(XMPPEvents.PASSWORD_REQUIRED, function (value) {
- self.doJoin(from, value);
- });
- } else if ($(pres).find(
- '>error[type="cancel"]>not-allowed[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]').length) {
- var toDomain = Strophe.getDomainFromJid(pres.getAttribute('to'));
- if (toDomain === config.hosts.anonymousdomain) {
- // enter the room by replying with 'not-authorized'. This would
- // result in reconnection from authorized domain.
- // We're either missing Jicofo/Prosody config for anonymous
- // domains or something is wrong.
-// XMPP.promptLogin();
- APP.UI.messageHandler.openReportDialog(null,
- "dialog.joinError", pres);
- } else {
- console.warn('onPresError ', pres);
- APP.UI.messageHandler.openReportDialog(null,
- "dialog.connectError",
- pres);
- }
- } else {
- console.warn('onPresError ', pres);
- APP.UI.messageHandler.openReportDialog(null,
- "dialog.connectError",
- pres);
- }
- return true;
- },
- sendMessage: function (body, nickname) {
- var msg = $msg({to: this.roomjid, type: 'groupchat'});
- msg.c('body', body).up();
- if (nickname) {
- msg.c('nick', {xmlns: 'http://jabber.org/protocol/nick'}).t(nickname).up().up();
- }
- this.connection.send(msg);
- eventEmitter.emit(XMPPEvents.SENDING_CHAT_MESSAGE, body);
- },
- setSubject: function (subject) {
- var msg = $msg({to: this.roomjid, type: 'groupchat'});
- msg.c('subject', subject);
- this.connection.send(msg);
- console.log("topic changed to " + subject);
- },
- onMessage: function (msg) {
- // FIXME: this is a hack. but jingle on muc makes nickchanges hard
- var from = msg.getAttribute('from');
- var nick =
- $(msg).find('>nick[xmlns="http://jabber.org/protocol/nick"]')
- .text() ||
- Strophe.getResourceFromJid(from);
-
- var txt = $(msg).find('>body').text();
- var type = msg.getAttribute("type");
- if (type == "error") {
- eventEmitter.emit(XMPPEvents.CHAT_ERROR_RECEIVED,
- $(msg).find('>text').text(), txt);
- return true;
- }
-
- var subject = $(msg).find('>subject');
- if (subject.length) {
- var subjectText = subject.text();
- if (subjectText || subjectText == "") {
- eventEmitter.emit(XMPPEvents.SUBJECT_CHANGED, subjectText);
- console.log("Subject is changed to " + subjectText);
- }
- }
-
- // xep-0203 delay
- var stamp = $(msg).find('>delay').attr('stamp');
-
- if (!stamp) {
- // or xep-0091 delay, UTC timestamp
- stamp = $(msg).find('>[xmlns="jabber:x:delay"]').attr('stamp');
-
- if (stamp) {
- // the format is CCYYMMDDThh:mm:ss
- var dateParts = stamp.match(/(\d{4})(\d{2})(\d{2}T\d{2}:\d{2}:\d{2})/);
- stamp = dateParts[1] + "-" + dateParts[2] + "-" + dateParts[3] + "Z";
- }
- }
-
- if (txt) {
- console.log('chat', nick, txt);
- eventEmitter.emit(XMPPEvents.MESSAGE_RECEIVED,
- from, nick, txt, this.myroomjid, stamp);
- }
- return true;
- },
- lockRoom: function (key, onSuccess, onError, onNotSupported) {
- //http://xmpp.org/extensions/xep-0045.html#roomconfig
- var ob = this;
- this.connection.sendIQ($iq({to: this.roomjid, type: 'get'}).c('query', {xmlns: 'http://jabber.org/protocol/muc#owner'}),
- function (res) {
- if ($(res).find('>query>x[xmlns="jabber:x:data"]>field[var="muc#roomconfig_roomsecret"]').length) {
- var formsubmit = $iq({to: ob.roomjid, type: 'set'}).c('query', {xmlns: 'http://jabber.org/protocol/muc#owner'});
- formsubmit.c('x', {xmlns: 'jabber:x:data', type: 'submit'});
- formsubmit.c('field', {'var': 'FORM_TYPE'}).c('value').t('http://jabber.org/protocol/muc#roomconfig').up().up();
- formsubmit.c('field', {'var': 'muc#roomconfig_roomsecret'}).c('value').t(key).up().up();
- // Fixes a bug in prosody 0.9.+ https://code.google.com/p/lxmppd/issues/detail?id=373
- formsubmit.c('field', {'var': 'muc#roomconfig_whois'}).c('value').t('anyone').up().up();
- // FIXME: is muc#roomconfig_passwordprotectedroom required?
- ob.connection.sendIQ(formsubmit,
- onSuccess,
- onError);
- } else {
- onNotSupported();
- }
- }, onError);
- },
- kick: function (jid) {
- var kickIQ = $iq({to: this.roomjid, type: 'set'})
- .c('query', {xmlns: 'http://jabber.org/protocol/muc#admin'})
- .c('item', {nick: Strophe.getResourceFromJid(jid), role: 'none'})
- .c('reason').t('You have been kicked.').up().up().up();
-
- this.connection.sendIQ(
- kickIQ,
- function (result) {
- console.log('Kick participant with jid: ', jid, result);
- },
- function (error) {
- console.log('Kick participant error: ', error);
- });
- },
- sendPresence: function () {
- if (!this.presMap['to']) {
- // Too early to send presence - not initialized
- return;
- }
- var pres = $pres({to: this.presMap['to'] });
- pres.c('x', {xmlns: this.presMap['xns']});
-
- if (this.presMap['password']) {
- pres.c('password').t(this.presMap['password']).up();
- }
-
- pres.up();
-
- // Send XEP-0115 'c' stanza that contains our capabilities info
- if (this.connection.caps) {
- this.connection.caps.node = config.clientNode;
- pres.c('c', this.connection.caps.generateCapsAttrs()).up();
- }
-
- pres.c('user-agent', {xmlns: 'http://jitsi.org/jitmeet/user-agent'})
- .t(navigator.userAgent).up();
-
- if (this.presMap['bridgeIsDown']) {
- pres.c('bridgeIsDown').up();
- }
-
- if (this.presMap['email']) {
- pres.c('email').t(this.presMap['email']).up();
- }
-
- if (this.presMap['userId']) {
- pres.c('userId').t(this.presMap['userId']).up();
- }
-
- if (this.presMap['displayName']) {
- // XEP-0172
- pres.c('nick', {xmlns: 'http://jabber.org/protocol/nick'})
- .t(this.presMap['displayName']).up();
- }
-
- if(this.presMap["devices"])
- {
- pres.c('devices').c('audio').t(this.presMap['devices'].audio).up()
- .c('video').t(this.presMap['devices'].video).up().up();
- }
- if (this.presMap['audions']) {
- pres.c('audiomuted', {xmlns: this.presMap['audions']})
- .t(this.presMap['audiomuted']).up();
- }
-
- if (this.presMap['videons']) {
- pres.c('videomuted', {xmlns: this.presMap['videons']})
- .t(this.presMap['videomuted']).up();
- }
-
- if (this.presMap['statsns']) {
- var stats = pres.c('stats', {xmlns: this.presMap['statsns']});
- for (var stat in this.presMap["stats"])
- if (this.presMap["stats"][stat] != null)
- stats.c("stat", {name: stat, value: this.presMap["stats"][stat]}).up();
- pres.up();
- }
-
- if (this.presMap['prezins']) {
- pres.c('prezi',
- {xmlns: this.presMap['prezins'],
- 'url': this.presMap['preziurl']})
- .c('current').t(this.presMap['prezicurrent']).up().up();
- }
-
- if (this.presMap['medians']) {
- pres.c('media', {xmlns: this.presMap['medians']});
- var sourceNumber = 0;
- Object.keys(this.presMap).forEach(function (key) {
- if (key.indexOf('source') >= 0) {
- sourceNumber++;
- }
- });
- if (sourceNumber > 0)
- for (var i = 1; i <= sourceNumber / 3; i++) {
- pres.c('source',
- {type: this.presMap['source' + i + '_type'],
- ssrc: this.presMap['source' + i + '_ssrc'],
- direction: this.presMap['source' + i + '_direction']
- || 'sendrecv' }
- ).up();
- }
- pres.up();
- }
-
- if(this.presMap["startMuted"] !== undefined)
- {
- pres.c("startmuted", {audio: this.presMap["startMuted"].audio,
- video: this.presMap["startMuted"].video,
- xmlns: "http://jitsi.org/jitmeet/start-muted"});
- delete this.presMap["startMuted"];
- }
-
- pres.up();
- this.connection.send(pres);
- },
- addDisplayNameToPresence: function (displayName) {
- this.presMap['displayName'] = displayName;
- },
- addMediaToPresence: function (sourceNumber, mtype, ssrcs, direction) {
- if (!this.presMap['medians'])
- this.presMap['medians'] = 'http://estos.de/ns/mjs';
-
- this.presMap['source' + sourceNumber + '_type'] = mtype;
- this.presMap['source' + sourceNumber + '_ssrc'] = ssrcs;
- this.presMap['source' + sourceNumber + '_direction'] = direction;
- },
- addDevicesToPresence: function (devices) {
- this.presMap['devices'] = devices;
- },
- clearPresenceMedia: function () {
- var self = this;
- Object.keys(this.presMap).forEach(function (key) {
- if (key.indexOf('source') != -1) {
- delete self.presMap[key];
- }
- });
- },
- addPreziToPresence: function (url, currentSlide) {
- this.presMap['prezins'] = 'http://jitsi.org/jitmeet/prezi';
- this.presMap['preziurl'] = url;
- this.presMap['prezicurrent'] = currentSlide;
- },
- removePreziFromPresence: function () {
- delete this.presMap['prezins'];
- delete this.presMap['preziurl'];
- delete this.presMap['prezicurrent'];
- },
- addCurrentSlideToPresence: function (currentSlide) {
- this.presMap['prezicurrent'] = currentSlide;
- },
- getPrezi: function (roomjid) {
- return this.preziMap[roomjid];
- },
- addAudioInfoToPresence: function (isMuted) {
- this.presMap['audions'] = 'http://jitsi.org/jitmeet/audio';
- this.presMap['audiomuted'] = isMuted.toString();
- },
- addVideoInfoToPresence: function (isMuted) {
- this.presMap['videons'] = 'http://jitsi.org/jitmeet/video';
- this.presMap['videomuted'] = isMuted.toString();
- },
- addConnectionInfoToPresence: function (stats) {
- this.presMap['statsns'] = 'http://jitsi.org/jitmeet/stats';
- this.presMap['stats'] = stats;
- },
- findJidFromResource: function (resourceJid) {
- if (resourceJid &&
- resourceJid === Strophe.getResourceFromJid(this.myroomjid)) {
- return this.myroomjid;
- }
- var peerJid = null;
- Object.keys(this.members).some(function (jid) {
- peerJid = jid;
- return Strophe.getResourceFromJid(jid) === resourceJid;
- });
- return peerJid;
- },
- addBridgeIsDownToPresence: function () {
- this.presMap['bridgeIsDown'] = true;
- },
- addEmailToPresence: function (email) {
- this.presMap['email'] = email;
- },
- addUserIdToPresence: function (userId) {
- this.presMap['userId'] = userId;
- },
- addStartMutedToPresence: function (audio, video) {
- this.presMap["startMuted"] = {audio: audio, video: video};
- },
- isModerator: function () {
- return this.role === 'moderator';
- },
- getMemberRole: function (peerJid) {
- if (this.members[peerJid]) {
- return this.members[peerJid].role;
- }
- return null;
- },
- onParticipantLeft: function (jid) {
-
- eventEmitter.emit(XMPPEvents.MUC_MEMBER_LEFT, jid);
-
- this.connection.jingle.terminateByJid(jid);
-
- if (this.getPrezi(jid)) {
- $(document).trigger('presentationremoved.muc',
- [jid, this.getPrezi(jid)]);
- }
-
- Moderator.onMucMemberLeft(jid);
- },
- parsePresence: function (from, memeber, pres) {
- if($(pres).find(">bridgeIsDown").length > 0 && !bridgeIsDown) {
- bridgeIsDown = true;
- eventEmitter.emit(XMPPEvents.BRIDGE_DOWN);
- }
-
- if(memeber.isFocus)
- return;
-
- var self = this;
- // Remove old ssrcs coming from the jid
- Object.keys(this.ssrc2jid).forEach(function (ssrc) {
- if (self.ssrc2jid[ssrc] == from) {
- delete self.ssrc2jid[ssrc];
- }
- });
-
- var changedStreams = [];
- $(pres).find('>media[xmlns="http://estos.de/ns/mjs"]>source').each(function (idx, ssrc) {
- //console.log(jid, 'assoc ssrc', ssrc.getAttribute('type'), ssrc.getAttribute('ssrc'));
- var ssrcV = ssrc.getAttribute('ssrc');
- self.ssrc2jid[ssrcV] = from;
-
- var type = ssrc.getAttribute('type');
-
- var direction = ssrc.getAttribute('direction');
-
- changedStreams.push({type: type, direction: direction});
-
- });
-
- eventEmitter.emit(XMPPEvents.STREAMS_CHANGED, from, changedStreams);
-
- var displayName = !config.displayJids
- ? memeber.displayName : Strophe.getResourceFromJid(from);
-
- if (displayName && displayName.length > 0)
- {
- eventEmitter.emit(XMPPEvents.DISPLAY_NAME_CHANGED, from, displayName);
- }
-
-
- var id = $(pres).find('>userID').text();
- var email = $(pres).find('>email');
- if(email.length > 0) {
- id = email.text();
- }
-
- eventEmitter.emit(XMPPEvents.USER_ID_CHANGED, from, id);
- }
- });
+},{"./moderator":58}],60:[function(require,module,exports){
+/* jshint -W117 */
+/* a simple MUC connection plugin
+ * can only handle a single MUC room
+ */
+var XMPPEvents = require("../../service/xmpp/XMPPEvents");
+var Moderator = require("./moderator");
+var JingleSession = require("./JingleSession");
+
+var bridgeIsDown = false;
+
+module.exports = function(XMPP, eventEmitter) {
+ Strophe.addConnectionPlugin('emuc', {
+ connection: null,
+ roomjid: null,
+ myroomjid: null,
+ members: {},
+ list_members: [], // so we can elect a new focus
+ presMap: {},
+ preziMap: {},
+ joined: false,
+ isOwner: false,
+ role: null,
+ focusMucJid: null,
+ ssrc2jid: {},
+ init: function (conn) {
+ this.connection = conn;
+ },
+ initPresenceMap: function (myroomjid) {
+ this.presMap['to'] = myroomjid;
+ this.presMap['xns'] = 'http://jabber.org/protocol/muc';
+ if(APP.RTC.localAudio.isMuted())
+ {
+ this.addAudioInfoToPresence(true);
+ }
+
+ if(APP.RTC.localVideo.isMuted())
+ {
+ this.addVideoInfoToPresence(true);
+ }
+ },
+ doJoin: function (jid, password) {
+ this.myroomjid = jid;
+
+ console.info("Joined MUC as " + this.myroomjid);
+
+ this.initPresenceMap(this.myroomjid);
+
+ if (!this.roomjid) {
+ this.roomjid = Strophe.getBareJidFromJid(jid);
+ // add handlers (just once)
+ this.connection.addHandler(this.onPresence.bind(this), null, 'presence', null, null, this.roomjid, {matchBare: true});
+ this.connection.addHandler(this.onPresenceUnavailable.bind(this), null, 'presence', 'unavailable', null, this.roomjid, {matchBare: true});
+ this.connection.addHandler(this.onPresenceError.bind(this), null, 'presence', 'error', null, this.roomjid, {matchBare: true});
+ this.connection.addHandler(this.onMessage.bind(this), null, 'message', null, null, this.roomjid, {matchBare: true});
+ }
+ if (password !== undefined) {
+ this.presMap['password'] = password;
+ }
+ this.sendPresence();
+ },
+ doLeave: function () {
+ console.log("do leave", this.myroomjid);
+ var pres = $pres({to: this.myroomjid, type: 'unavailable' });
+ this.presMap.length = 0;
+ this.connection.send(pres);
+ },
+ createNonAnonymousRoom: function () {
+ // http://xmpp.org/extensions/xep-0045.html#createroom-reserved
+
+ var getForm = $iq({type: 'get', to: this.roomjid})
+ .c('query', {xmlns: 'http://jabber.org/protocol/muc#owner'})
+ .c('x', {xmlns: 'jabber:x:data', type: 'submit'});
+
+ var self = this;
+
+ this.connection.sendIQ(getForm, function (form) {
+
+ if (!$(form).find(
+ '>query>x[xmlns="jabber:x:data"]' +
+ '>field[var="muc#roomconfig_whois"]').length) {
+
+ console.error('non-anonymous rooms not supported');
+ return;
+ }
+
+ var formSubmit = $iq({to: this.roomjid, type: 'set'})
+ .c('query', {xmlns: 'http://jabber.org/protocol/muc#owner'});
+
+ formSubmit.c('x', {xmlns: 'jabber:x:data', type: 'submit'});
+
+ formSubmit.c('field', {'var': 'FORM_TYPE'})
+ .c('value')
+ .t('http://jabber.org/protocol/muc#roomconfig').up().up();
+
+ formSubmit.c('field', {'var': 'muc#roomconfig_whois'})
+ .c('value').t('anyone').up().up();
+
+ self.connection.sendIQ(formSubmit);
+
+ }, function (error) {
+ console.error("Error getting room configuration form");
+ });
+ },
+ onPresence: function (pres) {
+ var from = pres.getAttribute('from');
+
+ // What is this for? A workaround for something?
+ if (pres.getAttribute('type')) {
+ return true;
+ }
+
+ // Parse etherpad tag.
+ var etherpad = $(pres).find('>etherpad');
+ if (etherpad.length) {
+ if (config.etherpad_base) {
+ eventEmitter.emit(XMPPEvents.ETHERPAD, etherpad.text());
+ }
+ }
+
+ // Parse prezi tag.
+ var presentation = $(pres).find('>prezi');
+ if (presentation.length) {
+ var url = presentation.attr('url');
+ var current = presentation.find('>current').text();
+
+ console.log('presentation info received from', from, url);
+
+ if (this.preziMap[from] == null) {
+ this.preziMap[from] = url;
+
+ $(document).trigger('presentationadded.muc', [from, url, current]);
+ }
+ else {
+ $(document).trigger('gotoslide.muc', [from, url, current]);
+ }
+ }
+ else if (this.preziMap[from] != null) {
+ var url = this.preziMap[from];
+ delete this.preziMap[from];
+ $(document).trigger('presentationremoved.muc', [from, url]);
+ }
+
+ // Parse audio info tag.
+ var audioMuted = $(pres).find('>audiomuted');
+ if (audioMuted.length) {
+ eventEmitter.emit(XMPPEvents.AUDIO_MUTED,
+ from, (audioMuted.text() === "true"));
+ }
+
+ // Parse video info tag.
+ var videoMuted = $(pres).find('>videomuted');
+ if (videoMuted.length) {
+ eventEmitter.emit(XMPPEvents.VIDEO_MUTED,
+ from, (videoMuted.text() === "true"));
+ }
+
+ var startMuted = $(pres).find('>startmuted');
+ if (startMuted.length)
+ {
+ eventEmitter.emit(XMPPEvents.START_MUTED,
+ startMuted.attr("audio") === "true", startMuted.attr("video") === "true");
+ }
+
+ var devices = $(pres).find('>devices');
+ if(devices.length)
+ {
+ var audio = devices.find('>audio');
+ var video = devices.find('>video');
+ var devicesValues = {audio: false, video: false};
+ if(audio.length && audio.text() === "true")
+ {
+ devicesValues.audio = true;
+ }
+
+ if(video.length && video.text() === "true")
+ {
+ devicesValues.video = true;
+ }
+ eventEmitter.emit(XMPPEvents.DEVICE_AVAILABLE,
+ Strophe.getResourceFromJid(from), devicesValues);
+ }
+
+ var stats = $(pres).find('>stats');
+ if (stats.length) {
+ var statsObj = {};
+ Strophe.forEachChild(stats[0], "stat", function (el) {
+ statsObj[el.getAttribute("name")] = el.getAttribute("value");
+ });
+ eventEmitter.emit(XMPPEvents.REMOTE_STATS, from, statsObj);
+ }
+
+ // Parse status.
+ if ($(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]>status[code="201"]').length) {
+ this.isOwner = true;
+ this.createNonAnonymousRoom();
+ }
+
+ // Parse roles.
+ var member = {};
+ member.show = $(pres).find('>show').text();
+ member.status = $(pres).find('>status').text();
+ var tmp = $(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]>item');
+ member.affiliation = tmp.attr('affiliation');
+ member.role = tmp.attr('role');
+
+ // Focus recognition
+ member.jid = tmp.attr('jid');
+ member.isFocus = false;
+ if (member.jid
+ && member.jid.indexOf(Moderator.getFocusUserJid() + "/") == 0) {
+ member.isFocus = true;
+ }
+
+ var nicktag = $(pres).find('>nick[xmlns="http://jabber.org/protocol/nick"]');
+ member.displayName = (nicktag.length > 0 ? nicktag.html() : null);
+
+ if (from == this.myroomjid) {
+ if (member.affiliation == 'owner') this.isOwner = true;
+ if (this.role !== member.role) {
+ this.role = member.role;
+
+ eventEmitter.emit(XMPPEvents.LOCAL_ROLE_CHANGED,
+ from, member, pres, Moderator.isModerator());
+ }
+ if (!this.joined) {
+ this.joined = true;
+ eventEmitter.emit(XMPPEvents.MUC_JOINED, from, member);
+ this.list_members.push(from);
+ }
+ } else if (this.members[from] === undefined) {
+ // new participant
+ this.members[from] = member;
+ this.list_members.push(from);
+ console.log('entered', from, member);
+ if (member.isFocus) {
+ this.focusMucJid = from;
+ console.info("Ignore focus: " + from + ", real JID: " + member.jid);
+ }
+ else {
+ var id = $(pres).find('>userId').text();
+ var email = $(pres).find('>email');
+ if (email.length > 0) {
+ id = email.text();
+ }
+ eventEmitter.emit(XMPPEvents.MUC_MEMBER_JOINED, from, id, member.displayName);
+ }
+ } else {
+ // Presence update for existing participant
+ // Watch role change:
+ if (this.members[from].role != member.role) {
+ this.members[from].role = member.role;
+ eventEmitter.emit(XMPPEvents.MUC_ROLE_CHANGED,
+ member.role, member.displayName);
+ }
+ }
+
+ // Always trigger presence to update bindings
+ this.parsePresence(from, member, pres);
+
+ // Trigger status message update
+ if (member.status) {
+ eventEmitter.emit(XMPPEvents.PRESENCE_STATUS, from, member);
+ }
+
+ return true;
+ },
+ onPresenceUnavailable: function (pres) {
+ var from = pres.getAttribute('from');
+ // room destroyed ?
+ if ($(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]' +
+ '>destroy').length) {
+ var reason;
+ var reasonSelect = $(pres).find(
+ '>x[xmlns="http://jabber.org/protocol/muc#user"]' +
+ '>destroy>reason');
+ if (reasonSelect.length) {
+ reason = reasonSelect.text();
+ }
+ XMPP.disposeConference(false);
+ eventEmitter.emit(XMPPEvents.MUC_DESTROYED, reason);
+ return true;
+ }
+
+ var self = this;
+ // Remove old ssrcs coming from the jid
+ Object.keys(this.ssrc2jid).forEach(function (ssrc) {
+ if (self.ssrc2jid[ssrc] == from) {
+ delete self.ssrc2jid[ssrc];
+ }
+ });
+
+ // Status code 110 indicates that this notification is "self-presence".
+ if (!$(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]>status[code="110"]').length) {
+ delete this.members[from];
+ this.list_members.splice(this.list_members.indexOf(from), 1);
+ this.onParticipantLeft(from);
+ }
+ // If the status code is 110 this means we're leaving and we would like
+ // to remove everyone else from our view, so we trigger the event.
+ else if (this.list_members.length > 1) {
+ for (var i = 0; i < this.list_members.length; i++) {
+ var member = this.list_members[i];
+ delete this.members[i];
+ this.list_members.splice(i, 1);
+ this.onParticipantLeft(member);
+ }
+ }
+ if ($(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]>status[code="307"]').length) {
+ $(document).trigger('kicked.muc', [from]);
+ if (this.myroomjid === from) {
+ XMPP.disposeConference(false);
+ eventEmitter.emit(XMPPEvents.KICKED);
+ }
+ }
+ return true;
+ },
+ onPresenceError: function (pres) {
+ var from = pres.getAttribute('from');
+ if ($(pres).find('>error[type="auth"]>not-authorized[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]').length) {
+ console.log('on password required', from);
+ var self = this;
+ eventEmitter.emit(XMPPEvents.PASSWORD_REQUIRED, function (value) {
+ self.doJoin(from, value);
+ });
+ } else if ($(pres).find(
+ '>error[type="cancel"]>not-allowed[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]').length) {
+ var toDomain = Strophe.getDomainFromJid(pres.getAttribute('to'));
+ if (toDomain === config.hosts.anonymousdomain) {
+ // enter the room by replying with 'not-authorized'. This would
+ // result in reconnection from authorized domain.
+ // We're either missing Jicofo/Prosody config for anonymous
+ // domains or something is wrong.
+// XMPP.promptLogin();
+ APP.UI.messageHandler.openReportDialog(null,
+ "dialog.joinError", pres);
+ } else {
+ console.warn('onPresError ', pres);
+ APP.UI.messageHandler.openReportDialog(null,
+ "dialog.connectError",
+ pres);
+ }
+ } else {
+ console.warn('onPresError ', pres);
+ APP.UI.messageHandler.openReportDialog(null,
+ "dialog.connectError",
+ pres);
+ }
+ return true;
+ },
+ sendMessage: function (body, nickname) {
+ var msg = $msg({to: this.roomjid, type: 'groupchat'});
+ msg.c('body', body).up();
+ if (nickname) {
+ msg.c('nick', {xmlns: 'http://jabber.org/protocol/nick'}).t(nickname).up().up();
+ }
+ this.connection.send(msg);
+ eventEmitter.emit(XMPPEvents.SENDING_CHAT_MESSAGE, body);
+ },
+ setSubject: function (subject) {
+ var msg = $msg({to: this.roomjid, type: 'groupchat'});
+ msg.c('subject', subject);
+ this.connection.send(msg);
+ console.log("topic changed to " + subject);
+ },
+ onMessage: function (msg) {
+ // FIXME: this is a hack. but jingle on muc makes nickchanges hard
+ var from = msg.getAttribute('from');
+ var nick =
+ $(msg).find('>nick[xmlns="http://jabber.org/protocol/nick"]')
+ .text() ||
+ Strophe.getResourceFromJid(from);
+
+ var txt = $(msg).find('>body').text();
+ var type = msg.getAttribute("type");
+ if (type == "error") {
+ eventEmitter.emit(XMPPEvents.CHAT_ERROR_RECEIVED,
+ $(msg).find('>text').text(), txt);
+ return true;
+ }
+
+ var subject = $(msg).find('>subject');
+ if (subject.length) {
+ var subjectText = subject.text();
+ if (subjectText || subjectText == "") {
+ eventEmitter.emit(XMPPEvents.SUBJECT_CHANGED, subjectText);
+ console.log("Subject is changed to " + subjectText);
+ }
+ }
+
+ // xep-0203 delay
+ var stamp = $(msg).find('>delay').attr('stamp');
+
+ if (!stamp) {
+ // or xep-0091 delay, UTC timestamp
+ stamp = $(msg).find('>[xmlns="jabber:x:delay"]').attr('stamp');
+
+ if (stamp) {
+ // the format is CCYYMMDDThh:mm:ss
+ var dateParts = stamp.match(/(\d{4})(\d{2})(\d{2}T\d{2}:\d{2}:\d{2})/);
+ stamp = dateParts[1] + "-" + dateParts[2] + "-" + dateParts[3] + "Z";
+ }
+ }
+
+ if (txt) {
+ console.log('chat', nick, txt);
+ eventEmitter.emit(XMPPEvents.MESSAGE_RECEIVED,
+ from, nick, txt, this.myroomjid, stamp);
+ }
+ return true;
+ },
+ lockRoom: function (key, onSuccess, onError, onNotSupported) {
+ //http://xmpp.org/extensions/xep-0045.html#roomconfig
+ var ob = this;
+ this.connection.sendIQ($iq({to: this.roomjid, type: 'get'}).c('query', {xmlns: 'http://jabber.org/protocol/muc#owner'}),
+ function (res) {
+ if ($(res).find('>query>x[xmlns="jabber:x:data"]>field[var="muc#roomconfig_roomsecret"]').length) {
+ var formsubmit = $iq({to: ob.roomjid, type: 'set'}).c('query', {xmlns: 'http://jabber.org/protocol/muc#owner'});
+ formsubmit.c('x', {xmlns: 'jabber:x:data', type: 'submit'});
+ formsubmit.c('field', {'var': 'FORM_TYPE'}).c('value').t('http://jabber.org/protocol/muc#roomconfig').up().up();
+ formsubmit.c('field', {'var': 'muc#roomconfig_roomsecret'}).c('value').t(key).up().up();
+ // Fixes a bug in prosody 0.9.+ https://code.google.com/p/lxmppd/issues/detail?id=373
+ formsubmit.c('field', {'var': 'muc#roomconfig_whois'}).c('value').t('anyone').up().up();
+ // FIXME: is muc#roomconfig_passwordprotectedroom required?
+ ob.connection.sendIQ(formsubmit,
+ onSuccess,
+ onError);
+ } else {
+ onNotSupported();
+ }
+ }, onError);
+ },
+ kick: function (jid) {
+ var kickIQ = $iq({to: this.roomjid, type: 'set'})
+ .c('query', {xmlns: 'http://jabber.org/protocol/muc#admin'})
+ .c('item', {nick: Strophe.getResourceFromJid(jid), role: 'none'})
+ .c('reason').t('You have been kicked.').up().up().up();
+
+ this.connection.sendIQ(
+ kickIQ,
+ function (result) {
+ console.log('Kick participant with jid: ', jid, result);
+ },
+ function (error) {
+ console.log('Kick participant error: ', error);
+ });
+ },
+ sendPresence: function () {
+ if (!this.presMap['to']) {
+ // Too early to send presence - not initialized
+ return;
+ }
+ var pres = $pres({to: this.presMap['to'] });
+ pres.c('x', {xmlns: this.presMap['xns']});
+
+ if (this.presMap['password']) {
+ pres.c('password').t(this.presMap['password']).up();
+ }
+
+ pres.up();
+
+ // Send XEP-0115 'c' stanza that contains our capabilities info
+ if (this.connection.caps) {
+ this.connection.caps.node = config.clientNode;
+ pres.c('c', this.connection.caps.generateCapsAttrs()).up();
+ }
+
+ pres.c('user-agent', {xmlns: 'http://jitsi.org/jitmeet/user-agent'})
+ .t(navigator.userAgent).up();
+
+ if (this.presMap['bridgeIsDown']) {
+ pres.c('bridgeIsDown').up();
+ }
+
+ if (this.presMap['email']) {
+ pres.c('email').t(this.presMap['email']).up();
+ }
+
+ if (this.presMap['userId']) {
+ pres.c('userId').t(this.presMap['userId']).up();
+ }
+
+ if (this.presMap['displayName']) {
+ // XEP-0172
+ pres.c('nick', {xmlns: 'http://jabber.org/protocol/nick'})
+ .t(this.presMap['displayName']).up();
+ }
+
+ if(this.presMap["devices"])
+ {
+ pres.c('devices').c('audio').t(this.presMap['devices'].audio).up()
+ .c('video').t(this.presMap['devices'].video).up().up();
+ }
+ if (this.presMap['audions']) {
+ pres.c('audiomuted', {xmlns: this.presMap['audions']})
+ .t(this.presMap['audiomuted']).up();
+ }
+
+ if (this.presMap['videons']) {
+ pres.c('videomuted', {xmlns: this.presMap['videons']})
+ .t(this.presMap['videomuted']).up();
+ }
+
+ if (this.presMap['statsns']) {
+ var stats = pres.c('stats', {xmlns: this.presMap['statsns']});
+ for (var stat in this.presMap["stats"])
+ if (this.presMap["stats"][stat] != null)
+ stats.c("stat", {name: stat, value: this.presMap["stats"][stat]}).up();
+ pres.up();
+ }
+
+ if (this.presMap['prezins']) {
+ pres.c('prezi',
+ {xmlns: this.presMap['prezins'],
+ 'url': this.presMap['preziurl']})
+ .c('current').t(this.presMap['prezicurrent']).up().up();
+ }
+
+ if (this.presMap['medians']) {
+ pres.c('media', {xmlns: this.presMap['medians']});
+ var sourceNumber = 0;
+ Object.keys(this.presMap).forEach(function (key) {
+ if (key.indexOf('source') >= 0) {
+ sourceNumber++;
+ }
+ });
+ if (sourceNumber > 0)
+ for (var i = 1; i <= sourceNumber / 3; i++) {
+ pres.c('source',
+ {type: this.presMap['source' + i + '_type'],
+ ssrc: this.presMap['source' + i + '_ssrc'],
+ direction: this.presMap['source' + i + '_direction']
+ || 'sendrecv' }
+ ).up();
+ }
+ pres.up();
+ }
+
+ if(this.presMap["startMuted"] !== undefined)
+ {
+ pres.c("startmuted", {audio: this.presMap["startMuted"].audio,
+ video: this.presMap["startMuted"].video,
+ xmlns: "http://jitsi.org/jitmeet/start-muted"});
+ delete this.presMap["startMuted"];
+ }
+
+ pres.up();
+ this.connection.send(pres);
+ },
+ addDisplayNameToPresence: function (displayName) {
+ this.presMap['displayName'] = displayName;
+ },
+ addMediaToPresence: function (sourceNumber, mtype, ssrcs, direction) {
+ if (!this.presMap['medians'])
+ this.presMap['medians'] = 'http://estos.de/ns/mjs';
+
+ this.presMap['source' + sourceNumber + '_type'] = mtype;
+ this.presMap['source' + sourceNumber + '_ssrc'] = ssrcs;
+ this.presMap['source' + sourceNumber + '_direction'] = direction;
+ },
+ addDevicesToPresence: function (devices) {
+ this.presMap['devices'] = devices;
+ },
+ clearPresenceMedia: function () {
+ var self = this;
+ Object.keys(this.presMap).forEach(function (key) {
+ if (key.indexOf('source') != -1) {
+ delete self.presMap[key];
+ }
+ });
+ },
+ addPreziToPresence: function (url, currentSlide) {
+ this.presMap['prezins'] = 'http://jitsi.org/jitmeet/prezi';
+ this.presMap['preziurl'] = url;
+ this.presMap['prezicurrent'] = currentSlide;
+ },
+ removePreziFromPresence: function () {
+ delete this.presMap['prezins'];
+ delete this.presMap['preziurl'];
+ delete this.presMap['prezicurrent'];
+ },
+ addCurrentSlideToPresence: function (currentSlide) {
+ this.presMap['prezicurrent'] = currentSlide;
+ },
+ getPrezi: function (roomjid) {
+ return this.preziMap[roomjid];
+ },
+ addAudioInfoToPresence: function (isMuted) {
+ this.presMap['audions'] = 'http://jitsi.org/jitmeet/audio';
+ this.presMap['audiomuted'] = isMuted.toString();
+ },
+ addVideoInfoToPresence: function (isMuted) {
+ this.presMap['videons'] = 'http://jitsi.org/jitmeet/video';
+ this.presMap['videomuted'] = isMuted.toString();
+ },
+ addConnectionInfoToPresence: function (stats) {
+ this.presMap['statsns'] = 'http://jitsi.org/jitmeet/stats';
+ this.presMap['stats'] = stats;
+ },
+ findJidFromResource: function (resourceJid) {
+ if (resourceJid &&
+ resourceJid === Strophe.getResourceFromJid(this.myroomjid)) {
+ return this.myroomjid;
+ }
+ var peerJid = null;
+ Object.keys(this.members).some(function (jid) {
+ peerJid = jid;
+ return Strophe.getResourceFromJid(jid) === resourceJid;
+ });
+ return peerJid;
+ },
+ addBridgeIsDownToPresence: function () {
+ this.presMap['bridgeIsDown'] = true;
+ },
+ addEmailToPresence: function (email) {
+ this.presMap['email'] = email;
+ },
+ addUserIdToPresence: function (userId) {
+ this.presMap['userId'] = userId;
+ },
+ addStartMutedToPresence: function (audio, video) {
+ this.presMap["startMuted"] = {audio: audio, video: video};
+ },
+ isModerator: function () {
+ return this.role === 'moderator';
+ },
+ getMemberRole: function (peerJid) {
+ if (this.members[peerJid]) {
+ return this.members[peerJid].role;
+ }
+ return null;
+ },
+ onParticipantLeft: function (jid) {
+
+ eventEmitter.emit(XMPPEvents.MUC_MEMBER_LEFT, jid);
+
+ this.connection.jingle.terminateByJid(jid);
+
+ if (this.getPrezi(jid)) {
+ $(document).trigger('presentationremoved.muc',
+ [jid, this.getPrezi(jid)]);
+ }
+
+ Moderator.onMucMemberLeft(jid);
+ },
+ parsePresence: function (from, memeber, pres) {
+ if($(pres).find(">bridgeIsDown").length > 0 && !bridgeIsDown) {
+ bridgeIsDown = true;
+ eventEmitter.emit(XMPPEvents.BRIDGE_DOWN);
+ }
+
+ if(memeber.isFocus)
+ return;
+
+ var self = this;
+ // Remove old ssrcs coming from the jid
+ Object.keys(this.ssrc2jid).forEach(function (ssrc) {
+ if (self.ssrc2jid[ssrc] == from) {
+ delete self.ssrc2jid[ssrc];
+ }
+ });
+
+ var changedStreams = [];
+ $(pres).find('>media[xmlns="http://estos.de/ns/mjs"]>source').each(function (idx, ssrc) {
+ //console.log(jid, 'assoc ssrc', ssrc.getAttribute('type'), ssrc.getAttribute('ssrc'));
+ var ssrcV = ssrc.getAttribute('ssrc');
+ self.ssrc2jid[ssrcV] = from;
+
+ var type = ssrc.getAttribute('type');
+
+ var direction = ssrc.getAttribute('direction');
+
+ changedStreams.push({type: type, direction: direction});
+
+ });
+
+ eventEmitter.emit(XMPPEvents.STREAMS_CHANGED, from, changedStreams);
+
+ var displayName = !config.displayJids
+ ? memeber.displayName : Strophe.getResourceFromJid(from);
+
+ if (displayName && displayName.length > 0)
+ {
+ eventEmitter.emit(XMPPEvents.DISPLAY_NAME_CHANGED, from, displayName);
+ }
+
+
+ var id = $(pres).find('>userID').text();
+ var email = $(pres).find('>email');
+ if(email.length > 0) {
+ id = email.text();
+ }
+
+ eventEmitter.emit(XMPPEvents.USER_ID_CHANGED, from, id);
+ }
+ });
+};
+
+
+},{"../../service/xmpp/XMPPEvents":109,"./JingleSession":53,"./moderator":58}],61:[function(require,module,exports){
+/* jshint -W117 */
+
+var JingleSession = require("./JingleSession");
+var XMPPEvents = require("../../service/xmpp/XMPPEvents");
+
+
+module.exports = function(XMPP, eventEmitter)
+{
+ function CallIncomingJingle(sid, connection) {
+ var sess = connection.jingle.sessions[sid];
+
+ // TODO: do we check activecall == null?
+ connection.jingle.activecall = sess;
+
+ eventEmitter.emit(XMPPEvents.CALL_INCOMING, sess);
+
+ // TODO: check affiliation and/or role
+ console.log('emuc data for', sess.peerjid, connection.emuc.members[sess.peerjid]);
+ sess.usedrip = true; // not-so-naive trickle ice
+ sess.sendAnswer();
+ sess.accept();
+
+ };
+
+ Strophe.addConnectionPlugin('jingle', {
+ connection: null,
+ sessions: {},
+ jid2session: {},
+ ice_config: {iceServers: []},
+ pc_constraints: {},
+ activecall: null,
+ media_constraints: {
+ mandatory: {
+ 'OfferToReceiveAudio': true,
+ 'OfferToReceiveVideo': true
+ }
+ // MozDontOfferDataChannel: true when this is firefox
+ },
+ init: function (conn) {
+ this.connection = conn;
+ if (this.connection.disco) {
+ // http://xmpp.org/extensions/xep-0167.html#support
+ // http://xmpp.org/extensions/xep-0176.html#support
+ this.connection.disco.addFeature('urn:xmpp:jingle:1');
+ this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:1');
+ this.connection.disco.addFeature('urn:xmpp:jingle:transports:ice-udp:1');
+ this.connection.disco.addFeature('urn:xmpp:jingle:apps:dtls:0');
+ this.connection.disco.addFeature('urn:xmpp:jingle:transports:dtls-sctp:1');
+ this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:audio');
+ this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:video');
+
+
+ // this is dealt with by SDP O/A so we don't need to annouce this
+ //this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:rtcp-fb:0'); // XEP-0293
+ //this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:rtp-hdrext:0'); // XEP-0294
+ if (config.useRtcpMux) {
+ this.connection.disco.addFeature('urn:ietf:rfc:5761'); // rtcp-mux
+ }
+ if (config.useBundle) {
+ this.connection.disco.addFeature('urn:ietf:rfc:5888'); // a=group, e.g. bundle
+ }
+ //this.connection.disco.addFeature('urn:ietf:rfc:5576'); // a=ssrc
+ }
+ this.connection.addHandler(this.onJingle.bind(this), 'urn:xmpp:jingle:1', 'iq', 'set', null, null);
+ },
+ onJingle: function (iq) {
+ var sid = $(iq).find('jingle').attr('sid');
+ var action = $(iq).find('jingle').attr('action');
+ var fromJid = iq.getAttribute('from');
+ // send ack first
+ var ack = $iq({type: 'result',
+ to: fromJid,
+ id: iq.getAttribute('id')
+ });
+ console.log('on jingle ' + action + ' from ' + fromJid, iq);
+ var sess = this.sessions[sid];
+ if ('session-initiate' != action) {
+ if (sess === null) {
+ ack.type = 'error';
+ ack.c('error', {type: 'cancel'})
+ .c('item-not-found', {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'}).up()
+ .c('unknown-session', {xmlns: 'urn:xmpp:jingle:errors:1'});
+ this.connection.send(ack);
+ return true;
+ }
+ // compare from to sess.peerjid (bare jid comparison for later compat with message-mode)
+ // local jid is not checked
+ if (Strophe.getBareJidFromJid(fromJid) != Strophe.getBareJidFromJid(sess.peerjid)) {
+ console.warn('jid mismatch for session id', sid, fromJid, sess.peerjid);
+ ack.type = 'error';
+ ack.c('error', {type: 'cancel'})
+ .c('item-not-found', {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'}).up()
+ .c('unknown-session', {xmlns: 'urn:xmpp:jingle:errors:1'});
+ this.connection.send(ack);
+ return true;
+ }
+ } else if (sess !== undefined) {
+ // existing session with same session id
+ // this might be out-of-order if the sess.peerjid is the same as from
+ ack.type = 'error';
+ ack.c('error', {type: 'cancel'})
+ .c('service-unavailable', {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'}).up();
+ console.warn('duplicate session id', sid);
+ this.connection.send(ack);
+ return true;
+ }
+ // FIXME: check for a defined action
+ this.connection.send(ack);
+ // see http://xmpp.org/extensions/xep-0166.html#concepts-session
+ switch (action) {
+ case 'session-initiate':
+ var startMuted = $(iq).find('jingle>startmuted');
+ if(startMuted && startMuted.length > 0)
+ {
+ var audioMuted = startMuted.attr("audio");
+ var videoMuted = startMuted.attr("video");
+ APP.UI.setInitialMuteFromFocus((audioMuted === "true"),
+ (videoMuted === "true"));
+ }
+ sess = new JingleSession(
+ $(iq).attr('to'), $(iq).find('jingle').attr('sid'),
+ this.connection, XMPP, eventEmitter);
+ // configure session
+
+ sess.media_constraints = this.media_constraints;
+ sess.pc_constraints = this.pc_constraints;
+ sess.ice_config = this.ice_config;
+
+ sess.initiate(fromJid, false);
+ // FIXME: setRemoteDescription should only be done when this call is to be accepted
+ sess.setRemoteDescription($(iq).find('>jingle'), 'offer');
+
+ this.sessions[sess.sid] = sess;
+ this.jid2session[sess.peerjid] = sess;
+
+ // the callback should either
+ // .sendAnswer and .accept
+ // or .sendTerminate -- not necessarily synchronus
+ CallIncomingJingle(sess.sid, this.connection);
+ break;
+ case 'session-accept':
+ sess.setRemoteDescription($(iq).find('>jingle'), 'answer');
+ sess.accept();
+ $(document).trigger('callaccepted.jingle', [sess.sid]);
+ break;
+ case 'session-terminate':
+ // If this is not the focus sending the terminate, we have
+ // nothing more to do here.
+ if (Object.keys(this.sessions).length < 1
+ || !(this.sessions[Object.keys(this.sessions)[0]]
+ instanceof JingleSession))
+ {
+ break;
+ }
+ console.log('terminating...', sess.sid);
+ sess.terminate();
+ this.terminate(sess.sid);
+ if ($(iq).find('>jingle>reason').length) {
+ $(document).trigger('callterminated.jingle', [
+ sess.sid,
+ sess.peerjid,
+ $(iq).find('>jingle>reason>:first')[0].tagName,
+ $(iq).find('>jingle>reason>text').text()
+ ]);
+ } else {
+ $(document).trigger('callterminated.jingle',
+ [sess.sid, sess.peerjid]);
+ }
+ break;
+ case 'transport-info':
+ sess.addIceCandidate($(iq).find('>jingle>content'));
+ break;
+ case 'session-info':
+ var affected;
+ if ($(iq).find('>jingle>ringing[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').length) {
+ $(document).trigger('ringing.jingle', [sess.sid]);
+ } else if ($(iq).find('>jingle>mute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').length) {
+ affected = $(iq).find('>jingle>mute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').attr('name');
+ $(document).trigger('mute.jingle', [sess.sid, affected]);
+ } else if ($(iq).find('>jingle>unmute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').length) {
+ affected = $(iq).find('>jingle>unmute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').attr('name');
+ $(document).trigger('unmute.jingle', [sess.sid, affected]);
+ }
+ break;
+ case 'addsource': // FIXME: proprietary, un-jingleish
+ case 'source-add': // FIXME: proprietary
+ sess.addSource($(iq).find('>jingle>content'), fromJid);
+ break;
+ case 'removesource': // FIXME: proprietary, un-jingleish
+ case 'source-remove': // FIXME: proprietary
+ sess.removeSource($(iq).find('>jingle>content'), fromJid);
+ break;
+ default:
+ console.warn('jingle action not implemented', action);
+ break;
+ }
+ return true;
+ },
+ initiate: function (peerjid, myjid) { // initiate a new jinglesession to peerjid
+ var sess = new JingleSession(myjid || this.connection.jid,
+ Math.random().toString(36).substr(2, 12), // random string
+ this.connection, XMPP, eventEmitter);
+ // configure session
+
+ sess.media_constraints = this.media_constraints;
+ sess.pc_constraints = this.pc_constraints;
+ sess.ice_config = this.ice_config;
+
+ sess.initiate(peerjid, true);
+ this.sessions[sess.sid] = sess;
+ this.jid2session[sess.peerjid] = sess;
+ sess.sendOffer();
+ return sess;
+ },
+ terminate: function (sid, reason, text) { // terminate by sessionid (or all sessions)
+ if (sid === null || sid === undefined) {
+ for (sid in this.sessions) {
+ if (this.sessions[sid].state != 'ended') {
+ this.sessions[sid].sendTerminate(reason || (!this.sessions[sid].active()) ? 'cancel' : null, text);
+ this.sessions[sid].terminate();
+ }
+ delete this.jid2session[this.sessions[sid].peerjid];
+ delete this.sessions[sid];
+ }
+ } else if (this.sessions.hasOwnProperty(sid)) {
+ if (this.sessions[sid].state != 'ended') {
+ this.sessions[sid].sendTerminate(reason || (!this.sessions[sid].active()) ? 'cancel' : null, text);
+ this.sessions[sid].terminate();
+ }
+ delete this.jid2session[this.sessions[sid].peerjid];
+ delete this.sessions[sid];
+ }
+ },
+ // Used to terminate a session when an unavailable presence is received.
+ terminateByJid: function (jid) {
+ if (this.jid2session.hasOwnProperty(jid)) {
+ var sess = this.jid2session[jid];
+ if (sess) {
+ sess.terminate();
+ console.log('peer went away silently', jid);
+ delete this.sessions[sess.sid];
+ delete this.jid2session[jid];
+ $(document).trigger('callterminated.jingle',
+ [sess.sid, jid], 'gone');
+ }
+ }
+ },
+ terminateRemoteByJid: function (jid, reason) {
+ if (this.jid2session.hasOwnProperty(jid)) {
+ var sess = this.jid2session[jid];
+ if (sess) {
+ sess.sendTerminate(reason || (!sess.active()) ? 'kick' : null);
+ sess.terminate();
+ console.log('terminate peer with jid', sess.sid, jid);
+ delete this.sessions[sess.sid];
+ delete this.jid2session[jid];
+ $(document).trigger('callterminated.jingle',
+ [sess.sid, jid, 'kicked']);
+ }
+ }
+ },
+ getStunAndTurnCredentials: function () {
+ // get stun and turn configuration from server via xep-0215
+ // uses time-limited credentials as described in
+ // http://tools.ietf.org/html/draft-uberti-behave-turn-rest-00
+ //
+ // see https://code.google.com/p/prosody-modules/source/browse/mod_turncredentials/mod_turncredentials.lua
+ // for a prosody module which implements this
+ //
+ // currently, this doesn't work with updateIce and therefore credentials with a long
+ // validity have to be fetched before creating the peerconnection
+ // TODO: implement refresh via updateIce as described in
+ // https://code.google.com/p/webrtc/issues/detail?id=1650
+ var self = this;
+ this.connection.sendIQ(
+ $iq({type: 'get', to: this.connection.domain})
+ .c('services', {xmlns: 'urn:xmpp:extdisco:1'}).c('service', {host: 'turn.' + this.connection.domain}),
+ function (res) {
+ var iceservers = [];
+ $(res).find('>services>service').each(function (idx, el) {
+ el = $(el);
+ var dict = {};
+ var type = el.attr('type');
+ switch (type) {
+ case 'stun':
+ dict.url = 'stun:' + el.attr('host');
+ if (el.attr('port')) {
+ dict.url += ':' + el.attr('port');
+ }
+ iceservers.push(dict);
+ break;
+ case 'turn':
+ case 'turns':
+ dict.url = type + ':';
+ if (el.attr('username')) { // https://code.google.com/p/webrtc/issues/detail?id=1508
+ if (navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./) && parseInt(navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./)[2], 10) < 28) {
+ dict.url += el.attr('username') + '@';
+ } else {
+ dict.username = el.attr('username'); // only works in M28
+ }
+ }
+ dict.url += el.attr('host');
+ if (el.attr('port') && el.attr('port') != '3478') {
+ dict.url += ':' + el.attr('port');
+ }
+ if (el.attr('transport') && el.attr('transport') != 'udp') {
+ dict.url += '?transport=' + el.attr('transport');
+ }
+ if (el.attr('password')) {
+ dict.credential = el.attr('password');
+ }
+ iceservers.push(dict);
+ break;
+ }
+ });
+ self.ice_config.iceServers = iceservers;
+ },
+ function (err) {
+ console.warn('getting turn credentials failed', err);
+ console.warn('is mod_turncredentials or similar installed?');
+ }
+ );
+ // implement push?
+ },
+
+ /**
+ * Populates the log data
+ */
+ populateData: function () {
+ var data = {};
+ var self = this;
+ Object.keys(this.sessions).forEach(function (sid) {
+ var session = self.sessions[sid];
+ if (session.peerconnection && session.peerconnection.updateLog) {
+ // FIXME: should probably be a .dump call
+ data["jingle_" + session.sid] = {
+ updateLog: session.peerconnection.updateLog,
+ stats: session.peerconnection.stats,
+ url: window.location.href
+ };
+ }
+ });
+ return data;
+ }
+ });
+};
+
+
+},{"../../service/xmpp/XMPPEvents":109,"./JingleSession":53}],62:[function(require,module,exports){
+/* global Strophe */
+module.exports = function () {
+
+ Strophe.addConnectionPlugin('logger', {
+ // logs raw stanzas and makes them available for download as JSON
+ connection: null,
+ log: [],
+ init: function (conn) {
+ this.connection = conn;
+ this.connection.rawInput = this.log_incoming.bind(this);
+ this.connection.rawOutput = this.log_outgoing.bind(this);
+ },
+ log_incoming: function (stanza) {
+ this.log.push([new Date().getTime(), 'incoming', stanza]);
+ },
+ log_outgoing: function (stanza) {
+ this.log.push([new Date().getTime(), 'outgoing', stanza]);
+ }
+ });
};
-
-
-},{"../../service/xmpp/XMPPEvents":111,"./JingleSession":51,"./moderator":56}],59:[function(require,module,exports){
-/* jshint -W117 */
-
-var JingleSession = require("./JingleSession");
-var XMPPEvents = require("../../service/xmpp/XMPPEvents");
-
-
-module.exports = function(XMPP, eventEmitter)
-{
- function CallIncomingJingle(sid, connection) {
- var sess = connection.jingle.sessions[sid];
-
- // TODO: do we check activecall == null?
- connection.jingle.activecall = sess;
-
- eventEmitter.emit(XMPPEvents.CALL_INCOMING, sess);
-
- // TODO: check affiliation and/or role
- console.log('emuc data for', sess.peerjid, connection.emuc.members[sess.peerjid]);
- sess.usedrip = true; // not-so-naive trickle ice
- sess.sendAnswer();
- sess.accept();
-
- };
-
- Strophe.addConnectionPlugin('jingle', {
- connection: null,
- sessions: {},
- jid2session: {},
- ice_config: {iceServers: []},
- pc_constraints: {},
- activecall: null,
- media_constraints: {
- mandatory: {
- 'OfferToReceiveAudio': true,
- 'OfferToReceiveVideo': true
- }
- // MozDontOfferDataChannel: true when this is firefox
- },
- init: function (conn) {
- this.connection = conn;
- if (this.connection.disco) {
- // http://xmpp.org/extensions/xep-0167.html#support
- // http://xmpp.org/extensions/xep-0176.html#support
- this.connection.disco.addFeature('urn:xmpp:jingle:1');
- this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:1');
- this.connection.disco.addFeature('urn:xmpp:jingle:transports:ice-udp:1');
- this.connection.disco.addFeature('urn:xmpp:jingle:apps:dtls:0');
- this.connection.disco.addFeature('urn:xmpp:jingle:transports:dtls-sctp:1');
- this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:audio');
- this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:video');
-
-
- // this is dealt with by SDP O/A so we don't need to annouce this
- //this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:rtcp-fb:0'); // XEP-0293
- //this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:rtp-hdrext:0'); // XEP-0294
- if (config.useRtcpMux) {
- this.connection.disco.addFeature('urn:ietf:rfc:5761'); // rtcp-mux
- }
- if (config.useBundle) {
- this.connection.disco.addFeature('urn:ietf:rfc:5888'); // a=group, e.g. bundle
- }
- //this.connection.disco.addFeature('urn:ietf:rfc:5576'); // a=ssrc
- }
- this.connection.addHandler(this.onJingle.bind(this), 'urn:xmpp:jingle:1', 'iq', 'set', null, null);
- },
- onJingle: function (iq) {
- var sid = $(iq).find('jingle').attr('sid');
- var action = $(iq).find('jingle').attr('action');
- var fromJid = iq.getAttribute('from');
- // send ack first
- var ack = $iq({type: 'result',
- to: fromJid,
- id: iq.getAttribute('id')
- });
- console.log('on jingle ' + action + ' from ' + fromJid, iq);
- var sess = this.sessions[sid];
- if ('session-initiate' != action) {
- if (sess === null) {
- ack.type = 'error';
- ack.c('error', {type: 'cancel'})
- .c('item-not-found', {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'}).up()
- .c('unknown-session', {xmlns: 'urn:xmpp:jingle:errors:1'});
- this.connection.send(ack);
- return true;
- }
- // compare from to sess.peerjid (bare jid comparison for later compat with message-mode)
- // local jid is not checked
- if (Strophe.getBareJidFromJid(fromJid) != Strophe.getBareJidFromJid(sess.peerjid)) {
- console.warn('jid mismatch for session id', sid, fromJid, sess.peerjid);
- ack.type = 'error';
- ack.c('error', {type: 'cancel'})
- .c('item-not-found', {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'}).up()
- .c('unknown-session', {xmlns: 'urn:xmpp:jingle:errors:1'});
- this.connection.send(ack);
- return true;
- }
- } else if (sess !== undefined) {
- // existing session with same session id
- // this might be out-of-order if the sess.peerjid is the same as from
- ack.type = 'error';
- ack.c('error', {type: 'cancel'})
- .c('service-unavailable', {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'}).up();
- console.warn('duplicate session id', sid);
- this.connection.send(ack);
- return true;
- }
- // FIXME: check for a defined action
- this.connection.send(ack);
- // see http://xmpp.org/extensions/xep-0166.html#concepts-session
- switch (action) {
- case 'session-initiate':
- var startMuted = $(iq).find('jingle>startmuted');
- if(startMuted && startMuted.length > 0)
- {
- var audioMuted = startMuted.attr("audio");
- var videoMuted = startMuted.attr("video");
- APP.UI.setInitialMuteFromFocus((audioMuted === "true"),
- (videoMuted === "true"));
- }
- sess = new JingleSession(
- $(iq).attr('to'), $(iq).find('jingle').attr('sid'),
- this.connection, XMPP, eventEmitter);
- // configure session
-
- sess.media_constraints = this.media_constraints;
- sess.pc_constraints = this.pc_constraints;
- sess.ice_config = this.ice_config;
-
- sess.initiate(fromJid, false);
- // FIXME: setRemoteDescription should only be done when this call is to be accepted
- sess.setRemoteDescription($(iq).find('>jingle'), 'offer');
-
- this.sessions[sess.sid] = sess;
- this.jid2session[sess.peerjid] = sess;
-
- // the callback should either
- // .sendAnswer and .accept
- // or .sendTerminate -- not necessarily synchronus
- CallIncomingJingle(sess.sid, this.connection);
- break;
- case 'session-accept':
- sess.setRemoteDescription($(iq).find('>jingle'), 'answer');
- sess.accept();
- $(document).trigger('callaccepted.jingle', [sess.sid]);
- break;
- case 'session-terminate':
- // If this is not the focus sending the terminate, we have
- // nothing more to do here.
- if (Object.keys(this.sessions).length < 1
- || !(this.sessions[Object.keys(this.sessions)[0]]
- instanceof JingleSession))
- {
- break;
- }
- console.log('terminating...', sess.sid);
- sess.terminate();
- this.terminate(sess.sid);
- if ($(iq).find('>jingle>reason').length) {
- $(document).trigger('callterminated.jingle', [
- sess.sid,
- sess.peerjid,
- $(iq).find('>jingle>reason>:first')[0].tagName,
- $(iq).find('>jingle>reason>text').text()
- ]);
- } else {
- $(document).trigger('callterminated.jingle',
- [sess.sid, sess.peerjid]);
- }
- break;
- case 'transport-info':
- sess.addIceCandidate($(iq).find('>jingle>content'));
- break;
- case 'session-info':
- var affected;
- if ($(iq).find('>jingle>ringing[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').length) {
- $(document).trigger('ringing.jingle', [sess.sid]);
- } else if ($(iq).find('>jingle>mute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').length) {
- affected = $(iq).find('>jingle>mute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').attr('name');
- $(document).trigger('mute.jingle', [sess.sid, affected]);
- } else if ($(iq).find('>jingle>unmute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').length) {
- affected = $(iq).find('>jingle>unmute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').attr('name');
- $(document).trigger('unmute.jingle', [sess.sid, affected]);
- }
- break;
- case 'addsource': // FIXME: proprietary, un-jingleish
- case 'source-add': // FIXME: proprietary
- sess.addSource($(iq).find('>jingle>content'), fromJid);
- break;
- case 'removesource': // FIXME: proprietary, un-jingleish
- case 'source-remove': // FIXME: proprietary
- sess.removeSource($(iq).find('>jingle>content'), fromJid);
- break;
- default:
- console.warn('jingle action not implemented', action);
- break;
- }
- return true;
- },
- initiate: function (peerjid, myjid) { // initiate a new jinglesession to peerjid
- var sess = new JingleSession(myjid || this.connection.jid,
- Math.random().toString(36).substr(2, 12), // random string
- this.connection, XMPP, eventEmitter);
- // configure session
-
- sess.media_constraints = this.media_constraints;
- sess.pc_constraints = this.pc_constraints;
- sess.ice_config = this.ice_config;
-
- sess.initiate(peerjid, true);
- this.sessions[sess.sid] = sess;
- this.jid2session[sess.peerjid] = sess;
- sess.sendOffer();
- return sess;
- },
- terminate: function (sid, reason, text) { // terminate by sessionid (or all sessions)
- if (sid === null || sid === undefined) {
- for (sid in this.sessions) {
- if (this.sessions[sid].state != 'ended') {
- this.sessions[sid].sendTerminate(reason || (!this.sessions[sid].active()) ? 'cancel' : null, text);
- this.sessions[sid].terminate();
- }
- delete this.jid2session[this.sessions[sid].peerjid];
- delete this.sessions[sid];
- }
- } else if (this.sessions.hasOwnProperty(sid)) {
- if (this.sessions[sid].state != 'ended') {
- this.sessions[sid].sendTerminate(reason || (!this.sessions[sid].active()) ? 'cancel' : null, text);
- this.sessions[sid].terminate();
- }
- delete this.jid2session[this.sessions[sid].peerjid];
- delete this.sessions[sid];
- }
- },
- // Used to terminate a session when an unavailable presence is received.
- terminateByJid: function (jid) {
- if (this.jid2session.hasOwnProperty(jid)) {
- var sess = this.jid2session[jid];
- if (sess) {
- sess.terminate();
- console.log('peer went away silently', jid);
- delete this.sessions[sess.sid];
- delete this.jid2session[jid];
- $(document).trigger('callterminated.jingle',
- [sess.sid, jid], 'gone');
- }
- }
- },
- terminateRemoteByJid: function (jid, reason) {
- if (this.jid2session.hasOwnProperty(jid)) {
- var sess = this.jid2session[jid];
- if (sess) {
- sess.sendTerminate(reason || (!sess.active()) ? 'kick' : null);
- sess.terminate();
- console.log('terminate peer with jid', sess.sid, jid);
- delete this.sessions[sess.sid];
- delete this.jid2session[jid];
- $(document).trigger('callterminated.jingle',
- [sess.sid, jid, 'kicked']);
- }
- }
- },
- getStunAndTurnCredentials: function () {
- // get stun and turn configuration from server via xep-0215
- // uses time-limited credentials as described in
- // http://tools.ietf.org/html/draft-uberti-behave-turn-rest-00
- //
- // see https://code.google.com/p/prosody-modules/source/browse/mod_turncredentials/mod_turncredentials.lua
- // for a prosody module which implements this
- //
- // currently, this doesn't work with updateIce and therefore credentials with a long
- // validity have to be fetched before creating the peerconnection
- // TODO: implement refresh via updateIce as described in
- // https://code.google.com/p/webrtc/issues/detail?id=1650
- var self = this;
- this.connection.sendIQ(
- $iq({type: 'get', to: this.connection.domain})
- .c('services', {xmlns: 'urn:xmpp:extdisco:1'}).c('service', {host: 'turn.' + this.connection.domain}),
- function (res) {
- var iceservers = [];
- $(res).find('>services>service').each(function (idx, el) {
- el = $(el);
- var dict = {};
- var type = el.attr('type');
- switch (type) {
- case 'stun':
- dict.url = 'stun:' + el.attr('host');
- if (el.attr('port')) {
- dict.url += ':' + el.attr('port');
- }
- iceservers.push(dict);
- break;
- case 'turn':
- case 'turns':
- dict.url = type + ':';
- if (el.attr('username')) { // https://code.google.com/p/webrtc/issues/detail?id=1508
- if (navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./) && parseInt(navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./)[2], 10) < 28) {
- dict.url += el.attr('username') + '@';
- } else {
- dict.username = el.attr('username'); // only works in M28
- }
- }
- dict.url += el.attr('host');
- if (el.attr('port') && el.attr('port') != '3478') {
- dict.url += ':' + el.attr('port');
- }
- if (el.attr('transport') && el.attr('transport') != 'udp') {
- dict.url += '?transport=' + el.attr('transport');
- }
- if (el.attr('password')) {
- dict.credential = el.attr('password');
- }
- iceservers.push(dict);
- break;
- }
- });
- self.ice_config.iceServers = iceservers;
- },
- function (err) {
- console.warn('getting turn credentials failed', err);
- console.warn('is mod_turncredentials or similar installed?');
- }
- );
- // implement push?
- },
-
- /**
- * Populates the log data
- */
- populateData: function () {
- var data = {};
- var self = this;
- Object.keys(this.sessions).forEach(function (sid) {
- var session = self.sessions[sid];
- if (session.peerconnection && session.peerconnection.updateLog) {
- // FIXME: should probably be a .dump call
- data["jingle_" + session.sid] = {
- updateLog: session.peerconnection.updateLog,
- stats: session.peerconnection.stats,
- url: window.location.href
- };
- }
- });
- return data;
- }
- });
-};
-
-
-},{"../../service/xmpp/XMPPEvents":111,"./JingleSession":51}],60:[function(require,module,exports){
-/* global Strophe */
-module.exports = function () {
-
- Strophe.addConnectionPlugin('logger', {
- // logs raw stanzas and makes them available for download as JSON
- connection: null,
- log: [],
- init: function (conn) {
- this.connection = conn;
- this.connection.rawInput = this.log_incoming.bind(this);
- this.connection.rawOutput = this.log_outgoing.bind(this);
- },
- log_incoming: function (stanza) {
- this.log.push([new Date().getTime(), 'incoming', stanza]);
- },
- log_outgoing: function (stanza) {
- this.log.push([new Date().getTime(), 'outgoing', stanza]);
- }
- });
-};
-},{}],61:[function(require,module,exports){
-/* global $, $iq, config, connection, focusMucJid, forceMuted,
- setAudioMuted, Strophe */
-/**
- * Moderate connection plugin.
- */
-module.exports = function (XMPP) {
- Strophe.addConnectionPlugin('moderate', {
- connection: null,
- init: function (conn) {
- this.connection = conn;
-
- this.connection.addHandler(this.onMute.bind(this),
- 'http://jitsi.org/jitmeet/audio',
- 'iq',
- 'set',
- null,
- null);
- },
- setMute: function (jid, mute) {
- console.info("set mute", mute);
- var iqToFocus = $iq({to: this.connection.emuc.focusMucJid, type: 'set'})
- .c('mute', {
- xmlns: 'http://jitsi.org/jitmeet/audio',
- jid: jid
- })
- .t(mute.toString())
- .up();
-
- this.connection.sendIQ(
- iqToFocus,
- function (result) {
- console.log('set mute', result);
- },
- function (error) {
- console.log('set mute error', error);
- });
- },
- onMute: function (iq) {
- var from = iq.getAttribute('from');
- if (from !== this.connection.emuc.focusMucJid) {
- console.warn("Ignored mute from non focus peer");
- return false;
- }
- var mute = $(iq).find('mute');
- if (mute.length) {
- var doMuteAudio = mute.text() === "true";
- APP.UI.setAudioMuted(doMuteAudio);
- XMPP.forceMuted = doMuteAudio;
- }
- return true;
- },
- eject: function (jid) {
- // We're not the focus, so can't terminate
- //connection.jingle.terminateRemoteByJid(jid, 'kick');
- this.connection.emuc.kick(jid);
- }
- });
-}
-},{}],62:[function(require,module,exports){
-/* jshint -W117 */
-module.exports = function() {
- Strophe.addConnectionPlugin('rayo',
- {
- RAYO_XMLNS: 'urn:xmpp:rayo:1',
- connection: null,
- init: function (conn) {
- this.connection = conn;
- if (this.connection.disco) {
- this.connection.disco.addFeature('urn:xmpp:rayo:client:1');
- }
-
- this.connection.addHandler(
- this.onRayo.bind(this), this.RAYO_XMLNS, 'iq', 'set', null, null);
- },
- onRayo: function (iq) {
- console.info("Rayo IQ", iq);
- },
- dial: function (to, from, roomName, roomPass) {
- var self = this;
- var req = $iq(
- {
- type: 'set',
- to: this.connection.emuc.focusMucJid
- }
- );
- req.c('dial',
- {
- xmlns: this.RAYO_XMLNS,
- to: to,
- from: from
- });
- req.c('header',
- {
- name: 'JvbRoomName',
- value: roomName
- }).up();
-
- if (roomPass !== null && roomPass.length) {
-
- req.c('header',
- {
- name: 'JvbRoomPassword',
- value: roomPass
- }).up();
- }
-
- this.connection.sendIQ(
- req,
- function (result) {
- console.info('Dial result ', result);
-
- var resource = $(result).find('ref').attr('uri');
- this.call_resource = resource.substr('xmpp:'.length);
- console.info(
- "Received call resource: " + this.call_resource);
- },
- function (error) {
- console.info('Dial error ', error);
- }
- );
- },
- hang_up: function () {
- if (!this.call_resource) {
- console.warn("No call in progress");
- return;
- }
-
- var self = this;
- var req = $iq(
- {
- type: 'set',
- to: this.call_resource
- }
- );
- req.c('hangup',
- {
- xmlns: this.RAYO_XMLNS
- });
-
- this.connection.sendIQ(
- req,
- function (result) {
- console.info('Hangup result ', result);
- self.call_resource = null;
- },
- function (error) {
- console.info('Hangup error ', error);
- self.call_resource = null;
- }
- );
- }
- }
- );
-};
-
},{}],63:[function(require,module,exports){
-/**
- * Strophe logger implementation. Logs from level WARN and above.
- */
-module.exports = function () {
-
- Strophe.log = function (level, msg) {
- switch (level) {
- case Strophe.LogLevel.WARN:
- console.warn("Strophe: " + msg);
- break;
- case Strophe.LogLevel.ERROR:
- case Strophe.LogLevel.FATAL:
- console.error("Strophe: " + msg);
- break;
- }
- };
-
- Strophe.getStatusString = function (status) {
- switch (status) {
- case Strophe.Status.ERROR:
- return "ERROR";
- case Strophe.Status.CONNECTING:
- return "CONNECTING";
- case Strophe.Status.CONNFAIL:
- return "CONNFAIL";
- case Strophe.Status.AUTHENTICATING:
- return "AUTHENTICATING";
- case Strophe.Status.AUTHFAIL:
- return "AUTHFAIL";
- case Strophe.Status.CONNECTED:
- return "CONNECTED";
- case Strophe.Status.DISCONNECTED:
- return "DISCONNECTED";
- case Strophe.Status.DISCONNECTING:
- return "DISCONNECTING";
- case Strophe.Status.ATTACHED:
- return "ATTACHED";
- default:
- return "unknown";
- }
- };
-};
-
+/* global $, $iq, config, connection, focusMucJid, forceMuted,
+ setAudioMuted, Strophe */
+/**
+ * Moderate connection plugin.
+ */
+module.exports = function (XMPP) {
+ Strophe.addConnectionPlugin('moderate', {
+ connection: null,
+ init: function (conn) {
+ this.connection = conn;
+
+ this.connection.addHandler(this.onMute.bind(this),
+ 'http://jitsi.org/jitmeet/audio',
+ 'iq',
+ 'set',
+ null,
+ null);
+ },
+ setMute: function (jid, mute) {
+ console.info("set mute", mute);
+ var iqToFocus = $iq({to: this.connection.emuc.focusMucJid, type: 'set'})
+ .c('mute', {
+ xmlns: 'http://jitsi.org/jitmeet/audio',
+ jid: jid
+ })
+ .t(mute.toString())
+ .up();
+
+ this.connection.sendIQ(
+ iqToFocus,
+ function (result) {
+ console.log('set mute', result);
+ },
+ function (error) {
+ console.log('set mute error', error);
+ });
+ },
+ onMute: function (iq) {
+ var from = iq.getAttribute('from');
+ if (from !== this.connection.emuc.focusMucJid) {
+ console.warn("Ignored mute from non focus peer");
+ return false;
+ }
+ var mute = $(iq).find('mute');
+ if (mute.length) {
+ var doMuteAudio = mute.text() === "true";
+ APP.UI.setAudioMuted(doMuteAudio);
+ XMPP.forceMuted = doMuteAudio;
+ }
+ return true;
+ },
+ eject: function (jid) {
+ // We're not the focus, so can't terminate
+ //connection.jingle.terminateRemoteByJid(jid, 'kick');
+ this.connection.emuc.kick(jid);
+ }
+ });
+}
},{}],64:[function(require,module,exports){
-/* global $, APP, config, Strophe*/
-var Moderator = require("./moderator");
-var EventEmitter = require("events");
-var Recording = require("./recording");
-var SDP = require("./SDP");
-var Settings = require("../settings/Settings");
-var Pako = require("pako");
-var StreamEventTypes = require("../../service/RTC/StreamEventTypes");
-var RTCEvents = require("../../service/RTC/RTCEvents");
-var UIEvents = require("../../service/UI/UIEvents");
-var XMPPEvents = require("../../service/xmpp/XMPPEvents");
-var retry = require('retry');
+/* jshint -W117 */
+module.exports = function() {
+ Strophe.addConnectionPlugin('rayo',
+ {
+ RAYO_XMLNS: 'urn:xmpp:rayo:1',
+ connection: null,
+ init: function (conn) {
+ this.connection = conn;
+ if (this.connection.disco) {
+ this.connection.disco.addFeature('urn:xmpp:rayo:client:1');
+ }
+
+ this.connection.addHandler(
+ this.onRayo.bind(this), this.RAYO_XMLNS, 'iq', 'set', null, null);
+ },
+ onRayo: function (iq) {
+ console.info("Rayo IQ", iq);
+ },
+ dial: function (to, from, roomName, roomPass) {
+ var self = this;
+ var req = $iq(
+ {
+ type: 'set',
+ to: this.connection.emuc.focusMucJid
+ }
+ );
+ req.c('dial',
+ {
+ xmlns: this.RAYO_XMLNS,
+ to: to,
+ from: from
+ });
+ req.c('header',
+ {
+ name: 'JvbRoomName',
+ value: roomName
+ }).up();
+
+ if (roomPass !== null && roomPass.length) {
+
+ req.c('header',
+ {
+ name: 'JvbRoomPassword',
+ value: roomPass
+ }).up();
+ }
+
+ this.connection.sendIQ(
+ req,
+ function (result) {
+ console.info('Dial result ', result);
+
+ var resource = $(result).find('ref').attr('uri');
+ this.call_resource = resource.substr('xmpp:'.length);
+ console.info(
+ "Received call resource: " + this.call_resource);
+ },
+ function (error) {
+ console.info('Dial error ', error);
+ }
+ );
+ },
+ hang_up: function () {
+ if (!this.call_resource) {
+ console.warn("No call in progress");
+ return;
+ }
+
+ var self = this;
+ var req = $iq(
+ {
+ type: 'set',
+ to: this.call_resource
+ }
+ );
+ req.c('hangup',
+ {
+ xmlns: this.RAYO_XMLNS
+ });
+
+ this.connection.sendIQ(
+ req,
+ function (result) {
+ console.info('Hangup result ', result);
+ self.call_resource = null;
+ },
+ function (error) {
+ console.info('Hangup error ', error);
+ self.call_resource = null;
+ }
+ );
+ }
+ }
+ );
+};
-var eventEmitter = new EventEmitter();
-var connection = null;
-var authenticatedUser = false;
+},{}],65:[function(require,module,exports){
+/**
+ * Strophe logger implementation. Logs from level WARN and above.
+ */
+module.exports = function () {
+
+ Strophe.log = function (level, msg) {
+ switch (level) {
+ case Strophe.LogLevel.WARN:
+ console.warn("Strophe: " + msg);
+ break;
+ case Strophe.LogLevel.ERROR:
+ case Strophe.LogLevel.FATAL:
+ console.error("Strophe: " + msg);
+ break;
+ }
+ };
+
+ Strophe.getStatusString = function (status) {
+ switch (status) {
+ case Strophe.Status.ERROR:
+ return "ERROR";
+ case Strophe.Status.CONNECTING:
+ return "CONNECTING";
+ case Strophe.Status.CONNFAIL:
+ return "CONNFAIL";
+ case Strophe.Status.AUTHENTICATING:
+ return "AUTHENTICATING";
+ case Strophe.Status.AUTHFAIL:
+ return "AUTHFAIL";
+ case Strophe.Status.CONNECTED:
+ return "CONNECTED";
+ case Strophe.Status.DISCONNECTED:
+ return "DISCONNECTED";
+ case Strophe.Status.DISCONNECTING:
+ return "DISCONNECTING";
+ case Strophe.Status.ATTACHED:
+ return "ATTACHED";
+ default:
+ return "unknown";
+ }
+ };
+};
-function connect(jid, password) {
+},{}],66:[function(require,module,exports){
+/* global $, APP, config, Strophe*/
+var Moderator = require("./moderator");
+var EventEmitter = require("events");
+var Recording = require("./recording");
+var SDP = require("./SDP");
+var Settings = require("../settings/Settings");
+var Pako = require("pako");
+var StreamEventTypes = require("../../service/RTC/StreamEventTypes");
+var RTCEvents = require("../../service/RTC/RTCEvents");
+var UIEvents = require("../../service/UI/UIEvents");
+var XMPPEvents = require("../../service/xmpp/XMPPEvents");
+var retry = require('retry');
+
+var eventEmitter = new EventEmitter();
+var connection = null;
+var authenticatedUser = false;
+
+function connect(jid, password) {
+
+ var faultTolerantConnect = retry.operation({
+ retries: 3
+ });
+
+ // fault tolerant connect
+ faultTolerantConnect.attempt(function () {
+
+ connection = XMPP.createConnection();
+ Moderator.setConnection(connection);
+
+ if (connection.disco) {
+ // for chrome, add multistream cap
+ }
+ connection.jingle.pc_constraints = APP.RTC.getPCConstraints();
+ if (config.useIPv6) {
+ // https://code.google.com/p/webrtc/issues/detail?id=2828
+ if (!connection.jingle.pc_constraints.optional)
+ connection.jingle.pc_constraints.optional = [];
+ connection.jingle.pc_constraints.optional.push({googIPv6: true});
+ }
+
+ // Include user info in MUC presence
+ var settings = Settings.getSettings();
+ if (settings.email) {
+ connection.emuc.addEmailToPresence(settings.email);
+ }
+ if (settings.uid) {
+ connection.emuc.addUserIdToPresence(settings.uid);
+ }
+ if (settings.displayName) {
+ connection.emuc.addDisplayNameToPresence(settings.displayName);
+ }
+
+
+ // connection.connect() starts the connection process.
+ //
+ // As the connection process proceeds, the user supplied callback will
+ // be triggered multiple times with status updates. The callback should
+ // take two arguments - the status code and the error condition.
+ //
+ // The status code will be one of the values in the Strophe.Status
+ // constants. The error condition will be one of the conditions defined
+ // in RFC 3920 or the condition ‘strophe-parsererror’.
+ //
+ // The Parameters wait, hold and route are optional and only relevant
+ // for BOSH connections. Please see XEP 124 for a more detailed
+ // explanation of the optional parameters.
+ //
+ // Connection status constants for use by the connection handler
+ // callback.
+ //
+ // Status.ERROR - An error has occurred (websockets specific)
+ // Status.CONNECTING - The connection is currently being made
+ // Status.CONNFAIL - The connection attempt failed
+ // Status.AUTHENTICATING - The connection is authenticating
+ // Status.AUTHFAIL - The authentication attempt failed
+ // Status.CONNECTED - The connection has succeeded
+ // Status.DISCONNECTED - The connection has been terminated
+ // Status.DISCONNECTING - The connection is currently being terminated
+ // Status.ATTACHED - The connection has been attached
+
+ var anonymousConnectionFailed = false;
+ var connectionFailed = false;
+ var lastErrorMsg;
+ connection.connect(jid, password, function (status, msg) {
+ console.log('Strophe status changed to',
+ Strophe.getStatusString(status), msg);
+ if (status === Strophe.Status.CONNECTED) {
+ if (config.useStunTurn) {
+ connection.jingle.getStunAndTurnCredentials();
+ }
+
+ console.info("My Jabber ID: " + connection.jid);
+
+ if (password)
+ authenticatedUser = true;
+ maybeDoJoin();
+ } else if (status === Strophe.Status.CONNFAIL) {
+ if (msg === 'x-strophe-bad-non-anon-jid') {
+ anonymousConnectionFailed = true;
+ } else {
+ connectionFailed = true;
+ }
+ lastErrorMsg = msg;
+ } else if (status === Strophe.Status.DISCONNECTED) {
+ if (anonymousConnectionFailed) {
+ // prompt user for username and password
+ XMPP.promptLogin();
+ } else {
+
+ // Strophe already has built-in HTTP/BOSH error handling and
+ // request retry logic. Requests are resent automatically
+ // until their error count reaches 5. Strophe.js disconnects
+ // if the error count is > 5. We are not replicating this
+ // here.
+ //
+ // The "problem" is that failed HTTP/BOSH requests don't
+ // trigger a callback with a status update, so when a
+ // callback with status Strophe.Status.DISCONNECTED arrives,
+ // we can't be sure if it's a graceful disconnect or if it's
+ // triggered by some HTTP/BOSH error.
+ //
+ // But that's a minor issue in Jitsi Meet as we never
+ // disconnect anyway, not even when the user closes the
+ // browser window (which is kind of wrong, but the point is
+ // that we should never ever get disconnected).
+ //
+ // On the other hand, failed connections due to XMPP layer
+ // errors, trigger a callback with status Strophe.Status.CONNFAIL.
+ //
+ // Here we implement retry logic for failed connections due
+ // to XMPP layer errors and we display an error to the user
+ // if we get disconnected from the XMPP server permanently.
+
+ // If the connection failed, retry.
+ if (connectionFailed
+ && faultTolerantConnect.retry("connection-failed")) {
+ return;
+ }
+
+ // If we failed to connect to the XMPP server, fire an event
+ // to let all the interested module now about it.
+ eventEmitter.emit(XMPPEvents.CONNECTION_FAILED,
+ msg ? msg : lastErrorMsg);
+ }
+ } else if (status === Strophe.Status.AUTHFAIL) {
+ // wrong password or username, prompt user
+ XMPP.promptLogin();
+
+ }
+ });
+ });
+}
+
+
+
+function maybeDoJoin() {
+ if (connection && connection.connected &&
+ Strophe.getResourceFromJid(connection.jid)
+ && (APP.RTC.localAudio || APP.RTC.localVideo)) {
+ // .connected is true while connecting?
+ doJoin();
+ }
+}
+
+function doJoin() {
+ var roomName = APP.UI.generateRoomName();
+
+ Moderator.allocateConferenceFocus(
+ roomName, APP.UI.checkForNicknameAndJoin);
+}
+
+function initStrophePlugins()
+{
+ require("./strophe.emuc")(XMPP, eventEmitter);
+ require("./strophe.jingle")(XMPP, eventEmitter);
+ require("./strophe.moderate")(XMPP);
+ require("./strophe.util")();
+ require("./strophe.rayo")();
+ require("./strophe.logger")();
+}
+
+function registerListeners() {
+ APP.RTC.addStreamListener(maybeDoJoin,
+ StreamEventTypes.EVENT_TYPE_LOCAL_CREATED);
+ APP.RTC.addListener(RTCEvents.AVAILABLE_DEVICES_CHANGED, function (devices) {
+ XMPP.addToPresence("devices", devices);
+ })
+ APP.UI.addListener(UIEvents.NICKNAME_CHANGED, function (nickname) {
+ XMPP.addToPresence("displayName", nickname);
+ });
+}
+
+var unload = (function () {
+ var unloaded = false;
+
+ return function () {
+ if (unloaded) { return; }
+ unloaded = true;
+
+ if (connection && connection.connected) {
+ // ensure signout
+ $.ajax({
+ type: 'POST',
+ url: config.bosh,
+ async: false,
+ cache: false,
+ contentType: 'application/xml',
+ data: "" +
+ " " +
+ "",
+ success: function (data) {
+ console.log('signed out');
+ console.log(data);
+ },
+ error: function (XMLHttpRequest, textStatus, errorThrown) {
+ console.log('signout error',
+ textStatus + ' (' + errorThrown + ')');
+ }
+ });
+ }
+ XMPP.disposeConference(true);
+ };
+})();
+
+function setupEvents() {
+ // In recent versions of FF the 'beforeunload' event is not fired when the
+ // window or the tab is closed. It is only fired when we leave the page
+ // (change URL). If this participant doesn't unload properly, then it
+ // becomes a ghost for the rest of the participants that stay in the
+ // conference. Thankfully handling the 'unload' event in addition to the
+ // 'beforeunload' event seems to garante the execution of the 'unload'
+ // method at least once.
+ //
+ // The 'unload' method can safely be run multiple times, it will actually do
+ // something only the first time that it's run, so we're don't have to worry
+ // about browsers that fire both events.
+
+ $(window).bind('beforeunload', unload);
+ $(window).bind('unload', unload);
+}
+
+var XMPP = {
+ getConnection: function(){ return connection; },
+ sessionTerminated: false,
+
+ /**
+ * XMPP connection status
+ */
+ Status: Strophe.Status,
+
+ /**
+ * Remembers if we were muted by the focus.
+ * @type {boolean}
+ */
+ forceMuted: false,
+ start: function () {
+ setupEvents();
+ initStrophePlugins();
+ registerListeners();
+ Moderator.init(this, eventEmitter);
+ var configDomain = config.hosts.anonymousdomain || config.hosts.domain;
+ // Force authenticated domain if room is appended with '?login=true'
+ if (config.hosts.anonymousdomain &&
+ window.location.search.indexOf("login=true") !== -1) {
+ configDomain = config.hosts.domain;
+ }
+ var jid = configDomain || window.location.hostname;
+ connect(jid, null);
+ },
+ createConnection: function () {
+ var bosh = config.bosh || '/http-bind';
+
+ return new Strophe.Connection(bosh);
+ },
+ getStatusString: function (status) {
+ return Strophe.getStatusString(status);
+ },
+ promptLogin: function () {
+ // FIXME: re-use LoginDialog which supports retries
+ APP.UI.showLoginPopup(connect);
+ },
+ joinRoom: function(roomName, useNicks, nick)
+ {
+ var roomjid;
+ roomjid = roomName;
+
+ if (useNicks) {
+ if (nick) {
+ roomjid += '/' + nick;
+ } else {
+ roomjid += '/' + Strophe.getNodeFromJid(connection.jid);
+ }
+ } else {
+
+ var tmpJid = Strophe.getNodeFromJid(connection.jid);
+
+ if(!authenticatedUser)
+ tmpJid = tmpJid.substr(0, 8);
+
+ roomjid += '/' + tmpJid;
+ }
+ connection.emuc.doJoin(roomjid);
+ },
+ myJid: function () {
+ if(!connection)
+ return null;
+ return connection.emuc.myroomjid;
+ },
+ myResource: function () {
+ if(!connection || ! connection.emuc.myroomjid)
+ return null;
+ return Strophe.getResourceFromJid(connection.emuc.myroomjid);
+ },
+ disposeConference: function (onUnload) {
+ var handler = connection.jingle.activecall;
+ if (handler && handler.peerconnection) {
+ // FIXME: probably removing streams is not required and close() should
+ // be enough
+ if (APP.RTC.localAudio) {
+ handler.peerconnection.removeStream(
+ APP.RTC.localAudio.getOriginalStream(), onUnload);
+ }
+ if (APP.RTC.localVideo) {
+ handler.peerconnection.removeStream(
+ APP.RTC.localVideo.getOriginalStream(), onUnload);
+ }
+ handler.peerconnection.close();
+ }
+ eventEmitter.emit(XMPPEvents.DISPOSE_CONFERENCE, onUnload);
+ connection.jingle.activecall = null;
+ if(!onUnload)
+ {
+ this.sessionTerminated = true;
+ connection.emuc.doLeave();
+ }
+ },
+ addListener: function(type, listener)
+ {
+ eventEmitter.on(type, listener);
+ },
+ removeListener: function (type, listener) {
+ eventEmitter.removeListener(type, listener);
+ },
+ allocateConferenceFocus: function(roomName, callback) {
+ Moderator.allocateConferenceFocus(roomName, callback);
+ },
+ getLoginUrl: function (roomName, callback) {
+ Moderator.getLoginUrl(roomName, callback);
+ },
+ getPopupLoginUrl: function (roomName, callback) {
+ Moderator.getPopupLoginUrl(roomName, callback);
+ },
+ isModerator: function () {
+ return Moderator.isModerator();
+ },
+ isSipGatewayEnabled: function () {
+ return Moderator.isSipGatewayEnabled();
+ },
+ isExternalAuthEnabled: function () {
+ return Moderator.isExternalAuthEnabled();
+ },
+ switchStreams: function (stream, oldStream, callback, isAudio) {
+ if (connection && connection.jingle.activecall) {
+ // FIXME: will block switchInProgress on true value in case of exception
+ connection.jingle.activecall.switchStreams(stream, oldStream, callback, isAudio);
+ } else {
+ // We are done immediately
+ console.warn("No conference handler or conference not started yet");
+ callback();
+ }
+ },
+ sendVideoInfoPresence: function (mute) {
+ if(!connection)
+ return;
+ connection.emuc.addVideoInfoToPresence(mute);
+ connection.emuc.sendPresence();
+ },
+ setVideoMute: function (mute, callback, options) {
+ if(!connection)
+ return;
+ var self = this;
+ var localCallback = function (mute) {
+ self.sendVideoInfoPresence(mute);
+ return callback(mute);
+ };
+
+ if(connection.jingle.activecall)
+ {
+ connection.jingle.activecall.setVideoMute(
+ mute, localCallback, options);
+ }
+ else {
+ localCallback(mute);
+ }
+
+ },
+ setAudioMute: function (mute, callback) {
+ if (!(connection && APP.RTC.localAudio)) {
+ return false;
+ }
+
+
+ if (this.forceMuted && !mute) {
+ console.info("Asking focus for unmute");
+ connection.moderate.setMute(connection.emuc.myroomjid, mute);
+ // FIXME: wait for result before resetting muted status
+ this.forceMuted = false;
+ }
+
+ if (mute == APP.RTC.localAudio.isMuted()) {
+ // Nothing to do
+ return true;
+ }
+
+ // It is not clear what is the right way to handle multiple tracks.
+ // So at least make sure that they are all muted or all unmuted and
+ // that we send presence just once.
+ APP.RTC.localAudio.setMute(!mute);
+ // isMuted is the opposite of audioEnabled
+ this.sendAudioInfoPresence(mute, callback);
+ return true;
+ },
+ sendAudioInfoPresence: function(mute, callback)
+ {
+ if(connection) {
+ connection.emuc.addAudioInfoToPresence(mute);
+ connection.emuc.sendPresence();
+ }
+ callback();
+ return true;
+ },
+ // Really mute video, i.e. dont even send black frames
+ muteVideo: function (pc, unmute) {
+ // FIXME: this probably needs another of those lovely state safeguards...
+ // which checks for iceconn == connected and sigstate == stable
+ pc.setRemoteDescription(pc.remoteDescription,
+ function () {
+ pc.createAnswer(
+ function (answer) {
+ var sdp = new SDP(answer.sdp);
+ if (sdp.media.length > 1) {
+ if (unmute)
+ sdp.media[1] = sdp.media[1].replace('a=recvonly', 'a=sendrecv');
+ else
+ sdp.media[1] = sdp.media[1].replace('a=sendrecv', 'a=recvonly');
+ sdp.raw = sdp.session + sdp.media.join('');
+ answer.sdp = sdp.raw;
+ }
+ pc.setLocalDescription(answer,
+ function () {
+ console.log('mute SLD ok');
+ },
+ function (error) {
+ console.log('mute SLD error');
+ APP.UI.messageHandler.showError("dialog.error",
+ "dialog.SLDFailure");
+ }
+ );
+ },
+ function (error) {
+ console.log(error);
+ APP.UI.messageHandler.showError();
+ }
+ );
+ },
+ function (error) {
+ console.log('muteVideo SRD error');
+ APP.UI.messageHandler.showError("dialog.error",
+ "dialog.SRDFailure");
+
+ }
+ );
+ },
+ toggleRecording: function (tokenEmptyCallback,
+ startingCallback, startedCallback) {
+ Recording.toggleRecording(tokenEmptyCallback,
+ startingCallback, startedCallback, connection);
+ },
+ addToPresence: function (name, value, dontSend) {
+ switch (name)
+ {
+ case "displayName":
+ connection.emuc.addDisplayNameToPresence(value);
+ break;
+ case "prezi":
+ connection.emuc.addPreziToPresence(value, 0);
+ break;
+ case "preziSlide":
+ connection.emuc.addCurrentSlideToPresence(value);
+ break;
+ case "connectionQuality":
+ connection.emuc.addConnectionInfoToPresence(value);
+ break;
+ case "email":
+ connection.emuc.addEmailToPresence(value);
+ break;
+ case "devices":
+ connection.emuc.addDevicesToPresence(value);
+ break;
+ case "startMuted":
+ if(!Moderator.isModerator())
+ return;
+ connection.emuc.addStartMutedToPresence(value[0],
+ value[1]);
+ break;
+ default :
+ console.log("Unknown tag for presence: " + name);
+ return;
+ }
+ if (!dontSend)
+ connection.emuc.sendPresence();
+ },
+ /**
+ * Sends 'data' as a log message to the focus. Returns true iff a message
+ * was sent.
+ * @param data
+ * @returns {boolean} true iff a message was sent.
+ */
+ sendLogs: function (data) {
+ if(!connection.emuc.focusMucJid)
+ return false;
+
+ var deflate = true;
+
+ var content = JSON.stringify(data);
+ if (deflate) {
+ content = String.fromCharCode.apply(null, Pako.deflateRaw(content));
+ }
+ content = Base64.encode(content);
+ // XEP-0337-ish
+ var message = $msg({to: connection.emuc.focusMucJid, type: 'normal'});
+ message.c('log', { xmlns: 'urn:xmpp:eventlog',
+ id: 'PeerConnectionStats'});
+ message.c('message').t(content).up();
+ if (deflate) {
+ message.c('tag', {name: "deflated", value: "true"}).up();
+ }
+ message.up();
+
+ connection.send(message);
+ return true;
+ },
+ populateData: function () {
+ var data = {};
+ if (connection.jingle) {
+ data = connection.jingle.populateData();
+ }
+ return data;
+ },
+ getLogger: function () {
+ if(connection.logger)
+ return connection.logger.log;
+ return null;
+ },
+ getPrezi: function () {
+ return connection.emuc.getPrezi(this.myJid());
+ },
+ removePreziFromPresence: function () {
+ connection.emuc.removePreziFromPresence();
+ connection.emuc.sendPresence();
+ },
+ sendChatMessage: function (message, nickname) {
+ connection.emuc.sendMessage(message, nickname);
+ },
+ setSubject: function (topic) {
+ connection.emuc.setSubject(topic);
+ },
+ lockRoom: function (key, onSuccess, onError, onNotSupported) {
+ connection.emuc.lockRoom(key, onSuccess, onError, onNotSupported);
+ },
+ dial: function (to, from, roomName,roomPass) {
+ connection.rayo.dial(to, from, roomName,roomPass);
+ },
+ setMute: function (jid, mute) {
+ connection.moderate.setMute(jid, mute);
+ },
+ eject: function (jid) {
+ connection.moderate.eject(jid);
+ },
+ logout: function (callback) {
+ Moderator.logout(callback);
+ },
+ findJidFromResource: function (resource) {
+ return connection.emuc.findJidFromResource(resource);
+ },
+ getMembers: function () {
+ return connection.emuc.members;
+ },
+ getJidFromSSRC: function (ssrc) {
+ if(!connection)
+ return null;
+ return connection.emuc.ssrc2jid[ssrc];
+ },
+ getMUCJoined: function () {
+ return connection.emuc.joined;
+ },
+ getSessions: function () {
+ return connection.jingle.sessions;
+ },
+ removeStream: function (stream) {
+ if(!connection || !connection.jingle.activecall ||
+ !connection.jingle.activecall.peerconnection)
+ return;
+ connection.jingle.activecall.peerconnection.removeStream(stream);
+ }
+};
+
+module.exports = XMPP;
- var faultTolerantConnect = retry.operation({
- retries: 3
- });
-
- // fault tolerant connect
- faultTolerantConnect.attempt(function () {
-
- connection = XMPP.createConnection();
- Moderator.setConnection(connection);
-
- if (connection.disco) {
- // for chrome, add multistream cap
- }
- connection.jingle.pc_constraints = APP.RTC.getPCConstraints();
- if (config.useIPv6) {
- // https://code.google.com/p/webrtc/issues/detail?id=2828
- if (!connection.jingle.pc_constraints.optional)
- connection.jingle.pc_constraints.optional = [];
- connection.jingle.pc_constraints.optional.push({googIPv6: true});
- }
-
- // Include user info in MUC presence
- var settings = Settings.getSettings();
- if (settings.email) {
- connection.emuc.addEmailToPresence(settings.email);
- }
- if (settings.uid) {
- connection.emuc.addUserIdToPresence(settings.uid);
- }
- if (settings.displayName) {
- connection.emuc.addDisplayNameToPresence(settings.displayName);
- }
-
-
- // connection.connect() starts the connection process.
- //
- // As the connection process proceeds, the user supplied callback will
- // be triggered multiple times with status updates. The callback should
- // take two arguments - the status code and the error condition.
- //
- // The status code will be one of the values in the Strophe.Status
- // constants. The error condition will be one of the conditions defined
- // in RFC 3920 or the condition ‘strophe-parsererror’.
- //
- // The Parameters wait, hold and route are optional and only relevant
- // for BOSH connections. Please see XEP 124 for a more detailed
- // explanation of the optional parameters.
- //
- // Connection status constants for use by the connection handler
- // callback.
- //
- // Status.ERROR - An error has occurred (websockets specific)
- // Status.CONNECTING - The connection is currently being made
- // Status.CONNFAIL - The connection attempt failed
- // Status.AUTHENTICATING - The connection is authenticating
- // Status.AUTHFAIL - The authentication attempt failed
- // Status.CONNECTED - The connection has succeeded
- // Status.DISCONNECTED - The connection has been terminated
- // Status.DISCONNECTING - The connection is currently being terminated
- // Status.ATTACHED - The connection has been attached
-
- var anonymousConnectionFailed = false;
- var connectionFailed = false;
- var lastErrorMsg;
- connection.connect(jid, password, function (status, msg) {
- console.log('Strophe status changed to',
- Strophe.getStatusString(status), msg);
- if (status === Strophe.Status.CONNECTED) {
- if (config.useStunTurn) {
- connection.jingle.getStunAndTurnCredentials();
- }
-
- console.info("My Jabber ID: " + connection.jid);
-
- if (password)
- authenticatedUser = true;
- maybeDoJoin();
- } else if (status === Strophe.Status.CONNFAIL) {
- if (msg === 'x-strophe-bad-non-anon-jid') {
- anonymousConnectionFailed = true;
- } else {
- connectionFailed = true;
- }
- lastErrorMsg = msg;
- } else if (status === Strophe.Status.DISCONNECTED) {
- if (anonymousConnectionFailed) {
- // prompt user for username and password
- XMPP.promptLogin();
- } else {
-
- // Strophe already has built-in HTTP/BOSH error handling and
- // request retry logic. Requests are resent automatically
- // until their error count reaches 5. Strophe.js disconnects
- // if the error count is > 5. We are not replicating this
- // here.
- //
- // The "problem" is that failed HTTP/BOSH requests don't
- // trigger a callback with a status update, so when a
- // callback with status Strophe.Status.DISCONNECTED arrives,
- // we can't be sure if it's a graceful disconnect or if it's
- // triggered by some HTTP/BOSH error.
- //
- // But that's a minor issue in Jitsi Meet as we never
- // disconnect anyway, not even when the user closes the
- // browser window (which is kind of wrong, but the point is
- // that we should never ever get disconnected).
- //
- // On the other hand, failed connections due to XMPP layer
- // errors, trigger a callback with status Strophe.Status.CONNFAIL.
- //
- // Here we implement retry logic for failed connections due
- // to XMPP layer errors and we display an error to the user
- // if we get disconnected from the XMPP server permanently.
-
- // If the connection failed, retry.
- if (connectionFailed
- && faultTolerantConnect.retry("connection-failed")) {
- return;
- }
-
- // If we failed to connect to the XMPP server, fire an event
- // to let all the interested module now about it.
- eventEmitter.emit(XMPPEvents.CONNECTION_FAILED,
- msg ? msg : lastErrorMsg);
- }
- } else if (status === Strophe.Status.AUTHFAIL) {
- // wrong password or username, prompt user
- XMPP.promptLogin();
-
- }
- });
- });
-}
-
-
-
-function maybeDoJoin() {
- if (connection && connection.connected &&
- Strophe.getResourceFromJid(connection.jid)
- && (APP.RTC.localAudio || APP.RTC.localVideo)) {
- // .connected is true while connecting?
- doJoin();
- }
-}
-
-function doJoin() {
- var roomName = APP.UI.generateRoomName();
-
- Moderator.allocateConferenceFocus(
- roomName, APP.UI.checkForNicknameAndJoin);
-}
-
-function initStrophePlugins()
-{
- require("./strophe.emuc")(XMPP, eventEmitter);
- require("./strophe.jingle")(XMPP, eventEmitter);
- require("./strophe.moderate")(XMPP);
- require("./strophe.util")();
- require("./strophe.rayo")();
- require("./strophe.logger")();
-}
-
-function registerListeners() {
- APP.RTC.addStreamListener(maybeDoJoin,
- StreamEventTypes.EVENT_TYPE_LOCAL_CREATED);
- APP.RTC.addListener(RTCEvents.AVAILABLE_DEVICES_CHANGED, function (devices) {
- XMPP.addToPresence("devices", devices);
- })
- APP.UI.addListener(UIEvents.NICKNAME_CHANGED, function (nickname) {
- XMPP.addToPresence("displayName", nickname);
- });
-}
-
-var unload = (function () {
- var unloaded = false;
-
- return function () {
- if (unloaded) { return; }
- unloaded = true;
-
- if (connection && connection.connected) {
- // ensure signout
- $.ajax({
- type: 'POST',
- url: config.bosh,
- async: false,
- cache: false,
- contentType: 'application/xml',
- data: "" +
- " " +
- "",
- success: function (data) {
- console.log('signed out');
- console.log(data);
- },
- error: function (XMLHttpRequest, textStatus, errorThrown) {
- console.log('signout error',
- textStatus + ' (' + errorThrown + ')');
- }
- });
- }
- XMPP.disposeConference(true);
- };
-})();
-
-function setupEvents() {
- // In recent versions of FF the 'beforeunload' event is not fired when the
- // window or the tab is closed. It is only fired when we leave the page
- // (change URL). If this participant doesn't unload properly, then it
- // becomes a ghost for the rest of the participants that stay in the
- // conference. Thankfully handling the 'unload' event in addition to the
- // 'beforeunload' event seems to garante the execution of the 'unload'
- // method at least once.
- //
- // The 'unload' method can safely be run multiple times, it will actually do
- // something only the first time that it's run, so we're don't have to worry
- // about browsers that fire both events.
-
- $(window).bind('beforeunload', unload);
- $(window).bind('unload', unload);
-}
-
-var XMPP = {
- getConnection: function(){ return connection; },
- sessionTerminated: false,
-
- /**
- * XMPP connection status
- */
- Status: Strophe.Status,
-
- /**
- * Remembers if we were muted by the focus.
- * @type {boolean}
- */
- forceMuted: false,
- start: function () {
- setupEvents();
- initStrophePlugins();
- registerListeners();
- Moderator.init(this, eventEmitter);
- var configDomain = config.hosts.anonymousdomain || config.hosts.domain;
- // Force authenticated domain if room is appended with '?login=true'
- if (config.hosts.anonymousdomain &&
- window.location.search.indexOf("login=true") !== -1) {
- configDomain = config.hosts.domain;
- }
- var jid = configDomain || window.location.hostname;
- connect(jid, null);
- },
- createConnection: function () {
- var bosh = config.bosh || '/http-bind';
-
- return new Strophe.Connection(bosh);
- },
- getStatusString: function (status) {
- return Strophe.getStatusString(status);
- },
- promptLogin: function () {
- // FIXME: re-use LoginDialog which supports retries
- APP.UI.showLoginPopup(connect);
- },
- joinRoom: function(roomName, useNicks, nick)
- {
- var roomjid;
- roomjid = roomName;
-
- if (useNicks) {
- if (nick) {
- roomjid += '/' + nick;
- } else {
- roomjid += '/' + Strophe.getNodeFromJid(connection.jid);
- }
- } else {
-
- var tmpJid = Strophe.getNodeFromJid(connection.jid);
-
- if(!authenticatedUser)
- tmpJid = tmpJid.substr(0, 8);
-
- roomjid += '/' + tmpJid;
- }
- connection.emuc.doJoin(roomjid);
- },
- myJid: function () {
- if(!connection)
- return null;
- return connection.emuc.myroomjid;
- },
- myResource: function () {
- if(!connection || ! connection.emuc.myroomjid)
- return null;
- return Strophe.getResourceFromJid(connection.emuc.myroomjid);
- },
- disposeConference: function (onUnload) {
- var handler = connection.jingle.activecall;
- if (handler && handler.peerconnection) {
- // FIXME: probably removing streams is not required and close() should
- // be enough
- if (APP.RTC.localAudio) {
- handler.peerconnection.removeStream(
- APP.RTC.localAudio.getOriginalStream(), onUnload);
- }
- if (APP.RTC.localVideo) {
- handler.peerconnection.removeStream(
- APP.RTC.localVideo.getOriginalStream(), onUnload);
- }
- handler.peerconnection.close();
- }
- eventEmitter.emit(XMPPEvents.DISPOSE_CONFERENCE, onUnload);
- connection.jingle.activecall = null;
- if(!onUnload)
- {
- this.sessionTerminated = true;
- connection.emuc.doLeave();
- }
- },
- addListener: function(type, listener)
- {
- eventEmitter.on(type, listener);
- },
- removeListener: function (type, listener) {
- eventEmitter.removeListener(type, listener);
- },
- allocateConferenceFocus: function(roomName, callback) {
- Moderator.allocateConferenceFocus(roomName, callback);
- },
- getLoginUrl: function (roomName, callback) {
- Moderator.getLoginUrl(roomName, callback);
- },
- getPopupLoginUrl: function (roomName, callback) {
- Moderator.getPopupLoginUrl(roomName, callback);
- },
- isModerator: function () {
- return Moderator.isModerator();
- },
- isSipGatewayEnabled: function () {
- return Moderator.isSipGatewayEnabled();
- },
- isExternalAuthEnabled: function () {
- return Moderator.isExternalAuthEnabled();
- },
- switchStreams: function (stream, oldStream, callback, isAudio) {
- if (connection && connection.jingle.activecall) {
- // FIXME: will block switchInProgress on true value in case of exception
- connection.jingle.activecall.switchStreams(stream, oldStream, callback, isAudio);
- } else {
- // We are done immediately
- console.warn("No conference handler or conference not started yet");
- callback();
- }
- },
- sendVideoInfoPresence: function (mute) {
- if(!connection)
- return;
- connection.emuc.addVideoInfoToPresence(mute);
- connection.emuc.sendPresence();
- },
- setVideoMute: function (mute, callback, options) {
- if(!connection)
- return;
- var self = this;
- var localCallback = function (mute) {
- self.sendVideoInfoPresence(mute);
- return callback(mute);
- };
-
- if(connection.jingle.activecall)
- {
- connection.jingle.activecall.setVideoMute(
- mute, localCallback, options);
- }
- else {
- localCallback(mute);
- }
-
- },
- setAudioMute: function (mute, callback) {
- if (!(connection && APP.RTC.localAudio)) {
- return false;
- }
-
-
- if (this.forceMuted && !mute) {
- console.info("Asking focus for unmute");
- connection.moderate.setMute(connection.emuc.myroomjid, mute);
- // FIXME: wait for result before resetting muted status
- this.forceMuted = false;
- }
-
- if (mute == APP.RTC.localAudio.isMuted()) {
- // Nothing to do
- return true;
- }
-
- // It is not clear what is the right way to handle multiple tracks.
- // So at least make sure that they are all muted or all unmuted and
- // that we send presence just once.
- APP.RTC.localAudio.setMute(!mute);
- // isMuted is the opposite of audioEnabled
- this.sendAudioInfoPresence(mute, callback);
- return true;
- },
- sendAudioInfoPresence: function(mute, callback)
- {
- if(connection) {
- connection.emuc.addAudioInfoToPresence(mute);
- connection.emuc.sendPresence();
- }
- callback();
- return true;
- },
- // Really mute video, i.e. dont even send black frames
- muteVideo: function (pc, unmute) {
- // FIXME: this probably needs another of those lovely state safeguards...
- // which checks for iceconn == connected and sigstate == stable
- pc.setRemoteDescription(pc.remoteDescription,
- function () {
- pc.createAnswer(
- function (answer) {
- var sdp = new SDP(answer.sdp);
- if (sdp.media.length > 1) {
- if (unmute)
- sdp.media[1] = sdp.media[1].replace('a=recvonly', 'a=sendrecv');
- else
- sdp.media[1] = sdp.media[1].replace('a=sendrecv', 'a=recvonly');
- sdp.raw = sdp.session + sdp.media.join('');
- answer.sdp = sdp.raw;
- }
- pc.setLocalDescription(answer,
- function () {
- console.log('mute SLD ok');
- },
- function (error) {
- console.log('mute SLD error');
- APP.UI.messageHandler.showError("dialog.error",
- "dialog.SLDFailure");
- }
- );
- },
- function (error) {
- console.log(error);
- APP.UI.messageHandler.showError();
- }
- );
- },
- function (error) {
- console.log('muteVideo SRD error');
- APP.UI.messageHandler.showError("dialog.error",
- "dialog.SRDFailure");
-
- }
- );
- },
- toggleRecording: function (tokenEmptyCallback,
- startingCallback, startedCallback) {
- Recording.toggleRecording(tokenEmptyCallback,
- startingCallback, startedCallback, connection);
- },
- addToPresence: function (name, value, dontSend) {
- switch (name)
- {
- case "displayName":
- connection.emuc.addDisplayNameToPresence(value);
- break;
- case "prezi":
- connection.emuc.addPreziToPresence(value, 0);
- break;
- case "preziSlide":
- connection.emuc.addCurrentSlideToPresence(value);
- break;
- case "connectionQuality":
- connection.emuc.addConnectionInfoToPresence(value);
- break;
- case "email":
- connection.emuc.addEmailToPresence(value);
- break;
- case "devices":
- connection.emuc.addDevicesToPresence(value);
- break;
- case "startMuted":
- if(!Moderator.isModerator())
- return;
- connection.emuc.addStartMutedToPresence(value[0],
- value[1]);
- break;
- default :
- console.log("Unknown tag for presence: " + name);
- return;
- }
- if (!dontSend)
- connection.emuc.sendPresence();
- },
- /**
- * Sends 'data' as a log message to the focus. Returns true iff a message
- * was sent.
- * @param data
- * @returns {boolean} true iff a message was sent.
- */
- sendLogs: function (data) {
- if(!connection.emuc.focusMucJid)
- return false;
-
- var deflate = true;
-
- var content = JSON.stringify(data);
- if (deflate) {
- content = String.fromCharCode.apply(null, Pako.deflateRaw(content));
- }
- content = Base64.encode(content);
- // XEP-0337-ish
- var message = $msg({to: connection.emuc.focusMucJid, type: 'normal'});
- message.c('log', { xmlns: 'urn:xmpp:eventlog',
- id: 'PeerConnectionStats'});
- message.c('message').t(content).up();
- if (deflate) {
- message.c('tag', {name: "deflated", value: "true"}).up();
- }
- message.up();
-
- connection.send(message);
- return true;
- },
- populateData: function () {
- var data = {};
- if (connection.jingle) {
- data = connection.jingle.populateData();
- }
- return data;
- },
- getLogger: function () {
- if(connection.logger)
- return connection.logger.log;
- return null;
- },
- getPrezi: function () {
- return connection.emuc.getPrezi(this.myJid());
- },
- removePreziFromPresence: function () {
- connection.emuc.removePreziFromPresence();
- connection.emuc.sendPresence();
- },
- sendChatMessage: function (message, nickname) {
- connection.emuc.sendMessage(message, nickname);
- },
- setSubject: function (topic) {
- connection.emuc.setSubject(topic);
- },
- lockRoom: function (key, onSuccess, onError, onNotSupported) {
- connection.emuc.lockRoom(key, onSuccess, onError, onNotSupported);
- },
- dial: function (to, from, roomName,roomPass) {
- connection.rayo.dial(to, from, roomName,roomPass);
- },
- setMute: function (jid, mute) {
- connection.moderate.setMute(jid, mute);
- },
- eject: function (jid) {
- connection.moderate.eject(jid);
- },
- logout: function (callback) {
- Moderator.logout(callback);
- },
- findJidFromResource: function (resource) {
- return connection.emuc.findJidFromResource(resource);
- },
- getMembers: function () {
- return connection.emuc.members;
- },
- getJidFromSSRC: function (ssrc) {
- if(!connection)
- return null;
- return connection.emuc.ssrc2jid[ssrc];
- },
- getMUCJoined: function () {
- return connection.emuc.joined;
- },
- getSessions: function () {
- return connection.jingle.sessions;
- },
- removeStream: function (stream) {
- if(!connection || !connection.jingle.activecall ||
- !connection.jingle.activecall.peerconnection)
- return;
- connection.jingle.activecall.peerconnection.removeStream(stream);
- }
-};
-
-module.exports = XMPP;
-
-},{"../../service/RTC/RTCEvents":102,"../../service/RTC/StreamEventTypes":104,"../../service/UI/UIEvents":105,"../../service/xmpp/XMPPEvents":111,"../settings/Settings":45,"./SDP":52,"./moderator":56,"./recording":57,"./strophe.emuc":58,"./strophe.jingle":59,"./strophe.logger":60,"./strophe.moderate":61,"./strophe.rayo":62,"./strophe.util":63,"events":112,"pako":67,"retry":83}],65:[function(require,module,exports){
+},{"../../service/RTC/RTCEvents":100,"../../service/RTC/StreamEventTypes":102,"../../service/UI/UIEvents":103,"../../service/xmpp/XMPPEvents":109,"../settings/Settings":47,"./SDP":54,"./moderator":58,"./recording":59,"./strophe.emuc":60,"./strophe.jingle":61,"./strophe.logger":62,"./strophe.moderate":63,"./strophe.rayo":64,"./strophe.util":65,"events":1,"pako":69,"retry":85}],67:[function(require,module,exports){
(function (process){
/*!
* async
@@ -17865,7 +18221,7 @@ module.exports = XMPP;
}());
}).call(this,require('_process'))
-},{"_process":113}],66:[function(require,module,exports){
+},{"_process":2}],68:[function(require,module,exports){
// i18next, v1.7.7
// Copyright (c)2014 Jan Mühlemann (jamuhl).
// Distributed under MIT license
@@ -19988,7 +20344,7 @@ module.exports = XMPP;
i18n.options = o;
})();
-},{"jquery":"jquery"}],67:[function(require,module,exports){
+},{"jquery":"jquery"}],69:[function(require,module,exports){
// Top level file is just a mixin of submodules & constants
'use strict';
@@ -20004,7 +20360,7 @@ assign(pako, deflate, inflate, constants);
module.exports = pako;
-},{"./lib/deflate":68,"./lib/inflate":69,"./lib/utils/common":70,"./lib/zlib/constants":73}],68:[function(require,module,exports){
+},{"./lib/deflate":70,"./lib/inflate":71,"./lib/utils/common":72,"./lib/zlib/constants":75}],70:[function(require,module,exports){
'use strict';
@@ -20382,7 +20738,7 @@ exports.deflate = deflate;
exports.deflateRaw = deflateRaw;
exports.gzip = gzip;
-},{"./utils/common":70,"./utils/strings":71,"./zlib/deflate.js":75,"./zlib/messages":80,"./zlib/zstream":82}],69:[function(require,module,exports){
+},{"./utils/common":72,"./utils/strings":73,"./zlib/deflate.js":77,"./zlib/messages":82,"./zlib/zstream":84}],71:[function(require,module,exports){
'use strict';
@@ -20763,7 +21119,7 @@ exports.inflate = inflate;
exports.inflateRaw = inflateRaw;
exports.ungzip = inflate;
-},{"./utils/common":70,"./utils/strings":71,"./zlib/constants":73,"./zlib/gzheader":76,"./zlib/inflate.js":78,"./zlib/messages":80,"./zlib/zstream":82}],70:[function(require,module,exports){
+},{"./utils/common":72,"./utils/strings":73,"./zlib/constants":75,"./zlib/gzheader":78,"./zlib/inflate.js":80,"./zlib/messages":82,"./zlib/zstream":84}],72:[function(require,module,exports){
'use strict';
@@ -20867,7 +21223,7 @@ exports.setTyped = function (on) {
exports.setTyped(TYPED_OK);
-},{}],71:[function(require,module,exports){
+},{}],73:[function(require,module,exports){
// String encode/decode helpers
'use strict';
@@ -21054,7 +21410,7 @@ exports.utf8border = function(buf, max) {
return (pos + _utf8len[buf[pos]] > max) ? pos : max;
};
-},{"./common":70}],72:[function(require,module,exports){
+},{"./common":72}],74:[function(require,module,exports){
'use strict';
// Note: adler32 takes 12% for level 0 and 2% for level 6.
@@ -21088,7 +21444,7 @@ function adler32(adler, buf, len, pos) {
module.exports = adler32;
-},{}],73:[function(require,module,exports){
+},{}],75:[function(require,module,exports){
module.exports = {
/* Allowed flush values; see deflate() and inflate() below for details */
@@ -21137,7 +21493,7 @@ module.exports = {
//Z_NULL: null // Use -1 or null inline, depending on var type
};
-},{}],74:[function(require,module,exports){
+},{}],76:[function(require,module,exports){
'use strict';
// Note: we can't get significant speed boost here.
@@ -21180,7 +21536,7 @@ function crc32(crc, buf, len, pos) {
module.exports = crc32;
-},{}],75:[function(require,module,exports){
+},{}],77:[function(require,module,exports){
'use strict';
var utils = require('../utils/common');
@@ -22947,7 +23303,7 @@ exports.deflatePrime = deflatePrime;
exports.deflateTune = deflateTune;
*/
-},{"../utils/common":70,"./adler32":72,"./crc32":74,"./messages":80,"./trees":81}],76:[function(require,module,exports){
+},{"../utils/common":72,"./adler32":74,"./crc32":76,"./messages":82,"./trees":83}],78:[function(require,module,exports){
'use strict';
@@ -22989,7 +23345,7 @@ function GZheader() {
module.exports = GZheader;
-},{}],77:[function(require,module,exports){
+},{}],79:[function(require,module,exports){
'use strict';
// See state defs from inflate.js
@@ -23316,7 +23672,7 @@ module.exports = function inflate_fast(strm, start) {
return;
};
-},{}],78:[function(require,module,exports){
+},{}],80:[function(require,module,exports){
'use strict';
@@ -24821,7 +25177,7 @@ exports.inflateSyncPoint = inflateSyncPoint;
exports.inflateUndermine = inflateUndermine;
*/
-},{"../utils/common":70,"./adler32":72,"./crc32":74,"./inffast":77,"./inftrees":79}],79:[function(require,module,exports){
+},{"../utils/common":72,"./adler32":74,"./crc32":76,"./inffast":79,"./inftrees":81}],81:[function(require,module,exports){
'use strict';
@@ -25150,7 +25506,7 @@ module.exports = function inflate_table(type, lens, lens_index, codes, table, ta
return 0;
};
-},{"../utils/common":70}],80:[function(require,module,exports){
+},{"../utils/common":72}],82:[function(require,module,exports){
'use strict';
module.exports = {
@@ -25165,7 +25521,7 @@ module.exports = {
'-6': 'incompatible version' /* Z_VERSION_ERROR (-6) */
};
-},{}],81:[function(require,module,exports){
+},{}],83:[function(require,module,exports){
'use strict';
@@ -26366,7 +26722,7 @@ exports._tr_flush_block = _tr_flush_block;
exports._tr_tally = _tr_tally;
exports._tr_align = _tr_align;
-},{"../utils/common":70}],82:[function(require,module,exports){
+},{"../utils/common":72}],84:[function(require,module,exports){
'use strict';
@@ -26397,9 +26753,9 @@ function ZStream() {
module.exports = ZStream;
-},{}],83:[function(require,module,exports){
+},{}],85:[function(require,module,exports){
module.exports = require('./lib/retry');
-},{"./lib/retry":84}],84:[function(require,module,exports){
+},{"./lib/retry":86}],86:[function(require,module,exports){
var RetryOperation = require('./retry_operation');
exports.operation = function(options) {
@@ -26450,7 +26806,7 @@ exports._createTimeout = function(attempt, opts) {
return timeout;
};
-},{"./retry_operation":85}],85:[function(require,module,exports){
+},{"./retry_operation":87}],87:[function(require,module,exports){
function RetryOperation(timeouts) {
this._timeouts = timeouts;
this._fn = null;
@@ -26560,7 +26916,7 @@ RetryOperation.prototype.mainError = function() {
return mainError;
};
-},{}],86:[function(require,module,exports){
+},{}],88:[function(require,module,exports){
module.exports = function arrayEquals(array) {
// if the other array is a falsy value, return
if (!array)
@@ -26586,10 +26942,10 @@ module.exports = function arrayEquals(array) {
}
-},{}],87:[function(require,module,exports){
+},{}],89:[function(require,module,exports){
exports.Interop = require('./interop');
-},{"./interop":88}],88:[function(require,module,exports){
+},{"./interop":90}],90:[function(require,module,exports){
"use strict";
var transform = require('./transform');
@@ -27171,7 +27527,7 @@ Interop.prototype.toUnifiedPlan = function(desc) {
//#endregion
};
-},{"./array-equals":86,"./transform":89}],89:[function(require,module,exports){
+},{"./array-equals":88,"./transform":91}],91:[function(require,module,exports){
var transform = require('sdp-transform');
exports.write = function(session, opts) {
@@ -27270,477 +27626,7 @@ exports.parse = function(sdp) {
};
-},{"sdp-transform":91}],90:[function(require,module,exports){
-var grammar = module.exports = {
- v: [{
- name: 'version',
- reg: /^(\d*)$/
- }],
- o: [{ //o=- 20518 0 IN IP4 203.0.113.1
- // NB: sessionId will be a String in most cases because it is huge
- name: 'origin',
- reg: /^(\S*) (\d*) (\d*) (\S*) IP(\d) (\S*)/,
- names: ['username', 'sessionId', 'sessionVersion', 'netType', 'ipVer', 'address'],
- format: "%s %s %d %s IP%d %s"
- }],
- // default parsing of these only (though some of these feel outdated)
- s: [{ name: 'name' }],
- i: [{ name: 'description' }],
- u: [{ name: 'uri' }],
- e: [{ name: 'email' }],
- p: [{ name: 'phone' }],
- z: [{ name: 'timezones' }], // TODO: this one can actually be parsed properly..
- r: [{ name: 'repeats' }], // TODO: this one can also be parsed properly
- //k: [{}], // outdated thing ignored
- t: [{ //t=0 0
- name: 'timing',
- reg: /^(\d*) (\d*)/,
- names: ['start', 'stop'],
- format: "%d %d"
- }],
- c: [{ //c=IN IP4 10.47.197.26
- name: 'connection',
- reg: /^IN IP(\d) (\S*)/,
- names: ['version', 'ip'],
- format: "IN IP%d %s"
- }],
- b: [{ //b=AS:4000
- push: 'bandwidth',
- reg: /^(TIAS|AS|CT|RR|RS):(\d*)/,
- names: ['type', 'limit'],
- format: "%s:%s"
- }],
- m: [{ //m=video 51744 RTP/AVP 126 97 98 34 31
- // NB: special - pushes to session
- // TODO: rtp/fmtp should be filtered by the payloads found here?
- reg: /^(\w*) (\d*) ([\w\/]*)(?: (.*))?/,
- names: ['type', 'port', 'protocol', 'payloads'],
- format: "%s %d %s %s"
- }],
- a: [
- { //a=rtpmap:110 opus/48000/2
- push: 'rtp',
- reg: /^rtpmap:(\d*) ([\w\-]*)\/(\d*)(?:\s*\/(\S*))?/,
- names: ['payload', 'codec', 'rate', 'encoding'],
- format: function (o) {
- return (o.encoding) ?
- "rtpmap:%d %s/%s/%s":
- "rtpmap:%d %s/%s";
- }
- },
- { //a=fmtp:108 profile-level-id=24;object=23;bitrate=64000
- push: 'fmtp',
- reg: /^fmtp:(\d*) (\S*)/,
- names: ['payload', 'config'],
- format: "fmtp:%d %s"
- },
- { //a=control:streamid=0
- name: 'control',
- reg: /^control:(.*)/,
- format: "control:%s"
- },
- { //a=rtcp:65179 IN IP4 193.84.77.194
- name: 'rtcp',
- reg: /^rtcp:(\d*)(?: (\S*) IP(\d) (\S*))?/,
- names: ['port', 'netType', 'ipVer', 'address'],
- format: function (o) {
- return (o.address != null) ?
- "rtcp:%d %s IP%d %s":
- "rtcp:%d";
- }
- },
- { //a=rtcp-fb:98 trr-int 100
- push: 'rtcpFbTrrInt',
- reg: /^rtcp-fb:(\*|\d*) trr-int (\d*)/,
- names: ['payload', 'value'],
- format: "rtcp-fb:%d trr-int %d"
- },
- { //a=rtcp-fb:98 nack rpsi
- push: 'rtcpFb',
- reg: /^rtcp-fb:(\*|\d*) ([\w-_]*)(?: ([\w-_]*))?/,
- names: ['payload', 'type', 'subtype'],
- format: function (o) {
- return (o.subtype != null) ?
- "rtcp-fb:%s %s %s":
- "rtcp-fb:%s %s";
- }
- },
- { //a=extmap:2 urn:ietf:params:rtp-hdrext:toffset
- //a=extmap:1/recvonly URI-gps-string
- push: 'ext',
- reg: /^extmap:([\w_\/]*) (\S*)(?: (\S*))?/,
- names: ['value', 'uri', 'config'], // value may include "/direction" suffix
- format: function (o) {
- return (o.config != null) ?
- "extmap:%s %s %s":
- "extmap:%s %s";
- }
- },
- {
- //a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:PS1uQCVeeCFCanVmcjkpPywjNWhcYD0mXXtxaVBR|2^20|1:32
- push: 'crypto',
- reg: /^crypto:(\d*) ([\w_]*) (\S*)(?: (\S*))?/,
- names: ['id', 'suite', 'config', 'sessionConfig'],
- format: function (o) {
- return (o.sessionConfig != null) ?
- "crypto:%d %s %s %s":
- "crypto:%d %s %s";
- }
- },
- { //a=setup:actpass
- name: 'setup',
- reg: /^setup:(\w*)/,
- format: "setup:%s"
- },
- { //a=mid:1
- name: 'mid',
- reg: /^mid:([^\s]*)/,
- format: "mid:%s"
- },
- { //a=msid:0c8b064d-d807-43b4-b434-f92a889d8587 98178685-d409-46e0-8e16-7ef0db0db64a
- name: 'msid',
- reg: /^msid:(.*)/,
- format: "msid:%s"
- },
- { //a=ptime:20
- name: 'ptime',
- reg: /^ptime:(\d*)/,
- format: "ptime:%d"
- },
- { //a=maxptime:60
- name: 'maxptime',
- reg: /^maxptime:(\d*)/,
- format: "maxptime:%d"
- },
- { //a=sendrecv
- name: 'direction',
- reg: /^(sendrecv|recvonly|sendonly|inactive)/
- },
- { //a=ice-lite
- name: 'icelite',
- reg: /^(ice-lite)/
- },
- { //a=ice-ufrag:F7gI
- name: 'iceUfrag',
- reg: /^ice-ufrag:(\S*)/,
- format: "ice-ufrag:%s"
- },
- { //a=ice-pwd:x9cml/YzichV2+XlhiMu8g
- name: 'icePwd',
- reg: /^ice-pwd:(\S*)/,
- format: "ice-pwd:%s"
- },
- { //a=fingerprint:SHA-1 00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33
- name: 'fingerprint',
- reg: /^fingerprint:(\S*) (\S*)/,
- names: ['type', 'hash'],
- format: "fingerprint:%s %s"
- },
- {
- //a=candidate:0 1 UDP 2113667327 203.0.113.1 54400 typ host
- //a=candidate:1162875081 1 udp 2113937151 192.168.34.75 60017 typ host generation 0
- //a=candidate:3289912957 2 udp 1845501695 193.84.77.194 60017 typ srflx raddr 192.168.34.75 rport 60017 generation 0
- push:'candidates',
- reg: /^candidate:(\S*) (\d*) (\S*) (\d*) (\S*) (\d*) typ (\S*)(?: raddr (\S*) rport (\d*))?(?: generation (\d*))?/,
- names: ['foundation', 'component', 'transport', 'priority', 'ip', 'port', 'type', 'raddr', 'rport', 'generation'],
- format: function (o) {
- var str = "candidate:%s %d %s %d %s %d typ %s";
- // NB: candidate has two optional chunks, so %void middle one if it's missing
- str += (o.raddr != null) ? " raddr %s rport %d" : "%v%v";
- if (o.generation != null) {
- str += " generation %d";
- }
- return str;
- }
- },
- { //a=end-of-candidates (keep after the candidates line for readability)
- name: 'endOfCandidates',
- reg: /^(end-of-candidates)/
- },
- { //a=remote-candidates:1 203.0.113.1 54400 2 203.0.113.1 54401 ...
- name: 'remoteCandidates',
- reg: /^remote-candidates:(.*)/,
- format: "remote-candidates:%s"
- },
- { //a=ice-options:google-ice
- name: 'iceOptions',
- reg: /^ice-options:(\S*)/,
- format: "ice-options:%s"
- },
- { //a=ssrc:2566107569 cname:t9YU8M1UxTF8Y1A1
- push: "ssrcs",
- reg: /^ssrc:(\d*) ([\w_]*):(.*)/,
- names: ['id', 'attribute', 'value'],
- format: "ssrc:%d %s:%s"
- },
- { //a=ssrc-group:FEC 1 2
- push: "ssrcGroups",
- reg: /^ssrc-group:(\w*) (.*)/,
- names: ['semantics', 'ssrcs'],
- format: "ssrc-group:%s %s"
- },
- { //a=msid-semantic: WMS Jvlam5X3SX1OP6pn20zWogvaKJz5Hjf9OnlV
- name: "msidSemantic",
- reg: /^msid-semantic:\s?(\w*) (\S*)/,
- names: ['semantic', 'token'],
- format: "msid-semantic: %s %s" // space after ":" is not accidental
- },
- { //a=group:BUNDLE audio video
- push: 'groups',
- reg: /^group:(\w*) (.*)/,
- names: ['type', 'mids'],
- format: "group:%s %s"
- },
- { //a=rtcp-mux
- name: 'rtcpMux',
- reg: /^(rtcp-mux)/
- },
- { //a=rtcp-rsize
- name: 'rtcpRsize',
- reg: /^(rtcp-rsize)/
- },
- { // any a= that we don't understand is kepts verbatim on media.invalid
- push: 'invalid',
- names: ["value"]
- }
- ]
-};
-
-// set sensible defaults to avoid polluting the grammar with boring details
-Object.keys(grammar).forEach(function (key) {
- var objs = grammar[key];
- objs.forEach(function (obj) {
- if (!obj.reg) {
- obj.reg = /(.*)/;
- }
- if (!obj.format) {
- obj.format = "%s";
- }
- });
-});
-
-},{}],91:[function(require,module,exports){
-var parser = require('./parser');
-var writer = require('./writer');
-
-exports.write = writer;
-exports.parse = parser.parse;
-exports.parseFmtpConfig = parser.parseFmtpConfig;
-exports.parsePayloads = parser.parsePayloads;
-exports.parseRemoteCandidates = parser.parseRemoteCandidates;
-
-},{"./parser":92,"./writer":93}],92:[function(require,module,exports){
-var toIntIfInt = function (v) {
- return String(Number(v)) === v ? Number(v) : v;
-};
-
-var attachProperties = function (match, location, names, rawName) {
- if (rawName && !names) {
- location[rawName] = toIntIfInt(match[1]);
- }
- else {
- for (var i = 0; i < names.length; i += 1) {
- if (match[i+1] != null) {
- location[names[i]] = toIntIfInt(match[i+1]);
- }
- }
- }
-};
-
-var parseReg = function (obj, location, content) {
- var needsBlank = obj.name && obj.names;
- if (obj.push && !location[obj.push]) {
- location[obj.push] = [];
- }
- else if (needsBlank && !location[obj.name]) {
- location[obj.name] = {};
- }
- var keyLocation = obj.push ?
- {} : // blank object that will be pushed
- needsBlank ? location[obj.name] : location; // otherwise, named location or root
-
- attachProperties(content.match(obj.reg), keyLocation, obj.names, obj.name);
-
- if (obj.push) {
- location[obj.push].push(keyLocation);
- }
-};
-
-var grammar = require('./grammar');
-var validLine = RegExp.prototype.test.bind(/^([a-z])=(.*)/);
-
-exports.parse = function (sdp) {
- var session = {}
- , media = []
- , location = session; // points at where properties go under (one of the above)
-
- // parse lines we understand
- sdp.split(/(\r\n|\r|\n)/).filter(validLine).forEach(function (l) {
- var type = l[0];
- var content = l.slice(2);
- if (type === 'm') {
- media.push({rtp: [], fmtp: []});
- location = media[media.length-1]; // point at latest media line
- }
-
- for (var j = 0; j < (grammar[type] || []).length; j += 1) {
- var obj = grammar[type][j];
- if (obj.reg.test(content)) {
- return parseReg(obj, location, content);
- }
- }
- });
-
- session.media = media; // link it up
- return session;
-};
-
-var fmtpReducer = function (acc, expr) {
- var s = expr.split('=');
- if (s.length === 2) {
- acc[s[0]] = toIntIfInt(s[1]);
- }
- return acc;
-};
-
-exports.parseFmtpConfig = function (str) {
- return str.split(';').reduce(fmtpReducer, {});
-};
-
-exports.parsePayloads = function (str) {
- return str.split(' ').map(Number);
-};
-
-exports.parseRemoteCandidates = function (str) {
- var candidates = [];
- var parts = str.split(' ').map(toIntIfInt);
- for (var i = 0; i < parts.length; i += 3) {
- candidates.push({
- component: parts[i],
- ip: parts[i + 1],
- port: parts[i + 2]
- });
- }
- return candidates;
-};
-
-},{"./grammar":90}],93:[function(require,module,exports){
-var grammar = require('./grammar');
-
-// customized util.format - discards excess arguments and can void middle ones
-var formatRegExp = /%[sdv%]/g;
-var format = function (formatStr) {
- var i = 1;
- var args = arguments;
- var len = args.length;
- return formatStr.replace(formatRegExp, function (x) {
- if (i >= len) {
- return x; // missing argument
- }
- var arg = args[i];
- i += 1;
- switch (x) {
- case '%%':
- return '%';
- case '%s':
- return String(arg);
- case '%d':
- return Number(arg);
- case '%v':
- return '';
- }
- });
- // NB: we discard excess arguments - they are typically undefined from makeLine
-};
-
-var makeLine = function (type, obj, location) {
- var str = obj.format instanceof Function ?
- (obj.format(obj.push ? location : location[obj.name])) :
- obj.format;
-
- var args = [type + '=' + str];
- if (obj.names) {
- for (var i = 0; i < obj.names.length; i += 1) {
- var n = obj.names[i];
- if (obj.name) {
- args.push(location[obj.name][n]);
- }
- else { // for mLine and push attributes
- args.push(location[obj.names[i]]);
- }
- }
- }
- else {
- args.push(location[obj.name]);
- }
- return format.apply(null, args);
-};
-
-// RFC specified order
-// TODO: extend this with all the rest
-var defaultOuterOrder = [
- 'v', 'o', 's', 'i',
- 'u', 'e', 'p', 'c',
- 'b', 't', 'r', 'z', 'a'
-];
-var defaultInnerOrder = ['i', 'c', 'b', 'a'];
-
-
-module.exports = function (session, opts) {
- opts = opts || {};
- // ensure certain properties exist
- if (session.version == null) {
- session.version = 0; // "v=0" must be there (only defined version atm)
- }
- if (session.name == null) {
- session.name = " "; // "s= " must be there if no meaningful name set
- }
- session.media.forEach(function (mLine) {
- if (mLine.payloads == null) {
- mLine.payloads = "";
- }
- });
-
- var outerOrder = opts.outerOrder || defaultOuterOrder;
- var innerOrder = opts.innerOrder || defaultInnerOrder;
- var sdp = [];
-
- // loop through outerOrder for matching properties on session
- outerOrder.forEach(function (type) {
- grammar[type].forEach(function (obj) {
- if (obj.name in session && session[obj.name] != null) {
- sdp.push(makeLine(type, obj, session));
- }
- else if (obj.push in session && session[obj.push] != null) {
- session[obj.push].forEach(function (el) {
- sdp.push(makeLine(type, obj, el));
- });
- }
- });
- });
-
- // then for each media line, follow the innerOrder
- session.media.forEach(function (mLine) {
- sdp.push(makeLine('m', grammar.m[0], mLine));
-
- innerOrder.forEach(function (type) {
- grammar[type].forEach(function (obj) {
- if (obj.name in mLine && mLine[obj.name] != null) {
- sdp.push(makeLine(type, obj, mLine));
- }
- else if (obj.push in mLine && mLine[obj.push] != null) {
- mLine[obj.push].forEach(function (el) {
- sdp.push(makeLine(type, obj, el));
- });
- }
- });
- });
- });
-
- return sdp.join('\r\n') + '\r\n';
-};
-
-},{"./grammar":90}],94:[function(require,module,exports){
+},{"sdp-transform":95}],92:[function(require,module,exports){
var transform = require('sdp-transform');
var transformUtils = require('./transform-utils');
var parseSsrcs = transformUtils.parseSsrcs;
@@ -28142,7 +28028,7 @@ Simulcast.prototype.mungeLocalDescription = function (desc) {
module.exports = Simulcast;
-},{"./transform-utils":95,"sdp-transform":97}],95:[function(require,module,exports){
+},{"./transform-utils":93,"sdp-transform":95}],93:[function(require,module,exports){
exports.writeSsrcs = function(sources, order) {
var ssrcs = [];
@@ -28193,573 +28079,673 @@ exports.parseSsrcs = function (mLine) {
};
-},{}],96:[function(require,module,exports){
-arguments[4][90][0].apply(exports,arguments)
-},{"dup":90}],97:[function(require,module,exports){
-arguments[4][91][0].apply(exports,arguments)
-},{"./parser":98,"./writer":99,"dup":91}],98:[function(require,module,exports){
-arguments[4][92][0].apply(exports,arguments)
-},{"./grammar":96,"dup":92}],99:[function(require,module,exports){
-arguments[4][93][0].apply(exports,arguments)
-},{"./grammar":96,"dup":93}],100:[function(require,module,exports){
-var MediaStreamType = {
- VIDEO_TYPE: "Video",
-
- AUDIO_TYPE: "Audio"
+},{}],94:[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";
+ }
+ });
+});
+
+},{}],95:[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":96,"./writer":97}],96:[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":94}],97:[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":94}],98:[function(require,module,exports){
+var MediaStreamType = {
+ VIDEO_TYPE: "Video",
+
+ AUDIO_TYPE: "Audio"
+};
module.exports = MediaStreamType;
-},{}],101:[function(require,module,exports){
-var RTCBrowserType = {
- RTC_BROWSER_CHROME: "rtc_browser.chrome",
-
- RTC_BROWSER_FIREFOX: "rtc_browser.firefox"
-};
-
+},{}],99:[function(require,module,exports){
+var RTCBrowserType = {
+ RTC_BROWSER_CHROME: "rtc_browser.chrome",
+
+ RTC_BROWSER_FIREFOX: "rtc_browser.firefox"
+};
+
module.exports = RTCBrowserType;
-},{}],102:[function(require,module,exports){
-var RTCEvents = {
- LASTN_CHANGED: "rtc.lastn_changed",
- DOMINANTSPEAKER_CHANGED: "rtc.dominantspeaker_changed",
- LASTN_ENDPOINT_CHANGED: "rtc.lastn_endpoint_changed",
- AVAILABLE_DEVICES_CHANGED: "rtc.available_devices_changed",
- AUDIO_MUTE: "rtc.audio_mute",
- VIDEO_MUTE: "rtc.video_mute"
-};
-
+},{}],100:[function(require,module,exports){
+var RTCEvents = {
+ LASTN_CHANGED: "rtc.lastn_changed",
+ DOMINANTSPEAKER_CHANGED: "rtc.dominantspeaker_changed",
+ LASTN_ENDPOINT_CHANGED: "rtc.lastn_endpoint_changed",
+ AVAILABLE_DEVICES_CHANGED: "rtc.available_devices_changed",
+ AUDIO_MUTE: "rtc.audio_mute",
+ VIDEO_MUTE: "rtc.video_mute"
+};
+
module.exports = RTCEvents;
-},{}],103:[function(require,module,exports){
-var Resolutions = {
- "1080": {
- width: 1920,
- height: 1080,
- order: 7
- },
- "fullhd": {
- width: 1920,
- height: 1080,
- order: 7
- },
- "720": {
- width: 1280,
- height: 720,
- order: 6
- },
- "hd": {
- width: 1280,
- height: 720,
- order: 6
- },
- "960": {
- width: 960,
- height: 720,
- order: 5
- },
- "640": {
- width: 640,
- height: 480,
- order: 4
- },
- "vga": {
- width: 640,
- height: 480,
- order: 4
- },
- "360": {
- width: 640,
- height: 360,
- order: 3
- },
- "320": {
- width: 320,
- height: 240,
- order: 2
- },
- "180": {
- width: 320,
- height: 180,
- order: 1
- }
-};
+},{}],101:[function(require,module,exports){
+var Resolutions = {
+ "1080": {
+ width: 1920,
+ height: 1080,
+ order: 7
+ },
+ "fullhd": {
+ width: 1920,
+ height: 1080,
+ order: 7
+ },
+ "720": {
+ width: 1280,
+ height: 720,
+ order: 6
+ },
+ "hd": {
+ width: 1280,
+ height: 720,
+ order: 6
+ },
+ "960": {
+ width: 960,
+ height: 720,
+ order: 5
+ },
+ "640": {
+ width: 640,
+ height: 480,
+ order: 4
+ },
+ "vga": {
+ width: 640,
+ height: 480,
+ order: 4
+ },
+ "360": {
+ width: 640,
+ height: 360,
+ order: 3
+ },
+ "320": {
+ width: 320,
+ height: 240,
+ order: 2
+ },
+ "180": {
+ width: 320,
+ height: 180,
+ order: 1
+ }
+};
module.exports = Resolutions;
-},{}],104:[function(require,module,exports){
-var StreamEventTypes = {
- EVENT_TYPE_LOCAL_CREATED: "stream.local_created",
-
- EVENT_TYPE_LOCAL_CHANGED: "stream.local_changed",
-
- EVENT_TYPE_LOCAL_ENDED: "stream.local_ended",
-
- EVENT_TYPE_REMOTE_CREATED: "stream.remote_created",
-
- EVENT_TYPE_REMOTE_ENDED: "stream.remote_ended",
-
- EVENT_TYPE_REMOTE_CHANGED: "stream.changed"
-};
-
+},{}],102:[function(require,module,exports){
+var StreamEventTypes = {
+ EVENT_TYPE_LOCAL_CREATED: "stream.local_created",
+
+ EVENT_TYPE_LOCAL_CHANGED: "stream.local_changed",
+
+ EVENT_TYPE_LOCAL_ENDED: "stream.local_ended",
+
+ EVENT_TYPE_REMOTE_CREATED: "stream.remote_created",
+
+ EVENT_TYPE_REMOTE_ENDED: "stream.remote_ended",
+
+ EVENT_TYPE_REMOTE_CHANGED: "stream.changed"
+};
+
module.exports = StreamEventTypes;
-},{}],105:[function(require,module,exports){
-var UIEvents = {
- NICKNAME_CHANGED: "UI.nickname_changed",
- SELECTED_ENDPOINT: "UI.selected_endpoint",
- PINNED_ENDPOINT: "UI.pinned_endpoint"
-};
+},{}],103:[function(require,module,exports){
+var UIEvents = {
+ NICKNAME_CHANGED: "UI.nickname_changed",
+ SELECTED_ENDPOINT: "UI.selected_endpoint",
+ PINNED_ENDPOINT: "UI.pinned_endpoint"
+};
module.exports = UIEvents;
-},{}],106:[function(require,module,exports){
-var AuthenticationEvents = {
- /**
- * Event callback arguments:
- * function(authenticationEnabled, userIdentity)
- * authenticationEnabled - indicates whether authentication has been enabled
- * in this session
- * userIdentity - if user has been logged in then it contains user name. If
- * contains 'null' or 'undefined' then user is not logged in.
- */
- IDENTITY_UPDATED: "authentication.identity_updated"
-};
-module.exports = AuthenticationEvents;
-
-},{}],107:[function(require,module,exports){
-var CQEvents = {
- LOCALSTATS_UPDATED: "cq.localstats_updated",
- REMOTESTATS_UPDATED: "cq.remotestats_updated",
- STOP: "cq.stop"
-};
+},{}],104:[function(require,module,exports){
+var AuthenticationEvents = {
+ /**
+ * Event callback arguments:
+ * function(authenticationEnabled, userIdentity)
+ * authenticationEnabled - indicates whether authentication has been enabled
+ * in this session
+ * userIdentity - if user has been logged in then it contains user name. If
+ * contains 'null' or 'undefined' then user is not logged in.
+ */
+ IDENTITY_UPDATED: "authentication.identity_updated"
+};
+module.exports = AuthenticationEvents;
+},{}],105:[function(require,module,exports){
+var CQEvents = {
+ LOCALSTATS_UPDATED: "cq.localstats_updated",
+ REMOTESTATS_UPDATED: "cq.remotestats_updated",
+ STOP: "cq.stop"
+};
+
module.exports = CQEvents;
-},{}],108:[function(require,module,exports){
-var DesktopSharingEventTypes = {
- INIT: "ds.init",
-
- SWITCHING_DONE: "ds.switching_done",
-
- NEW_STREAM_CREATED: "ds.new_stream_created"
-};
-
+},{}],106:[function(require,module,exports){
+var DesktopSharingEventTypes = {
+ INIT: "ds.init",
+
+ SWITCHING_DONE: "ds.switching_done",
+
+ NEW_STREAM_CREATED: "ds.new_stream_created"
+};
+
module.exports = DesktopSharingEventTypes;
+},{}],107:[function(require,module,exports){
+var Events = {
+ DTMF_SUPPORT_CHANGED: "members.dtmf_support_changed"
+};
+
+module.exports = Events;
+
+},{}],108:[function(require,module,exports){
+module.exports = {
+ getLanguages : function () {
+ var languages = [];
+ for(var lang in this)
+ {
+ if(typeof this[lang] === "string")
+ languages.push(this[lang]);
+ }
+ return languages;
+ },
+ EN: "en",
+ BG: "bg",
+ DE: "de",
+ TR: "tr",
+ FR: "fr"
+}
},{}],109:[function(require,module,exports){
-var Events = {
- DTMF_SUPPORT_CHANGED: "members.dtmf_support_changed"
-};
-
-module.exports = Events;
-
-},{}],110:[function(require,module,exports){
-module.exports = {
- getLanguages : function () {
- var languages = [];
- for(var lang in this)
- {
- if(typeof this[lang] === "string")
- languages.push(this[lang]);
- }
- return languages;
- },
- EN: "en",
- BG: "bg",
- DE: "de",
- TR: "tr",
- FR: "fr"
-}
-},{}],111:[function(require,module,exports){
-var XMPPEvents = {
- CONNECTION_FAILED: "xmpp.connection.failed",
- CONFERENCE_CREATED: "xmpp.conferenceCreated.jingle",
- CALL_INCOMING: "xmpp.callincoming.jingle",
- DISPOSE_CONFERENCE: "xmpp.dispose_conference",
- GRACEFUL_SHUTDOWN: "xmpp.graceful_shutdown",
- KICKED: "xmpp.kicked",
- BRIDGE_DOWN: "xmpp.bridge_down",
- USER_ID_CHANGED: "xmpp.user_id_changed",
- STREAMS_CHANGED: "xmpp.streams_changed",
- // We joined the MUC
- MUC_JOINED: "xmpp.muc_joined",
- // A member joined the MUC
- MUC_MEMBER_JOINED: "xmpp.muc_member_joined",
- // A member left the MUC
- MUC_MEMBER_LEFT: "xmpp.muc_member_left",
- MUC_ROLE_CHANGED: "xmpp.muc_role_changed",
- MUC_DESTROYED: "xmpp.muc_destroyed",
- DISPLAY_NAME_CHANGED: "xmpp.display_name_changed",
- REMOTE_STATS: "xmpp.remote_stats",
- LOCAL_ROLE_CHANGED: "xmpp.localrole_changed",
- PRESENCE_STATUS: "xmpp.presence_status",
- RESERVATION_ERROR: "xmpp.room_reservation_error",
- SUBJECT_CHANGED: "xmpp.subject_changed",
- MESSAGE_RECEIVED: "xmpp.message_received",
- SENDING_CHAT_MESSAGE: "xmpp.sending_chat_message",
- PASSWORD_REQUIRED: "xmpp.password_required",
- AUTHENTICATION_REQUIRED: "xmpp.authentication_required",
- CHAT_ERROR_RECEIVED: "xmpp.chat_error_received",
- ETHERPAD: "xmpp.etherpad",
- DEVICE_AVAILABLE: "xmpp.device_available",
- START_MUTED: "xmpp.start_muted",
- PEERCONNECTION_READY: "xmpp.peerconnection_ready",
- CONFERENCE_SETUP_FAILED: "xmpp.conference_setup_failed",
- AUDIO_MUTED: "xmpp.audio_muted",
- VIDEO_MUTED: "xmpp.video_muted"
-};
+var XMPPEvents = {
+ CONNECTION_FAILED: "xmpp.connection.failed",
+ CONFERENCE_CREATED: "xmpp.conferenceCreated.jingle",
+ CALL_INCOMING: "xmpp.callincoming.jingle",
+ DISPOSE_CONFERENCE: "xmpp.dispose_conference",
+ GRACEFUL_SHUTDOWN: "xmpp.graceful_shutdown",
+ KICKED: "xmpp.kicked",
+ BRIDGE_DOWN: "xmpp.bridge_down",
+ USER_ID_CHANGED: "xmpp.user_id_changed",
+ STREAMS_CHANGED: "xmpp.streams_changed",
+ // We joined the MUC
+ MUC_JOINED: "xmpp.muc_joined",
+ // A member joined the MUC
+ MUC_MEMBER_JOINED: "xmpp.muc_member_joined",
+ // A member left the MUC
+ MUC_MEMBER_LEFT: "xmpp.muc_member_left",
+ MUC_ROLE_CHANGED: "xmpp.muc_role_changed",
+ MUC_DESTROYED: "xmpp.muc_destroyed",
+ DISPLAY_NAME_CHANGED: "xmpp.display_name_changed",
+ REMOTE_STATS: "xmpp.remote_stats",
+ LOCAL_ROLE_CHANGED: "xmpp.localrole_changed",
+ PRESENCE_STATUS: "xmpp.presence_status",
+ RESERVATION_ERROR: "xmpp.room_reservation_error",
+ SUBJECT_CHANGED: "xmpp.subject_changed",
+ MESSAGE_RECEIVED: "xmpp.message_received",
+ SENDING_CHAT_MESSAGE: "xmpp.sending_chat_message",
+ PASSWORD_REQUIRED: "xmpp.password_required",
+ AUTHENTICATION_REQUIRED: "xmpp.authentication_required",
+ CHAT_ERROR_RECEIVED: "xmpp.chat_error_received",
+ ETHERPAD: "xmpp.etherpad",
+ DEVICE_AVAILABLE: "xmpp.device_available",
+ START_MUTED: "xmpp.start_muted",
+ PEERCONNECTION_READY: "xmpp.peerconnection_ready",
+ CONFERENCE_SETUP_FAILED: "xmpp.conference_setup_failed",
+ AUDIO_MUTED: "xmpp.audio_muted",
+ VIDEO_MUTED: "xmpp.video_muted"
+};
module.exports = XMPPEvents;
-},{}],112:[function(require,module,exports){
-// Copyright Joyent, Inc. and other Node contributors.
-//
-// Permission is hereby granted, free of charge, to any person obtaining a
-// copy of this software and associated documentation files (the
-// "Software"), to deal in the Software without restriction, including
-// without limitation the rights to use, copy, modify, merge, publish,
-// distribute, sublicense, and/or sell copies of the Software, and to permit
-// persons to whom the Software is furnished to do so, subject to the
-// following conditions:
-//
-// The above copyright notice and this permission notice shall be included
-// in all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
-// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
-// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
-// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
-// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
-// USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-function EventEmitter() {
- this._events = this._events || {};
- this._maxListeners = this._maxListeners || undefined;
-}
-module.exports = EventEmitter;
-
-// Backwards-compat with node 0.10.x
-EventEmitter.EventEmitter = EventEmitter;
-
-EventEmitter.prototype._events = undefined;
-EventEmitter.prototype._maxListeners = undefined;
-
-// By default EventEmitters will print a warning if more than 10 listeners are
-// added to it. This is a useful default which helps finding memory leaks.
-EventEmitter.defaultMaxListeners = 10;
-
-// Obviously not all Emitters should be limited to 10. This function allows
-// that to be increased. Set to zero for unlimited.
-EventEmitter.prototype.setMaxListeners = function(n) {
- if (!isNumber(n) || n < 0 || isNaN(n))
- throw TypeError('n must be a positive number');
- this._maxListeners = n;
- return this;
-};
-
-EventEmitter.prototype.emit = function(type) {
- var er, handler, len, args, i, listeners;
-
- if (!this._events)
- this._events = {};
-
- // If there is no 'error' event listener then throw.
- if (type === 'error') {
- if (!this._events.error ||
- (isObject(this._events.error) && !this._events.error.length)) {
- er = arguments[1];
- if (er instanceof Error) {
- throw er; // Unhandled 'error' event
- }
- throw TypeError('Uncaught, unspecified "error" event.');
- }
- }
-
- handler = this._events[type];
-
- if (isUndefined(handler))
- return false;
-
- if (isFunction(handler)) {
- switch (arguments.length) {
- // fast cases
- case 1:
- handler.call(this);
- break;
- case 2:
- handler.call(this, arguments[1]);
- break;
- case 3:
- handler.call(this, arguments[1], arguments[2]);
- break;
- // slower
- default:
- len = arguments.length;
- args = new Array(len - 1);
- for (i = 1; i < len; i++)
- args[i - 1] = arguments[i];
- handler.apply(this, args);
- }
- } else if (isObject(handler)) {
- len = arguments.length;
- args = new Array(len - 1);
- for (i = 1; i < len; i++)
- args[i - 1] = arguments[i];
-
- listeners = handler.slice();
- len = listeners.length;
- for (i = 0; i < len; i++)
- listeners[i].apply(this, args);
- }
-
- return true;
-};
-
-EventEmitter.prototype.addListener = function(type, listener) {
- var m;
-
- if (!isFunction(listener))
- throw TypeError('listener must be a function');
-
- if (!this._events)
- this._events = {};
-
- // To avoid recursion in the case that type === "newListener"! Before
- // adding it to the listeners, first emit "newListener".
- if (this._events.newListener)
- this.emit('newListener', type,
- isFunction(listener.listener) ?
- listener.listener : listener);
-
- if (!this._events[type])
- // Optimize the case of one listener. Don't need the extra array object.
- this._events[type] = listener;
- else if (isObject(this._events[type]))
- // If we've already got an array, just append.
- this._events[type].push(listener);
- else
- // Adding the second element, need to change to array.
- this._events[type] = [this._events[type], listener];
-
- // Check for listener leak
- if (isObject(this._events[type]) && !this._events[type].warned) {
- var m;
- if (!isUndefined(this._maxListeners)) {
- m = this._maxListeners;
- } else {
- m = EventEmitter.defaultMaxListeners;
- }
-
- if (m && m > 0 && this._events[type].length > m) {
- this._events[type].warned = true;
- console.error('(node) warning: possible EventEmitter memory ' +
- 'leak detected. %d listeners added. ' +
- 'Use emitter.setMaxListeners() to increase limit.',
- this._events[type].length);
- if (typeof console.trace === 'function') {
- // not supported in IE 10
- console.trace();
- }
- }
- }
-
- return this;
-};
-
-EventEmitter.prototype.on = EventEmitter.prototype.addListener;
-
-EventEmitter.prototype.once = function(type, listener) {
- if (!isFunction(listener))
- throw TypeError('listener must be a function');
-
- var fired = false;
-
- function g() {
- this.removeListener(type, g);
-
- if (!fired) {
- fired = true;
- listener.apply(this, arguments);
- }
- }
-
- g.listener = listener;
- this.on(type, g);
-
- return this;
-};
-
-// emits a 'removeListener' event iff the listener was removed
-EventEmitter.prototype.removeListener = function(type, listener) {
- var list, position, length, i;
-
- if (!isFunction(listener))
- throw TypeError('listener must be a function');
-
- if (!this._events || !this._events[type])
- return this;
-
- list = this._events[type];
- length = list.length;
- position = -1;
-
- if (list === listener ||
- (isFunction(list.listener) && list.listener === listener)) {
- delete this._events[type];
- if (this._events.removeListener)
- this.emit('removeListener', type, listener);
-
- } else if (isObject(list)) {
- for (i = length; i-- > 0;) {
- if (list[i] === listener ||
- (list[i].listener && list[i].listener === listener)) {
- position = i;
- break;
- }
- }
-
- if (position < 0)
- return this;
-
- if (list.length === 1) {
- list.length = 0;
- delete this._events[type];
- } else {
- list.splice(position, 1);
- }
-
- if (this._events.removeListener)
- this.emit('removeListener', type, listener);
- }
-
- return this;
-};
-
-EventEmitter.prototype.removeAllListeners = function(type) {
- var key, listeners;
-
- if (!this._events)
- return this;
-
- // not listening for removeListener, no need to emit
- if (!this._events.removeListener) {
- if (arguments.length === 0)
- this._events = {};
- else if (this._events[type])
- delete this._events[type];
- return this;
- }
-
- // emit removeListener for all listeners on all events
- if (arguments.length === 0) {
- for (key in this._events) {
- if (key === 'removeListener') continue;
- this.removeAllListeners(key);
- }
- this.removeAllListeners('removeListener');
- this._events = {};
- return this;
- }
-
- listeners = this._events[type];
-
- if (isFunction(listeners)) {
- this.removeListener(type, listeners);
- } else {
- // LIFO order
- while (listeners.length)
- this.removeListener(type, listeners[listeners.length - 1]);
- }
- delete this._events[type];
-
- return this;
-};
-
-EventEmitter.prototype.listeners = function(type) {
- var ret;
- if (!this._events || !this._events[type])
- ret = [];
- else if (isFunction(this._events[type]))
- ret = [this._events[type]];
- else
- ret = this._events[type].slice();
- return ret;
-};
-
-EventEmitter.listenerCount = function(emitter, type) {
- var ret;
- if (!emitter._events || !emitter._events[type])
- ret = 0;
- else if (isFunction(emitter._events[type]))
- ret = 1;
- else
- ret = emitter._events[type].length;
- return ret;
-};
-
-function isFunction(arg) {
- return typeof arg === 'function';
-}
-
-function isNumber(arg) {
- return typeof arg === 'number';
-}
-
-function isObject(arg) {
- return typeof arg === 'object' && arg !== null;
-}
-
-function isUndefined(arg) {
- return arg === void 0;
-}
-
-},{}],113:[function(require,module,exports){
-// shim for using process in browser
-
-var process = module.exports = {};
-var queue = [];
-var draining = false;
-
-function drainQueue() {
- if (draining) {
- return;
- }
- draining = true;
- var currentQueue;
- var len = queue.length;
- while(len) {
- currentQueue = queue;
- queue = [];
- var i = -1;
- while (++i < len) {
- currentQueue[i]();
- }
- len = queue.length;
- }
- draining = false;
-}
-process.nextTick = function (fun) {
- queue.push(fun);
- if (!draining) {
- setTimeout(drainQueue, 0);
- }
-};
-
-process.title = 'browser';
-process.browser = true;
-process.env = {};
-process.argv = [];
-process.version = ''; // empty string to avoid regexp issues
-
-function noop() {}
-
-process.on = noop;
-process.addListener = noop;
-process.once = noop;
-process.off = noop;
-process.removeListener = noop;
-process.removeAllListeners = noop;
-process.emit = noop;
-
-process.binding = function (name) {
- throw new Error('process.binding is not supported');
-};
-
-// TODO(shtylman)
-process.cwd = function () { return '/' };
-process.chdir = function (dir) {
- throw new Error('process.chdir is not supported');
-};
-process.umask = function() { return 0; };
-
-},{}]},{},[1])(1)
+},{}]},{},[3])(3)
});
\ No newline at end of file