refactoring of Etherpad and Prezi

This commit is contained in:
isymchych 2015-12-25 18:55:45 +02:00
parent e494c3028d
commit 0fd0f5b633
18 changed files with 2256 additions and 2078 deletions

51
app.js
View File

@ -41,6 +41,9 @@ const Commands = {
CONNECTION_QUALITY: "connectionQuality", CONNECTION_QUALITY: "connectionQuality",
EMAIL: "email", EMAIL: "email",
VIDEO_TYPE: "videoType" VIDEO_TYPE: "videoType"
ETHERPAD: "etherpad",
PREZI: "prezi",
STOP_PREZI: "stop-prezi"
}; };
function buildRoomName () { function buildRoomName () {
@ -218,16 +221,14 @@ function initConference(localTracks, connection) {
room.on(ConferenceEvents.USER_JOINED, function (id, user) { room.on(ConferenceEvents.USER_JOINED, function (id, user) {
if (APP.conference.isLocalId(id)) { console.error('USER %s connnected', id, user);
return;
}
console.error('USER %s connnected', id);
// FIXME email??? // FIXME email???
APP.UI.addUser(id, user.getDisplayName()); APP.UI.addUser(id, user.getDisplayName());
}); });
room.on(ConferenceEvents.USER_LEFT, function (id, user) { room.on(ConferenceEvents.USER_LEFT, function (id, user) {
console.error('USER LEFT', id); console.error('USER %s LEFT', id, user);
APP.UI.removeUser(id, user.getDisplayName()); APP.UI.removeUser(id, user.getDisplayName());
APP.UI.stopPrezi(id);
}); });
@ -348,9 +349,12 @@ function initConference(localTracks, connection) {
room.removeCommand(Commands.CONNECTION_QUALITY); room.removeCommand(Commands.CONNECTION_QUALITY);
}); });
// listen to remote stats // listen to remote stats
room.addCommandListener(Commands.CONNECTION_QUALITY, function (data) { room.addCommandListener(
APP.connectionquality.updateRemoteStats(data.attributes.id, data.value); Commands.CONNECTION_QUALITY,
}); function ({value, attributes}) {
APP.connectionquality.updateRemoteStats(attributes.id, value);
}
);
APP.connectionquality.addListener( APP.connectionquality.addListener(
CQEvents.REMOTESTATS_UPDATED, CQEvents.REMOTESTATS_UPDATED,
function (id, percent, stats) { function (id, percent, stats) {
@ -358,6 +362,37 @@ function initConference(localTracks, connection) {
} }
); );
room.addCommandListener(Commands.ETHERPAD, function ({value}) {
APP.UI.initEtherpad(value);
});
room.addCommandListener(Commands.PREZI, function ({value, attributes}) {
APP.UI.showPrezi(attributes.id, value, attributes.slide);
});
room.addCommandListener(Commands.STOP_PREZI, function ({attributes}) {
APP.UI.stopPrezi(attributes.id);
});
APP.UI.addListener(UIEvents.SHARE_PREZI, function (url, slide) {
console.log('Sharing Prezi %s slide %s', url, slide);
room.removeCommand(Commands.PREZI);
room.sendCommand(Commands.PREZI, {
value: url,
attributes: {
id: room.myUserId(),
slide
}
});
});
APP.UI.addListener(UIEvents.STOP_SHARING_PREZI, function () {
room.removeCommand(Commands.PREZI);
room.sendCommandOnce(Commands.STOP_PREZI, {
attributes: {
id: room.myUserId()
}
});
});
room.addCommandListener(Commands.VIDEO_TYPE, (data, from) => { room.addCommandListener(Commands.VIDEO_TYPE, (data, from) => {
APP.UI.onPeerVideoTypeChanged(from, data.value); APP.UI.onPeerVideoTypeChanged(from, data.value);
}); });

View File

@ -34,7 +34,7 @@
} }
#remoteVideos .videocontainer { #remoteVideos .videocontainer {
display: inline-block; display: none;
background-color: black; background-color: black;
background-size: contain; background-size: contain;
border-radius:8px; border-radius:8px;

View File

@ -140,6 +140,25 @@
</div> </div>
<div id="reloadPresentation"><a id="reloadPresentationLink"><i title="Reload Prezi" class="fa fa-repeat fa-lg"></i></a></div> <div id="reloadPresentation"><a id="reloadPresentationLink"><i title="Reload Prezi" class="fa fa-repeat fa-lg"></i></a></div>
<div id="videospace"> <div id="videospace">
<div id="largeVideoContainer" class="videocontainer">
<div id="presentation"></div>
<div id="etherpad"></div>
<a target="_new"><div class="watermark leftwatermark"></div></a>
<a target="_new"><div class="watermark rightwatermark"></div></a>
<a class="poweredby" href="http://jitsi.org" target="_new">
<span data-i18n="poweredby"></span> jitsi.org
</a>
<div id="activeSpeaker">
<img id="activeSpeakerAvatar" src=""/>
<canvas id="activeSpeakerAudioLevel"></canvas>
</div>
<div id="largeVideoWrapper">
<video id="largeVideo" muted="true" autoplay oncontextmenu="return false;"></video>
</div>
<span id="videoConnectionMessage"></span>
</div>
<div id="remoteVideos"> <div id="remoteVideos">
<span id="localVideoContainer" class="videocontainer"> <span id="localVideoContainer" class="videocontainer">
<span id="localNick" class="nick"></span> <span id="localNick" class="nick"></span>

File diff suppressed because it is too large Load Diff

View File

@ -2,7 +2,6 @@
/* jshint -W101 */ /* jshint -W101 */
var UI = {}; var UI = {};
import AudioLevels from './audio_levels/AudioLevels';
import Chat from "./side_pannels/chat/Chat"; import Chat from "./side_pannels/chat/Chat";
import Toolbar from "./toolbars/Toolbar"; import Toolbar from "./toolbars/Toolbar";
import ToolbarToggler from "./toolbars/ToolbarToggler"; import ToolbarToggler from "./toolbars/ToolbarToggler";
@ -12,12 +11,12 @@ import Avatar from "./avatar/Avatar";
import PanelToggler from "./side_pannels/SidePanelToggler"; import PanelToggler from "./side_pannels/SidePanelToggler";
import UIUtil from "./util/UIUtil"; import UIUtil from "./util/UIUtil";
import UIEvents from "../../service/UI/UIEvents"; import UIEvents from "../../service/UI/UIEvents";
import PreziManager from './prezi/Prezi';
import EtherpadManager from './etherpad/Etherpad';
import VideoLayout from "./videolayout/VideoLayout"; import VideoLayout from "./videolayout/VideoLayout";
import SettingsMenu from "./side_pannels/settings/SettingsMenu"; import SettingsMenu from "./side_pannels/settings/SettingsMenu";
var Prezi = require("./prezi/Prezi");
var Etherpad = require("./etherpad/Etherpad");
var EventEmitter = require("events"); var EventEmitter = require("events");
var Settings = require("./../settings/Settings"); var Settings = require("./../settings/Settings");
UI.messageHandler = require("./util/MessageHandler"); UI.messageHandler = require("./util/MessageHandler");
@ -32,6 +31,9 @@ var Feedback = require("./Feedback");
var eventEmitter = new EventEmitter(); var eventEmitter = new EventEmitter();
UI.eventEmitter = eventEmitter; UI.eventEmitter = eventEmitter;
let preziManager;
let etherpadManager;
function promptDisplayName() { function promptDisplayName() {
let nickRequiredMsg = APP.translation.translateString("dialog.displayNameRequired"); let nickRequiredMsg = APP.translation.translateString("dialog.displayNameRequired");
let defaultNickMsg = APP.translation.translateString( let defaultNickMsg = APP.translation.translateString(
@ -77,12 +79,6 @@ function promptDisplayName() {
); );
} }
function setupPrezi() {
$("#reloadPresentationLink").click(function() {
Prezi.reloadPresentation();
});
}
function setupChat() { function setupChat() {
Chat.init(eventEmitter); Chat.init(eventEmitter);
$("#toggle_smileys").click(function() { $("#toggle_smileys").click(function() {
@ -189,20 +185,18 @@ UI.initConference = function () {
}; };
function registerListeners() { function registerListeners() {
UI.addListener(UIEvents.LARGEVIDEO_INIT, function () {
AudioLevels.init();
});
UI.addListener(UIEvents.EMAIL_CHANGED, function (email) { UI.addListener(UIEvents.EMAIL_CHANGED, function (email) {
UI.setUserAvatar(APP.conference.localId, email); UI.setUserAvatar(APP.conference.localId, email);
}); });
UI.addListener(UIEvents.PREZI_CLICKED, function () { UI.addListener(UIEvents.PREZI_CLICKED, function () {
Prezi.openPreziDialog(); preziManager.handlePreziButtonClicked();
}); });
UI.addListener(UIEvents.ETHERPAD_CLICKED, function () { UI.addListener(UIEvents.ETHERPAD_CLICKED, function () {
Etherpad.toggleEtherpad(0); if (etherpadManager) {
etherpadManager.toggleEtherpad();
}
}); });
UI.addListener(UIEvents.FULLSCREEN_TOGGLE, toggleFullScreen); UI.addListener(UIEvents.FULLSCREEN_TOGGLE, toggleFullScreen);
@ -221,7 +215,7 @@ function registerListeners() {
function bindEvents() { function bindEvents() {
function onResize() { function onResize() {
PanelToggler.resizeChat(); PanelToggler.resizeChat();
VideoLayout.resizeLargeVideoContainer(); VideoLayout.resizeLargeVideoContainer(PanelToggler.isVisible());
} }
// Resize and reposition videos in full screen mode. // Resize and reposition videos in full screen mode.
@ -254,11 +248,17 @@ UI.start = function () {
registerListeners(); registerListeners();
VideoLayout.init(eventEmitter); VideoLayout.init(eventEmitter);
if (!interfaceConfig.filmStripOnly) {
VideoLayout.initLargeVideo(PanelToggler.isVisible());
}
VideoLayout.resizeLargeVideoContainer(PanelToggler.isVisible());
ContactList.init(eventEmitter); ContactList.init(eventEmitter);
bindEvents(); bindEvents();
setupPrezi(); preziManager = new PreziManager(eventEmitter);
if (!interfaceConfig.filmStripOnly) { if (!interfaceConfig.filmStripOnly) {
$("#videospace").mousemove(function () { $("#videospace").mousemove(function () {
return ToolbarToggler.showToolbar(); return ToolbarToggler.showToolbar();
}); });
@ -350,9 +350,14 @@ UI.setSubject = function (subject) {
Chat.setSubject(subject); Chat.setSubject(subject);
}; };
function initEtherpad(name) { UI.initEtherpad = function (name) {
Etherpad.init(name); if (etherpadManager) {
return;
} }
console.log('Etherpad is enabled');
etherpadManager = new EtherpadManager(config.etherpad_base, name);
Toolbar.showEtherpadButton();
};
UI.addUser = function (id, displayName) { UI.addUser = function (id, displayName) {
ContactList.addContact(id); ContactList.addContact(id);
@ -443,7 +448,6 @@ UI.getSettings = function () {
UI.toggleFilmStrip = function () { UI.toggleFilmStrip = function () {
BottomToolbar.toggleFilmStrip(); BottomToolbar.toggleFilmStrip();
VideoLayout.updateLargeVideoSize();
}; };
UI.toggleChat = function () { UI.toggleChat = function () {
@ -592,9 +596,7 @@ UI.handleLastNEndpoints = function (ids) {
}; };
UI.setAudioLevel = function (id, lvl) { UI.setAudioLevel = function (id, lvl) {
AudioLevels.updateAudioLevel( VideoLayout.setAudioLevel(id, lvl);
id, lvl, VideoLayout.getLargeVideoId()
);
}; };
UI.updateDesktopSharingButtons = function () { UI.updateDesktopSharingButtons = function () {
@ -750,4 +752,14 @@ UI.updateAuthInfo = function (isAuthEnabled, login) {
} }
}; };
UI.showPrezi = function (userId, url, slide) {
preziManager.showPrezi(userId, url, slide);
};
UI.stopPrezi = function (userId) {
if (preziManager.isSharing(userId)) {
preziManager.removePrezi(userId);
}
};
module.exports = UI; module.exports = UI;

View File

@ -112,33 +112,6 @@ function getVideoSpanId(id) {
return videoSpanId; return videoSpanId;
} }
/**
* Indicates that the remote video has been resized.
*/
$(document).bind('remotevideo.resized', function (event, width, height) {
let resized = false;
$('#remoteVideos>span>canvas').each(function() {
let canvas = $(this).get(0);
if (canvas.width !== width + interfaceConfig.CANVAS_EXTRA) {
canvas.width = width + interfaceConfig.CANVAS_EXTRA;
resized = true;
}
if (canvas.height !== height + interfaceConfig.CANVAS_EXTRA) {
canvas.height = height + interfaceConfig.CANVAS_EXTRA;
resized = true;
}
});
if (resized) {
Object.keys(audioLevelCanvasCache).forEach(function (id) {
audioLevelCanvasCache[id].width = width + interfaceConfig.CANVAS_EXTRA;
audioLevelCanvasCache[id].height = height + interfaceConfig.CANVAS_EXTRA;
});
}
});
/** /**
* The audio Levels plugin. * The audio Levels plugin.
*/ */
@ -248,6 +221,33 @@ const AudioLevels = {
// Fill the shape. // Fill the shape.
ASDrawContext.fill(); ASDrawContext.fill();
},
/**
* Indicates that the remote video has been resized.
*/
onRemoteVideoResized (width, height) {
let resized = false;
$('#remoteVideos>span>canvas').each(function() {
let canvas = $(this).get(0);
if (canvas.width !== width + interfaceConfig.CANVAS_EXTRA) {
canvas.width = width + interfaceConfig.CANVAS_EXTRA;
resized = true;
}
if (canvas.height !== height + interfaceConfig.CANVAS_EXTRA) {
canvas.height = height + interfaceConfig.CANVAS_EXTRA;
resized = true;
}
});
if (resized) {
Object.keys(audioLevelCanvasCache).forEach(function (id) {
audioLevelCanvasCache[id].width = width + interfaceConfig.CANVAS_EXTRA;
audioLevelCanvasCache[id].height = height + interfaceConfig.CANVAS_EXTRA;
});
}
} }
}; };

View File

@ -1,61 +1,16 @@
/* global $, config, /* global $ */
setLargeVideoVisible, Util */
var VideoLayout = require("../videolayout/VideoLayout"); import VideoLayout from "../videolayout/VideoLayout";
var Prezi = require("../prezi/Prezi"); import LargeContainer from '../videolayout/LargeContainer';
var UIUtil = require("../util/UIUtil"); import UIUtil from "../util/UIUtil";
import SidePanelToggler from "../side_pannels/SidePanelToggler";
var etherpadName = null; const options = $.param({
var etherpadIFrame = null; showControns: true,
var domain = null; showChat: false,
var options = "?showControls=true&showChat=false&showLineNumbers=true" + showLineNumbers: true,
"&useMonospaceFont=false"; useMonospaceFont: false
/**
* Resizes the etherpad.
*/
function resize() {
if ($('#etherpad>iframe').length) {
var remoteVideos = $('#remoteVideos');
var availableHeight
= window.innerHeight - remoteVideos.outerHeight();
var availableWidth = UIUtil.getAvailableVideoWidth();
$('#etherpad>iframe').width(availableWidth);
$('#etherpad>iframe').height(availableHeight);
}
}
/**
* Creates the Etherpad button and adds it to the toolbar.
*/
function enableEtherpadButton() {
if (!$('#toolbar_button_etherpad').is(":visible"))
$('#toolbar_button_etherpad').css({display: 'inline-block'});
}
/**
* Creates the IFrame for the etherpad.
*/
function createIFrame() {
etherpadIFrame = VideoLayout.createEtherpadIframe(
domain + etherpadName + options, function() {
document.domain = document.domain;
bubbleIframeMouseMove(etherpadIFrame);
setTimeout(function() {
// the iframes inside of the etherpad are
// not yet loaded when the etherpad iframe is loaded
var outer = etherpadIFrame.
contentDocument.getElementsByName("ace_outer")[0];
bubbleIframeMouseMove(outer);
var inner = outer.
contentDocument.getElementsByName("ace_inner")[0];
bubbleIframeMouseMove(inner);
}, 2000);
}); });
}
function bubbleIframeMouseMove(iframe){ function bubbleIframeMouseMove(iframe){
var existingOnMouseMove = iframe.contentWindow.onmousemove; var existingOnMouseMove = iframe.contentWindow.onmousemove;
@ -84,48 +39,123 @@ function bubbleIframeMouseMove(iframe){
}; };
} }
const DEFAULT_WIDTH = 640;
const DEFAULT_HEIGHT = 480;
var Etherpad = { const EtherpadContainerType = "etherpad";
/**
* Initializes the etherpad.
*/
init: function (name) {
if (config.etherpad_base && !etherpadName && name) { class Etherpad extends LargeContainer {
constructor (domain, name) {
super();
domain = config.etherpad_base; const iframe = document.createElement('iframe');
etherpadName = name; iframe.src = domain + name + '?' + options;
iframe.frameBorder = 0;
iframe.scrolling = "no";
iframe.width = DEFAULT_WIDTH;
iframe.height = DEFAULT_HEIGHT;
iframe.setAttribute('style', 'visibility: hidden;');
enableEtherpadButton(); this.container.appendChild(iframe);
/** iframe.onload = function() {
* Resizes the etherpad, when the window is resized. document.domain = document.domain;
*/ bubbleIframeMouseMove(iframe);
$(window).resize(function () {
resize();
});
}
},
/** setTimeout(function() {
* Opens/hides the Etherpad. const doc = iframe.contentDocument;
*/
toggleEtherpad: function (isPresentation) {
if (!etherpadIFrame)
createIFrame();
// the iframes inside of the etherpad are
// not yet loaded when the etherpad iframe is loaded
const outer = doc.getElementsByName("ace_outer")[0];
bubbleIframeMouseMove(outer);
if(VideoLayout.getLargeVideoState() === "etherpad") const inner = doc.getElementsByName("ace_inner")[0];
{ bubbleIframeMouseMove(inner);
VideoLayout.setLargeVideoState("video"); }, 2000);
}
else
{
VideoLayout.setLargeVideoState("etherpad");
}
resize();
}
}; };
module.exports = Etherpad; this.iframe = iframe;
}
get isOpen () {
return !!this.iframe;
}
get container () {
return document.getElementById('etherpad');
}
resize (containerWidth, containerHeight, animate) {
let remoteVideos = $('#remoteVideos');
let height = containerHeight - remoteVideos.outerHeight();
let width = containerWidth;
$(this.iframe).width(width).height(height);
}
show () {
const $iframe = $(this.iframe);
const $container = $(this.container);
return new Promise(resolve => {
$iframe.fadeIn(300, function () {
document.body.style.background = '#eeeeee';
$iframe.css({visibility: 'visible'});
$container.css({zIndex: 2});
resolve();
});
});
}
hide () {
const $iframe = $(this.iframe);
const $container = $(this.container);
return new Promise(resolve => {
$iframe.fadeOut(300, function () {
$iframe.css({visibility: 'hidden'});
$container.css({zIndex: 0});
resolve();
});
});
}
}
export default class EtherpadManager {
constructor (domain, name) {
if (!domain || !name) {
throw new Error("missing domain or name");
}
this.domain = domain;
this.name = name;
this.etherpad = null;
}
get isOpen () {
return !!this.etherpad;
}
openEtherpad () {
this.etherpad = new Etherpad(this.domain, this.name);
VideoLayout.addLargeVideoContainer(
EtherpadContainerType,
this.etherpad
);
}
toggleEtherpad () {
if (!this.isOpen) {
this.openEtherpad();
}
let isVisible = VideoLayout.isLargeContainerTypeVisible(
EtherpadContainerType
);
VideoLayout.showLargeVideoContainer(EtherpadContainerType, !isVisible);
}
}

View File

@ -1,268 +1,21 @@
/* global $, APP */ /* global $, APP */
/* jshint -W101 */ /* jshint -W101 */
import UIUtil from "../util/UIUtil";
import VideoLayout from "../videolayout/VideoLayout"; import VideoLayout from "../videolayout/VideoLayout";
import LargeContainer from '../videolayout/LargeContainer';
import PreziPlayer from './PreziPlayer';
import UIUtil from '../util/UIUtil';
import UIEvents from '../../../service/UI/UIEvents';
import messageHandler from '../util/MessageHandler';
import ToolbarToggler from "../toolbars/ToolbarToggler";
import SidePanelToggler from "../side_pannels/SidePanelToggler";
var messageHandler = require("../util/MessageHandler"); const defaultPreziLink = "http://prezi.com/wz7vhjycl7e6/my-prezi";
var PreziPlayer = require("./PreziPlayer"); const alphanumRegex = /^[a-z0-9-_\/&\?=;]+$/i;
const aspectRatio = 16.0 / 9.0;
var preziPlayer = null; const DEFAULT_WIDTH = 640;
const DEFAULT_HEIGHT = 480;
/**
* Shows/hides a presentation.
*/
function setPresentationVisible(visible) {
if (visible) {
VideoLayout.setLargeVideoState("prezi");
}
else {
VideoLayout.setLargeVideoState("video");
}
}
var Prezi = {
/**
* Reloads the current presentation.
*/
reloadPresentation: function() {
var iframe = document.getElementById(preziPlayer.options.preziId);
iframe.src = iframe.src;
},
/**
* Returns <tt>true</tt> if the presentation is visible, <tt>false</tt> -
* otherwise.
*/
isPresentationVisible: function () {
return ($('#presentation>iframe') != null
&& $('#presentation>iframe').css('opacity') == 1);
},
/**
* Opens the Prezi dialog, from which the user could choose a presentation
* to load.
*/
openPreziDialog: function() {
var myprezi = APP.xmpp.getPrezi();
if (myprezi) {
messageHandler.openTwoButtonDialog("dialog.removePreziTitle",
null,
"dialog.removePreziMsg",
null,
false,
"dialog.Remove",
function(e,v,m,f) {
if(v) {
APP.xmpp.removePreziFromPresence();
}
}
);
}
else if (preziPlayer != null) {
messageHandler.openTwoButtonDialog("dialog.sharePreziTitle",
null, "dialog.sharePreziMsg",
null,
false,
"dialog.Ok",
function(e,v,m,f) {
$.prompt.close();
}
);
}
else {
var html = APP.translation.generateTranslationHTML(
"dialog.sharePreziTitle");
var cancelButton = APP.translation.generateTranslationHTML(
"dialog.Cancel");
var shareButton = APP.translation.generateTranslationHTML(
"dialog.Share");
var backButton = APP.translation.generateTranslationHTML(
"dialog.Back");
var buttons = [];
var buttons1 = [];
// Cancel button to both states
buttons.push({title: cancelButton, value: false});
buttons1.push({title: cancelButton, value: false});
// Share button
buttons.push({title: shareButton, value: true});
// Back button
buttons1.push({title: backButton, value: true});
var linkError = APP.translation.generateTranslationHTML(
"dialog.preziLinkError");
var defaultUrl = APP.translation.translateString("defaultPreziLink",
{url: "http://prezi.com/wz7vhjycl7e6/my-prezi"});
var openPreziState = {
state0: {
html: '<h2>' + html + '</h2>' +
'<input name="preziUrl" type="text" ' +
'data-i18n="[placeholder]defaultPreziLink" data-i18n-options=\'' +
JSON.stringify({"url": "http://prezi.com/wz7vhjycl7e6/my-prezi"}) +
'\' placeholder="' + defaultUrl + '" autofocus>',
persistent: false,
buttons: buttons,
focus: ':input:first',
defaultButton: 0,
submit: function (e, v, m, f) {
e.preventDefault();
if(v)
{
var preziUrl = f.preziUrl;
if (preziUrl)
{
var urlValue
= encodeURI(UIUtil.escapeHtml(preziUrl));
if (urlValue.indexOf('http://prezi.com/') != 0
&& urlValue.indexOf('https://prezi.com/') != 0)
{
$.prompt.goToState('state1');
return false;
}
else {
var presIdTmp = urlValue.substring(
urlValue.indexOf("prezi.com/") + 10);
if (!isAlphanumeric(presIdTmp)
|| presIdTmp.indexOf('/') < 2) {
$.prompt.goToState('state1');
return false;
}
else {
APP.xmpp.addToPresence("prezi", urlValue);
$.prompt.close();
}
}
}
}
else
$.prompt.close();
}
},
state1: {
html: '<h2>' + html + '</h2>' +
linkError,
persistent: false,
buttons: buttons1,
focus: ':input:first',
defaultButton: 1,
submit: function (e, v, m, f) {
e.preventDefault();
if (v === 0)
$.prompt.close();
else
$.prompt.goToState('state0');
}
}
};
messageHandler.openDialogWithStates(openPreziState);
}
}
};
/**
* A new presentation has been added.
*
* @param event the event indicating the add of a presentation
* @param jid the jid from which the presentation was added
* @param presUrl url of the presentation
* @param currentSlide the current slide to which we should move
*/
function presentationAdded(event, jid, presUrl, currentSlide) {
console.log("presentation added", presUrl);
var presId = getPresentationId(presUrl);
var elementId = 'participant_'
+ Strophe.getResourceFromJid(jid)
+ '_' + presId;
VideoLayout.addPreziContainer(elementId);
var controlsEnabled = false;
if (jid === APP.xmpp.myJid())
controlsEnabled = true;
setPresentationVisible(true);
VideoLayout.setLargeVideoHover(
function (event) {
if (Prezi.isPresentationVisible()) {
var reloadButtonRight = window.innerWidth
- $('#presentation>iframe').offset().left
- $('#presentation>iframe').width();
$('#reloadPresentation').css({ right: reloadButtonRight,
display:'inline-block'});
}
},
function (event) {
if (!Prezi.isPresentationVisible())
$('#reloadPresentation').css({display:'none'});
else {
var e = event.toElement || event.relatedTarget;
if (e && e.id != 'reloadPresentation' && e.id != 'header')
$('#reloadPresentation').css({display:'none'});
}
});
preziPlayer = new PreziPlayer(
'presentation',
{preziId: presId,
width: getPresentationWidth(),
height: getPresentationHeihgt(),
controls: controlsEnabled,
debug: true
});
$('#presentation>iframe').attr('id', preziPlayer.options.preziId);
preziPlayer.on(PreziPlayer.EVENT_STATUS, function(event) {
console.log("prezi status", event.value);
if (event.value == PreziPlayer.STATUS_CONTENT_READY) {
if (jid != APP.xmpp.myJid())
preziPlayer.flyToStep(currentSlide);
}
});
preziPlayer.on(PreziPlayer.EVENT_CURRENT_STEP, function(event) {
console.log("event value", event.value);
APP.xmpp.addToPresence("preziSlide", event.value);
});
$("#" + elementId).css( 'background-image',
'url(../images/avatarprezi.png)');
$("#" + elementId).click(
function () {
setPresentationVisible(true);
}
);
};
/**
* A presentation has been removed.
*
* @param event the event indicating the remove of a presentation
* @param jid the jid for which the presentation was removed
* @param the url of the presentation
*/
function presentationRemoved(event, jid, presUrl) {
console.log('presentation removed', presUrl);
var presId = getPresentationId(presUrl);
setPresentationVisible(false);
$('#participant_'
+ Strophe.getResourceFromJid(jid)
+ '_' + presId).remove();
$('#presentation>iframe').remove();
if (preziPlayer != null) {
preziPlayer.destroy();
preziPlayer = null;
}
};
/** /**
* Indicates if the given string is an alphanumeric string. * Indicates if the given string is an alphanumeric string.
@ -270,76 +23,355 @@ function presentationRemoved(event, jid, presUrl) {
* purpose of checking URIs. * purpose of checking URIs.
*/ */
function isAlphanumeric(unsafeText) { function isAlphanumeric(unsafeText) {
var regex = /^[a-z0-9-_\/&\?=;]+$/i; return alphanumRegex.test(unsafeText);
return regex.test(unsafeText);
} }
/** /**
* Returns the presentation id from the given url. * Returns the presentation id from the given url.
*/ */
function getPresentationId (presUrl) { function getPresentationId (url) {
var presIdTmp = presUrl.substring(presUrl.indexOf("prezi.com/") + 10); let presId = url.substring(url.indexOf("prezi.com/") + 10);
return presIdTmp.substring(0, presIdTmp.indexOf('/')); return presId.substring(0, presId.indexOf('/'));
} }
/** function isPreziLink(url) {
* Returns the presentation width. if (url.indexOf('http://prezi.com/') !== 0 && url.indexOf('https://prezi.com/') !== 0) {
*/ return false;
function getPresentationWidth() {
var availableWidth = UIUtil.getAvailableVideoWidth();
var availableHeight = getPresentationHeihgt();
var aspectRatio = 16.0 / 9.0;
if (availableHeight < availableWidth / aspectRatio) {
availableWidth = Math.floor(availableHeight * aspectRatio);
}
return availableWidth;
} }
/** let presId = url.substring(url.indexOf("prezi.com/") + 10);
* Returns the presentation height. if (!isAlphanumeric(presId) || presId.indexOf('/') < 2) {
*/ return false;
function getPresentationHeihgt() {
var remoteVideos = $('#remoteVideos');
return window.innerHeight - remoteVideos.outerHeight();
} }
/** return true;
* Resizes the presentation iframe.
*/
function resize() {
if ($('#presentation>iframe')) {
$('#presentation>iframe').width(getPresentationWidth());
$('#presentation>iframe').height(getPresentationHeihgt());
}
} }
/** function notifyOtherIsSharingPrezi() {
* Presentation has been removed. messageHandler.openMessageDialog(
*/ "dialog.sharePreziTitle",
$(document).bind('presentationremoved.muc', presentationRemoved); "dialog.sharePreziMsg"
);
}
/** function proposeToClosePrezi() {
* Presentation has been added. return new Promise(function (resolve, reject) {
*/ messageHandler.openTwoButtonDialog(
$(document).bind('presentationadded.muc', presentationAdded); "dialog.removePreziTitle",
null,
"dialog.removePreziMsg",
null,
false,
"dialog.Remove",
function(e,v,m,f) {
if (v) {
resolve();
} else {
reject();
}
}
);
/* });
* Indicates presentation slide change. }
*/
$(document).bind('gotoslide.muc', function (event, jid, presUrl, current) {
if (preziPlayer && preziPlayer.getCurrentStep() != current) {
preziPlayer.flyToStep(current);
var animationStepsArray = preziPlayer.getAnimationCountOnSteps(); function requestPreziLink() {
for (var i = 0; i < parseInt(animationStepsArray[current]); i++) { const title = APP.translation.generateTranslationHTML("dialog.sharePreziTitle");
preziPlayer.flyToStep(current, i); const cancelButton = APP.translation.generateTranslationHTML("dialog.Cancel");
const shareButton = APP.translation.generateTranslationHTML("dialog.Share");
const backButton = APP.translation.generateTranslationHTML("dialog.Back");
const linkError = APP.translation.generateTranslationHTML("dialog.preziLinkError");
const i18nOptions = {url: defaultPreziLink};
const defaultUrl = APP.translation.translateString(
"defaultPreziLink", i18nOptions
);
return new Promise(function (resolve, reject) {
let dialog = messageHandler.openDialogWithStates({
state0: {
html: `
<h2>${title}</h2>
<input name="preziUrl" type="text"
data-i18n="[placeholder]defaultPreziLink"
data-i18n-options="${JSON.stringify(i18nOptions)}"
placeholder="${defaultUrl}" autofocus>`,
persistent: false,
buttons: [
{title: cancelButton, value: false},
{title: shareButton, value: true}
],
focus: ':input:first',
defaultButton: 1,
submit: function (e, v, m, f) {
e.preventDefault();
if (!v) {
reject('cancelled');
dialog.close();
return;
}
let preziUrl = f.preziUrl;
if (!preziUrl) {
return;
}
let urlValue = encodeURI(UIUtil.escapeHtml(preziUrl));
if (!isPreziLink(urlValue)) {
dialog.goToState('state1');
return false;
}
resolve(urlValue);
dialog.close();
}
},
state1: {
html: `<h2>${title}</h2> ${linkError}`,
persistent: false,
buttons: [
{title: cancelButton, value: false},
{title: backButton, value: true}
],
focus: ':input:first',
defaultButton: 1,
submit: function (e, v, m, f) {
e.preventDefault();
if (v === 0) {
reject();
dialog.close();
} else {
dialog.goToState('state0');
}
} }
} }
}); });
$(window).resize(function () { });
resize(); }
export const PreziContainerType = "prezi";
class PreziContainer extends LargeContainer {
constructor ({preziId, isMy, slide, onSlideChanged}) {
super();
this.reloadBtn = $('#reloadPresentation');
let preziPlayer = new PreziPlayer(
'presentation', {
preziId,
width: DEFAULT_WIDTH,
height: DEFAULT_HEIGHT,
controls: isMy,
debug: true
}
);
this.preziPlayer = preziPlayer;
this.$iframe = $(preziPlayer.iframe);
this.$iframe.attr('id', preziId);
preziPlayer.on(PreziPlayer.EVENT_STATUS, function({value}) {
console.log("prezi status", value);
if (value == PreziPlayer.STATUS_CONTENT_READY && !isMy) {
preziPlayer.flyToStep(slide);
}
}); });
module.exports = Prezi; preziPlayer.on(PreziPlayer.EVENT_CURRENT_STEP, function({value}) {
console.log("event value", value);
onSlideChanged(value);
});
}
goToSlide (slide) {
if (this.preziPlayer.getCurrentStep() === slide) {
return;
}
this.preziPlayer.flyToStep(slide);
let animationStepsArray = this.preziPlayer.getAnimationCountOnSteps();
if (!animationStepsArray) {
return;
}
for (var i = 0; i < parseInt(animationStepsArray[slide]); i += 1) {
this.preziPlayer.flyToStep(slide, i);
}
}
showReloadBtn (show) {
this.reloadBtn.css('display', show ? 'inline-block' : 'none');
}
show () {
return new Promise(resolve => {
this.$iframe.fadeIn(300, () => {
this.$iframe.css({opacity: 1});
ToolbarToggler.dockToolbar(true);
resolve();
});
});
}
hide () {
return new Promise(resolve => {
this.$iframe.fadeOut(300, () => {
this.$iframe.css({opacity: 0});
this.showReloadBtn(false);
ToolbarToggler.dockToolbar(false);
resolve();
});
});
}
onHoverIn () {
let rightOffset = window.innerWidth - this.$iframe.offset().left - this.$iframe.width();
this.showReloadBtn(true);
this.reloadBtn.css('right', rightOffset);
}
onHoverOut (event) {
let e = event.toElement || event.relatedTarget;
if (e && e.id != 'reloadPresentation' && e.id != 'header') {
this.showReloadBtn(false);
}
}
resize (containerWidth, containerHeight) {
let remoteVideos = $('#remoteVideos');
let height = containerHeight - remoteVideos.outerHeight();
let width = containerWidth;
if (height < width / aspectRatio) {
width = Math.floor(height * aspectRatio);
}
this.$iframe.width(width).height(height);
}
close () {
this.showReloadBtn(false);
this.preziPlayer.destroy();
this.$iframe.remove();
}
}
export default class PreziManager {
constructor (emitter) {
this.emitter = emitter;
this.userId = null;
this.url = null;
this.prezi = null;
$("#reloadPresentationLink").click(this.reloadPresentation.bind(this));
}
get isPresenting () {
return !!this.userId;
}
get isMyPrezi () {
return this.userId === APP.conference.localId;
}
isSharing (id) {
return this.userId === id;
}
handlePreziButtonClicked () {
if (!this.isPresenting) {
requestPreziLink().then(
url => this.emitter.emit(UIEvents.SHARE_PREZI, url, 0),
err => console.error('PREZI CANCELED', err)
);
return;
}
if (this.isMyPrezi) {
proposeToClosePrezi().then(() => this.emitter.emit(UIEvents.STOP_SHARING_PREZI));
} else {
notifyOtherIsSharingPrezi();
}
}
reloadPresentation () {
if (!this.prezi) {
return;
}
let iframe = this.prezi.$iframe[0];
iframe.src = iframe.src;
}
showPrezi (id, url, slide) {
if (!this.isPresenting) {
this.createPrezi(id, url, slide);
}
if (this.userId === id && this.url === url) {
this.prezi.goToSlide(slide);
} else {
console.error(this.userId, id);
console.error(this.url, url);
throw new Error("unexpected presentation change");
}
}
createPrezi (id, url, slide) {
console.log("presentation added", url);
this.userId = id;
this.url = url;
let preziId = getPresentationId(url);
let elementId = `participant_${id}_${preziId}`;
this.$thumb = $(VideoLayout.addRemoteVideoContainer(elementId));
VideoLayout.resizeThumbnails();
this.$thumb.css({
'background-image': 'url(../images/avatarprezi.png)'
}).click(() => VideoLayout.showLargeVideoContainer(PreziContainerType, true));
this.prezi = new PreziContainer({
preziId,
isMy: this.isMyPrezi,
slide,
onSlideChanged: newSlide => {
if (this.isMyPrezi) {
this.emitter.emit(UIEvents.SHARE_PREZI, url, newSlide);
}
}
});
VideoLayout.addLargeVideoContainer(PreziContainerType, this.prezi);
VideoLayout.showLargeVideoContainer(PreziContainerType, true);
}
removePrezi (id) {
if (this.userId !== id) {
throw new Error(`cannot close presentation from ${this.userId} instead of ${id}`);
}
this.$thumb.remove();
this.$thumb = null;
// wait until Prezi is hidden, then remove it
VideoLayout.showLargeVideoContainer(PreziContainerType, false).then(() => {
console.log("presentation removed", this.url);
VideoLayout.removeLargeVideoContainer(PreziContainerType);
this.userId = null;
this.url = null;
this.prezi.close();
this.prezi = null;
});
}
}

View File

@ -1,10 +1,6 @@
/* global PreziPlayer */
/* jshint -W101 */ /* jshint -W101 */
(function() {
"use strict";
var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
window.PreziPlayer = (function() { var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
PreziPlayer.API_VERSION = 1; PreziPlayer.API_VERSION = 1;
PreziPlayer.CURRENT_STEP = 'currentStep'; PreziPlayer.CURRENT_STEP = 'currentStep';
@ -289,10 +285,6 @@
window.attachEvent('onmessage', PreziPlayer.messageReceived); window.attachEvent('onmessage', PreziPlayer.messageReceived);
} }
return PreziPlayer; window.PreziPlayer = PreziPlayer;
})(); export default PreziPlayer;
})();
module.exports = PreziPlayer;

View File

@ -6,7 +6,6 @@ import SettingsMenu from "./settings/SettingsMenu";
import VideoLayout from "../videolayout/VideoLayout"; import VideoLayout from "../videolayout/VideoLayout";
import ToolbarToggler from "../toolbars/ToolbarToggler"; import ToolbarToggler from "../toolbars/ToolbarToggler";
import UIUtil from "../util/UIUtil"; import UIUtil from "../util/UIUtil";
import LargeVideo from "../videolayout/LargeVideo";
const buttons = { const buttons = {
'#chatspace': '#chatBottomButton', '#chatspace': '#chatBottomButton',
@ -47,7 +46,7 @@ function toggle (object, selector, onOpenComplete, onOpen, onClose) {
} else { } else {
// Undock the toolbar when the chat is shown and if we're in a // Undock the toolbar when the chat is shown and if we're in a
// video mode. // video mode.
if (LargeVideo.isLargeVideoVisible()) { if (VideoLayout.isLargeVideoVisible()) {
ToolbarToggler.dockToolbar(false); ToolbarToggler.dockToolbar(false);
} }
@ -62,7 +61,7 @@ function toggle (object, selector, onOpenComplete, onOpen, onClose) {
} }
$("#toast-container").animate({ $("#toast-container").animate({
right: (PanelToggler.getPanelSize()[0] + 5) right: (UIUtil.getSidePanelSize()[0] + 5)
}, { }, {
queue: false, queue: false,
duration: 500 duration: 500
@ -116,7 +115,7 @@ var PanelToggler = {
}, },
resizeChat () { resizeChat () {
let [width, height] = this.getPanelSize(); let [width, height] = UIUtil.getSidePanelSize();
Chat.resizeChat(width, height); Chat.resizeChat(width, height);
}, },
@ -156,21 +155,6 @@ var PanelToggler = {
null); null);
}, },
/**
* Returns the size of the side panel.
*/
getPanelSize () {
var availableHeight = window.innerHeight;
var availableWidth = window.innerWidth;
var panelWidth = 200;
if (availableWidth * 0.2 < 200) {
panelWidth = availableWidth * 0.2;
}
return [panelWidth, availableHeight];
},
isVisible () { isVisible () {
return (Chat.isVisible() || return (Chat.isVisible() ||
ContactList.isVisible() || ContactList.isVisible() ||

View File

@ -9,13 +9,6 @@ const defaultBottomToolbarButtons = {
'filmstrip': '#bottom_toolbar_film_strip' 'filmstrip': '#bottom_toolbar_film_strip'
}; };
$(document).bind("remotevideo.resized", function (event, width, height) {
let toolbar = $('#bottomToolbar');
let bottom = (height - toolbar.outerHeight())/2 + 18;
toolbar.css({bottom});
});
const BottomToolbar = { const BottomToolbar = {
init (emitter) { init (emitter) {
UIUtil.hideDisabledButtons(defaultBottomToolbarButtons); UIUtil.hideDisabledButtons(defaultBottomToolbarButtons);
@ -42,6 +35,13 @@ const BottomToolbar = {
toggleFilmStrip () { toggleFilmStrip () {
$("#remoteVideos").toggleClass("hidden"); $("#remoteVideos").toggleClass("hidden");
},
onRemoteVideoResized (width, height) {
let toolbar = $('#bottomToolbar');
let bottom = (height - toolbar.outerHeight())/2 + 18;
toolbar.css({bottom});
} }
}; };

View File

@ -250,7 +250,7 @@ const Toolbar = {
* Disables and enables some of the buttons. * Disables and enables some of the buttons.
*/ */
setupButtonsFromConfig () { setupButtonsFromConfig () {
if (UIUtil.isButtonEnabled('prezi')) { if (!UIUtil.isButtonEnabled('prezi')) {
$("#toolbar_button_prezi").css({display: "none"}); $("#toolbar_button_prezi").css({display: "none"});
} }
}, },
@ -283,6 +283,12 @@ const Toolbar = {
} }
}, },
showEtherpadButton () {
if (!$('#toolbar_button_etherpad').is(":visible")) {
$('#toolbar_button_etherpad').css({display: 'inline-block'});
}
},
// Shows or hides the 'recording' button. // Shows or hides the 'recording' button.
showRecordingButton (show) { showRecordingButton (show) {
if (UIUtil.isButtonEnabled('recording') && show) { if (UIUtil.isButtonEnabled('recording') && show) {

View File

@ -1,19 +1,34 @@
/* global $, config, interfaceConfig */ /* global $, config, interfaceConfig */
import PanelToggler from "../side_pannels/SidePanelToggler";
/** /**
* Created by hristo on 12/22/14. * Created by hristo on 12/22/14.
*/ */
var UIUtil = { var UIUtil = {
/**
* Returns the size of the side panel.
*/
getSidePanelSize () {
var availableHeight = window.innerHeight;
var availableWidth = window.innerWidth;
var panelWidth = 200;
if (availableWidth * 0.2 < 200) {
panelWidth = availableWidth * 0.2;
}
return [panelWidth, availableHeight];
},
/** /**
* Returns the available video width. * Returns the available video width.
*/ */
getAvailableVideoWidth: function (isVisible) { getAvailableVideoWidth: function (isSidePanelVisible) {
if(typeof isVisible === "undefined" || isVisible === null) let rightPanelWidth = 0;
isVisible = PanelToggler.isVisible();
var rightPanelWidth if (isSidePanelVisible) {
= isVisible ? PanelToggler.getPanelSize()[0] : 0; rightPanelWidth = UIUtil.getSidePanelSize()[0];
}
return window.innerWidth - rightPanelWidth; return window.innerWidth - rightPanelWidth;
}, },
@ -118,6 +133,12 @@ import PanelToggler from "../side_pannels/SidePanelToggler";
redirect (url) { redirect (url) {
window.location.href = url; window.location.href = url;
},
isFullScreen () {
return document.fullScreen
|| document.mozFullScreen
|| document.webkitIsFullScreen;
} }
}; };

View File

@ -0,0 +1,24 @@
export default class LargeContainer {
/**
* @returns Promise
*/
show () {
}
/**
* @returns Promise
*/
hide () {
}
resize (containerWidth, containerHeight, animate) {
}
onHoverIn (e) {
}
onHoverOut (e) {
}
}

View File

@ -1,98 +1,20 @@
/* global $, APP, interfaceConfig */ /* global $, APP, interfaceConfig */
/* jshint -W101 */ /* jshint -W101 */
import Avatar from "../avatar/Avatar";
import ToolbarToggler from "../toolbars/ToolbarToggler";
import UIUtil from "../util/UIUtil"; import UIUtil from "../util/UIUtil";
import UIEvents from "../../../service/UI/UIEvents"; import UIEvents from "../../../service/UI/UIEvents";
import LargeContainer from './LargeContainer';
var RTCBrowserType = require("../../RTC/RTCBrowserType"); const RTCBrowserType = require("../../RTC/RTCBrowserType");
// FIXME: With Temasys we have to re-select everytime const avatarSize = interfaceConfig.ACTIVE_SPEAKER_AVATAR_SIZE;
//var video = $('#largeVideo');
var currentVideoWidth = null; function getStreamId(stream) {
var currentVideoHeight = null; if (stream.isLocal()) {
// By default we use camera return APP.conference.localId;
var getVideoSize = getCameraVideoSize;
var getVideoPosition = getCameraVideoPosition;
/**
* The small video instance that is displayed in the large video
* @type {SmallVideo}
*/
var currentSmallVideo = null;
/**
* Indicates whether the large video is enabled.
* @type {boolean}
*/
var isEnabled = true;
/**
* Current large video state.
* Possible values - video, prezi or etherpad.
* @type {string}
*/
var state = "video";
/**
* Returns the html element associated with the passed state of large video
* @param state the state.
* @returns {JQuery|*|jQuery|HTMLElement} the container.
*/
function getContainerByState(state) {
var selector = null;
switch (state) {
case "video":
selector = "#largeVideoWrapper";
break;
case "etherpad":
selector = "#etherpad>iframe";
break;
case "prezi":
selector = "#presentation>iframe";
break;
default:
return null;
}
return $(selector);
}
/**
* Sets the size and position of the given video element.
*
* @param video the video element to position
* @param width the desired video width
* @param height the desired video height
* @param horizontalIndent the left and right indent
* @param verticalIndent the top and bottom indent
*/
function positionVideo(video,
width,
height,
horizontalIndent,
verticalIndent,
animate) {
if (animate) {
video.animate({
width: width,
height: height,
top: verticalIndent,
bottom: verticalIndent,
left: horizontalIndent,
right: horizontalIndent
}, {
queue: false,
duration: 500
});
} else { } else {
video.width(width); return stream.getParticipantId();
video.height(height);
video.css({
top: verticalIndent,
bottom: verticalIndent,
left: horizontalIndent,
right: horizontalIndent
});
} }
} }
/** /**
@ -106,80 +28,28 @@ function getDesktopVideoSize(videoWidth,
videoHeight, videoHeight,
videoSpaceWidth, videoSpaceWidth,
videoSpaceHeight) { videoSpaceHeight) {
if (!videoWidth)
videoWidth = currentVideoWidth;
if (!videoHeight)
videoHeight = currentVideoHeight;
var aspectRatio = videoWidth / videoHeight; let aspectRatio = videoWidth / videoHeight;
var availableWidth = Math.max(videoWidth, videoSpaceWidth); let availableWidth = Math.max(videoWidth, videoSpaceWidth);
var availableHeight = Math.max(videoHeight, videoSpaceHeight); let availableHeight = Math.max(videoHeight, videoSpaceHeight);
var filmstrip = $("#remoteVideos"); let filmstrip = $("#remoteVideos");
if (!filmstrip.hasClass("hidden")) if (!filmstrip.hasClass("hidden"))
videoSpaceHeight -= filmstrip.outerHeight(); videoSpaceHeight -= filmstrip.outerHeight();
if (availableWidth / aspectRatio >= videoSpaceHeight) if (availableWidth / aspectRatio >= videoSpaceHeight) {
{
availableHeight = videoSpaceHeight; availableHeight = videoSpaceHeight;
availableWidth = availableHeight * aspectRatio; availableWidth = availableHeight * aspectRatio;
} }
if (availableHeight * aspectRatio >= videoSpaceWidth) if (availableHeight * aspectRatio >= videoSpaceWidth) {
{
availableWidth = videoSpaceWidth; availableWidth = videoSpaceWidth;
availableHeight = availableWidth / aspectRatio; availableHeight = availableWidth / aspectRatio;
} }
return [availableWidth, availableHeight]; return { availableWidth, availableHeight };
}
/**
* Returns an array of the video horizontal and vertical indents,
* so that if fits its parent.
*
* @return an array with 2 elements, the horizontal indent and the vertical
* indent
*/
function getCameraVideoPosition(videoWidth,
videoHeight,
videoSpaceWidth,
videoSpaceHeight) {
// Parent height isn't completely calculated when we position the video in
// full screen mode and this is why we use the screen height in this case.
// Need to think it further at some point and implement it properly.
var isFullScreen = document.fullScreen ||
document.mozFullScreen ||
document.webkitIsFullScreen;
if (isFullScreen)
videoSpaceHeight = window.innerHeight;
var horizontalIndent = (videoSpaceWidth - videoWidth) / 2;
var verticalIndent = (videoSpaceHeight - videoHeight) / 2;
return [horizontalIndent, verticalIndent];
}
/**
* Returns an array of the video horizontal and vertical indents.
* Centers horizontally and top aligns vertically.
*
* @return an array with 2 elements, the horizontal indent and the vertical
* indent
*/
function getDesktopVideoPosition(videoWidth,
videoHeight,
videoSpaceWidth,
videoSpaceHeight) {
var horizontalIndent = (videoSpaceWidth - videoWidth) / 2;
var verticalIndent = 0;// Top aligned
return [horizontalIndent, verticalIndent];
} }
@ -199,15 +69,10 @@ function getCameraVideoSize(videoWidth,
videoSpaceWidth, videoSpaceWidth,
videoSpaceHeight) { videoSpaceHeight) {
if (!videoWidth) let aspectRatio = videoWidth / videoHeight;
videoWidth = currentVideoWidth;
if (!videoHeight)
videoHeight = currentVideoHeight;
var aspectRatio = videoWidth / videoHeight; let availableWidth = videoWidth;
let availableHeight = videoHeight;
var availableWidth = videoWidth;
var availableHeight = videoHeight;
if (interfaceConfig.VIDEO_LAYOUT_FIT == 'height') { if (interfaceConfig.VIDEO_LAYOUT_FIT == 'height') {
availableHeight = videoSpaceHeight; availableHeight = videoSpaceHeight;
@ -233,487 +98,341 @@ function getCameraVideoSize(videoWidth,
} }
return [availableWidth, availableHeight]; return { availableWidth, availableHeight };
} }
/** /**
* Updates the src of the active speaker avatar * Returns an array of the video horizontal and vertical indents,
* so that if fits its parent.
*
* @return an array with 2 elements, the horizontal indent and the vertical
* indent
*/ */
function updateActiveSpeakerAvatarSrc() { function getCameraVideoPosition(videoWidth,
let avatar = $("#activeSpeakerAvatar"); videoHeight,
let id = currentSmallVideo.id; videoSpaceWidth,
let url = Avatar.getActiveSpeakerUrl(id); videoSpaceHeight) {
if (id && avatar.attr('src') !== url) { // Parent height isn't completely calculated when we position the video in
avatar.attr('src', url); // full screen mode and this is why we use the screen height in this case.
currentSmallVideo.showAvatar(); // Need to think it further at some point and implement it properly.
if (UIUtil.isFullScreen()) {
videoSpaceHeight = window.innerHeight;
} }
let horizontalIndent = (videoSpaceWidth - videoWidth) / 2;
let verticalIndent = (videoSpaceHeight - videoHeight) / 2;
return { horizontalIndent, verticalIndent };
} }
/** /**
* Change the video source of the large video. * Returns an array of the video horizontal and vertical indents.
* @param isVisible * Centers horizontally and top aligns vertically.
*
* @return an array with 2 elements, the horizontal indent and the vertical
* indent
*/ */
function changeVideo(isVisible) { function getDesktopVideoPosition(videoWidth,
videoHeight,
videoSpaceWidth,
videoSpaceHeight) {
if (!currentSmallVideo) { let horizontalIndent = (videoSpaceWidth - videoWidth) / 2;
console.error("Unable to change large video - no 'currentSmallVideo'");
return; let verticalIndent = 0;// Top aligned
return { horizontalIndent, verticalIndent };
} }
updateActiveSpeakerAvatarSrc(); export const VideoContainerType = "video";
let largeVideoElement = $('#largeVideo');
currentSmallVideo.stream.attach(largeVideoElement); class VideoContainer extends LargeContainer {
// FIXME: With Temasys we have to re-select everytime
get $video () {
return $('#largeVideo');
}
let flipX = currentSmallVideo.flipX; get id () {
if (this.stream) {
largeVideoElement.css({ return getStreamId(this.stream);
transform: flipX ? "scaleX(-1)" : "none"
});
LargeVideo.updateVideoSizeAndPosition(currentSmallVideo.getVideoType());
// Only if the large video is currently visible.
if (isVisible) {
LargeVideo.VideoLayout.largeVideoUpdated(currentSmallVideo);
$('#largeVideoWrapper').fadeTo(300, 1);
} }
} }
/** constructor (onPlay) {
* Creates the html elements for the large video. super();
*/ this.stream = null;
function createLargeVideoHTML()
{
var html = '<div id="largeVideoContainer" class="videocontainer">';
html += '<div id="presentation"></div>' +
'<div id="etherpad"></div>' +
'<a target="_new"><div class="watermark leftwatermark"></div></a>' +
'<a target="_new"><div class="watermark rightwatermark"></div></a>' +
'<a class="poweredby" href="http://jitsi.org" target="_new" >' +
'<span data-i18n="poweredby"></span> jitsi.org' +
'</a>'+
'<div id="activeSpeaker">' +
'<img id="activeSpeakerAvatar" src=""/>' +
'<canvas id="activeSpeakerAudioLevel"></canvas>' +
'</div>' +
'<div id="largeVideoWrapper">' +
'<video id="largeVideo" muted="true"' +
'autoplay oncontextmenu="return false;"></video>' +
'</div id="largeVideoWrapper">' +
'<span id="videoConnectionMessage"></span>';
html += '</div>';
$(html).prependTo("#videospace");
if (interfaceConfig.SHOW_JITSI_WATERMARK) { this.$avatar = $('#activeSpeaker');
var leftWatermarkDiv this.$wrapper = $('#largeVideoWrapper');
= $("#largeVideoContainer div[class='watermark leftwatermark']");
leftWatermarkDiv.css({display: 'block'});
leftWatermarkDiv.parent().get(0).href
= interfaceConfig.JITSI_WATERMARK_LINK;
}
if (interfaceConfig.SHOW_BRAND_WATERMARK) {
var rightWatermarkDiv
= $("#largeVideoContainer div[class='watermark rightwatermark']");
rightWatermarkDiv.css({display: 'block'});
rightWatermarkDiv.parent().get(0).href
= interfaceConfig.BRAND_WATERMARK_LINK;
rightWatermarkDiv.get(0).style.backgroundImage
= "url(images/rightwatermark.png)";
}
if (interfaceConfig.SHOW_POWERED_BY) {
$("#largeVideoContainer>a[class='poweredby']").css({display: 'block'});
}
if (!RTCBrowserType.isIExplorer()) { if (!RTCBrowserType.isIExplorer()) {
$('#largeVideo').volume = 0; this.$video.volume = 0;
}
} }
var LargeVideo = { this.$video.on('play', onPlay);
}
init: function (VideoLayout, emitter) { getStreamSize () {
if(!isEnabled) let video = this.$video[0];
return; return {
createLargeVideoHTML(); width: video.videoWidth,
height: video.videoHeight
this.VideoLayout = VideoLayout;
this.eventEmitter = emitter;
this.eventEmitter.emit(UIEvents.LARGEVIDEO_INIT);
var self = this;
// Listen for large video size updates
var largeVideo = $('#largeVideo')[0];
var onplaying = function (arg1, arg2, arg3) {
// re-select
if (RTCBrowserType.isTemasysPluginUsed())
largeVideo = $('#largeVideo')[0];
currentVideoWidth = largeVideo.videoWidth;
currentVideoHeight = largeVideo.videoHeight;
self.position(currentVideoWidth, currentVideoHeight);
}; };
largeVideo.onplaying = onplaying;
},
/**
* Indicates if the large video is currently visible.
*
* @return <tt>true</tt> if visible, <tt>false</tt> - otherwise
*/
isLargeVideoVisible: function() {
return $('#largeVideoWrapper').is(':visible');
},
/**
* Returns <tt>true</tt> if the user is currently displayed on large video.
*/
isCurrentlyOnLarge: function (id) {
return id && id === this.getId();
},
/**
* Updates the large video with the given new video source.
*/
updateLargeVideo: function (id, forceUpdate) {
if(!isEnabled) {
return;
}
let newSmallVideo = this.VideoLayout.getSmallVideo(id);
console.info(`hover in ${id} , video: `, newSmallVideo);
if (!newSmallVideo) {
console.error("Small video not found for: " + id);
return;
} }
if (!LargeVideo.isCurrentlyOnLarge(id) || forceUpdate) { getVideoSize (containerWidth, containerHeight) {
$('#activeSpeaker').css('visibility', 'hidden'); let { width, height } = this.getStreamSize();
if (this.stream && this.stream.isScreenSharing()) {
let oldId = this.getId(); return getDesktopVideoSize(width, height, containerWidth, containerHeight);
} else {
currentSmallVideo = newSmallVideo; return getCameraVideoSize(width, height, containerWidth, containerHeight);
if (oldId !== id) {
// we want the notification to trigger even if id is undefined,
// or null.
this.eventEmitter.emit(UIEvents.SELECTED_ENDPOINT, id);
} }
}
getVideoPosition (width, height, containerWidth, containerHeight) {
if (this.stream && this.stream.isScreenSharing()) {
return getDesktopVideoPosition(width, height, containerWidth, containerHeight);
} else {
return getCameraVideoPosition(width, height, containerWidth, containerHeight);
}
}
resize (containerWidth, containerHeight, animate = false) {
let { width, height } = this.getVideoSize(containerWidth, containerHeight);
let { horizontalIndent, verticalIndent } = this.getVideoPosition(width, height, containerWidth, containerHeight);
// update avatar position
let top = this.containerHeight / 2 - avatarSize / 4 * 3;
this.$avatar.css('top', top);
this.$wrapper.animate({
width,
height,
top: verticalIndent,
bottom: verticalIndent,
left: horizontalIndent,
right: horizontalIndent
}, {
queue: false,
duration: animate ? 500 : 0
});
}
setStream (stream) {
this.stream = stream;
stream.attach(this.$video);
let flipX = stream.isLocal() && !stream.isScreenSharing();
this.$video.css({
transform: flipX ? 'scaleX(-1)' : 'none'
});
}
showAvatar (show) {
this.$avatar.css("visibility", show ? "visible" : "hidden");
}
// We are doing fadeOut/fadeIn animations on parent div which wraps // We are doing fadeOut/fadeIn animations on parent div which wraps
// largeVideo, because when Temasys plugin is in use it replaces // largeVideo, because when Temasys plugin is in use it replaces
// <video> elements with plugin <object> tag. In Safari jQuery is // <video> elements with plugin <object> tag. In Safari jQuery is
// unable to store values on this plugin object which breaks all // unable to store values on this plugin object which breaks all
// animation effects performed on it directly. // animation effects performed on it directly.
//
// If for any reason large video was hidden before calling fadeOut
// changeVideo will never be called, so we call show() in chain just
// to be sure
$('#largeVideoWrapper').show().fadeTo(300, 0,
changeVideo.bind($('#largeVideo'), this.isLargeVideoVisible()));
} else {
if (currentSmallVideo) {
currentSmallVideo.showAvatar();
}
}
}, show () {
let $wrapper = this.$wrapper;
/** return new Promise(resolve => {
* Shows/hides the large video. $wrapper.fadeIn(300, function () {
*/ $wrapper.css({visibility: 'visible'});
setLargeVideoVisible: function(isVisible) {
if(!isEnabled)
return;
if (isVisible) {
$('#largeVideoWrapper').css({visibility: 'visible'});
$('.watermark').css({visibility: 'visible'}); $('.watermark').css({visibility: 'visible'});
if(currentSmallVideo) });
currentSmallVideo.enableDominantSpeaker(true); resolve();
});
} }
else {
$('#largeVideoWrapper').css({visibility: 'hidden'}); hide () {
$('#activeSpeaker').css('visibility', 'hidden'); this.showAvatar(false);
let $wrapper = this.$wrapper;
return new Promise(resolve => {
$wrapper.fadeOut(300, function () {
$wrapper.css({visibility: 'hidden'});
$('.watermark').css({visibility: 'hidden'}); $('.watermark').css({visibility: 'hidden'});
if(currentSmallVideo) resolve();
currentSmallVideo.enableDominantSpeaker(false); });
});
} }
},
onVideoTypeChanged: function (id, newVideoType) {
if (!isEnabled)
return;
if (LargeVideo.isCurrentlyOnLarge(id)) {
LargeVideo.updateVideoSizeAndPosition(newVideoType);
this.position(null, null, null, null, true);
} }
},
/**
* Positions the large video. export default class LargeVideoManager {
* constructor () {
* @param videoWidth the stream video width this.containers = {};
* @param videoHeight the stream video height
*/ this.state = VideoContainerType;
position: function (videoWidth, videoHeight, this.videoContainer = new VideoContainer(() => this.resizeContainer(VideoContainerType));
videoSpaceWidth, videoSpaceHeight, animate) { this.addContainer(VideoContainerType, this.videoContainer);
if(!isEnabled)
this.width = 0;
this.height = 0;
this.$container = $('#largeVideoContainer');
this.$container.css({
display: 'inline-block'
});
if (interfaceConfig.SHOW_JITSI_WATERMARK) {
let leftWatermarkDiv = this.$container.find("div.watermark.leftwatermark");
leftWatermarkDiv.css({display: 'block'});
leftWatermarkDiv.parent().attr('href', interfaceConfig.JITSI_WATERMARK_LINK);
}
if (interfaceConfig.SHOW_BRAND_WATERMARK) {
let rightWatermarkDiv = this.$container.find("div.watermark.rightwatermark");
rightWatermarkDiv.css({
display: 'block',
backgroundImage: 'url(images/rightwatermark.png)'
});
rightWatermarkDiv.parent().attr('href', interfaceConfig.BRAND_WATERMARK_LINK);
}
if (interfaceConfig.SHOW_POWERED_BY) {
this.$container.children("a.poweredby").css({display: 'block'});
}
this.$container.hover(
e => this.onHoverIn(e),
e => this.onHoverOut(e)
);
}
onHoverIn (e) {
if (!this.state) {
return; return;
if(!videoSpaceWidth) }
videoSpaceWidth = $('#videospace').width(); let container = this.getContainer(this.state);
if(!videoSpaceHeight) container.onHoverIn(e);
videoSpaceHeight = window.innerHeight; }
var videoSize = getVideoSize(videoWidth, onHoverOut (e) {
videoHeight, if (!this.state) {
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($('#largeVideoWrapper'),
largeVideoWidth,
largeVideoHeight,
horizontalIndent, verticalIndent, animate);
},
/**
* Resizes the large html elements.
*
* @param animate boolean property that indicates whether the resize should
* be animated or not.
* @param isSideBarVisible boolean property that indicates whether the chat
* area is displayed or not.
* If that parameter is null the method will check the chat panel
* 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, isSideBarVisible, completeFunction) {
if(!isEnabled)
return; return;
var availableHeight = window.innerHeight; }
var availableWidth = UIUtil.getAvailableVideoWidth(isSideBarVisible); let container = this.getContainer(this.state);
container.onHoverOut(e);
}
if (availableWidth < 0 || availableHeight < 0) return; get id () {
return this.videoContainer.id;
}
var avatarSize = interfaceConfig.ACTIVE_SPEAKER_AVATAR_SIZE; updateLargeVideo (stream) {
var top = availableHeight / 2 - avatarSize / 4 * 3; let id = getStreamId(stream);
$('#activeSpeaker').css('top', top);
this.VideoLayout let container = this.getContainer(this.state);
.resizeVideoSpace(animate, isSideBarVisible, completeFunction);
if(animate) { container.hide().then(() => {
$('#largeVideoContainer').animate({ console.info("hover in %s", id);
width: availableWidth, this.state = VideoContainerType;
height: availableHeight this.videoContainer.setStream(stream);
}, this.videoContainer.show();
{ });
}
updateContainerSize (isSideBarVisible) {
this.width = UIUtil.getAvailableVideoWidth(isSideBarVisible);
this.height = window.innerHeight;
}
resizeContainer (type, animate = false) {
let container = this.getContainer(type);
container.resize(this.width, this.height, animate);
}
resize (animate) {
// resize all containers
Object.keys(this.containers).forEach(type => this.resizeContainer(type, animate));
this.$container.animate({
width: this.width,
height: this.height
}, {
queue: false, queue: false,
duration: 500 duration: animate ? 500 : 0
}); });
} else {
$('#largeVideoContainer').width(availableWidth);
$('#largeVideoContainer').height(availableHeight);
} }
return [availableWidth, availableHeight];
},
/**
* Resizes the large video.
*
* @param isSideBarVisible indicating if the side bar is visible
* @param completeFunction the callback function to be executed after the
* resize
*/
resizeVideoAreaAnimated: function (isSideBarVisible, completeFunction) {
if(!isEnabled)
return;
var size = this.resize(true, isSideBarVisible, completeFunction);
this.position(null, null, size[0], size[1], true);
},
/**
* Updates the video size and position.
*
* @param videoType the video type indicating if the stream is of type
* desktop or web cam
*/
updateVideoSizeAndPosition: function (videoType) {
if (!videoType)
videoType = currentSmallVideo.getVideoType();
var isDesktop = videoType === 'desktop';
// Change the way we'll be measuring and positioning large video
getVideoSize = isDesktop ? getDesktopVideoSize : getCameraVideoSize;
getVideoPosition = isDesktop ? getDesktopVideoPosition :
getCameraVideoPosition;
},
getId: function () {
return currentSmallVideo ? currentSmallVideo.id : null;
},
updateAvatar: function (id) {
if (!isEnabled) {
return;
}
if (id === this.getId()) {
updateActiveSpeakerAvatarSrc();
}
},
showAvatar: function (id, show) {
if (!isEnabled) {
return;
}
if (this.getId() === id && state === "video") {
$("#largeVideoWrapper").css("visibility", show ? "hidden" : "visible");
$('#activeSpeaker').css("visibility", show ? "visible" : "hidden");
return true;
}
return false;
},
/**
* Disables the large video
*/
disable: function () {
isEnabled = false;
},
/**
* Enables the large video
*/
enable: function () {
isEnabled = true;
},
/**
* Returns true if the video is enabled.
*/
isEnabled: function () {
return isEnabled;
},
/**
* Creates the iframe used by the etherpad
* @param src the value for src attribute
* @param onloadHandler handler executed when the iframe loads it content
* @returns {HTMLElement} the iframe
*/
createEtherpadIframe: function (src, onloadHandler) {
if(!isEnabled)
return;
var etherpadIFrame = document.createElement('iframe');
etherpadIFrame.src = src;
etherpadIFrame.frameBorder = 0;
etherpadIFrame.scrolling = "no";
etherpadIFrame.width = $('#largeVideoContainer').width() || 640;
etherpadIFrame.height = $('#largeVideoContainer').height() || 480;
etherpadIFrame.setAttribute('style', 'visibility: hidden;');
document.getElementById('etherpad').appendChild(etherpadIFrame);
etherpadIFrame.onload = onloadHandler;
return etherpadIFrame;
},
/**
* Changes the state of the large video.
* Possible values - video, prezi, etherpad.
* @param newState - the new state
*/
setState: function (newState) {
if(state === newState)
return;
var currentContainer = getContainerByState(state);
if(!currentContainer)
return;
var self = this;
var oldState = state;
switch (newState)
{
case "etherpad":
$('#activeSpeaker').css('visibility', 'hidden');
currentContainer.fadeOut(300, function () {
if (oldState === "prezi") {
currentContainer.css({opacity: '0'});
$('#reloadPresentation').css({display: 'none'});
}
else {
self.setLargeVideoVisible(false);
}
});
$('#etherpad>iframe').fadeIn(300, function () {
document.body.style.background = '#eeeeee';
$('#etherpad>iframe').css({visibility: 'visible'});
$('#etherpad').css({zIndex: 2});
});
break;
case "prezi":
var prezi = $('#presentation>iframe');
currentContainer.fadeOut(300, function() {
document.body.style.background = 'black';
});
prezi.fadeIn(300, function() {
prezi.css({opacity:'1'});
ToolbarToggler.dockToolbar(true);//fix that
self.setLargeVideoVisible(false);
$('#etherpad>iframe').css({visibility: 'hidden'});
$('#etherpad').css({zIndex: 0});
});
$('#activeSpeaker').css('visibility', 'hidden');
break;
case "video":
currentContainer.fadeOut(300, function () {
$('#presentation>iframe').css({opacity:'0'});
$('#reloadPresentation').css({display:'none'});
$('#etherpad>iframe').css({visibility: 'hidden'});
$('#etherpad').css({zIndex: 0});
document.body.style.background = 'black';
ToolbarToggler.dockToolbar(false);//fix that
});
$('#largeVideoWrapper').fadeIn(300, function () {
self.setLargeVideoVisible(true);
});
break;
}
state = newState;
},
/**
* Returns the current state of the large video.
* @returns {string} the current state - video, prezi or etherpad.
*/
getState: function () {
return state;
},
/**
* Sets hover handlers for the large video container div.
*
* @param inHandler
* @param outHandler
*/
setHover: function(inHandler, outHandler)
{
$('#largeVideoContainer').hover(inHandler, outHandler);
},
/** /**
* Enables/disables the filter indicating a video problem to the user. * Enables/disables the filter indicating a video problem to the user.
* *
* @param enable <tt>true</tt> to enable, <tt>false</tt> to disable * @param enable <tt>true</tt> to enable, <tt>false</tt> to disable
*/ */
enableVideoProblemFilter: function (enable) { enableVideoProblemFilter (enable) {
$("#largeVideo").toggleClass("videoProblemFilter", enable); this.videoContainer.$video.toggleClass("videoProblemFilter", enable);
} }
};
export default LargeVideo; /**
* Updates the src of the active speaker avatar
*/
updateAvatar (thumbUrl) {
$("#activeSpeakerAvatar").attr('src', thumbUrl);
}
showAvatar (show) {
this.videoContainer.showAvatar(show);
}
addContainer (type, container) {
if (this.containers[type]) {
throw new Error(`container of type ${type} already exist`);
}
this.containers[type] = container;
this.resizeContainer(type);
}
getContainer (type) {
let container = this.containers[type];
if (!container) {
throw new Error(`container of type ${type} doesn't exist`);
}
return container;
}
removeContainer (type) {
if (!this.containers[type]) {
throw new Error(`container of type ${type} doesn't exist`);
}
delete this.containers[type];
}
showContainer (type) {
if (this.state === type) {
return Promise.resolve();
}
let container = this.getContainer(type);
if (this.state) {
let oldContainer = this.containers[this.state];
if (oldContainer) {
oldContainer.hide();
}
}
this.state = type;
return container.show();
}
}

View File

@ -145,8 +145,8 @@ SmallVideo.prototype.bindHoverHandler = function () {
function () { function () {
// If the video has been "pinned" by the user we want to // If the video has been "pinned" by the user we want to
// keep the display name on place. // keep the display name on place.
if (!LargeVideo.isLargeVideoVisible() || if (!self.VideoLayout.isLargeVideoVisible() ||
!LargeVideo.isCurrentlyOnLarge(self.id)) !self.VideoLayout.isCurrentlyOnLarge(self.id))
self.showDisplayName(false); self.showDisplayName(false);
} }
); );
@ -254,7 +254,7 @@ SmallVideo.prototype.enableDominantSpeaker = function (isEnable) {
} }
if (isEnable) { if (isEnable) {
this.showDisplayName(LargeVideo.getState() === "video"); this.showDisplayName(this.VideoLayout.isLargeVideoVisible());
if (!this.container.classList.contains("dominantspeaker")) if (!this.container.classList.contains("dominantspeaker"))
this.container.classList.add("dominantspeaker"); this.container.classList.add("dominantspeaker");
@ -388,7 +388,10 @@ SmallVideo.prototype.showAvatar = function (show) {
} }
} }
if (LargeVideo.showAvatar(this.id, show)) { if (this.VideoLayout.isCurrentlyOnLarge(this.id)
&& this.VideoLayout.isLargeVideoVisible()) {
this.VideoLayout.showLargeVideoAvatar(show);
setVisibility(avatar, false); setVisibility(avatar, false);
setVisibility(video, false); setVisibility(video, false);
} else { } else {

View File

@ -2,12 +2,14 @@
/* jshint -W101 */ /* jshint -W101 */
import AudioLevels from "../audio_levels/AudioLevels"; import AudioLevels from "../audio_levels/AudioLevels";
import BottomToolbar from "../toolbars/BottomToolbar";
import UIEvents from "../../../service/UI/UIEvents"; import UIEvents from "../../../service/UI/UIEvents";
import UIUtil from "../util/UIUtil"; import UIUtil from "../util/UIUtil";
import RemoteVideo from "./RemoteVideo"; import RemoteVideo from "./RemoteVideo";
import LargeVideo from "./LargeVideo"; import LargeVideoManager, {VideoContainerType} from "./LargeVideo";
import {PreziContainerType} from '../prezi/Prezi';
import LocalVideo from "./LocalVideo"; import LocalVideo from "./LocalVideo";
var MediaStreamType = require("../../../service/RTC/MediaStreamTypes"); var MediaStreamType = require("../../../service/RTC/MediaStreamTypes");
@ -88,22 +90,31 @@ function getPeerContainerResourceId (containerElement) {
} }
} }
let largeVideo;
var VideoLayout = { var VideoLayout = {
init (emitter) { init (emitter) {
eventEmitter = emitter; eventEmitter = emitter;
localVideoThumbnail = new LocalVideo(VideoLayout, emitter); localVideoThumbnail = new LocalVideo(VideoLayout, emitter);
if (interfaceConfig.filmStripOnly) {
LargeVideo.disable();
} else {
LargeVideo.init(VideoLayout, emitter);
}
VideoLayout.resizeLargeVideoContainer();
emitter.addListener(UIEvents.CONTACT_CLICKED, onContactClicked); emitter.addListener(UIEvents.CONTACT_CLICKED, onContactClicked);
}, },
initLargeVideo (isSideBarVisible) {
largeVideo = new LargeVideoManager();
largeVideo.updateContainerSize(isSideBarVisible);
AudioLevels.init();
},
setAudioLevel(id, lvl) {
if (!largeVideo) {
return;
}
AudioLevels.updateAudioLevel(
id, lvl, largeVideo.id
);
},
isInLastN (resource) { isInLastN (resource) {
return lastNCount < 0 || // lastN is disabled return lastNCount < 0 || // lastN is disabled
// lastNEndpoints cache not built yet // lastNEndpoints cache not built yet
@ -147,8 +158,8 @@ var VideoLayout = {
localVideoThumbnail.changeVideo(stream); localVideoThumbnail.changeVideo(stream);
/* force update if we're currently being displayed */ /* force update if we're currently being displayed */
if (LargeVideo.isCurrentlyOnLarge(localId)) { if (this.isCurrentlyOnLarge(localId)) {
LargeVideo.updateLargeVideo(localId, true); this.updateLargeVideo(localId, true);
} }
}, },
@ -156,8 +167,8 @@ var VideoLayout = {
let id = APP.conference.localId; let id = APP.conference.localId;
localVideoThumbnail.joined(id); localVideoThumbnail.joined(id);
if (!LargeVideo.id) { if (largeVideo && !largeVideo.id) {
LargeVideo.updateLargeVideo(id, true); this.updateLargeVideo(id, true);
} }
}, },
@ -183,7 +194,7 @@ var VideoLayout = {
* another one instead. * another one instead.
*/ */
updateRemovedVideo (id) { updateRemovedVideo (id) {
if (id !== LargeVideo.getId()) { if (!this.isCurrentlyOnLarge(id)) {
return; return;
} }
@ -198,7 +209,7 @@ var VideoLayout = {
newId = this.electLastVisibleVideo(); newId = this.electLastVisibleVideo();
} }
LargeVideo.updateLargeVideo(newId); this.updateLargeVideo(newId);
}, },
electLastVisibleVideo () { electLastVisibleVideo () {
@ -242,10 +253,6 @@ var VideoLayout = {
remoteVideos[id].addRemoteStreamElement(stream); remoteVideos[id].addRemoteStreamElement(stream);
}, },
getLargeVideoId () {
return LargeVideo.getId();
},
/** /**
* Return the type of the remote video. * Return the type of the remote video.
* @param id the id for the remote video * @param id the id for the remote video
@ -255,25 +262,6 @@ var VideoLayout = {
return remoteVideoTypes[id]; return remoteVideoTypes[id];
}, },
/**
* Called when large video update is finished
* @param currentSmallVideo small video currently displayed on large video
*/
largeVideoUpdated (currentSmallVideo) {
// Makes sure that dominant speaker UI
// is enabled only on current small video
localVideoThumbnail.enableDominantSpeaker(localVideoThumbnail === currentSmallVideo);
Object.keys(remoteVideos).forEach(
function (resourceJid) {
var remoteVideo = remoteVideos[resourceJid];
if (remoteVideo) {
remoteVideo.enableDominantSpeaker(
remoteVideo === currentSmallVideo);
}
}
);
},
handleVideoThumbClicked (noPinnedEndpointChangedEvent, handleVideoThumbClicked (noPinnedEndpointChangedEvent,
resourceJid) { resourceJid) {
if(focusedVideoResourceJid) { if(focusedVideoResourceJid) {
@ -291,7 +279,7 @@ var VideoLayout = {
// Enable the currently set dominant speaker. // Enable the currently set dominant speaker.
if (currentDominantSpeaker) { if (currentDominantSpeaker) {
if(smallVideo && smallVideo.hasVideo()) { if(smallVideo && smallVideo.hasVideo()) {
LargeVideo.updateLargeVideo(currentDominantSpeaker); this.updateLargeVideo(currentDominantSpeaker);
} }
} }
@ -314,9 +302,7 @@ var VideoLayout = {
} }
} }
LargeVideo.setState("video"); this.updateLargeVideo(resourceJid);
LargeVideo.updateLargeVideo(resourceJid);
// Writing volume not allowed in IE // Writing volume not allowed in IE
if (!RTCBrowserType.isIExplorer()) { if (!RTCBrowserType.isIExplorer()) {
@ -370,11 +356,11 @@ var VideoLayout = {
// the current dominant speaker. // the current dominant speaker.
if ((!focusedVideoResourceJid && if ((!focusedVideoResourceJid &&
!currentDominantSpeaker && !currentDominantSpeaker &&
!require("../prezi/Prezi").isPresentationVisible()) || !this.isLargeContainerTypeVisible(PreziContainerType)) ||
focusedVideoResourceJid === resourceJid || focusedVideoResourceJid === resourceJid ||
(resourceJid && (resourceJid &&
currentDominantSpeaker === resourceJid)) { currentDominantSpeaker === resourceJid)) {
LargeVideo.updateLargeVideo(resourceJid, true); this.updateLargeVideo(resourceJid, true);
} }
}, },
@ -419,63 +405,44 @@ var VideoLayout = {
/** /**
* Resizes the large video container. * Resizes the large video container.
*/ */
resizeLargeVideoContainer () { resizeLargeVideoContainer (isSideBarVisible) {
if(LargeVideo.isEnabled()) { if (largeVideo) {
LargeVideo.resize(); largeVideo.updateContainerSize(isSideBarVisible);
largeVideo.resize(false);
} else { } else {
VideoLayout.resizeVideoSpace(); this.resizeVideoSpace(false, isSideBarVisible);
} }
VideoLayout.resizeThumbnails(); this.resizeThumbnails(false);
LargeVideo.position();
}, },
/** /**
* Resizes thumbnails. * Resizes thumbnails.
*/ */
resizeThumbnails (animate) { resizeThumbnails (animate = false) {
var videoSpaceWidth = $('#remoteVideos').width(); let videoSpaceWidth = $('#remoteVideos').width();
var thumbnailSize = VideoLayout.calculateThumbnailSize(videoSpaceWidth); let [width, height] = this.calculateThumbnailSize(videoSpaceWidth);
var width = thumbnailSize[0];
var height = thumbnailSize[1];
$('.userAvatar').css('left', (width - height) / 2); $('.userAvatar').css('left', (width - height) / 2);
if(animate) {
$('#remoteVideos').animate({ $('#remoteVideos').animate({
// adds 2 px because of small video 1px border // adds 2 px because of small video 1px border
height: height + 2 height: height + 2
}, }, {
{
queue: false, queue: false,
duration: 500 duration: animate ? 500 : 0
}); });
$('#remoteVideos>span').animate({ $('#remoteVideos>span').animate({
height: height, height, width
width: width }, {
},
{
queue: false, queue: false,
duration: 500, duration: animate ? 500 : 0,
complete: function () { complete: function () {
$(document).trigger( BottomToolbar.onRemoteVideoResized(width, height);
"remotevideo.resized", AudioLevels.onRemoteVideoResized(width, height);
[width,
height]);
} }
}); });
} else {
// size videos so that while keeping AR and max height, we have a
// nice fit
// adds 2 px because of small video 1px border
$('#remoteVideos').height(height + 2);
$('#remoteVideos>span').width(width);
$('#remoteVideos>span').height(height);
$(document).trigger("remotevideo.resized", [width, height]);
}
}, },
/** /**
@ -604,7 +571,7 @@ var VideoLayout = {
// Update the large video if the video source is already available, // Update the large video if the video source is already available,
// otherwise wait for the "videoactive.jingle" event. // otherwise wait for the "videoactive.jingle" event.
if (videoSel[0].currentTime > 0) { if (videoSel[0].currentTime > 0) {
LargeVideo.updateLargeVideo(id); this.updateLargeVideo(id);
} }
} }
}, },
@ -696,7 +663,7 @@ var VideoLayout = {
// displayed in the large video we have to switch to another // displayed in the large video we have to switch to another
// user. // user.
if (!updateLargeVideo && if (!updateLargeVideo &&
resourceJid === LargeVideo.getId()) { this.isCurrentlyOnLarge(resourceJid)) {
updateLargeVideo = true; updateLargeVideo = true;
} }
} }
@ -754,7 +721,7 @@ var VideoLayout = {
continue; continue;
// videoSrcToSsrc needs to be update for this call to succeed. // videoSrcToSsrc needs to be update for this call to succeed.
LargeVideo.updateLargeVideo(resource); this.updateLargeVideo(resource);
break; break;
} }
} }
@ -862,15 +829,9 @@ var VideoLayout = {
} }
smallVideo.setVideoType(newVideoType); smallVideo.setVideoType(newVideoType);
LargeVideo.onVideoTypeChanged(id, newVideoType); if (this.isCurrentlyOnLarge(id)) {
}, this.updateLargeVideo(id, true);
}
/**
* Updates the video size and position.
*/
updateLargeVideoSize () {
LargeVideo.updateVideoSizeAndPosition();
LargeVideo.position(null, null, null, null, true);
}, },
showMore (jid) { showMore (jid) {
@ -886,22 +847,8 @@ var VideoLayout = {
} }
}, },
addPreziContainer (id) { addRemoteVideoContainer (id) {
var container = RemoteVideo.createContainer(id); return RemoteVideo.createContainer(id);
VideoLayout.resizeThumbnails();
return container;
},
setLargeVideoVisible (isVisible) {
LargeVideo.setLargeVideoVisible(isVisible);
if(!isVisible && focusedVideoResourceJid) {
var smallVideo = VideoLayout.getSmallVideo(focusedVideoResourceJid);
if(smallVideo) {
smallVideo.focus(false);
smallVideo.showAvatar();
}
focusedVideoResourceJid = null;
}
}, },
/** /**
@ -912,8 +859,15 @@ var VideoLayout = {
* resized. * resized.
*/ */
resizeVideoArea (isSideBarVisible, callback) { resizeVideoArea (isSideBarVisible, callback) {
LargeVideo.resizeVideoAreaAnimated(isSideBarVisible, callback); let animate = true;
VideoLayout.resizeThumbnails(true);
if (largeVideo) {
largeVideo.updateContainerSize(isSideBarVisible);
largeVideo.resize(animate);
this.resizeVideoSpace(animate, isSideBarVisible, callback);
}
VideoLayout.resizeThumbnails(animate);
}, },
/** /**
@ -945,8 +899,7 @@ var VideoLayout = {
complete: completeFunction complete: completeFunction
}); });
} else { } else {
$('#videospace').width(availableWidth); $('#videospace').width(availableWidth).height(availableHeight);
$('#videospace').height(availableHeight);
} }
}, },
@ -968,43 +921,105 @@ var VideoLayout = {
"Missed avatar update - no small video yet for " + id "Missed avatar update - no small video yet for " + id
); );
} }
LargeVideo.updateAvatar(id, thumbUrl); if (this.isCurrentlyOnLarge(id)) {
}, largeVideo.updateAvatar(thumbUrl);
}
createEtherpadIframe (src, onloadHandler) {
return LargeVideo.createEtherpadIframe(src, onloadHandler);
},
setLargeVideoState (state) {
LargeVideo.setState(state);
},
getLargeVideoState () {
return LargeVideo.getState();
},
setLargeVideoHover (inHandler, outHandler) {
LargeVideo.setHover(inHandler, outHandler);
}, },
/** /**
* Indicates that the video has been interrupted. * Indicates that the video has been interrupted.
*/ */
onVideoInterrupted () { onVideoInterrupted () {
LargeVideo.enableVideoProblemFilter(true); this.enableVideoProblemFilter(true);
var reconnectingKey = "connection.RECONNECTING"; let reconnectingKey = "connection.RECONNECTING";
$('#videoConnectionMessage').attr("data-i18n", reconnectingKey);
$('#videoConnectionMessage') $('#videoConnectionMessage')
.text(APP.translation.translateString(reconnectingKey)); .attr("data-i18n", reconnectingKey)
$('#videoConnectionMessage').css({display: "block"}); .text(APP.translation.translateString(reconnectingKey))
.css({display: "block"});
}, },
/** /**
* Indicates that the video has been restored. * Indicates that the video has been restored.
*/ */
onVideoRestored () { onVideoRestored () {
LargeVideo.enableVideoProblemFilter(false); this.enableVideoProblemFilter(false);
$('#videoConnectionMessage').css({display: "none"}); $('#videoConnectionMessage').css({display: "none"});
},
enableVideoProblemFilter (enable) {
if (!largeVideo) {
return;
}
largeVideo.enableVideoProblemFilter(enable);
},
isLargeVideoVisible () {
return this.isLargeContainerTypeVisible(VideoContainerType);
},
isCurrentlyOnLarge (id) {
return largeVideo && largeVideo.id === id;
},
updateLargeVideo (id, forceUpdate) {
if (!largeVideo) {
return;
}
let isOnLarge = this.isCurrentlyOnLarge(id);
let currentId = largeVideo.id;
if (!isOnLarge || forceUpdate) {
if (id !== currentId) {
eventEmitter.emit(UIEvents.SELECTED_ENDPOINT, id);
}
if (currentId) {
let currentSmallVideo = this.getSmallVideo(currentId);
currentSmallVideo && currentSmallVideo.enableDominantSpeaker(false);
}
let smallVideo = this.getSmallVideo(id);
largeVideo.updateLargeVideo(smallVideo.stream);
smallVideo.enableDominantSpeaker(true);
} else if (currentId) {
let currentSmallVideo = this.getSmallVideo(currentId);
currentSmallVideo.showAvatar();
}
},
showLargeVideoAvatar (show) {
largeVideo && largeVideo.showAvatar(show);
},
addLargeVideoContainer (type, container) {
largeVideo && largeVideo.addContainer(type, container);
},
removeLargeVideoContainer (type) {
largeVideo && largeVideo.removeContainer(type);
},
/**
* @returns Promise
*/
showLargeVideoContainer (type, show) {
if (!largeVideo) {
return Promise.reject();
}
let isVisible = this.isLargeContainerTypeVisible(type);
if (isVisible === show) {
return Promise.resolve();
}
// if !show then use default type - large video
return largeVideo.showContainer(show ? type : VideoContainerType);
},
isLargeContainerTypeVisible (type) {
return largeVideo && largeVideo.state === type;
} }
}; };

View File

@ -2,7 +2,6 @@ export default {
NICKNAME_CHANGED: "UI.nickname_changed", NICKNAME_CHANGED: "UI.nickname_changed",
SELECTED_ENDPOINT: "UI.selected_endpoint", SELECTED_ENDPOINT: "UI.selected_endpoint",
PINNED_ENDPOINT: "UI.pinned_endpoint", PINNED_ENDPOINT: "UI.pinned_endpoint",
LARGEVIDEO_INIT: "UI.largevideo_init",
/** /**
* Notifies that local user created text message. * Notifies that local user created text message.
*/ */
@ -22,6 +21,9 @@ export default {
AUDIO_MUTED: "UI.audio_muted", AUDIO_MUTED: "UI.audio_muted",
VIDEO_MUTED: "UI.video_muted", VIDEO_MUTED: "UI.video_muted",
PREZI_CLICKED: "UI.prezi_clicked", PREZI_CLICKED: "UI.prezi_clicked",
SHARE_PREZI: "UI.share_prezi",
PREZI_SLIDE_CHANGED: "UI.prezi_slide_changed",
STOP_SHARING_PREZI: "UI.stop_sharing_prezi",
ETHERPAD_CLICKED: "UI.etherpad_clicked", ETHERPAD_CLICKED: "UI.etherpad_clicked",
ROOM_LOCK_CLICKED: "UI.room_lock_clicked", ROOM_LOCK_CLICKED: "UI.room_lock_clicked",
USER_INVITED: "UI.user_invited", USER_INVITED: "UI.user_invited",