Merge branch 'master' of https://github.com/jitsi/jitsi-meet into follow-me

This commit is contained in:
yanas 2016-03-22 15:24:02 -05:00
commit b1469186d1
26 changed files with 917 additions and 902 deletions

View File

@ -47,5 +47,6 @@ source-package:
mkdir -p source_package/jitsi-meet/css && \
cp -r analytics.js external_api.js favicon.ico fonts images index.html interface_config.js libs plugin.*html sounds title.html unsupported_browser.html LICENSE config.js lang source_package/jitsi-meet && \
cp css/all.css source_package/jitsi-meet/css && \
cp css/unsupported_browser.css source_package/jitsi-meet/css && \
(cd source_package ; tar cjf ../jitsi-meet.tar.bz2 jitsi-meet) && \
rm -rf source_package

View File

@ -4,7 +4,7 @@ Jitsi Meet is an open-source (Apache) WebRTC JavaScript application that uses [J
You can also try it out yourself at https://meet.jit.si .
Jitsi Meet allows for very efficient collaboration. It allows users to stream their desktop or only some windows. It also supports shared document editing with Etherpad and remote presentations with Prezi.
Jitsi Meet allows for very efficient collaboration. It allows users to stream their desktop or only some windows. It also supports shared document editing with Etherpad.
## Installation

View File

@ -28,8 +28,7 @@ const Commands = {
CONNECTION_QUALITY: "stats",
EMAIL: "email",
ETHERPAD: "etherpad",
PREZI: "prezi",
STOP_PREZI: "stop-prezi"
SHARED_VIDEO: "shared-video"
};
/**
@ -258,6 +257,10 @@ class ConferenceConnector {
APP.UI.notifyFocusLeft();
break;
case ConferenceErrors.CONFERENCE_MAX_USERS:
connection.disconnect();
APP.UI.notifyMaxUsersLimitReached();
break;
default:
this._handleConferenceFailed(err, ...params);
}
@ -732,7 +735,7 @@ export default {
console.log('USER %s LEFT', id, user);
APP.API.notifyUserLeft(id);
APP.UI.removeUser(id, user.getDisplayName());
APP.UI.stopPrezi(id);
APP.UI.stopSharedVideo({from: id});
});
@ -911,35 +914,6 @@ export default {
APP.UI.initEtherpad(value);
});
room.addCommandListener(Commands.PREZI, ({value, attributes}) => {
APP.UI.showPrezi(attributes.id, value, attributes.slide);
});
room.addCommandListener(Commands.STOP_PREZI, ({attributes}) => {
APP.UI.stopPrezi(attributes.id);
});
APP.UI.addListener(UIEvents.SHARE_PREZI, (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, () => {
room.removeCommand(Commands.PREZI);
room.sendCommandOnce(Commands.STOP_PREZI, {
attributes: {
id: room.myUserId()
}
});
});
APP.UI.addListener(UIEvents.EMAIL_CHANGED, (email = '') => {
email = email.trim();
@ -1085,5 +1059,47 @@ export default {
APP.UI.addListener(
UIEvents.TOGGLE_SCREENSHARING, this.toggleScreenSharing.bind(this)
);
APP.UI.addListener(UIEvents.UPDATE_SHARED_VIDEO,
(url, state, time, volume) => {
// send start and stop commands once, and remove any updates
// that had left
if (state === 'stop' || state === 'start' || state === 'playing') {
room.removeCommand(Commands.SHARED_VIDEO);
room.sendCommandOnce(Commands.SHARED_VIDEO, {
value: url,
attributes: {
from: APP.conference.localId,
state: state,
time: time,
volume: volume
}
});
}
else {
// in case of paused, in order to allow late users to join
// paused
room.sendCommand(Commands.SHARED_VIDEO, {
value: url,
attributes: {
from: APP.conference.localId,
state: state,
time: time,
volume: volume
}
});
}
});
room.addCommandListener(
Commands.SHARED_VIDEO, ({value, attributes}) => {
if (attributes.state === 'stop') {
APP.UI.stopSharedVideo(attributes);
} else if (attributes.state === 'start') {
APP.UI.showSharedVideo(value, attributes);
} else if (attributes.state === 'playing'
|| attributes.state === 'pause') {
APP.UI.updateSharedVideo(value, attributes);
}
});
}
};

View File

@ -63,9 +63,6 @@
.icon-exit-full-screen:before {
content: "\e60e";
}
.icon-prezi:before {
content: "\e60c";
}
.icon-link:before {
content: "\e600";
}

View File

@ -63,6 +63,7 @@ html, body{
text-align: center;
text-shadow: 0 1px 0 rgba(255,255,255,.3), 0 -1px 0 rgba(0,0,0,.6);
z-index: 1;
font-size: 1.22em !important;
}
.toolbar_span>span {

View File

@ -31,6 +31,7 @@
position: relative;
margin-left: auto;
margin-right: auto;
text-align: center;
}
#remoteVideos .videocontainer {
@ -112,6 +113,7 @@
}
#presentation,
#sharedVideo,
#etherpad,
#localVideoWrapper>video,
#localVideoWrapper>object,
@ -436,6 +438,10 @@
border-radius: 200px;
}
.sharedVideoAvatar {
height: 100%;
}
.noMic {
position: absolute;
border-radius: 8px;

View File

@ -122,8 +122,8 @@
<a class="button icon-chat" id="toolbar_button_chat" data-container="body" data-toggle="popover" shortcut="toggleChatPopover" data-placement="bottom" data-i18n="[content]toolbar.chat" content="Open / close chat">
<span id="unreadMessages"></span>
</a>
<a class="button icon-prezi" id="toolbar_button_prezi" data-container="body" data-toggle="popover" data-placement="bottom" data-i18n="[content]toolbar.prezi" content="Share Prezi"></a>
<a class="button icon-share-doc" id="toolbar_button_etherpad" data-container="body" data-toggle="popover" data-placement="bottom" content="Shared document" data-i18n="[content]toolbar.etherpad"></a>
<a class="button fa fa-share-alt-square" id="toolbar_button_sharedvideo" data-container="body" data-toggle="popover" data-placement="bottom" content="Shared Video" data-i18n="[content]toolbar.sharedvideo" style="display: none"></a>
<a class="button icon-share-desktop" id="toolbar_button_desktopsharing" data-container="body" data-toggle="popover" data-placement="bottom" shortcut="toggleDesktopSharingPopover" content="Share screen" data-i18n="[content]toolbar.sharescreen" style="display: none"></a>
<a class="button icon-full-screen" id="toolbar_button_fullScreen" data-container="body" data-toggle="popover" data-placement="bottom" content="Enter / Exit Full Screen" data-i18n="[content]toolbar.fullscreen"></a>
<a class="button icon-telephone" id="toolbar_button_sip" data-container="body" data-toggle="popover" data-placement="bottom" content="Call SIP number" data-i18n="[content]toolbar.sip" style="display: none"></a>
@ -134,11 +134,11 @@
</div>
<div id="subject"></div>
</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="largeVideoContainer" class="videocontainer">
<div id="presentation"></div>
<div id="sharedVideo"><div id="sharedVideoIFrame"></div></div>
<div id="etherpad"></div>
<a target="_new"><div class="watermark leftwatermark"></div></a>
<a target="_new"><div class="watermark rightwatermark"></div></a>

View File

@ -16,7 +16,7 @@ var interfaceConfig = {
INVITATION_POWERED_BY: true,
DOMINANT_SPEAKER_AVATAR_SIZE: 100,
TOOLBAR_BUTTONS: ['authentication', 'microphone', 'camera', 'desktop',
'recording', 'security', 'invite', 'chat', 'prezi', 'etherpad',
'recording', 'security', 'invite', 'chat', 'etherpad', 'sharedvideo',
'fullscreen', 'sip', 'dialpad', 'settings', 'hangup', 'filmstrip',
'contacts'],
// Determines how the video would fit the screen. 'both' would fit the whole

View File

@ -9,11 +9,11 @@
"me": "me",
"speaker": "Speaker",
"defaultNickname": "ex. __name__",
"defaultPreziLink": "e.g. __url__",
"defaultLink": "e.g. __url__",
"welcomepage":{
"go": "GO",
"roomname": "Enter room name",
"disable": "Don't show this page the next time I enter",
"disable": "Don't show this page again",
"feature1": {
"title": "Simple to use",
"content": "No downloads required. __app__ works directly within your browser. Simply share your conference URL with others to get started."
@ -55,8 +55,8 @@
"lock": "Lock / unlock room",
"invite": "Invite others",
"chat": "Open / close chat",
"prezi": "Share Prezi",
"etherpad": "Shared document",
"sharedvideo": "Shared video",
"sharescreen": "Share screen",
"fullscreen": "Enter / Exit Full Screen",
"sip": "Call SIP number",
@ -146,6 +146,7 @@
"failedpermissions": "Failed to obtain permissions to use the local microphone and/or camera.",
"bridgeUnavailable": "Jitsi Videobridge is currently unavailable. Please try again later!",
"jicofoUnavailable": "Jicofo is currently unavailable. Please try again later!",
"maxUsersLimitReached": "The limit for maximum number of participants in the conference has been reached. The conference is full. Please try again later!",
"lockTitle": "Lock failed",
"lockMessage": "Failed to lock the conference.",
"warning": "Warning",
@ -159,11 +160,11 @@
"defaultError": "There was some kind of error",
"passwordRequired": "Password required",
"Ok": "Ok",
"removePreziTitle": "Remove Prezi",
"removePreziMsg": "Are you sure you would like to remove your Prezi?",
"sharePreziTitle": "Share a Prezi",
"sharePreziMsg": "Another participant is already sharing a Prezi. This conference allows only one Prezi at a time.",
"Remove": "Remove",
"shareVideoTitle": "Share a video",
"shareVideoLinkError": "Please provide a correct youtube link.",
"removeSharedVideoTitle": "Remove shared video",
"removeSharedVideoMsg": "Are you sure you would like to remove your shared video?",
"WaitingForHost": "Waiting for the host ...",
"WaitForHostMsg": "The conference <b>__room__ </b> has not yet started. If you are the host then please authenticate. Otherwise, please wait for the host to arrive.",
"IamHost": "I am the host",
@ -175,7 +176,6 @@
"hungUp": "You hung up",
"joinAgain": "Join again",
"Share": "Share",
"preziLinkError": "Please provide a correct prezi link.",
"Save": "Save",
"recordingToken": "Enter recording token",
"Dial": "Dial",

View File

@ -12,8 +12,8 @@ import PanelToggler from "./side_pannels/SidePanelToggler";
import UIUtil from "./util/UIUtil";
import UIEvents from "../../service/UI/UIEvents";
import CQEvents from '../../service/connectionquality/CQEvents';
import PreziManager from './prezi/Prezi';
import EtherpadManager from './etherpad/Etherpad';
import SharedVideoManager from './shared_video/SharedVideo';
import VideoLayout from "./videolayout/VideoLayout";
import FilmStrip from "./videolayout/FilmStrip";
@ -32,8 +32,8 @@ import FollowMe from "../FollowMe";
var eventEmitter = new EventEmitter();
UI.eventEmitter = eventEmitter;
let preziManager;
let etherpadManager;
let sharedVideoManager;
/**
* Prompt user for nickname.
@ -98,7 +98,6 @@ function setupChat() {
*/
function setupToolbars() {
Toolbar.init(eventEmitter);
Toolbar.setupButtonsFromConfig();
BottomToolbar.setupListeners(eventEmitter);
}
@ -264,9 +263,6 @@ UI.mucJoined = function () {
* Setup some UI event listeners.
*/
function registerListeners() {
UI.addListener(UIEvents.PREZI_CLICKED, function () {
preziManager.handlePreziButtonClicked();
});
UI.addListener(UIEvents.ETHERPAD_CLICKED, function () {
if (etherpadManager) {
@ -274,6 +270,12 @@ function registerListeners() {
}
});
UI.addListener(UIEvents.SHARED_VIDEO_CLICKED, function () {
if (sharedVideoManager) {
sharedVideoManager.toggleSharedVideo();
}
});
UI.addListener(UIEvents.FULLSCREEN_TOGGLE, toggleFullScreen);
UI.addListener(UIEvents.TOGGLE_CHAT, UI.toggleChat);
@ -284,7 +286,10 @@ function registerListeners() {
UI.addListener(UIEvents.TOGGLE_CONTACT_LIST, UI.toggleContactList);
UI.addListener(UIEvents.TOGGLE_FILM_STRIP, UI.toggleFilmStrip);
UI.addListener(UIEvents.TOGGLE_FILM_STRIP, function () {
UI.toggleFilmStrip();
VideoLayout.resizeVideoArea(PanelToggler.isVisible(), true, false);
});
}
/**
@ -345,7 +350,7 @@ UI.start = function () {
ContactList.init(eventEmitter);
bindEvents();
preziManager = new PreziManager(eventEmitter);
sharedVideoManager = new SharedVideoManager(eventEmitter);
if (!interfaceConfig.filmStripOnly) {
$("#videospace").mousemove(function () {
@ -493,11 +498,11 @@ UI.addUser = function (id, displayName) {
config.startAudioMuted > APP.conference.membersCount)
UIUtil.playSoundNotification('userJoined');
// Configure avatar
UI.setUserAvatar(id);
// Add Peer's container
VideoLayout.addParticipantContainer(id);
// Configure avatar
UI.setUserAvatar(id);
};
/**
@ -542,6 +547,7 @@ UI.updateLocalRole = function (isModerator) {
Toolbar.showSipCallButton(isModerator);
Toolbar.showRecordingButton(isModerator);
Toolbar.showSharedVideoButton(isModerator);
SettingsMenu.showStartMutedOptions(isModerator);
if (isModerator) {
@ -741,6 +747,22 @@ UI.notifyConnectionFailed = function (stropheErrorMsg) {
);
};
/**
* Notify user that maximum users limit has been reached.
*/
UI.notifyMaxUsersLimitReached = function () {
var title = APP.translation.generateTranslationHTML(
"dialog.error");
var message = APP.translation.generateTranslationHTML(
"dialog.maxUsersLimitReached");
messageHandler.openDialog(
title, message, true, {}, function (e, v, m, f) { return false; }
);
};
/**
* Notify user that he was automatically muted when joned the conference.
*/
@ -1007,26 +1029,6 @@ UI.updateAuthInfo = function (isAuthEnabled, login) {
}
};
/**
* Show Prezi from the user.
* @param {string} userId user id
* @param {string} url Prezi url
* @param {number} slide slide to show
*/
UI.showPrezi = function (userId, url, slide) {
preziManager.showPrezi(userId, url, slide);
};
/**
* Stop showing Prezi from the user.
* @param {string} userId user id
*/
UI.stopPrezi = function (userId) {
if (preziManager.isSharing(userId)) {
preziManager.removePrezi(userId);
}
};
UI.onStartMutedChanged = function (startAudioMuted, startVideoMuted) {
SettingsMenu.updateStartMutedBox(startAudioMuted, startVideoMuted);
};
@ -1047,6 +1049,14 @@ UI.getLargeVideoID = function () {
return VideoLayout.getLargeVideoID();
};
/**
* Returns the current video shown on large.
* Currently used by tests (torture).
*/
UI.getLargeVideo = function () {
return VideoLayout.getLargeVideo();
};
/**
* Shows dialog with a link to FF extension.
*/
@ -1063,4 +1073,33 @@ UI.updateDevicesAvailability = function (id, devices) {
VideoLayout.setDeviceAvailabilityIcons(id, devices);
};
/**
* Show shared video.
* @param {string} url video url
* @param {string} attributes
*/
UI.showSharedVideo = function (url, attributes) {
if (sharedVideoManager)
sharedVideoManager.showSharedVideo(url, attributes);
};
/**
* Update shared video.
* @param {string} url video url
* @param {string} attributes
*/
UI.updateSharedVideo = function (url, attributes) {
if (sharedVideoManager)
sharedVideoManager.updateSharedVideo(url, attributes);
};
/**
* Stop showing shared video.
* @param {string} attributes
*/
UI.stopSharedVideo = function (attributes) {
if (sharedVideoManager)
sharedVideoManager.stopSharedVideo(attributes);
};
module.exports = UI;

View File

@ -52,7 +52,7 @@ const DEFAULT_WIDTH = 640;
*/
const DEFAULT_HEIGHT = 480;
const EtherpadContainerType = "etherpad";
const ETHERPAD_CONTAINER_TYPE = "etherpad";
/**
* Container for Etherpad iframe.
@ -110,9 +110,11 @@ class Etherpad extends LargeContainer {
show () {
const $iframe = $(this.iframe);
const $container = $(this.container);
let self = this;
return new Promise(resolve => {
$iframe.fadeIn(300, function () {
self.bodyBackground = document.body.style.background;
document.body.style.background = '#eeeeee';
$iframe.css({visibility: 'visible'});
$container.css({zIndex: 2});
@ -124,6 +126,7 @@ class Etherpad extends LargeContainer {
hide () {
const $iframe = $(this.iframe);
const $container = $(this.container);
document.body.style.background = this.bodyBackground;
return new Promise(resolve => {
$iframe.fadeOut(300, function () {
@ -133,6 +136,13 @@ class Etherpad extends LargeContainer {
});
});
}
/**
* @return {boolean} do not switch on dominant speaker event if on stage.
*/
stayOnStage () {
return true;
}
}
/**
@ -159,7 +169,7 @@ export default class EtherpadManager {
openEtherpad () {
this.etherpad = new Etherpad(this.domain, this.name);
VideoLayout.addLargeVideoContainer(
EtherpadContainerType,
ETHERPAD_CONTAINER_TYPE,
this.etherpad
);
}
@ -174,9 +184,10 @@ export default class EtherpadManager {
}
let isVisible = VideoLayout.isLargeContainerTypeVisible(
EtherpadContainerType
ETHERPAD_CONTAINER_TYPE
);
VideoLayout.showLargeVideoContainer(EtherpadContainerType, !isVisible);
VideoLayout.showLargeVideoContainer(
ETHERPAD_CONTAINER_TYPE, !isVisible);
}
}

View File

@ -1,448 +0,0 @@
/* global $, APP */
/* jshint -W101 */
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";
import FilmStrip from '../videolayout/FilmStrip';
/**
* Example of Prezi link.
*/
const defaultPreziLink = "http://prezi.com/wz7vhjycl7e6/my-prezi";
const alphanumRegex = /^[a-z0-9-_\/&\?=;]+$/i;
/**
* Default aspect ratio for Prezi frame.
*/
const aspectRatio = 16.0 / 9.0;
/**
* Default Prezi frame width.
*/
const DEFAULT_WIDTH = 640;
/**
* Default Prezi frame height.
*/
const DEFAULT_HEIGHT = 480;
/**
* Indicates if the given string is an alphanumeric string.
* Note that some special characters are also allowed (-, _ , /, &, ?, =, ;) for the
* purpose of checking URIs.
* @param {string} unsafeText string to check
* @returns {boolean}
*/
function isAlphanumeric(unsafeText) {
return alphanumRegex.test(unsafeText);
}
/**
* Returns the presentation id from the given url.
* @param {string} url Prezi link
* @returns {string} presentation id
*/
function getPresentationId (url) {
let presId = url.substring(url.indexOf("prezi.com/") + 10);
return presId.substring(0, presId.indexOf('/'));
}
/**
* Checks if given string is Prezi url.
* @param {string} url string to check.
* @returns {boolean}
*/
function isPreziLink(url) {
if (url.indexOf('http://prezi.com/') !== 0 && url.indexOf('https://prezi.com/') !== 0) {
return false;
}
let presId = url.substring(url.indexOf("prezi.com/") + 10);
if (!isAlphanumeric(presId) || presId.indexOf('/') < 2) {
return false;
}
return true;
}
/**
* Notify user that other user if already sharing Prezi.
*/
function notifyOtherIsSharingPrezi() {
messageHandler.openMessageDialog(
"dialog.sharePreziTitle",
"dialog.sharePreziMsg"
);
}
/**
* Ask user if he want to close Prezi he's sharing.
*/
function proposeToClosePrezi() {
return new Promise(function (resolve, reject) {
messageHandler.openTwoButtonDialog(
"dialog.removePreziTitle",
null,
"dialog.removePreziMsg",
null,
false,
"dialog.Remove",
function(e,v,m,f) {
if (v) {
resolve();
} else {
reject();
}
}
);
});
}
/**
* Ask user for Prezi url to share with others.
* Dialog validates client input to allow only Prezi urls.
*/
function requestPreziLink() {
const title = APP.translation.generateTranslationHTML("dialog.sharePreziTitle");
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');
}
}
}
});
});
}
export const PreziContainerType = "prezi";
/**
* Container for Prezi iframe.
*/
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);
}
});
preziPlayer.on(PreziPlayer.EVENT_CURRENT_STEP, function({value}) {
console.log("event value", value);
onSlideChanged(value);
});
}
/**
* Change Prezi slide.
* @param {number} slide slide to show
*/
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);
}
}
/**
* Show or hide "reload presentation" button.
* @param {boolean} show
*/
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 height = containerHeight - FilmStrip.getFilmStripHeight();
let width = containerWidth;
if (height < width / aspectRatio) {
width = Math.floor(height * aspectRatio);
}
this.$iframe.width(width).height(height);
}
/**
* Close Prezi frame.
*/
close () {
this.showReloadBtn(false);
this.preziPlayer.destroy();
this.$iframe.remove();
}
}
/**
* Manager of Prezi frames.
*/
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;
}
/**
* Check if user is currently sharing.
* @param {string} id user id to check for
*/
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();
}
}
/**
* Reload current Prezi frame.
*/
reloadPresentation () {
if (!this.prezi) {
return;
}
let iframe = this.prezi.$iframe[0];
iframe.src = iframe.src;
}
/**
* Show Prezi. Create new Prezi if there is no Prezi yet.
* @param {string} id owner id
* @param {string} url Prezi url
* @param {number} slide slide to show
*/
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");
}
}
/**
* Create new Prezi frame..
* @param {string} id owner id
* @param {string} url Prezi url
* @param {number} slide slide to show
*/
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);
}
/**
* Close Prezi.
* @param {string} id owner id
*/
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,290 +0,0 @@
/* jshint -W101 */
var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
PreziPlayer.API_VERSION = 1;
PreziPlayer.CURRENT_STEP = 'currentStep';
PreziPlayer.CURRENT_ANIMATION_STEP = 'currentAnimationStep';
PreziPlayer.CURRENT_OBJECT = 'currentObject';
PreziPlayer.STATUS_LOADING = 'loading';
PreziPlayer.STATUS_READY = 'ready';
PreziPlayer.STATUS_CONTENT_READY = 'contentready';
PreziPlayer.EVENT_CURRENT_STEP = "currentStepChange";
PreziPlayer.EVENT_CURRENT_ANIMATION_STEP = "currentAnimationStepChange";
PreziPlayer.EVENT_CURRENT_OBJECT = "currentObjectChange";
PreziPlayer.EVENT_STATUS = "statusChange";
PreziPlayer.EVENT_PLAYING = "isAutoPlayingChange";
PreziPlayer.EVENT_IS_MOVING = "isMovingChange";
PreziPlayer.domain = "https://prezi.com";
PreziPlayer.path = "/player/";
PreziPlayer.players = {};
PreziPlayer.binded_methods = ['changesHandler'];
PreziPlayer.createMultiplePlayers = function(optionArray){
for(var i=0; i<optionArray.length; i++) {
var optionSet = optionArray[i];
new PreziPlayer(optionSet.id, optionSet);
}
};
PreziPlayer.messageReceived = function(event){
var message, item, player;
try {
message = JSON.parse(event.data);
if (message.id && (player = PreziPlayer.players[message.id])) {
if (player.options.debug === true) {
if (console && console.log)
console.log('received', message);
}
if (message.type === "changes") {
player.changesHandler(message);
}
for (var i = 0; i < player.callbacks.length; i++) {
item = player.callbacks[i];
if (item && message.type === item.event) {
item.callback(message);
}
}
}
} catch (e) { }
};
/*jshint -W004 */
function PreziPlayer(id, options) {
/*jshint +W004 */
var params, paramString = "", _this = this;
if (PreziPlayer.players[id]){
PreziPlayer.players[id].destroy();
}
for(var i=0; i<PreziPlayer.binded_methods.length; i++) {
var method_name = PreziPlayer.binded_methods[i];
_this[method_name] = __bind(_this[method_name], _this);
}
options = options || {};
this.options = options;
this.values = {'status': PreziPlayer.STATUS_LOADING};
this.values[PreziPlayer.CURRENT_STEP] = 0;
this.values[PreziPlayer.CURRENT_ANIMATION_STEP] = 0;
this.values[PreziPlayer.CURRENT_OBJECT] = null;
this.callbacks = [];
this.id = id;
this.embedTo = document.getElementById(id);
if (!this.embedTo) {
throw "The element id is not available.";
}
this.iframe = document.createElement('iframe');
params = [
{ name: 'oid', value: options.preziId },
{ name: 'explorable', value: options.explorable ? 1 : 0 },
{ name: 'controls', value: options.controls ? 1 : 0 }
];
for (i=0; i<params.length; i++) {
var param = params[i];
paramString += (i===0 ? "?" : "&") + param.name + "=" + param.value;
}
this.iframe.src = PreziPlayer.domain + PreziPlayer.path + paramString;
this.iframe.frameBorder = 0;
this.iframe.scrolling = "no";
this.iframe.width = options.width || 640;
this.iframe.height = options.height || 480;
this.embedTo.innerHTML = '';
// JITSI: IN CASE SOMETHING GOES WRONG.
try {
this.embedTo.appendChild(this.iframe);
}
catch (err) {
console.log("CATCH ERROR");
}
// JITSI: Increase interval from 200 to 500, which fixes prezi
// crashes for us.
this.initPollInterval = setInterval(function(){
_this.sendMessage({'action': 'init'});
}, 500);
PreziPlayer.players[id] = this;
}
PreziPlayer.prototype.changesHandler = function(message) {
var key, value, j, item;
if (this.initPollInterval) {
clearInterval(this.initPollInterval);
this.initPollInterval = false;
}
for (key in message.data) {
if (message.data.hasOwnProperty(key)){
value = message.data[key];
this.values[key] = value;
for (j=0; j<this.callbacks.length; j++) {
item = this.callbacks[j];
if (item && item.event === key + "Change"){
item.callback({type: item.event, value: value});
}
}
}
}
};
PreziPlayer.prototype.destroy = function() {
if (this.initPollInterval) {
clearInterval(this.initPollInterval);
this.initPollInterval = false;
}
this.embedTo.innerHTML = '';
};
PreziPlayer.prototype.sendMessage = function(message) {
if (this.options.debug === true) {
if (console && console.log) console.log('sent', message);
}
message.version = PreziPlayer.API_VERSION;
message.id = this.id;
return this.iframe.contentWindow.postMessage(JSON.stringify(message), '*');
};
PreziPlayer.prototype.nextStep = /* nextStep is DEPRECATED */
PreziPlayer.prototype.flyToNextStep = function() {
return this.sendMessage({
'action': 'present',
'data': ['moveToNextStep']
});
};
PreziPlayer.prototype.previousStep = /* previousStep is DEPRECATED */
PreziPlayer.prototype.flyToPreviousStep = function() {
return this.sendMessage({
'action': 'present',
'data': ['moveToPrevStep']
});
};
PreziPlayer.prototype.toStep = /* toStep is DEPRECATED */
PreziPlayer.prototype.flyToStep = function(step, animation_step) {
var obj = this;
// check animation_step
if (animation_step > 0 &&
obj.values.animationCountOnSteps &&
obj.values.animationCountOnSteps[step] <= animation_step) {
animation_step = obj.values.animationCountOnSteps[step];
}
// jump to animation steps by calling flyToNextStep()
function doAnimationSteps() {
if (obj.values.isMoving) {
setTimeout(doAnimationSteps, 100); // wait until the flight ends
return;
}
while (animation_step-- > 0) {
obj.flyToNextStep(); // do the animation steps
}
}
setTimeout(doAnimationSteps, 200); // 200ms is the internal "reporting" time
// jump to the step
return this.sendMessage({
'action': 'present',
'data': ['moveToStep', step]
});
};
PreziPlayer.prototype.toObject = /* toObject is DEPRECATED */
PreziPlayer.prototype.flyToObject = function(objectId) {
return this.sendMessage({
'action': 'present',
'data': ['moveToObject', objectId]
});
};
PreziPlayer.prototype.play = function(defaultDelay) {
return this.sendMessage({
'action': 'present',
'data': ['startAutoPlay', defaultDelay]
});
};
PreziPlayer.prototype.stop = function() {
return this.sendMessage({
'action': 'present',
'data': ['stopAutoPlay']
});
};
PreziPlayer.prototype.pause = function(defaultDelay) {
return this.sendMessage({
'action': 'present',
'data': ['pauseAutoPlay', defaultDelay]
});
};
PreziPlayer.prototype.getCurrentStep = function() {
return this.values.currentStep;
};
PreziPlayer.prototype.getCurrentAnimationStep = function() {
return this.values.currentAnimationStep;
};
PreziPlayer.prototype.getCurrentObject = function() {
return this.values.currentObject;
};
PreziPlayer.prototype.getStatus = function() {
return this.values.status;
};
PreziPlayer.prototype.isPlaying = function() {
return this.values.isAutoPlaying;
};
PreziPlayer.prototype.getStepCount = function() {
return this.values.stepCount;
};
PreziPlayer.prototype.getAnimationCountOnSteps = function() {
return this.values.animationCountOnSteps;
};
PreziPlayer.prototype.getTitle = function() {
return this.values.title;
};
PreziPlayer.prototype.setDimensions = function(dims) {
for (var parameter in dims) {
this.iframe[parameter] = dims[parameter];
}
};
PreziPlayer.prototype.getDimensions = function() {
return {
width: parseInt(this.iframe.width, 10),
height: parseInt(this.iframe.height, 10)
};
};
PreziPlayer.prototype.on = function(event, callback) {
this.callbacks.push({
event: event,
callback: callback
});
};
PreziPlayer.prototype.off = function(event, callback) {
var j, item;
if (event === undefined) {
this.callbacks = [];
}
j = this.callbacks.length;
while (j--) {
item = this.callbacks[j];
if (item && item.event === event && (callback === undefined || item.callback === callback)){
this.callbacks.splice(j, 1);
}
}
};
if (window.addEventListener) {
window.addEventListener('message', PreziPlayer.messageReceived, false);
} else {
window.attachEvent('onmessage', PreziPlayer.messageReceived);
}
window.PreziPlayer = PreziPlayer;
export default PreziPlayer;

View File

@ -0,0 +1,537 @@
/* global $, APP, YT, onPlayerReady, onPlayerStateChange, onPlayerError */
import messageHandler from '../util/MessageHandler';
import UIUtil from '../util/UIUtil';
import UIEvents from '../../../service/UI/UIEvents';
import VideoLayout from "../videolayout/VideoLayout";
import LargeContainer from '../videolayout/LargeContainer';
import SmallVideo from '../videolayout/SmallVideo';
import FilmStrip from '../videolayout/FilmStrip';
import ToolbarToggler from "../toolbars/ToolbarToggler";
export const SHARED_VIDEO_CONTAINER_TYPE = "sharedvideo";
/**
* Example shared video link.
* @type {string}
*/
const defaultSharedVideoLink = "https://www.youtube.com/watch?v=xNXN7CZk8X0";
/**
* Manager of shared video.
*/
export default class SharedVideoManager {
constructor (emitter) {
this.emitter = emitter;
this.isSharedVideoShown = false;
this.isPlayerAPILoaded = false;
this.updateInterval = 5000; // milliseconds
}
/**
* Starts shared video by asking user for url, or if its already working
* asks whether the user wants to stop sharing the video.
*/
toggleSharedVideo () {
if(!this.isSharedVideoShown) {
requestVideoLink().then(
url => this.emitter.emit(
UIEvents.UPDATE_SHARED_VIDEO, url, 'start'),
err => console.error('SHARED VIDEO CANCELED', err)
);
return;
}
showStopVideoPropmpt().then(() =>
this.emitter.emit(UIEvents.UPDATE_SHARED_VIDEO, null, 'stop'));
}
/**
* Shows the player component and starts the checking function
* that will be sending updates, if we are the one shared the video
* @param url the video url
* @param attributes
*/
showSharedVideo (url, attributes) {
if (this.isSharedVideoShown)
return;
// the video url
this.url = url;
// the owner of the video
this.from = attributes.from;
// This code loads the IFrame Player API code asynchronously.
var tag = document.createElement('script');
tag.src = "https://www.youtube.com/iframe_api";
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
var self = this;
if(self.isPlayerAPILoaded)
window.onYouTubeIframeAPIReady();
else
window.onYouTubeIframeAPIReady = function() {
self.isPlayerAPILoaded = true;
let showControls = APP.conference.isLocalId(self.from) ? 1 : 0;
self.player = new YT.Player('sharedVideoIFrame', {
height: '100%',
width: '100%',
videoId: self.url,
playerVars: {
'origin': location.origin,
'fs': '0',
'autoplay': 1,
'controls': showControls,
'rel' : 0
},
events: {
'onReady': onPlayerReady,
'onStateChange': onPlayerStateChange,
'onError': onPlayerError
}
});
};
window.onPlayerStateChange = function(event) {
if (event.data == YT.PlayerState.PLAYING) {
self.playerPaused = false;
self.updateCheck();
} else if (event.data == YT.PlayerState.PAUSED) {
self.playerPaused = true;
self.updateCheck(true);
}
};
window.onPlayerReady = function(event) {
let player = event.target;
player.playVideo();
let thumb = new SharedVideoThumb(self.url);
thumb.setDisplayName(player.getVideoData().title);
VideoLayout.addParticipantContainer(self.url, thumb);
let iframe = player.getIframe();
self.sharedVideo = new SharedVideoContainer(
{url, iframe, player});
VideoLayout.addLargeVideoContainer(
SHARED_VIDEO_CONTAINER_TYPE, self.sharedVideo);
VideoLayout.handleVideoThumbClicked(true, self.url);
self.isSharedVideoShown = true;
// If we are sending the command and we are starting the player
// we need to continuously send the player current time position
if(APP.conference.isLocalId(self.from)) {
self.intervalId = setInterval(
self.updateCheck.bind(self),
self.updateInterval);
}
// set initial state of the player if there is enough information
if(attributes.state === 'pause')
player.pauseVideo();
else if(attributes.time > 0) {
console.log("Player seekTo:", attributes.time);
player.seekTo(attributes.time);
}
};
window.onPlayerError = function(event) {
console.error("Error in the player:" + event.data);
};
}
/**
* Checks current state of the player and fire an event with the values.
*/
updateCheck(sendPauseEvent)
{
// ignore update checks if we are not the owner of the video
if(!APP.conference.isLocalId(this.from))
return;
let state = this.player.getPlayerState();
// if its paused and haven't been pause - send paused
if (state === YT.PlayerState.PAUSED && sendPauseEvent) {
this.emitter.emit(UIEvents.UPDATE_SHARED_VIDEO,
this.url, 'pause');
}
// if its playing and it was paused - send update with time
// if its playing and was playing just send update with time
else if (state === YT.PlayerState.PLAYING) {
this.emitter.emit(UIEvents.UPDATE_SHARED_VIDEO,
this.url, 'playing',
this.player.getCurrentTime(),
this.player.isMuted() ? 0 : this.player.getVolume());
}
}
/**
* Updates video, if its not playing and needs starting or
* if its playing and needs to be paysed
* @param url the video url
* @param attributes
*/
updateSharedVideo (url, attributes) {
// if we are sending the event ignore
if(APP.conference.isLocalId(this.from)) {
return;
}
if (attributes.state == 'playing') {
if(!this.isSharedVideoShown) {
this.showSharedVideo(url, attributes);
return;
}
// ocasionally we get this.player.getCurrentTime is not a function
// it seems its that player hasn't really loaded
if(!this.player || !this.player.getCurrentTime
|| !this.player.pauseVideo
|| !this.player.playVideo
|| !this.player.getVolume
|| !this.player.seekTo
|| !this.player.getVolume)
return;
// check received time and current time
let currentPosition = this.player.getCurrentTime();
let diff = Math.abs(attributes.time - currentPosition);
// if we drift more than two times of the interval for checking
// sync, the interval is in milliseconds
if(diff > this.updateInterval*2/1000) {
console.log("Player seekTo:", attributes.time,
" current time is:", currentPosition, " diff:", diff);
this.player.seekTo(attributes.time);
}
// lets check the volume
if (attributes.volume !== undefined &&
this.player.getVolume() != attributes.volume) {
this.player.setVolume(attributes.volume);
console.log("Player change of volume:" + attributes.volume);
}
if(this.playerPaused)
this.player.playVideo();
} else if (attributes.state == 'pause') {
// if its not paused, pause it
if(this.isSharedVideoShown) {
this.player.pauseVideo();
}
else {
// if not shown show it, passing attributes so it can
// be shown paused
this.showSharedVideo(url, attributes);
}
}
}
/**
* Stop shared video if it is currently showed. If the user started the
* shared video is the one in the attributes.from (called when user
* left and we want to remove video if the user sharing it left).
* @param attributes
*/
stopSharedVideo (attributes) {
if (!this.isSharedVideoShown)
return;
if(this.from !== attributes.from)
return;
if(this.intervalId) {
clearInterval(this.intervalId);
this.intervalId = null;
}
VideoLayout.removeParticipantContainer(this.url);
VideoLayout.showLargeVideoContainer(SHARED_VIDEO_CONTAINER_TYPE, false)
.then(() => {
VideoLayout.removeLargeVideoContainer(
SHARED_VIDEO_CONTAINER_TYPE);
this.player.destroy();
this.player = null;
});
this.url = null;
this.isSharedVideoShown = false;
}
}
/**
* Container for shared video iframe.
*/
class SharedVideoContainer extends LargeContainer {
constructor ({url, iframe, player}) {
super();
this.$iframe = $(iframe);
this.url = url;
this.player = player;
}
get $video () {
return this.$iframe;
}
show () {
return new Promise(resolve => {
this.$iframe.fadeIn(300, () => {
this.$iframe.css({opacity: 1});
resolve();
});
});
}
hide () {
return new Promise(resolve => {
this.$iframe.fadeOut(300, () => {
this.$iframe.css({opacity: 0});
resolve();
});
});
}
onHoverIn () {
ToolbarToggler.showToolbar();
}
get id () {
return this.url;
}
resize (containerWidth, containerHeight) {
let height = containerHeight - FilmStrip.getFilmStripHeight();
let width = containerWidth;
this.$iframe.width(width).height(height);
}
/**
* @return {boolean} do not switch on dominant speaker event if on stage.
*/
stayOnStage () {
return false;
}
}
function SharedVideoThumb (url)
{
this.id = url;
this.url = url;
this.setVideoType(SHARED_VIDEO_CONTAINER_TYPE);
this.videoSpanId = "sharedVideoContainer";
this.container = this.createContainer(this.videoSpanId);
this.container.onclick = this.videoClick.bind(this);
this.bindHoverHandler();
SmallVideo.call(this, VideoLayout);
this.isVideoMuted = true;
}
SharedVideoThumb.prototype = Object.create(SmallVideo.prototype);
SharedVideoThumb.prototype.constructor = SharedVideoThumb;
/**
* hide display name
*/
SharedVideoThumb.prototype.setDeviceAvailabilityIcons = function () {};
SharedVideoThumb.prototype.avatarChanged = function () {};
SharedVideoThumb.prototype.createContainer = function (spanId) {
var container = document.createElement('span');
container.id = spanId;
container.className = 'videocontainer';
// add the avatar
var avatar = document.createElement('img');
avatar.id = 'avatar_' + this.id;
avatar.className = 'sharedVideoAvatar';
avatar.src = "https://img.youtube.com/vi/" + this.url + "/0.jpg";
container.appendChild(avatar);
var remotes = document.getElementById('remoteVideos');
return remotes.appendChild(container);
};
/**
* The thumb click handler.
*/
SharedVideoThumb.prototype.videoClick = function () {
VideoLayout.handleVideoThumbClicked(true, this.url);
};
/**
* Removes RemoteVideo from the page.
*/
SharedVideoThumb.prototype.remove = function () {
console.log("Remove shared video thumb", this.id);
// Make sure that the large video is updated if are removing its
// corresponding small video.
this.VideoLayout.updateRemovedVideo(this.id);
// Remove whole container
if (this.container.parentNode) {
this.container.parentNode.removeChild(this.container);
}
};
/**
* Sets the display name for the thumb.
*/
SharedVideoThumb.prototype.setDisplayName = function(displayName) {
if (!this.container) {
console.warn( "Unable to set displayName - " + this.videoSpanId +
" does not exist");
return;
}
var nameSpan = $('#' + this.videoSpanId + '>span.displayname');
// If we already have a display name for this video.
if (nameSpan.length > 0) {
if (displayName && displayName.length > 0) {
$('#' + this.videoSpanId + '_name').text(displayName);
}
} else {
nameSpan = document.createElement('span');
nameSpan.className = 'displayname';
$('#' + this.videoSpanId)[0].appendChild(nameSpan);
if (displayName && displayName.length > 0)
$(nameSpan).text(displayName);
nameSpan.id = this.videoSpanId + '_name';
}
};
/**
* Checks if given string is youtube url.
* @param {string} url string to check.
* @returns {boolean}
*/
function getYoutubeLink(url) {
let p = /^(?:https?:\/\/)?(?:www\.)?(?:youtu\.be\/|youtube\.com\/(?:embed\/|v\/|watch\?v=|watch\?.+&v=))((\w|-){11})(?:\S+)?$/;//jshint ignore:line
return (url.match(p)) ? RegExp.$1 : false;
}
/**
* Ask user if he want to close shared video.
*/
function showStopVideoPropmpt() {
return new Promise(function (resolve, reject) {
messageHandler.openTwoButtonDialog(
"dialog.removeSharedVideoTitle",
null,
"dialog.removeSharedVideoMsg",
null,
false,
"dialog.Remove",
function(e,v,m,f) {
if (v) {
resolve();
} else {
reject();
}
}
);
});
}
/**
* Ask user for shared video url to share with others.
* Dialog validates client input to allow only youtube urls.
*/
function requestVideoLink() {
let i18n = APP.translation;
const title = i18n.generateTranslationHTML("dialog.shareVideoTitle");
const cancelButton = i18n.generateTranslationHTML("dialog.Cancel");
const shareButton = i18n.generateTranslationHTML("dialog.Share");
const backButton = i18n.generateTranslationHTML("dialog.Back");
const linkError
= i18n.generateTranslationHTML("dialog.shareVideoLinkError");
const i18nOptions = {url: defaultSharedVideoLink};
const defaultUrl = i18n.translateString("defaultLink", i18nOptions);
return new Promise(function (resolve, reject) {
let dialog = messageHandler.openDialogWithStates({
state0: {
html: `
<h2>${title}</h2>
<input name="sharedVideoUrl" type="text"
data-i18n="[placeholder]defaultLink"
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 sharedVideoUrl = f.sharedVideoUrl;
if (!sharedVideoUrl) {
return;
}
let urlValue = encodeURI(UIUtil.escapeHtml(sharedVideoUrl));
let yVideoId = getYoutubeLink(urlValue);
if (!yVideoId) {
dialog.goToState('state1');
return false;
}
resolve(yVideoId);
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');
}
}
}
});
});
}

View File

@ -93,7 +93,7 @@ function toggle (object, selector, onOpenComplete,
function resizeVideoArea(isSidePanelVisible, completeFunction) {
VideoLayout.resizeVideoArea(!isSidePanelVisible,
false,
true,
false,
completeFunction);
}

View File

@ -122,14 +122,14 @@ const buttonHandlers = {
AnalyticsAdapter.sendEvent('toolbar.chat.toggled');
emitter.emit(UIEvents.TOGGLE_CHAT);
},
"toolbar_button_prezi": function () {
AnalyticsAdapter.sendEvent('toolbar.prezi.clicked');
emitter.emit(UIEvents.PREZI_CLICKED);
},
"toolbar_button_etherpad": function () {
AnalyticsAdapter.sendEvent('toolbar.etherpad.clicked');
emitter.emit(UIEvents.ETHERPAD_CLICKED);
},
"toolbar_button_sharedvideo": function () {
AnalyticsAdapter.sendEvent('toolbar.sharedvideo.clicked');
emitter.emit(UIEvents.SHARED_VIDEO_CLICKED);
},
"toolbar_button_desktopsharing": function () {
if (APP.conference.isSharingScreen) {
AnalyticsAdapter.sendEvent('toolbar.screen.disabled');
@ -188,7 +188,6 @@ const defaultToolbarButtons = {
'security': '#toolbar_button_security',
'invite': '#toolbar_button_link',
'chat': '#toolbar_button_chat',
'prezi': '#toolbar_button_prezi',
'etherpad': '#toolbar_button_etherpad',
'fullscreen': '#toolbar_button_fullScreen',
'settings': '#toolbar_button_settings',
@ -246,15 +245,6 @@ const Toolbar = {
}
},
/**
* Disables and enables some of the buttons.
*/
setupButtonsFromConfig () {
if (!UIUtil.isButtonEnabled('prezi')) {
$("#toolbar_button_prezi").css({display: "none"});
}
},
/**
* Unlocks the lock button state.
*/
@ -298,6 +288,15 @@ const Toolbar = {
}
},
// Shows or hides the 'shared video' button.
showSharedVideoButton (show) {
if (UIUtil.isButtonEnabled('sharedvideo') && show) {
$('#toolbar_button_sharedvideo').css({display: "inline-block"});
} else {
$('#toolbar_button_sharedvideo').css({display: "none"});
}
},
// checks whether recording is enabled and whether we have params
// to start automatically recording
checkAutoRecord () {

View File

@ -124,9 +124,7 @@
isButtonEnabled: function (name) {
var isEnabled = interfaceConfig.TOOLBAR_BUTTONS.indexOf(name) !== -1;
if (name === 'prezi') {
return isEnabled && !config.disablePrezi;
} else if (name === 'recording') {
if (name === 'recording') {
return isEnabled && config.enableRecording;
}
return isEnabled;

View File

@ -38,4 +38,27 @@ export default class LargeContainer {
*/
onHoverOut (e) {
}
/**
* Update video stream.
* @param {JitsiTrack?} stream new stream
* @param {string} videoType video type
*/
setStream (stream, videoType) {
}
/**
* Show or hide user avatar.
* @param {boolean} show
*/
showAvatar (show) {
}
/**
* Whether current container needs to be switched on dominant speaker event
* when the container is on stage.
* @return {boolean}
*/
stayOnStage () {
}
}

View File

@ -11,6 +11,8 @@ import {createDeferred} from '../../util/helpers';
const avatarSize = interfaceConfig.DOMINANT_SPEAKER_AVATAR_SIZE;
const FADE_DURATION_MS = 300;
export const VIDEO_CONTAINER_TYPE = "camera";
/**
* Get stream id.
* @param {JitsiTrack?} stream
@ -150,8 +152,6 @@ function getDesktopVideoPosition(videoWidth,
return { horizontalIndent, verticalIndent };
}
export const VideoContainerType = "video";
/**
* Container for user video.
*/
@ -332,6 +332,10 @@ class VideoContainer extends LargeContainer {
}
hide () {
// as the container is hidden/replaced by another container
// hide its avatar
this.showAvatar(false);
// its already hidden
if (!this.isVisible) {
return Promise.resolve();
@ -345,6 +349,13 @@ class VideoContainer extends LargeContainer {
});
});
}
/**
* @return {boolean} switch on dominant speaker event if on stage.
*/
stayOnStage () {
return false;
}
}
/**
@ -354,9 +365,12 @@ export default class LargeVideoManager {
constructor () {
this.containers = {};
this.state = VideoContainerType;
this.videoContainer = new VideoContainer(() => this.resizeContainer(VideoContainerType));
this.addContainer(VideoContainerType, this.videoContainer);
this.state = VIDEO_CONTAINER_TYPE;
this.videoContainer = new VideoContainer(
() => this.resizeContainer(VIDEO_CONTAINER_TYPE));
this.addContainer(VIDEO_CONTAINER_TYPE, this.videoContainer);
// use the same video container to handle and desktop tracks
this.addContainer("desktop", this.videoContainer);
this.width = 0;
this.height = 0;
@ -413,7 +427,8 @@ export default class LargeVideoManager {
}
get id () {
return this.videoContainer.id;
let container = this.getContainer(this.state);
return container.id;
}
scheduleLargeVideoUpdate () {
@ -430,16 +445,22 @@ export default class LargeVideoManager {
this.newStreamData = null;
console.info("hover in %s", id);
this.state = VideoContainerType;
this.videoContainer.setStream(stream, videoType);
this.state = videoType;
let container = this.getContainer(this.state);
container.setStream(stream, videoType);
// change the avatar url on large
this.updateAvatar(Avatar.getAvatarUrl(id));
let isVideoMuted = stream ? stream.isMuted() : true;
// If we the continer is VIDEO_CONTAINER_TYPE, we need to check
// its stream whether exist and is muted to set isVideoMuted
// in rest of the cases it is false
let isVideoMuted = false;
if (videoType == VIDEO_CONTAINER_TYPE)
isVideoMuted = stream ? stream.isMuted() : true;
// show the avatar on large if needed
this.videoContainer.showAvatar(isVideoMuted);
container.showAvatar(isVideoMuted);
let promise;
@ -449,7 +470,7 @@ export default class LargeVideoManager {
this.showWatermark(true);
promise = Promise.resolve();
} else {
promise = this.videoContainer.show();
promise = container.show();
}
// resolve updateLargeVideo promise after everything is done
@ -529,7 +550,8 @@ export default class LargeVideoManager {
* @param enable <tt>true</tt> to enable, <tt>false</tt> to disable
*/
enableVideoProblemFilter (enable) {
this.videoContainer.$video.toggleClass("videoProblemFilter", enable);
let container = this.getContainer(this.state);
container.$video.toggleClass("videoProblemFilter", enable);
}
/**
@ -600,7 +622,7 @@ export default class LargeVideoManager {
}
let oldContainer = this.containers[this.state];
if (this.state === VideoContainerType) {
if (this.state === VIDEO_CONTAINER_TYPE) {
this.showWatermark(false);
}
oldContainer.hide();
@ -609,7 +631,7 @@ export default class LargeVideoManager {
let container = this.getContainer(type);
return container.show().then(() => {
if (type === VideoContainerType) {
if (type === VIDEO_CONTAINER_TYPE) {
this.showWatermark(true);
}
});

View File

@ -13,7 +13,6 @@ function LocalVideo(VideoLayout, emitter) {
this.videoSpanId = "localVideoContainer";
this.container = $("#localVideoContainer").get(0);
this.bindHoverHandler();
this.VideoLayout = VideoLayout;
this.flipX = true;
this.isLocal = true;
this.emitter = emitter;
@ -22,7 +21,7 @@ function LocalVideo(VideoLayout, emitter) {
return APP.conference.localId;
}
});
SmallVideo.call(this);
SmallVideo.call(this, VideoLayout);
}
LocalVideo.prototype = Object.create(SmallVideo.prototype);

View File

@ -11,14 +11,13 @@ function RemoteVideo(id, VideoLayout, emitter) {
this.id = id;
this.emitter = emitter;
this.videoSpanId = `participant_${id}`;
this.VideoLayout = VideoLayout;
SmallVideo.call(this, VideoLayout);
this.addRemoteVideoContainer();
this.connectionIndicator = new ConnectionIndicator(this, id);
this.setDisplayName();
this.bindHoverHandler();
this.flipX = false;
this.isLocal = false;
SmallVideo.call(this);
}
RemoteVideo.prototype = Object.create(SmallVideo.prototype);

View File

@ -5,12 +5,13 @@ import UIUtil from "../util/UIUtil";
const RTCUIHelper = JitsiMeetJS.util.RTCUIHelper;
function SmallVideo() {
function SmallVideo(VideoLayout) {
this.isMuted = false;
this.hasAvatar = false;
this.isVideoMuted = false;
this.videoStream = null;
this.audioStream = null;
this.VideoLayout = VideoLayout;
}
function setVisibility(selector, show) {
@ -362,14 +363,7 @@ SmallVideo.prototype.updateView = function () {
}
setVisibility(avatar, showAvatar);
var showDisplayName = !showVideo && !showAvatar;
if (showDisplayName) {
this.showDisplayName(this.VideoLayout.isLargeVideoVisible());
}
else {
this.showDisplayName(false);
}
this.showDisplayName(!showVideo && !showAvatar);
};
SmallVideo.prototype.avatarChanged = function (avatarUrl) {

View File

@ -9,15 +9,14 @@ import UIEvents from "../../../service/UI/UIEvents";
import UIUtil from "../util/UIUtil";
import RemoteVideo from "./RemoteVideo";
import LargeVideoManager, {VideoContainerType} from "./LargeVideo";
import {PreziContainerType} from '../prezi/Prezi';
import LargeVideoManager, {VIDEO_CONTAINER_TYPE} from "./LargeVideo";
import {SHARED_VIDEO_CONTAINER_TYPE} from '../shared_video/SharedVideo';
import LocalVideo from "./LocalVideo";
import PanelToggler from "../side_pannels/SidePanelToggler";
const RTCUIUtil = JitsiMeetJS.util.RTCUIHelper;
var remoteVideos = {};
var remoteVideoTypes = {};
var localVideoThumbnail = null;
var currentDominantSpeaker = null;
@ -94,6 +93,11 @@ var VideoLayout = {
init (emitter) {
eventEmitter = emitter;
localVideoThumbnail = new LocalVideo(VideoLayout, emitter);
// sets default video type of local video
localVideoThumbnail.setVideoType(VIDEO_CONTAINER_TYPE);
// if we do not resize the thumbs here, if there is no video device
// the local video thumb maybe one pixel
this.resizeThumbnails(false, true, false);
emitter.addListener(UIEvents.CONTACT_CLICKED, onContactClicked);
this.lastNCount = config.channelLastN;
@ -278,10 +282,11 @@ var VideoLayout = {
/**
* Return the type of the remote video.
* @param id the id for the remote video
* @returns the video type video or screen.
* @returns {String} the video type video or screen.
*/
getRemoteVideoType (id) {
return remoteVideoTypes[id];
let smallVideo = VideoLayout.getSmallVideo(id);
return smallVideo ? smallVideo.getVideoType() : null;
},
handleVideoThumbClicked (noPinnedEndpointChangedEvent,
@ -327,22 +332,26 @@ var VideoLayout = {
this.updateLargeVideo(resourceJid);
},
/**
* Checks if container for participant identified by given id exists
* in the document and creates it eventually.
*
* @return Returns <tt>true</tt> if the peer container exists,
* <tt>false</tt> - otherwise
* Creates a remote video for participant for the given id.
* @param id the id of the participant to add
* @param {SmallVideo} smallVideo optional small video instance to add as a
* remote video, if undefined RemoteVideo will be created
*/
addParticipantContainer (id) {
let remoteVideo = new RemoteVideo(id, VideoLayout, eventEmitter);
addParticipantContainer (id, smallVideo) {
let remoteVideo;
if(smallVideo)
remoteVideo = smallVideo;
else
remoteVideo = new RemoteVideo(id, VideoLayout, eventEmitter);
remoteVideos[id] = remoteVideo;
let videoType = remoteVideoTypes[id];
if (videoType) {
remoteVideo.setVideoType(videoType);
let videoType = VideoLayout.getRemoteVideoType(id);
if (!videoType) {
// make video type the default one (camera)
videoType = VIDEO_CONTAINER_TYPE;
}
remoteVideo.setVideoType(videoType);
// In case this is not currently in the last n we don't show it.
if (localLastNCount && localLastNCount > 0 &&
@ -361,11 +370,11 @@ var VideoLayout = {
false, false, false, function() {$(videoelem).show();});
// Update the large video to the last added video only if there's no
// current dominant, focused speaker or prezi playing or update it to
// current dominant, focused speaker or update it to
// the current dominant speaker.
if ((!focusedVideoResourceJid &&
!currentDominantSpeaker &&
!this.isLargeContainerTypeVisible(PreziContainerType)) ||
this.isLargeContainerTypeVisible(VIDEO_CONTAINER_TYPE)) ||
focusedVideoResourceJid === resourceJid ||
(resourceJid &&
currentDominantSpeaker === resourceJid)) {
@ -522,7 +531,9 @@ var VideoLayout = {
// since we don't want to switch to local video.
// Update the large video if the video source is already available,
// otherwise wait for the "videoactive.jingle" event.
if (!focusedVideoResourceJid && remoteVideo.hasVideoStarted()) {
if (!focusedVideoResourceJid
&& remoteVideo.hasVideoStarted()
&& !this.getCurrentlyOnLargeContainer().stayOnStage()) {
this.updateLargeVideo(id);
}
},
@ -754,12 +765,11 @@ var VideoLayout = {
},
onVideoTypeChanged (id, newVideoType) {
if (remoteVideoTypes[id] === newVideoType) {
if (VideoLayout.getRemoteVideoType(id) === newVideoType) {
return;
}
console.info("Peer video type changed: ", id, newVideoType);
remoteVideoTypes[id] = newVideoType;
var smallVideo;
if (APP.conference.isLocalId(id)) {
@ -773,8 +783,8 @@ var VideoLayout = {
} else {
return;
}
smallVideo.setVideoType(newVideoType);
if (this.isCurrentlyOnLarge(id)) {
this.updateLargeVideo(id, true);
}
@ -793,10 +803,6 @@ var VideoLayout = {
}
},
addRemoteVideoContainer (id) {
return RemoteVideo.createContainer(id);
},
/**
* Resizes the video area.
*
@ -804,10 +810,11 @@ var VideoLayout = {
* @param forceUpdate indicates that hidden thumbnails will be shown
* @param completeFunction a function to be called when the video area is
* resized.
*/resizeVideoArea (isSideBarVisible,
forceUpdate = false,
animate = false,
completeFunction = null) {
*/
resizeVideoArea (isSideBarVisible,
forceUpdate = false,
animate = false,
completeFunction = null) {
if (largeVideo) {
largeVideo.updateContainerSize(isSideBarVisible);
@ -888,7 +895,15 @@ var VideoLayout = {
},
isLargeVideoVisible () {
return this.isLargeContainerTypeVisible(VideoContainerType);
return this.isLargeContainerTypeVisible(VIDEO_CONTAINER_TYPE);
},
/**
* @return {LargeContainer} the currently displayed container on large
* video.
*/
getCurrentlyOnLargeContainer () {
return largeVideo.getContainer(largeVideo.state);
},
isCurrentlyOnLarge (id) {
@ -952,8 +967,17 @@ var VideoLayout = {
return Promise.resolve();
}
let currentId = largeVideo.id;
if(currentId) {
var oldSmallVideo = this.getSmallVideo(currentId);
}
// if !show then use default type - large video
return largeVideo.showContainer(show ? type : VideoContainerType);
return largeVideo.showContainer(show ? type : VIDEO_CONTAINER_TYPE)
.then(() => {
if(oldSmallVideo)
oldSmallVideo && oldSmallVideo.updateView();
});
},
isLargeContainerTypeVisible (type) {
@ -962,10 +986,18 @@ var VideoLayout = {
/**
* Returns the id of the current video shown on large.
* Currently used by tests (troture).
* Currently used by tests (torture).
*/
getLargeVideoID () {
return largeVideo.id;
},
/**
* Returns the the current video shown on large.
* Currently used by tests (torture).
*/
getLargeVideo () {
return largeVideo;
}
};

View File

@ -0,0 +1,68 @@
<html>
<head>
<style>
body {
margin: 0;
}
iframe {
width: 100%;
height: 100%;
border: 0 none;
}
</style>
</head>
<body>
<script>
var gui = require('nw.gui');
var screenInitialized = false;
function obtainDesktopStream (callback, errorCallback) {
if (!screenInitialized) {
gui.Screen.Init();
screenInitialized = true;
}
gui.Screen.chooseDesktopMedia(
["window","screen"],
function(streamId) {
var vid_constraint = {
mandatory: {
chromeMediaSource: 'desktop',
chromeMediaSourceId: streamId,
maxWidth: 1920,
maxHeight: 1080
},
optional: []
};
navigator.webkitGetUserMedia({
audio: false, video: vid_constraint
}, callback, errorCallback);
}
);
}
// use Esc to leave fullscreen mode
nw.App.registerGlobalHotKey(new nw.Shortcut({
key: "Escape",
active: function () {
var win = nw.Window.get();
if (win.isFullscreen) {
win.leaveFullscreen();
}
}
}));
// create iframe with jitsi-meet
var iframe = document.createElement('iframe');
iframe.src = nw.App.manifest['jitsi-url'];
iframe.allowFullscreen = true;
iframe.onload = function () {
iframe.contentWindow.JitsiMeetNW = {
obtainDesktopStream: obtainDesktopStream
};
};
document.body.appendChild(iframe);
</script>
</body>
</html>

View File

@ -0,0 +1,8 @@
{
"name": "JitsiMeetNW!",
"version": "0.0.1",
"jitsi-url": "https://ivan.jitsi.net/",
"main": "index.html",
"chromium-args": "--ignore-certificate-errors",
"user-agent": "Chrome/%webkit_ver JitsiMeetNW/%ver"
}

View File

@ -20,11 +20,14 @@ export default {
START_MUTED_CHANGED: "UI.start_muted_changed",
AUDIO_MUTED: "UI.audio_muted",
VIDEO_MUTED: "UI.video_muted",
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",
SHARED_VIDEO_CLICKED: "UI.start_shared_video",
/**
* Updates shared video with params: url, state, time(optional)
* Where url is the video link, state is stop/start/pause and time is the
* current video playing time.
*/
UPDATE_SHARED_VIDEO: "UI.update_shared_video",
ROOM_LOCK_CLICKED: "UI.room_lock_clicked",
USER_INVITED: "UI.user_invited",
USER_KICKED: "UI.user_kicked",