mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2026-01-03 21:32:28 +00:00
Compare commits
118 Commits
base-sessi
...
3428
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6d8ec4d147 | ||
|
|
d65a068fdb | ||
|
|
cf23045f8d | ||
|
|
e47d2d13ce | ||
|
|
07b7f03aa7 | ||
|
|
7ce44f85ca | ||
|
|
41e0d782ce | ||
|
|
2a8fafdd36 | ||
|
|
faee1c139e | ||
|
|
eb644987ce | ||
|
|
de60a70daf | ||
|
|
2904dfa794 | ||
|
|
d51cf7c581 | ||
|
|
f25e6c6a5d | ||
|
|
5fb9422513 | ||
|
|
d01cfc8466 | ||
|
|
fa3888991f | ||
|
|
ded355a807 | ||
|
|
b655c8d54a | ||
|
|
42a6e6faaf | ||
|
|
c7954c284d | ||
|
|
251da1861a | ||
|
|
9712804040 | ||
|
|
fecbef0aff | ||
|
|
d65b71b584 | ||
|
|
579d291bca | ||
|
|
871026f4ba | ||
|
|
9a8a070c62 | ||
|
|
7cf4c7bd78 | ||
|
|
72a1def571 | ||
|
|
0dad99c3b7 | ||
|
|
840c0190c4 | ||
|
|
e0fdeea69b | ||
|
|
e3612929f8 | ||
|
|
70921bb6ef | ||
|
|
371ca4eef1 | ||
|
|
54fdb7066f | ||
|
|
85bcb0c757 | ||
|
|
d387cbe5bd | ||
|
|
1bc28e4904 | ||
|
|
cb3419ba2a | ||
|
|
a2f8e156da | ||
|
|
a4cf79c161 | ||
|
|
9352517705 | ||
|
|
47d5163c52 | ||
|
|
def22b01bb | ||
|
|
9445cf99fd | ||
|
|
96b226de24 | ||
|
|
5101f69e4e | ||
|
|
61b66e0edf | ||
|
|
700051f809 | ||
|
|
d16e10baec | ||
|
|
11e5c14f83 | ||
|
|
ded58d77d1 | ||
|
|
8642c372c4 | ||
|
|
edbf591059 | ||
|
|
ded4291d6a | ||
|
|
a14fead0f3 | ||
|
|
466e1e3eb8 | ||
|
|
8a90f0dab1 | ||
|
|
d7e0aa3f61 | ||
|
|
37b343a797 | ||
|
|
149485905c | ||
|
|
7f1df5629e | ||
|
|
f42d0411b1 | ||
|
|
8d1d573266 | ||
|
|
d86b60ea72 | ||
|
|
dfe5fbb702 | ||
|
|
09f881c0f5 | ||
|
|
f1546008f9 | ||
|
|
d8df7fde84 | ||
|
|
1c809eb428 | ||
|
|
e94edcd4ae | ||
|
|
b48651396f | ||
|
|
e2044074ad | ||
|
|
f060ac9db1 | ||
|
|
5a53d7f32a | ||
|
|
4eec13da1c | ||
|
|
cb8282dfe5 | ||
|
|
5cd0b1a9be | ||
|
|
504fadaf71 | ||
|
|
7187e540a8 | ||
|
|
34dffbfc5e | ||
|
|
a9637f93c3 | ||
|
|
0e8b0a9c5c | ||
|
|
e66b596a0d | ||
|
|
6f320f463d | ||
|
|
02955ab57c | ||
|
|
a9d76a2577 | ||
|
|
dcf31baf3a | ||
|
|
1e346f10ab | ||
|
|
a114d55fac | ||
|
|
afde717ca4 | ||
|
|
fb5a45f714 | ||
|
|
f5ac18da18 | ||
|
|
103ae363f6 | ||
|
|
9bde673397 | ||
|
|
ff6b27eafa | ||
|
|
8cb19ccbf6 | ||
|
|
198eba3682 | ||
|
|
a49f62238b | ||
|
|
a8233bdb84 | ||
|
|
ec2826e0fc | ||
|
|
3d9606f6da | ||
|
|
01458eeff9 | ||
|
|
0318568a30 | ||
|
|
4d04141f24 | ||
|
|
afbc622fb9 | ||
|
|
fbc7f865ec | ||
|
|
2a4bac7a27 | ||
|
|
b45a5da6e2 | ||
|
|
2fad9f9ba8 | ||
|
|
8b0e5b9d15 | ||
|
|
0889ffdf27 | ||
|
|
86d0d4fc22 | ||
|
|
7e9df74e60 | ||
|
|
3eca67e1ad | ||
|
|
c040b3a7dd |
@@ -2,5 +2,6 @@
|
||||
<!-- Base application theme. -->
|
||||
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
|
||||
<!-- Customize your theme here. -->
|
||||
<item name="android:navigationBarColor">#1081B2</item>
|
||||
</style>
|
||||
</resources>
|
||||
|
||||
@@ -10,6 +10,7 @@ MVN_HTTP=0
|
||||
DEFAULT_SDK_VERSION=$(grep sdkVersion ${THIS_DIR}/../gradle.properties | cut -d"=" -f2)
|
||||
SDK_VERSION=${OVERRIDE_SDK_VERSION:-${DEFAULT_SDK_VERSION}}
|
||||
RN_VERSION=$(jq -r '.dependencies."react-native"' ${THIS_DIR}/../../package.json)
|
||||
DO_GIT_TAG=${GIT_TAG:-0}
|
||||
|
||||
if [[ $THE_MVN_REPO == http* ]]; then
|
||||
MVN_HTTP=1
|
||||
@@ -64,17 +65,15 @@ pushd ${THIS_DIR}/../
|
||||
./gradlew clean assembleRelease publish
|
||||
popd
|
||||
|
||||
if [[ $MVN_HTTP == 0 ]]; then
|
||||
if [[ $DO_GIT_TAG == 1 ]]; then
|
||||
# The artifacts are now on the Maven repo, commit them
|
||||
pushd ${MVN_REPO_PATH}
|
||||
if [[ "$(git rev-parse --is-inside-work-tree 2>/dev/null)" == "true" ]]; then
|
||||
git add -A .
|
||||
git commit -m "Jitsi Meet SDK + dependencies"
|
||||
fi
|
||||
git add -A .
|
||||
git commit -m "Jitsi Meet SDK + dependencies: ${SDK_VERSION}"
|
||||
popd
|
||||
|
||||
# Tag the release
|
||||
git tag -a android-sdk-${SDK_VERSION}
|
||||
git tag android-sdk-${SDK_VERSION}
|
||||
fi
|
||||
|
||||
# Done!
|
||||
|
||||
@@ -53,6 +53,7 @@ dependencies {
|
||||
|
||||
implementation project(':react-native-background-timer')
|
||||
implementation project(':react-native-calendar-events')
|
||||
implementation project(':react-native-community-async-storage')
|
||||
implementation(project(':react-native-fast-image')) {
|
||||
exclude group: 'com.android.support'
|
||||
}
|
||||
@@ -62,6 +63,7 @@ dependencies {
|
||||
implementation project(':react-native-sound')
|
||||
implementation project(':react-native-vector-icons')
|
||||
implementation project(':react-native-webrtc')
|
||||
implementation project(':react-native-webview')
|
||||
|
||||
testImplementation 'junit:junit:4.12'
|
||||
}
|
||||
@@ -205,8 +207,7 @@ publishing {
|
||||
def groupId = it.moduleGroup
|
||||
def artifactId = it.moduleName
|
||||
|
||||
if (artifactId.startsWith('react-native-')
|
||||
&& groupId.equals('jitsi-meet')) {
|
||||
if (artifactId.startsWith('react-native-') && groupId.equals('jitsi-meet')) {
|
||||
groupId = rootProject.ext.moduleGroupId
|
||||
}
|
||||
|
||||
|
||||
@@ -52,6 +52,17 @@ class AmplitudeModule
|
||||
Amplitude.getInstance(instanceName).initialize(getCurrentActivity(), apiKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the user ID for an Amplitude instance.
|
||||
*
|
||||
* @param instanceName The name of the Amplitude instance.
|
||||
* @param userId The new value for the user ID.
|
||||
*/
|
||||
@ReactMethod
|
||||
public void setUserId(String instanceName, String userId) {
|
||||
Amplitude.getInstance(instanceName).setUserId(userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the user properties for an Amplitude instance.
|
||||
*
|
||||
|
||||
@@ -40,6 +40,15 @@ public class JitsiMeetActivityDelegate {
|
||||
private static PermissionListener permissionListener;
|
||||
private static Callback permissionsCallback;
|
||||
|
||||
/**
|
||||
* Tells whether or not the permissions request is currently in progress.
|
||||
*
|
||||
* @return {@code true} if the permssions are being requested or {@code false} otherwise.
|
||||
*/
|
||||
static boolean arePermissionsBeingRequested() {
|
||||
return permissionListener != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Activity} lifecycle method which should be called from
|
||||
* {@code Activity#onActivityResult} so we are notified about results of external intents
|
||||
|
||||
@@ -40,6 +40,10 @@ public class JitsiMeetConferenceOptions implements Parcelable {
|
||||
* Room name.
|
||||
*/
|
||||
private String room;
|
||||
/**
|
||||
* Conference subject.
|
||||
*/
|
||||
private String subject;
|
||||
/**
|
||||
* JWT token used for authentication.
|
||||
*/
|
||||
@@ -70,6 +74,7 @@ public class JitsiMeetConferenceOptions implements Parcelable {
|
||||
public static class Builder {
|
||||
private URL serverURL;
|
||||
private String room;
|
||||
private String subject;
|
||||
private String token;
|
||||
|
||||
private Bundle colorScheme;
|
||||
@@ -105,6 +110,17 @@ public class JitsiMeetConferenceOptions implements Parcelable {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the conference subject.
|
||||
* @param subject - Subject for the conference.
|
||||
* @return - The {@link Builder} object itself so the method calls can be chained.
|
||||
*/
|
||||
public Builder setSubject(String subject) {
|
||||
this.subject = subject;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the JWT token to be used for authentication when joining a conference.
|
||||
* @param token - The JWT token to be used for authentication.
|
||||
@@ -185,6 +201,7 @@ public class JitsiMeetConferenceOptions implements Parcelable {
|
||||
|
||||
options.serverURL = this.serverURL;
|
||||
options.room = this.room;
|
||||
options.subject = this.subject;
|
||||
options.token = this.token;
|
||||
options.colorScheme = this.colorScheme;
|
||||
options.audioMuted = this.audioMuted;
|
||||
@@ -201,6 +218,7 @@ public class JitsiMeetConferenceOptions implements Parcelable {
|
||||
|
||||
private JitsiMeetConferenceOptions(Parcel in) {
|
||||
room = in.readString();
|
||||
subject = in.readString();
|
||||
token = in.readString();
|
||||
colorScheme = in.readBundle();
|
||||
byte tmpAudioMuted = in.readByte();
|
||||
@@ -238,6 +256,9 @@ public class JitsiMeetConferenceOptions implements Parcelable {
|
||||
if (videoMuted != null) {
|
||||
config.putBoolean("startWithVideoMuted", videoMuted);
|
||||
}
|
||||
if (subject != null) {
|
||||
config.putString("subject", subject);
|
||||
}
|
||||
|
||||
Bundle urlProps = new Bundle();
|
||||
|
||||
@@ -281,6 +302,7 @@ public class JitsiMeetConferenceOptions implements Parcelable {
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeString(room);
|
||||
dest.writeString(subject);
|
||||
dest.writeString(token);
|
||||
dest.writeBundle(colorScheme);
|
||||
dest.writeByte((byte) (audioMuted == null ? 0 : audioMuted ? 1 : 2));
|
||||
|
||||
@@ -123,6 +123,7 @@ public class JitsiMeetView extends BaseReactView<JitsiMeetViewListener> {
|
||||
PictureInPictureModule.class);
|
||||
if (pipModule != null
|
||||
&& PictureInPictureModule.isPictureInPictureSupported()
|
||||
&& !JitsiMeetActivityDelegate.arePermissionsBeingRequested()
|
||||
&& this.url != null) {
|
||||
try {
|
||||
pipModule.enterPictureInPicture();
|
||||
|
||||
@@ -66,7 +66,7 @@ class ReactInstanceManagerHolder {
|
||||
}
|
||||
|
||||
try {
|
||||
Class<?> amplitudeModuleClass = Class.forName("AmplitudeModule");
|
||||
Class<?> amplitudeModuleClass = Class.forName("org.jitsi.meet.sdk.AmplitudeModule");
|
||||
Constructor constructor = amplitudeModuleClass.getConstructor(ReactApplicationContext.class);
|
||||
nativeModules.add((NativeModule)constructor.newInstance(reactContext));
|
||||
} catch (Exception e) {
|
||||
@@ -147,6 +147,8 @@ class ReactInstanceManagerHolder {
|
||||
new com.oblador.vectoricons.VectorIconsPackage(),
|
||||
new com.ocetnik.timer.BackgroundTimerPackage(),
|
||||
new com.oney.WebRTCModule.WebRTCModulePackage(),
|
||||
new com.reactnativecommunity.asyncstorage.AsyncStoragePackage(),
|
||||
new com.reactnativecommunity.webview.RNCWebViewPackage(),
|
||||
new com.rnimmersive.RNImmersivePackage(),
|
||||
new com.zmxv.RNSound.RNSoundPackage(),
|
||||
new ReactPackageAdapter() {
|
||||
|
||||
@@ -3,6 +3,10 @@ rootProject.name = 'jitsi-meet'
|
||||
include ':app', ':sdk'
|
||||
include ':react-native-background-timer'
|
||||
project(':react-native-background-timer').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-background-timer/android')
|
||||
include ':react-native-calendar-events'
|
||||
project(':react-native-calendar-events').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-calendar-events/android')
|
||||
include ':react-native-community-async-storage'
|
||||
project(':react-native-community-async-storage').projectDir = new File(rootProject.projectDir, '../node_modules/@react-native-community/async-storage/android')
|
||||
include ':react-native-fast-image'
|
||||
project(':react-native-fast-image').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-fast-image/android')
|
||||
include ':react-native-google-signin'
|
||||
@@ -19,5 +23,5 @@ include ':react-native-vector-icons'
|
||||
project(':react-native-vector-icons').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-vector-icons/android')
|
||||
include ':react-native-webrtc'
|
||||
project(':react-native-webrtc').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-webrtc/android')
|
||||
include ':react-native-calendar-events'
|
||||
project(':react-native-calendar-events').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-calendar-events/android')
|
||||
include ':react-native-webview'
|
||||
project(':react-native-webview').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-webview/android')
|
||||
|
||||
@@ -51,6 +51,8 @@ import {
|
||||
import {
|
||||
checkAndNotifyForNewDevice,
|
||||
getAvailableDevices,
|
||||
notifyCameraError,
|
||||
notifyMicError,
|
||||
setAudioOutputDeviceId,
|
||||
updateDeviceList
|
||||
} from './react/features/base/devices';
|
||||
@@ -486,10 +488,13 @@ class ConferenceConnector {
|
||||
* call in hangup() to resolve when all operations are finished.
|
||||
*/
|
||||
function disconnect() {
|
||||
connection.disconnect();
|
||||
APP.API.notifyConferenceLeft(APP.conference.roomName);
|
||||
const onDisconnected = () => {
|
||||
APP.API.notifyConferenceLeft(APP.conference.roomName);
|
||||
|
||||
return Promise.resolve();
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
return connection.disconnect().then(onDisconnected, onDisconnected);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -694,13 +699,14 @@ export default {
|
||||
// If both requests for 'audio' + 'video' and 'audio'
|
||||
// only failed, we assume that there are some problems
|
||||
// with user's microphone and show corresponding dialog.
|
||||
APP.UI.showMicErrorNotification(audioOnlyError);
|
||||
APP.UI.showCameraErrorNotification(videoOnlyError);
|
||||
APP.store.dispatch(notifyMicError(audioOnlyError));
|
||||
APP.store.dispatch(notifyCameraError(videoOnlyError));
|
||||
} else {
|
||||
// If request for 'audio' + 'video' failed, but request
|
||||
// for 'audio' only was OK, we assume that we had
|
||||
// problems with camera and show corresponding dialog.
|
||||
APP.UI.showCameraErrorNotification(audioAndVideoError);
|
||||
APP.store.dispatch(
|
||||
notifyCameraError(audioAndVideoError));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -718,13 +724,21 @@ export default {
|
||||
this.roomName = options.roomName;
|
||||
|
||||
return (
|
||||
this.createInitialLocalTracksAndConnect(
|
||||
|
||||
// Initialize the device list first. This way, when creating tracks
|
||||
// based on preferred devices, loose label matching can be done in
|
||||
// cases where the exact ID match is no longer available, such as
|
||||
// when the camera device has switched USB ports.
|
||||
this._initDeviceList()
|
||||
.catch(error => logger.warn(
|
||||
'initial device list initialization failed', error))
|
||||
.then(() => this.createInitialLocalTracksAndConnect(
|
||||
options.roomName, {
|
||||
startAudioOnly: config.startAudioOnly,
|
||||
startScreenSharing: config.startScreenSharing,
|
||||
startWithAudioMuted: config.startWithAudioMuted,
|
||||
startWithVideoMuted: config.startWithVideoMuted
|
||||
})
|
||||
}))
|
||||
.then(([ tracks, con ]) => {
|
||||
tracks.forEach(track => {
|
||||
if ((track.isAudioTrack() && this.isLocalAudioMuted())
|
||||
@@ -769,12 +783,22 @@ export default {
|
||||
this.setVideoMuteStatus(true);
|
||||
}
|
||||
|
||||
this._initDeviceList();
|
||||
// Initialize device list a second time to ensure device labels
|
||||
// get populated in case of an initial gUM acceptance; otherwise
|
||||
// they may remain as empty strings.
|
||||
this._initDeviceList(true);
|
||||
|
||||
if (config.iAmRecorder) {
|
||||
this.recorder = new Recorder();
|
||||
}
|
||||
|
||||
if (config.startSilent) {
|
||||
APP.store.dispatch(showNotification({
|
||||
descriptionKey: 'notify.startSilentDescription',
|
||||
titleKey: 'notify.startSilentTitle'
|
||||
}));
|
||||
}
|
||||
|
||||
// XXX The API will take care of disconnecting from the XMPP
|
||||
// server (and, thus, leaving the room) on unload.
|
||||
return new Promise((resolve, reject) => {
|
||||
@@ -828,7 +852,7 @@ export default {
|
||||
|
||||
if (!this.localAudio && !mute) {
|
||||
const maybeShowErrorDialog = error => {
|
||||
showUI && APP.UI.showMicErrorNotification(error);
|
||||
showUI && APP.store.dispatch(notifyMicError(error));
|
||||
};
|
||||
|
||||
createLocalTracksF({ devices: [ 'audio' ] }, false)
|
||||
@@ -891,7 +915,7 @@ export default {
|
||||
|
||||
if (!this.localVideo && !mute) {
|
||||
const maybeShowErrorDialog = error => {
|
||||
showUI && APP.UI.showCameraErrorNotification(error);
|
||||
showUI && APP.store.dispatch(notifyCameraError(error));
|
||||
};
|
||||
|
||||
// Try to create local video if there wasn't any.
|
||||
@@ -2098,7 +2122,7 @@ export default {
|
||||
this._updateVideoDeviceId();
|
||||
})
|
||||
.catch(err => {
|
||||
APP.UI.showCameraErrorNotification(err);
|
||||
APP.store.dispatch(notifyCameraError(err));
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -2131,7 +2155,7 @@ export default {
|
||||
this._updateAudioDeviceId();
|
||||
})
|
||||
.catch(err => {
|
||||
APP.UI.showMicErrorNotification(err);
|
||||
APP.store.dispatch(notifyMicError(err));
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -2277,20 +2301,23 @@ export default {
|
||||
},
|
||||
|
||||
/**
|
||||
* Inits list of current devices and event listener for device change.
|
||||
* Updates the list of current devices.
|
||||
* @param {boolean} setDeviceListChangeHandler - Whether to add the deviceList change handlers.
|
||||
* @private
|
||||
* @returns {Promise}
|
||||
*/
|
||||
_initDeviceList() {
|
||||
_initDeviceList(setDeviceListChangeHandler = false) {
|
||||
const { mediaDevices } = JitsiMeetJS;
|
||||
|
||||
if (mediaDevices.isDeviceListAvailable()
|
||||
&& mediaDevices.isDeviceChangeAvailable()) {
|
||||
this.deviceChangeListener = devices =>
|
||||
window.setTimeout(() => this._onDeviceListChanged(devices), 0);
|
||||
mediaDevices.addEventListener(
|
||||
JitsiMediaDevicesEvents.DEVICE_LIST_CHANGED,
|
||||
this.deviceChangeListener);
|
||||
if (setDeviceListChangeHandler) {
|
||||
this.deviceChangeListener = devices =>
|
||||
window.setTimeout(() => this._onDeviceListChanged(devices), 0);
|
||||
mediaDevices.addEventListener(
|
||||
JitsiMediaDevicesEvents.DEVICE_LIST_CHANGED,
|
||||
this.deviceChangeListener);
|
||||
}
|
||||
|
||||
const { dispatch } = APP.store;
|
||||
|
||||
@@ -2388,17 +2415,26 @@ export default {
|
||||
// Let's handle unknown/non-preferred devices
|
||||
const newAvailDevices
|
||||
= APP.store.getState()['features/base/devices'].availableDevices;
|
||||
let newAudioDevices = [];
|
||||
let oldAudioDevices = [];
|
||||
|
||||
if (typeof newDevices.audiooutput === 'undefined') {
|
||||
APP.store.dispatch(
|
||||
checkAndNotifyForNewDevice(newAvailDevices.audioOutput, oldDevices.audioOutput));
|
||||
newAudioDevices = newAvailDevices.audioOutput;
|
||||
oldAudioDevices = oldDevices.audioOutput;
|
||||
}
|
||||
|
||||
if (!requestedInput.audio) {
|
||||
APP.store.dispatch(
|
||||
checkAndNotifyForNewDevice(newAvailDevices.audioInput, oldDevices.audioInput));
|
||||
newAudioDevices = newAudioDevices.concat(newAvailDevices.audioInput);
|
||||
oldAudioDevices = oldAudioDevices.concat(oldDevices.audioInput);
|
||||
}
|
||||
|
||||
// check for audio
|
||||
if (newAudioDevices.length > 0) {
|
||||
APP.store.dispatch(
|
||||
checkAndNotifyForNewDevice(newAudioDevices, oldAudioDevices));
|
||||
}
|
||||
|
||||
// check for video
|
||||
if (!requestedInput.video) {
|
||||
APP.store.dispatch(
|
||||
checkAndNotifyForNewDevice(newAvailDevices.videoInput, oldDevices.videoInput));
|
||||
@@ -2577,8 +2613,7 @@ export default {
|
||||
leaveRoomAndDisconnect() {
|
||||
APP.store.dispatch(conferenceWillLeave(room));
|
||||
|
||||
return room.leave()
|
||||
.then(disconnect, disconnect);
|
||||
return room.leave().then(disconnect, disconnect);
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
16
config.js
16
config.js
@@ -90,6 +90,10 @@ var config = {
|
||||
// applied locally. FIXME: having these 2 options is confusing.
|
||||
// startWithAudioMuted: false,
|
||||
|
||||
// Enabling it (with #params) will disable local audio output of remote
|
||||
// participants and to enable it back a reload is needed.
|
||||
// startSilent: false
|
||||
|
||||
// Video
|
||||
|
||||
// Sets the preferred resolution (height) for local video. Defaults to 720.
|
||||
@@ -266,6 +270,13 @@ var config = {
|
||||
// Whether or not some features are checked based on token.
|
||||
// enableFeaturesBasedOnToken: false,
|
||||
|
||||
// Enable lock room for all moderators, even when userRolesBasedOnToken is enabled and participants are guests.
|
||||
// lockRoomGuestEnabled: false,
|
||||
|
||||
// When enabled the password used for locking a room is restricted to up to the number of digits specified
|
||||
// roomPasswordNumberOfDigits: 10,
|
||||
// default: roomPasswordNumberOfDigits: false,
|
||||
|
||||
// Message to show the users. Example: 'The service will be down for
|
||||
// maintenance at 01:00 AM GMT,
|
||||
// noticeMessage: '',
|
||||
@@ -407,6 +418,10 @@ var config = {
|
||||
// use only.
|
||||
// _desktopSharingSourceDevice: 'sample-id-or-label'
|
||||
|
||||
// A property to disable the right click context menu for localVideo
|
||||
// the menu has option to flip the locally seen video for local presentations
|
||||
// disableLocalVideoFlip: false
|
||||
|
||||
// List of undocumented settings used in jitsi-meet
|
||||
/**
|
||||
_immediateReloadThreshold
|
||||
@@ -421,7 +436,6 @@ var config = {
|
||||
dialOutCodesUrl
|
||||
disableRemoteControl
|
||||
displayJids
|
||||
enableLocalVideoFlip
|
||||
etherpad_base
|
||||
externalConnectUrl
|
||||
firefox_fake_device
|
||||
|
||||
194
css/_chat.scss
194
css/_chat.scss
@@ -1,6 +1,9 @@
|
||||
#sideToolbarContainer {
|
||||
background-color: $newToolbarBackgroundColor;
|
||||
box-sizing: border-box;
|
||||
color: #FFF;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
/**
|
||||
* Make the sidebar flush with the top of the toolbar. Take the size of
|
||||
* the toolbar and subtract from 100%.
|
||||
@@ -21,20 +24,6 @@
|
||||
&.slideInExt {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.sideToolbarContainer__inner {
|
||||
box-sizing: border-box;
|
||||
color: #FFF;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
width: $sidebarWidth;
|
||||
}
|
||||
}
|
||||
|
||||
#chat_container * {
|
||||
-webkit-user-select: text;
|
||||
user-select: text;
|
||||
}
|
||||
|
||||
#chatconversation {
|
||||
@@ -42,9 +31,8 @@
|
||||
flex: 1;
|
||||
font-size: 10pt;
|
||||
line-height: 20px;
|
||||
margin-top: $desktopAppDragBarHeight + 5px;
|
||||
overflow: auto;
|
||||
padding: 5px;
|
||||
padding: 16px;
|
||||
text-align: left;
|
||||
width: $sidebarWidth;
|
||||
word-wrap: break-word;
|
||||
@@ -92,26 +80,41 @@
|
||||
}
|
||||
}
|
||||
|
||||
.chat-close {
|
||||
background: gray;
|
||||
border: 3px solid rgba(255, 255, 255, 0.1);
|
||||
border-radius: 100%;
|
||||
color: white;
|
||||
cursor:pointer;
|
||||
height: 10px;
|
||||
line-height: 10px;
|
||||
padding: 4px;
|
||||
position: absolute;
|
||||
right: 5px;
|
||||
text-align: center;
|
||||
top: $desktopAppDragBarHeight;
|
||||
width: 10px;
|
||||
.chat-header {
|
||||
background-color: $chatHeaderBackgroundColor;
|
||||
height: 70px;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
z-index: 1;
|
||||
|
||||
.chat-close {
|
||||
align-items: center;
|
||||
bottom: 8px;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
font-size: 18px;
|
||||
height: 40px;
|
||||
justify-content: center;
|
||||
line-height: 15px;
|
||||
padding: 4px;
|
||||
position: absolute;
|
||||
right: 5px;
|
||||
width: 40px;
|
||||
|
||||
&:hover {
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#chat-input {
|
||||
background-color: $newToolbarBackgroundColor;
|
||||
border-top: 1px solid $chatInputSeparatorColor;
|
||||
display: flex;
|
||||
|
||||
* {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.remoteuser {
|
||||
@@ -123,16 +126,13 @@
|
||||
}
|
||||
|
||||
#usermsg {
|
||||
background-color: $newToolbarBackgroundColor;
|
||||
border: 0px none;
|
||||
border-radius:0;
|
||||
box-shadow: none;
|
||||
color: white;
|
||||
font-size: 10pt;
|
||||
font-size: 15px;
|
||||
line-height: 30px;
|
||||
padding: 5px;
|
||||
max-height:150px;
|
||||
min-height:35px;
|
||||
overflow-y: auto;
|
||||
resize: none;
|
||||
width: 100%;
|
||||
@@ -145,64 +145,47 @@
|
||||
}
|
||||
|
||||
#nickname {
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
color: #9d9d9d;
|
||||
font-size: 18px;
|
||||
top: 100px;
|
||||
margin-top: 30px;
|
||||
left: 5px;
|
||||
right: 5px;
|
||||
width: 95%;
|
||||
}
|
||||
|
||||
#chat_container .display-name {
|
||||
float: left;
|
||||
padding-left: 5px;
|
||||
font-weight: bold;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
width: 95%;
|
||||
overflow: hidden;
|
||||
}
|
||||
.sideToolbarContainer {
|
||||
* {
|
||||
-webkit-user-select: text;
|
||||
user-select: text;
|
||||
}
|
||||
|
||||
#chat_container .timestamp {
|
||||
float: right;
|
||||
padding-right: 5px;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.usermessage {
|
||||
padding-top: 20px;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.chatArrow {
|
||||
border-color:
|
||||
transparent $newToolbarBackgroundColor transparent transparent;
|
||||
border-style: solid;
|
||||
border-width: 0 10px 10px 0;
|
||||
left: -10px;
|
||||
position: absolute;
|
||||
.display-name {
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.chatmessage {
|
||||
background-color: $newToolbarBackgroundColor;
|
||||
width: 93%;
|
||||
margin-left: 9px;
|
||||
margin-right: auto;
|
||||
border-radius: 5px;
|
||||
border-top-left-radius: 0px;
|
||||
margin-top: 3px;
|
||||
background-color: $chatRemoteMessageBackgroundColor;
|
||||
border-radius: 0px 6px 6px 6px;
|
||||
box-sizing: border-box;
|
||||
color: white;
|
||||
margin-top: 3px;
|
||||
max-width: 100%;
|
||||
padding-bottom: 3px;
|
||||
position: relative;
|
||||
|
||||
&.localuser .display-name {
|
||||
color: #4C9AFF
|
||||
&.localuser {
|
||||
background-color: $chatLocalMessageBackgroundColor;
|
||||
border-radius: 6px 0px 6px 6px;
|
||||
}
|
||||
|
||||
&.error {
|
||||
.chatArrow,
|
||||
border-radius: 0px;
|
||||
|
||||
.timestamp,
|
||||
.display-name {
|
||||
display: none;
|
||||
@@ -231,7 +214,6 @@
|
||||
}
|
||||
|
||||
#smileysarea {
|
||||
background-color: $newToolbarBackgroundColor;
|
||||
display: flex;
|
||||
max-height: 150px;
|
||||
min-height: 35px;
|
||||
@@ -246,14 +228,22 @@
|
||||
.smileys-panel {
|
||||
bottom: 100%;
|
||||
box-sizing: border-box;
|
||||
height: 0;
|
||||
height: auto;
|
||||
max-height: 0;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
transition: height 0.3s;
|
||||
width: $sidebarWidth;
|
||||
|
||||
/**
|
||||
* CSS transitions do not apply for auto dimensions. So to produce the css
|
||||
* accordion effect for showing and hiding the smiley-panel, while allowing
|
||||
* for variable panel, height, use a very large max-height and animate off
|
||||
* of that.
|
||||
*/
|
||||
transition: max-height 0.3s;
|
||||
|
||||
&.show-smileys {
|
||||
height: 146px;
|
||||
max-height: 500%;
|
||||
}
|
||||
|
||||
#smileysContainer {
|
||||
@@ -287,3 +277,49 @@
|
||||
#usermsg::-webkit-scrollbar-track-piece {
|
||||
background: #3a3a3a;
|
||||
}
|
||||
|
||||
.chat-message-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
&.local {
|
||||
align-items: flex-end;
|
||||
|
||||
.chatmessage {
|
||||
background-color: $chatLocalMessageBackgroundColor;
|
||||
border-radius: 6px 0px 6px 6px;
|
||||
}
|
||||
|
||||
.display-name {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.timestamp {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
&.error {
|
||||
.chatmessage {
|
||||
border-radius: 0px;
|
||||
color: red;
|
||||
}
|
||||
|
||||
.display-name {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.chatmessage-wrapper {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.chatmessage {
|
||||
background-color: $chatRemoteMessageBackgroundColor;
|
||||
border-radius: 0px 6px 6px 6px;
|
||||
display: inline-block;
|
||||
margin-top: 3px;
|
||||
color: white;
|
||||
padding: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,11 +83,19 @@ $modalMockAKInputBorder: 1px solid #f4f5f7;
|
||||
$modalTextColor: #333;
|
||||
|
||||
/**
|
||||
* Chat
|
||||
*/
|
||||
$chatHeaderBackgroundColor: rgba(42, 58, 75, 0.9);
|
||||
$chatInputSeparatorColor: #A4B8D1;
|
||||
$chatLocalMessageBackgroundColor: rgba(26, 108, 180, 1);
|
||||
$chatRemoteMessageBackgroundColor: rgba(240, 243, 247, 0.15);
|
||||
$sidebarWidth: 375px;
|
||||
|
||||
/**
|
||||
* Misc.
|
||||
*/
|
||||
$borderRadius: 4px;
|
||||
$defaultWatermarkLink: '../images/watermark.png';
|
||||
$sidebarWidth: 220px;
|
||||
$popoverMenuPadding: 13px;
|
||||
$happySoftwareBackground: transparent;
|
||||
$desktopAppDragBarHeight: 25px;
|
||||
|
||||
@@ -514,6 +514,7 @@
|
||||
}
|
||||
#dominantSpeakerAvatar {
|
||||
background-color: #000000;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.dynamic-shadow {
|
||||
@@ -525,12 +526,21 @@
|
||||
transition: box-shadow 0.3s ease;
|
||||
}
|
||||
|
||||
.userAvatar {
|
||||
.avatar-container {
|
||||
@include maxSize(60px);
|
||||
@include absoluteAligning();
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
height: 50%;
|
||||
overflow: hidden;
|
||||
width: auto;
|
||||
|
||||
.userAvatar {
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
#videoNotAvailableScreen {
|
||||
|
||||
@@ -162,4 +162,10 @@ body.welcome-page {
|
||||
font-size: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
.welcome-watermark {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
/**
|
||||
* Let the avatar grow with the tile.
|
||||
*/
|
||||
.userAvatar {
|
||||
.avatar-container {
|
||||
max-height: initial;
|
||||
max-width: initial;
|
||||
}
|
||||
|
||||
@@ -185,6 +185,7 @@
|
||||
font-size: 12px;
|
||||
max-height: 100%;
|
||||
overflow: auto;
|
||||
padding: 15pt;
|
||||
position: absolute;
|
||||
transform: translateY(-50%);
|
||||
top: 50%;
|
||||
|
||||
28
doc/api.md
28
doc/api.md
@@ -222,6 +222,11 @@ api.executeCommand('toggleChat');
|
||||
api.executeCommand('toggleShareScreen');
|
||||
```
|
||||
|
||||
* **toggleTileView** - Enter / exit tile view layout mode. No arguments are required.
|
||||
```javascript
|
||||
api.executeCommand('toggleTileView');
|
||||
```
|
||||
|
||||
* **hangup** - Hangups the call. No arguments are required.
|
||||
```javascript
|
||||
api.executeCommand('hangup');
|
||||
@@ -259,6 +264,14 @@ The `event` parameter is a String object with the name of the event.
|
||||
The `listener` parameter is a Function object with one argument that will be notified when the event occurs with data related to the event.
|
||||
|
||||
The following events are currently supported:
|
||||
* **cameraError** - event notifications about Jitsi-Meet having failed to access the camera. The listener will receive an object with the following structure:
|
||||
```javascript
|
||||
{
|
||||
type: string, // A constant representing the overall type of the error.
|
||||
message: string // Additional information about the error.
|
||||
}
|
||||
```
|
||||
|
||||
* **avatarChanged** - event notifications about avatar
|
||||
changes. The listener will receive an object with the following structure:
|
||||
```javascript
|
||||
@@ -282,6 +295,14 @@ changes. The listener will receive an object with the following structure:
|
||||
}
|
||||
```
|
||||
|
||||
* **micError** - event notifications about Jitsi-Meet having failed to access the mic. The listener will receive an object with the following structure:
|
||||
```javascript
|
||||
{
|
||||
type: string, // A constant representing the overall type of the error.
|
||||
message: string // Additional information about the error.
|
||||
}
|
||||
```
|
||||
|
||||
* **screenSharingStatusChanged** - receives event notifications about turning on/off the local user screen sharing. The listener will receive object with the following structure:
|
||||
```javascript
|
||||
{
|
||||
@@ -296,6 +317,13 @@ changes. The listener will receive an object with the following structure:
|
||||
}
|
||||
```
|
||||
|
||||
* **tileViewChanged** - event notifications about tile view layout mode being entered or exited. The listener will receive object with the following structure:
|
||||
```javascript
|
||||
{
|
||||
enabled: boolean, // whether tile view is not displayed or not
|
||||
}
|
||||
```
|
||||
|
||||
* **incomingMessage** - Event notifications about incoming
|
||||
messages. The listener will receive an object with the following structure:
|
||||
```javascript
|
||||
|
||||
@@ -5,7 +5,7 @@ signed Android build for that, that can be a debug self-signed build too, just
|
||||
retrieve the signing hash. The key hash of an already signed ap can be obtained
|
||||
as follows (on macOS): ```keytool -list -printcert -jarfile the-app.apk```
|
||||
- Place the generated ```google-services.json``` file in ```android/app```
|
||||
for Android and the ```GoogleService-Info.plist``` into ```ios/app/src``` for
|
||||
for Android and the ```GoogleService-Info.plist``` into ```ios/app``` for
|
||||
iOS (you can stop at that step, no need for the driver and the code changes they
|
||||
suggest in the wizard).
|
||||
- You may want to exclude these files in YOUR GIT config (do not exclude them in
|
||||
|
||||
43
ios/Podfile
43
ios/Podfile
@@ -14,6 +14,9 @@ end
|
||||
target 'JitsiMeet' do
|
||||
project 'sdk/sdk.xcodeproj'
|
||||
|
||||
# React Native and its dependencies
|
||||
#
|
||||
|
||||
pod 'React', :path => '../node_modules/react-native', :subspecs => [
|
||||
'Core',
|
||||
'CxxBridge',
|
||||
@@ -27,33 +30,31 @@ target 'JitsiMeet' do
|
||||
'RCTWebSocket',
|
||||
]
|
||||
pod 'yoga', :path => '../node_modules/react-native/ReactCommon/yoga'
|
||||
pod 'DoubleConversion', :podspec => '../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec'
|
||||
pod 'glog', :podspec => '../node_modules/react-native/third-party-podspecs/glog.podspec'
|
||||
pod 'Folly', :podspec => '../node_modules/react-native/third-party-podspecs/Folly.podspec'
|
||||
|
||||
pod 'DoubleConversion',
|
||||
:podspec => '../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec'
|
||||
pod 'glog',
|
||||
:podspec => '../node_modules/react-native/third-party-podspecs/glog.podspec'
|
||||
pod 'Folly',
|
||||
:podspec => '../node_modules/react-native/third-party-podspecs/Folly.podspec'
|
||||
# React Native plugins
|
||||
#
|
||||
|
||||
pod 'Amplitude-iOS', '~> 4.0.4'
|
||||
pod 'ObjectiveDropboxOfficial', '~> 3.9.4'
|
||||
|
||||
pod 'react-native-background-timer',
|
||||
:path => '../node_modules/react-native-background-timer'
|
||||
pod 'react-native-fast-image',
|
||||
:path => '../node_modules/react-native-fast-image'
|
||||
pod 'react-native-keep-awake',
|
||||
:path => '../node_modules/react-native-keep-awake'
|
||||
pod 'BVLinearGradient',
|
||||
:path => '../node_modules/react-native-linear-gradient'
|
||||
pod 'react-native-background-timer', :path => '../node_modules/react-native-background-timer'
|
||||
pod 'react-native-calendar-events', :path => '../node_modules/react-native-calendar-events'
|
||||
pod 'react-native-fast-image', :path => '../node_modules/react-native-fast-image'
|
||||
pod 'react-native-keep-awake', :path => '../node_modules/react-native-keep-awake'
|
||||
pod 'react-native-webview', :path => '../node_modules/react-native-webview'
|
||||
pod 'react-native-webrtc', :path => '../node_modules/react-native-webrtc'
|
||||
pod 'RNGoogleSignin',
|
||||
:path => '../node_modules/react-native-google-signin'
|
||||
pod 'BVLinearGradient', :path => '../node_modules/react-native-linear-gradient'
|
||||
pod 'RNCAsyncStorage', :path => '../node_modules/@react-native-community/async-storage'
|
||||
pod 'RNGoogleSignin', :path => '../node_modules/react-native-google-signin'
|
||||
pod 'RNSound', :path => '../node_modules/react-native-sound'
|
||||
pod 'RNVectorIcons', :path => '../node_modules/react-native-vector-icons'
|
||||
pod 'RNWatch', :path => '../node_modules/react-native-watch-connectivity'
|
||||
pod 'react-native-calendar-events',
|
||||
:path => '../node_modules/react-native-calendar-events'
|
||||
|
||||
# Native pod dependencies
|
||||
#
|
||||
|
||||
pod 'Amplitude-iOS', '~> 4.0.4'
|
||||
pod 'ObjectiveDropboxOfficial', '~> 3.9.4'
|
||||
end
|
||||
|
||||
post_install do |installer|
|
||||
|
||||
@@ -84,8 +84,8 @@ PODS:
|
||||
- nanopb/decode (0.3.901)
|
||||
- nanopb/encode (0.3.901)
|
||||
- ObjectiveDropboxOfficial (3.9.4)
|
||||
- React (0.59.5):
|
||||
- React/Core (= 0.59.5)
|
||||
- React (0.59.8):
|
||||
- React/Core (= 0.59.8)
|
||||
- react-native-background-timer (2.1.1):
|
||||
- React
|
||||
- react-native-calendar-events (1.6.4):
|
||||
@@ -99,53 +99,57 @@ PODS:
|
||||
- React
|
||||
- react-native-webrtc (1.69.1):
|
||||
- React
|
||||
- React/Core (0.59.5):
|
||||
- yoga (= 0.59.5.React)
|
||||
- React/CxxBridge (0.59.5):
|
||||
- react-native-webview (5.8.1):
|
||||
- React
|
||||
- React/Core (0.59.8):
|
||||
- yoga (= 0.59.8.React)
|
||||
- React/CxxBridge (0.59.8):
|
||||
- Folly (= 2018.10.22.00)
|
||||
- React/Core
|
||||
- React/cxxreact
|
||||
- React/jsiexecutor
|
||||
- React/cxxreact (0.59.5):
|
||||
- React/cxxreact (0.59.8):
|
||||
- boost-for-react-native (= 1.63.0)
|
||||
- DoubleConversion
|
||||
- Folly (= 2018.10.22.00)
|
||||
- glog
|
||||
- React/jsinspector
|
||||
- React/DevSupport (0.59.5):
|
||||
- React/DevSupport (0.59.8):
|
||||
- React/Core
|
||||
- React/RCTWebSocket
|
||||
- React/fishhook (0.59.5)
|
||||
- React/jsi (0.59.5):
|
||||
- React/fishhook (0.59.8)
|
||||
- React/jsi (0.59.8):
|
||||
- DoubleConversion
|
||||
- Folly (= 2018.10.22.00)
|
||||
- glog
|
||||
- React/jsiexecutor (0.59.5):
|
||||
- React/jsiexecutor (0.59.8):
|
||||
- DoubleConversion
|
||||
- Folly (= 2018.10.22.00)
|
||||
- glog
|
||||
- React/cxxreact
|
||||
- React/jsi
|
||||
- React/jsinspector (0.59.5)
|
||||
- React/RCTActionSheet (0.59.5):
|
||||
- React/jsinspector (0.59.8)
|
||||
- React/RCTActionSheet (0.59.8):
|
||||
- React/Core
|
||||
- React/RCTAnimation (0.59.5):
|
||||
- React/RCTAnimation (0.59.8):
|
||||
- React/Core
|
||||
- React/RCTBlob (0.59.5):
|
||||
- React/RCTBlob (0.59.8):
|
||||
- React/Core
|
||||
- React/RCTImage (0.59.5):
|
||||
- React/RCTImage (0.59.8):
|
||||
- React/Core
|
||||
- React/RCTNetwork
|
||||
- React/RCTLinkingIOS (0.59.5):
|
||||
- React/RCTLinkingIOS (0.59.8):
|
||||
- React/Core
|
||||
- React/RCTNetwork (0.59.5):
|
||||
- React/RCTNetwork (0.59.8):
|
||||
- React/Core
|
||||
- React/RCTText (0.59.5):
|
||||
- React/RCTText (0.59.8):
|
||||
- React/Core
|
||||
- React/RCTWebSocket (0.59.5):
|
||||
- React/RCTWebSocket (0.59.8):
|
||||
- React/Core
|
||||
- React/fishhook
|
||||
- React/RCTBlob
|
||||
- RNCAsyncStorage (1.3.4):
|
||||
- React
|
||||
- RNGoogleSignin (1.0.2):
|
||||
- GoogleSignIn
|
||||
- React
|
||||
@@ -162,7 +166,7 @@ PODS:
|
||||
- SDWebImage/GIF (4.4.6):
|
||||
- FLAnimatedImage (~> 1.0)
|
||||
- SDWebImage/Core
|
||||
- yoga (0.59.5.React)
|
||||
- yoga (0.59.8.React)
|
||||
|
||||
DEPENDENCIES:
|
||||
- Amplitude-iOS (~> 4.0.4)
|
||||
@@ -180,6 +184,7 @@ DEPENDENCIES:
|
||||
- react-native-fast-image (from `../node_modules/react-native-fast-image`)
|
||||
- react-native-keep-awake (from `../node_modules/react-native-keep-awake`)
|
||||
- react-native-webrtc (from `../node_modules/react-native-webrtc`)
|
||||
- react-native-webview (from `../node_modules/react-native-webview`)
|
||||
- React/Core (from `../node_modules/react-native`)
|
||||
- React/CxxBridge (from `../node_modules/react-native`)
|
||||
- React/DevSupport (from `../node_modules/react-native`)
|
||||
@@ -190,6 +195,7 @@ DEPENDENCIES:
|
||||
- React/RCTNetwork (from `../node_modules/react-native`)
|
||||
- React/RCTText (from `../node_modules/react-native`)
|
||||
- React/RCTWebSocket (from `../node_modules/react-native`)
|
||||
- "RNCAsyncStorage (from `../node_modules/@react-native-community/async-storage`)"
|
||||
- RNGoogleSignin (from `../node_modules/react-native-google-signin`)
|
||||
- RNSound (from `../node_modules/react-native-sound`)
|
||||
- RNVectorIcons (from `../node_modules/react-native-vector-icons`)
|
||||
@@ -239,6 +245,10 @@ EXTERNAL SOURCES:
|
||||
:path: "../node_modules/react-native-keep-awake"
|
||||
react-native-webrtc:
|
||||
:path: "../node_modules/react-native-webrtc"
|
||||
react-native-webview:
|
||||
:path: "../node_modules/react-native-webview"
|
||||
RNCAsyncStorage:
|
||||
:path: "../node_modules/@react-native-community/async-storage"
|
||||
RNGoogleSignin:
|
||||
:path: "../node_modules/react-native-google-signin"
|
||||
RNSound:
|
||||
@@ -273,19 +283,21 @@ SPEC CHECKSUMS:
|
||||
GTMSessionFetcher: 32aeca0aa144acea523e1c8e053089dec2cb98ca
|
||||
nanopb: 2901f78ea1b7b4015c860c2fdd1ea2fee1a18d48
|
||||
ObjectiveDropboxOfficial: a5afefc83f6467c42c45f2253f583f2ad1ffc701
|
||||
React: 90adac468c7b72bf1fa6c64bf230650f851a8388
|
||||
React: 76e6aa2b87d05eb6cccb6926d72685c9a07df152
|
||||
react-native-background-timer: 0d34748e53a972507c66963490c775321a88f6f2
|
||||
react-native-calendar-events: ee9573e355711ac679e071be70789542431f4ce3
|
||||
react-native-fast-image: 47487b71169aea34868e7b38bf870b6b3f2157c5
|
||||
react-native-keep-awake: eba3137546b10003361b37c761f6c429b59814ae
|
||||
react-native-webrtc: 90a847d19deb2d7323fef8cc89ca12b8995fbc90
|
||||
react-native-webview: a95842e3f351a6d2c8bc8bcc9eab689c7e7e5ad4
|
||||
RNCAsyncStorage: 8e31405a9f12fbf42c2bb330e4560bfd79c18323
|
||||
RNGoogleSignin: 361174d9a3090d295b06257162b560d8efc8a6ed
|
||||
RNSound: e157320f503bdd4f4ee6d8542e948d54f90c3c3a
|
||||
RNVectorIcons: d819334932bcda3332deb3d2c8ea4d069e0b98f9
|
||||
RNWatch: 09738b339eceb66e4d80a2371633ca5fb380fa42
|
||||
SDWebImage: 3f3f0c02f09798048c47a5ed0a13f17b063572d8
|
||||
yoga: 2e571f113e8cbeb0eb752aeebc86c1bfe7a8200c
|
||||
yoga: 92b2102c3d373d1a790db4ab761d2b0ffc634f64
|
||||
|
||||
PODFILE CHECKSUM: 9e6bc935ea7d2974604572cc68938281a88cf35c
|
||||
PODFILE CHECKSUM: b55338cc43312051ed83f8d9c6aadbd8c9402e6a
|
||||
|
||||
COCOAPODS: 1.6.1
|
||||
|
||||
@@ -92,7 +92,7 @@ Leaves the currently active conference.
|
||||
|
||||
#### Universal / deep linking
|
||||
|
||||
In order to support Universal / deep linking, `JitsiMeetView` offers 2 class
|
||||
In order to support Universal / deep linking, `JitsiMeet` offers 2 class
|
||||
methods that you app's delegate should call in order for the app to follow those
|
||||
links.
|
||||
|
||||
@@ -104,7 +104,7 @@ is useful when the host application uses other SDKs which also use linking.
|
||||
continueUserActivity:(NSUserActivity *)userActivity
|
||||
restorationHandler:(void (^)(NSArray *restorableObjects))restorationHandler
|
||||
{
|
||||
return [JitsiMeetView application:application
|
||||
return [[JitsiMeet sharedInstance] application:application
|
||||
continueUserActivity:userActivity
|
||||
restorationHandler:restorationHandler];
|
||||
}
|
||||
@@ -117,7 +117,7 @@ And also one of the following:
|
||||
- (BOOL)application:(UIApplication *)app
|
||||
openURL:(NSURL *)url
|
||||
options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
|
||||
return [JitsiMeetView application:app
|
||||
return [[JitsiMeet sharedInstance] application:app
|
||||
openURL:url
|
||||
options: options];
|
||||
}
|
||||
|
||||
@@ -737,7 +737,7 @@
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEAD_CODE_STRIPPING = NO;
|
||||
DEVELOPMENT_TEAM = FC967L3QRG;
|
||||
ENABLE_BITCODE = YES;
|
||||
ENABLE_BITCODE = NO;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"../../node_modules/react-native-webrtc/ios",
|
||||
|
||||
@@ -42,11 +42,11 @@
|
||||
|
||||
- (void)_onJitsiMeetViewDelegateEvent:(NSString *)name
|
||||
withData:(NSDictionary *)data {
|
||||
#if DEBUG
|
||||
NSLog(
|
||||
@"[%s:%d] JitsiMeetViewDelegate %@ %@",
|
||||
__FILE__, __LINE__, name, data);
|
||||
|
||||
#if DEBUG
|
||||
NSAssert(
|
||||
[NSThread isMainThread],
|
||||
@"JitsiMeetViewDelegate %@ method invoked on a non-main thread",
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 4.2 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 4.7 KiB |
@@ -14,6 +14,7 @@
|
||||
<objects>
|
||||
<controller title="Meetings" id="AgC-eL-Hgc" customClass="InterfaceController" customModule="JitsiMeetCompanion" customModuleProvider="target">
|
||||
<items>
|
||||
<label alignment="left" textAlignment="left" numberOfLines="0" id="OQN-sx-tDt"/>
|
||||
<table alignment="left" id="gpO-ql-Xsr">
|
||||
<items>
|
||||
<tableRow identifier="MeetingRowType" id="GGl-av-xeJ" customClass="MeetingRowController" customModule="JitsiMeetCompanion_Extension">
|
||||
@@ -39,6 +40,7 @@
|
||||
</table>
|
||||
</items>
|
||||
<connections>
|
||||
<outlet property="infoLabel" destination="OQN-sx-tDt" id="Juv-tb-SNj"/>
|
||||
<outlet property="table" destination="gpO-ql-Xsr" id="aVV-iZ-z3l"/>
|
||||
</connections>
|
||||
</controller>
|
||||
|
||||
@@ -16,17 +16,31 @@
|
||||
*/
|
||||
|
||||
import WatchKit
|
||||
import WatchConnectivity
|
||||
import Foundation
|
||||
|
||||
|
||||
class InterfaceController: WKInterfaceController {
|
||||
|
||||
@IBOutlet var infoLabel: WKInterfaceLabel!
|
||||
@IBOutlet var table: WKInterfaceTable!
|
||||
|
||||
override func didAppear(){
|
||||
self.updateUI(ExtensionDelegate.currentJitsiMeetContext)
|
||||
}
|
||||
|
||||
func updateUI(_ newContext:JitsiMeetContext) {
|
||||
if let recentURLsArray = newContext.recentURLs {
|
||||
updateRecents(withRecents: recentURLsArray, currentContext: newContext)
|
||||
}
|
||||
if (newContext.recentURLs == nil || newContext.recentURLs!.count == 0) {
|
||||
infoLabel.setText("There are no recent meetings. Please use the app on the phone to start a new call.")
|
||||
|
||||
table.setHidden(true)
|
||||
infoLabel.setHidden(false)
|
||||
} else {
|
||||
updateRecents(withRecents: newContext.recentURLs!, currentContext: newContext)
|
||||
|
||||
table.setHidden(false)
|
||||
infoLabel.setHidden(true)
|
||||
}
|
||||
}
|
||||
|
||||
private func updateRecents(withRecents recents: NSArray, currentContext: JitsiMeetContext) {
|
||||
@@ -68,7 +82,7 @@ class InterfaceController: WKInterfaceController {
|
||||
override func contextForSegue(withIdentifier segueIdentifier: String, in table: WKInterfaceTable, rowIndex: Int) -> Any? {
|
||||
let controller = table.rowController(at: rowIndex) as! MeetingRowController
|
||||
let currentContext = ExtensionDelegate.currentJitsiMeetContext
|
||||
|
||||
|
||||
// Copy the current context and add the joinConferenceURL to trigger the command when the in-call screen is displayed
|
||||
let actionContext = JitsiMeetContext(jmContext: currentContext)
|
||||
actionContext.joinConferenceURL = controller.roomUrl
|
||||
|
||||
15
ios/scripts/bitcode.sh
Executable file
15
ios/scripts/bitcode.sh
Executable file
@@ -0,0 +1,15 @@
|
||||
#!/bin/bash
|
||||
|
||||
# This script will download a bitcode build of the WebRTC framework, if needed.
|
||||
|
||||
if [[ ! "$CONFIGURATION" = "Debug" ]]; then
|
||||
RN_WEBRTC="$SRCROOT/../../node_modules/react-native-webrtc"
|
||||
|
||||
if otool -arch arm64 -l $RN_WEBRTC/ios/WebRTC.framework/WebRTC | grep -q LLVM; then
|
||||
echo "WebRTC framework has bitcode"
|
||||
else
|
||||
echo "WebRTC framework has NO bitcode"
|
||||
$RN_WEBRTC/tools/downloadBitcode.sh
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -5,7 +5,9 @@ set -e -u
|
||||
THIS_DIR=$(cd -P "$(dirname "$(readlink "${BASH_SOURCE[0]}" || echo "${BASH_SOURCE[0]}")")" && pwd)
|
||||
PROJECT_REPO=$(realpath ${THIS_DIR}/../..)
|
||||
RELEASE_REPO=$(realpath ${THIS_DIR}/../../../jitsi-meet-ios-sdk-releases)
|
||||
SDK_VERSION=$(/usr/libexec/PlistBuddy -c "Print CFBundleShortVersionString" ${THIS_DIR}/../sdk/src/Info.plist)
|
||||
DEFAULT_SDK_VERSION=$(/usr/libexec/PlistBuddy -c "Print CFBundleShortVersionString" ${THIS_DIR}/../sdk/src/Info.plist)
|
||||
SDK_VERSION=${OVERRIDE_SDK_VERSION:-${DEFAULT_SDK_VERSION}}
|
||||
DO_GIT_TAG=${GIT_TAG:-0}
|
||||
|
||||
|
||||
echo "Releasing Jitsi Meet SDK ${SDK_VERSION}"
|
||||
@@ -24,7 +26,9 @@ popd
|
||||
pushd ${PROJECT_REPO}
|
||||
rm -rf ios/sdk/JitsiMeet.framework
|
||||
xcodebuild -workspace ios/jitsi-meet.xcworkspace -scheme JitsiMeet -destination='generic/platform=iOS' -configuration Release archive
|
||||
git tag -a ios-sdk-${SDK_VERSION}
|
||||
if [[ $DO_GIT_TAG == 1 ]]; then
|
||||
git tag ios-sdk-${SDK_VERSION}
|
||||
fi
|
||||
popd
|
||||
|
||||
pushd ${RELEASE_REPO}
|
||||
@@ -33,10 +37,16 @@ pushd ${RELEASE_REPO}
|
||||
cp -r ${PROJECT_REPO}/ios/sdk/JitsiMeet.framework Frameworks/
|
||||
cp -r ${PROJECT_REPO}/node_modules/react-native-webrtc/ios/WebRTC.framework Frameworks/
|
||||
|
||||
# Strip bitcode
|
||||
xcrun bitcode_strip -r Frameworks/JitsiMeet.framework/JitsiMeet -o Frameworks/JitsiMeet.framework/JitsiMeet
|
||||
xcrun bitcode_strip -r Frameworks/WebRTC.framework/WebRTC -o Frameworks/WebRTC.framework/WebRTC
|
||||
|
||||
# Add all files to git
|
||||
git add -A .
|
||||
git commit -m "${SDK_VERSION}"
|
||||
git tag ${SDK_VERSION}
|
||||
if [[ $DO_GIT_TAG == 1 ]]; then
|
||||
git add -A .
|
||||
git commit -m "${SDK_VERSION}"
|
||||
git tag ${SDK_VERSION}
|
||||
fi
|
||||
|
||||
popd
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
C6CC49AF207412CF000DFA42 /* PiPViewCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6CC49AE207412CF000DFA42 /* PiPViewCoordinator.swift */; };
|
||||
DEAD3226220C497000E93636 /* JitsiMeetConferenceOptions.h in Headers */ = {isa = PBXBuildFile; fileRef = DEAD3224220C497000E93636 /* JitsiMeetConferenceOptions.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
DEAD3227220C497000E93636 /* JitsiMeetConferenceOptions.m in Sources */ = {isa = PBXBuildFile; fileRef = DEAD3225220C497000E93636 /* JitsiMeetConferenceOptions.m */; };
|
||||
DEAFA779229EAD520033A7FA /* RNRootView.m in Sources */ = {isa = PBXBuildFile; fileRef = DEAFA778229EAD520033A7FA /* RNRootView.m */; };
|
||||
DEFC743F21B178FA00E4DD96 /* LocaleDetector.m in Sources */ = {isa = PBXBuildFile; fileRef = DEFC743D21B178FA00E4DD96 /* LocaleDetector.m */; };
|
||||
DEFE535421FB1BF800011A3A /* JitsiMeet.m in Sources */ = {isa = PBXBuildFile; fileRef = DEFE535321FB1BF800011A3A /* JitsiMeet.m */; };
|
||||
DEFE535621FB2E8300011A3A /* ReactUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = DEFE535521FB2E8300011A3A /* ReactUtils.m */; };
|
||||
@@ -96,6 +97,8 @@
|
||||
DEAD3224220C497000E93636 /* JitsiMeetConferenceOptions.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = JitsiMeetConferenceOptions.h; sourceTree = "<group>"; };
|
||||
DEAD3225220C497000E93636 /* JitsiMeetConferenceOptions.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = JitsiMeetConferenceOptions.m; sourceTree = "<group>"; };
|
||||
DEAD3228220C734300E93636 /* JitsiMeetConferenceOptions+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "JitsiMeetConferenceOptions+Private.h"; sourceTree = "<group>"; };
|
||||
DEAFA777229EAD3B0033A7FA /* RNRootView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNRootView.h; sourceTree = "<group>"; };
|
||||
DEAFA778229EAD520033A7FA /* RNRootView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RNRootView.m; sourceTree = "<group>"; };
|
||||
DEFC743D21B178FA00E4DD96 /* LocaleDetector.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LocaleDetector.m; sourceTree = "<group>"; };
|
||||
DEFE535321FB1BF800011A3A /* JitsiMeet.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = JitsiMeet.m; sourceTree = "<group>"; };
|
||||
DEFE535521FB2E8300011A3A /* ReactUtils.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ReactUtils.m; sourceTree = "<group>"; };
|
||||
@@ -173,6 +176,8 @@
|
||||
DEAD3225220C497000E93636 /* JitsiMeetConferenceOptions.m */,
|
||||
0B412F161EDEC65D00B1A0A6 /* JitsiMeetView.h */,
|
||||
0B412F171EDEC65D00B1A0A6 /* JitsiMeetView.m */,
|
||||
DEAFA777229EAD3B0033A7FA /* RNRootView.h */,
|
||||
DEAFA778229EAD520033A7FA /* RNRootView.m */,
|
||||
C6F99C13204DB63D0001F710 /* JitsiMeetView+Private.h */,
|
||||
0B412F1B1EDEC80100B1A0A6 /* JitsiMeetViewDelegate.h */,
|
||||
DEFC743D21B178FA00E4DD96 /* LocaleDetector.m */,
|
||||
@@ -270,6 +275,7 @@
|
||||
buildConfigurationList = 0BD906ED1EC0C00300C8C18E /* Build configuration list for PBXNativeTarget "JitsiMeet" */;
|
||||
buildPhases = (
|
||||
26796D8589142D80C8AFDA51 /* [CP] Check Pods Manifest.lock */,
|
||||
DE3D81D6228B50FB00A6C149 /* Bitcode */,
|
||||
0BD906E01EC0C00300C8C18E /* Sources */,
|
||||
0BD906E11EC0C00300C8C18E /* Frameworks */,
|
||||
0BD906E21EC0C00300C8C18E /* Headers */,
|
||||
@@ -450,6 +456,24 @@
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-JitsiMeet/Pods-JitsiMeet-resources.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
DE3D81D6228B50FB00A6C149 /* Bitcode */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = Bitcode;
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "../scripts/bitcode.sh\n";
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
@@ -459,6 +483,7 @@
|
||||
files = (
|
||||
0BB9AD7B1F5EC8F4001C08DB /* CallKit.m in Sources */,
|
||||
0BB9AD7D1F60356D001C08DB /* AppInfo.m in Sources */,
|
||||
DEAFA779229EAD520033A7FA /* RNRootView.m in Sources */,
|
||||
DEAD3227220C497000E93636 /* JitsiMeetConferenceOptions.m in Sources */,
|
||||
0B93EF7F1EC9DDCD0030D24D /* RCTBridgeWrapper.m in Sources */,
|
||||
0BA13D311EE83FF8007BEF7F /* ExternalAPI.m in Sources */,
|
||||
@@ -615,7 +640,7 @@
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
DYLIB_CURRENT_VERSION = 1;
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
ENABLE_BITCODE = YES;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = src/Info.plist;
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#import <React/RCTBridge.h>
|
||||
|
||||
#import "JitsiMeet.h"
|
||||
|
||||
@interface JitsiMeet ()
|
||||
|
||||
@@ -26,6 +26,10 @@
|
||||
* Room name.
|
||||
*/
|
||||
@property (nonatomic, copy, nullable) NSString *room;
|
||||
/**
|
||||
* Conference subject.
|
||||
*/
|
||||
@property (nonatomic, copy, nullable) NSString *subject;
|
||||
/**
|
||||
* JWT token used for authentication.
|
||||
*/
|
||||
@@ -56,7 +60,9 @@
|
||||
@interface JitsiMeetConferenceOptions : NSObject
|
||||
|
||||
@property (nonatomic, copy, nullable, readonly) NSURL *serverURL;
|
||||
|
||||
@property (nonatomic, copy, nullable, readonly) NSString *room;
|
||||
@property (nonatomic, copy, nullable, readonly) NSString *subject;
|
||||
@property (nonatomic, copy, nullable, readonly) NSString *token;
|
||||
|
||||
@property (nonatomic, copy, nullable) NSDictionary *colorScheme;
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
if (self = [super init]) {
|
||||
_serverURL = nil;
|
||||
_room = nil;
|
||||
_subject = nil;
|
||||
_token = nil;
|
||||
|
||||
_colorScheme = nil;
|
||||
@@ -138,6 +139,7 @@
|
||||
if (self = [super init]) {
|
||||
_serverURL = builder.serverURL;
|
||||
_room = builder.room;
|
||||
_subject = builder.subject;
|
||||
_token = builder.token;
|
||||
|
||||
_colorScheme = builder.colorScheme;
|
||||
@@ -183,6 +185,9 @@
|
||||
if (_videoMuted != nil) {
|
||||
config[@"startWithVideoMuted"] = @(self.videoMuted);
|
||||
}
|
||||
if (_subject != nil) {
|
||||
config[@"subject"] = self.subject;
|
||||
}
|
||||
|
||||
NSMutableDictionary *urlProps = [[NSMutableDictionary alloc] init];
|
||||
|
||||
|
||||
@@ -17,12 +17,11 @@
|
||||
|
||||
#include <mach/mach_time.h>
|
||||
|
||||
#import <React/RCTRootView.h>
|
||||
|
||||
#import "JitsiMeet+Private.h"
|
||||
#import "JitsiMeetConferenceOptions+Private.h"
|
||||
#import "JitsiMeetView+Private.h"
|
||||
#import "ReactUtils.h"
|
||||
#import "RNRootView.h"
|
||||
|
||||
|
||||
@implementation JitsiMeetView {
|
||||
@@ -36,7 +35,7 @@
|
||||
/**
|
||||
* React Native view where the entire content will be rendered.
|
||||
*/
|
||||
RCTRootView *rootView;
|
||||
RNRootView *rootView;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -145,9 +144,9 @@ static void initializeViewsMap() {
|
||||
} else {
|
||||
RCTBridge *bridge = [[JitsiMeet sharedInstance] getReactBridge];
|
||||
rootView
|
||||
= [[RCTRootView alloc] initWithBridge:bridge
|
||||
moduleName:@"App"
|
||||
initialProperties:props];
|
||||
= [[RNRootView alloc] initWithBridge:bridge
|
||||
moduleName:@"App"
|
||||
initialProperties:props];
|
||||
rootView.backgroundColor = self.backgroundColor;
|
||||
|
||||
// Add rootView as a subview which completely covers this one.
|
||||
|
||||
20
ios/sdk/src/RNRootView.h
Normal file
20
ios/sdk/src/RNRootView.h
Normal file
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright @ 2019-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 <React/RCTRootView.h>
|
||||
|
||||
@interface RNRootView : RCTRootView
|
||||
@end
|
||||
45
ios/sdk/src/RNRootView.m
Normal file
45
ios/sdk/src/RNRootView.m
Normal file
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright @ 2019-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 <React/RCTRootContentView.h>
|
||||
#import <React/RCTLog.h>
|
||||
|
||||
#import "RNRootView.h"
|
||||
|
||||
@implementation RNRootView
|
||||
|
||||
// Monkey-patch RCTRootView.runApplication to avoid logging initial props.
|
||||
- (void)runApplication:(RCTBridge *)bridge
|
||||
{
|
||||
NSString *moduleName = [self valueForKey:@"_moduleName"] ?: @"";
|
||||
RCTRootContentView *_contentView = [self valueForKey:@"_contentView"];
|
||||
NSNumber *reactTag = [_contentView valueForKey:@"reactTag"];
|
||||
|
||||
NSDictionary *appParameters = @{
|
||||
@"rootTag": reactTag,
|
||||
@"initialProps": self.appProperties ?: @{},
|
||||
};
|
||||
#if DEBUG
|
||||
RCTLogInfo(@"Running application %@ (%@)", moduleName, appParameters);
|
||||
#endif
|
||||
|
||||
[bridge enqueueJSCall:@"AppRegistry"
|
||||
method:@"runApplication"
|
||||
args:@[moduleName, appParameters]
|
||||
completion:NULL];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -33,6 +33,10 @@ RCT_EXPORT_METHOD(init:(NSString*)instanceName API_KEY:(NSString*)apiKey) {
|
||||
[[Amplitude instanceWithName:instanceName] initializeApiKey:apiKey];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(setUserId:(NSString*)instanceName userId: (NSString *) userId) {
|
||||
[[Amplitude instanceWithName:instanceName] setUserId:userId];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(setUserProperties:(NSString*)instanceName userPropsString:(NSDictionary*)userProps) {
|
||||
if (userProps != nil) {
|
||||
[[Amplitude instanceWithName:instanceName] setUserProperties:userProps];
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#import <React/RCTBridge.h>
|
||||
#import <React/RCTEventEmitter.h>
|
||||
#import <React/RCTUtils.h>
|
||||
#import <WebRTC/WebRTC.h>
|
||||
|
||||
#import <JitsiMeet/JitsiMeet-Swift.h>
|
||||
|
||||
@@ -307,21 +308,35 @@ RCT_EXPORT_METHOD(updateCall:(NSString *)callUUID
|
||||
startedConnectingAt:nil];
|
||||
}
|
||||
|
||||
// The following just help with debugging:
|
||||
#ifdef DEBUG
|
||||
|
||||
- (void) providerDidActivateAudioSessionWithSession:(AVAudioSession *)session {
|
||||
#ifdef DEBUG
|
||||
NSLog(@"[RNCallKit][CXProviderDelegate][provider:didActivateAudioSession:]");
|
||||
#endif
|
||||
[[RTCAudioSession sharedInstance] audioSessionDidActivate:session];
|
||||
}
|
||||
|
||||
- (void) providerDidDeactivateAudioSessionWithSession:(AVAudioSession *)session {
|
||||
#ifdef DEBUG
|
||||
NSLog(@"[RNCallKit][CXProviderDelegate][provider:didDeactivateAudioSession:]");
|
||||
#endif
|
||||
[[RTCAudioSession sharedInstance] audioSessionDidDeactivate:session];
|
||||
}
|
||||
|
||||
- (void) providerTimedOutPerformingActionWithAction:(CXAction *)action {
|
||||
#ifdef DEBUG
|
||||
NSLog(@"[RNCallKit][CXProviderDelegate][provider:timedOutPerformingAction:]");
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// The bridge might already be invalidated by the time a CallKit event is processed,
|
||||
// just ignore it and don't emit it.
|
||||
- (void)sendEventWithName:(NSString *)name body:(id)body {
|
||||
if (!self.bridge) {
|
||||
return;
|
||||
}
|
||||
|
||||
[super sendEventWithName:name body:body];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -21,6 +21,7 @@ import Foundation
|
||||
internal final class JMCallKitEmitter: NSObject, CXProviderDelegate {
|
||||
|
||||
private let listeners = NSMutableArray()
|
||||
private var pendingMuteActions = Set<UUID>()
|
||||
|
||||
internal override init() {}
|
||||
|
||||
@@ -36,6 +37,12 @@ internal final class JMCallKitEmitter: NSObject, CXProviderDelegate {
|
||||
listeners.remove(listener)
|
||||
}
|
||||
|
||||
// MARK: - Add mute action
|
||||
|
||||
func addMuteAction(_ actionUUID: UUID) {
|
||||
pendingMuteActions.insert(actionUUID)
|
||||
}
|
||||
|
||||
// MARK: - CXProviderDelegate
|
||||
|
||||
func providerDidReset(_ provider: CXProvider) {
|
||||
@@ -43,6 +50,7 @@ internal final class JMCallKitEmitter: NSObject, CXProviderDelegate {
|
||||
let listener = $0 as! JMCallKitListener
|
||||
listener.providerDidReset?()
|
||||
}
|
||||
pendingMuteActions.removeAll()
|
||||
}
|
||||
|
||||
func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
|
||||
@@ -64,9 +72,20 @@ internal final class JMCallKitEmitter: NSObject, CXProviderDelegate {
|
||||
}
|
||||
|
||||
func provider(_ provider: CXProvider, perform action: CXSetMutedCallAction) {
|
||||
listeners.forEach {
|
||||
let listener = $0 as! JMCallKitListener
|
||||
listener.performSetMutedCall?(UUID: action.callUUID, isMuted: action.isMuted)
|
||||
let uuid = pendingMuteActions.remove(action.uuid)
|
||||
|
||||
// Avoid mute actions ping-pong: if the mute action was caused by
|
||||
// the JS side (we requested a transaction) don't call the delegate
|
||||
// method. If it was called by the provder itself (when the user presses
|
||||
// the mute button in the CallKit view) then call the delegate method.
|
||||
//
|
||||
// NOTE: don't try to be clever and remove this. Been there, done that.
|
||||
// Won't work.
|
||||
if (uuid == nil) {
|
||||
listeners.forEach {
|
||||
let listener = $0 as! JMCallKitListener
|
||||
listener.performSetMutedCall?(UUID: action.callUUID, isMuted: action.isMuted)
|
||||
}
|
||||
}
|
||||
|
||||
action.fulfill()
|
||||
|
||||
@@ -160,6 +160,14 @@ import Foundation
|
||||
completion: @escaping (Error?) -> Swift.Void) {
|
||||
guard enabled else { return }
|
||||
|
||||
// XXX keep track of muted actions to avoid "ping-pong"ing. See
|
||||
// JMCallKitEmitter for details on the CXSetMutedCallAction handling.
|
||||
for action in transaction.actions {
|
||||
if (action as? CXSetMutedCallAction) != nil {
|
||||
emitter.addMuteAction(action.uuid)
|
||||
}
|
||||
}
|
||||
|
||||
callController.request(transaction, completion: completion)
|
||||
}
|
||||
|
||||
@@ -187,3 +195,4 @@ import Foundation
|
||||
return update
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -98,11 +98,13 @@ curl -L -o ${CERT_DIR}/AppleWWDRCA.cer 'http://developer.apple.com/certification
|
||||
curl -L -o ${CERT_DIR}/dev-cert.cer.enc ${IOS_DEV_CERT_URL}
|
||||
curl -L -o ${CERT_DIR}/dev-key.p12.enc ${IOS_DEV_CERT_KEY_URL}
|
||||
curl -L -o ${CERT_DIR}/dev-profile.mobileprovision.enc ${IOS_DEV_PROV_PROFILE_URL}
|
||||
curl -L -o ${CERT_DIR}/dev-watch-profile.mobileprovision.enc ${IOS_DEV_WATCH_PROV_PROFILE_URL}
|
||||
curl -L -o ${CERT_DIR}/id_rsa.enc ${DEPLOY_SSH_CERT_URL}
|
||||
|
||||
openssl aes-256-cbc -k "$ENCRYPTION_PASSWORD" -in ${CERT_DIR}/dev-cert.cer.enc -d -a -out ${CERT_DIR}/dev-cert.cer
|
||||
openssl aes-256-cbc -k "$ENCRYPTION_PASSWORD" -in ${CERT_DIR}/dev-key.p12.enc -d -a -out ${CERT_DIR}/dev-key.p12
|
||||
openssl aes-256-cbc -k "$ENCRYPTION_PASSWORD" -in ${CERT_DIR}/dev-profile.mobileprovision.enc -d -a -out ${CERT_DIR}/dev-profile.mobileprovision
|
||||
openssl aes-256-cbc -k "$ENCRYPTION_PASSWORD" -in ${CERT_DIR}/dev-watch-profile.mobileprovision.enc -d -a -out ${CERT_DIR}/dev-watch-profile.mobileprovision
|
||||
openssl aes-256-cbc -k "$ENCRYPTION_PASSWORD" -in ${CERT_DIR}/id_rsa.enc -d -a -out ${CERT_DIR}/id_rsa
|
||||
chmod 0600 ${CERT_DIR}/id_rsa
|
||||
|
||||
@@ -126,9 +128,13 @@ echo "done set-key-partition-list"
|
||||
mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
|
||||
|
||||
cp "${CERT_DIR}/dev-profile.mobileprovision" ~/Library/MobileDevice/Provisioning\ Profiles/
|
||||
cp "${CERT_DIR}/dev-watch-profile.mobileprovision" ~/Library/MobileDevice/Provisioning\ Profiles/
|
||||
|
||||
npm install
|
||||
|
||||
# Ever since the Apple Watch app has been added the bitcode for WebRTC needs to be downloaded in order to build successfully
|
||||
./node_modules/react-native-webrtc/tools/downloadBitcode.sh
|
||||
|
||||
cd ios
|
||||
pod update
|
||||
pod install
|
||||
@@ -146,4 +152,12 @@ xcodebuild -quiet -exportArchive -archivePath /tmp/jitsi-meet/jitsi-meet.xcarchi
|
||||
|
||||
echo "Will try deploy the .ipa to: ${IPA_DEPLOY_LOCATION}"
|
||||
|
||||
scp -i ${CERT_DIR}/id_rsa -o StrictHostKeyChecking=no -o LogLevel=DEBUG "${IPA_EXPORT_DIR}/jitsi-meet.ipa" "${IPA_DEPLOY_LOCATION}"
|
||||
ssh-add ${CERT_DIR}/id_rsa
|
||||
|
||||
if [ ! -z ${SCP_PROXY_HOST} ];
|
||||
then
|
||||
scp -o ProxyCommand="ssh -t -A -l %r ${SCP_PROXY_HOST} -o \"StrictHostKeyChecking no\" -o \"BatchMode yes\" -W %h:%p" -o StrictHostKeyChecking=no -o LogLevel=DEBUG "${IPA_EXPORT_DIR}/jitsi-meet.ipa" "${IPA_DEPLOY_LOCATION}"
|
||||
else
|
||||
scp -o StrictHostKeyChecking=no -o LogLevel=DEBUG "${IPA_EXPORT_DIR}/jitsi-meet.ipa" "${IPA_DEPLOY_LOCATION}"
|
||||
fi
|
||||
|
||||
|
||||
@@ -1,11 +1,19 @@
|
||||
{
|
||||
"en": "Английски",
|
||||
"af": "",
|
||||
"az": "",
|
||||
"bg": "Български",
|
||||
"cs": "",
|
||||
"de": "Немски",
|
||||
"el": "",
|
||||
"eo": "Есперанто",
|
||||
"es": "Испански",
|
||||
"fr": "Френски",
|
||||
"hy": "Арменски",
|
||||
"it": "Италиански",
|
||||
"ja": "",
|
||||
"ko": "",
|
||||
"nb": "Норвежки букмол",
|
||||
"oc": "Окситански",
|
||||
"pl": "Полски",
|
||||
"ptBR": "Португалски (Бразилия)",
|
||||
@@ -14,7 +22,6 @@
|
||||
"sl": "Словенски",
|
||||
"sv": "Шведски",
|
||||
"tr": "Турски",
|
||||
"zhCN": "Китайски (Китай)",
|
||||
"nb": "Норвежки букмол",
|
||||
"eo": "Есперанто"
|
||||
"vi": "",
|
||||
"zhCN": "Китайски (Китай)"
|
||||
}
|
||||
@@ -1,11 +1,18 @@
|
||||
{
|
||||
"en": "Englisch",
|
||||
"az": "",
|
||||
"bg": "Bulgarisch",
|
||||
"cs": "",
|
||||
"de": "Deutsch",
|
||||
"el": "",
|
||||
"eo": "Esperanto",
|
||||
"es": "Spanisch",
|
||||
"fr": "Französisch",
|
||||
"hy": "Armenisch",
|
||||
"it": "Italienisch",
|
||||
"ja": "",
|
||||
"ko": "",
|
||||
"nb": "Norwegisch (Bokmal)",
|
||||
"oc": "Okzitanisch",
|
||||
"pl": "Polnisch",
|
||||
"ptBR": "Portugiesisch (Brasilien)",
|
||||
@@ -14,7 +21,6 @@
|
||||
"sl": "Slowenisch",
|
||||
"sv": "Schwedisch",
|
||||
"tr": "Türkisch",
|
||||
"zhCN": "Chinesisch (China)",
|
||||
"nb": "Norwegisch (Bokmal)",
|
||||
"eo": "Esperanto"
|
||||
"vi": "",
|
||||
"zhCN": "Chinesisch (China)"
|
||||
}
|
||||
27
lang/languages-hr.json
Normal file
27
lang/languages-hr.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"en": "",
|
||||
"af": "",
|
||||
"az": "",
|
||||
"bg": "",
|
||||
"cs": "",
|
||||
"de": "",
|
||||
"el": "",
|
||||
"eo": "",
|
||||
"es": "",
|
||||
"fr": "",
|
||||
"hy": "",
|
||||
"it": "",
|
||||
"ja": "",
|
||||
"ko": "",
|
||||
"nb": "",
|
||||
"oc": "",
|
||||
"pl": "",
|
||||
"ptBR": "",
|
||||
"ru": "",
|
||||
"sk": "",
|
||||
"sl": "",
|
||||
"sv": "",
|
||||
"tr": "",
|
||||
"vi": "",
|
||||
"zhCN": ""
|
||||
}
|
||||
@@ -1,11 +1,19 @@
|
||||
{
|
||||
"en": "Anglés",
|
||||
"af": "Afrikaans",
|
||||
"az": "Azèri",
|
||||
"bg": "Bulgar",
|
||||
"cs": "Chèc",
|
||||
"de": "Aleman",
|
||||
"el": "Grèc",
|
||||
"eo": "Esperanto",
|
||||
"es": "Castelhan",
|
||||
"fr": "Francés",
|
||||
"hy": "Armenian",
|
||||
"it": "Italian",
|
||||
"ja": "Japonés",
|
||||
"ko": "Corean",
|
||||
"nb": "Norvegian Bokmål",
|
||||
"oc": "Occitan",
|
||||
"pl": "Polonés",
|
||||
"ptBR": "Portugués (Brasil)",
|
||||
@@ -14,7 +22,6 @@
|
||||
"sl": "Eslovèn",
|
||||
"sv": "Suedés",
|
||||
"tr": "Turc",
|
||||
"zhCN": "Chinés (China)",
|
||||
"nb": "Norvegian Bokmål",
|
||||
"eo": "Esperanto"
|
||||
"vi": "Vietnamian",
|
||||
"zhCN": "Chinés (China)"
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"en": "Inglês",
|
||||
"af": "Africâner",
|
||||
"az": "Azerbaijanês",
|
||||
"bg": "Búlgaro",
|
||||
"cs": "Checo",
|
||||
|
||||
@@ -1,11 +1,19 @@
|
||||
{
|
||||
"en": "Tiếng Anh",
|
||||
"af": "Tiếng Afrika",
|
||||
"az": "Tiếng Azecbaizan",
|
||||
"bg": "Tiếng Bulgaria",
|
||||
"cs": "Tiếng Séc",
|
||||
"de": "Tiếng Đức",
|
||||
"el": "Tiếng Nhật",
|
||||
"eo": "Tiếng Esperanto",
|
||||
"es": "Tiếng Tây Ban Nha",
|
||||
"fr": "Tiếng Pháp",
|
||||
"hy": "Tiếng Acmenia",
|
||||
"it": "Tiếng Ý",
|
||||
"ja": "Tiếng Nhật",
|
||||
"ko": "Tiếng Hàn",
|
||||
"nb": "Tiếng Na Uy",
|
||||
"oc": "Tiếng Occitan",
|
||||
"pl": "Tiếng Ba Lan",
|
||||
"ptBR": "Tiếng Bồ Đào Nha (Brazil)",
|
||||
@@ -14,7 +22,6 @@
|
||||
"sl": "Tiếng Slovenia",
|
||||
"sv": "Tiếng Thụy Điển",
|
||||
"tr": "Tiếng Thổ Nhĩ Kỳ",
|
||||
"zhCN": "Tiếng Hoa (Trung Quốc)",
|
||||
"nb": "Tiếng Na Uy",
|
||||
"eo": "Tiếng Esperanto"
|
||||
"vi": "Tiếng Việt",
|
||||
"zhCN": "Tiếng Hoa (Trung Quốc)"
|
||||
}
|
||||
1096
lang/main-bg.json
1096
lang/main-bg.json
File diff suppressed because it is too large
Load Diff
@@ -35,31 +35,43 @@
|
||||
"raiseHand": "Hand erheben",
|
||||
"pushToTalk": "Drücken um zu sprechen",
|
||||
"toggleScreensharing": "Zwischen Kamera und Bildschirmfreigabe wechseln",
|
||||
"toggleFilmstrip": "Videos anzeigen oder verbergen",
|
||||
"toggleShortcuts": "Hilfe-Menu anzeigen oder verdecken",
|
||||
"toggleFilmstrip": "",
|
||||
"toggleShortcuts": "",
|
||||
"focusLocal": "Lokales Video fokussieren",
|
||||
"focusRemote": "Auf das Video eines anderen Teilnehmers fokussieren",
|
||||
"toggleChat": "Chat öffnen oder schliessen",
|
||||
"mute": "Stummschaltung aktivieren oder deaktivieren",
|
||||
"fullScreen": "Vollbildmodus aktivieren / deaktivieren",
|
||||
"videoMute": "Kamera starten oder stoppen",
|
||||
"showSpeakerStats": "Statistiken für Sprecher anzeigen"
|
||||
"showSpeakerStats": "Statistiken für Sprecher anzeigen",
|
||||
"localRecording": ""
|
||||
},
|
||||
"\u0005keyboardShortcuts": {},
|
||||
"welcomepage": {
|
||||
"accessibilityLabel": {
|
||||
"join": "",
|
||||
"roomname": "Konferenzname eingeben"
|
||||
},
|
||||
"appDescription": "Auf geht's! Beginne eine Videokonferenz mit dem ganzen Team. Oder eigentlich, lade alle ein die du kennst. __app__ ist eine vollständig verschlüsselte, aus 100% Open-Source-Software bestehende Videokonferenzlösung die du den ganzen Tag kostenlos verwenden kannst — ohne Registrierung.",
|
||||
"audioVideoSwitch": {
|
||||
"audio": "Sprache",
|
||||
"video": "Video"
|
||||
},
|
||||
"calendar": "Kalender",
|
||||
"connectCalendarText": "",
|
||||
"connectCalendarButton": "",
|
||||
"enterRoomTitle": "",
|
||||
"go": "Los",
|
||||
"join": "Beitreten",
|
||||
"privacy": "Privatsphäre",
|
||||
"recentList": "",
|
||||
"recentListDelete": "",
|
||||
"recentListEmpty": "",
|
||||
"roomname": "Konferenzname eingeben",
|
||||
"roomnameHint": "Name oder URL der Konferenz der Sie beitreten möchten. Sie können einen Namen erfinden, er muss nur den anderen Teilnehmern übermittelt werden damit sie der gleichen Konferenz beitreten.",
|
||||
"sendFeedback": "Senden Sie uns Ihr Feedback",
|
||||
"terms": "Bedingungen",
|
||||
"title": "Sichere, flexible und vollständig freie Videokonferenzen"
|
||||
"title": ""
|
||||
},
|
||||
"startupoverlay": {
|
||||
"policyText": "",
|
||||
@@ -71,8 +83,41 @@
|
||||
"rejoinKeyTitle": "Erneut teilnehmen"
|
||||
},
|
||||
"toolbar": {
|
||||
"accessibilityLabel": {
|
||||
"audioOnly": "",
|
||||
"audioRoute": "",
|
||||
"callQuality": "Qualitätseinstellungen",
|
||||
"chat": "",
|
||||
"cc": "",
|
||||
"document": "Geteiltes Dokument schliessen",
|
||||
"feedback": "Feedback hinterlasen",
|
||||
"fullScreen": "",
|
||||
"hangup": "",
|
||||
"invite": "Teilnehmer einladen",
|
||||
"localRecording": "",
|
||||
"lockRoom": "",
|
||||
"moreActions": "",
|
||||
"moreActionsMenu": "",
|
||||
"mute": "",
|
||||
"pip": "",
|
||||
"profile": "Profil bearbeiten",
|
||||
"raiseHand": "",
|
||||
"recording": "",
|
||||
"Settings": "",
|
||||
"sharedvideo": "",
|
||||
"shareRoom": "",
|
||||
"shareYourScreen": "",
|
||||
"shortcuts": "",
|
||||
"speakerStats": "",
|
||||
"toggleCamera": "",
|
||||
"tileView": "",
|
||||
"videomute": ""
|
||||
},
|
||||
"addPeople": "Teilnehmer zur Konferenz hinzufügen",
|
||||
"audioonly": "Nur-Audio-Modus aktivieren/deaktivieren (spart Bandbreite)",
|
||||
"audioOnlyOn": "Nur-Audio-Modus aktivieren/deaktivieren (spart Bandbreite)",
|
||||
"audioOnlyOff": "",
|
||||
"audioRoute": "",
|
||||
"callQuality": "Qualitätseinstellungen",
|
||||
"enterFullScreen": "Vollbildmodus",
|
||||
"exitFullScreen": "Vollbildmodus verlassen",
|
||||
@@ -86,8 +131,8 @@
|
||||
"etherpad": "Geteiltes Dokument öffnen / schliessen",
|
||||
"documentOpen": "Geteiltes Dokument öffnen",
|
||||
"documentClose": "Geteiltes Dokument schliessen",
|
||||
"shareRoom": "",
|
||||
"sharedvideo": "YouTube-Video teilen",
|
||||
"sharescreen": "Bildschirmfreigabe",
|
||||
"stopSharedVideo": "YouTube Video stoppen",
|
||||
"fullscreen": "Vollbildmodus aktivieren / deaktivieren",
|
||||
"sip": "SIP Nummer anrufen",
|
||||
@@ -96,25 +141,40 @@
|
||||
"login": "Anmelden",
|
||||
"logout": "Abmelden",
|
||||
"sharedVideoMutedPopup": "Das freigegebene Video wurde stumm geschaltet um mit den anderen Teilnehmern zu sprechen.",
|
||||
"toggleCamera": "",
|
||||
"micMutedPopup": "Das Mikrofon wurde stumm geschaltet um das freigegebene Video geniessen zu können.",
|
||||
"talkWhileMutedPopup": "Versuchen sie zu sprechen? Ihr Mikrofon ist stummgeschaltet.",
|
||||
"unableToUnmutePopup": "Die Stummschaltung kann nicht aufgehoben werden während das geteilte Video abgespielt wird.",
|
||||
"cameraDisabled": "Keine Kamera verfügbar",
|
||||
"micDisabled": "Kein Mikrofon verfügbar",
|
||||
"filmstrip": "Videos anzeigen / verbergen",
|
||||
"pip": "",
|
||||
"profile": "Profil bearbeiten",
|
||||
"raiseHand": "Hand erheben",
|
||||
"shortcuts": "Tastenkürzel anzeigen",
|
||||
"speakerStats": "Sprecher-Statistiken"
|
||||
"speakerStats": "Sprecher-Statistiken",
|
||||
"tileViewToggle": "",
|
||||
"invite": "Teilnehmer einladen"
|
||||
},
|
||||
"\u0005toolbar": {
|
||||
"accessibilityLabel": {}
|
||||
},
|
||||
"chat": {
|
||||
"nickname": {
|
||||
"title": "Name eingeben",
|
||||
"popover": "Name"
|
||||
},
|
||||
"error": "",
|
||||
"messagebox": "Text eingeben..."
|
||||
},
|
||||
"settings": {
|
||||
"calendar": {
|
||||
"about": "",
|
||||
"disconnect": "Getrennt",
|
||||
"microsoftSignIn": "",
|
||||
"signedIn": "",
|
||||
"title": "Kalender"
|
||||
},
|
||||
"title": "Einstellungen",
|
||||
"update": "Aktualisieren",
|
||||
"name": "Name",
|
||||
@@ -124,11 +184,18 @@
|
||||
"selectMic": "Mikrofon",
|
||||
"selectAudioOutput": "Audioausgabe",
|
||||
"followMe": "Follow-me für alle Teilnehmer",
|
||||
"language": "",
|
||||
"loggedIn": "",
|
||||
"noDevice": "Kein",
|
||||
"cameraAndMic": "Kamera und Mikrofon",
|
||||
"moderator": "MODERATOR",
|
||||
"moderator": "Moderator",
|
||||
"more": "",
|
||||
"password": "PASSWORT SETZEN",
|
||||
"audioVideo": "AUDIO UND VIDEO"
|
||||
"audioVideo": "AUDIO UND VIDEO",
|
||||
"devices": ""
|
||||
},
|
||||
"\u0005settings": {
|
||||
"calendar": {}
|
||||
},
|
||||
"profile": {
|
||||
"title": "Profil",
|
||||
@@ -148,7 +215,9 @@
|
||||
},
|
||||
"connectionindicator": {
|
||||
"header": "Verbindungsdaten",
|
||||
"connectedTo": "",
|
||||
"bitrate": "Bitrate:",
|
||||
"bridgeCount": "",
|
||||
"packetloss": "Paketverlust:",
|
||||
"resolution": "Auflösung:",
|
||||
"framerate": "Bildwiederholrate:",
|
||||
@@ -187,7 +256,6 @@
|
||||
"focus": "Konferenz-Organisator",
|
||||
"focusFail": "__component__ ist im Moment nicht verfügbar - wiederholen in __ms__ Sekunden",
|
||||
"grantedTo": "Moderatorenrechte an __to__ vergeben.",
|
||||
"grantedToUnknown": "Moderatorenrechte an $t(notify.somebody) vergeben.",
|
||||
"muted": "Der Konferenz wurde stumm beigetreten.",
|
||||
"mutedTitle": "Stummschaltung aktiv!",
|
||||
"raisedHand": "Möchte sprechen.",
|
||||
@@ -195,8 +263,13 @@
|
||||
"suboptimalExperienceDescription": "Tut uns leid, aber die Konferenz wird mit __appName__ kein grossartiges Erlebnis. Wir versuchen immer die Situation zu verbessern, bis dahin empfehlen wir aber die Verwendung einer der <a href=\"static/recommendedBrowsers.html\" target=\"_blank\">vollständig unterstützen Browser</a>."
|
||||
},
|
||||
"dialog": {
|
||||
"accessibilityLabel": {
|
||||
"liveStreaming": "Livestream:"
|
||||
},
|
||||
"allow": "Erlauben",
|
||||
"confirm": "",
|
||||
"kickMessage": "Oh! Sie wurden aus der Konferenz ausgeschlossen.",
|
||||
"kickTitle": "",
|
||||
"popupErrorTitle": "Popup blockiert",
|
||||
"popupError": "Ihr Browser blockiert Popups von dieser Website. Bitte aktivieren Sie Popups in den Sicherheitseinstellungen des Browsers und versuchen Sie es erneut.",
|
||||
"passwordErrorTitle": "Passwort-Fehler",
|
||||
@@ -219,6 +292,7 @@
|
||||
"rejoinNow": "Jetzt erneut beitreten",
|
||||
"maxUsersLimitReachedTitle": "Maximale Anzahl Teilnehmer ist erreicht",
|
||||
"maxUsersLimitReached": "Das Limit der maximalen Anzahl Teilnehmer wurde erreicht. Die Konferenz ist voll. Bitte benachrichtigen Sie den Konferenzorganisator oder versuchen Sie es später noch einmal.",
|
||||
"lockRoom": "",
|
||||
"lockTitle": "Sperren fehlgeschlagen",
|
||||
"lockMessage": "Die Konferenz konnte nicht gesperrt werden.",
|
||||
"warning": "Warnung",
|
||||
@@ -266,6 +340,7 @@
|
||||
"reservationError": "Fehler im Reservationssystem",
|
||||
"reservationErrorMsg": "Fehler, Nummer: __code__, Nachricht: __msg__",
|
||||
"password": "Passwort eingeben",
|
||||
"unlockRoom": "",
|
||||
"userPassword": "Benutzerpasswort",
|
||||
"token": "Token",
|
||||
"tokenAuthFailedTitle": "Authentifizierung fehlgeschlagen",
|
||||
@@ -278,7 +353,7 @@
|
||||
"sorryFeedback": "Tut uns leid. Möchten Sie uns mehr mitteilen?",
|
||||
"liveStreaming": "Live-Streaming",
|
||||
"streamKey": "Name/Schlüssel für den Stream",
|
||||
"startLiveStreaming": "Live-Streaming starten",
|
||||
"startLiveStreaming": "Einen Livestream starten",
|
||||
"startRecording": "Aufnahme starten",
|
||||
"stopStreamingWarning": "Sind Sie sicher dass Sie das Live-Streaming stoppen möchten?",
|
||||
"stopRecordingWarning": "Sind Sie sicher dass Sie die Aufnahme stoppen möchten?",
|
||||
@@ -312,6 +387,10 @@
|
||||
"muteParticipantTitle": "Diesen Teilnehmer stummschalten?",
|
||||
"muteParticipantBody": "Sie können die Stummschaltung anderer Teilnehmer nicht aufheben, aber ein Teilnehmer kann seine eigene Stummschaltung jederzeit beenden.",
|
||||
"muteParticipantButton": "Stummschalten",
|
||||
"liveStreamingDisabledTooltip": "",
|
||||
"liveStreamingDisabledForGuestTooltip": "",
|
||||
"recordingDisabledTooltip": "",
|
||||
"recordingDisabledForGuestTooltip": "",
|
||||
"remoteControlTitle": "Fernsteuerung",
|
||||
"remoteControlRequestMessage": "Möchten Sie __user__ erlauben den Computer fernzusteuern?",
|
||||
"remoteControlShareScreenWarning": "Achtung, wenn Sie die Anfrage genehmigen starten Sie die Bildschirmfreigabe!",
|
||||
@@ -322,10 +401,15 @@
|
||||
"remoteControlStopMessage": "Die Fernsteuerung wurde beendet.",
|
||||
"close": "Schliessen",
|
||||
"shareYourScreen": "Bildschirm freigeben",
|
||||
"shareYourScreenDisabled": "",
|
||||
"shareYourScreenDisabledForGuest": "",
|
||||
"yourEntireScreen": "Ganzer Bildschirm",
|
||||
"applicationWindow": "Anwendungsfenster"
|
||||
"applicationWindow": "Anwendungsfenster",
|
||||
"transcribing": ""
|
||||
},
|
||||
"\u0005dialog": {
|
||||
"accessibilityLabel": {}
|
||||
},
|
||||
"\u0005dialog": {},
|
||||
"email": {
|
||||
"sharedKey": [
|
||||
"Diese Konferenz ist passwortgeschützt. Bitte verwenden Sie diese PIN zum Beitreten:",
|
||||
@@ -357,6 +441,10 @@
|
||||
],
|
||||
"and": "und"
|
||||
},
|
||||
"share": {
|
||||
"mainText": "",
|
||||
"dialInfoText": ""
|
||||
},
|
||||
"connection": {
|
||||
"ERROR": "Fehler",
|
||||
"CONNECTING": "Verbindung wird hergestellt",
|
||||
@@ -370,18 +458,44 @@
|
||||
"ATTACHED": "Angehängt"
|
||||
},
|
||||
"recording": {
|
||||
"beta": "",
|
||||
"busy": "Es werden Resourcen für eine Aufnahme bereitgestellt. Bitte in ein paar Minuten erneut versuchen.",
|
||||
"busyTitle": "Alle Aufnahme-Instanzen sind in Gebrauch",
|
||||
"buttonTooltip": "Aufnahme starten / stoppen",
|
||||
"error": "Die Aufzeichnung ist fehlgeschlagen. Bitte versuchen Sie es erneut.",
|
||||
"expandedOff": "",
|
||||
"expandedOn": "",
|
||||
"expandedPending": "",
|
||||
"failedToStart": "Die Aufnahme konnte nicht gestartet werden",
|
||||
"live": "",
|
||||
"off": "Aufnahme gestoppt",
|
||||
"on": "Aufnahme",
|
||||
"pending": "Die Aufnahme wartet auf den Beitritt eines Teilnehmers...",
|
||||
"pending": "",
|
||||
"rec": "",
|
||||
"authDropboxText": "",
|
||||
"serviceName": "Aufnahmedienst",
|
||||
"signOut": "",
|
||||
"signIn": "",
|
||||
"loggedIn": "",
|
||||
"availableSpace": "",
|
||||
"startRecordingBody": "Sind Sie sicher dass Sie die Aufnahme stoppen möchten?",
|
||||
"unavailable": "Oh! Der __serviceName__ ist aktuell nicht verfügbar. Wir arbeiten an der Behebung des Problems. Bitte versuchen Sie es später noch einmal.",
|
||||
"unavailableTitle": "Aufnahme nicht verfügbar"
|
||||
},
|
||||
"\u0005recording": {},
|
||||
"transcribing": {
|
||||
"pending": "",
|
||||
"off": "",
|
||||
"error": "Die Aufzeichnung ist fehlgeschlagen. Bitte versuchen Sie es erneut.",
|
||||
"expandedLabel": "",
|
||||
"failedToStart": "",
|
||||
"tr": "",
|
||||
"labelToolTip": "",
|
||||
"ccButtonTooltip": "",
|
||||
"start": "",
|
||||
"stop": ""
|
||||
},
|
||||
"\u0005transcribing": {},
|
||||
"liveStreaming": {
|
||||
"busy": "Es werden Resourcen zum Streamen bereitgestellt. Bitte in ein paar Minuten erneut versuchen.",
|
||||
"busyTitle": "Alle Streaming-Instanzen sind in Gebrauch",
|
||||
@@ -392,17 +506,24 @@
|
||||
"enterStreamKey": "Name/Schlüssel für den YouTube Livestream hier eingeben.",
|
||||
"error": "Das Live-Streaming ist fehlgeschlagen. Bitte versuchen Sie es erneut.",
|
||||
"errorAPI": "Beim abrufen der YouTube Livestreams ist ein Fehler aufgetreten. Bitte versuchen Sie sich erneut anzumelden.",
|
||||
"errorLiveStreamNotEnabled": "",
|
||||
"expandedOff": "",
|
||||
"expandedOn": "",
|
||||
"expandedPending": "",
|
||||
"failedToStart": "Live-Streaming konnte nicht gestartet werden",
|
||||
"off": "Live-Streaming gestoppt",
|
||||
"on": "Live-Streaming",
|
||||
"pending": "Live-Stream wird gestartet...",
|
||||
"serviceName": "Live Streaming-Dienst",
|
||||
"signedInAs": "",
|
||||
"signIn": "Mit Google anmelden",
|
||||
"signOut": "",
|
||||
"signInCTA": "Anmelden oder den Name/Schlüssel des YouTube Livestreams eingeben.",
|
||||
"start": "Einen Livestream starten",
|
||||
"streamIdHelp": "Was ist das?",
|
||||
"unavailableTitle": "Live-Streaming nicht verfügbar"
|
||||
},
|
||||
"\u0005liveStreaming": {},
|
||||
"videoSIPGW": {
|
||||
"busy": "Es stehen keine freien Ressourcen zur Verfügung. Bitte versuchen Sie es später noch einmal.",
|
||||
"busyTitle": "Keine freien Ressourcen",
|
||||
@@ -428,9 +549,11 @@
|
||||
"noPermission": "Berechtigungen nicht erteilt",
|
||||
"previewUnavailable": "Keine Vorschau verfügbar",
|
||||
"selectADevice": "Ein Gerät wählen",
|
||||
"testAudio": "Audio testen"
|
||||
"testAudio": ""
|
||||
},
|
||||
"videoStatus": {
|
||||
"audioOnly": "",
|
||||
"audioOnlyExpanded": "",
|
||||
"callQuality": "Konferenzqualität",
|
||||
"hd": "HD",
|
||||
"hdTooltip": "Video wird in HD angezeigt",
|
||||
@@ -458,7 +581,8 @@
|
||||
"add": "Einladen",
|
||||
"countryNotSupported": "Wir unterstützen dieses Land noch nicht.",
|
||||
"countryReminder": "Telefonnummer nicht in den USA? Bitte sicherstellen, dass die Telefonnummer mit dem Ländercode beginnt.",
|
||||
"disabled": "",
|
||||
"disabled": "Sie können keine Teilnehmer einladen.",
|
||||
"footerText": "",
|
||||
"invite": "Einladen",
|
||||
"loading": "Suche nach Teilnehmern und Telefonnummern",
|
||||
"loadingNumber": "Telefonnummer wird überprüft",
|
||||
@@ -495,6 +619,7 @@
|
||||
"veryGood": "Sehr gut"
|
||||
},
|
||||
"info": {
|
||||
"accessibilityLabel": "",
|
||||
"addPassword": "Passwort hinzufügen",
|
||||
"cancelPassword": "Password abbrechen",
|
||||
"conferenceURL": "Link:",
|
||||
@@ -516,7 +641,7 @@
|
||||
"numbers": "Einwählnummern",
|
||||
"password": "Passwort:",
|
||||
"title": "Teilen",
|
||||
"tooltip": "Zugriffsinformationen über die Konferenz abrufen"
|
||||
"tooltip": ""
|
||||
},
|
||||
"settingsView": {
|
||||
"alertOk": "OK",
|
||||
@@ -532,17 +657,22 @@
|
||||
"startWithVideoMuted": "Ohne Video beitreten"
|
||||
},
|
||||
"calendarSync": {
|
||||
"later": "Später",
|
||||
"next": "Folgend",
|
||||
"addMeetingURL": "",
|
||||
"confirmAddLink": "",
|
||||
"confirmAddLinkTitle": "Kalender",
|
||||
"join": "",
|
||||
"joinTooltip": "",
|
||||
"nextMeeting": "Nächste Konferenz",
|
||||
"now": "Jetzt",
|
||||
"noEvents": "",
|
||||
"ongoingMeeting": "",
|
||||
"permissionButton": "Einstellungen öffnen",
|
||||
"permissionMessage": "Die App benötigt Zugriff auf den Kalender um die Termine und Konferenzen anzuzeigen."
|
||||
"permissionMessage": "Die App benötigt Zugriff auf den Kalender um die Termine und Konferenzen anzuzeigen.",
|
||||
"refresh": "",
|
||||
"today": "Heute"
|
||||
},
|
||||
"\u0005calendarSync": {},
|
||||
"recentList": {
|
||||
"today": "Heute",
|
||||
"yesterday": "Gestern",
|
||||
"earlier": "Früher"
|
||||
"joinPastMeeting": ""
|
||||
},
|
||||
"sectionList": {
|
||||
"pullToRefresh": "Ziehen um zu aktualisieren"
|
||||
@@ -555,5 +685,62 @@
|
||||
"appNotInstalled": "Sie benötigen die __app__ App um der Konferenz auf dem Smartphone beizutreten.",
|
||||
"downloadApp": "App herunterladen",
|
||||
"openApp": "In der App fortfahren"
|
||||
}
|
||||
},
|
||||
"presenceStatus": {
|
||||
"invited": "Einladen",
|
||||
"ringing": "",
|
||||
"calling": "",
|
||||
"initializingCall": "",
|
||||
"connected": "Verbunden",
|
||||
"connecting": "Verbindung wird hergestellt",
|
||||
"connecting2": "",
|
||||
"disconnected": "Getrennt",
|
||||
"busy": "",
|
||||
"rejected": "",
|
||||
"ignored": "",
|
||||
"expired": ""
|
||||
},
|
||||
"\u0005presenceStatus": {},
|
||||
"dateUtils": {
|
||||
"today": "Heute",
|
||||
"yesterday": "Gestern",
|
||||
"earlier": "Früher"
|
||||
},
|
||||
"incomingCall": {
|
||||
"answer": "",
|
||||
"audioCallTitle": "",
|
||||
"decline": "OK",
|
||||
"productLabel": "",
|
||||
"videoCallTitle": ""
|
||||
},
|
||||
"localRecording": {
|
||||
"localRecording": "",
|
||||
"dialogTitle": "",
|
||||
"start": "Aufnahme starten",
|
||||
"stop": "Aufnahme stoppen",
|
||||
"moderator": "Moderator",
|
||||
"me": "Ich",
|
||||
"duration": "",
|
||||
"durationNA": "",
|
||||
"encoding": "",
|
||||
"participantStats": "",
|
||||
"participant": "Teilnehmer",
|
||||
"sessionToken": "",
|
||||
"clientState": {
|
||||
"on": "",
|
||||
"off": "",
|
||||
"unknown": ""
|
||||
},
|
||||
"messages": {
|
||||
"engaged": "",
|
||||
"finished": "",
|
||||
"finishedModerator": "",
|
||||
"notModerator": ""
|
||||
},
|
||||
"yes": "Ja",
|
||||
"no": "",
|
||||
"label": "",
|
||||
"labelToolTip": ""
|
||||
},
|
||||
"\u0005localRecording": {}
|
||||
}
|
||||
746
lang/main-hr.json
Normal file
746
lang/main-hr.json
Normal file
@@ -0,0 +1,746 @@
|
||||
{
|
||||
"addPeople": {
|
||||
"add": "",
|
||||
"countryNotSupported": "",
|
||||
"countryReminder": "",
|
||||
"disabled": "",
|
||||
"failedToAdd": "",
|
||||
"footerText": "",
|
||||
"invite": "",
|
||||
"loading": "",
|
||||
"loadingNumber": "",
|
||||
"loadingPeople": "",
|
||||
"noResults": "",
|
||||
"notAvailable": "",
|
||||
"noValidNumbers": "",
|
||||
"searchNumbers": "",
|
||||
"searchPeople": "",
|
||||
"searchPeopleAndNumbers": "",
|
||||
"telephone": "",
|
||||
"title": ""
|
||||
},
|
||||
"audioDevices": {
|
||||
"bluetooth": "",
|
||||
"headphones": "",
|
||||
"phone": "",
|
||||
"speaker": ""
|
||||
},
|
||||
"audioOnly": {
|
||||
"audioOnly": "",
|
||||
"featureToggleDisabled": ""
|
||||
},
|
||||
"calendarSync": {
|
||||
"addMeetingURL": "",
|
||||
"confirmAddLink": "",
|
||||
"confirmAddLinkTitle": "",
|
||||
"error": {
|
||||
"appConfiguration": "",
|
||||
"generic": "",
|
||||
"notSignedIn": ""
|
||||
},
|
||||
"join": "",
|
||||
"joinTooltip": "",
|
||||
"nextMeeting": "",
|
||||
"noEvents": "",
|
||||
"ongoingMeeting": "",
|
||||
"permissionButton": "",
|
||||
"permissionMessage": "",
|
||||
"refresh": "",
|
||||
"today": ""
|
||||
},
|
||||
"chat": {
|
||||
"error": "",
|
||||
"messagebox": "",
|
||||
"nickname": {
|
||||
"popover": "",
|
||||
"title": ""
|
||||
},
|
||||
"title": ""
|
||||
},
|
||||
"connection": {
|
||||
"ATTACHED": "",
|
||||
"AUTHENTICATING": "",
|
||||
"AUTHFAIL": "",
|
||||
"CONNECTED": "",
|
||||
"CONNECTING": "",
|
||||
"CONNFAIL": "",
|
||||
"DISCONNECTED": "",
|
||||
"DISCONNECTING": "",
|
||||
"ERROR": "",
|
||||
"RECONNECTING": ""
|
||||
},
|
||||
"connectionindicator": {
|
||||
"address": "",
|
||||
"bandwidth": "",
|
||||
"bitrate": "",
|
||||
"bridgeCount": "",
|
||||
"connectedTo": "",
|
||||
"framerate": "",
|
||||
"header": "",
|
||||
"less": "",
|
||||
"localaddress": "",
|
||||
"localaddress_plural_2": "",
|
||||
"localport": "",
|
||||
"localport_plural_2": "",
|
||||
"more": "",
|
||||
"na": "",
|
||||
"packetloss": "",
|
||||
"quality": {
|
||||
"good": "",
|
||||
"inactive": "",
|
||||
"lost": "",
|
||||
"nonoptimal": "",
|
||||
"poor": ""
|
||||
},
|
||||
"remoteaddress": "",
|
||||
"remoteaddress_plural_2": "",
|
||||
"remoteport": "",
|
||||
"remoteport_plural_2": "",
|
||||
"resolution": "",
|
||||
"status": "",
|
||||
"transport": "",
|
||||
"transport_plural_2": "",
|
||||
"turn": ""
|
||||
},
|
||||
"contactlist_plural": "",
|
||||
"dateUtils": {
|
||||
"earlier": "",
|
||||
"today": "",
|
||||
"yesterday": ""
|
||||
},
|
||||
"deepLinking": {
|
||||
"appNotInstalled": "",
|
||||
"description": "",
|
||||
"downloadApp": "",
|
||||
"launchWebButton": "",
|
||||
"openApp": "",
|
||||
"title": "",
|
||||
"tryAgainButton": ""
|
||||
},
|
||||
"defaultLink": "",
|
||||
"defaultNickname": "",
|
||||
"deviceError": {
|
||||
"cameraError": "",
|
||||
"cameraPermission": "",
|
||||
"microphoneError": "",
|
||||
"microphonePermission": ""
|
||||
},
|
||||
"deviceSelection": {
|
||||
"deviceSettings": "",
|
||||
"noPermission": "",
|
||||
"previewUnavailable": "",
|
||||
"selectADevice": "",
|
||||
"testAudio": ""
|
||||
},
|
||||
"dialog": {
|
||||
"accessibilityLabel": {
|
||||
"liveStreaming": ""
|
||||
},
|
||||
"allow": "",
|
||||
"alreadySharedVideoMsg": "",
|
||||
"alreadySharedVideoTitle": "",
|
||||
"applicationWindow": "",
|
||||
"Back": "",
|
||||
"cameraConstraintFailedError": "",
|
||||
"cameraNotFoundError": "",
|
||||
"cameraNotSendingData": "",
|
||||
"cameraNotSendingDataTitle": "",
|
||||
"cameraPermissionDeniedError": "",
|
||||
"cameraUnknownError": "",
|
||||
"cameraUnsupportedResolutionError": "",
|
||||
"Cancel": "",
|
||||
"close": "",
|
||||
"conferenceDisconnectMsg": "",
|
||||
"conferenceDisconnectTitle": "",
|
||||
"conferenceReloadMsg": "",
|
||||
"conferenceReloadTitle": "",
|
||||
"confirm": "",
|
||||
"confirmNo": "",
|
||||
"confirmYes": "",
|
||||
"connectError": "",
|
||||
"connectErrorWithMsg": "",
|
||||
"connecting": "",
|
||||
"contactSupport": "",
|
||||
"copy": "",
|
||||
"currentPassword": "",
|
||||
"defaultError": "",
|
||||
"detectext": "",
|
||||
"dismiss": "",
|
||||
"displayNameRequired": "",
|
||||
"done": "",
|
||||
"doNotShowMessageAgain": "",
|
||||
"enterDisplayName": "",
|
||||
"error": "",
|
||||
"externalInstallationMsg": "",
|
||||
"externalInstallationTitle": "",
|
||||
"failedpermissions": "",
|
||||
"feedbackHelp": "",
|
||||
"feedbackQuestion": "",
|
||||
"goToStore": "",
|
||||
"gracefulShutdown": "",
|
||||
"hungUp": "",
|
||||
"IamHost": "",
|
||||
"incorrectPassword": "",
|
||||
"inlineInstallationMsg": "",
|
||||
"inlineInstallExtension": "",
|
||||
"internalError": "",
|
||||
"internalErrorTitle": "",
|
||||
"joinAgain": "",
|
||||
"kickMessage": "",
|
||||
"kickParticipantButton": "",
|
||||
"kickParticipantDialog": "",
|
||||
"kickParticipantTitle": "",
|
||||
"kickTitle": "",
|
||||
"liveStreaming": "",
|
||||
"liveStreamingDisabledForGuestTooltip": "",
|
||||
"liveStreamingDisabledTooltip": "",
|
||||
"lockMessage": "",
|
||||
"lockRoom": "",
|
||||
"lockTitle": "",
|
||||
"logoutQuestion": "",
|
||||
"logoutTitle": "",
|
||||
"maxUsersLimitReached": "",
|
||||
"maxUsersLimitReachedTitle": "",
|
||||
"micConstraintFailedError": "",
|
||||
"micNotFoundError": "",
|
||||
"micNotSendingData": "",
|
||||
"micNotSendingDataTitle": "",
|
||||
"micPermissionDeniedError": "",
|
||||
"micUnknownError": "",
|
||||
"muteParticipantBody": "",
|
||||
"muteParticipantButton": "",
|
||||
"muteParticipantDialog": "",
|
||||
"muteParticipantTitle": "",
|
||||
"Ok": "",
|
||||
"oops": "",
|
||||
"password": "",
|
||||
"passwordError": "",
|
||||
"passwordError2": "",
|
||||
"passwordErrorTitle": "",
|
||||
"passwordLabel": "",
|
||||
"passwordNotSupported": "",
|
||||
"passwordNotSupportedTitle": "",
|
||||
"passwordRequired": "",
|
||||
"permissionDenied": "",
|
||||
"popupError": "",
|
||||
"popupErrorTitle": "",
|
||||
"recording": "",
|
||||
"recordingDisabledForGuestTooltip": "",
|
||||
"recordingDisabledTooltip": "",
|
||||
"recordingToken": "",
|
||||
"rejoinNow": "",
|
||||
"remoteControlAllowedMessage": "",
|
||||
"remoteControlDeniedMessage": "",
|
||||
"remoteControlErrorMessage": "",
|
||||
"remoteControlRequestMessage": "",
|
||||
"remoteControlShareScreenWarning": "",
|
||||
"remoteControlStopMessage": "",
|
||||
"remoteControlTitle": "",
|
||||
"Remove": "",
|
||||
"removePassword": "",
|
||||
"removeSharedVideoMsg": "",
|
||||
"removeSharedVideoTitle": "",
|
||||
"reservationError": "",
|
||||
"reservationErrorMsg": "",
|
||||
"retry": "",
|
||||
"Save": "",
|
||||
"screenSharingFailedToInstall": "",
|
||||
"screenSharingFailedToInstallTitle": "",
|
||||
"screenSharingFirefoxPermissionDeniedError": "",
|
||||
"screenSharingFirefoxPermissionDeniedTitle": "",
|
||||
"screenSharingPermissionDeniedError": "",
|
||||
"serviceUnavailable": "",
|
||||
"sessTerminated": "",
|
||||
"Share": "",
|
||||
"shareVideoLinkError": "",
|
||||
"shareVideoTitle": "",
|
||||
"shareYourScreen": "",
|
||||
"shareYourScreenDisabled": "",
|
||||
"shareYourScreenDisabledForGuest": "",
|
||||
"SLDFailure": "",
|
||||
"sorryFeedback": "",
|
||||
"SRDFailure": "",
|
||||
"startLiveStreaming": "",
|
||||
"startRecording": "",
|
||||
"startRemoteControlErrorMessage": "",
|
||||
"stopLiveStreaming": "",
|
||||
"stopRecording": "",
|
||||
"stopRecordingWarning": "",
|
||||
"stopStreamingWarning": "",
|
||||
"streamKey": "",
|
||||
"Submit": "",
|
||||
"thankYou": "",
|
||||
"token": "",
|
||||
"tokenAuthFailed": "",
|
||||
"tokenAuthFailedTitle": "",
|
||||
"transcribing": "",
|
||||
"unableToSwitch": "",
|
||||
"unlockRoom": "",
|
||||
"userPassword": "",
|
||||
"WaitForHostMsg": "",
|
||||
"WaitForHostMsgWOk": "",
|
||||
"WaitingForHost": "",
|
||||
"warning": "",
|
||||
"Yes": "",
|
||||
"yourEntireScreen": ""
|
||||
},
|
||||
"dialOut": {
|
||||
"statusMessage": ""
|
||||
},
|
||||
"email": {
|
||||
"and": "",
|
||||
"body": "",
|
||||
"sharedKey": "",
|
||||
"subject": ""
|
||||
},
|
||||
"feedback": {
|
||||
"average": "",
|
||||
"bad": "",
|
||||
"detailsLabel": "",
|
||||
"good": "",
|
||||
"rateExperience": "",
|
||||
"veryBad": "",
|
||||
"veryGood": ""
|
||||
},
|
||||
"incomingCall": {
|
||||
"answer": "",
|
||||
"audioCallTitle": "",
|
||||
"decline": "",
|
||||
"productLabel": "",
|
||||
"videoCallTitle": ""
|
||||
},
|
||||
"info": {
|
||||
"accessibilityLabel": "",
|
||||
"addPassword": "",
|
||||
"cancelPassword": "",
|
||||
"conferenceURL": "",
|
||||
"country": "",
|
||||
"dialANumber": "",
|
||||
"dialInConferenceID": "",
|
||||
"dialInNotSupported": "",
|
||||
"dialInNumber": "",
|
||||
"dialInTollFree": "",
|
||||
"genericError": "",
|
||||
"inviteLiveStream": "",
|
||||
"invitePhone": "",
|
||||
"invitePhoneAlternatives": "",
|
||||
"inviteURL": "",
|
||||
"liveStreamURL": "",
|
||||
"moreNumbers": "",
|
||||
"noNumbers": "",
|
||||
"noPassword": "",
|
||||
"noRoom": "",
|
||||
"numbers": "",
|
||||
"password": "",
|
||||
"title": "",
|
||||
"tooltip": "",
|
||||
"label": ""
|
||||
},
|
||||
"inviteDialog": {
|
||||
"alertOk": "",
|
||||
"alertText": "",
|
||||
"alertTitle": "",
|
||||
"header": "",
|
||||
"searchCallOnlyPlaceholder": "",
|
||||
"searchPeopleOnlyPlaceholder": "",
|
||||
"searchPlaceholder": "",
|
||||
"send": ""
|
||||
},
|
||||
"inlineDialogFailure": {
|
||||
"msg": "",
|
||||
"retry": "",
|
||||
"support": "",
|
||||
"supportMsg": ""
|
||||
},
|
||||
"inviteUrlDefaultMsg": "",
|
||||
"keyboardShortcuts": {
|
||||
"focusLocal": "",
|
||||
"focusRemote": "",
|
||||
"fullScreen": "",
|
||||
"keyboardShortcuts": "",
|
||||
"localRecording": "",
|
||||
"mute": "",
|
||||
"pushToTalk": "",
|
||||
"raiseHand": "",
|
||||
"showSpeakerStats": "",
|
||||
"toggleChat": "",
|
||||
"toggleFilmstrip": "",
|
||||
"toggleScreensharing": "",
|
||||
"toggleShortcuts": "",
|
||||
"videoMute": ""
|
||||
},
|
||||
"liveStreaming": {
|
||||
"busy": "",
|
||||
"busyTitle": "",
|
||||
"buttonTooltip": "",
|
||||
"changeSignIn": "",
|
||||
"choose": "",
|
||||
"chooseCTA": "",
|
||||
"enterStreamKey": "",
|
||||
"error": "",
|
||||
"errorAPI": "",
|
||||
"errorLiveStreamNotEnabled": "",
|
||||
"expandedOff": "",
|
||||
"expandedOn": "",
|
||||
"expandedPending": "",
|
||||
"failedToStart": "",
|
||||
"getStreamKeyManually": "",
|
||||
"invalidStreamKey": "",
|
||||
"off": "",
|
||||
"on": "",
|
||||
"pending": "",
|
||||
"serviceName": "",
|
||||
"signedInAs": "",
|
||||
"signIn": "",
|
||||
"signInCTA": "",
|
||||
"signOut": "",
|
||||
"start": "",
|
||||
"streamIdHelp": "",
|
||||
"unavailableTitle": ""
|
||||
},
|
||||
"localRecording": {
|
||||
"clientState": {
|
||||
"off": "",
|
||||
"on": "",
|
||||
"unknown": ""
|
||||
},
|
||||
"dialogTitle": "",
|
||||
"duration": "",
|
||||
"durationNA": "",
|
||||
"encoding": "",
|
||||
"label": "",
|
||||
"labelToolTip": "",
|
||||
"localRecording": "",
|
||||
"me": "",
|
||||
"messages": {
|
||||
"engaged": "",
|
||||
"finished": "",
|
||||
"finishedModerator": "",
|
||||
"notModerator": ""
|
||||
},
|
||||
"moderator": "",
|
||||
"no": "",
|
||||
"participant": "",
|
||||
"participantStats": "",
|
||||
"sessionToken": "",
|
||||
"start": "",
|
||||
"stop": "",
|
||||
"yes": ""
|
||||
},
|
||||
"me": "",
|
||||
"notify": {
|
||||
"connectedOneMember": "",
|
||||
"connectedThreePlusMembers": "",
|
||||
"connectedTwoMembers": "",
|
||||
"disconnected": "",
|
||||
"focus": "",
|
||||
"focusFail": "",
|
||||
"grantedTo": "",
|
||||
"me": "",
|
||||
"moderator": "",
|
||||
"muted": "",
|
||||
"mutedTitle": "",
|
||||
"raisedHand": "",
|
||||
"somebody": "",
|
||||
"suboptimalExperienceDescription": "",
|
||||
"suboptimalExperienceTitle": ""
|
||||
},
|
||||
"passwordSetRemotely": "",
|
||||
"poweredby": "",
|
||||
"presenceStatus": {
|
||||
"busy": "",
|
||||
"calling": "",
|
||||
"connected": "",
|
||||
"connecting": "",
|
||||
"connecting2": "",
|
||||
"disconnected": "",
|
||||
"expired": "",
|
||||
"ignored": "",
|
||||
"initializingCall": "",
|
||||
"invited": "",
|
||||
"rejected": "",
|
||||
"ringing": ""
|
||||
},
|
||||
"profile": {
|
||||
"setDisplayNameLabel": "",
|
||||
"setEmailInput": "",
|
||||
"setEmailLabel": "",
|
||||
"title": ""
|
||||
},
|
||||
"raisedHand": "",
|
||||
"recentList": {
|
||||
"joinPastMeeting": ""
|
||||
},
|
||||
"recording": {
|
||||
"authDropboxText": "",
|
||||
"availableSpace": "",
|
||||
"beta": "",
|
||||
"busy": "",
|
||||
"busyTitle": "",
|
||||
"buttonTooltip": "",
|
||||
"error": "",
|
||||
"expandedOff": "",
|
||||
"expandedOn": "",
|
||||
"expandedPending": "",
|
||||
"failedToStart": "",
|
||||
"live": "",
|
||||
"loggedIn": "",
|
||||
"off": "",
|
||||
"on": "",
|
||||
"pending": "",
|
||||
"rec": "",
|
||||
"serviceDescription": "",
|
||||
"serviceName": "",
|
||||
"signIn": "",
|
||||
"signOut": "",
|
||||
"startRecordingBody": "",
|
||||
"unavailable": "",
|
||||
"unavailableTitle": ""
|
||||
},
|
||||
"sectionList": {
|
||||
"pullToRefresh": ""
|
||||
},
|
||||
"settings": {
|
||||
"audioVideo": "",
|
||||
"calendar": {
|
||||
"about": "",
|
||||
"disconnect": "",
|
||||
"microsoftSignIn": "",
|
||||
"signedIn": "",
|
||||
"title": ""
|
||||
},
|
||||
"cameraAndMic": "",
|
||||
"devices": "",
|
||||
"followMe": "",
|
||||
"language": "",
|
||||
"loggedIn": "",
|
||||
"moderator": "",
|
||||
"more": "",
|
||||
"name": "",
|
||||
"noDevice": "",
|
||||
"password": "",
|
||||
"selectAudioOutput": "",
|
||||
"selectCamera": "",
|
||||
"selectMic": "",
|
||||
"startAudioMuted": "",
|
||||
"startVideoMuted": "",
|
||||
"title": "",
|
||||
"update": ""
|
||||
},
|
||||
"settingsView": {
|
||||
"alertOk": "",
|
||||
"alertTitle": "",
|
||||
"alertURLText": "",
|
||||
"conferenceSection": "",
|
||||
"displayName": "",
|
||||
"email": "",
|
||||
"header": "",
|
||||
"profileSection": "",
|
||||
"serverURL": "",
|
||||
"startWithAudioMuted": "",
|
||||
"startWithVideoMuted": ""
|
||||
},
|
||||
"share": {
|
||||
"dialInfoText": "",
|
||||
"mainText": ""
|
||||
},
|
||||
"speaker": "",
|
||||
"speakerStats": {
|
||||
"hours": "",
|
||||
"minutes": "",
|
||||
"name": "",
|
||||
"seconds": "",
|
||||
"speakerStats": "",
|
||||
"speakerTime": ""
|
||||
},
|
||||
"startupoverlay": {
|
||||
"policyText": "",
|
||||
"title": ""
|
||||
},
|
||||
"suspendedoverlay": {
|
||||
"rejoinKeyTitle": "",
|
||||
"text": "",
|
||||
"title": ""
|
||||
},
|
||||
"toolbar": {
|
||||
"accessibilityLabel": {
|
||||
"audioOnly": "",
|
||||
"audioRoute": "",
|
||||
"callQuality": "",
|
||||
"cc": "",
|
||||
"chat": "",
|
||||
"document": "",
|
||||
"feedback": "",
|
||||
"fullScreen": "",
|
||||
"hangup": "",
|
||||
"invite": "",
|
||||
"kick": "",
|
||||
"localRecording": "",
|
||||
"lockRoom": "",
|
||||
"moreActions": "",
|
||||
"moreActionsMenu": "",
|
||||
"mute": "",
|
||||
"pip": "",
|
||||
"profile": "",
|
||||
"raiseHand": "",
|
||||
"recording": "",
|
||||
"remoteMute": "",
|
||||
"Settings": "",
|
||||
"sharedvideo": "",
|
||||
"shareRoom": "",
|
||||
"shareYourScreen": "",
|
||||
"shortcuts": "",
|
||||
"speakerStats": "",
|
||||
"tileView": "",
|
||||
"toggleCamera": "",
|
||||
"videomute": ""
|
||||
},
|
||||
"addPeople": "",
|
||||
"audioonly": "",
|
||||
"audioOnlyOff": "",
|
||||
"audioOnlyOn": "",
|
||||
"audioRoute": "",
|
||||
"authenticate": "",
|
||||
"callQuality": "",
|
||||
"cameraDisabled": "",
|
||||
"chat": "",
|
||||
"closeChat": "",
|
||||
"documentClose": "",
|
||||
"documentOpen": "",
|
||||
"enterFullScreen": "",
|
||||
"enterTileView": "",
|
||||
"etherpad": "",
|
||||
"exitFullScreen": "",
|
||||
"exitTileView": "",
|
||||
"feedback": "",
|
||||
"filmstrip": "",
|
||||
"fullscreen": "",
|
||||
"hangup": "",
|
||||
"invite": "",
|
||||
"lock": "",
|
||||
"login": "",
|
||||
"logout": "",
|
||||
"lowerYourHand": "",
|
||||
"micDisabled": "",
|
||||
"micMutedPopup": "",
|
||||
"moreActions": "",
|
||||
"mute": "",
|
||||
"openChat": "",
|
||||
"pip": "",
|
||||
"profile": "",
|
||||
"raiseHand": "",
|
||||
"raiseYourHand": "",
|
||||
"Settings": "",
|
||||
"sharedvideo": "",
|
||||
"sharedVideoMutedPopup": "",
|
||||
"shareRoom": "",
|
||||
"shortcuts": "",
|
||||
"sip": "",
|
||||
"speakerStats": "",
|
||||
"startScreenSharing": "",
|
||||
"startSubtitles": "",
|
||||
"stopScreenSharing": "",
|
||||
"stopSubtitles": "",
|
||||
"stopSharedVideo": "",
|
||||
"talkWhileMutedPopup": "",
|
||||
"tileViewToggle": "",
|
||||
"toggleCamera": "",
|
||||
"unableToUnmutePopup": "",
|
||||
"videomute": ""
|
||||
},
|
||||
"transcribing": {
|
||||
"ccButtonTooltip": "",
|
||||
"error": "",
|
||||
"expandedLabel": "",
|
||||
"failedToStart": "",
|
||||
"labelToolTip": "",
|
||||
"off": "",
|
||||
"pending": "",
|
||||
"start": "",
|
||||
"stop": "",
|
||||
"tr": ""
|
||||
},
|
||||
"userMedia": {
|
||||
"androidGrantPermissions": "",
|
||||
"chromeGrantPermissions": "",
|
||||
"edgeGrantPermissions": "",
|
||||
"electronGrantPermissions": "",
|
||||
"firefoxGrantPermissions": "",
|
||||
"iexplorerGrantPermissions": "",
|
||||
"nwjsGrantPermissions": "",
|
||||
"operaGrantPermissions": "",
|
||||
"react-nativeGrantPermissions": "",
|
||||
"safariGrantPermissions": ""
|
||||
},
|
||||
"videoSIPGW": {
|
||||
"busy": "",
|
||||
"busyTitle": "",
|
||||
"errorAlreadyInvited": "",
|
||||
"errorInvite": "",
|
||||
"errorInviteFailed": "",
|
||||
"errorInviteFailedTitle": "",
|
||||
"errorInviteTitle": "",
|
||||
"pending": "",
|
||||
"serviceName": "",
|
||||
"unavailableTitle": ""
|
||||
},
|
||||
"videoStatus": {
|
||||
"audioOnly": "",
|
||||
"audioOnlyExpanded": "",
|
||||
"callQuality": "",
|
||||
"hd": "",
|
||||
"hdTooltip": "",
|
||||
"highDefinition": "",
|
||||
"labelTooiltipNoVideo": "",
|
||||
"labelTooltipAudioOnly": "",
|
||||
"labelTooltipVideo": "",
|
||||
"ld": "",
|
||||
"ldTooltip": "",
|
||||
"lowDefinition": "",
|
||||
"onlyAudioAvailable": "",
|
||||
"onlyAudioSupported": "",
|
||||
"p2pEnabled": "",
|
||||
"p2pVideoQualityDescription": "",
|
||||
"qualityButtonTip": "",
|
||||
"recHighDefinitionOnly": "",
|
||||
"sd": "",
|
||||
"sdTooltip": "",
|
||||
"standardDefinition": ""
|
||||
},
|
||||
"videothumbnail": {
|
||||
"domute": "",
|
||||
"flip": "",
|
||||
"kick": "",
|
||||
"moderator": "",
|
||||
"mute": "",
|
||||
"muted": "",
|
||||
"remoteControl": "",
|
||||
"videomute": ""
|
||||
},
|
||||
"welcomepage": {
|
||||
"accessibilityLabel": {
|
||||
"join": "",
|
||||
"roomname": ""
|
||||
},
|
||||
"appDescription": "",
|
||||
"audioVideoSwitch": {
|
||||
"audio": "",
|
||||
"video": ""
|
||||
},
|
||||
"calendar": "",
|
||||
"connectCalendarButton": "",
|
||||
"connectCalendarText": "",
|
||||
"enterRoomTitle": "",
|
||||
"go": "",
|
||||
"join": "",
|
||||
"privacy": "",
|
||||
"recentList": "",
|
||||
"recentListDelete": "",
|
||||
"recentListEmpty": "",
|
||||
"roomname": "",
|
||||
"roomnameHint": "",
|
||||
"sendFeedback": "",
|
||||
"terms": "",
|
||||
"title": ""
|
||||
}
|
||||
}
|
||||
1185
lang/main-oc.json
1185
lang/main-oc.json
File diff suppressed because it is too large
Load Diff
1260
lang/main-ptBR.json
1260
lang/main-ptBR.json
File diff suppressed because it is too large
Load Diff
1204
lang/main-vi.json
1204
lang/main-vi.json
File diff suppressed because it is too large
Load Diff
@@ -50,7 +50,7 @@
|
||||
},
|
||||
"chat": {
|
||||
"error": "Error: your message \"__originalText__\" was not sent. Reason: __error__",
|
||||
"messagebox": "Enter text...",
|
||||
"messagebox": "Type a message",
|
||||
"nickname": {
|
||||
"popover": "Choose a nickname",
|
||||
"title": "Enter a nickname to use chat"
|
||||
@@ -121,6 +121,7 @@
|
||||
"deepLinking": {
|
||||
"appNotInstalled": "You need the __app__ mobile app to join this meeting on your phone.",
|
||||
"description": "Nothing happened? We tried launching your meeting in the __app__ desktop app. Try again or launch it in the __app__ web app.",
|
||||
"descriptionWithoutWeb": "Nothing happened? We tried launching your meeting in the __app__ desktop app.",
|
||||
"downloadApp": "Download the app",
|
||||
"launchWebButton": "Launch in web",
|
||||
"openApp": "Continue to the app",
|
||||
@@ -351,12 +352,15 @@
|
||||
"dialInConferenceID": "PIN:",
|
||||
"dialInNotSupported": "Sorry, dialing in is currently not supported.",
|
||||
"dialInNumber": "Dial-in:",
|
||||
"dialInSummaryError": "Error fetching dial-in info now. Please try again later.",
|
||||
"dialInTollFree": "Toll Free",
|
||||
"genericError": "Whoops, something went wrong.",
|
||||
"inviteLiveStream": "To view the live stream of this meeting, click this link: __url__",
|
||||
"invitePhone": "One tap audio Dial In: __number__,,__conferenceID__#",
|
||||
"invitePhoneAlternatives": "Looking for a different dial in number? Please see: __url__",
|
||||
"inviteURL": "You are invited to join a meeting.\n__moreInfo__\nJoin meeting: __url__\n",
|
||||
"inviteURLFirstPartGeneral": "You are invited to join a meeting.",
|
||||
"inviteURLFirstPartPersonal": "__name__ is inviting you to a meeting.",
|
||||
"inviteURLSecondPart": "\n__moreInfo__\nJoin meeting: __url__\n",
|
||||
"inviteURLMoreInfo": "Meeting ID: __conferenceID__#\n",
|
||||
"liveStreamURL": "Live stream:",
|
||||
"moreNumbers": "More numbers",
|
||||
@@ -475,14 +479,16 @@
|
||||
"mutedTitle": "You're muted!",
|
||||
"raisedHand": "__name__ would like to speak.",
|
||||
"somebody": "Somebody",
|
||||
"startSilentTitle": "You joined with no audio output!",
|
||||
"startSilentDescription": "Rejoin the meeting to enable audio",
|
||||
"suboptimalExperienceDescription": "Eer... we are afraid your experience with __appName__ isn't going to be that great here. We are looking for ways to improve this but, until then, please try using one of the <a href='static/recommendedBrowsers.html' target='_blank'>fully supported browsers</a>.",
|
||||
"suboptimalExperienceTitle": "Browser Warning",
|
||||
"newDeviceCameraTitle": "New camera detected",
|
||||
"newDeviceMicTitle": "New microphone detected",
|
||||
"newDeviceCameraTitle": "New audio output detected",
|
||||
"newDeviceAudioTitle": "New audio device detected",
|
||||
"newDeviceAction": "Use"
|
||||
},
|
||||
"passwordSetRemotely": "set by another member",
|
||||
"passwordDigitsOnly": "Up to __number__ digits",
|
||||
"poweredby": "powered by",
|
||||
"presenceStatus": {
|
||||
"busy": "Busy",
|
||||
@@ -773,6 +779,7 @@
|
||||
"enterRoomTitle": "Start a new meeting",
|
||||
"go": "GO",
|
||||
"join": "JOIN",
|
||||
"info": "Info",
|
||||
"privacy": "Privacy",
|
||||
"recentList": "Recent",
|
||||
"recentListDelete": "Delete",
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
import { setSubject } from '../../react/features/base/conference';
|
||||
import { parseJWTFromURLParams } from '../../react/features/base/jwt';
|
||||
import { invite } from '../../react/features/invite';
|
||||
import { toggleTileView } from '../../react/features/video-layout';
|
||||
import { getJitsiMeetTransport } from '../transport';
|
||||
|
||||
import { API_ID } from './constants';
|
||||
@@ -97,9 +98,14 @@ function initCommands() {
|
||||
sendAnalytics(createApiEvent('screen.sharing.toggled'));
|
||||
toggleScreenSharing();
|
||||
},
|
||||
'video-hangup': () => {
|
||||
'toggle-tile-view': () => {
|
||||
sendAnalytics(createApiEvent('tile-view.toggled'));
|
||||
|
||||
APP.store.dispatch(toggleTileView());
|
||||
},
|
||||
'video-hangup': (showFeedbackDialog = true) => {
|
||||
sendAnalytics(createApiEvent('video.hangup'));
|
||||
APP.conference.hangup(true);
|
||||
APP.conference.hangup(showFeedbackDialog);
|
||||
},
|
||||
'email': email => {
|
||||
sendAnalytics(createApiEvent('email.changed'));
|
||||
@@ -553,6 +559,38 @@ class API {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify external application of an unexpected camera-related error having
|
||||
* occurred.
|
||||
*
|
||||
* @param {string} type - The type of the camera error.
|
||||
* @param {string} message - Additional information about the error.
|
||||
* @returns {void}
|
||||
*/
|
||||
notifyOnCameraError(type: string, message: string) {
|
||||
this._sendEvent({
|
||||
name: 'camera-error',
|
||||
type,
|
||||
message
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify external application of an unexpected mic-related error having
|
||||
* occurred.
|
||||
*
|
||||
* @param {string} type - The type of the mic error.
|
||||
* @param {string} message - Additional information about the error.
|
||||
* @returns {void}
|
||||
*/
|
||||
notifyOnMicError(type: string, message: string) {
|
||||
this._sendEvent({
|
||||
name: 'mic-error',
|
||||
type,
|
||||
message
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify external application (if API is enabled) that conference feedback
|
||||
* has been submitted. Intended to be used in conjunction with the
|
||||
@@ -622,6 +660,21 @@ class API {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify external application (if API is enabled) that tile view has been
|
||||
* entered or exited.
|
||||
*
|
||||
* @param {string} enabled - True if tile view is currently displayed, false
|
||||
* otherwise.
|
||||
* @returns {void}
|
||||
*/
|
||||
notifyTileViewChanged(enabled: boolean) {
|
||||
this._sendEvent({
|
||||
name: 'tile-view-changed',
|
||||
enabled
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Disposes the allocated resources.
|
||||
*
|
||||
|
||||
6
modules/API/external/external_api.js
vendored
6
modules/API/external/external_api.js
vendored
@@ -39,6 +39,7 @@ const commands = {
|
||||
toggleChat: 'toggle-chat',
|
||||
toggleFilmStrip: 'toggle-film-strip',
|
||||
toggleShareScreen: 'toggle-share-screen',
|
||||
toggleTileView: 'toggle-tile-view',
|
||||
toggleVideo: 'toggle-video'
|
||||
};
|
||||
|
||||
@@ -50,6 +51,7 @@ const events = {
|
||||
'avatar-changed': 'avatarChanged',
|
||||
'audio-availability-changed': 'audioAvailabilityChanged',
|
||||
'audio-mute-status-changed': 'audioMuteStatusChanged',
|
||||
'camera-error': 'cameraError',
|
||||
'device-list-changed': 'deviceListChanged',
|
||||
'display-name-change': 'displayNameChange',
|
||||
'email-change': 'emailChange',
|
||||
@@ -57,6 +59,7 @@ const events = {
|
||||
'feedback-prompt-displayed': 'feedbackPromptDisplayed',
|
||||
'filmstrip-display-changed': 'filmstripDisplayChanged',
|
||||
'incoming-message': 'incomingMessage',
|
||||
'mic-error': 'micError',
|
||||
'outgoing-message': 'outgoingMessage',
|
||||
'participant-joined': 'participantJoined',
|
||||
'participant-left': 'participantLeft',
|
||||
@@ -67,7 +70,8 @@ const events = {
|
||||
'video-availability-changed': 'videoAvailabilityChanged',
|
||||
'video-mute-status-changed': 'videoMuteStatusChanged',
|
||||
'screen-sharing-status-changed': 'screenSharingStatusChanged',
|
||||
'subject-change': 'subjectChange'
|
||||
'subject-change': 'subjectChange',
|
||||
'tile-view-changed': 'tileViewChanged'
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
104
modules/UI/UI.js
104
modules/UI/UI.js
@@ -13,16 +13,12 @@ import SharedVideoManager from './shared_video/SharedVideo';
|
||||
import VideoLayout from './videolayout/VideoLayout';
|
||||
import Filmstrip from './videolayout/Filmstrip';
|
||||
|
||||
import { JitsiTrackErrors } from '../../react/features/base/lib-jitsi-meet';
|
||||
import { getLocalParticipant } from '../../react/features/base/participants';
|
||||
import { toggleChat } from '../../react/features/chat';
|
||||
import { openDisplayNamePrompt } from '../../react/features/display-name';
|
||||
import { setEtherpadHasInitialzied } from '../../react/features/etherpad';
|
||||
import { setFilmstripVisible } from '../../react/features/filmstrip';
|
||||
import {
|
||||
setNotificationsEnabled,
|
||||
showWarningNotification
|
||||
} from '../../react/features/notifications';
|
||||
import { setNotificationsEnabled } from '../../react/features/notifications';
|
||||
import {
|
||||
dockToolbox,
|
||||
setToolboxEnabled,
|
||||
@@ -40,39 +36,6 @@ UI.eventEmitter = eventEmitter;
|
||||
let etherpadManager;
|
||||
let sharedVideoManager;
|
||||
|
||||
const JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP = {
|
||||
microphone: {},
|
||||
camera: {}
|
||||
};
|
||||
|
||||
JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP
|
||||
.camera[JitsiTrackErrors.UNSUPPORTED_RESOLUTION]
|
||||
= 'dialog.cameraUnsupportedResolutionError';
|
||||
JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.camera[JitsiTrackErrors.GENERAL]
|
||||
= 'dialog.cameraUnknownError';
|
||||
JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.camera[JitsiTrackErrors.PERMISSION_DENIED]
|
||||
= 'dialog.cameraPermissionDeniedError';
|
||||
JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.camera[JitsiTrackErrors.NOT_FOUND]
|
||||
= 'dialog.cameraNotFoundError';
|
||||
JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.camera[JitsiTrackErrors.CONSTRAINT_FAILED]
|
||||
= 'dialog.cameraConstraintFailedError';
|
||||
JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP
|
||||
.camera[JitsiTrackErrors.NO_DATA_FROM_SOURCE]
|
||||
= 'dialog.cameraNotSendingData';
|
||||
JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.microphone[JitsiTrackErrors.GENERAL]
|
||||
= 'dialog.micUnknownError';
|
||||
JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP
|
||||
.microphone[JitsiTrackErrors.PERMISSION_DENIED]
|
||||
= 'dialog.micPermissionDeniedError';
|
||||
JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.microphone[JitsiTrackErrors.NOT_FOUND]
|
||||
= 'dialog.micNotFoundError';
|
||||
JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP
|
||||
.microphone[JitsiTrackErrors.CONSTRAINT_FAILED]
|
||||
= 'dialog.micConstraintFailedError';
|
||||
JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP
|
||||
.microphone[JitsiTrackErrors.NO_DATA_FROM_SOURCE]
|
||||
= 'dialog.micNotSendingData';
|
||||
|
||||
const UIListeners = new Map([
|
||||
[
|
||||
UIEvents.ETHERPAD_CLICKED,
|
||||
@@ -295,12 +258,6 @@ UI.addLocalStream = track => {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Removed remote stream from UI.
|
||||
* @param {JitsiTrack} track stream to remove
|
||||
*/
|
||||
UI.removeRemoteStream = track => VideoLayout.onRemoteStreamRemoved(track);
|
||||
|
||||
/**
|
||||
* Setup and show Etherpad.
|
||||
* @param {string} name etherpad id
|
||||
@@ -774,65 +731,6 @@ UI.showExtensionInlineInstallationDialog = function(callback) {
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Shows a notifications about the passed in microphone error.
|
||||
*
|
||||
* @param {JitsiTrackError} micError - An error object related to using or
|
||||
* acquiring an audio stream.
|
||||
* @returns {void}
|
||||
*/
|
||||
UI.showMicErrorNotification = function(micError) {
|
||||
if (!micError) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { message, name } = micError;
|
||||
|
||||
const micJitsiTrackErrorMsg
|
||||
= JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.microphone[name];
|
||||
const micErrorMsg = micJitsiTrackErrorMsg
|
||||
|| JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP
|
||||
.microphone[JitsiTrackErrors.GENERAL];
|
||||
const additionalMicErrorMsg = micJitsiTrackErrorMsg ? null : message;
|
||||
|
||||
APP.store.dispatch(showWarningNotification({
|
||||
description: additionalMicErrorMsg,
|
||||
descriptionKey: micErrorMsg,
|
||||
titleKey: name === JitsiTrackErrors.PERMISSION_DENIED
|
||||
? 'deviceError.microphonePermission'
|
||||
: 'deviceError.microphoneError'
|
||||
}));
|
||||
};
|
||||
|
||||
/**
|
||||
* Shows a notifications about the passed in camera error.
|
||||
*
|
||||
* @param {JitsiTrackError} cameraError - An error object related to using or
|
||||
* acquiring a video stream.
|
||||
* @returns {void}
|
||||
*/
|
||||
UI.showCameraErrorNotification = function(cameraError) {
|
||||
if (!cameraError) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { message, name } = cameraError;
|
||||
|
||||
const cameraJitsiTrackErrorMsg
|
||||
= JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.camera[name];
|
||||
const cameraErrorMsg = cameraJitsiTrackErrorMsg
|
||||
|| JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP
|
||||
.camera[JitsiTrackErrors.GENERAL];
|
||||
const additionalCameraErrorMsg = cameraJitsiTrackErrorMsg ? null : message;
|
||||
|
||||
APP.store.dispatch(showWarningNotification({
|
||||
description: additionalCameraErrorMsg,
|
||||
descriptionKey: cameraErrorMsg,
|
||||
titleKey: name === JitsiTrackErrors.PERMISSION_DENIED
|
||||
? 'deviceError.cameraPermission' : 'deviceError.cameraError'
|
||||
}));
|
||||
};
|
||||
|
||||
/**
|
||||
* Shows error dialog that informs the user that no data is received from the
|
||||
* device.
|
||||
|
||||
@@ -361,6 +361,12 @@ const Filmstrip = {
|
||||
'min-width': `${local.thumbWidth}px`,
|
||||
width: `${local.thumbWidth}px`
|
||||
});
|
||||
|
||||
const avatarSize = local.thumbHeight / 2;
|
||||
|
||||
thumbs.localThumb.find('.avatar-container')
|
||||
.height(avatarSize)
|
||||
.width(avatarSize);
|
||||
}
|
||||
|
||||
if (thumbs.remoteThumbs) {
|
||||
@@ -371,6 +377,12 @@ const Filmstrip = {
|
||||
'min-width': `${remote.thumbWidth}px`,
|
||||
width: `${remote.thumbWidth}px`
|
||||
});
|
||||
|
||||
const avatarSize = remote.thumbHeight / 2;
|
||||
|
||||
thumbs.remoteThumbs.find('.avatar-container')
|
||||
.height(avatarSize)
|
||||
.width(avatarSize);
|
||||
}
|
||||
|
||||
const currentLayout = getCurrentLayout(APP.store.getState());
|
||||
|
||||
@@ -32,7 +32,7 @@ function LocalVideo(VideoLayout, emitter, streamEndedCallback) {
|
||||
|
||||
this.localVideoId = null;
|
||||
this.bindHoverHandler();
|
||||
if (config.enableLocalVideoFlip) {
|
||||
if (!config.disableLocalVideoFlip) {
|
||||
this._buildContextMenu();
|
||||
}
|
||||
this.isLocal = true;
|
||||
|
||||
@@ -357,6 +357,11 @@ RemoteVideo.prototype.removeRemoteStreamElement = function(stream) {
|
||||
logger.info(`${isVideo ? 'Video' : 'Audio'
|
||||
} removed ${this.id}`, select);
|
||||
|
||||
|
||||
if (stream === this.videoStream) {
|
||||
this.videoStream = null;
|
||||
}
|
||||
|
||||
this.updateView();
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* global $, APP, interfaceConfig */
|
||||
/* global $, APP, config, interfaceConfig */
|
||||
|
||||
/* eslint-disable no-unused-vars */
|
||||
import React from 'react';
|
||||
@@ -199,6 +199,8 @@ SmallVideo.createStreamElement = function(stream) {
|
||||
|
||||
if (isVideo) {
|
||||
element.setAttribute('muted', 'true');
|
||||
} else if (config.startSilent) {
|
||||
element.muted = true;
|
||||
}
|
||||
|
||||
element.autoplay = true;
|
||||
|
||||
@@ -166,22 +166,6 @@ function getCameraVideoPosition( // eslint-disable-line max-params
|
||||
verticalIndent };
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of the video horizontal and vertical indents.
|
||||
* Centers horizontally and top aligns vertically.
|
||||
*
|
||||
* @return an array with 2 elements, the horizontal indent and the vertical
|
||||
* indent
|
||||
*/
|
||||
function getDesktopVideoPosition(videoWidth, videoHeight, videoSpaceWidth) {
|
||||
const horizontalIndent = (videoSpaceWidth - videoWidth) / 2;
|
||||
|
||||
const verticalIndent = 0;// Top aligned
|
||||
|
||||
return { horizontalIndent,
|
||||
verticalIndent };
|
||||
}
|
||||
|
||||
/**
|
||||
* Container for user video.
|
||||
*/
|
||||
@@ -366,23 +350,23 @@ export class VideoContainer extends LargeContainer {
|
||||
* @returns {{horizontalIndent, verticalIndent}}
|
||||
*/
|
||||
getVideoPosition(width, height, containerWidth, containerHeight) {
|
||||
let containerWidthToUse = containerWidth;
|
||||
|
||||
/* eslint-enable max-params */
|
||||
if (this.stream && this.isScreenSharing()) {
|
||||
let availableContainerWidth = containerWidth;
|
||||
|
||||
if (interfaceConfig.VERTICAL_FILMSTRIP) {
|
||||
availableContainerWidth -= Filmstrip.getFilmstripWidth();
|
||||
containerWidthToUse -= Filmstrip.getFilmstripWidth();
|
||||
}
|
||||
|
||||
return getDesktopVideoPosition(width,
|
||||
return getCameraVideoPosition(width,
|
||||
height,
|
||||
availableContainerWidth,
|
||||
containerWidthToUse,
|
||||
containerHeight);
|
||||
}
|
||||
|
||||
return getCameraVideoPosition(width,
|
||||
height,
|
||||
containerWidth,
|
||||
containerWidthToUse,
|
||||
containerHeight);
|
||||
|
||||
}
|
||||
|
||||
@@ -348,10 +348,6 @@ const VideoLayout = {
|
||||
remoteVideo.removeRemoteStreamElement(stream);
|
||||
}
|
||||
|
||||
if (stream.isVideoTrack()) {
|
||||
this._updateLargeVideoIfDisplayed(id);
|
||||
}
|
||||
|
||||
this.updateMutedForNoTracks(id, stream.getType());
|
||||
},
|
||||
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
/* global APP, JitsiMeetJS */
|
||||
|
||||
import { getAudioOutputDeviceId } from '../../react/features/base/devices';
|
||||
import {
|
||||
getAudioOutputDeviceId,
|
||||
notifyCameraError,
|
||||
notifyMicError
|
||||
} from '../../react/features/base/devices';
|
||||
import {
|
||||
getUserSelectedCameraDeviceId,
|
||||
getUserSelectedMicDeviceId,
|
||||
getUserSelectedOutputDeviceId
|
||||
} from '../../react/features/base/settings';
|
||||
|
||||
/**
|
||||
* Determines if currently selected audio output device should be changed after
|
||||
@@ -26,8 +35,7 @@ function getNewAudioOutputDevice(newDevices) {
|
||||
return 'default';
|
||||
}
|
||||
|
||||
const settings = APP.store.getState()['features/base/settings'];
|
||||
const preferredAudioOutputDeviceId = settings.userSelectedAudioOutputDeviceId;
|
||||
const preferredAudioOutputDeviceId = getUserSelectedOutputDeviceId(APP.store.getState());
|
||||
|
||||
// if the preferred one is not the selected and is available in the new devices
|
||||
// we want to use it as it was just added
|
||||
@@ -49,8 +57,7 @@ function getNewAudioOutputDevice(newDevices) {
|
||||
function getNewAudioInputDevice(newDevices, localAudio) {
|
||||
const availableAudioInputDevices = newDevices.filter(
|
||||
d => d.kind === 'audioinput');
|
||||
const settings = APP.store.getState()['features/base/settings'];
|
||||
const selectedAudioInputDeviceId = settings.userSelectedMicDeviceId;
|
||||
const selectedAudioInputDeviceId = getUserSelectedMicDeviceId(APP.store.getState());
|
||||
const selectedAudioInputDevice = availableAudioInputDevices.find(
|
||||
d => d.deviceId === selectedAudioInputDeviceId);
|
||||
|
||||
@@ -88,8 +95,7 @@ function getNewAudioInputDevice(newDevices, localAudio) {
|
||||
function getNewVideoInputDevice(newDevices, localVideo) {
|
||||
const availableVideoInputDevices = newDevices.filter(
|
||||
d => d.kind === 'videoinput');
|
||||
const settings = APP.store.getState()['features/base/settings'];
|
||||
const selectedVideoInputDeviceId = settings.userSelectedCameraDeviceId;
|
||||
const selectedVideoInputDeviceId = getUserSelectedCameraDeviceId(APP.store.getState());
|
||||
const selectedVideoInputDevice = availableVideoInputDevices.find(
|
||||
d => d.deviceId === selectedVideoInputDeviceId);
|
||||
|
||||
@@ -174,11 +180,11 @@ export default {
|
||||
]))
|
||||
.then(tracks => {
|
||||
if (audioTrackError) {
|
||||
APP.UI.showMicErrorNotification(audioTrackError);
|
||||
APP.store.dispatch(notifyMicError(audioTrackError));
|
||||
}
|
||||
|
||||
if (videoTrackError) {
|
||||
APP.UI.showCameraErrorNotification(videoTrackError);
|
||||
APP.store.dispatch(notifyCameraError(videoTrackError));
|
||||
}
|
||||
|
||||
return tracks.filter(t => typeof t !== 'undefined');
|
||||
@@ -203,7 +209,7 @@ export default {
|
||||
})
|
||||
.catch(err => {
|
||||
audioTrackError = err;
|
||||
showError && APP.UI.showMicErrorNotification(err);
|
||||
showError && APP.store.disptach(notifyMicError(err));
|
||||
|
||||
return [];
|
||||
}));
|
||||
@@ -221,7 +227,7 @@ export default {
|
||||
})
|
||||
.catch(err => {
|
||||
videoTrackError = err;
|
||||
showError && APP.UI.showCameraErrorNotification(err);
|
||||
showError && APP.store.dispatch(notifyCameraError(err));
|
||||
|
||||
return [];
|
||||
}));
|
||||
|
||||
129
package-lock.json
generated
129
package-lock.json
generated
@@ -2448,10 +2448,15 @@
|
||||
"isomorphic-fetch": "^2.2.1"
|
||||
}
|
||||
},
|
||||
"@react-native-community/async-storage": {
|
||||
"version": "1.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@react-native-community/async-storage/-/async-storage-1.3.4.tgz",
|
||||
"integrity": "sha512-fJmzL27x0BEjhmMXPnDPnUNCZK7bph+NBVCfAz9fzHzAamaiOkdUwuL3PvE4Oj4Kw4knP8ocw5VRDGorAidZ2g=="
|
||||
},
|
||||
"@react-native-community/cli": {
|
||||
"version": "1.9.2",
|
||||
"resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-1.9.2.tgz",
|
||||
"integrity": "sha512-wSw3g6HrSUvLZiHiWRcO++JrKdbYNRWycGbGHVCnRLsdDRsj/y152xPlvBa29C8w+1SwiiN8aGsBOO0x9hkrCg==",
|
||||
"version": "1.9.4",
|
||||
"resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-1.9.4.tgz",
|
||||
"integrity": "sha512-7XjgqCdi23g6V7RV4tsYvqVqOBtNjAsWe5Oj2dR5KxDi3YqUyIyPjDWzyFkIxiO9XTGp9Al4QSmRwtOERvHO8A==",
|
||||
"requires": {
|
||||
"chalk": "^1.1.1",
|
||||
"commander": "^2.19.0",
|
||||
@@ -2835,7 +2840,7 @@
|
||||
"blueimp-md5": "^2.10.0",
|
||||
"json3": "^3.3.2",
|
||||
"lodash": "^4.17.4",
|
||||
"ua-parser-js": "github:amplitude/ua-parser-js#ed538f1"
|
||||
"ua-parser-js": "github:amplitude/ua-parser-js#ed538f16f5c6ecd8357da989b617d4f156dcf35d"
|
||||
},
|
||||
"dependencies": {
|
||||
"ua-parser-js": {
|
||||
@@ -5480,12 +5485,41 @@
|
||||
}
|
||||
},
|
||||
"errorhandler": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/errorhandler/-/errorhandler-1.5.0.tgz",
|
||||
"integrity": "sha1-6rpkyl1UKjEayUX1gt78M2Fl2fQ=",
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/errorhandler/-/errorhandler-1.5.1.tgz",
|
||||
"integrity": "sha512-rcOwbfvP1WTViVoUjcfZicVzjhjTuhSMntHh6mW3IrEiyE6mJyXvsToJUJGlGlw/2xU9P5whlWNGlIDVeCiT4A==",
|
||||
"requires": {
|
||||
"accepts": "~1.3.3",
|
||||
"accepts": "~1.3.7",
|
||||
"escape-html": "~1.0.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"accepts": {
|
||||
"version": "1.3.7",
|
||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
|
||||
"integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==",
|
||||
"requires": {
|
||||
"mime-types": "~2.1.24",
|
||||
"negotiator": "0.6.2"
|
||||
}
|
||||
},
|
||||
"mime-db": {
|
||||
"version": "1.40.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz",
|
||||
"integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA=="
|
||||
},
|
||||
"mime-types": {
|
||||
"version": "2.1.24",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz",
|
||||
"integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==",
|
||||
"requires": {
|
||||
"mime-db": "1.40.0"
|
||||
}
|
||||
},
|
||||
"negotiator": {
|
||||
"version": "0.6.2",
|
||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
|
||||
"integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"es-abstract": {
|
||||
@@ -6716,8 +6750,7 @@
|
||||
},
|
||||
"ansi-regex": {
|
||||
"version": "2.1.1",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"aproba": {
|
||||
"version": "1.2.0",
|
||||
@@ -6735,13 +6768,11 @@
|
||||
},
|
||||
"balanced-match": {
|
||||
"version": "1.0.0",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
@@ -6754,18 +6785,15 @@
|
||||
},
|
||||
"code-point-at": {
|
||||
"version": "1.1.0",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"concat-map": {
|
||||
"version": "0.0.1",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"console-control-strings": {
|
||||
"version": "1.1.0",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"core-util-is": {
|
||||
"version": "1.0.2",
|
||||
@@ -6868,8 +6896,7 @@
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.3",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"ini": {
|
||||
"version": "1.3.5",
|
||||
@@ -6879,7 +6906,6 @@
|
||||
"is-fullwidth-code-point": {
|
||||
"version": "1.0.0",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"number-is-nan": "^1.0.0"
|
||||
}
|
||||
@@ -6892,20 +6918,17 @@
|
||||
"minimatch": {
|
||||
"version": "3.0.4",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
}
|
||||
},
|
||||
"minimist": {
|
||||
"version": "0.0.8",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"minipass": {
|
||||
"version": "2.2.4",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"safe-buffer": "^5.1.1",
|
||||
"yallist": "^3.0.0"
|
||||
@@ -6922,7 +6945,6 @@
|
||||
"mkdirp": {
|
||||
"version": "0.5.1",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"minimist": "0.0.8"
|
||||
}
|
||||
@@ -6995,8 +7017,7 @@
|
||||
},
|
||||
"number-is-nan": {
|
||||
"version": "1.0.1",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"object-assign": {
|
||||
"version": "4.1.1",
|
||||
@@ -7006,7 +7027,6 @@
|
||||
"once": {
|
||||
"version": "1.4.0",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
@@ -7082,8 +7102,7 @@
|
||||
},
|
||||
"safe-buffer": {
|
||||
"version": "5.1.1",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
@@ -7113,7 +7132,6 @@
|
||||
"string-width": {
|
||||
"version": "1.0.2",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"code-point-at": "^1.0.0",
|
||||
"is-fullwidth-code-point": "^1.0.0",
|
||||
@@ -7131,7 +7149,6 @@
|
||||
"strip-ansi": {
|
||||
"version": "3.0.1",
|
||||
"bundled": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"ansi-regex": "^2.0.0"
|
||||
}
|
||||
@@ -7170,13 +7187,11 @@
|
||||
},
|
||||
"wrappy": {
|
||||
"version": "1.0.2",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
},
|
||||
"yallist": {
|
||||
"version": "3.0.2",
|
||||
"bundled": true,
|
||||
"optional": true
|
||||
"bundled": true
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -8912,8 +8927,8 @@
|
||||
}
|
||||
},
|
||||
"lib-jitsi-meet": {
|
||||
"version": "github:jitsi/lib-jitsi-meet#23503b0efd4b97989af7b10696ec9ae790aec7bb",
|
||||
"from": "github:jitsi/lib-jitsi-meet#23503b0efd4b97989af7b10696ec9ae790aec7bb",
|
||||
"version": "github:jitsi/lib-jitsi-meet#0ee30bf12a549d10bb5d559e19bd557c3ed179eb",
|
||||
"from": "github:jitsi/lib-jitsi-meet#0ee30bf12a549d10bb5d559e19bd557c3ed179eb",
|
||||
"requires": {
|
||||
"@jitsi/sdp-interop": "0.1.14",
|
||||
"@jitsi/sdp-simulcast": "0.2.1",
|
||||
@@ -12038,9 +12053,9 @@
|
||||
}
|
||||
},
|
||||
"react-native": {
|
||||
"version": "0.59.5",
|
||||
"resolved": "https://registry.npmjs.org/react-native/-/react-native-0.59.5.tgz",
|
||||
"integrity": "sha512-8Q/9cS6IMsGNiFhJgzmncbUeuacXQMe5EJl0c63fW30DvjEjeTVCvhM08eGzSpsNlOvL2XDRa4YOiCrwI7S1TA==",
|
||||
"version": "0.59.8",
|
||||
"resolved": "https://registry.npmjs.org/react-native/-/react-native-0.59.8.tgz",
|
||||
"integrity": "sha512-x1T+/pEXrjgdH9uDzd5doJy5aFlBqW04j7ljDKIGALchhnvdFbtXXrUZ/1PfWHMrIdZxtaDt4tkSttp662GSQA==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.0.0",
|
||||
"@react-native-community/cli": "^1.2.1",
|
||||
@@ -12274,6 +12289,25 @@
|
||||
"prop-types": "^15.5.10"
|
||||
}
|
||||
},
|
||||
"react-native-webview": {
|
||||
"version": "5.8.1",
|
||||
"resolved": "https://registry.npmjs.org/react-native-webview/-/react-native-webview-5.8.1.tgz",
|
||||
"integrity": "sha512-b6pSvmjoiWtcz6YspggW02X+BRXJWuquHwkh37BRx1NMW1iwMZA31SnFQvTpPzWYYIb9WF/mRsy2nGtt9C6NIg==",
|
||||
"requires": {
|
||||
"escape-string-regexp": "1.0.5",
|
||||
"invariant": "2.2.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"invariant": {
|
||||
"version": "2.2.4",
|
||||
"resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
|
||||
"integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
|
||||
"requires": {
|
||||
"loose-envify": "^1.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"react-node-resolver": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/react-node-resolver/-/react-node-resolver-1.0.1.tgz",
|
||||
@@ -12345,6 +12379,15 @@
|
||||
"exenv": "^1.2.2"
|
||||
}
|
||||
},
|
||||
"react-textarea-autosize": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-7.1.0.tgz",
|
||||
"integrity": "sha512-c2FlR/fP0qbxmlrW96SdrbgP/v0XZMTupqB90zybvmDVDutytUgPl7beU35klwcTeMepUIQEpQUn3P3bdshGPg==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.1.2",
|
||||
"prop-types": "^15.6.0"
|
||||
}
|
||||
},
|
||||
"react-transform-hmr": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/react-transform-hmr/-/react-transform-hmr-1.0.4.tgz",
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
"@atlaskit/toggle": "5.0.14",
|
||||
"@atlaskit/tooltip": "12.1.13",
|
||||
"@microsoft/microsoft-graph-client": "1.1.0",
|
||||
"@react-native-community/async-storage": "1.3.4",
|
||||
"@webcomponents/url": "0.7.1",
|
||||
"amplitude-js": "4.5.2",
|
||||
"bc-css-flags": "3.0.0",
|
||||
@@ -51,7 +52,7 @@
|
||||
"js-utils": "github:jitsi/js-utils#73a67a7a60d52f8e895f50939c8fcbd1f20fe7b5",
|
||||
"jsrsasign": "8.0.12",
|
||||
"jwt-decode": "2.2.0",
|
||||
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#23503b0efd4b97989af7b10696ec9ae790aec7bb",
|
||||
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#0ee30bf12a549d10bb5d559e19bd557c3ed179eb",
|
||||
"libflacjs": "github:mmig/libflac.js#93d37e7f811f01cf7d8b6a603e38bd3c3810907d",
|
||||
"lodash": "4.17.11",
|
||||
"moment": "2.19.4",
|
||||
@@ -62,7 +63,7 @@
|
||||
"react-emoji-render": "0.4.6",
|
||||
"react-i18next": "7.13.0",
|
||||
"react-linkify": "0.2.2",
|
||||
"react-native": "0.59.5",
|
||||
"react-native": "0.59.8",
|
||||
"react-native-background-timer": "2.1.1",
|
||||
"react-native-calendar-events": "1.6.4",
|
||||
"react-native-callstats": "3.58.2",
|
||||
@@ -76,7 +77,9 @@
|
||||
"react-native-vector-icons": "6.0.2",
|
||||
"react-native-watch-connectivity": "0.2.0",
|
||||
"react-native-webrtc": "github:jitsi/react-native-webrtc#4064c6f2db4f8b961daaaa8dafc6a896d7cfbc43",
|
||||
"react-native-webview": "5.8.1",
|
||||
"react-redux": "5.0.7",
|
||||
"react-textarea-autosize": "7.1.0",
|
||||
"react-transition-group": "2.4.0",
|
||||
"redux": "4.0.0",
|
||||
"redux-thunk": "2.2.0",
|
||||
|
||||
@@ -372,6 +372,28 @@ export function createLiveStreamingDialogEvent(dialogName, buttonName) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an event with the local tracks duration.
|
||||
*
|
||||
* @param {Object} duration - The object with the duration of the local tracks.
|
||||
* @returns {Object} The event in a format suitable for sending via
|
||||
* sendAnalytics.
|
||||
*/
|
||||
export function createLocalTracksDurationEvent(duration) {
|
||||
const { audio, video, conference } = duration;
|
||||
const { camera, desktop } = video;
|
||||
|
||||
return {
|
||||
action: 'local.tracks.durations',
|
||||
attributes: {
|
||||
audio: audio.value,
|
||||
camera: camera.value,
|
||||
conference: conference.value,
|
||||
desktop: desktop.value
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an event which indicates that an action related to recording has
|
||||
* occured.
|
||||
|
||||
11
react/features/analytics/actionTypes.js
Normal file
11
react/features/analytics/actionTypes.js
Normal file
@@ -0,0 +1,11 @@
|
||||
// @flow
|
||||
|
||||
/**
|
||||
* The type of (redux) action which signals that local media duration has changed.
|
||||
*
|
||||
* {
|
||||
* type: UPDATE_LOCAL_TRACKS_DURATION,
|
||||
* localTracksDuration: Object
|
||||
* }
|
||||
*/
|
||||
export const UPDATE_LOCAL_TRACKS_DURATION = 'UPDATE_LOCAL_TRACKS_DURATION';
|
||||
@@ -15,7 +15,7 @@ export default class AmplitudeHandler extends AbstractHandler {
|
||||
constructor(options) {
|
||||
super();
|
||||
|
||||
const { amplitudeAPPKey, host } = options;
|
||||
const { amplitudeAPPKey, host, user } = options;
|
||||
|
||||
if (!amplitudeAPPKey) {
|
||||
throw new Error('Failed to initialize Amplitude handler, no APP key');
|
||||
@@ -28,6 +28,10 @@ export default class AmplitudeHandler extends AbstractHandler {
|
||||
};
|
||||
|
||||
amplitude.getInstance(this._amplitudeOptions).init(amplitudeAPPKey);
|
||||
|
||||
if (user) {
|
||||
amplitude.getInstance(this._amplitudeOptions).setUserId(user);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -31,6 +31,16 @@ class Amplitude {
|
||||
AmplitudeNative.init(this._instanceName, apiKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an identifier for the current user.
|
||||
*
|
||||
* @param {string} userId - The new user id.
|
||||
* @returns {void}
|
||||
*/
|
||||
setUserId(userId) {
|
||||
AmplitudeNative.setUserId(this._instanceName, userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets user properties for the current user.
|
||||
*
|
||||
|
||||
@@ -2,3 +2,4 @@ export * from './AnalyticsEvents';
|
||||
export * from './functions';
|
||||
|
||||
import './middleware';
|
||||
import './reducer';
|
||||
|
||||
@@ -1,8 +1,74 @@
|
||||
import { SET_ROOM } from '../base/conference';
|
||||
// @flow
|
||||
|
||||
import {
|
||||
CONFERENCE_JOINED,
|
||||
CONFERENCE_WILL_LEAVE,
|
||||
SET_ROOM
|
||||
} from '../base/conference';
|
||||
import { SET_CONFIG } from '../base/config';
|
||||
import { MiddlewareRegistry } from '../base/redux';
|
||||
import {
|
||||
getLocalAudioTrack,
|
||||
getLocalVideoTrack,
|
||||
TRACK_ADDED,
|
||||
TRACK_REMOVED,
|
||||
TRACK_UPDATED
|
||||
} from '../base/tracks';
|
||||
|
||||
import { initAnalytics, resetAnalytics } from './functions';
|
||||
import { UPDATE_LOCAL_TRACKS_DURATION } from './actionTypes';
|
||||
import { createLocalTracksDurationEvent } from './AnalyticsEvents';
|
||||
import { initAnalytics, resetAnalytics, sendAnalytics } from './functions';
|
||||
|
||||
/**
|
||||
* Calculates the duration of the local tracks.
|
||||
*
|
||||
* @param {Object} state - The redux state.
|
||||
* @returns {Object} - The local tracks duration.
|
||||
*/
|
||||
function calculateLocalTrackDuration(state) {
|
||||
const now = Date.now();
|
||||
const { localTracksDuration } = state['features/analytics'];
|
||||
const { conference } = state['features/base/conference'];
|
||||
const { audio, video } = localTracksDuration;
|
||||
const { camera, desktop } = video;
|
||||
const tracks = state['features/base/tracks'];
|
||||
const audioTrack = getLocalAudioTrack(tracks);
|
||||
const videoTrack = getLocalVideoTrack(tracks);
|
||||
const newDuration = { ...localTracksDuration };
|
||||
|
||||
if (!audioTrack || audioTrack.muted || !conference) {
|
||||
newDuration.audio = {
|
||||
startedTime: -1,
|
||||
value: audio.value + (audio.startedTime === -1 ? 0 : now - audio.startedTime)
|
||||
};
|
||||
} else if (audio.startedTime === -1) {
|
||||
newDuration.audio.startedTime = now;
|
||||
}
|
||||
|
||||
if (!videoTrack || videoTrack.muted || !conference) {
|
||||
newDuration.video = {
|
||||
camera: {
|
||||
startedTime: -1,
|
||||
value: camera.value + (camera.startedTime === -1 ? 0 : now - camera.startedTime)
|
||||
},
|
||||
desktop: {
|
||||
startedTime: -1,
|
||||
value: desktop.value + (desktop.startedTime === -1 ? 0 : now - desktop.startedTime)
|
||||
}
|
||||
};
|
||||
} else {
|
||||
const { videoType } = videoTrack;
|
||||
|
||||
if (video[videoType].startedTime === -1) {
|
||||
newDuration.video[videoType].startedTime = now;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...localTracksDuration,
|
||||
...newDuration
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Middleware which intercepts config actions to handle evaluating analytics
|
||||
@@ -12,25 +78,81 @@ import { initAnalytics, resetAnalytics } from './functions';
|
||||
* @returns {Function}
|
||||
*/
|
||||
MiddlewareRegistry.register(store => next => action => {
|
||||
switch (action.type) {
|
||||
case SET_CONFIG: {
|
||||
if (action.type === SET_CONFIG) {
|
||||
if (navigator.product === 'ReactNative') {
|
||||
// Reseting the analytics is currently not needed for web because
|
||||
// the user will be redirected to another page and new instance of
|
||||
// Analytics will be created and initialized.
|
||||
resetAnalytics();
|
||||
}
|
||||
}
|
||||
|
||||
const result = next(action);
|
||||
|
||||
switch (action.type) {
|
||||
case CONFERENCE_JOINED: {
|
||||
const { dispatch, getState } = store;
|
||||
const state = getState();
|
||||
|
||||
dispatch({
|
||||
type: UPDATE_LOCAL_TRACKS_DURATION,
|
||||
localTracksDuration: {
|
||||
...calculateLocalTrackDuration(state),
|
||||
conference: {
|
||||
startedTime: Date.now(),
|
||||
value: 0
|
||||
}
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case CONFERENCE_WILL_LEAVE: {
|
||||
const { dispatch, getState } = store;
|
||||
const state = getState();
|
||||
const { localTracksDuration } = state['features/analytics'];
|
||||
const newLocalTracksDuration = {
|
||||
...calculateLocalTrackDuration(state),
|
||||
conference: {
|
||||
startedTime: -1,
|
||||
value: Date.now() - localTracksDuration.conference.startedTime
|
||||
}
|
||||
};
|
||||
|
||||
sendAnalytics(createLocalTracksDurationEvent(newLocalTracksDuration));
|
||||
|
||||
dispatch({
|
||||
type: UPDATE_LOCAL_TRACKS_DURATION,
|
||||
localTracksDuration: newLocalTracksDuration
|
||||
});
|
||||
break;
|
||||
}
|
||||
case SET_ROOM: {
|
||||
const result = next(action);
|
||||
|
||||
initAnalytics(store);
|
||||
break;
|
||||
}
|
||||
case TRACK_ADDED:
|
||||
case TRACK_REMOVED:
|
||||
case TRACK_UPDATED: {
|
||||
const { dispatch, getState } = store;
|
||||
const state = getState();
|
||||
const { localTracksDuration } = state['features/analytics'];
|
||||
|
||||
return result;
|
||||
if (localTracksDuration.conference.startedTime === -1) {
|
||||
// We don't want to track the media duration if the conference is not joined yet because otherwise we won't
|
||||
// be able to compare them with the conference duration (from conference join to conference will leave).
|
||||
break;
|
||||
}
|
||||
dispatch({
|
||||
type: UPDATE_LOCAL_TRACKS_DURATION,
|
||||
localTracksDuration: {
|
||||
...localTracksDuration,
|
||||
...calculateLocalTrackDuration(state)
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return next(action);
|
||||
return result;
|
||||
});
|
||||
|
||||
51
react/features/analytics/reducer.js
Normal file
51
react/features/analytics/reducer.js
Normal file
@@ -0,0 +1,51 @@
|
||||
// @flow
|
||||
|
||||
import { ReducerRegistry } from '../base/redux';
|
||||
|
||||
import { UPDATE_LOCAL_TRACKS_DURATION } from './actionTypes';
|
||||
|
||||
/**
|
||||
* Initial state.
|
||||
*/
|
||||
const DEFAULT_STATE = {
|
||||
localTracksDuration: {
|
||||
audio: {
|
||||
startedTime: -1,
|
||||
value: 0
|
||||
},
|
||||
video: {
|
||||
camera: {
|
||||
startedTime: -1,
|
||||
value: 0
|
||||
},
|
||||
desktop: {
|
||||
startedTime: -1,
|
||||
value: 0
|
||||
}
|
||||
},
|
||||
conference: {
|
||||
startedTime: -1,
|
||||
value: 0
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Listen for actions which changes the state of the analytics feature.
|
||||
*
|
||||
* @param {Object} state - The Redux state of the feature features/analytics.
|
||||
* @param {Object} action - Action object.
|
||||
* @param {string} action.type - Type of action.
|
||||
* @returns {Object}
|
||||
*/
|
||||
ReducerRegistry.register('features/analytics', (state = DEFAULT_STATE, action) => {
|
||||
switch (action.type) {
|
||||
case UPDATE_LOCAL_TRACKS_DURATION:
|
||||
return {
|
||||
...state,
|
||||
localTracksDuration: action.localTracksDuration
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
});
|
||||
@@ -10,8 +10,9 @@ import {
|
||||
setConfig,
|
||||
storeConfig
|
||||
} from '../base/config';
|
||||
import { setLocationURL } from '../base/connection';
|
||||
import { connect, disconnect, setLocationURL } from '../base/connection';
|
||||
import { loadConfig } from '../base/lib-jitsi-meet';
|
||||
import { createDesiredLocalTracks } from '../base/tracks';
|
||||
import { parseURIString, toURLString } from '../base/util';
|
||||
import { setFatalError } from '../overlay';
|
||||
|
||||
@@ -58,6 +59,12 @@ export function appNavigate(uri: ?string) {
|
||||
const { contextRoot, host, room } = location;
|
||||
const locationURL = new URL(location.toString());
|
||||
|
||||
// Disconnect from any current conference.
|
||||
// FIXME: unify with web.
|
||||
if (navigator.product === 'ReactNative') {
|
||||
dispatch(disconnect());
|
||||
}
|
||||
|
||||
dispatch(configWillLoad(locationURL, room));
|
||||
|
||||
let protocol = location.protocol.toLowerCase();
|
||||
@@ -74,25 +81,40 @@ export function appNavigate(uri: ?string) {
|
||||
|
||||
let config;
|
||||
|
||||
try {
|
||||
config = await loadConfig(url);
|
||||
dispatch(storeConfig(baseURL, config));
|
||||
} catch (error) {
|
||||
// Avoid (re)loading the config when there is no room.
|
||||
if (!room) {
|
||||
config = restoreConfig(baseURL);
|
||||
}
|
||||
|
||||
if (!config) {
|
||||
dispatch(loadConfigError(error, locationURL));
|
||||
if (!config) {
|
||||
try {
|
||||
config = await loadConfig(url);
|
||||
dispatch(storeConfig(baseURL, config));
|
||||
} catch (error) {
|
||||
config = restoreConfig(baseURL);
|
||||
|
||||
return;
|
||||
if (!config) {
|
||||
dispatch(loadConfigError(error, locationURL));
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (getState()['features/base/config'].locationURL === locationURL) {
|
||||
dispatch(setLocationURL(locationURL));
|
||||
dispatch(setConfig(config));
|
||||
dispatch(setRoom(room));
|
||||
} else {
|
||||
if (getState()['features/base/config'].locationURL !== locationURL) {
|
||||
dispatch(loadConfigError(new Error('Config no longer needed!'), locationURL));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(setLocationURL(locationURL));
|
||||
dispatch(setConfig(config));
|
||||
dispatch(setRoom(room));
|
||||
|
||||
// FIXME: unify with web, currently the connection and track creation happens in conference.js.
|
||||
if (room && navigator.product === 'ReactNative') {
|
||||
dispatch(createDesiredLocalTracks());
|
||||
dispatch(connect());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import React from 'react';
|
||||
import { DialogContainer } from '../../base/dialog';
|
||||
import '../../base/responsive-ui';
|
||||
import '../../chat';
|
||||
import '../../external-api';
|
||||
import '../../room-lock';
|
||||
import '../../video-layout';
|
||||
|
||||
|
||||
@@ -4,9 +4,8 @@ import { generateRoomWithoutSeparator } from 'js-utils/random';
|
||||
import type { Component } from 'react';
|
||||
|
||||
import { isRoomValid } from '../base/conference';
|
||||
import JitsiMeetJS from '../base/lib-jitsi-meet';
|
||||
import { Platform } from '../base/react';
|
||||
import { toState } from '../base/redux';
|
||||
import { isSupportedBrowser } from '../base/environment';
|
||||
import { Conference } from '../conference';
|
||||
import { getDeepLinkingPage } from '../deep-linking';
|
||||
import { UnsupportedDesktopBrowser } from '../unsupported-browser';
|
||||
@@ -40,69 +39,109 @@ export type Route = {
|
||||
*/
|
||||
export function _getRouteToRender(stateful: Function | Object): Promise<Route> {
|
||||
const state = toState(stateful);
|
||||
const { room } = state['features/base/conference'];
|
||||
const isMobileApp = navigator.product === 'ReactNative';
|
||||
const isMobileBrowser
|
||||
= !isMobileApp && (Platform.OS === 'android' || Platform.OS === 'ios');
|
||||
const route: Route = {
|
||||
|
||||
if (navigator.product === 'ReactNative') {
|
||||
return _getMobileRoute(state);
|
||||
}
|
||||
|
||||
return _getWebConferenceRoute(state) || _getWebWelcomePageRoute(state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@code Route} to display on the React Native app.
|
||||
*
|
||||
* @param {Object} state - The redux state.
|
||||
* @returns {Promise<Route>}
|
||||
*/
|
||||
function _getMobileRoute(state): Promise<Route> {
|
||||
const route = _getEmptyRoute();
|
||||
|
||||
if (isRoomValid(state['features/base/conference'].room)) {
|
||||
route.component = Conference;
|
||||
} else if (isWelcomePageAppEnabled(state)) {
|
||||
route.component = WelcomePage;
|
||||
} else {
|
||||
route.component = BlankPage;
|
||||
}
|
||||
|
||||
return Promise.resolve(route);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@code Route} to display when trying to access a conference if
|
||||
* a valid conference is being joined.
|
||||
*
|
||||
* @param {Object} state - The redux state.
|
||||
* @returns {Promise<Route>|undefined}
|
||||
*/
|
||||
function _getWebConferenceRoute(state): ?Promise<Route> {
|
||||
if (!isRoomValid(state['features/base/conference'].room)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const route = _getEmptyRoute();
|
||||
|
||||
// Update the location if it doesn't match. This happens when a room is
|
||||
// joined from the welcome page. The reason for doing this instead of using
|
||||
// the history API is that we want to load the config.js which takes the
|
||||
// room into account.
|
||||
const { locationURL } = state['features/base/connection'];
|
||||
|
||||
if (window.location.href !== locationURL.href) {
|
||||
route.href = locationURL.href;
|
||||
|
||||
return Promise.resolve(route);
|
||||
}
|
||||
|
||||
return getDeepLinkingPage(state)
|
||||
.then(deepLinkComponent => {
|
||||
if (deepLinkComponent) {
|
||||
route.component = deepLinkComponent;
|
||||
} else if (isSupportedBrowser()) {
|
||||
route.component = Conference;
|
||||
} else {
|
||||
route.component = UnsupportedDesktopBrowser;
|
||||
}
|
||||
|
||||
return route;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@code Route} to display when trying to access the welcome page.
|
||||
*
|
||||
* @param {Object} state - The redux state.
|
||||
* @returns {Promise<Route>}
|
||||
*/
|
||||
function _getWebWelcomePageRoute(state): Promise<Route> {
|
||||
const route = _getEmptyRoute();
|
||||
|
||||
if (isWelcomePageUserEnabled(state)) {
|
||||
if (isSupportedBrowser()) {
|
||||
route.component = WelcomePage;
|
||||
} else {
|
||||
route.component = UnsupportedDesktopBrowser;
|
||||
}
|
||||
} else {
|
||||
// Web: if the welcome page is disabled, go directly to a random room.
|
||||
|
||||
let href = window.location.href;
|
||||
|
||||
href.endsWith('/') || (href += '/');
|
||||
route.href = href + generateRoomWithoutSeparator();
|
||||
}
|
||||
|
||||
return Promise.resolve(route);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the default {@code Route}.
|
||||
*
|
||||
* @returns {Route}
|
||||
*/
|
||||
function _getEmptyRoute(): Route {
|
||||
return {
|
||||
component: BlankPage,
|
||||
href: undefined
|
||||
};
|
||||
|
||||
return new Promise(resolve => {
|
||||
// First, check if the current endpoint supports WebRTC. We are
|
||||
// intentionally not performing the check for mobile browsers because:
|
||||
// - the WelcomePage is mobile ready;
|
||||
// - if the URL points to a conference, getDeepLinkingPage will take
|
||||
// care of it.
|
||||
if (!isMobileBrowser && !JitsiMeetJS.isWebRtcSupported()) {
|
||||
route.component = UnsupportedDesktopBrowser;
|
||||
resolve(route);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (isRoomValid(room)) {
|
||||
if (isMobileApp) {
|
||||
route.component = Conference;
|
||||
resolve(route);
|
||||
} else {
|
||||
// Update the location if it doesn't match. This happens when a
|
||||
// room is joined from the welcome page. The reason for doing
|
||||
// this instead of using the history API is that we want to load
|
||||
// the config.js which takes the room into account.
|
||||
const { locationURL } = state['features/base/connection'];
|
||||
|
||||
// eslint-disable-next-line no-negated-condition
|
||||
if (window.location.href !== locationURL.href) {
|
||||
route.href = locationURL.href;
|
||||
resolve(route);
|
||||
} else {
|
||||
// Maybe show deep-linking, otherwise go to Conference.
|
||||
getDeepLinkingPage(state).then(component => {
|
||||
route.component = component || Conference;
|
||||
resolve(route);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isWelcomePageUserEnabled(state)) {
|
||||
// Web: if the welcome page is disabled, go directly to a random
|
||||
// room.
|
||||
|
||||
let href = window.location.href;
|
||||
|
||||
href.endsWith('/') || (href += '/');
|
||||
route.href = href + generateRoomWithoutSeparator();
|
||||
} else if (isWelcomePageAppEnabled(state)) {
|
||||
// Mobile: only go to the welcome page if enabled.
|
||||
|
||||
route.component = WelcomePage;
|
||||
}
|
||||
|
||||
resolve(route);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -64,12 +64,14 @@ class WaitForOwnerDialog extends Component<Props> {
|
||||
|
||||
return (
|
||||
<ConfirmDialog
|
||||
cancelKey = 'dialog.Cancel'
|
||||
contentKey = {
|
||||
{
|
||||
key: 'dialog.WaitForHostMsgWOk',
|
||||
params: { room }
|
||||
}
|
||||
}
|
||||
okKey = 'dialog.Ok'
|
||||
onCancel = { this._onCancel }
|
||||
onSubmit = { this._onLogin } />
|
||||
);
|
||||
|
||||
@@ -17,11 +17,6 @@ declare var APP;
|
||||
*/
|
||||
export function appWillMount(app: Object) {
|
||||
return (dispatch: Dispatch<any>) => {
|
||||
dispatch({
|
||||
type: APP_WILL_MOUNT,
|
||||
app
|
||||
});
|
||||
|
||||
// TODO There was a redux action creator appInit which I did not like
|
||||
// because we already had the redux action creator appWillMount and,
|
||||
// respectively, the redux action APP_WILL_MOUNT. So I set out to remove
|
||||
@@ -30,6 +25,11 @@ export function appWillMount(app: Object) {
|
||||
// API module into its own feature yet so we're bound to work on that in
|
||||
// the future.
|
||||
typeof APP === 'object' && APP.API.init();
|
||||
|
||||
dispatch({
|
||||
type: APP_WILL_MOUNT,
|
||||
app
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -84,6 +84,8 @@ function _addConferenceListeners(conference, dispatch) {
|
||||
conference.on(
|
||||
JitsiConferenceEvents.CONFERENCE_LEFT,
|
||||
(...args) => dispatch(conferenceLeft(conference, ...args)));
|
||||
conference.on(JitsiConferenceEvents.SUBJECT_CHANGED,
|
||||
(...args) => dispatch(conferenceSubjectChanged(...args)));
|
||||
|
||||
conference.on(
|
||||
JitsiConferenceEvents.KICKED,
|
||||
@@ -757,10 +759,6 @@ export function setSubject(subject: string = '') {
|
||||
const { conference } = getState()['features/base/conference'];
|
||||
|
||||
if (conference) {
|
||||
dispatch({
|
||||
type: SET_PENDING_SUBJECT_CHANGE,
|
||||
subject: undefined
|
||||
});
|
||||
conference.setSubject(subject);
|
||||
} else {
|
||||
dispatch({
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
// @flow
|
||||
|
||||
import _ from 'lodash';
|
||||
|
||||
import { JitsiTrackErrors } from '../lib-jitsi-meet';
|
||||
import {
|
||||
getLocalParticipant,
|
||||
@@ -155,10 +157,10 @@ export function forEachConference(
|
||||
export function getConferenceName(stateful: Function | Object): string {
|
||||
const state = toState(stateful);
|
||||
const { callee } = state['features/base/jwt'];
|
||||
const { callDisplayName } = state['features/base/config'];
|
||||
const { pendingSubjectChange, room, subject } = state['features/base/conference'];
|
||||
|
||||
return state['features/base/config'].callDisplayName
|
||||
|| (callee && callee.name)
|
||||
|| state['features/base/conference'].room;
|
||||
return pendingSubjectChange || subject || callDisplayName || (callee && callee.name) || _.startCase(room);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -24,7 +24,6 @@ import { TRACK_ADDED, TRACK_REMOVED } from '../tracks';
|
||||
|
||||
import {
|
||||
conferenceFailed,
|
||||
conferenceLeft,
|
||||
conferenceWillLeave,
|
||||
createConference,
|
||||
setLastN,
|
||||
@@ -38,6 +37,7 @@ import {
|
||||
DATA_CHANNEL_OPENED,
|
||||
SET_AUDIO_ONLY,
|
||||
SET_LASTN,
|
||||
SET_PENDING_SUBJECT_CHANGE,
|
||||
SET_ROOM
|
||||
} from './actionTypes';
|
||||
import {
|
||||
@@ -329,10 +329,17 @@ function _connectionFailed({ dispatch, getState }, next, action) {
|
||||
* @private
|
||||
* @returns {Object} The value returned by {@code next(action)}.
|
||||
*/
|
||||
function _conferenceSubjectChanged({ getState }, next, action) {
|
||||
function _conferenceSubjectChanged({ dispatch, getState }, next, action) {
|
||||
const result = next(action);
|
||||
const { subject } = getState()['features/base/conference'];
|
||||
|
||||
if (subject) {
|
||||
dispatch({
|
||||
type: SET_PENDING_SUBJECT_CHANGE,
|
||||
subject: undefined
|
||||
});
|
||||
}
|
||||
|
||||
typeof APP === 'object' && APP.API.notifySubjectChanged(subject);
|
||||
|
||||
return result;
|
||||
@@ -567,48 +574,30 @@ function _setReceiverVideoConstraint(conference, preferred, max) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies the feature {@code base/conference} that the redix action
|
||||
* {@link SET_ROOM} is being dispatched within a specific redux store.
|
||||
* Notifies the feature base/conference that the action
|
||||
* {@code SET_ROOM} is being dispatched within a specific
|
||||
* redux store.
|
||||
*
|
||||
* @param {Store} store - The redux store in which the specified {@code action}
|
||||
* is being dispatched.
|
||||
* @param {Dispatch} next - The redux {@code dispatch} function to dispatch the
|
||||
* specified {@code action} to the specified {@code store}.
|
||||
* @param {Action} action - The redux action {@code SET_ROOM} which is being
|
||||
* dispatched in the specified {@code store}.
|
||||
* @param {Action} action - The redux action {@code SET_ROOM}
|
||||
* which is being dispatched in the specified {@code store}.
|
||||
* @private
|
||||
* @returns {Object} The value returned by {@code next(action)}.
|
||||
*/
|
||||
function _setRoom({ dispatch, getState }, next, action) {
|
||||
const result = next(action);
|
||||
|
||||
// By the time SET_ROOM is dispatched, base/connection's locationURL and
|
||||
// base/conference's leaving should be the only conference-related sources
|
||||
// of truth.
|
||||
const state = getState();
|
||||
const { leaving } = state['features/base/conference'];
|
||||
const { locationURL } = state['features/base/connection'];
|
||||
const dispatchConferenceLeft = new Set();
|
||||
const { subject } = state['features/base/config'];
|
||||
const { room } = action;
|
||||
|
||||
// Figure out which of the JitsiConferences referenced by base/conference
|
||||
// have not dispatched or are not likely to dispatch CONFERENCE_FAILED and
|
||||
// CONFERENCE_LEFT.
|
||||
forEachConference(state, (conference, url) => {
|
||||
if (conference !== leaving && url && url !== locationURL) {
|
||||
dispatchConferenceLeft.add(conference);
|
||||
}
|
||||
|
||||
return true; // All JitsiConference instances are to be examined.
|
||||
});
|
||||
|
||||
// Dispatch CONFERENCE_LEFT for the JitsiConferences referenced by
|
||||
// base/conference which have not dispatched or are not likely to dispatch
|
||||
// CONFERENCE_FAILED or CONFERENCE_LEFT.
|
||||
for (const conference of dispatchConferenceLeft) {
|
||||
dispatch(conferenceLeft(conference));
|
||||
if (room) {
|
||||
// Set the stored subject.
|
||||
dispatch(setSubject(subject));
|
||||
}
|
||||
|
||||
return result;
|
||||
return next(action);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -21,6 +21,7 @@ const WHITELISTED_KEYS = [
|
||||
'_peerConnStatusOutOfLastNTimeout',
|
||||
'_peerConnStatusRtcMuteTimeout',
|
||||
'abTesting',
|
||||
'analytics.disabled',
|
||||
'autoRecord',
|
||||
'autoRecordToken',
|
||||
'avgRtpStatsN',
|
||||
@@ -101,7 +102,7 @@ const WHITELISTED_KEYS = [
|
||||
'enableDisplayNameInStats',
|
||||
'enableLayerSuspension',
|
||||
'enableLipSync',
|
||||
'enableLocalVideoFlip',
|
||||
'disableLocalVideoFlip',
|
||||
'enableRemb',
|
||||
'enableStatsID',
|
||||
'enableTalkWhileMuted',
|
||||
@@ -131,10 +132,12 @@ const WHITELISTED_KEYS = [
|
||||
'startAudioMuted',
|
||||
'startAudioOnly',
|
||||
'startBitrate',
|
||||
'startSilent',
|
||||
'startScreenSharing',
|
||||
'startVideoMuted',
|
||||
'startWithAudioMuted',
|
||||
'startWithVideoMuted',
|
||||
'subject',
|
||||
'testing',
|
||||
'useIPv6',
|
||||
'useNicks',
|
||||
|
||||
@@ -370,10 +370,7 @@ export function disconnect() {
|
||||
if (connection_) {
|
||||
promise = promise.then(() => connection_.disconnect());
|
||||
} else {
|
||||
// FIXME: We have no connection! Fake a disconnect. Because of how the current disconnec is implemented
|
||||
// (by doing the diconnect() in the Conference component unmount) we have lost the location URL already.
|
||||
// Oh well, at least send the event.
|
||||
promise.then(() => dispatch(_connectionDisconnected({}, '')));
|
||||
logger.info('No connection found while disconnecting.');
|
||||
}
|
||||
|
||||
return promise;
|
||||
|
||||
@@ -1,3 +1,25 @@
|
||||
/**
|
||||
* The type of Redux action which signals that an error occurred while obtaining
|
||||
* a camera.
|
||||
*
|
||||
* {
|
||||
* type: NOTIFY_CAMERA_ERROR,
|
||||
* error: Object
|
||||
* }
|
||||
*/
|
||||
export const NOTIFY_CAMERA_ERROR = 'NOTIFY_CAMERA_ERROR';
|
||||
|
||||
/**
|
||||
* The type of Redux action which signals that an error occurred while obtaining
|
||||
* a microphone.
|
||||
*
|
||||
* {
|
||||
* type: NOTIFY_MIC_ERROR,
|
||||
* error: Object
|
||||
* }
|
||||
*/
|
||||
export const NOTIFY_MIC_ERROR = 'NOTIFY_MIC_ERROR';
|
||||
|
||||
/**
|
||||
* The type of Redux action which signals that the currently used audio
|
||||
* input device should be changed.
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
import JitsiMeetJS from '../lib-jitsi-meet';
|
||||
import { updateSettings } from '../settings';
|
||||
import {
|
||||
getUserSelectedOutputDeviceId,
|
||||
updateSettings
|
||||
} from '../settings';
|
||||
|
||||
import {
|
||||
ADD_PENDING_DEVICE_REQUEST,
|
||||
CHECK_AND_NOTIFY_FOR_NEW_DEVICE,
|
||||
NOTIFY_CAMERA_ERROR,
|
||||
NOTIFY_MIC_ERROR,
|
||||
REMOVE_PENDING_DEVICE_REQUESTS,
|
||||
SET_AUDIO_INPUT_DEVICE,
|
||||
SET_VIDEO_INPUT_DEVICE,
|
||||
@@ -18,6 +23,28 @@ import {
|
||||
|
||||
const logger = require('jitsi-meet-logger').getLogger(__filename);
|
||||
|
||||
/**
|
||||
* Maps the WebRTC string for device type to the keys used to store configure,
|
||||
* within redux, which devices should be used by default.
|
||||
*/
|
||||
const DEVICE_TYPE_TO_SETTINGS_KEYS = {
|
||||
audioInput: {
|
||||
currentDeviceId: 'micDeviceId',
|
||||
userSelectedDeviceId: 'userSelectedMicDeviceId',
|
||||
userSelectedDeviceLabel: 'userSelectedMicDeviceLabel'
|
||||
},
|
||||
audioOutput: {
|
||||
currentDeviceId: 'audioOutputDeviceId',
|
||||
userSelectedDeviceId: 'userSelectedAudioOutputDeviceId',
|
||||
userSelectedDeviceLabel: 'userSelectedAudioOutputDeviceLabel'
|
||||
},
|
||||
videoInput: {
|
||||
currentDeviceId: 'audioOutputDeviceId',
|
||||
userSelectedDeviceId: 'userSelectedCameraDeviceId',
|
||||
userSelectedDeviceLabel: 'userSelectedCameraDeviceLabel'
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a pending device request.
|
||||
*
|
||||
@@ -67,19 +94,19 @@ export function configureInitialDevices() {
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const newSettings = {};
|
||||
const devicesKeysToSettingsKeys = {
|
||||
audioInput: 'micDeviceId',
|
||||
audioOutput: 'audioOutputDeviceId',
|
||||
videoInput: 'cameraDeviceId'
|
||||
};
|
||||
|
||||
Object.keys(deviceLabels).forEach(key => {
|
||||
const label = deviceLabels[key];
|
||||
const deviceId = getDeviceIdByLabel(state, label, key);
|
||||
|
||||
if (deviceId) {
|
||||
newSettings[devicesKeysToSettingsKeys[key]] = deviceId;
|
||||
const settingsTranslationMap = DEVICE_TYPE_TO_SETTINGS_KEYS[key];
|
||||
|
||||
newSettings[settingsTranslationMap.currentDeviceId] = deviceId;
|
||||
newSettings[settingsTranslationMap.userSelectedDeviceId] = deviceId;
|
||||
newSettings[settingsTranslationMap.userSelectedDeviceLabel] = label;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -91,8 +118,7 @@ export function configureInitialDevices() {
|
||||
|
||||
return updateSettingsPromise
|
||||
.then(() => {
|
||||
const { userSelectedAudioOutputDeviceId }
|
||||
= getState()['features/base/settings'];
|
||||
const userSelectedAudioOutputDeviceId = getUserSelectedOutputDeviceId(getState());
|
||||
|
||||
return setAudioOutputDeviceId(userSelectedAudioOutputDeviceId, dispatch)
|
||||
.catch(ex => logger.warn(`Failed to set audio output device.
|
||||
@@ -124,6 +150,43 @@ export function getAvailableDevices() {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Signals that an error occurred while trying to obtain a track from a camera.
|
||||
*
|
||||
* @param {Object} error - The device error, as provided by lib-jitsi-meet.
|
||||
* @param {string} error.name - The constant for the type of the error.
|
||||
* @param {string} error.message - Optional additional information about the
|
||||
* error.
|
||||
* @returns {{
|
||||
* type: NOTIFY_CAMERA_ERROR,
|
||||
* error: Object
|
||||
* }}
|
||||
*/
|
||||
export function notifyCameraError(error) {
|
||||
return {
|
||||
type: NOTIFY_CAMERA_ERROR,
|
||||
error
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Signals that an error occurred while trying to obtain a track from a mic.
|
||||
*
|
||||
* @param {Object} error - The device error, as provided by lib-jitsi-meet.
|
||||
* @param {Object} error.name - The constant for the type of the error.
|
||||
* @param {string} error.message - Optional additional information about the
|
||||
* error.
|
||||
* @returns {{
|
||||
* type: NOTIFY_MIC_ERROR,
|
||||
* error: Object
|
||||
* }}
|
||||
*/
|
||||
export function notifyMicError(error) {
|
||||
return {
|
||||
type: NOTIFY_MIC_ERROR,
|
||||
error
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all pending device requests.
|
||||
|
||||
@@ -67,6 +67,34 @@ export function getDeviceIdByLabel(state: Object, label: string, kind: string) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a device with a label that matches the passed id and returns its label.
|
||||
*
|
||||
* @param {Object} state - The redux state.
|
||||
* @param {string} id - The device id.
|
||||
* @param {string} kind - The type of the device. One of "audioInput",
|
||||
* "audioOutput", and "videoInput". Also supported is all lowercase versions
|
||||
* of the preceding types.
|
||||
* @returns {string|undefined}
|
||||
*/
|
||||
export function getDeviceLabelById(state: Object, id: string, kind: string) {
|
||||
const webrtcKindToJitsiKindTranslator = {
|
||||
audioinput: 'audioInput',
|
||||
audiooutput: 'audioOutput',
|
||||
videoinput: 'videoInput'
|
||||
};
|
||||
|
||||
const kindToSearch = webrtcKindToJitsiKindTranslator[kind] || kind;
|
||||
|
||||
const device
|
||||
= (state['features/base/devices'].availableDevices[kindToSearch] || [])
|
||||
.find(d => d.deviceId === id);
|
||||
|
||||
if (device) {
|
||||
return device.label;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the devices set in the URL.
|
||||
*
|
||||
@@ -118,24 +146,29 @@ export function groupDevicesByKind(devices: Object[]): Object {
|
||||
* @param {string} newId - New audio output device id.
|
||||
* @param {Function} dispatch - The Redux dispatch function.
|
||||
* @param {boolean} userSelection - Whether this is a user selection update.
|
||||
* @param {?string} newLabel - New audio output device label to store.
|
||||
* @returns {Promise}
|
||||
*/
|
||||
export function setAudioOutputDeviceId(
|
||||
newId: string = 'default',
|
||||
dispatch: Function,
|
||||
userSelection: boolean = false): Promise<*> {
|
||||
userSelection: boolean = false,
|
||||
newLabel: ?string): Promise<*> {
|
||||
return JitsiMeetJS.mediaDevices.setAudioOutputDevice(newId)
|
||||
.then(() => {
|
||||
const newSettings = {
|
||||
audioOutputDeviceId: newId,
|
||||
userSelectedAudioOutputDeviceId: undefined
|
||||
userSelectedAudioOutputDeviceId: undefined,
|
||||
userSelectedAudioOutputDeviceLabel: undefined
|
||||
};
|
||||
|
||||
if (userSelection) {
|
||||
newSettings.userSelectedAudioOutputDeviceId = newId;
|
||||
newSettings.userSelectedAudioOutputDeviceLabel = newLabel;
|
||||
} else {
|
||||
// a flow workaround, I needed to add 'userSelectedAudioOutputDeviceId: undefined'
|
||||
delete newSettings.userSelectedAudioOutputDeviceId;
|
||||
delete newSettings.userSelectedAudioOutputDeviceLabel;
|
||||
}
|
||||
|
||||
return dispatch(updateSettings(newSettings));
|
||||
|
||||
@@ -4,6 +4,7 @@ import { CONFERENCE_JOINED } from '../conference';
|
||||
import { processExternalDeviceRequest } from '../../device-selection';
|
||||
import { MiddlewareRegistry } from '../redux';
|
||||
import UIEvents from '../../../../service/UI/UIEvents';
|
||||
import { JitsiTrackErrors } from '../lib-jitsi-meet';
|
||||
|
||||
import {
|
||||
removePendingDeviceRequests,
|
||||
@@ -12,15 +13,35 @@ import {
|
||||
} from './actions';
|
||||
import {
|
||||
CHECK_AND_NOTIFY_FOR_NEW_DEVICE,
|
||||
NOTIFY_CAMERA_ERROR,
|
||||
NOTIFY_MIC_ERROR,
|
||||
SET_AUDIO_INPUT_DEVICE,
|
||||
SET_VIDEO_INPUT_DEVICE
|
||||
} from './actionTypes';
|
||||
import { showNotification } from '../../notifications';
|
||||
import { showNotification, showWarningNotification } from '../../notifications';
|
||||
import { updateSettings } from '../settings';
|
||||
import { setAudioOutputDeviceId } from './functions';
|
||||
|
||||
const logger = require('jitsi-meet-logger').getLogger(__filename);
|
||||
|
||||
const JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP = {
|
||||
microphone: {
|
||||
[JitsiTrackErrors.CONSTRAINT_FAILED]: 'dialog.micConstraintFailedError',
|
||||
[JitsiTrackErrors.GENERAL]: 'dialog.micUnknownError',
|
||||
[JitsiTrackErrors.NO_DATA_FROM_SOURCE]: 'dialog.micNotSendingData',
|
||||
[JitsiTrackErrors.NOT_FOUND]: 'dialog.micNotFoundError',
|
||||
[JitsiTrackErrors.PERMISSION_DENIED]: 'dialog.micPermissionDeniedError'
|
||||
},
|
||||
camera: {
|
||||
[JitsiTrackErrors.CONSTRAINT_FAILED]: 'dialog.cameraConstraintFailedError',
|
||||
[JitsiTrackErrors.GENERAL]: 'dialog.cameraUnknownError',
|
||||
[JitsiTrackErrors.NO_DATA_FROM_SOURCE]: 'dialog.cameraNotSendingData',
|
||||
[JitsiTrackErrors.NOT_FOUND]: 'dialog.cameraNotFoundError',
|
||||
[JitsiTrackErrors.PERMISSION_DENIED]: 'dialog.cameraPermissionDeniedError',
|
||||
[JitsiTrackErrors.UNSUPPORTED_RESOLUTION]: 'dialog.cameraUnsupportedResolutionError'
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Implements the middleware of the feature base/devices.
|
||||
*
|
||||
@@ -32,6 +53,53 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
switch (action.type) {
|
||||
case CONFERENCE_JOINED:
|
||||
return _conferenceJoined(store, next, action);
|
||||
case NOTIFY_CAMERA_ERROR: {
|
||||
if (typeof APP !== 'object' || !action.error) {
|
||||
break;
|
||||
}
|
||||
|
||||
const { message, name } = action.error;
|
||||
|
||||
const cameraJitsiTrackErrorMsg
|
||||
= JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.camera[name];
|
||||
const cameraErrorMsg = cameraJitsiTrackErrorMsg
|
||||
|| JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP
|
||||
.camera[JitsiTrackErrors.GENERAL];
|
||||
const additionalCameraErrorMsg = cameraJitsiTrackErrorMsg ? null : message;
|
||||
|
||||
store.dispatch(showWarningNotification({
|
||||
description: additionalCameraErrorMsg,
|
||||
descriptionKey: cameraErrorMsg,
|
||||
titleKey: name === JitsiTrackErrors.PERMISSION_DENIED
|
||||
? 'deviceError.cameraPermission' : 'deviceError.cameraError'
|
||||
}));
|
||||
|
||||
break;
|
||||
}
|
||||
case NOTIFY_MIC_ERROR: {
|
||||
if (typeof APP !== 'object' || !action.error) {
|
||||
break;
|
||||
}
|
||||
|
||||
const { message, name } = action.error;
|
||||
|
||||
const micJitsiTrackErrorMsg
|
||||
= JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.microphone[name];
|
||||
const micErrorMsg = micJitsiTrackErrorMsg
|
||||
|| JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP
|
||||
.microphone[JitsiTrackErrors.GENERAL];
|
||||
const additionalMicErrorMsg = micJitsiTrackErrorMsg ? null : message;
|
||||
|
||||
store.dispatch(showWarningNotification({
|
||||
description: additionalMicErrorMsg,
|
||||
descriptionKey: micErrorMsg,
|
||||
titleKey: name === JitsiTrackErrors.PERMISSION_DENIED
|
||||
? 'deviceError.microphonePermission'
|
||||
: 'deviceError.microphoneError'
|
||||
}));
|
||||
|
||||
break;
|
||||
}
|
||||
case SET_AUDIO_INPUT_DEVICE:
|
||||
APP.UI.emitEvent(UIEvents.AUDIO_DEVICE_CHANGED, action.deviceId);
|
||||
break;
|
||||
@@ -79,7 +147,9 @@ function _conferenceJoined({ dispatch, getState }, next, action) {
|
||||
|
||||
/**
|
||||
* Finds a new device by comparing new and old array of devices and dispatches
|
||||
* notification with the new device.
|
||||
* notification with the new device. For new devices with same groupId only one
|
||||
* notification will be shown, this is so to avoid showing multiple notifications
|
||||
* for audio input and audio output devices.
|
||||
*
|
||||
* @param {Store} store - The redux store in which the specified {@code action}
|
||||
* is being dispatched.
|
||||
@@ -97,7 +167,25 @@ function _checkAndNotifyForNewDevice(store, newDevices, oldDevices) {
|
||||
nDevice => !oldDevices.find(
|
||||
device => device.deviceId === nDevice.deviceId));
|
||||
|
||||
onlyNewDevices.forEach(newDevice => {
|
||||
// we group devices by groupID which normally is the grouping by physical device
|
||||
// plugging in headset we provide normally two device, one input and one output
|
||||
// and we want to show only one notification for this physical audio device
|
||||
const devicesGroupBy = onlyNewDevices.reduce((accumulated, value) => {
|
||||
accumulated[value.groupId] = accumulated[value.groupId] || [];
|
||||
accumulated[value.groupId].push(value);
|
||||
|
||||
return accumulated;
|
||||
}, {});
|
||||
|
||||
Object.values(devicesGroupBy).forEach(devicesArray => {
|
||||
|
||||
if (devicesArray.length < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
// let's get the first device as a reference, we will use it for
|
||||
// label and type
|
||||
const newDevice = devicesArray[0];
|
||||
|
||||
// we want to strip any device details that are not very
|
||||
// user friendly, like usb ids put in brackets at the end
|
||||
@@ -115,12 +203,9 @@ function _checkAndNotifyForNewDevice(store, newDevices, oldDevices) {
|
||||
titleKey = 'notify.newDeviceCameraTitle';
|
||||
break;
|
||||
}
|
||||
case 'audioinput': {
|
||||
titleKey = 'notify.newDeviceMicTitle';
|
||||
break;
|
||||
}
|
||||
case 'audioinput' :
|
||||
case 'audiooutput': {
|
||||
titleKey = 'notify.newDeviceCameraTitle';
|
||||
titleKey = 'notify.newDeviceAudioTitle';
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -129,7 +214,7 @@ function _checkAndNotifyForNewDevice(store, newDevices, oldDevices) {
|
||||
description,
|
||||
titleKey,
|
||||
customActionNameKey: 'notify.newDeviceAction',
|
||||
customActionHandler: _useDevice.bind(undefined, store, newDevice)
|
||||
customActionHandler: _useDevice.bind(undefined, store, devicesArray)
|
||||
}));
|
||||
});
|
||||
}
|
||||
@@ -139,44 +224,49 @@ function _checkAndNotifyForNewDevice(store, newDevices, oldDevices) {
|
||||
*
|
||||
* @param {Store} store - The redux store in which the specified {@code action}
|
||||
* is being dispatched.
|
||||
* @param {MediaDeviceInfo} device - The device to save.
|
||||
* @param {Array<MediaDeviceInfo|InputDeviceInfo>} devices - The devices to save.
|
||||
* @returns {boolean} - Returns true in order notifications to be dismissed.
|
||||
* @private
|
||||
*/
|
||||
function _useDevice({ dispatch }, device) {
|
||||
switch (device.kind) {
|
||||
case 'videoinput': {
|
||||
dispatch(updateSettings({
|
||||
userSelectedCameraDeviceId: device.deviceId
|
||||
}));
|
||||
function _useDevice({ dispatch }, devices) {
|
||||
devices.forEach(device => {
|
||||
switch (device.kind) {
|
||||
case 'videoinput': {
|
||||
dispatch(updateSettings({
|
||||
userSelectedCameraDeviceId: device.deviceId,
|
||||
userSelectedCameraDeviceLabel: device.label
|
||||
}));
|
||||
|
||||
dispatch(setVideoInputDevice(device.deviceId));
|
||||
break;
|
||||
}
|
||||
case 'audioinput': {
|
||||
dispatch(updateSettings({
|
||||
userSelectedMicDeviceId: device.deviceId
|
||||
}));
|
||||
dispatch(setVideoInputDevice(device.deviceId));
|
||||
break;
|
||||
}
|
||||
case 'audioinput': {
|
||||
dispatch(updateSettings({
|
||||
userSelectedMicDeviceId: device.deviceId,
|
||||
userSelectedMicDeviceLabel: device.label
|
||||
}));
|
||||
|
||||
dispatch(setAudioInputDevice(device.deviceId));
|
||||
break;
|
||||
}
|
||||
case 'audiooutput': {
|
||||
setAudioOutputDeviceId(
|
||||
device.deviceId,
|
||||
dispatch,
|
||||
true)
|
||||
.then(() => logger.log('changed audio output device'))
|
||||
.catch(err => {
|
||||
logger.warn(
|
||||
'Failed to change audio output device.',
|
||||
'Default or previously set audio output device will',
|
||||
' be used instead.',
|
||||
err);
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
dispatch(setAudioInputDevice(device.deviceId));
|
||||
break;
|
||||
}
|
||||
case 'audiooutput': {
|
||||
setAudioOutputDeviceId(
|
||||
device.deviceId,
|
||||
dispatch,
|
||||
true,
|
||||
device.label)
|
||||
.then(() => logger.log('changed audio output device'))
|
||||
.catch(err => {
|
||||
logger.warn(
|
||||
'Failed to change audio output device.',
|
||||
'Default or previously set audio output device will',
|
||||
' be used instead.',
|
||||
err);
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
52
react/features/base/dialog/components/native/AlertDialog.js
Normal file
52
react/features/base/dialog/components/native/AlertDialog.js
Normal file
@@ -0,0 +1,52 @@
|
||||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import { Text } from 'react-native';
|
||||
|
||||
import { translate } from '../../../i18n';
|
||||
import { connect } from '../../../redux';
|
||||
|
||||
import { _abstractMapStateToProps } from '../../functions';
|
||||
|
||||
import { type Props as AbstractProps } from './BaseDialog';
|
||||
import BaseSubmitDialog from './BaseSubmitDialog';
|
||||
|
||||
type Props = AbstractProps & {
|
||||
|
||||
/**
|
||||
* Untranslated i18n key of the content to be displayed.
|
||||
*
|
||||
* NOTE: This dialog also adds support to Object type keys that will be
|
||||
* translated using the provided params. See i18n function
|
||||
* {@code translate(string, Object)} for more details.
|
||||
*/
|
||||
contentKey: string | { key: string, params: Object},
|
||||
};
|
||||
|
||||
/**
|
||||
* Implements an alert dialog, to simply show an error or a message, then disappear on dismiss.
|
||||
*/
|
||||
class AlertDialog extends BaseSubmitDialog<Props, *> {
|
||||
/**
|
||||
* Implements {@code BaseSubmitDialog._renderSubmittable}.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
_renderSubmittable() {
|
||||
const { _dialogStyles, contentKey, t } = this.props;
|
||||
const content
|
||||
= typeof contentKey === 'string'
|
||||
? t(contentKey)
|
||||
: this._renderHTML(t(contentKey.key, contentKey.params));
|
||||
|
||||
return (
|
||||
<Text style = { _dialogStyles.text }>
|
||||
{ content }
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
_renderHTML: string => Object | string
|
||||
}
|
||||
|
||||
export default translate(connect(_abstractMapStateToProps)(AlertDialog));
|
||||
@@ -1,7 +1,7 @@
|
||||
// @flow
|
||||
|
||||
import React, { PureComponent, type Node } from 'react';
|
||||
import { SafeAreaView, View } from 'react-native';
|
||||
import { Platform, SafeAreaView, ScrollView, View } from 'react-native';
|
||||
|
||||
import { ColorSchemeRegistry } from '../../../color-scheme';
|
||||
import { SlidingView } from '../../../react';
|
||||
@@ -36,21 +36,6 @@ type Props = {
|
||||
* A component emulating Android's BottomSheet.
|
||||
*/
|
||||
class BottomSheet extends PureComponent<Props> {
|
||||
/**
|
||||
* Assembles a style for the BottomSheet container.
|
||||
*
|
||||
* @private
|
||||
* @returns {StyleType}
|
||||
*/
|
||||
_getContainerStyle() {
|
||||
const { _styles } = this.props;
|
||||
|
||||
return {
|
||||
...styles.container,
|
||||
backgroundColor: _styles.sheet.backgroundColor
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
@@ -65,15 +50,46 @@ class BottomSheet extends PureComponent<Props> {
|
||||
onHide = { this.props.onCancel }
|
||||
position = 'bottom'
|
||||
show = { true }>
|
||||
<SafeAreaView
|
||||
style = { this._getContainerStyle() }>
|
||||
<View style = { _styles.sheet }>
|
||||
{ this.props.children }
|
||||
<View
|
||||
pointerEvents = 'box-none'
|
||||
style = { styles.sheetContainer }>
|
||||
<View
|
||||
pointerEvents = 'box-none'
|
||||
style = { styles.sheetAreaCover } />
|
||||
<View
|
||||
style = { [
|
||||
styles.sheetItemContainer,
|
||||
_styles.sheet
|
||||
] }>
|
||||
<ScrollView bounces = { false }>
|
||||
{ this._getWrappedContent() }
|
||||
</ScrollView>
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
</View>
|
||||
</SlidingView>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps the content when needed (iOS 11 and above), or just returns the original children.
|
||||
*
|
||||
* @returns {React$Element}
|
||||
*/
|
||||
_getWrappedContent() {
|
||||
if (Platform.OS === 'ios') {
|
||||
const majorVersionIOS = parseInt(Platform.Version, 10);
|
||||
|
||||
if (majorVersionIOS > 10) {
|
||||
return (
|
||||
<SafeAreaView>
|
||||
{ this.props.children }
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -32,7 +32,12 @@ type Props = BaseProps & {
|
||||
|
||||
t: Function,
|
||||
|
||||
textInputProps: ?Object
|
||||
textInputProps: ?Object,
|
||||
|
||||
/**
|
||||
* Validating of the input.
|
||||
*/
|
||||
validateInput: ?Function
|
||||
}
|
||||
|
||||
type State = {
|
||||
@@ -118,6 +123,12 @@ class InputDialog extends BaseDialog<Props, State> {
|
||||
* @returns {void}
|
||||
*/
|
||||
_onChangeText(fieldValue) {
|
||||
|
||||
if (this.props.validateInput
|
||||
&& !this.props.validateInput(fieldValue)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
fieldValue
|
||||
});
|
||||
|
||||
@@ -4,6 +4,7 @@ export { default as BottomSheet } from './BottomSheet';
|
||||
export { default as ConfirmDialog } from './ConfirmDialog';
|
||||
export { default as CustomDialog } from './CustomDialog';
|
||||
export { default as DialogContainer } from './DialogContainer';
|
||||
export { default as AlertDialog } from './AlertDialog';
|
||||
export { default as InputDialog } from './InputDialog';
|
||||
export { default as CustomSubmitDialog } from './CustomSubmitDialog';
|
||||
|
||||
|
||||
@@ -27,17 +27,28 @@ export const PLACEHOLDER_COLOR = ColorPalette.lightGrey;
|
||||
* been implemented as per the Material Design guidelines:
|
||||
* {@link https://material.io/guidelines/components/bottom-sheets.html}.
|
||||
*/
|
||||
export const bottomSheetStyles = createStyleSheet({
|
||||
export const bottomSheetStyles = {
|
||||
sheetAreaCover: {
|
||||
backgroundColor: ColorPalette.transparent,
|
||||
flex: 1
|
||||
},
|
||||
|
||||
/**
|
||||
* Style for the container of the sheet.
|
||||
*/
|
||||
container: {
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
position: 'absolute',
|
||||
right: 0
|
||||
sheetContainer: {
|
||||
alignItems: 'stretch',
|
||||
flex: 1,
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'flex-end'
|
||||
},
|
||||
|
||||
sheetItemContainer: {
|
||||
flex: -1,
|
||||
maxHeight: '60%',
|
||||
paddingHorizontal: MD_ITEM_MARGIN_PADDING
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const brandedDialog = createStyleSheet({
|
||||
|
||||
@@ -137,10 +148,7 @@ ColorSchemeRegistry.register('BottomSheet', {
|
||||
* Bottom sheet's base style.
|
||||
*/
|
||||
sheet: {
|
||||
backgroundColor: schemeColor('background'),
|
||||
flex: 1,
|
||||
paddingHorizontal: MD_ITEM_MARGIN_PADDING,
|
||||
paddingVertical: 8
|
||||
backgroundColor: schemeColor('background')
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
25
react/features/base/environment/environment.js
Normal file
25
react/features/base/environment/environment.js
Normal file
@@ -0,0 +1,25 @@
|
||||
// @flow
|
||||
|
||||
import JitsiMeetJS from '../lib-jitsi-meet';
|
||||
import { Platform } from '../react';
|
||||
|
||||
import { isBlacklistedEnvironment } from './isBlacklistedEnvironment';
|
||||
|
||||
/**
|
||||
* Returns whether or not the current browser should allow the app to display.
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isSupportedBrowser() {
|
||||
if (navigator.product === 'ReactNative' || isBlacklistedEnvironment()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// We are intentionally allow mobile browsers because:
|
||||
// - the WelcomePage is mobile ready;
|
||||
// - if the URL points to a conference then deep-linking will take
|
||||
// care of it.
|
||||
return Platform.OS === 'android'
|
||||
|| Platform.OS === 'ios'
|
||||
|| JitsiMeetJS.isWebRtcSupported();
|
||||
}
|
||||
1
react/features/base/environment/index.js
Normal file
1
react/features/base/environment/index.js
Normal file
@@ -0,0 +1 @@
|
||||
export * from './environment';
|
||||
11
react/features/base/environment/isBlacklistedEnvironment.js
Normal file
11
react/features/base/environment/isBlacklistedEnvironment.js
Normal file
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Returns whether or not the current browser is supported for showing meeting
|
||||
* based on any custom overrides. This file should be overridden with branding
|
||||
* as needed to fit deployment needs.
|
||||
*
|
||||
* @returns {boolean} True the browser is unsupported due to being blacklisted
|
||||
* by the logic within this function.
|
||||
*/
|
||||
export function isBlacklistedEnvironment() {
|
||||
return false;
|
||||
}
|
||||
@@ -43,7 +43,9 @@ export function createLocalTrack(type: string, deviceId: string) {
|
||||
* otherwise.
|
||||
*/
|
||||
export function isAnalyticsEnabled(stateful: Function | Object) {
|
||||
return !toState(stateful)['features/base/config'].disableThirdPartyRequests;
|
||||
const { disableThirdPartyRequests, analytics = {} } = toState(stateful)['features/base/config'];
|
||||
|
||||
return !disableThirdPartyRequests && !analytics.disabled;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
import { Component } from 'react';
|
||||
|
||||
const logger = require('jitsi-meet-logger').getLogger(__filename);
|
||||
|
||||
/**
|
||||
* Describes audio element interface used in the base/media feature for audio
|
||||
* playback.
|
||||
@@ -10,7 +12,7 @@ export type AudioElement = {
|
||||
currentTime: number,
|
||||
pause: () => void,
|
||||
play: () => void,
|
||||
setSinkId?: string => void,
|
||||
setSinkId?: string => Function,
|
||||
stop: () => void
|
||||
};
|
||||
|
||||
@@ -113,7 +115,8 @@ export default class AbstractAudio extends Component<Props> {
|
||||
setSinkId(sinkId: string): void {
|
||||
this._audioElementImpl
|
||||
&& typeof this._audioElementImpl.setSinkId === 'function'
|
||||
&& this._audioElementImpl.setSinkId(sinkId);
|
||||
&& this._audioElementImpl.setSinkId(sinkId)
|
||||
.catch(error => logger.error('Error setting sink', error));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -260,15 +260,37 @@ function _getAllParticipants(stateful) {
|
||||
: toState(stateful)['features/base/participants'] || []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if all of the meeting participants are moderators.
|
||||
*
|
||||
* @param {Object|Function} stateful -Object or function that can be resolved
|
||||
* to the Redux state.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isEveryoneModerator(stateful: Object | Function) {
|
||||
const participants = _getAllParticipants(stateful);
|
||||
|
||||
for (const participant of participants) {
|
||||
if (participant.role !== PARTICIPANT_ROLE.MODERATOR) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the current local participant is a moderator in the
|
||||
* conference.
|
||||
*
|
||||
* @param {Object|Function} stateful - Object or function that can be resolved
|
||||
* to the Redux state.
|
||||
* @param {?boolean} ignoreToken - When true we ignore the token check.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isLocalParticipantModerator(stateful: Object | Function) {
|
||||
export function isLocalParticipantModerator(
|
||||
stateful: Object | Function,
|
||||
ignoreToken: ?boolean = false) {
|
||||
const state = toState(stateful);
|
||||
const localParticipant = getLocalParticipant(state);
|
||||
|
||||
@@ -278,7 +300,8 @@ export function isLocalParticipantModerator(stateful: Object | Function) {
|
||||
|
||||
return (
|
||||
localParticipant.role === PARTICIPANT_ROLE.MODERATOR
|
||||
&& (!state['features/base/config'].enableUserRolesBasedOnToken
|
||||
&& (ignoreToken
|
||||
|| !state['features/base/config'].enableUserRolesBasedOnToken
|
||||
|| !state['features/base/jwt'].isGuest));
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { translate } from '../../../i18n';
|
||||
|
||||
import BackButton from './BackButton';
|
||||
import ForwardButton from './ForwardButton';
|
||||
import Header from './Header';
|
||||
import HeaderLabel from './HeaderLabel';
|
||||
|
||||
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* Boolean to set the forward button disabled.
|
||||
*/
|
||||
forwardDisabled: boolean,
|
||||
|
||||
/**
|
||||
* The i18n key of the the forward button label.
|
||||
*/
|
||||
forwardLabelKey: ?string,
|
||||
|
||||
/**
|
||||
* The i18n key of the header label (title)
|
||||
*/
|
||||
headerLabelKey: ?string,
|
||||
|
||||
/**
|
||||
* Callback to be invoked on pressing the back button.
|
||||
*/
|
||||
onPressBack: ?Function,
|
||||
|
||||
/**
|
||||
* Callback to be invoked on pressing the forward button.
|
||||
*/
|
||||
onPressForward: ?Function,
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements a header with the standard navigation content.
|
||||
*/
|
||||
class HeaderWithNavigation extends Component<Props> {
|
||||
/**
|
||||
* Implements {@code Component#render}.
|
||||
*
|
||||
* @inheritdoc
|
||||
*/
|
||||
render() {
|
||||
const { onPressBack, onPressForward } = this.props;
|
||||
|
||||
return (
|
||||
<Header>
|
||||
{ onPressBack && <BackButton onPress = { onPressBack } /> }
|
||||
<HeaderLabel labelKey = { this.props.headerLabelKey } />
|
||||
{ onPressForward && <ForwardButton
|
||||
disabled = { this.props.forwardDisabled }
|
||||
labelKey = { this.props.forwardLabelKey }
|
||||
onPress = { onPressForward } /> }
|
||||
</Header>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(HeaderWithNavigation);
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user