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:
Leonard Kim 2017-06-27 15:56:55 -07:00 committed by Paweł Domas
parent 19d9b3f023
commit f1f46e0af5
8 changed files with 104 additions and 63 deletions

View File

@ -83,8 +83,6 @@ let room;
let connection;
let localAudio, localVideo;
import {VIDEO_CONTAINER_TYPE} from "./modules/UI/videolayout/VideoContainer";
/*
* Logic to open a desktop picker put on the window global for
* lib-jitsi-meet to detect and invoke
@ -1777,37 +1775,9 @@ export default {
UIEvents.VIDEO_UNMUTING_WHILE_AUDIO_ONLY,
() => this._displayAudioOnlyTooltip('videoMute'));
APP.UI.addListener(UIEvents.PINNED_ENDPOINT,
(smallVideo, isPinned) => {
let smallVideoId = smallVideo.getId();
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();
});
APP.UI.addListener(
UIEvents.PINNED_ENDPOINT,
updateRemoteThumbnailsVisibility);
}
room.on(ConferenceEvents.CONNECTION_INTERRUPTED, () => {

View File

@ -153,7 +153,7 @@ class FollowMe {
smallVideo = VideoLayout.getSmallVideo(pinnedId);
}
this._nextOnStage(smallVideo, isPinned);
this._nextOnStage(smallVideo.getId(), isPinned);
// check whether shared document is enabled/initialized
if(this._UI.getSharedDocumentManager())
@ -174,8 +174,8 @@ class FollowMe {
this.filmstripEventHandler);
var self = this;
this.pinnedEndpointEventHandler = function (smallVideo, isPinned) {
self._nextOnStage(smallVideo, isPinned);
this.pinnedEndpointEventHandler = function (videoId, isPinned) {
self._nextOnStage(videoId, isPinned);
};
this._UI.addListener(UIEvents.PINNED_ENDPOINT,
this.pinnedEndpointEventHandler);
@ -243,13 +243,13 @@ class FollowMe {
* unpinned
* @private
*/
_nextOnStage (smallVideo, isPinned) {
_nextOnStage (videoId, isPinned) {
if (!this._conference.isModerator)
return;
var nextOnStage = null;
if(isPinned)
nextOnStage = smallVideo.getId();
nextOnStage = videoId;
this._local.nextOnStage = nextOnStage;
}

View File

@ -10,6 +10,10 @@ import LargeContainer from '../videolayout/LargeContainer';
import SmallVideo from '../videolayout/SmallVideo';
import Filmstrip from '../videolayout/Filmstrip';
import {
participantJoined,
participantLeft
} from '../../../react/features/base/participants';
import { dockToolbox, showToolbox } from '../../../react/features/toolbox';
export const SHARED_VIDEO_CONTAINER_TYPE = "sharedvideo";
@ -267,6 +271,13 @@ export default class SharedVideoManager {
VideoLayout.addLargeVideoContainer(
SHARED_VIDEO_CONTAINER_TYPE, self.sharedVideo);
APP.store.dispatch(participantJoined({
id: self.url,
isBot: true,
name: player.getVideoData().title
}));
VideoLayout.handleVideoThumbClicked(self.url);
// 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');
});
APP.store.dispatch(participantLeft(this.url));
this.url = null;
this.isSharedVideoShown = false;
this.initialAttributes = null;

View File

@ -1,6 +1,8 @@
/* global APP, $, interfaceConfig, JitsiMeetJS */
const logger = require("jitsi-meet-logger").getLogger(__filename);
import { pinParticipant } from '../../../react/features/base/participants';
import Filmstrip from "./Filmstrip";
import UIEvents from "../../../service/UI/UIEvents";
import UIUtil from "../util/UIUtil";
@ -20,6 +22,8 @@ var currentDominantSpeaker = 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
* @type {String}
@ -59,7 +63,7 @@ function onContactClicked (id) {
// let the bridge adjust its lastN set for myjid and store
// the pinned user in the lastNPickupId variable to be
// 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);
if (oldSmallVideo && !interfaceConfig.filmStripOnly) {
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)
{
pinnedId = null;
APP.store.dispatch(pinParticipant(null));
// Enable the currently set dominant speaker.
if (currentDominantSpeaker) {
if(smallVideo && smallVideo.hasVideo()) {
@ -432,8 +433,6 @@ var VideoLayout = {
this.updateLargeVideo(this.electLastVisibleVideo());
}
eventEmitter.emit(UIEvents.PINNED_ENDPOINT, smallVideo, false);
return;
}
@ -442,10 +441,10 @@ var VideoLayout = {
// Update focused/pinned interface.
if (id) {
if (smallVideo && !interfaceConfig.filmStripOnly)
if (smallVideo && !interfaceConfig.filmStripOnly) {
smallVideo.focus(true);
eventEmitter.emit(UIEvents.PINNED_ENDPOINT, smallVideo, true);
APP.store.dispatch(pinParticipant(id));
}
}
this.updateLargeVideo(id);
@ -823,6 +822,7 @@ var VideoLayout = {
if (pinnedId === id) {
logger.info("Focused video owner has left the conference");
pinnedId = null;
APP.store.dispatch(pinParticipant(null));
}
if (currentDominantSpeaker === id) {

View File

@ -2,10 +2,12 @@
import UIEvents from '../../../../service/UI/UIEvents';
import { CONNECTION_ESTABLISHED } from '../connection';
import JitsiMeetJS from '../lib-jitsi-meet';
import { setVideoMuted, VIDEO_MUTISM_AUTHORITY } from '../media';
import {
getLocalParticipant,
getParticipantById,
getPinnedParticipant,
PIN_PARTICIPANT
} from '../participants';
import { MiddlewareRegistry } from '../redux';
@ -168,27 +170,46 @@ function _conferenceJoined(store, next, action) {
*/
function _pinParticipant(store, next, action) {
const state = store.getState();
const { conference } = state['features/base/conference'];
const participants = state['features/base/participants'];
const id = action.participant.id;
const participantById = getParticipantById(participants, id);
let pin;
// The following condition prevents signaling to pin local participant. The
// logic is:
const shouldEmitToLegacyApp = typeof APP !== 'undefined';
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
// 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
// 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.
if (participantById) {
pin = !participantById.local;
pin = !participantById.local && !participantById.isBot;
} else {
const localParticipant = getLocalParticipant(participants);
pin = !localParticipant || !localParticipant.pinned;
}
if (pin) {
const { conference } = state['features/base/conference'];
try {
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);
}

View File

@ -98,6 +98,38 @@ export function getParticipantById(stateOrGetState, 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.
*

View File

@ -73,6 +73,7 @@ function _participant(state, action) {
connectionStatus,
dominantSpeaker,
email,
isBot,
local,
pinned,
role
@ -108,6 +109,7 @@ function _participant(state, action) {
dominantSpeaker: dominantSpeaker || false,
email,
id,
isBot,
local: local || false,
name,
pinned: pinned || false,

View File

@ -9,7 +9,11 @@ import {
import { SET_CONFIG } from '../base/config';
import { SET_LOCATION_URL } from '../base/connection';
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 { setCallOverlayVisible, setJWT } from './actions';
@ -96,13 +100,8 @@ function _maybeSetCallOverlayVisible({ dispatch, getState }, next, action) {
default: {
// The CallOverlay it to no longer be displayed/visible as soon
// as another participant joins.
const participants = state['features/base/participants'];
callOverlayVisible
= Boolean(
participants
&& participants.length === 1
&& participants[0].local);
callOverlayVisible = getParticipantCount(state) === 1
&& Boolean(getLocalParticipant(state));
// However, the CallDialog is not to be displayed/visible again
// after all remote participants leave.