From 4fbc37b3452b00c377738c0aca2bdbda4447459a Mon Sep 17 00:00:00 2001 From: yanas Date: Sun, 13 Jul 2014 19:30:14 +0200 Subject: [PATCH 01/11] Restores invite participants email functionality. Adds the password to the invitation email, for password protected conferences. --- toolbar.js | 81 ++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 66 insertions(+), 15 deletions(-) diff --git a/toolbar.js b/toolbar.js index 5d078c2609..116b55adba 100644 --- a/toolbar.js +++ b/toolbar.js @@ -5,7 +5,7 @@ var Toolbar = (function (my) { /** * Opens the lock room dialog. */ - my.openLockDialog = function() { + my.openLockDialog = function () { // Only the focus is able to set a shared key. if (focus === null) { if (sharedKey) @@ -54,7 +54,7 @@ var Toolbar = (function (my) { submit: function (e, v, m, f) { if (v) { var lockKey = document.getElementById('lockKey'); - + if (lockKey.value) { setSharedKey(Util.escapeHtml(lockKey.value)); lockRoom(true); @@ -70,24 +70,75 @@ var Toolbar = (function (my) { /** * Opens the invite link dialog. */ - my.openLinkDialog = function() { + my.openLinkDialog = function () { + var inviteLink; + if (roomUrl == null) + inviteLink = "Your conference is currently being created..."; + else + inviteLink = encodeURI(roomUrl); + $.prompt('', - { - title: "Share this link with everyone you want to invite", - persistent: false, - buttons: { "Cancel": false}, - loaded: function (event) { - document.getElementById('inviteLinkRef').select(); + inviteLink + '" onclick="this.select();" readonly>', + { + title: "Share this link with everyone you want to invite", + persistent: false, + buttons: { "Invite": true, "Cancel": false}, + defaultButton: 1, + loaded: function (event) { + if (roomUrl) + document.getElementById('inviteLinkRef').select(); + else + document.getElementById('jqi_state0_buttonInvite') + .disabled = true; + }, + submit: function (e, v, m, f) { + if (v) { + if (roomUrl) { + inviteParticipants(); + } + } + } } - } - ); + ); }; + /** + * Invite participants to conference. + */ + function inviteParticipants() { + if (roomUrl == null) + return; + + var sharedKeyText = ""; + if (sharedKey && sharedKey.length > 0) + sharedKeyText + = "This conference is password protected. Please use the " + + "following pin when joining:%0D%0A%0D%0A" + + sharedKey + "%0D%0A%0D%0A"; + + var conferenceName = roomUrl.substring(roomUrl.lastIndexOf('/') + 1); + var subject = "Invitation to a Jitsi Meet (" + conferenceName + ")"; + var body = "Hey there, I%27d like to invite you to a Jitsi Meet" + + " conference I%27ve just set up.%0D%0A%0D%0A" + + "Please click on the following link in order" + + " to join the conference.%0D%0A%0D%0A" + + roomUrl + "%0D%0A%0D%0A" + + sharedKeyText + + "Note that Jitsi Meet is currently only supported by Chromim," + + " Google Chrome and Opera, so you need" + + " to be using one of these browsers.%0D%0A%0D%0A" + + "Talk to you in a sec!"; + + if (window.localStorage.displayname) + body += "%0D%0A%0D%0A" + window.localStorage.displayname; + + window.open("mailto:?subject=" + subject + "&body=" + body, '_blank'); + } + /** * Opens the settings dialog. */ - my.openSettingsDialog = function() { + my.openSettingsDialog = function () { $.prompt('

Configure your conference

' + ' Participants join muted
' + ' Require nicknames

' + @@ -104,13 +155,13 @@ var Toolbar = (function (my) { 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); From a89555b4b64bf35698b6265fa24ad95bf044cf4e Mon Sep 17 00:00:00 2001 From: yanas Date: Sun, 13 Jul 2014 20:12:38 +0200 Subject: [PATCH 02/11] Makes chat message notifications more visible. --- chat.js | 8 +++++--- css/main.css | 23 ++++++++++++++++------- toolbar.js | 7 +++++-- 3 files changed, 26 insertions(+), 12 deletions(-) diff --git a/chat.js b/chat.js index b7255a346c..d85bd7752e 100644 --- a/chat.js +++ b/chat.js @@ -135,8 +135,7 @@ var Chat = (function (my) { { $("#subject").css({display: "block"}); } - } - + }; /** * Opens / closes the chat area. @@ -187,6 +186,9 @@ var Chat = (function (my) { duration: 500}); } else { + // Undock the toolbar when the chat is shown. + Toolbar.dockToolbar(false); + videospace.animate({right: chatSize[0], width: videospaceWidth, height: videospaceHeight}, @@ -290,7 +292,7 @@ var Chat = (function (my) { if (unreadMessages) { unreadMsgElement.innerHTML = unreadMessages.toString(); - Toolbar.showToolbar(); + Toolbar.dockToolbar(true); var chatButtonElement = document.getElementById('chatButton').parentNode; diff --git a/css/main.css b/css/main.css index eb9cc650d3..e0a1726e91 100644 --- a/css/main.css +++ b/css/main.css @@ -131,15 +131,24 @@ html, body{ } #chatButton { - -webkit-transition: all .5s ease-in-out;; - -moz-transition: all .5s ease-in-out;; - transition: all .5s ease-in-out;; + -webkit-transition: all .5s ease-in-out; + -moz-transition: all .5s ease-in-out; + transition: all .5s ease-in-out; } - +/*#ffde00*/ #chatButton.active { - -webkit-text-shadow: 0 0 10px #ffffff; - -moz-text-shadow: 0 0 10px #ffffff; - text-shadow: 0 0 10px #ffffff; + -webkit-text-shadow: -1px 0 10px #00ccff, + 0 1px 10px #00ccff, + 1px 0 10px #00ccff, + 0 -1px 10px #00ccff; + -moz-text-shadow: 1px 0 10px #00ccff, + 0 1px 10px #00ccff, + 1px 0 10px #00ccff, + 0 -1px 10px #00ccff; + text-shadow: -1px 0 10px #00ccff, + 0 1px 10px #00ccff, + 1px 0 10px #00ccff, + 0 -1px 10px #00ccff; } a.button:hover { diff --git a/toolbar.js b/toolbar.js index 116b55adba..cdaaef2397 100644 --- a/toolbar.js +++ b/toolbar.js @@ -236,9 +236,12 @@ var Toolbar = (function (my) { if (!$('#header').is(':visible')) { Toolbar.showToolbar(); } + // Then clear the time out, to dock the toolbar. - clearTimeout(toolbarTimeout); - toolbarTimeout = null; + if (toolbarTimeout) { + clearTimeout(toolbarTimeout); + toolbarTimeout = null; + } } else { if (!$('#header').is(':visible')) { From 5ac83ec088eeff4d38f11a0bb60d9c4d95d57d5e Mon Sep 17 00:00:00 2001 From: paweldomas Date: Mon, 14 Jul 2014 09:28:22 +0200 Subject: [PATCH 03/11] Fixes issues when given participant does not support all media types. --- libs/colibri/colibri.focus.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/libs/colibri/colibri.focus.js b/libs/colibri/colibri.focus.js index e0e6cf2c5a..6c53a29d29 100644 --- a/libs/colibri/colibri.focus.js +++ b/libs/colibri/colibri.focus.js @@ -540,7 +540,8 @@ ColibriFocus.prototype.initiate = function (peer, isInitiator) { if (1) { //i > 0) { // not for audio FIXME: does not work as intended // re-add all remote a=ssrcs for (var jid in this.remotessrc) { - if (jid == peer) continue; + if (jid == peer || !this.remotessrc[jid][i]) + continue; sdp.media[i] += this.remotessrc[jid][i]; } // and local a=ssrc lines @@ -714,6 +715,9 @@ ColibriFocus.prototype.updateChannel = function (remoteSDP, participant) { change.c('conference', {xmlns: 'http://jitsi.org/protocol/colibri', id: this.confid}); for (channel = 0; channel < this.channels[participant].length; channel++) { + if (!remoteSDP.media[channel]) + continue; + var name = SDPUtil.parse_mid(SDPUtil.find_line(remoteSDP.media[channel], 'a=mid:')); change.c('content', {name: name}); if (name !== 'data') @@ -894,6 +898,9 @@ ColibriFocus.prototype.setRemoteDescription = function (session, elem, desctype) this.remotessrc[session.peerjid] = []; for (channel = 0; channel < this.channels[participant].length; channel++) { //if (channel == 0) continue; FIXME: does not work as intended + if (!remoteSDP.media[channel]) + continue; + if (SDPUtil.find_lines(remoteSDP.media[channel], 'a=ssrc:').length) { this.remotessrc[session.peerjid][channel] = @@ -905,6 +912,9 @@ ColibriFocus.prototype.setRemoteDescription = function (session, elem, desctype) // ACT 4: add new a=ssrc lines to local remotedescription for (channel = 0; channel < this.channels[participant].length; channel++) { //if (channel == 0) continue; FIXME: does not work as intended + if (!remoteSDP.media[channel]) + continue; + if (SDPUtil.find_lines(remoteSDP.media[channel], 'a=ssrc:').length) { this.peerconnection.enqueueAddSsrc( channel, From 3a87900bdfa981dc767549fb1a509bb48d97334b Mon Sep 17 00:00:00 2001 From: paweldomas Date: Mon, 14 Jul 2014 11:19:28 +0200 Subject: [PATCH 04/11] Adds googLeakyBucket flag to screen sharing mandatory constraints. --- libs/strophe/strophe.jingle.adapter.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libs/strophe/strophe.jingle.adapter.js b/libs/strophe/strophe.jingle.adapter.js index 2e8e1e2090..1db9c6fdd5 100644 --- a/libs/strophe/strophe.jingle.adapter.js +++ b/libs/strophe/strophe.jingle.adapter.js @@ -519,6 +519,7 @@ function getUserMediaWithConstraints(um, success_callback, failure_callback, res constraints.video = { mandatory: { chromeMediaSource: "screen", + googLeakyBucket: true, maxWidth: window.screen.width, maxHeight: window.screen.height, maxFrameRate: 3 @@ -530,6 +531,7 @@ function getUserMediaWithConstraints(um, success_callback, failure_callback, res mandatory: { chromeMediaSource: "desktop", chromeMediaSourceId: desktopStream, + googLeakyBucket: true, maxWidth: window.screen.width, maxHeight: window.screen.height, maxFrameRate: 3 From 66a64b6b787860b9f0a1822cf408151b175f04c4 Mon Sep 17 00:00:00 2001 From: yanas Date: Mon, 14 Jul 2014 12:33:57 +0200 Subject: [PATCH 05/11] Fixes thumbnails wrap after opening chat window. Closes issue #89. --- chat.js | 25 +++++++++++++++++++++++-- videolayout.js | 12 ++++++++---- 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/chat.js b/chat.js index d85bd7752e..051829dac3 100644 --- a/chat.js +++ b/chat.js @@ -115,8 +115,7 @@ var Chat = (function (my) { + ''); $('#chatconversation').animate( { scrollTop: $('#chatconversation')[0].scrollHeight}, 1000); - - } + }; /** * Sets the subject to the UI @@ -158,6 +157,10 @@ var Chat = (function (my) { var horizontalIndent = videoPosition[0]; var verticalIndent = videoPosition[1]; + var thumbnailSize = VideoLayout.calculateThumbnailSize(videospaceWidth); + var thumbnailsWidth = thumbnailSize[0]; + var thumbnailsHeight = thumbnailSize[1]; + if (chatspace.is(":visible")) { videospace.animate({right: chatSize[0], width: videospaceWidth, @@ -165,6 +168,15 @@ var Chat = (function (my) { {queue: false, duration: 500}); + $('#remoteVideos').animate({height: thumbnailsHeight}, + {queue: false, + duration: 500}); + + $('#remoteVideos>span').animate({height: thumbnailsHeight, + width: thumbnailsWidth}, + {queue: false, + duration: 500}); + $('#largeVideoContainer').animate({ width: videospaceWidth, height: videospaceHeight}, {queue: false, @@ -200,6 +212,15 @@ var Chat = (function (my) { } }); + $('#remoteVideos').animate({height: thumbnailsHeight}, + {queue: false, + duration: 500}); + + $('#remoteVideos>span').animate({height: thumbnailsHeight, + width: thumbnailsWidth}, + {queue: false, + duration: 500}); + $('#largeVideoContainer').animate({ width: videospaceWidth, height: videospaceHeight}, {queue: false, diff --git a/videolayout.js b/videolayout.js index fb25816ba3..7178c31901 100644 --- a/videolayout.js +++ b/videolayout.js @@ -570,7 +570,9 @@ var VideoLayout = (function (my) { * Resizes thumbnails. */ my.resizeThumbnails = function() { - var thumbnailSize = calculateThumbnailSize(); + var videoSpaceWidth = $('#remoteVideos').width(); + + var thumbnailSize = VideoLayout.calculateThumbnailSize(videoSpaceWidth); var width = thumbnailSize[0]; var height = thumbnailSize[1]; @@ -682,7 +684,7 @@ var VideoLayout = (function (my) { /** * Calculates the thumbnail size. */ - var calculateThumbnailSize = function () { + 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 @@ -691,8 +693,10 @@ var VideoLayout = (function (my) { var numvids = $('#remoteVideos>span:visible').length; - // Remove the 1px borders arround videos and the chat width. - var availableWinWidth = $('#remoteVideos').width() - 2 * numvids - 50; + // Remove the 3px borders arround videos and border around the remote + // videos area + var availableWinWidth = videoSpaceWidth - 2 * 3 * numvids - 50; + var availableWidth = availableWinWidth / numvids; var aspectRatio = 16.0 / 9.0; var maxHeight = Math.min(160, availableHeight); From 7c93a87a3e5be60156f4345fec90ef6427e100df Mon Sep 17 00:00:00 2001 From: yanas Date: Mon, 14 Jul 2014 12:58:58 +0200 Subject: [PATCH 06/11] Updates versions. --- index.html | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/index.html b/index.html index cb519c2508..5f2eca8725 100644 --- a/index.html +++ b/index.html @@ -27,9 +27,9 @@ - + - + @@ -39,11 +39,11 @@ - - + + - + From c32ad97c0a8bdc7057bf33de96eb0af6bef5764d Mon Sep 17 00:00:00 2001 From: yanas Date: Mon, 14 Jul 2014 13:54:26 +0200 Subject: [PATCH 07/11] Fixes focus indicator tooltip showing when we're not focus. --- index.html | 4 ++-- videolayout.js | 8 +++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/index.html b/index.html index 5f2eca8725..c197e091d7 100644 --- a/index.html +++ b/index.html @@ -39,7 +39,7 @@ - + @@ -123,7 +123,7 @@ - + diff --git a/videolayout.js b/videolayout.js index 7178c31901..8cb2437250 100644 --- a/videolayout.js +++ b/videolayout.js @@ -477,9 +477,7 @@ var VideoLayout = (function (my) { if (!indicatorSpan || indicatorSpan.length === 0) { indicatorSpan = document.createElement('span'); indicatorSpan.className = 'focusindicator'; - Util.setTooltip(indicatorSpan, - "The owner of
this conference", - "top"); + focusContainer.appendChild(indicatorSpan); createFocusIndicatorElement(indicatorSpan); @@ -772,6 +770,10 @@ var VideoLayout = (function (my) { var focusIndicator = document.createElement('i'); focusIndicator.className = 'fa fa-star'; parentElement.appendChild(focusIndicator); + + Util.setTooltip(parentElement, + "The owner of
this conference", + "top"); } /** From 9d24910f82614692298519437773604c1233986f Mon Sep 17 00:00:00 2001 From: George Politis Date: Thu, 3 Jul 2014 16:35:47 +0200 Subject: [PATCH 08/11] Adds the method focus.setRTCPTerminationStrategy() --- libs/colibri/colibri.focus.js | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/libs/colibri/colibri.focus.js b/libs/colibri/colibri.focus.js index 6c53a29d29..169db05fa6 100644 --- a/libs/colibri/colibri.focus.js +++ b/libs/colibri/colibri.focus.js @@ -1163,3 +1163,27 @@ ColibriFocus.prototype.sendTerminate = function (session, reason, text) { this.statsinterval = null; } }; + +ColibriFocus.prototype.setRTCPTerminationStrategy = function (strategyFQN) { + var self = this; + var strategyIQ = $iq({to: this.bridgejid, type: 'set'}); + strategyIQ.c('conference', { + xmlns: 'http://jitsi.org/protocol/colibri', + id: this.confid, + }); + + strategyIQ.c('rtcp-termination-strategy', {name: strategyFQN }); + + strategyIQ.c('content', {name: "video"}); + strategyIQ.up(); // end of content + + console.log('setting RTCP termination strategy', strategyFQN); + this.connection.sendIQ(strategyIQ, + function (res) { + console.log('got result'); + }, + function (err) { + console.error('got error', err); + } + ); + From 69508d77348b6232f27f1e3d3e01179fdfd304f8 Mon Sep 17 00:00:00 2001 From: Lyubomir Marinov Date: Wed, 16 Jul 2014 01:36:51 +0300 Subject: [PATCH 09/11] Fixes a syntax error. Adds the ability to define a default value for the channel attribute last-n. Parses JSON messages from Videobridge received on the data channel. Fixes unnecessary changing of the value of the channel attribute expire from 15 to 60. --- config.js | 3 +- data_channels.js | 38 ++++++++++++--- libs/colibri/colibri.focus.js | 90 +++++++++++++++++++++-------------- 3 files changed, 87 insertions(+), 44 deletions(-) diff --git a/config.js b/config.js index 90bf9cf856..b68914179f 100644 --- a/config.js +++ b/config.js @@ -13,6 +13,7 @@ var config = { chromeExtensionId: 'diibjkoicjeejcmhdnailmkgecihlobk', // Id of desktop streamer Chrome extension minChromeExtVersion: '0.1', // Required version of Chrome extension enableRtpStats: false, // Enables RTP stats processing - openSctp: true, //Toggle to enable/disable SCTP channels + openSctp: true, // Toggle to enable/disable SCTP channels +// channelLastN: -1, // The default value of the channel attribute last-n. enableRecording: false }; diff --git a/data_channels.js b/data_channels.js index 7a1ae59e37..06f0b5e848 100644 --- a/data_channels.js +++ b/data_channels.js @@ -10,7 +10,7 @@ function onDataChannel(event) dataChannel.onopen = function () { - console.info("Data channel opened by the bridge !!!", dataChannel); + console.info("Data channel opened by the Videobridge!", dataChannel); // Code sample for sending string and/or binary data // Sends String message to the bridge @@ -26,19 +26,42 @@ function onDataChannel(event) dataChannel.onmessage = function (event) { - var msgData = event.data; - console.info("Got Data Channel Message:", msgData, dataChannel); + var data = event.data; + + console.info("Got Data Channel Message:", data, dataChannel); // Active speaker event - if (msgData.indexOf('activeSpeaker') === 0) + if (data.indexOf('activeSpeaker') === 0) { - // Endpoint ID from the bridge - var resourceJid = msgData.split(":")[1]; + // Endpoint ID from the Videobridge. + var resourceJid = data.split(":")[1]; console.info( "Data channel new active speaker event: " + resourceJid); $(document).trigger('activespeakerchanged', [resourceJid]); } + else + { + // JSON + var obj; + + try + { + obj = JSON.parse(data); + } + catch (e) + { + console.error( + "Failed to parse data channel message as JSON: ", + data, + dataChannel); + } + if (('undefined' !== typeof(obj)) && (null !== obj)) + { + // TODO Consume the JSON-formatted data channel message. + console.debug("Data channel JSON-formatted message: ", obj); + } + } }; dataChannel.onclose = function () @@ -77,4 +100,5 @@ function bindDataChannelListener(peerConnection) var msgData = event.data; console.info("Got My Data Channel Message:", msgData, dataChannel); };*/ -} \ No newline at end of file +} + diff --git a/libs/colibri/colibri.focus.js b/libs/colibri/colibri.focus.js index 169db05fa6..688642f743 100644 --- a/libs/colibri/colibri.focus.js +++ b/libs/colibri/colibri.focus.js @@ -54,17 +54,16 @@ function ColibriFocus(connection, bridgejid) { * Default channel expire value in seconds. * @type {number} */ - this.channelExpire = 60; + this.channelExpire + = ('number' === typeof(config.channelExpire)) + ? config.channelExpire + : 15; // media types of the conference if (config.openSctp) - { this.media = ['audio', 'video', 'data']; - } else - { this.media = ['audio', 'video']; - } this.connection.jingle.sessions[this.sid] = this; this.mychannel = []; @@ -202,29 +201,38 @@ ColibriFocus.prototype._makeConference = function () { elem.c('conference', {xmlns: 'http://jitsi.org/protocol/colibri'}); this.media.forEach(function (name) { - var isData = name === 'data'; - var channel = isData ? 'sctpconnection' : 'channel'; + var elemName; + var elemAttrs = { initiator: 'true', expire: self.channelExpire }; + + if ('data' === name) + { + elemName = 'sctpconnection'; + elemAttrs['port'] = 5000; + } + else + { + elemName = 'channel'; + if ('video' === name) + { + // last-n + var lastN = config.channelLastN; + if ('undefined' !== typeof(lastN)) + elemAttrs['last-n'] = lastN; + } + } elem.c('content', {name: name}); - elem.c(channel, { - initiator: 'true', - expire: '15', - endpoint: self.myMucResource - }); - if (isData) - elem.attrs({port: 5000}); - elem.up();// end of channel + elem.c(elemName, elemAttrs); + elem.attrs({ endpoint: self.myMucResource }); + elem.up();// end of channel/sctpconnection for (var j = 0; j < self.peers.length; j++) { - elem.c(channel, { - initiator: 'true', - expire: '15', - endpoint: self.peers[j].substr(1 + self.peers[j].lastIndexOf('/')) - }); - if (isData) - elem.attrs({port: 5000}); - elem.up(); // end of channel + var peer = self.peers[j]; + + elem.c(elemName, elemAttrs); + elem.attrs({ endpoint: peer.substr(1 + peer.lastIndexOf('/')) }); + elem.up(); // end of channel/sctpconnection } elem.up(); // end of content }); @@ -233,7 +241,7 @@ ColibriFocus.prototype._makeConference = function () { localSDP.media.forEach(function (media, channel) { var name = SDPUtil.parse_mline(media.split('\r\n')[0]).media; elem.c('content', {name: name}); - elem.c('channel', {initiator: 'false', expire: '15'}); + elem.c('channel', {initiator: 'false', expire: self.channelExpire}); // FIXME: should reuse code from .toJingle var mline = SDPUtil.parse_mline(media.split('\r\n')[0]); @@ -247,7 +255,7 @@ ColibriFocus.prototype._makeConference = function () { elem.up(); // end of channel for (j = 0; j < self.peers.length; j++) { - elem.c('channel', {initiator: 'true', expire:'15' }).up(); + elem.c('channel', {initiator: 'true', expire: self.channelExpire }).up(); } elem.up(); // end of content }); @@ -662,24 +670,33 @@ ColibriFocus.prototype.addNewParticipant = function (peer) { var localSDP = new SDP(this.peerconnection.localDescription.sdp); localSDP.media.forEach(function (media, channel) { var name = SDPUtil.parse_mid(SDPUtil.find_line(media, 'a=mid:')); - elem.c('content', {name: name}); - if (name !== 'data') - { - elem.c('channel', { + var elemName; + var elemAttrs + = { initiator: 'true', expire: self.channelExpire, endpoint: peer.substr(1 + peer.lastIndexOf('/')) - }); + }; + + if ('data' == name) + { + elemName = 'sctpconnection'; + elemAttrs['port'] = 5000; } else { - elem.c('sctpconnection', { - endpoint: peer.substr(1 + peer.lastIndexOf('/')), - initiator: 'true', - expire: self.channelExpire, - port: 5000 - }); + elemName = 'channel'; + if ('video' === name) + { + // last-n + var lastN = config.channelLastN; + if ('undefined' !== typeof(lastN)) + elemAttrs['last-n'] = lastN; + } } + + elem.c('content', {name: name}); + elem.c(elemName, elemAttrs); elem.up(); // end of channel/sctpconnection elem.up(); // end of content }); @@ -1186,4 +1203,5 @@ ColibriFocus.prototype.setRTCPTerminationStrategy = function (strategyFQN) { console.error('got error', err); } ); +}; From 7ce446bcda8121dcc26ed0b8d4112c2937de1873 Mon Sep 17 00:00:00 2001 From: Lyubomir Marinov Date: Wed, 16 Jul 2014 17:35:54 +0300 Subject: [PATCH 10/11] Receives all data-channel events/messages from Videobridge as JSON-formatted text. Renames active speaker to dominant speaker. --- app.js | 2 +- css/videolayout_default.css | 2 +- data_channels.js | 59 ++++++++++++++++++++++------------- videolayout.js | 62 ++++++++++++++++++------------------- 4 files changed, 70 insertions(+), 55 deletions(-) diff --git a/app.js b/app.js index 93ec610358..29d00e983c 100644 --- a/app.js +++ b/app.js @@ -562,7 +562,7 @@ $(document).bind('callactive.jingle', function (event, videoelem, sid) { // Update the large video to the last added video only if there's no // current active or focused speaker. - if (!focusedVideoSrc && !VideoLayout.getActiveSpeakerResourceJid()) + if (!focusedVideoSrc && !VideoLayout.getDominantSpeakerResourceJid()) VideoLayout.updateLargeVideo(videoelem.attr('src'), 1); VideoLayout.showFocusIndicator(); diff --git a/css/videolayout_default.css b/css/videolayout_default.css index e37cf6c626..f140071b83 100644 --- a/css/videolayout_default.css +++ b/css/videolayout_default.css @@ -94,7 +94,7 @@ height: 100%; } -.activespeaker { +.dominantspeaker { background: #000 !important; } diff --git a/data_channels.js b/data_channels.js index 06f0b5e848..ac6ce4dd18 100644 --- a/data_channels.js +++ b/data_channels.js @@ -27,38 +27,53 @@ function onDataChannel(event) dataChannel.onmessage = function (event) { var data = event.data; + // JSON + var obj; - console.info("Got Data Channel Message:", data, dataChannel); - - // Active speaker event - if (data.indexOf('activeSpeaker') === 0) + try { - // Endpoint ID from the Videobridge. - var resourceJid = data.split(":")[1]; - - console.info( - "Data channel new active speaker event: " + resourceJid); - $(document).trigger('activespeakerchanged', [resourceJid]); + obj = JSON.parse(data); + } + catch (e) + { + console.error( + "Failed to parse data channel message as JSON: ", + data, + dataChannel); } - else + if (('undefined' !== typeof(obj)) && (null !== obj)) { - // JSON - var obj; + var colibriClass = obj.colibriClass; - try + if ("DominantSpeakerEndpointChangeEvent" === colibriClass) { - obj = JSON.parse(data); + // Endpoint ID from the Videobridge. + var dominantSpeakerEndpoint = obj.dominantSpeakerEndpoint; + + console.info( + "Data channel new dominant speaker event: ", + dominantSpeakerEndpoint); + $(document).trigger( + 'dominantspeakerchanged', + [dominantSpeakerEndpoint]); } - catch (e) + else if ("LastNEndpointsChangeEvent" === colibriClass) { - console.error( - "Failed to parse data channel message as JSON: ", - data, - dataChannel); + // The new/latest list of last-n endpoint IDs. + var lastNEndpoints = obj.lastNEndpoints; + /* + * The list of endpoint IDs which are entering the list of + * last-n at this time i.e. were not in the old list of last-n + * endpoint IDs. + */ + var endpointsEnteringLastN = obj.endpointsEnteringLastN; + + console.debug( + "Data channel new last-n event: ", + lastNEndpoints); } - if (('undefined' !== typeof(obj)) && (null !== obj)) + else { - // TODO Consume the JSON-formatted data channel message. console.debug("Data channel JSON-formatted message: ", obj); } } diff --git a/videolayout.js b/videolayout.js index 8cb2437250..11c2dbb1c7 100644 --- a/videolayout.js +++ b/videolayout.js @@ -1,6 +1,6 @@ var VideoLayout = (function (my) { var preMuted = false; - var currentActiveSpeaker = null; + var currentDominantSpeaker = null; my.changeLocalAudio = function(stream) { connection.jingle.localAudio = stream; @@ -139,19 +139,19 @@ var VideoLayout = (function (my) { if (isVisible) { // Only if the large video is currently visible. - // Disable previous active speaker video. + // Disable previous dominant speaker video. var oldJid = getJidFromVideoSrc(oldSrc); if (oldJid) { var oldResourceJid = Strophe.getResourceFromJid(oldJid); - VideoLayout.enableActiveSpeaker(oldResourceJid, false); + VideoLayout.enableDominantSpeaker(oldResourceJid, false); } - // Enable new active speaker in the remote videos section. + // Enable new dominant speaker in the remote videos section. var userJid = getJidFromVideoSrc(newSrc); if (userJid) { var resourceJid = Strophe.getResourceFromJid(userJid); - VideoLayout.enableActiveSpeaker(resourceJid, true); + VideoLayout.enableDominantSpeaker(resourceJid, true); } $(this).fadeIn(300); @@ -173,15 +173,15 @@ var VideoLayout = (function (my) { if (focusedVideoSrc === videoSrc) { focusedVideoSrc = null; - var activeSpeakerVideo = null; - // Enable the currently set active speaker. - if (currentActiveSpeaker) { - activeSpeakerVideo - = $('#participant_' + currentActiveSpeaker + '>video') + var dominantSpeakerVideo = null; + // Enable the currently set dominant speaker. + if (currentDominantSpeaker) { + dominantSpeakerVideo + = $('#participant_' + currentDominantSpeaker + '>video') .get(0); - if (activeSpeakerVideo) - VideoLayout.updateLargeVideo(activeSpeakerVideo.src, 1); + if (dominantSpeakerVideo) + VideoLayout.updateLargeVideo(dominantSpeakerVideo.src, 1); } return; @@ -254,12 +254,12 @@ var VideoLayout = (function (my) { if (isVisible) { $('#largeVideo').css({visibility: 'visible'}); $('.watermark').css({visibility: 'visible'}); - VideoLayout.enableActiveSpeaker(resourceJid, true); + VideoLayout.enableDominantSpeaker(resourceJid, true); } else { $('#largeVideo').css({visibility: 'hidden'}); $('.watermark').css({visibility: 'hidden'}); - VideoLayout.enableActiveSpeaker(resourceJid, false); + VideoLayout.enableDominantSpeaker(resourceJid, false); } }; @@ -582,20 +582,20 @@ var VideoLayout = (function (my) { }; /** - * Enables the active speaker UI. + * Enables the dominant speaker UI. * * @param resourceJid the jid indicating the video element to * activate/deactivate - * @param isEnable indicates if the active speaker should be enabled or + * @param isEnable indicates if the dominant speaker should be enabled or * disabled */ - my.enableActiveSpeaker = function(resourceJid, isEnable) { + my.enableDominantSpeaker = function(resourceJid, isEnable) { var displayName = resourceJid; var nameSpan = $('#participant_' + resourceJid + '>span.displayname'); if (nameSpan.length > 0) displayName = nameSpan.text(); - console.log("UI enable active speaker", + console.log("UI enable dominant speaker", displayName, resourceJid, isEnable); @@ -625,16 +625,16 @@ var VideoLayout = (function (my) { if (isEnable) { VideoLayout.showDisplayName(videoContainerId, true); - if (!videoSpan.classList.contains("activespeaker")) - videoSpan.classList.add("activespeaker"); + if (!videoSpan.classList.contains("dominantspeaker")) + videoSpan.classList.add("dominantspeaker"); video.css({visibility: 'hidden'}); } else { VideoLayout.showDisplayName(videoContainerId, false); - if (videoSpan.classList.contains("activespeaker")) - videoSpan.classList.remove("activespeaker"); + if (videoSpan.classList.contains("dominantspeaker")) + videoSpan.classList.remove("dominantspeaker"); video.css({visibility: 'visible'}); } @@ -805,10 +805,10 @@ var VideoLayout = (function (my) { }; /** - * Returns the current active speaker resource jid. + * Returns the current dominant speaker resource jid. */ - my.getActiveSpeakerResourceJid = function () { - return currentActiveSpeaker; + my.getDominantSpeakerResourceJid = function () { + return currentDominantSpeaker; }; /** @@ -926,21 +926,21 @@ var VideoLayout = (function (my) { }); /** - * On active speaker changed event. + * On dominant speaker changed event. */ - $(document).bind('activespeakerchanged', function (event, resourceJid) { + $(document).bind('dominantspeakerchanged', function (event, resourceJid) { // We ignore local user events. if (resourceJid === Strophe.getResourceFromJid(connection.emuc.myroomjid)) return; - // Obtain container for new active speaker. + // Obtain container for new dominant speaker. var container = document.getElementById( 'participant_' + resourceJid); - // Update the current active speaker. - if (resourceJid !== currentActiveSpeaker) - currentActiveSpeaker = resourceJid; + // Update the current dominant speaker. + if (resourceJid !== currentDominantSpeaker) + currentDominantSpeaker = resourceJid; else return; From 6a19d90420f49584946a014136effdb909a3904d Mon Sep 17 00:00:00 2001 From: Lyubomir Marinov Date: Thu, 17 Jul 2014 23:20:36 +0300 Subject: [PATCH 11/11] Adds a method setChannelLastN to ColibriFocus which sets the default value of the channel last-n attribute and updates/patches the existing channels with it. --- libs/colibri/colibri.focus.js | 69 ++++++++++++++++++++++++++++------- 1 file changed, 55 insertions(+), 14 deletions(-) diff --git a/libs/colibri/colibri.focus.js b/libs/colibri/colibri.focus.js index 688642f743..56708074b2 100644 --- a/libs/colibri/colibri.focus.js +++ b/libs/colibri/colibri.focus.js @@ -58,6 +58,12 @@ function ColibriFocus(connection, bridgejid) { = ('number' === typeof(config.channelExpire)) ? config.channelExpire : 15; + /** + * Default channel last-n value. + * @type {number} + */ + this.channelLastN + = ('number' === typeof(config.channelLastN)) ? config.channelLastN : -1; // media types of the conference if (config.openSctp) @@ -212,13 +218,8 @@ ColibriFocus.prototype._makeConference = function () { else { elemName = 'channel'; - if ('video' === name) - { - // last-n - var lastN = config.channelLastN; - if ('undefined' !== typeof(lastN)) - elemAttrs['last-n'] = lastN; - } + if (('video' === name) && (this.channelLastN >= 0)) + elemAttrs['last-n'] = this.channelLastN; } elem.c('content', {name: name}); @@ -686,13 +687,8 @@ ColibriFocus.prototype.addNewParticipant = function (peer) { else { elemName = 'channel'; - if ('video' === name) - { - // last-n - var lastN = config.channelLastN; - if ('undefined' !== typeof(lastN)) - elemAttrs['last-n'] = lastN; - } + if (('video' === name) && (this.channelLastN >= 0)) + elemAttrs['last-n'] = this.channelLastN; } elem.c('content', {name: name}); @@ -1205,3 +1201,48 @@ ColibriFocus.prototype.setRTCPTerminationStrategy = function (strategyFQN) { ); }; +/** + * Sets the default value of the channel last-n attribute in this conference and + * updates/patches the existing channels. + */ +ColibriFocus.prototype.setChannelLastN = function (channelLastN) { + if (('number' === typeof(channelLastN)) + && (this.channelLastN !== channelLastN)) + { + this.channelLastN = channelLastN; + + // Update/patch the existing channels. + var patch = $iq({ to:this.bridgejid, type:'set' }); + + patch.c( + 'conference', + { xmlns:'http://jitsi.org/protocol/colibri', id:this.confid }); + patch.c('content', { name:'video' }); + patch.c( + 'channel', + { + id:$(this.mychannel[1 /* video */]).attr('id'), + 'last-n':this.channelLastN + }); + patch.up(); // end of channel + for (var p = 0; p < this.channels.length; p++) + { + patch.c( + 'channel', + { + id:$(this.channels[p][1 /* video */]).attr('id'), + 'last-n':this.channelLastN + }); + patch.up(); // end of channel + } + this.connection.sendIQ( + patch, + function (res) { + console.info('Set channel last-n succeeded: ', res); + }, + function (err) { + console.error('Set channel last-n failed: ', err); + }); + } +}; +