commit
f788a45bac
|
@ -31,6 +31,8 @@ const Commands = {
|
|||
SHARED_VIDEO: "shared-video"
|
||||
};
|
||||
|
||||
import {VIDEO_CONTAINER_TYPE} from "./modules/UI/videolayout/LargeVideo";
|
||||
|
||||
/**
|
||||
* Open Connection. When authentication failed it shows auth dialog.
|
||||
* @param roomName the room name to use
|
||||
|
@ -509,6 +511,51 @@ export default {
|
|||
|
||||
this._setupListeners();
|
||||
},
|
||||
|
||||
/**
|
||||
* Exposes a Command(s) API on this instance. It is necessitated by (1) the
|
||||
* desire to keep room private to this instance and (2) the need of other
|
||||
* modules to send and receive commands to and from participants.
|
||||
* Eventually, this instance remains in control with respect to the
|
||||
* decision whether the Command(s) API of room (i.e. lib-jitsi-meet's
|
||||
* JitsiConference) is to be used in the implementation of the Command(s)
|
||||
* API of this instance.
|
||||
*/
|
||||
commands: {
|
||||
/**
|
||||
* Receives notifications from other participants about commands aka
|
||||
* custom events (sent by sendCommand or sendCommandOnce methods).
|
||||
* @param command {String} the name of the command
|
||||
* @param handler {Function} handler for the command
|
||||
*/
|
||||
addCommandListener () {
|
||||
room.addCommandListener.apply(room, arguments);
|
||||
},
|
||||
/**
|
||||
* Removes command.
|
||||
* @param name {String} the name of the command.
|
||||
*/
|
||||
removeCommand () {
|
||||
room.removeCommand.apply(room, arguments);
|
||||
},
|
||||
/**
|
||||
* Sends command.
|
||||
* @param name {String} the name of the command.
|
||||
* @param values {Object} with keys and values that will be sent.
|
||||
*/
|
||||
sendCommand () {
|
||||
room.sendCommand.apply(room, arguments);
|
||||
},
|
||||
/**
|
||||
* Sends command one time.
|
||||
* @param name {String} the name of the command.
|
||||
* @param values {Object} with keys and values that will be sent.
|
||||
*/
|
||||
sendCommandOnce () {
|
||||
room.sendCommandOnce.apply(room, arguments);
|
||||
},
|
||||
},
|
||||
|
||||
_getConferenceOptions() {
|
||||
let options = config;
|
||||
if(config.enableRecording) {
|
||||
|
@ -985,8 +1032,20 @@ export default {
|
|||
APP.UI.addListener(UIEvents.SELECTED_ENDPOINT, (id) => {
|
||||
room.selectParticipant(id);
|
||||
});
|
||||
APP.UI.addListener(UIEvents.PINNED_ENDPOINT, (id) => {
|
||||
room.pinParticipant(id);
|
||||
|
||||
APP.UI.addListener(UIEvents.PINNED_ENDPOINT, (smallVideo, isPinned) => {
|
||||
var smallVideoId = smallVideo.getId();
|
||||
|
||||
if (smallVideo.getVideoType() === VIDEO_CONTAINER_TYPE
|
||||
&& !APP.conference.isLocalId(smallVideoId))
|
||||
if (isPinned)
|
||||
room.pinParticipant(smallVideoId);
|
||||
// When the library starts supporting multiple pins we would
|
||||
// pass the isPinned parameter together with the identifier,
|
||||
// but currently we send null to indicate that we unpin the
|
||||
// last pinned.
|
||||
else
|
||||
room.pinParticipant(null);
|
||||
});
|
||||
|
||||
APP.UI.addListener(
|
||||
|
|
|
@ -43,29 +43,25 @@
|
|||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
||||
#startMutedOptions {
|
||||
#startMutedOptions,
|
||||
#followMeOptions {
|
||||
padding-left: 10%;
|
||||
text-indent: -10%;
|
||||
|
||||
margin-top: 10px;
|
||||
|
||||
display: none; /* hide by default */
|
||||
|
||||
/* clearfix */
|
||||
overflow: auto;
|
||||
zoom: 1;
|
||||
}
|
||||
|
||||
#startAudioMuted {
|
||||
#startAudioMuted,
|
||||
#startVideoMuted,
|
||||
#followMeCheckBox {
|
||||
width: 13px !important;
|
||||
}
|
||||
|
||||
#startVideoMuted {
|
||||
width: 13px !important;
|
||||
}
|
||||
|
||||
.startMutedLabel {
|
||||
.startMutedLabel,
|
||||
.followMeLabel {
|
||||
width: 94%;
|
||||
float: left;
|
||||
cursor: pointer;
|
||||
|
|
|
@ -241,6 +241,12 @@
|
|||
<select id="selectMic"></select>
|
||||
</label>
|
||||
</div>
|
||||
<div id="followMeOptions">
|
||||
<label class = "followMeLabel">
|
||||
<input type="checkbox" id="followMeCheckBox">
|
||||
<span data-i18n="settings.followMe"></span>
|
||||
</label>
|
||||
</div>
|
||||
<a id="downloadlog" data-container="body" data-toggle="popover" data-placement="right" data-i18n="[data-content]downloadlogs" ><i class="fa fa-cloud-download"></i></a>
|
||||
</div>
|
||||
<div class="feedbackButton">
|
||||
|
|
|
@ -86,7 +86,8 @@
|
|||
"startAudioMuted": "start without audio",
|
||||
"startVideoMuted": "start without video",
|
||||
"selectCamera": "select camera",
|
||||
"selectMic": "select microphone"
|
||||
"selectMic": "select microphone",
|
||||
"followMe": "Enable follow me"
|
||||
},
|
||||
"videothumbnail":
|
||||
{
|
||||
|
|
|
@ -0,0 +1,328 @@
|
|||
/*
|
||||
* Copyright @ 2015 Atlassian Pty Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import UIEvents from '../service/UI/UIEvents';
|
||||
import VideoLayout from './UI/videolayout/VideoLayout';
|
||||
import FilmStrip from './UI/videolayout/FilmStrip';
|
||||
|
||||
/**
|
||||
* The (name of the) command which transports the state (represented by
|
||||
* {State} for the local state at the time of this writing) of a {FollowMe}
|
||||
* (instance) between participants.
|
||||
*/
|
||||
const _COMMAND = "follow-me";
|
||||
|
||||
/**
|
||||
* Represents the set of {FollowMe}-related states (properties and their
|
||||
* respective values) which are to be followed by a participant. {FollowMe}
|
||||
* will send {_COMMAND} whenever a property of {State} changes (if the local
|
||||
* participant is in her right to issue such a command, of course).
|
||||
*/
|
||||
class State {
|
||||
/**
|
||||
* Initializes a new {State} instance.
|
||||
*
|
||||
* @param propertyChangeCallback {Function} which is to be called when a
|
||||
* property of the new instance has its value changed from an old value
|
||||
* into a (different) new value. The function is supplied with the name of
|
||||
* the property, the old value of the property before the change, and the
|
||||
* new value of the property after the change.
|
||||
*/
|
||||
constructor (propertyChangeCallback) {
|
||||
this._propertyChangeCallback = propertyChangeCallback;
|
||||
}
|
||||
|
||||
get filmStripVisible () { return this._filmStripVisible; }
|
||||
|
||||
set filmStripVisible (b) {
|
||||
var oldValue = this._filmStripVisible;
|
||||
if (oldValue !== b) {
|
||||
this._filmStripVisible = b;
|
||||
this._firePropertyChange('filmStripVisible', oldValue, b);
|
||||
}
|
||||
}
|
||||
|
||||
get nextOnStage() { return this._nextOnStage; }
|
||||
|
||||
set nextOnStage(id) {
|
||||
var oldValue = this._nextOnStage;
|
||||
if (oldValue !== id) {
|
||||
this._nextOnStage = id;
|
||||
this._firePropertyChange('nextOnStage', oldValue, id);
|
||||
}
|
||||
}
|
||||
|
||||
get sharedDocumentVisible () { return this._sharedDocumentVisible; }
|
||||
|
||||
set sharedDocumentVisible (b) {
|
||||
var oldValue = this._sharedDocumentVisible;
|
||||
if (oldValue !== b) {
|
||||
this._sharedDocumentVisible = b;
|
||||
this._firePropertyChange('sharedDocumentVisible', oldValue, b);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes {_propertyChangeCallback} to notify it that {property} had its
|
||||
* value changed from {oldValue} to {newValue}.
|
||||
*
|
||||
* @param property the name of the property which had its value changed
|
||||
* from {oldValue} to {newValue}
|
||||
* @param oldValue the value of {property} before the change
|
||||
* @param newValue the value of {property} after the change
|
||||
*/
|
||||
_firePropertyChange (property, oldValue, newValue) {
|
||||
var propertyChangeCallback = this._propertyChangeCallback;
|
||||
if (propertyChangeCallback)
|
||||
propertyChangeCallback(property, oldValue, newValue);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the "Follow Me" feature which enables a moderator to
|
||||
* (partially) control the user experience/interface (e.g. film strip
|
||||
* visibility) of (other) non-moderator particiapnts.
|
||||
*
|
||||
* @author Lyubomir Marinov
|
||||
*/
|
||||
class FollowMe {
|
||||
/**
|
||||
* Initializes a new {FollowMe} instance.
|
||||
*
|
||||
* @param conference the {conference} which is to transport
|
||||
* {FollowMe}-related information between participants
|
||||
* @param UI the {UI} which is the source (model/state) to be sent to
|
||||
* remote participants if the local participant is the moderator or the
|
||||
* destination (model/state) to receive from the remote moderator if the
|
||||
* local participant is not the moderator
|
||||
*/
|
||||
constructor (conference, UI) {
|
||||
this._conference = conference;
|
||||
this._UI = UI;
|
||||
|
||||
// The states of the local participant which are to be followed (by the
|
||||
// remote participants when the local participant is in her right to
|
||||
// issue such commands).
|
||||
this._local = new State(this._localPropertyChange.bind(this));
|
||||
|
||||
// Listen to "Follow Me" commands. I'm not sure whether a moderator can
|
||||
// (in lib-jitsi-meet and/or Meet) become a non-moderator. If that's
|
||||
// possible, then it may be easiest to always listen to commands. The
|
||||
// listener will validate received commands before acting on them.
|
||||
conference.commands.addCommandListener(
|
||||
_COMMAND,
|
||||
this._onFollowMeCommand.bind(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds listeners for the UI states of the local participant which are
|
||||
* to be followed (by the remote participants). A non-moderator (very
|
||||
* likely) can become a moderator so it may be easiest to always track
|
||||
* the states of interest.
|
||||
* @private
|
||||
*/
|
||||
_addFollowMeListeners () {
|
||||
this.filmStripEventHandler = this._filmStripToggled.bind(this);
|
||||
this._UI.addListener(UIEvents.TOGGLED_FILM_STRIP,
|
||||
this.filmStripEventHandler);
|
||||
|
||||
var self = this;
|
||||
this.pinnedEndpointEventHandler = function (smallVideo, isPinned) {
|
||||
self._nextOnStage(smallVideo, isPinned);
|
||||
};
|
||||
this._UI.addListener(UIEvents.PINNED_ENDPOINT,
|
||||
this.pinnedEndpointEventHandler);
|
||||
|
||||
this.sharedDocEventHandler = this._sharedDocumentToggled.bind(this);
|
||||
this._UI.addListener( UIEvents.TOGGLED_SHARED_DOCUMENT,
|
||||
this.sharedDocEventHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all follow me listeners.
|
||||
* @private
|
||||
*/
|
||||
_removeFollowMeListeners () {
|
||||
this._UI.removeListener(UIEvents.TOGGLED_FILM_STRIP,
|
||||
this.filmStripEventHandler);
|
||||
this._UI.removeListener(UIEvents.TOGGLED_SHARED_DOCUMENT,
|
||||
this.sharedDocEventHandler);
|
||||
this._UI.removeListener(UIEvents.PINNED_ENDPOINT,
|
||||
this.pinnedEndpointEventHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables or disabled the follow me functionality
|
||||
*
|
||||
* @param enable {true} to enable the follow me functionality, {false} -
|
||||
* to disable it
|
||||
*/
|
||||
enableFollowMe (enable) {
|
||||
this.isEnabled = enable;
|
||||
if (this.isEnabled)
|
||||
this._addFollowMeListeners();
|
||||
else
|
||||
this._removeFollowMeListeners();
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies this instance that the (visibility of the) film strip was
|
||||
* toggled (in the user interface of the local participant).
|
||||
*
|
||||
* @param filmStripVisible {Boolean} {true} if the film strip was shown (as
|
||||
* a result of the toggle) or {false} if the film strip was hidden
|
||||
*/
|
||||
_filmStripToggled (filmStripVisible) {
|
||||
this._local.filmStripVisible = filmStripVisible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies this instance that the (visibility of the) shared document was
|
||||
* toggled (in the user interface of the local participant).
|
||||
*
|
||||
* @param sharedDocumentVisible {Boolean} {true} if the shared document was
|
||||
* shown (as a result of the toggle) or {false} if it was hidden
|
||||
*/
|
||||
_sharedDocumentToggled (sharedDocumentVisible) {
|
||||
this._local.sharedDocumentVisible = sharedDocumentVisible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the nextOnPage property value.
|
||||
*
|
||||
* @param smallVideo the {SmallVideo} that was pinned or unpinned
|
||||
* @param isPinned indicates if the given {SmallVideo} was pinned or
|
||||
* unpinned
|
||||
* @private
|
||||
*/
|
||||
_nextOnStage (smallVideo, isPinned) {
|
||||
if (!this._conference.isModerator)
|
||||
return;
|
||||
|
||||
var nextOnStage = null;
|
||||
if(isPinned)
|
||||
nextOnStage = smallVideo.getId();
|
||||
|
||||
this._local.nextOnStage = nextOnStage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the follow-me command, when a local property change occurs.
|
||||
*
|
||||
* @param property the property name
|
||||
* @param oldValue the old value
|
||||
* @param newValue the new value
|
||||
* @private
|
||||
*/
|
||||
_localPropertyChange (property, oldValue, newValue) {
|
||||
// Only a moderator is allowed to send commands.
|
||||
var conference = this._conference;
|
||||
if (!conference.isModerator)
|
||||
return;
|
||||
|
||||
var commands = conference.commands;
|
||||
// XXX The "Follow Me" command represents a snapshot of all states
|
||||
// which are to be followed so don't forget to removeCommand before
|
||||
// sendCommand!
|
||||
commands.removeCommand(_COMMAND);
|
||||
var self = this;
|
||||
commands.sendCommandOnce(
|
||||
_COMMAND,
|
||||
{
|
||||
attributes: {
|
||||
filmStripVisible: self._local.filmStripVisible,
|
||||
nextOnStage: self._local.nextOnStage,
|
||||
sharedDocumentVisible: self._local.sharedDocumentVisible
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies this instance about a &qout;Follow Me&qout; command (delivered
|
||||
* by the Command(s) API of {this._conference}).
|
||||
*
|
||||
* @param attributes the attributes {Object} carried by the command
|
||||
* @param id the identifier of the participant who issued the command. A
|
||||
* notable idiosyncrasy of the Command(s) API to be mindful of here is that
|
||||
* the command may be issued by the local participant.
|
||||
*/
|
||||
_onFollowMeCommand ({ attributes }, id) {
|
||||
// We require to know who issued the command because (1) only a
|
||||
// moderator is allowed to send commands and (2) a command MUST be
|
||||
// issued by a defined commander.
|
||||
if (typeof id === 'undefined')
|
||||
return;
|
||||
// The Command(s) API will send us our own commands and we don't want
|
||||
// to act upon them.
|
||||
if (this._conference.isLocalId(id))
|
||||
return;
|
||||
|
||||
// TODO Don't obey commands issued by non-moderators.
|
||||
|
||||
// Applies the received/remote command to the user experience/interface
|
||||
// of the local participant.
|
||||
this._onFilmStripVisible(attributes.filmStripVisible);
|
||||
this._onNextOnStage(attributes.nextOnStage);
|
||||
this._onSharedDocumentVisible(attributes.sharedDocumentVisible);
|
||||
}
|
||||
|
||||
_onFilmStripVisible(filmStripVisible) {
|
||||
if (typeof filmStripVisible !== 'undefined') {
|
||||
// XXX The Command(s) API doesn't preserve the types (of
|
||||
// attributes, at least) at the time of this writing so take into
|
||||
// account that what originated as a Boolean may be a String on
|
||||
// receipt.
|
||||
filmStripVisible = (filmStripVisible == 'true');
|
||||
|
||||
// FIXME The UI (module) very likely doesn't (want to) expose its
|
||||
// eventEmitter as a public field. I'm not sure at the time of this
|
||||
// writing whether calling UI.toggleFilmStrip() is acceptable (from
|
||||
// a design standpoint) either.
|
||||
if (filmStripVisible !== FilmStrip.isFilmStripVisible())
|
||||
this._UI.eventEmitter.emit(
|
||||
UIEvents.TOGGLE_FILM_STRIP,
|
||||
filmStripVisible);
|
||||
}
|
||||
}
|
||||
|
||||
_onNextOnStage(id) {
|
||||
|
||||
var clickId = null;
|
||||
if(typeof id !== 'undefined' && !VideoLayout.isPinned(id))
|
||||
clickId = id;
|
||||
else if (typeof id == 'undefined')
|
||||
clickId = VideoLayout.getPinnedId();
|
||||
|
||||
if (clickId !== null)
|
||||
VideoLayout.handleVideoThumbClicked(clickId);
|
||||
}
|
||||
|
||||
_onSharedDocumentVisible(sharedDocumentVisible) {
|
||||
if (typeof sharedDocumentVisible !== 'undefined') {
|
||||
// XXX The Command(s) API doesn't preserve the types (of
|
||||
// attributes, at least) at the time of this writing so take into
|
||||
// account that what originated as a Boolean may be a String on
|
||||
// receipt.
|
||||
sharedDocumentVisible = (sharedDocumentVisible == 'true');
|
||||
|
||||
if (sharedDocumentVisible
|
||||
!== this._UI.getSharedDocumentManager().isVisible())
|
||||
this._UI.getSharedDocumentManager().toggleEtherpad();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default FollowMe;
|
|
@ -27,12 +27,16 @@ var messageHandler = UI.messageHandler;
|
|||
var JitsiPopover = require("./util/JitsiPopover");
|
||||
var Feedback = require("./Feedback");
|
||||
|
||||
import FollowMe from "../FollowMe";
|
||||
|
||||
var eventEmitter = new EventEmitter();
|
||||
UI.eventEmitter = eventEmitter;
|
||||
|
||||
let etherpadManager;
|
||||
let sharedVideoManager;
|
||||
|
||||
let followMeHandler;
|
||||
|
||||
/**
|
||||
* Prompt user for nickname.
|
||||
*/
|
||||
|
@ -245,6 +249,12 @@ UI.initConference = function () {
|
|||
if(!interfaceConfig.filmStripOnly) {
|
||||
Feedback.init();
|
||||
}
|
||||
|
||||
// FollowMe attempts to copy certain aspects of the moderator's UI into the
|
||||
// other participants' UI. Consequently, it needs (1) read and write access
|
||||
// to the UI (depending on the moderator role of the local participant) and
|
||||
// (2) APP.conference as means of communication between the participants.
|
||||
followMeHandler = new FollowMe(APP.conference, UI);
|
||||
};
|
||||
|
||||
UI.mucJoined = function () {
|
||||
|
@ -282,6 +292,11 @@ function registerListeners() {
|
|||
UI.toggleFilmStrip();
|
||||
VideoLayout.resizeVideoArea(PanelToggler.isVisible(), true, false);
|
||||
});
|
||||
|
||||
UI.addListener(UIEvents.FOLLOW_ME_ENABLED, function (isEnabled) {
|
||||
if (followMeHandler)
|
||||
followMeHandler.enableFollowMe(isEnabled);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -331,7 +346,7 @@ UI.start = function () {
|
|||
registerListeners();
|
||||
|
||||
BottomToolbar.init();
|
||||
FilmStrip.init();
|
||||
FilmStrip.init(eventEmitter);
|
||||
|
||||
VideoLayout.init(eventEmitter);
|
||||
if (!interfaceConfig.filmStripOnly) {
|
||||
|
@ -470,10 +485,19 @@ UI.initEtherpad = function (name) {
|
|||
return;
|
||||
}
|
||||
console.log('Etherpad is enabled');
|
||||
etherpadManager = new EtherpadManager(config.etherpad_base, name);
|
||||
etherpadManager
|
||||
= new EtherpadManager(config.etherpad_base, name, eventEmitter);
|
||||
Toolbar.showEtherpadButton();
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the shared document manager object.
|
||||
* @return {EtherpadManager} the shared document manager object
|
||||
*/
|
||||
UI.getSharedDocumentManager = function () {
|
||||
return etherpadManager;
|
||||
};
|
||||
|
||||
/**
|
||||
* Show user on UI.
|
||||
* @param {string} id user id
|
||||
|
@ -541,6 +565,7 @@ UI.updateLocalRole = function (isModerator) {
|
|||
Toolbar.showRecordingButton(isModerator);
|
||||
Toolbar.showSharedVideoButton(isModerator);
|
||||
SettingsMenu.showStartMutedOptions(isModerator);
|
||||
SettingsMenu.showFollowMeOptions(isModerator);
|
||||
|
||||
if (isModerator) {
|
||||
messageHandler.notify(null, "notify.me", 'connected', "notify.moderator");
|
||||
|
@ -589,7 +614,8 @@ UI.toggleSmileys = function () {
|
|||
* Toggles film strip.
|
||||
*/
|
||||
UI.toggleFilmStrip = function () {
|
||||
FilmStrip.toggleFilmStrip();
|
||||
var self = FilmStrip;
|
||||
self.toggleFilmStrip.apply(self, arguments);
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -677,10 +703,26 @@ UI.setVideoMuted = function (id, muted) {
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a listener that would be notified on the given type of event.
|
||||
*
|
||||
* @param type the type of the event we're listening for
|
||||
* @param listener a function that would be called when notified
|
||||
*/
|
||||
UI.addListener = function (type, listener) {
|
||||
eventEmitter.on(type, listener);
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes the given listener for the given type of event.
|
||||
*
|
||||
* @param type the type of the event we're listening for
|
||||
* @param listener the listener we want to remove
|
||||
*/
|
||||
UI.removeListener = function (type, listener) {
|
||||
eventEmitter.removeListener(type, listener);
|
||||
};
|
||||
|
||||
UI.clickOnVideo = function (videoNumber) {
|
||||
var remoteVideos = $(".videocontainer:not(#mixedstream)");
|
||||
if (remoteVideos.length > videoNumber) {
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
import VideoLayout from "../videolayout/VideoLayout";
|
||||
import LargeContainer from '../videolayout/LargeContainer';
|
||||
import UIUtil from "../util/UIUtil";
|
||||
import UIEvents from "../../../service/UI/UIEvents";
|
||||
import SidePanelToggler from "../side_pannels/SidePanelToggler";
|
||||
import FilmStrip from '../videolayout/FilmStrip';
|
||||
|
||||
|
@ -58,6 +59,7 @@ const ETHERPAD_CONTAINER_TYPE = "etherpad";
|
|||
* Container for Etherpad iframe.
|
||||
*/
|
||||
class Etherpad extends LargeContainer {
|
||||
|
||||
constructor (domain, name) {
|
||||
super();
|
||||
|
||||
|
@ -149,13 +151,14 @@ class Etherpad extends LargeContainer {
|
|||
* Manager of the Etherpad frame.
|
||||
*/
|
||||
export default class EtherpadManager {
|
||||
constructor (domain, name) {
|
||||
constructor (domain, name, eventEmitter) {
|
||||
if (!domain || !name) {
|
||||
throw new Error("missing domain or name");
|
||||
}
|
||||
|
||||
this.domain = domain;
|
||||
this.name = name;
|
||||
this.eventEmitter = eventEmitter;
|
||||
this.etherpad = null;
|
||||
}
|
||||
|
||||
|
@ -163,6 +166,10 @@ export default class EtherpadManager {
|
|||
return !!this.etherpad;
|
||||
}
|
||||
|
||||
isVisible() {
|
||||
return VideoLayout.isLargeContainerTypeVisible(ETHERPAD_CONTAINER_TYPE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new Etherpad frame.
|
||||
*/
|
||||
|
@ -183,11 +190,12 @@ export default class EtherpadManager {
|
|||
this.openEtherpad();
|
||||
}
|
||||
|
||||
let isVisible = VideoLayout.isLargeContainerTypeVisible(
|
||||
ETHERPAD_CONTAINER_TYPE
|
||||
);
|
||||
let isVisible = this.isVisible();
|
||||
|
||||
VideoLayout.showLargeVideoContainer(
|
||||
ETHERPAD_CONTAINER_TYPE, !isVisible);
|
||||
|
||||
this.eventEmitter
|
||||
.emit(UIEvents.TOGGLED_SHARED_DOCUMENT, !isVisible);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -120,7 +120,7 @@ export default class SharedVideoManager {
|
|||
|
||||
VideoLayout.addLargeVideoContainer(
|
||||
SHARED_VIDEO_CONTAINER_TYPE, self.sharedVideo);
|
||||
VideoLayout.handleVideoThumbClicked(true, self.url);
|
||||
VideoLayout.handleVideoThumbClicked(self.url);
|
||||
|
||||
self.isSharedVideoShown = true;
|
||||
|
||||
|
@ -372,7 +372,7 @@ SharedVideoThumb.prototype.createContainer = function (spanId) {
|
|||
* The thumb click handler.
|
||||
*/
|
||||
SharedVideoThumb.prototype.videoClick = function () {
|
||||
VideoLayout.handleVideoThumbClicked(true, this.url);
|
||||
VideoLayout.handleVideoThumbClicked(this.url);
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -89,6 +89,14 @@ export default {
|
|||
);
|
||||
});
|
||||
|
||||
// FOLLOW ME
|
||||
$("#followMeOptions").change(function () {
|
||||
let isFollowMeEnabled = $("#followMeCheckBox").is(":checked");
|
||||
emitter.emit(
|
||||
UIEvents.FOLLOW_ME_ENABLED,
|
||||
isFollowMeEnabled
|
||||
);
|
||||
});
|
||||
|
||||
// LANGUAGES BOX
|
||||
let languagesBox = $("#languages_selectbox");
|
||||
|
@ -135,6 +143,19 @@ export default {
|
|||
$("#startVideoMuted").attr("checked", startVideoMuted);
|
||||
},
|
||||
|
||||
/**
|
||||
* Shows/hides the follow me options in the settings dialog.
|
||||
*
|
||||
* @param {boolean} show {true} to show those options, {false} to hide them
|
||||
*/
|
||||
showFollowMeOptions (show) {
|
||||
if (show) {
|
||||
$("#followMeOptions").css("display", "block");
|
||||
} else {
|
||||
$("#followMeOptions").css("display", "none");
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if settings menu is visible or not.
|
||||
* @returns {boolean}
|
||||
|
|
|
@ -1,16 +1,44 @@
|
|||
/* global $, APP, interfaceConfig, config*/
|
||||
|
||||
import UIEvents from "../../../service/UI/UIEvents";
|
||||
import UIUtil from "../util/UIUtil";
|
||||
|
||||
const thumbAspectRatio = 1 / 1;
|
||||
|
||||
const FilmStrip = {
|
||||
init () {
|
||||
/**
|
||||
*
|
||||
* @param eventEmitter the {EventEmitter} through which {FilmStrip} is to
|
||||
* emit/fire {UIEvents} (such as {UIEvents.TOGGLED_FILM_STRIP}).
|
||||
*/
|
||||
init (eventEmitter) {
|
||||
this.filmStrip = $('#remoteVideos');
|
||||
this.eventEmitter = eventEmitter;
|
||||
},
|
||||
|
||||
toggleFilmStrip () {
|
||||
/**
|
||||
* Toggles the visibility of the film strip.
|
||||
*
|
||||
* @param visible optional {Boolean} which specifies the desired visibility
|
||||
* of the film strip. If not specified, the visibility will be flipped
|
||||
* (i.e. toggled); otherwise, the visibility will be set to the specified
|
||||
* value.
|
||||
*/
|
||||
toggleFilmStrip (visible) {
|
||||
if (typeof visible === 'boolean'
|
||||
&& this.isFilmStripVisible() == visible) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.filmStrip.toggleClass("hidden");
|
||||
|
||||
// Emit/fire UIEvents.TOGGLED_FILM_STRIP.
|
||||
var eventEmitter = this.eventEmitter;
|
||||
if (eventEmitter) {
|
||||
eventEmitter.emit(
|
||||
UIEvents.TOGGLED_FILM_STRIP,
|
||||
this.isFilmStripVisible());
|
||||
}
|
||||
},
|
||||
|
||||
isFilmStripVisible () {
|
||||
|
|
|
@ -158,7 +158,7 @@ LocalVideo.prototype.changeVideo = function (stream) {
|
|||
if (event.stopPropagation) {
|
||||
event.stopPropagation();
|
||||
}
|
||||
this.VideoLayout.handleVideoThumbClicked(true, this.id);
|
||||
this.VideoLayout.handleVideoThumbClicked(this.id);
|
||||
};
|
||||
|
||||
let localVideoContainerSelector = $('#localVideoContainer');
|
||||
|
|
|
@ -220,7 +220,7 @@ RemoteVideo.prototype.addRemoteStreamElement = function (stream) {
|
|||
|
||||
// ignore click if it was done in popup menu
|
||||
if ($(source).parents('.popupmenu').length === 0) {
|
||||
this.VideoLayout.handleVideoThumbClicked(false, this.id);
|
||||
this.VideoLayout.handleVideoThumbClicked(this.id);
|
||||
}
|
||||
|
||||
// On IE we need to populate this handler on video <object>
|
||||
|
|
|
@ -20,6 +20,14 @@ function setVisibility(selector, show) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the identifier of this small video.
|
||||
*
|
||||
* @returns the identifier of this small video
|
||||
*/
|
||||
SmallVideo.prototype.getId = function () {
|
||||
return this.id;
|
||||
};
|
||||
|
||||
/* Indicates if this small video is currently visible.
|
||||
*
|
||||
|
|
|
@ -31,7 +31,7 @@ var eventEmitter = null;
|
|||
* Currently focused video jid
|
||||
* @type {String}
|
||||
*/
|
||||
var focusedVideoResourceJid = null;
|
||||
var pinnedId = null;
|
||||
|
||||
/**
|
||||
* On contact list item clicked.
|
||||
|
@ -49,7 +49,7 @@ function onContactClicked (id) {
|
|||
if (remoteVideo.hasVideoStarted()) {
|
||||
// We have a video src, great! Let's update the large video
|
||||
// now.
|
||||
VideoLayout.handleVideoThumbClicked(false, id);
|
||||
VideoLayout.handleVideoThumbClicked(id);
|
||||
} else {
|
||||
|
||||
// If we don't have a video src for jid, there's absolutely
|
||||
|
@ -63,7 +63,7 @@ function onContactClicked (id) {
|
|||
// picked up later by the lastN changed event handler.
|
||||
|
||||
lastNPickupId = id;
|
||||
eventEmitter.emit(UIEvents.PINNED_ENDPOINT, id);
|
||||
eventEmitter.emit(UIEvents.PINNED_ENDPOINT, remoteVideo, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -209,7 +209,7 @@ var VideoLayout = {
|
|||
|
||||
// We'll show user's avatar if he is the dominant speaker or if
|
||||
// his video thumbnail is pinned
|
||||
if (remoteVideos[id] && (id === focusedVideoResourceJid
|
||||
if (remoteVideos[id] && (id === pinnedId
|
||||
|| id === currentDominantSpeaker)) {
|
||||
newId = id;
|
||||
} else {
|
||||
|
@ -289,20 +289,33 @@ var VideoLayout = {
|
|||
return smallVideo ? smallVideo.getVideoType() : null;
|
||||
},
|
||||
|
||||
handleVideoThumbClicked (noPinnedEndpointChangedEvent,
|
||||
resourceJid) {
|
||||
if(focusedVideoResourceJid) {
|
||||
isPinned (id) {
|
||||
return (pinnedId) ? (id === pinnedId) : false;
|
||||
},
|
||||
|
||||
getPinnedId () {
|
||||
return pinnedId;
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles the click on a video thumbnail.
|
||||
*
|
||||
* @param id the identifier of the video thumbnail
|
||||
*/
|
||||
handleVideoThumbClicked (id) {
|
||||
if(pinnedId) {
|
||||
var oldSmallVideo
|
||||
= VideoLayout.getSmallVideo(focusedVideoResourceJid);
|
||||
= VideoLayout.getSmallVideo(pinnedId);
|
||||
if (oldSmallVideo && !interfaceConfig.filmStripOnly)
|
||||
oldSmallVideo.focus(false);
|
||||
}
|
||||
|
||||
var smallVideo = VideoLayout.getSmallVideo(resourceJid);
|
||||
// Unlock current focused.
|
||||
if (focusedVideoResourceJid === resourceJid)
|
||||
var smallVideo = VideoLayout.getSmallVideo(id);
|
||||
|
||||
// Unpin if currently pinned.
|
||||
if (pinnedId === id)
|
||||
{
|
||||
focusedVideoResourceJid = null;
|
||||
pinnedId = null;
|
||||
// Enable the currently set dominant speaker.
|
||||
if (currentDominantSpeaker) {
|
||||
if(smallVideo && smallVideo.hasVideo()) {
|
||||
|
@ -310,26 +323,23 @@ var VideoLayout = {
|
|||
}
|
||||
}
|
||||
|
||||
if (!noPinnedEndpointChangedEvent) {
|
||||
eventEmitter.emit(UIEvents.PINNED_ENDPOINT);
|
||||
}
|
||||
eventEmitter.emit(UIEvents.PINNED_ENDPOINT, smallVideo, false);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Lock new video
|
||||
focusedVideoResourceJid = resourceJid;
|
||||
pinnedId = id;
|
||||
|
||||
// Update focused/pinned interface.
|
||||
if (resourceJid) {
|
||||
if (id) {
|
||||
if (smallVideo && !interfaceConfig.filmStripOnly)
|
||||
smallVideo.focus(true);
|
||||
|
||||
if (!noPinnedEndpointChangedEvent) {
|
||||
eventEmitter.emit(UIEvents.PINNED_ENDPOINT, resourceJid);
|
||||
}
|
||||
eventEmitter.emit(UIEvents.PINNED_ENDPOINT, smallVideo, true);
|
||||
}
|
||||
|
||||
this.updateLargeVideo(resourceJid);
|
||||
this.updateLargeVideo(id);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -372,10 +382,10 @@ var VideoLayout = {
|
|||
// Update the large video to the last added video only if there's no
|
||||
// current dominant, focused speaker or update it to
|
||||
// the current dominant speaker.
|
||||
if ((!focusedVideoResourceJid &&
|
||||
if ((!pinnedId &&
|
||||
!currentDominantSpeaker &&
|
||||
this.isLargeContainerTypeVisible(VIDEO_CONTAINER_TYPE)) ||
|
||||
focusedVideoResourceJid === resourceJid ||
|
||||
pinnedId === resourceJid ||
|
||||
(resourceJid &&
|
||||
currentDominantSpeaker === resourceJid)) {
|
||||
this.updateLargeVideo(resourceJid, true);
|
||||
|
@ -531,7 +541,7 @@ 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
|
||||
if (!pinnedId
|
||||
&& remoteVideo.hasVideoStarted()
|
||||
&& !this.getCurrentlyOnLargeContainer().stayOnStage()) {
|
||||
this.updateLargeVideo(id);
|
||||
|
@ -650,11 +660,7 @@ var VideoLayout = {
|
|||
// Clean up the lastN pickup id.
|
||||
lastNPickupId = null;
|
||||
|
||||
// Don't fire the events again, they've already
|
||||
// been fired in the contact list click handler.
|
||||
VideoLayout.handleVideoThumbClicked(
|
||||
false,
|
||||
resourceJid);
|
||||
VideoLayout.handleVideoThumbClicked(resourceJid);
|
||||
|
||||
updateLargeVideo = false;
|
||||
}
|
||||
|
@ -741,9 +747,9 @@ var VideoLayout = {
|
|||
|
||||
removeParticipantContainer (id) {
|
||||
// Unlock large video
|
||||
if (focusedVideoResourceJid === id) {
|
||||
if (pinnedId === id) {
|
||||
console.info("Focused video owner has left the conference");
|
||||
focusedVideoResourceJid = null;
|
||||
pinnedId = null;
|
||||
}
|
||||
|
||||
if (currentDominantSpeaker === id) {
|
||||
|
|
|
@ -37,14 +37,39 @@ export default {
|
|||
TOGGLE_CHAT: "UI.toggle_chat",
|
||||
TOGGLE_SETTINGS: "UI.toggle_settings",
|
||||
TOGGLE_CONTACT_LIST: "UI.toggle_contact_list",
|
||||
/**
|
||||
* Notifies that a command to toggle the film strip has been issued. The
|
||||
* event may optionally specify a {Boolean} (primitive) value to assign to
|
||||
* the visibility of the film strip (i.e. the event may act as a setter).
|
||||
* The very toggling of the film strip may or may not occurred at the time
|
||||
* of the receipt of the event depending on the position of the receiving
|
||||
* event listener in relation to the event listener which carries out the
|
||||
* command to toggle the film strip.
|
||||
*
|
||||
* @see {TOGGLED_FILM_STRIP}
|
||||
*/
|
||||
TOGGLE_FILM_STRIP: "UI.toggle_film_strip",
|
||||
/**
|
||||
* Notifies that the film strip was (actually) toggled. The event supplies
|
||||
* a {Boolean} (primitive) value indicating the visibility of the film
|
||||
* strip after the toggling (at the time of the event emission).
|
||||
*
|
||||
* @see {TOGGLE_FILM_STRIP}
|
||||
*/
|
||||
TOGGLED_FILM_STRIP: "UI.toggled_film_strip",
|
||||
TOGGLE_SCREENSHARING: "UI.toggle_screensharing",
|
||||
TOGGLED_SHARED_DOCUMENT: "UI.toggled_shared_document",
|
||||
CONTACT_CLICKED: "UI.contact_clicked",
|
||||
HANGUP: "UI.hangup",
|
||||
LOGOUT: "UI.logout",
|
||||
RECORDING_TOGGLE: "UI.recording_toggle",
|
||||
SIP_DIAL: "UI.sip_dial",
|
||||
SUBEJCT_CHANGED: "UI.subject_changed",
|
||||
SUBJECT_CHANGED: "UI.subject_changed",
|
||||
VIDEO_DEVICE_CHANGED: "UI.video_device_changed",
|
||||
AUDIO_DEVICE_CHANGED: "UI.audio_device_changed"
|
||||
AUDIO_DEVICE_CHANGED: "UI.audio_device_changed",
|
||||
/**
|
||||
* Notifies interested listeners that the follow-me feature is enabled or
|
||||
* disabled.
|
||||
*/
|
||||
FOLLOW_ME_ENABLED: "UI.follow_me_enabled"
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue