|
@@ -20,260 +20,264 @@ import SmallVideo from './SmallVideo';
|
20
|
20
|
/**
|
21
|
21
|
*
|
22
|
22
|
*/
|
23
|
|
-function LocalVideo(VideoLayout, emitter, streamEndedCallback) {
|
24
|
|
- this.videoSpanId = 'localVideoContainer';
|
25
|
|
- this.streamEndedCallback = streamEndedCallback;
|
26
|
|
- this.container = this.createContainer();
|
27
|
|
- this.$container = $(this.container);
|
28
|
|
- this.updateDOMLocation();
|
29
|
|
-
|
30
|
|
- this.localVideoId = null;
|
31
|
|
- this.bindHoverHandler();
|
32
|
|
- if (!config.disableLocalVideoFlip) {
|
33
|
|
- this._buildContextMenu();
|
34
|
|
- }
|
35
|
|
- this.isLocal = true;
|
36
|
|
- this.emitter = emitter;
|
37
|
|
- this.statsPopoverLocation = interfaceConfig.VERTICAL_FILMSTRIP
|
38
|
|
- ? 'left top' : 'top center';
|
39
|
|
-
|
40
|
|
- Object.defineProperty(this, 'id', {
|
41
|
|
- get() {
|
42
|
|
- return APP.conference.getMyUserId();
|
|
23
|
+export default class LocalVideo extends SmallVideo {
|
|
24
|
+ /**
|
|
25
|
+ *
|
|
26
|
+ * @param {*} VideoLayout
|
|
27
|
+ * @param {*} emitter
|
|
28
|
+ * @param {*} streamEndedCallback
|
|
29
|
+ */
|
|
30
|
+ constructor(VideoLayout, emitter, streamEndedCallback) {
|
|
31
|
+ super(VideoLayout);
|
|
32
|
+ this.videoSpanId = 'localVideoContainer';
|
|
33
|
+ this.streamEndedCallback = streamEndedCallback;
|
|
34
|
+ this.container = this.createContainer();
|
|
35
|
+ this.$container = $(this.container);
|
|
36
|
+ this.updateDOMLocation();
|
|
37
|
+
|
|
38
|
+ this.localVideoId = null;
|
|
39
|
+ this.bindHoverHandler();
|
|
40
|
+ if (!config.disableLocalVideoFlip) {
|
|
41
|
+ this._buildContextMenu();
|
43
|
42
|
}
|
44
|
|
- });
|
45
|
|
- this.initBrowserSpecificProperties();
|
46
|
|
-
|
47
|
|
- SmallVideo.call(this, VideoLayout);
|
48
|
|
-
|
49
|
|
- // Set default display name.
|
50
|
|
- this.updateDisplayName();
|
51
|
|
-
|
52
|
|
- // Initialize the avatar display with an avatar url selected from the redux
|
53
|
|
- // state. Redux stores the local user with a hardcoded participant id of
|
54
|
|
- // 'local' if no id has been assigned yet.
|
55
|
|
- this.initializeAvatar();
|
56
|
|
-
|
57
|
|
- this.addAudioLevelIndicator();
|
58
|
|
- this.updateIndicators();
|
59
|
|
-
|
60
|
|
- this.container.onclick = this._onContainerClick;
|
61
|
|
-}
|
62
|
|
-
|
63
|
|
-LocalVideo.prototype = Object.create(SmallVideo.prototype);
|
64
|
|
-LocalVideo.prototype.constructor = LocalVideo;
|
65
|
|
-
|
66
|
|
-LocalVideo.prototype.createContainer = function() {
|
67
|
|
- const containerSpan = document.createElement('span');
|
68
|
|
-
|
69
|
|
- containerSpan.classList.add('videocontainer');
|
70
|
|
- containerSpan.id = this.videoSpanId;
|
|
43
|
+ this.isLocal = true;
|
|
44
|
+ this.emitter = emitter;
|
|
45
|
+ this.statsPopoverLocation = interfaceConfig.VERTICAL_FILMSTRIP
|
|
46
|
+ ? 'left top' : 'top center';
|
|
47
|
+
|
|
48
|
+ Object.defineProperty(this, 'id', {
|
|
49
|
+ get() {
|
|
50
|
+ return APP.conference.getMyUserId();
|
|
51
|
+ }
|
|
52
|
+ });
|
|
53
|
+ this.initBrowserSpecificProperties();
|
71
|
54
|
|
72
|
|
- containerSpan.innerHTML = `
|
73
|
|
- <div class = 'videocontainer__background'></div>
|
74
|
|
- <span id = 'localVideoWrapper'></span>
|
75
|
|
- <div class = 'videocontainer__toolbar'></div>
|
76
|
|
- <div class = 'videocontainer__toptoolbar'></div>
|
77
|
|
- <div class = 'videocontainer__hoverOverlay'></div>
|
78
|
|
- <div class = 'displayNameContainer'></div>
|
79
|
|
- <div class = 'avatar-container'></div>`;
|
|
55
|
+ // Set default display name.
|
|
56
|
+ this.updateDisplayName();
|
80
|
57
|
|
81
|
|
- return containerSpan;
|
82
|
|
-};
|
|
58
|
+ // Initialize the avatar display with an avatar url selected from the redux
|
|
59
|
+ // state. Redux stores the local user with a hardcoded participant id of
|
|
60
|
+ // 'local' if no id has been assigned yet.
|
|
61
|
+ this.initializeAvatar();
|
83
|
62
|
|
84
|
|
-/**
|
85
|
|
- * Triggers re-rendering of the display name using current instance state.
|
86
|
|
- *
|
87
|
|
- * @returns {void}
|
88
|
|
- */
|
89
|
|
-LocalVideo.prototype.updateDisplayName = function() {
|
90
|
|
- if (!this.container) {
|
91
|
|
- logger.warn(
|
92
|
|
- `Unable to set displayName - ${this.videoSpanId
|
93
|
|
- } does not exist`);
|
|
63
|
+ this.addAudioLevelIndicator();
|
|
64
|
+ this.updateIndicators();
|
94
|
65
|
|
95
|
|
- return;
|
|
66
|
+ this.container.onclick = this._onContainerClick;
|
96
|
67
|
}
|
97
|
68
|
|
98
|
|
- this._renderDisplayName({
|
99
|
|
- allowEditing: APP.store.getState()['features/base/jwt'].isGuest,
|
100
|
|
- displayNameSuffix: interfaceConfig.DEFAULT_LOCAL_DISPLAY_NAME,
|
101
|
|
- elementID: 'localDisplayName',
|
102
|
|
- participantID: this.id
|
103
|
|
- });
|
104
|
|
-};
|
105
|
|
-
|
106
|
|
-LocalVideo.prototype.changeVideo = function(stream) {
|
107
|
|
- this.videoStream = stream;
|
|
69
|
+ /**
|
|
70
|
+ *
|
|
71
|
+ */
|
|
72
|
+ createContainer() {
|
|
73
|
+ const containerSpan = document.createElement('span');
|
|
74
|
+
|
|
75
|
+ containerSpan.classList.add('videocontainer');
|
|
76
|
+ containerSpan.id = this.videoSpanId;
|
|
77
|
+
|
|
78
|
+ containerSpan.innerHTML = `
|
|
79
|
+ <div class = 'videocontainer__background'></div>
|
|
80
|
+ <span id = 'localVideoWrapper'></span>
|
|
81
|
+ <div class = 'videocontainer__toolbar'></div>
|
|
82
|
+ <div class = 'videocontainer__toptoolbar'></div>
|
|
83
|
+ <div class = 'videocontainer__hoverOverlay'></div>
|
|
84
|
+ <div class = 'displayNameContainer'></div>
|
|
85
|
+ <div class = 'avatar-container'></div>`;
|
|
86
|
+
|
|
87
|
+ return containerSpan;
|
|
88
|
+ }
|
108
|
89
|
|
109
|
|
- this.localVideoId = `localVideo_${stream.getId()}`;
|
|
90
|
+ /**
|
|
91
|
+ * Triggers re-rendering of the display name using current instance state.
|
|
92
|
+ *
|
|
93
|
+ * @returns {void}
|
|
94
|
+ */
|
|
95
|
+ updateDisplayName() {
|
|
96
|
+ if (!this.container) {
|
|
97
|
+ logger.warn(
|
|
98
|
+ `Unable to set displayName - ${this.videoSpanId
|
|
99
|
+ } does not exist`);
|
|
100
|
+
|
|
101
|
+ return;
|
|
102
|
+ }
|
110
|
103
|
|
111
|
|
- this._updateVideoElement();
|
|
104
|
+ this._renderDisplayName({
|
|
105
|
+ allowEditing: APP.store.getState()['features/base/jwt'].isGuest,
|
|
106
|
+ displayNameSuffix: interfaceConfig.DEFAULT_LOCAL_DISPLAY_NAME,
|
|
107
|
+ elementID: 'localDisplayName',
|
|
108
|
+ participantID: this.id
|
|
109
|
+ });
|
|
110
|
+ }
|
112
|
111
|
|
113
|
|
- // eslint-disable-next-line eqeqeq
|
114
|
|
- const isVideo = stream.videoType != 'desktop';
|
115
|
|
- const settings = APP.store.getState()['features/base/settings'];
|
|
112
|
+ /**
|
|
113
|
+ *
|
|
114
|
+ * @param {*} stream
|
|
115
|
+ */
|
|
116
|
+ changeVideo(stream) {
|
|
117
|
+ this.videoStream = stream;
|
|
118
|
+ this.localVideoId = `localVideo_${stream.getId()}`;
|
|
119
|
+ this._updateVideoElement();
|
|
120
|
+
|
|
121
|
+ // eslint-disable-next-line eqeqeq
|
|
122
|
+ const isVideo = stream.videoType != 'desktop';
|
|
123
|
+ const settings = APP.store.getState()['features/base/settings'];
|
|
124
|
+
|
|
125
|
+ this._enableDisableContextMenu(isVideo);
|
|
126
|
+ this.setFlipX(isVideo ? settings.localFlipX : false);
|
|
127
|
+
|
|
128
|
+ const endedHandler = () => {
|
|
129
|
+ const localVideoContainer
|
|
130
|
+ = document.getElementById('localVideoWrapper');
|
|
131
|
+
|
|
132
|
+ // Only remove if there is no video and not a transition state.
|
|
133
|
+ // Previous non-react logic created a new video element with each track
|
|
134
|
+ // removal whereas react reuses the video component so it could be the
|
|
135
|
+ // stream ended but a new one is being used.
|
|
136
|
+ if (localVideoContainer && this.videoStream.isEnded()) {
|
|
137
|
+ ReactDOM.unmountComponentAtNode(localVideoContainer);
|
|
138
|
+ }
|
116
|
139
|
|
117
|
|
- this._enableDisableContextMenu(isVideo);
|
118
|
|
- this.setFlipX(isVideo ? settings.localFlipX : false);
|
|
140
|
+ this._notifyOfStreamEnded();
|
|
141
|
+ stream.off(JitsiTrackEvents.LOCAL_TRACK_STOPPED, endedHandler);
|
|
142
|
+ };
|
119
|
143
|
|
120
|
|
- const endedHandler = () => {
|
121
|
|
- const localVideoContainer
|
122
|
|
- = document.getElementById('localVideoWrapper');
|
|
144
|
+ stream.on(JitsiTrackEvents.LOCAL_TRACK_STOPPED, endedHandler);
|
|
145
|
+ }
|
123
|
146
|
|
124
|
|
- // Only remove if there is no video and not a transition state.
|
125
|
|
- // Previous non-react logic created a new video element with each track
|
126
|
|
- // removal whereas react reuses the video component so it could be the
|
127
|
|
- // stream ended but a new one is being used.
|
128
|
|
- if (localVideoContainer && this.videoStream.isEnded()) {
|
129
|
|
- ReactDOM.unmountComponentAtNode(localVideoContainer);
|
|
147
|
+ /**
|
|
148
|
+ * Notify any subscribers of the local video stream ending.
|
|
149
|
+ *
|
|
150
|
+ * @private
|
|
151
|
+ * @returns {void}
|
|
152
|
+ */
|
|
153
|
+ _notifyOfStreamEnded() {
|
|
154
|
+ if (this.streamEndedCallback) {
|
|
155
|
+ this.streamEndedCallback(this.id);
|
130
|
156
|
}
|
131
|
|
-
|
132
|
|
- this._notifyOfStreamEnded();
|
133
|
|
- stream.off(JitsiTrackEvents.LOCAL_TRACK_STOPPED, endedHandler);
|
134
|
|
- };
|
135
|
|
-
|
136
|
|
- stream.on(JitsiTrackEvents.LOCAL_TRACK_STOPPED, endedHandler);
|
137
|
|
-};
|
138
|
|
-
|
139
|
|
-/**
|
140
|
|
- * Notify any subscribers of the local video stream ending.
|
141
|
|
- *
|
142
|
|
- * @private
|
143
|
|
- * @returns {void}
|
144
|
|
- */
|
145
|
|
-LocalVideo.prototype._notifyOfStreamEnded = function() {
|
146
|
|
- if (this.streamEndedCallback) {
|
147
|
|
- this.streamEndedCallback(this.id);
|
148
|
157
|
}
|
149
|
|
-};
|
150
|
158
|
|
151
|
|
-/**
|
152
|
|
- * Shows or hides the local video container.
|
153
|
|
- * @param {boolean} true to make the local video container visible, false
|
154
|
|
- * otherwise
|
155
|
|
- */
|
156
|
|
-LocalVideo.prototype.setVisible = function(visible) {
|
157
|
|
-
|
158
|
|
- // We toggle the hidden class as an indication to other interested parties
|
159
|
|
- // that this container has been hidden on purpose.
|
160
|
|
- this.$container.toggleClass('hidden');
|
161
|
|
-
|
162
|
|
- // We still show/hide it as we need to overwrite the style property if we
|
163
|
|
- // want our action to take effect. Toggling the display property through
|
164
|
|
- // the above css class didn't succeed in overwriting the style.
|
165
|
|
- if (visible) {
|
166
|
|
- this.$container.show();
|
167
|
|
- } else {
|
168
|
|
- this.$container.hide();
|
|
159
|
+ /**
|
|
160
|
+ * Shows or hides the local video container.
|
|
161
|
+ * @param {boolean} true to make the local video container visible, false
|
|
162
|
+ * otherwise
|
|
163
|
+ */
|
|
164
|
+ setVisible(visible) {
|
|
165
|
+ // We toggle the hidden class as an indication to other interested parties
|
|
166
|
+ // that this container has been hidden on purpose.
|
|
167
|
+ this.$container.toggleClass('hidden');
|
|
168
|
+
|
|
169
|
+ // We still show/hide it as we need to overwrite the style property if we
|
|
170
|
+ // want our action to take effect. Toggling the display property through
|
|
171
|
+ // the above css class didn't succeed in overwriting the style.
|
|
172
|
+ if (visible) {
|
|
173
|
+ this.$container.show();
|
|
174
|
+ } else {
|
|
175
|
+ this.$container.hide();
|
|
176
|
+ }
|
169
|
177
|
}
|
170
|
|
-};
|
171
|
178
|
|
172
|
|
-/**
|
173
|
|
- * Sets the flipX state of the video.
|
174
|
|
- * @param val {boolean} true for flipped otherwise false;
|
175
|
|
- */
|
176
|
|
-LocalVideo.prototype.setFlipX = function(val) {
|
177
|
|
- this.emitter.emit(UIEvents.LOCAL_FLIPX_CHANGED, val);
|
178
|
|
- if (!this.localVideoId) {
|
179
|
|
- return;
|
180
|
|
- }
|
181
|
|
- if (val) {
|
182
|
|
- this.selectVideoElement().addClass('flipVideoX');
|
183
|
|
- } else {
|
184
|
|
- this.selectVideoElement().removeClass('flipVideoX');
|
|
179
|
+ /**
|
|
180
|
+ * Sets the flipX state of the video.
|
|
181
|
+ * @param val {boolean} true for flipped otherwise false;
|
|
182
|
+ */
|
|
183
|
+ setFlipX(val) {
|
|
184
|
+ this.emitter.emit(UIEvents.LOCAL_FLIPX_CHANGED, val);
|
|
185
|
+ if (!this.localVideoId) {
|
|
186
|
+ return;
|
|
187
|
+ }
|
|
188
|
+ if (val) {
|
|
189
|
+ this.selectVideoElement().addClass('flipVideoX');
|
|
190
|
+ } else {
|
|
191
|
+ this.selectVideoElement().removeClass('flipVideoX');
|
|
192
|
+ }
|
185
|
193
|
}
|
186
|
|
-};
|
187
|
194
|
|
188
|
|
-/**
|
189
|
|
- * Builds the context menu for the local video.
|
190
|
|
- */
|
191
|
|
-LocalVideo.prototype._buildContextMenu = function() {
|
192
|
|
- $.contextMenu({
|
193
|
|
- selector: `#${this.videoSpanId}`,
|
194
|
|
- zIndex: 10000,
|
195
|
|
- items: {
|
196
|
|
- flip: {
|
197
|
|
- name: 'Flip',
|
198
|
|
- callback: () => {
|
199
|
|
- const { store } = APP;
|
200
|
|
- const val = !store.getState()['features/base/settings']
|
201
|
|
- .localFlipX;
|
202
|
|
-
|
203
|
|
- this.setFlipX(val);
|
204
|
|
- store.dispatch(updateSettings({
|
205
|
|
- localFlipX: val
|
206
|
|
- }));
|
|
195
|
+ /**
|
|
196
|
+ * Builds the context menu for the local video.
|
|
197
|
+ */
|
|
198
|
+ _buildContextMenu() {
|
|
199
|
+ $.contextMenu({
|
|
200
|
+ selector: `#${this.videoSpanId}`,
|
|
201
|
+ zIndex: 10000,
|
|
202
|
+ items: {
|
|
203
|
+ flip: {
|
|
204
|
+ name: 'Flip',
|
|
205
|
+ callback: () => {
|
|
206
|
+ const { store } = APP;
|
|
207
|
+ const val = !store.getState()['features/base/settings']
|
|
208
|
+ .localFlipX;
|
|
209
|
+
|
|
210
|
+ this.setFlipX(val);
|
|
211
|
+ store.dispatch(updateSettings({
|
|
212
|
+ localFlipX: val
|
|
213
|
+ }));
|
|
214
|
+ }
|
|
215
|
+ }
|
|
216
|
+ },
|
|
217
|
+ events: {
|
|
218
|
+ show(options) {
|
|
219
|
+ options.items.flip.name
|
|
220
|
+ = APP.translation.generateTranslationHTML(
|
|
221
|
+ 'videothumbnail.flip');
|
207
|
222
|
}
|
208
|
223
|
}
|
209
|
|
- },
|
210
|
|
- events: {
|
211
|
|
- show(options) {
|
212
|
|
- options.items.flip.name
|
213
|
|
- = APP.translation.generateTranslationHTML(
|
214
|
|
- 'videothumbnail.flip');
|
215
|
|
- }
|
216
|
|
- }
|
217
|
|
- });
|
218
|
|
-};
|
219
|
|
-
|
220
|
|
-/**
|
221
|
|
- * Enables or disables the context menu for the local video.
|
222
|
|
- * @param enable {boolean} true for enable, false for disable
|
223
|
|
- */
|
224
|
|
-LocalVideo.prototype._enableDisableContextMenu = function(enable) {
|
225
|
|
- if (this.$container.contextMenu) {
|
226
|
|
- this.$container.contextMenu(enable);
|
|
224
|
+ });
|
227
|
225
|
}
|
228
|
|
-};
|
229
|
226
|
|
230
|
|
-/**
|
231
|
|
- * Places the {@code LocalVideo} in the DOM based on the current video layout.
|
232
|
|
- *
|
233
|
|
- * @returns {void}
|
234
|
|
- */
|
235
|
|
-LocalVideo.prototype.updateDOMLocation = function() {
|
236
|
|
- if (!this.container) {
|
237
|
|
- return;
|
238
|
|
- }
|
239
|
|
-
|
240
|
|
- if (this.container.parentElement) {
|
241
|
|
- this.container.parentElement.removeChild(this.container);
|
|
227
|
+ /**
|
|
228
|
+ * Enables or disables the context menu for the local video.
|
|
229
|
+ * @param enable {boolean} true for enable, false for disable
|
|
230
|
+ */
|
|
231
|
+ _enableDisableContextMenu(enable) {
|
|
232
|
+ if (this.$container.contextMenu) {
|
|
233
|
+ this.$container.contextMenu(enable);
|
|
234
|
+ }
|
242
|
235
|
}
|
243
|
236
|
|
244
|
|
- const appendTarget = shouldDisplayTileView(APP.store.getState())
|
245
|
|
- ? document.getElementById('localVideoTileViewContainer')
|
246
|
|
- : document.getElementById('filmstripLocalVideoThumbnail');
|
|
237
|
+ /**
|
|
238
|
+ * Places the {@code LocalVideo} in the DOM based on the current video layout.
|
|
239
|
+ *
|
|
240
|
+ * @returns {void}
|
|
241
|
+ */
|
|
242
|
+ updateDOMLocation() {
|
|
243
|
+ if (!this.container) {
|
|
244
|
+ return;
|
|
245
|
+ }
|
|
246
|
+ if (this.container.parentElement) {
|
|
247
|
+ this.container.parentElement.removeChild(this.container);
|
|
248
|
+ }
|
247
|
249
|
|
248
|
|
- appendTarget && appendTarget.appendChild(this.container);
|
|
250
|
+ const appendTarget = shouldDisplayTileView(APP.store.getState())
|
|
251
|
+ ? document.getElementById('localVideoTileViewContainer')
|
|
252
|
+ : document.getElementById('filmstripLocalVideoThumbnail');
|
249
|
253
|
|
250
|
|
- this._updateVideoElement();
|
251
|
|
-};
|
|
254
|
+ appendTarget && appendTarget.appendChild(this.container);
|
|
255
|
+ this._updateVideoElement();
|
|
256
|
+ }
|
252
|
257
|
|
253
|
|
-/**
|
254
|
|
- * Renders the React Element for displaying video in {@code LocalVideo}.
|
255
|
|
- *
|
256
|
|
- */
|
257
|
|
-LocalVideo.prototype._updateVideoElement = function() {
|
258
|
|
- const localVideoContainer = document.getElementById('localVideoWrapper');
|
259
|
|
- const videoTrack
|
260
|
|
- = getLocalVideoTrack(APP.store.getState()['features/base/tracks']);
|
261
|
|
-
|
262
|
|
- ReactDOM.render(
|
263
|
|
- <Provider store = { APP.store }>
|
264
|
|
- <VideoTrack
|
265
|
|
- id = 'localVideo_container'
|
266
|
|
- videoTrack = { videoTrack } />
|
267
|
|
- </Provider>,
|
268
|
|
- localVideoContainer
|
269
|
|
- );
|
270
|
|
-
|
271
|
|
- // Ensure the video gets play() called on it. This may be necessary in the
|
272
|
|
- // case where the local video container was moved and re-attached, in which
|
273
|
|
- // case video does not autoplay.
|
274
|
|
- const video = this.container.querySelector('video');
|
275
|
|
-
|
276
|
|
- video && !config.testing?.noAutoPlayVideo && video.play();
|
277
|
|
-};
|
278
|
|
-
|
279
|
|
-export default LocalVideo;
|
|
258
|
+ /**
|
|
259
|
+ * Renders the React Element for displaying video in {@code LocalVideo}.
|
|
260
|
+ *
|
|
261
|
+ */
|
|
262
|
+ _updateVideoElement() {
|
|
263
|
+ const localVideoContainer = document.getElementById('localVideoWrapper');
|
|
264
|
+ const videoTrack
|
|
265
|
+ = getLocalVideoTrack(APP.store.getState()['features/base/tracks']);
|
|
266
|
+
|
|
267
|
+ ReactDOM.render(
|
|
268
|
+ <Provider store = { APP.store }>
|
|
269
|
+ <VideoTrack
|
|
270
|
+ id = 'localVideo_container'
|
|
271
|
+ videoTrack = { videoTrack } />
|
|
272
|
+ </Provider>,
|
|
273
|
+ localVideoContainer
|
|
274
|
+ );
|
|
275
|
+
|
|
276
|
+ // Ensure the video gets play() called on it. This may be necessary in the
|
|
277
|
+ // case where the local video container was moved and re-attached, in which
|
|
278
|
+ // case video does not autoplay.
|
|
279
|
+ const video = this.container.querySelector('video');
|
|
280
|
+
|
|
281
|
+ video && !config.testing?.noAutoPlayVideo && video.play();
|
|
282
|
+ }
|
|
283
|
+}
|