fix(remote-control): when multistream is enabled
This commit is contained in:
parent
e218c0d3af
commit
53e4f584f9
|
@ -2660,17 +2660,6 @@ export default {
|
|||
APP.UI.updateLargeVideo(displayedUserId, true);
|
||||
}
|
||||
});
|
||||
|
||||
// Used in non-multi-stream.
|
||||
APP.UI.addListener(
|
||||
UIEvents.TOGGLE_SCREENSHARING, ({ enabled, audioOnly, ignoreDidHaveVideo, desktopStream }) => {
|
||||
this.toggleScreenSharing(enabled,
|
||||
{
|
||||
audioOnly,
|
||||
desktopStream
|
||||
}, ignoreDidHaveVideo);
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -36,7 +36,8 @@ import {
|
|||
isTrackStreamingStatusInactive,
|
||||
isTrackStreamingStatusInterrupted
|
||||
} from '../../../react/features/connection-indicator/functions';
|
||||
import { FILMSTRIP_BREAKPOINT, getVerticalViewMaxWidth, isFilmstripResizable } from '../../../react/features/filmstrip';
|
||||
import { FILMSTRIP_BREAKPOINT } from '../../../react/features/filmstrip/constants';
|
||||
import { getVerticalViewMaxWidth, isFilmstripResizable } from '../../../react/features/filmstrip/functions';
|
||||
import {
|
||||
updateKnownLargeVideoResolution
|
||||
} from '../../../react/features/large-video/actions';
|
||||
|
|
|
@ -1,78 +1 @@
|
|||
// @flow
|
||||
|
||||
import { setPictureInPictureEnabled } from '../../mobile/picture-in-picture/functions';
|
||||
import { setAudioOnly } from '../audio-only';
|
||||
import JitsiMeetJS from '../lib-jitsi-meet';
|
||||
import { MiddlewareRegistry } from '../redux';
|
||||
import { TOGGLE_SCREENSHARING } from '../tracks/actionTypes';
|
||||
import { destroyLocalDesktopTrackIfExists, replaceLocalTrack } from '../tracks/actions';
|
||||
import { getLocalVideoTrack, isLocalVideoTrackDesktop } from '../tracks/functions';
|
||||
|
||||
import './middleware.any';
|
||||
|
||||
MiddlewareRegistry.register(store => next => action => {
|
||||
switch (action.type) {
|
||||
case TOGGLE_SCREENSHARING: {
|
||||
_toggleScreenSharing(action.enabled, store);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return next(action);
|
||||
});
|
||||
|
||||
/**
|
||||
* Toggles screen sharing.
|
||||
*
|
||||
* @private
|
||||
* @param {boolean} enabled - The state to toggle screen sharing to.
|
||||
* @param {Store} store - The redux.
|
||||
* @returns {void}
|
||||
*/
|
||||
function _toggleScreenSharing(enabled, store) {
|
||||
const { dispatch, getState } = store;
|
||||
const state = getState();
|
||||
|
||||
if (enabled) {
|
||||
const isSharing = isLocalVideoTrackDesktop(state);
|
||||
|
||||
if (!isSharing) {
|
||||
_startScreenSharing(dispatch, state);
|
||||
}
|
||||
} else {
|
||||
dispatch(destroyLocalDesktopTrackIfExists());
|
||||
setPictureInPictureEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates desktop track and replaces the local one.
|
||||
*
|
||||
* @private
|
||||
* @param {Dispatch} dispatch - The redux {@code dispatch} function.
|
||||
* @param {Object} state - The redux state.
|
||||
* @returns {void}
|
||||
*/
|
||||
function _startScreenSharing(dispatch, state) {
|
||||
setPictureInPictureEnabled(false);
|
||||
|
||||
JitsiMeetJS.createLocalTracks({ devices: [ 'desktop' ] })
|
||||
.then(tracks => {
|
||||
const track = tracks[0];
|
||||
const currentLocalTrack = getLocalVideoTrack(state['features/base/tracks']);
|
||||
const currentJitsiTrack = currentLocalTrack && currentLocalTrack.jitsiTrack;
|
||||
|
||||
dispatch(replaceLocalTrack(currentJitsiTrack, track));
|
||||
|
||||
const { enabled: audioOnly } = state['features/base/audio-only'];
|
||||
|
||||
if (audioOnly) {
|
||||
dispatch(setAudioOnly(false));
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.log('ERROR creating ScreeSharing stream ', error);
|
||||
|
||||
setPictureInPictureEnabled(true);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,48 +1,14 @@
|
|||
// @flow
|
||||
|
||||
import { AUDIO_ONLY_SCREEN_SHARE_NO_TRACK } from '../../../../modules/UI/UIErrors';
|
||||
import UIEvents from '../../../../service/UI/UIEvents';
|
||||
import { showModeratedNotification } from '../../av-moderation/actions';
|
||||
import { shouldShowModeratedNotification } from '../../av-moderation/functions';
|
||||
import { setNoiseSuppressionEnabled } from '../../noise-suppression/actions';
|
||||
import {
|
||||
NOTIFICATION_TIMEOUT_TYPE,
|
||||
isModerationNotificationDisplayed,
|
||||
showNotification
|
||||
} from '../../notifications';
|
||||
import {
|
||||
setPrejoinPageVisibility,
|
||||
setSkipPrejoinOnReload
|
||||
} from '../../prejoin';
|
||||
import {
|
||||
isAudioOnlySharing,
|
||||
isScreenVideoShared,
|
||||
setScreenAudioShareState,
|
||||
setScreenshareAudioTrack
|
||||
} from '../../screen-share';
|
||||
import { isScreenshotCaptureEnabled, toggleScreenshotCaptureSummary } from '../../screenshot-capture';
|
||||
import { AudioMixerEffect } from '../../stream-effects/audio-mixer/AudioMixerEffect';
|
||||
import { setAudioOnly } from '../audio-only';
|
||||
import { getMultipleVideoSendingSupportFeatureFlag } from '../config/functions.any';
|
||||
import { JitsiConferenceErrors, JitsiTrackErrors, JitsiTrackEvents } from '../lib-jitsi-meet';
|
||||
import { MEDIA_TYPE, VIDEO_TYPE, setScreenshareMuted } from '../media';
|
||||
import { JitsiConferenceErrors } from '../lib-jitsi-meet';
|
||||
import { MiddlewareRegistry } from '../redux';
|
||||
import {
|
||||
TOGGLE_SCREENSHARING,
|
||||
addLocalTrack,
|
||||
createLocalTracksF,
|
||||
getLocalDesktopTrack,
|
||||
getLocalJitsiAudioTrack,
|
||||
replaceLocalTrack,
|
||||
toggleScreensharing
|
||||
} from '../tracks';
|
||||
|
||||
import { CONFERENCE_FAILED, CONFERENCE_JOINED, CONFERENCE_JOIN_IN_PROGRESS } from './actionTypes';
|
||||
import { getCurrentConference } from './functions';
|
||||
import './middleware.any';
|
||||
|
||||
declare var APP: Object;
|
||||
|
||||
MiddlewareRegistry.register(store => next => action => {
|
||||
const { dispatch, getState } = store;
|
||||
const { enableForcedReload } = getState()['features/base/config'];
|
||||
|
@ -69,220 +35,7 @@ MiddlewareRegistry.register(store => next => action => {
|
|||
|
||||
break;
|
||||
}
|
||||
case TOGGLE_SCREENSHARING:
|
||||
if (typeof APP === 'object') {
|
||||
// check for A/V Moderation when trying to start screen sharing
|
||||
if ((action.enabled || action.enabled === undefined)
|
||||
&& shouldShowModeratedNotification(MEDIA_TYPE.VIDEO, store.getState())) {
|
||||
if (!isModerationNotificationDisplayed(MEDIA_TYPE.PRESENTER, store.getState())) {
|
||||
store.dispatch(showModeratedNotification(MEDIA_TYPE.PRESENTER));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const { enabled, audioOnly, ignoreDidHaveVideo, shareOptions } = action;
|
||||
|
||||
if (getMultipleVideoSendingSupportFeatureFlag(store.getState())) {
|
||||
_toggleScreenSharing(action, store);
|
||||
} else {
|
||||
APP.UI.emitEvent(UIEvents.TOGGLE_SCREENSHARING,
|
||||
{
|
||||
enabled,
|
||||
audioOnly,
|
||||
ignoreDidHaveVideo,
|
||||
desktopStream: shareOptions?.desktopStream
|
||||
});
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return next(action);
|
||||
});
|
||||
|
||||
/**
|
||||
* Displays a UI notification for screensharing failure based on the error passed.
|
||||
*
|
||||
* @private
|
||||
* @param {Object} error - The error.
|
||||
* @param {Object} store - The redux store.
|
||||
* @returns {void}
|
||||
*/
|
||||
function _handleScreensharingError(error, { dispatch }) {
|
||||
if (error.name === JitsiTrackErrors.SCREENSHARING_USER_CANCELED) {
|
||||
return;
|
||||
}
|
||||
let descriptionKey, titleKey;
|
||||
|
||||
if (error.name === JitsiTrackErrors.PERMISSION_DENIED) {
|
||||
descriptionKey = 'dialog.screenSharingPermissionDeniedError';
|
||||
titleKey = 'dialog.screenSharingFailedTitle';
|
||||
} else if (error.name === JitsiTrackErrors.CONSTRAINT_FAILED) {
|
||||
descriptionKey = 'dialog.cameraConstraintFailedError';
|
||||
titleKey = 'deviceError.cameraError';
|
||||
} else if (error.name === JitsiTrackErrors.SCREENSHARING_GENERIC_ERROR) {
|
||||
descriptionKey = 'dialog.screenSharingFailed';
|
||||
titleKey = 'dialog.screenSharingFailedTitle';
|
||||
} else if (error === AUDIO_ONLY_SCREEN_SHARE_NO_TRACK) {
|
||||
descriptionKey = 'notify.screenShareNoAudio';
|
||||
titleKey = 'notify.screenShareNoAudioTitle';
|
||||
}
|
||||
|
||||
dispatch(showNotification({
|
||||
titleKey,
|
||||
descriptionKey
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.MEDIUM));
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the AudioMixer effect on the local audio track if applicable. If there is no local audio track, the desktop
|
||||
* audio track is added to the conference.
|
||||
*
|
||||
* @private
|
||||
* @param {JitsiLocalTrack} desktopAudioTrack - The audio track to be added to the conference.
|
||||
* @param {*} state - The redux state.
|
||||
* @returns {void}
|
||||
*/
|
||||
async function _maybeApplyAudioMixerEffect(desktopAudioTrack, state) {
|
||||
const localAudio = getLocalJitsiAudioTrack(state);
|
||||
const conference = getCurrentConference(state);
|
||||
|
||||
if (localAudio) {
|
||||
// If there is a localAudio stream, mix in the desktop audio stream captured by the screen sharing API.
|
||||
const mixerEffect = new AudioMixerEffect(desktopAudioTrack);
|
||||
|
||||
await localAudio.setEffect(mixerEffect);
|
||||
} else {
|
||||
// If no local stream is present ( i.e. no input audio devices) we use the screen share audio
|
||||
// stream as we would use a regular stream.
|
||||
await conference.replaceTrack(null, desktopAudioTrack);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles screen sharing.
|
||||
*
|
||||
* @private
|
||||
* @param {boolean} enabled - The state to toggle screen sharing to.
|
||||
* @param {Store} store - The redux store.
|
||||
* @returns {void}
|
||||
*/
|
||||
async function _toggleScreenSharing({ enabled, audioOnly = false, shareOptions = {} }, store) {
|
||||
const { dispatch, getState } = store;
|
||||
const state = getState();
|
||||
const audioOnlySharing = isAudioOnlySharing(state);
|
||||
const screenSharing = isScreenVideoShared(state);
|
||||
const conference = getCurrentConference(state);
|
||||
const localAudio = getLocalJitsiAudioTrack(state);
|
||||
const localScreenshare = getLocalDesktopTrack(state['features/base/tracks']);
|
||||
|
||||
// Toggle screenshare or audio-only share if the new state is not passed. Happens in the following two cases.
|
||||
// 1. ShareAudioDialog passes undefined when the user hits continue in the share audio demo modal.
|
||||
// 2. Toggle screenshare called from the external API.
|
||||
const enable = audioOnly
|
||||
? enabled ?? !audioOnlySharing
|
||||
: enabled ?? !screenSharing;
|
||||
const screensharingDetails = {};
|
||||
|
||||
if (enable) {
|
||||
let tracks;
|
||||
|
||||
// Spot proxy stream.
|
||||
if (shareOptions.desktopStream) {
|
||||
tracks = [ shareOptions.desktopStream ];
|
||||
} else {
|
||||
const { _desktopSharingSourceDevice } = state['features/base/config'];
|
||||
|
||||
if (!shareOptions.desktopSharingSources && _desktopSharingSourceDevice) {
|
||||
shareOptions.desktopSharingSourceDevice = _desktopSharingSourceDevice;
|
||||
}
|
||||
const options = {
|
||||
devices: [ VIDEO_TYPE.DESKTOP ],
|
||||
...shareOptions
|
||||
};
|
||||
|
||||
try {
|
||||
tracks = await createLocalTracksF(options);
|
||||
} catch (error) {
|
||||
_handleScreensharingError(error, store);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const desktopAudioTrack = tracks.find(track => track.getType() === MEDIA_TYPE.AUDIO);
|
||||
const desktopVideoTrack = tracks.find(track => track.getType() === MEDIA_TYPE.VIDEO);
|
||||
|
||||
if (audioOnly) {
|
||||
// Dispose the desktop track for audio-only screensharing.
|
||||
desktopVideoTrack.dispose();
|
||||
|
||||
if (!desktopAudioTrack) {
|
||||
_handleScreensharingError(AUDIO_ONLY_SCREEN_SHARE_NO_TRACK, store);
|
||||
|
||||
return;
|
||||
}
|
||||
} else if (desktopVideoTrack) {
|
||||
if (localScreenshare) {
|
||||
await dispatch(replaceLocalTrack(localScreenshare.jitsiTrack, desktopVideoTrack, conference));
|
||||
} else {
|
||||
await dispatch(addLocalTrack(desktopVideoTrack));
|
||||
}
|
||||
if (isScreenshotCaptureEnabled(state, false, true)) {
|
||||
dispatch(toggleScreenshotCaptureSummary(true));
|
||||
}
|
||||
screensharingDetails.sourceType = desktopVideoTrack.sourceType;
|
||||
}
|
||||
|
||||
// Apply the AudioMixer effect if there is a local audio track, add the desktop track to the conference
|
||||
// otherwise without unmuting the microphone.
|
||||
if (desktopAudioTrack) {
|
||||
// Noise suppression doesn't work with desktop audio because we can't chain track effects yet, disable it
|
||||
// first. We need to to wait for the effect to clear first or it might interfere with the audio mixer.
|
||||
await dispatch(setNoiseSuppressionEnabled(false));
|
||||
_maybeApplyAudioMixerEffect(desktopAudioTrack, state);
|
||||
dispatch(setScreenshareAudioTrack(desktopAudioTrack));
|
||||
|
||||
// Handle the case where screen share was stopped from the browsers 'screen share in progress' window.
|
||||
if (audioOnly) {
|
||||
desktopAudioTrack?.on(
|
||||
JitsiTrackEvents.LOCAL_TRACK_STOPPED,
|
||||
() => dispatch(toggleScreensharing(undefined, true)));
|
||||
}
|
||||
}
|
||||
|
||||
// Disable audio-only or best performance mode if the user starts screensharing. This doesn't apply to
|
||||
// audio-only screensharing.
|
||||
const { enabled: bestPerformanceMode } = state['features/base/audio-only'];
|
||||
|
||||
if (bestPerformanceMode && !audioOnly) {
|
||||
dispatch(setAudioOnly(false));
|
||||
}
|
||||
} else {
|
||||
const { desktopAudioTrack } = state['features/screen-share'];
|
||||
|
||||
dispatch(toggleScreenshotCaptureSummary(false));
|
||||
|
||||
// Mute the desktop track instead of removing it from the conference since we don't want the client to signal
|
||||
// a source-remove to the remote peer for the screenshare track. Later when screenshare is enabled again, the
|
||||
// same sender will be re-used without the need for signaling a new ssrc through source-add.
|
||||
dispatch(setScreenshareMuted(true));
|
||||
if (desktopAudioTrack) {
|
||||
if (localAudio) {
|
||||
localAudio.setEffect(undefined);
|
||||
} else {
|
||||
await conference.replaceTrack(desktopAudioTrack, null);
|
||||
}
|
||||
desktopAudioTrack.dispose();
|
||||
dispatch(setScreenshareAudioTrack(null));
|
||||
}
|
||||
}
|
||||
|
||||
if (audioOnly) {
|
||||
dispatch(setScreenAudioShareState(enable));
|
||||
} else {
|
||||
// Notify the external API.
|
||||
APP.API.notifyScreenSharingStatusChanged(enable, screensharingDetails);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,16 +9,6 @@
|
|||
*/
|
||||
export const SET_NO_SRC_DATA_NOTIFICATION_UID = 'SET_NO_SRC_DATA_NOTIFICATION_UID';
|
||||
|
||||
/**
|
||||
* The type of redux action dispatched to disable screensharing or to start the
|
||||
* flow for enabling screenshare.
|
||||
*
|
||||
* {
|
||||
* type: TOGGLE_SCREENSHARING
|
||||
* }
|
||||
*/
|
||||
export const TOGGLE_SCREENSHARING = 'TOGGLE_SCREENSHARING';
|
||||
|
||||
/**
|
||||
* The type of redux action dispatched when a track has been (locally or
|
||||
* remotely) added to the conference.
|
||||
|
|
|
@ -24,7 +24,6 @@ import { updateSettings } from '../settings/actions';
|
|||
|
||||
import {
|
||||
SET_NO_SRC_DATA_NOTIFICATION_UID,
|
||||
TOGGLE_SCREENSHARING,
|
||||
TRACK_ADDED,
|
||||
TRACK_CREATE_CANCELED,
|
||||
TRACK_CREATE_ERROR,
|
||||
|
@ -298,32 +297,6 @@ export function showNoDataFromSourceVideoError(jitsiTrack: any) {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Signals that the local participant is ending screensharing or beginning the screensharing flow.
|
||||
*
|
||||
* @param {boolean} enabled - The state to toggle screen sharing to.
|
||||
* @param {boolean} audioOnly - Only share system audio.
|
||||
* @param {boolean} ignoreDidHaveVideo - Whether or not to ignore if video was on when sharing started.
|
||||
* @param {Object} shareOptions - The options to be passed for capturing screenshare.
|
||||
* @returns {{
|
||||
* type: TOGGLE_SCREENSHARING,
|
||||
* on: boolean,
|
||||
* audioOnly: boolean,
|
||||
* ignoreDidHaveVideo: boolean,
|
||||
* shareOptions: Object
|
||||
* }}
|
||||
*/
|
||||
export function toggleScreensharing(enabled: boolean | undefined, audioOnly = false,
|
||||
ignoreDidHaveVideo = false, shareOptions = {}) {
|
||||
return {
|
||||
type: TOGGLE_SCREENSHARING,
|
||||
enabled,
|
||||
audioOnly,
|
||||
ignoreDidHaveVideo,
|
||||
shareOptions
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces one track with another for one renegotiation instead of invoking
|
||||
* two renegotiations with a separate removeTrack and addTrack. Disposes the
|
||||
|
@ -396,7 +369,7 @@ function replaceStoredTracks(oldTrack: any, newTrack: any) {
|
|||
* conference.
|
||||
*
|
||||
* @param {(JitsiLocalTrack|JitsiRemoteTrack)} track - JitsiTrack instance.
|
||||
* @returns {{ type: TRACK_ADDED, track: Track }}
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function trackAdded(track: any) {
|
||||
return async (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||
|
@ -490,7 +463,13 @@ export function trackAdded(track: any) {
|
|||
* track: Track
|
||||
* }}
|
||||
*/
|
||||
export function trackMutedChanged(track: any) {
|
||||
export function trackMutedChanged(track: any): {
|
||||
track: {
|
||||
jitsiTrack: any;
|
||||
muted: boolean;
|
||||
};
|
||||
type: 'TRACK_UPDATED';
|
||||
} {
|
||||
return {
|
||||
type: TRACK_UPDATED,
|
||||
track: {
|
||||
|
@ -511,7 +490,11 @@ export function trackMutedChanged(track: any) {
|
|||
* track: Track
|
||||
* }}
|
||||
*/
|
||||
export function trackMuteUnmuteFailed(track: any, wasMuting: boolean) {
|
||||
export function trackMuteUnmuteFailed(track: any, wasMuting: boolean): {
|
||||
track: any;
|
||||
type: 'TRACK_MUTE_UNMUTE_FAILED';
|
||||
wasMuting: boolean;
|
||||
} {
|
||||
return {
|
||||
type: TRACK_MUTE_UNMUTE_FAILED,
|
||||
track,
|
||||
|
@ -549,7 +532,12 @@ export function trackNoDataFromSourceNotificationInfoChanged(track: any, noDataF
|
|||
* track: Track
|
||||
* }}
|
||||
*/
|
||||
export function trackRemoved(track: any) {
|
||||
export function trackRemoved(track: any): {
|
||||
track: {
|
||||
jitsiTrack: any;
|
||||
};
|
||||
type: 'TRACK_REMOVED';
|
||||
} {
|
||||
track.removeAllListeners(JitsiTrackEvents.TRACK_MUTE_CHANGED);
|
||||
track.removeAllListeners(JitsiTrackEvents.TRACK_VIDEOTYPE_CHANGED);
|
||||
track.removeAllListeners(JitsiTrackEvents.NO_DATA_FROM_SOURCE);
|
||||
|
@ -571,7 +559,13 @@ export function trackRemoved(track: any) {
|
|||
* track: Track
|
||||
* }}
|
||||
*/
|
||||
export function trackVideoStarted(track: any) {
|
||||
export function trackVideoStarted(track: any): {
|
||||
track: {
|
||||
jitsiTrack: any;
|
||||
videoStarted: true;
|
||||
};
|
||||
type: 'TRACK_UPDATED';
|
||||
} {
|
||||
return {
|
||||
type: TRACK_UPDATED,
|
||||
track: {
|
||||
|
@ -611,7 +605,13 @@ export function trackVideoTypeChanged(track: any, videoType: VideoType) {
|
|||
* track: Track
|
||||
* }}
|
||||
*/
|
||||
export function trackStreamingStatusChanged(track: any, streamingStatus: string) {
|
||||
export function trackStreamingStatusChanged(track: any, streamingStatus: string): {
|
||||
track: {
|
||||
jitsiTrack: any;
|
||||
streamingStatus: string;
|
||||
};
|
||||
type: 'TRACK_UPDATED';
|
||||
} {
|
||||
return {
|
||||
type: TRACK_UPDATED,
|
||||
track: {
|
||||
|
@ -644,7 +644,7 @@ function _addTracks(tracks: any[]) {
|
|||
* about here is to be sure that the {@code getUserMedia} callbacks have
|
||||
* completed (i.e. Returned from the native side).
|
||||
*/
|
||||
function _cancelGUMProcesses(getState: IStore['getState']) {
|
||||
function _cancelGUMProcesses(getState: IStore['getState']): Promise<any> {
|
||||
const logError
|
||||
= (error: Error) =>
|
||||
logger.error('gumProcess.cancel failed', JSON.stringify(error));
|
||||
|
@ -678,9 +678,9 @@ export function _disposeAndRemoveTracks(tracks: any[]) {
|
|||
* @returns {Promise} - A Promise resolved once {@link JitsiTrack.dispose()} is
|
||||
* done for every track from the list.
|
||||
*/
|
||||
function _disposeTracks(tracks: any) {
|
||||
function _disposeTracks(tracks: any[]): Promise<any> {
|
||||
return Promise.all(
|
||||
tracks.map((t: any) =>
|
||||
tracks.map(t =>
|
||||
t.dispose()
|
||||
.catch((err: Error) => {
|
||||
// Track might be already disposed so ignore such an error.
|
||||
|
@ -701,7 +701,7 @@ function _disposeTracks(tracks: any) {
|
|||
* @private
|
||||
* @returns {Function}
|
||||
*/
|
||||
function _onCreateLocalTracksRejected(error: Error, device: string) {
|
||||
function _onCreateLocalTracksRejected(error?: Error, device?: string) {
|
||||
return (dispatch: IStore['dispatch']) => {
|
||||
// If permissions are not allowed, alert the user.
|
||||
dispatch({
|
||||
|
@ -728,7 +728,7 @@ function _onCreateLocalTracksRejected(error: Error, device: string) {
|
|||
* @private
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function _shouldMirror(track: any) {
|
||||
function _shouldMirror(track: any): boolean {
|
||||
return (
|
||||
track?.isLocal()
|
||||
&& track?.isVideoTrack()
|
||||
|
@ -755,7 +755,10 @@ function _shouldMirror(track: any) {
|
|||
* trackType: MEDIA_TYPE
|
||||
* }}
|
||||
*/
|
||||
function _trackCreateCanceled(mediaType: MediaType) {
|
||||
function _trackCreateCanceled(mediaType: MediaType): {
|
||||
trackType: MediaType;
|
||||
type: 'TRACK_CREATE_CANCELED';
|
||||
} {
|
||||
return {
|
||||
type: TRACK_CREATE_CANCELED,
|
||||
trackType: mediaType
|
||||
|
@ -806,7 +809,11 @@ export function setNoSrcDataNotificationUid(uid?: string) {
|
|||
* name: string
|
||||
* }}
|
||||
*/
|
||||
export function updateLastTrackVideoMediaEvent(track: any, name: string) {
|
||||
export function updateLastTrackVideoMediaEvent(track: any, name: string): {
|
||||
name: string;
|
||||
track: any;
|
||||
type: 'TRACK_UPDATE_LAST_VIDEO_MEDIA_EVENT';
|
||||
} {
|
||||
return {
|
||||
type: TRACK_UPDATE_LAST_VIDEO_MEDIA_EVENT,
|
||||
track,
|
|
@ -0,0 +1,80 @@
|
|||
/* eslint-disable lines-around-comment */
|
||||
import { IState, IStore } from '../../app/types';
|
||||
// @ts-ignore
|
||||
import { setPictureInPictureEnabled } from '../../mobile/picture-in-picture/functions';
|
||||
// @ts-ignore
|
||||
import { setAudioOnly } from '../audio-only';
|
||||
import JitsiMeetJS from '../lib-jitsi-meet';
|
||||
|
||||
import { destroyLocalDesktopTrackIfExists, replaceLocalTrack } from './actions.any';
|
||||
// @ts-ignore
|
||||
import { getLocalVideoTrack, isLocalVideoTrackDesktop } from './functions';
|
||||
/* eslint-enable lines-around-comment */
|
||||
|
||||
export * from './actions.any';
|
||||
|
||||
/**
|
||||
* Signals that the local participant is ending screensharing or beginning the screensharing flow.
|
||||
*
|
||||
* @param {boolean} enabled - The state to toggle screen sharing to.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function toggleScreensharing(enabled: boolean): Function {
|
||||
return (store: IStore) => _toggleScreenSharing(enabled, store);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles screen sharing.
|
||||
*
|
||||
* @private
|
||||
* @param {boolean} enabled - The state to toggle screen sharing to.
|
||||
* @param {Store} store - The redux.
|
||||
* @returns {void}
|
||||
*/
|
||||
function _toggleScreenSharing(enabled: boolean, store: IStore): void {
|
||||
const { dispatch, getState } = store;
|
||||
const state = getState();
|
||||
|
||||
if (enabled) {
|
||||
const isSharing = isLocalVideoTrackDesktop(state);
|
||||
|
||||
if (!isSharing) {
|
||||
_startScreenSharing(dispatch, state);
|
||||
}
|
||||
} else {
|
||||
dispatch(destroyLocalDesktopTrackIfExists());
|
||||
setPictureInPictureEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates desktop track and replaces the local one.
|
||||
*
|
||||
* @private
|
||||
* @param {Dispatch} dispatch - The redux {@code dispatch} function.
|
||||
* @param {Object} state - The redux state.
|
||||
* @returns {void}
|
||||
*/
|
||||
function _startScreenSharing(dispatch: Function, state: IState) {
|
||||
setPictureInPictureEnabled(false);
|
||||
|
||||
JitsiMeetJS.createLocalTracks({ devices: [ 'desktop' ] })
|
||||
.then((tracks: any[]) => {
|
||||
const track = tracks[0];
|
||||
const currentLocalTrack = getLocalVideoTrack(state['features/base/tracks']);
|
||||
const currentJitsiTrack = currentLocalTrack?.jitsiTrack;
|
||||
|
||||
dispatch(replaceLocalTrack(currentJitsiTrack, track));
|
||||
|
||||
const { enabled: audioOnly } = state['features/base/audio-only'];
|
||||
|
||||
if (audioOnly) {
|
||||
dispatch(setAudioOnly(false));
|
||||
}
|
||||
})
|
||||
.catch((error: any) => {
|
||||
console.log('ERROR creating ScreeSharing stream ', error);
|
||||
|
||||
setPictureInPictureEnabled(true);
|
||||
});
|
||||
}
|
|
@ -0,0 +1,284 @@
|
|||
/* eslint-disable lines-around-comment */
|
||||
// @ts-ignore
|
||||
import { AUDIO_ONLY_SCREEN_SHARE_NO_TRACK } from '../../../../modules/UI/UIErrors';
|
||||
import { IState, IStore } from '../../app/types';
|
||||
import { showModeratedNotification } from '../../av-moderation/actions';
|
||||
import { shouldShowModeratedNotification } from '../../av-moderation/functions';
|
||||
import { setNoiseSuppressionEnabled } from '../../noise-suppression/actions';
|
||||
import { showNotification } from '../../notifications/actions';
|
||||
import { NOTIFICATION_TIMEOUT_TYPE } from '../../notifications/constants';
|
||||
import { isModerationNotificationDisplayed } from '../../notifications/functions';
|
||||
// @ts-ignore
|
||||
import { stopReceiver } from '../../remote-control/actions';
|
||||
// @ts-ignore
|
||||
import { setScreenAudioShareState, setScreenshareAudioTrack } from '../../screen-share/actions';
|
||||
import { isAudioOnlySharing, isScreenVideoShared } from '../../screen-share/functions';
|
||||
// @ts-ignore
|
||||
import { isScreenshotCaptureEnabled, toggleScreenshotCaptureSummary } from '../../screenshot-capture';
|
||||
// @ts-ignore
|
||||
import { AudioMixerEffect } from '../../stream-effects/audio-mixer/AudioMixerEffect';
|
||||
import { setAudioOnly } from '../audio-only/actions';
|
||||
import { getCurrentConference } from '../conference/functions';
|
||||
import { getMultipleVideoSendingSupportFeatureFlag } from '../config/functions.any';
|
||||
import { JitsiTrackErrors, JitsiTrackEvents } from '../lib-jitsi-meet';
|
||||
import { setScreenshareMuted } from '../media/actions';
|
||||
import { MEDIA_TYPE, VIDEO_TYPE } from '../media/constants';
|
||||
/* eslint-enable lines-around-comment */
|
||||
|
||||
import {
|
||||
addLocalTrack,
|
||||
replaceLocalTrack
|
||||
} from './actions.any';
|
||||
import {
|
||||
createLocalTracksF,
|
||||
getLocalDesktopTrack,
|
||||
getLocalJitsiAudioTrack
|
||||
} from './functions';
|
||||
import { ShareOptions, ToggleScreenSharingOptions } from './types';
|
||||
|
||||
export * from './actions.any';
|
||||
|
||||
declare const APP: any;
|
||||
|
||||
/**
|
||||
* Signals that the local participant is ending screensharing or beginning the screensharing flow.
|
||||
*
|
||||
* @param {boolean} enabled - The state to toggle screen sharing to.
|
||||
* @param {boolean} audioOnly - Only share system audio.
|
||||
* @param {boolean} ignoreDidHaveVideo - Whether or not to ignore if video was on when sharing started.
|
||||
* @param {Object} shareOptions - The options to be passed for capturing screenshare.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function toggleScreensharing(
|
||||
enabled?: boolean,
|
||||
audioOnly = false,
|
||||
ignoreDidHaveVideo = false,
|
||||
shareOptions: ShareOptions = {}) {
|
||||
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||
// check for A/V Moderation when trying to start screen sharing
|
||||
if ((enabled || enabled === undefined)
|
||||
&& shouldShowModeratedNotification(MEDIA_TYPE.VIDEO, getState())) {
|
||||
if (!isModerationNotificationDisplayed(MEDIA_TYPE.PRESENTER, getState())) {
|
||||
dispatch(showModeratedNotification(MEDIA_TYPE.PRESENTER));
|
||||
}
|
||||
|
||||
return Promise.reject();
|
||||
}
|
||||
|
||||
if (getMultipleVideoSendingSupportFeatureFlag(getState())) {
|
||||
return _toggleScreenSharing({
|
||||
enabled,
|
||||
audioOnly,
|
||||
shareOptions
|
||||
}, {
|
||||
dispatch,
|
||||
getState
|
||||
});
|
||||
}
|
||||
|
||||
return APP.conference.toggleScreenSharing(enabled, {
|
||||
audioOnly,
|
||||
desktopStream: shareOptions?.desktopStream
|
||||
}, ignoreDidHaveVideo);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a UI notification for screensharing failure based on the error passed.
|
||||
*
|
||||
* @private
|
||||
* @param {Object} error - The error.
|
||||
* @param {Object} store - The redux store.
|
||||
* @returns {void}
|
||||
*/
|
||||
function _handleScreensharingError(
|
||||
error: Error | AUDIO_ONLY_SCREEN_SHARE_NO_TRACK,
|
||||
{ dispatch }: IStore): void {
|
||||
if (error.name === JitsiTrackErrors.SCREENSHARING_USER_CANCELED) {
|
||||
return;
|
||||
}
|
||||
let descriptionKey, titleKey;
|
||||
|
||||
if (error.name === JitsiTrackErrors.PERMISSION_DENIED) {
|
||||
descriptionKey = 'dialog.screenSharingPermissionDeniedError';
|
||||
titleKey = 'dialog.screenSharingFailedTitle';
|
||||
} else if (error.name === JitsiTrackErrors.CONSTRAINT_FAILED) {
|
||||
descriptionKey = 'dialog.cameraConstraintFailedError';
|
||||
titleKey = 'deviceError.cameraError';
|
||||
} else if (error.name === JitsiTrackErrors.SCREENSHARING_GENERIC_ERROR) {
|
||||
descriptionKey = 'dialog.screenSharingFailed';
|
||||
titleKey = 'dialog.screenSharingFailedTitle';
|
||||
} else if (error === AUDIO_ONLY_SCREEN_SHARE_NO_TRACK) {
|
||||
descriptionKey = 'notify.screenShareNoAudio';
|
||||
titleKey = 'notify.screenShareNoAudioTitle';
|
||||
}
|
||||
|
||||
dispatch(showNotification({
|
||||
titleKey,
|
||||
descriptionKey
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.MEDIUM));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Applies the AudioMixer effect on the local audio track if applicable. If there is no local audio track, the desktop
|
||||
* audio track is added to the conference.
|
||||
*
|
||||
* @private
|
||||
* @param {JitsiLocalTrack} desktopAudioTrack - The audio track to be added to the conference.
|
||||
* @param {*} state - The redux state.
|
||||
* @returns {void}
|
||||
*/
|
||||
async function _maybeApplyAudioMixerEffect(desktopAudioTrack: any, state: IState): Promise<void> {
|
||||
const localAudio = getLocalJitsiAudioTrack(state);
|
||||
const conference = getCurrentConference(state);
|
||||
|
||||
if (localAudio) {
|
||||
// If there is a localAudio stream, mix in the desktop audio stream captured by the screen sharing API.
|
||||
const mixerEffect = new AudioMixerEffect(desktopAudioTrack);
|
||||
|
||||
await localAudio.setEffect(mixerEffect);
|
||||
} else {
|
||||
// If no local stream is present ( i.e. no input audio devices) we use the screen share audio
|
||||
// stream as we would use a regular stream.
|
||||
await conference.replaceTrack(null, desktopAudioTrack);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Toggles screen sharing.
|
||||
*
|
||||
* @private
|
||||
* @param {boolean} enabled - The state to toggle screen sharing to.
|
||||
* @param {Store} store - The redux store.
|
||||
* @returns {void}
|
||||
*/
|
||||
async function _toggleScreenSharing(
|
||||
{
|
||||
enabled,
|
||||
audioOnly = false,
|
||||
shareOptions = {}
|
||||
}: ToggleScreenSharingOptions,
|
||||
store: IStore
|
||||
): Promise<void> {
|
||||
const { dispatch, getState } = store;
|
||||
const state = getState();
|
||||
const audioOnlySharing = isAudioOnlySharing(state);
|
||||
const screenSharing = isScreenVideoShared(state);
|
||||
const conference = getCurrentConference(state);
|
||||
const localAudio = getLocalJitsiAudioTrack(state);
|
||||
const localScreenshare = getLocalDesktopTrack(state['features/base/tracks']);
|
||||
|
||||
// Toggle screenshare or audio-only share if the new state is not passed. Happens in the following two cases.
|
||||
// 1. ShareAudioDialog passes undefined when the user hits continue in the share audio demo modal.
|
||||
// 2. Toggle screenshare called from the external API.
|
||||
const enable = audioOnly
|
||||
? enabled ?? !audioOnlySharing
|
||||
: enabled ?? !screenSharing;
|
||||
const screensharingDetails: { sourceType?: string; } = {};
|
||||
|
||||
if (enable) {
|
||||
let tracks;
|
||||
|
||||
// Spot proxy stream.
|
||||
if (shareOptions.desktopStream) {
|
||||
tracks = [ shareOptions.desktopStream ];
|
||||
} else {
|
||||
const { _desktopSharingSourceDevice } = state['features/base/config'];
|
||||
|
||||
if (!shareOptions.desktopSharingSources && _desktopSharingSourceDevice) {
|
||||
shareOptions.desktopSharingSourceDevice = _desktopSharingSourceDevice;
|
||||
}
|
||||
|
||||
const options = {
|
||||
devices: [ VIDEO_TYPE.DESKTOP ],
|
||||
...shareOptions
|
||||
};
|
||||
|
||||
try {
|
||||
tracks = await createLocalTracksF(options) as any[];
|
||||
} catch (error) {
|
||||
_handleScreensharingError(error as any, store);
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
const desktopAudioTrack = tracks.find(track => track.getType() === MEDIA_TYPE.AUDIO);
|
||||
const desktopVideoTrack = tracks.find(track => track.getType() === MEDIA_TYPE.VIDEO);
|
||||
|
||||
if (audioOnly) {
|
||||
// Dispose the desktop track for audio-only screensharing.
|
||||
desktopVideoTrack.dispose();
|
||||
|
||||
if (!desktopAudioTrack) {
|
||||
_handleScreensharingError(AUDIO_ONLY_SCREEN_SHARE_NO_TRACK, store);
|
||||
|
||||
throw new Error(AUDIO_ONLY_SCREEN_SHARE_NO_TRACK);
|
||||
}
|
||||
} else if (desktopVideoTrack) {
|
||||
if (localScreenshare) {
|
||||
await dispatch(replaceLocalTrack(localScreenshare.jitsiTrack, desktopVideoTrack, conference));
|
||||
} else {
|
||||
await dispatch(addLocalTrack(desktopVideoTrack));
|
||||
}
|
||||
if (isScreenshotCaptureEnabled(state, false, true)) {
|
||||
dispatch(toggleScreenshotCaptureSummary(true));
|
||||
}
|
||||
screensharingDetails.sourceType = desktopVideoTrack.sourceType;
|
||||
}
|
||||
|
||||
// Apply the AudioMixer effect if there is a local audio track, add the desktop track to the conference
|
||||
// otherwise without unmuting the microphone.
|
||||
if (desktopAudioTrack) {
|
||||
// Noise suppression doesn't work with desktop audio because we can't chain track effects yet, disable it
|
||||
// first. We need to to wait for the effect to clear first or it might interfere with the audio mixer.
|
||||
await dispatch(setNoiseSuppressionEnabled(false));
|
||||
_maybeApplyAudioMixerEffect(desktopAudioTrack, state);
|
||||
dispatch(setScreenshareAudioTrack(desktopAudioTrack));
|
||||
|
||||
// Handle the case where screen share was stopped from the browsers 'screen share in progress' window.
|
||||
if (audioOnly) {
|
||||
desktopAudioTrack?.on(
|
||||
JitsiTrackEvents.LOCAL_TRACK_STOPPED,
|
||||
() => dispatch(toggleScreensharing(undefined, true)));
|
||||
}
|
||||
}
|
||||
|
||||
// Disable audio-only or best performance mode if the user starts screensharing. This doesn't apply to
|
||||
// audio-only screensharing.
|
||||
const { enabled: bestPerformanceMode } = state['features/base/audio-only'];
|
||||
|
||||
if (bestPerformanceMode && !audioOnly) {
|
||||
dispatch(setAudioOnly(false));
|
||||
}
|
||||
} else {
|
||||
const { desktopAudioTrack } = state['features/screen-share'];
|
||||
|
||||
dispatch(stopReceiver());
|
||||
|
||||
dispatch(toggleScreenshotCaptureSummary(false));
|
||||
|
||||
// Mute the desktop track instead of removing it from the conference since we don't want the client to signal
|
||||
// a source-remove to the remote peer for the screenshare track. Later when screenshare is enabled again, the
|
||||
// same sender will be re-used without the need for signaling a new ssrc through source-add.
|
||||
dispatch(setScreenshareMuted(true));
|
||||
if (desktopAudioTrack) {
|
||||
if (localAudio) {
|
||||
localAudio.setEffect(undefined);
|
||||
} else {
|
||||
await conference.replaceTrack(desktopAudioTrack, null);
|
||||
}
|
||||
desktopAudioTrack.dispose();
|
||||
dispatch(setScreenshareAudioTrack(null));
|
||||
}
|
||||
}
|
||||
|
||||
if (audioOnly) {
|
||||
dispatch(setScreenAudioShareState(enable));
|
||||
} else {
|
||||
// Notify the external API.
|
||||
APP.API.notifyScreenSharingStatusChanged(enable, screensharingDetails);
|
||||
}
|
||||
}
|
|
@ -1,5 +1,3 @@
|
|||
// @flow
|
||||
|
||||
import { getLogger } from '../logging/functions';
|
||||
|
||||
export default getLogger('features/base/tracks');
|
||||
|
|
|
@ -42,6 +42,8 @@ import {
|
|||
trackMuteUnmuteFailed,
|
||||
trackNoDataFromSourceNotificationInfoChanged,
|
||||
trackRemoved
|
||||
|
||||
// @ts-ignore
|
||||
} from './actions';
|
||||
import {
|
||||
getLocalTrack,
|
||||
|
|
|
@ -17,3 +17,15 @@ export interface TrackOptions {
|
|||
micDeviceId?: string | null;
|
||||
timeout?: number;
|
||||
}
|
||||
|
||||
export interface ToggleScreenSharingOptions {
|
||||
audioOnly: boolean;
|
||||
enabled?: boolean;
|
||||
shareOptions: ShareOptions;
|
||||
}
|
||||
|
||||
export interface ShareOptions {
|
||||
desktopSharingSourceDevice?: string;
|
||||
desktopSharingSources?: string[];
|
||||
desktopStream?: any;
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ import { Prejoin, isPrejoinPageVisible } from '../../../prejoin';
|
|||
import { toggleToolboxVisible } from '../../../toolbox/actions.any';
|
||||
import { fullScreenChanged, showToolbox } from '../../../toolbox/actions.web';
|
||||
import { JitsiPortal, Toolbox } from '../../../toolbox/components/web';
|
||||
import { LAYOUTS, getCurrentLayout } from '../../../video-layout';
|
||||
import { LAYOUT_CLASSNAMES, getCurrentLayout } from '../../../video-layout';
|
||||
import { maybeShowSuboptimalExperienceNotification } from '../../functions';
|
||||
import {
|
||||
AbstractConference,
|
||||
|
@ -48,20 +48,6 @@ const FULL_SCREEN_EVENTS = [
|
|||
'fullscreenchange'
|
||||
];
|
||||
|
||||
/**
|
||||
* The CSS class to apply to the root element of the conference so CSS can
|
||||
* modify the app layout.
|
||||
*
|
||||
* @private
|
||||
* @type {Object}
|
||||
*/
|
||||
export const LAYOUT_CLASSNAMES = {
|
||||
[LAYOUTS.HORIZONTAL_FILMSTRIP_VIEW]: 'horizontal-filmstrip',
|
||||
[LAYOUTS.TILE_VIEW]: 'tile-view',
|
||||
[LAYOUTS.VERTICAL_FILMSTRIP_VIEW]: 'vertical-filmstrip',
|
||||
[LAYOUTS.STAGE_FILMSTRIP_VIEW]: 'stage-filmstrip'
|
||||
};
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link Conference}.
|
||||
*/
|
||||
|
|
|
@ -2,8 +2,7 @@
|
|||
import React from 'react';
|
||||
|
||||
import { connect } from '../../../base/redux';
|
||||
import { LAYOUT_CLASSNAMES } from '../../../conference/components/web/Conference';
|
||||
import { LAYOUTS, getCurrentLayout } from '../../../video-layout';
|
||||
import { LAYOUTS, LAYOUT_CLASSNAMES, getCurrentLayout } from '../../../video-layout';
|
||||
import {
|
||||
FILMSTRIP_TYPE
|
||||
} from '../../constants';
|
||||
|
|
|
@ -4,8 +4,7 @@ import React from 'react';
|
|||
import { getToolbarButtons } from '../../../base/config';
|
||||
import { isMobileBrowser } from '../../../base/environment/utils';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { LAYOUT_CLASSNAMES } from '../../../conference/components/web/Conference';
|
||||
import { LAYOUTS, getCurrentLayout } from '../../../video-layout';
|
||||
import { LAYOUTS, LAYOUT_CLASSNAMES, getCurrentLayout } from '../../../video-layout';
|
||||
import {
|
||||
ASPECT_RATIO_BREAKPOINT,
|
||||
FILMSTRIP_TYPE,
|
||||
|
|
|
@ -32,6 +32,7 @@ import {
|
|||
import { Participant } from '../../../base/participants/types';
|
||||
import { ASPECT_RATIO_NARROW } from '../../../base/responsive-ui/constants';
|
||||
import { isTestModeEnabled } from '../../../base/testing/functions';
|
||||
// @ts-ignore
|
||||
import { trackStreamingStatusChanged, updateLastTrackVideoMediaEvent } from '../../../base/tracks/actions';
|
||||
import {
|
||||
getLocalAudioTrack,
|
||||
|
|
|
@ -5,9 +5,15 @@ import $ from 'jquery';
|
|||
import { getMultipleVideoSendingSupportFeatureFlag } from '../base/config/functions.any';
|
||||
import { openDialog } from '../base/dialog';
|
||||
import { JitsiConferenceEvents } from '../base/lib-jitsi-meet';
|
||||
import { getParticipantDisplayName, getPinnedParticipant, pinParticipant } from '../base/participants';
|
||||
import {
|
||||
getParticipantDisplayName,
|
||||
getPinnedParticipant,
|
||||
getVirtualScreenshareParticipantByOwnerId,
|
||||
pinParticipant
|
||||
} from '../base/participants';
|
||||
import { getLocalDesktopTrack, getLocalVideoTrack, toggleScreensharing } from '../base/tracks';
|
||||
import { NOTIFICATION_TIMEOUT_TYPE, showNotification } from '../notifications';
|
||||
import { isScreenVideoShared } from '../screen-share';
|
||||
|
||||
import {
|
||||
CAPTURE_EVENTS,
|
||||
|
@ -198,9 +204,12 @@ export function processPermissionRequestReply(participantId: string, event: Obje
|
|||
// the remote control permissions has been granted
|
||||
// pin the controlled participant
|
||||
const pinnedParticipant = getPinnedParticipant(state);
|
||||
const virtualScreenshareParticipantId = getVirtualScreenshareParticipantByOwnerId(state, participantId);
|
||||
const pinnedId = pinnedParticipant?.id;
|
||||
|
||||
if (pinnedId !== participantId) {
|
||||
if (virtualScreenshareParticipantId && pinnedId !== virtualScreenshareParticipantId) {
|
||||
dispatch(pinParticipant(virtualScreenshareParticipantId));
|
||||
} else if (!virtualScreenshareParticipantId && pinnedId !== participantId) {
|
||||
dispatch(pinParticipant(participantId));
|
||||
}
|
||||
}
|
||||
|
@ -508,6 +517,10 @@ export function sendStartRequest() {
|
|||
const { sourceId } = track?.jitsiTrack || {};
|
||||
const { transport } = state['features/remote-control'].receiver;
|
||||
|
||||
if (typeof sourceId === 'undefined') {
|
||||
return Promise.reject(new Error('Cannot identify screen for the remote control session'));
|
||||
}
|
||||
|
||||
return transport.sendRequest({
|
||||
name: REMOTE_CONTROL_MESSAGE_NAME,
|
||||
type: REQUESTS.start,
|
||||
|
@ -536,7 +549,7 @@ export function grant(participantId: string) {
|
|||
const tracks = state['features/base/tracks'];
|
||||
const isMultiStreamSupportEnabled = getMultipleVideoSendingSupportFeatureFlag(state);
|
||||
const track = isMultiStreamSupportEnabled ? getLocalDesktopTrack(tracks) : getLocalVideoTrack(tracks);
|
||||
const isScreenSharing = track?.videoType === 'desktop';
|
||||
const isScreenSharing = isScreenVideoShared(state);
|
||||
const { sourceType } = track?.jitsiTrack || {};
|
||||
|
||||
if (isScreenSharing && sourceType === 'screen') {
|
||||
|
|
|
@ -6,6 +6,8 @@ import { translate } from '../../base/i18n/functions';
|
|||
import { connect } from '../../base/redux/functions';
|
||||
import { updateSettings } from '../../base/settings/actions';
|
||||
import { shouldHideShareAudioHelper } from '../../base/settings/functions.any';
|
||||
// eslint-disable-next-line lines-around-comment
|
||||
// @ts-ignore
|
||||
import { toggleScreensharing } from '../../base/tracks/actions';
|
||||
import Checkbox from '../../base/ui/components/web/Checkbox';
|
||||
import Dialog from '../../base/ui/components/web/Dialog';
|
||||
|
|
|
@ -4,6 +4,8 @@ import { WithTranslation } from 'react-i18next';
|
|||
import { IStore } from '../../app/types';
|
||||
import { translate } from '../../base/i18n/functions';
|
||||
import { connect } from '../../base/redux/functions';
|
||||
// eslint-disable-next-line lines-around-comment
|
||||
// @ts-ignore
|
||||
import { toggleScreensharing } from '../../base/tracks/actions';
|
||||
import Dialog from '../../base/ui/components/web/Dialog';
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ import {
|
|||
|
||||
export interface IScreenShareState {
|
||||
captureFrameRate?: number;
|
||||
desktopAudioTrack?: Object;
|
||||
desktopAudioTrack?: any;
|
||||
isSharingAudio?: boolean;
|
||||
}
|
||||
|
||||
|
|
|
@ -9,3 +9,16 @@ export const LAYOUTS = {
|
|||
VERTICAL_FILMSTRIP_VIEW: 'vertical-filmstrip-view',
|
||||
STAGE_FILMSTRIP_VIEW: 'stage-filmstrip-view'
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* The CSS class to apply so CSS can modify the app layout.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
export const LAYOUT_CLASSNAMES = {
|
||||
[LAYOUTS.HORIZONTAL_FILMSTRIP_VIEW]: 'horizontal-filmstrip',
|
||||
[LAYOUTS.TILE_VIEW]: 'tile-view',
|
||||
[LAYOUTS.VERTICAL_FILMSTRIP_VIEW]: 'vertical-filmstrip',
|
||||
[LAYOUTS.STAGE_FILMSTRIP_VIEW]: 'stage-filmstrip'
|
||||
};
|
||||
|
|
|
@ -40,7 +40,6 @@ export default {
|
|||
*/
|
||||
TOGGLE_FILMSTRIP: 'UI.toggle_filmstrip',
|
||||
|
||||
TOGGLE_SCREENSHARING: 'UI.toggle_screensharing',
|
||||
HANGUP: 'UI.hangup',
|
||||
LOGOUT: 'UI.logout',
|
||||
VIDEO_DEVICE_CHANGED: 'UI.video_device_changed',
|
||||
|
|
Loading…
Reference in New Issue