mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2026-02-20 12:50:19 +00:00
Compare commits
24 Commits
fix/consol
...
release-74
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d46f1bcd6e | ||
|
|
1d6d63906a | ||
|
|
b3782ba4ef | ||
|
|
08f0a095f3 | ||
|
|
0a7446e1e7 | ||
|
|
a07031cce7 | ||
|
|
c908814381 | ||
|
|
9f02bdd890 | ||
|
|
8a16fd5c1f | ||
|
|
9493ebdd1d | ||
|
|
e5bfe1c89e | ||
|
|
114c017f1d | ||
|
|
7287172bd3 | ||
|
|
584bdd3137 | ||
|
|
48a1724b19 | ||
|
|
2af41724bd | ||
|
|
4d63807a31 | ||
|
|
10b10b5f40 | ||
|
|
7921722039 | ||
|
|
7c6decb9a0 | ||
|
|
6e7452239f | ||
|
|
34180a7997 | ||
|
|
3298435e7d | ||
|
|
8b28b39c03 |
@@ -441,7 +441,7 @@ export default {
|
||||
|
||||
// Always get a handle on the audio input device so that we have statistics (such as "No audio input" or
|
||||
// "Are you trying to speak?" ) even if the user joins the conference muted.
|
||||
const initialDevices = config.disableInitialGUM ? [] : [ MEDIA_TYPE.AUDIO ];
|
||||
const initialDevices = config.startSilent || config.disableInitialGUM ? [] : [ MEDIA_TYPE.AUDIO ];
|
||||
const requestedAudio = !config.disableInitialGUM;
|
||||
let requestedVideo = false;
|
||||
|
||||
|
||||
@@ -871,7 +871,7 @@ var config = {
|
||||
// customParticipantMenuButtons: [],
|
||||
|
||||
// An array with custom option buttons for the toolbar
|
||||
// type: Array<{ icon: string; id: string; text: string; }>
|
||||
// type: Array<{ icon: string; id: string; text: string; backgroundColor?: string; }>
|
||||
// customToolbarButtons: [],
|
||||
|
||||
// Stats
|
||||
@@ -1571,6 +1571,8 @@ var config = {
|
||||
// disableFilmstripAutohiding: false,
|
||||
|
||||
// filmstrip: {
|
||||
// // Disable the vertical/horizonal filmstrip.
|
||||
// disabled: false,
|
||||
// // Disables user resizable filmstrip. Also, allows configuration of the filmstrip
|
||||
// // (width, tiles aspect ratios) through the interfaceConfig options.
|
||||
// disableResizable: false,
|
||||
@@ -1593,6 +1595,8 @@ var config = {
|
||||
|
||||
// Tile view related config options.
|
||||
// tileView: {
|
||||
// // Whether tileview should be disabled.
|
||||
// disabled: false,
|
||||
// // The optimal number of tiles that are going to be shown in tile view. Depending on the screen size it may
|
||||
// // not be possible to show the exact number of participants specified here.
|
||||
// numberOfVisibleTiles: 25,
|
||||
|
||||
@@ -269,6 +269,8 @@
|
||||
"addMeetingNote": "Add a note about this meeting",
|
||||
"addOptionalNote": "Add a note (optional):",
|
||||
"allow": "Allow",
|
||||
"allowToggleCameraDialog": "Do you allow {{initiatorName}} to toggle your camera facing mode?",
|
||||
"allowToggleCameraTitle": "Allow toggle camera?",
|
||||
"alreadySharedVideoMsg": "Another participant is already sharing a video. This conference allows only one shared video at a time.",
|
||||
"alreadySharedVideoTitle": "Only one shared video is allowed at a time",
|
||||
"applicationWindow": "Application window",
|
||||
@@ -530,6 +532,7 @@
|
||||
"password": "$t(lockRoomPasswordUppercase): ",
|
||||
"reachedLimit": "You have reached the limit of your plan.",
|
||||
"sip": "SIP address",
|
||||
"sipAudioOnly": "SIP audio only address",
|
||||
"title": "Share",
|
||||
"tooltip": "Share link and dial-in info for this meeting",
|
||||
"upgradeOptions": "Please check the upgrade options on"
|
||||
|
||||
@@ -50,8 +50,8 @@ import {
|
||||
} from '../../react/features/base/participants/functions';
|
||||
import { updateSettings } from '../../react/features/base/settings/actions';
|
||||
import { getDisplayName } from '../../react/features/base/settings/functions.web';
|
||||
import { toggleCamera } from '../../react/features/base/tracks/actions.any';
|
||||
import { isToggleCameraEnabled } from '../../react/features/base/tracks/functions';
|
||||
import { setCameraFacingMode } from '../../react/features/base/tracks/actions.web';
|
||||
import { CAMERA_FACING_MODE_MESSAGE } from '../../react/features/base/tracks/constants';
|
||||
import {
|
||||
autoAssignToBreakoutRooms,
|
||||
closeBreakoutRoom,
|
||||
@@ -395,12 +395,8 @@ function initCommands() {
|
||||
sendAnalytics(createApiEvent('film.strip.resize'));
|
||||
APP.store.dispatch(resizeFilmStrip(options.width));
|
||||
},
|
||||
'toggle-camera': () => {
|
||||
if (!isToggleCameraEnabled(APP.store.getState())) {
|
||||
return;
|
||||
}
|
||||
|
||||
APP.store.dispatch(toggleCamera());
|
||||
'toggle-camera': facingMode => {
|
||||
APP.store.dispatch(setCameraFacingMode(facingMode));
|
||||
},
|
||||
'toggle-camera-mirror': () => {
|
||||
const state = APP.store.getState();
|
||||
@@ -529,6 +525,18 @@ function initCommands() {
|
||||
logger.error('Failed sending endpoint text message', err);
|
||||
}
|
||||
},
|
||||
'send-camera-facing-mode-message': (to, facingMode) => {
|
||||
if (!to) {
|
||||
logger.warn('Participant id not set');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
APP.conference.sendEndpointMessage(to, {
|
||||
name: CAMERA_FACING_MODE_MESSAGE,
|
||||
facingMode
|
||||
});
|
||||
},
|
||||
'overwrite-names': participantList => {
|
||||
logger.debug('Overwrite names command received');
|
||||
|
||||
@@ -1133,7 +1141,11 @@ class API {
|
||||
*/
|
||||
_sendEvent(event = {}) {
|
||||
if (this._enabled) {
|
||||
transport.sendEvent(event);
|
||||
try {
|
||||
transport.sendEvent(event);
|
||||
} catch (error) {
|
||||
logger.error('Failed to send and IFrame API event', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1467,11 +1479,43 @@ class API {
|
||||
* @param {Array<string>} args - Array of strings composing the log message.
|
||||
* @returns {void}
|
||||
*/
|
||||
notifyLog(logLevel, args) {
|
||||
notifyLog(logLevel, args = []) {
|
||||
if (!Array.isArray(args)) {
|
||||
logger.error('notifyLog received wrong argument types!');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Trying to convert arguments to strings. Otherwise in order to send the event the arguments will be formatted
|
||||
// with JSON.stringify which can throw an error because of circular objects and we will lose the whole log.
|
||||
const formattedArguments = [];
|
||||
|
||||
args.forEach(arg => {
|
||||
let formattedArgument = '';
|
||||
|
||||
if (arg instanceof Error) {
|
||||
formattedArgument += `${arg.toString()}: ${arg.stack}`;
|
||||
} else if (typeof arg === 'object') {
|
||||
// NOTE: The non-enumerable properties of the objects wouldn't be included in the string after
|
||||
// JSON.strigify. For example Map instance will be translated to '{}'. So I think we have to eventually
|
||||
// do something better for parsing the arguments. But since this option for strigify is part of the
|
||||
// public interface and I think it could be useful in some cases I will it for now.
|
||||
try {
|
||||
formattedArgument += JSON.stringify(arg);
|
||||
} catch (error) {
|
||||
formattedArgument += arg;
|
||||
}
|
||||
} else {
|
||||
formattedArgument += arg;
|
||||
}
|
||||
|
||||
formattedArguments.push(formattedArgument);
|
||||
});
|
||||
|
||||
this._sendEvent({
|
||||
name: 'log',
|
||||
logLevel,
|
||||
args
|
||||
args: formattedArguments
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
3
modules/API/external/external_api.js
vendored
3
modules/API/external/external_api.js
vendored
@@ -54,6 +54,7 @@ const commands = {
|
||||
removeBreakoutRoom: 'remove-breakout-room',
|
||||
resizeFilmStrip: 'resize-film-strip',
|
||||
resizeLargeVideo: 'resize-large-video',
|
||||
sendCameraFacingMode: 'send-camera-facing-mode-message',
|
||||
sendChatMessage: 'send-chat-message',
|
||||
sendEndpointTextMessage: 'send-endpoint-text-message',
|
||||
sendParticipantToRoom: 'send-participant-to-room',
|
||||
@@ -111,6 +112,7 @@ const events = {
|
||||
'data-channel-opened': 'dataChannelOpened',
|
||||
'device-list-changed': 'deviceListChanged',
|
||||
'display-name-change': 'displayNameChange',
|
||||
'dominant-speaker-changed': 'dominantSpeakerChanged',
|
||||
'email-change': 'emailChange',
|
||||
'error-occurred': 'errorOccurred',
|
||||
'endpoint-text-message-received': 'endpointTextMessageReceived',
|
||||
@@ -152,7 +154,6 @@ const events = {
|
||||
'video-mute-status-changed': 'videoMuteStatusChanged',
|
||||
'video-quality-changed': 'videoQualityChanged',
|
||||
'screen-sharing-status-changed': 'screenSharingStatusChanged',
|
||||
'dominant-speaker-changed': 'dominantSpeakerChanged',
|
||||
'subject-change': 'subjectChange',
|
||||
'suspend-detected': 'suspendDetected',
|
||||
'tile-view-changed': 'tileViewChanged',
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/* global APP, interfaceConfig */
|
||||
|
||||
/* eslint-disable no-unused-vars */
|
||||
import Logger from '@jitsi/logger';
|
||||
import $ from 'jquery';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
@@ -23,6 +24,8 @@ export const VIDEO_CONTAINER_TYPE = 'camera';
|
||||
// Corresponds to animation duration from the animatedFadeIn and animatedFadeOut CSS classes.
|
||||
const FADE_DURATION_MS = 300;
|
||||
|
||||
const logger = Logger.getLogger(__filename);
|
||||
|
||||
/**
|
||||
* Returns an array of the video dimensions, so that it keeps it's aspect
|
||||
* ratio and fits available area with it's larger dimension. This method
|
||||
@@ -489,7 +492,9 @@ export class VideoContainer extends LargeContainer {
|
||||
}
|
||||
|
||||
if (this.video) {
|
||||
stream.attach(this.video);
|
||||
stream.attach(this.video).catch(error => {
|
||||
logger.error(`Attaching the remote track ${stream} has failed with `, error);
|
||||
});
|
||||
|
||||
// Ensure large video gets play() called on it when a new stream is attached to it. This is necessary in the
|
||||
// case of Safari as autoplay doesn't kick-in automatically on Safari 15 and newer versions.
|
||||
|
||||
@@ -23,11 +23,8 @@ const VideoLayout = {
|
||||
/**
|
||||
* Handler for local flip X changed event.
|
||||
*/
|
||||
onLocalFlipXChanged() {
|
||||
onLocalFlipXChanged(localFlipX) {
|
||||
if (largeVideo) {
|
||||
const { store } = APP;
|
||||
const { localFlipX } = store.getState()['features/base/settings'];
|
||||
|
||||
largeVideo.onLocalFlipXChange(localFlipX);
|
||||
}
|
||||
},
|
||||
|
||||
90
package-lock.json
generated
90
package-lock.json
generated
@@ -16,10 +16,9 @@
|
||||
"@giphy/js-fetch-api": "4.7.1",
|
||||
"@giphy/react-components": "6.8.1",
|
||||
"@giphy/react-native-sdk": "2.3.0",
|
||||
"@hapi/bourne": "2.0.0",
|
||||
"@jitsi/excalidraw": "https://github.com/jitsi/excalidraw/releases/download/v0.0.14/jitsi-excalidraw-0.0.14.tgz",
|
||||
"@jitsi/js-utils": "2.0.6",
|
||||
"@jitsi/logger": "2.0.0",
|
||||
"@jitsi/js-utils": "2.1.3",
|
||||
"@jitsi/logger": "2.0.2",
|
||||
"@jitsi/rnnoise-wasm": "0.1.0",
|
||||
"@jitsi/rtcstats": "9.5.1",
|
||||
"@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.3.tgz",
|
||||
@@ -60,7 +59,7 @@
|
||||
"js-md5": "0.6.1",
|
||||
"js-sha512": "0.8.0",
|
||||
"jwt-decode": "2.2.0",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1672.0.0+cce452f8/lib-jitsi-meet.tgz",
|
||||
"lib-jitsi-meet": "https://git@github.com/jitsi/lib-jitsi-meet#886cb6bb0cab43b8b23378ace4a63128ffcf90d2",
|
||||
"lodash": "4.17.21",
|
||||
"moment": "2.29.4",
|
||||
"moment-duration-format": "2.2.2",
|
||||
@@ -2949,9 +2948,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@hapi/bourne": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@hapi/bourne/-/bourne-2.0.0.tgz",
|
||||
"integrity": "sha512-WEezM1FWztfbzqIUbsDzFRVMxSoLy3HugVcux6KDDtTqzPsLE8NDRHfXvev66aH1i2oOKKar3/XDjbvh/OUBdg=="
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@hapi/bourne/-/bourne-3.0.0.tgz",
|
||||
"integrity": "sha512-Waj1cwPXJDucOib4a3bAISsKJVb15MKi9IvmTI/7ssVEm6sywXGjVJDhl6/umt1pK1ZS7PacXU3A1PmFKHEZ2w=="
|
||||
},
|
||||
"node_modules/@hapi/hoek": {
|
||||
"version": "9.3.0",
|
||||
@@ -3110,10 +3109,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@jitsi/js-utils": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@jitsi/js-utils/-/js-utils-2.0.6.tgz",
|
||||
"integrity": "sha512-Z5cy1d8JmymAK5O0AOtNQFDqbKictcnafFzDTRxYK7Dcf62GSVlxoHizAhoUowilb+HcZC0W3YA3+NqLtMM//w==",
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@jitsi/js-utils/-/js-utils-2.1.3.tgz",
|
||||
"integrity": "sha512-fXoNLu2JHQGPgzjCDG5qWPPStiXI+AxSzu1JIVY4dMULIFsw4a+doMgcjTIlSfB393J2xccKQYwZSejZUn9MEw==",
|
||||
"dependencies": {
|
||||
"@hapi/bourne": "^3.0.0",
|
||||
"bowser": "2.7.0",
|
||||
"js-md5": "0.7.3"
|
||||
}
|
||||
@@ -3124,9 +3124,9 @@
|
||||
"integrity": "sha512-ZC41vPSTLKGwIRjqDh8DfXoCrdQIyBgspJVPXHBGu4nZlAEvG3nf+jO9avM9RmLiGakg7vz974ms99nEV0tmTQ=="
|
||||
},
|
||||
"node_modules/@jitsi/logger": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@jitsi/logger/-/logger-2.0.0.tgz",
|
||||
"integrity": "sha512-QZE0NpI/GKRdZK0vhuyFYWr4XkCz4slihkSfy6RTszjj/YEHZKIV7yGJo6Hbs3kYI2h5v7apoy+h2WCOMumPJw=="
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@jitsi/logger/-/logger-2.0.2.tgz",
|
||||
"integrity": "sha512-qwbpRwuwkBFgh0F5jivq/5fAm46yVoXURc5LCklEs8lAShYVangFEXKW7RLpZuZ5nQnrHrlvU8MswQNREmvahg=="
|
||||
},
|
||||
"node_modules/@jitsi/rnnoise-wasm": {
|
||||
"version": "0.1.0",
|
||||
@@ -12770,13 +12770,13 @@
|
||||
},
|
||||
"node_modules/lib-jitsi-meet": {
|
||||
"version": "0.0.0",
|
||||
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1672.0.0+cce452f8/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-5c67mHgImndnt6ZgpHWXXZOEat94xpl9d9Rf9gSppMXFAMDAcYQF0M5SEfkjfZpKbtjG8Ydf+xpVrjF3Cqd4/w==",
|
||||
"resolved": "git+https://git@github.com/jitsi/lib-jitsi-meet.git#886cb6bb0cab43b8b23378ace4a63128ffcf90d2",
|
||||
"integrity": "sha512-w5H6uBJ6P32J82rIOve0Vj1qhsqOELJGdH0Garl/tsLzs1IwDhATcVBd1i3IMQNOHDEwt98SGm+hlm7BDnF2Cg==",
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@jitsi/js-utils": "2.0.0",
|
||||
"@jitsi/logger": "2.0.0",
|
||||
"@jitsi/js-utils": "2.1.3",
|
||||
"@jitsi/logger": "2.0.2",
|
||||
"@jitsi/sdp-interop": "git+https://github.com/jitsi/sdp-interop#3d49eb4aa26863a3f8d32d7581cdb4321244266b",
|
||||
"@jitsi/sdp-simulcast": "0.4.0",
|
||||
"@testrtc/watchrtc-sdk": "1.36.3",
|
||||
@@ -12797,15 +12797,6 @@
|
||||
"webrtc-adapter": "8.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/lib-jitsi-meet/node_modules/@jitsi/js-utils": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@jitsi/js-utils/-/js-utils-2.0.0.tgz",
|
||||
"integrity": "sha512-Rk1JFGdXEJ5+eALVRTMohfn3pdMDQqlCJQEkCMLXKlCpEo+JhsOrB4KzlPo1rV9U8PnRfrf0j5N9uf/0C2a8Gw==",
|
||||
"dependencies": {
|
||||
"bowser": "2.7.0",
|
||||
"js-md5": "0.7.3"
|
||||
}
|
||||
},
|
||||
"node_modules/lib-jitsi-meet/node_modules/ansi-styles": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
@@ -12888,11 +12879,6 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/lib-jitsi-meet/node_modules/js-md5": {
|
||||
"version": "0.7.3",
|
||||
"resolved": "https://registry.npmjs.org/js-md5/-/js-md5-0.7.3.tgz",
|
||||
"integrity": "sha512-ZC41vPSTLKGwIRjqDh8DfXoCrdQIyBgspJVPXHBGu4nZlAEvG3nf+jO9avM9RmLiGakg7vz974ms99nEV0tmTQ=="
|
||||
},
|
||||
"node_modules/lib-jitsi-meet/node_modules/jsonfile": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
|
||||
@@ -21915,9 +21901,9 @@
|
||||
}
|
||||
},
|
||||
"@hapi/bourne": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@hapi/bourne/-/bourne-2.0.0.tgz",
|
||||
"integrity": "sha512-WEezM1FWztfbzqIUbsDzFRVMxSoLy3HugVcux6KDDtTqzPsLE8NDRHfXvev66aH1i2oOKKar3/XDjbvh/OUBdg=="
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@hapi/bourne/-/bourne-3.0.0.tgz",
|
||||
"integrity": "sha512-Waj1cwPXJDucOib4a3bAISsKJVb15MKi9IvmTI/7ssVEm6sywXGjVJDhl6/umt1pK1ZS7PacXU3A1PmFKHEZ2w=="
|
||||
},
|
||||
"@hapi/hoek": {
|
||||
"version": "9.3.0",
|
||||
@@ -22031,10 +22017,11 @@
|
||||
"integrity": "sha512-iK7p7i6qJFOkjTVZhWDvurDW1u+eMoOhAVgpab9CZEqCTX+W4Ih4AOPrUpf+mjaAHK5XqmQZSc5nVEpMg+xIGQ=="
|
||||
},
|
||||
"@jitsi/js-utils": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@jitsi/js-utils/-/js-utils-2.0.6.tgz",
|
||||
"integrity": "sha512-Z5cy1d8JmymAK5O0AOtNQFDqbKictcnafFzDTRxYK7Dcf62GSVlxoHizAhoUowilb+HcZC0W3YA3+NqLtMM//w==",
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@jitsi/js-utils/-/js-utils-2.1.3.tgz",
|
||||
"integrity": "sha512-fXoNLu2JHQGPgzjCDG5qWPPStiXI+AxSzu1JIVY4dMULIFsw4a+doMgcjTIlSfB393J2xccKQYwZSejZUn9MEw==",
|
||||
"requires": {
|
||||
"@hapi/bourne": "^3.0.0",
|
||||
"bowser": "2.7.0",
|
||||
"js-md5": "0.7.3"
|
||||
},
|
||||
@@ -22047,9 +22034,9 @@
|
||||
}
|
||||
},
|
||||
"@jitsi/logger": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@jitsi/logger/-/logger-2.0.0.tgz",
|
||||
"integrity": "sha512-QZE0NpI/GKRdZK0vhuyFYWr4XkCz4slihkSfy6RTszjj/YEHZKIV7yGJo6Hbs3kYI2h5v7apoy+h2WCOMumPJw=="
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@jitsi/logger/-/logger-2.0.2.tgz",
|
||||
"integrity": "sha512-qwbpRwuwkBFgh0F5jivq/5fAm46yVoXURc5LCklEs8lAShYVangFEXKW7RLpZuZ5nQnrHrlvU8MswQNREmvahg=="
|
||||
},
|
||||
"@jitsi/rnnoise-wasm": {
|
||||
"version": "0.1.0",
|
||||
@@ -29289,11 +29276,12 @@
|
||||
}
|
||||
},
|
||||
"lib-jitsi-meet": {
|
||||
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1672.0.0+cce452f8/lib-jitsi-meet.tgz",
|
||||
"integrity": "sha512-5c67mHgImndnt6ZgpHWXXZOEat94xpl9d9Rf9gSppMXFAMDAcYQF0M5SEfkjfZpKbtjG8Ydf+xpVrjF3Cqd4/w==",
|
||||
"version": "git+https://git@github.com/jitsi/lib-jitsi-meet.git#886cb6bb0cab43b8b23378ace4a63128ffcf90d2",
|
||||
"integrity": "sha512-w5H6uBJ6P32J82rIOve0Vj1qhsqOELJGdH0Garl/tsLzs1IwDhATcVBd1i3IMQNOHDEwt98SGm+hlm7BDnF2Cg==",
|
||||
"from": "lib-jitsi-meet@https://git@github.com/jitsi/lib-jitsi-meet#886cb6bb0cab43b8b23378ace4a63128ffcf90d2",
|
||||
"requires": {
|
||||
"@jitsi/js-utils": "2.0.0",
|
||||
"@jitsi/logger": "2.0.0",
|
||||
"@jitsi/js-utils": "2.1.3",
|
||||
"@jitsi/logger": "2.0.2",
|
||||
"@jitsi/sdp-interop": "git+https://github.com/jitsi/sdp-interop#3d49eb4aa26863a3f8d32d7581cdb4321244266b",
|
||||
"@jitsi/sdp-simulcast": "0.4.0",
|
||||
"@testrtc/watchrtc-sdk": "1.36.3",
|
||||
@@ -29314,15 +29302,6 @@
|
||||
"webrtc-adapter": "8.1.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@jitsi/js-utils": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@jitsi/js-utils/-/js-utils-2.0.0.tgz",
|
||||
"integrity": "sha512-Rk1JFGdXEJ5+eALVRTMohfn3pdMDQqlCJQEkCMLXKlCpEo+JhsOrB4KzlPo1rV9U8PnRfrf0j5N9uf/0C2a8Gw==",
|
||||
"requires": {
|
||||
"bowser": "2.7.0",
|
||||
"js-md5": "0.7.3"
|
||||
}
|
||||
},
|
||||
"ansi-styles": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
@@ -29381,11 +29360,6 @@
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="
|
||||
},
|
||||
"js-md5": {
|
||||
"version": "0.7.3",
|
||||
"resolved": "https://registry.npmjs.org/js-md5/-/js-md5-0.7.3.tgz",
|
||||
"integrity": "sha512-ZC41vPSTLKGwIRjqDh8DfXoCrdQIyBgspJVPXHBGu4nZlAEvG3nf+jO9avM9RmLiGakg7vz974ms99nEV0tmTQ=="
|
||||
},
|
||||
"jsonfile": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
|
||||
|
||||
@@ -22,10 +22,9 @@
|
||||
"@giphy/js-fetch-api": "4.7.1",
|
||||
"@giphy/react-components": "6.8.1",
|
||||
"@giphy/react-native-sdk": "2.3.0",
|
||||
"@hapi/bourne": "2.0.0",
|
||||
"@jitsi/excalidraw": "https://github.com/jitsi/excalidraw/releases/download/v0.0.14/jitsi-excalidraw-0.0.14.tgz",
|
||||
"@jitsi/js-utils": "2.0.6",
|
||||
"@jitsi/logger": "2.0.0",
|
||||
"@jitsi/js-utils": "2.1.3",
|
||||
"@jitsi/logger": "2.0.2",
|
||||
"@jitsi/rnnoise-wasm": "0.1.0",
|
||||
"@jitsi/rtcstats": "9.5.1",
|
||||
"@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.3.tgz",
|
||||
@@ -66,7 +65,7 @@
|
||||
"js-md5": "0.6.1",
|
||||
"js-sha512": "0.8.0",
|
||||
"jwt-decode": "2.2.0",
|
||||
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1672.0.0+cce452f8/lib-jitsi-meet.tgz",
|
||||
"lib-jitsi-meet": "https://git@github.com/jitsi/lib-jitsi-meet#886cb6bb0cab43b8b23378ace4a63128ffcf90d2",
|
||||
"lodash": "4.17.21",
|
||||
"moment": "2.29.4",
|
||||
"moment-duration-format": "2.2.2",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
type ToolbarButtons = 'camera' |
|
||||
export type ToolbarButton = 'camera' |
|
||||
'chat' |
|
||||
'closedcaptions' |
|
||||
'desktop' |
|
||||
@@ -15,6 +15,9 @@ type ToolbarButtons = 'camera' |
|
||||
'linktosalesforce' |
|
||||
'livestreaming' |
|
||||
'microphone' |
|
||||
'mute-everyone' |
|
||||
'mute-video-everyone' |
|
||||
'noisesuppression' |
|
||||
'participants-pane' |
|
||||
'profile' |
|
||||
'raisehand' |
|
||||
@@ -30,6 +33,7 @@ type ToolbarButtons = 'camera' |
|
||||
'tileview' |
|
||||
'toggle-camera' |
|
||||
'videoquality' |
|
||||
'whiteboard' |
|
||||
'__end';
|
||||
|
||||
type ButtonsWithNotifyClick = 'camera' |
|
||||
@@ -270,7 +274,7 @@ export interface IConfig {
|
||||
};
|
||||
corsAvatarURLs?: Array<string>;
|
||||
customParticipantMenuButtons?: Array<{ icon: string; id: string; text: string; }>;
|
||||
customToolbarButtons?: Array<{ icon: string; id: string; text: string; }>;
|
||||
customToolbarButtons?: Array<{ backgroundColor?: string; icon: string; id: string; text: string; }>;
|
||||
deeplinking?: IDeeplinkingConfig;
|
||||
defaultLanguage?: string;
|
||||
defaultLocalDisplayName?: string;
|
||||
@@ -397,6 +401,7 @@ export interface IConfig {
|
||||
disableResizable?: boolean;
|
||||
disableStageFilmstrip?: boolean;
|
||||
disableTopPanel?: boolean;
|
||||
disabled?: boolean;
|
||||
minParticipantCountForTopPanel?: number;
|
||||
};
|
||||
firefox_fake_device?: string;
|
||||
@@ -573,12 +578,13 @@ export interface IConfig {
|
||||
testMode?: boolean;
|
||||
};
|
||||
tileView?: {
|
||||
disabled?: boolean;
|
||||
numberOfVisibleTiles?: number;
|
||||
};
|
||||
tokenAuthUrl?: string;
|
||||
tokenAuthUrlAutoRedirect?: string;
|
||||
tokenLogoutUrl?: string;
|
||||
toolbarButtons?: Array<ToolbarButtons>;
|
||||
toolbarButtons?: Array<ToolbarButton>;
|
||||
toolbarConfig?: {
|
||||
alwaysVisible?: boolean;
|
||||
autoHideWhileChatIsOpen?: boolean;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { ToolbarButton } from './configType';
|
||||
|
||||
/**
|
||||
* The prefix of the {@code localStorage} key into which {@link storeConfig}
|
||||
* stores and from which {@link restoreConfig} restores.
|
||||
@@ -13,7 +15,7 @@ export const _CONFIG_STORE_PREFIX = 'config.js';
|
||||
* @protected
|
||||
* @type Array<string>
|
||||
*/
|
||||
export const TOOLBAR_BUTTONS = [
|
||||
export const TOOLBAR_BUTTONS: ToolbarButton[] = [
|
||||
'camera',
|
||||
'chat',
|
||||
'closedcaptions',
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
// @ts-expect-error
|
||||
import Bourne from '@hapi/bourne';
|
||||
// eslint-disable-next-line lines-around-comment
|
||||
// @ts-expect-error
|
||||
// @ts-ignore
|
||||
import { jitsiLocalStorage } from '@jitsi/js-utils';
|
||||
// eslint-disable-next-line lines-around-comment
|
||||
// @ts-ignore
|
||||
import { safeJsonParse } from '@jitsi/js-utils/json';
|
||||
import _ from 'lodash';
|
||||
|
||||
import { IReduxState } from '../../app/types';
|
||||
@@ -223,7 +223,7 @@ export function restoreConfig(baseURL: string) {
|
||||
|
||||
if (config) {
|
||||
try {
|
||||
return Bourne.parse(config) || undefined;
|
||||
return safeJsonParse(config) || undefined;
|
||||
} catch (e) {
|
||||
// Somehow incorrect data ended up in the storage. Clean it up.
|
||||
jitsiLocalStorage.removeItem(key);
|
||||
|
||||
@@ -7,7 +7,8 @@ import {
|
||||
IDeeplinkingConfig,
|
||||
IDeeplinkingMobileConfig,
|
||||
IDeeplinkingPlatformConfig,
|
||||
NotifyClickButton
|
||||
NotifyClickButton,
|
||||
ToolbarButton
|
||||
} from './configType';
|
||||
import { TOOLBAR_BUTTONS } from './constants';
|
||||
|
||||
@@ -46,7 +47,7 @@ export function getToolbarButtons(state: IReduxState): Array<string> {
|
||||
const buttons = Array.isArray(toolbarButtons) ? toolbarButtons : TOOLBAR_BUTTONS;
|
||||
|
||||
if (customButtons) {
|
||||
buttons.push(...customButtons);
|
||||
buttons.push(...customButtons as ToolbarButton[]);
|
||||
}
|
||||
|
||||
return buttons;
|
||||
|
||||
@@ -16,8 +16,10 @@ import {
|
||||
IDeeplinkingConfig,
|
||||
IDeeplinkingMobileConfig,
|
||||
IDeeplinkingPlatformConfig,
|
||||
IMobileDynamicLink
|
||||
IMobileDynamicLink,
|
||||
ToolbarButton
|
||||
} from './configType';
|
||||
import { TOOLBAR_BUTTONS } from './constants';
|
||||
import { _cleanupConfig, _setDeeplinkingDefaults } from './functions';
|
||||
|
||||
/**
|
||||
@@ -546,6 +548,11 @@ function _translateLegacyConfig(oldValue: IConfig) {
|
||||
};
|
||||
}
|
||||
|
||||
if (oldValue.disableProfile) {
|
||||
newValue.toolbarButtons = (newValue.toolbarButtons || TOOLBAR_BUTTONS)
|
||||
.filter((button: ToolbarButton) => button !== 'profile');
|
||||
}
|
||||
|
||||
_setDeeplinkingDefaults(newValue.deeplinking as IDeeplinkingConfig);
|
||||
|
||||
return newValue;
|
||||
|
||||
@@ -9,10 +9,12 @@ import { NOTIFICATION_TIMEOUT_TYPE } from '../../notifications/constants';
|
||||
import { replaceAudioTrackById, replaceVideoTrackById, setDeviceStatusWarning } from '../../prejoin/actions';
|
||||
import { isPrejoinPageVisible } from '../../prejoin/functions';
|
||||
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../app/actionTypes';
|
||||
import { isMobileBrowser } from '../environment/utils';
|
||||
import JitsiMeetJS, { JitsiMediaDevicesEvents, JitsiTrackErrors } from '../lib-jitsi-meet';
|
||||
import { MEDIA_TYPE } from '../media/constants';
|
||||
import MiddlewareRegistry from '../redux/MiddlewareRegistry';
|
||||
import { updateSettings } from '../settings/actions';
|
||||
|
||||
import { getLocalTrack } from '../tracks/functions';
|
||||
|
||||
import {
|
||||
CHECK_AND_NOTIFY_FOR_NEW_DEVICE,
|
||||
@@ -182,13 +184,20 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
APP.UI.emitEvent(UIEvents.AUDIO_DEVICE_CHANGED, action.deviceId);
|
||||
}
|
||||
break;
|
||||
case SET_VIDEO_INPUT_DEVICE:
|
||||
case SET_VIDEO_INPUT_DEVICE: {
|
||||
const localTrack = getLocalTrack(store.getState()['features/base/tracks'], MEDIA_TYPE.VIDEO);
|
||||
|
||||
// on mobile devices the video stream has to be stopped before replacing it
|
||||
if (isMobileBrowser() && localTrack && !localTrack.muted) {
|
||||
localTrack.jitsiTrack.stopStream();
|
||||
}
|
||||
if (isPrejoinPageVisible(store.getState())) {
|
||||
store.dispatch(replaceVideoTrackById(action.deviceId));
|
||||
} else {
|
||||
APP.UI.emitEvent(UIEvents.VIDEO_DEVICE_CHANGED, action.deviceId);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case UPDATE_DEVICE_LIST:
|
||||
logDeviceList(groupDevicesByKind(action.devices));
|
||||
if (areDeviceLabelsInitialized(store.getState())) {
|
||||
|
||||
@@ -18,3 +18,4 @@ export function isMobileBrowser() {
|
||||
export function isIosMobileBrowser() {
|
||||
return Platform.OS === 'ios';
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
// @ts-expect-error
|
||||
import Bourne from '@hapi/bourne';
|
||||
// eslint-disable-next-line lines-around-comment
|
||||
// @ts-expect-error
|
||||
// @ts-ignore
|
||||
import { jitsiLocalStorage } from '@jitsi/js-utils/jitsi-local-storage';
|
||||
// eslint-disable-next-line lines-around-comment
|
||||
// @ts-ignore
|
||||
import { safeJsonParse } from '@jitsi/js-utils/json';
|
||||
|
||||
import { browser } from '../lib-jitsi-meet';
|
||||
import { inIframe } from '../util/iframeUtils';
|
||||
@@ -17,6 +17,8 @@ import logger from './logger';
|
||||
* @returns {void}
|
||||
*/
|
||||
function onFakeLocalStorageChanged() {
|
||||
console.error(jitsiLocalStorage.serialize([ 'jitsiLocalStorage' ]));
|
||||
|
||||
APP.API.notifyLocalStorageChanged(jitsiLocalStorage.serialize([ 'jitsiLocalStorage' ]));
|
||||
}
|
||||
|
||||
@@ -61,7 +63,14 @@ function setupJitsiLocalStorage() {
|
||||
|
||||
if (shouldUseHostPageLocalStorage(urlParams)) {
|
||||
try {
|
||||
const localStorageContent = Bourne.parse(urlParams['appData.localStorageContent']);
|
||||
const localStorageContent = safeJsonParse(urlParams['appData.localStorageContent']);
|
||||
|
||||
// We need to disable the local storage before setting the data in case the browser local storage doesn't
|
||||
// throw exception (in some cases when this happens the local storage may be cleared for every session.
|
||||
// Example: when loading meet from cross-domain with the IFrame API with Brave with the default
|
||||
// configuration). Otherwise we will set the data in the browser local storage and then switch to the dummy
|
||||
// local storage from jitsiLocalStorage and we will loose the data.
|
||||
jitsiLocalStorage.setLocalStorageDisabled(true);
|
||||
|
||||
if (typeof localStorageContent === 'object') {
|
||||
Object.keys(localStorageContent).forEach(key => {
|
||||
@@ -72,7 +81,6 @@ function setupJitsiLocalStorage() {
|
||||
logger.error('Can\'t parse localStorageContent.', error);
|
||||
}
|
||||
|
||||
jitsiLocalStorage.setLocalStorageDisabled(true);
|
||||
jitsiLocalStorage.on('changed', onFakeLocalStorageChanged);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// @ts-expect-error
|
||||
import Bourne from '@hapi/bourne';
|
||||
// @ts-ignore
|
||||
import { safeJsonParse } from '@jitsi/js-utils/json';
|
||||
import { NativeModules } from 'react-native';
|
||||
|
||||
import { loadScript } from '../util/loadScript.native';
|
||||
@@ -20,7 +20,7 @@ export async function loadConfig(url: string): Promise<Object> {
|
||||
try {
|
||||
const configTxt = await loadScript(url, 10 * 1000 /* Timeout in ms */, true /* skipeval */);
|
||||
const configJson = await JavaScriptSandbox.evaluate(`${configTxt}\nJSON.stringify(config);`);
|
||||
const config = Bourne.parse(configJson);
|
||||
const config = safeJsonParse(configJson);
|
||||
|
||||
if (typeof config !== 'object') {
|
||||
throw new Error('config is not an object');
|
||||
|
||||
@@ -120,9 +120,7 @@ export default class JitsiMeetLogStorage {
|
||||
conference.sendApplicationLog(logMessage);
|
||||
} catch (error) {
|
||||
// NOTE console is intentional here
|
||||
console.error(
|
||||
`Failed to store the logs, msg length: ${logMessage.length}`
|
||||
+ `error: ${JSON.stringify(error)}`);
|
||||
console.error(`Failed to store the logs, msg length: ${logMessage.length} error:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ class AudioTrack extends Component<IProps> {
|
||||
/**
|
||||
* Reference to the HTML audio element, stored until the file is ready.
|
||||
*/
|
||||
_ref: HTMLAudioElement | null;
|
||||
_ref: React.RefObject<HTMLAudioElement>;
|
||||
|
||||
/**
|
||||
* The current timeout ID for play() retries.
|
||||
@@ -80,7 +80,7 @@ class AudioTrack extends Component<IProps> {
|
||||
|
||||
// Bind event handlers so they are only bound once for every instance.
|
||||
this._errorHandler = this._errorHandler.bind(this);
|
||||
this._setRef = this._setRef.bind(this);
|
||||
this._ref = React.createRef();
|
||||
this._play = this._play.bind(this);
|
||||
}
|
||||
|
||||
@@ -94,19 +94,22 @@ class AudioTrack extends Component<IProps> {
|
||||
componentDidMount() {
|
||||
this._attachTrack(this.props.audioTrack);
|
||||
|
||||
if (this._ref) {
|
||||
if (this._ref?.current) {
|
||||
const audio = this._ref?.current;
|
||||
const { _muted, _volume } = this.props;
|
||||
|
||||
if (typeof _volume === 'number') {
|
||||
this._ref.volume = _volume;
|
||||
audio.volume = _volume;
|
||||
}
|
||||
|
||||
if (typeof _muted === 'boolean') {
|
||||
this._ref.muted = _muted;
|
||||
audio.muted = _muted;
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
this._ref.addEventListener('error', this._errorHandler);
|
||||
audio.addEventListener('error', this._errorHandler);
|
||||
} else { // This should never happen
|
||||
logger.error(`The react reference is null for AudioTrack ${this.props?.id}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,7 +124,7 @@ class AudioTrack extends Component<IProps> {
|
||||
this._detachTrack(this.props.audioTrack);
|
||||
|
||||
// @ts-ignore
|
||||
this._ref?.removeEventListener('error', this._errorHandler);
|
||||
this._ref?.current?.removeEventListener('error', this._errorHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -141,19 +144,25 @@ class AudioTrack extends Component<IProps> {
|
||||
this._attachTrack(nextProps.audioTrack);
|
||||
}
|
||||
|
||||
if (this._ref) {
|
||||
const currentVolume = this._ref.volume;
|
||||
if (this._ref?.current) {
|
||||
const audio = this._ref?.current;
|
||||
const currentVolume = audio.volume;
|
||||
const nextVolume = nextProps._volume;
|
||||
|
||||
if (typeof nextVolume === 'number' && !isNaN(nextVolume) && currentVolume !== nextVolume) {
|
||||
this._ref.volume = nextVolume;
|
||||
if (nextVolume === 0) {
|
||||
logger.debug(`Setting audio element ${nextProps?.id} volume to 0`);
|
||||
}
|
||||
audio.volume = nextVolume;
|
||||
}
|
||||
|
||||
const currentMuted = this._ref.muted;
|
||||
const currentMuted = audio.muted;
|
||||
const nextMuted = nextProps._muted;
|
||||
|
||||
if (typeof nextMuted === 'boolean' && currentMuted !== nextMuted) {
|
||||
this._ref.muted = nextMuted;
|
||||
logger.debug(`Setting audio element ${nextProps?.id} muted to true`);
|
||||
|
||||
audio.muted = nextMuted;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,7 +182,7 @@ class AudioTrack extends Component<IProps> {
|
||||
<audio
|
||||
autoPlay = { autoPlay }
|
||||
id = { id }
|
||||
ref = { this._setRef } />
|
||||
ref = { this._ref } />
|
||||
);
|
||||
}
|
||||
|
||||
@@ -185,12 +194,29 @@ class AudioTrack extends Component<IProps> {
|
||||
* @returns {void}
|
||||
*/
|
||||
_attachTrack(track?: ITrack) {
|
||||
const { id } = this.props;
|
||||
|
||||
if (!track?.jitsiTrack) {
|
||||
logger.warn(`Attach is called on audio element ${id} without tracks passed!`);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
track.jitsiTrack.attach(this._ref);
|
||||
this._play();
|
||||
if (!this._ref?.current) {
|
||||
logger.warn(`Attempting to attach track ${track?.jitsiTrack} on AudioTrack ${id} without reference!`);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
track.jitsiTrack.attach(this._ref.current)
|
||||
.catch((error: Error) => {
|
||||
logger.error(
|
||||
`Attaching the remote track ${track.jitsiTrack} to video with id ${id} has failed with `,
|
||||
error);
|
||||
})
|
||||
.finally(() => {
|
||||
this._play();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -202,10 +228,10 @@ class AudioTrack extends Component<IProps> {
|
||||
* @returns {void}
|
||||
*/
|
||||
_detachTrack(track?: ITrack) {
|
||||
if (this._ref && track && track.jitsiTrack) {
|
||||
if (this._ref?.current && track && track.jitsiTrack) {
|
||||
clearTimeout(this._playTimeout);
|
||||
this._playTimeout = undefined;
|
||||
track.jitsiTrack.detach(this._ref);
|
||||
track.jitsiTrack.detach(this._ref.current);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -229,18 +255,20 @@ class AudioTrack extends Component<IProps> {
|
||||
* @returns {void}
|
||||
*/
|
||||
_play(retries = 0) {
|
||||
if (!this._ref) {
|
||||
const { autoPlay, id } = this.props;
|
||||
|
||||
if (!this._ref?.current) {
|
||||
// nothing to play.
|
||||
logger.warn(`Attempting to call play on AudioTrack ${id} without reference!`);
|
||||
|
||||
return;
|
||||
}
|
||||
const { autoPlay, id } = this.props;
|
||||
|
||||
if (autoPlay) {
|
||||
// Ensure the audio gets play() called on it. This may be necessary in the
|
||||
// case where the local video container was moved and re-attached, in which
|
||||
// case the audio may not autoplay.
|
||||
this._ref.play()
|
||||
this._ref.current.play()
|
||||
.then(() => {
|
||||
if (retries !== 0) {
|
||||
// success after some failures
|
||||
@@ -249,7 +277,7 @@ class AudioTrack extends Component<IProps> {
|
||||
logger.info(`Successfully played audio track! retries: ${retries}`);
|
||||
}
|
||||
}, e => {
|
||||
logger.error(`Failed to play audio track! retry: ${retries} ; Error: ${e}`);
|
||||
logger.error(`Failed to play audio track on audio element ${id}! retry: ${retries} ; Error:`, e);
|
||||
|
||||
if (retries < 3) {
|
||||
this._playTimeout = window.setTimeout(() => this._play(retries + 1), 1000);
|
||||
@@ -264,17 +292,6 @@ class AudioTrack extends Component<IProps> {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the reference to the HTML audio element.
|
||||
*
|
||||
* @param {HTMLAudioElement} audioElement - The HTML audio element instance.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_setRef(audioElement: HTMLAudioElement | null) {
|
||||
this._ref = audioElement;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { Component, ReactEventHandler } from 'react';
|
||||
|
||||
import { ITrack } from '../../../tracks/types';
|
||||
import logger from '../../logger';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link Video}.
|
||||
@@ -227,13 +228,13 @@ class Video extends Component<IProps> {
|
||||
this._videoElement.onplaying = this._onVideoPlaying;
|
||||
}
|
||||
|
||||
this._attachTrack(this.props.videoTrack);
|
||||
this._attachTrack(this.props.videoTrack).finally(() => {
|
||||
if (this._videoElement && this.props.autoPlay) {
|
||||
// Ensure the video gets play() called on it. This may be necessary in the
|
||||
// case where the local video container was moved and re-attached, in which
|
||||
// case video does not autoplay.
|
||||
|
||||
if (this._videoElement && this.props.autoPlay) {
|
||||
// Ensure the video gets play() called on it. This may be necessary in the
|
||||
// case where the local video container was moved and re-attached, in which
|
||||
// case video does not autoplay.
|
||||
this._videoElement.play()
|
||||
this._videoElement.play()
|
||||
.catch(error => {
|
||||
// Prevent uncaught "DOMException: The play() request was interrupted by a new load request"
|
||||
// when video playback takes long to start and it starts after the component was unmounted.
|
||||
@@ -241,7 +242,8 @@ class Video extends Component<IProps> {
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -271,7 +273,9 @@ class Video extends Component<IProps> {
|
||||
|
||||
if (currentJitsiTrack !== nextJitsiTrack) {
|
||||
this._detachTrack(this.props.videoTrack);
|
||||
this._attachTrack(nextProps.videoTrack);
|
||||
this._attachTrack(nextProps.videoTrack).catch((_error: Error) => {
|
||||
// Ignore the error. We are already logging it.
|
||||
});
|
||||
}
|
||||
|
||||
if (this.props.style !== nextProps.style || this.props.className !== nextProps.className) {
|
||||
@@ -321,11 +325,22 @@ class Video extends Component<IProps> {
|
||||
* @returns {void}
|
||||
*/
|
||||
_attachTrack(videoTrack?: Partial<ITrack>) {
|
||||
const { id } = this.props;
|
||||
|
||||
if (!videoTrack?.jitsiTrack) {
|
||||
return;
|
||||
logger.warn(`Attach is called on video element ${id} without tracks passed!`);
|
||||
|
||||
// returning Promise.resolve just keep the previous logic.
|
||||
// TODO: Check if it make sense to call play on this element or we can just return promise.reject().
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
videoTrack.jitsiTrack.attach(this._videoElement);
|
||||
return videoTrack.jitsiTrack.attach(this._videoElement)
|
||||
.catch((error: Error) => {
|
||||
logger.error(
|
||||
`Attaching the remote track ${videoTrack.jitsiTrack} to video with id ${id} has failed with `,
|
||||
error);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
// @ts-expect-error
|
||||
import Bourne from '@hapi/bourne';
|
||||
// eslint-disable-next-line lines-around-comment
|
||||
// @ts-expect-error
|
||||
// @ts-ignore
|
||||
import { jitsiLocalStorage } from '@jitsi/js-utils';
|
||||
// eslint-disable-next-line lines-around-comment
|
||||
// @ts-ignore
|
||||
import { safeJsonParse } from '@jitsi/js-utils/json';
|
||||
import md5 from 'js-md5';
|
||||
|
||||
import logger from './logger';
|
||||
@@ -193,7 +193,7 @@ class PersistenceRegistry {
|
||||
|
||||
if (persistedSubtree) {
|
||||
try {
|
||||
persistedSubtree = Bourne.parse(persistedSubtree);
|
||||
persistedSubtree = safeJsonParse(persistedSubtree);
|
||||
|
||||
const filteredSubtree
|
||||
= this._getFilteredSubtree(persistedSubtree, subtreeConfig);
|
||||
|
||||
@@ -16,6 +16,11 @@ export interface IProps extends WithTranslation {
|
||||
*/
|
||||
afterClick?: Function;
|
||||
|
||||
/**
|
||||
* The button's background color.
|
||||
*/
|
||||
backgroundColor?: string;
|
||||
|
||||
/**
|
||||
* The button's key.
|
||||
*/
|
||||
@@ -108,6 +113,13 @@ export default class AbstractButton<P extends IProps, S=any> extends Component<P
|
||||
visible: true
|
||||
};
|
||||
|
||||
/**
|
||||
* The button's background color.
|
||||
*
|
||||
* @abstract
|
||||
*/
|
||||
backgroundColor?: string;
|
||||
|
||||
/**
|
||||
* A succinct description of what the button does. Used by accessibility
|
||||
* tools and torture tests.
|
||||
|
||||
@@ -9,6 +9,11 @@ import type { IProps as AbstractToolboxItemProps } from './AbstractToolboxItem';
|
||||
|
||||
interface IProps extends AbstractToolboxItemProps {
|
||||
|
||||
/**
|
||||
* The button's background color.
|
||||
*/
|
||||
backgroundColor?: string;
|
||||
|
||||
/**
|
||||
* Whether or not the item is displayed in a context menu.
|
||||
*/
|
||||
@@ -60,6 +65,7 @@ export default class ToolboxItem extends AbstractToolboxItem<IProps> {
|
||||
*/
|
||||
_renderItem() {
|
||||
const {
|
||||
backgroundColor,
|
||||
contextMenu,
|
||||
disabled,
|
||||
elementAfter,
|
||||
@@ -90,6 +96,7 @@ export default class ToolboxItem extends AbstractToolboxItem<IProps> {
|
||||
return (
|
||||
<ContextMenuItem
|
||||
accessibilityLabel = { this.accessibilityLabel }
|
||||
backgroundColor = { backgroundColor }
|
||||
disabled = { disabled }
|
||||
icon = { icon }
|
||||
onClick = { onClick }
|
||||
@@ -128,14 +135,18 @@ export default class ToolboxItem extends AbstractToolboxItem<IProps> {
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
_renderIcon() {
|
||||
const { customClass, disabled, icon, showLabel, toggled } = this.props;
|
||||
const { backgroundColor, customClass, disabled, icon, showLabel, toggled } = this.props;
|
||||
const iconComponent = (<Icon
|
||||
size = { showLabel ? undefined : 24 }
|
||||
src = { icon } />);
|
||||
const elementType = showLabel ? 'span' : 'div';
|
||||
const className = `${showLabel ? 'overflow-menu-item-icon' : 'toolbox-icon'} ${
|
||||
toggled ? 'toggled' : ''} ${disabled ? 'disabled' : ''} ${customClass ?? ''}`;
|
||||
const style = backgroundColor && !showLabel ? { backgroundColor } : {};
|
||||
|
||||
return React.createElement(elementType, { className }, iconComponent);
|
||||
return React.createElement(elementType, {
|
||||
className,
|
||||
style
|
||||
}, iconComponent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,18 +13,23 @@ import { toggleScreenshotCaptureSummary } from '../../screenshot-capture/actions
|
||||
import { isScreenshotCaptureEnabled } from '../../screenshot-capture/functions';
|
||||
import { AudioMixerEffect } from '../../stream-effects/audio-mixer/AudioMixerEffect';
|
||||
import { getCurrentConference } from '../conference/functions';
|
||||
import { openDialog } from '../dialog/actions';
|
||||
import { JitsiTrackErrors, JitsiTrackEvents } from '../lib-jitsi-meet';
|
||||
import { setScreenshareMuted } from '../media/actions';
|
||||
import { MEDIA_TYPE, VIDEO_TYPE } from '../media/constants';
|
||||
|
||||
import {
|
||||
addLocalTrack,
|
||||
replaceLocalTrack
|
||||
replaceLocalTrack,
|
||||
toggleCamera
|
||||
} from './actions.any';
|
||||
import AllowToggleCameraDialog from './components/web/AllowToggleCameraDialog';
|
||||
import {
|
||||
createLocalTracksF,
|
||||
getLocalDesktopTrack,
|
||||
getLocalJitsiAudioTrack
|
||||
getLocalJitsiAudioTrack,
|
||||
getLocalVideoTrack,
|
||||
isToggleCameraEnabled
|
||||
} from './functions';
|
||||
import { IShareOptions, IToggleScreenSharingOptions } from './types';
|
||||
|
||||
@@ -263,3 +268,52 @@ async function _toggleScreenSharing(
|
||||
APP.API.notifyScreenSharingStatusChanged(enable, screensharingDetails);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the camera facing mode(environment/user). If facing mode not provided, it will do a toggle.
|
||||
*
|
||||
* @param {string | undefined} facingMode - The selected facing mode.
|
||||
* @returns {void}
|
||||
*/
|
||||
export function setCameraFacingMode(facingMode: string | undefined) {
|
||||
return async (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||
const state = getState();
|
||||
|
||||
if (!isToggleCameraEnabled(state)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!facingMode) {
|
||||
dispatch(toggleCamera());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const tracks = state['features/base/tracks'];
|
||||
const localVideoTrack = getLocalVideoTrack(tracks)?.jitsiTrack;
|
||||
|
||||
if (!tracks || !localVideoTrack) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentFacingMode = localVideoTrack.getCameraFacingMode();
|
||||
|
||||
if (currentFacingMode !== facingMode) {
|
||||
dispatch(toggleCamera());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Signals to open the permission dialog for toggling camera remotely.
|
||||
*
|
||||
* @param {Function} onAllow - Callback to be executed if permission to toggle camera was granted.
|
||||
* @param {string} initiatorId - The participant id of the requester.
|
||||
* @returns {Object} - The open dialog action.
|
||||
*/
|
||||
export function openAllowToggleCameraDialog(onAllow: Function, initiatorId: string) {
|
||||
return openDialog(AllowToggleCameraDialog, {
|
||||
onAllow,
|
||||
initiatorId
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import React from 'react';
|
||||
import { WithTranslation } from 'react-i18next';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { IReduxState } from '../../../../app/types';
|
||||
import { translate } from '../../../i18n/functions';
|
||||
import { getParticipantDisplayName } from '../../../participants/functions';
|
||||
import Dialog from '../../../ui/components/web/Dialog';
|
||||
|
||||
|
||||
interface IProps extends WithTranslation {
|
||||
|
||||
/**
|
||||
* The participant id of the toggle camera requester.
|
||||
*/
|
||||
initiatorId: string;
|
||||
|
||||
/**
|
||||
* Function to be invoked after permission to toggle camera granted.
|
||||
*/
|
||||
onAllow: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dialog to allow toggling camera remotely.
|
||||
*
|
||||
* @returns {JSX.Element} - The allow toggle camera dialog.
|
||||
*/
|
||||
const AllowToggleCameraDialog = ({ onAllow, t, initiatorId }: IProps): JSX.Element => {
|
||||
const initiatorName = useSelector((state: IReduxState) => getParticipantDisplayName(state, initiatorId));
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
ok = {{ translationKey: 'dialog.allow' }}
|
||||
onSubmit = { onAllow }
|
||||
titleKey = 'dialog.allowToggleCameraTitle'>
|
||||
<div>
|
||||
{ t('dialog.allowToggleCameraDialog', { initiatorName }) }
|
||||
</div>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default translate(AllowToggleCameraDialog);
|
||||
4
react/features/base/tracks/constants.ts
Normal file
4
react/features/base/tracks/constants.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* The payload name for remotely setting the camera facing mode message.
|
||||
*/
|
||||
export const CAMERA_FACING_MODE_MESSAGE = 'camera-facing-mode-message';
|
||||
@@ -16,6 +16,11 @@ export interface IProps {
|
||||
*/
|
||||
accessibilityLabel: string;
|
||||
|
||||
/**
|
||||
* The context menu item background color.
|
||||
*/
|
||||
backgroundColor?: string;
|
||||
|
||||
/**
|
||||
* Component children.
|
||||
*/
|
||||
@@ -163,6 +168,7 @@ const useStyles = makeStyles()(theme => {
|
||||
|
||||
const ContextMenuItem = ({
|
||||
accessibilityLabel,
|
||||
backgroundColor,
|
||||
children,
|
||||
className,
|
||||
controls,
|
||||
@@ -181,7 +187,7 @@ const ContextMenuItem = ({
|
||||
textClassName }: IProps) => {
|
||||
const { classes: styles, cx } = useStyles();
|
||||
const _overflowDrawer: boolean = useSelector(showOverflowDrawer);
|
||||
|
||||
const style = backgroundColor ? { backgroundColor } : {};
|
||||
const onKeyPressHandler = useCallback(e => {
|
||||
// only trigger the fallback behavior (onClick) if we dont have any explicit keyboard event handler
|
||||
if (onClick && !onKeyPress && !onKeyDown && (e.key === 'Enter' || e.key === ' ')) {
|
||||
@@ -223,6 +229,7 @@ const ContextMenuItem = ({
|
||||
onKeyDown = { disabled ? undefined : onKeyDown }
|
||||
onKeyPress = { disabled ? undefined : onKeyPressHandler }
|
||||
role = { onClick ? role : undefined }
|
||||
style = { style }
|
||||
tabIndex = { onClick ? tabIndex : undefined }>
|
||||
{customIcon ? customIcon
|
||||
: icon && <Icon
|
||||
|
||||
@@ -260,6 +260,7 @@ export const commonStyles = (theme: Theme) => {
|
||||
padding: 6,
|
||||
textAlign: 'center' as const,
|
||||
pointerEvents: 'all' as const,
|
||||
display: 'flex',
|
||||
boxShadow: '0px 2px 8px 4px rgba(0, 0, 0, 0.25), 0px 0px 0px 1px rgba(0, 0, 0, 0.15)',
|
||||
|
||||
'& > div': {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// @ts-expect-error
|
||||
import Bourne from '@hapi/bourne';
|
||||
// @ts-ignore
|
||||
import { safeJsonParse } from '@jitsi/js-utils/json';
|
||||
|
||||
import { reportError } from './helpers';
|
||||
|
||||
@@ -62,7 +62,7 @@ export function parseURLParams(
|
||||
if (!dontParse) {
|
||||
const decoded = decodeURIComponent(value).replace(/\\&/, '&');
|
||||
|
||||
value = decoded === 'undefined' ? undefined : Bourne.parse(decoded);
|
||||
value = decoded === 'undefined' ? undefined : safeJsonParse(decoded);
|
||||
}
|
||||
} catch (e: any) {
|
||||
reportError(
|
||||
|
||||
@@ -130,7 +130,10 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
// recipient. This logic tries to mitigate this risk.
|
||||
const shouldSendPrivateMessageTo = _shouldSendPrivateMessageTo(state, action);
|
||||
|
||||
if (shouldSendPrivateMessageTo) {
|
||||
const participantExists = shouldSendPrivateMessageTo
|
||||
&& getParticipantById(state, shouldSendPrivateMessageTo);
|
||||
|
||||
if (shouldSendPrivateMessageTo && participantExists) {
|
||||
dispatch(openDialog(ChatPrivacyDialog, {
|
||||
message: action.message,
|
||||
participantID: shouldSendPrivateMessageTo
|
||||
|
||||
1
react/features/conference/middleware.native.ts
Normal file
1
react/features/conference/middleware.native.ts
Normal file
@@ -0,0 +1 @@
|
||||
import './middleware.any';
|
||||
45
react/features/conference/middleware.web.ts
Normal file
45
react/features/conference/middleware.web.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
|
||||
import { CONFERENCE_JOINED } from '../base/conference/actionTypes';
|
||||
import { IJitsiConference } from '../base/conference/reducer';
|
||||
import { JitsiConferenceEvents } from '../base/lib-jitsi-meet';
|
||||
import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
|
||||
import { openAllowToggleCameraDialog, setCameraFacingMode } from '../base/tracks/actions.web';
|
||||
import { CAMERA_FACING_MODE_MESSAGE } from '../base/tracks/constants';
|
||||
|
||||
import './middleware.any';
|
||||
|
||||
MiddlewareRegistry.register(_store => next => action => {
|
||||
switch (action.type) {
|
||||
case CONFERENCE_JOINED: {
|
||||
_addSetCameraFacingModeListener(action.conference);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return next(action);
|
||||
});
|
||||
|
||||
/**
|
||||
* Registers listener for {@link JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED} that
|
||||
* will perform various chat related activities.
|
||||
*
|
||||
* @param {IJitsiConference} conference - The conference.
|
||||
* @returns {void}
|
||||
*/
|
||||
function _addSetCameraFacingModeListener(conference: IJitsiConference) {
|
||||
conference.on(
|
||||
JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
|
||||
(...args: any) => {
|
||||
if (args && args.length >= 2) {
|
||||
const [ sender, eventData ] = args;
|
||||
|
||||
if (eventData.name === CAMERA_FACING_MODE_MESSAGE) {
|
||||
APP.store.dispatch(openAllowToggleCameraDialog(
|
||||
/* onAllow */ () => APP.store.dispatch(setCameraFacingMode(eventData.facingMode)),
|
||||
/* initiatorId */ sender._id
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -79,10 +79,8 @@ export function submitVideoDeviceSelectionTab(newState: any, isDisplayedOnWelcom
|
||||
userSelectedCameraDeviceLabel:
|
||||
getDeviceLabelById(getState(), newState.selectedVideoInputId, 'videoInput')
|
||||
}));
|
||||
|
||||
dispatch(setVideoInputDevice(newState.selectedVideoInputId));
|
||||
}
|
||||
|
||||
if (newState.localFlipX !== currentState.localFlipX) {
|
||||
dispatch(updateSettings({
|
||||
localFlipX: newState.localFlipX
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
getDeviceIdByLabel,
|
||||
groupDevicesByKind
|
||||
} from '../base/devices/functions.web';
|
||||
import { isIosMobileBrowser } from '../base/environment/utils';
|
||||
import { isIosMobileBrowser, isMobileBrowser } from '../base/environment/utils';
|
||||
import JitsiMeetJS from '../base/lib-jitsi-meet';
|
||||
import { toState } from '../base/redux/functions';
|
||||
import {
|
||||
@@ -97,7 +97,7 @@ export function getAudioDeviceSelectionDialogProps(stateful: IStateful, isDispla
|
||||
export function getVideoDeviceSelectionDialogProps(stateful: IStateful, isDisplayedOnWelcomePage: boolean) {
|
||||
// On mobile Safari because of https://bugs.webkit.org/show_bug.cgi?id=179363#c30, the old track is stopped
|
||||
// by the browser when a new track is created for preview. That's why we are disabling all previews.
|
||||
const disablePreviews = isIosMobileBrowser();
|
||||
const disablePreviews = isMobileBrowser();
|
||||
|
||||
const state = toState(stateful);
|
||||
const settings = state['features/base/settings'];
|
||||
|
||||
@@ -43,6 +43,7 @@ import {
|
||||
isStageFilmstripTopPanel,
|
||||
shouldRemoteVideosBeVisible
|
||||
} from '../../functions';
|
||||
import { isFilmstripDisabled } from '../../functions.web';
|
||||
|
||||
import AudioTracksContainer from './AudioTracksContainer';
|
||||
import Thumbnail from './Thumbnail';
|
||||
@@ -74,6 +75,11 @@ interface IProps extends WithTranslation {
|
||||
*/
|
||||
_disableSelfView: boolean;
|
||||
|
||||
/**
|
||||
* Whether vertical/horizontal filmstrip is disabled through config.
|
||||
*/
|
||||
_filmstripDisabled: boolean;
|
||||
|
||||
/**
|
||||
* The height of the filmstrip.
|
||||
*/
|
||||
@@ -333,6 +339,7 @@ class Filmstrip extends PureComponent <IProps, IState> {
|
||||
const {
|
||||
_currentLayout,
|
||||
_disableSelfView,
|
||||
_filmstripDisabled,
|
||||
_localScreenShareId,
|
||||
_mainFilmstripVisible,
|
||||
_resizableFilmstrip,
|
||||
@@ -381,7 +388,8 @@ class Filmstrip extends PureComponent <IProps, IState> {
|
||||
let toolbar = null;
|
||||
|
||||
if (!this.props._iAmRecorder && this.props._isFilmstripButtonEnabled
|
||||
&& _currentLayout !== LAYOUTS.TILE_VIEW && (filmstripType === FILMSTRIP_TYPE.MAIN
|
||||
&& _currentLayout !== LAYOUTS.TILE_VIEW
|
||||
&& ((filmstripType === FILMSTRIP_TYPE.MAIN && !_filmstripDisabled)
|
||||
|| (filmstripType === FILMSTRIP_TYPE.STAGE && _topPanelFilmstrip))) {
|
||||
toolbar = this._renderToggleButton();
|
||||
}
|
||||
@@ -885,6 +893,7 @@ function _mapStateToProps(state: IReduxState, ownProps: any) {
|
||||
const { isOpen: shiftRight } = state['features/chat'];
|
||||
const disableSelfView = getHideSelfView(state);
|
||||
const { clientWidth, clientHeight } = state['features/base/responsive-ui'];
|
||||
const filmstripDisabled = isFilmstripDisabled(state);
|
||||
|
||||
const collapseTileView = reduceHeight
|
||||
&& isMobileBrowser()
|
||||
@@ -893,7 +902,8 @@ function _mapStateToProps(state: IReduxState, ownProps: any) {
|
||||
const shouldReduceHeight = reduceHeight && isMobileBrowser();
|
||||
const _topPanelVisible = isStageFilmstripTopPanel(state) && topPanelVisible;
|
||||
|
||||
let isVisible = visible || filmstripType !== FILMSTRIP_TYPE.MAIN;
|
||||
const notDisabled = visible && !filmstripDisabled;
|
||||
let isVisible = notDisabled || filmstripType !== FILMSTRIP_TYPE.MAIN;
|
||||
|
||||
if (_topPanelFilmstrip) {
|
||||
isVisible = _topPanelVisible;
|
||||
@@ -912,13 +922,14 @@ function _mapStateToProps(state: IReduxState, ownProps: any) {
|
||||
_chatOpen: state['features/chat'].isOpen,
|
||||
_currentLayout,
|
||||
_disableSelfView: disableSelfView,
|
||||
_filmstripDisabled: filmstripDisabled,
|
||||
_hasScroll,
|
||||
_iAmRecorder: Boolean(iAmRecorder),
|
||||
_isFilmstripButtonEnabled: isButtonEnabled('filmstrip', state),
|
||||
_isToolboxVisible: isToolboxVisible(state),
|
||||
_isVerticalFilmstrip,
|
||||
_localScreenShareId: localScreenShare?.id,
|
||||
_mainFilmstripVisible: visible,
|
||||
_mainFilmstripVisible: notDisabled,
|
||||
_maxFilmstripWidth: clientWidth - MIN_STAGE_VIEW_WIDTH,
|
||||
_maxTopPanelHeight: clientHeight - MIN_STAGE_VIEW_HEIGHT,
|
||||
_remoteParticipantsLength: _remoteParticipants?.length ?? 0,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { IStore } from '../app/types';
|
||||
import { IReduxState, IStore } from '../app/types';
|
||||
import {
|
||||
getActiveSpeakersToBeDisplayed,
|
||||
getVirtualScreenshareParticipantOwnerId
|
||||
@@ -95,3 +95,15 @@ export function updateRemoteParticipantsOnLeave(store: IStore, participantId: st
|
||||
reorderedParticipants.delete(participantId)
|
||||
&& store.dispatch(setRemoteParticipants(Array.from(reorderedParticipants)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether tileview is completely disabled.
|
||||
*
|
||||
* @param {IReduxState} state - Redux state.
|
||||
* @returns {boolean} - Whether tileview is completely disabled.
|
||||
*/
|
||||
export function isTileViewModeDisabled(state: IReduxState) {
|
||||
const { tileView = {} } = state['features/base/config'];
|
||||
|
||||
return tileView.disabled;
|
||||
}
|
||||
|
||||
@@ -763,6 +763,18 @@ export function isStageFilmstripEnabled(state: IReduxState) {
|
||||
return Boolean(!filmstrip?.disableStageFilmstrip && interfaceConfig.VERTICAL_FILMSTRIP);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the vertical/horizontal filmstrip is disabled.
|
||||
*
|
||||
* @param {Object} state - Redux state.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isFilmstripDisabled(state: IReduxState) {
|
||||
const { filmstrip } = state['features/base/config'];
|
||||
|
||||
return Boolean(filmstrip?.disabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the thumbnail type by filmstrip type.
|
||||
*
|
||||
|
||||
@@ -120,7 +120,7 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
case SETTINGS_UPDATED: {
|
||||
if (typeof action.settings?.localFlipX === 'boolean') {
|
||||
// TODO: This needs to be removed once the large video is Reactified.
|
||||
VideoLayout.onLocalFlipXChanged();
|
||||
VideoLayout.onLocalFlipXChanged(action.settings.localFlipX);
|
||||
}
|
||||
if (action.settings?.disableSelfView) {
|
||||
const state = store.getState();
|
||||
|
||||
@@ -56,7 +56,7 @@ const NumbersList: React.FC<IProps> = ({ t, conferenceID, clickableNumbers, numb
|
||||
if (countryCode) {
|
||||
return (
|
||||
<td className = 'flag-cell'>
|
||||
{countryCode === 'SIP'
|
||||
{countryCode === 'SIP' || countryCode === 'SIP_AUDIO_ONLY'
|
||||
? <Icon src = { IconSip } />
|
||||
: <i className = { `flag iti-flag ${countryCode}` } />
|
||||
}
|
||||
@@ -129,6 +129,8 @@ const NumbersList: React.FC<IProps> = ({ t, conferenceID, clickableNumbers, numb
|
||||
|
||||
if (countryCode === 'SIP') {
|
||||
countryName = t('info.sip');
|
||||
} else if (countryCode === 'SIP_AUDIO_ONLY') {
|
||||
countryName = t('info.sipAudioOnly');
|
||||
} else {
|
||||
countryName = t(`countries:countries.${countryCode}`);
|
||||
|
||||
|
||||
@@ -20,10 +20,10 @@ export default function BreakoutRoomNamePrompt({ breakoutRoomJid, initialRoomNam
|
||||
const okDisabled = !roomName;
|
||||
const dispatch = useDispatch();
|
||||
const onBreakoutRoomNameChange = useCallback((newRoomName: string) => {
|
||||
setRoomName(newRoomName?.trim());
|
||||
setRoomName(newRoomName);
|
||||
}, [ setRoomName ]);
|
||||
const onSubmit = useCallback(() => {
|
||||
dispatch(renameBreakoutRoom(breakoutRoomJid, roomName));
|
||||
dispatch(renameBreakoutRoom(breakoutRoomJid, roomName?.trim()));
|
||||
}, [ breakoutRoomJid, dispatch, roomName ]);
|
||||
|
||||
return (<Dialog
|
||||
|
||||
@@ -476,7 +476,11 @@ export function endpointMessageReceived(participantId: string, message: {
|
||||
if (type === EVENTS.stop) {
|
||||
dispatch(stopReceiver(false, true));
|
||||
} else { // forward the message
|
||||
transport?.sendEvent(message);
|
||||
try {
|
||||
transport?.sendEvent(message);
|
||||
} catch (error) {
|
||||
logger.error('Error while trying to execute remote control message', error);
|
||||
}
|
||||
}
|
||||
} // else ignore
|
||||
} else {
|
||||
|
||||
@@ -212,15 +212,7 @@ function _mapStateToProps(state: IReduxState, ownProps: any) {
|
||||
cancel: () => {
|
||||
const { options } = getVirtualBackgroundTabProps(state, isDisplayedOnWelcomePage);
|
||||
|
||||
return submitVirtualBackgroundTab({
|
||||
options: {
|
||||
backgroundType: options.backgroundType,
|
||||
enabled: options.backgroundEffectEnabled,
|
||||
url: options.virtualSource,
|
||||
selectedThumbnail: options.selectedThumbnail,
|
||||
blurValue: options.blurValue
|
||||
}
|
||||
}, true);
|
||||
return submitVirtualBackgroundTab({ options }, true);
|
||||
},
|
||||
icon: IconImage
|
||||
});
|
||||
|
||||
@@ -4,6 +4,7 @@ import AbstractButton, { IProps as AbstractButtonProps } from '../../../base/too
|
||||
|
||||
|
||||
interface IProps extends AbstractButtonProps {
|
||||
backgroundColor?: string;
|
||||
icon: string;
|
||||
id?: string;
|
||||
text: string;
|
||||
@@ -18,6 +19,7 @@ class CustomOptionButton extends AbstractButton<IProps> {
|
||||
iconSrc = this.props.icon;
|
||||
id = this.props.id;
|
||||
text = this.props.text;
|
||||
backgroundColor = this.props.backgroundColor;
|
||||
|
||||
accessibilityLabel = this.text;
|
||||
|
||||
|
||||
@@ -103,14 +103,13 @@ class ProfileButton extends AbstractButton<IProps> {
|
||||
* @returns {Object}
|
||||
*/
|
||||
const mapStateToProps = (state: IReduxState) => {
|
||||
const { defaultLocalDisplayName, disableProfile } = state['features/base/config'];
|
||||
const { defaultLocalDisplayName } = state['features/base/config'];
|
||||
|
||||
return {
|
||||
_defaultLocalDisplayName: defaultLocalDisplayName ?? '',
|
||||
_localParticipant: getLocalParticipant(state),
|
||||
_unclickable: !interfaceConfig.SETTINGS_SECTIONS.includes('profile'),
|
||||
customClass: 'profile-button-avatar',
|
||||
visible: !disableProfile
|
||||
customClass: 'profile-button-avatar'
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ interface IProps extends WithTranslation {
|
||||
/**
|
||||
* Custom Toolbar buttons.
|
||||
*/
|
||||
_customToolbarButtons?: Array<{ icon: string; id: string; text: string; }>;
|
||||
_customToolbarButtons?: Array<{ backgroundColor?: string; icon: string; id: string; text: string; }>;
|
||||
|
||||
/**
|
||||
* Whether or not a dialog is displayed.
|
||||
@@ -290,7 +290,7 @@ const Toolbox = ({
|
||||
|
||||
setButtonsNotifyClickMode(buttons);
|
||||
const isHangupVisible = isToolbarButtonEnabled('hangup', _toolbarButtons);
|
||||
let { order } = THRESHOLDS.find(({ width }) => _clientWidth > width)
|
||||
const { order } = THRESHOLDS.find(({ width }) => _clientWidth > width)
|
||||
|| THRESHOLDS[THRESHOLDS.length - 1];
|
||||
|
||||
const keys = Object.keys(buttons);
|
||||
@@ -302,11 +302,8 @@ const Toolbox = ({
|
||||
!_jwtDisabledButtons.includes(key)
|
||||
&& (isToolbarButtonEnabled(key, _toolbarButtons) || isToolbarButtonEnabled(alias, _toolbarButtons))
|
||||
);
|
||||
const filteredKeys = filtered.map(button => button.key);
|
||||
|
||||
order = order.filter(key => filteredKeys.includes(buttons[key as keyof typeof buttons].key));
|
||||
|
||||
let sliceIndex = order.length + 2;
|
||||
let sliceIndex = _overflowDrawer || _reactionsButtonEnabled ? order.length + 2 : order.length + 1;
|
||||
|
||||
if (isHangupVisible) {
|
||||
sliceIndex -= 1;
|
||||
|
||||
@@ -199,6 +199,7 @@ export function getToolbarTimeout(state: IReduxState) {
|
||||
* @returns {Object} The button maps mainMenuButtons and overflowMenuButtons.
|
||||
*/
|
||||
export function getAllToolboxButtons(_customToolbarButtons?: {
|
||||
backgroundColor?: string;
|
||||
icon: string;
|
||||
id: string;
|
||||
text: string;
|
||||
@@ -395,10 +396,11 @@ export function getAllToolboxButtons(_customToolbarButtons?: {
|
||||
group: 4
|
||||
};
|
||||
|
||||
const customButtons = _customToolbarButtons?.reduce((prev, { icon, id, text }) => {
|
||||
const customButtons = _customToolbarButtons?.reduce((prev, { backgroundColor, icon, id, text }) => {
|
||||
return {
|
||||
...prev,
|
||||
[id]: {
|
||||
backgroundColor,
|
||||
key: id,
|
||||
Content: CustomOptionButton,
|
||||
group: 4,
|
||||
|
||||
@@ -42,9 +42,8 @@ export const useKeyboardShortcuts = (toolbarButtons: Array<string>) => {
|
||||
const desktopSharingEnabled = JitsiMeetJS.isDesktopSharingEnabled();
|
||||
const fullScreen = useSelector((state: IReduxState) => state['features/toolbox'].fullScreen);
|
||||
const gifsEnabled = useSelector(isGifEnabled);
|
||||
const localParticipant = useSelector(getLocalParticipant);
|
||||
const participantsPaneOpen = useSelector(getParticipantsPaneOpen);
|
||||
const raisedHand = hasRaisedHand(localParticipant);
|
||||
const raisedHand = useSelector((state: IReduxState) => hasRaisedHand(getLocalParticipant(state)));
|
||||
const reactionsEnabled = useSelector(isReactionsEnabled);
|
||||
const screenSharing = useSelector(isScreenVideoShared);
|
||||
const tileViewEnabled = useSelector(shouldDisplayTileView);
|
||||
@@ -306,5 +305,15 @@ export const useKeyboardShortcuts = (toolbarButtons: Array<string>) => {
|
||||
dispatch(unregisterShortcut(letter, true)));
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
}, [
|
||||
chatOpen,
|
||||
desktopSharingButtonDisabled,
|
||||
desktopSharingEnabled,
|
||||
fullScreen,
|
||||
gifsEnabled,
|
||||
participantsPaneOpen,
|
||||
raisedHand,
|
||||
screenSharing,
|
||||
tileViewEnabled
|
||||
]);
|
||||
};
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { IStore } from '../app/types';
|
||||
import { isTileViewModeDisabled } from '../filmstrip/functions.any';
|
||||
|
||||
import {
|
||||
SET_TILE_VIEW,
|
||||
@@ -34,9 +35,13 @@ export function virtualScreenshareParticipantsUpdated(participantIds: Array<stri
|
||||
* }}
|
||||
*/
|
||||
export function setTileView(enabled?: boolean) {
|
||||
return {
|
||||
type: SET_TILE_VIEW,
|
||||
enabled
|
||||
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||
const tileViewDisabled = isTileViewModeDisabled(getState());
|
||||
|
||||
!tileViewDisabled && dispatch({
|
||||
type: SET_TILE_VIEW,
|
||||
enabled
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import { getFeatureFlag } from '../base/flags/functions';
|
||||
import { pinParticipant } from '../base/participants/actions';
|
||||
import { getParticipantCount, getPinnedParticipant } from '../base/participants/functions';
|
||||
import { FakeParticipant } from '../base/participants/types';
|
||||
import { isStageFilmstripAvailable } from '../filmstrip/functions';
|
||||
import { isStageFilmstripAvailable, isTileViewModeDisabled } from '../filmstrip/functions';
|
||||
import { isVideoPlaying } from '../shared-video/functions';
|
||||
import { VIDEO_QUALITY_LEVELS } from '../video-quality/constants';
|
||||
import { getReceiverVideoQualityLevel } from '../video-quality/functions';
|
||||
@@ -60,6 +60,12 @@ export function getCurrentLayout(state: IReduxState) {
|
||||
* @returns {boolean} True if tile view should be displayed.
|
||||
*/
|
||||
export function shouldDisplayTileView(state: IReduxState) {
|
||||
const tileViewDisabled = isTileViewModeDisabled(state);
|
||||
|
||||
if (tileViewDisabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const { tileViewEnabled } = state['features/video-layout'] ?? {};
|
||||
|
||||
if (tileViewEnabled !== undefined) {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { createVirtualBackgroundEffect } from '../stream-effects/virtual-backgro
|
||||
|
||||
import { BACKGROUND_ENABLED, SET_VIRTUAL_BACKGROUND } from './actionTypes';
|
||||
import logger from './logger';
|
||||
import { IVirtualBackgroundOptions } from './types';
|
||||
import { IVirtualBackground } from './reducer';
|
||||
|
||||
/**
|
||||
* Signals the local participant activate the virtual background video or not.
|
||||
@@ -12,16 +12,16 @@ import { IVirtualBackgroundOptions } from './types';
|
||||
* @param {Object} jitsiTrack - Represents the jitsi track that will have backgraund effect applied.
|
||||
* @returns {Promise}
|
||||
*/
|
||||
export function toggleBackgroundEffect(options: IVirtualBackgroundOptions, jitsiTrack: any) {
|
||||
export function toggleBackgroundEffect(options: IVirtualBackground, jitsiTrack: any) {
|
||||
return async function(dispatch: IStore['dispatch'], getState: IStore['getState']) {
|
||||
await dispatch(backgroundEnabled(options.enabled));
|
||||
await dispatch(backgroundEnabled(options.backgroundEffectEnabled));
|
||||
await dispatch(setVirtualBackground(options));
|
||||
const state = getState();
|
||||
const virtualBackground = state['features/virtual-background'];
|
||||
|
||||
if (jitsiTrack) {
|
||||
try {
|
||||
if (options.enabled) {
|
||||
if (options.backgroundEffectEnabled) {
|
||||
await jitsiTrack.setEffect(await createVirtualBackgroundEffect(virtualBackground, dispatch));
|
||||
} else {
|
||||
await jitsiTrack.setEffect(undefined);
|
||||
@@ -46,10 +46,10 @@ export function toggleBackgroundEffect(options: IVirtualBackgroundOptions, jitsi
|
||||
* type: string,
|
||||
* }}
|
||||
*/
|
||||
export function setVirtualBackground(options?: IVirtualBackgroundOptions) {
|
||||
export function setVirtualBackground(options?: IVirtualBackground) {
|
||||
return {
|
||||
type: SET_VIRTUAL_BACKGROUND,
|
||||
virtualSource: options?.url,
|
||||
virtualSource: options?.virtualSource,
|
||||
blurValue: options?.blurValue,
|
||||
backgroundType: options?.backgroundType,
|
||||
selectedThumbnail: options?.selectedThumbnail
|
||||
@@ -65,7 +65,7 @@ export function setVirtualBackground(options?: IVirtualBackgroundOptions) {
|
||||
* backgroundEffectEnabled: boolean
|
||||
* }}
|
||||
*/
|
||||
export function backgroundEnabled(backgroundEffectEnabled: boolean) {
|
||||
export function backgroundEnabled(backgroundEffectEnabled?: boolean) {
|
||||
return {
|
||||
type: BACKGROUND_ENABLED,
|
||||
backgroundEffectEnabled
|
||||
|
||||
@@ -105,10 +105,10 @@ function UploadImageButton({
|
||||
}
|
||||
]);
|
||||
setOptions({
|
||||
backgroundEffectEnabled: true,
|
||||
backgroundType: VIRTUAL_BACKGROUND_TYPE.IMAGE,
|
||||
enabled: true,
|
||||
url,
|
||||
selectedThumbnail: uuId
|
||||
selectedThumbnail: uuId,
|
||||
virtualSource: url
|
||||
});
|
||||
};
|
||||
logger.info('New virtual background image uploaded!');
|
||||
|
||||
@@ -15,6 +15,7 @@ import { showWarningNotification } from '../../notifications/actions';
|
||||
import { NOTIFICATION_TIMEOUT_TYPE } from '../../notifications/constants';
|
||||
import { toggleBackgroundEffect } from '../actions';
|
||||
import logger from '../logger';
|
||||
import { IVirtualBackground } from '../reducer';
|
||||
|
||||
/**
|
||||
* The type of the React {@code PureComponent} props of {@link VirtualBackgroundPreview}.
|
||||
@@ -39,7 +40,7 @@ export interface IProps extends WithTranslation {
|
||||
/**
|
||||
* Represents the virtual background set options.
|
||||
*/
|
||||
options: any;
|
||||
options: IVirtualBackground;
|
||||
|
||||
/**
|
||||
* The id of the selected video device.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
// @ts-expect-error
|
||||
import Bourne from '@hapi/bourne';
|
||||
// eslint-disable-next-line lines-around-comment
|
||||
// @ts-expect-error
|
||||
// @ts-ignore
|
||||
import { jitsiLocalStorage } from '@jitsi/js-utils/jitsi-local-storage';
|
||||
// eslint-disable-next-line lines-around-comment
|
||||
// @ts-ignore
|
||||
import { safeJsonParse } from '@jitsi/js-utils/json';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { WithTranslation } from 'react-i18next';
|
||||
import { connect } from 'react-redux';
|
||||
@@ -19,6 +19,7 @@ import Spinner from '../../base/ui/components/web/Spinner';
|
||||
import { BACKGROUNDS_LIMIT, IMAGES, type Image, VIRTUAL_BACKGROUND_TYPE } from '../constants';
|
||||
import { toDataURL } from '../functions';
|
||||
import logger from '../logger';
|
||||
import { IVirtualBackground } from '../reducer';
|
||||
|
||||
import UploadImageButton from './UploadImageButton';
|
||||
import VirtualBackgroundPreview from './VirtualBackgroundPreview';
|
||||
@@ -72,7 +73,7 @@ interface IProps extends WithTranslation {
|
||||
/**
|
||||
* Virtual background options.
|
||||
*/
|
||||
options: any;
|
||||
options: IVirtualBackground;
|
||||
|
||||
/**
|
||||
* Returns the selected thumbnail identifier.
|
||||
@@ -217,7 +218,7 @@ function VirtualBackgrounds({
|
||||
const { classes, cx } = useStyles();
|
||||
const [ previewIsLoaded, setPreviewIsLoaded ] = useState(false);
|
||||
const localImages = jitsiLocalStorage.getItem('virtualBackgrounds');
|
||||
const [ storedImages, setStoredImages ] = useState<Array<Image>>((localImages && Bourne.parse(localImages)) || []);
|
||||
const [ storedImages, setStoredImages ] = useState<Array<Image>>((localImages && safeJsonParse(localImages)) || []);
|
||||
const [ loading, setLoading ] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -254,8 +255,8 @@ function VirtualBackgrounds({
|
||||
|
||||
const enableBlur = useCallback(async () => {
|
||||
onOptionsChange({
|
||||
backgroundEffectEnabled: true,
|
||||
backgroundType: VIRTUAL_BACKGROUND_TYPE.BLUR,
|
||||
enabled: true,
|
||||
blurValue: 25,
|
||||
selectedThumbnail: 'blur'
|
||||
});
|
||||
@@ -272,8 +273,8 @@ function VirtualBackgrounds({
|
||||
|
||||
const enableSlideBlur = useCallback(async () => {
|
||||
onOptionsChange({
|
||||
backgroundEffectEnabled: true,
|
||||
backgroundType: VIRTUAL_BACKGROUND_TYPE.BLUR,
|
||||
enabled: true,
|
||||
blurValue: 8,
|
||||
selectedThumbnail: 'slight-blur'
|
||||
});
|
||||
@@ -290,7 +291,7 @@ function VirtualBackgrounds({
|
||||
|
||||
const removeBackground = useCallback(async () => {
|
||||
onOptionsChange({
|
||||
enabled: false,
|
||||
backgroundEffectEnabled: false,
|
||||
selectedThumbnail: 'none'
|
||||
});
|
||||
logger.info('"None" option set for virtual background preview!');
|
||||
@@ -310,10 +311,10 @@ function VirtualBackgrounds({
|
||||
|
||||
if (image) {
|
||||
onOptionsChange({
|
||||
backgroundType: 'image',
|
||||
enabled: true,
|
||||
url: image.src,
|
||||
selectedThumbnail: image.id
|
||||
backgroundEffectEnabled: true,
|
||||
backgroundType: VIRTUAL_BACKGROUND_TYPE.IMAGE,
|
||||
selectedThumbnail: image.id,
|
||||
virtualSource: image.src
|
||||
});
|
||||
logger.info('Uploaded image set for virtual background preview!');
|
||||
}
|
||||
@@ -328,10 +329,10 @@ function VirtualBackgrounds({
|
||||
const url = await toDataURL(image.src);
|
||||
|
||||
onOptionsChange({
|
||||
backgroundType: 'image',
|
||||
enabled: true,
|
||||
url,
|
||||
selectedThumbnail: image.id
|
||||
backgroundEffectEnabled: true,
|
||||
backgroundType: VIRTUAL_BACKGROUND_TYPE.IMAGE,
|
||||
selectedThumbnail: image.id,
|
||||
virtualSource: url
|
||||
});
|
||||
logger.info('Image set for virtual background preview!');
|
||||
} catch (err) {
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
import { IVirtualBackground } from './reducer';
|
||||
|
||||
export interface IVirtualBackgroundOptions extends IVirtualBackground {
|
||||
enabled: boolean;
|
||||
url?: string;
|
||||
}
|
||||
@@ -339,7 +339,7 @@ module.exports = (_env, argv) => {
|
||||
...config.plugins,
|
||||
...getBundleAnalyzerPlugin(analyzeBundle, 'external_api')
|
||||
],
|
||||
performance: getPerformanceHints(perfHintOptions, 35 * 1024)
|
||||
performance: getPerformanceHints(perfHintOptions, 40 * 1024)
|
||||
}),
|
||||
Object.assign({}, config, {
|
||||
entry: {
|
||||
|
||||
Reference in New Issue
Block a user