Merge remote-tracking branch 'origin/master' into issue/toolbar-refactor
This commit is contained in:
commit
de30ce0f5c
|
@ -3,3 +3,4 @@ node_modules
|
|||
.idea/
|
||||
*.iml
|
||||
.*.tmp
|
||||
deploy-local.sh
|
||||
|
|
7
Makefile
7
Makefile
|
@ -18,7 +18,10 @@ app:
|
|||
$(NPM) update && $(BROWSERIFY) $(FLAGS) app.js -s APP -o $(OUTPUT_DIR)/app.bundle.js
|
||||
|
||||
clean:
|
||||
@rm -f $(OUTPUT_DIR)/*.bundle.js
|
||||
rm -f $(OUTPUT_DIR)/*.bundle.js
|
||||
|
||||
deploy:
|
||||
@mkdir -p $(DEPLOY_DIR) && cp $(OUTPUT_DIR)/*.bundle.js $(DEPLOY_DIR) && ./bump-js-versions.sh
|
||||
mkdir -p $(DEPLOY_DIR) && \
|
||||
cp $(OUTPUT_DIR)/*.bundle.js $(DEPLOY_DIR) && \
|
||||
./bump-js-versions.sh && \
|
||||
([ ! -x deploy-local.sh ] || ./deploy-local.sh)
|
||||
|
|
|
@ -446,6 +446,11 @@
|
|||
background-position: center;
|
||||
}
|
||||
|
||||
.videoMessageFilter {
|
||||
-webkit-filter: grayscale(.5) opacity(0.8);
|
||||
filter: grayscale(.5) opacity(0.8);
|
||||
}
|
||||
|
||||
.videoProblemFilter {
|
||||
-webkit-filter: blur(10px) grayscale(.5) opacity(0.8);
|
||||
filter: blur(10px) grayscale(.5) opacity(0.8);
|
||||
|
|
|
@ -29,6 +29,11 @@ constructor.
|
|||
```
|
||||
If you don't specify room the user will enter in new conference with random room name.
|
||||
|
||||
You can enable the "film strip only" mode(only the small videos are visible) by setting 6th parameter to ```true```:
|
||||
```javascript
|
||||
var api = new JitsiMeetExternalAPI(domain, room, width, height, htmlElement, true);
|
||||
```
|
||||
|
||||
Controlling embedded Jitsi Meet Conference
|
||||
=========
|
||||
|
||||
|
|
|
@ -23,9 +23,12 @@ var JitsiMeetExternalAPI = (function()
|
|||
* @param width width of the iframe
|
||||
* @param height height of the iframe
|
||||
* @param parent_node the node that will contain the iframe
|
||||
* @param filmStripOnly if the value is true only the small videos will be
|
||||
* visible.
|
||||
* @constructor
|
||||
*/
|
||||
function JitsiMeetExternalAPI(domain, room_name, width, height, parentNode) {
|
||||
function JitsiMeetExternalAPI(domain, room_name, width, height, parentNode,
|
||||
filmStripOnly) {
|
||||
if(!width || width < MIN_WIDTH)
|
||||
width = MIN_WIDTH;
|
||||
if(!height || height < MIN_HEIGHT)
|
||||
|
@ -49,6 +52,9 @@ var JitsiMeetExternalAPI = (function()
|
|||
if(room_name)
|
||||
this.url += room_name;
|
||||
this.url += "#external=true";
|
||||
if(filmStripOnly)
|
||||
this.url += "&interfaceConfig.filmStripOnly=true";
|
||||
|
||||
JitsiMeetExternalAPI.id++;
|
||||
|
||||
this.frame = document.createElement("iframe");
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
<script src="libs/popover.js?v=1"></script><!-- bootstrap tooltip lib -->
|
||||
<script src="libs/toastr.js?v=1"></script><!-- notifications lib -->
|
||||
<script src="interface_config.js?v=5"></script>
|
||||
<script src="libs/app.bundle.js?v=120"></script>
|
||||
<script src="libs/app.bundle.js?v=121"></script>
|
||||
<script src="analytics.js?v=1"></script><!-- google analytics plugin -->
|
||||
<link rel="stylesheet" href="css/font.css?v=7"/>
|
||||
<link rel="stylesheet" href="css/toastr.css?v=1">
|
||||
|
|
|
@ -15,5 +15,9 @@ var interfaceConfig = {
|
|||
GENERATE_ROOMNAMES_ON_WELCOME_PAGE: true,
|
||||
APP_NAME: "Jitsi Meet",
|
||||
INVITATION_POWERED_BY: true,
|
||||
ACTIVE_SPEAKER_AVATAR_SIZE: 100
|
||||
ACTIVE_SPEAKER_AVATAR_SIZE: 100,
|
||||
/**
|
||||
* Whether to only show the filmstrip (and hide the toolbar).
|
||||
*/
|
||||
filmStripOnly: false
|
||||
};
|
||||
|
|
|
@ -240,5 +240,11 @@
|
|||
"FETCH_SESSION_ID": "Obtaining session-id...",
|
||||
"GOT_SESSION_ID": "Obtaining session-id... Done",
|
||||
"GET_SESSION_ID_ERROR": "Get session-id error: "
|
||||
},
|
||||
"recording":
|
||||
{
|
||||
"toaster": "Currently recording!",
|
||||
"pending": "Your recording will start as soon as another participant joins",
|
||||
"on": "Recording has been started"
|
||||
}
|
||||
}
|
||||
|
|
1573
libs/app.bundle.js
1573
libs/app.bundle.js
File diff suppressed because it is too large
Load Diff
|
@ -1,6 +1,24 @@
|
|||
/* global APP */
|
||||
var StreamEventTypes = require("../../service/RTC/StreamEventTypes.js");
|
||||
var RTCEvents = require("../../service/RTC/RTCEvents");
|
||||
var RTCBrowserType = require("./RTCBrowserType");
|
||||
|
||||
/**
|
||||
* This implements 'onended' callback normally fired by WebRTC after the stream
|
||||
* is stopped. There is no such behaviour yet in FF, so we have to add it.
|
||||
* @param stream original WebRTC stream object to which 'onended' handling
|
||||
* will be added.
|
||||
*/
|
||||
function implementOnEndedHandling(stream) {
|
||||
var originalStop = stream.stop;
|
||||
stream.stop = function () {
|
||||
originalStop.apply(stream);
|
||||
if (!stream.ended) {
|
||||
stream.ended = true;
|
||||
stream.onended();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function LocalStream(stream, type, eventEmitter, videoType, isGUMStream) {
|
||||
this.stream = stream;
|
||||
|
@ -21,9 +39,12 @@ function LocalStream(stream, type, eventEmitter, videoType, isGUMStream) {
|
|||
};
|
||||
}
|
||||
|
||||
this.stream.onended = function() {
|
||||
this.stream.onended = function () {
|
||||
self.streamEnded();
|
||||
};
|
||||
if (RTCBrowserType.isFirefox()) {
|
||||
implementOnEndedHandling(this.stream);
|
||||
}
|
||||
}
|
||||
|
||||
LocalStream.prototype.streamEnded = function () {
|
||||
|
@ -45,9 +66,11 @@ LocalStream.prototype.setMute = function (mute)
|
|||
var eventType = isAudio ? RTCEvents.AUDIO_MUTE : RTCEvents.VIDEO_MUTE;
|
||||
|
||||
if ((window.location.protocol != "https:" && this.isGUMStream) ||
|
||||
(isAudio && this.isGUMStream) || this.videoType === "screen") {
|
||||
var tracks = this.getTracks();
|
||||
(isAudio && this.isGUMStream) || this.videoType === "screen" ||
|
||||
// FIXME FF does not support 'removeStream' method used to mute
|
||||
RTCBrowserType.isFirefox()) {
|
||||
|
||||
var tracks = this.getTracks();
|
||||
for (var idx = 0; idx < tracks.length; idx++) {
|
||||
tracks[idx].enabled = !mute;
|
||||
}
|
||||
|
|
|
@ -23,21 +23,17 @@ function getPreviousResolution(resolution) {
|
|||
}
|
||||
|
||||
function setResolutionConstraints(constraints, resolution, isAndroid) {
|
||||
if (resolution && !constraints.video || isAndroid) {
|
||||
// same behaviour as true
|
||||
constraints.video = { mandatory: {}, optional: [] };
|
||||
}
|
||||
|
||||
if(Resolutions[resolution]) {
|
||||
if (Resolutions[resolution]) {
|
||||
constraints.video.mandatory.minWidth = Resolutions[resolution].width;
|
||||
constraints.video.mandatory.minHeight = Resolutions[resolution].height;
|
||||
}
|
||||
else {
|
||||
if (isAndroid) {
|
||||
constraints.video.mandatory.minWidth = 320;
|
||||
constraints.video.mandatory.minHeight = 240;
|
||||
constraints.video.mandatory.maxFrameRate = 15;
|
||||
}
|
||||
else if (isAndroid) {
|
||||
// FIXME can't remember if the purpose of this was to always request
|
||||
// low resolution on Android ? if yes it should be moved up front
|
||||
constraints.video.mandatory.minWidth = 320;
|
||||
constraints.video.mandatory.minHeight = 240;
|
||||
constraints.video.mandatory.maxFrameRate = 15;
|
||||
}
|
||||
|
||||
if (constraints.video.mandatory.minWidth)
|
||||
|
@ -55,10 +51,28 @@ function getConstraints(um, resolution, bandwidth, fps, desktopStream, isAndroid
|
|||
if (um.indexOf('video') >= 0) {
|
||||
// same behaviour as true
|
||||
constraints.video = { mandatory: {}, optional: [] };
|
||||
|
||||
constraints.video.optional.push({ googLeakyBucket: true });
|
||||
|
||||
setResolutionConstraints(constraints, resolution, isAndroid);
|
||||
}
|
||||
if (um.indexOf('audio') >= 0) {
|
||||
// same behaviour as true
|
||||
constraints.audio = { mandatory: {}, optional: []};
|
||||
if (!RTCBrowserType.isFirefox()) {
|
||||
// same behaviour as true
|
||||
constraints.audio = { mandatory: {}, optional: []};
|
||||
// if it is good enough for hangouts...
|
||||
constraints.audio.optional.push(
|
||||
{googEchoCancellation: true},
|
||||
{googAutoGainControl: true},
|
||||
{googNoiseSupression: true},
|
||||
{googHighpassFilter: true},
|
||||
{googNoisesuppression2: true},
|
||||
{googEchoCancellation2: true},
|
||||
{googAutoGainControl2: true}
|
||||
);
|
||||
} else {
|
||||
constraints.audio = true;
|
||||
}
|
||||
}
|
||||
if (um.indexOf('screen') >= 0) {
|
||||
if (RTCBrowserType.isChrome()) {
|
||||
|
@ -100,30 +114,6 @@ function getConstraints(um, resolution, bandwidth, fps, desktopStream, isAndroid
|
|||
};
|
||||
}
|
||||
|
||||
if (constraints.audio) {
|
||||
// if it is good enough for hangouts...
|
||||
constraints.audio.optional.push(
|
||||
{googEchoCancellation: true},
|
||||
{googAutoGainControl: true},
|
||||
{googNoiseSupression: true},
|
||||
{googHighpassFilter: true},
|
||||
{googNoisesuppression2: true},
|
||||
{googEchoCancellation2: true},
|
||||
{googAutoGainControl2: true}
|
||||
);
|
||||
}
|
||||
if (constraints.video) {
|
||||
if (um.indexOf('video') >= 0) {
|
||||
constraints.video.optional.push(
|
||||
{googLeakyBucket: true}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (um.indexOf('video') >= 0) {
|
||||
setResolutionConstraints(constraints, resolution, isAndroid);
|
||||
}
|
||||
|
||||
if (bandwidth) {
|
||||
if (!constraints.video) {
|
||||
//same behaviour as true
|
||||
|
|
158
modules/UI/UI.js
158
modules/UI/UI.js
|
@ -21,6 +21,7 @@ var messageHandler = UI.messageHandler;
|
|||
var Authentication = require("./authentication/Authentication");
|
||||
var UIUtil = require("./util/UIUtil");
|
||||
var NicknameHandler = require("./util/NicknameHandler");
|
||||
var JitsiPopover = require("./util/JitsiPopover");
|
||||
var CQEvents = require("../../service/connectionquality/CQEvents");
|
||||
var DesktopSharingEventTypes
|
||||
= require("../../service/desktopsharing/DesktopSharingEventTypes");
|
||||
|
@ -169,13 +170,13 @@ function registerListeners() {
|
|||
VideoLayout.setDeviceAvailabilityIcons(null, devices);
|
||||
});
|
||||
APP.RTC.addListener(RTCEvents.VIDEO_MUTE, UI.setVideoMuteButtonsState);
|
||||
APP.RTC.addListener(RTCEvents.DATA_CHANNEL_OPEN, function() {
|
||||
APP.RTC.addListener(RTCEvents.DATA_CHANNEL_OPEN, function () {
|
||||
// when the data channel becomes available, tell the bridge about video
|
||||
// selections so that it can do adaptive simulcast,
|
||||
// we want the notification to trigger even if userJid is undefined,
|
||||
// or null.
|
||||
var userJid = APP.UI.getLargeVideoJid();
|
||||
eventEmitter.emit(UIEvents.SELECTED_ENDPOINT, userJid);
|
||||
var userResource = APP.UI.getLargeVideoResource();
|
||||
eventEmitter.emit(UIEvents.SELECTED_ENDPOINT, userResource);
|
||||
});
|
||||
APP.statistics.addAudioLevelListener(function(jid, audioLevel) {
|
||||
var resourceJid;
|
||||
|
@ -189,7 +190,7 @@ function registerListeners() {
|
|||
}
|
||||
|
||||
AudioLevels.updateAudioLevel(resourceJid, audioLevel,
|
||||
UI.getLargeVideoJid());
|
||||
UI.getLargeVideoResource());
|
||||
});
|
||||
APP.desktopsharing.addListener(function () {
|
||||
ToolbarToggler.showDesktopSharingButton();
|
||||
|
@ -254,10 +255,8 @@ function registerListeners() {
|
|||
APP.xmpp.addListener(XMPPEvents.MUC_ROLE_CHANGED, onMucRoleChanged);
|
||||
APP.xmpp.addListener(XMPPEvents.PRESENCE_STATUS, onMucPresenceStatus);
|
||||
APP.xmpp.addListener(XMPPEvents.SUBJECT_CHANGED, chatSetSubject);
|
||||
APP.xmpp.addListener(XMPPEvents.MESSAGE_RECEIVED, updateChatConversation);
|
||||
APP.xmpp.addListener(XMPPEvents.MUC_MEMBER_LEFT, onMucMemberLeft);
|
||||
APP.xmpp.addListener(XMPPEvents.PASSWORD_REQUIRED, onPasswordRequired);
|
||||
APP.xmpp.addListener(XMPPEvents.CHAT_ERROR_RECEIVED, chatAddError);
|
||||
APP.xmpp.addListener(XMPPEvents.ETHERPAD, initEtherpad);
|
||||
APP.xmpp.addListener(XMPPEvents.AUTHENTICATION_REQUIRED,
|
||||
onAuthenticationRequired);
|
||||
|
@ -269,11 +268,11 @@ function registerListeners() {
|
|||
|
||||
APP.xmpp.addListener(XMPPEvents.AUDIO_MUTED, VideoLayout.onAudioMute);
|
||||
APP.xmpp.addListener(XMPPEvents.VIDEO_MUTED, VideoLayout.onVideoMute);
|
||||
APP.xmpp.addListener(XMPPEvents.AUDIO_MUTED_BY_FOCUS, function(doMuteAudio) {
|
||||
APP.xmpp.addListener(XMPPEvents.AUDIO_MUTED_BY_FOCUS, function (doMuteAudio) {
|
||||
UI.setAudioMuted(doMuteAudio);
|
||||
});
|
||||
APP.members.addListener(MemberEvents.DTMF_SUPPORT_CHANGED,
|
||||
onDtmfSupportChanged);
|
||||
onDtmfSupportChanged);
|
||||
APP.xmpp.addListener(XMPPEvents.START_MUTED_SETTING_CHANGED, function (audio, video) {
|
||||
SettingsMenu.setStartMuted(audio, video);
|
||||
});
|
||||
|
@ -286,43 +285,43 @@ function registerListeners() {
|
|||
"dialog.internalError");
|
||||
});
|
||||
|
||||
APP.xmpp.addListener(XMPPEvents.SET_LOCAL_DESCRIPTION_ERROR, function() {
|
||||
APP.xmpp.addListener(XMPPEvents.SET_LOCAL_DESCRIPTION_ERROR, function () {
|
||||
messageHandler.showError("dialog.error",
|
||||
"dialog.SLDFailure");
|
||||
"dialog.SLDFailure");
|
||||
});
|
||||
APP.xmpp.addListener(XMPPEvents.SET_REMOTE_DESCRIPTION_ERROR, function() {
|
||||
APP.xmpp.addListener(XMPPEvents.SET_REMOTE_DESCRIPTION_ERROR, function () {
|
||||
messageHandler.showError("dialog.error",
|
||||
"dialog.SRDFailure");
|
||||
});
|
||||
APP.xmpp.addListener(XMPPEvents.CREATE_ANSWER_ERROR, function() {
|
||||
APP.xmpp.addListener(XMPPEvents.CREATE_ANSWER_ERROR, function () {
|
||||
messageHandler.showError();
|
||||
});
|
||||
APP.xmpp.addListener(XMPPEvents.PROMPT_FOR_LOGIN, function() {
|
||||
APP.xmpp.addListener(XMPPEvents.PROMPT_FOR_LOGIN, function () {
|
||||
// FIXME: re-use LoginDialog which supports retries
|
||||
UI.showLoginPopup(connect);
|
||||
});
|
||||
|
||||
APP.xmpp.addListener(XMPPEvents.FOCUS_DISCONNECTED, function(focusComponent, retrySec) {
|
||||
|
||||
APP.xmpp.addListener(XMPPEvents.FOCUS_DISCONNECTED, function (focusComponent, retrySec) {
|
||||
UI.messageHandler.notify(
|
||||
null, "notify.focus",
|
||||
'disconnected', "notify.focusFail",
|
||||
{component: focusComponent, ms: retrySec});
|
||||
});
|
||||
|
||||
APP.xmpp.addListener(XMPPEvents.ROOM_JOIN_ERROR, function(pres) {
|
||||
|
||||
APP.xmpp.addListener(XMPPEvents.ROOM_JOIN_ERROR, function (pres) {
|
||||
UI.messageHandler.openReportDialog(null,
|
||||
"dialog.joinError", pres);
|
||||
});
|
||||
APP.xmpp.addListener(XMPPEvents.ROOM_CONNECT_ERROR, function(pres) {
|
||||
APP.xmpp.addListener(XMPPEvents.ROOM_CONNECT_ERROR, function (pres) {
|
||||
UI.messageHandler.openReportDialog(null,
|
||||
"dialog.connectError", pres);
|
||||
});
|
||||
|
||||
APP.xmpp.addListener(XMPPEvents.READY_TO_JOIN, function() {
|
||||
APP.xmpp.addListener(XMPPEvents.READY_TO_JOIN, function () {
|
||||
var roomName = UI.generateRoomName();
|
||||
APP.xmpp.allocateConferenceFocus(roomName, UI.checkForNicknameAndJoin);
|
||||
});
|
||||
|
||||
|
||||
//NicknameHandler emits this event
|
||||
UI.addListener(UIEvents.NICKNAME_CHANGED, function (nickname) {
|
||||
APP.xmpp.addToPresence("displayName", nickname);
|
||||
|
@ -332,10 +331,14 @@ function registerListeners() {
|
|||
AudioLevels.init();
|
||||
});
|
||||
|
||||
// Listens for video interruption events.
|
||||
APP.xmpp.addListener(XMPPEvents.CONNECTION_INTERRUPTED, VideoLayout.onVideoInterrupted);
|
||||
// Listens for video restores events.
|
||||
APP.xmpp.addListener(XMPPEvents.CONNECTION_RESTORED, VideoLayout.onVideoRestored);
|
||||
if (!interfaceConfig.filmStripOnly) {
|
||||
APP.xmpp.addListener(XMPPEvents.MESSAGE_RECEIVED, updateChatConversation);
|
||||
APP.xmpp.addListener(XMPPEvents.CHAT_ERROR_RECEIVED, chatAddError);
|
||||
// Listens for video interruption events.
|
||||
APP.xmpp.addListener(XMPPEvents.CONNECTION_INTERRUPTED, VideoLayout.onVideoInterrupted);
|
||||
// Listens for video restores events.
|
||||
APP.xmpp.addListener(XMPPEvents.CONNECTION_RESTORED, VideoLayout.onVideoRestored);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -385,9 +388,6 @@ UI.start = function (init) {
|
|||
|
||||
$("#welcome_page").hide();
|
||||
|
||||
$("#videospace").mousemove(function () {
|
||||
return ToolbarToggler.showToolbar();
|
||||
});
|
||||
// Set the defaults for prompt dialogs.
|
||||
$.prompt.setDefaults({persistent: false});
|
||||
|
||||
|
@ -399,34 +399,39 @@ UI.start = function (init) {
|
|||
|
||||
bindEvents();
|
||||
setupPrezi();
|
||||
setupToolbars();
|
||||
setupChat();
|
||||
|
||||
if (!interfaceConfig.filmStripOnly) {
|
||||
$("#videospace").mousemove(function () {
|
||||
return ToolbarToggler.showToolbar();
|
||||
});
|
||||
setupToolbars();
|
||||
setupChat();
|
||||
// Display notice message at the top of the toolbar
|
||||
if (config.noticeMessage) {
|
||||
$('#noticeText').text(config.noticeMessage);
|
||||
$('#notice').css({display: 'block'});
|
||||
}
|
||||
$("#downloadlog").click(function (event) {
|
||||
dump(event.target);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
$("#header").css("display", "none");
|
||||
$("#bottomToolbar").css("display", "none");
|
||||
$("#downloadlog").css("display", "none");
|
||||
$("#remoteVideos").css("padding", "0px 0px 18px 0px");
|
||||
$("#remoteVideos").css("right", "0px");
|
||||
messageHandler.disableNotifications();
|
||||
$('body').popover("disable");
|
||||
// $("[data-toggle=popover]").popover("disable");
|
||||
JitsiPopover.enabled = false;
|
||||
}
|
||||
|
||||
document.title = interfaceConfig.APP_NAME;
|
||||
|
||||
$("#downloadlog").click(function (event) {
|
||||
dump(event.target);
|
||||
});
|
||||
|
||||
if(config.enableWelcomePage && window.location.pathname == "/" &&
|
||||
(!window.localStorage.welcomePageDisabled ||
|
||||
window.localStorage.welcomePageDisabled == "false")) {
|
||||
$("#videoconference_page").hide();
|
||||
if (!setupWelcomePage)
|
||||
setupWelcomePage = require("./welcome_page/WelcomePage");
|
||||
setupWelcomePage();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$("#welcome_page").hide();
|
||||
|
||||
// Display notice message at the top of the toolbar
|
||||
if (config.noticeMessage) {
|
||||
$('#noticeText').text(config.noticeMessage);
|
||||
$('#notice').css({display: 'block'});
|
||||
}
|
||||
|
||||
if(config.requireDisplayName) {
|
||||
var currentSettings = Settings.getSettings();
|
||||
|
@ -437,30 +442,33 @@ UI.start = function (init) {
|
|||
|
||||
init();
|
||||
|
||||
toastr.options = {
|
||||
"closeButton": true,
|
||||
"debug": false,
|
||||
"positionClass": "notification-bottom-right",
|
||||
"onclick": null,
|
||||
"showDuration": "300",
|
||||
"hideDuration": "1000",
|
||||
"timeOut": "2000",
|
||||
"extendedTimeOut": "1000",
|
||||
"showEasing": "swing",
|
||||
"hideEasing": "linear",
|
||||
"showMethod": "fadeIn",
|
||||
"hideMethod": "fadeOut",
|
||||
"reposition": function() {
|
||||
if(PanelToggler.isVisible()) {
|
||||
$("#toast-container").addClass("notification-bottom-right-center");
|
||||
} else {
|
||||
$("#toast-container").removeClass("notification-bottom-right-center");
|
||||
}
|
||||
},
|
||||
"newestOnTop": false
|
||||
};
|
||||
if (!interfaceConfig.filmStripOnly) {
|
||||
toastr.options = {
|
||||
"closeButton": true,
|
||||
"debug": false,
|
||||
"positionClass": "notification-bottom-right",
|
||||
"onclick": null,
|
||||
"showDuration": "300",
|
||||
"hideDuration": "1000",
|
||||
"timeOut": "2000",
|
||||
"extendedTimeOut": "1000",
|
||||
"showEasing": "swing",
|
||||
"hideEasing": "linear",
|
||||
"showMethod": "fadeIn",
|
||||
"hideMethod": "fadeOut",
|
||||
"reposition": function () {
|
||||
if (PanelToggler.isVisible()) {
|
||||
$("#toast-container").addClass("notification-bottom-right-center");
|
||||
} else {
|
||||
$("#toast-container").removeClass("notification-bottom-right-center");
|
||||
}
|
||||
},
|
||||
"newestOnTop": false
|
||||
};
|
||||
|
||||
SettingsMenu.init();
|
||||
|
||||
SettingsMenu.init();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
@ -533,6 +541,8 @@ function onLocalRoleChanged(jid, info, pres, isModerator) {
|
|||
Authentication.closeAuthenticationWindow();
|
||||
messageHandler.notify(null, "notify.me",
|
||||
'connected', "notify.moderator");
|
||||
|
||||
Toolbar.checkAutoRecord();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -667,8 +677,8 @@ UI.inputDisplayNameHandler = function (value) {
|
|||
VideoLayout.inputDisplayNameHandler(value);
|
||||
};
|
||||
|
||||
UI.getLargeVideoJid = function() {
|
||||
return VideoLayout.getLargeVideoJid();
|
||||
UI.getLargeVideoResource = function () {
|
||||
return VideoLayout.getLargeVideoResource();
|
||||
};
|
||||
|
||||
UI.generateRoomName = function() {
|
||||
|
|
|
@ -13,6 +13,7 @@ var AuthenticationEvents
|
|||
var roomUrl = null;
|
||||
var sharedKey = '';
|
||||
var UI = null;
|
||||
var recordingToaster = null;
|
||||
|
||||
var buttonHandlers = {
|
||||
"toolbar_button_mute": function () {
|
||||
|
@ -122,8 +123,13 @@ function hangup() {
|
|||
* Starts or stops the recording for the conference.
|
||||
*/
|
||||
|
||||
function toggleRecording() {
|
||||
function toggleRecording(predefinedToken) {
|
||||
APP.xmpp.toggleRecording(function (callback) {
|
||||
if (predefinedToken) {
|
||||
callback(UIUtil.escapeHtml(predefinedToken));
|
||||
return;
|
||||
}
|
||||
|
||||
var msg = APP.translation.generateTranslationHTML(
|
||||
"dialog.recordingToken");
|
||||
var token = APP.translation.translateString("dialog.token");
|
||||
|
@ -147,7 +153,7 @@ function toggleRecording() {
|
|||
function () { },
|
||||
':input:first'
|
||||
);
|
||||
}, Toolbar.setRecordingButtonState, Toolbar.setRecordingButtonState);
|
||||
}, Toolbar.setRecordingButtonState);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -548,17 +554,54 @@ var Toolbar = (function (my) {
|
|||
};
|
||||
|
||||
// Sets the state of the recording button
|
||||
my.setRecordingButtonState = function (isRecording) {
|
||||
my.setRecordingButtonState = function (recordingState) {
|
||||
var selector = $('#toolbar_button_record');
|
||||
if (isRecording) {
|
||||
|
||||
if (recordingState === 'on') {
|
||||
selector.removeClass("icon-recEnable");
|
||||
selector.addClass("icon-recEnable active");
|
||||
} else {
|
||||
|
||||
$("#largeVideo").toggleClass("videoMessageFilter", true);
|
||||
var recordOnKey = "recording.on";
|
||||
$('#videoConnectionMessage').attr("data-i18n", recordOnKey);
|
||||
$('#videoConnectionMessage').text(APP.translation.translateString(recordOnKey));
|
||||
|
||||
setTimeout(function(){
|
||||
$("#largeVideo").toggleClass("videoMessageFilter", false);
|
||||
$('#videoConnectionMessage').css({display: "none"});
|
||||
}, 1500);
|
||||
|
||||
recordingToaster = messageHandler.notify(null, "recording.toaster", null,
|
||||
null, null, {timeOut: 0, closeButton: null, tapToDismiss: false});
|
||||
} else if (recordingState === 'off') {
|
||||
selector.removeClass("icon-recEnable active");
|
||||
selector.addClass("icon-recEnable");
|
||||
|
||||
$("#largeVideo").toggleClass("videoMessageFilter", false);
|
||||
$('#videoConnectionMessage').css({display: "none"});
|
||||
|
||||
if (recordingToaster)
|
||||
messageHandler.remove(recordingToaster);
|
||||
|
||||
} else if (recordingState === 'pending') {
|
||||
selector.removeClass("icon-recEnable active");
|
||||
selector.addClass("icon-recEnable");
|
||||
|
||||
$("#largeVideo").toggleClass("videoMessageFilter", true);
|
||||
var recordPendingKey = "recording.pending";
|
||||
$('#videoConnectionMessage').attr("data-i18n", recordPendingKey);
|
||||
$('#videoConnectionMessage').text(APP.translation.translateString(recordPendingKey));
|
||||
$('#videoConnectionMessage').css({display: "block"});
|
||||
}
|
||||
};
|
||||
|
||||
// checks whether recording is enabled and whether we have params to start automatically recording
|
||||
my.checkAutoRecord = function () {
|
||||
if (config.enableRecording && config.autoRecord) {
|
||||
toggleRecording(config.autoRecordToken);
|
||||
}
|
||||
}
|
||||
|
||||
// Shows or hides SIP calls button
|
||||
my.showSipCallButton = function (show) {
|
||||
if (APP.xmpp.isSipGatewayEnabled() && show) {
|
||||
|
|
|
@ -53,6 +53,8 @@ var ToolbarToggler = {
|
|||
* Shows the main toolbar.
|
||||
*/
|
||||
showToolbar: function () {
|
||||
if (interfaceConfig.filmStripOnly)
|
||||
return;
|
||||
var header = $("#header"),
|
||||
bottomToolbar = $("#bottomToolbar");
|
||||
if (!header.is(':visible') || !bottomToolbar.is(":visible")) {
|
||||
|
@ -88,6 +90,9 @@ var ToolbarToggler = {
|
|||
* @param isDock indicates what operation to perform
|
||||
*/
|
||||
dockToolbar: function (isDock) {
|
||||
if (interfaceConfig.filmStripOnly)
|
||||
return;
|
||||
|
||||
if (isDock) {
|
||||
// First make sure the toolbar is shown.
|
||||
if (!$('#header').is(':visible')) {
|
||||
|
|
|
@ -46,6 +46,8 @@ var JitsiPopover = (function () {
|
|||
* Shows the popover
|
||||
*/
|
||||
JitsiPopover.prototype.show = function () {
|
||||
if(!JitsiPopover.enabled)
|
||||
return;
|
||||
this.createPopover();
|
||||
this.popoverShown = true;
|
||||
};
|
||||
|
@ -118,6 +120,8 @@ var JitsiPopover = (function () {
|
|||
this.createPopover();
|
||||
};
|
||||
|
||||
JitsiPopover.enabled = true;
|
||||
|
||||
return JitsiPopover;
|
||||
})();
|
||||
|
||||
|
|
|
@ -1,4 +1,11 @@
|
|||
/* global $, APP, jQuery, toastr */
|
||||
|
||||
/**
|
||||
* Flag for enable/disable of the notifications.
|
||||
* @type {boolean}
|
||||
*/
|
||||
var notificationsEnabled = true;
|
||||
|
||||
var messageHandler = (function(my) {
|
||||
|
||||
/**
|
||||
|
@ -172,8 +179,19 @@ var messageHandler = (function(my) {
|
|||
messageHandler.openMessageDialog(titleKey, msgKey);
|
||||
};
|
||||
|
||||
/**
|
||||
* Displayes notification.
|
||||
* @param displayName display name of the participant that is associated with the notification.
|
||||
* @param displayNameKey the key from the language file for the display name.
|
||||
* @param cls css class for the notification
|
||||
* @param messageKey the key from the language file for the text of the message.
|
||||
* @param messageArguments object with the arguments for the message.
|
||||
* @param options object with language options.
|
||||
*/
|
||||
my.notify = function(displayName, displayNameKey,
|
||||
cls, messageKey, messageArguments, options) {
|
||||
if(!notificationsEnabled)
|
||||
return;
|
||||
var displayNameSpan = '<span class="nickname" ';
|
||||
if (displayName) {
|
||||
displayNameSpan += ">" + displayName;
|
||||
|
@ -182,7 +200,7 @@ var messageHandler = (function(my) {
|
|||
"'>" + APP.translation.translateString(displayNameKey);
|
||||
}
|
||||
displayNameSpan += "</span>";
|
||||
toastr.info(
|
||||
return toastr.info(
|
||||
displayNameSpan + '<br>' +
|
||||
'<span class=' + cls + ' data-i18n="' + messageKey + '"' +
|
||||
(messageArguments?
|
||||
|
@ -193,6 +211,28 @@ var messageHandler = (function(my) {
|
|||
'</span>', null, options);
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes the toaster.
|
||||
* @param toasterElement
|
||||
*/
|
||||
my.remove = function(toasterElement) {
|
||||
toasterElement.remove();
|
||||
};
|
||||
|
||||
/**
|
||||
* Disables notifications.
|
||||
*/
|
||||
my.disableNotifications = function () {
|
||||
notificationsEnabled = false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Enables notifications.
|
||||
*/
|
||||
my.enableNotifications = function () {
|
||||
notificationsEnabled = true;
|
||||
};
|
||||
|
||||
return my;
|
||||
}(messageHandler || {}));
|
||||
|
||||
|
|
|
@ -469,6 +469,14 @@ var LargeVideo = {
|
|||
largeVideoHeight,
|
||||
horizontalIndent, verticalIndent, animate);
|
||||
},
|
||||
/**
|
||||
* Resizes the large html elements.
|
||||
* @param animate boolean property that indicates whether the resize should be animated or not.
|
||||
* @param isChatVisible boolean property that indicates whether the chat area is displayed or not.
|
||||
* If that parameter is null the method will check the chat pannel visibility.
|
||||
* @param completeFunction a function to be called when the video space is resized
|
||||
* @returns {*[]} array with the current width and height values of the largeVideo html element.
|
||||
*/
|
||||
resize: function (animate, isVisible, completeFunction) {
|
||||
if(!isEnabled)
|
||||
return;
|
||||
|
@ -481,18 +489,8 @@ var LargeVideo = {
|
|||
var top = availableHeight / 2 - avatarSize / 4 * 3;
|
||||
$('#activeSpeaker').css('top', top);
|
||||
|
||||
this.VideoLayout.resizeVideoSpace(animate, isVisible, completeFunction);
|
||||
if(animate) {
|
||||
$('#videospace').animate({
|
||||
right: window.innerWidth - availableWidth,
|
||||
width: availableWidth,
|
||||
height: availableHeight
|
||||
},
|
||||
{
|
||||
queue: false,
|
||||
duration: 500,
|
||||
complete: completeFunction
|
||||
});
|
||||
|
||||
$('#largeVideoContainer').animate({
|
||||
width: availableWidth,
|
||||
height: availableHeight
|
||||
|
@ -502,8 +500,6 @@ var LargeVideo = {
|
|||
duration: 500
|
||||
});
|
||||
} else {
|
||||
$('#videospace').width(availableWidth);
|
||||
$('#videospace').height(availableHeight);
|
||||
$('#largeVideoContainer').width(availableWidth);
|
||||
$('#largeVideoContainer').height(availableHeight);
|
||||
}
|
||||
|
|
|
@ -42,85 +42,96 @@ RemoteVideo.prototype.addRemoteVideoContainer = function() {
|
|||
* @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);
|
||||
if (!interfaceConfig.filmStripOnly) {
|
||||
RemoteVideo.prototype.addRemoteVideoMenu = function () {
|
||||
var spanElement = document.createElement('span');
|
||||
spanElement.className = 'remotevideomenu';
|
||||
|
||||
var menuElement = document.createElement('i');
|
||||
menuElement.className = 'fa fa-angle-down';
|
||||
menuElement.title = 'Remote user controls';
|
||||
spanElement.appendChild(menuElement);
|
||||
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.getResourceJid();
|
||||
spanElement.appendChild(popupmenuElement);
|
||||
var popupmenuElement = document.createElement('ul');
|
||||
popupmenuElement.className = 'popupmenu';
|
||||
popupmenuElement.id = 'remote_popupmenu_' + this.getResourceJid();
|
||||
spanElement.appendChild(popupmenuElement);
|
||||
|
||||
var muteMenuItem = document.createElement('li');
|
||||
var muteLinkItem = document.createElement('a');
|
||||
var muteMenuItem = document.createElement('li');
|
||||
var muteLinkItem = document.createElement('a');
|
||||
|
||||
var mutedIndicator = "<i style='float:left;' class='icon-mic-disabled'></i>";
|
||||
var mutedIndicator = "<i style='float:left;' " +
|
||||
"class='icon-mic-disabled'></i>";
|
||||
|
||||
if (!this.isMuted) {
|
||||
muteLinkItem.innerHTML = mutedIndicator +
|
||||
" <div style='width: 90px;margin-left: 20px;' data-i18n='videothumbnail.domute'></div>";
|
||||
muteLinkItem.className = 'mutelink';
|
||||
}
|
||||
else {
|
||||
muteLinkItem.innerHTML = mutedIndicator +
|
||||
" <div style='width: 90px;margin-left: 20px;' data-i18n='videothumbnail.muted'></div>";
|
||||
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 +
|
||||
" <div style='width: 90px;margin-left: 20px;' data-i18n='videothumbnail.muted'></div>";
|
||||
this.className = 'mutelink disabled';
|
||||
if (!this.isMuted) {
|
||||
muteLinkItem.innerHTML = mutedIndicator +
|
||||
" <div style='width: 90px;margin-left: 20px;' " +
|
||||
"data-i18n='videothumbnail.domute'></div>";
|
||||
muteLinkItem.className = 'mutelink';
|
||||
}
|
||||
else {
|
||||
this.innerHTML = mutedIndicator +
|
||||
" <div style='width: 90px;margin-left: 20px;' data-i18n='videothumbnail.domute'></div>";
|
||||
this.className = 'mutelink';
|
||||
muteLinkItem.innerHTML = mutedIndicator +
|
||||
" <div style='width: 90px;margin-left: 20px;' " +
|
||||
"data-i18n='videothumbnail.muted'></div>";
|
||||
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 +
|
||||
" <div style='width: 90px;margin-left: 20px;' " +
|
||||
"data-i18n='videothumbnail.muted'></div>";
|
||||
this.className = 'mutelink disabled';
|
||||
}
|
||||
else {
|
||||
this.innerHTML = mutedIndicator +
|
||||
" <div style='width: 90px;margin-left: 20px;' " +
|
||||
"data-i18n='videothumbnail.domute'></div>";
|
||||
this.className = 'mutelink';
|
||||
}
|
||||
};
|
||||
|
||||
muteMenuItem.appendChild(muteLinkItem);
|
||||
popupmenuElement.appendChild(muteMenuItem);
|
||||
|
||||
var ejectIndicator = "<i style='float:left;' class='fa fa-eject'></i>";
|
||||
|
||||
var ejectMenuItem = document.createElement('li');
|
||||
var ejectLinkItem = document.createElement('a');
|
||||
var ejectText = "<div style='width: 90px;margin-left: 20px;' " +
|
||||
"data-i18n='videothumbnail.kick'> </div>";
|
||||
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"));
|
||||
};
|
||||
|
||||
muteMenuItem.appendChild(muteLinkItem);
|
||||
popupmenuElement.appendChild(muteMenuItem);
|
||||
|
||||
var ejectIndicator = "<i style='float:left;' class='fa fa-eject'></i>";
|
||||
|
||||
var ejectMenuItem = document.createElement('li');
|
||||
var ejectLinkItem = document.createElement('a');
|
||||
var ejectText = "<div style='width: 90px;margin-left: 20px;' data-i18n='videothumbnail.kick'> </div>";
|
||||
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"));
|
||||
};
|
||||
|
||||
} else {
|
||||
RemoteVideo.prototype.addRemoteVideoMenu = function() {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the remote stream element corresponding to the given stream and
|
||||
|
|
|
@ -3,6 +3,7 @@ var AudioLevels = require("../audio_levels/AudioLevels");
|
|||
var ContactList = require("../side_pannels/contactlist/ContactList");
|
||||
var MediaStreamType = require("../../../service/RTC/MediaStreamTypes");
|
||||
var UIEvents = require("../../../service/UI/UIEvents");
|
||||
var UIUtil = require("../util/UIUtil");
|
||||
|
||||
var RTC = require("../../RTC/RTC");
|
||||
var RTCBrowserType = require('../../RTC/RTCBrowserType');
|
||||
|
@ -11,6 +12,7 @@ var RemoteVideo = require("./RemoteVideo");
|
|||
var LargeVideo = require("./LargeVideo");
|
||||
var LocalVideo = require("./LocalVideo");
|
||||
|
||||
|
||||
var remoteVideos = {};
|
||||
var remoteVideoTypes = {};
|
||||
var localVideoThumbnail = null;
|
||||
|
@ -34,8 +36,12 @@ var VideoLayout = (function (my) {
|
|||
my.init = function (emitter) {
|
||||
eventEmitter = emitter;
|
||||
localVideoThumbnail = new LocalVideo(VideoLayout);
|
||||
if (interfaceConfig.filmStripOnly) {
|
||||
LargeVideo.disable();
|
||||
} else {
|
||||
LargeVideo.init(VideoLayout, emitter);
|
||||
}
|
||||
|
||||
LargeVideo.init(VideoLayout, emitter);
|
||||
VideoLayout.resizeLargeVideoContainer();
|
||||
|
||||
};
|
||||
|
@ -164,7 +170,7 @@ var VideoLayout = (function (my) {
|
|||
}
|
||||
};
|
||||
|
||||
my.getLargeVideoJid = function () {
|
||||
my.getLargeVideoResource = function () {
|
||||
return LargeVideo.getResourceJid();
|
||||
};
|
||||
|
||||
|
@ -192,7 +198,7 @@ var VideoLayout = (function (my) {
|
|||
resourceJid) {
|
||||
if(focusedVideoResourceJid) {
|
||||
var oldSmallVideo = VideoLayout.getSmallVideo(focusedVideoResourceJid);
|
||||
if(oldSmallVideo)
|
||||
if (oldSmallVideo && !interfaceConfig.filmStripOnly)
|
||||
oldSmallVideo.focus(false);
|
||||
}
|
||||
|
||||
|
@ -219,7 +225,7 @@ var VideoLayout = (function (my) {
|
|||
|
||||
// Update focused/pinned interface.
|
||||
if (resourceJid) {
|
||||
if(smallVideo)
|
||||
if (smallVideo && !interfaceConfig.filmStripOnly)
|
||||
smallVideo.focus(true);
|
||||
|
||||
if (!noPinnedEndpointChangedEvent) {
|
||||
|
@ -354,7 +360,11 @@ var VideoLayout = (function (my) {
|
|||
* Resizes the large video container.
|
||||
*/
|
||||
my.resizeLargeVideoContainer = function () {
|
||||
LargeVideo.resize();
|
||||
if(LargeVideo.isEnabled()) {
|
||||
LargeVideo.resize();
|
||||
} else {
|
||||
VideoLayout.resizeVideoSpace();
|
||||
}
|
||||
VideoLayout.resizeThumbnails();
|
||||
LargeVideo.position();
|
||||
};
|
||||
|
@ -373,7 +383,7 @@ var VideoLayout = (function (my) {
|
|||
|
||||
if(animate) {
|
||||
$('#remoteVideos').animate({
|
||||
height: height
|
||||
height: height + 2 // adds 2 px because of small video 1px border
|
||||
},
|
||||
{
|
||||
queue: false,
|
||||
|
@ -398,7 +408,7 @@ var VideoLayout = (function (my) {
|
|||
} else {
|
||||
// size videos so that while keeping AR and max height, we have a
|
||||
// nice fit
|
||||
$('#remoteVideos').height(height);
|
||||
$('#remoteVideos').height(height + 2);// adds 2 px because of small video 1px border
|
||||
$('#remoteVideos>span').width(width);
|
||||
$('#remoteVideos>span').height(height);
|
||||
|
||||
|
@ -431,7 +441,7 @@ var VideoLayout = (function (my) {
|
|||
var availableWidth = availableWinWidth / numvids;
|
||||
var aspectRatio = 16.0 / 9.0;
|
||||
var maxHeight = Math.min(160, availableHeight);
|
||||
availableHeight = Math.min(maxHeight, availableWidth / aspectRatio);
|
||||
availableHeight = Math.min(maxHeight, availableWidth / aspectRatio, window.innerHeight - 18);
|
||||
if (availableHeight < availableWidth / aspectRatio) {
|
||||
availableWidth = Math.floor(availableHeight * aspectRatio);
|
||||
}
|
||||
|
@ -886,6 +896,37 @@ var VideoLayout = (function (my) {
|
|||
VideoLayout.resizeThumbnails(true);
|
||||
};
|
||||
|
||||
/**
|
||||
* Resizes the #videospace html element
|
||||
* @param animate boolean property that indicates whether the resize should be animated or not.
|
||||
* @param isChatVisible boolean property that indicates whether the chat area is displayed or not.
|
||||
* If that parameter is null the method will check the chat pannel visibility.
|
||||
* @param completeFunction a function to be called when the video space is resized
|
||||
*/
|
||||
my.resizeVideoSpace = function (animate, isChatVisible, completeFunction) {
|
||||
var availableHeight = window.innerHeight;
|
||||
var availableWidth = UIUtil.getAvailableVideoWidth(isChatVisible);
|
||||
|
||||
if (availableWidth < 0 || availableHeight < 0) return;
|
||||
|
||||
if(animate) {
|
||||
$('#videospace').animate({
|
||||
right: window.innerWidth - availableWidth,
|
||||
width: availableWidth,
|
||||
height: availableHeight
|
||||
},
|
||||
{
|
||||
queue: false,
|
||||
duration: 500,
|
||||
complete: completeFunction
|
||||
});
|
||||
} else {
|
||||
$('#videospace').width(availableWidth);
|
||||
$('#videospace').height(availableHeight);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
my.getSmallVideo = function (resourceJid) {
|
||||
if(resourceJid == APP.xmpp.myResource()) {
|
||||
return localVideoThumbnail;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* global $, $iq, config */
|
||||
/* global $, $iq, config, interfaceConfig */
|
||||
var params = {};
|
||||
function getConfigParamsFromUrl() {
|
||||
if(!location.hash)
|
||||
|
@ -17,22 +17,31 @@ params = getConfigParamsFromUrl();
|
|||
|
||||
var URLProcessor = {
|
||||
setConfigParametersFromUrl: function () {
|
||||
for(var k in params)
|
||||
{
|
||||
if(typeof k !== "string" || k.indexOf("config.") === -1)
|
||||
for(var key in params) {
|
||||
if(typeof key !== "string")
|
||||
continue;
|
||||
|
||||
var v = params[k];
|
||||
var confKey = k.substr(7);
|
||||
if(config[confKey] && typeof config[confKey] !== typeof v)
|
||||
var confObj = null, confKey;
|
||||
if (key.indexOf("config.") === 0) {
|
||||
confObj = config;
|
||||
confKey = key.substr("config.".length);
|
||||
} else if (key.indexOf("interfaceConfig.") === 0) {
|
||||
confObj = interfaceConfig;
|
||||
confKey = key.substr("interfaceConfig.".length);
|
||||
}
|
||||
|
||||
if (!confObj)
|
||||
continue;
|
||||
|
||||
var value = params[key];
|
||||
if (confObj[confKey] && typeof confObj[confKey] !== typeof value)
|
||||
{
|
||||
console.warn("The type of " + k +
|
||||
console.warn("The type of " + key +
|
||||
" is wrong. That parameter won't be updated in config.js.");
|
||||
continue;
|
||||
}
|
||||
|
||||
config[confKey] = v;
|
||||
|
||||
confObj[confKey] = value;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -1,8 +1,21 @@
|
|||
/* global $ */
|
||||
|
||||
/*
|
||||
The purpose of this hack is to re-use SSRC of first video stream ever created
|
||||
for any video streams created later on. In order to do that this hack:
|
||||
Here we do modifications of local video SSRCs. There are 2 situations we have
|
||||
to handle:
|
||||
|
||||
1. We generate SSRC for local recvonly video stream. This is the case when we
|
||||
have no local camera and it is not generated automatically, but SSRC=1 is
|
||||
used implicitly. If that happens RTCP packets will be dropped by the JVB
|
||||
and we won't be able to request video key frames correctly.
|
||||
|
||||
2. A hack to re-use SSRC of the first video stream for any new stream created
|
||||
in future. It turned out that Chrome may keep on using the SSRC of removed
|
||||
video stream in RTCP even though a new one has been created. So we just
|
||||
want to avoid that by re-using it. Jingle 'source-remove'/'source-add'
|
||||
notifications are blocked once first video SSRC is advertised to the focus.
|
||||
|
||||
What this hack does:
|
||||
|
||||
1. Stores the SSRC of the first video stream created by
|
||||
a) scanning Jingle session-accept/session-invite for existing video SSRC
|
||||
|
@ -19,12 +32,32 @@
|
|||
*/
|
||||
|
||||
var SDP = require('./SDP');
|
||||
var RTCBrowserType = require('../RTC/RTCBrowserType');
|
||||
|
||||
/**
|
||||
* The hack is enabled on all browsers except FF by default
|
||||
* FIXME finish the hack once removeStream method is implemented in FF
|
||||
* @type {boolean}
|
||||
*/
|
||||
var isEnabled = !RTCBrowserType.isFirefox();
|
||||
|
||||
/**
|
||||
* Stored SSRC of local video stream.
|
||||
*/
|
||||
var localVideoSSRC;
|
||||
|
||||
/**
|
||||
* SSRC used for recvonly video stream when we have no local camera.
|
||||
* This is in order to tell Chrome what SSRC should be used in RTCP requests
|
||||
* instead of 1.
|
||||
*/
|
||||
var localRecvOnlySSRC;
|
||||
|
||||
/**
|
||||
* cname for <tt>localRecvOnlySSRC</tt>
|
||||
*/
|
||||
var localRecvOnlyCName;
|
||||
|
||||
/**
|
||||
* Method removes <source> element which describes <tt>localVideoSSRC</tt>
|
||||
* from given Jingle IQ.
|
||||
|
@ -36,16 +69,17 @@ var localVideoSSRC;
|
|||
* other SSRCs left to be signaled after removing it.
|
||||
*/
|
||||
var filterOutSource = function (modifyIq, actionName) {
|
||||
if (!localVideoSSRC)
|
||||
return modifyIq;
|
||||
|
||||
var modifyIqTree = $(modifyIq.tree());
|
||||
|
||||
if (!localVideoSSRC)
|
||||
return modifyIqTree[0];
|
||||
|
||||
var videoSSRC = modifyIqTree.find(
|
||||
'>jingle>content[name="video"]' +
|
||||
'>description>source[ssrc="' + localVideoSSRC + '"]');
|
||||
|
||||
if (!videoSSRC.length) {
|
||||
return modifyIqTree;
|
||||
return modifyIqTree[0];
|
||||
}
|
||||
|
||||
console.info(
|
||||
|
@ -55,7 +89,7 @@ var filterOutSource = function (modifyIq, actionName) {
|
|||
|
||||
// Check if any sources still left to be added/removed
|
||||
if (modifyIqTree.find('>jingle>content>description>source').length) {
|
||||
return modifyIqTree;
|
||||
return modifyIqTree[0];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
@ -70,21 +104,41 @@ var storeLocalVideoSSRC = function (jingleIq) {
|
|||
$(jingleIq.tree())
|
||||
.find('>jingle>content[name="video"]>description>source');
|
||||
|
||||
console.info('Video desc: ', videoSSRCs);
|
||||
if (!videoSSRCs.length)
|
||||
return;
|
||||
|
||||
var ssrc = videoSSRCs.attr('ssrc');
|
||||
if (ssrc) {
|
||||
localVideoSSRC = ssrc;
|
||||
console.info(
|
||||
'Stored local video SSRC for future re-use: ' + localVideoSSRC);
|
||||
} else {
|
||||
console.error('No "ssrc" attribute present in <source> element');
|
||||
}
|
||||
videoSSRCs.each(function (idx, ssrcElem) {
|
||||
if (localVideoSSRC)
|
||||
return;
|
||||
// We consider SSRC real only if it has msid attribute
|
||||
// recvonly streams in FF do not have it as well as local SSRCs
|
||||
// we generate for recvonly streams in Chrome
|
||||
var ssrSel = $(ssrcElem);
|
||||
var msid = ssrSel.find('>parameter[name="msid"]');
|
||||
if (msid.length) {
|
||||
var ssrcVal = ssrSel.attr('ssrc');
|
||||
if (ssrcVal) {
|
||||
localVideoSSRC = ssrcVal;
|
||||
console.info('Stored local video SSRC' +
|
||||
' for future re-use: ' + localVideoSSRC);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var LocalVideoSSRCHack = {
|
||||
/**
|
||||
* Generates new SSRC for local video recvonly stream.
|
||||
* FIXME what about eventual SSRC collision ?
|
||||
*/
|
||||
function generateRecvonlySSRC() {
|
||||
//
|
||||
localRecvOnlySSRC =
|
||||
Math.random().toString(10).substring(2, 11);
|
||||
localRecvOnlyCName =
|
||||
Math.random().toString(36).substring(2);
|
||||
console.info(
|
||||
"Generated local recvonly SSRC: " + localRecvOnlySSRC +
|
||||
", cname: " + localRecvOnlyCName);
|
||||
}
|
||||
|
||||
var LocalSSRCReplacement = {
|
||||
/**
|
||||
* Method must be called before 'session-initiate' or 'session-invite' is
|
||||
* sent. Scans the IQ for local video SSRC and stores it if detected.
|
||||
|
@ -93,6 +147,9 @@ var LocalVideoSSRCHack = {
|
|||
* which will be scanned for local video SSRC.
|
||||
*/
|
||||
processSessionInit: function (sessionInit) {
|
||||
if (!isEnabled)
|
||||
return;
|
||||
|
||||
if (localVideoSSRC) {
|
||||
console.error("Local SSRC stored already: " + localVideoSSRC);
|
||||
return;
|
||||
|
@ -108,6 +165,9 @@ var LocalVideoSSRCHack = {
|
|||
* @returns modified <tt>localDescription</tt> object.
|
||||
*/
|
||||
mungeLocalVideoSSRC: function (localDescription) {
|
||||
if (!isEnabled)
|
||||
return localDescription;
|
||||
|
||||
// IF we have local video SSRC stored make sure it is replaced
|
||||
// with old SSRC
|
||||
if (localVideoSSRC) {
|
||||
|
@ -129,6 +189,25 @@ var LocalVideoSSRCHack = {
|
|||
new RegExp('a=ssrc:' + newSSRC, 'g'),
|
||||
'a=ssrc:' + localVideoSSRC);
|
||||
}
|
||||
} else {
|
||||
// Make sure we have any SSRC for recvonly video stream
|
||||
var sdp = new SDP(localDescription.sdp);
|
||||
|
||||
if (sdp.media[1] && sdp.media[1].indexOf('a=ssrc:') === -1 &&
|
||||
sdp.media[1].indexOf('a=recvonly') !== -1) {
|
||||
|
||||
if (!localRecvOnlySSRC) {
|
||||
generateRecvonlySSRC();
|
||||
}
|
||||
|
||||
console.info('No SSRC in video recvonly stream' +
|
||||
' - adding SSRC: ' + localRecvOnlySSRC);
|
||||
|
||||
sdp.media[1] += 'a=ssrc:' + localRecvOnlySSRC +
|
||||
' cname:' + localRecvOnlyCName + '\r\n';
|
||||
|
||||
localDescription.sdp = sdp.session + sdp.media.join('');
|
||||
}
|
||||
}
|
||||
return localDescription;
|
||||
},
|
||||
|
@ -143,6 +222,9 @@ var LocalVideoSSRCHack = {
|
|||
* a Strophe IQ Builder instance, but DOM element tree.
|
||||
*/
|
||||
processSourceAdd: function (sourceAdd) {
|
||||
if (!isEnabled)
|
||||
return sourceAdd;
|
||||
|
||||
if (!localVideoSSRC) {
|
||||
// Store local SSRC if available
|
||||
storeLocalVideoSSRC(sourceAdd);
|
||||
|
@ -162,8 +244,20 @@ var LocalVideoSSRCHack = {
|
|||
* a Strophe IQ Builder instance, but DOM element tree.
|
||||
*/
|
||||
processSourceRemove: function (sourceRemove) {
|
||||
if (!isEnabled)
|
||||
return sourceRemove;
|
||||
|
||||
return filterOutSource(sourceRemove, 'source-remove');
|
||||
},
|
||||
|
||||
/**
|
||||
* Turns the hack on or off
|
||||
* @param enabled <tt>true</tt> to enable the hack or <tt>false</tt>
|
||||
* to disable it
|
||||
*/
|
||||
setEnabled: function (enabled) {
|
||||
isEnabled = enabled;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = LocalVideoSSRCHack;
|
||||
module.exports = LocalSSRCReplacement;
|
|
@ -1,7 +1,7 @@
|
|||
var RTC = require('../RTC/RTC');
|
||||
var RTCBrowserType = require("../RTC/RTCBrowserType.js");
|
||||
var XMPPEvents = require("../../service/xmpp/XMPPEvents");
|
||||
var VideoSSRCHack = require("./VideoSSRCHack");
|
||||
var SSRCReplacement = require("./LocalSSRCReplacement");
|
||||
|
||||
function TraceablePeerConnection(ice_config, constraints, session) {
|
||||
var self = this;
|
||||
|
@ -213,7 +213,7 @@ if (TraceablePeerConnection.prototype.__defineGetter__ !== undefined) {
|
|||
function() {
|
||||
var desc = this.peerconnection.localDescription;
|
||||
|
||||
desc = VideoSSRCHack.mungeLocalVideoSSRC(desc);
|
||||
desc = SSRCReplacement.mungeLocalVideoSSRC(desc);
|
||||
|
||||
this.trace('getLocalDescription::preTransform', dumpSDP(desc));
|
||||
|
||||
|
@ -372,7 +372,7 @@ TraceablePeerConnection.prototype.createOffer
|
|||
self.trace('createOfferOnSuccess::postTransform (Plan B)', dumpSDP(offer));
|
||||
}
|
||||
|
||||
offer = VideoSSRCHack.mungeLocalVideoSSRC(offer);
|
||||
offer = SSRCReplacement.mungeLocalVideoSSRC(offer);
|
||||
|
||||
if (config.enableSimulcast && self.simulcast.isSupported()) {
|
||||
offer = self.simulcast.mungeLocalDescription(offer);
|
||||
|
@ -402,7 +402,7 @@ TraceablePeerConnection.prototype.createAnswer
|
|||
}
|
||||
|
||||
// munge local video SSRC
|
||||
answer = VideoSSRCHack.mungeLocalVideoSSRC(answer);
|
||||
answer = SSRCReplacement.mungeLocalVideoSSRC(answer);
|
||||
|
||||
if (config.enableSimulcast && self.simulcast.isSupported()) {
|
||||
answer = self.simulcast.mungeLocalDescription(answer);
|
||||
|
|
|
@ -19,6 +19,12 @@ var useJirecon = (typeof config.hosts.jirecon != "undefined");
|
|||
*/
|
||||
var jireconRid = null;
|
||||
|
||||
/**
|
||||
* The callback to update the recording button. Currently used from colibri
|
||||
* after receiving a pending status.
|
||||
*/
|
||||
var recordingStateChangeCallback = null;
|
||||
|
||||
function setRecordingToken(token) {
|
||||
recordingToken = token;
|
||||
}
|
||||
|
@ -30,9 +36,9 @@ function setRecordingJirecon(state, token, callback, connection) {
|
|||
|
||||
var iq = $iq({to: config.hosts.jirecon, type: 'set'})
|
||||
.c('recording', {xmlns: 'http://jitsi.org/protocol/jirecon',
|
||||
action: state ? 'start' : 'stop',
|
||||
action: (state === 'on') ? 'start' : 'stop',
|
||||
mucjid: connection.emuc.roomjid});
|
||||
if (!state){
|
||||
if (state === 'off'){
|
||||
iq.attrs({rid: jireconRid});
|
||||
}
|
||||
|
||||
|
@ -44,10 +50,10 @@ function setRecordingJirecon(state, token, callback, connection) {
|
|||
// 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') +
|
||||
console.log('Recording ' + ((state === 'on') ? 'started' : 'stopped') +
|
||||
'(jirecon)' + result);
|
||||
recordingEnabled = state;
|
||||
if (!state){
|
||||
if (state === 'off'){
|
||||
jireconRid = null;
|
||||
}
|
||||
|
||||
|
@ -73,10 +79,19 @@ function setRecordingColibri(state, token, callback, connection) {
|
|||
function (result) {
|
||||
console.log('Set recording "', state, '". Result:', result);
|
||||
var recordingElem = $(result).find('>conference>recording');
|
||||
var newState = ('true' === recordingElem.attr('state'));
|
||||
var newState = recordingElem.attr('state');
|
||||
|
||||
recordingEnabled = newState;
|
||||
callback(newState);
|
||||
|
||||
if (newState === 'pending' && recordingStateChangeCallback == null) {
|
||||
recordingStateChangeCallback = callback;
|
||||
connection.addHandler(function(iq){
|
||||
var state = $(iq).find('recording').attr('state');
|
||||
if (state)
|
||||
recordingStateChangeCallback(state);
|
||||
}, 'http://jitsi.org/protocol/colibri', 'iq', null, null, null);
|
||||
}
|
||||
},
|
||||
function (error) {
|
||||
console.warn(error);
|
||||
|
@ -94,8 +109,7 @@ function setRecording(state, token, callback, connection) {
|
|||
}
|
||||
|
||||
var Recording = {
|
||||
toggleRecording: function (tokenEmptyCallback,
|
||||
startingCallback, startedCallback, connection) {
|
||||
toggleRecording: function (tokenEmptyCallback, recordingStateChangeCallback, connection) {
|
||||
if (!Moderator.isModerator()) {
|
||||
console.log(
|
||||
'non-focus, or conference not yet organized:' +
|
||||
|
@ -108,16 +122,16 @@ var Recording = {
|
|||
if (!recordingToken && !useJirecon) {
|
||||
tokenEmptyCallback(function (value) {
|
||||
setRecordingToken(value);
|
||||
self.toggleRecording(tokenEmptyCallback,
|
||||
startingCallback, startedCallback, connection);
|
||||
self.toggleRecording(tokenEmptyCallback, recordingStateChangeCallback, connection);
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var oldState = recordingEnabled;
|
||||
startingCallback(!oldState);
|
||||
setRecording(!oldState,
|
||||
var newState = (oldState === 'off' || !oldState) ? 'on' : 'off';
|
||||
|
||||
setRecording(newState,
|
||||
recordingToken,
|
||||
function (state) {
|
||||
console.log("New recording state: ", state);
|
||||
|
@ -143,7 +157,7 @@ var Recording = {
|
|||
// have been wrong
|
||||
setRecordingToken(null);
|
||||
}
|
||||
startedCallback(state);
|
||||
recordingStateChangeCallback(state);
|
||||
|
||||
},
|
||||
connection
|
||||
|
|
|
@ -1,27 +1,11 @@
|
|||
/* jshint -W117 */
|
||||
|
||||
var JingleSession = require("./JingleSession");
|
||||
var JingleSession = require("./JingleSessionPC");
|
||||
var XMPPEvents = require("../../service/xmpp/XMPPEvents");
|
||||
var RTCBrowserType = require("../RTC/RTCBrowserType");
|
||||
|
||||
|
||||
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: {},
|
||||
|
@ -126,20 +110,30 @@ module.exports = function(XMPP, eventEmitter) {
|
|||
sess.pc_constraints = this.pc_constraints;
|
||||
sess.ice_config = this.ice_config;
|
||||
|
||||
sess.initiate(fromJid, false);
|
||||
sess.initialize(fromJid, false);
|
||||
// FIXME: setRemoteDescription should only be done when this call is to be accepted
|
||||
sess.setRemoteDescription($(iq).find('>jingle'), 'offer');
|
||||
sess.setOffer($(iq).find('>jingle'));
|
||||
|
||||
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);
|
||||
// or .sendTerminate -- not necessarily synchronous
|
||||
|
||||
// TODO: do we check activecall == null?
|
||||
this.connection.jingle.activecall = sess;
|
||||
|
||||
eventEmitter.emit(XMPPEvents.CALL_INCOMING, sess);
|
||||
|
||||
// TODO: check affiliation and/or role
|
||||
console.log('emuc data for', sess.peerjid,
|
||||
this.connection.emuc.members[sess.peerjid]);
|
||||
sess.sendAnswer();
|
||||
sess.accept();
|
||||
break;
|
||||
case 'session-accept':
|
||||
sess.setRemoteDescription($(iq).find('>jingle'), 'answer');
|
||||
sess.setAnswer($(iq).find('>jingle'));
|
||||
sess.accept();
|
||||
$(document).trigger('callaccepted.jingle', [sess.sid]);
|
||||
break;
|
||||
|
@ -206,7 +200,7 @@ module.exports = function(XMPP, eventEmitter) {
|
|||
sess.pc_constraints = this.pc_constraints;
|
||||
sess.ice_config = this.ice_config;
|
||||
|
||||
sess.initiate(peerjid, true);
|
||||
sess.initialize(peerjid, true);
|
||||
this.sessions[sess.sid] = sess;
|
||||
this.jid2session[sess.peerjid] = sess;
|
||||
sess.sendOffer();
|
||||
|
|
|
@ -434,9 +434,9 @@ var XMPP = {
|
|||
return true;
|
||||
},
|
||||
toggleRecording: function (tokenEmptyCallback,
|
||||
startingCallback, startedCallback) {
|
||||
recordingStateChangeCallback) {
|
||||
Recording.toggleRecording(tokenEmptyCallback,
|
||||
startingCallback, startedCallback, connection);
|
||||
recordingStateChangeCallback, connection);
|
||||
},
|
||||
addToPresence: function (name, value, dontSend) {
|
||||
switch (name) {
|
||||
|
|
Loading…
Reference in New Issue