Compare commits

..

505 Commits

Author SHA1 Message Date
Pietro Brenna 3c3f02d557 Aggiunto build system docker
Briq/multiparty-meeting/pipeline/head This commit looks good Details
2020-05-29 09:52:56 +02:00
Pietro Brenna 558129fe40 Tolgo cookie consent 2020-05-29 09:52:56 +02:00
Pietro Brenna 3e02a441c9 Tasto leave chiude finestra 2020-05-29 09:52:55 +02:00
Pietro Brenna b9acddd997 Fix temporaneo (bisognerebbe lavorare in App.js!) 2020-05-29 09:52:36 +02:00
Mészáros Mihály 32ee957561 Fix: bump version number 2020-05-29 09:52:35 +02:00
Håvar Aambø Fosstveit 46a2c075f0 Merge branch 'develop' 2020-05-22 22:07:17 +02:00
Håvar Aambø Fosstveit f703cd6b06 Update CHANGELOG 2020-05-22 21:37:15 +02:00
Håvar Aambø Fosstveit 63f6ba0509 Update dependencies 2020-05-22 21:36:47 +02:00
Håvar Aambø Fosstveit e1b56d41fd Update privacy.html 2020-05-22 21:35:23 +02:00
Mészáros Mihály f67b3aef88
Add support and privacy URLs (#414) 2020-05-22 21:26:15 +02:00
Håvar Aambø Fosstveit f546e22ae1 Remove video frame rate and screen sharing resolution, these are probably not required. Still configurable in app config. Fixes #415 2020-05-22 20:42:22 +02:00
Håvar Aambø Fosstveit ed0af0cd50 Move the button control bar if drawer is not overlayed, fixes #340 2020-05-22 20:30:29 +02:00
Håvar Aambø Fosstveit 2ca4cef3dc Make button control bar the top element, fixes #417 2020-05-22 20:29:02 +02:00
Håvar Aambø Fosstveit f064f98fd4 Lint 2020-05-22 20:03:05 +02:00
Håvar Aambø Fosstveit 803858575f Don't promote all peers on unlock, this bypassed roles/permissions, fixes #386 2020-05-22 19:59:43 +02:00
Håvar Aambø Fosstveit d5e14f8c5b Don't screen sharing icon to mobile, fixes #338 2020-05-22 19:53:26 +02:00
Håvar Aambø Fosstveit 7cb0150816 Screen sharing for opera and chromium, and only desktop 2020-05-22 19:47:51 +02:00
Håvar Aambø Fosstveit f0ee4de8fc Screensharing on Safari 13+, fixes #318 2020-05-22 19:36:38 +02:00
Håvar Aambø Fosstveit 6426a4d89e Hide advanced video settings by default, ref #415 2020-05-22 19:22:42 +02:00
Håvar Aambø Fosstveit 21f2018e71 Handle device change properly based on paused status. 2020-05-22 16:06:24 +02:00
Håvar Aambø Fosstveit 2e3749cafe Fixes #413 2020-05-22 16:05:44 +02:00
Mészáros Mihály 359bfac450 tidy for the previous commit 2020-05-22 11:47:12 +02:00
Mészáros Mihály 547e95b35c FF don't allows fingerprinting handle empty label 2020-05-22 11:45:14 +02:00
Håvar Aambø Fosstveit d1ffe6cd43 Adding frame rate option to videos. Make resolution and frame rate of screen sharing configurable. 2020-05-22 00:20:15 +02:00
Håvar Aambø Fosstveit b3ad0a44c2 Change resolution of extra videos too 2020-05-21 23:14:39 +02:00
Håvar Aambø Fosstveit 1efb1c4a2b Clean up device handling properly. Don't restart track on change in constraints. 2020-05-21 22:53:26 +02:00
Mészáros Mihály 147da7f793 Add chromioum to supportedBrowsers 2020-05-21 14:07:23 +02:00
Mészáros Mihály fc54ee7ddd Update hu translation 2020-05-21 11:39:49 +02:00
Håvar Aambø Fosstveit 11ec28aa5c Cleanup logging 2020-05-20 23:58:05 +02:00
Håvar Aambø Fosstveit 0654acded8 Cleaning up device handling. Now able to change devices properly when audio/video is disabled. 2020-05-20 23:57:41 +02:00
Håvar Aambø Fosstveit 425fd1a92b Hark cleanup 2020-05-20 23:48:03 +02:00
Håvar Aambø Fosstveit 93cc50ace2 Small fixes and cleanup 2020-05-20 23:44:33 +02:00
Håvar Aambø Fosstveit a1aec0a8d3 Cleanups 2020-05-20 23:44:11 +02:00
Håvar Aambø Fosstveit fbd39af210 Update translation 2020-05-20 23:41:45 +02:00
Roman Drozd 1179350759
Fix showing quality indicator on Me (#403) 2020-05-20 21:30:58 +02:00
mi4aux e0854d8d6b
Language files updates (#402)
* Added missing translation variable "settings.showAdvancedAudio"

Added missing translation variable "settings.showAdvancedAudio" to all translation files. Placed it before echoCancellation to keep logic order by the menu

* Update de.json

fix: SPACE-Taste ... it's LEERTASTE in german
added new translation variables

* Update de.json (typo-fix)

* Update en.json (suggestion)

Remove "Show" ... with the new layout/icon it should be clear for users.
Otherwise switching between "Show" and "Don't show" would be needed.
2020-05-20 21:30:31 +02:00
Håvar Aambø Fosstveit 27eeadbe84
Merge pull request #401 from havfo/feat-network-indicator
Fix crash when extra video is running
2020-05-20 20:17:22 +02:00
Håvar Aambø Fosstveit d75d2154f2 Proper locale setting, fixes #369 2020-05-20 20:15:56 +02:00
Roman Drozd e301474906 Add missing isExtraVideo props validation 2020-05-20 19:53:21 +02:00
Roman Drozd 522ecf873e Fix crash when extra video is running 2020-05-20 19:48:31 +02:00
Stefan Otto 28f92d79cf
Delete localAudioAnalyzer.js 2020-05-20 19:27:56 +02:00
Håvar Aambø Fosstveit 21a56f3b40
Merge pull request #349 from havfo/feat-network-indicator
create component "NetworkIndicator"

add to VideoView for Me,
receive data from server / getTransportStats,
estimate of connection/value [wip]
show recv/send bitrate in advanced mode

* Change probe counting from sec to state inc

* Rebuild, fix null error with null

* Store probe from zero

* Add highestBitrate

* Fix eslint errors

* remove graphical indicator, refactor only to statistics

* uninstall "wifi-network-indicator" package

* Remove "netInfo" class

* Change "advanced mode" box info "bitrate" from MB/s to Mb/s

* fix my own fault

* Fix crash when screen sharing is running

* Update transport stats only when advancedMode is on

* Fix/Mute eslint Multiline support info

* Fix lint

* Set VideoView/netInfo prop as not required

* Fix lint

* Set RoomClient/_getTransportStats() as public

Co-authored-by: Stefan Otto <stefan.otto@uninett.no>
2020-05-20 18:02:47 +02:00
Roman Drozd 50a9733af1 Set RoomClient/_getTransportStats() as public 2020-05-20 17:01:42 +02:00
Roman Drozd 81d4b015bc Fix lint 2020-05-20 16:56:53 +02:00
Roman Drozd ddd8c36c67 Merge branch 'develop' into feat-network-indicator 2020-05-20 16:47:02 +02:00
Roman Drozd 407a7d6991 Set VideoView/netInfo prop as not required 2020-05-20 16:21:07 +02:00
Roman Drozd ce2e18f01a Fix lint 2020-05-20 15:49:31 +02:00
Roman Drozd 90ef355fbf Fix/Mute eslint Multiline support info 2020-05-20 15:37:31 +02:00
Roman Drozd 3fc8f5683e Update transport stats only when advancedMode is on 2020-05-20 15:14:16 +02:00
Håvar Aambø Fosstveit ac079d1cd1 Change to Material-UI switch for settings 2020-05-20 14:56:39 +02:00
Stefan Otto 09ebb8f0d9 Merge branch 'develop' of https://github.com/havfo/multiparty-meeting into develop 2020-05-20 14:26:08 +02:00
Stefan Otto bb63b6304e Fix autoMuteThreshold bug #394 2020-05-20 14:25:13 +02:00
Mészáros Mihály 0b3514ddbc Translation fixes still 2020-05-20 14:17:40 +02:00
Mészáros Mihály 0660c151f9 Fix tranlation mistakes 2020-05-20 14:12:53 +02:00
Mészáros Mihály 5f8e2aaa36 Change value label from off to auto 2020-05-20 14:00:13 +02:00
Mészáros Mihály 4214522767 Change text template 2020-05-20 13:59:23 +02:00
Mészáros Mihály 58f153733d Add translations to autounmute 2020-05-20 13:58:18 +02:00
Stefan Otto 73709639e0 Merge branch 'develop' of https://github.com/havfo/multiparty-meeting into develop 2020-05-20 13:29:55 +02:00
Stefan Otto ee564e9187 Fix layout and padding for advanced audio settings 2020-05-20 13:29:40 +02:00
Mészáros Mihály 0154c3343c Add configurable, supported brwoser list. 2020-05-20 13:13:32 +02:00
Håvar Aambø Fosstveit d4a35e9e33 Make settings denser 2020-05-20 12:41:40 +02:00
Håvar Aambø Fosstveit 19c9f484fc Show error for IE users, fixes #393 2020-05-20 09:38:47 +02:00
Stefan Otto 28fb6f4a38 advanced audio settings as collapsed list 2020-05-20 11:21:19 +02:00
Stefan Otto ad3f7df203 more conservative settings for hark; adjust volume indicators colors to dB scale, improved decay for audio indicators 2020-05-20 11:19:20 +02:00
Stefan Otto 185b3d40bd Added som comments in config.example.js + alternative config for simulcast 2020-05-20 10:14:50 +02:00
Roman Drozd 014c805530 Fix crash when screen sharing is running 2020-05-20 00:09:44 +02:00
Mészáros Mihály 2bb5cd53af Tidy in hu translation 2020-05-19 21:18:00 +02:00
Mészáros Mihály ad1d4b80fa Remove unused moderator.muteScreenSharing 2020-05-19 21:12:19 +02:00
Håvar Aambø Fosstveit 209b545900 Lint 2020-05-19 15:16:24 +02:00
Mészáros Mihály a691e82f3e
Merge pull request #344 from havfo/feat-unsupportedbrowser
Detect unsupported browsers, add a dialog
2020-05-19 15:08:23 +02:00
Mészáros Mihály ee1a691642 Merge branch 'develop' into feat-unsupportedbrowser 2020-05-19 15:07:25 +02:00
Stefan Otto 78bdf80cea Fix initial audio level 2020-05-19 15:06:12 +02:00
Stefan Otto f17deb2589 lint; clean up; advanced audio settings switch 2020-05-19 15:01:38 +02:00
Mészáros Mihály a4ccf74bb9 Fix lint trailing spaces 2020-05-19 14:55:13 +02:00
Håvar Aambø Fosstveit 82c1d73698 Disallow trailing white spaces. 2020-05-19 14:53:08 +02:00
Mészáros Mihály d4759ba7fa Tidy in hu translation 2020-05-19 14:50:36 +02:00
Mészáros Mihály 4cb967664d Add moderator.stopScreenSharing translation 2020-05-19 14:50:36 +02:00
Mészáros Mihály f682529395 Change order in mobile menu 2020-05-19 14:50:32 +02:00
Mészáros Mihály c7ba8b2dc9 Remove lobby from mobile menu 2020-05-19 14:48:27 +02:00
Håvar Aambø Fosstveit d9cce46505 Lint 2020-05-19 14:26:19 +02:00
Håvar Aambø Fosstveit a0a23109a9 Lint 2020-05-19 14:25:13 +02:00
Håvar Aambø Fosstveit bb391b1390 Disallow trailing white spaces. 2020-05-19 14:22:03 +02:00
Stefan Otto a44e799c79 Merge branch 'develop' of https://github.com/havfo/multiparty-meeting into develop 2020-05-19 14:13:24 +02:00
Stefan Otto 384f0cf1e4 More sticky local speaker detection 2020-05-19 14:12:48 +02:00
Stefan Otto 8d55cd5d1a Fix Auto mute indicator error #392 2020-05-19 14:12:03 +02:00
Håvar Aambø Fosstveit 0f184a3a29 Socket request timeout handling, with retries. 2020-05-19 14:09:27 +02:00
Mészáros Mihály b0e57756cb Fix previous commit, move it to sectionMobile 2020-05-19 13:35:33 +02:00
Mészáros Mihály 28995ad74f Only on mobile add lobbypeers action 2020-05-19 13:25:52 +02:00
Stefan Otto b430802b76
fix my own fault 2020-05-19 12:04:09 +02:00
Mészáros Mihály aaa227e957
Merge pull request #389 from havfo/feat-version
Add version to about
2020-05-19 07:51:08 +02:00
Mészáros Mihály 2bd7218379 replace em with theme.spacing 2020-05-19 07:50:01 +02:00
Stefan Otto a6df51bd27
Merge branch 'develop' into feat-network-indicator 2020-05-19 03:17:02 +02:00
Stefan Otto 6576516091 lint + noiseVolume changes 2020-05-19 02:45:06 +02:00
Mészáros Mihály 868b91a776 Detect unsupported browsers, add a dialog 2020-05-19 01:57:34 +02:00
Stefan Otto 0f053d0282 Merge branch 'feat-audio-settings' into develop 2020-05-19 01:56:24 +02:00
Stefan Otto 0164312f30 lint 2020-05-19 01:51:38 +02:00
Stefan Otto ea84fc75c6 hark clean up; some state fixes for autoMute; fix remote hark audiolevel 2020-05-19 01:45:09 +02:00
Mészáros Mihály 2a4405fb7f Add version to about 2020-05-19 00:51:31 +02:00
Stefan Otto 6331bb18b4 mic indicator for autoMute with noiseThreshold 2020-05-18 23:38:17 +02:00
Mészáros Mihály 7f16487e45 Fixes in hu translation 2020-05-18 14:15:01 +02:00
Mészáros Mihály 0a860068b2 Merge branch 'moderator-tools' into develop 2020-05-18 13:45:08 +02:00
Mészáros Mihály 4ab7d7c95d Fix and add missing translations 2020-05-18 13:41:32 +02:00
Mészáros Mihály 2877ff2981 Remove end of line spaces 2020-05-18 11:30:23 +02:00
Mészáros Mihály c38e16f7f9 Merge branch 'PR#355' into moderator-tools 2020-05-18 11:23:33 +02:00
Luca 3a62abb7c3 Moderator: disable screen sharing 2020-05-18 09:58:46 +02:00
Luca 13c38cecfd Moderator: Mute all screen sharing 2020-05-18 09:52:19 +02:00
Luca e1f5f804b0 Lint fix 2020-05-18 09:52:19 +02:00
Luca ff096f993d Moderator: disable screen sharing 2020-05-18 09:52:14 +02:00
Mészáros Mihály 651fb4578d Add a lobby tooltip to topbar on mobile 2020-05-18 09:31:54 +02:00
Håvar Aambø Fosstveit ba6c57b283 Make notification position configurable. 2020-05-15 21:12:30 +02:00
Håvar Aambø Fosstveit 05d7cb571c Fix ptt over elements. FIxes #370 2020-05-15 20:54:48 +02:00
Mészáros Mihály 8ab8d0529f hide PTT Fix: #370 2020-05-15 17:16:12 +02:00
Mészáros Mihály 7373762889
Merge pull request #368 from sd4v1d/develop
Update hr translation and correction in en.json file
2020-05-15 15:25:45 +02:00
Saša Davidović 70ed6de497
Update en.json 2020-05-15 13:31:20 +02:00
Saša Davidović 80ff3a7171
Update hr.json 2020-05-15 13:29:00 +02:00
Saša Davidović 2467ac55da
Update hr.json 2020-05-15 13:24:33 +02:00
Saša Davidović a4272149f8
Merge pull request #7 from havfo/develop
Sync with upstream Develop
2020-05-15 12:57:58 +02:00
Stefan Otto 75912c71c2 ptt bar styles: same color as FAB-mute, z-index on top of drawer 2020-05-14 22:39:02 +02:00
Mészáros Mihály 87fd238682
Merge pull request #353 from havfo/feat-automutethreshold
Add autoMuteThreshold
2020-05-14 21:56:16 +02:00
Mészáros Mihály 042bc95b3a
Merge pull request #364 from Astagor/browse_passive_users
Browse passive users - fixed for filmstrip
2020-05-14 21:47:07 +02:00
Astagor daf958d627 Fixed lint 2 2020-05-14 14:27:03 +02:00
Astagor ceebea6a40 Fixed lint 2020-05-14 14:23:31 +02:00
Astagor 4e80c526a3 Fixed to work on filmstrip. Changed logic, use setSelectedPeer 2020-05-14 14:18:04 +02:00
Mészáros Mihály aae9e15812 fix: Stop screensharing remove stream properly 2020-05-14 14:01:56 +02:00
Mészáros Mihály eeb10d9313 Fix #358 set screenshare tooltip state manualy
See: mui-org/material-ui#12299
2020-05-14 09:45:42 +02:00
Mészáros Mihály b89bb3a513 Fix lint max length 2020-05-14 09:16:42 +02:00
Saša Davidović 77e807143b
Merge pull request #6 from havfo/develop
Sync with upstream Develop
2020-05-14 08:26:02 +02:00
Mészáros Mihály a1d348eefc fix lint 2020-05-14 08:09:53 +02:00
Stefan Otto b71731c62e Merge remote-tracking branch 'origin/develop' into feat-audio-settings 2020-05-14 04:13:35 +02:00
Stefan Otto 46174b414f Fix small audiobar to middle 2020-05-14 04:04:11 +02:00
Stefan Otto 534e862573 First working noiseThreshold setting + voiceActivatedUnmute 2020-05-14 04:02:52 +02:00
Luca 1257ce7cdd Translation for "Mute all screen sharing" button 2020-05-13 10:54:52 +02:00
Luca f54ed84edb Moderator: Mute all screen sharing 2020-05-13 10:42:03 +02:00
Luca 69244bdf12 Lint fix 2020-05-13 10:35:46 +02:00
Luca cbbfd1b26f Moderator: disable screen sharing 2020-05-13 10:35:46 +02:00
Mészáros Mihály ba8efb19ff
Merge pull request #291 from Astagor/browse_passive_users
Browse passive participants
2020-05-13 10:11:01 +02:00
Mészáros Mihály f99ae41eb3
Merge pull request #354 from YiPrograms/zh-hant-translation
Create Chinese(Traditional) translation
2020-05-13 09:41:34 +02:00
Yi Kuo 774532e33e
Create zh-Hant translation 2020-05-13 15:12:21 +08:00
Mészáros Mihály 8d9a045f32 Add autoMuteThreshold 2020-05-13 09:06:06 +02:00
Astagor e4f6bde164 Added description of arrow keys for browsing passive peers into spotlight 2020-05-13 08:27:21 +02:00
Astagor 122c21a632 Merge remote-tracking branch 'upstream/develop' into browse_passive_users 2020-05-13 07:59:09 +02:00
Mészáros Mihály 41d62cf9b8 Update hungarian translation 2020-05-13 07:57:38 +02:00
Mészáros Mihály 8af09ebf4f
Merge pull request #350 from mi4aux/develop
Different Typofixes / Suggestions
2020-05-13 06:12:59 +02:00
Mészáros Mihály c237da1f70
Merge pull request #348 from L4ky/translate-moderator-actions
Translate moderator actions
2020-05-13 06:11:04 +02:00
mi4aux bd06d68742 Typofix variablename 2020-05-12 22:56:46 +02:00
mi4aux af1dcd6bab Suggestions for better german translation
room.closeMeeting: "Meeting beenden"
or alternatively "Meeting abschließen"

room.about: "Über"
on software often also like "Über multiparty-meeting" or "Info" / "Informationen" in general
"Impressum" is more like "imprint" / "legal info" on a website, ..
but depends on what it's ment to be

label.medium: "Mittel"
better (true) german, "Medium" obtained on quality is german-english mix (or has other meaning)

label.appearence: "Ansicht"
shorter, more often used on software .. alternatively "Aussehen" or "Anzeige"
2020-05-12 22:55:42 +02:00
mi4aux d84d30e402 Typofix german translation 2020-05-12 22:52:30 +02:00
Roman Drozd 052dabaf8d Change "advanced mode" box info "bitrate" from MB/s to Mb/s 2020-05-12 21:40:47 +02:00
Roman Drozd 7b8ea2c756 Merge branch 'develop' into feat-network-indicator 2020-05-12 21:04:29 +02:00
Mészáros Mihály b3764a1d64 fixes #343 2020-05-12 20:35:37 +02:00
Roman Drozd 2c9d302d3c Remove "netInfo" class 2020-05-12 20:16:43 +02:00
Roman Drozd 972e74a00a uninstall "wifi-network-indicator" package 2020-05-12 19:53:36 +02:00
Roman Drozd 59d48253a3 remove graphical indicator, refactor only to statistics 2020-05-12 19:42:04 +02:00
Luca b8c0a8450d Lint fix 2020-05-12 19:31:51 +02:00
Luca 5ea482d01f Moderator: disable screen sharing 2020-05-12 19:04:41 +02:00
Luca 0e40ed7696 Update it translation 2020-05-12 18:17:51 +02:00
Luca 1e5b6680c4 Translate moderator mute actions on peer 2020-05-12 18:09:08 +02:00
Mészáros Mihály 2623ef2eaa Fix lint 2020-05-12 11:10:22 +02:00
Stefan Otto 349ace9fee
Merge pull request #335 from L4ky/italian-translation
Italian translation update
2020-05-11 12:12:56 +02:00
Roman Drozd 2a4f660691 Fix eslint errors 2020-05-11 08:55:05 +02:00
Luca 67ea56b02a Italian translation update 2020-05-11 08:50:34 +02:00
Håvar Aambø Fosstveit a7557e3e15 Show quality indicator on me view. Fix "Me" text on me view on hover on extra videos and screen sharing. 2020-05-10 23:08:43 +02:00
Håvar Aambø Fosstveit 7ab388a714 Propagate producer score into state. 2020-05-10 20:43:51 +02:00
Saša Davidović a8af8f0c8d
Merge pull request #5 from havfo/develop
sync with upstream Develop
2020-05-09 16:46:18 +02:00
Stefan Otto 36b40fd795
Added german translations 2020-05-09 12:15:05 +02:00
Håvar Aambø Fosstveit 439eb5cf4a
Merge pull request #326 from havfo/fix-translation-pl 2020-05-09 08:47:18 +02:00
Roman Drozd 2dcecae888 Update pl translation 2020-05-09 01:29:59 +02:00
Håvar Aambø Fosstveit 27ac8e2742 Remove unused function 2020-05-09 00:43:20 +02:00
Håvar Aambø Fosstveit f468f95cdc Translated media buttons, fixes #309 2020-05-09 00:43:05 +02:00
Håvar Aambø Fosstveit aa5f75b794 Do things in correct order. 2020-05-09 00:37:47 +02:00
Roman Drozd 1bccc9a985 Update pl translation 2020-05-09 00:29:20 +02:00
Roman Drozd 3624424e0a Add h shortcut for help dialog 2020-05-09 00:27:18 +02:00
Håvar Aambø Fosstveit 13e611e177 Missing tooltip, fixes #324 2020-05-09 00:07:12 +02:00
Håvar Aambø Fosstveit 1690394328 Option to make side drawer push videos to the side and be permanent, fixes #320 2020-05-09 00:03:56 +02:00
Roman Drozd c5143de647
Fix ptt break layout (#298) 2020-05-08 22:26:45 +02:00
Håvar Aambø Fosstveit a9e9a1c1fa Cleanup of join logic, and making sure that lobbyPeers are sent to all peers if last peer with PROMOTE_PEER leaves and allowWhenRoleMissing contains PROMOTE_PEER, fixes #303 2020-05-08 22:11:48 +02:00
Håvar Aambø Fosstveit c73ee5c26b Fix missing picture on peer in lobby. 2020-05-08 22:02:06 +02:00
Håvar Aambø Fosstveit 717c0053e5 Better handling of reconnect. Clear the state properly and handle spotlights. 2020-05-08 22:01:28 +02:00
Håvar Aambø Fosstveit d09e7f5565 Some error checks 2020-05-08 16:20:15 +02:00
Håvar Aambø Fosstveit a49258e840 New option for handling permissions in rooms. Set allowWhenRoleMissing to permit actions before a peer with that permission joins. Ref #303 2020-05-08 16:19:55 +02:00
Mészáros Mihály 220d4dd99d fix in lang hu 2020-05-08 14:46:30 +02:00
Mészáros Mihály 0de99d6da8 Update Hungarian language 2020-05-08 13:24:39 +02:00
Mészáros Mihály fbac155e30 Merge branch 'feat-help-about' into develop 2020-05-08 00:38:42 +02:00
L4ky 2eab32cafa
Moderator: Mute or stop video for a peer globally (#316) 2020-05-08 00:23:16 +02:00
Mészáros Mihály 3b779bd81d Add shotcuts to help and add labels 2020-05-08 00:20:35 +02:00
Håvar Aambø Fosstveit 837aa1ace2 Revert change for now, ref #249 2020-05-08 00:11:39 +02:00
Håvar Aambø Fosstveit a066a5df2f Revert server change, seems to be a problem with it. Ref #249 2020-05-07 23:22:46 +02:00
Håvar Aambø Fosstveit 16a59f1167 Don't show video mute icon for peer that does not have video visible. Fixes #302 2020-05-07 22:14:48 +02:00
Håvar Aambø Fosstveit ce8141eed7 Cleanup 2020-05-07 21:03:22 +02:00
Håvar Aambø Fosstveit 2373bf44d8 Allow serving other than root of site, fixes #249 2020-05-07 21:03:07 +02:00
Håvar Aambø Fosstveit 02b6c617c5 Respect hide media buttons setting on button control bar, ref #309 2020-05-07 15:15:44 +02:00
Håvar Aambø Fosstveit 4c6d9291bf Fix regression on advanced mode, ref #309 2020-05-07 14:57:42 +02:00
Håvar Aambø Fosstveit cfdaceed22 Add posibility to have separate media buttons, fixes #309 2020-05-07 13:35:29 +02:00
Saša Davidović 99a9ec7d1c
Merge pull request #4 from havfo/develop
sync with upstream repo
2020-05-07 12:53:09 +02:00
Håvar Aambø Fosstveit 7e91609276 Have a global try-catch in the server 2020-05-07 12:20:24 +02:00
Roman Drozd 084e5ab665 Add highestBitrate 2020-05-07 03:53:19 +02:00
Roman Drozd 49083376e2 Store probe from zero 2020-05-07 03:07:28 +02:00
Roman Drozd 71562cf57d Rebuild, fix null error with null 2020-05-07 02:39:23 +02:00
Mészáros Mihály 7c13403921 Add help and about modal 2020-05-06 23:39:40 +02:00
Håvar Aambø Fosstveit 457d679382 Setting to disable notifications, fixes #306 2020-05-06 23:08:51 +02:00
Roman Drozd 9a1646a39d Change probe counting from sec to state inc 2020-05-06 23:07:53 +02:00
Håvar Aambø Fosstveit c1aa62d22c Don't send lobbypeers to client if they don't have PROMOTE_PEER role, fixes #208 2020-05-06 22:22:57 +02:00
Håvar Aambø Fosstveit 1cbf2d5b38 Remove unused imports, fixes #305 2020-05-06 21:05:34 +02:00
Stefan Otto 3f1d102c59 fix my errors in gigantic merge + linting 2020-05-06 18:47:14 +02:00
Stefan Otto 57bb55764f Merge branch 'feat-audio-settings' into develop 2020-05-06 18:30:20 +02:00
Stefan Otto 59d4bd2ce7 Merge branch 'develop' into feat-audio-settings 2020-05-06 18:29:07 +02:00
Håvar Aambø Fosstveit 26874877ba Provide roomId to logout for load balanced scenarios, fixes #275 2020-05-06 14:03:47 +02:00
Håvar Aambø Fosstveit 45a89b9f1a Only keep one self destruct timeout, ref #255 2020-05-06 13:50:29 +02:00
Håvar Aambø Fosstveit feac3f069d Participant list is now animated on events, fixes #302 2020-05-06 13:30:05 +02:00
Stefan Otto 4f07b973c8 defaultAudio in config 2020-05-06 12:00:54 +02:00
Stefan Otto aca3499afb merge from develop 2020-05-06 02:33:37 +02:00
Håvar Aambø Fosstveit 136037d83f Simplify participantlist and order participants based on status. Raise hand queue, and moderator can remove raised hand. Fixes #146, #278 2020-05-06 01:40:08 +02:00
Stefan Otto da6c9d3ecf fix settings.js 2020-05-05 23:41:27 +02:00
Mészáros Mihály fa5f4f02a6 Use full room and peer object for the statusLogger 2020-05-05 23:24:00 +02:00
Stefan Otto a2d11121d3 next iteration state + audio settings 2020-05-05 22:28:46 +02:00
Håvar Aambø Fosstveit ee6409b2b3 Hide quality indicator if the quality has top score. 2020-05-05 22:20:21 +02:00
Mészáros Mihály 2acd35d32b Fixes in hungarian translation 2020-05-05 21:44:21 +02:00
Håvar Aambø Fosstveit 3c7afd2066 Make buttons in AppBar fit on narrow screens, fixes #279 2020-05-05 21:31:21 +02:00
Håvar Aambø Fosstveit c02c8b1d67 Missing translation string 2020-05-05 21:27:45 +02:00
Håvar Aambø Fosstveit 3710c3b3ac Add tooltips to participant list, fixes #299 2020-05-05 20:08:11 +02:00
Håvar Aambø Fosstveit 6ca73e190f
Merge pull request #300 from sd4v1d/develop
Croatian translation update
2020-05-05 19:50:36 +02:00
Håvar Aambø Fosstveit 458bc44211
Merge pull request #301 from L4ky/develop
Update italian translation
2020-05-05 19:49:41 +02:00
Luca 417788325b Update italian translation 2020-05-05 18:21:18 +02:00
Saša Davidović 22019434cc
Croatian translation update 2020-05-05 18:02:23 +02:00
Saša Davidović 51ff2cb80e
Merge pull request #3 from havfo/develop
sync with Develop upstream
2020-05-05 16:38:08 +02:00
Håvar Aambø Fosstveit 15fe4521b7 Fix bug on resizing filmstrip 2020-05-05 15:15:26 +02:00
Håvar Aambø Fosstveit e2211b000c Fix button scaling in filmstrip 2020-05-05 15:06:18 +02:00
Håvar Aambø Fosstveit 678abe05d0 Update translations 2020-05-05 15:00:58 +02:00
Håvar Aambø Fosstveit bf837b3398 Fix color on kick button 2020-05-05 14:31:02 +02:00
Håvar Aambø Fosstveit 2bb64596b0 Fix button sizes and take care not to overflow container in filmstrip, ref #115 2020-05-05 14:29:54 +02:00
Håvar Aambø Fosstveit 3f75a6c506 Fix icon colors 2020-05-05 13:43:32 +02:00
Mészáros Mihály dd9c0bb897 Update hungarian translation with the missing ones 2020-05-05 13:36:41 +02:00
Håvar Aambø Fosstveit dd6016e855 Remove unused function 2020-05-05 13:06:16 +02:00
Håvar Aambø Fosstveit 65f47d4b8d Fix screensharing on Edge 2020-05-05 10:31:41 +02:00
Håvar Aambø Fosstveit 7adcef19a3 Change to total number of peers, not just joined. Styling. 2020-05-05 09:56:21 +02:00
Håvar Aambø Fosstveit e2421f094f Shuffle workers to get routers on random cores. Increase routerScaleSize default to 40. 2020-05-05 09:48:26 +02:00
Håvar Aambø Fosstveit ee338accc8
Merge pull request #293 from Astagor/room_limit
Added limit for maximum number of users in a single room
2020-05-05 09:25:20 +02:00
Håvar Aambø Fosstveit 24f7bd2a9b
Merge pull request #292 from Astagor/share_photo_from_mobile_gallery
Added share photo from gallery on mobile
2020-05-05 09:22:51 +02:00
Mészáros Mihály 2b3ebe8eec
Merge pull request #281 from havfo/fix-oidc
Fix oidc
2020-05-05 08:55:33 +02:00
Mészáros Mihály a46de5ff54 eslint disable unused vars for next 2020-05-05 08:53:08 +02:00
Mészáros Mihály e950ec9dbe Add an error handler to Express to dump OIDC errors with uuid 2020-05-05 08:52:53 +02:00
Astagor 897b99cdbe Fixed lint 2020-05-05 08:31:45 +02:00
Astagor 5b8f2d83a9 Added a div to wrapp buttons in FileSharing 2020-05-05 08:25:33 +02:00
Astagor 9e14cfd924 Merge remote-tracking branch 'upstream/develop' into share_photo_from_mobile_gallery 2020-05-05 08:11:23 +02:00
Astagor 7e6795986e Fixed lint server 2020-05-05 08:08:35 +02:00
Astagor ab5893dbdf Fixed lint 2020-05-05 08:02:11 +02:00
Astagor 311936d21a Fixed conflict in server/config/config.example.js 2020-05-05 07:49:30 +02:00
Mészáros Mihály e039423dd5 Fix lint 2020-05-05 00:53:31 +02:00
Håvar Aambø Fosstveit 8c8a00f126 Remove config option that is not used anymore 2020-05-04 23:53:38 +02:00
Håvar Aambø Fosstveit 04c9cb3285 Merge branch 'develop' of github.com:havfo/multiparty-meeting into develop 2020-05-04 23:44:30 +02:00
Håvar Aambø Fosstveit 5af33068b1 Merge branch 'feat-server-scaling' into develop 2020-05-04 23:44:23 +02:00
Håvar Aambø Fosstveit 381f9cd733 All peers enter the same router up to config.routerScaleSize. Then go to the next one, and keep going until all routers are filled up to config.routerScaleSize. After that simple put peers into routers with least peers. 2020-05-04 23:33:51 +02:00
Mészáros Mihály 004fac3d13 Import Latvian translation 2020-05-04 21:48:14 +02:00
Mészáros Mihály b4b96e6551
Merge pull request #290 from osering/develop
Add Latvian (LV) localization, by Oskars Galanders
2020-05-04 21:35:49 +02:00
Astagor ac6ee1bfa3 Added limit for maximum number of users in a single room 2020-05-04 19:31:50 +02:00
Astagor f0b9d3be6a Added share photo from gallery on mobile 2020-05-04 18:59:06 +02:00
Astagor bad845e195 Browse passive participants 2020-05-04 18:27:50 +02:00
Oskars G 207b92cfb2
Add Latvian (LV) localization, by Oskars Galanders
Gift to Latvian people on the Day of the Restoration of Latvian Independence /from proprietary software/ :)
2020-05-04 17:03:00 +03:00
Håvar Aambø Fosstveit 4acedad987 Propagate raise hand timestamp to clients, ref #278 2020-05-04 15:21:49 +02:00
Håvar Aambø Fosstveit f70ed01e7e Add timestamps to various peer parameters. 2020-05-04 15:14:47 +02:00
Håvar Aambø Fosstveit 69a988200d
Merge pull request #289 from christian-2/feat-contributing
CONTRIBUTING.md
2020-05-04 15:12:22 +02:00
Håvar Aambø Fosstveit a1ed79c5db A button to promote all peers from lobby, fixes #287 2020-05-04 15:09:05 +02:00
christian2 ff285b2d87 app as well as server 2020-05-04 14:47:02 +02:00
Håvar Aambø Fosstveit a00d33ee4b
Merge pull request #272 from christian-2/mm-exporter
Prometheus monitoring
2020-05-04 14:35:08 +02:00
christian2 7282287d93 CONTRIBUTING.md 2020-05-04 14:34:50 +02:00
christian2 3c7dbb2109 Merge remote-tracking branch 'upstream/develop' into mm-exporter 2020-05-04 14:23:44 +02:00
christian2 f6c76f372a satisfy ESLint 2020-05-04 14:22:31 +02:00
Håvar Aambø Fosstveit b7aad16f65 Clean up CSS, make room for buttons, fixes #283 2020-05-04 13:54:13 +02:00
Håvar Aambø Fosstveit f4986cc9d7 Make participant list more compact 2020-05-04 11:14:09 +02:00
christian2 84f77f3813 employ config.js 2020-05-04 10:57:24 +02:00
christian2 9adcc807dd bug fixes 2020-05-04 09:47:10 +02:00
christian2 85c9062f86 Merge remote-tracking branch 'upstream/develop' into mm-exporter 2020-05-04 09:32:10 +02:00
Håvar Aambø Fosstveit e82f86c900
Merge pull request #282 from christian-2/issue-274
LICENSE.md
2020-05-04 08:59:55 +02:00
Mészáros Mihály e5fcda9fbf Bump version to 3.3.0 for ansible config templates 2020-05-04 08:51:56 +02:00
Håvar Aambø Fosstveit 2ed7c5711c Missing translation in extra video menu item 2020-05-04 08:49:22 +02:00
christian2 5d2634acbe MIT License 2020-05-04 07:49:09 +02:00
christian-2 dd8173081b
MIT License 2020-05-04 07:36:29 +02:00
christian2 29f3c5b036 MIT License 2020-05-04 07:34:01 +02:00
Håvar Aambø Fosstveit 28bad32f69 Add permission for sending extra video, fixes #280 2020-05-04 00:40:39 +02:00
Håvar Aambø Fosstveit 08e2c425c6 Add the ability for a peer to have several video producers in a room. 2020-05-04 00:16:23 +02:00
Håvar Aambø Fosstveit a0cd4416d8 Until we resolve login issue with service worker, disable it again 2020-05-04 00:07:56 +02:00
Håvar Aambø Fosstveit b2891090dc Register service worker 2020-05-03 00:13:02 +02:00
Håvar Aambø Fosstveit dc7b51b38a Make notification sounds configurable 2020-05-02 23:45:08 +02:00
Håvar Aambø Fosstveit c7c4a76b33 Sound and notification on raise hand, fixes #40 2020-05-02 23:28:03 +02:00
Håvar Aambø Fosstveit 1dcb4eabf2 Adding badge of "unread" raised hands, ref #40 2020-05-02 23:16:16 +02:00
Håvar Aambø Fosstveit 272cef52b4 You are muted message fix when hiding controls, fixes #267 2020-05-02 23:09:23 +02:00
Håvar Aambø Fosstveit c76c6d548a Make visibility of media controls configurable, fixes #267 2020-05-02 22:33:59 +02:00
Håvar Aambø Fosstveit c461ee4eb7 Clean up lock code. 2020-05-02 21:58:41 +02:00
Håvar Aambø Fosstveit 130ed19f13 Settings split into tabs. 2020-05-02 21:57:53 +02:00
Håvar Aambø Fosstveit d76682b6c8 Added raise hand button back to UI, fixes #40 2020-05-02 13:34:19 +02:00
Håvar Aambø Fosstveit e33c1f7c03 Clean up raised hand naming. Only needs UI now. Ref #40 2020-05-02 10:47:57 +02:00
Håvar Aambø Fosstveit e061cce53f Add raised hand status to peerInfo. This will give joining peers raise hand status. Ref #40 2020-05-01 23:49:16 +02:00
Håvar Aambø Fosstveit f28abc9c74 Fix videoview in videowindow 2020-05-01 23:34:29 +02:00
Håvar Aambø Fosstveit c0507daabc Change in lifecycle method 2020-05-01 23:33:59 +02:00
Håvar Aambø Fosstveit 3e858ae6cd Fix network indicator for fullscreen video. Fixes #218 2020-05-01 23:33:39 +02:00
Håvar Aambø Fosstveit d6d962e379 Fix filmstrip. Need to fix buttons inside videoview if screen is too small. Fixes #231, ref #115 2020-05-01 23:32:13 +02:00
Håvar Aambø Fosstveit 695d366e59 Fix network status icon for speaker in filmstrip view. 2020-05-01 23:28:42 +02:00
Håvar Aambø Fosstveit 36b7e45f05 Clean up lifecycle methods 2020-05-01 22:04:14 +02:00
Håvar Aambø Fosstveit 87b114fccc Styling and lint 2020-05-01 21:49:40 +02:00
Håvar Aambø Fosstveit 2ad7b45ef6 Fix translations 2020-05-01 21:49:04 +02:00
Håvar Aambø Fosstveit 7ed99c51d0 Add missing peer.picture to lobby peerlist 2020-05-01 21:48:28 +02:00
Håvar Aambø Fosstveit 881eac741a Remove getServerHistory, and use 'join' callback instead. 2020-05-01 21:48:09 +02:00
Håvar Aambø Fosstveit a8dabf7343 If peer gets role PROMOTE_PEER, notify peer of all peers in lobby, fixes #208 2020-05-01 21:45:16 +02:00
christian2 ebb728f4a8 remove extra identation 2020-05-01 15:37:03 +02:00
christian2 3717e41ac5 MIT License already mentioned in README.md 2020-05-01 11:46:23 +02:00
christian2 00f1ec7929 align coding conventions 2020-05-01 11:41:18 +02:00
Christian Hörtnagl c61ebc8287 Merge remote-tracking branch 'upstream/develop' into mm-exporter 2020-05-01 07:58:19 +02:00
Håvar Aambø Fosstveit 4d3ebefc92 Change login button based on login status, fixes #183 2020-05-01 00:44:02 +02:00
Håvar Aambø Fosstveit 49a951466f Fix missing roomId in login from joindialog, ref #183 2020-05-01 00:31:07 +02:00
Håvar Aambø Fosstveit 1e5c0eb772 Lint and a small bug 2020-05-01 00:24:19 +02:00
Håvar Aambø Fosstveit 574a86e3ec Don't show lobby, and don't provide events for lobby for peers that don't have PROMOTE_PEER permission, fixes #208 2020-05-01 00:23:38 +02:00
Mészáros Mihály 136a41a7e9 Tidy, add privkey password 2020-05-01 00:09:16 +02:00
Mészáros Mihály 27e6cd8d64 Regression from 71e695b912 2020-05-01 00:04:33 +02:00
Håvar Aambø Fosstveit ddd93038c4 Make joindialog text bold and bigger, fixes #154 2020-04-30 23:30:47 +02:00
Mészáros Mihály e574791cf0 Add privacy statement placeholder 2020-04-30 23:07:45 +02:00
Håvar Aambø Fosstveit 3de8555c10 Proper handling of moderator clearing files and sharing same file twice. Fixes #257 2020-04-30 23:06:02 +02:00
Mészáros Mihály de413fb670 Add eslint-plugin-react to deve dependency 2020-04-30 23:01:12 +02:00
Håvar Aambø Fosstveit 7a3bd327f2 Play audio from video elements, fixes #242 2020-04-30 22:42:47 +02:00
Håvar Aambø Fosstveit 5c4eeb121a Change to a more logical audio mute icon 2020-04-30 13:14:37 +02:00
Håvar Aambø Fosstveit 881164a718 Add option for muting remote videos, fixes #204 2020-04-30 13:10:34 +02:00
Mészáros Mihály 2e552fb07d
Merge pull request #269 from mertcelen/master
Automatic Debian Package
2020-04-30 12:59:42 +02:00
Håvar Aambø Fosstveit 2fced42c42 Cleanups. 2020-04-30 12:50:43 +02:00
Håvar Aambø Fosstveit 1ffa4fdc9a Better handling of default resolution. 2020-04-30 12:50:23 +02:00
Håvar Aambø Fosstveit 2ece8e9975 Make layout configurable in client, fixes #227 2020-04-30 12:48:36 +02:00
Håvar Aambø Fosstveit a64830a06a Make lastN configurable, max, and lock. Fixes #225 2020-04-30 12:32:43 +02:00
Mészáros Mihály 174a03814f Fixes: #268 2020-04-29 21:39:37 +02:00
Håvar Aambø Fosstveit c23d635803 ESlint for server 2020-04-28 22:23:15 +02:00
Mészáros Mihály 916003e085 clarify more that ip change is mandatory
Move to documentation IP range
2020-04-27 13:39:26 +02:00
Mészáros Mihály 4c8c746011 Add turkish translation 2020-04-27 11:59:38 +02:00
Ali Orhun Akkirman 9376e42d49 Create turkish language file 2020-04-27 11:55:51 +02:00
Roman Drozd b73b8c1aa0 create component "NetworkIndicator"
add to VideoView for Me,
receive data from server / getTransportStats,
estimate of connection/value [wip]
show recv/send bitrate in advanced mode
2020-04-27 00:14:29 +02:00
Mészáros Mihály cb5f4cd48e Only add OIDC request params that's really needed 2020-04-24 21:02:28 +02:00
Christian Hörtnagl b6d4833b62 Merge remote-tracking branch 'upstream/develop' into mm-exporter 2020-04-24 07:36:38 +02:00
Christian Hörtnagl 53e6ae68c9 documentation (firewall) 2020-04-24 07:36:11 +02:00
Mészáros Mihály aa096a755f
Merge pull request #253 from havfo/feat-output-select
Feat output select
2020-04-23 23:26:57 +02:00
Mészáros Mihály 1b3be17930 Add to devicechange listener 2020-04-23 23:05:15 +02:00
Mészáros Mihály e04d3c12bd Use the first audio output device by default
It is the OS default audio output in Chrome.
2020-04-23 22:53:34 +02:00
Mészáros Mihály d06f44d1d4 Remove redundant browser state, and tidy 2020-04-23 22:51:03 +02:00
Mészáros Mihály 3caa505d08 Fix state mistake 2020-04-23 22:42:25 +02:00
Mészáros Mihály f0c3b16197 Add new app config audioOutputSupportedBrowsers and tidy 2020-04-22 23:45:13 +02:00
Håvar Aambø Fosstveit 5124e44b5a Merge branch 'develop' of github.com:havfo/multiparty-meeting into develop 2020-04-22 22:15:50 +02:00
Håvar Aambø Fosstveit fcc37f5d89 Missing null in return, ref #209 2020-04-22 22:15:46 +02:00
Mészáros Mihály ff0a26a01f
Merge pull request #241 from mi4aux/develop
Typo fix of variable name / Replace static message with interantionalized message
2020-04-22 21:41:58 +02:00
Mészáros Mihály 6acfe57044
Merge branch 'develop' into develop 2020-04-22 21:41:39 +02:00
Mészáros Mihály d07b2a2101 update with new props 2020-04-22 13:09:32 +02:00
Mészáros Mihály 3236a18a0b Limit audio output selection to chrome - whitelist 2020-04-22 13:09:32 +02:00
Mészáros Mihály 58b4aaf6c1 Compare audioOutputDevice states 2020-04-22 13:09:32 +02:00
Mészáros Mihály 1ea330059e Change browser name to lowercase 2020-04-22 13:09:32 +02:00
Mészáros Mihály 5e3f50c052 Merge remote-tracking branch 'origin/develop' into feat-output-select 2020-04-22 13:07:21 +02:00
Christian Hörtnagl 8c7b741dea Merge remote-tracking branch 'upstream/develop' into mm-exporter 2020-04-22 10:49:05 +02:00
Håvar Aambø Fosstveit cdf899a948 We don't need to change sink manualy. State, and React handles that for us. 2020-04-22 10:41:12 +02:00
Håvar Aambø Fosstveit 37d5fdeefc Proper state handling 2020-04-22 10:30:29 +02:00
Håvar Aambø Fosstveit 0ed9d4db9b Various lint and fixes 2020-04-22 10:26:58 +02:00
Håvar Aambø Fosstveit 66b513cf19 Cleanup 2020-04-22 10:21:49 +02:00
Håvar Aambø Fosstveit 1cef52a3a1 Properly handle wakelock, fixes #237 2020-04-22 10:01:58 +02:00
Håvar Aambø Fosstveit e28b6cdc5d MODERATE_FILES role can clean all files in a room, fixes #209 2020-04-22 01:07:16 +02:00
Håvar Aambø Fosstveit 7340621b6e Unused CSS 2020-04-22 00:43:04 +02:00
mi4aux 9cc2999694 Added missing npm dependencie "classnames" 2020-04-21 21:10:22 +02:00
mi4aux 665dc235ff Revert "Added missing npm dependencie "classnames""
This reverts commit aa22cde501.
2020-04-21 20:19:15 +02:00
Christian Hörtnagl 60a6a6bc40 Merge remote-tracking branch 'upstream/develop' into mm-exporter 2020-04-21 18:34:45 +02:00
Mészáros Mihály bf2928efb3 Merge branch 'feat-alt-cellular-icon' into develop 2020-04-21 01:41:28 +02:00
Mészáros Mihály 4d427cef97 Add community list to readme 2020-04-21 01:15:10 +02:00
Mészáros Mihály f3ac236ccb Add classnames as dependency 2020-04-21 00:22:29 +02:00
Håvar Aambø Fosstveit 91a258c273 MODERATE_CHAT role can clear the chat in a room, references issue #209 2020-04-20 23:22:19 +02:00
mi4aux aa22cde501 Added missing npm dependencie "classnames" 2020-04-20 22:13:40 +02:00
mi4aux 9df750d7aa Replace static message with internationalized message by translation file 2020-04-20 21:09:57 +02:00
mi4aux b46e608022 Typo fix of variable name
Typo fix of variable name (to have uniform variable naming, same camel case)
2020-04-20 21:08:12 +02:00
Håvar Aambø Fosstveit df86c8b493
Merge pull request #240 from christian-2/issue-203
fixes #203
2020-04-20 12:58:44 +02:00
Christian Hörtnagl c0251ad0ce Merge remote-tracking branch 'upstream/develop' into mm-exporter 2020-04-20 12:12:45 +02:00
Christian Hörtnagl 804dd90e42 fixes #203 2020-04-20 11:49:59 +02:00
Mészáros Mihály 566fbb30fb Change Icon to alt cellular 2020-04-20 09:37:17 +02:00
Christian Hörtnagl ca53e4fb7f Prometheus exporter (initial version) 2020-04-20 06:22:20 +02:00
Mészáros Mihály 3fb24afe11 hu lang fixes 2020-04-19 15:49:06 +02:00
Mészáros Mihály 520f7cb6df Add translations 2020-04-19 15:48:28 +02:00
Mészáros Mihály 944b809c15 AudioPeers and Fixes 2020-04-19 13:52:15 +02:00
Mészáros Mihály e98d80ed57 Add a not yet complete audio out selection. 2020-04-18 23:20:58 +02:00
Mészáros Mihály bb9c07de3b Update 3.4.1 2020-04-17 13:34:17 +02:00
Mészáros Mihály f254440600 Update node version to 13.x 2020-04-17 10:12:01 +02:00
Mészáros Mihály da429cf1b5 Lowercase room name in url path 2020-04-16 23:06:18 +02:00
Mészáros Mihály 6cb8e1d158 Merge branch 'fix-hardcoded-rest-url' into develop
Fixes #190
2020-04-16 13:51:47 +02:00
Mészáros Mihály 5a9fc063bf Move params to config 2020-04-16 08:18:43 +02:00
Mészáros Mihály 8ac9bf1d9c Fix comment 2020-04-15 11:02:41 +02:00
Andrea Gelmini f9e3f9b622 Fix typos 2020-04-15 08:33:02 +02:00
Mészáros Mihály da40742fd7 Merge branch 'feat-userinfo' into develop 2020-04-15 06:44:32 +02:00
Mészáros Mihály 14ed627ca3 move lti claims to userinfo 2020-04-15 06:37:35 +02:00
Mészáros Mihály 4a08594532 move tokenset_claims to userinfo 2020-04-15 06:37:35 +02:00
Mészáros Mihály 36fab6c572 set in enable AV device constraint to ideal instead of exact 2020-04-13 23:17:17 +02:00
Andrea Gelmini 3cf0f9d3e7 Fix typos 2020-04-09 22:20:41 +02:00
Mészáros Mihály e39b475330
Merge pull request #194 from sd4v1d/patch-4
Croatian translation update
2020-04-09 22:00:36 +02:00
Saša Davidović 11441ca8ca
Croatian translation update
Terminology and syntax update in development branch
2020-04-09 21:59:44 +02:00
Håvar Aambø Fosstveit 6267f808db Lint 2020-04-09 19:09:14 +02:00
Håvar Aambø Fosstveit 0077c459e2 Fix if displayName is null 2020-04-09 18:58:44 +02:00
Stefan Otto 1469f6c5fb cleanup calling hark 2020-04-09 17:59:29 +02:00
Mészáros Mihály afa27fef44
Merge pull request #181 from sd4v1d/patch-2
Update Croatian translation
2020-04-09 11:49:28 +02:00
Håvar Aambø Fosstveit 403c52339a Enable wakelock only on mobile 2020-04-08 23:41:51 +02:00
Mészáros Mihály 724e5b7e75 Add wakelock for mobile 2020-04-08 16:20:47 +02:00
Mészáros Mihály 1067823ede Fixes: #188 handle if _webcamProducer is null 2020-04-08 09:32:36 +02:00
Håvar Aambø Fosstveit ee16bf809e Quality indicator on screen sharing missing 2020-04-07 21:38:35 +02:00
Håvar Aambø Fosstveit 7510a5da27 Merge branch 'develop' of github.com:havfo/multiparty-meeting into develop 2020-04-07 19:47:47 +02:00
Håvar Aambø Fosstveit 226b0f6293 Change quality indicator for peers 2020-04-07 19:47:36 +02:00
Saša Davidović bbc8ec3796
Update Croatian translation
development branch
2020-04-07 15:34:26 +02:00
Roman Drozd be8414212b Fix spacing of close button
Displayed incorrectly for some languages
2020-04-07 12:54:22 +02:00
Håvar Aambø Fosstveit 6f36b4f468 Update CHANGELOG 2020-04-07 10:17:54 +02:00
Mészáros Mihály 7e7f93d484 Update changelog 2020-04-07 10:17:43 +02:00
Håvar Aambø Fosstveit bac00a87e1 Cleaning 2020-04-07 10:05:01 +02:00
Mészáros Mihály 35c2bc7dd8 Add HAproxy doc 2020-04-07 10:04:36 +02:00
Håvar Aambø Fosstveit f2f548112d Change to arrow functions 2020-04-07 10:00:34 +02:00
Mészáros Mihály 2a2d61f860 Logout destroy session 2020-04-07 10:00:10 +02:00
Håvar Aambø Fosstveit c5bfcf3810 Update README 2020-04-07 09:54:32 +02:00
Melkin1968 8f4a5aff4e Update index.js to include Ukrainian localisation file 2020-04-07 09:52:23 +02:00
Melkin1968 ef6e521272 Add uk(Ukraine) localisation file 2020-04-07 09:52:23 +02:00
Mészáros Mihály a5b6c9a367 Fix cz=>cs iso lang code for Czech translation 2020-04-07 09:51:50 +02:00
Mészáros Mihály d9659a3935 Import and use Italian translation 2020-04-07 09:26:50 +02:00
Mészáros Mihály 87d7312a37 Add LTI doc 2020-04-07 09:24:57 +02:00
Mészáros Mihály 11b3f65ec9 Add listeningHost fix #179 2020-04-07 09:10:44 +02:00
Håvar Aambø Fosstveit 4d8dd667bc
Merge pull request #176 from havfo/fix-pl-translation
Fix yet another traslation
2020-04-06 17:48:02 +02:00
Roman Drozd c9b77ec962 Fix yet another traslation 2020-04-06 16:10:18 +02:00
Håvar Aambø Fosstveit b14ce6964c
Merge pull request #175 from havfo/fix-pl-translation
Fix pl translation
2020-04-06 08:08:17 +02:00
Roman Drozd b16a7cca6c Fix the translation 2020-04-06 05:07:50 +02:00
Håvar Aambø Fosstveit 938d344bd0 Login status not reflected in login button, fixes #173 2020-04-05 21:47:44 +02:00
Håvar Aambø Fosstveit 8ea875e8d1 Fix bug on rotated videos from mobile devices 2020-04-03 22:49:05 +02:00
Håvar Aambø Fosstveit 97bfca3471 Don't show screen sharing button on mobile 2020-04-03 22:22:33 +02:00
Håvar Aambø Fosstveit f174583ffd Remove unused variables 2020-04-03 21:29:59 +02:00
Håvar Aambø Fosstveit 77718516e2 Update dependencies 2020-04-03 19:45:20 +02:00
Håvar Aambø Fosstveit e1bb07bc4e Make tracker configurable, fixes #171 2020-04-03 18:12:31 +02:00
Håvar Aambø Fosstveit 10886e3dc6 Small bugs 2020-04-03 10:17:58 +02:00
Håvar Aambø Fosstveit 8544279425 Remove unused variable 2020-04-03 10:17:32 +02:00
Håvar Aambø Fosstveit 8e85ce51e8 Propagate click correctly 2020-04-03 10:07:20 +02:00
Håvar Aambø Fosstveit 4a273f0cd3 Clean up styling and use ONE set of controls regardless of platform 2020-04-03 10:02:23 +02:00
Håvar Aambø Fosstveit b5754bbf96 Merge branch 'feat-roles-authorization' into develop 2020-04-02 00:32:38 +02:00
Håvar Aambø Fosstveit a6347dc283 Expand permissions/role system. Clients are now provisioned with their roles when they join and will have features enabled/disabled based on their permissions. 2020-04-02 00:28:05 +02:00
Håvar Aambø Fosstveit fa032036d7 Give client correct authentication status when joining a room. Fixes #166 2020-04-01 21:58:41 +02:00
Håvar Aambø Fosstveit b19add7599 Remove unused and duplicate signaling 2020-04-01 21:40:55 +02:00
Håvar Aambø Fosstveit 0d4b1c3916 Sort participant list alphabetically 2020-04-01 21:38:54 +02:00
Håvar Aambø Fosstveit 038aeaff67 Add quality indicator pr peer 2020-04-01 14:13:46 +02:00
Håvar Aambø Fosstveit 197156e6f6 Handle it correctly if a user tries to share a file that has allready been shared. Closes #142 2020-03-31 20:32:36 +02:00
Håvar Aambø Fosstveit 160a510f8e Merge branch 'hotfix-react-scripts' into develop 2020-03-31 12:39:42 +02:00
Håvar Aambø Fosstveit fe53243372 Lint and fix 2020-03-31 01:31:53 +02:00
Håvar Aambø Fosstveit c0b37e0e1b Merge branch 'develop' of github.com:havfo/multiparty-meeting into develop 2020-03-31 01:29:34 +02:00
Håvar Aambø Fosstveit a31da9359b Add logging class that receives room and peer events 2020-03-31 01:27:59 +02:00
Stefan Otto 9442f529f4 clean up; first testing 2020-03-30 22:42:40 +02:00
Stefan Otto 924e78de39
lint 2020-03-30 12:54:56 +02:00
Stefan Otto 773184d41f
Merge pull request #158 from tobias231h/develop
Update de.json
2020-03-30 12:50:19 +02:00
Stefan Otto d606acf1e4
Merge branch 'develop' into develop 2020-03-30 12:49:46 +02:00
Mészáros Mihály 9065996abd Add trustProxy option to server config 2020-03-29 09:46:54 +02:00
Håvar Aambø Fosstveit 9da1afd4af Merge branch 'feat-client-reconnect' into develop 2020-03-28 23:24:18 +01:00
Håvar Aambø Fosstveit 87d4037562 We need jwt to make sure no one can hijack peerId 2020-03-28 23:20:37 +01:00
Håvar Aambø Fosstveit c521bb9ad1 Firefox relay if turnservers configured, fixes #160 2020-03-28 21:17:59 +01:00
Stefan Otto 43e5769151 linting + effect for ptt 2020-03-28 01:20:26 +01:00
Stefan Otto eef2c439f0 Merge branch 'feature-ptt' into develop 2020-03-28 00:51:21 +01:00
Stefan Otto de0cb99656 small layout fixes; enable clientside auto mute for > 4 peers 2020-03-28 00:09:49 +01:00
Håvar Aambø Fosstveit a1dc652d04 Linting and translations 2020-03-27 22:57:04 +01:00
Håvar Aambø Fosstveit 34cdac8102 Bug in Lobby close() 2020-03-27 21:49:38 +01:00
Håvar Aambø Fosstveit 2e68a7d891 Bug in Room close 2020-03-27 21:17:03 +01:00
Tobias H 5b96e34ea9
Update de.json 2020-03-27 15:56:24 +01:00
Håvar Aambø Fosstveit 1e1f714db5 Always give audio the priority if there is bandwidth problems 2020-03-27 14:23:51 +01:00
Håvar Aambø Fosstveit 76adbf1d69 Update french translation 2020-03-27 12:11:59 +01:00
Stefan Otto 274954b108
Merge pull request #155 from JCBRU/patch-2
Update fr.json
2020-03-27 12:07:20 +01:00
Jean-Christophe Bruneau 3756bfdfeb
Update fr.json 2020-03-27 11:24:51 +01:00
Håvar Aambø Fosstveit 71e695b912 Remove option to configure multiPartyMeeting server, use window.location.hostname 2020-03-27 11:18:49 +01:00
Håvar Aambø Fosstveit b4ad79d5f9 Merge branch 'develop' of github.com:havfo/multiparty-meeting into develop 2020-03-27 10:46:20 +01:00
Håvar Aambø Fosstveit 9cd6efe761 Merge branch 'feature-ptt' into develop 2020-03-27 10:46:14 +01:00
Håvar Aambø Fosstveit 3043098f0c Remove jwt, not needed 2020-03-27 10:38:51 +01:00
Mészáros Mihály 7d0dde8ada Add Czech translation
Thanks to Jan Ruzicka!
2020-03-27 10:36:44 +01:00
Mészáros Mihály c42e5f8e9c Fix regression: permanent top bar by default 2020-03-27 10:36:44 +01:00
Stefan Otto feeec35992 text size ptt; no audio indicator for muted mic 2020-03-27 02:06:17 +01:00
Stefan Otto 0228f1c372 fix: space activates elements in last clicked focus; fix: change muted mic deactivates hark 2020-03-27 01:47:04 +01:00
Håvar Aambø Fosstveit d20f0c161f Handle client reconnects better 2020-03-27 01:36:11 +01:00
Stefan Otto e12ff16115 first implementation of PTT; Adjusting Opus parameter for shorter pTime, lower bandwidth (mono) and Forward Error Correction 2020-03-27 01:01:54 +01:00
Håvar Aambø Fosstveit 2b96e0b039 Remove package-lock files 2020-03-26 22:21:27 +01:00
Mészáros Mihály 807b55c2ad Add some extra checks for video stream and track 2020-03-26 22:01:18 +01:00
Mészáros Mihály 62165ea597 Fix https redirect 2020-03-26 22:01:18 +01:00
Håvar Aambø Fosstveit 5071282d40 Merge branch 'feat-user-roles' into develop 2020-03-26 21:31:57 +01:00
Stefan Otto d83e035cda
Merge pull request #149 from dpakkia/patch-1
Create it.json
2020-03-25 00:00:04 +01:00
dpakkia 4dd274d8e7
Create it.json
Italian translation from a native speaker. Hope this helps!
2020-03-24 23:24:18 +01:00
Håvar Aambø Fosstveit 0600d1207b Temporary fix for Firefox because trouble with TURN tcp 2020-03-24 19:50:56 +01:00
Håvar Aambø Fosstveit 92a0370499 Don't break existing configs 2020-03-24 19:40:09 +01:00
Håvar Aambø Fosstveit c914cbcb9f Properly close the room on moderator close 2020-03-24 18:35:43 +01:00
Håvar Aambø Fosstveit 0b62856414 Messages that are not translated are now null 2020-03-24 11:04:44 +01:00
Håvar Aambø Fosstveit 253de07d95 Update translations, needs translation 2020-03-24 10:42:55 +01:00
Håvar Aambø Fosstveit 04b2d6d443 Add "close meeting" function for moderator 2020-03-24 01:43:40 +01:00
Håvar Aambø Fosstveit d756dd4721 Cleanup 2020-03-24 01:23:17 +01:00
Håvar Aambø Fosstveit 4135be9789 Remove duplicate signaling 2020-03-24 00:59:35 +01:00
Håvar Aambø Fosstveit dd6d07391d Remove login on room chooser 2020-03-24 00:57:14 +01:00
Håvar Aambø Fosstveit 43b218fb3a Remove roles on logout 2020-03-24 00:56:26 +01:00
Håvar Aambø Fosstveit 376ed6d44f Cleanup 2020-03-23 23:56:36 +01:00
Håvar Aambø Fosstveit 9b8853f984 Typo 2020-03-23 23:18:06 +01:00
Håvar Aambø Fosstveit c1cb0445fb Wrong state handling 2020-03-23 23:06:08 +01:00
Håvar Aambø Fosstveit 013abb15ba Include original peer in socket broadcast 2020-03-23 22:57:18 +01:00
Håvar Aambø Fosstveit 764e02c732 Typos 2020-03-23 22:52:53 +01:00
Håvar Aambø Fosstveit 37f3c8cabd Lint 2020-03-23 21:42:01 +01:00
Håvar Aambø Fosstveit 71b90dfb6c Use REST eduTurn 2020-03-23 21:41:34 +01:00
Håvar Aambø Fosstveit cc4053ebea Syntax 2020-03-23 20:35:51 +01:00
Håvar Aambø Fosstveit 33ef7746a3 Move audio/video controls out to bottom of screen if on mobile 2020-03-23 15:30:43 +01:00
Håvar Aambø Fosstveit 698a57cb3e Scaling up to new router after this many users connect 2020-03-23 14:59:25 +01:00
Håvar Aambø Fosstveit 002950d708 Fix for authentication data flow 2020-03-23 14:44:12 +01:00
Stefan Otto ec8c347a23
fix / typo 2020-03-23 14:13:05 +01:00
Håvar Aambø Fosstveit d446b33695 Room now scales up to total server capacity 2020-03-22 22:41:48 +01:00
Håvar Aambø Fosstveit ed6f256fb3 Various cleanups 2020-03-22 19:55:06 +01:00
Håvar Aambø Fosstveit 603368007a Added roomid to authenitcation data flow to make mapping user info better. 2020-03-22 15:20:49 +01:00
Håvar Aambø Fosstveit 7f2f27b858 Add support for moderating rooms. Kick user, mute all users, stop all videos. 2020-03-22 00:43:47 +01:00
Håvar Aambø Fosstveit c70740f5c7 Add support for user roles 2020-03-20 21:16:16 +01:00
Håvar Aambø Fosstveit 743b9e4869 Syntax 2020-03-20 21:15:35 +01:00
Håvar Aambø Fosstveit 587f185f09 Small fix for error in room reducer. 2020-03-20 21:14:43 +01:00
Håvar Aambø Fosstveit e674daaf1a Add user roles for server. 2020-03-20 21:14:08 +01:00
116 changed files with 12293 additions and 3553 deletions

View File

@ -1,5 +1,40 @@
# Changelog # Changelog
## 3.3
* Add: Rooms now scale across cores
* Add: Permissions and roles. Users can now have different roles (moderator, admin etc.) that give different permissions.
* Add: TURN API or fallback TURN server
* Add: Configurable room size limit
* Add: Prometheus monitoring support
* Add: Possible to share several videos (ex: 2 webcams)
* Add: Configurable audio settings (echocancellation etc.)
* Add: Configurable audio output device (in supported browsers)
* Add: Audio auto mute/unmute based on volume
* Add: Handle unsupported browsers properly
* Add: Lots of appearence settings
* Add: Side drawer can now stay permanently open
* Add: Move control buttons to separate control bar
* Add: Can now "raise hand"
* Add: Screen sharing in Safari 13+, Opera and Edge
* Add: Extended advanced info about network in client
* Add: Configurable screen sharing frame rate
* Add: Help and About dialogs
* Add: More keyboard shortcuts
* Add: Quality indicator on videos
* Add: More translations
* Fix: Various UI fixes and improvements
* Fix: Better audio/video device handling
* Fix: Update keyboard shortcut handling
* Fix: Authentication for load balanced scenarios
* Fix: Signaling when entering lobby
* Fix: Signaling timeouts and retries
* Fix: Filesharing fixes (sharing same file twice, etc.)
* Fix: Better handling of hark
* Fix: Use applyContraints instead of restarting producers
* Fix: Now handles reconnects properly if client loses connection
* Fix: Rotating devices don't show rotated videos
* Fix: Various fixes to client authentication
## 3.2.1 ## 3.2.1
* Fix: permananent top bar by default * Fix: permananent top bar by default

1
CONTRIBUTING.md 100644
View File

@ -0,0 +1 @@
Source code contributions should pass static code analysis as performed by `npm run lint` in `server` and `app` respectively.

View File

@ -62,14 +62,6 @@ OR
## Configure multiparty-meeting servers ## Configure multiparty-meeting servers
### App config
mm/configs/app/config.js
``` js
multipartyServer : 'meet.example.com',
```
### Server config ### Server config
mm/configs/server/config.js mm/configs/server/config.js

21
LICENSE.md 100644
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 GÉANT Association
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -23,11 +23,11 @@ If you want the ansible approach, you can find ansible role [here](https://githu
## Manual installation ## Manual installation
* Prerequisites: * Prerequisites:
Currently multiparty-meeting will only run on nodejs v10.* Currently multiparty-meeting will only run on nodejs v13.x
To install see here [here](https://github.com/nodesource/distributions/blob/master/README.md#debinstall). To install see here [here](https://github.com/nodesource/distributions/blob/master/README.md#debinstall).
```bash ```bash
$ sudo apt install npm build-essential redis $ sudo apt install git npm build-essential redis
``` ```
* Clone the project: * Clone the project:
@ -113,7 +113,7 @@ To integrate with an LMS (e.g. Moodle), have a look at [LTI](LTI/LTI.md).
## TURN configuration ## TURN configuration
* You need an additional [TURN](https://github.com/coturn/coturn)-server for clients located behind restrictive firewalls! Add your server and credentials to `app/public/config/config.js` * You need an additional [TURN](https://github.com/coturn/coturn)-server for clients located behind restrictive firewalls! Add your server and credentials to `server/config/config.js`
## Community-driven support ## Community-driven support
@ -134,7 +134,7 @@ This started as a fork of the [work](https://github.com/versatica/mediasoup-demo
## License ## License
MIT MIT License (see `LICENSE.md`)
Contributions to this work were made on behalf of the GÉANT project, a project that has received funding from the European Unions Horizon 2020 research and innovation programme under Grant Agreement No. 731122 (GN4-2). On behalf of GÉANT project, GÉANT Association is the sole owner of the copyright in all material which was developed by a member of the GÉANT project. Contributions to this work were made on behalf of the GÉANT project, a project that has received funding from the European Unions Horizon 2020 research and innovation programme under Grant Agreement No. 731122 (GN4-2). On behalf of GÉANT project, GÉANT Association is the sole owner of the copyright in all material which was developed by a member of the GÉANT project.

2
app/.env 100644
View File

@ -0,0 +1,2 @@
REACT_APP_VERSION=$npm_package_version
REACT_APP_NAME=$npm_package_name

View File

@ -159,6 +159,12 @@
"no-inner-declarations": 2, "no-inner-declarations": 2,
"no-invalid-regexp": 2, "no-invalid-regexp": 2,
"no-irregular-whitespace": 2, "no-irregular-whitespace": 2,
"no-trailing-spaces": [
"error",
{
"ignoreComments": true
}
],
"no-lonely-if": 2, "no-lonely-if": 2,
"no-mixed-operators": 2, "no-mixed-operators": 2,
"no-mixed-spaces-and-tabs": 2, "no-mixed-spaces-and-tabs": 2,

View File

@ -1,6 +1,6 @@
{ {
"name": "multiparty-meeting", "name": "multiparty-meeting",
"version": "3.2.1", "version": "3.3.0",
"private": true, "private": true,
"description": "multiparty meeting service", "description": "multiparty meeting service",
"author": "Håvar Aambø Fosstveit <h@fosstveit.net>", "author": "Håvar Aambø Fosstveit <h@fosstveit.net>",
@ -20,17 +20,19 @@
"hark": "^1.2.3", "hark": "^1.2.3",
"is-electron": "^2.2.0", "is-electron": "^2.2.0",
"marked": "^0.8.0", "marked": "^0.8.0",
"mediasoup-client": "^3.5.4", "mediasoup-client": "^3.6.5",
"notistack": "^0.9.5", "notistack": "^0.9.5",
"prop-types": "^15.7.2", "prop-types": "^15.7.2",
"random-string": "^0.2.0", "random-string": "^0.2.0",
"react": "^16.10.2", "react": "^16.10.2",
"react-cookie-consent": "^2.5.0", "react-cookie-consent": "^2.5.0",
"react-dom": "^16.10.2", "react-dom": "^16.10.2",
"react-flip-toolkit": "^7.0.9",
"react-intl": "^3.4.0", "react-intl": "^3.4.0",
"react-redux": "^7.1.1", "react-redux": "^7.1.1",
"react-router-dom": "^5.1.2", "react-router-dom": "^5.1.2",
"react-scripts": "3.0.1", "react-scripts": "3.4.1",
"react-wakelock-react16": "0.0.7",
"redux": "^4.0.4", "redux": "^4.0.4",
"redux-logger": "^3.0.6", "redux-logger": "^3.0.6",
"redux-persist": "^6.0.0", "redux-persist": "^6.0.0",
@ -48,7 +50,8 @@
"test": "react-scripts test", "test": "react-scripts test",
"eject": "react-scripts eject", "eject": "react-scripts eject",
"electron": "electron --no-sandbox .", "electron": "electron --no-sandbox .",
"dev": "nf start -p 3000" "dev": "nf start -p 3000",
"lint": "eslint -c .eslintrc.json --ext .js src"
}, },
"browserslist": [ "browserslist": [
">0.2%", ">0.2%",
@ -58,6 +61,7 @@
], ],
"devDependencies": { "devDependencies": {
"electron": "^7.1.1", "electron": "^7.1.1",
"eslint-plugin-react": "^7.19.0",
"foreman": "^3.0.1", "foreman": "^3.0.1",
"redux-mock-store": "^1.5.3" "redux-mock-store": "^1.5.3"
} }

View File

@ -4,25 +4,50 @@ var config =
loginEnabled : false, loginEnabled : false,
developmentPort : 3443, developmentPort : 3443,
productionPort : 443, productionPort : 443,
multipartyServer : 'letsmeet.no',
turnServers : [
{
urls : [
'turn:turn.example.com:443?transport=tcp'
],
username : 'example',
credential : 'example'
}
],
/** /**
* If defaultResolution is set, it will override user settings when joining: * Supported browsers version
* in bowser satisfy format.
* See more:
* https://www.npmjs.com/package/bowser#filtering-browsers
* Otherwise you got a unsupported browser page
*/
supportedBrowsers :
{
'windows' : {
'internet explorer' : '>12',
'microsoft edge' : '>18'
},
'safari' : '>12',
'firefox' : '>=60',
'chrome' : '>=74',
'chromium' : '>=74',
'opera' : '>=62',
'samsung internet for android' : '>=11.1.1.52'
},
/**
* Resolutions:
*
* low ~ 320x240 * low ~ 320x240
* medium ~ 640x480 * medium ~ 640x480
* high ~ 1280x720 * high ~ 1280x720
* veryhigh ~ 1920x1080 * veryhigh ~ 1920x1080
* ultra ~ 3840x2560 * ultra ~ 3840x2560
*
**/ **/
/**
* Frame rates:
*
* 1, 5, 10, 15, 20, 25, 30
*
**/
defaultResolution : 'medium', defaultResolution : 'medium',
defaultFrameRate : 15,
defaultScreenResolution : 'veryhigh',
defaultScreenSharingFrameRate : 5,
// Enable or disable simulcast for webcam video // Enable or disable simulcast for webcam video
simulcast : true, simulcast : true,
// Enable or disable simulcast for screen sharing video // Enable or disable simulcast for screen sharing video
@ -34,18 +59,89 @@ var config =
{ scaleResolutionDownBy: 2 }, { scaleResolutionDownBy: 2 },
{ scaleResolutionDownBy: 1 } { scaleResolutionDownBy: 1 }
], ],
/**
* Alternative simulcast setting:
* [
* { maxBitRate: 50000 },
* { maxBitRate: 1000000 },
* { maxBitRate: 4800000 }
*],
**/
/**
* White listing browsers that support audio output device selection.
* It is not yet fully implemented in Firefox.
* See: https://bugzilla.mozilla.org/show_bug.cgi?id=1498512
*/
audioOutputSupportedBrowsers :
[
'chrome',
'opera'
],
// Socket.io request timeout // Socket.io request timeout
requestTimeout : 10000, requestTimeout : 20000,
requestRetries : 3,
transportOptions : transportOptions :
{ {
tcp : true tcp : true
}, },
defaultAudio :
{
sampleRate : 48000,
channelCount : 1,
volume : 1.0,
autoGainControl : true,
echoCancellation : true,
noiseSuppression : true,
voiceActivityMute : false,
sampleSize : 16
},
/**
* Set max number participants in one room that join
* unmuted. Next participant will join automatically muted
* Default value is 4
*
* Set it to 0 to auto mute all,
* Set it to negative (-1) to never automatically auto mute
* but use it with caution
* full mesh audio strongly decrease room capacity!
*/
autoMuteThreshold : 4,
background : 'images/background.jpg',
defaultLayout : 'democratic', // democratic, filmstrip
// If true, will show media control buttons in separate
// control bar, not in the ME container.
buttonControlBar : false,
// If false, will push videos away to make room for side
// drawer. If true, will overlay side drawer over videos
drawerOverlayed : true,
// Position of notifications
notificationPosition : 'right',
// Timeout for autohiding topbar and button control bar
hideTimeout : 3000,
// max number of participant that will be visible in
// as speaker
lastN : 4, lastN : 4,
mobileLastN : 1, mobileLastN : 1,
background : 'images/background.jpg', // Highest number of lastN the user can select manually in
// userinteface
maxLastN : 5,
// If truthy, users can NOT change number of speakers visible
lockLastN : false,
// Add file and uncomment for adding logo to appbar // Add file and uncomment for adding logo to appbar
// logo : 'images/logo.svg', // logo : 'images/logo.svg',
title : 'Multiparty meeting', title : 'Multiparty meeting',
// Service & Support URL
// if not set then not displayed on the about modals
supportUrl : 'https://support.example.com',
// Privacy and dataprotection URL or path
// by default privacy/privacy.html
// that is a placeholder for your policies
//
// but an external url could be also used here
privacyUrl : 'static/privacy.html',
theme : theme :
{ {
palette : palette :

View File

@ -16,6 +16,44 @@
<title>Multiparty Meeting</title> <title>Multiparty Meeting</title>
<script src='%PUBLIC_URL%/config/config.js' type='text/javascript'></script> <script src='%PUBLIC_URL%/config/config.js' type='text/javascript'></script>
<!-- Show an error page to IE browsers -->
<script type='text/javascript'>
var fallback = '<style type="text/css">body{margin:40px auto;max-width:650px;line-height:1.6;font-size:18px;color:#444;padding:0 10px}h1,h2,h3{line-height:1.2}</style><header><h1>Your browser is not supported</h1><aside>You need to change to a different browser.</aside></header><h3>Supported browsers</h3><ul><li>Google Chrome/Chromium 55 +</li><li>Microsoft Edge 18 +</li><li>Mozilla Firefox 60 +</li><li>Apple Safari 12 +</li><li>Opera 62 +</li><li>Samsung Internet 11.1.1.52 +</li></ul>';
var fallbackCall = function() {
document.body.innerHTML = fallback;
};
if(navigator.userAgent.indexOf('MSIE') !== -1)
{
document.attachEvent('onreadystatechange', function() {
if (document.readyState === 'complete')
{
document.detachEvent('onreadystatechange', arguments.callee);
fallbackCall();
}
});
}
if (navigator.appVersion.indexOf('Trident/') > -1)
{
if (
document.readyState === 'complete' ||
(document.readyState !== 'loading' && !document.documentElement.doScroll)
)
{
document.removeEventListener('DOMContentLoaded', fallbackCall);
fallbackCall();
}
else
{
document.addEventListener('DOMContentLoaded', fallbackCall);
}
}
</script>
</head> </head>
<body> <body>
<noscript>You need to enable JavaScript to run this app.</noscript> <noscript>You need to enable JavaScript to run this app.</noscript>

View File

@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='UTF-8'>
<meta name='viewport' content='width=device-width, initial-scale=1.0'>
<title>Placeholder for Privacy Statement / Policy, AUP</title>
<style type='text/css'>body{margin:40px auto;max-width:650px;line-height:1.6;font-size:18px;color:#444;padding:0 10px}h1,h2,h3{line-height:1.2}</style>
</head>
<body>
<header><h1>Privacy Statement</h1></header>
<h2>Privacy Policy</h2>
<ul>
<li>User consent</li>
<li>Data deletion</li>
</ul>
<h2>Acceptable use policy (AUP)</h2>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@ -213,6 +213,8 @@ export default class ScreenShare
{ {
if (isElectron()) if (isElectron())
return new ElectronScreenShare(); return new ElectronScreenShare();
else if (device.platform !== 'desktop')
return new DefaultScreenShare();
else else
{ {
switch (device.flag) switch (device.flag)
@ -224,11 +226,17 @@ export default class ScreenShare
else else
return new DisplayMediaScreenShare(); return new DisplayMediaScreenShare();
} }
case 'chrome': case 'safari':
{ {
if (device.version >= 13.0)
return new DisplayMediaScreenShare(); return new DisplayMediaScreenShare();
else
return new DefaultScreenShare();
} }
case 'msedge': case 'chrome':
case 'chromium':
case 'opera':
case 'edge':
{ {
return new DisplayMediaScreenShare(); return new DisplayMediaScreenShare();
} }

View File

@ -12,6 +12,7 @@ export default class Spotlights extends EventEmitter
this._signalingSocket = signalingSocket; this._signalingSocket = signalingSocket;
this._maxSpotlights = maxSpotlights; this._maxSpotlights = maxSpotlights;
this._peerList = []; this._peerList = [];
this._unmutablePeerList = [];
this._selectedSpotlights = []; this._selectedSpotlights = [];
this._currentSpotlights = []; this._currentSpotlights = [];
this._started = false; this._started = false;
@ -45,6 +46,74 @@ export default class Spotlights extends EventEmitter
} }
} }
getNextAsSelected(peerId)
{
let newSelectedPeer = null;
if (peerId == null && this._unmutablePeerList.length > 0)
{
peerId = this._unmutablePeerList[0];
}
if (peerId != null && this._currentSpotlights.length < this._unmutablePeerList.length)
{
const oldIndex = this._unmutablePeerList.indexOf(peerId);
let index = oldIndex;
index++;
for (let i = 0; i < this._unmutablePeerList.length; i++)
{
if (index >= this._unmutablePeerList.length)
{
index = 0;
}
newSelectedPeer = this._unmutablePeerList[index];
if (!this._currentSpotlights.includes(newSelectedPeer))
{
break;
}
index++;
}
}
return newSelectedPeer;
}
getPrevAsSelected(peerId)
{
let newSelectedPeer = null;
if (peerId == null && this._unmutablePeerList.length > 0)
{
peerId = this._unmutablePeerList[0];
}
if (peerId != null && this._currentSpotlights.length < this._unmutablePeerList.length)
{
const oldIndex = this._unmutablePeerList.indexOf(peerId);
let index = oldIndex;
index--;
for (let i = 0; i < this._unmutablePeerList.length; i++)
{
if (index < 0)
{
index = this._unmutablePeerList.length - 1;
}
newSelectedPeer = this._unmutablePeerList[index];
if (!this._currentSpotlights.includes(newSelectedPeer))
{
break;
}
index--;
}
}
return newSelectedPeer;
}
setPeerSpotlight(peerId) setPeerSpotlight(peerId)
{ {
logger.debug('setPeerSpotlight() [peerId:"%s"]', peerId); logger.debug('setPeerSpotlight() [peerId:"%s"]', peerId);
@ -95,6 +164,15 @@ export default class Spotlights extends EventEmitter
}); });
} }
clearSpotlights()
{
this._started = false;
this._peerList = [];
this._selectedSpotlights = [];
this._currentSpotlights = [];
}
_newPeer(id) _newPeer(id)
{ {
logger.debug( logger.debug(
@ -105,6 +183,7 @@ export default class Spotlights extends EventEmitter
logger.debug('_handlePeer() | adding peer [peerId: "%s"]', id); logger.debug('_handlePeer() | adding peer [peerId: "%s"]', id);
this._peerList.push(id); this._peerList.push(id);
this._unmutablePeerList.push(id);
if (this._started) if (this._started)
this._spotlightsUpdated(); this._spotlightsUpdated();
@ -116,19 +195,10 @@ export default class Spotlights extends EventEmitter
logger.debug( logger.debug(
'room "peerClosed" event [peerId:%o]', id); 'room "peerClosed" event [peerId:%o]', id);
let index = this._peerList.indexOf(id); this._peerList = this._peerList.filter((peer) => peer !== id);
this._unmutablePeerList = this._unmutablePeerList.filter((peer) => peer !== id);
if (index !== -1) // We have this peer in the list, remove this._selectedSpotlights = this._selectedSpotlights.filter((peer) => peer !== id);
{
this._peerList.splice(index, 1);
}
index = this._selectedSpotlights.indexOf(id);
if (index !== -1) // We have this peer in the list, remove
{
this._selectedSpotlights.splice(index, 1);
}
if (this._started) if (this._started)
this._spotlightsUpdated(); this._spotlightsUpdated();

View File

@ -31,6 +31,8 @@ beforeEach(() =>
me : { me : {
audioDevices : null, audioDevices : null,
audioInProgress : false, audioInProgress : false,
audioOutputDevices : null,
audioOutputInProgress : false,
canSendMic : false, canSendMic : false,
canSendWebcam : false, canSendWebcam : false,
canShareFiles : false, canShareFiles : false,
@ -40,8 +42,8 @@ beforeEach(() =>
loggedIn : false, loggedIn : false,
loginEnabled : true, loginEnabled : true,
picture : null, picture : null,
raiseHand : false, raisedHand : false,
raiseHandInProgress : false, raisedHandInProgress : false,
screenShareInProgress : false, screenShareInProgress : false,
webcamDevices : null, webcamDevices : null,
webcamInProgress : false webcamInProgress : false
@ -76,6 +78,7 @@ beforeEach(() =>
displayName : 'Jest Tester', displayName : 'Jest Tester',
resolution : 'ultra', resolution : 'ultra',
selectedAudioDevice : 'default', selectedAudioDevice : 'default',
selectedAudioOutputDevice : 'default',
selectedWebcam : 'soifjsiajosjfoi' selectedWebcam : 'soifjsiajosjfoi'
}, },
toolarea : { toolarea : {

View File

@ -15,3 +15,8 @@ export const addChatHistory = (chatHistory) =>
type : 'ADD_CHAT_HISTORY', type : 'ADD_CHAT_HISTORY',
payload : { chatHistory } payload : { chatHistory }
}); });
export const clearChat = () =>
({
type : 'CLEAR_CHAT'
});

View File

@ -10,6 +10,11 @@ export const removeConsumer = (consumerId, peerId) =>
payload : { consumerId, peerId } payload : { consumerId, peerId }
}); });
export const clearConsumers = () =>
({
type : 'CLEAR_CONSUMERS'
});
export const setConsumerPaused = (consumerId, originator) => export const setConsumerPaused = (consumerId, originator) =>
({ ({
type : 'SET_CONSUMER_PAUSED', type : 'SET_CONSUMER_PAUSED',
@ -35,12 +40,10 @@ export const setConsumerPreferredLayers = (consumerId, spatialLayer, temporalLay
}); });
export const setConsumerPriority = (consumerId, priority) => export const setConsumerPriority = (consumerId, priority) =>
{ ({
return {
type : 'SET_CONSUMER_PRIORITY', type : 'SET_CONSUMER_PRIORITY',
payload : { consumerId, priority } payload : { consumerId, priority }
}; });
};
export const setConsumerTrack = (consumerId, track) => export const setConsumerTrack = (consumerId, track) =>
({ ({

View File

@ -33,3 +33,8 @@ export const setFileDone = (magnetUri, sharedFiles) =>
type : 'SET_FILE_DONE', type : 'SET_FILE_DONE',
payload : { magnetUri, sharedFiles } payload : { magnetUri, sharedFiles }
}); });
export const clearFiles = () =>
({
type : 'CLEAR_FILES'
});

View File

@ -4,12 +4,30 @@ export const setMe = ({ peerId, loginEnabled }) =>
payload : { peerId, loginEnabled } payload : { peerId, loginEnabled }
}); });
export const setBrowser = (browser) =>
({
type : 'SET_BROWSER',
payload : { browser }
});
export const loggedIn = (flag) => export const loggedIn = (flag) =>
({ ({
type : 'LOGGED_IN', type : 'LOGGED_IN',
payload : { flag } payload : { flag }
}); });
export const addRole = (role) =>
({
type : 'ADD_ROLE',
payload : { role }
});
export const removeRole = (role) =>
({
type : 'REMOVE_ROLE',
payload : { role }
});
export const setPicture = (picture) => export const setPicture = (picture) =>
({ ({
type : 'SET_PICTURE', type : 'SET_PICTURE',
@ -33,15 +51,21 @@ export const setAudioDevices = (devices) =>
payload : { devices } payload : { devices }
}); });
export const setAudioOutputDevices = (devices) =>
({
type : 'SET_AUDIO_OUTPUT_DEVICES',
payload : { devices }
});
export const setWebcamDevices = (devices) => export const setWebcamDevices = (devices) =>
({ ({
type : 'SET_WEBCAM_DEVICES', type : 'SET_WEBCAM_DEVICES',
payload : { devices } payload : { devices }
}); });
export const setMyRaiseHandState = (flag) => export const setRaisedHand = (flag) =>
({ ({
type : 'SET_MY_RAISE_HAND_STATE', type : 'SET_RAISED_HAND',
payload : { flag } payload : { flag }
}); });
@ -51,6 +75,12 @@ export const setAudioInProgress = (flag) =>
payload : { flag } payload : { flag }
}); });
export const setAudioOutputInProgress = (flag) =>
({
type : 'SET_AUDIO_OUTPUT_IN_PROGRESS',
payload : { flag }
});
export const setWebcamInProgress = (flag) => export const setWebcamInProgress = (flag) =>
({ ({
type : 'SET_WEBCAM_IN_PROGRESS', type : 'SET_WEBCAM_IN_PROGRESS',
@ -63,9 +93,9 @@ export const setScreenShareInProgress = (flag) =>
payload : { flag } payload : { flag }
}); });
export const setMyRaiseHandStateInProgress = (flag) => export const setRaisedHandInProgress = (flag) =>
({ ({
type : 'SET_MY_RAISE_HAND_STATE_IN_PROGRESS', type : 'SET_RAISED_HAND_IN_PROGRESS',
payload : { flag } payload : { flag }
}); });
@ -74,3 +104,15 @@ export const setDisplayNameInProgress = (flag) =>
type : 'SET_DISPLAY_NAME_IN_PROGRESS', type : 'SET_DISPLAY_NAME_IN_PROGRESS',
payload : { flag } payload : { flag }
}); });
export const setIsSpeaking = (flag) =>
({
type : 'SET_IS_SPEAKING',
payload : { flag }
});
export const setAutoMuted = (flag) =>
({
type : 'SET_AUTO_MUTED',
payload : { flag }
});

View File

@ -10,6 +10,11 @@ export const removePeer = (peerId) =>
payload : { peerId } payload : { peerId }
}); });
export const clearPeers = () =>
({
type : 'CLEAR_PEERS'
});
export const setPeerDisplayName = (displayName, peerId) => export const setPeerDisplayName = (displayName, peerId) =>
({ ({
type : 'SET_PEER_DISPLAY_NAME', type : 'SET_PEER_DISPLAY_NAME',
@ -34,10 +39,16 @@ export const setPeerScreenInProgress = (peerId, flag) =>
payload : { peerId, flag } payload : { peerId, flag }
}); });
export const setPeerRaiseHandState = (peerId, raiseHandState) => export const setPeerRaisedHand = (peerId, raisedHand, raisedHandTimestamp) =>
({ ({
type : 'SET_PEER_RAISE_HAND_STATE', type : 'SET_PEER_RAISED_HAND',
payload : { peerId, raiseHandState } payload : { peerId, raisedHand, raisedHandTimestamp }
});
export const setPeerRaisedHandInProgress = (peerId, flag) =>
({
type : 'SET_PEER_RAISED_HAND_IN_PROGRESS',
payload : { peerId, flag }
}); });
export const setPeerPicture = (peerId, picture) => export const setPeerPicture = (peerId, picture) =>
@ -45,3 +56,39 @@ export const setPeerPicture = (peerId, picture) =>
type : 'SET_PEER_PICTURE', type : 'SET_PEER_PICTURE',
payload : { peerId, picture } payload : { peerId, picture }
}); });
export const addPeerRole = (peerId, role) =>
({
type : 'ADD_PEER_ROLE',
payload : { peerId, role }
});
export const removePeerRole = (peerId, role) =>
({
type : 'REMOVE_PEER_ROLE',
payload : { peerId, role }
});
export const setPeerKickInProgress = (peerId, flag) =>
({
type : 'SET_PEER_KICK_IN_PROGRESS',
payload : { peerId, flag }
});
export const setMutePeerInProgress = (peerId, flag) =>
({
type : 'STOP_PEER_AUDIO_IN_PROGRESS',
payload : { peerId, flag }
});
export const setStopPeerVideoInProgress = (peerId, flag) =>
({
type : 'STOP_PEER_VIDEO_IN_PROGRESS',
payload : { peerId, flag }
});
export const setStopPeerScreenSharingInProgress = (peerId, flag) =>
({
type : 'STOP_PEER_SCREEN_SHARING_IN_PROGRESS',
payload : { peerId, flag }
});

View File

@ -40,6 +40,12 @@ export const setSignInRequired = (signInRequired) =>
payload : { signInRequired } payload : { signInRequired }
}); });
export const setOverRoomLimit = (overRoomLimit) =>
({
type : 'SET_OVER_ROOM_LIMIT',
payload : { overRoomLimit }
});
export const setAccessCode = (accessCode) => export const setAccessCode = (accessCode) =>
({ ({
type : 'SET_ACCESS_CODE', type : 'SET_ACCESS_CODE',
@ -52,13 +58,37 @@ export const setJoinByAccessCode = (joinByAccessCode) =>
payload : { joinByAccessCode } payload : { joinByAccessCode }
}); });
export const setSettingsOpen = ({ settingsOpen }) => export const setSettingsOpen = (settingsOpen) =>
({ ({
type : 'SET_SETTINGS_OPEN', type : 'SET_SETTINGS_OPEN',
payload : { settingsOpen } payload : { settingsOpen }
}); });
export const setLockDialogOpen = ({ lockDialogOpen }) => export const setExtraVideoOpen = (extraVideoOpen) =>
({
type : 'SET_EXTRA_VIDEO_OPEN',
payload : { extraVideoOpen }
});
export const setHelpOpen = (helpOpen) =>
({
type : 'SET_HELP_OPEN',
payload : { helpOpen }
});
export const setAboutOpen = (aboutOpen) =>
({
type : 'SET_ABOUT_OPEN',
payload : { aboutOpen }
});
export const setSettingsTab = (tab) =>
({
type : 'SET_SETTINGS_TAB',
payload : { tab }
});
export const setLockDialogOpen = (lockDialogOpen) =>
({ ({
type : 'SET_LOCK_DIALOG_OPEN', type : 'SET_LOCK_DIALOG_OPEN',
payload : { lockDialogOpen } payload : { lockDialogOpen }
@ -100,6 +130,11 @@ export const setSpotlights = (spotlights) =>
payload : { spotlights } payload : { spotlights }
}); });
export const clearSpotlights = () =>
({
type : 'CLEAR_SPOTLIGHTS'
});
export const toggleJoined = () => export const toggleJoined = () =>
({ ({
type : 'TOGGLE_JOINED' type : 'TOGGLE_JOINED'
@ -110,3 +145,57 @@ export const toggleConsumerFullscreen = (consumerId) =>
type : 'TOGGLE_FULLSCREEN_CONSUMER', type : 'TOGGLE_FULLSCREEN_CONSUMER',
payload : { consumerId } payload : { consumerId }
}); });
export const setLobbyPeersPromotionInProgress = (flag) =>
({
type : 'SET_LOBBY_PEERS_PROMOTION_IN_PROGRESS',
payload : { flag }
});
export const setMuteAllInProgress = (flag) =>
({
type : 'MUTE_ALL_IN_PROGRESS',
payload : { flag }
});
export const setStopAllVideoInProgress = (flag) =>
({
type : 'STOP_ALL_VIDEO_IN_PROGRESS',
payload : { flag }
});
export const setStopAllScreenSharingInProgress = (flag) =>
({
type : 'STOP_ALL_SCREEN_SHARING_IN_PROGRESS',
payload : { flag }
});
export const setCloseMeetingInProgress = (flag) =>
({
type : 'CLOSE_MEETING_IN_PROGRESS',
payload : { flag }
});
export const setClearChatInProgress = (flag) =>
({
type : 'CLEAR_CHAT_IN_PROGRESS',
payload : { flag }
});
export const setClearFileSharingInProgress = (flag) =>
({
type : 'CLEAR_FILE_SHARING_IN_PROGRESS',
payload : { flag }
});
export const setRoomPermissions = (roomPermissions) =>
({
type : 'SET_ROOM_PERMISSIONS',
payload : { roomPermissions }
});
export const setAllowWhenRoleMissing = (allowWhenRoleMissing) =>
({
type : 'SET_ALLOW_WHEN_ROLE_MISSING',
payload : { allowWhenRoleMissing }
});

View File

@ -4,6 +4,12 @@ export const setSelectedAudioDevice = (deviceId) =>
payload : { deviceId } payload : { deviceId }
}); });
export const setSelectedAudioOutputDevice = (deviceId) =>
({
type : 'CHANGE_AUDIO_OUTPUT_DEVICE',
payload : { deviceId }
});
export const setSelectedWebcamDevice = (deviceId) => export const setSelectedWebcamDevice = (deviceId) =>
({ ({
type : 'CHANGE_WEBCAM', type : 'CHANGE_WEBCAM',
@ -16,6 +22,24 @@ export const setVideoResolution = (resolution) =>
payload : { resolution } payload : { resolution }
}); });
export const setVideoFrameRate = (frameRate) =>
({
type : 'SET_VIDEO_FRAME_RATE',
payload : { frameRate }
});
export const setScreenSharingResolution = (screenSharingResolution) =>
({
type : 'SET_SCREEN_SHARING_RESOLUTION',
payload : { screenSharingResolution }
});
export const setScreenSharingFrameRate = (screenSharingFrameRate) =>
({
type : 'SET_SCREEN_SHARING_FRAME_RATE',
payload : { screenSharingFrameRate }
});
export const setDisplayName = (displayName) => export const setDisplayName = (displayName) =>
({ ({
type : 'SET_DISPLAY_NAME', type : 'SET_DISPLAY_NAME',
@ -32,6 +56,67 @@ export const togglePermanentTopBar = () =>
type : 'TOGGLE_PERMANENT_TOPBAR' type : 'TOGGLE_PERMANENT_TOPBAR'
}); });
export const toggleButtonControlBar = () =>
({
type : 'TOGGLE_BUTTON_CONTROL_BAR'
});
export const toggleDrawerOverlayed = () =>
({
type : 'TOGGLE_DRAWER_OVERLAYED'
});
export const toggleShowNotifications = () =>
({
type : 'TOGGLE_SHOW_NOTIFICATIONS'
});
export const setEchoCancellation = (echoCancellation) =>
({
type : 'SET_ECHO_CANCELLATION',
payload : { echoCancellation }
});
export const setAutoGainControl = (autoGainControl) =>
({
type : 'SET_AUTO_GAIN_CONTROL',
payload : { autoGainControl }
});
export const setNoiseSuppression = (noiseSuppression) =>
({
type : 'SET_NOISE_SUPPRESSION',
payload : { noiseSuppression }
});
export const setVoiceActivatedUnmute = (voiceActivatedUnmute) =>
({
type : 'SET_VOICE_ACTIVATED_UNMUTE',
payload : { voiceActivatedUnmute }
});
export const setNoiseThreshold = (noiseThreshold) =>
({
type : 'SET_NOISE_THRESHOLD',
payload : { noiseThreshold }
});
export const setDefaultAudio = (audio) =>
({
type : 'SET_DEFAULT_AUDIO',
payload : { audio }
});
export const toggleHiddenControls = () =>
({
type : 'TOGGLE_HIDDEN_CONTROLS'
});
export const toggleNotificationSounds = () =>
({
type : 'TOGGLE_NOTIFICATION_SOUNDS'
});
export const setLastN = (lastN) => export const setLastN = (lastN) =>
({ ({
type : 'SET_LAST_N', type : 'SET_LAST_N',

View File

@ -0,0 +1,5 @@
export const addTransportStats = (transport, type) =>
({
type : 'ADD_TRANSPORT_STATS',
payload : { transport, type }
});

View File

@ -5,74 +5,20 @@ import PropTypes from 'prop-types';
import classnames from 'classnames'; import classnames from 'classnames';
import { withRoomContext } from '../../../RoomContext'; import { withRoomContext } from '../../../RoomContext';
import { useIntl } from 'react-intl'; import { useIntl } from 'react-intl';
import { permissions } from '../../../permissions';
import { makePermissionSelector } from '../../Selectors';
import ListItem from '@material-ui/core/ListItem'; import ListItem from '@material-ui/core/ListItem';
import ListItemText from '@material-ui/core/ListItemText'; import ListItemText from '@material-ui/core/ListItemText';
import ListItemIcon from '@material-ui/core/ListItemIcon'; import IconButton from '@material-ui/core/IconButton';
import ListItemAvatar from '@material-ui/core/ListItemAvatar'; import ListItemAvatar from '@material-ui/core/ListItemAvatar';
import Avatar from '@material-ui/core/Avatar'; import Avatar from '@material-ui/core/Avatar';
import EmptyAvatar from '../../../images/avatar-empty.jpeg'; import EmptyAvatar from '../../../images/avatar-empty.jpeg';
import PromoteIcon from '@material-ui/icons/OpenInBrowser'; import PromoteIcon from '@material-ui/icons/OpenInBrowser';
import Tooltip from '@material-ui/core/Tooltip'; import Tooltip from '@material-ui/core/Tooltip';
const styles = (theme) => const styles = () =>
({ ({
root : root :
{
padding : theme.spacing(1),
width : '100%',
overflow : 'hidden',
cursor : 'auto',
display : 'flex'
},
avatar :
{
borderRadius : '50%',
height : '2rem'
},
peerInfo :
{
fontSize : '1rem',
border : 'none',
display : 'flex',
paddingLeft : theme.spacing(1),
flexGrow : 1,
alignItems : 'center'
},
controls :
{
float : 'right',
display : 'flex',
flexDirection : 'row',
justifyContent : 'flex-start',
alignItems : 'center'
},
button :
{
flex : '0 0 auto',
margin : '0.3rem',
borderRadius : 2,
backgroundColor : 'rgba(0, 0, 0, 0.5)',
cursor : 'pointer',
transitionProperty : 'opacity, background-color',
transitionDuration : '0.15s',
width : 'var(--media-control-button-size)',
height : 'var(--media-control-button-size)',
opacity : 0.85,
'&:hover' :
{
opacity : 1
},
'&.disabled' :
{
pointerEvents : 'none',
backgroundColor : 'var(--media-control-botton-disabled)'
},
'&.promote' :
{
backgroundColor : 'var(--media-control-botton-on)'
}
},
ListItem :
{ {
alignItems : 'center' alignItems : 'center'
} }
@ -83,6 +29,8 @@ const ListLobbyPeer = (props) =>
const { const {
roomClient, roomClient,
peer, peer,
promotionInProgress,
canPromote,
classes classes
} = props; } = props;
@ -92,7 +40,7 @@ const ListLobbyPeer = (props) =>
return ( return (
<ListItem <ListItem
className={classnames(classes.ListItem)} className={classnames(classes.root)}
key={peer.peerId} key={peer.peerId}
button button
alignItems='flex-start' alignItems='flex-start'
@ -109,10 +57,13 @@ const ListLobbyPeer = (props) =>
defaultMessage : 'Click to let them in' defaultMessage : 'Click to let them in'
})} })}
> >
<ListItemIcon <IconButton
className={classnames(classes.button, 'promote', { disabled={
disabled : peer.promotionInProgress !canPromote ||
})} peer.promotionInProgress ||
promotionInProgress
}
color='primary'
onClick={(e) => onClick={(e) =>
{ {
e.stopPropagation(); e.stopPropagation();
@ -120,7 +71,7 @@ const ListLobbyPeer = (props) =>
}} }}
> >
<PromoteIcon /> <PromoteIcon />
</ListItemIcon> </IconButton>
</Tooltip> </Tooltip>
</ListItem> </ListItem>
); );
@ -131,24 +82,38 @@ ListLobbyPeer.propTypes =
roomClient : PropTypes.any.isRequired, roomClient : PropTypes.any.isRequired,
advancedMode : PropTypes.bool, advancedMode : PropTypes.bool,
peer : PropTypes.object.isRequired, peer : PropTypes.object.isRequired,
promotionInProgress : PropTypes.bool.isRequired,
canPromote : PropTypes.bool.isRequired,
classes : PropTypes.object.isRequired classes : PropTypes.object.isRequired
}; };
const mapStateToProps = (state, { id }) => const makeMapStateToProps = (initialState, { id }) =>
{
const hasPermission = makePermissionSelector(permissions.PROMOTE_PEER);
const mapStateToProps = (state) =>
{ {
return { return {
peer : state.lobbyPeers[id] peer : state.lobbyPeers[id],
promotionInProgress : state.room.lobbyPeersPromotionInProgress,
canPromote : hasPermission(state)
}; };
}; };
return mapStateToProps;
};
export default withRoomContext(connect( export default withRoomContext(connect(
mapStateToProps, makeMapStateToProps,
null, null,
null, null,
{ {
areStatesEqual : (next, prev) => areStatesEqual : (next, prev) =>
{ {
return ( return (
prev.room === next.room &&
prev.peers === next.peers && // For checking permissions
prev.me.roles === next.me.roles &&
prev.lobbyPeers === next.lobbyPeers prev.lobbyPeers === next.lobbyPeers
); );
} }

View File

@ -1,8 +1,10 @@
import React from 'react'; import React from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { import {
lobbyPeersKeySelector lobbyPeersKeySelector,
makePermissionSelector
} from '../../Selectors'; } from '../../Selectors';
import { permissions } from '../../../permissions';
import * as appPropTypes from '../../appPropTypes'; import * as appPropTypes from '../../appPropTypes';
import { withStyles } from '@material-ui/core/styles'; import { withStyles } from '@material-ui/core/styles';
import { withRoomContext } from '../../../RoomContext'; import { withRoomContext } from '../../../RoomContext';
@ -15,14 +17,6 @@ import DialogActions from '@material-ui/core/DialogActions';
import DialogContent from '@material-ui/core/DialogContent'; import DialogContent from '@material-ui/core/DialogContent';
import DialogContentText from '@material-ui/core/DialogContentText'; import DialogContentText from '@material-ui/core/DialogContentText';
import Button from '@material-ui/core/Button'; import Button from '@material-ui/core/Button';
// import FormLabel from '@material-ui/core/FormLabel';
// import FormControl from '@material-ui/core/FormControl';
// import FormGroup from '@material-ui/core/FormGroup';
// import FormControlLabel from '@material-ui/core/FormControlLabel';
// import Checkbox from '@material-ui/core/Checkbox';
// import InputLabel from '@material-ui/core/InputLabel';
// import OutlinedInput from '@material-ui/core/OutlinedInput';
// import Switch from '@material-ui/core/Switch';
import List from '@material-ui/core/List'; import List from '@material-ui/core/List';
import ListSubheader from '@material-ui/core/ListSubheader'; import ListSubheader from '@material-ui/core/ListSubheader';
import ListLobbyPeer from './ListLobbyPeer'; import ListLobbyPeer from './ListLobbyPeer';
@ -59,11 +53,11 @@ const styles = (theme) =>
}); });
const LockDialog = ({ const LockDialog = ({
// roomClient, roomClient,
room, room,
handleCloseLockDialog, handleCloseLockDialog,
// handleAccessCode,
lobbyPeers, lobbyPeers,
canPromote,
classes classes
}) => }) =>
{ {
@ -71,7 +65,7 @@ const LockDialog = ({
<Dialog <Dialog
className={classes.root} className={classes.root}
open={room.lockDialogOpen} open={room.lockDialogOpen}
onClose={() => handleCloseLockDialog({ lockDialogOpen: false })} onClose={() => handleCloseLockDialog(false)}
classes={{ classes={{
paper : classes.dialogPaper paper : classes.dialogPaper
}} }}
@ -82,54 +76,6 @@ const LockDialog = ({
defaultMessage='Lobby administration' defaultMessage='Lobby administration'
/> />
</DialogTitle> </DialogTitle>
{/*
<FormControl component='fieldset' className={classes.formControl}>
<FormLabel component='legend'>Room lock</FormLabel>
<FormGroup>
<FormControlLabel
control={
<Switch checked={room.locked} onChange={() =>
{
if (room.locked)
{
roomClient.unlockRoom();
}
else
{
roomClient.lockRoom();
}
}}
/>}
label='Lock'
/>
TODO: access code
<FormControlLabel disabled={ room.locked ? false : true }
control={
<Checkbox checked={room.joinByAccessCode}
onChange={
(event) => roomClient.setJoinByAccessCode(event.target.checked)
}
/>}
label='Join by Access code'
/>
<InputLabel htmlFor='access-code-input' />
<OutlinedInput
disabled={ room.locked ? false : true }
id='acces-code-input'
label='Access code'
labelWidth={0}
variant='outlined'
value={room.accessCode}
onChange={(event) => handleAccessCode(event.target.value)}
>
</OutlinedInput>
<Button onClick={() => roomClient.setAccessCode(room.accessCode)} color='primary'>
save
</Button>
</FormGroup>
</FormControl>
*/}
{ lobbyPeers.length > 0 ? { lobbyPeers.length > 0 ?
<List <List
dense dense
@ -160,7 +106,21 @@ const LockDialog = ({
</DialogContent> </DialogContent>
} }
<DialogActions> <DialogActions>
<Button onClick={() => handleCloseLockDialog({ lockDialogOpen: false })} color='primary'> <Button
disabled={
lobbyPeers.length === 0 ||
!canPromote ||
room.lobbyPeersPromotionInProgress
}
onClick={() => roomClient.promoteAllLobbyPeers()}
color='primary'
>
<FormattedMessage
id='label.promoteAllPeers'
defaultMessage='Promote all'
/>
</Button>
<Button onClick={() => handleCloseLockDialog(false)} color='primary'>
<FormattedMessage <FormattedMessage
id='label.close' id='label.close'
defaultMessage='Close' defaultMessage='Close'
@ -173,41 +133,47 @@ const LockDialog = ({
LockDialog.propTypes = LockDialog.propTypes =
{ {
// roomClient : PropTypes.any.isRequired, roomClient : PropTypes.object.isRequired,
room : appPropTypes.Room.isRequired, room : appPropTypes.Room.isRequired,
handleCloseLockDialog : PropTypes.func.isRequired, handleCloseLockDialog : PropTypes.func.isRequired,
handleAccessCode : PropTypes.func.isRequired, handleAccessCode : PropTypes.func.isRequired,
lobbyPeers : PropTypes.array, lobbyPeers : PropTypes.array,
canPromote : PropTypes.bool,
classes : PropTypes.object.isRequired classes : PropTypes.object.isRequired
}; };
const makeMapStateToProps = () =>
{
const hasPermission = makePermissionSelector(permissions.PROMOTE_PEER);
const mapStateToProps = (state) => const mapStateToProps = (state) =>
{ {
return { return {
room : state.room, room : state.room,
lobbyPeers : lobbyPeersKeySelector(state) lobbyPeers : lobbyPeersKeySelector(state),
canPromote : hasPermission(state)
}; };
}; };
return mapStateToProps;
};
const mapDispatchToProps = { const mapDispatchToProps = {
handleCloseLockDialog : roomActions.setLockDialogOpen, handleCloseLockDialog : roomActions.setLockDialogOpen,
handleAccessCode : roomActions.setAccessCode handleAccessCode : roomActions.setAccessCode
}; };
export default withRoomContext(connect( export default withRoomContext(connect(
mapStateToProps, makeMapStateToProps,
mapDispatchToProps, mapDispatchToProps,
null, null,
{ {
areStatesEqual : (next, prev) => areStatesEqual : (next, prev) =>
{ {
return ( return (
prev.room.locked === next.room.locked && prev.room === next.room &&
prev.room.joinByAccessCode === next.room.joinByAccessCode && prev.me.roles === next.me.roles &&
prev.room.accessCode === next.room.accessCode && prev.peers === next.peers &&
prev.room.code === next.room.code &&
prev.room.lockDialogOpen === next.room.lockDialogOpen &&
prev.room.codeHidden === next.room.codeHidden &&
prev.lobbyPeers === next.lobbyPeers prev.lobbyPeers === next.lobbyPeers
); );
} }

View File

@ -1,21 +1,15 @@
import React, { useState, useEffect } from 'react'; import React, { useState } from 'react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { connect } from 'react-redux';
import { withStyles } from '@material-ui/core/styles'; import { withStyles } from '@material-ui/core/styles';
import { withRoomContext } from '../RoomContext';
import isElectron from 'is-electron'; import isElectron from 'is-electron';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { useIntl, FormattedMessage } from 'react-intl'; import { useIntl, FormattedMessage } from 'react-intl';
import randomString from 'random-string'; import randomString from 'random-string';
import Dialog from '@material-ui/core/Dialog'; import Dialog from '@material-ui/core/Dialog';
import DialogContentText from '@material-ui/core/DialogContentText'; import DialogContentText from '@material-ui/core/DialogContentText';
import IconButton from '@material-ui/core/IconButton';
import AccountCircle from '@material-ui/icons/AccountCircle';
import Avatar from '@material-ui/core/Avatar';
import Typography from '@material-ui/core/Typography'; import Typography from '@material-ui/core/Typography';
import Button from '@material-ui/core/Button'; import Button from '@material-ui/core/Button';
import TextField from '@material-ui/core/TextField'; import TextField from '@material-ui/core/TextField';
import Tooltip from '@material-ui/core/Tooltip';
import CookieConsent from 'react-cookie-consent'; import CookieConsent from 'react-cookie-consent';
import MuiDialogTitle from '@material-ui/core/DialogTitle'; import MuiDialogTitle from '@material-ui/core/DialogTitle';
import MuiDialogContent from '@material-ui/core/DialogContent'; import MuiDialogContent from '@material-ui/core/DialogContent';
@ -88,63 +82,12 @@ const styles = (theme) =>
const DialogTitle = withStyles(styles)((props) => const DialogTitle = withStyles(styles)((props) =>
{ {
const [ open, setOpen ] = useState(false); const { children, classes, ...other } = props;
const intl = useIntl();
useEffect(() =>
{
const openTimer = setTimeout(() => setOpen(true), 1000);
const closeTimer = setTimeout(() => setOpen(false), 4000);
return () =>
{
clearTimeout(openTimer);
clearTimeout(closeTimer);
};
}, []);
const { children, classes, myPicture, onLogin, ...other } = props;
const handleTooltipClose = () =>
{
setOpen(false);
};
const handleTooltipOpen = () =>
{
setOpen(true);
};
return ( return (
<MuiDialogTitle disableTypography className={classes.dialogTitle} {...other}> <MuiDialogTitle disableTypography className={classes.dialogTitle} {...other}>
{ window.config && window.config.logo && <img alt='Logo' className={classes.logo} src={window.config.logo} /> } { window.config.logo && <img alt='Logo' className={classes.logo} src={window.config.logo} /> }
<Typography variant='h5'>{children}</Typography> <Typography variant='h5'>{children}</Typography>
{ window.config && window.config.loginEnabled &&
<Tooltip
onClose={handleTooltipClose}
onOpen={handleTooltipOpen}
open={open}
title={intl.formatMessage({
id : 'tooltip.login',
defaultMessage : 'Click to log in'
})}
placement='left'
>
<IconButton
aria-label='Account'
className={classes.loginButton}
color='inherit'
onClick={onLogin}
>
{ myPicture ?
<Avatar src={myPicture} className={classes.largeAvatar} />
:
<AccountCircle className={classes.largeIcon} />
}
</IconButton>
</Tooltip>
}
</MuiDialogTitle> </MuiDialogTitle>
); );
}); });
@ -165,9 +108,6 @@ const DialogActions = withStyles((theme) => ({
}))(MuiDialogActions); }))(MuiDialogActions);
const ChooseRoom = ({ const ChooseRoom = ({
roomClient,
loggedIn,
myPicture,
classes classes
}) => }) =>
{ {
@ -184,14 +124,8 @@ const ChooseRoom = ({
paper : classes.dialogPaper paper : classes.dialogPaper
}} }}
> >
<DialogTitle <DialogTitle>
myPicture={myPicture} { window.config.title ? window.config.title : 'Multiparty meeting' }
onLogin={() =>
{
loggedIn ? roomClient.logout() : roomClient.login();
}}
>
{ window.config && window.config.title ? window.config.title : 'Multiparty meeting' }
<hr /> <hr />
</DialogTitle> </DialogTitle>
<DialogContent> <DialogContent>
@ -244,7 +178,8 @@ const ChooseRoom = ({
<CookieConsent buttonText={intl.formatMessage({ <CookieConsent buttonText={intl.formatMessage({
id : 'room.consentUnderstand', id : 'room.consentUnderstand',
defaultMessage : 'I understand' defaultMessage : 'I understand'
})}> })}
>
<FormattedMessage <FormattedMessage
id='room.cookieConsent' id='room.cookieConsent'
defaultMessage='This website uses cookies to enhance the user experience' defaultMessage='This website uses cookies to enhance the user experience'
@ -258,34 +193,7 @@ const ChooseRoom = ({
ChooseRoom.propTypes = ChooseRoom.propTypes =
{ {
roomClient : PropTypes.any.isRequired,
loginEnabled : PropTypes.bool.isRequired,
loggedIn : PropTypes.bool.isRequired,
myPicture : PropTypes.string,
classes : PropTypes.object.isRequired classes : PropTypes.object.isRequired
}; };
const mapStateToProps = (state) => export default withStyles(styles)(ChooseRoom);
{
return {
loginEnabled : state.me.loginEnabled,
loggedIn : state.me.loggedIn,
myPicture : state.me.picture
};
};
export default withRoomContext(connect(
mapStateToProps,
null,
null,
{
areStatesEqual : (next, prev) =>
{
return (
prev.me.loginEnabled === next.me.loginEnabled &&
prev.me.loggedIn === next.me.loggedIn &&
prev.me.picture === next.me.picture
);
}
}
)(withStyles(styles)(ChooseRoom)));

View File

@ -1,9 +1,12 @@
import React, { useState } from 'react'; import React, { useState, useEffect } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { meProducersSelector } from '../Selectors'; import {
meProducersSelector,
makePermissionSelector
} from '../Selectors';
import { permissions } from '../../permissions';
import { withRoomContext } from '../../RoomContext'; import { withRoomContext } from '../../RoomContext';
import { withStyles } from '@material-ui/core/styles'; import { withStyles } from '@material-ui/core/styles';
import useMediaQuery from '@material-ui/core/useMediaQuery';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import classnames from 'classnames'; import classnames from 'classnames';
import * as appPropTypes from '../appPropTypes'; import * as appPropTypes from '../appPropTypes';
@ -11,6 +14,7 @@ import { useIntl, FormattedMessage } from 'react-intl';
import VideoView from '../VideoContainers/VideoView'; import VideoView from '../VideoContainers/VideoView';
import Volume from './Volume'; import Volume from './Volume';
import Fab from '@material-ui/core/Fab'; import Fab from '@material-ui/core/Fab';
import IconButton from '@material-ui/core/IconButton';
import Tooltip from '@material-ui/core/Tooltip'; import Tooltip from '@material-ui/core/Tooltip';
import MicIcon from '@material-ui/icons/Mic'; import MicIcon from '@material-ui/icons/Mic';
import MicOffIcon from '@material-ui/icons/MicOff'; import MicOffIcon from '@material-ui/icons/MicOff';
@ -60,33 +64,26 @@ const styles = (theme) =>
margin : theme.spacing(1), margin : theme.spacing(1),
pointerEvents : 'auto' pointerEvents : 'auto'
}, },
smallContainer :
{
backgroundColor : 'rgba(255, 255, 255, 0.9)',
margin : '0.5vmin',
padding : '0.5vmin',
boxShadow : '0px 3px 5px -1px rgba(0, 0, 0, 0.2), 0px 6px 10px 0px rgba(0, 0, 0, 0.14), 0px 1px 18px 0px rgba(0, 0, 0, 0.12)',
pointerEvents : 'auto',
transition : 'background-color 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms,box-shadow 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms,border 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms',
'&:hover' :
{
backgroundColor : 'rgba(213, 213, 213, 1)'
}
},
viewContainer : viewContainer :
{ {
position : 'relative', position : 'relative',
width : '100%', width : '100%',
height : '100%' height : '100%'
}, },
controls : meTag :
{
position : 'absolute',
width : '100%',
height : '100%',
backgroundColor : 'rgba(0, 0, 0, 0.3)',
display : 'flex',
flexDirection : 'column',
justifyContent : 'center',
alignItems : 'flex-end',
padding : theme.spacing(1),
zIndex : 21,
opacity : 0,
transition : 'opacity 0.3s',
touchAction : 'none',
pointerEvents : 'none',
'&.hover' :
{
opacity : 1
},
'& p' :
{ {
position : 'absolute', position : 'absolute',
float : 'left', float : 'left',
@ -95,7 +92,64 @@ const styles = (theme) =>
transform : 'translate(-50%, -50%)', transform : 'translate(-50%, -50%)',
color : 'rgba(255, 255, 255, 0.5)', color : 'rgba(255, 255, 255, 0.5)',
fontSize : '7em', fontSize : '7em',
margin : 0 zIndex : 30,
margin : 0,
opacity : 0,
transition : 'opacity 0.1s ease-in-out',
'&.hover' :
{
opacity : 1
},
'&.smallContainer' :
{
fontSize : '3em'
}
},
controls :
{
position : 'absolute',
width : '100%',
height : '100%',
display : 'flex',
flexDirection : 'column',
justifyContent : 'center',
alignItems : 'flex-end',
padding : theme.spacing(1),
zIndex : 21,
touchAction : 'none',
pointerEvents : 'none',
'&.hide' :
{
transition : 'opacity 0.1s ease-in-out',
opacity : 0
},
'&.hover' :
{
opacity : 1
}
},
ptt :
{
position : 'absolute',
float : 'left',
top : '25%',
left : '50%',
transform : 'translate(-50%, 0%)',
color : 'rgba(255, 255, 255, 0.7)',
fontSize : '1.3em',
backgroundColor : 'rgba(245, 0, 87, 0.70)',
margin : '4px',
padding : theme.spacing(2),
zIndex : 1200,
borderRadius : '20px',
textAlign : 'center',
opacity : 0,
transition : 'opacity 1s ease',
pointerEvents : 'none',
'&.enabled' :
{
transition : 'opacity 0.1s',
opacity : 1
} }
} }
}); });
@ -115,13 +169,16 @@ const Me = (props) =>
activeSpeaker, activeSpeaker,
spacing, spacing,
style, style,
smallButtons, smallContainer,
advancedMode, advancedMode,
micProducer, micProducer,
webcamProducer, webcamProducer,
screenProducer, screenProducer,
classes, extraVideoProducers,
theme canShareScreen,
transports,
noiseVolume,
classes
} = props; } = props;
const videoVisible = ( const videoVisible = (
@ -230,13 +287,66 @@ const Me = (props) =>
defaultMessage : 'Start screen sharing' defaultMessage : 'Start screen sharing'
}); });
} }
const [
screenShareTooltipOpen,
screenShareTooltipSetOpen
] = React.useState(false);
const screenShareTooltipHandleClose = () =>
{
screenShareTooltipSetOpen(false);
};
const screenShareTooltipHandleOpen = () =>
{
screenShareTooltipSetOpen(true);
};
if (screenState === 'off' && me.screenShareInProgress && screenShareTooltipOpen)
{
screenShareTooltipHandleClose();
}
const spacingStyle = const spacingStyle =
{ {
'margin' : spacing 'margin' : spacing
}; };
const smallScreen = useMediaQuery(theme.breakpoints.down('sm')); let audioScore = null;
if (micProducer && micProducer.score)
{
audioScore =
micProducer.score.reduce(
(prev, curr) =>
(prev.score < curr.score ? prev : curr)
);
}
let videoScore = null;
if (webcamProducer && webcamProducer.score)
{
videoScore =
webcamProducer.score.reduce(
(prev, curr) =>
(prev.score < curr.score ? prev : curr)
);
}
useEffect(() =>
{
let poll;
const interval = 1000;
if (advancedMode)
{
poll = setInterval(() => roomClient.getTransportStats(), interval);
}
return () => clearInterval(poll);
}, [ roomClient, advancedMode ]);
return ( return (
<React.Fragment> <React.Fragment>
@ -270,9 +380,51 @@ const Me = (props) =>
}} }}
style={spacingStyle} style={spacingStyle}
> >
<div className={classnames(classes.viewContainer)} style={style}>
{ me.browser.platform !== 'mobile' && smallContainer &&
<div className={classnames(
classes.ptt,
(micState === 'muted' && me.isSpeaking) ? 'enabled' : null
)}
>
<FormattedMessage
id='me.mutedPTT'
defaultMessage='You are muted, hold down SPACE-BAR to talk'
/>
</div>
}
<div className={classes.viewContainer} style={style}>
{ me.browser.platform !== 'mobile' && !smallContainer &&
<div className={classnames(
classes.ptt,
(micState === 'muted' && me.isSpeaking) ? 'enabled' : null
)}
>
<FormattedMessage
id='me.mutedPTT'
defaultMessage='You are muted, hold down SPACE-BAR to talk'
/>
</div>
}
<p className={
classnames(
classes.meTag,
hover ? 'hover' : null,
smallContainer ? 'smallContainer' : null
)}
>
<FormattedMessage
id='room.me'
defaultMessage='ME'
/>
</p>
{ !settings.buttonControlBar &&
<div <div
className={classnames(classes.controls, hover ? 'hover' : null)} className={classnames(
classes.controls,
settings.hiddenControls ? 'hide' : null,
hover ? 'hover' : null
)}
onMouseOver={() => setHover(true)} onMouseOver={() => setHover(true)}
onMouseOut={() => setHover(false)} onMouseOut={() => setHover(false)}
onTouchStart={() => onTouchStart={() =>
@ -293,13 +445,45 @@ const Me = (props) =>
}, 2000); }, 2000);
}} }}
> >
<p> <React.Fragment>
<FormattedMessage <Tooltip title={micTip} placement='left'>
id='room.me' { smallContainer ?
defaultMessage='ME' <div>
<IconButton
aria-label={intl.formatMessage({
id : 'device.muteAudio',
defaultMessage : 'Mute audio'
})}
className={classes.smallContainer}
disabled={!me.canSendMic || me.audioInProgress}
color={
micState === 'on' ?
settings.voiceActivatedUnmute && !me.isAutoMuted ?
'primary'
: 'default'
: 'secondary'}
size='small'
onClick={() =>
{
if (micState === 'off')
roomClient.updateMic({ start: true });
else if (micState === 'on')
roomClient.muteMic();
else
roomClient.unmuteMic();
}}
>
{ micState === 'on' ?
<MicIcon
color={me.isAutoMuted ? 'secondary' : 'primary'}
style={{ opacity: noiseVolume }}
/> />
</p> :
<Tooltip title={micTip} placement={smallScreen ? 'top' : 'left'}> <MicOffIcon />
}
</IconButton>
</div>
:
<div> <div>
<Fab <Fab
aria-label={intl.formatMessage({ aria-label={intl.formatMessage({
@ -308,12 +492,15 @@ const Me = (props) =>
})} })}
className={classes.fab} className={classes.fab}
disabled={!me.canSendMic || me.audioInProgress} disabled={!me.canSendMic || me.audioInProgress}
color={micState === 'on' ? 'default' : 'secondary'} color={micState === 'on' ?
size={smallButtons ? 'small' : 'large'} settings.voiceActivatedUnmute && !me.isAutoMuted? 'primary'
: 'default'
: 'secondary'}
size='large'
onClick={() => onClick={() =>
{ {
if (micState === 'off') if (micState === 'off')
roomClient.enableMic(); roomClient.updateMic({ start: true });
else if (micState === 'on') else if (micState === 'on')
roomClient.muteMic(); roomClient.muteMic();
else else
@ -321,14 +508,47 @@ const Me = (props) =>
}} }}
> >
{ micState === 'on' ? { micState === 'on' ?
<MicIcon /> <MicIcon
color={me.isAutoMuted && settings.voiceActivatedUnmute ?
'secondary' : 'primary'}
style={me.isAutoMuted && settings.voiceActivatedUnmute ?
{ opacity: noiseVolume }
: { opacity: 1 }}
/>
: :
<MicOffIcon /> <MicOffIcon />
} }
</Fab> </Fab>
</div> </div>
}
</Tooltip> </Tooltip>
<Tooltip title={webcamTip} placement={smallScreen ? 'top' : 'left'}> <Tooltip title={webcamTip} placement='left'>
{ smallContainer ?
<div>
<IconButton
aria-label={intl.formatMessage({
id : 'device.startVideo',
defaultMessage : 'Start video'
})}
className={classes.smallContainer}
disabled={!me.canSendWebcam || me.webcamInProgress}
color={webcamState === 'on' ? 'primary' : 'secondary'}
size='small'
onClick={() =>
{
webcamState === 'on' ?
roomClient.disableWebcam() :
roomClient.updateWebcam({ start: true });
}}
>
{ webcamState === 'on' ?
<VideoIcon />
:
<VideoOffIcon />
}
</IconButton>
</div>
:
<div> <div>
<Fab <Fab
aria-label={intl.formatMessage({ aria-label={intl.formatMessage({
@ -338,12 +558,12 @@ const Me = (props) =>
className={classes.fab} className={classes.fab}
disabled={!me.canSendWebcam || me.webcamInProgress} disabled={!me.canSendWebcam || me.webcamInProgress}
color={webcamState === 'on' ? 'default' : 'secondary'} color={webcamState === 'on' ? 'default' : 'secondary'}
size={smallButtons ? 'small' : 'large'} size='large'
onClick={() => onClick={() =>
{ {
webcamState === 'on' ? webcamState === 'on' ?
roomClient.disableWebcam() : roomClient.disableWebcam() :
roomClient.enableWebcam(); roomClient.updateWebcam({ start: true });
}} }}
> >
{ webcamState === 'on' ? { webcamState === 'on' ?
@ -353,8 +573,47 @@ const Me = (props) =>
} }
</Fab> </Fab>
</div> </div>
}
</Tooltip> </Tooltip>
<Tooltip title={screenTip} placement={smallScreen ? 'top' : 'left'}> { me.browser.platform !== 'mobile' &&
<Tooltip open={screenShareTooltipOpen}
onClose={screenShareTooltipHandleClose}
onOpen={screenShareTooltipHandleOpen}
title={screenTip} placement='left'
>
{ smallContainer ?
<div>
<IconButton
aria-label={intl.formatMessage({
id : 'device.startScreenSharing',
defaultMessage : 'Start screen sharing'
})}
className={classes.smallContainer}
disabled={
!canShareScreen ||
!me.canShareScreen ||
me.screenShareInProgress
}
color='primary'
size='small'
onClick={() =>
{
if (screenState === 'off')
roomClient.updateScreenSharing({ start: true });
else if (screenState === 'on')
roomClient.disableScreenSharing();
}}
>
{ (screenState === 'on' || screenState === 'unsupported') &&
<ScreenOffIcon/>
}
{ screenState === 'off' &&
<ScreenIcon/>
}
</IconButton>
</div>
:
<div> <div>
<Fab <Fab
aria-label={intl.formatMessage({ aria-label={intl.formatMessage({
@ -362,28 +621,19 @@ const Me = (props) =>
defaultMessage : 'Start screen sharing' defaultMessage : 'Start screen sharing'
})} })}
className={classes.fab} className={classes.fab}
disabled={!me.canShareScreen || me.screenShareInProgress} disabled={
!canShareScreen ||
!me.canShareScreen ||
me.screenShareInProgress
}
color={screenState === 'on' ? 'primary' : 'default'} color={screenState === 'on' ? 'primary' : 'default'}
size={smallButtons ? 'small' : 'large'} size='large'
onClick={() => onClick={() =>
{ {
switch (screenState) if (screenState === 'off')
{ roomClient.updateScreenSharing({ start: true });
case 'on': else if (screenState === 'on')
{
roomClient.disableScreenSharing(); roomClient.disableScreenSharing();
break;
}
case 'off':
{
roomClient.enableScreenSharing();
break;
}
default:
{
break;
}
}
}} }}
> >
{ (screenState === 'on' || screenState === 'unsupported') && { (screenState === 'on' || screenState === 'unsupported') &&
@ -394,11 +644,16 @@ const Me = (props) =>
} }
</Fab> </Fab>
</div> </div>
}
</Tooltip> </Tooltip>
}
</React.Fragment>
</div> </div>
}
<VideoView <VideoView
isMe isMe
VideoView
advancedMode={advancedMode} advancedMode={advancedMode}
peer={me} peer={me}
displayName={settings.displayName} displayName={settings.displayName}
@ -407,18 +662,31 @@ const Me = (props) =>
videoVisible={videoVisible} videoVisible={videoVisible}
audioCodec={micProducer && micProducer.codec} audioCodec={micProducer && micProducer.codec}
videoCodec={webcamProducer && webcamProducer.codec} videoCodec={webcamProducer && webcamProducer.codec}
netInfo={transports && transports}
audioScore={audioScore}
videoScore={videoScore}
showQuality
onChangeDisplayName={(displayName) => onChangeDisplayName={(displayName) =>
{ {
roomClient.changeDisplayName(displayName); roomClient.changeDisplayName(displayName);
}} }}
> >
<Volume id={me.id} /> { micState === 'muted' ? null : <Volume id={me.id} /> }
</VideoView> </VideoView>
</div> </div>
</div> </div>
{ screenProducer && { extraVideoProducers.map((producer) =>
<div {
className={classnames(classes.root, 'screen', hover && 'hover')} return (
<div key={producer.id}
className={
classnames(
classes.root,
'webcam',
hover ? 'hover' : null,
activeSpeaker ? 'active-speaker' : null
)
}
onMouseOver={() => setHover(true)} onMouseOver={() => setHover(true)}
onMouseOut={() => setHover(false)} onMouseOut={() => setHover(false)}
onTouchStart={() => onTouchStart={() =>
@ -440,9 +708,25 @@ const Me = (props) =>
}} }}
style={spacingStyle} style={spacingStyle}
> >
<div className={classnames(classes.viewContainer)} style={style}> <div className={classes.viewContainer} style={style}>
<p className={
classnames(
classes.meTag,
hover ? 'hover' : null,
smallContainer ? 'smallContainer' : null
)}
>
<FormattedMessage
id='room.me'
defaultMessage='ME'
/>
</p>
<div <div
className={classnames(classes.controls, hover && 'hover')} className={classnames(
classes.controls,
settings.hiddenControls ? 'hide' : null,
hover ? 'hover' : null
)}
onMouseOver={() => setHover(true)} onMouseOver={() => setHover(true)}
onMouseOut={() => setHover(false)} onMouseOut={() => setHover(false)}
onTouchStart={() => onTouchStart={() =>
@ -454,7 +738,6 @@ const Me = (props) =>
}} }}
onTouchEnd={() => onTouchEnd={() =>
{ {
if (touchTimeout) if (touchTimeout)
clearTimeout(touchTimeout); clearTimeout(touchTimeout);
@ -464,13 +747,105 @@ const Me = (props) =>
}, 2000); }, 2000);
}} }}
> >
<p> <Tooltip title={webcamTip} placement='left'>
{ smallContainer ?
<div>
<IconButton
aria-label={intl.formatMessage({
id : 'device.stopVideo',
defaultMessage : 'Stop video'
})}
className={classes.smallContainer}
disabled={!me.canSendWebcam || me.webcamInProgress}
size='small'
color='primary'
onClick={() =>
{
roomClient.disableExtraVideo(producer.id);
}}
>
<VideoIcon />
</IconButton>
</div>
:
<div>
<Fab
aria-label={intl.formatMessage({
id : 'device.stopVideo',
defaultMessage : 'Stop video'
})}
className={classes.fab}
disabled={!me.canSendWebcam || me.webcamInProgress}
size={smallContainer ? 'small' : 'large'}
onClick={() =>
{
roomClient.disableExtraVideo(producer.id);
}}
>
<VideoIcon />
</Fab>
</div>
}
</Tooltip>
</div>
<VideoView
isMe
isExtraVideo
advancedMode={advancedMode}
peer={me}
displayName={settings.displayName}
showPeerInfo
videoTrack={producer && producer.track}
videoVisible={videoVisible}
videoCodec={producer && producer.codec}
onChangeDisplayName={(displayName) =>
{
roomClient.changeDisplayName(displayName);
}}
/>
</div>
</div>
);
})}
{ screenProducer &&
<div
className={classnames(classes.root, 'screen', hover ? 'hover' : null)}
onMouseOver={() => setHover(true)}
onMouseOut={() => setHover(false)}
onTouchStart={() =>
{
if (touchTimeout)
clearTimeout(touchTimeout);
setHover(true);
}}
onTouchEnd={() =>
{
if (touchTimeout)
clearTimeout(touchTimeout);
touchTimeout = setTimeout(() =>
{
setHover(false);
}, 2000);
}}
style={spacingStyle}
>
<div className={classes.viewContainer} style={style}>
<p className={
classnames(
classes.meTag,
hover ? 'hover' : null,
smallContainer ? 'smallContainer' : null
)}
>
<FormattedMessage <FormattedMessage
id='room.me' id='room.me'
defaultMessage='ME' defaultMessage='ME'
/> />
</p> </p>
</div>
<VideoView <VideoView
isMe isMe
@ -498,35 +873,66 @@ Me.propTypes =
micProducer : appPropTypes.Producer, micProducer : appPropTypes.Producer,
webcamProducer : appPropTypes.Producer, webcamProducer : appPropTypes.Producer,
screenProducer : appPropTypes.Producer, screenProducer : appPropTypes.Producer,
extraVideoProducers : PropTypes.arrayOf(appPropTypes.Producer),
spacing : PropTypes.number, spacing : PropTypes.number,
style : PropTypes.object, style : PropTypes.object,
smallButtons : PropTypes.bool, smallContainer : PropTypes.bool,
canShareScreen : PropTypes.bool.isRequired,
noiseVolume : PropTypes.number,
classes : PropTypes.object.isRequired, classes : PropTypes.object.isRequired,
theme : PropTypes.object.isRequired theme : PropTypes.object.isRequired,
transports : PropTypes.object.isRequired
}; };
const makeMapStateToProps = () =>
{
const hasPermission = makePermissionSelector(permissions.SHARE_SCREEN);
const mapStateToProps = (state) => const mapStateToProps = (state) =>
{ {
let volume;
// noiseVolume under threshold
if (state.peerVolumes[state.me.id] < state.settings.noiseThreshold)
{
// noiseVolume mapped to range 0.5 ... 1 (threshold switch)
volume = 1 + ((Math.abs(state.peerVolumes[state.me.id] -
state.settings.noiseThreshold) / (-120 -
state.settings.noiseThreshold)));
}
// noiseVolume over threshold: no noise but voice
else { volume = 0; }
return { return {
me : state.me, me : state.me,
...meProducersSelector(state), ...meProducersSelector(state),
settings : state.settings, settings : state.settings,
activeSpeaker : state.me.id === state.room.activeSpeakerId activeSpeaker : state.me.id === state.room.activeSpeakerId,
canShareScreen : hasPermission(state),
transports : state.transports,
noiseVolume : volume
}; };
}; };
return mapStateToProps;
};
export default withRoomContext(connect( export default withRoomContext(connect(
mapStateToProps, makeMapStateToProps,
null, null,
null, null,
{ {
areStatesEqual : (next, prev) => areStatesEqual : (next, prev) =>
{ {
return ( return (
prev.room === next.room &&
prev.me === next.me && prev.me === next.me &&
Math.round(prev.peerVolumes[prev.me.id]) ===
Math.round(next.peerVolumes[next.me.id]) &&
prev.peers === next.peers &&
prev.producers === next.producers && prev.producers === next.producers &&
prev.settings === next.settings && prev.settings === next.settings &&
prev.room.activeSpeakerId === next.room.activeSpeakerId prev.transports === next.transports
); );
} }
} }

View File

@ -12,8 +12,9 @@ import { useIntl, FormattedMessage } from 'react-intl';
import VideoView from '../VideoContainers/VideoView'; import VideoView from '../VideoContainers/VideoView';
import Tooltip from '@material-ui/core/Tooltip'; import Tooltip from '@material-ui/core/Tooltip';
import Fab from '@material-ui/core/Fab'; import Fab from '@material-ui/core/Fab';
import MicIcon from '@material-ui/icons/Mic'; import IconButton from '@material-ui/core/IconButton';
import MicOffIcon from '@material-ui/icons/MicOff'; import VolumeUpIcon from '@material-ui/icons/VolumeUp';
import VolumeOffIcon from '@material-ui/icons/VolumeOff';
import NewWindowIcon from '@material-ui/icons/OpenInNew'; import NewWindowIcon from '@material-ui/icons/OpenInNew';
import FullScreenIcon from '@material-ui/icons/Fullscreen'; import FullScreenIcon from '@material-ui/icons/Fullscreen';
import Volume from './Volume'; import Volume from './Volume';
@ -59,6 +60,19 @@ const styles = (theme) =>
{ {
margin : theme.spacing(1) margin : theme.spacing(1)
}, },
smallContainer :
{
backgroundColor : 'rgba(255, 255, 255, 0.9)',
margin : '0.5vmin',
padding : '0.5vmin',
boxShadow : '0px 3px 5px -1px rgba(0, 0, 0, 0.2), 0px 6px 10px 0px rgba(0, 0, 0, 0.14), 0px 1px 18px 0px rgba(0, 0, 0, 0.12)',
pointerEvents : 'auto',
transition : 'background-color 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms,box-shadow 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms,border 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms',
'&:hover' :
{
backgroundColor : 'rgba(213, 213, 213, 1)'
}
},
viewContainer : viewContainer :
{ {
position : 'relative', position : 'relative',
@ -121,14 +135,16 @@ const Peer = (props) =>
advancedMode, advancedMode,
peer, peer,
activeSpeaker, activeSpeaker,
browser,
micConsumer, micConsumer,
webcamConsumer, webcamConsumer,
screenConsumer, screenConsumer,
extraVideoConsumers,
toggleConsumerFullscreen, toggleConsumerFullscreen,
toggleConsumerWindow, toggleConsumerWindow,
spacing, spacing,
style, style,
smallButtons, smallContainer,
windowConsumer, windowConsumer,
classes, classes,
theme theme
@ -167,8 +183,8 @@ const Peer = (props) =>
classnames( classnames(
classes.root, classes.root,
'webcam', 'webcam',
hover && 'hover', hover ? 'hover' : null,
activeSpeaker && 'active-speaker' activeSpeaker ? 'active-speaker' : null
) )
} }
onMouseOver={() => setHover(true)} onMouseOver={() => setHover(true)}
@ -205,7 +221,7 @@ const Peer = (props) =>
} }
<div <div
className={classnames(classes.controls, hover && 'hover')} className={classnames(classes.controls, hover ? 'hover' : null)}
onMouseOver={() => setHover(true)} onMouseOver={() => setHover(true)}
onMouseOut={() => setHover(false)} onMouseOut={() => setHover(false)}
onTouchStart={() => onTouchStart={() =>
@ -233,16 +249,16 @@ const Peer = (props) =>
})} })}
placement={smallScreen ? 'top' : 'left'} placement={smallScreen ? 'top' : 'left'}
> >
<div> { smallContainer ?
<Fab <IconButton
aria-label={intl.formatMessage({ aria-label={intl.formatMessage({
id : 'device.muteAudio', id : 'device.muteAudio',
defaultMessage : 'Mute audio' defaultMessage : 'Mute audio'
})} })}
className={classes.fab} className={classes.smallContainer}
disabled={!micConsumer} disabled={!micConsumer}
color={micEnabled ? 'default' : 'secondary'} color='primary'
size={smallButtons ? 'small' : 'large'} size='small'
onClick={() => onClick={() =>
{ {
micEnabled ? micEnabled ?
@ -251,15 +267,38 @@ const Peer = (props) =>
}} }}
> >
{ micEnabled ? { micEnabled ?
<MicIcon /> <VolumeUpIcon />
: :
<MicOffIcon /> <VolumeOffIcon />
}
</IconButton>
:
<Fab
aria-label={intl.formatMessage({
id : 'device.muteAudio',
defaultMessage : 'Mute audio'
})}
className={classes.fab}
disabled={!micConsumer}
color={micEnabled ? 'default' : 'secondary'}
size='large'
onClick={() =>
{
micEnabled ?
roomClient.modifyPeerConsumer(peer.id, 'mic', true) :
roomClient.modifyPeerConsumer(peer.id, 'mic', false);
}}
>
{ micEnabled ?
<VolumeUpIcon />
:
<VolumeOffIcon />
} }
</Fab> </Fab>
</div> }
</Tooltip> </Tooltip>
{ !smallScreen && { browser.platform !== 'mobile' &&
<Tooltip <Tooltip
title={intl.formatMessage({ title={intl.formatMessage({
id : 'label.newWindow', id : 'label.newWindow',
@ -267,7 +306,27 @@ const Peer = (props) =>
})} })}
placement={smallScreen ? 'top' : 'left'} placement={smallScreen ? 'top' : 'left'}
> >
<div> { smallContainer ?
<IconButton
aria-label={intl.formatMessage({
id : 'label.newWindow',
defaultMessage : 'New window'
})}
className={classes.smallContainer}
disabled={
!videoVisible ||
(windowConsumer === webcamConsumer.id)
}
size='small'
color='primary'
onClick={() =>
{
toggleConsumerWindow(webcamConsumer);
}}
>
<NewWindowIcon />
</IconButton>
:
<Fab <Fab
aria-label={intl.formatMessage({ aria-label={intl.formatMessage({
id : 'label.newWindow', id : 'label.newWindow',
@ -278,7 +337,7 @@ const Peer = (props) =>
!videoVisible || !videoVisible ||
(windowConsumer === webcamConsumer.id) (windowConsumer === webcamConsumer.id)
} }
size={smallButtons ? 'small' : 'large'} size='large'
onClick={() => onClick={() =>
{ {
toggleConsumerWindow(webcamConsumer); toggleConsumerWindow(webcamConsumer);
@ -286,7 +345,7 @@ const Peer = (props) =>
> >
<NewWindowIcon /> <NewWindowIcon />
</Fab> </Fab>
</div> }
</Tooltip> </Tooltip>
} }
@ -297,7 +356,24 @@ const Peer = (props) =>
})} })}
placement={smallScreen ? 'top' : 'left'} placement={smallScreen ? 'top' : 'left'}
> >
<div> { smallContainer ?
<IconButton
aria-label={intl.formatMessage({
id : 'label.fullscreen',
defaultMessage : 'Fullscreen'
})}
className={classes.smallContainer}
disabled={!videoVisible}
size='small'
color='primary'
onClick={() =>
{
toggleConsumerFullscreen(webcamConsumer);
}}
>
<FullScreenIcon />
</IconButton>
:
<Fab <Fab
aria-label={intl.formatMessage({ aria-label={intl.formatMessage({
id : 'label.fullscreen', id : 'label.fullscreen',
@ -305,7 +381,7 @@ const Peer = (props) =>
})} })}
className={classes.fab} className={classes.fab}
disabled={!videoVisible} disabled={!videoVisible}
size={smallButtons ? 'small' : 'large'} size='large'
onClick={() => onClick={() =>
{ {
toggleConsumerFullscreen(webcamConsumer); toggleConsumerFullscreen(webcamConsumer);
@ -313,11 +389,12 @@ const Peer = (props) =>
> >
<FullScreenIcon /> <FullScreenIcon />
</Fab> </Fab>
</div> }
</Tooltip> </Tooltip>
</div> </div>
<VideoView <VideoView
showQuality
advancedMode={advancedMode} advancedMode={advancedMode}
peer={peer} peer={peer}
displayName={peer.displayName} displayName={peer.displayName}
@ -339,6 +416,7 @@ const Peer = (props) =>
videoMultiLayer={webcamConsumer && webcamConsumer.type !== 'simple'} videoMultiLayer={webcamConsumer && webcamConsumer.type !== 'simple'}
videoTrack={webcamConsumer && webcamConsumer.track} videoTrack={webcamConsumer && webcamConsumer.track}
videoVisible={videoVisible} videoVisible={videoVisible}
audioTrack={micConsumer && micConsumer.track}
audioCodec={micConsumer && micConsumer.codec} audioCodec={micConsumer && micConsumer.codec}
videoCodec={webcamConsumer && webcamConsumer.codec} videoCodec={webcamConsumer && webcamConsumer.codec}
audioScore={micConsumer ? micConsumer.score : null} audioScore={micConsumer ? micConsumer.score : null}
@ -349,9 +427,202 @@ const Peer = (props) =>
</div> </div>
</div> </div>
{ extraVideoConsumers.map((consumer) =>
{
return (
<div key={consumer.id}
className={
classnames(
classes.root,
'webcam',
hover ? 'hover' : null,
activeSpeaker ? 'active-speaker' : null
)
}
onMouseOver={() => setHover(true)}
onMouseOut={() => setHover(false)}
onTouchStart={() =>
{
if (touchTimeout)
clearTimeout(touchTimeout);
setHover(true);
}}
onTouchEnd={() =>
{
if (touchTimeout)
clearTimeout(touchTimeout);
touchTimeout = setTimeout(() =>
{
setHover(false);
}, 2000);
}}
style={rootStyle}
>
<div className={classnames(classes.viewContainer)}>
{ !videoVisible &&
<div className={classes.videoInfo}>
<p>
<FormattedMessage
id='room.videoPaused'
defaultMessage='This video is paused'
/>
</p>
</div>
}
<div
className={classnames(classes.controls, hover ? 'hover' : null)}
onMouseOver={() => setHover(true)}
onMouseOut={() => setHover(false)}
onTouchStart={() =>
{
if (touchTimeout)
clearTimeout(touchTimeout);
setHover(true);
}}
onTouchEnd={() =>
{
if (touchTimeout)
clearTimeout(touchTimeout);
touchTimeout = setTimeout(() =>
{
setHover(false);
}, 2000);
}}
>
{ browser.platform !== 'mobile' &&
<Tooltip
title={intl.formatMessage({
id : 'label.newWindow',
defaultMessage : 'New window'
})}
placement={smallScreen ? 'top' : 'left'}
>
{ smallContainer ?
<IconButton
aria-label={intl.formatMessage({
id : 'label.newWindow',
defaultMessage : 'New window'
})}
className={classes.smallContainer}
disabled={
!videoVisible ||
(windowConsumer === consumer.id)
}
size='small'
color='primary'
onClick={() =>
{
toggleConsumerWindow(consumer);
}}
>
<NewWindowIcon />
</IconButton>
:
<Fab
aria-label={intl.formatMessage({
id : 'label.newWindow',
defaultMessage : 'New window'
})}
className={classes.fab}
disabled={
!videoVisible ||
(windowConsumer === consumer.id)
}
size='large'
onClick={() =>
{
toggleConsumerWindow(consumer);
}}
>
<NewWindowIcon />
</Fab>
}
</Tooltip>
}
<Tooltip
title={intl.formatMessage({
id : 'label.fullscreen',
defaultMessage : 'Fullscreen'
})}
placement={smallScreen ? 'top' : 'left'}
>
{ smallContainer ?
<IconButton
aria-label={intl.formatMessage({
id : 'label.fullscreen',
defaultMessage : 'Fullscreen'
})}
className={classes.smallContainer}
disabled={!videoVisible}
size='small'
color='primary'
onClick={() =>
{
toggleConsumerFullscreen(consumer);
}}
>
<FullScreenIcon />
</IconButton>
:
<Fab
aria-label={intl.formatMessage({
id : 'label.fullscreen',
defaultMessage : 'Fullscreen'
})}
className={classes.fab}
disabled={!videoVisible}
size='large'
onClick={() =>
{
toggleConsumerFullscreen(consumer);
}}
>
<FullScreenIcon />
</Fab>
}
</Tooltip>
</div>
<VideoView
showQuality
advancedMode={advancedMode}
peer={peer}
displayName={peer.displayName}
showPeerInfo
consumerSpatialLayers={consumer ? consumer.spatialLayers : null}
consumerTemporalLayers={consumer ? consumer.temporalLayers : null}
consumerCurrentSpatialLayer={
consumer ? consumer.currentSpatialLayer : null
}
consumerCurrentTemporalLayer={
consumer ? consumer.currentTemporalLayer : null
}
consumerPreferredSpatialLayer={
consumer ? consumer.preferredSpatialLayer : null
}
consumerPreferredTemporalLayer={
consumer ? consumer.preferredTemporalLayer : null
}
videoMultiLayer={consumer && consumer.type !== 'simple'}
videoTrack={consumer && consumer.track}
videoVisible={videoVisible}
videoCodec={consumer && consumer.codec}
videoScore={consumer ? consumer.score : null}
/>
</div>
</div>
);
})}
{ screenConsumer && { screenConsumer &&
<div <div
className={classnames(classes.root, 'screen', hover && 'hover')} className={classnames(classes.root, 'screen', hover ? 'hover' : null)}
onMouseOver={() => setHover(true)} onMouseOver={() => setHover(true)}
onMouseOut={() => setHover(false)} onMouseOut={() => setHover(false)}
onTouchStart={() => onTouchStart={() =>
@ -385,7 +656,7 @@ const Peer = (props) =>
</div> </div>
} }
<div <div
className={classnames(classes.controls, hover && 'hover')} className={classnames(classes.controls, hover ? 'hover' : null)}
onMouseOver={() => setHover(true)} onMouseOver={() => setHover(true)}
onMouseOut={() => setHover(false)} onMouseOut={() => setHover(false)}
onTouchStart={() => onTouchStart={() =>
@ -407,7 +678,7 @@ const Peer = (props) =>
}, 2000); }, 2000);
}} }}
> >
{ !smallScreen && { browser.platform !== 'mobile' &&
<Tooltip <Tooltip
title={intl.formatMessage({ title={intl.formatMessage({
id : 'label.newWindow', id : 'label.newWindow',
@ -415,7 +686,6 @@ const Peer = (props) =>
})} })}
placement={smallScreen ? 'top' : 'left'} placement={smallScreen ? 'top' : 'left'}
> >
<div>
<Fab <Fab
aria-label={intl.formatMessage({ aria-label={intl.formatMessage({
id : 'label.newWindow', id : 'label.newWindow',
@ -426,7 +696,7 @@ const Peer = (props) =>
!screenVisible || !screenVisible ||
(windowConsumer === screenConsumer.id) (windowConsumer === screenConsumer.id)
} }
size={smallButtons ? 'small' : 'large'} size={smallContainer ? 'small' : 'large'}
onClick={() => onClick={() =>
{ {
toggleConsumerWindow(screenConsumer); toggleConsumerWindow(screenConsumer);
@ -434,7 +704,6 @@ const Peer = (props) =>
> >
<NewWindowIcon /> <NewWindowIcon />
</Fab> </Fab>
</div>
</Tooltip> </Tooltip>
} }
@ -445,7 +714,6 @@ const Peer = (props) =>
})} })}
placement={smallScreen ? 'top' : 'left'} placement={smallScreen ? 'top' : 'left'}
> >
<div>
<Fab <Fab
aria-label={intl.formatMessage({ aria-label={intl.formatMessage({
id : 'label.fullscreen', id : 'label.fullscreen',
@ -453,7 +721,7 @@ const Peer = (props) =>
})} })}
className={classes.fab} className={classes.fab}
disabled={!screenVisible} disabled={!screenVisible}
size={smallButtons ? 'small' : 'large'} size={smallContainer ? 'small' : 'large'}
onClick={() => onClick={() =>
{ {
toggleConsumerFullscreen(screenConsumer); toggleConsumerFullscreen(screenConsumer);
@ -461,10 +729,10 @@ const Peer = (props) =>
> >
<FullScreenIcon /> <FullScreenIcon />
</Fab> </Fab>
</div>
</Tooltip> </Tooltip>
</div> </div>
<VideoView <VideoView
showQuality
advancedMode={advancedMode} advancedMode={advancedMode}
videoContain videoContain
consumerSpatialLayers={ consumerSpatialLayers={
@ -489,6 +757,7 @@ const Peer = (props) =>
videoTrack={screenConsumer && screenConsumer.track} videoTrack={screenConsumer && screenConsumer.track}
videoVisible={screenVisible} videoVisible={screenVisible}
videoCodec={screenConsumer && screenConsumer.codec} videoCodec={screenConsumer && screenConsumer.codec}
videoScore={screenConsumer ? screenConsumer.score : null}
/> />
</div> </div>
</div> </div>
@ -505,11 +774,13 @@ Peer.propTypes =
micConsumer : appPropTypes.Consumer, micConsumer : appPropTypes.Consumer,
webcamConsumer : appPropTypes.Consumer, webcamConsumer : appPropTypes.Consumer,
screenConsumer : appPropTypes.Consumer, screenConsumer : appPropTypes.Consumer,
extraVideoConsumers : PropTypes.arrayOf(appPropTypes.Consumer),
windowConsumer : PropTypes.string, windowConsumer : PropTypes.string,
activeSpeaker : PropTypes.bool, activeSpeaker : PropTypes.bool,
browser : PropTypes.object.isRequired,
spacing : PropTypes.number, spacing : PropTypes.number,
style : PropTypes.object, style : PropTypes.object,
smallButtons : PropTypes.bool, smallContainer : PropTypes.bool,
toggleConsumerFullscreen : PropTypes.func.isRequired, toggleConsumerFullscreen : PropTypes.func.isRequired,
toggleConsumerWindow : PropTypes.func.isRequired, toggleConsumerWindow : PropTypes.func.isRequired,
classes : PropTypes.object.isRequired, classes : PropTypes.object.isRequired,
@ -526,7 +797,8 @@ const makeMapStateToProps = (initialState, { id }) =>
peer : state.peers[id], peer : state.peers[id],
...getPeerConsumers(state, id), ...getPeerConsumers(state, id),
windowConsumer : state.room.windowConsumer, windowConsumer : state.room.windowConsumer,
activeSpeaker : id === state.room.activeSpeakerId activeSpeaker : id === state.room.activeSpeakerId,
browser : state.me.browser
}; };
}; };
@ -560,7 +832,8 @@ export default withRoomContext(connect(
prev.peers === next.peers && prev.peers === next.peers &&
prev.consumers === next.consumers && prev.consumers === next.consumers &&
prev.room.activeSpeakerId === next.room.activeSpeakerId && prev.room.activeSpeakerId === next.room.activeSpeakerId &&
prev.room.windowConsumer === next.room.windowConsumer prev.room.windowConsumer === next.room.windowConsumer &&
prev.me.browser === next.me.browser
); );
} }
} }

View File

@ -91,16 +91,6 @@ const SpeakerPeer = (props) =>
!screenConsumer.remotelyPaused !screenConsumer.remotelyPaused
); );
let videoProfile;
if (webcamConsumer)
videoProfile = webcamConsumer.profile;
let screenProfile;
if (screenConsumer)
screenProfile = screenConsumer.profile;
const spacingStyle = const spacingStyle =
{ {
'margin' : spacing 'margin' : spacing
@ -134,11 +124,27 @@ const SpeakerPeer = (props) =>
peer={peer} peer={peer}
displayName={peer.displayName} displayName={peer.displayName}
showPeerInfo showPeerInfo
videoTrack={webcamConsumer ? webcamConsumer.track : null} consumerSpatialLayers={webcamConsumer ? webcamConsumer.spatialLayers : null}
consumerTemporalLayers={webcamConsumer ? webcamConsumer.temporalLayers : null}
consumerCurrentSpatialLayer={
webcamConsumer ? webcamConsumer.currentSpatialLayer : null
}
consumerCurrentTemporalLayer={
webcamConsumer ? webcamConsumer.currentTemporalLayer : null
}
consumerPreferredSpatialLayer={
webcamConsumer ? webcamConsumer.preferredSpatialLayer : null
}
consumerPreferredTemporalLayer={
webcamConsumer ? webcamConsumer.preferredTemporalLayer : null
}
videoMultiLayer={webcamConsumer && webcamConsumer.type !== 'simple'}
videoTrack={webcamConsumer && webcamConsumer.track}
videoVisible={videoVisible} videoVisible={videoVisible}
videoProfile={videoProfile} audioCodec={micConsumer && micConsumer.codec}
audioCodec={micConsumer ? micConsumer.codec : null} videoCodec={webcamConsumer && webcamConsumer.codec}
videoCodec={webcamConsumer ? webcamConsumer.codec : null} audioScore={micConsumer ? micConsumer.score : null}
videoScore={webcamConsumer ? webcamConsumer.score : null}
> >
<Volume id={peer.id} /> <Volume id={peer.id} />
</VideoView> </VideoView>
@ -165,10 +171,29 @@ const SpeakerPeer = (props) =>
<VideoView <VideoView
advancedMode={advancedMode} advancedMode={advancedMode}
videoContain videoContain
videoTrack={screenConsumer ? screenConsumer.track : null} consumerSpatialLayers={
screenConsumer ? screenConsumer.spatialLayers : null
}
consumerTemporalLayers={
screenConsumer ? screenConsumer.temporalLayers : null
}
consumerCurrentSpatialLayer={
screenConsumer ? screenConsumer.currentSpatialLayer : null
}
consumerCurrentTemporalLayer={
screenConsumer ? screenConsumer.currentTemporalLayer : null
}
consumerPreferredSpatialLayer={
screenConsumer ? screenConsumer.preferredSpatialLayer : null
}
consumerPreferredTemporalLayer={
screenConsumer ? screenConsumer.preferredTemporalLayer : null
}
videoMultiLayer={screenConsumer && screenConsumer.type !== 'simple'}
videoTrack={screenConsumer && screenConsumer.track}
videoVisible={screenVisible} videoVisible={screenVisible}
videoProfile={screenProfile} videoCodec={screenConsumer && screenConsumer.codec}
videoCodec={screenConsumer ? screenConsumer.codec : null} videoScore={screenConsumer ? screenConsumer.score : null}
/> />
</div> </div>
} }

View File

@ -58,27 +58,27 @@ const styles = () =>
'&.level6' : '&.level6' :
{ {
height : '60%', height : '60%',
backgroundColor : 'rgba(255, 0, 0, 0.65)' backgroundColor : 'rgba(255, 165, 0, 0.65)'
}, },
'&.level7' : '&.level7' :
{ {
height : '70%', height : '70%',
backgroundColor : 'rgba(255, 0, 0, 0.65)' backgroundColor : 'rgba(255, 100, 0, 0.65)'
}, },
'&.level8' : '&.level8' :
{ {
height : '80%', height : '80%',
backgroundColor : 'rgba(0, 0, 0, 0.65)' backgroundColor : 'rgba(255, 60, 0, 0.65)'
}, },
'&.level9' : '&.level9' :
{ {
height : '90%', height : '90%',
backgroundColor : 'rgba(0, 0, 0, 0.65)' backgroundColor : 'rgba(255, 30, 0, 0.65)'
}, },
'&.level10' : '&.level10' :
{ {
height : '100%', height : '100%',
backgroundColor : 'rgba(0, 0, 0, 0.65)' backgroundColor : 'rgba(255, 0, 0, 0.65)'
} }
}, },
volumeSmall : volumeSmall :
@ -94,17 +94,17 @@ const styles = () =>
smallBar : smallBar :
{ {
flex : '0 0 auto', flex : '0 0 auto',
margin : '0.3rem',
backgroundSize : '75%', backgroundSize : '75%',
backgroundRepeat : 'no-repeat', backgroundRepeat : 'no-repeat',
backgroundColor : 'rgba(0, 0, 0, 1)', backgroundColor : 'rgba(0, 0, 0, 1)',
cursor : 'pointer', cursor : 'pointer',
transitionProperty : 'opacity, background-color', transitionProperty : 'opacity, background-color',
width : 3, width : 3,
borderRadius : 6, borderRadius : 2,
transitionDuration : '0.25s', transitionDuration : '0.25s',
position : 'absolute', position : 'absolute',
bottom : 0, top : '50%',
transform : 'translateY(-50%)',
'&.level0' : { height: 0 }, '&.level0' : { height: 0 },
'&.level1' : { height: '0.2vh' }, '&.level1' : { height: '0.2vh' },
'&.level2' : { height: '0.4vh' }, '&.level2' : { height: '0.4vh' },
@ -148,10 +148,17 @@ Volume.propTypes =
const makeMapStateToProps = (initialState, props) => const makeMapStateToProps = (initialState, props) =>
{ {
const mapStateToProps = (state) => const mapStateToProps = (state) =>
{
if (state.peerVolumes[props.id]>state.settings.noiseThreshold)
{ {
return { return {
volume : state.peerVolumes[props.id] volume : Math.round((state.peerVolumes[props.id]+100) / 10)
}; };
}
else
{
return { volume: 0 };
}
}; };
return mapStateToProps; return mapStateToProps;

View File

@ -0,0 +1,164 @@
import React from 'react';
import { connect } from 'react-redux';
import { withStyles } from '@material-ui/core/styles';
import { withRoomContext } from '../../RoomContext';
import * as roomActions from '../../actions/roomActions';
import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import Dialog from '@material-ui/core/Dialog';
import DialogTitle from '@material-ui/core/DialogTitle';
import DialogActions from '@material-ui/core/DialogActions';
import DialogContent from '@material-ui/core/DialogContent';
import DialogContentText from '@material-ui/core/DialogContentText';
import Link from '@material-ui/core/Link';
import Button from '@material-ui/core/Button';
import Divider from '@material-ui/core/Divider';
const styles = (theme) =>
({
dialogPaper :
{
width : '30vw',
[theme.breakpoints.down('lg')] :
{
width : '40vw'
},
[theme.breakpoints.down('md')] :
{
width : '50vw'
},
[theme.breakpoints.down('sm')] :
{
width : '70vw'
},
[theme.breakpoints.down('xs')] :
{
width : '90vw'
}
},
logo :
{
marginRight : 'auto'
},
link :
{
display : 'block',
textAlign : 'center',
marginBottom : theme.spacing(1)
},
divider :
{
marginBottom : theme.spacing(3)
}
});
const eduMeetUrl='https://edumeet.org';
const About = ({
aboutOpen,
handleCloseAbout,
classes
}) =>
{
return (
<Dialog
open={aboutOpen}
onClose={() => handleCloseAbout(false)}
classes={{
paper : classes.dialogPaper
}}
>
<DialogTitle id='form-dialog-title'>
<FormattedMessage
id='room.about'
defaultMessage='About'
/>
</DialogTitle>
<DialogContent dividers>
<DialogContentText paragraph>
Contributions to this work were made on behalf of the GÉANT
project, a project that has received funding from the
European Unions Horizon 2020 research and innovation
programme under Grant Agreement No. 731122 (GN4-2).
On behalf of GÉANT project, GÉANT Association is the sole
owner of the copyright in all material which was developed
by a member of the GÉANT project.
</DialogContentText>
<DialogContentText paragraph>
GÉANT Vereniging (Association) is registered with the
Chamber of Commerce in Amsterdam with registration number
40535155 and operates in the UK as a branch of GÉANT
Vereniging. Registered office: Hoekenrode 3, 1102BR
Amsterdam, The Netherlands. UK branch address: City House,
126-130 Hills Road, Cambridge CB2 1PQ, UK.
</DialogContentText>
<DialogContentText align='center' paragraph>
<Link href={eduMeetUrl} target='_blank' rel='noreferrer' color='secondary' variant='h6'>
{eduMeetUrl}
</Link>
</DialogContentText>
<DialogContentText align='center' variant='body2'>
<FormattedMessage
id='label.version'
defaultMessage='Version'
/>
:{` ${process.env.REACT_APP_VERSION}`}
</DialogContentText>
<Divider variant='middle' light className={classes.divider}/>
{
window.config.supportUrl
&&
<DialogContentText align='center' paragraph>
<span>Visit for more info: </span>
<Link href={window.config.supportUrl} target='_blank' rel='noreferrer' color='secondary'>
{ window.config.supportUrl }
</Link>
</DialogContentText>
}
<Link href={window.config.privacyUrl ? window.config.privacyUrl : 'privacy/privacy.html'} target='_blank' rel='noreferrer' color='secondary' className={classes.link}>
Data protection and Privacy Policy
</Link>
</DialogContent>
<DialogActions>
{ window.config.logo && <img alt='Logo' className={classes.logo} src={window.config.logo} /> }
<Button onClick={() => { handleCloseAbout(false); }} color='primary'>
<FormattedMessage
id='label.close'
defaultMessage='Close'
/>
</Button>
</DialogActions>
</Dialog>
);
};
About.propTypes =
{
roomClient : PropTypes.object.isRequired,
aboutOpen : PropTypes.bool.isRequired,
handleCloseAbout : PropTypes.func.isRequired,
classes : PropTypes.object.isRequired
};
const mapStateToProps = (state) =>
({
aboutOpen : state.room.aboutOpen
});
const mapDispatchToProps = {
handleCloseAbout : roomActions.setAboutOpen
};
export default withRoomContext(connect(
mapStateToProps,
mapDispatchToProps,
null,
{
areStatesEqual : (next, prev) =>
{
return (
prev.room.aboutOpen === next.room.aboutOpen
);
}
}
)(withStyles(styles)(About)));

View File

@ -0,0 +1,341 @@
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { meProducersSelector } from '../Selectors';
import { withStyles } from '@material-ui/core/styles';
import useMediaQuery from '@material-ui/core/useMediaQuery';
import classnames from 'classnames';
import * as appPropTypes from '../appPropTypes';
import { withRoomContext } from '../../RoomContext';
import { useIntl } from 'react-intl';
import Fab from '@material-ui/core/Fab';
import Tooltip from '@material-ui/core/Tooltip';
import MicIcon from '@material-ui/icons/Mic';
import MicOffIcon from '@material-ui/icons/MicOff';
import VideoIcon from '@material-ui/icons/Videocam';
import VideoOffIcon from '@material-ui/icons/VideocamOff';
import ScreenIcon from '@material-ui/icons/ScreenShare';
import ScreenOffIcon from '@material-ui/icons/StopScreenShare';
const styles = (theme) =>
({
root :
{
position : 'fixed',
display : 'flex',
zIndex : 30,
[theme.breakpoints.up('md')] :
{
top : '50%',
transform : 'translate(0%, -50%)',
flexDirection : 'column',
justifyContent : 'center',
alignItems : 'center',
left : theme.spacing(1)
},
[theme.breakpoints.down('sm')] :
{
flexDirection : 'row',
bottom : theme.spacing(1),
left : '50%',
transform : 'translate(-50%, -0%)'
}
},
fab :
{
margin : theme.spacing(1)
},
show :
{
opacity : 1,
transition : 'opacity .5s'
},
hide :
{
opacity : 0,
transition : 'opacity .5s'
},
move :
{
left : '30vw',
top : '50%',
transform : 'translate(0%, -50%)',
flexDirection : 'column',
justifyContent : 'center',
alignItems : 'center',
[theme.breakpoints.down('lg')] :
{
left : '40vw'
},
[theme.breakpoints.down('md')] :
{
left : '50vw'
},
[theme.breakpoints.down('sm')] :
{
left : '70vw'
},
[theme.breakpoints.down('xs')] :
{
left : '90vw'
}
}
});
const ButtonControlBar = (props) =>
{
const intl = useIntl();
const {
roomClient,
toolbarsVisible,
hiddenControls,
drawerOverlayed,
toolAreaOpen,
me,
micProducer,
webcamProducer,
screenProducer,
classes,
theme
} = props;
let micState;
let micTip;
if (!me.canSendMic)
{
micState = 'unsupported';
micTip = intl.formatMessage({
id : 'device.audioUnsupported',
defaultMessage : 'Audio unsupported'
});
}
else if (!micProducer)
{
micState = 'off';
micTip = intl.formatMessage({
id : 'device.activateAudio',
defaultMessage : 'Activate audio'
});
}
else if (!micProducer.locallyPaused && !micProducer.remotelyPaused)
{
micState = 'on';
micTip = intl.formatMessage({
id : 'device.muteAudio',
defaultMessage : 'Mute audio'
});
}
else
{
micState = 'muted';
micTip = intl.formatMessage({
id : 'device.unMuteAudio',
defaultMessage : 'Unmute audio'
});
}
let webcamState;
let webcamTip;
if (!me.canSendWebcam)
{
webcamState = 'unsupported';
webcamTip = intl.formatMessage({
id : 'device.videoUnsupported',
defaultMessage : 'Video unsupported'
});
}
else if (webcamProducer)
{
webcamState = 'on';
webcamTip = intl.formatMessage({
id : 'device.stopVideo',
defaultMessage : 'Stop video'
});
}
else
{
webcamState = 'off';
webcamTip = intl.formatMessage({
id : 'device.startVideo',
defaultMessage : 'Start video'
});
}
let screenState;
let screenTip;
if (!me.canShareScreen)
{
screenState = 'unsupported';
screenTip = intl.formatMessage({
id : 'device.screenSharingUnsupported',
defaultMessage : 'Screen sharing not supported'
});
}
else if (screenProducer)
{
screenState = 'on';
screenTip = intl.formatMessage({
id : 'device.stopScreenSharing',
defaultMessage : 'Stop screen sharing'
});
}
else
{
screenState = 'off';
screenTip = intl.formatMessage({
id : 'device.startScreenSharing',
defaultMessage : 'Start screen sharing'
});
}
const smallScreen = useMediaQuery(theme.breakpoints.down('sm'));
return (
<div
className={
classnames(
classes.root,
hiddenControls ?
(toolbarsVisible ? classes.show : classes.hide) :
classes.show,
toolAreaOpen &&
(me.browser.platform !== 'mobile' && !drawerOverlayed) ?
classes.move : null
)
}
>
<Tooltip title={micTip} placement={smallScreen ? 'top' : 'right'}>
<Fab
aria-label={intl.formatMessage({
id : 'device.muteAudio',
defaultMessage : 'Mute audio'
})}
className={classes.fab}
disabled={!me.canSendMic || me.audioInProgress}
color={micState === 'on' ? 'default' : 'secondary'}
size={smallScreen ? 'large' : 'medium'}
onClick={() =>
{
if (micState === 'off')
roomClient.updateMic({ start: true });
else if (micState === 'on')
roomClient.muteMic();
else
roomClient.unmuteMic();
}}
>
{ micState === 'on' ?
<MicIcon />
:
<MicOffIcon />
}
</Fab>
</Tooltip>
<Tooltip title={webcamTip} placement={smallScreen ? 'top' : 'right'}>
<Fab
aria-label={intl.formatMessage({
id : 'device.startVideo',
defaultMessage : 'Start video'
})}
className={classes.fab}
disabled={!me.canSendWebcam || me.webcamInProgress}
color={webcamState === 'on' ? 'default' : 'secondary'}
size={smallScreen ? 'large' : 'medium'}
onClick={() =>
{
webcamState === 'on' ?
roomClient.disableWebcam() :
roomClient.updateWebcam({ start: true });
}}
>
{ webcamState === 'on' ?
<VideoIcon />
:
<VideoOffIcon />
}
</Fab>
</Tooltip>
{ me.browser.platform !== 'mobile' &&
<Tooltip title={screenTip} placement={smallScreen ? 'top' : 'right'}>
<Fab
aria-label={intl.formatMessage({
id : 'device.startScreenSharing',
defaultMessage : 'Start screen sharing'
})}
className={classes.fab}
disabled={!me.canShareScreen || me.screenShareInProgress}
color={screenState === 'on' ? 'primary' : 'default'}
size={smallScreen ? 'large' : 'medium'}
onClick={() =>
{
if (screenState === 'off')
roomClient.updateScreenSharing({ start: true });
else if (screenState === 'on')
roomClient.disableScreenSharing();
}}
>
{ screenState === 'on' || screenState === 'unsupported' ?
<ScreenOffIcon/>
:null
}
{ screenState === 'off' ?
<ScreenIcon/>
:null
}
</Fab>
</Tooltip>
}
</div>
);
};
ButtonControlBar.propTypes =
{
roomClient : PropTypes.any.isRequired,
toolbarsVisible : PropTypes.bool.isRequired,
hiddenControls : PropTypes.bool.isRequired,
drawerOverlayed : PropTypes.bool.isRequired,
toolAreaOpen : PropTypes.bool.isRequired,
me : appPropTypes.Me.isRequired,
micProducer : appPropTypes.Producer,
webcamProducer : appPropTypes.Producer,
screenProducer : appPropTypes.Producer,
classes : PropTypes.object.isRequired,
theme : PropTypes.object.isRequired
};
const mapStateToProps = (state) =>
({
toolbarsVisible : state.room.toolbarsVisible,
hiddenControls : state.settings.hiddenControls,
drawerOverlayed : state.settings.drawerOverlayed,
toolAreaOpen : state.toolarea.toolAreaOpen,
...meProducersSelector(state),
me : state.me
});
export default withRoomContext(connect(
mapStateToProps,
null,
null,
{
areStatesEqual : (next, prev) =>
{
return (
prev.room.toolbarsVisible === next.room.toolbarsVisible &&
prev.settings.hiddenControls === next.settings.hiddenControls &&
prev.settings.drawerOverlayed === next.settings.drawerOverlayed &&
prev.toolarea.toolAreaOpen === next.toolarea.toolAreaOpen &&
prev.producers === next.producers &&
prev.me === next.me
);
}
}
)(withStyles(styles, { withTheme: true })(ButtonControlBar)));

View File

@ -0,0 +1,167 @@
import React from 'react';
import { connect } from 'react-redux';
import { withStyles } from '@material-ui/core/styles';
import { withRoomContext } from '../../RoomContext';
import * as roomActions from '../../actions/roomActions';
import PropTypes from 'prop-types';
import { useIntl, FormattedMessage } from 'react-intl';
import Dialog from '@material-ui/core/Dialog';
import DialogTitle from '@material-ui/core/DialogTitle';
import DialogActions from '@material-ui/core/DialogActions';
import Button from '@material-ui/core/Button';
import MenuItem from '@material-ui/core/MenuItem';
import FormHelperText from '@material-ui/core/FormHelperText';
import FormControl from '@material-ui/core/FormControl';
import Select from '@material-ui/core/Select';
const styles = (theme) =>
({
dialogPaper :
{
width : '30vw',
[theme.breakpoints.down('lg')] :
{
width : '40vw'
},
[theme.breakpoints.down('md')] :
{
width : '50vw'
},
[theme.breakpoints.down('sm')] :
{
width : '70vw'
},
[theme.breakpoints.down('xs')] :
{
width : '90vw'
}
},
setting :
{
padding : theme.spacing(2)
},
formControl :
{
display : 'flex'
}
});
const ExtraVideo = ({
roomClient,
extraVideoOpen,
webcamDevices,
handleCloseExtraVideo,
classes
}) =>
{
const intl = useIntl();
const [ videoDevice, setVideoDevice ] = React.useState('');
const handleChange = (event) =>
{
setVideoDevice(event.target.value);
};
let videoDevices;
if (webcamDevices)
videoDevices = Object.values(webcamDevices);
else
videoDevices = [];
return (
<Dialog
open={extraVideoOpen}
onClose={() => handleCloseExtraVideo(false)}
classes={{
paper : classes.dialogPaper
}}
>
<DialogTitle id='form-dialog-title'>
<FormattedMessage
id='room.extraVideo'
defaultMessage='Extra video'
/>
</DialogTitle>
<form className={classes.setting} autoComplete='off'>
<FormControl className={classes.formControl}>
<Select
value={videoDevice}
displayEmpty
name={intl.formatMessage({
id : 'settings.camera',
defaultMessage : 'Camera'
})}
autoWidth
className={classes.selectEmpty}
disabled={videoDevices.length === 0}
onChange={handleChange}
>
{ videoDevices.map((webcam, index) =>
{
return (
<MenuItem key={index} value={webcam.deviceId}>{webcam.label}</MenuItem>
);
})}
</Select>
<FormHelperText>
{ videoDevices.length > 0 ?
intl.formatMessage({
id : 'settings.selectCamera',
defaultMessage : 'Select video device'
})
:
intl.formatMessage({
id : 'settings.cantSelectCamera',
defaultMessage : 'Unable to select video device'
})
}
</FormHelperText>
</FormControl>
</form>
<DialogActions>
<Button onClick={() => roomClient.addExtraVideo(videoDevice)} color='primary'>
<FormattedMessage
id='label.addVideo'
defaultMessage='Add video'
/>
</Button>
</DialogActions>
</Dialog>
);
};
ExtraVideo.propTypes =
{
roomClient : PropTypes.object.isRequired,
extraVideoOpen : PropTypes.bool.isRequired,
webcamDevices : PropTypes.object,
handleCloseExtraVideo : PropTypes.func.isRequired,
classes : PropTypes.object.isRequired
};
const mapStateToProps = (state) =>
({
webcamDevices : state.me.webcamDevices,
extraVideoOpen : state.room.extraVideoOpen
});
const mapDispatchToProps = {
handleCloseExtraVideo : roomActions.setExtraVideoOpen
};
export default withRoomContext(connect(
mapStateToProps,
mapDispatchToProps,
null,
{
areStatesEqual : (next, prev) =>
{
return (
prev.me.webcamDevices === next.me.webcamDevices &&
prev.room.extraVideoOpen === next.room.extraVideoOpen
);
}
}
)(withStyles(styles)(ExtraVideo)));

View File

@ -0,0 +1,167 @@
import React from 'react';
import { connect } from 'react-redux';
import { withStyles } from '@material-ui/core/styles';
import { withRoomContext } from '../../RoomContext';
import * as roomActions from '../../actions/roomActions';
import PropTypes from 'prop-types';
import { useIntl, FormattedMessage } from 'react-intl';
import Dialog from '@material-ui/core/Dialog';
import DialogTitle from '@material-ui/core/DialogTitle';
import DialogActions from '@material-ui/core/DialogActions';
import Button from '@material-ui/core/Button';
import Paper from '@material-ui/core/Paper';
import Tabs from '@material-ui/core/Tabs';
import Tab from '@material-ui/core/Tab';
const shortcuts=[
{ key: 'h', label: 'room.help', defaultMessage: 'Help' },
{ key: 'm', label: 'device.muteAudio', defaultMessage: 'Mute Audio' },
{ key: 'v', label: 'device.stopVideo', defaultMessage: 'Mute Video' },
{ key: '1', label: 'label.democratic', defaultMessage: 'Democratic View' },
{ key: '2', label: 'label.filmstrip', defaultMessage: 'Filmstrip View' },
{ key: 'space', label: 'me.mutedPTT', defaultMessage: 'Push SPACE to talk' },
{ key: 'a', label: 'label.advanced', defaultMessage: 'Show advanced information' },
{ key: `${String.fromCharCode(8592)} ${String.fromCharCode(8594)}`, label: 'room.browsePeersSpotlight', defaultMessage: 'Browse participants into Spotlight' }
];
const styles = (theme) =>
({
dialogPaper :
{
width : '30vw',
[theme.breakpoints.down('lg')] :
{
width : '40vw'
},
[theme.breakpoints.down('md')] :
{
width : '50vw'
},
[theme.breakpoints.down('sm')] :
{
width : '70vw'
},
[theme.breakpoints.down('xs')] :
{
width : '90vw'
},
display : 'flex',
flexDirection : 'column'
},
paper : {
padding : theme.spacing(1),
textAlign : 'center',
color : theme.palette.text.secondary,
whiteSpace : 'nowrap',
marginRight : theme.spacing(3),
marginBottom : theme.spacing(1),
minWidth : theme.spacing(8)
},
shortcuts : {
display : 'flex',
flexDirection : 'row',
alignItems : 'center',
paddingLeft : theme.spacing(2),
paddingRight : theme.spacing(2)
},
tabsHeader :
{
flexGrow : 1,
marginBottom : theme.spacing(1)
}
});
const Help = ({
helpOpen,
handleCloseHelp,
classes
}) =>
{
const intl = useIntl();
return (
<Dialog
open={helpOpen}
onClose={() => { handleCloseHelp(false); }}
classes={{
paper : classes.dialogPaper
}}
>
<DialogTitle id='form-dialog-title'>
<FormattedMessage
id='room.help'
defaultMessage='Help'
/>
</DialogTitle>
<Tabs
value={0}
className={classes.tabsHeader}
indicatorColor='primary'
textColor='primary'
variant='fullWidth'
>
<Tab
label={
intl.formatMessage({
id : 'room.shortcutKeys',
defaultMessage : 'Shortcut keys'
})
}
/>
</Tabs>
{shortcuts.map((value, index) =>
{
return (
<div key={index} className={classes.shortcuts}>
<Paper className={classes.paper}>
{value.key}
</Paper>
<FormattedMessage
id={value.label}
defaultMessage={value.defaultMessage}
/>
</div>
);
})}
<DialogActions>
<Button onClick={() => { handleCloseHelp(false); }} color='primary'>
<FormattedMessage
id='label.close'
defaultMessage='Close'
/>
</Button>
</DialogActions>
</Dialog>
);
};
Help.propTypes =
{
roomClient : PropTypes.object.isRequired,
helpOpen : PropTypes.bool.isRequired,
handleCloseHelp : PropTypes.func.isRequired,
classes : PropTypes.object.isRequired
};
const mapStateToProps = (state) =>
({
helpOpen : state.room.helpOpen
});
const mapDispatchToProps = {
handleCloseHelp : roomActions.setHelpOpen
};
export default withRoomContext(connect(
mapStateToProps,
mapDispatchToProps,
null,
{
areStatesEqual : (next, prev) =>
{
return (
prev.room.helpOpen === next.room.helpOpen
);
}
}
)(withStyles(styles)(Help)));

View File

@ -1,23 +1,32 @@
import React from 'react'; import React, { useState } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { import {
lobbyPeersKeySelector, lobbyPeersKeySelector,
peersLengthSelector peersLengthSelector,
raisedHandsSelector,
makePermissionSelector
} from '../Selectors'; } from '../Selectors';
import { permissions } from '../../permissions';
import * as appPropTypes from '../appPropTypes'; import * as appPropTypes from '../appPropTypes';
import { withRoomContext } from '../../RoomContext'; import { withRoomContext } from '../../RoomContext';
import { withStyles } from '@material-ui/core/styles'; import { withStyles } from '@material-ui/core/styles';
import * as roomActions from '../../actions/roomActions'; import * as roomActions from '../../actions/roomActions';
import * as toolareaActions from '../../actions/toolareaActions'; import * as toolareaActions from '../../actions/toolareaActions';
import { useIntl, FormattedMessage } from 'react-intl'; import { useIntl, FormattedMessage } from 'react-intl';
import classnames from 'classnames';
import AppBar from '@material-ui/core/AppBar'; import AppBar from '@material-ui/core/AppBar';
import Toolbar from '@material-ui/core/Toolbar'; import Toolbar from '@material-ui/core/Toolbar';
import MenuItem from '@material-ui/core/MenuItem';
import Menu from '@material-ui/core/Menu';
import Popover from '@material-ui/core/Popover';
import Typography from '@material-ui/core/Typography'; import Typography from '@material-ui/core/Typography';
import IconButton from '@material-ui/core/IconButton'; import IconButton from '@material-ui/core/IconButton';
import MenuIcon from '@material-ui/icons/Menu'; import MenuIcon from '@material-ui/icons/Menu';
import Avatar from '@material-ui/core/Avatar'; import Avatar from '@material-ui/core/Avatar';
import Badge from '@material-ui/core/Badge'; import Badge from '@material-ui/core/Badge';
import Paper from '@material-ui/core/Paper';
import ExtensionIcon from '@material-ui/icons/Extension';
import AccountCircle from '@material-ui/icons/AccountCircle'; import AccountCircle from '@material-ui/icons/AccountCircle';
import FullScreenIcon from '@material-ui/icons/Fullscreen'; import FullScreenIcon from '@material-ui/icons/Fullscreen';
import FullScreenExitIcon from '@material-ui/icons/FullscreenExit'; import FullScreenExitIcon from '@material-ui/icons/FullscreenExit';
@ -26,11 +35,40 @@ import SecurityIcon from '@material-ui/icons/Security';
import PeopleIcon from '@material-ui/icons/People'; import PeopleIcon from '@material-ui/icons/People';
import LockIcon from '@material-ui/icons/Lock'; import LockIcon from '@material-ui/icons/Lock';
import LockOpenIcon from '@material-ui/icons/LockOpen'; import LockOpenIcon from '@material-ui/icons/LockOpen';
import VideoCallIcon from '@material-ui/icons/VideoCall';
import Button from '@material-ui/core/Button'; import Button from '@material-ui/core/Button';
import Tooltip from '@material-ui/core/Tooltip'; import Tooltip from '@material-ui/core/Tooltip';
import MoreIcon from '@material-ui/icons/MoreVert';
import HelpIcon from '@material-ui/icons/Help';
import InfoIcon from '@material-ui/icons/Info';
const styles = (theme) => const styles = (theme) =>
({ ({
persistentDrawerOpen :
{
width : 'calc(100% - 30vw)',
marginLeft : '30vw',
[theme.breakpoints.down('lg')] :
{
width : 'calc(100% - 40vw)',
marginLeft : '40vw'
},
[theme.breakpoints.down('md')] :
{
width : 'calc(100% - 50vw)',
marginLeft : '50vw'
},
[theme.breakpoints.down('sm')] :
{
width : 'calc(100% - 70vw)',
marginLeft : '70vw'
},
[theme.breakpoints.down('xs')] :
{
width : 'calc(100% - 90vw)',
marginLeft : '90vw'
}
},
menuButton : menuButton :
{ {
margin : 0, margin : 0,
@ -72,14 +110,34 @@ const styles = (theme) =>
display : 'block' display : 'block'
} }
}, },
actionButtons : sectionDesktop : {
{ display : 'none',
[theme.breakpoints.up('md')] : {
display : 'flex' display : 'flex'
}
},
sectionMobile : {
display : 'flex',
[theme.breakpoints.up('md')] : {
display : 'none'
}
}, },
actionButton : actionButton :
{ {
margin : theme.spacing(1, 0), margin : theme.spacing(1, 0),
padding : theme.spacing(0, 1) padding : theme.spacing(0, 1)
},
disabledButton :
{
margin : theme.spacing(1, 0)
},
green :
{
color : 'rgba(0, 153, 0, 1)'
},
moreAction :
{
margin : theme.spacing(0.5, 0, 0.5, 1.5)
} }
}); });
@ -118,12 +176,47 @@ const TopBar = (props) =>
{ {
const intl = useIntl(); const intl = useIntl();
const [ mobileMoreAnchorEl, setMobileMoreAnchorEl ] = useState(null);
const [ anchorEl, setAnchorEl ] = useState(null);
const [ currentMenu, setCurrentMenu ] = useState(null);
const handleExited = () =>
{
setCurrentMenu(null);
};
const handleMobileMenuOpen = (event) =>
{
setMobileMoreAnchorEl(event.currentTarget);
};
const handleMobileMenuClose = () =>
{
setMobileMoreAnchorEl(null);
};
const handleMenuOpen = (event, menu) =>
{
setAnchorEl(event.currentTarget);
setCurrentMenu(menu);
};
const handleMenuClose = () =>
{
setAnchorEl(null);
handleMobileMenuClose();
};
const { const {
roomClient, roomClient,
room, room,
peersLength, peersLength,
lobbyPeers, lobbyPeers,
permanentTopBar, permanentTopBar,
drawerOverlayed,
toolAreaOpen,
isMobile,
myPicture, myPicture,
loggedIn, loggedIn,
loginEnabled, loginEnabled,
@ -131,13 +224,22 @@ const TopBar = (props) =>
fullscreen, fullscreen,
onFullscreen, onFullscreen,
setSettingsOpen, setSettingsOpen,
setExtraVideoOpen,
setHelpOpen,
setAboutOpen,
setLockDialogOpen, setLockDialogOpen,
toggleToolArea, toggleToolArea,
openUsersTab, openUsersTab,
unread, unread,
canProduceExtraVideo,
canLock,
canPromote,
classes classes
} = props; } = props;
const isMenuOpen = Boolean(anchorEl);
const isMobileMenuOpen = Boolean(mobileMoreAnchorEl);
const lockTooltip = room.locked ? const lockTooltip = room.locked ?
intl.formatMessage({ intl.formatMessage({
id : 'tooltip.unLockRoom', id : 'tooltip.unLockRoom',
@ -172,9 +274,15 @@ const TopBar = (props) =>
}); });
return ( return (
<React.Fragment>
<AppBar <AppBar
position='fixed' position='fixed'
className={room.toolbarsVisible || permanentTopBar ? classes.show : classes.hide} className={classnames(
room.toolbarsVisible || permanentTopBar ?
classes.show : classes.hide,
!(isMobile || drawerOverlayed) && toolAreaOpen ?
classes.persistentDrawerOpen : null
)}
> >
<Toolbar> <Toolbar>
<PulsingBadge <PulsingBadge
@ -193,17 +301,36 @@ const TopBar = (props) =>
<MenuIcon /> <MenuIcon />
</IconButton> </IconButton>
</PulsingBadge> </PulsingBadge>
{ window.config && window.config.logo && <img alt='Logo' className={classes.logo} src={window.config.logo} /> } { window.config.logo && <img alt='Logo' className={classes.logo} src={window.config.logo} /> }
<Typography <Typography
className={classes.title} className={classes.title}
variant='h6' variant='h6'
color='inherit' color='inherit'
noWrap noWrap
> >
{ window.config && window.config.title ? window.config.title : 'Multiparty meeting' } { window.config.title ? window.config.title : 'Multiparty meeting' }
</Typography> </Typography>
<div className={classes.grow} /> <div className={classes.grow} />
<div className={classes.actionButtons}> <div className={classes.sectionDesktop}>
<Tooltip
title={intl.formatMessage({
id : 'label.moreActions',
defaultMessage : 'More actions'
})}
>
<IconButton
aria-owns={
isMenuOpen &&
currentMenu === 'moreActions' ?
'material-appbar' : undefined
}
aria-haspopup
onClick={(event) => handleMenuOpen(event, 'moreActions')}
color='inherit'
>
<ExtensionIcon />
</IconButton>
</Tooltip>
{ fullscreenEnabled && { fullscreenEnabled &&
<Tooltip title={fullscreenTooltip}> <Tooltip title={fullscreenTooltip}>
<IconButton <IconButton
@ -264,6 +391,7 @@ const TopBar = (props) =>
</IconButton> </IconButton>
</Tooltip> </Tooltip>
<Tooltip title={lockTooltip}> <Tooltip title={lockTooltip}>
<span className={classes.disabledButton}>
<IconButton <IconButton
aria-label={intl.formatMessage({ aria-label={intl.formatMessage({
id : 'tooltip.lockRoom', id : 'tooltip.lockRoom',
@ -271,6 +399,7 @@ const TopBar = (props) =>
})} })}
className={classes.actionButton} className={classes.actionButton}
color='inherit' color='inherit'
disabled={!canLock}
onClick={() => onClick={() =>
{ {
if (room.locked) if (room.locked)
@ -289,6 +418,7 @@ const TopBar = (props) =>
<LockOpenIcon /> <LockOpenIcon />
} }
</IconButton> </IconButton>
</span>
</Tooltip> </Tooltip>
{ lobbyPeers.length > 0 && { lobbyPeers.length > 0 &&
<Tooltip <Tooltip
@ -297,12 +427,15 @@ const TopBar = (props) =>
defaultMessage : 'Show lobby' defaultMessage : 'Show lobby'
})} })}
> >
<span className={classes.disabledButton}>
<IconButton <IconButton
aria-label={intl.formatMessage({ aria-label={intl.formatMessage({
id : 'tooltip.lobby', id : 'tooltip.lobby',
defaultMessage : 'Show lobby' defaultMessage : 'Show lobby'
})} })}
className={classes.actionButton}
color='inherit' color='inherit'
disabled={!canPromote}
onClick={() => setLockDialogOpen(!room.lockDialogOpen)} onClick={() => setLockDialogOpen(!room.lockDialogOpen)}
> >
<PulsingBadge <PulsingBadge
@ -312,6 +445,7 @@ const TopBar = (props) =>
<SecurityIcon /> <SecurityIcon />
</PulsingBadge> </PulsingBadge>
</IconButton> </IconButton>
</span>
</Tooltip> </Tooltip>
} }
{ loginEnabled && { loginEnabled &&
@ -331,11 +465,49 @@ const TopBar = (props) =>
{ myPicture ? { myPicture ?
<Avatar src={myPicture} /> <Avatar src={myPicture} />
: :
<AccountCircle /> <AccountCircle className={loggedIn ? classes.green : null} />
} }
</IconButton> </IconButton>
</Tooltip> </Tooltip>
} }
</div>
<div className={classes.sectionMobile}>
{ lobbyPeers.length > 0 &&
<Tooltip
title={intl.formatMessage({
id : 'tooltip.lobby',
defaultMessage : 'Show lobby'
})}
>
<span className={classes.disabledButton}>
<IconButton
aria-label={intl.formatMessage({
id : 'tooltip.lobby',
defaultMessage : 'Show lobby'
})}
className={classes.actionButton}
color='inherit'
disabled={!canPromote}
onClick={() => setLockDialogOpen(!room.lockDialogOpen)}
>
<PulsingBadge
color='secondary'
badgeContent={lobbyPeers.length}
>
<SecurityIcon />
</PulsingBadge>
</IconButton>
</span>
</Tooltip>
}
<IconButton
aria-haspopup
onClick={handleMobileMenuOpen}
color='inherit'
>
<MoreIcon />
</IconButton>
</div>
<div className={classes.divider} /> <div className={classes.divider} />
<Button <Button
aria-label={intl.formatMessage({ aria-label={intl.formatMessage({
@ -352,9 +524,245 @@ const TopBar = (props) =>
defaultMessage='Leave' defaultMessage='Leave'
/> />
</Button> </Button>
</div>
</Toolbar> </Toolbar>
</AppBar> </AppBar>
<Popover
anchorEl={anchorEl}
anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }}
transformOrigin={{ vertical: 'top', horizontal: 'left' }}
open={isMenuOpen}
onClose={handleMenuClose}
onExited={handleExited}
getContentAnchorEl={null}
>
{ currentMenu === 'moreActions' &&
<Paper>
<MenuItem
disabled={!canProduceExtraVideo}
onClick={() =>
{
handleMenuClose();
setExtraVideoOpen(!room.extraVideoOpen);
}}
>
<VideoCallIcon
aria-label={intl.formatMessage({
id : 'label.addVideo',
defaultMessage : 'Add video'
})}
/>
<p className={classes.moreAction}>
<FormattedMessage
id='label.addVideo'
defaultMessage='Add video'
/>
</p>
</MenuItem>
<MenuItem
onClick={() =>
{
handleMenuClose();
setHelpOpen(!room.helpOpen);
}}
>
<HelpIcon
aria-label={intl.formatMessage({
id : 'room.help',
defaultMessage : 'Help'
})}
/>
<p className={classes.moreAction}>
<FormattedMessage
id='room.help'
defaultMessage='Help'
/>
</p>
</MenuItem>
<MenuItem
onClick={() =>
{
handleMenuClose();
setAboutOpen(!room.aboutOpen);
}}
>
<InfoIcon
aria-label={intl.formatMessage({
id : 'room.about',
defaultMessage : 'About'
})}
/>
<p className={classes.moreAction}>
<FormattedMessage
id='room.about'
defaultMessage='About'
/>
</p>
</MenuItem>
</Paper>
}
</Popover>
<Menu
anchorEl={mobileMoreAnchorEl}
anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }}
transformOrigin={{ vertical: 'bottom', horizontal: 'right' }}
open={isMobileMenuOpen}
onClose={handleMenuClose}
getContentAnchorEl={null}
>
{ loginEnabled &&
<MenuItem
aria-label={loginTooltip}
onClick={() =>
{
handleMenuClose();
loggedIn ? roomClient.logout() : roomClient.login();
}}
>
{ myPicture ?
<Avatar src={myPicture} />
:
<AccountCircle className={loggedIn ? classes.green : null} />
}
{ loggedIn ?
<p className={classes.moreAction}>
<FormattedMessage
id='tooltip.logout'
defaultMessage='Log out'
/>
</p>
:
<p className={classes.moreAction}>
<FormattedMessage
id='tooltip.login'
defaultMessage='Log in'
/>
</p>
}
</MenuItem>
}
<MenuItem
aria-label={lockTooltip}
disabled={!canLock}
onClick={() =>
{
handleMenuClose();
if (room.locked)
{
roomClient.unlockRoom();
}
else
{
roomClient.lockRoom();
}
}}
>
{ room.locked ?
<LockIcon />
:
<LockOpenIcon />
}
{ room.locked ?
<p className={classes.moreAction}>
<FormattedMessage
id='tooltip.unLockRoom'
defaultMessage='Unlock room'
/>
</p>
:
<p className={classes.moreAction}>
<FormattedMessage
id='tooltip.lockRoom'
defaultMessage='Lock room'
/>
</p>
}
</MenuItem>
<MenuItem
aria-label={intl.formatMessage({
id : 'tooltip.settings',
defaultMessage : 'Show settings'
})}
onClick={() =>
{
handleMenuClose();
setSettingsOpen(!room.settingsOpen);
}}
>
<SettingsIcon />
<p className={classes.moreAction}>
<FormattedMessage
id='tooltip.settings'
defaultMessage='Show settings'
/>
</p>
</MenuItem>
<MenuItem
aria-label={intl.formatMessage({
id : 'tooltip.participants',
defaultMessage : 'Show participants'
})}
onClick={() =>
{
handleMenuClose();
openUsersTab();
}}
>
<Badge
color='primary'
badgeContent={peersLength + 1}
>
<PeopleIcon />
</Badge>
<p className={classes.moreAction}>
<FormattedMessage
id='tooltip.participants'
defaultMessage='Show participants'
/>
</p>
</MenuItem>
{ fullscreenEnabled &&
<MenuItem
aria-label={intl.formatMessage({
id : 'tooltip.enterFullscreen',
defaultMessage : 'Enter fullscreen'
})}
onClick={() =>
{
handleMenuClose();
onFullscreen();
}}
>
{ fullscreen ?
<FullScreenExitIcon />
:
<FullScreenIcon />
}
<p className={classes.moreAction}>
<FormattedMessage
id='tooltip.enterFullscreen'
defaultMessage='Enter fullscreen'
/>
</p>
</MenuItem>
}
<MenuItem
aria-label={intl.formatMessage({
id : 'label.moreActions',
defaultMessage : 'Add video'
})}
onClick={(event) => handleMenuOpen(event, 'moreActions')}
>
<ExtensionIcon />
<p className={classes.moreAction}>
<FormattedMessage
id='label.moreActions'
defaultMessage='More actions'
/>
</p>
</MenuItem>
</Menu>
</React.Fragment>
); );
}; };
@ -362,9 +770,12 @@ TopBar.propTypes =
{ {
roomClient : PropTypes.object.isRequired, roomClient : PropTypes.object.isRequired,
room : appPropTypes.Room.isRequired, room : appPropTypes.Room.isRequired,
isMobile : PropTypes.bool.isRequired,
peersLength : PropTypes.number, peersLength : PropTypes.number,
lobbyPeers : PropTypes.array, lobbyPeers : PropTypes.array,
permanentTopBar : PropTypes.bool, permanentTopBar : PropTypes.bool.isRequired,
drawerOverlayed : PropTypes.bool.isRequired,
toolAreaOpen : PropTypes.bool.isRequired,
myPicture : PropTypes.string, myPicture : PropTypes.string,
loggedIn : PropTypes.bool.isRequired, loggedIn : PropTypes.bool.isRequired,
loginEnabled : PropTypes.bool.isRequired, loginEnabled : PropTypes.bool.isRequired,
@ -373,27 +784,53 @@ TopBar.propTypes =
onFullscreen : PropTypes.func.isRequired, onFullscreen : PropTypes.func.isRequired,
setToolbarsVisible : PropTypes.func.isRequired, setToolbarsVisible : PropTypes.func.isRequired,
setSettingsOpen : PropTypes.func.isRequired, setSettingsOpen : PropTypes.func.isRequired,
setExtraVideoOpen : PropTypes.func.isRequired,
setHelpOpen : PropTypes.func.isRequired,
setAboutOpen : PropTypes.func.isRequired,
setLockDialogOpen : PropTypes.func.isRequired, setLockDialogOpen : PropTypes.func.isRequired,
toggleToolArea : PropTypes.func.isRequired, toggleToolArea : PropTypes.func.isRequired,
openUsersTab : PropTypes.func.isRequired, openUsersTab : PropTypes.func.isRequired,
unread : PropTypes.number.isRequired, unread : PropTypes.number.isRequired,
canProduceExtraVideo : PropTypes.bool.isRequired,
canLock : PropTypes.bool.isRequired,
canPromote : PropTypes.bool.isRequired,
classes : PropTypes.object.isRequired, classes : PropTypes.object.isRequired,
theme : PropTypes.object.isRequired theme : PropTypes.object.isRequired
}; };
const makeMapStateToProps = () =>
{
const hasExtraVideoPermission =
makePermissionSelector(permissions.EXTRA_VIDEO);
const hasLockPermission =
makePermissionSelector(permissions.CHANGE_ROOM_LOCK);
const hasPromotionPermission =
makePermissionSelector(permissions.PROMOTE_PEER);
const mapStateToProps = (state) => const mapStateToProps = (state) =>
({ ({
room : state.room, room : state.room,
isMobile : state.me.browser.platform === 'mobile',
peersLength : peersLengthSelector(state), peersLength : peersLengthSelector(state),
lobbyPeers : lobbyPeersKeySelector(state), lobbyPeers : lobbyPeersKeySelector(state),
permanentTopBar : state.settings.permanentTopBar, permanentTopBar : state.settings.permanentTopBar,
drawerOverlayed : state.settings.drawerOverlayed,
toolAreaOpen : state.toolarea.toolAreaOpen,
loggedIn : state.me.loggedIn, loggedIn : state.me.loggedIn,
loginEnabled : state.me.loginEnabled, loginEnabled : state.me.loginEnabled,
myPicture : state.me.picture, myPicture : state.me.picture,
unread : state.toolarea.unreadMessages + unread : state.toolarea.unreadMessages +
state.toolarea.unreadFiles state.toolarea.unreadFiles + raisedHandsSelector(state),
canProduceExtraVideo : hasExtraVideoPermission(state),
canLock : hasLockPermission(state),
canPromote : hasPromotionPermission(state)
}); });
return mapStateToProps;
};
const mapDispatchToProps = (dispatch) => const mapDispatchToProps = (dispatch) =>
({ ({
setToolbarsVisible : (visible) => setToolbarsVisible : (visible) =>
@ -402,11 +839,23 @@ const mapDispatchToProps = (dispatch) =>
}, },
setSettingsOpen : (settingsOpen) => setSettingsOpen : (settingsOpen) =>
{ {
dispatch(roomActions.setSettingsOpen({ settingsOpen })); dispatch(roomActions.setSettingsOpen(settingsOpen));
},
setExtraVideoOpen : (extraVideoOpen) =>
{
dispatch(roomActions.setExtraVideoOpen(extraVideoOpen));
},
setHelpOpen : (helpOpen) =>
{
dispatch(roomActions.setHelpOpen(helpOpen));
},
setAboutOpen : (aboutOpen) =>
{
dispatch(roomActions.setAboutOpen(aboutOpen));
}, },
setLockDialogOpen : (lockDialogOpen) => setLockDialogOpen : (lockDialogOpen) =>
{ {
dispatch(roomActions.setLockDialogOpen({ lockDialogOpen })); dispatch(roomActions.setLockDialogOpen(lockDialogOpen));
}, },
toggleToolArea : () => toggleToolArea : () =>
{ {
@ -420,7 +869,7 @@ const mapDispatchToProps = (dispatch) =>
}); });
export default withRoomContext(connect( export default withRoomContext(connect(
mapStateToProps, makeMapStateToProps,
mapDispatchToProps, mapDispatchToProps,
null, null,
{ {
@ -431,11 +880,15 @@ export default withRoomContext(connect(
prev.peers === next.peers && prev.peers === next.peers &&
prev.lobbyPeers === next.lobbyPeers && prev.lobbyPeers === next.lobbyPeers &&
prev.settings.permanentTopBar === next.settings.permanentTopBar && prev.settings.permanentTopBar === next.settings.permanentTopBar &&
prev.settings.drawerOverlayed === next.settings.drawerOverlayed &&
prev.me.loggedIn === next.me.loggedIn && prev.me.loggedIn === next.me.loggedIn &&
prev.me.browser === next.me.browser &&
prev.me.loginEnabled === next.me.loginEnabled && prev.me.loginEnabled === next.me.loginEnabled &&
prev.me.picture === next.me.picture && prev.me.picture === next.me.picture &&
prev.me.roles === next.me.roles &&
prev.toolarea.unreadMessages === next.toolarea.unreadMessages && prev.toolarea.unreadMessages === next.toolarea.unreadMessages &&
prev.toolarea.unreadFiles === next.toolarea.unreadFiles prev.toolarea.unreadFiles === next.toolarea.unreadFiles &&
prev.toolarea.toolAreaOpen === next.toolarea.toolAreaOpen
); );
} }
} }

View File

@ -2,6 +2,7 @@ import React, { useState, useEffect } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { withStyles } from '@material-ui/core/styles'; import { withStyles } from '@material-ui/core/styles';
import { withRoomContext } from '../RoomContext'; import { withRoomContext } from '../RoomContext';
import classnames from 'classnames';
import isElectron from 'is-electron'; import isElectron from 'is-electron';
import * as settingsActions from '../actions/settingsActions'; import * as settingsActions from '../actions/settingsActions';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
@ -82,6 +83,10 @@ const styles = (theme) =>
green : green :
{ {
color : 'rgba(0, 153, 0, 1)' color : 'rgba(0, 153, 0, 1)'
},
red :
{
color : 'rgba(153, 0, 0, 1)'
} }
}); });
@ -103,7 +108,7 @@ const DialogTitle = withStyles(styles)((props) =>
}; };
}, []); }, []);
const { children, classes, myPicture, onLogin, ...other } = props; const { children, classes, myPicture, onLogin, loggedIn, ...other } = props;
const handleTooltipClose = () => const handleTooltipClose = () =>
{ {
@ -115,19 +120,27 @@ const DialogTitle = withStyles(styles)((props) =>
setOpen(true); setOpen(true);
}; };
const loginTooltip = loggedIn ?
intl.formatMessage({
id : 'tooltip.logout',
defaultMessage : 'Log out'
})
:
intl.formatMessage({
id : 'tooltip.login',
defaultMessage : 'Log in'
});
return ( return (
<MuiDialogTitle disableTypography className={classes.dialogTitle} {...other}> <MuiDialogTitle disableTypography className={classes.dialogTitle} {...other}>
{ window.config && window.config.logo && <img alt='Logo' className={classes.logo} src={window.config.logo} /> } { window.config.logo && <img alt='Logo' className={classes.logo} src={window.config.logo} /> }
<Typography variant='h5'>{children}</Typography> <Typography variant='h5'>{children}</Typography>
{ window.config && window.config.loginEnabled && { window.config.loginEnabled &&
<Tooltip <Tooltip
onClose={handleTooltipClose} onClose={handleTooltipClose}
onOpen={handleTooltipOpen} onOpen={handleTooltipOpen}
open={open} open={open}
title={intl.formatMessage({ title={loginTooltip}
id : 'tooltip.login',
defaultMessage : 'Click to log in'
})}
placement='left' placement='left'
> >
<IconButton <IconButton
@ -139,7 +152,9 @@ const DialogTitle = withStyles(styles)((props) =>
{ myPicture ? { myPicture ?
<Avatar src={myPicture} className={classes.largeAvatar} /> <Avatar src={myPicture} className={classes.largeAvatar} />
: :
<AccountCircle className={classes.largeIcon} /> <AccountCircle
className={classnames(classes.largeIcon, loggedIn ? classes.green : null)}
/>
} }
</IconButton> </IconButton>
</Tooltip> </Tooltip>
@ -193,10 +208,11 @@ const JoinDialog = ({
myPicture={myPicture} myPicture={myPicture}
onLogin={() => onLogin={() =>
{ {
loggedIn ? roomClient.logout() : roomClient.login(); loggedIn ? roomClient.logout(roomId) : roomClient.login(roomId);
}} }}
loggedIn={loggedIn}
> >
{ window.config && window.config.title ? window.config.title : 'Multiparty meeting' } { window.config.title ? window.config.title : 'Multiparty meeting' }
<hr /> <hr />
</DialogTitle> </DialogTitle>
<DialogContent> <DialogContent>
@ -253,6 +269,16 @@ const JoinDialog = ({
}} }}
fullWidth fullWidth
/> />
{!room.inLobby && room.overRoomLimit &&
<DialogContentText className={classes.red} variant='h6' gutterBottom>
<FormattedMessage
id='room.overRoomLimit'
defaultMessage={
'The room is full, retry after some time.'
}
/>
</DialogContentText>
}
</DialogContent> </DialogContent>
@ -291,6 +317,7 @@ const JoinDialog = ({
className={classes.green} className={classes.green}
gutterBottom gutterBottom
variant='h6' variant='h6'
style={{ fontWeight: '600' }}
align='center' align='center'
> >
<FormattedMessage <FormattedMessage
@ -299,7 +326,11 @@ const JoinDialog = ({
/> />
</DialogContentText> </DialogContentText>
{ room.signInRequired ? { room.signInRequired ?
<DialogContentText gutterBottom> <DialogContentText
gutterBottom
variant='h5'
style={{ fontWeight: '600' }}
>
<FormattedMessage <FormattedMessage
id='room.emptyRequireLogin' id='room.emptyRequireLogin'
defaultMessage={ defaultMessage={
@ -309,7 +340,11 @@ const JoinDialog = ({
/> />
</DialogContentText> </DialogContentText>
: :
<DialogContentText gutterBottom> <DialogContentText
gutterBottom
variant='h5'
style={{ fontWeight: '600' }}
>
<FormattedMessage <FormattedMessage
id='room.locketWait' id='room.locketWait'
defaultMessage='The room is locked - hang on until somebody lets you in ...' defaultMessage='The room is locked - hang on until somebody lets you in ...'
@ -323,7 +358,8 @@ const JoinDialog = ({
<CookieConsent buttonText={intl.formatMessage({ <CookieConsent buttonText={intl.formatMessage({
id : 'room.consentUnderstand', id : 'room.consentUnderstand',
defaultMessage : 'I understand' defaultMessage : 'I understand'
})}> })}
>
<FormattedMessage <FormattedMessage
id='room.cookieConsent' id='room.cookieConsent'
defaultMessage='This website uses cookies to enhance the user experience' defaultMessage='This website uses cookies to enhance the user experience'
@ -381,6 +417,7 @@ export default withRoomContext(connect(
return ( return (
prev.room.inLobby === next.room.inLobby && prev.room.inLobby === next.room.inLobby &&
prev.room.signInRequired === next.room.signInRequired && prev.room.signInRequired === next.room.signInRequired &&
prev.room.overRoomLimit === next.room.overRoomLimit &&
prev.settings.displayName === next.settings.displayName && prev.settings.displayName === next.settings.displayName &&
prev.me.displayNameInProgress === next.me.displayNameInProgress && prev.me.displayNameInProgress === next.me.displayNameInProgress &&
prev.me.loginEnabled === next.me.loginEnabled && prev.me.loginEnabled === next.me.loginEnabled &&

View File

@ -2,6 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { withStyles } from '@material-ui/core/styles'; import { withStyles } from '@material-ui/core/styles';
import Paper from '@material-ui/core/Paper'; import Paper from '@material-ui/core/Paper';
import ChatModerator from './ChatModerator';
import MessageList from './MessageList'; import MessageList from './MessageList';
import ChatInput from './ChatInput'; import ChatInput from './ChatInput';
@ -25,6 +26,7 @@ const Chat = (props) =>
return ( return (
<Paper className={classes.root}> <Paper className={classes.root}>
<ChatModerator />
<MessageList /> <MessageList />
<ChatInput /> <ChatInput />
</Paper> </Paper>

View File

@ -4,6 +4,8 @@ import PropTypes from 'prop-types';
import { withStyles } from '@material-ui/core/styles'; import { withStyles } from '@material-ui/core/styles';
import { withRoomContext } from '../../../RoomContext'; import { withRoomContext } from '../../../RoomContext';
import { useIntl } from 'react-intl'; import { useIntl } from 'react-intl';
import { permissions } from '../../../permissions';
import { makePermissionSelector } from '../../Selectors';
import Paper from '@material-ui/core/Paper'; import Paper from '@material-ui/core/Paper';
import InputBase from '@material-ui/core/InputBase'; import InputBase from '@material-ui/core/InputBase';
import IconButton from '@material-ui/core/IconButton'; import IconButton from '@material-ui/core/IconButton';
@ -54,6 +56,7 @@ const ChatInput = (props) =>
roomClient, roomClient,
displayName, displayName,
picture, picture,
canChat,
classes classes
} = props; } = props;
@ -66,6 +69,7 @@ const ChatInput = (props) =>
defaultMessage : 'Enter chat message...' defaultMessage : 'Enter chat message...'
})} })}
value={message || ''} value={message || ''}
disabled={!canChat}
onChange={handleChange} onChange={handleChange}
onKeyPress={(ev) => onKeyPress={(ev) =>
{ {
@ -89,6 +93,7 @@ const ChatInput = (props) =>
color='primary' color='primary'
className={classes.iconButton} className={classes.iconButton}
aria-label='Send' aria-label='Send'
disabled={!canChat}
onClick={() => onClick={() =>
{ {
if (message && message !== '') if (message && message !== '')
@ -112,24 +117,36 @@ ChatInput.propTypes =
roomClient : PropTypes.object.isRequired, roomClient : PropTypes.object.isRequired,
displayName : PropTypes.string, displayName : PropTypes.string,
picture : PropTypes.string, picture : PropTypes.string,
canChat : PropTypes.bool.isRequired,
classes : PropTypes.object.isRequired classes : PropTypes.object.isRequired
}; };
const makeMapStateToProps = () =>
{
const hasPermission = makePermissionSelector(permissions.SEND_CHAT);
const mapStateToProps = (state) => const mapStateToProps = (state) =>
({ ({
displayName : state.settings.displayName, displayName : state.settings.displayName,
picture : state.me.picture picture : state.me.picture,
canChat : hasPermission(state)
}); });
return mapStateToProps;
};
export default withRoomContext( export default withRoomContext(
connect( connect(
mapStateToProps, makeMapStateToProps,
null, null,
null, null,
{ {
areStatesEqual : (next, prev) => areStatesEqual : (next, prev) =>
{ {
return ( return (
prev.room === next.room &&
prev.me.roles === next.me.roles &&
prev.peers === next.peers &&
prev.settings.displayName === next.settings.displayName && prev.settings.displayName === next.settings.displayName &&
prev.me.picture === next.me.picture prev.me.picture === next.me.picture
); );

View File

@ -0,0 +1,108 @@
import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { withRoomContext } from '../../../RoomContext';
import { withStyles } from '@material-ui/core/styles';
import { useIntl, FormattedMessage } from 'react-intl';
import { permissions } from '../../../permissions';
import { makePermissionSelector } from '../../Selectors';
import Button from '@material-ui/core/Button';
const styles = (theme) =>
({
root :
{
display : 'flex',
padding : theme.spacing(1),
boxShadow : '0 2px 5px 2px rgba(0, 0, 0, 0.2)',
backgroundColor : 'rgba(255, 255, 255, 1)'
},
listheader :
{
padding : theme.spacing(1),
fontWeight : 'bolder'
},
actionButton :
{
marginLeft : 'auto'
}
});
const ChatModerator = (props) =>
{
const intl = useIntl();
const {
roomClient,
isChatModerator,
room,
classes
} = props;
if (!isChatModerator)
return null;
return (
<ul className={classes.root}>
<li className={classes.listheader}>
<FormattedMessage
id='room.moderatoractions'
defaultMessage='Moderator actions'
/>
</li>
<Button
aria-label={intl.formatMessage({
id : 'room.clearChat',
defaultMessage : 'Clear chat'
})}
className={classes.actionButton}
variant='contained'
color='secondary'
disabled={room.clearChatInProgress}
onClick={() => roomClient.clearChat()}
>
<FormattedMessage
id='room.clearChat'
defaultMessage='Clear chat'
/>
</Button>
</ul>
);
};
ChatModerator.propTypes =
{
roomClient : PropTypes.any.isRequired,
isChatModerator : PropTypes.bool,
room : PropTypes.object,
classes : PropTypes.object.isRequired
};
const makeMapStateToProps = () =>
{
const hasPermission = makePermissionSelector(permissions.MODERATE_CHAT);
const mapStateToProps = (state) =>
({
isChatModerator : hasPermission(state),
room : state.room
});
return mapStateToProps;
};
export default withRoomContext(connect(
makeMapStateToProps,
null,
null,
{
areStatesEqual : (next, prev) =>
{
return (
prev.room === next.room &&
prev.me === next.me &&
prev.peers === next.peers
);
}
}
)(withStyles(styles)(ChatModerator)));

View File

@ -6,6 +6,7 @@ import DOMPurify from 'dompurify';
import marked from 'marked'; import marked from 'marked';
import Paper from '@material-ui/core/Paper'; import Paper from '@material-ui/core/Paper';
import Typography from '@material-ui/core/Typography'; import Typography from '@material-ui/core/Typography';
import { useIntl } from 'react-intl';
const linkRenderer = new marked.Renderer(); const linkRenderer = new marked.Renderer();
@ -55,6 +56,8 @@ const styles = (theme) =>
const Message = (props) => const Message = (props) =>
{ {
const intl = useIntl();
const { const {
self, self,
picture, picture,
@ -88,7 +91,16 @@ const Message = (props) =>
} }
) }} ) }}
/> />
<Typography variant='caption'>{self ? 'Me' : name} - {time}</Typography> <Typography variant='caption'>
{ self ?
intl.formatMessage({
id : 'room.me',
defaultMessage : 'Me'
})
:
name
} - {time}
</Typography>
</div> </div>
</Paper> </Paper>
); );

View File

@ -4,7 +4,10 @@ import { connect } from 'react-redux';
import { withStyles } from '@material-ui/core/styles'; import { withStyles } from '@material-ui/core/styles';
import { withRoomContext } from '../../../RoomContext'; import { withRoomContext } from '../../../RoomContext';
import { useIntl } from 'react-intl'; import { useIntl } from 'react-intl';
import { permissions } from '../../../permissions';
import { makePermissionSelector } from '../../Selectors';
import FileList from './FileList'; import FileList from './FileList';
import FileSharingModerator from './FileSharingModerator';
import Paper from '@material-ui/core/Paper'; import Paper from '@material-ui/core/Paper';
import Button from '@material-ui/core/Button'; import Button from '@material-ui/core/Button';
@ -24,6 +27,10 @@ const styles = (theme) =>
button : button :
{ {
margin : theme.spacing(1) margin : theme.spacing(1)
},
shareButtonsWrapper :
{
display : 'flex'
} }
}); });
@ -35,12 +42,14 @@ const FileSharing = (props) =>
{ {
if (event.target.files.length > 0) if (event.target.files.length > 0)
{ {
props.roomClient.shareFiles(event.target.files); await props.roomClient.shareFiles(event.target.files);
} }
}; };
const { const {
canShareFiles, canShareFiles,
browser,
canShare,
classes classes
} = props; } = props;
@ -55,25 +64,61 @@ const FileSharing = (props) =>
defaultMessage : 'File sharing not supported' defaultMessage : 'File sharing not supported'
}); });
const buttonGalleryDescription = canShareFiles ?
intl.formatMessage({
id : 'label.shareGalleryFile',
defaultMessage : 'Share image'
})
:
intl.formatMessage({
id : 'label.fileSharingUnsupported',
defaultMessage : 'File sharing not supported'
});
return ( return (
<Paper className={classes.root}> <Paper className={classes.root}>
<FileSharingModerator />
<div className={classes.shareButtonsWrapper} >
<input <input
className={classes.input} className={classes.input}
type='file' type='file'
disabled={!canShare}
onChange={handleFileChange} onChange={handleFileChange}
// Need to reset to be able to share same file twice
onClick={(e) => (e.target.value = null)}
id='share-files-button' id='share-files-button'
/> />
<input
className={classes.input}
type='file'
disabled={!canShare}
onChange={handleFileChange}
accept='image/*'
id='share-files-gallery-button'
/>
<label htmlFor='share-files-button'> <label htmlFor='share-files-button'>
<Button <Button
variant='contained' variant='contained'
component='span' component='span'
className={classes.button} className={classes.button}
disabled={!canShareFiles} disabled={!canShareFiles || !canShare}
> >
{buttonDescription} {buttonDescription}
</Button> </Button>
</label> </label>
{
(browser.platform === 'mobile') && canShareFiles && canShare && <label htmlFor='share-files-gallery-button'>
<Button
variant='contained'
component='span'
className={classes.button}
disabled={!canShareFiles || !canShare}
>
{buttonGalleryDescription}
</Button>
</label>
}
</div>
<FileList /> <FileList />
</Paper> </Paper>
); );
@ -81,19 +126,45 @@ const FileSharing = (props) =>
FileSharing.propTypes = { FileSharing.propTypes = {
roomClient : PropTypes.any.isRequired, roomClient : PropTypes.any.isRequired,
browser : PropTypes.object.isRequired,
canShareFiles : PropTypes.bool.isRequired, canShareFiles : PropTypes.bool.isRequired,
tabOpen : PropTypes.bool.isRequired, tabOpen : PropTypes.bool.isRequired,
canShare : PropTypes.bool.isRequired,
classes : PropTypes.object.isRequired classes : PropTypes.object.isRequired
}; };
const makeMapStateToProps = () =>
{
const hasPermission = makePermissionSelector(permissions.SHARE_FILE);
const mapStateToProps = (state) => const mapStateToProps = (state) =>
{ {
return { return {
canShareFiles : state.me.canShareFiles, canShareFiles : state.me.canShareFiles,
tabOpen : state.toolarea.currentToolTab === 'files' browser : state.me.browser,
tabOpen : state.toolarea.currentToolTab === 'files',
canShare : hasPermission(state)
}; };
}; };
return mapStateToProps;
};
export default withRoomContext(connect( export default withRoomContext(connect(
mapStateToProps makeMapStateToProps,
null,
null,
{
areStatesEqual : (next, prev) =>
{
return (
prev.room === next.room &&
prev.me.browser === next.me.browser &&
prev.me.roles === next.me.roles &&
prev.me.canShareFiles === next.me.canShareFiles &&
prev.peers === next.peers &&
prev.toolarea.currentToolTab === next.toolarea.currentToolTab
);
}
}
)(withStyles(styles)(FileSharing))); )(withStyles(styles)(FileSharing)));

View File

@ -0,0 +1,108 @@
import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { withRoomContext } from '../../../RoomContext';
import { withStyles } from '@material-ui/core/styles';
import { useIntl, FormattedMessage } from 'react-intl';
import { permissions } from '../../../permissions';
import { makePermissionSelector } from '../../Selectors';
import Button from '@material-ui/core/Button';
const styles = (theme) =>
({
root :
{
display : 'flex',
padding : theme.spacing(1),
boxShadow : '0 2px 5px 2px rgba(0, 0, 0, 0.2)',
backgroundColor : 'rgba(255, 255, 255, 1)'
},
listheader :
{
padding : theme.spacing(1),
fontWeight : 'bolder'
},
actionButton :
{
marginLeft : 'auto'
}
});
const FileSharingModerator = (props) =>
{
const intl = useIntl();
const {
roomClient,
isFileSharingModerator,
room,
classes
} = props;
if (!isFileSharingModerator)
return null;
return (
<ul className={classes.root}>
<li className={classes.listheader}>
<FormattedMessage
id='room.moderatoractions'
defaultMessage='Moderator actions'
/>
</li>
<Button
aria-label={intl.formatMessage({
id : 'room.clearFileSharing',
defaultMessage : 'Clear files'
})}
className={classes.actionButton}
variant='contained'
color='secondary'
disabled={room.clearFileSharingInProgress}
onClick={() => roomClient.clearFileSharing()}
>
<FormattedMessage
id='room.clearFileSharing'
defaultMessage='Clear files'
/>
</Button>
</ul>
);
};
FileSharingModerator.propTypes =
{
roomClient : PropTypes.any.isRequired,
isFileSharingModerator : PropTypes.bool,
room : PropTypes.object,
classes : PropTypes.object.isRequired
};
const makeMapStateToProps = () =>
{
const hasPermission = makePermissionSelector(permissions.MODERATE_FILES);
const mapStateToProps = (state) =>
({
isFileSharingModerator : hasPermission(state),
room : state.room
});
return mapStateToProps;
};
export default withRoomContext(connect(
makeMapStateToProps,
null,
null,
{
areStatesEqual : (next, prev) =>
{
return (
prev.room === next.room &&
prev.me === next.me &&
prev.peers === next.peers
);
}
}
)(withStyles(styles)(FileSharingModerator)));

View File

@ -1,5 +1,6 @@
import React from 'react'; import React from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { raisedHandsSelector } from '../Selectors';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { withStyles } from '@material-ui/core/styles'; import { withStyles } from '@material-ui/core/styles';
import * as toolareaActions from '../../actions/toolareaActions'; import * as toolareaActions from '../../actions/toolareaActions';
@ -51,6 +52,7 @@ const MeetingDrawer = (props) =>
currentToolTab, currentToolTab,
unreadMessages, unreadMessages,
unreadFiles, unreadFiles,
raisedHands,
closeDrawer, closeDrawer,
setToolTab, setToolTab,
classes, classes,
@ -93,10 +95,14 @@ const MeetingDrawer = (props) =>
} }
/> />
<Tab <Tab
label={intl.formatMessage({ label={
<Badge color='secondary' badgeContent={raisedHands}>
{intl.formatMessage({
id : 'label.participants', id : 'label.participants',
defaultMessage : 'Participants' defaultMessage : 'Participants'
})} })}
</Badge>
}
/> />
</Tabs> </Tabs>
<IconButton onClick={closeDrawer}> <IconButton onClick={closeDrawer}>
@ -116,16 +122,21 @@ MeetingDrawer.propTypes =
setToolTab : PropTypes.func.isRequired, setToolTab : PropTypes.func.isRequired,
unreadMessages : PropTypes.number.isRequired, unreadMessages : PropTypes.number.isRequired,
unreadFiles : PropTypes.number.isRequired, unreadFiles : PropTypes.number.isRequired,
raisedHands : PropTypes.number.isRequired,
closeDrawer : PropTypes.func.isRequired, closeDrawer : PropTypes.func.isRequired,
classes : PropTypes.object.isRequired, classes : PropTypes.object.isRequired,
theme : PropTypes.object.isRequired theme : PropTypes.object.isRequired
}; };
const mapStateToProps = (state) => ({ const mapStateToProps = (state) =>
{
return {
currentToolTab : state.toolarea.currentToolTab, currentToolTab : state.toolarea.currentToolTab,
unreadMessages : state.toolarea.unreadMessages, unreadMessages : state.toolarea.unreadMessages,
unreadFiles : state.toolarea.unreadFiles unreadFiles : state.toolarea.unreadFiles,
}); raisedHands : raisedHandsSelector(state)
};
};
const mapDispatchToProps = { const mapDispatchToProps = {
setToolTab : toolareaActions.setToolTab setToolTab : toolareaActions.setToolTab
@ -141,7 +152,8 @@ export default connect(
return ( return (
prev.toolarea.currentToolTab === next.toolarea.currentToolTab && prev.toolarea.currentToolTab === next.toolarea.currentToolTab &&
prev.toolarea.unreadMessages === next.toolarea.unreadMessages && prev.toolarea.unreadMessages === next.toolarea.unreadMessages &&
prev.toolarea.unreadFiles === next.toolarea.unreadFiles prev.toolarea.unreadFiles === next.toolarea.unreadFiles &&
prev.peers === next.peers
); );
} }
} }

View File

@ -1,79 +1,55 @@
import React from 'react'; import React from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { withStyles } from '@material-ui/core/styles'; import { withStyles } from '@material-ui/core/styles';
import { withRoomContext } from '../../../RoomContext';
import classnames from 'classnames'; import classnames from 'classnames';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import * as appPropTypes from '../../appPropTypes'; import * as appPropTypes from '../../appPropTypes';
import { useIntl } from 'react-intl';
import IconButton from '@material-ui/core/IconButton';
import Tooltip from '@material-ui/core/Tooltip';
import PanIcon from '@material-ui/icons/PanTool';
import EmptyAvatar from '../../../images/avatar-empty.jpeg'; import EmptyAvatar from '../../../images/avatar-empty.jpeg';
import HandIcon from '../../../images/icon-hand-white.svg';
const styles = (theme) => const styles = (theme) =>
({ ({
root : root :
{ {
padding : theme.spacing(1),
width : '100%', width : '100%',
overflow : 'hidden', overflow : 'hidden',
cursor : 'auto', cursor : 'auto',
display : 'flex' display : 'flex'
}, },
listPeer :
{
display : 'flex'
},
avatar : avatar :
{ {
borderRadius : '50%', borderRadius : '50%',
height : '2rem' height : '2rem',
marginTop : theme.spacing(0.5)
}, },
peerInfo : peerInfo :
{ {
fontSize : '1rem', fontSize : '1rem',
border : 'none',
display : 'flex', display : 'flex',
paddingLeft : theme.spacing(1), paddingLeft : theme.spacing(1),
flexGrow : 1, flexGrow : 1,
alignItems : 'center' alignItems : 'center'
}, },
indicators : buttons :
{ {
left : 0, padding : theme.spacing(1)
top : 0,
display : 'flex',
flexDirection : 'row',
justifyContent : 'flex-start',
alignItems : 'center',
transition : 'opacity 0.3s'
}, },
icon : green :
{ {
flex : '0 0 auto', color : 'rgba(0, 153, 0, 1)'
margin : '0.3rem',
borderRadius : 2,
backgroundPosition : 'center',
backgroundSize : '75%',
backgroundRepeat : 'no-repeat',
backgroundColor : 'rgba(0, 0, 0, 0.5)',
transitionProperty : 'opacity, background-color',
transitionDuration : '0.15s',
width : 'var(--media-control-button-size)',
height : 'var(--media-control-button-size)',
opacity : 0.85,
'&:hover' :
{
opacity : 1
},
'&.raise-hand' :
{
backgroundImage : `url(${HandIcon})`,
opacity : 1
}
} }
}); });
const ListMe = (props) => const ListMe = (props) =>
{ {
const intl = useIntl();
const { const {
roomClient,
me, me,
settings, settings,
classes classes
@ -82,26 +58,46 @@ const ListMe = (props) =>
const picture = me.picture || EmptyAvatar; const picture = me.picture || EmptyAvatar;
return ( return (
<li className={classes.root}> <div className={classes.root}>
<div className={classes.listPeer}>
<img alt='My avatar' className={classes.avatar} src={picture} /> <img alt='My avatar' className={classes.avatar} src={picture} />
<div className={classes.peerInfo}> <div className={classes.peerInfo}>
{settings.displayName} {settings.displayName}
</div> </div>
<Tooltip
<div className={classes.indicators}> title={intl.formatMessage({
{ me.raisedHand && id : 'tooltip.raisedHand',
<div className={classnames(classes.icon, 'raise-hand')} /> defaultMessage : 'Raise hand'
})}
placement='bottom'
>
<IconButton
aria-label={intl.formatMessage({
id : 'tooltip.raisedHand',
defaultMessage : 'Raise hand'
})}
className={
classnames(me.raisedHand ? classes.green : null, classes.buttons)
} }
disabled={me.raisedHandInProgress}
color='primary'
onClick={(e) =>
{
e.stopPropagation();
roomClient.setRaisedHand(!me.raisedHand);
}}
>
<PanIcon />
</IconButton>
</Tooltip>
</div> </div>
</div>
</li>
); );
}; };
ListMe.propTypes = ListMe.propTypes =
{ {
roomClient : PropTypes.object.isRequired,
me : appPropTypes.Me.isRequired, me : appPropTypes.Me.isRequired,
settings : PropTypes.object.isRequired, settings : PropTypes.object.isRequired,
classes : PropTypes.object.isRequired classes : PropTypes.object.isRequired
@ -112,7 +108,7 @@ const mapStateToProps = (state) => ({
settings : state.settings settings : state.settings
}); });
export default connect( export default withRoomContext(connect(
mapStateToProps, mapStateToProps,
null, null,
null, null,
@ -125,4 +121,4 @@ export default connect(
); );
} }
} }
)(withStyles(styles)(ListMe)); )(withStyles(styles)(ListMe)));

View File

@ -0,0 +1,124 @@
import React from 'react';
import { connect } from 'react-redux';
import { withStyles } from '@material-ui/core/styles';
import PropTypes from 'prop-types';
import { withRoomContext } from '../../../RoomContext';
import { useIntl, FormattedMessage } from 'react-intl';
import Button from '@material-ui/core/Button';
const styles = (theme) =>
({
root :
{
padding : theme.spacing(1),
display : 'flex'
},
divider :
{
marginLeft : theme.spacing(2)
}
});
const ListModerator = (props) =>
{
const intl = useIntl();
const {
roomClient,
room,
classes
} = props;
return (
<div className={classes.root}>
<Button
aria-label={intl.formatMessage({
id : 'room.muteAll',
defaultMessage : 'Mute all'
})}
variant='contained'
color='secondary'
disabled={room.muteAllInProgress}
onClick={() => roomClient.muteAllPeers()}
>
<FormattedMessage
id='room.muteAll'
defaultMessage='Mute all'
/>
</Button>
<div className={classes.divider} />
<Button
aria-label={intl.formatMessage({
id : 'room.stopAllVideo',
defaultMessage : 'Stop all video'
})}
variant='contained'
color='secondary'
disabled={room.stopAllVideoInProgress}
onClick={() => roomClient.stopAllPeerVideo()}
>
<FormattedMessage
id='room.stopAllVideo'
defaultMessage='Stop all video'
/>
</Button>
<div className={classes.divider} />
<Button
aria-label={intl.formatMessage({
id : 'room.stopAllScreenSharing',
defaultMessage : 'Stop all screen sharing'
})}
variant='contained'
color='secondary'
disabled={room.stopAllScreenSharingInProgress}
onClick={() => roomClient.stopAllPeerScreenSharing()}
>
<FormattedMessage
id='room.stopAllScreenSharing'
defaultMessage='Stop all screen sharing'
/>
</Button>
<div className={classes.divider} />
<Button
aria-label={intl.formatMessage({
id : 'room.closeMeeting',
defaultMessage : 'Close meeting'
})}
variant='contained'
color='secondary'
disabled={room.closeMeetingInProgress}
onClick={() => roomClient.closeMeeting()}
>
<FormattedMessage
id='room.closeMeeting'
defaultMessage='Close meeting'
/>
</Button>
</div>
);
};
ListModerator.propTypes =
{
roomClient : PropTypes.any.isRequired,
room : PropTypes.object.isRequired,
classes : PropTypes.object.isRequired
};
const mapStateToProps = (state) => ({
room : state.room
});
export default withRoomContext(connect(
mapStateToProps,
null,
null,
{
areStatesEqual : (next, prev) =>
{
return (
prev.room === next.room
);
}
}
)(withStyles(styles)(ListModerator)));

View File

@ -3,41 +3,43 @@ import { connect } from 'react-redux';
import { makePeerConsumerSelector } from '../../Selectors'; import { makePeerConsumerSelector } from '../../Selectors';
import { withStyles } from '@material-ui/core/styles'; import { withStyles } from '@material-ui/core/styles';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import classnames from 'classnames';
import * as appPropTypes from '../../appPropTypes'; import * as appPropTypes from '../../appPropTypes';
import { withRoomContext } from '../../../RoomContext'; import { withRoomContext } from '../../../RoomContext';
import { useIntl } from 'react-intl'; import { useIntl } from 'react-intl';
import { green } from '@material-ui/core/colors';
import IconButton from '@material-ui/core/IconButton'; import IconButton from '@material-ui/core/IconButton';
import Tooltip from '@material-ui/core/Tooltip';
import VideocamIcon from '@material-ui/icons/Videocam';
import VideocamOffIcon from '@material-ui/icons/VideocamOff';
import MicIcon from '@material-ui/icons/Mic'; import MicIcon from '@material-ui/icons/Mic';
import MicOffIcon from '@material-ui/icons/MicOff'; import MicOffIcon from '@material-ui/icons/MicOff';
import VolumeUpIcon from '@material-ui/icons/VolumeUp';
import VolumeOffIcon from '@material-ui/icons/VolumeOff';
import ScreenIcon from '@material-ui/icons/ScreenShare'; import ScreenIcon from '@material-ui/icons/ScreenShare';
import ScreenOffIcon from '@material-ui/icons/StopScreenShare'; import ScreenOffIcon from '@material-ui/icons/StopScreenShare';
import ExitIcon from '@material-ui/icons/ExitToApp';
import EmptyAvatar from '../../../images/avatar-empty.jpeg'; import EmptyAvatar from '../../../images/avatar-empty.jpeg';
import HandIcon from '../../../images/icon-hand-white.svg'; import PanIcon from '@material-ui/icons/PanTool';
import RecordVoiceOverIcon from '@material-ui/icons/RecordVoiceOver';
const styles = (theme) => const styles = (theme) =>
({ ({
root : root :
{ {
padding : theme.spacing(1),
width : '100%', width : '100%',
overflow : 'hidden', overflow : 'hidden',
cursor : 'auto', cursor : 'auto',
display : 'flex' display : 'flex'
}, },
listPeer :
{
display : 'flex'
},
avatar : avatar :
{ {
borderRadius : '50%', borderRadius : '50%',
height : '2rem' height : '2rem',
marginTop : theme.spacing(0.5)
}, },
peerInfo : peerInfo :
{ {
fontSize : '1rem', fontSize : '1rem',
border : 'none',
display : 'flex', display : 'flex',
paddingLeft : theme.spacing(1), paddingLeft : theme.spacing(1),
flexGrow : 1, flexGrow : 1,
@ -45,86 +47,17 @@ const styles = (theme) =>
}, },
indicators : indicators :
{ {
left : 0,
top : 0,
display : 'flex', display : 'flex',
flexDirection : 'row', padding : theme.spacing(1)
justifyContent : 'flex-start',
alignItems : 'center',
transition : 'opacity 0.3s'
}, },
icon : buttons :
{ {
flex : '0 0 auto', padding : theme.spacing(1)
margin : '0.3rem',
borderRadius : 2,
backgroundPosition : 'center',
backgroundSize : '75%',
backgroundRepeat : 'no-repeat',
backgroundColor : 'rgba(0, 0, 0, 0.5)',
transitionProperty : 'opacity, background-color',
transitionDuration : '0.15s',
width : 'var(--media-control-button-size)',
height : 'var(--media-control-button-size)',
opacity : 0.85,
'&:hover' :
{
opacity : 1
}, },
'&.on' : green :
{ {
opacity : 1 color : 'rgba(0, 153, 0, 1)',
}, marginLeft : theme.spacing(2)
'&.off' :
{
opacity : 0.2
},
'&.raise-hand' :
{
backgroundImage : `url(${HandIcon})`
}
},
controls :
{
float : 'right',
display : 'flex',
flexDirection : 'row',
justifyContent : 'flex-start',
alignItems : 'center'
},
button :
{
flex : '0 0 auto',
margin : '0.3rem',
borderRadius : 2,
backgroundColor : 'rgba(0, 0, 0, 0.5)',
cursor : 'pointer',
transitionProperty : 'opacity, background-color',
transitionDuration : '0.15s',
width : 'var(--media-control-button-size)',
height : 'var(--media-control-button-size)',
opacity : 0.85,
'&:hover' :
{
opacity : 1
},
'&.unsupported' :
{
pointerEvents : 'none'
},
'&.disabled' :
{
pointerEvents : 'none',
backgroundColor : 'var(--media-control-botton-disabled)'
},
'&.on' :
{
backgroundColor : 'var(--media-control-botton-on)'
},
'&.off' :
{
backgroundColor : 'var(--media-control-botton-off)'
}
} }
}); });
@ -134,13 +67,22 @@ const ListPeer = (props) =>
const { const {
roomClient, roomClient,
isModerator,
spotlight,
peer, peer,
micConsumer, micConsumer,
webcamConsumer,
screenConsumer, screenConsumer,
children, children,
classes classes
} = props; } = props;
const webcamEnabled = (
Boolean(webcamConsumer) &&
!webcamConsumer.locallyPaused &&
!webcamConsumer.remotelyPaused
);
const micEnabled = ( const micEnabled = (
Boolean(micConsumer) && Boolean(micConsumer) &&
!micConsumer.locallyPaused && !micConsumer.locallyPaused &&
@ -162,22 +104,38 @@ const ListPeer = (props) =>
<div className={classes.peerInfo}> <div className={classes.peerInfo}>
{peer.displayName} {peer.displayName}
</div> </div>
<div className={classes.indicators}> { peer.raisedHand &&
{ peer.raiseHandState && <IconButton
<div className={ className={classes.buttons}
classnames( style={{ color: green[500] }}
classes.icon, 'raise-hand', { disabled={!isModerator || peer.raisedHandInProgress}
on : peer.raiseHandState, onClick={(e) =>
off : !peer.raiseHandState {
e.stopPropagation();
roomClient.lowerPeerHand(peer.id);
}}
>
<PanIcon />
</IconButton>
} }
) { spotlight &&
<IconButton
className={classes.buttons}
style={{ color: green[500] }}
disabled
>
<RecordVoiceOverIcon />
</IconButton>
} }
/> { screenConsumer && spotlight &&
} <Tooltip
</div> title={intl.formatMessage({
{children} id : 'tooltip.muteScreenSharing',
<div className={classes.controls}> defaultMessage : 'Mute participant share'
{ screenConsumer && })}
placement='bottom'
>
<IconButton <IconButton
aria-label={intl.formatMessage({ aria-label={intl.formatMessage({
id : 'tooltip.muteScreenSharing', id : 'tooltip.muteScreenSharing',
@ -185,9 +143,11 @@ const ListPeer = (props) =>
})} })}
color={screenVisible ? 'primary' : 'secondary'} color={screenVisible ? 'primary' : 'secondary'}
disabled={peer.peerScreenInProgress} disabled={peer.peerScreenInProgress}
className={classes.buttons}
onClick={(e) => onClick={(e) =>
{ {
e.stopPropagation(); e.stopPropagation();
screenVisible ? screenVisible ?
roomClient.modifyPeerConsumer(peer.id, 'screen', true) : roomClient.modifyPeerConsumer(peer.id, 'screen', true) :
roomClient.modifyPeerConsumer(peer.id, 'screen', false); roomClient.modifyPeerConsumer(peer.id, 'screen', false);
@ -199,7 +159,48 @@ const ListPeer = (props) =>
<ScreenOffIcon /> <ScreenOffIcon />
} }
</IconButton> </IconButton>
</Tooltip>
} }
{ spotlight &&
<Tooltip
title={intl.formatMessage({
id : 'tooltip.muteParticipantVideo',
defaultMessage : 'Mute participant video'
})}
placement='bottom'
>
<IconButton
aria-label={intl.formatMessage({
id : 'tooltip.muteParticipantVideo',
defaultMessage : 'Mute participant video'
})}
color={webcamEnabled ? 'primary' : 'secondary'}
disabled={peer.peerVideoInProgress}
className={classes.buttons}
onClick={(e) =>
{
e.stopPropagation();
webcamEnabled ?
roomClient.modifyPeerConsumer(peer.id, 'webcam', true) :
roomClient.modifyPeerConsumer(peer.id, 'webcam', false);
}}
>
{ webcamEnabled ?
<VideocamIcon />
:
<VideocamOffIcon />
}
</IconButton>
</Tooltip>
}
<Tooltip
title={intl.formatMessage({
id : 'tooltip.muteParticipant',
defaultMessage : 'Mute participant'
})}
placement='bottom'
>
<IconButton <IconButton
aria-label={intl.formatMessage({ aria-label={intl.formatMessage({
id : 'tooltip.muteParticipant', id : 'tooltip.muteParticipant',
@ -207,21 +208,132 @@ const ListPeer = (props) =>
})} })}
color={micEnabled ? 'primary' : 'secondary'} color={micEnabled ? 'primary' : 'secondary'}
disabled={peer.peerAudioInProgress} disabled={peer.peerAudioInProgress}
className={classes.buttons}
onClick={(e) => onClick={(e) =>
{ {
e.stopPropagation(); e.stopPropagation();
micEnabled ? micEnabled ?
roomClient.modifyPeerConsumer(peer.id, 'mic', true) : roomClient.modifyPeerConsumer(peer.id, 'mic', true) :
roomClient.modifyPeerConsumer(peer.id, 'mic', false); roomClient.modifyPeerConsumer(peer.id, 'mic', false);
}} }}
> >
{ micEnabled ? { micEnabled ?
<VolumeUpIcon />
:
<VolumeOffIcon />
}
</IconButton>
</Tooltip>
{ isModerator &&
<Tooltip
title={intl.formatMessage({
id : 'tooltip.kickParticipant',
defaultMessage : 'Kick out participant'
})}
placement='bottom'
>
<IconButton
aria-label={intl.formatMessage({
id : 'tooltip.kickParticipant',
defaultMessage : 'Kick out participant'
})}
disabled={peer.peerKickInProgress}
className={classes.buttons}
color='secondary'
onClick={(e) =>
{
e.stopPropagation();
roomClient.kickPeer(peer.id);
}}
>
<ExitIcon />
</IconButton>
</Tooltip>
}
{ isModerator && micConsumer &&
<Tooltip
title={intl.formatMessage({
id : 'tooltip.muteParticipantAudioModerator',
defaultMessage : 'Mute participant audio globally'
})}
placement='bottom'
>
<IconButton
className={classes.buttons}
style={{ color: green[500] }}
disabled={!isModerator || peer.stopPeerAudioInProgress}
onClick={(e) =>
{
e.stopPropagation();
roomClient.mutePeer(peer.id);
}}
>
{ !micConsumer.remotelyPaused ?
<MicIcon /> <MicIcon />
: :
<MicOffIcon /> <MicOffIcon />
} }
</IconButton> </IconButton>
</div> </Tooltip>
}
{ isModerator && webcamConsumer &&
<Tooltip
title={intl.formatMessage({
id : 'tooltip.muteParticipantVideoModerator',
defaultMessage : 'Mute participant video globally'
})}
placement='bottom'
>
<IconButton
className={classes.buttons}
style={{ color: green[500] }}
disabled={!isModerator || peer.stopPeerVideoInProgress}
onClick={(e) =>
{
e.stopPropagation();
roomClient.stopPeerVideo(peer.id);
}}
>
{ !webcamConsumer.remotelyPaused ?
<VideocamIcon />
:
<VideocamOffIcon />
}
</IconButton>
</Tooltip>
}
{ isModerator && screenConsumer &&
<Tooltip
title={intl.formatMessage({
id : 'tooltip.muteScreenSharingModerator',
defaultMessage : 'Mute participant screen share globally'
})}
placement='bottom'
>
<IconButton
className={classes.buttons}
style={{ color: green[500] }}
disabled={!isModerator || peer.stopPeerScreenSharingInProgress}
onClick={(e) =>
{
e.stopPropagation();
roomClient.stopPeerScreenSharing(peer.id);
}}
>
{ !screenConsumer.remotelyPaused ?
<ScreenIcon />
:
<ScreenOffIcon />
}
</IconButton>
</Tooltip>
}
{children}
</div> </div>
); );
}; };
@ -230,6 +342,8 @@ ListPeer.propTypes =
{ {
roomClient : PropTypes.any.isRequired, roomClient : PropTypes.any.isRequired,
advancedMode : PropTypes.bool, advancedMode : PropTypes.bool,
isModerator : PropTypes.bool,
spotlight : PropTypes.bool,
peer : appPropTypes.Peer.isRequired, peer : appPropTypes.Peer.isRequired,
micConsumer : appPropTypes.Consumer, micConsumer : appPropTypes.Consumer,
webcamConsumer : appPropTypes.Consumer, webcamConsumer : appPropTypes.Consumer,

View File

@ -1,16 +1,19 @@
import React from 'react'; import React from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { import {
passivePeersSelector, participantListSelector,
spotlightPeersSelector makePermissionSelector
} from '../../Selectors'; } from '../../Selectors';
import classNames from 'classnames'; import { permissions } from '../../../permissions';
import classnames from 'classnames';
import { withStyles } from '@material-ui/core/styles'; import { withStyles } from '@material-ui/core/styles';
import { withRoomContext } from '../../../RoomContext'; import { withRoomContext } from '../../../RoomContext';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Flipper, Flipped } from 'react-flip-toolkit';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import ListPeer from './ListPeer'; import ListPeer from './ListPeer';
import ListMe from './ListMe'; import ListMe from './ListMe';
import ListModerator from './ListModerator';
import Volume from '../../Containers/Volume'; import Volume from '../../Containers/Volume';
const styles = (theme) => const styles = (theme) =>
@ -30,12 +33,10 @@ const styles = (theme) =>
}, },
listheader : listheader :
{ {
padding : theme.spacing(1),
fontWeight : 'bolder' fontWeight : 'bolder'
}, },
listItem : listItem :
{ {
padding : theme.spacing(1),
width : '100%', width : '100%',
overflow : 'hidden', overflow : 'hidden',
cursor : 'pointer', cursor : 'pointer',
@ -76,14 +77,26 @@ class ParticipantList extends React.PureComponent
const { const {
roomClient, roomClient,
advancedMode, advancedMode,
passivePeers, isModerator,
participants,
spotlights,
selectedPeerId, selectedPeerId,
spotlightPeers,
classes classes
} = this.props; } = this.props;
return ( return (
<div className={classes.root} ref={(node) => { this.node = node; }}> <div className={classes.root} ref={(node) => { this.node = node; }}>
{ isModerator &&
<ul className={classes.list}>
<li className={classes.listheader}>
<FormattedMessage
id='room.moderatoractions'
defaultMessage='Moderator actions'
/>
</li>
<ListModerator />
</ul>
}
<ul className={classes.list}> <ul className={classes.list}>
<li className={classes.listheader}> <li className={classes.listheader}>
<FormattedMessage <FormattedMessage
@ -96,42 +109,42 @@ class ParticipantList extends React.PureComponent
<ul className={classes.list}> <ul className={classes.list}>
<li className={classes.listheader}> <li className={classes.listheader}>
<FormattedMessage <FormattedMessage
id='room.spotlights' id='label.participants'
defaultMessage='Participants in Spotlight' defaultMessage='Participants'
/> />
</li> </li>
{ spotlightPeers.map((peerId) => ( <Flipper
<li flipKey={participants}
key={peerId}
className={classNames(classes.listItem, {
selected : peerId === selectedPeerId
})}
onClick={() => roomClient.setSelectedPeer(peerId)}
> >
<ListPeer id={peerId} advancedMode={advancedMode}> { participants.map((peer) => (
<Volume small id={peerId} /> <Flipped key={peer.id} flipId={peer.id}>
<li
key={peer.id}
className={classnames(classes.listItem, {
selected : peer.id === selectedPeerId
})}
onClick={() => roomClient.setSelectedPeer(peer.id)}
>
{ spotlights.includes(peer.id) ?
<ListPeer
id={peer.id}
advancedMode={advancedMode}
isModerator={isModerator}
spotlight
>
<Volume small id={peer.id} />
</ListPeer> </ListPeer>
</li> :
))} <ListPeer
</ul> id={peer.id}
<ul className={classes.list}> advancedMode={advancedMode}
<li className={classes.listheader}> isModerator={isModerator}
<FormattedMessage
id='room.passive'
defaultMessage='Passive Participants'
/> />
}
</li> </li>
{ passivePeers.map((peerId) => ( </Flipped>
<li
key={peerId}
className={classNames(classes.listItem, {
selected : peerId === selectedPeerId
})}
onClick={() => roomClient.setSelectedPeer(peerId)}
>
<ListPeer id={peerId} advancedMode={advancedMode} />
</li>
))} ))}
</Flipper>
</ul> </ul>
</div> </div>
); );
@ -142,32 +155,41 @@ ParticipantList.propTypes =
{ {
roomClient : PropTypes.any.isRequired, roomClient : PropTypes.any.isRequired,
advancedMode : PropTypes.bool, advancedMode : PropTypes.bool,
passivePeers : PropTypes.array, isModerator : PropTypes.bool,
participants : PropTypes.array,
spotlights : PropTypes.array,
selectedPeerId : PropTypes.string, selectedPeerId : PropTypes.string,
spotlightPeers : PropTypes.array,
classes : PropTypes.object.isRequired classes : PropTypes.object.isRequired
}; };
const makeMapStateToProps = () =>
{
const hasPermission = makePermissionSelector(permissions.MODERATE_ROOM);
const mapStateToProps = (state) => const mapStateToProps = (state) =>
{ {
return { return {
passivePeers : passivePeersSelector(state), isModerator : hasPermission(state),
selectedPeerId : state.room.selectedPeerId, participants : participantListSelector(state),
spotlightPeers : spotlightPeersSelector(state) spotlights : state.room.spotlights,
selectedPeerId : state.room.selectedPeerId
}; };
}; };
return mapStateToProps;
};
const ParticipantListContainer = withRoomContext(connect( const ParticipantListContainer = withRoomContext(connect(
mapStateToProps, makeMapStateToProps,
null, null,
null, null,
{ {
areStatesEqual : (next, prev) => areStatesEqual : (next, prev) =>
{ {
return ( return (
prev.peers === next.peers && prev.room === next.room &&
prev.room.spotlights === next.room.spotlights && prev.me.roles === next.me.roles &&
prev.room.selectedPeerId === next.room.selectedPeerId prev.peers === next.peers
); );
} }
} }

View File

@ -11,10 +11,9 @@ import Peer from '../Containers/Peer';
import Me from '../Containers/Me'; import Me from '../Containers/Me';
const RATIO = 1.334; const RATIO = 1.334;
const PADDING_V = 50; const PADDING = 60;
const PADDING_H = 0;
const styles = () => const styles = (theme) =>
({ ({
root : root :
{ {
@ -23,6 +22,7 @@ const styles = () =>
display : 'flex', display : 'flex',
flexDirection : 'row', flexDirection : 'row',
flexWrap : 'wrap', flexWrap : 'wrap',
overflow : 'hidden',
justifyContent : 'center', justifyContent : 'center',
alignItems : 'center', alignItems : 'center',
alignContent : 'center' alignContent : 'center'
@ -36,6 +36,14 @@ const styles = () =>
{ {
paddingTop : 60, paddingTop : 60,
transition : 'padding .5s' transition : 'padding .5s'
},
buttonControlBar :
{
paddingLeft : 60,
[theme.breakpoints.down('sm')] :
{
paddingLeft : 0
}
} }
}); });
@ -66,9 +74,11 @@ class Democratic extends React.PureComponent
return; return;
} }
const width = this.peersRef.current.clientWidth - PADDING_H; const width =
const height = this.peersRef.current.clientHeight - this.peersRef.current.clientWidth - (this.props.buttonControlBar ? PADDING : 0);
(this.props.toolbarsVisible || this.props.permanentTopBar ? PADDING_V : PADDING_H); const height =
this.peersRef.current.clientHeight -
(this.props.toolbarsVisible || this.props.permanentTopBar ? PADDING : 0);
let x, y, space; let x, y, space;
@ -130,6 +140,7 @@ class Democratic extends React.PureComponent
spotlightsPeers, spotlightsPeers,
toolbarsVisible, toolbarsVisible,
permanentTopBar, permanentTopBar,
buttonControlBar,
classes classes
} = this.props; } = this.props;
@ -143,7 +154,9 @@ class Democratic extends React.PureComponent
<div <div
className={classnames( className={classnames(
classes.root, classes.root,
toolbarsVisible || permanentTopBar ? classes.showingToolBar : classes.hiddenToolBar toolbarsVisible || permanentTopBar ?
classes.showingToolBar : classes.hiddenToolBar,
buttonControlBar ? classes.buttonControlBar : null
)} )}
ref={this.peersRef} ref={this.peersRef}
> >
@ -175,7 +188,9 @@ Democratic.propTypes =
boxes : PropTypes.number, boxes : PropTypes.number,
spotlightsPeers : PropTypes.array.isRequired, spotlightsPeers : PropTypes.array.isRequired,
toolbarsVisible : PropTypes.bool.isRequired, toolbarsVisible : PropTypes.bool.isRequired,
permanentTopBar : PropTypes.bool, permanentTopBar : PropTypes.bool.isRequired,
buttonControlBar : PropTypes.bool.isRequired,
toolAreaOpen : PropTypes.bool.isRequired,
classes : PropTypes.object.isRequired classes : PropTypes.object.isRequired
}; };
@ -185,7 +200,9 @@ const mapStateToProps = (state) =>
boxes : videoBoxesSelector(state), boxes : videoBoxesSelector(state),
spotlightsPeers : spotlightPeersSelector(state), spotlightsPeers : spotlightPeersSelector(state),
toolbarsVisible : state.room.toolbarsVisible, toolbarsVisible : state.room.toolbarsVisible,
permanentTopBar : state.settings.permanentTopBar permanentTopBar : state.settings.permanentTopBar,
buttonControlBar : state.settings.buttonControlBar,
toolAreaOpen : state.toolarea.toolAreaOpen
}; };
}; };
@ -202,8 +219,10 @@ export default connect(
prev.consumers === next.consumers && prev.consumers === next.consumers &&
prev.room.spotlights === next.room.spotlights && prev.room.spotlights === next.room.spotlights &&
prev.room.toolbarsVisible === next.room.toolbarsVisible && prev.room.toolbarsVisible === next.room.toolbarsVisible &&
prev.settings.permanentTopBar === next.settings.permanentTopBar prev.settings.permanentTopBar === next.settings.permanentTopBar &&
prev.settings.buttonControlBar === next.settings.buttonControlBar &&
prev.toolarea.toolAreaOpen === next.toolarea.toolAreaOpen
); );
} }
} }
)(withStyles(styles)(Democratic)); )(withStyles(styles, { withTheme: true })(Democratic));

View File

@ -12,6 +12,12 @@ import Peer from '../Containers/Peer';
import SpeakerPeer from '../Containers/SpeakerPeer'; import SpeakerPeer from '../Containers/SpeakerPeer';
import Grid from '@material-ui/core/Grid'; import Grid from '@material-ui/core/Grid';
const RATIO = 1.334;
const PADDING_V = 40;
const PADDING_H = 0;
const FILMSTRING_PADDING_V = 10;
const FILMSTRING_PADDING_H = 0;
const styles = () => const styles = () =>
({ ({
root : root :
@ -19,25 +25,24 @@ const styles = () =>
height : '100%', height : '100%',
width : '100%', width : '100%',
display : 'grid', display : 'grid',
overflow : 'hidden',
gridTemplateColumns : '1fr', gridTemplateColumns : '1fr',
gridTemplateRows : '1.6fr minmax(0, 0.4fr)' gridTemplateRows : '1fr 0.25fr'
}, },
speaker : speaker :
{ {
gridArea : '1 / 1 / 2 / 2', gridArea : '1 / 1 / 1 / 1',
display : 'flex', display : 'flex',
justifyContent : 'center', justifyContent : 'center',
alignItems : 'center', alignItems : 'center'
paddingTop : 40
}, },
filmStrip : filmStrip :
{ {
gridArea : '2 / 1 / 3 / 2' gridArea : '2 / 1 / 2 / 1'
}, },
filmItem : filmItem :
{ {
display : 'flex', display : 'flex',
marginLeft : '6px',
border : 'var(--peer-border)', border : 'var(--peer-border)',
'&.selected' : '&.selected' :
{ {
@ -45,8 +50,18 @@ const styles = () =>
}, },
'&.active' : '&.active' :
{ {
opacity : '0.6' borderColor : 'var(--selected-peer-border-color)'
} }
},
hiddenToolBar :
{
paddingTop : 0,
transition : 'padding .5s'
},
showingToolBar :
{
paddingTop : 60,
transition : 'padding .5s'
} }
}); });
@ -58,6 +73,8 @@ class Filmstrip extends React.PureComponent
this.resizeTimeout = null; this.resizeTimeout = null;
this.rootContainer = React.createRef();
this.activePeerContainer = React.createRef(); this.activePeerContainer = React.createRef();
this.filmStripContainer = React.createRef(); this.filmStripContainer = React.createRef();
@ -105,24 +122,38 @@ class Filmstrip extends React.PureComponent
{ {
const newState = {}; const newState = {};
const root = this.rootContainer.current;
if (!root)
return;
const availableWidth = root.clientWidth;
// Grid is:
// 4/5 speaker
// 1/5 filmstrip
const availableSpeakerHeight = (root.clientHeight * 0.8) -
(this.props.toolbarsVisible || this.props.permanentTopBar ? PADDING_V : PADDING_H);
const availableFilmstripHeight = root.clientHeight * 0.2;
const speaker = this.activePeerContainer.current; const speaker = this.activePeerContainer.current;
if (speaker) if (speaker)
{ {
let speakerWidth = (speaker.clientWidth - 100); let speakerWidth = (availableWidth - PADDING_H);
let speakerHeight = (speakerWidth / 4) * 3; let speakerHeight = speakerWidth / RATIO;
if (this.isSharingCamera(this.getActivePeerId())) if (this.isSharingCamera(this.getActivePeerId()))
{ {
speakerWidth /= 2; speakerWidth /= 2;
speakerHeight = (speakerWidth / 4) * 3; speakerHeight = speakerWidth / RATIO;
} }
if (speakerHeight > (speaker.clientHeight - 60)) if (speakerHeight > (availableSpeakerHeight - PADDING_V))
{ {
speakerHeight = (speaker.clientHeight - 60); speakerHeight = (availableSpeakerHeight - PADDING_V);
speakerWidth = (speakerHeight / 3) * 4; speakerWidth = speakerHeight * RATIO;
} }
newState.speakerWidth = speakerWidth; newState.speakerWidth = speakerWidth;
@ -133,14 +164,18 @@ class Filmstrip extends React.PureComponent
if (filmStrip) if (filmStrip)
{ {
let filmStripHeight = filmStrip.clientHeight - 10; let filmStripHeight = availableFilmstripHeight - FILMSTRING_PADDING_V;
let filmStripWidth = (filmStripHeight / 3) * 4; let filmStripWidth = filmStripHeight * RATIO;
if (filmStripWidth * this.props.boxes > (filmStrip.clientWidth - 50)) if (
(filmStripWidth * this.props.boxes) >
(availableWidth - FILMSTRING_PADDING_H)
)
{ {
filmStripWidth = (filmStrip.clientWidth - 50) / this.props.boxes; filmStripWidth = (availableWidth - FILMSTRING_PADDING_H) /
filmStripHeight = (filmStripWidth / 4) * 3; this.props.boxes;
filmStripHeight = filmStripWidth / RATIO;
} }
newState.filmStripWidth = filmStripWidth; newState.filmStripWidth = filmStripWidth;
@ -172,27 +207,21 @@ class Filmstrip extends React.PureComponent
window.removeEventListener('resize', this.updateDimensions); window.removeEventListener('resize', this.updateDimensions);
} }
componentWillUpdate(nextProps)
{
if (nextProps !== this.props)
{
if (
nextProps.activeSpeakerId != null &&
nextProps.activeSpeakerId !== this.props.myId
)
{
// eslint-disable-next-line react/no-did-update-set-state
this.setState({
lastSpeaker : nextProps.activeSpeakerId
});
}
}
}
componentDidUpdate(prevProps) componentDidUpdate(prevProps)
{ {
if (prevProps !== this.props) if (prevProps !== this.props)
{ {
if (
this.props.activeSpeakerId != null &&
this.props.activeSpeakerId !== this.props.myId
)
{
// eslint-disable-next-line react/no-did-update-set-state
this.setState({
lastSpeaker : this.props.activeSpeakerId
});
}
this.updateDimensions(); this.updateDimensions();
} }
} }
@ -205,6 +234,8 @@ class Filmstrip extends React.PureComponent
myId, myId,
advancedMode, advancedMode,
spotlights, spotlights,
toolbarsVisible,
permanentTopBar,
classes classes
} = this.props; } = this.props;
@ -223,7 +254,14 @@ class Filmstrip extends React.PureComponent
}; };
return ( return (
<div className={classes.root}> <div
className={classnames(
classes.root,
toolbarsVisible || permanentTopBar ?
classes.showingToolBar : classes.hiddenToolBar
)}
ref={this.rootContainer}
>
<div className={classes.speaker} ref={this.activePeerContainer}> <div className={classes.speaker} ref={this.activePeerContainer}>
{ peers[activePeerId] && { peers[activePeerId] &&
<SpeakerPeer <SpeakerPeer
@ -245,7 +283,7 @@ class Filmstrip extends React.PureComponent
<Me <Me
advancedMode={advancedMode} advancedMode={advancedMode}
style={peerStyle} style={peerStyle}
smallButtons smallContainer
/> />
</div> </div>
</Grid> </Grid>
@ -268,7 +306,7 @@ class Filmstrip extends React.PureComponent
advancedMode={advancedMode} advancedMode={advancedMode}
id={peerId} id={peerId}
style={peerStyle} style={peerStyle}
smallButtons smallContainer
/> />
</div> </div>
</Grid> </Grid>
@ -296,6 +334,9 @@ Filmstrip.propTypes = {
selectedPeerId : PropTypes.string, selectedPeerId : PropTypes.string,
spotlights : PropTypes.array.isRequired, spotlights : PropTypes.array.isRequired,
boxes : PropTypes.number, boxes : PropTypes.number,
toolbarsVisible : PropTypes.bool.isRequired,
toolAreaOpen : PropTypes.bool.isRequired,
permanentTopBar : PropTypes.bool,
classes : PropTypes.object.isRequired classes : PropTypes.object.isRequired
}; };
@ -308,7 +349,10 @@ const mapStateToProps = (state) =>
consumers : state.consumers, consumers : state.consumers,
myId : state.me.id, myId : state.me.id,
spotlights : state.room.spotlights, spotlights : state.room.spotlights,
boxes : videoBoxesSelector(state) boxes : videoBoxesSelector(state),
toolbarsVisible : state.room.toolbarsVisible,
toolAreaOpen : state.toolarea.toolAreaOpen,
permanentTopBar : state.settings.permanentTopBar
}; };
}; };
@ -322,6 +366,9 @@ export default withRoomContext(connect(
return ( return (
prev.room.activeSpeakerId === next.room.activeSpeakerId && prev.room.activeSpeakerId === next.room.activeSpeakerId &&
prev.room.selectedPeerId === next.room.selectedPeerId && prev.room.selectedPeerId === next.room.selectedPeerId &&
prev.room.toolbarsVisible === next.room.toolbarsVisible &&
prev.toolarea.toolAreaOpen === next.toolarea.toolAreaOpen &&
prev.settings.permanentTopBar === next.settings.permanentTopBar &&
prev.peers === next.peers && prev.peers === next.peers &&
prev.consumers === next.consumers && prev.consumers === next.consumers &&
prev.room.spotlights === next.room.spotlights && prev.room.spotlights === next.room.spotlights &&

View File

@ -4,6 +4,8 @@ import PropTypes from 'prop-types';
import { withSnackbar } from 'notistack'; import { withSnackbar } from 'notistack';
import * as notificationActions from '../../actions/notificationActions'; import * as notificationActions from '../../actions/notificationActions';
const notificationPosition = window.config.notificationPosition || 'right';
class Notifications extends Component class Notifications extends Component
{ {
displayed = []; displayed = [];
@ -45,7 +47,7 @@ class Notifications extends Component
autoHideDuration : notification.timeout, autoHideDuration : notification.timeout,
anchorOrigin : { anchorOrigin : {
vertical : 'bottom', vertical : 'bottom',
horizontal : 'left' horizontal : notificationPosition
} }
} }
); );

View File

@ -1,13 +1,14 @@
import React from 'react'; import React from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { micConsumerSelector } from '../Selectors'; import { passiveMicConsumerSelector } from '../Selectors';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import PeerAudio from './PeerAudio'; import PeerAudio from './PeerAudio';
const AudioPeers = (props) => const AudioPeers = (props) =>
{ {
const { const {
micConsumers micConsumers,
audioOutputDevice
} = props; } = props;
return ( return (
@ -19,6 +20,7 @@ const AudioPeers = (props) =>
<PeerAudio <PeerAudio
key={micConsumer.id} key={micConsumer.id}
audioTrack={micConsumer.track} audioTrack={micConsumer.track}
audioOutputDevice={audioOutputDevice}
/> />
); );
}) })
@ -29,12 +31,14 @@ const AudioPeers = (props) =>
AudioPeers.propTypes = AudioPeers.propTypes =
{ {
micConsumers : PropTypes.array micConsumers : PropTypes.array,
audioOutputDevice : PropTypes.string
}; };
const mapStateToProps = (state) => const mapStateToProps = (state) =>
({ ({
micConsumers : micConsumerSelector(state) micConsumers : passiveMicConsumerSelector(state),
audioOutputDevice : state.settings.selectedAudioOutputDevice
}); });
const AudioPeersContainer = connect( const AudioPeersContainer = connect(
@ -45,7 +49,10 @@ const AudioPeersContainer = connect(
areStatesEqual : (next, prev) => areStatesEqual : (next, prev) =>
{ {
return ( return (
prev.consumers === next.consumers prev.consumers === next.consumers &&
prev.room.spotlights === next.room.spotlights &&
prev.settings.selectedAudioOutputDevice ===
next.settings.selectedAudioOutputDevice
); );
} }
} }

View File

@ -10,6 +10,7 @@ export default class PeerAudio extends React.PureComponent
// Latest received audio track. // Latest received audio track.
// @type {MediaStreamTrack} // @type {MediaStreamTrack}
this._audioTrack = null; this._audioTrack = null;
this._audioOutputDevice = null;
} }
render() render()
@ -24,17 +25,21 @@ export default class PeerAudio extends React.PureComponent
componentDidMount() componentDidMount()
{ {
const { audioTrack } = this.props; const { audioTrack, audioOutputDevice } = this.props;
this._setTrack(audioTrack); this._setTrack(audioTrack);
this._setOutputDevice(audioOutputDevice);
} }
// eslint-disable-next-line camelcase componentDidUpdate(prevProps)
UNSAFE_componentWillReceiveProps(nextProps)
{ {
const { audioTrack } = nextProps; if (prevProps !== this.props)
{
const { audioTrack, audioOutputDevice } = this.props;
this._setTrack(audioTrack); this._setTrack(audioTrack);
this._setOutputDevice(audioOutputDevice);
}
} }
_setTrack(audioTrack) _setTrack(audioTrack)
@ -60,9 +65,23 @@ export default class PeerAudio extends React.PureComponent
audio.srcObject = null; audio.srcObject = null;
} }
} }
_setOutputDevice(audioOutputDevice)
{
if (this._audioOutputDevice === audioOutputDevice)
return;
this._audioOutputDevice = audioOutputDevice;
const { audio } = this.refs;
if (audioOutputDevice && typeof audio.setSinkId === 'function')
audio.setSinkId(audioOutputDevice);
}
} }
PeerAudio.propTypes = PeerAudio.propTypes =
{ {
audioTrack : PropTypes.any audioTrack : PropTypes.any,
audioOutputDevice : PropTypes.string
}; };

View File

@ -12,6 +12,7 @@ import { FormattedMessage } from 'react-intl';
import CookieConsent from 'react-cookie-consent'; import CookieConsent from 'react-cookie-consent';
import CssBaseline from '@material-ui/core/CssBaseline'; import CssBaseline from '@material-ui/core/CssBaseline';
import SwipeableDrawer from '@material-ui/core/SwipeableDrawer'; import SwipeableDrawer from '@material-ui/core/SwipeableDrawer';
import Drawer from '@material-ui/core/Drawer';
import Hidden from '@material-ui/core/Hidden'; import Hidden from '@material-ui/core/Hidden';
import Notifications from './Notifications/Notifications'; import Notifications from './Notifications/Notifications';
import MeetingDrawer from './MeetingDrawer/MeetingDrawer'; import MeetingDrawer from './MeetingDrawer/MeetingDrawer';
@ -23,8 +24,13 @@ import VideoWindow from './VideoWindow/VideoWindow';
import LockDialog from './AccessControl/LockDialog/LockDialog'; import LockDialog from './AccessControl/LockDialog/LockDialog';
import Settings from './Settings/Settings'; import Settings from './Settings/Settings';
import TopBar from './Controls/TopBar'; import TopBar from './Controls/TopBar';
import WakeLock from 'react-wakelock-react16';
import ExtraVideo from './Controls/ExtraVideo';
import ButtonControlBar from './Controls/ButtonControlBar';
import Help from './Controls/Help';
import About from './Controls/About';
const TIMEOUT = 5 * 1000; const TIMEOUT = window.config.hideTimeout || 5000;
const styles = (theme) => const styles = (theme) =>
({ ({
@ -40,6 +46,27 @@ const styles = (theme) =>
backgroundSize : 'cover', backgroundSize : 'cover',
backgroundRepeat : 'no-repeat' backgroundRepeat : 'no-repeat'
}, },
drawer :
{
width : '30vw',
flexShrink : 0,
[theme.breakpoints.down('lg')] :
{
width : '40vw'
},
[theme.breakpoints.down('md')] :
{
width : '50vw'
},
[theme.breakpoints.down('sm')] :
{
width : '70vw'
},
[theme.breakpoints.down('xs')] :
{
width : '90vw'
}
},
drawerPaper : drawerPaper :
{ {
width : '30vw', width : '30vw',
@ -138,7 +165,11 @@ class Room extends React.PureComponent
{ {
const { const {
room, room,
browser,
advancedMode, advancedMode,
showNotifications,
buttonControlBar,
drawerOverlayed,
toolAreaOpen, toolAreaOpen,
toggleToolArea, toggleToolArea,
classes, classes,
@ -151,6 +182,8 @@ class Room extends React.PureComponent
democratic : Democratic democratic : Democratic
}[room.mode]; }[room.mode];
const container = window !== undefined ? window.document.body : undefined;
return ( return (
<div className={classes.root}> <div className={classes.root}>
{ !isElectron() && false && { !isElectron() && false &&
@ -175,7 +208,9 @@ class Room extends React.PureComponent
<AudioPeers /> <AudioPeers />
{ showNotifications &&
<Notifications /> <Notifications />
}
<CssBaseline /> <CssBaseline />
@ -185,9 +220,11 @@ class Room extends React.PureComponent
onFullscreen={this.handleToggleFullscreen} onFullscreen={this.handleToggleFullscreen}
/> />
{ (browser.platform === 'mobile' || drawerOverlayed) ?
<nav> <nav>
<Hidden implementation='css'> <Hidden implementation='css'>
<SwipeableDrawer <SwipeableDrawer
container={container}
variant='temporary' variant='temporary'
anchor={theme.direction === 'rtl' ? 'right' : 'left'} anchor={theme.direction === 'rtl' ? 'right' : 'left'}
open={toolAreaOpen} open={toolAreaOpen}
@ -196,14 +233,42 @@ class Room extends React.PureComponent
classes={{ classes={{
paper : classes.drawerPaper paper : classes.drawerPaper
}} }}
ModalProps={{
keepMounted : true // Better open performance on mobile.
}}
> >
<MeetingDrawer closeDrawer={toggleToolArea} /> <MeetingDrawer closeDrawer={toggleToolArea} />
</SwipeableDrawer> </SwipeableDrawer>
</Hidden> </Hidden>
</nav> </nav>
:
<nav className={toolAreaOpen ? classes.drawer : null}>
<Hidden implementation='css'>
<Drawer
variant='persistent'
anchor={theme.direction === 'rtl' ? 'right' : 'left'}
open={toolAreaOpen}
onClose={() => toggleToolArea()}
classes={{
paper : classes.drawerPaper
}}
>
<MeetingDrawer closeDrawer={toggleToolArea} />
</Drawer>
</Hidden>
</nav>
}
{ browser.platform === 'mobile' && browser.os !== 'ios' &&
<WakeLock />
}
<View advancedMode={advancedMode} /> <View advancedMode={advancedMode} />
{ buttonControlBar &&
<ButtonControlBar />
}
{ room.lockDialogOpen && { room.lockDialogOpen &&
<LockDialog /> <LockDialog />
} }
@ -211,6 +276,17 @@ class Room extends React.PureComponent
{ room.settingsOpen && { room.settingsOpen &&
<Settings /> <Settings />
} }
{ room.extraVideoOpen &&
<ExtraVideo />
}
{ room.helpOpen &&
<Help />
}
{ room.aboutOpen &&
<About />
}
</div> </div>
); );
} }
@ -219,7 +295,11 @@ class Room extends React.PureComponent
Room.propTypes = Room.propTypes =
{ {
room : appPropTypes.Room.isRequired, room : appPropTypes.Room.isRequired,
browser : PropTypes.object.isRequired,
advancedMode : PropTypes.bool.isRequired, advancedMode : PropTypes.bool.isRequired,
showNotifications : PropTypes.bool.isRequired,
buttonControlBar : PropTypes.bool.isRequired,
drawerOverlayed : PropTypes.bool.isRequired,
toolAreaOpen : PropTypes.bool.isRequired, toolAreaOpen : PropTypes.bool.isRequired,
setToolbarsVisible : PropTypes.func.isRequired, setToolbarsVisible : PropTypes.func.isRequired,
toggleToolArea : PropTypes.func.isRequired, toggleToolArea : PropTypes.func.isRequired,
@ -230,7 +310,11 @@ Room.propTypes =
const mapStateToProps = (state) => const mapStateToProps = (state) =>
({ ({
room : state.room, room : state.room,
browser : state.me.browser,
advancedMode : state.settings.advancedMode, advancedMode : state.settings.advancedMode,
showNotifications : state.settings.showNotifications,
buttonControlBar : state.settings.buttonControlBar,
drawerOverlayed : state.settings.drawerOverlayed,
toolAreaOpen : state.toolarea.toolAreaOpen toolAreaOpen : state.toolarea.toolAreaOpen
}); });
@ -255,7 +339,11 @@ export default connect(
{ {
return ( return (
prev.room === next.room && prev.room === next.room &&
prev.me.browser === next.me.browser &&
prev.settings.advancedMode === next.settings.advancedMode && prev.settings.advancedMode === next.settings.advancedMode &&
prev.settings.showNotifications === next.settings.showNotifications &&
prev.settings.buttonControlBar === next.settings.buttonControlBar &&
prev.settings.drawerOverlayed === next.settings.drawerOverlayed &&
prev.toolarea.toolAreaOpen === next.toolarea.toolAreaOpen prev.toolarea.toolAreaOpen === next.toolarea.toolAreaOpen
); );
} }

View File

@ -1,5 +1,8 @@
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
const meRolesSelect = (state) => state.me.roles;
const roomPermissionsSelect = (state) => state.room.roomPermissions;
const roomAllowWhenRoleMissing = (state) => state.room.allowWhenRoleMissing;
const producersSelect = (state) => state.producers; const producersSelect = (state) => state.producers;
const consumersSelect = (state) => state.consumers; const consumersSelect = (state) => state.consumers;
const spotlightsSelector = (state) => state.room.spotlights; const spotlightsSelector = (state) => state.room.spotlights;
@ -13,6 +16,11 @@ const peersKeySelector = createSelector(
(peers) => Object.keys(peers) (peers) => Object.keys(peers)
); );
export const peersValueSelector = createSelector(
peersSelector,
(peers) => Object.values(peers)
);
export const lobbyPeersKeySelector = createSelector( export const lobbyPeersKeySelector = createSelector(
lobbyPeersSelector, lobbyPeersSelector,
(lobbyPeers) => Object.keys(lobbyPeers) (lobbyPeers) => Object.keys(lobbyPeers)
@ -33,6 +41,11 @@ export const screenProducersSelector = createSelector(
(producers) => Object.values(producers).filter((producer) => producer.source === 'screen') (producers) => Object.values(producers).filter((producer) => producer.source === 'screen')
); );
export const extraVideoProducersSelector = createSelector(
producersSelect,
(producers) => Object.values(producers).filter((producer) => producer.source === 'extravideo')
);
export const micProducerSelector = createSelector( export const micProducerSelector = createSelector(
producersSelect, producersSelect,
(producers) => Object.values(producers).find((producer) => producer.source === 'mic') (producers) => Object.values(producers).find((producer) => producer.source === 'mic')
@ -63,6 +76,33 @@ export const screenConsumerSelector = createSelector(
(consumers) => Object.values(consumers).filter((consumer) => consumer.source === 'screen') (consumers) => Object.values(consumers).filter((consumer) => consumer.source === 'screen')
); );
export const spotlightScreenConsumerSelector = createSelector(
spotlightsSelector,
consumersSelect,
(spotlights, consumers) =>
Object.values(consumers).filter(
(consumer) => consumer.source === 'screen' && spotlights.includes(consumer.peerId)
)
);
export const spotlightExtraVideoConsumerSelector = createSelector(
spotlightsSelector,
consumersSelect,
(spotlights, consumers) =>
Object.values(consumers).filter(
(consumer) => consumer.source === 'extravideo' && spotlights.includes(consumer.peerId)
)
);
export const passiveMicConsumerSelector = createSelector(
spotlightsSelector,
consumersSelect,
(spotlights, consumers) =>
Object.values(consumers).filter(
(consumer) => consumer.source === 'mic' && !spotlights.includes(consumer.peerId)
)
);
export const spotlightsLengthSelector = createSelector( export const spotlightsLengthSelector = createSelector(
spotlightsSelector, spotlightsSelector,
(spotlights) => spotlights.length (spotlights) => spotlights.length
@ -74,35 +114,83 @@ export const spotlightPeersSelector = createSelector(
(spotlights, peers) => peers.filter((peerId) => spotlights.includes(peerId)) (spotlights, peers) => peers.filter((peerId) => spotlights.includes(peerId))
); );
export const spotlightSortedPeersSelector = createSelector(
spotlightsSelector,
peersValueSelector,
(spotlights, peers) =>
peers.filter((peer) => spotlights.includes(peer.id) && !peer.raisedHand)
.sort((a, b) => String(a.displayName || '').localeCompare(String(b.displayName || '')))
);
const raisedHandSortedPeers = createSelector(
peersValueSelector,
(peers) => peers.filter((peer) => peer.raisedHand)
.sort((a, b) => a.raisedHandTimestamp - b.raisedHandTimestamp)
);
const peersSortedSelector = createSelector(
spotlightsSelector,
peersValueSelector,
(spotlights, peers) =>
peers.filter((peer) => !spotlights.includes(peer.id) && !peer.raisedHand)
.sort((a, b) => String(a.displayName || '').localeCompare(String(b.displayName || '')))
);
export const participantListSelector = createSelector(
raisedHandSortedPeers,
spotlightSortedPeersSelector,
peersSortedSelector,
(raisedHands, spotlights, peers) =>
[ ...raisedHands, ...spotlights, ...peers ]
);
export const peersLengthSelector = createSelector( export const peersLengthSelector = createSelector(
peersSelector, peersSelector,
(peers) => Object.values(peers).length (peers) => Object.values(peers).length
); );
export const passivePeersSelector = createSelector( export const passivePeersSelector = createSelector(
peersKeySelector, peersValueSelector,
spotlightsSelector, spotlightsSelector,
(peers, spotlights) => peers.filter((peerId) => !spotlights.includes(peerId)) (peers, spotlights) => peers.filter((peer) => !spotlights.includes(peer.id))
.sort((a, b) => String(a.displayName || '').localeCompare(String(b.displayName || '')))
);
export const raisedHandsSelector = createSelector(
peersValueSelector,
(peers) => peers.reduce((a, b) => (a + (b.raisedHand ? 1 : 0)), 0)
); );
export const videoBoxesSelector = createSelector( export const videoBoxesSelector = createSelector(
spotlightsLengthSelector, spotlightsLengthSelector,
screenProducersSelector, screenProducersSelector,
screenConsumerSelector, spotlightScreenConsumerSelector,
(spotlightsLength, screenProducers, screenConsumers) => extraVideoProducersSelector,
spotlightsLength + 1 + screenProducers.length + screenConsumers.length spotlightExtraVideoConsumerSelector,
(
spotlightsLength,
screenProducers,
screenConsumers,
extraVideoProducers,
extraVideoConsumers
) =>
spotlightsLength + 1 + screenProducers.length +
screenConsumers.length + extraVideoProducers.length +
extraVideoConsumers.length
); );
export const meProducersSelector = createSelector( export const meProducersSelector = createSelector(
micProducerSelector, micProducerSelector,
webcamProducerSelector, webcamProducerSelector,
screenProducerSelector, screenProducerSelector,
(micProducer, webcamProducer, screenProducer) => extraVideoProducersSelector,
(micProducer, webcamProducer, screenProducer, extraVideoProducers) =>
{ {
return { return {
micProducer, micProducer,
webcamProducer, webcamProducer,
screenProducer screenProducer,
extraVideoProducers
}; };
} }
); );
@ -125,8 +213,60 @@ export const makePeerConsumerSelector = () =>
consumersArray.find((consumer) => consumer.source === 'webcam'); consumersArray.find((consumer) => consumer.source === 'webcam');
const screenConsumer = const screenConsumer =
consumersArray.find((consumer) => consumer.source === 'screen'); consumersArray.find((consumer) => consumer.source === 'screen');
const extraVideoConsumers =
consumersArray.filter((consumer) => consumer.source === 'extravideo');
return { micConsumer, webcamConsumer, screenConsumer }; return { micConsumer, webcamConsumer, screenConsumer, extraVideoConsumers };
}
);
};
// Very important that the Components that use this
// selector need to check at least these state changes:
//
// areStatesEqual : (next, prev) =>
// {
// return (
// prev.room.roomPermissions === next.room.roomPermissions &&
// prev.room.allowWhenRoleMissing === next.room.allowWhenRoleMissing &&
// prev.peers === next.peers &&
// prev.me.roles === next.me.roles
// );
// }
export const makePermissionSelector = (permission) =>
{
return createSelector(
meRolesSelect,
roomPermissionsSelect,
roomAllowWhenRoleMissing,
peersValueSelector,
(roles, roomPermissions, allowWhenRoleMissing, peers) =>
{
if (!roomPermissions)
return false;
const permitted = roles.some((role) =>
roomPermissions[permission].includes(role)
);
if (permitted)
return true;
if (!allowWhenRoleMissing)
return false;
// Allow if config is set, and no one is present
if (allowWhenRoleMissing.includes(permission) &&
peers.filter(
(peer) =>
peer.roles.some(
(role) => roomPermissions[permission].includes(role)
)
).length === 0
)
return true;
return false;
} }
); );
}; };

View File

@ -0,0 +1,135 @@
import React from 'react';
import { connect } from 'react-redux';
import { withStyles } from '@material-ui/core/styles';
import { withRoomContext } from '../../RoomContext';
import * as settingsActions from '../../actions/settingsActions';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import { useIntl, FormattedMessage } from 'react-intl';
import MenuItem from '@material-ui/core/MenuItem';
import FormHelperText from '@material-ui/core/FormHelperText';
import FormControl from '@material-ui/core/FormControl';
import FormControlLabel from '@material-ui/core/FormControlLabel';
import Select from '@material-ui/core/Select';
import Switch from '@material-ui/core/Switch';
const styles = (theme) =>
({
setting :
{
padding : theme.spacing(2)
},
formControl :
{
display : 'flex'
},
switchLabel : {
justifyContent : 'space-between',
flex : 'auto',
display : 'flex',
padding : theme.spacing(1),
marginRight : 0
}
});
const AdvancedSettings = ({
roomClient,
settings,
onToggleAdvancedMode,
onToggleNotificationSounds,
classes
}) =>
{
const intl = useIntl();
return (
<React.Fragment>
<FormControlLabel
className={classnames(classes.setting, classes.switchLabel)}
control={<Switch checked={settings.advancedMode} onChange={onToggleAdvancedMode} value='advancedMode' />}
labelPlacement='start'
label={intl.formatMessage({
id : 'settings.advancedMode',
defaultMessage : 'Advanced mode'
})}
/>
<FormControlLabel
className={classnames(classes.setting, classes.switchLabel)}
control={<Switch checked={settings.notificationSounds} onChange={onToggleNotificationSounds} value='notificationSounds' />}
labelPlacement='start'
label={intl.formatMessage({
id : 'settings.notificationSounds',
defaultMessage : 'Notification sounds'
})}
/>
{ !window.config.lockLastN &&
<form className={classes.setting} autoComplete='off'>
<FormControl className={classes.formControl}>
<Select
value={settings.lastN || ''}
onChange={(event) =>
{
if (event.target.value)
roomClient.changeMaxSpotlights(event.target.value);
}}
name='Last N'
autoWidth
className={classes.selectEmpty}
>
{ Array.from(
{ length: window.config.maxLastN || 10 },
(_, i) => i + 1
).map((lastN) =>
{
return (
<MenuItem key={lastN} value={lastN}>
{lastN}
</MenuItem>
);
})}
</Select>
<FormHelperText>
<FormattedMessage
id='settings.lastn'
defaultMessage='Number of visible videos'
/>
</FormHelperText>
</FormControl>
</form>
}
</React.Fragment>
);
};
AdvancedSettings.propTypes =
{
roomClient : PropTypes.any.isRequired,
settings : PropTypes.object.isRequired,
onToggleAdvancedMode : PropTypes.func.isRequired,
onToggleNotificationSounds : PropTypes.func.isRequired,
classes : PropTypes.object.isRequired
};
const mapStateToProps = (state) =>
({
settings : state.settings
});
const mapDispatchToProps = {
onToggleAdvancedMode : settingsActions.toggleAdvancedMode,
onToggleNotificationSounds : settingsActions.toggleNotificationSounds
};
export default withRoomContext(connect(
mapStateToProps,
mapDispatchToProps,
null,
{
areStatesEqual : (next, prev) =>
{
return (
prev.settings === next.settings
);
}
}
)(withStyles(styles)(AdvancedSettings)));

View File

@ -0,0 +1,196 @@
import React from 'react';
import { connect } from 'react-redux';
import * as appPropTypes from '../appPropTypes';
import { withStyles } from '@material-ui/core/styles';
import * as roomActions from '../../actions/roomActions';
import * as settingsActions from '../../actions/settingsActions';
import classnames from 'classnames';
import PropTypes from 'prop-types';
import { useIntl, FormattedMessage } from 'react-intl';
import MenuItem from '@material-ui/core/MenuItem';
import FormHelperText from '@material-ui/core/FormHelperText';
import FormControl from '@material-ui/core/FormControl';
import FormControlLabel from '@material-ui/core/FormControlLabel';
import Select from '@material-ui/core/Select';
import Switch from '@material-ui/core/Switch';
const styles = (theme) =>
({
setting :
{
padding : theme.spacing(2)
},
formControl :
{
display : 'flex'
},
switchLabel : {
justifyContent : 'space-between',
flex : 'auto',
display : 'flex',
padding : theme.spacing(1),
marginRight : 0
}
});
const AppearenceSettings = ({
isMobile,
room,
settings,
onTogglePermanentTopBar,
onToggleHiddenControls,
onToggleButtonControlBar,
onToggleShowNotifications,
onToggleDrawerOverlayed,
handleChangeMode,
classes
}) =>
{
const intl = useIntl();
const modes = [ {
value : 'democratic',
label : intl.formatMessage({
id : 'label.democratic',
defaultMessage : 'Democratic view'
})
}, {
value : 'filmstrip',
label : intl.formatMessage({
id : 'label.filmstrip',
defaultMessage : 'Filmstrip view'
})
} ];
return (
<React.Fragment>
<form className={classes.setting} autoComplete='off'>
<FormControl className={classes.formControl}>
<Select
value={room.mode || ''}
onChange={(event) =>
{
if (event.target.value)
handleChangeMode(event.target.value);
}}
name={intl.formatMessage({
id : 'settings.layout',
defaultMessage : 'Room layout'
})}
autoWidth
className={classes.selectEmpty}
>
{ modes.map((mode, index) =>
{
return (
<MenuItem key={index} value={mode.value}>
{mode.label}
</MenuItem>
);
})}
</Select>
<FormHelperText>
<FormattedMessage
id='settings.selectRoomLayout'
defaultMessage='Select room layout'
/>
</FormHelperText>
</FormControl>
</form>
<FormControlLabel
className={classnames(classes.setting, classes.switchLabel)}
control={
<Switch checked={settings.permanentTopBar} onChange={onTogglePermanentTopBar} value='permanentTopBar' />}
labelPlacement='start'
label={intl.formatMessage({
id : 'settings.permanentTopBar',
defaultMessage : 'Permanent top bar'
})}
/>
<FormControlLabel
className={classnames(classes.setting, classes.switchLabel)}
control={<Switch checked={settings.hiddenControls} onChange={onToggleHiddenControls} value='hiddenControls' />}
labelPlacement='start'
label={intl.formatMessage({
id : 'settings.hiddenControls',
defaultMessage : 'Hidden media controls'
})}
/>
<FormControlLabel
className={classnames(classes.setting, classes.switchLabel)}
control={<Switch checked={settings.buttonControlBar} onChange={onToggleButtonControlBar} value='buttonControlBar' />}
labelPlacement='start'
label={intl.formatMessage({
id : 'settings.buttonControlBar',
defaultMessage : 'Separate media controls'
})}
/>
{ !isMobile &&
<FormControlLabel
className={classnames(classes.setting, classes.switchLabel)}
control={<Switch checked={settings.drawerOverlayed} onChange={onToggleDrawerOverlayed} value='drawerOverlayed' />}
labelPlacement='start'
label={intl.formatMessage({
id : 'settings.drawerOverlayed',
defaultMessage : 'Side drawer over content'
})}
/>
}
<FormControlLabel
className={classnames(classes.setting, classes.switchLabel)}
control={<Switch checked={settings.showNotifications} onChange={onToggleShowNotifications} value='showNotifications' />}
labelPlacement='start'
label={intl.formatMessage({
id : 'settings.showNotifications',
defaultMessage : 'Show notifications'
})}
/>
</React.Fragment>
);
};
AppearenceSettings.propTypes =
{
isMobile : PropTypes.bool.isRequired,
room : appPropTypes.Room.isRequired,
settings : PropTypes.object.isRequired,
onTogglePermanentTopBar : PropTypes.func.isRequired,
onToggleHiddenControls : PropTypes.func.isRequired,
onToggleButtonControlBar : PropTypes.func.isRequired,
onToggleShowNotifications : PropTypes.func.isRequired,
onToggleDrawerOverlayed : PropTypes.func.isRequired,
handleChangeMode : PropTypes.func.isRequired,
classes : PropTypes.object.isRequired
};
const mapStateToProps = (state) =>
({
isMobile : state.me.browser.platform === 'mobile',
room : state.room,
settings : state.settings
});
const mapDispatchToProps = {
onTogglePermanentTopBar : settingsActions.togglePermanentTopBar,
onToggleHiddenControls : settingsActions.toggleHiddenControls,
onToggleShowNotifications : settingsActions.toggleShowNotifications,
onToggleButtonControlBar : settingsActions.toggleButtonControlBar,
onToggleDrawerOverlayed : settingsActions.toggleDrawerOverlayed,
handleChangeMode : roomActions.setDisplayMode
};
export default connect(
mapStateToProps,
mapDispatchToProps,
null,
{
areStatesEqual : (next, prev) =>
{
return (
prev.me.browser === next.me.browser &&
prev.room === next.room &&
prev.settings === next.settings
);
}
}
)(withStyles(styles)(AppearenceSettings));

View File

@ -0,0 +1,578 @@
import React from 'react';
import { connect } from 'react-redux';
import * as appPropTypes from '../appPropTypes';
import { withStyles } from '@material-ui/core/styles';
import { withRoomContext } from '../../RoomContext';
import * as settingsActions from '../../actions/settingsActions';
import PropTypes from 'prop-types';
import { useIntl, FormattedMessage } from 'react-intl';
import classnames from 'classnames';
import MenuItem from '@material-ui/core/MenuItem';
import FormHelperText from '@material-ui/core/FormHelperText';
import FormControl from '@material-ui/core/FormControl';
import FormControlLabel from '@material-ui/core/FormControlLabel';
import Select from '@material-ui/core/Select';
import Slider from '@material-ui/core/Slider';
import Typography from '@material-ui/core/Typography';
import Collapse from '@material-ui/core/Collapse';
import List from '@material-ui/core/List';
import ListItem from '@material-ui/core/ListItem';
import ListItemText from '@material-ui/core/ListItemText';
import ExpandLess from '@material-ui/icons/ExpandLess';
import ExpandMore from '@material-ui/icons/ExpandMore';
import Switch from '@material-ui/core/Switch';
const NoiseSlider = withStyles(
{
root :
{
color : '#3880ff',
height : 2,
padding : '15px 0'
},
track : {
height : 2
},
rail : {
height : 2,
opacity : 0.2
},
mark : {
backgroundColor : '#bfbfbf',
height : 10,
width : 3,
marginTop : -3
},
markActive : {
opacity : 1,
backgroundColor : 'currentColor'
}
})(Slider);
const styles = (theme) => ({
setting :
{
padding : theme.spacing(2)
},
margin :
{
height : theme.spacing(3)
},
root : {
width : '100%',
backgroundColor : theme.palette.background.paper
},
switchLabel : {
justifyContent : 'space-between',
flex : 'auto',
display : 'flex',
padding : theme.spacing(1)
},
nested : {
display : 'block',
paddingTop : 0,
paddingBottom : 0,
paddingLeft : '25px',
paddingRight : '25px'
},
formControl :
{
display : 'flex'
}
});
const MediaSettings = ({
setEchoCancellation,
setAutoGainControl,
setNoiseSuppression,
setVoiceActivatedUnmute,
roomClient,
me,
volume,
settings,
classes
}) =>
{
const intl = useIntl();
const [ audioSettingsOpen, setAudioSettingsOpen ] = React.useState(false);
const [ videoSettingsOpen, setVideoSettingsOpen ] = React.useState(false);
const resolutions = [ {
value : 'low',
label : intl.formatMessage({
id : 'label.low',
defaultMessage : 'Low'
})
},
{
value : 'medium',
label : intl.formatMessage({
id : 'label.medium',
defaultMessage : 'Medium'
})
},
{
value : 'high',
label : intl.formatMessage({
id : 'label.high',
defaultMessage : 'High (HD)'
})
},
{
value : 'veryhigh',
label : intl.formatMessage({
id : 'label.veryHigh',
defaultMessage : 'Very high (FHD)'
})
},
{
value : 'ultra',
label : intl.formatMessage({
id : 'label.ultra',
defaultMessage : 'Ultra (UHD)'
})
} ];
let webcams;
if (me.webcamDevices)
webcams = Object.values(me.webcamDevices);
else
webcams = [];
let audioDevices;
if (me.audioDevices)
audioDevices = Object.values(me.audioDevices);
else
audioDevices = [];
let audioOutputDevices;
if (me.audioOutputDevices)
audioOutputDevices = Object.values(me.audioOutputDevices);
else
audioOutputDevices = [];
return (
<React.Fragment>
<form className={classes.setting} autoComplete='off'>
<FormControl className={classes.formControl}>
<Select
value={settings.selectedWebcam || ''}
onChange={(event) =>
{
if (event.target.value)
{
roomClient.updateWebcam({
restart : true,
newDeviceId : event.target.value
});
}
}}
displayEmpty
name={intl.formatMessage({
id : 'settings.camera',
defaultMessage : 'Camera'
})}
autoWidth
className={classes.selectEmpty}
disabled={webcams.length === 0 || me.webcamInProgress}
>
{ webcams.map((webcam, index) =>
{
return (
<MenuItem key={index} value={webcam.deviceId}>{webcam.label}</MenuItem>
);
})}
</Select>
<FormHelperText>
{ webcams.length > 0 ?
intl.formatMessage({
id : 'settings.selectCamera',
defaultMessage : 'Select video device'
})
:
intl.formatMessage({
id : 'settings.cantSelectCamera',
defaultMessage : 'Unable to select video device'
})
}
</FormHelperText>
</FormControl>
<List className={classes.root} component='nav'>
<ListItem button onClick={() => setVideoSettingsOpen(!videoSettingsOpen)}>
<ListItemText primary={intl.formatMessage({
id : 'settings.showAdvancedVideo',
defaultMessage : 'Advanced video settings'
})}
/>
{videoSettingsOpen ? <ExpandLess /> : <ExpandMore />}
</ListItem>
<Collapse in={videoSettingsOpen} timeout='auto'>
<FormControl className={classes.formControl}>
<Select
value={settings.resolution || ''}
onChange={(event) =>
{
if (event.target.value)
roomClient.updateWebcam({ newResolution: event.target.value });
}}
name='Video resolution'
autoWidth
className={classes.selectEmpty}
>
{resolutions.map((resolution, index) =>
{
return (
<MenuItem key={index} value={resolution.value}>
{resolution.label}
</MenuItem>
);
})}
</Select>
<FormHelperText>
<FormattedMessage
id='settings.resolution'
defaultMessage='Select your video resolution'
/>
</FormHelperText>
</FormControl>
{ /*
<FormControl className={classes.formControl}>
<Select
value={settings.frameRate || ''}
onChange={(event) =>
{
if (event.target.value)
roomClient.updateWebcam({ newFrameRate: event.target.value });
}}
name='Frame rate'
autoWidth
className={classes.selectEmpty}
>
{ [ 1, 5, 10, 15, 20, 25, 30 ].map((frameRate) =>
{
return (
<MenuItem key={frameRate} value={frameRate}>
{frameRate}
</MenuItem>
);
})}
</Select>
<FormHelperText>
<FormattedMessage
id='settings.frameRate'
defaultMessage='Select your video frame rate'
/>
</FormHelperText>
</FormControl>
<FormControl className={classes.formControl}>
<Select
value={settings.screenSharingResolution || ''}
onChange={(event) =>
{
if (event.target.value)
roomClient.updateScreenSharing({ newResolution: event.target.value });
}}
name='Screen sharing resolution'
autoWidth
className={classes.selectEmpty}
>
{resolutions.map((resolution, index) =>
{
return (
<MenuItem key={index} value={resolution.value}>
{resolution.label}
</MenuItem>
);
})}
</Select>
<FormHelperText>
<FormattedMessage
id='settings.screenSharingResolution'
defaultMessage='Select your screen sharing resolution'
/>
</FormHelperText>
</FormControl>
*/ }
<FormControl className={classes.formControl}>
<Select
value={settings.screenSharingFrameRate || ''}
onChange={(event) =>
{
if (event.target.value)
roomClient.updateScreenSharing({ newFrameRate: event.target.value });
}}
name='Frame rate'
autoWidth
className={classes.selectEmpty}
>
{ [ 1, 5, 10, 15, 20, 25, 30 ].map((screenSharingFrameRate) =>
{
return (
<MenuItem key={screenSharingFrameRate} value={screenSharingFrameRate}>
{screenSharingFrameRate}
</MenuItem>
);
})}
</Select>
<FormHelperText>
<FormattedMessage
id='settings.screenSharingFrameRate'
defaultMessage='Select your screen sharing frame rate'
/>
</FormHelperText>
</FormControl>
</Collapse>
</List>
</form>
<form className={classes.setting} autoComplete='off'>
<FormControl className={classes.formControl}>
<Select
value={settings.selectedAudioDevice || ''}
onChange={(event) =>
{
if (event.target.value)
roomClient.updateMic({ restart: true, newDeviceId: event.target.value });
}}
displayEmpty
name={intl.formatMessage({
id : 'settings.audio',
defaultMessage : 'Audio input device'
})}
autoWidth
className={classes.selectEmpty}
disabled={audioDevices.length === 0 || me.audioInProgress}
>
{ audioDevices.map((audio, index) =>
{
return (
<MenuItem key={index} value={audio.deviceId}>{audio.label==='' ? index+1 : audio.label }</MenuItem>
);
})}
</Select>
<FormHelperText>
{ audioDevices.length > 0 ?
intl.formatMessage({
id : 'settings.selectAudio',
defaultMessage : 'Select audio input device'
})
:
intl.formatMessage({
id : 'settings.cantSelectAudio',
defaultMessage : 'Unable to select audio input device'
})
}
</FormHelperText>
</FormControl>
{ 'audioOutputSupportedBrowsers' in window.config &&
window.config.audioOutputSupportedBrowsers.includes(me.browser.name) &&
<FormControl className={classes.formControl}>
<Select
value={settings.selectedAudioOutputDevice || ''}
onChange={(event) =>
{
if (event.target.value)
roomClient.changeAudioOutputDevice(event.target.value);
}}
displayEmpty
name={intl.formatMessage({
id : 'settings.audioOutput',
defaultMessage : 'Audio output device'
})}
autoWidth
className={classes.selectEmpty}
disabled={audioOutputDevices.length === 0 || me.audioOutputInProgress}
>
{ audioOutputDevices.map((audioOutput, index) =>
{
return (
<MenuItem
key={index}
value={audioOutput.deviceId}
>
{audioOutput.label}
</MenuItem>
);
})}
</Select>
<FormHelperText>
{ audioOutputDevices.length > 0 ?
intl.formatMessage({
id : 'settings.selectAudioOutput',
defaultMessage : 'Select audio output device'
})
:
intl.formatMessage({
id : 'settings.cantSelectAudioOutput',
defaultMessage : 'Unable to select audio output device'
})
}
</FormHelperText>
</FormControl>
}
<List className={classes.root} component='nav'>
<ListItem button onClick={() => setAudioSettingsOpen(!audioSettingsOpen)}>
<ListItemText primary={intl.formatMessage({
id : 'settings.showAdvancedAudio',
defaultMessage : 'Advanced audio settings'
})}
/>
{audioSettingsOpen ? <ExpandLess /> : <ExpandMore />}
</ListItem>
<Collapse in={audioSettingsOpen} timeout='auto'>
<List component='div'>
<ListItem className={classes.nested}>
<FormControlLabel
className={classnames(classes.setting, classes.switchLabel)}
control={
<Switch color='secondary'
checked={settings.echoCancellation}
onChange={
(event) =>
{
setEchoCancellation(event.target.checked);
roomClient.updateMic();
}}
/>}
labelPlacement='start'
label={intl.formatMessage({
id : 'settings.echoCancellation',
defaultMessage : 'Echo cancellation'
})}
/>
</ListItem>
<ListItem className={classes.nested}>
<FormControlLabel
className={classnames(classes.setting, classes.switchLabel)}
control={
<Switch color='secondary'
checked={settings.autoGainControl} onChange={
(event) =>
{
setAutoGainControl(event.target.checked);
roomClient.updateMic();
}}
/>}
labelPlacement='start'
label={intl.formatMessage({
id : 'settings.autoGainControl',
defaultMessage : 'Auto gain control'
})}
/>
</ListItem>
<ListItem className={classes.nested}>
<FormControlLabel
className={classnames(classes.setting, classes.switchLabel)}
control={
<Switch color='secondary'
checked={settings.noiseSuppression} onChange={
(event) =>
{
setNoiseSuppression(event.target.checked);
roomClient.updateMic();
}}
/>}
labelPlacement='start'
label={intl.formatMessage({
id : 'settings.noiseSuppression',
defaultMessage : 'Noise suppression'
})}
/>
</ListItem>
<ListItem className={classes.nested}>
<FormControlLabel
className={classnames(classes.setting, classes.switchLabel)}
control={
<Switch color='secondary'
checked={settings.voiceActivatedUnmute} onChange={
(event) =>
{
setVoiceActivatedUnmute(event.target.checked);
}}
/>}
labelPlacement='start'
label={intl.formatMessage({
id : 'settings.voiceActivatedUnmute',
defaultMessage : 'Voice activated unmute'
})}
/>
</ListItem>
<ListItem className={classes.nested}>
<div className={classes.margin} />
<Typography gutterBottom>
{
intl.formatMessage({
id : 'settings.noiseThreshold',
defaultMessage : 'Noise threshold'
})
}:
</Typography>
<NoiseSlider className={classnames(classes.slider, classnames.setting)}
key={'noise-threshold-slider'}
min={-100}
value={settings.noiseThreshold}
max={0}
valueLabelDisplay={'auto'}
onChange={
(event, value) =>
{
roomClient._setNoiseThreshold(value);
}}
marks={[ { value: volume, label: `${volume} dB` } ]}
/>
</ListItem>
</List>
</Collapse>
</List>
</form>
</React.Fragment>
);
};
MediaSettings.propTypes =
{
roomClient : PropTypes.any.isRequired,
setEchoCancellation : PropTypes.func.isRequired,
setAutoGainControl : PropTypes.func.isRequired,
setNoiseSuppression : PropTypes.func.isRequired,
setVoiceActivatedUnmute : PropTypes.func.isRequired,
me : appPropTypes.Me.isRequired,
volume : PropTypes.number,
settings : PropTypes.object.isRequired,
classes : PropTypes.object.isRequired
};
const mapStateToProps = (state) =>
{
return {
me : state.me,
volume : state.peerVolumes[state.me.id],
settings : state.settings
};
};
const mapDispatchToProps = {
setEchoCancellation : settingsActions.setEchoCancellation,
setAutoGainControl : settingsActions.setAutoGainControl,
setNoiseSuppression : settingsActions.setNoiseSuppression,
setVoiceActivatedUnmute : settingsActions.setVoiceActivatedUnmute
};
export default withRoomContext(connect(
mapStateToProps,
mapDispatchToProps,
null,
{
areStatesEqual : (next, prev) =>
{
return (
prev.me === next.me &&
prev.settings === next.settings &&
prev.peerVolumes[prev.me.id] === next[next.me.id]
);
}
}
)(withStyles(styles)(MediaSettings)));

View File

@ -1,22 +1,25 @@
import React from 'react'; import React from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import * as appPropTypes from '../appPropTypes';
import { withStyles } from '@material-ui/core/styles'; import { withStyles } from '@material-ui/core/styles';
import { withRoomContext } from '../../RoomContext';
import * as roomActions from '../../actions/roomActions'; import * as roomActions from '../../actions/roomActions';
import * as settingsActions from '../../actions/settingsActions';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { useIntl, FormattedMessage } from 'react-intl'; import { useIntl, FormattedMessage } from 'react-intl';
import Tabs from '@material-ui/core/Tabs';
import Tab from '@material-ui/core/Tab';
import MediaSettings from './MediaSettings';
import AppearenceSettings from './AppearenceSettings';
import AdvancedSettings from './AdvancedSettings';
import Dialog from '@material-ui/core/Dialog'; import Dialog from '@material-ui/core/Dialog';
import DialogTitle from '@material-ui/core/DialogTitle'; import DialogTitle from '@material-ui/core/DialogTitle';
import DialogActions from '@material-ui/core/DialogActions'; import DialogActions from '@material-ui/core/DialogActions';
import Button from '@material-ui/core/Button'; import Button from '@material-ui/core/Button';
import MenuItem from '@material-ui/core/MenuItem';
import FormHelperText from '@material-ui/core/FormHelperText'; const tabs =
import FormControl from '@material-ui/core/FormControl'; [
import FormControlLabel from '@material-ui/core/FormControlLabel'; 'media',
import Select from '@material-ui/core/Select'; 'appearence',
import Checkbox from '@material-ui/core/Checkbox'; 'advanced'
];
const styles = (theme) => const styles = (theme) =>
({ ({
@ -43,99 +46,27 @@ const styles = (theme) =>
width : '90vw' width : '90vw'
} }
}, },
setting : tabsHeader :
{ {
padding : theme.spacing(2) flexGrow : 1
},
formControl :
{
display : 'flex'
} }
}); });
const Settings = ({ const Settings = ({
roomClient, currentSettingsTab,
room, settingsOpen,
me,
settings,
onToggleAdvancedMode,
onTogglePermanentTopBar,
handleCloseSettings, handleCloseSettings,
handleChangeMode, setSettingsTab,
classes classes
}) => }) =>
{ {
const intl = useIntl(); const intl = useIntl();
const modes = [ {
value : 'democratic',
label : intl.formatMessage({
id : 'label.democratic',
defaultMessage : 'Democratic view'
})
}, {
value : 'filmstrip',
label : intl.formatMessage({
id : 'label.filmstrip',
defaultMessage : 'Filmstrip view'
})
} ];
const resolutions = [ {
value : 'low',
label : intl.formatMessage({
id : 'label.low',
defaultMessage : 'Low'
})
},
{
value : 'medium',
label : intl.formatMessage({
id : 'label.medium',
defaultMessage : 'Medium'
})
},
{
value : 'high',
label : intl.formatMessage({
id : 'label.high',
defaultMessage : 'High (HD)'
})
},
{
value : 'veryhigh',
label : intl.formatMessage({
id : 'label.veryHigh',
defaultMessage : 'Very high (FHD)'
})
},
{
value : 'ultra',
label : intl.formatMessage({
id : 'label.ultra',
defaultMessage : 'Ultra (UHD)'
})
} ];
let webcams;
if (me.webcamDevices)
webcams = Object.values(me.webcamDevices);
else
webcams = [];
let audioDevices;
if (me.audioDevices)
audioDevices = Object.values(me.audioDevices);
else
audioDevices = [];
return ( return (
<Dialog <Dialog
className={classes.root} className={classes.root}
open={room.settingsOpen} open={settingsOpen}
onClose={() => handleCloseSettings({ settingsOpen: false })} onClose={() => handleCloseSettings(false)}
classes={{ classes={{
paper : classes.dialogPaper paper : classes.dialogPaper
}} }}
@ -146,201 +77,40 @@ const Settings = ({
defaultMessage='Settings' defaultMessage='Settings'
/> />
</DialogTitle> </DialogTitle>
<form className={classes.setting} autoComplete='off'> <Tabs
<FormControl className={classes.formControl}> className={classes.tabsHeader}
<Select value={tabs.indexOf(currentSettingsTab)}
value={settings.selectedWebcam || ''} onChange={(event, value) => setSettingsTab(tabs[value])}
onChange={(event) => indicatorColor='primary'
{ textColor='primary'
if (event.target.value) variant='fullWidth'
roomClient.changeWebcam(event.target.value);
}}
displayEmpty
name={intl.formatMessage({
id : 'settings.camera',
defaultMessage : 'Camera'
})}
autoWidth
className={classes.selectEmpty}
disabled={webcams.length === 0 || me.webcamInProgress}
> >
{ webcams.map((webcam, index) => <Tab
{ label={
return (
<MenuItem key={index} value={webcam.deviceId}>{webcam.label}</MenuItem>
);
})}
</Select>
<FormHelperText>
{ webcams.length > 0 ?
intl.formatMessage({ intl.formatMessage({
id : 'settings.selectCamera', id : 'label.media',
defaultMessage : 'Select video device' defaultMessage : 'Media'
})
:
intl.formatMessage({
id : 'settings.cantSelectCamera',
defaultMessage : 'Unable to select video device'
}) })
} }
</FormHelperText>
</FormControl>
</form>
<form className={classes.setting} autoComplete='off'>
<FormControl className={classes.formControl}>
<Select
value={settings.selectedAudioDevice || ''}
onChange={(event) =>
{
if (event.target.value)
roomClient.changeAudioDevice(event.target.value);
}}
displayEmpty
name={intl.formatMessage({
id : 'settings.audio',
defaultMessage : 'Audio device'
})}
autoWidth
className={classes.selectEmpty}
disabled={audioDevices.length === 0 || me.audioInProgress}
>
{ audioDevices.map((audio, index) =>
{
return (
<MenuItem key={index} value={audio.deviceId}>{audio.label}</MenuItem>
);
})}
</Select>
<FormHelperText>
{ audioDevices.length > 0 ?
intl.formatMessage({
id : 'settings.selectAudio',
defaultMessage : 'Select audio device'
})
:
intl.formatMessage({
id : 'settings.cantSelectAudio',
defaultMessage : 'Unable to select audio device'
})
}
</FormHelperText>
</FormControl>
</form>
<form className={classes.setting} autoComplete='off'>
<FormControl className={classes.formControl}>
<Select
value={settings.resolution || ''}
onChange={(event) =>
{
if (event.target.value)
roomClient.changeVideoResolution(event.target.value);
}}
name='Video resolution'
autoWidth
className={classes.selectEmpty}
>
{ resolutions.map((resolution, index) =>
{
return (
<MenuItem key={index} value={resolution.value}>
{resolution.label}
</MenuItem>
);
})}
</Select>
<FormHelperText>
<FormattedMessage
id='settings.resolution'
defaultMessage='Select your video resolution'
/> />
</FormHelperText> <Tab
</FormControl>
</form>
<form className={classes.setting} autoComplete='off'>
<FormControl className={classes.formControl}>
<Select
value={room.mode || ''}
onChange={(event) =>
{
if (event.target.value)
handleChangeMode(event.target.value);
}}
name={intl.formatMessage({
id : 'settings.layout',
defaultMessage : 'Room layout'
})}
autoWidth
className={classes.selectEmpty}
>
{ modes.map((mode, index) =>
{
return (
<MenuItem key={index} value={mode.value}>
{mode.label}
</MenuItem>
);
})}
</Select>
<FormHelperText>
<FormattedMessage
id='settings.selectRoomLayout'
defaultMessage='Select room layout'
/>
</FormHelperText>
</FormControl>
</form>
<FormControlLabel
className={classes.setting}
control={<Checkbox checked={settings.advancedMode} onChange={onToggleAdvancedMode} value='advancedMode' />}
label={intl.formatMessage({ label={intl.formatMessage({
id : 'settings.advancedMode', id : 'label.appearance',
defaultMessage : 'Advanced mode' defaultMessage : 'Appearence'
})} })}
/> />
{ settings.advancedMode && <Tab
<React.Fragment>
<form className={classes.setting} autoComplete='off'>
<FormControl className={classes.formControl}>
<Select
value={settings.lastN || ''}
onChange={(event) =>
{
if (event.target.value)
roomClient.changeMaxSpotlights(event.target.value);
}}
name='Last N'
autoWidth
className={classes.selectEmpty}
>
{ [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ].map((lastN) =>
{
return (
<MenuItem key={lastN} value={lastN}>
{lastN}
</MenuItem>
);
})}
</Select>
<FormHelperText>
<FormattedMessage
id='settings.lastn'
defaultMessage='Number of visible videos'
/>
</FormHelperText>
</FormControl>
</form>
<FormControlLabel
className={classes.setting}
control={<Checkbox checked={settings.permanentTopBar} onChange={onTogglePermanentTopBar} value='permanentTopBar' />}
label={intl.formatMessage({ label={intl.formatMessage({
id : 'settings.permanentTopBar', id : 'label.advanced',
defaultMessage : 'Permanent top bar' defaultMessage : 'Advanced'
})} })}
/> />
</React.Fragment> </Tabs>
} {currentSettingsTab === 'media' && <MediaSettings />}
{currentSettingsTab === 'appearence' && <AppearenceSettings />}
{currentSettingsTab === 'advanced' && <AdvancedSettings />}
<DialogActions> <DialogActions>
<Button onClick={() => handleCloseSettings({ settingsOpen: false })} color='primary'> <Button onClick={() => handleCloseSettings(false)} color='primary'>
<FormattedMessage <FormattedMessage
id='label.close' id='label.close'
defaultMessage='Close' defaultMessage='Close'
@ -353,34 +123,25 @@ const Settings = ({
Settings.propTypes = Settings.propTypes =
{ {
roomClient : PropTypes.any.isRequired, currentSettingsTab : PropTypes.string.isRequired,
me : appPropTypes.Me.isRequired, settingsOpen : PropTypes.bool.isRequired,
room : appPropTypes.Room.isRequired,
settings : PropTypes.object.isRequired,
onToggleAdvancedMode : PropTypes.func.isRequired,
onTogglePermanentTopBar : PropTypes.func.isRequired,
handleChangeMode : PropTypes.func.isRequired,
handleCloseSettings : PropTypes.func.isRequired, handleCloseSettings : PropTypes.func.isRequired,
setSettingsTab : PropTypes.func.isRequired,
classes : PropTypes.object.isRequired classes : PropTypes.object.isRequired
}; };
const mapStateToProps = (state) => const mapStateToProps = (state) =>
{ ({
return { currentSettingsTab : state.room.currentSettingsTab,
me : state.me, settingsOpen : state.room.settingsOpen
room : state.room, });
settings : state.settings
};
};
const mapDispatchToProps = { const mapDispatchToProps = {
onToggleAdvancedMode : settingsActions.toggleAdvancedMode, handleCloseSettings : roomActions.setSettingsOpen,
onTogglePermanentTopBar : settingsActions.togglePermanentTopBar, setSettingsTab : roomActions.setSettingsTab
handleChangeMode : roomActions.setDisplayMode,
handleCloseSettings : roomActions.setSettingsOpen
}; };
export default withRoomContext(connect( export default connect(
mapStateToProps, mapStateToProps,
mapDispatchToProps, mapDispatchToProps,
null, null,
@ -388,10 +149,9 @@ export default withRoomContext(connect(
areStatesEqual : (next, prev) => areStatesEqual : (next, prev) =>
{ {
return ( return (
prev.me === next.me && prev.room.currentSettingsTab === next.room.currentSettingsTab &&
prev.room === next.room && prev.room.settingsOpen === next.room.settingsOpen
prev.settings === next.settings
); );
} }
} }
)(withStyles(styles)(Settings))); )(withStyles(styles)(Settings));

View File

@ -0,0 +1,149 @@
import React from 'react';
import { withStyles } from '@material-ui/core/styles';
import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import Dialog from '@material-ui/core/Dialog';
import DialogTitle from '@material-ui/core/DialogTitle';
import DialogContent from '@material-ui/core/DialogContent';
import Grid from '@material-ui/core/Grid';
import List from '@material-ui/core/List';
import ListItem from '@material-ui/core/ListItem';
import ListItemText from '@material-ui/core/ListItemText';
import ListItemAvatar from '@material-ui/core/ListItemAvatar';
import Avatar from '@material-ui/core/Avatar';
import WebAssetIcon from '@material-ui/icons/WebAsset';
import ErrorIcon from '@material-ui/icons/Error';
import Hidden from '@material-ui/core/Hidden';
const styles = (theme) =>
({
dialogPaper :
{
width : '40vw',
[theme.breakpoints.down('lg')] :
{
width : '40vw'
},
[theme.breakpoints.down('md')] :
{
width : '50vw'
},
[theme.breakpoints.down('sm')] :
{
width : '70vw'
},
[theme.breakpoints.down('xs')] :
{
width : '90vw'
}
// display : 'flex',
// flexDirection : 'column'
},
list : {
backgroundColor : theme.palette.background.paper
},
errorAvatar : {
width : theme.spacing(20),
height : theme.spacing(20)
}
});
let dense = false;
const supportedBrowsers=[
{ name: 'Chrome/Chromium', version: '74', vendor: 'Google' },
{ name: 'Edge', version: '18', vendor: 'Microsoft' },
{ name: 'Firefox', version: '60', vendor: 'Mozilla' },
{ name: 'Safari', version: '12', vendor: 'Apple' },
{ name: 'Opera', version: '62', vendor: '' },
// { name: 'Brave', version: '1.5', vendor: '' },
// { name: 'Vivaldi', version: '3', vendor: '' },
{ name: 'Samsung Internet', version: '11.1.1.52', vendor: '' }
];
const UnsupportedBrowser = ({
platform,
webrtcUnavailable,
classes
}) =>
{
if (platform !== 'desktop')
dense = true;
return (
<Dialog
open
scroll={'body'}
classes={{
paper : classes.dialogPaper
}}
>
<DialogTitle id='form-dialog-title'>
{!webrtcUnavailable &&
<FormattedMessage
id='unsupportedBrowser.titleUnsupportedBrowser'
defaultMessage='Browser not supported'
/>
}
{webrtcUnavailable &&
<FormattedMessage
id='unsupportedBrowser.titlewebrtcUnavailable'
defaultMessage='Required functionality not availble in your browser'
/>
}
</DialogTitle>
<DialogContent dividers>
<FormattedMessage
id='unsupportedBrowser.bodyText'
defaultMessage='This meeting service requires
functionality not supported by your browser.
Please upgrade, switch to a different browser, or
check your settings. Supported browsers:'
/>
<Grid container spacing={2} justify='center' alignItems='center'>
<Grid item xs={12} md={7}>
<div className={classes.list}>
<List dense={dense}>
{supportedBrowsers.map((browser, index) =>
{
const supportedBrowser = `${browser.vendor} ${browser.name}`;
const supportedVersion = `${browser.version}+`;
return (
<ListItem key={index}>
<ListItemAvatar>
<Avatar>
<WebAssetIcon />
</Avatar>
</ListItemAvatar>
<ListItemText
primary={supportedBrowser}
secondary={supportedVersion}
/>
</ListItem>
);
})}
</List>
</div>
</Grid>
<Grid item xs={12} md={5} align='center'>
<Hidden mdDown>
<ErrorIcon className={classes.errorAvatar} color='error'/>
</Hidden>
</Grid>
</Grid>
</DialogContent>
</Dialog>
);
};
UnsupportedBrowser.propTypes =
{
webrtcUnavailable : PropTypes.bool.isRequired,
platform : PropTypes.string.isRequired,
classes : PropTypes.object.isRequired
};
export default withStyles(styles)(UnsupportedBrowser);

View File

@ -96,11 +96,6 @@ const FullScreenView = (props) =>
!consumer.remotelyPaused !consumer.remotelyPaused
); );
let consumerProfile;
if (consumer)
consumerProfile = consumer.profile;
return ( return (
<div className={classes.root}> <div className={classes.root}>
<div className={classes.controls}> <div className={classes.controls}>
@ -121,9 +116,25 @@ const FullScreenView = (props) =>
<VideoView <VideoView
advancedMode={advancedMode} advancedMode={advancedMode}
videoContain videoContain
videoTrack={consumer ? consumer.track : null} consumerSpatialLayers={consumer ? consumer.spatialLayers : null}
consumerTemporalLayers={consumer ? consumer.temporalLayers : null}
consumerCurrentSpatialLayer={
consumer ? consumer.currentSpatialLayer : null
}
consumerCurrentTemporalLayer={
consumer ? consumer.currentTemporalLayer : null
}
consumerPreferredSpatialLayer={
consumer ? consumer.preferredSpatialLayer : null
}
consumerPreferredTemporalLayer={
consumer ? consumer.preferredTemporalLayer : null
}
videoMultiLayer={consumer && consumer.type !== 'simple'}
videoTrack={consumer && consumer.track}
videoVisible={consumerVisible} videoVisible={consumerVisible}
videoProfile={consumerProfile} videoCodec={consumer && consumer.codec}
videoScore={consumer ? consumer.score : null}
/> />
</div> </div>
); );

View File

@ -81,12 +81,15 @@ class FullView extends React.PureComponent
this._setTracks(videoTrack); this._setTracks(videoTrack);
} }
componentDidUpdate() componentDidUpdate(prevProps)
{
if (prevProps !== this.props)
{ {
const { videoTrack } = this.props; const { videoTrack } = this.props;
this._setTracks(videoTrack); this._setTracks(videoTrack);
} }
}
_setTracks(videoTrack) _setTracks(videoTrack)
{ {

View File

@ -3,6 +3,15 @@ import PropTypes from 'prop-types';
import classnames from 'classnames'; import classnames from 'classnames';
import { withStyles } from '@material-ui/core/styles'; import { withStyles } from '@material-ui/core/styles';
import EditableInput from '../Controls/EditableInput'; import EditableInput from '../Controls/EditableInput';
import Logger from '../../Logger';
import { yellow, orange, red } from '@material-ui/core/colors';
import SignalCellularOffIcon from '@material-ui/icons/SignalCellularOff';
import SignalCellular0BarIcon from '@material-ui/icons/SignalCellular0Bar';
import SignalCellular1BarIcon from '@material-ui/icons/SignalCellular1Bar';
import SignalCellular2BarIcon from '@material-ui/icons/SignalCellular2Bar';
import SignalCellular3BarIcon from '@material-ui/icons/SignalCellular3Bar';
const logger = new Logger('VideoView');
const styles = (theme) => const styles = (theme) =>
({ ({
@ -16,6 +25,7 @@ const styles = (theme) =>
flexDirection : 'column', flexDirection : 'column',
overflow : 'hidden' overflow : 'hidden'
}, },
video : video :
{ {
flex : '100 100 auto', flex : '100 100 auto',
@ -60,24 +70,66 @@ const styles = (theme) =>
{ {
display : 'flex', display : 'flex',
transitionProperty : 'opacity', transitionProperty : 'opacity',
transitionDuration : '.15s', transitionDuration : '.15s'
'&.hidden' :
{
opacity : 0,
transitionDuration : '0s'
}
}, },
box : box :
{ {
padding : theme.spacing(0.5), padding : theme.spacing(0.5),
borderRadius : 2, borderRadius : 2,
backgroundColor : 'rgba(0, 0, 0, 0.25)',
'& p' :
{
userSelect : 'none', userSelect : 'none',
margin : 0, margin : 0,
color : 'rgba(255, 255, 255, 0.7)', color : 'rgba(255, 255, 255, 0.7)',
fontSize : '0.8em' fontSize : '0.8em',
'&.left' :
{
backgroundColor : 'rgba(0, 0, 0, 0.25)',
display : 'grid',
gap : '1px 5px',
// eslint-disable-next-line
gridTemplateAreas : '\
"AcodL Acod Acod Acod Acod" \
"VcodL Vcod Vcod Vcod Vcod" \
"ResL Res Res Res Res" \
"RecvL RecvBps RecvBps RecvSum RecvSum" \
"SendL SendBps SendBps SendSum SendSum" \
"IPlocL IPloc IPloc IPloc IPloc" \
"IPsrvL IPsrv IPsrv IPsrv IPsrv" \
"STLcurrL STLcurr STLcurr STLcurr STLcurr" \
"STLprefL STLpref STLpref STLpref STLpref"',
'& .AcodL' : { gridArea: 'AcodL' },
'& .Acod' : { gridArea: 'Acod' },
'& .VcodL' : { gridArea: 'VcodL' },
'& .Vcod' : { gridArea: 'Vcod' },
'& .ResL' : { gridArea: 'ResL' },
'& .Res' : { gridArea: 'Res' },
'& .RecvL' : { gridArea: 'RecvL' },
'& .RecvBps' : { gridArea: 'RecvBps', justifySelf: 'flex-end' },
'& .RecvSum' : { gridArea: 'RecvSum', justifySelf: 'flex-end' },
'& .SendL' : { gridArea: 'SendL' },
'& .SendBps' : { gridArea: 'SendBps', justifySelf: 'flex-end' },
'& .SendSum' : { gridArea: 'SendSum', justifySelf: 'flex-end' },
'& .IPlocL' : { gridArea: 'IPlocL' },
'& .IPloc' : { gridArea: 'IPloc' },
'& .IPsrvL' : { gridArea: 'IPsrvL' },
'& .IPsrv' : { gridArea: 'IPsrv' },
'& .STLcurrL' : { gridArea: 'STLcurrL' },
'& .STLcurr' : { gridArea: 'STLcurr' },
'& .STLprefL' : { gridArea: 'STLprefL' },
'& .STLpref' : { gridArea: 'STLpref' }
},
'&.right' :
{
marginLeft : 'auto',
width : 30
},
'&.hidden' :
{
opacity : 0,
transitionDuration : '0s'
} }
}, },
peer : peer :
@ -119,6 +171,10 @@ class VideoView extends React.PureComponent
videoHeight : null videoHeight : null
}; };
// Latest received audio track
// @type {MediaStreamTrack}
this._audioTrack = null;
// Latest received video track. // Latest received video track.
// @type {MediaStreamTrack} // @type {MediaStreamTrack}
this._videoTrack = null; this._videoTrack = null;
@ -132,16 +188,16 @@ class VideoView extends React.PureComponent
const { const {
isMe, isMe,
isScreen, isScreen,
isExtraVideo,
showQuality,
displayName, displayName,
showPeerInfo, showPeerInfo,
videoContain, videoContain,
advancedMode, advancedMode,
videoVisible, videoVisible,
videoMultiLayer, videoMultiLayer,
// audioScore, audioScore,
// videoScore, videoScore,
// consumerSpatialLayers,
// consumerTemporalLayers,
consumerCurrentSpatialLayer, consumerCurrentSpatialLayer,
consumerCurrentTemporalLayer, consumerCurrentTemporalLayer,
consumerPreferredSpatialLayer, consumerPreferredSpatialLayer,
@ -150,7 +206,8 @@ class VideoView extends React.PureComponent
videoCodec, videoCodec,
onChangeDisplayName, onChangeDisplayName,
children, children,
classes classes,
netInfo
} = this.props; } = this.props;
const { const {
@ -158,41 +215,155 @@ class VideoView extends React.PureComponent
videoHeight videoHeight
} = this.state; } = this.state;
let quality = null;
if (showQuality)
{
quality = <SignalCellularOffIcon style={{ color: red[500] }}/>;
if (videoScore || audioScore)
{
const score = videoScore ? videoScore : audioScore;
switch (isMe ? score.score : score.producerScore)
{
case 0:
case 1:
{
quality = <SignalCellular0BarIcon style={{ color: red[500] }}/>;
break;
}
case 2:
case 3:
{
quality = <SignalCellular1BarIcon style={{ color: red[500] }}/>;
break;
}
case 4:
case 5:
case 6:
{
quality = <SignalCellular2BarIcon style={{ color: orange[500] }}/>;
break;
}
case 7:
case 8:
case 9:
{
quality = <SignalCellular3BarIcon style={{ color: yellow[500] }}/>;
break;
}
case 10:
{
quality = null;
break;
}
default:
{
break;
}
}
}
}
return ( return (
<div className={classes.root}> <div className={classes.root}>
<div className={classes.info}> <div className={classes.info}>
<div className={classnames(classes.media, <div className={classes.media}>
{ <div className={classnames(classes.box, 'left', { hidden: !advancedMode })}>
hidden : !advancedMode { audioCodec &&
})} <React.Fragment>
> <span className={'AcodL'}>Acod: </span>
<div className={classes.box}> <span className={'Acod'}>
{ audioCodec && <p>{audioCodec}</p> } {audioCodec}
</span>
{ videoCodec && </React.Fragment>
<p>
{videoCodec}
</p>
} }
{ videoMultiLayer && { videoCodec &&
<p> <React.Fragment>
{`current spatial-temporal layers: ${consumerCurrentSpatialLayer} ${consumerCurrentTemporalLayer}`} <span className={'VcodL'}>Vcod: </span>
<br /> <span className={'Vcod'}>
{`preferred spatial-temporal layers: ${consumerPreferredSpatialLayer} ${consumerPreferredTemporalLayer}`} {videoCodec}
</p> </span>
</React.Fragment>
} }
{ (videoVisible && videoWidth !== null) && { (videoVisible && videoWidth !== null) &&
<p>{videoWidth}x{videoHeight}</p> <React.Fragment>
<span className={'ResL'}>Res: </span>
<span className={'Res'}>
{videoWidth}x{videoHeight}
</span>
</React.Fragment>
}
{ isMe && !isScreen && !isExtraVideo &&
(netInfo.recv && netInfo.send && netInfo.send.iceSelectedTuple) &&
<React.Fragment>
<span className={'RecvL'}>Recv: </span>
<span className={'RecvBps'}>
{(netInfo.recv.sendBitrate/1024/1024).toFixed(2)}Mb/s
</span>
<span className={'RecvSum'}>
{(netInfo.recv.bytesSent/1024/1024).toFixed(2)}MB
</span>
<span className={'SendL'}>Send: </span>
<span className={'SendBps'}>
{(netInfo.send.recvBitrate/1024/1024).toFixed(2)}Mb/s
</span>
<span className={'SendSum'}>
{(netInfo.send.bytesReceived/1024/1024).toFixed(2)}MB
</span>
<span className={'IPlocL'}>IPloc: </span>
<span className={'IPloc'}>
{netInfo.send.iceSelectedTuple.remoteIp}
</span>
<span className={'IPsrvL'}>IPsrv: </span>
<span className={'IPsrv'}>
{netInfo.send.iceSelectedTuple.localIp}
</span>
</React.Fragment>
}
{ videoMultiLayer &&
<React.Fragment>
<span className={'STLcurrL'}>STLcurr: </span>
<span className={'STLcurr'}>{consumerCurrentSpatialLayer} {consumerCurrentTemporalLayer}</span>
<span className={'STLprefL'}>STLpref: </span>
<span className={'STLpref'}>{consumerPreferredSpatialLayer} {consumerPreferredTemporalLayer}</span>
</React.Fragment>
}
</div>
{ showQuality &&
<div className={classnames(classes.box, 'right')}>
{
quality
} }
</div> </div>
}
</div> </div>
{ showPeerInfo && { showPeerInfo &&
<div className={classes.peer}> <div className={classes.peer}>
<div className={classes.box}> <div className={classes.box}>
{ isMe ? { isMe ?
<React.Fragment>
<EditableInput <EditableInput
value={displayName} value={displayName}
propName='newDisplayName' propName='newDisplayName'
@ -205,8 +376,11 @@ class VideoView extends React.PureComponent
autoCorrect : 'off', autoCorrect : 'off',
spellCheck : false spellCheck : false
}} }}
onChange={({ newDisplayName }) => onChangeDisplayName(newDisplayName)} onChange={
({ newDisplayName }) =>
onChangeDisplayName(newDisplayName)}
/> />
</React.Fragment>
: :
<span className={classes.displayNameStatic}> <span className={classes.displayNameStatic}>
{displayName} {displayName}
@ -218,7 +392,7 @@ class VideoView extends React.PureComponent
</div> </div>
<video <video
ref='video' ref='videoElement'
className={classnames(classes.video, { className={classnames(classes.video, {
hidden : !videoVisible, hidden : !videoVisible,
'isMe' : isMe && !isScreen, 'isMe' : isMe && !isScreen,
@ -226,6 +400,16 @@ class VideoView extends React.PureComponent
})} })}
autoPlay autoPlay
playsInline playsInline
muted
controls={false}
/>
<audio
ref='audioElement'
autoPlay
playsInline
muted={isMe}
controls={false}
/> />
{children} {children}
@ -235,52 +419,87 @@ class VideoView extends React.PureComponent
componentDidMount() componentDidMount()
{ {
const { videoTrack } = this.props; const { videoTrack, audioTrack } = this.props;
this._setTracks(videoTrack); this._setTracks(videoTrack, audioTrack);
} }
componentWillUnmount() componentWillUnmount()
{ {
clearInterval(this._videoResolutionTimer); clearInterval(this._videoResolutionTimer);
const { videoElement } = this.refs;
if (videoElement)
{
videoElement.oncanplay = null;
videoElement.onplay = null;
videoElement.onpause = null;
}
} }
// eslint-disable-next-line camelcase componentDidUpdate(prevProps)
UNSAFE_componentWillReceiveProps(nextProps)
{ {
const { videoTrack } = nextProps; if (prevProps !== this.props)
{
this._setTracks(videoTrack); const { videoTrack, audioTrack } = this.props;
this._setTracks(videoTrack, audioTrack);
}
} }
_setTracks(videoTrack) _setTracks(videoTrack, audioTrack)
{ {
if (this._videoTrack === videoTrack) if (this._videoTrack === videoTrack && this._audioTrack === audioTrack)
return; return;
this._videoTrack = videoTrack; this._videoTrack = videoTrack;
this._audioTrack = audioTrack;
clearInterval(this._videoResolutionTimer); clearInterval(this._videoResolutionTimer);
this._hideVideoResolution(); this._hideVideoResolution();
const { video } = this.refs; const { videoElement, audioElement } = this.refs;
if (videoTrack) if (videoTrack)
{ {
const stream = new MediaStream(); const stream = new MediaStream();
if (videoTrack)
stream.addTrack(videoTrack); stream.addTrack(videoTrack);
video.srcObject = stream; videoElement.srcObject = stream;
videoElement.oncanplay = () => this.setState({ videoCanPlay: true });
videoElement.onplay = () =>
{
audioElement.play()
.catch((error) => logger.warn('audioElement.play() [error:"%o]', error));
};
videoElement.play()
.catch((error) => logger.warn('videoElement.play() [error:"%o]', error));
if (videoTrack)
this._showVideoResolution(); this._showVideoResolution();
} }
else else
{ {
video.srcObject = null; videoElement.srcObject = null;
}
if (audioTrack)
{
const stream = new MediaStream();
stream.addTrack(audioTrack);
audioElement.srcObject = stream;
audioElement.play()
.catch((error) => logger.warn('audioElement.play() [error:"%o]', error));
}
else
{
audioElement.srcObject = null;
} }
} }
@ -289,16 +508,19 @@ class VideoView extends React.PureComponent
this._videoResolutionTimer = setInterval(() => this._videoResolutionTimer = setInterval(() =>
{ {
const { videoWidth, videoHeight } = this.state; const { videoWidth, videoHeight } = this.state;
const { video } = this.refs; const { videoElement } = this.refs;
// Don't re-render if nothing changed. // Don't re-render if nothing changed.
if (video.videoWidth === videoWidth && video.videoHeight === videoHeight) if (
videoElement.videoWidth === videoWidth &&
videoElement.videoHeight === videoHeight
)
return; return;
this.setState( this.setState(
{ {
videoWidth : video.videoWidth, videoWidth : videoElement.videoWidth,
videoHeight : video.videoHeight videoHeight : videoElement.videoHeight
}); });
}, 1000); }, 1000);
} }
@ -313,11 +535,14 @@ VideoView.propTypes =
{ {
isMe : PropTypes.bool, isMe : PropTypes.bool,
isScreen : PropTypes.bool, isScreen : PropTypes.bool,
isExtraVideo : PropTypes.bool,
showQuality : PropTypes.bool,
displayName : PropTypes.string, displayName : PropTypes.string,
showPeerInfo : PropTypes.bool, showPeerInfo : PropTypes.bool,
videoContain : PropTypes.bool, videoContain : PropTypes.bool,
advancedMode : PropTypes.bool, advancedMode : PropTypes.bool,
videoTrack : PropTypes.any, videoTrack : PropTypes.any,
audioTrack : PropTypes.any,
videoVisible : PropTypes.bool.isRequired, videoVisible : PropTypes.bool.isRequired,
consumerSpatialLayers : PropTypes.number, consumerSpatialLayers : PropTypes.number,
consumerTemporalLayers : PropTypes.number, consumerTemporalLayers : PropTypes.number,
@ -332,7 +557,8 @@ VideoView.propTypes =
videoCodec : PropTypes.string, videoCodec : PropTypes.string,
onChangeDisplayName : PropTypes.func, onChangeDisplayName : PropTypes.func,
children : PropTypes.object, children : PropTypes.object,
classes : PropTypes.object.isRequired classes : PropTypes.object.isRequired,
netInfo : PropTypes.object
}; };
export default withStyles(styles)(VideoView); export default withStyles(styles)(VideoView);

View File

@ -23,18 +23,29 @@ const VideoWindow = (props) =>
!consumer.remotelyPaused !consumer.remotelyPaused
); );
let consumerProfile;
if (consumer)
consumerProfile = consumer.profile;
return ( return (
<NewWindow onUnload={toggleConsumerWindow}> <NewWindow onUnload={toggleConsumerWindow}>
<FullView <FullView
advancedMode={advancedMode} advancedMode={advancedMode}
videoTrack={consumer ? consumer.track : null} consumerSpatialLayers={consumer ? consumer.spatialLayers : null}
consumerTemporalLayers={consumer ? consumer.temporalLayers : null}
consumerCurrentSpatialLayer={
consumer ? consumer.currentSpatialLayer : null
}
consumerCurrentTemporalLayer={
consumer ? consumer.currentTemporalLayer : null
}
consumerPreferredSpatialLayer={
consumer ? consumer.preferredSpatialLayer : null
}
consumerPreferredTemporalLayer={
consumer ? consumer.preferredTemporalLayer : null
}
videoMultiLayer={consumer && consumer.type !== 'simple'}
videoTrack={consumer && consumer.track}
videoVisible={consumerVisible} videoVisible={consumerVisible}
videoProfile={consumerProfile} videoCodec={consumer && consumer.codec}
videoScore={consumer ? consumer.score : null}
/> />
</NewWindow> </NewWindow>
); );

View File

@ -18,9 +18,9 @@ export const Me = PropTypes.shape(
export const Producer = PropTypes.shape( export const Producer = PropTypes.shape(
{ {
id : PropTypes.string.isRequired, id : PropTypes.string.isRequired,
source : PropTypes.oneOf([ 'mic', 'webcam', 'screen' ]).isRequired, source : PropTypes.oneOf([ 'mic', 'webcam', 'screen', 'extravideo' ]).isRequired,
deviceLabel : PropTypes.string, deviceLabel : PropTypes.string,
type : PropTypes.oneOf([ 'front', 'back', 'screen' ]), type : PropTypes.oneOf([ 'front', 'back', 'screen', 'extravideo' ]),
paused : PropTypes.bool.isRequired, paused : PropTypes.bool.isRequired,
track : PropTypes.any, track : PropTypes.any,
codec : PropTypes.string.isRequired codec : PropTypes.string.isRequired
@ -37,7 +37,7 @@ export const Consumer = PropTypes.shape(
{ {
id : PropTypes.string.isRequired, id : PropTypes.string.isRequired,
peerId : PropTypes.string.isRequired, peerId : PropTypes.string.isRequired,
source : PropTypes.oneOf([ 'mic', 'webcam', 'screen' ]).isRequired, source : PropTypes.oneOf([ 'mic', 'webcam', 'screen', 'extravideo' ]).isRequired,
locallyPaused : PropTypes.bool.isRequired, locallyPaused : PropTypes.bool.isRequired,
remotelyPaused : PropTypes.bool.isRequired, remotelyPaused : PropTypes.bool.isRequired,
profile : PropTypes.oneOf([ 'none', 'default', 'low', 'medium', 'high' ]), profile : PropTypes.oneOf([ 'none', 'default', 'low', 'medium', 'high' ]),

View File

@ -24,7 +24,9 @@ export default function()
return { return {
flag, flag,
name : browser.getBrowserName(), os : browser.getOSName(true), // ios, android, linux...
platform : browser.getPlatformType(true), // mobile, desktop, tablet
name : browser.getBrowserName(true),
version : browser.getBrowserVersion(), version : browser.getBrowserVersion(),
bowser : browser bowser : browser
}; };

View File

@ -1,26 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
version="1.1"
id="Layer_1"
x="0px"
y="0px"
viewBox="0 0 96 96"
style="enable-background:new 0 0 96 96;"
xml:space="preserve">
<metadata
id="metadata11"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata>
<defs
id="defs9" />
<path
style="fill:#000000;stroke-width:0.40677965"
d="m 33.894283,77.837288 c -1.428534,-1.845763 -3.909722,-5.220659 -5.513751,-7.499764 -1.60403,-2.279109 -4.323663,-5.940126 -6.043631,-8.135593 -5.698554,-7.273973 -6.224902,-8.044795 -6.226676,-9.118803 -0.0034,-2.075799 2.81181,-4.035355 4.9813,-3.467247 0.50339,0.131819 2.562712,1.72771 4.576272,3.546423 4.238418,3.828283 6.617166,5.658035 7.355654,5.658035 0.82497,0 1.045415,-1.364294 0.567453,-3.511881 C 33.348583,54.219654 31.1088,48.20339 28.613609,41.938983 23.524682,29.162764 23.215312,27.731034 25.178629,26.04226 c 2.443255,-2.101599 4.670178,-1.796504 6.362271,0.87165 0.639176,1.007875 2.666245,5.291978 4.504599,9.520229 1.838354,4.228251 3.773553,8.092718 4.300442,8.587705 l 0.957981,0.899977 0.419226,-1.102646 c 0.255274,-0.671424 0.419225,-6.068014 0.419225,-13.799213 0,-13.896836 -0.0078,-13.84873 2.44517,-15.1172 1.970941,-1.019214 4.2259,-0.789449 5.584354,0.569005 l 1.176852,1.176852 0.483523,11.738402 c 0.490017,11.896027 0.826095,14.522982 1.911266,14.939402 1.906224,0.731486 2.21601,-0.184677 4.465407,-13.206045 1.239206,-7.173539 1.968244,-10.420721 2.462128,-10.966454 1.391158,-1.537215 4.742705,-1.519809 6.295208,0.03269 1.147387,1.147388 1.05469,3.124973 -0.669503,14.283063 -0.818745,5.298489 -1.36667,10.090163 -1.220432,10.67282 0.14596,0.581557 0.724796,1.358395 1.286298,1.726306 0.957759,0.627548 1.073422,0.621575 1.86971,-0.09655 0.466837,-0.421011 1.761787,-2.595985 2.877665,-4.833273 2.564176,-5.141059 3.988466,-6.711864 6.085822,-6.711864 2.769954,0 3.610947,2.927256 2.139316,7.446329 C 78.799497,44.318351 66.752066,77.28024 65.51653,80.481356 65.262041,81.140709 64.18139,81.19322 50.866695,81.19322 H 36.491617 Z"
id="path3710"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -1,26 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
version="1.1"
id="Layer_1"
x="0px"
y="0px"
viewBox="0 0 96 96"
style="enable-background:new 0 0 96 96;"
xml:space="preserve">
<metadata
id="metadata11"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata>
<defs
id="defs9" />
<path
style="fill:#ffffff;stroke-width:0.40677965"
d="m 33.894283,77.837288 c -1.428534,-1.845763 -3.909722,-5.220659 -5.513751,-7.499764 -1.60403,-2.279109 -4.323663,-5.940126 -6.043631,-8.135593 -5.698554,-7.273973 -6.224902,-8.044795 -6.226676,-9.118803 -0.0034,-2.075799 2.81181,-4.035355 4.9813,-3.467247 0.50339,0.131819 2.562712,1.72771 4.576272,3.546423 4.238418,3.828283 6.617166,5.658035 7.355654,5.658035 0.82497,0 1.045415,-1.364294 0.567453,-3.511881 C 33.348583,54.219654 31.1088,48.20339 28.613609,41.938983 23.524682,29.162764 23.215312,27.731034 25.178629,26.04226 c 2.443255,-2.101599 4.670178,-1.796504 6.362271,0.87165 0.639176,1.007875 2.666245,5.291978 4.504599,9.520229 1.838354,4.228251 3.773553,8.092718 4.300442,8.587705 l 0.957981,0.899977 0.419226,-1.102646 c 0.255274,-0.671424 0.419225,-6.068014 0.419225,-13.799213 0,-13.896836 -0.0078,-13.84873 2.44517,-15.1172 1.970941,-1.019214 4.2259,-0.789449 5.584354,0.569005 l 1.176852,1.176852 0.483523,11.738402 c 0.490017,11.896027 0.826095,14.522982 1.911266,14.939402 1.906224,0.731486 2.21601,-0.184677 4.465407,-13.206045 1.239206,-7.173539 1.968244,-10.420721 2.462128,-10.966454 1.391158,-1.537215 4.742705,-1.519809 6.295208,0.03269 1.147387,1.147388 1.05469,3.124973 -0.669503,14.283063 -0.818745,5.298489 -1.36667,10.090163 -1.220432,10.67282 0.14596,0.581557 0.724796,1.358395 1.286298,1.726306 0.957759,0.627548 1.073422,0.621575 1.86971,-0.09655 0.466837,-0.421011 1.761787,-2.595985 2.877665,-4.833273 2.564176,-5.141059 3.988466,-6.711864 6.085822,-6.711864 2.769954,0 3.610947,2.927256 2.139316,7.446329 C 78.799497,44.318351 66.752066,77.28024 65.51653,80.481356 65.262041,81.140709 64.18139,81.19322 50.866695,81.19322 H 36.491617 Z"
id="path3710"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -12,6 +12,7 @@ import RoomClient from './RoomClient';
import RoomContext from './RoomContext'; import RoomContext from './RoomContext';
import deviceInfo from './deviceInfo'; import deviceInfo from './deviceInfo';
import * as meActions from './actions/meActions'; import * as meActions from './actions/meActions';
import UnsupportedBrowser from './components/UnsupportedBrowser';
import ChooseRoom from './components/ChooseRoom'; import ChooseRoom from './components/ChooseRoom';
import LoadingView from './components/LoadingView'; import LoadingView from './components/LoadingView';
import { MuiThemeProvider, createMuiTheme } from '@material-ui/core/styles'; import { MuiThemeProvider, createMuiTheme } from '@material-ui/core/styles';
@ -20,7 +21,7 @@ import { persistor, store } from './store';
import { SnackbarProvider } from 'notistack'; import { SnackbarProvider } from 'notistack';
import * as serviceWorker from './serviceWorker'; import * as serviceWorker from './serviceWorker';
import { ReactLazyPreload } from './components/ReactLazyPreload'; import { ReactLazyPreload } from './components/ReactLazyPreload';
import { detectDevice } from 'mediasoup-client';
// import messagesEnglish from './translations/en'; // import messagesEnglish from './translations/en';
import messagesNorwegian from './translations/nb'; import messagesNorwegian from './translations/nb';
import messagesGerman from './translations/de'; import messagesGerman from './translations/de';
@ -31,13 +32,15 @@ import messagesFrench from './translations/fr';
import messagesGreek from './translations/el'; import messagesGreek from './translations/el';
import messagesRomanian from './translations/ro'; import messagesRomanian from './translations/ro';
import messagesPortuguese from './translations/pt'; import messagesPortuguese from './translations/pt';
import messagesChinese from './translations/cn'; import messagesChineseSimplified from './translations/cn';
import messagesChineseTraditional from './translations/tw';
import messagesSpanish from './translations/es'; import messagesSpanish from './translations/es';
import messagesCroatian from './translations/hr'; import messagesCroatian from './translations/hr';
import messagesCzech from './translations/cs'; import messagesCzech from './translations/cs';
import messagesItalian from './translations/it'; import messagesItalian from './translations/it';
import messagesUkrainian from './translations/uk'; import messagesUkrainian from './translations/uk';
import messagesTurkish from './translations/tr'; import messagesTurkish from './translations/tr';
import messagesLatvian from './translations/lv';
import './index.css'; import './index.css';
@ -57,22 +60,48 @@ const messages =
'el' : messagesGreek, 'el' : messagesGreek,
'ro' : messagesRomanian, 'ro' : messagesRomanian,
'pt' : messagesPortuguese, 'pt' : messagesPortuguese,
'zh' : messagesChinese, 'zh-hans' : messagesChineseSimplified,
'zh-hant' : messagesChineseTraditional,
'es' : messagesSpanish, 'es' : messagesSpanish,
'hr' : messagesCroatian, 'hr' : messagesCroatian,
'cs' : messagesCzech, 'cs' : messagesCzech,
'it' : messagesItalian, 'it' : messagesItalian,
'uk' : messagesUkrainian, 'uk' : messagesUkrainian,
'tr' : messagesTurkish 'tr' : messagesTurkish,
'lv' : messagesLatvian
}; };
const locale = navigator.language.split(/[-_]/)[0]; // language without region code const supportedBrowsers={
'windows' : {
'internet explorer' : '>12',
'microsoft edge' : '>18'
},
'safari' : '>12',
'firefox' : '>=60',
'chrome' : '>=74',
'opera' : '>=62',
'samsung internet for android' : '>=11.1.1.52'
};
const browserLanguage = (navigator.language || navigator.browserLanguage).toLowerCase();
let locale = browserLanguage.split(/[-_]/)[0]; // language without region code
if (locale === 'zh')
{
if (browserLanguage === 'zh-cn')
locale = 'zh-hans';
else
locale = 'zh-hant';
}
const intl = createIntl({ const intl = createIntl({
locale, locale,
messages : messages[locale] messages : messages[locale]
}, cache); }, cache);
document.documentElement.lang = locale;
if (process.env.REACT_APP_DEBUG === '*' || process.env.NODE_ENV !== 'production') if (process.env.REACT_APP_DEBUG === '*' || process.env.NODE_ENV !== 'production')
{ {
debug.enable('* -engine* -socket* -RIE* *WARN* *ERROR*'); debug.enable('* -engine* -socket* -RIE* *WARN* *ERROR*');
@ -110,15 +139,75 @@ function run()
const accessCode = parameters.get('code'); const accessCode = parameters.get('code');
const produce = parameters.get('produce') !== 'false'; const produce = parameters.get('produce') !== 'false';
const useSimulcast = parameters.get('simulcast') === 'true';
const useSharingSimulcast = parameters.get('sharingSimulcast') === 'true';
const forceTcp = parameters.get('forceTcp') === 'true'; const forceTcp = parameters.get('forceTcp') === 'true';
const displayName = parameters.get('displayName'); const displayName = parameters.get('displayName');
const muted = parameters.get('muted') === 'true'; const muted = parameters.get('muted') === 'true';
const { pathname } = window.location;
let basePath = pathname.substring(0, pathname.lastIndexOf('/'));
if (!basePath)
basePath = '/';
// Get current device. // Get current device.
const device = deviceInfo(); const device = deviceInfo();
let unsupportedBrowser = false;
let webrtcUnavailable = false;
if (detectDevice() === undefined)
{
logger.error('Your browser is not supported [deviceInfo:"%o"]', device);
unsupportedBrowser = true;
}
else if (
navigator.mediaDevices === undefined ||
navigator.mediaDevices.getUserMedia === undefined ||
window.RTCPeerConnection === undefined
)
{
logger.error('Your browser is not supported [deviceInfo:"%o"]', device);
webrtcUnavailable = true;
}
else if (
!device.bowser.satisfies(
window.config.supportedBrowsers || supportedBrowsers
)
)
{
logger.error(
'Your browser is not supported [deviceInfo:"%o"]',
device
);
unsupportedBrowser = true;
}
else
{
logger.debug('Your browser is supported [deviceInfo:"%o"]', device);
}
if (unsupportedBrowser || webrtcUnavailable)
{
render(
<MuiThemeProvider theme={theme}>
<RawIntlProvider value={intl}>
<UnsupportedBrowser
webrtcUnavailable={webrtcUnavailable}
platform={device.platform}
/>
</RawIntlProvider>
</MuiThemeProvider>,
document.getElementById('multiparty-meeting')
);
return;
}
store.dispatch( store.dispatch(
meActions.setMe({ meActions.setMe({
peerId, peerId,
@ -131,12 +220,11 @@ function run()
peerId, peerId,
accessCode, accessCode,
device, device,
useSimulcast,
useSharingSimulcast,
produce, produce,
forceTcp, forceTcp,
displayName, displayName,
muted muted,
basePath
}); });
global.CLIENT = roomClient; global.CLIENT = roomClient;
@ -148,7 +236,7 @@ function run()
<PersistGate loading={<LoadingView />} persistor={persistor}> <PersistGate loading={<LoadingView />} persistor={persistor}>
<RoomContext.Provider value={roomClient}> <RoomContext.Provider value={roomClient}>
<SnackbarProvider> <SnackbarProvider>
<Router> <Router basename={basePath}>
<Suspense fallback={<LoadingView />}> <Suspense fallback={<LoadingView />}>
<React.Fragment> <React.Fragment>
<Route exact path='/' component={ChooseRoom} /> <Route exact path='/' component={ChooseRoom} />

View File

@ -0,0 +1,20 @@
export const permissions = {
// The role(s) have permission to lock/unlock a room
CHANGE_ROOM_LOCK : 'CHANGE_ROOM_LOCK',
// The role(s) have permission to promote a peer from the lobby
PROMOTE_PEER : 'PROMOTE_PEER',
// The role(s) have permission to send chat messages
SEND_CHAT : 'SEND_CHAT',
// The role(s) have permission to moderate chat
MODERATE_CHAT : 'MODERATE_CHAT',
// The role(s) have permission to share screen
SHARE_SCREEN : 'SHARE_SCREEN',
// The role(s) have permission to produce extra video
EXTRA_VIDEO : 'EXTRA_VIDEO',
// The role(s) have permission to share files
SHARE_FILE : 'SHARE_FILE',
// The role(s) have permission to moderate files
MODERATE_FILES : 'MODERATE_FILES',
// The role(s) have permission to moderate room (e.g. kick user)
MODERATE_ROOM : 'MODERATE_ROOM'
};

View File

@ -30,6 +30,11 @@ const chat = (state = [], action) =>
return [ ...state, ...chatHistory ]; return [ ...state, ...chatHistory ];
} }
case 'CLEAR_CHAT':
{
return [];
}
default: default:
return state; return state;
} }

View File

@ -110,6 +110,11 @@ const consumers = (state = initialState, action) =>
return { ...state, [consumerId]: newConsumer }; return { ...state, [consumerId]: newConsumer };
} }
case 'CLEAR_CONSUMERS':
{
return initialState;
}
default: default:
return state; return state;
} }

View File

@ -85,6 +85,9 @@ const files = (state = {}, action) =>
return { ...state, [magnetUri]: newFile }; return { ...state, [magnetUri]: newFile };
} }
case 'CLEAR_FILES':
return {};
default: default:
return state; return state;
} }

View File

@ -2,6 +2,8 @@ const initialState =
{ {
id : null, id : null,
picture : null, picture : null,
browser : null,
roles : [ 'normal' ], // Default role
canSendMic : false, canSendMic : false,
canSendWebcam : false, canSendWebcam : false,
canShareScreen : false, canShareScreen : false,
@ -13,9 +15,11 @@ const initialState =
screenShareInProgress : false, screenShareInProgress : false,
displayNameInProgress : false, displayNameInProgress : false,
loginEnabled : false, loginEnabled : false,
raiseHand : false, raisedHand : false,
raiseHandInProgress : false, raisedHandInProgress : false,
loggedIn : false loggedIn : false,
isSpeaking : false,
isAutoMuted : true
}; };
const me = (state = initialState, action) => const me = (state = initialState, action) =>
@ -36,6 +40,13 @@ const me = (state = initialState, action) =>
}; };
} }
case 'SET_BROWSER':
{
const { browser } = action.payload;
return { ...state, browser };
}
case 'LOGGED_IN': case 'LOGGED_IN':
{ {
const { flag } = action.payload; const { flag } = action.payload;
@ -43,6 +54,24 @@ const me = (state = initialState, action) =>
return { ...state, loggedIn: flag }; return { ...state, loggedIn: flag };
} }
case 'ADD_ROLE':
{
if (state.roles.includes(action.payload.role))
return state;
const roles = [ ...state.roles, action.payload.role ];
return { ...state, roles };
}
case 'REMOVE_ROLE':
{
const roles = state.roles.filter((role) =>
role !== action.payload.role);
return { ...state, roles };
}
case 'SET_PICTURE': case 'SET_PICTURE':
return { ...state, picture: action.payload.picture }; return { ...state, picture: action.payload.picture };
@ -71,6 +100,13 @@ const me = (state = initialState, action) =>
return { ...state, audioDevices: devices }; return { ...state, audioDevices: devices };
} }
case 'SET_AUDIO_OUTPUT_DEVICES':
{
const { devices } = action.payload;
return { ...state, audioOutputDevices: devices };
}
case 'SET_WEBCAM_DEVICES': case 'SET_WEBCAM_DEVICES':
{ {
const { devices } = action.payload; const { devices } = action.payload;
@ -99,18 +135,18 @@ const me = (state = initialState, action) =>
return { ...state, screenShareInProgress: flag }; return { ...state, screenShareInProgress: flag };
} }
case 'SET_MY_RAISE_HAND_STATE': case 'SET_RAISED_HAND':
{ {
const { flag } = action.payload; const { flag } = action.payload;
return { ...state, raiseHand: flag }; return { ...state, raisedHand: flag };
} }
case 'SET_MY_RAISE_HAND_STATE_IN_PROGRESS': case 'SET_RAISED_HAND_IN_PROGRESS':
{ {
const { flag } = action.payload; const { flag } = action.payload;
return { ...state, raiseHandInProgress: flag }; return { ...state, raisedHandInProgress: flag };
} }
case 'SET_DISPLAY_NAME_IN_PROGRESS': case 'SET_DISPLAY_NAME_IN_PROGRESS':
@ -120,6 +156,20 @@ const me = (state = initialState, action) =>
return { ...state, displayNameInProgress: flag }; return { ...state, displayNameInProgress: flag };
} }
case 'SET_IS_SPEAKING':
{
const { flag } = action.payload;
return { ...state, isSpeaking: flag };
}
case 'SET_AUTO_MUTED':
{
const { flag } = action.payload;
return { ...state, isAutoMuted: flag };
}
default: default:
return state; return state;
} }

View File

@ -10,13 +10,13 @@ const peerVolumes = (state = initialState, action) =>
peerId peerId
} = action.payload; } = action.payload;
return { ...state, [peerId]: 0 }; return { ...state, [peerId]: -100 };
} }
case 'ADD_PEER': case 'ADD_PEER':
{ {
const { peer } = action.payload; const { peer } = action.payload;
return { ...state, [peer.id]: 0 }; return { ...state, [peer.id]: -100 };
} }
case 'REMOVE_PEER': case 'REMOVE_PEER':
@ -31,9 +31,10 @@ const peerVolumes = (state = initialState, action) =>
case 'SET_PEER_VOLUME': case 'SET_PEER_VOLUME':
{ {
const { peerId, volume } = action.payload; const { peerId } = action.payload;
const dBs = action.payload.volume < -100 ? -100 : action.payload.volume;
return { ...state, [peerId]: volume }; return { ...state, [peerId]: Math.round(dBs) };
} }
default: default:

View File

@ -1,4 +1,6 @@
const peer = (state = {}, action) => const initialState = {};
const peer = (state = initialState, action) =>
{ {
switch (action.type) switch (action.type)
{ {
@ -17,8 +19,21 @@ const peer = (state = {}, action) =>
case 'SET_PEER_SCREEN_IN_PROGRESS': case 'SET_PEER_SCREEN_IN_PROGRESS':
return { ...state, peerScreenInProgress: action.payload.flag }; return { ...state, peerScreenInProgress: action.payload.flag };
case 'SET_PEER_RAISE_HAND_STATE': case 'SET_PEER_KICK_IN_PROGRESS':
return { ...state, raiseHandState: action.payload.raiseHandState }; return { ...state, peerKickInProgress: action.payload.flag };
case 'SET_PEER_RAISED_HAND':
return {
...state,
raisedHand : action.payload.raisedHand,
raisedHandTimestamp : action.payload.raisedHandTimestamp
};
case 'SET_PEER_RAISED_HAND_IN_PROGRESS':
return {
...state,
raisedHandInProgress : action.payload.flag
};
case 'ADD_CONSUMER': case 'ADD_CONSUMER':
{ {
@ -40,12 +55,44 @@ const peer = (state = {}, action) =>
return { ...state, picture: action.payload.picture }; return { ...state, picture: action.payload.picture };
} }
case 'ADD_PEER_ROLE':
{
const roles = [ ...state.roles, action.payload.role ];
return { ...state, roles };
}
case 'REMOVE_PEER_ROLE':
{
const roles = state.roles.filter((role) =>
role !== action.payload.role);
return { ...state, roles };
}
case 'STOP_PEER_AUDIO_IN_PROGRESS':
return {
...state,
stopPeerAudioInProgress : action.payload.flag
};
case 'STOP_PEER_VIDEO_IN_PROGRESS':
return {
...state,
stopPeerVideoInProgress : action.payload.flag
};
case 'STOP_PEER_SCREEN_SHARING_IN_PROGRESS':
return {
...state,
stopPeerScreenSharingInProgress : action.payload.flag
};
default: default:
return state; return state;
} }
}; };
const peers = (state = {}, action) => const peers = (state = initialState, action) =>
{ {
switch (action.type) switch (action.type)
{ {
@ -68,9 +115,15 @@ const peers = (state = {}, action) =>
case 'SET_PEER_VIDEO_IN_PROGRESS': case 'SET_PEER_VIDEO_IN_PROGRESS':
case 'SET_PEER_AUDIO_IN_PROGRESS': case 'SET_PEER_AUDIO_IN_PROGRESS':
case 'SET_PEER_SCREEN_IN_PROGRESS': case 'SET_PEER_SCREEN_IN_PROGRESS':
case 'SET_PEER_RAISE_HAND_STATE': case 'SET_PEER_RAISED_HAND':
case 'SET_PEER_RAISED_HAND_IN_PROGRESS':
case 'SET_PEER_PICTURE': case 'SET_PEER_PICTURE':
case 'ADD_CONSUMER': case 'ADD_CONSUMER':
case 'ADD_PEER_ROLE':
case 'REMOVE_PEER_ROLE':
case 'STOP_PEER_AUDIO_IN_PROGRESS':
case 'STOP_PEER_VIDEO_IN_PROGRESS':
case 'STOP_PEER_SCREEN_SHARING_IN_PROGRESS':
{ {
const oldPeer = state[action.payload.peerId]; const oldPeer = state[action.payload.peerId];
@ -82,6 +135,7 @@ const peers = (state = {}, action) =>
return { ...state, [oldPeer.id]: peer(oldPeer, action) }; return { ...state, [oldPeer.id]: peer(oldPeer, action) };
} }
case 'SET_PEER_KICK_IN_PROGRESS':
case 'REMOVE_CONSUMER': case 'REMOVE_CONSUMER':
{ {
const oldPeer = state[action.payload.peerId]; const oldPeer = state[action.payload.peerId];
@ -93,6 +147,11 @@ const peers = (state = {}, action) =>
return { ...state, [oldPeer.id]: peer(oldPeer, action) }; return { ...state, [oldPeer.id]: peer(oldPeer, action) };
} }
case 'CLEAR_PEERS':
{
return initialState;
}
default: default:
return state; return state;
} }

View File

@ -60,6 +60,17 @@ const producers = (state = initialState, action) =>
return { ...state, [producerId]: newProducer }; return { ...state, [producerId]: newProducer };
} }
case 'SET_PRODUCER_SCORE':
{
const { producerId, score } = action.payload;
const producer = state[producerId];
const newProducer = { ...producer, score };
return { ...state, [producerId]: newProducer };
}
default: default:
return state; return state;
} }

View File

@ -1,24 +1,40 @@
const initialState = const initialState =
{ {
name : '', name : '',
state : 'new', // new/connecting/connected/disconnected/closed, // new/connecting/connected/disconnected/closed,
state : 'new',
locked : false, locked : false,
inLobby : false, inLobby : false,
signInRequired : false, signInRequired : false,
accessCode : '', // access code to the room if locked and joinByAccessCode == true overRoomLimit : false,
joinByAccessCode : true, // if true: accessCode is a possibility to open the room // access code to the room if locked and joinByAccessCode == true
accessCode : '',
// if true: accessCode is a possibility to open the room
joinByAccessCode : true,
activeSpeakerId : null, activeSpeakerId : null,
torrentSupport : false, torrentSupport : false,
showSettings : false, showSettings : false,
fullScreenConsumer : null, // ConsumerID fullScreenConsumer : null, // ConsumerID
windowConsumer : null, // ConsumerID windowConsumer : null, // ConsumerID
toolbarsVisible : true, toolbarsVisible : true,
mode : 'democratic', mode : window.config.defaultLayout || 'democratic',
selectedPeerId : null, selectedPeerId : null,
spotlights : [], spotlights : [],
settingsOpen : false, settingsOpen : false,
extraVideoOpen : false,
helpOpen : false,
aboutOpen : false,
currentSettingsTab : 'media', // media, appearence, advanced
lockDialogOpen : false, lockDialogOpen : false,
joined : false joined : false,
muteAllInProgress : false,
lobbyPeersPromotionInProgress : false,
stopAllVideoInProgress : false,
closeMeetingInProgress : false,
clearChatInProgress : false,
clearFileSharingInProgress : false,
roomPermissions : null,
allowWhenRoleMissing : null
}; };
const room = (state = initialState, action) => const room = (state = initialState, action) =>
@ -65,7 +81,12 @@ const room = (state = initialState, action) =>
return { ...state, signInRequired }; return { ...state, signInRequired };
} }
case 'SET_OVER_ROOM_LIMIT':
{
const { overRoomLimit } = action.payload;
return { ...state, overRoomLimit };
}
case 'SET_ACCESS_CODE': case 'SET_ACCESS_CODE':
{ {
const { accessCode } = action.payload; const { accessCode } = action.payload;
@ -94,6 +115,34 @@ const room = (state = initialState, action) =>
return { ...state, settingsOpen }; return { ...state, settingsOpen };
} }
case 'SET_EXTRA_VIDEO_OPEN':
{
const { extraVideoOpen } = action.payload;
return { ...state, extraVideoOpen };
}
case 'SET_HELP_OPEN':
{
const { helpOpen } = action.payload;
return { ...state, helpOpen };
}
case 'SET_ABOUT_OPEN':
{
const { aboutOpen } = action.payload;
return { ...state, aboutOpen };
}
case 'SET_SETTINGS_TAB':
{
const { tab } = action.payload;
return { ...state, currentSettingsTab: tab };
}
case 'SET_ROOM_ACTIVE_SPEAKER': case 'SET_ROOM_ACTIVE_SPEAKER':
{ {
const { peerId } = action.payload; const { peerId } = action.payload;
@ -110,7 +159,7 @@ const room = (state = initialState, action) =>
case 'TOGGLE_JOINED': case 'TOGGLE_JOINED':
{ {
const joined = !state.joined; const joined = true;
return { ...state, joined }; return { ...state, joined };
} }
@ -163,6 +212,46 @@ const room = (state = initialState, action) =>
return { ...state, spotlights }; return { ...state, spotlights };
} }
case 'CLEAR_SPOTLIGHTS':
{
return { ...state, spotlights: [] };
}
case 'SET_LOBBY_PEERS_PROMOTION_IN_PROGRESS':
return { ...state, lobbyPeersPromotionInProgress: action.payload.flag };
case 'MUTE_ALL_IN_PROGRESS':
return { ...state, muteAllInProgress: action.payload.flag };
case 'STOP_ALL_VIDEO_IN_PROGRESS':
return { ...state, stopAllVideoInProgress: action.payload.flag };
case 'STOP_ALL_SCREEN_SHARING_IN_PROGRESS':
return { ...state, stopAllScreenSharingInProgress: action.payload.flag };
case 'CLOSE_MEETING_IN_PROGRESS':
return { ...state, closeMeetingInProgress: action.payload.flag };
case 'CLEAR_CHAT_IN_PROGRESS':
return { ...state, clearChatInProgress: action.payload.flag };
case 'CLEAR_FILE_SHARING_IN_PROGRESS':
return { ...state, clearFileSharingInProgress: action.payload.flag };
case 'SET_ROOM_PERMISSIONS':
{
const { roomPermissions } = action.payload;
return { ...state, roomPermissions };
}
case 'SET_ALLOW_WHEN_ROLE_MISSING':
{
const { allowWhenRoleMissing } = action.payload;
return { ...state, allowWhenRoleMissing };
}
default: default:
return state; return state;
} }

View File

@ -11,14 +11,16 @@ import chat from './chat';
import toolarea from './toolarea'; import toolarea from './toolarea';
import files from './files'; import files from './files';
import settings from './settings'; import settings from './settings';
import transports from './transports';
export default combineReducers({ export default combineReducers({
room, room,
me, me,
producers, producers,
consumers,
transports,
peers, peers,
lobbyPeers, lobbyPeers,
consumers,
peerVolumes, peerVolumes,
notifications, notifications,
chat, chat,

View File

@ -4,9 +4,28 @@ const initialState =
selectedWebcam : null, selectedWebcam : null,
selectedAudioDevice : null, selectedAudioDevice : null,
advancedMode : false, advancedMode : false,
resolution : 'medium', // low, medium, high, veryhigh, ultra sampleRate : 48000,
channelCount : 1,
volume : 1.0,
autoGainControl : false,
echoCancellation : true,
noiseSuppression : true,
voiceActivatedUnmute : false,
noiseThreshold : -50,
sampleSize : 16,
// low, medium, high, veryhigh, ultra
resolution : window.config.defaultResolution || 'medium',
frameRate : window.config.defaultFrameRate || 15,
screenSharingResolution : window.config.defaultScreenResolution || 'veryhigh',
screenSharingFrameRate : window.config.defaultScreenSharingFrameRate || 5,
lastN : 4, lastN : 4,
permanentTopBar : true permanentTopBar : true,
hiddenControls : false,
showNotifications : true,
notificationSounds : true,
buttonControlBar : window.config.buttonControlBar || false,
drawerOverlayed : window.config.drawerOverlayed || true,
...window.config.defaultAudio
}; };
const settings = (state = initialState, action) => const settings = (state = initialState, action) =>
@ -23,6 +42,11 @@ const settings = (state = initialState, action) =>
return { ...state, selectedAudioDevice: action.payload.deviceId }; return { ...state, selectedAudioDevice: action.payload.deviceId };
} }
case 'CHANGE_AUDIO_OUTPUT_DEVICE':
{
return { ...state, selectedAudioOutputDevice: action.payload.deviceId };
}
case 'SET_DISPLAY_NAME': case 'SET_DISPLAY_NAME':
{ {
const { displayName } = action.payload; const { displayName } = action.payload;
@ -37,6 +61,76 @@ const settings = (state = initialState, action) =>
return { ...state, advancedMode }; return { ...state, advancedMode };
} }
case 'SET_SAMPLE_RATE':
{
const { sampleRate } = action.payload;
return { ...state, sampleRate };
}
case 'SET_CHANNEL_COUNT':
{
const { channelCount } = action.payload;
return { ...state, channelCount };
}
case 'SET_VOLUME':
{
const { volume } = action.payload;
return { ...state, volume };
}
case 'SET_AUTO_GAIN_CONTROL':
{
const { autoGainControl } = action.payload;
return { ...state, autoGainControl };
}
case 'SET_ECHO_CANCELLATION':
{
const { echoCancellation } = action.payload;
return { ...state, echoCancellation };
}
case 'SET_NOISE_SUPPRESSION':
{
const { noiseSuppression } = action.payload;
return { ...state, noiseSuppression };
}
case 'SET_VOICE_ACTIVATED_UNMUTE':
{
const { voiceActivatedUnmute } = action.payload;
return { ...state, voiceActivatedUnmute };
}
case 'SET_NOISE_THRESHOLD':
{
const { noiseThreshold } = action.payload;
return { ...state, noiseThreshold };
}
case 'SET_DEFAULT_AUDIO':
{
const { audio } = action.payload;
return { ...state, audio };
}
case 'SET_SAMPLE_SIZE':
{
const { sampleSize } = action.payload;
return { ...state, sampleSize };
}
case 'SET_LAST_N': case 'SET_LAST_N':
{ {
const { lastN } = action.payload; const { lastN } = action.payload;
@ -51,6 +145,41 @@ const settings = (state = initialState, action) =>
return { ...state, permanentTopBar }; return { ...state, permanentTopBar };
} }
case 'TOGGLE_BUTTON_CONTROL_BAR':
{
const buttonControlBar = !state.buttonControlBar;
return { ...state, buttonControlBar };
}
case 'TOGGLE_DRAWER_OVERLAYED':
{
const drawerOverlayed = !state.drawerOverlayed;
return { ...state, drawerOverlayed };
}
case 'TOGGLE_HIDDEN_CONTROLS':
{
const hiddenControls = !state.hiddenControls;
return { ...state, hiddenControls };
}
case 'TOGGLE_NOTIFICATION_SOUNDS':
{
const notificationSounds = !state.notificationSounds;
return { ...state, notificationSounds };
}
case 'TOGGLE_SHOW_NOTIFICATIONS':
{
const showNotifications = !state.showNotifications;
return { ...state, showNotifications };
}
case 'SET_VIDEO_RESOLUTION': case 'SET_VIDEO_RESOLUTION':
{ {
const { resolution } = action.payload; const { resolution } = action.payload;
@ -58,6 +187,27 @@ const settings = (state = initialState, action) =>
return { ...state, resolution }; return { ...state, resolution };
} }
case 'SET_VIDEO_FRAME_RATE':
{
const { frameRate } = action.payload;
return { ...state, frameRate };
}
case 'SET_SCREEN_SHARING_RESOLUTION':
{
const { screenSharingResolution } = action.payload;
return { ...state, screenSharingResolution };
}
case 'SET_SCREEN_SHARING_FRAME_RATE':
{
const { screenSharingFrameRate } = action.payload;
return { ...state, screenSharingFrameRate };
}
default: default:
return state; return state;
} }

View File

@ -0,0 +1,19 @@
const initialState = {};
const transports = (state = initialState, action) =>
{
switch (action.type)
{
case 'ADD_TRANSPORT_STATS':
{
const { transport, type } = action.payload;
return { ...state, [type]: transport[0] };
}
default:
return state;
}
};
export default transports;

View File

@ -49,6 +49,26 @@
"room.spotlights": "Spotlight中的参与者", "room.spotlights": "Spotlight中的参与者",
"room.passive": "被动参与者", "room.passive": "被动参与者",
"room.videoPaused": "该视频已暂停", "room.videoPaused": "该视频已暂停",
"room.muteAll": null,
"room.stopAllVideo": null,
"room.closeMeeting": null,
"room.clearChat": null,
"room.clearFileSharing": null,
"room.speechUnsupported": null,
"room.moderatoractions": null,
"room.raisedHand": null,
"room.loweredHand": null,
"room.extraVideo": null,
"room.overRoomLimit": null,
"room.help": null,
"room.about": null,
"room.shortcutKeys": null,
"room.browsePeersSpotlight": null,
"me.mutedPTT": null,
"roles.gotRole": null,
"roles.lostRole": null,
"tooltip.login": "登录", "tooltip.login": "登录",
"tooltip.logout": "注销", "tooltip.logout": "注销",
@ -60,6 +80,15 @@
"tooltip.lobby": "显示大厅", "tooltip.lobby": "显示大厅",
"tooltip.settings": "显示设置", "tooltip.settings": "显示设置",
"tooltip.participants": "显示参加者", "tooltip.participants": "显示参加者",
"tooltip.kickParticipant": null,
"tooltip.muteParticipant": null,
"tooltip.muteParticipantVideo": null,
"tooltip.raisedHand": null,
"tooltip.muteScreenSharing": null,
"tooltip.muteParticipantAudioModerator": null,
"tooltip.muteParticipantVideoModerator": null,
"tooltip.muteScreenSharingModerator": null,
"room.stopAllScreenSharing": null,
"label.roomName": "房间名称", "label.roomName": "房间名称",
"label.chooseRoomButton": "继续", "label.chooseRoomButton": "继续",
@ -73,6 +102,7 @@
"label.filesharing": "文件共享", "label.filesharing": "文件共享",
"label.participants": "参与者", "label.participants": "参与者",
"label.shareFile": "共享文件", "label.shareFile": "共享文件",
"label.shareGalleryFile": null,
"label.fileSharingUnsupported": "不支持文件共享", "label.fileSharingUnsupported": "不支持文件共享",
"label.unknown": "未知", "label.unknown": "未知",
"label.democratic": "民主视图", "label.democratic": "民主视图",
@ -83,6 +113,13 @@
"label.veryHigh": "非常高 (FHD)", "label.veryHigh": "非常高 (FHD)",
"label.ultra": "超高 (UHD)", "label.ultra": "超高 (UHD)",
"label.close": "关闭", "label.close": "关闭",
"label.media": null,
"label.appearance": null,
"label.advanced": null,
"label.addVideo": null,
"label.promoteAllPeers": null,
"label.moreActions": null,
"label.version": null,
"settings.settings": "设置", "settings.settings": "设置",
"settings.camera": "视频设备", "settings.camera": "视频设备",
@ -91,12 +128,30 @@
"settings.audio": "音频设备", "settings.audio": "音频设备",
"settings.selectAudio": "选择音频设备", "settings.selectAudio": "选择音频设备",
"settings.cantSelectAudio": "无法选择音频设备", "settings.cantSelectAudio": "无法选择音频设备",
"settings.audioOutput": "音频输出设备",
"settings.selectAudioOutput": "选择音频输出设备",
"settings.cantSelectAudioOutput": "无法选择音频输出设备",
"settings.resolution": "选择视频分辨率", "settings.resolution": "选择视频分辨率",
"settings.frameRate": null,
"settings.screenSharingResolution": null,
"settings.screenSharingFrameRate": null,
"settings.layout": "房间布局", "settings.layout": "房间布局",
"settings.selectRoomLayout": "选择房间布局", "settings.selectRoomLayout": "选择房间布局",
"settings.advancedMode": "高级模式", "settings.advancedMode": "高级模式",
"settings.permanentTopBar": "永久顶吧", "settings.permanentTopBar": "永久顶吧",
"settings.lastn": "可见视频数量", "settings.lastn": "可见视频数量",
"settings.hiddenControls": null,
"settings.notificationSounds": null,
"settings.showNotifications": null,
"settings.buttonControlBar": null,
"settings.showAdvancedVideo": null,
"settings.showAdvancedAudio": null,
"settings.echoCancellation": null,
"settings.autoGainControl": null,
"settings.noiseSuppression": null,
"settings.drawerOverlayed": null,
"settings.voiceActivatedUnmute": null,
"settings.noiseThreshold": null,
"filesharing.saveFileError": "无法保存文件", "filesharing.saveFileError": "无法保存文件",
"filesharing.startingFileShare": "正在尝试共享文件", "filesharing.startingFileShare": "正在尝试共享文件",
@ -126,8 +181,8 @@
"devices.microphoneDisconnected": "麦克风已断开", "devices.microphoneDisconnected": "麦克风已断开",
"devices.microphoneError": "麦克风发生错误", "devices.microphoneError": "麦克风发生错误",
"devices.microPhoneMute": "麦克风静音", "devices.microphoneMute": "麦克风静音",
"devices.micophoneUnMute": "取消麦克风静音", "devices.microphoneUnMute": "取消麦克风静音",
"devices.microphoneEnable": "启用了麦克风", "devices.microphoneEnable": "启用了麦克风",
"devices.microphoneMuteError": "无法使麦克风静音", "devices.microphoneMuteError": "无法使麦克风静音",
"devices.microphoneUnMuteError": "无法取消麦克风静音", "devices.microphoneUnMuteError": "无法取消麦克风静音",
@ -136,5 +191,15 @@
"devices.screenSharingError": "访问屏幕时发生错误", "devices.screenSharingError": "访问屏幕时发生错误",
"devices.cameraDisconnected": "相机已断开连接", "devices.cameraDisconnected": "相机已断开连接",
"devices.cameraError": "访问相机时发生错误" "devices.cameraError": "访问相机时发生错误",
"moderator.clearChat": null,
"moderator.clearFiles": null,
"moderator.muteAudio": null,
"moderator.muteVideo": null,
"moderator.stopScreenSharing": null,
"unsupportedBrowser.titleUnsupportedBrowser": null,
"unsupportedBrowser.titlewebrtcUnavailable": null,
"unsupportedBrowser.bodyText": null
} }

View File

@ -48,6 +48,27 @@
"room.spotlights": "Aktivní Účastníci", "room.spotlights": "Aktivní Účastníci",
"room.passive": "Pasivní Účastníci", "room.passive": "Pasivní Účastníci",
"room.videoPaused": "Toto video bylo pozastaveno", "room.videoPaused": "Toto video bylo pozastaveno",
"room.muteAll": null,
"room.stopAllVideo": null,
"room.closeMeeting": null,
"room.clearChat": null,
"room.clearFileSharing": null,
"room.speechUnsupported": null,
"room.moderatoractions": null,
"room.raisedHand": null,
"room.loweredHand": null,
"room.extraVideo": null,
"room.overRoomLimit": null,
"room.help": null,
"room.about": null,
"room.shortcutKeys": null,
"room.browsePeersSpotlight": null,
"room.stopAllScreenSharing": null,
"me.mutedPTT": null,
"roles.gotRole": null,
"roles.lostRole": null,
"tooltip.login": "Přihlášení", "tooltip.login": "Přihlášení",
"tooltip.logout": "Odhlášení", "tooltip.logout": "Odhlášení",
@ -58,6 +79,15 @@
"tooltip.leaveFullscreen": "Vypnout režim celé obrazovky (fullscreen)", "tooltip.leaveFullscreen": "Vypnout režim celé obrazovky (fullscreen)",
"tooltip.lobby": "Ukázat Přijímací místnost", "tooltip.lobby": "Ukázat Přijímací místnost",
"tooltip.settings": "Zobrazit nastavení", "tooltip.settings": "Zobrazit nastavení",
"tooltip.participants": null,
"tooltip.kickParticipant": null,
"tooltip.muteParticipant": null,
"tooltip.muteParticipantVideo": null,
"tooltip.raisedHand": null,
"tooltip.muteScreenSharing": null,
"tooltip.muteParticipantAudioModerator": null,
"tooltip.muteParticipantVideoModerator": null,
"tooltip.muteScreenSharingModerator": null,
"label.roomName": "Jméno místnosti", "label.roomName": "Jméno místnosti",
"label.chooseRoomButton": "Pokračovat", "label.chooseRoomButton": "Pokračovat",
@ -71,6 +101,7 @@
"label.filesharing": "Sdílení souborů", "label.filesharing": "Sdílení souborů",
"label.participants": "Účastníci", "label.participants": "Účastníci",
"label.shareFile": "Sdílet soubor", "label.shareFile": "Sdílet soubor",
"label.shareGalleryFile": null,
"label.fileSharingUnsupported": "Sdílení souborů není podporováno", "label.fileSharingUnsupported": "Sdílení souborů není podporováno",
"label.unknown": "Neznámý", "label.unknown": "Neznámý",
"label.democratic": "Rozvržení: Demokratické", "label.democratic": "Rozvržení: Demokratické",
@ -81,6 +112,13 @@
"label.veryHigh": "Velmi vysoké (FHD)", "label.veryHigh": "Velmi vysoké (FHD)",
"label.ultra": "Ultra (UHD)", "label.ultra": "Ultra (UHD)",
"label.close": "Zavřít", "label.close": "Zavřít",
"label.media": null,
"label.appearance": null,
"label.advanced": null,
"label.addVideo": null,
"label.promoteAllPeers": null,
"label.moreActions": null,
"label.version": null,
"settings.settings": "Nastavení", "settings.settings": "Nastavení",
"settings.camera": "Kamera", "settings.camera": "Kamera",
@ -89,10 +127,30 @@
"settings.audio": "Audio zařízení", "settings.audio": "Audio zařízení",
"settings.selectAudio": "Vyberte audio zařízení", "settings.selectAudio": "Vyberte audio zařízení",
"settings.cantSelectAudio": "Není možno vybrat audio zařízení", "settings.cantSelectAudio": "Není možno vybrat audio zařízení",
"settings.audioOutput": "Audio output zařízení",
"settings.selectAudioOutput": "Vyberte audio output zařízení",
"settings.cantSelectAudioOutput": "Není možno vybrat audio output zařízení",
"settings.resolution": "Vyberte rozlišení vašeho videa", "settings.resolution": "Vyberte rozlišení vašeho videa",
"settings.frameRate": null,
"settings.screenSharingResolution": null,
"settings.screenSharingFrameRate": null,
"settings.layout": "Rozvržení místnosti", "settings.layout": "Rozvržení místnosti",
"settings.selectRoomLayout": "Vyberte rozvržení místnosti", "settings.selectRoomLayout": "Vyberte rozvržení místnosti",
"settings.advancedMode": "Pokočilý mód", "settings.advancedMode": "Pokočilý mód",
"settings.permanentTopBar": null,
"settings.lastn": null,
"settings.hiddenControls": null,
"settings.notificationSounds": null,
"settings.showNotifications": null,
"settings.buttonControlBar": null,
"settings.showAdvancedVideo": null,
"settings.showAdvancedAudio": null,
"settings.echoCancellation": null,
"settings.autoGainControl": null,
"settings.noiseSuppression": null,
"settings.drawerOverlayed": null,
"settings.voiceActivatedUnmute": null,
"settings.noiseThreshold": null,
"filesharing.saveFileError": "Není možné uložit soubor", "filesharing.saveFileError": "Není možné uložit soubor",
"filesharing.startingFileShare": "Pokouším se sdílet soubor", "filesharing.startingFileShare": "Pokouším se sdílet soubor",
@ -122,8 +180,8 @@
"devices.microphoneDisconnected": "Mikrofon odpojen", "devices.microphoneDisconnected": "Mikrofon odpojen",
"devices.microphoneError": "Při přístupu k vašemu mikrofonu se vyskytla chyba", "devices.microphoneError": "Při přístupu k vašemu mikrofonu se vyskytla chyba",
"devices.microPhoneMute": "Mikrofon ztišen", "devices.microphoneMute": "Mikrofon ztišen",
"devices.micophoneUnMute": "Ztišení mikrofonu zrušeno", "devices.microphoneUnMute": "Ztišení mikrofonu zrušeno",
"devices.microphoneEnable": "Mikrofon povolen", "devices.microphoneEnable": "Mikrofon povolen",
"devices.microphoneMuteError": "Není možné ztišit váš mikrofon", "devices.microphoneMuteError": "Není možné ztišit váš mikrofon",
"devices.microphoneUnMuteError": "Není možné zrušit ztišení vašeho mikrofonu", "devices.microphoneUnMuteError": "Není možné zrušit ztišení vašeho mikrofonu",
@ -132,5 +190,15 @@
"devices.screenSharingError": "Při přístupu k vaší obrazovce se vyskytla chyba", "devices.screenSharingError": "Při přístupu k vaší obrazovce se vyskytla chyba",
"devices.cameraDisconnected": "Kamera odpojena", "devices.cameraDisconnected": "Kamera odpojena",
"devices.cameraError": "Při přístupu k vaší kameře se vyskytla chyba" "devices.cameraError": "Při přístupu k vaší kameře se vyskytla chyba",
"moderator.clearChat": null,
"moderator.clearFiles": null,
"moderator.muteAudio": null,
"moderator.muteVideo": null,
"moderator.stopScreenSharing": null,
"unsupportedBrowser.titleUnsupportedBrowser": null,
"unsupportedBrowser.titlewebrtcUnavailable": null,
"unsupportedBrowser.bodyText": null
} }

View File

@ -1,46 +1,46 @@
{ {
"socket.disconnected": "Verbindung unterbrochen", "socket.disconnected": "Verbindung unterbrochen",
"socket.reconnecting": "Verbindung unterbrochen, versuche neu zu verbinden", "socket.reconnecting": "Verbindung unterbrochen, versuche neu zu verbinden",
"socket.reconnected": "Verbindung wieder herges|tellt", "socket.reconnected": "Verbindung wiederhergestellt",
"socket.requestError": "Fehler bei Serveranfrage", "socket.requestError": "Fehler bei Serveranfrage",
"room.chooseRoom": "Wähle den Namen für den Raum, den du betreten möchtest", "room.chooseRoom": "Wähle den Raum aus, den du betreten möchtest",
"room.cookieConsent": "Diese Seite verwendet Cookies, um die Benutzerfreundlichkeit zu erhöhen", "room.cookieConsent": "Diese Seite verwendet Cookies, um die Benutzerfreundlichkeit zu erhöhen",
"room.consentUnderstand": "Ich stimme zu", "room.consentUnderstand": "Verstanden",
"room.joined": "Konferenzraum betreten", "room.joined": "Du bist dem Raum beigetreten",
"room.cantJoin": "Betreten des Raumes nicht möglich", "room.cantJoin": "Betreten des Raumes nicht möglich",
"room.youLocked": "Raum wurde abgeschlossen", "room.youLocked": "Du hast den Raum abgeschlossen",
"room.cantLock": "Abschließen des Raumes nicht möglich", "room.cantLock": "Abschließen des Raumes nicht möglich",
"room.youUnLocked": "Raum geöffnet", "room.youUnLocked": "Du hast den Raum geöffnet",
"room.cantUnLock": "Öffnen des Raumes nicht möglich", "room.cantUnLock": "Öffnen des Raumes nicht möglich",
"room.locked": "Raum wurde abgeschlossen", "room.locked": "Raum wurde abgeschlossen",
"room.unlocked": "Raum wurde geöffnet", "room.unlocked": "Raum wurde geöffnet",
"room.newLobbyPeer": "Neuer Teilnehmer im Empfangsraum", "room.newLobbyPeer": "Neuer Teilnehmer im Warteraum",
"room.lobbyPeerLeft": "Teilnehmer hat Empfangsraum verlassen", "room.lobbyPeerLeft": "Ein Teilnehmer hat den Warteraum verlassen",
"room.lobbyPeerChangedDisplayName": "Teilnehmer im Empfangsraum hat seinen Namen geändert: {displayName}", "room.lobbyPeerChangedDisplayName": "Ein Teilnehmer im Warteraum hat seinen Namen geändert zu: {displayName}",
"room.lobbyPeerChangedPicture": "Teilnehmer in Empfangsraum hat sein Avatar geändert", "room.lobbyPeerChangedPicture": "Ein Teilnehmer im Warteraum hat seinen Avatar geändert",
"room.setAccessCode": "Zugangskode für den Raum geändert", "room.setAccessCode": "Zugangscode für den Raum geändert",
"room.accessCodeOn": "Zugangskode aktiviert", "room.accessCodeOn": "Zugangscode aktiviert",
"room.accessCodeOff": "Zugangskode deaktiviert", "room.accessCodeOff": "Zugangscode deaktiviert",
"room.peerChangedDisplayName": "{oldDisplayName} heißt jetzt {displayName}", "room.peerChangedDisplayName": "{oldDisplayName} heißt jetzt {displayName}",
"room.newPeer": "{displayName} hat den Raum betreten", "room.newPeer": "{displayName} hat den Raum betreten",
"room.newFile": "Neue Datei verfügbar", "room.newFile": "Neue Datei verfügbar",
"room.toggleAdvancedMode": "Erweiterter Modus aktiv", "room.toggleAdvancedMode": "Erweiterter Modus aktiv",
"room.setDemocraticView": "Raumlayout demokratisch", "room.setDemocraticView": "Demokratische Ansicht",
"room.setFilmStripView": "Raumlayout Filmstreifen", "room.setFilmStripView": "Filmstreifen-Ansicht",
"room.loggedIn": "Angemeldet", "room.loggedIn": "Angemeldet",
"room.loggedOut": "Abgemeldet", "room.loggedOut": "Abgemeldet",
"room.changedDisplayName": "Dein Name ist jetzt {displayName}", "room.changedDisplayName": "Dein Name ist jetzt {displayName}",
"room.changeDisplayNameError": "Konnte Name nicht ändern", "room.changeDisplayNameError": "Dein Name konnte nicht geändert werden",
"room.chatError": "Konnte Meldung nicht senden", "room.chatError": "Die Chat-Nachricht konnte nicht gesendet werden",
"room.aboutToJoin": "Du bist dabei den Raum zu betreten", "room.aboutToJoin": "Du bist dabei, folgenden Raum zu betreten:",
"room.roomId": "Raum ID: {roomName}", "room.roomId": "Raum ID: {roomName}",
"room.setYourName": "Gib deinen Namen an und wähle wie den Raum betreten willst", "room.setYourName": "Gib deinen Namen an und wähle aus, wie du den Raum betreten willst:",
"room.audioOnly": "Nur Audio", "room.audioOnly": "Nur Audio",
"room.audioVideo": "Audio und Video", "room.audioVideo": "Audio und Video",
"room.youAreReady": "Ok, Du bist bereit", "room.youAreReady": "Ok, du bist bereit",
"room.emptyRequireLogin": "Der Raum ist leer. Melde dich an um den Raum zu aktivieren, oder warte bis der Raum aktiviert wird", "room.emptyRequireLogin": "Der Raum ist leer. Melde dich an um die Konferenz zu starten oder warte bis der Raum aktiviert wird",
"room.locketWait": "Der Raum ist abgeschlossen, warte bis Dir jemand öffnet", "room.locketWait": "Der Raum ist abgeschlossen - warte, bis dich jemand rein lässt...",
"room.lobbyAdministration": "Warteraum", "room.lobbyAdministration": "Warteraum",
"room.peersInLobby": "Teilnehmer im Warteraum", "room.peersInLobby": "Teilnehmer im Warteraum",
"room.lobbyEmpty": "Der Warteraum ist leer", "room.lobbyEmpty": "Der Warteraum ist leer",
@ -49,92 +49,157 @@
"room.spotlights": "Aktive Teinehmer", "room.spotlights": "Aktive Teinehmer",
"room.passive": "Passive Teilnehmer", "room.passive": "Passive Teilnehmer",
"room.videoPaused": "Video gestoppt", "room.videoPaused": "Video gestoppt",
"room.muteAll": "Alle stummschalten",
"room.stopAllVideo": "Alle Videos stoppen",
"room.closeMeeting": "Meeting beenden",
"room.clearChat": "Liste löschen",
"room.clearFileSharing": "Liste löschen",
"room.speechUnsupported": "Dein Browser unterstützt keine Spracherkennung",
"room.moderatoractions": "Moderator Aktionen",
"room.raisedHand": "{displayName} hebt die Hand",
"room.loweredHand": "{displayName} senkt die Hand",
"room.extraVideo": "Video hinzufügen",
"room.overRoomLimit": "Der Raum ist voll, probiere es später nochmal",
"room.help": "Hilfe",
"room.about": "Über",
"room.shortcutKeys": "Tastaturkürzel",
"room.browsePeersSpotlight": "Teilnehmer durchgehen und anzeigen",
"room.stopAllScreenSharing": "Stoppe alle Bildschirmfreigaben",
"me.mutedPTT": "Du bist stummgeschaltet. Halte die LEERTASTE um zu sprechen",
"roles.gotRole": "Rolle erhalten: {role}",
"roles.lostRole": "Rolle entzogen: {role}",
"tooltip.login": "Anmelden", "tooltip.login": "Anmelden",
"tooltip.logout": "Abmelden", "tooltip.logout": "Abmelden",
"tooltip.admitFromLobby": "Teilnehmer aktivieren", "tooltip.admitFromLobby": "Teilnehmer reinlassen",
"tooltip.lockRoom": "Raum abschließen", "tooltip.lockRoom": "Raum abschließen",
"tooltip.unLockRoom": "Raum öffnen", "tooltip.unLockRoom": "Raum entsperren",
"tooltip.enterFullscreen": "Vollbild", "tooltip.enterFullscreen": "Vollbild",
"tooltip.leaveFullscreen": "Vollbild verlassen", "tooltip.leaveFullscreen": "Vollbild verlassen",
"tooltip.lobby": "Warteraum", "tooltip.lobby": "Warteraum",
"tooltip.settings": "Einstellungen", "tooltip.settings": "Einstellungen",
"tooltip.participants": "Teilnehmer", "tooltip.participants": "Teilnehmer",
"tooltip.kickParticipant": "Rauswerfen",
"tooltip.muteParticipant": "Stummschalten",
"tooltip.muteParticipantVideo": "Video stoppen",
"tooltip.raisedHand": "Hand heben",
"tooltip.muteScreenSharing": "Stoppe Bildschirmfreigabe",
"tooltip.muteParticipantAudioModerator": "Teilnehmer-Audio global stummschalten",
"tooltip.muteParticipantVideoModerator": "Teilnehmer-Video global stoppen",
"tooltip.muteScreenSharingModerator": "Teilnehmer-Bildschirmfreigabe global beenden",
"label.roomName": "Name des Raumes", "label.roomName": "Name des Raums",
"label.chooseRoomButton": "Weiter", "label.chooseRoomButton": "Weiter",
"label.yourName": "Dein Name", "label.yourName": "Dein Name",
"label.newWindow": "In separatem Fenster öffnen", "label.newWindow": "Neues Fenster",
"label.fullscreen": "Vollbild", "label.fullscreen": "Vollbild",
"label.openDrawer": "Menü", "label.openDrawer": "Menü",
"label.leave": "Ausgang", "label.leave": "Verlassen",
"label.chatInput": "Schreibe Chat...", "label.chatInput": "Schreibe eine Nachricht...",
"label.chat": "Chat", "label.chat": "Chat",
"label.filesharing": "Dateien", "label.filesharing": "Dateien",
"label.participants": "Teilnehmer", "label.participants": "Teilnehmer",
"label.shareFile": "Teile Datai", "label.shareFile": "Datei hochladen",
"label.shareGalleryFile": "Bild teilen",
"label.fileSharingUnsupported": "Dateifreigabe nicht unterstützt", "label.fileSharingUnsupported": "Dateifreigabe nicht unterstützt",
"label.unknown": "Unbekannt", "label.unknown": "Unbekannt",
"label.democratic": "Demokratisch", "label.democratic": "Demokratisch",
"label.filmstrip": "Filmstreifen", "label.filmstrip": "Filmstreifen",
"label.low": "Niedrig", "label.low": "Niedrig",
"label.medium": "Medium", "label.medium": "Mittel",
"label.high": "Hoch (HD)", "label.high": "Hoch (HD)",
"label.veryHigh": "Sehr hoch (FHD)", "label.veryHigh": "Sehr hoch (FHD)",
"label.ultra": "Ultra (UHD)", "label.ultra": "Ultra (UHD)",
"label.close": "Schließen", "label.close": "Schließen",
"label.media": "Audio / Video",
"label.appearance": "Ansicht",
"label.advanced": "Erweitert",
"label.addVideo": "Video hinzufügen",
"label.promoteAllPeers": "Alle Teilnehmer reinlassen",
"label.moreActions": "Weitere Aktionen",
"label.version": "Version",
"settings.settings": "Einstellungen", "settings.settings": "Einstellungen",
"settings.camera": "Kamera", "settings.camera": "Kamera",
"settings.selectCamera": "Wähle Videogerät", "settings.selectCamera": "Wähle ein Videogerät",
"settings.cantSelectCamera": "Kann Videogerät nicht aktivieren", "settings.cantSelectCamera": "Kann Videogerät nicht aktivieren",
"settings.audio": "Audiogerät", "settings.audio": "Audiogerät",
"settings.selectAudio": "Wähle Audiogerät", "settings.selectAudio": "Wähle ein Audiogerät",
"settings.cantSelectAudio": "Kann Audiogerät nicht aktivieren", "settings.cantSelectAudio": "Kann Audiogerät nicht aktivieren",
"settings.resolution": "Wähle Auflösung", "settings.audioOutput": "Audioausgabegerät",
"settings.selectAudioOutput": "Wähle ein Audioausgabegerät",
"settings.cantSelectAudioOutput": "Kann Audioausgabegerät nicht aktivieren",
"settings.resolution": "Wähle eine Auflösung",
"settings.frameRate": null,
"settings.screenSharingResolution": null,
"settings.screenSharingFrameRate": null,
"settings.layout": "Raumlayout", "settings.layout": "Raumlayout",
"settings.selectRoomLayout": "Wähle Raumlayout", "settings.selectRoomLayout": "Wähle ein Raumlayout",
"settings.advancedMode": "Erweiterter Modus", "settings.advancedMode": "Erweiterter Modus",
"settings.permanentTopBar": "Permanente obere Leiste", "settings.permanentTopBar": "Permanente obere Leiste",
"settings.lastn": "Anzahl der sichtbaren Videos", "settings.lastn": "Anzahl der sichtbaren Videos",
"settings.hiddenControls": "Medienwerkzeugleiste automatisch ausblenden",
"settings.notificationSounds": "Audiosignal bei Benachrichtigungen",
"settings.showNotifications": "Zeige Benachrichtigungen",
"settings.buttonControlBar": "Separate seitliche Medienwerkzeugleiste",
"settings.showAdvancedVideo": null,
"settings.showAdvancedAudio": "Erweiterte Audio-Einstellungen",
"settings.echoCancellation": "Echounterdrückung",
"settings.autoGainControl": "Automatische Pegelregelung (Audioeingang)",
"settings.noiseSuppression": "Rauschunterdrückung",
"settings.drawerOverlayed": "Seitenpanel verdeckt Hauptinhalt",
"settings.voiceActivatedUnmute": "Automatische Spracherkennung",
"settings.noiseThreshold": "Geräuschepegel",
"filesharing.saveFileError": "Fehler beim Speichern der Datei", "filesharing.saveFileError": "Fehler beim Speichern der Datei",
"filesharing.startingFileShare": "Starte Teilen der Datei", "filesharing.startingFileShare": "Starte Teilen der Datei",
"filesharing.successfulFileShare": "Datei wurde geteilt", "filesharing.successfulFileShare": "Datei wurde geteilt",
"filesharing.unableToShare": "Kann Datei nicht teilen", "filesharing.unableToShare": "Datei kann nicht geteilt werden",
"filesharing.error": "Fehler beim Teilen der Datei", "filesharing.error": "Fehler beim Teilen der Datei",
"filesharing.finished": "Datei heruntergeladen", "filesharing.finished": "Datei heruntergeladen",
"filesharing.save": "Speichern", "filesharing.save": "Speichern",
"filesharing.sharedFile": "{displayName} hat eine Datei geteilt", "filesharing.sharedFile": "{displayName} hat eine Datei geteilt",
"filesharing.download": "Herunterladen", "filesharing.download": "Herunterladen",
"filesharing.missingSeeds": "Wenn das Herunterladen nicht pausiert, ist wahrscheinlich niemand mehr im Raum, der die Datei teilen kann. Datei muss erneut geteilt werden.", "filesharing.missingSeeds": "Wenn der Download zu lange dauert, ist wahrscheinlich keiner mehr im Raum, der die Datei teilen kann. Die Datei muss erneut hochgeladen werden.",
"devices.devicesChanged": "Mediengeräte wurden aktualisiert und sind in Einstellungen verfügbar", "devices.devicesChanged": "Mediengeräte wurden aktualisiert und sind in den Einstellungen verfügbar",
"device.audioUnsupported": "Audio nicht unterstützt", "device.audioUnsupported": "Audio nicht unterstützt",
"device.activateAudio": "Aktiviere Audio", "device.activateAudio": "Aktiviere Audio",
"device.muteAudio": "stummschalten", "device.muteAudio": "Stummschalten",
"device.unMuteAudio": "Aktiviere Audio", "device.unMuteAudio": "Stummschaltung aufheben",
"device.videoUnsupported": "Video nicht unterstützt", "device.videoUnsupported": "Video nicht unterstützt",
"device.startVideo": "Starte Video", "device.startVideo": "Starte Video",
"device.stopVideo": "Stoppe Video", "device.stopVideo": "Stoppe Video",
"device.screenSharingUnsupported": "Bildschirmteilen nicht unterstützt", "device.screenSharingUnsupported": "Bildschirmfreigabe nicht unterstützt",
"device.startScreenSharing": "Bildschirmteilen", "device.startScreenSharing": "Starte Bildschirmfreigabe",
"device.stopScreenSharing": "Beende Bildschirmteilen", "device.stopScreenSharing": "Beende Bildschirmfreigabe",
"devices.microphoneDisconnected": "Mikrophon nicht verbunden", "devices.microphoneDisconnected": "Mikrofon nicht verbunden",
"devices.microphoneError": "Fehler mit Mikrophon", "devices.microphoneError": "Fehler beim Zugriff auf dein Mikrofon",
"devices.microPhoneMute": "Mikrophon stumm geschaltet", "devices.microphoneMute": "Mikrofon stummgeschaltet",
"devices.micophoneUnMute": "Mikrophon aktiviert", "devices.microphoneUnMute": "Mikrofon aktiviert",
"devices.microphoneEnable": "Mikrofonen aktiviert", "devices.microphoneEnable": "Mikrofon aktiviert",
"devices.microphoneMuteError": "Kann Mikrophon nicht stummschalten", "devices.microphoneMuteError": "Kann Mikrofon nicht stummschalten",
"devices.microphoneUnMuteError": "Kann Mikrophon nicht aktivieren", "devices.microphoneUnMuteError": "Kann Mikrofon nicht aktivieren",
"devices.screenSharingDisconnected" : "Bildschirmteilen unterbrochen", "devices.screenSharingDisconnected" : "Bildschirmfreigabe unterbrochen",
"devices.screenSharingError": "Fehler beim Bildschirmteilen", "devices.screenSharingError": "Fehler bei der Bildschirmfreigabe",
"devices.cameraDisconnected": "Video unterbrochen", "devices.cameraDisconnected": "Kamera getrennt",
"devices.cameraError": "Fehler mit Videogerät" "devices.cameraError": "Fehler mit deiner Kamera",
"moderator.clearChat": "Moderator hat Chat gelöscht",
"moderator.clearFiles": "Moderator hat geteilte Dateiliste gelöscht",
"moderator.muteAudio": "Moderator hat dich stummgeschaltet",
"moderator.muteVideo": "Moderator hat dein Video gestoppt",
"moderator.stopScreenSharing": "Moderator hat deine Bildschirmfreigabe gestoppt",
"unsupportedBrowser.titleUnsupportedBrowser": "Browser wird nicht unterstützt!",
"unsupportedBrowser.titlewebrtcUnavailable": "Benötigte Funktionen sind in deinem Browser nicht verfügbar!",
"unsupportedBrowser.bodyText": "Dieser Konferenz-Dienst benötigt Funktionen die von deinem Browser nicht unterstützt werden. Bitte aktualisiere deinen Browser, wechsle den Browser oder überprüfe die Browser-Einstellungen. Unterstützte Browser:"
} }

View File

@ -49,6 +49,27 @@
"room.spotlights": "Deltagere i fokus", "room.spotlights": "Deltagere i fokus",
"room.passive": "Passive deltagere", "room.passive": "Passive deltagere",
"room.videoPaused": "Denne video er sat på pause", "room.videoPaused": "Denne video er sat på pause",
"room.muteAll": null,
"room.stopAllVideo": null,
"room.closeMeeting": null,
"room.clearChat": null,
"room.clearFileSharing": null,
"room.speechUnsupported": null,
"room.moderatoractions": null,
"room.raisedHand": null,
"room.loweredHand": null,
"room.extraVideo": null,
"room.overRoomLimit": null,
"room.help": null,
"room.about": null,
"room.shortcutKeys": null,
"room.browsePeersSpotlight": null,
"room.stopAllScreenSharing": null,
"me.mutedPTT": null,
"roles.gotRole": null,
"roles.lostRole": null,
"tooltip.login": "Log ind", "tooltip.login": "Log ind",
"tooltip.logout": "Log ud", "tooltip.logout": "Log ud",
@ -60,6 +81,14 @@
"tooltip.lobby": "Vis lobby", "tooltip.lobby": "Vis lobby",
"tooltip.settings": "Vis indstillinger", "tooltip.settings": "Vis indstillinger",
"tooltip.participants": "Vis deltagere", "tooltip.participants": "Vis deltagere",
"tooltip.kickParticipant": null,
"tooltip.muteParticipant": null,
"tooltip.muteParticipantVideo": null,
"tooltip.raisedHand": null,
"tooltip.muteScreenSharing": null,
"tooltip.muteParticipantAudioModerator": null,
"tooltip.muteParticipantVideoModerator": null,
"tooltip.muteScreenSharingModerator": null,
"label.roomName": "Værelsesnavn", "label.roomName": "Værelsesnavn",
"label.chooseRoomButton": "Fortsæt", "label.chooseRoomButton": "Fortsæt",
@ -73,6 +102,7 @@
"label.filesharing": "Fildeling", "label.filesharing": "Fildeling",
"label.participants": "Deltagere", "label.participants": "Deltagere",
"label.shareFile": "Del fil", "label.shareFile": "Del fil",
"label.shareGalleryFile": null,
"label.fileSharingUnsupported": "Fildeling er ikke understøttet", "label.fileSharingUnsupported": "Fildeling er ikke understøttet",
"label.unknown": "Ukendt", "label.unknown": "Ukendt",
"label.democracy": "Galleri visning", "label.democracy": "Galleri visning",
@ -83,6 +113,13 @@
"label.veryHigh": "Meget høj (FHD)", "label.veryHigh": "Meget høj (FHD)",
"label.ultra": "Ultra (UHD)", "label.ultra": "Ultra (UHD)",
"label.close": "Luk", "label.close": "Luk",
"label.media": null,
"label.appearance": null,
"label.advanced": null,
"label.addVideo": null,
"label.promoteAllPeers": null,
"label.moreActions": null,
"label.version": null,
"settings.settings": "Indstillinger", "settings.settings": "Indstillinger",
"settings.camera": "Kamera", "settings.camera": "Kamera",
@ -91,12 +128,30 @@
"settings.audio": "Lydenhed", "settings.audio": "Lydenhed",
"settings.selectAudio": "Vælg lydenhed", "settings.selectAudio": "Vælg lydenhed",
"settings.cantSelectAudio": "Kan ikke vælge lydenhed", "settings.cantSelectAudio": "Kan ikke vælge lydenhed",
"settings.audioOutput": "Audio output enhed",
"settings.selectAudioOutput": "Vælg lydudgangsenhed",
"settings.cantSelectAudioOutput": "Kan ikke vælge lydoutputenhed",
"settings.resolution": "Vælg din videoopløsning", "settings.resolution": "Vælg din videoopløsning",
"settings.frameRate": null,
"settings.screenSharingResolution": null,
"settings.screenSharingFrameRate": null,
"settings.layout": "Møde visning", "settings.layout": "Møde visning",
"settings.selectRoomLayout": "Vælg møde visning", "settings.selectRoomLayout": "Vælg møde visning",
"settings.advancedMode": "Avanceret tilstand", "settings.advancedMode": "Avanceret tilstand",
"settings.permanentTopBar": "Permanent øverste linje", "settings.permanentTopBar": "Permanent øverste linje",
"settings.lastn": "Antal synlige videoer", "settings.lastn": "Antal synlige videoer",
"settings.hiddenControls": null,
"settings.notificationSounds": null,
"settings.showNotifications": null,
"settings.buttonControlBar": null,
"settings.showAdvancedVideo": null,
"settings.showAdvancedAudio": null,
"settings.echoCancellation": null,
"settings.autoGainControl": null,
"settings.noiseSuppression": null,
"settings.drawerOverlayed": null,
"settings.voiceActivatedUnmute": null,
"settings.noiseThreshold": null,
"filesharing.saveFileError": "Kan ikke gemme fil", "filesharing.saveFileError": "Kan ikke gemme fil",
"filesharing.startingFileShare": "Forsøger at dele filen", "filesharing.startingFileShare": "Forsøger at dele filen",
@ -106,7 +161,7 @@
"filesharing.finished": "Filen er færdig med at downloade", "filesharing.finished": "Filen er færdig med at downloade",
"filesharing.save": "Gem", "filesharing.save": "Gem",
"filesharing.sharedFile": "{displayName} delte en fil", "filesharing.sharedFile": "{displayName} delte en fil",
"filesharing.download": "Download", "filesharing.download": null,
"filesharing.missingSeeds": "Hvis denne proces tager lang tid, er der muligvis ikke nogen, der seedede denne torrent. Prøv at bede nogen om at uploade den fil, du ønsker at hente.", "filesharing.missingSeeds": "Hvis denne proces tager lang tid, er der muligvis ikke nogen, der seedede denne torrent. Prøv at bede nogen om at uploade den fil, du ønsker at hente.",
"device.devicesChanged": "Detekteret ndringer i dine enheder, konfigurer dine enheder i indstillingsdialogen", "device.devicesChanged": "Detekteret ndringer i dine enheder, konfigurer dine enheder i indstillingsdialogen",
@ -126,8 +181,8 @@
"device.microphoneDisconnected": "Mikrofon frakoblet", "device.microphoneDisconnected": "Mikrofon frakoblet",
"device.microphoneError": "Der opstod en fejl under adgang til din mikrofon", "device.microphoneError": "Der opstod en fejl under adgang til din mikrofon",
"device.microPhoneMute": "Dæmp din mikrofon", "device.microphoneMute": "Dæmp din mikrofon",
"device.micophoneUnMute": "Slå ikke lyden fra din mikrofon", "device.microphoneUnMute": "Slå ikke lyden fra din mikrofon",
"device.microphoneEnable": "Aktiveret din mikrofon", "device.microphoneEnable": "Aktiveret din mikrofon",
"device.microphoneMuteError": "Kan ikke slå din mikrofon fra", "device.microphoneMuteError": "Kan ikke slå din mikrofon fra",
"device.microphoneUnMuteError": "Kan ikke slå lyden til på din mikrofon", "device.microphoneUnMuteError": "Kan ikke slå lyden til på din mikrofon",
@ -136,5 +191,15 @@
"devices.screenSharingError": "Der opstod en fejl ved adgang til skærmdeling", "devices.screenSharingError": "Der opstod en fejl ved adgang til skærmdeling",
"device.cameraDisconnected": "Kamera frakoblet", "device.cameraDisconnected": "Kamera frakoblet",
"device.cameraError": "Der opstod en fejl ved tilkobling af dit kamera" "device.cameraError": "Der opstod en fejl ved tilkobling af dit kamera",
"moderator.clearChat": null,
"moderator.clearFiles": null,
"moderator.muteAudio": null,
"moderator.muteVideo": null,
"moderator.stopScreenSharing": null,
"unsupportedBrowser.titleUnsupportedBrowser": null,
"unsupportedBrowser.titlewebrtcUnavailable": null,
"unsupportedBrowser.bodyText": null
} }

View File

@ -49,6 +49,27 @@
"room.spotlights": "Συμμετέχοντες στο Spotlight", "room.spotlights": "Συμμετέχοντες στο Spotlight",
"room.passive": "Παθητικοί συμμετέχοντες", "room.passive": "Παθητικοί συμμετέχοντες",
"room.videoPaused": "Το βίντεο έχει σταματήσει", "room.videoPaused": "Το βίντεο έχει σταματήσει",
"room.muteAll": null,
"room.stopAllVideo": null,
"room.closeMeeting": null,
"room.clearChat": null,
"room.clearFileSharing": null,
"room.speechUnsupported": null,
"room.moderatoractions": null,
"room.raisedHand": null,
"room.loweredHand": null,
"room.extraVideo": null,
"room.overRoomLimit": null,
"room.help": null,
"room.about": null,
"room.shortcutKeys": null,
"room.browsePeersSpotlight": null,
"room.stopAllScreenSharing": null,
"me.mutedPTT": null,
"roles.gotRole": null,
"roles.lostRole": null,
"tooltip.login": "Σύνδεση", "tooltip.login": "Σύνδεση",
"tooltip.logout": "Αποσύνδεση", "tooltip.logout": "Αποσύνδεση",
@ -60,6 +81,14 @@
"tooltip.lobby": "Εμφάνιση λόμπι", "tooltip.lobby": "Εμφάνιση λόμπι",
"tooltip.settings": "Εμφάνιση ρυθμίσεων", "tooltip.settings": "Εμφάνιση ρυθμίσεων",
"tooltip.participants": "Εμφάνιση συμμετεχόντων", "tooltip.participants": "Εμφάνιση συμμετεχόντων",
"tooltip.kickParticipant": null,
"tooltip.muteParticipant": null,
"tooltip.muteParticipantVideo": null,
"tooltip.raisedHand": null,
"tooltip.muteScreenSharing": null,
"tooltip.muteParticipantAudioModerator": null,
"tooltip.muteParticipantVideoModerator": null,
"tooltip.muteScreenSharingModerator": null,
"label.roomName": "Όνομα δωματίου", "label.roomName": "Όνομα δωματίου",
"label.chooseRoomButton": "Συνέχεια", "label.chooseRoomButton": "Συνέχεια",
@ -73,16 +102,24 @@
"label.filesharing": "Διαμοιρασμοός αρχείου", "label.filesharing": "Διαμοιρασμοός αρχείου",
"label.participants": "Συμμετέχοντες", "label.participants": "Συμμετέχοντες",
"label.shareFile": "Διαμοιραστείτε ένα αρχείο", "label.shareFile": "Διαμοιραστείτε ένα αρχείο",
"label.shareGalleryFile": null,
"label.fileSharingUnsupported": "Ο διαμοιρασμός αρχείων δεν υποστηρίζεται", "label.fileSharingUnsupported": "Ο διαμοιρασμός αρχείων δεν υποστηρίζεται",
"label.unknown": "Άγνωστο", "label.unknown": "Άγνωστο",
"label.democratic": "Democratic view", "label.democratic": null,
"label.filmstrip": "Filmstrip view", "label.filmstrip": null,
"label.low": "Χαμηλή", "label.low": "Χαμηλή",
"label.medium": "Μέτρια", "label.medium": "Μέτρια",
"label.high": "Υψηλή (HD)", "label.high": "Υψηλή (HD)",
"label.veryHigh": "Πολύ υψηλή (FHD)", "label.veryHigh": "Πολύ υψηλή (FHD)",
"label.ultra": "Ultra (UHD)", "label.ultra": "Ultra (UHD)",
"label.close": "Κλείσιμο", "label.close": "Κλείσιμο",
"label.media": null,
"label.appearance": null,
"label.advanced": null,
"label.addVideo": null,
"label.promoteAllPeers": null,
"label.moreActions": null,
"label.version": null,
"settings.settings": "Ρυθμίσεις", "settings.settings": "Ρυθμίσεις",
"settings.camera": "Κάμερα", "settings.camera": "Κάμερα",
@ -91,12 +128,30 @@
"settings.audio": "Συσκευή ήχου", "settings.audio": "Συσκευή ήχου",
"settings.selectAudio": "Επιλογή συσκευής ήχου", "settings.selectAudio": "Επιλογή συσκευής ήχου",
"settings.cantSelectAudio": "Αδυναμία επιλογής συσκευής ήχου", "settings.cantSelectAudio": "Αδυναμία επιλογής συσκευής ήχου",
"settings.audioOutput": "Συσκευή εξόδου ήχου",
"settings.selectAudioOutput": "Επιλέξτε συσκευή εξόδου ήχου",
"settings.cantSelectAudioOutput": "Δεν είναι δυνατή η επιλογή συσκευής εξόδου ήχου",
"settings.resolution": "Επιλέξτε την ανάλυση του video", "settings.resolution": "Επιλέξτε την ανάλυση του video",
"settings.frameRate": null,
"settings.screenSharingResolution": null,
"settings.screenSharingFrameRate": null,
"settings.layout": "Περιβάλλον δωματίου", "settings.layout": "Περιβάλλον δωματίου",
"settings.selectRoomLayout": "Επιλογή περιβάλλοντος δωματίου", "settings.selectRoomLayout": "Επιλογή περιβάλλοντος δωματίου",
"settings.advancedMode": "Προηγμένη λειτουργία", "settings.advancedMode": "Προηγμένη λειτουργία",
"settings.permanentTopBar": "Μόνιμη μπάρα κορυφής", "settings.permanentTopBar": "Μόνιμη μπάρα κορυφής",
"settings.lastn": "Αριθμός ορατών βίντεο", "settings.lastn": "Αριθμός ορατών βίντεο",
"settings.hiddenControls": null,
"settings.notificationSounds": null,
"settings.showNotifications": null,
"settings.buttonControlBar": null,
"settings.showAdvancedVideo": null,
"settings.showAdvancedAudio": null,
"settings.echoCancellation": null,
"settings.autoGainControl": null,
"settings.noiseSuppression": null,
"settings.drawerOverlayed": null,
"settings.voiceActivatedUnmute": null,
"settings.noiseThreshold": null,
"filesharing.saveFileError": "Αδυναμία αποθήκευσης του αρχείου", "filesharing.saveFileError": "Αδυναμία αποθήκευσης του αρχείου",
"filesharing.startingFileShare": "Προσπάθεια διαμοιρασμού αρχείου", "filesharing.startingFileShare": "Προσπάθεια διαμοιρασμού αρχείου",
@ -126,8 +181,8 @@
"devices.microphoneDisconnected": "Το μικρόφωνο αποσυνδέθηκε", "devices.microphoneDisconnected": "Το μικρόφωνο αποσυνδέθηκε",
"devices.microphoneError": "Παρουσιάστηκε σφάλμα κατά την πρόσβαση στο μικρόφωνό σας", "devices.microphoneError": "Παρουσιάστηκε σφάλμα κατά την πρόσβαση στο μικρόφωνό σας",
"devices.microPhoneMute": "Το μικρόφωνό σας είναι σε σίγαση", "devices.microphoneMute": "Το μικρόφωνό σας είναι σε σίγαση",
"devices.micophoneUnMute": "Ανοίξτε το μικρόφωνό σας", "devices.microphoneUnMute": "Ανοίξτε το μικρόφωνό σας",
"devices.microphoneEnable": "Ενεργοποίησε το μικρόφωνό σας", "devices.microphoneEnable": "Ενεργοποίησε το μικρόφωνό σας",
"devices.microphoneMuteError": "Δεν είναι δυνατή η σίγαση του μικροφώνου σας", "devices.microphoneMuteError": "Δεν είναι δυνατή η σίγαση του μικροφώνου σας",
"devices.microphoneUnMuteError": "Δεν είναι δυνατό το άνοιγμα του μικροφώνου σας", "devices.microphoneUnMuteError": "Δεν είναι δυνατό το άνοιγμα του μικροφώνου σας",
@ -136,5 +191,15 @@
"devices.screenSharingError": "Παρουσιάστηκε σφάλμα κατά την πρόσβαση στην οθόνη σας", "devices.screenSharingError": "Παρουσιάστηκε σφάλμα κατά την πρόσβαση στην οθόνη σας",
"devices.cameraDisconnected": "Η κάμερα αποσυνδέθηκε", "devices.cameraDisconnected": "Η κάμερα αποσυνδέθηκε",
"devices.cameraError": "Παρουσιάστηκε σφάλμα κατά την πρόσβαση στην κάμερά σας" "devices.cameraError": "Παρουσιάστηκε σφάλμα κατά την πρόσβαση στην κάμερά σας",
"moderator.clearChat": null,
"moderator.clearFiles": null,
"moderator.muteAudio": null,
"moderator.muteVideo": null,
"moderator.stopScreenSharing": null,
"unsupportedBrowser.titleUnsupportedBrowser": null,
"unsupportedBrowser.titlewebrtcUnavailable": null,
"unsupportedBrowser.bodyText": null
} }

View File

@ -49,6 +49,27 @@
"room.spotlights": "Participants in Spotlight", "room.spotlights": "Participants in Spotlight",
"room.passive": "Passive Participants", "room.passive": "Passive Participants",
"room.videoPaused": "This video is paused", "room.videoPaused": "This video is paused",
"room.muteAll": "Mute all",
"room.stopAllVideo": "Stop all video",
"room.closeMeeting": "Close meeting",
"room.clearChat": "Clear chat",
"room.clearFileSharing": "Clear files",
"room.speechUnsupported": "Your browser does not support speech recognition",
"room.moderatoractions": "Moderator actions",
"room.raisedHand": "{displayName} raised their hand",
"room.loweredHand": "{displayName} put their hand down",
"room.extraVideo": "Extra video",
"room.overRoomLimit": "The room is full, retry after some time.",
"room.help": "Help",
"room.about": "About",
"room.shortcutKeys": "Shortcut Keys",
"room.browsePeersSpotlight": "Browse participants into Spotlight",
"room.stopAllScreenSharing": "Stop all screen sharing",
"me.mutedPTT": "You are muted, hold down SPACE-BAR to talk",
"roles.gotRole": "You got the role: {role}",
"roles.lostRole": "You lost the role: {role}",
"tooltip.login": "Log in", "tooltip.login": "Log in",
"tooltip.logout": "Log out", "tooltip.logout": "Log out",
@ -60,6 +81,14 @@
"tooltip.lobby": "Show lobby", "tooltip.lobby": "Show lobby",
"tooltip.settings": "Show settings", "tooltip.settings": "Show settings",
"tooltip.participants": "Show participants", "tooltip.participants": "Show participants",
"tooltip.kickParticipant": "Kick out participant",
"tooltip.muteParticipant": "Mute participant",
"tooltip.muteParticipantVideo": "Mute participant video",
"tooltip.raisedHand": "Raise hand",
"tooltip.muteScreenSharing": "Mute participant share",
"tooltip.muteParticipantAudioModerator": "Mute participant audio globally",
"tooltip.muteParticipantVideoModerator": "Mute participant video globally",
"tooltip.muteScreenSharingModerator": "Mute participant screen share globally",
"label.roomName": "Room name", "label.roomName": "Room name",
"label.chooseRoomButton": "Continue", "label.chooseRoomButton": "Continue",
@ -73,6 +102,7 @@
"label.filesharing": "File sharing", "label.filesharing": "File sharing",
"label.participants": "Participants", "label.participants": "Participants",
"label.shareFile": "Share file", "label.shareFile": "Share file",
"label.shareGalleryFile": "Share image",
"label.fileSharingUnsupported": "File sharing not supported", "label.fileSharingUnsupported": "File sharing not supported",
"label.unknown": "Unknown", "label.unknown": "Unknown",
"label.democratic": "Democratic view", "label.democratic": "Democratic view",
@ -83,6 +113,13 @@
"label.veryHigh": "Very high (FHD)", "label.veryHigh": "Very high (FHD)",
"label.ultra": "Ultra (UHD)", "label.ultra": "Ultra (UHD)",
"label.close": "Close", "label.close": "Close",
"label.media": "Media",
"label.appearance": "Appearence",
"label.advanced": "Advanced",
"label.addVideo": "Add video",
"label.promoteAllPeers": "Promote all",
"label.moreActions": "More actions",
"label.version": "Version",
"settings.settings": "Settings", "settings.settings": "Settings",
"settings.camera": "Camera", "settings.camera": "Camera",
@ -91,12 +128,30 @@
"settings.audio": "Audio device", "settings.audio": "Audio device",
"settings.selectAudio": "Select audio device", "settings.selectAudio": "Select audio device",
"settings.cantSelectAudio": "Unable to select audio device", "settings.cantSelectAudio": "Unable to select audio device",
"settings.audioOutput": "Audio output device",
"settings.selectAudioOutput": "Select audio output device",
"settings.cantSelectAudioOutput": "Unable to select audio output device",
"settings.resolution": "Select your video resolution", "settings.resolution": "Select your video resolution",
"settings.frameRate": "Select your video frame rate",
"settings.screenSharingResolution": "Select your screen sharing resolution",
"settings.screenSharingFrameRate": "Select your screen sharing frame rate",
"settings.layout": "Room layout", "settings.layout": "Room layout",
"settings.selectRoomLayout": "Select room layout", "settings.selectRoomLayout": "Select room layout",
"settings.advancedMode": "Advanced mode", "settings.advancedMode": "Advanced mode",
"settings.permanentTopBar": "Permanent top bar", "settings.permanentTopBar": "Permanent top bar",
"settings.lastn": "Number of visible videos", "settings.lastn": "Number of visible videos",
"settings.hiddenControls": "Hidden media controls",
"settings.notificationSounds": "Notification sounds",
"settings.showNotifications": "Show notifications",
"settings.buttonControlBar": "Separate media controls",
"settings.showAdvancedVideo": "Advanced video settings",
"settings.showAdvancedAudio": "Advanced audio settings",
"settings.echoCancellation": "Echo cancellation",
"settings.autoGainControl": "Auto gain control",
"settings.noiseSuppression": "Noise suppression",
"settings.drawerOverlayed": "Side drawer over content",
"settings.voiceActivatedUnmute": "Voice activated unmute",
"settings.noiseThreshold": "Noise threshold",
"filesharing.saveFileError": "Unable to save file", "filesharing.saveFileError": "Unable to save file",
"filesharing.startingFileShare": "Attempting to share file", "filesharing.startingFileShare": "Attempting to share file",
@ -125,9 +180,9 @@
"device.stopScreenSharing": "Stop screen sharing", "device.stopScreenSharing": "Stop screen sharing",
"devices.microphoneDisconnected": "Microphone disconnected", "devices.microphoneDisconnected": "Microphone disconnected",
"devices.microphoneError": "An error occurred while accessing your microphone", "devices.microphoneError": "An error occured while accessing your microphone",
"devices.microPhoneMute": "Muted your microphone", "devices.microphoneMute": "Muted your microphone",
"devices.micophoneUnMute": "Unmuted your microphone", "devices.microphoneUnMute": "Unmuted your microphone",
"devices.microphoneEnable": "Enabled your microphone", "devices.microphoneEnable": "Enabled your microphone",
"devices.microphoneMuteError": "Unable to mute your microphone", "devices.microphoneMuteError": "Unable to mute your microphone",
"devices.microphoneUnMuteError": "Unable to unmute your microphone", "devices.microphoneUnMuteError": "Unable to unmute your microphone",
@ -136,5 +191,15 @@
"devices.screenSharingError": "An error occurred while accessing your screen", "devices.screenSharingError": "An error occurred while accessing your screen",
"devices.cameraDisconnected": "Camera disconnected", "devices.cameraDisconnected": "Camera disconnected",
"devices.cameraError": "An error occurred while accessing your camera" "devices.cameraError": "An error occured while accessing your camera",
"moderator.clearChat": "Moderator cleared the chat",
"moderator.clearFiles": "Moderator cleared the files",
"moderator.muteAudio": "Moderator muted your audio",
"moderator.muteVideo": "Moderator muted your video",
"moderator.stopScreenSharing": "Moderator stopped your screen sharing",
"unsupportedBrowser.titleUsnsupportedBrowser": "Detected unsupported browser!",
"unsupportedBrowser.titlewebrtcUnavailable": "Required functionality not available in your browser!",
"unsupportedBrowser.bodyText": "This meeting service requires a functionality that is not supported by your browser. Please upgrade, or switch to a different browser, or check your settings. Supported browsers:"
} }

View File

@ -49,6 +49,27 @@
"room.spotlights": "Participantes destacados", "room.spotlights": "Participantes destacados",
"room.passive": "Participantes pasivos", "room.passive": "Participantes pasivos",
"room.videoPaused": "El vídeo está pausado", "room.videoPaused": "El vídeo está pausado",
"room.muteAll": null,
"room.stopAllVideo": null,
"room.closeMeeting": null,
"room.clearChat": null,
"room.clearFileSharing": null,
"room.speechUnsupported": null,
"room.moderatoractions": null,
"room.raisedHand": null,
"room.loweredHand": null,
"room.extraVideo": null,
"room.overRoomLimit": null,
"room.help": null,
"room.about": null,
"room.shortcutKeys": null,
"room.browsePeersSpotlight": null,
"room.stopAllScreenSharing": null,
"me.mutedPTT": null,
"roles.gotRole": null,
"roles.lostRole": null,
"tooltip.login": "Entrar", "tooltip.login": "Entrar",
"tooltip.logout": "Salir", "tooltip.logout": "Salir",
@ -60,6 +81,14 @@
"tooltip.lobby": "Mostrar sala de espera", "tooltip.lobby": "Mostrar sala de espera",
"tooltip.settings": "Mostrar ajustes", "tooltip.settings": "Mostrar ajustes",
"tooltip.participants": "Mostrar participantes", "tooltip.participants": "Mostrar participantes",
"tooltip.kickParticipant": null,
"tooltip.muteParticipant": null,
"tooltip.muteParticipantVideo": null,
"tooltip.raisedHand": null,
"tooltip.muteScreenSharing": null,
"tooltip.muteParticipantAudioModerator": null,
"tooltip.muteParticipantVideoModerator": null,
"tooltip.muteScreenSharingModerator": null,
"label.roomName": "Nombre de la sala", "label.roomName": "Nombre de la sala",
"label.chooseRoomButton": "Continuar", "label.chooseRoomButton": "Continuar",
@ -73,6 +102,7 @@
"label.filesharing": "Compartir ficheros", "label.filesharing": "Compartir ficheros",
"label.participants": "Participantes", "label.participants": "Participantes",
"label.shareFile": "Compartir fichero", "label.shareFile": "Compartir fichero",
"label.shareGalleryFile": null,
"label.fileSharingUnsupported": "Compartir ficheros no está disponible", "label.fileSharingUnsupported": "Compartir ficheros no está disponible",
"label.unknown": "Desconocido", "label.unknown": "Desconocido",
"label.democratic": "Vista democrática", "label.democratic": "Vista democrática",
@ -83,6 +113,13 @@
"label.veryHigh": "Muy alta (FHD)", "label.veryHigh": "Muy alta (FHD)",
"label.ultra": "Ultra (UHD)", "label.ultra": "Ultra (UHD)",
"label.close": "Cerrar", "label.close": "Cerrar",
"label.media": null,
"label.appearance": null,
"label.advanced": null,
"label.addVideo": null,
"label.promoteAllPeers": null,
"label.moreActions": null,
"label.version": null,
"settings.settings": "Ajustes", "settings.settings": "Ajustes",
"settings.camera": "Cámara", "settings.camera": "Cámara",
@ -91,12 +128,30 @@
"settings.audio": "Dispositivo de sonido", "settings.audio": "Dispositivo de sonido",
"settings.selectAudio": "Seleccione dispositivo de sonido", "settings.selectAudio": "Seleccione dispositivo de sonido",
"settings.cantSelectAudio": "No ha sido posible seleccionar el dispositivo de sonido", "settings.cantSelectAudio": "No ha sido posible seleccionar el dispositivo de sonido",
"settings.audioOutput": "Dispositivo de salida de audio",
"settings.selectAudioOutput": "Seleccionar dispositivo de salida de audio",
"settings.cantSelectAudioOutput": "No se puede seleccionar el dispositivo de salida de audio",
"settings.resolution": "Seleccione su resolución de imagen", "settings.resolution": "Seleccione su resolución de imagen",
"settings.frameRate": null,
"settings.screenSharingResolution": null,
"settings.screenSharingFrameRate": null,
"settings.layout": "Disposición de la sala", "settings.layout": "Disposición de la sala",
"settings.selectRoomLayout": "Seleccione la disposición de la sala", "settings.selectRoomLayout": "Seleccione la disposición de la sala",
"settings.advancedMode": "Modo avanzado", "settings.advancedMode": "Modo avanzado",
"settings.permanentTopBar": "Barra superior permanente", "settings.permanentTopBar": "Barra superior permanente",
"settings.lastn": "Cantidad de videos visibles", "settings.lastn": "Cantidad de videos visibles",
"settings.hiddenControls": null,
"settings.notificationSounds": null,
"settings.showNotifications": null,
"settings.buttonControlBar": null,
"settings.showAdvancedVideo": null,
"settings.showAdvancedAudio": null,
"settings.echoCancellation": null,
"settings.autoGainControl": null,
"settings.noiseSuppression": null,
"settings.drawerOverlayed": null,
"settings.voiceActivatedUnmute": null,
"settings.noiseThreshold": null,
"filesharing.saveFileError": "No ha sido posible guardar el fichero", "filesharing.saveFileError": "No ha sido posible guardar el fichero",
"filesharing.startingFileShare": "Intentando compartir el fichero", "filesharing.startingFileShare": "Intentando compartir el fichero",
@ -126,8 +181,8 @@
"devices.microphoneDisconnected": "Micrófono desconectado", "devices.microphoneDisconnected": "Micrófono desconectado",
"devices.microphoneError": "Hubo un error al acceder a su micrófono", "devices.microphoneError": "Hubo un error al acceder a su micrófono",
"devices.microPhoneMute": "Desactivar micrófono", "devices.microphoneMute": "Desactivar micrófono",
"devices.micophoneUnMute": "Activar micrófono", "devices.microphoneUnMute": "Activar micrófono",
"devices.microphoneEnable": "Micrófono activado", "devices.microphoneEnable": "Micrófono activado",
"devices.microphoneMuteError": "No ha sido posible desactivar su micrófono", "devices.microphoneMuteError": "No ha sido posible desactivar su micrófono",
"devices.microphoneUnMuteError": "No ha sido posible activar su micrófono", "devices.microphoneUnMuteError": "No ha sido posible activar su micrófono",
@ -136,5 +191,15 @@
"devices.screenSharingError": "Hubo un error al acceder a su pantalla", "devices.screenSharingError": "Hubo un error al acceder a su pantalla",
"devices.cameraDisconnected": "Cámara desconectada", "devices.cameraDisconnected": "Cámara desconectada",
"devices.cameraError": "Hubo un error al acceder a su cámara" "devices.cameraError": "Hubo un error al acceder a su cámara",
"moderator.clearChat": null,
"moderator.clearFiles": null,
"moderator.muteAudio": null,
"moderator.muteVideo": null,
"moderator.stopScreenSharing": null,
"unsupportedBrowser.titleUnsupportedBrowser": null,
"unsupportedBrowser.titlewebrtcUnavailable": null,
"unsupportedBrowser.bodyText": null
} }

View File

@ -11,7 +11,7 @@
"room.cantJoin": "Impossible de rejoindre la salle", "room.cantJoin": "Impossible de rejoindre la salle",
"room.youLocked": "Vous avez privatisé la salle", "room.youLocked": "Vous avez privatisé la salle",
"room.cantLock": "Impossible de privatiser la salle", "room.cantLock": "Impossible de privatiser la salle",
"room.youUnLocked" : " Vous avez dé-privatiser la salle", "room.youUnLocked": "Vous avez dé-privatisé la salle",
"room.cantUnLock": "Impossible de dé-privatiser la réunion", "room.cantUnLock": "Impossible de dé-privatiser la réunion",
"room.locked": "La réunion est privée", "room.locked": "La réunion est privée",
"room.unlocked": "La réunion est publique", "room.unlocked": "La réunion est publique",
@ -20,8 +20,8 @@
"room.lobbyPeerChangedDisplayName": "Un participant dans la salle dattente a changé de nom pour {displayName}", "room.lobbyPeerChangedDisplayName": "Un participant dans la salle dattente a changé de nom pour {displayName}",
"room.lobbyPeerChangedPicture": "Un participant dans le hall à changer de photo", "room.lobbyPeerChangedPicture": "Un participant dans le hall à changer de photo",
"room.setAccessCode": "Code daccès à la réunion mis à jour", "room.setAccessCode": "Code daccès à la réunion mis à jour",
"room.accessCodeOn" : " Code daccès à la réunion activée", "room.accessCodeOn": "Code daccès à la réunion activé",
"room.accessCodeOff" : " Code daccès à la réunion désactivée", "room.accessCodeOff": "Code daccès à la réunion désactivé",
"room.peerChangedDisplayName": "{oldDisplayName} est maintenant {displayName}", "room.peerChangedDisplayName": "{oldDisplayName} est maintenant {displayName}",
"room.newPeer": "{displayName} a rejoint la salle", "room.newPeer": "{displayName} a rejoint la salle",
"room.newFile": "Nouveau fichier disponible", "room.newFile": "Nouveau fichier disponible",
@ -30,7 +30,7 @@
"room.setFilmStripView": "Passer en vue vignette", "room.setFilmStripView": "Passer en vue vignette",
"room.loggedIn": "Vous êtes connecté", "room.loggedIn": "Vous êtes connecté",
"room.loggedOut": "Vous êtes déconnecté", "room.loggedOut": "Vous êtes déconnecté",
"room.changedDisplayName" : " Votre nom à changer pour {displayname}", "room.changedDisplayName": "Votre nom a changé pour {displayname}",
"room.changeDisplayNameError": "Une erreur sest produite pour votre changement de nom", "room.changeDisplayNameError": "Une erreur sest produite pour votre changement de nom",
"room.chatError": "Impossible denvoyer un message", "room.chatError": "Impossible denvoyer un message",
"room.aboutToJoin": "Vous allez rejoindre une réunion", "room.aboutToJoin": "Vous allez rejoindre une réunion",
@ -39,7 +39,7 @@
"room.audioOnly": "Audio uniquement", "room.audioOnly": "Audio uniquement",
"room.audioVideo": "Audio et Vidéo", "room.audioVideo": "Audio et Vidéo",
"room.youAreReady": "Ok, vous êtes prêt", "room.youAreReady": "Ok, vous êtes prêt",
"room.emptyRequireLogin" : " La réunion est vide ! Vous pouvez vous connecter pour commencer la réunion ou attendre qu'un hôte se connecte", "room.emptyRequireLogin": "La réunion est vide ! Vous pouvez vous connecter pour commencer la réunion ou attendre qu'un hôte se connecte",
"room.locketWait": "La réunion est privatisée - attendez que quelquun vous laisse entrer", "room.locketWait": "La réunion est privatisée - attendez que quelquun vous laisse entrer",
"room.lobbyAdministration": "Administration de la salle dattente", "room.lobbyAdministration": "Administration de la salle dattente",
"room.peersInLobby": "Participants dans la salle dattente", "room.peersInLobby": "Participants dans la salle dattente",
@ -49,6 +49,27 @@
"room.spotlights": "Participants actifs", "room.spotlights": "Participants actifs",
"room.passive": "Participants passifs", "room.passive": "Participants passifs",
"room.videoPaused": "La vidéo est en pause", "room.videoPaused": "La vidéo est en pause",
"room.muteAll": null,
"room.stopAllVideo": null,
"room.closeMeeting": null,
"room.clearChat": null,
"room.clearFileSharing": null,
"room.speechUnsupported": null,
"room.moderatoractions": null,
"room.raisedHand": null,
"room.loweredHand": null,
"room.extraVideo": null,
"room.overRoomLimit": null,
"room.help": null,
"room.about": null,
"room.shortcutKeys": null,
"room.browsePeersSpotlight": null,
"room.stopAllScreenSharing": null,
"me.mutedPTT": null,
"roles.gotRole": null,
"roles.lostRole": null,
"tooltip.login": "Connexion", "tooltip.login": "Connexion",
"tooltip.logout": "Déconnexion", "tooltip.logout": "Déconnexion",
@ -60,6 +81,14 @@
"tooltip.lobby": "Afficher la salle d'attente", "tooltip.lobby": "Afficher la salle d'attente",
"tooltip.settings": "Afficher les paramètres", "tooltip.settings": "Afficher les paramètres",
"tooltip.participants": "Afficher les participants", "tooltip.participants": "Afficher les participants",
"tooltip.kickParticipant": null,
"tooltip.muteParticipant": null,
"tooltip.muteParticipantVideo": null,
"tooltip.raisedHand": null,
"tooltip.muteScreenSharing": null,
"tooltip.muteParticipantAudioModerator": null,
"tooltip.muteParticipantVideoModerator": null,
"tooltip.muteScreenSharingModerator": null,
"label.roomName": "Nom de la salle", "label.roomName": "Nom de la salle",
"label.chooseRoomButton": "Continuer", "label.chooseRoomButton": "Continuer",
@ -67,12 +96,13 @@
"label.newWindow": "Nouvelle fenêtre", "label.newWindow": "Nouvelle fenêtre",
"label.fullscreen": "Plein écran", "label.fullscreen": "Plein écran",
"label.openDrawer": "Ouvrir Drawer", "label.openDrawer": "Ouvrir Drawer",
"label.leave" : " Quiter", "label.leave": "Quitter",
"label.chatInput": "Entrer un message", "label.chatInput": "Entrer un message",
"label.chat": "Chat", "label.chat": "Chat",
"label.filesharing": "Partage de fichier", "label.filesharing": "Partage de fichier",
"label.participants": "Participants", "label.participants": "Participants",
"label.shareFile": "Partager un fichier", "label.shareFile": "Partager un fichier",
"label.shareGalleryFile": null,
"label.fileSharingUnsupported": "Partage de fichier non supporté", "label.fileSharingUnsupported": "Partage de fichier non supporté",
"label.unknown": "Inconnu", "label.unknown": "Inconnu",
"label.democratic": "Vue démocratique", "label.democratic": "Vue démocratique",
@ -83,20 +113,45 @@
"label.veryHigh": "Très Haute Définition (FHD)", "label.veryHigh": "Très Haute Définition (FHD)",
"label.ultra": "Ultra Haute Définition", "label.ultra": "Ultra Haute Définition",
"label.close": "Fermer", "label.close": "Fermer",
"label.media": null,
"label.appearance": null,
"label.advanced": null,
"label.addVideo": null,
"label.promoteAllPeers": null,
"label.moreActions": null,
"label.version": null,
"settings.settings": "Paramètres", "settings.settings": "Paramètres",
"settings.camera": "Caméra", "settings.camera": "Caméra",
"settings.selectCamera" : " Sélectionner votre caméra", "settings.selectCamera": "Sélectionnez votre caméra",
"settings.cantSelectCamera": "Impossible de sélectionner votre caméra", "settings.cantSelectCamera": "Impossible de sélectionner votre caméra",
"settings.audio": "Microphone", "settings.audio": "Microphone",
"settings.selectAudio" : " Sélectionner votre microphone", "settings.selectAudio": "Sélectionnez votre microphone",
"settings.cantSelectAudio": "Impossible de sélectionner votre la caméra", "settings.cantSelectAudio": "Impossible de sélectionner votre la caméra",
"settings.resolution" : " Sélection votre résolution", "settings.audioOutput": "Périphérique de sortie audio",
"settings.selectAudioOutput": "Sélectionnez le périphérique de sortie audio",
"settings.cantSelectAudioOutput": "Impossible de sélectionner le périphérique de sortie audio",
"settings.resolution": "Sélectionnez votre résolution",
"settings.frameRate": null,
"settings.screenSharingResolution": null,
"settings.screenSharingFrameRate": null,
"settings.layout": "Mode d'affichage de la salle", "settings.layout": "Mode d'affichage de la salle",
"settings.selectRoomLayout" : " Sélectionner l'affiche de la salle", "settings.selectRoomLayout": "Sélectionnez la présentation de la salle",
"settings.advancedMode": "Mode avancé", "settings.advancedMode": "Mode avancé",
"settings.permanentTopBar": "Barre supérieure permanente", "settings.permanentTopBar": "Barre supérieure permanente",
"settings.lastn": "Nombre de vidéos visibles", "settings.lastn": "Nombre de vidéos visibles",
"settings.hiddenControls": null,
"settings.notificationSounds": null,
"settings.showNotifications": null,
"settings.buttonControlBar": null,
"settings.showAdvancedVideo": null,
"settings.showAdvancedAudio": null,
"settings.echoCancellation": null,
"settings.autoGainControl": null,
"settings.noiseSuppression": null,
"settings.drawerOverlayed": null,
"settings.voiceActivatedUnmute": null,
"settings.noiseThreshold": null,
"filesharing.saveFileError": "Impossible d'enregistrer le fichier", "filesharing.saveFileError": "Impossible d'enregistrer le fichier",
"filesharing.startingFileShare": "Début du transfert de fichier", "filesharing.startingFileShare": "Début du transfert de fichier",
@ -108,14 +163,14 @@
"filesharing.sharedFile": "{displayName} a partagé un fichier", "filesharing.sharedFile": "{displayName} a partagé un fichier",
"filesharing.download": "Télécharger", "filesharing.download": "Télécharger",
"filesharing.missingSeeds": "Si le téléchargement prend trop de temps cest quil ny a peut-être plus personne qui partage ce torrent. Demander à quelquun de repartager le document.", "filesharing.missingSeeds": "Si le téléchargement prend trop de temps cest quil ny a peut-être plus personne qui partage ce torrent. Demander à quelquun de repartager le document.",
"devices.devicesChanged" : " Vos périphériques ont changé, reconfigurer vos périphériques avec le menu paramètre", "devices.devicesChanged": "Vos périphériques ont changé, reconfigurez vos périphériques dans le menu paramètre",
"device.audioUnsupported": "Microphone non supporté", "device.audioUnsupported": "Microphone non supporté",
"device.activateAudio": "Activer l'audio", "device.activateAudio": "Activer l'audio",
"device.muteAudio": "Désactiver l'audio", "device.muteAudio": "Désactiver l'audio",
"device.unMuteAudio": "Réactiver l'audio", "device.unMuteAudio": "Réactiver l'audio",
"device.videoUnsupported" : " Vidéo non supporté", "device.videoUnsupported": "Vidéo non supportée",
"device.startVideo": "Démarrer la vidéo", "device.startVideo": "Démarrer la vidéo",
"device.stopVideo": "Arrêter la vidéo", "device.stopVideo": "Arrêter la vidéo",
@ -125,8 +180,8 @@
"devices.microphoneDisconnected": "Microphone déconnecté", "devices.microphoneDisconnected": "Microphone déconnecté",
"devices.microphoneError": "Une erreur est apparue lors de l'accès à votre microphone", "devices.microphoneError": "Une erreur est apparue lors de l'accès à votre microphone",
"devices.microPhoneMute" : " Désactiver le microphone", "devices.microphoneMute": "Désactiver le microphone",
"devices.micophoneUnMute" : " Réactiver le microphone", "devices.microphoneUnMute": "Réactiver le microphone",
"devices.microphoneEnable": "Activer le microphone", "devices.microphoneEnable": "Activer le microphone",
"devices.microphoneMuteError": "Impossible de désactiver le microphone", "devices.microphoneMuteError": "Impossible de désactiver le microphone",
"devices.microphoneUnMuteError": "Impossible de réactiver le microphone", "devices.microphoneUnMuteError": "Impossible de réactiver le microphone",
@ -135,5 +190,15 @@
"devices.screenSharingError": "Une erreur est apparue lors de l'accès à votre partage d'écran", "devices.screenSharingError": "Une erreur est apparue lors de l'accès à votre partage d'écran",
"devices.cameraDisconnected": "Caméra déconnectée", "devices.cameraDisconnected": "Caméra déconnectée",
"devices.cameraError" : " Une erreur est apparue lors de l'accès à votre caméra" "devices.cameraError": "Une erreur est apparue lors de l'accès à votre caméra",
"moderator.clearChat": null,
"moderator.clearFiles": null,
"moderator.muteAudio": null,
"moderator.muteVideo": null,
"moderator.stopScreenSharing": null,
"unsupportedBrowser.titleUnsupportedBrowser": null,
"unsupportedBrowser.titlewebrtcUnavailable": null,
"unsupportedBrowser.bodyText": null
} }

View File

@ -46,9 +46,30 @@
"room.lobbyEmpty": "Trenutno nema nikoga u predvorju", "room.lobbyEmpty": "Trenutno nema nikoga u predvorju",
"room.hiddenPeers": "{hiddenPeersCount, plural, one {participant} other {participants}}", "room.hiddenPeers": "{hiddenPeersCount, plural, one {participant} other {participants}}",
"room.me": "Ja", "room.me": "Ja",
"room.spotlights": "Sudionici u fokusu", "room.spotlights": "Sudionici u središtu pažnje",
"room.passive": "Pasivni sudionici", "room.passive": "Pasivni sudionici",
"room.videoPaused": "Video pauziran", "room.videoPaused": "Video pauziran",
"room.muteAll": "Utišaj sve",
"room.stopAllVideo": "Ugasi sve kamere",
"room.closeMeeting": "Završi sastanak",
"room.clearChat": "Izbriši razgovor",
"room.clearFileSharing": "Izbriši dijeljene datoteke",
"room.speechUnsupported": "Vaš preglednik ne podržava prepoznavanje govora",
"room.moderatoractions": "Akcije moderatora",
"room.raisedHand": "{displayName} je podigao ruku",
"room.loweredHand": "{displayName} je spustio ruku",
"room.extraVideo": "Dodatni video",
"room.overRoomLimit": "Soba je popunjena, pokušajte ponovno kasnije.",
"room.help": "Pomoć",
"room.about": "O programu",
"room.shortcutKeys": "Prečaci",
"room.browsePeersSpotlight": "Pretražite sudionike u središtu pažnje",
"room.stopAllScreenSharing": null,
"me.mutedPTT": "Utišani ste, pritisnite i držite SPACE tipku za razgovor",
"roles.gotRole": "Dodijeljena vam je uloga: {role}",
"roles.lostRole": "Uloga: {role} je povučena",
"tooltip.login": "Prijava", "tooltip.login": "Prijava",
"tooltip.logout": "Odjava", "tooltip.logout": "Odjava",
@ -59,7 +80,15 @@
"tooltip.leaveFullscreen": "Izađi iz punog ekrana", "tooltip.leaveFullscreen": "Izađi iz punog ekrana",
"tooltip.lobby": "Prikaži predvorje", "tooltip.lobby": "Prikaži predvorje",
"tooltip.settings": "Prikaži postavke", "tooltip.settings": "Prikaži postavke",
"tooltip.participants": "Pokažite sudionike", "tooltip.participants": "Prikaži sudionike",
"tooltip.kickParticipant": "Izbaci sudionika",
"tooltip.muteParticipant": "Utišaj sudionika",
"tooltip.muteParticipantVideo": "Ne prikazuj video sudionika",
"tooltip.raisedHand": "Podigni ruku",
"tooltip.muteScreenSharing": "Ne prikazuj dijeljenje ekrana sudionika",
"tooltip.muteParticipantAudioModerator": "Utišaj sve sudionike",
"tooltip.muteParticipantVideoModerator": "Ne prikazuj video svih sudionika",
"tooltip.muteScreenSharingModerator": "Ne prikazuj dijeljenje ekrana svih sudionika",
"label.roomName": "Naziv sobe", "label.roomName": "Naziv sobe",
"label.chooseRoomButton": "Nastavi", "label.chooseRoomButton": "Nastavi",
@ -73,6 +102,7 @@
"label.filesharing": "Dijeljenje datoteka", "label.filesharing": "Dijeljenje datoteka",
"label.participants": "Sudionici", "label.participants": "Sudionici",
"label.shareFile": "Dijeli datoteku", "label.shareFile": "Dijeli datoteku",
"label.shareGalleryFile": "Dijeli sliku",
"label.fileSharingUnsupported": "Dijeljenje datoteka nije podržano", "label.fileSharingUnsupported": "Dijeljenje datoteka nije podržano",
"label.unknown": "Nepoznato", "label.unknown": "Nepoznato",
"label.democratic":"Demokratski prikaz", "label.democratic":"Demokratski prikaz",
@ -83,6 +113,13 @@
"label.veryHigh": "Vrlo visoka (FHD)", "label.veryHigh": "Vrlo visoka (FHD)",
"label.ultra": "Ultra visoka (UHD)", "label.ultra": "Ultra visoka (UHD)",
"label.close": "Zatvori", "label.close": "Zatvori",
"label.media": "Medij",
"label.appearance": "Prikaz",
"label.advanced": "Napredno",
"label.addVideo": "Dodaj video",
"label.promoteAllPeers": "Promoviraj sve",
"label.moreActions": "Više akcija",
"label.version": null,
"settings.settings": "Postavke", "settings.settings": "Postavke",
"settings.camera": "Kamera", "settings.camera": "Kamera",
@ -91,12 +128,30 @@
"settings.audio": "Uređaj za zvuk", "settings.audio": "Uređaj za zvuk",
"settings.selectAudio": "Odaberi uređaj za zvuk", "settings.selectAudio": "Odaberi uređaj za zvuk",
"settings.cantSelectAudio": "Nije moguće odabrati uređaj za zvuk", "settings.cantSelectAudio": "Nije moguće odabrati uređaj za zvuk",
"settings.audioOutput": "Uređaj za izlaz zvuka",
"settings.selectAudioOutput": "Odaberite izlazni uređaj za zvuk",
"settings.cantSelectAudioOutput": "Nije moguće odabrati izlazni uređaj za zvuk",
"settings.resolution": "Odaberi video rezoluciju", "settings.resolution": "Odaberi video rezoluciju",
"settings.frameRate": null,
"settings.screenSharingResolution": null,
"settings.screenSharingFrameRate": null,
"settings.layout": "Način prikaza", "settings.layout": "Način prikaza",
"settings.selectRoomLayout": "Odaberi način prikaza", "settings.selectRoomLayout": "Odaberi način prikaza",
"settings.advancedMode": "Napredne mogućnosti", "settings.advancedMode": "Napredne mogućnosti",
"settings.permanentTopBar": "Stalna gornja šipka", "settings.permanentTopBar": "Stalna gornja šipka",
"settings.lastn": "Broj vidljivih videozapisa", "settings.lastn": "Broj vidljivih videozapisa",
"settings.hiddenControls": "Skrivene kontrole medija",
"settings.notificationSounds": "Zvuk obavijesti",
"settings.showNotifications": "Prikaži obavijesti",
"settings.buttonControlBar": "Razdvoji upravljanje medijima",
"settings.showAdvancedVideo": null,
"settings.showAdvancedAudio": null,
"settings.echoCancellation": "Poništavanje jeke",
"settings.autoGainControl": "Automatsko upravljanje jačinom zvuka",
"settings.noiseSuppression": "Poništavanje šuma",
"settings.drawerOverlayed": "Bočni izbornik iznad sadržaja",
"settings.voiceActivatedUnmute": null,
"settings.noiseThreshold": null,
"filesharing.saveFileError": "Nije moguće spremiti datoteku", "filesharing.saveFileError": "Nije moguće spremiti datoteku",
"filesharing.startingFileShare": "Pokušaj dijeljenja datoteke", "filesharing.startingFileShare": "Pokušaj dijeljenja datoteke",
@ -126,8 +181,8 @@
"devices.microphoneDisconnected": "Mikrofon odspojen", "devices.microphoneDisconnected": "Mikrofon odspojen",
"devices.microphoneError": "Greška prilikom pristupa mikrofonu", "devices.microphoneError": "Greška prilikom pristupa mikrofonu",
"devices.microPhoneMute": "Mikrofon utišan", "devices.microphoneMute": "Mikrofon utišan",
"devices.micophoneUnMute": "Mikrofon pojačan", "devices.microphoneUnMute": "Mikrofon pojačan",
"devices.microphoneEnable": "Mikrofon omogućen", "devices.microphoneEnable": "Mikrofon omogućen",
"devices.microphoneMuteError": "Nije moguće utišati mikrofon", "devices.microphoneMuteError": "Nije moguće utišati mikrofon",
"devices.microphoneUnMuteError": "Nije moguće pojačati mikrofon", "devices.microphoneUnMuteError": "Nije moguće pojačati mikrofon",
@ -136,5 +191,15 @@
"devices.screenSharingError": "Greška prilikom pristupa ekranu", "devices.screenSharingError": "Greška prilikom pristupa ekranu",
"devices.cameraDisconnected": "Kamera odspojena", "devices.cameraDisconnected": "Kamera odspojena",
"devices.cameraError": "Greška prilikom pristupa kameri" "devices.cameraError": "Greška prilikom pristupa kameri",
"moderator.clearChat": "Moderator je izbrisao razgovor",
"moderator.clearFiles": "Moderator je izbrisao datoteke",
"moderator.muteAudio": "Moderator je utišao tvoj zvuk",
"moderator.muteVideo": "Moderator je zaustavio tvoj video",
"moderator.stopScreenSharing": null,
"unsupportedBrowser.titleUnsupportedBrowser": null,
"unsupportedBrowser.titlewebrtcUnavailable": null,
"unsupportedBrowser.bodyText": null
} }

View File

@ -1,24 +1,24 @@
{ {
"socket.disconnected": "A kapcsolat lebomlott", "socket.disconnected": "A kapcsolat lebomlott",
"socket.reconnecting": "A kapcsolat lebomlott, újrapróbálkozás", "socket.reconnecting": "A kapcsolat lebomlott, újrapróbálkozás",
"socket.reconnected": "Sikeres újarkapcsolódás", "socket.reconnected": "Sikeres újrakapcsolódás",
"socket.requestError": "Sikertelen szerver lekérés", "socket.requestError": "Sikertelen szerver lekérés",
"room.chooseRoom": "Choose the name of the room you would like to join", "room.chooseRoom": "Válaszd ki a konferenciaszobát",
"room.cookieConsent": "Ez a weblap a felhasználói élmény fokozása miatt sütiket használ", "room.cookieConsent": "Ez a weblap a felhasználói élmény fokozása miatt sütiket használ",
"room.consentUnderstand": "I understand", "room.consentUnderstand": "Megértettem",
"room.joined": "Csatlakozátál a konferenciához", "room.joined": "Csatlakoztál a konferenciához",
"room.cantJoin": "Sikertelen csatlakozás a konferenciához", "room.cantJoin": "Sikertelen csatlakozás a konferenciához",
"room.youLocked": "A konferenciába való belépés letiltva", "room.youLocked": "A konferenciába való belépés letiltva",
"room.cantLock": "Sikertelen a konferenciaba való belépés letiltása", "room.cantLock": "Sikertelen a konferenciába való belépés letiltása",
"room.youUnLocked": "A konferenciába való belépés engedélyezve", "room.youUnLocked": "A konferenciába való belépés engedélyezve",
"room.cantUnLock": "Sikertelen a konferenciába való belépés engedélyezése", "room.cantUnLock": "Sikertelen a konferenciába való belépés engedélyezése",
"room.locked": "A konferenciába való belépés letiltva", "room.locked": "A konferenciába való belépés letiltva",
"room.unlocked": "A konferenciába való belépés engedélyezve", "room.unlocked": "A konferenciába való belépés engedélyezve",
"room.newLobbyPeer": "Új részvevő lépett be a konferencia előszobájába", "room.newLobbyPeer": "Új részvevő lépett be a konferencia előszobájába",
"room.lobbyPeerLeft": "A konferencia előszobájából a részvevő távozott", "room.lobbyPeerLeft": "A konferencia előszobájából a részvevő távozott",
"room.lobbyPeerChangedDisplayName": "Az előszobai résztvevő meváltoztatta a nevét: {displayName}", "room.lobbyPeerChangedDisplayName": "Az előszobai résztvevő megváltoztatta a nevét: {displayName}",
"room.lobbyPeerChangedPicture": "Az előszobai résztvevő meváltoztatta a képét", "room.lobbyPeerChangedPicture": "Az előszobai résztvevő megváltoztatta a képét",
"room.setAccessCode": "A konferencia hozzáférési kódja megváltozott", "room.setAccessCode": "A konferencia hozzáférési kódja megváltozott",
"room.accessCodeOn": "A konferencia hozzáférési kódja aktiválva", "room.accessCodeOn": "A konferencia hozzáférési kódja aktiválva",
"room.accessCodeOff": "A konferencia hozzáférési kódka deaktiválva", "room.accessCodeOff": "A konferencia hozzáférési kódka deaktiválva",
@ -39,8 +39,8 @@
"room.audioOnly": "csak Hang", "room.audioOnly": "csak Hang",
"room.audioVideo": "Hang és Videó", "room.audioVideo": "Hang és Videó",
"room.youAreReady": "Ok, kész vagy", "room.youAreReady": "Ok, kész vagy",
"room.emptyRequireLogin": "A konferencia üres! Be kell lépned a konferecnia elkezdéséhez, vagy várnod kell amíg a házigazda becsatlakozik.", "room.emptyRequireLogin": "A konferencia üres! Be kell lépned a konferencia elkezdéséhez, vagy várnod kell amíg a házigazda becsatlakozik.",
"room.locketWait": "A konferencia szobába a a belépés tilos - Várj amíg valaki be nem enged ...", "room.locketWait": "Az automatikus belépés tiltva van - Várj amíg valaki beenged ...",
"room.lobbyAdministration": "Előszoba adminisztráció", "room.lobbyAdministration": "Előszoba adminisztráció",
"room.peersInLobby": "Résztvevők az előszobában", "room.peersInLobby": "Résztvevők az előszobában",
"room.lobbyEmpty": "Épp senki sincs a konferencia előszobájában", "room.lobbyEmpty": "Épp senki sincs a konferencia előszobájában",
@ -49,10 +49,31 @@
"room.spotlights": "Látható résztvevők", "room.spotlights": "Látható résztvevők",
"room.passive": "Passzív résztvevők", "room.passive": "Passzív résztvevők",
"room.videoPaused": "Ez a videóstream szünetel", "room.videoPaused": "Ez a videóstream szünetel",
"room.muteAll": "Összes némítása",
"room.stopAllVideo": "Összes video némítása",
"room.closeMeeting": "Konferencia lebontása",
"room.clearChat": "Chat történelem kiürítése",
"room.clearFileSharing": "File megosztás kiürítése",
"room.speechUnsupported": "A böngésződ nem támogatja a hangfelismerést",
"room.moderatoractions": "Moderátor funkciók",
"room.raisedHand": "{displayName} jelentkezik",
"room.loweredHand": "{displayName} leeresztette a kezét",
"room.extraVideo": "Kiegészítő videó",
"room.overRoomLimit": "A konferenciaszoba betelt..",
"room.help": "Segítség",
"room.about": "Névjegy",
"room.shortcutKeys": "Billentyűparancsok",
"room.browsePeersSpotlight": "Résztvevők böngészése",
"room.stopAllScreenSharing": "Összes képernyőmegosztás leállítása",
"me.mutedPTT": "Némítva vagy, ha beszélnél nyomd le a SZÓKÖZ billentyűt",
"roles.gotRole": "{role} szerepet kaptál",
"roles.lostRole": "Elvesztetted a {role} szerepet",
"tooltip.login": "Belépés", "tooltip.login": "Belépés",
"tooltip.logout": "Kilépés", "tooltip.logout": "Kilépés",
"tooltip.admitFromLobby": "Beenegdem az előszobából", "tooltip.admitFromLobby": "Beengedem az előszobából",
"tooltip.lockRoom": "A konferenciába való belépés letiltása", "tooltip.lockRoom": "A konferenciába való belépés letiltása",
"tooltip.unLockRoom": "konferenciába való belépés engedélyezése", "tooltip.unLockRoom": "konferenciába való belépés engedélyezése",
"tooltip.enterFullscreen": "Teljes képernyős mód", "tooltip.enterFullscreen": "Teljes képernyős mód",
@ -60,6 +81,14 @@
"tooltip.lobby": "Az előszobában várakozók listája", "tooltip.lobby": "Az előszobában várakozók listája",
"tooltip.settings": "Beállítások", "tooltip.settings": "Beállítások",
"tooltip.participants": "Résztvevők", "tooltip.participants": "Résztvevők",
"tooltip.kickParticipant": "Résztvevő kirúgása",
"tooltip.muteParticipant": "Résztvevő némítása",
"tooltip.muteParticipantVideo": "Résztvevő videóstreamének némítása",
"tooltip.raisedHand": "Jelentkezés",
"tooltip.muteScreenSharing": "Képernyőmegosztás szüneteltetése",
"tooltip.muteParticipantAudioModerator": "Résztvevő hangjának némítása mindenkinél",
"tooltip.muteParticipantVideoModerator": "Résztvevő videójának némítása mindenkinél",
"tooltip.muteScreenSharingModerator": "Résztvevő képernyőmegosztásának leállítása mindenkinél",
"label.roomName": "Konferencia", "label.roomName": "Konferencia",
"label.chooseRoomButton": "Tovább", "label.chooseRoomButton": "Tovább",
@ -73,6 +102,7 @@
"label.filesharing": "Fájl megosztás", "label.filesharing": "Fájl megosztás",
"label.participants": "Résztvevők", "label.participants": "Résztvevők",
"label.shareFile": "Fájl megosztása", "label.shareFile": "Fájl megosztása",
"label.shareGalleryFile": "Fájl megosztás galériából",
"label.fileSharingUnsupported": "Fájl megosztás nem támogatott", "label.fileSharingUnsupported": "Fájl megosztás nem támogatott",
"label.unknown": "Ismeretlen", "label.unknown": "Ismeretlen",
"label.democratic": "Egyforma képméretű képkiosztás", "label.democratic": "Egyforma képméretű képkiosztás",
@ -83,25 +113,50 @@
"label.veryHigh": "Nagyon magas (FHD)", "label.veryHigh": "Nagyon magas (FHD)",
"label.ultra": "Ultra magas (UHD)", "label.ultra": "Ultra magas (UHD)",
"label.close": "Bezár", "label.close": "Bezár",
"label.media": "Média",
"label.appearance": "Megjelenés",
"label.advanced": "Részletek",
"label.addVideo": "Videó hozzáadása",
"label.promoteAllPeers": "Mindenkit beengedek",
"label.moreActions": "További műveletek",
"label.version": "Verzió",
"settings.settings": "Beállítások", "settings.settings": "Beállítások",
"settings.camera": "Kamera", "settings.camera": "Kamera",
"settings.selectCamera": "Válasz videóeszközt", "settings.selectCamera": "Válassz videoeszközt",
"settings.cantSelectCamera": "Nem lehet a videó eszközt kiválasztani", "settings.cantSelectCamera": "Nem lehet a videoeszközt kiválasztani",
"settings.audio": "Hang eszköz", "settings.audio": "Hang eszköz",
"settings.selectAudio": "Válasz hangeszközt", "settings.selectAudio": "Válassz hangeszközt",
"settings.cantSelectAudio": "Nem lehet a hang eszközt kiválasztani", "settings.cantSelectAudio": "Nem sikerült a hangeszközt kiválasztani",
"settings.resolution": "Válaszd ki a videóeszközöd felbontását", "settings.audioOutput": "Kimenti hangeszköz",
"settings.selectAudioOutput": "Válassz kimenti hangeszközt",
"settings.cantSelectAudioOutput": "Nem sikerült a kimeneti hangeszközt kiválasztani",
"settings.resolution": "Válaszd ki a videoeszközöd felbontását",
"settings.frameRate": null,
"settings.screenSharingResolution": null,
"settings.screenSharingFrameRate": null,
"settings.layout": "A konferencia képkiosztása", "settings.layout": "A konferencia képkiosztása",
"settings.selectRoomLayout": "Válaszd ki a konferencia képkiosztását", "settings.selectRoomLayout": "Válaszd ki a konferencia képkiosztását",
"settings.advancedMode": "Részletes információk", "settings.advancedMode": "Részletes információk",
"settings.permanentTopBar": "Állandó felső sáv", "settings.permanentTopBar": "Állandó felső sáv",
"settings.lastn": "A látható videók száma", "settings.lastn": "A látható videók száma",
"settings.hiddenControls": "Média Gombok automatikus elrejtése",
"settings.notificationSounds": "Értesítések hangjelzéssel",
"settings.showNotifications": "Értesítések megjelenítése",
"settings.buttonControlBar": "Médiavezérlő gombok leválasztása",
"settings.showAdvancedVideo": null,
"settings.showAdvancedAudio": "Speciális hang beállítások",
"settings.echoCancellation": "Visszhangelnyomás",
"settings.autoGainControl": "Automatikus hangerő",
"settings.noiseSuppression": "Zajelnyomás",
"settings.drawerOverlayed": "Oldalsáv a tartalom felett",
"settings.voiceActivatedUnmute": "Beszéd aktivált mikrofon némítás",
"settings.noiseThreshold": "Zajszint",
"filesharing.saveFileError": "A file-t nem sikerült elmenteni", "filesharing.saveFileError": "A file-t nem sikerült elmenteni",
"filesharing.startingFileShare": "Fájl megosztása", "filesharing.startingFileShare": "Fájl megosztása",
"filesharing.successfulFileShare": "A fájl sikeresen megosztva", "filesharing.successfulFileShare": "A fájl sikeresen megosztva",
"filesharing.unableToShare": "Sikereteln fájl megosztás", "filesharing.unableToShare": "Sikertelen fájl megosztás",
"filesharing.error": "Hiba a fájlmegosztás során", "filesharing.error": "Hiba a fájlmegosztás során",
"filesharing.finished": "A fájl letöltés befejeződött", "filesharing.finished": "A fájl letöltés befejeződött",
"filesharing.save": "Mentés", "filesharing.save": "Mentés",
@ -111,7 +166,7 @@
"devices.devicesChanged": "Az eszközei megváltoztak, konfiguráld őket be a beállítások menüben", "devices.devicesChanged": "Az eszközei megváltoztak, konfiguráld őket be a beállítások menüben",
"device.audioUnsupported": "A hnag nem támogatott", "device.audioUnsupported": "A hang nem támogatott",
"device.activateAudio": "Hang aktiválása", "device.activateAudio": "Hang aktiválása",
"device.muteAudio": "Hang némítása", "device.muteAudio": "Hang némítása",
"device.unMuteAudio": "Hang némítás kikapcsolása", "device.unMuteAudio": "Hang némítás kikapcsolása",
@ -122,12 +177,12 @@
"device.screenSharingUnsupported": "A képernyő megosztás nem támogatott", "device.screenSharingUnsupported": "A képernyő megosztás nem támogatott",
"device.startScreenSharing": "Képernyőmegosztás indítása", "device.startScreenSharing": "Képernyőmegosztás indítása",
"device.stopScreenSharing": "Képernyőmegosztás leáłłítása", "device.stopScreenSharing": "Képernyőmegosztás leállítása",
"devices.microphoneDisconnected": "Microphone kapcsolat bontva", "devices.microphoneDisconnected": "Mikrofon kapcsolat bontva",
"devices.microphoneError": "Hiba történt a mikrofon hangeszköz elérése közben", "devices.microphoneError": "Hiba történt a mikrofon hangeszköz elérése közben",
"devices.microPhoneMute": "A mikrofon némítva lett", "devices.microphoneMute": "A mikrofon némítva lett",
"devices.micophoneUnMute": "A mikrofon némítása ki lett kapocsolva", "devices.microphoneUnMute": "A mikrofon némítása ki lett kapocsolva",
"devices.microphoneEnable": "A mikrofon engedéylezve", "devices.microphoneEnable": "A mikrofon engedéylezve",
"devices.microphoneMuteError": "Nem sikerült a mikrofonod némítása", "devices.microphoneMuteError": "Nem sikerült a mikrofonod némítása",
"devices.microphoneUnMuteError": "Nem sikerült a mikrofonod némításának kikapcsolása", "devices.microphoneUnMuteError": "Nem sikerült a mikrofonod némításának kikapcsolása",
@ -136,5 +191,15 @@
"devices.screenSharingError": "Hiba történt a képernyőd megosztása során", "devices.screenSharingError": "Hiba történt a képernyőd megosztása során",
"devices.cameraDisconnected": "A kamera kapcsolata lebomlott", "devices.cameraDisconnected": "A kamera kapcsolata lebomlott",
"devices.cameraError": "Hiba történt a kamera elérése során" "devices.cameraError": "Hiba történt a kamera elérése során",
"moderator.clearChat": "A moderátor kiürítette a chat történelmet",
"moderator.clearFiles": "A moderátor kiürítette a file megosztás történelmet",
"moderator.muteAudio": "A moderátor elnémította a hangod",
"moderator.muteVideo": "A moderátor elnémította a videód",
"moderator.stopScreenSharing": "A moderátor leállította a képernyőmegosztásod",
"unsupportedBrowser.titleUnsupportedBrowser": "A bőngésző verziód sajnos nem támogatott! :-(",
"unsupportedBrowser.titlewebrtcUnavailable": "A böngésződ egy szükséges funkciója nem elérhető!",
"unsupportedBrowser.bodyText": "Kérlek frissítsd a böngésződ, válts másik böngészőre, vagy ellenőrizd a böngésződ beállításait! Támogatott böngészők:"
} }

View File

@ -49,10 +49,31 @@
"room.spotlights": "Partecipanti in Evidenza", "room.spotlights": "Partecipanti in Evidenza",
"room.passive": "Participanti Passivi", "room.passive": "Participanti Passivi",
"room.videoPaused": "Il video è in pausa", "room.videoPaused": "Il video è in pausa",
"room.muteAll": "Muta tutti",
"room.stopAllVideo": "Ferma tutti i video",
"room.closeMeeting": "Termina meeting",
"room.clearChat": "Pulisci chat",
"room.clearFileSharing": "Pulisci file sharing",
"room.speechUnsupported": "Il tuo browser non supporta il riconoscimento vocale",
"room.moderatoractions": "Azioni moderatore",
"room.raisedHand": "{displayName} ha alzato la mano",
"room.loweredHand": "{displayName} ha abbassato la mano",
"room.extraVideo": "Video extra",
"room.overRoomLimit": "La stanza è piena, riprova più tardi.",
"room.help": "Aiuto",
"room.about": "Informazioni su",
"room.shortcutKeys": "Scorciatoie da tastiera",
"room.browsePeersSpotlight": null,
"room.stopAllScreenSharing": null,
"me.mutedPTT": "Sei mutato, tieni premuto SPAZIO per parlare",
"roles.gotRole": "Hai ottenuto il ruolo: {role}",
"roles.lostRole": "Hai perso il ruolo: {role}",
"tooltip.login": "Log in", "tooltip.login": "Log in",
"tooltip.logout": "Log out", "tooltip.logout": "Log out",
"tooltip.admitFromLobby": "Ammetti dalla lobby", "tooltip.admitFromLobby": "Accetta partecipante dalla lobby",
"tooltip.lockRoom": "Blocca stanza", "tooltip.lockRoom": "Blocca stanza",
"tooltip.unLockRoom": "Sblocca stanza", "tooltip.unLockRoom": "Sblocca stanza",
"tooltip.enterFullscreen": "Modalità schermo intero", "tooltip.enterFullscreen": "Modalità schermo intero",
@ -60,6 +81,14 @@
"tooltip.lobby": "Mostra lobby", "tooltip.lobby": "Mostra lobby",
"tooltip.settings": "Mostra impostazioni", "tooltip.settings": "Mostra impostazioni",
"tooltip.participants": "Mostra partecipanti", "tooltip.participants": "Mostra partecipanti",
"tooltip.kickParticipant": "Espelli partecipante",
"tooltip.muteParticipant": "Muta partecipante",
"tooltip.muteParticipantVideo": "Ferma video partecipante",
"tooltip.raisedHand": "Mano alzata",
"tooltip.muteScreenSharing": "Ferma condivisione schermo partecipante",
"tooltip.muteParticipantAudioModerator": "Sospendi audio globale",
"tooltip.muteParticipantVideoModerator": "Sospendi video globale",
"tooltip.muteScreenSharingModerator": "Sospendi condivisione schermo globale",
"label.roomName": "Nome della stanza", "label.roomName": "Nome della stanza",
"label.chooseRoomButton": "Continua", "label.chooseRoomButton": "Continua",
@ -73,6 +102,7 @@
"label.filesharing": "Condivisione file", "label.filesharing": "Condivisione file",
"label.participants": "Partecipanti", "label.participants": "Partecipanti",
"label.shareFile": "Condividi file", "label.shareFile": "Condividi file",
"label.shareGalleryFile": "Condividi immagine",
"label.fileSharingUnsupported": "Condivisione file non supportata", "label.fileSharingUnsupported": "Condivisione file non supportata",
"label.unknown": "Sconosciuto", "label.unknown": "Sconosciuto",
"label.democratic": "Vista Democratica", "label.democratic": "Vista Democratica",
@ -83,6 +113,13 @@
"label.veryHigh": "Molto alta (FHD)", "label.veryHigh": "Molto alta (FHD)",
"label.ultra": "Ultra (UHD)", "label.ultra": "Ultra (UHD)",
"label.close": "Chiudi", "label.close": "Chiudi",
"label.media": "Media",
"label.appearance": "Aspetto",
"label.advanced": "Avanzate",
"label.addVideo": "Aggiungi video",
"label.promoteAllPeers": "Promuovi tutti",
"label.moreActions": "Altre azioni",
"label.version": null,
"settings.settings": "Impostazioni", "settings.settings": "Impostazioni",
"settings.camera": "Videocamera", "settings.camera": "Videocamera",
@ -91,12 +128,30 @@
"settings.audio": "Dispositivo audio", "settings.audio": "Dispositivo audio",
"settings.selectAudio": "Seleziona dispositivo audio", "settings.selectAudio": "Seleziona dispositivo audio",
"settings.cantSelectAudio": "Impossibile selezionare dispositivo audio", "settings.cantSelectAudio": "Impossibile selezionare dispositivo audio",
"settings.audioOutput": "Dispositivo di uscita audio",
"settings.selectAudioOutput": "Seleziona il dispositivo di uscita audio",
"settings.cantSelectAudioOutput": "Impossibile selezionare il dispositivo di uscita audio",
"settings.resolution": "Seleziona risoluzione", "settings.resolution": "Seleziona risoluzione",
"settings.frameRate": null,
"settings.screenSharingResolution": null,
"settings.screenSharingFrameRate": null,
"settings.layout": "Aspetto stanza", "settings.layout": "Aspetto stanza",
"settings.selectRoomLayout": "Seleziona aspetto stanza", "settings.selectRoomLayout": "Seleziona aspetto stanza",
"settings.advancedMode": "Modalità avanzata", "settings.advancedMode": "Modalità avanzata",
"settings.permanentTopBar": "Barra superiore permanente", "settings.permanentTopBar": "Barra superiore permanente",
"settings.lastn": "Numero di video visibili", "settings.lastn": "Numero di video visibili",
"settings.hiddenControls": "Controlli media nascosti",
"settings.notificationSounds": "Suoni di notifica",
"settings.showNotifications": "Mostra notifiche",
"settings.buttonControlBar": "Controlli media separati",
"settings.showAdvancedVideo": null,
"settings.showAdvancedAudio": null,
"settings.echoCancellation": "Cancellazione echo",
"settings.autoGainControl": "Controllo guadagno automatico",
"settings.noiseSuppression": "Riduzione del rumore",
"settings.drawerOverlayed": "Barra laterale sovrapposta",
"settings.voiceActivatedUnmute": null,
"settings.noiseThreshold": null,
"filesharing.saveFileError": "Impossibile salvare file", "filesharing.saveFileError": "Impossibile salvare file",
"filesharing.startingFileShare": "Tentativo di condivisione file", "filesharing.startingFileShare": "Tentativo di condivisione file",
@ -126,8 +181,8 @@
"devices.microphoneDisconnected": "Microfono scollegato", "devices.microphoneDisconnected": "Microfono scollegato",
"devices.microphoneError": "Errore con l'accesso al microfono", "devices.microphoneError": "Errore con l'accesso al microfono",
"devices.microPhoneMute": "Microfono silenziato", "devices.microphoneMute": "Microfono silenziato",
"devices.micophoneUnMute": "Microfono riattivato", "devices.microphoneUnMute": "Microfono riattivato",
"devices.microphoneEnable": "Microfono attivo", "devices.microphoneEnable": "Microfono attivo",
"devices.microphoneMuteError": "Impossibile silenziare il microfono", "devices.microphoneMuteError": "Impossibile silenziare il microfono",
"devices.microphoneUnMuteError": "Impossibile riattivare il microfono", "devices.microphoneUnMuteError": "Impossibile riattivare il microfono",
@ -136,5 +191,15 @@
"devices.screenSharingError": "Errore con l'accesso al tuo schermo", "devices.screenSharingError": "Errore con l'accesso al tuo schermo",
"devices.cameraDisconnected": "Videocamera scollegata", "devices.cameraDisconnected": "Videocamera scollegata",
"devices.cameraError": "Errore con l'accesso alla videocamera" "devices.cameraError": "Errore con l'accesso alla videocamera",
"moderator.clearChat": "Il moderatore ha pulito la chat",
"moderator.clearFiles": "Il moderatore ha pulito i file",
"moderator.muteAudio": "Il moderatore ha mutato il tuo audio",
"moderator.muteVideo": "Il moderatore ha fermato il tuo video",
"moderator.stopScreenSharing": null,
"unsupportedBrowser.titleUnsupportedBrowser": null,
"unsupportedBrowser.titlewebrtcUnavailable": null,
"unsupportedBrowser.bodyText": null
} }

View File

@ -0,0 +1,199 @@
{
"socket.disconnected": "Esat bezsaistē",
"socket.reconnecting": "Esat bezsaistē, tiek mēģināts pievienoties",
"socket.reconnected": "Esat atkārtoti pievienojies",
"socket.requestError": "Kļūme servera pieprasījumā",
"room.chooseRoom": "Ievadiet sapulces telpas nosaukumu (ID), kurai vēlaties pievienoties",
"room.cookieConsent": "Lai uzlabotu lietotāja pieredzi, šī vietne izmanto sīkfailus",
"room.consentUnderstand": "Es saprotu un piekrītu",
"room.joined": "Jūs esiet pievienojies sapulces telpai",
"room.cantJoin": "Nav iespējams pievienoties sapulces telpai",
"room.youLocked": "Jūs aizslēdzāt sapulces telpu",
"room.cantLock": "Nav iespējams aizslēgt sapulces telpu",
"room.youUnLocked": "Jūs atslēdzāt sapulces telpu",
"room.cantUnLock": "Nav iespējams atslēgt sapulces telpu",
"room.locked": "Sapulces telpa tagad ir AIZSLĒGTA",
"room.unlocked": "Sapulces telpa tagad ir ATSLĒGTA",
"room.newLobbyPeer": "Jauns dalībnieks ienācis uzgaidāmajā telpā",
"room.lobbyPeerLeft": "Dalībnieks uzgaidāmo telpu pameta",
"room.lobbyPeerChangedDisplayName": "Dalībnieks uzgaidāmajā telpā nomainīja vārdu uz {displayName}",
"room.lobbyPeerChangedPicture": "Dalībnieks uzgaidāmajā telpā nomainīja pašattēlu",
"room.setAccessCode": "Pieejas kods sapulces telpai aktualizēts",
"room.accessCodeOn": "Pieejas kods sapulces telpai tagad ir aktivēts",
"room.accessCodeOff": "Pieejas kods sapulces telpai tagad ir deaktivēts (atslēgts)",
"room.peerChangedDisplayName": "{oldDisplayName} pārsaucās par {displayName}",
"room.newPeer": "{displayName} pievienojās sapulces telpai",
"room.newFile": "Pieejams jauns fails",
"room.toggleAdvancedMode": "Pārslēgt uz advancēto režīmu",
"room.setDemocraticView": "Nomainīts izkārtojums uz demokrātisko skatu",
"room.setFilmStripView": "Nomainīts izkārtojums uz diapozitīvu (filmstrip) skatu",
"room.loggedIn": "Jūs esat ierakstījies (sistēmā)",
"room.loggedOut": "Jūs esat izrakstījies (no sistēmas)",
"room.changedDisplayName": "Jūsu vārds mainīts uz {displayName}",
"room.changeDisplayNameError": "Gadījās ķibele ar Jūsu vārda nomaiņu",
"room.chatError": "Nav iespējams nosūtīt tērziņa ziņu",
"room.aboutToJoin": "Jūs grasāties pievienoties sapulcei",
"room.roomId": "Sapulces telpas nosaukums (ID): {roomName}",
"room.setYourName": "Norādiet savu dalības vārdu un izvēlieties kā vēlaties pievienoties sapulcei:",
"room.audioOnly": "Vienīgi audio",
"room.audioVideo": "Audio & video",
"room.youAreReady": "Ok, Jūs esiet gatavi!",
"room.emptyRequireLogin": "Sapulces telpa ir tukša! Jūs varat Ierakstīties sistēmā, lai uzsāktu vadīt sapulci vai pagaidīt kamēr pievienojas sapulces rīkotājs/vadītājs",
"room.locketWait": "Sapulce telpa ir slēgta. Jūs atrodaties tās uzgaidāmajā telpā. Uzkavējieties, kamēr kāds Jūs sapulcē ielaiž ...",
"room.lobbyAdministration": "Uzgaidāmās telpas administrēšana",
"room.peersInLobby": "Dalībnieki uzgaidāmajā telpā",
"room.lobbyEmpty": "Pašreiz uzgaidāmajā telpā neviena nav",
"room.hiddenPeers": "{hiddenPeersCount, plural, one {participant} other {participants}}",
"room.me": "Es",
"room.spotlights": "Aktīvie (referējošie) dalībnieki",
"room.passive": "Pasīvie dalībnieki",
"room.videoPaused": "Šis video ir pauzēts",
"room.muteAll": "Noklusināt visus dalībnieku mikrofonus",
"room.stopAllVideo": "Izslēgt visu dalībnieku kameras",
"room.closeMeeting": "Beigt sapulci",
"room.clearChat": "Nodzēst visus tērziņus",
"room.clearFileSharing": "Notīrīt visus kopīgotos failus",
"room.speechUnsupported": "Jūsu pārlūks neatbalsta balss atpazīšanu",
"room.moderatoractions": "Moderatora rīcība",
"room.raisedHand": "{displayName} pacēla roku",
"room.loweredHand": "{displayName} nolaida roku",
"room.extraVideo": "Papildus video",
"room.help": null,
"room.about": null,
"room.shortcutKeys": null,
"room.browsePeersSpotlight": null,
"room.stopAllScreenSharing": null,
"me.mutedPTT": "Jūs esat noklusināts. Turiet taustiņu SPACE-BAR, lai runātu",
"roles.gotRole": "Jūs ieguvāt lomu: {role}",
"roles.lostRole": "Jūs zaudējāt lomu: {role}",
"tooltip.login": "Ierakstīties",
"tooltip.logout": "Izrakstīties",
"tooltip.admitFromLobby": "Ielaist no uzgaidāmās telpas",
"tooltip.lockRoom": "Aizslēgt sapulces telpu",
"tooltip.unLockRoom": "Atlēgt sapulces telpu",
"tooltip.enterFullscreen": "Aktivēt pilnekrāna režīmu",
"tooltip.leaveFullscreen": "Pamest pilnekrānu",
"tooltip.lobby": "Parādīt uzgaidāmo telpu",
"tooltip.settings": "Parādīt iestatījumus",
"tooltip.participants": "Parādīt dalībniekus",
"tooltip.kickParticipant": "Izvadīt (izspert) dalībnieku",
"tooltip.muteParticipant": "Noklusināt dalībnieku",
"tooltip.muteParticipantVideo": "Atslēgt dalībnieka video",
"tooltip.raisedHand": "Pacelt roku",
"tooltip.muteScreenSharing": null,
"tooltip.muteParticipantAudioModerator": null,
"tooltip.muteParticipantVideoModerator": null,
"tooltip.muteScreenSharingModerator": null,
"label.roomName": "Sapulces telpas nosaukums (ID)",
"label.chooseRoomButton": "Turpināt",
"label.yourName": "Jūu vārds",
"label.newWindow": "Jauns logs",
"label.fullscreen": "Pilnekrāns",
"label.openDrawer": "Atvērt atvilkni",
"label.leave": "Pamest",
"label.chatInput": "Rakstiet tērziņa ziņu...",
"label.chat": "Tērzētava",
"label.filesharing": "Failu koplietošana",
"label.participants": "Dalībnieki",
"label.shareFile": "Koplietot failu",
"label.fileSharingUnsupported": "Failu koplietošana netiek atbalstīta",
"label.unknown": "Nezināms",
"label.democratic": "Demokrātisks skats",
"label.filmstrip": "Diapozitīvu (filmstrip) skats",
"label.low": "Zema",
"label.medium": "Vidēja",
"label.high": "Augsta (HD)",
"label.veryHigh": "Ļoti augsta (FHD)",
"label.ultra": "Ultra (UHD)",
"label.close": "Aizvērt",
"label.media": "Mediji",
"label.appearance": "Izskats",
"label.advanced": "Advancēts",
"label.addVideo": "Pievienot video",
"label.moreActions": null,
"label.version": null,
"settings.settings": "Iestatījumi",
"settings.camera": "Kamera",
"settings.selectCamera": "Izvēlieties kameru (video ierīci)",
"settings.cantSelectCamera": "Nav iespējams lietot šo kameru (video ierīci)",
"settings.audio": "Skaņas ierīce",
"settings.selectAudio": "Izvēlieties skaņas ierīci",
"settings.cantSelectAudio": "Nav iespējams lietot šo skaņas (audio) ierīci",
"settings.resolution": "Iestatiet jūsu video izšķirtspēju",
"settings.frameRate": null,
"settings.screenSharingResolution": null,
"settings.screenSharingFrameRate": null,
"settings.layout": "Sapulces telpas izkārtojums",
"settings.selectRoomLayout": "Iestatiet sapulces telpas izkārtojumu",
"settings.advancedMode": "Advancētais režīms",
"settings.permanentTopBar": "Pastāvīga augšējā (ekrānaugšas) josla",
"settings.lastn": "Jums redzamo video/kameru skaits",
"settings.hiddenControls": "Slēpto mediju vadība",
"settings.notificationSounds": "Paziņojumu skaņas",
"settings.showNotifications": null,
"settings.buttonControlBar": null,
"settings.showAdvancedVideo": null,
"settings.showAdvancedAudio": null,
"settings.echoCancellation": null,
"settings.autoGainControl": null,
"settings.noiseSuppression": null,
"settings.drawerOverlayed": null,
"settings.voiceActivatedUnmute": null,
"settings.noiseThreshold": null,
"filesharing.saveFileError": "Nav iespējams saglabāt failu",
"filesharing.startingFileShare": "Tiek mēģināts kopīgot failu",
"filesharing.successfulFileShare": "Fails sekmīgi kopīgots",
"filesharing.unableToShare": "Nav iespējams kopīgot failu",
"filesharing.error": "Atgadījās faila kopīgošanas kļūme",
"filesharing.finished": "Fails ir lejupielādēts",
"filesharing.save": "Saglabāt",
"filesharing.sharedFile": "{displayName} kopīgoja failu",
"filesharing.download": "Lejuplādēt",
"filesharing.missingSeeds": "Ja šis process aizņem ilgu laiku, iespējams nav neviena, kas sēklo (seed) šo torentu. Mēģiniet palūgt kādu atkārtoti augšuplādēt Jūsu gribēto failu.",
"devices.devicesChanged": "Jūsu ierīces pamainījās. Iestatījumu izvēlnē (dialogā) iestatiet jaunās ierīces.",
"device.audioUnsupported": "Skaņa (audio) netiek atbalstīta",
"device.activateAudio": "Iespējot/aktivēt mikrofonu (izejošo skaņu)",
"device.muteAudio": "Atslēgt/noklusināt mikrofonu (izejošo skaņu) ",
"device.unMuteAudio": "Ieslēgt mikrofonu (izejošo skaņu)",
"device.videoUnsupported": "Kamera (izejošais video) netiek atbalstīta",
"device.startVideo": "Ieslēgt kameru (izejošo video)",
"device.stopVideo": "Izslēgt kameru (izejošo video)",
"device.screenSharingUnsupported": "Ekrāna kopīgošana netiek atbalstīta",
"device.startScreenSharing": "Sākt ekrāna kopīgošanu",
"device.stopScreenSharing": "Beigt ekrāna kopīgošanu",
"devices.microphoneDisconnected": "Mikrofons atvienots",
"devices.microphoneError": "Atgadījās kļūme, piekļūstot jūsu mikrofonam",
"devices.microPhoneMute": "Mikrofons izslēgts/noklusināts",
"devices.micophoneUnMute": "Mikrofons ieslēgts",
"devices.microphoneEnable": "Mikrofons iespējots",
"devices.microphoneMuteError": "Nav iespējams izslēgt Jūsu mikrofonu",
"devices.microphoneUnMuteError": "Nav iespējams ieslēgt Jūsu mikrofonu",
"devices.screenSharingDisconnected" : "Ekrāna kopīgošana nenotiek (atvienota)",
"devices.screenSharingError": "Atgadījās kļūme, piekļūstot Jūsu ekrānam",
"devices.cameraDisconnected": "Kamera atvienota",
"devices.cameraError": "Atgadījās kļūme, piekļūstot Jūsu kamerai",
"moderator.clearChat": "Moderators nodzēsa tērziņus",
"moderator.clearFiles": "Moderators notīrīja failus",
"moderator.muteAudio": "Moderators noklusināja jūsu mikrofonu",
"moderator.muteVideo": "Moderators atslēdza jūsu kameru",
"moderator.stopScreenSharing": null,
"unsupportedBrowser.titleUnsupportedBrowser": null,
"unsupportedBrowser.titlewebrtcUnavailable": null,
"unsupportedBrowser.bodyText": null
}

View File

@ -49,6 +49,27 @@
"room.spotlights": "Deltakere i fokus", "room.spotlights": "Deltakere i fokus",
"room.passive": "Passive deltakere", "room.passive": "Passive deltakere",
"room.videoPaused": "Denne videoen er inaktiv", "room.videoPaused": "Denne videoen er inaktiv",
"room.muteAll": "Demp alle",
"room.stopAllVideo": "Stopp all video",
"room.closeMeeting": "Avslutt møte",
"room.clearChat": "Tøm chat",
"room.clearFileSharing": "Fjern filer",
"room.speechUnsupported": "Din nettleser støtter ikke stemmegjenkjenning",
"room.moderatoractions": "Moderatorhandlinger",
"room.raisedHand": "{displayName} rakk opp hånden",
"room.loweredHand": "{displayName} tok ned hånden",
"room.extraVideo": "Ekstra video",
"room.overRoomLimit": "Rommet er fullt, prøv igjen om litt.",
"room.help": "Hjelp",
"room.about": "Om",
"room.shortcutKeys": "Hurtigtaster",
"room.browsePeersSpotlight": "Bytt mellom synlig deltaker",
"room.stopAllScreenSharing": "Stopp all skjermdeling",
"me.mutedPTT": "Du er dempet, hold nede SPACE for å snakke",
"roles.gotRole": "Du fikk rollen: {role}",
"roles.lostRole": "Du mistet rollen: {role}",
"tooltip.login": "Logg in", "tooltip.login": "Logg in",
"tooltip.logout": "Logg ut", "tooltip.logout": "Logg ut",
@ -60,6 +81,14 @@
"tooltip.lobby": "Vis lobby", "tooltip.lobby": "Vis lobby",
"tooltip.settings": "Vis innstillinger", "tooltip.settings": "Vis innstillinger",
"tooltip.participants": "Vis deltakere", "tooltip.participants": "Vis deltakere",
"tooltip.kickParticipant": "Spark ut deltaker",
"tooltip.muteParticipant": "Demp deltaker",
"tooltip.muteParticipantVideo": "Demp deltakervideo",
"tooltip.raisedHand": "Rekk opp hånden",
"tooltip.muteScreenSharing": "Demp deltaker skjermdeling",
"tooltip.muteParticipantAudioModerator": "Demp deltaker globalt",
"tooltip.muteParticipantVideoModerator": "Demp deltakervideo globalt",
"tooltip.muteScreenSharingModerator": "Demp deltakerskjermdeling globalt",
"label.roomName": "Møtenavn", "label.roomName": "Møtenavn",
"label.chooseRoomButton": "Fortsett", "label.chooseRoomButton": "Fortsett",
@ -73,6 +102,7 @@
"label.filesharing": "Fildeling", "label.filesharing": "Fildeling",
"label.participants": "Deltakere", "label.participants": "Deltakere",
"label.shareFile": "Del fil", "label.shareFile": "Del fil",
"label.shareGalleryFile": "Del bilde",
"label.fileSharingUnsupported": "Fildeling ikke støttet", "label.fileSharingUnsupported": "Fildeling ikke støttet",
"label.unknown": "Ukjent", "label.unknown": "Ukjent",
"label.democratic": "Demokratisk", "label.democratic": "Demokratisk",
@ -83,6 +113,13 @@
"label.veryHigh": "Veldig høy (FHD)", "label.veryHigh": "Veldig høy (FHD)",
"label.ultra": "Ultra (UHD)", "label.ultra": "Ultra (UHD)",
"label.close": "Lukk", "label.close": "Lukk",
"label.media": "Media",
"label.appearance": "Utseende",
"label.advanced": "Avansert",
"label.addVideo": "Legg til video",
"label.promoteAllPeers": "Slipp inn alle",
"label.moreActions": "Flere handlinger",
"label.version": "Versjon",
"settings.settings": "Innstillinger", "settings.settings": "Innstillinger",
"settings.camera": "Kamera", "settings.camera": "Kamera",
@ -91,12 +128,30 @@
"settings.audio": "Lydenhet", "settings.audio": "Lydenhet",
"settings.selectAudio": "Velg lydenhet", "settings.selectAudio": "Velg lydenhet",
"settings.cantSelectAudio": "Kan ikke velge lydenhet", "settings.cantSelectAudio": "Kan ikke velge lydenhet",
"settings.audioOutput": "Lydutgangsenhet",
"settings.selectAudioOutput": "Velg lydutgangsenhet",
"settings.cantSelectAudioOutput": "Kan ikke velge lydutgangsenhet",
"settings.resolution": "Velg oppløsning", "settings.resolution": "Velg oppløsning",
"settings.frameRate": "Velg bildefrekvens",
"settings.screenSharingResolution": "Velg skjermdelingsoppløsning",
"settings.screenSharingFrameRate": "Velg skjermdelingsbildefrekvens",
"settings.layout": "Møtelayout", "settings.layout": "Møtelayout",
"settings.selectRoomLayout": "Velg møtelayout", "settings.selectRoomLayout": "Velg møtelayout",
"settings.advancedMode": "Avansert modus", "settings.advancedMode": "Avansert modus",
"settings.permanentTopBar": "Permanent topplinje", "settings.permanentTopBar": "Permanent topplinje",
"settings.lastn": "Antall videoer synlig", "settings.lastn": "Antall videoer synlig",
"settings.showAdvancedVideo": "Avanserte videoinnstillinger",
"settings.showAdvancedAudio": "Avanserte audioinnstillinger",
"settings.hiddenControls": "Skjul media knapper",
"settings.notificationSounds": "Varslingslyder",
"settings.showNotifications": "Vis varslinger",
"settings.buttonControlBar": "Separate media knapper",
"settings.echoCancellation": "Echokansellering",
"settings.autoGainControl": "Auto gain kontroll",
"settings.noiseSuppression": "Støy reduksjon",
"settings.drawerOverlayed": "Sidemeny over innhold",
"settings.voiceActivatedUnmute": "Stemmeaktivert autodemping",
"settings.noiseThreshold": "Støyterskel",
"filesharing.saveFileError": "Klarte ikke å lagre fil", "filesharing.saveFileError": "Klarte ikke å lagre fil",
"filesharing.startingFileShare": "Starter fildeling", "filesharing.startingFileShare": "Starter fildeling",
@ -126,8 +181,8 @@
"devices.microphoneDisconnected": "Mikrofon koblet fra", "devices.microphoneDisconnected": "Mikrofon koblet fra",
"devices.microphoneError": "Det skjedde noe feil med mikrofonen din", "devices.microphoneError": "Det skjedde noe feil med mikrofonen din",
"devices.microPhoneMute": "Dempet mikrofonen", "devices.microphoneMute": "Dempet mikrofonen",
"devices.micophoneUnMute": "Aktiverte mikrofonen", "devices.microphoneUnMute": "Aktiverte mikrofonen",
"devices.microphoneEnable": "Aktiverte mikrofonen", "devices.microphoneEnable": "Aktiverte mikrofonen",
"devices.microphoneMuteError": "Klarte ikke å dempe mikrofonen", "devices.microphoneMuteError": "Klarte ikke å dempe mikrofonen",
"devices.microphoneUnMuteError": "Klarte ikke å aktivere mikrofonen", "devices.microphoneUnMuteError": "Klarte ikke å aktivere mikrofonen",
@ -136,5 +191,15 @@
"devices.screenSharingError": "Det skjedde noe feil med skjermdelingen din", "devices.screenSharingError": "Det skjedde noe feil med skjermdelingen din",
"devices.cameraDisconnected": "Kamera koblet fra", "devices.cameraDisconnected": "Kamera koblet fra",
"devices.cameraError": "Det skjedde noe feil med kameraet ditt" "devices.cameraError": "Det skjedde noe feil med kameraet ditt",
"moderator.clearChat": "Moderator tømte chatten",
"moderator.clearFiles": "Moderator fjernet filer",
"moderator.muteAudio": "Moderator mutet lyden din",
"moderator.muteVideo": "Moderator mutet videoen din",
"moderator.stopScreenSharing": "Moderator mutet skjermdelingen din",
"unsupportedBrowser.titleUnsupportedBrowser": "Nettleser ikke støttet",
"unsupportedBrowser.titlewebrtcUnavailable": "Funksjonalitet som kreves er ikke tilgjengelig i denne nettleseren",
"unsupportedBrowser.bodyText": "Denne møtetjenesten krever funksjonalitet som ikke er støttet i denne nettleseren. Vennligst oppgrader, bytt til en annen nettleser, eller sjekk innstillinger. Støttede nettlesere:"
} }

View File

@ -6,7 +6,7 @@
"room.chooseRoom": "Wybór konferencji", "room.chooseRoom": "Wybór konferencji",
"room.cookieConsent": "Ta strona internetowa wykorzystuje pliki cookie w celu zwiększenia wygody użytkowania.", "room.cookieConsent": "Ta strona internetowa wykorzystuje pliki cookie w celu zwiększenia wygody użytkowania.",
"room.consentUnderstand": "I understand", "room.consentUnderstand": "Rozumiem",
"room.joined": "Podłączono do konferencji", "room.joined": "Podłączono do konferencji",
"room.cantJoin": "Brak możliwości dołączenia do pokoju", "room.cantJoin": "Brak możliwości dołączenia do pokoju",
"room.youLocked": "Zakluczono pokój", "room.youLocked": "Zakluczono pokój",
@ -49,6 +49,27 @@
"room.spotlights": "Aktywni uczestnicy", "room.spotlights": "Aktywni uczestnicy",
"room.passive": "Pasywni uczestnicy", "room.passive": "Pasywni uczestnicy",
"room.videoPaused": "To wideo jest wstrzymane.", "room.videoPaused": "To wideo jest wstrzymane.",
"room.muteAll": "Wycisz wszystkich",
"room.stopAllVideo": "Zatrzymaj wszystkie Video",
"room.closeMeeting": "Zamknij spotkanie",
"room.clearChat": "Wyczyść Chat",
"room.clearFileSharing": "Wyczyść pliki",
"room.speechUnsupported": "Twoja przeglądarka nie rozpoznaje mowy",
"room.moderatoractions": "Akcje moderatora",
"room.raisedHand": "{displayName} podniósł rękę",
"room.loweredHand": "{displayName} opuścił rękę",
"room.extraVideo": "Dodatkowe Video",
"room.overRoomLimit": "Pokój jest pełny, spróbuj za jakiś czas.",
"room.help": "Pomoc",
"room.about": "O pogramie",
"room.shortcutKeys": "Skróty klawiaturowe",
"room.browsePeersSpotlight": null,
"room.stopAllScreenSharing": null,
"me.mutedPTT": "Masz wyciszony mikrofon, przytrzymaj spację aby mówić",
"roles.gotRole": "Masz rolę {role}",
"roles.lostRole": "Nie masz już roli {role}",
"tooltip.login": "Zaloguj", "tooltip.login": "Zaloguj",
"tooltip.logout": "Wyloguj", "tooltip.logout": "Wyloguj",
@ -60,6 +81,14 @@
"tooltip.lobby": "Pokaż poczekalnię", "tooltip.lobby": "Pokaż poczekalnię",
"tooltip.settings": "Pokaż ustawienia", "tooltip.settings": "Pokaż ustawienia",
"tooltip.participants": "Pokaż uczestników", "tooltip.participants": "Pokaż uczestników",
"tooltip.kickParticipant": "Wyrzuć użytkownika",
"tooltip.muteParticipant": "Wycisz użytkownika",
"tooltip.muteParticipantVideo": "Wyłącz wideo użytkownika",
"tooltip.raisedHand": "Podnieś rękę",
"tooltip.muteScreenSharing": "Anuluj udostępniania pulpitu przez użytkownika",
"tooltip.muteParticipantAudioModerator": null,
"tooltip.muteParticipantVideoModerator": null,
"tooltip.muteScreenSharingModerator": null,
"label.roomName": "Nazwa konferencji", "label.roomName": "Nazwa konferencji",
"label.chooseRoomButton": "Kontynuuj", "label.chooseRoomButton": "Kontynuuj",
@ -73,6 +102,7 @@
"label.filesharing": "Udostępnianie plików", "label.filesharing": "Udostępnianie plików",
"label.participants": "Uczestnicy", "label.participants": "Uczestnicy",
"label.shareFile": "Udostępnij plik", "label.shareFile": "Udostępnij plik",
"label.shareGalleryFile": "Udostępnij obraz",
"label.fileSharingUnsupported": "Udostępnianie plików nie jest obsługiwane", "label.fileSharingUnsupported": "Udostępnianie plików nie jest obsługiwane",
"label.unknown": "Nieznane", "label.unknown": "Nieznane",
"label.democratic": "Układ demokratyczny", "label.democratic": "Układ demokratyczny",
@ -83,6 +113,13 @@
"label.veryHigh": "Bardzo wysoka (FHD)", "label.veryHigh": "Bardzo wysoka (FHD)",
"label.ultra": "Ultra (UHD)", "label.ultra": "Ultra (UHD)",
"label.close": "Zamknij", "label.close": "Zamknij",
"label.media": "Media",
"label.appearance": "Wygląd",
"label.advanced": "Zaawansowane",
"label.addVideo": "Dodaj wideo",
"label.promoteAllPeers": "Wpuść wszystkich",
"label.moreActions": "Więcej akcji",
"label.version": null,
"settings.settings": "Ustawienia", "settings.settings": "Ustawienia",
"settings.camera": "Kamera", "settings.camera": "Kamera",
@ -91,12 +128,30 @@
"settings.audio": "Urządzenie audio", "settings.audio": "Urządzenie audio",
"settings.selectAudio": "Wybór urządzenia audio", "settings.selectAudio": "Wybór urządzenia audio",
"settings.cantSelectAudio": "Nie można wybrać urządzenia audio", "settings.cantSelectAudio": "Nie można wybrać urządzenia audio",
"settings.audioOutput": "Urządzenie wyjściowe audio",
"settings.selectAudioOutput": "Wybierz urządzenie wyjściowe audio",
"settings.cantSelectAudioOutput": "Nie można wybrać urządzenia wyjściowego audio",
"settings.resolution": "Wybór rozdzielczości wideo", "settings.resolution": "Wybór rozdzielczości wideo",
"settings.frameRate": null,
"settings.screenSharingResolution": null,
"settings.screenSharingFrameRate": null,
"settings.layout": "Układ konferencji", "settings.layout": "Układ konferencji",
"settings.selectRoomLayout": "Ustawienia układu konferencji", "settings.selectRoomLayout": "Ustawienia układu konferencji",
"settings.advancedMode": "Tryb zaawansowany", "settings.advancedMode": "Tryb zaawansowany",
"settings.permanentTopBar": "Stały górny pasek", "settings.permanentTopBar": "Stały górny pasek",
"settings.lastn": "Liczba widocznych filmów", "settings.lastn": "Liczba widocznych uczestników (zdalnych)",
"settings.hiddenControls": "Ukryte kontrolki mediów",
"settings.notificationSounds": "Powiadomienia dźwiękiem",
"settings.showNotifications": "Pokaż powiadomienia",
"settings.buttonControlBar": "Rozdziel kontrolki mediów",
"settings.showAdvancedVideo": null,
"settings.showAdvancedAudio": null,
"settings.echoCancellation": "Usuwanie echa",
"settings.autoGainControl": "Auto korekta wzmocnienia",
"settings.noiseSuppression": "Wyciszenie szumów",
"settings.drawerOverlayed": "Szuflada nad zawartością",
"settings.voiceActivatedUnmute": null,
"settings.noiseThreshold": null,
"filesharing.saveFileError": "Nie można zapisać pliku", "filesharing.saveFileError": "Nie można zapisać pliku",
"filesharing.startingFileShare": "Próba udostępnienia pliku", "filesharing.startingFileShare": "Próba udostępnienia pliku",
@ -126,8 +181,8 @@
"devices.microphoneDisconnected": "Odłączono mikrofon", "devices.microphoneDisconnected": "Odłączono mikrofon",
"devices.microphoneError": "Błąd dostępu do mikrofonu", "devices.microphoneError": "Błąd dostępu do mikrofonu",
"devices.microPhoneMute": "Wyciszenie mikrofonu włączone", "devices.microphoneMute": "Wyciszenie mikrofonu włączone",
"devices.micophoneUnMute": "Wyciszenie mikrofonu wyłączone", "devices.microphoneUnMute": "Wyciszenie mikrofonu wyłączone",
"devices.microphoneEnable": "Włączono mikrofon", "devices.microphoneEnable": "Włączono mikrofon",
"devices.microphoneMuteError": "Nie można wyciszyć mikrofonu", "devices.microphoneMuteError": "Nie można wyciszyć mikrofonu",
"devices.microphoneUnMuteError": "Nie można wyłączyć wyciszenia mikrofonu.", "devices.microphoneUnMuteError": "Nie można wyłączyć wyciszenia mikrofonu.",
@ -136,5 +191,15 @@
"devices.screenSharingError": "Wystąpił błąd podczas uzyskiwania dostępu do ekranu", "devices.screenSharingError": "Wystąpił błąd podczas uzyskiwania dostępu do ekranu",
"devices.cameraDisconnected": "Kamera odłączona", "devices.cameraDisconnected": "Kamera odłączona",
"devices.cameraError": "Wystąpił błąd podczas uzyskiwania dostępu do kamery" "devices.cameraError": "Wystąpił błąd podczas uzyskiwania dostępu do kamery",
"moderator.clearChat": "Moderator wyczyścił chat",
"moderator.clearFiles": "Moderator wyczyścił pliki",
"moderator.muteAudio": "Moderator wyciszył audio",
"moderator.muteVideo": "Moderator wyciszył twoje video",
"moderator.stopScreenSharing": null,
"unsupportedBrowser.titleUnsupportedBrowser": null,
"unsupportedBrowser.titlewebrtcUnavailable": null,
"unsupportedBrowser.bodyText": null
} }

View File

@ -49,6 +49,27 @@
"room.spotlights": "Participantes em foco", "room.spotlights": "Participantes em foco",
"room.passive": "Participantes passivos", "room.passive": "Participantes passivos",
"room.videoPaused": "Este vídeo está em pausa", "room.videoPaused": "Este vídeo está em pausa",
"room.muteAll": null,
"room.stopAllVideo": null,
"room.closeMeeting": null,
"room.clearChat": null,
"room.clearFileSharing": null,
"room.speechUnsupported": null,
"room.moderatoractions": null,
"room.raisedHand": null,
"room.loweredHand": null,
"room.extraVideo": null,
"room.overRoomLimit": null,
"room.help": null,
"room.about": null,
"room.shortcutKeys": null,
"room.browsePeersSpotlight": null,
"room.stopAllScreenSharing": null,
"me.mutedPTT": null,
"roles.gotRole": null,
"roles.lostRole": null,
"tooltip.login": "Entrar", "tooltip.login": "Entrar",
"tooltip.logout": "Sair", "tooltip.logout": "Sair",
@ -60,6 +81,14 @@
"tooltip.lobby": "Apresentar sala de espera", "tooltip.lobby": "Apresentar sala de espera",
"tooltip.settings": "Apresentar definições", "tooltip.settings": "Apresentar definições",
"tooltip.participants": "Apresentar participantes", "tooltip.participants": "Apresentar participantes",
"tooltip.kickParticipant": null,
"tooltip.muteParticipant": null,
"tooltip.muteParticipantVideo": null,
"tooltip.raisedHand": null,
"tooltip.muteScreenSharing": null,
"tooltip.muteParticipantAudioModerator": null,
"tooltip.muteParticipantVideoModerator": null,
"tooltip.muteScreenSharingModerator": null,
"label.roomName": "Nome da sala", "label.roomName": "Nome da sala",
"label.chooseRoomButton": "Continuar", "label.chooseRoomButton": "Continuar",
@ -73,6 +102,7 @@
"label.filesharing": "Partilha de ficheiro", "label.filesharing": "Partilha de ficheiro",
"label.participants": "Participantes", "label.participants": "Participantes",
"label.shareFile": "Partilhar ficheiro", "label.shareFile": "Partilhar ficheiro",
"label.shareGalleryFile": null,
"label.fileSharingUnsupported": "Partilha de ficheiro não disponível", "label.fileSharingUnsupported": "Partilha de ficheiro não disponível",
"label.unknown": "Desconhecido", "label.unknown": "Desconhecido",
"label.democratic": "Vista democrática", "label.democratic": "Vista democrática",
@ -83,6 +113,13 @@
"label.veryHigh": "Muito alta (FHD)", "label.veryHigh": "Muito alta (FHD)",
"label.ultra": "Ultra (UHD)", "label.ultra": "Ultra (UHD)",
"label.close": "Fechar", "label.close": "Fechar",
"label.media": null,
"label.appearance": null,
"label.advanced": null,
"label.addVideo": null,
"label.promoteAllPeers": null,
"label.moreActions": null,
"label.version": null,
"settings.settings": "Definições", "settings.settings": "Definições",
"settings.camera": "Camera", "settings.camera": "Camera",
@ -91,12 +128,30 @@
"settings.audio": "Dispositivo Áudio", "settings.audio": "Dispositivo Áudio",
"settings.selectAudio": "Selecione o seu dispositivo de áudio", "settings.selectAudio": "Selecione o seu dispositivo de áudio",
"settings.cantSelectAudio": "Impossível selecionar o seu dispositivo de áudio", "settings.cantSelectAudio": "Impossível selecionar o seu dispositivo de áudio",
"settings.audioOutput": "Dispositivo de saída de áudio",
"settings.selectAudioOutput": "Selecionar dispositivo de saída de áudio",
"settings.cantSelectAudioOutput": "Não foi possível selecionar o dispositivo de saída de áudio",
"settings.resolution": "Selecione a sua resolução de vídeo", "settings.resolution": "Selecione a sua resolução de vídeo",
"settings.frameRate": null,
"settings.screenSharingResolution": null,
"settings.screenSharingFrameRate": null,
"settings.layout": "Disposição da sala", "settings.layout": "Disposição da sala",
"settings.selectRoomLayout": "Seleccione a disposição da sala", "settings.selectRoomLayout": "Seleccione a disposição da sala",
"settings.advancedMode": "Modo avançado", "settings.advancedMode": "Modo avançado",
"settings.permanentTopBar": "Barra superior permanente", "settings.permanentTopBar": "Barra superior permanente",
"settings.lastn": "Número de vídeos visíveis", "settings.lastn": "Número de vídeos visíveis",
"settings.hiddenControls": null,
"settings.notificationSounds": null,
"settings.showNotifications": null,
"settings.buttonControlBar": null,
"settings.showAdvancedVideo": null,
"settings.showAdvancedAudio": null,
"settings.echoCancellation": null,
"settings.autoGainControl": null,
"settings.noiseSuppression": null,
"settings.drawerOverlayed": null,
"settings.voiceActivatedUnmute": null,
"settings.noiseThreshold": null,
"filesharing.saveFileError": "Impossível de gravar o ficheiro", "filesharing.saveFileError": "Impossível de gravar o ficheiro",
"filesharing.startingFileShare": "Tentando partilha de ficheiro", "filesharing.startingFileShare": "Tentando partilha de ficheiro",
@ -126,8 +181,8 @@
"devices.microphoneDisconnected": "Microfone desiligado", "devices.microphoneDisconnected": "Microfone desiligado",
"devices.microphoneError": "Ocorreu um erro no acesso ao microfone", "devices.microphoneError": "Ocorreu um erro no acesso ao microfone",
"devices.microPhoneMute": "Som microfone desativado", "devices.microphoneMute": "Som microfone desativado",
"devices.micophoneUnMute": "Som mmicrofone ativado", "devices.microphoneUnMute": "Som mmicrofone ativado",
"devices.microphoneEnable": "Microfone ativado", "devices.microphoneEnable": "Microfone ativado",
"devices.microphoneMuteError": "Não foi possível cortar o som do microfone", "devices.microphoneMuteError": "Não foi possível cortar o som do microfone",
"devices.microphoneUnMuteError": "Não foi possível ativar o som do microfone", "devices.microphoneUnMuteError": "Não foi possível ativar o som do microfone",
@ -136,5 +191,15 @@
"devices.screenSharingError": "Ocorreu um erro no acesso ao seu ecrã", "devices.screenSharingError": "Ocorreu um erro no acesso ao seu ecrã",
"devices.cameraDisconnected": "Câmara desconectada", "devices.cameraDisconnected": "Câmara desconectada",
"devices.cameraError": "Ocorreu um erro no acesso à sua câmara" "devices.cameraError": "Ocorreu um erro no acesso à sua câmara",
"moderator.clearChat": null,
"moderator.clearFiles": null,
"moderator.muteAudio": null,
"moderator.muteVideo": null,
"moderator.stopScreenSharing": null,
"unsupportedBrowser.titleUnsupportedBrowser": null,
"unsupportedBrowser.titlewebrtcUnavailable": null,
"unsupportedBrowser.bodyText": null
} }

View File

@ -49,6 +49,27 @@
"room.spotlights": "Participanți în Spotlight", "room.spotlights": "Participanți în Spotlight",
"room.passive": "Participanți pasivi", "room.passive": "Participanți pasivi",
"room.videoPaused": "Acest video este pus pe pauză", "room.videoPaused": "Acest video este pus pe pauză",
"room.muteAll": null,
"room.stopAllVideo": null,
"room.closeMeeting": null,
"room.clearChat": null,
"room.clearFileSharing": null,
"room.speechUnsupported": null,
"room.moderatoractions": null,
"room.raisedHand": null,
"room.loweredHand": null,
"room.extraVideo": null,
"room.overRoomLimit": null,
"room.help": null,
"room.about": null,
"room.shortcutKeys": null,
"room.browsePeersSpotlight": null,
"room.stopAllScreenSharing": null,
"me.mutedPTT": null,
"roles.gotRole": null,
"roles.lostRole": null,
"tooltip.login": "Intră în cont", "tooltip.login": "Intră în cont",
"tooltip.logout": "Deconectare", "tooltip.logout": "Deconectare",
@ -58,7 +79,16 @@
"tooltip.enterFullscreen": "Modul ecran complet", "tooltip.enterFullscreen": "Modul ecran complet",
"tooltip.leaveFullscreen": "Ieșire din modul ecran complet", "tooltip.leaveFullscreen": "Ieșire din modul ecran complet",
"tooltip.lobby": "Arată holul", "tooltip.lobby": "Arată holul",
"tooltip.settings": "Arată participanții", "tooltip.settings": "Arată setăile",
"tooltip.participants": null,
"tooltip.kickParticipant": null,
"tooltip.muteParticipant": null,
"tooltip.muteParticipantVideo": null,
"tooltip.raisedHand": null,
"tooltip.muteScreenSharing": null,
"tooltip.muteParticipantAudioModerator": null,
"tooltip.muteParticipantVideoModerator": null,
"tooltip.muteScreenSharingModerator": null,
"label.roomName": "Numele camerei", "label.roomName": "Numele camerei",
"label.chooseRoomButton": "Continuare", "label.chooseRoomButton": "Continuare",
@ -72,6 +102,7 @@
"label.filesharing": "Partajarea fișierelor", "label.filesharing": "Partajarea fișierelor",
"label.participants": "Participanți", "label.participants": "Participanți",
"label.shareFile": "Partajează fișierul", "label.shareFile": "Partajează fișierul",
"label.shareGalleryFile": null,
"label.fileSharingUnsupported": "Partajarea fișierelor nu este acceptată", "label.fileSharingUnsupported": "Partajarea fișierelor nu este acceptată",
"label.unknown": "Necunoscut", "label.unknown": "Necunoscut",
"label.democratic": "Distribuție egală a dimensiunii imaginii", "label.democratic": "Distribuție egală a dimensiunii imaginii",
@ -82,6 +113,13 @@
"label.veryHigh": "Rezoluție foarte înaltă (FHD)", "label.veryHigh": "Rezoluție foarte înaltă (FHD)",
"label.ultra": "Rezoluție ultra înaltă (UHD)", "label.ultra": "Rezoluție ultra înaltă (UHD)",
"label.close": "Închide", "label.close": "Închide",
"label.media": null,
"label.appearance": null,
"label.advanced": null,
"label.addVideo": null,
"label.promoteAllPeers": null,
"label.moreActions": null,
"label.version": null,
"settings.settings": "Setări", "settings.settings": "Setări",
"settings.camera": "Cameră video", "settings.camera": "Cameră video",
@ -90,12 +128,30 @@
"settings.audio": "Dispozitivul audio", "settings.audio": "Dispozitivul audio",
"settings.selectAudio": "Selectarea dispozitivul audio", "settings.selectAudio": "Selectarea dispozitivul audio",
"settings.cantSelectAudio": "Încercarea de a selecta dispozitivul audio a eșuat", "settings.cantSelectAudio": "Încercarea de a selecta dispozitivul audio a eșuat",
"settings.audioOutput": "Dispozitiv de ieșire audio",
"settings.selectAudioOutput": "Selectați dispozitivul de ieșire audio",
"settings.cantSelectAudioOutput": "Imposibil de selectat dispozitivul de ieșire audio",
"settings.resolution": "Selectează rezoluția video", "settings.resolution": "Selectează rezoluția video",
"settings.frameRate": null,
"settings.screenSharingResolution": null,
"settings.screenSharingFrameRate": null,
"settings.layout": "Aspectul camerei video", "settings.layout": "Aspectul camerei video",
"settings.selectRoomLayout": "Selectează spectul camerei video", "settings.selectRoomLayout": "Selectează spectul camerei video",
"settings.advancedMode": "Mod avansat", "settings.advancedMode": "Mod avansat",
"settings.permanentTopBar": "Bara de sus permanentă", "settings.permanentTopBar": "Bara de sus permanentă",
"settings.lastn": "Numărul de videoclipuri vizibile", "settings.lastn": "Numărul de videoclipuri vizibile",
"settings.hiddenControls": null,
"settings.notificationSounds": null,
"settings.showNotifications": null,
"settings.buttonControlBar": null,
"settings.showAdvancedVideo": null,
"settings.showAdvancedAudio": null,
"settings.echoCancellation": null,
"settings.autoGainControl": null,
"settings.noiseSuppression": null,
"settings.drawerOverlayed": null,
"settings.voiceActivatedUnmute": null,
"settings.noiseThreshold": null,
"filesharing.saveFileError": "Încercarea de a salva fișierul a eșuat", "filesharing.saveFileError": "Încercarea de a salva fișierul a eșuat",
"filesharing.startingFileShare": "Partajarea fișierului", "filesharing.startingFileShare": "Partajarea fișierului",
@ -125,8 +181,8 @@
"devices.microphoneDisconnected": "Microfonul e deconectat", "devices.microphoneDisconnected": "Microfonul e deconectat",
"devices.microphoneError": "A apărut o eroare la accesarea microfonului", "devices.microphoneError": "A apărut o eroare la accesarea microfonului",
"devices.microPhoneMute": "Microfonul e dezactivat", "devices.microphoneMute": "Microfonul e dezactivat",
"devices.micophoneUnMute": "Retragerea dezactivării microfonului", "devices.microphoneUnMute": "Retragerea dezactivării microfonului",
"devices.microphoneEnable": "Microfonul e activat", "devices.microphoneEnable": "Microfonul e activat",
"devices.microphoneMuteError": "Încercarea de a dezactiva microfonului a eșuat", "devices.microphoneMuteError": "Încercarea de a dezactiva microfonului a eșuat",
"devices.microphoneUnMuteError": "Încercarea de a retrage dezactivarea microfonului a eșuat", "devices.microphoneUnMuteError": "Încercarea de a retrage dezactivarea microfonului a eșuat",
@ -135,5 +191,15 @@
"devices.screenSharingError": "A apărut o eroare la accesarea ecranului", "devices.screenSharingError": "A apărut o eroare la accesarea ecranului",
"devices.cameraDisconnected": "Camera video e disconectată", "devices.cameraDisconnected": "Camera video e disconectată",
"devices.cameraError": "A apărut o eroare la accesarea camerei video" "devices.cameraError": "A apărut o eroare la accesarea camerei video",
"moderator.clearChat": null,
"moderator.clearFiles": null,
"moderator.muteAudio": null,
"moderator.muteVideo": null,
"moderator.stopScreenSharing": null,
"unsupportedBrowser.titleUnsupportedBrowser": null,
"unsupportedBrowser.titlewebrtcUnavailable": null,
"unsupportedBrowser.bodyText": null
} }

View File

@ -49,6 +49,27 @@
"room.spotlights": "Gündemdeki Katılımcılar", "room.spotlights": "Gündemdeki Katılımcılar",
"room.passive": "Pasif Katılımcılar", "room.passive": "Pasif Katılımcılar",
"room.videoPaused": "Video duraklatıldı", "room.videoPaused": "Video duraklatıldı",
"room.muteAll": null,
"room.stopAllVideo": null,
"room.closeMeeting": null,
"room.clearChat": null,
"room.clearFileSharing": null,
"room.speechUnsupported": null,
"room.moderatoractions": null,
"room.raisedHand": null,
"room.loweredHand": null,
"room.extraVideo": null,
"room.overRoomLimit": null,
"room.help": null,
"room.about": null,
"room.shortcutKeys": null,
"room.browsePeersSpotlight": null,
"room.stopAllScreenSharing": null,
"me.mutedPTT": null,
"roles.gotRole": null,
"roles.lostRole": null,
"tooltip.login": "Giriş", "tooltip.login": "Giriş",
"tooltip.logout": ıkış", "tooltip.logout": ıkış",
@ -60,6 +81,14 @@
"tooltip.lobby": "Lobiyi göster", "tooltip.lobby": "Lobiyi göster",
"tooltip.settings": "Ayarları göster", "tooltip.settings": "Ayarları göster",
"tooltip.participants": "Katılımcıları göster", "tooltip.participants": "Katılımcıları göster",
"tooltip.kickParticipant": null,
"tooltip.muteParticipant": null,
"tooltip.muteParticipantVideo": null,
"tooltip.raisedHand": null,
"tooltip.muteScreenSharing": null,
"tooltip.muteParticipantAudioModerator": null,
"tooltip.muteParticipantVideoModerator": null,
"tooltip.muteScreenSharingModerator": null,
"label.roomName": "Oda adı", "label.roomName": "Oda adı",
"label.chooseRoomButton": "Devam", "label.chooseRoomButton": "Devam",
@ -73,6 +102,7 @@
"label.filesharing": "Dosya paylaşım", "label.filesharing": "Dosya paylaşım",
"label.participants": "Katılımcı", "label.participants": "Katılımcı",
"label.shareFile": "Dosya paylaş", "label.shareFile": "Dosya paylaş",
"label.shareGalleryFile": null,
"label.fileSharingUnsupported": "Dosya paylaşımı desteklenmiyor", "label.fileSharingUnsupported": "Dosya paylaşımı desteklenmiyor",
"label.unknown": "Bilinmeyen", "label.unknown": "Bilinmeyen",
"label.democratic": "Demokratik görünüm", "label.democratic": "Demokratik görünüm",
@ -83,6 +113,13 @@
"label.veryHigh": "Çok Yüksek (FHD)", "label.veryHigh": "Çok Yüksek (FHD)",
"label.ultra": "Ultra (UHD)", "label.ultra": "Ultra (UHD)",
"label.close": "Kapat", "label.close": "Kapat",
"label.media": null,
"label.appearance": null,
"label.advanced": null,
"label.addVideo": null,
"label.promoteAllPeers": null,
"label.moreActions": null,
"label.version": null,
"settings.settings": "Ayarlar", "settings.settings": "Ayarlar",
"settings.camera": "Kamera", "settings.camera": "Kamera",
@ -92,11 +129,26 @@
"settings.selectAudio": "Ses aygıtını seç", "settings.selectAudio": "Ses aygıtını seç",
"settings.cantSelectAudio": "Ses aygıtı seçilemiyor", "settings.cantSelectAudio": "Ses aygıtı seçilemiyor",
"settings.resolution": "Video çözünürlüğü ayarla", "settings.resolution": "Video çözünürlüğü ayarla",
"settings.frameRate": null,
"settings.screenSharingResolution": null,
"settings.screenSharingFrameRate": null,
"settings.layout": "Oda düzeni", "settings.layout": "Oda düzeni",
"settings.selectRoomLayout": "Oda düzeni seç", "settings.selectRoomLayout": "Oda düzeni seç",
"settings.advancedMode": "Detaylı mod", "settings.advancedMode": "Detaylı mod",
"settings.permanentTopBar": "Üst barı kalıcı yap", "settings.permanentTopBar": "Üst barı kalıcı yap",
"settings.lastn": "İzlenebilir video sayısı", "settings.lastn": "İzlenebilir video sayısı",
"settings.hiddenControls": null,
"settings.notificationSounds": null,
"settings.showNotifications": null,
"settings.buttonControlBar": null,
"settings.showAdvancedVideo": null,
"settings.showAdvancedAudio": null,
"settings.echoCancellation": null,
"settings.autoGainControl": null,
"settings.noiseSuppression": null,
"settings.drawerOverlayed": null,
"settings.voiceActivatedUnmute": null,
"settings.noiseThreshold": null,
"filesharing.saveFileError": "Dosya kaydedilemiyor", "filesharing.saveFileError": "Dosya kaydedilemiyor",
"filesharing.startingFileShare": "Paylaşılan dosyaya erişiliyor", "filesharing.startingFileShare": "Paylaşılan dosyaya erişiliyor",
@ -136,5 +188,15 @@
"devices.screenSharingError": "Ekranınıza erişilirken bir hata oluştu", "devices.screenSharingError": "Ekranınıza erişilirken bir hata oluştu",
"devices.cameraDisconnected": "Kamera bağlı değil", "devices.cameraDisconnected": "Kamera bağlı değil",
"devices.cameraError": "Kameranıza erişilirken bir hata oluştu" "devices.cameraError": "Kameranıza erişilirken bir hata oluştu",
"moderator.clearChat": null,
"moderator.clearFiles": null,
"moderator.muteAudio": null,
"moderator.muteVideo": null,
"moderator.stopScreenSharing": null,
"unsupportedBrowser.titleUnsupportedBrowser": null,
"unsupportedBrowser.titlewebrtcUnavailable": null,
"unsupportedBrowser.bodyText": null
} }

View File

@ -0,0 +1,204 @@
{
"socket.disconnected": "您已斷開連接",
"socket.reconnecting": "嘗試重新連接",
"socket.reconnected": "您已重新連接",
"socket.requestError": "服務器請求錯誤",
"room.chooseRoom": "選擇您要加入的房間的名稱",
"room.cookieConsent": "這個網站使用Cookies來提升您的使用者體驗",
"room.consentUnderstand": "了解",
"room.joined": "您已加入房間",
"room.cantJoin": "無法加入房間",
"room.youLocked": "您已鎖定房間",
"room.cantLock": "無法鎖定房間",
"room.youUnLocked": "您解鎖了房間",
"room.cantUnLock": "無法解鎖房間",
"room.locked": "房間已鎖定",
"room.unlocked": "房間現已解鎖",
"room.newLobbyPeer": "新參與者進入大廳",
"room.lobbyPeerLeft": "參與者離開大廳",
"room.lobbyPeerChangedDisplayName": "大廳的參與者將名稱變更為 {displayName}",
"room.lobbyPeerChangedPicture": "大廳的參與者變更了圖片",
"room.setAccessCode": "設置房間的進入密碼",
"room.accessCodeOn": "房間的進入密碼現已啟用",
"room.accessCodeOff": "房間的進入密碼已停用",
"room.peerChangedDisplayName": "{oldDisplayName} 已變更名稱為 {displayName}",
"room.newPeer": "{displayName} 加入了會議室",
"room.newFile": "有新文件",
"room.toggleAdvancedMode": "切換進階模式",
"room.setDemocraticView": "已更改為使用者佈局",
"room.setFilmStripView": "已更改為投影片佈局",
"room.loggedIn": "您已登入",
"room.loggedOut": "您已登出",
"room.changedDisplayName": "您的顯示名稱已變更為 {displayName}",
"room.changeDisplayNameError": "更改顯示名稱時發生錯誤",
"room.chatError": "無法發送聊天消息",
"room.aboutToJoin": "您即將參加會議",
"room.roomId": "房間ID: {roomName}",
"room.setYourName": "設置您的顯示名稱,並選擇您想加入的方式:",
"room.audioOnly": "僅通話",
"room.audioVideo": "通話和視訊",
"room.youAreReady": "準備完畢!",
"room.emptyRequireLogin": "房間是空的! 您可以登錄以開始會議或等待主持人加入",
"room.locketWait": "房間已鎖定! 請等待其他人允許您進入...",
"room.lobbyAdministration": "大廳管理",
"room.peersInLobby": "大廳的參與者",
"room.lobbyEmpty": "大廳目前沒有人",
"room.hiddenPeers": "{hiddenPeersCount, plural, one {participant} other {participants}}",
"room.me": "我",
"room.spotlights": "Spotlight中的參與者",
"room.passive": "被動參與者",
"room.videoPaused": "視訊已關閉",
"room.muteAll": "全部靜音",
"room.stopAllVideo": "關閉全部視訊",
"room.closeMeeting": "關閉會議",
"room.clearChat": "清除聊天",
"room.clearFileSharing": "清除檔案",
"room.speechUnsupported": "您的瀏覽器不支援語音辨識",
"room.moderatoractions": "管理員動作",
"room.raisedHand": "{displayName} 舉手了",
"room.loweredHand": "{displayName} 放下了他的手",
"room.extraVideo": "其他視訊",
"room.overRoomLimit": "房間已滿,請稍後重試",
"room.help": "幫助",
"room.about": "關於",
"room.shortcutKeys": "鍵盤快速鍵",
"room.stopAllScreenSharing": null,
"me.mutedPTT": "您已靜音,請按下 空白鍵 來說話",
"roles.gotRole": "您已取得身份: {role}",
"roles.lostRole": "您的 {role} 身份已被撤銷",
"tooltip.login": "登入",
"tooltip.logout": "登出",
"tooltip.admitFromLobby": "從大廳允許",
"tooltip.lockRoom": "鎖定房間",
"tooltip.unLockRoom": "解鎖房間",
"tooltip.enterFullscreen": "進入全螢幕",
"tooltip.leaveFullscreen": "退出全螢幕",
"tooltip.lobby": "顯示大廳",
"tooltip.settings": "顯示設置",
"tooltip.participants": "顯示參加者",
"tooltip.kickParticipant": "踢出",
"tooltip.muteParticipant": "靜音",
"tooltip.muteParticipantVideo": "隱藏視訊",
"tooltip.raisedHand": "舉手",
"tooltip.muteScreenSharing": "隱藏螢幕分享",
"tooltip.muteParticipantAudioModerator": "關閉聲音",
"tooltip.muteParticipantVideoModerator": "關閉視訊",
"tooltip.muteScreenSharingModerator": "關閉螢幕分享",
"label.roomName": "房間名稱",
"label.chooseRoomButton": "繼續",
"label.yourName": "您的名字",
"label.newWindow": "新視窗",
"label.fullscreen": "全螢幕",
"label.openDrawer": "打開側邊欄",
"label.leave": "離開",
"label.chatInput": "輸入聊天訊息",
"label.chat": "聊天",
"label.filesharing": "文件分享",
"label.participants": "參與者",
"label.shareFile": "分享文件",
"label.shareGalleryFile": "分享圖片",
"label.fileSharingUnsupported": "不支援文件分享",
"label.unknown": "未知",
"label.democratic": "使用者佈局",
"label.filmstrip": "投影片佈局",
"label.low": "低",
"label.medium": "中",
"label.high": "高 (HD)",
"label.veryHigh": "非常高 (FHD)",
"label.ultra": "超高 (UHD)",
"label.close": "關閉",
"label.media": "媒體",
"label.appearance": "外觀",
"label.advanced": "進階",
"label.addVideo": "新增視訊",
"label.promoteAllPeers": "提升全部",
"label.moreActions": "更多",
"label.version": null,
"settings.settings": "設置",
"settings.camera": "視訊來源",
"settings.selectCamera": "選擇視訊來源",
"settings.cantSelectCamera": "無法選擇此視訊來源",
"settings.audio": "音訊來源",
"settings.selectAudio": "選擇音訊來源",
"settings.cantSelectAudio": "無法選擇音訊來源",
"settings.audioOutput": "音訊輸出",
"settings.selectAudioOutput": "選擇音訊輸出設備",
"settings.cantSelectAudioOutput": "無法選擇音訊輸出設備",
"settings.resolution": "選擇視訊解析度",
"settings.frameRate": null,
"settings.screenSharingResolution": null,
"settings.screenSharingFrameRate": null,
"settings.layout": "房間佈局",
"settings.selectRoomLayout": "選擇房間佈局",
"settings.advancedMode": "進階模式",
"settings.permanentTopBar": "固定頂端列",
"settings.lastn": "視訊數量上限",
"settings.hiddenControls": "隱藏控制按鈕",
"settings.notificationSounds": "通知音效",
"settings.showNotifications": "顯示通知",
"settings.buttonControlBar": "獨立控制按鈕",
"settings.showAdvancedVideo": null,
"settings.showAdvancedAudio": null,
"settings.echoCancellation": "回音消除",
"settings.autoGainControl": "自動增益控制",
"settings.noiseSuppression": "噪音消除",
"settings.drawerOverlayed": "側邊欄覆蓋畫面",
"settings.voiceActivatedUnmute": null,
"settings.noiseThreshold": null,
"filesharing.saveFileError": "無法保存文件",
"filesharing.startingFileShare": "開始分享文件",
"filesharing.successfulFileShare": "文件已成功分享",
"filesharing.unableToShare": "無法分享文件",
"filesharing.error": "文件分享發生錯誤",
"filesharing.finished": "文件分享成功",
"filesharing.save": "保存文件",
"filesharing.sharedFile": "{displayName} 分享了一個文件",
"filesharing.download": "下載文件",
"filesharing.missingSeeds": "如果過了很久還是無法下載,則可能沒有人播種了。請讓上傳者重新上傳您想要的文件。",
"devices.devicesChanged": "您的設備已更改,請在設置中設定您的設備",
"device.audioUnsupported": "不支援您的音訊格式",
"device.activateAudio": "開啟音訊",
"device.muteAudio": "靜音",
"device.unMuteAudio": "取消靜音",
"device.videoUnsupported": "不支援您的視訊格式",
"device.startVideo": "開啟視訊",
"device.stopVideo": "關閉視訊",
"device.screenSharingUnsupported": "不支援您的螢幕分享格式",
"device.startScreenSharing": "開始螢幕分享",
"device.stopScreenSharing": "停止螢幕分享",
"devices.microphoneDisconnected": "麥克風已斷開",
"devices.microphoneError": "麥克風發生錯誤",
"devices.microphoneMute": "麥克風靜音",
"devices.microphoneUnMute": "取消麥克風靜音",
"devices.microphoneEnable": "麥克風已啟用",
"devices.microphoneMuteError": "無法使麥克風靜音",
"devices.microphoneUnMuteError": "無法取消麥克風靜音",
"devices.screenSharingDisconnected" : "螢幕分享已斷開",
"devices.screenSharingError": "螢幕分享時發生錯誤",
"devices.cameraDisconnected": "相機已斷開連接",
"devices.cameraError": "存取相機時發生錯誤",
"moderator.clearChat": "管理員清除了聊天",
"moderator.clearFiles": "管理員清除了所有檔案",
"moderator.muteAudio": "您已被管理員靜音",
"moderator.muteVideo": "您的視訊已被管理員關閉",
"moderator.stopScreenSharing": null,
"unsupportedBrowser.titleUnsupportedBrowser": null,
"unsupportedBrowser.titlewebrtcUnavailable": null,
"unsupportedBrowser.bodyText": null
}

View File

@ -49,6 +49,27 @@
"room.spotlights": "Учасники у центрі уваги", "room.spotlights": "Учасники у центрі уваги",
"room.passive": "Пасивні учасники", "room.passive": "Пасивні учасники",
"room.videoPaused": "Це відео призупинено", "room.videoPaused": "Це відео призупинено",
"room.muteAll": null,
"room.stopAllVideo": null,
"room.closeMeeting": null,
"room.clearChat": null,
"room.clearFileSharing": null,
"room.speechUnsupported": null,
"room.moderatoractions": null,
"room.raisedHand": null,
"room.loweredHand": null,
"room.extraVideo": null,
"room.overRoomLimit": null,
"room.help": null,
"room.about": null,
"room.shortcutKeys": null,
"room.browsePeersSpotlight": null,
"room.stopAllScreenSharing": null,
"me.mutedPTT": null,
"roles.gotRole": null,
"roles.lostRole": null,
"tooltip.login": "Увійти", "tooltip.login": "Увійти",
"tooltip.logout": "Вихід", "tooltip.logout": "Вихід",
@ -60,6 +81,14 @@
"tooltip.lobby": "Показати зал очікувань", "tooltip.lobby": "Показати зал очікувань",
"tooltip.settings": "Показати налаштування", "tooltip.settings": "Показати налаштування",
"tooltip.participants": "Показати учасників", "tooltip.participants": "Показати учасників",
"tooltip.kickParticipant": null,
"tooltip.muteParticipant": null,
"tooltip.muteParticipantVideo": null,
"tooltip.raisedHand": null,
"tooltip.muteScreenSharing": null,
"tooltip.muteParticipantAudioModerator": null,
"tooltip.muteParticipantVideoModerator": null,
"tooltip.muteScreenSharingModerator": null,
"label.roomName": "Назва кімнати", "label.roomName": "Назва кімнати",
"label.chooseRoomButton": "Продовжити", "label.chooseRoomButton": "Продовжити",
@ -73,6 +102,7 @@
"label.filesharing": "Обмін файлами", "label.filesharing": "Обмін файлами",
"label.participants": "Учасники", "label.participants": "Учасники",
"label.shareFile": "Надіслати файл", "label.shareFile": "Надіслати файл",
"label.shareGalleryFile": null,
"label.fileSharingUnsupported": "Обмін файлами не підтримується", "label.fileSharingUnsupported": "Обмін файлами не підтримується",
"label.unknown": "Невідомо", "label.unknown": "Невідомо",
"label.democrat": "Демократичний вигляд", "label.democrat": "Демократичний вигляд",
@ -83,6 +113,13 @@
"label.veryHigh": "Дуже високий (FHD)", "label.veryHigh": "Дуже високий (FHD)",
"label.ultra": "Ультра (UHD)", "label.ultra": "Ультра (UHD)",
"label.close": "Закрити", "label.close": "Закрити",
"label.media": null,
"label.appearance": null,
"label.advanced": null,
"label.addVideo": null,
"label.promoteAllPeers": null,
"label.moreActions": null,
"label.version": null,
"settings.settings": "Налаштування", "settings.settings": "Налаштування",
"settings.camera": "Камера", "settings.camera": "Камера",
@ -91,12 +128,30 @@
"settings.audio": "Аудіопристрій", "settings.audio": "Аудіопристрій",
"settings.selectAudio": "Вибрати аудіопристрій", "settings.selectAudio": "Вибрати аудіопристрій",
"settings.cantSelectAudio": "Неможливо вибрати аудіопристрій", "settings.cantSelectAudio": "Неможливо вибрати аудіопристрій",
"settings.audioOutput": "Пристрій аудіовиходу",
"settings.selectAudioOutput": "Виберіть пристрій аудіовиходу",
"settings.cantSelectAudioOutput": "Неможливо вибрати аудіо вихідний пристрій",
"settings.resolution": "Виберіть роздільну здатність відео", "settings.resolution": "Виберіть роздільну здатність відео",
"settings.frameRate": null,
"settings.screenSharingResolution": null,
"settings.screenSharingFrameRate": null,
"settings.layout": "Розміщення кімнати", "settings.layout": "Розміщення кімнати",
"settings.selectRoomLayout": "Вибір розташування кімнати", "settings.selectRoomLayout": "Вибір розташування кімнати",
"settings.advancedMode": "Розширений режим", "settings.advancedMode": "Розширений режим",
"settings.permanentTopBar": "Постійний верхній рядок", "settings.permanentTopBar": "Постійний верхній рядок",
"settings.lastn": "Кількість видимих ​​відео", "settings.lastn": "Кількість видимих ​​відео",
"settings.hiddenControls": null,
"settings.notificationSounds": null,
"settings.showNotifications": null,
"settings.buttonControlBar": null,
"settings.showAdvancedVideo": null,
"settings.showAdvancedAudio": null,
"settings.echoCancellation": null,
"settings.autoGainControl": null,
"settings.noiseSuppression": null,
"settings.drawerOverlayed": null,
"settings.voiceActivatedUnmute": null,
"settings.noiseThreshold": null,
"filesharing.saveFileError": "Неможливо зберегти файл", "filesharing.saveFileError": "Неможливо зберегти файл",
"filesharing.startingFileShare": "Спроба поділитися файлом", "filesharing.startingFileShare": "Спроба поділитися файлом",
@ -136,5 +191,15 @@
"devices.screenSharingError": "Сталася помилка під час доступу до екрану", "devices.screenSharingError": "Сталася помилка під час доступу до екрану",
"devices.cameraDisconnected": "Камера відключена", "devices.cameraDisconnected": "Камера відключена",
"devices.cameraError": "Під час доступу до камери сталася помилка" "devices.cameraError": "Під час доступу до камери сталася помилка",
"moderator.clearChat": null,
"moderator.clearFiles": null,
"moderator.muteAudio": null,
"moderator.muteVideo": null,
"moderator.stopScreenSharing": null,
"unsupportedBrowser.titleUnsupportedBrowser": null,
"unsupportedBrowser.titlewebrtcUnavailable": null,
"unsupportedBrowser.bodyText": null
} }

View File

@ -1,14 +1,12 @@
export function getSignalingUrl(peerId, roomId) export function getSignalingUrl(peerId, roomId)
{ {
const hostname = window.config.multipartyServer;
const port = const port =
process.env.NODE_ENV !== 'production' ? process.env.NODE_ENV !== 'production' ?
window.config.developmentPort window.config.developmentPort
: :
window.config.productionPort; window.config.productionPort;
const url = `wss://${hostname}:${port}/?peerId=${peerId}&roomId=${roomId}`; const url = `wss://${window.location.hostname}:${port}/?peerId=${peerId}&roomId=${roomId}`;
return url; return url;
} }

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