Implements an initial (demo) version of "Follow Me" for film strip visibility.
This commit is contained in:
parent
c35590dbda
commit
605a892f78
|
@ -0,0 +1,196 @@
|
||||||
|
/*
|
||||||
|
* 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';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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).
|
||||||
|
*/
|
||||||
|
/* private */ 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.
|
||||||
|
*/
|
||||||
|
/* public */ constructor (propertyChangeCallback) {
|
||||||
|
/* private*/ this._propertyChangeCallback = propertyChangeCallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* public */ get filmStripVisible () { return this._filmStripVisible; }
|
||||||
|
|
||||||
|
/* public */ set filmStripVisible (b) {
|
||||||
|
var oldValue = this._filmStripVisible;
|
||||||
|
if (oldValue !== b) {
|
||||||
|
this._filmStripVisible = b;
|
||||||
|
this._firePropertyChange('filmStripVisible', 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
|
||||||
|
*/
|
||||||
|
/* private */ _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
|
||||||
|
*/
|
||||||
|
/* public */ 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
|
||||||
|
*/
|
||||||
|
/* public */ constructor (conference, UI) {
|
||||||
|
/* private */ this._conference = conference;
|
||||||
|
/* private */ 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));
|
||||||
|
|
||||||
|
// 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));
|
||||||
|
// 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.
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
/* private */ _filmStripToggled (filmStripVisible) {
|
||||||
|
this._local.filmStripVisible = filmStripVisible;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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.sendCommand(
|
||||||
|
_COMMAND,
|
||||||
|
{
|
||||||
|
attributes: {
|
||||||
|
filmStripVisible: self._local.filmStripVisible,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
/* private */ _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.
|
||||||
|
|
||||||
|
// Apply the received/remote command to the user experience/interface
|
||||||
|
// of the local participant.
|
||||||
|
|
||||||
|
// filmStripVisible
|
||||||
|
var filmStripVisible = attributes.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(
|
||||||
|
UIEvents.TOGGLE_FILM_STRIP,
|
||||||
|
filmStripVisible);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FollowMe;
|
|
@ -27,6 +27,8 @@ var messageHandler = UI.messageHandler;
|
||||||
var JitsiPopover = require("./util/JitsiPopover");
|
var JitsiPopover = require("./util/JitsiPopover");
|
||||||
var Feedback = require("./Feedback");
|
var Feedback = require("./Feedback");
|
||||||
|
|
||||||
|
import FollowMe from "../FollowMe";
|
||||||
|
|
||||||
var eventEmitter = new EventEmitter();
|
var eventEmitter = new EventEmitter();
|
||||||
UI.eventEmitter = eventEmitter;
|
UI.eventEmitter = eventEmitter;
|
||||||
|
|
||||||
|
@ -246,6 +248,12 @@ UI.initConference = function () {
|
||||||
if(!interfaceConfig.filmStripOnly) {
|
if(!interfaceConfig.filmStripOnly) {
|
||||||
Feedback.init();
|
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.
|
||||||
|
new FollowMe(APP.conference, UI);
|
||||||
};
|
};
|
||||||
|
|
||||||
UI.mucJoined = function () {
|
UI.mucJoined = function () {
|
||||||
|
@ -326,7 +334,7 @@ UI.start = function () {
|
||||||
registerListeners();
|
registerListeners();
|
||||||
|
|
||||||
BottomToolbar.init();
|
BottomToolbar.init();
|
||||||
FilmStrip.init();
|
FilmStrip.init(eventEmitter);
|
||||||
|
|
||||||
VideoLayout.init(eventEmitter);
|
VideoLayout.init(eventEmitter);
|
||||||
if (!interfaceConfig.filmStripOnly) {
|
if (!interfaceConfig.filmStripOnly) {
|
||||||
|
|
|
@ -1,12 +1,19 @@
|
||||||
/* global $, APP, interfaceConfig, config*/
|
/* global $, APP, interfaceConfig, config*/
|
||||||
|
|
||||||
|
import UIEvents from "../../../service/UI/UIEvents";
|
||||||
import UIUtil from "../util/UIUtil";
|
import UIUtil from "../util/UIUtil";
|
||||||
|
|
||||||
const thumbAspectRatio = 16.0 / 9.0;
|
const thumbAspectRatio = 16.0 / 9.0;
|
||||||
|
|
||||||
const FilmStrip = {
|
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.filmStrip = $('#remoteVideos');
|
||||||
|
this.eventEmitter = eventEmitter;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -24,6 +31,14 @@ const FilmStrip = {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.filmStrip.toggleClass("hidden");
|
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 () {
|
isFilmStripVisible () {
|
||||||
|
|
|
@ -46,6 +46,14 @@ export default {
|
||||||
* @see {TOGGLED_FILM_STRIP}
|
* @see {TOGGLED_FILM_STRIP}
|
||||||
*/
|
*/
|
||||||
TOGGLE_FILM_STRIP: "UI.toggle_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_fim_strip",
|
||||||
TOGGLE_SCREENSHARING: "UI.toggle_screensharing",
|
TOGGLE_SCREENSHARING: "UI.toggle_screensharing",
|
||||||
CONTACT_CLICKED: "UI.contact_clicked",
|
CONTACT_CLICKED: "UI.contact_clicked",
|
||||||
HANGUP: "UI.hangup",
|
HANGUP: "UI.hangup",
|
||||||
|
|
Loading…
Reference in New Issue