Extends the follow-me feature by adding the possibility to follow the pinned participant, the shared video and the shared document. Adds the possibility to enable disable follow-me from the settings panel. Some other small fixes throughout the UI.
This commit is contained in:
parent
b1469186d1
commit
cc761700fe
|
@ -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
|
||||
|
@ -1030,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;
|
||||
|
|
|
@ -240,6 +240,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":
|
||||
{
|
||||
|
|
|
@ -15,13 +15,15 @@
|
|||
*/
|
||||
|
||||
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.
|
||||
*/
|
||||
/* private */ const _COMMAND = "follow-me";
|
||||
const _COMMAND = "follow-me";
|
||||
|
||||
/**
|
||||
* Represents the set of {FollowMe}-related states (properties and their
|
||||
|
@ -29,7 +31,7 @@ import UIEvents from '../service/UI/UIEvents';
|
|||
* will send {_COMMAND} whenever a property of {State} changes (if the local
|
||||
* participant is in her right to issue such a command, of course).
|
||||
*/
|
||||
/* private */ class State {
|
||||
class State {
|
||||
/**
|
||||
* Initializes a new {State} instance.
|
||||
*
|
||||
|
@ -39,13 +41,13 @@ import UIEvents from '../service/UI/UIEvents';
|
|||
* the property, the old value of the property before the change, and the
|
||||
* new value of the property after the change.
|
||||
*/
|
||||
/* public */ constructor (propertyChangeCallback) {
|
||||
/* private*/ this._propertyChangeCallback = propertyChangeCallback;
|
||||
constructor (propertyChangeCallback) {
|
||||
this._propertyChangeCallback = propertyChangeCallback;
|
||||
}
|
||||
|
||||
/* public */ get filmStripVisible () { return this._filmStripVisible; }
|
||||
get filmStripVisible () { return this._filmStripVisible; }
|
||||
|
||||
/* public */ set filmStripVisible (b) {
|
||||
set filmStripVisible (b) {
|
||||
var oldValue = this._filmStripVisible;
|
||||
if (oldValue !== b) {
|
||||
this._filmStripVisible = b;
|
||||
|
@ -53,6 +55,26 @@ import UIEvents from '../service/UI/UIEvents';
|
|||
}
|
||||
}
|
||||
|
||||
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}.
|
||||
|
@ -62,7 +84,7 @@ import UIEvents from '../service/UI/UIEvents';
|
|||
* @param oldValue the value of {property} before the change
|
||||
* @param newValue the value of {property} after the change
|
||||
*/
|
||||
/* private */ _firePropertyChange (property, oldValue, newValue) {
|
||||
_firePropertyChange (property, oldValue, newValue) {
|
||||
var propertyChangeCallback = this._propertyChangeCallback;
|
||||
if (propertyChangeCallback)
|
||||
propertyChangeCallback(property, oldValue, newValue);
|
||||
|
@ -76,7 +98,7 @@ import UIEvents from '../service/UI/UIEvents';
|
|||
*
|
||||
* @author Lyubomir Marinov
|
||||
*/
|
||||
/* public */ class FollowMe {
|
||||
class FollowMe {
|
||||
/**
|
||||
* Initializes a new {FollowMe} instance.
|
||||
*
|
||||
|
@ -87,15 +109,14 @@ import UIEvents from '../service/UI/UIEvents';
|
|||
* destination (model/state) to receive from the remote moderator if the
|
||||
* local participant is not the moderator
|
||||
*/
|
||||
/* public */ constructor (conference, UI) {
|
||||
/* private */ this._conference = conference;
|
||||
/* private */ this._UI = UI;
|
||||
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).
|
||||
/* private */ this._local
|
||||
= new State(this._localPropertyChange.bind(this));
|
||||
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
|
||||
|
@ -104,18 +125,57 @@ import UIEvents from '../service/UI/UIEvents';
|
|||
conference.commands.addCommandListener(
|
||||
_COMMAND,
|
||||
this._onFollowMeCommand.bind(this));
|
||||
// Listen to (user interface) 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.
|
||||
UI.addListener(
|
||||
UIEvents.TOGGLED_FILM_STRIP,
|
||||
this._filmStripToggled.bind(this));
|
||||
// TODO Listen to changes in the moderator role of the local
|
||||
// participant. When the local participant is granted the moderator
|
||||
// role, it may need to start sending "Follow Me" commands. Obviously,
|
||||
// this depends on how we decide to enable the feature at runtime as
|
||||
// well.
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -125,11 +185,49 @@ import UIEvents from '../service/UI/UIEvents';
|
|||
* @param filmStripVisible {Boolean} {true} if the film strip was shown (as
|
||||
* a result of the toggle) or {false} if the film strip was hidden
|
||||
*/
|
||||
/* private */ _filmStripToggled (filmStripVisible) {
|
||||
_filmStripToggled (filmStripVisible) {
|
||||
this._local.filmStripVisible = filmStripVisible;
|
||||
}
|
||||
|
||||
/* private */ _localPropertyChange (property, oldValue, newValue) {
|
||||
/**
|
||||
* 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)
|
||||
|
@ -141,12 +239,14 @@ import UIEvents from '../service/UI/UIEvents';
|
|||
// sendCommand!
|
||||
commands.removeCommand(_COMMAND);
|
||||
var self = this;
|
||||
commands.sendCommand(
|
||||
commands.sendCommandOnce(
|
||||
_COMMAND,
|
||||
{
|
||||
attributes: {
|
||||
filmStripVisible: self._local.filmStripVisible,
|
||||
},
|
||||
nextOnStage: self._local.nextOnStage,
|
||||
sharedDocumentVisible: self._local.sharedDocumentVisible
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -159,7 +259,7 @@ import UIEvents from '../service/UI/UIEvents';
|
|||
* notable idiosyncrasy of the Command(s) API to be mindful of here is that
|
||||
* the command may be issued by the local participant.
|
||||
*/
|
||||
/* private */ _onFollowMeCommand ({ attributes }, id) {
|
||||
_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.
|
||||
|
@ -169,28 +269,60 @@ import UIEvents from '../service/UI/UIEvents';
|
|||
// to act upon them.
|
||||
if (this._conference.isLocalId(id))
|
||||
return;
|
||||
|
||||
// TODO Don't obey commands issued by non-moderators.
|
||||
|
||||
// Apply the received/remote command to the user experience/interface
|
||||
// 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);
|
||||
}
|
||||
|
||||
// filmStripVisible
|
||||
var filmStripVisible = attributes.filmStripVisible;
|
||||
_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.
|
||||
this._UI.eventEmitter.emit(
|
||||
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;
|
||||
|
|
|
@ -35,6 +35,8 @@ UI.eventEmitter = eventEmitter;
|
|||
let etherpadManager;
|
||||
let sharedVideoManager;
|
||||
|
||||
let followMeHandler;
|
||||
|
||||
/**
|
||||
* Prompt user for nickname.
|
||||
*/
|
||||
|
@ -252,7 +254,7 @@ UI.initConference = function () {
|
|||
// 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.
|
||||
new FollowMe(APP.conference, UI);
|
||||
followMeHandler = new FollowMe(APP.conference, UI);
|
||||
};
|
||||
|
||||
UI.mucJoined = function () {
|
||||
|
@ -290,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);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -478,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
|
||||
|
@ -549,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");
|
||||
|
@ -686,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}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -56,14 +56,20 @@ export default {
|
|||
*
|
||||
* @see {TOGGLE_FILM_STRIP}
|
||||
*/
|
||||
TOGGLED_FILM_STRIP: "UI.toggled_fim_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