feat(pinning): move web pinning logic into redux
- Re-use the native redux pinning implementation for web - Remove pinning logic from conference.js - To the native pinning add a check for sharedVideo so youtube videos do not send a pin event - Add shared videos as a participant to enable pinning and so they can eventually get added to the filmstrip - Emit UIEvents.PINNED_ENDPOINT from middleware
This commit is contained in:
parent
19d9b3f023
commit
f1f46e0af5
|
@ -83,8 +83,6 @@ let room;
|
||||||
let connection;
|
let connection;
|
||||||
let localAudio, localVideo;
|
let localAudio, localVideo;
|
||||||
|
|
||||||
import {VIDEO_CONTAINER_TYPE} from "./modules/UI/videolayout/VideoContainer";
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Logic to open a desktop picker put on the window global for
|
* Logic to open a desktop picker put on the window global for
|
||||||
* lib-jitsi-meet to detect and invoke
|
* lib-jitsi-meet to detect and invoke
|
||||||
|
@ -1777,37 +1775,9 @@ export default {
|
||||||
UIEvents.VIDEO_UNMUTING_WHILE_AUDIO_ONLY,
|
UIEvents.VIDEO_UNMUTING_WHILE_AUDIO_ONLY,
|
||||||
() => this._displayAudioOnlyTooltip('videoMute'));
|
() => this._displayAudioOnlyTooltip('videoMute'));
|
||||||
|
|
||||||
APP.UI.addListener(UIEvents.PINNED_ENDPOINT,
|
APP.UI.addListener(
|
||||||
(smallVideo, isPinned) => {
|
UIEvents.PINNED_ENDPOINT,
|
||||||
let smallVideoId = smallVideo.getId();
|
updateRemoteThumbnailsVisibility);
|
||||||
let isLocal = APP.conference.isLocalId(smallVideoId);
|
|
||||||
|
|
||||||
let eventName
|
|
||||||
= (isPinned ? "pinned" : "unpinned") + "." +
|
|
||||||
(isLocal ? "local" : "remote");
|
|
||||||
let participantCount = room.getParticipantCount();
|
|
||||||
JitsiMeetJS.analytics.sendEvent(
|
|
||||||
eventName,
|
|
||||||
{ value: participantCount });
|
|
||||||
|
|
||||||
// FIXME why VIDEO_CONTAINER_TYPE instead of checking if
|
|
||||||
// the participant is on the large video ?
|
|
||||||
if (smallVideo.getVideoType() === VIDEO_CONTAINER_TYPE
|
|
||||||
&& !isLocal) {
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
try {
|
|
||||||
room.pinParticipant(isPinned ? smallVideoId : null);
|
|
||||||
} catch (e) {
|
|
||||||
reportError(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
updateRemoteThumbnailsVisibility();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
room.on(ConferenceEvents.CONNECTION_INTERRUPTED, () => {
|
room.on(ConferenceEvents.CONNECTION_INTERRUPTED, () => {
|
||||||
|
|
|
@ -153,7 +153,7 @@ class FollowMe {
|
||||||
smallVideo = VideoLayout.getSmallVideo(pinnedId);
|
smallVideo = VideoLayout.getSmallVideo(pinnedId);
|
||||||
}
|
}
|
||||||
|
|
||||||
this._nextOnStage(smallVideo, isPinned);
|
this._nextOnStage(smallVideo.getId(), isPinned);
|
||||||
|
|
||||||
// check whether shared document is enabled/initialized
|
// check whether shared document is enabled/initialized
|
||||||
if(this._UI.getSharedDocumentManager())
|
if(this._UI.getSharedDocumentManager())
|
||||||
|
@ -174,8 +174,8 @@ class FollowMe {
|
||||||
this.filmstripEventHandler);
|
this.filmstripEventHandler);
|
||||||
|
|
||||||
var self = this;
|
var self = this;
|
||||||
this.pinnedEndpointEventHandler = function (smallVideo, isPinned) {
|
this.pinnedEndpointEventHandler = function (videoId, isPinned) {
|
||||||
self._nextOnStage(smallVideo, isPinned);
|
self._nextOnStage(videoId, isPinned);
|
||||||
};
|
};
|
||||||
this._UI.addListener(UIEvents.PINNED_ENDPOINT,
|
this._UI.addListener(UIEvents.PINNED_ENDPOINT,
|
||||||
this.pinnedEndpointEventHandler);
|
this.pinnedEndpointEventHandler);
|
||||||
|
@ -243,13 +243,13 @@ class FollowMe {
|
||||||
* unpinned
|
* unpinned
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
_nextOnStage (smallVideo, isPinned) {
|
_nextOnStage (videoId, isPinned) {
|
||||||
if (!this._conference.isModerator)
|
if (!this._conference.isModerator)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var nextOnStage = null;
|
var nextOnStage = null;
|
||||||
if(isPinned)
|
if(isPinned)
|
||||||
nextOnStage = smallVideo.getId();
|
nextOnStage = videoId;
|
||||||
|
|
||||||
this._local.nextOnStage = nextOnStage;
|
this._local.nextOnStage = nextOnStage;
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,10 @@ import LargeContainer from '../videolayout/LargeContainer';
|
||||||
import SmallVideo from '../videolayout/SmallVideo';
|
import SmallVideo from '../videolayout/SmallVideo';
|
||||||
import Filmstrip from '../videolayout/Filmstrip';
|
import Filmstrip from '../videolayout/Filmstrip';
|
||||||
|
|
||||||
|
import {
|
||||||
|
participantJoined,
|
||||||
|
participantLeft
|
||||||
|
} from '../../../react/features/base/participants';
|
||||||
import { dockToolbox, showToolbox } from '../../../react/features/toolbox';
|
import { dockToolbox, showToolbox } from '../../../react/features/toolbox';
|
||||||
|
|
||||||
export const SHARED_VIDEO_CONTAINER_TYPE = "sharedvideo";
|
export const SHARED_VIDEO_CONTAINER_TYPE = "sharedvideo";
|
||||||
|
@ -267,6 +271,13 @@ export default class SharedVideoManager {
|
||||||
|
|
||||||
VideoLayout.addLargeVideoContainer(
|
VideoLayout.addLargeVideoContainer(
|
||||||
SHARED_VIDEO_CONTAINER_TYPE, self.sharedVideo);
|
SHARED_VIDEO_CONTAINER_TYPE, self.sharedVideo);
|
||||||
|
|
||||||
|
APP.store.dispatch(participantJoined({
|
||||||
|
id: self.url,
|
||||||
|
isBot: true,
|
||||||
|
name: player.getVideoData().title
|
||||||
|
}));
|
||||||
|
|
||||||
VideoLayout.handleVideoThumbClicked(self.url);
|
VideoLayout.handleVideoThumbClicked(self.url);
|
||||||
|
|
||||||
// If we are sending the command and we are starting the player
|
// If we are sending the command and we are starting the player
|
||||||
|
@ -461,6 +472,8 @@ export default class SharedVideoManager {
|
||||||
UIEvents.UPDATE_SHARED_VIDEO, null, 'removed');
|
UIEvents.UPDATE_SHARED_VIDEO, null, 'removed');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
APP.store.dispatch(participantLeft(this.url));
|
||||||
|
|
||||||
this.url = null;
|
this.url = null;
|
||||||
this.isSharedVideoShown = false;
|
this.isSharedVideoShown = false;
|
||||||
this.initialAttributes = null;
|
this.initialAttributes = null;
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
/* global APP, $, interfaceConfig, JitsiMeetJS */
|
/* global APP, $, interfaceConfig, JitsiMeetJS */
|
||||||
const logger = require("jitsi-meet-logger").getLogger(__filename);
|
const logger = require("jitsi-meet-logger").getLogger(__filename);
|
||||||
|
|
||||||
|
import { pinParticipant } from '../../../react/features/base/participants';
|
||||||
|
|
||||||
import Filmstrip from "./Filmstrip";
|
import Filmstrip from "./Filmstrip";
|
||||||
import UIEvents from "../../../service/UI/UIEvents";
|
import UIEvents from "../../../service/UI/UIEvents";
|
||||||
import UIUtil from "../util/UIUtil";
|
import UIUtil from "../util/UIUtil";
|
||||||
|
@ -20,6 +22,8 @@ var currentDominantSpeaker = null;
|
||||||
|
|
||||||
var eventEmitter = null;
|
var eventEmitter = null;
|
||||||
|
|
||||||
|
// TODO Remove this private reference to pinnedId once other components
|
||||||
|
// interested in its updates are moved to react/redux.
|
||||||
/**
|
/**
|
||||||
* Currently focused video jid
|
* Currently focused video jid
|
||||||
* @type {String}
|
* @type {String}
|
||||||
|
@ -59,7 +63,7 @@ function onContactClicked (id) {
|
||||||
// let the bridge adjust its lastN set for myjid and store
|
// let the bridge adjust its lastN set for myjid and store
|
||||||
// the pinned user in the lastNPickupId variable to be
|
// the pinned user in the lastNPickupId variable to be
|
||||||
// picked up later by the lastN changed event handler.
|
// picked up later by the lastN changed event handler.
|
||||||
eventEmitter.emit(UIEvents.PINNED_ENDPOINT, remoteVideo, true);
|
APP.store.dispatch(pinParticipant(remoteVideo.id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -406,12 +410,6 @@ var VideoLayout = {
|
||||||
var oldSmallVideo = VideoLayout.getSmallVideo(pinnedId);
|
var oldSmallVideo = VideoLayout.getSmallVideo(pinnedId);
|
||||||
if (oldSmallVideo && !interfaceConfig.filmStripOnly) {
|
if (oldSmallVideo && !interfaceConfig.filmStripOnly) {
|
||||||
oldSmallVideo.focus(false);
|
oldSmallVideo.focus(false);
|
||||||
// as no pinned event will be sent for local video
|
|
||||||
// and we will unpin old one, lets signal it
|
|
||||||
// otherwise we will just send the new pinned one
|
|
||||||
if (smallVideo.isLocal)
|
|
||||||
eventEmitter.emit(
|
|
||||||
UIEvents.PINNED_ENDPOINT, oldSmallVideo, false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -419,6 +417,9 @@ var VideoLayout = {
|
||||||
if (pinnedId === id)
|
if (pinnedId === id)
|
||||||
{
|
{
|
||||||
pinnedId = null;
|
pinnedId = null;
|
||||||
|
|
||||||
|
APP.store.dispatch(pinParticipant(null));
|
||||||
|
|
||||||
// Enable the currently set dominant speaker.
|
// Enable the currently set dominant speaker.
|
||||||
if (currentDominantSpeaker) {
|
if (currentDominantSpeaker) {
|
||||||
if(smallVideo && smallVideo.hasVideo()) {
|
if(smallVideo && smallVideo.hasVideo()) {
|
||||||
|
@ -432,8 +433,6 @@ var VideoLayout = {
|
||||||
this.updateLargeVideo(this.electLastVisibleVideo());
|
this.updateLargeVideo(this.electLastVisibleVideo());
|
||||||
}
|
}
|
||||||
|
|
||||||
eventEmitter.emit(UIEvents.PINNED_ENDPOINT, smallVideo, false);
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -442,10 +441,10 @@ var VideoLayout = {
|
||||||
|
|
||||||
// Update focused/pinned interface.
|
// Update focused/pinned interface.
|
||||||
if (id) {
|
if (id) {
|
||||||
if (smallVideo && !interfaceConfig.filmStripOnly)
|
if (smallVideo && !interfaceConfig.filmStripOnly) {
|
||||||
smallVideo.focus(true);
|
smallVideo.focus(true);
|
||||||
|
APP.store.dispatch(pinParticipant(id));
|
||||||
eventEmitter.emit(UIEvents.PINNED_ENDPOINT, smallVideo, true);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.updateLargeVideo(id);
|
this.updateLargeVideo(id);
|
||||||
|
@ -823,6 +822,7 @@ var VideoLayout = {
|
||||||
if (pinnedId === id) {
|
if (pinnedId === id) {
|
||||||
logger.info("Focused video owner has left the conference");
|
logger.info("Focused video owner has left the conference");
|
||||||
pinnedId = null;
|
pinnedId = null;
|
||||||
|
APP.store.dispatch(pinParticipant(null));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentDominantSpeaker === id) {
|
if (currentDominantSpeaker === id) {
|
||||||
|
|
|
@ -2,10 +2,12 @@
|
||||||
import UIEvents from '../../../../service/UI/UIEvents';
|
import UIEvents from '../../../../service/UI/UIEvents';
|
||||||
|
|
||||||
import { CONNECTION_ESTABLISHED } from '../connection';
|
import { CONNECTION_ESTABLISHED } from '../connection';
|
||||||
|
import JitsiMeetJS from '../lib-jitsi-meet';
|
||||||
import { setVideoMuted, VIDEO_MUTISM_AUTHORITY } from '../media';
|
import { setVideoMuted, VIDEO_MUTISM_AUTHORITY } from '../media';
|
||||||
import {
|
import {
|
||||||
getLocalParticipant,
|
getLocalParticipant,
|
||||||
getParticipantById,
|
getParticipantById,
|
||||||
|
getPinnedParticipant,
|
||||||
PIN_PARTICIPANT
|
PIN_PARTICIPANT
|
||||||
} from '../participants';
|
} from '../participants';
|
||||||
import { MiddlewareRegistry } from '../redux';
|
import { MiddlewareRegistry } from '../redux';
|
||||||
|
@ -168,27 +170,46 @@ function _conferenceJoined(store, next, action) {
|
||||||
*/
|
*/
|
||||||
function _pinParticipant(store, next, action) {
|
function _pinParticipant(store, next, action) {
|
||||||
const state = store.getState();
|
const state = store.getState();
|
||||||
|
const { conference } = state['features/base/conference'];
|
||||||
const participants = state['features/base/participants'];
|
const participants = state['features/base/participants'];
|
||||||
const id = action.participant.id;
|
const id = action.participant.id;
|
||||||
const participantById = getParticipantById(participants, id);
|
const participantById = getParticipantById(participants, id);
|
||||||
let pin;
|
let pin;
|
||||||
|
|
||||||
// The following condition prevents signaling to pin local participant. The
|
const shouldEmitToLegacyApp = typeof APP !== 'undefined';
|
||||||
// logic is:
|
|
||||||
|
if (shouldEmitToLegacyApp) {
|
||||||
|
const pinnedParticipant = getPinnedParticipant(participants);
|
||||||
|
const actionName = action.participant.id ? 'pinned' : 'unpinned';
|
||||||
|
let videoType;
|
||||||
|
|
||||||
|
if ((participantById && participantById.local)
|
||||||
|
|| (!id && pinnedParticipant && pinnedParticipant.local)) {
|
||||||
|
videoType = 'local';
|
||||||
|
} else {
|
||||||
|
videoType = 'remote';
|
||||||
|
}
|
||||||
|
|
||||||
|
JitsiMeetJS.analytics.sendEvent(
|
||||||
|
`${actionName}.${videoType}`,
|
||||||
|
{ value: conference.getParticipantCount() });
|
||||||
|
}
|
||||||
|
|
||||||
|
// The following condition prevents signaling to pin local participant and
|
||||||
|
// shared videos. The logic is:
|
||||||
// - If we have an ID, we check if the participant identified by that ID is
|
// - If we have an ID, we check if the participant identified by that ID is
|
||||||
// local.
|
// local or a bot/fake participant (such as with shared video).
|
||||||
// - If we don't have an ID (i.e. no participant identified by an ID), we
|
// - If we don't have an ID (i.e. no participant identified by an ID), we
|
||||||
// check for local participant. If she's currently pinned, then this
|
// check for local participant. If she's currently pinned, then this
|
||||||
// action will unpin her and that's why we won't signal here too.
|
// action will unpin her and that's why we won't signal here too.
|
||||||
if (participantById) {
|
if (participantById) {
|
||||||
pin = !participantById.local;
|
pin = !participantById.local && !participantById.isBot;
|
||||||
} else {
|
} else {
|
||||||
const localParticipant = getLocalParticipant(participants);
|
const localParticipant = getLocalParticipant(participants);
|
||||||
|
|
||||||
pin = !localParticipant || !localParticipant.pinned;
|
pin = !localParticipant || !localParticipant.pinned;
|
||||||
}
|
}
|
||||||
if (pin) {
|
if (pin) {
|
||||||
const { conference } = state['features/base/conference'];
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
conference.pinParticipant(id);
|
conference.pinParticipant(id);
|
||||||
|
@ -197,6 +218,10 @@ function _pinParticipant(store, next, action) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (shouldEmitToLegacyApp) {
|
||||||
|
APP.UI.emitEvent(UIEvents.PINNED_ENDPOINT, id, Boolean(id));
|
||||||
|
}
|
||||||
|
|
||||||
return next(action);
|
return next(action);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -98,6 +98,38 @@ export function getParticipantById(stateOrGetState, id) {
|
||||||
return participants.find(p => p.id === id);
|
return participants.find(p => p.id === id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a count of the known participants in the passed in redux state,
|
||||||
|
* excluding any fake participants.
|
||||||
|
*
|
||||||
|
* @param {(Function|Object|Participant[])} stateOrGetState - The redux state
|
||||||
|
* features/base/participants, the (whole) redux state, or redux's
|
||||||
|
* {@code getState} function to be used to retrieve the
|
||||||
|
* features/base/participants state.
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
export function getParticipantCount(stateOrGetState) {
|
||||||
|
const participants = _getParticipants(stateOrGetState);
|
||||||
|
const realParticipants = participants.filter(p => !p.isBot);
|
||||||
|
|
||||||
|
return realParticipants.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the participant which has its pinned state set to truthy.
|
||||||
|
*
|
||||||
|
* @param {(Function|Object|Participant[])} stateOrGetState - The redux state
|
||||||
|
* features/base/participants, the (whole) redux state, or redux's
|
||||||
|
* {@code getState} function to be used to retrieve the
|
||||||
|
* features/base/participants state.
|
||||||
|
* @returns {(Participant|undefined)}
|
||||||
|
*/
|
||||||
|
export function getPinnedParticipant(stateOrGetState) {
|
||||||
|
const participants = _getParticipants(stateOrGetState);
|
||||||
|
|
||||||
|
return participants.find(p => p.pinned);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns array of participants from Redux state.
|
* Returns array of participants from Redux state.
|
||||||
*
|
*
|
||||||
|
|
|
@ -73,6 +73,7 @@ function _participant(state, action) {
|
||||||
connectionStatus,
|
connectionStatus,
|
||||||
dominantSpeaker,
|
dominantSpeaker,
|
||||||
email,
|
email,
|
||||||
|
isBot,
|
||||||
local,
|
local,
|
||||||
pinned,
|
pinned,
|
||||||
role
|
role
|
||||||
|
@ -108,6 +109,7 @@ function _participant(state, action) {
|
||||||
dominantSpeaker: dominantSpeaker || false,
|
dominantSpeaker: dominantSpeaker || false,
|
||||||
email,
|
email,
|
||||||
id,
|
id,
|
||||||
|
isBot,
|
||||||
local: local || false,
|
local: local || false,
|
||||||
name,
|
name,
|
||||||
pinned: pinned || false,
|
pinned: pinned || false,
|
||||||
|
|
|
@ -9,7 +9,11 @@ import {
|
||||||
import { SET_CONFIG } from '../base/config';
|
import { SET_CONFIG } from '../base/config';
|
||||||
import { SET_LOCATION_URL } from '../base/connection';
|
import { SET_LOCATION_URL } from '../base/connection';
|
||||||
import { LIB_INIT_ERROR } from '../base/lib-jitsi-meet';
|
import { LIB_INIT_ERROR } from '../base/lib-jitsi-meet';
|
||||||
import { PARTICIPANT_JOINED } from '../base/participants';
|
import {
|
||||||
|
getLocalParticipant,
|
||||||
|
getParticipantCount,
|
||||||
|
PARTICIPANT_JOINED
|
||||||
|
} from '../base/participants';
|
||||||
import { MiddlewareRegistry } from '../base/redux';
|
import { MiddlewareRegistry } from '../base/redux';
|
||||||
|
|
||||||
import { setCallOverlayVisible, setJWT } from './actions';
|
import { setCallOverlayVisible, setJWT } from './actions';
|
||||||
|
@ -96,13 +100,8 @@ function _maybeSetCallOverlayVisible({ dispatch, getState }, next, action) {
|
||||||
default: {
|
default: {
|
||||||
// The CallOverlay it to no longer be displayed/visible as soon
|
// The CallOverlay it to no longer be displayed/visible as soon
|
||||||
// as another participant joins.
|
// as another participant joins.
|
||||||
const participants = state['features/base/participants'];
|
callOverlayVisible = getParticipantCount(state) === 1
|
||||||
|
&& Boolean(getLocalParticipant(state));
|
||||||
callOverlayVisible
|
|
||||||
= Boolean(
|
|
||||||
participants
|
|
||||||
&& participants.length === 1
|
|
||||||
&& participants[0].local);
|
|
||||||
|
|
||||||
// However, the CallDialog is not to be displayed/visible again
|
// However, the CallDialog is not to be displayed/visible again
|
||||||
// after all remote participants leave.
|
// after all remote participants leave.
|
||||||
|
|
Loading…
Reference in New Issue