Compare commits

...

195 Commits

Author SHA1 Message Date
Aaron van Meerten
20b597abda Merge branch 'analytics_include_region_info' of github.com:jitsi/jitsi-meet into analytics_include_region_info 2017-05-23 15:01:18 -05:00
Aaron van Meerten
e68be6fa84 assign isn’t supported, so do a for loop 2017-05-23 15:00:42 -05:00
Aaron van Meerten
44d75b5253 add new local file for inclusion via ssi, intended for local variables describing the server
change analytics to pull from new collection jitsiAnalyticsPermanentProperties, which is to be user-defined via javascript in local.html
2017-05-23 15:00:42 -05:00
Aaron van Meerten
440865f3ca missing assignments
lots of line breaks for coding style
2017-05-23 15:00:42 -05:00
Aaron van Meerten
e91c62142d fixed trailing comma 2017-05-23 15:00:42 -05:00
Aaron van Meerten
1a892a689e moved to explicit assignment of properties from jitsiRegionInfo
removed no longer needed jshint ignore statement
2017-05-23 15:00:42 -05:00
Aaron van Meerten
04851b4baa add jitsi regional information to all analytics events via permanent properties if available
ignore listing on modules/analytics/analytics.js to avoid es6 linting errors
2017-05-23 15:00:42 -05:00
Aaron van Meerten
beec60be47 assign isn’t supported, so do a for loop 2017-05-23 14:59:02 -05:00
Aaron van Meerten
36d40cdec9 add new local file for inclusion via ssi, intended for local variables describing the server
change analytics to pull from new collection jitsiAnalyticsPermanentProperties, which is to be user-defined via javascript in local.html
2017-05-23 14:49:34 -05:00
hristoterezov
23fea490aa Merge pull request #1585 from jitsi/dial-out-ui
Adds dial-out UI.
2017-05-23 10:29:23 -05:00
yanas
1d60300016 Merge pull request #1592 from virtuacoplenny/lenny/slider-width
fix(volume-slider): modify positioning so slider fits popup width
2017-05-23 10:15:36 -05:00
Aaron van Meerten
5be3504fad missing assignments
lots of line breaks for coding style
2017-05-23 09:50:14 -05:00
Aaron van Meerten
bc9ef4421a fixed trailing comma 2017-05-23 09:37:06 -05:00
Aaron van Meerten
4ffe668dd2 moved to explicit assignment of properties from jitsiRegionInfo
removed no longer needed jshint ignore statement
2017-05-23 09:26:49 -05:00
yanas
6536f82559 fix(Dialog.web.js): Fixes okDisabled state not taken into account 2017-05-23 09:00:40 -05:00
yanas
4464a11314 Changes telephone icon 2017-05-23 09:00:40 -05:00
yanas
2855ea1500 Adds dial-out UI. 2017-05-23 09:00:40 -05:00
Leonard Kim
258dc594dd fix(volume-slider): modify positioning so slider fits popup width 2017-05-22 14:16:06 -07:00
Leonard Kim
a1476c68f1 fix(audio-only): remove button from toolbar and set label cursor
Audio only mode will be toggleable only from the VideoStatusLabel,
so remove AudioOnlyButton from the toolbar and delete the component
itself. As a result of the button being removed, a truthy check in
VideoStatusLabel was also removed to ensure it will display as it
is now the only way to toggle audio only mode. Also set the cursor
on VideoStatusLabel to always be default, so it can never show the
text cursor.
2017-05-22 14:46:05 -05:00
Aaron van Meerten
3cc4d44376 add jitsi regional information to all analytics events via permanent properties if available
ignore listing on modules/analytics/analytics.js to avoid es6 linting errors
2017-05-22 14:40:18 -05:00
damencho
bf163d221c Adds download source archive link to the README. 2017-05-22 11:28:04 -05:00
yanas
7900b9c294 Merge pull request #1577 from virtuacoplenny/lenny/invite-conference-number
feat(invite): Add conference id to dial-in numbers display
2017-05-22 11:08:18 -05:00
jitsi-pootle
6a17d50423 New files added from translate.jitsi.org based on templates 2017-05-22 14:54:57 +00:00
Leonard Kim
9e7f8d0e16 SQUASH: use redux to get config 2017-05-19 16:07:13 -07:00
Leonard Kim
3a99ef512e SQUASH: add comment to styling and alpha order 2017-05-19 15:41:42 -07:00
Leonard Kim
a14886031f SQUASH: changes based on feedback: rename, handle error 2017-05-19 15:35:47 -07:00
Дамян Минков
ec881e0fd0 Merge pull request #1580 from jitsi/fix-isguest-typeerror
Fix TypeError: Cannot read property 'isGuest' of undefined
2017-05-18 13:10:28 -05:00
Leonard Kim
80989147ad feat(video-label): Add dropdown for toggling audio only
Add a menu that displays when hovering over VideoStatusLabel. The menu's
display is controlled by CSS. As the existing AudioOnlyLabel no longer needs
needs its own tooltip, it has been removed and label display logic has been
moved into VideoStatusLabel.
2017-05-18 13:09:34 -05:00
Lyubo Marinov
3c31a60b32 Fix TypeError: Cannot read property 'isGuest' of undefined 2017-05-18 11:53:45 -05:00
Lyubo Marinov
db59b45076 Upgrade NPM dependencies/packages 2017-05-17 16:41:52 -05:00
Leonard Kim
0f0ff6788c fix(config): Bring back minHDHeight 2017-05-17 16:13:16 -05:00
Leonard Kim
47c07c2e76 feat(invite): Add conference id to dial-in numbers display
DialInNumbersForm has been modified to display a conference id to be used for
dialing into the conference. The changes include:
- Requesting the conference id and adding the conference id to the redux store
- Displaying the conference id in DialInNumbersForm
- Modifying the copy behavior to support copying the new message to clipboard
- DialInNumbersForm does not display until all ajax requests have completed
  successfully. This eliminates the need for the REQUESTING state.
2017-05-17 10:25:07 -07:00
Leonard Kim
896dcde2b2 fix(video-label): Listen to resize events on video elements when possible 2017-05-17 11:54:22 -05:00
Leonard Kim
a88409bbfa fix(video-label): Display based on video dimensions in LargeVideoManager
In its current implementation, the VideoStatusLabel shows HD based on peer
connection stats. These stats will be available on temasys browsers soon but
will remain unavailable on Firefox, which does not collect height/width stats.
To support VideoStatusLabel showing cross-browser, move the high-definition
detection out of stat sniffing and instead check the video element itself using
an interval in LargeVideoManager. (An interval was used because the temasys
video object does not support the onresize event.) Also, add a cleanup path from
conference.web to LargeVideoManager to remove the interval.
2017-05-17 11:54:22 -05:00
damencho
b8189a31ad Updates quick-install with mention of jvb behind nat and link to config. 2017-05-16 16:26:47 -05:00
damencho
e90d09a6d9 Updates recording instructions in manual install doc. 2017-05-16 16:19:31 -05:00
damencho
9fb49cb59b Updates default config, avoids storing muc data on prosody restart. 2017-05-16 16:04:26 -05:00
Любомир Маринов
77ab05823d Merge pull request #1576 from jitsi/remote_control_enabled
fix(remotecontrol): Controller enabled property
2017-05-16 12:49:17 -05:00
hristoterezov
28ff188f96 fix(remotecontrol): Controller enabled property 2017-05-16 12:45:39 -05:00
Saúl Ibarra Corretgé
bac191f96c [RN] Rename jitsi-meet-react to jitsi-meet 2017-05-16 12:38:36 -05:00
yanas
e1a9487896 Merge pull request #1562 from virtuacoplenny/lenny/filmstrip-disable-hiding
fix(filmstrip): Disable keyboard shortcut for hiding videos
2017-05-16 11:59:44 -05:00
damencho
9e728e4b25 Fixes crashing jwt util for anonymous domains.
Room name verification crashes when we have a configured anonymousdomain as it doesn't have any token extracted data. It is safe to skip this check as room creation is verified by jicofo and we have the option restrict_room_creation to admin users.
Removes obsolete print when updating jitsi-meet-tokens.
2017-05-16 08:21:46 -05:00
bgrozev
06d2c9fb7b Merge pull request #1573 from saghul/enable-ff-ds
fix(screen-sharing) Enable it by default on Firefox
2017-05-15 15:14:49 -05:00
Дамян Минков
63c862d925 Updates docs report issue part. 2017-05-15 10:57:31 -05:00
Saúl Ibarra Corretgé
a96a70869d fix(screen-sharing) Enable it by default on Firefox
Starting with Firefox 51 the extension is no longer mandatory, so make sure the
feature is not desabled by default.
2017-05-15 12:15:00 +01:00
damencho
ede5be119f Skips changing prosody config on upgrading jitsi-meet-tokens package. 2017-05-12 16:12:15 -05:00
Saúl Ibarra Corretgé
b7c57d306a Merge pull request #1565 from virtuacoplenny/lenny/defensive-classnames
fix(toolbox): Defensively check classNames when mapping button attrib…
2017-05-12 10:10:04 +02:00
Leonard Kim
816eef1702 fix(toolbox): Defensively check classNames when mapping button attributes 2017-05-11 14:35:33 -07:00
Leonard Kim
92eeba5392 fix(filmstrip): Disable keyboard shortcut for hiding videos 2017-05-10 14:04:09 -07:00
Lyubo Marinov
2f3706bd37 [RN] Simplify
There were getDomain, setDomain, SET_DOMAIN, setRoomURL, SET_ROOM_URL
which together were repeating one and the same information and in the
case of the 'room URL' abstraction was not 100% accurate because it
would exist even when there was no room. Replace them all with a
'location URL' abstraction which exists with or without a room.

Then the 'room URL' abstraction was not used in (mobile) feature
share-room. Use the 'location URL' there now.

Finally, removes source code duplication in supporting the Web
application context root.
2017-05-09 16:31:21 -05:00
Lyubo Marinov
e6f6884c36 [RN] Support JSON Web Token (JWT)
Make 'Add jwt module to react' work on mobile.
2017-05-09 00:21:14 -05:00
Ilya Daynatovich
ab5c2e9ded Add jwt module to react 2017-05-09 00:21:14 -05:00
Ilya Daynatovich
4f72225372 Add set room url action 2017-05-09 00:21:13 -05:00
Ilya Daynatovich
3af0976a43 Beautify URLProcessor 2017-05-09 00:21:13 -05:00
Ilya Daynatovich
96b1f0ca74 Create config util 2017-05-09 00:21:13 -05:00
damencho
32ea2161eb Hides sip dialout button if configured to show it for non guest users. 2017-05-08 15:34:32 -05:00
damencho
c8ab1b9892 Catches promise errors, returned when error is received after dial cmd. 2017-05-08 15:34:32 -05:00
damencho
61e637a639 Adds prosody module to filter incoming rayo iqs based on jwt token.
Returns forbidden error message if module is enabled and the user sending a dialout rayo command is not authenticated through jwt token or is not allowed to enter the room name from the rayo iq.
2017-05-08 15:34:32 -05:00
damencho
7d94d3fd1a Updates room size API to work with multiple domains.
Checks for a parameter named subdomain and if it exists, adds it to the roomname as used in multiple domain mode ([subdomain]roomname@conference.example.com).
Moves muc_size module to per-host module and adds token verification.
2017-05-08 11:23:13 -05:00
damencho
88a58a057e Removes not needed parameter token in process_and_verify_token. 2017-05-08 11:23:13 -05:00
damencho
4bb51516bb Adds domain name verification and multidomain support.
Adds option to enable/disable domain checking, disabled by default. Domain verification for multiple domains depends on new option muc_mapper_domain_base.
2017-05-08 11:23:13 -05:00
damencho
0805b9e99e Removes disableRoomNameConstraints option.
This option is useless, as if we do not need to verify room name, we just disable the mod_token_verification module.
2017-05-08 11:23:13 -05:00
damencho
82b27b45fe Moves token related code into util so it can be reused. 2017-05-08 11:23:13 -05:00
yanas
166fb1d13f Merge pull request #1509 from virtuacoplenny/lenny/web-audio-only
Audio only mode for web
2017-05-05 11:52:15 -05:00
Leonard Kim
ef9f145cb5 fix(video-layout): always honor local video changes
During the implementation of starting as video muted, a check
was put in place to update the local thumbnail view container's
known local track only if the track was not muted. This can
cause the container to become desynced with the current local
track.(Ideally in the future all state would be in redux so this
manual syncing would not be needed.) Removing the if-muted
check seems to cause no side-effects and makes implementation of
device-switch-while-audio-only a lot simpler because new tracks
can be muted and used immediately.
2017-05-05 09:27:59 -07:00
Leonard Kim
929bc8b8b9 fix(device-selection): do not reuse tracks in previews
Device selection has live previews that reuse the current local
audio and video tracks for the sake of internet explorer. This
means when the local video was muted, device selection would
show a muted message. It is preferred to show a live preview
even when muted.

The changes include:
- Passing device ids into DeviceSelectionDialog, not tracks.
- Setting default selected devices to use for live previews.
- Removing all checks in DeviceSelectionDialog involving local tracks.
- Catching and displaying errors when creating a live video preview.
2017-05-05 09:27:59 -07:00
Leonard Kim
d24d5d95dd fix(audio-only): combine video status labels
Move the HD label into the newly renamed VideoStatusLabel
component. That way it cannot be possible for the audio only
label and the HD label to display simultaneously.
2017-05-05 09:27:59 -07:00
Saúl Ibarra Corretgé
9ba3a1c4ff feat(conference): add audio only mode
Audio only mode can be used to save bandwidth. In this mode local video is muted
and last N is set to 0, thus disabling all remote video.

When this mode is enabled avatars are shown.
2017-05-05 09:27:59 -07:00
yanas
1bcdbd1d96 Merge pull request #1552 from virtuacoplenny/lenny/ff-avatar-inlay
fix(filmstrip): Set avatar container height within inlay
2017-05-05 11:25:08 -05:00
Leonard Kim
8ada06cfe3 fix(filmstrip): Set avatar container height within inlay
In the filmstrip inlay, the avatar container holds the avatar
image. The image is set to 100% height, so it displays entirely
in the container. However, this does not adjust the horizontal
space created by the image in firefox, leaving whitespace to the
right of the avatar. The fix is to set the container height to
100% so that all its content will fit inside, automatically
adjusting the space created by the image width.
2017-05-04 11:46:10 -07:00
yanas
bf0be99366 Merge pull request #1551 from jitsi/smileys-fix
Fixes #1549, toggles smileys menu.
2017-05-04 11:28:27 -05:00
damencho
653f1dae4c Fixes #1549, toggles smileys menu. 2017-05-04 11:19:46 -05:00
hristoterezov
d91340166d Remote control - display the authorization dialog in meet (#1541)
* fix(react/participant): store display name in redux

* feat(remotecontrol): Add option to display the authorization dialog in meet

* feat(remotecontrol): Enable ESLint and Flow
2017-05-03 18:57:52 -05:00
Дамян Минков
d694e8df86 Updates README, removing "Discuss" section. 2017-05-03 13:00:46 -05:00
Lyubo Marinov
75a486ff96 Merge branch 'abstract_transport2'
Additionally, attempt to move closer to the coding style adopted by
react/.
2017-05-02 17:40:16 -05:00
Leonard Kim
48626ee71b fix(toolbox): move default toolbox buttons logic to web only
toolbox/functions has functions that are specific only to web,
specifically defaultToolbarButtons. This has caused the native
build to attempt to bring in a web dependency which leads to a
build error. The fix for now is splitting web functions from
native functions to resolve the build error.
2017-05-02 12:01:54 -05:00
hristoterezov
b297aa3f3a ref(remotecontrol): Changing the format of the messages 2017-05-02 09:47:07 -05:00
hristoterezov
dfc94ff144 ref(iframe_API): Changing the format of the outgoing messages for API.js 2017-05-01 15:59:18 -05:00
yanas
1ffa7be4e1 Merge pull request #1531 from virtuacoplenny/lenny/invite-phone-numbers
feat(invite): include dial-in numbers in the invite modal
2017-05-01 15:39:38 -05:00
Leonard Kim
d7cccacc12 feat(invite): include dial-in numbers in the invite modal
Create a new React Component for displaying a list of dial-in
numbers. The Component will fetch the numbers from a new
numberRetreviewUrl key/value set in config. If not present in
config, the Component will not be displayed.
2017-05-01 12:47:35 -07:00
hristoterezov
96bde3ff44 fix(trnasport): Names of the arguments for Transport and backend
Now the backends are working with 'message' and Transport is
working with 'request', 'response' and 'event'.
2017-04-28 16:03:36 -05:00
hristoterezov
b49c1c6ba2 fix(transport): Code style issues and enableLegacyFormat param bug
Improves naming.
Fixing typos.
enableLegacyFormat param was working like disableLegacyFormat.
Improves the structure of transport/index.js
2017-04-28 15:24:20 -05:00
Lyubo Marinov
e9dc9c47a9 Coding style
For lack of a better word/phrase, I'm calling all changes coding style.
I'm targeting readability through naming and syntax.
2017-04-28 10:41:08 -05:00
hristoterezov
3e055c1201 ref(external_api): To use transport module 2017-04-28 10:41:08 -05:00
hristoterezov
54388b6a0a ref(remotecontrol): To use transport module instead of API 2017-04-28 10:41:08 -05:00
hristoterezov
0dff35c0db feat(transport): Implement transport module
The transport module will be handling all external app
communication.
2017-04-28 10:41:08 -05:00
yanas
6c676f8d5f Merge pull request #1538 from virtuacoplenny/lenny/device-selection-disabled
fix(device-selection): do not create a dropdown menu if disabled
2017-04-26 17:16:28 -05:00
Leonard Kim
4e95dbf0e5 fix(device-selection): do not create a dropdown menu if disabled
AtlasKit DropdownMenu cannot be disabled, unlike Single Select.
The result is the isDisabled prop was not being honored. The
workaround is returning only the trigger element for the dropdown
and styling it to look like the dropdown is disabled. The text
for disabled device selection was changed along the way to fit
into the trigger.
2017-04-26 14:29:32 -07:00
Guus der Kinderen
00b4176bf8 Non-clickable watermarks without links
Watermarks can be used to link to an external site by configuring a URL.
However, the URL is optional. When it is not set, the watermark should
not be clickable. This prevents users from reloading the room by
clicking on a watermark (caused by an HTML anchor element without an
href).
2017-04-26 10:54:32 -05:00
Lyubo Marinov
7836fd1990 Introduce features/base/logging
The functionality around logging including logging_config.js i.e.
loggingConfig and the other classes and/or functions that initialize
loggers for Jits Meet truly deserves a feature of its own. Start getting
in that direction on both Web and mobile by introducing
features/base/logging and bringing loggingConfig to mobile.
2017-04-23 23:30:53 -05:00
Lyubo Marinov
92e765ea21 Introduce features/base/config
The config object defined by lib-jitsi-meet is not used by
lib-jitsi-meet only, jitsi-meet respects its values as well.
Moreover, jitsi-meet defined classes and/or functions which manipulate
that config object. Consequently, it makes sense to move the config
object and the associated classes and functions in a dedicated feature.
2017-04-23 15:18:41 -05:00
Lyubo Marinov
bce1610794 Simplify, comply w/ coding style
Rename setStateProperties and setStateProperty to assign and set,
respectively. Inspired by Object.assign, _.assign, and _.set.
2017-04-22 17:57:08 -05:00
Lyubo Marinov
1f16233afa Upgrade NPM dependencies/packages 2017-04-22 17:27:16 -05:00
yanas
e804548b22 Merge pull request #1529 from virtuacoplenny/lenny/device-overflow-stopgap
fix(device-selection): convert trigger element to a div
2017-04-21 17:05:33 -05:00
yanas
dc994173d7 Merge pull request #1500 from virtuacoplenny/whitelist-user-select
fix: do not apply user-select none to inputs
2017-04-21 13:12:07 -05:00
Leonard Kim
283140d16a fix: do not apply user-select none to inputs
Safari will prevent proper input behavior when user-select none
is applied. It prevents such actions as putting in a room lock
password or setting a room lock password. Other browsers allow
selecting on inputs while user-select is none.
2017-04-21 11:10:47 -07:00
Leonard Kim
03155c63ae fix(device-selection): pass translation key into the translate function 2017-04-21 08:58:34 -07:00
Leonard Kim
17fc28b020 fix(device-selection): convert trigger element to a div
AtlasKit Dropdown was recently updated to support fitting the
width of its container. However, AtlasKit Button, the trigger
element currently used for the dropdowns, does not fit the width
of AtlasKit Dropdown and stll has text overflowing out of its
button when there is an iconBefore prop passed in. Instead of
using AtlasKit Button, use a div and mimic the button look. This
allows the "button" to fit the container width and can display
ellipsized text within itself.
2017-04-20 19:34:02 -07:00
Saúl Ibarra Corretgé
607bef8d68 Merge pull request #1518 from jitsi/fix_removeListeners
fix(external_api): JS error in removeListeners and simplifies dispose
2017-04-20 23:47:15 +02:00
hristoterezov
55f5ceb85a fix(external_api): JS error in removeListeners and simplifies dispose 2017-04-20 15:08:13 -05:00
Saúl Ibarra Corretgé
7be8e3e1e9 Merge pull request #1523 from virtuacoplenny/lenny/mute-overlay-black
fix(device-selection): show black background only when video muted
2017-04-20 15:16:53 +02:00
Lyubo Marinov
55c3f5ddff Comply w/ coding style
@virtuacoplenny, the changes of this commit are not necessarily in
source code that you introduced in
https://github.com/jitsi/jitsi-meet/pull/1499 but I saw violations in
files modified in the PR which I had to read in order to understand the
PR.
2017-04-19 20:56:19 -05:00
Leonard Kim
44b81b20e3 feat: convert invite dialog to react and redux
Converting the invite modal includes the following:
- Creating new react components to display InviteDialog. The
  main parent components are ShareLink and PasswordOverview,
  the later handles displaying lock state and password editing.
  These components do not make use of atlaskit as the component
  for input does not yet support readonly, so for consistency
  within the modal content no atlaskit was used.
- Using redux for keeping and accessing lock state instead of
  RoomLocker.
- Publicly exposing the redux action lockStateChanged for direct
  calling on lock events experienced on the web client.
- Removing Invite, InviteDialogView, and RoomLocker and references
  to them.
- Handling errors that occur when setting a password to preserve
  existing web funtionality.
2017-04-19 20:41:23 -05:00
yanas
4097be1908 Merge pull request #1524 from jitsi/overlay_on_top
fix(overlay): Make the overlays on top of everything
2017-04-19 17:42:02 -05:00
hristoterezov
f6ef727573 fix(overlay): Make the overlays on top of everything 2017-04-19 17:09:22 -05:00
Leonard Kim
1045cb56fe fix(device-selection): show black background only when video muted
On certain modal dimensions, the black background of the video
preview could peek through, making it look like the video has a
black line. The change is to remove the black background from
showing by default and having it only display when the video is
muted. Also, the video preview dimension stylings have been
changed to facilitiate smoother size adjusting with modal size
changes.
2017-04-19 14:51:51 -07:00
Lyubo Marinov
6c0ad4966e [Android] Go back to minSdkVersion 16 2017-04-19 13:55:44 -05:00
Saúl Ibarra Corretgé
5d50792a56 [RN] Make AudioMode more resilient on Android
Some devices may give an error stating that INTERACT_ACROSS_USERS_FULL
permission is neeced. This permission can only be achieved by signing the
application with the same key as the system, which is never going to happen so
deal with it by catching any exceptions setting the mode may cause and failing
as gracefully as we can.

Ref:
http://stackoverflow.com/questions/34172575/audiomanager-setmode-securityexception-on-huawei-android-4
2017-04-19 13:55:44 -05:00
Saúl Ibarra Corretgé
0c16842e0d [RN] Fix full-screen mode when a dialog is opened on Android
When a dialog is opened on Android, full-screen mode is exited but we (the app)
know nothing about this. Make sure we re-enter full-screen mode once a dialog is
closed, if the conditions to be in such mode are still met.
2017-04-19 13:55:44 -05:00
Saúl Ibarra Corretgé
d9538845bc [RN] Set iOS status bar style to light
It will render as white in dark backgrounds. This is what FaceTime does and what
we already do on Android. The problem with the default look (black text) is
noticeable in audio only mode, since the background is dark.
2017-04-19 14:07:20 +02:00
Saúl Ibarra Corretgé
7f1579d96d [RN] Update iOS Xcode project file 2017-04-19 14:06:10 +02:00
yanas
3674694d12 Merge pull request #1514 from virtuacoplenny/lenny/video-preview-mute-string
fix: change string displayed when previewing a muted video input
2017-04-18 16:33:50 -05:00
Leonard Kim
3e518e8040 feat: convert device selection modal to use AtlasKit Dropdown
Instead of using AtlasKit Single-Select, use Dropdown. Dropdown
differs in that an icon can be specified for the trigger element,
whereas Single-Select currently supports icons for all elements,
and Dropdown can show all options incuding the already-selected
option.

This change does introduce the issue of the trigger element not
taking up 100% width of the parent. Supporting such would involve
overriding AtlasKit CSS. The compromise made here was to do a
generic override of max-width so the trigger elements at least
stay within the parent and aligning the trigger elements to the
right.
2017-04-18 16:08:03 -05:00
bgrozev
33c92a31bf Merge pull request #1516 from jitsi/remove_cfg_log_stats
fix(config.js): remove unused 'logStats'
2017-04-18 16:01:49 -05:00
paweldomas
5e2e7902ce fix(config.js): remove unused 'logStats'
This config option is not used anymore.
2017-04-18 14:27:00 -05:00
Leonard Kim
f9585430bb fix: set a default color for text in modal dialogs
Some atlaskit components, such as field-text, inherit text color.
This is a problem with components that are white as they will
inherit $defaultColor, which is a light gray. So instead, for
the atlaskit modal, set a color for all the form content so it
can be inherited instead.
2017-04-18 14:14:26 -05:00
Leonard Kim
19de32e206 feat: support directly setting dialog title text
Dialog does not currently support displaying dynamic strings
for titles, only static strings listed for translation. Accept
a new prop that explicitly states it is for setting the title
and have the web dialog prefer it over the titleKey.
2017-04-18 14:14:26 -05:00
Leonard Kim
a82bc1df64 fix: honor updates of the local user role before conference join
When the prosody setting has muc_allowners, everyone joins as a
moderator. In this case, the local user will not be set as a
moderator in the redux store as the USER_ROLE_CHANGE event will
trigger with the local user id before the redux store has set
the actual local user id--something that happens on
CONFERENCE_JOINED. The fix is to explicitly signal the local user
role has changed to the redux store, which follows the
implementation of pre-existing web logic.
2017-04-18 14:14:04 -05:00
Дамян Минков
c34e841710 Merge pull request #1505 from cmrd-senya/prosody-default-fixup
Explicitly set c2s_require_encryption to false
2017-04-18 13:50:25 -05:00
Leonard Kim
a5c78be52c fix: change string displayed when previewing a muted video input
Device Selection re-uses the local video track for its preview.
When displaying Device Selection while video muted, the text
"muted" displays within the video preview, but some translations
may mistake this to mean audio muted. The text has been changed
to be explicit about video mute. This is a temporary solution;
at some point Device Selection should not re-use the local track
except for IE, the one browser that cannot get multiple tracks
from the same video input.
2017-04-18 10:21:41 -07:00
Дамян Минков
edbbaef26f Merge pull request #1513 from jitsi/fix_change_display_name
fix(conference): Change display name when ChatRoom is not created yet
2017-04-18 11:08:10 -05:00
hristoterezov
bdd133309d fix(conference): Change display name when ChatRoom is not created yet 2017-04-18 10:35:14 -05:00
cmrd Senya
71da05dc96 Explicitly set c2s_require_encryption to false
Jitsi Meet doesn't seem to work with c2s_require_encryption set to true.
c2s_require_encryption is false by default. However it is possible that
in some Prosody configurations it is overriden by a global config to be
true. In that case Jitsi Meet might not work out-of-box. So let's set it
explicitly to be sure it is correct.
2017-04-16 21:19:47 +03:00
Christopher Edsall
f1cbafb097 Spelling in main.json
Fix spelling/grammar for  micConstraintFailedError and cameraConstraintFailedError
2017-04-15 17:41:28 +12:00
Lyubo Marinov
3db557e2c9 Move roomnameGenerator.js out of features/base/util
Over time features/base/util became a bucket where people seemed to dump
just about anything they couldn't think of a better place for. That's a
trend I don't like encouraging. Given that roomnameGenerator.js is
currently used in features/welcome only, I'm fine with moving it there
for the greater good.
2017-04-14 13:14:02 -05:00
Lyubo Marinov
a8b3177e20 Move timeUtil.js out of features/base/util
Because timeUtil.js computes hours, minutes, and seconds out of a single
time/duration using three separate functions, I wouldn't recommend using
it, especially reusing it. That's why I'm even making the functions
private to their current use location.
2017-04-14 13:14:02 -05:00
Lyubo Marinov
8e6f043586 Move interceptComponent.js out of features/base/util
I don't like the file/function name, I'm not excited about the
complexity of the logic it implements, and it's definitely a reusable
piece worthy of being called a utility.
2017-04-14 13:12:47 -05:00
damencho
5163472392 Merge pull request #1502 from BeatC:fix-dialpad-button 2017-04-14 11:46:32 -05:00
damencho
965c811025 Disable dialpad button for now.
Disable the button, till we need it, UI is fixed and it is tested that everything works from jitsi-meet, through lib-jitsi-meet and through jigasi.
2017-04-14 11:44:09 -05:00
Ilya Daynatovich
adc2260b63 Add dialpad icon 2017-04-14 14:21:16 +03:00
yanas
4ef84054dc Merge pull request #1481 from virtuacoplenny/device-picker-settings
fix: open device selection if it is the only available setting
2017-04-13 13:46:08 -05:00
Leonard Kim
7db1c9b8eb fix: open device selection if it is the only available setting
Move logic to open device selection outside of SettingsMenu so
it can be called independently by either SettingsMenu or by
the settings button itself if no other settings but devices will
be displayed.
2017-04-13 11:43:06 -07:00
Lyubo Marinov
ae06a6ce41 Fix Recording regression caused by 'React Toolbar'
Saúl Ibarra Corretgé reported that Recording shows an error dialog
stating "There was an error connecting to your camera". Hristo Terezov
and Yana Stamcheva traced that the problem originates in
da4425b5c0
and, more specifically, is caused by a different order of execution due
to the move of the invocation of the function Recording.init.

The solution is to bring back the execution location of Recording.init.
2017-04-12 14:10:00 -05:00
bgrozev
0316450ee2 Merge pull request #1494 from saghul/doc-browserify
doc: remove mention to Browserify from README
2017-04-12 11:07:06 -05:00
Saúl Ibarra Corretgé
281305147b doc: remove mention to Browserify from README 2017-04-12 18:01:58 +02:00
Saúl Ibarra Corretgé
ef41e32af5 deps: fix ordering in package.json 2017-04-12 16:31:07 +02:00
Saúl Ibarra Corretgé
4ef8172f8d Merge pull request #1487 from jitsi/p2p_callstats
CallStats log level 'info'
2017-04-12 10:48:43 +02:00
Lyubo Marinov
5106f9f958 Process do_external_connect.js through webpack 2017-04-11 18:29:18 -05:00
yanas
2c61d8d94b Merge pull request #1486 from virtuacoplenny/lenny/modal-button-font
fix: set button font-size for modals
2017-04-11 16:18:16 -05:00
Lyubo Marinov
bc8c8c1bb9 Comply w/ coding style 2017-04-11 14:40:03 -05:00
Lyubo Marinov
cbc08eb96d Merge remote-tracking branch 'origin/race_conditions' into api_eslint 2017-04-11 14:31:05 -05:00
Lyubo Marinov
f4de65a647 Comply w/ coding style 2017-04-11 14:30:00 -05:00
hristoterezov
0f42f18100 ref(iframe_api): ESLint support for API.js 2017-04-11 13:31:07 -05:00
paweldomas
a9d9dc6658 log: CallStats log level 'info'
Reduces the log level for the CallStats module to 'info', because recent
changes are adding a lot of debug logs.
2017-04-11 13:11:20 -05:00
Leonard Kim
07cd6a8b88 fix: set button font-size for modals
Atlaskit at times will have localized styling for font-size and
sometimes will not. The button component will inherit its
font-size whereas selectors have localized font-size of 14px. For
consistency, the cancel/submit buttons on the atlaskit modals
will also have 14px. The atlaskit story book examples also use
buttons with 14px font-size.
2017-04-11 10:35:59 -07:00
hristoterezov
ab62690b97 fix(iframe_api): toggle audio/video race condition
If toggle audio or video is executed too early and the local
tracks don't exist we fail to execute the operation. Now we store
the mute state and we are executing it after the tracks are
created
2017-04-11 12:22:04 -05:00
hristoterezov
e7a3ee477d fix(frame_api): toggle SS race condition
If toggle SS is executed too early and lib-jitsi-meet is not yet
initialized toggle SS will fail. Now we are storing the whether
SS is on or off and when lib-jitsi-meet is ready we are starting
SS if needed.
2017-04-11 12:19:28 -05:00
Lyubo Marinov
1ec06f4bf0 React (Native) optimizations/performance
Remove toolbar button and icon style literals from the render method of
Toolbox.native.js.

Additionally, comply w/ coding style.
2017-04-11 12:00:41 -05:00
Saúl Ibarra Corretgé
849f93375c [RN] Use a handset icon for audio-only mode button 2017-04-11 16:15:33 +02:00
Lyubo Marinov
35ba6cef4e React (Native) optimizations/performance 2017-04-10 19:16:35 -05:00
Lyubo Marinov
b0d63dae16 Comply w/ coding style 2017-04-10 19:14:14 -05:00
Saúl Ibarra Corretgé
14d394aed8 [RN] Add workaround for broken border radius on Android
For images < 80 of size forder radius doesn't work properly (it looks like a
square with rounded corders), however, using a duble sized radius does the
trick. Go figure.
2017-04-10 19:13:47 -05:00
Saúl Ibarra Corretgé
50fea44ce2 [RN] Use rounded avatars in the film strip
Also move (native) avatar style to film-strip styles, since  that's where it
applies. This is analogous to how the large-view avatar is styled.
2017-04-10 19:13:40 -05:00
yanas
98004c2328 Merge pull request #1447 from virtuacoplenny/device-picker
New device selection modal
2017-04-10 17:31:59 -05:00
Leonard Kim
eb7dda85a1 feat: replace device selection in settings with button for modal
Cleanup existing logic for displaying and updating device
selection settings in the settings menu. In its place
is a button to open the device selection modal.
2017-04-10 13:30:00 -07:00
Leonard Kim
2f994b1227 feat: new device selection modal with previews
The Device Selection modal consists of:
- DeviceSelection, an overly smart component responsible for
  triggering stream creation and cleanup.
- DeviceSelector for selector elements.
- VideoInputPreview for displaying a video preview.
- AudioInputPreview for displaying a volume meter.
- AudioOutputPreview for a test sound output link.

Store changes include is primarily storing the list of
available devices in redux. Other app state has been left
alone for future refactoring.
2017-04-10 13:30:00 -07:00
Lyubo Marinov
a9bdde193d Approach consistent filmstrip naming
We seemed to be using the names "film strip" and "filmstrip" (and,
consequently, their source code-conscious forms such as film-strip,
FilmStrip, etc.) In order to comply with our coding style which requires
a consistent one name for a given abstraction, choose one name and
rename the uses of the other name.

Wikipedia has a definition of a "filmstrip", I couldn't find a "film
strip". I guess our abstraction can be seen as what's described there.
When I google "film strip", I get results about "filmstrip" at the top.
That's why I chose "filmstrip".

Certain uses of "film strip" such as interfaceConfig.filmStripOnly and
in the external API I left untouched in an attempt to preserve
compatibility.

I wasn't sure whether CSS was tangled in compatibility so I made a
choice and renamed there was well.
2017-04-10 12:59:44 -05:00
yanas
2ffef3bdda Fixes toolbar tooltip positioning 2017-04-10 09:37:10 -05:00
yanas
77b789e26a Implements a filmstrip-only mode for the toolbox 2017-04-10 09:36:25 -05:00
yanas
031f2dfeb8 Fixes showToolbar in filmstrip-only mode and renames some funcs 2017-04-10 09:31:35 -05:00
yanas
cb0eef9edd Fix(SideContainerToggler.js): Check if the component exists on init 2017-04-10 09:31:35 -05:00
yanas
8be85de6ef Changes scss variable name 2017-04-10 09:31:26 -05:00
Lyubo Marinov
9cf7f2b83d Update NPM dependencies/packages 2017-04-09 21:10:39 -05:00
Lyubo Marinov
95667ef98e Revert "[RN] Use rounded avatars in the film strip"
This reverts commit 739298c782.
2017-04-09 12:58:27 -05:00
Lyubo Marinov
b211ce02a8 [RN] Increment short app version from 1.3 to 1.4
Now that Apple have approved build 1.3.204 for release in the App Store,
the short app version needs to be incremented; otherwise, no new builds
can be uploaded to TestFlight and, respectively, for release in the App
Store.
2017-04-09 12:00:58 -05:00
Saúl Ibarra Corretgé
739298c782 [RN] Use rounded avatars in the film strip 2017-04-09 11:55:57 -05:00
Saúl Ibarra Corretgé
a1da6bff1a [RN] Fix loading config from non-default domains
When a conference is to happen in a domain which is not the defaut, its config
is loaded and set. As part of this process, lib-jitsi-meet is disposed. Because
disposing is asynchronous, events happen in this sequence:

- set new config
- dispose lib (which effectively wipes the config)
- init lib

This results in the library to be initialized without the loaded config, which
was lost. This commit fixes that by delaying setting the config and
re-initializing the library until it was disposed.
2017-04-07 14:54:32 -05:00
Saúl Ibarra Corretgé
18a81d7ca0 [RN] Fix passing config options when creating a conference
JitsiConnection.initJitsiConference doesn't automatically pass the global config
options, so grab the config from the Redux store and pass it.
2017-04-07 14:53:52 -05:00
yanas
6f15903019 Merge pull request #1474 from jitsi/fix-profile-sidebar-translation
Fixes profile panel translation.
2017-04-06 16:06:55 -05:00
damencho
a26f7a1292 Fixes profile panel translation.
Strings are not translated when opening the profile side panel on FF. It was that we were creating profile panel html after i18n library had loaded and had translated the rest of the html.
2017-04-06 15:21:04 -05:00
Saúl Ibarra Corretgé
ae8c5287e4 [RN] Remove workaround for video mirroring on iOS
It's now natively supported:
https://github.com/oney/react-native-webrtc/pull/244
2017-04-06 14:17:12 -05:00
Lyubo Marinov
fd10362bef Comply w/ coding style 2017-04-06 12:09:01 -05:00
Lyubo Marinov
3af6cc53d1 Explain _ and UPPER_CASE naming 2017-04-06 11:45:13 -05:00
Saúl Ibarra Corretgé
54bb5f1879 [RN] Add ability to share the URL for a conference 2017-04-06 00:24:26 -05:00
Saúl Ibarra Corretgé
13e3375e8a [RN] Use proximity sensor when in audio-only mode
When the audio-only mode is enabled, turn on the proximity sensor to dim the
screen and disable touch controls when there is an object nearby.
2017-04-05 22:06:30 -05:00
Saúl Ibarra Corretgé
37157dc9e2 [RN] Use _switchCameras provided by lib-jitsi-meet
The functionality to use the react-native-webrtc custom API for fast switching
cameras was moved to JitsiLocalTrack in lib-jitsi-meet. Use that.

Ref: https://github.com/jitsi/lib-jitsi-meet/pull/444
2017-04-05 21:01:00 -05:00
Saúl Ibarra Corretgé
8fe3dce649 [RN] Add audio only mode for conferences
The behavior can be triggered with the toggleAudioOnly action, which is
currently fired with a button.

The following aspects of the conference will change when in audio only mode:

- local video is muted
- last N is set to 0 (effectively muting remote video)
- full-screen mode is exited
- audio mode is set to "audio chat" (default output is the earpiece)
- the wake lock is disengaged

One aspect not handled in this patch is disabling the video mute button while in
audio only mode. The user should not be able to turn back video on in that case.
2017-04-05 15:07:34 -05:00
damencho
4ec4c45a90 Adds a second parameter named domain to muc_size module.
Adds and the default conference. part where the muc module live in default deployments.
2017-04-05 13:41:21 -05:00
Saúl Ibarra Corretgé
19f46ed4f0 Merge pull request #1460 from jitsi/iframe_api_commands
IFrame API improvements
2017-04-05 17:37:02 +02:00
Lyubo Marinov
0e9509ae9b Comply w/ coding style 2017-04-05 04:01:57 -05:00
Saúl Ibarra Corretgé
618dedc58e [RN] Use participant connection status events instead of last N
They better represent if a participant has video available or not. There are
cases when even a participant in the last N set would not have video because it
disconnected momentarily, for example.
2017-04-05 01:41:46 -05:00
Saúl Ibarra Corretgé
623b7a8d6f [RN] Show avatar if a participant is not in last N 2017-04-05 01:21:23 -05:00
Saúl Ibarra Corretgé
7c76f124bf [RN] Use native API for toggling cameras
Use the curstom _switchCamera API provided by react-native-webrtc to toggle the
camera instead of destroying the current track and creating a new one.

_switchCamera is implemented at a low level, so the track perceives no changes,
thus being a lot faster and less involved since the capturer doesn't need to be
destroyed and re-created.

In addition, don't mirror the video for the back camera.

Ref: https://github.com/oney/react-native-webrtc/pull/235
2017-04-05 00:21:35 -05:00
Saúl Ibarra Corretgé
f5973e0eee [RN] Fix toggling camera
When a new local video track is created an associated video capturer is created
for it. The cause for the freezes seems to be creating mutliple tracks (which
come with a video capturer each). Fix this by first disposing of the previous
video track before creating the new one.

Ref:
https://github.com/oney/react-native-webrtc/issues/209#issuecomment-281482869
2017-04-05 00:20:17 -05:00
Lyubo Marinov
32634356a6 Simplify naming 2017-04-05 00:20:17 -05:00
Lyubo Marinov
6d0a07a4cd Remove unnecessary source code
Lib-jitsi-meet does not really implement isScreenSharing. Besides,
getCameraFacingMode will already make sure that the video track does not
represent a desktop stream.
2017-04-05 00:20:17 -05:00
damencho
e0b829f92f Revert "Adds a second parameter named domain to muc_size module."
This reverts commit e2e04e3f16.
2017-04-04 18:45:58 -05:00
Lyubo Marinov
684572bd05 Comply w/ coding style 2017-04-04 17:52:06 -05:00
hristoterezov
334eb5d423 feat(iframe_api): Add more ESLint rules 2017-04-04 17:20:08 -05:00
Ilya Daynatovich
bcbdaaa6ea Fix interface_config.js/interfaceConfig overriding
It got broken while rewriting the Web toolbar in React Toolbox. There is
a problem with the toolbars and how we construct the intialState of the
buttons. The _getInitialState() in the toolbox reducer gets the list of
buttons from interfaceConfig, but in fact interfaceConfig is meant to be
overriden in several very important cases. One of the cases being the
external API, which we use in several projects in production.
2017-04-04 17:00:39 -05:00
Leonard Kim
986939e501 deps: re-add babel-polyfill as a dependency
babel does not modify existing builtins by default. That means
some newer methods, such as Array.prototype.includes, may not
be available unless babel-polyfill is used.
2017-04-04 16:54:59 -05:00
hristoterezov
d416fd8c0f ref(iframe_api): Use EventEmitter 2017-04-04 16:45:47 -05:00
hristoterezov
78119df2db ref(iframe_api): Use ES6 2017-04-04 16:45:47 -05:00
damencho
e2e04e3f16 Adds a second parameter named domain to muc_size module. 2017-04-04 15:15:18 -05:00
Lyubo Marinov
d37468975c Fix ESLint warnings
JSDoc comments didn't follow the ESLint rule for properly formatted
sentences.

BTW, I'm not blind to the fact that PasswordRequiredPrompt and
RoomLockPrompt participated in a birthing of source code through
copy+paste. (If we do not copy+paste, we will not have to fix one and
the same source code such as comments  multiple times.)
2017-04-04 13:47:35 -05:00
299 changed files with 12541 additions and 5116 deletions

View File

@@ -35,6 +35,11 @@ module.system=haste
experimental.strict_type_args=true
; FIXME: munge_underscores should be false but right now there are some errors
; if we change the value to false
; Treats class properties with underscore as private. Disabled because currently
; for us "_" can mean protected too.
; munge_underscores=false
munge_underscores=true
module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> 'RelativeImageStub'
@@ -46,6 +51,7 @@ suppress_type=$FixMe
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(3[0-8]\\|[1-2][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(3[0-8]\\|1[0-9]\\|[1-2][0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+
suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy
suppress_comment=\\(.\\|\n\\)*\\$FlowDisableNextLine
unsafe.enable_getters_and_setters=true

View File

@@ -8,6 +8,9 @@ node_modules/
# The following are checked by ESLint with the maximum configuration which
# supersedes JSHint.
flow-typed/
modules/API/
modules/remotecontrol/
modules/transport/
react/
# The following are checked by ESLint with the minimum configuration which does

View File

@@ -3,13 +3,9 @@ We would love to have your help. Before you start working however, please read
and follow this short guide.
# Reporting Issues
Before you open an issue on GitHub, please discuss it on one of our
[mailing lists](https://jitsi.org/Development/MailingLists) and wait for
confirmation from one of the committers. Once you have that confirmation,
please proceed to reporting the issue on GitHub, while providing as much
information as possible. Mention the version of Jitsi Meet, Jicofo and JVB
you are using, and explain (as detailed as you can) how the problem can
be reproduced.
Provide as much information as possible. Mention the version of Jitsi Meet,
Jicofo and JVB you are using, and explain (as detailed as you can) how the
problem can be reproduced.
# Code contributions
Found a bug and know how to fix it? Great! Please read on.

View File

@@ -2,3 +2,10 @@
* Notifies interested parties that hangup procedure will start.
*/
export const BEFORE_HANGUP = "conference.before_hangup";
/**
* Notifies interested parties that desktop sharing enable/disable state is
* changed.
*/
export const DESKTOP_SHARING_ENABLED_CHANGED
= "conference.desktop_sharing_enabled_changed";

View File

@@ -27,6 +27,8 @@ deploy-appbundle:
cp \
$(BUILD_DIR)/app.bundle.min.js \
$(BUILD_DIR)/app.bundle.min.map \
$(BUILD_DIR)/do_external_connect.min.js \
$(BUILD_DIR)/do_external_connect.min.map \
$(BUILD_DIR)/external_api.min.js \
$(BUILD_DIR)/external_api.min.map \
$(OUTPUT_DIR)/analytics.js \

View File

@@ -19,14 +19,15 @@ You can download Debian/Ubuntu binaries:
* [testing](https://download.jitsi.org/testing/) ([instructions](https://jitsi.org/Main/InstallJitsiMeetDebianTestingRepository))
* [nightly](https://download.jitsi.org/unstable/) ([instructions](https://jitsi.org/Main/InstallJitsiMeetDebianNightlyRepository))
You can download source archives (produced by ```make source-package```):
* [source builds](https://download.jitsi.org/jitsi-meet/src/)
You can get our mobile versions from here:
* [Android](https://play.google.com/store/apps/details?id=org.jitsi.meet)
* [iOS](https://itunes.apple.com/us/app/jitsi-meet/id1165103905)
## Building the sources
Jitsi Meet uses [Browserify](http://browserify.org). If you want to make changes in the code you need to [install Browserify](http://browserify.org/#install). Browserify requires [nodejs](http://nodejs.org).
On Debian/Ubuntu systems, the required packages can be installed with:
```
sudo apt-get install npm nodejs-legacy
@@ -99,9 +100,6 @@ Jitsi Meet provides a very flexible way of embedding it in external applications
Jitsi Meet is also available as a React Native application for Android and iOS.
Instructions on how to build it can be found [here](doc/mobile.md).
## Discuss
Please use the [Jitsi dev mailing list](http://lists.jitsi.org/pipermail/dev/) to discuss feature requests before opening an issue on Github.
## Acknowledgements
Jitsi Meet started out as a sample conferencing application using Jitsi Videobridge. It was originally developed by then ESTOS' developer Philipp Hancke who then contributed it to the community where development continues with joint forces!

View File

@@ -91,7 +91,7 @@ android {
minSdkVersion 16
targetSdkVersion 22
versionCode Integer.parseInt("${version}")
versionName "1.3.${version}"
versionName "1.4.${version}"
ndk {
abiFilters 'armeabi-v7a', 'x86'
}

View File

@@ -11,12 +11,13 @@
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus"/>
<uses-sdk
android:minSdkVersion="19"
android:minSdkVersion="16"
android:targetSdkVersion="23" />
<application

View File

@@ -32,7 +32,8 @@ public class MainApplication extends Application implements ReactApplication {
new com.ocetnik.timer.BackgroundTimerPackage(),
new com.oney.WebRTCModule.WebRTCModulePackage(),
new com.rnimmersive.RNImmersivePackage(),
new org.jitsi.meet.audiomode.AudioModePackage()
new org.jitsi.meet.audiomode.AudioModePackage(),
new org.jitsi.meet.proximity.ProximityPackage()
);
}
};

View File

@@ -188,13 +188,24 @@ public class AudioModeModule extends ReactContextBaseJavaModule {
Runnable r = new Runnable() {
@Override
public void run() {
if (updateAudioRoute(mode)) {
boolean success;
try {
success = updateAudioRoute(mode);
} catch (Throwable e) {
success = false;
Log.e(
TAG,
"Failed to update audio route for mode: " + mode,
e);
}
if (success) {
AudioModeModule.this.mode = mode;
promise.resolve(null);
} else {
promise.reject(
"setMode",
"Failed to set the requested audio mode");
"Failed to set audio mode to " + mode);
}
}
};

View File

@@ -0,0 +1,101 @@
package org.jitsi.meet.proximity;
import android.content.Context;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.UiThreadUtil;
/**
* Module implementing a simple API to enable a proximity sensor-controlled
* wake lock. When the lock is held, if the proximity sensor detects a nearby
* object it will dim the screen and disable touch controls. The functionality
* is used with the conference audio-only mode.
*/
public class ProximityModule extends ReactContextBaseJavaModule {
/**
* React Native module name.
*/
private static final String MODULE_NAME = "Proximity";
/**
* This type of wake lock (the one activated by the proximity sensor) has
* been available for a while, but the constant was only exported in API
* level 21 (Android Marshmallow) so make no assumptions and use its value
* directly.
*
* TODO: Remove when we bump the API level to 21.
*/
private static final int PROXIMITY_SCREEN_OFF_WAKE_LOCK = 32;
/**
* {@link WakeLock} instance.
*/
private final WakeLock wakeLock;
/**
* Initializes a new module instance. There shall be a single instance of
* this module throughout the lifetime of the application.
*
* @param reactContext The {@link ReactApplicationContext} where this module
* is created.
*/
public ProximityModule(ReactApplicationContext reactContext) {
super(reactContext);
WakeLock wakeLock;
PowerManager powerManager
= (PowerManager)
reactContext.getSystemService(Context.POWER_SERVICE);
try {
wakeLock
= powerManager.newWakeLock(
PROXIMITY_SCREEN_OFF_WAKE_LOCK,
MODULE_NAME);
} catch (Throwable ignored) {
wakeLock = null;
}
this.wakeLock = wakeLock;
}
/**
* Gets the name of this module to be used in the React Native bridge.
*
* @return The name of this module to be used in the React Native bridge.
*/
@Override
public String getName() {
return MODULE_NAME;
}
/**
* Acquires / releases the proximity sensor wake lock.
*
* @param enabled {@code true} to enable the proximity sensor; otherwise,
* {@code false}.
*/
@ReactMethod
public void setEnabled(final boolean enabled) {
if (wakeLock == null) {
return;
}
UiThreadUtil.runOnUiThread(new Runnable() {
@Override
public void run() {
if (enabled) {
if (!wakeLock.isHeld()) {
wakeLock.acquire();
}
} else if (wakeLock.isHeld()) {
wakeLock.release();
}
}
});
}
}

View File

@@ -0,0 +1,48 @@
package org.jitsi.meet.proximity;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Implements {@link ReactPackage} for {@link ProximityModule}.
*/
public class ProximityPackage implements ReactPackage {
/**
* {@inheritDoc}
*/
@Override
public List<Class<? extends JavaScriptModule>> createJSModules() {
return Collections.emptyList();
}
/**
* {@inheritDoc}
*
* @return List of native modules to be exposed by React Native.
*/
@Override
public List<NativeModule> createNativeModules(
ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();
modules.add(new ProximityModule(reactContext));
return modules;
}
/**
* {@inheritDoc}
*/
@Override
public List<ViewManager> createViewManagers(
ReactApplicationContext reactContext) {
return Collections.emptyList();
}
}

View File

@@ -1,4 +1,4 @@
rootProject.name = 'jitsi-meet-react'
rootProject.name = 'jitsi-meet'
include ':app'
include ':react-native-background-timer'

2
app.js
View File

@@ -18,7 +18,7 @@ window.toastr = require("toastr");
import UI from "./modules/UI/UI";
import settings from "./modules/settings/Settings";
import conference from './conference';
import API from './modules/API/API';
import API from './modules/API';
import translation from "./modules/translation/translation";
import remoteControl from "./modules/remotecontrol/RemoteControl";

View File

@@ -2,7 +2,6 @@
const logger = require("jitsi-meet-logger").getLogger(__filename);
import {openConnection} from './connection';
import Invite from './modules/UI/invite/Invite';
import ContactList from './modules/UI/side_pannels/contactlist/ContactList';
import AuthHandler from './modules/UI/authentication/AuthHandler';
@@ -20,20 +19,24 @@ import analytics from './modules/analytics/analytics';
import EventEmitter from "events";
import { showDesktopSharingButton } from './react/features/toolbox';
import { getLocationContextRoot } from './react/features/app';
import {
AVATAR_ID_COMMAND,
AVATAR_URL_COMMAND,
conferenceFailed,
conferenceJoined,
conferenceLeft,
EMAIL_COMMAND
EMAIL_COMMAND,
lockStateChanged
} from './react/features/base/conference';
import {
updateDeviceList
} from './react/features/base/devices';
import {
isFatalJitsiConnectionError
} from './react/features/base/lib-jitsi-meet';
import {
localParticipantRoleChanged,
participantJoined,
participantLeft,
participantRoleChanged,
@@ -46,6 +49,7 @@ import {
mediaPermissionPromptVisibilityChanged,
suspendDetected
} from './react/features/overlay';
import { showDesktopSharingButton } from './react/features/toolbox';
const ConnectionEvents = JitsiMeetJS.events.connection;
const ConnectionErrors = JitsiMeetJS.errors.connection;
@@ -60,7 +64,10 @@ const ConnectionQualityEvents = JitsiMeetJS.events.connectionQuality;
const eventEmitter = new EventEmitter();
let room, connection, localAudio, localVideo;
let room;
let connection;
let localAudio, localVideo;
let initialAudioMutedState = false, initialVideoMutedState = false;
/**
* Indicates whether extension external installation is in progress or not.
@@ -173,7 +180,7 @@ function createInitialLocalTracksAndConnect(roomName) {
* @param command the command
* @param {string} value new value
*/
function sendData (command, value) {
function sendData(command, value) {
if(!room) {
return;
}
@@ -210,7 +217,7 @@ function _setupLocalParticipantProperties() {
* @param {string} id user id
* @returns {string?} user nickname or undefined if user is unknown.
*/
function getDisplayName (id) {
function getDisplayName(id) {
if (APP.conference.isLocalId(id)) {
return APP.settings.getDisplayName();
}
@@ -227,7 +234,7 @@ function getDisplayName (id) {
* @param {boolean} userInteraction - indicates if this local audio mute was a
* result of user interaction
*/
function muteLocalAudio (muted) {
function muteLocalAudio(muted) {
muteLocalMedia(localAudio, muted, 'Audio');
}
@@ -247,7 +254,7 @@ function muteLocalMedia(localMedia, muted, localMediaTypeString) {
* Mute or unmute local video stream if it exists.
* @param {boolean} muted if video stream should be muted or unmuted.
*/
function muteLocalVideo (muted) {
function muteLocalVideo(muted) {
muteLocalMedia(localVideo, muted, 'Video');
}
@@ -266,9 +273,11 @@ function muteLocalVideo (muted) {
function maybeRedirectToWelcomePage(options) {
// if close page is enabled redirect to it, without further action
if (config.enableClosePage) {
const { isGuest } = APP.store.getState()['features/jwt'];
// save whether current user is guest or not, before navigating
// to close page
window.sessionStorage.setItem('guest', APP.tokenData.isGuest);
window.sessionStorage.setItem('guest', isGuest);
assignWindowLocationPathname('static/'
+ (options.feedbackSubmitted ? "close.html" : "close2.html"));
return;
@@ -302,18 +311,11 @@ function assignWindowLocationPathname(pathname) {
const windowLocation = window.location;
if (!pathname.startsWith('/')) {
// XXX To support a deployment in a sub-directory, assume that the room
// (name) is the last non-directory component of the path (name).
let contextRoot = windowLocation.pathname;
contextRoot
= contextRoot.substring(0, contextRoot.lastIndexOf('/') + 1);
// A pathname equal to ./ specifies the current directory. It will be
// fine but pointless to include it because contextRoot is the current
// directory.
pathname.startsWith('./') && (pathname = pathname.substring(2));
pathname = contextRoot + pathname;
pathname = getLocationContextRoot(windowLocation) + pathname;
}
windowLocation.pathname = pathname;
@@ -332,7 +334,7 @@ function assignWindowLocationPathname(pathname) {
* for gUM permission prompt
* @returns {Promise<JitsiLocalTrack[]>}
*/
function createLocalTracks (options, checkForPermissionPrompt) {
function createLocalTracks(options, checkForPermissionPrompt) {
options || (options = {});
return JitsiMeetJS
@@ -366,10 +368,9 @@ function createLocalTracks (options, checkForPermissionPrompt) {
}
class ConferenceConnector {
constructor(resolve, reject, invite) {
constructor(resolve, reject) {
this._resolve = resolve;
this._reject = reject;
this._invite = invite;
this.reconnectTimeout = null;
room.on(ConferenceEvents.CONFERENCE_JOINED,
this._handleConferenceJoined.bind(this));
@@ -403,15 +404,15 @@ class ConferenceConnector {
break;
// not enough rights to create conference
case ConferenceErrors.AUTHENTICATION_REQUIRED:
// schedule reconnect to check if someone else created the room
this.reconnectTimeout = setTimeout(function () {
room.join();
}, 5000);
case ConferenceErrors.AUTHENTICATION_REQUIRED: {
// Schedule reconnect to check if someone else created the room.
this.reconnectTimeout = setTimeout(() => room.join(), 5000);
// notify user that auth is required
AuthHandler.requireAuth(
room, this._invite.getRoomLocker().password);
const { password }
= APP.store.getState()['features/base/conference'];
AuthHandler.requireAuth(room, password);
}
break;
case ConferenceErrors.RESERVATION_ERROR:
@@ -578,6 +579,12 @@ export default {
analytics.init();
return createInitialLocalTracksAndConnect(options.roomName);
}).then(([tracks, con]) => {
tracks.forEach(track => {
if((track.isAudioTrack() && initialAudioMutedState)
|| (track.isVideoTrack() && initialVideoMutedState)) {
track.mute();
}
});
logger.log('initialized with %s local tracks', tracks.length);
con.addEventListener(
ConnectionEvents.CONNECTION_FAILED,
@@ -585,11 +592,14 @@ export default {
APP.connection = connection = con;
this.isDesktopSharingEnabled =
JitsiMeetJS.isDesktopSharingEnabled();
eventEmitter.emit(
JitsiMeetConferenceEvents.DESKTOP_SHARING_ENABLED_CHANGED,
this.isDesktopSharingEnabled);
APP.store.dispatch(showDesktopSharingButton());
APP.remoteControl.init();
this._createRoom(tracks);
APP.remoteControl.init();
if (UIUtil.isButtonEnabled('contacts')
&& !interfaceConfig.filmStripOnly) {
@@ -614,8 +624,7 @@ export default {
// XXX The API will take care of disconnecting from the XMPP
// server (and, thus, leaving the room) on unload.
return new Promise((resolve, reject) => {
(new ConferenceConnector(
resolve, reject, this.invite)).connect();
(new ConferenceConnector(resolve, reject)).connect();
});
});
},
@@ -624,14 +633,14 @@ export default {
* @param {string} id id to check
* @returns {boolean}
*/
isLocalId (id) {
isLocalId(id) {
return this.getMyUserId() === id;
},
/**
* Simulates toolbar button click for audio mute. Used by shortcuts and API.
* @param mute true for mute and false for unmute.
*/
muteAudio (mute) {
muteAudio(mute) {
muteLocalAudio(mute);
},
/**
@@ -642,36 +651,51 @@ export default {
return this.audioMuted;
},
/**
* Simulates toolbar button click for audio mute. Used by shortcuts and API.
* Simulates toolbar button click for audio mute. Used by shortcuts
* and API.
* @param {boolean} force - If the track is not created, the operation
* will be executed after the track is created. Otherwise the operation
* will be ignored.
*/
toggleAudioMuted () {
toggleAudioMuted(force = false) {
if(!localAudio && force) {
initialAudioMutedState = !initialAudioMutedState;
return;
}
this.muteAudio(!this.audioMuted);
},
/**
* Simulates toolbar button click for video mute. Used by shortcuts and API.
* @param mute true for mute and false for unmute.
*/
muteVideo (mute) {
muteVideo(mute) {
muteLocalVideo(mute);
},
/**
* Simulates toolbar button click for video mute. Used by shortcuts and API.
* @param {boolean} force - If the track is not created, the operation
* will be executed after the track is created. Otherwise the operation
* will be ignored.
*/
toggleVideoMuted () {
toggleVideoMuted(force = false) {
if(!localVideo && force) {
initialVideoMutedState = !initialVideoMutedState;
return;
}
this.muteVideo(!this.videoMuted);
},
/**
* Retrieve list of conference participants (without local user).
* @returns {JitsiParticipant[]}
*/
listMembers () {
listMembers() {
return room.getParticipants();
},
/**
* Retrieve list of ids of conference participants (without local user).
* @returns {string[]}
*/
listMembersIds () {
listMembersIds() {
return room.getParticipants().map(p => p.getId());
},
/**
@@ -679,7 +703,7 @@ export default {
* @id id to search for participant
* @return {boolean} whether the participant is moderator
*/
isParticipantModerator (id) {
isParticipantModerator(id) {
let user = room.getParticipantById(id);
return user && user.isModerator();
},
@@ -687,10 +711,10 @@ export default {
* Check if SIP is supported.
* @returns {boolean}
*/
sipGatewayEnabled () {
sipGatewayEnabled() {
return room.isSIPCallingSupported();
},
get membersCount () {
get membersCount() {
return room.getParticipants().length + 1;
},
/**
@@ -700,7 +724,7 @@ export default {
* @returns true if the callstats integration is enabled, otherwise returns
* false.
*/
isCallstatsEnabled () {
isCallstatsEnabled() {
return room && room.isCallstatsEnabled();
},
/**
@@ -710,7 +734,7 @@ export default {
* user feedback
* @param detailedFeedback detailed feedback from the user. Not yet used
*/
sendFeedback (overallFeedback, detailedFeedback) {
sendFeedback(overallFeedback, detailedFeedback) {
return room.sendFeedback (overallFeedback, detailedFeedback);
},
@@ -728,15 +752,15 @@ export default {
/**
* Returns the connection times stored in the library.
*/
getConnectionTimes () {
getConnectionTimes() {
return this._room.getConnectionTimes();
},
// used by torture currently
isJoined () {
isJoined() {
return this._room
&& this._room.isJoined();
},
getConnectionState () {
getConnectionState() {
return this._room
&& this._room.getConnectionState();
},
@@ -745,7 +769,7 @@ export default {
* @return {string|null} ICE connection state or <tt>null</tt> if there's no
* P2P connection
*/
getP2PConnectionState () {
getP2PConnectionState() {
return this._room
&& this._room.getP2PConnectionState();
},
@@ -753,7 +777,7 @@ export default {
* Starts P2P (for tests only)
* @private
*/
_startP2P () {
_startP2P() {
try {
this._room && this._room.startP2PSession();
} catch (error) {
@@ -765,7 +789,7 @@ export default {
* Stops P2P (for tests only)
* @private
*/
_stopP2P () {
_stopP2P() {
try {
this._room && this._room.stopP2PSession();
} catch (error) {
@@ -780,7 +804,7 @@ export default {
* @returns {boolean} true if the connection is in interrupted state or
* false otherwise.
*/
isConnectionInterrupted () {
isConnectionInterrupted() {
return this._room.isConnectionInterrupted();
},
/**
@@ -791,7 +815,7 @@ export default {
* @returns {JitsiParticipant|null} participant instance for given id or
* null if not found.
*/
getParticipantById (id) {
getParticipantById(id) {
return room ? room.getParticipantById(id) : null;
},
/**
@@ -802,10 +826,9 @@ export default {
* @returns {ParticipantConnectionStatus|null} the status of the participant
* or null if no such participant is found or participant is the local user.
*/
getParticipantConnectionStatus (id) {
getParticipantConnectionStatus(id) {
let participant = this.getParticipantById(id);
return participant
? participant.getConnectionStatus() : null;
return participant ? participant.getConnectionStatus() : null;
},
/**
* Gets the display name foe the <tt>JitsiParticipant</tt> identified by
@@ -816,7 +839,7 @@ export default {
* @return {string} the participant's display name or the default string if
* absent.
*/
getParticipantDisplayName (id) {
getParticipantDisplayName(id) {
let displayName = getDisplayName(id);
if (displayName) {
return displayName;
@@ -829,7 +852,7 @@ export default {
}
}
},
getMyUserId () {
getMyUserId() {
return this._room
&& this._room.myUserId();
},
@@ -855,7 +878,7 @@ export default {
* @param id the id for the user audio level to return (the id value is
* returned for the participant using getMyUserId() method)
*/
getPeerSSRCAudioLevel (id) {
getPeerSSRCAudioLevel(id) {
return this.audioLevelsMap[id];
},
/**
@@ -875,7 +898,7 @@ export default {
},
// end used by torture
getLogs () {
getLogs() {
return room.getLogs();
},
@@ -884,7 +907,7 @@ export default {
* debugging.
* @param filename (optional) specify target filename
*/
saveLogs (filename = 'meetlog.json') {
saveLogs(filename = 'meetlog.json') {
// this can be called from console and will not have reference to this
// that's why we reference the global var
let logs = APP.conference.getLogs();
@@ -955,7 +978,6 @@ export default {
room = connection.initJitsiConference(APP.conference.roomName,
this._getConferenceOptions());
this._setLocalAudioVideoStreams(localTracks);
this.invite = new Invite(room);
this._room = room; // FIXME do not use this
_setupLocalParticipantProperties();
@@ -994,13 +1016,13 @@ export default {
},
/**
* Start using provided video stream.
* Stops previous video stream.
* @param {JitsiLocalTrack} [stream] new stream to use or null
* @returns {Promise}
*/
useVideoStream (newStream) {
/**
* Start using provided video stream.
* Stops previous video stream.
* @param {JitsiLocalTrack} [stream] new stream to use or null
* @returns {Promise}
*/
useVideoStream(newStream) {
return room.replaceTrack(localVideo, newStream)
.then(() => {
// We call dispose after doing the replace because
@@ -1035,7 +1057,7 @@ export default {
* @param {JitsiLocalTrack} [stream] new stream to use or null
* @returns {Promise}
*/
useAudioStream (newStream) {
useAudioStream(newStream) {
return room.replaceTrack(localAudio, newStream)
.then(() => {
// We call dispose after doing the replace because
@@ -1058,8 +1080,46 @@ export default {
});
},
/**
* Triggers a tooltip to display when a feature was attempted to be used
* while in audio only mode.
*
* @param {string} featureName - The name of the feature that attempted to
* toggle.
* @private
* @returns {void}
*/
_displayAudioOnlyTooltip(featureName) {
let tooltipElementId = null;
switch (featureName) {
case 'screenShare':
tooltipElementId = '#screenshareWhileAudioOnly';
break;
case 'videoMute':
tooltipElementId = '#unmuteWhileAudioOnly';
break;
}
if (tooltipElementId) {
APP.UI.showToolbar(6000);
APP.UI.showCustomToolbarPopup(
tooltipElementId, true, 5000);
}
},
/**
* Returns whether or not the conference is currently in audio only mode.
*
* @returns {boolean}
*/
isAudioOnly() {
return Boolean(
APP.store.getState()['features/base/conference'].audioOnly);
},
videoSwitchInProgress: false,
toggleScreenSharing (shareScreen = !this.isSharingScreen) {
toggleScreenSharing(shareScreen = !this.isSharingScreen) {
if (this.videoSwitchInProgress) {
logger.warn("Switch in progress.");
return;
@@ -1069,6 +1129,11 @@ export default {
return;
}
if (this.isAudioOnly()) {
this._displayAudioOnlyTooltip('screenShare');
return;
}
this.videoSwitchInProgress = true;
let externalInstallation = false;
@@ -1182,7 +1247,7 @@ export default {
/**
* Setup interaction between conference and UI.
*/
_setupListeners () {
_setupListeners() {
// add local streams when joined to the conference
room.on(ConferenceEvents.CONFERENCE_JOINED, () => {
APP.store.dispatch(conferenceJoined(room));
@@ -1230,16 +1295,26 @@ export default {
APP.UI.onSharedVideoStop(id);
});
room.on(ConferenceEvents.USER_STATUS_CHANGED, (id, status) => {
let user = room.getParticipantById(id);
if (user) {
APP.UI.updateUserStatus(user, status);
}
});
room.on(ConferenceEvents.USER_ROLE_CHANGED, (id, role) => {
APP.store.dispatch(participantRoleChanged(id, role));
if (this.isLocalId(id)) {
logger.info(`My role changed, new role: ${role}`);
APP.store.dispatch(localParticipantRoleChanged(role));
if (this.isModerator !== room.isModerator()) {
this.isModerator = room.isModerator();
APP.UI.updateLocalRole(room.isModerator());
}
} else {
APP.store.dispatch(participantRoleChanged(id, role));
let user = room.getParticipantById(id);
if (user) {
APP.UI.updateUserRole(user);
@@ -1336,10 +1411,15 @@ export default {
room.on(ConferenceEvents.CONNECTION_RESTORED, () => {
APP.UI.markVideoInterrupted(false);
});
room.on(ConferenceEvents.MESSAGE_RECEIVED, (id, text, ts) => {
room.on(ConferenceEvents.MESSAGE_RECEIVED, (id, body, ts) => {
let nick = getDisplayName(id);
APP.API.notifyReceivedChatMessage(id, nick, text, ts);
APP.UI.addMessage(id, nick, text, ts);
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);
@@ -1363,6 +1443,10 @@ export default {
}
});
APP.UI.addListener(
UIEvents.VIDEO_UNMUTING_WHILE_AUDIO_ONLY,
() => this._displayAudioOnlyTooltip('videoMute'));
APP.UI.addListener(UIEvents.PINNED_ENDPOINT,
(smallVideo, isPinned) => {
let smallVideoId = smallVideo.getId();
@@ -1405,10 +1489,18 @@ export default {
room.on(ConferenceEvents.DISPLAY_NAME_CHANGED, (id, displayName) => {
const formattedDisplayName
= displayName.substr(0, MAX_DISPLAY_NAME_LENGTH);
APP.store.dispatch(participantUpdated({
id,
name: formattedDisplayName
}));
APP.API.notifyDisplayNameChanged(id, formattedDisplayName);
APP.UI.changeDisplayName(id, formattedDisplayName);
});
room.on(
ConferenceEvents.LOCK_STATE_CHANGED,
(...args) => APP.store.dispatch(lockStateChanged(room, ...args)));
room.on(ConferenceEvents.PARTICIPANT_PROPERTY_CHANGED,
(participant, name, oldValue, newValue) => {
if (name === "raisedHand") {
@@ -1467,7 +1559,14 @@ export default {
});
APP.UI.addListener(UIEvents.AUDIO_MUTED, muteLocalAudio);
APP.UI.addListener(UIEvents.VIDEO_MUTED, muteLocalVideo);
APP.UI.addListener(UIEvents.VIDEO_MUTED, muted => {
if (this.isAudioOnly() && !muted) {
this._displayAudioOnlyTooltip('videoMute');
return;
}
muteLocalVideo(muted);
});
room.on(ConnectionQualityEvents.LOCAL_STATS_UPDATED,
(stats) => {
@@ -1558,10 +1657,6 @@ export default {
});
});
APP.UI.addListener(UIEvents.SIP_DIAL, (sipNumber) => {
room.dial(sipNumber);
});
APP.UI.addListener(UIEvents.RESOLUTION_CHANGED,
(id, oldResolution, newResolution, delay) => {
var logObject = {
@@ -1616,13 +1711,20 @@ export default {
micDeviceId: null
})
.then(([stream]) => {
if (this.isAudioOnly()) {
return stream.mute()
.then(() => stream);
}
return stream;
})
.then(stream => {
this.useVideoStream(stream);
logger.log('switched local video device');
APP.settings.setCameraDeviceId(cameraDeviceId, true);
})
.catch((err) => {
APP.UI.showDeviceErrorDialog(null, err);
APP.UI.setSelectedCameraFromSettings();
});
}
);
@@ -1644,7 +1746,6 @@ export default {
})
.catch((err) => {
APP.UI.showDeviceErrorDialog(err, null);
APP.UI.setSelectedMicFromSettings();
});
}
);
@@ -1660,11 +1761,22 @@ export default {
logger.warn('Failed to change audio output device. ' +
'Default or previously set audio output device ' +
'will be used instead.', err);
APP.UI.setSelectedAudioOutputFromSettings();
});
}
);
APP.UI.addListener(UIEvents.TOGGLE_AUDIO_ONLY, audioOnly => {
muteLocalVideo(audioOnly);
// Immediately update the UI by having remote videos and the large
// video update themselves instead of waiting for some other event
// to cause the update, usually PARTICIPANT_CONN_STATUS_CHANGED.
// There is no guarantee another event will trigger the update
// immediately and in all situations, for example because a remote
// participant is having connection trouble so no status changes.
APP.UI.updateAllVideos();
});
APP.UI.addListener(
UIEvents.TOGGLE_SCREENSHARING, this.toggleScreenSharing.bind(this)
);
@@ -1756,8 +1868,8 @@ export default {
}
mediaDeviceHelper.setCurrentMediaDevices(devices);
APP.UI.onAvailableDevicesChanged(devices);
APP.store.dispatch(updateDeviceList(devices));
});
this.deviceChangeListener = (devices) =>
@@ -1890,7 +2002,7 @@ export default {
* @param {boolean} [requestFeedback=false] if user feedback should be
* requested
*/
hangup (requestFeedback = false) {
hangup(requestFeedback = false) {
eventEmitter.emit(JitsiMeetConferenceEvents.BEFORE_HANGUP);
APP.UI.hideRingOverLay();
let requestFeedbackPromise = requestFeedback
@@ -1968,7 +2080,7 @@ export default {
* @throws NetworkError or InvalidStateError or Error if the operation
* fails.
*/
sendEndpointMessage (to, payload) {
sendEndpointMessage(to, payload) {
room.sendEndpointMessage(to, payload);
},
@@ -1977,7 +2089,7 @@ export default {
* @param {String} eventName the name of the event
* @param {Function} listener the listener.
*/
addListener (eventName, listener) {
addListener(eventName, listener) {
eventEmitter.addListener(eventName, listener);
},
@@ -1987,7 +2099,7 @@ export default {
* listener
* @param {Function} listener the listener.
*/
removeListener (eventName, listener) {
removeListener(eventName, listener) {
eventEmitter.removeListener(eventName, listener);
},
@@ -2000,7 +2112,7 @@ export default {
* is currently in the last N set or if there's no last N set at this point
* and {false} otherwise
*/
isInLastN (participantId) {
isInLastN(participantId) {
return room.isInLastN(participantId);
},
/**
@@ -2015,9 +2127,16 @@ export default {
return;
}
APP.store.dispatch(participantUpdated({
id: this.getMyUserId(),
local: true,
name: formattedNickname
}));
APP.settings.setDisplayName(formattedNickname);
room.setDisplayName(formattedNickname);
APP.UI.changeDisplayName(this.getMyUserId(),
formattedNickname);
if (room) {
room.setDisplayName(formattedNickname);
APP.UI.changeDisplayName(this.getMyUserId(), formattedNickname);
}
}
};

View File

@@ -41,7 +41,7 @@ var config = { // eslint-disable-line no-unused-vars
// extension is required.
desktopSharingFirefoxExtId: null,
// Whether desktop sharing should be disabled on Firefox.
desktopSharingFirefoxDisabled: true,
desktopSharingFirefoxDisabled: false,
// The maximum version of Firefox which requires a jidesha extension.
// Example: if set to 41, we will require the extension for Firefox versions
// up to and including 41. On Firefox 42 and higher, we will run without the
@@ -65,7 +65,6 @@ var config = { // eslint-disable-line no-unused-vars
//enableClosePage: false, // enabling the close page will ignore the welcome
// page redirection when call is hangup
disableSimulcast: false,
logStats: false, // Enable logging of PeerConnection stats via the focus
// requireDisplayName: true, // Forces the participants that doesn't have display name to enter it when they enter the room.
// startAudioMuted: 10, // every participant after the Nth will start audio muted
// startVideoMuted: 10, // every participant after the Nth will start video muted
@@ -77,6 +76,8 @@ var config = { // eslint-disable-line no-unused-vars
'During that time service will not be available. ' +
'Apologise for inconvenience.',*/
disableThirdPartyRequests: false,
// The minumum value a video's height (or width, whichever is smaller) needs
// to be in order to be considered high-definition.
minHDHeight: 540,
// If true - all users without token will be considered guests and all users
// with token will be considered non-guests. Only guests will be allowed to

View File

@@ -1,5 +1,4 @@
/* global APP, JitsiMeetJS, config */
const logger = require("jitsi-meet-logger").getLogger(__filename);
import AuthHandler from './modules/UI/authentication/AuthHandler';
import jitsiLocalStorage from './modules/util/JitsiLocalStorage';
@@ -14,6 +13,7 @@ import {
const ConnectionEvents = JitsiMeetJS.events.connection;
const ConnectionErrors = JitsiMeetJS.errors.connection;
const logger = require("jitsi-meet-logger").getLogger(__filename);
/**
* Checks if we have data to use attach instead of connect. If we have the data
@@ -61,22 +61,27 @@ function checkForAttachParametersAndConnect(id, password, connection) {
* everything is ok, else error.
*/
function connect(id, password, roomName) {
let connectionConfig = Object.assign({}, config);
const connectionConfig = Object.assign({}, config);
const { issuer, jwt } = APP.store.getState()['features/jwt'];
connectionConfig.bosh += '?room=' + roomName;
let connection
= new JitsiMeetJS.JitsiConnection(null, config.token, connectionConfig);
= new JitsiMeetJS.JitsiConnection(
null,
jwt && issuer && issuer !== 'anonymous' ? jwt : undefined,
connectionConfig);
return new Promise(function (resolve, reject) {
connection.addEventListener(
ConnectionEvents.CONNECTION_ESTABLISHED, handleConnectionEstablished
);
ConnectionEvents.CONNECTION_ESTABLISHED,
handleConnectionEstablished);
connection.addEventListener(
ConnectionEvents.CONNECTION_FAILED, handleConnectionFailed
);
ConnectionEvents.CONNECTION_FAILED,
handleConnectionFailed);
connection.addEventListener(
ConnectionEvents.CONNECTION_FAILED, connectionFailedHandler);
ConnectionEvents.CONNECTION_FAILED,
connectionFailedHandler);
function connectionFailedHandler(error, errMsg) {
APP.store.dispatch(connectionFailed(connection, error, errMsg));
@@ -91,12 +96,10 @@ function connect(id, password, roomName) {
function unsubscribe() {
connection.removeEventListener(
ConnectionEvents.CONNECTION_ESTABLISHED,
handleConnectionEstablished
);
handleConnectionEstablished);
connection.removeEventListener(
ConnectionEvents.CONNECTION_FAILED,
handleConnectionFailed
);
handleConnectionFailed);
}
function handleConnectionEstablished() {
@@ -129,7 +132,6 @@ function connect(id, password, roomName) {
* @returns {Promise<JitsiConnection>}
*/
export function openConnection({id, password, retry, roomName}) {
let usernameOverride
= jitsiLocalStorage.getItem("xmpp_username_override");
let passwordOverride
@@ -138,25 +140,20 @@ export function openConnection({id, password, retry, roomName}) {
if (usernameOverride && usernameOverride.length > 0) {
id = usernameOverride;
}
if (passwordOverride && passwordOverride.length > 0) {
password = passwordOverride;
}
return connect(id, password, roomName).catch(function (err) {
if (!retry) {
throw err;
}
return connect(id, password, roomName).catch(err => {
if (retry) {
const { issuer, jwt } = APP.store.getState()['features/jwt'];
if (err === ConnectionErrors.PASSWORD_REQUIRED) {
// do not retry if token is not valid
if (config.token) {
throw err;
} else {
if (err === ConnectionErrors.PASSWORD_REQUIRED
&& (!jwt || issuer === 'anonymous')) {
return AuthHandler.requestAuth(roomName, connect);
}
} else {
throw err;
}
throw err;
});
}

View File

@@ -0,0 +1,3 @@
module.exports = {
'extends': '../react/.eslintrc.js'
};

View File

@@ -1,75 +1,82 @@
/* global config, getRoomName, getConfigParamsFromUrl */
/* global createConnectionExternally */
/* global config, createConnectionExternally */
import getRoomName from '../react/features/base/config/getRoomName';
import parseURLParams from '../react/features/base/config/parseURLParams';
/**
* Implements extrnal connect using createConnectionExtenally function defined
* in external_connect.js for Jitsi Meet. Parses the room name and token from
* the url and executes createConnectionExtenally.
* Implements external connect using createConnectionExternally function defined
* in external_connect.js for Jitsi Meet. Parses the room name and JSON Web
* Token (JWT) from the URL and executes createConnectionExternally.
*
* NOTE: If you are using lib-jitsi-meet without Jitsi Meet you should use this
* file as reference only because the implementation is Jitsi Meet specific.
* NOTE: If you are using lib-jitsi-meet without Jitsi Meet, you should use this
* file as reference only because the implementation is Jitsi Meet-specific.
*
* NOTE: For optimal results this file should be included right after
* exrnal_connect.js.
* external_connect.js.
*/
if (typeof createConnectionExternally === 'function') {
// URL params have higher proirity than config params.
let url
= parseURLParams(window.location, true, 'hash')[
'config.externalConnectUrl']
|| config.externalConnectUrl;
let roomName;
if (url && (roomName = getRoomName())) {
url += `?room=${roomName}`;
const token = parseURLParams(window.location, true, 'search').jwt;
if (token) {
url += `&token=${token}`;
}
createConnectionExternally(
url,
connectionInfo => {
// Sets that global variable to be used later by connect method
// in connection.js.
window.XMPPAttachInfo = {
status: 'success',
data: connectionInfo
};
checkForConnectHandlerAndConnect();
},
errorCallback);
} else {
errorCallback();
}
} else {
errorCallback();
}
/**
* Executes createConnectionExternally function.
* Check if connect from connection.js was executed and executes the handler
* that is going to finish the connect work.
*
* @returns {void}
*/
(function () {
var hashParams = getConfigParamsFromUrl("hash", true);
var searchParams = getConfigParamsFromUrl("search", true);
function checkForConnectHandlerAndConnect() {
window.APP
&& window.APP.connect.status === 'ready'
&& window.APP.connect.handler();
}
//Url params have higher proirity than config params
var url = config.externalConnectUrl;
if(hashParams.hasOwnProperty('config.externalConnectUrl'))
url = hashParams["config.externalConnectUrl"];
/**
* Implements a callback to be invoked if anything goes wrong.
*
* @param {Error} error - The specifics of what went wrong.
* @returns {void}
*/
function errorCallback(error) {
// The value of error is undefined if external connect is disabled.
error && console.warn(error);
/**
* Check if connect from connection.js was executed and executes the handler
* that is going to finish the connect work.
*/
function checkForConnectHandlerAndConnect() {
if(window.APP && window.APP.connect.status === "ready") {
window.APP.connect.handler();
}
}
function error_callback(error){
if(error) //error=undefined if external connect is disabled.
console.warn(error);
// Sets that global variable to be used later by connect method in
// connection.js
window.XMPPAttachInfo = {
status: "error"
};
checkForConnectHandlerAndConnect();
}
if(!url || !window.createConnectionExternally) {
error_callback();
return;
}
var room_name = getRoomName();
if(!room_name) {
error_callback();
return;
}
url += "?room=" + room_name;
var token = hashParams["config.token"] || config.token ||
searchParams.jwt;
if(token)
url += "&token=" + token;
createConnectionExternally(url, function(connectionInfo) {
// Sets that global variable to be used later by connect method in
// connection.js
window.XMPPAttachInfo = {
status: "success",
data: connectionInfo
};
checkForConnectHandlerAndConnect();
}, error_callback);
})();
// Sets that global variable to be used later by connect method in
// connection.js.
window.XMPPAttachInfo = {
status: 'error'
};
checkForConnectHandlerAndConnect();
}

View File

@@ -1,4 +1,9 @@
* {
/**
* Safari will limit input in input elements to one character when user-select
* none is applied. Other browsers already support selecting within inputs while
* user-select is none. As such, disallow user-select except on inputs.
*/
*:not(input) {
-webkit-user-select: none;
user-select: none;
}

View File

@@ -1,31 +0,0 @@
.settingsContent {
display: flex;
display: -webkit-flex;
#localVideoPreview {
width: 50%;
align-self: baseline;
}
.deviceSelection {
display: flex;
display: -webkit-flex;
-webkit-flex: 1;
flex: 1;
flex-direction: column;
flex-wrap: nowrap;
justify-content: flex-start;
align-items: left;
margin-left: 10px;
.device {
display: flex;
margin-bottom: 5px;
select {
flex: 1;
margin_right: 5px;
}
}
}
}

64
css/_dial-out.scss Normal file
View File

@@ -0,0 +1,64 @@
/**
* The dialog content element.
*/
.dial-out-content {
margin-top: 5px;
/**
* The style of the flag icon.
*/
.dial-out-flag-icon {
position: absolute;
left: 5px;
top: 10px;
}
/**
* The style of the dial code element.
*/
.dial-out-code {
padding-left: 25px !important;
}
/**
* The dial-out dialog error element.
*/
.dial-out-error {
color: $errorColor;
}
/**
* The style of the dial input element.
*/
.dial-out-input {
padding-left: 70px;
}
/**
* Re-styling the default dropdown inside the dial-out-content.
*/
.dropdown {
left: $formPadding;
position: absolute !important;
width: 65px
}
/**
* Re-styling the default form-control inside the dial-out-content.
*/
.form-control {
padding-bottom: 8px !important;
}
.dropdown {
display: inline-block;
position: relative;
overflow: hidden;
}
.dropdown-trigger-icon {
position: absolute;
right: 0;
top: 4px;
}
}

View File

@@ -17,8 +17,8 @@
flex-direction: column-reverse;
flex-wrap: nowrap;
position: relative;
z-index: $zindex1; // Set z-index to make element visible
width: $hideFilmstripButtonWidth;
z-index: $zindex1; // Set z-index to make element visible.
width: $filmstripToggleButtonWidth;
button {
font-size: 14px;
@@ -50,7 +50,7 @@
position:relative;
height:196px;
padding: 0;
/*The filmstrip should not be covered by the left toolbar*/
/* The filmstrip should not be covered by the left toolbar. */
padding-left: $defaultToolbarSize + 5;
bottom: 0;
width:auto;
@@ -58,8 +58,8 @@
z-index: $filmstripVideosZ;
transition: bottom 2s;
overflow: visible !important;
/*!!!Removes the gap between the local video container and the remote
videos.*/
/*!!! Removes the gap between the local video container and the remote
videos. */
font-size: 0pt;
&.hidden {
@@ -79,8 +79,8 @@
}
/**
* Focused video thumbnail.
*/
* Focused video thumbnail.
*/
&.videoContainerFocused {
transition-duration: 0.5s;
-webkit-transition-duration: 0.5s;
@@ -97,8 +97,8 @@
}
/**
* Hovered video thumbnail.
*/
* Hovered video thumbnail.
*/
&:hover {
cursor: hand;
border: $thumbnailVideoBorder solid $videoThumbnailHovered;
@@ -110,7 +110,7 @@
}
}
/* With TemasysWebRTC plugin <object/> element is used
/* With the TemasysWebRTC plugin <object/> element is used
instead of <video/> */
& > video,
& > object {
@@ -121,4 +121,13 @@
}
}
}
/**
* Style the filmstrip videos in filmstrip-only mode.
*/
&__videos-filmstripOnly {
margin-top: auto;
margin-bottom: auto;
padding-right: $defaultToolbarSize;
}
}

35
css/_flag-icon.scss Executable file
View File

@@ -0,0 +1,35 @@
.flag-icon-background {
background-size: contain;
background-position: 50%;
background-repeat: no-repeat;
}
.flag-icon {
background-size: contain;
background-position: 50%;
background-repeat: no-repeat;
position: relative;
display: inline-block;
width: 1.33333333em;
line-height: 1em;
}
.flag-icon:before {
content: "\00a0";
}
.flag-icon-au {
background-image: url(../images/countries/au.svg);
}
.flag-icon-ca {
background-image: url(../images/countries/ca.svg);
}
.flag-icon-de {
background-image: url(../images/countries/de.svg);
}
.flag-icon-gb {
background-image: url(../images/countries/gb.svg);
}
.flag-icon-fr {
background-image: url(../images/countries/fr.svg);
}
.flag-icon-us {
background-image: url(../images/countries/us.svg);
}

View File

@@ -26,116 +26,125 @@
}
.icon-mic-camera-combined:before {
content: "\e903";
content: "\e903";
}
.icon-feedback:before {
content: "\e91d";
content: "\e91d";
}
.icon-toggle-filmstrip:before {
content: "\e91c";
content: "\e91c";
}
.icon-avatar:before {
content: "\e901";
content: "\e901";
}
.icon-hangup:before {
content: "\e905";
content: "\e905";
}
.icon-chat:before {
content: "\e906";
content: "\e906";
}
.icon-download:before {
content: "\e902";
content: "\e902";
}
.icon-edit:before {
content: "\e907";
content: "\e907";
}
.icon-share-doc:before {
content: "\e908";
}
.icon-telephone:before {
content: "\e909";
content: "\e908";
}
.icon-kick:before {
content: "\e904";
content: "\e904";
}
.icon-menu-up:before {
content: "\e91f";
content: "\e91f";
}
.icon-menu-down:before {
content: "\e920";
content: "\e920";
}
.icon-full-screen:before {
content: "\e90b";
content: "\e90b";
}
.icon-exit-full-screen:before {
content: "\e90c";
content: "\e90c";
}
.icon-star-full:before {
content: "\e90a";
content: "\e90a";
}
.icon-security:before {
content: "\e90d";
content: "\e90d";
}
.icon-security-locked:before {
content: "\e90e";
content: "\e90e";
}
.icon-reload:before {
content: "\e90f";
content: "\e90f";
}
.icon-microphone:before {
content: "\e910";
content: "\e910";
}
.icon-mic-empty:before {
content: "\e911";
content: "\e911";
}
.icon-mic-disabled:before {
content: "\e912";
content: "\e912";
}
.icon-raised-hand:before {
content: "\e91e";
content: "\e91e";
}
.icon-contactList:before {
content: "\e91b";
content: "\e91b";
}
.icon-link:before {
content: "\e913";
content: "\e913";
}
.icon-shared-video:before {
content: "\e914";
content: "\e914";
}
.icon-settings:before {
content: "\e915";
content: "\e915";
}
.icon-star:before {
content: "\e916";
content: "\e916";
}
.icon-switch-camera:before {
content: "\e921";
content: "\e921";
}
.icon-share-desktop:before {
content: "\e917";
content: "\e917";
}
.icon-camera:before {
content: "\e918";
content: "\e918";
}
.icon-camera-disabled:before {
content: "\e919";
content: "\e919";
}
.icon-volume:before {
content: "\e91a";
content: "\e91a";
}
.icon-connection-lost:before {
content: "\e900";
content: "\e900";
}
.icon-connection:before {
content: "\e61a";
content: "\e61a";
}
.icon-recDisable:before {
content: "\e613";
content: "\e613";
}
.icon-recEnable:before {
content: "\e614";
content: "\e614";
}
.icon-presentation:before {
content: "\e603";
}
content: "\e603";
}
.icon-dialpad:before {
content: "\e925";
}
.icon-visibility:before {
content: "\e923";
}
.icon-visibility-off:before {
content: "\e924";
}
.icon-telephone:before {
content: "\e0cd";
}

View File

@@ -93,6 +93,7 @@
}
&__avatar-container {
height: 100%;
position: relative;
> img {
height: 100%;

View File

@@ -41,21 +41,37 @@
}
}
&__text,
&__slider {
&__text {
display: inline-block;
vertical-align: middle;
}
&__slider {
width: 50px;
&__contents {
display: flex;
/**
* Positioning styles on the slider and its container are used to make
* the container fit the popup width, by removing the slider from the
* page flow, and then making the slider fit the container.
*/
.popupmenu__slider_container {
position: relative;
width: 100%;
.popupmenu__slider {
bottom: 50%;
position: absolute;
top: 50%;
width: 100%;
}
}
}
&__icon {
vertical-align: middle;
position: relative;
display: inline-block;
width: 20px;
min-width: 20px;
height: 100%;
text-align: center;

View File

@@ -113,6 +113,12 @@
text-align: center;
}
#deviceOptionsWrapper {
button {
float: none;
}
}
/**
* Profile
*/

View File

@@ -94,7 +94,7 @@
#toast-container.notification-bottom-right {
$videoOffset: 2 * ($thumbnailVideoMargin + $thumbnailsBorder) + $thumbnailVideoBorder;
bottom: 135px;
right: $hideFilmstripButtonWidth + $videoOffset;
right: $filmstripToggleButtonWidth + $videoOffset;
}
#toast-container * {

View File

@@ -19,9 +19,9 @@
vertical-align: middle;
}
/**
* Toolbar button styles.
*/
/**
* Toolbar button styles.
*/
.button {
color: #FFFFFF;
cursor: pointer;
@@ -71,6 +71,10 @@
&.icon-microphone {
@extend .icon-mic-disabled;
}
&.icon-visibility {
@extend .icon-visibility-off;
}
}
&.unclickable {
@@ -97,8 +101,8 @@
}
/**
* Common toolbar styles.
*/
* Common toolbar styles.
*/
.toolbar {
background-color: $toolbarBackground;
height: 100%;
@@ -119,8 +123,8 @@
}
/**
* Primary toolbar styles.
*/
* Primary toolbar styles.
*/
&_primary {
position: absolute;
left: 50%;
@@ -148,8 +152,8 @@
}
/**
* Secondary toolbar styles.
*/
* Secondary toolbar styles.
*/
&_secondary {
position: absolute;
align-items: center;
@@ -170,7 +174,7 @@
width: $defaultToolbarSize;
-webkit-transform: translateX(-100%);
.button.toggled:not(.icon-raised-hand) {
.button.toggled:not(.icon-raised-hand):not(.button-active) {
background: $toolbarSelectBackground;
cursor: pointer;
text-decoration: none;
@@ -186,6 +190,28 @@
}
}
/**
* Styles the toolbar in filmstrip-only mode.
*/
&_filmstrip-only {
border-radius: 3px;
bottom: 0;
display: inline-block;
height: auto;
position: absolute;
right: 0;
width: $defaultToolbarSize;
.button:first-child {
border-top-left-radius: 3px;
border-top-right-radius: 3px;
}
.button:last-child {
border-bottom-right-radius: 3px;
border-bottom-left-radius: 3px;
}
}
/**
* Toolbar specific round badge.
*/

View File

@@ -18,7 +18,7 @@ $thumbnailIndicatorSize: $thumbnailToolbarHeight;
$thumbnailVideoMargin: 2px;
$thumbnailsBorder: 2px;
$thumbnailVideoBorder: 2px;
$hideFilmstripButtonWidth: 17px;
$filmstripToggleButtonWidth: 17px;
/**
@@ -79,6 +79,14 @@ $rateStarDefault: #ccc;
$rateStarActivity: #165ecc;
$rateStarSize: 34px;
/**
* Modals
*/
$modalButtonFontSize: 14px;
$modalMockAKInputBackground: #fafbfc;
$modalMockAKInputBorder: 1px solid #f4f5f7;
$modalTextColor: #333;
/**
* Notifications
*/
@@ -119,11 +127,11 @@ $toolbarZ: 400;
$tooltipsZ: 401;
$dropdownMaskZ: 900;
$dropdownZ: 901;
$overlayZ: 902;
$jitsipopoverZ: 1010;
$centeredVideoLabelZ: 1011;
$notificationZ: 1012;
$popoverZ: 1015;
$overlayZ: 1016;
/**
@@ -141,6 +149,7 @@ $inputControlEmColor: #f29424;
//buttons
$linkFontColor: #489afe;
$linkHoverFontColor: #287ade;
$formPadding: 16px;
/**
* Unsupported browser

View File

@@ -115,6 +115,12 @@
visibility: hidden;
z-index: $zindex2;
}
&.audio-only {
.videoThumbnailProblemFilter {
filter: none;
}
}
}
#localVideoWrapper {
@@ -489,14 +495,23 @@
0px 0px 1px rgba(0,0,0,0.3);
}
.audio-only-label {
display: flex;
height: auto;
justify-content: center;
z-index: $centeredVideoLabelZ;
}
.audio-only-label,
.video-state-indicator {
background: $videoStateIndicatorBackground;
color: $videoStateIndicatorColor;
cursor: default;
font-size: 13px;
height: 40px;
line-height: 20px;
text-align: center;
min-width: 40px;
height: 40px;
padding: 10px 5px;
border-radius: 50%;
position: absolute;
@@ -505,13 +520,13 @@
#videoResolutionLabel,
.centeredVideoLabel {
display: none;
z-index: $centeredVideoLabelZ;
}
.centeredVideoLabel {
bottom: 45%;
border-radius: 2px;
display: none;
-webkit-transition: all 2s 2s linear;
transition: all 2s 2s linear;
@@ -528,4 +543,58 @@
.moveToCorner + .moveToCorner {
right: 80px;
}
}
.video-state-indicator-menu {
display: none;
padding: 10px;
position: absolute;
right: -10px;
top: 20px;
.video-state-indicator-menu-options {
background: $popoverBg;
border-radius: 3px;
color: $popoverFontColor;
margin-top: 20px;
padding: 5px 0;
position: relative;
div {
cursor: pointer;
padding: 10px;
padding-right: 30px;
text-align: left;
white-space: nowrap;
&.active {
background: $toolbarToggleBackground;
}
&:hover:not(.active) {
background: $popupMenuSelectedItemBackground;
}
i {
margin-right: 5px;
vertical-align: middle;
}
}
}
.video-state-indicator-menu-options::after {
content: " ";
border-color: transparent transparent $popoverBg transparent;
border-style: solid;
border-width: 5px;
position: absolute;
right: 15px;
top: -10px;
}
}
.video-state-indicator:hover,
.video-state-indicator *:hover {
.video-state-indicator-menu {
display: block;
}
}

View File

@@ -1,5 +1,5 @@
.form-control {
padding: 16px 0;
padding: $formPadding 0;
&:first-child {
padding-top: 0;

View File

@@ -26,11 +26,13 @@
@import 'font';
@import 'font-awesome';
/* Fonts END */
@import 'flag-icon';
/* Modules BEGIN */
@import 'dial-out';
@import 'toastr';
@import 'base';
@import 'utils';
@@ -38,6 +40,7 @@
@import 'inlay';
@import 'reload_overlay/reload_overlay';
@import 'modals/desktop-picker/desktop-picker';
@import 'modals/device-selection/device-selection';
@import 'modals/dialog';
@import 'modals/feedback/feedback';
@import 'modals/speaker_stats/speaker_stats';
@@ -54,7 +57,6 @@
@import 'welcome_page';
@import 'toolbars';
@import 'side_toolbar_container';
@import 'device_settings_dialog';
@import 'jquery.contextMenu';
@import 'keyboard-shortcuts';
@import 'redirect_page';

View File

@@ -76,3 +76,16 @@
border-bottom: 1px solid $auiBorderColor;
}
}
.modal-dialog-form {
color: $modalTextColor;
.input-control {
background: $modalMockAKInputBackground;
border: $modalMockAKInputBorder;
color: inherit;
}
}
.modal-dialog-footer {
font-size: $modalButtonFontSize;
}

View File

@@ -0,0 +1,132 @@
.device-selection {
color: $feedbackInputTextColor;
.device-selectors {
font-size: 14px;
> div {
display: block;
margin-bottom: 10px;
}
> div:last-child {
margin-bottom: 5px;
}
.device-selector-icon {
align-self: center;
color: inherit;
font-size: 20px;
margin-left: 3px;
}
/* device-selector-trigger stylings attempt to mimic AtlasKit button */
.device-selector-trigger {
background-color: rgba(9, 30, 66, 0.04);
border-radius: 3px;
color: #505f79;
display: flex;
height: 2.3em;
justify-content: space-between;
line-height: 2.3em;
overflow: hidden;
padding: 0 8px;
&:hover {
background-color: rgba(9,30,66,.08);
}
}
.device-selector-trigger-disabled {
.device-selector-trigger {
color: #a5adba;
cursor: default;
}
}
.device-selector-trigger-text {
overflow: hidden;
margin-left: 8px;
text-overflow: ellipsis;
white-space: nowrap;
width: 100%;
}
}
.device-selection-column {
box-sizing: border-box;
display: inline-block;
vertical-align: top;
&.column-selectors {
margin-left: 15px;
width: 45%;
}
&.column-video {
width: 50%;
}
}
.device-selection-video-container {
border-radius: 3px;
margin-bottom: 5px;
.video-input-preview {
margin-top: 2px;
position: relative;
> video {
border-radius: 3px;
}
.video-input-preview-error {
color: $participantNameColor;
display: none;
left: 0;
position: absolute;
right: 0;
text-align: center;
top: 50%;
}
&.video-preview-has-error {
background: black;
.video-input-preview-error {
display: block;
}
}
.video-input-preview-display {
height: auto;
overflow: hidden;
width: 100%;
}
}
}
.audio-output-preview {
font-size: 14px;
margin-top: 10px;
a {
cursor: pointer;
text-decoration: none;
}
}
.audio-input-preview {
background: #f4f5f7;
border-radius: 5px;
height: 6px;
.audio-input-preview-level {
background: #0052cc;
border-radius: 5px;
height: 100%;
-webkit-transition: width .1s ease-in-out;
-moz-transition: width .1s ease-in-out;
-o-transition: width .1s ease-in-out;
transition: width .1s ease-in-out;
}
}
}

View File

@@ -4,4 +4,84 @@
*/
#inviteDialogRemovePassword {
cursor: hand;
}
}
.invite-dialog {
.dial-in-numbers {
.dial-in-numbers-conference-id {
color: orange;
margin-left: 3px;
}
/*
* dial-in-numbers-copy styling is needed for the feature of copying
* text to the clipboard. The styling keeps the element invisible
* to the user but still programmatically selectable for copying.
*/
.dial-in-numbers-copy {
opacity: 0;
pointer-events: none;
position: fixed;
-webkit-user-select: text;
user-select: text;
}
.dial-in-numbers-trigger {
position: relative;
width: 100%;
.dial-in-numbers-trigger-icon {
position: absolute;
right: 0;
top: 4px;
}
}
.is-disabled,
.is-loading {
.dial-in-numbers-trigger-icon {
display: none;
}
}
}
.form-control {
padding: 0;
}
.inviteLink {
color: $readOnlyInputColor;
}
.lock-state {
display: flex;
}
.password-overview {
margin-top: 10px;
.form-control {
margin-top: 10px;
}
.password-overview-status,
.remove-password {
display: flex;
justify-content: space-between;
}
.password-overview-toggle-edit,
.remove-password-link {
cursor: pointer;
text-decoration: none;
}
.remove-password {
margin-top: 15px;
}
}
.remove-password-current {
color: $inputControlEmColor;
}
}

View File

@@ -1,7 +1,6 @@
.speaker-stats {
list-style: none;
padding: 0;
color: $auiDialogColor;
width: 100%;
font-weight: 500;

View File

@@ -9,14 +9,14 @@
z-index: $overlayZ;
background: $defaultBackground;
&.filmstrip-only {
@include transparentBg($filmStripOnlyOverlayBg, 0.8);
@include transparentBg($filmstripOnlyOverlayBg, 0.8);
}
}
&__container-light {
@include transparentBg($defaultBackground, 0.7);
&.filmstrip-only {
@include transparentBg($filmStripOnlyOverlayBg, 0.2);
@include transparentBg($filmstripOnlyOverlayBg, 0.2);
}
}

View File

@@ -41,7 +41,7 @@ $overlayButtonBg: #0074E0;
* Color variables
**/
$defaultBackground: #474747;
$filmStripOnlyOverlayBg: #000;
$filmstripOnlyOverlayBg: #000;
$reloadProgressBarBg: #0074E0;
/**

View File

@@ -48,7 +48,9 @@ case "$1" in
db_stop
if [ -f "$PROSODY_HOST_CONFIG" ] ; then
if grep -q "plugin_paths" "$PROSODY_HOST_CONFIG"; then
# search for --plugin_paths, if this is not enabled this is the
# first time we install tokens package and needs a config change
if grep -q "\-\-plugin_paths" "$PROSODY_HOST_CONFIG"; then
# enable tokens in prosody host config
sed -i 's/--plugin_paths/plugin_paths/g' $PROSODY_HOST_CONFIG
sed -i 's/authentication = "anonymous"/authentication = "token"/g' $PROSODY_HOST_CONFIG
@@ -70,8 +72,6 @@ case "$1" in
echo "Use the following command, after this package has been installed and"
echo "after every prosody-trunk upgrade:"
echo "sudo patch -N /usr/lib/prosody/modules/mod_bosh.lua /usr/share/jitsi-meet/prosody-plugins/mod_bosh.lua.patch"
else
echo "Failed apply auto-config to $PROSODY_HOST_CONFIG which most likely comes from not supported version of jitsi-meet"
fi
else
echo "Prosody config not found at $PROSODY_HOST_CONFIG - unable to auto-configure token authentication"

View File

@@ -44,7 +44,7 @@ var api = new JitsiMeetExternalAPI(domain, room, width, height, htmlElement);
```
You can overwrite options set in [config.js] and [interface_config.js].
For example, to enable the film-strip-only interface mode, you can use:
For example, to enable the filmstrip-only interface mode, you can use:
```javascript
var interfaceConfigOverwrite = {filmStripOnly: true};
@@ -84,7 +84,7 @@ api.executeCommand('toggleAudio')
api.executeCommand('toggleVideo')
```
* **toggleFilmStrip** - Hides / shows the film strip. No arguments are required.
* **toggleFilmStrip** - Hides / shows the filmstrip. No arguments are required.
```javascript
api.executeCommand('toggleFilmStrip')
```
@@ -123,12 +123,13 @@ You can also execute multiple commands using the `executeCommands` method:
```javascript
api.executeCommands(commands)
```
The `commands` parameter is an object with the names of the commands as keys and the arguments for the commands asvalues:
The `commands` parameter is an object with the names of the commands as keys and the arguments for the commands as values:
```javascript
api.executeCommands({displayName: ['nickname'], toggleAudio: []});
```
You can add event listeners to the embedded Jitsi Meet using the `addEventListener` method.
**NOTE: This method still exists but it is deprecated. JitsiMeetExternalAPI class extends [EventEmitter]. Use [EventEmitter] methods (`addListener` or `on`).**
```javascript
api.addEventListener(event, listener)
```
@@ -198,6 +199,7 @@ changes. The listener will receive an object with the following structure:
You can also add multiple event listeners by using `addEventListeners`.
This method requires one argument of type Object. The object argument must
have the names of the events as keys and the listeners of the events as values.
**NOTE: This method still exists but it is deprecated. JitsiMeetExternalAPI class extends [EventEmitter]. Use [EventEmitter] methods.**
```javascript
function incomingMessageListener(object)
@@ -216,12 +218,13 @@ api.addEventListeners({
```
If you want to remove a listener you can use `removeEventListener` method with argument the name of the event.
**NOTE: This method still exists but it is deprecated. JitsiMeetExternalAPI class extends [EventEmitter]. Use [EventEmitter] methods( `removeListener`).**
```javascript
api.removeEventListener("incomingMessage");
```
If you want to remove more than one event you can use `removeEventListeners` method with an Array with the names of the events as an argument.
**NOTE: This method still exists but it is deprecated. JitsiMeetExternalAPI class extends [EventEmitter]. Use [EventEmitter] methods.**
```javascript
api.removeEventListeners(["incomingMessage", "outgoingMessageListener"]);
```
@@ -240,3 +243,4 @@ NOTE: It's a good practice to remove the conference before the page is unloaded.
[config.js]: https://github.com/jitsi/jitsi-meet/blob/master/config.js
[interface_config.js]: https://github.com/jitsi/jitsi-meet/blob/master/interface_config.js
[EventEmitter]: https://nodejs.org/api/events.html

View File

@@ -76,9 +76,18 @@
projects. For example:
* The instance of lib-jitsi-meet's `JitsiConnection` type should be named
`connection` or `jitsiConnection` in jitsi-meet-react, not `client`.
`connection` or `jitsiConnection` in jitsi-meet, not `client`.
* The class `ReducerRegistry` should be defined in ReducerRegistry.js and its
imports in other files should use the same name. Don't define the class
`Registry` in ReducerRegistry.js and then import it as `Reducers` in other
files.
* The names of global constants (including ES6 module-global constants) should
be written in uppercase with underscores to separate words. For example,
`BACKGROUND_COLOR`.
* The underscore character at the beginning of a name signals that the
respective variable, function, property is non-public i.e. private, protected,
or internal. In contrast, the lack of an underscore at the beginning of a name
signals public API.

View File

@@ -23,7 +23,10 @@ VirtualHost "jitmeet.example.com"
"ping"; -- Enable mod_ping
}
c2s_require_encryption = false
Component "conference.jitmeet.example.com" "muc"
storage = "null"
--modules_enabled = { "token_verification" }
admins = { "focusUser@auth.jitmeet.example.com" }

View File

@@ -179,6 +179,8 @@ VirtualHost "jitsi.example.com"
certificate = "/var/lib/prosody/jitsi.example.com.crt";
}
c2s_require_encryption = false
------ Components ------
-- You can specify components to add hosts that provide special services,
-- like multi-user conferences, and transports.

View File

@@ -55,6 +55,7 @@ VirtualHost "jitsi.example.com"
"bosh";
"pubsub";
}
c2s_require_encryption = false
```
- add domain with authentication for conference focus user:
```
@@ -237,23 +238,4 @@ You are now all set and ready to have your first meet by going to http://jitsi.e
## Enabling recording
Currently recording is only supported for linux-64 and macos. To enable it, add
the following properties to sip-communicator.properties:
```
org.jitsi.videobridge.ENABLE_MEDIA_RECORDING=true
org.jitsi.videobridge.MEDIA_RECORDING_PATH=/path/to/recordings/dir
org.jitsi.videobridge.MEDIA_RECORDING_TOKEN=secret
```
where /path/to/recordings/dir is the path to a pre-existing directory where recordings
will be stored (needs to be writeable by the user running jitsi-videobridge),
and "secret" is a string which will be used for authentication.
Then, edit the Jitsi-Meet config.js file and set:
```
enableRecording: true
```
Restart jitsi-videobridge and start a new conference (making sure that the page
is reloaded with the new config.js) -- the organizer of the conference should
now have a "recording" button in the floating menu, near the "mute" button.
[Jibri](https://github.com/jitsi/jibri)is a set of tools for recording and/or streaming a Jitsi Meet conference.

View File

@@ -43,8 +43,8 @@ work properly with the native plugins we require.
Using Xcode
- Open **ios/jitsi-meet-react.xcworkspace** in Xcode. Make sure it's the
workspace file!
- Open **ios/jitsi-meet.xcworkspace** in Xcode. Make sure it's the workspace
file!
- Select your device from the top bar and hit the "play" button.

View File

@@ -30,6 +30,17 @@ During the installation, you will be asked to enter the hostname of the Jitsi Me
This hostname (or IP address) will be used for virtualhost configuration inside the Jitsi Meet and also, you and your correspondents will be using it to access the web conferences.
#### Advanced configuration
If installation is on a machine behind NAT further configuration of jitsi-videobridge is needed in order for it to be accessible.
Provided that all required ports are routed (forwarded) to the machine that it runs on. By default these ports are (TCP/443 or TCP/4443 and UDP 10000).
The following extra lines need to be added the file `/etc/jitsi/videobridge/sip-communicator.properties`:
```
org.ice4j.ice.harvest.NAT_HARVESTER_LOCAL_ADDRESS=<Local.IP.Address>
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.
### Open a conference
Launch a web browser (Chrome, Chromium or latest Opera) and enter in the URL bar the hostname (or IP address) you used in the previous step.
@@ -63,7 +74,7 @@ Enjoy!
## Uninstall
```sh
apt-get purge jigasi jitsi-meet jicofo jitsi-videobridge
apt-get purge jigasi jitsi-meet jitsi-meet-web-config jitsi-meet-web jicofo jitsi-videobridge
```
Sometimes the following packages will fail to uninstall properly:

View File

@@ -1,36 +0,0 @@
// flow-typed signature: 57cf34196930be78935a42e5c8ac3cb6
// flow-typed version: ae6284e7b7/react-i18next_v2.x.x/flow_>=v0.36.x_<=v0.39.x
declare module 'react-i18next' {
declare type TFunction = (key?: ?string, data?: ?Object) => string;
declare type Locales = string | Array<string>;
declare type StatelessComponent<P> = (props: P) => ?React$Element<any>;
declare type Comp<P> = StatelessComponent<P> | Class<React$Component<*, P, *>>;
declare type Translator<OP, P> = {
(component: StatelessComponent<P>): Class<React$Component<void, OP, void>>;
<Def, St>(component: Class<React$Component<Def, P, St>>): Class<React$Component<Def, OP, St>>;
}
declare function translate<OP, P>(locales: Locales): Translator<OP, P>;
declare type NamespacesProps = {
components: Array<Comp<*>>,
i18n: { loadNamespaces: Function },
};
declare function loadNamespaces(props: NamespacesProps): Promise<void>;
declare type ProviderProps = { i18n: Object, children: React$Element<any> };
declare var I18nextProvider: Class<React$Component<void, ProviderProps, void>>;
declare type InterpolateProps = {
children?: React$Element<any>,
className?: string,
};
declare var Interpolate: Class<React$Component<void, InterpolateProps, void>>;
}

View File

@@ -1,5 +1,5 @@
// flow-typed signature: ba132c96664f1a05288f3eb2272a3c35
// flow-typed version: c4bbd91cfc/redux_v3.x.x/flow_>=v0.33.x
// flow-typed signature: 7f1a115f75043c44385071ea3f33c586
// flow-typed version: 358375125e/redux_v3.x.x/flow_>=v0.33.x
declare module 'redux' {
@@ -27,6 +27,8 @@ declare module 'redux' {
declare type Reducer<S, A> = (state: S, action: A) => S;
declare type CombinedReducer<S, A> = (state: $Shape<S> & {} | void, action: A) => S;
declare type Middleware<S, A> =
(api: MiddlewareAPI<S, A>) =>
(next: Dispatch<A>) => Dispatch<A>;
@@ -49,7 +51,7 @@ declare module 'redux' {
declare function bindActionCreators<A, C: ActionCreator<A, any>>(actionCreator: C, dispatch: Dispatch<A>): C;
declare function bindActionCreators<A, K, C: ActionCreators<K, A>>(actionCreators: C, dispatch: Dispatch<A>): C;
declare function combineReducers<O: Object, A>(reducers: O): Reducer<$ObjMap<O, <S>(r: Reducer<S, any>) => S>, A>;
declare function combineReducers<O: Object, A>(reducers: O): CombinedReducer<$ObjMap<O, <S>(r: Reducer<S, any>) => S>, A>;
declare function compose<S, A>(...fns: Array<StoreEnhancer<S, A>>): Function;

BIN
fonts/jitsi.eot Normal file → Executable file

Binary file not shown.

5
fonts/jitsi.svg Normal file → Executable file
View File

@@ -7,6 +7,7 @@
<font-face units-per-em="1024" ascent="1024" descent="0" />
<missing-glyph horiz-adv-x="1024" />
<glyph unicode="&#x20;" d="" />
<glyph unicode="&#xe0cd;" glyph-name="phone" d="M282 564c62-120 162-220 282-282l94 94c12 12 30 16 44 10 48-16 100-24 152-24 24 0 42-18 42-42v-150c0-24-18-42-42-42-400 0-726 326-726 726 0 24 18 42 42 42h150c24 0 42-18 42-42 0-54 8-104 24-152 4-14 2-32-10-44z" />
<glyph unicode="&#xe603;" glyph-name="presentation" horiz-adv-x="1088" d="M952.495 1019.065h-818.689c-72.81 0-132.183-60.63-132.183-135.162v-750.719c0-74.473 59.372-135.101 132.183-135.101h818.686c72.936 0 132.314 60.625 132.314 135.101v750.722c0.003 74.532-59.378 135.159-132.311 135.159zM946.346 139.651h-806.14v737.822h806.015l0.126-737.822zM685.753 738.544h216.911v-566.758h-216.911v566.758zM428.672 610.002h216.911v-438.216h-216.911v438.216zM172.339 481.46h216.161v-309.677h-216.161v309.677z" />
<glyph unicode="&#xe613;" glyph-name="recDisable" horiz-adv-x="1140" d="M1123.444 1003.015c-23.593 26.481-64.131 28.989-90.74 5.395l-1008.269-893.436c-26.609-23.468-28.991-64.131-5.46-90.676 12.674-14.306 30.308-21.649 48.126-21.649 15.123 0 30.372 5.401 42.544 16.195l130.045 115.22c90.743-81.844 210.569-132.165 342.473-132.101 282.816 0.061 510.913 227.969 511.287 510.972 0.126 109.934-34.682 211.367-93.499 294.72l118.088 104.625c26.483 23.526 28.997 64.129 5.404 90.735zM944.422 513.818c0.128-200.922-161.896-363.201-362.509-362.952-87.56 0.123-167.573 31.151-230.061 82.569l331.277 293.509v-73.176c1.071-60.993 32.696-92.18 94.944-93.692 61.997 1.512 93.686 32.763 95.131 93.756v41.096h-72.227v-47.499c0.251-4.642-0.564-10.607-2.511-17.949-1.25-3.261-3.448-6.020-6.525-8.093-3.197-2.572-7.845-3.828-13.868-3.828-10.543 0.31-17.132 4.268-19.827 11.921-1.068 3.512-1.947 6.905-2.508 10.163-0.254 2.887-0.377 5.532-0.377 7.786v143.511l42.477 37.634c0.215-0.432 0.452-0.851 0.63-1.303 1.947-6.467 2.762-12.799 2.511-19.076v-36.772h72.227v30.121c-0.246 31.245-9.086 54.699-26.363 70.447l40.711 36.069c35.787-56.055 56.803-122.585 56.867-194.244zM239.795 395.47c-12.613 37.023-19.827 76.557-19.827 117.913-0.19 200.236 161.584 362.009 361.945 362.135 56.853 0 110.313-13.302 158.133-36.398l117.846 104.421c-79.444 50.952-173.758 80.817-275.292 80.948-283.377 0.181-511.354-227.729-511.789-511.675-0.126-79.567 18.636-154.679 51.137-221.882l117.848 104.538zM388.576 690.020h-97.514v-249.057l72.23 64.070v0.689h0.815l117.72 104.418c0 0.564 0.123 0.94 0.123 1.509 0.753 53.898-30.369 80.069-93.374 78.37zM405.959 625.517c1.942-2.767 3.074-6.469 3.323-11.112 0.312-4.452 0.438-9.6 0.438-15.246 0.251-10.916-0.689-19.83-2.949-26.985-2.952-7.594-10.983-11.357-24.159-11.357h-19.325v74.043h15.31c7.842 0 13.865-0.683 18.072-2.19 4.397-1.573 7.468-3.953 9.29-7.153z" />
<glyph unicode="&#xe614;" glyph-name="recEnable" horiz-adv-x="1142" d="M581.278 1025.708c284.857-0.19 514.807-230.517 514.427-514.997-0.378-285.047-230.073-514.553-514.869-514.615-284.541-0.062-515.311 230.517-514.933 514.422 0.439 285.936 230.009 515.439 515.375 515.19zM580.579 875.756c-201.764-0.123-364.666-163.032-364.478-364.663 0-202.018 162.524-364.735 364.478-364.984 202.018-0.316 365.174 163.030 365.048 365.423-0.252 201.767-163.156 364.35-365.048 364.224zM287.698 688.907h98.196c63.442 1.767 94.785-24.518 94.027-78.863 0.254-19.081-2.211-34.882-7.456-47.521-6.005-12.508-18.706-21.988-38.167-28.181v-0.819c28.373-6.259 43.031-23.573 43.981-51.946v-57.689c0-11.247 0.254-22.813 0.758-34.756 0.819-12.005 3.033-20.979 6.696-27.043h-71.846c-3.727 6.064-6.128 15.038-7.14 27.043-1.012 11.943-1.454 23.509-1.138 34.756v52.321c0 9.603-2.214 16.553-6.573 20.979-4.675 4.107-12.701 6.19-24.012 6.19h-14.599v-141.291h-72.73v326.82zM360.428 558.861h19.463c13.271 0 21.359 3.794 24.331 11.375 2.276 7.204 3.221 16.304 2.969 27.171 0 5.815-0.126 10.867-0.442 15.418-0.252 4.675-1.392 8.404-3.352 11.247-1.831 3.157-4.926 5.561-9.352 7.14-4.233 1.454-10.299 2.211-18.2 2.211h-15.418v-74.564zM498.372 688.907h162.082v-62.687h-89.35v-65.587h78.103v-62.685h-78.103v-73.11h92.822v-62.749h-165.557v326.818zM682.507 599.999c0.316 31.782 9.416 55.542 27.425 71.407 17.44 15.29 40.185 22.936 68.181 22.936 28.247 0 51.119-7.646 68.623-23 17.82-15.798 26.92-39.623 27.171-71.407v-30.333h-72.73v37.031c0.254 6.192-0.57 12.639-2.527 19.209-1.264 3.157-3.475 5.938-6.573 8.214-3.221 1.515-7.898 2.404-13.964 2.404-10.615-0.316-17.249-3.855-19.967-10.618-2.211-6.573-3.223-13.017-2.907-19.209v-161.956c0-2.273 0.126-4.865 0.38-7.772 0.568-3.411 1.454-6.824 2.527-10.233 2.717-7.775 9.352-11.756 19.967-12.007 6.067 0 10.744 1.261 13.964 3.791 3.098 2.15 5.309 4.867 6.573 8.216 1.96 7.33 2.782 13.33 2.527 18.007v47.837h72.73v-41.328c-1.451-61.547-33.364-93.015-95.794-94.469-62.685 1.454-94.53 32.922-95.607 94.343v148.937z" />
@@ -20,7 +21,6 @@
<glyph unicode="&#xe906;" glyph-name="chat" d="M854 342v512h-684v-598l86 86h598zM854 938c46 0 84-38 84-84v-512c0-46-38-86-84-86h-598l-170-170v768c0 46 38 84 84 84h684z" />
<glyph unicode="&#xe907;" glyph-name="edit" d="M884 724l-78-78-160 160 78 78c16 16 44 16 60 0l100-100c16-16 16-44 0-60zM128 288l472 472 160-160-472-472h-160v160z" />
<glyph unicode="&#xe908;" glyph-name="share-doc" d="M554 640h236l-236 234v-234zM682 426v86h-340v-86h340zM682 256v86h-340v-86h340zM598 938l256-256v-512c0-46-40-84-86-84h-512c-46 0-86 38-86 84l2 684c0 46 38 84 84 84h342z" />
<glyph unicode="&#xe909;" glyph-name="telephone" d="M854 362c24 0 42-18 42-42v-150c0-24-18-42-42-42-400 0-726 326-726 726 0 24 18 42 42 42h150c24 0 42-18 42-42 0-54 8-104 24-152 4-14 2-32-10-44l-94-94c62-122 162-220 282-282l94 94c12 12 30 14 44 10 48-16 98-24 152-24zM854 810v44h-44v-44h44zM768 896h128v-128h-86v-86h-42v214zM640 810v-128h-128v44h86v42h-86v128h128v-42h-86v-44h86zM726 896v-214h-44v214h44z" />
<glyph unicode="&#xe90a;" glyph-name="star-full" d="M512 288l-264-160 70 300-232 202 306 26 120 282 120-282 306-26-232-202 70-300z" />
<glyph unicode="&#xe90b;" glyph-name="full-screen" d="M598 810h212v-212h-84v128h-128v84zM726 298v128h84v-212h-212v84h128zM214 598v212h212v-84h-128v-128h-84zM298 426v-128h128v-84h-212v212h84z" />
<glyph unicode="&#xe90c;" glyph-name="exit-full-screen" d="M682 682h128v-84h-212v212h84v-128zM598 214v212h212v-84h-128v-128h-84zM342 682v128h84v-212h-212v84h128zM214 342v84h212v-212h-84v128h-128z" />
@@ -45,4 +45,7 @@
<glyph unicode="&#xe91f;" glyph-name="menu-up" d="M512 682l256-256-60-60-196 196-196-196-60 60z" />
<glyph unicode="&#xe920;" glyph-name="menu-down" d="M708 658l60-60-256-256-256 256 60 60 196-196z" />
<glyph unicode="&#xe921;" glyph-name="switch-camera" d="M640 362l150 150-150 150v-108h-256v108l-150-150 150-150v108h256v-108zM854 854c46 0 84-40 84-86v-512c0-46-38-86-84-86h-684c-46 0-84 40-84 86v512c0 46 38 86 84 86h136l78 84h256l78-84h136z" />
<glyph unicode="&#xe923;" glyph-name="visibility" d="M512 640c70 0 128-58 128-128s-58-128-128-128-128 58-128 128 58 128 128 128zM512 298c118 0 214 96 214 214s-96 214-214 214-214-96-214-214 96-214 214-214zM512 832c214 0 396-132 470-320-74-188-256-320-470-320s-396 132-470 320c74 188 256 320 470 320z" />
<glyph unicode="&#xe924;" glyph-name="visibility-off" d="M506 640h6c70 0 128-58 128-128v-8zM322 606c-14-28-24-60-24-94 0-118 96-214 214-214 34 0 66 10 94 24l-66 66c-8-2-18-4-28-4-70 0-128 58-128 128 0 10 2 20 4 28zM86 842l54 54 756-756-54-54c-47.968 47.365-96.266 94.401-144 142-58-24-120-36-186-36-214 0-396 132-470 320 34 84 90 156 160 212-39.017 38.983-77.307 78.693-116 118zM512 726c-28 0-54-6-78-16l-92 92c52 20 110 30 170 30 214 0 394-132 468-320-32-80-82-148-146-202l-124 124c10 24 16 50 16 78 0 118-96 214-214 214z" />
<glyph unicode="&#xe925;" glyph-name="dialpad" d="M512 982c46 0 86-40 86-86s-40-86-86-86-86 40-86 86 40 86 86 86zM512 726c46 0 86-40 86-86s-40-86-86-86-86 40-86 86 40 86 86 86zM768 726c46 0 86-40 86-86s-40-86-86-86-86 40-86 86 40 86 86 86zM768 470c46 0 86-40 86-86s-40-86-86-86-86 40-86 86 40 86 86 86zM512 470c46 0 86-40 86-86s-40-86-86-86-86 40-86 86 40 86 86 86zM768 810c-46 0-86 40-86 86s40 86 86 86 86-40 86-86-40-86-86-86zM256 470c46 0 86-40 86-86s-40-86-86-86-86 40-86 86 40 86 86 86zM256 726c46 0 86-40 86-86s-40-86-86-86-86 40-86 86 40 86 86 86zM256 982c46 0 86-40 86-86s-40-86-86-86-86 40-86 86 40 86 86 86zM512 214c46 0 86-40 86-86s-40-86-86-86-86 40-86 86 40 86 86 86z" />
</font></defs></svg>

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 17 KiB

BIN
fonts/jitsi.ttf Normal file → Executable file

Binary file not shown.

BIN
fonts/jitsi.woff Normal file → Executable file

Binary file not shown.

794
fonts/selection.json Normal file → Executable file

File diff suppressed because it is too large Load Diff

9
images/countries/au.svg Executable file
View File

@@ -0,0 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" height="480" width="640" viewBox="0 0 640 480">
<g stroke-width="1pt">
<path fill="#006" d="M0 0h640v480H0z"/>
<path d="M0 0v27.95L307.037 250h38.647v-27.95L38.647 0H0zm345.684 0v27.95L38.647 250H0v-27.95L307.037 0h38.647z" fill="#fff"/>
<path d="M144.035 0v250h57.614V0h-57.615zM0 83.333v83.333h345.684V83.333H0z" fill="#fff"/>
<path d="M0 100v50h345.684v-50H0zM155.558 0v250h34.568V0h-34.568zM0 250l115.228-83.334h25.765L25.765 250H0zM0 0l115.228 83.333H89.463L0 18.633V0zm204.69 83.333L319.92 0h25.764L230.456 83.333H204.69zM345.685 250l-115.228-83.334h25.765l89.464 64.7V250z" fill="#c00"/>
<path d="M299.762 392.523l-43.653 3.795 6.013 43.406-30.187-31.764-30.186 31.764 6.014-43.406-43.653-3.795 37.68-22.364-24.244-36.495 40.97 15.514 13.42-41.713 13.42 41.712 40.97-15.515-24.242 36.494m224.444 62.372l-10.537-15.854 17.81 6.742 5.824-18.125 5.825 18.126 17.807-6.742-10.537 15.854 16.37 9.718-18.965 1.65 2.616 18.85-13.116-13.793-13.117 13.794 2.616-18.85-18.964-1.65m16.368-291.815l-10.537-15.856 17.81 6.742 5.824-18.122 5.825 18.12 17.807-6.74-10.537 15.855 16.37 9.717-18.965 1.65 2.616 18.85-13.116-13.793-13.117 13.794 2.616-18.85-18.964-1.65m-89.418 104.883l-10.537-15.853 17.808 6.742 5.825-18.125 5.825 18.125 17.808-6.742-10.536 15.853 16.37 9.72-18.965 1.65 2.615 18.85-13.117-13.795-13.117 13.795 2.617-18.85-18.964-1.65m216.212-37.929l-10.558-15.854 17.822 6.742 5.782-18.125 5.854 18.125 17.772-6.742-10.508 15.854 16.362 9.718-18.97 1.65 2.608 18.85-13.118-13.793-13.117 13.793 2.61-18.85-18.936-1.65m-22.251 73.394l-10.367 6.425 2.914-11.84-9.316-7.863 12.165-.896 4.605-11.29 4.606 11.29 12.165.897-9.317 7.863 2.912 11.84" fill-rule="evenodd" fill="#fff"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

6
images/countries/ca.svg Executable file
View File

@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" height="480" width="640" viewBox="0 0 640 480">
<g transform="translate(74.118) scale(.9375)">
<path fill="#fff" d="M81.137 0h362.276v512H81.137z"/>
<path fill="#bf0a30" d="M-100 0H81.138v512H-100zm543.413 0H624.55v512H443.414zM135.31 247.41l-14.067 4.808 65.456 57.446c4.95 14.764-1.72 19.116-5.97 26.86l71.06-9.02-1.85 71.512 14.718-.423-3.21-70.918 71.13 8.432c-4.402-9.297-8.32-14.233-4.247-29.098l65.414-54.426-11.447-4.144c-9.36-7.222 4.044-34.784 6.066-52.178 0 0-38.195 13.135-40.698 6.262l-9.727-18.685-34.747 38.17c-3.796.91-5.413-.6-6.304-3.808l16.053-79.766-25.42 14.297c-2.128.91-4.256.125-5.658-2.355l-24.45-49.06-25.21 50.95c-1.9 1.826-3.803 2.037-5.382.796l-24.204-13.578 14.53 79.143c-1.156 3.14-3.924 4.025-7.18 2.324l-33.216-37.737c-4.345 6.962-7.29 18.336-13.033 20.885-5.744 2.387-24.98-4.823-37.873-7.637 4.404 15.895 18.176 42.302 9.46 50.957z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 934 B

5
images/countries/de.svg Executable file
View File

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" height="480" width="640" viewBox="0 0 640 480">
<path fill="#ffce00" d="M0 320h640v160.002H0z"/>
<path d="M0 0h640v160H0z"/>
<path fill="#d00" d="M0 160h640v160H0z"/>
</svg>

After

Width:  |  Height:  |  Size: 220 B

7
images/countries/fr.svg Executable file
View File

@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" height="480" width="640" viewBox="0 0 640 480">
<g fill-rule="evenodd" stroke-width="1pt">
<path fill="#fff" d="M0 0h640v480H0z"/>
<path fill="#00267f" d="M0 0h213.337v480H0z"/>
<path fill="#f31830" d="M426.662 0H640v480H426.662z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 301 B

15
images/countries/gb.svg Executable file
View File

@@ -0,0 +1,15 @@
<svg xmlns="http://www.w3.org/2000/svg" height="480" width="640" viewBox="0 0 640 480">
<defs>
<clipPath id="a">
<path fill-opacity=".67" d="M-85.333 0h682.67v512h-682.67z"/>
</clipPath>
</defs>
<g clip-path="url(#a)" transform="translate(80) scale(.94)">
<g stroke-width="1pt">
<path fill="#006" d="M-256 0H768.02v512.01H-256z"/>
<path d="M-256 0v57.244l909.535 454.768H768.02V454.77L-141.515 0H-256zM768.02 0v57.243L-141.515 512.01H-256v-57.243L653.535 0H768.02z" fill="#fff"/>
<path d="M170.675 0v512.01h170.67V0h-170.67zM-256 170.67v170.67H768.02V170.67H-256z" fill="#fff"/>
<path d="M-256 204.804v102.402H768.02V204.804H-256zM204.81 0v512.01h102.4V0h-102.4zM-256 512.01L85.34 341.34h76.324l-341.34 170.67H-256zM-256 0L85.34 170.67H9.016L-256 38.164V0zm606.356 170.67L691.696 0h76.324L426.68 170.67h-76.324zM768.02 512.01L426.68 341.34h76.324L768.02 473.848v38.162z" fill="#c00"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 956 B

18
images/countries/us.svg Executable file
View File

@@ -0,0 +1,18 @@
<svg xmlns="http://www.w3.org/2000/svg" height="480" width="640" viewBox="0 0 640 480">
<g fill-rule="evenodd" transform="scale(.9375)">
<g stroke-width="1pt">
<path d="M0 0h972.81v39.385H0zm0 78.77h972.81v39.385H0zm0 78.77h972.81v39.385H0zm0 78.77h972.81v39.385H0zm0 78.77h972.81v39.385H0zm0 78.77h972.81v39.385H0zm0 78.77h972.81v39.385H0z" fill="#bd3d44"/>
<path d="M0 39.385h972.81V78.77H0zm0 78.77h972.81v39.385H0zm0 78.77h972.81v39.385H0zm0 78.77h972.81v39.385H0zm0 78.77h972.81v39.385H0zm0 78.77h972.81v39.385H0z" fill="#fff"/>
</g>
<path fill="#192f5d" d="M0 0h389.12v275.69H0z"/>
<g fill="#fff">
<path d="M32.427 11.8l3.54 10.896h11.458l-9.27 6.735 3.541 10.896-9.27-6.734-9.268 6.734 3.54-10.896-9.269-6.735h11.457zm64.853 0l3.541 10.896h11.458l-9.27 6.735 3.541 10.896-9.27-6.734-9.268 6.734 3.54-10.896-9.269-6.735H93.74zm64.856 0l3.54 10.896h11.458l-9.27 6.735 3.541 10.896-9.269-6.734-9.269 6.734 3.54-10.896-9.269-6.735h11.458zm64.852 0l3.54 10.896h11.457l-9.269 6.735 3.54 10.896-9.268-6.734-9.27 6.734 3.541-10.896-9.27-6.735h11.458zm64.855 0l3.54 10.896h11.458l-9.27 6.735 3.541 10.896-9.27-6.734-9.268 6.734 3.54-10.896-9.269-6.735h11.457zm64.855 0l3.54 10.896h11.458l-9.27 6.735 3.541 10.896-9.269-6.734-9.27 6.734 3.542-10.896-9.27-6.735h11.458zM64.855 39.37l3.54 10.896h11.458L70.583 57l3.542 10.897-9.27-6.734-9.269 6.734L59.126 57l-9.269-6.734h11.458zm64.852 0l3.54 10.896h11.457L135.435 57l3.54 10.897-9.268-6.734-9.27 6.734L123.978 57l-9.27-6.734h11.458zm64.855 0l3.54 10.896h11.458L200.29 57l3.541 10.897-9.27-6.734-9.268 6.734L188.833 57l-9.269-6.734h11.457zm64.855 0l3.54 10.896h11.458L265.145 57l3.541 10.897-9.269-6.734-9.27 6.734L253.69 57l-9.27-6.734h11.458zm64.852 0l3.54 10.896h11.457L329.997 57l3.54 10.897-9.268-6.734-9.27 6.734L318.54 57l-9.27-6.734h11.458zM32.427 66.939l3.54 10.896h11.458l-9.27 6.735 3.541 10.896-9.27-6.734-9.268 6.734 3.54-10.896-9.269-6.735h11.457zm64.853 0l3.541 10.896h11.458l-9.27 6.735 3.541 10.896-9.27-6.734-9.268 6.734 3.54-10.896-9.269-6.735H93.74zm64.856 0l3.54 10.896h11.458l-9.27 6.735 3.541 10.896-9.269-6.734-9.269 6.734 3.54-10.896-9.269-6.735h11.458zm64.852 0l3.54 10.896h11.457l-9.269 6.735 3.54 10.896-9.268-6.734-9.27 6.734 3.541-10.896-9.27-6.735h11.458zm64.855 0l3.54 10.896h11.458l-9.27 6.735 3.541 10.896-9.27-6.734-9.268 6.734 3.54-10.896-9.269-6.735h11.457zm64.855 0l3.54 10.896h11.458l-9.27 6.735 3.541 10.896-9.269-6.734-9.27 6.734 3.542-10.896-9.27-6.735h11.458zM64.855 94.508l3.54 10.897h11.458l-9.27 6.734 3.542 10.897-9.27-6.734-9.269 6.734 3.54-10.897-9.269-6.734h11.458zm64.852 0l3.54 10.897h11.457l-9.269 6.734 3.54 10.897-9.268-6.734-9.27 6.734 3.541-10.897-9.27-6.734h11.458zm64.855 0l3.54 10.897h11.458l-9.27 6.734 3.541 10.897-9.27-6.734-9.268 6.734 3.54-10.897-9.269-6.734h11.457zm64.855 0l3.54 10.897h11.458l-9.27 6.734 3.541 10.897-9.269-6.734-9.27 6.734 3.542-10.897-9.27-6.734h11.458zm64.852 0l3.54 10.897h11.457l-9.269 6.734 3.54 10.897-9.268-6.734-9.27 6.734 3.541-10.897-9.27-6.734h11.458zM32.427 122.078l3.54 10.896h11.458l-9.27 6.735 3.541 10.896-9.27-6.734-9.268 6.734 3.54-10.896-9.269-6.735h11.457zm64.853 0l3.541 10.896h11.458l-9.27 6.735 3.541 10.896-9.27-6.734-9.268 6.734 3.54-10.896-9.269-6.735H93.74zm64.856 0l3.54 10.896h11.458l-9.27 6.735 3.541 10.896-9.269-6.734-9.269 6.734 3.54-10.896-9.269-6.735h11.458zm64.852 0l3.54 10.896h11.457l-9.269 6.735 3.54 10.896-9.268-6.734-9.27 6.734 3.541-10.896-9.27-6.735h11.458zm64.855 0l3.54 10.896h11.458l-9.27 6.735 3.541 10.896-9.27-6.734-9.268 6.734 3.54-10.896-9.269-6.735h11.457zm64.855 0l3.54 10.896h11.458l-9.27 6.735 3.541 10.896-9.269-6.734-9.27 6.734 3.542-10.896-9.27-6.735h11.458zM64.855 149.647l3.54 10.897h11.458l-9.27 6.734 3.542 10.897-9.27-6.734-9.269 6.734 3.54-10.897-9.269-6.734h11.458zm64.852 0l3.54 10.897h11.457l-9.269 6.734 3.54 10.897-9.268-6.734-9.27 6.734 3.541-10.897-9.27-6.734h11.458zm64.855 0l3.54 10.897h11.458l-9.27 6.734 3.541 10.897-9.27-6.734-9.268 6.734 3.54-10.897-9.269-6.734h11.457zm64.855 0l3.54 10.897h11.458l-9.27 6.734 3.541 10.897-9.269-6.734-9.27 6.734 3.542-10.897-9.27-6.734h11.458zm64.852 0l3.54 10.897h11.457l-9.269 6.734 3.54 10.897-9.268-6.734-9.27 6.734 3.541-10.897-9.27-6.734h11.458z"/>
<g>
<path d="M32.427 177.217l3.54 10.896h11.458l-9.27 6.735 3.541 10.896-9.27-6.734-9.268 6.734 3.54-10.896-9.269-6.735h11.457zm64.853 0l3.541 10.896h11.458l-9.27 6.735 3.541 10.896-9.27-6.734-9.268 6.734 3.54-10.896-9.269-6.735H93.74zm64.856 0l3.54 10.896h11.458l-9.27 6.735 3.541 10.896-9.269-6.734-9.269 6.734 3.54-10.896-9.269-6.735h11.458zm64.852 0l3.54 10.896h11.457l-9.269 6.735 3.54 10.896-9.268-6.734-9.27 6.734 3.541-10.896-9.27-6.735h11.458zm64.855 0l3.54 10.896h11.458l-9.27 6.735 3.541 10.896-9.27-6.734-9.268 6.734 3.54-10.896-9.269-6.735h11.457zm64.855 0l3.54 10.896h11.458l-9.27 6.735 3.541 10.896-9.269-6.734-9.27 6.734 3.542-10.896-9.27-6.735h11.458zM64.855 204.786l3.54 10.897h11.458l-9.27 6.734 3.542 10.897-9.27-6.734-9.269 6.734 3.54-10.897-9.269-6.734h11.458zm64.852 0l3.54 10.897h11.457l-9.269 6.734 3.54 10.897-9.268-6.734-9.27 6.734 3.541-10.897-9.27-6.734h11.458zm64.855 0l3.54 10.897h11.458l-9.27 6.734 3.541 10.897-9.27-6.734-9.268 6.734 3.54-10.897-9.269-6.734h11.457zm64.855 0l3.54 10.897h11.458l-9.27 6.734 3.541 10.897-9.269-6.734-9.27 6.734 3.542-10.897-9.27-6.734h11.458zm64.852 0l3.54 10.897h11.457l-9.269 6.734 3.54 10.897-9.268-6.734-9.27 6.734 3.541-10.897-9.27-6.734h11.458z"/>
</g>
<g>
<path d="M32.427 232.356l3.54 10.896h11.458l-9.27 6.735 3.541 10.896-9.27-6.734-9.268 6.734 3.54-10.896-9.269-6.735h11.457zm64.853 0l3.541 10.896h11.458l-9.27 6.735 3.541 10.896-9.27-6.734-9.268 6.734 3.54-10.896-9.269-6.735H93.74zm64.856 0l3.54 10.896h11.458l-9.27 6.735 3.541 10.896-9.269-6.734-9.269 6.734 3.54-10.896-9.269-6.735h11.458zm64.852 0l3.54 10.896h11.457l-9.269 6.735 3.54 10.896-9.268-6.734-9.27 6.734 3.541-10.896-9.27-6.735h11.458zm64.855 0l3.54 10.896h11.458l-9.27 6.735 3.541 10.896-9.27-6.734-9.268 6.734 3.54-10.896-9.269-6.735h11.457zm64.855 0l3.54 10.896h11.458l-9.27 6.735 3.541 10.896-9.269-6.734-9.27 6.734 3.542-10.896-9.27-6.735h11.458z"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.1 KiB

View File

@@ -4,6 +4,7 @@
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!--#include virtual="base.html" -->
<!--#include virtual="local.html" -->
<script>
window.indexLoadedTime = window.performance.now();
console.log("(TIME) index.html loaded:\t", indexLoadedTime);
@@ -127,9 +128,8 @@
'error', loadErrHandler, true /* capture phase type of listener */);
</script>
<script><!--#include virtual="/config.js" --></script><!-- adapt to your needs, i.e. set hosts and bosh path -->
<script src="static/utils.js?v=1"></script>
<!--#include virtual="connection_optimization/connection_optimization.html" -->
<script src="connection_optimization/do_external_connect.js?v=1"></script>
<script src="libs/do_external_connect.min.js?v=1"></script>
<script><!--#include virtual="/interface_config.js" --></script>
<script><!--#include virtual="/logging_config.js" --></script>
<script src="libs/lib-jitsi-meet.min.js?v=139"></script>

View File

@@ -38,7 +38,7 @@ var interfaceConfig = { // eslint-disable-line no-unused-vars
//main toolbar
'microphone', 'camera', 'desktop', 'invite', 'fullscreen', 'hangup',
//extended toolbar
'profile', 'contacts', 'chat', 'recording', 'etherpad', 'sharedvideo', 'sip', 'dialpad', 'settings', 'raisehand', 'filmstrip'], // jshint ignore:line
'profile', 'contacts', 'chat', 'recording', 'etherpad', 'sharedvideo', 'dialout', 'settings', 'raisehand', 'filmstrip'], // jshint ignore:line
/**
* Main Toolbar Buttons
* All of them should be in TOOLBAR_BUTTONS

View File

@@ -17,7 +17,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.3</string>
<string>1.4</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleURLTypes</key>
@@ -78,6 +78,8 @@
<array>
<string>armv7</string>
</array>
<key>UIStatusBarStyle</key>
<string>UIStatusBarStyleLightContent</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationLandscapeLeft</string>

24
ios/app/Proximity.m Normal file
View File

@@ -0,0 +1,24 @@
#import "RCTBridgeModule.h"
#import <UIKit/UIKit.h>
@interface Proximity : NSObject<RCTBridgeModule>
@end
@implementation Proximity
RCT_EXPORT_MODULE();
/**
* Enables / disables the proximity sensor monitoring. On iOS enabling the
* proximity sensor automatically dims the screen and disables touch controls,
* so there is nothing else to do (unlike on Android)!
*
* @param enabled {@code YES} to enable proximity (sensor) monitoring;
* {@code NO}, otherwise.
*/
RCT_EXPORT_METHOD(setEnabled:(BOOL)enabled) {
[[UIDevice currentDevice] setProximityMonitoringEnabled:enabled];
}
@end

View File

@@ -14,6 +14,7 @@
00C302E91ABCBA2D00DB3ED1 /* libRCTNetwork.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302DC1ABCB9D200DB3ED1 /* libRCTNetwork.a */; };
00C302EA1ABCBA2D00DB3ED1 /* libRCTVibration.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302E41ABCB9EE00DB3ED1 /* libRCTVibration.a */; };
0B42DFAE1E2FD90700111B12 /* AudioMode.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B42DFAD1E2FD90700111B12 /* AudioMode.m */; };
0B96CAF11E8CF0E8005F348C /* Proximity.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B96CAF01E8CF0E8005F348C /* Proximity.m */; };
133E29F31AD74F7200F7D852 /* libRCTLinking.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 78C398B91ACF4ADC00677621 /* libRCTLinking.a */; };
139105C61AF99C1200B5F7CC /* libRCTSettings.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 139105C11AF99BAD00B5F7CC /* libRCTSettings.a */; };
139FDEF61B0652A700C62182 /* libRCTWebSocket.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 139FDEF41B06529B00C62182 /* libRCTWebSocket.a */; };
@@ -80,6 +81,13 @@
remoteGlobalIDString = 832C81801AAF6DEF007FA2F7;
remoteInfo = RCTVibration;
};
0B50D60B1EA7891100B34818 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 5B09C20C78C74A548AAAC1FA /* KCKeepAwake.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 325F5AB71E6763EC00E6CDE4;
remoteInfo = "KCKeepAwake-tvOS";
};
0B8752851E26E54A004C5CAB /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 5B09C20C78C74A548AAAC1FA /* KCKeepAwake.xcodeproj */;
@@ -273,10 +281,11 @@
00C302DF1ABCB9EE00DB3ED1 /* RCTVibration.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTVibration.xcodeproj; path = "../node_modules/react-native/Libraries/Vibration/RCTVibration.xcodeproj"; sourceTree = "<group>"; };
0965153BB98645B4A8B6AA10 /* RNBackgroundTimer.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = RNBackgroundTimer.xcodeproj; path = "../node_modules/react-native-background-timer/ios/RNBackgroundTimer.xcodeproj"; sourceTree = "<group>"; };
0B42DFAD1E2FD90700111B12 /* AudioMode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AudioMode.m; path = app/AudioMode.m; sourceTree = "<group>"; };
0B96CAF01E8CF0E8005F348C /* Proximity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Proximity.m; path = app/Proximity.m; sourceTree = "<group>"; };
0EA8C046B2BF46279796F07D /* libKCKeepAwake.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libKCKeepAwake.a; sourceTree = "<group>"; };
139105B61AF99BAD00B5F7CC /* RCTSettings.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTSettings.xcodeproj; path = "../node_modules/react-native/Libraries/Settings/RCTSettings.xcodeproj"; sourceTree = "<group>"; };
139FDEE61B06529A00C62182 /* RCTWebSocket.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTWebSocket.xcodeproj; path = "../node_modules/react-native/Libraries/WebSocket/RCTWebSocket.xcodeproj"; sourceTree = "<group>"; };
13B07F961A680F5B00A75B9A /* jitsi-meet-react.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "jitsi-meet-react.app"; sourceTree = BUILT_PRODUCTS_DIR; };
13B07F961A680F5B00A75B9A /* jitsi-meet.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "jitsi-meet.app"; sourceTree = BUILT_PRODUCTS_DIR; };
13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = app/AppDelegate.h; sourceTree = "<group>"; };
13B07FB01A68108700A75B9A /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AppDelegate.m; path = app/AppDelegate.m; sourceTree = "<group>"; };
13B07FB21A68108700A75B9A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = "<group>"; };
@@ -294,7 +303,7 @@
832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTText.xcodeproj; path = "../node_modules/react-native/Libraries/Text/RCTText.xcodeproj"; sourceTree = "<group>"; };
B30EF2301DC0ED7C00690F45 /* WebRTC.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebRTC.framework; path = "../node_modules/react-native-webrtc/ios/WebRTC.framework"; sourceTree = "<group>"; };
B3A9D0241E0481E10009343D /* POSIX.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = POSIX.m; path = app/POSIX.m; sourceTree = "<group>"; };
B3B083EB1D4955FF0069CEE7 /* jitsi-meet-react.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = "jitsi-meet-react.entitlements"; sourceTree = "<group>"; };
B3B083EB1D4955FF0069CEE7 /* jitsi-meet.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "jitsi-meet.entitlements"; sourceTree = "<group>"; };
B96AF9B6FBC0453798399985 /* FontAwesome.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = FontAwesome.ttf; path = "../node_modules/react-native-vector-icons/Fonts/FontAwesome.ttf"; sourceTree = "<group>"; };
BF9643811C34FBB300B0BBDF /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; };
BF9643831C34FBBB00B0BBDF /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; };
@@ -392,6 +401,7 @@
isa = PBXGroup;
children = (
0B8752861E26E54A004C5CAB /* libKCKeepAwake.a */,
0B50D60C1EA7891100B34818 /* libKCKeepAwake-tvOS.a */,
);
name = Products;
sourceTree = "<group>";
@@ -426,6 +436,7 @@
008F07F21AC5B25A0029DE68 /* main.jsbundle */,
13B07FB71A68108700A75B9A /* main.m */,
B3A9D0241E0481E10009343D /* POSIX.m */,
0B96CAF01E8CF0E8005F348C /* Proximity.m */,
);
name = app;
sourceTree = "<group>";
@@ -519,7 +530,7 @@
children = (
13B07FAE1A68108700A75B9A /* app */,
B3BA19B71DC6B02F00BCD481 /* Frameworks */,
B3B083EB1D4955FF0069CEE7 /* jitsi-meet-react.entitlements */,
B3B083EB1D4955FF0069CEE7 /* jitsi-meet.entitlements */,
832341AE1AAA6A7D00B99B32 /* Libraries */,
83CBBA001A601CBA00E9B192 /* Products */,
6956B374CC3C453DB7B8E82D /* Resources */,
@@ -531,7 +542,7 @@
83CBBA001A601CBA00E9B192 /* Products */ = {
isa = PBXGroup;
children = (
13B07F961A680F5B00A75B9A /* jitsi-meet-react.app */,
13B07F961A680F5B00A75B9A /* jitsi-meet.app */,
);
name = Products;
sourceTree = "<group>";
@@ -570,9 +581,9 @@
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
13B07F861A680F5B00A75B9A /* jitsi-meet-react */ = {
13B07F861A680F5B00A75B9A /* jitsi-meet */ = {
isa = PBXNativeTarget;
buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "jitsi-meet-react" */;
buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "jitsi-meet" */;
buildPhases = (
13B07F871A680F5B00A75B9A /* Sources */,
13B07F8C1A680F5B00A75B9A /* Frameworks */,
@@ -585,9 +596,9 @@
);
dependencies = (
);
name = "jitsi-meet-react";
name = "jitsi-meet";
productName = "Hello World";
productReference = 13B07F961A680F5B00A75B9A /* jitsi-meet-react.app */;
productReference = 13B07F961A680F5B00A75B9A /* jitsi-meet.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
@@ -609,7 +620,7 @@
};
};
};
buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "jitsi-meet-react" */;
buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "jitsi-meet" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
hasScannedForEncodings = 0;
@@ -684,7 +695,7 @@
);
projectRoot = "";
targets = (
13B07F861A680F5B00A75B9A /* jitsi-meet-react */,
13B07F861A680F5B00A75B9A /* jitsi-meet */,
);
};
/* End PBXProject section */
@@ -725,6 +736,13 @@
remoteRef = 00C302E31ABCB9EE00DB3ED1 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
0B50D60C1EA7891100B34818 /* libKCKeepAwake-tvOS.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = "libKCKeepAwake-tvOS.a";
remoteRef = 0B50D60B1EA7891100B34818 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
0B8752861E26E54A004C5CAB /* libKCKeepAwake.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
@@ -945,6 +963,7 @@
buildActionMask = 2147483647;
files = (
B3A9D0251E0481E10009343D /* POSIX.m in Sources */,
0B96CAF11E8CF0E8005F348C /* Proximity.m in Sources */,
0B42DFAE1E2FD90700111B12 /* AudioMode.m in Sources */,
13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */,
13B07FC11A68108700A75B9A /* main.m in Sources */,
@@ -970,7 +989,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = "jitsi-meet-react.entitlements";
CODE_SIGN_ENTITLEMENTS = "jitsi-meet.entitlements";
CURRENT_PROJECT_VERSION = 1;
DEAD_CODE_STRIPPING = NO;
ENABLE_BITCODE = NO;
@@ -1000,7 +1019,7 @@
"-lc++",
);
PRODUCT_BUNDLE_IDENTIFIER = org.jitsi.JitsiMeet.ios;
PRODUCT_NAME = "jitsi-meet-react";
PRODUCT_NAME = "jitsi-meet";
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
@@ -1009,7 +1028,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = "jitsi-meet-react.entitlements";
CODE_SIGN_ENTITLEMENTS = "jitsi-meet.entitlements";
CURRENT_PROJECT_VERSION = 1;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
@@ -1038,7 +1057,7 @@
"-lc++",
);
PRODUCT_BUNDLE_IDENTIFIER = org.jitsi.JitsiMeet.ios;
PRODUCT_NAME = "jitsi-meet-react";
PRODUCT_NAME = "jitsi-meet";
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
@@ -1145,7 +1164,7 @@
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "jitsi-meet-react" */ = {
13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "jitsi-meet" */ = {
isa = XCConfigurationList;
buildConfigurations = (
13B07F941A680F5B00A75B9A /* Debug */,
@@ -1154,7 +1173,7 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "jitsi-meet-react" */ = {
83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "jitsi-meet" */ = {
isa = XCConfigurationList;
buildConfigurations = (
83CBBA201A601CBA00E9B192 /* Debug */,

View File

@@ -29,9 +29,9 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
BuildableName = "jitsi-meet-react.app"
BlueprintName = "jitsi-meet-react"
ReferencedContainer = "container:jitsi-meet-react.xcodeproj">
BuildableName = "jitsi-meet.app"
BlueprintName = "jitsi-meet"
ReferencedContainer = "container:jitsi-meet.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
@@ -47,9 +47,9 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
BuildableName = "jitsi-meet-react.app"
BlueprintName = "jitsi-meet-react"
ReferencedContainer = "container:jitsi-meet-react.xcodeproj">
BuildableName = "jitsi-meet.app"
BlueprintName = "jitsi-meet"
ReferencedContainer = "container:jitsi-meet.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
@@ -70,9 +70,9 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
BuildableName = "jitsi-meet-react.app"
BlueprintName = "jitsi-meet-react"
ReferencedContainer = "container:jitsi-meet-react.xcodeproj">
BuildableName = "jitsi-meet.app"
BlueprintName = "jitsi-meet"
ReferencedContainer = "container:jitsi-meet.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
@@ -89,9 +89,9 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
BuildableName = "jitsi-meet-react.app"
BlueprintName = "jitsi-meet-react"
ReferencedContainer = "container:jitsi-meet-react.xcodeproj">
BuildableName = "jitsi-meet.app"
BlueprintName = "jitsi-meet"
ReferencedContainer = "container:jitsi-meet.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>

View File

@@ -2,6 +2,6 @@
<Workspace
version = "1.0">
<FileRef
location = "group:jitsi-meet-react.xcodeproj">
location = "group:jitsi-meet.xcodeproj">
</FileRef>
</Workspace>

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

@@ -0,0 +1,20 @@
{
"en": "",
"bg": "",
"de": "",
"es": "",
"fr": "",
"hy": "",
"it": "",
"oc": "",
"pl": "",
"ptBR": "",
"ru": "",
"sk": "",
"sl": "",
"sv": "",
"tr": "",
"zhCN": "",
"nb": "",
"eo": ""
}

415
lang/main-vi.json Normal file
View File

@@ -0,0 +1,415 @@
{
"contactlist": "",
"addParticipants": "",
"roomLocked": "",
"roomUnlocked": "",
"passwordSetRemotely": "",
"connectionsettings": "",
"poweredby": "",
"feedback": "",
"inviteUrlDefaultMsg": "",
"me": "",
"speaker": "",
"raisedHand": "",
"defaultNickname": "",
"defaultLink": "",
"callingName": "",
"audioOnly": {
"audioOnly": "",
"featureToggleDisabled": "",
"howToDisable": ""
},
"userMedia": {
"react-nativeGrantPermissions": "",
"chromeGrantPermissions": "",
"androidGrantPermissions": "",
"firefoxGrantPermissions": "",
"operaGrantPermissions": "",
"iexplorerGrantPermissions": "",
"safariGrantPermissions": "",
"nwjsGrantPermissions": "",
"edgeGrantPermissions": ""
},
"keyboardShortcuts": {
"keyboardShortcuts": "",
"raiseHand": "",
"pushToTalk": "",
"toggleScreensharing": "",
"toggleFilmstrip": "",
"toggleShortcuts": "",
"focusLocal": "",
"focusRemote": "",
"toggleChat": "",
"mute": "",
"fullScreen": "",
"videoMute": "",
"showSpeakerStats": ""
},
"welcomepage": {
"disable": "",
"feature1": {
"content": "",
"title": ""
},
"feature2": {
"content": "",
"title": ""
},
"feature3": {
"content": "",
"title": ""
},
"feature4": {
"content": "",
"title": ""
},
"feature5": {
"content": "",
"title": ""
},
"feature6": {
"content": "",
"title": ""
},
"feature7": {
"content": "",
"title": ""
},
"feature8": {
"content": "",
"title": ""
},
"go": "",
"join": "",
"privacy": "",
"roomname": "",
"roomnamePlaceHolder": "",
"sendFeedback": "",
"terms": ""
},
"startupoverlay": {
"policyText": "",
"title": ""
},
"suspendedoverlay": {
"title": "",
"text": "",
"rejoinKeyTitle": ""
},
"toolbar": {
"audioonly": "",
"mute": "",
"videomute": "",
"authenticate": "",
"lock": "",
"invite": "",
"chat": "",
"etherpad": "",
"sharedvideo": "",
"sharescreen": "",
"fullscreen": "",
"sip": "",
"Settings": "",
"hangup": "",
"login": "",
"logout": "",
"dialpad": "",
"sharedVideoMutedPopup": "",
"micMutedPopup": "",
"talkWhileMutedPopup": "",
"unableToUnmutePopup": "",
"cameraDisabled": "",
"micDisabled": "",
"filmstrip": "",
"profile": "",
"raiseHand": ""
},
"unsupportedBrowser": {
"appInstalled": "",
"appNotInstalled": "",
"downloadApp": "",
"joinConversation": "",
"startConference": ""
},
"bottomtoolbar": {
"chat": "",
"filmstrip": "",
"contactlist": ""
},
"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": {
"editnickname": "",
"moderator": "",
"videomute": "",
"mute": "",
"kick": "",
"muted": "",
"domute": "",
"flip": "",
"remoteControl": ""
},
"connectionindicator": {
"header": "",
"bitrate": "",
"packetloss": "",
"resolution": "",
"framerate": "",
"less": "",
"more": "",
"address": "",
"remoteport": "",
"remoteport_plural_undefined": "",
"localport": "",
"localport_plural_undefined": "",
"localaddress": "",
"localaddress_plural_undefined": "",
"remoteaddress": "",
"remoteaddress_plural_undefined": "",
"transport": "",
"transport_plural_undefined": "",
"bandwidth": "",
"na": ""
},
"notify": {
"disconnected": "",
"moderator": "",
"connected": "",
"somebody": "",
"me": "",
"focus": "",
"focusFail": "",
"grantedTo": "",
"grantedToUnknown": "",
"muted": "",
"mutedTitle": "",
"raisedHand": ""
},
"dialog": {
"add": "",
"allow": "",
"kickMessage": "",
"popupError": "",
"passwordErrorTitle": "",
"passwordError": "",
"passwordError2": "",
"connectError": "",
"connectErrorWithMsg": "",
"incorrectPassword": "",
"connecting": "",
"copy": "",
"error": "",
"createPassword": "",
"detectext": "",
"failtoinstall": "",
"failedpermissions": "",
"conferenceReloadTitle": "",
"conferenceReloadMsg": "",
"conferenceDisconnectTitle": "",
"conferenceDisconnectMsg": "",
"rejoinNow": "",
"maxUsersLimitReached": "",
"lockTitle": "",
"lockMessage": "",
"warning": "",
"passwordNotSupported": "",
"internalErrorTitle": "",
"internalError": "",
"unableToSwitch": "",
"SLDFailure": "",
"SRDFailure": "",
"oops": "",
"currentPassword": "",
"passwordLabel": "",
"defaultError": "",
"passwordRequired": "",
"Ok": "",
"done": "",
"Remove": "",
"removePassword": "",
"shareVideoTitle": "",
"shareVideoLinkError": "",
"removeSharedVideoTitle": "",
"removeSharedVideoMsg": "",
"alreadySharedVideoMsg": "",
"WaitingForHost": "",
"WaitForHostMsg": "",
"IamHost": "",
"Cancel": "",
"Submit": "",
"retry": "",
"logoutTitle": "",
"logoutQuestion": "",
"sessTerminated": "",
"hungUp": "",
"joinAgain": "",
"Share": "",
"Save": "",
"recording": "",
"recordingToken": "",
"Dial": "",
"sipMsg": "",
"passwordCheck": "",
"passwordMsg": "",
"shareLink": "",
"settings1": "",
"settings2": "",
"settings3": "",
"yourPassword": "",
"Back": "",
"serviceUnavailable": "",
"gracefulShutdown": "",
"Yes": "",
"reservationError": "",
"reservationErrorMsg": "",
"password": "",
"userPassword": "",
"token": "",
"tokenAuthFailedTitle": "",
"tokenAuthFailed": "",
"displayNameRequired": "",
"enterDisplayName": "",
"extensionRequired": "",
"firefoxExtensionPrompt": "",
"rateExperience": "",
"feedbackHelp": "",
"feedbackQuestion": "",
"thankYou": "",
"sorryFeedback": "",
"liveStreaming": "",
"streamKey": "",
"startLiveStreaming": "",
"stopStreamingWarning": "",
"stopRecordingWarning": "",
"stopLiveStreaming": "",
"stopRecording": "",
"doNotShowWarningAgain": "",
"doNotShowMessageAgain": "",
"permissionDenied": "",
"screenSharingPermissionDeniedError": "",
"micErrorPresent": "",
"cameraErrorPresent": "",
"cameraUnsupportedResolutionError": "",
"cameraUnknownError": "",
"cameraPermissionDeniedError": "",
"cameraNotFoundError": "",
"cameraConstraintFailedError": "",
"micUnknownError": "",
"micPermissionDeniedError": "",
"micNotFoundError": "",
"micConstraintFailedError": "",
"micNotSendingData": "",
"cameraNotSendingData": "",
"goToStore": "",
"externalInstallationTitle": "",
"externalInstallationMsg": "",
"muteParticipantTitle": "",
"muteParticipantBody": "",
"muteParticipantButton": "",
"remoteControlTitle": "",
"remoteControlRequestMessage": "",
"remoteControlShareScreenWarning": "",
"remoteControlDeniedMessage": "",
"remoteControlAllowedMessage": "",
"remoteControlErrorMessage": "",
"remoteControlStopMessage": "",
"close": "",
"shareYourScreen": "",
"yourEntireScreen": "",
"applicationWindow": ""
},
"email": {
"sharedKey": "",
"subject": "",
"body": "",
"and": ""
},
"connection": {
"ERROR": "",
"CONNECTING": "",
"RECONNECTING": "",
"CONNFAIL": "",
"AUTHENTICATING": "",
"AUTHFAIL": "",
"CONNECTED": "",
"DISCONNECTED": "",
"DISCONNECTING": "",
"ATTACHED": ""
},
"recording": {
"pending": "",
"on": "",
"off": "",
"failedToStart": "",
"buttonTooltip": "",
"error": "",
"unavailable": ""
},
"liveStreaming": {
"pending": "",
"on": "",
"off": "",
"unavailable": "",
"failedToStart": "",
"buttonTooltip": "",
"streamIdRequired": "",
"streamIdHelp": "",
"error": "",
"busy": ""
},
"speakerStats": {
"hours": "",
"minutes": "",
"name": "",
"seconds": "",
"speakerStats": "",
"speakerTime": ""
},
"deviceSelection": {
"deviceSettings": "",
"noPermission": "",
"previewUnavailable": "",
"selectADevice": "",
"testAudio": ""
},
"invite": {
"addPassword": "",
"dialInNumbers": "",
"errorFetchingNumbers": "",
"hidePassword": "",
"inviteTo": "",
"loadingNumbers": "",
"locked": "",
"noNumbers": "",
"numbersDisabled": "",
"showPassword": "",
"unlocked": ""
}
}

View File

@@ -14,6 +14,10 @@
"defaultNickname": "ex. Jane Pink",
"defaultLink": "e.g. __url__",
"callingName": "__name__",
"audioOnly": {
"audioOnly": "Audio only",
"featureToggleDisabled": "Toggling of __feature__ is disabled while in audio only mode"
},
"userMedia": {
"react-nativeGrantPermissions": "Select <b><i>Allow</i></b> when your browser asks for permissions.",
"chromeGrantPermissions": "Select <b><i>Allow</i></b> when your browser asks for permissions.",
@@ -92,6 +96,7 @@
"rejoinKeyTitle": "Rejoin"
},
"toolbar": {
"audioonly": "Enable / Disable audio only mode (saves bandwidth)",
"mute": "Mute / Unmute",
"videomute": "Start / Stop camera",
"authenticate": "Authenticate",
@@ -149,12 +154,10 @@
"selectAudioOutput": "Audio output",
"followMe": "Everyone follows me",
"noDevice": "None",
"noPermission": "Permission to use device is not granted",
"cameraAndMic": "Camera and microphone",
"moderator": "MODERATOR",
"password": "SET PASSWORD",
"audioVideo": "AUDIO AND VIDEO",
"setPasswordLabel": "Lock your room with a password."
"audioVideo": "AUDIO AND VIDEO"
},
"profile": {
"title": "Profile",
@@ -214,6 +217,7 @@
},
"dialog": {
"add": "Add",
"allow": "Allow",
"kickMessage": "Ouch! You have been kicked out of the meet!",
"popupError": "Your browser is blocking popup windows from this site. Please enable popups in your browser's security settings and try again.",
"passwordErrorTitle": "Password Error",
@@ -225,8 +229,6 @@
"connecting": "Connecting",
"copy": "Copy",
"error": "Error",
"roomLocked": "This call is locked. New callers must have the link and enter the password to join",
"addPassword": "Add a password",
"createPassword": "Create password",
"detectext": "Error when trying to detect desktopsharing extension.",
"failtoinstall": "Failed to install desktop sharing extension",
@@ -275,8 +277,6 @@
"Save": "Save",
"recording": "Recording",
"recordingToken": "Enter recording token",
"Dial": "Dial",
"sipMsg": "Enter SIP number",
"passwordCheck": "Are you sure you would like to remove your password?",
"passwordMsg": "Set a password to lock your room",
"shareLink": "Share the link to the call",
@@ -321,11 +321,11 @@
"cameraUnknownError": "Cannot use camera for a unknown reason.",
"cameraPermissionDeniedError": "You have not granted permission to use your camera. You can still join the conference but others won't see you. Use the camera button in the address bar to fix this.",
"cameraNotFoundError": "Camera was not found.",
"cameraConstraintFailedError": "Yor camera does not satisfy some of required constraints.",
"cameraConstraintFailedError": "Your camera does not satisfy some of the required constraints.",
"micUnknownError": "Cannot use microphone for a unknown reason.",
"micPermissionDeniedError": "You have not granted permission to use your microphone. You can still join the conference but others won't hear you. Use the camera button in the address bar to fix this.",
"micNotFoundError": "Microphone was not found.",
"micConstraintFailedError": "Yor microphone does not satisfy some of required constraints.",
"micConstraintFailedError": "Your microphone does not satisfy some of the required constraints.",
"micNotSendingData": "We are unable to access your microphone. Please select another device from the settings menu or try to restart the application.",
"cameraNotSendingData": "We are unable to access your camera. Please check if another application is using this device, select another device from the settings menu or try to restart the application.",
"goToStore": "Go to the webstore",
@@ -334,7 +334,9 @@
"muteParticipantTitle": "Mute this participant?",
"muteParticipantBody": "You won't be able to unmute them, but they can unmute themselves at any time.",
"muteParticipantButton": "Mute",
"remoteControlTitle": "Remote Control",
"remoteControlTitle": "Remote desktop control",
"remoteControlRequestMessage": "Will you allow __user__ to remotely control your desktop?",
"remoteControlShareScreenWarning": "Note that if you press \"Allow\" you will share your screen!",
"remoteControlDeniedMessage": "__user__ rejected your remote control request!",
"remoteControlAllowedMessage": "__user__ accepted your remote control request!",
"remoteControlErrorMessage": "An error occurred while trying to request remote control permissions from __user__!",
@@ -422,5 +424,37 @@
"seconds": "__count__s",
"speakerStats": "Speaker Stats",
"speakerTime": "Speaker Time"
},
"deviceSelection": {
"deviceSettings": "Device settings",
"noPermission": "Permission not granted",
"previewUnavailable": "Preview unavailable",
"selectADevice": "Select a device",
"testAudio": "Test sound"
},
"invite": {
"addPassword": "Add password",
"callNumber": "Call __number__",
"enterId": "Enter Meeting ID: __meetingId__ following by # to dial in from a phone",
"howToDialIn": "To dial in, use one of the following numbers and meeting ID",
"hidePassword": "Hide password",
"inviteTo": "Invite people to __conferenceName__",
"invitedYouTo": "__userName__ has invited you to the __meetingUrl__ conference",
"locked": "This call is locked. New callers must have the link and enter the password to join.",
"showPassword": "Show password",
"unlocked": "This call is unlocked. Any new caller with the link may join the call."
},
"videoStatus": {
"hd": "HD",
"hdVideo": "HD video",
"sd": "SD",
"sdVideo": "SD video"
},
"dialOut": {
"dial": "Dial",
"dialOut": "Call a phone number",
"statusMessage": "is now __status__",
"enterPhone": "Enter phone number",
"phoneNotAllowed": "Oh, we don't support that destination yet! Sorry!"
}
}

0
local.html Normal file
View File

View File

@@ -5,5 +5,6 @@ var loggingConfig = { // eslint-disable-line no-unused-vars
// Option to disable LogCollector (which stores the logs on CallStats)
//disableLogCollector: true,
// Logging level adjustments for verbose modules:
'modules/xmpp/strophe.util.js': 'log'
'modules/xmpp/strophe.util.js': 'log',
'modules/statistics/CallStats.js': 'info'
};

3
modules/API/.eslintrc.js Normal file
View File

@@ -0,0 +1,3 @@
module.exports = {
'extends': '../../react/.eslintrc.js'
};

View File

@@ -1,240 +1,271 @@
/* global APP, getConfigParamsFromUrl */
import * as JitsiMeetConferenceEvents from '../../ConferenceEvents';
import { getJitsiMeetTransport } from '../transport';
/**
* Implements API class that communicates with external api class
* and provides interface to access Jitsi Meet features by external
* applications that embed Jitsi Meet
*/
import { API_ID } from './constants';
import postisInit from 'postis';
declare var APP: Object;
/**
* List of the available commands.
* @type {{
* displayName: inputDisplayNameHandler,
* toggleAudio: toggleAudio,
* toggleVideo: toggleVideo,
* toggleFilmStrip: toggleFilmStrip,
* toggleChat: toggleChat,
* toggleContactList: toggleContactList
* }}
*/
let commands = {};
let hashParams = getConfigParamsFromUrl();
/**
* The state of screen sharing(started/stopped) before the screen sharing is
* enabled and initialized.
* NOTE: This flag help us to cache the state and use it if toggle-share-screen
* was received before the initialization.
*/
let initialScreenSharingState = false;
/**
* JitsiMeetExternalAPI id - unique for a webpage.
* The transport instance used for communication with external apps.
*
* @type {Transport}
*/
let jitsi_meet_external_api_id = hashParams.jitsi_meet_external_api_id;
const transport = getJitsiMeetTransport();
/**
* Object that will execute sendMessage
* Initializes supported commands.
*
* @returns {void}
*/
let target = window.opener ? window.opener : window.parent;
/**
* Postis instance. Used to communicate with the external application.
*/
let postis;
/**
* Current status (enabled/disabled) of API.
*/
let enabled = false;
function initCommands() {
commands = {
"display-name":
'display-name':
APP.conference.changeLocalDisplayName.bind(APP.conference),
"toggle-audio": APP.conference.toggleAudioMuted.bind(APP.conference),
"toggle-video": APP.conference.toggleVideoMuted.bind(APP.conference),
"toggle-film-strip": APP.UI.toggleFilmStrip,
"toggle-chat": APP.UI.toggleChat,
"toggle-contact-list": APP.UI.toggleContactList,
"toggle-share-screen":
APP.conference.toggleScreenSharing.bind(APP.conference),
"video-hangup": () => APP.conference.hangup(),
"email": APP.conference.changeLocalEmail,
"avatar-url": APP.conference.changeLocalAvatarUrl,
"remote-control-event": event =>
APP.remoteControl.onRemoteControlAPIEvent(event)
'toggle-audio': () => APP.conference.toggleAudioMuted(true),
'toggle-video': () => APP.conference.toggleVideoMuted(true),
'toggle-film-strip': APP.UI.toggleFilmstrip,
'toggle-chat': APP.UI.toggleChat,
'toggle-contact-list': APP.UI.toggleContactList,
'toggle-share-screen': toggleScreenSharing,
'video-hangup': () => APP.conference.hangup(),
'email': APP.conference.changeLocalEmail,
'avatar-url': APP.conference.changeLocalAvatarUrl
};
Object.keys(commands).forEach(function (key) {
postis.listen(key, args => commands[key](...args));
transport.on('event', ({ data, name }) => {
if (name && commands[name]) {
commands[name](...data);
return true;
}
return false;
});
}
/**
* Sends message to the external application.
* @param message {object}
* @param method {string}
* @param params {object} the object that will be sent as JSON string
* Listens for desktop/screen sharing enabled events and toggles the screen
* sharing if needed.
*
* @param {boolean} enabled - Current screen sharing enabled status.
* @returns {void}
*/
function sendMessage(message) {
if(enabled) {
postis.send(message);
function onDesktopSharingEnabledChanged(enabled = false) {
if (enabled && initialScreenSharingState) {
toggleScreenSharing();
}
}
/**
* Check whether the API should be enabled or not.
*
* @returns {boolean}
*/
function shouldBeEnabled () {
return (typeof jitsi_meet_external_api_id === "number");
function shouldBeEnabled() {
return typeof API_ID === 'number';
}
/**
* Sends event object to the external application that has been subscribed
* for that event.
* @param name the name event
* @param object data associated with the event
* Executes on toggle-share-screen command.
*
* @returns {void}
*/
function triggerEvent (name, object) {
if(enabled) {
sendMessage({method: name, params: object});
function toggleScreenSharing() {
if (APP.conference.isDesktopSharingEnabled) {
APP.conference.toggleScreenSharing();
} else {
initialScreenSharingState = !initialScreenSharingState;
}
}
/**
* Implements API class that communicates with external API class and provides
* interface to access Jitsi Meet features by external applications that embed
* Jitsi Meet.
*/
class API {
/**
* Constructs new instance
* @constructor
* Initializes the API. Setups message event listeners that will receive
* information from external applications that embed Jitsi Meet. It also
* sends a message to the external application that API is initialized.
*
* @param {Object} options - Optional parameters.
* @param {boolean} options.forceEnable - True to forcefully enable the
* module.
* @returns {void}
*/
constructor() { }
/**
* Initializes the APIConnector. Setups message event listeners that will
* receive information from external applications that embed Jitsi Meet.
* It also sends a message to the external application that APIConnector
* is initialized.
* @param options {object}
* @param forceEnable {boolean} if true the module will be enabled.
*/
init (options = {}) {
if(!shouldBeEnabled() && !options.forceEnable)
init({ forceEnable } = {}) {
if (!shouldBeEnabled() && !forceEnable) {
return;
enabled = true;
if(!postis) {
this._initPostis();
}
}
/**
* initializes postis library.
* @private
*/
_initPostis() {
let postisOptions = {
window: target
};
if(typeof jitsi_meet_external_api_id === "number")
postisOptions.scope
= "jitsi_meet_external_api_" + jitsi_meet_external_api_id;
postis = postisInit(postisOptions);
/**
* Current status (enabled/disabled) of API.
*
* @private
* @type {boolean}
*/
this._enabled = true;
APP.conference.addListener(
JitsiMeetConferenceEvents.DESKTOP_SHARING_ENABLED_CHANGED,
onDesktopSharingEnabledChanged);
initCommands();
}
/**
* Notify external application (if API is enabled) that message was sent.
* @param {string} body message body
* Sends event to the external application.
*
* @param {Object} event - The event to be sent.
* @returns {void}
*/
notifySendingChatMessage (body) {
triggerEvent("outgoing-message", {"message": body});
_sendEvent(event = {}) {
if (this._enabled) {
transport.sendEvent(event);
}
}
/**
* Notify external application (if API is enabled) that
* message was received.
* @param {string} id user id
* @param {string} nick user nickname
* @param {string} body message body
* @param {number} ts message creation timestamp
* Notify external application (if API is enabled) that message was sent.
*
* @param {string} message - Message body.
* @returns {void}
*/
notifyReceivedChatMessage (id, nick, body, ts) {
notifySendingChatMessage(message) {
this._sendEvent({
name: 'outgoing-message',
message
});
}
/**
* Notify external application (if API is enabled) that message was
* received.
*
* @param {Object} options - Object with the message properties.
* @returns {void}
*/
notifyReceivedChatMessage({ body, id, nick, ts } = {}) {
if (APP.conference.isLocalId(id)) {
return;
}
triggerEvent(
"incoming-message",
{"from": id, "nick": nick, "message": body, "stamp": ts}
);
this._sendEvent({
name: 'incoming-message',
from: id,
message: body,
nick,
stamp: ts
});
}
/**
* Notify external application (if API is enabled) that
* user joined the conference.
* @param {string} id user id
* Notify external application (if API is enabled) that user joined the
* conference.
*
* @param {string} id - User id.
* @returns {void}
*/
notifyUserJoined (id) {
triggerEvent("participant-joined", {id});
notifyUserJoined(id) {
this._sendEvent({
name: 'participant-joined',
id
});
}
/**
* Notify external application (if API is enabled) that
* user left the conference.
* @param {string} id user id
* Notify external application (if API is enabled) that user left the
* conference.
*
* @param {string} id - User id.
* @returns {void}
*/
notifyUserLeft (id) {
triggerEvent("participant-left", {id});
notifyUserLeft(id) {
this._sendEvent({
name: 'participant-left',
id
});
}
/**
* Notify external application (if API is enabled) that
* user changed their nickname.
* @param {string} id user id
* @param {string} displayName user nickname
* Notify external application (if API is enabled) that user changed their
* nickname.
*
* @param {string} id - User id.
* @param {string} displayname - User nickname.
* @returns {void}
*/
notifyDisplayNameChanged (id, displayName) {
triggerEvent("display-name-change", {id, displayname: displayName});
notifyDisplayNameChanged(id, displayname) {
this._sendEvent({
name: 'display-name-change',
displayname,
id
});
}
/**
* Notify external application (if API is enabled) that
* user changed their nickname.
* @param {string} id user id
* @param {string} displayName user nickname
* Notify external application (if API is enabled) that the conference has
* been joined.
*
* @param {string} roomName - The room name.
* @returns {void}
*/
notifyConferenceJoined (room) {
triggerEvent("video-conference-joined", {roomName: room});
notifyConferenceJoined(roomName) {
this._sendEvent({
name: 'video-conference-joined',
roomName
});
}
/**
* Notify external application (if API is enabled) that
* user changed their nickname.
* @param {string} id user id
* @param {string} displayName user nickname
* Notify external application (if API is enabled) that user changed their
* nickname.
*
* @param {string} roomName - User id.
* @returns {void}
*/
notifyConferenceLeft (room) {
triggerEvent("video-conference-left", {roomName: room});
notifyConferenceLeft(roomName) {
this._sendEvent({
name: 'video-conference-left',
roomName
});
}
/**
* Notify external application (if API is enabled) that
* we are ready to be closed.
* Notify external application (if API is enabled) that we are ready to be
* closed.
*
* @returns {void}
*/
notifyReadyToClose () {
triggerEvent("video-ready-to-close", {});
notifyReadyToClose() {
this._sendEvent({ name: 'video-ready-to-close' });
}
/**
* Sends remote control event.
* @param {RemoteControlEvent} event the remote control event.
* Disposes the allocated resources.
*
* @returns {void}
*/
sendRemoteControlEvent(event) {
sendMessage({method: "remote-control-event", params: event});
}
/**
* Removes the listeners.
*/
dispose () {
if(enabled)
postis.destroy();
dispose() {
if (this._enabled) {
this._enabled = false;
APP.conference.removeListener(
JitsiMeetConferenceEvents.DESKTOP_SHARING_ENABLED_CHANGED,
onDesktopSharingEnabledChanged);
}
}
}

13
modules/API/constants.js Normal file
View File

@@ -0,0 +1,13 @@
// XXX The function parseURLParams is exported by the feature base/config (as
// defined in the terminology of react/). However, this file is (very likely)
// bundled in external_api in addition to app.bundle and, consequently, it is
// best to import as little as possible here (rather than the whole feature
// base/config) in order to minimize the amount of source code bundled into
// multiple bundles.
import parseURLParams from '../../react/features/base/config/parseURLParams';
/**
* JitsiMeetExternalAPI id - unique for a webpage.
*/
export const API_ID
= parseURLParams(window.location).jitsi_meet_external_api_id;

View File

@@ -1,80 +1,70 @@
const logger = require("jitsi-meet-logger").getLogger(__filename);
import EventEmitter from 'events';
/**
* Implements API class that embeds Jitsi Meet in external applications.
*/
import {
PostMessageTransportBackend,
Transport
} from '../../transport';
var postisInit = require("postis");
/**
* The minimum width for the Jitsi Meet frame
* @type {number}
*/
var MIN_WIDTH = 790;
/**
* The minimum height for the Jitsi Meet frame
* @type {number}
*/
var MIN_HEIGHT = 300;
/**
* Last id of api object
* @type {number}
*/
var id = 0;
const logger = require('jitsi-meet-logger').getLogger(__filename);
/**
* Maps the names of the commands expected by the API with the name of the
* commands expected by jitsi-meet
*/
var commands = {
"displayName": "display-name",
"toggleAudio": "toggle-audio",
"toggleVideo": "toggle-video",
"toggleFilmStrip": "toggle-film-strip",
"toggleChat": "toggle-chat",
"toggleContactList": "toggle-contact-list",
"toggleShareScreen": "toggle-share-screen",
"hangup": "video-hangup",
"email": "email",
"avatarUrl": "avatar-url"
const commands = {
avatarUrl: 'avatar-url',
displayName: 'display-name',
email: 'email',
hangup: 'video-hangup',
toggleAudio: 'toggle-audio',
toggleChat: 'toggle-chat',
toggleContactList: 'toggle-contact-list',
toggleFilmStrip: 'toggle-film-strip',
toggleShareScreen: 'toggle-share-screen',
toggleVideo: 'toggle-video'
};
/**
* Maps the names of the events expected by the API with the name of the
* events expected by jitsi-meet
*/
var events = {
"incomingMessage": "incoming-message",
"outgoingMessage": "outgoing-message",
"displayNameChange": "display-name-change",
"participantJoined": "participant-joined",
"participantLeft": "participant-left",
"videoConferenceJoined": "video-conference-joined",
"videoConferenceLeft": "video-conference-left",
"readyToClose": "video-ready-to-close"
const events = {
'display-name-change': 'displayNameChange',
'incoming-message': 'incomingMessage',
'outgoing-message': 'outgoingMessage',
'participant-joined': 'participantJoined',
'participant-left': 'participantLeft',
'video-ready-to-close': 'readyToClose',
'video-conference-joined': 'videoConferenceJoined',
'video-conference-left': 'videoConferenceLeft'
};
/**
* Sends the passed object to Jitsi Meet
* @param postis {Postis object} the postis instance that is going to be used
* to send the message
* @param object the object to be sent
* - method {sting}
* - params {object}
* Last id of api object
* @type {number}
*/
function sendMessage(postis, object) {
postis.send(object);
}
let id = 0;
/**
* The minimum height for the Jitsi Meet frame
* @type {number}
*/
const MIN_HEIGHT = 300;
/**
* The minimum width for the Jitsi Meet frame
* @type {number}
*/
const MIN_WIDTH = 790;
/**
* Adds given number to the numberOfParticipants property of given APIInstance.
* @param {JitsiMeetExternalAPI} APIInstance the instance of the
* JitsiMeetExternalAPI
* @param {int} number - the number of participants to be added to
*
* @param {JitsiMeetExternalAPI} APIInstance - The instance of the API.
* @param {int} number - The number of participants to be added to
* numberOfParticipants property (this parameter can be negative number if the
* numberOfParticipants should be decreased).
* @returns {void}
*/
function changeParticipantNumber(APIInstance, number) {
APIInstance.numberOfParticipants += number;
@@ -84,323 +74,355 @@ function changeParticipantNumber(APIInstance, number) {
* Generates array with URL params based on the passed config object that will
* be used for the Jitsi Meet URL generation.
*
* @param config {object} the config object.
* @returns {Array<string>} the array with URL param strings.
* @param {Object} config - The config object.
* @returns {Array<string>} The array with URL param strings.
*/
function configToURLParamsArray(config) {
function configToURLParamsArray(config = {}) {
const params = [];
for (const key in config) {
for (const key in config) { // eslint-disable-line guard-for-in
try {
params.push(key + '='
+ encodeURIComponent(JSON.stringify(config[key])));
params.push(
`${key}=${encodeURIComponent(JSON.stringify(config[key]))}`);
} catch (e) {
console.warn(`Error encoding ${key}: ${e}`);
}
}
return params;
}
/**
* Constructs new API instance. Creates iframe element that loads
* Jitsi Meet.
* @param domain the domain name of the server that hosts the conference
* @param room_name the name of the room to join
* @param width width of the iframe
* @param height height of the iframe
* @param parent_node the node that will contain the iframe
* @param configOverwrite object containing configuration options defined in
* config.js to be overridden.
* @param interfaceConfigOverwrite object containing configuration options
* defined in interface_config.js to be overridden.
* @param noSsl if the value is true https won't be used
* @param {string} [jwt] the JWT token if needed by jitsi-meet for
* Generates the URL for the iframe.
*
* @param {string} domain - The domain name of the server that hosts the
* conference.
* @param {string} [options] - Another optional parameters.
* @param {Object} [options.configOverwrite] - Object containing configuration
* options defined in config.js to be overridden.
* @param {Object} [options.interfaceConfigOverwrite] - Object containing
* configuration options defined in interface_config.js to be overridden.
* @param {string} [options.jwt] - The JWT token if needed by jitsi-meet for
* authentication.
* @constructor
* @param {boolean} [options.noSsl] - If the value is true https won't be used.
* @param {string} [options.roomName] - The name of the room to join.
* @returns {string} The URL.
*/
function JitsiMeetExternalAPI(domain, room_name, width, height, parentNode,
configOverwrite, interfaceConfigOverwrite, noSsl, jwt) {
if (!width || width < MIN_WIDTH)
width = MIN_WIDTH;
if (!height || height < MIN_HEIGHT)
height = MIN_HEIGHT;
function generateURL(domain, options = {}) {
const {
configOverwrite,
interfaceConfigOverwrite,
jwt,
noSSL,
roomName
} = options;
this.parentNode = null;
if (parentNode) {
this.parentNode = parentNode;
} else {
var scriptTag = document.scripts[document.scripts.length - 1];
this.parentNode = scriptTag.parentNode;
}
this.iframeHolder =
this.parentNode.appendChild(document.createElement("div"));
this.iframeHolder.id = "jitsiConference" + id;
if(width)
this.iframeHolder.style.width = width + "px";
if(height)
this.iframeHolder.style.height = height + "px";
this.frameName = "jitsiConferenceFrame" + id;
this.url = (noSsl) ? "http" : "https" +"://" + domain + "/";
if(room_name)
this.url += room_name;
let url = `${noSSL ? 'http' : 'https'}://${domain}/${roomName || ''}`;
if (jwt) {
this.url += '?jwt=' + jwt;
url += `?jwt=${jwt}`;
}
this.url += "#jitsi_meet_external_api_id=" + id;
url += `#jitsi_meet_external_api_id=${id}`;
const configURLParams = configToURLParamsArray(configOverwrite);
if (configURLParams.length) {
this.url += '&config.' + configURLParams.join('&config.');
url += `&config.${configURLParams.join('&config.')}`;
}
const interfaceConfigURLParams
= configToURLParamsArray(interfaceConfigOverwrite);
if (interfaceConfigURLParams.length) {
this.url += '&interfaceConfig.'
+ interfaceConfigURLParams.join('&interfaceConfig.');
url += `&interfaceConfig.${
interfaceConfigURLParams.join('&interfaceConfig.')}`;
}
this.frame = document.createElement("iframe");
this.frame.src = this.url;
this.frame.name = this.frameName;
this.frame.id = this.frameName;
this.frame.width = "100%";
this.frame.height = "100%";
this.frame.setAttribute("allowFullScreen","true");
this.frame = this.iframeHolder.appendChild(this.frame);
this.postis = postisInit({
window: this.frame.contentWindow,
scope: "jitsi_meet_external_api_" + id
});
this.eventHandlers = {};
// Map<{string} event_name, {boolean} postis_listener_added>
this.postisListeners = {};
this.numberOfParticipants = 1;
this._setupListeners();
id++;
return url;
}
/**
* 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
* filmStrip - hides / shows the film strip with no arguments
* If the command doesn't require any arguments the parameter should be set
* to empty array or it may be omitted.
* @param name the name of the command
* @param arguments array of arguments
* The IFrame API interface class.
*/
JitsiMeetExternalAPI.prototype.executeCommand
= function(name, ...argumentsList) {
if(!(name in commands)) {
logger.error("Not supported command name.");
return;
class JitsiMeetExternalAPI extends EventEmitter {
/**
* Constructs new API instance. Creates iframe and loads Jitsi Meet in it.
*
* @param {string} domain - The domain name of the server that hosts the
* conference.
* @param {string} [roomName] - The name of the room to join.
* @param {number} [width] - Width of the iframe.
* @param {number} [height] - Height of the iframe.
* @param {DOMElement} [parentNode] - The node that will contain the
* iframe.
* @param {Object} [configOverwrite] - Object containing configuration
* options defined in config.js to be overridden.
* @param {Object} [interfaceConfigOverwrite] - Object containing
* configuration options defined in interface_config.js to be overridden.
* @param {boolean} [noSSL] - If the value is true https won't be used.
* @param {string} [jwt] - The JWT token if needed by jitsi-meet for
* authentication.
*/
constructor(domain, // eslint-disable-line max-params
roomName = '',
width = MIN_WIDTH,
height = MIN_HEIGHT,
parentNode = document.body,
configOverwrite = {},
interfaceConfigOverwrite = {},
noSSL = false,
jwt = undefined) {
super();
this.parentNode = parentNode;
this.url = generateURL(domain, {
configOverwrite,
interfaceConfigOverwrite,
jwt,
noSSL,
roomName
});
this._createIFrame(Math.max(height, MIN_HEIGHT),
Math.max(width, MIN_WIDTH));
this._transport = new Transport({
backend: new PostMessageTransportBackend({
postisOptions: {
scope: `jitsi_meet_external_api_${id}`,
window: this.frame.contentWindow
}
})
});
this.numberOfParticipants = 1;
this._setupListeners();
id++;
}
sendMessage(this.postis, {method: commands[name], params: argumentsList});
};
/**
* 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
* filmStrip - hides / shows the film strip. no arguments
* toggleChat - hides / shows chat. no arguments.
* toggleContactList - hides / shows contact list. no arguments.
* toggleShareScreen - starts / stops screen sharing. no arguments.
* @param object the object with commands to be executed. The keys of the
* object are the commands that will be executed and the values are the
* arguments for the command.
*/
JitsiMeetExternalAPI.prototype.executeCommands = function(object) {
for(var key in object)
this.executeCommand(key, object[key]);
};
/**
* Creates the iframe element.
*
* @param {number} height - The height of the iframe.
* @param {number} width - The with of the iframe.
* @returns {void}
*
* @private
*/
_createIFrame(height, width) {
this.iframeHolder
= this.parentNode.appendChild(document.createElement('div'));
this.iframeHolder.id = `jitsiConference${id}`;
this.iframeHolder.style.width = `${width}px`;
this.iframeHolder.style.height = `${height}px`;
/**
* Adds event listeners to Meet Jitsi. The object key should be the name of
* the event and value - the listener.
* Currently we support the following
* events:
* 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
* 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:
* {{
* 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.
* 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.
* 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.
* 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.
* The listener will receive object with the following structure:
* {{
* roomName: room //the room name of the conference
* }}
* readyToClose - all hangup operations are completed and Jitsi Meet is ready
* to be disposed.
* @param object
*/
JitsiMeetExternalAPI.prototype.addEventListeners = function(object) {
for(var i in object)
this.addEventListener(i, object[i]);
};
this.frameName = `jitsiConferenceFrame${id}`;
/**
* Adds event listeners to Meet Jitsi. Currently we support the following
* events:
* 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
* 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:
* {{
* 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.
* The listener will receive object with the following structure:
* {{
* jid: jid //the jid of the participant
* }}
* participantLeft - receives event notifications about participant the 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 fired when the local
* user has 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 fired when the local
* user has joined the video conference.
* The listener will receive object with the following structure:
* {{
* roomName: room //the room name of the conference
* }}
* @param event the name of the event
* @param listener the listener
*/
JitsiMeetExternalAPI.prototype.addEventListener = function(event, listener) {
if(!(event in events)) {
logger.error("Not supported event name.");
return;
this.frame = document.createElement('iframe');
this.frame.src = this.url;
this.frame.name = this.frameName;
this.frame.id = this.frameName;
this.frame.width = '100%';
this.frame.height = '100%';
this.frame.setAttribute('allowFullScreen', 'true');
this.frame = this.iframeHolder.appendChild(this.frame);
}
// We cannot remove listeners from postis that's why we are handling the
// callback that way.
if(!this.postisListeners[event]) {
this.postis.listen(events[event], function(data) {
if((event in this.eventHandlers) &&
typeof this.eventHandlers[event] === "function")
this.eventHandlers[event].call(null, data);
}.bind(this));
this.postisListeners[event] = true;
/**
* Setups listeners that are used internally for JitsiMeetExternalAPI.
*
* @returns {void}
*
* @private
*/
_setupListeners() {
this._transport.on('event', ({ name, ...data }) => {
if (name === 'participant-joined') {
changeParticipantNumber(this, 1);
} else if (name === 'participant-left') {
changeParticipantNumber(this, -1);
}
const eventName = events[name];
if (eventName) {
this.emit(eventName, data);
return true;
}
return false;
});
}
this.eventHandlers[event] = listener;
};
/**
* Removes event listener.
* @param event the name of the event.
*/
JitsiMeetExternalAPI.prototype.removeEventListener = function(event) {
if(!(event in this.eventHandlers))
{
logger.error("The event " + event + " is not registered.");
return;
/**
* Adds event listener to Meet Jitsi.
*
* @param {string} event - The name of the event.
* @param {Function} listener - The listener.
* @returns {void}
*
* @deprecated
* NOTE: This method is not removed for backward comatability purposes.
*/
addEventListener(event, listener) {
this.on(event, listener);
}
delete this.eventHandlers[event];
};
/**
* Removes event listeners.
* @param events array with the names of the events.
*/
JitsiMeetExternalAPI.prototype.removeEventListeners = function(events) {
for(var i = 0; i < events.length; i++)
this.removeEventListener(events[i]);
};
/**
* Adds event listeners to Meet Jitsi.
*
* @param {Object} listeners - The object key should be the name of
* the event and value - the listener.
* Currently we support the following
* events:
* 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
* 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:
* {{
* 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.
* 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.
* 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.
* 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.
* The listener will receive object with the following structure:
* {{
* roomName: room //the room name of the conference
* }}
* readyToClose - all hangup operations are completed and Jitsi Meet is
* ready to be disposed.
* @returns {void}
*
* @deprecated
* NOTE: This method is not removed for backward comatability purposes.
*/
addEventListeners(listeners) {
for (const event in listeners) { // eslint-disable-line guard-for-in
this.addEventListener(event, listeners[event]);
}
}
/**
* Returns the number of participants in the conference.
* NOTE: the local participant is included.
* @returns {int} the number of participants in the conference.
*/
JitsiMeetExternalAPI.prototype.getNumberOfParticipants = function() {
return this.numberOfParticipants;
};
/**
* Removes the listeners and removes the Jitsi Meet frame.
*
* @returns {void}
*/
dispose() {
this._transport.dispose();
this.removeAllListeners();
if (this.iframeHolder) {
this.iframeHolder.parentNode.removeChild(this.iframeHolder);
}
}
/**
* Setups listeners that are used internally for JitsiMeetExternalAPI.
*/
JitsiMeetExternalAPI.prototype._setupListeners = function() {
this.postis.listen("participant-joined",
changeParticipantNumber.bind(null, this, 1));
this.postis.listen("participant-left",
changeParticipantNumber.bind(null, this, -1));
};
/**
* 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.
* If the command doesn't require any arguments the parameter should be set
* to empty array or it may be omitted.
*
* @param {string} name - The name of the command.
* @returns {void}
*/
executeCommand(name, ...args) {
if (!(name in commands)) {
logger.error('Not supported command name.');
/**
* Removes the listeners and removes the Jitsi Meet frame.
*/
JitsiMeetExternalAPI.prototype.dispose = function() {
this.postis.destroy();
var frame = document.getElementById(this.frameName);
if(frame)
frame.src = 'about:blank';
var self = this;
window.setTimeout(function () {
self.iframeHolder.removeChild(self.frame);
self.iframeHolder.parentNode.removeChild(self.iframeHolder);
}, 10);
};
return;
}
this._transport.sendEvent({
data: args,
name: commands[name]
});
}
/**
* 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.
* toggleContactList - hides / shows contact list. no arguments.
* 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
* values are the arguments for the command.
* @returns {void}
*/
executeCommands(commandList) {
for (const key in commandList) { // eslint-disable-line guard-for-in
this.executeCommand(key, commandList[key]);
}
}
/**
* Returns the number of participants in the conference. The local
* participant is included.
*
* @returns {int} The number of participants in the conference.
*/
getNumberOfParticipants() {
return this.numberOfParticipants;
}
/**
* Removes event listener.
*
* @param {string} event - The name of the event.
* @returns {void}
*
* @deprecated
* NOTE: This method is not removed for backward comatability purposes.
*/
removeEventListener(event) {
this.removeAllListeners(event);
}
/**
* Removes event listeners.
*
* @param {Array<string>} eventList - Array with the names of the events.
* @returns {void}
*
* @deprecated
* NOTE: This method is not removed for backward comatability purposes.
*/
removeEventListeners(eventList) {
eventList.forEach(event => this.removeEventListener(event));
}
}
module.exports = JitsiMeetExternalAPI;

2
modules/API/index.js Normal file
View File

@@ -0,0 +1,2 @@
export default from './API';
export * from './constants';

View File

@@ -54,13 +54,13 @@ class State {
this._propertyChangeCallback = propertyChangeCallback;
}
get filmStripVisible () { return this._filmStripVisible; }
get filmstripVisible () { return this._filmstripVisible; }
set filmStripVisible (b) {
var oldValue = this._filmStripVisible;
set filmstripVisible (b) {
var oldValue = this._filmstripVisible;
if (oldValue !== b) {
this._filmStripVisible = b;
this._firePropertyChange('filmStripVisible', oldValue, b);
this._filmstripVisible = b;
this._firePropertyChange('filmstripVisible', oldValue, b);
}
}
@@ -102,7 +102,7 @@ class State {
/**
* Represents the &quot;Follow Me&quot; feature which enables a moderator to
* (partially) control the user experience/interface (e.g. film strip
* (partially) control the user experience/interface (e.g. filmstrip
* visibility) of (other) non-moderator particiapnts.
*
* @author Lyubomir Marinov
@@ -143,7 +143,7 @@ class FollowMe {
* @private
*/
_setFollowMeInitialState() {
this._filmStripToggled.bind(this, this._UI.isFilmStripVisible());
this._filmstripToggled.bind(this, this._UI.isFilmstripVisible());
var pinnedId = VideoLayout.getPinnedId();
var isPinned = false;
@@ -169,9 +169,9 @@ class FollowMe {
* @private
*/
_addFollowMeListeners () {
this.filmStripEventHandler = this._filmStripToggled.bind(this);
this._UI.addListener(UIEvents.TOGGLED_FILM_STRIP,
this.filmStripEventHandler);
this.filmstripEventHandler = this._filmstripToggled.bind(this);
this._UI.addListener(UIEvents.TOGGLED_FILMSTRIP,
this.filmstripEventHandler);
var self = this;
this.pinnedEndpointEventHandler = function (smallVideo, isPinned) {
@@ -190,8 +190,8 @@ class FollowMe {
* @private
*/
_removeFollowMeListeners () {
this._UI.removeListener(UIEvents.TOGGLED_FILM_STRIP,
this.filmStripEventHandler);
this._UI.removeListener(UIEvents.TOGGLED_FILMSTRIP,
this.filmstripEventHandler);
this._UI.removeListener(UIEvents.TOGGLED_SHARED_DOCUMENT,
this.sharedDocEventHandler);
this._UI.removeListener(UIEvents.PINNED_ENDPOINT,
@@ -214,14 +214,14 @@ class FollowMe {
}
/**
* Notifies this instance that the (visibility of the) film strip was
* Notifies this instance that the (visibility of the) filmstrip was
* toggled (in the user interface of the local participant).
*
* @param filmStripVisible {Boolean} {true} if the film strip was shown (as
* a result of the toggle) or {false} if the film strip was hidden
* @param filmstripVisible {Boolean} {true} if the filmstrip was shown (as a
* result of the toggle) or {false} if the filmstrip was hidden
*/
_filmStripToggled (filmStripVisible) {
this._local.filmStripVisible = filmStripVisible;
_filmstripToggled (filmstripVisible) {
this._local.filmstripVisible = filmstripVisible;
}
/**
@@ -279,7 +279,7 @@ class FollowMe {
_COMMAND,
{
attributes: {
filmStripVisible: local.filmStripVisible,
filmstripVisible: local.filmstripVisible,
nextOnStage: local.nextOnStage,
sharedDocumentVisible: local.sharedDocumentVisible
}
@@ -316,32 +316,32 @@ class FollowMe {
// Applies the received/remote command to the user experience/interface
// of the local participant.
this._onFilmStripVisible(attributes.filmStripVisible);
this._onFilmstripVisible(attributes.filmstripVisible);
this._onNextOnStage(attributes.nextOnStage);
this._onSharedDocumentVisible(attributes.sharedDocumentVisible);
}
/**
* Process a film strip open / close event received from FOLLOW-ME
* Process a filmstrip open / close event received from FOLLOW-ME
* command.
* @param filmStripVisible indicates if the film strip has been shown or
* @param filmstripVisible indicates if the filmstrip has been shown or
* hidden
* @private
*/
_onFilmStripVisible(filmStripVisible) {
if (typeof filmStripVisible !== 'undefined') {
_onFilmstripVisible(filmstripVisible) {
if (typeof filmstripVisible !== 'undefined') {
// XXX The Command(s) API doesn't preserve the types (of
// attributes, at least) at the time of this writing so take into
// account that what originated as a Boolean may be a String on
// receipt.
filmStripVisible = (filmStripVisible == 'true');
filmstripVisible = (filmstripVisible == 'true');
// FIXME The UI (module) very likely doesn't (want to) expose its
// eventEmitter as a public field. I'm not sure at the time of this
// writing whether calling UI.toggleFilmStrip() is acceptable (from
// writing whether calling UI.toggleFilmstrip() is acceptable (from
// a design standpoint) either.
if (filmStripVisible !== this._UI.isFilmStripVisible())
this._UI.eventEmitter.emit(UIEvents.TOGGLE_FILM_STRIP);
if (filmstripVisible !== this._UI.isFilmstripVisible())
this._UI.eventEmitter.emit(UIEvents.TOGGLE_FILMSTRIP);
}
}

View File

@@ -4,6 +4,10 @@ const logger = require("jitsi-meet-logger").getLogger(__filename);
var UI = {};
import {
updateDeviceList
} from '../../react/features/base/devices';
import Chat from "./side_pannels/chat/Chat";
import SidePanels from "./side_pannels/SidePanels";
import Avatar from "./avatar/Avatar";
@@ -15,7 +19,7 @@ import SharedVideoManager from './shared_video/SharedVideo';
import Recording from "./recording/Recording";
import VideoLayout from "./videolayout/VideoLayout";
import FilmStrip from "./videolayout/FilmStrip";
import Filmstrip from "./videolayout/Filmstrip";
import SettingsMenu from "./side_pannels/settings/SettingsMenu";
import Profile from "./side_pannels/profile/Profile";
import Settings from "./../settings/Settings";
@@ -27,6 +31,9 @@ import {
setAudioMuted,
setVideoMuted
} from '../../react/features/base/media';
import {
openDeviceSelectionDialog
} from '../../react/features/device-selection';
import {
checkAutoEnableDesktopSharing,
dockToolbox,
@@ -36,7 +43,7 @@ import {
showDialPadButton,
showEtherpadButton,
showSharedVideoButton,
showSIPCallButton,
showDialOutButton,
showToolbox
} from '../../react/features/toolbox';
@@ -258,7 +265,7 @@ UI.mucJoined = function () {
/***
* Handler for toggling filmstrip
*/
UI.handleToggleFilmStrip = () => UI.toggleFilmStrip();
UI.handleToggleFilmstrip = () => UI.toggleFilmstrip();
/**
* Sets tooltip defaults.
@@ -300,7 +307,7 @@ UI.start = function () {
_setTooltipDefaults();
SideContainerToggler.init(eventEmitter);
FilmStrip.init(eventEmitter);
Filmstrip.init(eventEmitter);
VideoLayout.init(eventEmitter);
if (!interfaceConfig.filmStripOnly) {
@@ -318,12 +325,16 @@ UI.start = function () {
$("#videoconference_page").mousemove(debouncedShowToolbar);
// Initialise the recording module.
if (config.enableRecording) {
Recording.init(eventEmitter, config.recordingType);
}
// Initialize side panels
SidePanels.init(eventEmitter);
} else {
$("body").addClass("filmstrip-only");
UIUtil.setVisible('mainToolbarContainer', false);
FilmStrip.setupFilmStripOnly();
UI.showToolbar();
Filmstrip.setFilmstripOnly();
messageHandler.enableNotifications(false);
JitsiPopover.enabled = false;
}
@@ -351,9 +362,18 @@ UI.start = function () {
}
if(APP.tokenData.callee) {
UI.showRingOverlay();
}
const { callee } = APP.store.getState()['features/jwt'];
callee && UI.showRingOverlay();
};
/**
* Invokes cleanup of any deferred execution within relevant UI modules.
*
* @returns {void}
*/
UI.stopDaemons = () => {
VideoLayout.resetLargeVideo();
};
/**
@@ -524,7 +544,7 @@ UI.onPeerVideoTypeChanged
UI.updateLocalRole = isModerator => {
VideoLayout.showModeratorIndicator();
APP.store.dispatch(showSIPCallButton(isModerator));
APP.store.dispatch(showDialOutButton(isModerator));
APP.store.dispatch(showSharedVideoButton());
Recording.showRecordingButton(isModerator);
@@ -569,25 +589,40 @@ UI.updateUserRole = user => {
}
};
/**
* Updates the user status.
*
* @param {JitsiParticipant} user - The user which status we need to update.
* @param {string} status - The new status.
*/
UI.updateUserStatus = (user, status) => {
let displayName = user.getDisplayName();
messageHandler.notify(
displayName, '', 'connected', "dialOut.statusMessage",
{
status: UIUtil.escapeHtml(status)
});
};
/**
* Toggles smileys in the chat.
*/
UI.toggleSmileys = () => Chat.toggleSmileys();
/**
* Toggles film strip.
* Toggles filmstrip.
*/
UI.toggleFilmStrip = function () {
var self = FilmStrip;
self.toggleFilmStrip.apply(self, arguments);
UI.toggleFilmstrip = function () {
var self = Filmstrip;
self.toggleFilmstrip.apply(self, arguments);
VideoLayout.resizeVideoArea(true, false);
};
/**
* Indicates if the film strip is currently visible or not.
* @returns {true} if the film strip is currently visible, otherwise
* Indicates if the filmstrip is currently visible or not.
* @returns {true} if the filmstrip is currently visible, otherwise
*/
UI.isFilmStripVisible = () => FilmStrip.isFilmStripVisible();
UI.isFilmstripVisible = () => Filmstrip.isFilmstripVisible();
/**
* Toggles chat panel.
@@ -700,6 +735,14 @@ UI.setVideoMuted = function (id, muted) {
}
};
/**
* Triggers an update of remote video and large video displays so they may pick
* up any state changes that have occurred elsewhere.
*
* @returns {void}
*/
UI.updateAllVideos = () => VideoLayout.updateAllVideos();
/**
* Adds a listener that would be notified on the given type of event.
*
@@ -1080,29 +1123,7 @@ UI.onLocalRaiseHandChanged = function (isRaisedHand) {
* @param {object[]} devices new list of available devices
*/
UI.onAvailableDevicesChanged = function (devices) {
SettingsMenu.changeDevicesList(devices);
};
/**
* Sets microphone's <select> element to select microphone ID from settings.
*/
UI.setSelectedMicFromSettings = function () {
SettingsMenu.setSelectedMicFromSettings();
};
/**
* Sets camera's <select> element to select camera ID from settings.
*/
UI.setSelectedCameraFromSettings = function () {
SettingsMenu.setSelectedCameraFromSettings();
};
/**
* Sets audio outputs's <select> element to select audio output ID from
* settings.
*/
UI.setSelectedAudioOutputFromSettings = function () {
SettingsMenu.setSelectedAudioOutputFromSettings();
APP.store.dispatch(updateDeviceList(devices));
};
/**
@@ -1335,14 +1356,17 @@ UI.setMicrophoneButtonEnabled
= enabled => APP.store.dispatch(setAudioIconEnabled(enabled));
UI.showRingOverlay = function () {
RingOverlay.show(APP.tokenData.callee, interfaceConfig.DISABLE_RINGING);
FilmStrip.toggleFilmStrip(false, false);
const { callee } = APP.store.getState()['features/jwt'];
callee && RingOverlay.show(callee, interfaceConfig.DISABLE_RINGING);
Filmstrip.toggleFilmstrip(false, false);
};
UI.hideRingOverLay = function () {
if (!RingOverlay.hide())
return;
FilmStrip.toggleFilmStrip(true, false);
Filmstrip.toggleFilmstrip(true, false);
};
/**
@@ -1383,16 +1407,33 @@ const UIListeners = new Map([
UI.toggleChat
], [
UIEvents.TOGGLE_SETTINGS,
() => UI.toggleSidePanel("settings_container")
() => {
// Opening of device selection is special-cased as it is a dialog
// opened through a button in settings and not directly displayed in
// settings itself. As it is not useful to only have a settings menu
// with a button to open a dialog, open the dialog directly instead.
if (interfaceConfig.SETTINGS_SECTIONS.length === 1
&& UIUtil.isSettingEnabled('devices')) {
APP.store.dispatch(openDeviceSelectionDialog());
} else {
UI.toggleSidePanel("settings_container");
}
}
], [
UIEvents.TOGGLE_CONTACT_LIST,
UI.toggleContactList
], [
UIEvents.TOGGLE_PROFILE,
() => APP.tokenData.isGuest && UI.toggleSidePanel("profile_container")
() => {
const {
isGuest
} = APP.store.getState()['features/jwt'];
isGuest && UI.toggleSidePanel('profile_container');
}
], [
UIEvents.TOGGLE_FILM_STRIP,
UI.handleToggleFilmStrip
UIEvents.TOGGLE_FILMSTRIP,
UI.handleToggleFilmstrip
], [
UIEvents.FOLLOW_ME_ENABLED,
enabled => (followMeHandler && followMeHandler.enableFollowMe(enabled))

View File

@@ -1,11 +1,13 @@
/* global APP, config, JitsiMeetJS, Promise */
const logger = require("jitsi-meet-logger").getLogger(__filename);
import { openConnection } from '../../../connection';
import { setJWT } from '../../../react/features/jwt';
import UIUtil from '../util/UIUtil';
import LoginDialog from './LoginDialog';
import UIUtil from '../util/UIUtil';
import {openConnection} from '../../../connection';
const ConnectionErrors = JitsiMeetJS.errors.connection;
const logger = require("jitsi-meet-logger").getLogger(__filename);
let externalAuthWindow;
let authRequiredDialog;
@@ -73,15 +75,20 @@ function redirectToTokenAuthService(roomName) {
* @param room the name fo the conference room.
*/
function initJWTTokenListener(room) {
var listener = function (event) {
if (externalAuthWindow !== event.source) {
var listener = function ({ data, source }) {
if (externalAuthWindow !== source) {
logger.warn("Ignored message not coming " +
"from external authnetication window");
return;
}
if (event.data && event.data.jwtToken) {
config.token = event.data.jwtToken;
logger.info("Received JWT token:", config.token);
let jwt;
if (data && (jwt = data.jwtToken)) {
logger.info("Received JSON Web Token (JWT):", jwt);
APP.store.dispatch(setJWT(jwt));
var roomName = room.getName();
openConnection({retry: false, roomName: roomName })
.then(function (connection) {

View File

@@ -3,7 +3,7 @@
import VideoLayout from "../videolayout/VideoLayout";
import LargeContainer from '../videolayout/LargeContainer';
import UIEvents from "../../../service/UI/UIEvents";
import FilmStrip from '../videolayout/FilmStrip';
import Filmstrip from '../videolayout/Filmstrip';
/**
* Etherpad options.
@@ -103,7 +103,7 @@ class Etherpad extends LargeContainer {
// eslint-disable-next-line no-unused-vars
resize (containerWidth, containerHeight, animate) {
let height = containerHeight - FilmStrip.getFilmStripHeight();
let height = containerHeight - Filmstrip.getFilmstripHeight();
let width = containerWidth;
$(this.iframe).width(width).height(height);

View File

@@ -1,187 +0,0 @@
/* global JitsiMeetJS, APP */
const logger = require("jitsi-meet-logger").getLogger(__filename);
import InviteDialogView from './InviteDialogView';
import createRoomLocker from './RoomLocker';
import UIEvents from '../../../service/UI/UIEvents';
const ConferenceEvents = JitsiMeetJS.events.conference;
/**
* Invite module
* Constructor takes conference object giving
* ability to subscribe on its events
*/
class Invite {
constructor(conference) {
this.conference = conference;
this.inviteUrl = APP.ConferenceUrl.getInviteUrl();
this.createRoomLocker(conference);
this.registerListeners();
}
/**
* Registering listeners.
* Primarily listeners for conference events.
*/
registerListeners() {
this.conference.on(ConferenceEvents.LOCK_STATE_CHANGED,
(locked, error) => {
logger.log("Received channel password lock change: ", locked,
error);
if (!locked) {
this.getRoomLocker().resetPassword();
}
this.setLockedFromElsewhere(locked);
});
this.conference.on(ConferenceEvents.USER_ROLE_CHANGED, (id) => {
if (APP.conference.isLocalId(id)
&& this.isModerator !== this.conference.isModerator()) {
this.setModerator(this.conference.isModerator());
}
});
APP.UI.addListener( UIEvents.INVITE_CLICKED,
() => { this.openLinkDialog(); });
}
/**
* Updates the view.
* If dialog hasn't been defined -
* creates it and updates
*/
updateView() {
if (!this.view) {
this.initDialog();
}
this.view.updateView();
}
/**
* Room locker factory
* @param room
* @returns {Object} RoomLocker
* @factory
*/
createRoomLocker(room = this.conference) {
let roomLocker = createRoomLocker(room);
this.roomLocker = roomLocker;
return this.getRoomLocker();
}
/**
* Room locker getter
* @returns {Object} RoomLocker
*/
getRoomLocker() {
return this.roomLocker;
}
/**
* Opens the invite link dialog.
*/
openLinkDialog () {
if (!this.view) {
this.initDialog();
}
this.view.open();
}
/**
* Dialog initialization.
* creating view object using as a model this module
*/
initDialog() {
this.view = new InviteDialogView(this);
}
/**
* Password getter
* @returns {String} password
*/
getPassword() {
return this.getRoomLocker().password;
}
/**
* Switches between the moderator view and normal view.
*
* @param isModerator indicates if the participant is moderator
*/
setModerator(isModerator) {
this.isModerator = isModerator;
this.updateView();
}
/**
* Allows to unlock the room.
* If the current user is moderator.
*/
setRoomUnlocked() {
if (this.isModerator) {
this.getRoomLocker().lock().then(() => {
APP.UI.emitEvent(UIEvents.TOGGLE_ROOM_LOCK);
this.updateView();
});
}
}
/**
* Allows to lock the room if
* the current user is moderator.
* Takes the password.
* @param {String} newPass
*/
setRoomLocked(newPass) {
let isModerator = this.isModerator;
if (isModerator && (newPass || !this.getRoomLocker().isLocked)) {
this.getRoomLocker().lock(newPass).then(() => {
APP.UI.emitEvent(UIEvents.TOGGLE_ROOM_LOCK);
this.updateView();
});
}
}
/**
* Helper method for encoding
* Invite URL
* @returns {string}
*/
getEncodedInviteUrl() {
return encodeURI(this.inviteUrl);
}
/**
* Is locked flag.
* Delegates to room locker
* @returns {Boolean} isLocked
*/
isLocked() {
return this.getRoomLocker().isLocked;
}
/**
* Set flag locked from elsewhere to room locker.
* @param isLocked
*/
setLockedFromElsewhere(isLocked) {
let roomLocker = this.getRoomLocker();
let oldLockState = roomLocker.isLocked;
if (oldLockState !== isLocked) {
roomLocker.lockedElsewhere = isLocked;
APP.UI.emitEvent(UIEvents.TOGGLE_ROOM_LOCK);
this.updateView();
}
}
}
export default Invite;

View File

@@ -1,378 +0,0 @@
/* global $, APP, JitsiMeetJS */
const logger = require("jitsi-meet-logger").getLogger(__filename);
/**
* Substate for password
* @type {{LOCKED: string, UNLOCKED: string}}
*/
const States = {
LOCKED: 'locked',
UNLOCKED: 'unlocked'
};
/**
* Class representing view for Invite dialog
* @class InviteDialogView
*/
export default class InviteDialogView {
constructor(model) {
this.unlockHint = "unlockHint";
this.lockHint = "lockHint";
this.model = model;
if (this.model.inviteUrl === null) {
this.inviteAttributes = `data-i18n="[value]inviteUrlDefaultMsg"`;
} else {
this.inviteAttributes
= `value="${this.model.getEncodedInviteUrl()}"`;
}
this.initDialog();
}
/**
* Initialization of dialog property
*/
initDialog() {
let dialog = {};
dialog.closeFunction = this.closeFunction.bind(this);
dialog.submitFunction = this.submitFunction.bind(this);
dialog.loadedFunction = this.loadedFunction.bind(this);
dialog.titleKey = "dialog.shareLink";
this.dialog = dialog;
this.dialog.states = this.getStates();
}
/**
* Event handler for submitting dialog
* @param e
* @param v
*/
submitFunction(e, v) {
if (v && this.model.inviteUrl) {
JitsiMeetJS.analytics.sendEvent('toolbar.invite.button');
} else {
JitsiMeetJS.analytics.sendEvent('toolbar.invite.cancel');
}
}
/**
* Event handler for load dialog
* @param event
*/
loadedFunction(event) {
if (this.model.inviteUrl) {
document.getElementById('inviteLinkRef').select();
} else {
if (event && event.target) {
$(event.target).find('button[value=true]')
.prop('disabled', true);
}
}
}
/**
* Event handler for closing dialog
* @param e
* @param v
* @param m
* @param f
*/
closeFunction(e, v, m, f) {
$(document).off('click', '.copyInviteLink', this.copyToClipboard);
if(!v && !m && !f)
JitsiMeetJS.analytics.sendEvent('toolbar.invite.close');
}
/**
* Returns all states of the dialog
* @returns {{}}
*/
getStates() {
let {
titleKey
} = this.dialog;
let doneMsg = APP.translation.generateTranslationHTML('dialog.done');
let states = {};
let buttons = {};
buttons[`${doneMsg}`] = true;
states[States.UNLOCKED] = {
titleKey,
html: this.getShareLinkBlock() + this.getAddPasswordBlock(),
buttons
};
states[States.LOCKED] = {
titleKey,
html: this.getShareLinkBlock() + this.getPasswordBlock(),
buttons
};
return states;
}
/**
* Layout for invite link input
* @returns {string}
*/
getShareLinkBlock() {
let classes = 'button-control button-control_light copyInviteLink';
return (
`<div class="form-control">
<label class="form-control__label" for="inviteLinkRef"
data-i18n="${this.dialog.titleKey}"></label>
<div class="form-control__container">
<input class="input-control inviteLink"
id="inviteLinkRef" type="text"
${this.inviteAttributes} readonly>
<button data-i18n="dialog.copy" class="${classes}"></button>
</div>
<p class="form-control__hint ${this.lockHint}">
<span class="icon-security-locked"></span>
<span data-i18n="dialog.roomLocked"></span>
</p>
<p class="form-control__hint ${this.unlockHint}">
<span class="icon-security"></span>
<span data-i18n="roomUnlocked"></span>
</p>
</div>`
);
}
/**
* Layout for adding password input
* @returns {string}
*/
getAddPasswordBlock() {
let html;
if (this.model.isModerator) {
html = (`
<div class="form-control">
<label class="form-control__label"
for="newPasswordInput" data-i18n="dialog.addPassword">
</label>
<div class="form-control__container">
<input class="input-control"
id="newPasswordInput"
type="text"
data-i18n="[placeholder]dialog.createPassword">
<button id="addPasswordBtn" id="inviteDialogAddPassword"
disabled data-i18n="dialog.add"
class="button-control button-control_light">
</button>
</div>
</div>
`);
} else {
html = '';
}
return html;
}
/**
* Layout for password (when room is locked)
* @returns {string}
*/
getPasswordBlock() {
let password = this.model.getPassword();
let { isModerator } = this.model;
if (isModerator) {
return (`
<div class="form-control">
<label class="form-control__label"
data-i18n="dialog.passwordLabel"></label>
<div class="form-control__container">
<p>
<span class="form-control__text"
data-i18n="dialog.currentPassword"></span>
<span id="inviteDialogPassword"
class="form-control__em">
${password}
</span>
</p>
<a class="link form-control__right"
id="inviteDialogRemovePassword"
data-i18n="dialog.removePassword"></a>
</div>
</div>
`);
} else {
return (`
<div class="form-control">
<p>A participant protected this call with a password.</p>
</div>
`);
}
}
/**
* Opening the dialog
*/
open() {
let {
submitFunction,
loadedFunction,
closeFunction
} = this.dialog;
let states = this.getStates();
let initial = this.model.roomLocked ? States.LOCKED : States.UNLOCKED;
APP.UI.messageHandler.openDialogWithStates(states, {
submit: submitFunction,
loaded: loadedFunction,
close: closeFunction,
size: 'medium'
});
$.prompt.goToState(initial);
this.registerListeners();
this.updateView();
}
/**
* Setting event handlers
* used in dialog
*/
registerListeners() {
const ENTER_KEY = 13;
let addPasswordBtn = '#addPasswordBtn';
let copyInviteLink = '.copyInviteLink';
let newPasswordInput = '#newPasswordInput';
let removePassword = '#inviteDialogRemovePassword';
$(document).on('click', copyInviteLink, this.copyToClipboard);
$(removePassword).on('click', () => {
this.model.setRoomUnlocked();
});
let boundSetPassword = this._setPassword.bind(this);
$(document).on('click', addPasswordBtn, boundSetPassword);
let boundDisablePass = this.disableAddPassIfInputEmpty.bind(this);
$(document).on('keypress', newPasswordInput, boundDisablePass);
// We need to handle keydown event because impromptu
// is listening to it too for closing the dialog
$(newPasswordInput).on('keydown', (e) => {
if (e.keyCode === ENTER_KEY) {
e.stopPropagation();
this._setPassword();
}
});
}
/**
* Marking room as locked
* @private
*/
_setPassword() {
let $passInput = $('#newPasswordInput');
let newPass = $passInput.val();
if(newPass) {
this.model.setRoomLocked(newPass);
}
}
/**
* Checking input and if it's empty then
* disable add pass button
*/
disableAddPassIfInputEmpty() {
let $passInput = $('#newPasswordInput');
let $addPassBtn = $('#addPasswordBtn');
if(!$passInput.val()) {
$addPassBtn.prop('disabled', true);
} else {
$addPassBtn.prop('disabled', false);
}
}
/**
* Copying text to clipboard
*/
copyToClipboard() {
$('.inviteLink').each(function () {
let $el = $(this).closest('.jqistate');
// TOFIX: We can select only visible elements
if($el.css('display') === 'block') {
this.select();
try {
document.execCommand('copy');
this.blur();
}
catch (err) {
logger.error('error when copy the text');
}
}
});
}
/**
* Method syncing the view and the model
*/
updateView() {
let pass = this.model.getPassword();
let { isModerator } = this.model;
if (this.model.getRoomLocker().lockedElsewhere || !pass) {
$('#inviteDialogPassword').attr("data-i18n", "passwordSetRemotely");
APP.translation.translateElement($('#inviteDialogPassword'));
} else {
$('#inviteDialogPassword').removeAttr("data-i18n");
$('#inviteDialogPassword').text(pass);
}
// if we are not moderator we cannot remove password
if (isModerator)
$('#inviteDialogRemovePassword').show();
else
$('#inviteDialogRemovePassword').hide();
$('#newPasswordInput').val('');
this.disableAddPassIfInputEmpty();
this.updateInviteLink();
$.prompt.goToState(
(this.model.isLocked())
? States.LOCKED
: States.UNLOCKED);
let roomLocked = `.${this.lockHint}`;
let roomUnlocked = `.${this.unlockHint}`;
let showDesc = this.model.isLocked() ? roomLocked : roomUnlocked;
let hideDesc = !this.model.isLocked() ? roomLocked : roomUnlocked;
$(showDesc).show();
$(hideDesc).hide();
}
/**
* Updates invite link
*/
updateInviteLink() {
// If the invite dialog has been already opened we update the
// information.
let inviteLink = document.querySelectorAll('.inviteLink');
let list = Array.from(inviteLink);
list.forEach((inviteLink) => {
inviteLink.value = this.model.inviteUrl;
inviteLink.select();
});
$('#inviteLinkRef').parent()
.find('button[value=true]').prop('disabled', false);
}
}

View File

@@ -1,103 +0,0 @@
/* global APP, JitsiMeetJS */
const logger = require("jitsi-meet-logger").getLogger(__filename);
/**
* Show notification that user cannot set password for the conference
* because server doesn't support that.
*/
function notifyPasswordNotSupported () {
logger.warn('room passwords not supported');
APP.UI.messageHandler.showError(
"dialog.warning", "dialog.passwordNotSupported");
}
/**
* Show notification that setting password for the conference failed.
* @param {Error} err error
*/
function notifyPasswordFailed(err) {
logger.warn('setting password failed', err);
APP.UI.messageHandler.showError(
"dialog.lockTitle", "dialog.lockMessage");
}
const ConferenceErrors = JitsiMeetJS.errors.conference;
/**
* Create new RoomLocker for the conference.
* It allows to set or remove password for the conference,
* or ask for required password.
* @returns {RoomLocker}
*/
export default function createRoomLocker (room) {
let password;
/**
* If the room was locked from someone other than us, we indicate it with
* this property in order to have correct roomLocker state of isLocked.
* @type {boolean} whether room is locked, but not from us.
*/
let lockedElsewhere = false;
/**
* @class RoomLocker
*/
return {
get isLocked () {
return !!password || lockedElsewhere;
},
get password () {
return password;
},
/**
* Allows to set new password
* @param newPass
* @returns {Promise.<TResult>}
*/
lock (newPass) {
return room.lock(newPass).then(() => {
password = newPass;
// If the password is undefined this means that we're removing
// it for everyone.
if (!password)
lockedElsewhere = false;
}).catch(function (err) {
logger.error(err);
if (err === ConferenceErrors.PASSWORD_NOT_SUPPORTED) {
notifyPasswordNotSupported();
} else {
notifyPasswordFailed(err);
}
throw err;
});
},
/**
* Sets that the room is locked from another user, not us.
* @param {boolean} value locked/unlocked state
*/
set lockedElsewhere (value) {
lockedElsewhere = value;
},
/**
* Whether room is locked from someone else.
* @returns {boolean} whether room is not locked locally,
* but it is still locked.
*/
get lockedElsewhere () {
return lockedElsewhere;
},
/**
* Reset the password. Can be useful when room
* has been unlocked from elsewhere and we can use
* this method for sync the pass
*/
resetPassword() {
password = null;
},
};
}

View File

@@ -21,7 +21,7 @@ import UIUtil from '../util/UIUtil';
import VideoLayout from '../videolayout/VideoLayout';
import Feedback from '../feedback/Feedback.js';
import { hideToolbox } from '../../../react/features/toolbox';
import { setToolboxEnabled } from '../../../react/features/toolbox';
/**
* The dialog for user input.
@@ -35,8 +35,10 @@ let dialog = null;
* @private
*/
function _isRecordingButtonEnabled() {
return interfaceConfig.TOOLBAR_BUTTONS.indexOf("recording") !== -1
&& config.enableRecording && APP.conference.isRecordingSupported();
return (
interfaceConfig.TOOLBAR_BUTTONS.indexOf("recording") !== -1
&& config.enableRecording
&& APP.conference.isRecordingSupported());
}
/**
@@ -129,7 +131,7 @@ function _requestLiveStreamId() {
* Request recording token from the user.
* @returns {Promise}
*/
function _requestRecordingToken () {
function _requestRecordingToken() {
let titleKey = "dialog.recordingToken";
let messageString = (
`<input name="recordingToken" type="text"
@@ -164,7 +166,7 @@ function _requestRecordingToken () {
* @returns {Promise}
* @private
*/
function _showStopRecordingPrompt (recordingType) {
function _showStopRecordingPrompt(recordingType) {
var title;
var message;
var buttonKey;
@@ -179,19 +181,13 @@ function _showStopRecordingPrompt (recordingType) {
buttonKey = "dialog.stopRecording";
}
return new Promise(function (resolve, reject) {
return new Promise((resolve, reject) => {
dialog = APP.UI.messageHandler.openTwoButtonDialog({
titleKey: title,
msgKey: message,
leftButtonKey: buttonKey,
submitFunction: function(e,v) {
if (v) {
resolve();
} else {
reject();
}
},
closeFunction: function () {
submitFunction: (e, v) => (v ? resolve : reject)(),
closeFunction: () => {
dialog = null;
}
});
@@ -250,35 +246,12 @@ var Recording = {
/**
* Initializes the recording UI.
*/
init (emitter, recordingType) {
this.eventEmitter = emitter;
init(eventEmitter, recordingType) {
this.eventEmitter = eventEmitter;
this.recordingType = recordingType;
this.updateRecordingState(APP.conference.getRecordingState());
this.initRecordingButton(recordingType);
// If I am a recorder then I publish my recorder custom role to notify
// everyone.
if (config.iAmRecorder) {
VideoLayout.enableDeviceAvailabilityIcons(
APP.conference.getMyUserId(), false);
VideoLayout.setLocalVideoVisible(false);
Feedback.enableFeedback(false);
APP.store.dispatch(hideToolbox());
APP.UI.messageHandler.enableNotifications(false);
APP.UI.messageHandler.enablePopups(false);
}
},
/**
* Initialise the recording button.
*/
initRecordingButton(recordingType) {
let selector = $('#toolbar_button_record');
let button = selector.get(0);
UIUtil.setTooltip(button, 'liveStreaming.buttonTooltip', 'right');
if (recordingType === 'jibri') {
this.baseClass = "fa fa-play-circle";
this.recordingTitle = "dialog.liveStreaming";
@@ -304,101 +277,44 @@ var Recording = {
this.recordingBusy = "liveStreaming.busy";
}
// XXX Due to the React-ification of Toolbox, the HTMLElement with id
// toolbar_button_record may not exist yet.
$(document).on(
'click',
'#toolbar_button_record',
ev => this._onToolbarButtonClick(ev));
// If I am a recorder then I publish my recorder custom role to notify
// everyone.
if (config.iAmRecorder) {
VideoLayout.enableDeviceAvailabilityIcons(
APP.conference.getMyUserId(), false);
VideoLayout.setLocalVideoVisible(false);
Feedback.enableFeedback(false);
APP.store.dispatch(setToolboxEnabled(false));
APP.UI.messageHandler.enableNotifications(false);
APP.UI.messageHandler.enablePopups(false);
}
},
/**
* Initialise the recording button.
*/
initRecordingButton() {
const selector = $('#toolbar_button_record');
UIUtil.setTooltip(selector, 'liveStreaming.buttonTooltip', 'right');
selector.addClass(this.baseClass);
selector.attr("data-i18n", "[content]" + this.recordingButtonTooltip);
APP.translation.translateElement(selector);
var self = this;
selector.click(function () {
if (dialog)
return;
JitsiMeetJS.analytics.sendEvent('recording.clicked');
switch (self.currentState) {
case Status.ON:
case Status.RETRYING:
case Status.PENDING: {
_showStopRecordingPrompt(recordingType).then(
() => {
self.eventEmitter.emit(UIEvents.RECORDING_TOGGLED);
JitsiMeetJS.analytics.sendEvent(
'recording.stopped');
},
() => {});
break;
}
case Status.AVAILABLE:
case Status.OFF: {
if (recordingType === 'jibri')
_requestLiveStreamId().then((streamId) => {
self.eventEmitter.emit( UIEvents.RECORDING_TOGGLED,
{streamId: streamId});
JitsiMeetJS.analytics.sendEvent(
'recording.started');
}).catch(
reason => {
if (reason !== APP.UI.messageHandler.CANCEL)
logger.error(reason);
else
JitsiMeetJS.analytics.sendEvent(
'recording.canceled');
}
);
else {
if (self.predefinedToken) {
self.eventEmitter.emit( UIEvents.RECORDING_TOGGLED,
{token: self.predefinedToken});
JitsiMeetJS.analytics.sendEvent(
'recording.started');
return;
}
_requestRecordingToken().then((token) => {
self.eventEmitter.emit( UIEvents.RECORDING_TOGGLED,
{token: token});
JitsiMeetJS.analytics.sendEvent(
'recording.started');
}).catch(
reason => {
if (reason !== APP.UI.messageHandler.CANCEL)
logger.error(reason);
else
JitsiMeetJS.analytics.sendEvent(
'recording.canceled');
}
);
}
break;
}
case Status.BUSY: {
dialog = APP.UI.messageHandler.openMessageDialog(
self.recordingTitle,
self.recordingBusy,
null,
function () {
dialog = null;
}
);
break;
}
default: {
dialog = APP.UI.messageHandler.openMessageDialog(
self.recordingTitle,
self.recordingUnavailable,
null,
function () {
dialog = null;
}
);
}
}
});
},
/**
* Shows or hides the 'recording' button.
* @param show {true} to show the recording button, {false} to hide it
*/
showRecordingButton (show) {
showRecordingButton(show) {
let shouldShow = show && _isRecordingButtonEnabled();
let id = 'toolbar_button_record';
@@ -425,7 +341,7 @@ var Recording = {
* Sets the state of the recording button.
* @param recordingState gives us the current recording state
*/
updateRecordingUI (recordingState) {
updateRecordingUI(recordingState) {
let oldState = this.currentState;
this.currentState = recordingState;
@@ -491,7 +407,7 @@ var Recording = {
},
// checks whether recording is enabled and whether we have params
// to start automatically recording
checkAutoRecord () {
checkAutoRecord() {
if (_isRecordingButtonEnabled && config.autoRecord) {
this.predefinedToken = UIUtil.escapeHtml(config.autoRecordToken);
this.eventEmitter.emit(UIEvents.RECORDING_TOGGLED,
@@ -514,6 +430,90 @@ var Recording = {
APP.translation.translateElement(labelSelector);
},
/**
* Handles {@code click} on {@code toolbar_button_record}.
*
* @returns {void}
*/
_onToolbarButtonClick() {
if (dialog) {
return;
}
JitsiMeetJS.analytics.sendEvent('recording.clicked');
switch (this.currentState) {
case Status.ON:
case Status.RETRYING:
case Status.PENDING: {
_showStopRecordingPrompt(this.recordingType).then(
() => {
this.eventEmitter.emit(UIEvents.RECORDING_TOGGLED);
JitsiMeetJS.analytics.sendEvent('recording.stopped');
},
() => {});
break;
}
case Status.AVAILABLE:
case Status.OFF: {
if (this.recordingType === 'jibri')
_requestLiveStreamId().then(streamId => {
this.eventEmitter.emit(
UIEvents.RECORDING_TOGGLED,
{ streamId });
JitsiMeetJS.analytics.sendEvent('recording.started');
}).catch(reason => {
if (reason !== APP.UI.messageHandler.CANCEL)
logger.error(reason);
else
JitsiMeetJS.analytics.sendEvent('recording.canceled');
});
else {
if (this.predefinedToken) {
this.eventEmitter.emit(
UIEvents.RECORDING_TOGGLED,
{ token: this.predefinedToken });
JitsiMeetJS.analytics.sendEvent('recording.started');
return;
}
_requestRecordingToken().then((token) => {
this.eventEmitter.emit(
UIEvents.RECORDING_TOGGLED,
{ token });
JitsiMeetJS.analytics.sendEvent('recording.started');
}).catch(reason => {
if (reason !== APP.UI.messageHandler.CANCEL)
logger.error(reason);
else
JitsiMeetJS.analytics.sendEvent('recording.canceled');
});
}
break;
}
case Status.BUSY: {
dialog = APP.UI.messageHandler.openMessageDialog(
this.recordingTitle,
this.recordingBusy,
null,
() => {
dialog = null;
}
);
break;
}
default: {
dialog = APP.UI.messageHandler.openMessageDialog(
this.recordingTitle,
this.recordingUnavailable,
null,
() => {
dialog = null;
}
);
}
}
},
/**
* Sets the toggled state of the recording toolbar button.
*

View File

@@ -22,7 +22,8 @@ function onAvatarVisible(shown) {
*/
class RingOverlay {
/**
* @param callee instance of User class from TokenData.js
*
* @param callee The callee (Object) as defined by the JWT support.
* @param {boolean} disableRingingSound if true the ringing sound wont be played.
*/
constructor(callee, disableRingingSound) {
@@ -77,9 +78,9 @@ class RingOverlay {
<div id="${this._containerId}" class='ringing' >
<div class='ringing__content'>
${callingLabel}
<img class='ringing__avatar' src="${callee.getAvatarUrl()}" />
<img class='ringing__avatar' src="${callee.avatarUrl}" />
<div class="ringing__caller-info">
<p>${callee.getName()}${callerStateLabel}</p>
<p>${callee.name}${callerStateLabel}</p>
</div>
</div>
${audioHTML}
@@ -137,9 +138,12 @@ class RingOverlay {
export default {
/**
* Shows the ring overlay for the passed callee.
* @param callee {class User} the callee. Instance of User class from
* TokenData.js
* @param {boolean} disableRingingSound if true the ringing sound wont be played.
*
* @param {Object} callee - The callee. Object containing data about
* callee.
* @param {boolean} disableRingingSound - If true the ringing sound won't be
* played.
* @returns {void}
*/
show(callee, disableRingingSound = false) {
if(overlay) {

View File

@@ -8,7 +8,7 @@ import UIEvents from '../../../service/UI/UIEvents';
import VideoLayout from "../videolayout/VideoLayout";
import LargeContainer from '../videolayout/LargeContainer';
import SmallVideo from '../videolayout/SmallVideo';
import FilmStrip from '../videolayout/FilmStrip';
import Filmstrip from '../videolayout/Filmstrip';
import { dockToolbox, showToolbox } from '../../../react/features/toolbox';
@@ -606,7 +606,7 @@ class SharedVideoContainer extends LargeContainer {
}
resize (containerWidth, containerHeight) {
let height = containerHeight - FilmStrip.getFilmStripHeight();
let height = containerHeight - Filmstrip.getFilmstripHeight();
let width = containerWidth;

View File

@@ -16,17 +16,27 @@ const SideContainerToggler = {
init(eventEmitter) {
this.eventEmitter = eventEmitter;
// Adds a listener for the animation end event that would take care
// of hiding all internal containers when the extendedToolbarPanel is
// 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.
document.getElementById("sideToolbarContainer")
.addEventListener("animationend", function(e) {
if(e.animationName === "slideOutExt")
sideToolbarContainer.addEventListener(
"animationend",
function(e) {
if (e.animationName === "slideOutExt")
$("#sideToolbarContainer").children().each(function() {
if ($(this).hasClass("show"))
SideContainerToggler.hideInnerContainer($(this));
});
}, false);
},
false);
},
/**
@@ -134,4 +144,4 @@ const SideContainerToggler = {
}
};
export default SideContainerToggler;
export default SideContainerToggler;

View File

@@ -30,7 +30,7 @@ const htmlStr = `
<textarea id="usermsg" autofocus
data-i18n="[placeholder]chat.messagebox"></textarea>
<div id="smileysarea">
<div id="smileys" id="toggle_smileys">
<div id="smileys">
<img src="images/smile.svg"/>
</div>
</div>
@@ -191,7 +191,7 @@ var Chat = {
Chat.setChatConversationMode(true);
}
$("#toggle_smileys").click(function() {
$("#smileys").click(function() {
Chat.toggleSmileys();
});

View File

@@ -19,14 +19,12 @@ class ContactList {
}
/**
* Is locked flag.
* Delegates to Invite module
* TO FIX: find a better way to access the IS LOCKED state of the invite.
* Returns true if the current conference is locked.
*
* @returns {Boolean}
*/
isLocked() {
return APP.conference.invite.isLocked();
return APP.store.getState()['features/base/conference'].locked;
}
/**
@@ -36,9 +34,9 @@ class ContactList {
* @param isLocal
*/
addContact(id, isLocal) {
let isExist = this.contacts.some((el) => el.id === id);
const exists = this.contacts.some(el => el.id === id);
if (!isExist) {
if (!exists) {
let newContact = new Contact({ id, isLocal });
this.contacts.push(newContact);
APP.UI.emitEvent(UIEvents.CONTACT_ADDED, { id, isLocal });
@@ -92,4 +90,4 @@ class ContactList {
}
}
export default ContactList;
export default ContactList;

View File

@@ -1,10 +1,13 @@
/* global $, APP, interfaceConfig */
const logger = require("jitsi-meet-logger").getLogger(__filename);
import { openInviteDialog } from '../../../../react/features/invite';
import Avatar from '../../avatar/Avatar';
import UIEvents from '../../../../service/UI/UIEvents';
import UIUtil from '../../util/UIUtil';
const logger = require('jitsi-meet-logger').getLogger(__filename);
let numberOfContacts = 0;
const sidePanelsContainerId = 'sideToolbarContainer';
const htmlStr = `
@@ -96,7 +99,7 @@ var ContactListView = {
this.model = model;
this.addInviteButton();
this.registerListeners();
this.toggleLock();
this.setLockDisplay(false);
},
/**
* Adds layout for invite button
@@ -108,8 +111,9 @@ var ContactListView = {
.insertAdjacentHTML('afterend', this.getInviteButtonLayout());
APP.translation.translateElement($(container));
$(document).on('click', '#addParticipantsBtn', () => {
APP.UI.emitEvent(UIEvents.INVITE_CLICKED);
APP.store.dispatch(openInviteDialog());
});
},
/**
@@ -125,8 +129,8 @@ var ContactListView = {
return (
`<div class="sideToolbarBlock first">
<button id="addParticipantsBtn"
data-i18n="${key}"
<button id="addParticipantsBtn"
data-i18n="${key}"
class="${classes}"></button>
<div>
${lockedHtml}
@@ -158,7 +162,7 @@ var ContactListView = {
let displayNameChange = this.onDisplayNameChange.bind(this);
APP.UI.addListener( UIEvents.TOGGLE_ROOM_LOCK,
this.toggleLock.bind(this));
this.setLockDisplay.bind(this));
APP.UI.addListener( UIEvents.CONTACT_ADDED,
this.onAddContact.bind(this));
@@ -166,21 +170,28 @@ var ContactListView = {
APP.UI.addListener(UIEvents.USER_AVATAR_CHANGED, changeAvatar);
APP.UI.addListener(UIEvents.DISPLAY_NAME_CHANGED, displayNameChange);
},
/**
* Updating the view according the model
* @param type {String} type of change
* @returns {Promise}
*/
toggleLock() {
let isLocked = this.model.isLocked();
let showKey = isLocked ? this.lockKey : this.unlockKey;
let hideKey = !isLocked ? this.lockKey : this.unlockKey;
let showId = `contactList${showKey}`;
let hideId = `contactList${hideKey}`;
$(`#${showId}`).show();
$(`#${hideId}`).hide();
/**
* Updates the view according to the passed in lock state.
*
* @param {boolean} locked - True to display the locked UI state or false to
* display the unlocked UI state.
*/
setLockDisplay(locked) {
let hideKey, showKey;
if (locked) {
hideKey = this.unlockKey;
showKey = this.lockKey;
} else {
hideKey = this.lockKey;
showKey = this.unlockKey;
}
$(`#contactList${hideKey}`).hide();
$(`#contactList${showKey}`).show();
},
/**
* Indicates if the chat is currently visible.
*
@@ -275,4 +286,4 @@ var ContactListView = {
}
};
export default ContactListView;
export default ContactListView;

View File

@@ -36,6 +36,9 @@ const htmlStr = `
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}`));
}
export default {

View File

@@ -1,12 +1,14 @@
/* global $, APP, AJS, interfaceConfig, JitsiMeetJS */
/* global $, APP, AJS, interfaceConfig */
import { LANGUAGES } from "../../../../react/features/base/i18n";
import { openDeviceSelectionDialog }
from '../../../../react/features/device-selection';
import UIUtil from "../../util/UIUtil";
import UIEvents from "../../../../service/UI/UIEvents";
import Settings from '../../../settings/Settings';
const sidePanelsContainerId = 'sideToolbarContainer';
const deviceSelectionButtonClasses
= 'button-control button-control_primary button-control_full-width';
const htmlStr = `
<div id="settings_container" class="sideToolbarContainer__inner">
<div class="title" data-i18n="settings.title"></div>
@@ -19,17 +21,11 @@ const htmlStr = `
<div id="deviceOptionsTitle" class="subTitle hide"
data-i18n="settings.audioVideo"></div>
<div class="sideToolbarBlock first">
<label class="first" data-i18n="settings.selectCamera">
</label>
<select id="selectCamera"></select>
</div>
<div class="sideToolbarBlock">
<label data-i18n="settings.selectMic"></label>
<select id="selectMic"></select>
</div>
<div class="sideToolbarBlock">
<label data-i18n="settings.selectAudioOutput"></label>
<select id="selectAudioOutput"></select>
<button
class="${deviceSelectionButtonClasses}"
data-i18n="deviceSelection.deviceSettings"
id="deviceSelection"
type="button"></button>
</div>
</div>
<div id="moderatorOptionsWrapper" class="hide">
@@ -89,40 +85,6 @@ function generateLanguagesOptions(items, currentLang) {
}).join('');
}
/**
* Generate html select options for available physical devices.
*
* @param {{ deviceId, label }[]} items available devices
* @param {string} [selectedId] id of selected device
* @param {boolean} permissionGranted if permission to use selected device type
* is granted
* @returns {string}
*/
function generateDevicesOptions(items, selectedId, permissionGranted) {
if (!permissionGranted && items.length) {
return '<option data-i18n="settings.noPermission"></option>';
}
var options = items.map(function (item) {
let attrs = {
value: item.deviceId
};
if (item.deviceId === selectedId) {
attrs.selected = 'selected';
}
let attrsStr = UIUtil.attrsToString(attrs);
return `<option ${attrsStr}>${item.label}</option>`;
});
if (!items.length) {
options.unshift('<option data-i18n="settings.noDevice"></option>');
}
return options.join('');
}
/**
* Replace html select element to select2 custom dropdown
*
@@ -179,13 +141,9 @@ export default {
if (UIUtil.isSettingEnabled('devices')) {
const wrapperId = 'deviceOptionsWrapper';
JitsiMeetJS.mediaDevices.isDeviceListAvailable()
.then((isDeviceListAvailable) => {
if (isDeviceListAvailable &&
JitsiMeetJS.mediaDevices.isDeviceChangeAvailable()) {
this._initializeDeviceSelectionSettings(emitter);
}
});
$('#deviceSelection').on('click', () =>
APP.store.dispatch(openDeviceSelectionDialog()));
// Only show the subtitle if this isn't the only setting section.
if (interfaceConfig.SETTINGS_SECTIONS.length > 1)
UIUtil.setVisible("deviceOptionsTitle", true);
@@ -219,30 +177,6 @@ export default {
}
},
_initializeDeviceSelectionSettings(emitter) {
this.changeDevicesList([]);
$('#selectCamera').change(function () {
let cameraDeviceId = $(this).val();
if (cameraDeviceId !== Settings.getCameraDeviceId()) {
emitter.emit(UIEvents.VIDEO_DEVICE_CHANGED, cameraDeviceId);
}
});
$('#selectMic').change(function () {
let micDeviceId = $(this).val();
if (micDeviceId !== Settings.getMicDeviceId()) {
emitter.emit(UIEvents.AUDIO_DEVICE_CHANGED, micDeviceId);
}
});
$('#selectAudioOutput').change(function () {
let audioOutputDeviceId = $(this).val();
if (audioOutputDeviceId !== Settings.getAudioOutputDeviceId()) {
emitter.emit(
UIEvents.AUDIO_OUTPUT_DEVICE_CHANGED, audioOutputDeviceId);
}
});
},
/**
* If start audio muted/start video muted options should be visible or not.
* @param {boolean} show
@@ -286,91 +220,5 @@ export default {
*/
isVisible () {
return UIUtil.isVisible(document.getElementById("settings_container"));
},
/**
* Sets microphone's <select> element to select microphone ID from settings.
*/
setSelectedMicFromSettings () {
$('#selectMic').val(Settings.getMicDeviceId());
},
/**
* Sets camera's <select> element to select camera ID from settings.
*/
setSelectedCameraFromSettings () {
$('#selectCamera').val(Settings.getCameraDeviceId());
},
/**
* Sets audio outputs's <select> element to select audio output ID from
* settings.
*/
setSelectedAudioOutputFromSettings () {
$('#selectAudioOutput').val(Settings.getAudioOutputDeviceId());
},
/**
* Change available cameras/microphones or hide selects completely if
* no devices available.
* @param {{ deviceId, label, kind }[]} devices list of available devices
*/
changeDevicesList (devices) {
let $selectCamera= AJS.$('#selectCamera'),
$selectMic = AJS.$('#selectMic'),
$selectAudioOutput = AJS.$('#selectAudioOutput'),
$selectAudioOutputParent = $selectAudioOutput.parent();
let audio = devices.filter(device => device.kind === 'audioinput'),
video = devices.filter(device => device.kind === 'videoinput'),
audioOutput = devices
.filter(device => device.kind === 'audiooutput'),
selectedAudioDevice = audio.find(
d => d.deviceId === Settings.getMicDeviceId()) || audio[0],
selectedVideoDevice = video.find(
d => d.deviceId === Settings.getCameraDeviceId()) || video[0],
selectedAudioOutputDevice = audioOutput.find(
d => d.deviceId === Settings.getAudioOutputDeviceId()),
videoPermissionGranted =
JitsiMeetJS.mediaDevices.isDevicePermissionGranted('video'),
audioPermissionGranted =
JitsiMeetJS.mediaDevices.isDevicePermissionGranted('audio');
$selectCamera
.html(generateDevicesOptions(
video,
selectedVideoDevice ? selectedVideoDevice.deviceId : '',
videoPermissionGranted))
.prop('disabled', !video.length || !videoPermissionGranted);
initSelect2($selectCamera);
$selectMic
.html(generateDevicesOptions(
audio,
selectedAudioDevice ? selectedAudioDevice.deviceId : '',
audioPermissionGranted))
.prop('disabled', !audio.length || !audioPermissionGranted);
initSelect2($selectMic);
if (JitsiMeetJS.mediaDevices.isDeviceChangeAvailable('output')) {
$selectAudioOutput
.html(generateDevicesOptions(
audioOutput,
selectedAudioOutputDevice
? selectedAudioOutputDevice.deviceId
: 'default',
videoPermissionGranted || audioPermissionGranted))
.prop('disabled', !audioOutput.length ||
(!videoPermissionGranted && !audioPermissionGranted));
initSelect2($selectAudioOutput);
$selectAudioOutputParent.show();
} else {
$selectAudioOutputParent.hide();
}
APP.translation.translateElement($('#settings_container option'));
}
};

View File

@@ -157,11 +157,13 @@ const IndicatorFontSizes = {
* @param position the position of the tooltip in relation to the element
*/
setTooltip(element, key, position) {
if (element !== null) {
element.setAttribute('data-tooltip', TOOLTIP_POSITIONS[position]);
element.setAttribute('data-i18n', '[content]' + key);
if (element) {
const selector = element.jquery ? element : $(element);
APP.translation.translateElement($(element));
selector.attr('data-tooltip', TOOLTIP_POSITIONS[position]);
selector.attr('data-i18n', `[content]${key}`);
APP.translation.translateElement(selector);
}
},

View File

@@ -1,7 +1,7 @@
/* global $, APP, config */
/* global $, APP */
/* jshint -W101 */
import JitsiPopover from "../util/JitsiPopover";
import VideoLayout from "./VideoLayout";
import UIUtil from "../util/UIUtil";
/**
@@ -34,7 +34,6 @@ function ConnectionIndicator(videoContainer, videoId) {
this.bitrate = null;
this.showMoreValue = false;
this.resolution = null;
this.isResolutionHD = null;
this.transport = [];
this.framerate = null;
this.popover = null;
@@ -401,10 +400,6 @@ ConnectionIndicator.prototype.updateConnectionQuality =
let width = qualityToWidth.find(x => percent >= x.percent);
this.fullIcon.style.width = width.width;
if (object && typeof object.isResolutionHD === 'boolean') {
this.isResolutionHD = object.isResolutionHD;
}
this.updateResolutionIndicator();
this.updatePopoverData();
};
@@ -414,7 +409,6 @@ ConnectionIndicator.prototype.updateConnectionQuality =
*/
ConnectionIndicator.prototype.updateResolution = function (resolution) {
this.resolution = resolution;
this.updateResolutionIndicator();
this.updatePopoverData();
};
@@ -457,31 +451,6 @@ ConnectionIndicator.prototype.hideIndicator = function () {
this.popover.forceHide();
};
/**
* Updates the resolution indicator.
*/
ConnectionIndicator.prototype.updateResolutionIndicator = function () {
if (this.id !== null
&& VideoLayout.isCurrentlyOnLarge(this.id)) {
let showResolutionLabel = false;
if (this.isResolutionHD !== null)
showResolutionLabel = this.isResolutionHD;
else if (this.resolution !== null) {
let resolutions = this.resolution || {};
Object.keys(resolutions).map(function (ssrc) {
const { height } = resolutions[ssrc];
if (height >= config.minHDHeight)
showResolutionLabel = true;
});
}
VideoLayout.updateResolutionLabel(showResolutionLabel);
}
};
/**
* Adds a hover listener to the popover.
*/

View File

@@ -3,53 +3,56 @@
import UIEvents from "../../../service/UI/UIEvents";
import UIUtil from "../util/UIUtil";
const FilmStrip = {
const Filmstrip = {
/**
*
* @param eventEmitter the {EventEmitter} through which {FilmStrip} is to
* emit/fire {UIEvents} (such as {UIEvents.TOGGLED_FILM_STRIP}).
* @param eventEmitter the {EventEmitter} through which {Filmstrip} is to
* emit/fire {UIEvents} (such as {UIEvents.TOGGLED_FILMSTRIP}).
*/
init (eventEmitter) {
this.iconMenuDownClassName = 'icon-menu-down';
this.iconMenuUpClassName = 'icon-menu-up';
this.filmStripContainerClassName = 'filmstrip';
this.filmStrip = $('#remoteVideos');
this.filmstripContainerClassName = 'filmstrip';
this.filmstrip = $('#remoteVideos');
this.eventEmitter = eventEmitter;
this._initFilmStripToolbar();
this.registerListeners();
// 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
* Initializes the filmstrip toolbar.
*/
_initFilmStripToolbar() {
let toolbar = this._generateFilmStripToolbar();
let className = this.filmStripContainerClassName;
_initFilmstripToolbar() {
let toolbarContainerHTML = this._generateToolbarHTML();
let className = this.filmstripContainerClassName;
let container = document.querySelector(`.${className}`);
UIUtil.prependChild(container, toolbar);
UIUtil.prependChild(container, toolbarContainerHTML);
let iconSelector = '#hideVideoToolbar i';
this.toggleFilmStripIcon = document.querySelector(iconSelector);
let iconSelector = '#toggleFilmstripButton i';
this.toggleFilmstripIcon = document.querySelector(iconSelector);
},
/**
* Generates HTML layout for filmstrip toolbar
* Generates HTML layout for filmstrip toggle button and wrapping container.
* @returns {HTMLElement}
* @private
*/
_generateFilmStripToolbar() {
_generateToolbarHTML() {
let container = document.createElement('div');
let isVisible = this.isFilmStripVisible();
let isVisible = this.isFilmstripVisible();
container.className = 'filmstrip__toolbar';
if(!interfaceConfig.filmStripOnly) {
container.innerHTML = `
<button id="hideVideoToolbar">
<i class="icon-menu-${isVisible ? 'down' : 'up'}">
</i>
</button>
`;
}
container.innerHTML = `
<button id="toggleFilmstripButton">
<i class="icon-menu-${isVisible ? 'down' : 'up'}">
</i>
</button>
`;
return container;
},
@@ -62,8 +65,8 @@ const FilmStrip = {
// 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.
$('#hideVideoToolbar').on('click',
() => this.eventEmitter.emit(UIEvents.TOGGLE_FILM_STRIP));
$('#toggleFilmstripButton').on('click',
() => this.eventEmitter.emit(UIEvents.TOGGLE_FILMSTRIP));
this._registerToggleFilmstripShortcut();
},
@@ -80,7 +83,7 @@ const FilmStrip = {
// 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.
let handler = () => this.eventEmitter.emit(UIEvents.TOGGLE_FILM_STRIP);
let handler = () => this.eventEmitter.emit(UIEvents.TOGGLE_FILMSTRIP);
APP.keyboardshortcut.registerShortcut(
shortcut,
@@ -94,7 +97,7 @@ const FilmStrip = {
* Changes classes of icon for showing down state
*/
showMenuDownIcon() {
let icon = this.toggleFilmStripIcon;
let icon = this.toggleFilmstripIcon;
if(icon) {
icon.classList.add(this.iconMenuDownClassName);
icon.classList.remove(this.iconMenuUpClassName);
@@ -105,7 +108,7 @@ const FilmStrip = {
* Changes classes of icon for showing up state
*/
showMenuUpIcon() {
let icon = this.toggleFilmStripIcon;
let icon = this.toggleFilmstripIcon;
if(icon) {
icon.classList.add(this.iconMenuUpClassName);
icon.classList.remove(this.iconMenuDownClassName);
@@ -113,10 +116,10 @@ const FilmStrip = {
},
/**
* Toggles the visibility of the film strip.
* Toggles the visibility of the filmstrip.
*
* @param visible optional {Boolean} which specifies the desired visibility
* of the film strip. If not specified, the visibility will be flipped
* of the filmstrip. If not specified, the visibility will be flipped
* (i.e. toggled); otherwise, the visibility will be set to the specified
* value.
* @param {Boolean} sendAnalytics - True to send an analytics event. The
@@ -127,17 +130,17 @@ const FilmStrip = {
* It's important to hide the filmstrip with UI.toggleFilmstrip in order
* to correctly resize the video area.
*/
toggleFilmStrip(visible, sendAnalytics = true) {
toggleFilmstrip(visible, sendAnalytics = true) {
const isVisibleDefined = typeof visible === 'boolean';
if (!isVisibleDefined) {
visible = this.isFilmStripVisible();
} else if (this.isFilmStripVisible() === visible) {
visible = this.isFilmstripVisible();
} else if (this.isFilmstripVisible() === visible) {
return;
}
if (sendAnalytics) {
JitsiMeetJS.analytics.sendEvent('toolbar.filmstrip.toggled');
}
this.filmStrip.toggleClass("hidden");
this.filmstrip.toggleClass("hidden");
if (visible) {
this.showMenuUpIcon();
@@ -145,12 +148,12 @@ const FilmStrip = {
this.showMenuDownIcon();
}
// Emit/fire UIEvents.TOGGLED_FILM_STRIP.
// Emit/fire UIEvents.TOGGLED_FILMSTRIP.
const eventEmitter = this.eventEmitter;
if (eventEmitter) {
eventEmitter.emit(
UIEvents.TOGGLED_FILM_STRIP,
this.isFilmStripVisible());
UIEvents.TOGGLED_FILMSTRIP,
this.isFilmstripVisible());
}
},
@@ -158,24 +161,24 @@ const FilmStrip = {
* Shows if filmstrip is visible
* @returns {boolean}
*/
isFilmStripVisible() {
return !this.filmStrip.hasClass('hidden');
isFilmstripVisible() {
return !this.filmstrip.hasClass('hidden');
},
setupFilmStripOnly() {
this.filmStrip.css({
padding: "0px 0px 18px 0px",
right: 0
});
/**
* Adjusts styles for filmstrip-only mode.
*/
setFilmstripOnly() {
this.filmstrip.addClass('filmstrip__videos-filmstripOnly');
},
/**
* Returns the height of filmstrip
* @returns {number} height
*/
getFilmStripHeight() {
if (this.isFilmStripVisible()) {
return $(`.${this.filmStripContainerClassName}`).outerHeight();
getFilmstripHeight() {
if (this.isFilmstripVisible()) {
return $(`.${this.filmstripContainerClassName}`).outerHeight();
} else {
return 0;
}
@@ -185,10 +188,10 @@ const FilmStrip = {
* Returns the width of filmstip
* @returns {number} width
*/
getFilmStripWidth() {
return this.filmStrip.innerWidth()
- parseInt(this.filmStrip.css('paddingLeft'), 10)
- parseInt(this.filmStrip.css('paddingRight'), 10);
getFilmstripWidth() {
return this.filmstrip.innerWidth()
- parseInt(this.filmstrip.css('paddingLeft'), 10)
- parseInt(this.filmstrip.css('paddingRight'), 10);
},
/**
@@ -218,17 +221,17 @@ const FilmStrip = {
/**
* If the videoAreaAvailableWidth is set we use this one to calculate
* the filmStrip width, because we're probably in a state where the
* film strip size hasn't been updated yet, but it will be.
* the filmstrip width, because we're probably in a state where the
* filmstrip size hasn't been updated yet, but it will be.
*/
let videoAreaAvailableWidth
= UIUtil.getAvailableVideoWidth()
- this._getFilmstripExtraPanelsWidth()
- UIUtil.parseCssInt(this.filmStrip.css('right'), 10)
- UIUtil.parseCssInt(this.filmStrip.css('paddingLeft'), 10)
- UIUtil.parseCssInt(this.filmStrip.css('paddingRight'), 10)
- UIUtil.parseCssInt(this.filmStrip.css('borderLeftWidth'), 10)
- UIUtil.parseCssInt(this.filmStrip.css('borderRightWidth'), 10)
- UIUtil.parseCssInt(this.filmstrip.css('right'), 10)
- UIUtil.parseCssInt(this.filmstrip.css('paddingLeft'), 10)
- UIUtil.parseCssInt(this.filmstrip.css('paddingRight'), 10)
- UIUtil.parseCssInt(this.filmstrip.css('borderLeftWidth'), 10)
- UIUtil.parseCssInt(this.filmstrip.css('borderRightWidth'), 10)
- 5;
let availableWidth = videoAreaAvailableWidth;
@@ -295,7 +298,7 @@ const FilmStrip = {
* @private
*/
_getFilmstripExtraPanelsWidth() {
let className = this.filmStripContainerClassName;
let className = this.filmstripContainerClassName;
let width = 0;
$(`.${className}`)
.children()
@@ -403,7 +406,7 @@ const FilmStrip = {
}));
}
promises.push(new Promise((resolve) => {
this.filmStrip.animate({
this.filmstrip.animate({
// adds 2 px because of small video 1px border
height: remote.thumbHeight + 2
}, this._getAnimateOptions(animate, resolve));
@@ -413,7 +416,7 @@ const FilmStrip = {
let { localThumb } = this.getThumbs();
let height = localThumb.height();
let fontSize = UIUtil.getIndicatorFontSize(height);
this.filmStrip.find('.indicator').animate({
this.filmstrip.find('.indicator').animate({
fontSize
}, this._getAnimateOptions(animate, resolve));
}));
@@ -453,7 +456,7 @@ const FilmStrip = {
}
let localThumb = $("#localVideoContainer");
let remoteThumbs = this.filmStrip.children(selector)
let remoteThumbs = this.filmstrip.children(selector)
.not("#localVideoContainer");
// Exclude the local video container if it has been hidden.
@@ -465,4 +468,4 @@ const FilmStrip = {
}
};
export default FilmStrip;
export default Filmstrip;

View File

@@ -1,6 +1,8 @@
/* global $, APP, JitsiMeetJS */
/* global $, APP, config, JitsiMeetJS */
const logger = require("jitsi-meet-logger").getLogger(__filename);
import { setLargeVideoHDStatus } from '../../../react/features/base/conference';
import Avatar from "../avatar/Avatar";
import {createDeferred} from '../../util/helpers';
import UIEvents from "../../../service/UI/UIEvents";
@@ -11,6 +13,14 @@ import AudioLevels from "../audio_levels/AudioLevels";
const ParticipantConnectionStatus
= JitsiMeetJS.constants.participantConnectionStatus;
const DESKTOP_CONTAINER_TYPE = 'desktop';
/**
* The time interval in milliseconds to check the video resolution of the video
* being displayed.
*
* @type {number}
*/
const VIDEO_RESOLUTION_POLL_INTERVAL = 2000;
/**
* Manager for all Large containers.
@@ -33,7 +43,7 @@ export default class LargeVideoManager {
this.addContainer(VIDEO_CONTAINER_TYPE, this.videoContainer);
// use the same video container to handle desktop tracks
this.addContainer("desktop", this.videoContainer);
this.addContainer(DESKTOP_CONTAINER_TYPE, this.videoContainer);
this.width = 0;
this.height = 0;
@@ -48,6 +58,39 @@ export default class LargeVideoManager {
e => this.onHoverIn(e),
e => this.onHoverOut(e)
);
// Bind event handler so it is only bound once for every instance.
this._updateVideoResolutionStatus
= this._updateVideoResolutionStatus.bind(this);
this.videoContainer.addResizeListener(
this._updateVideoResolutionStatus);
if (!JitsiMeetJS.util.RTCUIHelper.isResizeEventSupported()) {
/**
* An interval for polling if the displayed video resolution is or
* is not high-definition. For browsers that do not support video
* resize events, polling is the fallback.
*
* @private
* @type {timeoutId}
*/
this._updateVideoResolutionInterval = window.setInterval(
this._updateVideoResolutionStatus,
VIDEO_RESOLUTION_POLL_INTERVAL);
}
}
/**
* Stops any polling intervals on the instance and and removes any
* listeners registered on child components.
*
* @returns {void}
*/
destroy() {
window.clearInterval(this._updateVideoResolutionInterval);
this.videoContainer.removeResizeListener(
this._updateVideoResolutionStatus);
}
onHoverIn (e) {
@@ -103,6 +146,8 @@ export default class LargeVideoManager {
preUpdate.then(() => {
const { id, stream, videoType, resolve } = this.newStreamData;
const isVideoFromCamera = videoType === VIDEO_CONTAINER_TYPE;
this.newStreamData = null;
logger.info("hover in %s", id);
@@ -120,9 +165,7 @@ export default class LargeVideoManager {
// If the container is VIDEO_CONTAINER_TYPE, we need to check
// its stream whether exist and is muted to set isVideoMuted
// in rest of the cases it is false
let showAvatar
= (videoType === VIDEO_CONTAINER_TYPE)
&& (!stream || stream.isMuted());
let showAvatar = isVideoFromCamera && (!stream || stream.isMuted());
// If the user's connection is disrupted then the avatar will be
// displayed in case we have no video image cached. That is if
@@ -130,12 +173,20 @@ export default class LargeVideoManager {
// the video was not rendered, before the connection has failed.
const isConnectionActive = this._isConnectionActive(id);
if (videoType === VIDEO_CONTAINER_TYPE
if (isVideoFromCamera
&& !isConnectionActive
&& (isUserSwitch || !container.wasVideoRendered)) {
showAvatar = true;
}
// If audio only mode is enabled, always show the avatar for
// videos from another participant.
if (APP.conference.isAudioOnly()
&& (isVideoFromCamera
|| videoType === DESKTOP_CONTAINER_TYPE)) {
showAvatar = true;
}
let promise;
// do not show stream if video is muted
@@ -159,8 +210,12 @@ export default class LargeVideoManager {
// Make sure no notification about remote failure is shown as
// its UI conflicts with the one for local connection interrupted.
const isConnected = APP.conference.isConnectionInterrupted()
|| isConnectionActive;
// For the purposes of UI indicators, audio only is considered as
// an "active" connection.
const isConnected
= APP.conference.isAudioOnly()
|| APP.conference.isConnectionInterrupted()
|| isConnectionActive;
// when isHavingConnectivityIssues, state can be inactive,
// interrupted or restoring. We show different message for
@@ -504,4 +559,18 @@ export default class LargeVideoManager {
onLocalFlipXChange(val) {
this.videoContainer.setLocalFlipX(val);
}
/**
* Dispatches an action to update the known resolution state of the
* large video.
*
* @private
* @returns {void}
*/
_updateVideoResolutionStatus() {
const { height, width } = this.videoContainer.getStreamSize();
const isCurrentlyHD = Math.min(height, width) >= config.minHDHeight;
APP.store.dispatch(setLargeVideoHDStatus(isCurrentlyHD));
}
}

View File

@@ -375,12 +375,14 @@ RemoteVideo.prototype._generatePopupMenuSliderItem = function (options) {
<span class='popupmenu__icon'>
<i class=${options.icon}></i>
</span>
<input class='popupmenu__slider'
type='range'
min='0'
max=${options.maxValue || 100}
value=${options.initialValue || 0}>
</input>
<div class='popupmenu__slider_container'>
<input class='popupmenu__slider'
type='range'
min='0'
max=${options.maxValue || 100}
value=${options.initialValue || 0}>
</input>
</div>
</div>`;
const menuItem = document.createElement('li');
@@ -556,6 +558,7 @@ RemoteVideo.prototype.isVideoPlayable = function () {
* @inheritDoc
*/
RemoteVideo.prototype.updateView = function () {
$(this.container).toggleClass('audio-only', APP.conference.isAudioOnly());
this.updateConnectionStatusIndicator();

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