Browse Source

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
j8
Leonard Kim 7 years ago
parent
commit
f1f46e0af5

+ 3
- 33
conference.js View File

@@ -83,8 +83,6 @@ let room;
83 83
 let connection;
84 84
 let localAudio, localVideo;
85 85
 
86
-import {VIDEO_CONTAINER_TYPE} from "./modules/UI/videolayout/VideoContainer";
87
-
88 86
 /*
89 87
  * Logic to open a desktop picker put on the window global for
90 88
  * lib-jitsi-meet to detect and invoke
@@ -1777,37 +1775,9 @@ export default {
1777 1775
                 UIEvents.VIDEO_UNMUTING_WHILE_AUDIO_ONLY,
1778 1776
                 () => this._displayAudioOnlyTooltip('videoMute'));
1779 1777
 
1780
-            APP.UI.addListener(UIEvents.PINNED_ENDPOINT,
1781
-            (smallVideo, isPinned) => {
1782
-                let smallVideoId = smallVideo.getId();
1783
-                let isLocal = APP.conference.isLocalId(smallVideoId);
1784
-
1785
-                let eventName
1786
-                    = (isPinned ? "pinned" : "unpinned") + "." +
1787
-                            (isLocal ? "local" : "remote");
1788
-                let participantCount = room.getParticipantCount();
1789
-                JitsiMeetJS.analytics.sendEvent(
1790
-                        eventName,
1791
-                        { value: participantCount });
1792
-
1793
-                // FIXME why VIDEO_CONTAINER_TYPE instead of checking if
1794
-                // the participant is on the large video ?
1795
-                if (smallVideo.getVideoType() === VIDEO_CONTAINER_TYPE
1796
-                    && !isLocal) {
1797
-
1798
-                    // When the library starts supporting multiple pins we
1799
-                    // would pass the isPinned parameter together with the
1800
-                    // identifier, but currently we send null to indicate that
1801
-                    // we unpin the last pinned.
1802
-                    try {
1803
-                        room.pinParticipant(isPinned ? smallVideoId : null);
1804
-                    } catch (e) {
1805
-                        reportError(e);
1806
-                    }
1807
-                }
1808
-
1809
-                updateRemoteThumbnailsVisibility();
1810
-            });
1778
+            APP.UI.addListener(
1779
+                UIEvents.PINNED_ENDPOINT,
1780
+                updateRemoteThumbnailsVisibility);
1811 1781
         }
1812 1782
 
1813 1783
         room.on(ConferenceEvents.CONNECTION_INTERRUPTED, () => {

+ 5
- 5
modules/FollowMe.js View File

@@ -153,7 +153,7 @@ class FollowMe {
153 153
             smallVideo = VideoLayout.getSmallVideo(pinnedId);
154 154
         }
155 155
 
156
-        this._nextOnStage(smallVideo, isPinned);
156
+        this._nextOnStage(smallVideo.getId(), isPinned);
157 157
 
158 158
         // check whether shared document is enabled/initialized
159 159
         if(this._UI.getSharedDocumentManager())
@@ -174,8 +174,8 @@ class FollowMe {
174 174
                             this.filmstripEventHandler);
175 175
 
176 176
         var self = this;
177
-        this.pinnedEndpointEventHandler = function (smallVideo, isPinned) {
178
-            self._nextOnStage(smallVideo, isPinned);
177
+        this.pinnedEndpointEventHandler = function (videoId, isPinned) {
178
+            self._nextOnStage(videoId, isPinned);
179 179
         };
180 180
         this._UI.addListener(UIEvents.PINNED_ENDPOINT,
181 181
                             this.pinnedEndpointEventHandler);
@@ -243,13 +243,13 @@ class FollowMe {
243 243
      * unpinned
244 244
      * @private
245 245
      */
246
-    _nextOnStage (smallVideo, isPinned) {
246
+    _nextOnStage (videoId, isPinned) {
247 247
         if (!this._conference.isModerator)
248 248
             return;
249 249
 
250 250
         var nextOnStage = null;
251 251
         if(isPinned)
252
-            nextOnStage = smallVideo.getId();
252
+            nextOnStage = videoId;
253 253
 
254 254
         this._local.nextOnStage = nextOnStage;
255 255
     }

+ 13
- 0
modules/UI/shared_video/SharedVideo.js View File

@@ -10,6 +10,10 @@ import LargeContainer from '../videolayout/LargeContainer';
10 10
 import SmallVideo from '../videolayout/SmallVideo';
11 11
 import Filmstrip from '../videolayout/Filmstrip';
12 12
 
13
+import {
14
+    participantJoined,
15
+    participantLeft
16
+} from '../../../react/features/base/participants';
13 17
 import { dockToolbox, showToolbox } from '../../../react/features/toolbox';
14 18
 
15 19
 export const SHARED_VIDEO_CONTAINER_TYPE = "sharedvideo";
@@ -267,6 +271,13 @@ export default class SharedVideoManager {
267 271
 
268 272
             VideoLayout.addLargeVideoContainer(
269 273
                 SHARED_VIDEO_CONTAINER_TYPE, self.sharedVideo);
274
+
275
+            APP.store.dispatch(participantJoined({
276
+                id: self.url,
277
+                isBot: true,
278
+                name: player.getVideoData().title
279
+            }));
280
+
270 281
             VideoLayout.handleVideoThumbClicked(self.url);
271 282
 
272 283
             // If we are sending the command and we are starting the player
@@ -461,6 +472,8 @@ export default class SharedVideoManager {
461 472
                     UIEvents.UPDATE_SHARED_VIDEO, null, 'removed');
462 473
         });
463 474
 
475
+        APP.store.dispatch(participantLeft(this.url));
476
+
464 477
         this.url = null;
465 478
         this.isSharedVideoShown = false;
466 479
         this.initialAttributes = null;

+ 12
- 12
modules/UI/videolayout/VideoLayout.js View File

@@ -1,6 +1,8 @@
1 1
 /* global APP, $, interfaceConfig, JitsiMeetJS  */
2 2
 const logger = require("jitsi-meet-logger").getLogger(__filename);
3 3
 
4
+import { pinParticipant } from '../../../react/features/base/participants';
5
+
4 6
 import Filmstrip from "./Filmstrip";
5 7
 import UIEvents from "../../../service/UI/UIEvents";
6 8
 import UIUtil from "../util/UIUtil";
@@ -20,6 +22,8 @@ var currentDominantSpeaker = null;
20 22
 
21 23
 var eventEmitter = null;
22 24
 
25
+// TODO Remove this private reference to pinnedId once other components
26
+// interested in its updates are moved to react/redux.
23 27
 /**
24 28
  * Currently focused video jid
25 29
  * @type {String}
@@ -59,7 +63,7 @@ function onContactClicked (id) {
59 63
             // let the bridge adjust its lastN set for myjid and store
60 64
             // the pinned user in the lastNPickupId variable to be
61 65
             // picked up later by the lastN changed event handler.
62
-            eventEmitter.emit(UIEvents.PINNED_ENDPOINT, remoteVideo, true);
66
+            APP.store.dispatch(pinParticipant(remoteVideo.id));
63 67
         }
64 68
     }
65 69
 }
@@ -406,12 +410,6 @@ var VideoLayout = {
406 410
             var oldSmallVideo = VideoLayout.getSmallVideo(pinnedId);
407 411
             if (oldSmallVideo && !interfaceConfig.filmStripOnly) {
408 412
                 oldSmallVideo.focus(false);
409
-                // as no pinned event will be sent for local video
410
-                // and we will unpin old one, lets signal it
411
-                // otherwise we will just send the new pinned one
412
-                if (smallVideo.isLocal)
413
-                    eventEmitter.emit(
414
-                        UIEvents.PINNED_ENDPOINT, oldSmallVideo, false);
415 413
             }
416 414
         }
417 415
 
@@ -419,6 +417,9 @@ var VideoLayout = {
419 417
         if (pinnedId === id)
420 418
         {
421 419
             pinnedId = null;
420
+
421
+            APP.store.dispatch(pinParticipant(null));
422
+
422 423
             // Enable the currently set dominant speaker.
423 424
             if (currentDominantSpeaker) {
424 425
                 if(smallVideo && smallVideo.hasVideo()) {
@@ -432,8 +433,6 @@ var VideoLayout = {
432 433
                 this.updateLargeVideo(this.electLastVisibleVideo());
433 434
             }
434 435
 
435
-            eventEmitter.emit(UIEvents.PINNED_ENDPOINT, smallVideo, false);
436
-
437 436
             return;
438 437
         }
439 438
 
@@ -442,10 +441,10 @@ var VideoLayout = {
442 441
 
443 442
         // Update focused/pinned interface.
444 443
         if (id) {
445
-            if (smallVideo && !interfaceConfig.filmStripOnly)
444
+            if (smallVideo && !interfaceConfig.filmStripOnly) {
446 445
                 smallVideo.focus(true);
447
-
448
-            eventEmitter.emit(UIEvents.PINNED_ENDPOINT, smallVideo, true);
446
+                APP.store.dispatch(pinParticipant(id));
447
+            }
449 448
         }
450 449
 
451 450
         this.updateLargeVideo(id);
@@ -823,6 +822,7 @@ var VideoLayout = {
823 822
         if (pinnedId === id) {
824 823
             logger.info("Focused video owner has left the conference");
825 824
             pinnedId = null;
825
+            APP.store.dispatch(pinParticipant(null));
826 826
         }
827 827
 
828 828
         if (currentDominantSpeaker === id) {

+ 30
- 5
react/features/base/conference/middleware.js View File

@@ -2,10 +2,12 @@
2 2
 import UIEvents from '../../../../service/UI/UIEvents';
3 3
 
4 4
 import { CONNECTION_ESTABLISHED } from '../connection';
5
+import JitsiMeetJS from '../lib-jitsi-meet';
5 6
 import { setVideoMuted, VIDEO_MUTISM_AUTHORITY } from '../media';
6 7
 import {
7 8
     getLocalParticipant,
8 9
     getParticipantById,
10
+    getPinnedParticipant,
9 11
     PIN_PARTICIPANT
10 12
 } from '../participants';
11 13
 import { MiddlewareRegistry } from '../redux';
@@ -168,27 +170,46 @@ function _conferenceJoined(store, next, action) {
168 170
  */
169 171
 function _pinParticipant(store, next, action) {
170 172
     const state = store.getState();
173
+    const { conference } = state['features/base/conference'];
171 174
     const participants = state['features/base/participants'];
172 175
     const id = action.participant.id;
173 176
     const participantById = getParticipantById(participants, id);
174 177
     let pin;
175 178
 
176
-    // The following condition prevents signaling to pin local participant. The
177
-    // logic is:
179
+    const shouldEmitToLegacyApp = typeof APP !== 'undefined';
180
+
181
+    if (shouldEmitToLegacyApp) {
182
+        const pinnedParticipant = getPinnedParticipant(participants);
183
+        const actionName = action.participant.id ? 'pinned' : 'unpinned';
184
+        let videoType;
185
+
186
+        if ((participantById && participantById.local)
187
+            || (!id && pinnedParticipant && pinnedParticipant.local)) {
188
+            videoType = 'local';
189
+        } else {
190
+            videoType = 'remote';
191
+        }
192
+
193
+        JitsiMeetJS.analytics.sendEvent(
194
+                `${actionName}.${videoType}`,
195
+                { value: conference.getParticipantCount() });
196
+    }
197
+
198
+    // The following condition prevents signaling to pin local participant and
199
+    // shared videos. The logic is:
178 200
     // - If we have an ID, we check if the participant identified by that ID is
179
-    //   local.
201
+    //   local or a bot/fake participant (such as with shared video).
180 202
     // - If we don't have an ID (i.e. no participant identified by an ID), we
181 203
     //   check for local participant. If she's currently pinned, then this
182 204
     //   action will unpin her and that's why we won't signal here too.
183 205
     if (participantById) {
184
-        pin = !participantById.local;
206
+        pin = !participantById.local && !participantById.isBot;
185 207
     } else {
186 208
         const localParticipant = getLocalParticipant(participants);
187 209
 
188 210
         pin = !localParticipant || !localParticipant.pinned;
189 211
     }
190 212
     if (pin) {
191
-        const { conference } = state['features/base/conference'];
192 213
 
193 214
         try {
194 215
             conference.pinParticipant(id);
@@ -197,6 +218,10 @@ function _pinParticipant(store, next, action) {
197 218
         }
198 219
     }
199 220
 
221
+    if (shouldEmitToLegacyApp) {
222
+        APP.UI.emitEvent(UIEvents.PINNED_ENDPOINT, id, Boolean(id));
223
+    }
224
+
200 225
     return next(action);
201 226
 }
202 227
 

+ 32
- 0
react/features/base/participants/functions.js View File

@@ -98,6 +98,38 @@ export function getParticipantById(stateOrGetState, id) {
98 98
     return participants.find(p => p.id === id);
99 99
 }
100 100
 
101
+/**
102
+ * Returns a count of the known participants in the passed in redux state,
103
+ * excluding any fake participants.
104
+ *
105
+ * @param {(Function|Object|Participant[])} stateOrGetState - The redux state
106
+ * features/base/participants, the (whole) redux state, or redux's
107
+ * {@code getState} function to be used to retrieve the
108
+ * features/base/participants state.
109
+ * @returns {number}
110
+ */
111
+export function getParticipantCount(stateOrGetState) {
112
+    const participants = _getParticipants(stateOrGetState);
113
+    const realParticipants = participants.filter(p => !p.isBot);
114
+
115
+    return realParticipants.length;
116
+}
117
+
118
+/**
119
+ * Returns the participant which has its pinned state set to truthy.
120
+ *
121
+ * @param {(Function|Object|Participant[])} stateOrGetState - The redux state
122
+ * features/base/participants, the (whole) redux state, or redux's
123
+ * {@code getState} function to be used to retrieve the
124
+ * features/base/participants state.
125
+ * @returns {(Participant|undefined)}
126
+ */
127
+export function getPinnedParticipant(stateOrGetState) {
128
+    const participants = _getParticipants(stateOrGetState);
129
+
130
+    return participants.find(p => p.pinned);
131
+}
132
+
101 133
 /**
102 134
  * Returns array of participants from Redux state.
103 135
  *

+ 2
- 0
react/features/base/participants/reducer.js View File

@@ -73,6 +73,7 @@ function _participant(state, action) {
73 73
             connectionStatus,
74 74
             dominantSpeaker,
75 75
             email,
76
+            isBot,
76 77
             local,
77 78
             pinned,
78 79
             role
@@ -108,6 +109,7 @@ function _participant(state, action) {
108 109
             dominantSpeaker: dominantSpeaker || false,
109 110
             email,
110 111
             id,
112
+            isBot,
111 113
             local: local || false,
112 114
             name,
113 115
             pinned: pinned || false,

+ 7
- 8
react/features/jwt/middleware.js View File

@@ -9,7 +9,11 @@ import {
9 9
 import { SET_CONFIG } from '../base/config';
10 10
 import { SET_LOCATION_URL } from '../base/connection';
11 11
 import { LIB_INIT_ERROR } from '../base/lib-jitsi-meet';
12
-import { PARTICIPANT_JOINED } from '../base/participants';
12
+import {
13
+    getLocalParticipant,
14
+    getParticipantCount,
15
+    PARTICIPANT_JOINED
16
+} from '../base/participants';
13 17
 import { MiddlewareRegistry } from '../base/redux';
14 18
 
15 19
 import { setCallOverlayVisible, setJWT } from './actions';
@@ -96,13 +100,8 @@ function _maybeSetCallOverlayVisible({ dispatch, getState }, next, action) {
96 100
             default: {
97 101
                 // The CallOverlay it to no longer be displayed/visible as soon
98 102
                 // as another participant joins.
99
-                const participants = state['features/base/participants'];
100
-
101
-                callOverlayVisible
102
-                    = Boolean(
103
-                        participants
104
-                            && participants.length === 1
105
-                            && participants[0].local);
103
+                callOverlayVisible = getParticipantCount(state) === 1
104
+                    && Boolean(getLocalParticipant(state));
106 105
 
107 106
                 // However, the CallDialog is not to be displayed/visible again
108 107
                 // after all remote participants leave.

Loading…
Cancel
Save