Compare commits

...

658 Commits

Author SHA1 Message Date
Aaron van Meerten
e48ddc28eb updated node-sass version for npm 10 2018-11-20 16:39:41 -06:00
Bettenbuk Zoltan
71edea8aac Rearrange recording feature files 2018-11-20 14:42:33 +01:00
Bettenbuk Zoltan
2b1cb75e40 [RN] Update react-native-calendar-events lib
This is required to get rid of a warning after react native update. See related commit in lib.
2018-11-19 14:35:22 +01:00
damencho
216782d606 Commit from translate.jitsi.org by user damencho.: 583 of 583 strings translated (0 fuzzy). 2018-11-14 03:33:01 +00:00
Emil Ivov
c707b82419 Replacing Atlassian with 8x8 2018-11-11 08:43:34 -06:00
Bettenbuk Zoltan
3fdf944763 Fix eslint/jsdoc warnings (doc change only!) 2018-11-08 15:52:34 +01:00
virtuacoplenny
56100d0d5c Merge pull request #3594 from mmoanis/update-docs
Update docs for AbstractRecordButton _mapStateToProps
2018-11-07 09:20:05 -08:00
Leonard Kim
486e8e35d9 ref: move all prop type declaration to flow
For the most part the changes are taking the "static propTypes" declaration off
of components and declaring them as Flow types. Sometimes to support flow some
method signatures had to be added. There are some exceptions in which more had
to be done to tame the beast:
- AbstractVideoTrack: put in additional truthy checks for videoTrack.
- Video: add truthy checks for the _videoElement ref.
- shouldRenderVideoTrack function: Some component could pass null for the
  videoTrack argument and Flow wanted that called out explicitly.
- DisplayName: Add a truthy check for the input ref before acting on it.
- NumbersList: Move array checks inline for Flow to comprehend array methods
  could be called. Add type checks in the Object.entries loop as the value is
  assumed to be a mixed type by Flow.
- AbstractToolbarButton: add additional truthy check for passed in type.
2018-11-07 17:38:10 +01:00
Bettenbuk Zoltan
554974a36d [RN] Fix YouTube channel name list 2018-11-07 16:48:56 +01:00
mmoanis
cd943319d6 Update docs for AbstractRecordButton _mapStateToProps 2018-11-06 11:36:00 +01:00
Дамян Минков
837f496e8f Moves muc definition to be last.
Fixes common problem where people following https://github.com/jitsi/jicofo#secure-domain have a syntax error, forgetting the comma after muc definition.
2018-11-06 09:55:32 +01:00
Aaron van Meerten
c43f7c8979 Merge pull request #3589 from jitsi/max-occupants
Adds max occupant module.
2018-11-03 11:37:14 -05:00
damencho
c9c9f7eac0 Adds max occupant module. 2018-11-03 10:45:59 -05:00
Leonard Kim
5ccc397e47 chore(deps): update react-i18next from 4.8.0 to 7.13.0
None of the breaking changes seemed to affect current
usage of react-i18next and light testing of features
and language switching did not produce issues.

This update is a pre-requisite for removing deprecated react
lifecycle methods, as older versions of react-i18next
have a higher order component that uses the deprecated
componentWillMount, and that issue has been fixed since 7.8.0.
2018-10-29 20:49:53 +01:00
hristoterezov
00cd82d976 fix(analytics-ga): Ignore some events 2018-10-22 16:18:47 -05:00
hristoterezov
61deb74444 fix(welcome-page): Change text 2018-10-22 16:18:30 -05:00
Hristo Terezov
b30008e3a5 feat(welcome-page): Redesign. (#3559)
* feat(welcome-page): Redesign.

* Style adjustments.
2018-10-22 13:49:18 -05:00
bgrozev
62b6737a3f fix: Filter more events for google analytics. (#3557) 2018-10-18 17:15:27 -05:00
damencho
cd77a9176c Make sure we do only one replacement, not one over another for messages. 2018-10-18 23:37:07 +02:00
damencho
2a61968566 Fixes small video's top toolbar background in dark theme. 2018-10-18 09:51:10 +02:00
yanas
be4813e10d Revert "feat(cleanup): remove no longer used FontAwesome"
This reverts commit d3c5756f7a.
2018-10-17 16:27:18 -05:00
Saúl Ibarra Corretgé
ae890dc093 deps: update react-native-webrtc (M69) 2018-10-16 16:32:26 +02:00
Saúl Ibarra Corretgé
9407f562f6 [iOS] Simplify dynamically loading fonts 2018-10-16 14:33:48 +02:00
Saúl Ibarra Corretgé
011a46ce2d [RN] Don't bundle fonts we don't use 2018-10-16 14:33:48 +02:00
Saúl Ibarra Corretgé
8e0bd36ece deps: update react-native-vector-icons 2018-10-16 14:33:48 +02:00
Saúl Ibarra Corretgé
6f8743af3a doc: update Google SignIn integration documentation 2018-10-16 14:01:11 +02:00
paweldomas
58d220d645 deps: update LJM to fix grey overlay
Updates LJM to fix grey overlay with "user is having connectivity
issues" caused by remote track overwrite.
2018-10-16 09:58:42 +02:00
Saúl Ibarra Corretgé
d3c5756f7a feat(cleanup): remove no longer used FontAwesome 2018-10-15 14:14:25 -05:00
Saúl Ibarra Corretgé
5ff1ce5a60 [iOS] Don't show google signin button on iOS <= 10
It doesn't seem to work properly.
2018-10-12 13:17:29 -05:00
Saúl Ibarra Corretgé
843f08f38e [RN] Don't show a beta label for recordings 2018-10-12 12:03:32 -05:00
Saúl Ibarra Corretgé
418575136f [RN] Don't use webClientId on mobile
That is only required if we'd want our backend to authenticate on behalf of our
users. If the app is to authenticate directly it's not needed.
2018-10-12 13:08:20 +02:00
Saúl Ibarra Corretgé
8c97ce2ee9 deps: update react-native-google-signin 2018-10-12 13:08:20 +02:00
Saúl Ibarra Corretgé
b2245729cc [iOS] Update Podfile.lock 2018-10-12 13:08:20 +02:00
Saúl Ibarra Corretgé
cc2b5a261b deps: update lib-jitsi-meet 2018-10-10 18:07:36 +02:00
Saúl Ibarra Corretgé
13c4ec884b deps: update package-lock.json
It's impossible to avoid conflicts on a long lived PR like this. Sigh.
2018-10-10 18:07:36 +02:00
Saúl Ibarra Corretgé
7162080d00 feat(flow): tame the beast 2018-10-10 18:07:36 +02:00
Saúl Ibarra Corretgé
b71adbdf70 deps: update React Native to version 0.57 2018-10-10 18:07:36 +02:00
Saúl Ibarra Corretgé
2ae2f04f0a feat(eslint): tame the beast 2018-10-10 18:07:36 +02:00
Saúl Ibarra Corretgé
4424c456a9 deps: update eslint
This is required due to the Babel update.
2018-10-10 18:07:36 +02:00
Saúl Ibarra Corretgé
cfa1e2f90d deps: update to Babel 7 2018-10-10 18:07:36 +02:00
Saúl Ibarra Corretgé
d290d28248 feat(Audio): fix react warning 2018-10-10 18:07:36 +02:00
Saúl Ibarra Corretgé
0474031a78 deps: update react and styled-components 2018-10-10 18:07:36 +02:00
paweldomas
a57a5ca49d chore(Travis): use XCode 10 image 2018-10-09 17:54:40 -05:00
Bettenbuk Zoltan
d8c1f107da [RN] Add swipe to delete feature 2018-10-09 13:35:06 +02:00
Saúl Ibarra Corretgé
9d27c36d80 config: remove no longer used option 2018-10-08 11:31:47 -05:00
Saúl Ibarra Corretgé
057b300074 feat(Participant): reuse avatar URL generation logic
It was moved to js-utils, so make use of it.
2018-10-05 17:15:00 +02:00
Bettenbuk Zoltan
e164a23cf0 [RN] Fix start recording dialog after dropbox changes 2018-10-04 12:10:28 -05:00
Bettenbuk Zoltan
61456b0d99 Handle all day events in calendar 2018-10-04 16:21:51 +02:00
damencho
df55448a2c Fixes chat image links to reflect html base. 2018-10-03 14:44:33 -05:00
Bettenbuk Zoltan
6953569629 Fix sticky recording labels 2018-10-03 12:52:21 +02:00
hristoterezov
4d2614660c fix(recording): Show the button when the dropbox integration is disabled 2018-10-02 13:50:05 -05:00
Bettenbuk Zoltan
60f7ba7301 [RN] Remove mobile notifications 2018-10-02 20:47:41 +02:00
Bettenbuk Zoltan
e5cc732b72 [RN] Add ExpandedLabel 2018-10-02 20:47:41 +02:00
Bettenbuk Zoltan
d604cdfe27 Turn TranscribingLabel a self-containing component 2018-10-02 20:47:41 +02:00
Lars Spaenij
dc90800e50 Removed unnecessary string wrapping (#3490)
* Removed unnecessary string wrapping
2018-10-01 22:39:16 -05:00
Saúl Ibarra Corretgé
6f17988d17 [iOS] Fix warning (#3491)
When a native iOS module implements `constantsToExport` it must define
`requiresMainQueueSetup`. In this case we don't do any UI stuff so it doesn't
need to be initialized in the main thread.
2018-10-01 21:47:48 -05:00
Saúl Ibarra Corretgé
c54db8337d [iOS] Unify openURL methods and mark the old one deprecated (#3489)
Make sure both methods offer the same behavior and mark the old one as
deprecated so SDK users get a warning.
2018-09-28 14:54:09 -05:00
Дамян Минков
9a1e9fff98 Updates quick-install with instructions for systemd config. (#3488)
Fixes too many open files.
2018-09-27 16:58:10 -05:00
hristoterezov
a214be0dfe doc(dropbox): Add documentation for the mobile app. 2018-09-27 01:42:59 -05:00
hristoterezov
39a22effb1 fix(build.gradle): Move dropboxAppKey definition to defaultConfig 2018-09-27 01:42:59 -05:00
hristoterezov
ca600928f5 feat(build.gradle): Use the Dropbox app key specified in Info.plist. 2018-09-27 01:42:59 -05:00
hristoterezov
60decf7692 ref(dropbox): Consistency for the naming around the app key. 2018-09-27 01:42:59 -05:00
hristoterezov
467452d110 fix(deeplinking): After braking it with the dropbox implementation. 2018-09-27 01:42:59 -05:00
hristoterezov
af37141e3d feat(dropbox): For mobile.
This involves redesign of the web recording dialog in order to look the
same as the mobile one.
2018-09-27 01:42:59 -05:00
hristoterezov
ae7a882188 feat(Switch): Implement 2018-09-27 01:42:59 -05:00
hristoterezov
a49e590e7c feat(LoadingIndicator): Implementation for web. 2018-09-27 01:42:59 -05:00
hristoterezov
1928efda11 fix(web/Text): p->span 2018-09-27 01:42:59 -05:00
hristoterezov
57bf165ebd doc(config): dropbox 2018-09-27 01:42:59 -05:00
hristoterezov
38517127c3 feat(dropbox): Implement react-native module. 2018-09-27 01:42:59 -05:00
Pratik Shah
b7b43e8d9c feat(chat): convert to use React
- Change "features/chat" to support listening for new chat messages
  and storing them, removing that logic from conference.js.
- Combine chat.scss and side_toolbar_container.css, and remove unused
  scss files. Chat is the only side panel so the two concepts have
  been merged.
- Remove direct access to the chat feature from non-react and non-redux
  flows.
- Modify the i18n translate function to take in an options object.
  By default the option "wait" is set to true, but that causes
  components to mount after the parent has been notified of
  an update, which means autoscrolling down to the latest rendered
  messages does not work. With "wait" set to false, the children
  will mount and then the parent will trigger componentDidUpdate.
- Create react components for chat. Chat is the side panel
  plus the entiren chat feature. ChatInput is a child of Chat and
  is used for composing messages. ChatMessage displays one message
  and extends PureComponent to limit re-renders.
- Fix a bug where the toolbar was not showing automatically when
  chat is closed and a new message is received.
- Import react-transition-group to time the animation of the
  side panel showing/hiding and unmounting the Chat component.
  This gets around the issue of having to control autofocus if the
  component were always mounted and visibility toggled, but
  introduces not being able to store previous scroll state
  (without additional work or re-work).
2018-09-26 14:48:10 -05:00
Zoltan Bettenbuk
8adc8a090a [RN] Fix duplicated notifications (#3479) 2018-09-26 11:06:26 -05:00
jakob
8c23d43a3a doc: add ubuntu universe repo note to quickinstall
Recent Ubuntus come with only main repositories enabled. See #3427.
2018-09-25 11:12:04 -05:00
bgrozev
5773bc48ed fix: Filters out e2e_rtt events for google analytics. (#3476) 2018-09-25 11:00:46 -05:00
paweldomas
9613755055 fix(Travis/iOS): use '-quiet' with 'xcodebuild' commands
This reduces 'xcodebuild' verbosity and fixed problem with exceeding 4MB
of logs size imposed by Travis CI.
2018-09-24 14:34:11 -05:00
virtuacoplenny
1fbc68d0cc Merge pull request #3471 from saghul/update-ljm
deps: update lib-jitsi-meet dependency
2018-09-21 09:01:42 -07:00
Bettenbuk Zoltan
2cbe7922f6 [RN] Dialogs: Replace legacy common style with common container 2018-09-21 15:22:27 +02:00
Saúl Ibarra Corretgé
a712e26ee2 deps: update lib-jitsi-meet dependency 2018-09-21 15:18:46 +02:00
Saúl Ibarra Corretgé
b673c4a11a feat(permissions): adjust to changes in permissions checking 2018-09-21 11:35:16 +02:00
paweldomas
8282873de5 fix(travis): tail last 500 lines of logs only on failure
This is a workaround suggested by Travis support to workaround the
"The job exceeded the maximum log length, and has been terminated."
error.

Another option would be to upload the logs somewhere, but actually it
might be more convenient not having to scroll down all those logs. We
can revisit this case if some problems will be encountered (like for
example if there's need to see something in the middle).
2018-09-20 14:49:19 -05:00
Saúl Ibarra Corretgé
2101f70a09 cleanup: remove no longer used code 🔥🔥🔥
The code for handling device availability has been disabled for a long time,
plus it's ill named since it represents 2 abstractions: lack of permissions and
lack of devices.

Time for it to rest in the git graveyard.
2018-09-19 15:12:31 +00:00
hristoterezov
717fade79c ref(proguard): Create common proguard config. 2018-09-18 14:37:05 -05:00
hristoterezov
959e687ed4 feat(proguard): Add crashlytics rules 2018-09-18 14:37:05 -05:00
hristoterezov
5f5adc3fa8 feat(proguard): enable 2018-09-18 14:37:05 -05:00
bgrozev
f317f993fd chore: Updates lib-jitsi-meet to 51a982a7c8b9c3e89be75b0fdf7fedf4748a7345. (#3463) 2018-09-18 12:45:05 -05:00
Zoltan Bettenbuk
b2baab573e Package lock changes for atlaskit (#3462) 2018-09-18 10:20:23 -05:00
virtuacoplenny
12ed711cce Merge pull request #3457 from bgrozev/update-lib-jitsi-meet
chore: Updates lib-jitsi-meet to 615934a78afb3b501976263f5f916efed7af…
2018-09-17 20:50:11 -07:00
Boris Grozev
dfbd8d71ad chore: Updates lib-jitsi-meet to 615934a78afb3b501976263f5f916efed7af0080. 2018-09-17 22:10:48 -05:00
bgrozev
d051d3450d Displays the region participants are connected to (#3451)
* feat: Displays the server region in the stats panels.

* feat: Displays the server count in the local stats panel.

* ref: Renames a variable.

* fix: Makes bridgeCount a number, clarifies docs.

* chore: Updates lib-jitsi-meet to 1ac6df97e3aa5ff880129a95754d491d89ea8c25.
2018-09-17 13:21:03 -05:00
Leonard Kim
7c88de20fe feat(deps): update atlaskit/tabs to 8.0.8
There are (at least) two changes that are breaking:
- defaultTab is gone
- The re-rendering logic looks to have been re-written so that
  passing in a new array of tabs causes a re-render, which can
  reset the currently selected tab.

The fixes involved removing defaultTab from each tab configuration,
as it is no longer respected anyway. Also, instead of letting Tabs
be uncontrolled and allowing it to set its own selected, which
would result in the first tab automatically being selected on
Tabs re-render, use Tabs a controlled prop to dicate which
tab is selected; this is accomplished by specifying a selected
prop.
2018-09-14 14:50:08 -05:00
Leonard Kim
7b71482b03 feat(deps): update atlaskit/dropdown-menu to 6.1.12
This is a pre-requisite to updating atlaskit/tabs to 8.0.8.
Without updating, clicking a dropdown menu within a tab
component within a modal, such as the language selector in
the settings dialog, will lock the browser.
2018-09-14 14:50:08 -05:00
bgrozev
2339f232a5 Merge pull request #3435 from jitsi/recording_analytics
feat(recording): Add analytics.
2018-09-14 14:45:33 -05:00
Leonard Kim
3bb3b4500d provide the exact classname match the tests are looking for 2018-09-14 14:40:41 -05:00
Leonard Kim
0fca0f392d feat(filmstrip): reactify the filmstrip toggle button 2018-09-14 14:40:41 -05:00
virtuacoplenny
c25d6eb9a8 [RN] Implement tile view
* feat(tile-view): initial implementation for mobile

- Create a tile view component for displaying thumbnails in a
  two-dimensional grid.
- Update the existing TileViewButton so it shows a label in the
  overflow menu.
- Modify conference so it can display TileView while hiding
  Filmstrip.
- Modify Thumbnail so its width/height can be set and to prevent
  pinning while in tile view mode.

* use style array for thumbnail styles

* change ternary to math.min for expressiveness

* use dimensiondetector

* pass explicit disableTint prop

* use makeAspectRatioAware instead of aspectRatio prop

* update docs

* fix docs again (fix laziest copy/paste job I've ever done)

* large-video: rename onPress prop to onClick

* change forEach to for...of

* use truthy check fallthrough logic instead of explicit if

* put tile view button second to last in menu

* move spacer to a constant

* the magical incantation to make flow shut up
2018-09-13 17:20:22 +02:00
virtuacoplenny
37ff77cd5b Merge pull request #3416 from zbettenbuk/calendar-invite
[RN] Add calendar invite
2018-09-12 10:03:27 -07:00
Дамян Минков
fd30481ac2 Disable buttons only when token features is enabled. Fixes #3355. (#3443)
* Disable buttons only when token features is enabled. Fixes #3355.

* squash: update disabled check.

* squash: update disabled and disabledByFeatures.
2018-09-11 15:33:45 -07:00
Bettenbuk Zoltan
2d87757aaa [RN] Add invite function to calendar 2018-09-11 23:27:11 +02:00
Bettenbuk Zoltan
126e2d6e14 Move DialogContainer to BaseApp to have dialogs on the welcome screen too 2018-09-11 23:27:11 +02:00
virtuacoplenny
32fbcb17b9 Merge pull request #3442 from virtuacoplenny/lenny/subtitles-blown-away
fix(subtitles): fix typo that was blowing away subtitles on update
2018-09-11 13:41:34 -07:00
hristoterezov
d3bf0b7862 feat(recording): Add analytics. 2018-09-11 13:35:38 -05:00
Leonard Kim
17f4b24a3f fix(notification): change title for kick notification 2018-09-11 13:10:04 -05:00
virtuacoplenny
e63cd8c81b feat(tile-view): exit tile view on pin (#3430)
* feat(tile-view): exit tile view on pin

* Try out this other ux impl
2018-09-11 13:09:07 -05:00
Leonard Kim
282e66b2dc fix(subtitles): fix typo that was blowing away subtitles on update 2018-09-11 11:08:15 -07:00
Leonard Kim
72922130a2 fix(calendar): allow text to wrap and grow tile
Long meeting titles and urls can force text outside of the
tile.
2018-09-11 10:30:24 -05:00
Leonard Kim
514175b1af chore(deps): update most atlaskit dependencies
Update the following to the latest:
avatar
button
checkbox
field-text
field-text-area
icon
inline-message
layer-manager
lozenge
modal-dialog (one version before breaking changes)
multi-select
spinner
theme
tooltip

The following were not updated:
- droplist was removed because usage could not be found
- flag was not updated due to regressions with stacking animations
- inline-dialog was not updated because it requires (likely simple)
  fixing of position props
2018-09-11 10:27:00 -05:00
damencho
ceb8d7b03d Commit from translate.jitsi.org by user damencho.: 562 of 562 strings translated (0 fuzzy). 2018-09-11 14:43:48 +00:00
paweldomas
22803f36e9 chore(package.json): bump react-native-callstats to 3.53.4 2018-09-07 20:16:06 -05:00
Дамян Минков
7674e90d4d Adds dial in default number and pin to the text for calendar/share. (#3421)
* Adds dial in default number and pin to the text for calendar/share.

* Handles fail to fetch numbers or conference id.
2018-09-07 17:48:58 -05:00
Lyubo Marinov
1d128e027a Coding style: utilize default values
Since they are a language feature, they make the source code more easily
comprehensible than `if (typeof XXX === 'undefined') { XXX = ...; }`.
2018-09-07 16:48:16 -05:00
paweldomas
ee9f304345 fix(RN): show the CC button only when transcribing is available 2018-09-07 16:48:16 -05:00
Leonard Kim
f148b50100 fix(calendar): join button goes to meeting 2018-09-06 15:21:14 -05:00
paweldomas
95785a9585 ref(Notification.native): remove unnecessary View
The styles.actionColumn does not exist. It looks the same without the
extra View.
2018-09-05 18:43:49 -05:00
paweldomas
26d906fa46 feat(RN): displays transcription subtitles 2018-09-05 18:43:49 -05:00
paweldomas
eac069c930 ref(Conference.native): move notifications container
Moves NotificationContainer to the toolbox and filmstrip container, so
that there's no need to manually calculate the positions.
2018-09-05 18:43:49 -05:00
paweldomas
5119f41af6 ref(NotificationsContainer.native): simplify
The outer container is not necessary if 'justifyContent: flex-end' is
used with the absolute fill on the main container.
2018-09-05 18:43:49 -05:00
paweldomas
e2771b53bb feat(transcriptions): add ClosedCaptionButton.native 2018-09-05 18:43:49 -05:00
paweldomas
008fb868a6 feat(transcriptions): add TranscribingLabel.native 2018-09-05 18:43:49 -05:00
paweldomas
6dea107bcd ref(conference.js): unify "user joined/left" handling on web and RN
Extracts methods which share the common logic. There are still some
leftovers on the web side left which are not used on RN. But this can be
a first step.
2018-09-05 18:43:49 -05:00
Bettenbuk Zoltan
d10d61fb7a [RN] Add Google Sign In to live streaming 2018-09-05 23:09:56 +02:00
Bettenbuk Zoltan
9fe2b834eb [Android] Implement Activity.onActivityResult 2018-09-05 23:09:56 +02:00
Saúl Ibarra Corretgé
a327a5d804 [RN] Drop the react-native-permissions dependency
It causes false positives when submitting the app to the Store. Use the new
permissions API in react-native-webrtc instead.
2018-09-05 14:56:00 -05:00
Saúl Ibarra Corretgé
288bb59f71 deps: update react-native-webrtc dependency
It includes a W3C-ish permissions API which we will leverage.
2018-09-05 14:56:00 -05:00
Saúl Ibarra Corretgé
f3d623e0ca android: move calendar permission handling to the SDK
Since this is a feature implemented in the SDK, it makes sense that all the
plumbing required to make it work it's in the SDK itself.
2018-09-05 14:56:00 -05:00
Saúl Ibarra Corretgé
388c906312 android: implement the PermissionAwareActivity interface
This makes the PermissionsAndroid builtin module work.

Introduce the JitsiMeetActivityInterface, which defines the interface that
activities using JitsiMeetView directly must implement in order to ensure full
functionality.
2018-09-05 14:56:00 -05:00
virtuacoplenny
2043845d52 Merge pull request #3419 from virtuacoplenny/lenny/queue-replace-track
Queue replaceLocalTrack
2018-09-05 10:20:12 -07:00
Lyubo Marinov
024671165a [RN] No VideoQualityLabel in Picture-in-Picture 2018-09-05 00:22:19 -05:00
Lyubo Marinov
aba0912abf [RN] No upcoming-meeting notification in Picture-in-Picture 2018-09-05 00:22:19 -05:00
Lyubo Marinov
e446acb045 Coding style: consistency, documentation comments, formatting 2018-09-05 00:22:19 -05:00
Leonard Kim
3927f29ba8 fix(tracks): enqueue track replacement
The process for doing a replaceLocalTrack is async. Is it
possible to trigger replaceLocalTrack multiple times before
each call is finished. This leads to situations where
replaceLocalTrack is called multiple times with oldTrack being
null and a new track. In this scenario, each new track will be
added, causing UI issues such as the local participant's
large video not displaying for remote participants.

The action replaceLocalTrack is used when unmuting audio or
video, when creating new tracks on device switch, and when
toggling screensharing. These actions can collide with each
other. One way to fix this would be to queue replaceLocalTrack.
2018-09-04 09:39:02 -07:00
Leonard Kim
dafcde5060 ref(video-layout): remove instance variable for gating show/hide
The instance variable is not accurate. By default isVisible is
set to false but nothing sets the video container to actually
not be visible. As such it is possible for the video element
itself to autoplay, thereby making video visible, while the
isVisible boolean is still false. The fix chosen is to remove
instance variable and always respect calls to show/hide so
that the video container can be set to hidden.
2018-09-04 09:39:02 -07:00
Leonard Kim
3b754fa219 fix(tracks): mute tracks before using when created on device list change 2018-09-04 08:51:02 -07:00
Geert Stappers
4283d8b342 doc: describe what BOSH is 2018-09-04 17:34:38 +02:00
yanas
31cc63b757 Add join button to calendar events. (#3408)
* Add joing button to the calendar events.

* Add space between calendar lines.

* Adjust recent list name.

* Fixes test failure.

* Restyle mobile recent list message.

* Add analytics events.

* Addressing PR review comments.
2018-08-31 18:03:35 -07:00
Lyubo Marinov
79bd5cce00 react-native-webrtc: "android: prevent crash when checking camera facing mode (#37)" 2018-08-31 16:27:51 -05:00
Saúl Ibarra Corretgé
4fd8172126 [Android] Add LeakCanary
LeakCanary is a memory leak detection library which will run only in Debug mode.
2018-08-31 16:27:51 -05:00
virtuacoplenny
fe7652ec90 feat(tile-view): persist setting in local storage (#3379)
* feat(tile-view): persist setting in local storage

* comment
2018-08-31 10:36:01 -05:00
virtuacoplenny
72776e3a23 chore(deps): update lib for duplicate device list event fix (#3413) 2018-08-30 19:35:11 -07:00
Saúl Ibarra Corretgé
8addf0f436 deps: update react-native-fast-image (#3411)
Fixes a memory leak in Android: https://github.com/DylanVann/react-native-fast-image/pull/214

We are using our fork which is 4.0.14 + the leak fix because the last version
(5.0.3 at the time of this writing) contains a bug that prevents us for using
it: https://github.com/DylanVann/react-native-fast-image/issues/208
2018-08-30 18:19:04 -05:00
damencho
73146e77cc Commit from translate.jitsi.org by user damencho.: 447 of 447 strings translated (0 fuzzy). 2018-08-30 19:14:54 +00:00
damencho
28115b963d Commit from translate.jitsi.org by user damencho.: 447 of 447 strings translated (0 fuzzy). 2018-08-30 19:11:12 +00:00
jitsi-pootle
15819f7974 New files added from translate.jitsi.org based on templates 2018-08-30 19:07:20 +00:00
Leonard Kim
deb58798ba fix(tile-view): stop using border on active-speaker, shadow only
The border changes the tile sizing, due to box-sizing, and that
messes with the video aspect ratio.
2018-08-29 12:41:42 -05:00
Leonard Kim
07ccb0a386 fix(tile-view): hide any horizontal overflow 2018-08-29 12:41:42 -05:00
Saúl Ibarra Corretgé
8d6e1b1872 deps: update lib-jitsi-meet
Required due to API changes for promises support.
2018-08-29 09:22:28 -05:00
Saúl Ibarra Corretgé
80dadd0218 [RN] Update react-native-webrtc for promises support 2018-08-29 09:22:28 -05:00
Saúl Ibarra Corretgé
955e0a3382 [RN] Simplify RTCPeerConnection.setRemoteDescription override 2018-08-29 09:22:28 -05:00
Saúl Ibarra Corretgé
1354731fc5 [RN] Update WebRTC polyfills 2018-08-29 09:22:28 -05:00
Saúl Ibarra Corretgé
3ca704d81d [RN] Update react-native-webrtc and remove no longer needed polyfills 2018-08-29 09:22:28 -05:00
Lyubo Marinov
3ad27961e5 [iOS] Fix Jitsi Meet v1.18.x "Missing Purpose String in Info.plist File" issues reported by App Store Connect
App Store Connect reported the following issues in (and rejected the binary
of) Jitsi Meet 1.18.x:

NSBluetoothPeripheralUsageDescription
NSAppleMusicUsageDescription
NSMotionUsageDescription
NSSpeechRecognitionUsageDescription

Starting spring 2019, all apps submitted to the App Store that access user
data will be required to include a purpose string for the following:

NSLocationAlwaysUsageDescription
NSLocationWhenInUseUsageDescription
2018-08-28 10:00:55 +02:00
yanas
86caf52d08 Welcome page calendar ui improvements (#3405)
* Welcome page calendar ui improvements

* Addressing PR review comments.
2018-08-27 17:56:17 -07:00
yanas
f2cb15ba44 [WiP] Calendar integration ui (#3395)
Calendar integration ui
2018-08-27 10:13:59 -05:00
Lyubo Marinov
d62974b433 [RN] Update react-native-calendar-events (continued)
PR https://github.com/wmcmahan/react-native-calendar-events/pull/186 has
been merged upstream.
2018-08-26 21:53:42 -05:00
Saúl Ibarra Corretgé
8ff33684f7 [RN] Update react-native-calendar-events
Fixes a crash on Android. Upstream PR: https://github.com/wmcmahan/react-native-calendar-events/pull/186
2018-08-26 20:30:30 -05:00
bgrozev
b8179102c5 Merge pull request #3396 from nikvaessen/pr_independend_subtitles_cherry
independently display subtitles based on participants choice
2018-08-24 12:38:11 -05:00
linkmauve
c23c798f7a Display the correct display name in the menu (#3388)
The current code was splitting it on a space, which made nicknames such as “Link Mauve” appear as “Link”, whereas it gets displayed correctly everywhere else in the UI.
2018-08-22 12:35:48 -07:00
Nik
3c27d2ee54 independently display subtitles based on participants choice 2018-08-22 19:49:58 +02:00
Дамян Минков
7267f386dc Implements calendar entries edit. (#3382)
* Implements calendar entries edit.

Share text generation between calendar-sync and the share-room feature.

* Fixing comments.

* Clone the event element we modify on update.
2018-08-17 12:34:41 -07:00
Saúl Ibarra Corretgé
dba7f2d429 [RN] Remove no longer needed polyfills (#3377)
- navigator.{platform,plugins} were needed by the no longer existing screenshare
  adapter
- document.implementation is already polyfilled by xmldom
2018-08-16 23:03:53 -05:00
Saúl Ibarra Corretgé
a896d8f076 [RN] Fix normalizing BOSH URLs (#3376)
If a relative BOSH URL is found (as docker-jitsi-meet does) construct a full URL
based on the location URL and context root.

Also remove some default options since we need the config file anyway, so I see
no point in doing the extra work.
2018-08-16 23:03:15 -05:00
Hristo Terezov
99d285519d chore(lib-jitsi-meet): Update version. (#3381) 2018-08-16 16:58:54 -07:00
hristoterezov
2704b2f822 fix(dropbox): Address code review comments. 2018-08-16 13:53:43 -05:00
hristoterezov
62544188bd feat(recording): Add analytics event and logging. 2018-08-16 13:53:43 -05:00
hristoterezov
df0e107ea6 feat(recording): Implement dropbox integration 2018-08-16 13:53:43 -05:00
Saúl Ibarra Corretgé
f10d42f8e4 Fix processing context root
Yours truly refactored routing in https://github.com/jitsi/jitsi-meet/pull/3222
and broke it. When a bare room is entered the pathname was not updated when
applying the default URL.
2018-08-16 12:02:14 +02:00
Дамян Минков
7eda31315f Google & Microsoft calendar API integration (#3340)
* Refactor calendar-sync feature to be loaded on web.

For the web part it just adds new property to enable/disable calendar web integration, disabled by default.

* Initial implementation of retrieving google calendar events.

* Initial implementation of retrieving microsoft calendar events.

* Fixes comments.

* Rework to use the promise part of microsoft-graph-client api.

* Moves dispatching some actions, fixing comments.

* Makes sure we do not initializeClient google-api client multiple times.

* Do not try to login when fetching calendar entries.

The case where there is a calendar type google selected, but not logged in, trying to login on loading welcome page will show a warning that it tried to open a popup, which was denied by browser.

* Updates profile display data on sign in.

* Propagate google-api state to calendar-sync only if we use google cal.

* Adds sign out action.

* Clears the event listener when the popup closes.

* Clears calendarIntegrationInstance on signOut.

* WIP: UI for calendar settings, refactor auth flows

* Clean up some unused constants, functions and exports.

* break circular dependency of function and constant

* Exports only isCalendarEnabled from functions.

* Checks isSignedIn when doing fetchCalendarEntries on web.

* address comments

List microsoftApiApplicationClientID in undocument config.

remove unused SET_CALENDAR_TYPE action

use helper for calendar enabled in bootstrap

reorder actions

reorder imports

change order of signin -> set type -> update profile

add logging for signout error

reword setting dialog desc to avoid redundancy

add jsdoc to microsoft button props

reorder calendar constants

move default state to reducer (not reused anywhere)

update comment about calendar-sync due to removal of getCalendarState

update comment for getCalendarIntegration

remove vague comment

alpha order reducer, return default state on reset

alpha order persistence registry

remove unnecessary getType from apis

update comments in microsoftCalendar

alpha order google-api exports, use api.get in loadGoogleAPI

set jsdoc for google signin props

alpha order googleapi methods

fix calendartab docs

* Moves fetching calendar from APP_WILL_MOUNT to SET_CONFIG.

The web part needs configuration in order to refresh tokens (Microsoft).

* Fixes storing token expire time and refreshing tokens in Microsoft impl.

* Address comments

updateProfile changed to getCurrentEmail

rename result to results

stop storing integration in redux, store if ready for use

use existing helpers to parse redirect url

* update jsdocs, get google app id from redux

* clear integration instead of actual sign out
2018-08-15 13:11:54 -07:00
virtuacoplenny
87c010a9bd fix(subtitles): adjust styling for tile view (#3365)
- Increase z-index so the subtitles display over tiles.
- Add a background to the subtitle text.
- In general make the subtitles narrower.
2018-08-14 17:44:21 -07:00
linkmauve
8d0d92a437 Log the amount of local tracks properly
This changes a log message from “initialized with %s local tracks 2” to “initialized with 2 local tracks”.
2018-08-14 10:53:47 +02:00
linkmauve
faada0abae Print a nicer log message on participant join/part
This makes the logs more readable.
2018-08-14 10:53:18 +02:00
Ritwik Heda
1d99abc4a4 removes need for eslint-disable-next-line react/jsx-wrap-multilines and eslint-diable-line no extra-parens 2018-08-12 17:06:35 -05:00
Lyubo Marinov
9aed4df6d2 react-native-webrtc: android: pass correct constraints map to VideoCaptureController 2018-08-11 18:03:05 -05:00
Saúl Ibarra Corretgé
d92b720704 [RN] Update calendar-events dependency
Includes a fix for not running expensive operations on the main thread.
2018-08-10 15:11:37 +02:00
bgrozev
25aaa74edc Merge pull request #3223 from ztl8702/local-recording
Feature: Local recording (Ready for review)
2018-08-08 19:35:11 -05:00
Boris Grozev
195462a1a8 Merge branch 'master' into pr/3223 2018-08-08 15:35:40 -05:00
bgrozev
9c03e95bf1 npm: Updates lib-jitsi-meet to 4a28a196160411d657518022de8bded7c02ad679. (#3357) 2018-08-08 14:42:32 -05:00
virtuacoplenny
c353e9377f feat(tile-view): initial implementation for tile view (#3317)
* feat(tile-view): initial implementation for tile view

- Modify the classname on the app root so layout can adjust
  depending on the desired layout mode--vertical filmstrip,
  horizontal filmstrip, and tile view.
- Create a button for toggling tile view.
- Add a StateListenerRegistry to automatically update the
  selected participant and max receiver frame height on tile
  view toggle.
- Rezise thumbnails when switching in and out of tile view.
- Move the local video when switching in and out of tile view.
- Update reactified pieces of thumbnails when switching in and
  out of tile view.
- Cap the max receiver video quality in tile view based on tile
  size.
- Use CSS to hide UI components that should not display in tile
  view.
- Signal follow me changes.

* change local video id for tests

* change approach: leverage more css

* squash: fix some formatting

* squash: prevent pinning, hide pin border in tile view

* squash: change logic for maxReceiverQuality due to sidestepping resizing logic

* squash: fix typo, columns configurable, remove unused constants

* squash: resize with js again

* squash: use yana's math for calculating tile size
2018-08-08 13:48:23 -05:00
Radium Zheng
913c56c408 fix comments and docs 2018-08-08 11:58:38 +10:00
bgrozev
2f1223f721 fix: Handles the case of e2eRtt being undefined. (#3354) 2018-08-07 18:39:10 -07:00
Radium Zheng
4f1aaf89bf update package-lock.json 2018-08-08 09:26:49 +10:00
Radium Zheng
df6df1c6c3 refactor: AbstractAudioContextAdapter
move duplicate code from WavAdapter and FlacAdapter to a base class
2018-08-08 09:19:53 +10:00
Radium Zheng
1e804e552e fix: FlacAdapter get sampleRate 2018-08-08 09:19:53 +10:00
Radium Zheng
b284f25fde Refactor how download works. Cleaner filenames. 2018-08-08 09:19:53 +10:00
Radium Zheng
49bdd53bee Fix issue on mobile platforms 2018-08-08 09:19:53 +10:00
Radium Zheng
0827e02de9 use official repo for libflac.js 2018-08-08 09:19:53 +10:00
Radium Zheng
0410af9e5e add guard before APP in middleware.js 2018-08-08 09:19:28 +10:00
Radium Zheng
5a051024e6 clean up WavAdapter 2018-08-08 09:19:28 +10:00
Radium Zheng
e2def5f88b simplify Promise chaining in FlacAdapter 2018-08-08 09:19:28 +10:00
Radium Zheng
1078fa9d05 remove 'localRecording' from interface_config.js 2018-08-08 09:19:28 +10:00
Radium Zheng
dda7568a48 UI: refine LocalRecordingInfoDialog 2018-08-08 09:19:28 +10:00
Radium Zheng
4550848eac fix comments in flac-related codebase 2018-08-08 09:19:28 +10:00
Radium Zheng
7822831b1e UI: add a "Local Recording" label 2018-08-08 09:19:28 +10:00
Radium Zheng
e03126e422 fix sampleRate issues in flac and wav 2018-08-08 09:19:28 +10:00
Radium Zheng
61652c69b3 SessionManager 2018-08-08 09:19:28 +10:00
Radium Zheng
b6e1a49d33 Switching microphone on the fly: flac and wav support 2018-08-08 09:19:28 +10:00
Radium Zheng
e0ac3efb5c comment out section in config.js 2018-08-08 09:19:28 +10:00
Radium Zheng
65c76dcde5 Muting support
fix Promise in setMuted
2018-08-08 09:19:28 +10:00
Radium Zheng
5daa91ec1b update libflac.js to 4 and use proper fork 2018-08-08 09:19:28 +10:00
Radium Zheng
473ba28171 feature flag 2018-08-08 09:18:16 +10:00
Radium Zheng
52b55d65a0 change LocalRecordingInfoDialog 2018-08-08 09:18:16 +10:00
Radium Zheng
8ebf2b7e47 analytics: keyboard shortcut 2018-08-08 09:18:16 +10:00
Radium Zheng
cc38fcc5d0 register shortcuts in the middleware 2018-08-08 09:18:16 +10:00
Radium Zheng
a277421ecb WIP: Convert inline dialog to modal dialog 2018-08-08 09:18:16 +10:00
Radium Zheng
2f2e69a6f5 Add keyboard shortcuts for LocalRecordingInfoDialog
Which key should we use? Using "L" for now.
2018-08-08 09:18:16 +10:00
Radium Zheng
0490a3cf73 Refactor RecordingController 2018-08-08 09:18:16 +10:00
Radium Zheng
bfc8ecfaa6 changed one comment line 2018-08-08 09:18:16 +10:00
Radium Zheng
42c827434c clean up in LocalRecordingInfoDialog 2018-08-08 09:18:16 +10:00
Radium Zheng
0f3b67e53e reducer should be a pure function 2018-08-08 09:18:16 +10:00
Radium Zheng
2dfb107c57 UI strings: durationNA and moderater's finish message 2018-08-08 09:18:16 +10:00
Radium Zheng
f8c01646c7 Temp fix: newly joined clients miss the commands
When newly joined clients register for XMPP events upon
CONFERENCE_JOINED, those events that is carried by presence (e.g. START_COMMAND) was
already fired.
Temporary solution is to let the client send a ping message after
registering XMPP event listeners. The moderator will respond with
pong, which forces the presence to be resent.
2018-08-08 09:18:16 +10:00
Radium Zheng
0f0f9ea1b2 bug fix: multiple StartCommands
Situation when the RecordingController receives a new START_COMMAND
while it is initializing the recording adapter for the previous
START_COMMAND.
2018-08-08 09:18:16 +10:00
Radium Zheng
ce308eaa8b refactor: remove ensureInitialized 2018-08-08 09:18:16 +10:00
Radium Zheng
337cea6488 don't use params to switch actionType 2018-08-08 09:18:16 +10:00
Radium Zheng
e125861b29 refactor: use createLocalTracks instead of gUM; fix some docs; 2018-08-08 09:18:16 +10:00
Radium Zheng
3241c7a929 guard LocalRecordingButton with _shouldShowButton 2018-08-08 09:18:16 +10:00
Radium Zheng
55a2ef30a0 a11y label 2018-08-08 09:18:16 +10:00
Radium Zheng
ae0bd9e64e remove excessive comments in flacEncodeWorker.js 2018-08-08 09:18:16 +10:00
Radium Zheng
9c769a650e fix a missing doc string in Toolbox.js; reorder props alphabetically 2018-08-08 09:18:16 +10:00
Radium Zheng
07bc70c2f5 Implement local recording
index.js of local recording

local-recording(ui): recording button

local-recording(encoding): flac support with libflac.js

Fixes in RecordingController; integration with UI

local-recording(controller): coordinate recording on different clients

local-recording(controller): allow recording on remote participants

local-recording(controller): global singleton

local-recording(controller): use middleware to init LocalRecording

cleanup and documentation in RecordingController

local-recording(refactor): "Delegate" -> "Adapter"

code style

stop eslint and flow from complaining

temp save: client status

fix linter issues

fix some docs; remove global LocalRecording instance

use node.js packaging for libflac.js; remove vendor/ folder

code style: flacEncodeWorker.js

use moment.js to do time diff

remove the use of console.log

code style: flac related files

remove excessive empty lines; and more docs

remove the use of clockTick for UI updates

initalize flacEncodeWorker properly, to avoid premature audio data transmission

move the realization of recordingController events
from LocalRecordingButton to middleware

i18n strings

minor markup changes in LocalRecordingInfoDialog

fix documentation
2018-08-08 09:18:16 +10:00
bgrozev
2ee1bf9351 feat: Displays the E2E RTT in the connection stats table. (#3344)
* feat: Displays the E2E RTT in the connection stats table.

* fix: Whitelists the ping config properties.

* ref: Addresses feedback.

* npm: Updates lib-jitsi-meet to e097a1189ed99838605d90b959e129155bc0e50a.

* ref: Moves the e2ertt and region to the existing stats object.
2018-08-07 11:31:51 -07:00
Nik
7e1d97665a fix: only access nested json values when corrent payload type (#3352) 2018-08-07 09:03:31 -07:00
Zoltan Bettenbuk
b978851a0f [RN] Fix streaming on mobile (#3351) 2018-08-06 17:30:32 -07:00
Nik
ef49817eaf fix: add a timer which automatically clears subtitles (#3349) 2018-08-06 14:30:50 -07:00
virtuacoplenny
cac8888b37 feat(welcome-page): be able to open settings dialog (#3327)
* feat(welcome-page): be able to open settings dialog

- Create a getter for getting a settings tab's props so the device
  selection tab can get updated available devices.
- Be able to call a function from a tab after it has mounted. This is
  used for device selection to essentially call enumerateDevices on
  the welcome page so the device selectors are populated.
- Remove event UIEvents.AUDIO_OUTPUT_DEVICE_CHANGED. Instead directly call
  setAudioOutputDeviceId where possible.
- Fix initialization of the audioOutputDeviceId in settings by defaulting
  the audio output device to the one set in settings.

* squash: updateAvailableDevices -> getAvailableDevices, add comment for propsUpdateFunction
2018-08-06 10:24:59 -05:00
Praveen Gupta
81853d971a [WEB] Show final translated speech to text results as subtitles (#3276)
* Shows final translated speech to text results as subtitles

* Use conference from redux state and removes addTranscriptMessage
2018-08-06 11:24:37 +02:00
Lyubo Marinov
b9c5ed3b03 Fixes typo, comment 2018-08-05 17:18:14 -05:00
Lyubo Marinov
0892e0b644 Remove duplication 2018-08-05 17:04:19 -05:00
Bettenbuk Zoltan
b41bf22be7 Replace console with logger 2018-08-05 17:04:19 -05:00
Saúl Ibarra Corretgé
a1cc9bce91 [RN] Drop no longer needed polyfills
They were required only on Android because of its old JSC version. With the JSC
version bump they are no longer required.
2018-08-05 17:04:19 -05:00
Saúl Ibarra Corretgé
8d3cecad86 [Android] Update JSC version
The JSC version used by React Native is about 3 years old, and doesn't implement
things like Symbol or Typed Arrays, which require polyfills. These polyfills are
sometimes a los less performant, as is the case for Typed Arrays.

Bumping an updated JSC version makes both platforms consistent when it comes to
the JavaScript platform.
2018-08-05 17:04:16 -05:00
hristoterezov
bd8559fad6 fix(invite): IFrame api when invalid invitees are passed. 2018-08-03 12:42:38 -05:00
hristoterezov
fb75180632 ref(RecentList): Improvements after review. 2018-08-03 11:25:03 -05:00
Ritwik Heda
046b06e436 added recent list 2018-08-03 11:25:03 -05:00
Дамян Минков
af7c69a1aa Moves google-api in its own feature. (#3339)
* Moves google-api in its own feature.

* Stores the profile email in redux.
2018-08-02 14:56:36 -07:00
Saúl Ibarra Corretgé
7ad0639f7a [RN] Fix setting audio mode for audio-only calls
When a call is tarted in audio only mode due to the switch on the welcome page,
the wrong audio mode was chosen.
2018-08-01 22:12:08 +02:00
paweldomas
54a1853e60 fix(ios/Podfile.lock): bump SDWebImage/Core version 2018-07-31 14:07:17 -05:00
Saúl Ibarra Corretgé
27021ea271 [RN] Replace cached image implementation
Use react-native-fastimage, which uses 2 full-native image impleentations using
well known and mature (native) libraries.

This gets us rid of 2 libraries which were observerd as a source of bugs and
created trouble with dependencies: react-native-fetch-blob and
react-native-img-cache. They are also no longer well maintained.
2018-07-31 14:07:17 -05:00
Saúl Ibarra Corretgé
f5a667ad9e feat(Avatar): simplified code 2018-07-31 14:07:17 -05:00
paweldomas
2b9ce40533 feat(travis): bump image version 2018-07-31 12:54:01 -05:00
paweldomas
d3dd833f21 fix(ios/travis-ci) try pod update
With the fastimage lib Travis complains about:

CocoaPods could not find compatible versions for pod "SDWebImage/Core"
2018-07-31 12:54:01 -05:00
Neil Brown
1cc372868b Update quick-install.md
Tweaks for clarity.
2018-07-31 11:35:18 +02:00
bgrozev
a6956c7c34 Commit from translate.jitsi.org by user bgrozev.: 447 of 447 strings translated (0 fuzzy). 2018-07-30 14:27:03 -05:00
Leonard Kim
aaaa3e05d1 ref(thumbnail): pass in position of remote menu popover 2018-07-30 11:48:52 -05:00
Saúl Ibarra Corretgé
467a5aaae3 ios: run audio mode operations on a dedicated thread
There is no reason for them to run on the main thread, it's safe to call
AVFoundation functions on threads other than the main thread.

The previous code made an incorrect claim about the thread in which the audio
route change notification selector is called: it's called on a secondary thread:
https://developer.apple.com/documentation/avfoundation/avaudiosessionroutechangenotification
2018-07-27 15:39:39 -05:00
Saúl Ibarra Corretgé
243dd16285 android: run all audio and bluetooth operation on a dedicated thread 2018-07-27 15:39:39 -05:00
Saúl Ibarra Corretgé
92001f4d37 android: run WiFi stats operations on a dedicated thread 2018-07-27 15:39:39 -05:00
paweldomas
6a31c59081 ref(media/VideoTrack.native): remove fade animation 2018-07-27 12:08:54 +02:00
paweldomas
11c5b220a1 fix(participants/Avatar.native): disable fade animation
The Image adds a fade effect without asking, so lets explicitly disable
it. More info here:
https://github.com/facebook/react-native/issues/10194
2018-07-27 12:08:54 +02:00
virtuacoplenny
590ad90cd1 ref(video-layout): resize thumbnails first when resizing video area (#3308) 2018-07-26 11:45:04 -07:00
Nik
ca62e902bc Merge pull request #3312 from nikvaessen/master
comment out transcribingEnabled property; in code defaults to false
2018-07-26 19:51:38 +02:00
virtuacoplenny
34d1eb6768 ref(filmstrip): create an empty container for local filmstrip move (#3303)
* ref(filmstrip): create an empty container for local filmstrip move

This might be necessary for tile view. To support making the
local video display at the end of remote videos while in tile
view, but separateed from scrollable remote videos, moving
the local video might be necessary. By creating an empty
container, there is a target for local video to move to.

* squash: rename id
2018-07-26 12:51:15 -05:00
Nik Vaessen
b6b21e5410 comment out transcribingEnabled property; in code defaults to false 2018-07-26 19:10:40 +02:00
Nik
b8daf0a9f9 [WEB] add UI for transcription (#3213)
* [WEB] add UI for transcription

* add analytics event for button, do not use global APP object

* use props instead of state, use local conference to kick participant

* put imports in alphabetical order

* add translation for TranscribingLabel

* fix merge conflict

* add closed caption button

* purge OverFlowMenuItem which starts and stops Transcription

* readd closed caption icon and fix small issues due to purge

* delete unused icon in _font.scss
2018-07-26 09:33:40 -07:00
virtuacoplenny
39f1958300 ref(filmstrip): apply filmstrip class to Conference root (#3294)
* ref(filmstrip): apply filmstrip class to Conference root

Instead of apply the layout class to the body, it can be
applied to Conference. This will allow easier switching
between tile filmstrip and horizontal/vertical filmstrip.

* squash: fix typo filstrip
2018-07-25 13:00:00 -07:00
Leonard Kim
0b1224495b ref(video-quality): update video quality post redux update
Move away from middleware and instead update video quality
when the selected video quality updates in redux. This also
lead to removing of automatically exiting audio only because
with the change it's not so readily possible to tell if the
user switched off audio only by re-selecting the already
preferred video quality. Removing this automagic removed
some additional checking done for mobile.
2018-07-25 12:17:13 -07:00
Leonard Kim
ee7d180cbb feat(video-quality): be able to set an internal max
The internal max will be used for tile view. Whatever the
user has set for preferred video quality, the internal
maximum will be respected. This allows for the case where
the user prefers high definition video, but in tile view
it only makes sense to send low definition; ux wise the
user is allowed to continue messing with the video quality
slider.
2018-07-25 12:17:13 -07:00
Leonard Kim
4d3383c620 ref(video-quality): rename receiveVideoQuality to preferredReceiverVideoQuality
- "preferred" is being appended because in tile view there is a
  concept of what the user prefers to be the maximum video quality
  but there is also a maximum respected internall. For example,
  the user may prefer HD, but in tile view the tiles may be small
  so internall the preferred would be set to LD.
- "receive" is being renamed to "receiver" to be consistent with
  the naming in lib-jitsi-meet.
2018-07-25 12:17:13 -07:00
Pablo Saavedra
fd78203ff8 noticeMessage is not shown (refs #3295)
* Get back the Notice class
* Add Notice component in the Conference web view
* Notice is not exported in index.js. Only used internally by
  Conference.
* noticeMessage value obtained from features/base/config
  * using mapStateToProps
  * value is stored in the internal _message property
* Notice component, orignal in `toolbox` is moved from
  `toolbox/components` to `conference/components`
* Notice component only implemented and renderable in web views
* Dummy `conference/components/Notice.naive.js`

This patch is partially based in the removed logic included
originally in:

    commit 59a74153dc
    (tag: jitsi-meet_1886, tag: jitsi-meet_1885, tag: 1797, tag: 1796)
    Author: Ilya Daynatovich <shupuercha@gmail.com>
    Date:   Mon Mar 20 11:04:54 2017 -0500

      Toolbar notice as React Component

In reply to: Saúl Ibarra Corretgé @saghul> comments

Signed-off-by: Pablo Saavedra <psaavedra@igalia.com>
2018-07-25 14:16:47 -05:00
virtuacoplenny
a36b341865 ref(popover): allow for popover content from the right (#3302)
* ref(popover): allow for popover content from the right

Popovers contents can display to the left of the trigger
and above the trigger. Add the ability to display to the
right of the trigger my adding mouseover padding. This
may be needed for tile view, depending on where the triggers
are located.

* squash: abstract common css proprties into placeholder class
2018-07-25 13:28:36 -05:00
Saúl Ibarra Corretgé
3d6e18394e deps: update url-polyfill dependency
The previous location no longer exists. This is a fork of the original package,
which is actively maintained.

Fixes: #3304
2018-07-25 11:27:54 -05:00
virtuacoplenny
9a6e5c67f5 feat(tile-view): add new toolbar icon (#3292) 2018-07-25 08:22:18 -07:00
virtuacoplenny
50ea847905 Refactor welcome page in prep for branding (#3230)
* fix(welcome-page): css tweaks in prep for branded welcome page

- Watermarks should no longer depend on toolbar size. The left watermark made
  room for the toolbar when the toolbar was on the left side of the screen, but
  the toolbar has been moved to the bottom. The right watermark...well it'll
  clash with the vertical filmstrip but at least the margins will be consistent
  with the left watermark.
- Apply new font-family so fonts are more likely to be consistent across the
  app. Design likes SF UI and keeps requesting it so use it by default.
- Change sizings of welcome page header to be more responsive. This will help
  the header be scrollable when there is no additional content and the header
  overflows.
- Change colors of the welcome page header and remove background image that
  was in the header. Leave in the dom for the background image in case other
  deployments need to continue showing an image.
- Add a period to the title of the welcome page.
- Move watermarks dom location as it is not part of the header; it's part of the
  whole page.

* [squash] Size and font adjustments. Renaming.
2018-07-24 14:26:17 -05:00
virtuacoplenny
b54a9e2bf7 chore(deps): update lib for selecting participants and maxFrameHeight caching (#3291) 2018-07-23 14:20:30 -07:00
virtuacoplenny
918fb1dfc6 ref(utils): use web reportError helper (#3283) 2018-07-21 08:16:32 -07:00
Дамян Минков
9f015df61d Commit from translate.jitsi.org by user damencho.: 446 of 447 strings translated (0 fuzzy). (#3257) 2018-07-20 15:07:29 -05:00
Leonard Kim
2cd1b7f80b fix(presence-label): set position for small video presence label only 2018-07-20 13:27:28 -05:00
virtuacoplenny
afd2aea79c ref(large-video): combine selectParticipant logic from web (#3266)
* ref(large-video): combine selectParticipant logic from web

Currently native/middleware/redux has its own logic for selecting a participant
on the bridge. To have the logic web respect that logic, a few changes are
needed.
- Web no longer has its own call to selectParticipant.
- To keep in line with web logic selectParticipant action should act even when
  there is no track. This makes it so that when a participant does get a track
  that the bridge will send high quality. The bridge can already handle when the
  selected participant does not have a video track.
- The timing of web is such that on joining an existing conference, a
  participant joins and the participant's tracks get updated and then the
  conference is joined. The result is selectParticipant does not get fired
  because it no-ops when there is no conference. To avoid having to make
  uncertain changes (to be lazy), update the selected participant on conference
  join as well.

* squash: update comment, pass message to error handler
2018-07-20 13:19:26 -05:00
virtuacoplenny
c62f761d67 fix(dial-in): allow scroll on dial in info page (#3271)
* fix(dial-in): allow scroll on dial in info page

* squash: some more tweaks for flexible sizing
2018-07-20 10:32:28 -05:00
Boris Grozev
acda279111 npm: Updates lib-jitsi-meet. 2018-07-19 17:10:28 -05:00
Daniel Ornelas
ccf9e2a362 deps: update react-native-webrtc
This version uses a worker queue for all WebRTC operations in iOS.
2018-07-19 18:35:56 +02:00
Saúl Ibarra Corretgé
3154c6f936 [RN] Don't request camera permission on first launch
It will only be requested if a user joins a meeting or flips the switch from
video to audio and back, but never as the first thing when the welcome page is
mounted.
2018-07-19 09:03:22 -05:00
Lyubo Marinov
8ff3ae0ab2 [Android] Introduce IncomingCallView (continued) 2018-07-18 22:47:18 -05:00
Saúl Ibarra Corretgé
ea22d12581 [Android] Introduce IncomingCallView
It's a separate view (on the native side) and app (on the JavaScript side) so
applications can use it independently.

Co-authored-by: Shuai Li <sli@atlassian.com>
Co-authored-by: Pawel Domas <pawel.domas@jitsi.org>
2018-07-18 22:47:18 -05:00
Saúl Ibarra Corretgé
39e236a42c feat(external_api): export sendEvent function
Small reorganization so other features can send events to the native side.
2018-07-18 22:47:18 -05:00
paweldomas
01c2786c95 ref(base/util): move getSymbolDescription to util 2018-07-18 22:47:18 -05:00
Saúl Ibarra Corretgé
9972e88b67 [Android] Split base functionality out of JitsiMeetView
As the need for adding more views connected with our React code arises, having
everything in JitsiMeetView is not going to scale.

In order to pave the way for multiple apps / views feeding off the React side,
the following changes have been made:

- All base functionality related to creating a ReactRootView and layout are now
  in BaseReactView
- All Activity lifecycle methods that need to be called by any activity holding
  a BaseReactView are now conveniently placed in ReactActivityLifecycleAdapter
- ExternalAPIModule has been refactored to cater for multiple views: events are
  delivered to views, and its their resposibility to deal with them
- Following on the previous point, ListenerUtils is a utility class for helping
  with the translation from events into listener methods
2018-07-18 22:47:18 -05:00
Дамян Минков
cd1c384cc8 Enables live-streaming for guests. (#3274) 2018-07-18 18:11:54 -07:00
Leonard Kim
f97f294d1a feat(live-streaming): add beta tag to mobile 2018-07-18 10:42:14 +02:00
Nik
d3dd54ac3b Show subtitles when Jigasi sends transcription results in JSON (#1914)
* Show subtitles when Jigasi sends transcription results in JSON

* fix: Import PropTypes from prop-types.

* apply feedback on initial PR

* Changed Object to Map, alphabetic ordering fixes ,css changes in transcription subtitles

* Sends Map of transcriptMessages as prop to Component

* Documentation fixes and uses config in redux state

* Minor doc fix

* rename feature 'transcription' to 'subtitles'

* Moves subtitles config to interfaceConfig and minor fixes

* minor lint fix
2018-07-17 12:31:12 -05:00
Saúl Ibarra Corretgé
13ee67d15c config: default to 720p (#3269) 2018-07-17 08:18:32 -07:00
Saúl Ibarra Corretgé
b25caedce7 feat(eslint): fix 2 eslint warnings (#3268) 2018-07-17 08:08:22 -07:00
Leonard Kim
5d4a2e87f8 fix(device-selection): use persisted settings as default values if available 2018-07-16 20:38:04 -07:00
Leonard Kim
44baca3185 fix(device-selection): pass dispatch so preferred speaker is saved 2018-07-16 20:38:04 -07:00
virtuacoplenny
b9f28a1beb fix(live-streaming): add beta tag to toolbar button (#3263) 2018-07-16 19:15:34 -07:00
Aaron van Meerten
6b7a883331 Merge pull request #3265 from jmacelroy/table-async-wrapper
Creating a new async prosody http wrapper.
2018-07-16 17:01:48 -05:00
jmacelroy
944cf4272d Creating a new async prosody http wrapper. 2018-07-16 21:58:48 +00:00
Aaron van Meerten
cc27e96b22 Merge pull request #3264 from jmacelroy/missed-calls
feat(calls): Adding missed call event triggering.
2018-07-16 12:52:58 -05:00
virtuacoplenny
4e4755f91e Remove state from mediaDeviceHelper (#3226)
* ref(device-selection): do not override var that is not reference again

* ref(device-selection): do not override var that is not reference again

* ref(device-selection): always update known devices on device list update

* ref(device-selection): replace call to get devices from legacy to redux

* ref(device-selection): remove unused device list state from mediaDeviceHelper

* ref(device-selection): update store before updating UI
2018-07-13 10:31:28 -07:00
virtuacoplenny
0dcf8ef2f6 fix(device-selection): add hover color for device output test (#3254) 2018-07-13 10:08:35 -07:00
Saúl Ibarra Corretgé
1ee71be961 [RN] Kill some dead code 2018-07-13 10:01:39 -05:00
Lyubo Marinov
bfdfb5321c feat(App): refactor App and split it into BaseApp and App (continued)
There doesn't seem to be a strong need for the initialized React
Component state in BaseApp so remove/delete it.
2018-07-12 11:28:48 -05:00
Saúl Ibarra Corretgé
dc246960df feat(App): refactor App and split it into BaseApp and App
BaseApp does all the heavy-lifting related to creating the redux store,
navigation, and so on.

App currently handles URL props and actually triggering navigation based on
them.
2018-07-12 11:28:19 -05:00
Saúl Ibarra Corretgé
3bfab7718f [RN] Refactor getting the default URL
Move it away from AbstractApp into an auxiliary function. In addition, introduce
a new `getServerURL` function which gets the configured server URL and defaults
to meet.jit.si as before.
2018-07-12 11:28:18 -05:00
Saúl Ibarra Corretgé
980648df4d feat(App): remove ability to specify an external redux store
It was never used in practice, and it would be very cumbersome to use, since it
would have to bcreated with all the middlewares and reducers we need. After
discussing this with Lyubomir, we are confident this is not going to be needed
so it can go.
2018-07-12 11:28:18 -05:00
Saúl Ibarra Corretgé
f2f991e969 feat(App): move participant leaving logic to base/participants 2018-07-12 11:28:17 -05:00
Bettenbuk Zoltan
96a837801e [RN] Tint active speaker thumbnail 2018-07-12 09:43:29 +02:00
Lyubo Marinov
9c4da125c8 lib-jitsi-meet "core: refactor initialization not to return a Promise (continued)" 2018-07-12 00:05:40 -05:00
Lyubo Marinov
c203215c54 core: refactor routing (continued) 2018-07-11 22:58:41 -05:00
Saúl Ibarra Corretgé
155e02bbfb core: refactor routing
Unfortunately, as the Jitsi Meet development evolved the routing mechanism
became more complex and thre logic ended up spread across multiple parts of the
codebase, which made it hard to follow and extend.

This change aims to fix that by rewriting the routing logic and centralizing it
in (pretty much) a single place, with no implicit inter-dependencies.

In order to arrive there, however, some extra changes were needed, which were
not caught early enough and are thus part of this change:

- JitsiMeetJS initialization is now synchronous: there is nothing async about
  it, and the only async requirement (Temasys support) was lifted. See [0].
- WebRTC support can be detected early: building on top of the above, WebRTC
  support can now be detected immediately, so take advantage of this to simplify
  how we handle unsupported browsers. See [0].

The new router takes decissions based on the Redux state at the time of
invocation. A route can be represented by either a component or a URl reference,
with the latter taking precedence. On mobile, obviously, there is no concept of
URL reference so routing is based solely on components.

[0]: https://github.com/jitsi/lib-jitsi-meet/pull/779
2018-07-11 22:58:41 -05:00
jmacelroy
d189888902 feat(calls): Adding missed call event triggering. 2018-07-11 21:09:53 +00:00
Bettenbuk Zoltan
5aee082bf9 [RN] Implement streaming on mobile 2018-07-11 15:13:16 -05:00
Saúl Ibarra Corretgé
453c4b99dc cleanup: drop polyfills which were required for IE11 2018-07-11 17:53:32 +02:00
Bettenbuk Zoltan
961e1d611f [RN] Only ask for calendar permission on user interaction 2018-07-11 17:17:24 +02:00
hristoterezov
bd449be20d fix(VideoLayout): JS error if updateLargeVideo is called too early. 2018-07-09 20:22:43 -05:00
hristoterezov
9331b0870b fix(presence-label):styles 2018-07-09 20:22:43 -05:00
hristoterezov
00d1edcdef fix(jwt): import for mobile. 2018-07-09 20:22:43 -05:00
hristoterezov
769e782c6f feat(callee-info): Redesign. 2018-07-09 20:22:43 -05:00
virtuacoplenny
485ff81443 fix(hangup): truthy check for deviceChangeListener before removing it (#3235)
It can be that deviceChangeListener is never defined because
the isDeviceList call never completes. On hangup, that would
cause an error to be thrown within lib-jitsi-meet because of
an attempt to remove an undefined event handler. That is
what happens on Safari right now.
2018-07-09 11:46:26 -07:00
damencho
d12afc5c07 Fixes the room size api which returns string result back to client. 2018-07-09 13:44:24 -05:00
akshitkrnagpal
20444adbc9 Added emailChange listener to API 2018-07-09 10:14:27 -05:00
Zoltan Bettenbuk
63c017f8e6 Fix persistency to handle default values too (#3228) 2018-07-06 11:03:16 -07:00
virtuacoplenny
afe7c4470d feat(small-video): add flag to hide connection indicators (#3225) 2018-07-06 08:24:38 -07:00
Дамян Минков
3f3a957f40 Removes unneeded translation. (#3217) 2018-07-03 13:34:43 -07:00
Emil Ivov
26c0164f1e Merge pull request #3211 from saghul/audio-route-copy
[RN] Update audio route selection copy
2018-07-03 19:01:06 +03:00
Bettenbuk Zoltan
b48c897d9b [WEB] Move RecordButton to the new ToolBox abstraction layer 2018-07-03 11:08:37 +01:00
Bettenbuk Zoltan
e59761baa2 Implement ToolboxItem features: disabled, tooltip with label 2018-07-03 11:08:37 +01:00
damencho
baa2c217de Syncs with latest lib-jitsi-meet (98acf13).
Detects msid changes and signals them, part of fixing start A/V muted using Firefox.
2018-07-02 22:41:00 -05:00
Leonard Kim
5dc2aca081 fix(video-layout): handle undefined video type for large video update
When replace track is called in JitsiConference, there is no
guarantee a videoType update will come in presence before
the track added event. This can lead to the situation in
LargeVideoManager where an update is called with a track
with an undefined videoType.
2018-07-02 21:54:16 -05:00
virtuacoplenny
84b589719f fix(connection): reload immediately on possible split-brain (#3162)
* fix(connection): reload immediately on possible split-brain

There isn't an explicit way to know when a split brain
scenario has happened. It is assumed it arises when an
"item-not-found" connection error is encountered early
on in the conference. So, store when a connection has
happened so it be calculated how much time has
elapsed and if the threshold has not been exceeded
then do an immediate reload of the app instead of
showing the overlay with a reload timer.

* squash: rename isItemNotFoundError -> isShardChangedError
2018-07-02 16:22:51 -05:00
jmacelroy
1c6d22b75e Adding state to poltergeist store for correlating external resources with calls. 2018-06-29 14:51:48 -05:00
Leonard Kim
2547ee3a04 ref(filmstrip): use explicit class for horizontal filmstrip
This will make it easier to support horizontal, vertical, and
tile layout filmstrip by reducing the css overriding needed
for tile layout.
2018-06-29 20:11:59 +01:00
Leonard Kim
7328dd9125 ref(filmstrip): add class to body for horizontal filmstrip 2018-06-29 20:11:59 +01:00
Leonard Kim
0aa2d81844 ref(filmstrip): move vertical filmstrip container styles to own file 2018-06-29 20:11:59 +01:00
Leonard Kim
e1f7d4585e ref(filmstrip): move some video container overrides 2018-06-29 20:11:59 +01:00
Leonard Kim
bdae4b9493 ref(filmstrip): remove ie11 css flex hack 2018-06-29 20:11:59 +01:00
Leonard Kim
8f688c3535 ref(filmstrip): move around small video and quality label styles 2018-06-29 20:11:59 +01:00
Leonard Kim
60ae8497c0 ref(filmstrip): move some small video specific styling to own file 2018-06-29 20:11:59 +01:00
Leonard Kim
7c1b7a588e ref(filmstrip): move filmstrip styles to filmstrip folder 2018-06-29 20:11:59 +01:00
Leonard Kim
fd05f120ff ref(filmstrip): move vertical filmstrip overrides to new filmstrip folder 2018-06-29 20:11:59 +01:00
Leonard Kim
d9fa05f42e ref(filmstrip): move toolbar css to own file 2018-06-29 20:11:59 +01:00
Leonard Kim
12901be6be ref(filmstrip): move presence label styles with similar styles 2018-06-29 20:11:59 +01:00
Zoltan Bettenbuk
009eeccf3c Merge pull request #3142 from virtuacoplenny/lenny/new-audio-output-icon
feat(device-selection): new icon for audio output
2018-06-29 14:50:46 +02:00
Zoltan Bettenbuk
e01acd9cf0 Merge pull request #3187 from virtuacoplenny/lenny/cleanup-new-toolbox-css
ref(toolbar): remove use-new-toolbox class
2018-06-29 14:48:15 +02:00
Bettenbuk Zoltan
ac63a0fa73 Calendar feature disabled state getter
This commit adds a state getter that considers checking the enabled/disabled state of the calendar feature, so then other features don’t have to do it manually.
2018-06-29 09:36:34 +01:00
Saúl Ibarra Corretgé
1d296f9704 [RN] Update audio route selection copy 2018-06-29 10:08:04 +02:00
Leonard Kim
98e3bcb691 feat(device-selection): new icon for audio output 2018-06-28 14:59:07 -07:00
Leonard Kim
880d3525db squash: pass class name into filmstrip 2018-06-28 11:06:10 -07:00
Leonard Kim
c958c64ba8 ref(toolbar): remove use-new-toolbox class
Very likely I broke something subtle and I'm prepared to fix it.
2018-06-28 11:06:10 -07:00
bgrozev
cc319ad5e9 chore: Updates the callstats lib. (#3207) 2018-06-28 17:40:33 +01:00
Saúl Ibarra Corretgé
75b2eb0f99 deps: update lib-jitsi-meet dependency 2018-06-28 13:55:12 +02:00
Saúl Ibarra Corretgé
acb3bd7ad7 feat(BrowserSupport): remove PluginRequiredBrowser
WebRTC plugin support has been axed, this is now dead code.
2018-06-28 13:55:12 +02:00
Saúl Ibarra Corretgé
7fcc95c9da feat(UnsupportedDesktopBrowser): recommend Edge, not IE 2018-06-28 13:55:12 +02:00
Saúl Ibarra Corretgé
87fa8de815 feat(sanity): axe IE and Temasys plugin support 🔥🔥🔥 2018-06-28 13:55:12 +02:00
Bettenbuk Zoltan
7164cd49e4 [RN] Implement Recording on mobile 2018-06-28 12:47:50 +02:00
Bettenbuk Zoltan
4ac367d403 [RN] Implement Labels on mobile 2018-06-28 12:47:50 +02:00
Bettenbuk Zoltan
ffd0827354 [RN] Implement Notifications on mobile 2018-06-28 12:47:50 +02:00
Sam Joseph
00f18e9369 doc: minor grammatical fix (#3202) 2018-06-28 11:07:47 +02:00
jmacelroy
401c43ee02 fix: Properly setting poltergeist ignore status. 2018-06-27 17:28:20 -05:00
Jacob MacElroy
6ae5adcb3d Creating a poltergiest library and using in for mod_muc_poltergeist. 2018-06-27 11:59:38 -05:00
Saúl Ibarra Corretgé
535e5b4f64 [iOS] Update Podfile.lock
Syntax changed slightly with an update to CocoaPods.
2018-06-26 17:59:02 +02:00
Saúl Ibarra Corretgé
a5d0bfe1d4 Merge pull request #3179 from jitsi/reload_screen_a_catch_all
[RN] Reload screen a catch all
2018-06-26 17:07:06 +02:00
paweldomas
67d7d4fc14 feat(RN): add a fatal error state which is a catch all
Adds a fatal error state on which will depend whether or not the reload
screen is to be displayed. It is to happen when a relevant fatal error
action is not claimed by any feature for error recovery (the recoverable
flag is not set).
2018-06-26 15:39:56 +02:00
Lyubo Marinov
342a00a6af lib-jitsi-meet M67 2018-06-26 01:36:50 -05:00
Lyubo Marinov
bf14e66142 react-native-webrtc M67 2018-06-26 00:08:58 -05:00
Saúl Ibarra Corretgé
c3f602b7b6 Revert "fix(android): do not require java 8 target"
This reverts commit 9e0fee6c7d.

WebRTC requires Java 8, and Java 7 is now considered unsupported:
https://groups.google.com/forum/?utm_medium=email&utm_source=footer#!msg/discuss-webrtc/V1h2uQMDCkA/RA-uzncVAAAJ
2018-06-25 22:58:26 -05:00
virtuacoplenny
9a06d2bf52 ref(video-layout): consolidate connection status update handling (#3185)
- Instead of having 4 listeners for local connection status
  updates and 1 for remote, remove two of the redundant listeners.
- Instead of calling into 4 separate VideoLayout methods to update a
  participant's connection status, expose one handler.
2018-06-25 10:44:12 -07:00
virtuacoplenny
2951fefef9 ref(toolbar): change tooltip prop name to stop deprecation warning (#3186) 2018-06-25 09:31:41 -07:00
damencho
361e5f0fad Adds identification of poltergeist's in presence. 2018-06-22 18:23:17 -05:00
damencho
682169e44c Renames isBot -> isFakeParticipant. 2018-06-22 18:23:17 -05:00
Leonard Kim
c65ccb0af5 fix(filmstrip): do not show video for large video speaker in audio only
When in audio only, the video should not be shown and instead the
avatar should display.
2018-06-22 17:03:25 -05:00
Leonard Kim
f035861617 ref(toolbar): allow OverflowMenuItem to show beta tag 2018-06-22 15:53:35 -05:00
Boris Grozev
045dad3354 doc: Adds a link to the quick install tutorial. 2018-06-22 14:52:17 -05:00
Zoltan Bettenbuk
b5e9c71865 Merge pull request #3160 from saghul/mobile-sounds-loop
Mobile looping sounds
2018-06-22 17:48:13 +02:00
Saúl Ibarra Corretgé
7b0a6a2ee5 [RN] Add ability to loop sounds 2018-06-22 14:34:01 +02:00
Saúl Ibarra Corretgé
333a0f5f90 [RN] Handle presence
Up until now, mobile was oblivious to participants' presence state. Presence
state handling is required (probably, amongst other things) for "call flows".
So, let's add it! This is done by gathering the presence state when a
participant first joins, and handling subsequent changes.
2018-06-22 12:22:12 +02:00
Saúl Ibarra Corretgé
47aa14e9f6 [iOS] Fix syncing muted state with CallKit
Fix the "mute ping pong" for once and for all. This patch takes a new approach
to the problem: it keeps track of the user generated CallKit transaction ations
and avoids calling the delegate method in those cases.

This results in a much cleaner and easier to understand handling of the flow: if
the delegate method is called it means the user tapped on the mute button. When
we sync the muted state in JS with CallKit the delegate method won't be called
at all, thus avoiding the ping-pong altogether.

In addition, make sure all CallKit methods run in the UI thread. CallKit will
call our delegate methods in the UI thread too, thsu there is no need to
synchronize access to the listener / pending action sets.
2018-06-22 11:25:09 +02:00
Saúl Ibarra Corretgé
ec8ad6190d [iOS] Only update the matching local track data in CallKit 2018-06-22 11:25:09 +02:00
Saúl Ibarra Corretgé
42b85f73bd [iOS] Fix checking if a track is local in ithe CallKit middleware
Not all TRACK_ actions include the `local` attribute, so use the underlying
`jitsiTrack` to check it.
2018-06-22 11:25:09 +02:00
virtuacoplenny
2bd0f77671 Move a couple calls to update VideoLayout into the redux update flow (#3173)
* ref(video-layout): move middleware for TRACK_ADDED

* ref(video-layout): call mucJoined when redux knowns of conference join
2018-06-21 21:33:33 -07:00
bbaldino
11c9d5f0ef change the levels for each gsm bar color (#3174)
old values: 0-39% -> red, 40-69% -> yellow, 70%+ -> green
new values: 0-9% -> red, 10-29% -> yellow, 30%+ -> green
2018-06-21 17:30:19 -07:00
virtuacoplenny
942d7d7e56 chore(deps): update lib for recording refactoring and no display name escaping (#3169) 2018-06-21 15:47:25 -07:00
Leonard Kim
e431acda18 ref(css): add height/width reset for html tag
This is needed for newer versions of electron that might
use the iframe integration of jitsi-meet. Newer versions
seem to have some kind of regression with setting height
and width to 100%.
2018-06-21 12:39:55 -05:00
virtuacoplenny
7ee63a44c5 ref(info): use conference existence as trigger for autoshowing dialog (#3083)
* ref(info): use conference existence as trigger for autoshowing dialog

* squash: combine maybeShow checks, inheritdoc

* squash: flow type tweaks
2018-06-21 12:38:53 -05:00
Saúl Ibarra Corretgé
e3bc333115 Merge pull request #3163 from manquer/patch-1
grammar and spelling fixes
2018-06-21 09:16:08 +02:00
Manquer
750d6cf534 grammer and spelling fixes 2018-06-21 11:36:16 +05:30
Leonard Kim
fecd138a3c fix(recording): red error text for google api errors 2018-06-20 23:09:43 -05:00
Hristo Terezov
1f8fa3b6d4 Refactor settings modal (#3121)
* feat(settings): setting dialog

- Move device selection, profile edit, language select, moderator
  options, and server auth into one modal with tabs.
- Remove side panel profile and settings and logic used to update
  them.
- Pipe server auth status into redux to display in the settings
  dialog.
- Change filmstrip only device selection popup to use the new
  stateless settings dialog component.

* squash: do not show profile tab if not guest

* squash: profile button not clickable if no profile to show

* squash: nits

* ref: Settings dialog.
2018-06-20 13:19:53 -07:00
Jacob MacElroy
0acc9187ed Preventing expired notification for poltergeist that have left.
The original presence stanza generation code for a poltergeist
has been re-factored and simplified a bit. Every time a
poltergeist presence is updated we first check that the poltergeist
still exists.
2018-06-20 14:37:58 -05:00
paweldomas
675eea7b99 fix(base/conference): do not execute leave conference on web
On web CONFERENCE_FAILED handlers are not setting the 'recoverable'
flag thus any middleware which rely on those should not execute on web.
2018-06-20 10:58:57 -05:00
Saúl Ibarra Corretgé
146ffb0918 Merge pull request #3153 from jitsi/connection_corner_cases
Connection corner cases
2018-06-20 16:53:48 +02:00
paweldomas
57b302da3e feat(base/conference): CONFERENCE_FAILED on CONNECTION_FAILED
Emits CONFERENCE_FAILED in response to CONNECTION_FAILED event
which then triggers JitsiConference.leave() through the middleware
processing. Also base/conference state will be adjusted. It is to have
a consistent redux state in which both connection and conference are
failed. It could happen that in a buggy environment the XMPP connection
is dropped, but the media is still flowing which would result in weird
user experience.
2018-06-20 15:52:46 +02:00
paweldomas
b2f76f3ed6 ref(mobile/external-api): skip event if conference exists
The change to mobile/external-api is required to not emit
CONFERENCE_FAILED for CONNECTION_FAILED if the conference has been
started, because base/conference state will still hold conference
instances which are to be ended by other means and result in the
appropriate event (which will adjust the base/conference state).
2018-06-20 15:52:46 +02:00
paweldomas
85fbaac9b2 chore(deps): update LJM to 1bd6ee4b1207e6fd57f5b7c15cc17e0a6087f26d
Updates lib-jitsi-meet to have the getConnection getter in
JitsiConference.
2018-06-20 15:52:45 +02:00
paweldomas
022954b40b fix(base/connection/reducer): clear 'connection' field
Currently the listeners for disconnected and failed connection events
are unsubscribed as soon as the connection is established, so
the CONNECTION_DISCONNECTED is never triggered which would clear the
'connection' field. This commit will clear the 'connection' state on
CONNECTION_WILL_CONNECT. It's needed anyway given that there's no
guarantee on when and if the async disconnect operation will finish.

One issue caused by the 'connection' not cleared was that
CONNECTION_FAILED was not reduced correctly and the reload screen was
not displayed for the following scenario:
1. Join and leave any working conference.
2. Turn off network connectivity on the device.
3. Wait for CONNECTION_FAILED. The reload screen will not be displayed,
   because CONNECTION_FAILED is not reduced correctly, because the old
   'connection' value is still there.
2018-06-20 15:52:45 +02:00
Saúl Ibarra Corretgé
7fa941cb8c [iOS] Fix setting call type in CallKit
Your truly introduced this regression in
8c7a3f16b1, alas.

The audio only mode is used to set the CallKit call type. This affects the
behavior on the recent calls entries (calls are marked either as audio or video
calls).

Sync both at the start and for transitions. The previous code was working by
chance (in a way): when the CallKit UI is presented the local video is muted,
which triggers a SET_VIDEO_MUTED action, at which point the audio-only mode was
checked for. Now we are more explicit and act on SET_AUDIO_MUTED.
2018-06-20 08:42:30 -05:00
Daniel Ornelas
ad259988b9 [RN] Fix for creating video track when conference is ending. 2018-06-20 11:24:07 +02:00
Saúl Ibarra Corretgé
8c7a3f16b1 [iOS] Refactor muted state handling in CallKit
Rely solely on actual track state, rather than the desired state, (what
base/media represents).
2018-06-19 15:53:43 +02:00
Saúl Ibarra Corretgé
84c1c3dfd3 [iOS] Fix starting a call muted when permission was not granted
Read the muted state from the track itself instead of from base/media. This
avoid expressing the incorrect desire when the call starts muted because
permission was never granted.
2018-06-19 15:53:43 +02:00
hristoterezov
fccd0d6b29 ref(deep-linking): Improve the window loaded detection logic. 2018-06-18 18:01:22 -05:00
hristoterezov
12dda7acb9 fix(deep-linking): GUM when the deep linking page have been displayed. 2018-06-18 18:01:22 -05:00
Saúl Ibarra Corretgé
28861c0054 [iOS] Fix incorrect call to setAudioMuted in CallKit
Audio muting does not have an authority.
2018-06-18 15:45:37 -05:00
Lyubo Marinov
0d3fac7c0f [RN] Change default WelcomeScreen tab and persist user choice (coding style) 2018-06-18 15:42:09 -05:00
Bettenbuk Zoltan
dcfebf746f [RN] Change default WelcomeScreen tab and persist user choice 2018-06-18 12:21:55 -05:00
virtuacoplenny
4ab8d98cd1 ref(large-video): permanently enable canvas based background (#3084)
* ref(large-video): permanently enable canvas based background

* squash: leave flag for disabling background
2018-06-15 16:41:37 -05:00
Дамян Минков
fc643f06e4 Creates issue templates 2018-06-15 15:37:01 -05:00
Leonard Kim
c89791069b fix(large-video): do not reselect video on self dominant speaker
In the current middleware logic, when the local participant becomes
dominant speaker, a new participant can be selected to receive
high quality video from. This means large-video could potentially
do a switch to another participant when the local participant
becomes dominant speaker. Prevent such behavior.
2018-06-15 15:31:23 -05:00
virtuacoplenny
e06ad6cea9 chore(deps): update lib to get bridge migration fix (#3141) 2018-06-15 11:22:01 -07:00
Дамян Минков
ac834326e7 Token based features (#3075)
* Adds an option to disable features based on token data.

Reverts changes from b84e910086, removes disableDesktopSharing option and an interface_config option.

* Disable recording button based on token features data.

Hide recording if local participant isGuest and roles based on token.
When enableUserRolesBasedOnToken is enabled we were not hiding the record button for guests.

* Adds filtering of jibri iqs and rayo based on features.

Moves feature checking in separate utility function.
Renames utility method.

* Adds a footer text when outbound-call is not feature enabled.

* Fixes comments.
2018-06-15 13:10:22 -05:00
Leonard Kim
0cf585860b fix(invite): allow arbitrary strings if no dialOutAuthUrl 2018-06-15 11:24:01 -05:00
virtuacoplenny
259066e2c6 chore(deps): update lib to e7b81910 for layer suspension (#3130) 2018-06-13 10:48:43 -07:00
virtuacoplenny
fa0dacf7c8 fix(keyboard-shortcuts): change copies for some descriptions (#2965) 2018-06-13 06:49:13 -07:00
Zoltan Bettenbuk
ad4e73cc0a Merge pull request #3136 from saghul/update-eslint
feat(eslint): use new eslint-config-jitsi
2018-06-13 12:48:03 +02:00
Saúl Ibarra Corretgé
89eacd6982 feat(eslint): use new eslint-config-jitsi
It contains all the rules we use, minus the react-native specific ones, which we
are keeping here.
2018-06-13 11:27:39 +02:00
Bettenbuk Zoltan
4a9fdb8a10 Update npm version and package-lock 2018-06-13 11:23:59 +02:00
Saúl Ibarra Corretgé
602c8610bf misc: ignore jshint files
They are automatically created by precommit-hook:
362a202498/bin/install (L5-L6)
2018-06-13 11:09:55 +02:00
Lyubo Marinov
07613aa856 [RN] Make the calendar list distinct (coding style) 2018-06-12 23:24:20 -05:00
Bettenbuk Zoltan
db6f2c8868 [RN] Make the calendar list distinct 2018-06-12 23:24:20 -05:00
Saúl Ibarra Corretgé
c7fc26d864 [iOS] Fix crash on startup when fetching calendar entries
Turns out sometimes a calendar is missing the tile and it crashes because nil is
inserted into a NSDictionary. Fix it by applying this pending PR:
https://github.com/wmcmahan/react-native-calendar-events/pull/164
2018-06-12 20:38:50 -05:00
hristoterezov
a5f2cb8bd9 fix(google-auth): popup. 2018-06-12 19:14:05 -05:00
Leonard Kim
78866b0dd7 fix(toolbar): ensure centered toolbar
Maybe there is a case that can be triggered somehow where
the toolbar becomes off center.
2018-06-12 13:07:24 -05:00
Дамян Минков
31011b24c2 Syncs with latest lib-jitsi-meet (d0cb2b2). (#3125) 2018-06-12 09:27:03 -07:00
bbaldino
8df54d2cb3 document layer suspension config and add to whitelist (#3123) 2018-06-12 09:06:10 -07:00
Vangelis Zacharioudakis
0f9c7d8697 Add Greek to languages (#3111) 2018-06-12 08:19:12 -07:00
virtuacoplenny
9d62ecb742 fix(recording): change pending file recording text (#3124) 2018-06-11 14:23:20 -07:00
Saúl Ibarra Corretgé
bda1d7fdab [RN] Update react-native-locale-detector dependency
Fixes the issue / warning about non-UI thread initialization.
2018-06-09 12:05:27 +02:00
Saúl Ibarra Corretgé
d35a1d60a0 [RN] Update react-native-fetch-blob dependency
Maintainership changed, and in addition, they fixed the issue / warning about
non-UI thread initialization.
2018-06-09 12:05:24 +02:00
Saúl Ibarra Corretgé
90d2340609 [iOS] Fix React Native warnings
Fixes the following warning:

~~~
Module XXX requires main queue setup since it overrides `constantsToExport` but doesn't implement `requiresMainQueueSetup`. In a future release React Native will default to initializing all native modules on a background thread unless explicitly opted-out of.
~~~

For AppInfo and AuioMode, there is no need to initialize anything in the UI
thread, so just return NO.
2018-06-09 12:03:45 +02:00
Hristo Terezov
d70ca48728 fix(aot): JS error (#3118)
The following import chain is braking the bundle
AOT->base/toolbox->base/styles->base/react->base/i18n
2018-06-08 14:46:58 -07:00
Lyubo Marinov
a82ed4653e [RN] Allow to override callHandle for CallKit (coding style) 2018-06-08 15:18:11 -05:00
Daniel Ornelas
81be082fe7 [RN] Allow to override callHandle for CallKit 2018-06-08 15:18:11 -05:00
Lyubo Marinov
546651e51f [RN] Hide conference indicators on reduced UI (coding style) 2018-06-08 12:25:02 -05:00
Bettenbuk Zoltan
79b31543c5 [RN] Hide conference indicators on reduced UI 2018-06-08 12:19:34 -05:00
Saúl Ibarra Corretgé
7c8fa57bba [RN] Remove unneeded code 2018-06-08 08:22:18 -05:00
Saúl Ibarra Corretgé
880fb59b2c [RN] Simplify logic for using tinted view in ParticipantView
Use it unless the connection is not ACTIVE. We don't really care if it's
recovering or whatever, if it's not active it has problems, so that's that.

This fixes a potential edge case in which the connection remains in RESTORING
state for some time.
2018-06-08 08:22:18 -05:00
damencho
89160e55f0 Updates react-native-callstats to 3.50.4. 2018-06-08 11:41:29 +02:00
Guus der Kinderen
ccf0c8a363 fix(i18n) Accessiblity labels translations (#3071)
* fix(toolbar): accessibilityLabel should be translatable.

This commit adds a helper property to get the accessibilityLabel of an item,
providing a translation if one is available. This mimics the behavior of
label and tooltip.

* fix(toolbar) 'hangup' button accessibilityLabel i18n

* fix(toolbar) 'mute' button accessibilityLabel i18n

* fix(toolbar) 'videomute' button accessibilityLabel i18n

* fix(toolbar) 'moreActions' button accessibilityLabel i18n

* fix(toolbar) 'shareRoom' button accessibilityLabel i18n

* fix(toolbar) 'audioRoute' button accessibilityLabel i18n

* fix(toolbar) 'toggleCamera' button accessibilityLabel i18n

* fix(toolbar) 'audioOnly' button accessibilityLabel i18n

* fix(toolbar) 'roomLock' button accessibilityLabel i18n

* fix(toolbar) 'pip' button accessibilityLabel i18n

* fix(toolbar) 'invite' button accessibilityLabel i18n

* fix(toolbar) 'raiseHand' button accessibilityLabel i18n

* fix(toolbar) 'chat' button accessibilityLabel i18n

* fix(toolbar) 'shareYourScreen' button accessibilityLabel i18n

* fix(toolbar) 'fullScreen' button accessibilityLabel i18n

* fix(toolbar) 'sharedvideo' button accessibilityLabel i18n

* fix(toolbar) 'document' button accessibilityLabel i18n

* fix(toolbar) 'speakerStats' button accessibilityLabel i18n

* fix(toolbar) 'feedback' button accessibilityLabel i18n

* fix(toolbar) 'shortcuts' button accessibilityLabel i18n

* fix(toolbar) 'recording' button accessibilityLabel i18n

* fix(toolbar) 'settings' button accessibilityLabel i18n

* fix(welcomepage) accessibilityLabels i18n

* fix(toolbar) 'info' button accessibilityLabel i18n

* fix(i18n): Add translation to various aria-label property values.

* fix(i18n): Differentiate between overflow menu and button.
2018-06-07 13:32:18 -07:00
virtuacoplenny
84f303dd3c ref(toolbar): show recording features based on explicit configs (#3080)
* ref(toolbar): show recording features based on explicit configs

* squash: bring back button configs, use final config names

* squash: update interfaceConfig comment, remove unused config whitelist

* squash: change order of button enabled checks to reduce diff

* squash: fileRecording -> fileRecordings
2018-06-05 22:19:28 -07:00
virtuacoplenny
3e79926ad4 feat(recording): add sounds for when recording starts and stops (#3078)
* feat(recording): add sounds for when recording starts and stops

* squash: use constants, play sounds for file only

* squash: rename recordingStopped.mp3 -> recordingOff.mp3

* squash: flip var declaration for alpha order
2018-06-05 20:20:43 +02:00
Aaron van Meerten
ff0d42a95b Merge pull request #3096 from jmacelroy/master
Cleaning up call flow presence stanzas and cancel triggers.
2018-06-05 10:08:05 -05:00
Saúl Ibarra Corretgé
2c0ef822ae deps: update react-native-webrtc and lib-jitsi-meet 2018-06-05 15:40:37 +02:00
Jacob MacElroy
83720a4ed5 fix(call-flows): Maintain presence tags and call id in poltergeist presence stanza. 2018-06-05 13:09:46 +00:00
Jacob MacElroy
01899b1dfd feat(call-flows): Removing cancel hook for ringing status. 2018-06-05 13:09:46 +00:00
Daniel Ornelas
c62809d910 [iOS] Fix SDK deployment target to be able to compile with iOS 9.3
Fixes: https://github.com/jitsi/jitsi-meet/issues/3086
2018-06-05 11:35:25 +02:00
Daniel Ornelas
de725404ef [iOS] Fix issue with Invite RNModule being nil
This happend after initialization and joining a conference for the first time
in JitsiMeetView.
2018-06-05 11:21:22 +02:00
Lyubo Marinov
f30bdb3dd9 lib-jitsi-meet: Upgrade NPM dependencies/packages: react-native 0.55 (continued) 2018-06-04 16:41:34 -05:00
paweldomas
2b20c55bfe ref(types): use IntervalID and TimeoutID types defined by flow 2018-06-04 16:05:48 -05:00
Lyubo Marinov
c6d553738f [RN] Refactor SideBar layout and animation (coding style) 2018-06-04 16:05:48 -05:00
Saúl Ibarra Corretgé
c700261852 [RN] Refactor SideBar layout and animation
Layout:

Use an absolute-fill view as the background with the sidebar on top of. This
greatly simplifies styling, as there is no need to calculate how large the
backdrop needs to be.

Animation:

Switch to a translateX transform animation. This serves 2 purposes: first,
there seems to be a bug somewhere in React Native 0.51-0.55 where the content
that is being animated starts to be clipped. Very weird! But more importantly,
translateX transmorm animations are supported by the native animation driver!

https://facebook.github.io/react-native/blog/2017/02/14/using-native-driver-for-animated.html
8f5ebe5952/Libraries/Animated/src/NativeAnimatedHelper.js (L138-L176)

This makes the animation more performant and buttery smooth.

Some small cleanups are also included here.
2018-06-04 16:05:48 -05:00
Lyubo Marinov
cbd510bf7d flow: tame the beast (coding style) 2018-06-04 16:05:48 -05:00
Saúl Ibarra Corretgé
0817482b9c flow: tame the beast 2018-06-04 16:05:48 -05:00
Saúl Ibarra Corretgé
9ac5aafe10 react: remove custom Fragment
Fragment now works on both React and React Native, so use it.
2018-06-04 16:05:48 -05:00
Lyubo Marinov
efc9cc9f50 [RN] Update react-native to 0.55.4 (continued) 2018-06-04 16:05:48 -05:00
Saúl Ibarra Corretgé
2b7976380e [RN] Update react-native to 0.55.4
Also bump React to 16.3.2, since it's required.
2018-06-04 16:05:33 -05:00
Saúl Ibarra Corretgé
e93c9dde5d [iOS] Fix warning about RCTBatchedBridge deprecation
It's removed in RN >= 0.55. This aligns the project with the official
documentation: https://facebook.github.io/react-native/docs/integration-with-existing-apps.html#configuring-cocoapods-dependencies
2018-06-04 12:23:55 -05:00
Aaron van Meerten
9aa12f7d14 Merge pull request #3044 from jmacelroy/master
Properly propagating call id for call response handling.
2018-06-04 11:54:22 -05:00
Jacob MacElroy
e367490839 Properly propagating call id for call response handling.
Previously a new call id was generated for INVITE and CANCEL.
Now the id generated during the initial INVITE will be used for
corresponding CANCEL events. Also, adding the ability to
trigger a call cancel via the poltergeist update api.
2018-06-01 19:18:09 +00:00
Leonard Kim
91323ebfec ref(video-layout): add thumbnails on participant join action 2018-06-01 10:42:57 -07:00
Leonard Kim
60c68b624e ref(video-layout): local video does not call video layout directly on stream end 2018-06-01 10:42:57 -07:00
Leonard Kim
92414a346a ref(video-layout): remote thumbnail should not update large video directly 2018-06-01 10:42:57 -07:00
Leonard Kim
6f962be322 ref(video-layout): remove unused param in addParticipantContainer 2018-06-01 10:42:57 -07:00
Leonard Kim
1e3dc20b44 ref(video-layout): video layout controls own updating after user leave 2018-06-01 10:42:57 -07:00
Leonard Kim
ec0439cbb1 ref(video-layout): updates connection status when redux updates 2018-06-01 10:42:57 -07:00
Leonard Kim
05801711a7 ref(video-layout): get pinned ID directly from redux 2018-06-01 10:42:57 -07:00
Leonard Kim
57f7abc6dd ref(video-layout): get dominant speaker state from redux
Instead of keeping dominant speaker locally, get it from redux and be
updated when the dominant speaker changes. This is in an attempt to mimic
the video layout being reactified and connected to redux.
2018-06-01 10:42:57 -07:00
Leonard Kim
c4b31435fb ref(video-layout): create middleware to update video layout
The goal will be to make video layout stateless and instead
get all state from redux.
2018-06-01 10:42:57 -07:00
Bettenbuk Zoltan
6a1e9e256d [RN] Make the calendar the default tab when there are calendar entries fetched. 2018-06-01 10:54:11 +02:00
Aaron van Meerten
a4cfe97b38 Merge pull request #3073 from jmacelroy/new-invite-only
No longer triggering calls for the Invited status of a poltergeist.
2018-05-31 14:06:20 -05:00
Jacob MacElroy
b4983cfe04 No longer triggering calls for the Invited status of a poltergeist. 2018-05-31 18:58:47 +00:00
hristoterezov
4a680e11ac fix(analytics): Room name persistant prop. 2018-05-31 16:41:44 +02:00
hristoterezov
ce69ee60ca chore(lib-jitsi-meet): Update. 2018-05-30 16:21:07 -05:00
Jacob MacElroy
fa9a4480e6 Fixing an issue with asnyc http request handlers.
The current poltergeist http api immediately returns
and does not wait for async work in the handler to finish. This
mostly occurs when a public asap key needs to be fetched due
to a cache miss. The fix implements the strategy described at
https://prosody.im/doc/developers/http.html
2018-05-30 11:41:44 -05:00
Guus der Kinderen
f604b1c82d doc: elaborate in Jitsi Meet SDK for Android readme 2018-05-30 16:51:18 +02:00
paweldomas
701552ec8f ref(mobile/wake-lock): convert middleware to a state listener
If CONFERENCE_LEFT would arrive with a delay while we're in
another conference already, then the wake lock could end up in
an incorrect state.
2018-05-30 16:29:27 +02:00
paweldomas
d26d1ff925 ref(mobile/proximity): convert middleware to a state listener 2018-05-30 16:29:27 +02:00
paweldomas
bcb955ea72 ref(full-screen/middleware): use StateListenerRegistry
Use state listener to simplify the logic and not care about the actions
since the fullscreen flag is calculated from the current conference
state.
2018-05-30 16:29:27 +02:00
paweldomas
dbd1091364 ref: use getCurrentConference
Try to use the getCurrentConference function wherever the indention is
to check for the current conference.
2018-05-30 16:29:27 +02:00
Guus der Kinderen
acc41e6d0b feat(toolbar): add 'always-visibile' config option
The visibility of the toolbar can be toggled by interacting with the main screen.
This change allows the toolbar to be configured to be 'always visible'. This voids
the 'toggle' functionality.
2018-05-30 16:12:05 +02:00
Saúl Ibarra Corretgé
4d21c28421 feat(conference): don't add hidden participants to redux
This includes recording agents, for example.
2018-05-30 12:13:32 +02:00
Bettenbuk Zoltan
d15753f719 [RN] Add indicator container 2018-05-30 12:13:32 +02:00
Bettenbuk Zoltan
bced38cefc feat(recording): Move RECORDER_STATE_CHANGED handling to Redux 2018-05-30 12:13:32 +02:00
Bettenbuk Zoltan
5499599720 [RN] Add RecordingLabel indicator for mobile 2018-05-30 12:13:32 +02:00
Bettenbuk Zoltan
118250750a [RN] Add VideoQualityLabel indicator for mobile 2018-05-30 12:13:32 +02:00
Bettenbuk Zoltan
9eb9306e87 [RN] Implement web's CircularLabel component for mobile 2018-05-30 12:13:32 +02:00
Bettenbuk Zoltan
5579464951 [RN] WelcomeScreen post-merge changes 2018-05-30 11:13:16 +02:00
Lyubo Marinov
693b1c392f Fix joining a locked room 2018-05-29 23:13:01 -05:00
Saúl Ibarra Corretgé
bbf505c076 ref(base/conference): simplify code
Simplify parts of the logic introduced in
11b7144ad0.

Specificaly, using all the state change avoiding functions doesn't give us much
since we need to copy the state for sure.
2018-05-29 23:13:01 -05:00
virtuacoplenny
ead62a5dde fix(livestreaming): show separate message for live streaming not enabled (#3063) 2018-05-29 18:53:52 -07:00
Zoltan Bettenbuk
455660c891 Merge pull request #3042 from saghul/android-audiofocus
[Android] Handle audio focus changes while in a conference
2018-05-29 21:29:16 +02:00
Leonard Kim
7de8b96a07 feat(filmstrip): participant on stage displays with transparent video, not hidden 2018-05-29 14:27:07 -05:00
Aaron van Meerten
ca4c7129f1 Merge pull request #3061 from jitsi/update-post-load-error-handler-params
Passes the asset that fail to load to postLoadErrorHandler.
2018-05-29 13:49:33 -05:00
damencho
ad7728a599 Passes the asset that fail to load to postLoadErrorHandler. 2018-05-29 13:41:15 -05:00
Bettenbuk Zoltan
5c7b7cd625 [RN] Add ongoing label to calendar notifications for past meetings 2018-05-29 17:16:17 +02:00
paweldomas
7186a9c79c fix(base/participants): ensure default local id outside of conference
Makes sure that whenever a conference is left or switched, the local
participant's id will be equal to the default value.

The problem fixed by this commit is a situation where the local
participant may end up sharing the same ID with it's "ghost" when
rejoining a disconnected conference. The most important and easiest to
hit case is when the conference is left after the CONFERENCE_FAILED
event.

Another rare and harder to encounter in the real world issue is
where CONFERENCE_LEFT may come with the delay due to it's asynchronous
nature. The step by step scenario is as follows: trying to leave a
conference, but the network is not doing well, so it takes time,
requests are timing out. After getting back to the welcome page the
the CONFERENCE_LEFT has not arrived yet. The same conference is joined
again and the load config may timeout, but it will be read from the
cache. Now the network gets better and conference is joining which
results in our ghost participant added to the redux state. At this point
there's the root issue: two participants with the same id, because the
local one was neither cleared nor set to the new one yet
(PARTICIPANT_JOINED come, before CONFERENCE_JOINED where we adjust the
id). Then comes CONFERENCE_JOINED and we try to update our local id.
We're updating the ID of both ghost and local participant. It could be
also that the delayed CONFERENCE_LEFT comes for the old conference, but
it's too late and it would update the id for both participants.

The approach here reasons that the ID of the local participant
may be reset as soon as the local participant and, respectively, her ID
is no longer involved in a recoverable JitsiConference of interest to
the user and, consequently, the app.

Co-authored-by: Pawel Domas <pawel.domas@jitsi.org>
Co-authored-by: Lyubo Marinov <lmarinov@atlassian.com>
2018-05-29 13:44:06 +02:00
Lyubo Marinov
c672ffd435 Refine PARTICIPANT_LEFT for ID collisions
If the ID of a remote participant was the same as the ID of the local
participant (across multiple conferences), removing the remote
participant on PARTICIPANT_LEFT would remove the local participant.

Like the preceding commit "ref(base/conference): clear the 'conference'
field on WILL_LEAVE", this commit is part of the story how we are to
deal with conferences which take noticeable time to leave.
2018-05-29 12:24:39 +02:00
Lyubo Marinov
8cfc83f18c Protect against late PARTICIPANT_JOINED
Like the preceding commit "ref(base/conference): clear the 'conference'
field on WILL_LEAVE", this commit is part of the story how we are to
deal with conferences which take noticeable time to leave.

If a leave is delayed and the leaving JitsiConference manages to sneak a
PARTICIPANT_JOINED in, it may create a remote participant who even
collides with the local participant.
2018-05-29 12:24:39 +02:00
Lyubo Marinov
fa9549582f Fix JitsiConference access
With so many abstractions called conference, I'm not surprised I made a
mistake and my reviewer didn't catch it.

As we are transitioning from remote participants identified by ID alone
to an ID-conference pair, the subsequent commits "Protect against late
PARTICIPANT_JOINED" and "Refine PARTICIPANT_LEFT for ID collisions"
caught the error.
2018-05-29 12:24:39 +02:00
paweldomas
11b7144ad0 ref(base/conference): clear the 'conference' field on WILL_LEAVE
Pawel Domas said:
The conference state field is referring to the current conference in
progress, so it feels like this field should be cleared as soon as we
declare that the conference is being left and the asynchronous process
of leaving the conference starts (which happens on
CONFERENCE_WILL_LEAVE).

Lyubo Marinov said:
Merely setting conference to undefined is incomplete because there are
other redux state properties associated with conference.

What we're doing here is redefining the redux action
CONFERENCE_WILL_LEAVE:

The redux action CONFERENCE_WILL_LEAVE represents the order of the user
to leave a JitsiConference instance. From the user's perspective, there's
no going back (with respect to the instance itself). The app will perform
due clean-up like leaving the associated room, but the instance is no
longer the focus of the attention of the user and, consequently, the app.

Now CONFERENCE_LEFT and CONFERENCE_WILL_LEAVE have more in common than
before and we can do a much better job by expressing the common parts in
source code. Additionally, our source code can be rewritten to better
express the meaning of any of these two actions.

Co-authored-by: Pawel Domas <pawel.domas@jitsi.org>
Co-authored-by: Lyubo Marinov <lmarinov@atlassian.com>
2018-05-29 12:24:39 +02:00
Lyubo Marinov
92fee790f2 Fix jsdoc 2018-05-28 18:54:11 -05:00
Daniel Ornelas
72487fa7c7 [iOS] Custom CallKit display name
Add ability to provide a display name in the configOverwrite object that
when available it will be used to customize the name of the meeting in
callkit screen and recent call list.

Co-authored-by: Daniel Ornelas <daniel.ob64@gmail.com>
Co-authored-by: Lyubo Marinov <lmarinov@atlassian.com>
2018-05-28 18:53:48 -05:00
paweldomas
380909371f fix(AudioMuteButton): ensure track on unmute
If "startWithAudioMuted" option is used we need to make sure that new
local audio track will be created on unmute.
2018-05-27 15:07:23 -05:00
virtuacoplenny
d4d5ef202a ref(small-video): use css to set component visibility (#3039) 2018-05-25 13:19:51 -07:00
Bettenbuk Zoltan
c95cb0e9cf [RN] Disable filmstrip separation on Android
This zOrder change fixes the issue that the scrollable filmstrip videos
rendered in front of the local participant, however there is still an
issue that needs to be fixed later: The rendered videos should have
overflow: 'hidden' property applied so then they don't get rendered when
scrolled out of the ScrollView, that property however doesn't seem to
work for Video components.
2018-05-25 09:21:26 -05:00
Saúl Ibarra Corretgé
ab83a97fd5 [Android] Handle audio focus changes while in a conference 2018-05-25 15:33:36 +02:00
Jacob MacElroy
9e2a101089 Changing the status strings for call flows to be lowercased where possible.
This should allow us to have a consistent convention and assist
with client translation of status strings.
2018-05-24 10:49:31 -05:00
Saúl Ibarra Corretgé
c8c198b0b1 [Android] Add support for USB headset detection
The API to gather these was introduced in API level 26.
2018-05-24 16:40:11 +02:00
Daniel Ornelas
4bb6e5aefd Implement a delegate of PiPViewCoordinator to notify when user exits Picture in Picture mode 2018-05-23 17:56:16 -05:00
hristoterezov
3dbb663922 feat(call-flows): Add config property for enabling call flows. (#3031) 2018-05-23 14:52:44 -07:00
Lyubo Marinov
592305df74 [RN] Fix processing outdated loadConfig requests (continued)
Co-authored-by: Lyubo Marinov <lmarinov@atlassian.com>
Co-authored-by: Paweł Domas <pawel.domas@jitsi.org>
2018-05-23 16:48:23 -05:00
Saúl Ibarra Corretgé
357b206831 [RN] Fix processing outdated loadConfig requests
This fix is based on storing the location URL object we are loading the
configuration for in the redux store. Once the config has been loaded (or it has
failed, for that matter!) we'll check if the current "config URL" is the same we
set, and discard the old one if they don't match.
2018-05-23 16:48:23 -05:00
Saúl Ibarra Corretgé
d557a6505d [RN] Fix handling XMPP connection failures
After the connection is established, it can either be properly disconnected or
fail. We need to make sure to be listening for events for either.
2018-05-23 16:37:58 -05:00
hristoterezov
f12ba37cf3 fix(presence-status): Use lower case for call flow related statuses. 2018-05-23 15:22:01 -05:00
hristoterezov
d4d2c25499 [RN] Add rejected ringtone sound assets to build 2018-05-23 15:22:01 -05:00
hristoterezov
8b91afd80d fix(invite-sounds): Don't play joined sound for the poltergeist. 2018-05-23 15:22:01 -05:00
hristoterezov
d89227829f feat(invite-sounds): Add expired and rejected sounds. 2018-05-23 15:22:01 -05:00
hristoterezov
2b1c875b91 feat(presence-status): Add more statuses. 2018-05-23 15:22:01 -05:00
Дамян Минков
5b4a16cf6b Updates lib-jitsi-meet that uses strophe.js 1.2.15. (#3020) 2018-05-23 11:08:47 -07:00
Zoltan Bettenbuk
480fe53001 Merge pull request #3009 from jitsi/state-listener
[RN] Prevent multiplying remote thumbnails
2018-05-23 18:48:16 +02:00
Lyubo Marinov
2ecacf6c3e [RN] Fix a "TypeError: Cannot read property handlers of undefined" in ImageCache
With some of the preceding commits in the "multiplying remote
thumbnails" story line, I started hitting this error with 100%
reproducibility:

1. Have a remote participant prepared in conferenceA. Web will do as
well.
2. On iOS prepare to join conferenceB in Safari and use the same device
for step 3.
3. Join conferenceA on the iOS device from step 2 with audio-only. The
audio-only is so that avatars are always visible. Wait for the remote
participant prepared in step 1 to appear.
4. Switch to Safari and hit "Continue in the app" to have the app leave
conferenceA and join conferenceB.

What happens:
After the iOS device joins conferenceB in the Jitsi Meet app, the local
participant is on the large video (as expected) but the avatar of the
local participant is the default audo-generated auto-colored
placeholder. That's because this error was hit and the avatar couldn't
be "fetched".
2018-05-23 10:26:59 -05:00
Lyubo Marinov
ee9fcbb735 Remove remote participants who are no longer of interest
The Jitsi Meet app always has at most 1 conference of primary interest.
It may have to juggle with 2 JitsiConference instances at the same time
if 1 is in the process of being left and one is joining/joined. But the
one which is joining or joined (which we call conference in the
features/base/conference redux state) is the one "of interest", the
other one is "clean up". Consequently, the remote participants of the
conference "of interest" are the remote participants "of interest" and
the others are "clean up". In order to reduce the time during which
there may be multiplying remote thumbnails, clean the remote
participants who are no longer "of interest" up.
2018-05-23 09:46:15 -05:00
Lyubo Marinov
db21e97c19 StateListenerRegistry
"Middleware" redux state changes, not actions.
2018-05-23 09:46:15 -05:00
Lyubo Marinov
8cd2bd272b Reduce direct read access to the features/base/participants redux state
As part of the work on fixing the problem with the multiplying
thumbnails, we've associated remote participant w/ JitsiConference.
However, there are periods of time when multiple JitsiConferences are in
the redux state (and that period is going to be shorted by
StateListenerRegistry). In order to give more control to the feature
base/participants, reduce the occurrences of direct access to the
features/base/participants redux state and utilize the feature's
existing read access functions. Which will allow us in the future to
enhance these functions to access participants which are relevant to the
current conference of interest to the user only.
2018-05-23 09:46:15 -05:00
Lyubo Marinov
771d60f954 Associate remote participant w/ JitsiConference (_UPDATED)
The commit message of "Associate remote participant w/ JitsiConference
(_JOINED)" explains the motivation for this commit.

Practically, _JOINED and _LEFT combined with "Remove remote participants
who are no longer of interest" should alleviate the problem with
multiplying remote participants to an acceptable level of annoyance.

Technically though, a remote participant cannot be identified by an ID
only. The ID is (somewhat) "unique" in the context of a single
JitsiConference instance. So in order to not have to scratch our heads
over an obscure corner, racing case, it's better to always identify
remote participants by the pair id-conference. Unfortunately, that's a
bit of a high order given the existing source code. So I've implemented
the cases which are the easiest so that new source code written with
participantUpdated is more likely to identify a remote participant with
the pair id-conference.

Additionally, the commit "Reduce direct read access to the
features/base/participants redux state" brings more control back to the
functions of the feature base/participants so that one day we can (if we
choose to) do something like, for example:

If getParticipants is called with a conference, it returns the
participants from features/base/participants who are associated with the
specified conference. If no conference is specified in the function
call, then default to the conference which is the primary focus of the
app at the time of the function call. Added to the above, this should
allow us to further reduce the cases in which we're identifying remote
participants by id only and get us even closer to a more "predictable"
behavior in corner, racing cases.
2018-05-23 09:46:15 -05:00
Lyubo Marinov
37cd5bb5b9 Associate remote participant w/ JitsiConference (_LEFT)
The commit message of "Associate remote participant w/ JitsiConference
(_JOINED)" explains the motivation for this commit.
2018-05-23 09:46:15 -05:00
Lyubo Marinov
983e62ffe9 Associate remote participant w/ JitsiConference (_JOINED)
The plan set in motion here is to associate remote participants with the
JitsiConference instances that created them in order to be able to
remove remote participants when a JitsiConference is no longer the
primary focus of the jitsi-meet app. And that's supposed to alleviate a
problem with multiplying remote thumbnails.

Doing all of the above in a single commit is a bit of a high order. So
I'm splitting the whole into multiple successive commits for the
purposes of observability, comprehension. Each commit is supposed to be
safe even if subsequent commits are not accepted, are reverted,
whatever. Obviously, without the successive commits, a commit may be
"unused".

One of the important pieces of the multiplying remote thumbnails "fix"
offered is removing remote participants who are no longer of interest
i.e. PARTICIPANT_LEFT. But in order for _LEFT to be implemented, _JOINED
must be implemented first.
2018-05-23 09:44:26 -05:00
Lyubo Marinov
fcca15c827 Coding style 2018-05-23 08:48:13 -05:00
Lyubo Marinov
6b3da4a4a4 redux 4.0.0, react-redux 5.0.7 2018-05-23 08:48:13 -05:00
Zoltan Bettenbuk
8ecd6e6ced Merge pull request #3028 from saghul/rn-improve-styles
[RN] Simplify absolute fill styles
2018-05-23 15:28:07 +02:00
Saúl Ibarra Corretgé
f7c4133fb7 [RN] Use a SafeAreaView for OverlayFrame 2018-05-23 14:30:42 +02:00
Saúl Ibarra Corretgé
bbb1dce42a [RN] Simplify styles which fill the parent view
Turns out React Native offers an object with the following definition:

{
    bottom: 0,
    left: 0,
    position: 'absolute',
    right: 0,
    top: 0
}
2018-05-23 14:30:22 +02:00
Zoltan Bettenbuk
31864b95ab Merge pull request #3005 from saghul/fix-ios-dev-debug
[iOS] Fix running in Debug mode on a device
2018-05-23 12:36:22 +02:00
Zoltan Bettenbuk
33a748b569 Merge pull request #3013 from saghul/toolbox-disabled-button
[RN] Toolbox disabled button styles
2018-05-23 12:02:49 +02:00
Saúl Ibarra Corretgé
83ede66037 feat(toolbox): add disabledStyles to AbstractButton
It allows for specifying an override style collection which is applied when the
button is disabled.
2018-05-23 11:14:42 +02:00
Saúl Ibarra Corretgé
2128c84212 [RN] Add utility function to combine 2 sets of styles 2018-05-23 10:03:58 +02:00
Zoltan Bettenbuk
a72463a302 Merge pull request #3014 from saghul/toolbox-toggled-labels
[RN] Toolbox toggled item labels
2018-05-22 21:00:32 +02:00
Zoltan Bettenbuk
62cc44f791 Merge pull request #3012 from saghul/overflow-menu-dismiss
[RN] Dismiss OverflowMenu after selecting an option
2018-05-22 17:57:23 +02:00
Saúl Ibarra Corretgé
b4c95bec39 [RN] Adjust button labels to reflext the actual state 2018-05-22 12:02:05 +02:00
Saúl Ibarra Corretgé
7012c77fe9 feat(toolbox): add ability to specify a toggled label on AbstractButton 2018-05-22 12:01:00 +02:00
Saúl Ibarra Corretgé
a8b1ca38dc [RN] Dismiss OverflowMenu after selecting an option 2018-05-22 11:33:03 +02:00
Saúl Ibarra Corretgé
99b5a41269 feat(toolbox): add ability to run a handler after click to AbstractButton 2018-05-22 11:32:09 +02:00
virtuacoplenny
f608ad4e5e feat(toolbar): add beta tag to live streaming button (#3007)
* feat(toolbar): add beta tag to live streaming button

* tweak colors and border radius
2018-05-21 15:16:38 -07:00
Saúl Ibarra Corretgé
0d81e677bf [iOS] Fix running in Debug mode on a device
Thsi fixed a regression in 8f75c2e279

The bundler script doesn't do anything (it literally exits right at the top)
when skipping the bundle. This is arguably wrong, because it doesn't generate
"ip.txt", the file with the bundler IP address either!

So, generate that ourselves. While ding this, also drop the need for xip.io,
which has also been removed from RN, since it gives more trouble than it solves.
2018-05-21 14:05:58 +02:00
Lyubo Marinov
22ce001f14 Coding style: commends, formatting, sorting order 2018-05-20 22:58:34 -05:00
paweldomas
9650404099 fix(base/conference): leave a failed conference
Because a conference can fail before or after it's joined it must be
"left" in order to release any allocated resources like peerconnections,
tracks and all the other things.
2018-05-20 21:23:25 -05:00
paweldomas
7704809c4c feat(base/connection): log leave failure reason
Because there are multiple reasons on why a conference.leave() may fail
it's helpful to know if and why it happened.
2018-05-20 21:17:36 -05:00
Lyubo Marinov
75c7cfd9e1 Coding style: comments, formatting, sorting order 2018-05-20 19:01:59 -05:00
Saúl Ibarra Corretgé
ef7fb1a7b0 [RN] Make all delegate / listener methods run in the main / UI thread 2018-05-20 16:46:44 -05:00
Lyubo Marinov
ccbf3efa38 Coding style: comments, sorting order 2018-05-18 15:36:43 -05:00
Saúl Ibarra Corretgé
39e46bacf6 [RN] Avoid Toolbox changing size on first render
Wait until the right button size has been calculated before rendering it.
2018-05-18 15:36:43 -05:00
Leonard Kim
12d7ab9026 ref(layout): use css to make video layout containers fit window
Instead of using JS, just use CSS 100% width and height. This
also resolves a jitter that occurs on edge when a modal's
container appends to the body.
2018-05-18 15:34:40 -05:00
hristoterezov
9131d2448d ref(i18next): Remove the base/config dependancy. 2018-05-18 13:34:08 -05:00
Saúl Ibarra Corretgé
44c498a566 [RN] Adjust toolbar button sizes / margins
- add 10px of padding on the sizes of the toolbar
- make the button margin smaller (from 10 to 7)
- increate the secondary button factor to 85%, thus rising the maximum secondary
  button size to 50 (from the previous 48)
2018-05-18 13:19:17 -05:00
Zoltan Bettenbuk
192f1d44f5 [RN] Add unlock room function to mobile 2018-05-18 13:18:24 -05:00
Zoltan Bettenbuk
b57dad576a feat(participants): add isLocalParticipantModerator utility method 2018-05-18 13:18:24 -05:00
Saúl Ibarra Corretgé
494e8eb8d9 [RN] Set the local user ID on CONFERENCE_WILL_JOIN
Doing so in CONFERENCE_JOINED is too late because if we are moderators that
event will come first and we won't know what ID to match it with.

This is safe because our local ID is created early.
2018-05-18 13:18:24 -05:00
Lyubo Marinov
21c1e4abc4 Coding style: formatting, comments 2018-05-18 08:58:08 -05:00
Saúl Ibarra Corretgé
5bd975e3f3 [RN] Show / hide the toolbox based on the participant count
Show it if we are the only participant, and hide it the moment someone else
joins the conference.
2018-05-18 08:07:34 -05:00
Saúl Ibarra Corretgé
8f75c2e279 [iOS] Don't bundle the React resources in Debug mode
It significantly speeds up debug builds, putting them on par with Android, where
we also don't bundle the React resources.
2018-05-18 14:36:03 +02:00
hristoterezov
add89e2488 fix(dial-in-info): Qiuck fix of JitsiMeetJS undefined error. 2018-05-17 15:07:03 -05:00
Saúl Ibarra Corretgé
284d09eae7 [RN] Add ringtone sound assets to build 2018-05-17 08:31:46 -05:00
Bettenbuk Zoltan
180141cefb [RN] Fix ios audio issue with locked rooms 2018-05-17 10:43:48 +02:00
Lyubo Marinov
df8eb36d0e Coding style: comments 2018-05-16 16:49:03 -05:00
Saúl Ibarra Corretgé
f54f5df428 [RN] Implement a new UI for the Toolbox
- 5 buttons in the (now single) toolbar
- Overflow menu in the form of a BottomSheet
- Filmstrip on the right when in wide mode
2018-05-16 12:18:38 -05:00
hristoterezov
c344a83376 Outgoing call ringtones (#2949)
* fix(PresenceLabel): Use translated strings for the presence label.

* feat(sounds): Implements loop and stop functionality.

* feat(invite): Add ringtones.

* fix(invite): Code style issues.
2018-05-16 11:03:10 -04:00
virtuacoplenny
ee74f11c3d feat(recording): frontend logic can support live streaming and recording (#2952)
* feat(recording): frontend logic can support live streaming and recording

Instead of either live streaming or recording, now both can live together. The
changes to facilitate such include the following:
- Killing the state storing in Recording.js. Instead state is stored in the lib
  and updated in redux for labels to display the necessary state updates.
- Creating a new container, Labels, for recording labels. Previously labels were
  manually created and positioned. The container can create a reasonable number
  of labels and only the container itself needs to be positioned with CSS. The
  VideoQualityLabel has been shoved into the container as well because it moves
  along with the recording labels.
- The action for updating recording state has been modified to enable updating
  an array of recording sessions to support having multiple sessions.
- Confirmation dialogs for stopping and starting a file recording session have
  been created, as they previously were jquery modals opened by Recording.js.
- Toolbox.web displays live streaming and recording buttons based on
  configuration instead of recording availability.
- VideoQualityLabel and RecordingLabel have been simplified to remove any
  positioning logic, as the Labels container handles such.
- Previous recording state update logic has been moved into the RecordingLabel
  component. Each RecordingLabel is in charge of displaying state for a
  recording session. The display UX has been left alone.
- Sipgw availability is no longer broadcast so remove logic depending on its
  state. Some moving around of code was necessary to get around linting errors
  about the existing code being too deeply nested (even though I didn't touch
  it).

* work around lib-jitsi-meet circular dependency issues

* refactor labels to use html base

* pass in translation keys to video quality label

* add video quality classnames for torture tests

* break up, rearrange recorder session update listener

* add comment about disabling startup resize animation

* rename session to sessionData

* chore(deps): update to latest lib for recording changes
2018-05-16 07:00:16 -07:00
Zoltan Bettenbuk
5fd0f95a89 Merge pull request #2976 from saghul/bottom-sheet-orientation
[RN] Support landscape and portrait orientations in BottomSheet
2018-05-16 10:28:41 +02:00
Zoltan Bettenbuk
0d1d0d06a4 Merge pull request #2977 from saghul/dialog-modal-orientation
[RN] Support landscape and portrait orientations in Dialog
2018-05-16 10:28:28 +02:00
Mark Madlangbayan
b037e1d736 Updated nodejs requirement 2018-05-16 09:48:47 +02:00
Saúl Ibarra Corretgé
bce2438471 feat(toolbox): rename label / tooltip getters in ToolboxItem 2018-05-15 14:12:39 -05:00
Saúl Ibarra Corretgé
8a160fd9ab feat(toolbox): fix ToggleCameraButton tooltip / label 2018-05-15 14:12:39 -05:00
Saúl Ibarra Corretgé
cd57477b68 feat(toolbox): restore emitting UI events for legacy code 2018-05-15 14:12:39 -05:00
Lyubo Marinov
f1123a8cdd Restore the audio and video mute/unmute keyboard shortcuts
I don't think it's realistic that we'd merge a PR that breaks the most
important shortcuts of the app (in my opinion): audio and video
mute/unmute.
2018-05-15 14:12:39 -05:00
Lyubo Marinov
447035c8b2 Coding style 2018-05-15 14:12:39 -05:00
Lyubo Marinov
7fe421aeba Removes unnecessary source code 2018-05-15 14:12:38 -05:00
Lyubo Marinov
3aff4967f1 Keep buttons in their associated features
Contributing all buttons in one place goes against the designs that we
set out at the beginning of the project's rewrite and that multiple of
us have been following since then.
2018-05-15 14:12:38 -05:00
Lyubo Marinov
a42496ba53 Coding style 2018-05-15 14:12:38 -05:00
Saúl Ibarra Corretgé
9ca7ca9515 feat(toolbox): move visibleButtons to redux
Technically we still depend on interfaceConfig, but this paves the way for when
that is no longer the case.
2018-05-15 14:12:38 -05:00
Saúl Ibarra Corretgé
9a3effe97a feat(toolbox): move OverflowMenu show logic to redux 2018-05-15 14:12:38 -05:00
Saúl Ibarra Corretgé
a2834a2495 [RN] Refactor Toolbox
Create standalone components for each feature and move all state to them.
Toolbars are now dummy containers.
2018-05-15 14:12:38 -05:00
Saúl Ibarra Corretgé
450400b768 feat(filmstrip): refactor filmstrip only toolbar
- Move the toolbar to the filmstrip feature
- Use all the buttons from the toolbox feature
2018-05-15 14:12:38 -05:00
Saúl Ibarra Corretgé
c7d72ee3f6 feat(toolbox): create SettingsButton
Only on web, since there is currently no equivalent on mobile. It encapsulates
all funcionality related to opening the settings dialog / panel.
2018-05-15 14:12:38 -05:00
Saúl Ibarra Corretgé
01cb4ac7c8 feat(alwaysontop): refactor toolbox
Use the new abstractions, which already take care of the rendering part.
2018-05-15 14:12:38 -05:00
Saúl Ibarra Corretgé
b634f6b200 feat(toolbox): implement buttons using ToolboxItem
Currently the following are implemented:

- AudioMuteButton
- HangupButton
- VideoMuteButton

In order to implement these new buttons a new abstract class was introduced,
which abstracts the ToolboxItem into a button with enough hooks so a stateful
and a stateless version of it can be created.

This patch only adds the stateful implementation of the aforementioned buttons.
2018-05-15 14:12:38 -05:00
Saúl Ibarra Corretgé
8d94cc5cb2 feat(toolbox): introduce ToolboxItem
This abstraction represents an action which can go anywhere in a toolbox (be
that the main toolbar or the overflow menu) and it's platform independent.

It does not depend on Redux, thus making it stateless, which facilitates its use
in stateful button implementations as well as stateless ones.
2018-05-15 14:12:38 -05:00
Saúl Ibarra Corretgé
52da5010cc [Android] Make sure we use the react-native version in node_modules
Releases are also published to jcenter, and due to how the dependency is
declared, we are picking the latest release from there, which is arguably not
what we want.
2018-05-15 13:48:34 -05:00
Saúl Ibarra Corretgé
362486cbea deps: update react-native-locale-detector
Includes a fix for compatibility with RN >= 0.47.
2018-05-15 13:48:34 -05:00
Saúl Ibarra Corretgé
eea6af51db [RN] Support landscape and portrait orientations in Dialog 2018-05-15 17:55:33 +02:00
Bettenbuk Zoltan
df7b8e51fc [RN] Improve video switch style and remove react warning 2018-05-15 17:42:54 +02:00
Lyubo Marinov
f64b511682 Fix "objc[59448]: Cannot form weak reference to instance (0x60c000039440) of class RNCallKit. It is possible that this object was over-released, or is in the process of deallocation." 2018-05-15 17:29:20 +02:00
Saúl Ibarra Corretgé
c6f99f3dda [RN] Support landscape and portrait orientations in BottomSheet
One has to be explicit on iOS, otherwise it seems to be locked to portrait only.
2018-05-15 17:12:40 +02:00
Lyubo Marinov
75fe3e3b9d [RN] If base/config knows a domain, then the app knows it
Knowledge is power, man!

The config.js cache predates the feature base/known-domains.
Technically, it's also able to recall more domains that the feature
recent-list can (because the latter limits its entries).
2018-05-14 16:33:02 -05:00
Lyubo Marinov
631f51d627 [RN] Legacy support of calendar-sync's knownDomains
Knowledge is power, man!

We moved "knownDomains" from calendar-sync to base/known-domains.
However, we do have an official release in the app stores and I'd like
us to not throw away the knowledge it has acquired.
2018-05-14 16:32:30 -05:00
Lyubo Marinov
d4dea22576 [RN] If recent-list knows a domain, then the app knows it 2018-05-14 16:32:19 -05:00
Lyubo Marinov
5e8ecc5fee Coding style: naming, comments 2018-05-14 16:31:47 -05:00
Zoltan Bettenbuk
46bc63b79e [RN] If calendar-sync knows a domain, then the app knows it 2018-05-14 16:28:38 -05:00
Zoltan Bettenbuk
ee94d79ee6 [RN] Add known-domains feature 2018-05-14 16:27:48 -05:00
Aaron van Meerten
1ba564f49c Merge pull request #2966 from jmacelroy/call-response
Adding cancel to mod_muc_call
2018-05-14 12:31:58 -05:00
Leonard Kim
e649ae9016 fix(toolbar): show profile button if guest
If authenticated using JWT, don't show the profile button but
do show it if a guest.
2018-05-14 13:05:24 -04:00
Jacob MacElroy
f60559fb67 Adding cancel to mod_muc_call 2018-05-14 16:34:37 +00:00
hristoterezov
39ba83131b fix(ga): Change the cookieDomain parameter to 'auto'. (#2954) 2018-05-11 08:32:02 -07:00
Paweł Domas
b2ad8a95d4 fix(ios,build-ipa.sh): lib-jitsi-meet package sed escape (#2953)
It needs to be global and the old replace seems to escape only the first
occurrence.
2018-05-10 12:46:34 -07:00
Zoltan Bettenbuk
b797b0b201 Merge pull request #2946 from jitsi/fix-app-props-typeerror
[Android] Fix possible TypeError in multi-JitsiMeetView SDK consumers
2018-05-10 12:40:11 +02:00
Lyubo Marinov
bd7c9473e7 [Android] Fix possible TypeError in multi-JitsiMeetView SDK consumers
If multiple JitsiMeetView instances are created (not necessarily
existing at once), it's possible to hit a TypeError when reading the
React Component props of the currently mounted App. Anyway, in certain
places we're already protecting against that out of abundance of caution
so it makes no sense to not protect everywhere.
2018-05-09 23:45:24 -05:00
hristoterezov
53971d0b50 feat(DeepLinkingMobilePage): Add dynamic links support. 2018-05-09 09:16:02 -05:00
Zoltan Bettenbuk
44a65eb329 Move local participant join to base/participants 2018-05-09 15:20:22 +02:00
zbettenbuk
cc7e15ab8f Fix accidental overwrite of localParticipant with empty values 2018-05-09 15:20:22 +02:00
zbettenbuk
959db3a665 Make web use the redux settings/profile 2018-05-09 15:20:22 +02:00
Zoltan Bettenbuk
ab7e572162 Merge pull request #2932 from saghul/refactor-bottomsheet
[RN] Refactor SimpleBottomSheet
2018-05-09 10:59:34 +02:00
Saúl Ibarra Corretgé
4fdd71d1bd [RN] Refactor SimpleBottomSheet
Make it more generic by accepting any content except of just rows with text and
icons.

In addition, rework its structure so the animation is smoother, by putting the
background overlay outside of the Modal. This way, the animation doesn't affect
the background, which won't slide down.
2018-05-08 22:25:25 +02:00
paweldomas
8f5ec20da8 fix(.travis.yml): xcode9.3 2018-05-08 10:38:17 -05:00
paweldomas
740817fdcf chmod +x on build-ipa.sh 2018-05-07 17:04:40 -05:00
paweldomas
8ca836c922 feat(iOS): build .ipa for PRs in Travis CI 2018-05-07 15:31:23 -05:00
Lyubo Marinov
6a0de0ddde Coding style: consistency, formatting, naming 2018-05-07 10:05:37 -05:00
zbettenbuk
b777322fdc [RN] Make the audio-video labels touchable 2018-05-07 10:05:22 -05:00
zbettenbuk
0826ffa974 [RN] Increase header height 2018-05-07 10:05:06 -05:00
hristoterezov
6aae56527f fix(web_ios): Not rendering when the browser back button is pressed. 2018-05-07 15:26:19 +02:00
Lyubo Marinov
7ffdaf59c7 [RN] Add an example how to consume the public SDK invite API 2018-05-07 00:38:49 -05:00
Lyubo Marinov
85612b9ae1 [iOS] Namespace the public API of the feature invite 2018-05-06 22:42:32 -05:00
Daniel Ornelas
529e5e8938 [iOS] Fix nullability warnings/errors caused by the feature invite 2018-05-06 22:41:45 -05:00
hristoterezov
bcd9163284 chore(package.json): Update lib-jitsi-meet version. 2018-05-04 17:05:54 -05:00
virtuacoplenny
d62ac72cfa Various pixel pushing, tooltip copy changes (#2918)
* fix(toolbar): make button hover bigger

* fix(toolbar): make hangup button bigger

* fix(always-on-top): make toolbar and buttons same sizes as main toolbar

* fix(toolbar): change some tooltips

* fix(toolbar): adjust side panel and filmstrip for new toolbar sizes
2018-05-04 13:10:48 -07:00
Saúl Ibarra Corretgé
8656c7e760 doc: add a badge / link to the latest stable release 2018-05-04 07:49:55 -05:00
paweldomas
a9ee5944e1 ref(base/connection): conferenceFailed error argument
Introduce ConnectionFailedError type.
2018-05-04 13:02:19 +02:00
paweldomas
36ecc99b5b fix(auth/external API): CONFERENCE_FAILED with login dialog
Delay the CONFERENCE_FAILED event until the user cancels the login
dialog using the 'recoverable' event flag.
2018-05-04 13:02:19 +02:00
paweldomas
82f5eb894b ref(external API): emit CONFERENCE_FAILED on CONNECTION_FAILED
It seems that the external API will not send any event to let the sdk
consumer know that the conference has failed if the problem occurs at
the establishing of XMPP connection stage. That's because the config was
loaded successfully, but the conference instance does not exist yet, so
neither base/config nor base/conference will emit any failure.
2018-05-04 13:02:19 +02:00
paweldomas
91a65735f9 ref(external API): emit CONFERENCE_WILL_JOIN on SET_ROOM
Make the external API emit CONFERENCE_WILL_JOIN early on SET_ROOM action
which occurs before the XMPP connection is created. At this point we
know that config has loaded and if there's a valid conference room to
be joined. We were thinking of doing that even on CONFIG_WILL_LOAD,
but that seemed to be to risky at this point.
2018-05-04 13:02:19 +02:00
virtuacoplenny
c2e40f975e chore(deps): update lib to hide error on extension install cancel (#2911) 2018-05-03 16:56:44 -07:00
Lyubo Marinov
5e79bbecef Codying style: naming, formatting, comments 2018-05-03 18:04:59 -05:00
Daniel Ornelas
e5309a6482 [iOS] Proxy CallKit API to be a feature of the SDK
With this the RN component and the consumer app can share same CallKit
provider, configuration, and enable to be part of multiple listeners of
the CallKit flow events. The main driver of this is to enable the
consumer app to be able to report an incoming call to the OS before
loading the JitsiMeetView. Once the user answers the call, the app can
instantiate a JitsiMeetView, pass the CallKit call UUIID, and the Jitsi
Meet components will handle the connection and report back to CallKit
that the  call has been established.
2018-05-03 18:04:59 -05:00
Lyubo Marinov
520bb8bd22 Coding style/naming: invite & invitee(s)
Hristo Terezov, Chris Cordle, and I/Lyubomir Marinov agreed that we'd
try to use "invite" & "invitee(s)" in Web/React's iframe API,
mobile/react-native's SDK invite API, and internally for the purposes of
consistency, ease of understanding, etc.
2018-05-03 10:31:15 +02:00
hristoterezov
69eefc82a5 feat(iframe_api): Add invite functionality. 2018-05-03 10:31:15 +02:00
hristoterezov
d1af11c67e ref(sendInvitesForItems): convert to action. 2018-05-03 10:31:15 +02:00
Leonard Kim
3091d9e6dd fix(feedback): remove textarea overflow hiding to allow scrolling
I'm unsure why all textareas need overflow hidden. Doing so
essentially overrides what I would expect to be standard textarea
behavior. I would rather remove the reset and fix any areas that
have issues.
2018-05-02 16:53:06 -05:00
Leonard Kim
19ce472d4d fix(large-video): enable canvas based background by default 2018-05-02 12:56:01 -05:00
Leonard Kim
a793742e3a fix(always-on-top): change button order
To be consistent with how the main window toolbar has audio mute,
hangup, and then video mute.
2018-05-02 19:04:13 +02:00
hristoterezov
3d8d5e3107 fix(deep-linking-mobile): Path of the images. 2018-05-02 11:16:00 -05:00
Saúl Ibarra Corretgé
0bad2dffb4 [iOS] Dybamically enable addPeopleEnabled / dialOutEnabled
If the beginAddPeople delegate method is implemented, and they haven't been
explicitly set to NO, they willbe YES.
2018-05-02 10:56:41 -05:00
Saúl Ibarra Corretgé
552fadc4a4 [RN] Don't process invite events if the external API scope doesn't match
This can happen if there are multiple JitsiMeetView instances are active at the
same time, because there is a single bridge, which means all of them would get
the events.
2018-05-02 10:56:41 -05:00
Saúl Ibarra Corretgé
ed7d8ac57e [RN] Cleanup subscriptions for the invite module 2018-05-02 10:56:41 -05:00
Saúl Ibarra Corretgé
cf6dd98b02 [iOS] Document invite module methods 2018-05-02 10:56:41 -05:00
Saúl Ibarra Corretgé
6c602accae [iOS] Fixup style 2018-05-02 10:56:41 -05:00
paweldomas
17ae89a56c ref(PiP/actions): remove 'is ok to enter PiP' check
Currently enterPictureInPicture action can only be dispatched when
the app is on the conference view and the enter PiP button is displayed,
so no check should be necessary.
2018-05-02 17:06:24 +02:00
paweldomas
565fd37f28 fix(Android|PiP): do not invoke 'enterPictureInPicture' in PAUSED state
Activity.enterPictureInPictureMode method must be invoked synchronously
on userLeaveHint callback in order to be sure that the current Activity
is still visible (does not transit to PAUSED state). Previously if the
asynchronous processing would be delayed enough for the Activity to go
into the PAUSED state it will be too late to go into the PiP mode.
2018-05-02 17:06:24 +02:00
Lyubo Marinov
effd3728b6 [RN] add support for inviting participants during a call on mobile (2) 2018-05-02 12:54:02 +02:00
virtuacoplenny
f450756337 chore(deps): update lib-jitsi-meet for getting youtube url (#2902) 2018-05-01 15:14:44 -07:00
Leonard Kim
f015f4edc3 feat(large-video): background blur through canvas and feature flag
To reduce the amount of motion that has to be blurred, use a canvas
to essentially set the FPS of the video background. This canvas
component is behind a temporary feature flag, as well as being able
to disable the blur, so it can be played around with on deployed
environments.
2018-05-01 17:11:25 -05:00
Leonard Kim
0831c16d7e fix(large-video): apply blur to only one container 2018-05-01 17:11:16 -05:00
Leonard Kim
d94093a01e fix(hangup): remove device change listener
When closing and reopening a notebook, the browser will fire
a devicechange event. This event should not be listened to
after hangup because the current listener will call gum if
there is no current audio/video, which is the case after
hangup.

Initially this change was going to be remove all conference
listeners but then I noticed that the listener for devicechange
is set differently. It might be worthwhile future work, although
potentially unnecessary right now, to ensure all listeners
registered in conference.js are properly cleaned up on hangup.
2018-05-01 17:10:56 -05:00
zbettenbuk
1eac4c51ca [RN] Add missing key prop to Android page indicator 2018-05-01 12:57:03 -05:00
Leonard Kim
eaea0e5f49 fix(languages): remove trailing comma from json 2018-05-01 10:24:22 -05:00
smithgeek
b2971aa9be Fix name of display name change action. (#2468) 2018-05-01 17:13:18 +02:00
Tunc Kayikcioglu
f64fdb4a8f Updated available languages list (#2850) 2018-05-01 17:10:51 +02:00
damencho
31dec2cf99 Cleans libs folder before create it and deploy to it. 2018-05-01 09:15:57 -05:00
zbettenbuk
54b6db02d8 Fix error on conference join when calendar is disabled 2018-05-01 09:09:32 -05:00
zbettenbuk
68608478f6 Refactor PagedList components to be independent from the lists it renders 2018-05-01 09:09:32 -05:00
Дамян Минков
2ee8f1ef58 Updates postinst prosody (#2896)
* Creates conf.d in /etc/prosody if missing.

Fixes a problem installing prosody 0.10 when using prosody repositories.

* Cleanups certificates on purge.

There are various occasions where users purge packages and the new installations after that generate certificate which doesn't end up in the java trust store on the target machine.

* Generate jicofo user and component passwords if missing.

There are situations where if prosody is already installed, the order of configuring the packages is not in the correct order. In those situations jitsi-meet-prosody got configured before jicofo and the user password and the component secret are not available and we ask the user for that and later when jicofo is configured we generate new set of them. Now we will end up always generating them in jitsi-meet-prosody or jicofo and we will reuse them. See https://github.com/jitsi/jicofo/pull/283.
2018-04-30 15:19:30 -07:00
Louis Chen
627b9d319c [RN] Pollyfill DOMParser
It's required by lib-jitsi-meet when using WebSockets.
2018-04-30 08:52:41 +02:00
virtuacoplenny
aaf2f428e6 feat(recording): new label for live streaming (#2890) 2018-04-29 21:30:07 -07:00
virtuacoplenny
f4060975d1 feat(device-selection): design tweaks (#2802)
- Change copy
- Add labels to dropdowns
- Adjust color and positioning
2018-04-27 18:43:11 -07:00
virtuacoplenny
0a0256501c feat(recording): new icon when recording is active (#2884)
- Update font files to add new icon.
- Update markup and style so the icon has a small background
  to fill in the text of the icon.
- Remove some css transitions that don't seem to do much.
2018-04-26 14:24:52 -07:00
paweldomas
81fc961998 fix(invite): check Toolbox render method error
The app will crash for me with react complaining about not a component
being passed.
2018-04-26 15:12:03 -05:00
virtuacoplenny
e098ad87f4 feat(icons): add new recording button icon (#2834)
* feat(icons): add new recording button icon

* feat(toolbar): show different rec/streaming icon
2018-04-26 10:26:41 -07:00
virtuacoplenny
ed395036b7 fix(info): change jsdoc to avoid linting warning (#2881) 2018-04-26 10:10:24 -07:00
Zoltan Bettenbuk
4f8fd1019b Separate local thumbnail in filmstrip (#2848)
* Separate local thumbnail in filmstrip

* style(Filmstrip.native): utilize full line length
2018-04-26 07:44:23 -05:00
hristoterezov
f14095ecfc feat(deep_linking): add analytics
In order to be able to add analytics to the deep-linking pages the
lib-jitsi-meet initialization has been moved so it happens earlier.

The introduced `initPromise` will eventually disappear, once conference is
migrated into React and / or support for Temasys is dropped. At that stage, it
can be turned into a sync function which all platforms share.
2018-04-26 10:11:34 +02:00
zbettenbuk
6947926494 Add dynamic move threshold to pnz touch detection 2018-04-25 16:35:22 -05:00
hristoterezov
784d94b30f fix(deeplinking): logo image 2018-04-25 14:19:08 -05:00
Saúl Ibarra Corretgé
1d7e0845aa [RN] use the share button if the invite button is not enabled 2018-04-25 18:58:06 +02:00
Ryan Peck
f64c13d4b7 [RN] add support for inviting participants during a call on mobile
* Button conditionally shown based on if the feature is enabled and available
* Hooks for launching the invite UI (delegates to the native layer)
* Hooks for using the search and dial out checks from the native layer (calls back into JS)
* Hooks for handling sending invites and passing any failures back to the native layer
* Android and iOS handling for those hooks

Author: Ryan Peck <rpeck@atlassian.com>
Author: Eric Brynsvold <ebrynsvold@atlassian.com>
2018-04-25 18:58:06 +02:00
George Politis
4e36127dc7 config: Whitelists enable{Remb,Tcc} and minParticipants. 2018-04-24 16:01:23 -05:00
Дамян Минков
8b1aff5512 Adds in memory log storage, to be used while testing. (#2858)
* Adds in memory log storage, to be used while testing.

Enabling it only when config.debug is set, a configuration provided by jitsi-meet-torture.

* Moves to using config.testing.testMode property for logs storage.

* Fixes comments.
2018-04-24 13:56:54 -05:00
941 changed files with 65206 additions and 31286 deletions

View File

@@ -16,18 +16,14 @@
; Ignore polyfills
.*/Libraries/polyfills/.*
; Ignore metro
.*/node_modules/metro/.*
; Ignore packages in node_modules which we (i.e. the jitsi-meet project) have
; seen to cause errors and we have chosen not to fix.
.*/node_modules/@atlaskit/.*/*.js.flow
.*/node_modules/@atlassian
.*/node_modules/bower/lib/node_modules/bower-json/test/.*
.*/node_modules/immutable/dist/immutable.js.flow
.*/node_modules/jsonlint/test/.*
; FIXME Remove once we update past commit
; https://github.com/facebook/react-native/commit/df8d0d1db9203cc87ad3682e6138b2a9ed714365
.*/node_modules/react-native/local-cli/link/link.js
.*/node_modules/react-native-keep-awake/.*
.*/node_modules/react-native-permissions/.*
.*/node_modules/styled-components/.*
.*/\.git/.*
@@ -37,33 +33,43 @@
[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
esproposal.optional_chaining=enable
esproposal.nullish_coalescing=enable
module.system=haste
module.system.haste.use_name_reducers=true
# get basename
module.system.haste.name_reducers='^.*/\([a-zA-Z0-9$_.-]+\.js\(\.flow\)?\)$' -> '\1'
# strip .js or .js.flow suffix
module.system.haste.name_reducers='^\(.*\)\.js\(\.flow\)?$' -> '\1'
# strip .ios suffix
module.system.haste.name_reducers='^\(.*\)\.ios$' -> '\1'
module.system.haste.name_reducers='^\(.*\)\.android$' -> '\1'
module.system.haste.name_reducers='^\(.*\)\.native$' -> '\1'
module.system.haste.paths.blacklist=.*/__tests__/.*
module.system.haste.paths.blacklist=.*/__mocks__/.*
module.system.haste.paths.blacklist=<PROJECT_ROOT>/node_modules/react-native/Libraries/Animated/src/polyfills/.*
module.system.haste.paths.whitelist=<PROJECT_ROOT>/node_modules/react-native/Libraries/.*
munge_underscores=true
; FIXME Remove once we update past commit
; https://github.com/facebook/react-native/commit/df8d0d1db9203cc87ad3682e6138b2a9ed714365
module.name_mapper='^./link/link$' -> 'emptyObject'
module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> 'RelativeImageStub'
suppress_type=$FlowIssue
suppress_type=$FlowFixMe
suppress_type=$FlowFixMeProps
suppress_type=$FlowFixMeState
suppress_type=$FixMe
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(5[0-7]\\|[1-4][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(5[0-7]\\|[1-4][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(<VERSION>\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(<VERSION>\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+
suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy
suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError
unsafe.enable_getters_and_setters=true
; We (i.e. the jitsi-meet project) are using the haste module system on Web as
; well, not only on React Native. Unfortunately, Flow does not support .web.js
; by default. Override Flow's defaults to include .web.js as well. Technically,
@@ -77,4 +83,4 @@ module.file_ext=.jsx
module.file_ext=.json
[version]
^0.57.0
^0.78.0

27
.github/ISSUE_TEMPLATE/bug-report.md vendored Normal file
View File

@@ -0,0 +1,27 @@
---
name: Bug Report
about: Before posting, please make sure you check https://community.jitsi.org
---
*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.
## Description
---
## Current behavior
---
## Expected Behavior
---
## Possible Solution
---
## Steps to reproduce
---
# Environment details
---

8
.gitignore vendored
View File

@@ -68,3 +68,11 @@ buck-out/
*/fastlane/report.xml
*/fastlane/Preview.html
*/fastlane/screenshots
# Bundle artifact
*.jsbundle
# precommit-hook
.jshintignore
.jshintrc

4
.travis.yml Normal file
View File

@@ -0,0 +1,4 @@
osx_image: xcode10
language: objective-c
script:
- "./ios/travis-ci/build-ipa.sh"

View File

@@ -2,6 +2,7 @@ BUILD_DIR = build
CLEANCSS = ./node_modules/.bin/cleancss
DEPLOY_DIR = libs
LIBJITSIMEET_DIR = node_modules/lib-jitsi-meet/
LIBFLAC_DIR = node_modules/libflacjs/dist/min/
NODE_SASS = ./node_modules/.bin/node-sass
NPM = npm
OUTPUT_DIR = .
@@ -19,9 +20,10 @@ compile:
clean:
rm -fr $(BUILD_DIR)
deploy: deploy-init deploy-appbundle deploy-lib-jitsi-meet deploy-css deploy-local
deploy: deploy-init deploy-appbundle deploy-lib-jitsi-meet deploy-libflac deploy-css deploy-local
deploy-init:
rm -fr $(DEPLOY_DIR)
mkdir -p $(DEPLOY_DIR)
deploy-appbundle:
@@ -32,6 +34,8 @@ deploy-appbundle:
$(BUILD_DIR)/do_external_connect.min.map \
$(BUILD_DIR)/external_api.min.js \
$(BUILD_DIR)/external_api.min.map \
$(BUILD_DIR)/flacEncodeWorker.min.js \
$(BUILD_DIR)/flacEncodeWorker.min.map \
$(BUILD_DIR)/device_selection_popup_bundle.min.js \
$(BUILD_DIR)/device_selection_popup_bundle.min.map \
$(BUILD_DIR)/dial_in_info_bundle.min.js \
@@ -49,6 +53,12 @@ deploy-lib-jitsi-meet:
$(LIBJITSIMEET_DIR)/modules/browser/capabilities.json \
$(DEPLOY_DIR)
deploy-libflac:
cp \
$(LIBFLAC_DIR)/libflac4-1.3.2.min.js \
$(LIBFLAC_DIR)/libflac4-1.3.2.min.js.mem \
$(DEPLOY_DIR)
deploy-css:
$(NODE_SASS) $(STYLES_MAIN) $(STYLES_BUNDLE) && \
$(CLEANCSS) $(STYLES_BUNDLE) > $(STYLES_DESTINATION) ; \
@@ -57,7 +67,7 @@ deploy-css:
deploy-local:
([ ! -x deploy-local.sh ] || ./deploy-local.sh)
dev: deploy-init deploy-css deploy-lib-jitsi-meet
dev: deploy-init deploy-css deploy-lib-jitsi-meet deploy-libflac
$(WEBPACK_DEV_SERVER)
source-package:

View File

@@ -10,12 +10,15 @@ Jitsi Meet allows for very efficient collaboration. It allows users to stream th
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.
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.
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).
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).
## Download
| Latest stable release | [![release](https://img.shields.io/badge/release-latest-green.svg)](https://github.com/jitsi/jitsi-meet/releases/latest) |
|---|---|
You can download Debian/Ubuntu binaries:
* [stable](https://download.jitsi.org/stable/) ([instructions](https://jitsi.org/downloads/ubuntu-debian-installations-instructions/))
* [testing](https://download.jitsi.org/testing/) ([instructions](https://jitsi.org/downloads/ubuntu-debian-installations-instructions-for-testing/))
@@ -30,11 +33,11 @@ You can get our mobile versions from here:
## Building the sources
Node.js >= 6 is required.
Node.js >= 8 and npm >= 6 are required.
On Debian/Ubuntu systems, the required packages can be installed with:
```
sudo apt-get install npm nodejs-legacy
sudo apt-get install npm nodejs
cd jitsi-meet
npm install
```
@@ -127,7 +130,7 @@ equivalent to these of a direct one-to-one WebRTC call. This is what's unique to
Jitsi Meet in terms of security.
The [meet.jit.si](https://meet.jit.si) service is maintained by the Jitsi team
at [Atlassian](https://atlassian.com).
at [8x8](https://8x8.com).
## Mobile app
Jitsi Meet is also available as a React Native app for Android and iOS.

View File

@@ -20,7 +20,7 @@
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', options.googleAnalyticsTrackingId, 'jit.si');
ga('create', options.googleAnalyticsTrackingId, 'auto');
ga('send', 'pageview');
/* eslint-enable */
@@ -126,6 +126,15 @@
return;
}
const ignoredEvents
= [ 'e2e_rtt', 'rtp.stats', 'rtt.by.region', 'available.device',
'stream.switch.delay', 'ice.state.changed', 'ice.duration' ];
// Temporary removing some of the events that are too noisy.
if (ignoredEvents.indexOf(event.action) !== -1) {
return;
}
const gaEvent = {
'eventCategory': 'jitsi-meet',
'eventAction': this._extractAction(event),

View File

@@ -1,24 +1,58 @@
# Jitsi Meet SDK for Android
## Build
## 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.
The repository typically goes into the `build.gradle` file in the root of your project:
```gradle
allprojects {
repositories {
google()
jcenter()
maven {
url "https://github.com/jitsi/jitsi-maven-repository/raw/master/releases"
}
}
}
```
Dependency definitions belong in the individual module `build.gradle` files:
```gradle
dependencies {
// (other dependencies)
implementation ('org.jitsi.react:jitsi-meet-sdk:+') { transitive = true }
}
```
### Build and use your own SDK artifacts/binaries
1. Install all required [dependencies](https://github.com/jitsi/jitsi-meet/blob/master/doc/mobile.md).
2. ```bash
2. Create the SDK-release assembly, by invoking the following in the jitsi-meet
project source:
```bash
cd android/
./gradlew :sdk:assembleRelease
```
When this successfully executes, artifacts/binaries are ready to be published
into a Maven repository of your choice.
3. Configure the Maven repositories in which you are going to publish the
artifacts/binaries during step 4. Modify
artifacts/binaries during step 4.
In the file `android/sdk/build.gradle` modify the line that contains
`"file:${rootProject.projectDir}/../../../jitsi/jitsi-maven-repository/releases"`
in adroid/sdk/build.gradle for Jitsi Meet SDK for Android and/or
`"file:${rootProject.projectDir}/../../../jitsi/jitsi-maven-repository/releases"`
in android/build.gradle for the third-party react-native modules which Jitsi
Meet SDK for Android depends on and are not publicly available in Maven
repositories. Generally, if you are modifying the JavaScript code of Jitsi
Meet SDK for Android only, you will very likely need to consider the former
only.
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.
4. Publish the Maven artifact/binary of Jitsi Meet SDK for Android in the Maven
repository configured in step 3:
@@ -27,27 +61,66 @@
./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:
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, replace `sdk` with the name of the react-native module. For
example, to publish react-native-webrtc:
```gradle
implementation ('org.jitsi.react:jitsi-meet-sdk:+') { transitive = true }
```
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.
6. Create the release assembly for _each_ third-party react-native module that you
need, replacing it's name in the example below.
```bash
./gradlew :react-native-webrtc:assembleRelease
```
7. Configure the Maven repositories in which you are going to publish the
artifacts/binaries during step 8.
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"`
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.
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:
```bash
./gradlew :react-native-webrtc:publish
```
## Install
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`.
Add the Maven repository
`https://github.com/jitsi/jitsi-maven-repository/raw/master/releases` and the
dependency `org.jitsi.react:jitsi-meet-sdk:1.9.0` into your `build.gradle`.
## API
## 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.
First, add Java 1.8 compatibility support to your project by adding the
following lines into your `build.gradle` file:
```
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
```
To get started, extends your `android.app.Activity` from
`org.jitsi.meet.sdk.JitsiMeetActivity`:
@@ -132,13 +205,13 @@ which displays a single `JitsiMeetView`.
See JitsiMeetView.getDefaultURL.
#### getPictureInPictureEnabled()
#### isPictureInPictureEnabled()
See JitsiMeetView.getPictureInPictureEnabled.
See JitsiMeetView.isPictureInPictureEnabled.
#### getWelcomePageEnabled()
#### isWelcomePageEnabled()
See JitsiMeetView.getWelcomePageEnabled.
See JitsiMeetView.isWelcomePageEnabled.
#### loadURL(URL)
@@ -177,13 +250,13 @@ if set to `null`, the default built in JavaScript is used: https://meet.jit.si.
Returns the `JitsiMeetViewListener` instance attached to the view.
#### getPictureInPictureEnabled()
#### 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.
#### getWelcomePageEnabled()
#### 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.
@@ -243,7 +316,7 @@ effect.
#### setWelcomePageEnabled(boolean)
Sets whether the Welcome page is enabled. See `getWelcomePageEnabled` for more
Sets whether the Welcome page is enabled. See `isWelcomePageEnabled` for more
information.
NOTE: Must be called (if at all) before `loadURL`/`loadURLString` for it to take
@@ -418,3 +491,29 @@ Picture-in-Picture style scenario, in a rectangle too small to accommodate its
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:
1. Add the following to the app's AndroidManifest.xml and change `<APP_KEY>` to
your Dropbox app key:
```
<activity
android:configChanges="keyboard|orientation"
android:launchMode="singleTask"
android:name="com.dropbox.core.android.AuthActivity">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="db-<APP_KEY>" />
</intent-filter>
</activity>
```
2. Add the following to the app's strings.xml and change `<APP_KEY>` to your
Dropbox app key:
```
<string name="dropbox_app_key"><APP_KEY></string>
```

View File

@@ -2,6 +2,7 @@ apply plugin: 'com.android.application'
android {
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
defaultConfig {
applicationId 'org.jitsi.meet'
@@ -25,16 +26,88 @@ android {
exclude '/lib/x86_64/**'
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
debug {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules-debug.pro'
}
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules-release.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
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 project(':sdk')
debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.1'
releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.1'
}
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 = dropboxScheme?.text() - 'db-'
if (dropboxAppKey) {
android.defaultConfig.resValue('string', 'dropbox_app_key', "${dropboxAppKey}")
def dropboxActivity = """
<activity
android:configChanges="keyboard|orientation"
android:launchMode="singleTask"
android:name="com.dropbox.core.android.AuthActivity">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="db-${dropboxAppKey}" />
</intent-filter>
</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)
}
}
}
}
}
}
if (project.file('google-services.json').exists()) {
apply plugin: 'com.google.gms.google-services'
}

View File

@@ -0,0 +1,5 @@
-include proguard-rules.pro
# Disabling obfuscation is useful if you collect stack traces from production crashes
# (unless you are using a system that supports de-obfuscate the stack traces).
-dontobfuscate

View File

@@ -0,0 +1,6 @@
-include proguard-rules.pro
# Crashlytics
-keepattributes *Annotation*
-keepattributes SourceFile,LineNumberTable
-keep public class * extends java.lang.Exception

View File

@@ -16,10 +16,6 @@
# public *;
#}
# Disabling obfuscation is useful if you collect stack traces from production crashes
# (unless you are using a system that supports de-obfuscate the stack traces).
-dontobfuscate
# React Native
# Keep our interfaces so they can be used by other ProGuard rules.
@@ -68,3 +64,26 @@
-dontwarn java.nio.file.*
-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
-dontwarn okio.**
# FastImage
-keep public class com.dylanvann.fastimage.** {*;}
# We added the following when we switched minifyEnabled on. Probably because we
# ran the app and hit problems...
-keep class com.facebook.react.bridge.CatalystInstanceImpl { *; }
-keep class com.facebook.react.bridge.ExecutorToken { *; }
-keep class com.facebook.react.bridge.JavaScriptExecutor { *; }
-keep class com.facebook.react.bridge.ModuleRegistryHolder { *; }
-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.**
-dontwarn com.squareup.okhttp.**
-dontwarn javax.servlet.**
# ^^^ We added the above when we switched minifyEnabled on.

View File

@@ -5,6 +5,7 @@
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:name=".MainApplication"
android:theme="@style/AppTheme">
<activity
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize"

View File

@@ -22,9 +22,15 @@ import android.util.Log;
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 com.calendarevents.CalendarEventsPackage;
import com.facebook.react.bridge.UiThreadUtil;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
@@ -40,6 +46,14 @@ import java.util.Map;
* {@code react-native run-android}.
*/
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.
*/
private static final String ADD_PEOPLE_CONTROLLER_QUERY = null;
@Override
protected JitsiMeetView initializeView() {
JitsiMeetView view = super.initializeView();
@@ -50,6 +64,8 @@ public class MainActivity extends JitsiMeetActivity {
if (BuildConfig.DEBUG && view != null) {
view.setListener(new JitsiMeetViewListener() {
private void on(String name, Map<String, Object> data) {
UiThreadUtil.assertOnUiThread();
// Log with the tag "ReactNative" in order to have the log
// visible in react-native log-android as well.
Log.d(
@@ -89,11 +105,73 @@ public class MainActivity extends JitsiMeetActivity {
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());
}
return view;
}
private void onAddPeopleControllerInviteSettled(
AddPeopleController addPeopleController,
List<Map<String, Object>> failedInvitees) {
UiThreadUtil.assertOnUiThread();
// 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();
}
private void onAddPeopleControllerReceivedResults(
AddPeopleController addPeopleController,
List<Map<String, Object>> results,
String query) {
UiThreadUtil.assertOnUiThread();
int size = results.size();
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());
}
}
addPeopleController.inviteById(ids);
return;
}
// 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();
}
@Override
protected void onCreate(Bundle savedInstanceState) {
// As this is the Jitsi Meet app (i.e. not the Jitsi Meet SDK), we do
@@ -106,9 +184,47 @@ public class MainActivity extends JitsiMeetActivity {
super.onCreate(savedInstanceState);
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
CalendarEventsPackage.onRequestPermissionsResult(requestCode, permissions, grantResults);
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
private void onInviteControllerBeginAddPeople(
InviteController inviteController,
AddPeopleController addPeopleController) {
UiThreadUtil.assertOnUiThread();
// 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();
}
}
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright @ 2018-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;
import android.app.Application;
import com.squareup.leakcanary.LeakCanary;
/**
* Simple {@link Application} for hooking up LeakCanary:
* https://github.com/square/leakcanary
*/
public class MainApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
if (!LeakCanary.isInAnalyzerProcess(this)) {
LeakCanary.install(this);
}
}
}

View File

@@ -7,7 +7,8 @@ buildscript {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.0.1'
classpath 'com.android.tools.build:gradle:3.1.4'
classpath 'com.google.gms:google-services:3.2.1'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files.
@@ -16,13 +17,36 @@ 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" }
}
// Make sure we use the react-native version in node_modules and not the one
// published in jcenter / elsewhere.
configurations.all {
resolutionStrategy {
eachDependency { DependencyResolveDetails details ->
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
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]}"
}
}
}
}
// Third-party react-native modules which Jitsi Meet SDK for Android depends
// on and which are not available in third-party Maven repositories need to
// be deployed in a Maven repository of ours.
@@ -129,10 +153,11 @@ allprojects {
}
ext {
buildToolsVersion = "26.0.2"
compileSdkVersion = 26
minSdkVersion = 16
buildToolsVersion = "27.0.3"
compileSdkVersion = 27
minSdkVersion = 21
targetSdkVersion = 26
supportLibVersion = "27.1.1"
// 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
@@ -155,3 +180,8 @@ subprojects { subproject ->
}
}
}
task wrapper(type: Wrapper) {
gradleVersion = '4.4'
distributionUrl = distributionUrl.replace("bin", "all")
}

View File

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

Binary file not shown.

View File

@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip

110
android/gradlew vendored
View File

@@ -1,4 +1,4 @@
#!/usr/bin/env bash
#!/usr/bin/env sh
##############################################################################
##
@@ -6,47 +6,6 @@
##
##############################################################################
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn ( ) {
echo "$*"
}
die ( ) {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
esac
# For Cygwin, ensure paths are in UNIX format before anything is touched.
if $cygwin ; then
[ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
fi
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
@@ -61,9 +20,49 @@ while [ -h "$PRG" ] ; do
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >&-
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >&-
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
@@ -90,7 +89,7 @@ location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
@@ -114,6 +113,7 @@ fi
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
@@ -154,11 +154,19 @@ if $cygwin ; then
esac
fi
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
JVM_OPTS=("$@")
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
APP_ARGS=$(save "$@")
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"

14
android/gradlew.bat vendored
View File

@@ -8,14 +8,14 @@
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
@@ -46,10 +46,9 @@ echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windowz variants
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args
@rem Slurp the command line arguments.
@@ -60,11 +59,6 @@ set _SKIP=2
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
goto execute
:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$
:execute
@rem Setup the command line

View File

@@ -19,126 +19,144 @@ android {
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
implementation fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:27.0.2'
compile 'com.facebook.react:react-native:+'
implementation "com.android.support:support-v4:${rootProject.ext.supportLibVersion}"
implementation "com.android.support:appcompat-v7:${rootProject.ext.supportLibVersion}"
compile project(':react-native-background-timer')
compile project(':react-native-fetch-blob')
compile project(':react-native-immersive')
compile project(':react-native-keep-awake')
compile project(':react-native-locale-detector')
compile project(':react-native-sound')
compile project(':react-native-vector-icons')
compile project(':react-native-webrtc')
compile project(':react-native-calendar-events')
implementation 'com.dropbox.core:dropbox-core-sdk:3.0.8'
api 'com.facebook.react:react-native:+'
testCompile 'junit:junit:4.12'
}
// Build process helpers
//
void runBefore(String dependentTaskName, Task task) {
Task dependentTask = tasks.findByPath(dependentTaskName);
if (dependentTask != null) {
dependentTask.dependsOn task
implementation project(':react-native-background-timer')
implementation project(':react-native-calendar-events')
implementation(project(':react-native-fast-image')) {
exclude group: 'com.android.support'
}
implementation(project(":react-native-google-signin")) {
exclude group: 'com.google.android.gms'
exclude group: 'com.android.support'
}
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')
testImplementation 'junit:junit:4.12'
}
gradle.projectsEvaluated {
android.buildTypes.all { buildType ->
def buildNameCapitalized = "${buildType.name.capitalize()}"
def bundlePath = "${buildDir}/intermediates/bundles/${buildType.name}"
// Bundle fonts in react-native-vector-icons.
// Here we bundle all assets, resources and React files. We cannot use the
// react.gradle file provided by react-native because it's designed to be used
// in an application (it taps into applicationVariants, but the SDK is a library
// so we need libraryVariants instead).
android.libraryVariants.all { def variant ->
// Create variant and target names
def targetName = variant.name.capitalize()
def targetPath = variant.dirName
// React js bundle directories
def jsBundleDir = file("$buildDir/generated/assets/react/${targetPath}")
def resourcesDir = file("$buildDir/generated/res/react/${targetPath}")
def jsBundleFile = file("$jsBundleDir/index.android.bundle")
def currentBundleTask = tasks.create(
name: "bundle${targetName}JsAndAssets",
type: Exec) {
group = "react"
description = "bundle JS and assets for ${targetName}."
// Create dirs if they are not there (e.g. the "clean" task just ran)
doFirst {
jsBundleDir.deleteDir()
jsBundleDir.mkdirs()
resourcesDir.deleteDir()
resourcesDir.mkdirs()
}
// Set up inputs and outputs so gradle can cache the result
def reactRoot = file("${projectDir}/../../")
inputs.files fileTree(dir: reactRoot, excludes: ["android/**", "ios/**"])
outputs.dir jsBundleDir
outputs.dir resourcesDir
// Set up the call to the react-native cli
workingDir reactRoot
// Set up dev mode
def devEnabled = !targetName.toLowerCase().contains("release")
// Run the bundler
commandLine(
"node",
"node_modules/react-native/local-cli/cli.js",
"bundle",
"--platform", "android",
"--dev", "${devEnabled}",
"--reset-cache",
"--entry-file", "index.android.js",
"--bundle-output", jsBundleFile,
"--assets-dest", resourcesDir)
// Disable bundling on dev builds
enabled !devEnabled
}
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
variant.mergeAssets.doLast {
// Bundle fonts
//
def currentFontTask = tasks.create(
name: "copy${buildNameCapitalized}Fonts",
type: Copy) {
copy {
from("${projectDir}/../../fonts/jitsi.ttf")
from("${projectDir}/../../node_modules/react-native-vector-icons/Fonts/")
into("${bundlePath}/assets/fonts")
into("${assetsDir}/fonts")
}
currentFontTask.dependsOn("merge${buildNameCapitalized}Resources")
currentFontTask.dependsOn("merge${buildNameCapitalized}Assets")
runBefore("processArmeabi-v7a${buildNameCapitalized}Resources", currentFontTask)
runBefore("processX86${buildNameCapitalized}Resources", currentFontTask)
runBefore("processUniversal${buildNameCapitalized}Resources", currentFontTask)
runBefore("process${buildNameCapitalized}Resources", currentFontTask)
def currentSoundsTask = tasks.create(
name: "copy${buildNameCapitalized}Sounds",
type: Copy) {
from("${projectDir}/../../sounds/joined.wav")
from("${projectDir}/../../sounds/left.wav")
into("${bundlePath}/assets/sounds")
}
currentSoundsTask.dependsOn("merge${buildNameCapitalized}Resources")
currentSoundsTask.dependsOn("merge${buildNameCapitalized}Assets")
runBefore("processArmeabi-v7a${buildNameCapitalized}Resources", currentSoundsTask)
runBefore("processX86${buildNameCapitalized}Resources", currentSoundsTask)
runBefore("processUniversal${buildNameCapitalized}Resources", currentSoundsTask)
runBefore("process${buildNameCapitalized}Resources", currentSoundsTask)
// Bundle JavaScript and React resources.
// (adapted from react-native/react.gradle)
// Bundle sounds
//
// React JS bundle directories
def jsBundleDir = file("${bundlePath}/assets")
def resourcesDir = file("${bundlePath}/res/merged")
def jsBundleFile = file("${jsBundleDir}/index.android.bundle")
// Bundle task name for variant.
def bundleJsAndAssetsTaskName = "bundle${buildNameCapitalized}JsAndAssets"
def currentBundleTask = tasks.create(
name: bundleJsAndAssetsTaskName,
type: Exec) {
// Set up inputs and outputs so gradle can cache the result.
def reactRoot = file("${projectDir}/../../")
inputs.files fileTree(dir: reactRoot, excludes: ['android/**', 'ios/**'])
outputs.dir jsBundleDir
outputs.dir resourcesDir
// Set up the call to the react-native cli.
workingDir reactRoot
// Create JS bundle
def devEnabled = !buildNameCapitalized.toLowerCase().contains('release')
commandLine(
'node',
'node_modules/react-native/local-cli/cli.js',
'bundle',
'--assets-dest', resourcesDir,
'--bundle-output', jsBundleFile,
'--dev', "${devEnabled}",
'--entry-file', 'index.android.js',
'--platform', 'android',
'--reset-cache')
// Disable bundling on dev builds
enabled !devEnabled
copy {
from("${projectDir}/../../sounds/joined.wav")
from("${projectDir}/../../sounds/left.wav")
from("${projectDir}/../../sounds/outgoingRinging.wav")
from("${projectDir}/../../sounds/outgoingStart.wav")
from("${projectDir}/../../sounds/recordingOn.mp3")
from("${projectDir}/../../sounds/recordingOff.mp3")
from("${projectDir}/../../sounds/rejected.wav")
into("${assetsDir}/sounds")
}
// Hook bundle${productFlavor}${buildType}JsAndAssets into the android build process
currentBundleTask.dependsOn("merge${buildNameCapitalized}Resources")
currentBundleTask.dependsOn("merge${buildNameCapitalized}Assets")
// Copy React assets
//
if (currentBundleTask.enabled) {
copy {
from(jsBundleDir)
into(assetsDir)
}
}
}
runBefore("processArmeabi-v7a${buildNameCapitalized}Resources", currentBundleTask)
runBefore("processX86${buildNameCapitalized}Resources", currentBundleTask)
runBefore("processUniversal${buildNameCapitalized}Resources", currentBundleTask)
runBefore("process${buildNameCapitalized}Resources", currentBundleTask)
variant.mergeResources.doLast {
// Copy React resources
//
if (currentBundleTask.enabled) {
copy {
from(resourcesDir)
into(variant.mergeResources.outputDir)
}
}
}
}
publishing {
publications {
aarArchive(MavenPublication) {

View File

@@ -16,7 +16,9 @@ import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
class AndroidSettingsModule extends ReactContextBaseJavaModule {
class AndroidSettingsModule
extends ReactContextBaseJavaModule {
public AndroidSettingsModule(ReactApplicationContext reactContext) {
super(reactContext);
}

View File

@@ -1,3 +1,19 @@
/*
* 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.content.Context;
@@ -11,7 +27,9 @@ import com.facebook.react.bridge.ReactContextBaseJavaModule;
import java.util.HashMap;
import java.util.Map;
class AppInfoModule extends ReactContextBaseJavaModule {
class AppInfoModule
extends ReactContextBaseJavaModule {
public AppInfoModule(ReactApplicationContext reactContext) {
super(reactContext);
}

View File

@@ -25,8 +25,6 @@ import android.content.pm.PackageManager;
import android.media.AudioDeviceInfo;
import android.media.AudioManager;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import com.facebook.react.bridge.Arguments;
@@ -41,6 +39,8 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Module implementing a simple API to select the appropriate audio device for a
@@ -57,7 +57,10 @@ import java.util.Set;
* Before a call has started and after it has ended the
* {@code AudioModeModule.DEFAULT} mode should be used.
*/
class AudioModeModule extends ReactContextBaseJavaModule {
class AudioModeModule
extends ReactContextBaseJavaModule
implements AudioManager.OnAudioFocusChangeListener {
/**
* Constants representing the audio mode.
* - DEFAULT: Used before and after every call. It represents the default
@@ -72,13 +75,20 @@ class AudioModeModule extends ReactContextBaseJavaModule {
private static final int VIDEO_CALL = 2;
/**
*
* Constant defining the action for plugging in a headset. This is used on
* our device detection system for API < 23.
*/
private static final String ACTION_HEADSET_PLUG
= (Build.VERSION.SDK_INT >= 21)
? AudioManager.ACTION_HEADSET_PLUG
: Intent.ACTION_HEADSET_PLUG;
/**
* Constant defining a USB headset. Only available on API level >= 26.
* The value of: AudioDeviceInfo.TYPE_USB_HEADSET
*/
private static final int TYPE_USB_HEADSET = 22;
/**
* The name of {@code AudioModeModule} to be used in the React Native
* bridge.
@@ -90,6 +100,11 @@ class AudioModeModule extends ReactContextBaseJavaModule {
*/
static final String TAG = MODULE_NAME;
/**
* Indicator that we have lost audio focus.
*/
private boolean audioFocusLost = false;
/**
* {@link AudioManager} instance used to interact with the Android audio
* subsystem.
@@ -103,10 +118,11 @@ class AudioModeModule extends ReactContextBaseJavaModule {
private BluetoothHeadsetMonitor bluetoothHeadsetMonitor;
/**
* {@link Handler} for running all operations on the main thread.
* {@link ExecutorService} for running all audio operations on a dedicated
* thread.
*/
private final Handler mainThreadHandler
= new Handler(Looper.getMainLooper());
private static final ExecutorService executor
= Executors.newSingleThreadExecutor();
/**
* {@link Runnable} for running audio device detection the main thread.
@@ -133,6 +149,7 @@ class AudioModeModule extends ReactContextBaseJavaModule {
break;
case AudioDeviceInfo.TYPE_WIRED_HEADPHONES:
case AudioDeviceInfo.TYPE_WIRED_HEADSET:
case TYPE_USB_HEADSET:
devices.add(DEVICE_HEADPHONES);
break;
}
@@ -212,7 +229,7 @@ class AudioModeModule extends ReactContextBaseJavaModule {
// Do an initial detection on Android >= M.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
mainThreadHandler.post(onAudioDeviceChangeRunner);
runInAudioThread(onAudioDeviceChangeRunner);
} else {
// On Android < M, detect if we have an earpiece.
PackageManager pm = reactContext.getPackageManager();
@@ -252,7 +269,7 @@ class AudioModeModule extends ReactContextBaseJavaModule {
*/
@ReactMethod
public void getAudioDevices(final Promise promise) {
mainThreadHandler.post(new Runnable() {
runInAudioThread(new Runnable() {
@Override
public void run() {
WritableMap map = Arguments.createMap();
@@ -289,7 +306,7 @@ class AudioModeModule extends ReactContextBaseJavaModule {
* Only used on Android >= M.
*/
void onAudioDeviceChange() {
mainThreadHandler.post(onAudioDeviceChangeRunner);
runInAudioThread(onAudioDeviceChangeRunner);
}
/**
@@ -317,7 +334,7 @@ class AudioModeModule extends ReactContextBaseJavaModule {
* Only used on Android < M.
*/
void onHeadsetDeviceChange() {
mainThreadHandler.post(new Runnable() {
runInAudioThread(new Runnable() {
@Override
public void run() {
// XXX: isWiredHeadsetOn is not deprecated when used just for
@@ -337,6 +354,44 @@ class AudioModeModule extends ReactContextBaseJavaModule {
});
}
/**
* {@link AudioManager.OnAudioFocusChangeListener} interface method. Called
* when the audio focus of the system is updated.
*
* @param focusChange - The type of focus change.
*/
@Override
public void onAudioFocusChange(int focusChange) {
switch (focusChange) {
case AudioManager.AUDIOFOCUS_GAIN: {
Log.d(TAG, "Audio focus gained");
// Some other application potentially stole our audio focus
// temporarily. Restore our mode.
if (audioFocusLost) {
updateAudioRoute(mode);
}
audioFocusLost = false;
break;
}
case AudioManager.AUDIOFOCUS_LOSS:
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: {
Log.d(TAG, "Audio focus lost");
audioFocusLost = true;
break;
}
}
}
/**
* Helper function to run operations on a dedicated thread.
* @param runnable
*/
public void runInAudioThread(Runnable runnable) {
executor.execute(runnable);
}
/**
* Sets the user selected audio device as the active audio device.
*
@@ -344,7 +399,7 @@ class AudioModeModule extends ReactContextBaseJavaModule {
*/
@ReactMethod
public void setAudioDevice(final String device) {
mainThreadHandler.post(new Runnable() {
runInAudioThread(new Runnable() {
@Override
public void run() {
if (!availableDevices.contains(device)) {
@@ -415,7 +470,7 @@ class AudioModeModule extends ReactContextBaseJavaModule {
}
}
};
mainThreadHandler.post(r);
runInAudioThread(r);
}
/**
@@ -487,8 +542,9 @@ class AudioModeModule extends ReactContextBaseJavaModule {
Log.d(TAG, "Update audio route for mode: " + mode);
if (mode == DEFAULT) {
audioFocusLost = false;
audioManager.setMode(AudioManager.MODE_NORMAL);
audioManager.abandonAudioFocus(null);
audioManager.abandonAudioFocus(this);
audioManager.setSpeakerphoneOn(false);
setBluetoothAudioRoute(false);
selectedDevice = null;
@@ -501,7 +557,7 @@ class AudioModeModule extends ReactContextBaseJavaModule {
audioManager.setMicrophoneMute(false);
if (audioManager.requestAudioFocus(
null,
this,
AudioManager.STREAM_VOICE_CALL,
AudioManager.AUDIOFOCUS_GAIN)
== AudioManager.AUDIOFOCUS_REQUEST_FAILED) {

View File

@@ -0,0 +1,231 @@
/*
* Copyright @ 2018-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.app.Activity;
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;
import com.facebook.react.bridge.ReadableMap;
import com.rnimmersive.RNImmersiveModule;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.WeakHashMap;
/**
* Base class for all views which are backed by a React Native view.
*/
public abstract class BaseReactView<ListenerT>
extends FrameLayout {
/**
* Background color used by {@code BaseReactView} and the React Native root
* view.
*/
protected static int BACKGROUND_COLOR = 0xFF111111;
/**
* The collection of all existing {@code BaseReactView}s. Used to find the
* {@code BaseReactView} when delivering events coming from
* {@link ExternalAPIModule}.
*/
static final Set<BaseReactView> views
= Collections.newSetFromMap(new WeakHashMap<BaseReactView, Boolean>());
/**
* Finds a {@code BaseReactView} which matches a specific external API
* scope.
*
* @param externalAPIScope - The external API scope associated with the
* {@code BaseReactView} to find.
* @return The {@code BaseReactView}, if any, associated with the specified
* {@code externalAPIScope}; otherwise, {@code null}.
*/
public static BaseReactView findViewByExternalAPIScope(
String externalAPIScope) {
synchronized (views) {
for (BaseReactView view : views) {
if (view.externalAPIScope.equals(externalAPIScope)) {
return view;
}
}
}
return null;
}
/**
* The unique identifier of this {@code BaseReactView} within the process
* for the purposes of {@link ExternalAPIModule}. The name scope was
* inspired by postis which we use on Web for the similar purposes of the
* iframe-based external API.
*/
protected final String externalAPIScope;
/**
* The listener (e.g. {@link JitsiMeetViewListener}) instance for reporting
* events occurring in Jitsi Meet.
*/
private ListenerT listener;
/**
* React Native root view.
*/
private ReactRootView reactRootView;
public BaseReactView(@NonNull Context context) {
super(context);
setBackgroundColor(BACKGROUND_COLOR);
ReactInstanceManagerHolder.initReactInstanceManager(
((Activity) context).getApplication());
// Hook this BaseReactView into ExternalAPI.
externalAPIScope = UUID.randomUUID().toString();
synchronized (views) {
views.add(this);
}
}
/**
* Creates the {@code ReactRootView} for the given app name with the given
* props. Once created it's set as the view of this {@code FrameLayout}.
*
* @param appName - The name of the "app" (in React Native terms) to load.
* @param props - The React Component props to pass to the app.
*/
public void createReactRootView(String appName, @Nullable Bundle props) {
if (props == null) {
props = new Bundle();
}
props.putString("externalAPIScope", externalAPIScope);
if (reactRootView == null) {
reactRootView = new ReactRootView(getContext());
reactRootView.startReactApplication(
ReactInstanceManagerHolder.getReactInstanceManager(),
appName,
props);
reactRootView.setBackgroundColor(BACKGROUND_COLOR);
addView(reactRootView);
} else {
reactRootView.setAppProperties(props);
}
}
/**
* Releases the React resources (specifically the {@link ReactRootView})
* associated with this view.
*
* MUST be called when the {@link Activity} holding this view is destroyed,
* typically in the {@code onDestroy} method.
*/
public void dispose() {
if (reactRootView != null) {
removeView(reactRootView);
reactRootView.unmountReactApplication();
reactRootView = null;
}
}
/**
* Gets the listener set on this {@code BaseReactView}.
*
* @return The listener set on this {@code BaseReactView}.
*/
public ListenerT getListener() {
return listener;
}
/**
* Abstract method called by {@link ExternalAPIModule} when an event is
* received for this view.
*
* @param name - The name of the event.
* @param data - The details of the event associated with/specific to the
* specified {@code name}.
*/
public abstract void onExternalAPIEvent(String name, ReadableMap data);
protected void onExternalAPIEvent(
Map<String, Method> listenerMethods,
String name, ReadableMap data) {
ListenerT listener = getListener();
if (listener != null) {
ListenerUtils.runListenerMethod(
listener, listenerMethods, name, data);
}
}
/**
* Called when the window containing this view gains or loses focus.
*
* @param hasFocus If the window of this view now has focus, {@code true};
* otherwise, {@code false}.
*/
@Override
public void onWindowFocusChanged(boolean hasFocus) {
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);
}
}
}
/**
* Sets a specific listener on this {@code BaseReactView}.
*
* @param listener The listener to set on this {@code BaseReactView}.
*/
public void setListener(ListenerT listener) {
this.listener = listener;
}
}

View File

@@ -24,8 +24,6 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.AudioManager;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
/**
@@ -55,12 +53,6 @@ class BluetoothHeadsetMonitor {
*/
private boolean headsetAvailable = false;
/**
* {@link Handler} for running all operations on the main thread.
*/
private final Handler mainThreadHandler
= new Handler(Looper.getMainLooper());
/**
* Helper for running Bluetooth operations on the main thread.
*/
@@ -200,6 +192,6 @@ class BluetoothHeadsetMonitor {
* {@link AudioModeModule#onAudioDeviceChange()} callback.
*/
private void updateDevices() {
mainThreadHandler.post(updateDevicesRunnable);
audioModeModule.runInAudioThread(updateDevicesRunnable);
}
}

View File

@@ -16,83 +16,24 @@
package org.jitsi.meet.sdk;
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.ReadableMap;
import com.facebook.react.bridge.ReadableMapKeySetIterator;
import org.jitsi.meet.sdk.JitsiMeetView;
import org.jitsi.meet.sdk.JitsiMeetViewListener;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.regex.Pattern;
/**
* Module implementing a simple API to enable a proximity sensor-controlled
* wake lock. When the lock is held, if the proximity sensor detects a nearby
* object it will dim the screen and disable touch controls. The functionality
* is used with the conference audio-only mode.
* Module implementing an API for sending events from JavaScript to native code.
*/
class ExternalAPIModule extends ReactContextBaseJavaModule {
/**
* The {@code Method}s of {@code JitsiMeetViewListener} by event name i.e.
* redux action types.
*/
private static final Map<String, Method> JITSI_MEET_VIEW_LISTENER_METHODS
= new HashMap<>();
class ExternalAPIModule
extends ReactContextBaseJavaModule {
static {
// Figure out the mapping between the JitsiMeetViewListener methods
// and the events i.e. redux action types.
Pattern onPattern = Pattern.compile("^on[A-Z]+");
Pattern camelcasePattern = Pattern.compile("([a-z0-9]+)([A-Z0-9]+)");
for (Method method : JitsiMeetViewListener.class.getDeclaredMethods()) {
// * The method must be public (because it is declared by an
// interface).
// * The method must be/return void.
if (!Modifier.isPublic(method.getModifiers())
|| !Void.TYPE.equals(method.getReturnType())) {
continue;
}
// * The method name must start with "on" followed by a
// capital/uppercase letter (in agreement with the camelcase
// coding style customary to Java in general and the projects of
// the Jitsi community in particular).
String name = method.getName();
if (!onPattern.matcher(name).find()) {
continue;
}
// * The method must accept/have exactly 1 parameter of a type
// assignable from HashMap.
Class<?>[] parameterTypes = method.getParameterTypes();
if (parameterTypes.length != 1
|| !parameterTypes[0].isAssignableFrom(HashMap.class)) {
continue;
}
// Convert the method name to an event name.
name
= camelcasePattern.matcher(name.substring(2))
.replaceAll("$1_$2")
.toUpperCase(Locale.ROOT);
JITSI_MEET_VIEW_LISTENER_METHODS.put(name, method);
}
}
private static final String TAG = ExternalAPIModule.class.getSimpleName();
/**
* Initializes a new module instance. There shall be a single instance of
* this module throughout the lifetime of the application.
* this module throughout the lifetime of the app.
*
* @param reactContext the {@link ReactApplicationContext} where this module
* is created.
@@ -112,7 +53,8 @@ class ExternalAPIModule extends ReactContextBaseJavaModule {
}
/**
* Dispatches an event that occurred on JavaScript to the view's listener.
* Dispatches an event that occurred on the JavaScript side of the SDK to
* the specified {@link BaseReactView}'s listener.
*
* @param name The name of the event.
* @param data The details/specifics of the event to send determined
@@ -123,50 +65,15 @@ class ExternalAPIModule extends ReactContextBaseJavaModule {
public void sendEvent(String name, ReadableMap data, String scope) {
// The JavaScript App needs to provide uniquely identifying information
// to the native ExternalAPI module so that the latter may match the
// former to the native JitsiMeetView which hosts it.
JitsiMeetView view = JitsiMeetView.findViewByExternalAPIScope(scope);
// former to the native BaseReactView which hosts it.
BaseReactView view = BaseReactView.findViewByExternalAPIScope(scope);
if (view == null) {
return;
}
JitsiMeetViewListener listener = view.getListener();
if (listener == null) {
return;
}
Method method = JITSI_MEET_VIEW_LISTENER_METHODS.get(name);
if (method != null) {
if (view != null) {
try {
method.invoke(listener, toHashMap(data));
} catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
view.onExternalAPIEvent(name, data);
} catch(Exception e) {
Log.e(TAG, "onExternalAPIEvent: error sending event", e);
}
}
}
/**
* Initializes a new {@code HashMap} instance with the key-value
* associations of a specific {@code ReadableMap}.
*
* @param readableMap the {@code ReadableMap} specifying the key-value
* associations with which the new {@code HashMap} instance is to be
* initialized.
* @return a new {@code HashMap} instance initialized with the key-value
* associations of the specified {@code readableMap}.
*/
private HashMap<String, Object> toHashMap(ReadableMap readableMap) {
HashMap<String, Object> hashMap = new HashMap<>();
for (ReadableMapKeySetIterator i = readableMap.keySetIterator();
i.hasNextKey();) {
String key = i.nextKey();
hashMap.put(key, readableMap.getString(key));
}
return hashMap;
}
}

View File

@@ -17,7 +17,6 @@
package org.jitsi.meet.sdk;
import android.content.Intent;
import android.content.res.Configuration;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
@@ -28,6 +27,7 @@ import android.view.KeyEvent;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
import com.facebook.react.modules.core.PermissionListener;
import java.net.URL;
@@ -42,7 +42,9 @@ import java.net.URL;
* hooked to the React Native subsystem via proxy calls through the
* {@code JitsiMeetView} static methods.
*/
public class JitsiMeetActivity extends AppCompatActivity {
public class JitsiMeetActivity
extends AppCompatActivity implements JitsiMeetActivityInterface {
/**
* 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.
@@ -96,25 +98,6 @@ public class JitsiMeetActivity extends AppCompatActivity {
return view == null ? defaultURL : view.getDefaultURL();
}
/**
*
* @see JitsiMeetView#getPictureInPictureEnabled()
*/
public boolean getPictureInPictureEnabled() {
return
view == null
? pictureInPictureEnabled
: view.getPictureInPictureEnabled();
}
/**
*
* @see JitsiMeetView#getWelcomePageEnabled()
*/
public boolean getWelcomePageEnabled() {
return view == null ? welcomePageEnabled : view.getWelcomePageEnabled();
}
/**
* Initializes the {@link #view} of this {@code JitsiMeetActivity} with a
* new {@link JitsiMeetView} instance.
@@ -123,6 +106,11 @@ public class JitsiMeetActivity extends AppCompatActivity {
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);
}
@@ -145,11 +133,28 @@ public class JitsiMeetActivity extends AppCompatActivity {
}
view.setWelcomePageEnabled(welcomePageEnabled);
view.loadURL(null);
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.
@@ -170,12 +175,17 @@ public class JitsiMeetActivity extends AppCompatActivity {
if (Settings.canDrawOverlays(this)) {
initializeContentView();
}
return;
}
ReactActivityLifecycleCallbacks.onActivityResult(
this, requestCode, resultCode, data);
}
@Override
public void onBackPressed() {
if (!JitsiMeetView.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
@@ -218,7 +228,7 @@ public class JitsiMeetActivity extends AppCompatActivity {
view = null;
}
JitsiMeetView.onHostDestroy(this);
ReactActivityLifecycleCallbacks.onHostDestroy(this);
}
// ReactAndroid/src/main/java/com/facebook/react/ReactActivity.java
@@ -229,7 +239,7 @@ public class JitsiMeetActivity extends AppCompatActivity {
if (!super.onKeyUp(keyCode, event)
&& BuildConfig.DEBUG
&& (reactInstanceManager
= JitsiMeetView.getReactInstanceManager())
= ReactInstanceManagerHolder.getReactInstanceManager())
!= null
&& keyCode == KeyEvent.KEYCODE_MENU) {
reactInstanceManager.showDevOptionsDialog();
@@ -240,7 +250,29 @@ public class JitsiMeetActivity extends AppCompatActivity {
@Override
public void onNewIntent(Intent intent) {
JitsiMeetView.onNewIntent(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;
if (Intent.ACTION_VIEW.equals(intent.getAction())
&& (uri = intent.getData()) != null
&& JitsiMeetView.loadURLStringInViews(uri.toString())) {
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
@@ -248,20 +280,30 @@ public class JitsiMeetActivity extends AppCompatActivity {
super.onResume();
defaultBackButtonImpl = new DefaultHardwareBackBtnHandlerImpl(this);
JitsiMeetView.onHostResume(this, defaultBackButtonImpl);
ReactActivityLifecycleCallbacks.onHostResume(this, defaultBackButtonImpl);
}
@Override
public void onStop() {
super.onStop();
JitsiMeetView.onHostPause(this);
ReactActivityLifecycleCallbacks.onHostPause(this);
defaultBackButtonImpl = null;
}
@Override
protected void onUserLeaveHint() {
JitsiMeetView.onUserLeaveHint();
if (view != null) {
view.enterPictureInPicture();
}
}
/**
* Implementation of the {@code PermissionAwareActivity} interface.
*/
@Override
public void requestPermissions(String[] permissions, int requestCode, PermissionListener listener) {
ReactActivityLifecycleCallbacks.requestPermissions(this, permissions, requestCode, listener);
}
/**

View File

@@ -0,0 +1,15 @@
package org.jitsi.meet.sdk;
import android.support.v4.app.ActivityCompat;
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
* functionality.
*/
public interface JitsiMeetActivityInterface
extends ActivityCompat.OnRequestPermissionsResultCallback,
PermissionAwareActivity {
}

View File

@@ -16,125 +16,35 @@
package org.jitsi.meet.sdk;
import android.app.Activity;
import android.app.Application;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
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.ReactInstanceManager;
import com.facebook.react.ReactRootView;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.common.LifecycleState;
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.rnimmersive.RNImmersiveModule;
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.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.WeakHashMap;
import java.util.Map;
public class JitsiMeetView
extends BaseReactView<JitsiMeetViewListener> {
public class JitsiMeetView extends FrameLayout {
/**
* Background color used by {@code JitsiMeetView} and the React Native root
* view.
* The {@code Method}s of {@code JitsiMeetViewListener} by event name i.e.
* redux action types.
*/
private static final int BACKGROUND_COLOR = 0xFF111111;
private static final Map<String, Method> LISTENER_METHODS
= ListenerUtils.mapListenerMethods(JitsiMeetViewListener.class);
/**
* The {@link Log} tag which identifies the source of the log messages of
* {@code JitsiMeetView}.
*/
private final static String TAG = JitsiMeetView.class.getSimpleName();
/**
* React Native bridge. The instance manager allows embedding applications
* to create multiple root views off the same JavaScript bundle.
*/
private static ReactInstanceManager reactInstanceManager;
private static final Set<JitsiMeetView> views
= Collections.newSetFromMap(new WeakHashMap<JitsiMeetView, Boolean>());
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.net.NAT64AddrInfoModule(reactContext)
);
}
public static JitsiMeetView findViewByExternalAPIScope(
String externalAPIScope) {
synchronized (views) {
for (JitsiMeetView view : views) {
if (view.externalAPIScope.equals(externalAPIScope)) {
return view;
}
}
}
return null;
}
// XXX Strictly internal use only (at the time of this writing)!
static ReactInstanceManager getReactInstanceManager() {
return reactInstanceManager;
}
/**
* Internal method to initialize the React Native instance manager. We
* create a single instance in order to load the JavaScript bundle a single
* time. All {@code ReactRootView} instances will be tied to the one and
* only {@code ReactInstanceManager}.
*
* @param application {@code Application} instance which is running.
*/
private static void initReactInstanceManager(Application application) {
reactInstanceManager
= ReactInstanceManager.builder()
.setApplication(application)
.setBundleAssetName("index.android.bundle")
.setJSMainModulePath("index.android")
.addPackage(new com.calendarevents.CalendarEventsPackage())
.addPackage(new com.corbt.keepawake.KCKeepAwakePackage())
.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())
.addPackage(new com.RNFetchBlob.RNFetchBlobPackage())
.addPackage(new com.rnimmersive.RNImmersivePackage())
.addPackage(new com.zmxv.RNSound.RNSoundPackage())
.addPackage(new ReactPackageAdapter() {
@Override
public List<NativeModule> createNativeModules(
ReactApplicationContext reactContext) {
return JitsiMeetView.createNativeModules(reactContext);
}
})
.setUseDeveloperSupport(BuildConfig.DEBUG)
.setInitialLifecycleState(LifecycleState.RESUMED)
.build();
}
private static final String TAG = JitsiMeetView.class.getSimpleName();
/**
* Loads a specific URL {@code String} in all existing
@@ -146,147 +56,19 @@ public class JitsiMeetView extends FrameLayout {
* at least one {@code JitsiMeetView}, then {@code true}; otherwise,
* {@code false}.
*/
private static boolean loadURLStringInViews(String urlString) {
public static boolean loadURLStringInViews(String urlString) {
boolean loaded = false;
synchronized (views) {
if (!views.isEmpty()) {
for (JitsiMeetView view : views) {
view.loadURLString(urlString);
for (BaseReactView view : views) {
if (view instanceof JitsiMeetView) {
((JitsiMeetView)view).loadURLString(urlString);
loaded = true;
}
return true;
}
}
return false;
}
/**
* Activity lifecycle method which should be called from
* {@code Activity.onBackPressed} so we can do the required internal
* processing.
*
* @return {@code true} if the back-press was processed; {@code false},
* otherwise. If {@code false}, the application should call the parent's
* implementation.
*/
public static boolean onBackPressed() {
if (reactInstanceManager == null) {
return false;
} else {
reactInstanceManager.onBackPressed();
return true;
}
}
/**
* Activity lifecycle method which should be called from
* {@code Activity.onDestroy} so we can do the required internal
* processing.
*
* @param activity {@code Activity} being destroyed.
*/
public static void onHostDestroy(Activity activity) {
if (reactInstanceManager != null) {
reactInstanceManager.onHostDestroy(activity);
}
}
/**
* Activity lifecycle method which should be called from
* {@code Activity.onPause} so we can do the required internal processing.
*
* @param activity {@code Activity} being paused.
*/
public static void onHostPause(Activity activity) {
if (reactInstanceManager != null) {
reactInstanceManager.onHostPause(activity);
}
}
/**
* 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.
*/
public static void onHostResume(Activity activity) {
onHostResume(activity, new DefaultHardwareBackBtnHandlerImpl(activity));
}
/**
* 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 {@code DefaultHardwareBackBtnHandler} to
* handle invoking the back button if no {@code JitsiMeetView} handles it.
*/
public static void onHostResume(
Activity activity,
DefaultHardwareBackBtnHandler defaultBackButtonImpl) {
if (reactInstanceManager != null) {
reactInstanceManager.onHostResume(activity, defaultBackButtonImpl);
}
}
/**
* Activity lifecycle method which should be called from
* {@code Activity.onNewIntent} so we can do the required internal
* processing. Note that this is only needed if the activity's "launchMode"
* was set to "singleTask". This is required for deep linking to work once
* the application is already running.
*
* @param intent {@code Intent} instance which was received.
*/
public static 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;
if (Intent.ACTION_VIEW.equals(intent.getAction())
&& (uri = intent.getData()) != null
&& loadURLStringInViews(uri.toString())) {
return;
}
if (reactInstanceManager != null) {
reactInstanceManager.onNewIntent(intent);
}
}
/**
* Activity lifecycle method which should be called from
* {@code Activity.onUserLeaveHint} so we can do the required internal
* processing.
*
* This is currently not mandatory.
*/
public static void onUserLeaveHint() {
sendEvent("onUserLeaveHint", null);
}
/**
* Helper function to send an event to JavaScript.
*
* @param eventName {@code String} containing the event name.
* @param params {@code WritableMap} optional ancillary data for the event.
*/
private static void sendEvent(
String eventName,
@Nullable WritableMap params) {
if (reactInstanceManager != null) {
ReactContext reactContext
= reactInstanceManager.getCurrentReactContext();
if (reactContext != null) {
reactContext
.getJSModule(
DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit(eventName, params);
}
}
return loaded;
}
/**
@@ -297,18 +79,10 @@ public class JitsiMeetView extends FrameLayout {
private URL defaultURL;
/**
* The unique identifier of this {@code JitsiMeetView} within the process
* for the purposes of {@link ExternalAPI}. The name scope was inspired by
* postis which we use on Web for the similar purposes of the iframe-based
* external API.
* The entry point into the invite feature of Jitsi Meet. The Java
* counterpart of the JavaScript {@code InviteButton}.
*/
private final String externalAPIScope;
/**
* {@link JitsiMeetViewListener} instance for reporting events occurring in
* Jitsi Meet.
*/
private JitsiMeetViewListener listener;
private final InviteController inviteController;
/**
* Whether Picture-in-Picture is enabled. If {@code null}, defaults to
@@ -318,9 +92,11 @@ public class JitsiMeetView extends FrameLayout {
private Boolean pictureInPictureEnabled;
/**
* React Native root view.
* The URL of the current conference.
*/
private ReactRootView reactRootView;
// XXX Currently, one thread writes and one thread reads, so it should be
// fine to have this field volatile without additional synchronization.
private volatile String url;
/**
* Whether the Welcome page is enabled.
@@ -330,31 +106,33 @@ public class JitsiMeetView extends FrameLayout {
public JitsiMeetView(@NonNull Context context) {
super(context);
setBackgroundColor(BACKGROUND_COLOR);
if (reactInstanceManager == null) {
initReactInstanceManager(((Activity) context).getApplication());
}
// Hook this JitsiMeetView into ExternalAPI.
externalAPIScope = UUID.randomUUID().toString();
synchronized (views) {
views.add(this);
}
// The entry point into the invite feature of Jitsi Meet. The Java
// counterpart of the JavaScript InviteButton.
inviteController = new InviteController(externalAPIScope);
}
/**
* Releases the React resources (specifically the {@link ReactRootView})
* associated with this view.
* Enters Picture-In-Picture mode, if possible. This method is designed to
* be called from the {@code Activity.onUserLeaveHint} method.
*
* This method MUST be called when the Activity holding this view is
* destroyed, typically in the {@code onDestroy} method.
* This is currently not mandatory, but if used will provide automatic
* handling of the picture in picture mode when user minimizes the app. It
* will be probably the most useful in case the app is using the welcome
* page.
*/
public void dispose() {
if (reactRootView != null) {
removeView(reactRootView);
reactRootView.unmountReactApplication();
reactRootView = null;
public void enterPictureInPicture() {
if (isPictureInPictureEnabled() && getURL() != null) {
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);
}
}
}
}
@@ -363,7 +141,7 @@ public class JitsiMeetView extends FrameLayout {
* 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:
* {@link https://meet.jit.si}
* https://meet.jit.si
*
* @return The default base {@code URL} or {@code null}.
*/
@@ -372,13 +150,29 @@ public class JitsiMeetView extends FrameLayout {
}
/**
* Gets the {@link JitsiMeetViewListener} set on this {@code JitsiMeetView}.
* 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 {@code JitsiMeetViewListener} set on this
* {@code JitsiMeetView}.
* @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}
*/
public JitsiMeetViewListener getListener() {
return listener;
public InviteController getInviteController() {
return inviteController;
}
/**
* 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}.
*/
String getURL() {
return url;
}
/**
@@ -389,11 +183,11 @@ public class JitsiMeetView extends FrameLayout {
* @return If Picture-in-Picture is enabled, {@code true}; {@code false},
* otherwise.
*/
public boolean getPictureInPictureEnabled() {
public boolean isPictureInPictureEnabled() {
return
PictureInPictureModule.isPictureInPictureSupported()
&& (pictureInPictureEnabled == null
|| pictureInPictureEnabled.booleanValue());
|| pictureInPictureEnabled);
}
/**
@@ -404,7 +198,7 @@ public class JitsiMeetView extends FrameLayout {
* @return {@code true} if the Welcome page is enabled; otherwise,
* {@code false}.
*/
public boolean getWelcomePageEnabled() {
public boolean isWelcomePageEnabled() {
return welcomePageEnabled;
}
@@ -438,13 +232,22 @@ public class JitsiMeetView extends FrameLayout {
props.putString("defaultURL", defaultURL.toString());
}
// externalAPIScope
props.putString("externalAPIScope", externalAPIScope);
// inviteController
InviteController inviteController = getInviteController();
if (inviteController != null) {
props.putBoolean(
"addPeopleEnabled",
inviteController.isAddPeopleEnabled());
props.putBoolean(
"dialOutEnabled",
inviteController.isDialOutEnabled());
}
// pictureInPictureEnabled
props.putBoolean(
"pictureInPictureEnabled",
getPictureInPictureEnabled());
isPictureInPictureEnabled());
// url
if (urlObject != null) {
@@ -465,17 +268,7 @@ public class JitsiMeetView extends FrameLayout {
// per loadURLObject: invocation.
props.putLong("timestamp", System.currentTimeMillis());
if (reactRootView == null) {
reactRootView = new ReactRootView(getContext());
reactRootView.startReactApplication(
reactInstanceManager,
"App",
props);
reactRootView.setBackgroundColor(BACKGROUND_COLOR);
addView(reactRootView);
} else {
reactRootView.setAppProperties(props);
}
createReactRootView("App", props);
}
/**
@@ -499,40 +292,48 @@ public class JitsiMeetView extends FrameLayout {
}
/**
* Called when the window containing this view gains or loses focus.
* The internal processing for the URL of the current conference set on the
* associated {@link JitsiMeetView}.
*
* @param hasFocus If the window of this view now has focus, {@code true};
* otherwise, {@code false}.
* @param eventName the name of the external API event to be processed
* @param eventData the details/specifics of the event to process determined
* by/associated with the specified {@code eventName}.
*/
private void maybeSetViewURL(String eventName, ReadableMap eventData) {
switch(eventName) {
case "CONFERENCE_WILL_JOIN":
setURL(eventData.getString("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);
}
break;
}
}
/**
* Handler for {@link ExternalAPIModule} events.
*
* @param name The name of the event.
* @param data The details/specifics of the event to send determined
* by/associated with the specified {@code name}.
*/
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
public 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
// reduce these delays do not wait for the call to be transferred to the
// UI thread.
maybeSetViewURL(name, data);
// 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(
TAG,
"RNImmersiveModule#emitImmersiveStateChangeEvent() failed!",
re);
}
}
onExternalAPIEvent(LISTENER_METHODS, name, data);
}
/**
@@ -548,17 +349,6 @@ public class JitsiMeetView extends FrameLayout {
this.defaultURL = defaultURL;
}
/**
* Sets a specific {@link JitsiMeetViewListener} on this
* {@code JitsiMeetView}.
*
* @param listener The {@code JitsiMeetViewListener} to set on this
* {@code JitsiMeetView}.
*/
public void setListener(JitsiMeetViewListener listener) {
this.listener = listener;
}
/**
* Sets whether Picture-in-Picture is enabled. Because Picture-in-Picture is
* natively supported only since certain platform versions, specifying
@@ -568,7 +358,20 @@ public class JitsiMeetView extends FrameLayout {
* {@code true}; otherwise, {@code false}.
*/
public void setPictureInPictureEnabled(boolean pictureInPictureEnabled) {
this.pictureInPictureEnabled = Boolean.valueOf(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;
}
/**

View File

@@ -22,7 +22,9 @@ 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 {
public abstract class JitsiMeetViewAdapter
implements JitsiMeetViewListener {
@Override
public void onConferenceFailed(Map<String, Object> data) {
}

View File

@@ -0,0 +1,166 @@
/*
* Copyright @ 2018-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 com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.ReadableMapKeySetIterator;
import com.facebook.react.bridge.UiThreadUtil;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.regex.Pattern;
/**
* Utility methods for helping with transforming {@link ExternalAPIModule}
* events into listener methods. Used with descendants of {@link BaseReactView}.
*/
public final class ListenerUtils {
/**
* Extracts the methods defined in a listener and creates a mapping of this
* form: event name -> method.
*
* @param listener - The listener whose methods we want to slurp.
* @return A mapping with event names - methods.
*/
public static Map<String, Method> mapListenerMethods(Class listener) {
Map<String, Method> methods = new HashMap<>();
// Figure out the mapping between the listener methods
// and the events i.e. redux action types.
Pattern onPattern = Pattern.compile("^on[A-Z]+");
Pattern camelcasePattern = Pattern.compile("([a-z0-9]+)([A-Z0-9]+)");
for (Method method : listener.getDeclaredMethods()) {
// * The method must be public (because it is declared by an
// interface).
// * The method must be/return void.
if (!Modifier.isPublic(method.getModifiers())
|| !Void.TYPE.equals(method.getReturnType())) {
continue;
}
// * The method name must start with "on" followed by a
// capital/uppercase letter (in agreement with the camelcase
// coding style customary to Java in general and the projects of
// the Jitsi community in particular).
String name = method.getName();
if (!onPattern.matcher(name).find()) {
continue;
}
// * The method must accept/have exactly 1 parameter of a type
// assignable from HashMap.
Class<?>[] parameterTypes = method.getParameterTypes();
if (parameterTypes.length != 1
|| !parameterTypes[0].isAssignableFrom(HashMap.class)) {
continue;
}
// Convert the method name to an event name.
name
= camelcasePattern.matcher(name.substring(2))
.replaceAll("$1_$2")
.toUpperCase(Locale.ROOT);
methods.put(name, method);
}
return methods;
}
/**
* Executes the right listener method for the given event.
* NOTE: This function will run asynchronously on the UI thread.
*
* @param listener - The listener on which the method will be called.
* @param listenerMethods - Mapping with event names and the matching
* methods.
* @param eventName - Name of the event.
* @param eventData - Data associated with the event.
*/
public static void runListenerMethod(
final Object listener,
final Map<String, Method> listenerMethods,
final String eventName,
final ReadableMap eventData) {
// Make sure listener methods are invoked on the UI thread. It
// was requested by SDK consumers.
if (UiThreadUtil.isOnUiThread()) {
runListenerMethodOnUiThread(
listener, listenerMethods, eventName, eventData);
} else {
UiThreadUtil.runOnUiThread(new Runnable() {
@Override
public void run() {
runListenerMethodOnUiThread(
listener, listenerMethods, eventName, eventData);
}
});
}
}
/**
* Helper companion for {@link ListenerUtils#runListenerMethod} which runs
* in the UI thread.
*/
private static void runListenerMethodOnUiThread(
Object listener,
Map<String, Method> listenerMethods,
String eventName,
ReadableMap eventData) {
UiThreadUtil.assertOnUiThread();
Method method = listenerMethods.get(eventName);
if (method != null) {
try {
method.invoke(listener, toHashMap(eventData));
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
}
}
}
/**
* Initializes a new {@code HashMap} instance with the key-value
* associations of a specific {@code ReadableMap}.
*
* @param readableMap the {@code ReadableMap} specifying the key-value
* associations with which the new {@code HashMap} instance is to be
* initialized.
* @return a new {@code HashMap} instance initialized with the key-value
* associations of the specified {@code readableMap}.
*/
private static HashMap<String, Object> toHashMap(ReadableMap readableMap) {
HashMap<String, Object> hashMap = new HashMap<>();
for (ReadableMapKeySetIterator i = readableMap.keySetIterator();
i.hasNextKey();) {
String key = i.nextKey();
hashMap.put(key, readableMap.getString(key));
}
return hashMap;
}
}

View File

@@ -1,3 +1,19 @@
/*
* 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.app.Activity;
@@ -11,7 +27,9 @@ import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
public class PictureInPictureModule extends ReactContextBaseJavaModule {
class PictureInPictureModule
extends ReactContextBaseJavaModule {
private final static String TAG = "PictureInPicture";
static boolean isPictureInPictureSupported() {
@@ -22,6 +40,46 @@ public class PictureInPictureModule extends ReactContextBaseJavaModule {
super(reactContext);
}
/**
* Enters Picture-in-Picture (mode) for the current {@link Activity}.
* Supported on Android API >= 26 (Oreo) only.
*
* @throws IllegalStateException if {@link #isPictureInPictureSupported()}
* returns {@code false} or if {@link #getCurrentActivity()} returns
* {@code null}.
* @throws RuntimeException if
* {@link Activity#enterPictureInPictureMode(PictureInPictureParams)} fails.
* That method can also throw a {@link RuntimeException} in various cases,
* including when the activity is not visible (paused or stopped), if the
* screen is locked or if the user has an activity pinned.
*/
public void enterPictureInPicture() {
if (!isPictureInPictureSupported()) {
throw new IllegalStateException("Picture-in-Picture not supported");
}
Activity currentActivity = getCurrentActivity();
if (currentActivity == null) {
throw new IllegalStateException("No current Activity!");
}
Log.d(TAG, "Entering Picture-in-Picture");
PictureInPictureParams.Builder builder
= new PictureInPictureParams.Builder()
.setAspectRatio(new Rational(1, 1));
// https://developer.android.com/reference/android/app/Activity.html#enterPictureInPictureMode(android.app.PictureInPictureParams)
//
// The system may disallow entering picture-in-picture in various cases,
// including when the activity is not visible, if the screen is locked
// or if the user has an activity pinned.
if (!currentActivity.enterPictureInPictureMode(builder.build())) {
throw new RuntimeException("Failed to enter Picture-in-Picture");
}
}
/**
* Enters Picture-in-Picture (mode) for the current {@link Activity}.
* Supported on Android API >= 26 (Oreo) only.
@@ -31,45 +89,12 @@ public class PictureInPictureModule extends ReactContextBaseJavaModule {
*/
@ReactMethod
public void enterPictureInPicture(Promise promise) {
if (isPictureInPictureSupported()) {
Activity currentActivity = getCurrentActivity();
if (currentActivity == null) {
promise.reject(new Exception("No current Activity!"));
return;
}
Log.d(TAG, "Entering Picture-in-Picture");
PictureInPictureParams.Builder builder
= new PictureInPictureParams.Builder()
.setAspectRatio(new Rational(1, 1));
Throwable error;
// https://developer.android.com/reference/android/app/Activity.html#enterPictureInPictureMode(android.app.PictureInPictureParams)
//
// The system may disallow entering picture-in-picture in various
// cases, including when the activity is not visible, if the screen
// is locked or if the user has an activity pinned.
try {
error
= currentActivity.enterPictureInPictureMode(builder.build())
? null
: new Exception("Failed to enter Picture-in-Picture");
} catch (RuntimeException re) {
error = re;
}
if (error == null) {
promise.resolve(null);
} else {
promise.reject(error);
}
return;
try {
enterPictureInPicture();
promise.resolve(null);
} catch (RuntimeException re) {
promise.reject(re);
}
promise.reject(new Exception("Picture-in-Picture not supported"));
}
@Override

View File

@@ -31,7 +31,9 @@ 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.
*/
class ProximityModule extends ReactContextBaseJavaModule {
class ProximityModule
extends ReactContextBaseJavaModule {
/**
* The name of {@code ProximityModule} to be used in the React Native
* bridge.

View File

@@ -0,0 +1,195 @@
/*
* Copyright @ 2018-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.annotation.TargetApi;
import android.app.Activity;
import android.content.Intent;
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;
/**
* Helper class to encapsulate the work which needs to be done on
* {@link Activity} lifecycle methods in order for the React side to be aware of
* it.
*/
public class ReactActivityLifecycleCallbacks {
/**
* {@link Activity} lifecycle method which should be called from
* {@code Activity#onActivityResult} so we are notified about results of external intents
* started/finished.
*
* @param activity {@code Activity} activity from where the result comes from.
* @param requestCode {@code int} code of the request.
* @param resultCode {@code int} code of the result.
* @param data {@code Intent} the intent of the activity.
*/
public static void onActivityResult(
Activity activity,
int requestCode,
int resultCode,
Intent data) {
ReactInstanceManager reactInstanceManager
= ReactInstanceManagerHolder.getReactInstanceManager();
if (reactInstanceManager != null) {
reactInstanceManager.onActivityResult(activity, requestCode, resultCode, data);
}
}
/**
* 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
* processing.
*
* @return {@code true} if the back-press was processed; {@code false},
* otherwise. If {@code false}, the application should call the
* {@code super}'s implementation.
*/
public static boolean onBackPressed() {
ReactInstanceManager reactInstanceManager
= ReactInstanceManagerHolder.getReactInstanceManager();
if (reactInstanceManager == null) {
return false;
} else {
reactInstanceManager.onBackPressed();
return true;
}
}
/**
* {@link Activity} lifecycle method which should be called from
* {@code Activity#onDestroy} so we can do the required internal
* processing.
*
* @param activity {@code Activity} being destroyed.
*/
public static void onHostDestroy(Activity activity) {
ReactInstanceManager reactInstanceManager
= ReactInstanceManagerHolder.getReactInstanceManager();
if (reactInstanceManager != null) {
reactInstanceManager.onHostDestroy(activity);
}
}
/**
* {@link Activity} lifecycle method which should be called from
* {@code Activity#onPause} so we can do the required internal processing.
*
* @param activity {@code Activity} being paused.
*/
public static void onHostPause(Activity activity) {
ReactInstanceManager reactInstanceManager
= ReactInstanceManagerHolder.getReactInstanceManager();
if (reactInstanceManager != null) {
reactInstanceManager.onHostPause(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.
*/
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);
}
if (permissionsCallback != null) {
permissionsCallback.invoke();
permissionsCallback = null;
}
}
/**
* {@link Activity} lifecycle method which should be called from
* {@code Activity#onNewIntent} so we can do the required internal
* processing. Note that this is only needed if the activity's "launchMode"
* was set to "singleTask". This is required for deep linking to work once
* the application is already running.
*
* @param intent {@code Intent} instance which was received.
*/
public static void onNewIntent(Intent intent) {
ReactInstanceManager reactInstanceManager
= ReactInstanceManagerHolder.getReactInstanceManager();
if (reactInstanceManager != null) {
reactInstanceManager.onNewIntent(intent);
}
}
public static void onRequestPermissionsResult(
final int requestCode,
final String[] permissions,
final int[] grantResults) {
CalendarEventsPackage.onRequestPermissionsResult(
requestCode,
permissions,
grantResults);
permissionsCallback = new Callback() {
@Override
public void invoke(Object... args) {
if (permissionListener != null
&& permissionListener.onRequestPermissionsResult(requestCode, permissions, grantResults)) {
permissionListener = null;
}
}
};
}
@TargetApi(Build.VERSION_CODES.M)
public static void requestPermissions(Activity activity, String[] permissions, int requestCode, PermissionListener listener) {
permissionListener = listener;
activity.requestPermissions(permissions, requestCode);
}
}

View File

@@ -0,0 +1,43 @@
/*
* 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

@@ -0,0 +1,148 @@
/*
* 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.app.Application;
import android.support.annotation.Nullable;
import com.facebook.react.ReactInstanceManager;
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 java.util.Arrays;
import java.util.List;
class ReactInstanceManagerHolder {
/**
* React Native bridge. The instance manager allows embedding applications
* to create multiple root views off the same JavaScript bundle.
*/
private static ReactInstanceManager reactInstanceManager;
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)
);
}
/**
* Helper function to send an event to JavaScript.
*
* @param eventName {@code String} containing the event name.
* @param data {@code Object} optional ancillary data for the event.
*/
public static boolean emitEvent(
String eventName,
@Nullable Object data) {
ReactInstanceManager reactInstanceManager
= ReactInstanceManagerHolder.getReactInstanceManager();
if (reactInstanceManager != null) {
ReactContext reactContext
= reactInstanceManager.getCurrentReactContext();
return
reactContext != null
&& ReactContextUtils.emitEvent(
reactContext,
eventName,
data);
}
return false;
}
/**
* Finds a native React module for given class.
*
* @param nativeModuleClass the native module's class for which an instance
* is to be retrieved from the {@link #reactInstanceManager}.
* @param <T> the module's type.
* @return {@link NativeModule} instance for given interface type or
* {@code null} if no instance for this interface is available, or if
* {@link #reactInstanceManager} has not been initialized yet.
*/
static <T extends NativeModule> T getNativeModule(
Class<T> nativeModuleClass) {
ReactContext reactContext
= reactInstanceManager != null
? reactInstanceManager.getCurrentReactContext() : null;
return reactContext != null
? reactContext.getNativeModule(nativeModuleClass) : null;
}
static ReactInstanceManager getReactInstanceManager() {
return reactInstanceManager;
}
/**
* Internal method to initialize the React Native instance manager. We
* create a single instance in order to load the JavaScript bundle a single
* time. All {@code ReactRootView} instances will be tied to the one and
* only {@code ReactInstanceManager}.
*
* @param application {@code Application} instance which is running.
*/
static void initReactInstanceManager(Application application) {
if (reactInstanceManager != null) {
return;
}
reactInstanceManager
= ReactInstanceManager.builder()
.setApplication(application)
.setBundleAssetName("index.android.bundle")
.setJSMainModulePath("index.android")
.addPackage(new co.apptailor.googlesignin.RNGoogleSigninPackage())
.addPackage(new com.BV.LinearGradient.LinearGradientPackage())
.addPackage(new com.calendarevents.CalendarEventsPackage())
.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())
.addPackage(new com.rnimmersive.RNImmersivePackage())
.addPackage(new com.zmxv.RNSound.RNSoundPackage())
.addPackage(new ReactPackageAdapter() {
@Override
public List<NativeModule> createNativeModules(
ReactApplicationContext reactContext) {
return
ReactInstanceManagerHolder.createNativeModules(
reactContext);
}
})
.setUseDeveloperSupport(BuildConfig.DEBUG)
.setInitialLifecycleState(LifecycleState.RESUMED)
.build();
}
}

View File

@@ -25,7 +25,9 @@ import com.facebook.react.uimanager.ViewManager;
import java.util.Collections;
import java.util.List;
public class ReactPackageAdapter implements ReactPackage {
class ReactPackageAdapter
implements ReactPackage {
@Override
public List<NativeModule> createNativeModules(
ReactApplicationContext reactContext) {

View File

@@ -19,8 +19,6 @@ package org.jitsi.meet.sdk;
import android.content.Context;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import com.facebook.react.bridge.Promise;
@@ -36,14 +34,18 @@ import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Enumeration;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Module exposing WiFi statistics.
*
* Gathers rssi, signal in percentage, timestamp and the addresses
* of the wifi device.
* Gathers rssi, signal in percentage, timestamp and the addresses of the wifi
* device.
*/
class WiFiStatsModule extends ReactContextBaseJavaModule {
class WiFiStatsModule
extends ReactContextBaseJavaModule {
/**
* The name of {@code WiFiStatsModule} to be used in the React Native
* bridge.
@@ -56,17 +58,16 @@ class WiFiStatsModule extends ReactContextBaseJavaModule {
static final String TAG = MODULE_NAME;
/**
* The scale used for the signal value.
* A level of the signal, given in the range
* of 0 to SIGNAL_LEVEL_SCALE-1 (both inclusive).
* The scale used for the signal value. A level of the signal, given in the
* range of 0 to SIGNAL_LEVEL_SCALE-1 (both inclusive).
*/
public final static int SIGNAL_LEVEL_SCALE = 101;
/**
* {@link Handler} for running all operations on the main thread.
* {@link ExecutorService} for running all operations on a dedicated thread.
*/
private final Handler mainThreadHandler
= new Handler(Looper.getMainLooper());
private static final ExecutorService executor
= Executors.newSingleThreadExecutor();
/**
* Initializes a new module instance. There shall be a single instance of
@@ -119,7 +120,6 @@ class WiFiStatsModule extends ReactContextBaseJavaModule {
@Override
public void run() {
try {
Context context
= getReactApplicationContext().getApplicationContext();
WifiManager wifiManager
@@ -203,6 +203,6 @@ class WiFiStatsModule extends ReactContextBaseJavaModule {
}
}
};
mainThreadHandler.post(r);
executor.execute(r);
}
}

View File

@@ -0,0 +1,184 @@
package org.jitsi.meet.sdk.dropbox;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.text.TextUtils;
import com.dropbox.core.DbxException;
import com.dropbox.core.DbxRequestConfig;
import com.dropbox.core.v2.DbxClientV2;
import com.dropbox.core.v2.users.FullAccount;
import com.dropbox.core.v2.users.SpaceAllocation;
import com.dropbox.core.v2.users.SpaceUsage;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.LifecycleEventListener;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
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 java.util.HashMap;
import java.util.Map;
/**
* Implements the react-native module for the dropbox integration.
*/
public class Dropbox
extends ReactContextBaseJavaModule
implements LifecycleEventListener {
private String appKey;
private String clientId;
private final boolean isEnabled;
private Promise promise;
public Dropbox(ReactApplicationContext reactContext) {
super(reactContext);
appKey
= reactContext.getString(
org.jitsi.meet.sdk.R.string.dropbox_app_key);
isEnabled = !TextUtils.isEmpty(appKey);
clientId = generateClientId();
reactContext.addLifecycleEventListener(this);
}
/**
* Executes the dropbox auth flow.
*
* @param promise The promise used to return the result of the auth flow.
*/
@ReactMethod
public void authorize(final Promise promise) {
if (isEnabled) {
Auth.startOAuth2Authentication(this.getCurrentActivity(), appKey);
this.promise = promise;
} else {
promise.reject(
new Exception("Dropbox integration isn't configured."));
}
}
/**
* Generate a client identifier for the dropbox sdk.
*
* @returns a client identifier for the dropbox sdk.
* @see {https://dropbox.github.io/dropbox-sdk-java/api-docs/v3.0.x/com/dropbox/core/DbxRequestConfig.html#getClientIdentifier--}
*/
private String generateClientId() {
Context context = getReactApplicationContext();
PackageManager packageManager = context.getPackageManager();
ApplicationInfo applicationInfo = null;
PackageInfo packageInfo = null;
try {
String packageName = context.getPackageName();
applicationInfo = packageManager.getApplicationInfo(packageName, 0);
packageInfo = packageManager.getPackageInfo(packageName, 0);
} catch (PackageManager.NameNotFoundException e) {
}
String applicationLabel
= applicationInfo == null
? "JitsiMeet"
: packageManager.getApplicationLabel(applicationInfo).toString()
.replaceAll("\\s", "");
String version = packageInfo == null ? "dev" : packageInfo.versionName;
return applicationLabel + "/" + version;
}
@Override
public Map<String, Object> getConstants() {
Map<String, Object> constants = new HashMap<>();
constants.put("ENABLED", isEnabled);
return constants;
}
/**
* Resolves the current user dropbox display name.
*
* @param token A dropbox access token.
* @param promise The promise used to return the result of the auth flow.
*/
@ReactMethod
public void getDisplayName(final String token, final Promise promise) {
DbxRequestConfig config = DbxRequestConfig.newBuilder(clientId).build();
DbxClientV2 client = new DbxClientV2(config, token);
// Get current account info
try {
FullAccount account = client.users().getCurrentAccount();
promise.resolve(account.getName().getDisplayName());
} catch (DbxException e) {
promise.reject(e);
}
}
@Override
public String getName() {
return "Dropbox";
}
/**
* Resolves the current user space usage.
*
* @param token A dropbox access token.
* @param promise The promise used to return the result of the auth flow.
*/
@ReactMethod
public void getSpaceUsage(final String token, final Promise promise) {
DbxRequestConfig config = DbxRequestConfig.newBuilder(clientId).build();
DbxClientV2 client = new DbxClientV2(config, token);
try {
SpaceUsage spaceUsage = client.users().getSpaceUsage();
WritableMap map = Arguments.createMap();
map.putString("used", String.valueOf(spaceUsage.getUsed()));
SpaceAllocation allocation = spaceUsage.getAllocation();
long allocated = 0;
if (allocation.isIndividual()) {
allocated += allocation.getIndividualValue().getAllocated();
}
if (allocation.isTeam()) {
allocated += allocation.getTeamValue().getAllocated();
}
map.putString("allocated", String.valueOf(allocated));
promise.resolve(map);
} catch (DbxException e) {
promise.reject(e);
}
}
@Override
public void onHostDestroy() {}
@Override
public void onHostPause() {}
@Override
public void onHostResume() {
String token = Auth.getOAuth2Token();
if (token != null && this.promise != null) {
this.promise.resolve(token);
this.promise = null;
}
}
}

View File

@@ -0,0 +1,72 @@
/*
* Copyright @ 2018-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.incoming_call;
import android.support.annotation.NonNull;
public class IncomingCallInfo {
/**
* URL for the caller avatar.
*/
private final String callerAvatarURL;
/**
* Caller's name.
*/
private final String callerName;
/**
* Whether this is a regular call or a video call.
*/
private final boolean hasVideo;
public IncomingCallInfo(
@NonNull String callerName,
@NonNull String callerAvatarURL,
boolean hasVideo) {
this.callerName = callerName;
this.callerAvatarURL = callerAvatarURL;
this.hasVideo = hasVideo;
}
/**
* Gets the caller's avatar URL.
*
* @return - The URL as a string.
*/
public String getCallerAvatarURL() {
return callerAvatarURL;
}
/**
* Gets the caller's name.
*
* @return - The caller's name.
*/
public String getCallerName() {
return callerName;
}
/**
* Gets whether the call is a video call or not.
*
* @return - {@code true} if this call has video; {@code false}, otherwise.
*/
public boolean hasVideo() {
return hasVideo;
}
}

View File

@@ -0,0 +1,73 @@
/*
* Copyright @ 2018-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.incoming_call;
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.NonNull;
import com.facebook.react.bridge.ReadableMap;
import org.jitsi.meet.sdk.BaseReactView;
import org.jitsi.meet.sdk.ListenerUtils;
import java.lang.reflect.Method;
import java.util.Map;
public class IncomingCallView
extends BaseReactView<IncomingCallViewListener> {
/**
* The {@code Method}s of {@code JitsiMeetViewListener} by event name i.e.
* redux action types.
*/
private static final Map<String, Method> LISTENER_METHODS
= ListenerUtils.mapListenerMethods(IncomingCallViewListener.class);
public IncomingCallView(@NonNull Context context) {
super(context);
}
/**
* Handler for {@link ExternalAPIModule} events.
*
* @param name The name of the event.
* @param data The details/specifics of the event to send determined
* by/associated with the specified {@code name}.
*/
@Override
public void onExternalAPIEvent(String name, ReadableMap data) {
onExternalAPIEvent(LISTENER_METHODS, name, data);
}
/**
* Sets the information for the incoming call this {@code IncomingCallView}
* represents.
*
* @param callInfo - {@link IncomingCallInfo} object representing the caller
* information.
*/
public void setIncomingCallInfo(IncomingCallInfo callInfo) {
Bundle props = new Bundle();
props.putString("callerAvatarURL", callInfo.getCallerAvatarURL());
props.putString("callerName", callInfo.getCallerName());
props.putBoolean("hasVideo", callInfo.hasVideo());
createReactRootView("IncomingCallApp", props);
}
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright @ 2018-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.incoming_call;
import java.util.Map;
/**
* Interface for listening to events coming from Jitsi Meet, related to
* {@link IncomingCallView}.
*/
public interface IncomingCallViewListener {
/**
* Called when the user presses the "Answer" button on the
* {@link IncomingCallView}.
*
* @param data - Unused at the moment.
*/
void onIncomingCallAnswered(Map<String, Object> data);
/**
* Called when the user presses the "Decline" button on the
* {@link IncomingCallView}.
*
* @param data - Unused at the moment.
*/
void onIncomingCallDeclined(Map<String, Object> data);
}

View File

@@ -0,0 +1,211 @@
/*
* 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

@@ -0,0 +1,56 @@
/*
* 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

@@ -0,0 +1,265 @@
/*
* 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

@@ -0,0 +1,29 @@
/*
* 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;
public interface InviteControllerListener {
/**
* Called when the add user button is tapped.
*
* @param addPeopleController {@code AddPeopleController} scoped for this
* user invite flow. The {@code AddPeopleController} is used to start user
* queries and accepts an {@code AddPeopleControllerListener} for receiving
* user query responses.
*/
void beginAddPeople(AddPeopleController addPeopleController);
}

View File

@@ -0,0 +1,171 @@
/*
* 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

@@ -32,7 +32,9 @@ import java.net.UnknownHostException;
* [1]: https://tools.ietf.org/html/rfc6146
* [2]: https://tools.ietf.org/html/rfc6052
*/
public class NAT64AddrInfoModule extends ReactContextBaseJavaModule {
public class NAT64AddrInfoModule
extends ReactContextBaseJavaModule {
/**
* The host for which the module wil try to resolve both IPv4 and IPv6
* addresses in order to figure out the NAT64 prefix.

View File

@@ -1,3 +1,4 @@
<resources>
<string name="app_name">Jitsi Meet SDK</string>
<string name="dropbox_app_key"></string>
</resources>

View File

@@ -3,12 +3,16 @@ rootProject.name = 'jitsi-meet'
include ':app', ':sdk'
include ':react-native-background-timer'
project(':react-native-background-timer').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-background-timer/android')
include ':react-native-fetch-blob'
project(':react-native-fetch-blob').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-fetch-blob/android')
include ':react-native-fast-image'
project(':react-native-fast-image').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-fast-image/android')
include ':react-native-google-signin'
project(':react-native-google-signin').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-google-signin/android')
include ':react-native-immersive'
project(':react-native-immersive').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-immersive/android')
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'

2
app.js
View File

@@ -15,7 +15,6 @@ import conference from './conference';
import API from './modules/API';
import keyboardshortcut from './modules/keyboardshortcut/keyboardshortcut';
import remoteControl from './modules/remotecontrol/RemoteControl';
import settings from './modules/settings/Settings';
import translation from './modules/translation/translation';
import UI from './modules/UI/UI';
@@ -41,7 +40,6 @@ window.APP = {
keyboardshortcut,
remoteControl,
settings,
translation,
UI
};

View File

@@ -7,49 +7,52 @@ import Recorder from './modules/recorder/Recorder';
import mediaDeviceHelper from './modules/devices/mediaDeviceHelper';
import { reportError } from './modules/util/helpers';
import * as RemoteControlEvents
from './service/remotecontrol/RemoteControlEvents';
import UIEvents from './service/UI/UIEvents';
import UIUtil from './modules/UI/util/UIUtil';
import { createTaskQueue } from './modules/util/helpers';
import * as JitsiMeetConferenceEvents from './ConferenceEvents';
import {
createDeviceChangedEvent,
createScreenSharingEvent,
createSelectParticipantFailedEvent,
createStreamSwitchDelayEvent,
createTrackMutedEvent,
initAnalytics,
sendAnalytics
} from './react/features/analytics';
import {
redirectWithStoredParams,
reloadWithStoredParams
} from './react/features/app';
import { updateRecordingState } from './react/features/recording';
import EventEmitter from 'events';
import {
AVATAR_ID_COMMAND,
AVATAR_URL_COMMAND,
EMAIL_COMMAND,
authStatusChanged,
commonUserJoinedHandling,
commonUserLeftHandling,
conferenceFailed,
conferenceJoined,
conferenceLeft,
conferenceWillJoin,
conferenceWillLeave,
dataChannelOpened,
EMAIL_COMMAND,
lockStateChanged,
onStartMutedPolicyChanged,
p2pStatusChanged,
sendLocalParticipant,
setDesktopSharingEnabled
} from './react/features/base/conference';
import { updateDeviceList } from './react/features/base/devices';
import {
isAnalyticsEnabled,
getAvailableDevices,
setAudioOutputDeviceId,
updateDeviceList
} from './react/features/base/devices';
import {
isFatalJitsiConnectionError,
JitsiConferenceErrors,
JitsiConferenceEvents,
@@ -78,12 +81,11 @@ import {
localParticipantRoleChanged,
MAX_DISPLAY_NAME_LENGTH,
participantConnectionStatusChanged,
participantJoined,
participantLeft,
participantPresenceChanged,
participantRoleChanged,
participantUpdated
} from './react/features/base/participants';
import { updateSettings } from './react/features/base/settings';
import {
createLocalTracksF,
isLocalTrackMuted,
@@ -95,6 +97,7 @@ import {
getLocationContextRoot,
getJitsiMeetGlobalNS
} from './react/features/base/util';
import { addMessage } from './react/features/chat';
import { showDesktopPicker } from './react/features/desktop-picker';
import { appendSuffix } from './react/features/display-name';
import {
@@ -107,6 +110,7 @@ import {
} from './react/features/overlay';
import { setSharedVideoStatus } from './react/features/shared-video';
import { isButtonEnabled } from './react/features/toolbox';
import { endpointMessageReceived } from './react/features/subtitles';
const logger = require('jitsi-meet-logger').getLogger(__filename);
@@ -270,6 +274,27 @@ function redirectToStaticPage(pathname) {
windowLocation.pathname = newPathname;
}
/**
* A queue for the async replaceLocalTrack action so that multiple audio
* replacements cannot happen simultaneously. This solves the issue where
* replaceLocalTrack is called multiple times with an oldTrack of null, causing
* multiple local tracks of the same type to be used.
*
* @private
* @type {Object}
*/
const _replaceLocalAudioTrackQueue = createTaskQueue();
/**
* A task queue for replacement local video tracks. This separate queue exists
* so video replacement is not blocked by audio replacement tasks in the queue
* {@link _replaceLocalAudioTrackQueue}.
*
* @private
* @type {Object}
*/
const _replaceLocalVideoTrackQueue = createTaskQueue();
/**
*
*/
@@ -370,6 +395,8 @@ class ConferenceConnector {
case JitsiConferenceErrors.FOCUS_LEFT:
case JitsiConferenceErrors.VIDEOBRIDGE_NOT_AVAILABLE:
APP.store.dispatch(conferenceWillLeave(room));
// FIXME the conference should be stopped by the library and not by
// the app. Both the errors above are unrecoverable from the library
// perspective.
@@ -398,10 +425,16 @@ class ConferenceConnector {
switch (err) {
case JitsiConferenceErrors.CHAT_ERROR:
logger.error('Chat error.', err);
if (isButtonEnabled('chat')) {
if (isButtonEnabled('chat') && !interfaceConfig.filmStripOnly) {
const [ code, msg ] = params;
APP.UI.showChatError(code, msg);
APP.store.dispatch(addMessage({
hasRead: true,
error: code,
message: msg,
timestamp: Date.now(),
type: 'error'
}));
}
break;
default:
@@ -466,6 +499,7 @@ function _connectionFailedHandler(error) {
JitsiConnectionEvents.CONNECTION_FAILED,
_connectionFailedHandler);
if (room) {
APP.store.dispatch(conferenceWillLeave(room));
room.leave();
}
}
@@ -483,8 +517,7 @@ export default {
/**
* Indicates if the desktop sharing functionality has been enabled.
* It takes into consideration {@link isDesktopSharingDisabledByConfig}
* as well as the status returned by
* It takes into consideration the status returned by
* {@link JitsiMeetJS.isDesktopSharingEnabled()}. The latter can be false
* either if the desktop sharing is not supported by the current browser
* or if it was disabled through lib-jitsi-meet specific options (check
@@ -492,19 +525,6 @@ export default {
*/
isDesktopSharingEnabled: false,
/**
* Set to <tt>true</tt> if the desktop sharing functionality has been
* explicitly disabled in the config.
*/
isDesktopSharingDisabledByConfig: false,
/**
* The text displayed when the desktop sharing button is disabled through
* the config. The value is set through
* {@link interfaceConfig.DESKTOP_SHARING_BUTTON_DISABLED_TOOLTIP}.
*/
desktopSharingDisabledTooltip: null,
/**
* The local audio track (if any).
* FIXME tracks from redux store should be the single source of truth
@@ -685,53 +705,20 @@ export default {
/**
* Open new connection and join to the conference.
* @param {object} options
* @param {string} roomName name of the conference
* @param {string} roomName - The name of the conference.
* @returns {Promise}
*/
init(options) {
this.roomName = options.roomName;
// attaches global error handler, if there is already one, respect it
if (JitsiMeetJS.getGlobalOnErrorHandler) {
const oldOnErrorHandler = window.onerror;
// eslint-disable-next-line max-params
window.onerror = (message, source, lineno, colno, error) => {
JitsiMeetJS.getGlobalOnErrorHandler(
message, source, lineno, colno, error);
if (oldOnErrorHandler) {
oldOnErrorHandler(message, source, lineno, colno, error);
}
};
const oldOnUnhandledRejection = window.onunhandledrejection;
window.onunhandledrejection = function(event) {
JitsiMeetJS.getGlobalOnErrorHandler(
null, null, null, null, event.reason);
if (oldOnUnhandledRejection) {
oldOnUnhandledRejection(event);
}
};
}
return (
JitsiMeetJS.init({
enableAnalyticsLogging: isAnalyticsEnabled(APP.store),
...config
}).then(() => {
initAnalytics(APP.store);
return this.createInitialLocalTracksAndConnect(
options.roomName, {
startAudioOnly: config.startAudioOnly,
startScreenSharing: config.startScreenSharing,
startWithAudioMuted: config.startWithAudioMuted,
startWithVideoMuted: config.startWithVideoMuted
});
})
this.createInitialLocalTracksAndConnect(
options.roomName, {
startAudioOnly: config.startAudioOnly,
startScreenSharing: config.startScreenSharing,
startWithAudioMuted: config.startWithAudioMuted,
startWithVideoMuted: config.startWithVideoMuted
})
.then(([ tracks, con ]) => {
tracks.forEach(track => {
if ((track.isAudioTrack() && this.isLocalAudioMuted())
@@ -744,7 +731,7 @@ export default {
track.mute();
}
});
logger.log('initialized with %s local tracks', tracks.length);
logger.log(`initialized with ${tracks.length} local tracks`);
this._localTracksInitialized = true;
con.addEventListener(
JitsiConnectionEvents.CONNECTION_FAILED,
@@ -752,13 +739,8 @@ export default {
APP.connection = connection = con;
// Desktop sharing related stuff:
this.isDesktopSharingDisabledByConfig
= config.disableDesktopSharing;
this.isDesktopSharingEnabled
= !this.isDesktopSharingDisabledByConfig
&& JitsiMeetJS.isDesktopSharingEnabled();
this.desktopSharingDisabledTooltip
= interfaceConfig.DESKTOP_SHARING_BUTTON_DISABLED_TOOLTIP;
= JitsiMeetJS.isDesktopSharingEnabled();
eventEmitter.emit(
JitsiMeetConferenceEvents.DESKTOP_SHARING_ENABLED_CHANGED,
this.isDesktopSharingEnabled);
@@ -901,9 +883,6 @@ export default {
return;
}
// FIXME it is possible to queue this task twice, but it's not causing
// any issues. Specifically this can happen when the previous
// get user media call is blocked on "ask user for permissions" dialog.
if (!this.localVideo && !mute) {
const maybeShowErrorDialog = error => {
showUI && APP.UI.showCameraErrorNotification(error);
@@ -1131,20 +1110,6 @@ export default {
return this._room && this._room.myUserId();
},
/**
* Indicates if recording is supported in this conference.
*/
isRecordingSupported() {
return this._room && this._room.isRecordingSupported();
},
/**
* Returns the recording state or undefined if the room is not defined.
*/
getRecordingState() {
return this._room ? this._room.getRecordingState() : undefined;
},
/**
* Will be filled with values only when config.debug is enabled.
* Its used by torture to check audio levels.
@@ -1301,14 +1266,7 @@ export default {
_getConferenceOptions() {
const options = config;
if (config.enableRecording && !config.recordingType) {
options.recordingType
= config.hosts && (typeof config.hosts.jirecon !== 'undefined')
? 'jirecon'
: 'colibri';
}
const nick = APP.settings.getDisplayName();
const nick = APP.store.getState()['features/base/settings'].displayName;
if (nick) {
options.displayName = nick;
@@ -1327,16 +1285,23 @@ export default {
* @returns {Promise}
*/
useVideoStream(newStream) {
return APP.store.dispatch(
replaceLocalTrack(this.localVideo, newStream, room))
.then(() => {
this.localVideo = newStream;
this._setSharingScreen(newStream);
if (newStream) {
APP.UI.addLocalStream(newStream);
}
this.setVideoMuteStatus(this.isLocalVideoMuted());
return new Promise((resolve, reject) => {
_replaceLocalVideoTrackQueue.enqueue(onFinish => {
APP.store.dispatch(
replaceLocalTrack(this.localVideo, newStream, room))
.then(() => {
this.localVideo = newStream;
this._setSharingScreen(newStream);
if (newStream) {
APP.UI.addLocalStream(newStream);
}
this.setVideoMuteStatus(this.isLocalVideoMuted());
})
.then(resolve)
.catch(reject)
.then(onFinish);
});
});
},
/**
@@ -1366,15 +1331,22 @@ export default {
* @returns {Promise}
*/
useAudioStream(newStream) {
return APP.store.dispatch(
replaceLocalTrack(this.localAudio, newStream, room))
.then(() => {
this.localAudio = newStream;
if (newStream) {
APP.UI.addLocalStream(newStream);
}
this.setAudioMuteStatus(this.isLocalAudioMuted());
return new Promise((resolve, reject) => {
_replaceLocalAudioTrackQueue.enqueue(onFinish => {
APP.store.dispatch(
replaceLocalTrack(this.localAudio, newStream, room))
.then(() => {
this.localAudio = newStream;
if (newStream) {
APP.UI.addLocalStream(newStream);
}
this.setAudioMuteStatus(this.isLocalAudioMuted());
})
.then(resolve)
.catch(reject)
.then(onFinish);
});
});
},
/**
@@ -1722,23 +1694,21 @@ export default {
room.on(
JitsiConferenceEvents.AUTH_STATUS_CHANGED,
(authEnabled, authLogin) =>
APP.UI.updateAuthInfo(authEnabled, authLogin));
APP.store.dispatch(authStatusChanged(authEnabled, authLogin)));
room.on(JitsiConferenceEvents.PARTCIPANT_FEATURES_CHANGED,
user => APP.UI.onUserFeaturesChanged(user));
room.on(JitsiConferenceEvents.USER_JOINED, (id, user) => {
// The logic shared between RN and web.
commonUserJoinedHandling(APP.store, room, user);
if (user.isHidden()) {
return;
}
const displayName = user.getDisplayName();
APP.store.dispatch(participantJoined({
id,
name: displayName,
role: user.getRole()
}));
logger.log('USER %s connnected', id, user);
logger.log(`USER ${id} connnected:`, user);
APP.API.notifyUserJoined(id, {
displayName,
formattedDisplayName: appendSuffix(
@@ -1751,13 +1721,20 @@ export default {
});
room.on(JitsiConferenceEvents.USER_LEFT, (id, user) => {
// The logic shared between RN and web.
commonUserLeftHandling(APP.store, room, user);
if (user.isHidden()) {
return;
}
APP.store.dispatch(participantLeft(id, user));
logger.log('USER %s LEFT', id, user);
logger.log(`USER ${id} LEFT:`, user);
APP.API.notifyUserLeft(id);
APP.UI.removeUser(id, user.getDisplayName());
APP.UI.messageHandler.participantNotification(
user.getDisplayName(),
'notify.somebody',
'disconnected',
'notify.disconnected');
APP.UI.onSharedVideoStop(id);
});
@@ -1842,88 +1819,21 @@ export default {
room.on(
JitsiConferenceEvents.PARTICIPANT_CONN_STATUS_CHANGED,
(id, connectionStatus) => {
APP.store.dispatch(participantConnectionStatusChanged(
id, connectionStatus));
(id, connectionStatus) => APP.store.dispatch(
participantConnectionStatusChanged(id, connectionStatus)));
APP.UI.participantConnectionStatusChanged(id);
});
room.on(JitsiConferenceEvents.DOMINANT_SPEAKER_CHANGED, id => {
APP.store.dispatch(dominantSpeakerChanged(id));
});
room.on(JitsiConferenceEvents.LIVE_STREAM_URL_CHANGED,
(from, liveStreamViewURL) =>
APP.store.dispatch(updateRecordingState({
liveStreamViewURL
})));
if (!interfaceConfig.filmStripOnly) {
room.on(JitsiConferenceEvents.CONNECTION_INTERRUPTED, () => {
APP.UI.markVideoInterrupted(true);
});
room.on(JitsiConferenceEvents.CONNECTION_RESTORED, () => {
APP.UI.markVideoInterrupted(false);
});
if (isButtonEnabled('chat')) {
room.on(
JitsiConferenceEvents.MESSAGE_RECEIVED,
(id, body, ts) => {
let nick = getDisplayName(id);
if (!nick) {
nick = `${
interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME} (${
id})`;
}
APP.API.notifyReceivedChatMessage({
id,
nick,
body,
ts
});
APP.UI.addMessage(id, nick, body, ts);
}
);
APP.UI.addListener(UIEvents.MESSAGE_CREATED, message => {
APP.API.notifySendingChatMessage(message);
room.sendTextMessage(message);
});
}
APP.UI.addListener(UIEvents.SELECTED_ENDPOINT, id => {
APP.API.notifyOnStageParticipantChanged(id);
try {
// do not try to select participant if there is none (we
// are alone in the room), otherwise an error will be
// thrown cause reporting mechanism is not available
// (datachannels currently)
if (room.getParticipants().length === 0) {
return;
}
room.selectParticipant(id);
} catch (e) {
sendAnalytics(createSelectParticipantFailedEvent(e));
reportError(e);
}
});
}
room.on(
JitsiConferenceEvents.DOMINANT_SPEAKER_CHANGED,
id => APP.store.dispatch(dominantSpeakerChanged(id, room)));
room.on(JitsiConferenceEvents.CONNECTION_INTERRUPTED, () => {
APP.store.dispatch(localParticipantConnectionStatusChanged(
JitsiParticipantConnectionStatus.INTERRUPTED));
APP.UI.showLocalConnectionInterrupted(true);
});
room.on(JitsiConferenceEvents.CONNECTION_RESTORED, () => {
APP.store.dispatch(localParticipantConnectionStatusChanged(
JitsiParticipantConnectionStatus.ACTIVE));
APP.UI.showLocalConnectionInterrupted(false);
});
room.on(
@@ -1933,6 +1843,7 @@ export default {
= displayName.substr(0, MAX_DISPLAY_NAME_LENGTH);
APP.store.dispatch(participantUpdated({
conference: room,
id,
name: formattedDisplayName
}));
@@ -1946,6 +1857,21 @@ export default {
APP.UI.changeDisplayName(id, formattedDisplayName);
}
);
room.on(
JitsiConferenceEvents.BOT_TYPE_CHANGED,
(id, botType) => {
APP.store.dispatch(participantUpdated({
conference: room,
id,
botType
}));
}
);
room.on(
JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
(...args) => APP.store.dispatch(endpointMessageReceived(...args)));
room.on(
JitsiConferenceEvents.LOCK_STATE_CHANGED,
@@ -1964,8 +1890,17 @@ 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'
}));
@@ -1981,16 +1916,6 @@ export default {
}
});
/* eslint-enable max-params */
room.on(
JitsiConferenceEvents.RECORDER_STATE_CHANGED,
(status, error) => {
logger.log('Received recorder status change: ', status, error);
APP.UI.updateRecordingState(status);
}
);
room.on(JitsiConferenceEvents.KICKED, () => {
APP.UI.hideStats();
APP.UI.notifyKicked();
@@ -2040,13 +1965,14 @@ export default {
}
);
APP.UI.addListener(UIEvents.EMAIL_CHANGED, this.changeLocalEmail);
APP.UI.addListener(UIEvents.EMAIL_CHANGED,
this.changeLocalEmail.bind(this));
room.addCommandListener(this.commands.defaults.EMAIL, (data, from) => {
APP.store.dispatch(participantUpdated({
conference: room,
id: from,
email: data.value
}));
APP.UI.setUserEmail(from, data.value);
});
room.addCommandListener(
@@ -2054,6 +1980,7 @@ export default {
(data, from) => {
APP.store.dispatch(
participantUpdated({
conference: room,
id: from,
avatarURL: data.value
}));
@@ -2063,6 +1990,7 @@ export default {
(data, from) => {
APP.store.dispatch(
participantUpdated({
conference: room,
id: from,
avatarID: data.value
}));
@@ -2083,13 +2011,6 @@ export default {
&& APP.UI.notifyInitiallyMuted();
});
room.on(
JitsiConferenceEvents.AVAILABLE_DEVICES_CHANGED,
(id, devices) => {
APP.UI.updateDevicesAvailability(id, devices);
}
);
room.on(
JitsiConferenceEvents.DATA_CHANNEL_OPENED, () => {
APP.store.dispatch(dataChannelOpened());
@@ -2124,13 +2045,6 @@ export default {
}));
});
/* eslint-enable max-params */
// Starts or stops the recording for the conference.
APP.UI.addListener(UIEvents.RECORDING_TOGGLED, options => {
room.toggleRecording(options);
});
APP.UI.addListener(UIEvents.AUTH_CLICKED, () => {
AuthHandler.authenticate(room);
});
@@ -2166,7 +2080,9 @@ export default {
})
.then(() => {
logger.log('switched local video device');
APP.settings.setCameraDeviceId(cameraDeviceId, true);
APP.store.dispatch(updateSettings({
cameraDeviceId
}));
})
.catch(err => {
APP.UI.showCameraErrorNotification(err);
@@ -2198,7 +2114,9 @@ export default {
.then(stream => {
this.useAudioStream(stream);
logger.log('switched local audio device');
APP.settings.setMicDeviceId(micDeviceId, true);
APP.store.dispatch(updateSettings({
micDeviceId
}));
})
.catch(err => {
APP.UI.showMicErrorNotification(err);
@@ -2206,20 +2124,6 @@ export default {
}
);
APP.UI.addListener(
UIEvents.AUDIO_OUTPUT_DEVICE_CHANGED,
audioOutputDeviceId => {
sendAnalytics(createDeviceChangedEvent('audio', 'output'));
APP.settings.setAudioOutputDeviceId(audioOutputDeviceId)
.then(() => logger.log('changed audio output device'))
.catch(err => {
logger.warn('Failed to change audio output device. '
+ 'Default or previously set audio output device '
+ 'will be used instead.', err);
});
}
);
APP.UI.addListener(UIEvents.TOGGLE_AUDIO_ONLY, audioOnly => {
// FIXME On web video track is stored both in redux and in
@@ -2351,9 +2255,10 @@ export default {
APP.store.dispatch(conferenceJoined(room));
APP.UI.mucJoined();
const displayName = APP.settings.getDisplayName();
const displayName
= APP.store.getState()['features/base/settings'].displayName;
APP.UI.changeDisplayName('localVideoContainer', displayName);
APP.API.notifyConferenceJoined(
this.roomName,
this._room.myUserId(),
@@ -2366,7 +2271,6 @@ export default {
APP.store.getState(), this._room.myUserId())
}
);
APP.UI.markVideoInterrupted(false);
},
/**
@@ -2391,43 +2295,43 @@ export default {
/**
* Inits list of current devices and event listener for device change.
* @private
* @returns {Promise}
*/
_initDeviceList() {
JitsiMeetJS.mediaDevices.isDeviceListAvailable()
.then(isDeviceListAvailable => {
if (isDeviceListAvailable
&& JitsiMeetJS.mediaDevices.isDeviceChangeAvailable()) {
JitsiMeetJS.mediaDevices.enumerateDevices(devices => {
// Ugly way to synchronize real device IDs with local
// storage and settings menu. This is a workaround until
// getConstraints() method will be implemented
// in browsers.
if (this.localAudio) {
APP.settings.setMicDeviceId(
this.localAudio.getDeviceId(), false);
}
const { mediaDevices } = JitsiMeetJS;
if (this.localVideo) {
APP.settings.setCameraDeviceId(
this.localVideo.getDeviceId(), false);
}
if (mediaDevices.isDeviceListAvailable()
&& mediaDevices.isDeviceChangeAvailable()) {
this.deviceChangeListener = devices =>
window.setTimeout(() => this._onDeviceListChanged(devices), 0);
mediaDevices.addEventListener(
JitsiMediaDevicesEvents.DEVICE_LIST_CHANGED,
this.deviceChangeListener);
mediaDeviceHelper.setCurrentMediaDevices(devices);
APP.UI.onAvailableDevicesChanged(devices);
APP.store.dispatch(updateDeviceList(devices));
});
const { dispatch } = APP.store;
this.deviceChangeListener = devices =>
window.setTimeout(
() => this._onDeviceListChanged(devices), 0);
JitsiMeetJS.mediaDevices.addEventListener(
JitsiMediaDevicesEvents.DEVICE_LIST_CHANGED,
this.deviceChangeListener);
}
})
.catch(error => {
logger.warn(`Error getting device list: ${error}`);
});
return dispatch(getAvailableDevices())
.then(devices => {
// Ugly way to synchronize real device IDs with local
// storage and settings menu. This is a workaround until
// getConstraints() method will be implemented in browsers.
if (this.localAudio) {
dispatch(updateSettings({
micDeviceId: this.localAudio.getDeviceId()
}));
}
if (this.localVideo) {
dispatch(updateSettings({
cameraDeviceId: this.localVideo.getDeviceId()
}));
}
APP.UI.onAvailableDevicesChanged(devices);
});
}
return Promise.resolve();
},
/**
@@ -2438,16 +2342,7 @@ export default {
* @returns {Promise}
*/
_onDeviceListChanged(devices) {
let currentDevices = mediaDeviceHelper.getCurrentMediaDevices();
// Event handler can be fired before direct
// enumerateDevices() call, so handle this situation here.
if (!currentDevices.audioinput
&& !currentDevices.videoinput
&& !currentDevices.audiooutput) {
mediaDeviceHelper.setCurrentMediaDevices(devices);
currentDevices = mediaDeviceHelper.getCurrentMediaDevices();
}
APP.store.dispatch(updateDeviceList(devices));
const newDevices
= mediaDeviceHelper.getNewMediaDevicesAfterDeviceListChanged(
@@ -2460,10 +2355,13 @@ export default {
const videoWasMuted = this.isLocalVideoMuted();
if (typeof newDevices.audiooutput !== 'undefined') {
// Just ignore any errors in catch block.
promises.push(APP.settings
.setAudioOutputDeviceId(newDevices.audiooutput)
.catch());
const { dispatch } = APP.store;
const setAudioOutputPromise
= setAudioOutputDeviceId(newDevices.audiooutput, dispatch)
.catch(); // Just ignore any errors in catch block.
promises.push(setAudioOutputPromise);
}
promises.push(
@@ -2471,11 +2369,24 @@ export default {
createLocalTracksF,
newDevices.videoinput,
newDevices.audioinput)
.then(tracks =>
Promise.all(this._setLocalAudioVideoStreams(tracks)))
.then(tracks => {
// If audio or video muted before, or we unplugged current
// device and selected new one, then mute new track.
const muteSyncPromises = tracks.map(track => {
if ((track.isVideoTrack() && videoWasMuted)
|| (track.isAudioTrack() && audioWasMuted)) {
return track.mute();
}
return Promise.resolve();
});
return Promise.all(muteSyncPromises)
.then(() => Promise.all(
this._setLocalAudioVideoStreams(tracks)));
})
.then(() => {
// If audio was muted before, or we unplugged current device
// and selected new one, then mute new audio track.
// Log and sync known mute state.
if (audioWasMuted) {
sendAnalytics(createTrackMutedEvent(
'audio',
@@ -2484,8 +2395,6 @@ export default {
muteLocalAudio(true);
}
// If video was muted before, or we unplugged current device
// and selected new one, then mute new video track.
if (!this.isSharingScreen && videoWasMuted) {
sendAnalytics(createTrackMutedEvent(
'video',
@@ -2497,7 +2406,6 @@ export default {
return Promise.all(promises)
.then(() => {
mediaDeviceHelper.setCurrentMediaDevices(devices);
APP.UI.onAvailableDevicesChanged(devices);
});
},
@@ -2507,7 +2415,7 @@ export default {
*/
updateAudioIconEnabled() {
const audioMediaDevices
= mediaDeviceHelper.getCurrentMediaDevices().audioinput;
= APP.store.getState()['features/base/devices'].audioInput;
const audioDeviceCount
= audioMediaDevices ? audioMediaDevices.length : 0;
@@ -2530,7 +2438,7 @@ export default {
*/
updateVideoIconEnabled() {
const videoMediaDevices
= mediaDeviceHelper.getCurrentMediaDevices().videoinput;
= APP.store.getState()['features/base/devices'].videoInput;
const videoDeviceCount
= videoMediaDevices ? videoMediaDevices.length : 0;
@@ -2560,6 +2468,13 @@ export default {
eventEmitter.emit(JitsiMeetConferenceEvents.BEFORE_HANGUP);
APP.UI.removeLocalMedia();
// Remove unnecessary event listeners from firing callbacks.
if (this.deviceChangeListener) {
JitsiMeetJS.mediaDevices.removeEventListener(
JitsiMediaDevicesEvents.DEVICE_LIST_CHANGED,
this.deviceChangeListener);
}
let requestFeedbackPromise;
if (requestFeedback) {
@@ -2578,13 +2493,24 @@ export default {
// before all operations are done.
Promise.all([
requestFeedbackPromise,
room.leave().then(disconnect, disconnect)
this.leaveRoomAndDisconnect()
]).then(values => {
APP.API.notifyReadyToClose();
maybeRedirectToWelcomePage(values[0]);
});
},
/**
* Leaves the room and calls JitsiConnection.disconnect.
*
* @returns {Promise}
*/
leaveRoomAndDisconnect() {
APP.store.dispatch(conferenceWillLeave(room));
return room.leave().then(disconnect, disconnect);
},
/**
* Changes the email for the local user
* @param email {string} the new email
@@ -2601,13 +2527,23 @@ export default {
const localId = localParticipant.id;
APP.store.dispatch(participantUpdated({
// XXX Only the local participant is allowed to update without
// stating the JitsiConference instance (i.e. participant property
// `conference` for a remote participant) because the local
// participant is uniquely identified by the very fact that there is
// only one local participant.
id: localId,
local: true,
email: formattedEmail
}));
APP.settings.setEmail(formattedEmail);
APP.UI.setUserEmail(localId, formattedEmail);
APP.store.dispatch(updateSettings({
email: formattedEmail
}));
APP.API.notifyEmailChanged(localId, {
email: formattedEmail
});
sendData(commands.EMAIL, formattedEmail);
},
@@ -2625,12 +2561,20 @@ export default {
}
APP.store.dispatch(participantUpdated({
// XXX Only the local participant is allowed to update without
// stating the JitsiConference instance (i.e. participant property
// `conference` for a remote participant) because the local
// participant is uniquely identified by the very fact that there is
// only one local participant.
id,
local: true,
avatarURL: formattedUrl
}));
APP.settings.setAvatarUrl(url);
APP.store.dispatch(updateSettings({
avatarURL: formattedUrl
}));
sendData(commands.AVATAR_URL, url);
},
@@ -2679,12 +2623,21 @@ export default {
}
APP.store.dispatch(participantUpdated({
// XXX Only the local participant is allowed to update without
// stating the JitsiConference instance (i.e. participant property
// `conference` for a remote participant) because the local
// participant is uniquely identified by the very fact that there is
// only one local participant.
id,
local: true,
name: formattedNickname
}));
APP.settings.setDisplayName(formattedNickname);
APP.store.dispatch(updateSettings({
displayName: formattedNickname
}));
APP.API.notifyDisplayNameChanged(id, {
displayName: formattedNickname,
formattedDisplayName:

View File

@@ -18,9 +18,6 @@ var config = {
// XMPP domain.
domain: 'jitsi-meet.example.com',
// XMPP MUC domain. FIXME: use XEP-0030 to discover it.
muc: 'conference.jitsi-meet.example.com'
// When using authentication, domain for guest users.
// anonymousdomain: 'guest.example.com',
@@ -35,6 +32,9 @@ var config = {
// Focus component domain. Defaults to focus.<domain>.
// focus: 'focus.jitsi-meet.example.com',
// XMPP MUC domain. FIXME: use XEP-0030 to discover it.
muc: 'conference.jitsi-meet.example.com'
},
// BOSH URL. FIXME: use XEP-0156 to discover it.
@@ -99,13 +99,13 @@ var config = {
// used by browsers that return true from lib-jitsi-meet's
// util#browser#usesNewGumFlow. The constraints are independency from
// this config's resolution value. Defaults to requesting an ideal aspect
// ratio of 16:9 with an ideal resolution of 1080p.
// ratio of 16:9 with an ideal resolution of 720.
// constraints: {
// video: {
// aspectRatio: 16 / 9,
// height: {
// ideal: 1080,
// max: 1080,
// ideal: 720,
// max: 720,
// min: 240
// }
// }
@@ -114,6 +114,11 @@ var config = {
// Enable / disable simulcast support.
// disableSimulcast: false,
// Enable / disable layer suspension. If enabled, endpoints whose HD
// layers are not in use will be suspended (no longer sent) until they
// are requested again.
// enableLayerSuspension: false,
// Suspend sending video if bandwidth estimation is too low. This may cause
// problems with audio playback. Disabled until these are fixed.
disableSuspendVideo: true,
@@ -137,9 +142,6 @@ var config = {
// Desktop sharing
// Enable / disable desktop sharing
// disableDesktopSharing: false,
// The ID of the jidesha extension for Chrome.
desktopSharingChromeExtId: null,
@@ -167,11 +169,19 @@ var config = {
// Recording
// Whether to enable recording or not.
// enableRecording: false,
// Whether to enable file recording or not.
// fileRecordingsEnabled: false,
// Enable the dropbox integration.
// dropbox: {
// appKey: '<APP_KEY>' // Specify your app key here.
// },
// Type for recording: one of jibri or jirecon.
// recordingType: 'jibri',
// Whether to enable live streaming or not.
// liveStreamingEnabled: false,
// Transcription (in interface_config,
// subtitles and buttons can be configured)
// transcribingEnabled: false,
// Misc
@@ -231,10 +241,6 @@ var config = {
// Disable hiding of remote thumbnails when in a 1-on-1 conference call.
// disable1On1Mode: false,
// The minimum value a video's height (or width, whichever is smaller) needs
// to be in order to be considered high-definition.
minHDHeight: 540,
// Default language for the user interface.
// defaultLanguage: 'en',
@@ -243,10 +249,16 @@ var config = {
// edit their profile.
enableUserRolesBasedOnToken: false,
// Whether or not some features are checked based on token.
// enableFeaturesBasedOnToken: false,
// Message to show the users. Example: 'The service will be down for
// maintenance at 01:00 AM GMT,
// noticeMessage: '',
// Enables calendar integration, depends on googleApiApplicationClientID
// and microsoftApiApplicationClientID
// enableCalendarIntegration: false,
// Stats
//
@@ -339,9 +351,39 @@ var config = {
// userRegion: "asia"
}
// Local Recording
//
// localRecording: {
// Enables local recording.
// Additionally, 'localrecording' (all lowercase) needs to be added to
// TOOLBAR_BUTTONS in interface_config.js for the Local Recording
// button to show up on the toolbar.
//
// enabled: true,
//
// The recording format, can be one of 'ogg', 'flac' or 'wav'.
// format: 'flac'
//
// }
// Options related to end-to-end (participant to participant) ping.
// e2eping: {
// // The interval in milliseconds at which pings will be sent.
// // Defaults to 10000, set to <= 0 to disable.
// pingInterval: 10000,
//
// // The interval in milliseconds at which analytics events
// // with the measured RTT will be sent. Defaults to 60000, set
// // to <= 0 to disable.
// analyticsInterval: 60000,
// }
// List of undocumented settings used in jitsi-meet
/**
alwaysVisibleToolbar
_immediateReloadThreshold
autoRecord
autoRecordToken
debug
@@ -358,8 +400,10 @@ var config = {
externalConnectUrl
firefox_fake_device
googleApiApplicationClientID
googleApiIOSClientID
iAmRecorder
iAmSipGateway
microsoftApiApplicationClientID
peopleSearchQueryTypes
peopleSearchUrl
requireDisplayName
@@ -388,6 +432,7 @@ var config = {
nick
startBitrate
*/
};
/* eslint-enable no-unused-vars, no-var */

View File

@@ -102,7 +102,12 @@ function connect(id, password, roomName) {
/* eslint-enable max-params */
APP.store.dispatch(
connectionFailed(
connection, error, message, credentials, details));
connection, {
credentials,
details,
message,
name: error
}));
if (isFatalJitsiConnectionError(error)) {
connection.removeEventListener(
@@ -127,7 +132,7 @@ function connect(id, password, roomName) {
*
*/
function handleConnectionEstablished() {
APP.store.dispatch(connectionEstablished(connection));
APP.store.dispatch(connectionEstablished(connection, Date.now()));
unsubscribe();
resolve(connection);
}

View File

@@ -1,19 +0,0 @@
/**
* Project animations
**/
/**
* Slide in animation for extended toolbar (inner) panel.
*/
// FIX: Can't use percentage because of breaking animation when width is changed
// (100% of 0 is also zero) Extracted this to config variable.
@include keyframes(slideInExt) {
from { left: -$sidebarWidth; }
to { left: 0; }
}
@include keyframes(slideOutExt) {
from { left: 0; }
to { left: -$sidebarWidth; }
}

View File

@@ -0,0 +1,57 @@
/**
* Move Atlaskit Flag up a little bit so it does not cover the toolbar with the
* first notification.
*/
.cxGWJB{
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.
*/
.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;
}
}
.modal-dialog-form {
/**
* Update the dropdown trigger wrapper to make sure it looks click-able.
*/
.gwEjuO {
cursor: pointer;
}
/**
* Override Atlaskit dropdown styling when in a modal because the dropdown
* backgrounds clash with the modal backgrounds.
*/
.gBLqhw[data-role=droplistContent] {
border: 1px solid #455166;
}
}

View File

@@ -14,6 +14,11 @@ textarea {
user-select: text;
}
html {
height: 100%;
width: 100%;
}
body {
margin: 0px;
width: 100%;
@@ -66,7 +71,6 @@ input[type="reset"], input[type="submit"] {
}
textarea {
overflow: hidden;
word-wrap: break-word;
resize: none;
line-height: 1.5em;
@@ -104,14 +108,15 @@ form {
}
.leftwatermark {
left: $defaultToolbarSize;
margin-left: 10px;
left: 32px;
top: 32px;
background-image: url($defaultWatermarkLink);
background-position: center left;
}
.rightwatermark {
right: 15;
right: 32px;
top: 32px;
background-position: center right;
}

View File

@@ -1,21 +1,58 @@
#sideToolbarContainer {
background-color: $newToolbarBackgroundColor;
display: flex;
/**
* Make the sidebar flush with the top of the toolbar. Take the size of
* the toolbar and subtract from 100%.
*/
height: calc(100% - #{$newToolbarSizeWithPadding});
left: -$sidebarWidth;
overflow: hidden;
position: absolute;
top: 0;
transition: left 0.5s;
width: $sidebarWidth;
z-index: $sideToolbarContainerZ;
/**
* The sidebar (chat) is off-screen when hidden. Move it flush to the left
* side of the window when it should be visible.
*/
&.slideInExt {
left: 0;
}
.sideToolbarContainer__inner {
box-sizing: border-box;
color: #FFF;
display: flex;
flex-direction: column;
height: 100%;
width: $sidebarWidth;
}
}
#chat_container * {
-webkit-user-select: text;
user-select: text;
}
#chatconversation {
visibility: hidden;
position: relative;
top: 5px;
box-sizing: border-box;
flex: 1;
font-size: 10pt;
line-height: 20px;
margin-top: 15px;
overflow: auto;
padding: 5px;
text-align: left;
line-height: 20px;
font-size: 10pt;
width: 100%;
height: 90%;
overflow: auto;
width: $sidebarWidth;
word-wrap: break-word;
a {
display: block;
}
a:link {
color: rgb(184, 184, 184);
}
@@ -55,71 +92,52 @@
}
}
#chat_container.is-conversation-mode #chatconversation {
visibility: visible;
.chat-close {
background: gray;
border: 3px solid rgba(255, 255, 255, 0.1);
border-radius: 100%;
color: white;
cursor:pointer;
height: 10px;
line-height: 10px;
padding: 4px;
position: absolute;
right: 5px;
text-align: center;
top: 5px;
width: 10px;
z-index: 1;
}
.localuser {
color: #087dba;
}
.use-new-toolbox {
.chatmessage {
color: white;
}
.localuser {
color: #4C9AFF;
}
.remoteuser {
color: #B8C7E0;
}
#usermsg {
color: white;
}
.chatmessage,
#smileysarea,
#smileysContainer,
#usermsg {
background-color: $newToolbarBackgroundColor;
}
.smileyContainer:hover {
background-color: $newToolbarButtonToggleColor;
border-radius: 5px;
cursor: pointer;
}
}
.errorMessage {
color: red;
#chat-input {
background-color: $newToolbarBackgroundColor;
display: flex;
}
.remoteuser {
color: white;
color: #B8C7E0;
}
.usrmsg-form {
flex: 1;
margin-left: 5px;
}
#usermsg {
visibility:hidden;
position: absolute;
bottom: 0px;
right: 0px;
width: 83%;
height: 30px;
background-color: $newToolbarBackgroundColor;
border: 0px none;
border-radius:0;
box-shadow: none;
color: white;
font-size: 10pt;
line-height: 30px;
padding: 5px 5px 5px 0px;
max-height:150px;
min-height:35px;
border: 0px none;
background: #3a3a3a;
color: #a7a7a7;
box-shadow: none;
border-radius:0;
font-size: 10pt;
line-height: 30px;
overflow: hidden;
overflow-y: auto;
resize: none;
width: 100%;
word-break: break-word;
}
#usermsg:hover {
@@ -127,10 +145,6 @@
box-shadow: none;
}
#chat_container.is-conversation-mode #usermsg {
visibility: visible;
}
#nickname {
position: absolute;
text-align: center;
@@ -142,20 +156,7 @@
width: 95%;
}
#chat_container.is-conversation-mode #nickname {
visibility: hidden;
}
#nickinput {
margin-top: 20px;
font-size: 14px;
background: #3a3a3a;
box-shadow: inset 0 0 3px 2px #a7a7a7;
border: 1px solid #a7a7a7;
color: #a7a7a7;
}
#chat_container .username {
#chat_container .display-name {
float: left;
padding-left: 5px;
font-weight: bold;
@@ -171,29 +172,45 @@
font-size: 11px;
}
#chat_container .usermessage {
.usermessage {
padding-top: 20px;
padding-left: 5px;
}
.chatArrow {
position: absolute;
height: 15px;
left: 5px;
left: -10px;
position: absolute;
}
.chatmessage {
background: #3a3a3a;
background-color: $newToolbarBackgroundColor;
width: 93%;
margin-left: 9px;
margin-right: auto;
border-radius: 5px;
border-top-left-radius: 0px;
margin-top: 3px;
left: 5px;
color: #a7a7a7;
overflow: hidden;
color: white;
padding-bottom: 3px;
position: relative;
&.localuser .display-name {
color: #4C9AFF
}
&.error {
.chatArrow,
.timestamp,
.display-name {
display: none;
}
.usermessage {
color: red;
padding: 0;
}
}
}
.smiley {
@@ -201,11 +218,9 @@
}
#smileys {
position: absolute;
bottom: 7px;
right: 5px;
background: white;
border-radius: 50px;
border-radius: 50%;
display: inline-block;
height: 26px;
margin: auto;
cursor: pointer;
@@ -217,33 +232,40 @@
}
#smileysarea {
position: absolute;
bottom: 0px;
left: 0px;
width: 17%;
min-width: 31px;
height: 40px;
padding: 0px;
max-height:150px;
min-height:35px;
background-color: $newToolbarBackgroundColor;
border: 0px none;
background: #3a3a3a;
display: flex;
height: 70px;
max-height: 150px;
min-height: 35px;
min-width: 31px;
padding: 0px;
overflow: hidden;
visibility: hidden;
width: 17%;
}
#chat_container.is-conversation-mode #smileysarea {
visibility: visible;
.smiley-input {
position: relative;
}
#smileysContainer {
display: none;
.smileys-panel {
bottom: 100%;
box-sizing: border-box;
height: 0;
overflow: hidden;
position: absolute;
background: #3a3a3a;
border-bottom: 1px solid;
border-top: 1px solid;
width: 100%;
bottom: 10%;
transition: height 0.3s;
width: $sidebarWidth;
&.show-smileys {
height: 202px;
}
#smileysContainer {
background-color: $newToolbarBackgroundColor;
border-bottom: 1px solid;
border-top: 1px solid;
}
}
#smileysContainer .smiley {
@@ -257,7 +279,9 @@
}
.smileyContainer:hover {
background: #3e3e3e;
background-color: $newToolbarButtonToggleColor;
border-radius: 5px;
cursor: pointer;
}
#usermsg::-webkit-input-placeholder {

View File

@@ -1,191 +0,0 @@
%align-right {
@include flex();
flex-direction: row-reverse;
flex-wrap: nowrap;
justify-content: flex-start;
}
.use-new-toolbox {
.filmstrip.reduce-height {
bottom: $newToolbarSizeWithPadding;
}
.filmstrip {
transition: bottom .3s;
}
.filmstrip__videos.hidden {
bottom: calc(-196px - #{$newToolbarSizeWithPadding});
}
}
.filmstrip {
position: absolute;
bottom: 0;
right: 0;
padding: 10px 5px;
@extend %align-right;
z-index: $filmstripVideosZ;
&__toolbar {
@include flex();
flex-direction: column-reverse;
flex-wrap: nowrap;
position: relative;
width: $filmstripToggleButtonWidth;
button {
font-size: 14px;
line-height: 1.2;
text-align: center;
background: transparent;
opacity: 0.7;
height: auto;
width: 100%;
padding: 0;
margin: 0;
border: none;
outline: none;
-webkit-appearance: none;
&:hover {
opacity: 1;
}
i {
cursor: pointer;
}
}
}
&__videos {
@extend %align-right;
position:relative;
padding: 0;
/* The filmstrip should not be covered by the left toolbar. */
bottom: 0;
width:auto;
overflow: visible !important;
&#remoteVideos {
border: $thumbnailsBorder solid transparent;
padding-left: $defaultToolbarSize + 5;
transition: bottom 2s;
}
/**
* The local video identifier.
*/
&#filmstripLocalVideo {
align-self: flex-end;
display: block;
}
&.hidden {
bottom: -196px;
}
.remote-videos-container {
display: flex;
}
.videocontainer {
display: none;
position: relative;
background-size: contain;
border: $thumbnailVideoBorder solid transparent;
border-radius: $borderRadius;
margin: 0 $thumbnailVideoMargin;
&.videoContainerFocused, &:hover {
cursor: hand;
}
/**
* Focused video thumbnail.
*/
&.videoContainerFocused {
transition-duration: 0.5s;
-webkit-transition-duration: 0.5s;
-webkit-animation-name: greyPulse;
-webkit-animation-duration: 2s;
-webkit-animation-iteration-count: 1;
border: $thumbnailVideoBorder solid $videoThumbnailSelected !important;
box-shadow: inset 0 0 3px $videoThumbnailSelected,
0 0 3px $videoThumbnailSelected !important;
}
.remotevideomenu > .icon-menu {
display: none;
}
.presence-label {
color: $participantNameColor;
font-size: 12px;
font-weight: 100;
left: 0;
margin: 0 auto;
overflow: hidden;
pointer-events: none;
position: absolute;
right: 0;
text-align: center;
text-overflow: ellipsis;
top: calc(50% + 30px);
white-space: nowrap;
width: 100%;
z-index: $zindex3;
}
/**
* Hovered video thumbnail.
*/
&:hover {
cursor: hand;
border: $thumbnailVideoBorder solid $videoThumbnailHovered;
box-shadow: inset 0 0 3px $videoThumbnailHovered,
0 0 3px $videoThumbnailHovered;
.remotevideomenu > .icon-menu {
display: inline-block;
}
}
/* With the TemasysWebRTC plugin <object/> element is used
instead of <video/> */
& > video,
& > object {
cursor: hand;
border-radius: $borderRadius;
object-fit: cover;
overflow: hidden;
}
}
}
/**
* Style the filmstrip videos in filmstrip-only mode.
*/
&__videos-filmstripOnly {
margin-top: auto;
margin-bottom: auto;
.filmstrip__videos {
&#filmstripLocalVideo {
bottom: 0px;
}
}
}
.remote-videos-container {
transition: opacity 1s;
}
&.hide-videos {
.remote-videos-container {
opacity: 0;
pointer-events: none;
}
}
}

View File

@@ -24,9 +24,13 @@
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.icon-arrow_back:before {
content: "\e5c4";
}
.icon-close:before {
content: "\e5cd";
}
.icon-event_note:before {
content: "\e616";
}
@@ -195,3 +199,24 @@
.icon-SD:before {
content: "\e929";
}
.icon-camera-take-picture:before {
content: "\e92a";
}
.icon-rec:before {
content: "\e92b";
}
.icon-live:before {
content: "\e92c";
}
.icon-speaker:before {
content: "\e92d";
}
.icon-tiles-many:before {
content: "\e92e";
}
.icon-tiles-one:before {
content: "\e92f";
}
.icon-closed_caption:before {
content: "\e930";
}

117
css/_meetings_list.scss Normal file
View File

@@ -0,0 +1,117 @@
.meetings-list {
font-size: 14px;
color: #253858;
line-height: 20px;
text-align: left;
text-overflow: ellipsis;
display: flex;
flex-direction: column;
position: relative;
width: 100%;
height: 100%;
overflow: auto;
.meetings-list-empty {
text-align: center;
align-items: center;
justify-content: center;
display: flex;
flex-grow: 1;
flex-direction: column;
.description {
font-size: 16px;
padding: 20px;
}
}
.button {
background: #0074E0;
border-radius: 4px;
color: #FFFFFF;
display: flex;
justify-content: center;
align-items: center;
padding: 5px 10px;
cursor: pointer;
}
.item {
background: rgba(255,255,255,0.50);
box-sizing: border-box;
display: inline-flex;
margin-top: 5px;
min-height: 92px;
width: 100%;
word-break: break-word;
display: flex;
flex-direction: row;
text-align: left;
&:first-child {
margin-top: 0px;
}
.left-column {
display: flex;
flex-direction: column;
width: 140px;
flex-grow: 0;
padding-left: 30px;
padding-top: 25px;
.date {
font-weight: bold;
padding-bottom: 5px;
}
}
.right-column {
display: flex;
flex-direction: column;
flex-grow: 1;
padding-left: 30px;
padding-top: 25px;
.title {
font-size: 16px;
font-weight: bold;
padding-bottom: 5px;
}
}
.actions {
display: flex;
align-items: center;
justify-content: center;
flex-grow: 0;
padding-right: 30px;
}
&.with-click-handler {
cursor: pointer;
}
&.with-click-handler:hover {
background-color: #75A7E7;
}
.add-button {
width: 30px;
height: 30px;
padding: 0px;
}
i {
cursor: inherit;
}
.join-button {
display: none;
}
&:hover .join-button {
display: block
}
}
}

View File

@@ -0,0 +1,80 @@
%navigate-section-list-text {
width: 100%;
font-size: 14px;
line-height: 20px;
color: $welcomePageTitleColor;
text-align: left;
font-family: 'open_sanslight', Helvetica, sans-serif;
}
%navigate-section-list-tile-text {
@extend %navigate-section-list-text;
overflow: hidden;
text-overflow: ellipsis;
float: left;
}
.navigate-section-list-tile {
background-color: #1754A9;
border-radius: 4px;
box-sizing: border-box;
display: inline-flex;
margin-bottom: 8px;
margin-right: 8px;
min-height: 100px;
padding: 16px;
width: 100%;
&.with-click-handler {
cursor: pointer;
}
&.with-click-handler:hover {
background-color: #1a5dbb;
}
i {
cursor: inherit;
}
.element-after {
display: flex;
align-items: center;
justify-content: center;
}
.join-button {
display: none;
}
&:hover .join-button {
display: block
}
}
.navigate-section-tile-body {
@extend %navigate-section-list-tile-text;
font-weight: normal;
line-height: 24px;
}
.navigate-section-list-tile-info {
flex: 1;
word-break: break-word;
}
.navigate-section-tile-title {
@extend %navigate-section-list-tile-text;
font-weight: bold;
line-height: 24px;
}
.navigate-section-section-header {
@extend %navigate-section-list-text;
font-weight: bold;
margin-bottom: 16px;
display: block;
}
.navigate-section-list {
position: relative;
margin-top: 36px;
margin-bottom: 36px;
width: 100%;
}
.navigate-section-list-empty {
text-align: center;
}

View File

@@ -10,14 +10,24 @@
right: 0;
width: 100%;
}
.popover-mousemove-padding-right {
%vertical-popover-padding {
height: 100%;
position: absolute;
right: -20;
top: 0;
width: 40px;
}
.popover-mousemove-padding-left {
@extend %vertical-popover-padding;
left: -20px;
}
.popover-mousemove-padding-right {
@extend %vertical-popover-padding;
right: -20px;
}
/**
* An invisible element is added to the top of the popover to ensure the mouse
* stays over the popover when the popover's height is shrunk, which would then

View File

@@ -2,6 +2,57 @@
vertical-align: top;
}
.recording-dialog {
flex: 0;
flex-direction: column;
.recording-header {
display: flex;
flex: 0;
flex-direction: row;
justify-content: space-between;
align-items: center;
.recording-title {
font-size: 16px;
font-weight: bold;
}
}
.authorization-panel {
display: flex;
flex-direction: column;
margin-bottom: 10px;
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;
}
}
}
.live-stream-dialog {
/**
* Set font-size to be consistent with Atlaskit FieldText.
@@ -30,37 +81,8 @@
width: 100%;
}
/**
* The Google sign in button must follow Google's design guidelines.
* See: https://developers.google.com/identity/branding-guidelines
*/
.google-sign-in {
background-color: #4285f4;
border-radius: 2px;
cursor: pointer;
display: inline-flex;
font-family: Roboto, arial, sans-serif;
font-size: 14px;
padding: 1px;
.google-cta {
color: white;
display: inline-block;
/**
* Hack the line height for vertical centering of text.
*/
line-height: 32px;
margin: 0 15px;
}
.google-logo {
background-color: white;
border-radius: 2px;
display: inline-block;
padding: 8px;
height: 18px;
width: 18px;
}
.google-error {
color: $errorColor;
}
.google-panel {

View File

@@ -1,164 +0,0 @@
/**
* Toolbar side panel main container element.
*/
.use-new-toolbox #sideToolbarContainer {
background-color: $newToolbarBackgroundColor;
/**
* Make the sidebar flush with the top of the toolbar. Take the size of
* the toolbar and subtract from 100%.
*/
height: calc(100% - #{$newToolbarSizeWithPadding});
left: 0;
.side-toolbar-close {
background: gray;
border: 3px solid rgba(255, 255, 255, 0.1);
border-radius: 100%;
color: white;
cursor:pointer;
height: 10px;
line-height: 10px;
padding: 4px;
position: absolute;
right: 5px;
text-align: center;
top: 5px;
width: 10px;
z-index: 1;
}
#chatconversation {
top: 15px;
}
}
#sideToolbarContainer {
background-color: $sideToolbarContainerBg;
height: 100%;
left: $defaultToolbarSize;
max-width: $sidebarWidth;
overflow: hidden;
position: absolute;
top: 0;
width: 0;
z-index: $sideToolbarContainerZ;
/**
* Labels inside the side panel.
*/
label {
color: $baseLight;
}
/**
* Form elements and blocks.
*/
input,
a,
.sideToolbarBlock,
.form-control {
display: block;
margin-top: 15px;
margin-left: 10%;
width: 80%;
}
/**
* Specify styling of elements inside a block.
*/
.sideToolbarBlock {
input, a {
margin-left: 0;
margin-top: 5px;
width: 100%;
}
}
/**
* Inner container, for example settings or profile.
*/
.sideToolbarContainer__inner {
display: none;
height: 100%;
width: $sidebarWidth;
position: absolute;
box-sizing: border-box;
color: #FFF;
.input-control {
border: 0;
}
/**
* Titles and subtitles of inner containers.
*/
div.title, div.subTitle {
margin: 24px 0 11px;
}
/**
* Main title size.
*/
div.title {
color: $toolbarTitleColor;
text-align: center;
font-size: $toolbarTitleFontSize;
}
/**
* First element after a title.
*/
.first {
margin-top: 0 !important;
}
}
.settings-menu {
display: flex;
flex-direction: column;
padding-left: 10%;
padding-right: 10%;
.moderator-checkbox {
display: inline-block;
margin: 0 5px 0;
width: auto;
}
.moderator-option {
margin-top: 15px;
}
.subTitle {
color: $defaultSideBarFontColor;
font-size: 11px;
font-weight: 500;
}
}
}
/**
* Profile
*/
.auth_container {
ul {
padding: 0;
li {
list-style-type: none;
a.authButton {
width: 160px;
margin: 10px 20px;
padding: 3px 29px;
box-sizing: border-box;
background-color: #06a5df;
border-radius: 4px;
cursor: pointer;
color: $defaultColor;
text-decoration: none;
text-align: center;
}
}
}
}

View File

@@ -19,17 +19,6 @@
vertical-align: middle;
}
.use-new-toolbox {
.cxGWJB{
bottom: calc(#{$newToolbarSizeWithPadding});
}
.gXSEsl:nth-child(n+2) {
transform: translateX(0) translateY(100%) translateY(16px);
-ms-transform: translateX(0) translateY(100%) translateY(16px);
-webkit-transform: translateX(0) translateY(100%) translateY(16px);
}
}
/**
* TODO: when the old filmstrip has been removed, remove the "new-" prefix.
*/
@@ -39,8 +28,12 @@
box-sizing: border-box;
display: flex;
justify-content: space-between;
padding: 16px 8px;
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;
@@ -68,15 +61,6 @@
justify-content: flex-end;
}
/**
* Overwrite font-awesome styling to match jitsi-icon styling.
*/
.fa {
height: 24px;
margin-left: 2px;
width: 24px;
}
i {
border-radius: 5px;
cursor: pointer;
@@ -100,7 +84,13 @@
}
i.disabled {
cursor: initial
cursor: initial;
color: #3b475c;
}
.disabled i {
cursor: initial;
color: #3b475c;
}
i.disabled:hover {
@@ -109,6 +99,7 @@
.icon-hangup {
color: $hangupColor;
font-size: $newToolbarHangupFontSize;
}
.overflow-menu {
@@ -129,6 +120,12 @@
height: 22px;
padding: 5px 12px;
div {
display: flex;
flex-direction: row;
align-items: center;
}
&:hover {
background: #313D52;
}
@@ -139,6 +136,20 @@
&.unclickable:hover {
background: inherit;
}
&.disabled {
cursor: initial;
color: #3b475c;
}
}
.beta-tag {
background: #B8C7E0;
border-radius: 2px;
color: $newToolbarBackgroundColor;
font-size: 11px;
font-weight: bold;
margin-left: 8px;
padding: 0 6px;
}
.overflow-menu-item-icon {
@@ -248,10 +259,14 @@
z-index: $toolbarZ;
i {
font-size: $alwaysOnTopToolbarFontSize;
height: $alwaysOnTopToolbarSize;
line-height: $alwaysOnTopToolbarSize;
width: $alwaysOnTopToolbarSize;
font-size: $newToolbarFontSize;
height: $newToolbarSize;
line-height: $newToolbarSize;
width: $newToolbarSize;
}
.icon-hangup {
font-size: $newToolbarHangupFontSize;
}
.disabled {
@@ -288,27 +303,6 @@
}
}
/**
* START of slide in animation for extended toolbar panel.
*/
@include keyframes(slideInExt) {
from { width: 0px; }
to { width: $sidebarWidth; } // TO FIX: Make this value a percentage.
}
.slideInExt {
@include animation("slideInExt .5s forwards");
}
@include keyframes(slideOutExt) {
from { width: $sidebarWidth; } // TO FIX: Make this value a percentage.
to { width: 0px; }
}
.slideOutExt {
@include animation("slideOutExt .5s forwards");
}
/**
* START of fade in animation for main toolbar
*/

View File

@@ -0,0 +1,20 @@
.transcription-subtitles{
bottom: 10%;
font-size: 16px;
font-weight: 1000;
left: 50%;
max-width: 50vw;
opacity: 0.80;
pointer-events: none;
position: absolute;
text-shadow: 0px 0px 1px rgba(0,0,0,0.3),
0px 1px 1px rgba(0,0,0,0.3),
1px 0px 1px rgba(0,0,0,0.3),
0px 0px 1px rgba(0,0,0,0.3);
transform: translateX(-50%);
z-index: $filmstripVideosZ + 1;
span {
background: black;
}
}

View File

@@ -3,7 +3,7 @@
/**
* Style variables
*/
$baseFontFamily: 'open_sanslight', 'Helvetica Neue', Helvetica, Arial, sans-serif;
$baseFontFamily: -apple-system, BlinkMacSystemFont, 'open_sanslight', 'Helvetica Neue', Helvetica, Arial, sans-serif;
$hangupColor: #bf2117;
$hangupFontSize: 2em;
@@ -32,15 +32,14 @@ $defaultDarkColor: #2b3d5c;
/**
* Toolbar
*/
$alwaysOnTopToolbarFontSize: 1em;
$alwaysOnTopToolbarSize: 30px;
$defaultToolbarSize: 50px;
$newToolbarBackgroundColor: rgba(22, 38, 55, 0.8);
$newToolbarButtonHoverColor: rgba(14, 20, 35, 0.6);
$newToolbarButtonToggleColor: rgba(14, 20, 35, 1);
$newToolbarFontSize: 1.9em;
$newToolbarSize: 32px;
$newToolbarSizeWithPadding: calc(32px + 32px);
$newToolbarFontSize: 24px;
$newToolbarHangupFontSize: 32px;
$newToolbarSize: 40px;
$newToolbarSizeWithPadding: calc(#{$newToolbarSize} + 24px);
$toolbarTitleFontSize: 19px;
/**
@@ -147,5 +146,5 @@ $watermarkHeight: 74px;
*/
$welcomePageDescriptionColor: #fff;
$welcomePageFontFamily: inherit;
$welcomePageHeaderBackground: linear-gradient(#165ecc, #44A5FF);
$welcomePageHeaderBackground: linear-gradient(-90deg, #1251AE 0%, #0074FF 50%, #1251AE 100%);
$welcomePageTitleColor: #fff;

View File

@@ -1,272 +0,0 @@
/**
* Override other styles to support vertical filmstrip mode.
*/
.vertical-filmstrip {
/*
* Firefox sets flex items to min-height: auto and min-width: auto,
* preventing flex children from shrinking like they do on other browsers.
* Setting min-height and min-width 0 is a workaround for the issue so
* Firefox behaves like other browsers.
* https://bugzilla.mozilla.org/show_bug.cgi?id=1043520
*/
@mixin minHWAutoFix() {
min-height: 0;
min-width: 0;
}
#etherpad,
#sharedvideo {
text-align: left;
}
&.use-new-toolbox {
/**
* Adjust the height of the filmstrip as the toolbar is displayed.
*/
.filmstrip {
top: 0;
transition: height .3s ease-in;
&.reduce-height {
height: calc(100% - #{$newToolbarSizeWithPadding});
}
}
}
.filmstrip {
align-items: flex-end;
box-sizing: border-box;
display: flex;
flex-direction: column-reverse;
height: 100%;
/**
* fixed positioning is necessary for remote menus and tooltips to pop
* out of the scrolling filmstrip. AtlasKit dialogs and tooltips use
* a library called popper which will position its elements fixed if
* any parent is also fixed.
*/
position: fixed;
z-index: $filmstripVideosZ;
/**
* Hide videos by making them slight to the right.
*/
.filmstrip__videos {
right: 0;
&.hidden {
bottom: auto;
right: -196px;
}
/**
* An id selector is used to match id specificity with existing
* filmstrip styles.
*/
&#remoteVideos {
/**
* Remove horizontal filmstrip padding used to prevent videos
* from overlapping getting near the left-side of the screen.
* An id selector is used to match id specificity with filmstrip
* styles.
*/
padding-left: 0;
transition: right 2s;
}
}
/**
* Re-styles the local Video to better fit vertical filmstrip layout.
*/
#filmstripLocalVideo {
align-self: initial;
bottom: 5px;
display: flex;
flex-direction: column-reverse;
height: auto;
justify-content: flex-start;
}
/**
* Remove unnecssary padding that is normally used to prevent horizontal
* filmstrip from overlapping the left edge of the screen.
*/
#filmstripLocalVideo,
#filmstripRemoteVideos {
padding: 0;
}
#filmstripRemoteVideos {
@include minHWAutoFix();
display: flex;
flex: 1;
flex-direction: column;
height: auto;
justify-content: flex-end;
#filmstripRemoteVideosContainer {
flex-direction: column-reverse;
/**
* Add padding as a hack for Firefox not to show scrollbars when
* unnecessary.
*/
padding: 1px 0;
overflow-x: hidden;
}
}
/**
* Rotate the hide filmstrip icon so it points towards the right edge
* of the screen.
*/
&__toolbar {
transform: rotate(-90deg);
}
/**
* Move the remote video menu trigger to the bottom left of the
* video thumbnail.
*/
.remotevideomenu,
.remote-video-menu-trigger {
bottom: 0;
left: 0;
top: auto;
right: auto;
}
.remote-video-menu-trigger {
margin-bottom: 7px;
}
#remoteVideos {
@include minHWAutoFix();
flex-direction: column;
flex-grow: 1;
}
.videocontainer {
/**
* Move status icons to the bottom right of the thumbnail.
*/
&__toolbar {
text-align: right;
.right {
float: none;
margin: auto;
}
}
}
}
&.filmstrip-only {
.filmstrip {
flex-direction: row-reverse;
}
.filmstrip__videos-filmstripOnly {
height: 100%;
}
}
/**
* These styles are for the video labels that display on the top right. The
* styles adjust the labels' positioning as the filmstrip itself or
* filmstrip's remote videos appear and disappear.
*
* The class with-filmstrip is for when the filmstrip is visible.
* The class without-filmstrip is for when the filmstrip has been toggled to
* be hidden.
* The class opening is for when the filmstrip is transitioning from hidden
* to visible.
*/
.video-state-indicator.moveToCorner {
transition: right 0.5s;
&.with-filmstrip {
&#recordingLabel {
right: 200px;
}
&#videoResolutionLabel {
right: 150px;
}
}
&.with-filmstrip.opening {
transition: 0.9s;
transition-timing-function: ease-in-out;
}
&.without-filmstrip {
transition: 1.2s ease-in-out;
transition-delay: 0.1s;
}
}
/**
* Apply hardware acceleration to prevent flickering on scroll. The
* selectors are specific to icon wrappers to prevent fixed position dialogs
* and tooltips from getting a new location context due to translate3d.
*/
.connection-indicator,
.remote-video-menu-trigger,
.indicator-icon-container {
transform: translate3d(0, 0, 0);
}
.indicator-container {
float: none;
}
/**
* FIXME: disable pointer to allow any elements moved below to still be
* clickable. The real fix would to make sure those moved elements are
* actually part of the toolbar instead of positioning being faked.
*/
.videocontainer__toolbar {
pointer-events: none;
> div {
pointer-events: none;
}
.toolbar-icon {
pointer-events: all;
}
}
}
/**
* Workarounds for Edge, IE11, and Firefox not handling scrolling properly
* with flex-direction: column-reverse. The remove videos in filmstrip should
* start scrolling from the bottom of the filmstrip, but in those browsers the
* scrolling won't happen. Per W3C spec, scrolling should happen from the
* bottom. As such, use css hacks to get around the css issue, with the intent
* being to remove the hacks as the spec is supported.
*/
@mixin undoColumnReverseVideos() {
.vertical-filmstrip {
#remoteVideos #filmstripRemoteVideos #filmstripRemoteVideosContainer {
flex-direction: column;
}
}
}
/** Firefox detection hack **/
@-moz-document url-prefix() {
@include undoColumnReverseVideos();
}
/** IE11 detection hack **/
@media screen and (-ms-high-contrast: active),
screen and (-ms-high-contrast: none) {
@include undoColumnReverseVideos();
}
/** Edge detection hack **/
@supports (-ms-ime-align:auto) {
@include undoColumnReverseVideos();
}

View File

@@ -4,6 +4,8 @@
#videospace {
display: block;
height: 100%;
width: 100%;
min-height: 100%;
position: absolute;
top: 0px;
@@ -15,7 +17,6 @@
#largeVideoBackgroundContainer,
.large-video-background {
height: 100%;
filter: blur(40px);
left: 0;
overflow: hidden;
position: absolute;
@@ -32,6 +33,9 @@
width: 100%;
}
}
#largeVideoBackgroundContainer {
filter: blur(40px);
}
.videocontainer {
position: relative;
@@ -82,7 +86,7 @@
* wrapper needed before we're able to move all top toolbar indicators
* creation to react.
*/
.ckAJgx {
.sc-ifAKCX {
background: none;
}
}
@@ -222,6 +226,11 @@
text-align: center;
}
#largeVideoContainer {
height: 100%;
width: 100%;
}
#largeVideoWrapper {
box-shadow: 0 0 20px -2px #444;
}
@@ -548,30 +557,6 @@
object-fit: cover;
}
.noMic {
position: absolute;
border-radius: 8px;
z-index: $zindex1;
width: 100%;
height: 100%;
background-image: url("../images/noMic.png");
background-color: #000;
background-repeat: no-repeat;
background-position: center;
}
.noVideo {
position: absolute;
border-radius: 8px;
z-index: $zindex1;
width: 100%;
height: 100%;
background-image: url("../images/noVideo.png");
background-color: #000;
background-repeat: no-repeat;
background-position: center;
}
.videoMessageFilter {
-webkit-filter: grayscale(.5) opacity(0.8);
filter: grayscale(.5) opacity(0.8);
@@ -618,7 +603,6 @@
padding-left: 10px;
padding-right: 10px;
}
#remotePresenceMessage .no-presence,
#remoteConnectionMessage {
display: none;
}
@@ -640,3 +624,110 @@
1px 0px 1px rgba(0,0,0,0.3),
0px 0px 1px rgba(0,0,0,0.3);
}
.display-avatar-with-name {
.avatar-container {
visibility: visible;
}
.displayNameContainer {
visibility: visible;
}
.videocontainer__hoverOverlay {
visibility: visible;
}
video {
visibility: hidden;
}
}
.display-name-on-black {
.avatar-container {
visibility: hidden;
}
.displayNameContainer {
visibility: visible;
}
.videocontainer__hoverOverlay {
visibility: hidden;
}
video {
opacity: 0.2;
visibility: visible;
}
}
.display-video {
.avatar-container {
visibility: hidden;
}
.displayNameContainer {
visibility: hidden;
}
.videocontainer__hoverOverlay {
visibility: hidden;
}
video {
visibility: visible;
}
}
.display-name-on-video {
.avatar-container {
visibility: hidden;
}
.displayNameContainer {
visibility: visible;
}
.videocontainer__hoverOverlay {
visibility: visible;
}
video {
visibility: visible;
}
}
.display-avatar-only {
.avatar-container {
visibility: visible;
}
.displayNameContainer {
visibility: hidden;
}
.videocontainer__hoverOverlay {
visibility: hidden;
}
video {
visibility: hidden;
}
}
.presence-label {
color: $participantNameColor;
font-size: 12px;
font-weight: 100;
left: 0;
margin: 0 auto;
overflow: hidden;
pointer-events: none;
right: 0;
text-align: center;
text-overflow: ellipsis;
top: calc(50% + 30px);
white-space: nowrap;
width: 100%;
}

View File

@@ -4,15 +4,19 @@ body.welcome-page {
}
.welcome {
background-image: $welcomePageHeaderBackground;
display: flex;
flex-direction: column;
font-family: $welcomePageFontFamily;
height: 100%;
justify-content: space-between;
min-height: 100vh;
position: relative;
.header {
align-items: center;
background: $welcomePageHeaderBackground;
display: flex;
flex-direction: column;
min-height: fit-content;
overflow: hidden;
position: relative;
text-align: center;
@@ -20,74 +24,148 @@ body.welcome-page {
.header-text {
display: flex;
flex-direction: column;
justify-content: space-around;
margin-top: 120px;
margin-bottom: 20px;
margin-top: $watermarkHeight + 35;
margin-bottom: 35px;
max-width: calc(100% - 40px);
min-height: 286px;
width: 645px;
width: 650px;
z-index: $zindex2;
}
.header-text-title {
color: $welcomePageTitleColor;
font-size: 48px;
letter-spacing: -1px;
line-height: 58px;
margin-bottom: 20px;
font-size: 2.5rem;
font-weight: 500;
line-height: 1.18;
margin-bottom: 16px;
}
.header-text-description {
color: $welcomePageDescriptionColor;
font-size: 20px;
line-height: 28px;
opacity: 0.8;
}
.header-image {
background-image: url(../images/welcome_page/curves.png);
background-size: contain;
height: 209px;
position: absolute;
width: 1070px;
}
#new_enter_room {
align-items: center;
display: flex;
font-size: 1rem;
font-weight: 400;
line-height: 24px;
margin-bottom: 20px;
max-width: calc(100% - 40px);
position: relative;
z-index: 2;
}
.enter-room-input {
display: inline-block;
margin-right: 15px;
width: 350px;
#enter_room {
display: flex;
align-items: center;
max-width: calc(100% - 40px);
width: 680px;
z-index: $zindex2;
background-color: #fff;
padding: 25px 30px;
.enter-room-input-container {
width: 100%;
padding-right: 8px;
padding-bottom: 5px;
text-align: left;
color: #253858;
height: fit-content;
border-width: 0px 0px 2px 0px;
border-style: solid;
border-image: linear-gradient(to right, #dee1e6, #fff) 1;
.enter-room-title {
font-size: 18px;
font-weight: bold;
padding-bottom: 5px;
}
.enter-room-input {
border: none;
display: inline-block;
width: 100%;
font-size: 14px;
}
::placeholder {
color: #253858;
}
}
}
.tab-container {
font-size: 16px;
position: relative;
text-align: left;
min-height: 354px;
width: 710px;
background: #75A7E7;
display: flex;
flex-direction: column;
.tab-content{
margin: 5px 0px;
overflow: hidden;
flex-grow: 1;
position: relative;
> * {
position: absolute;
}
}
.tab-buttons {
font-size: 18px;
color: #FFFFFF;
display: flex;
flex-grow: 0;
flex-direction: row;
min-height: 54px;
width: 100%;
.tab {
text-align: center;
background: rgba(9,30,66,0.37);
height: 55px;
line-height: 54px;
flex-grow: 1;
cursor: pointer;
&.selected, &:hover {
background: rgba(9,30,66,0.71);
}
&:last-child {
margin-left: 1px;
}
}
}
}
}
.welcome-page-button {
font-size: 16px;
width: 51px;
height: 35px;
font-size: 14px;
background: #0074E0;
border-radius: 4px;
color: #FFFFFF;
text-align: center;
vertical-align: middle;
line-height: 35px;
cursor: pointer;
}
}
.welcome.with-content {
.header {
min-height: 552px;
}
.header-image {
left: -61px;
top: 401px;
}
}
.welcome-page-settings {
color: $welcomePageDescriptionColor;
position: absolute;
top: 32px;
right: 32px;
z-index: $zindex2;
.welcome.without-content {
.header {
* {
cursor: pointer;
font-size: 32px;
}
}
.welcome-watermark {
position: absolute;
width: 100%;
height: 100%;
}
.header-image {
bottom: -20px;
left: 0;
}
}

View File

@@ -0,0 +1,31 @@
.filmstrip__toolbar {
@include flex();
flex-direction: column-reverse;
flex-wrap: nowrap;
position: relative;
width: $filmstripToggleButtonWidth;
button {
font-size: 14px;
line-height: 1.2;
text-align: center;
background: transparent;
opacity: 0.7;
height: auto;
width: 100%;
padding: 0;
margin: 0;
border: none;
outline: none;
-webkit-appearance: none;
&:hover {
opacity: 1;
}
i {
cursor: pointer;
}
}
}

View File

@@ -0,0 +1,77 @@
%align-right {
@include flex();
flex-direction: row-reverse;
flex-wrap: nowrap;
justify-content: flex-start;
}
.horizontal-filmstrip .filmstrip {
position: absolute;
bottom: 0;
right: 0;
padding: 10px 5px;
@extend %align-right;
transition: bottom .3s;
z-index: $filmstripVideosZ;
&.reduce-height {
bottom: $newToolbarSizeWithPadding;
}
&__videos {
@extend %align-right;
position:relative;
padding: 0;
/* The filmstrip should not be covered by the left toolbar. */
bottom: 0;
width:auto;
overflow: visible !important;
&#remoteVideos {
border: $thumbnailsBorder solid transparent;
padding-left: $defaultToolbarSize + 5;
transition: bottom 2s;
}
/**
* The local video identifier.
*/
&#filmstripLocalVideo {
align-self: flex-end;
display: block;
}
&.hidden {
bottom: calc(-196px - #{$newToolbarSizeWithPadding});
}
.remote-videos-container {
display: flex;
}
}
/**
* Style the filmstrip videos in filmstrip-only mode.
*/
&__videos-filmstripOnly {
margin-top: auto;
margin-bottom: auto;
.filmstrip__videos {
&#filmstripLocalVideo {
bottom: 0px;
}
}
}
.remote-videos-container {
transition: opacity 1s;
}
&.hide-videos {
.remote-videos-container {
opacity: 0;
pointer-events: none;
}
}
}

View File

@@ -0,0 +1,51 @@
.filmstrip__videos .videocontainer {
display: none;
position: relative;
background-size: contain;
border: $thumbnailVideoBorder solid transparent;
border-radius: $borderRadius;
margin: 0 $thumbnailVideoMargin;
&.videoContainerFocused, &:hover {
cursor: hand;
}
/**
* Focused video thumbnail.
*/
&.videoContainerFocused {
border: $thumbnailVideoBorder solid $videoThumbnailSelected;
box-shadow: inset 0 0 3px $videoThumbnailSelected,
0 0 3px $videoThumbnailSelected;
}
.remotevideomenu > .icon-menu {
display: none;
}
/**
* Hovered video thumbnail.
*/
&:hover:not(.videoContainerFocused):not(.active-speaker) {
cursor: hand;
border: $thumbnailVideoBorder solid $videoThumbnailHovered;
box-shadow: inset 0 0 3px $videoThumbnailHovered,
0 0 3px $videoThumbnailHovered;
.remotevideomenu > .icon-menu {
display: inline-block;
}
}
& > video {
cursor: hand;
border-radius: $borderRadius;
object-fit: cover;
overflow: hidden;
}
.presence-label {
position: absolute;
z-index: $zindex3;
}
}

View File

@@ -0,0 +1,113 @@
/**
* CSS styles that are specific to the filmstrip that shows the thumbnail tiles.
*/
.tile-view {
/**
* Add a border around the active speaker to make the thumbnail easier to
* see.
*/
.active-speaker {
box-shadow: 0 0 5px 3px $videoThumbnailSelected
}
#filmstripRemoteVideos {
align-items: center;
box-sizing: border-box;
display: flex;
flex-direction: column;
height: 100vh;
width: 100vw;
}
.filmstrip__videos .videocontainer {
&:not(.active-speaker),
&:hover:not(.active-speaker) {
border: none;
box-shadow: none;
}
}
#remoteVideos {
/**
* Height is modified with an inline style in horizontal filmstrip mode
* so !important is used to override that.
*/
height: 100% !important;
width: 100%;
}
.filmstrip {
align-items: center;
display: flex;
height: 100%;
justify-content: center;
left: 0;
position: fixed;
top: 0;
width: 100%;
z-index: $filmstripVideosZ
}
/**
* Regardless of the user setting, do not let the filmstrip be in a hidden
* state.
*/
.filmstrip__videos.hidden {
display: block;
}
#filmstripRemoteVideos {
box-sizing: border-box;
/**
* Allow vertical scrolling of the thumbnails.
*/
overflow-x: hidden;
overflow-y: auto;
}
/**
* The size of the thumbnails should be set with javascript, based on
* desired column count and window width. The rows are created using flex
* and allowing the thumbnails to wrap.
*/
#filmstripRemoteVideosContainer {
align-content: center;
align-items: center;
box-sizing: border-box;
display: flex;
flex-wrap: wrap;
height: 100vh;
justify-content: center;
padding: 100px 0;
.videocontainer {
border: 0;
box-sizing: border-box;
display: block;
margin: 5px;
}
video {
object-fit: contain;
}
}
.has-overflow#filmstripRemoteVideosContainer {
align-content: baseline;
}
.has-overflow .videocontainer {
align-self: baseline;
}
/**
* Firefox flex acts a little differently. To make sure the bottom row of
* thumbnails is not overlapped by the horizontal toolbar, margin is added
* to the local thumbnail to keep it from the bottom of the screen. It is
* assumed the local thumbnail will always be on the bottom row.
*/
.has-overflow #localVideoContainer {
margin-bottom: 100px !important;
}
}

View File

@@ -0,0 +1,47 @@
/**
* Various overrides outside of the filmstrip to style the app to support a
* tiled thumbnail experience.
*/
.tile-view {
/**
* Let the avatar grow with the tile.
*/
.userAvatar {
max-height: initial;
max-width: initial;
}
/**
* Hide various features that should not be displayed while in tile view.
*/
#dominantSpeaker,
#filmstripLocalVideoThumbnail,
#largeVideoElementsContainer,
#sharedVideo,
.filmstrip__toolbar {
display: none;
}
#localConnectionMessage,
#remoteConnectionMessage,
.watermark {
z-index: $filmstripVideosZ + 1;
}
/**
* The follow styling uses !important to override inline styles set with
* javascript.
*
* TODO: These overrides should be more easy to remove and should be removed
* when the components are in react so their rendering done declaratively,
* making conditional styling easier to apply.
*/
#largeVideoElementsContainer,
#remoteConnectionMessage,
#remotePresenceMessage {
display: none !important;
}
#largeVideoContainer {
background-color: $defaultBackground !important;
}
}

View File

@@ -0,0 +1,171 @@
.vertical-filmstrip .filmstrip {
&.hide-videos {
.remote-videos-container {
opacity: 0;
pointer-events: none;
}
}
/*
* Firefox sets flex items to min-height: auto and min-width: auto,
* preventing flex children from shrinking like they do on other browsers.
* Setting min-height and min-width 0 is a workaround for the issue so
* Firefox behaves like other browsers.
* https://bugzilla.mozilla.org/show_bug.cgi?id=1043520
*/
@mixin minHWAutoFix() {
min-height: 0;
min-width: 0;
}
@extend %align-right;
align-items: flex-end;
bottom: 0;
box-sizing: border-box;
display: flex;
flex-direction: column-reverse;
height: 100%;
padding: 10px 5px;
/**
* fixed positioning is necessary for remote menus and tooltips to pop
* out of the scrolling filmstrip. AtlasKit dialogs and tooltips use
* a library called popper which will position its elements fixed if
* any parent is also fixed.
*/
position: fixed;
top: 0;
transition: height .3s ease-in;
right: 0;
z-index: $filmstripVideosZ;
&.reduce-height {
height: calc(100% - #{$newToolbarSizeWithPadding});
}
/**
* Hide videos by making them slight to the right.
*/
.filmstrip__videos {
@extend %align-right;
bottom: 0;
overflow: visible !important;
padding: 0;
position:relative;
right: 0;
width: auto;
&.hidden {
bottom: auto;
right: -196px;
}
/**
* An id selector is used to match id specificity with existing
* filmstrip styles.
*/
&#remoteVideos {
border: $thumbnailsBorder solid transparent;
padding-left: 0;
transition: right 2s;
}
}
/**
* Re-styles the local Video to better fit vertical filmstrip layout.
*/
#filmstripLocalVideo {
align-self: initial;
bottom: 5px;
display: flex;
flex-direction: column-reverse;
height: auto;
justify-content: flex-start;
}
/**
* Remove unnecssary padding that is normally used to prevent horizontal
* filmstrip from overlapping the left edge of the screen.
*/
#filmstripLocalVideo,
#filmstripRemoteVideos {
padding: 0;
}
#filmstripRemoteVideos {
@include minHWAutoFix();
display: flex;
flex: 1;
flex-direction: column;
height: auto;
justify-content: flex-end;
#filmstripRemoteVideosContainer {
flex-direction: column-reverse;
/**
* Add padding as a hack for Firefox not to show scrollbars when
* unnecessary.
*/
padding: 1px 0;
overflow-x: hidden;
}
}
#remoteVideos {
@include minHWAutoFix();
flex-direction: column;
flex-grow: 1;
}
.remote-videos-container {
display: flex;
transition: opacity 1s;
}
}
/**
* Override other styles to support vertical filmstrip mode.
*/
.filmstrip-only .vertical-filmstrip {
.filmstrip {
flex-direction: row-reverse;
}
.filmstrip__videos-filmstripOnly {
margin-top: auto;
margin-bottom: auto;
height: 100%;
}
.filmstrip__videos {
&#filmstripLocalVideo {
bottom: 0px;
}
}
}
/**
* Workarounds for Edge and Firefox not handling scrolling properly with
* flex-direction: column-reverse. The remove videos in filmstrip should
* start scrolling from the bottom of the filmstrip, but in those browsers the
* scrolling won't happen. Per W3C spec, scrolling should happen from the
* bottom. As such, use css hacks to get around the css issue, with the intent
* being to remove the hacks as the spec is supported.
*/
@mixin undoColumnReverseVideos() {
.vertical-filmstrip {
#remoteVideos #filmstripRemoteVideos #filmstripRemoteVideosContainer {
flex-direction: column;
}
}
}
/** Firefox detection hack **/
@-moz-document url-prefix() {
@include undoColumnReverseVideos();
}
/** Edge detection hack **/
@supports (-ms-ime-align:auto) {
@include undoColumnReverseVideos();
}

View File

@@ -0,0 +1,105 @@
/**
* Rotate the hide filmstrip icon so it points towards the right edge
* of the screen.
*/
.vertical-filmstrip .filmstrip__toolbar {
transform: rotate(-90deg);
}
/**
* Overrides for video containers that should not be centered aligned to avoid=
* clashing with the filmstrip.
*/
.vertical-filmstrip #etherpad,
.vertical-filmstrip #sharedvideo {
text-align: left;
}
/**
* Overrides for small videos in vertical filmstrip mode.
*/
.vertical-filmstrip .filmstrip__videos .videocontainer {
/**
* Move status icons to the bottom right of the thumbnail.
*/
.videocontainer__toolbar {
/**
* FIXME: disable pointer to allow any elements moved below to still
* be clickable. The real fix would to make sure those moved elements
* are actually part of the toolbar instead of positioning being faked.
*/
pointer-events: none;
text-align: right;
> div {
pointer-events: none;
}
.right {
float: none;
margin: auto;
}
.toolbar-icon {
pointer-events: all;
}
}
/**
* Apply hardware acceleration to prevent flickering on scroll. The
* selectors are specific to icon wrappers to prevent fixed position dialogs
* and tooltips from getting a new location context due to translate3d.
*/
.connection-indicator,
.remote-video-menu-trigger,
.indicator-icon-container {
transform: translate3d(0, 0, 0);
}
.indicator-container {
float: none;
}
/**
* Move the remote video menu trigger to the bottom left of the video
* thumbnail.
*/
.remotevideomenu,
.remote-video-menu-trigger {
bottom: 0;
left: 0;
top: auto;
right: auto;
}
.remote-video-menu-trigger {
margin-bottom: 7px;
}
}
/**
* Overrides for quality labels in filmstrip only mode. The styles adjust the
* labels' positioning as the filmstrip itself or filmstrip's remote videos
* appear and disappear.
*
* The class with-filmstrip is for when the filmstrip is visible.
* The class without-filmstrip is for when the filmstrip has been toggled to
* be hidden.
* The class opening is for when the filmstrip is transitioning from hidden
* to visible.
*/
.vertical-filmstrip .large-video-labels {
&.with-filmstrip {
right: 150px;
}
&.with-filmstrip.opening {
transition: 0.9s;
transition-timing-function: ease-in-out;
}
&.without-filmstrip {
transition: 1.2s ease-in-out;
transition-delay: 0.1s;
}
}

View File

@@ -16,10 +16,6 @@
/* Mixins END */
/* Animations BEGIN */
@import "animations";
/* Animations END */
/* Fonts BEGIN */
@@ -31,6 +27,7 @@
/* Modules BEGIN */
@import 'aui_reset';
@import 'atlaskit_overrides';
@import 'base';
@import 'utils';
@import 'overlay/overlay';
@@ -41,8 +38,10 @@
@import 'modals/dialog';
@import 'modals/feedback/feedback';
@import 'modals/invite/info';
@import 'modals/settings/settings';
@import 'modals/speaker_stats/speaker_stats';
@import 'modals/video-quality/video-quality';
@import 'modals/local-recording/local-recording';
@import 'videolayout_default';
@import 'notice';
@import 'popup_menu';
@@ -53,7 +52,6 @@
@import 'welcome_page';
@import 'welcome_page_content';
@import 'toolbars';
@import 'side_toolbar_container';
@import 'jquery.contextMenu';
@import 'keyboard-shortcuts';
@import 'redirect_page';
@@ -67,10 +65,20 @@
@import '404';
@import 'policy';
@import 'popover';
@import 'filmstrip';
@import 'filmstrip/filmstrip_toolbar';
@import 'filmstrip/horizontal_filmstrip';
@import 'filmstrip/small_video';
@import 'filmstrip/tile_view';
@import 'filmstrip/tile_view_overrides';
@import 'filmstrip/vertical_filmstrip';
@import 'filmstrip/vertical_filmstrip_overrides';
@import 'unsupported-browser/main';
@import 'modals/invite/add-people';
@import 'vertical_filmstrip_overrides';
@import 'deep-linking/main';
@import 'transcription-subtitles';
@import '_meetings_list.scss';
@import 'navigate_section_list';
@import 'third-party-branding/google';
@import 'third-party-branding/microsoft';
/* Modules END */

View File

@@ -154,14 +154,6 @@
&-error {
margin-bottom: 8px;
}
/**
* Override Atlaskit dropdown styling when in a modal because the dropdown
* backgrounds clash with the modal backgrounds.
*/
.htclLc[data-role=droplistContent] {
border: 1px solid #455166;
}
}
.modal-dialog-footer {
font-size: $modalButtonFontSize;

View File

@@ -4,11 +4,7 @@
> div {
display: block;
margin-bottom: 10px;
}
> div:last-child {
margin-bottom: 5px;
margin-bottom: 4px;
}
.device-selector-icon {
@@ -18,6 +14,10 @@
margin-left: 3px;
}
.device-selector-label {
margin-bottom: 1px;
}
/* device-selector-trigger stylings attempt to mimic AtlasKit button */
.device-selector-trigger {
background-color: #0E1624;
@@ -101,19 +101,22 @@
.audio-output-preview {
font-size: 14px;
margin-top: 10px;
a {
color: 4C9AFF;
color: #2684FF;
cursor: pointer;
text-decoration: none;
}
a:hover {
color: #B3D4FF;
}
}
.audio-input-preview {
background: #7b7b7b;
background: #1B2638;
border-radius: 5px;
height: 6px;
height: 8px;
.audio-input-preview-level {
background: #4C9AFF;

View File

@@ -23,6 +23,21 @@
margin: auto;
}
}
.footer-text-wrap {
display: flex;
}
.footer-telephone-icon {
display: flex;
transform: scaleX(-1);
padding-left: 10px;
i {
line-height: 20px;
margin: auto;
}
}
}
}

View File

@@ -1,10 +1,7 @@
.use-new-toolbox {
font-size: 14px;
}
.info-dialog {
cursor: default;
display: flex;
font-size: 14px;
.info-dialog-action-link {
display: inline-block;
@@ -129,11 +126,16 @@
.dial-in-page {
align-items: center;
box-sizing: border-box;
display: flex;
flex-direction: column;
font-size: 24px;
height: 100%;
justify-content: center;
max-height: 100%;
overflow: auto;
padding: 25px;
position: absolute;
transform: translateY(-50%);
top: 50%;
width: 100%;
.dial-in-numbers-list {
@@ -143,6 +145,7 @@
.dial-in-conference-id {
text-align: center;
min-width: 200px;
width: 30%;
}
}

View File

@@ -0,0 +1,92 @@
.localrec-participant-stats {
list-style: none;
padding: 0;
width: 100%;
font-weight: 500;
.localrec-participant-stats-item__status-dot {
position: relative;
display: block;
width: 9px;
height: 9px;
border-radius: 50%;
margin: 0 auto;
&.status-on {
background: green;
}
&.status-off {
background: gray;
}
&.status-unknown {
background: darkgoldenrod;
}
&.status-error {
background: darkred;
}
}
.localrec-participant-stats-item__status,
.localrec-participant-stats-item__name,
.localrec-participant-stats-item__sessionid {
display: inline-block;
margin: 5px 0;
vertical-align: middle;
}
.localrec-participant-stats-item__status {
width: 5%;
}
.localrec-participant-stats-item__name {
width: 40%;
}
.localrec-participant-stats-item__sessionid {
width: 55%;
}
.localrec-participant-stats-item__name,
.localrec-participant-stats-item__sessionid {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
.localrec-control-info-label {
font-weight: bold;
}
.localrec-control-info-label:after {
content: ' ';
}
.localrec-control-action-link {
display: inline-block;
line-height: 1.5em;
a {
cursor: pointer;
vertical-align: middle;
}
}
.localrec-control-action-link:before {
color: $linkFontColor;
content: '\2022';
font-size: 1.5em;
padding: 0 10px;
vertical-align: middle;
}
.localrec-control-action-link:first-child:before {
content: '';
padding: 0;
}
.localrec-control-action-links {
font-weight: bold;
margin-top: 10px;
white-space: nowrap;
}

View File

@@ -0,0 +1,61 @@
.settings-pane {
display: flex;
width: 100%;
&.profile-pane {
flex-direction: column;
}
.auth-name {
margin-bottom: 4px;
}
.calendar-tab,
.device-selection {
margin-top: 20px;
}
.mock-atlaskit-label {
color: #56637A;
font-size: 12px;
font-weight: 600;
line-height: 1.33;
padding: 20px 0px 4px 0px;
}
.calendar-tab,
.more-tab,
.profile-edit {
display: flex;
width: 100%;
}
.profile-edit-field,
.settings-sub-pane {
flex: 1;
}
.profile-edit-field {
margin-right: 20px;
}
.language-settings {
max-width: 50%;
}
.calendar-tab {
align-items: center;
flex-direction: column;
font-size: 14px;
min-height: 100px;
text-align: center;
}
.calendar-tab-sign-in {
margin-top: 20px;
}
.sign-out-cta {
margin-bottom: 20px;
}
}

View File

@@ -141,62 +141,61 @@
}
}
.video-state-indicator {
background: $videoStateIndicatorBackground;
cursor: default;
font-size: 13px;
height: $videoStateIndicatorSize;
line-height: 20px;
text-align: left;
min-width: $videoStateIndicatorSize;
border-radius: 50%;
position: absolute;
box-sizing: border-box;
i {
line-height: $videoStateIndicatorSize;
}
/**
* Give the label padding so it has more volume and can be easily clicked.
*/
.video-quality-label-status {
line-height: $videoStateIndicatorSize;
min-width: $videoStateIndicatorSize;
text-align: center;
}
}
.centeredVideoLabel.moveToCorner {
z-index: $zindex3;
}
#videoResolutionLabel {
z-index: $zindex3 + 1;
}
.centeredVideoLabel {
bottom: 45%;
border-radius: 2px;
display: none;
padding: 10px;
transform: translate(-50%, 0);
z-index: $centeredVideoLabelZ;
&.moveToCorner {
bottom: auto;
transform: none;
-webkit-transition: all 2s 2s linear;
transition: all 2s 2s linear;
}
}
.moveToCorner {
.large-video-labels {
display: flex;
position: absolute;
top: 30px;
right: 30px;
transition: right 0.5s;
z-index: $zindex3;
.circular-label {
color: white;
font-weight: bold;
margin-left: 8px;
opacity: 0.8;
}
.circular-label {
background: #B8C7E0;
}
.circular-label.file {
background: #FF5630;
}
.circular-label.local-rec {
background: #FF5630;
}
.circular-label.stream {
background: #0065FF;
}
.recording-label.center-message {
background: $videoStateIndicatorBackground;
bottom: 50%;
display: block;
left: 50%;
padding: 10px;
position: fixed;
transform: translate(-50%, -50%);
z-index: $centeredVideoLabelZ;
}
}
.moveToCorner + .moveToCorner {
right: 80px;
}
.circular-label {
background: $videoStateIndicatorBackground;
border-radius: 50%;
box-sizing: border-box;
cursor: default;
font-size: 13px;
height: $videoStateIndicatorSize;
line-height: $videoStateIndicatorSize;
text-align: center;
min-width: $videoStateIndicatorSize;
}

View File

@@ -6,12 +6,10 @@
height: 100%;
position: fixed;
z-index: $ringingZ;
background: linear-gradient(transparent, #000);
opacity: 0.8;
@include transparentBg(#283447, 0.95);
&.solidBG {
background: $defaultBackground;
opacity: 1;
}
&__content {
@@ -22,20 +20,26 @@
top: 50%;
margin-left: -200px;
margin-top: -125px;
font-weight: 400;
font-size: 14px;
text-align: center;
font-weight: normal;
color: #FFFFFF;
}
&__avatar {
width: 100px;
height: 100px;
width: 128px;
height: 128px;
border-radius: 50%;
border: 2px solid #1B2638;
}
&__caller-info {
.mention {
color: #333;
}
&__status{
margin-top: 15px;
font-size: 14px;
line-height: 20px;
}
&__name {
font-size: 24px;
line-height: 32px;
}
}

View File

@@ -0,0 +1,32 @@
/**
* The Google sign in button must follow Google's design guidelines.
* See: https://developers.google.com/identity/branding-guidelines
*/
.google-sign-in {
background-color: #4285f4;
border-radius: 2px;
cursor: pointer;
display: inline-flex;
font-family: Roboto, arial, sans-serif;
font-size: 14px;
padding: 1px;
.google-cta {
color: white;
display: inline-block;
/**
* Hack the line height for vertical centering of text.
*/
line-height: 32px;
margin: 0 15px;
}
.google-logo {
background-color: white;
border-radius: 2px;
display: inline-block;
padding: 8px;
height: 18px;
width: 18px;
}
}

View File

@@ -0,0 +1,28 @@
/**
* The Microsoft sign in button must follow Microsoft's brand guidelines.
* See: https://docs.microsoft.com/en-us/azure/active-directory/
* develop/active-directory-branding-guidelines
*/
.microsoft-sign-in {
align-items: center;
background: #FFFFFF;
border: 1px solid #8C8C8C;
box-sizing: border-box;
cursor: pointer;
display: inline-flex;
font-family: Segoe UI, Roboto, arial, sans-serif;
height: 41px;
padding: 12px;
.microsoft-cta {
display: inline-block;
color: #5E5E5E;
font-size: 15px;
line-height: 41px;
}
.microsoft-logo {
display: inline-block;
margin-right: 12px;
}
}

View File

@@ -50,17 +50,21 @@ case "$1" in
db_get jicofo/jicofo-authpassword
if [ -z "$RET" ] ; then
db_input critical jicofo/jicofo-authpassword || true
db_go
# if password is missing generate it, and store it
JICOFO_AUTH_PASSWORD=`head -c 8 /dev/urandom | tr '\0-\377' 'a-zA-Z0-9a-zA-Z0-9a-zA-Z0-9a-zA-Z0-9@@@@####'`
db_set jicofo/jicofo-authpassword "$JICOFO_AUTH_PASSWORD"
else
JICOFO_AUTH_PASSWORD="$RET"
fi
JICOFO_AUTH_PASSWORD="$RET"
db_get jicofo/jicofosecret
if [ -z "$RET" ] ; then
db_input critical jicofo/jicofosecret || true
db_go
# if secret is missing generate it, and store it
JICOFO_SECRET=`head -c 8 /dev/urandom | tr '\0-\377' 'a-zA-Z0-9a-zA-Z0-9a-zA-Z0-9a-zA-Z0-9@@@@####'`
db_set jicofo/jicofosecret "$JICOFO_SECRET"
else
JICOFO_SECRET="$RET"
fi
JICOFO_SECRET="$RET"
JICOFO_AUTH_DOMAIN="auth.$JVB_HOSTNAME"
@@ -88,6 +92,7 @@ case "$1" in
if [ ! -f $PROSODY_HOST_CONFIG ] && ! grep -q "VirtualHost \"$JVB_HOSTNAME\"" $PROSODY_CONFIG_OLD; then
PROSODY_CONFIG_PRESENT="false"
mkdir -p /etc/prosody/conf.avail/
mkdir -p /etc/prosody/conf.d/
cp /usr/share/doc/jitsi-meet-prosody/prosody.cfg.lua-jvb.example $PROSODY_HOST_CONFIG
sed -i "s/jitmeet.example.com/$JVB_HOSTNAME/g" $PROSODY_HOST_CONFIG
sed -i "s/jitmeetSecret/$JVB_SECRET/g" $PROSODY_HOST_CONFIG
@@ -132,7 +137,9 @@ case "$1" in
ln -sf /var/lib/prosody/$JICOFO_AUTH_DOMAIN.crt $AUTH_CRT_FILE
ln -sf /var/lib/prosody/$JICOFO_AUTH_DOMAIN.crt /usr/local/share/ca-certificates/$JICOFO_AUTH_DOMAIN.crt
update-ca-certificates
# we need to force updating certificates, in some cases java trust
# store not get re-generated with latest changes
update-ca-certificates -f
# don't fail on systems with custom config ($PROSODY_HOST_CONFIG is missing)
if [ -f $PROSODY_HOST_CONFIG ]; then

View File

@@ -35,6 +35,14 @@ case "$1" in
if [ -n "$RET" ]; then
rm -f /etc/prosody/conf.avail/$JVB_HOSTNAME.cfg.lua
rm -f /etc/prosody/conf.d/$JVB_HOSTNAME.cfg.lua
# clean up generated certificates
rm -f /etc/prosody/certs/$JVB_HOSTNAME.crt
rm -f /etc/prosody/certs/$JVB_HOSTNAME.key
rm -f /etc/prosody/certs/auth.$JVB_HOSTNAME.crt
rm -f /etc/prosody/certs/auth.$JVB_HOSTNAME.key
rm -rf /var/lib/prosody/auth.$JVB_HOSTNAME.*
rm -rf /var/lib/prosody/$JVB_HOSTNAME.*
fi
# Clear the debconf variable

View File

@@ -28,6 +28,7 @@ Its constructor gets a number of options:
* **noSSL**: (optional, defaults to true) Boolean indicating if the server should be contacted using HTTP or HTTPS.
* **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.
Example:
@@ -184,7 +185,7 @@ messages. The listener will receive an object with the following structure:
}
```
* **displayNameChanged** - event notifications about display name
* **displayNameChange** - event notifications about display name
changes. The listener will receive an object with the following structure:
```javascript
{
@@ -193,6 +194,15 @@ changes. The listener will receive an object with the following structure:
}
```
* **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
}
```
* **participantJoined** - event notifications about new participants who join the room. The listener will receive an object with the following structure:
```javascript
{
@@ -289,6 +299,11 @@ You can get the display name of a participant in the conference with the followi
var 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);
```
You can get the iframe HTML element where Jitsi Meet is loaded with the following API function:
```javascript
var iframe = api.getIFrame();
@@ -296,32 +311,42 @@ var iframe = api.getIFrame();
You can check whether the audio is muted with the following API function:
```javascript
isAudioMuted().then(function(muted) {
api.isAudioMuted().then(function(muted) {
...
});
```
You can check whether the video is muted with the following API function:
```javascript
isVideoMuted().then(function(muted) {
api.isVideoMuted().then(function(muted) {
...
});
```
You can check whether the audio is available with the following API function:
```javascript
isAudioAvailable().then(function(available) {
api.isAudioAvailable().then(function(available) {
...
});
```
You can check whether the video is available with the following API function:
```javascript
isVideoAvailable().then(function(available) {
api.isVideoAvailable().then(function(available) {
...
});
```
You can invite new participants to the call with the following API function:
```javascript
api.invite([{...}, {...}, {...}]).then(function() {
// success
}).catch(function() {
// failure
});
```
**NOTE: The format of the invitees in the array depends on the invite service used for the deployment.**
You can remove the embedded Jitsi Meet Conference with the following API function:
```javascript
api.dispose()

View File

@@ -13,7 +13,7 @@ There are also some complete [example config files](https://github.com/jitsi/jit
## Network description
This how the network look like:
This is how the network looks:
```
+ +
| |
@@ -135,7 +135,8 @@ server {
location / {
ssi on;
}
# BOSH
# BOSH, Bidirectional-streams Over Synchronous HTTP
# https://en.wikipedia.org/wiki/BOSH_(protocol)
location /http-bind {
proxy_pass http://localhost:5280/http-bind;
proxy_set_header X-Forwarded-For $remote_addr;

28
doc/mobile-dropbox.md Normal file
View File

@@ -0,0 +1,28 @@
# Setting up Dropbox integration
1. Create a Dropbox app.
2. Add the following to ```ios/app/src/Info.plist``` by replacing `<APP_KEY>`
with your own Dropbox app key (which can be found in the
[App Console](https://www.dropbox.com/developers/apps)):
```
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string></string>
<key>CFBundleURLSchemes</key>
<array>
<string>db-<APP_KEY></string>
</array>
</dict>
</array>
<key>LSApplicationQueriesSchemes</key>
<array>
<string>dbapi-2</string>
<string>dbapi-8-emm</string>
</array>
```
**NOTE:** Both Android and iOS builds of the apps will parse the Dropbox app key
from ```ios/app/src/Info.plist```.
**NOTE:** See [Dropbox developer guide](https://www.dropbox.com/developers/reference/developer-guide) for more information

22
doc/mobile-google-auth.md Normal file
View File

@@ -0,0 +1,22 @@
# Setting up Google Authentication
- Create a Firebase project here: https://firebase.google.com/. You'll need a
signed Android build for that, that can be a debug self-signed build too, just
retrieve the signing hash. The key hash of an already signed ap can be obtained
as follows (on macOS): ```keytool -list -printcert -jarfile the-app.apk```
- Place the generated ```google-services.json``` file in ```android/app```
for Android and the ```GoogleService-Info.plist``` into ```ios/app/src``` for
iOS (you can stop at that step, no need for the driver and the code changes they
suggest in the wizard).
- You may want to exclude these files in YOUR GIT config (do not exclude them in
the ```.gitignore``` of the application itself!).
- Your web client ID is auto generated during the Firebase project
creation. Find them in the Google Developer console
(https://console.developers.google.com/)
- Make sure your config reflects this ID by setting
```googleApiApplicationClientID``` in config.js.
- Add your iOS client ID (the REVERSED_CLIENT_ID in the plist file) as an
application URL schema into ```ios/app/src/Info.plist```
(replacing placeholder).
- Enable YouTube API access on the developer console (see above) to enable live
streaming.

View File

@@ -4,7 +4,13 @@ This document describes the required steps for a quick Jitsi Meet installation o
Debian Wheezy and other older systems may require additional things to be done. Specifically for Wheezy, [libc needs to be updated](http://lists.jitsi.org/pipermail/users/2015-September/010064.html).
N.B.: All commands are supposed to be run by root. If you are logged in as a regular user with sudo rights, please prepend ___sudo___ to each of the commands.
Also note that a recent default Ubuntu installation has only the `main` repository enabled, and Jitsi Meet needs packages from `universe`. Check your `/etc/apt/sources.list` file, and if `universe` is not present refer to [Ubuntu's documentation](https://help.ubuntu.com/community/Repositories/Ubuntu) on how to enable it. (Usually it amounts to copying the `main` lines and changing to `universe`.)
N.B.:
a.) All commands are supposed to be run by root. If you are logged in as a regular user with sudo rights, please prepend ___sudo___ to each of the commands.
b.) You only need to do this if you want to ___host your own Jitsi server___. If you just want to have a video conference with someone, use https://meet.jit.si instead.
## Basic Jitsi Meet install
@@ -59,6 +65,17 @@ org.ice4j.ice.harvest.NAT_HARVESTER_PUBLIC_ADDRESS=<Public.IP.Address>
See [the documenation of ice4j](https://github.com/jitsi/ice4j/blob/master/doc/configuration.md)
for details.
Default deployments on systems using systemd will have low default values for maximum processes and open files. If the used bridge will expect higher number of participants the default values need to be adjusted (the default values are good for less than 100 participants).
To update the values edit `/etc/systemd/system.conf` and make sure you have the following values:
```
DefaultLimitNOFILE=65000
DefaultLimitNPROC=65000
DefaultTasksMax=65000
```
To load the values and check them look [here](#systemd-details) for details.
By default, anyone who has access to your jitsi instance will be able to start a conferencee: if your server is open to the world, anyone can have a chat with anyone else. If you want to limit the ability to start a conference to registered users, set up a "secure domain". Follow the instructions at https://github.com/jitsi/jicofo#secure-domain.
### Open a conference
Launch a web browser (Chrome, Chromium or latest Opera) and enter in the URL bar the hostname (or IP address) you used in the previous step.
@@ -103,3 +120,12 @@ Sometimes the following packages will fail to uninstall properly:
When this happens, just run the uninstall command a second time and it should be ok.
The reason for failure is that sometimes, the uninstall script is faster than the process that stops the daemons. The second run of the uninstall command fixes this, as by then the jigasi or jvb daemons are already stopped.
#### Systemd details
To reload the systemd changes on a running system execute `systemctl daemon-reload` and `service jitsi-videobridge restart`.
To check the tasks part execute `service jitsi-videobridge status` and you should see `Tasks: XX (limit: 65000)`.
To check the files and process part execute ```cat /proc/`cat /var/run/jitsi-videobridge.pid`/limits``` and you should see:
```
Max processes 65000 65000 processes
Max open files 65000 65000 files
```

View File

@@ -1,6 +1,6 @@
One-to-one calls should avoid going throught the JVB for optimal performance and for optimal resource usage. This is why we've added the peer-to-peer mode where the two participants connect directly to each other. Unfortunately, a direct connection is not always possible between the participants. In those cases you can use a TURN server to relay the traffic (n.b. the JVB does much more than just relay the traffic, so this is not the same as using the JVB to "relay" the traffic).
This document describes how to enable TURN server support in one-to-one calls in Jitsi Meet. Even tho it gives some hints how to configure [prosody](prosody.im) and [coTURN](https://github.com/coturn/coturn), it assumes a properly configured TURN server and a proprely configured XMPP server.
This document describes how to enable TURN server support in one-to-one calls in Jitsi Meet, even though it gives some hints how to configure [prosody](prosody.im) and [coTURN](https://github.com/coturn/coturn), it assumes a properly configured TURN server and a proprely configured XMPP server.
One way to configure TURN support in meet with a static configuration. You can simply fill out the `p2p.stunServers` option with appropriate values, e.g.:

3686
flow-typed/npm/jquery_v3.x.x.js vendored Normal file

File diff suppressed because it is too large Load Diff

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