瀏覽代碼

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 年之前
父節點
當前提交
f1f46e0af5

+ 3
- 33
conference.js 查看文件

83
 let connection;
83
 let connection;
84
 let localAudio, localVideo;
84
 let localAudio, localVideo;
85
 
85
 
86
-import {VIDEO_CONTAINER_TYPE} from "./modules/UI/videolayout/VideoContainer";
87
-
88
 /*
86
 /*
89
  * Logic to open a desktop picker put on the window global for
87
  * Logic to open a desktop picker put on the window global for
90
  * lib-jitsi-meet to detect and invoke
88
  * lib-jitsi-meet to detect and invoke
1777
                 UIEvents.VIDEO_UNMUTING_WHILE_AUDIO_ONLY,
1775
                 UIEvents.VIDEO_UNMUTING_WHILE_AUDIO_ONLY,
1778
                 () => this._displayAudioOnlyTooltip('videoMute'));
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
         room.on(ConferenceEvents.CONNECTION_INTERRUPTED, () => {
1783
         room.on(ConferenceEvents.CONNECTION_INTERRUPTED, () => {

+ 5
- 5
modules/FollowMe.js 查看文件

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

+ 13
- 0
modules/UI/shared_video/SharedVideo.js 查看文件

10
 import SmallVideo from '../videolayout/SmallVideo';
10
 import SmallVideo from '../videolayout/SmallVideo';
11
 import Filmstrip from '../videolayout/Filmstrip';
11
 import Filmstrip from '../videolayout/Filmstrip';
12
 
12
 
13
+import {
14
+    participantJoined,
15
+    participantLeft
16
+} from '../../../react/features/base/participants';
13
 import { dockToolbox, showToolbox } from '../../../react/features/toolbox';
17
 import { dockToolbox, showToolbox } from '../../../react/features/toolbox';
14
 
18
 
15
 export const SHARED_VIDEO_CONTAINER_TYPE = "sharedvideo";
19
 export const SHARED_VIDEO_CONTAINER_TYPE = "sharedvideo";
267
 
271
 
268
             VideoLayout.addLargeVideoContainer(
272
             VideoLayout.addLargeVideoContainer(
269
                 SHARED_VIDEO_CONTAINER_TYPE, self.sharedVideo);
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
             VideoLayout.handleVideoThumbClicked(self.url);
281
             VideoLayout.handleVideoThumbClicked(self.url);
271
 
282
 
272
             // If we are sending the command and we are starting the player
283
             // If we are sending the command and we are starting the player
461
                     UIEvents.UPDATE_SHARED_VIDEO, null, 'removed');
472
                     UIEvents.UPDATE_SHARED_VIDEO, null, 'removed');
462
         });
473
         });
463
 
474
 
475
+        APP.store.dispatch(participantLeft(this.url));
476
+
464
         this.url = null;
477
         this.url = null;
465
         this.isSharedVideoShown = false;
478
         this.isSharedVideoShown = false;
466
         this.initialAttributes = null;
479
         this.initialAttributes = null;

+ 12
- 12
modules/UI/videolayout/VideoLayout.js 查看文件

1
 /* global APP, $, interfaceConfig, JitsiMeetJS  */
1
 /* global APP, $, interfaceConfig, JitsiMeetJS  */
2
 const logger = require("jitsi-meet-logger").getLogger(__filename);
2
 const logger = require("jitsi-meet-logger").getLogger(__filename);
3
 
3
 
4
+import { pinParticipant } from '../../../react/features/base/participants';
5
+
4
 import Filmstrip from "./Filmstrip";
6
 import Filmstrip from "./Filmstrip";
5
 import UIEvents from "../../../service/UI/UIEvents";
7
 import UIEvents from "../../../service/UI/UIEvents";
6
 import UIUtil from "../util/UIUtil";
8
 import UIUtil from "../util/UIUtil";
20
 
22
 
21
 var eventEmitter = null;
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
  * Currently focused video jid
28
  * Currently focused video jid
25
  * @type {String}
29
  * @type {String}
59
             // let the bridge adjust its lastN set for myjid and store
63
             // let the bridge adjust its lastN set for myjid and store
60
             // the pinned user in the lastNPickupId variable to be
64
             // the pinned user in the lastNPickupId variable to be
61
             // picked up later by the lastN changed event handler.
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
             var oldSmallVideo = VideoLayout.getSmallVideo(pinnedId);
410
             var oldSmallVideo = VideoLayout.getSmallVideo(pinnedId);
407
             if (oldSmallVideo && !interfaceConfig.filmStripOnly) {
411
             if (oldSmallVideo && !interfaceConfig.filmStripOnly) {
408
                 oldSmallVideo.focus(false);
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
         if (pinnedId === id)
417
         if (pinnedId === id)
420
         {
418
         {
421
             pinnedId = null;
419
             pinnedId = null;
420
+
421
+            APP.store.dispatch(pinParticipant(null));
422
+
422
             // Enable the currently set dominant speaker.
423
             // Enable the currently set dominant speaker.
423
             if (currentDominantSpeaker) {
424
             if (currentDominantSpeaker) {
424
                 if(smallVideo && smallVideo.hasVideo()) {
425
                 if(smallVideo && smallVideo.hasVideo()) {
432
                 this.updateLargeVideo(this.electLastVisibleVideo());
433
                 this.updateLargeVideo(this.electLastVisibleVideo());
433
             }
434
             }
434
 
435
 
435
-            eventEmitter.emit(UIEvents.PINNED_ENDPOINT, smallVideo, false);
436
-
437
             return;
436
             return;
438
         }
437
         }
439
 
438
 
442
 
441
 
443
         // Update focused/pinned interface.
442
         // Update focused/pinned interface.
444
         if (id) {
443
         if (id) {
445
-            if (smallVideo && !interfaceConfig.filmStripOnly)
444
+            if (smallVideo && !interfaceConfig.filmStripOnly) {
446
                 smallVideo.focus(true);
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
         this.updateLargeVideo(id);
450
         this.updateLargeVideo(id);
823
         if (pinnedId === id) {
822
         if (pinnedId === id) {
824
             logger.info("Focused video owner has left the conference");
823
             logger.info("Focused video owner has left the conference");
825
             pinnedId = null;
824
             pinnedId = null;
825
+            APP.store.dispatch(pinParticipant(null));
826
         }
826
         }
827
 
827
 
828
         if (currentDominantSpeaker === id) {
828
         if (currentDominantSpeaker === id) {

+ 30
- 5
react/features/base/conference/middleware.js 查看文件

2
 import UIEvents from '../../../../service/UI/UIEvents';
2
 import UIEvents from '../../../../service/UI/UIEvents';
3
 
3
 
4
 import { CONNECTION_ESTABLISHED } from '../connection';
4
 import { CONNECTION_ESTABLISHED } from '../connection';
5
+import JitsiMeetJS from '../lib-jitsi-meet';
5
 import { setVideoMuted, VIDEO_MUTISM_AUTHORITY } from '../media';
6
 import { setVideoMuted, VIDEO_MUTISM_AUTHORITY } from '../media';
6
 import {
7
 import {
7
     getLocalParticipant,
8
     getLocalParticipant,
8
     getParticipantById,
9
     getParticipantById,
10
+    getPinnedParticipant,
9
     PIN_PARTICIPANT
11
     PIN_PARTICIPANT
10
 } from '../participants';
12
 } from '../participants';
11
 import { MiddlewareRegistry } from '../redux';
13
 import { MiddlewareRegistry } from '../redux';
168
  */
170
  */
169
 function _pinParticipant(store, next, action) {
171
 function _pinParticipant(store, next, action) {
170
     const state = store.getState();
172
     const state = store.getState();
173
+    const { conference } = state['features/base/conference'];
171
     const participants = state['features/base/participants'];
174
     const participants = state['features/base/participants'];
172
     const id = action.participant.id;
175
     const id = action.participant.id;
173
     const participantById = getParticipantById(participants, id);
176
     const participantById = getParticipantById(participants, id);
174
     let pin;
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
     // - If we have an ID, we check if the participant identified by that ID is
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
     // - If we don't have an ID (i.e. no participant identified by an ID), we
202
     // - If we don't have an ID (i.e. no participant identified by an ID), we
181
     //   check for local participant. If she's currently pinned, then this
203
     //   check for local participant. If she's currently pinned, then this
182
     //   action will unpin her and that's why we won't signal here too.
204
     //   action will unpin her and that's why we won't signal here too.
183
     if (participantById) {
205
     if (participantById) {
184
-        pin = !participantById.local;
206
+        pin = !participantById.local && !participantById.isBot;
185
     } else {
207
     } else {
186
         const localParticipant = getLocalParticipant(participants);
208
         const localParticipant = getLocalParticipant(participants);
187
 
209
 
188
         pin = !localParticipant || !localParticipant.pinned;
210
         pin = !localParticipant || !localParticipant.pinned;
189
     }
211
     }
190
     if (pin) {
212
     if (pin) {
191
-        const { conference } = state['features/base/conference'];
192
 
213
 
193
         try {
214
         try {
194
             conference.pinParticipant(id);
215
             conference.pinParticipant(id);
197
         }
218
         }
198
     }
219
     }
199
 
220
 
221
+    if (shouldEmitToLegacyApp) {
222
+        APP.UI.emitEvent(UIEvents.PINNED_ENDPOINT, id, Boolean(id));
223
+    }
224
+
200
     return next(action);
225
     return next(action);
201
 }
226
 }
202
 
227
 

+ 32
- 0
react/features/base/participants/functions.js 查看文件

98
     return participants.find(p => p.id === id);
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
  * Returns array of participants from Redux state.
134
  * Returns array of participants from Redux state.
103
  *
135
  *

+ 2
- 0
react/features/base/participants/reducer.js 查看文件

73
             connectionStatus,
73
             connectionStatus,
74
             dominantSpeaker,
74
             dominantSpeaker,
75
             email,
75
             email,
76
+            isBot,
76
             local,
77
             local,
77
             pinned,
78
             pinned,
78
             role
79
             role
108
             dominantSpeaker: dominantSpeaker || false,
109
             dominantSpeaker: dominantSpeaker || false,
109
             email,
110
             email,
110
             id,
111
             id,
112
+            isBot,
111
             local: local || false,
113
             local: local || false,
112
             name,
114
             name,
113
             pinned: pinned || false,
115
             pinned: pinned || false,

+ 7
- 8
react/features/jwt/middleware.js 查看文件

9
 import { SET_CONFIG } from '../base/config';
9
 import { SET_CONFIG } from '../base/config';
10
 import { SET_LOCATION_URL } from '../base/connection';
10
 import { SET_LOCATION_URL } from '../base/connection';
11
 import { LIB_INIT_ERROR } from '../base/lib-jitsi-meet';
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
 import { MiddlewareRegistry } from '../base/redux';
17
 import { MiddlewareRegistry } from '../base/redux';
14
 
18
 
15
 import { setCallOverlayVisible, setJWT } from './actions';
19
 import { setCallOverlayVisible, setJWT } from './actions';
96
             default: {
100
             default: {
97
                 // The CallOverlay it to no longer be displayed/visible as soon
101
                 // The CallOverlay it to no longer be displayed/visible as soon
98
                 // as another participant joins.
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
                 // However, the CallDialog is not to be displayed/visible again
106
                 // However, the CallDialog is not to be displayed/visible again
108
                 // after all remote participants leave.
107
                 // after all remote participants leave.

Loading…
取消
儲存