Compare commits

...

9 Commits

Author SHA1 Message Date
Jaya Allamsetty
23cc91391b fix(tracks) Replace the tracks directly on camera toggle.
Fixes an issue where p2p peer stops rendering remote video when the mobile client toggles camera. This happens only when the peer starts video muted.
2025-02-13 15:25:23 -05:00
Hristo Terezov
737d24824a fix(config): Allow only enableMediaOnPromote from visitors config to be overriden. 2025-01-16 09:02:32 -06:00
Jaya Allamsetty
bff8bbb624 chore(deps) update to release branch release-8302 for LJM. 2025-01-08 14:16:32 -05:00
damencho
d5b76c1abf fix(prosody): Adds another condition to the filter. 2025-01-08 12:02:36 -06:00
Hristo Terezov
73505d56d7 feat(customParticipantButton): metrics 2025-01-08 11:13:01 -06:00
Hristo Terezov
c426f96943 feat(URL-overrides): Add metrics. 2025-01-07 16:16:38 -06:00
damencho
2beea0a952 fix(shared-video): Gets from info from the incoming presence.
Ignore using from field send in attributes of the command.

Remove disable button action from web.
2024-12-23 10:55:36 -06:00
damencho
52b2444749 fix(polls): Returns an error on duplicate poll. 2024-12-17 16:45:34 -06:00
damencho
7ce85a3e83 fix(visitors): Fix a check that can result missing main participants. 2024-12-17 11:01:00 -06:00
23 changed files with 119 additions and 129 deletions

View File

@@ -1620,6 +1620,16 @@ var config = {
// For external entities (e. g. email), the localStorage key holding the token value for directory authentication
// peopleSearchTokenLocation: "mytoken",
// Options related to visitors.
// visitors: {
// // Starts audio/video when the participant is promoted from visitor.
// enableMediaOnPromote: {
// audio: true,
// video: true
// },
// },
// List of undocumented settings used in jitsi-meet
/**
_immediateReloadThreshold

View File

@@ -115,7 +115,7 @@ import { toggleScreenshotCaptureSummary } from '../../react/features/screenshot-
import { isScreenshotCaptureEnabled } from '../../react/features/screenshot-capture/functions';
import SettingsDialog from '../../react/features/settings/components/web/SettingsDialog';
import { SETTINGS_TABS } from '../../react/features/settings/constants';
import { playSharedVideo, stopSharedVideo } from '../../react/features/shared-video/actions.any';
import { playSharedVideo, stopSharedVideo } from '../../react/features/shared-video/actions';
import { extractYoutubeIdOrURL } from '../../react/features/shared-video/functions';
import { setRequestingSubtitles, toggleRequestingSubtitles } from '../../react/features/subtitles/actions';
import { isAudioMuteButtonDisabled } from '../../react/features/toolbox/functions';

9
package-lock.json generated
View File

@@ -62,7 +62,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/v1890.0.0+144b0cab/lib-jitsi-meet.tgz",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet#release-8302",
"lodash-es": "4.17.21",
"moment": "2.29.4",
"moment-duration-format": "2.2.2",
@@ -16440,8 +16440,7 @@
},
"node_modules/lib-jitsi-meet": {
"version": "0.0.0",
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1890.0.0+144b0cab/lib-jitsi-meet.tgz",
"integrity": "sha512-/NoS/uUJvMXdbi5gQoihcQ9ovXlQtFKgdmtiLqLJxz91T9JwdVZNsjo3G6RUVIJ07myb/vjQp91kpm45U7CgIw==",
"resolved": "git+ssh://git@github.com/jitsi/lib-jitsi-meet.git#51852e0af2d703c6948cdd04910f7f7302cf2434",
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
@@ -36259,8 +36258,8 @@
}
},
"lib-jitsi-meet": {
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1890.0.0+144b0cab/lib-jitsi-meet.tgz",
"integrity": "sha512-/NoS/uUJvMXdbi5gQoihcQ9ovXlQtFKgdmtiLqLJxz91T9JwdVZNsjo3G6RUVIJ07myb/vjQp91kpm45U7CgIw==",
"version": "git+ssh://git@github.com/jitsi/lib-jitsi-meet.git#51852e0af2d703c6948cdd04910f7f7302cf2434",
"from": "lib-jitsi-meet@https://github.com/jitsi/lib-jitsi-meet#release-8302",
"requires": {
"@jitsi/js-utils": "2.2.1",
"@jitsi/logger": "2.0.2",

View File

@@ -68,7 +68,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/v1890.0.0+144b0cab/lib-jitsi-meet.tgz",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet#release-8302",
"lodash-es": "4.17.21",
"moment": "2.29.4",
"moment-duration-format": "2.2.2",

View File

@@ -154,6 +154,30 @@ export async function createHandlers({ getState }: IStore) {
return handlers;
}
/**
* Checks whether a url is a data URL or not.
*
* @param {string} url - The URL to be checked.
* @returns {boolean}
*/
function isDataURL(url?: string): boolean {
if (typeof url !== 'string') { // The icon will be ignored
return false;
}
try {
const urlObject = new URL(url);
if (urlObject.protocol === 'data:') {
return false;
}
} catch {
return false;
}
return true;
}
/**
* Inits JitsiMeetJS.analytics by setting permanent properties and setting the handlers from the loaded scripts.
* NOTE: Has to be used after JitsiMeetJS.init. Otherwise analytics will be null.
@@ -185,13 +209,21 @@ export function initAnalytics(store: IStore, handlers: Array<Object>): boolean {
inIframe?: boolean;
isPromotedFromVisitor?: boolean;
isVisitor?: boolean;
overwritesCustomButtonsWithURL?: boolean;
overwritesCustomParticipantButtonsWithURL?: boolean;
overwritesDefaultLogoUrl?: boolean;
overwritesDeploymentUrls?: boolean;
overwritesEtherpadBase?: boolean;
overwritesHosts?: boolean;
overwritesIceServers?: boolean;
overwritesLiveStreamingUrls?: boolean;
overwritesPeopleSearchUrl?: boolean;
overwritesPrejoinConfigICEUrl?: boolean;
overwritesSalesforceUrl?: boolean;
overwritesSupportUrl?: boolean;
overwritesWatchRTCConfigParams?: boolean;
overwritesWatchRTCProxyUrl?: boolean;
overwritesWatchRTCWSUrl?: boolean;
server?: string;
tenant?: string;
wasLobbyVisible?: boolean;
@@ -235,6 +267,21 @@ export function initAnalytics(store: IStore, handlers: Array<Object>): boolean {
permanentProperties.overwritesSalesforceUrl = 'config.salesforceUrl' in params;
permanentProperties.overwritesPeopleSearchUrl = 'config.peopleSearchUrl' in params;
permanentProperties.overwritesDefaultLogoUrl = 'config.defaultLogoUrl' in params;
permanentProperties.overwritesEtherpadBase = 'config.etherpad_base' in params;
const hosts = params['config.hosts'] ?? {};
const hostsProps = [ 'anonymousdomain', 'authdomain', 'domain', 'focus', 'muc', 'visitorFocus' ];
permanentProperties.overwritesHosts = 'config.hosts' in params
|| Boolean(hostsProps.find(p => `config.hosts.${p}` in params || (typeof hosts === 'object' && p in hosts)));
permanentProperties.overwritesWatchRTCConfigParams = 'config.watchRTCConfigParams' in params;
const watchRTCConfigParams = params['config.watchRTCConfigParams'] ?? {};
permanentProperties.overwritesWatchRTCProxyUrl = ('config.watchRTCConfigParams.proxyUrl' in params)
|| (typeof watchRTCConfigParams === 'object' && 'proxyUrl' in watchRTCConfigParams);
permanentProperties.overwritesWatchRTCWSUrl = ('config.watchRTCConfigParams.wsUrl' in params)
|| (typeof watchRTCConfigParams === 'object' && 'wsUrl' in watchRTCConfigParams);
const prejoinConfig = params['config.prejoinConfig'] ?? {};
permanentProperties.overwritesPrejoinConfigICEUrl = ('config.prejoinConfig.preCallTestICEUrl' in params)
@@ -260,6 +307,18 @@ export function initAnalytics(store: IStore, handlers: Array<Object>): boolean {
)
);
permanentProperties.overwritesIceServers = Boolean(Object.keys(params).find(k => k.startsWith('iceServers')));
const customToolbarButtons = params['config.customToolbarButtons'] ?? [];
permanentProperties.overwritesCustomButtonsWithURL = Boolean(
customToolbarButtons.find(({ icon }: { icon: string; }) => isDataURL(icon)));
const customParticipantMenuButtons = params['config.customParticipantMenuButtons'] ?? [];
permanentProperties.overwritesCustomParticipantButtonsWithURL = Boolean(
customParticipantMenuButtons.find(({ icon }: { icon: string; }) => isDataURL(icon)));
// Optionally, include local deployment information based on the
// contents of window.config.deploymentInfo.
if (deploymentInfo) {

View File

@@ -232,7 +232,7 @@ export default [
'useHostPageLocalStorage',
'useTurnUdp',
'videoQuality',
'visitors',
'visitors.enableMediaOnPromote',
'watchRTCConfigParams',
'webrtcIceTcpDisable',
'webrtcIceUdpDisable',

View File

@@ -834,16 +834,6 @@ export function toggleCamera() {
const localVideoTrack = getLocalVideoTrack(tracks)?.jitsiTrack;
const currentFacingMode = localVideoTrack.getCameraFacingMode();
const { localFlipX } = state['features/base/settings'];
/**
* FIXME: Ideally, we should be dispatching {@code replaceLocalTrack} here,
* but it seems to not trigger the re-rendering of the local video on Chrome;
* could be due to a plan B vs unified plan issue. Therefore, we use the legacy
* method defined in conference.js that manually takes care of updating the local
* video as well.
*/
await APP.conference.useVideoStream(null);
const targetFacingMode = currentFacingMode === CAMERA_FACING_MODE.USER
? CAMERA_FACING_MODE.ENVIRONMENT
: CAMERA_FACING_MODE.USER;
@@ -853,7 +843,6 @@ export function toggleCamera() {
const newVideoTrack = await createLocalTrack('video', null, null, { facingMode: targetFacingMode });
// FIXME: See above.
await APP.conference.useVideoStream(newVideoTrack);
dispatch(replaceLocalTrack(localVideoTrack, newVideoTrack));
};
}

View File

@@ -28,17 +28,6 @@ export const RESET_SHARED_VIDEO_STATUS = 'RESET_SHARED_VIDEO_STATUS';
*/
export const SET_CONFIRM_SHOW_VIDEO = 'SET_CONFIRM_SHOW_VIDEO';
/**
* The type of the action which signals to disable or enable the shared video
* button.
*
* {
* type: SET_DISABLE_BUTTON
* }
*/
export const SET_DISABLE_BUTTON = 'SET_DISABLE_BUTTON';
/**
* The type of the action which sets an array of whitelisted urls.
*

View File

@@ -1 +0,0 @@
export * from './actions.any';

View File

@@ -1,19 +0,0 @@
import { SET_DISABLE_BUTTON } from './actionTypes';
export * from './actions.any';
/**
* Disabled share video button.
*
* @param {boolean} disabled - The current state of the share video button.
* @returns {{
* type: SET_DISABLE_BUTTON,
* disabled: boolean
* }}
*/
export function setDisableButton(disabled: boolean) {
return {
type: SET_DISABLE_BUTTON,
disabled
};
}

View File

@@ -5,7 +5,7 @@ import { IReduxState, IStore } from '../../../app/types';
import { getCurrentConference } from '../../../base/conference/functions';
import { IJitsiConference } from '../../../base/conference/reducer';
import { getLocalParticipant } from '../../../base/participants/functions';
import { setSharedVideoStatus } from '../../actions.any';
import { setSharedVideoStatus } from '../../actions';
import { PLAYBACK_STATUSES } from '../../constants';
/**

View File

@@ -7,7 +7,7 @@ import { translate } from '../../../base/i18n/functions';
import { IconPlay } from '../../../base/icons/svg';
import { getLocalParticipant } from '../../../base/participants/functions';
import AbstractButton, { IProps as AbstractButtonProps } from '../../../base/toolbox/components/AbstractButton';
import { toggleSharedVideo } from '../../actions.native';
import { toggleSharedVideo } from '../../actions';
import { isSharingStatus } from '../../functions';
/**

View File

@@ -15,7 +15,7 @@ import { showWarningNotification } from '../../../notifications/actions';
import { NOTIFICATION_TIMEOUT_TYPE } from '../../../notifications/constants';
import { dockToolbox } from '../../../toolbox/actions';
import { muteLocal } from '../../../video-menu/actions.any';
import { setSharedVideoStatus, stopSharedVideo } from '../../actions.any';
import { setSharedVideoStatus, stopSharedVideo } from '../../actions';
import { PLAYBACK_STATUSES } from '../../constants';
const logger = Logger.getLogger(__filename);

View File

@@ -3,8 +3,9 @@ import { connect } from 'react-redux';
import { IReduxState } from '../../../app/types';
import { translate } from '../../../base/i18n/functions';
import { IconPlay } from '../../../base/icons/svg';
import { getLocalParticipant } from '../../../base/participants/functions';
import AbstractButton, { IProps as AbstractButtonProps } from '../../../base/toolbox/components/AbstractButton';
import { toggleSharedVideo } from '../../actions.any';
import { toggleSharedVideo } from '../../actions';
import { isSharingStatus } from '../../functions';
interface IProps extends AbstractButtonProps {
@@ -83,16 +84,14 @@ class SharedVideoButton extends AbstractButton<IProps> {
* @returns {IProps}
*/
function _mapStateToProps(state: IReduxState) {
const {
disabled: sharedVideoBtnDisabled,
status: sharedVideoStatus
} = state['features/shared-video'];
const { ownerId, status: sharedVideoStatus } = state['features/shared-video'];
const localParticipantId = getLocalParticipant(state)?.id;
const isSharing = isSharingStatus(sharedVideoStatus ?? '');
return {
_isDisabled: Boolean(sharedVideoBtnDisabled),
_sharingVideo: isSharingStatus(sharedVideoStatus ?? '')
_isDisabled: isSharing && ownerId !== localParticipantId,
_sharingVideo: isSharing
};
}
export default translate(connect(_mapStateToProps)(SharedVideoButton));

View File

@@ -1 +0,0 @@
import './middleware.any';

View File

@@ -22,7 +22,7 @@ import {
setAllowedUrlDomians,
setSharedVideoStatus,
showConfirmPlayingDialog
} from './actions.any';
} from './actions';
import {
DEFAULT_ALLOWED_URL_DOMAINS,
PLAYBACK_START,
@@ -55,27 +55,41 @@ MiddlewareRegistry.register(store => next => action => {
conference.addCommandListener(SHARED_VIDEO,
({ value, attributes }: { attributes: {
from: string; muted: string; state: string; time: string; }; value: string; }) => {
muted: string; state: string; time: string; }; value: string; },
from: string) => {
const state = getState();
const { from } = attributes;
const sharedVideoStatus = attributes.state;
const { ownerId } = state['features/shared-video'];
if (ownerId && ownerId !== from) {
logger.warn(
`User with id: ${from} sent shared video command: ${sharedVideoStatus} while we are playing.`);
return;
}
if (isSharingStatus(sharedVideoStatus)) {
// confirmShowVideo is undefined the first time we receive
// when confirmShowVideo is false we ignore everything except stop that resets it
if (getState()['features/shared-video'].confirmShowVideo === false) {
if (state['features/shared-video'].confirmShowVideo === false) {
return;
}
if (isURLAllowedForSharedVideo(value, getState()['features/shared-video'].allowedUrlDomains, true)
if (isURLAllowedForSharedVideo(value, state['features/shared-video'].allowedUrlDomains, true)
|| localParticipantId === from
|| getState()['features/shared-video'].confirmShowVideo) { // if confirmed skip asking again
handleSharingVideoStatus(store, value, attributes, conference);
|| state['features/shared-video'].confirmShowVideo) { // if confirmed skip asking again
handleSharingVideoStatus(store, value, {
...attributes,
from
}, conference);
} else {
dispatch(showConfirmPlayingDialog(getParticipantDisplayName(getState(), from), () => {
dispatch(showConfirmPlayingDialog(getParticipantDisplayName(state, from), () => {
handleSharingVideoStatus(store, value, attributes, conference);
handleSharingVideoStatus(store, value, {
...attributes,
from
}, conference);
return true; // on mobile this is used to close the dialog
}));
@@ -87,11 +101,11 @@ MiddlewareRegistry.register(store => next => action => {
if (sharedVideoStatus === 'stop') {
const videoParticipant = getParticipantById(state, value);
if (getState()['features/shared-video'].confirmShowVideo === false) {
if (state['features/shared-video'].confirmShowVideo === false) {
dispatch(showWarningNotification({
titleKey: 'dialog.shareVideoLinkStopped',
titleArguments: {
name: getParticipantDisplayName(getState(), from)
name: getParticipantDisplayName(state, from)
}
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
}

View File

@@ -1,41 +0,0 @@
import { CONFERENCE_JOIN_IN_PROGRESS } from '../base/conference/actionTypes';
import { getLocalParticipant } from '../base/participants/functions';
import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
import { setDisableButton } from './actions.web';
import { PLAYBACK_STATUSES, SHARED_VIDEO } from './constants';
import { isSharedVideoEnabled } from './functions';
import './middleware.any';
MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
const state = getState();
const localParticipantId = getLocalParticipant(state)?.id;
switch (action.type) {
case CONFERENCE_JOIN_IN_PROGRESS: {
if (!isSharedVideoEnabled(state)) {
break;
}
const { conference } = action;
conference.addCommandListener(SHARED_VIDEO, ({ attributes }: { attributes:
{ from: string; state: string; }; }) => {
const { from } = attributes;
const status = attributes.state;
if (status === PLAYBACK_STATUSES.PLAYING) {
if (localParticipantId !== from) {
dispatch(setDisableButton(true));
}
} else if (status === 'stop') {
dispatch(setDisableButton(false));
}
});
break;
}
}
return next(action);
});

View File

@@ -4,7 +4,6 @@ import {
RESET_SHARED_VIDEO_STATUS,
SET_ALLOWED_URL_DOMAINS,
SET_CONFIRM_SHOW_VIDEO,
SET_DISABLE_BUTTON,
SET_SHARED_VIDEO_STATUS
} from './actionTypes';
import { DEFAULT_ALLOWED_URL_DOMAINS } from './constants';
@@ -16,7 +15,6 @@ const initialState = {
export interface ISharedVideoState {
allowedUrlDomains: Array<string>;
confirmShowVideo?: boolean;
disabled?: boolean;
muted?: boolean;
ownerId?: string;
status?: string;
@@ -30,7 +28,7 @@ export interface ISharedVideoState {
*/
ReducerRegistry.register<ISharedVideoState>('features/shared-video',
(state = initialState, action): ISharedVideoState => {
const { videoUrl, status, time, ownerId, disabled, muted, volume } = action;
const { videoUrl, status, time, ownerId, muted, volume } = action;
switch (action.type) {
case RESET_SHARED_VIDEO_STATUS:
@@ -55,12 +53,6 @@ ReducerRegistry.register<ISharedVideoState>('features/shared-video',
volume
};
case SET_DISABLE_BUTTON:
return {
...state,
disabled
};
case SET_ALLOWED_URL_DOMAINS: {
return {
...state,

View File

@@ -9,7 +9,7 @@ import { isWhiteboardParticipant } from '../../../base/participants/functions';
import { IParticipant } from '../../../base/participants/types';
import ContextMenu from '../../../base/ui/components/web/ContextMenu';
import ContextMenuItemGroup from '../../../base/ui/components/web/ContextMenuItemGroup';
import { stopSharedVideo } from '../../../shared-video/actions.any';
import { stopSharedVideo } from '../../../shared-video/actions';
import { getParticipantMenuButtonsWithNotifyClick, showOverflowDrawer } from '../../../toolbox/functions.web';
import { NOTIFY_CLICK_MODE } from '../../../toolbox/types';
import { setWhiteboardOpen } from '../../../whiteboard/actions';

View File

@@ -41,7 +41,7 @@ module:hook("pre-iq/full", function(event)
session.granted_jitsi_meet_context_features,
occupant.role == 'moderator');
if jibri.attr.action == 'start' then
if jibri.attr.action == 'start' or jibri.attr.action == 'stop' then
if not is_allowed then
module:log('info', 'Filtering jibri start recording, stanza:%s', tostring(stanza));
session.send(st.error_reply(stanza, 'auth', 'forbidden'));

View File

@@ -141,7 +141,7 @@ module:hook('muc-occupant-pre-join', function (event)
else
occupant.role = 'visitor';
end
elseif room.moderators_list:contains(resource) then
elseif room.moderators_list and room.moderators_list:contains(resource) then
-- remote participants, host is the main prosody
occupant.role = 'moderator';
end

View File

@@ -115,7 +115,8 @@ module:hook("message/bare", function(event)
if room.polls.by_id[data.pollId] ~= nil then
module:log("error", "Poll already exists: %s", data.pollId);
return;
event.origin.send(st.error_reply(event.stanza, 'cancel', 'not-allowed', 'Poll already exists'));
return true;
end
local answers = {}