Compare commits

...

118 Commits

Author SHA1 Message Date
Leonard Kim
6d8ec4d147 fix(unsupported): stop recommending safari 2019-06-07 07:36:20 -07:00
Leonard Kim
d65a068fdb feat(deep-linking): hide web button on unsupported browsers 2019-06-07 07:36:20 -07:00
Leonard Kim
cf23045f8d feat(unsupported): provide custom blacklist for branding 2019-06-07 07:36:20 -07:00
Leonard Kim
e47d2d13ce fix(deep-linking): deep link first, then show unsupported
Re-structure the custom routing to split between
platforms instead of between intended route features.
This made it easier for me to understand where to
do the checks for unsupported browser after deep-linking
had been checked.
2019-06-07 07:36:20 -07:00
Aaron van Meerten
07b7f03aa7 Merge pull request #4308 from jitsi/prosody-token-allow-asap-server-override
allows override of asap key server in token utility
2019-06-06 14:26:25 -06:00
Aaron van Meerten
7ce44f85ca changed to using a setter for the asapKeyServer 2019-06-06 15:22:38 -05:00
Aaron van Meerten
41e0d782ce allows override of asap key server in token utility 2019-06-06 14:41:46 -05:00
Aaron van Meerten
2a8fafdd36 Merge pull request #4303 from jitsi/start-silent
Adds a config param startSilent to disable audio output.
2019-06-05 12:29:57 -06:00
damencho
faee1c139e Adds a config param startSilent to disable audio output. 2019-06-05 18:01:18 +01:00
virtuacoplenny
eb644987ce fix(settings): do not use non-set localStorage values (#4299)
If a value is not set in localStorage then null is
returned. null should not be converted to an empty
string (via _.escape) because that will then be
stored in localStorage as the user set preference
and will keep overriding any other values set
in localStorage for the displayname.
2019-06-04 17:42:48 -07:00
damencho
de60a70daf Commit from translate.jitsi.org by user damencho.: 555 of 625 strings translated (8 fuzzy). 2019-06-03 11:19:10 +00:00
damencho
2904dfa794 Commit from translate.jitsi.org by user damencho.: 584 of 625 strings translated (2 fuzzy). 2019-06-03 11:19:01 +00:00
damencho
d51cf7c581 Commit from translate.jitsi.org by user damencho.: 625 of 625 strings translated (0 fuzzy). 2019-06-03 10:58:25 +00:00
Hristo Terezov
f25e6c6a5d chore(package.json): Update lib-jitsi-meet version 2019-06-01 02:28:04 -07:00
Hristo Terezov
5fb9422513 feat(API): Add show feedback parameter to hangup 2019-06-01 02:28:04 -07:00
Hristo Terezov
d01cfc8466 fix(conference): API left event. 2019-06-01 02:28:04 -07:00
Saúl Ibarra Corretgé
fa3888991f rn: avoid logging initial props in release builds
They may contain sensitive information.
2019-05-31 11:49:36 +02:00
virtuacoplenny
ded355a807 fix(settings): use moderator check helper (#4292) 2019-05-30 14:10:40 -07:00
Leonard Kim
b655c8d54a fix(large-video): clear remote video stream on track removal
VideoLayout schedules a large video update by passing in
the video stream on the small video instance. When a stream
is removed, the UI is removed from the small video instance
but a reference to the stream is left. So when VideoLayout
schedules the large video update after a stream removal,
the old stream from the small video instance is re-used,
even though it has been removed.

This change also brings balance with RemoteVideo method
"addRemoteStreamElement" which sets the stream on the
small video instance, so now "removeRemoteStreamElement
unsets it.
2019-05-30 09:46:35 -07:00
Leonard Kim
42a6e6faaf ref(large-video): remove redundant call to update on stream removal 2019-05-30 09:46:35 -07:00
Leonard Kim
c7954c284d ref(large-video): move layout update on stream removal to layout middleware 2019-05-30 09:46:35 -07:00
virtuacoplenny
251da1861a feat(api): notify api of mic and camera errors (#4289)
- Use actions to notify the rest of the app that
  a mic or camera error has occurred
- Use middleware to respond to those notifications
  of errors by showing in-app notifications and
  notifying the external api
2019-05-29 14:17:07 -07:00
Hristo Terezov
9712804040 fix(Amplitude): user id 2019-05-29 09:53:31 -07:00
Hristo Terezov
fecbef0aff fix(AmplitudeModule): class name 2019-05-29 17:22:50 +02:00
Saúl Ibarra Corretgé
d65b71b584 rn: add ability to set the conference subject 2019-05-29 14:48:02 +02:00
Saúl Ibarra Corretgé
579d291bca config: add ability to pass the subject as a URL parameter 2019-05-29 14:48:02 +02:00
Saúl Ibarra Corretgé
871026f4ba conference: clear the pending subject after it has been set 2019-05-29 14:48:02 +02:00
Saúl Ibarra Corretgé
9a8a070c62 rn: show conference subject if set 2019-05-29 14:48:02 +02:00
Leonard Kim
7cf4c7bd78 Revert "feat(screenshare): enable auto-pin of latest and last screenshare"
This reverts commit f42d0411b1.

The UX provided by this feature flag in its current state is not
desired. Also, I noticed filmstrip sometimes failing to properly
update small video display mode on pin/unpin. The feature is
being left in for consumers of jitsi-meet to enable as needed.
2019-05-28 15:28:50 -07:00
Hristo Terezov
72a1def571 feat(config): whitelist config.analytics 2019-05-24 13:42:12 -07:00
damencho
0dad99c3b7 Enables local video flip menu by default. 2019-05-24 23:09:24 +03:00
Hristo Terezov
840c0190c4 fix(deep-linking): Don't rely on custom scheme 2019-05-24 12:51:14 -07:00
Leonard Kim
e0fdeea69b fix(large-video): do not stretch dominant speaker avatar 2019-05-24 12:00:35 -07:00
Leonard Kim
e3612929f8 fix(avatar): dynamically size avatar in dynamically sizable filmstrip 2019-05-24 12:00:35 -07:00
Hristo Terezov
70921bb6ef feat(analytics): local tracks duration event. 2019-05-24 10:09:25 -07:00
Saúl Ibarra Corretgé
371ca4eef1 ios: don't require bitcode for Debug builds 2019-05-24 14:11:08 +02:00
Bettenbuk Zoltan
54fdb7066f feat: scrollable bottom sheet 2019-05-24 14:06:17 +02:00
Bettenbuk Zoltan
85bcb0c757 fix: auth dialog button labels 2019-05-24 11:35:56 +02:00
Bettenbuk Zoltan
d387cbe5bd fix: iOS 10 bottom sheet style 2019-05-24 11:28:28 +02:00
paweldomas
1bc28e4904 watchos: display a message if the recent list is empty 2019-05-24 09:41:31 +02:00
Saúl Ibarra Corretgé
cb3419ba2a android: enter PiP mode when pressing back button
When in a conference, try to enter PiP when pressing the back button. If this is
not possible (because it's unsupported, not enabled, etc.) fall back to the
previous behavior of simply hanging up.
2019-05-23 16:00:12 +02:00
Saúl Ibarra Corretgé
a2f8e156da app: avoid loading config when going back to the welcome page 2019-05-23 15:16:31 +02:00
Saúl Ibarra Corretgé
a4cf79c161 rn: fix losing audio if call is hangup too quickly
This PR changes the logic for connecting / disconnecting conferences. Instead of
doing it in mount / unmount events from the Conference component, it moves the
logic to the appNavigatee action.

This fixes a regression introduced in 774c5ecd when trying to make sure the
conference terminated event is always sent.

By moving the logic to appNavigate we no longer depend on side-effects for
connecting / disconnecting, and the code should be more maintainable moving
forward.

An improvement to this is the concept of sessions, which, while not tackled
here, was taken into consideration.
2019-05-23 15:16:31 +02:00
Saúl Ibarra Corretgé
9352517705 ios: always log delegate method calls 2019-05-23 15:16:31 +02:00
Saúl Ibarra Corretgé
47d5163c52 rn: don't tag builds by default
People run these in their own checkout and will run into problems because
tagging will fail.
2019-05-23 12:07:04 +02:00
Bettenbuk Zoltan
def22b01bb fix: set explicit color for search field to avoid theme override 2019-05-23 12:06:50 +02:00
Saúl Ibarra Corretgé
9445cf99fd Revert "ios: remove no longer needed code"
This reverts commit 603d161788.
2019-05-22 18:10:35 +02:00
paweldomas
96b226de24 watchos: change the icons
Inverts the icons to follow more what's in the phone app instead of
CallKit.
2019-05-22 17:25:35 +02:00
Bettenbuk Zoltan
5101f69e4e feat: don’t render moderator icon if everyone is moderator 2019-05-22 14:08:52 +02:00
François Benaiteau
61b66e0edf doc: fix incorrect code examples for universal / deep linking 2019-05-22 14:08:37 +02:00
Bettenbuk Zoltan
700051f809 fix: device selection colour scheme support 2019-05-22 12:29:28 +02:00
Дамян Минков
d16e10baec room-lock: adds ability to allow only digits for room locking 2019-05-22 09:43:17 +02:00
ibauersachs
11e5c14f83 Commit from translate.jitsi.org by user ibauersachs.: 317 of 625 strings translated (31 fuzzy). 2019-05-21 17:02:23 +00:00
ibauersachs
ded58d77d1 Commit from translate.jitsi.org by user ibauersachs.: 319 of 613 strings translated (26 fuzzy). 2019-05-21 16:58:20 +00:00
ibauersachs
8642c372c4 Commit from translate.jitsi.org by user ibauersachs.: 323 of 583 strings translated (4 fuzzy). 2019-05-21 16:56:52 +00:00
ibauersachs
edbf591059 Commit from translate.jitsi.org by user ibauersachs.: 430 of 583 strings translated (18 fuzzy). 2019-05-21 16:55:09 +00:00
jitsi-pootle
ded4291d6a New files added from translate.jitsi.org based on templates 2019-05-21 14:09:04 +00:00
Дамян Минков
a14fead0f3 Groups devices notifications by type audio/video. (#4238)
* Groups devices notifications by type audio/video.

* Fixes passing correct device array.
2019-05-20 21:35:42 +01:00
Saúl Ibarra Corretgé
466e1e3eb8 android: fix publishing new async storage package
The naming didn't match, so adjust it. @ cannot be used for maven artifact
names.
2019-05-20 17:33:36 +02:00
Saúl Ibarra Corretgé
8a90f0dab1 android: include SDK version in Maven repo commit message 2019-05-20 17:33:17 +02:00
Leonard Kim
d7e0aa3f61 fix(api): enable the external api before the first redux update
For the external api to fire update events out of the iframe, it
must first be initialized within the jitsi app. Any invocations
by the app to send updates events before initialization will
cause the api to swallow the events. The chosen fix is to
initialize the api earlier so the first update of app's redux
store fires update events that the api will also fire out of
the iframe.

This change will affect current behavior in that right now
the update event of the initial set of the avatar url is
blocked, but the change will make that event fire out of the
iframe.
2019-05-20 11:56:08 +02:00
Leonard Kim
37b343a797 feat(api): add ability to toggle tile view 2019-05-20 02:53:16 -07:00
Leonard Kim
149485905c fix(api): store passed in devices as user selected
Currently devices set through the api are stored
as ids, and not user selected. This can cause
other existing user selected devices to take
precedence over the devices passed into the api.
2019-05-17 10:47:31 +01:00
Aaron van Meerten
7f1df5629e Merge pull request #4229 from jitsi/poltergeist-prefix-support
updates bosh to support optional prefix
2019-05-16 15:40:28 -06:00
Leonard Kim
f42d0411b1 feat(screenshare): enable auto-pin of latest and last screenshare 2019-05-16 14:19:34 -07:00
Aaron van Meerten
8d1d573266 updates bosh to support optional prefix
use optional prefix in poltergeist room lookup
2019-05-16 14:23:36 -05:00
Leonard Kim
d86b60ea72 fix(chat): maintain bottom scroll on input resize 2019-05-15 08:06:35 -07:00
Leonard Kim
dfe5fbb702 ref(chat): change initial input size to 1 line 2019-05-15 08:06:35 -07:00
Leonard Kim
09f881c0f5 ref(chat): bring in package for text area auto-resizing 2019-05-15 08:06:35 -07:00
Leonard Kim
f1546008f9 ref(chat): removed unused getChatInputRef callback for input 2019-05-15 08:06:35 -07:00
Leonard Kim
d8df7fde84 ref(chat): clean up public blur/focus methods on input
Method blur is not called. Method blur is called
internally only.
2019-05-15 08:06:35 -07:00
Saúl Ibarra Corretgé
1c809eb428 ios: strip bitcode when releasing the SDK 2019-05-15 14:07:25 +02:00
Saúl Ibarra Corretgé
e94edcd4ae ios: automagically download a bitcode WebRTC build if needed 2019-05-15 09:54:17 +02:00
paweldomas
b48651396f fix(travis): upload through ssh proxy 2019-05-14 19:37:37 -05:00
Leonard Kim
e2044074ad Revert "fix(welcome-page): remove watermark container to avoid z-index wars"
This reverts commit 890151fa72.
2019-05-14 12:42:54 -07:00
Saúl Ibarra Corretgé
f060ac9db1 ios: notify RTCAudioSession about CallKit AVAudioSession activation 2019-05-14 21:09:39 +02:00
Leonard Kim
5a53d7f32a fix(chat): re-fix letting long messages wrap 2019-05-14 09:20:25 -07:00
Leonard Kim
4eec13da1c ref(chat): de-parameterize AbstractMessageContainer 2019-05-14 09:20:25 -07:00
Leonard Kim
cb8282dfe5 ref(chat): remove unused method 2019-05-14 09:20:25 -07:00
Leonard Kim
5cd0b1a9be fix(chat): fix auto-scrolling to bottom
Empower the parent.
2019-05-14 09:20:25 -07:00
Leonard Kim
504fadaf71 ref(chat): on web, move timestamp to chat message 2019-05-14 09:20:25 -07:00
Leonard Kim
7187e540a8 ref(chat): on native, show messages as grouped by sender 2019-05-14 09:20:25 -07:00
Leonard Kim
34dffbfc5e ref(chat): on native, group messages by sender (no styling) 2019-05-14 09:20:25 -07:00
Leonard Kim
a9637f93c3 ref(chat): create AbstractMessageContainer
So mobile and web can share logic for grouping chat
messages by sender.
2019-05-14 09:20:25 -07:00
Leonard Kim
0e8b0a9c5c ref(chat): create web MessageContainer component 2019-05-14 09:20:25 -07:00
Saúl Ibarra Corretgé
e66b596a0d ios: add ability to override SDK version when releasing 2019-05-14 17:00:02 +02:00
Saúl Ibarra Corretgé
6f320f463d rn: don't use annotated tags when building the SDKs 2019-05-14 17:00:02 +02:00
Saúl Ibarra Corretgé
02955ab57c deps: react-native@0.59.8
https://github.com/react-native-community/releases/blob/master/CHANGELOG.md#v0598
2019-05-14 10:22:50 +02:00
Leonard Kim
a9d76a2577 fix(large-video): vertically align center screenshare
Stop using special case logic for aligning screenshare videos.
It may be possible to have positioning all done using CSS but that
seems to be a more significant refactoring.
2019-05-10 08:09:56 -07:00
Bettenbuk Zoltan
dcf31baf3a doc: update google auth doc 2019-05-10 13:22:05 +02:00
Bettenbuk Zoltan
1e346f10ab rn: fix streaming key input color 2019-05-10 13:22:05 +02:00
Leonard Kim
a114d55fac fix(chat): ensure really long words can trigger wrapping 2019-05-09 08:25:47 -07:00
Leonard Kim
afde717ca4 ref(chat): use message type as classname 2019-05-09 07:06:27 -07:00
Leonard Kim
fb5a45f714 feat(chat): on web, group messages by sender 2019-05-09 07:06:27 -07:00
Дамян Минков
f5ac18da18 Add option to allow guest(moderators) to add a room password 2019-05-09 13:30:38 +02:00
Saúl Ibarra Corretgé
103ae363f6 ios: fix CallKit crash in development mode
It's possible a CallKit event arrives when the React Bridge has been torn down
and there is an assert that checks this. In order to avoid a crash, just skip
the event.
2019-05-09 13:22:58 +02:00
damencho
9bde673397 Updates copy info with parltcipant name info if available. 2019-05-09 10:33:55 +01:00
paweldomas
ff6b27eafa fix(travis): add watch dev prov profile 2019-05-08 19:01:28 -05:00
paweldomas
8cb19ccbf6 fix(travis): download WebRTC bitcode 2019-05-08 19:01:28 -05:00
damencho
198eba3682 Does not play sound notifications on the recording side. 2019-05-08 10:27:05 -07:00
Leonard Kim
a49f62238b ref(chat): clean up extra dom 2019-05-08 08:57:00 -07:00
Leonard Kim
a8233bdb84 ref(chat): move some colors to css variables 2019-05-08 08:57:00 -07:00
Leonard Kim
ec2826e0fc ref(chat): make wider 2019-05-08 08:57:00 -07:00
Leonard Kim
3d9606f6da ref(chat): use somewhat transparent background 2019-05-08 08:57:00 -07:00
Leonard Kim
01458eeff9 ref(chat): add a header for holding the close button 2019-05-08 08:57:00 -07:00
Leonard Kim
0318568a30 ref(chat): add light top border on input for visual separation 2019-05-08 08:57:00 -07:00
Leonard Kim
4d04141f24 ref(chat): change input placeholder 2019-05-08 08:57:00 -07:00
Leonard Kim
afbc622fb9 ref(chat): differentiate local and remote messages with background color 2019-05-08 08:57:00 -07:00
Leonard Kim
fbc7f865ec ref(chat): change chat bubble borders based on sender
For remote chat messages, all corners should be rounded
except the top left. For local messages all corners
should be rounded except the top right.
2019-05-08 08:57:00 -07:00
Leonard Kim
2a4bac7a27 ref(chat): remove chat bubble arrow 2019-05-08 08:57:00 -07:00
Saúl Ibarra Corretgé
b45a5da6e2 rn: use new AsyncStorage package
It was extracted from the RN core to a community maintained package.
2019-05-08 16:03:09 +02:00
Saúl Ibarra Corretgé
2fad9f9ba8 ios: update Podfile.lock 2019-05-08 12:12:54 +01:00
Saúl Ibarra Corretgé
8b0e5b9d15 android: set system navbar color to match the header 2019-05-08 12:44:17 +02:00
paweldomas
0889ffdf27 android fix: do not enter PiP mode when the permissions alert is shown
Entering PiP mode while the permissions dialog is display will not only
fail, but also mess up the Activity lifecycle on some OS versions.
We may end up with two activity/fragment instances and a situation where
the onStop callback was not called yet on the instance #1 while
the onResume has been already called on instance #2.
2019-05-08 10:15:14 +02:00
Bettenbuk Zoltan
86d0d4fc22 rn: add DialInSummary 2019-05-07 18:02:14 +02:00
Bettenbuk Zoltan
7e9df74e60 rn: add generic alert dialog 2019-05-07 18:02:14 +02:00
Bettenbuk Zoltan
3eca67e1ad rn: add HeaderWithNavigation component 2019-05-07 18:02:14 +02:00
Дамян Минков
c040b3a7dd Fall back to using label for preferred devices (#4171)
* Skips setting undefined device id to sink in audio preview.

* Fallbacks to use labels for user selected devices.

* Fixes comment.
2019-05-07 09:53:01 +01:00
174 changed files with 6970 additions and 3148 deletions

View File

@@ -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>

View File

@@ -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!

View File

@@ -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
}

View File

@@ -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.
*

View File

@@ -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

View File

@@ -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));

View File

@@ -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();

View File

@@ -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() {

View File

@@ -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')

View File

@@ -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);
},
/**

View File

@@ -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

View File

@@ -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;
}
}

View File

@@ -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;

View File

@@ -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 {

View File

@@ -162,4 +162,10 @@ body.welcome-page {
font-size: 32px;
}
}
.welcome-watermark {
position: absolute;
width: 100%;
height: 100%;
}
}

View File

@@ -6,7 +6,7 @@
/**
* Let the avatar grow with the tile.
*/
.userAvatar {
.avatar-container {
max-height: initial;
max-width: initial;
}

View File

@@ -185,6 +185,7 @@
font-size: 12px;
max-height: 100%;
overflow: auto;
padding: 15pt;
position: absolute;
transform: translateY(-50%);
top: 50%;

View File

@@ -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

View File

@@ -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

View File

@@ -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|

View File

@@ -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

View File

@@ -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];
}

View File

@@ -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",

View File

@@ -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

View File

@@ -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>

View File

@@ -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
View 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

View File

@@ -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

View File

@@ -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";

View File

@@ -14,6 +14,8 @@
* limitations under the License.
*/
#import <React/RCTBridge.h>
#import "JitsiMeet.h"
@interface JitsiMeet ()

View File

@@ -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;

View File

@@ -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];

View File

@@ -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
View 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
View 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

View File

@@ -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];

View File

@@ -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

View File

@@ -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()

View File

@@ -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
}
}

View File

@@ -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

View File

@@ -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": "Китайски (Китай)"
}

View File

@@ -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
View 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": ""
}

View File

@@ -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)"
}

View File

@@ -1,5 +1,6 @@
{
"en": "Inglês",
"af": "Africâner",
"az": "Azerbaijanês",
"bg": "Búlgaro",
"cs": "Checo",

View File

@@ -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)"
}

File diff suppressed because it is too large Load Diff

View File

@@ -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
View 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": ""
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -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",

View File

@@ -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.
*

View File

@@ -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'
};
/**

View File

@@ -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.

View File

@@ -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());

View File

@@ -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;

View File

@@ -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();
};

View File

@@ -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;

View File

@@ -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);
}

View File

@@ -348,10 +348,6 @@ const VideoLayout = {
remoteVideo.removeRemoteStreamElement(stream);
}
if (stream.isVideoTrack()) {
this._updateLargeVideoIfDisplayed(id);
}
this.updateMutedForNoTracks(id, stream.getType());
},

View File

@@ -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
View File

@@ -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",

View File

@@ -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",

View File

@@ -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.

View 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';

View File

@@ -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);
}
}
/**

View File

@@ -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.
*

View File

@@ -2,3 +2,4 @@ export * from './AnalyticsEvents';
export * from './functions';
import './middleware';
import './reducer';

View File

@@ -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;
});

View 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;
}
});

View File

@@ -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());
}
};
}

View File

@@ -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';

View File

@@ -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);
});
}

View File

@@ -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 } />
);

View File

@@ -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
});
};
}

View File

@@ -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({

View File

@@ -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);
}
/**

View File

@@ -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);
}
/**

View File

@@ -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',

View File

@@ -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;

View File

@@ -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.

View File

@@ -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.

View File

@@ -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));

View File

@@ -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;
}

View 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));

View File

@@ -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;
}
}
/**

View File

@@ -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
});

View File

@@ -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';

View File

@@ -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')
},
/**

View 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();
}

View File

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

View 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;
}

View File

@@ -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;
}
/**

View File

@@ -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));
}
/**

View File

@@ -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));
}

View File

@@ -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