Compare commits

...

157 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
447 changed files with 18576 additions and 12421 deletions

View File

@@ -38,7 +38,23 @@ 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
@@ -67,4 +83,4 @@ module.file_ext=.jsx
module.file_ext=.json
[version]
^0.67.0
^0.78.0

View File

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

View File

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

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

@@ -491,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'
@@ -27,9 +28,13 @@ android {
}
buildTypes {
debug {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules-debug.pro'
}
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules-release.pro'
}
}
@@ -40,7 +45,69 @@ android {
}
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.
@@ -71,6 +67,23 @@
# FastImage
-keep public class com.dylanvann.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

@@ -27,11 +27,9 @@ 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.HashMap;
import java.util.List;
import java.util.Map;
@@ -229,20 +227,4 @@ public class MainActivity extends JitsiMeetActivity {
addPeopleController.endAddPeople();
}
}
@Override
public void onRequestPermissionsResult(
int requestCode,
String[] permissions,
int[] grantResults) {
CalendarEventsPackage.onRequestPermissionsResult(
requestCode,
permissions,
grantResults);
super.onRequestPermissionsResult(
requestCode,
permissions,
grantResults);
}
}

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,6 +17,7 @@ buildscript {
allprojects {
repositories {
maven { url "https://maven.google.com" }
google()
jcenter()
maven { url "$rootDir/../node_modules/jsc-android/dist" }
@@ -151,10 +153,11 @@ allprojects {
}
ext {
buildToolsVersion = "26.0.2"
compileSdkVersion = 26
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
@@ -177,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,132 +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-fast-image')
compile project(':react-native-immersive')
compile project(':react-native-keep-awake')
compile project(':react-native-linear-gradient')
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")
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("${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

@@ -27,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 +43,7 @@ import java.net.URL;
* {@code JitsiMeetView} static methods.
*/
public class JitsiMeetActivity
extends AppCompatActivity {
extends AppCompatActivity implements JitsiMeetActivityInterface {
/**
* The request code identifying requests for the permission to draw on top
@@ -174,7 +175,12 @@ public class JitsiMeetActivity
if (Settings.canDrawOverlays(this)) {
initializeContentView();
}
return;
}
ReactActivityLifecycleCallbacks.onActivityResult(
this, requestCode, resultCode, data);
}
@Override
@@ -260,6 +266,15 @@ public class JitsiMeetActivity
ReactActivityLifecycleCallbacks.onNewIntent(intent);
}
// https://developer.android.com/reference/android/support/v4/app/ActivityCompat.OnRequestPermissionsResultCallback
@Override
public void onRequestPermissionsResult(
final int requestCode,
final String[] permissions,
final int[] grantResults) {
ReactActivityLifecycleCallbacks.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
@Override
protected void onResume() {
super.onResume();
@@ -283,6 +298,14 @@ public class JitsiMeetActivity
}
}
/**
* Implementation of the {@code PermissionAwareActivity} interface.
*/
@Override
public void requestPermissions(String[] permissions, int requestCode, PermissionListener listener) {
ReactActivityLifecycleCallbacks.requestPermissions(this, permissions, requestCode, listener);
}
/**
*
* @see JitsiMeetView#setDefaultURL(URL)

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,11 +16,16 @@
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
@@ -28,6 +33,37 @@ import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
* 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
@@ -107,6 +143,11 @@ public class ReactActivityLifecycleCallbacks {
if (reactInstanceManager != null) {
reactInstanceManager.onHostResume(activity, defaultBackButtonImpl);
}
if (permissionsCallback != null) {
permissionsCallback.invoke();
permissionsCallback = null;
}
}
/**
@@ -126,4 +167,29 @@ public class ReactActivityLifecycleCallbacks {
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

@@ -45,6 +45,7 @@ class ReactInstanceManagerHolder {
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)
);
@@ -119,6 +120,7 @@ class ReactInstanceManagerHolder {
.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())

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

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

View File

@@ -5,6 +5,8 @@ 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-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'

View File

@@ -11,6 +11,7 @@ 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 {
@@ -30,14 +31,16 @@ 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,
@@ -74,14 +77,10 @@ import {
getAvatarURLByParticipantId,
getLocalParticipant,
getParticipantById,
hiddenParticipantJoined,
hiddenParticipantLeft,
localParticipantConnectionStatusChanged,
localParticipantRoleChanged,
MAX_DISPLAY_NAME_LENGTH,
participantConnectionStatusChanged,
participantJoined,
participantLeft,
participantPresenceChanged,
participantRoleChanged,
participantUpdated
@@ -98,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 {
@@ -274,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();
/**
*
*/
@@ -404,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:
@@ -856,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);
@@ -1261,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);
});
});
},
/**
@@ -1300,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);
});
});
},
/**
@@ -1661,22 +1699,14 @@ export default {
room.on(JitsiConferenceEvents.PARTCIPANT_FEATURES_CHANGED,
user => APP.UI.onUserFeaturesChanged(user));
room.on(JitsiConferenceEvents.USER_JOINED, (id, user) => {
const displayName = user.getDisplayName();
// The logic shared between RN and web.
commonUserJoinedHandling(APP.store, room, user);
if (user.isHidden()) {
APP.store.dispatch(hiddenParticipantJoined(id, displayName));
return;
}
APP.store.dispatch(participantJoined({
botType: user.getBotType(),
conference: room,
id,
name: displayName,
presence: user.getStatus(),
role: user.getRole()
}));
const displayName = user.getDisplayName();
logger.log(`USER ${id} connnected:`, user);
APP.API.notifyUserJoined(id, {
@@ -1691,13 +1721,13 @@ export default {
});
room.on(JitsiConferenceEvents.USER_LEFT, (id, user) => {
if (user.isHidden()) {
APP.store.dispatch(hiddenParticipantLeft(id));
// The logic shared between RN and web.
commonUserLeftHandling(APP.store, room, user);
if (user.isHidden()) {
return;
}
APP.store.dispatch(participantLeft(id, room));
logger.log(`USER ${id} LEFT:`, user);
APP.API.notifyUserLeft(id);
APP.UI.messageHandler.participantNotification(
@@ -1796,35 +1826,6 @@ export default {
JitsiConferenceEvents.DOMINANT_SPEAKER_CHANGED,
id => APP.store.dispatch(dominantSpeakerChanged(id, room)));
if (!interfaceConfig.filmStripOnly) {
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);
});
}
}
room.on(JitsiConferenceEvents.CONNECTION_INTERRUPTED, () => {
APP.store.dispatch(localParticipantConnectionStatusChanged(
JitsiParticipantConnectionStatus.INTERRUPTED));
@@ -2010,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());
@@ -2375,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',
@@ -2388,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',

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.
@@ -171,6 +171,10 @@ var config = {
// Whether to enable file recording or not.
// fileRecordingsEnabled: false,
// Enable the dropbox integration.
// dropbox: {
// appKey: '<APP_KEY>' // Specify your app key here.
// },
// Whether to enable live streaming or not.
// liveStreamingEnabled: false,
@@ -237,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',
@@ -400,6 +400,7 @@ var config = {
externalConnectUrl
firefox_fake_device
googleApiApplicationClientID
googleApiIOSClientID
iAmRecorder
iAmSipGateway
microsoftApiApplicationClientID

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

@@ -10,3 +10,48 @@
-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

@@ -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: 15px;
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,41 +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: #4C9AFF
}
.errorMessage {
color: red;
#chat-input {
background-color: $newToolbarBackgroundColor;
display: flex;
}
.remoteuser {
color: #B8C7E0;
}
.usrmsg-form {
flex: 1;
margin-left: 5px;
}
#usermsg {
background-color: $newToolbarBackgroundColor;
visibility:hidden;
position: absolute;
bottom: 0px;
right: 0px;
width: 83%;
height: 30px;
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;
color: white;
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 {
@@ -97,10 +145,6 @@
box-shadow: none;
}
#chat_container.is-conversation-mode #usermsg {
visibility: visible;
}
#nickname {
position: absolute;
text-align: center;
@@ -112,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;
@@ -141,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-color: $newToolbarBackgroundColor;;
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: white;
overflow: hidden;
padding-bottom: 3px;
position: relative;
&.localuser .display-name {
color: #4C9AFF
}
&.error {
.chatArrow,
.timestamp,
.display-name {
display: none;
}
.usermessage {
color: red;
padding: 0;
}
}
}
.smiley {
@@ -171,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;
@@ -187,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-color: $newToolbarBackgroundColor;
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-color: $newToolbarBackgroundColor;
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 {

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

@@ -13,31 +13,68 @@
float: left;
}
.navigate-section-list-tile {
height: 90px;
width: 260px;
border-radius: 4px;
background-color: #1754A9;
margin-right: 8px;
padding: 16px;
display: inline-block;
border-radius: 4px;
box-sizing: border-box;
cursor: pointer;
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

@@ -3,8 +3,23 @@
}
.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 {
border-bottom: 2px solid rgba(0, 0, 0, 0.3);
display: flex;
flex-direction: column;
margin-bottom: 10px;
@@ -32,7 +47,7 @@
}
}
.logged-in-pannel {
.logged-in-panel {
padding: 10px;
}
}

View File

@@ -1,105 +0,0 @@
/**
* Toolbar side panel main container element.
*/
#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;
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 {
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;
}
}
.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;
}
}

View File

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

@@ -144,7 +144,7 @@ $watermarkHeight: 74px;
/**
* Welcome page variables.
*/
$welcomePageDescriptionColor: #E6EDFA;
$welcomePageDescriptionColor: #fff;
$welcomePageFontFamily: inherit;
$welcomePageHeaderBackground: #1D69D4;
$welcomePageHeaderBackground: linear-gradient(-90deg, #1251AE 0%, #0074FF 50%, #1251AE 100%);
$welcomePageTitleColor: #fff;

View File

@@ -86,7 +86,7 @@
* wrapper needed before we're able to move all top toolbar indicators
* creation to react.
*/
.ckAJgx {
.sc-ifAKCX {
background: none;
}
}
@@ -557,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);

View File

@@ -4,7 +4,7 @@ body.welcome-page {
}
.welcome {
background-color: $welcomePageHeaderBackground;
background-image: $welcomePageHeaderBackground;
display: flex;
flex-direction: column;
font-family: $welcomePageFontFamily;
@@ -24,8 +24,8 @@ body.welcome-page {
.header-text {
display: flex;
flex-direction: column;
margin-top: $watermarkHeight + 80;
margin-bottom: 36px;
margin-top: $watermarkHeight + 35;
margin-bottom: 35px;
max-width: calc(100% - 40px);
width: 650px;
z-index: $zindex2;
@@ -35,7 +35,6 @@ body.welcome-page {
color: $welcomePageTitleColor;
font-size: 2.5rem;
font-weight: 500;
letter-spacing: 0;
line-height: 1.18;
margin-bottom: 16px;
}
@@ -45,27 +44,123 @@ body.welcome-page {
font-size: 1rem;
font-weight: 400;
line-height: 24px;
margin-bottom: 20px;
}
#enter_room {
align-items: center;
display: flex;
align-items: center;
max-width: calc(100% - 40px);
margin-bottom: 20px;
position: relative;
width: 650px;
width: 680px;
z-index: $zindex2;
background-color: #fff;
padding: 25px 30px;
.enter-room-input {
display: inline-block;
margin-right: 8px;
.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-page-settings {
color: $welcomePageDescriptionColor;
position: absolute;
top: 32px;
right: 32px;
z-index: $zindex2;
* {
cursor: pointer;
font-size: 32px;
}
}
.welcome-watermark {

View File

@@ -7,9 +7,7 @@
* see.
*/
.active-speaker {
border: $thumbnailVideoBorder solid $videoThumbnailSelected;
box-shadow: inset 0 0 3px $videoThumbnailSelected,
0 0 3px $videoThumbnailSelected;
box-shadow: 0 0 5px 3px $videoThumbnailSelected
}
#filmstripRemoteVideos {
@@ -62,9 +60,10 @@
box-sizing: border-box;
/**
* Allow scrolling of the thumbnails.
* Allow vertical scrolling of the thumbnails.
*/
overflow: auto;
overflow-x: hidden;
overflow-y: auto;
}
/**
@@ -83,6 +82,7 @@
padding: 100px 0;
.videocontainer {
border: 0;
box-sizing: border-box;
display: block;
margin: 5px;

View File

@@ -16,10 +16,6 @@
/* Mixins END */
/* Animations BEGIN */
@import "animations";
/* Animations END */
/* Fonts BEGIN */
@@ -56,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';
@@ -81,6 +76,7 @@
@import 'modals/invite/add-people';
@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';

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

@@ -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,6 +4,8 @@ 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).
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.
@@ -63,6 +65,15 @@ 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
@@ -109,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
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 612 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 889 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

View File

@@ -52,7 +52,7 @@ var interfaceConfig = {
'tileview'
],
SETTINGS_SECTIONS: [ 'devices', 'language', 'moderator', 'profile' ],
SETTINGS_SECTIONS: [ 'devices', 'language', 'moderator', 'profile', 'calendar' ],
// Determines how the video would fit the screen. 'both' would fit the whole
// screen, 'height' would fit the original video height to the height of the

View File

@@ -26,6 +26,8 @@ target 'JitsiMeet' do
pod 'Folly',
:podspec => '../node_modules/react-native/third-party-podspecs/Folly.podspec'
pod 'ObjectiveDropboxOfficial'
pod 'react-native-background-timer',
:path => '../node_modules/react-native-background-timer'
pod 'react-native-fast-image',
@@ -35,8 +37,8 @@ target 'JitsiMeet' do
pod 'react-native-locale-detector',
:path => '../node_modules/react-native-locale-detector'
pod 'react-native-webrtc', :path => '../node_modules/react-native-webrtc'
pod 'ReactNativePermissions',
:path => '../node_modules/react-native-permissions'
pod 'RNGoogleSignin',
:path => '../node_modules/react-native-google-signin'
pod 'RNSound', :path => '../node_modules/react-native-sound'
pod 'RNVectorIcons', :path => '../node_modules/react-native-vector-icons'
pod 'react-native-calendar-events',

View File

@@ -1,17 +1,38 @@
PODS:
- boost-for-react-native (1.63.0)
- DoubleConversion (1.1.5)
- DoubleConversion (1.1.6)
- FLAnimatedImage (1.0.12)
- Folly (2016.09.26.00):
- Folly (2016.10.31.00):
- boost-for-react-native
- DoubleConversion
- glog
- glog (0.3.4)
- React (0.55.4):
- React/Core (= 0.55.4)
- glog (0.3.5)
- GoogleSignIn (4.3.0):
- "GoogleToolboxForMac/NSDictionary+URLArguments (~> 2.1)"
- "GoogleToolboxForMac/NSString+URLArguments (~> 2.1)"
- GTMOAuth2 (~> 1.0)
- GTMSessionFetcher/Core (~> 1.1)
- GoogleToolboxForMac/DebugUtils (2.1.4):
- GoogleToolboxForMac/Defines (= 2.1.4)
- GoogleToolboxForMac/Defines (2.1.4)
- "GoogleToolboxForMac/NSDictionary+URLArguments (2.1.4)":
- GoogleToolboxForMac/DebugUtils (= 2.1.4)
- GoogleToolboxForMac/Defines (= 2.1.4)
- "GoogleToolboxForMac/NSString+URLArguments (= 2.1.4)"
- "GoogleToolboxForMac/NSString+URLArguments (2.1.4)"
- GTMOAuth2 (1.1.6):
- GTMSessionFetcher (~> 1.1)
- GTMSessionFetcher (1.2.0):
- GTMSessionFetcher/Full (= 1.2.0)
- GTMSessionFetcher/Core (1.2.0)
- GTMSessionFetcher/Full (1.2.0):
- GTMSessionFetcher/Core (= 1.2.0)
- ObjectiveDropboxOfficial (3.9.2)
- React (0.57.1):
- React/Core (= 0.57.1)
- react-native-background-timer (2.0.0):
- React
- react-native-calendar-events (1.6.1):
- react-native-calendar-events (1.6.2):
- React
- react-native-fast-image (4.0.14):
- FLAnimatedImage
@@ -22,66 +43,68 @@ PODS:
- React
- react-native-locale-detector (1.0.0):
- React
- react-native-webrtc (1.58.2):
- react-native-webrtc (1.67.1):
- React
- React/Core (0.55.4):
- yoga (= 0.55.4.React)
- React/CxxBridge (0.55.4):
- Folly (= 2016.09.26.00)
- React/Core (0.57.1):
- yoga (= 0.57.1.React)
- React/CxxBridge (0.57.1):
- Folly (= 2016.10.31.00)
- React/Core
- React/cxxreact
- React/cxxreact (0.55.4):
- React/cxxreact (0.57.1):
- boost-for-react-native (= 1.63.0)
- Folly (= 2016.09.26.00)
- Folly (= 2016.10.31.00)
- React/jschelpers
- React/jsinspector
- React/DevSupport (0.55.4):
- React/DevSupport (0.57.1):
- React/Core
- React/RCTWebSocket
- React/fishhook (0.55.4)
- React/jschelpers (0.55.4):
- Folly (= 2016.09.26.00)
- React/fishhook (0.57.1)
- React/jschelpers (0.57.1):
- Folly (= 2016.10.31.00)
- React/PrivateDatabase
- React/jsinspector (0.55.4)
- React/PrivateDatabase (0.55.4)
- React/RCTActionSheet (0.55.4):
- React/jsinspector (0.57.1)
- React/PrivateDatabase (0.57.1)
- React/RCTActionSheet (0.57.1):
- React/Core
- React/RCTAnimation (0.55.4):
- React/RCTAnimation (0.57.1):
- React/Core
- React/RCTBlob (0.55.4):
- React/RCTBlob (0.57.1):
- React/Core
- React/RCTImage (0.55.4):
- React/RCTImage (0.57.1):
- React/Core
- React/RCTNetwork
- React/RCTLinkingIOS (0.55.4):
- React/RCTLinkingIOS (0.57.1):
- React/Core
- React/RCTNetwork (0.55.4):
- React/RCTNetwork (0.57.1):
- React/Core
- React/RCTText (0.55.4):
- React/RCTText (0.57.1):
- React/Core
- React/RCTWebSocket (0.55.4):
- React/RCTWebSocket (0.57.1):
- React/Core
- React/fishhook
- React/RCTBlob
- ReactNativePermissions (1.1.1):
- RNGoogleSignin (1.0.0-rc6):
- GoogleSignIn
- React
- RNSound (0.10.9):
- React/Core
- RNSound/Core (= 0.10.9)
- RNSound/Core (0.10.9):
- React/Core
- RNVectorIcons (4.4.2):
- RNVectorIcons (6.0.2):
- React
- SDWebImage/Core (4.4.2)
- SDWebImage/GIF (4.4.2):
- FLAnimatedImage (~> 1.0)
- SDWebImage/Core
- yoga (0.55.4.React)
- yoga (0.57.1.React)
DEPENDENCIES:
- DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`)
- Folly (from `../node_modules/react-native/third-party-podspecs/Folly.podspec`)
- glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`)
- ObjectiveDropboxOfficial
- react-native-background-timer (from `../node_modules/react-native-background-timer`)
- react-native-calendar-events (from `../node_modules/react-native-calendar-events`)
- react-native-fast-image (from `../node_modules/react-native-fast-image`)
@@ -98,7 +121,7 @@ DEPENDENCIES:
- React/RCTNetwork (from `../node_modules/react-native`)
- React/RCTText (from `../node_modules/react-native`)
- React/RCTWebSocket (from `../node_modules/react-native`)
- ReactNativePermissions (from `../node_modules/react-native-permissions`)
- RNGoogleSignin (from `../node_modules/react-native-google-signin`)
- RNSound (from `../node_modules/react-native-sound`)
- RNVectorIcons (from `../node_modules/react-native-vector-icons`)
- yoga (from `../node_modules/react-native/ReactCommon/yoga`)
@@ -107,6 +130,11 @@ SPEC REPOS:
https://github.com/cocoapods/specs.git:
- boost-for-react-native
- FLAnimatedImage
- GoogleSignIn
- GoogleToolboxForMac
- GTMOAuth2
- GTMSessionFetcher
- ObjectiveDropboxOfficial
- SDWebImage
EXTERNAL SOURCES:
@@ -130,8 +158,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native-locale-detector"
react-native-webrtc:
:path: "../node_modules/react-native-webrtc"
ReactNativePermissions:
:path: "../node_modules/react-native-permissions"
RNGoogleSignin:
:path: "../node_modules/react-native-google-signin"
RNSound:
:path: "../node_modules/react-native-sound"
RNVectorIcons:
@@ -141,23 +169,28 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS:
boost-for-react-native: 39c7adb57c4e60d6c5479dd8623128eb5b3f0f2c
DoubleConversion: e22e0762848812a87afd67ffda3998d9ef29170c
DoubleConversion: bb338842f62ab1d708ceb63ec3d999f0f3d98ecd
FLAnimatedImage: 4a0b56255d9b05f18b6dd7ee06871be5d3b89e31
Folly: 211775e49d8da0ca658aebc8eab89d642935755c
glog: 1de0bb937dccdc981596d3b5825ebfb765017ded
React: aa2040dbb6f317b95314968021bd2888816e03d5
Folly: c89ac2d5c6ab169cd7397ef27485c44f35f742c7
glog: e8acf0ebbf99759d3ff18c86c292a5898282dcde
GoogleSignIn: 11183592dc63e105475c7305a325045ff95e02b7
GoogleToolboxForMac: 91c824d21e85b31c2aae9bb011c5027c9b4e738f
GTMOAuth2: c77fe325e4acd453837e72d91e3b5f13116857b2
GTMSessionFetcher: 0c4baf0a73acd0041bf9f71ea018deedab5ea84e
ObjectiveDropboxOfficial: aa792e0556ceb7b72955fa29a2709072f6e35fd9
React: 1fe0eb13d90b625d94c3b117c274dcfd2e760e11
react-native-background-timer: 63dcbf37dbcf294b5c6c071afcdc661fa06a7594
react-native-calendar-events: fe6fbc8ed337a7423c98f2c9012b25f20444de09
react-native-fast-image: cba3d9bf9c2cf8ddb643d887a686c53a5dd90a2c
react-native-keep-awake: 0de4bd66de0c23178107dce0c2fcc3354b2a8e94
react-native-locale-detector: d1b2c6fe5abb56e3a1efb6c2d6f308c05c4251f1
react-native-webrtc: 31b6d3f1e3e2ce373aa43fd682b04367250f807d
ReactNativePermissions: 9f2d9c45c98800795e6c2ed330e25d11a66a8169
RNGoogleSignin: 44debd8c359a662c0e2d585952e88b985bf78008
RNSound: b360b3862d3118ed1c74bb9825696b5957686ac4
RNVectorIcons: c0dbfbf6068fefa240c37b0f71bd03b45dddac44
RNVectorIcons: 8c52e1e8da1153613fdef44748e865c25556cb9c
SDWebImage: 624d6e296c69b244bcede364c72ae0430ac14681
yoga: a23273df0088bf7f2bb7e5d7b00044ea57a2a54a
yoga: b1ce48b6cf950b98deae82838f5173ea7cf89e85
PODFILE CHECKSUM: 1d5c8382f73d9540fac68d93b32e1d3b58d069ee
PODFILE CHECKSUM: cf8276ba4b0933b24c6082a25a5f4eabe0ba4ea6
COCOAPODS: 1.5.3

View File

@@ -125,7 +125,23 @@ continueUserActivity:(NSUserActivity *)userActivity
continueUserActivity:userActivity
restorationHandler:restorationHandler];
}
```
And also one of the following:
```objc
// See https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1623073-application?language=objc
- (BOOL)application:(UIApplication *)app
openURL:(NSURL *)url
options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
return [JitsiMeetView application:app
openURL:url
options: options];
}
```
or
```objc
// See https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1623112-application?language=objc
- (BOOL)application:(UIApplication *)application
openURL:(NSURL *)url
sourceApplication:(NSString *)sourceApplication
@@ -138,6 +154,8 @@ continueUserActivity:(NSUserActivity *)userActivity
}
```
NOTE: The latter is deprecated.
### JitsiMeetViewDelegate
This delegate is optional, and can be set on the `JitsiMeetView` instance using
@@ -212,3 +230,39 @@ resize `JitsiMeetView`.
If `pictureInPictureEnabled` is set to `YES` or `delegate` implements
`enterPictureInPicture:`, the in-call toolbar will render a button to afford the
user to request entering Picture-in-Picture.
## Dropbox integration
To setup the Dropbox integration, follow these steps:
1. Add the following to the app's Info.plist and change `<APP_KEY>` to your
Dropbox app key:
```
<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>
```
2. Add the following to the app's `AppDelegate`:
```objc
- (BOOL)application:(UIApplication *)app
openURL:(NSURL *)url
options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
return [JitsiMeetView application:app
openURL:url
options:options];
}
```

View File

@@ -138,7 +138,7 @@
83CBB9F71A601CBA00E9B192 /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 0920;
LastUpgradeCheck = 1000;
ORGANIZATIONNAME = Facebook;
TargetAttributes = {
13B07F861A680F5B00A75B9A = {
@@ -327,12 +327,14 @@
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
@@ -383,12 +385,14 @@
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0920"
LastUpgradeVersion = "1000"
version = "1.3">
<BuildAction
parallelizeBuildables = "NO"
@@ -40,7 +40,6 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
@@ -60,7 +59,6 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"

View File

@@ -36,14 +36,13 @@
restorationHandler:restorationHandler];
}
- (BOOL)application:(UIApplication *)application
- (BOOL)application:(UIApplication *)app
openURL:(NSURL *)url
sourceApplication:(NSString *)sourceApplication
annotation:(id)annotation {
return [JitsiMeetView application:application
options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
return [JitsiMeetView application:app
openURL:url
sourceApplication:sourceApplication
annotation:annotation];
options:options];
}
@end

View File

@@ -32,6 +32,16 @@
<string>org.jitsi.meet</string>
</array>
</dict>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string>com.googleusercontent.apps</string>
<key>CFBundleURLSchemes</key>
<array>
<string>com.googleusercontent.apps.YOUR_ID_HERE</string>
</array>
</dict>
</array>
<key>CFBundleVersion</key>
<string>1</string>

View File

@@ -3,14 +3,17 @@
# This script is executed from Xcode to start the React packager for Debug
# targets.
export RCT_METRO_PORT="${RCT_METRO_PORT:=8081}"
echo "export RCT_METRO_PORT=${RCT_METRO_PORT}" > "${SRCROOT}/../../node_modules/react-native/scripts/.packager.env"
if [[ "$CONFIGURATION" = "Debug" ]]; then
if nc -w 5 -z localhost 8081 ; then
if ! curl -s "http://localhost:8081/status" | grep -q "packager-status:running" ; then
echo "Port 8081 already in use, packager is either not running or not running correctly"
if nc -w 5 -z localhost ${RCT_METRO_PORT} ; then
if ! curl -s "http://localhost:${RCT_METRO_PORT}/status" | grep -q "packager-status:running" ; then
echo "Port ${RCT_METRO_PORT} already in use, packager is either not running or not running correctly"
exit 2
fi
else
open -g "$SRCROOT/../../node_modules/react-native/scripts/launchPackager.command" || echo "Can't start packager automatically"
fi
fi

View File

@@ -14,7 +14,6 @@
0B49424520AD8DBD00BD2DE0 /* outgoingStart.wav in Resources */ = {isa = PBXBuildFile; fileRef = 0B49424320AD8DBD00BD2DE0 /* outgoingStart.wav */; };
0B49424620AD8DBD00BD2DE0 /* outgoingRinging.wav in Resources */ = {isa = PBXBuildFile; fileRef = 0B49424420AD8DBD00BD2DE0 /* outgoingRinging.wav */; };
0B7C2CFD200F51D60060D076 /* LaunchOptions.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B7C2CFC200F51D60060D076 /* LaunchOptions.m */; };
0B93EF7B1EC608550030D24D /* CoreText.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0B93EF7A1EC608550030D24D /* CoreText.framework */; };
0B93EF7E1EC9DDCD0030D24D /* RCTBridgeWrapper.h in Headers */ = {isa = PBXBuildFile; fileRef = 0B93EF7C1EC9DDCD0030D24D /* RCTBridgeWrapper.h */; };
0B93EF7F1EC9DDCD0030D24D /* RCTBridgeWrapper.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B93EF7D1EC9DDCD0030D24D /* RCTBridgeWrapper.m */; };
0BA13D311EE83FF8007BEF7F /* ExternalAPI.m in Sources */ = {isa = PBXBuildFile; fileRef = 0BA13D301EE83FF8007BEF7F /* ExternalAPI.m */; };
@@ -34,6 +33,7 @@
75635B0A20751D6D00F29C9F /* joined.wav in Resources */ = {isa = PBXBuildFile; fileRef = 75635B0820751D6D00F29C9F /* joined.wav */; };
75635B0B20751D6D00F29C9F /* left.wav in Resources */ = {isa = PBXBuildFile; fileRef = 75635B0920751D6D00F29C9F /* left.wav */; };
A4414AE020B37F1A003546E6 /* rejected.wav in Resources */ = {isa = PBXBuildFile; fileRef = A4414ADF20B37F1A003546E6 /* rejected.wav */; };
A4A934E9212F3ADB001E9388 /* Dropbox.m in Sources */ = {isa = PBXBuildFile; fileRef = A4A934E8212F3ADB001E9388 /* Dropbox.m */; };
B386B85720981A75000DEF7A /* InviteController.m in Sources */ = {isa = PBXBuildFile; fileRef = B386B85020981A74000DEF7A /* InviteController.m */; };
B386B85820981A75000DEF7A /* AddPeopleController.m in Sources */ = {isa = PBXBuildFile; fileRef = B386B85120981A74000DEF7A /* AddPeopleController.m */; };
B386B85920981A75000DEF7A /* AddPeopleControllerDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = B386B85220981A74000DEF7A /* AddPeopleControllerDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; };
@@ -87,6 +87,8 @@
98E09B5C73D9036B4ED252FC /* Pods-JitsiMeet.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JitsiMeet.debug.xcconfig"; path = "../Pods/Target Support Files/Pods-JitsiMeet/Pods-JitsiMeet.debug.xcconfig"; sourceTree = "<group>"; };
9C77CA3CC919B081F1A52982 /* Pods-JitsiMeet.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JitsiMeet.release.xcconfig"; path = "../Pods/Target Support Files/Pods-JitsiMeet/Pods-JitsiMeet.release.xcconfig"; sourceTree = "<group>"; };
A4414ADF20B37F1A003546E6 /* rejected.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; name = rejected.wav; path = ../../sounds/rejected.wav; sourceTree = "<group>"; };
A4A934E8212F3ADB001E9388 /* Dropbox.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Dropbox.m; sourceTree = "<group>"; };
A4A934EB21349A06001E9388 /* Dropbox.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Dropbox.h; sourceTree = "<group>"; };
B386B85020981A74000DEF7A /* InviteController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = InviteController.m; sourceTree = "<group>"; };
B386B85120981A74000DEF7A /* AddPeopleController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AddPeopleController.m; sourceTree = "<group>"; };
B386B85220981A74000DEF7A /* AddPeopleControllerDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AddPeopleControllerDelegate.h; sourceTree = "<group>"; };
@@ -111,7 +113,6 @@
files = (
0BB9AD791F5EC6D7001C08DB /* Intents.framework in Frameworks */,
0BB9AD771F5EC6CE001C08DB /* CallKit.framework in Frameworks */,
0B93EF7B1EC608550030D24D /* CoreText.framework in Frameworks */,
0F65EECE1D95DA94561BB47E /* libPods-JitsiMeet.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -122,9 +123,6 @@
0BCA49681EC4BBE500B793EE /* Resources */ = {
isa = PBXGroup;
children = (
6C31EDC720C06D490089C899 /* recordingOn.mp3 */,
6C31EDC920C06D530089C899 /* recordingOff.mp3 */,
A4414ADF20B37F1A003546E6 /* rejected.wav */,
0BC4B8681F8C01E100CE8B21 /* CallKitIcon.png */,
C6245F5B2053091D0040BE68 /* image-resize@2x.png */,
C6245F5C2053091D0040BE68 /* image-resize@3x.png */,
@@ -133,6 +131,9 @@
75635B0920751D6D00F29C9F /* left.wav */,
0B49424420AD8DBD00BD2DE0 /* outgoingRinging.wav */,
0B49424320AD8DBD00BD2DE0 /* outgoingStart.wav */,
6C31EDC920C06D530089C899 /* recordingOff.mp3 */,
6C31EDC720C06D490089C899 /* recordingOn.mp3 */,
A4414ADF20B37F1A003546E6 /* rejected.wav */,
);
name = Resources;
sourceTree = "<group>";
@@ -162,6 +163,7 @@
0BB9AD7C1F60356D001C08DB /* AppInfo.m */,
0BCA495C1EC4B6C600B793EE /* AudioMode.m */,
C69EFA02209A0EFD0027712B /* callkit */,
A4A934E7212F3AB8001E9388 /* dropbox */,
0BA13D301EE83FF8007BEF7F /* ExternalAPI.m */,
0BD906E91EC0C00300C8C18E /* Info.plist */,
B386B84F20981A11000DEF7A /* invite */,
@@ -193,6 +195,15 @@
name = Frameworks;
sourceTree = "<group>";
};
A4A934E7212F3AB8001E9388 /* dropbox */ = {
isa = PBXGroup;
children = (
A4A934EB21349A06001E9388 /* Dropbox.h */,
A4A934E8212F3ADB001E9388 /* Dropbox.m */,
);
path = dropbox;
sourceTree = "<group>";
};
B386B84F20981A11000DEF7A /* invite */ = {
isa = PBXGroup;
children = (
@@ -272,6 +283,7 @@
0BD906E31EC0C00300C8C18E /* Resources */,
0BCA49651EC4B77500B793EE /* Package React bundle */,
C7BC10B338C94EEB98048E64 /* [CP] Copy Pods Resources */,
0B64F7BE2175DFEA005009CD /* Remove unneeded fonts */,
);
buildRules = (
);
@@ -288,7 +300,7 @@
0BD906DC1EC0C00300C8C18E /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 0920;
LastUpgradeCheck = 1000;
ORGANIZATIONNAME = Jitsi;
TargetAttributes = {
0BD906E41EC0C00300C8C18E = {
@@ -337,6 +349,24 @@
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
0B64F7BE2175DFEA005009CD /* Remove unneeded fonts */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
name = "Remove unneeded fonts";
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "# We need to manually do this because react-native-vecotr-icons lists fonts as resources in the Pod spec file\n# so they are automatically added.\n\nshopt -s extglob\n\nrm -f ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/!(jitsi).ttf\n";
};
0BCA49651EC4B77500B793EE /* Package React bundle */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
@@ -376,10 +406,16 @@
);
inputPaths = (
"${SRCROOT}/../Pods/Target Support Files/Pods-JitsiMeet/Pods-JitsiMeet-resources.sh",
"${PODS_ROOT}/GTMOAuth2/Source/Touch/GTMOAuth2ViewTouch.xib",
"${PODS_ROOT}/GoogleSignIn/Resources/GoogleSignIn.bundle",
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/AntDesign.ttf",
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Entypo.ttf",
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/EvilIcons.ttf",
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Feather.ttf",
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome.ttf",
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Brands.ttf",
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Regular.ttf",
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Solid.ttf",
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Foundation.ttf",
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Ionicons.ttf",
"${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/MaterialCommunityIcons.ttf",
@@ -390,10 +426,16 @@
);
name = "[CP] Copy Pods Resources";
outputPaths = (
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GTMOAuth2ViewTouch.nib",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleSignIn.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AntDesign.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Entypo.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EvilIcons.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Feather.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome5_Brands.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome5_Regular.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome5_Solid.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Foundation.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Ionicons.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/MaterialCommunityIcons.ttf",
@@ -429,6 +471,7 @@
0BCA49611EC4B6C600B793EE /* Proximity.m in Sources */,
C69EFA0C209A0F660027712B /* JMCallKitEmitter.swift in Sources */,
C6A34261204EF76800E062DD /* DragGestureController.swift in Sources */,
A4A934E9212F3ADB001E9388 /* Dropbox.m in Sources */,
C69EFA0D209A0F660027712B /* JMCallKitProxy.swift in Sources */,
C69EFA0E209A0F660027712B /* JMCallKitListener.swift in Sources */,
0B412F191EDEC65D00B1A0A6 /* JitsiMeetView.m in Sources */,
@@ -452,6 +495,7 @@
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
@@ -459,6 +503,7 @@
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
@@ -511,6 +556,7 @@
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
@@ -518,6 +564,7 @@
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
@@ -543,6 +590,7 @@
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
VERSIONING_SYSTEM = "apple-generic";

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0920"
LastUpgradeVersion = "1000"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
@@ -26,7 +26,6 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
@@ -37,7 +36,6 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = ""
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"

View File

@@ -18,12 +18,12 @@
<string>1.9.0</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>LSApplicationQueriesSchemes</key>
<array>
<string>dbapi-2</string>
<string>dbapi-8-emm</string>
</array>
<key>NSPrincipalClass</key>
<string></string>
<key>JitsiMeetFonts</key>
<array>
<string>FontAwesome.ttf</string>
<string>jitsi.ttf</string>
</array>
</dict>
</plist>

View File

@@ -39,10 +39,14 @@
continueUserActivity:(NSUserActivity * _Nonnull)userActivity
restorationHandler:(void (^ _Nullable)(NSArray * _Nullable))restorationHandler;
+ (BOOL)application:(UIApplication *)app
openURL:(NSURL *)url
options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options;
+ (BOOL)application:(UIApplication * _Nonnull)application
openURL:(NSURL * _Nonnull)URL
sourceApplication:(NSString * _Nullable)sourceApplication
annotation:(id _Nullable)annotation;
annotation:(id _Nullable)annotation __deprecated;
- (void)loadURL:(NSURL * _Nullable)url;

View File

@@ -14,7 +14,6 @@
* limitations under the License.
*/
#import <CoreText/CoreText.h>
#import <Intents/Intents.h>
#include <mach/mach_time.h>
@@ -23,6 +22,7 @@
#import <React/RCTLinkingManager.h>
#import <React/RCTRootView.h>
#import "Dropbox.h"
#import "Invite+Private.h"
#import "InviteController+Private.h"
#import "JitsiMeetView+Private.h"
@@ -52,35 +52,6 @@ RCTFatalHandler _RCTFatal = ^(NSError *error) {
}
};
/**
* Helper function to dynamically load custom fonts. The `UIAppFonts` key in the
* plist file doesn't work for frameworks, so fonts have to be manually loaded.
*/
void loadCustomFonts(Class clazz) {
NSBundle *bundle = [NSBundle bundleForClass:clazz];
NSArray *fonts = [bundle objectForInfoDictionaryKey:@"JitsiMeetFonts"];
for (NSString *item in fonts) {
NSString *fontName = [item stringByDeletingPathExtension];
NSString *fontExt = [item pathExtension];
NSString *fontPath = [bundle pathForResource:fontName ofType:fontExt];
NSData *inData = [NSData dataWithContentsOfFile:fontPath];
CFErrorRef error;
CGDataProviderRef provider
= CGDataProviderCreateWithCFData((__bridge CFDataRef)inData);
CGFontRef font = CGFontCreateWithDataProvider(provider);
if (!CTFontManagerRegisterGraphicsFont(font, &error)) {
CFStringRef errorDescription = CFErrorCopyDescription(error);
NSLog(@"Failed to load font: %@", errorDescription);
CFRelease(errorDescription);
}
CFRelease(font);
CFRelease(provider);
}
}
/**
* Helper function to register a fatal error handler for React. Our handler
* won't kill the process, it will swallow JS errors and print stack traces
@@ -137,6 +108,8 @@ static NSMapTable<NSString *, JitsiMeetView *> *views;
// Store launch options, will be used when we create the bridge.
_launchOptions = [launchOptions copy];
[Dropbox setAppKey];
return YES;
}
@@ -192,10 +165,13 @@ static NSMapTable<NSString *, JitsiMeetView *> *views;
restorationHandler:restorationHandler];
}
+ (BOOL)application:(UIApplication *)application
+ (BOOL)application:(UIApplication *)app
openURL:(NSURL *)url
sourceApplication:(NSString *)sourceApplication
annotation:(id)annotation {
options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
if ([Dropbox application:app openURL:url options:options]) {
return YES;
}
// 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
@@ -205,10 +181,14 @@ static NSMapTable<NSString *, JitsiMeetView *> *views;
return YES;
}
return [RCTLinkingManager application:application
openURL:url
sourceApplication:sourceApplication
annotation:annotation];
return [RCTLinkingManager application:app openURL:url options:options];
}
+ (BOOL)application:(UIApplication *)application
openURL:(NSURL *)url
sourceApplication:(NSString *)sourceApplication
annotation:(id)annotation {
return [self application:application openURL:url options:@{}];
}
#pragma mark Initializers
@@ -400,9 +380,6 @@ static NSMapTable<NSString *, JitsiMeetView *> *views;
= [[RCTBridgeWrapper alloc] initWithLaunchOptions:_launchOptions];
views = [NSMapTable strongToWeakObjectsMapTable];
// Dynamically load custom bundled fonts.
loadCustomFonts(self.class);
// Register a fatal error handler for React.
registerFatalErrorHandler();
});

View File

@@ -0,0 +1,27 @@
/*
* 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.
*/
#import <React/RCTBridge.h>
@interface Dropbox : NSObject<RCTBridgeModule>
+ (BOOL)application:(UIApplication *)app
openURL:(NSURL *)url
options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options;
+ (void)setAppKey;
@end

View File

@@ -0,0 +1,173 @@
/*
* 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.
*/
#import <React/RCTBridgeModule.h>
#import <ObjectiveDropboxOfficial/ObjectiveDropboxOfficial.h>
#import "Dropbox.h"
RCTPromiseResolveBlock currentResolve = nil;
RCTPromiseRejectBlock currentReject = nil;
@implementation Dropbox
+ (NSString *)getAppKey{
NSArray *urlTypes
= [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleURLTypes"];
for (NSDictionary<NSString *, NSArray *> *urlType in urlTypes) {
NSArray *urlSchemes = urlType[@"CFBundleURLSchemes"];
if (urlSchemes) {
for (NSString *urlScheme in urlSchemes) {
if (urlScheme && [urlScheme hasPrefix:@"db-"]) {
return [urlScheme substringFromIndex:3];
}
}
}
}
return nil;
}
RCT_EXPORT_MODULE();
+ (BOOL)requiresMainQueueSetup {
return NO;
}
- (NSDictionary *)constantsToExport {
BOOL enabled = [Dropbox getAppKey] != nil;
return @{
@"ENABLED": [NSNumber numberWithBool:enabled]
};
};
RCT_EXPORT_METHOD(authorize:(RCTPromiseResolveBlock)resolve
reject:(__unused RCTPromiseRejectBlock)reject) {
currentResolve = resolve;
currentReject = reject;
dispatch_async(dispatch_get_main_queue(), ^{
[DBClientsManager authorizeFromController:[UIApplication sharedApplication]
controller:[[self class] topMostController]
openURL:^(NSURL *url) {
[[UIApplication sharedApplication] openURL:url];
}];
});
}
RCT_EXPORT_METHOD(getDisplayName: (NSString *)token
resolve: (RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject) {
DBUserClient *client = [[DBUserClient alloc] initWithAccessToken:token];
[[client.usersRoutes getCurrentAccount] setResponseBlock:^(DBUSERSFullAccount *result, DBNilObject *routeError, DBRequestError *networkError) {
if (result) {
resolve(result.name.displayName);
} else {
NSString *msg = @"Failed!";
if (networkError) {
msg = [NSString stringWithFormat:@"Failed! Error: %@", networkError];
}
reject(@"getDisplayName", @"Failed", nil);
}
}];
}
RCT_EXPORT_METHOD(getSpaceUsage: (NSString *)token
resolve: (RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject) {
DBUserClient *client = [[DBUserClient alloc] initWithAccessToken:token];
[[client.usersRoutes getSpaceUsage] setResponseBlock:^(DBUSERSSpaceUsage *result, DBNilObject *routeError, DBRequestError *networkError) {
if (result) {
DBUSERSSpaceAllocation *allocation = result.allocation;
NSNumber *allocated = 0;
NSNumber *used = 0;
if ([allocation isIndividual]) {
allocated = allocation.individual.allocated;
used = result.used;
} else if ([allocation isTeam]) {
allocated = allocation.team.allocated;
used = allocation.team.used;
}
id objects[] = { used, allocated };
id keys[] = { @"used", @"allocated" };
NSDictionary *dictionary = [NSDictionary dictionaryWithObjects:objects
forKeys:keys
count:2];
resolve(dictionary);
} else {
NSString *msg = @"Failed!";
if (networkError) {
msg = [NSString stringWithFormat:@"Failed! Error: %@", networkError];
}
reject(@"getSpaceUsage", msg, nil);
}
}];
}
+ (BOOL)application:(UIApplication *)app
openURL:(NSURL *)url
options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
if (currentReject == nil || currentResolve == nil) {
return NO;
}
DBOAuthResult *authResult = [DBClientsManager handleRedirectURL:url];
if (authResult) {
if ([authResult isSuccess]) {
currentResolve(authResult.accessToken.accessToken);
currentResolve = nil;
currentReject = nil;
} else {
NSString *msg;
if ([authResult isError]) {
msg = [NSString stringWithFormat:@"%@, error type: %ld",[authResult errorDescription], [authResult errorType]];
} else {
msg = @"OAuth canceled!";
}
currentReject(@"authorize", msg, nil);
currentResolve = nil;
currentReject = nil;
}
return YES;
}
return NO;
}
+ (UIViewController *)topMostController
{
UIViewController *topController
= [UIApplication sharedApplication].keyWindow.rootViewController;
while (topController.presentedViewController) {
topController = topController.presentedViewController;
}
return topController;
}
+ (void)setAppKey {
NSString *appKey = [self getAppKey];
if (appKey) {
[DBClientsManager setupWithAppKey:appKey];
}
}
@end

View File

@@ -136,13 +136,13 @@ cd ..
mkdir -p /tmp/jitsi-meet/
xcodebuild archive -workspace ios/jitsi-meet.xcworkspace -scheme jitsi-meet -configuration Release -archivePath /tmp/jitsi-meet/jitsi-meet.xcarchive
xcodebuild archive -quiet -workspace ios/jitsi-meet.xcworkspace -scheme jitsi-meet -configuration Release -archivePath /tmp/jitsi-meet/jitsi-meet.xcarchive
sed -e "s/YOUR_TEAM_ID/${IOS_TEAM_ID}/g" ios/travis-ci/build-ipa.plist.template > ios/travis-ci/build-ipa.plist
IPA_EXPORT_DIR=/tmp/jitsi-meet/jitsi-meet-ipa
xcodebuild -exportArchive -archivePath /tmp/jitsi-meet/jitsi-meet.xcarchive -exportPath $IPA_EXPORT_DIR -exportOptionsPlist ios/travis-ci/build-ipa.plist
xcodebuild -quiet -exportArchive -archivePath /tmp/jitsi-meet/jitsi-meet.xcarchive -exportPath $IPA_EXPORT_DIR -exportOptionsPlist ios/travis-ci/build-ipa.plist
echo "Will try deploy the .ipa to: ${IPA_DEPLOY_LOCATION}"

View File

@@ -1,11 +1,18 @@
{
"en": "Anglais",
"az": "Azerbaïdjanais",
"bg": "Bulgare",
"cs": "Tchèque",
"de": "Allemand",
"el": "Grec",
"eo": "Espéranto",
"es": "Espagnol",
"fr": "Français",
"hy": "Arménien",
"it": "Italien",
"ja": "Japonais",
"ko": "Coréen",
"nb": "Norvégien Bokmal",
"oc": "Occitan",
"pl": "Polonais",
"ptBR": "Portugais (Brésil)",
@@ -14,7 +21,6 @@
"sl": "Slovène",
"sv": "Suédois",
"tr": "Turc",
"zhCN": "Chinois (Chine)",
"nb": "Norvégien Bokmal",
"eo": "Espéranto"
"vi": "Vietnamien",
"zhCN": "Chinois (Chine)"
}

View File

@@ -1,11 +1,18 @@
{
"en": "Inglês",
"az": "Azerbaijanês",
"bg": "Búlgaro",
"cs": "Checo",
"de": "Alemão",
"el": "Grego",
"eo": "Esperanto",
"es": "Espanhol",
"fr": "Francês",
"hy": "Armênio",
"it": "Italiano",
"ja": "Japonês",
"ko": "Coreano",
"nb": "Bokmal norueguês",
"oc": "Occitano",
"pl": "Polonês",
"ptBR": "Português (Brasil)",
@@ -14,7 +21,6 @@
"sl": "Esloveno",
"sv": "Sueco",
"tr": "Turco",
"zhCN": "Chinês (China)",
"nb": "Bokmal norueguês",
"eo": "Esperanto"
"vi": "Vietnamita",
"zhCN": "Chinês (China)"
}

View File

@@ -15,6 +15,6 @@
"sv": "Шведский",
"tr": "Турецкий",
"zhCN": "Китайский (Китай)",
"nb": "",
"eo": ""
"nb": "Норвежский букмол",
"eo": "Эсперанто"
}

20
lang/languages-zhTW.json Normal file
View File

@@ -0,0 +1,20 @@
{
"en": "English",
"bg": "Bulgarian",
"de": "German",
"es": "Spanish",
"fr": "French",
"hy": "Armenian",
"it": "Italian",
"oc": "Occitan",
"pl": "Polish",
"ptBR": "Portuguese (Brazil)",
"ru": "Russian",
"sk": "Slovak",
"sl": "Slovenian",
"sv": "Swedish",
"tr": "Turkish",
"zhCN": "中文 简体 (中国)",
"nb": "Norwegian Bokmal",
"eo": "Esperanto"
}

View File

@@ -1,6 +1,5 @@
{
"contactlist": "__count__ Membres",
"contactlist_plural": "",
"contactlist_plural": "__count__ Membres",
"passwordSetRemotely": "défini par un autre membre",
"poweredby": "Produit par",
"inviteUrlDefaultMsg": "Votre conférence est en cours de création...",
@@ -23,6 +22,7 @@
"react-nativeGrantPermissions": "Sélectionnez <b><i>Autoriser</i></b> lorsque votre navigateur demande des autorisations.",
"chromeGrantPermissions": "Sélectionnez <b><i>Autoriser</i></b> lorsque votre navigateur demande des autorisations.",
"androidGrantPermissions": "Sélectionnez <b><i>Autoriser</i></b> lorsque votre navigateur demande des autorisations.",
"electronGrantPermissions": "Merci d'autoriser le partage de votre camera et microphone",
"firefoxGrantPermissions": "Sélectionnez <b><i>Partager le périphérique sélectionné</i></b> lorsque votre navigateur demande des autorisations.",
"operaGrantPermissions": "Sélectionnez <b><i>Autoriser</i></b> lorsque votre navigateur demande des autorisations.",
"iexplorerGrantPermissions": "Sélectionnez <b><i>OK</i></b> quand le navigateur demande les permissions.",
@@ -35,57 +35,42 @@
"raiseHand": "Lever ou baisser la main",
"pushToTalk": "Appuyer pour parler",
"toggleScreensharing": "Basculer entre la caméra et le partage d'écran",
"toggleFilmstrip": "Afficher ou cacher la vidéo",
"toggleShortcuts": "Afficher ou masquer ce menu d'aide",
"toggleFilmstrip": "Afficher ou masquer les vignettes vidéos",
"toggleShortcuts": "Afficher ou masquer les raccourcis clavier",
"focusLocal": "Épingler ma vidéo",
"focusRemote": "Épingler la vidéo des autres",
"focusRemote": "Épingler la vidéo de quelqu'un d'autre",
"toggleChat": "Ouvrir ou fermer le panneau de conversation",
"mute": "Activer ou désactiver le microphone",
"fullScreen": "Activer ou Désactiver le plein écran",
"fullScreen": "Activer / Désactiver le mode plein écran",
"videoMute": "Démarrer ou arrêter votre caméra",
"showSpeakerStats": "Afficher les statistiques de l'interlocuteur"
"showSpeakerStats": "Afficher les statistiques de l'interlocuteur",
"localRecording": "Afficher ou masquer les commandes de l'enregistrement local"
},
"welcomepage": {
"disable": "Ne plus afficher cette page",
"feature1": {
"content": "Aucun téléchargement requis. __app__ s'utilise directement depuis votre navigateur. Partager simplement l'URL de votre conférence avec les autres pour commencer.",
"title": "Simple à utiliser"
"accessibilityLabel": {
"join": "Touchez pour rejoindre",
"roomname": "Saisissez un nom de salle"
},
"feature2": {
"content": "Les vidéo conférences à plusieurs participants nécessitent moins de 128 kbps. Le partage d'écran et les conférences avec seulement de l'audio sont possibles avec beaucoup moins de débit.",
"title": "Bande passante faible"
},
"feature3": {
"content": "__app__ est sous licence Apache. Vous êtes libre de télécharger, d'utiliser, de modifier et de partager __app__ selon cette licence libre.",
"title": "Open source"
},
"feature4": {
"content": "Il n'y a pas de limitation sur le nombre d'utilisateurs ou de conférences. Seules la puissance et la bande passante du serveur sont des facteurs limitants.",
"title": "Nombre d'utilisateurs illimité"
},
"feature5": {
"content": "C'est facile de partager votre écran avec d'autres personnes. __app__ est idéal pour les présentations en ligne, les cours, et les sessions de support technique.",
"title": "Partage d'écran"
},
"feature6": {
"content": "Besoin de confidentialité ? Les salles de conférence __app__ peuvent être sécurisées par un mot de passe pour exclure les invités non désirées, et prévenir des interruptions.",
"title": "Salles sécurisées"
},
"feature7": {
"content": "__app__ propose Etherpad, un éditeur de texte collaboratif en temps réel qui est parfait pour les procès-verbaux, l'édition d'articles et plus encore.",
"title": "Notes partagées"
},
"feature8": {
"content": "Apprenez plus au sujet de vos utilisateurs avec une intégration facile de Piwik, Google Analytics et d'autres systèmes de statistiques et supervision d'utilisation.",
"title": "Statistiques d'utilisation"
"appDescription": "Allez-y, chat vidéo avec toute l'équipe. En fait, invitez tout le monde que vous connaissez. __app__ est une solution de visioconférence entièrement cryptée et 100% open source que vous pouvez utiliser toute la journée, tous les jours, gratuitement— aucun compte requis.",
"audioVideoSwitch": {
"audio": "Voix",
"video": "Vidéo"
},
"calendar": "Calendrier",
"connectCalendarText": "Connectez votre calendrier pour voir toutes vos réunion dans __app__. Ajoutez les réunions __app__ dans votre calendrier pour les lancer en un seul clic.",
"connectCalendarButton": "Connecter votre calendrier",
"enterRoomTitle": "Démarrer une nouvelle réunion",
"go": "Créer",
"join": "REJOINDRE",
"privacy": "Confidentialité",
"recentList": "Récent",
"recentListDelete": "Supprimer",
"recentListEmpty": "Votre liste récente est actuellement vide. Discuter avec votre équipe et vous trouverez toutes vos réunions récentes ici.",
"roomname": "Saisissez un nom de salle",
"roomnamePlaceHolder": "nom de la conférence",
"roomnameHint": "Entrez le nom ou l'URL de la salle que vous souhaitez rejoindre. Vous pouvez faire un nom, laissez les gens que vous rencontrerez le savoir afin qu'ils entrent le même nom.",
"sendFeedback": "Envoyer votre avis",
"terms": "Termes"
"terms": "Termes",
"title": "Vidéoconférence Sécurisée, entièrement en vedette et gratuite"
},
"startupoverlay": {
"policyText": " ",
@@ -97,52 +82,95 @@
"rejoinKeyTitle": "Rejoindre"
},
"toolbar": {
"accessibilityLabel": {
"audioOnly": "Activer/désactiver le mode voix uniquement",
"audioRoute": "",
"callQuality": "Accorder la qualité des appels",
"chat": "Afficher/masquer la discussion instantanée",
"cc": "Activer/désactiver les sous-titres",
"document": "Activer/désactiver le document partagé",
"feedback": "Laisser des commentaires",
"fullScreen": "Activer/désactiver le plein écran",
"hangup": "Quitter la conversation",
"invite": "Inviter des participants",
"localRecording": "Activer/désactiver les contrôles d'enregistrement local",
"lockRoom": "Activer/Désactiver le verrouillage de la session",
"moreActions": "Activer/désactiver le menu d'actions supplémentaires",
"moreActionsMenu": "Menu d'actions supplémentaires",
"mute": "Activer/désactiver l'audio",
"pip": "Activer/désactiver le mode Picture in Picture",
"profile": "Éditer votre profil",
"raiseHand": "Lever/baisser la main",
"recording": "Activer/désactiver l'enregistrement",
"Settings": "Afficher/masquer le menu des paramètres",
"sharedvideo": "Démarrer/arrêter le partage de vidéo Youtube",
"shareRoom": "Inviter quelqu'un",
"shareYourScreen": "Activer/désactiver le partage décran",
"shortcuts": "Afficher/masquer les raccourcis",
"speakerStats": "Afficher/cacher les statistiques de parole",
"toggleCamera": "Activer/désactiver la caméra",
"tileView": "Activer/désactiver la vue mosaïque",
"videomute": "Activer/désactiver la vidéo"
},
"addPeople": "Ajouter des personnes à votre appel",
"audioonly": "Activer / Désactiver le mode audio uniquement (économiser de la bande passante)",
"audioOnlyOn": "Activer/désactiver le mode audio uniquement (économiser de la bande passante)",
"audioOnlyOff": "Désactiver le mode audio uniquement",
"audioRoute": "Sélectionner le périphérique audio",
"callQuality": "Accorder la qualité des appels",
"enterFullScreen": "Afficher en plein écran",
"exitFullScreen": "Quitter le mode plein écran",
"feedback": "Laisser des commentaires",
"moreActions": "Plus d'actions",
"mute": "Muet / Actif",
"videomute": "Démarrer / Arrêter la caméra",
"authenticate": "Authentifiez-vous",
"lock": "Verrouiller / déverrouiller la conférence",
"invite": "Partager le lien",
"chat": "Ouvrir / Fermer le chat",
"etherpad": "Ouvrir / Fermer le document partagé",
"documentOpen": "Ouvrir le document partagé",
"documentClose": "Fermer le document partagé",
"shareRoom": "Partager le salon",
"sharedvideo": "Partager une vidéo YouTube",
"sharescreen": "Démarrer / Arrêter le partage d'écran",
"stopSharedVideo": "Arrêter la vidéo YouTube",
"fullscreen": "Activer / Désactiver le plein écran",
"sip": "Appeler un numéro SIP",
"Settings": "Paramètres",
"hangup": "Quitter",
"login": "Connexion",
"logout": "Déconnexion",
"dialpad": "Ouvrir / Fermer le pavé numérique",
"sharedVideoMutedPopup": "Votre vidéo a été coupée pour que vous puissiez parler aux autres participants.",
"toggleCamera": "Activer/désactiver la caméra",
"micMutedPopup": "Votre microphone a été coupé afin que vous puissiez profiter de la vidéo partagée",
"talkWhileMutedPopup": "Vous voulez parler? Vous êtes en muet.",
"unableToUnmutePopup": "Vous ne pouvez pas réactiver votre microphone pendant que la vidéo partagée est activée.",
"cameraDisabled": "La camera n'est pas disponible",
"micDisabled": "Le microphone n'est pas disponible",
"filmstrip": "Afficher / Masquer les vidéos",
"pip": "Entrer en mode Picture-in-Picture",
"profile": "Éditer votre profil",
"raiseHand": "Lever / Baisser la main"
},
"unsupportedBrowser": {
"appNotInstalled": "Rejoignez cette réunion avec __app__ sur votre téléphone.",
"downloadApp": "Télécharger l'application",
"openApp": "Continuer sur __app__"
},
"bottomtoolbar": {
"chat": "Ouvrir / fermer le chat",
"filmstrip": "Afficher / cacher les vidéos",
"contactlist": "Voir et inviter des participants"
"raiseHand": "Lever / Baisser la main",
"shortcuts": "Afficher les raccourcis",
"speakerStats": "Statistiques de l'interlocuteur",
"tileViewToggle": "Activer/désactiver la vue mosaïque",
"invite": "Inviter des participants"
},
"chat": {
"nickname": {
"title": "Saisissez un pseudonyme dans le champ ci-dessous",
"popover": "Choisissez un pseudonyme"
},
"error": "Erreur : votre message \"__originalText__\" n'a pas été envoyé. Raison : __error__",
"messagebox": "Saisissez votre texte..."
},
"settings": {
"calendar": {
"about": "L'intégration de __appName__ avec votre calendrier permet daccéder de manière sécurisée aux événement à venir.",
"disconnect": "Se déconnecter",
"microsoftSignIn": "Se connecter avec Microsoft",
"signedIn": "Accès aux événements du calendrier __email__. Cliquez sur le bouton se déconnecter ci-dessous pour arrêter l'accès aux événements du calendrier.",
"title": "Calendrier"
},
"title": "Paramètres",
"update": "Mise à jour",
"name": "Nom",
@@ -152,11 +180,15 @@
"selectMic": "Microphone",
"selectAudioOutput": "Sortie audio",
"followMe": "Tout le monde me suit",
"language": "Langue",
"loggedIn": "Connecté en tant que __name__",
"noDevice": "Aucun",
"cameraAndMic": "Caméra et microphone",
"moderator": "MODÉRATEUR",
"moderator": "Moderateur",
"more": "Plus",
"password": "DÉFINIR UN MOT DE PASSE",
"audioVideo": "AUDIO ET VIDÉO"
"audioVideo": "AUDIO ET VIDÉO",
"devices": "Périphériques"
},
"profile": {
"title": "Profil",
@@ -176,7 +208,9 @@
},
"connectionindicator": {
"header": "État de la connexion",
"connectedTo": "Connecté à :",
"bitrate": "Débit :",
"bridgeCount": "Nombre de serveurs :",
"packetloss": "Perte de paquets :",
"resolution": "Résolution :",
"framerate": "Images par seconde",
@@ -216,15 +250,20 @@
"focus": "Focus de conférence",
"focusFail": "__component__ n'est pas disponible - réessayez dans __ms__ sec",
"grantedTo": "Droits modérateur accordés à __to__ !",
"grantedToUnknown": "Droits modérateur accordés à $t(notify.somebody)!",
"muted": "Vous avez commencé la conversation en muet.",
"mutedTitle": "Vous êtes en muet !",
"raisedHand": "Aimerait prendre la parole."
"raisedHand": "Aimerait prendre la parole.",
"suboptimalExperienceTitle": "Avertissement du navigateur",
"suboptimalExperienceDescription": "Eer ... nous craignons que votre expérience avec __appName__ ne sera pas aussi excellente que nous le pensions. Nous cherchons des moyens d'améliorer cela, mais jusque là, essayez d'utiliser l'un des <a href='static/recommendedBrowsers.html' target='_blank'>navigateurs entièrement pris en charge</a>."
},
"dialog": {
"add": "Ajouter",
"accessibilityLabel": {
"liveStreaming": "Diffusion en direct"
},
"allow": "Autoriser",
"confirm": "Confirmer",
"kickMessage": "Oups! Vous avez été renvoyé de la réunion !",
"kickTitle": "Viré de la conférence",
"popupErrorTitle": "Pop-up bloquée",
"popupError": "Votre navigateur bloque les fenêtres pop-up. Veuillez autoriser les fenêtres pop-up dans les paramètres de votre navigateur.",
"passwordErrorTitle": "Problème avec le mot de passe",
@@ -237,7 +276,6 @@
"copy": "Copier",
"contactSupport": "Contacter le support",
"error": "Erreur",
"createPassword": "Créer un mot de passe",
"detectext": "Une erreur est survenue pendant la détection de l'extension de partage d'écran.",
"failedpermissions": "Échec d'obtention des permissions pour utiliser le micro et/ou la caméra.",
"conferenceReloadTitle": "Malheureusement, un problème est survenu",
@@ -248,6 +286,7 @@
"rejoinNow": "Rejoindre maintenant",
"maxUsersLimitReachedTitle": "Le nombre maximal de participants est atteint",
"maxUsersLimitReached": "Le nombre maximal de participants est atteint. La conférence est complète. Merci de contacter le propriétaire du salon ou réessayer plus tard.",
"lockRoom": "Verrouiller la réunion",
"lockTitle": "Échec du verrouillage",
"lockMessage": "Impossible de verrouiller la conférence.",
"warning": "Avertissement",
@@ -288,10 +327,6 @@
"Save": "Sauvegarder",
"recording": "Enregistrement",
"recordingToken": "Saisissez un jeton d'enregistrement",
"passwordCheck": "Voulez-vous vraiment supprimer votre mot de passe ?",
"passwordMsg": "Saisissez un mot de passe pour verrouiller la conférence",
"shareLink": "Partager le lien de la conférence",
"yourPassword": "Saisissez un nouveau mot de passe",
"Back": "Retour",
"serviceUnavailable": "Service indisponible",
"gracefulShutdown": "Le service est actuellement en maintenance. Réessayez plus tard.",
@@ -299,29 +334,31 @@
"reservationError": "Erreur du système de réservation",
"reservationErrorMsg": "Code d'erreur: __code__, message: __msg__",
"password": "Saisir le mot de passe",
"unlockRoom": "Déverrouiller la réunion",
"userPassword": "mot de passe utilisateur",
"token": "jeton",
"tokenAuthFailedTitle": "Échec de l'authentification",
"tokenAuthFailed": "Désolé, vous n'êtes pas autorisé à rejoindre cette conversation.",
"displayNameRequired": "Un nom d'utilisateur est requis",
"enterDisplayName": "Veuillez saisir votre nom",
"extensionRequired": "Extension requise :",
"firefoxExtensionPrompt": "Vous devez installer une extension Firefox pour utiliser le partage d'écran. Merci d'essayer de nouveau après l'installation <a href='__url__'>depuis ce lien</a> !",
"feedbackHelp": "Vos retours nous permettrons d'améliorer notre expérience vidéo.",
"feedbackQuestion": "Informez-nous à propos de votre appel !",
"thankYou": "Merci d'avoir utilisé __appName__ !",
"sorryFeedback": "Nous sommes désolés d'apprendre cela. Voulez-vous nous en dire plus ?",
"liveStreaming": "Direct",
"streamKey": "Stream name/key",
"startLiveStreaming": "Commencer le direct",
"streamKey": "Clé Live stream",
"startLiveStreaming": "Démarrer la diffusion en direct",
"startRecording": "Commencer l'enregistrement",
"stopStreamingWarning": "Désirez-vous vraiment arrêter le direct?",
"stopRecordingWarning": "Désirez-vous vraiment arrêter l'enregistrement?",
"stopLiveStreaming": "Arrêter le direct",
"stopLiveStreaming": "Arrêter la diffusion en direct",
"stopRecording": "Arrêter l'enregistrement",
"doNotShowMessageAgain": "Ne plus afficher ce message",
"permissionDenied": "Permission refusée",
"screenSharingFailedToInstall": "Oups! Votre extension de partage d'écran n'a pas pu être installée.",
"screenSharingFailedToInstallTitle": "L'extension de partage d'écran n'a pas pu être installée",
"screenSharingFirefoxPermissionDeniedError": "Quelque chose s'est mal passé pendant que nous essayions de partager votre écran. S'il vous plaît assurez-vous que vous nous avez donné la permission de le faire.",
"screenSharingFirefoxPermissionDeniedTitle": "Oups! Nous ne pouvions pas démarrer le partage d'écran!",
"screenSharingPermissionDeniedError": "Oups! Une erreur s'est produite avec vos autorisations d'extension de partage d'écran. Veuillez rafraîchir et réessayer.",
"cameraUnsupportedResolutionError": "Votre appareil ne prend pas en charge la résolution vidéo requise.",
"cameraUnknownError": "Vous ne pouvez pas utiliser la caméra pour une raison inconnue.",
@@ -344,6 +381,10 @@
"muteParticipantTitle": "Couper le micro de ce participant?",
"muteParticipantBody": "Vous ne pourrez plus réactiver leurs micros, mais ils peuvent l'activer par eux-même à tout moment.",
"muteParticipantButton": "Couper le micro",
"liveStreamingDisabledTooltip": "La diffusion en direct est désactivé",
"liveStreamingDisabledForGuestTooltip": "Les invités ne peuvent démarrer la diffusion en direct.",
"recordingDisabledTooltip": "L'enregistrement est désactivé.",
"recordingDisabledForGuestTooltip": "Les invités ne peuvent enregistrer.",
"remoteControlTitle": "Contrôle de bureau à distance",
"remoteControlRequestMessage": "Voulez-vous autoriser __user__ à contrôler votre bureau?",
"remoteControlShareScreenWarning": "Si vous appuyez sur \"Autoriser\" vous allez partager votre écran!",
@@ -354,8 +395,11 @@
"remoteControlStopMessage": "La prise en main à distance est terminée!",
"close": "Fermer",
"shareYourScreen": "Partagez votre écran",
"shareYourScreenDisabled": "Le partage décran est désactivé.",
"shareYourScreenDisabledForGuest": "Les invités ne peuvent partager l'écran",
"yourEntireScreen": "Votre écran entier",
"applicationWindow": "Fenêtre d'application"
"applicationWindow": "Fenêtre d'application",
"transcribing": "Transcription"
},
"email": {
"sharedKey": [
@@ -385,6 +429,22 @@
],
"and": "et"
},
"share": {
"mainText": [
"Cliquez sur le lien suivant pour rejoindre une conférence :",
"__roomUrl__"
],
"dialInfoText": [
"",
"",
"=====",
"",
"Voulez-vous appeler depuis votre téléphone?",
"",
"__defaultDialInNumber__Cliquez sur ce lien pour voir les numéros de téléphone pour cette conférence",
"__dialInfoPageUrl__"
]
},
"connection": {
"ERROR": "Erreur",
"CONNECTING": "Connexion en cours",
@@ -398,30 +458,67 @@
"ATTACHED": "Attachée"
},
"recording": {
"beta": "BETA",
"busy": "Nous sommes en train de libérer les ressources d'enregistrement. Réessayez dans quelques minutes.",
"busyTitle": "Tous les enregistreurs sont actuellement occupés",
"buttonTooltip": "Démarrer / Arrêter l'enregistrement",
"error": "Échec de l'enregistrement. Veuillez réessayer.",
"expandedOff": "L'enregistrement a été arrêté",
"expandedOn": "Cette conférence est actuellement en cours d'enregistrement.",
"expandedPending": "Démarrage de l'enregistrement...",
"failedToStart": "L'enregistrement n'as pas réussi à démarrer",
"live": "DIRECT",
"off": "Enregistrement arrêté",
"on": "Enregistrement",
"pending": "Enregistrement en attente de participant...",
"pending": "Préparation de l'enregistrement de la réunion...",
"rec": "REC",
"authDropboxText": "Téléchargement vers Dropbox",
"serviceName": "Service d'enregistrement",
"signOut": "Se déconnecter",
"signIn": "s'identifier",
"loggedIn": "Connecté en tant que __userName__",
"availableSpace": "Espace disponible: __spaceLeft_ Mo (approximativement __duration__ minutes d'enregistrement)",
"startRecordingBody": "Voulez-vous vraiment démarrer l'enregistrement?",
"unavailable": "Oups! Le __serviceName__ est actuellement indisponible. Nous travaillons sur la résolution du problème. Veuillez réessayer plus tard.",
"unavailableTitle": "Enregistrement indisponible"
},
"transcribing": {
"pending": "Préparation de la transcription de la réunion...",
"off": "La transcription désactivée",
"error": "Échec de la transcription. Veuillez réessayer.",
"expandedLabel": "La transcription est actuellement activée",
"failedToStart": "Échec de démarrage de la transcription",
"tr": "TR",
"labelToolTip": "La transcription de la réunion est en cours",
"ccButtonTooltip": "Afficher/masquer les sous-titres",
"start": "Afficher/masquer les sous-titres",
"stop": "Désactiver le sous-titrage"
},
"liveStreaming": {
"busy": "Nous travaillons sur la libération des ressources de Streaming. Veuillez réessayez dans quelques minutes.",
"busyTitle": "Tous les streamers sont actuellement occupés",
"buttonTooltip": "Démarrer / Arrêter le Stream",
"changeSignIn": "Changer de compte.",
"choose": "Choisir un flux live",
"chooseCTA": "Choisissez une option de diffusion. Vous êtes actuellement connecté comme __email__.",
"enterStreamKey": "Entrez votre clé de flux live Youtube ici",
"error": "Le Streaming a échoué. Veuillez réessayer.",
"errorAPI": "Une erreur s'est produite lors de l'accès à vos diffusions YouTube. Veuillez réessayer de vous connecter.",
"errorLiveStreamNotEnabled": "La diffusion en direct n'est pas activée pour __email__. Merci de l'activer ou de vous connecter avec un compte où elle est déjà activée.",
"expandedOff": "La diffusion en direct a été arrêtée",
"expandedOn": "La conférence est en cours de diffusion sur YouTube.",
"expandedPending": "La diffusion en direct a commencé...",
"failedToStart": "Le Streaming n'as pas réussi à démarrer",
"off": "Le Streaming a été arrêter",
"off": "Le Streaming a été arrêté",
"on": "Direct",
"pending": "Commencer le direct...",
"serviceName": "Service de diffusion en direct",
"streamIdRequired": "Merci de renseigner le stream id pour lancer le streaming.",
"streamIdHelp": "Où puis-je trouver ceci?",
"signedInAs": "Vous êtes connecté en tant que :",
"signIn": "Se connecter avec Google",
"signOut": "Se déconnecter",
"signInCTA": "Connectez vous ou entrez votre clé de flux live provenant de Youtube.",
"start": "Démarrer la diffusion en direct",
"streamIdHelp": "Qu'est-ce que c'est?",
"unavailableTitle": "Le Streaming est indisponible"
},
"videoSIPGW": {
@@ -449,28 +546,20 @@
"noPermission": "Permission non accordée",
"previewUnavailable": "Aperçu non disponible",
"selectADevice": "Sélectionner un périphérique",
"testAudio": "Son de test"
},
"invite": {
"addPassword": "Ajouter un mot de passe",
"callNumber": "Appeler le __number__",
"enterID": "Saisissez l'identifiant: __conferenceID__ suivi de # pour rejoindre avec un téléphone",
"howToDialIn": "Pour rejoindre avec un téléphone, utilisez un des des numéros suivants et l'identifiant de la conférence",
"hidePassword": "Cacher le mot de passe",
"inviteTo": "inviter des participants à __conferenceName__",
"invitedYouTo": "__userName__ vous a invité(e) à la conférence __inviteURL__",
"invitePeople": "Inviter",
"locked": "Cet appel est verrouillé. les nouveaux interlocuteurs devraient avoir le lien et saisir le mot de passe pour rejoindre.",
"showPassword": "Afficher le mot de passe",
"unlocked": "Cet appel est verrouillé. Tout nouveau participant avec un lien peut rejoindre l'appel."
"testAudio": "Lire un audio de test"
},
"videoStatus": {
"audioOnly": "VOIX",
"audioOnlyExpanded": "Vous êtes en mode audio uniquement. Ce mode économise de la bande passant mais vous ne pourrez pas voir la vidéo des autres participants.",
"callQuality": "Qualité de l'appel",
"hd": "HD",
"hdTooltip": "Regardez la vidéo en haute définition",
"highDefinition": "Haute définition",
"labelTooltipVideo": "Qualité vidéo actuelle",
"labelTooltipAudioOnly": "Mode audio uniquement activé",
"labelTooiltipNoVideo": "Aucune vidéo",
"labelTooltipVideo": "Qualité vidéo actuelle",
"ld": "BD",
"ldTooltip": "Regardez la vidéo en basse définition",
"lowDefinition": "Basse définition",
"onlyAudioAvailable": "Seul l'audio est disponible",
"onlyAudioSupported": "Nous ne supportons que l'audio sur ce navigateur.",
@@ -478,21 +567,31 @@
"p2pVideoQualityDescription": "En mode peer to peer, la qualité d'appel reçue ne peut être basculée qu'entre haut et audio. Les autres paramètres ne seront pas respectés tant que l'on n'aura pas quitté peer to peer.",
"recHighDefinitionOnly": "Va préférer la haute définition",
"sd": "MD",
"sdTooltip": "Regardez la vidéo en définition standard",
"standardDefinition": "Moyenne Définition",
"qualityButtonTip": "Changer la qualité de vidéo reçue"
},
"dialOut": {
"dial": "Composer",
"dialOut": "Appeler #",
"statusMessage": "est maintenant __status__",
"enterPhone": "Saisissez un numéro de téléphone",
"phoneNotAllowed": "Désolé, nous ne supportons pas encore cette destination!"
"statusMessage": "est maintenant __status__"
},
"addPeople": {
"add": "Ajouter",
"add": "Inviter",
"countryNotSupported": "Nous ne supportons pas encore cette destination.",
"countryReminder": "Appel hors États-Unis? Veuillez commencer avec le code du pays!",
"disabled": "Vous ne pouvez pas inviter quelqu'un.",
"footerText": "Appels sortants désactivés",
"invite": "Inviter",
"loading": "Rechercher des personnes et des numéros de téléphone",
"loadingNumber": "Validation du numéro de téléphone",
"loadingPeople": "Recherche de personnes à inviter",
"noResults": "Aucun résultat de recherche correspondant",
"searchPlaceholder": "Rechercher des personnes et des salons à ajouter",
"title": "Ajouter des personnes à votre appel",
"noValidNumbers": "Veuillez entrer un numéro de téléphone",
"notAvailable": "Vous ne pouvez pas inviter quelqu'un.",
"searchNumbers": "Ajouter des numéros de téléphone",
"searchPeople": "Rechercher une personne",
"searchPeopleAndNumbers": "Rechercher des personnes ou ajouter leurs numéros de téléphone",
"telephone": "Téléphone: __number__",
"title": "Inviter une personne à cette réunion",
"failedToAdd": "Échec de l'ajout de membres"
},
"inlineDialogFailure": {
@@ -511,14 +610,131 @@
"average": "Moyen",
"bad": "Mauvais",
"good": "Bien",
"rateExperience": "Veuillez évaluer votre réunion.",
"detailsLabel": "Dites nous en plus à ce sujet.",
"rateExperience": "Veuillez évaluer votre expérience.",
"veryBad": "Très mauvais",
"veryGood": "Très bon"
},
"info": {
"copy": "Copier le lien",
"invite": "Inviter à __app__",
"title": "Informations sur la conférence",
"tooltip": "Obtenir des informations d'accès"
"accessibilityLabel": "Afficher les informations",
"addPassword": "Ajouter un mot de passe",
"cancelPassword": "Annuler mot de passe",
"conferenceURL": "Lien:",
"country": "Pays",
"dialANumber": "Pour rejoindre votre réunion, composez l'un de ces numéros, puis entrez ce code PIN: __conferenceID __ #",
"dialInNumber": "Composer:",
"dialInConferenceID": "PIN:",
"dialInNotSupported": "Désolé, les appels entrants ne sont pas supportés.",
"genericError": "Oups, quelque chose a mal tourné.",
"inviteLiveStream": "Pour voir la diffusion en direct de cette réunion, cliquez sur ce lien : __url__",
"invitePhone": "Pour joindre par téléphone, composez __number__ et saisissez ce code PIN : __conferenceID__ #",
"invitePhoneAlternatives": "Pour voir plus de numéros de téléphone, suivez ce lien : __url__",
"inviteURL": "Pour rejoindre la vidéoconférence, cliquez sur ce lien : __url__",
"liveStreamURL": "Diffusion en direct :",
"moreNumbers": "Plus de numéros ",
"noNumbers": "Numéros à composer non trouvés",
"noPassword": "Aucun",
"noRoom": "Aucune réunion n'a été spécifiée pour l'appel entrant.",
"numbers": "Numéros d'appel",
"password": "Mot de passe:",
"title": "Partager",
"tooltip": "Partager le lien et les informations de connexion pour cette conférence"
},
"settingsView": {
"alertOk": "D'accord",
"alertTitle": "Avertissement",
"alertURLText": "L'URL du serveur est invalide",
"conferenceSection": "Conférence",
"displayName": "Pseudo",
"email": "Email",
"header": "Paramètres",
"profileSection": "Profil",
"serverURL": "URL du serveur",
"startWithAudioMuted": "Commencez avec la vidéo en sourdine",
"startWithVideoMuted": "Commencez avec la vidéo en sourdine"
},
"calendarSync": {
"addMeetingURL": "Ajouter un lien de conférence",
"confirmAddLink": "Voulez-vous ajouter un lien Jitsi à cet événement?",
"confirmAddLinkTitle": "Calendrier",
"join": "Joindre",
"joinTooltip": "Rejoindre la réunion",
"nextMeeting": "prochaine réunion",
"noEvents": "Il n'y a pas dévénement à venir.",
"ongoingMeeting": "La réunion en cours",
"permissionButton": "Afficher les réglages",
"permissionMessage": "La permission du calendrier est requise pour afficher vos réunions dans l'application.",
"refresh": "Rafraîchir le calendrier",
"today": "Aujourd'hui"
},
"recentList": {
"joinPastMeeting": "Rejoindre une réunion précédente"
},
"sectionList": {
"pullToRefresh": "Tirer pour recharger"
},
"deepLinking": {
"title": "Lancement de votre réunion dans __app __ en cours...",
"description": "Rien ne s'est passé? Nous avons essayé de lancer votre réunion dans l'application de bureau __app__. Essayez à nouveau ou lancez-la dans l'application web __app__.",
"tryAgainButton": "Réessayez sur le bureau",
"launchWebButton": "Lancer dans le navigateur",
"appNotInstalled": "Vous avez besoin de l'application mobile __app__ pour participer à cette réunion avec votre téléphone.",
"downloadApp": "Télécharger l'application",
"openApp": "Continuer vers l'application"
},
"presenceStatus": {
"invited": "Invité(e)",
"ringing": "Appel en cours...",
"calling": "Appel...",
"initializingCall": "Lancement de l'appel...",
"connected": "Connecté",
"connecting": "Connexion en cours...",
"connecting2": "Connexion en cours*...",
"disconnected": "Déconnecté",
"busy": "Occupé",
"rejected": "Rejeté",
"ignored": "Ignoré",
"expired": "Expiré"
},
"dateUtils": {
"today": "Aujourd'hui",
"yesterday": "Hier",
"earlier": "Plus tôt"
},
"incomingCall": {
"answer": "Répondre",
"audioCallTitle": "Appel entrant",
"decline": "Rejeter",
"productLabel": "de Jitsi Meet",
"videoCallTitle": "Appel vidéo entrant"
},
"localRecording": {
"localRecording": "Enregistrement local",
"dialogTitle": "Commandes de l'enregistrement local",
"start": "Démarrer l'enregistrement",
"stop": "Arrêter l'enregistrement",
"moderator": "Moderateur",
"me": "Moi",
"duration": "Durée",
"durationNA": "N/A",
"encoding": "Encodage",
"participantStats": "Statistiques du participant",
"participant": "Participant",
"sessionToken": "Token de la session",
"clientState": {
"on": "Actif",
"off": "Inactif",
"unknown": "Inconnu"
},
"messages": {
"engaged": "Enregistrement local engagé.",
"finished": "L'enregistrement de la session __token__ s'est terminé. Merci d'envoyer le fichier au modérateur.",
"finishedModerator": "L'enregistrement de la session __token__ s'est terminé. La piste a bien été sauvegardée. Merci de demander aux autres participants de soumettre leurs enregistrements.",
"notModerator": "Vous n'êtes pas le modérateur. Vous ne pouvez pas démarrer ou arrêter un enregistrement local."
},
"yes": "Oui",
"no": "Non",
"label": "ENR-LOC",
"labelToolTip": "L'enregistrement local est engagé"
}
}

View File

@@ -35,31 +35,39 @@
"raiseHand": "Erga ou baixe sua mão",
"pushToTalk": "Pressione para falar",
"toggleScreensharing": "Trocar entre câmera e compartilhamento de tela",
"toggleFilmstrip": "Mostrar ou ocultar a barra lateral",
"toggleShortcuts": "Mostrar ou ocultar este menu de ajuda",
"toggleFilmstrip": "Mostrar ou ocultar miniaturas de vídeo",
"toggleShortcuts": "Mostrar ou ocultar atalhos de teclado",
"focusLocal": "Focar no seu vídeo",
"focusRemote": "Focar no vídeo de outro participante",
"toggleChat": "Abrir ou fechar o painel de bate-papo",
"mute": "Deixar mudo ou não o microfone",
"fullScreen": "Entrar ou sair da tela cheia",
"videoMute": "Iniciar ou parar sua câmera",
"showSpeakerStats": "Exibir estatísticas do alto falante"
"showSpeakerStats": "Exibir estatísticas do alto falante",
"localRecording": "Mostrar ou ocultar controles de gravação local"
},
"welcomepage": {
"accessibilityLabel": {
"join": "Toque para entrar",
"roomname": "Digite o nome da sala"
},
"appDescription": "Vá em frente, converse por vídeo com toda a equipe. De fato, convide todos que você conhece. __app__ é uma solução de videoconferência totalmente criptografada e 100% de código aberto que você pode usar todos os dias, a cada dia, gratuitamente — sem necessidade de conta.",
"audioVideoSwitch": {
"audio": "Voz",
"video": "Vídeo"
},
"calendar": "Calendário",
"connectCalendarText": "Conecte seu calendário para ver todas as suas reuniões no __app__. Além disso, adicione reuniões no __app__ ao seu calendário e inicie-as com um clique.",
"connectCalendarButton": "Conectar seu calendário",
"go": "IR",
"join": "Entrar",
"privacy": "Política de Privacidade",
"recentList": "Hitstórico",
"roomname": "Digite o nome da sala",
"roomnameHint": "Digite o nome ou a URL da sala que você deseja entrar. Você pode digitar um nome, e apenas deixe para as pessoas que você quer se reunir digitem o mesmo nome.",
"sendFeedback": "Enviar comentários",
"terms": "Termos",
"title": "Vídeo conferência mais segura, mais flexível e completamente livre "
"title": "Vídeo conferência mais segura, mais flexível e completamente livre."
},
"startupoverlay": {
"policyText": " ",
@@ -71,8 +79,41 @@
"rejoinKeyTitle": "Reconectar"
},
"toolbar": {
"accessibilityLabel": {
"audioOnly": "Alternar para apenas áudio",
"audioRoute": "",
"callQuality": "Gerenciar qualidade da chamada",
"chat": "Alternar para janela de chat",
"cc": "Alternar legendas",
"document": "Alternar para documento compartilhado",
"feedback": "Deixar feedback",
"fullScreen": "Alternar para tela cheia",
"hangup": "Sair da chamada",
"invite": "Convidar pessoas",
"localRecording": "Alternar controles de gravação local",
"lockRoom": "Alternar trava da sala",
"moreActions": "Alternar mais menu de ações",
"moreActionsMenu": "Menu de mais ações",
"mute": "Alternar mudo do áudio",
"pip": "Alternar modo Picture-in-Picture",
"profile": "Editar seu perfil",
"raiseHand": "Alternar levantar a mão",
"recording": "Alternar gravação",
"Settings": "Alternar configurações",
"sharedvideo": "Alternar compartilhamento de vídeo do Youtube",
"shareRoom": "Convidar alguém",
"shareYourScreen": "Alternar compartilhamento de tela",
"shortcuts": "Alternar atalhos",
"speakerStats": "Alternar estatísticas do apresentador",
"toggleCamera": "",
"tileView": "",
"videomute": "Alternar mudo do vídeo"
},
"addPeople": "Adicionar pessoas à sua chamada",
"audioonly": "Ativar / desativar modo somente áudio (economiza banda)",
"audioOnlyOn": "Ativar modo somente áudio (economiza banda)",
"audioOnlyOff": "Desativar modo somente áudio",
"audioRoute": "Selecionar o dispositivo de som",
"callQuality": "Gerenciar qualidade da chamada",
"enterFullScreen": "Ver em tela cheia",
"exitFullScreen": "Sair da tela cheia",
@@ -86,8 +127,8 @@
"etherpad": "Abrir ou fechar o documento compartilhado",
"documentOpen": "Abrir documento compartilhado",
"documentClose": "Fechar documento compartilhado",
"shareRoom": "Compartilhar sala",
"sharedvideo": "Compartilhar um vídeo do YouTube",
"sharescreen": "Compartilhamento de tela",
"stopSharedVideo": "Parar vídeo do YouTube",
"fullscreen": "Entrar ou sair da tela cheia",
"sip": "Chamar número SIP",
@@ -96,16 +137,20 @@
"login": "Iniciar sessão",
"logout": "Encerrar sessão",
"sharedVideoMutedPopup": "Seu vídeo compartilhado foi silenciado para que você possa conversar com os outros membros.",
"toggleCamera": "Alternar câmera",
"micMutedPopup": "Seu microfone foi silenciado para que você aproveite plenamente seu vídeo compartilhado.",
"talkWhileMutedPopup": "Tentando falar? Você está em mudo.",
"unableToUnmutePopup": "Você não pode sair do mudo enquanto seu vídeo compartilhado está ativo.",
"cameraDisabled": "A câmera não está disponível",
"micDisabled": "O microfone não está disponível",
"filmstrip": "Mostrar / ocultar vídeos",
"pip": "Entrar em modo Quadro-a-Quadro",
"profile": "Editar seu perfil",
"raiseHand": "Erguer o baixar sua mão",
"raiseHand": "Erguer / Baixar sua mão",
"shortcuts": "Ver atalhos",
"speakerStats": "Estatísticas do Apresentador"
"speakerStats": "Estatísticas do Apresentador",
"tileViewToggle": "Alternar visualização em blocos",
"invite": "Convidar pessoas"
},
"chat": {
"nickname": {
@@ -115,6 +160,13 @@
"messagebox": "Digite um texto..."
},
"settings": {
"calendar": {
"about": "A integração do calendário __appName__ é usada para acessar com segurança o seu calendário para que ele possa ler os próximos eventos.",
"disconnect": "Desconectar",
"microsoftSignIn": "Entrar com Microsoft",
"signedIn": "Atualmente acessando eventos do calendário para __email__. Clique no botão Desconectar abaixo para parar de acessar os eventos da agenda.",
"title": "Calendário"
},
"title": "Configurações",
"update": "Atualizar",
"name": "Nome",
@@ -124,11 +176,15 @@
"selectMic": "Microfone",
"selectAudioOutput": "Saída de áudio",
"followMe": "Todos me seguem",
"language": "Idioma",
"loggedIn": "Conectado como __name__",
"noDevice": "Nenhum",
"cameraAndMic": "Câmera e microfone",
"moderator": "MODERADOR",
"moderator": "Moderador",
"more": "Mais",
"password": "DEFINIR SENHA",
"audioVideo": "ÁUDIO E VÍDEO"
"audioVideo": "ÁUDIO E VÍDEO",
"devices": "Dispositivos"
},
"profile": {
"title": "Perfil",
@@ -187,7 +243,6 @@
"focus": "Foco da conferência",
"focusFail": "__component__ não disponĩvel - tente em __ms__ seg.",
"grantedTo": "Direitos de moderador concedido para __to__!",
"grantedToUnknown": "Direitos de moderador concedido para $t(notify.somebody)!",
"muted": "Você iniciou uma conversa em mudo.",
"mutedTitle": "Você está mudo!",
"raisedHand": "Gostaria de falar.",
@@ -195,7 +250,11 @@
"suboptimalExperienceDescription": "Eer ... temos medo de que sua experiência com o __appName__ não seja tão boa aqui. Estamos procurando maneiras de melhorar isso, mas até lá tente usar um dos <a href='static/recommendedBrowsers.html' target='_blank'> navegadores totalmente compatíveis</a>."
},
"dialog": {
"accessibilityLabel": {
"liveStreaming": "Transmissão ao vivo"
},
"allow": "Permitir",
"confirm": "Confirmar",
"kickMessage": "Ouch! Você foi expulso da reunião!",
"popupErrorTitle": "Popup bloqueado",
"popupError": "Seu navegador está bloqueando janelas popup deste site. Habilite os popups nas configurações de segurança no seu navegador e tente novamente.",
@@ -219,6 +278,7 @@
"rejoinNow": "Reconectar agora",
"maxUsersLimitReachedTitle": "Limite máximo de membros alcançado",
"maxUsersLimitReached": "O limite para o máximo do número de membros foi alcançado. A conferência está cheia. Contate o dono da reunião ou tente de novo depois!",
"lockRoom": "Travar sala",
"lockTitle": "Bloqueio falhou",
"lockMessage": "Falha ao travar a conferência.",
"warning": "Atenção",
@@ -266,6 +326,7 @@
"reservationError": "Erro de sistema de reserva",
"reservationErrorMsg": "Código do erro: __code__, mensagem: __msg__",
"password": "Insira a senha",
"unlockRoom": "Destravar sala",
"userPassword": "senha do usuário",
"token": "token",
"tokenAuthFailedTitle": "Falha de autenticação",
@@ -278,11 +339,11 @@
"sorryFeedback": "Sentimos muito pelos transtornos. Poderia nos das mais detalhes?",
"liveStreaming": "Transmissão ao Vivo",
"streamKey": "Chave para transmissão ao vivo",
"startLiveStreaming": "Ir ao vivo agora",
"startLiveStreaming": "Iniciar transmissão ao vivo",
"startRecording": "Parar gravação",
"stopStreamingWarning": "Tem certeza que deseja parar a transmissão ao vivo?",
"stopRecordingWarning": "Tem certeza que deseja parar a gravação?",
"stopLiveStreaming": "Parar a transmissão ao vivo",
"stopLiveStreaming": "Parar transmissão ao vivo",
"stopRecording": "Parar a gravação",
"doNotShowMessageAgain": "Não mostre esta mensagem novamente",
"permissionDenied": "Permissão Negada",
@@ -312,6 +373,10 @@
"muteParticipantTitle": "Silenciar esse membro?",
"muteParticipantBody": "Você não está habilitado para tirar o mudo deles, mas eles podem tirar o mudo deles mesmos a qualquer tempo.",
"muteParticipantButton": "Mudo",
"liveStreamingDisabledTooltip": "Iniciar transmissão ao vivo desativada.",
"liveStreamingDisabledForGuestTooltip": "Visitantes não podem iniciar transmissão ao vivo.",
"recordingDisabledTooltip": "Iniciar gravação desativada.",
"recordingDisabledForGuestTooltip": "Visitantes não podem iniciar gravações.",
"remoteControlTitle": "Conexão de área de trabalho remota",
"remoteControlRequestMessage": "Deseja permitir que __user__ controle remotamente sua área de trabalho?",
"remoteControlShareScreenWarning": "Note que se você pressionar \"Permitir\" você vai compartilhar sua tela!",
@@ -322,8 +387,11 @@
"remoteControlStopMessage": "A sessão de controle remoto terminou!",
"close": "Fechar",
"shareYourScreen": "Compartilhar sua tela",
"shareYourScreenDisabled": "Compartilhamento de tela desativada.",
"shareYourScreenDisabledForGuest": "Visitantes não podem compartilhar tela.",
"yourEntireScreen": "Toda sua tela",
"applicationWindow": "Janela de aplicativo"
"applicationWindow": "Janela de aplicativo",
"transcribing": "Transcrevendo"
},
"email": {
"sharedKey": [
@@ -353,6 +421,22 @@
],
"and": "e"
},
"share": {
"mainText": [
"Clique no seguinte link para entrar na reunião:",
"__roomUrl__"
],
"dialInfoText": [
"",
"",
"=====",
"",
"Quer apenas ligar no seu telefone?",
"",
"Clique neste link para ver a discagem nos números de telefone para esta reunião",
"__dialInfoPageUrl__"
]
},
"connection": {
"ERROR": "Erro",
"CONNECTING": "Conectando",
@@ -366,18 +450,37 @@
"ATTACHED": "Anexado"
},
"recording": {
"beta": "BETA",
"busy": "Estamos trabalhando para liberar recursos de gravação. Tente novamente em alguns minutos.",
"busyTitle": "Todas as gravações estão atualmente ocupadas",
"buttonTooltip": "Iniciar / parar gravação",
"error": "A gravação falhou. Tente novamente.",
"failedToStart": "Falha ao iniciar a gravação",
"live": "AOVIVO",
"off": "Gravação parada",
"on": "Gravando",
"pending": "Aguardando um participante para iniciar a gravação...",
"pending": "Preparando para gravar a reunião...",
"rec": "GRAVAR",
"authDropboxText": "Enviar sua gravação para o Dropbox.",
"authDropboxCompletedText": "Seu arquivo de gravação aparecerá no seu Dropbox logo após terminar a gravação.",
"serviceName": "Serviço de gravação",
"signOut": "Sair",
"signIn": "entrar",
"loggedIn": "Conectado como __userName__",
"availableSpace": "Espaço disponível: __spaceLeft__ MB (aproximadamente __duration__ minutos de gravação)",
"startRecordingBody": "Tem certeza que deseja iniciar a gravação?",
"unavailable": "Oops! O __serviceName__ está indisponível. Estamos trabalhando para resolver o problema. Por favor, tente mais tarde.",
"unavailableTitle": "Gravação indisponível"
},
"transcribing": {
"pending": "Preparando a transcrição da reunião...",
"off": "Transcrição parada",
"error": "Transcrição falhou. Tente novamente.",
"failedToStart": "Transcrição falhou ao iniciar",
"tr": "TR",
"labelToolTip": "A reunião esta sendo transcrita",
"ccButtonTooltip": "Iniciar / Parar de mostrar as legendas"
},
"liveStreaming": {
"busy": "Estamos trabalhando para liberar os recursos de transmissão. Tente novamente em alguns minutos.",
"busyTitle": "Todas as transmissões estão atualmente ocupadas",
@@ -388,6 +491,7 @@
"enterStreamKey": "Insira sua chave de transmissão ao vivo do YouTube aqui.",
"error": "Falha na transmissão ao vivo. Tente de novo.",
"errorAPI": "Ocorreu um erro ao acessar suas transmissões do YouTube. Por favor tente logar novamente.",
"errorLiveStreamNotEnabled": "Transmissão ao vivo não está ativada em __email__. Ative a transmissão ao vivo ou registre numa conta com transmissão ao vivo ativada.",
"failedToStart": "Falha ao iniciar a transmissão ao vivo",
"off": "Transmissão ao vivo encerrada",
"on": "Transmissão ao Vivo",
@@ -424,9 +528,10 @@
"noPermission": "Permissão não concedida",
"previewUnavailable": "Visualização indisponível",
"selectADevice": "Selecione um dispositivo",
"testAudio": "Testar o som"
"testAudio": "Tocar um som de teste"
},
"videoStatus": {
"audioOnly": "AUD",
"callQuality": "Qualidade da Chamada",
"hd": "HD",
"hdTooltip": "Ver vídeo em alta definição",
@@ -455,6 +560,7 @@
"countryNotSupported": "Ainda não suportamos este destino.",
"countryReminder": "Ligando fora dos EUA? Por favor, certifique-se de começar com o código do país!",
"disabled": "Você não pode convidar pessoas.",
"footerText": "Discagem está desativada.",
"invite": "Convidar",
"loading": "Procurando por pessoas e números de telefone",
"loadingNumber": "Validando o número de telefone",
@@ -491,6 +597,7 @@
"veryGood": "Muito boa"
},
"info": {
"accessibilityLabel": "Mostrar info",
"addPassword": "Adicionar uma senha",
"cancelPassword": "Cancelar senha",
"conferenceURL": "Link:",
@@ -512,7 +619,7 @@
"numbers": "Números de discagem",
"password": "Senha:",
"title": "Compartilhar",
"tooltip": "Obtenha informações de acesso sobre a reunião"
"tooltip": "Compartilhar link e discagem para esta reunião"
},
"settingsView": {
"alertOk": "OK",
@@ -528,17 +635,17 @@
"startWithVideoMuted": "Iniciar sem vídeo"
},
"calendarSync": {
"later": "Depois",
"next": "Recebendo",
"addMeetingURL": "Adicionar um link da reunião",
"today": "Hoje",
"nextMeeting": "próxima reunião",
"now": "Agora",
"noEvents": "Não há eventos próximos agendados.",
"ongoingMeeting": "reunião em progresso",
"permissionButton": "Abrir configurações",
"permissionMessage": "Permissão do calendário é requerida para listar suas reuniões na aplicação."
"permissionMessage": "Permissão do calendário é requerida para ver suas reuniões na aplicação.",
"refresh": "Atualizar calendário"
},
"recentList": {
"today": "Hoje",
"yesterday": "Ontem",
"earlier": "Mais cedo"
"joinPastMeeting": "Entrar em uma reunião passada"
},
"sectionList": {
"pullToRefresh": "Puxe para atualizar"
@@ -551,5 +658,60 @@
"appNotInstalled": "Você precisa do aplicativo móvel __app__ para participar da reunião no seu telefone.",
"downloadApp": "Baixe o Aplicativo",
"openApp": "Continue na aplicação"
},
"presenceStatus": {
"invited": "Convidar",
"ringing": "Toque...",
"calling": "Chamando...",
"initializingCall": "Iniciando Chamada...",
"connected": "Conectado",
"connecting": "Conectando...",
"connecting2": "Conectando*...",
"disconnected": "Desconectado",
"busy": "Ocupado",
"rejected": "Rejeitado",
"ignored": "Ignorado",
"expired": "Expirado"
},
"dateUtils": {
"today": "Hoje",
"yesterday": "Ontem",
"earlier": "Mais cedo"
},
"incomingCall": {
"answer": "Responder",
"audioCallTitle": "Chamada chegando",
"decline": "Dispensar",
"productLabel": "do Jitsi Meet",
"videoCallTitle": "Chamada de vídeo chegando"
},
"localRecording": {
"localRecording": "Gravação local",
"dialogTitle": "Controles da Gravação Local",
"start": "Iniciar gravação",
"stop": "Parar a Gravação",
"moderator": "Moderador",
"me": "Eu",
"duration": "Duração",
"durationNA": "N/A",
"encoding": "Codificando",
"participantStats": "Estatísticas dos Participantes",
"participant": "Participante",
"sessionToken": "Token de Sessão",
"clientState": {
"on": "On",
"off": "Off",
"unknown": "Desconhecido"
},
"messages": {
"engaged": "Gravação local iniciada.",
"finished": "Sessão de gravação __token__ terminada. Por favor, envie o arquivo gravado para o moderador.",
"finishedModerator": "Sessão de gravação __token__ terminada. A gravação da faixa local foi salva. Por favor, peça aos outros participantes para enviar suas gravações.",
"notModerator": "Você não é o moderador. Você não pode iniciar ou parar a gravação local."
},
"yes": "Sim",
"no": "Não",
"label": "LOR",
"labelToolTip": "Gravação local está envolvida"
}
}

View File

@@ -1,183 +1,148 @@
{
"contactlist": "Участники (__pcount__)",
"addParticipants": "Поделиться ссылкой",
"roomLocked": "Вызывающие должны ввести пароль",
"roomUnlocked": "Любой, владеющий ссылкой, может присоединиться",
"contactlist_plural": "Участников: __count__",
"passwordSetRemotely": "установлен другим участником",
"connectionsettings": "Настройки подключения",
"poweredby": "работает на",
"feedback": {
"average": "",
"bad": "",
"good": "",
"rateExperience": "Пожалуйста, оцените ваш опыт встречи.",
"veryBad": "",
"veryGood": ""
},
"inviteUrlDefaultMsg": "Ваша конференция создается в данный момент...",
"me": "Я",
"speaker": "Говорящий",
"inviteUrlDefaultMsg": "Подготавливаем вашу встречу...",
"me": "я",
"speaker": "Колонка",
"raisedHand": "Хочет говорить",
"defaultNickname": "напр. Яна Цветочкина",
"defaultNickname": "напр. Яна Цветкова",
"defaultLink": "напр. __url__",
"callingName": "__name__",
"audioDevices": {
"bluetooth": "Bluetooth",
"headphones": "Наушники",
"phone": "Телефон",
"speaker": "Колонка"
},
"audioOnly": {
"audioOnly": "",
"featureToggleDisabled": ""
"audioOnly": "Только звук",
"featureToggleDisabled": "Переключение функции __feature__ недоступно в режиме \"только звук\""
},
"userMedia": {
"react-nativeGrantPermissions": "",
"chromeGrantPermissions": "",
"androidGrantPermissions": "",
"firefoxGrantPermissions": "",
"operaGrantPermissions": "",
"iexplorerGrantPermissions": "",
"safariGrantPermissions": "",
"nwjsGrantPermissions": "Пожалуйста дайте разрешение на доступ к камере и микрофону",
"edgeGrantPermissions": ""
"react-nativeGrantPermissions": "Выберите <b><i>Разрешить</i></b>, когда браузер спросит о разрешениях.",
"chromeGrantPermissions": "Выберите <b><i>Разрешить</i></b>, когда браузер спросит о разрешениях.",
"androidGrantPermissions": "Выберите <b><i>Разрешить</i></b>, когда браузер спросит о разрешениях.",
"electronGrantPermissions": "Пожалуйста, дайте разрешение на доступ к камере и микрофону",
"firefoxGrantPermissions": "Выберите <b><i>Поделиться выбранным устройством</i></b>, когда браузер спросит о разрешениях.",
"operaGrantPermissions": "Выберите <b><i>Разрешить</i></b>, когда браузер спросит о разрешениях.",
"iexplorerGrantPermissions": "Выберите <b><i>OK</i></b>, когда браузер спросит о разрешениях.",
"safariGrantPermissions": "Выберите <b><i>OK</i></b>, когда браузер спросит о разрешениях.",
"nwjsGrantPermissions": "Пожалуйста, дайте разрешение на доступ к камере и микрофону",
"edgeGrantPermissions": "Выберите <b><i>Да</i></b>, когда браузер спросит о разрешениях."
},
"keyboardShortcuts": {
"keyboardShortcuts": "Комбинации клавиш",
"raiseHand": "Поднять или опустить руку",
"pushToTalk": "Нажмите, чтобы говорить",
"toggleScreensharing": "Переключиться между камерой и совместным использованием экрана",
"toggleScreensharing": "Переключиться между камерой и показом экрана",
"toggleFilmstrip": "Показать или скрыть видео",
"toggleShortcuts": "Показать или скрыть это справочное меню",
"focusLocal": "Фокус на ваше видео",
"focusRemote": "Фокус на видео другого абонента",
"toggleChat": "Открыть или закрыть чат",
"mute": "Заглушить или включить микрофон",
"fullScreen": "Войти или выйти из полноэкранного режима",
"videoMute": "Включить или выключить вашу камеру",
"showSpeakerStats": ""
"focusRemote": "Фокус на видео другого участника",
"toggleChat": "Чат (открыть/закрыть)",
"mute": "Микрофон (вкл./выкл.)",
"fullScreen": "Полноэкранный режим (вкл./выкл.)",
"videoMute": "Камера (вкл./выкл.)",
"showSpeakerStats": "Показать статистику выступающего"
},
"welcomepage": {
"disable": "Не показывать эту страницу снова",
"feature1": {
"content": "Нет нужды что-либо скачивать. __app__ работает прямо из вашего браузера. Просто отправьте URL ссылку на вашу конференцию другим, чтобы начать общение.",
"title": "Простой в использовании"
"appDescription": "Попробуйте видеочат со всей командой. Приглашайте знакомых! __app__ — полностью зашифрованное решение для видеоконференций с открытым исходным кодом. Пользуйтесь каждый день, бесплатно и без регистрации.",
"audioVideoSwitch": {
"audio": "Календарь",
"video": "Видео"
},
"feature2": {
"content": "Многопользовательским видеоконференциям достаточно скорости передачи данных в 128 Кбит/с. Демонстрация экрана или аудиоконференции требуют и того меньше.",
"title": "Низкие требования к ширине канала"
},
"feature3": {
"content": "__app__ лицензирован под Apache License. Вы можете свободно скачивать, использовать, изменять это ПО в соответствии с условиями лицензии.",
"title": "Исходный код открыт"
},
"feature4": {
"content": "Нет никаких искусственных ограничений по количеству пользовательниц или участников конференций. Вас отграничивают только мощность сервера и качество соединения.",
"title": "Количество пользовательниц не ограничено"
},
"feature5": {
"content": "С лёгкостью можно пользоваться экраном совместно. __app__ идеально для онлайн презентаций, лекций и сеансов техподдержки.",
"title": "Общий доступ к экрану"
},
"feature6": {
"content": "Нужно больше приватности? __app__ конференц-комнаты могут быть защищены паролем, чтобы исключить незваных гостей или заминки.",
"title": "Защищённые комнаты"
},
"feature7": {
"content": "__app__ включает Etherpad, текстовый редактор для совместной работы над текстом в реальном времени, который замечательно подходит, чтобы вести протоколы или совместно писать статьи.",
"title": "Поделиться заметками"
},
"feature8": {
"content": "Узнайте больше о пользователях с помощью интеграции с Piwik, Google Analytics и другими системами мониторига и сбора статистики.",
"title": "Статистика использования"
},
"go": "Вперед!",
"join": "",
"privacy": "",
"roomname": "Введите название комнаты",
"roomnamePlaceHolder": "",
"sendFeedback": "",
"terms": ""
"calendar": "Календарь",
"go": "ОК",
"join": "ПРИСОЕДИНИТЬСЯ",
"privacy": "Приватность",
"roomname": "Укажите название комнаты",
"roomnameHint": "Укажите название комнаты или ее адрес. Можете сами создать название и передать его будущим участникам встречи, чтобы они использовали именно его.",
"sendFeedback": "Обратная связь",
"terms": "Условия",
"title": "Видеоконференции. Безопасно. Гибко. Полностью бесплатно"
},
"\u0005welcomepage": {},
"startupoverlay": {
"policyText": "",
"title": "__app__ нуждается в использовании вашего микрофона и камеры."
"policyText": " ",
"title": "__app__ требуется доступ к микрофону и камере."
},
"suspendedoverlay": {
"title": "",
"text": "",
"rejoinKeyTitle": рисоединиться повторно"
"title": "Видеосвязь прервана. Причина: этот компьютер перешел в режим сна.",
"text": "Для восстановления связи нажмите кнопку <i>Подключиться снова</i>.",
"rejoinKeyTitle": одключиться снова"
},
"toolbar": {
"addPeople": "",
"audioonly": "",
"mute": "Вкл. / Выкл. звук",
"videomute": "Вкл / Выкл камеру",
"addPeople": "Добавить людей к вашему сеансу связи",
"audioonly": "Режим \"только звук\" (вкл./выкл.), экономит трафик",
"callQuality": "Качество связи",
"enterFullScreen": "Полный экран",
"exitFullScreen": "Полный экран",
"feedback": "Оставить отзыв",
"moreActions": "Больше",
"mute": "Звук (вкл./выкл.)",
"videomute": "Камера",
"authenticate": "Аутентифицировать",
"lock": "Заблокировать / разблокировать комнату",
"invite": "Поделиться ссылкой",
"chat": "Открыть / Закрыть чат",
"etherpad": "Открыть / Закрыть общий документ",
"sharedvideo": "Поделиться YouTube видео",
"sharescreen": "Начать / Завершить совместное использование экрана",
"fullscreen": "Вкл / Выкл полноэкранный режим",
"sip": "Набрать SIP номер",
"lock": "Блокировка комнаты",
"chat": "Чат",
"etherpad": "Общий документ",
"documentOpen": "Открыть общий документ",
"documentClose": "Закрыть общий документ",
"sharedvideo": "Видео YouTube",
"sharescreen": "Показ экрана",
"stopSharedVideo": "Остановить видео на YouTube",
"fullscreen": "Полноэкранный режим (вкл./выкл.)",
"sip": "Набрать номер SIP",
"Settings": "Настройки",
"hangup": "Покинуть",
"hangup": "Выход",
"login": "Войти",
"logout": "Завершить сеанс",
"dialpad": "Открыть / Закрыть клавиатуру для набора номера",
"sharedVideoMutedPopup": "У видео, которым Вы поделились, отключён звук, чтобы вы могли говорить с остальными.",
"micMutedPopup": "Ваш микрофон отключён, чтобы вы могли сосредоточиться на видео, которым поделились.",
"talkWhileMutedPopup": "Пытаетесь говорить? Вы приглушены.",
"sharedVideoMutedPopup": "Звук видео, которым вы делитесь, отключен, чтобы вы могли общаться с другими участниками.",
"micMutedPopup": "Ваш микрофон отключен, чтобы вы могли слышать звук вашего видео.",
"talkWhileMutedPopup": "Пытаетесь говорить? У вас отключен звук.",
"unableToUnmutePopup": "Вы не можете включить звук, потому что включено видео.",
"cameraDisabled": "Камера недоступна",
"micDisabled": "Микрофон недоступен",
"filmstrip": "Показать / Скрыть видео",
"profile": "Редактировать ваш профиль",
"raiseHand": "Поднять / Опустить вашу руку"
},
"unsupportedBrowser": {
"appInstalled": "",
"appNotInstalled": "",
"downloadApp": "",
"joinConversation": "",
"startConference": ""
},
"bottomtoolbar": {
"chat": "Открыть / Закрыть чат",
"filmstrip": "Показать / Скрыть видео",
"contactlist": "Просмотреть и пригласить участников"
"filmstrip": "Видео (показать/скрыть)",
"profile": "Редактировать профиль",
"raiseHand": "Хочу говорить",
"shortcuts": "Комбинации клавиш",
"speakerStats": "Статистика"
},
"\u0005toolbar": {},
"chat": {
"nickname": {
"title": "Введите имя в поле ниже",
"popover": "Выберите имя"
},
"messagebox": "Введите текст.."
"messagebox": "Пишите..."
},
"settings": {
"title": "Настройки",
"update": "Обновить",
"name": "Имя",
"startAudioMuted": "Каждый начинает глушиться",
"startVideoMuted": "Все начинают скрываться",
"startAudioMuted": "Все начинают с выключенным звуком",
"startVideoMuted": "Все начинают в скрытом режиме",
"selectCamera": "Камера",
"selectMic": "Микрофон",
"selectAudioOutput": "Звуковой выход",
"followMe": "Каждый следует за мной",
"noDevice": "Нет",
"followMe": "Все следуют за мной",
"noDevice": "Все как я",
"cameraAndMic": "Камера и микрофон",
"moderator": "МОДЕРАТОР",
"password": "УСТАНОВИТЬ ПАРОЛЬ",
"audioVideo": "АУДИО И ВИДЕО"
"audioVideo": "ЗВУК И ВИДЕО"
},
"profile": {
"title": "Профиль",
"setDisplayNameLabel": "Установить ваше отображаемое имя",
"setEmailLabel": "Установить электронную почту для gravatar",
"setEmailInput": "Введите электронную почту"
"setDisplayNameLabel": "Отображаемое имя",
"setEmailLabel": "E-mail для gravatar",
"setEmailInput": "Введите e-mail"
},
"videothumbnail": {
"editnickname": "Нажми, чтобы<br/>поменять имя экрана",
"moderator": "Хозяйка конференции.",
"videomute": "Участник<br/>остановил камеру",
"mute": "Без звука",
"kick": "Прогнать",
"moderator": "Модератор",
"videomute": "Участник отключил камеру",
"mute": "Участник отключил звук",
"kick": "Выкинуть",
"muted": "Звук выключен",
"domute": "Выключить звук",
"flip": "Отразить",
@@ -185,208 +150,221 @@
},
"connectionindicator": {
"header": "Данные соединения",
"bitrate": "Битрейт",
"packetloss": "Потеря пакетов:",
"bitrate": "Битрейт:",
"packetloss": "Потери пакетов:",
"resolution": "Разрешение:",
"framerate": "",
"less": "Свернуть",
"more": "Показать больше",
"framerate": "Частота кадров:",
"less": "Меньше",
"more": "Больше",
"address": "Адрес:",
"remoteport": "Удалённый порт:",
"remoteport_plural_2": "",
"remoteport_plural_5": "",
"localport": "Локальный порт:",
"remoteport": "Удаленные порты:",
"remoteport_plural_2": "Удаленные порты:",
"remoteport_plural_5": "Удаленные порты:",
"localport": "Локальные порты:",
"localport_plural_2": "Локальные порты:",
"localport_plural_5": "",
"localaddress": "Локальный адрес:",
"localport_plural_5": "Локальные порты:",
"localaddress": "Локальные адреса:",
"localaddress_plural_2": "Локальные адреса:",
"localaddress_plural_5": "",
"remoteaddress": "Удалённый адрес:",
"remoteaddress_plural_2": "",
"remoteaddress_plural_5": "",
"transport": "Метод отправки:",
"bandwidth": "Средняя скорость соединения:",
"na": "Вернитесь сюда за информацией о соединении, когда конференция начнётся",
"turn": ""
"localaddress_plural_5": "Локальные адреса:",
"remoteaddress": "Удаленные адреса:",
"remoteaddress_plural_2": "Удаленные адреса:",
"remoteaddress_plural_5": "Удаленные адреса:",
"transport": "Методы отправки:",
"transport_plural_2": "Методы отправки:",
"transport_plural_5": "Методы отправки:",
"bandwidth": "Средняя скорость:",
"na": "Во время беседы вы можете следить за данными о соединении",
"turn": " (повернуть)",
"quality": {
"good": "Хорошо",
"inactive": "не активно",
"lost": "потеряно",
"nonoptimal": "не оптимально",
"poor": "плохо"
},
"status": "Связь:"
},
"notify": {
"disconnected": "соединение разорвано",
"moderator": "Получены права для модерации!",
"connected": "подключено",
"moderator": "Получены права модератора!",
"connectedOneMember": "__name__ подключен",
"connectedTwoMembers": "__first__ и __second__ подключены",
"connectedThreePlusMembers": "__name__ и другие (__count__) подключены",
"somebody": "Кто-то",
"me": "Я",
"focus": "Фокусировка конференции",
"focusFail": "__component__ недоступен - повторите через __ms__ секунд",
"grantedTo": "Теперь модерирует __to__!",
"grantedToUnknown": "",
"muted": "Вы начали конференцию без звука.",
"focus": "Фокус встречи",
"focusFail": "__component__ недоступен, повторите через __ms__ с",
"grantedTo": "__to__ получил права модератора!",
"grantedToUnknown": "К сожалению, что-то пошло не так.",
"muted": "Вы начали разговор без звука.",
"mutedTitle": "Вы без звука!",
"raisedHand": "Хочу высказаться."
"raisedHand": "Хочет говорить.",
"suboptimalExperienceTitle": "К сожалению, этот браузер может не подойти для работы с __appName__. Мы работаем над проблемой, а пока попробуйте один из <a href='static/recommendedBrowsers.html 'target='_blank'>полностью поддерживаемых браузеров</a>.",
"suboptimalExperienceDescription": "К сожалению, этот браузер не очень подходит для работы с __appName__. Мы работаем над проблемой, а пока попробуйте один из <a href='static/recommendedBrowsers.html 'target='_blank'>полностью поддерживаемых браузеров</a>."
},
"dialog": {
"add": "Добавить",
"allow": "",
"kickMessage": "Фигасе! Вас прогнали со встречи!",
"popupError": "Ваш браузер блокирует всплывающие окна на этом сайте. Пожалуйста разрешите всплывающие окна в настройках безопасности и попробуйте снова.",
"allow": "Разрешить",
"kickMessage": "Вас выкинули из комнаты!",
"popupErrorTitle": "Заблокировано всплывающее окно",
"popupError": "Ваш браузер блокирует всплывающие окна этого сайта. Пожалуйста, разрешите всплывающие окна в настройках безопасности браузера и попробуйте снова.",
"passwordErrorTitle": "Ошибка пароля",
"passwordError": "Этот разговор сейчас защищён паролем. Только хозяйка конференции может устанавливать пароль.",
"passwordError2": "Эта конференция защищена паролем. Только хозяйка конференции может устанавливать пароль.",
"connectError": "Ёпрст! Что-то пошло не так и мы не можем связаться с конференцией.",
"connectErrorWithMsg": "Ёпрст! Что-то пошло не так и мы не можем связаться с конференцией: __msg__",
"incorrectPassword": "Неверный пароль",
"connecting": "Идёт подключение",
"passwordError": "Эта встреча защищена паролем. Только организатор встречи может устанавливать пароль.",
"passwordError2": "Эта встреча не защищена паролем. Только организатор встречи может устанавливать пароль.",
"connectError": "Ошибка. Невозможно установить связь для вашей встречи.",
"connectErrorWithMsg": "Ошибка. Невозможно установить связь для вашей встречи: __msg__",
"incorrectPassword": "Ошибка имени пользователя или пароля",
"connecting": "Подключение",
"copy": "Копировать",
"contactSupport": "Связь с поддержкой",
"error": "Ошибка",
"createPassword": "Создать пароль",
"detectext": "Ошибка при попытке определить расширение для совместного использования экрана.",
"failtoinstall": "Невозможно установить расширение для совместного использования рабочего стола",
"failedpermissions": "Невозможно получить права на использование локального микрофона и/или камеры.",
"conferenceReloadTitle": "",
"conferenceReloadMsg": "",
"conferenceDisconnectTitle": "",
"conferenceDisconnectMsg": "",
"rejoinNow": "",
"maxUsersLimitReached": "Достигнут максимум количества участников конференции. Конференция заполнена. Пожалуйста попробуйте позже!",
"detectext": "Ошибка связи с расширением для показа экрана.",
"failedpermissions": "Ошибка доступа к локальному микрофону и/или камере.",
"conferenceReloadTitle": "К сожалению, что-то пошло не так.",
"conferenceReloadMsg": "Мы стараемся это исправить. Восстановление связи через __seconds__ с.",
"conferenceDisconnectTitle": "Вы отключены.",
"conferenceDisconnectMsg": "Следует проверить интернет-соединение. Попытка восстановления связи через __seconds__ с.",
"dismiss": "Отклонить",
"rejoinNow": "Подключиться снова",
"maxUsersLimitReachedTitle": "Достигнут лимит числа участников",
"maxUsersLimitReached": "Достигнуто максимальное число участников. Больше нельзя. Пожалуйста, свяжитесь с организатором встречи или попробуйте позже!",
"lockTitle": "Блокировка не удалась",
"lockMessage": "Не удалось запереть конференцию",
"warning": "Внимание",
"passwordNotSupported": "Пароли для комнат сейчас не поддерживаются.",
"passwordNotSupportedTitle": "Пароль не поддержвается",
"passwordNotSupported": "Установка пароля не поддерживается.",
"internalErrorTitle": "Внутренняя ошибка",
"internalError": "Ой! Что-то пошло не так. Возникла следующая ошибка: [setRemoteDescription]",
"unableToSwitch": "Невозможно сменить видео трансляцию.",
"SLDFailure": "Ёпрст! Что-то пошло не так и мы не можем отключить звук! (ошибка SLD)",
"SRDFailure": "Ёпрст! Что-то пошло не так и мы не можем остановить видео! (ошибка SRD)",
"oops": "Ёпрст!",
"currentPassword": "Текущим паролем является",
"internalError": "Что-то пошло не так. Ошибка: __error__",
"unableToSwitch": "Невозможно сменить трансляцию видео.",
"SLDFailure": "Что-то пошло не так, невозможно выключить звук! (ошибка SLD)",
"SRDFailure": "Что-то пошло не так, невозможно выключить видео! (ошибка SRD)",
"oops": "Ой!",
"currentPassword": "Текущий пароль",
"passwordLabel": "Пароль",
"defaultError": "Какая-то ошибка",
"defaultError": "Произошла ошибка",
"passwordRequired": "Требуется пароль",
"Ok": "Ok",
"done": "Готово",
"Remove": "Удалить",
"removePassword": "Удалить пароль",
"shareVideoTitle": "Поделиться видео",
"shareVideoLinkError": "Пожалуйста введите корректную youtube ссылку.",
"removeSharedVideoTitle": "Удалить общее видео",
"removeSharedVideoMsg": "Вы уверрены, что хотите удалить ваше расшаренное видео?",
"alreadySharedVideoMsg": "Другая участница сейчас делится видео. В этой конференции можно делиться только одним видео одновременно.",
"WaitingForHost": "Ожидание хоста...",
"WaitForHostMsg": "Конференция <b>__room__ </b> ещё не началась. Если вы её хост - аутентифицируйтесь. Или сидите ждите хоста.",
"IamHost": "Я хост",
"Cancel": "Отменить",
"Submit": "Принять",
"shareVideoLinkError": "Пожалуйста, укажите корректную ссылку Youtube.",
"removeSharedVideoTitle": "Убрать видео",
"removeSharedVideoMsg": "Уверены, что хотите убрать видео, которым поделились?",
"alreadySharedVideoMsg": "Другой участник уже показывает свое видео. Нельзя показывать два видео одновременно.",
"alreadySharedVideoTitle": "Допускается показ только одного видео",
"WaitingForHost": "Ждем организатора...",
"WaitForHostMsg": "Встреча <b>__room__ </b> еще не началась. Если вы организатор встречи, пожалуйста, представьтесь. Если нет, подождите организатора.",
"IamHost": "Я организатор",
"Cancel": "Отмена",
"Submit": "ОК",
"retry": "Повторить",
"logoutTitle": "Завершить сеанс",
"logoutQuestion": "Вы уверены, что хотите выйти и остановить конференцию?",
"sessTerminated": "Сеанс закрыт",
"logoutQuestion": "Уверены, что хотите выйти и остановить встречу?",
"sessTerminated": "Связь прервана",
"hungUp": "Вы повесили трубку",
"joinAgain": "Войдите заново",
"joinAgain": "Войти снова",
"Share": "Поделиться",
"Save": "Сохранить",
"recording": "Запись",
"recordingToken": "Введите токен для записи",
"passwordCheck": "Вы уверены, что хотите удалить ваш пароль?",
"passwordMsg": "Введите пароль для вашей комнаты",
"shareLink": "Поделитесь ссылкой на звонок",
"settings1": "Настройка Вашей конференции",
"settings2": "Участница подключилась без звука",
"settings3": "Нужны имена<br/><br/>Установите пароль, чтобы запереть Вашу комнату:",
"yourPassword": "Введите новый пароль",
"Back": "Назад",
"serviceUnavailable": "Служба недоступна",
"gracefulShutdown": "Сервис закрыт на переучёт. Пожалуйста попробуйте позже.",
"gracefulShutdown": "Технические работы. Пожалуйста, попробуйте позже.",
"Yes": "Да",
"reservationError": "Ошибка системы резервации",
"reservationError": "Ошибка системы резервирования",
"reservationErrorMsg": "Код ошибки: __code__, сообщение: __msg__",
"password": "Введите пароль",
"userPassword": "пароль пользователя",
"token": "токен",
"tokenAuthFailedTitle": "Ошибка аутентификации",
"tokenAuthFailed": "Извините, вам не разрешено присоединиться к этому звонку.",
"displayNameRequired": "Требуется отображаемое имя",
"enterDisplayName": "Пожалуйста, введите Ваше имя экрана",
"extensionRequired": "Требуется расширение:",
"firefoxExtensionPrompt": "Нужно установить расширение Firefox, чтобы совместно пользоваться экраном. Попробуйте позже, скачав его <a href='__url__'>отсюда</a>!",
"feedbackHelp": "Ваша поддержка поможет нам улучшить опыт видео.",
"feedbackQuestion": "Расскажите нам о вашем звонке!",
"thankYou": "Спасибо за использование __appName__!",
"sorryFeedback": "Мы удручены услышанным. Может расскажете поподробнее?",
"tokenAuthFailed": "Извините, вам не разрешено присоединиться к этому сеансу связи.",
"displayNameRequired": "Требуется имя",
"enterDisplayName": "Пожалуйста, введите имя",
"feedbackHelp": "Ваш отзыв поможет нам улучшать параметры видеосвязи.",
"feedbackQuestion": "Расскажите, как вам понравилась связь!",
"thankYou": "Спасибо, что используете __appName__!",
"sorryFeedback": "Жаль. Может, расскажете подробнее?",
"liveStreaming": "Трансляция",
"streamKey": "Имя/ключ трансляции",
"streamKey": "Ключ трансляции",
"startLiveStreaming": "Начать трансляцию",
"stopStreamingWarning": "Вы уверены, что хотите остановить трансляцию?",
"stopRecordingWarning": "Вы уверены, что хотите остановить запись?",
"startRecording": "Начать запись",
"stopStreamingWarning": "Уверены, что хотите остановить трансляцию?",
"stopRecordingWarning": "Уверены, что хотите остановить запись?",
"stopLiveStreaming": "Остановить трансляцию",
"stopRecording": "Остановить запись",
"doNotShowWarningAgain": "Больше не показывать это предупреждение",
"doNotShowMessageAgain": "Не показывать больше это сообщение",
"permissionDenied": "Доступ запрещён",
"screenSharingPermissionDeniedError": "У Вас нет прав совместно использовать Ваш экран",
"micErrorPresent": "Произошла ошибка при подключении к Вашему микрофону",
"cameraErrorPresent": "Произошла ошибка при подключении к Вашей камере",
"cameraUnsupportedResolutionError": "Ваша камера не поддерживает необходимое разрешение.",
"cameraUnknownError": "Не могу использовать камеру по неизвестной причине.",
"cameraPermissionDeniedError": "У вас нет прав на использование камеры. Вы можете участвовать в конференции, но другие не будут Вас видеть. Используйте значок с камерой в строке адреса, чтобы устранить проблему.",
"cameraNotFoundError": "Камера не была найдена.",
"cameraConstraintFailedError": "",
"micUnknownError": "Не могу пользоваться микрофоном по непонятным причинам.",
"micPermissionDeniedError": "Вы не дали прав на использование микрофона. Вы все-равно можете присоединиться к конференции, но никто не будет Вас слышать. Используйте иконку с камерой в адресной строке браузера, чтобы исправить это.",
"micNotFoundError": "Микрофон не был найден.",
"micConstraintFailedError": "",
"micNotSendingData": "Мы не можем получить доступ к вашему микрофону. Пожалуйста, выберите другое устройство из меню настроек или попробуйте перезапустить приложение.",
"cameraNotSendingData": "Мы не можем получить доступ к вашей камере. Пожалуйста, проверьте, используется ли это устройство другим приложением, выберите другое устройство из меню настроек или же перезапустите приложение.",
"doNotShowMessageAgain": "Больше не показывать это сообщение",
"permissionDenied": "Доступ запрещен",
"screenSharingFailedToInstall": "Ошибка установки расширения для показа экрана.",
"screenSharingFailedToInstallTitle": "Расширение для показа экрана не установлено",
"screenSharingFirefoxPermissionDeniedError": "Что-то пошло не так, когда мы пытались поделиться вашим экраном. Пожалуйста, убедитесь, что вы дали нам разрешение на это.",
"screenSharingFirefoxPermissionDeniedTitle": "Ошибка показа экрана!",
"screenSharingPermissionDeniedError": "Ошибка доступа к вашему расширению для показа экрана. Пожалуйста, перезапустите браузер и попробуйте снова.",
"cameraUnsupportedResolutionError": "Ваша камера не поддерживает необходимое разрешение видео.",
"cameraUnknownError": "Неизвестная ошибка использования камеры.",
"cameraPermissionDeniedError": "Нет доступа к камере. Вы можете участвовать во встрече, но другие не будут вас видеть. Используйте значок камеры в адресной строке браузера, чтобы устранить проблему.",
"cameraNotFoundError": "Камера не обнаружена.",
"cameraConstraintFailedError": "Камера не отвечает определенным требованиям.",
"micUnknownError": "Неизвестная ошибка использования микрофона.",
"micPermissionDeniedError": "Нет доступа к микрофону. Вы можете участвовать во встрече, но другие не будут вас слышать. Используйте значок камеры в адресной строке браузера, чтобы устранить проблему.",
"micNotFoundError": "Микрофон не обнаружен.",
"micConstraintFailedError": "Ваш микрофон не отвечает определенным требованиям.",
"micNotSendingDataTitle": "Нет доступа к микрофону",
"micNotSendingData": "Ошибка доступа к микрофону. Пожалуйста, выберите другое устройство из меню настроек или попробуйте перезапустить приложение.",
"cameraNotSendingDataTitle": "Нет доступа к камере",
"cameraNotSendingData": "Ошибка доступа к камере. Пожалуйста, проверьте, не использует ли камеру какая-нибудь другая программа. Вы можете также выбрать другое устройство из меню настроек или попробовать перезапустить приложение.",
"goToStore": "Перейти к интернет-магазину",
"externalInstallationTitle": "Требуется расширение",
"externalInstallationMsg": "Вам необходимо установить наше дополнение для совместного использования рабочего стола.",
"inlineInstallationMsg": "Вам необходимо установить наше дополнение для совместного использования рабочего стола.",
"inlineInstallExtension": "",
"muteParticipantTitle": "Приглушить этого участника?",
"muteParticipantBody": "Вы не сможете перестать глушить их, но они могут сделать это сами в любое время.",
"inlineInstallExtension": "Установить",
"muteParticipantTitle": "Выключить звук этому участнику?",
"muteParticipantBody": "Вы не можете включить им звук, но они могут сделать это сами в любое время.",
"muteParticipantButton": "Выключить звук",
"remoteControlTitle": "",
"remoteControlRequestMessage": "",
"remoteControlShareScreenWarning": "",
"remoteControlDeniedMessage": "__user__ отклонил ваш запрос на дистанционное управление!",
"remoteControlAllowedMessage": "__user__ принял ваш запрос на дистанционное управление!",
"remoteControlErrorMessage": "Произошла ошибка при попытке запросить разрешения удалённого управления от __user__!",
"startRemoteControlErrorMessage": "",
"remoteControlStopMessage": "Сессия дистанционного управления завершена!",
"close": "",
"shareYourScreen": "",
"yourEntireScreen": "",
"applicationWindow": ""
"remoteControlTitle": "Удаленное управление рабочим столом",
"remoteControlRequestMessage": "Разрешить __user__ удаленное управление вашим рабочим столом?",
"remoteControlShareScreenWarning": "Если нажмете \"Разрешить\", то поделитесь своим экраном!",
"remoteControlDeniedMessage": "__user__ отклонил ваш запрос на удаленное управление!",
"remoteControlAllowedMessage": "__user__ принял ваш запрос на удаленное управление!",
"remoteControlErrorMessage": "Произошла ошибка при попытке запросить разрешения удаленного управления от __user__.",
"startRemoteControlErrorMessage": "Ошибка начала сессии удаленного управления!",
"remoteControlStopMessage": "Сессия удаленного управления завершена!",
"close": "Закрыть",
"shareYourScreen": "Показать экран",
"yourEntireScreen": "Весь экран",
"applicationWindow": "Окно приложения"
},
"\u0005dialog": {},
"email": {
"sharedKey": [
"Эта конференция защищена паролем. Пожалуйста, используйте это пин для входа:",
"Эта встреча защищена паролем. Пожалуйста, используйте для входа:",
"",
"",
"__sharedKey__",
"",
""
],
"subject": "Приглашение для __appName__ (__conferenceName__)",
"subject": "Приглашение в __appName__ (__conferenceName__)",
"body": [
"Привет! я бы хотел пригласить тебя на __appName__ конференцию, которую мы как раз начали.",
"Здравствуйте! Приглашаю вас на текущую встречу __appName__.",
"",
"",
"Пожелуста, следуй по ссылке, чтобы подключиться к конференции.",
"Для подключения, пожалуйста, используйте ссылку:",
"",
"",
"__roomUrl__",
"",
"",
"__sharedKeyText__",
"Имей в виду, что __appName__ сейчас поддерживается только __supportedBrowsers__, так что полюзуйся одним из этих браузеров.",
"Имейте в виду, что __appName__ в настоящее время поддерживается только в __supportedBrowsers__. Вам понадобится один из этих браузеров.",
"",
"",
"Услышимся через секунду!"
"Присоединяйтесь!"
],
"and": "и"
},
"connection": {
"ERROR": "Ошибка",
"CONNECTING": "Идёт подключение",
"CONNECTING": "Подключение",
"RECONNECTING": "Проблема с сетью. Переподключение...",
"CONNFAIL": "Сбой подключения",
"AUTHENTICATING": "Аутентификация",
@@ -397,89 +375,190 @@
"ATTACHED": "Прикреплено"
},
"recording": {
"pending": "Записываем ожидаем подключение участницы...",
"on": "Запись",
"busy": "Мы стараемся обеспечить больше ресурсов для записи. Пожалуйста, попробуйте через несколько минут.",
"busyTitle": "Все записывающие устройства заняты",
"buttonTooltip": "Запись (старт/стоп)",
"error": "Ошибка записи. Пожалуйста, попробуйте позже.",
"failedToStart": "Ошибка начала записи",
"off": "Запись остановлена",
"failedToStart": "Ошибка при начале записи",
"buttonTooltip": "Начать / Остановить запись",
"error": "Ошибка записи. Попробуйте позже.",
"unavailable": "Сервис записи сейчас недоступен. Попробуйте позже."
"on": "Запись",
"pending": "Запись приостановлена: ожидаем подключение участника...",
"serviceName": "Служба записи",
"unavailable": "Служба __serviceName__ сейчас недоступна. Мы работаем над исправлением этой ошибки. Пожалуйста, попробуйте позже.",
"unavailableTitle": "Запись невозможна"
},
"liveStreaming": {
"pending": "Начинаю трансляцию...",
"on": "Трансляция",
"busy": "Освобождаем новые ресурсы для трансляции. Пожалуйста, попробуйте снова через несколько минут.",
"busyTitle": "Все ресурсы для трансляции уже задействованы",
"buttonTooltip": "Трансляция (старт/стоп)",
"changeSignIn": "Переключить аккаунты.",
"choose": "Выбрать трансляцию",
"chooseCTA": "Выберите трансляцию. Вы вошли в систему как __email__. ",
"enterStreamKey": "Введите ваш ключ трансляции YouTube.",
"error": "Ошибка трансляции. Пожалуйста, попробуйте снова.",
"errorAPI": "Произошла ошибка при доступе к вашим трансляциям на YouTube. Повторите попытку входа в систему.",
"failedToStart": "Ошибка трансляции видео",
"off": "Трансляция остановлена",
"unavailable": "Служба трансляций сейчас недоступна. Попробуйте позже.",
"failedToStart": "Трансляция видео не может быть начата",
"buttonTooltip": "Начать / Остановить прямую трансляцию",
"streamIdRequired": "Пожалуйста введите идентификатор трансляции, чтобы запустить её.",
"streamIdHelp": "Где я могу найти это?",
"error": "Не удалось начать трансляцию. Попробуйте снова.",
"busy": "Все рекордеры сейчас заняты. Попробуйте позже."
"on": "Трансляция",
"pending": "Начинаем трансляцию...",
"serviceName": "Служба трансляции",
"signIn": "Войти через Google",
"signInCTA": "Войдите или введите свой ключ трансляции YouTube.",
"start": "Начать трансляцию",
"streamIdHelp": "Что это?",
"unavailableTitle": "Трансляция недоступна"
},
"videoSIPGW": {
"busy": "Мы работаем над высвобождением ресурсов. Пожалуйста, попробуйте через несколько минут.",
"busyTitle": "Служба сейчас занята",
"errorInvite": "Встреча еще не началась. Пожалуйста, попробуйте позже.",
"errorInviteTitle": "Ошибка приглашения в комнату",
"errorAlreadyInvited": "__displayName__ уже приглашен",
"errorInviteFailedTitle": "Ошибка приглашения __displayName__",
"errorInviteFailed": "Мы работаем над решением проблемы. Пожалуйста, попробуйте позже.",
"pending": "__displayName__ был приглашен",
"serviceName": "Служба комнат",
"unavailableTitle": "Служба недоступна"
},
"speakerStats": {
"hours": "",
"minutes": "",
"hours": "__count__ч",
"minutes": "__count__м",
"name": "Имя",
"seconds": "",
"speakerStats": "",
"speakerTime": ""
"seconds": "__count__с",
"speakerStats": "Статистика выступлений",
"speakerTime": "Время выступлений"
},
"deviceSelection": {
"deviceSettings": "",
"noPermission": "",
"previewUnavailable": "",
"selectADevice": "",
"testAudio": ""
},
"invite": {
"addPassword": "",
"callNumber": "",
"enterID": "",
"howToDialIn": "",
"hidePassword": "",
"inviteTo": "",
"invitedYouTo": "",
"locked": "",
"showPassword": "",
"unlocked": ""
"deviceSettings": "Настройки устройства",
"noPermission": "Нет доступа",
"previewUnavailable": "Предпросмотр недоступен",
"selectADevice": "Выбор устройства",
"testAudio": "Тест звука"
},
"videoStatus": {
"callQuality": "",
"changeVideoTip": "",
"hd": "",
"highDefinition": "",
"ld": "",
"lowDefinition": "",
"p2pEnabled": "",
"p2pVideoQualityDescription": "",
"recHighDefinitionOnly": "",
"sd": "",
"standardDefinition": "",
"qualityButtonTip": ""
"callQuality": "Качество связи",
"hd": "HD",
"hdTooltip": "Видео высокого качества",
"highDefinition": "Высокое качество",
"labelTooltipAudioOnly": "Включен режим \"только звук\"",
"labelTooiltipNoVideo": "Нет видео",
"labelTooltipVideo": "Текущее качество видео",
"ld": "LD",
"ldTooltip": "Видео низкого качества",
"lowDefinition": "Низкое качество",
"onlyAudioAvailable": "Только звук",
"onlyAudioSupported": "В этом браузере разрешен только звук.",
"p2pEnabled": "Включен режим \"точка-к-точке\"",
"p2pVideoQualityDescription": "В режиме \"точка-к-точке\" качество входящего сигнала может быть установлено в позицию \"высокое\" или \"только звук\". Остальные варианты в этом режиме не работают.",
"recHighDefinitionOnly": "Предпочтительно высокое качество.",
"sd": "SD",
"sdTooltip": "Видео стандартного качества",
"standardDefinition": "Стандартное качество (SD)",
"qualityButtonTip": "Изменить качество входящего видео"
},
"dialOut": {
"dial": "Дозвон",
"dialOut": "",
"statusMessage": "",
"enterPhone": "",
"phoneNotAllowed": ""
"statusMessage": "сейчас __status__"
},
"addPeople": {
"add": "Добавить",
"noResults": "",
"searchPlaceholder": "",
"title": "",
"failedToAdd": ""
"add": "Пригласить",
"countryNotSupported": "Эта страна пока не поддерживается.",
"countryReminder": "Вызов не в США? Пожалуйста, убедитесь, что указали код страны!",
"disabled": "",
"invite": "Пригласить",
"loading": "Поиск людей и номеров телефонов",
"loadingNumber": "Поиск людей для приглашения",
"loadingPeople": "Поиск людей для приглашения",
"noResults": "Поиск не дал результата",
"noValidNumbers": "Пожалуйста, введите номер телефона",
"notAvailable": "Поиск не дал результата",
"searchNumbers": "Добавить номера телефонов",
"searchPeople": "Поиск не дал результата",
"searchPeopleAndNumbers": "Поиск людей или добавление их телефонов",
"telephone": "Номер: __number__",
"title": "Пригласить людей на эту встречу",
"failedToAdd": "Ошибка добавления участников"
},
"inlineDialogFailure": {
"msg": "",
"retry": "",
"support": "",
"supportMsg": ""
"msg": "Небольшая заминка.",
"retry": "Попробовать снова",
"support": "Поддержка",
"supportMsg": "Если это продолжится, свяжитесь с"
},
"deviceError": {
"cameraPermission": "",
"microphonePermission": ""
"cameraError": "Ошибка доступа к камере",
"microphoneError": "Ошибка доступа к микрофону",
"cameraPermission": "Ошибка доступа к микрофону",
"microphonePermission": "Нет разрешения на доступ к микрофону"
},
"feedback": {
"average": "Средне",
"bad": "Плохо",
"good": "Хорошо",
"detailsLabel": "Расскажите подробнее.",
"rateExperience": "Оценка качества связи",
"veryBad": "Очень плохо",
"veryGood": "Очень хорошо"
},
"info": {
"addPassword": "Добавить пароль",
"cancelPassword": "Убрать пароль",
"conferenceURL": "Ссылка:",
"country": "Страна",
"dialANumber": "Чтобы присоединиться к встрече, наберите один из этих номеров, а затем введите PIN-код: __conferenceID __ #",
"dialInNumber": "Номер:",
"dialInConferenceID": "PIN:",
"dialInNotSupported": "К сожалению, набор номера в настоящее время не поддерживается.",
"genericError": "Что-то пошло не так.",
"inviteLiveStream": "Трансляция этой встречи: __url__",
"invitePhone": "Чтобы присоединиться по телефону, наберите __number__ и введите PIN-код: __conferenceID __#",
"invitePhoneAlternatives": "Посмотреть другие номера телефонов: __url__",
"inviteURL": "Присоединиться к видеоконференции: __url__",
"liveStreamURL": "Трансляция:",
"moreNumbers": "Больше номеров",
"noNumbers": "Нет номеров для набора.",
"noPassword": "нет",
"noRoom": "Для набора номера не было указано ни одной комнаты.",
"numbers": "Номера для набора",
"password": "Пароль:",
"title": "Поделиться",
"tooltip": "Получить информацию об этой встрече"
},
"settingsView": {
"alertOk": "OK",
"alertTitle": "Внимание",
"alertURLText": "Ошибка адреса сервера",
"conferenceSection": "Номера для набора",
"displayName": "Отображаемое имя",
"email": "Email",
"header": "Настройки",
"profileSection": "Профиль",
"serverURL": "Адрес сервера",
"startWithAudioMuted": "Начать с отключенным звуком",
"startWithVideoMuted": "Начать с отключенным видео"
},
"calendarSync": {
"later": "Позже",
"next": "Предстоящие",
"nextMeeting": "следующая встреча",
"now": "Сейчас",
"permissionButton": "Открыть настройки",
"permissionMessage": "Для показа ваших встреч в приложении нужен доступ к календарю."
},
"recentList": {
"today": "Сегодня",
"yesterday": "Вчера",
"earlier": "Ранее"
},
"sectionList": {
"pullToRefresh": "Потяните для обновления"
},
"deepLinking": {
"title": "Запуск вашей встречи в __app __...",
"description": "Ничего не случилось? Мы попытались запустить вашу встречу в настольном приложении __app__. Повторите попытку или запустите ее в веб-приложении __app__.",
"tryAgainButton": "Повторите в настольном приложении",
"launchWebButton": "Запустить в браузере",
"appNotInstalled": "Чтобы присоединиться к этой встрече на телефоне, нужно мобильное приложение __app__.",
"downloadApp": "Скачать приложение",
"openApp": "Перейти к приложению"
}
}

551
lang/main-zhTW.json Normal file
View File

@@ -0,0 +1,551 @@
{
"contactlist_plural": "__count__ 位成員",
"passwordSetRemotely": "由另一位成員設置",
"poweredby": "技術支援",
"inviteUrlDefaultMsg": "您的會議正在建立起來………",
"me": "我",
"speaker": "",
"raisedHand": "請求發言",
"defaultNickname": "例如 阿美 志明",
"defaultLink": "例如 __url__",
"audioDevices": {
"bluetooth": "藍牙",
"headphones": "耳機",
"phone": "電話",
"speaker": "發言者"
},
"audioOnly": {
"audioOnly": "僅用音訊",
"featureToggleDisabled": "在僅用音訊模式下,開關 __feature__ 功能是停用的"
},
"userMedia": {
"react-nativeGrantPermissions": "",
"chromeGrantPermissions": "",
"androidGrantPermissions": "",
"electronGrantPermissions": "",
"firefoxGrantPermissions": "當瀏覽器要求權限允許時,請選擇<b><i>分享設備</i></b> ",
"operaGrantPermissions": "當瀏覽器要求權限允許時,請選擇 <b><i>允許</i></b>",
"iexplorerGrantPermissions": "",
"safariGrantPermissions": "當瀏覽器要求權限允許時,請選擇 <b><i>OK</i></b>",
"nwjsGrantPermissions": "請允許權限使用您的攝影裝置和麥克風",
"edgeGrantPermissions": "當瀏覽器要求權限允許時,請選擇 <b><i>是的</i></b>"
},
"keyboardShortcuts": {
"keyboardShortcuts": "快捷鍵",
"raiseHand": "舉手發言或不作發言",
"pushToTalk": "按鍵通話",
"toggleScreensharing": "在攝影鏡頭和螢幕分享之間進行切換",
"toggleFilmstrip": "顯示或隱藏視訊",
"toggleShortcuts": "顯示或隱藏說明選單",
"focusLocal": "聚焦於自己的視訊",
"focusRemote": "聚焦於另一位通話者的視訊",
"toggleChat": "開啟或關閉聊天",
"mute": "靜音或解除靜音",
"fullScreen": "進入或退出全螢幕",
"videoMute": "啟動或停止自己的攝影裝置",
"showSpeakerStats": "顯示發言者數據"
},
"welcomepage": {
"appDescription": "快來使用吧,團隊全部成員使用視訊通話,可以邀請任何您所認識的人。 __app__ 是一套完全加密、100% 開放源碼的視訊會議解決方案。無需註冊帳號,無時無刻不分日夜均可免費使用。",
"audioVideoSwitch": {
"audio": "語音",
"video": "視訊"
},
"calendar": "日曆",
"go": "開始",
"join": "加入",
"privacy": "隱私",
"roomname": "輸入會議室名稱",
"roomnameHint": "請輸入您想加入的會議室 URL 網址或名稱。您可以用個名稱來建立會議室,只要其他人輸入相同的名稱就能加入會議室喔。",
"sendFeedback": "發送回報",
"terms": "條款",
"title": "更加安全、更具彈性、又完全免費的視訊會議系統"
},
"startupoverlay": {
"policyText": " ",
"title": "__app__ 需要使用您的麥克風和攝影裝置。"
},
"suspendedoverlay": {
"title": "由於電腦進入休眠,您的視訊通話已經中斷。",
"text": "按下 <i>重新加入</i> 按鈕重新連接。",
"rejoinKeyTitle": "重新加入"
},
"toolbar": {
"addPeople": "新增人員到您的通話中",
"audioonly": "啟用/停用 僅用音訊模式(節省頻寬)",
"callQuality": "管理通話品質",
"enterFullScreen": "觀看全螢幕",
"exitFullScreen": "跳出全螢幕",
"feedback": "留言回報",
"moreActions": "更多動作",
"mute": "靜音 / 解除靜音",
"videomute": "啟動/停止 攝影裝置",
"authenticate": "驗證",
"lock": "鎖定/解鎖 會議室",
"chat": "開啟/關閉 聊天",
"etherpad": "開啟/關閉 分享文件檔案",
"documentOpen": "開啟分享的文件檔案",
"documentClose": "關閉分享的文件檔案",
"sharedvideo": "分享 YouTube 視訊",
"sharescreen": "螢幕分享",
"stopSharedVideo": "停止 YouTube 視訊",
"fullscreen": "觀看/跳出 全螢幕",
"sip": "播打 SIP 號碼",
"Settings": "",
"hangup": "留言",
"login": "登入",
"logout": "",
"sharedVideoMutedPopup": "您分享的視訊已經靜音,現在可以和其他成員交談了。",
"micMutedPopup": "您的麥克風已經處於靜音,可以觀看分享視訊了。",
"talkWhileMutedPopup": "您要發言嗎? 目前您處於靜音。",
"unableToUnmutePopup": "當分享視訊正在使用時,您不能解除靜音。",
"cameraDisabled": "攝影裝置無法使用",
"micDisabled": "麥克風無法使用",
"filmstrip": "顯示/隱藏 視訊",
"profile": "編輯您的簡介",
"raiseHand": "舉手/取消 請求發言",
"shortcuts": "查看快捷鍵",
"speakerStats": "發言者數據"
},
"chat": {
"nickname": {
"title": "請在下面欄位輸入暱稱",
"popover": "選擇暱稱"
},
"messagebox": "請輸入文字..."
},
"settings": {
"title": "",
"update": "更新",
"name": "",
"startAudioMuted": "全部人啟動時處於靜音",
"startVideoMuted": "全部人啟動時隱藏視訊畫面",
"selectCamera": "攝影裝置",
"selectMic": "麥克風",
"selectAudioOutput": "音訊輸出",
"followMe": "全部人跟隨仿照我",
"noDevice": "",
"cameraAndMic": "攝影裝置和麥克風",
"moderator": "主持人",
"password": "設定密碼",
"audioVideo": "音訊和視訊"
},
"profile": {
"title": "",
"setDisplayNameLabel": "設定您的顯示名稱",
"setEmailLabel": "設置您的大頭人像電子信箱",
"setEmailInput": "輸入您的電子信箱"
},
"videothumbnail": {
"moderator": "主持人",
"videomute": "成員已經停用攝影裝置",
"mute": "成員處於靜音",
"kick": "踢出",
"muted": "處於靜音",
"domute": "",
"flip": "翻轉",
"remoteControl": "遠端控制"
},
"connectionindicator": {
"header": "連接資料",
"bitrate": "比特率:",
"packetloss": "丟包:",
"resolution": "解析度:",
"framerate": "影格率:",
"less": "顯示較少",
"more": "顯示更多",
"address": "地址:",
"remoteport": "遠端端口:",
"localport": "本地端口:",
"localaddress": "本地地址:",
"remoteaddress": "遠端地址:",
"transport": "傳輸:",
"bandwidth": "估計頻寬:",
"na": "一旦會議啟動,即可回到此處查看連接資訊",
"turn": " (轉)",
"quality": {
"good": "",
"inactive": "未啟用",
"lost": "漏失",
"nonoptimal": "不甚理想",
"poor": "不好"
},
"status": "連接:"
},
"notify": {
"disconnected": "已經中斷連接",
"moderator": "主持人權限已經取得!",
"connectedOneMember": "__name__ 已經連接",
"connectedTwoMembers": "__first__ 與 __second__ 已經連接",
"connectedThreePlusMembers": "__name__ 與 __count__ 位其他成員已經連接",
"somebody": "某人",
"me": "自己",
"focus": "會議焦點",
"focusFail": "__component__ 無法使用 - 請在 __ms__ 秒後重試",
"grantedTo": "主持人權限已授予 __to__!",
"grantedToUnknown": "主持人權限已經授予 $t(somebody) ",
"muted": "您已經啟動通話,並處於靜音狀態。",
"mutedTitle": "您目前處於靜音!",
"raisedHand": "請求發言。",
"suboptimalExperienceTitle": "瀏覽器警告",
"suboptimalExperienceDescription": "呃……恐怕您對 __appName__ 的體驗不是很好,我們正在嘗試找方法改進對此瀏覽器的支援。現下敬請選用 <a href='static/recommendedBrowsers.html' target='_blank'>全力支援的瀏覽器</a> 來進行。"
},
"dialog": {
"allow": "允許",
"kickMessage": "您已經被踢出會議!",
"popupErrorTitle": "彈出視窗遭到阻攔",
"popupError": "您的瀏覽器在此網站上阻攔彈出視窗。請在瀏覽器的安全設置中開啟它並再試一次。",
"passwordErrorTitle": "密碼錯誤",
"passwordError": "此會議目前已受密碼保護。只有會議的擁有者可以設定密碼。",
"passwordError2": "此會議目前未受密碼保護。只有會議的擁有者可以設定密碼。",
"connectError": "喔哦!發生錯誤,無法連接至會議。",
"connectErrorWithMsg": "喔哦!發生錯誤,無法連接至會議: __msg__",
"incorrectPassword": "錯誤的用戶名稱或密碼",
"connecting": "",
"copy": "複製",
"contactSupport": "聯絡支援",
"error": "",
"detectext": "嘗試偵測桌面分享擴充應用程式時發生錯誤。",
"failedpermissions": "未能取得使用本地麥克風或攝影裝置的權限。",
"conferenceReloadTitle": "不好意思,出錯了。",
"conferenceReloadMsg": "我們正試著修復狀況。重新連接於 __seconds__ 秒內……",
"conferenceDisconnectTitle": "您已經被中斷連接。",
"conferenceDisconnectMsg": "請檢查一下網路連接。將在 __seconds__ 秒後重新連接…",
"dismiss": "解除",
"rejoinNow": "立即重新加入",
"maxUsersLimitReachedTitle": "成員人數已經達到上限",
"maxUsersLimitReached": "由於會議已達到人數上限,額滿不能加入。請聯絡會議發起人,或是稍後再次嘗試!",
"lockTitle": "鎖定失敗",
"lockMessage": "鎖定會議失敗。",
"warning": "",
"passwordNotSupportedTitle": "不支援密碼",
"passwordNotSupported": "不支援設置會議密碼。",
"internalErrorTitle": "內部錯誤",
"internalError": "喔哦!出現了點問題。發生錯誤: __error__",
"unableToSwitch": "無法切換視訊串流。",
"SLDFailure": "喔哦!發生錯誤,無法靜音! (SLD故障)",
"SRDFailure": "喔哦!發生錯誤,無法停止視訊! (SRD故障)",
"oops": "喔哦!",
"currentPassword": "目前的密碼是",
"passwordLabel": "密碼",
"defaultError": "存在某種錯誤",
"passwordRequired": "需要密碼",
"Ok": "Ok",
"done": "完成",
"Remove": "移除",
"removePassword": "移除密碼",
"shareVideoTitle": "分享視訊",
"shareVideoLinkError": "請提供正確的 YouTube 連結。",
"removeSharedVideoTitle": "移除分享視訊",
"removeSharedVideoMsg": "您確定要移除自己的分享視訊嗎?",
"alreadySharedVideoMsg": "另一位成員正準備分享視訊。會議一次僅允許一位視訊分享。",
"alreadySharedVideoTitle": "一次只能允許一位視訊分享",
"WaitingForHost": "等侯主辦人………",
"WaitForHostMsg": "會議 <b>__room__ </b> 尚未啟動。如果您是主辦人,請授權開始,否則請等候主辦人。",
"IamHost": "我是主辦人",
"Cancel": "取消",
"Submit": "提交",
"retry": "重試",
"logoutTitle": "登出",
"logoutQuestion": "您確定要登出並停止會議嗎?",
"sessTerminated": "通話已經終止",
"hungUp": "我方掛斷",
"joinAgain": "再次加入",
"Share": "",
"Save": "儲存",
"recording": "",
"recordingToken": "輸入錄製標記",
"Back": "返回",
"serviceUnavailable": "服務無法使用",
"gracefulShutdown": "本伺服器閉關維護中,請稍後再試。",
"Yes": "是的",
"reservationError": "預約系統錯誤",
"reservationErrorMsg": "錯誤碼: __code__, 訊息: __msg__",
"password": "輸入密碼",
"userPassword": "用戶密碼",
"token": "標記",
"tokenAuthFailedTitle": "",
"tokenAuthFailed": "對不起,您未被允許加入此會議。",
"displayNameRequired": "顯示名稱是必須的",
"enterDisplayName": "請輸入您的顯示名稱",
"feedbackHelp": "您的回報將幫助提昇視訊體驗。",
"feedbackQuestion": "請告訴我們本次通話體驗!",
"thankYou": "感謝您使用 __appName__",
"sorryFeedback": "很抱歉聽到這些,能告訴我們更多詳情嗎?",
"liveStreaming": "",
"streamKey": "直播串流密鑰",
"startLiveStreaming": "立即開始直播",
"startRecording": "啟動錄製作業",
"stopStreamingWarning": "確定要停止直播串流嗎?",
"stopRecordingWarning": "確定要停止錄製作業嗎?",
"stopLiveStreaming": "停止直播串流",
"stopRecording": "停止錄製作業",
"doNotShowMessageAgain": "不再顯示此訊息",
"permissionDenied": "權限受到禁止",
"screenSharingFailedToInstall": "喔哦!螢幕分享擴充程式安裝失敗。",
"screenSharingFailedToInstallTitle": "螢幕分享擴充安裝失敗",
"screenSharingFirefoxPermissionDeniedError": "嘗試進行螢幕分享時遇到問題。請確認您有賦予相對的權限允許。",
"screenSharingFirefoxPermissionDeniedTitle": "喔哦!我們無法啟動螢幕分享!",
"screenSharingPermissionDeniedError": "喔哦!您的視訊分享擴充權限發生一點問題。請重新載入再試一次。",
"cameraUnsupportedResolutionError": "您的攝影裝置不支援所需的視訊解析度。",
"cameraUnknownError": "由於不明原因,無法使用攝影裝置。",
"cameraPermissionDeniedError": "您未取得權限使用您的攝影裝置。您仍然可參加會議,但是其他人無法看到。可以利用位址欄中的攝影裝置按鈕來修復啟動。",
"cameraNotFoundError": "未發現攝影裝置。",
"cameraConstraintFailedError": "您的攝影裝置不符合要求。",
"micUnknownError": "不明原因造成麥克風無法使用。",
"micPermissionDeniedError": "您未取得權限使用麥克風。您仍然可參加會議,但是其他人無法聽到。可以利用位址欄中的攝影裝置按鈕來修復啟動。",
"micNotFoundError": "未發現麥克風。",
"micConstraintFailedError": "您的麥克風不符合要求。",
"micNotSendingDataTitle": "無法取用麥克風",
"micNotSendingData": "我們無法取用您的麥克風。請從設罝選單裡選擇其他設備或者重新裝載。",
"cameraNotSendingDataTitle": "無法取用攝影裝置",
"cameraNotSendingData": "我們無法取用您的攝影裝置。請檢查是否有其他程序正在使用這個設備,否則請從設置選單裡選擇其他設備或者重新裝載。",
"goToStore": "前往應用商店",
"externalInstallationTitle": "需要擴充應用程式",
"externalInstallationMsg": "",
"inlineInstallationMsg": "您需要安裝桌面分享擴充應用程式。",
"inlineInstallExtension": "立即安裝",
"muteParticipantTitle": "靜音這位成員?",
"muteParticipantBody": "您無法對他們解除靜音,但是他們自己隨時可以解除靜音。",
"muteParticipantButton": "靜音",
"remoteControlTitle": "遠端桌面控制",
"remoteControlRequestMessage": "您要允許 __user__ 遠端控制您的桌面嗎?",
"remoteControlShareScreenWarning": "注意:如果按下 \"允許\" 您將分享自己的螢幕!",
"remoteControlDeniedMessage": "__user__ 拒絕您進行遠端控制的要求!",
"remoteControlAllowedMessage": "__user__ 接受您進行遠端控制的要求!",
"remoteControlErrorMessage": "在嘗試向 __user__ 請求遠端控制權限時發生錯誤!",
"startRemoteControlErrorMessage": "嘗試啟動遠端控制階段時發生錯誤!",
"remoteControlStopMessage": "遠端控制階段結束!",
"close": "關閉",
"shareYourScreen": "分享自己的螢幕",
"yourEntireScreen": "自己的全螢幕",
"applicationWindow": "應用程式視窗"
},
"email": {
"sharedKey": [
"該會議受密碼保護,請在加入會議時使用下列密碼:",
"",
"",
"__sharedKey__",
"",
""
],
"subject": "邀請發至__appName__ (__conferenceName__)",
"body": [
"嗨, 我想邀請您加入剛剛建立的 __appName__ 會議。",
"",
"",
"請點按下面的連結來加入會議。",
"",
"",
"__roomUrl__",
"",
"",
"__sharedKeyText__",
" 請注意 __appName__ 目前僅支援下列瀏覽器__supportedBrowsers__, 敬請選用其一。",
"",
"",
"即刻加入與您交談喔!"
],
"and": "與"
},
"connection": {
"ERROR": "錯誤",
"CONNECTING": "連接中",
"RECONNECTING": "網絡錯誤發生。重新連接中………",
"CONNFAIL": "連接失敗",
"AUTHENTICATING": "驗證中",
"AUTHFAIL": "驗證失敗",
"CONNECTED": "已經連接",
"DISCONNECTED": "已經中斷連接",
"DISCONNECTING": "中斷連接中",
"ATTACHED": "已經附加"
},
"recording": {
"busy": "我們正在釋放錄製資源。請過幾分鐘後再試。",
"busyTitle": "全部錄製設備正在忙碌",
"buttonTooltip": "啟動/停止 錄製作業",
"error": "錄製作業失敗。請再次重試。",
"failedToStart": "錄製啟動失敗",
"off": "錄製作業已經停止",
"on": "錄製作業中",
"pending": "錄製作業正在等待成員加入……",
"serviceName": "錄製作業服務",
"unavailable": "喔哦__serviceName__ 目前無法使用。我們正在解決此問題,請稍後再試。",
"unavailableTitle": "錄製作業無法使用"
},
"liveStreaming": {
"busy": "我們正在釋放串流資源。請過幾分鐘後再試。",
"busyTitle": "全部串流設備正在忙碌",
"buttonTooltip": "啟動/停止 直播串流",
"changeSignIn": "切換帳號。",
"choose": "選擇直播串流",
"chooseCTA": "請選擇直播串流選項。您目前是以 __email__ 身份登入。",
"enterStreamKey": "在此輸入您的 YouTube 直播串流密鑰。",
"error": "直播串流失敗。請重試。",
"errorAPI": "取用您的 YouTube 播出時發生錯誤。請重新登入。",
"failedToStart": "直播串流啟動失敗",
"off": "直播串流已經停止",
"on": "直播串流中",
"pending": "啟動直播串流………",
"serviceName": "直播串流服務",
"signIn": "使用 Google 帳戶登入",
"signInCTA": "輸入 YouTube 直播串流密鑰,或登入 YouTube 帳號。",
"start": "啟動直播串流",
"streamIdHelp": "這是什麼?",
"unavailableTitle": "直播串流無法使用"
},
"videoSIPGW": {
"busy": "我們正在清理釋放資源。請過幾分鐘後再試。",
"busyTitle": "會議室服務正處於忙碌中",
"errorInvite": "會議尚未開始,請稍後再來。",
"errorInviteTitle": "錯誤邀請會議室",
"errorAlreadyInvited": "__displayName__ 已受邀請",
"errorInviteFailedTitle": "邀請 __displayName__ 失敗",
"errorInviteFailed": "我們正在解決問題。請稍後再試。",
"pending": "__displayName__ 已經邀請",
"serviceName": "會議室服務",
"unavailableTitle": "會議室服務無法使用"
},
"speakerStats": {
"hours": "__count__h",
"minutes": "__count__m",
"name": "名稱",
"seconds": "__count__s",
"speakerStats": "發言者數據",
"speakerTime": "發言者時間"
},
"deviceSelection": {
"deviceSettings": "設備設置",
"noPermission": "未取得權限",
"previewUnavailable": "預覽無法使用",
"selectADevice": "選擇設備",
"testAudio": "測試聲音"
},
"videoStatus": {
"callQuality": "通話品質",
"hd": "HD 高清",
"hdTooltip": "觀看高清視訊 HD",
"highDefinition": "高清品質 HD",
"labelTooltipAudioOnly": "僅有音訊模式已經啟用",
"labelTooiltipNoVideo": "沒有視訊",
"labelTooltipVideo": "目前視訊品質",
"ld": "LD 低清",
"ldTooltip": "觀看低清視訊 LD",
"lowDefinition": "低清品質 LD",
"onlyAudioAvailable": "僅有音訊可以使用",
"onlyAudioSupported": "在此瀏覽器我們僅支援音訊功能。",
"p2pEnabled": "點對點功能已經啟用",
"p2pVideoQualityDescription": "在點對點模式下,通話品質只能使有高清和僅用音訊兩個選項。其他選項只有在點對點模式退出後才可使用。",
"recHighDefinitionOnly": "將會偏好使用高清模式 HD。",
"sd": "SD 標清",
"sdTooltip": "觀看標清視訊 SD",
"standardDefinition": "標清品質 SD",
"qualityButtonTip": "修改接收視訊品質"
},
"dialOut": {
"statusMessage": "現在狀態為 __status__"
},
"addPeople": {
"add": "",
"countryNotSupported": "此目標區域尚未支援。",
"countryReminder": "嘗試在美國外地通話?請確認開頭使用的國家代碼!",
"disabled": "",
"invite": "邀請",
"loading": "尋找聯絡人及電話號碼",
"loadingNumber": "驗證電話號碼",
"loadingPeople": "正在尋搜人員進行邀請",
"noResults": "沒有符合要求的搜尋結果",
"noValidNumbers": "請輸入一組電話號碼",
"notAvailable": "您不可以邀請人員。",
"searchNumbers": "新增電話號碼",
"searchPeople": "尋找人員",
"searchPeopleAndNumbers": "尋找人員或新增電話號碼",
"telephone": "電話: __number__",
"title": "邀請人員參加會議",
"failedToAdd": "無法增加成員"
},
"inlineDialogFailure": {
"msg": "好像有點卡卡不順。",
"retry": "重試",
"support": "支援",
"supportMsg": "如果狀況一直發生,請聯絡"
},
"deviceError": {
"cameraError": "無法取用您的攝影裝置",
"microphoneError": "無法取用您的麥克風",
"cameraPermission": "無法獲得攝影裝置取用權限",
"microphonePermission": "無法獲得麥克風取用權限"
},
"feedback": {
"average": "普通中等",
"bad": "很差",
"good": "很好",
"detailsLabel": "告訴我們本次會議使用上更多結果。",
"rateExperience": "請您評價這次會議的體驗成效",
"veryBad": "極差",
"veryGood": "極好"
},
"info": {
"addPassword": "新增密碼",
"cancelPassword": "取消密碼",
"conferenceURL": "連結:",
"country": "國家",
"dialANumber": "要加入您的會議,請撥打其中之一的電話號碼並輸入 PIN__conferenceID__#",
"dialInNumber": "播入:",
"dialInConferenceID": "PIN 號碼:",
"dialInNotSupported": "抱歉,目前不支援電話播入。",
"genericError": "糟糕!出錯了。",
"inviteLiveStream": "要觀看這場會議的直播串流,點按此連結: __url__",
"invitePhone": "要想使用電話加入會議,請撥打 __number__ 並輸入 PIN __conferenceID__#",
"invitePhoneAlternatives": "查看更多電話號碼,點按此連結: __url__",
"inviteURL": "加入視訊會議,請點按此連結: __url__",
"liveStreamURL": "直播串流:",
"moreNumbers": "更多成員",
"noNumbers": "無播入號碼。",
"noPassword": "無",
"noRoom": "沒有會議室是指定要播打進入。",
"numbers": "播入號碼",
"password": "密碼:",
"title": "分享",
"tooltip": "取得關於會議的連接使用資訊"
},
"settingsView": {
"alertOk": "確認",
"alertTitle": "警告",
"alertURLText": "所輸入的伺服器 URL 是無效的",
"conferenceSection": "會議",
"displayName": "顯示名稱",
"email": "電子郵件",
"header": "設置",
"profileSection": "簡介",
"serverURL": "伺服器 URL",
"startWithAudioMuted": "啟動並音訊靜音",
"startWithVideoMuted": "啟動並視訊靜音"
},
"calendarSync": {
"later": "稍後",
"next": "即將推出",
"nextMeeting": "下次會議",
"now": "現在",
"permissionButton": "開啟設定",
"permissionMessage": "日曆允許權限是必須的,以列入您的會議於應用程式中。"
},
"recentList": {
"today": "今日",
"yesterday": "昨天",
"earlier": "稍早"
},
"sectionList": {
"pullToRefresh": "下滑以重新整理"
},
"deepLinking": {
"title": "發起您的會議於 __app__...",
"description": "沒有發生作用嗎?我們嘗試發起您的會議於 __app__ desktop 桌面應用程式。請再試一次,或是發起會議於 __app__ 網路應用程式。",
"tryAgainButton": "在桌面上再試一次",
"launchWebButton": "在網路上發起",
"appNotInstalled": "在您的手機上需要 __app__ 行動應用程式去加入這場會議。",
"downloadApp": "下載應用 APP",
"openApp": "繼續前往此應用程式"
}
}

View File

@@ -57,15 +57,20 @@
"video": "Video"
},
"calendar": "Calendar",
"connectCalendarText": "Connect your calendar to view all your meetings in __app__. Plus, add __app__ meetings to your calendar and start them with one click.",
"connectCalendarButton": "Connect your calendar",
"enterRoomTitle": "Start a new meeting",
"go": "GO",
"join": "JOIN",
"privacy": "Privacy",
"recentList": "History",
"recentList": "Recent",
"recentListDelete": "Delete",
"recentListEmpty": "Your recent list is currently empty. Chat with your team and you will find all your recent meetings here.",
"roomname": "Enter room name",
"roomnameHint": "Enter the name or URL of the room you want to join. You may make a name up, just let the people you are meeting know it so that they enter the same name.",
"sendFeedback": "Send feedback",
"terms": "Terms",
"title": "More secure, more flexible, and completely free video conferencing."
"title": "Secure, fully featured, and completely free video conferencing"
},
"startupoverlay": {
"policyText": " ",
@@ -155,6 +160,7 @@
"title": "Enter a nickname in the box below",
"popover": "Choose a nickname"
},
"error": "Error: your message \"__originalText__\" was not sent. Reason: __error__",
"messagebox": "Enter text..."
},
"settings": {
@@ -204,7 +210,9 @@
"connectionindicator":
{
"header": "Connection data",
"connectedTo": "Connected to:",
"bitrate": "Bitrate:",
"bridgeCount": "Server count: ",
"packetloss": "Packet loss:",
"resolution": "Resolution:",
"framerate": "Frame rate:",
@@ -259,6 +267,7 @@
"allow": "Allow",
"confirm": "Confirm",
"kickMessage": "Ouch! You have been kicked out of the meet!",
"kickTitle": "Kicked from meeting",
"popupErrorTitle": "Pop-up blocked",
"popupError": "Your browser is blocking pop-up windows from this site. Please enable pop-ups in your browser's security settings and try again.",
"passwordErrorTitle": "Password Error",
@@ -424,6 +433,11 @@
],
"and": "and"
},
"share":
{
"mainText": "Click the following link to join the meeting:\n__roomUrl__",
"dialInfoText": "\n\n=====\n\nJust want to dial in on your phone?\n\n__defaultDialInNumber__Click this link to see the dial in phone numbers for this meeting\n__dialInfoPageUrl__"
},
"connection":
{
"ERROR": "Error",
@@ -450,14 +464,16 @@
"busyTitle": "All recorders are currently busy",
"buttonTooltip": "Start / Stop recording",
"error": "Recording failed. Please try again.",
"expandedOff": "Recording has stopped",
"expandedOn": "The meeting is currently being recorded.",
"expandedPending": "Recording is being started...",
"failedToStart": "Recording failed to start",
"live": "LIVE",
"off": "Recording stopped",
"on": "Recording",
"pending": "Preparing to record the meeting...",
"rec": "REC",
"authDropboxText": "Upload your recording to Dropbox.",
"authDropboxCompletedText": "Your recording file will appear in your Dropbox shortly after the recording has finished.",
"authDropboxText": "Upload to Dropbox",
"serviceName": "Recording service",
"signOut": "Sign Out",
"signIn": "sign in",
@@ -472,10 +488,13 @@
"pending" : "Preparing to transcribe the meeting...",
"off" : "Transcribing stopped",
"error": "Transcribing failed. Please try again.",
"expandedLabel": "Transcribing is currently on",
"failedToStart": "Transcribing failed to start",
"tr": "TR",
"labelToolTip": "The meeting is being transcribed",
"ccButtonTooltip": "Start / Stop showing subtitles"
"ccButtonTooltip": "Start / Stop showing subtitles",
"start": "Start showing subtitles",
"stop": "Stop showing subtitles"
},
"liveStreaming":
{
@@ -489,12 +508,17 @@
"error": "Live Streaming failed. Please try again.",
"errorAPI": "An error occurred while accessing your YouTube broadcasts. Please try logging in again.",
"errorLiveStreamNotEnabled": "Live Streaming is not enabled on __email__. Please enable live streaming or log into an account with live streaming enabled.",
"expandedOff": "The live streaming has stopped",
"expandedOn": "The meeting is currently being streamed to YouTube.",
"expandedPending": "The live streaming is being started...",
"failedToStart": "Live Streaming failed to start",
"off": "Live Streaming stopped",
"on": "Live Streaming",
"pending": "Starting Live Stream...",
"serviceName": "Live Streaming service",
"signedInAs": "You are currently signed in as:",
"signIn": "Sign in with Google",
"signOut": "Sign out",
"signInCTA": "Sign in or enter your live stream key from YouTube.",
"start": "Start a live stream",
"streamIdHelp": "What's this?",
@@ -531,6 +555,7 @@
},
"videoStatus": {
"audioOnly": "AUD",
"audioOnlyExpanded": "You are in audio only mode. This mode saves bandwidth but you won't see videos of others.",
"callQuality": "Call Quality",
"hd": "HD",
"hdTooltip": "Viewing high definition video",
@@ -634,16 +659,21 @@
"startWithVideoMuted": "Start with video muted"
},
"calendarSync": {
"later": "Later",
"next": "Upcoming",
"addMeetingURL": "Add a meeting link",
"confirmAddLink": "Do you want to add a Jitsi link to this event?",
"confirmAddLinkTitle": "Calendar",
"join": "Join",
"joinTooltip": "Join the meeting",
"nextMeeting": "next meeting",
"now": "Now",
"noEvents": "There are no upcoming events scheduled.",
"ongoingMeeting": "ongoing meeting",
"permissionButton": "Open settings",
"permissionMessage": "The Calendar permission is required to see your meetings in the app."
"permissionMessage": "The Calendar permission is required to see your meetings in the app.",
"refresh": "Refresh calendar",
"today": "Today"
},
"recentList": {
"joinPastMeeting": "Join A Past Meeting"
"joinPastMeeting": "Join a past meeting"
},
"sectionList": {
"pullToRefresh": "Pull to refresh"

View File

@@ -112,7 +112,7 @@ function initCommands() {
const { name } = request;
switch (name) {
case 'invite': // eslint-disable-line no-case-declarations
case 'invite': {
const { invitees } = request;
if (!Array.isArray(invitees) || invitees.length === 0) {
@@ -143,6 +143,7 @@ function initCommands() {
});
});
break;
}
case 'is-audio-muted':
callback(APP.conference.isLocalAudioMuted());
break;

View File

@@ -114,10 +114,10 @@ function parseArguments(args) {
switch (typeof firstArg) {
case 'string': // old arguments format
case undefined: // eslint-disable-line no-case-declarations
// not sure which format but we are trying to parse the old
// format because if the new format is used everything will be undefined
// anyway.
case undefined: {
// Not sure which format but we are trying to parse the old
// format because if the new format is used everything will be undefined
// anyway.
const [
roomName,
width,
@@ -141,6 +141,7 @@ function parseArguments(args) {
jwt,
onload
};
}
case 'object': // new arguments format
return args[0];
default:
@@ -461,55 +462,57 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
* the event and value - the listener.
* Currently we support the following
* events:
* incomingMessage - receives event notifications about incoming
* {@code incomingMessage} - receives event notifications about incoming
* messages. The listener will receive object with the following structure:
* {{
* 'from': from,//JID of the user that sent the message
* 'nick': nick,//the nickname of the user that sent the message
* 'message': txt//the text of the message
* }}
* outgoingMessage - receives event notifications about outgoing
* {@code outgoingMessage} - receives event notifications about outgoing
* messages. The listener will receive object with the following structure:
* {{
* 'message': txt//the text of the message
* }}
* displayNameChanged - receives event notifications about display name
* change. The listener will receive object with the following structure:
* {@code displayNameChanged} - receives event notifications about display
* name change. The listener will receive object with the following
* structure:
* {{
* jid: jid,//the JID of the participant that changed his display name
* displayname: displayName //the new display name
* }}
* participantJoined - receives event notifications about new participant.
* {@code participantJoined} - receives event notifications about new
* participant.
* The listener will receive object with the following structure:
* {{
* jid: jid //the jid of the participant
* }}
* participantLeft - receives event notifications about the participant that
* left the room.
* {@code participantLeft} - receives event notifications about the
* participant that left the room.
* The listener will receive object with the following structure:
* {{
* jid: jid //the jid of the participant
* }}
* video-conference-joined - receives event notifications about the local
* user has successfully joined the video conference.
* {@code video-conference-joined} - receives event notifications about the
* local user has successfully joined the video conference.
* The listener will receive object with the following structure:
* {{
* roomName: room //the room name of the conference
* }}
* video-conference-left - receives event notifications about the local user
* has left the video conference.
* {@code video-conference-left} - receives event notifications about the
* local user has left the video conference.
* The listener will receive object with the following structure:
* {{
* roomName: room //the room name of the conference
* }}
* screenSharingStatusChanged - receives event notifications about
* {@code screenSharingStatusChanged} - receives event notifications about
* turning on/off the local user screen sharing.
* The listener will receive object with the following structure:
* {{
* on: on //whether screen sharing is on
* }}
* readyToClose - all hangup operations are completed and Jitsi Meet is
* ready to be disposed.
* {@code readyToClose} - all hangup operations are completed and Jitsi Meet
* is ready to be disposed.
* @returns {void}
*
* @deprecated
@@ -536,11 +539,12 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
/**
* Executes command. The available commands are:
* displayName - sets the display name of the local participant to the value
* passed in the arguments array.
* toggleAudio - mutes / unmutes audio with no arguments.
* toggleVideo - mutes / unmutes video with no arguments.
* toggleFilmStrip - hides / shows the filmstrip with no arguments.
* {@code displayName} - Sets the display name of the local participant to
* the value passed in the arguments array.
* {@code toggleAudio} - Mutes / unmutes audio with no arguments.
* {@code toggleVideo} - Mutes / unmutes video with no arguments.
* {@code toggleFilmStrip} - Hides / shows the filmstrip with no arguments.
*
* If the command doesn't require any arguments the parameter should be set
* to empty array or it may be omitted.
*
@@ -561,13 +565,13 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
/**
* Executes commands. The available commands are:
* displayName - sets the display name of the local participant to the value
* passed in the arguments array.
* toggleAudio - mutes / unmutes audio. no arguments
* toggleVideo - mutes / unmutes video. no arguments
* toggleFilmStrip - hides / shows the filmstrip. no arguments
* toggleChat - hides / shows chat. no arguments.
* toggleShareScreen - starts / stops screen sharing. no arguments.
* {@code displayName} - Sets the display name of the local participant to
* the value passed in the arguments array.
* {@code toggleAudio} - Mutes / unmutes audio. No arguments.
* {@code toggleVideo} - Mutes / unmutes video. No arguments.
* {@code toggleFilmStrip} - Hides / shows the filmstrip. No arguments.
* {@code toggleChat} - Hides / shows chat. No arguments.
* {@code toggleShareScreen} - Starts / stops screen sharing. No arguments.
*
* @param {Object} commandList - The object with commands to be executed.
* The keys of the object are the commands that will be executed and the

View File

@@ -4,9 +4,6 @@ const logger = require('jitsi-meet-logger').getLogger(__filename);
const UI = {};
import Chat from './side_pannels/chat/Chat';
import SidePanels from './side_pannels/SidePanels';
import SideContainerToggler from './side_pannels/SideContainerToggler';
import messageHandler from './util/MessageHandler';
import UIUtil from './util/UIUtil';
import UIEvents from '../../service/UI/UIEvents';
@@ -22,8 +19,10 @@ import {
showParticipantJoinedNotification
} from '../../react/features/base/participants';
import { destroyLocalTracks } from '../../react/features/base/tracks';
import { toggleChat } from '../../react/features/chat';
import { openDisplayNamePrompt } from '../../react/features/display-name';
import { setEtherpadHasInitialzied } from '../../react/features/etherpad';
import { setFilmstripVisible } from '../../react/features/filmstrip';
import {
setNotificationsEnabled,
showWarningNotification
@@ -88,12 +87,9 @@ const UIListeners = new Map([
], [
UIEvents.SHARED_VIDEO_CLICKED,
() => sharedVideoManager && sharedVideoManager.toggleSharedVideo()
], [
UIEvents.TOGGLE_CHAT,
() => UI.toggleChat()
], [
UIEvents.TOGGLE_FILMSTRIP,
() => UI.handleToggleFilmstrip()
() => UI.toggleFilmstrip()
], [
UIEvents.FOLLOW_ME_ENABLED,
enabled => followMeHandler && followMeHandler.enableFollowMe(enabled)
@@ -157,7 +153,7 @@ UI.notifyKicked = function() {
messageHandler.showError({
hideErrorSupportLink: true,
descriptionKey: 'dialog.kickMessage',
titleKey: 'dialog.sessTerminated'
titleKey: 'dialog.kickTitle'
});
};
@@ -174,17 +170,6 @@ UI.notifyConferenceDestroyed = function(reason) {
});
};
/**
* Show chat error.
* @param err the Error
* @param msg
*/
UI.showChatError = function(err, msg) {
if (!interfaceConfig.filmStripOnly) {
Chat.chatAddError(err, msg);
}
};
/**
* Change nickname for the user.
* @param {string} id user id
@@ -192,10 +177,6 @@ UI.showChatError = function(err, msg) {
*/
UI.changeDisplayName = function(id, displayName) {
VideoLayout.onDisplayNameChanged(id, displayName);
if (APP.conference.isLocalId(id) || id === 'localVideoContainer') {
Chat.setChatConversationMode(Boolean(displayName));
}
};
/**
@@ -255,11 +236,6 @@ UI.initConference = function() {
followMeHandler = new FollowMe(APP.conference, UI);
};
/** *
* Handler for toggling filmstrip
*/
UI.handleToggleFilmstrip = () => UI.toggleFilmstrip();
/**
* Returns the shared document manager object.
* @return {EtherpadManager} the shared document manager object
@@ -280,7 +256,6 @@ UI.start = function() {
// Set the defaults for prompt dialogs.
$.prompt.setDefaults({ persistent: false });
SideContainerToggler.init(eventEmitter);
Filmstrip.init(eventEmitter);
VideoLayout.init(eventEmitter);
@@ -298,26 +273,16 @@ UI.start = function() {
if (interfaceConfig.filmStripOnly) {
$('body').addClass('filmstrip-only');
Filmstrip.setFilmstripOnly();
APP.store.dispatch(setNotificationsEnabled(false));
} else {
// Initialize recording mode UI.
if (config.iAmRecorder) {
VideoLayout.enableDeviceAvailabilityIcons(
APP.conference.getMyUserId(), false);
// in case of iAmSipGateway keep local video visible
if (!config.iAmSipGateway) {
VideoLayout.setLocalVideoVisible(false);
}
APP.store.dispatch(setToolboxEnabled(false));
APP.store.dispatch(setNotificationsEnabled(false));
UI.messageHandler.enablePopups(false);
} else if (config.iAmRecorder) {
// in case of iAmSipGateway keep local video visible
if (!config.iAmSipGateway) {
VideoLayout.setLocalVideoVisible(false);
}
// Initialize side panels
SidePanels.init(eventEmitter);
APP.store.dispatch(setToolboxEnabled(false));
APP.store.dispatch(setNotificationsEnabled(false));
UI.messageHandler.enablePopups(false);
}
document.title = interfaceConfig.APP_NAME;
@@ -343,7 +308,6 @@ UI.bindEvents = () => {
*
*/
function onResize() {
SideContainerToggler.resize();
VideoLayout.resizeVideoArea();
}
@@ -503,18 +467,13 @@ UI.updateUserStatus = (user, status) => {
{ status: UIUtil.escapeHtml(status) });
};
/**
* Toggles smileys in the chat.
*/
UI.toggleSmileys = () => Chat.toggleSmileys();
/**
* Toggles filmstrip.
*/
UI.toggleFilmstrip = function() {
// eslint-disable-next-line prefer-rest-params
Filmstrip.toggleFilmstrip(...arguments);
VideoLayout.resizeVideoArea(true, false);
const { visible } = APP.store.getState()['features/filmstrip'];
APP.store.dispatch(setFilmstripVisible(!visible));
};
/**
@@ -524,22 +483,9 @@ UI.toggleFilmstrip = function() {
UI.isFilmstripVisible = () => Filmstrip.isFilmstripVisible();
/**
* @returns {true} if the chat panel is currently visible, and false otherwise.
* Toggles the visibility of the chat panel.
*/
UI.isChatVisible = () => Chat.isVisible();
/**
* Toggles chat panel.
*/
UI.toggleChat = () => UI.toggleSidePanel('chat_container');
/**
* Toggles the given side panel.
*
* @param {String} sidePanelId the identifier of the side panel to toggle
*/
UI.toggleSidePanel = sidePanelId => SideContainerToggler.toggle(sidePanelId);
UI.toggleChat = () => APP.store.dispatch(toggleChat());
/**
* Handle new user display name.
@@ -749,17 +695,6 @@ UI.hideStats = function() {
VideoLayout.hideStats();
};
/**
* Add chat message.
* @param {string} from user id
* @param {string} displayName user nickname
* @param {string} message message text
* @param {number} stamp timestamp when message was created
*/
// eslint-disable-next-line max-params
UI.addMessage = function(from, displayName, message, stamp) {
Chat.updateChatConversation(from, displayName, message, stamp);
};
UI.notifyTokenAuthFailed = function() {
messageHandler.showError({

View File

@@ -87,6 +87,7 @@ class Etherpad extends LargeContainer {
this.container.appendChild(iframe);
iframe.onload = function() {
// eslint-disable-next-line no-self-assign
document.domain = document.domain;
bubbleIframeMouseMove(iframe);

View File

@@ -1,5 +1,4 @@
/* global $, APP */
import { shouldDisplayTileView } from '../../../react/features/video-layout';
/* global $ */
import SmallVideo from '../videolayout/SmallVideo';
@@ -66,9 +65,7 @@ SharedVideoThumb.prototype.createContainer = function(spanId) {
* The thumb click handler.
*/
SharedVideoThumb.prototype.videoClick = function() {
if (!shouldDisplayTileView(APP.store.getState())) {
this._togglePin();
}
this._togglePin();
};
/**

View File

@@ -1,168 +0,0 @@
/* global $, APP */
import UIEvents from '../../../service/UI/UIEvents';
import { setVisiblePanel } from '../../../react/features/side-panel';
/**
* Handles open and close of the extended toolbar side panel
* (chat, settings, etc.).
*
* @type {{init, toggle, isVisible, hide, show, resize}}
*/
const SideContainerToggler = {
/**
* Initialises this toggler by registering the listeners.
*
* @param eventEmitter
*/
init(eventEmitter) {
this.eventEmitter = eventEmitter;
// We may not have a side toolbar container, for example, in
// filmstrip-only mode.
const sideToolbarContainer
= document.getElementById('sideToolbarContainer');
if (!sideToolbarContainer) {
return;
}
// Adds a listener for the animationend event that would take care of
// hiding all internal containers when the extendedToolbarPanel is
// closed.
sideToolbarContainer.addEventListener(
'animationend',
e => {
if (e.animationName === 'slideOutExt') {
$('#sideToolbarContainer').children()
.each(function() {
/* eslint-disable no-invalid-this */
if ($(this).hasClass('show')) {
SideContainerToggler.hideInnerContainer($(this));
}
/* eslint-enable no-invalid-this */
});
}
},
false);
},
/**
* Toggles the container with the given element id.
*
* @param {String} elementId the identifier of the container element to
* toggle
*/
toggle(elementId) {
const elementSelector = $(`#${elementId}`);
const isSelectorVisible = elementSelector.hasClass('show');
if (isSelectorVisible) {
this.hide();
APP.store.dispatch(setVisiblePanel(null));
} else {
if (this.isVisible()) {
$('#sideToolbarContainer').children()
.each(function() {
/* eslint-disable no-invalid-this */
if ($(this).id !== elementId && $(this).hasClass('show')) {
SideContainerToggler.hideInnerContainer($(this));
}
/* eslint-enable no-invalid-this */
});
}
if (!this.isVisible()) {
this.show();
}
this.showInnerContainer(elementSelector);
APP.store.dispatch(setVisiblePanel(elementId));
}
},
/**
* Returns {true} if the side toolbar panel is currently visible,
* otherwise returns {false}.
*/
isVisible() {
return $('#sideToolbarContainer').hasClass('slideInExt');
},
/**
* Returns {true} if the side toolbar panel is currently hovered and
* {false} otherwise.
*/
isHovered() {
return $('#sideToolbarContainer:hover').length > 0;
},
/**
* Hides the side toolbar panel with a slide out animation.
*/
hide() {
$('#sideToolbarContainer')
.removeClass('slideInExt')
.addClass('slideOutExt');
},
/**
* Shows the side toolbar panel with a slide in animation.
*/
show() {
if (!this.isVisible()) {
$('#sideToolbarContainer')
.removeClass('slideOutExt')
.addClass('slideInExt');
}
},
/**
* Hides the inner container given by the selector.
*
* @param {Object} containerSelector the jquery selector for the
* element to hide
*/
hideInnerContainer(containerSelector) {
containerSelector.removeClass('show').addClass('hide');
this.eventEmitter.emit(UIEvents.SIDE_TOOLBAR_CONTAINER_TOGGLED,
containerSelector.attr('id'), false);
},
/**
* Shows the inner container given by the selector.
*
* @param {Object} containerSelector the jquery selector for the
* element to show
*/
showInnerContainer(containerSelector) {
// Before showing the container, make sure there is no other visible.
// If we quickly show a container, while another one is animating
// and animation never ends, so we do not really hide the first one and
// we end up with to shown panels
$('#sideToolbarContainer').children()
.each(function() {
/* eslint-disable no-invalid-this */
if ($(this).hasClass('show')) {
SideContainerToggler.hideInnerContainer($(this));
}
/* eslint-enable no-invalid-this */
});
containerSelector.removeClass('hide').addClass('show');
this.eventEmitter.emit(UIEvents.SIDE_TOOLBAR_CONTAINER_TOGGLED,
containerSelector.attr('id'), true);
},
/**
* TO FIX: do we need to resize the chat?
*/
resize() {
// let [width, height] = UIUtil.getSidePanelSize();
// Chat.resizeChat(width, height);
}
};
export default SideContainerToggler;

View File

@@ -1,13 +0,0 @@
import Chat from './chat/Chat';
import { isButtonEnabled } from '../../../react/features/toolbox';
const SidePanels = {
init(eventEmitter) {
// Initialize chat
if (isButtonEnabled('chat')) {
Chat.init(eventEmitter);
}
}
};
export default SidePanels;

View File

@@ -1,404 +0,0 @@
/* global APP, $ */
import { processReplacements } from './Replacement';
import VideoLayout from '../../videolayout/VideoLayout';
import UIUtil from '../../util/UIUtil';
import UIEvents from '../../../../service/UI/UIEvents';
import { smileys } from './smileys';
import { addMessage, markAllRead } from '../../../../react/features/chat';
import {
dockToolbox,
getToolboxHeight
} from '../../../../react/features/toolbox';
let unreadMessages = 0;
const sidePanelsContainerId = 'sideToolbarContainer';
const htmlStr = `
<div id="chat_container" class="sideToolbarContainer__inner">
<div id="nickname">
<span data-i18n="chat.nickname.title"></span>
<form>
<input type='text'
class="input-control" id="nickinput" autofocus
data-i18n="[placeholder]chat.nickname.popover">
</form>
</div>
<div id="chatconversation"></div>
<textarea id="usermsg" autofocus
data-i18n="[placeholder]chat.messagebox"></textarea>
<div id="smileysarea">
<div id="smileys">
<img src="images/smile.svg"/>
</div>
</div>
</div>`;
/**
*
*/
function initHTML() {
$(`#${sidePanelsContainerId}`)
.append(htmlStr);
// make sure we translate the panel, as adding it can be after i18n
// library had initialized and translated already present html
APP.translation.translateElement($(`#${sidePanelsContainerId}`));
}
/**
* The container id, which is and the element id.
*/
const CHAT_CONTAINER_ID = 'chat_container';
/**
* Updates visual notification, indicating that a message has arrived.
*/
function updateVisualNotification() {
// XXX The rewrite of the toolbar in React delayed the availability of the
// element unreadMessages. In order to work around the delay, I introduced
// and utilized unreadMsgSelector in addition to unreadMsgElement.
const unreadMsgSelector = $('#unreadMessages');
const unreadMsgElement
= unreadMsgSelector.length > 0 ? unreadMsgSelector[0] : undefined;
if (unreadMessages && unreadMsgElement) {
unreadMsgElement.innerHTML = unreadMessages.toString();
APP.store.dispatch(dockToolbox(true));
const chatButtonElement
= document.getElementById('toolbar_button_chat');
const leftIndent
= (UIUtil.getTextWidth(chatButtonElement)
- UIUtil.getTextWidth(unreadMsgElement)) / 2;
const topIndent
= ((UIUtil.getTextHeight(chatButtonElement)
- UIUtil.getTextHeight(unreadMsgElement)) / 2) - 5;
unreadMsgElement.setAttribute(
'style',
`top:${topIndent}; left:${leftIndent};`);
} else {
unreadMsgSelector.html('');
}
if (unreadMsgElement) {
unreadMsgSelector.parent()[unreadMessages > 0 ? 'show' : 'hide']();
}
}
/**
* Returns the current time in the format it is shown to the user
* @returns {string}
*/
function getCurrentTime(stamp) {
const now = stamp ? new Date(stamp) : new Date();
let hour = now.getHours();
let minute = now.getMinutes();
let second = now.getSeconds();
if (hour.toString().length === 1) {
hour = `0${hour}`;
}
if (minute.toString().length === 1) {
minute = `0${minute}`;
}
if (second.toString().length === 1) {
second = `0${second}`;
}
return `${hour}:${minute}:${second}`;
}
/**
*
*/
function toggleSmileys() {
const smileys = $('#smileysContainer'); // eslint-disable-line no-shadow
smileys.slideToggle();
$('#usermsg').focus();
}
/**
*
*/
function addClickFunction(smiley, number) {
smiley.onclick = function addSmileyToMessage() {
const usermsg = $('#usermsg');
let message = usermsg.val();
message += smileys[`smiley${number}`];
usermsg.val(message);
usermsg.get(0).setSelectionRange(message.length, message.length);
toggleSmileys();
usermsg.focus();
};
}
/**
* Adds the smileys container to the chat
*/
function addSmileys() {
const smileysContainer = document.createElement('div');
smileysContainer.id = 'smileysContainer';
for (let i = 1; i <= 21; i++) {
const smileyContainer = document.createElement('div');
smileyContainer.id = `smiley${i}`;
smileyContainer.className = 'smileyContainer';
const smiley = document.createElement('img');
smiley.src = `images/smileys/smiley${i}.svg`;
smiley.className = 'smiley';
addClickFunction(smiley, i);
smileyContainer.appendChild(smiley);
smileysContainer.appendChild(smileyContainer);
}
$('#chat_container').append(smileysContainer);
}
/**
* Resizes the chat conversation.
*/
function resizeChatConversation() {
// FIXME: this function can all be done with CSS. If Chat is ever rewritten,
// do not copy over this logic.
const msgareaHeight = $('#usermsg').outerHeight();
const chatspace = $(`#${CHAT_CONTAINER_ID}`);
const width = chatspace.width();
const chat = $('#chatconversation');
const smileys = $('#smileysarea'); // eslint-disable-line no-shadow
smileys.height(msgareaHeight);
$('#smileys').css('bottom', (msgareaHeight - 26) / 2);
$('#smileysContainer').css('bottom', msgareaHeight);
chat.width(width - 10);
const maybeAMagicNumberForPaddingAndMargin = 100;
const offset = maybeAMagicNumberForPaddingAndMargin
+ msgareaHeight + getToolboxHeight();
chat.height(window.innerHeight - offset);
}
/**
* Focus input after 400 ms
* Found input by id
*
* @param id {string} input id
*/
function deferredFocus(id) {
setTimeout(() => $(`#${id}`).focus(), 400);
}
/**
* Chat related user interface.
*/
const Chat = {
/**
* Initializes chat related interface.
*/
init(eventEmitter) {
initHTML();
if (APP.conference.getLocalDisplayName()) {
Chat.setChatConversationMode(true);
}
$('#smileys').click(() => {
Chat.toggleSmileys();
});
$('#nickinput').keydown(function(event) {
if (event.keyCode === 13) {
event.preventDefault();
const val = this.value; // eslint-disable-line no-invalid-this
this.value = '';// eslint-disable-line no-invalid-this
eventEmitter.emit(UIEvents.NICKNAME_CHANGED, val);
deferredFocus('usermsg');
}
});
const usermsg = $('#usermsg');
usermsg.keydown(function(event) {
if (event.keyCode === 13) {
event.preventDefault();
const value = this.value; // eslint-disable-line no-invalid-this
usermsg.val('').trigger('autosize.resize');
this.focus();// eslint-disable-line no-invalid-this
const message = UIUtil.escapeHtml(value);
eventEmitter.emit(UIEvents.MESSAGE_CREATED, message);
}
});
const onTextAreaResize = function() {
resizeChatConversation();
Chat.scrollChatToBottom();
};
usermsg.autosize({ callback: onTextAreaResize });
eventEmitter.on(UIEvents.SIDE_TOOLBAR_CONTAINER_TOGGLED,
(containerId, isVisible) => {
if (containerId !== CHAT_CONTAINER_ID || !isVisible) {
return;
}
unreadMessages = 0;
APP.store.dispatch(markAllRead());
updateVisualNotification();
// Undock the toolbar when the chat is shown and if we're in a
// video mode.
if (VideoLayout.isLargeVideoVisible()) {
APP.store.dispatch(dockToolbox(false));
}
// if we are in conversation mode focus on the text input
// if we are not, focus on the display name input
deferredFocus(
APP.conference.getLocalDisplayName()
? 'usermsg'
: 'nickinput');
});
addSmileys();
updateVisualNotification();
},
/**
* Appends the given message to the chat conversation.
*/
// eslint-disable-next-line max-params
updateChatConversation(id, displayName, message, stamp) {
const isFromLocalParticipant = APP.conference.isLocalId(id);
let divClassName = '';
if (isFromLocalParticipant) {
divClassName = 'localuser';
} else {
divClassName = 'remoteuser';
if (!Chat.isVisible()) {
unreadMessages++;
updateVisualNotification();
}
}
// replace links and smileys
// Strophe already escapes special symbols on sending,
// so we escape here only tags to avoid double &amp;
const escMessage = message.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/\n/g, '<br/>');
const escDisplayName = UIUtil.escapeHtml(displayName);
const timestamp = getCurrentTime(stamp);
// eslint-disable-next-line no-param-reassign
message = processReplacements(escMessage);
const messageContainer
= `${'<div class="chatmessage">'
+ '<img src="images/chatArrow.svg" class="chatArrow">'
+ '<div class="username '}${divClassName}">${escDisplayName
}</div><div class="timestamp">${timestamp
}</div><div class="usermessage">${message}</div>`
+ '</div>';
$('#chatconversation').append(messageContainer);
$('#chatconversation').animate(
{ scrollTop: $('#chatconversation')[0].scrollHeight }, 1000);
const markAsRead = Chat.isVisible() || isFromLocalParticipant;
APP.store.dispatch(addMessage(
escDisplayName, message, timestamp, markAsRead));
},
/**
* Appends error message to the conversation
* @param errorMessage the received error message.
* @param originalText the original message.
*/
chatAddError(errorMessage, originalText) {
// eslint-disable-next-line no-param-reassign
errorMessage = UIUtil.escapeHtml(errorMessage);
// eslint-disable-next-line no-param-reassign
originalText = UIUtil.escapeHtml(originalText);
$('#chatconversation').append(
`${'<div class="errorMessage"><b>Error: </b>Your message'}${
originalText ? ` "${originalText}"` : ''
} was not sent.${
errorMessage ? ` Reason: ${errorMessage}` : ''}</div>`);
$('#chatconversation').animate(
{ scrollTop: $('#chatconversation')[0].scrollHeight }, 1000);
},
/**
* Sets the chat conversation mode.
* Conversation mode is the normal chat mode, non conversation mode is
* where we ask user to input its display name.
* @param {boolean} isConversationMode if chat should be in
* conversation mode or not.
*/
setChatConversationMode(isConversationMode) {
$(`#${CHAT_CONTAINER_ID}`)
.toggleClass('is-conversation-mode', isConversationMode);
},
/**
* Resizes the chat area.
*/
resizeChat(width, height) {
$(`#${CHAT_CONTAINER_ID}`).width(width)
.height(height);
resizeChatConversation();
},
/**
* Indicates if the chat is currently visible.
*/
isVisible() {
return UIUtil.isVisible(
document.getElementById(CHAT_CONTAINER_ID));
},
/**
* Shows and hides the window with the smileys
*/
toggleSmileys,
/**
* Scrolls chat to the bottom.
*/
scrollChatToBottom() {
setTimeout(
() => {
const chatconversation = $('#chatconversation');
// XXX Prevent TypeError: undefined is not an object when the
// Web browser does not support WebRTC (yet).
chatconversation.length > 0
&& chatconversation.scrollTop(
chatconversation[0].scrollHeight);
},
5);
}
};
export default Chat;

View File

@@ -1,58 +0,0 @@
import { regexes } from './smileys';
/**
* Processes links and smileys in "body"
*/
export function processReplacements(body) {
// make links clickable + add smileys
return smilify(linkify(body));
}
/**
* Finds and replaces all links in the links in "body"
* with their <a href=""></a>
*/
export function linkify(inputText) {
let replacedText;
/* eslint-disable no-useless-escape, max-len */
// URLs starting with http://, https://, or ftp://
const replacePattern1 = /(\b(https?|ftp):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gim;
replacedText = inputText.replace(replacePattern1, '<a href="$1" target="_blank" rel="noopener noreferrer">$1</a>');
// URLs starting with "www." (without // before it, or it'd re-link the ones done above).
const replacePattern2 = /(^|[^\/])(www\.[\S]+(\b|$))/gim;
replacedText = replacedText.replace(replacePattern2, '$1<a href="https://$2" target="_blank" rel="noopener noreferrer">$2</a>');
// Change email addresses to mailto: links.
const replacePattern3 = /(([a-zA-Z0-9\-\_\.])+@[a-zA-Z\_]+?(\.[a-zA-Z]{2,6})+)/gim;
replacedText = replacedText.replace(replacePattern3, '<a href="mailto:$1">$1</a>');
/* eslint-enable no-useless-escape */
return replacedText;
}
/**
* Replaces common smiley strings with images
*/
function smilify(body) {
if (!body) {
return body;
}
let formattedBody = body;
for (const smiley in regexes) {
if (regexes.hasOwnProperty(smiley)) {
formattedBody = formattedBody.replace(regexes[smiley],
`<img class="smiley" src="images/smileys/${smiley}.svg">`);
}
}
return formattedBody;
}

View File

@@ -1,6 +1,5 @@
/* global $, APP, interfaceConfig */
import { setFilmstripVisible } from '../../../react/features/filmstrip';
import {
LAYOUTS,
getCurrentLayout,
@@ -9,188 +8,16 @@ import {
shouldDisplayTileView
} from '../../../react/features/video-layout';
import UIEvents from '../../../service/UI/UIEvents';
import UIUtil from '../util/UIUtil';
import {
createShortcutEvent,
createToolbarEvent,
sendAnalytics
} from '../../../react/features/analytics';
const Filmstrip = {
/**
*
* @param eventEmitter the {EventEmitter} through which {Filmstrip} is to
* emit/fire {UIEvents} (such as {UIEvents.TOGGLED_FILMSTRIP}).
* Caches jquery lookups of the filmstrip for future use.
*/
init(eventEmitter) {
this.iconMenuDownClassName = 'icon-menu-down';
this.iconMenuUpClassName = 'icon-menu-up';
init() {
this.filmstripContainerClassName = 'filmstrip';
this.filmstrip = $('#remoteVideos');
this.filmstripRemoteVideos = $('#filmstripRemoteVideosContainer');
this.eventEmitter = eventEmitter;
// Show the toggle button and add event listeners only when out of
// filmstrip only mode.
if (!interfaceConfig.filmStripOnly) {
this._initFilmstripToolbar();
this.registerListeners();
}
},
/**
* Initializes the filmstrip toolbar.
*/
_initFilmstripToolbar() {
const toolbarContainerHTML = this._generateToolbarHTML();
const className = this.filmstripContainerClassName;
const container = document.querySelector(`.${className}`);
UIUtil.prependChild(container, toolbarContainerHTML);
const iconSelector = '#toggleFilmstripButton i';
this.toggleFilmstripIcon = document.querySelector(iconSelector);
},
/**
* Generates HTML layout for filmstrip toggle button and wrapping container.
* @returns {HTMLElement}
* @private
*/
_generateToolbarHTML() {
const container = document.createElement('div');
const isVisible = this.isFilmstripVisible();
container.className = 'filmstrip__toolbar';
container.innerHTML = `
<button id="toggleFilmstripButton">
<i class="icon-menu-${isVisible ? 'down' : 'up'}">
</i>
</button>
`;
return container;
},
/**
* Attach 'click' listener to "hide filmstrip" button
*/
registerListeners() {
// Important:
// Firing the event instead of executing toggleFilmstrip method because
// it's important to hide the filmstrip by UI.toggleFilmstrip in order
// to correctly resize the video area.
$('#toggleFilmstripButton').on(
'click',
() => {
// The 'enable' parameter is set to true if the action results
// in the filmstrip being hidden.
sendAnalytics(createToolbarEvent(
'toggle.filmstrip.button',
{
enable: this.isFilmstripVisible()
}));
this.eventEmitter.emit(UIEvents.TOGGLE_FILMSTRIP);
});
this._registerToggleFilmstripShortcut();
},
/**
* Registering toggle filmstrip shortcut
* @private
*/
_registerToggleFilmstripShortcut() {
const shortcut = 'F';
const shortcutAttr = 'filmstripPopover';
const description = 'keyboardShortcuts.toggleFilmstrip';
// Important:
// Firing the event instead of executing toggleFilmstrip method because
// it's important to hide the filmstrip by UI.toggleFilmstrip in order
// to correctly resize the video area.
const handler = () => {
sendAnalytics(createShortcutEvent(
'toggle.filmstrip',
{
enable: this.isFilmstripVisible()
}));
this.eventEmitter.emit(UIEvents.TOGGLE_FILMSTRIP);
};
APP.keyboardshortcut.registerShortcut(
shortcut,
shortcutAttr,
handler,
description
);
},
/**
* Changes classes of icon for showing down state
*/
showMenuDownIcon() {
const icon = this.toggleFilmstripIcon;
if (icon) {
icon.classList.add(this.iconMenuDownClassName);
icon.classList.remove(this.iconMenuUpClassName);
}
},
/**
* Changes classes of icon for showing up state
*/
showMenuUpIcon() {
const icon = this.toggleFilmstripIcon;
if (icon) {
icon.classList.add(this.iconMenuUpClassName);
icon.classList.remove(this.iconMenuDownClassName);
}
},
/**
* Toggles the visibility of the filmstrip, or sets it to a specific value
* if the 'visible' parameter is specified.
*
* @param visible optional {Boolean} which specifies the desired visibility
* of the filmstrip. If not specified, the visibility will be flipped
* (i.e. toggled); otherwise, the visibility will be set to the specified
* value.
*
* Note:
* This method shouldn't be executed directly to hide the filmstrip.
* It's important to hide the filmstrip with UI.toggleFilmstrip in order
* to correctly resize the video area.
*/
toggleFilmstrip(visible) {
const wasFilmstripVisible = this.isFilmstripVisible();
// If 'visible' is defined and matches the current state, we have
// nothing to do. Otherwise (regardless of whether 'visible' is defined)
// we need to toggle the state.
if (visible === wasFilmstripVisible) {
return;
}
this.filmstrip.toggleClass('hidden');
if (wasFilmstripVisible) {
this.showMenuUpIcon();
} else {
this.showMenuDownIcon();
}
if (this.eventEmitter) {
this.eventEmitter.emit(
UIEvents.TOGGLED_FILMSTRIP,
!wasFilmstripVisible);
}
APP.store.dispatch(setFilmstripVisible(!wasFilmstripVisible));
},
/**
@@ -198,14 +25,7 @@ const Filmstrip = {
* @returns {boolean}
*/
isFilmstripVisible() {
return !this.filmstrip.hasClass('hidden');
},
/**
* Adjusts styles for filmstrip-only mode.
*/
setFilmstripOnly() {
this.filmstrip.addClass('filmstrip__videos-filmstripOnly');
return APP.store.getState()['features/filmstrip'].visible;
},
/**

View File

@@ -275,9 +275,7 @@ LocalVideo.prototype._onContainerClick = function(event) {
= $source.parents('.displayNameContainer').length > 0;
const clickedOnPopover = $source.parents('.popover').length > 0
|| classList.contains('popover');
const ignoreClick = clickedOnDisplayName
|| clickedOnPopover
|| shouldDisplayTileView(APP.store.getState());
const ignoreClick = clickedOnDisplayName || clickedOnPopover;
if (event.stopPropagation && !ignoreClick) {
event.stopPropagation();

View File

@@ -627,8 +627,7 @@ RemoteVideo.prototype._onContainerClick = function(event) {
const { classList } = event.target;
const ignoreClick = $source.parents('.popover').length > 0
|| classList.contains('popover')
|| shouldDisplayTileView(APP.store.getState());
|| classList.contains('popover');
if (!ignoreClick) {
this._togglePin();

View File

@@ -30,6 +30,7 @@ import {
import {
LAYOUTS,
getCurrentLayout,
setTileView,
shouldDisplayTileView
} from '../../../react/features/video-layout';
/* eslint-enable no-unused-vars */
@@ -161,53 +162,6 @@ SmallVideo.prototype.isVisible = function() {
return this.$container.is(':visible');
};
/**
* Enables / disables the device availability icons for this small video.
* @param {enable} set to {true} to enable and {false} to disable
*/
SmallVideo.prototype.enableDeviceAvailabilityIcons = function(enable) {
if (typeof enable === 'undefined') {
return;
}
this.deviceAvailabilityIconsEnabled = enable;
};
/**
* Sets the device "non" availability icons.
* @param devices the devices, which will be checked for availability
*/
SmallVideo.prototype.setDeviceAvailabilityIcons = function(devices) {
if (!this.deviceAvailabilityIconsEnabled) {
return;
}
if (!this.container) {
return;
}
const noMic = this.$container.find('.noMic');
const noVideo = this.$container.find('.noVideo');
noMic.remove();
noVideo.remove();
if (!devices.audio) {
this.container.appendChild(
document.createElement('div')).setAttribute('class', 'noMic');
}
if (!devices.video) {
this.container.appendChild(
document.createElement('div')).setAttribute('class', 'noVideo');
}
if (!devices.audio && !devices.video) {
noMic.css('background-position', '75%');
noVideo.css('background-position', '25%');
noVideo.css('background-color', 'transparent');
}
};
/**
* Sets the type of the video displayed by this instance.
* Note that this is a string without clearly defined or checked values, and

View File

@@ -216,8 +216,6 @@ export class VideoContainer extends LargeContainer {
this.emitter = emitter;
this.resizeContainer = resizeContainer;
this.isVisible = false;
/**
* Whether the background should fit the height of the container
* (portrait) or fit the width of the container (landscape).
@@ -603,17 +601,11 @@ export class VideoContainer extends LargeContainer {
* TODO: refactor this since Temasys is no longer supported.
*/
show() {
// its already visible
if (this.isVisible) {
return Promise.resolve();
}
return new Promise(resolve => {
this.$wrapperParent.css('visibility', 'visible').fadeTo(
FADE_DURATION_MS,
1,
() => {
this.isVisible = true;
resolve();
}
);
@@ -628,15 +620,9 @@ export class VideoContainer extends LargeContainer {
// hide its avatar
this.showAvatar(false);
// its already hidden
if (!this.isVisible) {
return Promise.resolve();
}
return new Promise(resolve => {
this.$wrapperParent.fadeTo(FADE_DURATION_MS, 0, () => {
this.$wrapperParent.css('visibility', 'hidden');
this.isVisible = false;
resolve();
});
});

View File

@@ -230,26 +230,6 @@ const VideoLayout = {
video.setDeviceAvailabilityIcons(devices);
},
/**
* Enables/disables device availability icons for the given participant id.
* The default value is {true}.
* @param id the identifier of the participant
* @param enable {true} to enable device availability icons
*/
enableDeviceAvailabilityIcons(id, enable) {
let video;
if (APP.conference.isLocalId(id)) {
video = localVideoThumbnail;
} else {
video = remoteVideos[id];
}
if (video) {
video.enableDeviceAvailabilityIcons(enable);
}
},
/**
* Shows/hides local video.
* @param {boolean} true to make the local video visible, false - otherwise

View File

@@ -55,10 +55,6 @@ const KeyboardShortcut = {
APP.UI.clickOnVideo(num);
}
// esc while the smileys are visible hides them
} else if (key === 'ESCAPE'
&& $('#smileysContainer').is(':visible')) {
APP.UI.toggleSmileys();
}
};

63
modules/util/TaskQueue.js Normal file
View File

@@ -0,0 +1,63 @@
const logger = require('jitsi-meet-logger').getLogger(__filename);
/**
* Manages a queue of functions where the current function in progress will
* automatically execute the next queued function.
*/
export class TaskQueue {
/**
* Creates a new instance of {@link TaskQueue} and sets initial instance
* variable values.
*/
constructor() {
this._queue = [];
this._currentTask = null;
this._onTaskComplete = this._onTaskComplete.bind(this);
}
/**
* Adds a new function to the queue. It will be immediately invoked if no
* other functions are queued.
*
* @param {Function} taskFunction - The function to be queued for execution.
* @private
* @returns {void}
*/
enqueue(taskFunction) {
this._queue.push(taskFunction);
this._executeNext();
}
/**
* If no queued task is currently executing, invokes the first task in the
* queue if any.
*
* @private
* @returns {void}
*/
_executeNext() {
if (this._currentTask) {
logger.warn('Task queued while a task is in progress.');
return;
}
this._currentTask = this._queue.shift() || null;
if (this._currentTask) {
this._currentTask(this._onTaskComplete);
}
}
/**
* Prepares to invoke the next function in the queue.
*
* @private
* @returns {void}
*/
_onTaskComplete() {
this._currentTask = null;
this._executeNext();
}
}

View File

@@ -1,3 +1,5 @@
import { TaskQueue } from './TaskQueue';
/**
* Create deferred object.
*
@@ -13,3 +15,12 @@ export function createDeferred() {
return deferred;
}
/**
* Returns an instance of {@link TaskQueue}.
*
* @returns {Object}
*/
export function createTaskQueue() {
return new TaskQueue();
}

7141
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -15,25 +15,25 @@
"author": "",
"readmeFilename": "README.md",
"dependencies": {
"@atlaskit/avatar": "8.0.5",
"@atlaskit/button": "5.4.2",
"@atlaskit/checkbox": "2.0.2",
"@atlaskit/dropdown-menu": "3.10.2",
"@atlaskit/droplist": "4.11.1",
"@atlaskit/field-text": "4.0.1",
"@atlaskit/field-text-area": "1.2.0",
"@atlaskit/avatar": "14.0.10",
"@atlaskit/button": "9.0.8",
"@atlaskit/checkbox": "4.0.6",
"@atlaskit/dropdown-menu": "6.1.12",
"@atlaskit/field-text": "7.0.10",
"@atlaskit/field-text-area": "4.0.9",
"@atlaskit/flag": "6.1.0",
"@atlaskit/icon": "10.0.0",
"@atlaskit/icon": "13.8.1",
"@atlaskit/inline-dialog": "5.3.0",
"@atlaskit/inline-message": "4.0.0",
"@atlaskit/layer-manager": "2.8.0",
"@atlaskit/lozenge": "3.4.2",
"@atlaskit/modal-dialog": "3.4.0",
"@atlaskit/multi-select": "7.1.3",
"@atlaskit/spinner": "4.0.0",
"@atlaskit/tabs": "4.0.1",
"@atlaskit/theme": "2.4.0",
"@atlaskit/tooltip": "9.1.1",
"@atlaskit/inline-message": "7.0.4",
"@atlaskit/layer-manager": "5.0.12",
"@atlaskit/lozenge": "6.2.0",
"@atlaskit/modal-dialog": "6.0.12",
"@atlaskit/multi-select": "11.0.6",
"@atlaskit/spinner": "9.0.8",
"@atlaskit/tabs": "8.0.8",
"@atlaskit/theme": "6.0.2",
"@atlaskit/toggle": "5.0.8",
"@atlaskit/tooltip": "12.0.14",
"@microsoft/microsoft-graph-client": "1.1.0",
"@webcomponents/url": "0.7.1",
"autosize": "1.18.13",
@@ -47,61 +47,70 @@
"jquery-contextmenu": "2.4.5",
"jquery-i18next": "1.2.0",
"js-md5": "0.6.1",
"js-utils": "github:jitsi/js-utils#446497893023aa8dec403e0e4e35a22cae6bc87d",
"jsc-android": "224109.1.0",
"jsrsasign": "8.0.12",
"jwt-decode": "2.2.0",
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#095a4485f2e6749f5b4fe91da7088aed359ee728",
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#34f728456eb5e8e9817bdaf999e4702bac2ee6ce",
"libflacjs": "github:mmig/libflac.js#93d37e7f811f01cf7d8b6a603e38bd3c3810907d",
"lodash": "4.17.4",
"moment": "2.19.4",
"moment-duration-format": "2.2.2",
"postis": "2.2.0",
"prop-types": "15.6.0",
"react": "16.3.1",
"react-dom": "16.3.1",
"react-i18next": "4.8.0",
"react-native": "0.55.4",
"react": "16.5.0",
"react-dom": "16.5.0",
"react-i18next": "7.13.0",
"react-native": "0.57.1",
"react-native-background-timer": "2.0.0",
"react-native-calendar-events": "github:jitsi/react-native-calendar-events#03babdb99e7fea3539796804cecdef8a907f2a3f",
"react-native-callstats": "3.52.0",
"react-native-fast-image": "4.0.14",
"react-native-calendar-events": "github:wmcmahan/react-native-calendar-events#056807286da610d884fb6b4c8ca187a767b261f7",
"react-native-callstats": "3.53.4",
"react-native-fast-image": "github:jitsi/react-native-fast-image#1f8c93a5584869848d75cc9b946beb9688efe285",
"react-native-google-signin": "1.0.0-rc6",
"react-native-immersive": "1.1.0",
"react-native-keep-awake": "2.0.6",
"react-native-linear-gradient": "2.4.0",
"react-native-locale-detector": "github:jitsi/react-native-locale-detector#845281e9fd4af756f6d0f64afe5cce08c63e5ee9",
"react-native-permissions": "1.1.1",
"react-native-prompt": "1.0.0",
"react-native-sound": "0.10.9",
"react-native-vector-icons": "4.4.2",
"react-native-webrtc": "github:jitsi/react-native-webrtc#bed49210a51cf53081954028589d720381e7cf40",
"react-native-swipeout": "2.3.6",
"react-native-vector-icons": "6.0.2",
"react-native-webrtc": "github:jitsi/react-native-webrtc#6322a9b5a38ce590cfaea4041072ea87c8dbf558",
"react-redux": "5.0.7",
"react-transition-group": "2.4.0",
"redux": "4.0.0",
"redux-thunk": "2.2.0",
"styled-components": "1.4.6",
"styled-components": "3.4.9",
"uuid": "3.1.0",
"xmldom": "0.1.27"
},
"devDependencies": {
"babel-core": "6.26.0",
"babel-eslint": "8.0.3",
"babel-loader": "7.1.2",
"babel-preset-env": "1.6.1",
"babel-preset-react": "6.24.1",
"babel-preset-stage-1": "6.24.1",
"@babel/core": "7.1.2",
"@babel/preset-env": "7.1.0",
"@babel/preset-flow": "7.0.0",
"@babel/preset-react": "7.0.0",
"@babel/plugin-proposal-class-properties": "7.1.0",
"@babel/plugin-proposal-export-default-from": "7.0.0",
"@babel/plugin-proposal-export-namespace-from": "7.0.0",
"@babel/plugin-transform-flow-strip-types": "7.0.0",
"@babel/runtime": "7.1.2",
"babel-eslint": "10.0.1",
"babel-loader": "8.0.4",
"clean-css": "3.4.25",
"css-loader": "0.28.7",
"eslint": "4.12.1",
"eslint": "5.6.1",
"eslint-config-jitsi": "github:jitsi/eslint-config-jitsi#7474f6668515eb5852f1273dc5a50b940a550d3f",
"eslint-plugin-flowtype": "2.39.1",
"eslint-plugin-import": "2.8.0",
"eslint-plugin-jsdoc": "3.2.0",
"eslint-plugin-react": "7.5.1",
"eslint-plugin-react-native": "3.2.0",
"eslint-plugin-flowtype": "2.50.3",
"eslint-plugin-import": "2.14.0",
"eslint-plugin-jsdoc": "3.8.0",
"eslint-plugin-react": "7.11.1",
"eslint-plugin-react-native": "3.3.0",
"expose-loader": "0.7.4",
"file-loader": "1.1.5",
"flow-bin": "0.67.1",
"flow-bin": "0.78.0",
"imports-loader": "0.7.1",
"node-sass": "4.8.3",
"metro-react-native-babel-preset": "0.47.0",
"node-sass": "4.10.0",
"precommit-hook": "3.0.0",
"string-replace-loader": "1.3.0",
"style-loader": "0.19.0",

View File

@@ -4,5 +4,8 @@ module.exports = {
'eslint-config-jitsi/jsdoc',
'eslint-config-jitsi/react',
'.eslintrc-react-native.js'
]
],
'rules': {
'react/no-deprecated': 0
}
};

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