diff --git a/README.md b/README.md
index f21db5cfb..64fe4e7b0 100644
--- a/README.md
+++ b/README.md
@@ -14,6 +14,9 @@ You can find information on how to deploy Jitsi Meet in the [installation instru
You may also find it helpful to have a look at our sample [config files](https://github.com/jitsi/jitsi-meet/tree/master/doc/example-config-files/)
+## Discuss
+Please use the [Jitsi dev mailing list](http://lists.jitsi.org/pipermail/dev/) to discuss feature requests before opening an issue on github.
+
## Acknowledgements
Jitsi Meet started out as a sample conferencing application using Jitsi Videobridge. It was originally developed by Philipp Hancke who then contributed it to the community where development continues with joint forces!
diff --git a/app.js b/app.js
index db471bd9b..e671642fc 100644
--- a/app.js
+++ b/app.js
@@ -8,6 +8,17 @@ var nickname = null;
var sharedKey = '';
var roomUrl = null;
var ssrc2jid = {};
+/**
+ * The stats collector that process stats data and triggers updates to app.js.
+ * @type {StatsCollector}
+ */
+var statsCollector = null;
+
+/**
+ * The stats collector for the local stream.
+ * @type {LocalStatsCollector}
+ */
+var localStatsCollector = null;
/**
* Indicates whether ssrc is camera video or desktop stream.
@@ -15,6 +26,11 @@ var ssrc2jid = {};
*/
var ssrc2videoType = {};
var videoSrcToSsrc = {};
+/**
+ * Currently focused video "src"(displayed in large video).
+ * @type {String}
+ */
+var focusedVideoSrc = null;
var mutedAudios = {};
var localVideoSrc = null;
@@ -36,8 +52,6 @@ var getVideoPosition;
/* window.onbeforeunload = closePageWarning; */
-var preMuted = false;
-
var sessionTerminated = false;
function init() {
@@ -113,24 +127,27 @@ function obtainAudioAndVideoPermissions(callback) {
function audioStreamReady(stream) {
- change_local_audio(stream);
+ VideoLayout.changeLocalAudio(stream);
+
+ startLocalRtpStatsCollector(stream);
if (RTC.browser !== 'firefox') {
- getUserMediaWithConstraints(['video'], videoStreamReady, videoStreamFailed, config.resolution || '360');
+ getUserMediaWithConstraints(['video'],
+ videoStreamReady,
+ videoStreamFailed,
+ config.resolution || '360');
} else {
doJoin();
}
}
function videoStreamReady(stream) {
-
- change_local_video(stream, true);
+ VideoLayout.changeLocalVideo(stream, true);
doJoin();
}
function videoStreamFailed(error) {
-
console.warn("Failed to obtain video stream - continue anyway", error);
doJoin();
@@ -179,52 +196,6 @@ function doJoin() {
connection.emuc.doJoin(roomjid);
}
-function change_local_audio(stream) {
- connection.jingle.localAudio = stream;
- RTC.attachMediaStream($('#localAudio'), stream);
- document.getElementById('localAudio').autoplay = true;
- document.getElementById('localAudio').volume = 0;
- if (preMuted) {
- toggleAudio();
- preMuted = false;
- }
-}
-
-function change_local_video(stream, flipX) {
-
- connection.jingle.localVideo = stream;
-
- var localVideo = document.createElement('video');
- localVideo.id = 'localVideo_' + stream.id;
- 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
- localVideoSelector.click(function () {
- handleVideoThumbClicked(localVideo.src);
- });
- // Add stream ended handler
- stream.onended = function () {
- localVideoContainer.removeChild(localVideo);
- checkChangeLargeVideo(localVideo.src);
- };
- // Flip video x axis if needed
- flipXLocalVideo = flipX;
- if (flipX) {
- localVideoSelector.addClass("flipVideoX");
- }
- // Attach WebRTC stream
- RTC.attachMediaStream(localVideoSelector, stream);
-
- localVideoSrc = localVideo.src;
- updateLargeVideo(localVideoSrc, 0);
-}
-
$(document).bind('remotestreamadded.jingle', function (event, data, sid) {
function waitForRemoteVideo(selector, sid, ssrc) {
if (selector.removed) {
@@ -276,12 +247,14 @@ $(document).bind('remotestreamadded.jingle', function (event, data, sid) {
var remotes = document.getElementById('remoteVideos');
if (data.peerjid) {
- ensurePeerContainerExists(data.peerjid);
+ VideoLayout.ensurePeerContainerExists(data.peerjid);
container = document.getElementById(
'participant_' + Strophe.getResourceFromJid(data.peerjid));
} else {
if (data.stream.id !== 'mixedmslabel') {
- console.error('can not associate stream', data.stream.id, 'with a participant');
+ console.error( 'can not associate stream',
+ data.stream.id,
+ 'with a participant');
// We don't want to add it here since it will cause troubles
return;
}
@@ -330,16 +303,37 @@ $(document).bind('remotestreamadded.jingle', function (event, data, sid) {
// Remove whole container
container.remove();
Util.playSoundNotification('userLeft');
- resizeThumbnails();
+ VideoLayout.resizeThumbnails();
}
- checkChangeLargeVideo(vid.src);
+ VideoLayout.checkChangeLargeVideo(vid.src);
};
// Add click handler
sel.click(function () {
- handleVideoThumbClicked(vid.src);
+ VideoLayout.handleVideoThumbClicked(vid.src);
});
+ // Add hover handler
+ $(container).hover(
+ function() {
+ VideoLayout.showDisplayName(container.id, true);
+ },
+ function() {
+ var videoSrc = null;
+ if ($('#' + container.id + '>video')
+ && $('#' + container.id + '>video').length > 0) {
+ videoSrc = $('#' + container.id + '>video').get(0).src;
+ }
+
+ // If the video has been "pinned" by the user we want to keep the
+ // display name on place.
+ if (focusedVideoSrc && focusedVideoSrc !== videoSrc
+ || (!focusedVideoSrc
+ && container.id
+ !== VideoLayout.getActiveSpeakerContainerId()))
+ VideoLayout.showDisplayName(container.id, false);
+ }
+ );
// an attempt to work around https://github.com/jitsi/jitmeet/issues/32
if (isVideo &&
@@ -353,48 +347,23 @@ $(document).bind('remotestreamadded.jingle', function (event, data, sid) {
}
});
-function handleVideoThumbClicked(videoSrc) {
-
- $(document).trigger("video.selected", [false]);
-
- updateLargeVideo(videoSrc, 1);
-
- $('audio').each(function (idx, el) {
- if (el.id.indexOf('mixedmslabel') !== -1) {
- el.volume = 0;
- el.volume = 1;
- }
- });
-}
-
/**
- * Checks if removed video is currently displayed and tries to display another one instead.
- * @param removedVideoSrc src stream identifier of the video.
+ * Returns the JID of the user to whom given videoSrc belongs.
+ * @param videoSrc the video "src" identifier.
+ * @returns {null | String} the JID of the user to whom given videoSrc
+ * belongs.
*/
-function checkChangeLargeVideo(removedVideoSrc) {
- if (removedVideoSrc === $('#largeVideo').attr('src')) {
- // 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);
+function getJidFromVideoSrc(videoSrc)
+{
+ if (videoSrc === localVideoSrc)
+ return connection.emuc.myroomjid;
- if (!pick) {
- console.info("Last visible video no longer exists");
- pick = $('#remoteVideos>span[id!="mixedstream"]>video').get(0);
- if (!pick) {
- // Try local video
- console.info("Fallback to local video...");
- pick = $('#remoteVideos>span>span>video').get(0);
- }
- }
-
- // mute if localvideo
- if (pick) {
- updateLargeVideo(pick.src, pick.volume);
- } else {
- console.warn("Failed to elect large video");
- }
+ var ssrc = videoSrcToSsrc[videoSrc];
+ if (!ssrc)
+ {
+ return null;
}
+ return ssrc2jid[ssrc];
}
// an attempt to work around https://github.com/jitsi/jitmeet/issues/32
@@ -427,7 +396,7 @@ function sendKeyframe(pc) {
);
}
-// really mute video, i.e. dont even send black frames
+// Really mute video, i.e. dont even send black frames
function muteVideo(pc, unmute) {
// FIXME: this probably needs another of those lovely state safeguards...
// which checks for iceconn == connected and sigstate == stable
@@ -464,12 +433,90 @@ function muteVideo(pc, unmute) {
);
}
+/**
+ * Callback called by {@link StatsCollector} in intervals supplied to it's
+ * constructor.
+ * @param statsCollector {@link StatsCollector} source of the event.
+ */
+function statsUpdated(statsCollector)
+{
+ Object.keys(statsCollector.jid2stats).forEach(function (jid)
+ {
+ var peerStats = statsCollector.jid2stats[jid];
+ Object.keys(peerStats.ssrc2AudioLevel).forEach(function (ssrc)
+ {
+ console.info(jid + " audio level: " +
+ peerStats.ssrc2AudioLevel[ssrc] + " of ssrc: " + ssrc);
+// VideoLayout.updateAudioLevel( Strophe.getResourceFromJid(jid),
+// peerStats.ssrc2AudioLevel[ssrc]);
+ });
+ });
+}
+
+/**
+ * Callback called by {@link LocalStatsCollector} in intervals supplied to it's
+ * constructor.
+ * @param statsCollector {@link LocalStatsCollector} source of the event.
+ */
+function localStatsUpdated(statsCollector)
+{
+ console.info("Local audio level: " + statsCollector.audioLevel);
+}
+
+/**
+ * Starts the {@link StatsCollector} if the feature is enabled in config.js.
+ */
+function startRtpStatsCollector()
+{
+ if (config.enableRtpStats && !statsCollector)
+ {
+ statsCollector = new StatsCollector(
+ getConferenceHandler().peerconnection, 200, statsUpdated);
+
+ stopLocalRtpStatsCollector();
+
+ statsCollector.start();
+ }
+}
+/**
+ * Starts the {@link LocalStatsCollector} if the feature is enabled in config.js
+ * @param stream the stream that will be used for collecting statistics.
+ */
+function startLocalRtpStatsCollector(stream)
+{
+ if(config.enableRtpStats)
+ {
+ localStatsCollector = new LocalStatsCollector(stream, 200, localStatsUpdated);
+ localStatsCollector.start();
+ }
+}
+
+/**
+ * Stops the {@link LocalStatsCollector}.
+ */
+function stopLocalRtpStatsCollector()
+{
+ if(localStatsCollector)
+ {
+ localStatsCollector.stop();
+ localStatsCollector = null;
+ }
+}
+
$(document).bind('callincoming.jingle', function (event, sid) {
var sess = connection.jingle.sessions[sid];
// TODO: do we check activecall == null?
activecall = sess;
+ startRtpStatsCollector();
+
+ // Bind data channel listener in case we're a regular participant
+ if (config.openSctp)
+ {
+ bindDataChannelListener(sess.peerconnection);
+ }
+
// 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
@@ -478,15 +525,30 @@ $(document).bind('callincoming.jingle', function (event, sid) {
});
+$(document).bind('conferenceCreated.jingle', function (event, focus)
+{
+ startRtpStatsCollector();
+});
+
+$(document).bind('conferenceCreated.jingle', function (event, focus)
+{
+ // Bind data channel listener in case we're the focus
+ if (config.openSctp)
+ {
+ bindDataChannelListener(focus.peerconnection);
+ }
+});
+
$(document).bind('callactive.jingle', function (event, videoelem, sid) {
if (videoelem.attr('id').indexOf('mixedmslabel') === -1) {
// ignore mixedmslabela0 and v0
videoelem.show();
- resizeThumbnails();
+ VideoLayout.resizeThumbnails();
- updateLargeVideo(videoelem.attr('src'), 1);
+ if (!focusedVideoSrc)
+ VideoLayout.updateLargeVideo(videoelem.attr('src'), 1);
- showFocusIndicator();
+ VideoLayout.showFocusIndicator();
}
});
@@ -507,7 +569,7 @@ $(document).bind('setLocalDescription.jingle', function (event, sid) {
var directions = {};
var localSDP = new SDP(sess.peerconnection.localDescription.sdp);
localSDP.media.forEach(function (media) {
- var type = SDPUtil.parse_mline(media.split('\r\n')[0]).media;
+ var type = SDPUtil.parse_mid(SDPUtil.find_line(media, 'a=mid:'));
if (SDPUtil.find_line(media, 'a=ssrc:')) {
// assumes a single local ssrc
@@ -517,8 +579,8 @@ $(document).bind('setLocalDescription.jingle', function (event, sid) {
directions[type] = (
SDPUtil.find_line(media, 'a=sendrecv') ||
SDPUtil.find_line(media, 'a=recvonly') ||
- SDPUtil.find_line('a=sendonly') ||
- SDPUtil.find_line('a=inactive') ||
+ SDPUtil.find_line(media, 'a=sendonly') ||
+ SDPUtil.find_line(media, 'a=inactive') ||
'a=sendrecv').substr(2);
}
});
@@ -555,16 +617,16 @@ $(document).bind('joined.muc', function (event, jid, info) {
Etherpad.init();
}
- showFocusIndicator();
+ VideoLayout.showFocusIndicator();
// Once we've joined the muc show the toolbar
- showToolbar();
+ Toolbar.showToolbar();
var displayName = '';
if (info.displayName)
displayName = info.displayName + ' (me)';
- showDisplayName('localVideoContainer', displayName);
+ VideoLayout.setDisplayName('localVideoContainer', displayName);
});
$(document).bind('entered.muc', function (event, jid, info, pres) {
@@ -572,7 +634,7 @@ $(document).bind('entered.muc', function (event, jid, info, pres) {
console.log('is focus?' + focus ? 'true' : 'false');
// Add Peer's container
- ensurePeerContainerExists(jid);
+ VideoLayout.ensurePeerContainerExists(jid);
if (focus !== null) {
// FIXME: this should prepare the video
@@ -585,7 +647,7 @@ $(document).bind('entered.muc', function (event, jid, info, pres) {
}
}
else if (sharedKey) {
- updateLockButton();
+ Toolbar.updateLockButton();
}
});
@@ -599,10 +661,20 @@ $(document).bind('left.muc', function (event, jid) {
if (container) {
// hide here, wait for video to close before removing
$(container).hide();
- resizeThumbnails();
+ VideoLayout.resizeThumbnails();
}
}, 10);
+ // Unlock large video
+ if (focusedVideoSrc)
+ {
+ if (getJidFromVideoSrc(focusedVideoSrc) === jid)
+ {
+ console.info("Focused video owner has left the conference");
+ focusedVideoSrc = null;
+ }
+ }
+
connection.jingle.terminateByJid(jid);
if (focus == null
@@ -662,7 +734,7 @@ $(document).bind('presence.muc', function (event, jid, info, pres) {
case 'recvonly':
el.hide();
// FIXME: Check if we have to change large video
- //checkChangeLargeVideo(el);
+ //VideoLayout.checkChangeLargeVideo(el);
break;
}
}
@@ -670,11 +742,13 @@ $(document).bind('presence.muc', function (event, jid, info, pres) {
if (info.displayName) {
if (jid === connection.emuc.myroomjid) {
- showDisplayName('localVideoContainer', info.displayName + ' (me)');
+ VideoLayout.setDisplayName('localVideoContainer',
+ info.displayName + ' (me)');
} else {
- ensurePeerContainerExists(jid);
- showDisplayName('participant_' + Strophe.getResourceFromJid(jid),
- info.displayName);
+ VideoLayout.ensurePeerContainerExists(jid);
+ VideoLayout.setDisplayName(
+ 'participant_' + Strophe.getResourceFromJid(jid),
+ info.displayName);
}
}
});
@@ -703,75 +777,10 @@ $(document).bind('passwordrequired.muc', function (event, jid) {
});
});
-$(document).bind('audiomuted.muc', function (event, jid, isMuted) {
- var videoSpanId = null;
- if (jid === connection.emuc.myroomjid) {
- videoSpanId = 'localVideoContainer';
- } else {
- ensurePeerContainerExists(jid);
- videoSpanId = 'participant_' + Strophe.getResourceFromJid(jid);
- }
-
- if (focus) {
- mutedAudios[jid] = isMuted;
- updateRemoteVideoMenu(jid, isMuted);
- }
-
- if (videoSpanId)
- showAudioIndicator(videoSpanId, isMuted);
-});
-
-$(document).bind('videomuted.muc', function (event, jid, isMuted) {
- var videoSpanId = null;
- if (jid === connection.emuc.myroomjid) {
- videoSpanId = 'localVideoContainer';
- } else {
- ensurePeerContainerExists(jid);
- videoSpanId = 'participant_' + Strophe.getResourceFromJid(jid);
- }
-
- if (videoSpanId)
- showVideoIndicator(videoSpanId, isMuted);
-});
-
-/**
- * Updates the large video with the given new video source.
- */
-function updateLargeVideo(newSrc, vol) {
- console.log('hover in', newSrc);
-
- if ($('#largeVideo').attr('src') != newSrc) {
-
- var isVisible = $('#largeVideo').is(':visible');
-
- $('#largeVideo').fadeOut(300, function () {
- $(this).attr('src', newSrc);
-
- // Screen stream is already rotated
- var flipX = (newSrc === localVideoSrc) && flipXLocalVideo;
-
- var videoTransform = document.getElementById('largeVideo').style.webkitTransform;
- if (flipX && videoTransform !== 'scaleX(-1)') {
- document.getElementById('largeVideo').style.webkitTransform = "scaleX(-1)";
- }
- else if (!flipX && videoTransform === 'scaleX(-1)') {
- document.getElementById('largeVideo').style.webkitTransform = "none";
- }
-
- // Change the way we'll be measuring and positioning large video
- var isDesktop = isVideoSrcDesktop(newSrc);
- getVideoSize = isDesktop ? getDesktopVideoSize : getCameraVideoSize;
- getVideoPosition = isDesktop ? getDesktopVideoPosition : getCameraVideoPosition;
-
- if (isVisible)
- $(this).fadeIn(300);
- });
- }
-}
-
/**
* Checks if video identified by given src is desktop stream.
- * @param videoSrc eg. blob:https%3A//pawel.jitsi.net/9a46e0bd-131e-4d18-9c14-a9264e8db395
+ * @param videoSrc eg.
+ * blob:https%3A//pawel.jitsi.net/9a46e0bd-131e-4d18-9c14-a9264e8db395
* @returns {boolean}
*/
function isVideoSrcDesktop(videoSrc) {
@@ -799,20 +808,6 @@ function isVideoSrcDesktop(videoSrc) {
return isDesktop;
}
-/**
- * Shows/hides the large video.
- */
-function setLargeVideoVisible(isVisible) {
- if (isVisible) {
- $('#largeVideo').css({visibility: 'visible'});
- $('.watermark').css({visibility: 'visible'});
- }
- else {
- $('#largeVideo').css({visibility: 'hidden'});
- $('.watermark').css({visibility: 'hidden'});
- }
-}
-
function getConferenceHandler() {
return focus ? focus : activecall;
}
@@ -872,38 +867,6 @@ function toggleAudio() {
buttonClick("#mute", "icon-microphone icon-mic-disabled");
}
-/**
- * Positions the large video.
- *
- * @param videoWidth the stream video width
- * @param videoHeight the stream video height
- */
-var positionLarge = function (videoWidth, videoHeight) {
- var videoSpaceWidth = $('#videospace').width();
- var 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);
-};
-
/**
* Returns an array of the video horizontal and vertical indents,
* so that if fits its parent.
@@ -982,116 +945,12 @@ function getCameraVideoSize(videoWidth,
return [availableWidth, availableHeight];
}
-/**
- * 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];
-}
-
-/**
- * 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) {
- video.width(width);
- video.height(height);
- video.css({ top: verticalIndent + 'px',
- bottom: verticalIndent + 'px',
- left: horizontalIndent + 'px',
- right: horizontalIndent + 'px'});
-}
-
-var resizeLargeVideoContainer = function () {
- Chat.resizeChat();
- var availableHeight = window.innerHeight;
- var availableWidth = Util.getAvailableVideoWidth();
-
- if (availableWidth < 0 || availableHeight < 0) return;
-
- $('#videospace').width(availableWidth);
- $('#videospace').height(availableHeight);
- $('#largeVideoContainer').width(availableWidth);
- $('#largeVideoContainer').height(availableHeight);
-
- resizeThumbnails();
-};
-
-var calculateThumbnailSize = function () {
- // 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;
-
- // Remove the 1px borders arround videos and the chat width.
- var availableWinWidth = $('#remoteVideos').width() - 2 * numvids - 50;
- 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];
-};
-
-function resizeThumbnails() {
- var thumbnailSize = calculateThumbnailSize();
- var width = thumbnailSize[0];
- var height = thumbnailSize[1];
-
- // 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).ready(function () {
Chat.init();
+ $('body').popover({ selector: '[data-toggle=popover]',
+ trigger: 'click hover'});
+
// Set the defaults for prompt dialogs.
jQuery.prompt.setDefaults({persistent: false});
@@ -1106,17 +965,17 @@ $(document).ready(function () {
getVideoSize = getCameraVideoSize;
getVideoPosition = getCameraVideoPosition;
- resizeLargeVideoContainer();
+ VideoLayout.resizeLargeVideoContainer();
$(window).resize(function () {
- resizeLargeVideoContainer();
- positionLarge();
+ VideoLayout.resizeLargeVideoContainer();
+ VideoLayout.positionLarge();
});
// Listen for large video size updates
document.getElementById('largeVideo')
.addEventListener('loadedmetadata', function (e) {
currentVideoWidth = this.videoWidth;
currentVideoHeight = this.videoHeight;
- positionLarge(currentVideoWidth, currentVideoHeight);
+ VideoLayout.positionLarge(currentVideoWidth, currentVideoHeight);
});
if (!$('#settings').is(':visible')) {
@@ -1150,10 +1009,10 @@ $(window).bind('beforeunload', function () {
}
});
}
- disposeConference();
+ disposeConference(true);
});
-function disposeConference() {
+function disposeConference(onUnload) {
var handler = getConferenceHandler();
if (handler && handler.peerconnection) {
// FIXME: probably removing streams is not required and close() should be enough
@@ -1165,6 +1024,18 @@ function disposeConference() {
}
handler.peerconnection.close();
}
+ if (statsCollector)
+ {
+ statsCollector.stop();
+ statsCollector = null;
+ }
+ if(!onUnload) {
+ startLocalRtpStatsCollector(connection.jingle.localAudio);
+ }
+ else
+ {
+ stopLocalRtpStatsCollector();
+ }
focus = null;
activecall = null;
}
@@ -1221,167 +1092,6 @@ function openMessageDialog(titleString, messageString) {
);
}
-/**
- * Opens the lock room dialog.
- */
-function openLockDialog() {
- // Only the focus is able to set a shared key.
- if (focus === null) {
- if (sharedKey)
- $.prompt("This conversation is currently protected by a shared secret key.",
- {
- title: "Secrect key",
- persistent: false
- }
- );
- else
- $.prompt("This conversation isn't currently protected by a secret key. Only the owner of the conference could set a shared key.",
- {
- title: "Secrect key",
- persistent: false
- }
- );
- } else {
- if (sharedKey) {
- $.prompt("Are you sure you would like to remove your secret key?",
- {
- title: "Remove secrect key",
- persistent: false,
- buttons: { "Remove": true, "Cancel": false},
- defaultButton: 1,
- submit: function (e, v, m, f) {
- if (v) {
- setSharedKey('');
- lockRoom(false);
- }
- }
- }
- );
- } else {
- $.prompt('
Set a secrect key to lock your room
' +
- '',
- {
- persistent: false,
- buttons: { "Save": true, "Cancel": false},
- defaultButton: 1,
- loaded: function (event) {
- document.getElementById('lockKey').focus();
- },
- submit: function (e, v, m, f) {
- if (v) {
- var lockKey = document.getElementById('lockKey');
-
- if (lockKey.value) {
- setSharedKey(Util.escapeHtml(lockKey.value));
- lockRoom(true);
- }
- }
- }
- }
- );
- }
- }
-}
-
-/**
- * Opens the invite link dialog.
- */
-function openLinkDialog() {
- 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: { "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 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"
- + "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.
- */
-function openSettingsDialog() {
- $.prompt('Configure your conference
' +
- ' Participants join muted
' +
- ' Require nicknames
' +
- 'Set a secrect key to lock your room: ',
- {
- persistent: false,
- buttons: { "Save": true, "Cancel": false},
- defaultButton: 1,
- loaded: function (event) {
- document.getElementById('lockKey').focus();
- },
- submit: function (e, v, m, f) {
- 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);
- }
- */
- }
- }
- }
- );
-}
-
/**
* Locks / unlocks the room.
*/
@@ -1391,7 +1101,7 @@ function lockRoom(lock) {
else
connection.emuc.lockRoom('');
- updateLockButton();
+ Toolbar.updateLockButton();
}
/**
@@ -1401,86 +1111,6 @@ function setSharedKey(sKey) {
sharedKey = sKey;
}
-/**
- * Updates the lock button state.
- */
-function updateLockButton() {
- buttonClick("#lockIcon", "icon-security icon-security-locked");
-}
-
-/**
- * Hides the toolbar.
- */
-var hideToolbar = function () {
-
- var isToolbarHover = false;
- $('#header').find('*').each(function () {
- var id = $(this).attr('id');
- if ($("#" + id + ":hover").length > 0) {
- isToolbarHover = true;
- }
- });
-
- clearTimeout(toolbarTimeout);
- toolbarTimeout = null;
-
- if (!isToolbarHover) {
- $('#header').hide("slide", { direction: "up", duration: 300});
- }
- else {
- toolbarTimeout = setTimeout(hideToolbar, 2000);
- }
-};
-
-/**
- * Shows the call main toolbar.
- */
-function showToolbar() {
- if (!$('#header').is(':visible')) {
- $('#header').show("slide", { direction: "up", duration: 300});
-
- if (toolbarTimeout) {
- clearTimeout(toolbarTimeout);
- toolbarTimeout = null;
- }
- toolbarTimeout = setTimeout(hideToolbar, 2000);
- }
-
- if (focus != null)
- {
-// 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
- */
-function dockToolbar(isDock) {
- if (isDock) {
- // First make sure the toolbar is shown.
- if (!$('#header').is(':visible')) {
- showToolbar();
- }
- // Then clear the time out, to dock the toolbar.
- clearTimeout(toolbarTimeout);
- toolbarTimeout = null;
- }
- else {
- if (!$('#header').is(':visible')) {
- showToolbar();
- }
- else {
- toolbarTimeout = setTimeout(hideToolbar, 2000);
- }
- }
-}
-
/**
* Updates the room invite url.
*/
@@ -1501,378 +1131,19 @@ function updateRoomUrl(newRoomUrl) {
*/
function closePageWarning() {
if (focus !== null)
- return "You are the owner of this conference call and you are about to end it.";
+ return "You are the owner of this conference call and"
+ + " you are about to end it.";
else
return "You are about to leave this conversation.";
}
-/**
- * Shows a visual indicator for the focus of the conference.
- * Currently if we're not the owner of the conference we obtain the focus
- * from the connection.jingle.sessions.
- */
-function showFocusIndicator() {
- if (focus !== null) {
- var indicatorSpan = $('#localVideoContainer .focusindicator');
-
- if (indicatorSpan.children().length === 0)
- {
- createFocusIndicatorElement(indicatorSpan[0]);
- }
- }
- else if (Object.keys(connection.jingle.sessions).length > 0) {
- // If we're only a participant the focus will be the only session we have.
- var session
- = connection.jingle.sessions
- [Object.keys(connection.jingle.sessions)[0]];
- var focusId
- = 'participant_' + Strophe.getResourceFromJid(session.peerjid);
- var focusContainer = document.getElementById(focusId);
- if (!focusContainer) {
- console.error("No focus container!");
- return;
- }
- var indicatorSpan = $('#' + focusId + ' .focusindicator');
-
- if (!indicatorSpan || indicatorSpan.length === 0) {
- indicatorSpan = document.createElement('span');
- indicatorSpan.className = 'focusindicator';
- focusContainer.appendChild(indicatorSpan);
-
- createFocusIndicatorElement(indicatorSpan);
- }
- }
-}
-
-/**
- * Checks if container for participant identified by given peerJid exists in the document and creates it eventually.
- * @param peerJid peer Jid to check.
- */
-function ensurePeerContainerExists(peerJid) {
- var peerResource = Strophe.getResourceFromJid(peerJid);
- var videoSpanId = 'participant_' + peerResource;
-
- if ($('#' + videoSpanId).length > 0) {
- // If there's been a focus change, make sure we add focus related
- // interface!!
- if (focus && $('#remote_popupmenu_' + peerResource).length <= 0)
- addRemoteVideoMenu( peerJid,
- document.getElementById(videoSpanId));
- return;
- }
-
- var container = addRemoteVideoContainer(peerJid, videoSpanId);
-
- var nickfield = document.createElement('span');
- nickfield.className = "nick";
- nickfield.appendChild(document.createTextNode(peerResource));
- container.appendChild(nickfield);
- resizeThumbnails();
-}
-
-function addRemoteVideoContainer(peerJid, spanId) {
- var container = document.createElement('span');
- container.id = spanId;
- container.className = 'videocontainer';
- var remotes = document.getElementById('remoteVideos');
-
- // If the peerJid is null then this video span couldn't be directly
- // associated with a participant (this could happen in the case of prezi).
- if (focus && peerJid != null)
- addRemoteVideoMenu(peerJid, container);
-
- remotes.appendChild(container);
- return container;
-}
-
-/**
- * Creates the element indicating the focus of the conference.
- */
-function createFocusIndicatorElement(parentElement) {
- var focusIndicator = document.createElement('i');
- focusIndicator.className = 'fa fa-star';
- focusIndicator.title = "The owner of this conference";
- parentElement.appendChild(focusIndicator);
-}
-
-function addRemoteVideoMenu(jid, parentElement) {
- var spanElement = document.createElement('span');
- spanElement.className = 'remotevideomenu';
-
- parentElement.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_' + Strophe.getResourceFromJid(jid);
- spanElement.appendChild(popupmenuElement);
-
- var muteMenuItem = document.createElement('li');
- var muteLinkItem = document.createElement('a');
-
- var mutedIndicator = "";
-
- if (!mutedAudios[jid]) {
- muteLinkItem.innerHTML = mutedIndicator + 'Mute';
- muteLinkItem.className = 'mutelink';
- }
- else {
- muteLinkItem.innerHTML = mutedIndicator + 'Muted';
- muteLinkItem.className = 'mutelink disabled';
- }
-
- muteLinkItem.onclick = function(){
- if ($(this).attr('disabled') != undefined) {
- event.preventDefault();
- }
- var isMute = !mutedAudios[jid];
- connection.moderate.setMute(jid, isMute);
- popupmenuElement.setAttribute('style', 'display:none;');
-
- if (isMute) {
- this.innerHTML = mutedIndicator + 'Muted';
- this.className = 'mutelink disabled';
- }
- else {
- this.innerHTML = mutedIndicator + 'Mute';
- this.className = 'mutelink';
- }
- };
-
- muteMenuItem.appendChild(muteLinkItem);
- popupmenuElement.appendChild(muteMenuItem);
-
- var ejectIndicator = "";
-
- var ejectMenuItem = document.createElement('li');
- var ejectLinkItem = document.createElement('a');
- ejectLinkItem.innerHTML = ejectIndicator + 'Kick out';
- ejectLinkItem.onclick = function(){
- connection.moderate.eject(jid);
- popupmenuElement.setAttribute('style', 'display:none;');
- };
-
- ejectMenuItem.appendChild(ejectLinkItem);
- popupmenuElement.appendChild(ejectMenuItem);
-}
-
-function updateRemoteVideoMenu(jid, isMuted) {
- var muteMenuItem
- = $('#remote_popupmenu_'
- + Strophe.getResourceFromJid(jid)
- + '>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';
- }
- }
-}
-
-/**
- * Toggles the application in and out of full screen mode
- * (a.k.a. presentation mode in Chrome).
- */
-function toggleFullScreen() {
- 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();
- }
- }
-}
-
-/**
- * Shows the display name for the given video.
- */
-function showDisplayName(videoSpanId, displayName) {
- var nameSpan = $('#' + videoSpanId + '>span.displayname');
-
- // If we already have a display name for this video.
- if (nameSpan.length > 0) {
- var nameSpanElement = nameSpan.get(0);
-
- if (nameSpanElement.id === 'localDisplayName' &&
- $('#localDisplayName').text() !== displayName) {
- $('#localDisplayName').text(displayName);
- } else {
- $('#' + videoSpanId + '_name').text(displayName);
- }
- } else {
- var editButton = null;
-
- if (videoSpanId === 'localVideoContainer') {
- editButton = createEditDisplayNameButton();
- }
- if (displayName.length) {
- nameSpan = document.createElement('span');
- nameSpan.className = 'displayname';
- nameSpan.innerText = displayName;
- $('#' + videoSpanId)[0].appendChild(nameSpan);
- }
-
- if (!editButton) {
- nameSpan.id = videoSpanId + '_name';
- } else {
- nameSpan.id = 'localDisplayName';
- $('#' + videoSpanId)[0].appendChild(editButton);
-
- var editableText = document.createElement('input');
- editableText.className = 'displayname';
- editableText.id = 'editDisplayName';
-
- if (displayName.length) {
- editableText.value = displayName.substring(0, displayName.indexOf(' (me)'));
- }
-
- editableText.setAttribute('style', 'display:none;');
- editableText.setAttribute('placeholder', 'ex. Jane Pink');
- $('#' + videoSpanId)[0].appendChild(editableText);
-
- $('#localVideoContainer .displayname').bind("click", function (e) {
- e.preventDefault();
- $('#localDisplayName').hide();
- $('#editDisplayName').show();
- $('#editDisplayName').focus();
- $('#editDisplayName').select();
-
- var inputDisplayNameHandler = function (name) {
- if (nickname !== name) {
- nickname = name;
- window.localStorage.displayname = nickname;
- connection.emuc.addDisplayNameToPresence(nickname);
- connection.emuc.sendPresence();
-
- Chat.setChatConversationMode(true);
- }
-
- if (!$('#localDisplayName').is(":visible")) {
- $('#localDisplayName').text(nickname + " (me)");
- $('#localDisplayName').show();
- $('#editDisplayName').hide();
- }
- };
-
- $('#editDisplayName').one("focusout", function (e) {
- inputDisplayNameHandler(this.value);
- });
-
- $('#editDisplayName').on('keydown', function (e) {
- if (e.keyCode === 13) {
- e.preventDefault();
- inputDisplayNameHandler(this.value);
- }
- });
- });
- }
- }
-}
-
-/**
- * Creates the edit display name button.
- *
- * @returns the edit button
- */
-function createEditDisplayNameButton() {
- var editButton = document.createElement('a');
- editButton.className = 'displayname';
- editButton.innerHTML = '';
-
- return editButton;
-}
-
-/**
- * Shows audio muted indicator over small videos.
- */
-function showAudioIndicator(videoSpanId, isMuted) {
- var audioMutedSpan = $('#' + videoSpanId + '>span.audioMuted');
-
- if (isMuted === 'false') {
- if (audioMutedSpan.length > 0) {
- audioMutedSpan.remove();
- }
- }
- else {
- var videoMutedSpan = $('#' + videoSpanId + '>span.videoMuted');
-
- audioMutedSpan = document.createElement('span');
- audioMutedSpan.className = 'audioMuted';
- if (videoMutedSpan) {
- audioMutedSpan.right = '30px';
- }
- $('#' + videoSpanId)[0].appendChild(audioMutedSpan);
-
- var mutedIndicator = document.createElement('i');
- mutedIndicator.className = 'icon-mic-disabled';
- mutedIndicator.title = "Participant is muted";
- audioMutedSpan.appendChild(mutedIndicator);
- }
-}
-
-/**
- * Shows video muted indicator over small videos.
- */
-function showVideoIndicator(videoSpanId, isMuted) {
- var videoMutedSpan = $('#' + videoSpanId + '>span.videoMuted');
-
- if (isMuted === 'false') {
- if (videoMutedSpan.length > 0) {
- videoMutedSpan.remove();
- }
- }
- else {
- var audioMutedSpan = $('#' + videoSpanId + '>span.audioMuted');
-
- videoMutedSpan = document.createElement('span');
- videoMutedSpan.className = 'videoMuted';
- if (audioMutedSpan) {
- videoMutedSpan.right = '30px';
- }
- $('#' + videoSpanId)[0].appendChild(videoMutedSpan);
-
- var mutedIndicator = document.createElement('i');
- mutedIndicator.className = 'icon-camera-disabled';
- mutedIndicator.title = "Participant has stopped the camera.";
- videoMutedSpan.appendChild(mutedIndicator);
- }
-}
-
/**
* Resizes and repositions videos in full screen mode.
*/
$(document).on('webkitfullscreenchange mozfullscreenchange fullscreenchange',
function () {
- resizeLargeVideoContainer();
- positionLarge();
+ VideoLayout.resizeLargeVideoContainer();
+ VideoLayout.positionLarge();
isFullScreen = document.fullScreen ||
document.mozFullScreen ||
document.webkitIsFullScreen;
@@ -1899,3 +1170,13 @@ function setView(viewName) {
// document.getElementById('videolayout_fullscreen').disabled = true;
// }
}
+
+$(document).bind('fatalError.jingle',
+ function (event, session, error)
+ {
+ sessionTerminated = true;
+ connection.emuc.doLeave();
+ openMessageDialog( "Sorry",
+ "Your browser version is too old. Please update and try again...");
+ }
+);
diff --git a/chat.js b/chat.js
index ad7e50ba5..b7255a346 100644
--- a/chat.js
+++ b/chat.js
@@ -39,10 +39,19 @@ var Chat = (function (my) {
$('#usermsg').keydown(function (event) {
if (event.keyCode === 13) {
event.preventDefault();
- var message = Util.escapeHtml(this.value);
+ var value = this.value;
$('#usermsg').val('').trigger('autosize.resize');
this.focus();
- connection.emuc.sendMessage(message, nickname);
+ var command = new CommandsProcessor(value);
+ if(command.isCommand())
+ {
+ command.processCommand();
+ }
+ else
+ {
+ var message = Util.escapeHtml(value);
+ connection.emuc.sendMessage(message, nickname);
+ }
}
});
@@ -90,6 +99,45 @@ var Chat = (function (my) {
{ 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 = Util.escapeHtml(errorMessage);
+ originalText = Util.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(linkify(Util.escapeHtml(subject)));
+ if(subject == "")
+ {
+ $("#subject").css({display: "none"});
+ }
+ else
+ {
+ $("#subject").css({display: "block"});
+ }
+ }
+
+
/**
* Opens / closes the chat area.
*/
@@ -242,7 +290,7 @@ var Chat = (function (my) {
if (unreadMessages) {
unreadMsgElement.innerHTML = unreadMessages.toString();
- showToolbar();
+ Toolbar.showToolbar();
var chatButtonElement
= document.getElementById('chatButton').parentNode;
diff --git a/commands.js b/commands.js
new file mode 100644
index 000000000..110e8cf1c
--- /dev/null
+++ b/commands.js
@@ -0,0 +1,98 @@
+/**
+ * Handles commands received via chat messages.
+ */
+var CommandsProcessor = (function()
+{
+ /**
+ * Constructs new CommandProccessor instance from a message.
+ * @param message the message
+ * @constructor
+ */
+ function CommandsPrototype(message)
+ {
+ /**
+ * 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 "";
+ };
+
+ 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}
+ */
+ CommandsPrototype.prototype.isCommand = function()
+ {
+ if(this.getCommand())
+ return true;
+ return false;
+ }
+
+ /**
+ * Processes the command.
+ */
+ CommandsPrototype.prototype.processCommand = function()
+ {
+ if(!this.isCommand())
+ return;
+
+ commands[this.getCommand()](this.getArgument());
+
+ }
+
+ /**
+ * Processes the data for topic command.
+ * @param commandArguments the arguments of the topic command.
+ */
+ var processTopic = function(commandArguments)
+ {
+ var topic = Util.escapeHtml(commandArguments);
+ connection.emuc.setSubject(topic);
+ }
+
+ /**
+ * 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
+ };
+
+ return CommandsPrototype;
+})();
diff --git a/config.js b/config.js
index be326bf83..22d93589a 100644
--- a/config.js
+++ b/config.js
@@ -11,5 +11,7 @@ var config = {
bosh: '//lambada.jitsi.net/http-bind', // FIXME: use xep-0156 for that
desktopSharing: 'ext', // Desktop sharing method. Can be set to 'ext', 'webrtc' or false to disable.
chromeExtensionId: 'diibjkoicjeejcmhdnailmkgecihlobk', // Id of desktop streamer Chrome extension
- minChromeExtVersion: '0.1' // Required version of Chrome extension
+ minChromeExtVersion: '0.1', // Required version of Chrome extension
+ enableRtpStats: false, // Enables RTP stats processing
+ openSctp: true //Toggle to enable/disable SCTP channels
};
\ No newline at end of file
diff --git a/css/main.css b/css/main.css
index afbd5b8cc..eb9cc650d 100644
--- a/css/main.css
+++ b/css/main.css
@@ -43,6 +43,10 @@ html, body{
color: #087dba;
}
+.errorMessage {
+ color: red;
+}
+
.remoteuser {
color: #424242;
}
diff --git a/css/popover.css b/css/popover.css
new file mode 100644
index 000000000..f32c6034a
--- /dev/null
+++ b/css/popover.css
@@ -0,0 +1,124 @@
+.popover {
+ position: absolute;
+ top: 0;
+ left: 0;
+ z-index: 1010;
+ display: none;
+ max-width: 300px;
+ min-width: 100px;
+ padding: 1px;
+ text-align: left;
+ color: #428bca;
+ background-color: #ffffff;
+ background-clip: padding-box;
+ border: 1px solid #cccccc;
+ border: 1px solid rgba(0, 0, 0, 0.2);
+ border-radius: 6px;
+ -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
+ box-shadow: 0 5px 10px rgba(0, 0, 0, 0.4);
+ white-space: normal;
+}
+.popover.top {
+ margin-top: -10px;
+}
+.popover.right {
+ margin-left: 10px;
+}
+.popover.bottom {
+ margin-top: 10px;
+}
+.popover.left {
+ margin-left: -10px;
+}
+.popover-title {
+ margin: 0;
+ padding: 8px 14px;
+ font-size: 11pt;
+ font-weight: normal;
+ line-height: 18px;
+ background-color: #f7f7f7;
+ border-bottom: 1px solid #ebebeb;
+ border-radius: 5px 5px 0 0;
+}
+.popover-content {
+ padding: 9px 14px;
+ font-size: 10pt;
+ white-space:pre-wrap;
+ text-align: center;
+}
+.popover > .arrow,
+.popover > .arrow:after {
+ position: absolute;
+ display: block;
+ width: 0;
+ height: 0;
+ border-color: transparent;
+ border-style: solid;
+}
+.popover > .arrow {
+ border-width: 11px;
+}
+.popover > .arrow:after {
+ border-width: 10px;
+ content: "";
+}
+.popover.top > .arrow {
+ left: 50%;
+ margin-left: -11px;
+ border-bottom-width: 0;
+ border-top-color: #999999;
+ border-top-color: rgba(0, 0, 0, 0.25);
+ bottom: -11px;
+}
+.popover.top > .arrow:after {
+ content: " ";
+ bottom: 1px;
+ margin-left: -10px;
+ border-bottom-width: 0;
+ border-top-color: #ffffff;
+}
+.popover.right > .arrow {
+ top: 50%;
+ left: -11px;
+ margin-top: -11px;
+ border-left-width: 0;
+ border-right-color: #999999;
+ border-right-color: rgba(0, 0, 0, 0.25);
+}
+.popover.right > .arrow:after {
+ content: " ";
+ left: 1px;
+ bottom: -10px;
+ border-left-width: 0;
+ border-right-color: #ffffff;
+}
+.popover.bottom > .arrow {
+ left: 50%;
+ margin-left: -11px;
+ border-top-width: 0;
+ border-bottom-color: #999999;
+ border-bottom-color: rgba(0, 0, 0, 0.25);
+ top: -11px;
+}
+.popover.bottom > .arrow:after {
+ content: " ";
+ top: 1px;
+ margin-left: -10px;
+ border-top-width: 0;
+ border-bottom-color: #ffffff;
+}
+.popover.left > .arrow {
+ top: 50%;
+ right: -11px;
+ margin-top: -11px;
+ border-right-width: 0;
+ border-left-color: #999999;
+ border-left-color: rgba(0, 0, 0, 0.25);
+}
+.popover.left > .arrow:after {
+ content: " ";
+ right: 1px;
+ border-right-width: 0;
+ border-left-color: #ffffff;
+ bottom: -10px;
+}
\ No newline at end of file
diff --git a/css/popup_menu.css b/css/popup_menu.css
index 74d99551d..f4ba0008b 100644
--- a/css/popup_menu.css
+++ b/css/popup_menu.css
@@ -9,7 +9,7 @@ ul.popupmenu {
padding-bottom: 5px;
padding-top: 5px;
right: 10px;
- left: 0px;
+ left: -5px;
width: 100px;
background-color: rgba(0,0,0,1);
-webkit-box-shadow: 0 0 2px #000000, 0 0 10px #000000;
@@ -21,7 +21,7 @@ ul.popupmenu:after {
display: block;
position: absolute;
bottom: -9px;
- left: 13px;
+ left: 11px;
}
ul.popupmenu li {
diff --git a/css/videolayout_default.css b/css/videolayout_default.css
index 7c7823100..5c04ef009 100644
--- a/css/videolayout_default.css
+++ b/css/videolayout_default.css
@@ -32,9 +32,11 @@
background-size: contain;
border-radius:8px;
border: 2px solid #212425;
+ margin-right: 3px;
}
-#remoteVideos .videocontainer:hover {
+#remoteVideos .videocontainer:hover,
+#remoteVideos .videocontainer.videoContainerFocused {
width: 100%;
height: 100%;
content:"";
@@ -49,7 +51,6 @@
-webkit-animation-iteration-count: 1;
-webkit-box-shadow: 0 0 18px #388396;
border: 2px solid #388396;
- z-index: 3;
}
#localVideoWrapper {
@@ -93,6 +94,11 @@
height: 100%;
}
+.activespeaker {
+ -webkit-filter: grayscale(1);
+ filter: grayscale(1);
+}
+
#etherpad,
#presentation {
text-align: center;
@@ -119,6 +125,7 @@
text-shadow: 0px 1px 0px rgba(255,255,255,.3), 0px -1px 0px rgba(0,0,0,.7);
border: 0px;
z-index: 2;
+ text-align: center;
}
#remoteVideos .nick {
@@ -133,25 +140,22 @@
.videocontainer>span.displayname,
.videocontainer>input.displayname {
- display: inline-block;
+ display: none;
position: absolute;
- background: -webkit-linear-gradient(left, rgba(0,0,0,.7), rgba(0,0,0,0));
color: #FFFFFF;
- bottom: 0;
- left: 0;
- padding: 3px 5px;
- width: 100%;
- height: auto;
- max-height: 18px;
- font-size: 9pt;
- text-align: left;
+ background: rgba(0,0,0,.7);
+ text-align: center;
text-overflow: ellipsis;
+ width: 70%;
+ height: 20%;
+ left: 15%;
+ top: 40%;
+ padding: 5px;
+ font-size: 11pt;
overflow: hidden;
white-space: nowrap;
z-index: 2;
- box-sizing: border-box;
- border-bottom-left-radius:4px;
- border-bottom-right-radius:4px;
+ border-radius:20px;
}
#localVideoContainer>span.displayname:hover {
@@ -162,6 +166,10 @@
pointer-events: none;
}
+.videocontainer>input.displayname {
+ height: auto;
+}
+
#localDisplayName {
pointer-events: auto !important;
}
@@ -190,6 +198,7 @@
text-shadow: 0px 1px 0px rgba(255,255,255,.3), 0px -1px 0px rgba(0,0,0,.7);
border: 0px;
z-index: 3;
+ text-align: center;
}
.videocontainer>span.videoMuted {
@@ -226,7 +235,6 @@
#header{
display:none;
position:absolute;
- height: 0px;
text-align:center;
top:0;
left:0;
@@ -241,13 +249,27 @@
margin-right:auto;
height:39px;
width:auto;
- overflow: hidden;
background: linear-gradient(to bottom, rgba(103,103,103,.65) , rgba(0,0,0,.65));
-webkit-box-shadow: 0 0 2px #000000, 0 0 10px #000000;
border-bottom-left-radius: 12px;
border-bottom-right-radius: 12px;
}
+#subject {
+ position: relative;
+ z-index: 3;
+ width: auto;
+ padding: 5px;
+ margin-left: 40%;
+ margin-right: 40%;
+ text-align: center;
+ background: linear-gradient(to bottom, rgba(255,255,255,.85) , rgba(255,255,255,.35));
+ -webkit-box-shadow: 0 0 2px #000000, 0 0 10px #000000;
+ border-bottom-left-radius: 12px;
+ border-bottom-right-radius: 12px;
+ display: none;
+}
+
.watermark {
display: block;
position: absolute;
diff --git a/data_channels.js b/data_channels.js
new file mode 100644
index 000000000..7a1ae59e3
--- /dev/null
+++ b/data_channels.js
@@ -0,0 +1,80 @@
+/* global connection, Strophe, updateLargeVideo, focusedVideoSrc*/
+/**
+ * Callback triggered by PeerConnection when new data channel is opened
+ * on the bridge.
+ * @param event the event info object.
+ */
+function onDataChannel(event)
+{
+ var dataChannel = event.channel;
+
+ dataChannel.onopen = function ()
+ {
+ console.info("Data channel opened by the bridge !!!", dataChannel);
+
+ // Code sample for sending string and/or binary data
+ // Sends String message to the bridge
+ //dataChannel.send("Hello bridge!");
+ // Sends 12 bytes binary message to the bridge
+ //dataChannel.send(new ArrayBuffer(12));
+ };
+
+ dataChannel.onerror = function (error)
+ {
+ console.error("Data Channel Error:", error, dataChannel);
+ };
+
+ dataChannel.onmessage = function (event)
+ {
+ var msgData = event.data;
+ console.info("Got Data Channel Message:", msgData, dataChannel);
+
+ // Active speaker event
+ if (msgData.indexOf('activeSpeaker') === 0)
+ {
+ // Endpoint ID from the bridge
+ var resourceJid = msgData.split(":")[1];
+
+ console.info(
+ "Data channel new active speaker event: " + resourceJid);
+ $(document).trigger('activespeakerchanged', [resourceJid]);
+ }
+ };
+
+ dataChannel.onclose = function ()
+ {
+ console.info("The Data Channel closed", dataChannel);
+ };
+}
+
+/**
+ * Binds "ondatachannel" event listener to given PeerConnection instance.
+ * @param peerConnection WebRTC peer connection instance.
+ */
+function bindDataChannelListener(peerConnection)
+{
+ peerConnection.ondatachannel = onDataChannel;
+
+ // Sample code for opening new data channel from Jitsi Meet to the bridge.
+ // Although it's not a requirement to open separate channels from both bridge
+ // and peer as single channel can be used for sending and receiving data.
+ // So either channel opened by the bridge or the one opened here is enough
+ // for communication with the bridge.
+ /*var dataChannelOptions =
+ {
+ reliable: true
+ };
+ var dataChannel
+ = peerConnection.createDataChannel("myChannel", dataChannelOptions);
+
+ // Can be used only when is in open state
+ dataChannel.onopen = function ()
+ {
+ dataChannel.send("My channel !!!");
+ };
+ dataChannel.onmessage = function (event)
+ {
+ var msgData = event.data;
+ console.info("Got My Data Channel Message:", msgData, dataChannel);
+ };*/
+}
\ No newline at end of file
diff --git a/desktopsharing.js b/desktopsharing.js
index dc8194f54..2e5e41895 100644
--- a/desktopsharing.js
+++ b/desktopsharing.js
@@ -1,4 +1,4 @@
-/* global $, config, connection, chrome, alert, getUserMediaWithConstraints, change_local_video, getConferenceHandler */
+/* global $, config, connection, chrome, alert, getUserMediaWithConstraints, changeLocalVideo, getConferenceHandler */
/**
* Indicates that desktop stream is currently in use(for toggle purpose).
* @type {boolean}
@@ -251,7 +251,9 @@ function newStreamCreated(stream) {
var oldStream = connection.jingle.localVideo;
- change_local_video(stream, !isUsingScreenStream);
+ connection.jingle.localVideo = stream;
+
+ VideoLayout.changeLocalVideo(stream, !isUsingScreenStream);
var conferenceHandler = getConferenceHandler();
if (conferenceHandler) {
diff --git a/etherpad.js b/etherpad.js
index 6bb63e2e1..78111fd0c 100644
--- a/etherpad.js
+++ b/etherpad.js
@@ -45,8 +45,8 @@ var Etherpad = (function (my) {
if (Prezi.isPresentationVisible()) {
largeVideo.css({opacity: '0'});
} else {
- setLargeVideoVisible(false);
- dockToolbar(true);
+ VideoLayout.setLargeVideoVisible(false);
+ Toolbar.dockToolbar(true);
}
$('#etherpad>iframe').fadeIn(300, function () {
@@ -63,8 +63,8 @@ var Etherpad = (function (my) {
document.body.style.background = 'black';
if (!isPresentation) {
$('#largeVideo').fadeIn(300, function () {
- setLargeVideoVisible(true);
- dockToolbar(false);
+ VideoLayout.setLargeVideoVisible(true);
+ Toolbar.dockToolbar(false);
});
}
});
diff --git a/index.html b/index.html
index d323dd1de..a99546eb9 100644
--- a/index.html
+++ b/index.html
@@ -20,26 +20,35 @@
-
-
+
+
+
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
-
+
+
@@ -49,35 +58,40 @@
-