Compare commits

...

444 Commits

Author SHA1 Message Date
Saúl Ibarra Corretgé
384f7d7890 ios: update Podfile.lock 2019-04-02 16:18:09 +02:00
Bettenbuk Zoltan
4d9dcf5d43 [RN] Add InfoDialogButton 2019-04-02 16:18:09 +02:00
Saúl Ibarra Corretgé
e217e10af5 android: update custom version for native dependencies 2019-04-02 14:17:45 +02:00
Saúl Ibarra Corretgé
7feec7c11d deps: react-native-sound€0.10.12 2019-04-02 14:17:45 +02:00
Saúl Ibarra Corretgé
36eb27e233 rn: add build information to SettingsView 2019-04-02 12:40:35 +02:00
Bettenbuk Zoltan
b791fc32fd [RN] Make BaseIndicator render simpler 2019-04-01 21:03:36 +02:00
Bettenbuk Zoltan
b1a70240fc Clear raise hand status on confidence leave 2019-04-01 21:03:36 +02:00
Bettenbuk Zoltan
50d7c1521f Remove legacy web raise hand code 2019-04-01 21:03:36 +02:00
Bettenbuk Zoltan
5d9762b429 Extract notification timeout to a constant 2019-04-01 21:03:36 +02:00
Bettenbuk Zoltan
4e78502c9e Generalize indicators 2019-04-01 21:03:36 +02:00
Bettenbuk Zoltan
6ff733dae0 Platform generic notification for raised hand 2019-04-01 21:03:36 +02:00
Bettenbuk Zoltan
2dc59b9ea0 [RN] Add button to toggle raised hand 2019-04-01 21:03:36 +02:00
Bettenbuk Zoltan
e65918564b [RN] Add UI for raised hand feature 2019-04-01 21:03:36 +02:00
Bettenbuk Zoltan
ce9744b9c3 Move participant event handler to a platform generic location 2019-04-01 21:03:36 +02:00
damencho
b413457a4f Commit from translate.jitsi.org by user damencho.: 262 of 588 strings translated (0 fuzzy). 2019-04-01 12:41:41 +00:00
damencho
75daedf9ab Commit from translate.jitsi.org by user damencho.: 406 of 583 strings translated (30 fuzzy). 2019-04-01 12:40:08 +00:00
damencho
45eeea447a Commit from translate.jitsi.org by user damencho.: 583 of 583 strings translated (0 fuzzy). 2019-04-01 12:40:00 +00:00
damencho
16fcc55ad1 Commit from translate.jitsi.org by user damencho.: 583 of 583 strings translated (0 fuzzy). 2019-04-01 12:39:52 +00:00
damencho
a70009e486 Commit from translate.jitsi.org by user damencho.: 583 of 583 strings translated (0 fuzzy). 2019-04-01 12:39:41 +00:00
damencho
06502e5aac Commit from translate.jitsi.org by user damencho.: 423 of 583 strings translated (24 fuzzy). 2019-04-01 12:39:33 +00:00
damencho
6316447d4b Commit from translate.jitsi.org by user damencho.: 504 of 583 strings translated (0 fuzzy). 2019-04-01 12:39:06 +00:00
jitsi-pootle
df5fa71b92 New files added from translate.jitsi.org based on templates 2019-04-01 12:39:06 +00:00
Bettenbuk Zoltan
10e951c17c Reorg notifications feature files 2019-03-29 18:52:44 +01:00
Hristo Terezov
f12317dc59 docs(api.md): Comply with our coding style. 2019-03-29 16:39:21 +00:00
Hristo Terezov
829e5597d5 fix(iframe-api-devices): Misc small issues. 2019-03-29 15:42:02 +00:00
Hristo Terezov
f2e0704b93 fix(filmstrip-only): DeviceSelectionPopup 2019-03-29 15:42:02 +00:00
Hristo Terezov
a7aaf31c79 feat(iframe-api): Add deviceListChanged event. 2019-03-29 15:42:02 +00:00
Hristo Terezov
4967488e56 ref(iframe-api-devices): Use labels instead of IDs 2019-03-29 15:42:02 +00:00
Hristo Terezov
ed1d3d3df5 fix(api-devices): Initial device function calls 2019-03-29 15:42:02 +00:00
Hristo Terezov
427f49367b feat(iframe-api): Device handling. 2019-03-29 15:42:02 +00:00
Saúl Ibarra Corretgé
659e420005 ios: make sure Fastlane can update the provisioning profile
https://docs.fastlane.tools/codesigning/xcode-project/#xcode-9-and-up
2019-03-29 14:50:59 +01:00
Saúl Ibarra Corretgé
9a46606f0d misc: s/Atlassian/8x8/ 2019-03-29 13:11:36 +01:00
Leonard Kim
3b911b0362 chore(deps): bump lib-jitsi-meet for permissions api fixes 2019-03-28 08:47:42 -05:00
Philip-Choi
7ae8ae5791 doc: update README 2019-03-28 09:46:03 +01:00
paweldomas
a386740103 fix(ConnectionService): history and audio focus on Samsung devices
On some Samsung devices the call done with the ConnectionService end up
in the native call history which we don't want. That's fixable by
marking the Connection as "external" just before the call is
disconnected.

Another issue specific to Samsung devices about the audio focus not
always being release when that call ends. That's fixable by marking
the call as holding just before disconnecting it.
2019-03-27 13:39:52 -05:00
Saúl Ibarra Corretgé
451949f49d android: enable PiP support by default in JitsiMeetActivity 2019-03-27 17:39:09 +01:00
Saúl Ibarra Corretgé
61ed459971 android: add JitsiMeetActivity.launch helper methods
They greatly simplify starting a JitsiMeetActivity by encapsulating the creation
of the Intent adn extras placement.

In order to make this possible JitsiMeetConferenceOptions now implements
Parcelable so it can be serialized and passed around when creating an Intent.
2019-03-27 17:39:09 +01:00
Saúl Ibarra Corretgé
c30a4a0aa6 android: make Amplitude and Dropbox modules package private 2019-03-27 17:39:09 +01:00
paweldomas
e839684ae9 fix(base/conference): tracks not added to the conference
If tracks are created while the conference is in the 'joining' state
they will never be added.
2019-03-27 10:06:59 +01:00
Saúl Ibarra Corretgé
15c5a2339b android: fix getting permission request results
Now that we have both a Fragment and an Activity there are lifecycle methods
that overlap. If a Fragment requests permission by calling requestPermissions
then the result handler will be called on itself. React Native's permissions
module, however, calls ActivityCompat.requestPermissions on the Activity, thus
we need to handle the results at the Activity level and not at the Fragment
level.
2019-03-26 11:56:14 -05:00
virtuacoplenny
d7e112aaf0 fix(display-name): do not default name to placeholder name (#4027)
* ref(display-name): do not pass in display name

The component gets the state itself from redux.

* fix(display-name): do not default name to placeholder name

The web display name component supports inline editing of
the name. Problems can occur when the displayed name
differs from the actual saved name, because participants
without a display name, including the local user, have
a different, default display name displayed. So when
editing starts, the input field is populated with the
default name. To workaround such while supporting fetching
the display name using mapStateToProps, pass in both the
name which should be shown and the name value saved in
settings.

* ref(display-name): rename methods
2019-03-26 09:34:02 -07:00
Saúl Ibarra Corretgé
24339b2461 ios: update Podfile.lock 2019-03-26 13:47:57 +01:00
Bettenbuk Zoltan
5b02c575f7 eslint-config-jitsi#1.0.0 2019-03-26 13:35:02 +01:00
Mayur Shah
1f8904a95b deps: react-native@0.59.2 2019-03-26 10:53:50 +01:00
Saúl Ibarra Corretgé
50268a08a0 ios: ensure the git tree is clean when / after building 2019-03-25 19:08:12 +01:00
Saúl Ibarra Corretgé
985385f364 ios: update fastlane Fastfile 2019-03-25 19:08:12 +01:00
Saúl Ibarra Corretgé
f662edd135 ios: don't bundle a dummy GoogleService-Info.plist file
It complicates automated builds.
2019-03-25 19:08:12 +01:00
Saúl Ibarra Corretgé
7ebcf69937 android: add fastlane integration 2019-03-25 19:08:12 +01:00
Saúl Ibarra Corretgé
40364ae269 misc: update gitignore 2019-03-25 19:08:12 +01:00
Saúl Ibarra Corretgé
030af37668 android: generate versionCode automatically
It's a number whichb must be ever increasing with each build submitted to the
store.

Automate its value by using the number of seconds since 1st of January 2019.
That should be enough for ~680 years.
2019-03-25 19:08:12 +01:00
Saúl Ibarra Corretgé
e29bc4bbb6 deps: update react-native-callstats to version 3.58.2 2019-03-25 14:56:52 +01:00
Bettenbuk Zoltan
13212a5980 [RN] Replace chat modal with SlidingView 2019-03-25 14:52:43 +01:00
Bettenbuk Zoltan
2a5adfc601 Remove some Flow annotations 2019-03-25 13:53:08 +01:00
paweldomas
19e8e8710a fix(Android/ConnectionService): do not display the address
Turns out that on Samsung phones the calls placed with
the ConnectionService appear in the calls log as weird long numbers.
The system mangles the address we give it ("sip:meet.jit.si/something")
into this weird long number and the call to request.getAddress() returns
that. Turn off the presentation as neither this number nor our address
makes sense. This way the call appears as from "Unknown" caller in call
history which is still not perfect, but better than the random number.

Note that other phones will preserve the originally passed address value
(tested on One Plus 5).
2019-03-25 09:24:33 +01:00
paweldomas
3b24124d57 fix(Android/ConnectionService): mic not working
Turns out the microphone will not work on some devices when starting in
"audio only", because the audio mode is not set to the MODE_IN_COMMUNICATION,
but to the MODE_IN_CALL. Calling setAudioModeIsVoip(true) makes
the system adjust to MODE_IN_COMMUNICATION and the mic works fine.
2019-03-25 09:24:33 +01:00
damencho
6894de00cc Fixes a typo of getting default number. 2019-03-23 09:59:25 +00:00
paweldomas
6d0b6bee85 ref(AudioModeModule): check 1 method to enable ConnectionService 2019-03-22 09:17:14 +01:00
virtuacoplenny
ac02a17943 feat(notifications): provide a way to turn off sticky notifications (#4010) 2019-03-21 14:06:33 -07:00
paweldomas
043d4db314 fix(NotificationsContainer.native): flow error 2019-03-21 17:09:56 +01:00
Saúl Ibarra Corretgé
6a919916d3 ios: pin all pod dependencies 2019-03-21 16:57:58 +01:00
Saúl Ibarra Corretgé
1de4897a6b ios: xcode project change shenanigans
Signing wouldn't work, disabling it and enabling it again created these changes.
Oh well!
2019-03-21 16:29:18 +01:00
paweldomas
0175690a2b ref(mobile): display only the topmost notification
Only one notification will be displayed at a time on mobile.
2019-03-21 15:47:14 +01:00
paweldomas
c7979a3944 feat(mobile): add 1 liner notifications
Adds 1 liner notifications to mobile. Only the title is displayed. In
case the title is missing there's a fallback to the description.
2019-03-21 15:47:14 +01:00
paweldomas
15fd27543a ref(Conference): extract AbstractConference 2019-03-21 15:47:14 +01:00
paweldomas
64f8a8d700 fix(AbstractNotificationsContainer): broken timeouts chain
If user dismisses the not topmost notification the timeout will be
cleared and a new one will not be set, because the top notification
remained the same (see the if at line 90).
2019-03-21 15:47:14 +01:00
paweldomas
95f684da2f fix(AbstractNotificationsContainer): dismiss timeout not always set
The docs of 'componentDidUpdate' say that it's not called for the
initial render. If the component is added to the DOM with 1 notification
already, then the update will not happen and timeout will never be set
which will effectively break the timeouts chain.
2019-03-21 15:47:14 +01:00
paweldomas
f3f8dc2072 ref: move participant joined notification to the middleware 2019-03-21 15:47:14 +01:00
Saúl Ibarra Corretgé
08efc46f21 android: fix crash in debug mode 2019-03-21 14:47:55 +01:00
Saúl Ibarra Corretgé
e32336b96f android: run the React packager when running from AS
When running the app from Android Studio the React packager is not automatically
started. In vanilla RN projects this is done by the "react-native run-android"
command, but often times it is desired to run from Android Studio.

This fixes that by starting the packager from Gradle.
2019-03-21 14:47:55 +01:00
Saúl Ibarra Corretgé
c91880859b android: fix gradle warning 2019-03-21 14:47:55 +01:00
Saúl Ibarra Corretgé
cff78d7a83 android: disable delta updates
It's enabled by default, but marked as experimental (uh?!). It creates trouble
as sometimes the packager goes bananas. Disable them until further notice, our
bundle is not that large anyway.
2019-03-21 14:47:55 +01:00
Saúl Ibarra Corretgé
49a0c03ff0 ios: fix deprecation warning
NSURLConnection sendSynchronousRequest is deprecated since iOS 9. Replace the
method by whjat's currently on RN master, which implements a modern alternative.
2019-03-21 14:47:55 +01:00
Saúl Ibarra Corretgé
2c592f61c3 android: enable 64bit builds 2019-03-21 14:47:55 +01:00
Saúl Ibarra Corretgé
c025c7e132 flow: tame the beast
🔥🔥🔥
2019-03-21 14:47:55 +01:00
Saúl Ibarra Corretgé
278d3a163b flow: update type definitions 2019-03-21 14:47:55 +01:00
Saúl Ibarra Corretgé
0e92e73789 chore: use strings as action types
Using anything non-serializable for action types is discouraged:
https://redux.js.org/faq/actions#actions

In fact, this is the Flow definition for dispatching actions:

declare export type DispatchAPI<A> = (action: A) => A;
declare export type Dispatch<A: { type: $Subtype<string> }> = DispatchAPI<A>;

Note how the `type` field is defined as a subtype of string, which Symbol isn’t.
2019-03-21 14:47:55 +01:00
Saúl Ibarra Corretgé
75527e01dd deps: update lib-jitsi-meet 2019-03-21 14:47:55 +01:00
Saúl Ibarra Corretgé
b53a034aaf deps: update React Native to version 0.59
This new version comes with an updated JSC runtime, so we no longer need to
depend on the updated version ourselves.
2019-03-21 14:47:55 +01:00
paweldomas
4d1c0cf219 fix eslint error 2019-03-19 21:59:45 +01:00
virtuacoplenny
a667c9bff2 ref: js-utils for random and room name generator (#3975) 2019-03-19 10:35:36 -07:00
Damien Fetis
e652117571 Remove the boolean conversion of authLogin string to display the string value instead of 'true'. (#3995) 2019-03-19 08:55:14 -07:00
Hristo Terezov
a6719896a2 fix(recording): Respect the selected recording service. 2019-03-15 20:12:25 +00:00
damencho
f5a7e0bccb Adds provider name to fix welcomepage text.
* Removes unused ADD_PEOPLE_APP_NAME
* Moves deep-link header background and logo size as variables.
* Fixes more numbers page space in the header.
* Fixes left padding on deep-linking mobile page.
2019-03-15 11:24:18 +00:00
damencho
f94db0da2c Adjust some paths to respect base. 2019-03-14 11:22:37 +00:00
paweldomas
460593a93e chrome: bump LJM to 74f48e168eec4c05fd8600812cc00e6e34e9ab90
Required for the Spot's TURN changes to work
2019-03-13 14:25:19 -05:00
paweldomas
19a27e75bd feat(spot): pass JitsiConnection to ProxyConnectionService to get TURN 2019-03-13 14:25:19 -05:00
Hristo Terezov
cb8e9eed5e feat(subject): UI 2019-03-12 23:03:58 +00:00
Saúl Ibarra Corretgé
2715e81f1d rn: add more SDK documentation 2019-03-12 16:55:28 +01:00
Saúl Ibarra Corretgé
f941f15def ios: remove Jitsi Meet specific defaults
The app should always provide them.
2019-03-12 16:55:28 +01:00
Saúl Ibarra Corretgé
7712c6913c android: make ReactInstanceManagerHolder.emitEvent return void 2019-03-12 16:55:28 +01:00
Saúl Ibarra Corretgé
45b6a8b5d5 android: throw if a unsupported type makes it to the props Bundle 2019-03-12 16:55:28 +01:00
Saúl Ibarra Corretgé
26ca0e6630 android: throw if the overlsay permission is not granted in Debug mode 2019-03-12 16:55:28 +01:00
Saúl Ibarra Corretgé
7978f9f5f4 misc: update .gitignore 2019-03-12 16:55:28 +01:00
Saúl Ibarra Corretgé
d39290f9fa rn: refactor conference events
Consolidate all failure cases into a single one: CONFERENCE_TERMINATED. If the
conference ended gracefully no error indicator will be present, otherwise there
will be.
2019-03-12 16:55:28 +01:00
Saúl Ibarra Corretgé
f696a6dbe2 ios: update SDK documentation 2019-03-12 16:55:28 +01:00
Saúl Ibarra Corretgé
bf3bcd65d6 android: add JitsiMeetActivity
It renders a single JitsiMeetFragment which holds the JitsiMeetView view.
2019-03-12 16:55:28 +01:00
Saúl Ibarra Corretgé
a7018970ca android: update SDK documentation 2019-03-12 16:55:28 +01:00
Saúl Ibarra Corretgé
20edb7c279 android: make onExternalAPIEvent protected 2019-03-12 16:55:28 +01:00
Saúl Ibarra Corretgé
59b00d022b android: don't proxy enterPictureInPicture in JitsiMeetFragment 2019-03-12 16:55:28 +01:00
Saúl Ibarra Corretgé
53722fd2e6 android: remove ReactContextUtils
In practice, we are never going to be in a position where we don't have a
ReactContext but we do have some React Native code running. So let's not expect
the impossible.
2019-03-12 16:55:28 +01:00
Saúl Ibarra Corretgé
54bab793e5 android: remove JitsiMeetViewAdapter
It was never used and typicallt the Activity / Fragment holding the
JitsiMeetView object will be the listener.

In addition, once we refactor the events they will be reduced into far fewer.
2019-03-12 16:55:28 +01:00
Saúl Ibarra Corretgé
975ff9c83d rn: support passing serverURL and room to URL object
That's what the SDK passes now, if the room URL is not absolute.
2019-03-12 16:55:28 +01:00
paweldomas
5b3e8a9b5e android: introduce JitsiMeetConferenceOptions
Co-authored-by: Saúl Ibarra Corretgé <saghul@jitsi.org>
2019-03-12 16:55:28 +01:00
Saúl Ibarra Corretgé
aedcfba263 ios: introduce JitsiMeetConferenceOptions 2019-03-12 16:55:28 +01:00
Saúl Ibarra Corretgé
b97cb3509a rn: document externalAPIScope prop 2019-03-12 16:55:28 +01:00
Saúl Ibarra Corretgé
7d8ea85ea0 rn: start removing defaultURL 2019-03-12 16:55:28 +01:00
Saúl Ibarra Corretgé
e4c3e15791 rn: simplified code
There is no need for AbstractApp to require some getWindowLocation function.
It's only used in one place and we even polyfill it on mobile.

Thus replace it's usage with more specific functions.
2019-03-12 16:55:28 +01:00
Saúl Ibarra Corretgé
56135bd085 android: add initial implementation of join / leave 2019-03-12 16:55:28 +01:00
Saúl Ibarra Corretgé
549b495d16 ios: add initial implementation of join / leave 2019-03-12 16:55:28 +01:00
Saúl Ibarra Corretgé
f3abca6462 ios: add ability to control deep / universal linking
Since the SDK may be embedded with other apps, we need to recognize our custom
URL scheme and universal links in order to tell the user if we will process the
request or not.

Make them configurable with sane defaults.
2019-03-12 16:55:28 +01:00
Saúl Ibarra Corretgé
405905be82 rn: raise SDK version 2019-03-12 16:55:28 +01:00
Saúl Ibarra Corretgé
468b02b812 ios: adjust to latest Swift syntax 2019-03-12 16:55:28 +01:00
Saúl Ibarra Corretgé
3fa5aed950 rn: drop deep / universal links handling from JS
It's now all handled in the SDK an we'll get the new URLs via props.
2019-03-12 16:55:28 +01:00
Saúl Ibarra Corretgé
90803c8ff6 android: SDK v2 pass one
Add JitsiMeetFragment and refactor the app to use it.
2019-03-12 16:55:28 +01:00
Saúl Ibarra Corretgé
dbc88b972e ios: SDK v2 pass one
- cleanup
- API simplification (single loadURL method)
- JitsiMeet singleton for linking helpers and globals
- Linking moved to app
2019-03-12 16:55:28 +01:00
Saúl Ibarra Corretgé
1c47720a08 misc: ignore build artifacts 2019-03-12 16:55:28 +01:00
Hristo Terezov
59fc3642a6 feat(amplitude): add mobile implementation 2019-03-12 16:48:08 +01:00
Saúl Ibarra Corretgé
19b4b92150 deps: update lib-jitsi-meet 2019-03-12 16:48:08 +01:00
Bettenbuk Zoltan
8400d01d75 [RN] Add color scheme support to dialog buttons 2019-03-12 12:36:15 +01:00
Bettenbuk Zoltan
d04068344a [RN] Make header button same size as header label 2019-03-12 12:36:15 +01:00
Bettenbuk Zoltan
55a971c0fd [RN] Add color scheme support to header 2019-03-12 12:36:15 +01:00
Bettenbuk Zoltan
20c1b1cfae [RN] Wrap PagedList navigator with SafeAreaView 2019-03-12 12:36:15 +01:00
Bettenbuk Zoltan
ecb44b6ab4 [RN] Make the header more compact 2019-03-12 12:36:15 +01:00
Jose Angel Gonzalez
039805eba3 fix(android-sdk): Recover audio device if the OS changes it 2019-03-12 09:55:51 +01:00
virtuacoplenny
22277ad799 feat(api): add notification for when filmstrip gets toggled (#3974) 2019-03-11 11:17:28 -07:00
Hristo Terezov
2af1e8da95 fix(remote-video-menu): Icon position 2019-03-11 17:36:58 +00:00
Дамян Минков
12d0aef686 Updates recording dialog. (#3953)
* Updates recording dialog.

* Update config.js doc.

* Adds comment and make a check more intuitive.

* Changes of using enum for recording types.
2019-03-11 09:17:21 -07:00
Hristo Terezov
f439ad2999 feat(popover): Make the popover menus customizable. 2019-03-11 16:04:20 +00:00
Leonard Kim
81d4f694b7 fix(chat): prevent empty messages 2019-03-09 12:09:29 +01:00
Saúl Ibarra Corretgé
c737d46d90 android: update gradle plugin version 2019-03-08 17:24:49 +01:00
Saúl Ibarra Corretgé
3f2a559d64 rn: now working on version 19.1 2019-03-08 14:04:03 +01:00
Saúl Ibarra Corretgé
bdb3099073 android: fix running on Android < M
The android.telecom.CallAudioState class was only added in API level 23 (Android
M), so make sure we don't import it in lower versioned devices.
2019-03-08 11:37:25 +01:00
damencho
b3a05db286 Update dropbox redirect uri to always use the main domain static page. 2019-03-07 15:39:16 +00:00
virtuacoplenny
08f2edf350 feat(screenshare): emit source type when starting screenshare (#3959)
* feat(screenshare): emit source type when starting screenshare

* squash: update doc
2019-03-06 21:46:17 -08:00
Bettenbuk Zoltan
98c7430b6f [RN] Replace red screen with dialog 2019-03-07 01:09:03 +01:00
Bettenbuk Zoltan
ebdcbe122a Change dialog button property keys 2019-03-07 01:09:03 +01:00
virtuacoplenny
31c1034be7 deps(chore): bump lib-jitsi-meet to e398584 (#3958)
Bring over two fixes for spot. One is for
identifying the screenshare type when using
a camera for screenshare or when using a proxy
stream. Also bring in a fix to avoid a js error
in chrome ios.
2019-03-06 15:39:01 -08:00
Hristo Terezov
a9d82a79ea fix(toolbar): Move buttons to overflow menu when the space isn't enough 2019-03-06 17:51:31 +00:00
Bettenbuk Zoltan
27e1f5a1bc [RN] Avoid adding undefined in the invite 2019-03-06 16:52:22 +01:00
virtuacoplenny
dbedee5e22 chore(deps): update to lib 320919e (#3951) 2019-03-05 17:02:22 -08:00
Дамян Минков
636c63397b Adds integrations doc. (#3929)
* Adds integrations doc.

Google, Microsoft and Dropbox for now.

* Updates doc.
2019-02-28 09:09:09 +00:00
damencho
67e7994e36 Removes unused config. 2019-02-27 16:45:26 +00:00
damencho
40f03fedc2 Replaces emoji flags with flag from a png.
Seems like windows does not have emojis for flags.
2019-02-27 16:45:26 +00:00
Hristo Terezov
55149670da fix(dropbox-auth): In Electron. 2019-02-27 16:12:54 +00:00
Gabriel-Tiberiu Imre-Lucaci
5739e1deaa feat(external_api): notify when api is disposed 2019-02-27 14:39:04 +00:00
Bettenbuk Zoltan
b6e2701991 [RN] Add invite screen 2019-02-27 13:26:21 +01:00
Bettenbuk Zoltan
38b1be1291 [RN] Extract AvatarListItem 2019-02-27 13:26:21 +01:00
damencho
555f8b3a99 Fixes toll free position. 2019-02-26 17:08:56 +00:00
Дамян Минков
ea4d49f2a0 Adds new format of phoneList service and re-design dial in numbers page. (#3903)
* Adds new format of phoneList service and re-design dial in numbers page.

Adds flags and country names (with translations) for the numbers if using the new format.

* Fixes tests and fixes get default number.

* Updates swagger with new format.

* Moves html back yo table.

Fixes displaying on mobile and also the tel: URI generation. The tel: URI is tested on Android and iOS and seems to work (Android was not interpreting 'p', but both seems to like ',').

* Fixes a wrong return statement.

* Small fixes.
2019-02-26 13:32:46 +00:00
fossterer
d7eea8abbc Added links related to NAT config in a new FAQ doc 2019-02-26 09:53:48 +00:00
paweldomas
1b8ef9a05a chore: update LJM to 5a9fc76739bcf0bed50676c7be160f688f3a19b5 2019-02-25 14:16:05 -06:00
Emil Ivov
ac7311cb52 Merge pull request #3920 from jitsi/emcho-patch-3
Update api.md
2019-02-23 17:04:13 +00:00
Emil Ivov
f498c8d402 Update api.md 2019-02-23 16:39:27 +00:00
Leonard Kim
8c8b09878c fix(screensharing): do not sync camera device id on start
When a conference is started, the currently used
camera device id is saved. I believe this is happening
because lib-jitsi-meet does not use exact device id
mathcing when calling getUserMedia, so it's possibl
to request camera A but get camera B back because
camera A is not available. When config.startScreenSharing
is true, the syncing occurs and saves the desktop
source id. So when screensharing is stopped, jitsi-meet
requests that desktop source id instead of the preferred
camera.
2019-02-22 10:50:43 -06:00
Leonard Kim
03f8d8b51a fix(external_api): detect and skip params for hash routers 2019-02-22 10:34:49 -06:00
Hristo Terezov
32083fc44d fix(connection-quality): Bring back the icon. 2019-02-21 18:49:49 +00:00
Hristo Terezov
c4361ed7da feat(css): configurable Videolayot and overflow menu. 2019-02-21 18:49:49 +00:00
Hristo Terezov
a95d38a0f4 feat(thumbnail-indicators): Configurable icon sizes. 2019-02-21 18:49:49 +00:00
Hristo Terezov
981600a999 fix(toolbar): button distance and colors. 2019-02-21 18:49:49 +00:00
Hristo Terezov
0d674001d2 chore(icons): Cleanup 2019-02-21 16:18:01 +00:00
Hristo Terezov
f7b930409b feat(toolbox): Redesign. 2019-02-21 00:42:13 +00:00
paweldomas
4312512d2f feat(external API): add feedbackPromptDisplayed event
Adds a new event fired when Jitsi Meet shows the feedback prompt.
2019-02-20 11:59:46 -06:00
Saúl Ibarra Corretgé
b7b2745dae ios: update podfile for CocoaPods 1.6.0 2019-02-19 19:13:07 +01:00
damencho
fc129d9849 Adds setting subject and adding event on receiving such change. 2019-02-19 13:22:27 +00:00
paweldomas
54c36198d0 fix(mobile/call-integration): cleanup if leave takes too long
The conference disconnection process is asynchronous which means there's
no guarantee that there will be CONFERENCE_LEFT event for the old
conference, before the next conference is joined. Because of that we can
end up with two simultaneous calls on the native side which is not
always supported. End the call on CONFERENCE_WILL_LEAVE to fix this
corner case.
2019-02-16 17:10:04 -08:00
virtuacoplenny
16b440bbd0 chore(deps): upgrade webpack-dev-server 3.1.10 to 3.1.14 (#3900) 2019-02-15 09:03:05 -08:00
Bettenbuk Zoltan
d56d01cebb [Android] Change the type of the color scheme 2019-02-15 13:33:38 +01:00
Leonard Kim
d872728966 fix(recording): support passing styles in firefox
Using an array of styles in Firefox causes an error
that triggers jitsi-meet to redirect to a static page.
2019-02-15 10:11:26 +01:00
Saúl Ibarra Corretgé
877cea59e7 ios: don't configure CXProvider if CallKit is disabled 2019-02-14 13:16:31 -08:00
Saúl Ibarra Corretgé
a4b3f8ade6 android: don't build javadocs when publishing react-native plugins
They generate a bunch of harmless yet confusing error messages and they are not
really useful.
2019-02-14 13:16:04 -08:00
damencho
2ac5d136dc Detects nginx-extras package. Fixes #3891. 2019-02-14 14:05:27 +00:00
Saúl Ibarra Corretgé
7e320a5d38 ios: refactor AudioMode to use RTCAudioSession
RTCAudioSession is a thin wrapper around AVAudioSession provided by the WebRTC
framework. It makes some use-cases easier, and leads us closer to manual audio
unit management, which we will likely need in the near future.
2019-02-14 10:20:45 +01:00
Saúl Ibarra Corretgé
7289e59ca9 audio-mode: don't change the mode unless there is no active conference
If a new conference is joined and as a result the current one is terminated,
don't ever attempt to set the audio mode.
2019-02-14 10:20:45 +01:00
Saúl Ibarra Corretgé
e1b989e99b ios: make sure our CXProvider is invalidated when CallKit is disabled 2019-02-14 10:20:45 +01:00
Saúl Ibarra Corretgé
070a34e30d ios: enable proximity sensor on the main thread
Fixes this issue:

Main Thread Checker: UI API called on a background thread: -[UIApplication setExpectsFaceContact:inLandscape:]
PID: 25442, TID: 10886619, Thread name: (none), Queue name: com.facebook.react.ProximityQueue, QoS: 0
Backtrace:
4   JitsiMeet                           0x000000010a0eaadc -[Proximity setEnabled:] + 64
5   CoreFoundation                      0x00000001fea34630 <redacted> + 144
6   CoreFoundation                      0x00000001fe912450 <redacted> + 292
7   CoreFoundation                      0x00000001fe913034 <redacted> + 60
8   JitsiMeet                           0x000000010a4e08e8 -[RCTModuleMethod invokeWithBridge:module:arguments:] + 492
9   JitsiMeet                           0x000000010a4e7a10 _ZN8facebook5reactL11invokeInnerEP9RCTBridgeP13RCTModuleDatajRKN5folly7dynamicE + 248
10  JitsiMeet                           0x000000010a4e776c ___ZN8facebook5react15RCTNativeModule6invokeEjON5folly7dynamicEi_block_invoke + 88
11  libdispatch.dylib                   0x000000010b943824 _dispatch_call_block_and_release + 24
12  libdispatch.dylib                   0x000000010b944dc8 _dispatch_client_callout + 16
13  libdispatch.dylib                   0x000000010b94ce6c _dispatch_lane_serial_drain + 720
14  libdispatch.dylib                   0x000000010b94db60 _dispatch_lane_invoke + 460
15  libdispatch.dylib                   0x000000010b957bfc _dispatch_workloop_worker_thread + 1220
16  libsystem_pthread.dylib             0x00000001fe6490dc _pthread_wqthread + 312
17  libsystem_pthread.dylib             0x00000001fe64bcec start_wqthread + 4
2019-02-14 10:20:45 +01:00
Aaron van Meerten
e144c2fb3e Merge pull request #3886 from jitsi/invitation-post-application-json
invite post to use application/JSON
2019-02-13 16:42:47 -06:00
Aaron van Meerten
1a5e2763c1 use fetch syntax from virtuacoplenny 2019-02-13 16:23:26 -06:00
Saúl Ibarra Corretgé
a4121fef36 deps: update package-lock.json for npm 6.7.0 2019-02-13 21:59:38 +01:00
Aaron van Meerten
76638f524d invite post to use application/JSON
changed to .ajax from .post to allow content type to be set
2019-02-12 16:28:33 -06:00
Saúl Ibarra Corretgé
8ea693616d color-scheme: fix React warning
A reducer must always return a state or null to ignore it. When the color scheme
is undefined we should return the previous state.
2019-02-12 20:22:28 +01:00
Дамян Минков
2442ef80b0 Adds Afrikaans to languages. (#3884) 2019-02-12 13:18:56 +00:00
Saúl Ibarra Corretgé
87f171caa4 feat(ScreenSharing): use sane defaults
Both Chrome and Firefox can work without extensions now, so it no longer makes
sense to default to disabling it in Chrome. Moreover, rely on the fact that
undefined is falsey so no actual config needs to be provided.
2019-02-12 10:00:23 +00:00
Bettenbuk Zoltan
e094b6516a [RN] Add color scheme support - Components 2019-02-08 11:43:21 +01:00
Bettenbuk Zoltan
2941f5dde4 [RN] Add color scheme support - JS 2019-02-08 11:43:21 +01:00
Bettenbuk Zoltan
eec7a1b628 [RN] Add color scheme support - native 2019-02-08 11:43:21 +01:00
Saúl Ibarra Corretgé
5f7a515610 rn: drop {AddPeople,Invite}Controller
We are going to implement the invite dialog *inside* the SDK, so there is no
need to have all this machinery anymore.
2019-02-08 09:02:15 +01:00
virtuacoplenny
b7133f5717 fix(large-video): do not show avatar if no url (#3871)
* fix(large-video): do not show avatar if no url

By default the large video dominant speaker avatar
has an empty src, which will result in a broken
image displaying. There is also disconnect with
non-react code trying to set an undefined src.
To prevent such until local avatar generation
work is done in the future, just don't show the
avatar.

* fix(conference): set the room instance earlier

Set the room instance on APP.conference before triggering
a redux update of the conference being set,, because
middleware can then fire and call methods on APP.conference
that depend on the room being set.

* get local participant directly from store instead of from global
2019-02-06 19:19:02 -08:00
virtuacoplenny
f77e1dc591 fix(speaker-levels): convert calculation from string to float (#3870) 2019-02-06 10:49:20 -08:00
virtuacoplenny
4d817fc6c2 fix(home): fix plus button alignment for calendar events (#3869) 2019-02-06 09:33:44 -08:00
Paweł Domas
b8a7037959 Merge pull request #3866 from jitsi/ice_failed_notification
chore(deps): update LJM to get ICE failed notifications
2019-02-06 09:00:55 -06:00
Saúl Ibarra Corretgé
6f95c50d6e Revert "misc: make URL protocol matching regexes non-greedy"
This reverts commit 7c911eca96.

I'm dumb. We need global mode because otherwise lastIndex is not updated in the
regex object, which we rely upon, so this is intentional.
2019-02-06 15:49:44 +01:00
Bettenbuk Zoltan
9f3ef43daa [RN] Add conference navigation bar 2019-02-06 14:27:25 +01:00
Bettenbuk Zoltan
46713cab3b Move Labels to Conference 2019-02-06 14:27:25 +01:00
Bettenbuk Zoltan
8065cc0348 [RN] Remove unused code 2019-02-06 14:27:25 +01:00
Bettenbuk Zoltan
045a2d6aca Extract isToolboxVisible function 2019-02-06 14:27:25 +01:00
Bettenbuk Zoltan
d7d9bc4eeb Reorganize conference feature files 2019-02-06 14:27:25 +01:00
Saúl Ibarra Corretgé
33db155eb9 ios: don't override AVAudioSession category and mode in default state
When we are in the default state (ie, not in a meeting) we shouldn't override
the AVAudioSession category and mode. It's a singleton and we might be bothering
other components of the host app which use it.
2019-02-06 10:17:39 +01:00
paweldomas
1cfd6164f5 chore(deps): update LJM to get ICE failed notifications
Updates LJM to 2e1436e20d4d8fb6020497a87b2714dff38a6c86 which includes
the ICE failed notification logic.
2019-02-05 21:45:02 -06:00
bgrozev
d9cf33b4c4 fix: Shows the "turn" indication for non-p2p connections. (#3865) 2019-02-05 23:13:59 +00:00
virtuacoplenny
2b56822a41 fix(ie11): redirect to recommended browsers page (#3853) 2019-02-05 08:26:27 -08:00
damencho
c34fee4305 Commit from translate.jitsi.org by user damencho.: 500 of 583 strings translated (0 fuzzy). 2019-02-05 13:36:52 +00:00
jitsi-pootle
ddc8a670f9 New files added from translate.jitsi.org based on templates 2019-02-05 10:09:34 +00:00
Saúl Ibarra Corretgé
7c911eca96 misc: make URL protocol matching regexes non-greedy 2019-02-02 21:21:21 +01:00
Saúl Ibarra Corretgé
f3c83f6e6d rn: finally fix Android deep-linking
The URL cannot end in a /.
2019-02-02 21:21:21 +01:00
Aaron van Meerten
b9a14acd3c adds conference to lookup of dial in numbers (#3859) 2019-02-01 19:10:34 -08:00
Aaron van Meerten
c998dbb47e Merge pull request #3858 from jmacelroy/master
Normalizing subdomain when checking JWTs; similar to room.
2019-02-01 13:53:39 -06:00
jmacelroy
573cc64fcd Normalizing subdomain when checking JWTs; similar to room. 2019-02-01 13:19:33 -06:00
Saúl Ibarra Corretgé
53c232fd76 misc: fix off-by-one error
e729f0948c contained an off-by-one error:

URI_PROTOCOL_PATTERN includes the colon, so after applyting the regex we are
left with something like '//example.com/room' thus we only need to strip the
first 2 characters.

🤦
2019-02-01 15:22:17 +01:00
paweldomas
3b6e34e96b fix(JitsiMeetLogStorage): do not log whole message
The logMessage can be very long. Probably only it's length could be
important to why sendApplicationLog has failed. Stringify the error
though.
2019-02-01 10:34:03 +01:00
paweldomas
8fe5814831 ref(JitsiMeetLogStorage): move to base/logging 2019-02-01 10:34:03 +01:00
paweldomas
2305effa5c feat(RN): enable log collector on mobile
Stores the Logger.LogCollector instance in base/logging state instead of
global APP variable and enables it on mobile.
2019-02-01 10:34:03 +01:00
Saúl Ibarra Corretgé
e729f0948c android: fix deep-linking from web
Looks like custom-scheme links no longer work in all browsers. They do on
Firefox, but the don't in Chrome and other default browsers.

So, switch to intent links on Android:
https://developer.chrome.com/multidevice/android/intents

Example:
```
<a href="intent://meet.jit.si/test123#Intent;scheme=org.jitsi.meet;package=org.jitsi.meet;end">Open Jitsi Meet</a>
```
2019-02-01 09:30:09 +01:00
virtuacoplenny
6f57d58dd9 fix(participant): update local name only on explicit update (#3849)
Dominant speaker events can trigger local participant updates
without a display name. Do not update the name unless there
is an explicit update in the action.
2019-01-31 15:46:34 -08:00
paweldomas
d0858b95b8 update lib-jitsi-meet 2019-01-31 13:50:51 -06:00
Saúl Ibarra Corretgé
5afb057387 rn: polyfill callstats
Instead of bundling it in lib-jitsi-meet, which unnecessarily increases
lib-jitsi-meet's bundle size, polyfill it here so it's available in the global
scope, just like the web does.
2019-01-31 13:50:51 -06:00
Saúl Ibarra Corretgé
5d86e3b674 deps: update react-native-callstats to 3.57.1 2019-01-31 13:50:51 -06:00
Paweł Domas
f8294fb312 android: add ConnectionService
* feat(Android): implement ConnectionService

Adds basic integration with Android's ConnectionService by implementing
the outgoing call scenario.

* ref(callkit): rename _SET_CALLKIT_SUBSCRIPTIONS

* ref(callkit): move feature to call-integration directory

* feat(ConnectionService): synchronize video state

* ref(AudioMode): use ConnectionService on API >= 26

Not ready yet - few details left mentioned in the FIXMEs

* feat(ConnectionService): add debug logs

Adds logs to trace the calls.

* fix(ConnectionService): leaking ConnectionImpl instances

Turns out there is no callback fired back from the JavaScript side after
the disconnect or abort event is sent from the native. The connection
must be marked as disconnected and removed immediately.

* feat(ConnectionService): handle onCreateOutgoingConnectionFailed

* ref(ConnectionService): merge classes and move to the sdk package

* feat(CallIntegration): show Alert if outgoing call fails

* fix(ConnectionService): alternatively get call UUID from the account

Some Android flavours (or versions ?) do copy over extras to
the onCreateOutgoingConnectionFailed callback. But the call UUID is also
set as the PhoneAccount's label, so eventually it should be available
there.

* ref(ConnectionService): use call UUID as PhoneAccount ID.

The extra is not reliable on some custom Android flavours. It also makes
sense to use unique id for the account instead of the URL given that
it's created on the per call basis.

* fix(ConnectionService): abort the call when hold is requested

Turns out Android P can sometimes request HOLD even though there's no
HOLD capability added to the connection (what!?), so just abort the call
in that case.

* fix(ConnectionService): unregister account on call failure

Unregister the PhoneAccount onCreateOutgoingConnectionFailed. That's
before the ConnectionImpl instance is created which is normally
responsible for doing that.

* fix(AudioModeModule): make package private and run on the audio thread

* address other review comments
2019-01-31 17:20:53 +01:00
Aaron van Meerten
3ad99e24cf Merge pull request #3840 from jitsi/prosody-token-wildcard-subdomain
supports a '*' in the sub claim to allow access to any room
2019-01-29 13:48:33 -06:00
Saúl Ibarra Corretgé
14990a427a rn: set version to 19.0.0
This marks our switch to CalVer: http://calver.org/

Major: year
Minor: release number
Patch: build (in case we need to retry)
2019-01-29 17:25:00 +01:00
virtuacoplenny
a1383bf730 fix(local-recording): allow config override to enable (#3615)
* fix(local-recording): allow config override to enable

Config overrides are not set until some time after
APP_WILL_MOUNT has completed and not in the same execution
context as when APP_WILL_MOUNT is called. So instead
choose recording controller initialization at a later time.
The time chosen is after conference join because the
controller needs the conference instance to work.

* remove redundant conditional check
2019-01-29 08:22:50 -08:00
Saúl Ibarra Corretgé
9bfe54475b android: read Dropbox API from main package resources
This makes Dropbox work on apps using the SDK without needing to build it
themselves.
2019-01-29 15:39:20 +01:00
Saúl Ibarra Corretgé
d5a43426ed android: don't read Dropbox key from iOS files 2019-01-29 15:39:20 +01:00
Saúl Ibarra Corretgé
612028ce3c rn: fix showing Dropbox controls when recording
- remove unneeded dialog (it's taken care of by StartRecordingDialogContent)
- pass correct props so integrations (Dropbox) show up
2019-01-29 15:39:20 +01:00
Saúl Ibarra Corretgé
3cec4989fd ios: enable recording in dev mode
While Apple doesn't want to allow us to enable Dropbox, it's good to have it
available for testing.
2019-01-29 15:39:20 +01:00
Saúl Ibarra Corretgé
77f220753f ios: dynamically load Dropbox API key
Load it at build time from a "dropbox.key" file. The file should contain the API
key in a single line.
2019-01-29 15:39:20 +01:00
Aaron van Meerten
13165990fc supports a '*' in the sub claim to allow access to any room 2019-01-28 16:19:43 -06:00
Bettenbuk Zoltan
63ff0c27a9 [RN] Add display name to on-stage participant 2019-01-28 18:34:12 +01:00
Bettenbuk Zoltan
f2b2cfda44 Extract shouldRenderParticipantVideo from ParticipantView 2019-01-28 18:34:12 +01:00
Leonard Kim
1b59b21fa8 chore(deps): bump lib to get wireless screensharing service 2019-01-27 17:55:01 +01:00
virtuacoplenny
6241172af8 feat(screenshare): support remote wireless screensharing (#3809)
* feat(screenshare): support remote wireless screensharing

- Pass events to the ProxyConnectionService so it can
  handle establishing a peer connection so a remote
  participant, not in the conference, can send a
  video stream to the local participant to use as a
  local desktop stream.
- Modify the existing start screensharing flow to accept
  a desktop stream instead of always trying to create one.

* adjust ProxyConnectionService for lib review changes
2019-01-26 12:53:11 -08:00
Saúl Ibarra Corretgé
8e58ce7500 ios: re-enable live streaming on iOS 10
There was a missing delegate method call into RNGoogleSignIn, which fixed this.
2019-01-25 11:06:35 +01:00
Saúl Ibarra Corretgé
37d3625210 ios: fix Goggle Sign-In deep-lining 2019-01-25 11:06:35 +01:00
Saúl Ibarra Corretgé
111397d944 ios: style 2019-01-25 11:06:35 +01:00
Saúl Ibarra Corretgé
211b3b55b1 ios: simplify code 2019-01-25 11:06:35 +01:00
Saúl Ibarra Corretgé
8c0317cac0 ios: fix compilation warnings 2019-01-25 11:06:35 +01:00
Saúl Ibarra Corretgé
15c8f2b125 android: remove unused import 2019-01-25 11:06:35 +01:00
Saúl Ibarra Corretgé
349b1ff70e android: fix warning 2019-01-25 11:06:35 +01:00
Leonard Kim
244f206a87 chore(deps): bump lib for old conference listener cleanup 2019-01-25 09:19:10 +01:00
Bettenbuk Zoltan
82963b0aaf Add VSCode files to gitignore 2019-01-23 20:22:37 +01:00
paweldomas
92a412f814 chore(ios): update Podfile.lock 2019-01-23 17:04:41 +01:00
Guus der Kinderen
4b99caa1a9 Android SDK build instructions: add 'clean'
I'm ashamed to admit that I spent several _days_ chasing a bug that was already fixed. :( This obvious instruction would have been a time-saver.
2019-01-23 16:46:51 +01:00
Saúl Ibarra Corretgé
6190173ea7 misc: fixup MD format 2019-01-21 15:36:12 +01:00
Saúl Ibarra Corretgé
54df0f5d0f misc: add some more issue templates 2019-01-21 11:58:08 +01:00
damencho
4d440f5f64 Fixes showing video after waiting for owner. Fixes #3671. 2019-01-20 14:17:50 -08:00
virtuacoplenny
10624e87f5 ref(conference): change when the room reference is removed (#3808)
Delay removing the room reference. This is in case a
consumer of the API is attempting to submit feedback
after hangup but before redirecting to another page.
If the room reference is removed, feedback submission
will fail during this period.
2019-01-17 16:04:35 -08:00
Leonard Kim
7f29b47f3a chore(deps): update lib for camera-as-screenshare feature 2019-01-17 09:51:15 +01:00
Saúl Ibarra Corretgé
7c69308270 android: remove unused ProGuard rules file 2019-01-16 14:55:58 -06:00
Saúl Ibarra Corretgé
50b4212463 android: add missing ProGuard rules 2019-01-16 14:55:58 -06:00
Saúl Ibarra Corretgé
bb8fc8770a android: fix packager in debug mode in API 28
These values must match these ones in React Native:
5939d078a0/ReactAndroid/src/main/java/com/facebook/react/modules/systeminfo/AndroidInfoHelpers.java (L20-L22)
2019-01-16 14:55:12 -06:00
Bettenbuk Zoltan
79209535ea Centralise display name normalisation 2019-01-16 11:03:29 +01:00
Bettenbuk Zoltan
4bddae0bdb Remove default value from openDisplayNamePrompt action 2019-01-16 11:03:29 +01:00
Saúl Ibarra Corretgé
c203a452f7 ios: add initial Fastlane integration
Used for building and deploying builds to TestFlight and the App Store.
2019-01-16 11:01:42 +01:00
Saúl Ibarra Corretgé
515d2f11ce ios: set Google reverse client ID at build time
Read it from the GoogleService-Info.plist file and apply it into Info.plist.
2019-01-16 11:01:42 +01:00
Saúl Ibarra Corretgé
f7134722d0 ios: let the system reorder the file 2019-01-16 11:01:42 +01:00
Saúl Ibarra Corretgé
d8fa52fcaf ios: use a proper URL scheme placeholder
No caps are allowed, so this would trigger a rejection when uploading to
TestFlight.
2019-01-16 11:01:42 +01:00
Saúl Ibarra Corretgé
f7162c1500 rn: add some cleanup tasks when a conference ends / changes
- unpin participant (if the local one was pinned it would remain)
- close any dialog (except if authentication is pending)
2019-01-16 11:00:37 +01:00
virtuacoplenny
998db80db1 Merge pull request #3782 from virtuacoplenny/lenny/camera-as-ss
feat(screenshare): use camera as a screenshare source
2019-01-15 12:05:09 -08:00
Hristo Terezov
4575e7e119 fix(calendar): Remove logs with invalid calendar items. 2019-01-15 17:00:23 +01:00
Bettenbuk Zoltan
230b2b02fa Chat render improbement 2019-01-15 13:38:26 +01:00
Bettenbuk Zoltan
8a241ba2b7 [RN] Add chat functionality
Co-authored-by: DimaG <dgeorgiev06@gmail.com>
2019-01-15 11:33:12 +01:00
Bettenbuk Zoltan
82f714b608 Move display name handling into redux 2019-01-15 10:15:02 +01:00
Leonard Kim
8c9ba325ca fix(pinning): send a participant id on unpin
Analytics is erroring when unpinning because the logged
event sends null for the objectId. The objectId should
be the id of the person getting unpinned.
2019-01-15 09:58:45 +01:00
virtuacoplenny
693d4357a0 Merge pull request #3788 from virtuacoplenny/lenny/alpha-main
ref: alphabetize translation strings
2019-01-14 08:22:00 -08:00
Bettenbuk Zoltan
eef31d05cf [RN] Add an abstraction layer to Modal 2019-01-12 15:51:50 +01:00
Bettenbuk Zoltan
d7475a44e4 [RN] Extract header components for reuse 2019-01-12 15:51:50 +01:00
Leonard Kim
d09bfa7c0a ref: alphabetize translation strings 2019-01-11 15:33:01 -08:00
Leonard Kim
69dfa30142 feat(screenshare): use camera as a screenshare source
This feature is intended for spot. Spot can have an
HDMI -> usb adapter hooked up to it. In that case,
attempting to screenshare should use that adapter
as a screensharing source. Jitsi-Meet should pass
a configured screenshare source into lib-jitsi-meet
so it can be used as a source.
2019-01-11 09:52:53 -08:00
damencho
c13424f7c0 Fixes some lint warnings. 2019-01-11 15:11:17 +01:00
srmcgann
e7d0bf7b66 feat(DeepLinkingMobilePage): fix nested iframe button 2019-01-11 09:42:52 +01:00
damencho
653471e1c0 Adds specific class name to kick button.
The class name is similar to the one used for the mute button and is used by the tests to locate and click the button.
2019-01-10 12:02:55 -06:00
Bettenbuk Zoltan
e960002c45 [RN] Fix InviteButton translation 2019-01-10 16:31:03 +01:00
Bettenbuk Zoltan
c503187dc1 [RN] Fix input dialog value bug 2019-01-10 16:10:07 +01:00
virtuacoplenny
71563fec5b Merge pull request #3773 from zbettenbuk/fix-file-location
Move DeviceSelectionPopup to its right place
2019-01-09 22:17:49 -08:00
virtuacoplenny
5bf692a386 Merge pull request #3775 from jitsi/ljm_2080e81
Update LJM
2019-01-09 22:17:31 -08:00
paweldomas
f25556e63a chore: update lib-jitsi-meet to 2080e81 2019-01-09 18:48:59 -06:00
Saúl Ibarra Corretgé
a5696bb3e4 ref(eslint): remove React deprecated method rule suppression 2019-01-09 14:28:59 +01:00
Leonard Kim
29809ab024 ref(app): set url prop to state in componentDidUpdate
This is done to kill off the last deprecated lifecycle
usage. There is special logic within index.native to get a
default meeting url by asynchronously fetching it, if
a url is not passed initially. The url is then put onto
state and overridable on subsequent prop updates.
2019-01-09 14:28:59 +01:00
Bettenbuk Zoltan
5c0ae10ccb Remote video menu post-PR improvements 2019-01-09 12:13:30 +01:00
Bettenbuk Zoltan
82f6931ee8 [RN] Fix a react warning on remote video menu 2019-01-09 12:13:30 +01:00
Bettenbuk Zoltan
2ea5f3c1aa [RN] Add avatar to remote video menu 2019-01-09 12:13:30 +01:00
Saúl Ibarra Corretgé
889644f7bd [iOS] Add support for Siri shortcuts
This is mostly implemented in the app, with the needed support in the SDK. Since
the app needs to donate intents and deal with creating NSUserActivity objects it
doesn't feel right to do this in a library. Instead, we donate the intents from
the app, but the SDK is ready to extract conference URLs from any intent which
was registered as a conference activity.

This also opens the door for eventually adding Handoff support.
2019-01-09 12:05:58 +01:00
Saúl Ibarra Corretgé
4898f81596 [iOS] Simplify code
Share the code for extracting the URL for conference from a NSUserActivity.
2019-01-09 12:05:58 +01:00
Bettenbuk Zoltan
20d597e402 Move DeviceSelectionPopup to its right place 2019-01-09 12:02:42 +01:00
virtuacoplenny
fcd12a9cf6 Merge pull request #3770 from virtuacoplenny/lenny/bump-lib-json-override
chore(deps): bump lib to fix json message type overrriding
2019-01-08 13:27:42 -08:00
Leonard Kim
03094030a5 chore(deps): bump lib to fix json message type overriding 2019-01-08 11:32:06 -08:00
Buddhika Jayawardhana
16f47e5e0a doc: mention Java version 8 is required
Ref: #3543
2019-01-08 17:45:05 +01:00
Saúl Ibarra Corretgé
634f304815 android: simplify handling of the back button
Provide a default and builtin default implementation which finishes the
Activity, same as before.

What this PR removes is the ability to provide a custom default handler because
applications can already take this decision when calling `onBackPressed`. In
addition, make `onBackPressed` return `void` because it's virtually impossible
for it to return `false` (that would mean that there is no
`ReactInstanceManager`, which means there is no app to begin with).

In addition, remove the use of `BackAndroid` since `BackHandler` contains an iOS
shim now.
2019-01-08 17:43:36 +01:00
Saúl Ibarra Corretgé
148d4ebb90 rn: add Firebase integration
This is done at the app level, not the SDK.

Currently 2 Firebase services are used:

  - Crashlytics
  - Dynamic Links

They are enabled in tandem, if the appropriate Google services file
(GoogleService-Info.plist on iOS or google-services.json on Android) is found.

Each service needs to be individually enabled in the Firebase console.
2019-01-08 17:42:59 +01:00
Saúl Ibarra Corretgé
a1ebba0ef7 android: fix compilation warning
The annotation processor is required for our Glide module to be included.
2019-01-08 17:42:59 +01:00
Saúl Ibarra Corretgé
7e231c2826 ios: fix compilation warning 2019-01-08 17:42:59 +01:00
Saúl Ibarra Corretgé
5cbddb4874 ios: update SDK installation instructions
Mention the availability of CocoaPods.
2019-01-08 09:41:27 +01:00
Hristo Terezov
5a5dd6f5c5 chore(lib-jitsi-meet): Update. 2019-01-07 19:48:48 +00:00
Hristo Terezov
c2b2b4eba4 fix(initAnalytics): Add catch. 2019-01-07 19:48:48 +00:00
Hristo Terezov
155c7ba633 chore(lib-jitsi-meet): Update. 2019-01-07 14:32:31 +00:00
Hristo Terezov
5ad98dd058 ref(config): Create 'analytics' section. 2019-01-07 14:32:31 +00:00
Hristo Terezov
e5a8d95f1f feat(Amplitude): Integration. 2019-01-07 14:32:31 +00:00
virtuacoplenny
2d57d22a3f Merge pull request #3762 from saghul/no-prop-types
misc: drop dependency on prop-types and polyfill
2019-01-04 08:37:18 -08:00
virtuacoplenny
132dd98ecf Merge pull request #3763 from saghul/update-loaders
deps: update webpack loaders
2019-01-04 08:36:34 -08:00
virtuacoplenny
9b47dd1403 Merge pull request #3753 from virtuacoplenny/lenny/hangup-clean-ui
ref(hangup): clean up some UI state on hangup
2019-01-04 08:29:40 -08:00
virtuacoplenny
5b45542009 Merge pull request #3764 from virtuacoplenny/lenny/no-cwrp-abstract-app
ref(app): move url change handling to componentDidUpdate
2019-01-04 07:58:21 -08:00
Saúl Ibarra Corretgé
0b6496bf4d misc: drop dependency on prop-types and polyfill 2019-01-04 10:53:07 +01:00
Saúl Ibarra Corretgé
8ac701ab74 deps: drop @atlaskit/layer-manager dependency
We no longer need it since Rect 16 takes care of passing the context around.
It's also deprecated: https://atlaskit.atlassian.com/packages/core/layer-manager
2019-01-04 10:52:31 +01:00
Saúl Ibarra Corretgé
80bfeb6613 deps: update webpack loaders
- bump expose-loader to 0.7.5
- remove unused file-loader

NOTE: The first incarnation of this commmit also removed string-replace-loader,
but alas lib-jitsi-meet lists it as a devDependency so it's not installed and we
are currently running webpack on install. This is arguably wrong, but that's a
discussion for another day.
2019-01-04 10:27:40 +01:00
Leonard Kim
22a1917107 ref(app): move url change handling to componentDidUpdate
Instead of handling the side effect of navigating to another
url from within componentWillReceiveProps, try to match the
same logic instead in componentDidUpdate.
2019-01-03 19:34:46 -08:00
Sebastian Safari
ce01b31514 doc: add notes to enable 32bit only mode 2019-01-03 13:43:11 +01:00
Saúl Ibarra Corretgé
274b7148b0 ios: update development team
Also update the bundle ID since we don't release with that anyway.
2019-01-03 13:11:41 +01:00
Saúl Ibarra Corretgé
937c74f49e rn: disable touch feedback on Thumbnail
Touch feedback manifests in some ugly black border bleeding out of the thumbnail
itself. Since we already provide feedback (be that by adding the blue border in
case of pinning, or showing the menu in case of long press) the perception is
the same, without the graphical glitch.
2019-01-03 13:10:51 +01:00
Leonard Kim
be2bd9e2e6 chore(deps): bump @atlaskit/flag to 9.1.8 from 6.1.0
- Change the existing overrides to move the flags
  so the first flag does not cover the toolbar.
- Add a new override to disable the slide in
  animation, as it will play for each flag once
  it becomes the first flag--instead of playing
  only once when the flag queue has items.
2019-01-03 11:20:46 +00:00
Leonard Kim
50d3f46934 fix(thumbnail): re-override atlaskit theme for top toolbar
A CSS override prevents atlaskit theme from setting a
dark background on the top toolbar. With the upgrade
of the theme package the CSS class names changed.
2019-01-03 11:42:31 +02:00
Leonard Kim
14cc4ea54a ref(hangup): clean up some UI state on hangup
- Reset some state on the singletons conference
  and VideoLayout.
- Add a way for LocalVideo to clean itself up
  by sharing logic with the other SmallVideos.
- Add clearing of chat messages so they don't
  linger.
- Remove some UI event listeners.
2019-01-02 09:54:05 -08:00
Leonard Kim
9215b1e8b2 ref(app): move initialization into componentDidMount
componentWillMount is a deprecated lifecycle method;
componentDidMount should be used to kick off things
like ajax. In the case of the _App hierarchy, a promise
chain is used to perform initialization, and it is
first started in the constructor by initializing
storage. However, by the time storage is initialized,
resolving the first promise, _App has already mounted.
So, move it all to the componentDidMount lifecycle.
2019-01-02 10:02:04 +01:00
Leonard Kim
d996d51653 chore(deps): bump to @atlaskit/icon 15.0.5, @atlaskit/theme 7.0.2 2019-01-02 09:46:03 +01:00
Leonard Kim
ebcde745ef feat(tile-view): double click to pin 2019-01-02 09:43:20 +01:00
Saúl Ibarra Corretgé
fc75adc6ff feat(DialInInfo): fix webpack warning 2019-01-02 09:26:05 +01:00
Leonard Kim
3c4907ee0a fix(dial-in): update jsdoc 2019-01-02 09:22:58 +01:00
virtuacoplenny
914a64df7a Merge pull request #3747 from virtuacoplenny/lenny/remove-tab-hacks
ref(welcome-page): remove unused atlaskit overrides
2018-12-30 10:39:00 -08:00
Leonard Kim
eb34b0b11d ref(welcome-page): remove unused atlaskit overrides
WelcomePage used to use @atlaskit/tabs to switch between
recent meetings and calendar meetings. @atlaskit/tabs is
no longer used there so remove the css hacks which made
it look more presentable.
2018-12-29 16:42:55 -08:00
michael-dev
f6d3ca23a5 Fix LoginDialog hidden by gUM-Overlay (#766)
* Fix LoginDialog hidden by gUM-Overlay

Running FF46 on Linux and Android. The gUM Dialog (zIndex 1013) hides the LoginDialog (zIndex 999 by default) , but the gUM Dialog will only be resolved when connection is completed (aka hideUserMediaPermissionsGuidanceOverlay is called once the Promise.all in createInitialLocalTracksAndConnect is resolved and that Promise includes "connect").

Fix this by increasing the connection dialog zIndex.

Alternatively this could by fixed by handling gUM and connection one after the other.

* remove whitespace change
2018-12-28 09:37:29 -08:00
virtuacoplenny
fe33ad5026 Merge pull request #3724 from virtuacoplenny/lenny/bump-modal-dialog
chore(dep): bump @atlaskit/modal-dialog 6.0.12 to 7.1.2
2018-12-28 08:13:42 -08:00
damencho
380d9c75d1 Simplifies logic and renames a method. 2018-12-28 13:54:29 +00:00
damencho
7a09befd87 Updates time to be in ms and sends update of stats when user joins. 2018-12-28 13:54:29 +00:00
damencho
3b4037553a Adds server-side speaker stats handling.
Adds the component which receives the messages from client and a module which enabled on a virtual host will start advertising the component. When clients discover the component they will send message to the component with the name of the room where the dominant speaker event happen.
2018-12-28 13:54:29 +00:00
Saúl Ibarra Corretgé
f1ca2cac96 build: fix setting webpack mode 2018-12-21 17:04:08 +01:00
Saúl Ibarra Corretgé
8d1fd9841e misc: shorten text description 2018-12-21 15:12:13 +01:00
Leonard Kim
8b399e8caf chore(dep): bump @atlaskit/modal-dialog 6.0.12 to 7.1.2
The package now requires using a ModalTransition component
to handle animations. The existing DialogContainer component
has been split into native and web implementations to support
this change.
2018-12-20 20:05:49 -08:00
Bettenbuk Zoltan
6b68fba220 [RN] Add remote video menu 2018-12-20 17:23:07 +01:00
Saúl Ibarra Corretgé
d4c0840659 deps: update react-native-webrtc 2018-12-20 16:31:33 +01:00
Saúl Ibarra Corretgé
46a9891763 deps: update react-native-linear-gradient 2018-12-20 14:55:59 +01:00
Saúl Ibarra Corretgé
24bd62c22a ios: disable recording
Apple rejected our app on account of requiring Dropbox not being acceptable. Oh
well! Disable it until we find a way around it. Sigh.
2018-12-20 14:15:12 +01:00
Saúl Ibarra Corretgé
fef47684d9 deps: update react-native-background-timer 2018-12-20 13:59:17 +01:00
Saúl Ibarra Corretgé
58887577b4 ios: fix compilation warning 2018-12-20 13:56:46 +01:00
Saúl Ibarra Corretgé
c3a91c3194 deps: update react-native-sound
The avid reader may notice we have switched to using our own fork. That is
indeed the case. The upstream author hasn't maintained the library in months,
and changes to the Android build system are required at this point, hence the
fork.
2018-12-20 13:53:55 +01:00
Saúl Ibarra Corretgé
0469e5af5e deps: update react-native-calendar-events 2018-12-20 13:52:14 +01:00
Leonard Kim
eeb0697e52 chore(deps): update atlaskit deps with minor or patch bumps 2018-12-20 09:31:24 +01:00
Leonard Kim
e3415df6a3 chore(deps): update @atlaskit/button 9.0.8 to 10.1.1 2018-12-20 09:31:24 +01:00
Leonard Kim
b36fd96b07 chore(deps): update @atlaskit/checkbox from 4.0.6 to 5.0.10 2018-12-20 09:31:24 +01:00
Leonard Kim
07bcb38dd6 fix(live-streaming): show message if no broadcasts are found
It's possible for the YouTube api to return zero broadcasts
or broadcasts without any streams--streams are what are
associated with stream keys. In this case, instead of showing
an empty selector or no selector, show a message with a link
to where the stream key can be obtained.
2018-12-19 22:12:44 +01:00
virtuacoplenny
699b13066e Merge pull request #3696 from virtuacoplenny/lenny/stream-key-validation
Add some live stream key validation
2018-12-19 10:18:09 -08:00
Saúl Ibarra Corretgé
1e83891a70 deps: update react-native-immersive 2018-12-19 17:44:29 +01:00
Leonard Kim
001e8fe0a7 fix(tile-view): prevent local participant being selected on pin exit
On tile view enter/exit, local video is moved in the DOM (an effect
of not being reactified and moving being easier) and play is called
on its video element. The race condition setup is such: in tile
view with other participants and local video is on large (not
visible in the UI but visible in the app state and pip popout).
The race is such: pin a remote video, large video update is queued,
tile view is exited, local video is moved, play is called,,
onVideoPlaying callback executed, middleware fires mute update,
which checks if local is on large (it is), previous large video
update is cleared, and local is placed on large.

The fix is ensuring the redux representation of local video is
passed in, which holds the boolean videoStarted, which prevents
the onVideoPlaying callback from firing on subsequent plays.
2018-12-19 15:35:25 +01:00
Saúl Ibarra Corretgé
f97869ffde deps: update react-native-keep-awake 2018-12-19 15:20:10 +01:00
Saúl Ibarra Corretgé
0fc69416d4 android: update build and target SDK versions
Note that Android 9 Pie (API 28) disallows HTTP requests by default, so an
exception was needed in the app in order for the Metro bundler to work in debug
mode.
2018-12-19 15:18:10 +01:00
Saúl Ibarra Corretgé
f8f544c615 android: remove unneeded gradle task 2018-12-19 15:18:10 +01:00
Saúl Ibarra Corretgé
ac624b104f android: remove duplicated Maven repo
The Google repository is already added with google().
2018-12-19 15:18:10 +01:00
Saúl Ibarra Corretgé
d18f582922 android: fix warning 2018-12-19 15:18:10 +01:00
Saúl Ibarra Corretgé
85b141db89 android: update proguard rules 2018-12-19 15:18:10 +01:00
Saúl Ibarra Corretgé
e4b1e40cc6 deps: update react-native-google-signin 2018-12-19 15:15:21 +01:00
damencho
bc06d969b5 chore(deps): bump lib-jitsi-meet. 2018-12-19 14:08:28 +02:00
Saúl Ibarra Corretgé
1ff8d52b6b deps: update react-native to 0.57.8
Bumping React to 16.6.3 is a requirement for this update.
2018-12-19 10:28:28 +01:00
Leonard Kim
5598b8443a fix(live-streaming): show stream key validation in mobile 2018-12-18 14:29:13 -08:00
Leonard Kim
920c179f56 fix(live-streaming): show warning if stream key seems wrong
Provide a client-side notice if the YouTube live stream key
looks like it might be in the wrong format. Normally the
stream key looks like 4 groups of 4 numbers and letters,
each separated by a dash. The warning does not block submission
in case YouTube changes their stream key format.
2018-12-18 12:59:02 -08:00
Leonard Kim
b57eaed940 fix(live-streaming): trim the entered stream key 2018-12-18 12:59:02 -08:00
Bettenbuk Zoltan
4da8c626f7 Exclude static jitsi links from calendar fetch 2018-12-18 17:36:44 +01:00
Saúl Ibarra Corretgé
65519ec926 rn: drop support for beta.meet.jit.si over HTTP
It doesn't add any value, by default HTTP is not supported unless an exception
is added, plus it doesn't work in browsers at all.
2018-12-18 16:18:08 +01:00
Saúl Ibarra Corretgé
342718f673 rn: drop support for no longer supported deployments 2018-12-18 16:18:08 +01:00
virtuacoplenny
f35653b8fa chore(deps): bump lib-jitsi-meet (#3695)
* chore(deps): bump lib-jitsi-meet

Brings in a fix for getDisplayMedia being moved onto
navigator.mediaDevices and a hack fix for SDP
interop between Chrome and Firefox.

* Update package lock file sed/http/https.
2018-12-18 15:36:15 +02:00
Bettenbuk Zoltan
80e8afa9c1 [RN] Remove react-native-prompt 2018-12-18 13:21:48 +01:00
Saúl Ibarra Corretgé
3212bde6e6 [RN] Recolor AudioRoutePickerDialog 2018-12-18 13:21:48 +01:00
Bettenbuk Zoltan
506b15e3b5 [RN] Recolor BottomSheet 2018-12-18 13:21:48 +01:00
Bettenbuk Zoltan
62e7fd7e8e [RN] Make feature dialogs branded: recording 2018-12-18 13:21:48 +01:00
Bettenbuk Zoltan
4bc09dd8b9 [RN] Make feature dialogs branded: room-lock 2018-12-18 13:21:48 +01:00
Bettenbuk Zoltan
f6e6b09e78 [RN] Make feature dialogs branded: calendar-sync 2018-12-18 13:21:48 +01:00
Bettenbuk Zoltan
9645de33bc [RN] Make feature dialogs branded: authentication 2018-12-18 13:21:48 +01:00
Bettenbuk Zoltan
22a602768c [RN] Add branded dialog component 2018-12-18 13:21:48 +01:00
Leonard Kim
3ebad112a2 ref(conference): remove deprecated lifecycle methods 2018-12-18 12:38:25 +01:00
Saúl Ibarra Corretgé
545ad0e1a6 android: update documentation 2018-12-18 09:05:50 +01:00
Saúl Ibarra Corretgé
053437c86e android: circumvent trouble with apps using Glide
Glide (which is used by react-native-fast-image) can cause trouble if the host
app (the one using the SDK) is using Glide already.

To avoid this, don't use the builtin AppGlideModule (as the docs recommend) and
let apps define it.
2018-12-18 09:05:50 +01:00
Saúl Ibarra Corretgé
0a9333af02 rn: refactor Avatar to deal with FastImage changes
Updating react-native-fast-image brings a couple of interesting changes:

- onLoad is not called for cached images (reported and ignored upstream)
- load progress not working if component not displayed (on Android)

In order to fix this, a combination of 2 approaches was used:

- onLoadEnd / onError are used to detect if the image is loaded
- off-screen rendering is used on Android to get progress events

While implementing the above, yours truly noticed the complexity was increasing
way too much, so some extra refactoring was also performed:

- componentWillReceiveProps is dropped
- an auxiliary component (AvatarContent) is used for the actual content of the
  Avatar, with the former passing the key prop to the latter

Using the key prop ensures AvatarContent will be recreated if the URI changes,
which is not a bad idea anyway, since the new image needs to be downloaded.
2018-12-18 09:05:50 +01:00
Saúl Ibarra Corretgé
0031fd2678 android, ios: update react-native-fast-image dependency 2018-12-18 09:05:50 +01:00
damencho
32798b1a80 Recognises calendar events with conference data. 2018-12-17 09:51:13 +00:00
virtuacoplenny
2ea856acee Merge pull request #3688 from virtuacoplenny/lenny/fix-api-doc
fix(docs): change parentNode option name in api example
2018-12-15 11:45:55 -08:00
Leonard Kim
b8b2fb2d56 fix(docs): change parentNode option name in api example 2018-12-15 08:25:56 -08:00
virtuacoplenny
f89f3f144f Merge pull request #3597 from virtuacoplenny/lenny/handle-calendar-signed-out
fix(calendar): show error message if authorization fails on event fetch
2018-12-10 17:51:08 -08:00
Saúl Ibarra Corretgé
22199cb57a android: update documentation to match the new SDK API 2018-12-06 15:29:59 +01:00
Saúl Ibarra Corretgé
e5c9c69ec9 ios: drop iOS 9 support
WebRTC no longer actively fixes iOS 9 issues.
2018-12-06 10:47:44 +01:00
Saúl Ibarra Corretgé
d48bef6c11 ios: set version to 1.21 2018-12-06 10:47:44 +01:00
Saúl Ibarra Corretgé
0c3d037cb5 android: throw if Activity doesn't implement the required interface 2018-12-04 20:09:54 +01:00
Saúl Ibarra Corretgé
47830dfc3d ios: switch back to the "legacy" build system in Xcode
Xcode 10 introduced a new build system. Alas, it breaks a number of important
flows, such as creating an archive for the framework (ie SDK) target.

In order to "fix" this, switch back to the former (Xcode 9) build system for the
time being.
2018-12-04 12:12:01 +01:00
Saúl Ibarra Corretgé
45291e1054 deps: update to webpack 4 2018-12-04 11:28:52 +01:00
Saúl Ibarra Corretgé
66832ada68 deps: update lodash 2018-12-03 19:54:21 +01:00
Дамян Минков
f11b6cbb1e Replaces smileys and the logic of replacing links/emails. (#3560)
* Replaces smileys and the logic of replacing links/emails.

Now using react-emoji-render and react-linkify.

* Fixes heart emoji.

It is known that current implementation doesn't work with ascii emojis that contain < or >, like >:( >:-( </3 <\3 <3. Making those work may bring some xss issues.

* Adds '_blank' and 'noopener noreferrer' to the replaced links.

* Fixes package-lock links (http vs https).

* Fixes comments.
2018-12-03 18:01:40 +00:00
damencho
34f2ff9b85 Update comments. 2018-12-03 16:46:59 +01:00
damencho
21b0ed691b Auto accept android license on build machines.
Every time the android version that is used changes we need to update the license hash.
2018-12-03 16:46:59 +01:00
Saúl Ibarra Corretgé
8b359f48db android: remove no longer needed code
The dependency was dropped, so it's no longer needed.
2018-12-03 16:41:12 +01:00
Saúl Ibarra Corretgé
b66e2e4104 misc: add stale bot to close stalled issues and PRs
https://probot.github.io/apps/stale/
2018-12-03 12:55:33 +01:00
Saúl Ibarra Corretgé
ee8d2df355 android: update build instructions 2018-12-03 11:49:03 +01:00
Saúl Ibarra Corretgé
bc77a62626 android: fix maven repo relative location
Assume it's at the same level as the Jitsi Meet repo, by default.
2018-12-03 11:49:03 +01:00
Saúl Ibarra Corretgé
b15533d75f android: update app / sdk version
Set them to the next release versions. In additon, the buildNumber variable will
be used to match the requirements of versionCode:
https://developer.android.com/studio/publish/versioning

that is, a monotonically increasing number, independent of the app / sdk
version.
2018-12-03 11:49:03 +01:00
Saúl Ibarra Corretgé
cf9a65f475 android: update dependencies for publishing 2018-12-03 11:49:03 +01:00
Saúl Ibarra Corretgé
d7ba4a8a2a android: add helper scripts to publish RN and JSC to Maven 2018-12-03 11:49:03 +01:00
Saúl Ibarra Corretgé
26ba974757 [RN] Drop react-native-locale-detector dependency
The upstream package has been unmaintained for 2 years now, and making the litle
changes needed as React Native needs them is getting old. The actual
funcionality is a couple of one-liners plus tons of boliterplate, which gets
reduced by quite a bit if we just embed it. So here it goes.
2018-12-03 11:48:44 +01:00
Leonard Kim
7614ceda68 ref(video): remove deprecated lifecycle methods from gesture handler 2018-12-03 11:45:09 +01:00
Saúl Ibarra Corretgé
10163274d3 [RN] Share font selections between web and native 2018-12-03 11:27:12 +01:00
Saúl Ibarra Corretgé
2b91745af1 [RN] Fix dominant speaker and moderator indicators
They are part of the Jitsi font now, there is no need to load them from
FontAwesome.
2018-12-03 11:27:12 +01:00
Saúl Ibarra Corretgé
c9b910b1c1 feat(dev): don't proxy fonts
Use the local ones, this simplifies testing adding icons, for example.
2018-12-03 11:27:12 +01:00
Saúl Ibarra Corretgé
e452867e12 feat(cleanup): remove no longer used FontAwesome 2018-12-03 11:27:12 +01:00
Leonard Kim
f83d609f1a ref(video): calculate tint styles at render 2018-12-03 10:27:08 +01:00
Leonard Kim
822bc31d69 ref(video): use videoTrack from props
It doesn't seem like videoTrack needs to be set onto state
if it can be accessed directly from props. Removing the state
automatically removes the deprecated componentWillReceiveProps.
2018-12-03 10:10:24 +01:00
Roland
cea12c9a8b Fix typo which made this module unusable
showControls instead of showControns
2018-12-03 10:08:25 +01:00
virtuacoplenny
05b7e6facc Merge pull request #3636 from virtuacoplenny/lenny/tile-view-toggles-some-features
Tile view toggles some features and some features toggle tile view
2018-11-30 09:10:23 -08:00
Saúl Ibarra Corretgé
34e6ea2f26 deps: remove no longer used autosize 2018-11-30 16:38:17 +00:00
Saúl Ibarra Corretgé
9c4ca38222 deps: update lib-jitsi-meet
Introduces the sdpSemantics configuration option.
2018-11-30 16:22:07 +00:00
virtuacoplenny
5292d14412 Merge pull request #3637 from virtuacoplenny/lenny/floor-tile-sizes
fix(tile-view): thumbnail videos should cover entire thumbnail
2018-11-30 08:14:17 -08:00
virtuacoplenny
e207ad609a Merge pull request #3642 from virtuacoplenny/lenny/tile-view-popover-z
fix(tile-view): popovers should display over icons in other thumbnails
2018-11-30 08:08:21 -08:00
Leonard Kim
35da17f5a6 ref(local-video): merge styles at render
Remove caching of calculated styles, thereby removing
componentWillReceiveProps, by passing in base styles
and passed in styles when rendering.
2018-11-30 08:42:44 +00:00
Guus der Kinderen
fb6949f7ba [Android] Add jsc-android to SDK project / improve build docs
Due to a switch to a newer version of JSCore, the jsc-android dependency is now used by the
SDK. As this dependency is not (yet) available in the Jitsi Maven repository, an error like
this is reported when an application is ran that uses the SDK:

    com.facebook.react.common.JavascriptException: Can't find variable: Symbol

This commit primarily improves the instructions on how to create a local Maven repository
that contains all required dependencies, including the JSCore dependency that was missing.

This intends to address the issue described in https://github.com/jitsi/jitsi-meet/issues/3399
2018-11-29 22:21:27 +00:00
Leonard Kim
9013c0db39 fix(tile-view): popovers should display over icons in other thumbnails 2018-11-29 10:50:25 -08:00
Guus der Kinderen
99542e29e6 [RN] Upgrade React Native to 0.57.6 2018-11-29 10:35:31 +00:00
Leonard Kim
a1ef845663 fix(tile-view): thumbnail videos should cover entire thumbnail
Video elements may have problems scaling to cover pixel fractions,
so there could be a 1px black border line displaying in the
thumbnail. It's most visible in tile view. Flooring the sizing
calculations hides the border.
2018-11-28 13:36:12 -08:00
Leonard Kim
1396d59ce2 fix(tile-view): disable on etherpad display, disable etherpad on view enter 2018-11-28 11:48:15 -08:00
Leonard Kim
29bc18df01 fix(tile-view): disable tile view on pin, unpin all on view enter 2018-11-28 11:36:23 -08:00
Saúl Ibarra Corretgé
1ba66e4b65 ios: update CocoaPods dependencies 2018-11-28 16:59:37 +01:00
Saúl Ibarra Corretgé
80afe30e9e feat(chore): make sure all links in package-lock are HTTPS 2018-11-28 16:41:51 +01:00
virtuacoplenny
957606b3f8 Merge pull request #3630 from virtuacoplenny/lenny/youtube-1-on-1
fix(filmstrip): show thumbnails in 1-on-1 with a fake participant
2018-11-27 14:36:43 -08:00
Leonard Kim
769a2c7c94 fix(filmstrip): show thumbnails in 1-on-1 with a fake participant
Filmstrip remote thumbnails display under certain conditions, as
defined in filmstrip/functions.web.js. Previously the raw
participant count was used, which included fake participants.
Using the selector getParticipantCount excludes fake participants,
causing YouTube thumbnails to remain hidden in a 1-on-1 call.
2018-11-27 12:31:27 -08:00
virtuacoplenny
f349357d3c Merge pull request #3584 from virtuacoplenny/lenny/update-lifecycles-1
Remove some usages of deprecated lifecycle methods
2018-11-27 09:02:05 -08:00
virtuacoplenny
9c2f816c29 Merge pull request #3619 from mmoanis/dialog-with-tabs
Abstract the DialogWithTabs title so it can be reused with other comp…
2018-11-24 10:31:58 -08:00
mmoanis
b844a9f06b Abstract the DialogWithTabs title so it can be reused with other components 2018-11-24 12:50:09 +01:00
Leonard Kim
d4e18e78fa ref(recording-label): derive when the label state is no longer stale 2018-11-21 08:08:45 -08:00
Leonard Kim
f13cfe70f3 ref(sidebar): derive showOverlay state
- Derive the showOverlay state. When the sidebar should be hidden,
  the internal showOverlay state should remain true until the
  animation hides it. When the sidebar should show, the showOverlay
  state should become true immediately.
- Use PureComponent to prevent additional animation triggers
  instead of explicitly checking changes to the "show" prop.
2018-11-21 08:08:45 -08:00
Leonard Kim
5cb4bec633 ref(circular-label): animate after dom updates
Based on react-native docs, looks like animations should be
started after mount. Updating animation states I'm not certain
on so I moved it to componentDidUpdate and tested with the
live streaming label to ensure the component still animated fine.
2018-11-21 08:08:45 -08:00
Leonard Kim
4409bbabb7 ref(blank-page): destroy local track after mount
To kill componentWillMount, call destroyLocalTrack after mount.
Navigation to the blank page was synthetically forced and no
UI issues were noticed, possibly because destroyLocalTrack may
already be async so destruction may already have been occurring
after mount.
2018-11-21 08:08:45 -08:00
Leonard Kim
d6216f21d5 ref(live-streaming): remove picker state to remove componentWillReceiveProps 2018-11-21 08:08:45 -08:00
Leonard Kim
3a32f7f3f0 ref(audio-picker): fetch audio devices after mount
Per react migration docs, initially fetching external data is
recommended to be done in componentDidMount.
2018-11-21 08:08:45 -08:00
Leonard Kim
d5fb2c2717 ref(sdk): update comments to exclude mention of componentWillReceiveProps 2018-11-21 08:08:45 -08:00
Leonard Kim
c4f1588bb0 ref(dialog): set mounted flag after mount 2018-11-21 08:08:45 -08:00
Leonard Kim
609f3887f2 ref(welcome-page): native creates/destroys camera after mount 2018-11-21 08:08:45 -08:00
Leonard Kim
77f8f85b96 ref(device-selection): update preview tracks on component update 2018-11-21 08:08:45 -08:00
Leonard Kim
14adc0b887 ref(always-on-top): trigger toolbar hide timeout after update 2018-11-21 08:08:45 -08:00
Leonard Kim
c288d0e18c ref(deep-linking): set initial state in constructor 2018-11-21 08:08:45 -08:00
Leonard Kim
eaafc21133 ref(desktop-picker): derive desired types when props change 2018-11-21 08:08:45 -08:00
Leonard Kim
72c1fa38be ref(modal): simplify functional footer passing to remove componentWillUpdate 2018-11-21 08:08:45 -08:00
Leonard Kim
45068f68db ref(welcome-page): use getDerivedStateFromProps, set mounted after actual mount 2018-11-21 08:08:45 -08:00
Leonard Kim
e0cbb838be ref(info): derive when to clear the entered password state 2018-11-21 08:08:45 -08:00
Leonard Kim
c28c70fb2f ref(device-selection): change audio preview listener on component update 2018-11-21 08:08:45 -08:00
Leonard Kim
280178f5d1 ref(info-dialog): derive when to autoshow or autohide 2018-11-21 08:08:45 -08:00
Leonard Kim
e9b2518f8a ref(info): use getDerivedStateFromProps to update state 2018-11-21 08:08:45 -08:00
Leonard Kim
1e3e71c2ff ref(speaker-stats): begin polling for stats after mount 2018-11-21 08:08:45 -08:00
Leonard Kim
007d60eb6c ref(toolbox): getter for the recording/streaming disabled tooltip 2018-11-21 08:08:45 -08:00
Leonard Kim
85f487cca5 ref(large-video): use componentDidUpdate to change background image 2018-11-21 08:08:44 -08:00
Leonard Kim
b24e7ec5f0 ref(labels): use getDerivedStateFromProps to get display state 2018-11-21 08:08:44 -08:00
Leonard Kim
a045353e6e ref(tooltbox): use componentDidUpdate to trigger more changes 2018-11-21 08:08:44 -08:00
Aaron van Meerten
420c466f80 Merge pull request #3612 from jitsi/node_10_build_support
updated node-sass version for node 10
2018-11-20 17:31:12 -06:00
Leonard Kim
7a2c465c4a fix(calendar): show error message if authorization fails on event fetch 2018-11-06 11:56:25 -08:00
784 changed files with 28551 additions and 25870 deletions

View File

@@ -33,7 +33,6 @@
[libs]
node_modules/react-native/Libraries/react-native/react-native-interface.js
node_modules/react-native/flow/
node_modules/react-native/flow-github/
[options]
emoji=true
@@ -83,4 +82,4 @@ module.file_ext=.jsx
module.file_ext=.json
[version]
^0.78.0
^0.92.0

View File

@@ -1,12 +1,13 @@
---
name: Bug Report
about: Before posting, please make sure you check https://community.jitsi.org
name: Bug report
about: Create a report to help us improve
---
*This Issue tracker is only for reporting bugs and tracking code related issues.*
Before posting, please make sure you check community.jitsi.org to see if the same or similar bugs have already been discussed. General questions, installation help, and feature requests can also be posted to community.jitsi.org.
Before posting, please make sure you check community.jitsi.org to see if the same or similar bugs have already been discussed.
General questions, installation help, and feature requests can also be posted to community.jitsi.org.
## Description
---

10
.github/ISSUE_TEMPLATE/2-help.md vendored Normal file
View File

@@ -0,0 +1,10 @@
---
name: Need help with Jitsi Meet?
about: Please ask it in our community at https://community.jitsi.org
---
If you have a question about Jitsi Meet that is not a bug report or feature
request, please post it in https://community.jitsi.org
Questions posted to this repository will be closed.

View File

@@ -0,0 +1,23 @@
---
name: "Feature request"
about: Suggest an idea for this project
---
<!--
Thank you for suggesting an idea to make Jitsi Meet better.
Please fill in as much of the template below as you're able.
Note that the ultimate decission for implementing features lies on the Jitsi team, not all feature requests shall be accepted.
-->
**Is your feature request related to a problem you are facing?**
Please describe the problem you are trying to solve.
**Describe the solution you'd like**
Please describe the desired behavior.
**Describe alternatives you've considered**
Please describe alternative solutions or features you have considered.

16
.github/stale.yml vendored Normal file
View File

@@ -0,0 +1,16 @@
# Number of days of inactivity before an issue becomes stale
daysUntilStale: 90
# Number of days of inactivity before a stale issue is closed
daysUntilClose: 7
# Issues with these labels will never be considered stale
exemptLabels:
- confirmed
staleLabel: wontfix
# Comment to post when marking an issue as stale. Set to `false` to disable
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions.
# Comment to post when closing a stale issue. Set to `false` to disable
closeComment: false

15
.gitignore vendored
View File

@@ -35,6 +35,7 @@ xcuserdata
DerivedData
*.hmap
*.ipa
*.dSYM.zip
*.xcuserstate
project.xcworkspace
@@ -69,10 +70,22 @@ buck-out/
*/fastlane/Preview.html
*/fastlane/screenshots
# Bundle artifact
# Build artifacts
*.jsbundle
*.framework
android/app/release
# precommit-hook
.jshintignore
.jshintrc
# VSCode files
android/.project
android/.settings/org.eclipse.buildship.core.prefs
# Secrets
android/app/dropbox.key
android/app/google-services.json
ios/app/dropbox.key
ios/app/GoogleService-Info.plist

View File

@@ -13,7 +13,7 @@ Found a bug and know how to fix it? Great! Please read on.
## Contributor License Agreement
While the Jitsi projects are released under the
[Apache License 2.0](https://github.com/jitsi/jitsi-meet/blob/master/LICENSE), the copyright
holder and principal creator is [Atlassian](https://www.atlassian.com/). To
holder and principal creator is [8x8](https://www.8x8.com/). To
ensure that we can continue making these projects available under an Open Source license,
we need you to sign our Apache-based contributor
license agreement as either a [corporation](https://jitsi.org/ccla) or an

View File

@@ -43,6 +43,8 @@ deploy-appbundle:
$(BUILD_DIR)/alwaysontop.min.js \
$(BUILD_DIR)/alwaysontop.min.map \
$(OUTPUT_DIR)/analytics-ga.js \
$(BUILD_DIR)/analytics-ga.min.js \
$(BUILD_DIR)/analytics-ga.min.map \
$(DEPLOY_DIR)
deploy-lib-jitsi-meet:

View File

@@ -1,16 +1,16 @@
# Jitsi Meet - Secure, Simple and Scalable Video Conferences
Jitsi Meet is an open-source (Apache) WebRTC JavaScript application that uses [Jitsi Videobridge](https://jitsi.org/videobridge) to provide high quality, [secure](#security) and scalable video conferences. You can see Jitsi Meet in action [here at the session #482 of the VoIP Users Conference](http://youtu.be/7vFUVClsNh0).
Jitsi Meet is an open-source (Apache) WebRTC JavaScript application that uses [Jitsi Videobridge](https://jitsi.org/videobridge) to provide high quality, [secure](#security) and scalable video conferences. Jitsi Meet in action can be seen at [here at the session #482 of the VoIP Users Conference](http://youtu.be/7vFUVClsNh0).
The Jitsi Meet client runs in your browser, without the need for installing anything on your computer. You can also try it out yourself at https://meet.jit.si .
The Jitsi Meet client runs in your browser, without installing anything on your computer. You can try it out at https://meet.jit.si .
Jitsi Meet allows for very efficient collaboration. It allows users to stream their desktop or only some windows. It also supports shared document editing with Etherpad.
Jitsi Meet allows very efficient collaboration. Users can stream their desktop or only some windows. It also supports shared document editing with Etherpad.
## Installation
On the client side, no installation is necessary. You just point your browser to the URL of your deployment. This section is about installing the Jitsi Meet suite on your server and hosting your own conferencing service.
On the client side, no installation is necessary. You just point your browser to the URL of your deployment. This section is about installing a Jitsi Meet suite on your server and hosting your own conferencing service.
Installing Jitsi Meet is quite a simple experience. For Debian-based systems, we recommend following the [quick-install](https://github.com/jitsi/jitsi-meet/blob/master/doc/quick-install.md) document, which uses the package system. You can also see a demonstration of the process in [this tutorial video](https://jitsi.org/tutorial).
Installing Jitsi Meet is a simple experience. For Debian-based system, following the [quick-install](https://github.com/jitsi/jitsi-meet/blob/master/doc/quick-install.md) document, which uses the package system. You can also see a demonstration of the process in [this tutorial video](https://jitsi.org/tutorial).
For other systems, or if you wish to install all components manually, see the [detailed manual installation instructions](https://github.com/jitsi/jitsi-meet/blob/master/doc/manual-install.md).
@@ -59,7 +59,7 @@ To work with local copy you must change the path to:
"lib-jitsi-meet": "file:///Users/name/local-lib-jitsi-meet-copy",
```
To make the project you must force it to take the sources as 'npm update' will not do it.
To make the project you must force it to take the sources as 'npm update':
```
npm install lib-jitsi-meet --force && make
```
@@ -84,8 +84,8 @@ cd ../jitsi-meet
npm link lib-jitsi-meet
```
So now after changes in local `lib-jitsi-meet` repository you can rebuild it with `npm run install` and your `jitsi-meet` repository will use that modified library.
Note: when using node version 4.x, the make file of jitsi-meet do npm update which will delete the link, no longer the case with version 6.x.
After changes in local `lib-jitsi-meet` repository, you can rebuild it with `npm run install` and your `jitsi-meet` repository will use that modified library.
Note: when using node version 4.x, the make file of jitsi-meet do npm update which will delete the link. It is no longer the case with version 6.x.
If you do not want to use local repository anymore you should run
```bash
@@ -100,7 +100,7 @@ Use it at the CLI, type
make dev
```
By default the backend deployment used is `beta.meet.jit.si`, you can point the Jitsi-Meet app at a different backend by using a proxy server. To do this set the WEBPACK_DEV_SERVER_PROXY_TARGET variable:
By default the backend deployment used is `beta.meet.jit.si`. You can point the Jitsi-Meet app at a different backend by using a proxy server. To do this, set the WEBPACK_DEV_SERVER_PROXY_TARGET variable:
```
export WEBPACK_DEV_SERVER_PROXY_TARGET=https://your-example-server.com
make dev
@@ -115,18 +115,16 @@ see our [guidelines for contributing](CONTRIBUTING.md).
## Embedding in external applications
Jitsi Meet provides a very flexible way of embedding it in external applications by using the [Jitsi Meet API](doc/api.md).
Jitsi Meet provides a very flexible way of embedding in external applications by using the [Jitsi Meet API](doc/api.md).
## Security
WebRTC today does not provide a way of conducting multiparty conversations with
end-to-end encryption. As a matter of fact, unless you consistently vocally
compare DTLS fingerprints with your peers, the same goes for one-to-one calls.
As a result when using a Jitsi Meet instance, your stream is encrypted on the
network but decrypted on the machine that hosts the bridge.
WebRTC does not provide a way of conducting multi-party conversations with end-to-end encryption.
Unless you consistently compare DTLS fingerprints with your peers vocally, the same goes for one-to-one calls.
As a result, your stream is encrypted on the network but decrypted on the machine that hosts the bridge when using Jitsi Meet.
The Jitsi Meet architecture allows you to deploy your own version, including
all server components, and in that case your security guarantees will be roughly
equivalent to these of a direct one-to-one WebRTC call. This is what's unique to
all server components. In that case, your security guarantees will be roughly
equivalent to a direct one-to-one WebRTC call. This is the uniqueness of
Jitsi Meet in terms of security.
The [meet.jit.si](https://meet.jit.si) service is maintained by the Jitsi team
@@ -138,4 +136,4 @@ Instructions on how to build it can be found [here](doc/mobile.md).
## Acknowledgements
Jitsi Meet started out as a sample conferencing application using Jitsi Videobridge. It was originally developed by then ESTOS' developer Philipp Hancke who then contributed it to the community where development continues with joint forces!
Jitsi Meet started out as a sample conferencing application using Jitsi Videobridge. It was originally developed by ESTOS' developer Philipp Hancke who then contributed it to the community where development continues with joint forces!

View File

@@ -1,9 +1,11 @@
# Jitsi Meet SDK for Android
## Build your own, or use a pre-build SDK artifacts/binaries
Jitsi conveniently provides a pre-build SDK artifacts/binaries in its Maven repository. When you do not require any modification to the SDK itself, it's suggested to use the pre-build SDK. This avoids the complexity of building and installing your own SDK artifacts/binaries.
### Use pre-build SDK artifacts/binaries
In your project, add the Maven repository
`https://github.com/jitsi/jitsi-maven-repository/raw/master/releases` and the
dependency `org.jitsi.react:jitsi-meet-sdk` into your `build.gradle` files.
@@ -33,80 +35,80 @@ dependencies {
### Build and use your own SDK artifacts/binaries
1. Install all required [dependencies](https://github.com/jitsi/jitsi-meet/blob/master/doc/mobile.md).
<details>
<summary>Show building instructions</summary>
2. Create the SDK-release assembly, by invoking the following in the jitsi-meet
project source:
Start by making sure that your development environment [is set up correctly](https://github.com/jitsi/jitsi-meet/blob/master/doc/mobile.md).
```bash
cd android/
./gradlew :sdk:assembleRelease
```
When this successfully executes, artifacts/binaries are ready to be published
into a Maven repository of your choice.
A note on dependencies: Apart from the SDK, Jitsi also publishes a binary Maven artifact for some of the SDK dependencies (that are not otherwise publicly available) to the Jitsi Maven repository. When you're planning to use a SDK that is built from source, you'll likely use a version of the source code that is newer (or at least _different_) than the version of the source that was used to create the binary SDK artifact. As a consequence, the dependencies that your project will need, might also be different from those that are published in the Jitsi Maven repository. This might lead to build problems, caused by dependencies that are unavailable.
3. Configure the Maven repositories in which you are going to publish the
artifacts/binaries during step 4.
If you want to use a SDK that is built from source, you will likely benefit from composing a local Maven repository that contains these dependencies. The text below describes how you create a repository that includes both the SDK as well as these dependencies. For illustration purposes, we'll define the location of this local Maven repository as `/tmp/repo`
In the file `android/sdk/build.gradle` modify the line that contains
`"file:${rootProject.projectDir}/../../../jitsi/jitsi-maven-repository/releases"`
In source code form, the Android SDK dependencies are locked/pinned by package.json and package-lock.json of the Jitsi Meet project. To obtain the data, execute NPM in the parent directory:
Change this value (which represents the Maven repository location used internally
by the Jitsi Developers) to the location of the repository that you'd like to use.
$ (cd ..; npm install)
4. Publish the Maven artifact/binary of Jitsi Meet SDK for Android in the Maven
repository configured in step 3:
This will pull in the dependencies in either binary format, or in source code format, somewhere under /node_modules/
```bash
./gradlew :sdk:publish
cd ../
```
5. In _your_ project, add the Maven repository that you configured in step 3, as well
as the dependency `org.jitsi.react:jitsi-meet-sdk` into your `build.gradle`
file. Note that it's needed to pull in the transitive dependencies:
At the time of writing, there are two packages pulled in in binary format.
```gradle
implementation ('org.jitsi.react:jitsi-meet-sdk:+') { transitive = true }
```
To copy React Native to your local Maven repository, you can simply copy part of the directory structure that was pulled in by NPM:
Generally, if you are modifying the JavaScript code of Jitsi Meet SDK for Android only,
the above will suffice. If you would like to publish a third-party react-native module
which Jitsi Meet SDK for Android depends on (and is not publicly available in Maven
repositories) continue below.
$ cp -r ../node_modules/react-native/android/com /tmp/repo/
6. Create the release assembly for _each_ third-party react-native module that you
need, replacing it's name in the example below.
Alternatively, you can use the scripts located in the android/scripts directory to publish these dependencies to your Maven repo.
```bash
./gradlew :react-native-webrtc:assembleRelease
```
Third-party React Native _modules_, which Jitsi Meet SDK for Android depends on, are download by NPM in source code form. These need to be assembled into Maven artifacts, and then published to your local Maven repository. The SDK project facilitates this.
7. Configure the Maven repositories in which you are going to publish the
artifacts/binaries during step 8.
To prepare, Configure the Maven repositories in which you are going to publish the SDK artifacts/binaries. In `android/sdk/build.gradle` as well as in `android/build.gradle` modify the lines that contain:
In the file `android/build.gradle` (note that this is a different file than the file
that was modified in step 3) modify the line that contains
`"file:${rootProject.projectDir}/../../../jitsi/jitsi-maven-repository/releases"`
"file:${rootProject.projectDir}/../../jitsi-maven-repository/releases"
Change this value (which represents the Maven repository location used internally
by the Jitsi Developers) to the location of the repository that you'd like to use.
You can use the same repository as the one you configured in step 3 if you want.
Change this value (which represents the Maven repository location used internally by the Jitsi Developers) to the location of the repository that you'd like to use:
8. Publish the Maven artifact/binary of _each_ third-party react-native module that
you need, replacing it's name in the example below. For example, to publish
react-native-webrtc:
"file:/tmp/repo"
```bash
./gradlew :react-native-webrtc:publish
```
Make sure to do this in both files! Each file should require one line to be changed.
Note that there should not be a need to explicitly add these dependencies in
_your_ project, as they will be pulled in as transitive dependencies of
`jitsi-meet-sdk`.
To prevent artifacts from previous builds affecting you're outcome, it's good to start with cleaning your work directories:
$ ./gradlew clean
To create the release assembly for any _specific_ third-party React Native module that you need, you can execture the following commands, replace the module name in the examples below.
$ ./gradlew :react-native-webrtc:assembleRelease
$ ./gradlew :react-native-webrtc:publish
You build and publish the SDK itself in the same way:
$ ./gradlew :sdk:assembleRelease
$ ./gradlew :sdk:publish
Alternatively, you can assemble and publish _all_ subprojects, which include the react-native modules, but also the SDK itself, with a single command:
$ ./gradlew clean assembleRelease publish
You're now ready to use the artifacts. In _your_ project, add the Maven repository that you used above (`/tmp/repo`) into your top-level `build.gradle` file:
allprojects {
repositories {
maven { url "file:/tmp/repo" }
google()
jcenter()
}
}
You can use your local repository to replace the Jitsi repository (`maven { url "https://github.com/jitsi/jitsi-maven-repository/raw/master/releases" }`) when you published _all_ subprojects. If you didn't do that, you'll have to add both repositories. Make sure your local repository is listed first!
Then, define the dependency `org.jitsi.react:jitsi-meet-sdk` into the `build.gradle` file of your module:
implementation ('org.jitsi.react:jitsi-meet-sdk:+') { transitive = true }
Note that there should not be a need to explicitly add the other dependencies, as they will be pulled in as transitive dependencies of `jitsi-meet-sdk`.
</details>
## Using the API
=======
Jitsi Meet SDK is an Android library which embodies the whole Jitsi Meet
experience and makes it reusable by third-party apps.
@@ -134,25 +136,42 @@ public class MainActivity extends JitsiMeetActivity {
```
Alternatively, you can use the `org.jitsi.meet.sdk.JitsiMeetView` class which
extends `android.view.View`:
extends `android.view.View`.
Note that this should only be needed when `JitsiMeetActivity` cannot be used for
some reason. Extending `JitsiMeetView` requires manual wiring of the view to
the activity, using a lot of boilerplate code. Using the Activity instead of the
View is strongly recommended.
<details>
<summary>Show example</summary>
```java
package org.jitsi.example;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v4.app.FragmentActivity;
import org.jitsi.meet.sdk.JitsiMeetView;
import org.jitsi.meet.sdk.ReactActivityLifecycleCallbacks;
public class MainActivity extends AppCompatActivity {
// Example
//
public class MainActivity extends FragmentActivity implements JitsiMeetActivityInterface {
private JitsiMeetView view;
@Override
protected void onActivityResult(
int requestCode,
int resultCode,
Intent data) {
JitsiMeetActivityDelegate.onActivityResult(
this, requestCode, resultCode, data);
}
@Override
public void onBackPressed() {
if (!JitsiMeetView.onBackPressed()) {
// Invoke the default handler if it wasn't handled by React.
super.onBackPressed();
}
JitsiMeetActivityDelegate.onBackPressed();
}
@Override
@@ -160,7 +179,10 @@ public class MainActivity extends AppCompatActivity {
super.onCreate(savedInstanceState);
view = new JitsiMeetView(this);
view.loadURL(null);
JitsiMeetConferenceOptions options = new JitsiMeetConferenceOptions.Builder()
.setRoom("https://meet.jit.si/test123")
.build();
view.join(options);
setContentView(view);
}
@@ -172,155 +194,127 @@ public class MainActivity extends AppCompatActivity {
view.dispose();
view = null;
JitsiMeetView.onHostDestroy(this);
JitsiMeetActivityDelegate.onHostDestroy(this);
}
@Override
public void onNewIntent(Intent intent) {
JitsiMeetView.onNewIntent(intent);
JitsiMeetActivityDelegate.onNewIntent(intent);
}
@Override
public void onRequestPermissionsResult(
final int requestCode,
final String[] permissions,
final int[] grantResults) {
JitsiMeetActivityDelegate.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
@Override
protected void onResume() {
super.onResume();
JitsiMeetView.onHostResume(this);
JitsiMeetActivityDelegate.onHostResume(this);
}
@Override
protected void onStop() {
super.onStop();
JitsiMeetView.onHostPause(this);
JitsiMeetActivityDelegate.onHostPause(this);
}
}
```
</details>
Starting with SDK version 1.22, a Glide module must be provided by the host app.
This makes it possible to use the Glide image processing library from both the
SDK and the host app itself.
You can use the code in `JitsiGlideModule.java` and adjust the package name.
When building, add the following code in your `app/build.gradle` file, adjusting
the Glide version to match the one in https://github.com/jitsi/jitsi-meet/blob/master/android/build.gradle
```
// Glide
implementation("com.github.bumptech.glide:glide:${glideVersion}") {
exclude group: "com.android.support", module: "glide"
}
implementation("com.github.bumptech.glide:annotations:${glideVersion}") {
exclude group: "com.android.support", module: "annotations"
}
```
### JitsiMeetActivity
This class encapsulates a high level API in the form of an Android `Activity`
which displays a single `JitsiMeetView`.
#### getDefaultURL()
See JitsiMeetView.getDefaultURL.
#### isPictureInPictureEnabled()
See JitsiMeetView.isPictureInPictureEnabled.
#### isWelcomePageEnabled()
See JitsiMeetView.isWelcomePageEnabled.
#### loadURL(URL)
See JitsiMeetView.loadURL.
#### setDefaultURL(URL)
See JitsiMeetView.setDefaultURL.
#### setPictureInPictureEnabled(boolean)
See JitsiMeetView.setPictureInPictureEnabled.
#### setWelcomePageEnabled(boolean)
See JitsiMeetView.setWelcomePageEnabled.
This class encapsulates a high level API in the form of an Android `FragmentActivity`
which displays a single `JitsiMeetView`. You can pass a URL as a `ACTION_VIEW`
on the Intent when starting it and it will join the conference, and will be
automatically terminated (finish() will be called on the activity) when the
conference ends or fails.
### JitsiMeetView
The `JitsiMeetView` class is the core of Jitsi Meet SDK. It's designed to
display a Jitsi Meet conference (or a welcome page).
#### join(options)
Joins the conference specified by the given `JitsiMeetConferenceOptions`.
#### leave()
Leaves the currently active conference. If the welcome page is enabled it will
go back to it, otherwise a black window will be shown.
#### dispose()
Releases all resources associated with this view. This method MUST be called
when the Activity holding this view is going to be destroyed, usually in the
`onDestroy()` method.
#### getDefaultURL()
Returns the default base URL used to join a conference when a partial URL (e.g.
a room name only) is specified to `loadURLString`/`loadURLObject`. If not set or
if set to `null`, the default built in JavaScript is used: https://meet.jit.si.
#### getListener()
Returns the `JitsiMeetViewListener` instance attached to the view.
#### isPictureInPictureEnabled()
Returns `true` if Picture-in-Picture is enabled; `false`, otherwise. If not
explicitly set (by a preceding `setPictureInPictureEnabled` call), defaults to
`true` if the platform supports Picture-in-Picture natively; `false`, otherwise.
#### isWelcomePageEnabled()
Returns true if the Welcome page is enabled; otherwise, false. If false, a black
empty view will be rendered when not in a conference. Defaults to false.
#### loadURL(URL)
Loads a specific URL which may identify a conference to join. If the specified
URL is null and the Welcome page is enabled, the Welcome page is displayed
instead.
#### loadURLString(String)
Loads a specific URL which may identify a conference to join. If the specified
URL is null and the Welcome page is enabled, the Welcome page is displayed
instead.
#### loadURLObject(Bundle)
Loads a specific URL which may identify a conference to join. The URL is
specified in the form of a Bundle of properties which (1) internally are
sufficient to construct a URL (string) while (2) abstracting the specifics of
constructing the URL away from API clients/consumers. If the specified URL is
null and the Welcome page is enabled, the Welcome page is displayed instead.
Example:
```java
Bundle config = new Bundle();
config.putBoolean("startWithAudioMuted", true);
config.putBoolean("startWithVideoMuted", false);
Bundle urlObject = new Bundle();
urlObject.putBundle("config", config);
urlObject.putString("url", "https://meet.jit.si/Test123");
view.loadURLObject(urlObject);
```
#### setDefaultURL(URL)
Sets the default URL. See `getDefaultURL` for more information.
NOTE: Must be called before (if at all) `loadURL`/`loadURLString` for it to take
effect.
#### setListener(listener)
Sets the given listener (class implementing the `JitsiMeetViewListener`
interface) on the view.
#### setPictureInPictureEnabled(boolean)
### JitsiMeetConferenceOptions
Sets whether Picture-in-Picture is enabled. If not set, Jitsi Meet SDK
automatically enables/disables Picture-in-Picture based on native platform
support.
This object encapsulates all the options that can be tweaked when joining
a conference.
NOTE: Must be called (if at all) before `loadURL`/`loadURLString` for it to take
effect.
Example:
#### setWelcomePageEnabled(boolean)
```java
JitsiMeetConferenceOptions options = new JitsiMeetConferenceOptions.Builder()
.setServerURL(new URL("https://meet.jit.si"))
.setRoom("test123")
.setAudioMuted(false)
.setVideoMuted(false)
.setAudioOnly(false)
.setWelcomePageEnabled(false)
.build();
```
Sets whether the Welcome page is enabled. See `isWelcomePageEnabled` for more
information.
See the `JitsiMeetConferenceOptions` implementation for all available options.
NOTE: Must be called (if at all) before `loadURL`/`loadURLString` for it to take
effect.
### JitsiMeetActivityDelegate
This class handles the interaction between `JitsiMeetView` and its enclosing
`Activity`. Generally this shouldn't be consumed by users, because they'd be
using `JitsiMeetActivity` instead, which is already completely integrated.
All its methods are static.
#### onActivityResult(...)
Helper method to handle results of auxiliary activities launched by the SDK.
Should be called from the activity method of the same name.
#### onBackPressed()
@@ -329,34 +323,29 @@ If this function returns `true`, it means the action was handled and thus no
extra processing is required; otherwise the app should call the parent's
`onBackPressed` method.
This is a static method.
#### onHostDestroy(activity)
#### onHostDestroy(...)
Helper method which should be called from the activity's `onDestroy` method.
This is a static method.
#### onHostPause(activity)
Helper method which should be called from the activity's `onPause` method.
This is a static method.
#### onHostResume(activity)
#### onHostResume(...)
Helper method which should be called from the activity's `onResume` or `onStop`
method.
This is a static method.
#### onHostStop(...)
#### onNewIntent(intent)
Helper method which should be called from the activity's `onSstop` method.
#### onNewIntent(...)
Helper method for integrating the *deep linking* functionality. If your app's
activity is launched in "singleTask" mode this method should be called from the
activity's `onNewIntent` method.
This is a static method.
#### onRequestPermissionsResult(...)
Helper method to handle permission requests inside the SDK. It should be called
from the activity method of the same name.
#### onUserLeaveHint()
@@ -370,31 +359,20 @@ This is a static method.
`JitsiMeetViewListener` provides an interface apps can implement to listen to
the state of the Jitsi Meet conference displayed in a `JitsiMeetView`.
### JitsiMeetViewAdapter
A default implementation of the `JitsiMeetViewListener` interface. Apps may
extend the class instead of implementing the interface in order to minimize
boilerplate.
##### onConferenceFailed
Called when a joining a conference was unsuccessful or when there was an error
while in a conference.
The `data` `Map` contains an "error" key describing the error and a "url" key
with the conference URL.
#### onConferenceJoined
Called when a conference was joined.
The `data` `Map` contains a "url" key with the conference URL.
#### onConferenceLeft
#### onConferenceTerminated
Called when a conference was left.
Called when a conference was terminated either by user choice or due to a
failure.
The `data` `Map` contains a "url" key with the conference URL.
The `data` `Map` contains an "error" key with the error and a "url" key
with the conference URL. If the conference finished gracefully no `error`
key will be present.
#### onConferenceWillJoin
@@ -402,85 +380,11 @@ Called before a conference is joined.
The `data` `Map` contains a "url" key with the conference URL.
#### onConferenceWillLeave
Called before a conference is left.
The `data` `Map` contains a "url" key with the conference URL.
#### onLoadConfigError
Called when loading the main configuration file from the Jitsi Meet deployment
fails.
The `data` `Map` contains an "error" key with the error and a "url" key with the
conference URL which necessitated the loading of the configuration file.
## ProGuard rules
When using the SDK on a project some proguard rules have to be added in order
to avoid necessary code being stripped. Add the following to your project's
rules file:
```
# React Native
# Keep our interfaces so they can be used by other ProGuard rules.
# See http://sourceforge.net/p/proguard/bugs/466/
-keep,allowobfuscation @interface com.facebook.proguard.annotations.DoNotStrip
-keep,allowobfuscation @interface com.facebook.proguard.annotations.KeepGettersAndSetters
-keep,allowobfuscation @interface com.facebook.common.internal.DoNotStrip
# Do not strip any method/class that is annotated with @DoNotStrip
-keep @com.facebook.proguard.annotations.DoNotStrip class *
-keep @com.facebook.common.internal.DoNotStrip class *
-keepclassmembers class * {
@com.facebook.proguard.annotations.DoNotStrip *;
@com.facebook.common.internal.DoNotStrip *;
}
-keepclassmembers @com.facebook.proguard.annotations.KeepGettersAndSetters class * {
void set*(***);
*** get*();
}
-keep class * extends com.facebook.react.bridge.JavaScriptModule { *; }
-keep class * extends com.facebook.react.bridge.NativeModule { *; }
-keepclassmembers,includedescriptorclasses class * { native <methods>; }
-keepclassmembers class * { @com.facebook.react.uimanager.UIProp <fields>; }
-keepclassmembers class * { @com.facebook.react.uimanager.annotations.ReactProp <methods>; }
-keepclassmembers class * { @com.facebook.react.uimanager.annotations.ReactPropGroup <methods>; }
-dontwarn com.facebook.react.**
# TextLayoutBuilder uses a non-public Android constructor within StaticLayout.
# See libs/proxy/src/main/java/com/facebook/fbui/textlayoutbuilder/proxy for details.
-dontwarn android.text.StaticLayout
# okhttp
-keepattributes Signature
-keepattributes *Annotation*
-keep class okhttp3.** { *; }
-keep interface okhttp3.** { *; }
-dontwarn okhttp3.**
# okio
-keep class sun.misc.Unsafe { *; }
-dontwarn java.nio.file.*
-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
-dontwarn okio.**
# WebRTC
-keep class org.webrtc.** { *; }
-dontwarn org.chromium.build.BuildHooksAndroid
# Jisti Meet SDK
-keep class org.jitsi.meet.sdk.** { *; }
```
rules file: https://github.com/jitsi/jitsi-meet/blob/master/android/app/proguard-rules.pro
## Picture-in-Picture
@@ -488,10 +392,6 @@ rules file:
Picture-in-Picture style scenario, in a rectangle too small to accommodate its
"full" UI.
Jitsi Meet SDK automatically enables (unless explicitly disabled by a
`setPictureInPictureEnabled(false)` call) Android's native Picture-in-Picture
mode iff the platform is supported i.e. Android >= Oreo.
## Dropbox integration
To setup the Dropbox integration, follow these steps:

View File

@@ -1,29 +1,33 @@
apply plugin: 'com.android.application'
boolean googleServicesEnabled = project.file('google-services.json').exists()
// Crashlytics integration is done as part of Firebase now, so it gets
// automagically activated with google-services.json
if (googleServicesEnabled) {
apply plugin: 'io.fabric'
}
// Use the number of seconds/10 since Jan 1 2019 as the versionCode.
// This lets us upload a new build at most every 10 seconds for the
// next ~680 years.
// https://stackoverflow.com/a/38643838
def vcode = (int)(((new Date().getTime()/1000) - 1546297200) / 10)
android {
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
defaultConfig {
applicationId 'org.jitsi.meet'
versionCode Integer.parseInt("${version}")
versionName "1.9.${version}"
versionCode vcode
versionName project.appVersion
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
ndk {
abiFilters 'armeabi-v7a', 'x86'
}
packagingOptions {
// The project react-native does not provide 64-bit binaries at the
// time of this writing. Unfortunately, packaging any 64-bit
// binaries into the .apk will crash the app at runtime on 64-bit
// platforms.
exclude '/lib/mips64/**'
exclude '/lib/arm64-v8a/**'
exclude '/lib/x86_64/**'
abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
}
}
@@ -31,10 +35,12 @@ android {
debug {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules-debug.pro'
buildConfigField "boolean", "GOOGLE_SERVICES_ENABLED", "${googleServicesEnabled}"
}
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules-release.pro'
buildConfigField "boolean", "GOOGLE_SERVICES_ENABLED", "${googleServicesEnabled}"
}
}
@@ -44,34 +50,46 @@ android {
}
}
repositories {
maven { url 'https://maven.fabric.io/public' }
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "com.android.support:support-v4:${rootProject.ext.supportLibVersion}"
implementation "com.android.support:appcompat-v7:${rootProject.ext.supportLibVersion}"
implementation 'com.google.android.gms:play-services-auth:15.0.0'
implementation 'com.google.android.gms:play-services-auth:16.0.1'
implementation project(':sdk')
debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.1'
releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.1'
// Glide
implementation("com.github.bumptech.glide:glide:${rootProject.ext.glideVersion}") {
exclude group: "com.android.support", module: "glide"
}
implementation("com.github.bumptech.glide:annotations:${rootProject.ext.glideVersion}") {
exclude group: "com.android.support", module: "annotations"
}
annotationProcessor "com.github.bumptech.glide:compiler:${rootProject.ext.glideVersion}"
// Firebase
// - Crashlytics
// - Dynamic Links
implementation 'com.google.firebase:firebase-core:16.0.6'
implementation 'com.crashlytics.sdk.android:crashlytics:2.9.8'
implementation 'com.google.firebase:firebase-dynamic-links:16.1.5'
}
gradle.projectsEvaluated {
// Dropbox integration
//
def plistParser = new XmlSlurper(
/* validating */ false,
/* namespaceAware */ false,
/* allowDocTypeDeclaration */ true)
plistParser.setFeature(
'http://apache.org/xml/features/nonvalidating/load-external-dtd',
false)
def plist = plistParser.parse('../ios/app/src/Info.plist')
def dropboxScheme = plist.dict.array.dict.array.string.find { string ->
string.text().startsWith('db-')
def dropboxAppKey
if (project.file('dropbox.key').exists()) {
dropboxAppKey = project.file('dropbox.key').text.trim() - 'db-'
}
def dropboxAppKey = dropboxScheme?.text() - 'db-'
if (dropboxAppKey) {
android.defaultConfig.resValue('string', 'dropbox_app_key', "${dropboxAppKey}")
@@ -87,27 +105,59 @@ gradle.projectsEvaluated {
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="db-${dropboxAppKey}" />
</intent-filter>
</activity>""";
</activity>"""
android.applicationVariants.all { variant ->
variant.outputs.each { output ->
output.processManifest.doLast {
def f = new File(manifestOutputDirectory, 'AndroidManifest.xml')
if (!f.isFile()) {
f = new File(new File(manifestOutputDirectory, output.dirName), 'AndroidManifest.xml')
}
if (f.exists()) {
def charset = 'UTF-8'
def s = f.getText(charset)
s = s.replace('</application>', "${dropboxActivity}</application>")
f.write(s, charset)
}
output.getProcessManifestProvider().get().doLast {
def outputDir = manifestOutputDirectory.get().asFile
def manifestPath = new File(outputDir, 'AndroidManifest.xml')
def charset = 'UTF-8'
def text
text = manifestPath.getText(charset)
text = text.replace('</application>', "${dropboxActivity}</application>")
manifestPath.write(text, charset)
}
}
}
}
// Run React packager
android.applicationVariants.all { variant ->
def targetName = variant.name.capitalize()
def currentRunPackagerTask = tasks.create(
name: "run${targetName}ReactPackager",
type: Exec) {
group = "react"
description = "Run the React packager."
doFirst {
println "Starting the React packager..."
def androidRoot = file("${projectDir}/../")
// Set up the call to the script
workingDir androidRoot
// Run the packager
commandLine("scripts/run-packager.sh")
}
// Set up dev mode
def devEnabled = !targetName.toLowerCase().contains("release")
// Only enable for dev builds
enabled devEnabled
}
def packageTask = variant.packageApplicationProvider.get()
packageTask.dependsOn(currentRunPackagerTask)
}
}
if (project.file('google-services.json').exists()) {
if (googleServicesEnabled) {
apply plugin: 'com.google.gms.google-services'
}

View File

@@ -45,10 +45,7 @@
-keepclassmembers class * { @com.facebook.react.uimanager.annotations.ReactPropGroup <methods>; }
-dontwarn com.facebook.react.**
# TextLayoutBuilder uses a non-public Android constructor within StaticLayout.
# See libs/proxy/src/main/java/com/facebook/fbui/textlayoutbuilder/proxy for details.
-dontwarn android.text.StaticLayout
-keep,includedescriptorclasses class com.facebook.react.bridge.** { *; }
# okhttp
@@ -65,9 +62,25 @@
-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
-dontwarn okio.**
# FastImage
# FastImage + Glide
-keep public class com.dylanvann.fastimage.* {*;}
-keep public class com.dylanvann.fastimage.** {*;}
-keep public class * implements com.bumptech.glide.module.GlideModule
-keep public class * extends com.bumptech.glide.module.AppGlideModule
-keep public enum com.bumptech.glide.load.ImageHeaderParser$** {
**[] $VALUES;
public *;
}
# WebRTC
-keep class org.webrtc.** { *; }
-dontwarn org.chromium.build.BuildHooksAndroid
# Jisti Meet SDK
-keep class org.jitsi.meet.sdk.** { *; }
# We added the following when we switched minifyEnabled on. Probably because we
# ran the app and hit problems...
@@ -79,7 +92,6 @@
-keep class com.facebook.react.bridge.ReadableType { *; }
-keep class com.facebook.react.bridge.queue.NativeRunnable { *; }
-keep class com.facebook.react.devsupport.** { *; }
-keep class org.webrtc.** { *; }
-dontwarn com.facebook.react.devsupport.**
-dontwarn com.google.appengine.**

View File

@@ -6,6 +6,7 @@
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:name=".MainApplication"
android:networkSecurityConfig="@xml/network_security_config"
android:theme="@style/AppTheme">
<activity
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize"
@@ -15,6 +16,7 @@
android:resizeableActivity="true"
android:supportsPictureInPicture="true"
android:windowSoftInputMode="adjustResize">
<meta-data android:name="firebase_crashlytics_collection_enabled" android:value="false" />
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
@@ -23,11 +25,7 @@
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:host="beta.hipchat.me" android:scheme="https" />
<data android:host="beta.meet.jit.si" android:scheme="https" />
<data android:host="chaos.hipchat.me" android:scheme="https" />
<data android:host="enso.me" android:scheme="https" />
<data android:host="hipchat.me" android:scheme="https" />
<data android:host="meet.jit.si" android:scheme="https" />
</intent-filter>
<intent-filter>

View File

@@ -0,0 +1,16 @@
package org.jitsi.meet;
import com.bumptech.glide.annotation.GlideModule;
import com.bumptech.glide.module.AppGlideModule;
/**
* An AppGlideModule needs to be present for image loading events to work in
* react-native-fast-image. However, if this is defined by the SDK it will cause trouble with
* apps which are using Glide themselves.
*
* In order to avoid the problem, define a Jitsi Glide module here, so applications already using
* it are not in trouble.
*/
@GlideModule
public final class JitsiGlideModule extends AppGlideModule {
}

View File

@@ -1,5 +1,6 @@
/*
* Copyright @ 2017-present Atlassian Pty Ltd
* Copyright @ 2018-present 8x8, Inc.
* Copyright @ 2017-2018 Atlassian Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,215 +17,143 @@
package org.jitsi.meet;
import android.os.Bundle;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.provider.Settings;
import android.support.annotation.Nullable;
import android.util.Log;
import android.view.KeyEvent;
import org.jitsi.meet.sdk.JitsiMeet;
import org.jitsi.meet.sdk.JitsiMeetActivity;
import org.jitsi.meet.sdk.JitsiMeetView;
import org.jitsi.meet.sdk.JitsiMeetViewListener;
import org.jitsi.meet.sdk.invite.AddPeopleController;
import org.jitsi.meet.sdk.invite.AddPeopleControllerListener;
import org.jitsi.meet.sdk.invite.InviteController;
import org.jitsi.meet.sdk.invite.InviteControllerListener;
import org.jitsi.meet.sdk.JitsiMeetConferenceOptions;
import com.facebook.react.bridge.UiThreadUtil;
import com.crashlytics.android.Crashlytics;
import com.google.firebase.dynamiclinks.FirebaseDynamicLinks;
import io.fabric.sdk.android.Fabric;
import java.util.ArrayList;
import java.util.List;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Map;
/**
* The one and only {@link Activity} that the Jitsi Meet app needs. The
* The one and only Activity that the Jitsi Meet app needs. The
* {@code Activity} is launched in {@code singleTask} mode, so it will be
* created upon application initialization and there will be a single instance
* of it. Further attempts at launching the application once it was already
* launched will result in {@link Activity#onNewIntent(Intent)} being called.
*
* This {@code Activity} extends {@link JitsiMeetActivity} to keep the React
* Native CLI working, since the latter always tries to launch an
* {@code Activity} named {@code MainActivity} when doing
* {@code react-native run-android}.
* launched will result in {@link MainActivity#onNewIntent(Intent)} being called.
*/
public class MainActivity extends JitsiMeetActivity {
/**
* The query to perform through {@link AddPeopleController} when the
* {@code InviteButton} is tapped in order to exercise the public API of the
* feature invite. If {@code null}, the {@code InviteButton} will not be
* rendered.
* The request code identifying requests for the permission to draw on top
* of other apps. The value must be 16-bit and is arbitrarily chosen here.
*/
private static final String ADD_PEOPLE_CONTROLLER_QUERY = null;
private static final int OVERLAY_PERMISSION_REQUEST_CODE
= (int) (Math.random() * Short.MAX_VALUE);
// JitsiMeetActivity overrides
//
@Override
protected JitsiMeetView initializeView() {
JitsiMeetView view = super.initializeView();
protected boolean extraInitialize() {
// Setup Crashlytics and Firebase Dynamic Links
if (BuildConfig.GOOGLE_SERVICES_ENABLED) {
Fabric.with(this, new Crashlytics());
// XXX In order to increase (1) awareness of API breakages and (2) API
// coverage, utilize JitsiMeetViewListener in the Debug configuration of
// the app.
if (BuildConfig.DEBUG && view != null) {
view.setListener(new JitsiMeetViewListener() {
private void on(String name, Map<String, Object> data) {
UiThreadUtil.assertOnUiThread();
FirebaseDynamicLinks.getInstance().getDynamicLink(getIntent())
.addOnSuccessListener(this, pendingDynamicLinkData -> {
Uri dynamicLink = null;
// Log with the tag "ReactNative" in order to have the log
// visible in react-native log-android as well.
Log.d(
"ReactNative",
JitsiMeetViewListener.class.getSimpleName() + " "
+ name + " "
+ data);
}
if (pendingDynamicLinkData != null) {
dynamicLink = pendingDynamicLinkData.getLink();
}
@Override
public void onConferenceFailed(Map<String, Object> data) {
on("CONFERENCE_FAILED", data);
}
@Override
public void onConferenceJoined(Map<String, Object> data) {
on("CONFERENCE_JOINED", data);
}
@Override
public void onConferenceLeft(Map<String, Object> data) {
on("CONFERENCE_LEFT", data);
}
@Override
public void onConferenceWillJoin(Map<String, Object> data) {
on("CONFERENCE_WILL_JOIN", data);
}
@Override
public void onConferenceWillLeave(Map<String, Object> data) {
on("CONFERENCE_WILL_LEAVE", data);
}
@Override
public void onLoadConfigError(Map<String, Object> data) {
on("LOAD_CONFIG_ERROR", data);
}
});
// inviteController
final InviteController inviteController
= view.getInviteController();
inviteController.setListener(new InviteControllerListener() {
public void beginAddPeople(
AddPeopleController addPeopleController) {
onInviteControllerBeginAddPeople(
inviteController,
addPeopleController);
}
});
inviteController.setAddPeopleEnabled(
ADD_PEOPLE_CONTROLLER_QUERY != null);
inviteController.setDialOutEnabled(
inviteController.isAddPeopleEnabled());
if (dynamicLink != null) {
join(dynamicLink.toString());
}
});
}
return view;
// In Debug builds React needs permission to write over other apps in
// order to display the warning and error overlays.
if (BuildConfig.DEBUG) {
if (canRequestOverlayPermission() && !Settings.canDrawOverlays(this)) {
Intent intent
= new Intent(
Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
Uri.parse("package:" + getPackageName()));
startActivityForResult(intent, OVERLAY_PERMISSION_REQUEST_CODE);
return true;
}
}
return false;
}
private void onAddPeopleControllerInviteSettled(
AddPeopleController addPeopleController,
List<Map<String, Object>> failedInvitees) {
UiThreadUtil.assertOnUiThread();
@Override
protected void initialize() {
// Set default options
JitsiMeetConferenceOptions defaultOptions
= new JitsiMeetConferenceOptions.Builder()
.setWelcomePageEnabled(true)
.setServerURL(buildURL("https://meet.jit.si"))
.build();
JitsiMeet.setDefaultConferenceOptions(defaultOptions);
// XXX Explicitly invoke endAddPeople on addPeopleController; otherwise,
// it is going to be memory-leaked in the associated InviteController
// and no subsequent InviteButton clicks/taps will be delivered.
// Technically, endAddPeople will automatically be invoked if there are
// no failedInviteees i.e. the invite succeeeded for all specified
// invitees.
addPeopleController.endAddPeople();
super.initialize();
}
private void onAddPeopleControllerReceivedResults(
AddPeopleController addPeopleController,
List<Map<String, Object>> results,
String query) {
UiThreadUtil.assertOnUiThread();
@Override
public void onConferenceTerminated(Map<String, Object> data) {
Log.d(TAG, "Conference terminated: " + data);
}
int size = results.size();
// Activity lifecycle method overrides
//
if (size > 0) {
// Exercise AddPeopleController's inviteById implementation.
List<String> ids = new ArrayList<>(size);
for (Map<String, Object> result : results) {
Object id = result.get("id");
if (id != null) {
ids.add(id.toString());
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == OVERLAY_PERMISSION_REQUEST_CODE
&& canRequestOverlayPermission()) {
if (Settings.canDrawOverlays(this)) {
initialize();
return;
}
addPeopleController.inviteById(ids);
return;
throw new RuntimeException("Overlay permission is required when running in Debug mode.");
}
// XXX Explicitly invoke endAddPeople on addPeopleController; otherwise,
// it is going to be memory-leaked in the associated InviteController
// and no subsequent InviteButton clicks/taps will be delivered.
addPeopleController.endAddPeople();
super.onActivityResult(requestCode, resultCode, data);
}
// ReactAndroid/src/main/java/com/facebook/react/ReactActivity.java
@Override
protected void onCreate(Bundle savedInstanceState) {
// As this is the Jitsi Meet app (i.e. not the Jitsi Meet SDK), we do
// want to enable some options.
public boolean onKeyUp(int keyCode, KeyEvent event) {
if (BuildConfig.DEBUG && keyCode == KeyEvent.KEYCODE_MENU) {
JitsiMeet.showDevOptions();
return true;
}
// The welcome page defaults to disabled in the SDK at the time of this
// writing but it is clearer to be explicit about what we want anyway.
setWelcomePageEnabled(true);
super.onCreate(savedInstanceState);
return super.onKeyUp(keyCode, event);
}
private void onInviteControllerBeginAddPeople(
InviteController inviteController,
AddPeopleController addPeopleController) {
UiThreadUtil.assertOnUiThread();
// Helper methods
//
// Log with the tag "ReactNative" in order to have the log visible in
// react-native log-android as well.
Log.d(
"ReactNative",
InviteControllerListener.class.getSimpleName() + ".beginAddPeople");
String query = ADD_PEOPLE_CONTROLLER_QUERY;
if (query != null
&& (inviteController.isAddPeopleEnabled()
|| inviteController.isDialOutEnabled())) {
addPeopleController.setListener(new AddPeopleControllerListener() {
public void onInviteSettled(
AddPeopleController addPeopleController,
List<Map<String, Object>> failedInvitees) {
onAddPeopleControllerInviteSettled(
addPeopleController,
failedInvitees);
}
public void onReceivedResults(
AddPeopleController addPeopleController,
List<Map<String, Object>> results,
String query) {
onAddPeopleControllerReceivedResults(
addPeopleController,
results, query);
}
});
addPeopleController.performQuery(query);
} else {
// XXX Explicitly invoke endAddPeople on addPeopleController;
// otherwise, it is going to be memory-leaked in the associated
// InviteController and no subsequent InviteButton clicks/taps will
// be delivered.
addPeopleController.endAddPeople();
private @Nullable URL buildURL(String urlStr) {
try {
return new URL(urlStr);
} catch (MalformedURLException e) {
return null;
}
}
private boolean canRequestOverlayPermission() {
return
Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
&& getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.M;
}
}

View File

@@ -0,0 +1,6 @@
<network-security-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="false">localhost</domain>
<domain includeSubdomains="false">10.0.2.2</domain>
</domain-config>
</network-security-config>

View File

@@ -1,3 +1,5 @@
import groovy.json.JsonSlurper
// Top-level build file where you can add configuration options common to all
// sub-projects/modules.
@@ -5,10 +7,14 @@ buildscript {
repositories {
google()
jcenter()
repositories {
maven { url 'https://maven.fabric.io/public' }
}
}
dependencies {
classpath 'com.android.tools.build:gradle:3.1.4'
classpath 'com.google.gms:google-services:3.2.1'
classpath 'com.android.tools.build:gradle:3.3.2'
classpath 'com.google.gms:google-services:4.2.0'
classpath 'io.fabric.tools:gradle:1.27.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files.
@@ -17,10 +23,8 @@ buildscript {
allprojects {
repositories {
maven { url "https://maven.google.com" }
google()
jcenter()
maven { url "$rootDir/../node_modules/jsc-android/dist" }
// React Native (JS, Obj-C sources, Android binaries) is installed from
// npm.
maven { url "$rootDir/../node_modules/react-native/android" }
@@ -34,15 +38,9 @@ allprojects {
if (details.requested.group == 'com.facebook.react'
&& details.requested.name == 'react-native') {
def file = new File("$rootDir/../node_modules/react-native/package.json")
def version = new groovy.json.JsonSlurper().parseText(file.text).version
def version = new JsonSlurper().parseText(file.text).version
details.useVersion version
}
if (details.requested.group == 'org.webkit'
&& details.requested.name == 'android-jsc') {
def file = new File("$rootDir/../node_modules/jsc-android/package.json")
def version = new groovy.json.JsonSlurper().parseText(file.text).version
details.useVersion "r${version.tokenize('.')[0]}"
}
}
}
}
@@ -57,7 +55,7 @@ allprojects {
publishing {
publications {}
repositories {
maven { url "file:${rootProject.projectDir}/../../../jitsi/jitsi-maven-repository/releases" }
maven { url "file:${rootProject.projectDir}/../../jitsi-maven-repository/releases" }
}
}
}
@@ -65,7 +63,7 @@ allprojects {
afterEvaluate { project ->
if (project.name.startsWith('react-native-')) {
def npmManifest = project.file('../package.json')
def json = new groovy.json.JsonSlurper().parseText(npmManifest.text)
def json = new JsonSlurper().parseText(npmManifest.text)
// React Native modules have an npm peer dependency on react-native,
// they do not have an npm dependency on it. Further below though we
@@ -74,8 +72,26 @@ allprojects {
// complying with the full range of their npm peer dependency and,
// consequently, we should qualify their version.
def versionQualifier = '-jitsi-1'
if ('react-native-webrtc'.equals(project.name))
versionQualifier = '-jitsi-1'
if ('react-native-background-timer' == project.name)
versionQualifier = '-jitsi-4' // 2.1.1 + react-native 0.59
else if ('react-native-calendar-events' == project.name)
versionQualifier = '-jitsi-3' // 1.6.4 + react-native 0.59
else if ('react-native-fast-image' == project.name)
versionQualifier = '-jitsi-3' // 5.1.1 + react-native 0.59
else if ('react-native-google-signin' == project.name)
versionQualifier = '-jitsi-3' // 1.0.2 + react-native 0.59
else if ('react-native-immersive' == project.name)
versionQualifier = '-jitsi-6' // 2.0.0 + react-native 0.59
else if ('react-native-keep-awake' == project.name)
versionQualifier = '-jitsi-5' // 4.0.0 + react-native 0.59
else if ('react-native-linear-gradient' == project.name)
versionQualifier = '-jitsi-2' // 2.5.3 + react-native 0.59
else if ('react-native-sound' == project.name)
versionQualifier = '-jitsi-3' // 0.10.12 + react-native 0.59
else if ('react-native-vector-icons' == project.name)
versionQualifier = '-jitsi-4' // 6.0.2 + react-native 0.59
else if ('react-native-webrtc' == project.name)
versionQualifier = '-jitsi-10' // 032ee5c90e2c5ff27ab2f952217104772fcbd155 + react-native 0.59
project.version = "${json.version}${versionQualifier}"
@@ -90,15 +106,6 @@ allprojects {
}
}
task androidJavadocs(type: Javadoc) {
source = android.sourceSets.main.java.source
classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
failOnError false
}
task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) {
classifier = 'javadoc'
from androidJavadocs.destinationDir
}
task androidSourcesJar(type: Jar) {
classifier = 'sources'
from android.sourceSets.main.java.source
@@ -114,7 +121,6 @@ allprojects {
extension "aar"
}
artifact(androidSourcesJar)
artifact(androidJavadocsJar)
pom.withXml {
def pomXml = asNode()
pomXml.appendNode('name', project.name)
@@ -135,7 +141,7 @@ allprojects {
// (third-party) React Native modules we utilize can
// depend not on a specific react-native release but
// a wider range.
if (artifactId.equals('react-native')) {
if (artifactId == 'react-native') {
def versionNumber = VersionNumber.parse(version)
version = "${versionNumber.major}.${versionNumber.minor}"
}
@@ -153,17 +159,65 @@ allprojects {
}
ext {
buildToolsVersion = "27.0.3"
compileSdkVersion = 27
buildToolsVersion = "28.0.3"
compileSdkVersion = 28
minSdkVersion = 21
targetSdkVersion = 26
supportLibVersion = "27.1.1"
targetSdkVersion = 28
supportLibVersion = "28.0.0"
// The Maven artifact groupdId of the third-party react-native modules which
// Jitsi Meet SDK for Android depends on and which are not available in
// third-party Maven repositories so we have to deploy to a Maven repository
// of ours.
moduleGroupId = 'com.facebook.react'
// Glide
excludeAppGlideModule = true
glideVersion = "4.7.1" // keep in sync with react-native-fast-image
}
// If Android SDK is not installed, accept its license so that it
// is automatically downloaded.
afterEvaluate { project ->
// Either the environment variable ANDROID_HOME or the property sdk.dir in
// local.properties identifies where Android SDK is installed.
def androidHome = System.env.ANDROID_HOME
if (!androidHome) {
// ANDROID_HOME is not set. Is sdk.dir set?
def file = file("${project.rootDir}/local.properties")
def props = new Properties()
if (file.canRead()) {
file.withInputStream {
props.load(it)
androidHome = props.'sdk.dir'
}
}
if (!androidHome && (!file.exists() || file.canWrite())) {
// Neither ANDROID_HOME nor sdk.dir is set. Set sdk.dir (because
// environment variables cannot be set).
props.'sdk.dir' = "${project.buildDir}/android-sdk".toString()
file.withOutputStream {
props.store(it, null)
androidHome = props.'sdk.dir'
}
}
}
// If the license is not accepted, accept it so that automatic downloading
// kicks in.
// The license hash can be taken from the accepted licenses, by doing this
// on your local machine the file is
// ${androidHome}/licenses/android-sdk-license
if (androidHome) {
def dir = file("${androidHome}/licenses")
dir.mkdirs()
def file = file("${dir.path}/android-sdk-license")
if (!file.exists()) {
file.withWriter {
def hash = 'd56f5187479451eabf01fb78af6dfcb131a6481e'
it.write(hash, 0, hash.length())
}
}
}
}
// Force the version of the Android build tools we have chosen on all
@@ -180,8 +234,3 @@ subprojects { subproject ->
}
}
}
task wrapper(type: Wrapper) {
gradleVersion = '4.4'
distributionUrl = distributionUrl.replace("bin", "all")
}

2
android/fastlane/Appfile Normal file
View File

@@ -0,0 +1,2 @@
json_key_file("")
package_name("org.jitsi.meet")

34
android/fastlane/Fastfile Normal file
View File

@@ -0,0 +1,34 @@
ENV["FASTLANE_SKIP_UPDATE_CHECK"] = "1"
opt_out_usage
default_platform(:android)
platform :android do
desc "Deploy a new version to Goolge Play (Closed Beta)"
lane :deploy do
# Cleanup
gradle(task: "clean")
# Build and sign the app
gradle(
task: "assemble",
build_type: "Release",
print_command: false,
properties: {
"android.injected.signing.store.file" => ENV["JITSI_KEYSTORE"],
"android.injected.signing.store.password" => ENV["JITSI_KEYSTORE_PASSWORD"],
"android.injected.signing.key.alias" => ENV["JITSI_KEY_ALIAS"],
"android.injected.signing.key.password" => ENV["JITSI_KEY_PASSWORD"],
}
)
# Upload built artifact to the Closed Beta track
upload_to_play_store(
track: "Closed Beta",
json_key: ENV["JITSI_JSON_KEY_FILE"],
skip_upload_metadata: true,
skip_upload_images: true,
skip_upload_screenshots: true
)
end
end

View File

@@ -0,0 +1,29 @@
fastlane documentation
================
# Installation
Make sure you have the latest version of the Xcode command line tools installed:
```
xcode-select --install
```
Install _fastlane_ using
```
[sudo] gem install fastlane -NV
```
or alternatively using `brew cask install fastlane`
# Available Actions
## Android
### android deploy
```
fastlane android deploy
```
Deploy a new version to Goolge Play (Closed Beta)
----
This README.md is auto-generated and will be re-generated every time [fastlane](https://fastlane.tools) is run.
More information about fastlane can be found on [fastlane.tools](https://fastlane.tools).
The documentation of fastlane can be found on [docs.fastlane.tools](https://docs.fastlane.tools).

View File

@@ -17,4 +17,5 @@
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
version=1
appVersion=19.1.0
sdkVersion=2.0.0

View File

@@ -1,6 +1,6 @@
#Fri Sep 08 10:42:14 CEST 2017
#Fri Mar 08 13:36:51 CET 2019
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip

View File

@@ -0,0 +1,19 @@
#!/bin/bash
CWD=$(dirname $0)
MVN_REPO=$(realpath $1)
RN_VERSION=$(jq -r '.dependencies."react-native"' ${CWD}/../../package.json)
pushd ${CWD}/../../node_modules/react-native/android/com/facebook/react/react-native/${RN_VERSION}
mvn \
deploy:deploy-file \
-Durl=file://${MVN_REPO} \
-Dfile=react-native-${RN_VERSION}.aar \
-Dpackaging=aar \
-Dsources=react-native-${RN_VERSION}-sources.jar \
-Djavadoc=react-native-${RN_VERSION}-javadoc.jar \
-DgeneratePom=false \
-DpomFile=react-native-${RN_VERSION}.pom
popd

24
android/scripts/run-packager.sh Executable file
View File

@@ -0,0 +1,24 @@
#!/bin/bash
# This script is executed bt Gradle to start the React packager for Debug
# targets.
THIS_DIR=$(cd -P "$(dirname "$(readlink "${BASH_SOURCE[0]}" || echo "${BASH_SOURCE[0]}")")" && pwd)
export RCT_METRO_PORT="${RCT_METRO_PORT:=8081}"
echo "export RCT_METRO_PORT=${RCT_METRO_PORT}" > "${THIS_DIR}/../../node_modules/react-native/scripts/.packager.env"
if nc -w 5 -z localhost ${RCT_METRO_PORT} ; then
if ! curl -s "http://localhost:${RCT_METRO_PORT}/status" | grep -q "packager-status:running" ; then
echo "Port ${RCT_METRO_PORT} already in use, packager is either not running or not running correctly"
exit 2
fi
else
CMD="${THIS_DIR}/../../node_modules/react-native/scripts/launchPackager.command"
if [[ `uname` == "Darwin" ]]; then
open -g "${CMD}" || echo "Can't start packager automatically"
else
xdg-open "${CMD}" || echo "Can't start packager automatically"
fi
fi

View File

@@ -24,6 +24,7 @@ dependencies {
implementation "com.android.support:support-v4:${rootProject.ext.supportLibVersion}"
implementation "com.android.support:appcompat-v7:${rootProject.ext.supportLibVersion}"
implementation 'com.amplitude:android-sdk:2.14.1'
implementation 'com.dropbox.core:dropbox-core-sdk:3.0.8'
api 'com.facebook.react:react-native:+'
@@ -39,7 +40,6 @@ dependencies {
implementation project(':react-native-immersive')
implementation project(':react-native-keep-awake')
implementation project(':react-native-linear-gradient')
implementation project(':react-native-locale-detector')
implementation project(':react-native-sound')
implementation project(':react-native-vector-icons')
implementation project(':react-native-webrtc')
@@ -107,13 +107,17 @@ android.libraryVariants.all { def variant ->
currentBundleTask.ext.generatedResFolders = files(resourcesDir).builtBy(currentBundleTask)
currentBundleTask.ext.generatedAssetsFolders = files(jsBundleDir).builtBy(currentBundleTask)
variant.registerGeneratedResFolders(currentBundleTask.generatedResFolders)
variant.mergeResources.dependsOn(currentBundleTask)
def assetsDir = variant.mergeAssets.outputDir
def mergeAssetsTask = variant.mergeAssetsProvider.get()
def mergeResourcesTask = variant.mergeResourcesProvider.get()
mergeAssetsTask.dependsOn(currentBundleTask)
mergeResourcesTask.dependsOn(currentBundleTask)
mergeAssetsTask.doLast {
def assetsDir = mergeAssetsTask.outputDir
variant.mergeAssets.doLast {
// Bundle fonts
//
copy {
@@ -124,6 +128,7 @@ android.libraryVariants.all { def variant ->
// Bundle sounds
//
copy {
from("${projectDir}/../../sounds/incomingMessage.wav")
from("${projectDir}/../../sounds/joined.wav")
from("${projectDir}/../../sounds/left.wav")
from("${projectDir}/../../sounds/outgoingRinging.wav")
@@ -138,19 +143,19 @@ android.libraryVariants.all { def variant ->
//
if (currentBundleTask.enabled) {
copy {
from(jsBundleDir)
from(jsBundleFile)
into(assetsDir)
}
}
}
variant.mergeResources.doLast {
mergeResourcesTask.doLast {
// Copy React resources
//
if (currentBundleTask.enabled) {
copy {
from(resourcesDir)
into(variant.mergeResources.outputDir)
into(mergeResourcesTask.outputDir)
}
}
}
@@ -162,7 +167,7 @@ publishing {
aarArchive(MavenPublication) {
groupId 'org.jitsi.react'
artifactId 'jitsi-meet-sdk'
version '1.9.0'
version project.sdkVersion
artifact("${project.buildDir}/outputs/aar/${project.name}-release.aar") {
extension "aar"
@@ -196,6 +201,6 @@ publishing {
}
repositories {
maven { url "file:${rootProject.projectDir}/../../../jitsi/jitsi-maven-repository/releases" }
maven { url "file:${rootProject.projectDir}/../../jitsi-maven-repository/releases" }
}
}

View File

@@ -1,25 +0,0 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /Users/scorretge/Library/Android/sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@@ -1,30 +1,49 @@
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.jitsi.meet.sdk">
<!-- XXX ACCESS_NETWORK_STATE is required by WebRTC. -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<!-- XXX ACCESS_NETWORK_STATE is required by WebRTC. -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-feature android:glEsVersion="0x00020000" android:required="true" />
<uses-feature
android:name="android.hardware.camera"
android:required="false" />
<uses-feature
android:name="android.hardware.camera.autofocus"
android:required="false" />
<uses-feature
android:glEsVersion="0x00020000"
android:required="true" />
<uses-feature
android:name="android.hardware.camera"
android:required="false" />
<uses-feature
android:name="android.hardware.camera.autofocus"
android:required="false" />
<application
android:allowBackup="true"
android:label="@string/app_name"
android:supportsRtl="true">
<activity
android:name="com.facebook.react.devsupport.DevSettingsActivity" />
</application>
</manifest>
<application
android:allowBackup="true"
android:label="@string/app_name"
android:supportsRtl="true">
<activity
android:name=".JitsiMeetActivity"
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize"
android:launchMode="singleTask"
android:resizeableActivity="true"
android:supportsPictureInPicture="true"
android:windowSoftInputMode="adjustResize">
</activity>
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
<service
android:name=".ConnectionService"
android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE">
<intent-filter>
<action android:name="android.telecom.ConnectionService" />
</intent-filter>
</service>
</application>
</manifest>

View File

@@ -0,0 +1,90 @@
/*
* 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.
*/
package org.jitsi.meet.sdk;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableMap;
import com.amplitude.api.Amplitude;
import com.facebook.react.module.annotations.ReactModule;
import org.json.JSONException;
import org.json.JSONObject;
/**
* Implements the react-native module for the Amplitude integration.
*/
@ReactModule(name = AmplitudeModule.NAME)
class AmplitudeModule
extends ReactContextBaseJavaModule {
public static final String NAME = "Amplitude";
public AmplitudeModule(ReactApplicationContext reactContext) {
super(reactContext);
}
/**
* Initializes the Amplitude SDK.
*
* @param instanceName The name of the Amplitude instance. Should
* be used only for multi-project logging.
* @param apiKey The API_KEY of the Amplitude project.
*/
@ReactMethod
public void init(String instanceName, String apiKey) {
Amplitude.getInstance(instanceName).initialize(getCurrentActivity(), apiKey);
}
/**
* Sets the user properties for an Amplitude instance.
*
* @param instanceName The name of the Amplitude instance.
* @param userProps JSON string with user properties to be set.
*/
@ReactMethod
public void setUserProperties(String instanceName, ReadableMap userProps) {
if (userProps != null) {
Amplitude.getInstance(instanceName).setUserProperties(
new JSONObject(userProps.toHashMap()));
}
}
/**
* Log an analytics event.
*
* @param instanceName The name of the Amplitude instance.
* @param eventType The event type.
* @param eventPropsString JSON string with the event properties.
*/
@ReactMethod
public void logEvent(String instanceName, String eventType, String eventPropsString) {
try {
JSONObject eventProps = new JSONObject(eventPropsString);
Amplitude.getInstance(instanceName).logEvent(eventType, eventProps);
} catch (JSONException e) {
e.printStackTrace();
}
}
@Override
public String getName() {
return NAME;
}
}

View File

@@ -15,17 +15,21 @@ import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.module.annotations.ReactModule;
@ReactModule(name = AndroidSettingsModule.NAME)
class AndroidSettingsModule
extends ReactContextBaseJavaModule {
public static final String NAME = "AndroidSettings";
public AndroidSettingsModule(ReactApplicationContext reactContext) {
super(reactContext);
}
@Override
public String getName() {
return "AndroidSettings";
return NAME;
}
@ReactMethod

View File

@@ -23,13 +23,17 @@ import android.content.pm.PackageManager;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.module.annotations.ReactModule;
import java.util.HashMap;
import java.util.Map;
@ReactModule(name = AppInfoModule.NAME)
class AppInfoModule
extends ReactContextBaseJavaModule {
public static final String NAME = "AppInfo";
public AppInfoModule(ReactApplicationContext reactContext) {
super(reactContext);
}
@@ -60,6 +64,9 @@ class AppInfoModule
Map<String, Object> constants = new HashMap<>();
constants.put(
"buildNumber",
packageInfo == null ? "" : String.valueOf(packageInfo.versionCode));
constants.put(
"name",
applicationInfo == null
@@ -74,6 +81,6 @@ class AppInfoModule
@Override
public String getName() {
return "AppInfo";
return NAME;
}
}

View File

@@ -25,6 +25,7 @@ import android.content.pm.PackageManager;
import android.media.AudioDeviceInfo;
import android.media.AudioManager;
import android.os.Build;
import android.support.annotation.RequiresApi;
import android.util.Log;
import com.facebook.react.bridge.Arguments;
@@ -34,6 +35,7 @@ import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.module.annotations.ReactModule;
import java.util.HashMap;
import java.util.HashSet;
@@ -57,10 +59,12 @@ import java.util.concurrent.Executors;
* Before a call has started and after it has ended the
* {@code AudioModeModule.DEFAULT} mode should be used.
*/
class AudioModeModule
extends ReactContextBaseJavaModule
@ReactModule(name = AudioModeModule.NAME)
class AudioModeModule extends ReactContextBaseJavaModule
implements AudioManager.OnAudioFocusChangeListener {
public static final String NAME = "AudioMode";
/**
* Constants representing the audio mode.
* - DEFAULT: Used before and after every call. It represents the default
@@ -89,16 +93,74 @@ class AudioModeModule
*/
private static final int TYPE_USB_HEADSET = 22;
/**
* The name of {@code AudioModeModule} to be used in the React Native
* bridge.
*/
private static final String MODULE_NAME = "AudioMode";
/**
* The {@code Log} tag {@code AudioModeModule} is to log messages with.
*/
static final String TAG = MODULE_NAME;
static final String TAG = NAME;
/**
* Converts any of the "DEVICE_" constants into the corresponding
* {@link android.telecom.CallAudioState} "ROUTE_" number.
*
* @param audioDevice one of the "DEVICE_" constants.
* @return a route number {@link android.telecom.CallAudioState#ROUTE_EARPIECE} if
* no match is found.
*/
@RequiresApi(api = Build.VERSION_CODES.M)
private static int audioDeviceToRouteInt(String audioDevice) {
if (audioDevice == null) {
return android.telecom.CallAudioState.ROUTE_EARPIECE;
}
switch (audioDevice) {
case DEVICE_BLUETOOTH:
return android.telecom.CallAudioState.ROUTE_BLUETOOTH;
case DEVICE_EARPIECE:
return android.telecom.CallAudioState.ROUTE_EARPIECE;
case DEVICE_HEADPHONES:
return android.telecom.CallAudioState.ROUTE_WIRED_HEADSET;
case DEVICE_SPEAKER:
return android.telecom.CallAudioState.ROUTE_SPEAKER;
default:
Log.e(TAG, "Unsupported device name: " + audioDevice);
return android.telecom.CallAudioState.ROUTE_EARPIECE;
}
}
/**
* Populates given route mask into the "DEVICE_" list.
*
* @param supportedRouteMask an integer coming from
* {@link android.telecom.CallAudioState#getSupportedRouteMask()}.
* @return a list of device names.
*/
@RequiresApi(api = Build.VERSION_CODES.M)
private static Set<String> routesToDeviceNames(int supportedRouteMask) {
Set<String> devices = new HashSet<>();
if ((supportedRouteMask & android.telecom.CallAudioState.ROUTE_EARPIECE)
== android.telecom.CallAudioState.ROUTE_EARPIECE) {
devices.add(DEVICE_EARPIECE);
}
if ((supportedRouteMask & android.telecom.CallAudioState.ROUTE_BLUETOOTH)
== android.telecom.CallAudioState.ROUTE_BLUETOOTH) {
devices.add(DEVICE_BLUETOOTH);
}
if ((supportedRouteMask & android.telecom.CallAudioState.ROUTE_SPEAKER)
== android.telecom.CallAudioState.ROUTE_SPEAKER) {
devices.add(DEVICE_SPEAKER);
}
if ((supportedRouteMask & android.telecom.CallAudioState.ROUTE_WIRED_HEADSET)
== android.telecom.CallAudioState.ROUTE_WIRED_HEADSET) {
devices.add(DEVICE_HEADPHONES);
}
return devices;
}
/**
* Whether or not the ConnectionService is used for selecting audio devices.
*/
static boolean useConnectionService() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
}
/**
* Indicator that we have lost audio focus.
@@ -204,6 +266,15 @@ class AudioModeModule
*/
private String selectedDevice;
/**
* Used on API >= 26 to store the most recently reported audio devices.
* Makes it easier to compare for a change, because the devices are stored
* as a mask in the {@link android.telecom.CallAudioState}. The mask is populated into
* the {@link #availableDevices} on each update.
*/
@RequiresApi(api = Build.VERSION_CODES.O)
private int supportedRouteMask;
/**
* User selected device. When null the default is used depending on the
* mode.
@@ -224,21 +295,25 @@ class AudioModeModule
= (AudioManager)
reactContext.getSystemService(Context.AUDIO_SERVICE);
// Setup runtime device change detection.
setupAudioRouteChangeDetection();
// Starting Oreo the ConnectionImpl from ConnectionService is used to
// detect the available devices.
if (!useConnectionService()) {
// Setup runtime device change detection.
setupAudioRouteChangeDetection();
// Do an initial detection on Android >= M.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
runInAudioThread(onAudioDeviceChangeRunner);
} else {
// On Android < M, detect if we have an earpiece.
PackageManager pm = reactContext.getPackageManager();
if (pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
availableDevices.add(DEVICE_EARPIECE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// Do an initial detection on Android >= M.
runInAudioThread(onAudioDeviceChangeRunner);
} else {
// On Android < M, detect if we have an earpiece.
PackageManager pm = reactContext.getPackageManager();
if (pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
availableDevices.add(DEVICE_EARPIECE);
}
// Always assume there is a speaker.
availableDevices.add(DEVICE_SPEAKER);
}
// Always assume there is a speaker.
availableDevices.add(DEVICE_SPEAKER);
}
}
@@ -296,7 +371,7 @@ class AudioModeModule
*/
@Override
public String getName() {
return MODULE_NAME;
return NAME;
}
/**
@@ -354,6 +429,44 @@ class AudioModeModule
});
}
@RequiresApi(api = Build.VERSION_CODES.O)
void onCallAudioStateChange(Object callAudioState_) {
final android.telecom.CallAudioState callAudioState
= (android.telecom.CallAudioState)callAudioState_;
runInAudioThread(new Runnable() {
@Override
public void run() {
int newSupportedRoutes = callAudioState.getSupportedRouteMask();
boolean audioDevicesChanged
= supportedRouteMask != newSupportedRoutes;
if (audioDevicesChanged) {
supportedRouteMask = newSupportedRoutes;
availableDevices = routesToDeviceNames(supportedRouteMask);
Log.d(TAG,
"Available audio devices: "
+ availableDevices.toString());
}
boolean audioRouteChanged
= audioDeviceToRouteInt(selectedDevice)
!= callAudioState.getRoute();
if (audioRouteChanged || audioDevicesChanged) {
// Reset user selection
userSelectedDevice = null;
// If the OS changes the Audio Route or Devices we could have lost
// the selected audio device
selectedDevice = null;
if (mode != -1) {
updateAudioRoute(mode);
}
}
}
});
}
/**
* {@link AudioManager.OnAudioFocusChangeListener} interface method. Called
* when the audio focus of the system is updated.
@@ -417,6 +530,31 @@ class AudioModeModule
});
}
/**
* The API >= 26 way of adjusting the audio route.
*
* @param audioDevice one of the "DEVICE_" names to set as the audio route.
*/
@RequiresApi(api = Build.VERSION_CODES.O)
private void setAudioRoute(String audioDevice) {
int newAudioRoute = audioDeviceToRouteInt(audioDevice);
RNConnectionService.setAudioRoute(newAudioRoute);
}
/**
* The API < 26 way of adjusting the audio route.
*
* @param audioDevice one of the "DEVICE_" names to set as the audio route.
*/
private void setAudioRoutePreO(String audioDevice) {
// Turn bluetooth on / off
setBluetoothAudioRoute(audioDevice.equals(DEVICE_BLUETOOTH));
// Turn speaker on / off
audioManager.setSpeakerphoneOn(audioDevice.equals(DEVICE_SPEAKER));
}
/**
* Helper method to set the output route to a Bluetooth device.
*
@@ -475,7 +613,7 @@ class AudioModeModule
/**
* Setup the audio route change detection mechanism. We use the
* {@link android.media.AudioDeviceCallback} API on Android >= 23 only.
* {@link android.media.AudioDeviceCallback} on 23 >= Android API < 26.
*/
private void setupAudioRouteChangeDetection() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
@@ -486,7 +624,7 @@ class AudioModeModule
}
/**
* Audio route change detection mechanism for Android API >= 23.
* Audio route change detection mechanism for 23 >= Android API < 26.
*/
@TargetApi(Build.VERSION_CODES.M)
private void setupAudioRouteChangeDetectionM() {
@@ -542,27 +680,31 @@ class AudioModeModule
Log.d(TAG, "Update audio route for mode: " + mode);
if (mode == DEFAULT) {
audioFocusLost = false;
audioManager.setMode(AudioManager.MODE_NORMAL);
audioManager.abandonAudioFocus(this);
audioManager.setSpeakerphoneOn(false);
setBluetoothAudioRoute(false);
if (!useConnectionService()) {
audioFocusLost = false;
audioManager.setMode(AudioManager.MODE_NORMAL);
audioManager.abandonAudioFocus(this);
audioManager.setSpeakerphoneOn(false);
setBluetoothAudioRoute(false);
}
selectedDevice = null;
userSelectedDevice = null;
return true;
}
audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
audioManager.setMicrophoneMute(false);
if (!useConnectionService()) {
audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
audioManager.setMicrophoneMute(false);
if (audioManager.requestAudioFocus(
if (audioManager.requestAudioFocus(
this,
AudioManager.STREAM_VOICE_CALL,
AudioManager.AUDIOFOCUS_GAIN)
== AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
Log.d(TAG, "Audio focus request failed");
return false;
== AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
Log.d(TAG, "Audio focus request failed");
return false;
}
}
boolean bluetoothAvailable = availableDevices.contains(DEVICE_BLUETOOTH);
@@ -596,11 +738,11 @@ class AudioModeModule
selectedDevice = audioDevice;
Log.d(TAG, "Selected audio device: " + audioDevice);
// Turn bluetooth on / off
setBluetoothAudioRoute(audioDevice.equals(DEVICE_BLUETOOTH));
// Turn speaker on / off
audioManager.setSpeakerphoneOn(audioDevice.equals(DEVICE_SPEAKER));
if (useConnectionService()) {
setAudioRoute(audioDevice);
} else {
setAudioRoutePreO(audioDevice);
}
return true;
}

View File

@@ -1,5 +1,6 @@
/*
* Copyright @ 2018-present Atlassian Pty Ltd
* Copyright @ 2018-present 8x8, Inc.
* Copyright @ 2018 Atlassian Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,7 +22,6 @@ import android.content.Context;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;
import android.widget.FrameLayout;
import com.facebook.react.ReactRootView;
@@ -170,7 +170,7 @@ public abstract class BaseReactView<ListenerT>
* @param data - The details of the event associated with/specific to the
* specified {@code name}.
*/
public abstract void onExternalAPIEvent(String name, ReadableMap data);
protected abstract void onExternalAPIEvent(String name, ReadableMap data);
protected void onExternalAPIEvent(
Map<String, Method> listenerMethods,
@@ -194,29 +194,10 @@ public abstract class BaseReactView<ListenerT>
super.onWindowFocusChanged(hasFocus);
// https://github.com/mockingbot/react-native-immersive#restore-immersive-state
// FIXME The singleton pattern employed by RNImmersiveModule is not
// advisable because a react-native mobule is consumable only after its
// BaseJavaModule#initialize() has completed and here we have no
// knowledge of whether the precondition is really met.
RNImmersiveModule immersive = RNImmersiveModule.getInstance();
if (hasFocus && immersive != null) {
try {
immersive.emitImmersiveStateChangeEvent();
} catch (RuntimeException re) {
// FIXME I don't know how to check myself whether
// BaseJavaModule#initialize() has been invoked and thus
// RNImmersiveModule is consumable. A safe workaround is to
// swallow the failure because the whole full-screen/immersive
// functionality is brittle anyway, akin to the icing on the
// cake, and has been working without onWindowFocusChanged for a
// very long time.
Log.e(
"RNImmersiveModule",
"emitImmersiveStateChangeEvent() failed!",
re);
}
immersive.emitImmersiveStateChangeEvent();
}
}

View File

@@ -0,0 +1,451 @@
package org.jitsi.meet.sdk;
import android.content.ComponentName;
import android.content.Context;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.RequiresApi;
import android.telecom.CallAudioState;
import android.telecom.Connection;
import android.telecom.ConnectionRequest;
import android.telecom.DisconnectCause;
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
import android.telecom.VideoProfile;
import android.util.Log;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableNativeMap;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
* Jitsi Meet implementation of {@link ConnectionService}. At the time of this
* writing it implements only the outgoing call scenario.
*
* NOTE the class needs to be public, but is not part of the SDK API and should
* never be used directly.
*
* @author Pawel Domas
*/
@RequiresApi(api = Build.VERSION_CODES.O)
public class ConnectionService extends android.telecom.ConnectionService {
/**
* Tag used for logging.
*/
static final String TAG = "JitsiConnectionService";
/**
* The extra added to the {@link ConnectionImpl} and
* {@link ConnectionRequest} which stores the {@link PhoneAccountHandle}
* created for the call.
*/
static final String EXTRA_PHONE_ACCOUNT_HANDLE
= "org.jitsi.meet.sdk.connection_service.PHONE_ACCOUNT_HANDLE";
/**
* Connections mapped by call UUID.
*/
static private final Map<String, ConnectionImpl> connections
= new HashMap<>();
/**
* The start call Promises mapped by call UUID.
*/
static private final HashMap<String, Promise> startCallPromises
= new HashMap<>();
/**
* Adds {@link ConnectionImpl} to the list.
*
* @param connection - {@link ConnectionImpl}
*/
static void addConnection(ConnectionImpl connection) {
connections.put(connection.getCallUUID(), connection);
}
/**
* Returns all {@link ConnectionImpl} instances held in this list.
*
* @return a list of {@link ConnectionImpl}.
*/
static List<ConnectionImpl> getConnections() {
return new ArrayList<>(connections.values());
}
/**
* @return {@code true} if running a Samsung device.
*/
static boolean isSamsungDevice() {
return android.os.Build.MANUFACTURER.toLowerCase().contains("samsung");
}
/**
* Registers a start call promise.
*
* @param uuid - the call UUID to which the start call promise belongs to.
* @param promise - the Promise instance to be stored for later use.
*/
static void registerStartCallPromise(String uuid, Promise promise) {
startCallPromises.put(uuid, promise);
}
/**
* Removes {@link ConnectionImpl} from the list.
*
* @param connection - {@link ConnectionImpl}
*/
static void removeConnection(ConnectionImpl connection) {
connections.remove(connection.getCallUUID());
}
/**
* Used to adjusts the connection's state to
* {@link android.telecom.Connection#STATE_ACTIVE}.
*
* @param callUUID the call UUID which identifies the connection.
*/
static void setConnectionActive(String callUUID) {
ConnectionImpl connection = connections.get(callUUID);
if (connection != null) {
connection.setActive();
} else {
Log.e(TAG, String.format(
"setConnectionActive - no connection for UUID: %s",
callUUID));
}
}
/**
* Used to adjusts the connection's state to
* {@link android.telecom.Connection#STATE_DISCONNECTED}.
*
* @param callUUID the call UUID which identifies the connection.
* @param cause disconnection reason.
*/
static void setConnectionDisconnected(String callUUID, DisconnectCause cause) {
ConnectionImpl connection = connections.get(callUUID);
if (connection != null) {
if (isSamsungDevice()) {
// Required to release the audio focus correctly.
connection.setOnHold();
// Prevents from including in the native phone calls history
connection.setConnectionProperties(
Connection.PROPERTY_SELF_MANAGED
| Connection.PROPERTY_IS_EXTERNAL_CALL);
}
// Note that the connection is not removed from the list here, but
// in ConnectionImpl's state changed callback. It's a safer
// approach, because in case the app would crash on the JavaScript
// side the calls would be cleaned up by the system they would still
// be removed from the ConnectionList.
connection.setDisconnected(cause);
connection.destroy();
} else {
Log.e(TAG, "endCall no connection for UUID: " + callUUID);
}
}
/**
* Unregisters a start call promise. Must be called after the Promise is
* rejected or resolved.
*
* @param uuid the call UUID which identifies the call to which the promise
* belongs to.
* @return the unregistered Promise instance or <tt>null</tt> if there
* wasn't any for the given call UUID.
*/
static Promise unregisterStartCallPromise(String uuid) {
return startCallPromises.remove(uuid);
}
/**
* Used to adjusts the call's state.
*
* @param callUUID the call UUID which identifies the connection.
* @param callState a map which carries the properties to be modified. See
* "KEY_*" constants in {@link ConnectionImpl} for the list of keys.
*/
static void updateCall(String callUUID, ReadableMap callState) {
ConnectionImpl connection = connections.get(callUUID);
if (connection != null) {
if (callState.hasKey(ConnectionImpl.KEY_HAS_VIDEO)) {
boolean hasVideo
= callState.getBoolean(ConnectionImpl.KEY_HAS_VIDEO);
Log.d(TAG, String.format(
"updateCall: %s hasVideo: %s", callUUID, hasVideo));
connection.setVideoState(
hasVideo
? VideoProfile.STATE_BIDIRECTIONAL
: VideoProfile.STATE_AUDIO_ONLY);
}
} else {
Log.e(TAG, "updateCall no connection for UUID: " + callUUID);
}
}
@Override
public Connection onCreateOutgoingConnection(
PhoneAccountHandle accountHandle, ConnectionRequest request) {
ConnectionImpl connection = new ConnectionImpl();
connection.setConnectionProperties(Connection.PROPERTY_SELF_MANAGED);
connection.setAddress(
request.getAddress(),
TelecomManager.PRESENTATION_UNKNOWN);
connection.setExtras(request.getExtras());
connection.setAudioModeIsVoip(true);
// NOTE there's a time gap between the placeCall and this callback when
// things could get out of sync, but they are put back in sync once
// the startCall Promise is resolved below. That's because on
// the JavaScript side there's a logic to sync up in .then() callback.
connection.setVideoState(request.getVideoState());
Bundle moreExtras = new Bundle();
moreExtras.putParcelable(
EXTRA_PHONE_ACCOUNT_HANDLE,
Objects.requireNonNull(request.getAccountHandle(), "accountHandle"));
connection.putExtras(moreExtras);
addConnection(connection);
Promise startCallPromise
= unregisterStartCallPromise(connection.getCallUUID());
if (startCallPromise != null) {
Log.d(TAG,
"onCreateOutgoingConnection " + connection.getCallUUID());
startCallPromise.resolve(null);
} else {
Log.e(TAG, String.format(
"onCreateOutgoingConnection: no start call Promise for %s",
connection.getCallUUID()));
}
return connection;
}
@Override
public Connection onCreateIncomingConnection(
PhoneAccountHandle accountHandle, ConnectionRequest request) {
throw new RuntimeException("Not implemented");
}
@Override
public void onCreateIncomingConnectionFailed(
PhoneAccountHandle accountHandle, ConnectionRequest request) {
throw new RuntimeException("Not implemented");
}
@Override
public void onCreateOutgoingConnectionFailed(
PhoneAccountHandle accountHandle, ConnectionRequest request) {
PhoneAccountHandle theAccountHandle = request.getAccountHandle();
String callUUID = theAccountHandle.getId();
Log.e(TAG, "onCreateOutgoingConnectionFailed " + callUUID);
if (callUUID != null) {
Promise startCallPromise = unregisterStartCallPromise(callUUID);
if (startCallPromise != null) {
startCallPromise.reject(
"CREATE_OUTGOING_CALL_FAILED",
"The request has been denied by the system");
} else {
Log.e(TAG, String.format(
"startCallFailed - no start call Promise for UUID: %s",
callUUID));
}
} else {
Log.e(TAG, "onCreateOutgoingConnectionFailed - no call UUID");
}
unregisterPhoneAccount(theAccountHandle);
}
private void unregisterPhoneAccount(PhoneAccountHandle phoneAccountHandle) {
TelecomManager telecom = getSystemService(TelecomManager.class);
if (telecom != null) {
if (phoneAccountHandle != null) {
telecom.unregisterPhoneAccount(phoneAccountHandle);
} else {
Log.e(TAG, "unregisterPhoneAccount - account handle is null");
}
} else {
Log.e(TAG, "unregisterPhoneAccount - telecom is null");
}
}
/**
* Registers new {@link PhoneAccountHandle}.
*
* @param context the current Android context.
* @param address the phone account's address. At the time of this writing
* it's the call handle passed from the Java Script side.
* @param callUUID the call's UUID for which the account is to be created.
* It will be used as the account's id.
* @return {@link PhoneAccountHandle} described by the given arguments.
*/
static PhoneAccountHandle registerPhoneAccount(
Context context, Uri address, String callUUID) {
PhoneAccountHandle phoneAccountHandle
= new PhoneAccountHandle(
new ComponentName(context, ConnectionService.class),
callUUID);
PhoneAccount.Builder builder
= PhoneAccount.builder(phoneAccountHandle, address.toString())
.setAddress(address)
.setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED |
PhoneAccount.CAPABILITY_VIDEO_CALLING |
PhoneAccount.CAPABILITY_SUPPORTS_VIDEO_CALLING)
.addSupportedUriScheme(PhoneAccount.SCHEME_SIP);
PhoneAccount account = builder.build();
TelecomManager telecomManager
= context.getSystemService(TelecomManager.class);
telecomManager.registerPhoneAccount(account);
return phoneAccountHandle;
}
/**
* Connection implementation for Jitsi Meet's {@link ConnectionService}.
*
* @author Pawel Domas
*/
class ConnectionImpl extends Connection {
/**
* The constant which defines the key for the "has video" property.
* The key is used in the map which carries the call's state passed as
* the argument of the {@link RNConnectionService#updateCall} method.
*/
static final String KEY_HAS_VIDEO = "hasVideo";
/**
* Called when system wants to disconnect the call.
*
* {@inheritDoc}
*/
@Override
public void onDisconnect() {
Log.d(TAG, "onDisconnect " + getCallUUID());
WritableNativeMap data = new WritableNativeMap();
data.putString("callUUID", getCallUUID());
ReactInstanceManagerHolder.emitEvent(
"org.jitsi.meet:features/connection_service#disconnect",
data);
// The JavaScript side will not go back to the native with
// 'endCall', so the Connection must be removed immediately.
setConnectionDisconnected(
getCallUUID(),
new DisconnectCause(DisconnectCause.LOCAL));
}
/**
* Called when system wants to abort the call.
*
* {@inheritDoc}
*/
@Override
public void onAbort() {
Log.d(TAG, "onAbort " + getCallUUID());
WritableNativeMap data = new WritableNativeMap();
data.putString("callUUID", getCallUUID());
ReactInstanceManagerHolder.emitEvent(
"org.jitsi.meet:features/connection_service#abort",
data);
// The JavaScript side will not go back to the native with
// 'endCall', so the Connection must be removed immediately.
setConnectionDisconnected(
getCallUUID(),
new DisconnectCause(DisconnectCause.CANCELED));
}
@Override
public void onHold() {
// What ?! Android will still call this method even if we do not add
// the HOLD capability, so do the same thing as on abort.
// TODO implement HOLD
Log.d(TAG, String.format(
"onHold %s - HOLD is not supported, aborting the call...",
getCallUUID()));
this.onAbort();
}
/**
* Called when there's change to the call audio state. Either by
* the system after the connection is initialized or in response to
* {@link #setAudioRoute(int)}.
*
* @param state the new {@link CallAudioState}
*/
@Override
public void onCallAudioStateChanged(CallAudioState state) {
Log.d(TAG, "onCallAudioStateChanged: " + state);
AudioModeModule audioModeModule
= ReactInstanceManagerHolder
.getNativeModule(AudioModeModule.class);
if (audioModeModule != null) {
audioModeModule.onCallAudioStateChange(state);
}
}
/**
* Unregisters the account when the call is disconnected.
*
* @param state - the new connection's state.
*/
@Override
public void onStateChanged(int state) {
Log.d(TAG,
String.format("onStateChanged: %s %s",
Connection.stateToString(state),
getCallUUID()));
if (state == STATE_DISCONNECTED) {
removeConnection(this);
unregisterPhoneAccount(getPhoneAccountHandle());
}
}
/**
* Retrieves the UUID of the call associated with this connection.
*
* @return call UUID
*/
String getCallUUID() {
return getPhoneAccountHandle().getId();
}
private PhoneAccountHandle getPhoneAccountHandle() {
return getExtras().getParcelable(
ConnectionService.EXTRA_PHONE_ACCOUNT_HANDLE);
}
@Override
public String toString() {
return String.format(
"ConnectionImpl[address=%s, uuid=%s]@%d",
getAddress(), getCallUUID(), hashCode());
}
}
}

View File

@@ -1,5 +1,6 @@
/*
* Copyright @ 2017-present Atlassian Pty Ltd
* Copyright @ 2019-present 8x8, Inc.
* Copyright @ 2017-2018 Atlassian Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,15 +22,14 @@ import android.app.Activity;
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
/**
* Defines the default behavior of {@code JitsiMeetActivity} and
* Defines the default behavior of {@code JitsiMeetFragment} and
* {@code JitsiMeetView} upon invoking the back button if no
* {@code JitsiMeetView} handles the invocation. For example, a
* {@code JitsiMeetView} may (1) handle the invocation of the back button
* during a conference by leaving the conference and (2) not handle the
* invocation when not in a conference.
*/
public class DefaultHardwareBackBtnHandlerImpl
implements DefaultHardwareBackBtnHandler {
class DefaultHardwareBackBtnHandlerImpl implements DefaultHardwareBackBtnHandler {
/**
* The {@code Activity} to which the default handling of the back button

View File

@@ -1,4 +1,4 @@
package org.jitsi.meet.sdk.dropbox;
package org.jitsi.meet.sdk;
import android.content.Context;
import android.content.pm.ApplicationInfo;
@@ -20,6 +20,7 @@ import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.dropbox.core.android.Auth;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.module.annotations.ReactModule;
import java.util.HashMap;
import java.util.Map;
@@ -27,9 +28,13 @@ import java.util.Map;
/**
* Implements the react-native module for the dropbox integration.
*/
public class Dropbox
@ReactModule(name = DropboxModule.NAME)
class DropboxModule
extends ReactContextBaseJavaModule
implements LifecycleEventListener {
public static final String NAME = "Dropbox";
private String appKey;
private String clientId;
@@ -38,12 +43,14 @@ public class Dropbox
private Promise promise;
public Dropbox(ReactApplicationContext reactContext) {
public DropboxModule(ReactApplicationContext reactContext) {
super(reactContext);
String pkg = reactContext.getApplicationContext().getPackageName();
int resId = reactContext.getResources()
.getIdentifier("dropbox_app_key", "string", pkg);
appKey
= reactContext.getString(
org.jitsi.meet.sdk.R.string.dropbox_app_key);
= reactContext.getString(resId);
isEnabled = !TextUtils.isEmpty(appKey);
clientId = generateClientId();
@@ -129,7 +136,7 @@ public class Dropbox
@Override
public String getName() {
return "Dropbox";
return NAME;
}
/**

View File

@@ -22,14 +22,18 @@ import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.module.annotations.ReactModule;
/**
* Module implementing an API for sending events from JavaScript to native code.
*/
@ReactModule(name = ExternalAPIModule.NAME)
class ExternalAPIModule
extends ReactContextBaseJavaModule {
private static final String TAG = ExternalAPIModule.class.getSimpleName();
public static final String NAME = "ExternalAPI";
private static final String TAG = NAME;
/**
* Initializes a new module instance. There shall be a single instance of
@@ -49,7 +53,7 @@ class ExternalAPIModule
*/
@Override
public String getName() {
return "ExternalAPI";
return NAME;
}
/**

View File

@@ -0,0 +1,63 @@
/*
* Copyright @ 2018-present 8x8, Inc.
* Copyright @ 2017-2018 Atlassian Pty Ltd
*
* 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.
*/
package org.jitsi.meet.sdk;
import android.os.Bundle;
import com.facebook.react.ReactInstanceManager;
public class JitsiMeet {
/**
* Default {@link JitsiMeetConferenceOptions} which will be used for all conferences. When
* joining a conference these options will be merged with the ones passed to
* {@link JitsiMeetView} join().
*/
private static JitsiMeetConferenceOptions defaultConferenceOptions;
public static JitsiMeetConferenceOptions getDefaultConferenceOptions() {
return defaultConferenceOptions;
}
public static void setDefaultConferenceOptions(JitsiMeetConferenceOptions options) {
defaultConferenceOptions = options;
}
/**
* Helper to get the default conference options as a {@link Bundle}.
*
* @return a {@link Bundle} with the default conference options.
*/
static Bundle getDefaultProps() {
if (defaultConferenceOptions != null) {
return defaultConferenceOptions.asProps();
}
return new Bundle();
}
/**
* Used in development mode. It displays the React Native development menu.
*/
public static void showDevOptions() {
ReactInstanceManager reactInstanceManager
= ReactInstanceManagerHolder.getReactInstanceManager();
if (reactInstanceManager != null) {
reactInstanceManager.showDevOptionsDialog();
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright @ 2017-present Atlassian Pty Ltd
* 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.
@@ -16,330 +16,180 @@
package org.jitsi.meet.sdk;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.view.KeyEvent;
import android.support.v4.app.FragmentActivity;
import android.util.Log;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
import com.facebook.react.modules.core.PermissionListener;
import java.net.URL;
import java.util.Map;
/**
* Base Activity for applications integrating Jitsi Meet at a higher level. It
* contains all the required wiring between the {@code JitsiMeetView} and
* the Activity lifecycle methods already implemented.
*
* In this activity we use a single {@code JitsiMeetView} instance. This
* instance gives us access to a view which displays the welcome page and the
* conference itself. All lifetime methods associated with this Activity are
* hooked to the React Native subsystem via proxy calls through the
* {@code JitsiMeetView} static methods.
* A base activity for SDK users to embed. It uses {@link JitsiMeetFragment} to do the heavy
* lifting and wires the remaining Activity lifecycle methods so it works out of the box.
*/
public class JitsiMeetActivity
extends AppCompatActivity implements JitsiMeetActivityInterface {
public class JitsiMeetActivity extends FragmentActivity
implements JitsiMeetActivityInterface, JitsiMeetViewListener {
/**
* The request code identifying requests for the permission to draw on top
* of other apps. The value must be 16-bit and is arbitrarily chosen here.
*/
private static final int OVERLAY_PERMISSION_REQUEST_CODE
= (int) (Math.random() * Short.MAX_VALUE);
protected static final String TAG = JitsiMeetActivity.class.getSimpleName();
/**
* The default behavior of this {@code JitsiMeetActivity} upon invoking the
* back button if {@link #view} does not handle the invocation.
*/
private DefaultHardwareBackBtnHandler defaultBackButtonImpl;
public static final String ACTION_JITSI_MEET_CONFERENCE = "org.jitsi.meet.CONFERENCE";
public static final String JITSI_MEET_CONFERENCE_OPTIONS = "JitsiMeetConferenceOptions";
/**
* The default base {@code URL} used to join a conference when a partial URL
* (e.g. a room name only) is specified. The value is used only while
* {@link #view} equals {@code null}.
*/
private URL defaultURL;
// Helpers for starting the activity
//
/**
* Instance of the {@link JitsiMeetView} which this activity will display.
*/
private JitsiMeetView view;
/**
* Whether Picture-in-Picture is enabled. The value is used only while
* {@link #view} equals {@code null}.
*/
private Boolean pictureInPictureEnabled;
/**
* Whether the Welcome page is enabled. The value is used only while
* {@link #view} equals {@code null}.
*/
private boolean welcomePageEnabled;
private boolean canRequestOverlayPermission() {
return
BuildConfig.DEBUG
&& Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
&& getApplicationInfo().targetSdkVersion
>= Build.VERSION_CODES.M;
public static void launch(Context context, JitsiMeetConferenceOptions options) {
Intent intent = new Intent(context, JitsiMeetActivity.class);
intent.setAction(ACTION_JITSI_MEET_CONFERENCE);
intent.putExtra(JITSI_MEET_CONFERENCE_OPTIONS, options);
context.startActivity(intent);
}
/**
*
* @see JitsiMeetView#getDefaultURL()
*/
public URL getDefaultURL() {
return view == null ? defaultURL : view.getDefaultURL();
public static void launch(Context context, String url) {
JitsiMeetConferenceOptions options
= new JitsiMeetConferenceOptions.Builder().setRoom(url).build();
launch(context, options);
}
/**
* Initializes the {@link #view} of this {@code JitsiMeetActivity} with a
* new {@link JitsiMeetView} instance.
*/
private void initializeContentView() {
JitsiMeetView view = initializeView();
if (view != null) {
// XXX Allow extenders who override initializeView() to configure
// the view before the first loadURL(). Probably works around a
// problem related to ReactRootView#setAppProperties().
view.loadURL(null);
this.view = view;
setContentView(this.view);
}
}
/**
* Initializes a new {@link JitsiMeetView} instance.
*
* @return a new {@code JitsiMeetView} instance.
*/
protected JitsiMeetView initializeView() {
JitsiMeetView view = new JitsiMeetView(this);
// XXX Before calling JitsiMeetView#loadURL, make sure to call whatever
// is documented to need such an order in order to take effect:
view.setDefaultURL(defaultURL);
if (pictureInPictureEnabled != null) {
view.setPictureInPictureEnabled(
pictureInPictureEnabled.booleanValue());
}
view.setWelcomePageEnabled(welcomePageEnabled);
return view;
}
/**
*
* @see JitsiMeetView#isPictureInPictureEnabled()
*/
public boolean isPictureInPictureEnabled() {
return
view == null
? pictureInPictureEnabled
: view.isPictureInPictureEnabled();
}
/**
*
* @see JitsiMeetView#isWelcomePageEnabled()
*/
public boolean isWelcomePageEnabled() {
return view == null ? welcomePageEnabled : view.isWelcomePageEnabled();
}
/**
* Loads the given URL and displays the conference. If the specified URL is
* null, the welcome page is displayed instead.
*
* @param url The conference URL.
*/
public void loadURL(@Nullable URL url) {
view.loadURL(url);
}
@Override
protected void onActivityResult(
int requestCode,
int resultCode,
Intent data) {
if (requestCode == OVERLAY_PERMISSION_REQUEST_CODE
&& canRequestOverlayPermission()) {
if (Settings.canDrawOverlays(this)) {
initializeContentView();
}
return;
}
ReactActivityLifecycleCallbacks.onActivityResult(
this, requestCode, resultCode, data);
}
@Override
public void onBackPressed() {
if (!ReactActivityLifecycleCallbacks.onBackPressed()) {
// JitsiMeetView didn't handle the invocation of the back button.
// Generally, an Activity extender would very likely want to invoke
// Activity#onBackPressed(). For the sake of consistency with
// JitsiMeetView and within the Jitsi Meet SDK for Android though,
// JitsiMeetActivity does what JitsiMeetView would've done if it
// were able to handle the invocation.
if (defaultBackButtonImpl == null) {
super.onBackPressed();
} else {
defaultBackButtonImpl.invokeDefaultOnBackPressed();
}
}
}
// Overrides
//
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// In Debug builds React needs permission to write over other apps in
// order to display the warning and error overlays.
if (canRequestOverlayPermission() && !Settings.canDrawOverlays(this)) {
Intent intent
= new Intent(
Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
Uri.parse("package:" + getPackageName()));
setContentView(R.layout.activity_jitsi_meet);
startActivityForResult(intent, OVERLAY_PERMISSION_REQUEST_CODE);
return;
if (!extraInitialize()) {
initialize();
}
initializeContentView();
}
@Override
protected void onDestroy() {
super.onDestroy();
public void finish() {
getJitsiView().leave();
if (view != null) {
view.dispose();
view = null;
}
ReactActivityLifecycleCallbacks.onHostDestroy(this);
super.finish();
}
// ReactAndroid/src/main/java/com/facebook/react/ReactActivity.java
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
ReactInstanceManager reactInstanceManager;
// Helper methods
//
if (!super.onKeyUp(keyCode, event)
&& BuildConfig.DEBUG
&& (reactInstanceManager
= ReactInstanceManagerHolder.getReactInstanceManager())
!= null
&& keyCode == KeyEvent.KEYCODE_MENU) {
reactInstanceManager.showDevOptionsDialog();
return true;
protected JitsiMeetView getJitsiView() {
JitsiMeetFragment fragment
= (JitsiMeetFragment) getSupportFragmentManager().findFragmentById(R.id.jitsiFragment);
return fragment.getJitsiView();
}
protected void join(@Nullable String url) {
JitsiMeetConferenceOptions options
= new JitsiMeetConferenceOptions.Builder()
.setRoom(url)
.build();
join(options);
}
protected void join(JitsiMeetConferenceOptions options) {
getJitsiView().join(options);
}
private @Nullable JitsiMeetConferenceOptions getConferenceOptions(Intent intent) {
String action = intent.getAction();
if (Intent.ACTION_VIEW.equals(action)) {
Uri uri = intent.getData();
if (uri != null) {
return new JitsiMeetConferenceOptions.Builder().setRoom(uri.toString()).build();
}
} else if (ACTION_JITSI_MEET_CONFERENCE.equals(action)) {
return intent.getParcelableExtra(JITSI_MEET_CONFERENCE_OPTIONS);
}
return null;
}
/**
* Helper function called during activity initialization. If {@code true} is returned, the
* initialization is delayed and the {@link JitsiMeetActivity#initialize()} method is not
* called. In this case, it's up to the subclass to call the initialize method when ready.
*
* This is mainly required so we do some extra initialization in the Jitsi Meet app.
*
* @return {@code true} if the initialization will be delayed, {@code false} otherwise.
*/
protected boolean extraInitialize() {
return false;
}
protected void initialize() {
// Listen for conference events.
getJitsiView().setListener(this);
// Join the room specified by the URL the app was launched with.
// Joining without the room option displays the welcome page.
join(getConferenceOptions(getIntent()));
}
// Activity lifecycle methods
//
@Override
public void onBackPressed() {
JitsiMeetActivityDelegate.onBackPressed();
}
@Override
public void onNewIntent(Intent intent) {
// XXX At least twice we received bug reports about malfunctioning
// loadURL in the Jitsi Meet SDK while the Jitsi Meet app seemed to
// functioning as expected in our testing. But that was to be expected
// because the app does not exercise loadURL. In order to increase the
// test coverage of loadURL, channel deep linking through loadURL.
Uri uri;
JitsiMeetConferenceOptions options;
if (Intent.ACTION_VIEW.equals(intent.getAction())
&& (uri = intent.getData()) != null
&& JitsiMeetView.loadURLStringInViews(uri.toString())) {
if ((options = getConferenceOptions(intent)) != null) {
join(options);
return;
}
ReactActivityLifecycleCallbacks.onNewIntent(intent);
}
// https://developer.android.com/reference/android/support/v4/app/ActivityCompat.OnRequestPermissionsResultCallback
@Override
public void onRequestPermissionsResult(
final int requestCode,
final String[] permissions,
final int[] grantResults) {
ReactActivityLifecycleCallbacks.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
@Override
protected void onResume() {
super.onResume();
defaultBackButtonImpl = new DefaultHardwareBackBtnHandlerImpl(this);
ReactActivityLifecycleCallbacks.onHostResume(this, defaultBackButtonImpl);
}
@Override
public void onStop() {
super.onStop();
ReactActivityLifecycleCallbacks.onHostPause(this);
defaultBackButtonImpl = null;
JitsiMeetActivityDelegate.onNewIntent(intent);
}
@Override
protected void onUserLeaveHint() {
if (view != null) {
view.enterPictureInPicture();
}
getJitsiView().enterPictureInPicture();
}
/**
* Implementation of the {@code PermissionAwareActivity} interface.
*/
// JitsiMeetActivityInterface
//
@Override
public void requestPermissions(String[] permissions, int requestCode, PermissionListener listener) {
ReactActivityLifecycleCallbacks.requestPermissions(this, permissions, requestCode, listener);
JitsiMeetActivityDelegate.requestPermissions(this, permissions, requestCode, listener);
}
/**
*
* @see JitsiMeetView#setDefaultURL(URL)
*/
public void setDefaultURL(URL defaultURL) {
if (view == null) {
this.defaultURL = defaultURL;
} else {
view.setDefaultURL(defaultURL);
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
JitsiMeetActivityDelegate.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
/**
*
* @see JitsiMeetView#setPictureInPictureEnabled(boolean)
*/
public void setPictureInPictureEnabled(boolean pictureInPictureEnabled) {
if (view == null) {
this.pictureInPictureEnabled
= Boolean.valueOf(pictureInPictureEnabled);
} else {
view.setPictureInPictureEnabled(pictureInPictureEnabled);
}
// JitsiMeetViewListener
//
@Override
public void onConferenceJoined(Map<String, Object> data) {
Log.d(TAG, "Conference joined: " + data);
}
/**
*
* @see JitsiMeetView#setWelcomePageEnabled(boolean)
*/
public void setWelcomePageEnabled(boolean welcomePageEnabled) {
if (view == null) {
this.welcomePageEnabled = welcomePageEnabled;
} else {
view.setWelcomePageEnabled(welcomePageEnabled);
}
@Override
public void onConferenceTerminated(Map<String, Object> data) {
Log.d(TAG, "Conference terminated: " + data);
finish();
}
@Override
public void onConferenceWillJoin(Map<String, Object> data) {
Log.d(TAG, "Conference will join: " + data);
}
}

View File

@@ -1,5 +1,6 @@
/*
* Copyright @ 2018-present Atlassian Pty Ltd
* Copyright @ 2019-present 8x8, Inc.
* Copyright @ 2018 Atlassian Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,7 +25,6 @@ import android.os.Build;
import com.calendarevents.CalendarEventsPackage;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.bridge.Callback;
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
import com.facebook.react.modules.core.PermissionListener;
/**
@@ -32,7 +32,13 @@ import com.facebook.react.modules.core.PermissionListener;
* {@link Activity} lifecycle methods in order for the React side to be aware of
* it.
*/
public class ReactActivityLifecycleCallbacks {
public class JitsiMeetActivityDelegate {
/**
* Needed for making sure this class working with the "PermissionsAndroid"
* React Native module.
*/
private static PermissionListener permissionListener;
private static Callback permissionsCallback;
/**
* {@link Activity} lifecycle method which should be called from
@@ -57,13 +63,6 @@ public class ReactActivityLifecycleCallbacks {
}
}
/**
* Needed for making sure this class working with the "PermissionsAndroid"
* React Native module.
*/
private static PermissionListener permissionListener;
private static Callback permissionsCallback;
/**
* {@link Activity} lifecycle method which should be called from
* {@link Activity#onBackPressed} so we can do the required internal
@@ -73,15 +72,12 @@ public class ReactActivityLifecycleCallbacks {
* otherwise. If {@code false}, the application should call the
* {@code super}'s implementation.
*/
public static boolean onBackPressed() {
public static void onBackPressed() {
ReactInstanceManager reactInstanceManager
= ReactInstanceManagerHolder.getReactInstanceManager();
if (reactInstanceManager == null) {
return false;
} else {
if (reactInstanceManager != null) {
reactInstanceManager.onBackPressed();
return true;
}
}
@@ -123,25 +119,11 @@ public class ReactActivityLifecycleCallbacks {
* @param activity {@code Activity} being resumed.
*/
public static void onHostResume(Activity activity) {
onHostResume(activity, new DefaultHardwareBackBtnHandlerImpl(activity));
}
/**
* {@link Activity} lifecycle method which should be called from
* {@code Activity#onResume} so we can do the required internal processing.
*
* @param activity {@code Activity} being resumed.
* @param defaultBackButtonImpl a {@link DefaultHardwareBackBtnHandler} to
* handle invoking the back button if no {@link BaseReactView} handles it.
*/
public static void onHostResume(
Activity activity,
DefaultHardwareBackBtnHandler defaultBackButtonImpl) {
ReactInstanceManager reactInstanceManager
= ReactInstanceManagerHolder.getReactInstanceManager();
if (reactInstanceManager != null) {
reactInstanceManager.onHostResume(activity, defaultBackButtonImpl);
reactInstanceManager.onHostResume(activity, new DefaultHardwareBackBtnHandlerImpl(activity));
}
if (permissionsCallback != null) {

View File

@@ -6,7 +6,7 @@ import com.facebook.react.modules.core.PermissionAwareActivity;
/**
* This interface serves as the umbrella interface that applications not using
* {@code JitsiMeetActivity} must implement in order to ensure full
* {@code JitsiMeetFragment} must implement in order to ensure full
* functionality.
*/
public interface JitsiMeetActivityInterface

View File

@@ -0,0 +1,296 @@
/*
* 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.
*/
package org.jitsi.meet.sdk;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import java.net.URL;
/**
* This class represents the options when joining a Jitsi Meet conference. The user can create an
* instance by using {@link JitsiMeetConferenceOptions.Builder} and setting the desired options
* there.
*
* The resulting {@link JitsiMeetConferenceOptions} object is immutable and represents how the
* conference will be joined.
*/
public class JitsiMeetConferenceOptions implements Parcelable {
/**
* Server where the conference should take place.
*/
private URL serverURL;
/**
* Room name.
*/
private String room;
/**
* JWT token used for authentication.
*/
private String token;
/**
* Color scheme override, see: https://github.com/jitsi/jitsi-meet/blob/dbedee5e22e5dcf9c92db96ef5bb3c9982fc526d/react/features/base/color-scheme/defaultScheme.js
*/
private Bundle colorScheme;
/**
* Set to {@code true} to join the conference with audio / video muted or to start in audio
* only mode respectively.
*/
private Boolean audioMuted;
private Boolean audioOnly;
private Boolean videoMuted;
/**
* Set to {@code true} to enable the welcome page. Typically SDK users won't need this enabled
* since the host application decides which meeting to join.
*/
private Boolean welcomePageEnabled;
/**
* Class used to build the immutable {@link JitsiMeetConferenceOptions} object.
*/
public static class Builder {
private URL serverURL;
private String room;
private String token;
private Bundle colorScheme;
private Boolean audioMuted;
private Boolean audioOnly;
private Boolean videoMuted;
private Boolean welcomePageEnabled;
public Builder() {
}
/**\
* Sets the server URL.
* @param url - {@link URL} of the server where the conference should take place.
* @return - The {@link Builder} object itself so the method calls can be chained.
*/
public Builder setServerURL(URL url) {
this.serverURL = url;
return this;
}
/**
* Sets the room where the conference will take place.
* @param room - Name of the room.
* @return - The {@link Builder} object itself so the method calls can be chained.
*/
public Builder setRoom(String room) {
this.room = room;
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.
* @return - The {@link Builder} object itself so the method calls can be chained.
*/
public Builder setToken(String token) {
this.token = token;
return this;
}
/**
* Sets the color scheme override so the app is themed. See:
* https://github.com/jitsi/jitsi-meet/blob/master/react/features/base/color-scheme/defaultScheme.js
* for the structure.
* @param colorScheme - A color scheme to be applied to the app.
* @return - The {@link Builder} object itself so the method calls can be chained.
*/
public Builder setColorScheme(Bundle colorScheme) {
this.colorScheme = colorScheme;
return this;
}
/**
* Indicates the conference will be joined with the microphone muted.
* @param muted - Muted indication.
* @return - The {@link Builder} object itself so the method calls can be chained.
*/
public Builder setAudioMuted(boolean muted) {
this.audioMuted = muted;
return this;
}
/**
* Indicates the conference will be joined in audio-only mode. In this mode no video is
* sent or received.
* @param audioOnly - Audio-mode indicator.
* @return - The {@link Builder} object itself so the method calls can be chained.
*/
public Builder setAudioOnly(boolean audioOnly) {
this.audioOnly = audioOnly;
return this;
}
/**
* Indicates the conference will be joined with the camera muted.
* @param videoMuted - Muted indication.
* @return - The {@link Builder} object itself so the method calls can be chained.
*/
public Builder setVideoMuted(boolean videoMuted) {
this.videoMuted = videoMuted;
return this;
}
/**
* Sets the welcome page enabled / disabled. The welcome page lists recent meetings and
* calendar appointments and it's meant to be used by standalone applications. Defaults to
* false.
* @param enabled - Whether the welcome page should be enabled or not.
* @return - The {@link Builder} object itself so the method calls can be chained.
*/
public Builder setWelcomePageEnabled(boolean enabled) {
this.welcomePageEnabled = enabled;
return this;
}
/**
* Builds the immutable {@link JitsiMeetConferenceOptions} object with the configuration
* that this {@link Builder} instance specified.
* @return - The built {@link JitsiMeetConferenceOptions} object.
*/
public JitsiMeetConferenceOptions build() {
JitsiMeetConferenceOptions options = new JitsiMeetConferenceOptions();
options.serverURL = this.serverURL;
options.room = this.room;
options.token = this.token;
options.colorScheme = this.colorScheme;
options.audioMuted = this.audioMuted;
options.audioOnly = this.audioOnly;
options.videoMuted = this.videoMuted;
options.welcomePageEnabled = this.welcomePageEnabled;
return options;
}
}
private JitsiMeetConferenceOptions() {
}
private JitsiMeetConferenceOptions(Parcel in) {
room = in.readString();
token = in.readString();
colorScheme = in.readBundle();
byte tmpAudioMuted = in.readByte();
audioMuted = tmpAudioMuted == 0 ? null : tmpAudioMuted == 1;
byte tmpAudioOnly = in.readByte();
audioOnly = tmpAudioOnly == 0 ? null : tmpAudioOnly == 1;
byte tmpVideoMuted = in.readByte();
videoMuted = tmpVideoMuted == 0 ? null : tmpVideoMuted == 1;
byte tmpWelcomePageEnabled = in.readByte();
welcomePageEnabled = tmpWelcomePageEnabled == 0 ? null : tmpWelcomePageEnabled == 1;
}
Bundle asProps() {
Bundle props = new Bundle();
if (colorScheme != null) {
props.putBundle("colorScheme", colorScheme);
}
if (welcomePageEnabled != null) {
props.putBoolean("welcomePageEnabled", welcomePageEnabled);
}
// TODO: get rid of this.
props.putBoolean("pictureInPictureEnabled", true);
Bundle config = new Bundle();
if (audioMuted != null) {
config.putBoolean("startWithAudioMuted", audioMuted);
}
if (audioOnly != null) {
config.putBoolean("startAudioOnly", audioOnly);
}
if (videoMuted != null) {
config.putBoolean("startWithVideoMuted", videoMuted);
}
Bundle urlProps = new Bundle();
// The room is fully qualified
if (room != null && room.contains("://")) {
urlProps.putString("url", room);
} else {
if (serverURL != null) {
urlProps.putString("serverURL", serverURL.toString());
}
if (room != null) {
urlProps.putString("room", room);
}
}
if (token != null) {
urlProps.putString("jwt", token);
}
urlProps.putBundle("config", config);
props.putBundle("url", urlProps);
return props;
}
// Parcelable interface
//
public static final Creator<JitsiMeetConferenceOptions> CREATOR = new Creator<JitsiMeetConferenceOptions>() {
@Override
public JitsiMeetConferenceOptions createFromParcel(Parcel in) {
return new JitsiMeetConferenceOptions(in);
}
@Override
public JitsiMeetConferenceOptions[] newArray(int size) {
return new JitsiMeetConferenceOptions[size];
}
};
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(room);
dest.writeString(token);
dest.writeBundle(colorScheme);
dest.writeByte((byte) (audioMuted == null ? 0 : audioMuted ? 1 : 2));
dest.writeByte((byte) (audioOnly == null ? 0 : audioOnly ? 1 : 2));
dest.writeByte((byte) (videoMuted == null ? 0 : videoMuted ? 1 : 2));
dest.writeByte((byte) (welcomePageEnabled == null ? 0 : welcomePageEnabled ? 1 : 2));
}
@Override
public int describeContents() {
return 0;
}
}

View File

@@ -0,0 +1,97 @@
/*
* Copyright @ 2019-present 8x8, Inc.
* Copyright @ 2017-2018 Atlassian Pty Ltd
*
* 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.
*/
package org.jitsi.meet.sdk;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import java.net.URL;
/**
* Base {@link Fragment} for applications integrating Jitsi Meet at a higher level. It
* contains all the required wiring between the {@code JitsiMeetView} and
* the Fragment lifecycle methods already implemented.
*
* In this fragment we use a single {@code JitsiMeetView} instance. This
* instance gives us access to a view which displays the welcome page and the
* conference itself. All lifecycle methods associated with this Fragment are
* hooked to the React Native subsystem via proxy calls through the
* {@code JitsiMeetActivityDelegate} static methods.
*/
public class JitsiMeetFragment extends Fragment {
/**
* Instance of the {@link JitsiMeetView} which this activity will display.
*/
private JitsiMeetView view;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
return this.view = new JitsiMeetView(getActivity());
}
public JitsiMeetView getJitsiView() {
return view;
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
JitsiMeetActivityDelegate.onActivityResult(
getActivity(), requestCode, resultCode, data);
}
@Override
public void onDestroyView() {
if (view != null) {
view.dispose();
view = null;
}
super.onDestroyView();
}
@Override
public void onDestroy() {
super.onDestroy();
JitsiMeetActivityDelegate.onHostDestroy(getActivity());
}
@Override
public void onResume() {
super.onResume();
JitsiMeetActivityDelegate.onHostResume(getActivity());
}
@Override
public void onStop() {
super.onStop();
JitsiMeetActivityDelegate.onHostPause(getActivity());
}
}

View File

@@ -1,5 +1,6 @@
/*
* Copyright @ 2017-present Atlassian Pty Ltd
* Copyright @ 2018-present 8x8, Inc.
* Copyright @ 2017-2018 Atlassian Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,14 +25,11 @@ import android.util.Log;
import com.facebook.react.bridge.ReadableMap;
import org.jitsi.meet.sdk.invite.InviteController;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.Map;
public class JitsiMeetView
extends BaseReactView<JitsiMeetViewListener> {
public class JitsiMeetView extends BaseReactView<JitsiMeetViewListener> {
/**
* The {@code Method}s of {@code JitsiMeetViewListener} by event name i.e.
@@ -46,51 +44,6 @@ public class JitsiMeetView
*/
private static final String TAG = JitsiMeetView.class.getSimpleName();
/**
* Loads a specific URL {@code String} in all existing
* {@code JitsiMeetView}s.
*
* @param urlString he URL {@code String} to load in all existing
* {@code JitsiMeetView}s.
* @return If the specified {@code urlString} was submitted for loading in
* at least one {@code JitsiMeetView}, then {@code true}; otherwise,
* {@code false}.
*/
public static boolean loadURLStringInViews(String urlString) {
boolean loaded = false;
synchronized (views) {
for (BaseReactView view : views) {
if (view instanceof JitsiMeetView) {
((JitsiMeetView)view).loadURLString(urlString);
loaded = true;
}
}
}
return loaded;
}
/**
* The default base {@code URL} used to join a conference when a partial URL
* (e.g. a room name only) is specified to {@link #loadURLString(String)} or
* {@link #loadURLObject(Bundle)}.
*/
private URL defaultURL;
/**
* The entry point into the invite feature of Jitsi Meet. The Java
* counterpart of the JavaScript {@code InviteButton}.
*/
private final InviteController inviteController;
/**
* Whether Picture-in-Picture is enabled. If {@code null}, defaults to
* {@code true} iff the Android platform supports Picture-in-Picture
* natively.
*/
private Boolean pictureInPictureEnabled;
/**
* The URL of the current conference.
*/
@@ -99,16 +52,60 @@ public class JitsiMeetView
private volatile String url;
/**
* Whether the Welcome page is enabled.
* Helper method to recursively merge 2 {@link Bundle} objects representing React Native props.
*
* @param a - The first {@link Bundle}.
* @param b - The second {@link Bundle}.
* @return The merged {@link Bundle} object.
*/
private boolean welcomePageEnabled;
private static Bundle mergeProps(@Nullable Bundle a, @Nullable Bundle b) {
Bundle result = new Bundle();
if (a == null) {
if (b != null) {
result.putAll(b);
}
return result;
}
if (b == null) {
result.putAll(a);
return result;
}
// Start by putting all of a in the result.
result.putAll(a);
// Iterate over each key in b and override if appropriate.
for (String key : b.keySet()) {
Object bValue = b.get(key);
Object aValue = a.get(key);
String valueType = bValue.getClass().getSimpleName();
if (valueType.contentEquals("Boolean")) {
result.putBoolean(key, (Boolean)bValue);
} else if (valueType.contentEquals("String")) {
result.putString(key, (String)bValue);
} else if (valueType.contentEquals("Bundle")) {
result.putBundle(key, mergeProps((Bundle)aValue, (Bundle)bValue));
} else {
throw new RuntimeException("Unsupported type: " + valueType);
}
}
return result;
}
public JitsiMeetView(@NonNull Context context) {
super(context);
// The entry point into the invite feature of Jitsi Meet. The Java
// counterpart of the JavaScript InviteButton.
inviteController = new InviteController(externalAPIScope);
// Check if the parent Activity implements JitsiMeetActivityInterface,
// otherwise things may go wrong.
if (!(context instanceof JitsiMeetActivityInterface)) {
throw new RuntimeException("Enclosing Activity must implement JitsiMeetActivityInterface");
}
}
/**
@@ -121,176 +118,59 @@ public class JitsiMeetView
* page.
*/
public void enterPictureInPicture() {
if (isPictureInPictureEnabled() && getURL() != null) {
PictureInPictureModule pipModule
= ReactInstanceManagerHolder.getNativeModule(
PictureInPictureModule pipModule
= ReactInstanceManagerHolder.getNativeModule(
PictureInPictureModule.class);
if (pipModule != null) {
try {
pipModule.enterPictureInPicture();
} catch (RuntimeException re) {
Log.e(TAG, "onUserLeaveHint: failed to enter PiP mode", re);
}
if (pipModule != null
&& PictureInPictureModule.isPictureInPictureSupported()
&& this.url != null) {
try {
pipModule.enterPictureInPicture();
} catch (RuntimeException re) {
Log.e(TAG, "failed to enter PiP mode", re);
}
}
}
/**
* Gets the default base {@code URL} used to join a conference when a
* partial URL (e.g. a room name only) is specified to
* {@link #loadURLString(String)} or {@link #loadURLObject(Bundle)}. If not
* set or if set to {@code null}, the default built in JavaScript is used:
* https://meet.jit.si
*
* @return The default base {@code URL} or {@code null}.
* Joins the conference specified by the given {@link JitsiMeetConferenceOptions}. If there is
* already an active conference, it will be left and the new one will be joined.
* @param options - Description of what conference must be joined and what options will be used
* when doing so.
*/
public URL getDefaultURL() {
return defaultURL;
public void join(@Nullable JitsiMeetConferenceOptions options) {
setProps(options != null ? options.asProps() : new Bundle());
}
/**
* Gets the {@link InviteController} which represents the entry point into
* the invite feature of Jitsi Meet and is the Java counterpart of the
* JavaScript {@code InviteButton}.
*
* @return the {@link InviteController} which represents the entry point
* into the invite feature of Jitsi Meet and is the Java counterpart of the
* JavaScript {@code InviteButton}
* Leaves the currently active conference.
*/
public InviteController getInviteController() {
return inviteController;
public void leave() {
setProps(new Bundle());
}
/**
* Gets the URL of the current conference.
*
* XXX The method is meant for internal purposes only at the time of this
* writing because there is no equivalent API on iOS.
*
* @return the URL {@code String} of the current conference if any;
* otherwise, {@code null}.
* Helper method to set the React Native props.
* @param newProps - New props to be set on the React Native view.
*/
String getURL() {
return url;
}
private void setProps(@NonNull Bundle newProps) {
// Merge the default options with the newly provided ones.
Bundle props = mergeProps(JitsiMeet.getDefaultProps(), newProps);
/**
* Gets whether Picture-in-Picture is enabled. Picture-in-Picture is
* natively supported on Android API >= 26 (Oreo), so it should not be
* enabled on older platform versions.
*
* @return If Picture-in-Picture is enabled, {@code true}; {@code false},
* otherwise.
*/
public boolean isPictureInPictureEnabled() {
return
PictureInPictureModule.isPictureInPictureSupported()
&& (pictureInPictureEnabled == null
|| pictureInPictureEnabled);
}
/**
* Gets whether the Welcome page is enabled. If {@code true}, the Welcome
* page is rendered when this {@code JitsiMeetView} is not at a URL
* identifying a Jitsi Meet conference/room.
*
* @return {@code true} if the Welcome page is enabled; otherwise,
* {@code false}.
*/
public boolean isWelcomePageEnabled() {
return welcomePageEnabled;
}
/**
* Loads a specific {@link URL} which may identify a conference to join. If
* the specified {@code URL} is {@code null} and the Welcome page is
* enabled, the Welcome page is displayed instead.
*
* @param url The {@code URL} to load which may identify a conference to
* join.
*/
public void loadURL(@Nullable URL url) {
loadURLString(url == null ? null : url.toString());
}
/**
* Loads a specific URL which may identify a conference to join. The URL is
* specified in the form of a {@link Bundle} of properties which (1)
* internally are sufficient to construct a URL {@code String} while (2)
* abstracting the specifics of constructing the URL away from API
* clients/consumers. If the specified URL is {@code null} and the Welcome
* page is enabled, the Welcome page is displayed instead.
*
* @param urlObject The URL to load which may identify a conference to join.
*/
public void loadURLObject(@Nullable Bundle urlObject) {
Bundle props = new Bundle();
// defaultURL
if (defaultURL != null) {
props.putString("defaultURL", defaultURL.toString());
}
// inviteController
InviteController inviteController = getInviteController();
if (inviteController != null) {
props.putBoolean(
"addPeopleEnabled",
inviteController.isAddPeopleEnabled());
props.putBoolean(
"dialOutEnabled",
inviteController.isDialOutEnabled());
}
// pictureInPictureEnabled
props.putBoolean(
"pictureInPictureEnabled",
isPictureInPictureEnabled());
// url
if (urlObject != null) {
props.putBundle("url", urlObject);
}
// welcomePageEnabled
props.putBoolean("welcomePageEnabled", welcomePageEnabled);
// XXX The method loadURLObject: is supposed to be imperative i.e.
// XXX The setProps() method is supposed to be imperative i.e.
// a second invocation with one and the same URL is expected to join
// the respective conference again if the first invocation was followed
// by leaving the conference. However, React and, respectively,
// appProperties/initialProperties are declarative expressions i.e. one
// and the same URL will not trigger componentWillReceiveProps in the
// and the same URL will not trigger an automatic re-render in the
// JavaScript source code. The workaround implemented bellow introduces
// imperativeness in React Component props by defining a unique value
// per loadURLObject: invocation.
// "imperativeness" in React Component props by defining a unique value
// per setProps() invocation.
props.putLong("timestamp", System.currentTimeMillis());
createReactRootView("App", props);
}
/**
* Loads a specific URL {@link String} which may identify a conference to
* join. If the specified URL {@code String} is {@code null} and the Welcome
* page is enabled, the Welcome page is displayed instead.
*
* @param urlString The URL {@code String} to load which may identify a
* conference to join.
*/
public void loadURLString(@Nullable String urlString) {
Bundle urlObject;
if (urlString == null) {
urlObject = null;
} else {
urlObject = new Bundle();
urlObject.putString("url", urlString);
}
loadURLObject(urlObject);
}
/**
* The internal processing for the URL of the current conference set on the
* associated {@link JitsiMeetView}.
@@ -300,18 +180,16 @@ public class JitsiMeetView
* by/associated with the specified {@code eventName}.
*/
private void maybeSetViewURL(String eventName, ReadableMap eventData) {
String url = eventData.getString("url");
switch(eventName) {
case "CONFERENCE_WILL_JOIN":
setURL(eventData.getString("url"));
this.url = url;
break;
case "CONFERENCE_FAILED":
case "CONFERENCE_WILL_LEAVE":
case "LOAD_CONFIG_ERROR":
String url = eventData.getString("url");
if (url != null && url.equals(getURL())) {
setURL(null);
case "CONFERENCE_TERMINATED":
if (url != null && url.equals(this.url)) {
this.url = null;
}
break;
}
@@ -325,7 +203,7 @@ public class JitsiMeetView
* by/associated with the specified {@code name}.
*/
@Override
public void onExternalAPIEvent(String name, ReadableMap data) {
protected void onExternalAPIEvent(String name, ReadableMap data) {
// XXX The JitsiMeetView property URL was introduced in order to address
// an exception in the Picture-in-Picture functionality which arose
// because of delays related to bridging between JavaScript and Java. To
@@ -335,53 +213,4 @@ public class JitsiMeetView
onExternalAPIEvent(LISTENER_METHODS, name, data);
}
/**
* Sets the default base {@code URL} used to join a conference when a
* partial URL (e.g. a room name only) is specified to
* {@link #loadURLString(String)} or {@link #loadURLObject(Bundle)}. Must be
* called before {@link #loadURL(URL)} for it to take effect.
*
* @param defaultURL The {@code URL} to be set as the default base URL.
* @see #getDefaultURL()
*/
public void setDefaultURL(URL defaultURL) {
this.defaultURL = defaultURL;
}
/**
* Sets whether Picture-in-Picture is enabled. Because Picture-in-Picture is
* natively supported only since certain platform versions, specifying
* {@code true} will have no effect on unsupported platform versions.
*
* @param pictureInPictureEnabled To enable Picture-in-Picture,
* {@code true}; otherwise, {@code false}.
*/
public void setPictureInPictureEnabled(boolean pictureInPictureEnabled) {
this.pictureInPictureEnabled = pictureInPictureEnabled;
}
/**
* Sets the URL of the current conference.
*
* XXX The method is meant for internal purposes only. It does not
* {@code loadURL}, it merely remembers the specified URL.
*
* @param url the URL {@code String} which to be set as the URL of the
* current conference.
*/
void setURL(String url) {
this.url = url;
}
/**
* Sets whether the Welcome page is enabled. Must be called before
* {@link #loadURL(URL)} for it to take effect.
*
* @param welcomePageEnabled {@code true} to enable the Welcome page;
* otherwise, {@code false}.
*/
public void setWelcomePageEnabled(boolean welcomePageEnabled) {
this.welcomePageEnabled = welcomePageEnabled;
}
}

View File

@@ -1,51 +0,0 @@
/*
* Copyright @ 2017-present Atlassian Pty Ltd
*
* 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.
*/
package org.jitsi.meet.sdk;
import java.util.Map;
/**
* Implements {@link JitsiMeetViewListener} so apps don't have to add stubs for
* all methods in the interface if they are only interested in some.
*/
public abstract class JitsiMeetViewAdapter
implements JitsiMeetViewListener {
@Override
public void onConferenceFailed(Map<String, Object> data) {
}
@Override
public void onConferenceJoined(Map<String, Object> data) {
}
@Override
public void onConferenceLeft(Map<String, Object> data) {
}
@Override
public void onConferenceWillJoin(Map<String, Object> data) {
}
@Override
public void onConferenceWillLeave(Map<String, Object> data) {
}
@Override
public void onLoadConfigError(Map<String, Object> data) {
}
}

View File

@@ -22,15 +22,6 @@ import java.util.Map;
* Interface for listening to events coming from Jitsi Meet.
*/
public interface JitsiMeetViewListener {
/**
* Called when joining a conference fails or an ongoing conference is
* interrupted due to a failure.
*
* @param data Map with an "error" key describing the problem, and a "url"
* key with the conference URL.
*/
void onConferenceFailed(Map<String, Object> data);
/**
* Called when a conference was joined.
*
@@ -39,11 +30,16 @@ public interface JitsiMeetViewListener {
void onConferenceJoined(Map<String, Object> data);
/**
* Called when the conference was left, typically after hanging up.
* Called when the active conference ends, be it because of user choice or
* because of a failure.
*
* @param data Map with a "url" key with the conference URL.
* @param data Map with an "error" key with the error and a "url" key with
* the conference URL. If the conference finished gracefully no `error`
* key will be present. The possible values for "error" are described here:
* https://github.com/jitsi/lib-jitsi-meet/blob/master/JitsiConnectionErrors.js
* https://github.com/jitsi/lib-jitsi-meet/blob/master/JitsiConferenceErrors.js
*/
void onConferenceLeft(Map<String, Object> data);
void onConferenceTerminated(Map<String, Object> data);
/**
* Called before the conference is joined.
@@ -51,21 +47,4 @@ public interface JitsiMeetViewListener {
* @param data Map with a "url" key with the conference URL.
*/
void onConferenceWillJoin(Map<String, Object> data);
/**
* Called before the conference is left.
*
* @param data Map with a "url" key with the conference URL.
*/
void onConferenceWillLeave(Map<String, Object> data);
/**
* Called when loading the main configuration file from the Jitsi Meet
* deployment fails.
*
* @param data Map with an "error" key with the error and a "url" key with
* the conference URL which necessitated the loading of the configuration
* file.
*/
void onLoadConfigError(Map<String, Object> data);
}

View File

@@ -0,0 +1,58 @@
/*
* Copyright @ 2018-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.
*/
/*
* Based on https://github.com/DylanVann/react-native-locale-detector
*/
package org.jitsi.meet.sdk;
import android.content.Context;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import java.util.HashMap;
import java.util.Map;
/**
* Module which provides information about the system locale.
*/
class LocaleDetector extends ReactContextBaseJavaModule {
public LocaleDetector(ReactApplicationContext reactContext) {
super(reactContext);
}
/**
* Gets a {@code Map} of constants this module exports to JS. Supports JSON
* types.
*
* @return a {@link Map} of constants this module exports to JS
*/
@Override
public Map<String, Object> getConstants() {
Context context = getReactApplicationContext();
HashMap<String,Object> constants = new HashMap<>();
constants.put("locale", context.getResources().getConfiguration().locale.toString());
return constants;
}
@Override
public String getName() {
return "LocaleDetector";
}
}

View File

@@ -16,6 +16,7 @@
package org.jitsi.meet.sdk;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.PictureInPictureParams;
import android.os.Build;
@@ -26,11 +27,15 @@ import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.module.annotations.ReactModule;
@ReactModule(name = PictureInPictureModule.NAME)
class PictureInPictureModule
extends ReactContextBaseJavaModule {
private final static String TAG = "PictureInPicture";
public static final String NAME = "PictureInPicture";
private static final String TAG = NAME;
static boolean isPictureInPictureSupported() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
@@ -53,6 +58,7 @@ class PictureInPictureModule
* including when the activity is not visible (paused or stopped), if the
* screen is locked or if the user has an activity pinned.
*/
@TargetApi(Build.VERSION_CODES.O)
public void enterPictureInPicture() {
if (!isPictureInPictureSupported()) {
throw new IllegalStateException("Picture-in-Picture not supported");
@@ -99,6 +105,6 @@ class PictureInPictureModule
@Override
public String getName() {
return TAG;
return NAME;
}
}

View File

@@ -24,6 +24,7 @@ import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.UiThreadUtil;
import com.facebook.react.module.annotations.ReactModule;
/**
* Module implementing a simple API to enable a proximity sensor-controlled
@@ -31,14 +32,11 @@ import com.facebook.react.bridge.UiThreadUtil;
* object it will dim the screen and disable touch controls. The functionality
* is used with the conference audio-only mode.
*/
@ReactModule(name = ProximityModule.NAME)
class ProximityModule
extends ReactContextBaseJavaModule {
/**
* The name of {@code ProximityModule} to be used in the React Native
* bridge.
*/
private static final String MODULE_NAME = "Proximity";
public static final String NAME = "Proximity";
/**
* This type of wake lock (the one activated by the proximity sensor) has
@@ -74,7 +72,7 @@ class ProximityModule
wakeLock
= powerManager.newWakeLock(
PROXIMITY_SCREEN_OFF_WAKE_LOCK,
MODULE_NAME);
"jitsi:"+NAME);
} catch (Throwable ignored) {
wakeLock = null;
}
@@ -89,7 +87,7 @@ class ProximityModule
*/
@Override
public String getName() {
return MODULE_NAME;
return NAME;
}
/**

View File

@@ -0,0 +1,169 @@
package org.jitsi.meet.sdk;
import android.annotation.SuppressLint;
import android.content.Context;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.RequiresApi;
import android.telecom.DisconnectCause;
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
import android.telecom.VideoProfile;
import android.util.Log;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.module.annotations.ReactModule;
/**
* The react-native side of Jitsi Meet's {@link ConnectionService}. Exposes
* the Java Script API.
*
* @author Pawel Domas
*/
@RequiresApi(api = Build.VERSION_CODES.O)
@ReactModule(name = RNConnectionService.NAME)
class RNConnectionService
extends ReactContextBaseJavaModule {
public static final String NAME = "ConnectionService";
private static final String TAG = ConnectionService.TAG;
/**
* Sets the audio route on all existing {@link android.telecom.Connection}s
*
* @param audioRoute the new audio route to be set. See
* {@link android.telecom.CallAudioState} constants prefixed with "ROUTE_".
*/
@RequiresApi(api = Build.VERSION_CODES.O)
static void setAudioRoute(int audioRoute) {
for (ConnectionService.ConnectionImpl c
: ConnectionService.getConnections()) {
c.setAudioRoute(audioRoute);
}
}
RNConnectionService(ReactApplicationContext reactContext) {
super(reactContext);
}
/**
* Starts a new outgoing call.
*
* @param callUUID - unique call identifier assigned by Jitsi Meet to
* a conference call.
* @param handle - a call handle which by default is Jitsi Meet room's URL.
* @param hasVideo - whether or not user starts with the video turned on.
* @param promise - the Promise instance passed by the React-native bridge,
* so that this method returns a Promise on the JS side.
*
* NOTE regarding the "missingPermission" suppress - SecurityException will
* be handled as part of the Exception try catch block and the Promise will
* be rejected.
*/
@SuppressLint("MissingPermission")
@ReactMethod
public void startCall(
String callUUID,
String handle,
boolean hasVideo,
Promise promise) {
Log.d(TAG,
String.format("startCall UUID=%s, h=%s, v=%s",
callUUID,
handle,
hasVideo));
ReactApplicationContext ctx = getReactApplicationContext();
Uri address = Uri.fromParts(PhoneAccount.SCHEME_SIP, handle, null);
PhoneAccountHandle accountHandle
= ConnectionService.registerPhoneAccount(
getReactApplicationContext(), address, callUUID);
Bundle extras = new Bundle();
extras.putParcelable(
TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,
accountHandle);
extras.putInt(
TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
hasVideo
? VideoProfile.STATE_BIDIRECTIONAL
: VideoProfile.STATE_AUDIO_ONLY);
ConnectionService.registerStartCallPromise(callUUID, promise);
try {
TelecomManager tm
= (TelecomManager) ctx.getSystemService(
Context.TELECOM_SERVICE);
tm.placeCall(address, extras);
} catch (Exception e) {
ConnectionService.unregisterStartCallPromise(callUUID);
promise.reject(e);
}
}
/**
* Called by the JS side of things to mark the call as failed.
*
* @param callUUID - the call's UUID.
*/
@ReactMethod
public void reportCallFailed(String callUUID) {
Log.d(TAG, "reportCallFailed " + callUUID);
ConnectionService.setConnectionDisconnected(
callUUID,
new DisconnectCause(DisconnectCause.ERROR));
}
/**
* Called by the JS side of things to mark the call as disconnected.
*
* @param callUUID - the call's UUID.
*/
@ReactMethod
public void endCall(String callUUID) {
Log.d(TAG, "endCall " + callUUID);
ConnectionService.setConnectionDisconnected(
callUUID,
new DisconnectCause(DisconnectCause.LOCAL));
}
/**
* Called by the JS side of things to mark the call as active.
*
* @param callUUID - the call's UUID.
*/
@ReactMethod
public void reportConnectedOutgoingCall(String callUUID) {
Log.d(TAG, "reportConnectedOutgoingCall " + callUUID);
ConnectionService.setConnectionActive(callUUID);
}
@Override
public String getName() {
return NAME;
}
/**
* Called by the JS side to update the call's state.
*
* @param callUUID - the call's UUID.
* @param callState - the map which carries infor about the current call's
* state. See static fields in {@link ConnectionService.ConnectionImpl}
* prefixed with "KEY_" for the values supported by the Android
* implementation.
*/
@ReactMethod
public void updateCall(String callUUID, ReadableMap callState) {
ConnectionService.updateCall(callUUID, callState);
}
}

View File

@@ -1,43 +0,0 @@
/*
* Copyright @ 2017-present Atlassian Pty Ltd
*
* 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.
*/
package org.jitsi.meet.sdk;
import android.support.annotation.Nullable;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.modules.core.DeviceEventManagerModule;
public class ReactContextUtils {
public static boolean emitEvent(
ReactContext reactContext,
String eventName,
@Nullable Object data) {
if (reactContext == null) {
// XXX If no ReactContext is specified, emit through the
// ReactContext of ReactInstanceManager. ReactInstanceManager
// cooperates with ReactContextUtils i.e. ReactInstanceManager will
// not invoke ReactContextUtils without a ReactContext.
return ReactInstanceManagerHolder.emitEvent(eventName, data);
}
reactContext
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit(eventName, data);
return true;
}
}

View File

@@ -1,5 +1,6 @@
/*
* Copyright @ 2017-present Atlassian Pty Ltd
* Copyright @ 2019-present 8x8, Inc.
* Copyright @ 2017-2018 Atlassian Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,12 +25,20 @@ import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.common.LifecycleState;
import com.facebook.react.devsupport.DevInternalSettings;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
class ReactInstanceManagerHolder {
/**
* FIXME (from linter): Do not place Android context classes in static
* fields (static reference to ReactInstanceManager which has field
* mApplicationContext pointing to Context); this is a memory leak (and
* also breaks Instant Run).
*
* React Native bridge. The instance manager allows embedding applications
* to create multiple root views off the same JavaScript bundle.
*/
@@ -37,18 +46,25 @@ class ReactInstanceManagerHolder {
private static List<NativeModule> createNativeModules(
ReactApplicationContext reactContext) {
return Arrays.<NativeModule>asList(
new AndroidSettingsModule(reactContext),
new AppInfoModule(reactContext),
new AudioModeModule(reactContext),
new ExternalAPIModule(reactContext),
new PictureInPictureModule(reactContext),
new ProximityModule(reactContext),
new WiFiStatsModule(reactContext),
new org.jitsi.meet.sdk.dropbox.Dropbox(reactContext),
new org.jitsi.meet.sdk.invite.InviteModule(reactContext),
new org.jitsi.meet.sdk.net.NAT64AddrInfoModule(reactContext)
);
List<NativeModule> nativeModules
= new ArrayList<>(Arrays.<NativeModule>asList(
new AmplitudeModule(reactContext),
new AndroidSettingsModule(reactContext),
new AppInfoModule(reactContext),
new AudioModeModule(reactContext),
new DropboxModule(reactContext),
new ExternalAPIModule(reactContext),
new LocaleDetector(reactContext),
new PictureInPictureModule(reactContext),
new ProximityModule(reactContext),
new WiFiStatsModule(reactContext),
new org.jitsi.meet.sdk.net.NAT64AddrInfoModule(reactContext)));
if (AudioModeModule.useConnectionService()) {
nativeModules.add(new RNConnectionService(reactContext));
}
return nativeModules;
}
/**
@@ -57,7 +73,7 @@ class ReactInstanceManagerHolder {
* @param eventName {@code String} containing the event name.
* @param data {@code Object} optional ancillary data for the event.
*/
public static boolean emitEvent(
static void emitEvent(
String eventName,
@Nullable Object data) {
ReactInstanceManager reactInstanceManager
@@ -67,15 +83,12 @@ class ReactInstanceManagerHolder {
ReactContext reactContext
= reactInstanceManager.getCurrentReactContext();
return
reactContext != null
&& ReactContextUtils.emitEvent(
reactContext,
eventName,
data);
if (reactContext != null) {
reactContext
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit(eventName, data);
}
}
return false;
}
/**
@@ -126,7 +139,6 @@ class ReactInstanceManagerHolder {
.addPackage(new com.corbt.keepawake.KCKeepAwakePackage())
.addPackage(new com.dylanvann.fastimage.FastImageViewPackage())
.addPackage(new com.facebook.react.shell.MainReactPackage())
.addPackage(new com.i18n.reactnativei18n.ReactNativeI18n())
.addPackage(new com.oblador.vectoricons.VectorIconsPackage())
.addPackage(new com.ocetnik.timer.BackgroundTimerPackage())
.addPackage(new com.oney.WebRTCModule.WebRTCModulePackage())
@@ -144,5 +156,12 @@ class ReactInstanceManagerHolder {
.setUseDeveloperSupport(BuildConfig.DEBUG)
.setInitialLifecycleState(LifecycleState.RESUMED)
.build();
// Disable delta updates on Android, they have caused trouble.
DevInternalSettings devSettings
= (DevInternalSettings)reactInstanceManager.getDevSupportManager().getDevSettings();
if (devSettings != null) {
devSettings.setBundleDeltasEnabled(false);
}
}
}

View File

@@ -17,7 +17,6 @@
package org.jitsi.meet.sdk;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;

View File

@@ -25,6 +25,7 @@ import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.module.annotations.ReactModule;
import org.json.JSONArray;
import org.json.JSONObject;
@@ -43,19 +44,16 @@ import java.util.concurrent.Executors;
* Gathers rssi, signal in percentage, timestamp and the addresses of the wifi
* device.
*/
@ReactModule(name = WiFiStatsModule.NAME)
class WiFiStatsModule
extends ReactContextBaseJavaModule {
/**
* The name of {@code WiFiStatsModule} to be used in the React Native
* bridge.
*/
private static final String MODULE_NAME = "WiFiStats";
public static final String NAME = "WiFiStats";
/**
* The {@code Log} tag {@code WiFiStatsModule} is to log messages with.
*/
static final String TAG = MODULE_NAME;
static final String TAG = NAME;
/**
* The scale used for the signal value. A level of the signal, given in the
@@ -87,7 +85,7 @@ class WiFiStatsModule
*/
@Override
public String getName() {
return MODULE_NAME;
return NAME;
}
/**

View File

@@ -50,7 +50,7 @@ public class IncomingCallView
* by/associated with the specified {@code name}.
*/
@Override
public void onExternalAPIEvent(String name, ReadableMap data) {
protected void onExternalAPIEvent(String name, ReadableMap data) {
onExternalAPIEvent(LISTENER_METHODS, name, data);
}

View File

@@ -1,211 +0,0 @@
/*
* Copyright @ 2017-present Atlassian Pty Ltd
*
* 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.
*/
package org.jitsi.meet.sdk.invite;
import android.util.Log;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableNativeArray;
import com.facebook.react.bridge.WritableNativeMap;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
/**
* Controller object used by native code to query and submit user selections for
* the user invitation flow.
*/
public class AddPeopleController {
/**
* The AddPeopleControllerListener for this controller, used to pass query
* results back to the native code that initiated the query.
*/
private AddPeopleControllerListener listener;
/**
* Local cache of search query results. Used to re-hydrate the list of
* selected items based on their ids passed to inviteById in order to pass
* the full item maps back to the JitsiMeetView during submission.
*/
private final Map<String, ReadableMap> items = new HashMap<>();
private final WeakReference<InviteController> owner;
private final WeakReference<ReactApplicationContext> reactContext;
/**
* Randomly generated UUID, used for identification in the InviteModule.
*/
private final String uuid = UUID.randomUUID().toString();
public AddPeopleController(
InviteController owner,
ReactApplicationContext reactContext) {
this.owner = new WeakReference<>(owner);
this.reactContext = new WeakReference<>(reactContext);
}
/**
* Cancel the invitation flow and free memory allocated to the
* AddPeopleController. After calling this method, this object is invalid -
* a new AddPeopleController will be passed to the caller through
* beginAddPeople.
*/
public void endAddPeople() {
InviteController owner = this.owner.get();
if (owner != null) {
owner.endAddPeople(this);
}
}
/**
*
* @return the AddPeopleControllerListener for this controller, used to pass
* query results back to the native code that initiated the query.
*/
public AddPeopleControllerListener getListener() {
return listener;
}
final ReactApplicationContext getReactApplicationContext() {
return reactContext.get();
}
/**
*
* @return the unique identifier for this AddPeopleController
*/
public String getUuid() {
return uuid;
}
/**
* Send invites to selected users based on their item ids
*
* @param ids
*/
public void inviteById(List<String> ids) {
InviteController owner = this.owner.get();
if (owner != null) {
WritableArray invitees = new WritableNativeArray();
for(int i = 0, size = ids.size(); i < size; i++) {
String id = ids.get(i);
if(items.containsKey(id)) {
WritableNativeMap map = new WritableNativeMap();
map.merge(items.get(id));
invitees.pushMap(map);
} else {
// If the id doesn't exist in the map, we can't do anything,
// so just skip it.
}
}
owner.invite(this, invitees);
}
}
void inviteSettled(ReadableArray failedInvitees) {
AddPeopleControllerListener listener = getListener();
if (listener != null) {
ArrayList<Map<String, Object>> jFailedInvitees = new ArrayList<>();
for (int i = 0, size = failedInvitees.size(); i < size; ++i) {
jFailedInvitees.add(failedInvitees.getMap(i).toHashMap());
}
listener.onInviteSettled(this, jFailedInvitees);
}
}
/**
* Start a search for entities to invite with the given query. Results will
* be returned through the associated AddPeopleControllerListener's
* onReceivedResults method.
*
* @param query
*/
public void performQuery(String query) {
InviteController owner = this.owner.get();
if (owner != null) {
owner.performQuery(this, query);
}
}
/**
* Caches results received by the search into a local map for use later when
* the items are submitted. Submission requires the full map of
* information, but only the IDs are returned back to the delegate. Using
* this map means we don't have to send the whole map back to the delegate.
*
* @param results
* @param query
*/
void receivedResultsForQuery(ReadableArray results, String query) {
AddPeopleControllerListener listener = getListener();
if (listener != null) {
List<Map<String, Object>> jvmResults = new ArrayList<>();
// cache results for use in submission later
// convert to jvm array
for(int i = 0; i < results.size(); i++) {
ReadableMap map = results.getMap(i);
if(map.hasKey("id")) {
items.put(map.getString("id"), map);
} else if(map.hasKey("type")
&& map.getString("type").equals("phone")
&& map.hasKey("number")) {
items.put(map.getString("number"), map);
} else {
Log.w(
"AddPeopleController",
"Received result without id and that was not a phone number, so not adding it to suggestions: "
+ map);
}
jvmResults.add(map.toHashMap());
}
listener.onReceivedResults(this, jvmResults, query);
}
}
/**
* Sets the AddPeopleControllerListener for this controller, used to pass
* query results back to the native code that initiated the query.
*
* @param listener
*/
public void setListener(AddPeopleControllerListener listener) {
this.listener = listener;
}
}

View File

@@ -1,56 +0,0 @@
/*
* Copyright @ 2017-present Atlassian Pty Ltd
*
* 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.
*/
package org.jitsi.meet.sdk.invite;
import java.util.List;
import java.util.Map;
public interface AddPeopleControllerListener {
/**
* Called when the call to {@link AddPeopleController#inviteById(List)}
* completes.
*
* @param addPeopleController the active {@link AddPeopleController} for
* this invite flow. This object should be cleaned up by calling
* {@link AddPeopleController#endAddPeople()} if the user exits the invite
* flow. Otherwise, it can stay active if the user will attempt to invite
* @param failedInvitees a {@code List} of {@code Map<String, Object>}
* dictionaries that represent the invitations that failed. The data type of
* the objects is identical to the results returned in onReceivedResuls.
*/
void onInviteSettled(
AddPeopleController addPeopleController,
List<Map<String, Object>> failedInvitees);
/**
* Called when results are received for a query called through
* AddPeopleController.query().
*
* @param addPeopleController
* @param results a List of Map<String, Object> objects that represent items
* returned by the query. The object at key "type" describes the type of
* item: "user", "videosipgw" (conference room), or "phone". "user" types
* have properties at "id", "name", and "avatar". "videosipgw" types have
* properties at "id" and "name". "phone" types have properties at "number",
* "title", "and "subtitle"
* @param query the query that generated the given results
*/
void onReceivedResults(
AddPeopleController addPeopleController,
List<Map<String, Object>> results,
String query);
}

View File

@@ -1,265 +0,0 @@
/*
* Copyright @ 2017-present Atlassian Pty Ltd
*
* 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.
*/
package org.jitsi.meet.sdk.invite;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableNativeMap;
import org.jitsi.meet.sdk.ReactContextUtils;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
/**
* Represents the entry point into the invite feature of Jitsi Meet and is the
* Java counterpart of the JavaScript {@code InviteButton}.
*/
public class InviteController {
private AddPeopleController addPeopleController;
/**
* Whether adding/inviting people by name (as opposed to phone number) is
* enabled.
*/
private Boolean addPeopleEnabled;
/**
* Whether adding/inviting people by phone number (as opposed to name) is
* enabled.
*/
private Boolean dialOutEnabled;
private final String externalAPIScope;
private InviteControllerListener listener;
public InviteController(String externalAPIScope) {
this.externalAPIScope = externalAPIScope;
}
void beginAddPeople(ReactApplicationContext reactContext) {
InviteControllerListener listener = getListener();
if (listener != null) {
// XXX For the sake of simplicity and in order to reduce the risk of
// memory leaks, allow a single AddPeopleController at a time.
AddPeopleController addPeopleController = this.addPeopleController;
if (addPeopleController != null) {
return;
}
// Initialize a new AddPeopleController to represent the click/tap
// on the InviteButton and notify the InviteControllerListener
// about the event.
addPeopleController = new AddPeopleController(this, reactContext);
boolean success = false;
this.addPeopleController = addPeopleController;
try {
listener.beginAddPeople(addPeopleController);
success = true;
} finally {
if (!success) {
endAddPeople(addPeopleController);
}
}
}
}
void endAddPeople(AddPeopleController addPeopleController) {
if (this.addPeopleController == addPeopleController) {
this.addPeopleController = null;
}
}
public InviteControllerListener getListener() {
return listener;
}
/**
* Sends JavaScript event to submit invitations to the given item ids
*
* @param invitees a WritableArray of WritableNativeMaps representing
* selected items. Each map representing a selected item should match the
* data passed back in the return from a query.
*/
boolean invite(
AddPeopleController addPeopleController,
WritableArray invitees) {
return
invite(
addPeopleController.getUuid(),
addPeopleController.getReactApplicationContext(),
invitees);
}
public Future<List<Map<String, Object>>> invite(
final List<Map<String, Object>> invitees) {
final boolean inviteBegan
= invite(
UUID.randomUUID().toString(),
/* reactContext */ null,
Arguments.makeNativeArray(invitees));
FutureTask futureTask
= new FutureTask(new Callable() {
@Override
public List<Map<String, Object>> call() {
if (inviteBegan) {
// TODO Complete the returned Future when the invite
// settles.
return Collections.emptyList();
} else {
// The invite failed to even begin so report that all
// invitees failed.
return invitees;
}
}
});
// If the invite failed to even begin, complete the returned Future
// already and the Future implementation will report that all invitees
// failed.
if (!inviteBegan) {
futureTask.run();
}
return futureTask;
}
private boolean invite(
String addPeopleControllerScope,
ReactContext reactContext,
WritableArray invitees) {
WritableNativeMap data = new WritableNativeMap();
data.putString("addPeopleControllerScope", addPeopleControllerScope);
data.putString("externalAPIScope", externalAPIScope);
data.putArray("invitees", invitees);
return
ReactContextUtils.emitEvent(
reactContext,
"org.jitsi.meet:features/invite#invite",
data);
}
void inviteSettled(
String addPeopleControllerScope,
ReadableArray failedInvitees) {
AddPeopleController addPeopleController = this.addPeopleController;
if (addPeopleController != null
&& addPeopleController.getUuid().equals(
addPeopleControllerScope)) {
try {
addPeopleController.inviteSettled(failedInvitees);
} finally {
if (failedInvitees.size() == 0) {
endAddPeople(addPeopleController);
}
}
}
}
public boolean isAddPeopleEnabled() {
Boolean b = this.addPeopleEnabled;
return
(b == null || b.booleanValue()) ? (getListener() != null) : false;
}
public boolean isDialOutEnabled() {
Boolean b = this.dialOutEnabled;
return
(b == null || b.booleanValue()) ? (getListener() != null) : false;
}
/**
* Starts a query for users to invite to the conference. Results will be
* returned through
* {@link AddPeopleControllerListener#onReceivedResults(AddPeopleController, List, String)}.
*
* @param query {@code String} to use for the query
*/
void performQuery(AddPeopleController addPeopleController, String query) {
WritableNativeMap params = new WritableNativeMap();
params.putString("addPeopleControllerScope", addPeopleController.getUuid());
params.putString("externalAPIScope", externalAPIScope);
params.putString("query", query);
ReactContextUtils.emitEvent(
addPeopleController.getReactApplicationContext(),
"org.jitsi.meet:features/invite#performQuery",
params);
}
void receivedResultsForQuery(
String addPeopleControllerScope,
String query,
ReadableArray results) {
AddPeopleController addPeopleController = this.addPeopleController;
if (addPeopleController != null
&& addPeopleController.getUuid().equals(
addPeopleControllerScope)) {
addPeopleController.receivedResultsForQuery(results, query);
}
}
/**
* Sets whether the ability to add users to the call is enabled. If this is
* enabled, an add user button will appear on the {@link JitsiMeetView}. If
* enabled, and the user taps the add user button,
* {@link InviteControllerListener#beginAddPeople(AddPeopleController)}
* will be called.
*
* @param addPeopleEnabled {@code true} to enable the add people button;
* otherwise, {@code false}
*/
public void setAddPeopleEnabled(boolean addPeopleEnabled) {
this.addPeopleEnabled = Boolean.valueOf(addPeopleEnabled);
}
/**
* Sets whether the ability to add phone numbers to the call is enabled.
* Must be enabled along with {@link #setAddPeopleEnabled(boolean)} to be
* effective.
*
* @param dialOutEnabled {@code true} to enable the ability to add phone
* numbers to the call; otherwise, {@code false}
*/
public void setDialOutEnabled(boolean dialOutEnabled) {
this.dialOutEnabled = Boolean.valueOf(dialOutEnabled);
}
public void setListener(InviteControllerListener listener) {
this.listener = listener;
}
}

View File

@@ -1,171 +0,0 @@
/*
* Copyright @ 2017-present Atlassian Pty Ltd
*
* 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.
*/
package org.jitsi.meet.sdk.invite;
import android.util.Log;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.UiThreadUtil;
import org.jitsi.meet.sdk.BaseReactView;
import org.jitsi.meet.sdk.JitsiMeetView;
/**
* Implements the react-native module of the feature invite.
*/
public class InviteModule
extends ReactContextBaseJavaModule {
public InviteModule(ReactApplicationContext reactContext) {
super(reactContext);
}
/**
* Signals that a click/tap has been performed on {@code InviteButton} and
* that the execution flow for adding/inviting people to the current
* conference/meeting is to begin
*
* @param externalAPIScope the unique identifier of the
* {@code JitsiMeetView} whose {@code InviteButton} was clicked/tapped.
*/
@ReactMethod
public void beginAddPeople(final String externalAPIScope) {
// Make sure InviteControllerListener (like all other listeners of the
// SDK) is invoked on the UI thread. It was requested by SDK consumers.
if (!UiThreadUtil.isOnUiThread()) {
UiThreadUtil.runOnUiThread(new Runnable() {
@Override
public void run() {
beginAddPeople(externalAPIScope);
}
});
return;
}
InviteController inviteController
= findInviteControllerByExternalAPIScope(externalAPIScope);
if (inviteController != null) {
inviteController.beginAddPeople(getReactApplicationContext());
}
}
private InviteController findInviteControllerByExternalAPIScope(
String externalAPIScope) {
JitsiMeetView view
= (JitsiMeetView)
BaseReactView.findViewByExternalAPIScope(externalAPIScope);
return view == null ? null : view.getInviteController();
}
@Override
public String getName() {
return "Invite";
}
/**
* Callback for invitation failures
*
* @param failedInvitees the items for which the invitation failed
* @param addPeopleControllerScope a string that represents a connection to
* a specific AddPeopleController
*/
@ReactMethod
public void inviteSettled(
final String externalAPIScope,
final String addPeopleControllerScope,
final ReadableArray failedInvitees) {
// Make sure AddPeopleControllerListener (like all other listeners of
// the SDK) is invoked on the UI thread. It was requested by SDK
// consumers.
if (!UiThreadUtil.isOnUiThread()) {
UiThreadUtil.runOnUiThread(new Runnable() {
@Override
public void run() {
inviteSettled(
externalAPIScope,
addPeopleControllerScope,
failedInvitees);
}
});
return;
}
InviteController inviteController
= findInviteControllerByExternalAPIScope(externalAPIScope);
if (inviteController == null) {
Log.w(
"InviteModule",
"Invite settled, but failed to find active controller to notify");
} else {
inviteController.inviteSettled(
addPeopleControllerScope,
failedInvitees);
}
}
/**
* Callback for results received from the JavaScript invite search call
*
* @param results the results in a ReadableArray of ReadableMap objects
* @param query the query associated with the search
* @param addPeopleControllerScope a string that represents a connection to
* a specific AddPeopleController
*/
@ReactMethod
public void receivedResults(
final String externalAPIScope,
final String addPeopleControllerScope,
final String query,
final ReadableArray results) {
// Make sure AddPeopleControllerListener (like all other listeners of
// the SDK) is invoked on the UI thread. It was requested by SDK
// consumers.
if (!UiThreadUtil.isOnUiThread()) {
UiThreadUtil.runOnUiThread(new Runnable() {
@Override
public void run() {
receivedResults(
externalAPIScope,
addPeopleControllerScope,
query,
results);
}
});
return;
}
InviteController inviteController
= findInviteControllerByExternalAPIScope(externalAPIScope);
if (inviteController == null) {
Log.w(
"InviteModule",
"Received results, but failed to find active controller to send results back");
} else {
inviteController.receivedResultsForQuery(
addPeopleControllerScope,
query,
results);
}
}
}

View File

@@ -21,6 +21,7 @@ import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.module.annotations.ReactModule;
import java.net.UnknownHostException;
@@ -32,9 +33,12 @@ import java.net.UnknownHostException;
* [1]: https://tools.ietf.org/html/rfc6146
* [2]: https://tools.ietf.org/html/rfc6052
*/
@ReactModule(name = NAT64AddrInfoModule.NAME)
public class NAT64AddrInfoModule
extends ReactContextBaseJavaModule {
public final static String NAME = "NAT64AddrInfo";
/**
* The host for which the module wil try to resolve both IPv4 and IPv6
* addresses in order to figure out the NAT64 prefix.
@@ -46,15 +50,10 @@ public class NAT64AddrInfoModule
*/
private final static long INFO_LIFETIME = 60 * 1000;
/**
* The name of this module.
*/
private final static String MODULE_NAME = "NAT64AddrInfo";
/**
* The {@code Log} tag {@code NAT64AddrInfoModule} is to log messages with.
*/
private final static String TAG = MODULE_NAME;
private final static String TAG = NAME;
/**
* The {@link NAT64AddrInfo} instance which holds NAT64 prefix/suffix.
@@ -119,6 +118,6 @@ public class NAT64AddrInfoModule
@Override
public String getName() {
return MODULE_NAME;
return NAME;
}
}

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".JitsiMeetActivity">
<fragment
android:layout_width="match_parent"
android:layout_height="match_parent"
android:name="org.jitsi.meet.sdk.JitsiMeetFragment"
android:id="@+id/jitsiFragment"/>
</FrameLayout>

View File

@@ -13,8 +13,6 @@ include ':react-native-keep-awake'
project(':react-native-keep-awake').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-keep-awake/android')
include ':react-native-linear-gradient'
project(':react-native-linear-gradient').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-linear-gradient/android')
include ':react-native-locale-detector'
project(':react-native-locale-detector').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-locale-detector/android')
include ':react-native-sound'
project(':react-native-sound').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-sound/android')
include ':react-native-vector-icons'

7
app.js
View File

@@ -1,15 +1,8 @@
/* application specific logic */
// FIXME: remove once atlaskit work with React 16
// It seems that @atlaskit/icon is importing PropTypes from React, but it
// happens through some glyph coffee script template. It could be that more
// things are broken there (not only the icon).
import './react/features/base/react/prop-types-polyfill.js';
import 'jquery';
import 'jquery-contextmenu';
import 'jQuery-Impromptu';
import 'autosize';
import conference from './conference';
import API from './modules/API';

View File

@@ -38,6 +38,7 @@ import {
conferenceFailed,
conferenceJoined,
conferenceLeft,
conferenceSubjectChanged,
conferenceWillJoin,
conferenceWillLeave,
dataChannelOpened,
@@ -76,10 +77,10 @@ import {
dominantSpeakerChanged,
getAvatarURLByParticipantId,
getLocalParticipant,
getNormalizedDisplayName,
getParticipantById,
localParticipantConnectionStatusChanged,
localParticipantRoleChanged,
MAX_DISPLAY_NAME_LENGTH,
participantConnectionStatusChanged,
participantPresenceChanged,
participantRoleChanged,
@@ -88,6 +89,7 @@ import {
import { updateSettings } from './react/features/base/settings';
import {
createLocalTracksF,
destroyLocalTracks,
isLocalTrackMuted,
replaceLocalTrack,
trackAdded,
@@ -346,7 +348,10 @@ class ConferenceConnector {
// not enough rights to create conference
case JitsiConferenceErrors.AUTHENTICATION_REQUIRED: {
// Schedule reconnect to check if someone else created the room.
this.reconnectTimeout = setTimeout(() => room.join(), 5000);
this.reconnectTimeout = setTimeout(() => {
APP.store.dispatch(conferenceWillJoin(room));
room.join();
}, 5000);
const { password }
= APP.store.getState()['features/base/conference'];
@@ -1233,6 +1238,7 @@ export default {
= connection.initJitsiConference(
APP.conference.roomName,
this._getConferenceOptions());
APP.store.dispatch(conferenceWillJoin(room));
this._setLocalAudioVideoStreams(localTracks);
this._room = room; // FIXME do not use this
@@ -1320,7 +1326,14 @@ export default {
this.isSharingScreen = newStream && newStream.videoType === 'desktop';
if (wasSharingScreen !== this.isSharingScreen) {
APP.API.notifyScreenSharingStatusChanged(this.isSharingScreen);
const details = {};
if (this.isSharingScreen) {
details.sourceType = newStream.sourceType;
}
APP.API.notifyScreenSharingStatusChanged(
this.isSharingScreen, details);
}
},
@@ -1396,6 +1409,8 @@ export default {
receiver.stop();
}
this._stopProxyConnection();
let promise = null;
if (didHaveVideo) {
@@ -1471,9 +1486,12 @@ export default {
/**
* Creates desktop (screensharing) {@link JitsiLocalTrack}
*
* @param {Object} [options] - Screen sharing options that will be passed to
* createLocalTracks.
*
* @param {Object} [options.desktopSharing]
* @param {Object} [options.desktopStream] - An existing desktop stream to
* use instead of creating a new desktop stream.
* @return {Promise.<JitsiLocalTrack>} - A Promise resolved with
* {@link JitsiLocalTrack} for the screensharing or rejected with
* {@link JitsiTrackError}.
@@ -1486,47 +1504,52 @@ export default {
const didHaveVideo = Boolean(this.localVideo);
const wasVideoMuted = this.isLocalVideoMuted();
return createLocalTracksF({
desktopSharingSources: options.desktopSharingSources,
devices: [ 'desktop' ],
desktopSharingExtensionExternalInstallation: {
interval: 500,
checkAgain: () => DSExternalInstallationInProgress,
listener: (status, url) => {
switch (status) {
case 'waitingForExtension': {
DSExternalInstallationInProgress = true;
externalInstallation = true;
const listener = () => {
// Wait a little bit more just to be sure that we
// won't miss the extension installation
setTimeout(
() => {
const getDesktopStreamPromise = options.desktopStream
? Promise.resolve([ options.desktopStream ])
: createLocalTracksF({
desktopSharingSourceDevice: options.desktopSharingSources
? null : config._desktopSharingSourceDevice,
desktopSharingSources: options.desktopSharingSources,
devices: [ 'desktop' ],
desktopSharingExtensionExternalInstallation: {
interval: 500,
checkAgain: () => DSExternalInstallationInProgress,
listener: (status, url) => {
switch (status) {
case 'waitingForExtension': {
DSExternalInstallationInProgress = true;
externalInstallation = true;
const listener = () => {
// Wait a little bit more just to be sure that
// we won't miss the extension installation
setTimeout(() => {
DSExternalInstallationInProgress = false;
},
500);
APP.UI.removeListener(
APP.UI.removeListener(
UIEvents.EXTERNAL_INSTALLATION_CANCELED,
listener);
};
APP.UI.addListener(
UIEvents.EXTERNAL_INSTALLATION_CANCELED,
listener);
};
APP.UI.showExtensionExternalInstallationDialog(url);
break;
}
case 'extensionFound':
// Close the dialog.
externalInstallation && $.prompt.close();
break;
default:
APP.UI.addListener(
UIEvents.EXTERNAL_INSTALLATION_CANCELED,
listener);
APP.UI.showExtensionExternalInstallationDialog(url);
break;
}
case 'extensionFound':
// Close the dialog.
externalInstallation && $.prompt.close();
break;
default:
// Unknown status
// Unknown status
}
}
}
}
}).then(([ desktopStream ]) => {
});
return getDesktopStreamPromise.then(([ desktopStream ]) => {
// Stores the "untoggle" handler which remembers whether was
// there any video before and whether was it muted.
this._untoggleScreenSharing
@@ -1644,6 +1667,7 @@ export default {
// Handling:
// JitsiTrackErrors.PERMISSION_DENIED
// JitsiTrackErrors.CHROME_EXTENSION_INSTALLATION_ERROR
// JitsiTrackErrors.CONSTRAINT_FAILED
// JitsiTrackErrors.GENERAL
// and any other
let descriptionKey;
@@ -1667,6 +1691,9 @@ export default {
descriptionKey = 'dialog.screenSharingPermissionDeniedError';
titleKey = 'dialog.screenSharingFailedToInstallTitle';
}
} else if (error.name === JitsiTrackErrors.CONSTRAINT_FAILED) {
descriptionKey = 'dialog.cameraConstraintFailedError';
titleKey = 'deviceError.cameraError';
} else {
descriptionKey = 'dialog.screenSharingFailedToInstall';
titleKey = 'dialog.screenSharingFailedToInstallTitle';
@@ -1806,6 +1833,8 @@ export default {
room.on(JitsiConferenceEvents.TALK_WHILE_MUTED, () => {
APP.UI.showToolbar(6000);
});
room.on(JitsiConferenceEvents.SUBJECT_CHANGED,
subject => APP.store.dispatch(conferenceSubjectChanged(subject)));
room.on(
JitsiConferenceEvents.LAST_N_ENDPOINTS_CHANGED,
@@ -1840,7 +1869,7 @@ export default {
JitsiConferenceEvents.DISPLAY_NAME_CHANGED,
(id, displayName) => {
const formattedDisplayName
= displayName.substr(0, MAX_DISPLAY_NAME_LENGTH);
= getNormalizedDisplayName(displayName);
APP.store.dispatch(participantUpdated({
conference: room,
@@ -1890,21 +1919,6 @@ export default {
JitsiConferenceEvents.PARTICIPANT_PROPERTY_CHANGED,
(participant, name, oldValue, newValue) => {
switch (name) {
case 'features_screen-sharing': {
APP.store.dispatch(participantUpdated({
conference: room,
id: participant.getId(),
features: { 'screen-sharing': true }
}));
break;
}
case 'raisedHand':
APP.store.dispatch(participantUpdated({
conference: room,
id: participant.getId(),
raisedHand: newValue === 'true'
}));
break;
case 'remoteControlSessionStatus':
APP.UI.setRemoteControlActiveStatus(
participant.getId(),
@@ -2216,34 +2230,6 @@ export default {
* @returns {void}
*/
_onConferenceJoined() {
if (APP.logCollector) {
// Start the LogCollector's periodic "store logs" task
APP.logCollector.start();
APP.logCollectorStarted = true;
// Make an attempt to flush in case a lot of logs have been
// cached, before the collector was started.
APP.logCollector.flush();
// This event listener will flush the logs, before
// the statistics module (CallStats) is stopped.
//
// NOTE The LogCollector is not stopped, because this event can
// be triggered multiple times during single conference
// (whenever statistics module is stopped). That includes
// the case when Jicofo terminates the single person left in the
// room. It will then restart the media session when someone
// eventually join the room which will start the stats again.
APP.conference.addConferenceListener(
JitsiConferenceEvents.BEFORE_STATISTICS_DISPOSED,
() => {
if (APP.logCollector) {
APP.logCollector.flush();
}
}
);
}
APP.UI.initConference();
APP.keyboardshortcut.init();
@@ -2321,7 +2307,8 @@ export default {
}));
}
if (this.localVideo) {
if (this.localVideo
&& this.localVideo.videoType === 'camera') {
dispatch(updateSettings({
cameraDeviceId: this.localVideo.getDeviceId()
}));
@@ -2415,7 +2402,7 @@ export default {
*/
updateAudioIconEnabled() {
const audioMediaDevices
= APP.store.getState()['features/base/devices'].audioInput;
= APP.store.getState()['features/base/devices'].availableDevices.audioInput;
const audioDeviceCount
= audioMediaDevices ? audioMediaDevices.length : 0;
@@ -2438,7 +2425,7 @@ export default {
*/
updateVideoIconEnabled() {
const videoMediaDevices
= APP.store.getState()['features/base/devices'].videoInput;
= APP.store.getState()['features/base/devices'].availableDevices.videoInput;
const videoDeviceCount
= videoMediaDevices ? videoMediaDevices.length : 0;
@@ -2466,7 +2453,13 @@ export default {
*/
hangup(requestFeedback = false) {
eventEmitter.emit(JitsiMeetConferenceEvents.BEFORE_HANGUP);
APP.UI.removeLocalMedia();
this._stopProxyConnection();
APP.store.dispatch(destroyLocalTracks());
this._localTracksInitialized = false;
this.localVideo = null;
this.localAudio = null;
// Remove unnecessary event listeners from firing callbacks.
if (this.deviceChangeListener) {
@@ -2475,6 +2468,9 @@ export default {
this.deviceChangeListener);
}
APP.UI.removeAllListeners();
APP.remoteControl.removeAllListeners();
let requestFeedbackPromise;
if (requestFeedback) {
@@ -2495,6 +2491,9 @@ export default {
requestFeedbackPromise,
this.leaveRoomAndDisconnect()
]).then(values => {
this._room = undefined;
room = undefined;
APP.API.notifyReadyToClose();
maybeRedirectToWelcomePage(values[0]);
});
@@ -2508,7 +2507,8 @@ export default {
leaveRoomAndDisconnect() {
APP.store.dispatch(conferenceWillLeave(room));
return room.leave().then(disconnect, disconnect);
return room.leave()
.then(disconnect, disconnect);
},
/**
@@ -2614,8 +2614,7 @@ export default {
* @param nickname {string} the new display name
*/
changeLocalDisplayName(nickname = '') {
const formattedNickname
= nickname.trim().substr(0, MAX_DISPLAY_NAME_LENGTH);
const formattedNickname = getNormalizedDisplayName(nickname);
const { id, name } = getLocalParticipant(APP.store.getState());
if (formattedNickname === name) {
@@ -2647,7 +2646,6 @@ export default {
});
if (room) {
room.setDisplayName(formattedNickname);
APP.UI.changeDisplayName(id, formattedNickname);
}
},
@@ -2675,6 +2673,72 @@ export default {
return this.localVideo.sourceType;
},
/**
* Callback invoked by the external api create or update a direct connection
* from the local client to an external client.
*
* @param {Object} event - The object containing information that should be
* passed to the {@code ProxyConnectionService}.
* @returns {void}
*/
onProxyConnectionEvent(event) {
if (!this._proxyConnection) {
this._proxyConnection = new JitsiMeetJS.ProxyConnectionService({
/**
* Pass the {@code JitsiConnection} instance which will be used
* to fetch TURN credentials.
*/
jitsiConnection: APP.connection,
/**
* The proxy connection feature is currently tailored towards
* taking a proxied video stream and showing it as a local
* desktop screen.
*/
convertVideoToDesktop: true,
/**
* Callback invoked to pass messages from the local client back
* out to the external client.
*
* @param {string} peerJid - The jid of the intended recipient
* of the message.
* @param {Object} data - The message that should be sent. For
* screensharing this is an iq.
* @returns {void}
*/
onSendMessage: (peerJid, data) =>
APP.API.sendProxyConnectionEvent({
data,
to: peerJid
}),
/**
* Callback invoked when the remote peer of the proxy connection
* has provided a video stream, intended to be used as a local
* desktop stream.
*
* @param {JitsiLocalTrack} remoteProxyStream - The media
* stream to use as a local desktop stream.
* @returns {void}
*/
onRemoteStream: desktopStream => {
if (desktopStream.videoType !== 'desktop') {
logger.warn('Received a non-desktop stream to proxy.');
desktopStream.dispose();
return;
}
this.toggleScreenSharing(undefined, { desktopStream });
}
});
}
this._proxyConnection.processMessage(event);
},
/**
* Sets the video muted status.
*
@@ -2709,5 +2773,19 @@ export default {
if (score === -1 || (score >= 1 && score <= 5)) {
APP.store.dispatch(submitFeedback(score, message, room));
}
},
/**
* Terminates any proxy screensharing connection that is active.
*
* @private
* @returns {void}
*/
_stopProxyConnection() {
if (this._proxyConnection) {
this._proxyConnection.stop();
}
this._proxyConnection = null;
}
};

View File

@@ -146,7 +146,7 @@ var config = {
desktopSharingChromeExtId: null,
// Whether desktop sharing should be disabled on Chrome.
desktopSharingChromeDisabled: true,
// desktopSharingChromeDisabled: false,
// The media sources to use when using screen sharing with the Chrome
// extension.
@@ -156,7 +156,7 @@ var config = {
desktopSharingChromeMinExtVersion: '0.1',
// Whether desktop sharing should be disabled on Firefox.
desktopSharingFirefoxDisabled: false,
// desktopSharingFirefoxDisabled: false,
// Optional desktop sharing frame rate options. Default value: min:5, max:5.
// desktopSharingFrameRate: {
@@ -174,7 +174,17 @@ var config = {
// Enable the dropbox integration.
// dropbox: {
// appKey: '<APP_KEY>' // Specify your app key here.
// // A URL to redirect the user to, after authenticating
// // by default uses:
// // 'https://jitsi-meet.example.com/static/oauth.html'
// redirectURI:
// 'https://jitsi-meet.example.com/subfolder/static/oauth.html'
// },
// When integrations like dropbox are enabled only that will be shown,
// by enabling fileRecordingsServiceEnabled, we show both the integrations
// and the generic recording service (its configuration and storage type
// depends on jibri configuration)
// fileRecordingsServiceEnabled: false
// Whether to enable live streaming or not.
// liveStreamingEnabled: false,
@@ -334,14 +344,19 @@ var config = {
// backToP2PDelay: 5
},
// A list of scripts to load as lib-jitsi-meet "analytics handlers".
// analyticsScriptUrls: [
// "libs/analytics-ga.js", // google-analytics
// "https://example.com/my-custom-analytics.js"
// ],
analytics: {
// The Google Analytics Tracking ID:
// googleAnalyticsTrackingId: 'your-tracking-id-UA-123456-1'
// The Google Analytics Tracking ID
// googleAnalyticsTrackingId = 'your-tracking-id-here-UA-123456-1',
// The Amplitude APP Key:
// amplitudeAPPKey: '<APP_KEY>'
// Array of script URLs to load as lib-jitsi-meet "analytics handlers".
// scriptURLs: [
// "libs/analytics-ga.min.js", // google-analytics
// "https://example.com/my-custom-analytics.js"
// ],
},
// Information about the jitsi-meet instance we are connecting to, including
// the user region as seen by the server.
@@ -381,6 +396,13 @@ var config = {
// analyticsInterval: 60000,
// }
// If set, will attempt to use the provided video input device label when
// triggering a screenshare, instead of proceeding through the normal flow
// for obtaining a desktop stream.
// NOTE: This option is experimental and is currently intended for internal
// use only.
// _desktopSharingSourceDevice: 'sample-id-or-label'
// List of undocumented settings used in jitsi-meet
/**
_immediateReloadThreshold
@@ -400,7 +422,6 @@ var config = {
externalConnectUrl
firefox_fake_device
googleApiApplicationClientID
googleApiIOSClientID
iAmRecorder
iAmSipGateway
microsoftApiApplicationClientID

View File

@@ -1,57 +1,50 @@
/**
* Move Atlaskit Flag up a little bit so it does not cover the toolbar with the
* first notification.
* Move the @atlaskit/flag container up a little bit so it does not cover the
* toolbar with the first notification.
*/
.cxGWJB{
.cjMOOK{
bottom: calc(#{$newToolbarSizeWithPadding}) !important;
}
.gXSEsl:nth-child(n+2) {
transform: translateX(0) translateY(100%) translateY(16px) !important;
-ms-transform: translateX(0) translateY(100%) translateY(16px) !important;
-webkit-transform: translateX(0) translateY(100%) translateY(16px) !important;
}
/**
* Welcome page tab color adjustments.
* Disable the slide-in animation for @atlaskit/flag due to the animation
* repeating for each queued flag once it becomes the top flag.
*/
.welcome {
/**
* The text color of the selected tab and hovered tabs.
*/
.bVobOt,
.bVobOt:hover,
.ebveIl:hover {
color: #172B4D;
}
/**
* The color of the inactive tab text.
*/
.ebveIl {
color: #FFFFFF;
}
/**
* The color of the underline of a selected tab.
*/
.kByArU {
background-color: #172B4D;
}
.mIBKA:first-child {
animation: cbfRuT 0s !important;
-webkit-animation: cbfRuT 0s !important;
}
.modal-dialog-form {
/**
* Update the dropdown trigger wrapper to make sure it looks click-able.
* Update the @atlaskit/dropdown-menu trigger wrapper to make sure it looks
* click-able.
*/
.gwEjuO {
.cjJUnw {
cursor: pointer;
}
/**
* Override Atlaskit dropdown styling when in a modal because the dropdown
* backgrounds clash with the modal backgrounds.
* Override @atlaskit/dropdown-menu styling when in a modal because the
* dropdown backgrounds clash with the modal backgrounds.
*/
.gBLqhw[data-role=droplistContent] {
.cksvax[data-role=droplistContent] {
border: 1px solid #455166;
}
}
/**
* Override @atlaskit/theme styling for the top toolbar so it displays over
* the video thumbnail while obscuring as little as possible.
*/
.videocontainer .tOoji {
background: none;
}
/**
* Override @atlaskit/InlineDialog styling for the overflowmenu so it displays
* with the correct height.
*/
.toolbox-button-wth-dialog .eYJELv {
max-height: initial;
}

View File

@@ -55,10 +55,6 @@ body, input, textarea, keygen, select, button {
display:none;
}
.no-fa-video-camera, .fa-microphone-slash {
color: #636363;
}
button, input, select, textarea {
margin: 0;
vertical-align: baseline;

View File

@@ -214,12 +214,11 @@
}
.smiley {
height: 26px;
font-size: 14pt;
}
#smileys {
background: white;
border-radius: 50%;
font-size: 20pt;
display: inline-block;
height: 26px;
margin: auto;
@@ -258,7 +257,7 @@
width: $sidebarWidth;
&.show-smileys {
height: 202px;
height: 146px;
}
#smileysContainer {
@@ -269,13 +268,14 @@
}
#smileysContainer .smiley {
padding: 7px;
font-size: 20pt;
}
.smileyContainer {
width: 40px;
height: 40px;
height: 36px;
display: inline-block;
text-align: center;
}
.smileyContainer:hover {

File diff suppressed because it is too large Load Diff

View File

@@ -1,10 +1,10 @@
@font-face {
font-family: 'jitsi';
src:url('../fonts/jitsi.eot?94d075');
src:url('../fonts/jitsi.eot?#iefix94d075') format('embedded-opentype'),
url('../fonts/jitsi.woff?94d075') format('woff'),
url('../fonts/jitsi.ttf?94d075') format('truetype'),
url('../fonts/jitsi.svg?94d075#jitsi') format('svg');
src: url('../fonts/jitsi.eot?3vw865');
src: url('../fonts/jitsi.eot?3vw865#iefix') format('embedded-opentype'),
url('../fonts/jitsi.ttf?3vw865') format('truetype'),
url('../fonts/jitsi.woff?3vw865') format('woff'),
url('../fonts/jitsi.svg?3vw865#jitsi') format('svg');
font-weight: normal;
font-style: normal;
}
@@ -25,167 +25,89 @@
-moz-osx-font-smoothing: grayscale;
}
.icon-arrow_back:before {
content: "\e5c4";
.icon-phone:before {
content: "\e0cd";
}
.icon-radio_button_unchecked:before {
content: "\e836";
}
.icon-radio_button_checked:before {
content: "\e837";
}
.icon-search:before {
content: "\e8b6";
}
.icon-chat-unread:before {
content: "\e0b7";
}
.icon-closed_caption:before {
content: "\e930";
}
.icon-tiles-many:before {
content: "\e92e";
}
.icon-close:before {
content: "\e5cd";
}
.icon-event_note:before {
content: "\e616";
}
.icon-menu:before {
content: "\e5d2";
}
.icon-navigate_before:before {
content: "\e408";
}
.icon-navigate_next:before {
content: "\e409";
}
.icon-public:before {
content: "\e80b";
.icon-open_in_new:before {
content: "\e89e";
}
.icon-restore:before {
content: "\e8b3";
}
.icon-timer:before {
content: "\e425";
.icon-navigate_next:before {
content: "\e409";
}
.icon-menu:before {
content: "\e5d2";
}
.icon-arrow_back:before {
content: "\e5c4";
}
.icon-public:before {
content: "\e80b";
}
.icon-event_note:before {
content: "\e616";
}
.icon-bluetooth:before {
content: "\e1aa";
}
.icon-headset:before {
content: "\e310";
}
.icon-phone-talk:before {
content: "\e61d";
}
.icon-thumb-menu:before {
content: "\e5d4";
}
.icon-mic-camera-combined:before {
content: "\e903";
}
.icon-feedback:before {
content: "\e91d";
}
.icon-toggle-filmstrip:before {
content: "\e91c";
}
.icon-avatar:before {
content: "\e901";
}
.icon-hangup:before {
content: "\e905";
}
.icon-chat:before {
content: "\e906";
}
.icon-download:before {
content: "\e902";
}
.icon-edit:before {
content: "\e907";
}
.icon-share-doc:before {
content: "\e908";
}
.icon-kick:before {
content: "\e904";
}
.icon-menu-up:before {
content: "\e91f";
}
.icon-menu-down:before {
content: "\e920";
}
.icon-full-screen:before {
content: "\e90b";
}
.icon-exit-full-screen:before {
content: "\e90c";
}
.icon-star-full:before {
content: "\e90a";
}
.icon-security:before {
content: "\e90d";
}
.icon-security-locked:before {
content: "\e90e";
}
.icon-reload:before {
content: "\e90f";
}
.icon-microphone:before {
content: "\e910";
}
.icon-mic-empty:before {
content: "\e911";
}
.icon-mic-disabled:before {
content: "\e912";
content: "\e5d4";
}
.icon-ninja:before {
content: "\e909";
content: "\e909";
}
.icon-raised-hand:before {
content: "\e91e";
}
.icon-contactList:before {
content: "\e91b";
}
.icon-link:before {
content: "\e913";
}
.icon-shared-video:before {
content: "\e914";
}
.icon-settings:before {
content: "\e915";
}
.icon-star:before {
content: "\e916";
}
.icon-switch-camera:before {
content: "\e921";
}
.icon-share-desktop:before {
content: "\e917";
}
.icon-camera:before {
content: "\e918";
}
.icon-camera-disabled:before {
content: "\e919";
}
.icon-volume:before {
content: "\e91a";
}
.icon-recDisable:before {
content: "\e613";
}
.icon-recEnable:before {
content: "\e614";
}
.icon-presentation:before {
content: "\e603";
}
.icon-dialpad:before {
content: "\e925";
}
.icon-visibility:before {
content: "\e923";
}
.icon-visibility-off:before {
content: "\e924";
}
.icon-telephone:before {
content: "\e0cd";
.icon-invite:before {
content: "\e145";
}
.icon-add:before {
content: "\e145";
content: "\e146";
}
.icon-info:before {
content: "\e922";
.icon-play:before {
content: "\f04b";
}
.icon-gsm-bars:before {
content: "\e926";
.icon-stop:before {
content: "\f04d";
}
.icon-open_in_new:before {
content: "\e89e";
.icon-dominant-speaker:before {
content: "\f0a1";
}
.icon-speaker:before {
content: "\e92d";
}
.icon-rec:before {
content: "\e92b";
}
.icon-camera-take-picture:before {
content: "\e92a";
}
.icon-AUD:before {
content: "\e900";
@@ -199,24 +121,90 @@
.icon-SD:before {
content: "\e929";
}
.icon-camera-take-picture:before {
content: "\e92a";
.icon-gsm-bars:before {
content: "\e926";
}
.icon-rec:before {
content: "\e92b";
.icon-info:before {
content: "\e922";
}
.icon-live:before {
content: "\e92c";
.icon-mic-camera-combined:before {
content: "\e903";
}
.icon-speaker:before {
content: "\e92d";
.icon-feedback:before {
content: "\e91d";
}
.icon-tiles-many:before {
content: "\e92e";
.icon-hangup:before {
content: "\e905";
}
.icon-tiles-one:before {
content: "\e92f";
.icon-chat:before {
content: "\e906";
}
.icon-closed_caption:before {
content: "\e930";
.icon-share-doc:before {
content: "\e908";
}
.icon-kick:before {
content: "\e904";
}
.icon-menu-up:before {
content: "\e91f";
}
.icon-menu-down:before {
content: "\e920";
}
.icon-full-screen:before {
content: "\e90b";
}
.icon-exit-full-screen:before {
content: "\e90c";
}
.icon-security:before {
content: "\e90d";
}
.icon-security-locked:before {
content: "\e90e";
}
.icon-microphone:before {
content: "\e910";
}
.icon-mic-disabled:before {
content: "\e912";
}
.icon-raised-hand:before {
content: "\e91e";
}
.icon-link:before {
content: "\e913";
}
.icon-shared-video:before {
content: "\e914";
}
.icon-settings:before {
content: "\e915";
}
.icon-star:before {
content: "\e916";
}
.icon-switch-camera:before {
content: "\e921";
}
.icon-share-desktop:before {
content: "\e917";
}
.icon-camera:before {
content: "\e918";
}
.icon-camera-disabled:before {
content: "\e919";
}
.icon-volume:before {
content: "\e91a";
}
.icon-presentation:before {
content: "\e603";
}
.icon-visibility:before {
content: "\e923";
}
.icon-visibility-off:before {
content: "\e924";
}

View File

@@ -36,6 +36,12 @@
cursor: pointer;
}
.calendar-action-buttons {
.button {
margin: 0px 10px;
}
}
.item {
background: rgba(255,255,255,0.50);
box-sizing: border-box;

View File

@@ -41,3 +41,10 @@
top: -25px;
width: 100%;
}
.popover {
background-color: $popoverBg;
border-radius: 3px;
margin: -16px -24px;
padding: 16px 24px;
}

View File

@@ -5,16 +5,13 @@
.popupmenu {
min-width: 75px;
text-align: left;
padding: 0;
padding: 0px;
width: 150px;
white-space: nowrap;
&__item {
list-style-type: none;
height: 35px;
&:hover {
background-color: rgba(9, 30, 66, 0.04);
}
}
// Link Appearance
@@ -28,6 +25,12 @@
width: 100%;
cursor: pointer;
padding: 0 5px;
color: $popupMenuColor;
&:hover {
background-color: $popupMenuHoverBackground;
color: $popupMenuHoverColor;
}
&.disabled {
pointer-events: none;
@@ -62,6 +65,18 @@
top: 50%;
transform: translate(0, -50%);
width: 100%;
&::-webkit-slider-runnable-track {
background-color: $popupSliderColor;
}
&::-moz-range-track {
background-color: $popupSliderColor;
}
&::-ms-fill-lower {
background-color: $popupSliderColor;
}
}
}
}
@@ -78,7 +93,9 @@
}
}
.icon-kick {
.icon-kick,
.icon-play,
.icon-stop {
font-size: 8pt;
}
}
@@ -89,14 +106,9 @@
* InlineDialogs.
*/
ul.popupmenu {
margin: -15px;
margin: -16px -24px;
}
span.remotevideomenu:hover ul.popupmenu, ul.popupmenu:hover {
display:block !important;
}
.remote-control-spinner {
top: 6px;
left: 2px;
}

View File

@@ -11,42 +11,37 @@
flex: 0;
flex-direction: row;
justify-content: space-between;
align-items: center;
margin-top: 32px;
.recording-title {
display: inline-flex;
align-items: center;
font-size: 16px;
font-weight: bold;
margin-left: 16px;
}
}
.recording-icon-container {
display: inline-flex;
align-items: center;
}
.recording-icon {
width: 32px;
height: 32px;
object-fit: contain;
}
.recording-switch {
margin-left: auto;
}
.authorization-panel {
display: flex;
flex-direction: column;
margin-bottom: 10px;
margin: 0 40px 10px 40px;
padding-bottom: 10px;
.dropbox-sign-in {
align-items: center;
border: 1px solid #4285f4;
background-color: white;
border-radius: 2px;
cursor: pointer;
display: inline-flex;
padding: 10px;
font-size: 18px;
font-weight: 600;
margin: 10px 0px;
color: #4285f4;
.dropbox-logo {
background-color: white;
border-radius: 2px;
display: inline-block;
padding-right: 5px;
height: 18px;
}
}
.logged-in-panel {
padding: 10px;
}
@@ -65,6 +60,8 @@
}
.form-footer {
display: flex;
margin-top: 5px;
text-align: right;
}
@@ -93,11 +90,15 @@
padding-bottom: 10px;
}
.stream-key-form {
.helper-link {
display: inline-block;
cursor: pointer;
margin-top: 5px;
}
.helper-link {
cursor: pointer;
display: inline-block;
flex-shrink: 0;
margin-left: auto;
}
.warning-text {
color:#FFD740;
font-size: 12px;
}
}

21
css/_subject.scss Normal file
View File

@@ -0,0 +1,21 @@
.subject {
top: -120px;
transition: top .3s ease-in;
height: 95px;
width: 100%;
position: absolute;
padding: 25px 140px 0 140px;
text-align: center;
font-size: 17px;
color: #fff;
z-index: $toolbarBackgroundZ;
overflow: hidden;
text-overflow: ellipsis;
box-sizing: border-box;
white-space: nowrap;
background-image: linear-gradient(to bottom, rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0));
&.visible {
top: 0px;
}
}

View File

@@ -23,192 +23,251 @@
* TODO: when the old filmstrip has been removed, remove the "new-" prefix.
*/
.new-toolbox {
background-color: $newToolbarBackgroundColor;
bottom: calc((#{$newToolbarSize} * 2) * -1);
box-sizing: border-box;
display: flex;
justify-content: space-between;
left: 0;
margin-left: auto;
margin-right: auto;
padding: 12px 8px;
position: absolute;
right: 0;
transition: bottom .3s ease-in;
width: 100%;
z-index: $toolbarZ;
&.visible {
bottom: 0;
.toolbox-background {
bottom: 0px;
}
}
&.no-buttons {
display: none;
}
.button-group-center,
.button-group-left,
.button-group-right {
display: flex;
width: 33%;
}
.button-group-center {
justify-content: center;
}
.button-group-right {
justify-content: flex-end;
}
i {
border-radius: 5px;
cursor: pointer;
display: block;
font-size: inherit;
height: 100%;
line-height: inherit;
.toolbox-background {
background-image: linear-gradient(to top, rgba(0, 0, 0, 0.6), rgba(0, 0, 0, 0));
transition: bottom .3s ease-in;
height: 160px;
width: 100%;
bottom: -160px;
position: absolute;
z-index: $toolbarBackgroundZ;
}
i:hover {
background-color: $newToolbarButtonHoverColor;
}
.toolbox-content {
box-sizing: border-box;
display: flex;
justify-content: space-between;
margin-left: auto;
margin-right: auto;
padding: 20px 16px;
position: relative;
width: 100%;
z-index: $toolbarZ;
i.toggled {
background: $newToolbarButtonToggleColor;
}
i.toggled:hover {
background-color: $newToolbarButtonHoverColor;
}
i.disabled {
cursor: initial;
color: #3b475c;
}
.disabled i {
cursor: initial;
color: #3b475c;
}
i.disabled:hover {
background-color: initial;
}
.icon-hangup {
color: $hangupColor;
font-size: $newToolbarHangupFontSize;
}
.overflow-menu {
font-size: 1.2em;
list-style-type: none;
/**
* Undo atlaskit padding by reducing margins.
*/
margin: -15px -24px;
padding: 4px 0;
.overflow-menu-item {
align-items: center;
color: #B8C7E0;
cursor: pointer;
.button-group-center,
.button-group-left,
.button-group-right {
display: flex;
font-size: 14px;
height: 22px;
padding: 5px 12px;
width: 33%;
}
div {
display: flex;
flex-direction: row;
align-items: center;
.button-group-center {
justify-content: center;
.toolbox-icon {
margin: 0px 4px;
}
}
.button-group-right {
justify-content: flex-end;
}
i {
border-radius: 5px;
cursor: pointer;
display: block;
font-size: inherit;
height: 100%;
line-height: inherit;
width: 100%;
}
i:hover {
background: $newToolbarButtonHoverColor;
}
i.toggled {
background: $newToolbarButtonToggleColor;
}
i.toggled:hover {
background: $newToolbarButtonHoverColor;
}
.icon-hangup {
background-color: #e12d2d;
color: #fff;
border-radius: 50%;
width: 40px;
height: 40px;
&:hover {
background: #313D52;
background-color: #e54b4b;
}
}
i.disabled, .disabled i {
cursor: initial;
color: #fff;
background-color: #a4b8d1;
}
.icon-mic-disabled, .icon-microphone, .icon-camera-disabled, .icon-camera {
background-color: #fff;
color: #5e6d7a;
border-radius: 50%;
border: 1px solid #d1dbe8;
width: 38px;
height: 38px;
&:hover {
background-color: #daebfa;
border: 1px solid #daebfa;
}
&.unclickable {
cursor: default;
&.toggled {
background: #2a3a4b;
color: #fff;
border: 1px solid #5e6d7a;
&:hover {
background-color: #5e6d7a;
}
}
&.unclickable:hover {
background: inherit;
}
&.disabled {
&.disabled, .disabled & {
cursor: initial;
color: #3b475c;
color: #fff;
background-color: #a4b8d1;
}
}
.beta-tag {
background: #B8C7E0;
border-radius: 2px;
color: $newToolbarBackgroundColor;
font-size: 11px;
font-weight: bold;
margin-left: 8px;
padding: 0 6px;
}
.overflow-menu {
font-size: 1.2em;
list-style-type: none;
background-color: $overflowMenuBG;
/**
* Undo atlaskit padding by reducing margins.
*/
margin: -16px -24px;
padding: 4px 0;
.overflow-menu-item-icon {
margin-right: 10px;
.overflow-menu-item {
align-items: center;
color: $overflowMenuItemColor;
cursor: pointer;
display: flex;
font-size: 14px;
height: 22px;
padding: 5px 12px;
i {
display: inline;
font-size: 24px;
div {
display: flex;
flex-direction: row;
align-items: center;
}
&:hover {
background-color: $overflowMenuItemHoverBG;
color: $overflowMenuItemHoverColor;
}
&.unclickable {
cursor: default;
}
&.unclickable:hover {
background: inherit;
}
&.disabled {
cursor: initial;
color: #3b475c;
}
i.toggled {
background: inherit;
}
i.toggled:hover {
background: inherit;
}
}
i:hover {
background-color: initial;
.beta-tag {
background: $overflowMenuItemColor;
border-radius: 2px;
color: $overflowMenuBG;
font-size: 11px;
font-weight: bold;
margin-left: 8px;
padding: 0 6px;
}
img {
max-width: 24px;
max-height: 24px;
.overflow-menu-item-icon {
margin-right: 10px;
i {
display: inline;
font-size: 24px;
}
i:hover {
background-color: initial;
}
img {
max-width: 24px;
max-height: 24px;
}
}
.profile-text {
max-width: 150px;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
}
.profile-text {
max-width: 150px;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
.toolbox-button {
color: $toolbarButtonColor;
cursor: pointer;
display: inline-block;
line-height: $newToolbarSize;
margin: 0 8px;
text-align: center;
}
}
.toolbox-button {
color: $toolbarButtonColor;
cursor: pointer;
display: inline-block;
line-height: $newToolbarSize;
margin: 0 4px;
text-align: center;
}
.toolbar-button-with-badge {
position: relative;
.toolbar-button-with-badge {
position: relative;
.badge-round {
bottom: -5px;
font-size: 12px;
line-height: 20px;
min-width: 20px;
pointer-events: none;
position: absolute;
right: -5px;
.badge-round {
bottom: -5px;
font-size: 12px;
line-height: 20px;
min-width: 20px;
pointer-events: none;
position: absolute;
right: -5px;
}
}
}
.toolbox-button-wth-dialog {
display: inline-block;
}
.toolbox-button-wth-dialog {
display: inline-block;
}
.toolbox-icon {
height: $newToolbarSize;
font-size: 24px;
width: $newToolbarSize;
.toolbox-icon {
height: $newToolbarSize;
font-size: 24px;
width: $newToolbarSize;
}
}
}
@@ -226,15 +285,15 @@
}
i:hover {
background-color: $newToolbarButtonHoverColor;
background-color: $AOTToolbarButtonHoverColor;
}
i.toggled {
background: $newToolbarButtonToggleColor;
background: $AOTToolbarButtonToggleColor;
}
i.toggled:hover:not(.disabled) {
background-color: $newToolbarButtonHoverColor;
background-color: $AOTToolbarButtonHoverColor;
}
.icon-hangup {

View File

@@ -34,13 +34,19 @@ $defaultDarkColor: #2b3d5c;
*/
$defaultToolbarSize: 50px;
$newToolbarBackgroundColor: rgba(22, 38, 55, 0.8);
$newToolbarButtonHoverColor: rgba(14, 20, 35, 0.6);
$newToolbarButtonToggleColor: rgba(14, 20, 35, 1);
$newToolbarButtonHoverColor: rgba(255, 255, 255, 0.15);
$newToolbarButtonToggleColor: rgba(255, 255, 255, 0.2);
$AOTToolbarButtonHoverColor: rgba(14, 20, 35, 0.6);
$AOTToolbarButtonToggleColor: rgba(14, 20, 35, 1);
$newToolbarFontSize: 24px;
$newToolbarHangupFontSize: 32px;
$newToolbarSize: 40px;
$newToolbarSizeWithPadding: calc(#{$newToolbarSize} + 24px);
$toolbarTitleFontSize: 19px;
$overflowMenuBG: initial;
$overflowMenuItemHoverBG: #313D52;
$overflowMenuItemHoverColor: #B8C7E0;
$overflowMenuItemColor: #B8C7E0;
/**
* Video layout
@@ -57,6 +63,7 @@ $audioLevelShadow: rgba(9, 36, 77, 0.9);
$videoStateIndicatorColor: $defaultColor;
$videoStateIndicatorBackground: $toolbarBackground;
$videoStateIndicatorSize: 40px;
$remoteVideoMenuIconMargin: initial;
/**
* Feedback Modal
@@ -91,6 +98,7 @@ $zindex0: 0;
$zindex1: 1;
$zindex2: 2;
$zindex3: 3;
$toolbarBackgroundZ: 4;
$filmstripVideosZ: 5;
$zindex10: 10;
$reloadZ: 20;
@@ -148,3 +156,9 @@ $welcomePageDescriptionColor: #fff;
$welcomePageFontFamily: inherit;
$welcomePageHeaderBackground: linear-gradient(-90deg, #1251AE 0%, #0074FF 50%, #1251AE 100%);
$welcomePageTitleColor: #fff;
/**
* Deep-linking page variables.
*/
$deepLinkingMobileLogoHeight: 40px;
$deepLinkingMobileHeaderBackground: #f1f2f5;

View File

@@ -80,15 +80,6 @@
float: left;
pointer-events: all;
}
/**
* Need to overwrite the background for the top toolbar dark theme div
* wrapper needed before we're able to move all top toolbar indicators
* creation to react.
*/
.sc-ifAKCX {
background: none;
}
}
&__toolbar {
@@ -418,6 +409,7 @@
height: 13px;
color: #FFF;
font-size: 10pt;
margin-right: $remoteVideoMenuIconMargin;
>i{
cursor: hand;
@@ -505,18 +497,22 @@
display:none !important;
}
#dominantSpeakerAvatarContainer,
#dominantSpeakerAvatar,
.dynamic-shadow {
width: 200px;
height: 200px;
}
#dominantSpeakerAvatar {
#dominantSpeakerAvatarContainer {
top: 50px;
margin: auto;
position: relative;
border-radius: 100px;
overflow: hidden;
visibility: inherit;
}
#dominantSpeakerAvatar {
background-color: #000000;
}

View File

@@ -8,13 +8,13 @@
.header {
width: 100%;
height: 70px;
background-color: #f1f2f5;
background-color: $deepLinkingMobileHeaderBackground;
text-align: center;
.logo {
margin-top: 15px;
margin-left: auto;
margin-right: auto;
height: 40px;
height: $deepLinkingMobileLogoHeight;
}
}
@@ -28,7 +28,7 @@
max-width: 40em;
padding: 35px 0 40px 0;
text-align: center;
width: 75%;
width: 90%;
a:active {
text-decoration: none;
@@ -46,7 +46,7 @@
&__text,
.deep-linking-dial-in {
font-size: 1.2em;
font-size: 1em;
line-height: em(29px, 21px);
margin-bottom: 0.65em;
@@ -59,6 +59,31 @@
font-size: em(21, 18);
}
}
table {
font-size: 1em;
}
.dial-in-conference-id {
margin: 10px 0 10px 0;
}
.dial-in-conference-description {
font-size: 0.8em;
}
.toll-free-list {
min-width: 80px;
}
.numbers-list {
min-width: 150px;
}
li.toll-free:empty:before {
content: '.';
visibility: hidden;
}
}
&__href {
@@ -108,6 +133,7 @@
.dial-in-numbers-list {
color: $unsupportedBrowserTextColor;
padding-left: 20px;
}
.dial-in-numbers-body {

View File

@@ -44,4 +44,15 @@
#largeVideoContainer {
background-color: $defaultBackground !important;
}
/**
* Thumbnail popover menus can overlap other thumbnails. Setting an auto
* z-index will allow AtlasKit InlineDialog's large z-index to be
* respected and thereby display over elements in other thumbnails,
* specifically the various status icons.
*/
.remotevideomenu,
.videocontainer__toptoolbar {
z-index: auto;
}
}

View File

@@ -74,6 +74,7 @@
.remote-video-menu-trigger {
margin-bottom: 7px;
margin-left: $remoteVideoMenuIconMargin;
}
}

View File

@@ -18,10 +18,15 @@
/* Animations END */
/* Flags BEGIN */
$flagsImagePath: "../images/";
@import "../node_modules/bc-css-flags/dist/css/bc-css-flags.scss";
/* Flags END */
/* Fonts BEGIN */
@import 'font';
@import 'font-awesome';
/* Fonts END */
/* Modules BEGIN */
@@ -44,6 +49,7 @@
@import 'modals/local-recording/local-recording';
@import 'videolayout_default';
@import 'notice';
@import 'subject';
@import 'popup_menu';
@import 'recording';
@import 'login_menu';

View File

@@ -12,43 +12,6 @@
.aui {
&-icon {
color: $auiDialogColor;
text-indent: -999em;
&-small {
width: 14px;
height: 14px;
&:before {
color: inherit;
font-family: "FontAwesome";
font-size: 16px;
-webkit-font-smoothing: antialiased;
font-style: normal;
font-weight: normal;
left: 0;
line-height: 1;
margin-top: -8px;
position: absolute;
text-indent: 0;
speak: none;
top: 50%;
}
}
}
&-iconfont-close-dialog {
cursor: pointer;
right: 20px;
position: absolute;
top: -49px;
&:before {
content: "\f00d";
}
}
&-dialog2 {
&-header, &-footer {
background-color: $auiDialogBg;
@@ -124,6 +87,10 @@
}
}
}
&-hide {
display: none;
}
}
.input-control {

View File

@@ -66,7 +66,7 @@
}
.info-dialog-dial-in {
white-space: nowrap;
word-break: break-all;
.conference-id,
.phone-number {
@@ -77,6 +77,7 @@
.info-dialog-icon {
color: #6453C0;
font-size: 16px;
min-width: 30px;
}
.info-dialog-url-text,
@@ -124,29 +125,84 @@
}
}
.dial-in-numbers-list {
margin-top: 20px;
font-size: 12px;
line-height: 24px;
border-collapse: separate;
border-spacing: 0 5px;
thead {
text-align: left;
}
td,
th {
border-bottom: 1px solid #d1dbe8;
}
.flag-cell {
vertical-align: top;
width: 30px;
}
.flag {
display: block;
margin: 5px 5px 0px 5px;
}
.country {
font-weight: bold;
vertical-align: top;
padding: 0 20px 0 0;
}
ul {
padding: 0px 0px 0px 0px;
}
.numbers-list {
list-style: none;
padding: 0 20px 0 0;
}
.toll-free-list {
font-weight: bold;
list-style: none;
vertical-align: top;
}
li.toll-free:empty:before {
content: '.';
visibility: hidden;
}
}
.dial-in-page {
align-items: center;
box-sizing: border-box;
display: flex;
flex-direction: column;
font-size: 24px;
font-size: 12px;
max-height: 100%;
overflow: auto;
padding: 25px;
position: absolute;
transform: translateY(-50%);
top: 50%;
width: 100%;
.dial-in-numbers-list {
font-size: 24px;
margin-top: 20px;
}
.dial-in-conference-id {
text-align: center;
min-width: 200px;
width: 30%;
margin-top: 40px;
}
.dial-in-conference-name,
.dial-in-conference-pin {
font-size: 18px;
}
.dial-in-conference-description {
margin: 12px;
}
}

View File

@@ -80,9 +80,12 @@ $errorColor: #c61600;
$feedbackCancelFontColor: #333;
// Popover colors
$popoverBg: #000;
$popoverFontColor: #ffffff;
$popupMenuSelectedItemBackground: rgba(256, 256, 256, .2);
$popoverBg: initial;
$popoverFontColor: #ffffff !important;
$popupMenuColor: #ffffff !important;
$popupMenuHoverColor: #ffffff !important;
$popupMenuHoverBackground: rgba(255, 255, 255, 0.1);
$popupSliderColor: #0376da;
// Toolbar
$secondaryToolbarBg: rgba(0, 0, 0, 0.5);

2
debian/control vendored
View File

@@ -21,7 +21,7 @@ Description: WebRTC JavaScript video conferences
Package: jitsi-meet-web-config
Architecture: all
Depends: openssl, openjdk-8-jre-headless | nginx | apache2
Depends: openssl, openjdk-8-jre-headless | nginx | nginx-extras | apache2
Description: Configuration for web serving of Jitsi Meet
Jitsi Meet is a WebRTC JavaScript application that uses Jitsi
Videobridge to provide high quality, scalable video conferences.

View File

@@ -52,7 +52,11 @@ case "$1" in
db_set jitsi-meet/jvb-hostname $JVB_HOSTNAME
NGINX_INSTALL_CHECK="$(dpkg-query -f '${Status}' -W 'nginx' 2>/dev/null | awk '{print $3}' || true)"
if [ "$NGINX_INSTALL_CHECK" = "installed" ] || [ "$NGINX_INSTALL_CHECK" = "unpacked" ] ; then
NGINX_EXTRAS_INSTALL_CHECK="$(dpkg-query -f '${Status}' -W 'nginx-extras' 2>/dev/null | awk '{print $3}' || true)"
if [ "$NGINX_INSTALL_CHECK" = "installed" ] \
|| [ "$NGINX_INSTALL_CHECK" = "unpacked" ] \
|| [ "$NGINX_EXTRAS_INSTALL_CHECK" = "installed" ] \
|| [ "$NGINX_EXTRAS_INSTALL_CHECK" = "unpacked" ] ; then
FORCE_NGINX="true"
fi
APACHE_INSTALL_CHECK="$(dpkg-query -f '${Status}' -W 'apache2' 2>/dev/null | awk '{print $3}' || true)"

12
debian/rules vendored
View File

@@ -3,12 +3,22 @@
# Uncomment this to turn on verbose mode.
#export DH_VERBOSE=1
LANGUAGES := $(shell node -p "Object.keys(require('./lang/languages.json')).join(' ')")
COUNTRIES_DIR := node_modules/i18n-iso-countries/langs
%:
dh $@
# we skip making Makefile exists for updating browserify modules when developing
override_dh_auto_build:
override_dh_install:
override_dh_install: $(LANGUAGES)
dh_installdirs
dh_install -X/config.js -X/package.json
$(LANGUAGES):
if [ -f $(COUNTRIES_DIR)/$@.json ] ; \
then \
dh_install -pjitsi-meet-web $(COUNTRIES_DIR)/$@.json usr/share/jitsi-meet/lang/; \
mv debian/jitsi-meet-web/usr/share/jitsi-meet/lang/$@.json debian/jitsi-meet-web/usr/share/jitsi-meet/lang/countries-$@.json; \
fi;

View File

@@ -8,6 +8,5 @@
7. Download the result in a zip file using the "download" button.
8. Copy <code>selection.json</code> and <code>fonts/jitsi.*</code> from the zip file to <code>fonts/</code> in Jitsi Meet
9. Copy the class for the new icon from <code>style.css</code> in the zip file to <code>css/_font.scss</code> in Jitsi Meet (do *not* copy the whole file)
10. Copy the <code>selection.json</code> file to <code>react/features/base/font-icons</code> overwriting <code>jitsi.json</code>
Sample commit: https://github.com/jitsi/jitsi-meet/commit/68bc819b89aec12364fcf07b81efa83a1900eed6

View File

@@ -1,13 +1,13 @@
# Jitsi Meet API
You can use the Jitsi Meet API to embed Jitsi Meet in to your application.
You can use the Jitsi Meet API to embed Jitsi Meet in to your application. You are also welcome to use it for embedding the globally distributed and highly available deployment on meet.jit.si itself. The only thing we ask for in that case is that you please DO NOT remove the jitsi.org logo from the top left corner.
## Installation
To embed Jitsi Meet in your application you need to add the Jitsi Meet API library:
```javascript
<script src="https://meet.jit.si/external_api.js"></script>
<script src='https://meet.jit.si/external_api.js'></script>
```
## API
@@ -16,7 +16,7 @@ To embed Jitsi Meet in your application you need to add the Jitsi Meet API libra
The next step for embedding Jitsi Meet is to create the Jitsi Meet API object.
Its constructor gets a number of options:
* **domain**: domain used to build the conference URL, "meet.jit.si" for
* **domain**: domain used to build the conference URL, 'meet.jit.si' for
example.
* **options**: object with properties - the optional arguments:
* **roomName**: (optional) name of the room to join.
@@ -29,46 +29,160 @@ Its constructor gets a number of options:
* **jwt**: (optional) [JWT](https://jwt.io/) token.
* **onload**: (optional) handler for the iframe onload event.
* **invitees**: (optional) Array of objects containing information about new participants that will be invited in the call.
* **devices**: (optional) A map containing information about the initial devices that will be used in the call.
Example:
```javascript
var domain = "meet.jit.si";
var options = {
roomName: "JitsiMeetAPIExample",
const domain = 'meet.jit.si';
const options = {
roomName: 'JitsiMeetAPIExample',
width: 700,
height: 700,
parentNode: document.querySelector('#meet')
}
var api = new JitsiMeetExternalAPI(domain, options);
};
const api = new JitsiMeetExternalAPI(domain, options);
```
You can set the initial media devices for the call:
```javascript
const domain = 'meet.jit.si';
const options = {
...
devices: {
audioInput: '<deviceLabel>',
audioOutput: '<deviceLabel>',
videoInput: '<deviceLabel>'
},
...
};
const api = new JitsiMeetExternalAPI(domain, options);
```
You can overwrite options set in [config.js] and [interface_config.js].
For example, to enable the filmstrip-only interface mode, you can use:
```javascript
var options = {
interfaceConfigOverwrite: {filmStripOnly: true}
const options = {
...
interfaceConfigOverwrite: { filmStripOnly: true },
...
};
var api = new JitsiMeetExternalAPI(domain, options);
const api = new JitsiMeetExternalAPI(domain, options);
```
You can also pass a jwt token to Jitsi Meet:
```javascript
var options = {
jwt: "<jwt_token>",
noSsl: false
const options = {
...
jwt: '<jwt_token>',
noSsl: false,
...
};
var api = new JitsiMeetExternalAPI(domain, options);
const api = new JitsiMeetExternalAPI(domain, options);
```
### Controlling the embedded Jitsi Meet Conference
Device management `JitsiMeetExternalAPI` methods:
* **getAvailableDevices** - Retrieve a list of available devices.
```javascript
api.getAvailableDevices().then(devices => {
// devices = {
// audioInput: [{
// deviceId: 'ID'
// groupId: 'grpID'
// kind: 'audioinput'
// label: 'label'
// },....],
// audioOutput: [{
// deviceId: 'ID'
// groupId: 'grpID'
// kind: 'audioOutput'
// label: 'label'
// },....],
// videoInput: [{
// deviceId: 'ID'
// groupId: 'grpID'
// kind: 'videoInput'
// label: 'label'
// },....]
// }
...
});
```
* **getCurrentDevices** - Retrieve a list with the devices that are currently selected.
```javascript
api.getCurrentDevices().then(devices => {
// devices = {
// audioInput: {
// deviceId: 'ID'
// groupId: 'grpID'
// kind: 'videoInput'
// label: 'label'
// },
// audioOutput: {
// deviceId: 'ID'
// groupId: 'grpID'
// kind: 'videoInput'
// label: 'label'
// },
// videoInput: {
// deviceId: 'ID'
// groupId: 'grpID'
// kind: 'videoInput'
// label: 'label'
// }
// }
...
});
```
* **isDeviceChangeAvailable** - Resolves with true if the device change is available and with false if not.
```javascript
// The accepted deviceType values are - 'output', 'input' or undefined.
api.isDeviceChangeAvailable(deviceType).then(isDeviceChangeAvailable => {
...
});
```
* **isDeviceListAvailable** - Resolves with true if the device list is available and with false if not.
```javascript
api.isDeviceListAvailable().then(isDeviceListAvailable => {
...
});
```
* **isMultipleAudioInputSupported** - Resolves with true if multiple audio input is supported and with false if not.
```javascript
api.isMultipleAudioInputSupported().then(isMultipleAudioInputSupported => {
...
});
```
* **setAudioInputDevice** - Sets the audio input device to the one with the label or id that is passed.
```javascript
api.setAudioInputDevice(deviceLabel, deviceId);
```
* **setAudioOutputDevice** - Sets the audio output device to the one with the label or id that is passed.
```javascript
api.setAudioOutputDevice(deviceLabel, deviceId);
```
* **setVideoInputDevice** - Sets the video input device to the one with the label or id that is passed.
```javascript
api.setVideoInputDevice(deviceLabel, deviceId);
```
You can control the embedded Jitsi Meet conference using the `JitsiMeetExternalAPI` object by using `executeCommand`:
```javascript
api.executeCommand(command, ...arguments)
api.executeCommand(command, ...arguments);
```
The `command` parameter is String object with the name of the command. The following commands are currently supported:
@@ -78,59 +192,67 @@ The `command` parameter is String object with the name of the command. The follo
api.executeCommand('displayName', 'New Nickname');
```
* **subject** - Sets the subject of the conference. This command requires one argument - the new subject to be set.
```javascript
api.executeCommand('subject', 'New Conference Subject');
```
* **toggleAudio** - Mutes / unmutes the audio for the local participant. No arguments are required.
```javascript
api.executeCommand('toggleAudio')
api.executeCommand('toggleAudio');
```
* **toggleVideo** - Mutes / unmutes the video for the local participant. No arguments are required.
```javascript
api.executeCommand('toggleVideo')
api.executeCommand('toggleVideo');
```
* **toggleFilmStrip** - Hides / shows the filmstrip. No arguments are required.
```javascript
api.executeCommand('toggleFilmStrip')
api.executeCommand('toggleFilmStrip');
```
* **toggleChat** - Hides / shows the chat. No arguments are required.
```javascript
api.executeCommand('toggleChat')
api.executeCommand('toggleChat');
```
* **toggleShareScreen** - Starts / stops screen sharing. No arguments are required.
```javascript
api.executeCommand('toggleShareScreen')
api.executeCommand('toggleShareScreen');
```
* **hangup** - Hangups the call. No arguments are required.
```javascript
api.executeCommand('hangup')
api.executeCommand('hangup');
```
* **email** - Changes the local email address. This command requires one argument - the new email address to be set.
```javascript
api.executeCommand('email', 'example@example.com')
api.executeCommand('email', 'example@example.com');
```
* **avatarUrl** - Changes the local avatar URL. This command requires one argument - the new avatar URL to be set.
```javascript
api.executeCommand('avatarUrl', 'https://avatars0.githubusercontent.com/u/3671647')
api.executeCommand('avatarUrl', 'https://avatars0.githubusercontent.com/u/3671647');
```
You can also execute multiple commands using the `executeCommands` method:
```javascript
api.executeCommands(commands)
api.executeCommands(commands);
```
The `commands` parameter is an object with the names of the commands as keys and the arguments for the commands as values:
```javascript
api.executeCommands({displayName: ['nickname'], toggleAudio: []});
api.executeCommands({
displayName: [ 'nickname' ],
toggleAudio: []
});
```
You can add event listeners to the embedded Jitsi Meet using the `addEventListener` method.
**NOTE: This method still exists but it is deprecated. JitsiMeetExternalAPI class extends [EventEmitter]. Use [EventEmitter] methods (`addListener` or `on`).**
```javascript
api.addEventListener(event, listener)
api.addEventListener(event, listener);
```
The `event` parameter is a String object with the name of the event.
@@ -141,29 +263,36 @@ The following events are currently supported:
changes. The listener will receive an object with the following structure:
```javascript
{
"id": id, // the id of the participant that changed his avatar.
"avatarURL": avatarURL // the new avatar URL.
id: string, // the id of the participant that changed his avatar.
avatarURL: string // the new avatar URL.
}
```
* **audioAvailabilityChanged** - event notifications about audio availability status changes. The listener will receive an object with the following structure:
```javascript
{
"available": available // new available status - boolean
available: boolean // new available status - boolean
}
```
* **audioMuteStatusChanged** - event notifications about audio mute status changes. The listener will receive an object with the following structure:
```javascript
{
"muted": muted // new muted status - boolean
muted: boolean // new muted status - boolean
}
```
* **screenSharingStatusChanged** - receives event notifications about turning on/off the local user screen sharing. The listener will receive object with the following structure:
```javascript
{
"on": on //whether screen sharing is on
on: boolean, //whether screen sharing is on
details: {
// From where the screen sharing is capturing, if known. Values which are
// passed include 'window', 'screen', 'proxy', 'device'. The value undefined
// will be passed if the source type is unknown or screen share is off.
sourceType: string|undefined
}
}
```
@@ -171,9 +300,9 @@ changes. The listener will receive an object with the following structure:
messages. The listener will receive an object with the following structure:
```javascript
{
"from": from, // The id of the user that sent the message
"nick": nick, // the nickname of the user that sent the message
"message": txt // the text of the message
from: string, // The id of the user that sent the message
nick: string, // the nickname of the user that sent the message
message: string // the text of the message
}
```
@@ -181,7 +310,7 @@ messages. The listener will receive an object with the following structure:
messages. The listener will receive an object with the following structure:
```javascript
{
"message": txt // the text of the message
message: string // the text of the message
}
```
@@ -189,68 +318,90 @@ messages. The listener will receive an object with the following structure:
changes. The listener will receive an object with the following structure:
```javascript
{
"id": id, // the id of the participant that changed his display name
"displayname": displayName // the new display name
id: string, // the id of the participant that changed his display name
displayname: string // the new display name
}
```
* **deviceListChanged** - event notifications about device list changes. The listener will receive an object with the following structure:
```javascript
{
devices: Object // the new list of available devices.
}
```
NOTE: The devices object has the same format as the getAvailableDevices result format.
* **emailChange** - event notifications about email
changes. The listener will receive an object with the following structure:
```javascript
{
"id": id, // the id of the participant that changed his email
"email": email // the new email
id: string, // the id of the participant that changed his email
email: string // the new email
}
```
* **filmstripDisplayChanged** - event notifications about the visibility of the filmstrip being updated.
```javascript
{
visible: boolean // Whether or not the filmstrip is displayed or hidden.
}
```
* **participantJoined** - event notifications about new participants who join the room. The listener will receive an object with the following structure:
```javascript
{
"id": id, // the id of the participant
"displayName": displayName // the display name of the participant
id: string, // the id of the participant
displayName: string // the display name of the participant
}
```
* **participantLeft** - event notifications about participants that leave the room. The listener will receive an object with the following structure:
```javascript
{
"id": id // the id of the participant
id: string // the id of the participant
}
```
* **videoConferenceJoined** - event notifications fired when the local user has joined the video conference. The listener will receive an object with the following structure:
```javascript
{
"roomName": room, // the room name of the conference
"id": id, // the id of the local participant
"displayName": displayName, // the display name of the local participant
"avatarURL": avatarURL // the avatar URL of the local participant
roomName: string, // the room name of the conference
id: string, // the id of the local participant
displayName: string, // the display name of the local participant
avatarURL: string // the avatar URL of the local participant
}
```
* **videoConferenceLeft** - event notifications fired when the local user has left the video conference. The listener will receive an object with the following structure:
```javascript
{
"roomName": room // the room name of the conference
roomName: string // the room name of the conference
}
```
* **videoAvailabilityChanged** - event notifications about video availability status changes. The listener will receive an object with the following structure:
```javascript
{
"available": available // new available status - boolean
available: boolean // new available status - boolean
}
```
* **videoMuteStatusChanged** - event notifications about video mute status changes. The listener will receive an object with the following structure:
```javascript
{
"muted": muted // new muted status - boolean
muted: boolean // new muted status - boolean
}
```
* **readyToClose** - event notification fired when Jitsi Meet is ready to be closed (hangup operations are completed).
* **subjectChange** - event notifications about subject of conference changes.
The listener will receive an object with the following structure:
```javascript
{
subject: string // the new subject
}
```
You can also add multiple event listeners by using `addEventListeners`.
This method requires one argument of type Object. The object argument must
have the names of the events as keys and the listeners of the events as values.
@@ -269,79 +420,80 @@ function outgoingMessageListener(object)
api.addEventListeners({
incomingMessage: incomingMessageListener,
outgoingMessage: outgoingMessageListener})
outgoingMessage: outgoingMessageListener
});
```
If you want to remove a listener you can use `removeEventListener` method with argument the name of the event.
**NOTE: This method still exists but it is deprecated. JitsiMeetExternalAPI class extends [EventEmitter]. Use [EventEmitter] methods( `removeListener`).**
```javascript
api.removeEventListener("incomingMessage");
api.removeEventListener('incomingMessage');
```
If you want to remove more than one event you can use `removeEventListeners` method with an Array with the names of the events as an argument.
**NOTE: This method still exists but it is deprecated. JitsiMeetExternalAPI class extends [EventEmitter]. Use [EventEmitter] methods.**
```javascript
api.removeEventListeners(["incomingMessage", "outgoingMessageListener"]);
api.removeEventListeners([ 'incomingMessage', 'outgoingMessageListener' ]);
```
You can get the number of participants in the conference with the following API function:
```javascript
var numberOfParticipants = api.getNumberOfParticipants();
const numberOfParticipants = api.getNumberOfParticipants();
```
You can get the avatar URL of a participant in the conference with the following API function:
```javascript
var avatarURL = api.getAvatarURL(participantId);
const avatarURL = api.getAvatarURL(participantId);
```
You can get the display name of a participant in the conference with the following API function:
```javascript
var displayName = api.getDisplayName(participantId);
const displayName = api.getDisplayName(participantId);
```
You can get the email of a participant in the conference with the following API function:
```javascript
var email = api.getEmail(participantId);
const email = api.getEmail(participantId);
```
You can get the iframe HTML element where Jitsi Meet is loaded with the following API function:
```javascript
var iframe = api.getIFrame();
const iframe = api.getIFrame();
```
You can check whether the audio is muted with the following API function:
```javascript
api.isAudioMuted().then(function(muted) {
api.isAudioMuted().then(muted => {
...
});
```
You can check whether the video is muted with the following API function:
```javascript
api.isVideoMuted().then(function(muted) {
api.isVideoMuted().then(muted => {
...
});
```
You can check whether the audio is available with the following API function:
```javascript
api.isAudioAvailable().then(function(available) {
api.isAudioAvailable().then(available => {
...
});
```
You can check whether the video is available with the following API function:
```javascript
api.isVideoAvailable().then(function(available) {
api.isVideoAvailable().then(available => {
...
});
```
You can invite new participants to the call with the following API function:
```javascript
api.invite([{...}, {...}, {...}]).then(function() {
api.invite([ {...}, {...}, {...} ]).then(() => {
// success
}).catch(function() {
}).catch(() => {
// failure
});
```
@@ -349,7 +501,7 @@ api.invite([{...}, {...}, {...}]).then(function() {
You can remove the embedded Jitsi Meet Conference with the following API function:
```javascript
api.dispose()
api.dispose();
```
NOTE: It's a good practice to remove the conference before the page is unloaded.

View File

@@ -71,7 +71,7 @@ paths:
$ref: "#/definitions/ConferenceMapperDetails"
405:
description: "Invalid input"
/phoneNumberList:
get:
tags:
@@ -96,7 +96,7 @@ securityDefinitions:
name: "Authorization"
in: "header"
definitions:
ConferenceMapperRequest:
description: "Request to create or find a conference mapping"
type: "object"
@@ -114,7 +114,7 @@ definitions:
domain:
type: "string"
description: "Domain part of the conference. Used if 'conference' is not provided. Defaults to domain of the API endpoint. Used to generate a 'conference' value (search by conference)"
ConferenceMapperDetails:
description: "Conference mapping between conference JID and numeric ID"
type: "object"
@@ -126,22 +126,26 @@ definitions:
type: "string"
format: "JID"
description: "Full JID for the conference OR boolean false if no conference was found (search by ID)"
PhoneNumberList:
type: "object"
properties:
numbersEnabled:
type: "boolean"
description: "Control flag for Jitsi Meet user interface. Must be set to true for Jitsi Meet to display phone-in UI elements"
numbers:
description: "List of dial in numbers for the conference."
type: "array"
items:
type: "object"
description: "Keys are Country Names, each value is an array of phone numbers"
additionalProperties:
type: "array"
items:
properties:
countryCode:
type: "string"
format: "phone"
description: "ISO 3166-1 country code. Alpha-2 supported."
default:
type: "boolean"
description: "Whether this number is the default one to show. Optional."
formattedNumber:
type: "string"
description: "The formatted telephone number to show."
tollFree:
type: "boolean"
description: "Whether the number is toll free number."
externalDocs:
description: "Find out more about the Jitsi Cloud API"
url: "https://jitsi.org/CloudAPI"
url: "https://jitsi.org/CloudAPI"

View File

@@ -11,7 +11,7 @@
roomName: "JitsiMeetAPIExample",
width: 700,
height: 180,
parent: undefined,
parentNode: undefined,
configOverwrite: {},
interfaceConfigOverwrite: {
filmStripOnly: true

7
doc/faq.md Normal file
View File

@@ -0,0 +1,7 @@
**1. How to tell if my server instance is behind NAT?**
A. In general, if the tool ifconfig (or ipconfig) shows the assigned IP address to be some local address (10.x.x.x or 192.x.x.x) but you know that its public IP address is different from that, the server is most probably behind NAT
**2. Clients could communicate well in room created at meet.jit.si . The same clients still could connect to my self-hosted instance but can neither hear nor see one another. What's wrong?**
A. Most probably, the server is behind NAT. See this [resolved question](https://community.jitsi.org/t/cannot-see-video-or-hear-audio-on-self-hosted-instance/). You need to follow the steps detailed [here](https://github.com/jitsi/ice4j/blob/master/doc/quick-install.md#Advanced-configuration)

38
doc/integrations.md Normal file
View File

@@ -0,0 +1,38 @@
Document describing enabling various jitsi-meet integrations.
## Creating the Google API client for Google Calendar and Youtube integration
1. Log into a Google admin account.
1. Go to Google cloud platform dashboard. https://console.cloud.google.com/apis/dashboard
1. In the Select a Project dropdown, click New Project.
1. Give the project a name.
1. Proceed to the Credentials settings of the new project.
1. In the Credentials tab of the Credentials settings, click Create Credentials and select the type OAuth client ID.
1. Proceed with creating a Web application and add the domains (origins) on which the application will be hosted. Local development environments (http://localhost:8000 for example) can be added here.
1. While still in the Google cloud platform dashboard, click the Library settings for the calendar project.
1. Search for the Google Calendar API (used for calendar accessing), click its result, and enable it.
1. Do the same for YouTube Data API v3
## Creating the Microsoft app for Microsoft Outlook integration
1. Go to https://apps.dev.microsoft.com/
1. Proceed through the "Add an app" flow. Once created, a page with several Graph Permissions fields should display.
1. Under "Platforms" add "Web"
1. Add a redirect URL for the Microsoft auth flow to visit once a user has confirmed authentication. Target domain if available is just 'yourdomain.com' (the deployment address) and the redirect URL is `https://yourdomain.com/static/msredirect.html`.
1. Add Microsoft Graph delegated permissions, if this option is available: Calendars.Read, Calendars.ReadWrite, Calendars.Read.Shared, Calendars.ReadWrite.Shared.
1. Check `Allow Implicit Flow` (and `Restrict token issuing to this app` if available).
1. Save the changes.
## Creating the Dropbox app for Dropbox recording integration
1. You need a Dropbox account (If you don't already have one, you can sign up for a free account [here](https://www.dropbox.com/register).)
1. Create new App as described in [Getting Started Guide](https://www.dropbox.com/developers/reference/getting-started?_tk=guides_lp&_ad=guides2&_camp=get_started#app%20console) in App Console section.
1. Choose
1. 'Dropbox API - For apps that need to access files in Dropbox.'
1. 'App folder Access to a single folder created specifically for your app.'
1. Fill in the name of your app
1. You need only, the newly created App key, goes in config.js in
```
dropbox: {
appKey: '__dropbox_app_key__'
}
```
1. Add your Redirect URIs in the form `https://yourdeployment.com//static/oauth.html`
1. Fill in Branding

View File

@@ -159,7 +159,7 @@ unzip jitsi-videobridge-linux-{arch-buildnum}.zip
Install JRE if missing:
```
apt-get install default-jre
apt-get install openjdk-8-jre
```
_NOTE: When installing on older Debian releases keep in mind that you need JRE >= 1.7._
@@ -182,7 +182,7 @@ Or autostart it by adding the line in `/etc/rc.local`:
Install JDK and Maven if missing:
```
apt-get install default-jdk maven
apt-get install openjdk-8-jdk maven
```
_NOTE: When installing on older Debian releases keep in mind that you need JDK >= 1.7._

View File

@@ -55,7 +55,7 @@ Simply run the following in your shell
```
#### Advanced configuration
If installation is on a machine behind NAT further configuration of jitsi-videobridge is needed in order for it to be accessible.
If installation is on a machine [behind NAT](https://github.com/jitsi/jitsi-meet/blob/master/doc/faq.md) further configuration of jitsi-videobridge is needed in order for it to be accessible.
Provided that all required ports are routed (forwarded) to the machine that it runs on. By default these ports are (TCP/443 or TCP/4443 and UDP 10000).
The following extra lines need to be added the file `/etc/jitsi/videobridge/sip-communicator.properties`:
```

View File

@@ -19,10 +19,6 @@ edit /etc/jitsi/jicofo/sip-communicator.properties (or similar), set the appropr
peopleSearchQueryTypes: ['conferenceRooms'],
peopleSearchUrl: 'https://api.yourdomain.com/testpath/searchpeople',
```
- interface_config.js:
```
ADD_PEOPLE_APP_NAME: 'Jitsi'
```
The combination of the above settings and providing a jwt token will enable a button under invite option which will show the dialog 'Add people'.

View File

@@ -0,0 +1,22 @@
# Enabling speakerstats prosody module
To enable the speaker stats we need to enable speakerstats module under the main
virtual host, this is to enable the advertising the speaker stats component,
which address needs to be specified in `speakerstats_component` option.
We need to also enable the component with the address specified in `speakerstats_component`.
The component needs also to have the option with the muc component address in
`muc_component` option.
```lua
VirtualHost "jitsi.example.com"
speakerstats_component = "speakerstats.jitsi.example.com"
modules_enabled = {
"speakerstats";
}
Component "speakerstats.jitsi.example.com" "speakerstats_component"
muc_component = "conference.jitsi.example.com"
Component "conference.jitsi.example.com" "muc"
```

View File

@@ -1,5 +1,5 @@
// flow-typed signature: d71d314ca25fc6c20610a3ba80af9df0
// flow-typed version: 9698a46399/jquery_v3.x.x/flow_>=v0.28.x
// flow-typed signature: f26fda66e3a551aef37d3b0f53058e6a
// flow-typed version: 44ad941b7a/jquery_v3.x.x/flow_>=v0.28.x
/* eslint-disable max-len, no-unused-vars, flowtype/no-weak-types */
@@ -364,6 +364,13 @@ declare class JQueryGenericPromise<T> {
failFilter?: (...reasons: any[]) => any,
progressFilter?: (...progression: any[]) => any
): JQueryPromise<void>;
/**
* Add handlers to be called when the Deferred object is rejected.
*
* @param failFilter An function that is called when the Deferred is rejected.
*/
catch(failFilter: (...reasons: any[]) => any): JQueryPromise<T>;
}
/**
@@ -822,7 +829,7 @@ declare class JQueryStatic {
*/
get(
url: string,
data?: Object | string,
data?: {} | string,
success?: (data: any, textStatus: string, jqXHR: JQueryXHR) => any,
dataType?: string
): JQueryXHR;
@@ -845,7 +852,7 @@ declare class JQueryStatic {
*/
getJSON(
url: string,
data?: Object | string,
data?: {} | string,
success?: (data: any, textStatus: string, jqXHR: JQueryXHR) => any
): JQueryXHR;
/**
@@ -886,7 +893,7 @@ declare class JQueryStatic {
*/
post(
url: string,
data?: Object | string,
data?: {} | string,
success?: (data: any, textStatus: string, jqXHR: JQueryXHR) => any,
dataType?: string
): JQueryXHR;
@@ -967,7 +974,7 @@ declare class JQueryStatic {
* @param html A string defining a single, standalone, HTML element (e.g. <div/> or <div></div>).
* @param attributes An object of attributes, events, and methods to call on the newly-created element.
*/
(html: string, attributes: Object): JQuery;
(html: string, attributes: {}): JQuery;
/**
* Relinquish jQuery's control of the $ variable.
@@ -1425,7 +1432,7 @@ declare class JQuery {
*/
load(
url: string,
data?: string | Object,
data?: string | {},
complete?: (
responseText: string,
textStatus: string,
@@ -1482,7 +1489,7 @@ declare class JQuery {
*
* @param attributes An object of attribute-value pairs to set.
*/
attr(attributes: Object): JQuery;
attr(attributes: {}): JQuery;
/**
* Get the value of an attribute for the first element in the set of matched elements.
*
@@ -1532,7 +1539,7 @@ declare class JQuery {
*
* @param properties An object of property-value pairs to set.
*/
prop(properties: Object): JQuery;
prop(properties: {}): JQuery;
/**
* Set one or more properties for the set of matched elements.
*
@@ -1598,7 +1605,7 @@ declare class JQuery {
/**
* Get the current value of the first element in the set of matched elements.
*/
val(_: void): any;
val(_: void): string | string[] | number;
/**
* Set the value of each element in the set of matched elements.
*
@@ -1634,7 +1641,7 @@ declare class JQuery {
*
* @param properties An object of property-value pairs to set.
*/
css(properties: Object): JQuery;
css(properties: {}): JQuery;
/**
* Get the value of style properties for the first element in the set of matched elements.
*
@@ -1847,7 +1854,7 @@ declare class JQuery {
* @param complete A function to call once the animation is complete.
*/
animate(
properties: Object,
properties: {},
duration?: string | number,
complete?: Function
): JQuery;
@@ -1860,7 +1867,7 @@ declare class JQuery {
* @param complete A function to call once the animation is complete.
*/
animate(
properties: Object,
properties: {},
duration?: string | number,
easing?: string,
complete?: Function
@@ -1871,7 +1878,7 @@ declare class JQuery {
* @param properties An object of CSS properties and values that the animation will move toward.
* @param options A map of additional options to pass to the method.
*/
animate(properties: Object, options: JQueryAnimationOptions): JQuery;
animate(properties: {}, options: JQueryAnimationOptions): JQuery;
/**
* Set a timer to delay execution of subsequent items in the queue.
@@ -2920,14 +2927,14 @@ declare class JQuery {
* @param eventType A string containing a JavaScript event type, such as click or submit.
* @param extraParameters Additional parameters to pass along to the event handler.
*/
trigger(eventType: string, extraParameters?: any[] | Object): JQuery;
trigger(eventType: string, extraParameters?: any[] | {}): JQuery;
/**
* Execute all handlers and behaviors attached to the matched elements for the given event type.
*
* @param event A jQuery.Event object.
* @param extraParameters Additional parameters to pass along to the event handler.
*/
trigger(event: JQueryEventObject, extraParameters?: any[] | Object): JQuery;
trigger(event: JQueryEventObject, extraParameters?: any[] | {}): JQuery;
/**
* Execute all handlers attached to an element for an event.

File diff suppressed because it is too large Load Diff

View File

@@ -1,329 +0,0 @@
// flow-typed signature: c30aa20539f52183d4d30dd36d8ab9c2
// flow-typed version: 886cf7c002/moment_v2.3.x/flow_>=v0.63.x
type moment$MomentOptions = {
y?: number | string,
year?: number | string,
years?: number | string,
M?: number | string,
month?: number | string,
months?: number | string,
d?: number | string,
day?: number | string,
days?: number | string,
date?: number | string,
h?: number | string,
hour?: number | string,
hours?: number | string,
m?: number | string,
minute?: number | string,
minutes?: number | string,
s?: number | string,
second?: number | string,
seconds?: number | string,
ms?: number | string,
millisecond?: number | string,
milliseconds?: number | string
};
type moment$MomentObject = {
years: number,
months: number,
date: number,
hours: number,
minutes: number,
seconds: number,
milliseconds: number
};
type moment$MomentCreationData = {
input: string,
format: string,
locale: Object,
isUTC: boolean,
strict: boolean
};
type moment$CalendarFormat = string | ((moment: moment$Moment) => string);
type moment$CalendarFormats = {
sameDay?: moment$CalendarFormat,
nextDay?: moment$CalendarFormat,
nextWeek?: moment$CalendarFormat,
lastDay?: moment$CalendarFormat,
lastWeek?: moment$CalendarFormat,
sameElse?: moment$CalendarFormat
};
declare class moment$LocaleData {
months(moment: moment$Moment): string,
monthsShort(moment: moment$Moment): string,
monthsParse(month: string): number,
weekdays(moment: moment$Moment): string,
weekdaysShort(moment: moment$Moment): string,
weekdaysMin(moment: moment$Moment): string,
weekdaysParse(weekDay: string): number,
longDateFormat(dateFormat: string): string,
isPM(date: string): boolean,
meridiem(hours: number, minutes: number, isLower: boolean): string,
calendar(
key:
| "sameDay"
| "nextDay"
| "lastDay"
| "nextWeek"
| "prevWeek"
| "sameElse",
moment: moment$Moment
): string,
relativeTime(
number: number,
withoutSuffix: boolean,
key: "s" | "m" | "mm" | "h" | "hh" | "d" | "dd" | "M" | "MM" | "y" | "yy",
isFuture: boolean
): string,
pastFuture(diff: any, relTime: string): string,
ordinal(number: number): string,
preparse(str: string): any,
postformat(str: string): any,
week(moment: moment$Moment): string,
invalidDate(): string,
firstDayOfWeek(): number,
firstDayOfYear(): number
}
declare class moment$MomentDuration {
humanize(suffix?: boolean): string,
milliseconds(): number,
asMilliseconds(): number,
seconds(): number,
asSeconds(): number,
minutes(): number,
asMinutes(): number,
hours(): number,
asHours(): number,
days(): number,
asDays(): number,
months(): number,
asMonths(): number,
years(): number,
asYears(): number,
add(value: number | moment$MomentDuration | Object, unit?: string): this,
subtract(value: number | moment$MomentDuration | Object, unit?: string): this,
as(unit: string): number,
get(unit: string): number,
toJSON(): string,
toISOString(): string,
isValid(): boolean
}
declare class moment$Moment {
static ISO_8601: string,
static (
string?: string,
format?: string | Array<string>,
strict?: boolean
): moment$Moment,
static (
string?: string,
format?: string | Array<string>,
locale?: string,
strict?: boolean
): moment$Moment,
static (
initDate: ?Object | number | Date | Array<number> | moment$Moment | string
): moment$Moment,
static unix(seconds: number): moment$Moment,
static utc(): moment$Moment,
static utc(number: number | Array<number>): moment$Moment,
static utc(
str: string,
str2?: string | Array<string>,
str3?: string
): moment$Moment,
static utc(moment: moment$Moment): moment$Moment,
static utc(date: Date): moment$Moment,
static parseZone(): moment$Moment,
static parseZone(rawDate: string): moment$Moment,
static parseZone(
rawDate: string,
format: string | Array<string>
): moment$Moment,
static parseZone(
rawDate: string,
format: string,
strict: boolean
): moment$Moment,
static parseZone(
rawDate: string,
format: string,
locale: string,
strict: boolean
): moment$Moment,
isValid(): boolean,
invalidAt(): 0 | 1 | 2 | 3 | 4 | 5 | 6,
creationData(): moment$MomentCreationData,
millisecond(number: number): this,
milliseconds(number: number): this,
millisecond(): number,
milliseconds(): number,
second(number: number): this,
seconds(number: number): this,
second(): number,
seconds(): number,
minute(number: number): this,
minutes(number: number): this,
minute(): number,
minutes(): number,
hour(number: number): this,
hours(number: number): this,
hour(): number,
hours(): number,
date(number: number): this,
dates(number: number): this,
date(): number,
dates(): number,
day(day: number | string): this,
days(day: number | string): this,
day(): number,
days(): number,
weekday(number: number): this,
weekday(): number,
isoWeekday(number: number): this,
isoWeekday(): number,
dayOfYear(number: number): this,
dayOfYear(): number,
week(number: number): this,
weeks(number: number): this,
week(): number,
weeks(): number,
isoWeek(number: number): this,
isoWeeks(number: number): this,
isoWeek(): number,
isoWeeks(): number,
month(number: number): this,
months(number: number): this,
month(): number,
months(): number,
quarter(number: number): this,
quarter(): number,
year(number: number): this,
years(number: number): this,
year(): number,
years(): number,
weekYear(number: number): this,
weekYear(): number,
isoWeekYear(number: number): this,
isoWeekYear(): number,
weeksInYear(): number,
isoWeeksInYear(): number,
get(string: string): number,
set(unit: string, value: number): this,
set(options: { [unit: string]: number }): this,
static max(...dates: Array<moment$Moment>): moment$Moment,
static max(dates: Array<moment$Moment>): moment$Moment,
static min(...dates: Array<moment$Moment>): moment$Moment,
static min(dates: Array<moment$Moment>): moment$Moment,
add(
value: number | moment$MomentDuration | moment$Moment | Object,
unit?: string
): this,
subtract(
value: number | moment$MomentDuration | moment$Moment | string | Object,
unit?: string
): this,
startOf(unit: string): this,
endOf(unit: string): this,
local(): this,
utc(): this,
utcOffset(
offset: number | string,
keepLocalTime?: boolean,
keepMinutes?: boolean
): this,
utcOffset(): number,
format(format?: string): string,
fromNow(removeSuffix?: boolean): string,
from(
value: moment$Moment | string | number | Date | Array<number>,
removePrefix?: boolean
): string,
toNow(removePrefix?: boolean): string,
to(
value: moment$Moment | string | number | Date | Array<number>,
removePrefix?: boolean
): string,
calendar(refTime?: any, formats?: moment$CalendarFormats): string,
diff(
date: moment$Moment | string | number | Date | Array<number>,
format?: string,
floating?: boolean
): number,
valueOf(): number,
unix(): number,
daysInMonth(): number,
toDate(): Date,
toArray(): Array<number>,
toJSON(): string,
toISOString(
keepOffset?: boolean
): string,
toObject(): moment$MomentObject,
isBefore(
date?: moment$Moment | string | number | Date | Array<number>,
units?: ?string
): boolean,
isSame(
date?: moment$Moment | string | number | Date | Array<number>,
units?: ?string
): boolean,
isAfter(
date?: moment$Moment | string | number | Date | Array<number>,
units?: ?string
): boolean,
isSameOrBefore(
date?: moment$Moment | string | number | Date | Array<number>,
units?: ?string
): boolean,
isSameOrAfter(
date?: moment$Moment | string | number | Date | Array<number>,
units?: ?string
): boolean,
isBetween(
fromDate: moment$Moment | string | number | Date | Array<number>,
toDate?: ?moment$Moment | string | number | Date | Array<number>,
granularity?: ?string,
inclusion?: ?string
): boolean,
isDST(): boolean,
isDSTShifted(): boolean,
isLeapYear(): boolean,
clone(): moment$Moment,
static isMoment(obj: any): boolean,
static isDate(obj: any): boolean,
static locale(locale: string, localeData?: Object): string,
static updateLocale(locale: string, localeData?: ?Object): void,
static locale(locales: Array<string>): string,
locale(locale: string, customization?: Object | null): moment$Moment,
locale(): string,
static months(): Array<string>,
static monthsShort(): Array<string>,
static weekdays(): Array<string>,
static weekdaysShort(): Array<string>,
static weekdaysMin(): Array<string>,
static months(): string,
static monthsShort(): string,
static weekdays(): string,
static weekdaysShort(): string,
static weekdaysMin(): string,
static localeData(key?: string): moment$LocaleData,
static duration(
value: number | Object | string,
unit?: string
): moment$MomentDuration,
static isDuration(obj: any): boolean,
static normalizeUnits(unit: string): string,
static invalid(object: any): moment$Moment
}
declare module "moment" {
declare module.exports: Class<moment$Moment>;
}

Some files were not shown because too many files have changed in this diff Show More