Compare commits
85 Commits
saghul-pat
...
4792
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
226581a81a | ||
|
|
e1c5b1e626 | ||
|
|
831c5ba59d | ||
|
|
bad1bc91cf | ||
|
|
30d0aabaca | ||
|
|
22b6d32174 | ||
|
|
31ace267ce | ||
|
|
194d357005 | ||
|
|
c2ad06c5e6 | ||
|
|
e40b02ab3c | ||
|
|
2587eefefc | ||
|
|
b87c433e99 | ||
|
|
751644db16 | ||
|
|
c508572cc5 | ||
|
|
b86c271a80 | ||
|
|
5efbe5f0ec | ||
|
|
2784c43a1b | ||
|
|
f5a34183e9 | ||
|
|
29f5d87d77 | ||
|
|
ab6790bdaa | ||
|
|
2e308d67d8 | ||
|
|
91ba835f78 | ||
|
|
2643029ac8 | ||
|
|
e40e078a29 | ||
|
|
6df5a4cf31 | ||
|
|
c7c7d7a155 | ||
|
|
8f06866646 | ||
|
|
b559cb8ec6 | ||
|
|
30a2e84da1 | ||
|
|
3122983000 | ||
|
|
407021e258 | ||
|
|
1a62a7b1cc | ||
|
|
0ee03f1538 | ||
|
|
572beb8382 | ||
|
|
d0d32b8a19 | ||
|
|
82ff988c18 | ||
|
|
8fa5d09612 | ||
|
|
508f1e0da9 | ||
|
|
dcda89012e | ||
|
|
d93a402cc2 | ||
|
|
b7b260f4c9 | ||
|
|
4db3f04c0c | ||
|
|
126a2bd0d7 | ||
|
|
29bbcf8590 | ||
|
|
5c46b03251 | ||
|
|
eeb5abbbe8 | ||
|
|
49583b611c | ||
|
|
9e29dd063f | ||
|
|
a2e2d31dfd | ||
|
|
62c06441b1 | ||
|
|
f718a3e050 | ||
|
|
899968d3a9 | ||
|
|
696f509f18 | ||
|
|
430591bd1e | ||
|
|
8ee324b37f | ||
|
|
399d6b6a4b | ||
|
|
ffad21cb59 | ||
|
|
ce6debac45 | ||
|
|
5d8bf0c1e7 | ||
|
|
7bbd06c9f4 | ||
|
|
79a67049a9 | ||
|
|
b1a3c5cd7b | ||
|
|
9573a615b1 | ||
|
|
5d09102e48 | ||
|
|
cc0ecc1fdd | ||
|
|
cecf324023 | ||
|
|
943d5dca35 | ||
|
|
dd1f8339b1 | ||
|
|
159f59b665 | ||
|
|
23bb824731 | ||
|
|
42d926eef3 | ||
|
|
87a110b9c3 | ||
|
|
a7db7ecaff | ||
|
|
79bb98dab3 | ||
|
|
d22792c9e3 | ||
|
|
41e6af3464 | ||
|
|
f50fd7b7bd | ||
|
|
43761fc398 | ||
|
|
4c39d83ff1 | ||
|
|
e525c2b2ec | ||
|
|
f69a31d9c6 | ||
|
|
67930edae2 | ||
|
|
c11a94f7d7 | ||
|
|
bfd093b0ba | ||
|
|
861935c9d7 |
@@ -5,8 +5,8 @@ build/*
|
||||
# modify as little as possible.
|
||||
flow-typed/*
|
||||
libs/*
|
||||
|
||||
react/features/stream-effects/blur/vendor/*
|
||||
resources/*
|
||||
react/features/stream-effects/virtual-background/vendor/*
|
||||
|
||||
# ESLint will by default ignore its own configuration file. However, there does
|
||||
# not seem to be a reason why we will want to risk being inconsistent with our
|
||||
|
||||
1
.github/workflows/ci.yml
vendored
@@ -12,5 +12,6 @@ jobs:
|
||||
with:
|
||||
node-version: '12.x'
|
||||
- run: npm install
|
||||
- run: git status -s --untracked-files=no
|
||||
- run: npm run lint
|
||||
- run: make
|
||||
|
||||
8
Makefile
@@ -5,8 +5,8 @@ LIBJITSIMEET_DIR = node_modules/lib-jitsi-meet/
|
||||
LIBFLAC_DIR = node_modules/libflacjs/dist/min/
|
||||
OLM_DIR = node_modules/olm
|
||||
RNNOISE_WASM_DIR = node_modules/rnnoise-wasm/dist/
|
||||
TFLITE_WASM = react/features/stream-effects/blur/vendor/tflite
|
||||
MEET_MODELS_DIR = react/features/stream-effects/blur/vendor/models/
|
||||
TFLITE_WASM = react/features/stream-effects/virtual-background/vendor/tflite
|
||||
MEET_MODELS_DIR = react/features/stream-effects/virtual-background/vendor/models/
|
||||
NODE_SASS = ./node_modules/.bin/sass
|
||||
NPM = npm
|
||||
OUTPUT_DIR = .
|
||||
@@ -51,10 +51,6 @@ deploy-appbundle:
|
||||
$(OUTPUT_DIR)/analytics-ga.js \
|
||||
$(BUILD_DIR)/analytics-ga.min.js \
|
||||
$(BUILD_DIR)/analytics-ga.min.map \
|
||||
$(BUILD_DIR)/video-blur-effect.min.js \
|
||||
$(BUILD_DIR)/video-blur-effect.min.map \
|
||||
$(BUILD_DIR)/rnnoise-processor.min.js \
|
||||
$(BUILD_DIR)/rnnoise-processor.min.map \
|
||||
$(BUILD_DIR)/close3.min.js \
|
||||
$(BUILD_DIR)/close3.min.map \
|
||||
$(DEPLOY_DIR)
|
||||
|
||||
@@ -26,4 +26,4 @@ android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
|
||||
appVersion=21.0.0
|
||||
sdkVersion=3.1.0
|
||||
sdkVersion=3.2.0
|
||||
|
||||
162
conference.js
@@ -99,6 +99,7 @@ import {
|
||||
destroyLocalTracks,
|
||||
getLocalJitsiAudioTrack,
|
||||
getLocalJitsiVideoTrack,
|
||||
getLocalTracks,
|
||||
isLocalCameraTrackMuted,
|
||||
isLocalTrackMuted,
|
||||
isUserInteractionRequiredForUnmute,
|
||||
@@ -115,7 +116,7 @@ import {
|
||||
submitFeedback
|
||||
} from './react/features/feedback';
|
||||
import { showNotification } from './react/features/notifications';
|
||||
import { mediaPermissionPromptVisibilityChanged } from './react/features/overlay';
|
||||
import { mediaPermissionPromptVisibilityChanged, toggleSlowGUMOverlay } from './react/features/overlay';
|
||||
import { suspendDetected } from './react/features/power-monitor';
|
||||
import {
|
||||
initPrejoin,
|
||||
@@ -125,7 +126,7 @@ import {
|
||||
} from './react/features/prejoin';
|
||||
import { disableReceiver, stopReceiver } from './react/features/remote-control';
|
||||
import { toggleScreenshotCaptureEffect } from './react/features/screenshot-capture';
|
||||
import { setSharedVideoStatus } from './react/features/shared-video';
|
||||
import { setSharedVideoStatus } from './react/features/shared-video/actions';
|
||||
import { AudioMixerEffect } from './react/features/stream-effects/audio-mixer/AudioMixerEffect';
|
||||
import { createPresenterEffect } from './react/features/stream-effects/presenter';
|
||||
import { endpointMessageReceived } from './react/features/subtitles';
|
||||
@@ -473,18 +474,13 @@ export default {
|
||||
*/
|
||||
createInitialLocalTracks(options = {}) {
|
||||
const errors = {};
|
||||
|
||||
// Always get a handle on the audio input device so that we have statistics (such as "No audio input" or
|
||||
// "Are you trying to speak?" ) even if the user joins the conference muted.
|
||||
const initialDevices = config.disableInitialGUM ? [] : [ 'audio' ];
|
||||
const requestedAudio = !config.disableInitialGUM;
|
||||
let requestedVideo = false;
|
||||
|
||||
// Always get a handle on the audio input device so that we have statistics even if the user joins the
|
||||
// conference muted. Previous implementation would only acquire the handle when the user first unmuted,
|
||||
// which would results in statistics ( such as "No audio input" or "Are you trying to speak?") being available
|
||||
// only after that point.
|
||||
if (options.startWithAudioMuted) {
|
||||
this.muteAudio(true, true);
|
||||
}
|
||||
|
||||
if (!config.disableInitialGUM
|
||||
&& !options.startWithVideoMuted
|
||||
&& !options.startAudioOnly
|
||||
@@ -502,6 +498,11 @@ export default {
|
||||
);
|
||||
}
|
||||
|
||||
JitsiMeetJS.mediaDevices.addEventListener(
|
||||
JitsiMediaDevicesEvents.SLOW_GET_USER_MEDIA,
|
||||
() => APP.store.dispatch(toggleSlowGUMOverlay(true))
|
||||
);
|
||||
|
||||
let tryCreateLocalTracks;
|
||||
|
||||
// On Electron there is no permission prompt for granting permissions. That's why we don't need to
|
||||
@@ -519,8 +520,10 @@ export default {
|
||||
|
||||
return createLocalTracksF({
|
||||
devices: [ 'audio' ],
|
||||
timeout
|
||||
}, true)
|
||||
timeout,
|
||||
firePermissionPromptIsShownEvent: true,
|
||||
fireSlowPromiseEvent: true
|
||||
})
|
||||
.then(([ audioStream ]) =>
|
||||
[ desktopStream, audioStream ])
|
||||
.catch(error => {
|
||||
@@ -536,8 +539,10 @@ export default {
|
||||
return requestedAudio
|
||||
? createLocalTracksF({
|
||||
devices: [ 'audio' ],
|
||||
timeout
|
||||
}, true)
|
||||
timeout,
|
||||
firePermissionPromptIsShownEvent: true,
|
||||
fireSlowPromiseEvent: true
|
||||
})
|
||||
: [];
|
||||
})
|
||||
.catch(error => {
|
||||
@@ -551,8 +556,10 @@ export default {
|
||||
} else {
|
||||
tryCreateLocalTracks = createLocalTracksF({
|
||||
devices: initialDevices,
|
||||
timeout
|
||||
}, true)
|
||||
timeout,
|
||||
firePermissionPromptIsShownEvent: true,
|
||||
fireSlowPromiseEvent: true
|
||||
})
|
||||
.catch(err => {
|
||||
if (requestedAudio && requestedVideo) {
|
||||
|
||||
@@ -574,8 +581,10 @@ export default {
|
||||
return (
|
||||
createLocalTracksF({
|
||||
devices: [ 'audio' ],
|
||||
timeout
|
||||
}, true));
|
||||
timeout,
|
||||
firePermissionPromptIsShownEvent: true,
|
||||
fireSlowPromiseEvent: true
|
||||
}));
|
||||
} else if (requestedAudio && !requestedVideo) {
|
||||
errors.audioOnlyError = err;
|
||||
|
||||
@@ -598,8 +607,9 @@ export default {
|
||||
return requestedVideo
|
||||
? createLocalTracksF({
|
||||
devices: [ 'video' ],
|
||||
timeout
|
||||
}, true)
|
||||
firePermissionPromptIsShownEvent: true,
|
||||
fireSlowPromiseEvent: true
|
||||
})
|
||||
: [];
|
||||
})
|
||||
.catch(err => {
|
||||
@@ -619,6 +629,7 @@ export default {
|
||||
// the user inputs their credentials, but the dialog would be
|
||||
// overshadowed by the overlay.
|
||||
tryCreateLocalTracks.then(tracks => {
|
||||
APP.store.dispatch(toggleSlowGUMOverlay(false));
|
||||
APP.store.dispatch(mediaPermissionPromptVisibilityChanged(false));
|
||||
|
||||
return tracks;
|
||||
@@ -809,12 +820,16 @@ export default {
|
||||
return this._setLocalAudioVideoStreams(tracks);
|
||||
}
|
||||
|
||||
const [ tracks, con ] = await this.createInitialLocalTracksAndConnect(
|
||||
roomName, initialOptions);
|
||||
const [ tracks, con ] = await this.createInitialLocalTracksAndConnect(roomName, initialOptions);
|
||||
let localTracks = tracks;
|
||||
|
||||
this._initDeviceList(true);
|
||||
|
||||
return this.startConference(con, tracks);
|
||||
if (initialOptions.startWithAudioMuted) {
|
||||
localTracks = localTracks.filter(track => track.getType() !== MEDIA_TYPE.AUDIO);
|
||||
}
|
||||
|
||||
return this.startConference(con, localTracks);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -882,7 +897,7 @@ export default {
|
||||
showUI && APP.store.dispatch(notifyMicError(error));
|
||||
};
|
||||
|
||||
createLocalTracksF({ devices: [ 'audio' ] }, false)
|
||||
createLocalTracksF({ devices: [ 'audio' ] })
|
||||
.then(([ audioTrack ]) => audioTrack)
|
||||
.catch(error => {
|
||||
maybeShowErrorDialog(error);
|
||||
@@ -996,7 +1011,7 @@ export default {
|
||||
//
|
||||
// FIXME when local track creation is moved to react/redux
|
||||
// it should take care of the use case described above
|
||||
createLocalTracksF({ devices: [ 'video' ] }, false)
|
||||
createLocalTracksF({ devices: [ 'video' ] })
|
||||
.then(([ videoTrack ]) => videoTrack)
|
||||
.catch(error => {
|
||||
// FIXME should send some feedback to the API on error ?
|
||||
@@ -1005,7 +1020,11 @@ export default {
|
||||
// Rollback the video muted status by using null track
|
||||
return null;
|
||||
})
|
||||
.then(videoTrack => this.useVideoStream(videoTrack));
|
||||
.then(videoTrack => {
|
||||
logger.debug(`muteVideo: calling useVideoStream for track: ${videoTrack}`);
|
||||
|
||||
return this.useVideoStream(videoTrack);
|
||||
});
|
||||
} else {
|
||||
// FIXME show error dialog if it fails (should be handled by react)
|
||||
muteLocalVideo(mute);
|
||||
@@ -1305,7 +1324,11 @@ export default {
|
||||
this._getConferenceOptions());
|
||||
|
||||
APP.store.dispatch(conferenceWillJoin(room));
|
||||
this._setLocalAudioVideoStreams(localTracks);
|
||||
|
||||
// Filter out the tracks that are muted.
|
||||
const tracks = localTracks.filter(track => !track.isMuted());
|
||||
|
||||
this._setLocalAudioVideoStreams(tracks);
|
||||
this._room = room; // FIXME do not use this
|
||||
|
||||
sendLocalParticipant(APP.store, room);
|
||||
@@ -1324,8 +1347,11 @@ export default {
|
||||
if (track.isAudioTrack()) {
|
||||
return this.useAudioStream(track);
|
||||
} else if (track.isVideoTrack()) {
|
||||
logger.debug(`_setLocalAudioVideoStreams is calling useVideoStream with track: ${track}`);
|
||||
|
||||
return this.useVideoStream(track);
|
||||
}
|
||||
|
||||
logger.error(
|
||||
'Ignored not an audio nor a video track: ', track);
|
||||
|
||||
@@ -1345,6 +1371,8 @@ export default {
|
||||
* @returns {Promise}
|
||||
*/
|
||||
useVideoStream(newTrack) {
|
||||
logger.debug(`useVideoStream: ${newTrack}`);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
_replaceLocalVideoTrackQueue.enqueue(onFinish => {
|
||||
const state = APP.store.getState();
|
||||
@@ -1354,14 +1382,20 @@ export default {
|
||||
if (isPrejoinPageVisible(state)) {
|
||||
const oldTrack = getLocalJitsiVideoTrack(state);
|
||||
|
||||
logger.debug(`useVideoStream on the prejoin screen: Replacing ${oldTrack} with ${newTrack}`);
|
||||
|
||||
return APP.store.dispatch(replaceLocalTrack(oldTrack, newTrack))
|
||||
.then(resolve)
|
||||
.catch(reject)
|
||||
.catch(error => {
|
||||
logger.error(`useVideoStream failed on the prejoin screen: ${error}`);
|
||||
reject(error);
|
||||
})
|
||||
.then(onFinish);
|
||||
}
|
||||
|
||||
logger.debug(`useVideoStream: Replacing ${this.localVideo} with ${newTrack}`);
|
||||
APP.store.dispatch(
|
||||
replaceLocalTrack(this.localVideo, newTrack, room))
|
||||
replaceLocalTrack(this.localVideo, newTrack, room))
|
||||
.then(() => {
|
||||
this.localVideo = newTrack;
|
||||
this._setSharingScreen(newTrack);
|
||||
@@ -1371,7 +1405,10 @@ export default {
|
||||
this.setVideoMuteStatus(this.isLocalVideoMuted());
|
||||
})
|
||||
.then(resolve)
|
||||
.catch(reject)
|
||||
.catch(error => {
|
||||
logger.error(`useVideoStream failed: ${error}`);
|
||||
reject(error);
|
||||
})
|
||||
.then(onFinish);
|
||||
});
|
||||
});
|
||||
@@ -1518,7 +1555,11 @@ export default {
|
||||
|
||||
if (didHaveVideo) {
|
||||
promise = promise.then(() => createLocalTracksF({ devices: [ 'video' ] }))
|
||||
.then(([ stream ]) => this.useVideoStream(stream))
|
||||
.then(([ stream ]) => {
|
||||
logger.debug(`_turnScreenSharingOff using ${stream} for useVideoStream`);
|
||||
|
||||
return this.useVideoStream(stream);
|
||||
})
|
||||
.catch(error => {
|
||||
logger.error('failed to switch back to local video', error);
|
||||
|
||||
@@ -1529,7 +1570,11 @@ export default {
|
||||
);
|
||||
});
|
||||
} else {
|
||||
promise = promise.then(() => this.useVideoStream(null));
|
||||
promise = promise.then(() => {
|
||||
logger.debug('_turnScreenSharingOff using null for useVideoStream');
|
||||
|
||||
return this.useVideoStream(null);
|
||||
});
|
||||
}
|
||||
|
||||
return promise.then(
|
||||
@@ -1540,6 +1585,8 @@ export default {
|
||||
},
|
||||
error => {
|
||||
this.videoSwitchInProgress = false;
|
||||
logger.error(`_turnScreenSharingOff failed: ${error}`);
|
||||
|
||||
throw error;
|
||||
});
|
||||
},
|
||||
@@ -1560,6 +1607,7 @@ export default {
|
||||
* @return {Promise.<T>}
|
||||
*/
|
||||
async toggleScreenSharing(toggle = !this._untoggleScreenSharing, options = {}) {
|
||||
logger.debug(`toggleScreenSharing: ${toggle}`);
|
||||
if (this.videoSwitchInProgress) {
|
||||
return Promise.reject('Switch in progress.');
|
||||
}
|
||||
@@ -1626,6 +1674,8 @@ export default {
|
||||
desktopVideoStream.on(
|
||||
JitsiTrackEvents.LOCAL_TRACK_STOPPED,
|
||||
() => {
|
||||
logger.debug(`Local screensharing track stopped. ${this.isSharingScreen}`);
|
||||
|
||||
// If the stream was stopped during screen sharing
|
||||
// session then we should switch back to video.
|
||||
this.isSharingScreen
|
||||
@@ -1790,6 +1840,7 @@ export default {
|
||||
const desktopVideoStream = streams.find(stream => stream.getType() === MEDIA_TYPE.VIDEO);
|
||||
|
||||
if (desktopVideoStream) {
|
||||
logger.debug(`_switchToScreenSharing is using ${desktopVideoStream} for useVideoStream`);
|
||||
await this.useVideoStream(desktopVideoStream);
|
||||
}
|
||||
|
||||
@@ -1993,7 +2044,11 @@ export default {
|
||||
|
||||
room.on(JitsiConferenceEvents.TRACK_MUTE_CHANGED, (track, participantThatMutedUs) => {
|
||||
if (participantThatMutedUs) {
|
||||
APP.store.dispatch(participantMutedUs(participantThatMutedUs));
|
||||
APP.store.dispatch(participantMutedUs(participantThatMutedUs, track));
|
||||
if (this.isSharingScreen && track.isVideoTrack()) {
|
||||
logger.debug('TRACK_MUTE_CHANGED while screen sharing');
|
||||
this._turnScreenSharingOff(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -2145,8 +2200,26 @@ export default {
|
||||
}
|
||||
);
|
||||
room.on(JitsiConferenceEvents.STARTED_MUTED, () => {
|
||||
(room.isStartAudioMuted() || room.isStartVideoMuted())
|
||||
&& APP.UI.notifyInitiallyMuted();
|
||||
const audioMuted = room.isStartAudioMuted();
|
||||
const videoMuted = room.isStartVideoMuted();
|
||||
const localTracks = getLocalTracks(APP.store.getState()['features/base/tracks']);
|
||||
const promises = [];
|
||||
|
||||
APP.store.dispatch(setAudioMuted(audioMuted));
|
||||
APP.store.dispatch(setVideoMuted(videoMuted));
|
||||
|
||||
// Remove the tracks from the peerconnection.
|
||||
for (const track of localTracks) {
|
||||
if (audioMuted && track.jitsiTrack?.getType() === MEDIA_TYPE.AUDIO) {
|
||||
promises.push(this.useAudioStream(null));
|
||||
}
|
||||
if (videoMuted && track.jitsiTrack?.getType() === MEDIA_TYPE.VIDEO) {
|
||||
promises.push(this.useVideoStream(null));
|
||||
}
|
||||
}
|
||||
|
||||
Promise.allSettled(promises)
|
||||
.then(() => APP.UI.notifyInitiallyMuted());
|
||||
});
|
||||
|
||||
room.on(
|
||||
@@ -2197,7 +2270,7 @@ export default {
|
||||
.then(effect => this.localVideo.setEffect(effect))
|
||||
.then(() => {
|
||||
this.setVideoMuteStatus(false);
|
||||
logger.log('switched local video device');
|
||||
logger.log('Switched local video device while screen sharing and the video is unmuted');
|
||||
this._updateVideoDeviceId();
|
||||
})
|
||||
.catch(err => APP.store.dispatch(notifyCameraError(err)));
|
||||
@@ -2206,7 +2279,7 @@ export default {
|
||||
// id for video, dispose the existing presenter track and create a new effect
|
||||
// that can be applied on un-mute.
|
||||
} else if (this.isSharingScreen && videoWasMuted) {
|
||||
logger.log('switched local video device');
|
||||
logger.log('Switched local video device: while screen sharing and the video is muted');
|
||||
const { height } = this.localVideo.track.getSettings();
|
||||
|
||||
this._updateVideoDeviceId();
|
||||
@@ -2233,12 +2306,20 @@ export default {
|
||||
|
||||
return stream;
|
||||
})
|
||||
.then(stream => this.useVideoStream(stream))
|
||||
.then(stream => {
|
||||
logger.log('Switching the local video device.');
|
||||
|
||||
return this.useVideoStream(stream);
|
||||
})
|
||||
.then(() => {
|
||||
logger.log('switched local video device');
|
||||
logger.log('Switched local video device.');
|
||||
this._updateVideoDeviceId();
|
||||
})
|
||||
.catch(err => APP.store.dispatch(notifyCameraError(err)));
|
||||
.catch(error => {
|
||||
logger.error(`Switching the local video device failed: ${error}`);
|
||||
|
||||
return APP.store.dispatch(notifyCameraError(error));
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -2590,6 +2671,7 @@ export default {
|
||||
delete newDevices.videoinput;
|
||||
|
||||
// Removing the current video track in order to force the unmute to select the preferred device.
|
||||
logger.debug('_onDeviceListChanged: Removing the current video track.');
|
||||
this.useVideoStream(null);
|
||||
|
||||
}
|
||||
|
||||
32
config.js
@@ -240,6 +240,12 @@ var config = {
|
||||
// 90: 2
|
||||
// },
|
||||
|
||||
// Provides a way to translate the legacy bridge signaling messages, 'LastNChangedEvent',
|
||||
// 'SelectedEndpointsChangedEvent' and 'ReceiverVideoConstraint' into the new 'ReceiverVideoConstraints' message
|
||||
// that invokes the new bandwidth allocation algorithm in the bridge which is described here
|
||||
// - https://github.com/jitsi/jitsi-videobridge/blob/master/doc/allocation.md.
|
||||
// useNewBandwidthAllocationStrategy: false,
|
||||
|
||||
// Specify the settings for video quality optimizations on the client.
|
||||
// videoQuality: {
|
||||
// // Provides a way to prevent a video codec from being negotiated on the JVB connection. The codec specified
|
||||
@@ -406,7 +412,26 @@ var config = {
|
||||
// enableAutomaticUrlCopy: false,
|
||||
|
||||
// Base URL for a Gravatar-compatible service. Defaults to libravatar.
|
||||
// gravatarBaseURL: 'https://seccdn.libravatar.org/avatar/';
|
||||
// gravatarBaseURL: 'https://seccdn.libravatar.org/avatar/',
|
||||
|
||||
// Moved from interfaceConfig(TOOLBAR_BUTTONS).
|
||||
// The name of the toolbar buttons to display in the toolbar, including the
|
||||
// "More actions" menu. If present, the button will display. Exceptions are
|
||||
// "livestreaming" and "recording" which also require being a moderator and
|
||||
// some other values in config.js to be enabled. Also, the "profile" button will
|
||||
// not display for users with a JWT.
|
||||
// Notes:
|
||||
// - it's impossible to choose which buttons go in the "More actions" menu
|
||||
// - it's impossible to control the placement of buttons
|
||||
// - 'desktop' controls the "Share your screen" button
|
||||
// - if `toolbarButtons` is undefined, we fallback to enabling all buttons on the UI
|
||||
// toolbarButtons: [
|
||||
// 'microphone', 'camera', 'closedcaptions', 'desktop', 'embedmeeting', 'fullscreen',
|
||||
// 'fodeviceselection', 'hangup', 'profile', 'chat', 'recording',
|
||||
// 'livestreaming', 'etherpad', 'sharedvideo', 'settings', 'raisehand',
|
||||
// 'videoquality', 'filmstrip', 'invite', 'feedback', 'stats', 'shortcuts',
|
||||
// 'tileview', 'select-background', 'download', 'help', 'mute-everyone', 'mute-video-everyone', 'security'
|
||||
// ],
|
||||
|
||||
// Stats
|
||||
//
|
||||
@@ -660,6 +685,11 @@ var config = {
|
||||
// Sets the conference subject
|
||||
// subject: 'Conference Subject',
|
||||
|
||||
// This property is related to the use case when jitsi-meet is used via the IFrame API. When the property is true
|
||||
// jitsi-meet will use the local storage of the host page instead of its own. This option is useful if the browser
|
||||
// is not persisting the local storage inside the iframe.
|
||||
// useHostPageLocalStorage: true,
|
||||
|
||||
// List of undocumented settings used in jitsi-meet
|
||||
/**
|
||||
_immediateReloadThreshold
|
||||
|
||||
@@ -1,3 +1,21 @@
|
||||
/**
|
||||
* Mixins that mimic the way Atlaskit fills the screen with modals at low screen widths.
|
||||
*/
|
||||
@mixin full-size-modal-positioner() {
|
||||
height: 100%;
|
||||
left: 0;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
max-width: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@mixin full-size-modal-dialog() {
|
||||
height: 100%;
|
||||
max-height: 100%;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the @atlaskit/flag container up a little bit so it does not cover the
|
||||
* toolbar with the first notification.
|
||||
@@ -55,5 +73,59 @@
|
||||
*/
|
||||
.toolbox-button-wth-dialog > div:nth-child(2) {
|
||||
max-height: calc(100vh - #{$newToolbarSizeWithPadding} - 46px);
|
||||
margin-bottom: 4px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.audio-preview > div:nth-child(2),
|
||||
.video-preview > div:nth-child(2) {
|
||||
margin-bottom: 4px;
|
||||
outline: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* The following selectors keep the chat modal full-size anywhere between 100px
|
||||
* and 580px for desktop or 680px for mobile.
|
||||
*/
|
||||
@media (min-width: 100px) and (max-width: 320px) {
|
||||
.smiley-input {
|
||||
display: none;
|
||||
}
|
||||
.shift-right .focus-lock > div > div {
|
||||
@include full-size-modal-positioner();
|
||||
}
|
||||
|
||||
.shift-right .focus-lock [role="dialog"] {
|
||||
@include full-size-modal-dialog();
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 480px) and (max-width: 580px) {
|
||||
.shift-right .focus-lock > div > div {
|
||||
@include full-size-modal-positioner();
|
||||
}
|
||||
|
||||
.shift-right .focus-lock [role="dialog"] {
|
||||
@include full-size-modal-dialog();
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 580px) and (max-width: 680px) {
|
||||
.mobile-browser {
|
||||
&.shift-right .focus-lock > div > div {
|
||||
@include full-size-modal-positioner();
|
||||
}
|
||||
|
||||
&.shift-right .focus-lock [role="dialog"] {
|
||||
@include full-size-modal-dialog();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
div.Tooltip {
|
||||
color: #fff;
|
||||
font-size: 12px;
|
||||
line-height: 14px;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
@@ -1,26 +1,37 @@
|
||||
.audio-preview {
|
||||
display: inline-block;
|
||||
|
||||
&-content {
|
||||
background: #2A3A4B;
|
||||
font-size: 15px;
|
||||
background: $menuBG;
|
||||
border-radius: 3px;
|
||||
font-size: 14px;
|
||||
line-height: 24px;
|
||||
max-height: 456px;
|
||||
overflow: auto;
|
||||
width: 328px;
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
&-header {
|
||||
color: #fff;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
padding: 16px;
|
||||
margin-top: 8px;
|
||||
padding: 8px 16px;
|
||||
|
||||
&-icon {
|
||||
color: #A4B8D1;
|
||||
display: inline-block;
|
||||
|
||||
svg {
|
||||
fill: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
&--bordered {
|
||||
border-bottom: 1px solid #4C4D50;
|
||||
}
|
||||
|
||||
&-text {
|
||||
font-weight: bold;
|
||||
margin-left: 8px;
|
||||
margin-left: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,19 +40,18 @@
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
padding: 12px 0;
|
||||
padding: 8px 0;
|
||||
margin-left: 48px;
|
||||
|
||||
&--selected {
|
||||
background: #1C2025;
|
||||
background: #131519;
|
||||
cursor: initial;
|
||||
margin-left: 0;
|
||||
padding-left: 21px;
|
||||
padding-left: 18px;
|
||||
}
|
||||
|
||||
&-text {
|
||||
color: #fff;
|
||||
font-size: 15px;
|
||||
display: inline-block;
|
||||
line-height: 24px;
|
||||
text-overflow: ellipsis;
|
||||
@@ -56,12 +66,13 @@
|
||||
|
||||
&:hover {
|
||||
.audio-preview-entry {
|
||||
background: #3F4E5E;
|
||||
background: #36383C;
|
||||
margin-left: 0;
|
||||
padding-left: 48px;
|
||||
|
||||
&--selected {
|
||||
padding-left: 21px;
|
||||
padding-left: 18px;
|
||||
background: #131519;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,6 +85,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
.audio-preview-entry-text {
|
||||
max-width: 256px;
|
||||
}
|
||||
@@ -84,18 +99,19 @@
|
||||
|
||||
&:hover {
|
||||
.audio-preview-entry {
|
||||
background: #3F4E5E;
|
||||
background: #36383C;
|
||||
margin-left: 0;
|
||||
padding-left: 48px;
|
||||
|
||||
&--selected {
|
||||
padding-left: 21px;
|
||||
background: #131519;
|
||||
padding-left: 18px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.audio-preview-entry-text {
|
||||
max-width: 196px;
|
||||
max-width: 178px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,7 +126,7 @@
|
||||
|
||||
&--check {
|
||||
background: #31B76A;
|
||||
margin-right: 13px;
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
&--exclamation {
|
||||
@@ -121,6 +137,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
&-hr {
|
||||
border-top: 1px solid #4C4D50;
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
&-test-button {
|
||||
display: none;
|
||||
background: #FFF;
|
||||
@@ -129,23 +150,16 @@
|
||||
color: #1C2025;
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
font-size: 15px;
|
||||
line-height: 24px;
|
||||
padding: 4px 16px;
|
||||
padding: 2px 16px;
|
||||
position: absolute;
|
||||
right: 16px;
|
||||
top: 8px;
|
||||
top: 5px;
|
||||
}
|
||||
|
||||
&-meter-mic {
|
||||
position: absolute;
|
||||
right: 16px;
|
||||
top: 18px;
|
||||
}
|
||||
|
||||
// Override @atlaskit/InlineDialog container which is made with styled components
|
||||
& > div:nth-child(2) {
|
||||
outline: none;
|
||||
padding: 0;
|
||||
top: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
109
css/_chat.scss
@@ -32,6 +32,13 @@
|
||||
width: $sidebarWidth;
|
||||
word-wrap: break-word;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
& > :first-child {
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
a {
|
||||
display: block;
|
||||
}
|
||||
@@ -122,16 +129,61 @@
|
||||
}
|
||||
}
|
||||
|
||||
.chat-input-container {
|
||||
padding: 0 16px 24px;
|
||||
|
||||
&.populated {
|
||||
#chat-input {
|
||||
border: 1px solid #619CF4;
|
||||
|
||||
.send-button {
|
||||
background: #1B67EC;
|
||||
cursor: pointer;
|
||||
|
||||
path {
|
||||
fill: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#chat-input {
|
||||
border-top: 1px solid $chatInputSeparatorColor;
|
||||
border: 1px solid $chatInputSeparatorColor;
|
||||
display: flex;
|
||||
padding: 5px 10px;
|
||||
border-radius: 3px;
|
||||
|
||||
* {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.send-button-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.send-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
border-radius: 3px;
|
||||
|
||||
path {
|
||||
fill: $chatInputSeparatorColor;
|
||||
}
|
||||
}
|
||||
|
||||
.mobile-browser {
|
||||
.send-button {
|
||||
height: 48px;
|
||||
width: 48px;
|
||||
}
|
||||
}
|
||||
|
||||
.remoteuser {
|
||||
color: #B8C7E0;
|
||||
}
|
||||
@@ -161,10 +213,47 @@
|
||||
#nickname {
|
||||
text-align: center;
|
||||
color: #9d9d9d;
|
||||
font-size: 18px;
|
||||
margin-top: 30px;
|
||||
left: 5px;
|
||||
right: 5px;
|
||||
font-size: 16px;
|
||||
margin: auto 0;
|
||||
padding: 0 16px;
|
||||
|
||||
input {
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
label {
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.enter-chat {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 16px;
|
||||
height: 40px;
|
||||
background: #1B67EC;
|
||||
border-radius: 3px;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
|
||||
&.disabled {
|
||||
color: #757575;
|
||||
background: #11336E;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mobile-browser {
|
||||
#nickname {
|
||||
input {
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
.enter-chat {
|
||||
height: 48px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sideToolbarContainer {
|
||||
@@ -411,6 +500,16 @@
|
||||
#chatconversation {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.chat-input-container {
|
||||
padding: 0 0 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.touchmove-hack {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
}
|
||||
|
||||
.drawer-menu {
|
||||
padding: 0 16px;
|
||||
max-height: 50vh;
|
||||
background: #242528;
|
||||
border-radius: 16px 16px 0 0;
|
||||
@@ -24,12 +23,6 @@
|
||||
height: 44px;
|
||||
cursor: pointer;
|
||||
|
||||
@media (hover: hover) and (pointer: fine) {
|
||||
&:hover {
|
||||
background-color: $overflowMenuItemHoverBG;
|
||||
}
|
||||
}
|
||||
|
||||
svg {
|
||||
fill: none;
|
||||
}
|
||||
@@ -67,13 +60,6 @@
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
@media (hover: hover) and (pointer: fine) {
|
||||
&:hover {
|
||||
background-color: $overflowMenuItemHoverBG;
|
||||
color: $overflowMenuItemHoverColor;
|
||||
}
|
||||
}
|
||||
|
||||
&.unclickable {
|
||||
cursor: default;
|
||||
}
|
||||
@@ -88,42 +74,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.beta-tag {
|
||||
background: $overflowMenuItemColor;
|
||||
border-radius: 2px;
|
||||
color: $overflowMenuBG;
|
||||
font-size: 11px;
|
||||
font-weight: bold;
|
||||
margin-left: 8px;
|
||||
padding: 0 6px;
|
||||
}
|
||||
|
||||
.overflow-menu-item-icon {
|
||||
margin-right: 16px;
|
||||
|
||||
i {
|
||||
display: inline;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
@media (hover: hover) and (pointer: fine) {
|
||||
i:hover {
|
||||
background-color: initial;
|
||||
}
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 24px;
|
||||
max-height: 24px;
|
||||
}
|
||||
|
||||
svg {
|
||||
fill: #B8C7E0 !important;
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.profile-text {
|
||||
max-width: 100%;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
min-width: 75px;
|
||||
text-align: left;
|
||||
padding: 0px;
|
||||
width: 180px;
|
||||
white-space: nowrap;
|
||||
|
||||
&__item {
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
text {
|
||||
fill: black;
|
||||
font-size: 26px;
|
||||
font-weight: 400;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,14 +197,6 @@
|
||||
&> div {
|
||||
margin: 0 12px;
|
||||
}
|
||||
|
||||
.settings-button-small-icon {
|
||||
right: -8px;
|
||||
|
||||
&--hovered {
|
||||
right: -10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,73 +1,3 @@
|
||||
@mixin small-button-size() {
|
||||
.new-toolbox {
|
||||
.toolbox-content {
|
||||
.button-group-center, .button-group-left, .button-group-right {
|
||||
.toolbox-button {
|
||||
.toolbox-icon {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
svg {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
&:nth-child(2) {
|
||||
.toolbox-icon {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@mixin very-small-button-size() {
|
||||
.new-toolbox {
|
||||
.toolbox-content {
|
||||
.button-group-center, .button-group-left, .button-group-right {
|
||||
.settings-button-small-icon {
|
||||
display: none;
|
||||
}
|
||||
.toolbox-button {
|
||||
.toolbox-icon {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
svg {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
&:nth-child(2) {
|
||||
.toolbox-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@mixin full-size-modal-positioner() {
|
||||
height: 100%;
|
||||
left: 0;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
max-width: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@mixin full-size-modal-dialog() {
|
||||
height: 100%;
|
||||
max-height: 100%;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: $verySmallScreen) {
|
||||
.welcome {
|
||||
display: block;
|
||||
@@ -137,21 +67,9 @@
|
||||
}
|
||||
|
||||
.desktop-browser {
|
||||
@media only screen and (max-width: $smallScreen) {
|
||||
@include small-button-size();
|
||||
}
|
||||
|
||||
@media only screen and (max-width: $verySmallScreen) {
|
||||
@include very-small-button-size();
|
||||
}
|
||||
|
||||
&.shift-right {
|
||||
@media only screen and (max-width: $smallScreen + $sidebarWidth) {
|
||||
@include small-button-size()
|
||||
}
|
||||
|
||||
@media only screen and (max-width: $verySmallScreen + $sidebarWidth) {
|
||||
@include very-small-button-size();
|
||||
|
||||
#videoResolutionLabel {
|
||||
display: none;
|
||||
@@ -165,25 +83,3 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 480px) and (max-width: 580px) {
|
||||
.shift-right .focus-lock > div > div {
|
||||
@include full-size-modal-positioner();
|
||||
}
|
||||
|
||||
.shift-right .focus-lock [role="dialog"] {
|
||||
@include full-size-modal-dialog();
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 580px) and (max-width: 680px) {
|
||||
.mobile-browser {
|
||||
&.shift-right .focus-lock > div > div {
|
||||
@include full-size-modal-positioner();
|
||||
}
|
||||
|
||||
&.shift-right .focus-lock [role="dialog"] {
|
||||
@include full-size-modal-dialog();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,84 +1,60 @@
|
||||
.settings-button {
|
||||
&-container {
|
||||
position: relative;
|
||||
.settings-button-container {
|
||||
position: relative;
|
||||
|
||||
.toolbox-icon {
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
background-color: #fff;
|
||||
border-radius: 50%;
|
||||
border: 1px solid #d1dbe8;
|
||||
justify-content: center;
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
.toolbox-icon {
|
||||
align-items: center;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
&.disabled, .disabled & {
|
||||
cursor: initial;
|
||||
color: #929292;
|
||||
background-color: #36383c;
|
||||
|
||||
&:hover {
|
||||
background-color: #daebfa;
|
||||
border: 1px solid #daebfa;
|
||||
}
|
||||
|
||||
&.toggled {
|
||||
background: #2a3a4b;
|
||||
border: 1px solid #5e6d7a;
|
||||
|
||||
svg {
|
||||
fill: #fff;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: #5e6d7a;
|
||||
}
|
||||
}
|
||||
|
||||
&.disabled, .disabled & {
|
||||
cursor: initial;
|
||||
color: #fff;
|
||||
background-color: #a4b8d1;
|
||||
|
||||
&:hover {
|
||||
background-color: #a4b8d1;
|
||||
}
|
||||
}
|
||||
|
||||
svg {
|
||||
fill: #5e6d7a;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-small-icon {
|
||||
background: #FFF;
|
||||
border: 1px solid rgba(0, 0, 0, 0.2);
|
||||
border-radius: 50%;
|
||||
bottom: 0;
|
||||
box-shadow: 0px 1px 4px rgba(0, 0, 0, 0.25);
|
||||
cursor: pointer;
|
||||
height: 16px;
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
right: 4px;
|
||||
width: 16px;
|
||||
|
||||
&> svg {
|
||||
fill: #5e6d7a;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
&--disabled {
|
||||
background-color: #a4b8d1;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
&--hovered {
|
||||
bottom: -1px;
|
||||
height: 20px;
|
||||
right: 2px;
|
||||
width: 20px;
|
||||
|
||||
&> svg {
|
||||
margin-top: 6px;
|
||||
background-color: #36383c;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.settings-button-small-icon {
|
||||
background: #36383C;
|
||||
box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25), 0px 0px 0px 1px rgba(0, 0, 0, 0.1);
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
padding: 4px;
|
||||
position: absolute;
|
||||
right: -4px;
|
||||
top: -3px;
|
||||
|
||||
&:hover {
|
||||
background: #F2F3F4;
|
||||
box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25), 0px 0px 0px 1px rgba(0, 0, 0, 0.1);
|
||||
|
||||
&> svg {
|
||||
fill: #000;
|
||||
}
|
||||
|
||||
&.settings-button-small-icon--disabled {
|
||||
&> svg {
|
||||
fill: #929292;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&> svg {
|
||||
fill: #fff;
|
||||
}
|
||||
|
||||
&--disabled {
|
||||
background-color: #36383c;
|
||||
cursor: default;
|
||||
|
||||
&> svg {
|
||||
fill: #929292;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,9 +33,6 @@
|
||||
|
||||
&.visible {
|
||||
bottom: 0;
|
||||
.toolbox-background {
|
||||
bottom: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
&.no-buttons {
|
||||
@@ -48,253 +45,221 @@
|
||||
width: calc(100% - #{$sidebarWidth});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.toolbox-background {
|
||||
background-image: linear-gradient(to top, rgba(0, 0, 0, 0.6), rgba(0, 0, 0, 0));
|
||||
transition: bottom .3s ease-in;
|
||||
height: 160px;
|
||||
width: 100%;
|
||||
bottom: -160px;
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
z-index: $toolbarBackgroundZ;
|
||||
.toolbox-content {
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
margin-bottom: 16px;
|
||||
position: relative;
|
||||
z-index: $toolbarZ;
|
||||
|
||||
.button-group-center,
|
||||
.button-group-left,
|
||||
.button-group-right {
|
||||
display: flex;
|
||||
width: 33%;
|
||||
}
|
||||
|
||||
.toolbox-content {
|
||||
box-sizing: border-box;
|
||||
.button-group-center {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.button-group-right {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.toolbox-button-wth-dialog {
|
||||
display: inline-block;
|
||||
|
||||
&> div {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.toolbox-button {
|
||||
color: $toolbarButtonColor;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
line-height: $newToolbarSize;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.toolbar-button-with-badge {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
|
||||
.badge-round {
|
||||
bottom: -5px;
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
min-width: 20px;
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
right: -5px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.toolbox-content-items {
|
||||
background: #131519;
|
||||
box-shadow: 0px 2px 8px 4px rgba(0, 0, 0, 0.25), 0px 0px 0px 1px rgba(0, 0, 0, 0.15);
|
||||
border-radius: 6px;
|
||||
margin: 0 auto;
|
||||
padding: 6px;
|
||||
text-align: center;
|
||||
|
||||
>div {
|
||||
margin-left: 8px;
|
||||
|
||||
&:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.overflow-menu {
|
||||
font-size: 14px;
|
||||
list-style-type: none;
|
||||
padding: 8px 0;
|
||||
background-color: $menuBG;
|
||||
|
||||
.profile-text {
|
||||
max-width: 150px;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.overflow-menu-item {
|
||||
align-items: center;
|
||||
color: $overflowMenuItemColor;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
height: 40px;
|
||||
line-height: 24px;
|
||||
padding: 8px 16px;
|
||||
box-sizing: border-box;
|
||||
|
||||
@media (hover: hover) and (pointer: fine) {
|
||||
&:hover {
|
||||
background: $overflowMenuItemBackground;
|
||||
}
|
||||
}
|
||||
|
||||
div {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
padding: 20px 16px;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
z-index: $toolbarZ;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.button-group-center,
|
||||
.button-group-left,
|
||||
.button-group-right {
|
||||
display: flex;
|
||||
width: 33%;
|
||||
&.unclickable {
|
||||
cursor: default;
|
||||
}
|
||||
&.disabled {
|
||||
cursor: initial;
|
||||
color: #929292;
|
||||
|
||||
svg {
|
||||
fill: #929292;
|
||||
}
|
||||
}
|
||||
|
||||
.button-group-center {
|
||||
justify-content: center;
|
||||
|
||||
.toolbox-button {
|
||||
|
||||
.toolbox-icon {
|
||||
background-color: #fff;
|
||||
border-radius: 50%;
|
||||
border: 1px solid #d1dbe8;
|
||||
margin: 0px 4px;
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
|
||||
@media (hover: hover) and (pointer: fine) {
|
||||
&:hover {
|
||||
background-color: #daebfa;
|
||||
border: 1px solid #daebfa;
|
||||
}
|
||||
}
|
||||
|
||||
&.toggled {
|
||||
background: #2a3a4b;
|
||||
border: 1px solid #5e6d7a;
|
||||
|
||||
svg {
|
||||
fill: #fff;
|
||||
}
|
||||
|
||||
@media (hover: hover) and (pointer: fine) {
|
||||
&:hover {
|
||||
background-color: #5e6d7a;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.disabled, .disabled & {
|
||||
cursor: initial;
|
||||
color: #fff;
|
||||
background-color: #a4b8d1;
|
||||
}
|
||||
|
||||
svg {
|
||||
fill: #5e6d7a;
|
||||
}
|
||||
}
|
||||
|
||||
&:nth-child(2) {
|
||||
.toolbox-icon {
|
||||
background-color: $hangupColor;
|
||||
border: 1px solid $hangupColor;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
|
||||
@media (hover: hover) and (pointer: fine) {
|
||||
&:hover {
|
||||
background-color: $hangupColor;
|
||||
}
|
||||
}
|
||||
|
||||
svg {
|
||||
fill: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@media (hover: hover) and (pointer: fine) {
|
||||
&.unclickable:hover {
|
||||
background: inherit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.button-group-right {
|
||||
justify-content: flex-end;
|
||||
.beta-tag {
|
||||
background: #36383C;
|
||||
border-radius: 3px;
|
||||
color: #fff;
|
||||
font-size: 12px;
|
||||
margin-left: 8px;
|
||||
padding: 0 4px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.overflow-menu-item-icon {
|
||||
margin-right: 16px;
|
||||
|
||||
i {
|
||||
display: inline;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
@media (hover: hover) and (pointer: fine) {
|
||||
i:hover {
|
||||
background-color: initial;
|
||||
}
|
||||
}
|
||||
|
||||
.overflow-menu {
|
||||
font-size: 1.2em;
|
||||
list-style-type: none;
|
||||
background-color: $overflowMenuBG;
|
||||
/**
|
||||
* Undo atlaskit padding by reducing margins.
|
||||
*/
|
||||
margin: -16px -24px;
|
||||
padding: 4px 0;
|
||||
img {
|
||||
max-width: 24px;
|
||||
max-height: 24px;
|
||||
}
|
||||
|
||||
.overflow-menu-item {
|
||||
align-items: center;
|
||||
color: $overflowMenuItemColor;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
font-size: 14px;
|
||||
height: 40px;
|
||||
padding: 5px 12px;
|
||||
box-sizing: border-box;
|
||||
svg {
|
||||
fill: #fff;
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
div {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
@media (hover: hover) and (pointer: fine) {
|
||||
&:hover {
|
||||
background-color: $overflowMenuItemHoverBG;
|
||||
color: $overflowMenuItemHoverColor;
|
||||
}
|
||||
}
|
||||
.overflow-menu-hr {
|
||||
border-top: 1px solid #4C4D50;
|
||||
border-bottom: 0;
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
&.unclickable {
|
||||
cursor: default;
|
||||
}
|
||||
@media (hover: hover) and (pointer: fine) {
|
||||
&.unclickable:hover {
|
||||
background: inherit;
|
||||
}
|
||||
}
|
||||
&.disabled {
|
||||
cursor: initial;
|
||||
color: #3b475c;
|
||||
}
|
||||
}
|
||||
.toolbox-icon {
|
||||
display: flex;
|
||||
border-radius: 3px;
|
||||
flex-direction: column;
|
||||
font-size: 24px;
|
||||
height: $newToolbarSize;
|
||||
justify-content: center;
|
||||
width: $newToolbarSize;
|
||||
|
||||
.beta-tag {
|
||||
background: $overflowMenuItemColor;
|
||||
border-radius: 2px;
|
||||
color: $overflowMenuBG;
|
||||
font-size: 11px;
|
||||
font-weight: bold;
|
||||
margin-left: 8px;
|
||||
padding: 0 6px;
|
||||
}
|
||||
|
||||
.overflow-menu-item-icon {
|
||||
margin-right: 16px;
|
||||
|
||||
i {
|
||||
display: inline;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
@media (hover: hover) and (pointer: fine) {
|
||||
i:hover {
|
||||
background-color: initial;
|
||||
}
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 24px;
|
||||
max-height: 24px;
|
||||
}
|
||||
|
||||
svg {
|
||||
fill: #B8C7E0 !important;
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.profile-text {
|
||||
max-width: 150px;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
@media (hover: hover) and (pointer: fine) {
|
||||
&:hover {
|
||||
background: $newToolbarButtonHoverColor;
|
||||
}
|
||||
}
|
||||
|
||||
.toolbox-button {
|
||||
color: $toolbarButtonColor;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
line-height: $newToolbarSize;
|
||||
margin: 0 8px;
|
||||
text-align: center;
|
||||
&.toggled {
|
||||
background: $newToolbarButtonToggleColor;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
cursor: initial !important;
|
||||
background-color: #36383c !important;
|
||||
|
||||
svg {
|
||||
fill: #929292 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.toolbar-button-with-badge {
|
||||
position: relative;
|
||||
.hangup-button {
|
||||
background-color: $hangupColor;
|
||||
|
||||
.badge-round {
|
||||
bottom: -5px;
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
min-width: 20px;
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
right: -5px;
|
||||
}
|
||||
@media (hover: hover) and (pointer: fine) {
|
||||
&:hover {
|
||||
background-color: $hangupHoverColor;
|
||||
}
|
||||
}
|
||||
|
||||
.toolbox-button-wth-dialog {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.toolbox-icon {
|
||||
display: flex;
|
||||
border-radius: 5px;
|
||||
flex-direction: column;
|
||||
font-size: 24px;
|
||||
height: $newToolbarSize;
|
||||
justify-content: center;
|
||||
width: $newToolbarSize;
|
||||
|
||||
@media (hover: hover) and (pointer: fine) {
|
||||
&:hover {
|
||||
background: $newToolbarButtonHoverColor;
|
||||
}
|
||||
}
|
||||
|
||||
&.toggled {
|
||||
background: $newToolbarButtonHoverColor;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
cursor: initial !important;
|
||||
background-color: #a4b8d1 !important;
|
||||
|
||||
svg {
|
||||
fill: #fff !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
svg {
|
||||
fill: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -312,3 +277,35 @@
|
||||
|
||||
@include transition(all .3s ease-out);
|
||||
}
|
||||
|
||||
/**
|
||||
* Audio and video buttons do not have toggled state.
|
||||
*/
|
||||
.audio-preview,
|
||||
.video-preview {
|
||||
.toolbox-icon.toggled {
|
||||
background: none;
|
||||
|
||||
&:hover {
|
||||
background: $newToolbarButtonHoverColor;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* On small mobile devices make the toolbar full width.
|
||||
*/
|
||||
.toolbox-content-mobile {
|
||||
@media (max-width: 500px) {
|
||||
margin-bottom: 0;
|
||||
|
||||
.toolbox-content-items {
|
||||
border-radius: 0;
|
||||
display: flex;
|
||||
justify-content: space-evenly;
|
||||
padding: 6px 0;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
* Style variables
|
||||
*/
|
||||
$baseFontFamily: -apple-system, BlinkMacSystemFont, 'open_sanslight', 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
$hangupColor: #bf2117;
|
||||
$hangupColor:#DD3849;
|
||||
$hangupHoverColor: #F25363;
|
||||
$hangupFontSize: 2em;
|
||||
|
||||
/**
|
||||
@@ -38,19 +39,19 @@ $presence-idle: rgb(172, 172, 172);
|
||||
* Toolbar
|
||||
*/
|
||||
$newToolbarBackgroundColor: rgba(22, 38, 55, 0.8);
|
||||
$newToolbarButtonHoverColor: rgba(255, 255, 255, 0.15);
|
||||
$newToolbarButtonToggleColor: rgba(255, 255, 255, 0.2);
|
||||
$newToolbarButtonHoverColor: rgba(255, 255, 255, 0.2);
|
||||
$newToolbarButtonToggleColor: rgba(255, 255, 255, 0.15);
|
||||
$AOTToolbarButtonHoverColor: rgba(14, 20, 35, 0.6);
|
||||
$AOTToolbarButtonToggleColor: rgba(14, 20, 35, 1);
|
||||
$menuBG:#242528;
|
||||
$newToolbarFontSize: 24px;
|
||||
$newToolbarHangupFontSize: 32px;
|
||||
$newToolbarSize: 40px;
|
||||
$newToolbarSize: 48px;
|
||||
$newToolbarSizeWithPadding: calc(#{$newToolbarSize} + 24px);
|
||||
$toolbarTitleFontSize: 19px;
|
||||
$overflowMenuBG: initial;
|
||||
$overflowMenuItemHoverBG: #313D52;
|
||||
$overflowMenuItemHoverColor: #B8C7E0;
|
||||
$overflowMenuItemColor: #B8C7E0;
|
||||
$overflowMenuItemColor: #fff;
|
||||
$overflowMenuItemBackground: #36383C;
|
||||
|
||||
|
||||
/**
|
||||
* Video layout
|
||||
@@ -122,8 +123,8 @@ $zindex10: 10;
|
||||
$reloadZ: 20;
|
||||
$poweredByZ: 100;
|
||||
$ringingZ: 300;
|
||||
$sideToolbarContainerZ: 300;
|
||||
$toolbarZ: 350;
|
||||
$sideToolbarContainerZ: 200;
|
||||
$toolbarZ: 250;
|
||||
$drawerZ: 351;
|
||||
$tooltipsZ: 401;
|
||||
$dropdownMaskZ: 900;
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
.video-preview {
|
||||
background: none;
|
||||
max-height: 290px;
|
||||
display: inline-block;
|
||||
max-height: 344px;
|
||||
|
||||
&-container {
|
||||
background: $menuBG;
|
||||
border-radius: 3px;
|
||||
overflow: auto;
|
||||
padding: 16px;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
&-entry {
|
||||
cursor: pointer;
|
||||
height: 135px;
|
||||
margin-bottom: 16px;
|
||||
height: 168px;
|
||||
margin-bottom: 8px;
|
||||
position: relative;
|
||||
width: 240px;
|
||||
width: 284px;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
@@ -20,13 +23,15 @@
|
||||
|
||||
&--selected {
|
||||
border: 3px solid #31B76A;
|
||||
border-radius: 3px;
|
||||
cursor: default;
|
||||
height: 129px;
|
||||
width: 234px;
|
||||
height: 162px;
|
||||
width: 278px;
|
||||
}
|
||||
}
|
||||
|
||||
&-video {
|
||||
border-radius: 3px;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
width: 100%;
|
||||
@@ -50,21 +55,28 @@
|
||||
}
|
||||
|
||||
&-label {
|
||||
bottom: 8px;
|
||||
color: #fff;
|
||||
font-size: 13px;
|
||||
line-height: 20px;
|
||||
overflow: hidden;
|
||||
padding: 8px;
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
text-overflow: ellipsis;
|
||||
width: 220px;
|
||||
width: 100%;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
// Override @atlaskit/InlineDialog container which is made with styled components
|
||||
& > div:nth-child(2) {
|
||||
outline: none;
|
||||
padding: 0;
|
||||
&-container {
|
||||
margin: 0 16px;
|
||||
}
|
||||
|
||||
&-text {
|
||||
background-color: #131519;
|
||||
border-radius: 3px;
|
||||
padding: 2px 8px;
|
||||
font-size: 13px;
|
||||
line-height: 20px;
|
||||
margin: 0 auto;
|
||||
max-width: calc(100% - 16px);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
width: fit-content;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,6 +208,11 @@ body.welcome-page {
|
||||
cursor: pointer;
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
.toolbox-icon {
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.welcome-watermark {
|
||||
|
||||
@@ -43,6 +43,7 @@ $flagsImagePath: "../images/";
|
||||
@import 'modals/settings/settings';
|
||||
@import 'modals/speaker_stats/speaker_stats';
|
||||
@import 'modals/video-quality/video-quality';
|
||||
@import 'modals/virtual-background/virtual-background';
|
||||
@import 'modals/local-recording/local-recording';
|
||||
@import 'videolayout_default';
|
||||
@import 'notice';
|
||||
|
||||
44
css/modals/virtual-background/_virtual-background.scss
Normal file
@@ -0,0 +1,44 @@
|
||||
.virtual-background-dialog{
|
||||
display: inline-flex;
|
||||
cursor: pointer;
|
||||
.thumbnail{
|
||||
object-fit: cover;
|
||||
padding: 5px;
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
}
|
||||
.thumbnail-selected{
|
||||
object-fit: cover;
|
||||
padding: 5px;
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
border: 2px solid #a4b8d1;
|
||||
}
|
||||
.blur-selected{
|
||||
border: 2px solid #a4b8d1;
|
||||
}
|
||||
.virtual-background-none{
|
||||
font-weight: bold;
|
||||
padding: 5px;
|
||||
height: 35px;
|
||||
width: 35px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid #a4b8d1;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
line-height: 35px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
.none-selected{
|
||||
font-weight: bold;
|
||||
padding: 5px;
|
||||
height: 35px;
|
||||
width: 35px;
|
||||
border-radius: 10px;
|
||||
border: 2px solid #a4b8d1;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
line-height: 35px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
@@ -33,4 +33,12 @@
|
||||
bottom: 24px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&__spinner-container {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
4
debian/jitsi-meet-prosody.postinst
vendored
@@ -35,7 +35,7 @@ case "$1" in
|
||||
db_input critical jitsi-videobridge/jvb-hostname || true
|
||||
db_go
|
||||
fi
|
||||
JVB_HOSTNAME="$RET"
|
||||
JVB_HOSTNAME=$(echo "$RET" | xargs echo -n)
|
||||
|
||||
db_get jitsi-videobridge/jvbsecret
|
||||
if [ -z "$RET" ] ; then
|
||||
@@ -64,7 +64,7 @@ case "$1" in
|
||||
|
||||
# detect dpkg-reconfigure, just delete old links
|
||||
db_get jitsi-meet-prosody/jvb-hostname
|
||||
JVB_HOSTNAME_OLD=$RET
|
||||
JVB_HOSTNAME_OLD=$(echo "$RET" | xargs echo -n)
|
||||
if [ -n "$RET" ] && [ ! "$JVB_HOSTNAME_OLD" = "$JVB_HOSTNAME" ] ; then
|
||||
rm -f /etc/prosody/conf.d/$JVB_HOSTNAME_OLD.cfg.lua
|
||||
rm -f /etc/prosody/certs/$JVB_HOSTNAME_OLD.key
|
||||
|
||||
2
debian/jitsi-meet-prosody.postrm
vendored
@@ -31,7 +31,7 @@ case "$1" in
|
||||
|
||||
purge)
|
||||
db_get jitsi-meet-prosody/jvb-hostname
|
||||
JVB_HOSTNAME=$RET
|
||||
JVB_HOSTNAME=$(echo "$RET" | xargs echo -n)
|
||||
if [ -n "$RET" ]; then
|
||||
rm -f /etc/prosody/conf.avail/$JVB_HOSTNAME.cfg.lua
|
||||
rm -f /etc/prosody/conf.d/$JVB_HOSTNAME.cfg.lua
|
||||
|
||||
2
debian/jitsi-meet-tokens.postinst
vendored
@@ -25,7 +25,7 @@ case "$1" in
|
||||
. /usr/share/debconf/confmodule
|
||||
|
||||
db_get jitsi-meet-prosody/jvb-hostname
|
||||
JVB_HOSTNAME="$RET"
|
||||
JVB_HOSTNAME=$(echo "$RET" | xargs echo -n)
|
||||
|
||||
db_get jitsi-meet-tokens/appid
|
||||
if [ "$RET" = "false" ] ; then
|
||||
|
||||
4
debian/jitsi-meet-turnserver.postinst
vendored
@@ -30,7 +30,7 @@ case "$1" in
|
||||
db_input critical jitsi-videobridge/jvb-hostname || true
|
||||
db_go
|
||||
fi
|
||||
JVB_HOSTNAME="$RET"
|
||||
JVB_HOSTNAME=$(echo "$RET" | xargs echo -n)
|
||||
|
||||
TURN_CONFIG="/etc/turnserver.conf"
|
||||
NGINX_CONFIG="/etc/nginx/sites-available/$JVB_HOSTNAME.conf"
|
||||
@@ -44,7 +44,7 @@ case "$1" in
|
||||
|
||||
# detect dpkg-reconfigure, just delete old links
|
||||
db_get jitsi-meet-turnserver/jvb-hostname
|
||||
JVB_HOSTNAME_OLD=$RET
|
||||
JVB_HOSTNAME_OLD=$(echo "$RET" | xargs echo -n)
|
||||
if [ -n "$RET" ] && [ ! "$JVB_HOSTNAME_OLD" = "$JVB_HOSTNAME" ] ; then
|
||||
if [[ -f $TURN_CONFIG ]] && grep -q "jitsi-meet coturn config" "$TURN_CONFIG" ; then
|
||||
rm -f $TURN_CONFIG
|
||||
|
||||
4
debian/jitsi-meet-web-config.postinst
vendored
@@ -32,12 +32,12 @@ case "$1" in
|
||||
db_go
|
||||
db_get jitsi-videobridge/jvb-hostname
|
||||
fi
|
||||
JVB_HOSTNAME="$RET"
|
||||
JVB_HOSTNAME=$(echo "$RET" | xargs echo -n)
|
||||
|
||||
# detect dpkg-reconfigure
|
||||
RECONFIGURING="false"
|
||||
db_get jitsi-meet/jvb-hostname
|
||||
JVB_HOSTNAME_OLD=$RET
|
||||
JVB_HOSTNAME_OLD=$(echo "$RET" | xargs echo -n)
|
||||
if [ -n "$RET" ] && [ ! "$JVB_HOSTNAME_OLD" = "$JVB_HOSTNAME" ] ; then
|
||||
RECONFIGURING="true"
|
||||
rm -f /etc/jitsi/meet/$JVB_HOSTNAME_OLD-config.js
|
||||
|
||||
2
debian/jitsi-meet-web-config.postrm
vendored
@@ -33,7 +33,7 @@ case "$1" in
|
||||
;;
|
||||
purge)
|
||||
db_get jitsi-meet/jvb-hostname
|
||||
JVB_HOSTNAME=$RET
|
||||
JVB_HOSTNAME=$(echo "$RET" | xargs echo -n)
|
||||
if [ -n "$RET" ]; then
|
||||
rm -f /etc/jitsi/meet/$JVB_HOSTNAME-config.js
|
||||
rm -f /etc/nginx/sites-available/$JVB_HOSTNAME.conf
|
||||
|
||||
@@ -2,9 +2,6 @@
|
||||
<VirtualHost *:80>
|
||||
ServerName jitsi-meet.example.com
|
||||
Redirect permanent / https://jitsi-meet.example.com/
|
||||
RewriteEngine On
|
||||
RewriteCond %{HTTPS} off
|
||||
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
|
||||
</VirtualHost>
|
||||
|
||||
<VirtualHost *:443>
|
||||
@@ -42,11 +39,12 @@
|
||||
</Location>
|
||||
|
||||
ProxyPreserveHost on
|
||||
ProxyPass /http-bind http://localhost:5280/http-bind/
|
||||
ProxyPassReverse /http-bind http://localhost:5280/http-bind/
|
||||
ProxyPass /http-bind http://localhost:5280/http-bind
|
||||
ProxyPassReverse /http-bind http://localhost:5280/http-bind
|
||||
ProxyPass /xmpp-websocket ws://localhost:5280/xmpp-websocket
|
||||
ProxyPassReverse /xmpp-websocket ws://localhost:5280/xmpp-websocket
|
||||
ProxyPassMatch ^/colibri-ws/default-id ws://localhost:9090
|
||||
ProxyPass /colibri-ws/default-id ws://localhost:9090/colibri-ws/default-id
|
||||
ProxyPassReverse /colibri-ws/default-id ws://localhost:9090/colibri-ws/default-id
|
||||
|
||||
RewriteEngine on
|
||||
RewriteRule ^/([a-zA-Z0-9]+)$ /index.html
|
||||
|
||||
BIN
images/virtual-background/background-1.jpg
Normal file
|
After Width: | Height: | Size: 437 KiB |
BIN
images/virtual-background/background-2.jpg
Normal file
|
After Width: | Height: | Size: 284 KiB |
BIN
images/virtual-background/background-3.jpg
Normal file
|
After Width: | Height: | Size: 216 KiB |
BIN
images/virtual-background/background-4.jpg
Normal file
|
After Width: | Height: | Size: 341 KiB |
@@ -168,6 +168,13 @@ var interfaceConfig = {
|
||||
REMOTE_THUMBNAIL_RATIO: 1, // 1:1
|
||||
|
||||
SETTINGS_SECTIONS: [ 'devices', 'language', 'moderator', 'profile', 'calendar' ],
|
||||
|
||||
/**
|
||||
* Specify which sharing features should be displayed. If the value is not set
|
||||
* all sharing features will be shown. You can set [] to disable all.
|
||||
*/
|
||||
// SHARING_FEATURES: ['email', 'url', 'dial-in', 'embed'],
|
||||
|
||||
SHOW_BRAND_WATERMARK: false,
|
||||
|
||||
/**
|
||||
@@ -191,23 +198,16 @@ var interfaceConfig = {
|
||||
TOOLBAR_ALWAYS_VISIBLE: false,
|
||||
|
||||
/**
|
||||
* The name of the toolbar buttons to display in the toolbar, including the
|
||||
* "More actions" menu. If present, the button will display. Exceptions are
|
||||
* "livestreaming" and "recording" which also require being a moderator and
|
||||
* some values in config.js to be enabled. Also, the "profile" button will
|
||||
* not display for users with a JWT.
|
||||
* Notes:
|
||||
* - it's impossible to choose which buttons go in the "More actions" menu
|
||||
* - it's impossible to control the placement of buttons
|
||||
* - 'desktop' controls the "Share your screen" button
|
||||
* DEPRECATED!
|
||||
* This config was moved to config.js as `toolbarButtons`.
|
||||
*/
|
||||
TOOLBAR_BUTTONS: [
|
||||
'microphone', 'camera', 'closedcaptions', 'desktop', 'embedmeeting', 'fullscreen',
|
||||
'fodeviceselection', 'hangup', 'profile', 'chat', 'recording',
|
||||
'livestreaming', 'etherpad', 'sharedvideo', 'settings', 'raisehand',
|
||||
'videoquality', 'filmstrip', 'invite', 'feedback', 'stats', 'shortcuts',
|
||||
'tileview', 'videobackgroundblur', 'download', 'help', 'mute-everyone', 'security'
|
||||
],
|
||||
// TOOLBAR_BUTTONS: [
|
||||
// 'microphone', 'camera', 'closedcaptions', 'desktop', 'embedmeeting', 'fullscreen',
|
||||
// 'fodeviceselection', 'hangup', 'profile', 'chat', 'recording',
|
||||
// 'livestreaming', 'etherpad', 'sharedvideo', 'settings', 'raisehand',
|
||||
// 'videoquality', 'filmstrip', 'invite', 'feedback', 'stats', 'shortcuts',
|
||||
// 'tileview', 'select-background', 'download', 'help', 'mute-everyone', 'mute-video-everyone', 'security'
|
||||
// ],
|
||||
|
||||
TOOLBAR_TIMEOUT: 4000,
|
||||
|
||||
|
||||
@@ -586,4 +586,4 @@ SPEC CHECKSUMS:
|
||||
|
||||
PODFILE CHECKSUM: 5be5132e41831a98362eeed760558227a4df89ae
|
||||
|
||||
COCOAPODS: 1.10.0
|
||||
COCOAPODS: 1.10.1
|
||||
|
||||
@@ -8,6 +8,10 @@
|
||||
<string>applinks:beta.meet.jit.si</string>
|
||||
<string>applinks:meet.jit.si</string>
|
||||
</array>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>group.org.jitsi.meet.appgroup</string>
|
||||
</array>
|
||||
<key>com.apple.developer.siri</key>
|
||||
<true/>
|
||||
</dict>
|
||||
|
||||
@@ -23,6 +23,12 @@
|
||||
13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB11A68108700A75B9A /* LaunchScreen.xib */; };
|
||||
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
|
||||
13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; };
|
||||
4E51B75E25E4115F0038575A /* DarwinNotificationCenter.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E51B75D25E4115F0038575A /* DarwinNotificationCenter.m */; };
|
||||
4EC49BB725BEDAC100E76218 /* ReplayKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4EC49B8625BED71300E76218 /* ReplayKit.framework */; };
|
||||
4EC49BBB25BEDAC100E76218 /* SampleHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 4EC49BBA25BEDAC100E76218 /* SampleHandler.m */; };
|
||||
4EC49BBF25BEDAC100E76218 /* JitsiMeetBroadcast Extension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 4EC49BB625BEDAC100E76218 /* JitsiMeetBroadcast Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
4EC49BCB25BEDB6400E76218 /* SocketConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = 4EC49BCA25BEDB6400E76218 /* SocketConnection.m */; };
|
||||
4EC49BD125BF19CF00E76218 /* SampleUploader.m in Sources */ = {isa = PBXBuildFile; fileRef = 4EC49BD025BF19CF00E76218 /* SampleUploader.m */; };
|
||||
55BEDABDA92D47D399A70A5E /* libPods-JitsiMeet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D878B07B3FBD6E305EAA6B27 /* libPods-JitsiMeet.a */; };
|
||||
DE050389256E904600DEE3A5 /* WebRTC.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = DE050388256E904600DEE3A5 /* WebRTC.xcframework */; };
|
||||
DE05038A256E904600DEE3A5 /* WebRTC.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = DE050388256E904600DEE3A5 /* WebRTC.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
@@ -48,6 +54,13 @@
|
||||
remoteGlobalIDString = 0BEA5C241F7B8F73000D0AB4;
|
||||
remoteInfo = JitsiMeetCompanion;
|
||||
};
|
||||
4EC49BBD25BEDAC100E76218 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 4EC49BB525BEDAC100E76218;
|
||||
remoteInfo = "JitsiMeetBroadcast Extension";
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
@@ -85,6 +98,17 @@
|
||||
name = "Embed Watch Content";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
4EC49B9025BED71300E76218 /* Embed App Extensions */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 13;
|
||||
files = (
|
||||
4EC49BBF25BEDAC100E76218 /* JitsiMeetBroadcast Extension.appex in Embed App Extensions */,
|
||||
);
|
||||
name = "Embed App Extensions";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
@@ -115,6 +139,18 @@
|
||||
13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
|
||||
4670A512A688E2DC34528282 /* Pods-jitsi-meet.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-jitsi-meet.debug.xcconfig"; path = "../Pods/Target Support Files/Pods-jitsi-meet/Pods-jitsi-meet.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
4E51B75C25E4115F0038575A /* DarwinNotificationCenter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DarwinNotificationCenter.h; sourceTree = "<group>"; };
|
||||
4E51B75D25E4115F0038575A /* DarwinNotificationCenter.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DarwinNotificationCenter.m; sourceTree = "<group>"; };
|
||||
4EC49B8625BED71300E76218 /* ReplayKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ReplayKit.framework; path = System/Library/Frameworks/ReplayKit.framework; sourceTree = SDKROOT; };
|
||||
4EC49BB625BEDAC100E76218 /* JitsiMeetBroadcast Extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "JitsiMeetBroadcast Extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
4EC49BB925BEDAC100E76218 /* SampleHandler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SampleHandler.h; sourceTree = "<group>"; };
|
||||
4EC49BBA25BEDAC100E76218 /* SampleHandler.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SampleHandler.m; sourceTree = "<group>"; };
|
||||
4EC49BBC25BEDAC100E76218 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
4EC49BC925BEDB6400E76218 /* SocketConnection.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SocketConnection.h; sourceTree = "<group>"; };
|
||||
4EC49BCA25BEDB6400E76218 /* SocketConnection.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SocketConnection.m; sourceTree = "<group>"; };
|
||||
4EC49BCF25BF19CF00E76218 /* SampleUploader.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SampleUploader.h; sourceTree = "<group>"; };
|
||||
4EC49BD025BF19CF00E76218 /* SampleUploader.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SampleUploader.m; sourceTree = "<group>"; };
|
||||
4EC49BDB25BF280A00E76218 /* extension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = extension.entitlements; sourceTree = "<group>"; };
|
||||
609CB2080B75F75A89923F3D /* Pods-JitsiMeet.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JitsiMeet.debug.xcconfig"; path = "../Pods/Target Support Files/Pods-JitsiMeet/Pods-JitsiMeet.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
B3B083EB1D4955FF0069CEE7 /* app.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = app.entitlements; sourceTree = "<group>"; };
|
||||
D878B07B3FBD6E305EAA6B27 /* libPods-JitsiMeet.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-JitsiMeet.a"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
@@ -153,6 +189,14 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
4EC49BB325BEDAC100E76218 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
4EC49BB725BEDAC100E76218 /* ReplayKit.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
@@ -165,6 +209,7 @@
|
||||
DEFDBBDB25656E3B00344B23 /* WebRTC.xcframework */,
|
||||
0BD6B4361EF82A6B00D1F4CD /* WebRTC.framework */,
|
||||
D878B07B3FBD6E305EAA6B27 /* libPods-JitsiMeet.a */,
|
||||
4EC49B8625BED71300E76218 /* ReplayKit.framework */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
@@ -216,6 +261,24 @@
|
||||
path = src;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4EC49BB825BEDAC100E76218 /* JitsiMeetBroadcast Extension */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4EC49BDB25BF280A00E76218 /* extension.entitlements */,
|
||||
4EC49BB925BEDAC100E76218 /* SampleHandler.h */,
|
||||
4EC49BBA25BEDAC100E76218 /* SampleHandler.m */,
|
||||
4EC49BC925BEDB6400E76218 /* SocketConnection.h */,
|
||||
4EC49BCA25BEDB6400E76218 /* SocketConnection.m */,
|
||||
4EC49BCF25BF19CF00E76218 /* SampleUploader.h */,
|
||||
4EC49BD025BF19CF00E76218 /* SampleUploader.m */,
|
||||
4EC49BBC25BEDAC100E76218 /* Info.plist */,
|
||||
4E51B75C25E4115F0038575A /* DarwinNotificationCenter.h */,
|
||||
4E51B75D25E4115F0038575A /* DarwinNotificationCenter.m */,
|
||||
);
|
||||
name = "JitsiMeetBroadcast Extension";
|
||||
path = "broadcast-extension";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
5E96ADD5E49F3B3822EF9A52 /* Pods */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -236,6 +299,7 @@
|
||||
13B07FAE1A68108700A75B9A /* src */,
|
||||
5E96ADD5E49F3B3822EF9A52 /* Pods */,
|
||||
0BEA5C261F7B8F73000D0AB4 /* Watch app */,
|
||||
4EC49BB825BEDAC100E76218 /* JitsiMeetBroadcast Extension */,
|
||||
0BEA5C351F7B8F73000D0AB4 /* WatchKit extension */,
|
||||
);
|
||||
indentWidth = 2;
|
||||
@@ -248,6 +312,7 @@
|
||||
13B07F961A680F5B00A75B9A /* jitsi-meet.app */,
|
||||
0BEA5C251F7B8F73000D0AB4 /* JitsiMeetCompanion.app */,
|
||||
0BEA5C311F7B8F73000D0AB4 /* JitsiMeetCompanion Extension.appex */,
|
||||
4EC49BB625BEDAC100E76218 /* JitsiMeetBroadcast Extension.appex */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
@@ -305,17 +370,36 @@
|
||||
DE11877A21EE09640078D059 /* Setup Google reverse URL handler */,
|
||||
DE4F6D6E22005C0400DE699E /* Setup Dropbox */,
|
||||
0BEA5C491F7B8F73000D0AB4 /* Embed Watch Content */,
|
||||
4EC49B9025BED71300E76218 /* Embed App Extensions */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
0BEA5C401F7B8F73000D0AB4 /* PBXTargetDependency */,
|
||||
4EC49BBE25BEDAC100E76218 /* PBXTargetDependency */,
|
||||
);
|
||||
name = JitsiMeet;
|
||||
productName = "Jitsi Meet";
|
||||
productReference = 13B07F961A680F5B00A75B9A /* jitsi-meet.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
4EC49BB525BEDAC100E76218 /* JitsiMeetBroadcast Extension */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 4EC49BC025BEDAC100E76218 /* Build configuration list for PBXNativeTarget "JitsiMeetBroadcast Extension" */;
|
||||
buildPhases = (
|
||||
4EC49BB225BEDAC100E76218 /* Sources */,
|
||||
4EC49BB325BEDAC100E76218 /* Frameworks */,
|
||||
4EC49BB425BEDAC100E76218 /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = "JitsiMeetBroadcast Extension";
|
||||
productName = "JitsiMeetBroadcast Extension";
|
||||
productReference = 4EC49BB625BEDAC100E76218 /* JitsiMeetBroadcast Extension.appex */;
|
||||
productType = "com.apple.product-type.app-extension";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
@@ -336,8 +420,6 @@
|
||||
ProvisioningStyle = Automatic;
|
||||
};
|
||||
13B07F861A680F5B00A75B9A = {
|
||||
DevelopmentTeam = FC967L3QRG;
|
||||
ProvisioningStyle = Automatic;
|
||||
SystemCapabilities = {
|
||||
com.apple.SafariKeychain = {
|
||||
enabled = 1;
|
||||
@@ -347,6 +429,9 @@
|
||||
};
|
||||
};
|
||||
};
|
||||
4EC49BB525BEDAC100E76218 = {
|
||||
CreatedOnToolsVersion = 12.2;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "app" */;
|
||||
@@ -365,6 +450,7 @@
|
||||
13B07F861A680F5B00A75B9A /* JitsiMeet */,
|
||||
0BEA5C241F7B8F73000D0AB4 /* JitsiMeetCompanion */,
|
||||
0BEA5C301F7B8F73000D0AB4 /* JitsiMeetCompanion Extension */,
|
||||
4EC49BB525BEDAC100E76218 /* JitsiMeetBroadcast Extension */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
@@ -397,6 +483,13 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
4EC49BB425BEDAC100E76218 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
@@ -532,6 +625,17 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
4EC49BB225BEDAC100E76218 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
4EC49BCB25BEDB6400E76218 /* SocketConnection.m in Sources */,
|
||||
4EC49BBB25BEDAC100E76218 /* SampleHandler.m in Sources */,
|
||||
4E51B75E25E4115F0038575A /* DarwinNotificationCenter.m in Sources */,
|
||||
4EC49BD125BF19CF00E76218 /* SampleUploader.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
@@ -545,6 +649,11 @@
|
||||
target = 0BEA5C241F7B8F73000D0AB4 /* JitsiMeetCompanion */;
|
||||
targetProxy = 0BEA5C3F1F7B8F73000D0AB4 /* PBXContainerItemProxy */;
|
||||
};
|
||||
4EC49BBE25BEDAC100E76218 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 4EC49BB525BEDAC100E76218 /* JitsiMeetBroadcast Extension */;
|
||||
targetProxy = 4EC49BBD25BEDAC100E76218 /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
@@ -718,7 +827,7 @@
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIconDebug;
|
||||
CODE_SIGN_ENTITLEMENTS = app.entitlements;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEAD_CODE_STRIPPING = NO;
|
||||
@@ -748,7 +857,7 @@
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIconRelease;
|
||||
CODE_SIGN_ENTITLEMENTS = app.entitlements;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = FC967L3QRG;
|
||||
@@ -770,6 +879,70 @@
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
4EC49BC125BEDAC100E76218 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_ENTITLEMENTS = "broadcast-extension/extension.entitlements";
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = FC967L3QRG;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
INFOPLIST_FILE = "broadcast-extension/Info.plist";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.jitsi.meet.broadcast.extension;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
4EC49BC225BEDAC100E76218 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_ENTITLEMENTS = "broadcast-extension/extension.entitlements";
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = FC967L3QRG;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
INFOPLIST_FILE = "broadcast-extension/Info.plist";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.jitsi.meet.broadcast.extension;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
83CBBA201A601CBA00E9B192 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
@@ -914,6 +1087,15 @@
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
4EC49BC025BEDAC100E76218 /* Build configuration list for PBXNativeTarget "JitsiMeetBroadcast Extension" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
4EC49BC125BEDAC100E76218 /* Debug */,
|
||||
4EC49BC225BEDAC100E76218 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "app" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
|
||||
31
ios/app/broadcast-extension/DarwinNotificationCenter.h
Normal file
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright @ 2021-present 8x8, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
extern NSNotificationName const kBroadcastStartedNotification;
|
||||
extern NSNotificationName const kBroadcastStoppedNotification;
|
||||
|
||||
@interface DarwinNotificationCenter: NSObject
|
||||
|
||||
+ (instancetype)sharedInstance;
|
||||
- (void)postNotificationWithName:(NSNotificationName)name;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
50
ios/app/broadcast-extension/DarwinNotificationCenter.m
Normal file
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright @ 2021-present 8x8, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#import "DarwinNotificationCenter.h"
|
||||
|
||||
NSNotificationName const kBroadcastStartedNotification = @"iOS_BroadcastStarted";
|
||||
NSNotificationName const kBroadcastStoppedNotification = @"iOS_BroadcastStopped";
|
||||
|
||||
@implementation DarwinNotificationCenter {
|
||||
CFNotificationCenterRef _notificationCenter;
|
||||
}
|
||||
|
||||
+ (instancetype)sharedInstance {
|
||||
static DarwinNotificationCenter *sharedInstance = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
sharedInstance = [[self alloc] init];
|
||||
});
|
||||
|
||||
return sharedInstance;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_notificationCenter = CFNotificationCenterGetDarwinNotifyCenter();
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)postNotificationWithName:(NSString*)name {
|
||||
CFNotificationCenterPostNotification(_notificationCenter, (__bridge CFStringRef)name, NULL, NULL, true);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
33
ios/app/broadcast-extension/Info.plist
Normal file
@@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>JitsiMeet Broadcast Extension</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>21.0.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>NSExtension</key>
|
||||
<dict>
|
||||
<key>NSExtensionPointIdentifier</key>
|
||||
<string>com.apple.broadcast-services-upload</string>
|
||||
<key>NSExtensionPrincipalClass</key>
|
||||
<string>SampleHandler</string>
|
||||
<key>RPBroadcastProcessMode</key>
|
||||
<string>RPBroadcastProcessModeSampleBuffer</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
21
ios/app/broadcast-extension/SampleHandler.h
Normal file
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright @ 2021-present 8x8, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#import <ReplayKit/ReplayKit.h>
|
||||
|
||||
@interface SampleHandler : RPBroadcastSampleHandler
|
||||
|
||||
@end
|
||||
123
ios/app/broadcast-extension/SampleHandler.m
Normal file
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Copyright @ 2021-present 8x8, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#import "SampleHandler.h"
|
||||
#import "SocketConnection.h"
|
||||
#import "SampleUploader.h"
|
||||
#import "DarwinNotificationCenter.h"
|
||||
|
||||
@interface SampleHandler ()
|
||||
|
||||
@property (nonatomic, retain) SocketConnection *clientConnection;
|
||||
@property (nonatomic, retain) SampleUploader *uploader;
|
||||
|
||||
@end
|
||||
|
||||
@implementation SampleHandler
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
self.clientConnection = [[SocketConnection alloc] initWithFilePath:self.socketFilePath];
|
||||
[self setupConnection];
|
||||
|
||||
self.uploader = [[SampleUploader alloc] initWithConnection:self.clientConnection];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)broadcastStartedWithSetupInfo:(NSDictionary<NSString *,NSObject *> *)setupInfo {
|
||||
// User has requested to start the broadcast. Setup info from the UI extension can be supplied but optional.
|
||||
NSLog(@"broadcast started");
|
||||
|
||||
[[DarwinNotificationCenter sharedInstance] postNotificationWithName:kBroadcastStartedNotification];
|
||||
[self openConnection];
|
||||
}
|
||||
|
||||
- (void)broadcastPaused {
|
||||
// User has requested to pause the broadcast. Samples will stop being delivered.
|
||||
}
|
||||
|
||||
- (void)broadcastResumed {
|
||||
// User has requested to resume the broadcast. Samples delivery will resume.
|
||||
}
|
||||
|
||||
- (void)broadcastFinished {
|
||||
// User has requested to finish the broadcast.
|
||||
[[DarwinNotificationCenter sharedInstance] postNotificationWithName:kBroadcastStoppedNotification];
|
||||
[self.clientConnection close];
|
||||
}
|
||||
|
||||
- (void)processSampleBuffer:(CMSampleBufferRef)sampleBuffer withType:(RPSampleBufferType)sampleBufferType {
|
||||
static NSUInteger frameCount = 0;
|
||||
switch (sampleBufferType) {
|
||||
case RPSampleBufferTypeVideo:
|
||||
// adjust frame rate by using every third frame
|
||||
if (++frameCount%3 == 0 && self.uploader.isReady) {
|
||||
[self.uploader sendSample:sampleBuffer];
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Private Methods
|
||||
|
||||
- (NSString *)socketFilePath {
|
||||
// the appGroupIdentifier must match the value provided in the app's info.plist for the RTCAppGroupIdentifier key
|
||||
NSString *appGroupIdentifier = @"group.org.jitsi.meet.appgroup";
|
||||
NSURL *sharedContainer = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:appGroupIdentifier];
|
||||
NSString *socketFilePath = [[sharedContainer URLByAppendingPathComponent:@"rtc_SSFD"] path];
|
||||
|
||||
return socketFilePath;
|
||||
}
|
||||
|
||||
- (void)setupConnection {
|
||||
__weak __typeof(self) weakSelf = self;
|
||||
self.clientConnection.didClose = ^(NSError *error) {
|
||||
NSLog(@"client connection did close: %@", error);
|
||||
if (error) {
|
||||
[weakSelf finishBroadcastWithError:error];
|
||||
}
|
||||
else {
|
||||
NSInteger JMScreenSharingStopped = 10001;
|
||||
NSError *customError = [NSError errorWithDomain:RPRecordingErrorDomain
|
||||
code:JMScreenSharingStopped
|
||||
userInfo:@{NSLocalizedDescriptionKey: @"Screen sharing stopped"}];
|
||||
[weakSelf finishBroadcastWithError:customError];
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
- (void)openConnection {
|
||||
dispatch_queue_t queue = dispatch_queue_create("org.jitsi.meet.broadcast.connectTimer", 0);
|
||||
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
|
||||
dispatch_source_set_timer(timer, dispatch_walltime(NULL, 0), 0.1 * NSEC_PER_SEC, 0.1 * NSEC_PER_SEC);
|
||||
|
||||
dispatch_source_set_event_handler(timer, ^{
|
||||
BOOL success = [self.clientConnection open];
|
||||
if (success) {
|
||||
dispatch_source_cancel(timer);
|
||||
}
|
||||
});
|
||||
|
||||
dispatch_resume(timer);
|
||||
}
|
||||
|
||||
@end
|
||||
33
ios/app/broadcast-extension/SampleUploader.h
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright @ 2021-present 8x8, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <ReplayKit/ReplayKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class SocketConnection;
|
||||
|
||||
@interface SampleUploader : NSObject
|
||||
|
||||
@property (nonatomic, assign, readonly) BOOL isReady;
|
||||
|
||||
- (instancetype)initWithConnection:(SocketConnection *)connection;
|
||||
- (void)sendSample:(CMSampleBufferRef)sampleBuffer;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
155
ios/app/broadcast-extension/SampleUploader.m
Normal file
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* Copyright @ 2021-present 8x8, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#import <MessageUI/MessageUI.h>
|
||||
#import <ReplayKit/ReplayKit.h>
|
||||
|
||||
#import "SampleUploader.h"
|
||||
#import "SocketConnection.h"
|
||||
|
||||
static const NSInteger kBufferMaxLenght = 10 * 1024;
|
||||
|
||||
@interface SampleUploader ()
|
||||
|
||||
@property (nonatomic, assign) BOOL isReady;
|
||||
|
||||
@property (nonatomic, strong) dispatch_queue_t serialQueue;
|
||||
@property (nonatomic, strong) SocketConnection *connection;
|
||||
@property (nonatomic, strong) CIContext *imageContext;
|
||||
|
||||
@property (nonatomic, strong) NSData *dataToSend;
|
||||
@property (nonatomic, assign) NSUInteger byteIndex;
|
||||
|
||||
@end
|
||||
|
||||
@implementation SampleUploader
|
||||
|
||||
- (instancetype)initWithConnection:(SocketConnection *)connection {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
self.serialQueue = dispatch_queue_create("org.jitsi.meet.broadcast.sampleUploader", DISPATCH_QUEUE_SERIAL);
|
||||
|
||||
self.connection = connection;
|
||||
[self setupConnection];
|
||||
|
||||
self.imageContext = [[CIContext alloc] initWithOptions:nil];
|
||||
self.isReady = false;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)sendSample:(CMSampleBufferRef)sampleBuffer {
|
||||
self.isReady = false;
|
||||
|
||||
self.dataToSend = [self prepareSample:sampleBuffer];
|
||||
self.byteIndex = 0;
|
||||
|
||||
dispatch_async(self.serialQueue, ^{
|
||||
[self sendData];
|
||||
});
|
||||
}
|
||||
|
||||
// MARK: Private Methods
|
||||
|
||||
- (void)setupConnection {
|
||||
__weak __typeof(self) weakSelf = self;
|
||||
self.connection.didOpen = ^{
|
||||
weakSelf.isReady = true;
|
||||
};
|
||||
self.connection.streamHasSpaceAvailable = ^{
|
||||
dispatch_async(weakSelf.serialQueue, ^{
|
||||
weakSelf.isReady = ![weakSelf sendData];
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
This function downscales and converts to jpeg the provided sample buffer, then wraps the resulted image data into a CFHTTPMessageRef. Returns the serialized CFHTTPMessageRef.
|
||||
*/
|
||||
- (NSData *)prepareSample:(CMSampleBufferRef)sampleBuffer {
|
||||
CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
|
||||
|
||||
CVPixelBufferLockBaseAddress(imageBuffer, kCVPixelBufferLock_ReadOnly);
|
||||
|
||||
CGFloat scaleFactor = 2;
|
||||
size_t width = CVPixelBufferGetWidth(imageBuffer)/scaleFactor;
|
||||
size_t height = CVPixelBufferGetHeight(imageBuffer)/scaleFactor;
|
||||
|
||||
CGAffineTransform scaleTransform = CGAffineTransformMakeScale(1/scaleFactor, 1/scaleFactor);
|
||||
NSData *bufferData = [self jpegDataFromPixelBuffer:imageBuffer withScaling:scaleTransform];
|
||||
|
||||
CVPixelBufferUnlockBaseAddress(imageBuffer, kCVPixelBufferLock_ReadOnly);
|
||||
|
||||
if (bufferData) {
|
||||
CFHTTPMessageRef httpResponse = CFHTTPMessageCreateResponse(kCFAllocatorDefault, 200, NULL, kCFHTTPVersion1_1);
|
||||
CFHTTPMessageSetHeaderFieldValue(httpResponse, (__bridge CFStringRef)@"Content-Length", (__bridge CFStringRef)[NSString stringWithFormat:@"%ld", bufferData.length]);
|
||||
CFHTTPMessageSetHeaderFieldValue(httpResponse, (__bridge CFStringRef)@"Buffer-Width", (__bridge CFStringRef)[NSString stringWithFormat:@"%ld", width]);
|
||||
CFHTTPMessageSetHeaderFieldValue(httpResponse, (__bridge CFStringRef)@"Buffer-Height", (__bridge CFStringRef)[NSString stringWithFormat:@"%ld", height]);
|
||||
|
||||
CFHTTPMessageSetBody(httpResponse, (__bridge CFDataRef)bufferData);
|
||||
|
||||
CFDataRef serializedMessage = CFHTTPMessageCopySerializedMessage(httpResponse);
|
||||
CFRelease(httpResponse);
|
||||
|
||||
return CFBridgingRelease(serializedMessage);
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (BOOL)sendData {
|
||||
if (!self.dataToSend) {
|
||||
NSLog(@"no data to send");
|
||||
return false;
|
||||
}
|
||||
|
||||
NSUInteger bytesLeft = self.dataToSend.length - self.byteIndex;
|
||||
|
||||
NSInteger length = bytesLeft > kBufferMaxLenght ? kBufferMaxLenght : bytesLeft;
|
||||
uint8_t buffer[length];
|
||||
[self.dataToSend getBytes:&buffer range:NSMakeRange(self.byteIndex, length)];
|
||||
|
||||
length = [self.connection writeBufferToStream:buffer maxLength:length];
|
||||
if (length > 0) {
|
||||
self.byteIndex += length;
|
||||
bytesLeft -= length;
|
||||
|
||||
if (bytesLeft == 0) {
|
||||
NSLog(@"video sample processed successfully");
|
||||
self.dataToSend = nil;
|
||||
self.byteIndex = 0;
|
||||
}
|
||||
}
|
||||
else {
|
||||
NSLog(@"writeBufferToStream failure");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
- (NSData *)jpegDataFromPixelBuffer:(CVPixelBufferRef)pixelBuffer withScaling:(CGAffineTransform)scaleTransform {
|
||||
CIImage *image = [[CIImage alloc] initWithCVPixelBuffer:pixelBuffer];
|
||||
image = [image imageByApplyingTransform:scaleTransform];
|
||||
|
||||
NSDictionary *options = @{(NSString *)kCGImageDestinationLossyCompressionQuality: [NSNumber numberWithFloat:1.0]};
|
||||
NSData *imageData = [self.imageContext JPEGRepresentationOfImage:image
|
||||
colorSpace:image.colorSpace
|
||||
options:options];
|
||||
return imageData;
|
||||
}
|
||||
|
||||
@end
|
||||
34
ios/app/broadcast-extension/SocketConnection.h
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright @ 2021-present 8x8, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface SocketConnection : NSObject
|
||||
|
||||
@property (nonatomic, copy, nullable) void (^didOpen)(void);
|
||||
@property (nonatomic, copy, nullable) void (^didClose)(NSError*);
|
||||
@property (nonatomic, copy, nullable) void (^streamHasSpaceAvailable)(void);
|
||||
|
||||
- (instancetype)initWithFilePath:(nonnull NSString *)filePath;
|
||||
- (BOOL)open;
|
||||
- (void)close;
|
||||
- (NSInteger)writeBufferToStream:(const uint8_t*)buffer maxLength:(NSInteger)length;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
189
ios/app/broadcast-extension/SocketConnection.m
Normal file
@@ -0,0 +1,189 @@
|
||||
/*
|
||||
* Copyright @ 2021-present 8x8, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
|
||||
#import "SocketConnection.h"
|
||||
|
||||
@interface SocketConnection () <NSStreamDelegate>
|
||||
|
||||
@property (nonatomic, copy) NSString *filePath;
|
||||
|
||||
@property (nonatomic, strong) NSInputStream *inputStream;
|
||||
@property (nonatomic, strong) NSOutputStream *outputStream;
|
||||
|
||||
@property (nonatomic, strong) NSThread *networkThread;
|
||||
|
||||
@end
|
||||
|
||||
@implementation SocketConnection {
|
||||
int _socket;
|
||||
struct sockaddr_un _socketAddr;
|
||||
}
|
||||
|
||||
- (instancetype)initWithFilePath:(NSString *)path {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
self.filePath = path;
|
||||
|
||||
[self setupSocketWithFilePath:path];
|
||||
[self setupNetworkThread];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)open {
|
||||
NSLog(@"Open socket connection");
|
||||
|
||||
if (![[NSFileManager defaultManager] fileExistsAtPath:self.filePath]) {
|
||||
NSLog(@"failure: socket file missing");
|
||||
return false;
|
||||
}
|
||||
|
||||
int status = connect(_socket, (struct sockaddr *)&_socketAddr, sizeof(_socketAddr));
|
||||
if (status < 0) {
|
||||
NSLog(@"failure: socket connect (%d)", status);
|
||||
return false;
|
||||
}
|
||||
|
||||
[self.networkThread start];
|
||||
|
||||
CFReadStreamRef readStream;
|
||||
CFWriteStreamRef writeStream;
|
||||
|
||||
CFStreamCreatePairWithSocket(kCFAllocatorDefault, _socket, &readStream, &writeStream);
|
||||
|
||||
self.inputStream = (__bridge_transfer NSInputStream *)readStream;
|
||||
self.inputStream.delegate = self;
|
||||
[self.inputStream setProperty:@"kCFBooleanTrue" forKey:@"kCFStreamPropertyShouldCloseNativeSocket"];
|
||||
|
||||
self.outputStream = (__bridge_transfer NSOutputStream *)writeStream;
|
||||
self.outputStream.delegate = self;
|
||||
[self.outputStream setProperty:@"kCFBooleanTrue" forKey:@"kCFStreamPropertyShouldCloseNativeSocket"];
|
||||
|
||||
[self performSelector:@selector(scheduleStreams) onThread:self.networkThread withObject:nil waitUntilDone:true];
|
||||
|
||||
[self.inputStream open];
|
||||
[self.outputStream open];
|
||||
|
||||
NSLog(@"read stream status: %ld", CFReadStreamGetStatus(readStream));
|
||||
NSLog(@"write stream status: %ld", CFWriteStreamGetStatus(writeStream));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
- (void)close {
|
||||
[self performSelector:@selector(unscheduleStreams) onThread:self.networkThread withObject:nil waitUntilDone:true];
|
||||
|
||||
self.inputStream.delegate = nil;
|
||||
self.outputStream.delegate = nil;
|
||||
|
||||
[self.inputStream close];
|
||||
[self.outputStream close];
|
||||
|
||||
[self.networkThread cancel];
|
||||
}
|
||||
|
||||
- (NSInteger)writeBufferToStream:(const uint8_t*)buffer maxLength:(NSInteger)length {
|
||||
return [self.outputStream write:buffer maxLength:length];
|
||||
}
|
||||
|
||||
// MARK: Private Methods
|
||||
|
||||
- (BOOL)isOpen {
|
||||
return self.inputStream.streamStatus == NSStreamStatusOpen && self.outputStream.streamStatus == NSStreamStatusOpen;
|
||||
}
|
||||
|
||||
- (void)setupSocketWithFilePath:(NSString*)path {
|
||||
_socket = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
|
||||
memset(&_socketAddr, 0, sizeof(_socketAddr));
|
||||
_socketAddr.sun_family = AF_UNIX;
|
||||
strncpy(_socketAddr.sun_path, path.UTF8String, sizeof(_socketAddr.sun_path) - 1);
|
||||
}
|
||||
|
||||
- (void)setupNetworkThread {
|
||||
self.networkThread = [[NSThread alloc] initWithBlock:^{
|
||||
do {
|
||||
@autoreleasepool {
|
||||
[[NSRunLoop currentRunLoop] run];
|
||||
}
|
||||
} while (![NSThread currentThread].isCancelled);
|
||||
}];
|
||||
self.networkThread.qualityOfService = NSQualityOfServiceUserInitiated;
|
||||
}
|
||||
|
||||
- (void)scheduleStreams {
|
||||
[self.inputStream scheduleInRunLoop:NSRunLoop.currentRunLoop forMode:NSRunLoopCommonModes];
|
||||
[self.outputStream scheduleInRunLoop:NSRunLoop.currentRunLoop forMode:NSRunLoopCommonModes];
|
||||
}
|
||||
|
||||
- (void)unscheduleStreams {
|
||||
[self.inputStream removeFromRunLoop:NSRunLoop.currentRunLoop forMode:NSRunLoopCommonModes];
|
||||
[self.outputStream removeFromRunLoop:NSRunLoop.currentRunLoop forMode:NSRunLoopCommonModes];
|
||||
}
|
||||
|
||||
- (void)notifyDidClose:(NSError *)error {
|
||||
if (self.didClose) {
|
||||
self.didClose(error);
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - NSStreamDelegate
|
||||
|
||||
@implementation SocketConnection (NSStreamDelegate)
|
||||
|
||||
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode {
|
||||
switch (eventCode) {
|
||||
case NSStreamEventOpenCompleted:
|
||||
NSLog(@"client stream open completed");
|
||||
if (aStream == self.outputStream && self.didOpen) {
|
||||
self.didOpen();
|
||||
}
|
||||
break;
|
||||
case NSStreamEventHasBytesAvailable:
|
||||
if (aStream == self.inputStream) {
|
||||
uint8_t buffer;
|
||||
NSInteger numberOfBytesRead = [(NSInputStream *)aStream read:&buffer maxLength:sizeof(buffer)];
|
||||
if (!numberOfBytesRead && aStream.streamStatus == NSStreamStatusAtEnd) {
|
||||
NSLog(@"server socket closed");
|
||||
[self close];
|
||||
[self notifyDidClose:nil];
|
||||
}
|
||||
}
|
||||
break;
|
||||
case NSStreamEventHasSpaceAvailable:
|
||||
if (aStream == self.outputStream && self.streamHasSpaceAvailable) {
|
||||
NSLog(@"client stream has space available");
|
||||
self.streamHasSpaceAvailable();
|
||||
}
|
||||
break;
|
||||
case NSStreamEventErrorOccurred:
|
||||
NSLog(@"client stream error occurred: %@", aStream.streamError);
|
||||
[self close];
|
||||
[self notifyDidClose:aStream.streamError];
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
10
ios/app/broadcast-extension/extension.entitlements
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>group.org.jitsi.meet.appgroup</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -45,6 +45,8 @@
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>FirebaseCrashlyticsCollectionEnabled</key>
|
||||
<string>false</string>
|
||||
<key>FirebaseScreenReportingEnabled</key>
|
||||
<false/>
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
@@ -66,14 +68,18 @@
|
||||
<string>See your scheduled meetings in the app.</string>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>Participate in meetings with video.</string>
|
||||
<key>NSMicrophoneUsageDescription</key>
|
||||
<string>Participate in meetings with voice.</string>
|
||||
<key>NSLocalNetworkUsageDescription</key>
|
||||
<string>Local network is used for establishing Peer-to-Peer connections.</string>
|
||||
<key>NSMicrophoneUsageDescription</key>
|
||||
<string>Participate in meetings with voice.</string>
|
||||
<key>NSUserActivityTypes</key>
|
||||
<array>
|
||||
<string>org.jitsi.JitsiMeet.ios.conference</string>
|
||||
</array>
|
||||
<key>RTCAppGroupIdentifier</key>
|
||||
<string>group.org.jitsi.meet.appgroup</string>
|
||||
<key>RTCScreenSharingExtension</key>
|
||||
<string>org.jitsi.meet.broadcast.extension</string>
|
||||
<key>UIBackgroundModes</key>
|
||||
<array>
|
||||
<string>audio</string>
|
||||
@@ -99,7 +105,5 @@
|
||||
</array>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<false/>
|
||||
<key>FirebaseCrashlyticsCollectionEnabled</key>
|
||||
<string>false</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -16,6 +16,20 @@ platform :ios do
|
||||
app_identifier: "com.atlassian.JitsiMeet.ios"
|
||||
)
|
||||
|
||||
# Set the broadcast extension identifier
|
||||
update_app_identifier(
|
||||
xcodeproj: "app/app.xcodeproj",
|
||||
plist_path: "broadcast-extension/Info.plist",
|
||||
app_identifier: "com.atlassian.JitsiMeet.ios.broadcast"
|
||||
)
|
||||
update_info_plist(
|
||||
xcodeproj: "app/app.xcodeproj",
|
||||
plist_path: "src/Info.plist",
|
||||
block: proc do |plist|
|
||||
plist["RTCScreenSharingExtension"] = "com.atlassian.JitsiMeet.ios.broadcast"
|
||||
end
|
||||
)
|
||||
|
||||
# Set the (watch) app identifier
|
||||
update_app_identifier(
|
||||
xcodeproj: "app/app.xcodeproj",
|
||||
|
||||
@@ -24,6 +24,8 @@
|
||||
0BCA49601EC4B6C600B793EE /* POSIX.m in Sources */ = {isa = PBXBuildFile; fileRef = 0BCA495D1EC4B6C600B793EE /* POSIX.m */; };
|
||||
0BCA49611EC4B6C600B793EE /* Proximity.m in Sources */ = {isa = PBXBuildFile; fileRef = 0BCA495E1EC4B6C600B793EE /* Proximity.m */; };
|
||||
0BD906EA1EC0C00300C8C18E /* JitsiMeet.h in Headers */ = {isa = PBXBuildFile; fileRef = 0BD906E81EC0C00300C8C18E /* JitsiMeet.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
4E51B76425E5345E0038575A /* ScheenshareEventEmiter.h in Headers */ = {isa = PBXBuildFile; fileRef = 4E51B76225E5345E0038575A /* ScheenshareEventEmiter.h */; };
|
||||
4E51B76525E5345E0038575A /* ScheenshareEventEmiter.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E51B76325E5345E0038575A /* ScheenshareEventEmiter.m */; };
|
||||
6C31EDC820C06D490089C899 /* recordingOn.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 6C31EDC720C06D490089C899 /* recordingOn.mp3 */; };
|
||||
6C31EDCA20C06D530089C899 /* recordingOff.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 6C31EDC920C06D530089C899 /* recordingOff.mp3 */; };
|
||||
6F08DF7D4458EE3CF3F36F6D /* libPods-JitsiMeetSDK.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E4376CA6886DE68FD7A4294B /* libPods-JitsiMeetSDK.a */; };
|
||||
@@ -85,6 +87,8 @@
|
||||
0BD906E51EC0C00300C8C18E /* JitsiMeetSDK.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = JitsiMeetSDK.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
0BD906E81EC0C00300C8C18E /* JitsiMeet.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = JitsiMeet.h; sourceTree = "<group>"; };
|
||||
0BD906E91EC0C00300C8C18E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
4E51B76225E5345E0038575A /* ScheenshareEventEmiter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ScheenshareEventEmiter.h; sourceTree = "<group>"; };
|
||||
4E51B76325E5345E0038575A /* ScheenshareEventEmiter.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ScheenshareEventEmiter.m; sourceTree = "<group>"; };
|
||||
6C31EDC720C06D490089C899 /* recordingOn.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; name = recordingOn.mp3; path = ../../sounds/recordingOn.mp3; sourceTree = "<group>"; };
|
||||
6C31EDC920C06D530089C899 /* recordingOff.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; name = recordingOff.mp3; path = ../../sounds/recordingOff.mp3; sourceTree = "<group>"; };
|
||||
75635B0820751D6D00F29C9F /* joined.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; name = joined.wav; path = ../../sounds/joined.wav; sourceTree = "<group>"; };
|
||||
@@ -231,6 +235,8 @@
|
||||
C8AFD27D2462C613000293D2 /* InfoPlistUtil.h */,
|
||||
C8AFD27E2462C613000293D2 /* InfoPlistUtil.m */,
|
||||
C81E9AB825AC5AD800B134D9 /* ExternalAPI.h */,
|
||||
4E51B76225E5345E0038575A /* ScheenshareEventEmiter.h */,
|
||||
4E51B76325E5345E0038575A /* ScheenshareEventEmiter.m */,
|
||||
);
|
||||
path = src;
|
||||
sourceTree = "<group>";
|
||||
@@ -298,6 +304,7 @@
|
||||
0B93EF7E1EC9DDCD0030D24D /* RCTBridgeWrapper.h in Headers */,
|
||||
DE81A2DE2317ED5400AE1940 /* JitsiMeetBaseLogHandler.h in Headers */,
|
||||
DEA9F284258A5D9900D4CD74 /* JitsiMeetSDK.h in Headers */,
|
||||
4E51B76425E5345E0038575A /* ScheenshareEventEmiter.h in Headers */,
|
||||
DE65AACC2318028300290BEC /* JitsiMeetBaseLogHandler+Private.h in Headers */,
|
||||
0B412F221EDEF6EA00B1A0A6 /* JitsiMeetViewDelegate.h in Headers */,
|
||||
0BD906EA1EC0C00300C8C18E /* JitsiMeet.h in Headers */,
|
||||
@@ -466,6 +473,7 @@
|
||||
C69EFA0C209A0F660027712B /* JMCallKitEmitter.swift in Sources */,
|
||||
DEFE535621FB2E8300011A3A /* ReactUtils.m in Sources */,
|
||||
C6A34261204EF76800E062DD /* DragGestureController.swift in Sources */,
|
||||
4E51B76525E5345E0038575A /* ScheenshareEventEmiter.m in Sources */,
|
||||
A4A934E9212F3ADB001E9388 /* Dropbox.m in Sources */,
|
||||
C69EFA0D209A0F660027712B /* JMCallKitProxy.swift in Sources */,
|
||||
DE81A2D52316AC4D00AE1940 /* JitsiMeetLogger.m in Sources */,
|
||||
|
||||
@@ -20,11 +20,11 @@
|
||||
|
||||
- (void)sendHangUp;
|
||||
- (void)sendSetAudioMuted:(BOOL)muted;
|
||||
- (void)sendEndpointTextMessage:(NSString*)to :(NSString*)message;
|
||||
- (void)sendEndpointTextMessage:(NSString*)message :(NSString*)to;
|
||||
- (void)toggleScreenShare;
|
||||
- (void)retrieveParticipantsInfo:(void (^)(NSArray*))completion;
|
||||
- (void)openChat:(NSString*)to;
|
||||
- (void)closeChat;
|
||||
- (void)sendChatMessage:(NSString*)to :(NSString*)message;
|
||||
- (void)sendChatMessage:(NSString*)message :(NSString*)to ;
|
||||
|
||||
@end
|
||||
|
||||
@@ -153,7 +153,7 @@ RCT_EXPORT_METHOD(sendEvent:(NSString *)name
|
||||
[self sendEventWithName:setAudioMutedAction body:data];
|
||||
}
|
||||
|
||||
- (void)sendEndpointTextMessage:(NSString*)to :(NSString*)message {
|
||||
- (void)sendEndpointTextMessage:(NSString*)message :(NSString*)to {
|
||||
NSMutableDictionary *data = [[NSMutableDictionary alloc] init];
|
||||
data[@"to"] = to;
|
||||
data[@"message"] = message;
|
||||
@@ -185,7 +185,7 @@ RCT_EXPORT_METHOD(sendEvent:(NSString *)name
|
||||
[self sendEventWithName:closeChatAction body:nil];
|
||||
}
|
||||
|
||||
- (void)sendChatMessage:(NSString*)to :(NSString*)message {
|
||||
- (void)sendChatMessage:(NSString*)message :(NSString*)to {
|
||||
NSMutableDictionary *data = [[NSMutableDictionary alloc] init];
|
||||
data[@"to"] = to;
|
||||
data[@"message"] = message;
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>3.1.0</string>
|
||||
<string>3.2.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#import "RCTBridgeWrapper.h"
|
||||
#import "ReactUtils.h"
|
||||
#import "RNSplashScreen.h"
|
||||
#import "ScheenshareEventEmiter.h"
|
||||
|
||||
#import <RNGoogleSignin/RNGoogleSignin.h>
|
||||
#import <WebRTC/RTCLogging.h>
|
||||
@@ -31,6 +32,7 @@
|
||||
@implementation JitsiMeet {
|
||||
RCTBridgeWrapper *_bridgeWrapper;
|
||||
NSDictionary *_launchOptions;
|
||||
ScheenshareEventEmiter *_screenshareEventEmiter;
|
||||
}
|
||||
|
||||
#pragma mak - This class is a singleton
|
||||
@@ -50,6 +52,9 @@
|
||||
if (self = [super init]) {
|
||||
// Initialize the on and only bridge for interfacing with React Native.
|
||||
_bridgeWrapper = [[RCTBridgeWrapper alloc] init];
|
||||
|
||||
// Initialize the listener for handling start/stop screensharing notifications.
|
||||
_screenshareEventEmiter = [[ScheenshareEventEmiter alloc] init];
|
||||
|
||||
// Register a fatal error handler for React.
|
||||
registerReactFatalErrorHandler();
|
||||
|
||||
@@ -38,11 +38,11 @@
|
||||
- (void)leave;
|
||||
- (void)hangUp;
|
||||
- (void)setAudioMuted:(BOOL)muted;
|
||||
- (void)sendEndpointTextMessage:(NSString*)to :(NSString*)message;
|
||||
- (void)sendEndpointTextMessage:(NSString * _Nonnull)message :(NSString * _Nullable)to;
|
||||
- (void)toggleScreenShare;
|
||||
- (void)retrieveParticipantsInfo:(void (^)(NSArray*))completionHandler;
|
||||
- (void)openChat:(NSString*)to;
|
||||
- (void)retrieveParticipantsInfo:(void (^ _Nonnull)(NSArray * _Nullable))completionHandler;
|
||||
- (void)openChat:(NSString * _Nullable)to;
|
||||
- (void)closeChat;
|
||||
- (void)sendChatMessage:(NSString*)to :(NSString*)message;
|
||||
- (void)sendChatMessage:(NSString * _Nonnull)message :(NSString * _Nullable)to;
|
||||
|
||||
@end
|
||||
|
||||
@@ -125,9 +125,9 @@ static void initializeViewsMap() {
|
||||
[externalAPI sendSetAudioMuted:muted];
|
||||
}
|
||||
|
||||
- (void)sendEndpointTextMessage:(NSString*)to :(NSString*)message {
|
||||
- (void)sendEndpointTextMessage:(NSString * _Nonnull)message :(NSString * _Nullable)to {
|
||||
ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI];
|
||||
[externalAPI sendEndpointTextMessage:to :message];
|
||||
[externalAPI sendEndpointTextMessage:message :to];
|
||||
}
|
||||
|
||||
- (void)toggleScreenShare {
|
||||
@@ -135,7 +135,7 @@ static void initializeViewsMap() {
|
||||
[externalAPI toggleScreenShare];
|
||||
}
|
||||
|
||||
- (void)retrieveParticipantsInfo:(void (^)(NSArray*))completionHandler {
|
||||
- (void)retrieveParticipantsInfo:(void (^ _Nonnull)(NSArray * _Nullable))completionHandler {
|
||||
ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI];
|
||||
[externalAPI retrieveParticipantsInfo:completionHandler];
|
||||
}
|
||||
@@ -150,9 +150,9 @@ static void initializeViewsMap() {
|
||||
[externalAPI closeChat];
|
||||
}
|
||||
|
||||
- (void)sendChatMessage:(NSString*)to :(NSString*)message {
|
||||
- (void)sendChatMessage:(NSString * _Nonnull)message :(NSString * _Nullable)to {
|
||||
ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI];
|
||||
[externalAPI sendChatMessage:to :message];
|
||||
[externalAPI sendChatMessage:message :to];
|
||||
}
|
||||
|
||||
#pragma mark Private methods
|
||||
|
||||
25
ios/sdk/src/ScheenshareEventEmiter.h
Normal file
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright @ 2021-present 8x8, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface ScheenshareEventEmiter : NSObject
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
63
ios/sdk/src/ScheenshareEventEmiter.m
Normal file
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright @ 2021-present 8x8, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#import "ScheenshareEventEmiter.h"
|
||||
#import "JitsiMeet+Private.h"
|
||||
#import "ExternalAPI.h"
|
||||
|
||||
NSNotificationName const kBroadcastStartedNotification = @"iOS_BroadcastStarted";
|
||||
NSNotificationName const kBroadcastStoppedNotification = @"iOS_BroadcastStopped";
|
||||
|
||||
@implementation ScheenshareEventEmiter {
|
||||
CFNotificationCenterRef _notificationCenter;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_notificationCenter = CFNotificationCenterGetDarwinNotifyCenter();
|
||||
[self setupObserver];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[self clearObserver];
|
||||
}
|
||||
|
||||
// MARK: Private Methods
|
||||
|
||||
- (void)setupObserver {
|
||||
CFNotificationCenterAddObserver(_notificationCenter, (__bridge const void *)(self), broadcastToggleNotificationCallback, (__bridge CFStringRef)kBroadcastStartedNotification, NULL, CFNotificationSuspensionBehaviorDeliverImmediately);
|
||||
CFNotificationCenterAddObserver(_notificationCenter, (__bridge const void *)(self), broadcastToggleNotificationCallback, (__bridge CFStringRef)kBroadcastStoppedNotification, NULL, CFNotificationSuspensionBehaviorDeliverImmediately);
|
||||
}
|
||||
|
||||
- (void)clearObserver {
|
||||
CFNotificationCenterRemoveObserver(_notificationCenter, (__bridge const void *)(self), (__bridge CFStringRef)kBroadcastStartedNotification, NULL);
|
||||
CFNotificationCenterRemoveObserver(_notificationCenter, (__bridge const void *)(self), (__bridge CFStringRef)kBroadcastStoppedNotification, NULL);
|
||||
}
|
||||
|
||||
void broadcastToggleNotificationCallback(CFNotificationCenterRef center,
|
||||
void *observer,
|
||||
CFStringRef name,
|
||||
const void *object,
|
||||
CFDictionaryRef userInfo) {
|
||||
ExternalAPI *externalAPI = [[JitsiMeet sharedInstance] getExternalAPI];
|
||||
[externalAPI toggleScreenShare];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,6 +1,7 @@
|
||||
|
||||
{
|
||||
"addPeople": {
|
||||
"add": "Inviter",
|
||||
"add": "Invitér",
|
||||
"countryNotSupported": "Vi supporterer ikke dette land endnu.",
|
||||
"countryReminder": "Ringer du til uden for USA? Benyt venligst landekode!",
|
||||
"disabled": "Du kan ikke invitere deltagere.",
|
||||
@@ -15,33 +16,33 @@
|
||||
"searchPeople": "Søg efter personer",
|
||||
"searchPeopleAndNumbers": "Søg efter personer eller tilføj deres telefonnummer",
|
||||
"telephone": "Telefon: {{number}}",
|
||||
"title": "Inviter personer til dette møde"
|
||||
"title": "Invitér personer til dette møde"
|
||||
},
|
||||
"audioDevices": {
|
||||
"bluetooth": "Bluetooth",
|
||||
"headphones": "Høretelefoner",
|
||||
"phone": "Telefon",
|
||||
"speaker": "Højtaler",
|
||||
"none": "Der er ikke nogen lyd enheder tilgængelige"
|
||||
"none": "Der er ikke nogen lydenheder tilgængelige"
|
||||
},
|
||||
"audioOnly": {
|
||||
"audioOnly": "Kun lyd"
|
||||
},
|
||||
"calendarSync": {
|
||||
"addMeetingURL": "Tilføj et mødelink",
|
||||
"confirmAddLink": "Ønsker du at tilføj et Jitsi link til denne aftale?",
|
||||
"confirmAddLink": "Ønsker du at tilføje et Jitsi link til denne aftale?",
|
||||
"error": {
|
||||
"appConfiguration": "Kalender integration er ikke sat korrekt op.",
|
||||
"generic": "Der er sket en fejl. Verificer venligst dine kalenderindstilliinger eller prøv på at genopfriske din kalender.",
|
||||
"notSignedIn": "Der er sket en fejl under log ind for at hente kalenderaftalerne. Kontroller venligst dine kalenderindstilliinger og forsøg at logge ind igen."
|
||||
"appConfiguration": "Kalenderintegration er ikke sat korrekt op.",
|
||||
"generic": "Der er sket en fejl. Verificér venligst dine kalenderindstillinger eller prøv at genopfriske din kalender.",
|
||||
"notSignedIn": "Der er sket en fejl under login for at hente kalenderaftalerne. Kontroller venligst dine kalenderindstillinger og forsøg at logge ind igen."
|
||||
},
|
||||
"join": "Deltag",
|
||||
"joinTooltip": "Deltag i mødet",
|
||||
"nextMeeting": "næste møde",
|
||||
"noEvents": "Der er ikke nogen kommande aftaler i kalenderen.",
|
||||
"ongoingMeeting": "igangværende møde",
|
||||
"permissionButton": "Åben indstillinger",
|
||||
"permissionMessage": "Kalender tilladelsen er nødvendig for at kunne se dine aftaler i appen.",
|
||||
"nextMeeting": "Næste møde",
|
||||
"noEvents": "Der er ikke nogen kommende aftaler i kalenderen.",
|
||||
"ongoingMeeting": "Igangværende møde",
|
||||
"permissionButton": "Åbn indstillinger",
|
||||
"permissionMessage": "Kalendertilladelsen er nødvendig for at kunne se dine aftaler i appen.",
|
||||
"refresh": "Genopfrisk aftaler",
|
||||
"today": "I dag"
|
||||
},
|
||||
@@ -57,7 +58,7 @@
|
||||
},
|
||||
"privateNotice": "Privat besked til {{recipient}}",
|
||||
"title": "Chat",
|
||||
"you": "dig"
|
||||
"you": "Dig"
|
||||
},
|
||||
"chromeExtensionBanner": {
|
||||
"installExtensionText": "Installér plugin for Google Kalender og Office 365 integration",
|
||||
@@ -69,8 +70,8 @@
|
||||
},
|
||||
"connection": {
|
||||
"ATTACHED": "Forbundet",
|
||||
"AUTHENTICATING": "Autoriserer",
|
||||
"AUTHFAIL": "Autorisation lykkedes ikke",
|
||||
"AUTHENTICATING": "Godkender",
|
||||
"AUTHFAIL": "Godkendelse lykkedes ikke",
|
||||
"CONNECTED": "Forbundet",
|
||||
"CONNECTING": "Forbinder",
|
||||
"CONNFAIL": "Forbindelse kunne ikke oprettes",
|
||||
@@ -96,7 +97,7 @@
|
||||
"localport": "Lokal port:",
|
||||
"localport_plural": "Lokale porte:",
|
||||
"more": "Vis mere",
|
||||
"packetloss": "Pakke tab:",
|
||||
"packetloss": "Pakketab:",
|
||||
"quality": {
|
||||
"good": "God",
|
||||
"inactive": "Inaktiv",
|
||||
@@ -133,9 +134,9 @@
|
||||
"defaultNickname": "ex. Jane Pink",
|
||||
"deviceError": {
|
||||
"cameraError": "Kunne ikke forbinde til dit kamera",
|
||||
"cameraPermission": "Kamera tilladelse mangler",
|
||||
"cameraPermission": "Kameratilladelse mangler",
|
||||
"microphoneError": "Kunne ikke forbinde til din mikrofon",
|
||||
"microphonePermission": "Mikrofon tilladelse mangler"
|
||||
"microphonePermission": "Mikrofontilladelse mangler"
|
||||
},
|
||||
"deviceSelection": {
|
||||
"noPermission": "Tilladelse ikke givet",
|
||||
@@ -145,21 +146,21 @@
|
||||
},
|
||||
"dialog": {
|
||||
"accessibilityLabel": {
|
||||
"liveStreaming": "Live Stream"
|
||||
"liveStreaming": "Livestream"
|
||||
},
|
||||
"allow": "Tillad",
|
||||
"alreadySharedVideoMsg": "En anden deltager deler allerede en video. Denne konference tillader kun en delt video af gangen.",
|
||||
"alreadySharedVideoTitle": "Det er kun muligt at dele en video af gangen",
|
||||
"applicationWindow": "Applikations vindue",
|
||||
"alreadySharedVideoTitle": "Det er kun muligt at dele én video ad gangen",
|
||||
"applicationWindow": "Applikationsvindue",
|
||||
"Back": "Tilbage",
|
||||
"cameraConstraintFailedError": "Dit kamera lever ikke op til de nødvendige krav..",
|
||||
"cameraNotFoundError": "Kamera kunne ikke findes.",
|
||||
"cameraNotSendingData": "Vi kan ikke tilgå dit kamera. Kontroller venligst om der er en anden applikation der gør brug af dit kamera, eller vælg en andet kamera og genindlæs siden.",
|
||||
"cameraNotSendingData": "Vi kan ikke tilgå dit kamera. Kontrollér venligst om der er en anden applikation der gør brug af dit kamera, eller vælg et andet kamera og genindlæs siden.",
|
||||
"cameraNotSendingDataTitle": "Kan ikke tilgå kamera",
|
||||
"cameraPermissionDeniedError": "Du har ikke givet tilladelse til at bruge dit kamera. Du kan stadig deltage i mødet men de andre deltagere vil ikke kunne se dig. Gør brug af kamera knappen i adressebaren for at give tilladelse.",
|
||||
"cameraUnknownError": "Kan ikke gør brug af dit kamera, årsag ukendt.",
|
||||
"cameraUnsupportedResolutionError": "Dit kamera supporterer ikke den nødvendige opløsning.",
|
||||
"Cancel": "Afbryd",
|
||||
"cameraPermissionDeniedError": "Du har ikke givet tilladelse til at bruge dit kamera. Du kan stadig deltage i mødet, men de andre deltagere vil ikke kunne se dig. Gør brug af kameraknappen i adressebaren for at give tilladelse.",
|
||||
"cameraUnknownError": "Kan ikke gøre brug af dit kamera, årsag ukendt.",
|
||||
"cameraUnsupportedResolutionError": "Dit kamera understøtter ikke den nødvendige opløsning.",
|
||||
"Cancel": "Annullér",
|
||||
"close": "Luk",
|
||||
"conferenceDisconnectMsg": "Kontroller venligst din netværksforbindelse. Forbinder igen om {{seconds}} sekunder…",
|
||||
"conferenceDisconnectTitle": "Din forbindelse er blevet afbrudt.",
|
||||
@@ -172,31 +173,31 @@
|
||||
"connectErrorWithMsg": "Det var ikke muligt at forbinde til mødet: {{msg}}",
|
||||
"connecting": "Forbinder",
|
||||
"contactSupport": "Kontakt support",
|
||||
"copy": "Kopier invitation",
|
||||
"dismiss": "Afbryd",
|
||||
"copy": "Kopiér invitation",
|
||||
"dismiss": "Afvis",
|
||||
"displayNameRequired": "Navn/alias er påkrævet",
|
||||
"done": "Gem",
|
||||
"enterDisplayName": "Indtast venligst dit navn/alias",
|
||||
"error": "Fejl",
|
||||
"externalInstallationMsg": "Du skal installerer vores skærmdelings plugin.",
|
||||
"externalInstallationMsg": "Du skal installere vores skærmdelingsplugin.",
|
||||
"externalInstallationTitle": "Plugin skal bruges",
|
||||
"goToStore": "Gå til webstore",
|
||||
"gracefulShutdown": "Vores service er pt. under vedligeholdese. Forsøg venligst igen senere.",
|
||||
"gracefulShutdown": "Vores service er pt. under vedligeholdelse. Prøv igen senere.",
|
||||
"IamHost": "Jeg er vært",
|
||||
"incorrectRoomLockPassword": "",
|
||||
"incorrectPassword": "Forkert brugernavn eller adgangskode",
|
||||
"inlineInstallationMsg": "Du skal installerer vores skærmdelings plugin.",
|
||||
"inlineInstallExtension": "Installer nu",
|
||||
"inlineInstallationMsg": "Du skal installere vores skærmdelingsplugin.",
|
||||
"inlineInstallExtension": "Installér nu",
|
||||
"internalError": "Der er opstået en fejl: {{error}}",
|
||||
"internalErrorTitle": "Intern fejl",
|
||||
"kickMessage": "Du er blevet afbrudt fra mødet!",
|
||||
"kickParticipantButton": "Afbryd",
|
||||
"kickParticipantDialog": "Er du sikker på at du vil afbryde den deltager?",
|
||||
"kickParticipantTitle": "Afbryd denne deltager?",
|
||||
"kickTitle": "Afbrudt fra møde",
|
||||
"liveStreaming": "Live Streaming",
|
||||
"liveStreamingDisabledForGuestTooltip": "Gæster kan ikke starte en live stream.",
|
||||
"liveStreamingDisabledTooltip": "Live streaming er slået fra.",
|
||||
"kickParticipantButton": "Smid ud",
|
||||
"kickParticipantDialog": "Er du sikker på at du vil smide denne deltager ud?",
|
||||
"kickParticipantTitle": "Smid denne deltager ud?",
|
||||
"kickTitle": "Smidt ud af mødet",
|
||||
"liveStreaming": "Livestreaming",
|
||||
"liveStreamingDisabledForGuestTooltip": "Gæster kan ikke starte en livestream.",
|
||||
"liveStreamingDisabledTooltip": "Livestreaming er slået fra.",
|
||||
"lockMessage": "Kunne ikke sikre mødet.",
|
||||
"lockRoom": "Tilføj adgangskode",
|
||||
"lockTitle": "Sikring fejlet",
|
||||
@@ -208,79 +209,79 @@
|
||||
"micNotFoundError": "Mikrofon ikke fundet.",
|
||||
"micNotSendingData": "Vi kunne ikke tilgå din mikrofon. Vælg en anden mikrofon under indstillinger eller genindlæs applikationen.",
|
||||
"micNotSendingDataTitle": "Kunne ikke tilgå din mikrofon",
|
||||
"micPermissionDeniedError": "Du har ikke givet tilladelse til at bruge din mikrofon. Du kan stadig deltage i mødet men de andre deltagere kan ikke høre dig. Benyt kamera knappen i adressbaren for at give tilladelse.",
|
||||
"micPermissionDeniedError": "Du har ikke givet tilladelse til at bruge din mikrofon. Du kan stadig deltage i mødet men de andre deltagere kan ikke høre dig. Benyt kameraknappen i adressebaren for at give tilladelse.",
|
||||
"micUnknownError": "Kan ikke tilgå mikrofon af ukendt årsag.",
|
||||
"muteEveryoneElseDialog": "Er du sikker på at du vil slå lyden fra for denne deltager? Du kan ikke tænde igen, men de kan selv tænde til enhver tid.",
|
||||
"muteEveryoneElseDialog": "Er du sikker på at du vil slå lyden fra for alle andre end denne deltager? Du kan ikke tænde lyden igen, men de kan selv tænde til enhver tid.",
|
||||
"muteEveryoneElseTitle": "Slå lyd fra for alle undtagen {{whom}}?",
|
||||
"muteEveryoneDialog": "Er du sikker på at du vil slå lyden fra for denne deltager? Du kan ikke tænde igen, men de kan selv tænde til enhver tid.",
|
||||
"muteEveryoneDialog": "Er du sikker på at du vil slå lyden fra for alle? Du kan ikke tænde lyden igen, men de kan selv tænde til enhver tid.",
|
||||
"muteEveryoneTitle": "Slå lyd fra for alle?",
|
||||
"muteEveryoneSelf": "dig selv",
|
||||
"muteEveryoneStartMuted": "Lyden er slpet fra for alle fra nu af",
|
||||
"muteEveryoneSelf": "Dig selv",
|
||||
"muteEveryoneStartMuted": "Lyden er slået fra for alle fra nu af",
|
||||
"muteParticipantBody": "Du kan ikke tænde for deres mikrofon, men de kan selv tænde for deres mikrofon til enhver tid.",
|
||||
"muteParticipantButton": "Slå lyd fra",
|
||||
"muteParticipantDialog": "Er du sikker på at du vil slå lyden fra for denne deltager? Du kan ikke tænde igen, men de kan selv tænde til enhver tid.",
|
||||
"muteParticipantTitle": "Slå lyd fra for denne deltager?",
|
||||
"Ok": "Ok",
|
||||
"muteParticipantTitle": "Slå lyden fra for denne deltager?",
|
||||
"Ok": "OK",
|
||||
"passwordLabel": "Adgangskode",
|
||||
"passwordNotSupported": "Sætte en adgangskode for mødet er ikke understøttet.",
|
||||
"passwordNotSupported": "Det er ikke understøttet at sætte en adgangskode for mødet",
|
||||
"passwordNotSupportedTitle": "Adgangskode er ikke understøttet",
|
||||
"passwordRequired": "Adgangskode påkrævet",
|
||||
"popupError": "Din browser blockerer for pop-upper fra denne hjemmeside. Slå venligst pop-upper til i din browsers indstillinger og forsøg igen.",
|
||||
"popupErrorTitle": "Pop-up blokkeret",
|
||||
"popupError": "Din browser blokerer for pop op-vinduer fra denne hjemmeside. Slå venligst pop op-vinduer til i din browsers indstillinger og forsøg igen.",
|
||||
"popupErrorTitle": "Pop op-vinduer er blokeret",
|
||||
"recording": "Optager",
|
||||
"recordingDisabledForGuestTooltip": "Gæster kan ikke starte en optagelse.",
|
||||
"recordingDisabledTooltip": "Start optagelse er slået fra.",
|
||||
"recordingDisabledForGuestTooltip": "Gæster kan ikke starte en optagelse",
|
||||
"recordingDisabledTooltip": "Optagefunktionalitet er slået fra",
|
||||
"rejoinNow": "Deltag igen",
|
||||
"remoteControlAllowedMessage": "{{user}} accepterede din fjernstyring anmodning!",
|
||||
"remoteControlDeniedMessage": "{{user}} afviste din fjernstyring anmodning!",
|
||||
"remoteControlAllowedMessage": "{{user}} accepterede din anmodning om fjernstyring!",
|
||||
"remoteControlDeniedMessage": "{{user}} afviste din anmodning om fjernstyring!",
|
||||
"remoteControlErrorMessage": "En fejl er opstået ved anmodning om fjernstyring for {{user}}!",
|
||||
"remoteControlRequestMessage": "Vil du tillade at {{user}} fjerstyrer din computer?",
|
||||
"remoteControlShareScreenWarning": "Bemærk at hvis du vælger \"Tillad\" så vil du tillade fjernstyring!",
|
||||
"remoteControlStopMessage": "Fjernstyrings sessionen er afsluttet!",
|
||||
"remoteControlTitle": "Fjerstyring",
|
||||
"remoteControlStopMessage": "Fjernstyringssessionen er afsluttet!",
|
||||
"remoteControlTitle": "Fjernstyring",
|
||||
"Remove": "Fjern",
|
||||
"removePassword": "Fjern adgangskode",
|
||||
"removeSharedVideoMsg": "Er du sikker på at du vil fjerne di delte video?",
|
||||
"removeSharedVideoMsg": "Er du sikker på at du vil fjerne din delte video?",
|
||||
"removeSharedVideoTitle": "Fjern delt video",
|
||||
"reservationError": "Reservation - system fejl",
|
||||
"reservationErrorMsg": "Fejlkode: {{code}}, fejl: {{msg}}",
|
||||
"retry": "Forsøg igen",
|
||||
"screenSharingFailedToInstall": "Skærmdelings-plugin kunne ikke installeres.",
|
||||
"screenSharingFailedToInstallTitle": "Skærmdelings-plugin kunne ikke installeres.",
|
||||
"screenSharingFirefoxPermissionDeniedError": "Noget gik galt under skærmdeling. Kontroller venligst at du har givet tilladelse til skærmdeling. ",
|
||||
"screenSharingFirefoxPermissionDeniedError": "Noget gik galt under skærmdeling. Kontrollér venligst at du har givet tilladelse til skærmdeling. ",
|
||||
"screenSharingFirefoxPermissionDeniedTitle": "Skærmdeling kunne ikke startes!",
|
||||
"screenSharingPermissionDeniedError": "Noget gik galt under skærmdeling. Kontroller venligst at du har givet tilladelse til skærmdeling. Genindlæs siden og forsøg igen.",
|
||||
"sendPrivateMessage": "Du har modtage en privat besked. Ønsker du at svare private eller øsnker du at svare til gruppen?",
|
||||
"screenSharingPermissionDeniedError": "Noget gik galt under skærmdeling. Kontrollér venligst at du har givet tilladelse til skærmdeling. Genindlæs siden og forsøg igen.",
|
||||
"sendPrivateMessage": "Du har modtaget en privat besked. Ønsker du at svare privat eller til gruppen?",
|
||||
"sendPrivateMessageCancel": "Send til gruppen",
|
||||
"sendPrivateMessageOk": "Send privat",
|
||||
"sendPrivateMessageTitle": "Send privat?",
|
||||
"serviceUnavailable": "Service er ikke tilgængelig",
|
||||
"sessTerminated": "Møde afsluttet",
|
||||
"Share": "Del",
|
||||
"shareVideoLinkError": "Angiv venligst et validt Youtube link.",
|
||||
"shareVideoLinkError": "Angiv venligst et gyldigt YouTube link.",
|
||||
"shareVideoTitle": "Del en video",
|
||||
"shareYourScreen": "Del din skærm",
|
||||
"shareYourScreenDisabled": "Skærmdeling er ikke slået til.",
|
||||
"shareYourScreenDisabledForGuest": "Gæster kan ikke dele deres skærm.",
|
||||
"startLiveStreaming": "Start live stream",
|
||||
"startLiveStreaming": "Start livestream",
|
||||
"startRecording": "Start optagelse",
|
||||
"startRemoteControlErrorMessage": "Der er sket en fejl under opstart af fjern kontrol sessionen!",
|
||||
"stopLiveStreaming": "Stop live stream",
|
||||
"startRemoteControlErrorMessage": "Der er sket en fejl under opstart af fjernstyringssessionen!",
|
||||
"stopLiveStreaming": "Stop livestream",
|
||||
"stopRecording": "Stop optagelse",
|
||||
"stopRecordingWarning": "Er du sikker på at du ønsker at stoppe optagelesen?",
|
||||
"stopStreamingWarning": "Er du sikker på at du ønsker at stoppe live streaming?",
|
||||
"streamKey": "Live stream nøgle",
|
||||
"stopRecordingWarning": "Er du sikker på at du ønsker at stoppe optagelsen?",
|
||||
"stopStreamingWarning": "Er du sikker på at du ønsker at stoppe livestreaming?",
|
||||
"streamKey": "Livestream-nøgle",
|
||||
"Submit": "Gem",
|
||||
"thankYou": "Tak for at du har benyttet {{appName}}!",
|
||||
"token": "token",
|
||||
"tokenAuthFailed": "Beklager, du kan ikke deltage i dette møde.",
|
||||
"tokenAuthFailedTitle": "Log ind fejlede",
|
||||
"transcribing": "Transcribering",
|
||||
"unlockRoom": "Fjern møde adgangskode",
|
||||
"userPassword": "bruger adgangskode",
|
||||
"WaitForHostMsg": "Mødet <b>{{room}}</b> er ikke startet endnu. Hvis du er hosten log venligst ind, ellers vent på at hosten kommer.",
|
||||
"WaitForHostMsgWOk": "Mødet <b>{{room}}</b> er ikke startet endnu. Hvis du er hosten tryk venligst på OK for at logge ind, ellers vent på at hosten kommer.",
|
||||
"WaitingForHost": "Venter på host …",
|
||||
"tokenAuthFailedTitle": "Login fejlede",
|
||||
"transcribing": "Transskribering",
|
||||
"unlockRoom": "Fjern mødeadgangskode",
|
||||
"userPassword": "Brugeradgangskode",
|
||||
"WaitForHostMsg": "Mødet <b>{{room}}</b> er ikke startet endnu. Hvis du er værten, log venligst ind. Ellers vent på at værten kommer",
|
||||
"WaitForHostMsgWOk": "Mødet <b>{{room}}</b> er ikke startet endnu. Hvis du er værten, tryk venligst på OK for at logge ind. Ellers vent på at værten kommer.",
|
||||
"WaitingForHost": "Venter på vært …",
|
||||
"Yes": "Ja",
|
||||
"yourEntireScreen": "Hele din skærm"
|
||||
},
|
||||
@@ -295,37 +296,37 @@
|
||||
"bad": "Dårlig",
|
||||
"detailsLabel": "Uddyb.",
|
||||
"good": "God",
|
||||
"rateExperience": "Vurder din oplevelse",
|
||||
"rateExperience": "Vurdér din oplevelse",
|
||||
"veryBad": "Meget dårlig",
|
||||
"veryGood": "Meget god"
|
||||
},
|
||||
"incomingCall": {
|
||||
"answer": "Besvar",
|
||||
"audioCallTitle": "Indkommende opkald",
|
||||
"audioCallTitle": "Indgående opkald",
|
||||
"decline": "Afvis",
|
||||
"productLabel": "fra Appinux skærmbesøg",
|
||||
"videoCallTitle": "Indkommende videoopkald"
|
||||
"productLabel": "Fra Appinux skærmbesøg",
|
||||
"videoCallTitle": "Indgående videoopkald"
|
||||
},
|
||||
"info": {
|
||||
"accessibilityLabel": "Vis info",
|
||||
"addPassword": "Tilføj adgangskode",
|
||||
"cancelPassword": "Annulé adgangskode",
|
||||
"cancelPassword": "Annullér adgangskode",
|
||||
"conferenceURL": "Link:",
|
||||
"country": "Land",
|
||||
"dialANumber": "For at deltage i mødet ring til et af følgende telefonnumre og indtast pinkode.",
|
||||
"dialInConferenceID": "Pinkode:",
|
||||
"dialInNotSupported": "Deltagelse vis telfonen er pt. ikke understøttet.",
|
||||
"dialInNotSupported": "Deltagelse via telefonopkald er pt. ikke understøttet.",
|
||||
"dialInNumber": "Ring ind:",
|
||||
"dialInSummaryError": "Der er opstået en fejl under hentning af ring ind information. Forsøg venligst igen senere.",
|
||||
"dialInSummaryError": "Der er opstået en fejl under indhenting af telefonopkaldsdetaljer. Forsøg venligst igen senere.",
|
||||
"dialInTollFree": "Gratis",
|
||||
"genericError": "Der er opstået en fejl",
|
||||
"inviteLiveStream": "For at se en live stream i dette møde klik på dette link: {{url}}",
|
||||
"inviteLiveStream": "For at se en livestream i dette møde klik på dette link: {{url}}",
|
||||
"invitePhone": "Ring-ind: {{number}},,{{conferenceID}}#\n",
|
||||
"invitePhoneAlternatives": "",
|
||||
"inviteURLFirstPartGeneral": "Der er blevet inviteret til at deltage i et møde.",
|
||||
"inviteURLFirstPartPersonal": "{{name}} inviterer dig til at deltage i et møde.\n",
|
||||
"inviteURLSecondPart": "\nDeltag i mødet:\n{{url}}\n",
|
||||
"liveStreamURL": "Live stream:",
|
||||
"liveStreamURL": "Livestream:",
|
||||
"moreNumbers": "Flere telefonnumre",
|
||||
"noNumbers": "Ring-ind nummer findes ikke.",
|
||||
"noPassword": "Ingen",
|
||||
@@ -333,8 +334,8 @@
|
||||
"numbers": "Ring-ind numre",
|
||||
"password": "Adgangskode:",
|
||||
"title": "Del",
|
||||
"tooltip": "Del link og rind-ind information for dette møde",
|
||||
"label": "Møde information"
|
||||
"tooltip": "Del link og ring-ind information for dette møde",
|
||||
"label": "Mødeinformation"
|
||||
},
|
||||
"inviteDialog": {
|
||||
"alertText": "Kunne ikke invitere nogle af deltagerne.",
|
||||
@@ -348,54 +349,54 @@
|
||||
"msg": "Der er opstået en fejl.",
|
||||
"retry": "Forsøg igen",
|
||||
"support": "Support",
|
||||
"supportMsg": "Hvis denne fejl bliver ved, kontakt"
|
||||
"supportMsg": "Hvis denne fejl bliver ved, kontakt da support"
|
||||
},
|
||||
"keyboardShortcuts": {
|
||||
"focusLocal": "Fokusér på din video",
|
||||
"focusRemote": "Fokusér på en anden persons video",
|
||||
"focusLocal": "Sæt fokus din video",
|
||||
"focusRemote": "Sæt fokus på en anden persons billede",
|
||||
"fullScreen": "Vis eller annullér fuld skærm",
|
||||
"keyboardShortcuts": "Tastaturgenveje",
|
||||
"localRecording": "Vis eller skjul optage kontrolknapper",
|
||||
"mute": "Slå lyd fra eller til for din mikrofon",
|
||||
"localRecording": "Vis eller skjul optagelseskontrolknapper",
|
||||
"mute": "Slå din mikrofon til eller fra",
|
||||
"pushToTalk": "Tryk for at tale",
|
||||
"raiseHand": "Lyft eller sænk din hånd",
|
||||
"showSpeakerStats": "Vis højtaler statistik",
|
||||
"toggleChat": "Åben eller luk chat",
|
||||
"toggleFilmstrip": "Vis eller skjul video små billeder",
|
||||
"toggleScreensharing": "Skift mellem video og skærmdeling",
|
||||
"raiseHand": "Løft eller sænk din hånd",
|
||||
"showSpeakerStats": "Vis højtalerstatistik",
|
||||
"toggleChat": "Åbn eller luk chat",
|
||||
"toggleFilmstrip": "Vis eller skjul små videobilleder",
|
||||
"toggleScreensharing": "Skift mellem video- og skærmdeling",
|
||||
"toggleShortcuts": "Vis eller skjul tastaturgenveje",
|
||||
"videoMute": "Tænd eller sluk dit kamera",
|
||||
"videoQuality": "Indstil opkaldskvalitet"
|
||||
"videoQuality": "Indstil videokvalitet"
|
||||
},
|
||||
"liveStreaming": {
|
||||
"busy": "Der arbejdes på at frigive streamingresourcer, forsøg venligst igen om et par minutter.",
|
||||
"busyTitle": "Alle streamers er optaget",
|
||||
"busy": "Der arbejdes på at frigive streamingressourcer, forsøg venligst igen om et par minutter.",
|
||||
"busyTitle": "Alle streams er optaget",
|
||||
"changeSignIn": "Skift konto.",
|
||||
"choose": "Vælg en live stream",
|
||||
"chooseCTA": "Vælg en stream mulighed. Du er pt. logget ind som {{email}}.",
|
||||
"enterStreamKey": "Indtast din Youtube live stream her.",
|
||||
"error": "Fejl under live streaming. Forsøg venligst igen.",
|
||||
"choose": "Vælg en livestream",
|
||||
"chooseCTA": "Vælg en stream-mulighed. Du er pt. logget ind som {{email}}.",
|
||||
"enterStreamKey": "Indtast din livestream-nøgle til Youtube her.",
|
||||
"error": "Fejl under livestreaming. Forsøg venligst igen.",
|
||||
"errorAPI": "En fejl opstod ved forsøg på at tilgå din YouTube-broadcast. Forsøg venligst at logge ind igen.",
|
||||
"errorLiveStreamNotEnabled": "Live Streaming er ikke slpet til for {{email}}. Slå venligst live streaming til eller log ind på en konto der har live streaming slået til.",
|
||||
"expandedOff": "Live stream er stoppet",
|
||||
"errorLiveStreamNotEnabled": "Livestreaming er ikke slået til for {{email}}. Slå venligst livestreaming til eller log ind på en konto der har livestreaming slået til.",
|
||||
"expandedOff": "Livestream er stoppet",
|
||||
"expandedOn": "Mødet bliver pt. streamet til Youtube.",
|
||||
"expandedPending": "Live streaming starter…",
|
||||
"failedToStart": "Kunne ikke starte live streaming",
|
||||
"getStreamKeyManually": "Vi kunne ikke hente nogle live streams. Forsøg at hente din live stream nøgle fra Youtube.",
|
||||
"invalidStreamKey": "Live stream nøgle er muligvis forkert.",
|
||||
"off": "Live Streaming er stoppet",
|
||||
"offBy": "{{name}} stoppede live streaming",
|
||||
"on": "Live Streaming",
|
||||
"onBy": "{{name}} startede live streaming",
|
||||
"pending": "Starter Live Stream…",
|
||||
"serviceName": "Live Streaming service",
|
||||
"expandedPending": "Livestreaming starter…",
|
||||
"failedToStart": "Kunne ikke starte livestreaming",
|
||||
"getStreamKeyManually": "Vi kunne ikke hente nogle livestreams. Forsøg at hente din livestream-nøgle fra YouTube.",
|
||||
"invalidStreamKey": "Livestream-nøgle er muligvis forkert.",
|
||||
"off": "Livestreaming er stoppet",
|
||||
"offBy": "{{name}} stoppede livestreaming",
|
||||
"on": "Livestreaming",
|
||||
"onBy": "{{name}} startede livestreaming",
|
||||
"pending": "Starter livestream…",
|
||||
"serviceName": "Livestreaming-service",
|
||||
"signedInAs": "Du er pt. logget ind som:",
|
||||
"signIn": "Log ind med Google",
|
||||
"signInCTA": "Log ind eller indtast din live stream nøgle fra YouTube.",
|
||||
"signInCTA": "Log ind eller indtast din livestream-nøgle fra YouTube.",
|
||||
"signOut": "Log ud",
|
||||
"start": "Start en live stream",
|
||||
"start": "Start en livestream",
|
||||
"streamIdHelp": "Hvad er dette?",
|
||||
"unavailableTitle": "Live Streaming er ikke tilgængelig"
|
||||
"unavailableTitle": "Livestreaming er ikke tilgængelig"
|
||||
},
|
||||
"localRecording": {
|
||||
"clientState": {
|
||||
@@ -403,24 +404,24 @@
|
||||
"on": "Slået til",
|
||||
"unknown": "Ukendt"
|
||||
},
|
||||
"dialogTitle": "Optage kontrolknapper",
|
||||
"dialogTitle": "Optagelseskontrolknapper",
|
||||
"duration": "Varighed",
|
||||
"durationNA": "Ikke tilgængelig",
|
||||
"encoding": "Encoding",
|
||||
"label": "Optager",
|
||||
"labelToolTip": "Optager er slået til",
|
||||
"localRecording": "Local Recording",
|
||||
"labelToolTip": "Optagelse er slået til",
|
||||
"localRecording": "Lokal optagelse",
|
||||
"me": "Mig",
|
||||
"messages": {
|
||||
"engaged": "Optager er slået til.",
|
||||
"finished": "Optagelses session {{token}} afsluttet. Send venligst den optagede fil til moderator.",
|
||||
"finishedModerator": "Optagelses session {{token}} afsluttet. Optagelsen er gemt. Bed deltageren om at sende deres optagelser.",
|
||||
"engaged": "Optagelse er slået til.",
|
||||
"finished": "Optagelse {{token}} afsluttet. Send venligst den optagne fil til moderator.",
|
||||
"finishedModerator": "Optagelse {{token}} afsluttet. Optagelsen er gemt. Bed deltageren om at sende deres optagelser.",
|
||||
"notModerator": "Du er ikke moderator. Du kan ikke starte eller stoppe optagelser."
|
||||
},
|
||||
"moderator": "Moderator",
|
||||
"no": "Nej",
|
||||
"participant": "Deltager",
|
||||
"participantStats": "Deltager statistik",
|
||||
"participantStats": "Deltagerstatistik",
|
||||
"sessionToken": "Sessionsnøgle",
|
||||
"start": "Start optagelse",
|
||||
"stop": "Stop optagelse",
|
||||
@@ -433,19 +434,19 @@
|
||||
"connectedOneMember": "{{name}} deltager nu i mødet",
|
||||
"connectedThreePlusMembers": "{{name}} og {{count}} andre deltager nu i mødet",
|
||||
"connectedTwoMembers": "{{first}} og {{second}} deltager nu i mødet",
|
||||
"disconnected": "afbrudt",
|
||||
"focus": "Konference fokus",
|
||||
"disconnected": "Afbrudt",
|
||||
"focus": "Konferencefokus",
|
||||
"focusFail": "{{component}} ikke tilgængelig - forsøg igen om {{ms}} sekunder",
|
||||
"grantedTo": "Moderator rettigheder er givet til {{to}}!",
|
||||
"grantedTo": "Moderatorrettigheder er givet til {{to}}!",
|
||||
"invitedOneMember": "{{name}} er blevet inviteret",
|
||||
"invitedThreePlusMembers": "{{name}} og {{count}} andre er blevet inviteret",
|
||||
"invitedTwoMembers": "{{first}} og {{second}} er blevet inviteret",
|
||||
"kickParticipant": "{{kicked}} blev afbrudt fra mødet af {{kicker}}",
|
||||
"me": "Mig",
|
||||
"moderator": "Moderator rettigheder givet!",
|
||||
"moderator": "Moderatorrettigheder givet",
|
||||
"muted": "Du har startet samtalen med lyden slået fra.",
|
||||
"mutedTitle": "Din lyd er slået fra!",
|
||||
"mutedRemotelyTitle": "Din lyd er slået fra af {{participantDisplayName}}!",
|
||||
"mutedTitle": "Din lyd er slået fra",
|
||||
"mutedRemotelyTitle": "Din lyd er slået fra af {{participantDisplayName}}",
|
||||
"mutedRemotelyDescription": "",
|
||||
"passwordRemovedRemotely": "",
|
||||
"passwordSetRemotely": "",
|
||||
@@ -453,16 +454,16 @@
|
||||
"somebody": "Nogen",
|
||||
"startSilentTitle": "",
|
||||
"startSilentDescription": "",
|
||||
"suboptimalExperienceDescription": "Hmmm... vi er bange for at din oplevelse med {{appName}} ikke vil være optimal. Vi arbejder på at forbedre dette, men indtil da så forsøg venligst at bruge en af følgende <a href='{{recommendedBrowserPageLink}}' target='_blank'>fuldt understøttede internet browsere</a>.",
|
||||
"suboptimalExperienceTitle": "Browser Advarsel",
|
||||
"suboptimalExperienceDescription": "Hmmm... vi er bange for at din oplevelse med {{appName}} ikke vil være optimal. Vi arbejder på at forbedre dette, men indtil da forsøg venligst at bruge en af følgende <a href='{{recommendedBrowserPageLink}}' target='_blank'>fuldt understøttede internet browsere</a>.",
|
||||
"suboptimalExperienceTitle": "Browser-advarsel",
|
||||
"unmute": "",
|
||||
"newDeviceCameraTitle": "Nyt kamerat fundet",
|
||||
"newDeviceAudioTitle": "Ny lyd enhed fundet",
|
||||
"newDeviceCameraTitle": "Nyt kamera fundet",
|
||||
"newDeviceAudioTitle": "Ny lydenhed fundet",
|
||||
"newDeviceAction": "Benyt"
|
||||
},
|
||||
"passwordSetRemotely": "sat af et andet medlem",
|
||||
"passwordSetRemotely": "Sat af et andet medlem",
|
||||
"passwordDigitsOnly": "Op til {{number}} tal",
|
||||
"poweredby": "powered by",
|
||||
"poweredby": "Powered by",
|
||||
"presenceStatus": {
|
||||
"busy": "Optaget",
|
||||
"calling": "Ringer op…",
|
||||
@@ -479,8 +480,8 @@
|
||||
},
|
||||
"profile": {
|
||||
"setDisplayNameLabel": "Vælg navn/alias",
|
||||
"setEmailInput": "Indtast email",
|
||||
"setEmailLabel": "Indtast Gravatar-e-post",
|
||||
"setEmailInput": "Indtast email-adresse",
|
||||
"setEmailLabel": "Indtast Gravatar-email-adresse",
|
||||
"title": "Profil"
|
||||
},
|
||||
"raisedHand": "Ønsker at tale",
|
||||
@@ -488,13 +489,13 @@
|
||||
"authDropboxText": "Upload til Dropbox",
|
||||
"availableSpace": "Tilgængelig plads: {{spaceLeft}} MB (plads til ca. {{duration}} minutters optagelse)",
|
||||
"beta": "BETA",
|
||||
"busy": "Der arbejdes på at frigive optagelsesresourcer. Prøv venligst igen om nogle få minutter.",
|
||||
"busyTitle": "Alle optagere er pt optaget",
|
||||
"busy": "Der arbejdes på at frigive optagelsesressourcer. Prøv venligst igen om nogle få minutter.",
|
||||
"busyTitle": "Alle optagere er pt. optaget",
|
||||
"error": "Fejl under optagelse, forsøg venligst igen om lidt.",
|
||||
"expandedOff": "Optagelse er stoppet½",
|
||||
"expandedOn": "Mødet bliver pt. optaget.",
|
||||
"expandedOff": "Optagelse er stoppet",
|
||||
"expandedOn": "Mødet bliver pt. optaget",
|
||||
"expandedPending": "Optagelse starter…",
|
||||
"failedToStart": "Kunne ikke start optagelse",
|
||||
"failedToStart": "Kunne ikke starte optagelse",
|
||||
"fileSharingdescription": "Del optagelse med deltagere",
|
||||
"live": "LIVE",
|
||||
"loggedIn": "Logget ind som {{userName}}",
|
||||
@@ -504,22 +505,22 @@
|
||||
"onBy": "{{name}} startede optagelsen",
|
||||
"pending": "Gør klar til at optage mødet…",
|
||||
"rec": "REC",
|
||||
"serviceDescription": "Din optagelse bliver gemt af optager servicen",
|
||||
"serviceName": "Optager service",
|
||||
"serviceDescription": "Din optagelse bliver gemt af optagelsesservicen",
|
||||
"serviceName": "Optagelsesservice",
|
||||
"signIn": "Log ind",
|
||||
"signOut": "Log ud",
|
||||
"unavailable": "Optager service {{serviceName}} er ikke tilgængelig. Der arbejdes på at løse problemet, forsøg igen senere.",
|
||||
"unavailable": "Optagelsesservice {{serviceName}} er ikke tilgængelig. Der arbejdes på at løse problemet, forsøg igen senere.",
|
||||
"unavailableTitle": "Optagelse ikke tilgængelig"
|
||||
},
|
||||
"sectionList": {
|
||||
"pullToRefresh": "Træk ned for update"
|
||||
"pullToRefresh": "Træk ned for at opdatere"
|
||||
},
|
||||
"settings": {
|
||||
"calendar": {
|
||||
"about": "{{appName}}s kalenderintegration benyttes til at sikkert tilgå din kalender så den kan læse fremtidige aftaler.",
|
||||
"about": "{{appName}}s kalenderintegration benyttes til sikkert at tilgå din kalender så den kan læse fremtidige aftaler.",
|
||||
"disconnect": "Afbryd",
|
||||
"microsoftSignIn": "Log ind med Microsoft",
|
||||
"signedIn": "Henter kalenderaftaler for {{email}}. Tryk på Afbryd knappen for at afbryde forbindelsen.",
|
||||
"signedIn": "Henter kalenderaftaler for {{email}}. Tryk på Afbryd for at afbryde forbindelsen.",
|
||||
"title": "Kalender"
|
||||
},
|
||||
"devices": "Enheder",
|
||||
@@ -530,28 +531,28 @@
|
||||
"more": "Mere",
|
||||
"name": "Navn",
|
||||
"noDevice": "Ingen",
|
||||
"selectAudioOutput": "Lyd udgang",
|
||||
"selectAudioOutput": "Lydudgang",
|
||||
"selectCamera": "Kamera",
|
||||
"selectMic": "Mikrofon",
|
||||
"startAudioMuted": "Alle starter med lyden slået fra",
|
||||
"startVideoMuted": "Alle starter skjult",
|
||||
"startVideoMuted": "Alle starter med video slået fra",
|
||||
"title": "Indstillinger"
|
||||
},
|
||||
"settingsView": {
|
||||
"advanced": "Avanceret",
|
||||
"alertOk": "OK",
|
||||
"alertTitle": "Advarsel",
|
||||
"alertURLText": "Den indtastede server url er ikke korrekt",
|
||||
"alertURLText": "Den indtastede server-URL er ikke korrekt",
|
||||
"buildInfoSection": "Build Information",
|
||||
"conferenceSection": "Konference",
|
||||
"disableCallIntegration": "Slå indbygget opkalds integration fra",
|
||||
"disableCallIntegration": "Slå indbygget opkaldsintegration fra",
|
||||
"disableP2P": "Slå Peer-To-Peer fra",
|
||||
"displayName": "Navn/alias",
|
||||
"email": "Email",
|
||||
"header": "Indstillinger",
|
||||
"profileSection": "Profil",
|
||||
"serverURL": "Server URL",
|
||||
"showAdvanced": "Show advanced settings",
|
||||
"serverURL": "Server-URL",
|
||||
"showAdvanced": "Vis avancerede indstillinger",
|
||||
"startWithAudioMuted": "Start med lyden slået fra",
|
||||
"startWithVideoMuted": "Start med video slået fra",
|
||||
"version": "Version"
|
||||
@@ -560,18 +561,18 @@
|
||||
"dialInfoText": "\n\n=====\n\nØnsker du at ringe ind fra din telefon?\n\n{{defaultDialInNumber}}Klik på dette link for at se telefonnummeret for dette møde\n{{dialInfoPageUrl}}",
|
||||
"mainText": "Klik på det følgende link for at deltage i mødet:\n{{roomUrl}}"
|
||||
},
|
||||
"speaker": "Højtaler",
|
||||
"speaker": "Deltager",
|
||||
"speakerStats": {
|
||||
"hours": "{{count}}t",
|
||||
"minutes": "{{count}}m",
|
||||
"name": "Navn",
|
||||
"seconds": "{{count}}s",
|
||||
"speakerStats": "Højtaler info",
|
||||
"speakerTime": "Højtaler tid"
|
||||
"speakerStats": "Deltagerstatistik",
|
||||
"speakerTime": "Taletid"
|
||||
},
|
||||
"startupoverlay": {
|
||||
"policyText": " ",
|
||||
"title": "{{app}} har brug for adgang til din mikrofon og kamera."
|
||||
"title": "{{app}} har brug for tilladelse til din mikrofon og dit kamera."
|
||||
},
|
||||
"suspendedoverlay": {
|
||||
"rejoinKeyTitle": "Forbind igen",
|
||||
@@ -582,157 +583,157 @@
|
||||
"accessibilityLabel": {
|
||||
"audioOnly": "Tænd/sluk lyd",
|
||||
"audioRoute": "Vælg lydenhed",
|
||||
"callQuality": "Indstil møde kvalitet",
|
||||
"cc": "Slå undertekst fra/til",
|
||||
"callQuality": "Indstil opkaldskvalitet",
|
||||
"cc": "Slå undertekster fra/til",
|
||||
"chat": "Slå chat fra/til",
|
||||
"document": "Slå delte dokumenter fra/til",
|
||||
"download": "Hent vores apps",
|
||||
"feedback": "Giv tilbagemelding",
|
||||
"feedback": "Giv feedback",
|
||||
"fullScreen": "Slå fuld skærm fra/til",
|
||||
"hangup": "Forlad mødet",
|
||||
"help": "Hjælp",
|
||||
"invite": "Inviter deltagere",
|
||||
"kick": "Afbryd deltager",
|
||||
"localRecording": "Slå optagekontroller fra/til",
|
||||
"invite": "Invitér deltagere",
|
||||
"kick": "Smid deltager ud",
|
||||
"localRecording": "Slå optagelseskontrol fra/til",
|
||||
"lockRoom": "Slå mødeadgangskode fra/til",
|
||||
"moreActions": "Slå \"Flere indstillinger\" menu fra/til",
|
||||
"moreActionsMenu": "Flere indtstillinger",
|
||||
"moreOptions": "Show more options",
|
||||
"moreActionsMenu": "Flere indstillinger",
|
||||
"moreOptions": "Vis flere indstillinger",
|
||||
"mute": "Slå lyd til/fra",
|
||||
"muteEveryone": "Mute everyone",
|
||||
"muteEveryone": "Slå lyd fra for alle",
|
||||
"pip": "Slå Billede-i-billede fra/til",
|
||||
"privateMessage": "Send privat besked",
|
||||
"privateMessage": "Send privatbesked",
|
||||
"profile": "Redigér profil",
|
||||
"raiseHand": "Slå løft hånden fra/til",
|
||||
"recording": "Slå optagelse til/fra",
|
||||
"remoteMute": "Slå lyd fra for deltager",
|
||||
"Settings": "Slå indstillinger fra/til",
|
||||
"sharedvideo": "Slå YouTube video deling fra/til",
|
||||
"sharedvideo": "Slå YouTube-videodeling fra/til",
|
||||
"shareRoom": "Invitér nogen",
|
||||
"shareYourScreen": "Slå skærmdeling fra/til",
|
||||
"shortcuts": "Slå genveje fra/til",
|
||||
"show": "Vis",
|
||||
"speakerStats": "Slå højtaler info fra/til",
|
||||
"tileView": "Slå tileview fra/til",
|
||||
"speakerStats": "Slå højtalerinfo fra/til",
|
||||
"tileView": "Slå gittervisning fra/til",
|
||||
"toggleCamera": "Slå kamera fra/til",
|
||||
"videomute": "Slå video fra/til",
|
||||
"videoblur": "Slå baggrundsløring fra/til"
|
||||
"videoblur": "Slå baggrundssløring fra/til"
|
||||
},
|
||||
"addPeople": "Tilføj deltager til mødet",
|
||||
"audioOnlyOff": "Slå kun-lyd møder fra",
|
||||
"audioOnlyOn": "Slå kun-lyd møder til",
|
||||
"audioRoute": "Vælg lydenhed",
|
||||
"authenticate": "Log ind",
|
||||
"callQuality": "Indstil møde kvalitet",
|
||||
"chat": "Åben / Luk chat",
|
||||
"callQuality": "Indstil opkaldskvalitet",
|
||||
"chat": "Åbn/luk chat",
|
||||
"closeChat": "Luk chat",
|
||||
"documentClose": "Luk delt dokument",
|
||||
"documentOpen": "Åben delt dokument",
|
||||
"download": "Hent vores apps",
|
||||
"enterFullScreen": "Vis fuld skærm",
|
||||
"enterTileView": "Tileview",
|
||||
"enterTileView": "Gittervisning",
|
||||
"exitFullScreen": "Luk fuld skærm",
|
||||
"exitTileView": "Luk Tileview",
|
||||
"feedback": "Giv tilbagemeldign",
|
||||
"exitTileView": "Luk gittervisning",
|
||||
"feedback": "Giv feedback",
|
||||
"hangup": "Forlad",
|
||||
"help": "Hjælp",
|
||||
"invite": "Invitér deltagere",
|
||||
"login": "Log ind",
|
||||
"logout": "Log ud",
|
||||
"lowerYourHand": "Ta hånden ned",
|
||||
"lowerYourHand": "Tag hånden ned",
|
||||
"moreActions": "Flere handlinger",
|
||||
"moreOptions": "Flere indstillinger",
|
||||
"mute": "Slå lyd fra / Slå lyd til",
|
||||
"mute": "Slå lyd fra/til",
|
||||
"muteEveryone": "Slå lyd fra for alle",
|
||||
"noAudioSignalTitle": "Der kommer ingen input fra din mikrofon!",
|
||||
"noAudioSignalTitle": "Der kommer ingen input fra din mikrofon",
|
||||
"noAudioSignalDesc": "Hvis du ikke med vilje har slået den fra under systemindstillinger eller hardware, kan du overveje at skifte enhed.",
|
||||
"noAudioSignalDescSuggestion": "Hvis du ikke med vilje har slået den fra under systemindstillinger eller hardware, skal du overveje at skifte til den foreslåede enhed.",
|
||||
"noAudioSignalDialInDesc": "Du kan også ringe op ved hjælp af:",
|
||||
"noAudioSignalDialInLinkDesc": "Opkaldsnumre",
|
||||
"noisyAudioInputTitle": "Det lyder som om din mikrofon laver støj!",
|
||||
"noisyAudioInputTitle": "Det lyder som om din mikrofon laver støj",
|
||||
"noisyAudioInputDesc": "Det lyder som om din mikrofon laver støj. Overvej venligst at slå lyden fra eller skifte enhed.",
|
||||
"openChat": "Åben chat",
|
||||
"openChat": "Åbn chat",
|
||||
"pip": "Benyt Billede-i-billede",
|
||||
"privateMessage": "Send private message",
|
||||
"privateMessage": "Send privat besked",
|
||||
"profile": "Rediger profil",
|
||||
"raiseHand": "Ræk hånden op / Tag hånden ned",
|
||||
"raiseYourHand": "Ræk hånden op",
|
||||
"Settings": "Indstillinger",
|
||||
"sharedvideo": "Del en Youtube video",
|
||||
"sharedvideo": "Del en YouTube-video",
|
||||
"shareRoom": "Invitér deltagere",
|
||||
"shortcuts": "Vis genveje",
|
||||
"speakerStats": "Højtaler info",
|
||||
"speakerStats": "Deltagerstatistik",
|
||||
"startScreenSharing": "Start skærmdeling",
|
||||
"startSubtitles": "Vis undertekster",
|
||||
"stopScreenSharing": "Stop skærmdeling",
|
||||
"stopSubtitles": "Skjul undertekster",
|
||||
"stopSharedVideo": "Stop YouTube video",
|
||||
"stopSharedVideo": "Stop YouTube-video",
|
||||
"talkWhileMutedPopup": "Forsøger du at sige noget? Din lyd er slået fra.",
|
||||
"tileViewToggle": "Slå tileview fra/til",
|
||||
"tileViewToggle": "Slå gittervisning fra/til",
|
||||
"toggleCamera": "Slå kamera fra/til",
|
||||
"videomute": "Start / Stop kamera",
|
||||
"videomute": "Start/stop kamera",
|
||||
"startvideoblur": "Slå baggrundssløring til",
|
||||
"stopvideoblur": "Slå baggrundssløring fra"
|
||||
},
|
||||
"transcribing": {
|
||||
"ccButtonTooltip": "Start / Stop undertekster",
|
||||
"error": "Transkription mislykkedes. Prøv venligst igen.",
|
||||
"expandLabel": "Transkription er i øjeblikket tændt",
|
||||
"failedToStart": "Transkription kunne ikke starte",
|
||||
"ccButtonTooltip": "Start/stop undertekster",
|
||||
"error": "Transskription mislykkedes. Prøv venligst igen.",
|
||||
"expandLabel": "Transskription er slået til",
|
||||
"failedToStart": "Transskription kunne ikke starte",
|
||||
"labelToolTip": "Mødet transkriberes",
|
||||
"off": "Transkription stoppet",
|
||||
"pending": "Forbereder sig til at transkribere mødet …",
|
||||
"pending": "Forbereder sig til at transskribere mødet…",
|
||||
"start": "Begynd at vise undertekster",
|
||||
"stop": "Stop med at få vist undertekster",
|
||||
"stop": "Stop med at vise undertekster",
|
||||
"tr": "TR"
|
||||
},
|
||||
"userMedia": {
|
||||
"androidGrantPermissions": "Vælg <b><i> Tillad</i> </b>, når din browser beder om tilladelser.",
|
||||
"chromeGrantPermissions": "Vælg <b><i> Tillad</i> </b>, når din browser beder om tilladelser.",
|
||||
"edgeGrantPermissions": "Vælg <b><i>Ja</i> </b>, når din browser beder om tilladelser.",
|
||||
"electronGrantPermissions": "Giv tilladelse til at bruge dit kamera og din mikrofon",
|
||||
"firefoxGrantPermissions": "Vælg <b> <i> Del valgt enhed </i> </b>, når din browser beder om tilladelser.",
|
||||
"iexplorerGrantPermissions": "Vælg <b><i>OK</i> </b>, når din browser beder om tilladelser.",
|
||||
"nwjsGrantPermissions": "Giv tilladelse til at bruge dit kamera og din mikrofon",
|
||||
"operaGrantPermissions": "Vælg <b><i> Tillad</i> </b>, når din browser beder om tilladelser.",
|
||||
"react-nativeGrantPermissions": "Vælg <b><i> Tillad</i> </b>, når din browser beder om tilladelser.",
|
||||
"safariGrantPermissions": "Vælg <b><i>OK</i> </b>, når din browser beder om tilladelser."
|
||||
"androidGrantPermissions": "Vælg <b><i> Tillad</i> </b>, når din browser beder om tilladelser",
|
||||
"chromeGrantPermissions": "Vælg <b><i> Tillad</i> </b>, når din browser beder om tilladelser",
|
||||
"edgeGrantPermissions": "Vælg <b><i>Ja</i> </b>, når din browser beder om tilladelser",
|
||||
"electronGrantPermissions": "Giv tilladelse til at dit kamera og din mikrofon benyttes",
|
||||
"firefoxGrantPermissions": "Vælg <b> <i> Del valgt enhed </i> </b>, når din browser beder om tilladelser",
|
||||
"iexplorerGrantPermissions": "Vælg <b><i>OK</i> </b>, når din browser beder om tilladelser",
|
||||
"nwjsGrantPermissions": "Giv tilladelse til at dit kamera og din mikrofon benyttes",
|
||||
"operaGrantPermissions": "Vælg <b><i> Tillad</i> </b>, når din browser beder om tilladelser",
|
||||
"react-nativeGrantPermissions": "Vælg <b><i> Tillad</i> </b>, når din browser beder om tilladelser",
|
||||
"safariGrantPermissions": "Vælg <b><i>OK</i> </b>, når din browser beder om tilladelser"
|
||||
},
|
||||
"videoSIPGW": {
|
||||
"busy": "Vi arbejder på at frigøre ressourcer. Prøv igen om et par minutter.",
|
||||
"busy": "Vi arbejder på at frigøre ressourcer. Prøv igen om et par minutter",
|
||||
"busyTitle": "Mødeservice er i øjeblikket optaget",
|
||||
"errorAlreadyInvited": "{{displayName}} allerede inviteret",
|
||||
"errorInvite": "Mødet er endnu ikke etableret. Prøv igen senere.",
|
||||
"errorInviteFailed": "Vi arbejder på at løse problemet. Prøv igen senere.",
|
||||
"errorInviteFailedTitle": "Inviterer {{displayName}} mislykkedes",
|
||||
"errorInviteTitle": "Fejl ved indbydelse til møde",
|
||||
"errorInvite": "Mødet er endnu ikke etableret. Prøv igen senere",
|
||||
"errorInviteFailed": "Vi arbejder på at løse problemet. Prøv igen senere",
|
||||
"errorInviteFailedTitle": "Invitationen af {{displayName}} mislykkedes",
|
||||
"errorInviteTitle": "Fejl ved invitation til møde",
|
||||
"pending": "{{displayName}} er blevet inviteret"
|
||||
},
|
||||
"videoStatus": {
|
||||
"audioOnly": "AUD",
|
||||
"audioOnlyExpanded": "Du er i kun-lyd tilstand. Denne tilstand sparrer båndbredde, men du kan ikke se andres video.",
|
||||
"audioOnlyExpanded": "Du er i kun-lyd-tilstand. Denne tilstand sparer båndbredde, men du kan ikke se andres video",
|
||||
"callQuality": "Opkaldskvalitet",
|
||||
"hd": "HD",
|
||||
"hdTooltip": "Viser HD video",
|
||||
"hdTooltip": "Viser høj opløsning",
|
||||
"highDefinition": "Høj opløsning",
|
||||
"labelTooiltipNoVideo": "Ingen video",
|
||||
"labelTooltipAudioOnly": "Tilstand kun-lyd er slået til",
|
||||
"labelTooltipAudioOnly": "Kun-lyd er slået til",
|
||||
"ld": "LD",
|
||||
"ldTooltip": "Viser lav opløsnings video",
|
||||
"ldTooltip": "Viser lav opløsning",
|
||||
"lowDefinition": "Lav opløsning",
|
||||
"onlyAudioAvailable": "Kun-lyd er tilgængelig",
|
||||
"onlyAudioSupported": "Vi understøtter kun-lyd i denne browser.",
|
||||
"sd": "SD",
|
||||
"sdTooltip": "Vider normal opløsnings video",
|
||||
"sdTooltip": "Viser standard opløsning",
|
||||
"standardDefinition": "Standard opløsning"
|
||||
},
|
||||
"videothumbnail": {
|
||||
"domute": "Slå lyd fra",
|
||||
"domuteOthers": "Slå lyd fra for alle andre",
|
||||
"flip": "Flip",
|
||||
"kick": "Afbryd",
|
||||
"kick": "Smid ud",
|
||||
"moderator": "Moderator",
|
||||
"mute": "Medlemmets lyd er slået fra",
|
||||
"mute": "Deltagerens lyd er slået fra",
|
||||
"muted": "Lyd slået fra",
|
||||
"remoteControl": "Fjernbetjening",
|
||||
"show": "",
|
||||
@@ -741,9 +742,9 @@
|
||||
"welcomepage": {
|
||||
"accessibilityLabel": {
|
||||
"join": "Tryk for at deltage",
|
||||
"roomname": "Skriv møde navn"
|
||||
"roomname": "Skriv mødenavn"
|
||||
},
|
||||
"appDescription": "Chat med hele dit team eller inviter alle du kender. {{app}} er en fuldt krypteret, 100% open source videokonference løsning som du kan bruge hele dagen, hver dag, gratis - uden at du behøver en konto. ",
|
||||
"appDescription": "Chat med hele dit team eller invitér alle du kender. {{app}} er en fuldt krypteret, 100% open source videokonference løsning som du kan bruge hele dagen, hver dag, gratis - uden at du behøver en konto. ",
|
||||
"audioVideoSwitch": {
|
||||
"audio": "Lyd",
|
||||
"video": "Video"
|
||||
@@ -755,13 +756,13 @@
|
||||
"go": "Start",
|
||||
"join": "Deltag",
|
||||
"info": "Info",
|
||||
"privacy": "Privatliv",
|
||||
"privacy": "Privacy",
|
||||
"recentList": "Seneste",
|
||||
"recentListDelete": "Slet",
|
||||
"recentListEmpty": "Din seneste liste er i øjeblikket tom. Chat med dit team, og du finder alle dine seneste møder her.",
|
||||
"reducedUIText": "",
|
||||
"roomname": "Indtast mødenavn",
|
||||
"roomnameHint": "Indtast mødenavnet, du vil deltage i.",
|
||||
"roomnameHint": "Indtast mødenavnet på det møde, du vil deltage i.",
|
||||
"sendFeedback": "Giv tilbagemelding",
|
||||
"betingelser": "Betingelser",
|
||||
"title": "Skærmbesøg & videomøder"
|
||||
|
||||
@@ -239,12 +239,19 @@
|
||||
"muteEveryoneElseTitle": "Alle außer {{whom}} stummschalten?",
|
||||
"muteEveryoneDialog": "Wollen Sie wirklich alle stummschalten? Sie können deren Stummschaltung nicht mehr beenden, aber sie können ihre Stummschaltung jederzeit selbst beenden.",
|
||||
"muteEveryoneTitle": "Alle stummschalten?",
|
||||
"muteEveryoneElsesVideoDialog": "Sobald die Kamera deaktiviert ist, können Sie sie nicht wieder aktivieren, die Teilnehmer können dies aber jederzeit wieder ändern.",
|
||||
"muteEveryoneElsesVideoTitle": "Die Kamera von allen außer {{whom}} ausschalten?",
|
||||
"muteEveryonesVideoDialog": "Sind Sie sicher, dass Sie die Kamera von allen Teilnehmern deaktivieren möchten? Sie können sie nicht wieder aktivieren, die Teilnehmer können dies aber jederzeit wieder ändern.",
|
||||
"muteEveryonesVideoTitle": "Die Kamera von allen anderen ausschalten?",
|
||||
"muteEveryoneSelf": "sich selbst",
|
||||
"muteEveryoneStartMuted": "Alle beginnen von jetzt an stummgeschaltet",
|
||||
"muteParticipantBody": "Sie können die Stummschaltung anderer Personen nicht aufheben, aber eine Person kann ihre eigene Stummschaltung jederzeit beenden.",
|
||||
"muteParticipantButton": "Stummschalten",
|
||||
"muteParticipantDialog": "Wollen Sie diese Person wirklich stummschalten? Sie können die Stummschaltung nicht wieder aufheben, die Person kann dies aber jederzeit selbst tun.",
|
||||
"muteParticipantTitle": "Person stummschalten?",
|
||||
"muteParticipantsVideoButton": "Kamera ausschalten",
|
||||
"muteParticipantsVideoTitle": "Die Kamera von dieser Person ausschalten?",
|
||||
"muteParticipantsVideoBody": "Sie können die Kamera nicht wieder aktivieren, die Teilnehmer können dies aber jederzeit wieder ändern.",
|
||||
"Ok": "OK",
|
||||
"passwordLabel": "Dieses Meeting wurde gesichert. Bitte geben Sie das $t(lockRoomPasswordUppercase) ein, um dem Meeting beizutreten.",
|
||||
"passwordNotSupported": "Das Festlegen eines Konferenzpassworts wird nicht unterstützt.",
|
||||
@@ -484,6 +491,8 @@
|
||||
"mutedTitle": "Stummschaltung aktiv!",
|
||||
"mutedRemotelyTitle": "Sie wurden von {{participantDisplayName}} stummgeschaltet!",
|
||||
"mutedRemotelyDescription": "Sie können jederzeit die Stummschaltung aufheben, wenn Sie bereit sind zu sprechen. Wenn Sie fertig sind, können Sie sich wieder stummschalten, um Geräusche vom Meeting fernzuhalten.",
|
||||
"videoMutedRemotelyTitle": "Ihre Kamera wurde von {{participantDisplayName}} ausgeschaltet!",
|
||||
"videoMutedRemotelyDescription": "Sie können sie jederzeit wieder einschalten.",
|
||||
"passwordRemovedRemotely": "$t(lockRoomPasswordUppercase) von einer anderen Person entfernt",
|
||||
"passwordSetRemotely": "$t(lockRoomPasswordUppercase) von einer anderen Person gesetzt",
|
||||
"raisedHand": "{{name}} möchte sprechen.",
|
||||
@@ -682,6 +691,7 @@
|
||||
},
|
||||
"startupoverlay": {
|
||||
"policyText": " ",
|
||||
"genericTitle": "Die Konferenz muss Ihr Mikrofon und Ihre Kamera verwenden.",
|
||||
"title": "{{app}} benötigt Kamera und Mikrofon."
|
||||
},
|
||||
"suspendedoverlay": {
|
||||
@@ -714,12 +724,16 @@
|
||||
"moreOptions": "Menü „Weitere Optionen“",
|
||||
"mute": "„Audio stummschalten“ ein-/ausschalten",
|
||||
"muteEveryone": "Alle stummschalten",
|
||||
"muteEveryoneElse": "Alle anderen stummschalten",
|
||||
"muteEveryonesVideo": "Alle Kameras ausschalten",
|
||||
"muteEveryoneElsesVideo": "Alle anderen Kameras ausschalten",
|
||||
"pip": "Bild-in-Bild-Modus ein-/ausschalten",
|
||||
"privateMessage": "Private Nachricht senden",
|
||||
"profile": "Profil bearbeiten",
|
||||
"raiseHand": "„Melden“ ein-/ausschalten",
|
||||
"recording": "Aufzeichnung ein-/ausschalten",
|
||||
"remoteMute": "Personen stummschalten",
|
||||
"remoteVideoMute": "Kamera von dieser Person ausschalten",
|
||||
"security": "Sicherheitsoptionen",
|
||||
"Settings": "Einstellungen ein-/ausschalten",
|
||||
"sharedvideo": "YouTube-Videofreigabe ein-/ausschalten",
|
||||
@@ -764,6 +778,7 @@
|
||||
"moreOptions": "Weitere Optionen",
|
||||
"mute": "Stummschaltung aktivieren / deaktivieren",
|
||||
"muteEveryone": "Alle stummschalten",
|
||||
"muteEveryonesVideo": "Alle Kameras ausschalten",
|
||||
"noAudioSignalTitle": "Es kommt kein Input von Ihrem Mikrofon!",
|
||||
"noAudioSignalDesc": "Wenn Sie das Gerät nicht absichtlich über die Systemeinstellungen oder die Hardware stumm geschaltet haben, sollten Sie einen Wechsel des Geräts in Erwägung ziehen.",
|
||||
"noAudioSignalDescSuggestion": "Wenn Sie das Gerät nicht absichtlich über die Systemeinstellungen oder die Hardware stummgeschaltet haben, sollten Sie einen Wechsel auf das vorgeschlagene Gerät in Erwägung ziehen.",
|
||||
@@ -849,13 +864,16 @@
|
||||
},
|
||||
"videothumbnail": {
|
||||
"domute": "Stummschalten",
|
||||
"domuteVideo": "Kamera ausschalten",
|
||||
"domuteOthers": "Alle anderen stummschalten",
|
||||
"domuteVideoOfOthers": "Alle anderen Kameras auschalten",
|
||||
"flip": "Spiegeln",
|
||||
"grantModerator": "Moderationsrechte vergeben",
|
||||
"kick": "Hinauswerfen",
|
||||
"moderator": "Moderation",
|
||||
"mute": "Person ist stumm geschaltet",
|
||||
"muted": "Stummgeschaltet",
|
||||
"videoMuted": "Kamera ausgeschaltet",
|
||||
"remoteControl": "Fernsteuerung",
|
||||
"show": "Im Vordergrund anzeigen",
|
||||
"videomute": "Person hat die Kamera angehalten"
|
||||
|
||||
@@ -105,8 +105,8 @@
|
||||
"bitrate": "Débit :",
|
||||
"bridgeCount": "Nombre de serveurs :",
|
||||
"codecs": "Codecs (A/V) :",
|
||||
"e2e_rtt": "E2E RTT :",
|
||||
"connectedTo": "Connecté à :",
|
||||
"e2e_rtt": "E2E RTT :",
|
||||
"framerate": "Images par seconde :",
|
||||
"less": "Cacher les détails",
|
||||
"localaddress": "Adresse locale :",
|
||||
@@ -251,7 +251,7 @@
|
||||
"muteParticipantDialog": "Êtes-vous sûr(e) de vouloir couper le micro de ce participant ? Seul le participant pourra ensuite réactiver son micro à tout moment.",
|
||||
"muteParticipantTitle": "Couper le micro de ce participant ?",
|
||||
"Ok": "Ok",
|
||||
"passwordLabel": "La réunion a été verrouillée par un·e participant·e. Veuillez entrer le $t(lockRoomPassword) pour la rejoindre.",
|
||||
"passwordLabel": "La réunion a été verrouillée par un(e) participant(e). Veuillez entrer le $t(lockRoomPassword) pour la rejoindre.",
|
||||
"passwordNotSupported": "La définition d'un $t(lockRoomPassword) de réunion n'est pas prise en charge.",
|
||||
"passwordNotSupportedTitle": "L'ajout d'un $t(lockRoomPassword) n'est pas supporté",
|
||||
"passwordRequired": "$t(lockRoomPasswordUppercase) requis",
|
||||
@@ -396,16 +396,16 @@
|
||||
"focusRemote": "Épingler la vidéo de quelqu'un d'autre",
|
||||
"fullScreen": "Activer / Désactiver le mode plein écran",
|
||||
"keyboardShortcuts": "Raccourcis clavier",
|
||||
"localRecording": "Afficher ou masquer les commandes de l'enregistrement local",
|
||||
"mute": "Activer ou désactiver le microphone",
|
||||
"pushToTalk": "Appuyer pour parler",
|
||||
"raiseHand": "Lever ou baisser la main",
|
||||
"localRecording": "Afficher / Masquer les commandes de l'enregistrement local",
|
||||
"mute": "Activer / Désactiver le microphone",
|
||||
"pushToTalk": "Maintenir la touche pour parler",
|
||||
"raiseHand": "Lever / Baisser la main",
|
||||
"showSpeakerStats": "Afficher les statistiques de l'interlocuteur",
|
||||
"toggleChat": "Ouvrir ou fermer le panneau de conversation",
|
||||
"toggleFilmstrip": "Afficher ou masquer les vignettes vidéos",
|
||||
"toggleChat": "Ouvrir / Fermer le panneau de conversation",
|
||||
"toggleFilmstrip": "Afficher / Masquer les vignettes vidéos",
|
||||
"toggleScreensharing": "Basculer entre la caméra et le partage d'écran",
|
||||
"toggleShortcuts": "Afficher ou masquer les raccourcis clavier",
|
||||
"videoMute": "Démarrer ou arrêter votre caméra",
|
||||
"toggleShortcuts": "Afficher / Masquer les raccourcis clavier",
|
||||
"videoMute": "Démarrer / Arrêter votre caméra",
|
||||
"videoQuality": "Accorder la qualité des appels"
|
||||
},
|
||||
"liveStreaming": {
|
||||
@@ -423,15 +423,15 @@
|
||||
"expandedOff": "La diffusion en direct a été arrêtée",
|
||||
"expandedOn": "La conférence est en cours de diffusion sur YouTube.",
|
||||
"expandedPending": "La diffusion en direct a commencé ...",
|
||||
"failedToStart": "La diffusion n'as pas réussi à démarrer",
|
||||
"getStreamKeyManually": "Nous n'avons pu récupérer aucun flux en direct. Essayez d'obtenir votre clé de diffusion en direct sur YouTube.",
|
||||
"failedToStart": "La diffusion n'a pas réussi à démarrer",
|
||||
"getStreamKeyManually": "Nous n'avons pas réussi à récupérer un flux de direct. Essayez d'obtenir votre clé de diffusion en direct sur YouTube.",
|
||||
"invalidStreamKey": "La clé de diffusion en direct n'est peut-être pas correcte.",
|
||||
"limitNotificationDescriptionWeb": "En raison de la forte demande, votre diffusion sera limitée à {{limit}} min. Pour un streaming illimité, essayez <a href={{url}} rel='noopener noreferrer' target='_blank'> {{app}} </a>.",
|
||||
"limitNotificationDescriptionWeb": "En raison d'une forte demande, votre diffusion sera limitée à {{limit}} min. Pour un streaming illimité, essayez <a href={{url}} rel='noopener noreferrer' target='_blank'> {{app}} </a>.",
|
||||
"limitNotificationDescriptionNative": "Votre diffusion sera limitée à {{limit}} min. Pour un streaming illimité, essayez {{app}}.",
|
||||
"off": "Le Streaming a été arrêté",
|
||||
"offBy": "{{name}} a arrêté la diffusion en continu",
|
||||
"off": "La diffusion en direct (streaming) a été arrêté",
|
||||
"offBy": "{{name}} a arrêté la diffusion en direct",
|
||||
"on": "En direct",
|
||||
"onBy": "{{name}} a démarré la diffusion en continu",
|
||||
"onBy": "{{name}} a démarré la diffusion en direct",
|
||||
"pending": "Lancement du direct ...",
|
||||
"serviceName": "Service de diffusion en direct",
|
||||
"signedInAs": "Vous êtes connecté en tant que :",
|
||||
@@ -461,12 +461,12 @@
|
||||
"messages": {
|
||||
"engaged": "L'enregistrement local a démarré.",
|
||||
"finished": "L'enregistrement de la session {{token}} s'est terminé. Merci d'envoyer le fichier au modérateur.",
|
||||
"finishedModerator": "L'enregistrement de la session {{token}} s'est terminé. La piste a bien été sauvegardée. Merci de demander aux autres participants de soumettre leurs enregistrements.",
|
||||
"finishedModerator": "L'enregistrement de la session {{token}} s'est terminé. L'enregistrement a bien été sauvegardée. Merci de demander aux autres participants de soumettre leurs enregistrements.",
|
||||
"notModerator": "Vous n'êtes pas le modérateur. Vous ne pouvez pas démarrer ou arrêter un enregistrement local."
|
||||
},
|
||||
"moderator": "Modérateur",
|
||||
"moderator": "Modérateur ",
|
||||
"no": "Non",
|
||||
"participant": "Participant·e",
|
||||
"participant": "Participant(e)",
|
||||
"participantStats": "Statistiques du participant",
|
||||
"sessionToken": "Token de la session",
|
||||
"start": "Démarrer l'enregistrement",
|
||||
@@ -484,7 +484,7 @@
|
||||
"focus": "Focus de conférence",
|
||||
"focusFail": "{{component}} n'est pas disponible - réessayez dans {{ms}} sec",
|
||||
"grantedTo": "Droits modérateur accordés à {{to}} !",
|
||||
"invitedOneMember": "{{name}} a été invité·e",
|
||||
"invitedOneMember": "{{name}} a été invité(e)",
|
||||
"invitedThreePlusMembers": "{{name}} et {{count}} autres ont été invités",
|
||||
"invitedTwoMembers": "{{first}} et {{second}} ont été invités",
|
||||
"kickParticipant": "{{kicked}} a été expulsé par {{kicker}}",
|
||||
@@ -516,7 +516,8 @@
|
||||
"poweredby": "produit par",
|
||||
"prejoin": {
|
||||
"audioAndVideoError": "Erreur audio et video:",
|
||||
"audioOnlyError": "Erreur audio:",
|
||||
"audioDeviceProblem": "Il y a un problème avec votre périphérique audio",
|
||||
"audioOnlyError": "Erreur audio :",
|
||||
"audioTrackError": "N'a pas pu créer la piste audio.",
|
||||
"calling": "Appel",
|
||||
"callMe": "Appelez-moi",
|
||||
@@ -602,7 +603,7 @@
|
||||
"expandedOff": "L'enregistrement a été arrêté",
|
||||
"expandedOn": "Cette conférence est actuellement en cours d'enregistrement.",
|
||||
"expandedPending": "Démarrage de l'enregistrement ...",
|
||||
"failedToStart": "L'enregistrement n'as pas réussi à démarrer",
|
||||
"failedToStart": "L'enregistrement n'a pas réussi à démarrer",
|
||||
"fileSharingdescription": "Partager l'enregistrement avec les participants de la réunion",
|
||||
"limitNotificationDescriptionWeb": "En raison d'une forte demande, votre enregistrement sera limité à {{limit}} min. Pour des enregistrements illimités, essayez <a href={{url}} rel='noopener noreferrer' target='_blank'> {{app}} </a>.",
|
||||
"limitNotificationDescriptionNative": "En raison d'une forte demande, votre enregistrement sera limité à {{limit}} min. Pour des enregistrements illimités, essayez <3> {{app}} </3>.",
|
||||
@@ -702,48 +703,48 @@
|
||||
},
|
||||
"toolbar": {
|
||||
"accessibilityLabel": {
|
||||
"audioOnly": "Activer/désactiver le mode voix uniquement",
|
||||
"audioOnly": "Activer / Désactiver le mode voix uniquement",
|
||||
"audioRoute": "Sélectionner la source audio",
|
||||
"callQuality": "Ajuster la qualité vidéo",
|
||||
"cc": "Activer/désactiver les sous-titres",
|
||||
"chat": "Afficher/masquer la discussion instantanée",
|
||||
"document": "Activer/désactiver le document partagé",
|
||||
"cc": "Activer / Désactiver les sous-titres",
|
||||
"chat": "Afficher / Masquer la discussion instantanée",
|
||||
"document": "Activer / Désactiver le document partagé",
|
||||
"download": "Télécharger nos applications",
|
||||
"embedMeeting": "Intégrer la réunion",
|
||||
"feedback": "Laisser des commentaires",
|
||||
"fullScreen": "Activer/désactiver le plein écran",
|
||||
"fullScreen": "Activer / Désactiver le plein écran",
|
||||
"grantModerator": "Nommer modérateur",
|
||||
"hangup": "Quitter la conversation",
|
||||
"help": "Aide",
|
||||
"invite": "Inviter des participants",
|
||||
"kick": "Expulser le participant",
|
||||
"lobbyButton": "Activer / désactiver le mode lobby",
|
||||
"localRecording": "Activer/désactiver les contrôles d'enregistrement local",
|
||||
"lockRoom": "Activer/Désactiver le mot de passe de la réunion",
|
||||
"moreActions": "Activer/désactiver le menu d'actions supplémentaires",
|
||||
"lobbyButton": "Activer / Désactiver le mode salle d'attente",
|
||||
"localRecording": "Activer / Désactiver les contrôles d'enregistrement local",
|
||||
"lockRoom": "Activer / Désactiver le mot de passe de la réunion",
|
||||
"moreActions": "Activer / Désactiver le menu d'actions supplémentaires",
|
||||
"moreActionsMenu": "Menu d'actions supplémentaires",
|
||||
"moreOptions": "Voir plus d'options",
|
||||
"mute": "Activer/désactiver l'audio",
|
||||
"mute": "Activer / Désactiver l'audio",
|
||||
"muteEveryone": "Rendre muet tout le monde",
|
||||
"pip": "Activer/désactiver le mode Picture in Picture",
|
||||
"pip": "Activer / Désactiver le mode Picture in Picture",
|
||||
"privateMessage": "Envoyer un message privé",
|
||||
"profile": "Éditer votre profil",
|
||||
"raiseHand": "Lever/baisser la main",
|
||||
"recording": "Activer/désactiver l'enregistrement",
|
||||
"raiseHand": "Lever / Baisser la main",
|
||||
"recording": "Activer / Désactiver l'enregistrement",
|
||||
"remoteMute": "Désactiver le micro du participant",
|
||||
"security": "Options de sécurité",
|
||||
"Settings": "Afficher/masquer le menu des paramètres",
|
||||
"sharedvideo": "Démarrer/arrêter le partage de vidéo YouTube",
|
||||
"Settings": "Afficher / Masquer le menu des paramètres",
|
||||
"sharedvideo": "Démarrer / Arrêter le partage de vidéo YouTube",
|
||||
"shareRoom": "Inviter quelqu'un",
|
||||
"shareYourScreen": "Activer/désactiver le partage d'écran",
|
||||
"shortcuts": "Afficher/masquer les raccourcis",
|
||||
"shareYourScreen": "Activer / Désactiver le partage d'écran",
|
||||
"shortcuts": "Afficher / Masquer les raccourcis",
|
||||
"show": "Afficher en premier plan",
|
||||
"speakerStats": "Afficher/cacher les statistiques de parole",
|
||||
"tileView": "Activer/désactiver la vue mosaïque",
|
||||
"speakerStats": "Afficher / Cacher les statistiques de parole",
|
||||
"tileView": "Activer / Désactiver la vue mosaïque",
|
||||
"toggleCamera": "Changer de caméra",
|
||||
"toggleFilmstrip": "Basculer de pellicule",
|
||||
"videomute": "Activer/désactiver la vidéo",
|
||||
"videoblur": "Activer/désactiver le flou de la vidéo"
|
||||
"videomute": "Activer / Désactiver la vidéo",
|
||||
"videoblur": "Activer / Désactiver le flou de la vidéo"
|
||||
},
|
||||
"addPeople": "Ajouter des personnes à votre appel",
|
||||
"audioOnlyOff": "Désactiver le mode bande passante réduite",
|
||||
@@ -766,8 +767,8 @@
|
||||
"hangup": "Quitter",
|
||||
"help": "Aide",
|
||||
"invite": "Inviter des participants",
|
||||
"lobbyButtonDisable": "Désactiver le contrôle des participant·e·s",
|
||||
"lobbyButtonEnable": "Activer le contrôle des participant·e·s",
|
||||
"lobbyButtonDisable": "Désactiver le mode salle d'attente / contrôle des participant(e)s",
|
||||
"lobbyButtonEnable": "Activer le mode salle d'attente / contrôle des participant(e)s",
|
||||
"login": "Connexion",
|
||||
"logout": "Déconnexion",
|
||||
"lowerYourHand": "Baisser la main",
|
||||
@@ -800,22 +801,22 @@
|
||||
"stopSubtitles": "Désactiver les sous-titres",
|
||||
"stopSharedVideo": "Arrêter la vidéo YouTube",
|
||||
"talkWhileMutedPopup": "Vous voulez parler ? Votre micro est coupé.",
|
||||
"tileViewToggle": "Activer/désactiver la vue mosaïque",
|
||||
"tileViewToggle": "Activer / Désactiver la vue mosaïque",
|
||||
"toggleCamera": "Changer de caméra",
|
||||
"videomute": "Démarrer / Arrêter la caméra",
|
||||
"startvideoblur": "Flouter mon arrière plan",
|
||||
"stopvideoblur": "Désactiver le flou d'arrière-plan"
|
||||
},
|
||||
"transcribing": {
|
||||
"ccButtonTooltip": "Activer/Désactiver les sous-titres",
|
||||
"ccButtonTooltip": "Activer / Désactiver les sous-titres",
|
||||
"error": "Échec de la transcription. Veuillez réessayer.",
|
||||
"expandedLabel": "La transcription est actuellement activée",
|
||||
"failedToStart": "Échec de démarrage de la transcription",
|
||||
"labelToolTip": "La transcription de la réunion est en cours",
|
||||
"off": "La transcription désactivée",
|
||||
"pending": "Préparation de la transcription de la réunion ...",
|
||||
"start": "Afficher/masquer les sous-titres",
|
||||
"stop": "Désactiver le sous-titrage",
|
||||
"start": "Activer les sous-titres",
|
||||
"stop": "Désactiver les sous-titres",
|
||||
"tr": "TR"
|
||||
},
|
||||
"userMedia": {
|
||||
@@ -939,10 +940,10 @@
|
||||
"nameField": "Saisissez votre nom",
|
||||
"notificationLobbyAccessDenied": "{{targetParticipantName}} a été refusé par {{originParticipantName}}",
|
||||
"notificationLobbyAccessGranted": "{{targetParticipantName}} a été accepté par {{originParticipantName}}",
|
||||
"notificationLobbyDisabled": "Lobby a été désactivé par {{originParticipantName}}",
|
||||
"notificationLobbyEnabled": "Lobby a été activé par {{originParticipantName}}",
|
||||
"notificationTitle": "Lobby",
|
||||
"passwordField": "Saisissez le mot de passe de la réunion",
|
||||
"notificationLobbyDisabled": "Le mode salle d'attente a été désactivé par {{originParticipantName}}",
|
||||
"notificationLobbyEnabled": "Le mode salle d'attente a été activé par {{originParticipantName}}",
|
||||
"notificationTitle": "Salle d'attente",
|
||||
"passwordField": "Veuillez saisir le mot de passe de la réunion",
|
||||
"passwordJoinButton": "Rejoindre",
|
||||
"reject": "Refuser",
|
||||
"toggleLabel": "Activer la salle d'attente"
|
||||
|
||||
@@ -61,6 +61,7 @@
|
||||
"today": "Today"
|
||||
},
|
||||
"chat": {
|
||||
"enter": "Enter chat room",
|
||||
"error": "Error: your message was not sent. Reason: {{error}}",
|
||||
"fieldPlaceHolder": "Type your message here",
|
||||
"messagebox": "Type a message",
|
||||
@@ -240,12 +241,19 @@
|
||||
"muteEveryoneElseTitle": "Mute everyone except {{whom}}?",
|
||||
"muteEveryoneDialog": "Are you sure you want to mute everyone? You won't be able to unmute them, but they can unmute themselves at any time.",
|
||||
"muteEveryoneTitle": "Mute everyone?",
|
||||
"muteEveryoneElsesVideoDialog": "Once the camera is disabled, you won't be able to turn it back on, but they can turn it back on at any time.",
|
||||
"muteEveryoneElsesVideoTitle": "Disable everyone's camera except {{whom}}?",
|
||||
"muteEveryonesVideoDialog": "Are you sure you want to disable everyone's camera? You won't be able to turn it back on, but they can turn it back on at any time.",
|
||||
"muteEveryonesVideoTitle": "Disable everyone's camera?",
|
||||
"muteEveryoneSelf": "yourself",
|
||||
"muteEveryoneStartMuted": "Everyone starts muted from now on",
|
||||
"muteParticipantBody": "You won't be able to unmute them, but they can unmute themselves at any time.",
|
||||
"muteParticipantButton": "Mute",
|
||||
"muteParticipantDialog": "Are you sure you want to mute this participant? You won't be able to unmute them, but they can unmute themselves at any time.",
|
||||
"muteParticipantTitle": "Mute this participant?",
|
||||
"muteParticipantsVideoButton": "Disable camera",
|
||||
"muteParticipantsVideoTitle": "Disable camera of this participant?",
|
||||
"muteParticipantsVideoBody": "You won't be able to turn the camera back on, but they can turn it back on at any time.",
|
||||
"Ok": "OK",
|
||||
"passwordLabel": "The meeting has been locked by a participant. Please enter the $t(lockRoomPassword) to join.",
|
||||
"passwordNotSupported": "Setting a meeting $t(lockRoomPassword) is not supported.",
|
||||
@@ -305,6 +313,7 @@
|
||||
"unlockRoom": "Remove meeting $t(lockRoomPassword)",
|
||||
"user": "user",
|
||||
"userPassword": "user password",
|
||||
"videoLink": "Video link",
|
||||
"WaitForHostMsg": "The conference <b>{{room}}</b> has not yet started. If you are the host then please authenticate. Otherwise, please wait for the host to arrive.",
|
||||
"WaitForHostMsgWOk": "The conference <b>{{room}}</b> has not yet started. If you are the host then please press Ok to authenticate. Otherwise, please wait for the host to arrive.",
|
||||
"WaitingForHost": "Waiting for the host ...",
|
||||
@@ -323,6 +332,11 @@
|
||||
"embedMeeting": {
|
||||
"title": "Embed this meeting"
|
||||
},
|
||||
"virtualBackground": {
|
||||
"title": "Backgrounds",
|
||||
"enableBlur": "Enable blur",
|
||||
"removeBackground": "Remove background"
|
||||
},
|
||||
"feedback": {
|
||||
"average": "Average",
|
||||
"bad": "Bad",
|
||||
@@ -483,6 +497,8 @@
|
||||
"mutedTitle": "You're muted!",
|
||||
"mutedRemotelyTitle": "You have been muted by {{participantDisplayName}}!",
|
||||
"mutedRemotelyDescription": "You can always unmute when you're ready to speak. Mute back when you're done to keep noise away from the meeting.",
|
||||
"videoMutedRemotelyTitle": "Your camera has been disabled by {{participantDisplayName}}!",
|
||||
"videoMutedRemotelyDescription": "You can always turn it on again.",
|
||||
"passwordRemovedRemotely": "$t(lockRoomPasswordUppercase) removed by another participant",
|
||||
"passwordSetRemotely": "$t(lockRoomPasswordUppercase) set by another participant",
|
||||
"raisedHand": "{{name}} would like to speak.",
|
||||
@@ -715,15 +731,19 @@
|
||||
"moreOptions": "Show more options",
|
||||
"mute": "Toggle mute audio",
|
||||
"muteEveryone": "Mute everyone",
|
||||
"muteEveryoneElse": "Mute everyone else",
|
||||
"muteEveryonesVideo": "Disable everyone's camera",
|
||||
"muteEveryoneElsesVideo": "Disable everyone else's camera",
|
||||
"pip": "Toggle Picture-in-Picture mode",
|
||||
"privateMessage": "Send private message",
|
||||
"profile": "Edit your profile",
|
||||
"raiseHand": "Toggle raise hand",
|
||||
"recording": "Toggle recording",
|
||||
"remoteMute": "Mute participant",
|
||||
"remoteVideoMute": "Disable camera of participant",
|
||||
"security": "Security options",
|
||||
"Settings": "Toggle settings",
|
||||
"sharedvideo": "Toggle Youtube video sharing",
|
||||
"sharedvideo": "Toggle YouTube video sharing",
|
||||
"shareRoom": "Invite someone",
|
||||
"shareYourScreen": "Toggle screenshare",
|
||||
"shortcuts": "Toggle shortcuts",
|
||||
@@ -733,9 +753,10 @@
|
||||
"toggleCamera": "Toggle camera",
|
||||
"toggleFilmstrip": "Toggle filmstrip",
|
||||
"videomute": "Toggle mute video",
|
||||
"videoblur": "Toggle video blur"
|
||||
"selectBackground": "Select Background"
|
||||
},
|
||||
"addPeople": "Add people to your call",
|
||||
"audioSettings": "Audio settings",
|
||||
"audioOnlyOff": "Disable low bandwidth mode",
|
||||
"audioOnlyOn": "Enable low bandwidth mode",
|
||||
"audioRoute": "Select the sound device",
|
||||
@@ -765,6 +786,7 @@
|
||||
"moreOptions": "More options",
|
||||
"mute": "Mute / Unmute",
|
||||
"muteEveryone": "Mute everyone",
|
||||
"muteEveryonesVideo": "Disable everyone's camera",
|
||||
"noAudioSignalTitle": "There is no input coming from your mic!",
|
||||
"noAudioSignalDesc": "If you did not purposely mute it from system settings or hardware, consider switching the device.",
|
||||
"noAudioSignalDescSuggestion": "If you did not purposely mute it from system settings or hardware, consider switching to the suggested device.",
|
||||
@@ -793,8 +815,7 @@
|
||||
"tileViewToggle": "Toggle tile view",
|
||||
"toggleCamera": "Toggle camera",
|
||||
"videomute": "Start / Stop camera",
|
||||
"startvideoblur": "Blur my background",
|
||||
"stopvideoblur": "Disable background blur"
|
||||
"selectBackground": "Select background"
|
||||
},
|
||||
"transcribing": {
|
||||
"ccButtonTooltip": "Start / Stop subtitles",
|
||||
@@ -849,13 +870,16 @@
|
||||
"videothumbnail": {
|
||||
"connectionInfo": "Connection Info",
|
||||
"domute": "Mute",
|
||||
"domuteVideo": "Disable camera",
|
||||
"domuteOthers": "Mute everyone else",
|
||||
"domuteVideoOfOthers": "Disable camera of everyone else",
|
||||
"flip": "Flip",
|
||||
"grantModerator": "Grant Moderator",
|
||||
"kick": "Kick out",
|
||||
"moderator": "Moderator",
|
||||
"mute": "Participant is muted",
|
||||
"muted": "Muted",
|
||||
"videoMuted": "Camera disabled",
|
||||
"remoteControl": "Start / Stop remote control",
|
||||
"show": "Show on stage",
|
||||
"videomute": "Participant has stopped the camera"
|
||||
|
||||
@@ -12,8 +12,10 @@ import {
|
||||
setPassword,
|
||||
setSubject
|
||||
} from '../../react/features/base/conference';
|
||||
import { overwriteConfig, getWhitelistedJSON } from '../../react/features/base/config';
|
||||
import { parseJWTFromURLParams } from '../../react/features/base/jwt';
|
||||
import JitsiMeetJS, { JitsiRecordingConstants } from '../../react/features/base/lib-jitsi-meet';
|
||||
import { MEDIA_TYPE } from '../../react/features/base/media';
|
||||
import { pinParticipant, getParticipantById, kickParticipant } from '../../react/features/base/participants';
|
||||
import { setPrivateMessageRecipient } from '../../react/features/chat/actions';
|
||||
import { openChat } from '../../react/features/chat/actions.web';
|
||||
@@ -79,7 +81,9 @@ function initCommands() {
|
||||
sendAnalytics(createApiEvent('display.name.changed'));
|
||||
APP.conference.changeLocalDisplayName(displayName);
|
||||
},
|
||||
'mute-everyone': () => {
|
||||
'mute-everyone': mediaType => {
|
||||
const muteMediaType = mediaType ? mediaType : MEDIA_TYPE.AUDIO;
|
||||
|
||||
sendAnalytics(createApiEvent('muted-everyone'));
|
||||
const participants = APP.store.getState()['features/base/participants'];
|
||||
const localIds = participants
|
||||
@@ -87,7 +91,7 @@ function initCommands() {
|
||||
.filter(participant => participant.role === 'moderator')
|
||||
.map(participant => participant.id);
|
||||
|
||||
APP.store.dispatch(muteAllParticipants(localIds));
|
||||
APP.store.dispatch(muteAllParticipants(localIds, muteMediaType));
|
||||
},
|
||||
'toggle-lobby': isLobbyEnabled => {
|
||||
APP.store.dispatch(toggleLobbyMode(isLobbyEnabled));
|
||||
@@ -353,6 +357,11 @@ function initCommands() {
|
||||
},
|
||||
'kick-participant': participantId => {
|
||||
APP.store.dispatch(kickParticipant(participantId));
|
||||
},
|
||||
'overwrite-config': config => {
|
||||
const whitelistedConfig = getWhitelistedJSON('config', config);
|
||||
|
||||
APP.store.dispatch(overwriteConfig(whitelistedConfig));
|
||||
}
|
||||
};
|
||||
transport.on('event', ({ data, name }) => {
|
||||
|
||||
3
modules/API/external/external_api.js
vendored
@@ -37,6 +37,7 @@ const commands = {
|
||||
intiatePrivateChat: 'initiate-private-chat',
|
||||
kickParticipant: 'kick-participant',
|
||||
muteEveryone: 'mute-everyone',
|
||||
overwriteConfig: 'overwrite-config',
|
||||
password: 'password',
|
||||
pinParticipant: 'pin-participant',
|
||||
resizeLargeVideo: 'resize-large-video',
|
||||
@@ -320,7 +321,7 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
|
||||
const frameName = `jitsiConferenceFrame${id}`;
|
||||
|
||||
this._frame = document.createElement('iframe');
|
||||
this._frame.allow = 'camera; microphone; display-capture; autoplay;';
|
||||
this._frame.allow = 'camera; microphone; display-capture; autoplay; clipboard-write';
|
||||
this._frame.src = this._url;
|
||||
this._frame.name = frameName;
|
||||
this._frame.id = frameName;
|
||||
|
||||
@@ -491,6 +491,25 @@ UI.onSharedVideoStop = function(id, attributes) {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Show shared video.
|
||||
* @param {string} url video url
|
||||
*/
|
||||
UI.startSharedVideoEmitter = function(url) {
|
||||
if (sharedVideoManager) {
|
||||
sharedVideoManager.startSharedVideoEmitter(url);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Stop shared video.
|
||||
*/
|
||||
UI.stopSharedVideoEmitter = function() {
|
||||
if (sharedVideoManager) {
|
||||
sharedVideoManager.stopSharedVideoEmitter();
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: Export every function separately. For now there is no point of doing
|
||||
// this because we are importing everything.
|
||||
export default UI;
|
||||
|
||||
@@ -12,11 +12,10 @@ import {
|
||||
participantLeft,
|
||||
pinParticipant
|
||||
} from '../../../react/features/base/participants';
|
||||
import { VIDEO_PLAYER_PARTICIPANT_NAME } from '../../../react/features/shared-video/constants';
|
||||
import { dockToolbox, showToolbox } from '../../../react/features/toolbox/actions.web';
|
||||
import { getToolboxHeight } from '../../../react/features/toolbox/functions.web';
|
||||
import { YOUTUBE_PARTICIPANT_NAME } from '../../../react/features/youtube-player/constants';
|
||||
import UIEvents from '../../../service/UI/UIEvents';
|
||||
import UIUtil from '../util/UIUtil';
|
||||
import Filmstrip from '../videolayout/Filmstrip';
|
||||
import LargeContainer from '../videolayout/LargeContainer';
|
||||
import VideoLayout from '../videolayout/VideoLayout';
|
||||
@@ -29,14 +28,8 @@ export const SHARED_VIDEO_CONTAINER_TYPE = 'sharedvideo';
|
||||
* Example shared video link.
|
||||
* @type {string}
|
||||
*/
|
||||
const defaultSharedVideoLink = 'https://youtu.be/TB7LlM4erx8';
|
||||
const updateInterval = 5000; // milliseconds
|
||||
|
||||
/**
|
||||
* The dialog for user input (video link).
|
||||
* @type {null}
|
||||
*/
|
||||
let dialog = null;
|
||||
|
||||
/**
|
||||
* Manager of shared video.
|
||||
@@ -76,52 +69,37 @@ export default class SharedVideoManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts shared video by asking user for url, or if its already working
|
||||
* asks whether the user wants to stop sharing the video.
|
||||
* Start shared video event emitter if a video is not shown.
|
||||
*
|
||||
* @param url of the video
|
||||
*/
|
||||
toggleSharedVideo() {
|
||||
if (dialog) {
|
||||
return;
|
||||
}
|
||||
startSharedVideoEmitter(url) {
|
||||
|
||||
if (!this.isSharedVideoShown) {
|
||||
requestVideoLink().then(
|
||||
url => {
|
||||
this.emitter.emit(
|
||||
UIEvents.UPDATE_SHARED_VIDEO, url, 'start');
|
||||
sendAnalytics(createEvent('started'));
|
||||
},
|
||||
err => {
|
||||
logger.log('SHARED VIDEO CANCELED', err);
|
||||
sendAnalytics(createEvent('canceled'));
|
||||
}
|
||||
);
|
||||
if (url) {
|
||||
this.emitter.emit(
|
||||
UIEvents.UPDATE_SHARED_VIDEO, url, 'start');
|
||||
sendAnalytics(createEvent('started'));
|
||||
}
|
||||
|
||||
return;
|
||||
logger.log('SHARED VIDEO CANCELED');
|
||||
sendAnalytics(createEvent('canceled'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop shared video event emitter done by the one who shared the video.
|
||||
*/
|
||||
stopSharedVideoEmitter() {
|
||||
|
||||
if (APP.conference.isLocalId(this.from)) {
|
||||
showStopVideoPropmpt().then(
|
||||
() => {
|
||||
// make sure we stop updates for playing before we send stop
|
||||
// if we stop it after receiving self presence, we can end
|
||||
// up sending stop playing, and on the other end it will not
|
||||
// stop
|
||||
if (this.intervalId) {
|
||||
clearInterval(this.intervalId);
|
||||
this.intervalId = null;
|
||||
}
|
||||
this.emitter.emit(
|
||||
UIEvents.UPDATE_SHARED_VIDEO, this.url, 'stop');
|
||||
sendAnalytics(createEvent('stopped'));
|
||||
},
|
||||
() => {}); // eslint-disable-line no-empty-function
|
||||
} else {
|
||||
APP.UI.messageHandler.showWarning({
|
||||
descriptionKey: 'dialog.alreadySharedVideoMsg',
|
||||
titleKey: 'dialog.alreadySharedVideoTitle'
|
||||
});
|
||||
sendAnalytics(createEvent('already.shared'));
|
||||
if (this.intervalId) {
|
||||
clearInterval(this.intervalId);
|
||||
this.intervalId = null;
|
||||
}
|
||||
this.emitter.emit(
|
||||
UIEvents.UPDATE_SHARED_VIDEO, this.url, 'stop');
|
||||
sendAnalytics(createEvent('stopped'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -303,7 +281,7 @@ export default class SharedVideoManager {
|
||||
conference: APP.conference._room,
|
||||
id: self.url,
|
||||
isFakeParticipant: true,
|
||||
name: YOUTUBE_PARTICIPANT_NAME
|
||||
name: VIDEO_PLAYER_PARTICIPANT_NAME
|
||||
}));
|
||||
|
||||
APP.store.dispatch(pinParticipant(self.url));
|
||||
@@ -675,134 +653,3 @@ class SharedVideoContainer extends LargeContainer {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if given string is youtube url.
|
||||
* @param {string} url string to check.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function getYoutubeLink(url) {
|
||||
const p = /^(?:https?:\/\/)?(?:www\.)?(?:youtu\.be\/|youtube\.com\/(?:embed\/|v\/|watch\?v=|watch\?.+&v=))((\w|-){11})(?:\S+)?$/;// eslint-disable-line max-len
|
||||
|
||||
|
||||
return url.match(p) ? RegExp.$1 : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ask user if he want to close shared video.
|
||||
*/
|
||||
function showStopVideoPropmpt() {
|
||||
return new Promise((resolve, reject) => {
|
||||
const submitFunction = function(e, v) {
|
||||
if (v) {
|
||||
resolve();
|
||||
} else {
|
||||
reject();
|
||||
}
|
||||
};
|
||||
|
||||
const closeFunction = function() {
|
||||
dialog = null;
|
||||
};
|
||||
|
||||
dialog = APP.UI.messageHandler.openTwoButtonDialog({
|
||||
titleKey: 'dialog.removeSharedVideoTitle',
|
||||
msgKey: 'dialog.removeSharedVideoMsg',
|
||||
leftButtonKey: 'dialog.Remove',
|
||||
submitFunction,
|
||||
closeFunction
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Ask user for shared video url to share with others.
|
||||
* Dialog validates client input to allow only youtube urls.
|
||||
*/
|
||||
function requestVideoLink() {
|
||||
const i18n = APP.translation;
|
||||
const cancelButton = i18n.generateTranslationHTML('dialog.Cancel');
|
||||
const shareButton = i18n.generateTranslationHTML('dialog.Share');
|
||||
const backButton = i18n.generateTranslationHTML('dialog.Back');
|
||||
const linkError
|
||||
= i18n.generateTranslationHTML('dialog.shareVideoLinkError');
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
dialog = APP.UI.messageHandler.openDialogWithStates({
|
||||
state0: {
|
||||
titleKey: 'dialog.shareVideoTitle',
|
||||
html: `
|
||||
<input name='sharedVideoUrl' type='text'
|
||||
class='input-control'
|
||||
data-i18n='[placeholder]defaultLink'
|
||||
autofocus>`,
|
||||
persistent: false,
|
||||
buttons: [
|
||||
{ title: cancelButton,
|
||||
value: false },
|
||||
{ title: shareButton,
|
||||
value: true }
|
||||
],
|
||||
focus: ':input:first',
|
||||
defaultButton: 1,
|
||||
submit(e, v, m, f) { // eslint-disable-line max-params
|
||||
e.preventDefault();
|
||||
if (!v) {
|
||||
reject('cancelled');
|
||||
dialog.close();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const sharedVideoUrl = f.sharedVideoUrl;
|
||||
|
||||
if (!sharedVideoUrl) {
|
||||
return;
|
||||
}
|
||||
|
||||
const urlValue
|
||||
= encodeURI(UIUtil.escapeHtml(sharedVideoUrl));
|
||||
const yVideoId = getYoutubeLink(urlValue);
|
||||
|
||||
if (!yVideoId) {
|
||||
dialog.goToState('state1');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
resolve(yVideoId);
|
||||
dialog.close();
|
||||
}
|
||||
},
|
||||
|
||||
state1: {
|
||||
titleKey: 'dialog.shareVideoTitle',
|
||||
html: linkError,
|
||||
persistent: false,
|
||||
buttons: [
|
||||
{ title: cancelButton,
|
||||
value: false },
|
||||
{ title: backButton,
|
||||
value: true }
|
||||
],
|
||||
focus: ':input:first',
|
||||
defaultButton: 1,
|
||||
submit(e, v) {
|
||||
e.preventDefault();
|
||||
if (v === 0) {
|
||||
reject();
|
||||
dialog.close();
|
||||
} else {
|
||||
dialog.goToState('state0');
|
||||
}
|
||||
}
|
||||
}
|
||||
}, {
|
||||
close() {
|
||||
dialog = null;
|
||||
}
|
||||
}, {
|
||||
url: defaultSharedVideoLink
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -221,6 +221,8 @@ export default class RemoteVideo extends SmallVideo {
|
||||
|
||||
streamElement.autoplay = !config.testing?.noAutoPlayVideo;
|
||||
streamElement.id = `remoteVideo_${stream.getId()}`;
|
||||
streamElement.mute = true;
|
||||
streamElement.playsInline = true;
|
||||
|
||||
// Put new stream element always in front
|
||||
streamElement = UIUtils.prependChild(this.container, streamElement);
|
||||
|
||||
@@ -610,7 +610,7 @@ export class VideoContainer extends LargeContainer {
|
||||
// explicitly disabled.
|
||||
if (interfaceConfig.DISABLE_VIDEO_BACKGROUND
|
||||
|| browser.isFirefox()
|
||||
|| browser.isSafari()) {
|
||||
|| browser.isWebKitBased()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -46,7 +46,14 @@ export class TaskQueue {
|
||||
this._currentTask = this._queue.shift() || null;
|
||||
|
||||
if (this._currentTask) {
|
||||
this._currentTask(this._onTaskComplete);
|
||||
logger.debug('Executing a task.');
|
||||
|
||||
try {
|
||||
this._currentTask(this._onTaskComplete);
|
||||
} catch (error) {
|
||||
logger.error(`Task execution failed: ${error}`);
|
||||
this._onTaskComplete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,6 +65,7 @@ export class TaskQueue {
|
||||
*/
|
||||
_onTaskComplete() {
|
||||
this._currentTask = null;
|
||||
logger.debug('Task completed.');
|
||||
this._executeNext();
|
||||
}
|
||||
}
|
||||
|
||||
154
package-lock.json
generated
@@ -3781,68 +3781,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"@tensorflow-models/body-pix": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@tensorflow-models/body-pix/-/body-pix-2.0.4.tgz",
|
||||
"integrity": "sha512-wRoEZBv2BNORDZjNNRLu1W4Td4/LRbaqSJFVWryHeBcHr5m5xSSETwnDfFRtLLofFtVNHfi7VUZ7TFjkaktNug=="
|
||||
},
|
||||
"@tensorflow/tfjs": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@tensorflow/tfjs/-/tfjs-1.5.1.tgz",
|
||||
"integrity": "sha512-WiE+JQ3ibr5LibGiBz6HWUqLJW8HiX6ywUSCA7ehZ67vFsw4mPuVjv0WEEUfD/l47PkXYVAmWd+RYOJiuZC7Eg==",
|
||||
"requires": {
|
||||
"@tensorflow/tfjs-converter": "1.5.1",
|
||||
"@tensorflow/tfjs-core": "1.5.1",
|
||||
"@tensorflow/tfjs-data": "1.5.1",
|
||||
"@tensorflow/tfjs-layers": "1.5.1"
|
||||
}
|
||||
},
|
||||
"@tensorflow/tfjs-converter": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@tensorflow/tfjs-converter/-/tfjs-converter-1.5.1.tgz",
|
||||
"integrity": "sha512-M9tl2/ep8ntcZpmncHwKuvThsS7TaUWqJ9vJSgJmkazwTfAvlAJmZ8/p1miJ+m5sH1EJO4oTjiEmch6g8IA5IQ=="
|
||||
},
|
||||
"@tensorflow/tfjs-core": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@tensorflow/tfjs-core/-/tfjs-core-1.5.1.tgz",
|
||||
"integrity": "sha512-N4fsi8mLsRwRs8UJN2cARB1rYFxyVXkLyZ4wOusiR976BwwZbCwQrTTSIPzPqYT3rwiexEUzm7sM6ZaDl5dpXA==",
|
||||
"requires": {
|
||||
"@types/offscreencanvas": "~2019.3.0",
|
||||
"@types/seedrandom": "2.4.27",
|
||||
"@types/webgl-ext": "0.0.30",
|
||||
"@types/webgl2": "0.0.4",
|
||||
"node-fetch": "~2.1.2",
|
||||
"seedrandom": "2.4.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"node-fetch": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.1.2.tgz",
|
||||
"integrity": "sha1-q4hOjn5X44qUR1POxwb3iNF2i7U="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@tensorflow/tfjs-data": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@tensorflow/tfjs-data/-/tfjs-data-1.5.1.tgz",
|
||||
"integrity": "sha512-eu4X0tHS1Tng+cvMO9gkMhUWX/UZQ//VpiaZfQJfa3zvUgxw6s1MHJFb0JC1T1FOnEgDVriZ8G758ysJZOybog==",
|
||||
"requires": {
|
||||
"@types/node-fetch": "^2.1.2",
|
||||
"node-fetch": "~2.1.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"node-fetch": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.1.2.tgz",
|
||||
"integrity": "sha1-q4hOjn5X44qUR1POxwb3iNF2i7U="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@tensorflow/tfjs-layers": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@tensorflow/tfjs-layers/-/tfjs-layers-1.5.1.tgz",
|
||||
"integrity": "sha512-DyuhifqflK+bdpBRLAj3RuWm1eTVe8yNX2+WH+W+wmhpjGg7Yagnar6/66JdS2h3WUFoiplCpZRAVMVw631E5g=="
|
||||
},
|
||||
"@types/color-name": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz",
|
||||
@@ -3902,20 +3840,8 @@
|
||||
"@types/node": {
|
||||
"version": "12.7.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.7.5.tgz",
|
||||
"integrity": "sha512-9fq4jZVhPNW8r+UYKnxF1e2HkDWOWKM5bC2/7c9wPV835I0aOrVbS/Hw/pWPk2uKrNXQqg9Z959Kz+IYDd5p3w=="
|
||||
},
|
||||
"@types/node-fetch": {
|
||||
"version": "2.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.4.tgz",
|
||||
"integrity": "sha512-Oz6id++2qAOFuOlE1j0ouk1dzl3mmI1+qINPNBhi9nt/gVOz0G+13Ao6qjhdF0Ys+eOkhu6JnFmt38bR3H0POQ==",
|
||||
"requires": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/offscreencanvas": {
|
||||
"version": "2019.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.3.0.tgz",
|
||||
"integrity": "sha512-esIJx9bQg+QYF0ra8GnvfianIY8qWB0GBx54PK5Eps6m+xTj86KLavHv6qDhzKcu5UUOgNfJ2pWaIIV7TRUd9Q=="
|
||||
"integrity": "sha512-9fq4jZVhPNW8r+UYKnxF1e2HkDWOWKM5bC2/7c9wPV835I0aOrVbS/Hw/pWPk2uKrNXQqg9Z959Kz+IYDd5p3w==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/parse-json": {
|
||||
"version": "4.0.0",
|
||||
@@ -3927,26 +3853,11 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.4.tgz",
|
||||
"integrity": "sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug=="
|
||||
},
|
||||
"@types/seedrandom": {
|
||||
"version": "2.4.27",
|
||||
"resolved": "https://registry.npmjs.org/@types/seedrandom/-/seedrandom-2.4.27.tgz",
|
||||
"integrity": "sha1-nbVjk33YaRX2kJK8QyWdL0hXjkE="
|
||||
},
|
||||
"@types/stack-utils": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz",
|
||||
"integrity": "sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw=="
|
||||
},
|
||||
"@types/webgl-ext": {
|
||||
"version": "0.0.30",
|
||||
"resolved": "https://registry.npmjs.org/@types/webgl-ext/-/webgl-ext-0.0.30.tgz",
|
||||
"integrity": "sha512-LKVgNmBxN0BbljJrVUwkxwRYqzsAEPcZOe6S2T6ZaBDIrFp0qu4FNlpc5sM1tGbXUYFgdVQIoeLk1Y1UoblyEg=="
|
||||
},
|
||||
"@types/webgl2": {
|
||||
"version": "0.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/webgl2/-/webgl2-0.0.4.tgz",
|
||||
"integrity": "sha512-PACt1xdErJbMUOUweSrbVM7gSIYm1vTncW2hF6Os/EeWi6TXYAYMPp+8v6rzHmypE5gHrxaxZNXgMkJVIdZpHw=="
|
||||
},
|
||||
"@types/yargs": {
|
||||
"version": "13.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.8.tgz",
|
||||
@@ -5947,6 +5858,11 @@
|
||||
"resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz",
|
||||
"integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk="
|
||||
},
|
||||
"clipboard-copy": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/clipboard-copy/-/clipboard-copy-4.0.1.tgz",
|
||||
"integrity": "sha512-wOlqdqziE/NNTUJsfSgXmBMIrYmfd5V0HCGsR8uAKHcg+h9NENWINcfRjtWGU77wDHC8B8ijV4hMTGYbrKovng=="
|
||||
},
|
||||
"cliui": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz",
|
||||
@@ -7069,24 +6985,30 @@
|
||||
"integrity": "sha512-T7mYop3aDpRHIQaUYcmzmh6j9MAe560n6ukqjJMbVC6bVTau7dSpvB18bcsBPPtOSe10cKxhJFtlbEzLa0LL1g=="
|
||||
},
|
||||
"elliptic": {
|
||||
"version": "6.5.3",
|
||||
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz",
|
||||
"integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==",
|
||||
"version": "6.5.4",
|
||||
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz",
|
||||
"integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"bn.js": "^4.4.0",
|
||||
"brorand": "^1.0.1",
|
||||
"bn.js": "^4.11.9",
|
||||
"brorand": "^1.1.0",
|
||||
"hash.js": "^1.0.0",
|
||||
"hmac-drbg": "^1.0.0",
|
||||
"inherits": "^2.0.1",
|
||||
"minimalistic-assert": "^1.0.0",
|
||||
"minimalistic-crypto-utils": "^1.0.0"
|
||||
"hmac-drbg": "^1.0.1",
|
||||
"inherits": "^2.0.4",
|
||||
"minimalistic-assert": "^1.0.1",
|
||||
"minimalistic-crypto-utils": "^1.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"bn.js": {
|
||||
"version": "4.11.9",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
|
||||
"integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==",
|
||||
"version": "4.12.0",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
|
||||
"integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==",
|
||||
"dev": true
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
@@ -10343,8 +10265,8 @@
|
||||
}
|
||||
},
|
||||
"lib-jitsi-meet": {
|
||||
"version": "github:jitsi/lib-jitsi-meet#c534f748849a308d08b06e306f5a66709ccae056",
|
||||
"from": "github:jitsi/lib-jitsi-meet#c534f748849a308d08b06e306f5a66709ccae056",
|
||||
"version": "github:jitsi/lib-jitsi-meet#676c7a910505833810314a665ad1e825a158850c",
|
||||
"from": "github:jitsi/lib-jitsi-meet#676c7a910505833810314a665ad1e825a158850c",
|
||||
"requires": {
|
||||
"@jitsi/js-utils": "1.0.2",
|
||||
"@jitsi/sdp-interop": "1.0.3",
|
||||
@@ -10440,9 +10362,9 @@
|
||||
}
|
||||
},
|
||||
"lodash": {
|
||||
"version": "4.17.19",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz",
|
||||
"integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ=="
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
||||
},
|
||||
"lodash.clonedeep": {
|
||||
"version": "4.5.0",
|
||||
@@ -11570,9 +11492,9 @@
|
||||
}
|
||||
},
|
||||
"moment": {
|
||||
"version": "2.19.4",
|
||||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.19.4.tgz",
|
||||
"integrity": "sha512-1xFTAknSLfc47DIxHDUbnJWC+UwgWxATmymaxIPQpmMh7LBm7ZbwVEsuushqwL2GYZU0jie4xO+TK44hJPjNSQ=="
|
||||
"version": "2.29.1",
|
||||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz",
|
||||
"integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ=="
|
||||
},
|
||||
"moment-duration-format": {
|
||||
"version": "2.2.2",
|
||||
@@ -14155,9 +14077,8 @@
|
||||
"integrity": "sha512-iqdJ1KpZbR4XGahgVmaeibB7kDhyMT7wrylINgJaYBY38IAiI0LF32VX1umO4pko6n21YF5I/kSeNQ+OXGqqow=="
|
||||
},
|
||||
"react-native-webrtc": {
|
||||
"version": "1.87.3",
|
||||
"resolved": "https://registry.npmjs.org/react-native-webrtc/-/react-native-webrtc-1.87.3.tgz",
|
||||
"integrity": "sha512-fWnaEHFCFD7YnPR95aaUqLQ5b4dY4av0qHjmwHXeLHGvGrVeWF1je9PNhet7PDHUIJa4GIYKB/8+co51SXm5dA==",
|
||||
"version": "github:react-native-webrtc/react-native-webrtc#1066b92d48048d67ff23288d68ab1734ec364cab",
|
||||
"from": "github:react-native-webrtc/react-native-webrtc#1066b92d48048d67ff23288d68ab1734ec364cab",
|
||||
"requires": {
|
||||
"base64-js": "^1.1.2",
|
||||
"cross-os": "^1.3.0",
|
||||
@@ -14897,11 +14818,6 @@
|
||||
"resolved": "https://registry.npmjs.org/sdp-transform/-/sdp-transform-2.3.0.tgz",
|
||||
"integrity": "sha1-V6lXWUIEHYV3qGnXx01MOgvYiPY="
|
||||
},
|
||||
"seedrandom": {
|
||||
"version": "2.4.3",
|
||||
"resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-2.4.3.tgz",
|
||||
"integrity": "sha1-JDhQTa0zkXMUv/GKxNeU8W1qrsw="
|
||||
},
|
||||
"select-hose": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz",
|
||||
|
||||
16
package.json
@@ -38,11 +38,10 @@
|
||||
"@react-native-community/google-signin": "3.0.1",
|
||||
"@react-native-community/netinfo": "4.1.5",
|
||||
"@svgr/webpack": "4.3.2",
|
||||
"@tensorflow-models/body-pix": "2.0.4",
|
||||
"@tensorflow/tfjs": "1.5.1",
|
||||
"amplitude-js": "7.3.3",
|
||||
"base64-js": "1.3.1",
|
||||
"bc-css-flags": "3.0.0",
|
||||
"clipboard-copy": "4.0.1",
|
||||
"dropbox": "4.0.9",
|
||||
"focus-visible": "5.1.0",
|
||||
"i18n-iso-countries": "3.7.8",
|
||||
@@ -56,10 +55,10 @@
|
||||
"jquery-i18next": "1.2.1",
|
||||
"js-md5": "0.6.1",
|
||||
"jwt-decode": "2.2.0",
|
||||
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#c534f748849a308d08b06e306f5a66709ccae056",
|
||||
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#676c7a910505833810314a665ad1e825a158850c",
|
||||
"libflacjs": "github:mmig/libflac.js#93d37e7f811f01cf7d8b6a603e38bd3c3810907d",
|
||||
"lodash": "4.17.19",
|
||||
"moment": "2.19.4",
|
||||
"lodash": "4.17.21",
|
||||
"moment": "2.29.1",
|
||||
"moment-duration-format": "2.2.2",
|
||||
"olm": "https://packages.matrix.org/npm/olm/olm-3.2.1.tgz",
|
||||
"pixelmatch": "5.1.0",
|
||||
@@ -85,7 +84,7 @@
|
||||
"react-native-svg-transformer": "0.14.3",
|
||||
"react-native-url-polyfill": "1.2.0",
|
||||
"react-native-watch-connectivity": "0.4.3",
|
||||
"react-native-webrtc": "1.87.3",
|
||||
"react-native-webrtc": "github:react-native-webrtc/react-native-webrtc#1066b92d48048d67ff23288d68ab1734ec364cab",
|
||||
"react-native-webview": "11.0.2",
|
||||
"react-native-youtube-iframe": "1.2.3",
|
||||
"react-redux": "7.1.0",
|
||||
@@ -149,7 +148,10 @@
|
||||
"scripts": {
|
||||
"lint": "eslint . && flow",
|
||||
"postinstall": "jetify",
|
||||
"validate": "npm ls"
|
||||
"validate": "npm ls",
|
||||
"start": "make dev",
|
||||
"ios": "react-native run-ios",
|
||||
"android": "react-native run-android"
|
||||
},
|
||||
"browser": {
|
||||
"jQuery-Impromptu": "jQuery-Impromptu/src/jquery-impromptu.js"
|
||||
|
||||
@@ -52,7 +52,7 @@ export default class Toolbar extends Component<Props> {
|
||||
onMouseOut = { onMouseOut }
|
||||
onMouseOver = { onMouseOver }>
|
||||
<AudioMuteButton />
|
||||
<HangupButton />
|
||||
<HangupButton customClass = 'hangup-button' />
|
||||
<VideoMuteButton />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -504,15 +504,17 @@ export function createRejoinedEvent({ url, lastConferenceDuration, timeSinceLeft
|
||||
*
|
||||
* @param {string} participantId - The ID of the participant that was remotely
|
||||
* muted.
|
||||
* @param {string} mediaType - The media type of the channel to mute.
|
||||
* @returns {Object} The event in a format suitable for sending via
|
||||
* sendAnalytics.
|
||||
*/
|
||||
export function createRemoteMuteConfirmedEvent(participantId) {
|
||||
export function createRemoteMuteConfirmedEvent(participantId, mediaType) {
|
||||
return {
|
||||
action: 'clicked',
|
||||
actionSubject: 'remote.mute.dialog.confirm.button',
|
||||
attributes: {
|
||||
'participant_id': participantId
|
||||
'participant_id': participantId,
|
||||
'media_type': mediaType
|
||||
},
|
||||
source: 'remote.mute.dialog',
|
||||
type: TYPE_UI
|
||||
|
||||
@@ -13,6 +13,6 @@ import '../mobile/proximity/middleware';
|
||||
import '../mobile/wake-lock/middleware';
|
||||
import '../mobile/watchos/middleware';
|
||||
import '../share-room/middleware';
|
||||
import '../youtube-player/middleware';
|
||||
import '../shared-video/middleware';
|
||||
|
||||
import './middlewares.any';
|
||||
|
||||
@@ -25,7 +25,6 @@ import '../base/testing/reducer';
|
||||
import '../base/tracks/reducer';
|
||||
import '../base/user-interaction/reducer';
|
||||
import '../billing-counter/reducer';
|
||||
import '../blur/reducer';
|
||||
import '../calendar-sync/reducer';
|
||||
import '../chat/reducer';
|
||||
import '../deep-linking/reducer';
|
||||
|
||||
@@ -8,6 +8,6 @@ import '../mobile/external-api/reducer';
|
||||
import '../mobile/full-screen/reducer';
|
||||
import '../mobile/incoming-call/reducer';
|
||||
import '../mobile/watchos/reducer';
|
||||
import '../youtube-player/reducer';
|
||||
import '../shared-video/reducer';
|
||||
|
||||
import './reducers.any';
|
||||
|
||||
@@ -12,5 +12,5 @@ import '../remote-control/reducer';
|
||||
import '../screenshot-capture/reducer';
|
||||
import '../shared-video/reducer';
|
||||
import '../talk-while-muted/reducer';
|
||||
|
||||
import '../virtual-background/reducer';
|
||||
import './reducers.any';
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { translate } from '../../base/i18n';
|
||||
import { Icon, IconCheck, IconCopy } from '../../base/icons';
|
||||
import { copyText } from '../../base/util';
|
||||
import { translate } from '../i18n';
|
||||
import { copyText } from '../util';
|
||||
|
||||
|
||||
type Props = {
|
||||
@@ -49,9 +49,12 @@ function CopyButton({ className, displayedText, textToCopy, textOnHover, textOnC
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
function onClick() {
|
||||
async function onClick() {
|
||||
setIsHovered(false);
|
||||
if (copyText(textToCopy)) {
|
||||
|
||||
const isCopied = await copyText(textToCopy);
|
||||
|
||||
if (isCopied) {
|
||||
setIsClicked(true);
|
||||
|
||||
setTimeout(() => {
|
||||
|
||||
@@ -55,6 +55,6 @@ export default {
|
||||
button: 'rgb(255, 255, 255)',
|
||||
buttonToggled: 'rgb(38, 58, 76)',
|
||||
buttonToggledBorder: getRGBAFormat('#a4b8d1', 0.6),
|
||||
hangup: 'rgb(225, 45, 45)'
|
||||
hangup: 'rgb(227,79,86)'
|
||||
}
|
||||
};
|
||||
|
||||
@@ -10,7 +10,7 @@ import { getName } from '../../app/functions';
|
||||
import { endpointMessageReceived } from '../../subtitles';
|
||||
import { JITSI_CONNECTION_CONFERENCE_KEY } from '../connection';
|
||||
import { JitsiConferenceEvents } from '../lib-jitsi-meet';
|
||||
import { setAudioMuted, setVideoMuted } from '../media';
|
||||
import { MEDIA_TYPE, setAudioMuted, setVideoMuted } from '../media';
|
||||
import {
|
||||
dominantSpeakerChanged,
|
||||
getLocalParticipant,
|
||||
@@ -22,7 +22,7 @@ import {
|
||||
participantRoleChanged,
|
||||
participantUpdated
|
||||
} from '../participants';
|
||||
import { getLocalTracks, trackAdded, trackRemoved } from '../tracks';
|
||||
import { getLocalTracks, replaceLocalTrack, trackAdded, trackRemoved } from '../tracks';
|
||||
import {
|
||||
getBackendSafePath,
|
||||
getBackendSafeRoomName,
|
||||
@@ -72,10 +72,11 @@ declare var APP: Object;
|
||||
*
|
||||
* @param {JitsiConference} conference - The JitsiConference instance.
|
||||
* @param {Dispatch} dispatch - The Redux dispatch function.
|
||||
* @param {Object} state - The Redux state.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
function _addConferenceListeners(conference, dispatch) {
|
||||
function _addConferenceListeners(conference, dispatch, state) {
|
||||
// A simple logger for conference errors received through
|
||||
// the listener. These errors are not handled now, but logged.
|
||||
conference.on(JitsiConferenceEvents.CONFERENCE_ERROR,
|
||||
@@ -118,13 +119,12 @@ function _addConferenceListeners(conference, dispatch) {
|
||||
conference.on(
|
||||
JitsiConferenceEvents.STARTED_MUTED,
|
||||
() => {
|
||||
const audioMuted = Boolean(conference.startAudioMuted);
|
||||
const videoMuted = Boolean(conference.startVideoMuted);
|
||||
const audioMuted = Boolean(conference.isStartAudioMuted());
|
||||
const videoMuted = Boolean(conference.isStartVideoMuted());
|
||||
const localTracks = getLocalTracks(state['features/base/tracks']);
|
||||
|
||||
sendAnalytics(createStartMutedConfigurationEvent(
|
||||
'remote', audioMuted, videoMuted));
|
||||
logger.log(`Start muted: ${audioMuted ? 'audio, ' : ''}${
|
||||
videoMuted ? 'video' : ''}`);
|
||||
sendAnalytics(createStartMutedConfigurationEvent('remote', audioMuted, videoMuted));
|
||||
logger.log(`Start muted: ${audioMuted ? 'audio, ' : ''}${videoMuted ? 'video' : ''}`);
|
||||
|
||||
// XXX Jicofo tells lib-jitsi-meet to start with audio and/or video
|
||||
// muted i.e. Jicofo expresses an intent. Lib-jitsi-meet has turned
|
||||
@@ -136,6 +136,14 @@ function _addConferenceListeners(conference, dispatch) {
|
||||
// acting on Jicofo's intent without the app's knowledge.
|
||||
dispatch(setAudioMuted(audioMuted));
|
||||
dispatch(setVideoMuted(videoMuted));
|
||||
|
||||
// Remove the tracks from peerconnection as well.
|
||||
for (const track of localTracks) {
|
||||
if ((audioMuted && track.jitsiTrack.getType() === MEDIA_TYPE.AUDIO)
|
||||
|| (videoMuted && track.jitsiTrack.getType() === MEDIA_TYPE.VIDEO)) {
|
||||
replaceLocalTrack(track.jitsiTrack, null, conference);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Dispatches into features/base/tracks follow:
|
||||
@@ -149,9 +157,9 @@ function _addConferenceListeners(conference, dispatch) {
|
||||
|
||||
conference.on(
|
||||
JitsiConferenceEvents.TRACK_MUTE_CHANGED,
|
||||
(_, participantThatMutedUs) => {
|
||||
(track, participantThatMutedUs) => {
|
||||
if (participantThatMutedUs) {
|
||||
dispatch(participantMutedUs(participantThatMutedUs));
|
||||
dispatch(participantMutedUs(participantThatMutedUs, track));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -448,7 +456,7 @@ export function createConference() {
|
||||
|
||||
dispatch(_conferenceWillJoin(conference));
|
||||
|
||||
_addConferenceListeners(conference, dispatch);
|
||||
_addConferenceListeners(conference, dispatch, state);
|
||||
|
||||
sendLocalParticipant(state, conference);
|
||||
|
||||
|
||||
@@ -48,3 +48,14 @@ export const SET_CONFIG = 'SET_CONFIG';
|
||||
* }
|
||||
*/
|
||||
export const UPDATE_CONFIG = 'UPDATE_CONFIG';
|
||||
|
||||
/**
|
||||
* The redux action which overwrites configurations represented by the feature
|
||||
* base/config. The passed on config values overwrite the current values for given props.
|
||||
*
|
||||
* {
|
||||
* type: OVERWRITE_CONFIG,
|
||||
* config: Object
|
||||
* }
|
||||
*/
|
||||
export const OVERWRITE_CONFIG = 'OVERWRITE_CONFIG';
|
||||
|
||||
@@ -6,7 +6,13 @@ import type { Dispatch } from 'redux';
|
||||
import { addKnownDomains } from '../known-domains';
|
||||
import { parseURIString } from '../util';
|
||||
|
||||
import { CONFIG_WILL_LOAD, LOAD_CONFIG_ERROR, SET_CONFIG, UPDATE_CONFIG } from './actionTypes';
|
||||
import {
|
||||
CONFIG_WILL_LOAD,
|
||||
LOAD_CONFIG_ERROR,
|
||||
SET_CONFIG,
|
||||
UPDATE_CONFIG,
|
||||
OVERWRITE_CONFIG
|
||||
} from './actionTypes';
|
||||
import { _CONFIG_STORE_PREFIX } from './constants';
|
||||
import { setConfigFromURLParams } from './functions';
|
||||
|
||||
@@ -67,6 +73,22 @@ export function loadConfigError(error: Error, locationURL: URL) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Overwrites some config values.
|
||||
*
|
||||
* @param {Object} config - The new options (to overwrite).
|
||||
* @returns {{
|
||||
* type: OVERWRITE_CONFIG,
|
||||
* config: Object
|
||||
* }}
|
||||
*/
|
||||
export function overwriteConfig(config: Object) {
|
||||
return {
|
||||
type: OVERWRITE_CONFIG,
|
||||
config
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the configuration represented by the feature base/config. The
|
||||
* configuration is defined and consumed by the library lib-jitsi-meet but some
|
||||
|
||||
@@ -157,6 +157,8 @@ export default [
|
||||
'stereo',
|
||||
'subject',
|
||||
'testing',
|
||||
'toolbarButtons',
|
||||
'useHostPageLocalStorage',
|
||||
'useTurnUdp',
|
||||
'videoQuality.persist',
|
||||
'webrtcIceTcpDisable',
|
||||
|
||||
@@ -6,3 +6,17 @@
|
||||
* @type string
|
||||
*/
|
||||
export const _CONFIG_STORE_PREFIX = 'config.js';
|
||||
|
||||
/**
|
||||
* The list of all possible UI buttons.
|
||||
*
|
||||
* @protected
|
||||
* @type Array<string>
|
||||
*/
|
||||
export const TOOLBAR_BUTTONS = [
|
||||
'microphone', 'camera', 'closedcaptions', 'desktop', 'embedmeeting', 'fullscreen',
|
||||
'fodeviceselection', 'hangup', 'profile', 'chat', 'recording',
|
||||
'livestreaming', 'etherpad', 'sharedvideo', 'settings', 'raisehand',
|
||||
'videoquality', 'filmstrip', 'invite', 'feedback', 'stats', 'shortcuts',
|
||||
'tileview', 'select-background', 'download', 'help', 'mute-everyone', 'mute-video-everyone', 'security'
|
||||
];
|
||||
|
||||
@@ -82,7 +82,7 @@ export function overrideConfigJSON(
|
||||
}
|
||||
if (configObj) {
|
||||
const configJSON
|
||||
= _getWhitelistedJSON(configName, json[configName]);
|
||||
= getWhitelistedJSON(configName, json[configName]);
|
||||
|
||||
if (!_.isEmpty(configJSON)) {
|
||||
logger.info(
|
||||
@@ -111,11 +111,10 @@ export function overrideConfigJSON(
|
||||
* @param {string} configName - The config name, one of config,
|
||||
* interfaceConfig, loggingConfig.
|
||||
* @param {Object} configJSON - The object with keys and values to override.
|
||||
* @private
|
||||
* @returns {Object} - The result object only with the keys
|
||||
* that are whitelisted.
|
||||
*/
|
||||
function _getWhitelistedJSON(configName, configJSON) {
|
||||
export function getWhitelistedJSON(configName: string, configJSON: Object): Object {
|
||||
if (configName === 'interfaceConfig') {
|
||||
return _.pick(configJSON, INTERFACE_CONFIG_WHITELIST);
|
||||
} else if (configName === 'config') {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
// @flow
|
||||
|
||||
import { TOOLBAR_BUTTONS } from './constants';
|
||||
|
||||
export * from './functions.any';
|
||||
|
||||
/**
|
||||
@@ -30,3 +32,15 @@ export function getDialOutStatusUrl(state: Object): string {
|
||||
export function getDialOutUrl(state: Object): string {
|
||||
return state['features/base/config'].guestDialOutUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of enabled toolbar buttons.
|
||||
*
|
||||
* @param {Object} state - The redux state.
|
||||
* @returns {Array<string>} - The list of enabled toolbar buttons.
|
||||
*/
|
||||
export function getToolbarButtons(state: Object): Array<string> {
|
||||
const { toolbarButtons } = state['features/base/config'];
|
||||
|
||||
return Array.isArray(toolbarButtons) ? toolbarButtons : TOOLBAR_BUTTONS;
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@ export default [
|
||||
'RECENT_LIST_ENABLED',
|
||||
'REMOTE_THUMBNAIL_RATIO',
|
||||
'SETTINGS_SECTIONS',
|
||||
'SHARING_FEATURES',
|
||||
'SHOW_CHROME_EXTENSION_BANNER',
|
||||
'SHOW_DEEP_LINKING_IMAGE',
|
||||
'SHOW_POWERED_BY',
|
||||
|
||||
@@ -4,9 +4,17 @@ import _ from 'lodash';
|
||||
|
||||
import { equals, ReducerRegistry, set } from '../redux';
|
||||
|
||||
import { UPDATE_CONFIG, CONFIG_WILL_LOAD, LOAD_CONFIG_ERROR, SET_CONFIG } from './actionTypes';
|
||||
import {
|
||||
UPDATE_CONFIG,
|
||||
CONFIG_WILL_LOAD,
|
||||
LOAD_CONFIG_ERROR,
|
||||
SET_CONFIG,
|
||||
OVERWRITE_CONFIG
|
||||
} from './actionTypes';
|
||||
import { _cleanupConfig } from './functions';
|
||||
|
||||
declare var interfaceConfig: Object;
|
||||
|
||||
/**
|
||||
* The initial state of the feature base/config when executing in a
|
||||
* non-React Native environment. The mandatory configuration to be passed to
|
||||
@@ -88,6 +96,12 @@ ReducerRegistry.register('features/base/config', (state = _getInitialState(), ac
|
||||
|
||||
case SET_CONFIG:
|
||||
return _setConfig(state, action);
|
||||
|
||||
case OVERWRITE_CONFIG:
|
||||
return {
|
||||
...state,
|
||||
...action.config
|
||||
};
|
||||
}
|
||||
|
||||
return state;
|
||||
@@ -197,6 +211,10 @@ function _translateLegacyConfig(oldValue: Object) {
|
||||
}
|
||||
});
|
||||
|
||||
if (typeof interfaceConfig === 'object' && Array.isArray(interfaceConfig.TOOLBAR_BUTTONS)) {
|
||||
newValue.toolbarButtons = interfaceConfig.TOOLBAR_BUTTONS;
|
||||
}
|
||||
|
||||
return newValue;
|
||||
}
|
||||
|
||||
|
||||
@@ -53,6 +53,11 @@ type Props = {
|
||||
*/
|
||||
hideCancelButton: boolean,
|
||||
|
||||
/**
|
||||
* If true, no footer will be displayed.
|
||||
*/
|
||||
disableFooter?: boolean,
|
||||
|
||||
i18n: Object,
|
||||
|
||||
/**
|
||||
@@ -174,6 +179,10 @@ class StatelessDialog extends Component<Props> {
|
||||
this._renderCancelButton()
|
||||
].filter(Boolean);
|
||||
|
||||
if (this.props.disableFooter) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<ModalFooter showKeyline = { propsFromModalFooter.showKeyline } >
|
||||
{
|
||||
|
||||
@@ -92,7 +92,7 @@ export function isSupportedBrowser() {
|
||||
export function isSupportedMobileBrowser() {
|
||||
return (Platform.OS === 'android' && browser.isChromiumBased())
|
||||
|| (Platform.OS === 'android' && browser.isFirefox())
|
||||
|| (Platform.OS === 'ios' && browser.isSafari());
|
||||
|| (Platform.OS === 'ios' && browser.isWebKitBased());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -11,6 +11,16 @@ export function isMobileBrowser() {
|
||||
return Platform.OS === 'android' || Platform.OS === 'ios';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether or not the current environment is an ios mobile device.
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isIosMobileBrowser() {
|
||||
return Platform.OS === 'ios';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the chrome extensions defined in the config file are installed or not.
|
||||
*
|
||||
|
||||
@@ -11,7 +11,7 @@ export const ADD_PEOPLE_ENABLED = 'add-people.enabled';
|
||||
* Used by apps that do not use Jitsi audio.
|
||||
* Default: disabled (false)
|
||||
*/
|
||||
export const ANDROID_AUDIO_FOCUS_DISABLED = 'android.audio-focus.disabled';
|
||||
export const AUDIO_FOCUS_DISABLED = 'audio-focus.disabled';
|
||||
|
||||
/**
|
||||
* Flag indicating if the audio mute button should be displayed.
|
||||
@@ -56,6 +56,12 @@ export const CHAT_ENABLED = 'chat.enabled';
|
||||
*/
|
||||
export const FILMSTRIP_ENABLED = 'filmstrip.enabled';
|
||||
|
||||
/**
|
||||
* Flag indicating if fullscreen (immersive) mode should be enabled.
|
||||
* Default: enabled (true).
|
||||
*/
|
||||
export const FULLSCREEN_ENABLED = 'fullscreen.enabled';
|
||||
|
||||
/**
|
||||
* Flag indicating if the Help button should be enabled.
|
||||
* Default: enabled (true).
|
||||
|
||||
@@ -11,7 +11,7 @@ import { toState } from '../redux';
|
||||
* @param {string} flag - The name of the React {@code Component} prop of
|
||||
* the currently mounted {@code App} to get.
|
||||
* @param {*} defaultValue - A default value for the flag, in case it's not defined.
|
||||
* @returns {*} The value of the specified React {@code Compoennt} prop of the
|
||||
* @returns {*} The value of the specified React {@code Component} prop of the
|
||||
* currently mounted {@code App}.
|
||||
*/
|
||||
export function getFeatureFlag(stateful: Function | Object, flag: string, defaultValue: any) {
|
||||
|
||||
@@ -36,8 +36,7 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
// Fix language format (en-US => enUS)
|
||||
found = found.map<string>(f => f.replace(/[-_]+/g, ''));
|
||||
found = found.map<string>(normalizeLanguage);
|
||||
|
||||
return found.length > 0 ? found : undefined;
|
||||
},
|
||||
@@ -47,3 +46,23 @@ export default {
|
||||
*/
|
||||
name: 'customNavigatorDetector'
|
||||
};
|
||||
|
||||
/**
|
||||
* Normalize language format.
|
||||
*
|
||||
* (en-US => enUS)
|
||||
* (en-gb => enGB)
|
||||
* (es-es => es).
|
||||
*
|
||||
* @param {string} language - Language.
|
||||
* @returns {string} The normalized language.
|
||||
*/
|
||||
function normalizeLanguage(language) {
|
||||
const [ lang, variant ] = language.replace('_', '-').split('-');
|
||||
|
||||
if (!variant || lang === variant) {
|
||||
return lang;
|
||||
}
|
||||
|
||||
return lang + variant.toUpperCase();
|
||||
}
|
||||
|
||||
@@ -10,27 +10,57 @@ import 'moment-duration-format';
|
||||
// MomentJS uses static language bundle loading, so in order to support dynamic
|
||||
// language selection in the app we need to load all bundles that we support in
|
||||
// the app.
|
||||
require('moment/locale/af');
|
||||
require('moment/locale/ar');
|
||||
require('moment/locale/be');
|
||||
require('moment/locale/bg');
|
||||
require('moment/locale/ca');
|
||||
require('moment/locale/cs');
|
||||
require('moment/locale/da');
|
||||
require('moment/locale/de');
|
||||
require('moment/locale/el');
|
||||
require('moment/locale/en-gb');
|
||||
require('moment/locale/eo');
|
||||
require('moment/locale/es-us');
|
||||
require('moment/locale/es');
|
||||
require('moment/locale/et');
|
||||
require('moment/locale/eu');
|
||||
require('moment/locale/fa');
|
||||
require('moment/locale/fi');
|
||||
require('moment/locale/fr-ca');
|
||||
require('moment/locale/fr');
|
||||
require('moment/locale/gl');
|
||||
require('moment/locale/he');
|
||||
require('moment/locale/hr');
|
||||
require('moment/locale/hu');
|
||||
require('moment/locale/hy-am');
|
||||
require('moment/locale/id');
|
||||
require('moment/locale/is');
|
||||
require('moment/locale/it');
|
||||
require('moment/locale/ja');
|
||||
require('moment/locale/ko');
|
||||
require('moment/locale/lt');
|
||||
require('moment/locale/lv');
|
||||
require('moment/locale/ml');
|
||||
require('moment/locale/mn');
|
||||
require('moment/locale/mr');
|
||||
require('moment/locale/nb');
|
||||
|
||||
// OC is not available. Please submit OC translation to the MomentJS project.
|
||||
|
||||
require('moment/locale/nl');
|
||||
require('moment/locale/oc-lnc');
|
||||
require('moment/locale/pl');
|
||||
require('moment/locale/pt');
|
||||
require('moment/locale/pt-br');
|
||||
require('moment/locale/ro');
|
||||
require('moment/locale/ru');
|
||||
require('moment/locale/sk');
|
||||
require('moment/locale/sl');
|
||||
require('moment/locale/sr');
|
||||
require('moment/locale/sv');
|
||||
require('moment/locale/tr');
|
||||
require('moment/locale/uk');
|
||||
require('moment/locale/vi');
|
||||
require('moment/locale/zh-cn');
|
||||
require('moment/locale/zh-tw');
|
||||
|
||||
/**
|
||||
* Returns a localized date formatter initialized with a specific {@code Date}
|
||||
|
||||
@@ -44,7 +44,7 @@ type Props = {
|
||||
};
|
||||
|
||||
export const DEFAULT_COLOR = navigator.product === 'ReactNative' ? 'white' : undefined;
|
||||
export const DEFAULT_SIZE = navigator.product === 'ReactNative' ? 36 : 24;
|
||||
export const DEFAULT_SIZE = navigator.product === 'ReactNative' ? 36 : 22;
|
||||
|
||||
/**
|
||||
* Implements an Icon component that takes a loaded SVG file as prop and renders it as an icon.
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
<svg width="10" height="7" viewBox="0 0 10 7" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.41115 6.05746C8.71903 6.39955 9.24594 6.42729 9.58803 6.1194C9.93012 5.81152 9.95786 5.28461 9.64997 4.94252L5.72917 0.562752C5.39813 0.194935 4.82138 0.194935 4.49034 0.562752L0.63061 4.94252C0.322728 5.28461 0.35046 5.81152 0.692552 6.1194C1.03464 6.42729 1.56155 6.39955 1.86943 6.05746L5.10975 2.36593L8.41115 6.05746Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.41115 6.05746C8.71903 6.39955 9.24594 6.42729 9.58803 6.1194C9.93012 5.81152 9.95786 5.28461 9.64997 4.94252L5.72917 0.562752C5.39813 0.194935 4.82138 0.194935 4.49034 0.562752L0.63061 4.94252C0.322728 5.28461 0.35046 5.81152 0.692552 6.1194C1.03464 6.42729 1.56155 6.39955 1.86943 6.05746L5.10975 2.36593L8.41115 6.05746Z" />
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 492 B After Width: | Height: | Size: 480 B |
@@ -1,16 +1,3 @@
|
||||
<!-- Generated by IcoMoon.io -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
|
||||
<title>blur-background</title>
|
||||
<path fill="#a4b8d1" d="M14.667 12c0 1.473-1.194 2.667-2.667 2.667s-2.667-1.194-2.667-2.667c0-1.473 1.194-2.667 2.667-2.667s2.667 1.194 2.667 2.667z"></path>
|
||||
<path fill="#a4b8d1" d="M22.667 12c0 1.473-1.194 2.667-2.667 2.667s-2.667-1.194-2.667-2.667c0-1.473 1.194-2.667 2.667-2.667s2.667 1.194 2.667 2.667z"></path>
|
||||
<path fill="#a4b8d1" d="M14.667 20c0 1.473-1.194 2.667-2.667 2.667s-2.667-1.194-2.667-2.667c0-1.473 1.194-2.667 2.667-2.667s2.667 1.194 2.667 2.667z"></path>
|
||||
<path fill="#a4b8d1" d="M13.333 26.667c0 0.736-0.597 1.333-1.333 1.333s-1.333-0.597-1.333-1.333c0-0.736 0.597-1.333 1.333-1.333s1.333 0.597 1.333 1.333z"></path>
|
||||
<path fill="#a4b8d1" d="M21.333 26.667c0 0.736-0.597 1.333-1.333 1.333s-1.333-0.597-1.333-1.333c0-0.736 0.597-1.333 1.333-1.333s1.333 0.597 1.333 1.333z"></path>
|
||||
<path fill="#a4b8d1" d="M6.667 20c0 0.736-0.597 1.333-1.333 1.333s-1.333-0.597-1.333-1.333c0-0.736 0.597-1.333 1.333-1.333s1.333 0.597 1.333 1.333z"></path>
|
||||
<path fill="#a4b8d1" d="M6.667 12c0 0.736-0.597 1.333-1.333 1.333s-1.333-0.597-1.333-1.333c0-0.736 0.597-1.333 1.333-1.333s1.333 0.597 1.333 1.333z"></path>
|
||||
<path fill="#a4b8d1" d="M28 20c0 0.736-0.597 1.333-1.333 1.333s-1.333-0.597-1.333-1.333c0-0.736 0.597-1.333 1.333-1.333s1.333 0.597 1.333 1.333z"></path>
|
||||
<path fill="#a4b8d1" d="M28 12c0 0.736-0.597 1.333-1.333 1.333s-1.333-0.597-1.333-1.333c0-0.736 0.597-1.333 1.333-1.333s1.333 0.597 1.333 1.333z"></path>
|
||||
<path fill="#a4b8d1" d="M13.333 5.333c0 0.736-0.597 1.333-1.333 1.333s-1.333-0.597-1.333-1.333c0-0.736 0.597-1.333 1.333-1.333s1.333 0.597 1.333 1.333z"></path>
|
||||
<path fill="#a4b8d1" d="M21.333 5.333c0 0.736-0.597 1.333-1.333 1.333s-1.333-0.597-1.333-1.333c0-0.736 0.597-1.333 1.333-1.333s1.333 0.597 1.333 1.333z"></path>
|
||||
<path fill="#a4b8d1" d="M22.667 20c0 1.473-1.194 2.667-2.667 2.667s-2.667-1.194-2.667-2.667c0-1.473 1.194-2.667 2.667-2.667s2.667 1.194 2.667 2.667z"></path>
|
||||
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.25 4.58333C8.75626 4.58333 9.16667 4.17293 9.16667 3.66667C9.16667 3.16041 8.75626 2.75 8.25 2.75C7.74374 2.75 7.33333 3.16041 7.33333 3.66667C7.33333 4.17293 7.74374 4.58333 8.25 4.58333ZM8.25 10.0833C9.26252 10.0833 10.0833 9.26252 10.0833 8.25C10.0833 7.23748 9.26252 6.41667 8.25 6.41667C7.23748 6.41667 6.41667 7.23748 6.41667 8.25C6.41667 9.26252 7.23748 10.0833 8.25 10.0833ZM13.75 10.0833C14.7625 10.0833 15.5833 9.26252 15.5833 8.25C15.5833 7.23748 14.7625 6.41667 13.75 6.41667C12.7375 6.41667 11.9167 7.23748 11.9167 8.25C11.9167 9.26252 12.7375 10.0833 13.75 10.0833ZM10.0833 13.75C10.0833 14.7625 9.26252 15.5833 8.25 15.5833C7.23748 15.5833 6.41667 14.7625 6.41667 13.75C6.41667 12.7375 7.23748 11.9167 8.25 11.9167C9.26252 11.9167 10.0833 12.7375 10.0833 13.75ZM8.25 19.25C8.75626 19.25 9.16667 18.8396 9.16667 18.3333C9.16667 17.8271 8.75626 17.4167 8.25 17.4167C7.74374 17.4167 7.33333 17.8271 7.33333 18.3333C7.33333 18.8396 7.74374 19.25 8.25 19.25ZM14.6667 18.3333C14.6667 18.8396 14.2563 19.25 13.75 19.25C13.2437 19.25 12.8333 18.8396 12.8333 18.3333C12.8333 17.8271 13.2437 17.4167 13.75 17.4167C14.2563 17.4167 14.6667 17.8271 14.6667 18.3333ZM3.66667 14.6667C4.17293 14.6667 4.58333 14.2563 4.58333 13.75C4.58333 13.2437 4.17293 12.8333 3.66667 12.8333C3.16041 12.8333 2.75 13.2437 2.75 13.75C2.75 14.2563 3.16041 14.6667 3.66667 14.6667ZM4.58333 8.25C4.58333 8.75626 4.17293 9.16667 3.66667 9.16667C3.16041 9.16667 2.75 8.75626 2.75 8.25C2.75 7.74374 3.16041 7.33333 3.66667 7.33333C4.17293 7.33333 4.58333 7.74374 4.58333 8.25ZM18.3333 14.6667C18.8396 14.6667 19.25 14.2563 19.25 13.75C19.25 13.2437 18.8396 12.8333 18.3333 12.8333C17.8271 12.8333 17.4167 13.2437 17.4167 13.75C17.4167 14.2563 17.8271 14.6667 18.3333 14.6667ZM19.25 8.25C19.25 8.75626 18.8396 9.16667 18.3333 9.16667C17.8271 9.16667 17.4167 8.75626 17.4167 8.25C17.4167 7.74374 17.8271 7.33333 18.3333 7.33333C18.8396 7.33333 19.25 7.74374 19.25 8.25ZM14.6667 3.66667C14.6667 4.17293 14.2563 4.58333 13.75 4.58333C13.2437 4.58333 12.8333 4.17293 12.8333 3.66667C12.8333 3.16041 13.2437 2.75 13.75 2.75C14.2563 2.75 14.6667 3.16041 14.6667 3.66667ZM13.75 15.5833C14.7625 15.5833 15.5833 14.7625 15.5833 13.75C15.5833 12.7375 14.7625 11.9167 13.75 11.9167C12.7375 11.9167 11.9167 12.7375 11.9167 13.75C11.9167 14.7625 12.7375 15.5833 13.75 15.5833Z"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.4 KiB |
3
react/features/base/icons/svg/camera-empty-disabled.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.84074 5.49992H6.81762L3.42398 2.10629C3.06002 1.74232 2.47001 1.74223 2.10617 2.10608C1.74232 2.46992 1.74241 3.05993 2.10638 3.42389L4.1824 5.49992H3.66668C2.65415 5.49992 1.83334 6.32073 1.83334 7.33325V14.6666C1.83334 15.6791 2.65415 16.4999 3.66668 16.4999H13.75C14.154 16.4999 14.5274 16.3693 14.8304 16.1479L18.576 19.8936C18.94 20.2575 19.53 20.2576 19.8939 19.8938C20.2577 19.5299 20.2576 18.9399 19.8936 18.5759L15.5833 14.2656V14.2425L13.75 12.4092V12.4323L8.65095 7.33325H8.67407L6.84074 5.49992ZM13.75 9.77398V9.16659V7.33325H11.3093L9.47595 5.49992H13.75C14.7625 5.49992 15.5833 6.32073 15.5833 7.33325V8.11897L18.7952 6.28361C19.2348 6.03243 19.7947 6.18515 20.0459 6.62471C20.125 6.76321 20.1667 6.91998 20.1667 7.0795V14.9203C20.1667 15.2643 19.9772 15.5641 19.6969 15.7209L15.9614 11.9853L18.3333 13.3408V8.65908L15.5833 10.2305V11.6073L13.75 9.77398ZM3.66668 7.33325H6.01574L13.3491 14.6666H3.66668V7.33325Z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
3
react/features/base/icons/svg/camera-empty.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.75 5.5H3.66668C2.65415 5.5 1.83334 6.32081 1.83334 7.33333V14.6667C1.83334 15.6792 2.65415 16.5 3.66668 16.5H13.75C14.7625 16.5 15.5833 15.6792 15.5833 14.6667V13.881L18.7952 15.7163C18.9337 15.7955 19.0905 15.8371 19.25 15.8371C19.7563 15.8371 20.1667 15.4267 20.1667 14.9204V7.07958C20.1667 6.92006 20.125 6.76329 20.0459 6.62479C19.7947 6.18523 19.2348 6.03252 18.7952 6.28369L15.5833 8.11905V7.33333C15.5833 6.32081 14.7625 5.5 13.75 5.5ZM13.75 9.16667V12.8333V14.6667H3.66668V7.33333H13.75V9.16667ZM18.3333 13.3408L15.5833 11.7694V10.2306L18.3333 8.65916V13.3408Z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 729 B |
@@ -1,6 +1,3 @@
|
||||
<!-- Generated by IcoMoon.io -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
|
||||
<title>camera-take-picture</title>
|
||||
<path d="M22.667 16c0 3.682-2.985 6.667-6.667 6.667s-6.667-2.985-6.667-6.667c0-3.682 2.985-6.667 6.667-6.667s6.667 2.985 6.667 6.667z"></path>
|
||||
<path d="M16 24c4.418 0 8-3.582 8-8s-3.582-8-8-8v0c-4.418 0-8 3.582-8 8s3.582 8 8 8v0zM16 25.333c-5.155 0-9.333-4.179-9.333-9.333s4.179-9.333 9.333-9.333v0c5.155 0 9.333 4.179 9.333 9.333s-4.179 9.333-9.333 9.333v0z"></path>
|
||||
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M11 20.1666C5.93743 20.1666 1.83337 16.0626 1.83337 11C1.83337 5.93737 5.93743 1.83331 11 1.83331C16.0627 1.83331 20.1667 5.93737 20.1667 11C20.1667 16.0626 16.0627 20.1666 11 20.1666ZM11 18.3333C15.0501 18.3333 18.3334 15.0501 18.3334 11C18.3334 6.94989 15.0501 3.66665 11 3.66665C6.94995 3.66665 3.66671 6.94989 3.66671 11C3.66671 15.0501 6.94995 18.3333 11 18.3333ZM15.5834 11C15.5834 13.5313 13.5313 15.5833 11 15.5833C8.46874 15.5833 6.41671 13.5313 6.41671 11C6.41671 8.46867 8.46874 6.41665 11 6.41665C13.5313 6.41665 15.5834 8.46867 15.5834 11Z" />
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 541 B After Width: | Height: | Size: 709 B |