feat(remotecontrol): Prevent multiple remote control sessions (#1875)
This commit is contained in:
parent
31dd3da2b6
commit
378a8d014e
|
@ -11,6 +11,8 @@ import mediaDeviceHelper from './modules/devices/mediaDeviceHelper';
|
|||
|
||||
import { reload, reportError } from './modules/util/helpers';
|
||||
|
||||
import * as RemoteControlEvents
|
||||
from './service/remotecontrol/RemoteControlEvents';
|
||||
import UIEvents from './service/UI/UIEvents';
|
||||
import UIUtil from './modules/UI/util/UIUtil';
|
||||
import * as JitsiMeetConferenceEvents from './ConferenceEvents';
|
||||
|
@ -1813,10 +1815,27 @@ export default {
|
|||
ConferenceEvents.LOCK_STATE_CHANGED,
|
||||
(...args) => APP.store.dispatch(lockStateChanged(room, ...args)));
|
||||
|
||||
APP.remoteControl.on(RemoteControlEvents.ACTIVE_CHANGED, isActive => {
|
||||
room.setLocalParticipantProperty(
|
||||
"remoteControlSessionStatus",
|
||||
isActive
|
||||
);
|
||||
APP.UI.setLocalRemoteControlActiveChanged();
|
||||
});
|
||||
|
||||
room.on(ConferenceEvents.PARTICIPANT_PROPERTY_CHANGED,
|
||||
(participant, name, oldValue, newValue) => {
|
||||
if (name === "raisedHand") {
|
||||
switch (name) {
|
||||
case 'raisedHand':
|
||||
APP.UI.setRaisedHandStatus(participant, newValue);
|
||||
break;
|
||||
case 'remoteControlSessionStatus':
|
||||
APP.UI.setRemoteControlActiveStatus(
|
||||
participant.getId(),
|
||||
newValue);
|
||||
break;
|
||||
default:
|
||||
// ignore
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -2361,6 +2380,7 @@ export default {
|
|||
APP.UI.setLocalRaisedHandStatus(raisedHand);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Log event to callstats and analytics.
|
||||
* @param {string} name the event name
|
||||
|
|
|
@ -1230,6 +1230,26 @@ UI.getRemoteVideosCount = () => VideoLayout.getRemoteVideosCount();
|
|||
UI.setRemoteThumbnailsVisibility
|
||||
= shouldHide => Filmstrip.setRemoteVideoVisibility(shouldHide);
|
||||
|
||||
/**
|
||||
* Sets the remote control active status for a remote participant.
|
||||
*
|
||||
* @param {string} participantID - The id of the remote participant.
|
||||
* @param {boolean} isActive - The new remote control active status.
|
||||
* @returns {void}
|
||||
*/
|
||||
UI.setRemoteControlActiveStatus = function(participantID, isActive) {
|
||||
VideoLayout.setRemoteControlActiveStatus(participantID, isActive);
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the remote control active status for the local participant.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
UI.setLocalRemoteControlActiveChanged = function() {
|
||||
VideoLayout.setLocalRemoteControlActiveChanged();
|
||||
};
|
||||
|
||||
const UIListeners = new Map([
|
||||
[
|
||||
UIEvents.ETHERPAD_CLICKED,
|
||||
|
|
|
@ -51,6 +51,7 @@ function RemoteVideo(user, VideoLayout, emitter) {
|
|||
this.flipX = false;
|
||||
this.isLocal = false;
|
||||
this.popupMenuIsHovered = false;
|
||||
this._isRemoteControlSessionActive = false;
|
||||
/**
|
||||
* The flag is set to <tt>true</tt> after the 'onplay' event has been
|
||||
* triggered on the current video element. It goes back to <tt>false</tt>
|
||||
|
@ -87,9 +88,7 @@ RemoteVideo.prototype.addRemoteVideoContainer = function() {
|
|||
|
||||
this.initBrowserSpecificProperties();
|
||||
|
||||
if (APP.conference.isModerator || this._supportsRemoteControl) {
|
||||
this.addRemoteVideoMenu();
|
||||
}
|
||||
this.addRemoteVideoMenu();
|
||||
|
||||
this.VideoLayout.resizeThumbnails(false, true);
|
||||
|
||||
|
@ -135,7 +134,9 @@ RemoteVideo.prototype._generatePopupContent = function () {
|
|||
let remoteControlState = null;
|
||||
let onRemoteControlToggle;
|
||||
|
||||
if (this._supportsRemoteControl) {
|
||||
if (this._supportsRemoteControl
|
||||
&& ((!APP.remoteControl.active && !this._isRemoteControlSessionActive)
|
||||
|| APP.remoteControl.controller.activeParticipant === this.id)) {
|
||||
if (controller.getRequestedParticipant() === this.id) {
|
||||
onRemoteControlToggle = () => {};
|
||||
remoteControlState = REMOTE_CONTROL_MENU_STATES.REQUESTING;
|
||||
|
@ -179,7 +180,18 @@ RemoteVideo.prototype._generatePopupContent = function () {
|
|||
};
|
||||
|
||||
RemoteVideo.prototype._onRemoteVideoMenuDisplay = function () {
|
||||
this.updateRemoteVideoMenu(this.isAudioMuted, true);
|
||||
this.updateRemoteVideoMenu();
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the remote control active status for the remote video.
|
||||
*
|
||||
* @param {boolean} isActive - The new remote control active status.
|
||||
* @returns {void}
|
||||
*/
|
||||
RemoteVideo.prototype.setRemoteControlActiveStatus = function(isActive) {
|
||||
this._isRemoteControlSessionActive = isActive;
|
||||
this.updateRemoteVideoMenu();
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -192,18 +204,7 @@ RemoteVideo.prototype.setRemoteControlSupport = function(isSupported = false) {
|
|||
return;
|
||||
}
|
||||
this._supportsRemoteControl = isSupported;
|
||||
if(!isSupported) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(!this.hasRemoteVideoMenu) {
|
||||
//create menu
|
||||
this.addRemoteVideoMenu();
|
||||
} else {
|
||||
//update the content
|
||||
this.updateRemoteVideoMenu(this.isAudioMuted, true);
|
||||
}
|
||||
|
||||
this.updateRemoteVideoMenu();
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -215,7 +216,7 @@ RemoteVideo.prototype._requestRemoteControlPermissions = function () {
|
|||
if(result === null) {
|
||||
return;
|
||||
}
|
||||
this.updateRemoteVideoMenu(this.isAudioMuted, true);
|
||||
this.updateRemoteVideoMenu();
|
||||
APP.UI.messageHandler.notify(
|
||||
"dialog.remoteControlTitle",
|
||||
(result === false) ? "dialog.remoteControlDeniedMessage"
|
||||
|
@ -232,7 +233,7 @@ RemoteVideo.prototype._requestRemoteControlPermissions = function () {
|
|||
}
|
||||
}, error => {
|
||||
logger.error(error);
|
||||
this.updateRemoteVideoMenu(this.isAudioMuted, true);
|
||||
this.updateRemoteVideoMenu();
|
||||
APP.UI.messageHandler.notify(
|
||||
"dialog.remoteControlTitle",
|
||||
"dialog.remoteControlErrorMessage",
|
||||
|
@ -240,7 +241,7 @@ RemoteVideo.prototype._requestRemoteControlPermissions = function () {
|
|||
|| interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME}
|
||||
);
|
||||
});
|
||||
this.updateRemoteVideoMenu(this.isAudioMuted, true);
|
||||
this.updateRemoteVideoMenu();
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -249,7 +250,7 @@ RemoteVideo.prototype._requestRemoteControlPermissions = function () {
|
|||
RemoteVideo.prototype._stopRemoteControl = function () {
|
||||
// send message about stopping
|
||||
APP.remoteControl.controller.stop();
|
||||
this.updateRemoteVideoMenu(this.isAudioMuted, true);
|
||||
this.updateRemoteVideoMenu();
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -286,9 +287,10 @@ RemoteVideo.prototype._setAudioVolume = function (newVal) {
|
|||
* Updates the remote video menu.
|
||||
*
|
||||
* @param isMuted the new muted state to update to
|
||||
* @param force to work even if popover is not visible
|
||||
*/
|
||||
RemoteVideo.prototype.updateRemoteVideoMenu = function (isMuted) {
|
||||
RemoteVideo.prototype.updateRemoteVideoMenu = function (
|
||||
isMuted = this.isAudioMuted
|
||||
) {
|
||||
this.isAudioMuted = isMuted;
|
||||
|
||||
this._generatePopupContent();
|
||||
|
@ -323,8 +325,6 @@ RemoteVideo.prototype._figureOutMutedWhileDisconnected = function() {
|
|||
* Adds the remote video menu element for the given <tt>id</tt> in the
|
||||
* given <tt>parentElement</tt>.
|
||||
*
|
||||
* @param id the id indicating the video for which we're adding a menu.
|
||||
* @param parentElement the parent element where this menu will be added
|
||||
*/
|
||||
RemoteVideo.prototype.addRemoteVideoMenu = function () {
|
||||
if (interfaceConfig.filmStripOnly) {
|
||||
|
@ -576,6 +576,11 @@ RemoteVideo.prototype.addRemoteStreamElement = function (stream) {
|
|||
|
||||
if (!isVideo) {
|
||||
this._audioStreamElement = streamElement;
|
||||
|
||||
// If the remote video menu was created before the audio stream was
|
||||
// attached we need to update the menu in order to show the volume
|
||||
// slider.
|
||||
this.updateRemoteVideoMenu();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -642,9 +642,7 @@ var VideoLayout = {
|
|||
return;
|
||||
|
||||
remoteVideo.showAudioIndicator(isMuted);
|
||||
if (APP.conference.isModerator) {
|
||||
remoteVideo.updateRemoteVideoMenu(isMuted);
|
||||
}
|
||||
remoteVideo.updateRemoteVideoMenu(isMuted);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -1165,6 +1163,27 @@ var VideoLayout = {
|
|||
*/
|
||||
getRemoteVideosCount() {
|
||||
return Object.keys(remoteVideos).length;
|
||||
},
|
||||
/**
|
||||
* Sets the remote control active status for a remote participant.
|
||||
*
|
||||
* @param {string} participantID - The id of the remote participant.
|
||||
* @param {boolean} isActive - The new remote control active status.
|
||||
* @returns {void}
|
||||
*/
|
||||
setRemoteControlActiveStatus(participantID, isActive) {
|
||||
remoteVideos[participantID].setRemoteControlActiveStatus(isActive);
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the remote control active status for the local participant.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
setLocalRemoteControlActiveChanged() {
|
||||
Object.values(remoteVideos).forEach(
|
||||
remoteVideo => remoteVideo.updateRemoteVideoMenu()
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -8,6 +8,8 @@ import {
|
|||
PERMISSIONS_ACTIONS,
|
||||
REMOTE_CONTROL_MESSAGE_NAME
|
||||
} from '../../service/remotecontrol/Constants';
|
||||
import * as RemoteControlEvents
|
||||
from '../../service/remotecontrol/RemoteControlEvents';
|
||||
import UIEvents from '../../service/UI/UIEvents';
|
||||
|
||||
import RemoteControlParticipant from './RemoteControlParticipant';
|
||||
|
@ -86,6 +88,15 @@ export default class Controller extends RemoteControlParticipant {
|
|||
= this._onLargeVideoIdChanged.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current active participant's id.
|
||||
*
|
||||
* @returns {string|null} - The id of the current active participant.
|
||||
*/
|
||||
get activeParticipant(): string | null {
|
||||
return this._requestedParticipant || this._controlledParticipant;
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests permissions from the remote control receiver side.
|
||||
*
|
||||
|
@ -100,7 +111,7 @@ export default class Controller extends RemoteControlParticipant {
|
|||
if (!this._enabled) {
|
||||
return Promise.reject(new Error('Remote control is disabled!'));
|
||||
}
|
||||
|
||||
this.emit(RemoteControlEvents.ACTIVE_CHANGED, true);
|
||||
this._area = eventCaptureArea;// $("#largeVideoWrapper")
|
||||
logger.log(`Requsting remote control permissions from: ${userId}`);
|
||||
|
||||
|
@ -125,16 +136,21 @@ export default class Controller extends RemoteControlParticipant {
|
|||
result = this._handleReply(participant, event);
|
||||
} catch (e) {
|
||||
clearRequest();
|
||||
this.emit(RemoteControlEvents.ACTIVE_CHANGED, false);
|
||||
reject(e);
|
||||
}
|
||||
if (result !== null) {
|
||||
clearRequest();
|
||||
if (result === false) {
|
||||
this.emit(RemoteControlEvents.ACTIVE_CHANGED, false);
|
||||
}
|
||||
resolve(result);
|
||||
}
|
||||
};
|
||||
onUserLeft = id => {
|
||||
if (id === this._requestedParticipant) {
|
||||
clearRequest();
|
||||
this.emit(RemoteControlEvents.ACTIVE_CHANGED, false);
|
||||
resolve(null);
|
||||
}
|
||||
};
|
||||
|
@ -316,6 +332,7 @@ export default class Controller extends RemoteControlParticipant {
|
|||
this.pause();
|
||||
this._controlledParticipant = null;
|
||||
this._area = undefined;
|
||||
this.emit(RemoteControlEvents.ACTIVE_CHANGED, false);
|
||||
APP.UI.messageHandler.notify(
|
||||
'dialog.remoteControlTitle',
|
||||
'dialog.remoteControlStopMessage'
|
||||
|
|
|
@ -13,6 +13,8 @@ import {
|
|||
REMOTE_CONTROL_MESSAGE_NAME,
|
||||
REQUESTS
|
||||
} from '../../service/remotecontrol/Constants';
|
||||
import * as RemoteControlEvents
|
||||
from '../../service/remotecontrol/RemoteControlEvents';
|
||||
import {
|
||||
Transport,
|
||||
PostMessageTransportBackend
|
||||
|
@ -132,6 +134,7 @@ export default class Receiver extends RemoteControlParticipant {
|
|||
name: REMOTE_CONTROL_MESSAGE_NAME,
|
||||
type: EVENTS.stop
|
||||
});
|
||||
this.emit(RemoteControlEvents.ACTIVE_CHANGED, false);
|
||||
if (!dontNotify) {
|
||||
APP.UI.messageHandler.notify(
|
||||
'dialog.remoteControlTitle',
|
||||
|
@ -177,6 +180,7 @@ export default class Receiver extends RemoteControlParticipant {
|
|||
&& message.action === PERMISSIONS_ACTIONS.request) {
|
||||
const userId = participant.getId();
|
||||
|
||||
this.emit(RemoteControlEvents.ACTIVE_CHANGED, true);
|
||||
APP.store.dispatch(
|
||||
openRemoteControlAuthorizationDialog(userId));
|
||||
} else if (this._controller === participant.getId()) {
|
||||
|
@ -200,6 +204,7 @@ export default class Receiver extends RemoteControlParticipant {
|
|||
* @returns {void}
|
||||
*/
|
||||
deny(userId: string) {
|
||||
this.emit(RemoteControlEvents.ACTIVE_CHANGED, false);
|
||||
this.sendRemoteControlEndpointMessage(userId, {
|
||||
type: EVENTS.permissions,
|
||||
action: PERMISSIONS_ACTIONS.deny
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
/* @flow */
|
||||
|
||||
import EventEmitter from 'events';
|
||||
import { getLogger } from 'jitsi-meet-logger';
|
||||
|
||||
import { DISCO_REMOTE_CONTROL_FEATURE }
|
||||
from '../../service/remotecontrol/Constants';
|
||||
import * as RemoteControlEvents
|
||||
from '../../service/remotecontrol/RemoteControlEvents';
|
||||
|
||||
import Controller from './Controller';
|
||||
import Receiver from './Receiver';
|
||||
|
@ -16,7 +19,8 @@ declare var config: Object;
|
|||
/**
|
||||
* Implements the remote control functionality.
|
||||
*/
|
||||
class RemoteControl {
|
||||
class RemoteControl extends EventEmitter {
|
||||
_active: boolean;
|
||||
_initialized: boolean;
|
||||
controller: Controller;
|
||||
receiver: Receiver;
|
||||
|
@ -25,8 +29,36 @@ class RemoteControl {
|
|||
* Constructs new instance. Creates controller and receiver properties.
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
this.controller = new Controller();
|
||||
this._active = false;
|
||||
this._initialized = false;
|
||||
|
||||
this.controller.on(RemoteControlEvents.ACTIVE_CHANGED, active => {
|
||||
this.active = active;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the remote control session active status.
|
||||
*
|
||||
* @param {boolean} isActive - True - if the controller or the receiver is
|
||||
* currently in remote control session and false otherwise.
|
||||
* @returns {void}
|
||||
*/
|
||||
set active(isActive: boolean) {
|
||||
this._active = isActive;
|
||||
this.emit(RemoteControlEvents.ACTIVE_CHANGED, isActive);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the remote control session active status.
|
||||
*
|
||||
* @returns {boolean} - True - if the controller or the receiver is
|
||||
* currently in remote control session and false otherwise.
|
||||
*/
|
||||
get active(): boolean {
|
||||
return this._active;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -45,6 +77,10 @@ class RemoteControl {
|
|||
this._initialized = true;
|
||||
this.controller.enable(true);
|
||||
this.receiver = new Receiver();
|
||||
|
||||
this.receiver.on(RemoteControlEvents.ACTIVE_CHANGED, active => {
|
||||
this.active = active;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/* @flow */
|
||||
|
||||
import EventEmitter from 'events';
|
||||
import { getLogger } from 'jitsi-meet-logger';
|
||||
|
||||
import {
|
||||
|
@ -13,13 +14,14 @@ declare var APP: Object;
|
|||
/**
|
||||
* Implements common logic for Receiver class and Controller class.
|
||||
*/
|
||||
export default class RemoteControlParticipant {
|
||||
export default class RemoteControlParticipant extends EventEmitter {
|
||||
_enabled: boolean;
|
||||
|
||||
/**
|
||||
* Creates new instance.
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
this._enabled = false;
|
||||
}
|
||||
|
||||
|
|
|
@ -99,9 +99,15 @@ class RemoteVideoMenuTriggerButton extends Component {
|
|||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const content = this._renderRemoteVideoMenu();
|
||||
|
||||
if (!content) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<AKInlineDialog
|
||||
content = { this._renderRemoteVideoMenu() }
|
||||
content = { content }
|
||||
isOpen = { this.state.showRemoteMenu }
|
||||
onClose = { this._onRemoteMenuClose }
|
||||
position = { interfaceConfig.VERTICAL_FILMSTRIP
|
||||
|
@ -162,32 +168,52 @@ class RemoteVideoMenuTriggerButton extends Component {
|
|||
participantID
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<RemoteVideoMenu id = { participantID }>
|
||||
{ isModerator
|
||||
? <MuteButton
|
||||
isAudioMuted = { isAudioMuted }
|
||||
onClick = { this._onRemoteMenuClose }
|
||||
participantID = { participantID } />
|
||||
: null }
|
||||
{ isModerator
|
||||
? <KickButton
|
||||
onClick = { this._onRemoteMenuClose }
|
||||
participantID = { participantID } />
|
||||
: null }
|
||||
{ remoteControlState
|
||||
? <RemoteControlButton
|
||||
onClick = { onRemoteControlToggle }
|
||||
participantID = { participantID }
|
||||
remoteControlState = { remoteControlState } />
|
||||
: null }
|
||||
{ onVolumeChange
|
||||
? <VolumeSlider
|
||||
initialValue = { initialVolumeValue }
|
||||
onChange = { onVolumeChange } />
|
||||
: null }
|
||||
</RemoteVideoMenu>
|
||||
);
|
||||
const buttons = [];
|
||||
|
||||
if (isModerator) {
|
||||
buttons.push(
|
||||
<MuteButton
|
||||
isAudioMuted = { isAudioMuted }
|
||||
key = 'mute'
|
||||
onClick = { this._onRemoteMenuClose }
|
||||
participantID = { participantID } />
|
||||
);
|
||||
buttons.push(
|
||||
<KickButton
|
||||
key = 'kick'
|
||||
onClick = { this._onRemoteMenuClose }
|
||||
participantID = { participantID } />
|
||||
);
|
||||
}
|
||||
|
||||
if (remoteControlState) {
|
||||
buttons.push(
|
||||
<RemoteControlButton
|
||||
key = 'remote-control'
|
||||
onClick = { onRemoteControlToggle }
|
||||
participantID = { participantID }
|
||||
remoteControlState = { remoteControlState } />
|
||||
);
|
||||
}
|
||||
|
||||
if (onVolumeChange && isModerator) {
|
||||
buttons.push(
|
||||
<VolumeSlider
|
||||
initialValue = { initialVolumeValue }
|
||||
key = 'volume-slider'
|
||||
onChange = { onVolumeChange } />
|
||||
);
|
||||
}
|
||||
|
||||
if (buttons.length > 0) {
|
||||
return (
|
||||
<RemoteVideoMenu id = { participantID }>
|
||||
{ buttons }
|
||||
</RemoteVideoMenu>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
/**
|
||||
* Events fired from the remote control module through the EventEmitter.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Notifies about remote control active session status changes.
|
||||
*/
|
||||
export const ACTIVE_CHANGED = 'active-changed';
|
Loading…
Reference in New Issue