Compare commits

..

3 Commits

Author SHA1 Message Date
titus.moldovan
d5916cdb3a e2ee stuff 2022-06-23 15:18:13 +03:00
tmoldovan8x8
9a99c517ab fix(rn, pip) enables PiP on conference mounted 2022-06-23 14:42:41 +03:00
Robert Pintilii
40a6240444 feat(local-recording) Update config (#11731)
Enable feature by default
2022-06-23 14:03:21 +03:00
30 changed files with 197 additions and 442 deletions

View File

@@ -24,6 +24,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.ReadableMap;
import com.oney.WebRTCModule.WebRTCModule;
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
@@ -125,18 +126,14 @@ public class JitsiMeetView extends BaseReactView<JitsiMeetViewListener>
* page.
*/
public void enterPictureInPicture() {
PictureInPictureModule pipModule
= ReactInstanceManagerHolder.getNativeModule(
PictureInPictureModule.class);
if (pipModule != null
&& pipModule.isPictureInPictureSupported()
&& !JitsiMeetActivityDelegate.arePermissionsBeingRequested()
&& this.url != null) {
try {
pipModule.enterPictureInPicture();
} catch (RuntimeException re) {
JitsiMeetLogger.e(re, "Failed to enter PiP mode");
}
try {
WebRTCModule pipModule
= ReactInstanceManagerHolder.getNativeModule(
WebRTCModule.class);
pipModule.addDecryptors();
}
catch (Exception e) {
int a = 1;
}
}

View File

@@ -28,6 +28,7 @@ import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.module.annotations.ReactModule;
import com.oney.WebRTCModule.WebRTCModule;
import org.jitsi.meet.sdk.log.JitsiMeetLogger;
@@ -84,34 +85,10 @@ class PictureInPictureModule extends ReactContextBaseJavaModule {
*/
@TargetApi(Build.VERSION_CODES.O)
public void enterPictureInPicture() {
if (!isEnabled) {
return;
}
if (!isSupported) {
throw new IllegalStateException("Picture-in-Picture not supported");
}
Activity currentActivity = getCurrentActivity();
if (currentActivity == null) {
throw new IllegalStateException("No current Activity!");
}
JitsiMeetLogger.i(TAG + " Entering Picture-in-Picture");
PictureInPictureParams.Builder builder
= new PictureInPictureParams.Builder()
.setAspectRatio(new Rational(1, 1));
// https://developer.android.com/reference/android/app/Activity.html#enterPictureInPictureMode(android.app.PictureInPictureParams)
//
// The system may disallow entering picture-in-picture in various cases,
// including when the activity is not visible, if the screen is locked
// or if the user has an activity pinned.
if (!currentActivity.enterPictureInPictureMode(builder.build())) {
throw new RuntimeException("Failed to enter Picture-in-Picture");
}
WebRTCModule pipModule
= ReactInstanceManagerHolder.getNativeModule(
WebRTCModule.class);
pipModule.addDecryptors();
}
/**
@@ -123,12 +100,10 @@ class PictureInPictureModule extends ReactContextBaseJavaModule {
*/
@ReactMethod
public void enterPictureInPicture(Promise promise) {
try {
enterPictureInPicture();
promise.resolve(null);
} catch (RuntimeException re) {
promise.reject(re);
}
WebRTCModule pipModule
= ReactInstanceManagerHolder.getNativeModule(
WebRTCModule.class);
pipModule.addDecryptors();
}
@ReactMethod

View File

@@ -35,6 +35,8 @@ import com.oney.WebRTCModule.RTCVideoViewManager;
import com.oney.WebRTCModule.WebRTCModule;
import org.devio.rn.splashscreen.SplashScreenModule;
import org.webrtc.Loggable;
import org.webrtc.Logging;
import org.webrtc.SoftwareVideoDecoderFactory;
import org.webrtc.SoftwareVideoEncoderFactory;
import org.webrtc.audio.AudioDeviceModule;
@@ -57,6 +59,13 @@ class ReactInstanceManagerHolder {
*/
private static ReactInstanceManager reactInstanceManager;
private static Loggable webrtcLogger = new Loggable() {
@Override
public void onLogMessage(String message, Logging.Severity severity, String tag) {
Log.d(tag,message);
}
};
private static List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
List<NativeModule> nativeModules
= new ArrayList<>(Arrays.<NativeModule>asList(
@@ -88,6 +97,8 @@ class ReactInstanceManagerHolder {
options.setVideoDecoderFactory(new SoftwareVideoDecoderFactory());
options.setVideoEncoderFactory(new SoftwareVideoEncoderFactory());
options.setInjectableLogger(webrtcLogger);
options.setLoggingSeverity(Logging.Severity.LS_VERBOSE);
nativeModules.add(new WebRTCModule(reactContext, options));

View File

@@ -271,9 +271,8 @@ var config = {
// Recording
// DEPRECATED. Use recordingService.enabled instead.
// Whether to enable file recording or not.
// fileRecordingsEnabled: false,
// Enable the dropbox integration.
// dropbox: {
// appKey: '<APP_KEY>' // Specify your app key here.
@@ -283,27 +282,14 @@ var config = {
// redirectURI:
// 'https://jitsi-meet.example.com/subfolder/static/oauth.html'
// },
// recordingService: {
// // When integrations like dropbox are enabled only that will be shown,
// // by enabling fileRecordingsServiceEnabled, we show both the integrations
// // and the generic recording service (its configuration and storage type
// // depends on jibri configuration)
// enabled: false,
// // Whether to show the possibility to share file recording with other people
// // (e.g. meeting participants), based on the actual implementation
// // on the backend.
// sharingEnabled: false,
// // Hide the warning that says we only store the recording for 24 hours.
// hideStorageWarning: false
// },
// DEPRECATED. Use recordingService.enabled instead.
// When integrations like dropbox are enabled only that will be shown,
// by enabling fileRecordingsServiceEnabled, we show both the integrations
// and the generic recording service (its configuration and storage type
// depends on jibri configuration)
// fileRecordingsServiceEnabled: false,
// DEPRECATED. Use recordingService.sharingEnabled instead.
// Whether to show the possibility to share file recording with other people
// (e.g. meeting participants), based on the actual implementation
// on the backend.
// fileRecordingsServiceSharingEnabled: false,
// Whether to enable live streaming or not.
@@ -311,8 +297,8 @@ var config = {
// Local recording configuration.
// localRecording: {
// // Whether to enable local recording or not.
// enable: false,
// // Whether to disable local recording or not.
// disable: false,
// // Whether to notify all participants when a participant is recording locally.
// notifyAllParticipants: false
// },

View File

@@ -19,10 +19,6 @@
font-size: 14px;
margin-left: 16px;
}
&.space-top {
margin-top: 10px;
}
}
.recording-header-line {

View File

@@ -895,19 +895,12 @@
"linkGenerated": "We have generated a link to your recording.",
"live": "LIVE",
"localRecordingNoNotificationWarning": "The recording will not be announced to other participants. You will need to let them know that the meeting is recorded.",
"localRecordingNoVideo": "Video is not being recorded",
"localRecordingStartWarning": "Please make sure you stop the recording before exiting the meeting in order to save it.",
"localRecordingStartWarningTitle": "Stop the recording to save it",
"localRecordingVideoStop": "Stopping your video will also stop the local recording. Are you sure you want to continue?",
"localRecordingVideoWarning": "To record your video you must have it on when starting the recording",
"localRecordingWarning": "Make sure you select the current tab in order to use the right video and audio. The recording is currently limited to 1GB, which is around 100 minutes.",
"loggedIn": "Logged in as {{userName}}",
"noStreams": "No audio or video stream detected.",
"off": "Recording stopped",
"offBy": "{{name}} stopped the recording",
"on": "Recording started",
"onBy": "{{name}} started the recording",
"onlyRecordSelf": "Record only my audio and video streams",
"pending": "Preparing to record the meeting...",
"rec": "REC",
"saveLocalRecording": "Save recording file locally",

View File

@@ -3,7 +3,6 @@
import '../authentication/middleware';
import '../base/i18n/middleware';
import '../base/devices/middleware';
import '../base/media/middleware';
import '../dynamic-branding/middleware';
import '../e2ee/middleware';
import '../external-api/middleware';

View File

@@ -353,20 +353,6 @@ function _translateLegacyConfig(oldValue: Object) {
newValue.defaultRemoteDisplayName
= newValue.defaultRemoteDisplayName || 'Fellow Jitster';
newValue.recordingService = newValue.recordingService || {};
if (oldValue.fileRecordingsServiceEnabled !== undefined) {
newValue.recordingService = {
...newValue.recordingService,
enabled: oldValue.fileRecordingsServiceEnabled
};
}
if (oldValue.fileRecordingsServiceSharingEnabled !== undefined) {
newValue.recordingService = {
...newValue.recordingService,
sharingEnabled: oldValue.fileRecordingsServiceSharingEnabled
};
}
return newValue;
}

View File

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

View File

@@ -1,40 +0,0 @@
import './middleware.any.js';
// @ts-ignore
import { MiddlewareRegistry } from '../redux';
import { IStore } from '../../app/types';
import { SET_VIDEO_MUTED } from './actionTypes';
import LocalRecordingManager from '../../recording/components/Recording/LocalRecordingManager.web';
// @ts-ignore
import { openDialog } from '../dialog';
// @ts-ignore
import { NOTIFICATION_TIMEOUT_TYPE, showNotification } from '../../notifications';
// @ts-ignore
import StopRecordingDialog from '../../recording/components/Recording/web/StopRecordingDialog';
/**
* Implements the entry point of the middleware of the feature base/media.
*
* @param {IStore} store - The redux store.
* @returns {Function}
*/
MiddlewareRegistry.register((store: IStore) => (next: Function) => (action: any) => {
const { dispatch } = store;
switch(action.type) {
case SET_VIDEO_MUTED: {
if (LocalRecordingManager.isRecordingLocally() && LocalRecordingManager.selfRecording.on) {
if (action.muted && LocalRecordingManager.selfRecording.withVideo) {
dispatch(openDialog(StopRecordingDialog, { localRecordingVideoStop: true }));
return;
} else if (!action.muted && !LocalRecordingManager.selfRecording.withVideo) {
dispatch(showNotification({
titleKey: 'recording.localRecordingNoVideo',
descriptionKey: 'recording.localRecordingVideoWarning',
uid: 'recording.localRecordingNoVideo'
}, NOTIFICATION_TIMEOUT_TYPE.MEDIUM));
}
}
}
}
return next(action);
});

View File

@@ -236,8 +236,7 @@ export const OVERWRITE_PARTICIPANTS_NAMES = 'OVERWRITE_PARTICIPANTS_NAMES';
* Updates participants local recording status.
* {
* type: SET_LOCAL_PARTICIPANT_RECORDING_STATUS,
* recording: boolean,
* onlySelf: boolean
* recording: boolean
* }
*/
export const SET_LOCAL_PARTICIPANT_RECORDING_STATUS = 'SET_LOCAL_PARTICIPANT_RECORDING_STATUS';

View File

@@ -689,16 +689,14 @@ export function overwriteParticipantsNames(participantList) {
* Local video recording status for the local participant.
*
* @param {boolean} recording - If local recording is ongoing.
* @param {boolean} onlySelf - If recording only local streams.
* @returns {{
* type: SET_LOCAL_PARTICIPANT_RECORDING_STATUS,
* recording: boolean
* }}
*/
export function updateLocalRecordingStatus(recording, onlySelf) {
export function updateLocalRecordingStatus(recording) {
return {
type: SET_LOCAL_PARTICIPANT_RECORDING_STATUS,
recording,
onlySelf
recording
};
}

View File

@@ -179,11 +179,11 @@ MiddlewareRegistry.register(store => next => action => {
case SET_LOCAL_PARTICIPANT_RECORDING_STATUS: {
const state = store.getState();
const { recording, onlySelf } = action;
const { recording } = action;
const localId = getLocalParticipant(state)?.id;
const { localRecording } = state['features/base/config'];
if (localRecording?.notifyAllParticipants && !onlySelf) {
if (localRecording.notifyAllParticipants) {
store.dispatch(participantUpdated({
// XXX Only the local participant is allowed to update without
// stating the JitsiConference instance (i.e. participant property

View File

@@ -3,4 +3,6 @@
/**
* The default server URL to open if no other was specified.
*/
export const DEFAULT_SERVER_URL = 'https://meet.jit.si';
//export const DEFAULT_SERVER_URL = 'https://abora6.jitsi.net#config.replaceParticipant=true';
export const DEFAULT_SERVER_URL = 'https://alpha.jitsi.net';
//export const DEFAULT_SERVER_URL = 'https://meet.jit.si';

View File

@@ -189,7 +189,7 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
}
case TOGGLE_E2EE: {
if (conference && conference.isE2EEEnabled() !== action.enabled) {
if (conference) {
logger.debug(`E2EE will be ${action.enabled ? 'enabled' : 'disabled'}`);
conference.toggleE2EE(action.enabled);

View File

@@ -1,7 +1,7 @@
import { GiphyContent, GiphyGridView, GiphyMediaType } from '@giphy/react-native-sdk';
import React, { useCallback, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Image, Text, View } from 'react-native';
import { Image, Keyboard, Text, View } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { useDispatch } from 'react-redux';
import { createGifSentEvent, sendAnalytics } from '../../../analytics';
@@ -16,7 +16,7 @@ import styles from './styles';
const GifsMenu = () => {
const [ searchQuery, setSearchQuery ] = useState('');
const dispatch = useDispatch();
const { t } = useTranslation();
const insets = useSafeAreaInsets();
const content = searchQuery === ''
? GiphyContent.trending({ mediaType: GiphyMediaType.Gif })
@@ -34,32 +34,33 @@ const GifsMenu = () => {
goBack();
}, []);
const onScroll = useCallback(Keyboard.dismiss, []);
const footerComponent = () => (
<View style = { styles.credit }>
return (<JitsiScreen
style = { styles.container }>
<ClearableInput
autoFocus = { true }
customStyles = { styles.clearableInput }
onChange = { setSearchQuery }
placeholder = 'Search GIPHY'
value = { searchQuery } />
<GiphyGridView
cellPadding = { 5 }
content = { content }
onMediaSelect = { sendGif }
onScroll = { onScroll }
style = { styles.grid } />
<View
style = { [ styles.credit, {
bottom: insets.bottom,
left: insets.left,
right: insets.right
} ] }>
<Text
style = { styles.creditText }>Powered by</Text>
<Image source = { require('../../../../../images/GIPHY_logo.png') } />
</View>
);
return (
<JitsiScreen
/* eslint-disable-next-line react/jsx-no-bind */
footerComponent = { footerComponent }
style = { styles.container }>
<ClearableInput
customStyles = { styles.clearableInput }
onChange = { setSearchQuery }
placeholder = { t('giphy.search') }
value = { searchQuery } />
<GiphyGridView
cellPadding = { 5 }
content = { content }
onMediaSelect = { sendGif }
style = { styles.grid } />
</JitsiScreen>
);
</JitsiScreen>);
};
export default GifsMenu;

View File

@@ -22,15 +22,15 @@ export default {
},
credit: {
alignItems: 'center',
backgroundColor: BaseTheme.palette.ui01,
width: '100%',
height: 40,
position: 'absolute',
marginBottom: 0,
display: 'flex',
flexDirection: 'row',
height: 56,
justifyContent: 'center',
marginBottom: BaseTheme.spacing[0],
paddingBottom: BaseTheme.spacing[4],
width: '100%'
alignItems: 'center',
justifyContent: 'center'
},
creditText: {

View File

@@ -26,6 +26,7 @@ export function enterPictureInPicture() {
// fine to enter PiP mode.
if (getFeatureFlag(getState, PIP_ENABLED)) {
const { PictureInPicture } = NativeModules;
console.log("XXX enter PIP")
const p
= Platform.OS === 'android'
? PictureInPicture

View File

@@ -71,8 +71,7 @@ export const SET_MEETING_HIGHLIGHT_BUTTON_STATE = 'SET_MEETING_HIGHLIGHT_BUTTON_
* Attempts to start the local recording.
*
* {
* type: START_LOCAL_RECORDING,
* onlySelf: boolean
* type: START_LOCAL_RECORDING
* }
*/
export const START_LOCAL_RECORDING = 'START_LOCAL_RECORDING';

View File

@@ -338,13 +338,11 @@ function _setPendingRecordingNotificationUid(uid: ?number, streamType: string) {
/**
* Starts local recording.
*
* @param {boolean} onlySelf - Whether to only record the local streams.
* @returns {Object}
*/
export function startLocalVideoRecording(onlySelf) {
export function startLocalVideoRecording() {
return {
type: START_LOCAL_RECORDING,
onlySelf
type: START_LOCAL_RECORDING
};
}

View File

@@ -139,7 +139,6 @@ class AbstractStartRecordingDialog extends Component<Props, State> {
= this._onSelectedRecordingServiceChanged.bind(this);
this._onSharingSettingChanged = this._onSharingSettingChanged.bind(this);
this._toggleScreenshotCapture = this._toggleScreenshotCapture.bind(this);
this._onLocalRecordingSelfChange = this._onLocalRecordingSelfChange.bind(this);
let selectedRecordingService;
@@ -158,8 +157,7 @@ class AbstractStartRecordingDialog extends Component<Props, State> {
userName: undefined,
sharingEnabled: true,
spaceLeft: undefined,
selectedRecordingService,
localRecordingOnlySelf: false
selectedRecordingService
};
}
@@ -213,19 +211,6 @@ class AbstractStartRecordingDialog extends Component<Props, State> {
});
}
_onLocalRecordingSelfChange: () => void;
/**
* Callback to handle local recording only self setting change.
*
* @returns {void}
*/
_onLocalRecordingSelfChange() {
this.setState({
localRecordingOnlySelf: !this.state.localRecordingOnlySelf
});
}
_onSelectedRecordingServiceChanged: (string) => void;
/**
@@ -341,7 +326,7 @@ class AbstractStartRecordingDialog extends Component<Props, State> {
break;
}
case RECORDING_TYPES.LOCAL: {
dispatch(startLocalVideoRecording(this.state.localRecordingOnlySelf));
dispatch(startLocalVideoRecording());
return true;
}
@@ -405,7 +390,8 @@ class AbstractStartRecordingDialog extends Component<Props, State> {
export function mapStateToProps(state: Object) {
const {
autoCaptionOnRecord = false,
recordingService,
fileRecordingsServiceEnabled = false,
fileRecordingsServiceSharingEnabled = false,
dropbox = {}
} = state['features/base/config'];
@@ -413,8 +399,8 @@ export function mapStateToProps(state: Object) {
_appKey: dropbox.appKey,
_autoCaptionOnRecord: autoCaptionOnRecord,
_conference: state['features/base/conference'].conference,
_fileRecordingsServiceEnabled: recordingService?.enabled ?? false,
_fileRecordingsServiceSharingEnabled: recordingService?.sharingEnabled ?? false,
_fileRecordingsServiceEnabled: fileRecordingsServiceEnabled,
_fileRecordingsServiceSharingEnabled: fileRecordingsServiceSharingEnabled,
_isDropboxEnabled: isDropboxEnabled(state),
_rToken: state['features/dropbox'].rToken,
_tokenExpireDate: state['features/dropbox'].expireDate,

View File

@@ -7,7 +7,6 @@ import {
sendAnalytics
} from '../../../analytics';
import { JitsiRecordingConstants } from '../../../base/lib-jitsi-meet';
import { setVideoMuted } from '../../../base/media';
import { stopLocalVideoRecording } from '../../actions';
import { getActiveSession } from '../../functions';
@@ -39,11 +38,6 @@ export type Props = {
*/
dispatch: Function,
/**
* The user trying to stop the video while local recording is running.
*/
localRecordingVideoStop?: boolean,
/**
* Invoked to obtain translated strings.
*/
@@ -84,9 +78,6 @@ export default class AbstractStopRecordingDialog<P: Props>
if (this.props._localRecording) {
this.props.dispatch(stopLocalVideoRecording());
if (this.props.localRecordingVideoStop) {
this.props.dispatch(setVideoMuted(true));
}
} else {
const { _fileRecordingSession } = this.props;

View File

@@ -6,23 +6,16 @@ import { getRoomName } from '../../../base/conference';
// @ts-ignore
import { MEDIA_TYPE } from '../../../base/media';
// @ts-ignore
import { getTrackState, getLocalTrack } from '../../../base/tracks';
import { getTrackState } from '../../../base/tracks';
import { inIframe } from '../../../base/util/iframeUtils';
// @ts-ignore
import { stopLocalVideoRecording } from '../../actions.any';
declare var APP: any;
interface IReduxStore {
dispatch: Function;
getState: Function;
}
interface SelfRecording {
on: boolean;
withVideo: boolean;
}
interface ILocalRecordingManager {
recordingData: Blob[];
recorder: MediaRecorder|undefined;
@@ -37,10 +30,9 @@ interface ILocalRecordingManager {
getFilename: () => string;
saveRecording: (recordingData: Blob[], filename: string) => void;
stopLocalRecording: () => void;
startLocalRecording: (store: IReduxStore, onlySelf: boolean) => void;
startLocalRecording: (store: IReduxStore) => void;
isRecordingLocally: () => boolean;
totalSize: number;
selfRecording: SelfRecording;
}
const getMimeType = (): string => {
@@ -71,10 +63,6 @@ const LocalRecordingManager: ILocalRecordingManager = {
audioDestination: undefined,
roomName: '',
totalSize: 1073741824, // 1GB in bytes
selfRecording: {
on: false,
withVideo: false
},
get mediaType() {
if (!preferredMediaType) {
@@ -105,9 +93,6 @@ const LocalRecordingManager: ILocalRecordingManager = {
* Adds audio track to the recording stream.
*/
addAudioTrackToLocalRecording(track) {
if(this.selfRecording.on) {
return;
}
if (track) {
const stream = new MediaStream([ track ]);
@@ -158,85 +143,58 @@ const LocalRecordingManager: ILocalRecordingManager = {
/**
* Starts a local recording.
*/
async startLocalRecording(store, onlySelf) {
async startLocalRecording(store) {
const { dispatch, getState } = store;
// @ts-ignore
const supportsCaptureHandle = Boolean(navigator.mediaDevices.setCaptureHandleConfig) && !inIframe();
const tabId = uuidV4();
this.selfRecording.on = onlySelf;
this.recordingData = [];
this.roomName = getRoomName(getState());
let gdmStream: MediaStream = new MediaStream();
const tracks = getTrackState(getState());
if(onlySelf) {
let audioTrack: MediaStreamTrack | undefined = getLocalTrack(tracks, MEDIA_TYPE.AUDIO)?.jitsiTrack?.track;
let videoTrack: MediaStreamTrack | undefined = getLocalTrack(tracks, MEDIA_TYPE.VIDEO)?.jitsiTrack?.track;
if(!audioTrack) {
APP.conference.muteAudio(false);
setTimeout(() => APP.conference.muteAudio(true), 100);
await new Promise((resolve) => {
setTimeout(resolve, 100);
});
}
if(videoTrack && videoTrack.readyState !== 'live') {
videoTrack = undefined;
}
audioTrack = getLocalTrack(getTrackState(getState()), MEDIA_TYPE.AUDIO)?.jitsiTrack?.track;
if(!audioTrack && !videoTrack) {
throw new Error('NoLocalStreams')
}
this.selfRecording.withVideo = Boolean(videoTrack);
const localTracks = [];
audioTrack && localTracks.push(audioTrack);
videoTrack && localTracks.push(videoTrack);
this.stream = new MediaStream(localTracks);
} else {
if (supportsCaptureHandle) {
// @ts-ignore
navigator.mediaDevices.setCaptureHandleConfig({
handle: `JitsiMeet-${tabId}`,
permittedOrigins: [ '*' ]
});
}
if (supportsCaptureHandle) {
// @ts-ignore
gdmStream = await navigator.mediaDevices.getDisplayMedia({
// @ts-ignore
video: { displaySurface: 'browser', frameRate: 30 },
audio: {
autoGainControl: false,
channelCount: 2,
echoCancellation: false,
noiseSuppression: false
}
navigator.mediaDevices.setCaptureHandleConfig({
handle: `JitsiMeet-${tabId}`,
permittedOrigins: [ '*' ]
});
// @ts-ignore
const isBrowser = gdmStream.getVideoTracks()[0].getSettings().displaySurface === 'browser';
if (!isBrowser || (supportsCaptureHandle // @ts-ignore
&& gdmStream.getVideoTracks()[0].getCaptureHandle()?.handle !== `JitsiMeet-${tabId}`)) {
gdmStream.getTracks().forEach((track: MediaStreamTrack) => track.stop());
throw new Error('WrongSurfaceSelected');
}
this.initializeAudioMixer();
this.mixAudioStream(gdmStream);
tracks.forEach((track: any) => {
if (track.mediaType === MEDIA_TYPE.AUDIO) {
const audioTrack = track?.jitsiTrack?.track;
this.addAudioTrackToLocalRecording(audioTrack);
}
});
this.stream = new MediaStream([
...(this.audioDestination?.stream.getAudioTracks() || []),
gdmStream.getVideoTracks()[0]
]);
}
this.recordingData = [];
// @ts-ignore
const gdmStream = await navigator.mediaDevices.getDisplayMedia({
// @ts-ignore
video: { displaySurface: 'browser', frameRate: 30 },
audio: {
autoGainControl: false,
channelCount: 2,
echoCancellation: false,
noiseSuppression: false
}
});
// @ts-ignore
const isBrowser = gdmStream.getVideoTracks()[0].getSettings().displaySurface === 'browser';
if (!isBrowser || (supportsCaptureHandle // @ts-ignore
&& gdmStream.getVideoTracks()[0].getCaptureHandle()?.handle !== `JitsiMeet-${tabId}`)) {
gdmStream.getTracks().forEach((track: MediaStreamTrack) => track.stop());
throw new Error('WrongSurfaceSelected');
}
this.initializeAudioMixer();
this.mixAudioStream(gdmStream);
this.roomName = getRoomName(getState());
const tracks = getTrackState(getState());
tracks.forEach((track: any) => {
if (track.mediaType === MEDIA_TYPE.AUDIO) {
const audioTrack = track?.jitsiTrack?.track;
this.addAudioTrackToLocalRecording(audioTrack);
}
});
this.stream = new MediaStream([
...(this.audioDestination?.stream.getAudioTracks() || []),
gdmStream.getVideoTracks()[0]
]);
this.recorder = new MediaRecorder(this.stream, {
mimeType: this.mediaType,
videoBitsPerSecond: VIDEO_BIT_RATE
@@ -251,20 +209,18 @@ const LocalRecordingManager: ILocalRecordingManager = {
}
});
if(!onlySelf) {
this.recorder.addEventListener('stop', () => {
this.stream?.getTracks().forEach((track: MediaStreamTrack) => track.stop());
gdmStream?.getTracks().forEach((track: MediaStreamTrack) => track.stop());
});
this.recorder.addEventListener('stop', () => {
this.stream?.getTracks().forEach((track: MediaStreamTrack) => track.stop());
gdmStream.getTracks().forEach((track: MediaStreamTrack) => track.stop());
});
gdmStream?.addEventListener('inactive', () => {
dispatch(stopLocalVideoRecording());
});
gdmStream.addEventListener('inactive', () => {
dispatch(stopLocalVideoRecording());
});
this.stream.addEventListener('inactive', () => {
dispatch(stopLocalVideoRecording());
});
}
this.stream.addEventListener('inactive', () => {
dispatch(stopLocalVideoRecording());
});
this.recorder.start(5000);
},

View File

@@ -44,11 +44,6 @@ type Props = {
*/
_dialogStyles: StyleType,
/**
* Whether to hide the storage warning or not.
*/
_hideStorageWarning: boolean,
/**
* Whether local recording is enabled or not.
*/
@@ -101,22 +96,12 @@ type Props = {
*/
isVpaas: boolean,
/**
* Whether or not we should only record the local streams.
*/
localRecordingOnlySelf: boolean,
/**
* The function will be called when there are changes related to the
* switches.
*/
onChange: Function,
/**
* Callback to change the local recording only self setting.
*/
onLocalRecordingSelfChange: Function,
/**
* Callback to be invoked on sharing setting change.
*/
@@ -216,15 +201,9 @@ class StartRecordingDialogContent extends Component<Props> {
* @returns {boolean}
*/
_shouldRenderFileSharingContent() {
const {
fileRecordingsServiceEnabled,
fileRecordingsServiceSharingEnabled,
isVpaas,
selectedRecordingService
} = this.props;
const { fileRecordingsServiceSharingEnabled, isVpaas, selectedRecordingService } = this.props;
if (!fileRecordingsServiceEnabled
|| !fileRecordingsServiceSharingEnabled
if (!fileRecordingsServiceSharingEnabled
|| isVpaas
|| selectedRecordingService !== RECORDING_TYPES.JITSI_REC_SERVICE) {
return false;
@@ -291,14 +270,13 @@ class StartRecordingDialogContent extends Component<Props> {
_renderUploadToTheCloudInfo() {
const {
_dialogStyles,
_hideStorageWarning,
_styles: styles,
isVpaas,
selectedRecordingService,
t
} = this.props;
if (!(isVpaas && selectedRecordingService === RECORDING_TYPES.JITSI_REC_SERVICE) || _hideStorageWarning) {
if (!(isVpaas && selectedRecordingService === RECORDING_TYPES.JITSI_REC_SERVICE)) {
return null;
}
@@ -330,8 +308,9 @@ class StartRecordingDialogContent extends Component<Props> {
*/
_shouldRenderNoIntegrationsContent() {
// show the non integrations part only if fileRecordingsServiceEnabled
// is enabled
if (!this.props.fileRecordingsServiceEnabled) {
// is enabled or when there are no integrations enabled
if (!(this.props.fileRecordingsServiceEnabled
|| !this.props.integrationsEnabled)) {
return false;
}
@@ -650,76 +629,45 @@ class StartRecordingDialogContent extends Component<Props> {
}
return (
<>
<Container>
<Container>
<Container
className = 'recording-header recording-header-line'
style = { styles.header }>
<Container
className = 'recording-header recording-header-line'
style = { styles.header }>
<Container
className = 'recording-icon-container'>
<Image
className = 'recording-icon'
src = { LOCAL_RECORDING }
style = { styles.recordingIcon } />
</Container>
<Text
className = 'recording-title'
style = {{
..._dialogStyles.text,
...styles.title
}}>
{ t('recording.saveLocalRecording') }
</Text>
<Switch
className = 'recording-switch'
disabled = { isValidating }
onValueChange = { this._onLocalRecordingSwitchChange }
style = { styles.switch }
trackColor = {{ false: TRACK_COLOR }}
value = { this.props.selectedRecordingService
=== RECORDING_TYPES.LOCAL } />
className = 'recording-icon-container'>
<Image
className = 'recording-icon'
src = { LOCAL_RECORDING }
style = { styles.recordingIcon } />
</Container>
<Text
className = 'recording-title'
style = {{
..._dialogStyles.text,
...styles.title
}}>
{ t('recording.saveLocalRecording') }
</Text>
<Switch
className = 'recording-switch'
disabled = { isValidating }
onValueChange = { this._onLocalRecordingSwitchChange }
style = { styles.switch }
trackColor = {{ false: TRACK_COLOR }}
value = { this.props.selectedRecordingService
=== RECORDING_TYPES.LOCAL } />
</Container>
{selectedRecordingService === RECORDING_TYPES.LOCAL && (
<>
<Container>
<Container
className = 'recording-header space-top'
style = { styles.header }>
<Container className = 'recording-icon-container file-sharing-icon-container'>
<Image
className = 'recording-file-sharing-icon'
src = { ICON_USERS }
style = { styles.recordingIcon } />
</Container>
<Text
className = 'recording-title'
style = {{
..._dialogStyles.text,
...styles.title
}}>
{t('recording.onlyRecordSelf')}
</Text>
<Switch
className = 'recording-switch'
disabled = { isValidating }
onValueChange = { this.props.onLocalRecordingSelfChange }
style = { styles.switch }
trackColor = {{ false: TRACK_COLOR }}
value = { this.props.localRecordingOnlySelf } />
</Container>
</Container>
{selectedRecordingService === RECORDING_TYPES.LOCAL
&& <>
<Text className = 'local-recording-warning text'>
{t('recording.localRecordingWarning')}
</Text>
{_localRecordingNoNotification && !this.props.localRecordingOnlySelf
&& <Text className = 'local-recording-warning notification'>
{t('recording.localRecordingNoNotificationWarning')}
</Text>
}
{_localRecordingNoNotification && <Text className = 'local-recording-warning notification'>
{t('recording.localRecordingNoNotificationWarning')}
</Text>}
</>
)}
</>
}
</Container>
);
}
@@ -759,9 +707,8 @@ function _mapStateToProps(state) {
return {
..._abstractMapStateToProps(state),
isVpaas: isVpaasMeeting(state),
_hideStorageWarning: state['features/base/config'].recording?.hideStorageWarning,
_localRecordingEnabled: !state['features/base/config'].localRecording?.disable,
_localRecordingNoNotification: !state['features/base/config'].localRecording?.notifyAllParticipants,
_localRecordingEnabled: !state['features/base/config'].localRecording.disable,
_localRecordingNoNotification: !state['features/base/config'].localRecording.notifyAllParticipants,
_styles: ColorSchemeRegistry.get(state, 'StartRecordingDialogContent')
};
}

View File

@@ -55,7 +55,6 @@ class StartRecordingDialog extends AbstractStartRecordingDialog {
const {
isTokenValid,
isValidating,
localRecordingOnlySelf,
selectedRecordingService,
sharingEnabled,
spaceLeft,
@@ -79,9 +78,7 @@ class StartRecordingDialog extends AbstractStartRecordingDialog {
integrationsEnabled = { this._areIntegrationsEnabled() }
isTokenValid = { isTokenValid }
isValidating = { isValidating }
localRecordingOnlySelf = { localRecordingOnlySelf }
onChange = { this._onSelectedRecordingServiceChanged }
onLocalRecordingSelfChange = { this._onLocalRecordingSelfChange }
onSharingSettingChanged = { this._onSharingSettingChanged }
selectedRecordingService = { selectedRecordingService }
sharingSetting = { sharingEnabled }
@@ -108,7 +105,6 @@ class StartRecordingDialog extends AbstractStartRecordingDialog {
_onSubmit: () => boolean;
_onSelectedRecordingServiceChanged: (string) => void;
_onSharingSettingChanged: () => void;
_onLocalRecordingSelfChange: () => void;
}
/**

View File

@@ -25,7 +25,7 @@ class StopRecordingDialog extends AbstractStopRecordingDialog<Props> {
* @returns {ReactElement}
*/
render() {
const { t, localRecordingVideoStop } = this.props;
const { t } = this.props;
return (
<Dialog
@@ -33,7 +33,7 @@ class StopRecordingDialog extends AbstractStopRecordingDialog<Props> {
onSubmit = { this._onSubmit }
titleKey = 'dialog.recording'
width = 'small'>
{t(localRecordingVideoStop ? 'recording.localRecordingVideoStop' : 'dialog.stopRecordingWarning') }
{ t('dialog.stopRecordingWarning') }
</Dialog>
);
}

View File

@@ -151,19 +151,11 @@ export function getRecordButtonProps(state: Object): ?string {
const isModerator = isLocalParticipantModerator(state);
const {
enableFeaturesBasedOnToken,
recordingService,
localRecording
fileRecordingsEnabled
} = state['features/base/config'];
const { features = {} } = getLocalParticipant(state);
let localRecordingEnabled = !localRecording?.disable;
if (navigator.product === 'ReactNative') {
localRecordingEnabled = false;
}
const dropboxEnabled = isDropboxEnabled(state);
visible = isModerator && (recordingService?.enabled || localRecordingEnabled || dropboxEnabled);
visible = isModerator && fileRecordingsEnabled;
if (enableFeaturesBasedOnToken) {
visible = visible && String(features.recording) === 'true';

View File

@@ -133,39 +133,27 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => async action =>
case START_LOCAL_RECORDING: {
const { localRecording } = getState()['features/base/config'];
const { onlySelf } = action;
try {
await LocalRecordingManager.startLocalRecording({ dispatch,
getState }, action.onlySelf);
getState });
const props = {
descriptionKey: 'recording.on',
titleKey: 'dialog.recording'
};
if (localRecording?.notifyAllParticipants && !onlySelf) {
if (localRecording.notifyAllParticipants) {
dispatch(playSound(RECORDING_ON_SOUND_ID));
}
dispatch(showNotification(props, NOTIFICATION_TIMEOUT_TYPE.MEDIUM));
dispatch(showNotification({
titleKey: 'recording.localRecordingStartWarningTitle',
descriptionKey: 'recording.localRecordingStartWarning'
}, NOTIFICATION_TIMEOUT_TYPE.STICKY));
dispatch(updateLocalRecordingStatus(true, onlySelf));
sendAnalytics(createRecordingEvent('started', `local${onlySelf ? '.self' : ''}`));
dispatch(updateLocalRecordingStatus(true));
sendAnalytics(createRecordingEvent('started', 'local'));
} catch (err) {
logger.error('Capture failed', err);
let descriptionKey = 'recording.error';
if (err.message === 'WrongSurfaceSelected') {
descriptionKey = 'recording.surfaceError';
} else if (err.message === 'NoLocalStreams') {
descriptionKey = 'recording.noStreams';
}
const noTabError = err.message === 'WrongSurfaceSelected';
const props = {
descriptionKey,
descriptionKey: noTabError ? 'recording.surfaceError' : 'recording.error',
titleKey: 'recording.failedToStart'
};
@@ -176,12 +164,11 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => async action =>
case STOP_LOCAL_RECORDING: {
const { localRecording } = getState()['features/base/config'];
const { onlySelf } = action;
if (LocalRecordingManager.isRecordingLocally()) {
LocalRecordingManager.stopLocalRecording();
dispatch(updateLocalRecordingStatus(false));
if (localRecording?.notifyAllParticipants && !onlySelf) {
if (localRecording.notifyAllParticipants) {
dispatch(playSound(RECORDING_OFF_SOUND_ID));
}
}

View File

@@ -98,10 +98,10 @@ function SecurityDialog({
setPassword = { setPassword }
setPasswordEditEnabled = { setPasswordEditEnabled } />
{
_showE2ee ? <>
<>
<div className = 'separator-line' />
<E2EESection />
</> : null
</>
}
</div>