mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2026-01-09 00:00:19 +00:00
Compare commits
331 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a2b43843b7 | ||
|
|
2f03a0a7fe | ||
|
|
4c2f0d3600 | ||
|
|
a8a0945d73 | ||
|
|
a7048fba06 | ||
|
|
7b35dd89bb | ||
|
|
3561204bb5 | ||
|
|
ee50d07dc3 | ||
|
|
9ec4bc91fc | ||
|
|
88071e5258 | ||
|
|
e79d476d89 | ||
|
|
0fe4999beb | ||
|
|
ae96b9f365 | ||
|
|
922d0bd512 | ||
|
|
9a7bc4ebab | ||
|
|
2081757ba1 | ||
|
|
e9c9fc5e69 | ||
|
|
562761196d | ||
|
|
420514b921 | ||
|
|
eb63b24a9a | ||
|
|
c8bbded994 | ||
|
|
2a2702c13a | ||
|
|
5fc868ee96 | ||
|
|
502eab7278 | ||
|
|
332aafbe20 | ||
|
|
d5258e6197 | ||
|
|
9cc9e6132c | ||
|
|
f60c1d9751 | ||
|
|
5d32318d93 | ||
|
|
fee8482bae | ||
|
|
f2b5cdbfb8 | ||
|
|
60afe2d202 | ||
|
|
18f03e296b | ||
|
|
5cd9db1b6a | ||
|
|
f83404a99e | ||
|
|
7c1ba9242b | ||
|
|
bfcc587047 | ||
|
|
e90d8f5531 | ||
|
|
59033aab28 | ||
|
|
7f1eb617c3 | ||
|
|
fd7e8c9162 | ||
|
|
51e886142b | ||
|
|
dcc206b2b4 | ||
|
|
da75e17ff5 | ||
|
|
8fea9b76ee | ||
|
|
cb024be2d6 | ||
|
|
4c4e99c51a | ||
|
|
4b8bc398dd | ||
|
|
466e7dcc91 | ||
|
|
de30ce0f5c | ||
|
|
fc6f5717cb | ||
|
|
b680ecd2ff | ||
|
|
2bea2eec74 | ||
|
|
f52b1380ee | ||
|
|
baf720c553 | ||
|
|
deaff6af5b | ||
|
|
6ca1e131af | ||
|
|
57b9aeb38c | ||
|
|
cc20a4d776 | ||
|
|
fd404b8465 | ||
|
|
cc29df6376 | ||
|
|
44136e8a55 | ||
|
|
fb875423a9 | ||
|
|
ab4c29eddc | ||
|
|
95e964a089 | ||
|
|
c288aa6e84 | ||
|
|
e5d03d1d11 | ||
|
|
59147f059d | ||
|
|
7793d65a99 | ||
|
|
b77791f4b2 | ||
|
|
4092d67853 | ||
|
|
2ea6be9b2c | ||
|
|
74e7507a73 | ||
|
|
9a31fa3d63 | ||
|
|
fd44cfa7a0 | ||
|
|
ab570d63fa | ||
|
|
b4983b2566 | ||
|
|
fdb470d22f | ||
|
|
c163a22415 | ||
|
|
1dea41d3d4 | ||
|
|
9d321df49e | ||
|
|
d92d8e8299 | ||
|
|
6b48bf0d84 | ||
|
|
de82a8e32b | ||
|
|
fe4661078e | ||
|
|
46554f75a2 | ||
|
|
dbd68d2daa | ||
|
|
67a52e6f72 | ||
|
|
2c790f86ad | ||
|
|
fd5a739f3c | ||
|
|
35e46a2cfa | ||
|
|
24f9a1c8d1 | ||
|
|
71229bdba9 | ||
|
|
84a8d00234 | ||
|
|
ee95e99f57 | ||
|
|
be6d7af377 | ||
|
|
b8548757b4 | ||
|
|
df932bb89f | ||
|
|
50e67a0658 | ||
|
|
3289f6f68e | ||
|
|
a701821698 | ||
|
|
5588bcd167 | ||
|
|
a7058747ac | ||
|
|
5017fed28e | ||
|
|
8469a282c1 | ||
|
|
2ffe178456 | ||
|
|
349f196664 | ||
|
|
f2bd76ac93 | ||
|
|
baee96734c | ||
|
|
4cac7ac97f | ||
|
|
46a17948d0 | ||
|
|
79ac1e800f | ||
|
|
b0c81985d4 | ||
|
|
f8b7d048b0 | ||
|
|
dc19620edc | ||
|
|
4aa6fbc4b9 | ||
|
|
03902de511 | ||
|
|
d44aed2c11 | ||
|
|
9bbf17e541 | ||
|
|
156b218bfc | ||
|
|
ca5a1001a1 | ||
|
|
1010a56899 | ||
|
|
0902cbb164 | ||
|
|
9b4ee7c0c5 | ||
|
|
2545441def | ||
|
|
9206b470ef | ||
|
|
cb7ff545b6 | ||
|
|
c149b22ac2 | ||
|
|
2d522f735e | ||
|
|
bc935eb5dc | ||
|
|
c3548eb866 | ||
|
|
a66459e206 | ||
|
|
829d8dab16 | ||
|
|
072905b7cd | ||
|
|
7e1c7d19d6 | ||
|
|
a3886cc56c | ||
|
|
b22ddea71c | ||
|
|
b5135c455e | ||
|
|
7776bc92a4 | ||
|
|
420bbe136c | ||
|
|
2b103288c2 | ||
|
|
17f245df5e | ||
|
|
61f4674a28 | ||
|
|
879fb9a590 | ||
|
|
a1b0677442 | ||
|
|
d74a356a40 | ||
|
|
609029bc93 | ||
|
|
f29fc0f8e7 | ||
|
|
d430074ed8 | ||
|
|
ae759fab5b | ||
|
|
cd6928d770 | ||
|
|
94f2ace120 | ||
|
|
6ec7be09f2 | ||
|
|
5c136f1da6 | ||
|
|
9268da65c2 | ||
|
|
3fc6da1ed5 | ||
|
|
c1226d8c07 | ||
|
|
62a731e244 | ||
|
|
47b6355d66 | ||
|
|
1e0bf42203 | ||
|
|
6235ff163e | ||
|
|
f11c3b94ed | ||
|
|
dd8371f49c | ||
|
|
506312ac95 | ||
|
|
239f271caf | ||
|
|
7a54537bee | ||
|
|
6a492d96c2 | ||
|
|
38b180ad81 | ||
|
|
c8c0c8b1ab | ||
|
|
2b692f5a0a | ||
|
|
502aeca132 | ||
|
|
288fbff677 | ||
|
|
bdca07be17 | ||
|
|
5f48e4cf9d | ||
|
|
bff9648abc | ||
|
|
7874e09a7e | ||
|
|
311df7ec7f | ||
|
|
93648f361b | ||
|
|
8380a7bb9d | ||
|
|
a6c8d0787a | ||
|
|
3f9b220ee9 | ||
|
|
15db9ca7e4 | ||
|
|
224dff7481 | ||
|
|
643b2024c0 | ||
|
|
f378d43e31 | ||
|
|
23f1dc174e | ||
|
|
0c3802183d | ||
|
|
b038d276c9 | ||
|
|
797036e888 | ||
|
|
a426b65969 | ||
|
|
1d57cb9dae | ||
|
|
be64bd883c | ||
|
|
497c60375c | ||
|
|
7f616fc823 | ||
|
|
8a4c341512 | ||
|
|
c65343b2c5 | ||
|
|
d0efa6a77b | ||
|
|
01deadf078 | ||
|
|
6eaa3cd45d | ||
|
|
b0d2a79873 | ||
|
|
d94f001f25 | ||
|
|
afb85e2fd9 | ||
|
|
fffb5801c5 | ||
|
|
d81cd20ee6 | ||
|
|
3e7a9228bc | ||
|
|
835e199135 | ||
|
|
1d660e1883 | ||
|
|
5746261961 | ||
|
|
cbeae8eb30 | ||
|
|
95b2752d2a | ||
|
|
e3da472e7a | ||
|
|
43f60ca336 | ||
|
|
118a61c416 | ||
|
|
bf99a129bd | ||
|
|
fb6ad8cffd | ||
|
|
21fef57bc4 | ||
|
|
777422c87d | ||
|
|
ee6fd63c25 | ||
|
|
b9f00b71b2 | ||
|
|
099e3340bc | ||
|
|
172c2d3d71 | ||
|
|
854c8e5f2f | ||
|
|
b2cff193a9 | ||
|
|
ad1772178d | ||
|
|
0959b3d5b8 | ||
|
|
36f91f7f1e | ||
|
|
2c9d0606c3 | ||
|
|
1ce22fb8c9 | ||
|
|
e0cba855a6 | ||
|
|
8af3a65d37 | ||
|
|
667f67376e | ||
|
|
ce7b6be024 | ||
|
|
57cd2647f3 | ||
|
|
efcfe99707 | ||
|
|
cc1ad1bc13 | ||
|
|
29f06829e7 | ||
|
|
0fdf5e0102 | ||
|
|
5b7083f5f7 | ||
|
|
adb1c572ed | ||
|
|
5d17cd0bcc | ||
|
|
134d89a3d6 | ||
|
|
0efcbdcd37 | ||
|
|
878713a15d | ||
|
|
e01713f6f8 | ||
|
|
b6155c04ad | ||
|
|
8075d0a0fd | ||
|
|
029851fe3f | ||
|
|
886fb2ac43 | ||
|
|
99b1a51df0 | ||
|
|
795ec24246 | ||
|
|
ecf9c6fc6b | ||
|
|
68bc819b89 | ||
|
|
80c5779de9 | ||
|
|
d175dfdef7 | ||
|
|
93c13f5a11 | ||
|
|
ff8b880948 | ||
|
|
5b550c8a5b | ||
|
|
ce7d3c5c81 | ||
|
|
c99350308c | ||
|
|
e98c8ada6a | ||
|
|
ce8aa961ea | ||
|
|
fbd08ba3a6 | ||
|
|
61594cb877 | ||
|
|
520e655100 | ||
|
|
58d1697b00 | ||
|
|
f902b99287 | ||
|
|
d25a9b0e41 | ||
|
|
0e0f7d7ccb | ||
|
|
58cc21d417 | ||
|
|
8ac44491d0 | ||
|
|
a093b455b3 | ||
|
|
58494d45db | ||
|
|
f98621173f | ||
|
|
dbcfc92dc4 | ||
|
|
b9bd1d599b | ||
|
|
99b0be91ed | ||
|
|
f2ae29d8e4 | ||
|
|
4c3d415a07 | ||
|
|
7b65798758 | ||
|
|
c1c5a305c6 | ||
|
|
291211c029 | ||
|
|
a3a9e8d951 | ||
|
|
3a0ee11ccd | ||
|
|
2568b07075 | ||
|
|
e5fa02a1d4 | ||
|
|
fb5550bc38 | ||
|
|
bc5565251c | ||
|
|
4f548ce748 | ||
|
|
0faeb450c0 | ||
|
|
e12ffd7a24 | ||
|
|
d4c78617a7 | ||
|
|
db2296953d | ||
|
|
cd19c0e9e3 | ||
|
|
44e558e5a0 | ||
|
|
0da2547360 | ||
|
|
f3274ea42e | ||
|
|
0848283f6d | ||
|
|
6e99286bd0 | ||
|
|
588c2d9e4b | ||
|
|
7c201573fb | ||
|
|
581ffec5ed | ||
|
|
c296940fd4 | ||
|
|
f48633fa1d | ||
|
|
66a53c7dc0 | ||
|
|
09f53c6b21 | ||
|
|
b3a17040e5 | ||
|
|
774a7b41e4 | ||
|
|
062e671290 | ||
|
|
90d979b69d | ||
|
|
9d50084b98 | ||
|
|
e1d71a41f7 | ||
|
|
e781eb6423 | ||
|
|
15f4f03ba3 | ||
|
|
4f9b6f7180 | ||
|
|
b36ec5fd01 | ||
|
|
ac95ea03fe | ||
|
|
ae535fcb7d | ||
|
|
957cc6afc1 | ||
|
|
16fdd59617 | ||
|
|
fabf8f42c6 | ||
|
|
c98a56dc37 | ||
|
|
deb68dd420 | ||
|
|
0fd1a7fa08 | ||
|
|
c6ff8aa5dd | ||
|
|
06f025e92a | ||
|
|
f14329f2cd | ||
|
|
53e525597a | ||
|
|
54b3cbcf94 | ||
|
|
2852740e71 | ||
|
|
5322ba086b | ||
|
|
9e6e23ce97 |
5
.gitignore
vendored
5
.gitignore
vendored
@@ -1 +1,6 @@
|
||||
node_modules
|
||||
*.swp
|
||||
.idea/
|
||||
*.iml
|
||||
.*.tmp
|
||||
deploy-local.sh
|
||||
|
||||
215
LICENSE
215
LICENSE
@@ -1,21 +1,202 @@
|
||||
The MIT License (MIT)
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
Copyright (c) 2013 ESTOS GmbH
|
||||
Copyright (c) 2013 BlueJimp SARL
|
||||
|
||||
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:
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
1. Definitions.
|
||||
|
||||
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.
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
11
Makefile
11
Makefile
@@ -1,3 +1,4 @@
|
||||
NPM = npm
|
||||
BROWSERIFY = browserify
|
||||
GLOBAL_FLAGS = -x jquery -e
|
||||
OUTPUT_DIR = .
|
||||
@@ -14,11 +15,13 @@ compile-debug:FLAGS = -d $(GLOBAL_FLAGS)
|
||||
compile-debug: app
|
||||
|
||||
app:
|
||||
$(BROWSERIFY) $(FLAGS) app.js -s APP -o $(OUTPUT_DIR)/app.bundle.js
|
||||
$(NPM) update && $(BROWSERIFY) $(FLAGS) app.js -s APP -o $(OUTPUT_DIR)/app.bundle.js
|
||||
|
||||
clean:
|
||||
@rm -f $(OUTPUT_DIR)/*.bundle.js
|
||||
rm -f $(OUTPUT_DIR)/*.bundle.js
|
||||
|
||||
deploy:
|
||||
@mkdir -p $(DEPLOY_DIR) && cp $(OUTPUT_DIR)/*.bundle.js $(DEPLOY_DIR)
|
||||
scp $(DEPLOY_DIR)/app.bundle.js hristo.jitsi.net:/srv/web/hristo.jitsi.net/$(DEPLOY_DIR)
|
||||
mkdir -p $(DEPLOY_DIR) && \
|
||||
cp $(OUTPUT_DIR)/*.bundle.js $(DEPLOY_DIR) && \
|
||||
./bump-js-versions.sh && \
|
||||
([ ! -x deploy-local.sh ] || ./deploy-local.sh)
|
||||
|
||||
21
README.md
21
README.md
@@ -1,6 +1,6 @@
|
||||
Jitsi Meet - Secure, Simple and Scalable Video Conferences
|
||||
====
|
||||
Jitsi Meet is an open-source (MIT) WebRTC JavaScript application that uses [Jitsi Videobridge](https://jitsi.org/videobridge) to provide high quality, scalable video conferences. You can see [Jitsi Meet in action](http://youtu.be/7vFUVClsNh0) here at the 482 session of the VoIP Users Conference.
|
||||
Jitsi Meet is an open-source (Apache) WebRTC JavaScript application that uses [Jitsi Videobridge](https://jitsi.org/videobridge) to provide high quality, scalable video conferences. You can see [Jitsi Meet in action](http://youtu.be/7vFUVClsNh0) here at the session #482 of the VoIP Users Conference.
|
||||
|
||||
You can also try it out yourself at https://meet.jit.si .
|
||||
|
||||
@@ -30,8 +30,23 @@ make
|
||||
```
|
||||
|
||||
## Discuss
|
||||
Please use the [Jitsi dev mailing list](http://lists.jitsi.org/pipermail/dev/) to discuss feature requests before opening an issue on github.
|
||||
Please use the [Jitsi dev mailing list](http://lists.jitsi.org/pipermail/dev/) to discuss feature requests before opening an issue on Github.
|
||||
|
||||
## Acknowledgements
|
||||
|
||||
Jitsi Meet started out as a sample conferencing application using Jitsi Videobridge. It was originally developed by Philipp Hancke who then contributed it to the community where development continues with joint forces!
|
||||
Jitsi Meet started out as a sample conferencing application using Jitsi Videobridge. It was originally developed by then ESTOS' developer Philipp Hancke who then contributed it to the community where development continues with joint forces!
|
||||
|
||||
## Miscellaneous
|
||||
|
||||
This project was originally contributed to the community under the MIT license and with the following notice:
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013 ESTOS GmbH
|
||||
Copyright (c) 2013 BlueJimp SARL
|
||||
|
||||
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.
|
||||
|
||||
44
app.js
44
app.js
@@ -9,31 +9,61 @@ var APP =
|
||||
this.connectionquality = require("./modules/connectionquality/connectionquality");
|
||||
this.statistics = require("./modules/statistics/statistics");
|
||||
this.RTC = require("./modules/RTC/RTC");
|
||||
this.simulcast = require("./modules/simulcast/simulcast");
|
||||
this.desktopsharing = require("./modules/desktopsharing/desktopsharing");
|
||||
this.xmpp = require("./modules/xmpp/xmpp");
|
||||
this.keyboardshortcut = require("./modules/keyboardshortcut/keyboardshortcut");
|
||||
this.translation = require("./modules/translation/translation");
|
||||
this.settings = require("./modules/settings/Settings");
|
||||
this.DTMF = require("./modules/DTMF/DTMF");
|
||||
this.members = require("./modules/members/MemberList");
|
||||
this.configFetch = require("./modules/config/HttpConfigFetch");
|
||||
}
|
||||
};
|
||||
|
||||
function init() {
|
||||
|
||||
APP.desktopsharing.init();
|
||||
APP.RTC.start();
|
||||
APP.xmpp.start(APP.UI.getCredentials());
|
||||
APP.xmpp.start();
|
||||
APP.statistics.start();
|
||||
APP.connectionquality.init();
|
||||
|
||||
// Set default desktop sharing method
|
||||
APP.desktopsharing.init();
|
||||
|
||||
APP.keyboardshortcut.init();
|
||||
APP.members.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* If we have HTTP endpoint for getting confgi.json configured we're going to
|
||||
* read it and override properties from config.js and interfaceConfig.js.
|
||||
* If there is no endpoint we'll just continue with initialization.
|
||||
* Keep in mind that if the endpoint has been configured and we fail to obtain
|
||||
* the config for any reason then the conference won't start and error message
|
||||
* will be displayed to the user.
|
||||
*/
|
||||
function obtainConfigAndInit() {
|
||||
if (config.configLocation) {
|
||||
APP.configFetch.obtainConfig(
|
||||
config.configLocation, APP.UI.getRoomNode(),
|
||||
// Get config result callback
|
||||
function(success, error) {
|
||||
if (success) {
|
||||
init();
|
||||
} else {
|
||||
// Show obtain config error,
|
||||
// pass the error object for report
|
||||
APP.UI.messageHandler.openReportDialog(
|
||||
null, "dialog.connectError", error);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
init();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$(document).ready(function () {
|
||||
|
||||
var URLProcessor = require("./modules/config/URLProcessor");
|
||||
URLProcessor.setConfigParametersFromUrl();
|
||||
APP.init();
|
||||
|
||||
APP.translation.init();
|
||||
@@ -41,7 +71,7 @@ $(document).ready(function () {
|
||||
if(APP.API.isEnabled())
|
||||
APP.API.init();
|
||||
|
||||
APP.UI.start(init);
|
||||
APP.UI.start(obtainConfigAndInit);
|
||||
|
||||
});
|
||||
|
||||
|
||||
34
bump-js-versions.sh
Executable file
34
bump-js-versions.sh
Executable file
@@ -0,0 +1,34 @@
|
||||
#!/bin/sh
|
||||
|
||||
if ! which git > /dev/null 2>&1 ;then
|
||||
echo "Cannot find git executable, not bumping js versions."
|
||||
exit
|
||||
fi
|
||||
if ! git status > /dev/null 2>&1 ;then
|
||||
echo "Not a git repository, not bumping js versions."
|
||||
exit
|
||||
fi
|
||||
|
||||
# This script finds all js files included from index.html which have been
|
||||
# modified and bumps their version (the value of the "v" parameter used
|
||||
# in index.html)
|
||||
|
||||
# contents of index.html at HEAD (excluding not-committed changes)
|
||||
index=`git show HEAD:index.html`
|
||||
|
||||
# js files included from index.html. The sort needed for comm
|
||||
jsfiles=.bump-js-versions-jsfiles.tmp
|
||||
echo "$index" | grep '<script src=".*"' -o | sed -e 's/<script src="//' | sed -e 's/\?.*//' | tr -d \" | sort > $jsfiles
|
||||
|
||||
# modified files since the last commit
|
||||
gitmodified=.bump-js-versions-gitmodified.tmp
|
||||
git ls-files -m | sort > $gitmodified
|
||||
|
||||
for file in `comm -12 $jsfiles $gitmodified` ;do
|
||||
old_version=`echo "$index" | grep "<script src=\"${file}?v=[0-9]*" -o | sed -e 's/.*v=//'| tr -d \"`
|
||||
new_version=$((1+$old_version))
|
||||
echo Bumping version of $file from $old_version to $new_version
|
||||
sed -i.tmp -e "s%script src=\"${file}\?v=.*\"%script src=\"$file?v=$new_version\"%" index.html
|
||||
done
|
||||
|
||||
rm -f $jsfiles $gitmodified index.html.tmp
|
||||
33
config.js
33
config.js
@@ -1,10 +1,10 @@
|
||||
var config = {
|
||||
// configLocation: './config.json', // see ./modules/HttpConfigFetch.js
|
||||
hosts: {
|
||||
domain: 'hristo.jitsi.net',
|
||||
domain: 'jitsi-meet.example.com',
|
||||
//anonymousdomain: 'guest.example.com',
|
||||
// anonymousdomain: 'guest.hristo.jitsi.net',
|
||||
muc: 'conference.hristo.jitsi.net', // FIXME: use XEP-0030
|
||||
bridge: 'jitsi-videobridge.hristo.jitsi.net', // FIXME: use XEP-0030
|
||||
muc: 'conference.jitsi-meet.example.com', // FIXME: use XEP-0030
|
||||
bridge: 'jitsi-videobridge.jitsi-meet.example.com', // FIXME: use XEP-0030
|
||||
//jirecon: 'jirecon.jitsi-meet.example.com',
|
||||
//call_control: 'callcontrol.jitsi-meet.example.com',
|
||||
//focus: 'focus.jitsi-meet.example.com' - defaults to 'focus.jitsi-meet.example.com'
|
||||
@@ -13,12 +13,11 @@ var config = {
|
||||
// useStunTurn: true, // use XEP-0215 to fetch STUN and TURN server
|
||||
// useIPv6: true, // ipv6 support. use at your own risk
|
||||
useNicks: false,
|
||||
bosh: '//hristo.jitsi.net/http-bind', // FIXME: use xep-0156 for that
|
||||
bosh: '//jitsi-meet.example.com/http-bind', // FIXME: use xep-0156 for that
|
||||
clientNode: 'http://jitsi.org/jitsimeet', // The name of client node advertised in XEP-0115 'c' stanza
|
||||
//focusUserJid: 'focus@auth.jitsi-meet.example.com', // The real JID of focus participant - can be overridden here
|
||||
defaultSipNumber: '123', // Default SIP number
|
||||
//defaultSipNumber: '', // Default SIP number
|
||||
desktopSharing: 'ext', // Desktop sharing method. Can be set to 'ext', 'webrtc' or false to disable.
|
||||
// resolution: "1080",
|
||||
chromeExtensionId: 'diibjkoicjeejcmhdnailmkgecihlobk', // Id of desktop streamer Chrome extension
|
||||
desktopSharingSources: ['screen', 'window'],
|
||||
minChromeExtVersion: '0.1', // Required version of Chrome extension
|
||||
@@ -28,12 +27,18 @@ var config = {
|
||||
channelLastN: -1, // The default value of the channel attribute last-n.
|
||||
adaptiveLastN: false,
|
||||
adaptiveSimulcast: false,
|
||||
useRtcpMux: false,
|
||||
useBundle: false,
|
||||
enableRecording: true,
|
||||
enableRecording: false,
|
||||
enableWelcomePage: true,
|
||||
enableSimulcast: false,
|
||||
enableFirefoxSupport: false, //firefox support is still experimental, only one-to-one conferences with chrome focus
|
||||
// will work when simulcast, bundle, mux, lastN and SCTP are disabled.
|
||||
logStats: false // Enable logging of PeerConnection stats via the focus
|
||||
enableSimulcast: false, // blocks FF support
|
||||
logStats: false, // Enable logging of PeerConnection stats via the focus
|
||||
// requireDisplayName: true,//Forces the participants that doesn't have display name to enter it when they enter the room.
|
||||
// startAudioMuted: 10, //every participant after the Nth will start audio muted
|
||||
// startVideoMuted: 10, //every participant after the Nth will start video muted
|
||||
// defaultLanguage: "en",
|
||||
// To enable sending statistics to callstats.io you should provide Applicaiton ID and Secret.
|
||||
// callStatsID: "",//Application ID for callstats.io API
|
||||
// callStatsSecret: ""//Secret for callstats.io API
|
||||
/*noticeMessage: 'Service update is scheduled for 16th March 2015. ' +
|
||||
'During that time service will not be available. ' +
|
||||
'Apologise for inconvenience.'*/
|
||||
};
|
||||
|
||||
@@ -3,12 +3,27 @@
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
#contactlist>ul {
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
#contactlist>div.title {
|
||||
text-align: left;
|
||||
padding: 7px 10px;
|
||||
margin: 2px;
|
||||
color: #00ccff;
|
||||
font-size: 11pt;
|
||||
border-bottom: 1px solid #676767;
|
||||
}
|
||||
|
||||
#contactlist>ul>li {
|
||||
#contactlist>ul#contacts {
|
||||
position: absolute;
|
||||
top: 31px;
|
||||
bottom: 0px;
|
||||
width: 100%;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
overflow-y: scroll;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
#contacts>li {
|
||||
list-style-type: none;
|
||||
text-align: left;
|
||||
color: #FFF;
|
||||
@@ -17,18 +32,12 @@
|
||||
margin: 2px;
|
||||
}
|
||||
|
||||
#contactlist>ul>li>p {
|
||||
#contacts>li>p {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
#contactlist>ul>li.title {
|
||||
color: #00ccff;
|
||||
font-size: 11pt;
|
||||
border-bottom: 1px solid #676767;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
padding: 0px;
|
||||
margin-right: 10px;
|
||||
|
||||
@@ -119,4 +119,8 @@
|
||||
|
||||
.icon-settings:before {
|
||||
content: "\e61b";
|
||||
}
|
||||
}
|
||||
|
||||
.icon-dialPad:before {
|
||||
content: "\e61c";
|
||||
}
|
||||
|
||||
40
css/main.css
40
css/main.css
@@ -29,10 +29,6 @@ html, body{
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
#settings {
|
||||
display:none;
|
||||
}
|
||||
|
||||
#nowebrtc {
|
||||
display:none;
|
||||
}
|
||||
@@ -42,17 +38,30 @@ html, body{
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#toolbar a.button::after {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
left: 40px;
|
||||
width: 1px;
|
||||
height: 20px;
|
||||
background: #373737;
|
||||
}
|
||||
|
||||
#toolbar a.button:last-child::after {
|
||||
content: none;
|
||||
}
|
||||
|
||||
.button {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
color: #FFFFFF;
|
||||
top: 0px;
|
||||
padding: 10px 0px;
|
||||
top: 0;
|
||||
padding: 10px 0;
|
||||
width: 38px;
|
||||
cursor: pointer;
|
||||
font-size: 11pt;
|
||||
text-align: center;
|
||||
text-shadow: 0px 1px 0px rgba(255,255,255,.3), 0px -1px 0px rgba(0,0,0,.6);
|
||||
text-shadow: 0 1px 0 rgba(255,255,255,.3), 0 -1px 0 rgba(0,0,0,.6);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
@@ -65,13 +74,13 @@ html, body{
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#chatButton, #chatBottomButton, #contactListButton, #numberOfParticipants {
|
||||
#toolbar_button_chat, #chatBottomButton, #contactListButton, #numberOfParticipants {
|
||||
-webkit-transition: all .5s ease-in-out;
|
||||
-moz-transition: all .5s ease-in-out;
|
||||
transition: all .5s ease-in-out;
|
||||
}
|
||||
/*#ffde00*/
|
||||
#chatButton.active, #contactListButton.glowing, #chatBottomButton.glowing {
|
||||
#toolbar_button_chat.active, #contactListButton.glowing, #chatBottomButton.glowing {
|
||||
-webkit-text-shadow: -1px 0 10px #00ccff,
|
||||
0 1px 10px #00ccff,
|
||||
1px 0 10px #00ccff,
|
||||
@@ -86,6 +95,11 @@ html, body{
|
||||
0 -1px 10px #00ccff;
|
||||
}
|
||||
|
||||
#toolbar_button_hangup {
|
||||
color: #ff0000;
|
||||
font-size: 1.4em;
|
||||
}
|
||||
|
||||
#numberOfParticipants {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
@@ -104,13 +118,13 @@ html, body{
|
||||
color: #00ccff;
|
||||
}
|
||||
|
||||
#recordButton {
|
||||
#toolbar_button_record {
|
||||
-webkit-transition: all .5s ease-in-out;
|
||||
-moz-transition: all .5s ease-in-out;
|
||||
transition: all .5s ease-in-out;
|
||||
}
|
||||
/*#ffde00*/
|
||||
#recordButton.active {
|
||||
#toolbar_button_record.active {
|
||||
-webkit-text-shadow: -1px 0 10px #00ccff,
|
||||
0 1px 10px #00ccff,
|
||||
1px 0 10px #00ccff,
|
||||
@@ -159,6 +173,8 @@ a.bottomToolbarButton:hover {
|
||||
}
|
||||
|
||||
input[type='text'], input[type='password'], textarea {
|
||||
-webkit-user-select: text;
|
||||
user-select: text;
|
||||
display: inline-block;
|
||||
font-size: 14px;
|
||||
padding: 5px;
|
||||
|
||||
@@ -26,4 +26,9 @@
|
||||
|
||||
button.jqidefaultbutton #inviteLinkRef {
|
||||
color: #2c8ad2;
|
||||
}
|
||||
|
||||
#inviteLinkRef {
|
||||
-webkit-user-select: text;
|
||||
user-select: text;
|
||||
}
|
||||
11
css/notice.css
Normal file
11
css/notice.css
Normal file
@@ -0,0 +1,11 @@
|
||||
#notice {
|
||||
position: relative;
|
||||
z-index: 3;
|
||||
margin-top: 6px;
|
||||
}
|
||||
#noticeText {
|
||||
background-color: #000000;
|
||||
color: white;
|
||||
padding: 3px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
@@ -47,3 +47,22 @@
|
||||
#languages_selectbox{
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
|
||||
#startMutedOptions {
|
||||
padding-left: 10%;
|
||||
text-indent: -10%;
|
||||
}
|
||||
|
||||
#startAudioMuted {
|
||||
width: 13px !important;
|
||||
}
|
||||
|
||||
#startVideoMuted {
|
||||
width: 13px !important;
|
||||
}
|
||||
|
||||
.startMutedLabel {
|
||||
width: 94%;
|
||||
float: left;
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ a {
|
||||
.supported_browsers
|
||||
{
|
||||
margin: 0px auto 0px auto;
|
||||
width: 460px;
|
||||
width: 660px;
|
||||
}
|
||||
|
||||
.clear
|
||||
@@ -101,6 +101,13 @@ a {
|
||||
height: 79px;
|
||||
background-image: url('/images/chromium.png');
|
||||
}
|
||||
#firefox-nightly_logo
|
||||
{
|
||||
width: 73px;
|
||||
height: 79px;
|
||||
background-image: url('/images/firefox-nightly.png');
|
||||
}
|
||||
|
||||
#opera_logo
|
||||
{
|
||||
width: 73px;
|
||||
|
||||
@@ -60,28 +60,31 @@
|
||||
}
|
||||
|
||||
#remoteVideos .videocontainer:hover {
|
||||
-webkit-box-shadow: inset 0 0 10px #FFFFFF, 0 0 10px #FFFFFF;
|
||||
box-shadow: inset 0 0 10px #FFFFFF, 0 0 10px #FFFFFF;
|
||||
border: 2px solid #FFFFFF;
|
||||
}
|
||||
|
||||
#remoteVideos .videocontainer.videoContainerFocused {
|
||||
-webkit-box-shadow: inset 0 0 28px #006d91;
|
||||
box-shadow: inset 0 0 28px #006d91;
|
||||
border: 2px solid #006d91;
|
||||
}
|
||||
|
||||
#remoteVideos .videocontainer.videoContainerFocused:hover {
|
||||
-webkit-box-shadow: inset 0 0 5px #FFFFFF, 0 0 10px #FFFFFF, inset 0 0 60px #006d91;
|
||||
box-shadow: inset 0 0 5px #FFFFFF, 0 0 10px #FFFFFF, inset 0 0 60px #006d91;
|
||||
border: 2px solid #FFFFFF;
|
||||
}
|
||||
|
||||
#localVideoWrapper {
|
||||
display:inline-block;
|
||||
-webkit-mask-box-image: url(../images/videomask.svg);
|
||||
border-radius:0px !important;
|
||||
border-radius:4px !important;
|
||||
border: 0px !important;
|
||||
}
|
||||
|
||||
#remoteVideos .videocontainer>video {
|
||||
/* With TemasysWebRTC plugin <object/> element is used
|
||||
instead of <video/> */
|
||||
#remoteVideos .videocontainer>video,
|
||||
#remoteVideos .videocontainer>object {
|
||||
border-radius:4px;
|
||||
}
|
||||
|
||||
@@ -92,17 +95,20 @@
|
||||
-o-transform: scale(-1, 1);
|
||||
}
|
||||
|
||||
#localVideoWrapper>video {
|
||||
border-radius:0px !important;
|
||||
#localVideoWrapper>video,
|
||||
#localVideoWrapper>object {
|
||||
border-radius:4px !important;
|
||||
}
|
||||
|
||||
#largeVideo,
|
||||
#largeVideoWrapper,
|
||||
#largeVideoContainer {
|
||||
overflow: hidden;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#largeVideo
|
||||
#largeVideo,
|
||||
#largeVideoWrapper
|
||||
{
|
||||
object-fit: cover;
|
||||
}
|
||||
@@ -110,8 +116,13 @@
|
||||
#presentation,
|
||||
#etherpad,
|
||||
#localVideoWrapper>video,
|
||||
#localVideoWrapper>object,
|
||||
#localVideoWrapper,
|
||||
.videocontainer>video {
|
||||
#largeVideoWrapper,
|
||||
#largeVideoWrapper>video,
|
||||
#largeVideoWrapper>object,
|
||||
.videocontainer>video,
|
||||
.videocontainer>object {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
@@ -129,7 +140,7 @@
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
#etherpadButton {
|
||||
#toolbar_button_etherpad {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -363,7 +374,7 @@
|
||||
margin-right: 40%;
|
||||
text-align: center;
|
||||
background: linear-gradient(to bottom, rgba(255,255,255,.85) , rgba(255,255,255,.35));
|
||||
-webkit-box-shadow: 0 0 2px #000000, 0 0 10px #000000;
|
||||
box-shadow: 0 0 2px #000000, 0 0 10px #000000;
|
||||
border-bottom-left-radius: 12px;
|
||||
border-bottom-right-radius: 12px;
|
||||
display: none;
|
||||
@@ -415,3 +426,54 @@
|
||||
left: 35px;
|
||||
border-radius: 200px;
|
||||
}
|
||||
|
||||
.noMic {
|
||||
position: absolute;
|
||||
border-radius: 8px;
|
||||
z-index: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image: url("../images/noMic.png");
|
||||
background-color: #000;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
.noVideo {
|
||||
position: absolute;
|
||||
border-radius: 8px;
|
||||
z-index: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image: url("../images/noVideo.png");
|
||||
background-color: #000;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
.videoMessageFilter {
|
||||
-webkit-filter: grayscale(.5) opacity(0.8);
|
||||
filter: grayscale(.5) opacity(0.8);
|
||||
}
|
||||
|
||||
.videoProblemFilter {
|
||||
-webkit-filter: blur(10px) grayscale(.5) opacity(0.8);
|
||||
filter: blur(10px) grayscale(.5) opacity(0.8);
|
||||
}
|
||||
|
||||
#videoConnectionMessage {
|
||||
display: none;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
top:50%;
|
||||
z-index: 10000;
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
color: #FFF;
|
||||
opacity: .80;
|
||||
text-shadow: 0px 0px 1px rgba(0,0,0,0.3),
|
||||
0px 1px 1px rgba(0,0,0,0.3),
|
||||
1px 0px 1px rgba(0,0,0,0.3),
|
||||
0px 0px 1px rgba(0,0,0,0.3);
|
||||
}
|
||||
14
debian/control
vendored
14
debian/control
vendored
@@ -10,26 +10,26 @@ Homepage: https://jitsi.org/meet
|
||||
|
||||
Package: jitsi-meet
|
||||
Architecture: all
|
||||
Pre-Depends: jitsi-videobridge
|
||||
Depends: ${misc:Depends}, nginx, jitsi-meet-prosody, libjs-strophe (>= 1.1.3),
|
||||
libjs-jquery, libjs-jquery-ui
|
||||
Depends: ${misc:Depends}, jitsi-videobridge, jitsi-meet-prosody, libjs-strophe (>= 1.1.3),
|
||||
libjs-jquery, libjs-jquery-ui, openjdk-8-jre-headless | nginx
|
||||
Description: WebRTC JavaScript video conferences
|
||||
Jitsi Meet is a WebRTC JavaScript application that uses Jitsi
|
||||
Videobridge to provide high quality, scalable video conferences.
|
||||
.
|
||||
It is a web interface to Jitsi Videobridge for audio and video
|
||||
forwarding and relaying, configured to work with nginx
|
||||
forwarding and relaying, configured to work with jetty instance
|
||||
running embedded into Jitsi Videobridge
|
||||
|
||||
Package: jitsi-meet-prosody
|
||||
Architecture: all
|
||||
Pre-Depends: openssl, prosody | prosody-trunk, jitsi-videobridge
|
||||
Depends: ${misc:Depends}, jicofo
|
||||
Depends: ${misc:Depends}, openssl, prosody | prosody-trunk, jitsi-videobridge, jicofo
|
||||
Description: Prosody configuration for Jitsi Meet
|
||||
Jitsi Meet is a WebRTC JavaScript application that uses Jitsi
|
||||
Videobridge to provide high quality, scalable video conferences.
|
||||
.
|
||||
It is a web interface to Jitsi Videobridge for audio and video
|
||||
forwarding and relaying, configured to work with nginx
|
||||
forwarding and relaying, configured to work with jetty instance
|
||||
running embedded into Jitsi Videobridge
|
||||
.
|
||||
This package contains configuration for Prosody to be used with
|
||||
Jitsi Meet.
|
||||
|
||||
9
debian/jitsi-meet-prosody.postinst
vendored
9
debian/jitsi-meet-prosody.postinst
vendored
@@ -28,6 +28,15 @@ case "$1" in
|
||||
# loading debconf
|
||||
. /usr/share/debconf/confmodule
|
||||
|
||||
# detect dpkg-reconfigure, just delete old links
|
||||
db_get jitsi-meet-prosody/jvb-hostname
|
||||
JVB_HOSTNAME_OLD=$RET
|
||||
if [ -n "$RET" ] && [ ! "$JVB_HOSTNAME_OLD" = "$JVB_HOSTNAME" ] ; then
|
||||
rm -f /etc/prosody/conf.d/$JVB_HOSTNAME_OLD.cfg.lua
|
||||
rm -f /etc/prosody/certs/$JVB_HOSTNAME_OLD.key
|
||||
rm -f /etc/prosody/certs/$JVB_HOSTNAME_OLD.crt
|
||||
fi
|
||||
|
||||
# stores the hostname so we will reuse it later, like in purge
|
||||
db_set jitsi-meet-prosody/jvb-hostname $JVB_HOSTNAME
|
||||
|
||||
|
||||
2
debian/jitsi-meet-prosody.postrm
vendored
2
debian/jitsi-meet-prosody.postrm
vendored
@@ -25,7 +25,7 @@ set -e
|
||||
case "$1" in
|
||||
remove)
|
||||
if [ -x "/etc/init.d/prosody" ]; then
|
||||
invoke-rc.d nginx reload
|
||||
invoke-rc.d prosody reload
|
||||
fi
|
||||
;;
|
||||
|
||||
|
||||
1
debian/jitsi-meet-prosody.templates
vendored
1
debian/jitsi-meet-prosody.templates
vendored
@@ -1,5 +1,4 @@
|
||||
Template: jitsi-meet-prosody/jvb-hostname
|
||||
Type: string
|
||||
Default: ${default-key}
|
||||
_Description: The hostname of the current installation:
|
||||
The value for the hostname that is set in Jitsi Videobridge installation.
|
||||
|
||||
166
debian/jitsi-meet.postinst
vendored
166
debian/jitsi-meet.postinst
vendored
@@ -1,4 +1,4 @@
|
||||
#!/bin/sh
|
||||
#!/bin/bash
|
||||
# postinst script for jitsi-meet
|
||||
#
|
||||
# see: dh_installdeb(1)
|
||||
@@ -20,59 +20,159 @@ set -e
|
||||
case "$1" in
|
||||
configure)
|
||||
|
||||
. /etc/jitsi/videobridge/config
|
||||
JVB_ETC_CONFIG="/etc/jitsi/videobridge/config"
|
||||
JVB_CONFIG="/usr/share/jitsi-videobridge/.sip-communicator/sip-communicator.properties"
|
||||
|
||||
. $JVB_ETC_CONFIG
|
||||
|
||||
# loading debconf
|
||||
. /usr/share/debconf/confmodule
|
||||
|
||||
# detect dpkg-reconfigure
|
||||
RECONFIGURING="false"
|
||||
db_get jitsi-meet/jvb-hostname
|
||||
JVB_HOSTNAME_OLD=$RET
|
||||
if [ -n "$RET" ] && [ ! "$JVB_HOSTNAME_OLD" = "$JVB_HOSTNAME" ] ; then
|
||||
RECONFIGURING="true"
|
||||
rm -f /etc/jitsi/meet/$JVB_HOSTNAME_OLD-config.js
|
||||
fi
|
||||
|
||||
JVB_SERVE="false"
|
||||
db_get jitsi-meet/jvb-serve
|
||||
if [ -n "$RET" ] && [ "$RET" = "true" ] ; then
|
||||
JVB_SERVE="true"
|
||||
fi
|
||||
|
||||
# stores the hostname so we will reuse it later, like in purge
|
||||
db_set jitsi-meet/jvb-hostname $JVB_HOSTNAME
|
||||
|
||||
# nginx conf
|
||||
if [ ! -f /etc/nginx/sites-available/$JVB_HOSTNAME.conf ]; then
|
||||
cp /usr/share/doc/jitsi-meet/jitsi-meet.example /etc/nginx/sites-available/$JVB_HOSTNAME.conf
|
||||
if [ ! -f /etc/nginx/sites-enabled/$JVB_HOSTNAME.conf ]; then
|
||||
ln -s /etc/nginx/sites-available/$JVB_HOSTNAME.conf /etc/nginx/sites-enabled/$JVB_HOSTNAME.conf
|
||||
fi
|
||||
sed -i "s/jitsi-meet.example.com/$JVB_HOSTNAME/g" /etc/nginx/sites-available/$JVB_HOSTNAME.conf
|
||||
NGINX_INSTALL_CHECK="$(dpkg-query -W -f '${PackageSpec}:${Status}\n' nginx 2>&1 | grep -v "ok installed" || :)"
|
||||
if [ -z "${NGINX_INSTALL_CHECK}" ]; then
|
||||
FORCE_NGINX="true"
|
||||
fi
|
||||
|
||||
# SSL for nginx
|
||||
db_get jitsi-meet/cert-choice
|
||||
CERT_CHOICE="$RET"
|
||||
if [ "$CERT_CHOICE" = 'A certificate is available and the files are uploaded on the server' ]; then
|
||||
db_set jitsi-meet/cert-path-key "/etc/ssl/$JVB_HOSTNAME.key"
|
||||
db_input critical jitsi-meet/cert-path-key || true
|
||||
db_go
|
||||
db_get jitsi-meet/cert-path-key
|
||||
CERT_KEY="$RET"
|
||||
db_set jitsi-meet/cert-path-crt "/etc/ssl/$JVB_HOSTNAME.crt"
|
||||
db_input critical jitsi-meet/cert-path-crt || true
|
||||
db_go
|
||||
db_get jitsi-meet/cert-path-crt
|
||||
CERT_CRT="$RET"
|
||||
# replace self-signed certificate paths with user provided ones
|
||||
CERT_KEY_ESC=$(echo $CERT_KEY | sed 's/\./\\\./g')
|
||||
CERT_KEY_ESC=$(echo $CERT_KEY_ESC | sed 's/\//\\\//g')
|
||||
sed -i "s/ssl_certificate_key\ \/var\/lib\/prosody\/.*key/ssl_certificate_key\ $CERT_KEY_ESC/g" \
|
||||
/etc/nginx/sites-available/$JVB_HOSTNAME.conf
|
||||
CERT_CRT_ESC=$(echo $CERT_CRT | sed 's/\./\\\./g')
|
||||
CERT_CRT_ESC=$(echo $CERT_CRT_ESC | sed 's/\//\\\//g')
|
||||
sed -i "s/ssl_certificate\ \/var\/lib\/prosody\/.*crt/ssl_certificate\ $CERT_CRT_ESC/g" \
|
||||
/etc/nginx/sites-available/$JVB_HOSTNAME.conf
|
||||
fi
|
||||
UPLOADED_CERT_CHOICE="A certificate is available and the files are uploaded on the server"
|
||||
|
||||
# jitsi meet
|
||||
JITSI_MEET_CONFIG="/etc/jitsi/meet/$JVB_HOSTNAME-config.js"
|
||||
if [ ! -f $JITSI_MEET_CONFIG ]; then
|
||||
if [ ! -f $JITSI_MEET_CONFIG ] ; then
|
||||
cp /usr/share/doc/jitsi-meet/config.js $JITSI_MEET_CONFIG
|
||||
sed -i "s/jitsi-meet.example.com/$JVB_HOSTNAME/g" $JITSI_MEET_CONFIG
|
||||
fi
|
||||
|
||||
# this is new install let's configure jvb to serve meet
|
||||
if [[ -z $FORCE_NGINX && ( -z $JVB_HOSTNAME_OLD || "$JVB_SERVE" = "true" ) ]] ; then
|
||||
# this is a reconfigure, lets just delete old links
|
||||
if [ "$RECONFIGURING" = "true" ] ; then
|
||||
rm -f $JVB_CONFIG
|
||||
fi
|
||||
|
||||
# configure jvb
|
||||
echo "AUTHBIND=yes" >> $JVB_ETC_CONFIG
|
||||
sed -i "s/JVB_OPTS=.*/JVB_OPTS=--apis=rest,xmpp/g" $JVB_ETC_CONFIG
|
||||
|
||||
echo "org.jitsi.videobridge.rest.jetty.host=::" >> $JVB_CONFIG
|
||||
echo "org.jitsi.videobridge.rest.jetty.port=443" >> $JVB_CONFIG
|
||||
echo "org.jitsi.videobridge.rest.jetty.ProxyServlet.hostHeader=$JVB_HOSTNAME" >> $JVB_CONFIG
|
||||
echo "org.jitsi.videobridge.rest.jetty.ProxyServlet.pathSpec=/http-bind" >> $JVB_CONFIG
|
||||
echo "org.jitsi.videobridge.rest.jetty.ProxyServlet.proxyTo=http://localhost:5280/http-bind" >> $JVB_CONFIG
|
||||
echo "org.jitsi.videobridge.rest.jetty.ResourceHandler.resourceBase=/usr/share/jitsi-meet" >> $JVB_CONFIG
|
||||
echo "org.jitsi.videobridge.rest.jetty.ResourceHandler.alias./config.js=/etc/jitsi/meet/$JVB_HOSTNAME-config.js" >> $JVB_CONFIG
|
||||
echo "org.jitsi.videobridge.rest.jetty.RewriteHandler.regex=^/([a-zA-Z0-9]+)$" >> $JVB_CONFIG
|
||||
echo "org.jitsi.videobridge.rest.jetty.RewriteHandler.replacement=/" >> $JVB_CONFIG
|
||||
echo "org.jitsi.videobridge.rest.jetty.tls.port=443" >> $JVB_CONFIG
|
||||
echo "org.jitsi.videobridge.TCP_HARVESTER_PORT=443" >> $JVB_CONFIG
|
||||
echo "org.jitsi.videobridge.rest.jetty.sslContextFactory.keyStorePath=/etc/jitsi/videobridge/$JVB_HOSTNAME.jks" >> $JVB_CONFIG
|
||||
echo "org.jitsi.videobridge.rest.jetty.sslContextFactory.keyStorePassword=changeit" >> $JVB_CONFIG
|
||||
|
||||
# configure authbind to allow jvb to bind to privileged ports
|
||||
OWNER=$(stat -c '%U' /usr/share/jitsi-videobridge)
|
||||
GROUP=$(stat -c '%G' /usr/share/jitsi-videobridge)
|
||||
JVB_UID="`id -u $OWNER`"
|
||||
if [ ! -f "/etc/authbind/byuid/$JVB_UID" ] ; then
|
||||
if [ ! -d "/etc/authbind/byuid" ] ; then
|
||||
mkdir -p /etc/authbind/byuid
|
||||
chmod 755 /etc/authbind
|
||||
chmod 755 /etc/authbind/byuid
|
||||
fi
|
||||
echo '::,443' >/etc/authbind/byuid/$JVB_UID
|
||||
chown $OWNER:$GROUP /etc/authbind/byuid/$JVB_UID
|
||||
chmod 700 /etc/authbind/byuid/$JVB_UID
|
||||
fi
|
||||
|
||||
if [ "$CERT_CHOICE" = "$UPLOADED_CERT_CHOICE" ] ; then
|
||||
# create jks from uploaded certs
|
||||
openssl pkcs12 -export \
|
||||
-in /etc/ssl/$JVB_HOSTNAME.crt \
|
||||
-inkey /etc/ssl/$JVB_HOSTNAME.key \
|
||||
-passout pass:changeit > /etc/jitsi/videobridge/$JVB_HOSTNAME.p12
|
||||
keytool -importkeystore \
|
||||
-srckeystore /etc/jitsi/videobridge/$JVB_HOSTNAME.p12 \
|
||||
-destkeystore /etc/jitsi/videobridge/$JVB_HOSTNAME.jks \
|
||||
-srcstoretype pkcs12 \
|
||||
-noprompt -storepass changeit -srcstorepass changeit
|
||||
else
|
||||
# create jks from self-signed certs
|
||||
openssl pkcs12 -export \
|
||||
-in /var/lib/prosody/$JVB_HOSTNAME.crt \
|
||||
-inkey /var/lib/prosody/$JVB_HOSTNAME.key \
|
||||
-passout pass:changeit > /etc/jitsi/videobridge/$JVB_HOSTNAME.p12
|
||||
keytool -importkeystore \
|
||||
-srckeystore /etc/jitsi/videobridge/$JVB_HOSTNAME.p12 \
|
||||
-destkeystore /etc/jitsi/videobridge/$JVB_HOSTNAME.jks \
|
||||
-srcstoretype pkcs12 \
|
||||
-noprompt -storepass changeit -srcstorepass changeit
|
||||
fi
|
||||
|
||||
db_set jitsi-meet/jvb-serve "true"
|
||||
|
||||
invoke-rc.d jitsi-videobridge restart
|
||||
elif [[ "$FORCE_NGINX" = "true" || ( -n $JVB_HOSTNAME_OLD && "$JVB_SERVE" = "false" ) ]] ; then
|
||||
# this is a reconfigure, lets just delete old links
|
||||
if [ "$RECONFIGURING" = "true" ] ; then
|
||||
rm -f /etc/nginx/sites-enabled/$JVB_HOSTNAME_OLD.conf
|
||||
rm -f /etc/jitsi/meet/$JVB_HOSTNAME_OLD-config.js
|
||||
fi
|
||||
|
||||
# nginx conf
|
||||
if [ ! -f /etc/nginx/sites-available/$JVB_HOSTNAME.conf ] ; then
|
||||
cp /usr/share/doc/jitsi-meet/jitsi-meet.example /etc/nginx/sites-available/$JVB_HOSTNAME.conf
|
||||
if [ ! -f /etc/nginx/sites-enabled/$JVB_HOSTNAME.conf ] ; then
|
||||
ln -s /etc/nginx/sites-available/$JVB_HOSTNAME.conf /etc/nginx/sites-enabled/$JVB_HOSTNAME.conf
|
||||
fi
|
||||
sed -i "s/jitsi-meet.example.com/$JVB_HOSTNAME/g" /etc/nginx/sites-available/$JVB_HOSTNAME.conf
|
||||
fi
|
||||
|
||||
if [ "$CERT_CHOICE" = "$UPLOADED_CERT_CHOICE" ] ; then
|
||||
db_set jitsi-meet/cert-path-key "/etc/ssl/$JVB_HOSTNAME.key"
|
||||
db_input critical jitsi-meet/cert-path-key || true
|
||||
db_go
|
||||
db_get jitsi-meet/cert-path-key
|
||||
CERT_KEY="$RET"
|
||||
db_set jitsi-meet/cert-path-crt "/etc/ssl/$JVB_HOSTNAME.crt"
|
||||
db_input critical jitsi-meet/cert-path-crt || true
|
||||
db_go
|
||||
db_get jitsi-meet/cert-path-crt
|
||||
CERT_CRT="$RET"
|
||||
# replace self-signed certificate paths with user provided ones
|
||||
CERT_KEY_ESC=$(echo $CERT_KEY | sed 's/\./\\\./g')
|
||||
CERT_KEY_ESC=$(echo $CERT_KEY_ESC | sed 's/\//\\\//g')
|
||||
sed -i "s/ssl_certificate_key\ \/var\/lib\/prosody\/.*key/ssl_certificate_key\ $CERT_KEY_ESC/g" \
|
||||
/etc/nginx/sites-available/$JVB_HOSTNAME.conf
|
||||
CERT_CRT_ESC=$(echo $CERT_CRT | sed 's/\./\\\./g')
|
||||
CERT_CRT_ESC=$(echo $CERT_CRT_ESC | sed 's/\//\\\//g')
|
||||
sed -i "s/ssl_certificate\ \/var\/lib\/prosody\/.*crt/ssl_certificate\ $CERT_CRT_ESC/g" \
|
||||
/etc/nginx/sites-available/$JVB_HOSTNAME.conf
|
||||
fi
|
||||
|
||||
invoke-rc.d nginx reload
|
||||
fi
|
||||
|
||||
# and we're done with debconf
|
||||
db_stop
|
||||
|
||||
invoke-rc.d nginx reload
|
||||
;;
|
||||
|
||||
abort-upgrade|abort-remove|abort-deconfigure)
|
||||
|
||||
9
debian/jitsi-meet.templates
vendored
9
debian/jitsi-meet.templates
vendored
@@ -10,20 +10,23 @@ _Description: SSL certificate for the Jitsi Meet instance
|
||||
|
||||
Template: jitsi-meet/cert-path-key
|
||||
Type: string
|
||||
Default: ${default-key}
|
||||
_Description: Full local server path to the SSL key file:
|
||||
The full path to the SSL key file on the server.
|
||||
If it has not been uploaded, now is a good time to do so.
|
||||
|
||||
Template: jitsi-meet/cert-path-crt
|
||||
Type: string
|
||||
Default: ${default-crt}
|
||||
_Description: Full local server path to the SSL certificate file:
|
||||
The full path to the SSL certificate file on the server.
|
||||
If you haven't uploaded it, now is a good time to upload it in another console.
|
||||
|
||||
Template: jitsi-meet/jvb-hostname
|
||||
Type: string
|
||||
Default: ${default-key}
|
||||
_Description: The hostname of the current installation:
|
||||
The value for the hostname that is set in Jitsi Videobridge installation.
|
||||
|
||||
Template: jitsi-meet/jvb-serve
|
||||
Type: boolean
|
||||
Default: false
|
||||
_Description: for internal use
|
||||
for internal use.
|
||||
|
||||
11
debian/patches/jquery-package
vendored
11
debian/patches/jquery-package
vendored
@@ -1,15 +1,18 @@
|
||||
Description: Update the used js files for jquery to generic ones, to be able to use local system installed version (through symlinks).
|
||||
Index: jitsi-meet/index.html
|
||||
===================================================================
|
||||
--- jitsi-meet.orig/index.html
|
||||
+++ jitsi-meet/index.html
|
||||
@@ -9,12 +9,12 @@
|
||||
<meta itemprop="name" content="Jitsi Meet"/>
|
||||
@@ -10,14 +10,14 @@
|
||||
<meta itemprop="description" content="Join a WebRTC video conference powered by the Jitsi Videobridge"/>
|
||||
<meta itemprop="image" content="/images/jitsilogo.png"/>
|
||||
<script src="https://api.callstats.io/static/callstats.min.js"></script>
|
||||
- <script src="libs/jquery-2.1.1.min.js"></script>
|
||||
+ <script src="libs/jquery.min.js"></script>
|
||||
<script src="config.js?v=6"></script><!-- adapt to your needs, i.e. set hosts and bosh path -->
|
||||
<script src="libs/strophe/strophe.min.js?v=1"></script>
|
||||
<script src="https://cdn.socket.io/socket.io-1.2.0.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jsSHA/1.5.0/sha.js"></script>
|
||||
<script src="config.js?v=11"></script><!-- adapt to your needs, i.e. set hosts and bosh path -->
|
||||
<script src="libs/strophe/strophe.min.js?v=2"></script>
|
||||
<script src="libs/strophe/strophe.disco.min.js?v=1"></script>
|
||||
<script src="libs/strophe/strophe.caps.jsonly.min.js?v=1"></script>
|
||||
- <script src="libs/jquery-ui.js"></script>
|
||||
|
||||
19
debian/po/templates.pot
vendored
19
debian/po/templates.pot
vendored
@@ -1,7 +1,3 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
@@ -74,7 +70,6 @@ msgid ""
|
||||
"uploaded it, now is a good time to upload it in another console."
|
||||
msgstr ""
|
||||
|
||||
|
||||
#. Type: string
|
||||
#. Description
|
||||
#: ../jitsi-meet.templates:4001
|
||||
@@ -87,3 +82,17 @@ msgstr ""
|
||||
msgid ""
|
||||
"The value for the hostname that is set in Jitsi Videobridge installation."
|
||||
msgstr ""
|
||||
|
||||
|
||||
#. Type: string
|
||||
#. Description
|
||||
#: ../jitsi-meet.templates:5001
|
||||
msgid "for internal use"
|
||||
msgstr ""
|
||||
|
||||
#. Type: string
|
||||
#. Description
|
||||
#: ../jitsi-meet.templates:5001
|
||||
msgid ""
|
||||
"Jitsi Videobridge installation can use its internal jetty to serve static meet pages."
|
||||
msgstr ""
|
||||
|
||||
2
debian/watch
vendored
2
debian/watch
vendored
@@ -1,2 +1,2 @@
|
||||
version=3
|
||||
https://github.com/jitsi/jitsi-meet/releases/ /jitsi/jitsi-meet/archive/(\S+)\.tar\.gz
|
||||
opts="uversionmangle=s/^/1.0./" https://github.com/jitsi/jitsi-meet/releases/ /jitsi/jitsi-meet/archive/(\S+)\.tar\.gz
|
||||
12
doc/adding-an-icon.md
Normal file
12
doc/adding-an-icon.md
Normal file
@@ -0,0 +1,12 @@
|
||||
### Adding an icon to the font file (e.g. for the floating menu)
|
||||
1. Go to https://icomoon.io/app/
|
||||
2. Go to "Manage Projects" from the menu on the top left.
|
||||
3. Use "Import project" and select <code>fonts/selection.json</code> from Jitsi Meet.
|
||||
4. Import icons (e.g. svg files) using the "import items" button.
|
||||
5. Go to "generate font" and make sure the identifiers for the new icons are correct.
|
||||
6. Download the result in a zip file using the "download" button.
|
||||
7. Copy <code>selection.json</code> and <code>fonts/jitsi.*</code> from the zip file to <code>fonts/</code> in Jitsi Meet
|
||||
8. Copy the class for the new icon from <code>style.css</code> in the zip file to <code>css/font.css</code> in Jitsi Meet (do *not* copy the whole file)
|
||||
|
||||
Sample commit: https://github.com/jitsi/jitsi-meet/commit/68bc819b89aec12364fcf07b81efa83a1900eed6
|
||||
|
||||
19
doc/api.md
19
doc/api.md
@@ -29,6 +29,13 @@ constructor.
|
||||
```
|
||||
If you don't specify room the user will enter in new conference with random room name.
|
||||
|
||||
You can overwrite options set in config.js and interface_config.js. For example, to enable the film-strip-only interface mode and disable simulcast, you can use:
|
||||
```javascript
|
||||
var configOverwrite = {enableSimulcast: false};
|
||||
var interfaceConfigOverwrite = {filmStripOnly: true};
|
||||
var api = new JitsiMeetExternalAPI(domain, room, width, height, htmlElement, true, configOverwrite, interfaceConfigOverwrite);
|
||||
```
|
||||
|
||||
Controlling embedded Jitsi Meet Conference
|
||||
=========
|
||||
|
||||
@@ -49,13 +56,13 @@ the new display name to be set
|
||||
```
|
||||
api.executeCommand('displayName', ['New Nickname']);
|
||||
```
|
||||
* **muteAudio** - mutes / unmutes the audio for the local participant. No arguments are required.
|
||||
* **toggleAudio** - mutes / unmutes the audio for the local participant. No arguments are required.
|
||||
```
|
||||
api.executeCommand('muteAudio', [])
|
||||
api.executeCommand('toggleAudio', [])
|
||||
```
|
||||
* **muteVideo** - mutes / unmutes the video for the local participant. No arguments are required.
|
||||
* **toggleVideo** - mutes / unmutes the video for the local participant. No arguments are required.
|
||||
```
|
||||
api.executeCommand('muteVideo', [])
|
||||
api.executeCommand('toggleVideo', [])
|
||||
```
|
||||
* **toggleFilmStrip** - hides / shows the film strip. No arguments are required.
|
||||
```
|
||||
@@ -78,7 +85,7 @@ The ```commands``` parameter is object with keys the names of the commands and v
|
||||
commands.
|
||||
|
||||
```
|
||||
api.executeCommands({displayName: ['nickname'], muteAudio: []});
|
||||
api.executeCommands({displayName: ['nickname'], toggleAudio: []});
|
||||
```
|
||||
|
||||
You can add event listeners to the embedded Jitsi Meet using ```addEventListener``` method.
|
||||
@@ -166,4 +173,4 @@ You can remove the embedded Jitsi Meet Conference with the following code:
|
||||
api.dispose()
|
||||
```
|
||||
|
||||
It is a good practice to remove the conference before the page is unloaded.
|
||||
It is a good practice to remove the conference before the page is unloaded.
|
||||
|
||||
29
doc/influxdb.md
Normal file
29
doc/influxdb.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# Overview
|
||||
Jitsi Meet supports logging to an [InfluxDB](http://influxdb.com/) database.
|
||||
|
||||
# Configuration
|
||||
The following needs to be done to enable this functionality.
|
||||
|
||||
## Install InfluxDB
|
||||
The details are outside the scope of the document, see http://influxdb.com/download/ .
|
||||
|
||||
## Create an InfluxDB database
|
||||
Use the InfluxDB admin interface (running on port 8083) and create a database. In this example we name it <code>jitsi_database</code>
|
||||
|
||||
## Enable logging for Jitsi Videobridge
|
||||
Add the following properties to <code>/usr/share/jitsi-videobridge/.sip-communicator/sip-communicator.properties</code>.
|
||||
|
||||
- org.jitsi.videobridge.log.INFLUX_DB_ENABLED=true
|
||||
- org.jitsi.videobridge.log.INFLUX_URL_BASE=http://influxdb.example.com:8086
|
||||
- org.jitsi.videobridge.log.INFLUX_DATABASE=jitsi_database
|
||||
- org.jitsi.videobridge.log.INFLUX_USER=user
|
||||
- org.jitsi.videobridge.log.INFLUX_PASS=pass
|
||||
|
||||
## Enable logging for Jicofo
|
||||
Add the same properties as above to <code>/usr/share/jicofo/.sip-communicator/sip-communicator.properties</code>.
|
||||
|
||||
## Enable logging for Jitsi Meet itself
|
||||
Change "logStats" to "true" in <code>/etc/jitsi/meet/you-domain.config.js</code> or the <code>config.js</code> file used in your installation.
|
||||
|
||||
# User interface
|
||||
You can explore the database using the [Jiloin](https://github.com/jitsi/jiloin) web interface.
|
||||
@@ -1,39 +1,21 @@
|
||||
# Server Installation for Jitsi Meet
|
||||
|
||||
This describes configuring a server `jitsi.example.com`. You will need to
|
||||
This describes configuring a server `jitsi.example.com` running Debian or a Debian Derivative. You will need to
|
||||
change references to that to match your host, and generate some passwords for
|
||||
`YOURSECRET1`, `YOURSECRET2`, `YOURSECRET3` and `YOURSECRET4`.
|
||||
|
||||
There are also some complete [example config files](https://github.com/jitsi/jitsi-meet/tree/master/doc/example-config-files/) available, mentioned in each section.
|
||||
|
||||
## Install prosody and otalk modules
|
||||
## Install prosody
|
||||
```sh
|
||||
apt-get install lsb-release
|
||||
echo deb http://packages.prosody.im/debian $(lsb_release -sc) main | sudo tee -a /etc/apt/sources.list
|
||||
wget --no-check-certificate https://prosody.im/files/prosody-debian-packages.key -O- | sudo apt-key add -
|
||||
apt-get update
|
||||
apt-get install prosody-trunk
|
||||
apt-get install git lua-zlib lua-sec-prosody lua-dbi-sqlite3 liblua5.1-bitop-dev liblua5.1-bitop0
|
||||
git clone https://github.com/andyet/otalk-server.git
|
||||
cd otalk-server
|
||||
cp -r mod* /usr/lib/prosody/modules
|
||||
apt-get install prosody
|
||||
```
|
||||
|
||||
## Configure prosody
|
||||
Modify the config file in `/etc/prosody/prosody.cfg.lua` (see also the example config file):
|
||||
Add config file in `/etc/prosody/conf.avail/jitsi.example.com.cfg.lua` :
|
||||
|
||||
- add your domain virtual host section:
|
||||
|
||||
- modules to enable/add: compression, bosh, smacks, carbons, mam, lastactivity, offline, pubsub, adhoc, websocket, http_altconnect
|
||||
- comment out: `c2s_require_encryption = true`, and `s2s_secure_auth = false`
|
||||
- change `authentication = "internal_hashed"`
|
||||
- add this:
|
||||
```
|
||||
daemonize = true
|
||||
cross_domain_bosh = true;
|
||||
storage = {archive2 = "sql2"}
|
||||
sql = { driver = "SQLite3", database = "prosody.sqlite" }
|
||||
default_archive_policy = "roster"
|
||||
```
|
||||
- configure your domain by editing the example.com virtual host section section:
|
||||
```
|
||||
VirtualHost "jitsi.example.com"
|
||||
authentication = "anonymous"
|
||||
@@ -41,15 +23,15 @@ VirtualHost "jitsi.example.com"
|
||||
key = "/var/lib/prosody/jitsi.example.com.key";
|
||||
certificate = "/var/lib/prosody/jitsi.example.com.crt";
|
||||
}
|
||||
modules_enabled = {
|
||||
"bosh";
|
||||
"pubsub";
|
||||
}
|
||||
```
|
||||
- add domain with authentication for conference focus user:
|
||||
```
|
||||
VirtualHost "auth.jitsi.example.com"
|
||||
authentication = "internal_plain"
|
||||
ssl = {
|
||||
key = "/var/lib/prosody/jitsi.example.com.key";
|
||||
certificate = "/var/lib/prosody/jitsi.example.com.crt";
|
||||
}
|
||||
```
|
||||
- add focus user to server admins:
|
||||
```
|
||||
@@ -64,6 +46,11 @@ Component "focus.jitsi.example.com"
|
||||
component_secret = "YOURSECRET2"
|
||||
```
|
||||
|
||||
Add link for the added configuration
|
||||
```sh
|
||||
ln -s /etc/prosody/conf.avail/jitsi.example.com.cfg.lua /etc/prosody/conf.d/jitsi.example.com.cfg.lua
|
||||
```
|
||||
|
||||
Generate certs for the domain:
|
||||
```sh
|
||||
prosodyctl cert generate jitsi.example.com
|
||||
@@ -84,39 +71,28 @@ prosodyctl restart
|
||||
apt-get install nginx
|
||||
```
|
||||
|
||||
Add nginx config for domain in `/etc/nginx/nginx.conf`:
|
||||
```
|
||||
tcp_nopush on;
|
||||
types_hash_max_size 2048;
|
||||
server_names_hash_bucket_size 64;
|
||||
```
|
||||
|
||||
Add a new file `jitsi.example.com` in `/etc/nginx/sites-available` (see also the example config file):
|
||||
```
|
||||
server_names_hash_bucket_size 64;
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name jitsi.example.com;
|
||||
# set the root
|
||||
root /srv/jitsi.example.com;
|
||||
index index.html;
|
||||
location ~ ^/([a-zA-Z0-9]+)$ {
|
||||
location ~ ^/([a-zA-Z0-9=\?]+)$ {
|
||||
rewrite ^/(.*)$ / break;
|
||||
}
|
||||
location / {
|
||||
ssi on;
|
||||
}
|
||||
# BOSH
|
||||
location /http-bind {
|
||||
proxy_pass http://localhost:5280/http-bind;
|
||||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
proxy_set_header Host $http_host;
|
||||
}
|
||||
# xmpp websockets
|
||||
location /xmpp-websocket {
|
||||
proxy_pass http://localhost:5280;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_set_header Host $host;
|
||||
tcp_nodelay on;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -126,12 +102,6 @@ cd /etc/nginx/sites-enabled
|
||||
ln -s ../sites-available/jitsi.example.com jitsi.example.com
|
||||
```
|
||||
|
||||
## Fix firewall if needed
|
||||
```sh
|
||||
ufw allow 80
|
||||
ufw allow 5222
|
||||
```
|
||||
|
||||
## Install Jitsi Videobridge
|
||||
```sh
|
||||
wget https://download.jitsi.org/jitsi-videobridge/linux/jitsi-videobridge-linux-{arch-buildnum}.zip
|
||||
@@ -143,6 +113,8 @@ Install JRE if missing:
|
||||
apt-get install default-jre
|
||||
```
|
||||
|
||||
_NOTE: When installing on older Debian releases keep in mind that you need JRE >= 1.7._
|
||||
|
||||
In the user home that will be starting Jitsi Videobridge create `.sip-communicator` folder and add the file `sip-communicator.properties` with one line in it:
|
||||
```
|
||||
org.jitsi.impl.neomedia.transform.srtp.SRTPCryptoContext.checkReplay=false
|
||||
@@ -157,7 +129,15 @@ Or autostart it by adding the line in `/etc/rc.local`:
|
||||
/bin/bash /root/jitsi-videobridge-linux-{arch-buildnum}/jvb.sh --host=localhost --domain=jitsi.example.com --port=5347 --secret=YOURSECRET1 </dev/null >> /var/log/jvb.log 2>&1
|
||||
```
|
||||
|
||||
## Install Jitsi Conference Focus(jicofo)
|
||||
## Install Jitsi Conference Focus (jicofo)
|
||||
|
||||
Install JDK and Ant if missing:
|
||||
```
|
||||
apt-get install default-jdk ant
|
||||
```
|
||||
|
||||
_NOTE: When installing on older Debian releases keep in mind that you need JDK >= 1.7._
|
||||
|
||||
Clone source from Github repo:
|
||||
```sh
|
||||
git clone https://github.com/jitsi/jicofo.git
|
||||
@@ -170,7 +150,7 @@ ant dist.{os-name}
|
||||
Run jicofo:
|
||||
```sh
|
||||
cd dist/{os-name}'
|
||||
./jicofo.sh --domain=jitsi.exmaple.com --secret=YOURSECRET2 --user_domain=auth.jitsi.example.com --user_name=focus --user_password=YOURSECRET3
|
||||
./jicofo.sh --domain=jitsi.example.com --secret=YOURSECRET2 --user_domain=auth.jitsi.example.com --user_name=focus --user_password=YOURSECRET3
|
||||
```
|
||||
|
||||
## Deploy Jitsi Meet
|
||||
@@ -202,54 +182,6 @@ Restart nginx to get the new configuration:
|
||||
invoke-rc.d nginx restart
|
||||
```
|
||||
|
||||
|
||||
## Install [Turn server](https://github.com/andyet/otalk-server/tree/master/restund)
|
||||
```sh
|
||||
apt-get install make gcc
|
||||
wget http://creytiv.com/pub/re-0.4.7.tar.gz
|
||||
tar zxvf re-0.4.7.tar.gz
|
||||
ln -s re-0.4.7 re
|
||||
cd re-0.4.7
|
||||
sudo make install PREFIX=/usr
|
||||
cd ..
|
||||
wget http://creytiv.com/pub/restund-0.4.2.tar.gz
|
||||
wget https://raw.github.com/andyet/otalk-server/master/restund/restund-auth.patch
|
||||
tar zxvf restund-0.4.2.tar.gz
|
||||
cd restund-0.4.2/
|
||||
patch -p1 < ../restund-auth.patch
|
||||
sudo make install PREFIX=/usr
|
||||
cp debian/restund.init /etc/init.d/restund
|
||||
chmod +x /etc/init.d/restund
|
||||
cd /etc
|
||||
wget https://raw.github.com/andyet/otalk-server/master/restund/restund.conf
|
||||
```
|
||||
|
||||
Configure addresses and ports as desired, and the password to be configured in prosody:
|
||||
```
|
||||
realm jitsi.example.com
|
||||
# share this with your prosody server
|
||||
auth_shared YOURSECRET4
|
||||
|
||||
# modules
|
||||
module_path /usr/lib/restund/modules
|
||||
turn_relay_addr [turn ip address]
|
||||
```
|
||||
|
||||
Configure prosody to use it in `/etc/prosody/prosody.cfg.lua`. Add to your virtual host:
|
||||
```
|
||||
turncredentials_secret = "YOURSECRET4";
|
||||
turncredentials = {
|
||||
{ type = "turn", host = "turn.address.ip.configured", port = 3478, transport = "tcp" }
|
||||
}
|
||||
```
|
||||
|
||||
Add turncredentials module in the "modules_enabled" section
|
||||
|
||||
Reload prosody if needed
|
||||
```
|
||||
prosodyctl restart
|
||||
```
|
||||
|
||||
## Running behind NAT
|
||||
In case of videobridge being installed on a machine behind NAT, add the following extra lines to the file `~/.sip-communicator/sip-communicator.properties` (in the home of user running the videobridge):
|
||||
```
|
||||
@@ -288,4 +220,4 @@ enableRecording: true
|
||||
|
||||
Restart jitsi-videobridge and start a new conference (making sure that the page
|
||||
is reloaded with the new config.js) -- the organizer of the conference should
|
||||
now have a "recoriding" button in the floating menu, near the "mute" button.
|
||||
now have a "recording" button in the floating menu, near the "mute" button.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Jitsi Meet quick install
|
||||
|
||||
This documents decribes the needed steps for quick Jitsi Meet installation on a Debian based GNU/Linux system.
|
||||
This document describes the required steps for a quick Jitsi Meet installation on a Debian based GNU/Linux system.
|
||||
|
||||
N.B.: All commands are supposed to be run by root. If you are logged in as a regular user with sudo rights, please prepend ___sudo___ to each of the commands.
|
||||
|
||||
@@ -24,13 +24,13 @@ apt-get update
|
||||
apt-get -y install jitsi-meet
|
||||
```
|
||||
|
||||
During the installation you'll be asked to enter the hostname of the Jitsi Meet instance. If you have a FQDN hostname for the instance already set ip in DNS, enter it there. If you don't have a resolvable hostname, you can enter the IP address of the machine (if it is static or doesn't change).
|
||||
During the installation, you will be asked to enter the hostname of the Jitsi Meet instance. If you have a FQDN hostname for the instance already set up in DNS, enter it there. If you don't have a resolvable hostname, you can enter the IP address of the machine (if it is static or doesn't change).
|
||||
|
||||
This hostname (or IP address) will be used for virtualhost configuration inside the Jitsi Meet and also you and your correspondents will be using it to access the web conferences.
|
||||
This hostname (or IP address) will be used for virtualhost configuration inside the Jitsi Meet and also, you and your correspondents will be using it to access the web conferences.
|
||||
|
||||
### Open a conference
|
||||
|
||||
Launch a web broswer (Chrome, Chromium or latest Opera) and enter in the URL bar the hostname (or IP address) you used in the previous step.
|
||||
Launch a web browser (Chrome, Chromium or latest Opera) and enter in the URL bar the hostname (or IP address) you used in the previous step.
|
||||
|
||||
Confirm that you trust the self-signed certificate of the newly installed Jitsi Meet.
|
||||
|
||||
@@ -50,7 +50,7 @@ wget https://download.jitsi.org/jigasi_1.0-1_amd64.deb
|
||||
dpkg -i jigasi_1.0-1_amd64.deb
|
||||
```
|
||||
|
||||
During the installation you'll be asked to enter your SIP account and password. This account will be used to invite the other SIP participants.
|
||||
During the installation, you will be asked to enter your SIP account and password. This account will be used to invite the other SIP participants.
|
||||
|
||||
### Reload Jitsi Meet
|
||||
|
||||
@@ -58,19 +58,17 @@ Launch again a browser with the Jitsi Meet URL and you'll see a telephone icon o
|
||||
|
||||
Enjoy!
|
||||
|
||||
## Deinstall
|
||||
## Uninstall
|
||||
|
||||
```sh
|
||||
apt-get purge jigasi jitsi-meet jicofo jitsi-videobridge
|
||||
```
|
||||
|
||||
Somethimes the following packages will fail to uninstall properly:
|
||||
Sometimes the following packages will fail to uninstall properly:
|
||||
|
||||
- jigasi
|
||||
- jitsi-videobridge
|
||||
|
||||
When this happens, just run the deinstall command a second time and it should be ok.
|
||||
When this happens, just run the uninstall command a second time and it should be ok.
|
||||
|
||||
The reason for failure is that not allways the daemons are stopped right away, there is a timeout before the actual stop. And if the unistall script goes on before the services' stop, there is an error.
|
||||
|
||||
The second run of the deinstall command fixes this, as by then the jigasi or jvb daemons are already stopped.
|
||||
The reason for failure is that sometimes, the uninstall script is faster than the process that stops the daemons. The second run of the uninstall command fixes this, as by then the jigasi or jvb daemons are already stopped.
|
||||
|
||||
119
external_api.js
119
external_api.js
@@ -23,17 +23,21 @@ var JitsiMeetExternalAPI = (function()
|
||||
* @param width width of the iframe
|
||||
* @param height height of the iframe
|
||||
* @param parent_node the node that will contain the iframe
|
||||
* @param filmStripOnly if the value is true only the small videos will be
|
||||
* visible.
|
||||
* @constructor
|
||||
*/
|
||||
function JitsiMeetExternalAPI(domain, room_name, width, height, parent_node)
|
||||
{
|
||||
function JitsiMeetExternalAPI(domain, room_name, width, height, parentNode,
|
||||
configOverwrite, interfaceConfigOverwrite) {
|
||||
if((!width || width < MIN_WIDTH) && !filmStripOnly)
|
||||
width = MIN_WIDTH;
|
||||
if((!height || height < MIN_HEIGHT) && !filmStripOnly)
|
||||
height = MIN_HEIGHT;
|
||||
|
||||
this.parentNode = null;
|
||||
if(parent_node)
|
||||
{
|
||||
this.parentNode = parent_node;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (parentNode) {
|
||||
this.parentNode = parentNode;
|
||||
} else {
|
||||
var scriptTag = document.scripts[document.scripts.length - 1];
|
||||
this.parentNode = scriptTag.parentNode;
|
||||
}
|
||||
@@ -41,17 +45,35 @@ var JitsiMeetExternalAPI = (function()
|
||||
this.iframeHolder =
|
||||
this.parentNode.appendChild(document.createElement("div"));
|
||||
this.iframeHolder.id = "jitsiConference" + JitsiMeetExternalAPI.id;
|
||||
if(width < MIN_WIDTH)
|
||||
width = MIN_WIDTH;
|
||||
if(height < MIN_HEIGHT)
|
||||
height = MIN_HEIGHT;
|
||||
this.iframeHolder.style.width = width + "px";
|
||||
this.iframeHolder.style.height = height + "px";
|
||||
if(width)
|
||||
this.iframeHolder.style.width = width + "px";
|
||||
if(height)
|
||||
this.iframeHolder.style.height = height + "px";
|
||||
this.frameName = "jitsiConferenceFrame" + JitsiMeetExternalAPI.id;
|
||||
this.url = "//" + domain + "/";
|
||||
if(room_name)
|
||||
this.url += room_name;
|
||||
this.url += "#external";
|
||||
this.url += "#external=true";
|
||||
|
||||
var key;
|
||||
if (configOverwrite) {
|
||||
for (key in configOverwrite) {
|
||||
if (!configOverwrite.hasOwnProperty(key) ||
|
||||
typeof key !== 'string')
|
||||
continue;
|
||||
this.url += "&config." + key + "=" + configOverwrite[key];
|
||||
}
|
||||
}
|
||||
|
||||
if (interfaceConfigOverwrite) {
|
||||
for (key in interfaceConfigOverwrite) {
|
||||
if (!interfaceConfigOverwrite.hasOwnProperty(key) ||
|
||||
typeof key !== 'string')
|
||||
continue;
|
||||
this.url += "&interfaceConfig." + key + "=" + interfaceConfigOverwrite[key];
|
||||
}
|
||||
}
|
||||
|
||||
JitsiMeetExternalAPI.id++;
|
||||
|
||||
this.frame = document.createElement("iframe");
|
||||
@@ -80,15 +102,12 @@ var JitsiMeetExternalAPI = (function()
|
||||
* Sends the passed object to Jitsi Meet
|
||||
* @param object the object to be sent
|
||||
*/
|
||||
JitsiMeetExternalAPI.prototype.sendMessage = function(object)
|
||||
{
|
||||
if(this.frameLoaded)
|
||||
{
|
||||
JitsiMeetExternalAPI.prototype.sendMessage = function(object) {
|
||||
if (this.frameLoaded) {
|
||||
this.frame.contentWindow.postMessage(
|
||||
JSON.stringify(object), this.frame.src);
|
||||
}
|
||||
else
|
||||
{
|
||||
else {
|
||||
this.initialCommands.push(object);
|
||||
}
|
||||
|
||||
@@ -98,8 +117,8 @@ var JitsiMeetExternalAPI = (function()
|
||||
* Executes command. The available commands are:
|
||||
* displayName - sets the display name of the local participant to the value
|
||||
* passed in the arguments array.
|
||||
* muteAudio - mutes / unmutes audio with no arguments
|
||||
* muteVideo - mutes / unmutes video with no arguments
|
||||
* toggleAudio - mutes / unmutes audio with no arguments
|
||||
* toggleVideo - mutes / unmutes video with no arguments
|
||||
* filmStrip - hides / shows the film strip with no arguments
|
||||
* If the command doesn't require any arguments the parameter should be set
|
||||
* to empty array or it may be omitted.
|
||||
@@ -107,10 +126,9 @@ var JitsiMeetExternalAPI = (function()
|
||||
* @param arguments array of arguments
|
||||
*/
|
||||
JitsiMeetExternalAPI.prototype.executeCommand = function(name,
|
||||
argumentsList)
|
||||
{
|
||||
argumentsList) {
|
||||
var argumentsArray = argumentsList;
|
||||
if(!argumentsArray)
|
||||
if (!argumentsArray)
|
||||
argumentsArray = [];
|
||||
var object = {type: "command", action: "execute"};
|
||||
object[name] = argumentsArray;
|
||||
@@ -121,8 +139,8 @@ var JitsiMeetExternalAPI = (function()
|
||||
* Executes commands. The available commands are:
|
||||
* displayName - sets the display name of the local participant to the value
|
||||
* passed in the arguments array.
|
||||
* muteAudio - mutes / unmutes audio with no arguments
|
||||
* muteVideo - mutes / unmutes video with no arguments
|
||||
* toggleAudio - mutes / unmutes audio with no arguments
|
||||
* toggleVideo - mutes / unmutes video with no arguments
|
||||
* filmStrip - hides / shows the film strip with no arguments
|
||||
* @param object the object with commands to be executed. The keys of the
|
||||
* object are the commands that will be executed and the values are the
|
||||
@@ -135,8 +153,8 @@ var JitsiMeetExternalAPI = (function()
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds event listeners to Meet Jitsi. The object key should be the name of the
|
||||
* event and value - the listener.
|
||||
* Adds event listeners to Meet Jitsi. The object key should be the name of
|
||||
* the event and value - the listener.
|
||||
* Currently we support the following
|
||||
* events:
|
||||
* incomingMessage - receives event notifications about incoming
|
||||
@@ -170,8 +188,7 @@ var JitsiMeetExternalAPI = (function()
|
||||
* @param object
|
||||
*/
|
||||
JitsiMeetExternalAPI.prototype.addEventListeners
|
||||
= function (object)
|
||||
{
|
||||
= function (object) {
|
||||
|
||||
var message = {type: "event", action: "add", events: []};
|
||||
for(var i in object)
|
||||
@@ -217,8 +234,7 @@ var JitsiMeetExternalAPI = (function()
|
||||
* @param listener the listener
|
||||
*/
|
||||
JitsiMeetExternalAPI.prototype.addEventListener
|
||||
= function (event, listener)
|
||||
{
|
||||
= function (event, listener) {
|
||||
|
||||
var message = {type: "event", action: "add", events: [event]};
|
||||
this.eventHandlers[event] = listener;
|
||||
@@ -230,8 +246,7 @@ var JitsiMeetExternalAPI = (function()
|
||||
* @param event the name of the event.
|
||||
*/
|
||||
JitsiMeetExternalAPI.prototype.removeEventListener
|
||||
= function (event)
|
||||
{
|
||||
= function (event) {
|
||||
if(!this.eventHandlers[event])
|
||||
{
|
||||
console.error("The event " + event + " is not registered.");
|
||||
@@ -247,8 +262,7 @@ var JitsiMeetExternalAPI = (function()
|
||||
* @param events array with the names of the events.
|
||||
*/
|
||||
JitsiMeetExternalAPI.prototype.removeEventListeners
|
||||
= function (events)
|
||||
{
|
||||
= function (events) {
|
||||
var eventsArray = [];
|
||||
for(var i = 0; i < events.length; i++)
|
||||
{
|
||||
@@ -274,8 +288,7 @@ var JitsiMeetExternalAPI = (function()
|
||||
* Processes message events sent from Jitsi Meet
|
||||
* @param event the event
|
||||
*/
|
||||
JitsiMeetExternalAPI.prototype.processMessage = function(event)
|
||||
{
|
||||
JitsiMeetExternalAPI.prototype.processMessage = function(event) {
|
||||
var message;
|
||||
try {
|
||||
message = JSON.parse(event.data);
|
||||
@@ -285,18 +298,15 @@ var JitsiMeetExternalAPI = (function()
|
||||
console.error("Message without type is received.");
|
||||
return;
|
||||
}
|
||||
switch (message.type)
|
||||
{
|
||||
switch (message.type) {
|
||||
case "system":
|
||||
if(message.loaded)
|
||||
{
|
||||
if(message.loaded) {
|
||||
this.onFrameLoaded();
|
||||
}
|
||||
break;
|
||||
case "event":
|
||||
if(message.action != "result" ||
|
||||
!message.event || !this.eventHandlers[message.event])
|
||||
{
|
||||
!message.event || !this.eventHandlers[message.event]) {
|
||||
console.warn("The received event cannot be parsed.");
|
||||
return;
|
||||
}
|
||||
@@ -306,8 +316,6 @@ var JitsiMeetExternalAPI = (function()
|
||||
console.error("Unknown message type.");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -316,8 +324,7 @@ var JitsiMeetExternalAPI = (function()
|
||||
*/
|
||||
JitsiMeetExternalAPI.prototype.onFrameLoaded = function () {
|
||||
this.frameLoaded = true;
|
||||
for (var i = 0; i < this.initialCommands.length; i++)
|
||||
{
|
||||
for (var i = 0; i < this.initialCommands.length; i++) {
|
||||
this.sendMessage(this.initialCommands[i]);
|
||||
}
|
||||
this.initialCommands = null;
|
||||
@@ -331,13 +338,11 @@ var JitsiMeetExternalAPI = (function()
|
||||
this.eventListener = function (event) {
|
||||
self.processMessage(event);
|
||||
};
|
||||
if (window.addEventListener)
|
||||
{
|
||||
if (window.addEventListener) {
|
||||
window.addEventListener('message',
|
||||
this.eventListener, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
else {
|
||||
window.attachEvent('onmessage', this.eventListener);
|
||||
}
|
||||
};
|
||||
@@ -346,13 +351,11 @@ var JitsiMeetExternalAPI = (function()
|
||||
* Removes the listeners and removes the Jitsi Meet frame.
|
||||
*/
|
||||
JitsiMeetExternalAPI.prototype.dispose = function () {
|
||||
if (window.removeEventListener)
|
||||
{
|
||||
if (window.removeEventListener) {
|
||||
window.removeEventListener('message',
|
||||
this.eventListener, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
else {
|
||||
window.detachEvent('onmessage',
|
||||
this.eventListener);
|
||||
}
|
||||
|
||||
BIN
fonts/jitsi.eot
BIN
fonts/jitsi.eot
Binary file not shown.
@@ -35,4 +35,5 @@
|
||||
<glyph unicode="" d="M46.993 961.7c461.234 0 553.793 0 1015.024 0 35.919 0 53.356-25.959 53.356-57.959-0.581-303.259-0.325-606.488-0.449-909.809 0-43.984-13.203-57.058-57.703-57.058-443.072-0.126-556.453-0.126-999.553 0-44.534 0-57.799 13.009-57.799 57.058-0.098 303.257 0.485 608.072-0.093 911.329-0.034 26.21 11.301 53.761 47.217 56.439zM311.405 450.298c0-119.045-0.072-172.168 0.057-291.249 0.036-50.043 11.208-61.050 62.12-61.050 233.352 0 137.075 0 370.522 0 47.075 0 59.249 11.982 59.249 58.095 0.126 239.111 0.126 346.338 0 585.389 0 48.138-10.687 58.991-57.768 58.991-235.323 0.101-140.844 0.101-376.157 0-47.044 0-57.93-11.043-57.966-58.89-0.129-119.109-0.057-172.209-0.057-291.287zM153.944 838.566c-74.929-0.062-66.687 5.958-66.845-66.685-0.201-63.95-7.054-63.534 62.528-63.372 72.999 0.194 67.201-3.764 67.302 67.554 0 67.722 4.087 62.595-62.985 62.502zM963.644 838.566c-71.159-0.034-65.56 6.185-65.751-65.364-0.129-67.302-4.508-64.693 64.528-64.693 73.089 0 65.299-2.031 65.299 66.238-0.003 68.646 6.956 63.911-64.076 63.818zM216.828 122.408c0.359 73.094 4.639 66.914-67.358 67.17-68.104 0.191-62.569 2.763-62.407-63.31 0.129-73.476-6.954-66.52 67.074-66.649 66.042-0.065 63.142-6.056 62.691 62.789zM1027.718 124.4c0.134 68.334 6.443 65.304-63.297 65.178-70.132-0.132-66.656 5.793-66.527-65.304 0.129-70.645-4.384-64.721 63.756-64.657 71.995 0.132 66.202-6.698 66.068 64.783zM1027.718 342.077c0 70.55 7.219 66.842-67.485 66.522-0.898 0-1.873 0-2.838 0-59.375 0-59.375 0-59.375-58.023 0-77.922-6.443-69.936 69.293-70.196 66.076-0.387 60.539-3.091 60.405 61.697zM151.307 489.873c68.295-0.163 65.815-5.568 65.624 62.982-0.194 71.128 4.895 64.917-66.014 65.010-69.905 0.101-63.813 4.704-63.885-63.978-0.062-67.431-5.7-64.463 64.275-64.014zM961.263 489.873c72.511-0.258 66.589-4.603 66.455 64.494 0 68.558 6.185 63.537-64.267 63.498-70.196-0.028-65.686 6.053-65.498-65.493 0.132-62.5 0.067-62.5 63.31-62.5zM150.399 280.38c71.004 0 66.659-6.567 66.466 64.528-0.163 63.694-0.036 63.501-65.013 63.756-70.805 0.258-64.822 2.673-64.822-63.756 0.036-69.167-5.919-64.788 63.369-64.528z" horiz-adv-x="1115" />
|
||||
<glyph unicode="" d="M3.881 146.835h220.26v-210.835h-220.26v210.835zM308.817 350.143h220.27v-414.143h-220.27v414.143zM613.764 553.412h220.268v-617.412h-220.268v617.412zM918.685 756.715h220.265v-820.715h-220.265v820.715zM1223.629 960h220.263v-1024h-220.263v1024z" horiz-adv-x="1444" />
|
||||
<glyph unicode="" d="M526.071 234.749c-28.637-30.869-56.465-60.861-84.282-90.859-51.578-55.636-103.047-111.376-154.842-166.832-7.606-8.135-15.958-16.1-25.317-22.012-28.075-17.708-58.31-18.090-88.472-6.492-59.84 23.028-80.004 90.727-59.734 139.234 5.413 12.95 13.721 23.601 23.709 33.173 70.256 67.351 140.506 134.717 210.76 202.077 15.638 14.993 31.264 29.995 47.364 45.45-9.302 9.529-18.386 18.833-27.451 28.137-12.122 12.442-13.234 20.28-5.067 35.498 4.735 8.816 4.789 8.878-2.627 16.198-20.012 19.72-40.168 39.198-63.498 55.188-27.167 18.624-57.161 24.233-89.083 19.849-53.402-7.328-91.609-38.372-121.413-81.046-12.774-18.299-15.365-40.313-17.517-61.875-3.23-32.245-2.415-64.479 2.209-96.597 1.654-11.515-3.863-16.539-13.835-11.175-8.306 4.448-16.095 11.048-22.115 18.353-15.574 18.89-22.223 42.042-27.474 65.395-12.955 57.652-8.86 114.49 12.191 169.495 32.345 84.537 79.743 159.571 145.953 221.932 13.659 12.857 176.841 180.564 202.944 207.021 7.493 7.599 14.895 7.635 22.393 0.028 43.009-43.641 85.985-87.316 128.927-131.029 8.117-8.267 8.019-15.097-0.222-23.49-26.339-26.834-52.726-53.627-79.106-80.419-6.244-6.334-97.34-82.437-73.027-128.816 22.693-25.090 46.196-49.449 69.575-73.904 1.189-1.238 4.686-1.386 6.523-0.632 3.63 1.499 6.848 3.997 10.248 6.066 9.745 5.94 19.545 4.918 27.812-3.083 11.755-11.381 23.405-22.858 35.392-34.59 4.807 4.575 9.939 9.41 15.027 14.294 27.128 26.039 54.272 52.071 81.351 78.146 16.413 15.778 18.652 28.418 11.038 49.658-10.473 29.221-14.356 59.677-13.85 90.624 1.017 61.045 20.438 115.334 61.003 161.416 32.825 37.286 72.054 64.311 121.643 74.325 35.227 7.101 69.139 4.513 100.663-14.026 6.365-3.752 11.908-9.007 17.455-14.005 3.491-3.125 3.153-6.236-0.565-9.98-42.503-42.885-84.772-86.013-127.154-129.035-12.442-12.638-12.356-23.167 0.196-35.914 40.344-40.978 80.597-82.050 120.936-123.052 10.076-10.233 19.537-10.021 29.504 0.134 43.195 44.077 86.449 88.090 129.706 132.118 1.21 1.233 2.572 2.322 5.135 4.624 5.491-5.893 11.895-10.924 15.961-17.406 19.452-30.944 22.608-64.83 17.073-100.25-14.253-91.080-97.188-175.638-197.712-190.123-39.977-5.764-79.372-2.562-118.067 9.031-5.898 1.775-11.541 4.629-17.538 5.829-12.47 2.474-23.872-0.366-32.74-9.877-30.921-33.168-61.674-66.484-92.474-99.758-0.73-0.805-1.349-1.718-0.181-1.099 8.992-10.006 17.354-20.662 27.061-29.94 81.064-77.54 164.91-151.986 250.882-224.063 9.936-8.347 10.274-15.695 1.040-25.1-42.338-43.068-84.689-86.111-127.059-129.154-9.413-9.575-16.846-9.152-25.291 1.295-76.686 94.78-156.8 186.609-239.707 276.002-1.334 1.453-2.562 3.029-4.257 5.042z" horiz-adv-x="1105" />
|
||||
<glyph unicode="" d="M74.418 881.299h239.304v-228.491h-239.304v228.491zM393.455 881.299h239.304v-228.491h-239.304v228.491zM712.494 881.299h239.263v-228.491h-239.263v228.491zM74.418 562.265h239.304v-228.555h-239.304v228.555zM393.455 562.265h239.304v-228.555h-239.304v228.555zM712.494 562.265h239.263v-228.555h-239.263v228.555zM74.418 243.166h239.304v-228.465h-239.304v228.465zM393.455 243.166h239.304v-228.465h-239.304v228.465zM712.494 243.166h239.263v-228.465h-239.263v228.465z" horiz-adv-x="1026" />
|
||||
</font></defs></svg>
|
||||
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 34 KiB |
BIN
fonts/jitsi.ttf
BIN
fonts/jitsi.ttf
Binary file not shown.
BIN
fonts/jitsi.woff
BIN
fonts/jitsi.woff
Binary file not shown.
@@ -1,6 +1,59 @@
|
||||
{
|
||||
"IcoMoonType": "selection",
|
||||
"icons": [
|
||||
{
|
||||
"icon": {
|
||||
"paths": [
|
||||
"M74.418 78.701h239.304v228.491h-239.304v-228.491z",
|
||||
"M393.455 78.701h239.304v228.491h-239.304v-228.491z",
|
||||
"M712.494 78.701h239.263v228.491h-239.263v-228.491z",
|
||||
"M74.418 397.735h239.304v228.555h-239.304v-228.555z",
|
||||
"M393.455 397.735h239.304v228.555h-239.304v-228.555z",
|
||||
"M712.494 397.735h239.263v228.555h-239.263v-228.555z",
|
||||
"M74.418 716.834h239.304v228.465h-239.304v-228.465z",
|
||||
"M393.455 716.834h239.304v228.465h-239.304v-228.465z",
|
||||
"M712.494 716.834h239.263v228.465h-239.263v-228.465z"
|
||||
],
|
||||
"attrs": [
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{}
|
||||
],
|
||||
"isMulticolor": false,
|
||||
"width": 1026,
|
||||
"grid": 0,
|
||||
"tags": [
|
||||
"dailPad"
|
||||
]
|
||||
},
|
||||
"attrs": [
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{}
|
||||
],
|
||||
"properties": {
|
||||
"order": 29,
|
||||
"id": 0,
|
||||
"prevSize": 32,
|
||||
"code": 58908,
|
||||
"name": "dialPad"
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setId": 2,
|
||||
"iconIdx": 0
|
||||
},
|
||||
{
|
||||
"icon": {
|
||||
"paths": [
|
||||
@@ -31,7 +84,8 @@
|
||||
"code": 58907,
|
||||
"name": "settings"
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"setId": 1,
|
||||
"iconIdx": 0
|
||||
},
|
||||
{
|
||||
@@ -74,7 +128,8 @@
|
||||
"name": "webCam",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"setId": 1,
|
||||
"iconIdx": 1
|
||||
},
|
||||
{
|
||||
@@ -144,7 +199,8 @@
|
||||
"name": "connection",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"setId": 1,
|
||||
"iconIdx": 2
|
||||
},
|
||||
{
|
||||
@@ -168,7 +224,8 @@
|
||||
"name": "filmstrip",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"setId": 1,
|
||||
"iconIdx": 3
|
||||
},
|
||||
{
|
||||
@@ -194,7 +251,8 @@
|
||||
"name": "reload",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"setId": 1,
|
||||
"iconIdx": 4
|
||||
},
|
||||
{
|
||||
@@ -218,7 +276,8 @@
|
||||
"name": "hangup",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"setId": 1,
|
||||
"iconIdx": 5
|
||||
},
|
||||
{
|
||||
@@ -242,7 +301,8 @@
|
||||
"name": "contactList",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"setId": 1,
|
||||
"iconIdx": 6
|
||||
},
|
||||
{
|
||||
@@ -267,7 +327,8 @@
|
||||
"name": "avatar",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"setId": 1,
|
||||
"iconIdx": 7
|
||||
},
|
||||
{
|
||||
@@ -292,7 +353,8 @@
|
||||
"name": "callRetro",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"setId": 1,
|
||||
"iconIdx": 8
|
||||
},
|
||||
{
|
||||
@@ -317,7 +379,8 @@
|
||||
"name": "callModern",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"setId": 1,
|
||||
"iconIdx": 9
|
||||
},
|
||||
{
|
||||
@@ -343,7 +406,8 @@
|
||||
"name": "recDisable",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"setId": 1,
|
||||
"iconIdx": 10
|
||||
},
|
||||
{
|
||||
@@ -370,7 +434,8 @@
|
||||
"name": "recEnable",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"setId": 1,
|
||||
"iconIdx": 11
|
||||
},
|
||||
{
|
||||
@@ -395,7 +460,8 @@
|
||||
"name": "kick1",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"setId": 1,
|
||||
"iconIdx": 12
|
||||
},
|
||||
{
|
||||
@@ -421,7 +487,8 @@
|
||||
"name": "kick",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"setId": 1,
|
||||
"iconIdx": 13
|
||||
},
|
||||
{
|
||||
@@ -447,7 +514,8 @@
|
||||
"name": "share-desktop",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"setId": 1,
|
||||
"iconIdx": 14
|
||||
},
|
||||
{
|
||||
@@ -471,7 +539,8 @@
|
||||
"name": "chat-simple",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"setId": 1,
|
||||
"iconIdx": 15
|
||||
},
|
||||
{
|
||||
@@ -497,7 +566,8 @@
|
||||
"name": "full-screen",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"setId": 1,
|
||||
"iconIdx": 16
|
||||
},
|
||||
{
|
||||
@@ -523,7 +593,8 @@
|
||||
"name": "exit-full-screen",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"setId": 1,
|
||||
"iconIdx": 17
|
||||
},
|
||||
{
|
||||
@@ -552,7 +623,8 @@
|
||||
"name": "prezi",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"setId": 1,
|
||||
"iconIdx": 18
|
||||
},
|
||||
{
|
||||
@@ -595,7 +667,8 @@
|
||||
"name": "addNew-V5",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"setId": 1,
|
||||
"iconIdx": 19
|
||||
},
|
||||
{
|
||||
@@ -621,7 +694,8 @@
|
||||
"name": "chat",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"setId": 1,
|
||||
"iconIdx": 20
|
||||
},
|
||||
{
|
||||
@@ -648,7 +722,8 @@
|
||||
"name": "presentation",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"setId": 1,
|
||||
"iconIdx": 21
|
||||
},
|
||||
{
|
||||
@@ -672,7 +747,8 @@
|
||||
"name": "security",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"setId": 1,
|
||||
"iconIdx": 22
|
||||
},
|
||||
{
|
||||
@@ -697,7 +773,8 @@
|
||||
"name": "share-doc",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"setId": 1,
|
||||
"iconIdx": 23
|
||||
},
|
||||
{
|
||||
@@ -721,7 +798,8 @@
|
||||
"name": "security-locked",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"setId": 1,
|
||||
"iconIdx": 24
|
||||
},
|
||||
{
|
||||
@@ -746,7 +824,8 @@
|
||||
"name": "camera-disabled",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"setId": 1,
|
||||
"iconIdx": 25
|
||||
},
|
||||
{
|
||||
@@ -772,7 +851,8 @@
|
||||
"name": "mic-disabled",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"setId": 1,
|
||||
"iconIdx": 26
|
||||
},
|
||||
{
|
||||
@@ -797,7 +877,8 @@
|
||||
"name": "microphone",
|
||||
"ligatures": ""
|
||||
},
|
||||
"setIdx": 0,
|
||||
"setIdx": 1,
|
||||
"setId": 1,
|
||||
"iconIdx": 27
|
||||
}
|
||||
],
|
||||
|
||||
BIN
images/firefox-nightly.png
Normal file
BIN
images/firefox-nightly.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.5 KiB |
BIN
images/noMic.png
Normal file
BIN
images/noMic.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.8 KiB |
BIN
images/noVideo.png
Normal file
BIN
images/noVideo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.8 KiB |
152
index.html
152
index.html
@@ -9,9 +9,10 @@
|
||||
<meta itemprop="name" content="Jitsi Meet"/>
|
||||
<meta itemprop="description" content="Join a WebRTC video conference powered by the Jitsi Videobridge"/>
|
||||
<meta itemprop="image" content="/images/jitsilogo.png"/>
|
||||
<script src="https://api.callstats.io/static/callstats.min.js"></script>
|
||||
<script src="libs/jquery-2.1.1.min.js"></script>
|
||||
<script src="config.js?v=6"></script><!-- adapt to your needs, i.e. set hosts and bosh path -->
|
||||
<script src="libs/strophe/strophe.min.js?v=1"></script>
|
||||
<script src="config.js?v=12"></script><!-- adapt to your needs, i.e. set hosts and bosh path -->
|
||||
<script src="libs/strophe/strophe.min.js?v=2"></script>
|
||||
<script src="libs/strophe/strophe.disco.min.js?v=1"></script>
|
||||
<script src="libs/strophe/strophe.caps.jsonly.min.js?v=1"></script>
|
||||
<script src="libs/jquery-ui.js"></script>
|
||||
@@ -19,15 +20,16 @@
|
||||
<script src="libs/popover.js?v=1"></script><!-- bootstrap tooltip lib -->
|
||||
<script src="libs/toastr.js?v=1"></script><!-- notifications lib -->
|
||||
<script src="interface_config.js?v=5"></script>
|
||||
<script src="libs/app.bundle.js?v=32"></script>
|
||||
<script src="libs/app.bundle.js?v=130"></script>
|
||||
<script src="analytics.js?v=1"></script><!-- google analytics plugin -->
|
||||
<link rel="stylesheet" href="css/font.css?v=6"/>
|
||||
<link rel="stylesheet" href="css/font.css?v=7"/>
|
||||
<link rel="stylesheet" href="css/toastr.css?v=1">
|
||||
<link rel="stylesheet" type="text/css" media="screen" href="css/main.css?v=30"/>
|
||||
<link rel="stylesheet" type="text/css" media="screen" href="css/videolayout_default.css?v=16" id="videolayout_default"/>
|
||||
<link rel="stylesheet" type="text/css" media="screen" href="css/main.css?v=31"/>
|
||||
<link rel="stylesheet" type="text/css" media="screen" href="css/videolayout_default.css?v=20" id="videolayout_default"/>
|
||||
<link href="//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="css/jquery-impromptu.css?v=4">
|
||||
<link rel="stylesheet" href="css/modaldialog.css?v=3">
|
||||
<link rel="stylesheet" href="css/notice.css?v=1">
|
||||
<link rel="stylesheet" href="css/popup_menu.css?v=4">
|
||||
<link rel="stylesheet" href="css/login_menu.css?v=1">
|
||||
<link rel="stylesheet" href="css/popover.css?v=2">
|
||||
@@ -35,13 +37,14 @@
|
||||
<link rel="stylesheet" href="css/contact_list.css?v=4">
|
||||
<link rel="stylesheet" href="css/chat.css?v=5">
|
||||
<link rel="stylesheet" href="css/welcome_page.css?v=2">
|
||||
<link rel="stylesheet" href="css/settingsmenu.css?v=1">
|
||||
<link rel="stylesheet" href="css/settingsmenu.css?v=2">
|
||||
<!--
|
||||
Link used for inline installation of chrome desktop streaming extension,
|
||||
is updated automatically from the code with the value defined in config.js -->
|
||||
<link rel="chrome-webstore-item" href="https://chrome.google.com/webstore/detail/diibjkoicjeejcmhdnailmkgecihlobk">
|
||||
<script src="libs/jquery-impromptu.js"></script>
|
||||
<script src="libs/jquery-impromptu.js?v=2"></script>
|
||||
<script src="libs/jquery.autosize.js"></script>
|
||||
<!--#include virtual="plugin.head.html" -->
|
||||
</head>
|
||||
<body>
|
||||
<div id="welcome_page">
|
||||
@@ -68,7 +71,9 @@
|
||||
<div id="brand_header"></div>
|
||||
<input type='checkbox' name='checkbox' id="disable_welcome"/>
|
||||
<label for="disable_welcome" class="disable_welcome_position" data-i18n="welcomepage.disable"></label>
|
||||
<div id="header_text"></div>
|
||||
<div id="header_text">
|
||||
<!--#include virtual="plugin.header.text.html" -->
|
||||
</div>
|
||||
</div>
|
||||
<div id="welcome_page_main">
|
||||
<div id="features">
|
||||
@@ -116,15 +121,17 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!--#include virtual="plugin.welcomepage.footer.html" -->
|
||||
</div>
|
||||
<div id="videoconference_page">
|
||||
<div style="position: relative;" id="header_container">
|
||||
<div id="header">
|
||||
<div id="notice" class="notice" style="display: none">
|
||||
<span id="noticeText" class="noticeText"></span>
|
||||
</div>
|
||||
<span id="toolbar">
|
||||
<span id="authentication" class="authentication" style="display: none">
|
||||
<a class="button" id="toolbar_button_authentication" >
|
||||
<i id="authButton" class="icon-avatar"></i>
|
||||
</a>
|
||||
<a class="button icon-avatar" id="toolbar_button_authentication" data-i18n="[content]toolbar.authenticate"></a>
|
||||
<ul class="loginmenu">
|
||||
<span class="loginmenuPadding"></span>
|
||||
<li id="toolbar_auth_identity" class="identity"></li>
|
||||
@@ -135,101 +142,29 @@
|
||||
<a class="authButton" data-i18n="toolbar.logout"></a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="header_button_separator"></div>
|
||||
</span>
|
||||
<a class="button" id="toolbar_button_mute" data-container="body" data-toggle="popover" data-placement="bottom" shortcut="mutePopover" data-i18n="[content]toolbar.mute" content="Mute / Unmute">
|
||||
<i id="mute" class="icon-microphone"></i>
|
||||
<a class="button icon-microphone" id="toolbar_button_mute" data-container="body" data-toggle="popover" data-placement="bottom" shortcut="mutePopover" data-i18n="[content]toolbar.mute" content="Mute / Unmute"></a>
|
||||
<a class="button icon-camera" id="toolbar_button_camera" data-container="body" data-toggle="popover" data-placement="bottom" shortcut="toggleVideoPopover" data-i18n="[content]toolbar.videomute" content="Start / stop camera"></a>
|
||||
<a class="button icon-recEnable" id="toolbar_button_record" data-container="body" data-toggle="popover" data-placement="bottom" data-i18n="[content]toolbar.record" content="Record" style="display: none"></a>
|
||||
<a class="button icon-security" id="toolbar_button_security" data-container="body" data-toggle="popover" data-placement="bottom" data-i18n="[content]toolbar.lock" content="Lock / unlock room"></a>
|
||||
<a class="button icon-link" id="toolbar_button_link" data-container="body" data-toggle="popover" data-placement="bottom" data-i18n="[content]toolbar.invite" content="Invite others"></a>
|
||||
<a class="button icon-chat" id="toolbar_button_chat" data-container="body" data-toggle="popover" shortcut="toggleChatPopover" data-placement="bottom" data-i18n="[content]toolbar.chat" content="Open / close chat">
|
||||
<span id="unreadMessages"></span>
|
||||
</a>
|
||||
<div class="header_button_separator"></div>
|
||||
<a class="button" id="toolbar_button_camera" data-container="body" data-toggle="popover" data-placement="bottom" shortcut="toggleVideoPopover" data-i18n="[content]toolbar.videomute" content="Start / stop camera">
|
||||
<i id="video" class="icon-camera"></i>
|
||||
</a>
|
||||
<span id="recording" style="display: none">
|
||||
<div class="header_button_separator"></div>
|
||||
<a class="button" id="toolbar_button_record" data-container="body" data-toggle="popover" data-placement="bottom" data-i18n="[content]toolbar.record" content="Record">
|
||||
<i id="recordButton" class="icon-recEnable"></i>
|
||||
</a>
|
||||
</span>
|
||||
<div class="header_button_separator"></div>
|
||||
<a class="button" id="toolbar_button_security" data-container="body" data-toggle="popover" data-placement="bottom" data-i18n="[content]toolbar.lock" content="Lock / unlock room">
|
||||
<i id="lockIcon" class="icon-security"></i>
|
||||
</a>
|
||||
<div class="header_button_separator"></div>
|
||||
<a class="button" id="toolbar_button_link" data-container="body" data-toggle="popover" data-placement="bottom" data-i18n="[content]toolbar.invite" content="Invite others">
|
||||
<i class="icon-link"></i>
|
||||
</a>
|
||||
<div class="header_button_separator"></div>
|
||||
<span class="toolbar_span">
|
||||
<a class="button" id="toolbar_button_chat" data-container="body" data-toggle="popover" shortcut="toggleChatPopover" data-placement="bottom" data-i18n="[content]toolbar.chat" content="Open / close chat">
|
||||
<i id="chatButton" class="icon-chat">
|
||||
<span id="unreadMessages"></span>
|
||||
</i>
|
||||
</a>
|
||||
</span>
|
||||
<span id="prezi_button">
|
||||
<div class="header_button_separator"></div>
|
||||
<a class="button" id="toolbar_button_prezi" data-container="body" data-toggle="popover" data-placement="bottom" data-i18n="[content]toolbar.prezi" content="Share Prezi">
|
||||
<i class="icon-prezi"></i>
|
||||
</a>
|
||||
</span>
|
||||
<span id="etherpadButton">
|
||||
<div class="header_button_separator"></div>
|
||||
<a class="button" id="toolbar_button_etherpad" data-container="body" data-toggle="popover" data-placement="bottom" content="Shared document" data-i18n="[content]toolbar.etherpad">
|
||||
<i class="icon-share-doc"></i>
|
||||
</a>
|
||||
</span>
|
||||
<div class="header_button_separator"></div>
|
||||
<span id="desktopsharing" style="display: none">
|
||||
<a class="button" id="toolbar_button_desktopsharing" data-container="body" data-toggle="popover" data-placement="bottom" content="Share screen" data-i18n="[content]toolbar.sharescreen">
|
||||
<i class="icon-share-desktop"></i>
|
||||
</a>
|
||||
</span>
|
||||
<div class="header_button_separator"></div>
|
||||
<a class="button" id="toolbar_button_fullScreen" data-container="body" data-toggle="popover" data-placement="bottom" content="Enter / Exit Full Screen" data-i18n="[content]toolbar.fullscreen">
|
||||
<i id="fullScreen" class="icon-full-screen"></i>
|
||||
</a>
|
||||
<span id="sipCallButton" style="display: none">
|
||||
<div class="header_button_separator"></div>
|
||||
<a class="button" id="toolbar_button_sip" data-container="body" data-toggle="popover" data-placement="bottom" content="Call SIP number" data-i18n="[content]toolbar.sip">
|
||||
<i class="icon-telephone"></i></a>
|
||||
</span>
|
||||
<div class="header_button_separator"></div>
|
||||
<a class="button" id="toolbar_button_settings" data-container="body" data-toggle="popover" data-placement="bottom" content="Settings" data-i18n="[content]toolbar.Settings">
|
||||
<i id="settingsButton" class="icon-settings"></i>
|
||||
</a>
|
||||
<div class="header_button_separator"></div>
|
||||
<span id="hangup">
|
||||
<a class="button" id="toolbar_button_hangup" data-container="body" data-toggle="popover" data-placement="bottom" content="Hang Up" data-i18n="[content]toolbar.hangup">
|
||||
<i class="icon-hangup" style="color:#ff0000;font-size: 1.4em;"></i>
|
||||
</a>
|
||||
</span>
|
||||
<a class="button icon-prezi" id="toolbar_button_prezi" data-container="body" data-toggle="popover" data-placement="bottom" data-i18n="[content]toolbar.prezi" content="Share Prezi"></a>
|
||||
<a class="button icon-share-doc" id="toolbar_button_etherpad" data-container="body" data-toggle="popover" data-placement="bottom" content="Shared document" data-i18n="[content]toolbar.etherpad"></a>
|
||||
<a class="button icon-share-desktop" id="toolbar_button_desktopsharing" data-container="body" data-toggle="popover" data-placement="bottom" content="Share screen" data-i18n="[content]toolbar.sharescreen" style="display: none"></a>
|
||||
<a class="button icon-full-screen" id="toolbar_button_fullScreen" data-container="body" data-toggle="popover" data-placement="bottom" content="Enter / Exit Full Screen" data-i18n="[content]toolbar.fullscreen"></a>
|
||||
<a class="button icon-telephone" id="toolbar_button_sip" data-container="body" data-toggle="popover" data-placement="bottom" content="Call SIP number" data-i18n="[content]toolbar.sip" style="display: none"></a>
|
||||
<a class="button icon-dialpad" id="toolbar_button_dialpad" data-container="body" data-toggle="popover" data-placement="bottom" content="Open dialpad" data-i18n="[content]toolbar.dialpad" style="display: none"></a>
|
||||
<a class="button icon-settings" id="toolbar_button_settings" data-container="body" data-toggle="popover" data-placement="bottom" content="Settings" data-i18n="[content]toolbar.Settings"></a>
|
||||
<a class="button icon-hangup" id="toolbar_button_hangup" data-container="body" data-toggle="popover" data-placement="bottom" content="Hang Up" data-i18n="[content]toolbar.hangup"></a>
|
||||
</span>
|
||||
</div>
|
||||
<div id="subject"></div>
|
||||
</div>
|
||||
<div id="settings">
|
||||
<h1 data-i18n="connectionsettings"></h1>
|
||||
<form id="loginInfo">
|
||||
<label>JID: <input id="jid" type="text" name="jid" placeholder="me@example.com"/></label>
|
||||
<label>Password: <input id="password" type="password" name="password" placeholder="secret"/></label>
|
||||
<label>BOSH URL: <input id="boshURL" type="text" name="boshURL" placeholder="/http-bind"/></label>
|
||||
<input id="connect" type="submit" value="Connect" />
|
||||
</form>
|
||||
</div>
|
||||
<div id="reloadPresentation"><a id="reloadPresentationLink"><i title="Reload Prezi" class="fa fa-repeat fa-lg"></i></a></div>
|
||||
<div id="videospace">
|
||||
<div id="largeVideoContainer" class="videocontainer">
|
||||
<div id="presentation"></div>
|
||||
<div id="etherpad"></div>
|
||||
<a target="_new"><div class="watermark leftwatermark"></div></a>
|
||||
<a target="_new"><div class="watermark rightwatermark"></div></a>
|
||||
<a class="poweredby" href="http://jitsi.org" target="_new" ><span data-i18n="poweredby"></span> jitsi.org</a>
|
||||
<div id="activeSpeaker">
|
||||
<img id="activeSpeakerAvatar" src=""/>
|
||||
<canvas id="activeSpeakerAudioLevel"></canvas>
|
||||
</div>
|
||||
<video id="largeVideo" autoplay oncontextmenu="return false;"></video>
|
||||
</div>
|
||||
<div id="remoteVideos">
|
||||
<span id="localVideoContainer" class="videocontainer">
|
||||
<span id="localNick" class="nick"></span>
|
||||
@@ -285,9 +220,10 @@
|
||||
</div>
|
||||
</div>
|
||||
<div id="contactlist" class="right-panel">
|
||||
<ul>
|
||||
<li class="title"><i class="icon-contact-list"></i><span data-i18n="contactlist"></span></li>
|
||||
</ul>
|
||||
<div class="title">
|
||||
<i class="icon-contactList"><span data-i18n="contactlist"></span></i>
|
||||
</div>
|
||||
<ul id="contacts"></ul>
|
||||
</div>
|
||||
<div id="settingsmenu" class="right-panel">
|
||||
<div class="icon-settings" data-i18n="settings.title"></div>
|
||||
@@ -295,9 +231,19 @@
|
||||
<div class="arrow-up"></div>
|
||||
<input type="text" id="setDisplayName" data-i18n="[placeholder]settings.name" placeholder="Name">
|
||||
<input type="text" id="setEmail" placeholder="E-Mail">
|
||||
<div id = "startMutedOptions">
|
||||
<label class = "startMutedLabel">
|
||||
<input type="checkbox" id="startAudioMuted">
|
||||
<span data-i18n="settings.startAudioMuted"></span>
|
||||
</label>
|
||||
<label class = "startMutedLabel">
|
||||
<input type="checkbox" id="startVideoMuted">
|
||||
<span data-i18n="settings.startVideoMuted"></span>
|
||||
</label>
|
||||
</div>
|
||||
<button id="updateSettings" data-i18n="settings.update"></button>
|
||||
</div>
|
||||
<a id="downloadlog" onclick='dump(event.target);' data-container="body" data-toggle="popover" data-placement="right" data-i18n="[data-content]downloadlogs" ><i class="fa fa-cloud-download"></i></a>
|
||||
<a id="downloadlog" data-container="body" data-toggle="popover" data-placement="right" data-i18n="[data-content]downloadlogs" ><i class="fa fa-cloud-download"></i></a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -8,12 +8,16 @@ var interfaceConfig = {
|
||||
DEFAULT_DOMINANT_SPEAKER_DISPLAY_NAME: "speaker",
|
||||
DEFAULT_LOCAL_DISPLAY_NAME: "me",
|
||||
SHOW_JITSI_WATERMARK: true,
|
||||
JITSI_WATERMARK_LINK: "http://jitsi.org",
|
||||
JITSI_WATERMARK_LINK: "https://jitsi.org",
|
||||
SHOW_BRAND_WATERMARK: false,
|
||||
BRAND_WATERMARK_LINK: "",
|
||||
SHOW_POWERED_BY: false,
|
||||
GENERATE_ROOMNAMES_ON_WELCOME_PAGE: true,
|
||||
APP_NAME: "Jitsi Meet",
|
||||
INVITATION_POWERED_BY: true,
|
||||
ACTIVE_SPEAKER_AVATAR_SIZE: 100
|
||||
ACTIVE_SPEAKER_AVATAR_SIZE: 100,
|
||||
/**
|
||||
* Whether to only show the filmstrip (and hide the toolbar).
|
||||
*/
|
||||
filmStripOnly: false
|
||||
};
|
||||
|
||||
57
lang/Translation.md
Normal file
57
lang/Translation.md
Normal file
@@ -0,0 +1,57 @@
|
||||
Jitsi Meet Translation
|
||||
==========================
|
||||
Jitsi Meet uses [i18next](http://i18next.com) library for translation.
|
||||
i18next uses separate json files for each language.
|
||||
|
||||
|
||||
Translating Jitsi Meet
|
||||
======================
|
||||
The translation of Jitsi Meet is integrated with Pootle. You can translate Jitsi Meet via our Pootle user interface on
|
||||
[http://translate.jitsi.org](http://translate.jitsi.org).
|
||||
|
||||
**WARNING: Please don't create or edit manually the language files! Please use our Pootle user interface!**
|
||||
|
||||
Development
|
||||
===========
|
||||
If you want to add new functionality for Jitsi Meet and you have texts that need to be translated please use our translation module.
|
||||
It is located in modules/translation. You must add key and value in main.json file in English for each translatable text.
|
||||
Than you can use the key to get the translated text for the current language.
|
||||
|
||||
**WARNING: Please don't change the other language files except main.json! They must be updated and translated via our Pootle user interface!**
|
||||
|
||||
You can add translatable text in the HTML:
|
||||
|
||||
|
||||
* **via attribute on HTML element** - add **data-i18n** attribute with value the key of the translatable text.
|
||||
|
||||
|
||||
```
|
||||
<span data-i18n="dialog.OK">OK</span>
|
||||
```
|
||||
|
||||
|
||||
You can also use APP.translation.generateTranslationHTML(key, options) to get this HTML code as Javascript string.
|
||||
|
||||
|
||||
```
|
||||
APP.translation.generateTranslationHTML("dialog.OK") // returns <span data-i18n="dialog.OK">OK</span>
|
||||
```
|
||||
|
||||
The value in the options parameter will be added in data-i18n-options attribute of the element.
|
||||
|
||||
**Note:** If you dynamically add HTML elements don't forget to call APP.translation.translateElement(jquery_selector) to translate the text initially.
|
||||
|
||||
* **via Javascript string** - call APP.translation.translateString(key, options). You can use that method to get the translated string in Javascript and then attach it in the HTML.
|
||||
|
||||
```
|
||||
APP.translation.translateString("dialog.OK") // returns the value for the key of the current language file. "OK" for example.
|
||||
```
|
||||
|
||||
For the available values of ``options`` parameter for the above methods of translation module see [i18next documentation](http://i18next.com/pages/doc_features).
|
||||
|
||||
**Note:** It is useful to add attributes in the HTML for persistent HTML elements because when the language is changed the text will be automatically translated.
|
||||
Otherwise you should call ``APP.translation.translateString`` and manually change the text every time the language is changed.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1 +1,7 @@
|
||||
{}
|
||||
{
|
||||
"en": "Английски",
|
||||
"bg": "Български",
|
||||
"de": "Немски",
|
||||
"tr": "Турски",
|
||||
"it": "Италиански"
|
||||
}
|
||||
@@ -1,5 +1,9 @@
|
||||
{
|
||||
"en": "Englisch",
|
||||
"bg": "Bulgarisch",
|
||||
"de": "Deutsch"
|
||||
"de": "Deutsch",
|
||||
"tr": "Türkisch",
|
||||
"it": "Italienisch",
|
||||
"fr": "Französisch",
|
||||
"sl": "Slowenisch"
|
||||
}
|
||||
7
lang/languages-fr.json
Normal file
7
lang/languages-fr.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"en": "Anglais",
|
||||
"bg": "Bulgare",
|
||||
"de": "Allemand",
|
||||
"tr": "Turc",
|
||||
"it": "Italien"
|
||||
}
|
||||
7
lang/languages-it.json
Normal file
7
lang/languages-it.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"en": "Inglese",
|
||||
"bg": "Bulgaro",
|
||||
"de": "Tedesco",
|
||||
"tr": "Turco",
|
||||
"it": "Italiano"
|
||||
}
|
||||
9
lang/languages-sl.json
Normal file
9
lang/languages-sl.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"en": "Angleščina",
|
||||
"bg": "Bolgarščina",
|
||||
"de": "Nemščina",
|
||||
"tr": "Turščina",
|
||||
"it": "Italjanščina",
|
||||
"fr": "Francoščina",
|
||||
"sl": ""
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"en": "",
|
||||
"bg": "",
|
||||
"de": ""
|
||||
"en": "İngilizce",
|
||||
"bg": "Bulgarca",
|
||||
"de": "Almanca"
|
||||
}
|
||||
@@ -1,5 +1,9 @@
|
||||
{
|
||||
"en": "English",
|
||||
"bg": "Bulgarian",
|
||||
"de": "German"
|
||||
}
|
||||
"de": "German",
|
||||
"tr": "Turkish",
|
||||
"it": "Italian",
|
||||
"fr": "French",
|
||||
"sl": "Slovenian"
|
||||
}
|
||||
|
||||
@@ -1,170 +1,224 @@
|
||||
{
|
||||
"contactlist": "СПИСЪК С КОНТАКТИ",
|
||||
"connectionsettings": "Настройки на връзката",
|
||||
"poweredby": "",
|
||||
"downloadlogs": "",
|
||||
"poweredby": "powered by",
|
||||
"downloadlogs": "Изтегли логовете",
|
||||
"roomUrlDefaultMsg": "Конференцията се създава...",
|
||||
"participant": "Участник",
|
||||
"me": "аз",
|
||||
"speaker": "Говорител",
|
||||
"defaultNickname": "например __name__",
|
||||
"defaultPreziLink": "например __url__",
|
||||
"welcomepage": {
|
||||
"go": "",
|
||||
"roomname": "",
|
||||
"disable": "",
|
||||
"go": "Влез",
|
||||
"roomname": "Въведете име на стаята",
|
||||
"disable": "Не показвай страницата следващия път",
|
||||
"feature1": {
|
||||
"title": "",
|
||||
"content": ""
|
||||
"title": "Лесен за употреба",
|
||||
"content": "Не е необходимо да сваляте нищо. _app_ работи директно във вашия браузър. Просто споделете адреса на вашата конференция с другите за да започнете."
|
||||
},
|
||||
"feature2": {
|
||||
"title": "",
|
||||
"content": ""
|
||||
"content": "Видео конференциите могат да работят с по-малко от 128Kbps, а аудио конференциите и конференциите с споделен екран дори с по-малко."
|
||||
},
|
||||
"feature3": {
|
||||
"title": "",
|
||||
"content": ""
|
||||
"title": "Отворен код",
|
||||
"content": "__app__ е лицензиран под MIT лиценз. Можете свободно да го изтеглите, използвате, променяте и споделяте според тези лицензи."
|
||||
},
|
||||
"feature4": {
|
||||
"title": "",
|
||||
"content": ""
|
||||
"title": "Неограничен брой потребители",
|
||||
"content": "Няма изкуствени ограничения за броя на потребителите или участниците в конференция. Единствените ограничения са мощността на вашия сървър и качеството на интернет връзката му."
|
||||
},
|
||||
"feature5": {
|
||||
"title": "",
|
||||
"content": ""
|
||||
"title": "Споделяне на екрана",
|
||||
"content": "Лесно е да споделите екрана си с другите. __app__ е идеален за онлайн презентации, лекции и техническа подръжка."
|
||||
},
|
||||
"feature6": {
|
||||
"title": "",
|
||||
"content": ""
|
||||
"title": "Сигурни стаи",
|
||||
"content": "Нуждаете се от уединение? _app__ конферентните стай могат да бъдат защитени от парола за да се препазите от нежелани гости или прекъсвания."
|
||||
},
|
||||
"feature7": {
|
||||
"title": "",
|
||||
"content": ""
|
||||
"title": "Споделени бележки",
|
||||
"content": "__app__ използва Etherpad, с който можете да редактирате текст в реално време заедно."
|
||||
},
|
||||
"feature8": {
|
||||
"title": "",
|
||||
"content": ""
|
||||
"title": "Статистики за използване",
|
||||
"content": "Научете повече за своите потребители като интегрирате лесно Piwik, Google Analytics и други статистики за изполването."
|
||||
}
|
||||
},
|
||||
"toolbar": {
|
||||
"mute": "",
|
||||
"videomute": "",
|
||||
"mute": "Включи / Изключи микрофона",
|
||||
"videomute": "Спри / пусни камерата",
|
||||
"authenticate": "",
|
||||
"record": "",
|
||||
"lock": "",
|
||||
"invite": "",
|
||||
"record": "Запис",
|
||||
"lock": "Заключи / отключи стаята",
|
||||
"invite": "Поканване на други",
|
||||
"chat": "",
|
||||
"prezi": "",
|
||||
"etherpad": "",
|
||||
"sharescreen": "",
|
||||
"fullscreen": "",
|
||||
"sip": "",
|
||||
"Settings": "",
|
||||
"hangup": "",
|
||||
"login": "",
|
||||
"prezi": "Сподели Prezi",
|
||||
"etherpad": "Споделяне на документ",
|
||||
"sharescreen": "Споделяне на екрана",
|
||||
"fullscreen": "Влез / Излез от Пълен екран",
|
||||
"sip": "Обади се на SIP номер",
|
||||
"Settings": "Настройки",
|
||||
"hangup": "Затвори",
|
||||
"login": "Влез",
|
||||
"logout": ""
|
||||
},
|
||||
"bottomtoolbar": {
|
||||
"chat": "",
|
||||
"filmstrip": "",
|
||||
"contactlist": ""
|
||||
"chat": "Отвори / затвори чат",
|
||||
"filmstrip": "Покажи / скрий лентата с видеа",
|
||||
"contactlist": "Отвори / затвори контакт листа"
|
||||
},
|
||||
"chat": {
|
||||
"nickname": {
|
||||
"title": "",
|
||||
"popover": ""
|
||||
"title": "Въведете име в полето",
|
||||
"popover": "Избор на име"
|
||||
},
|
||||
"messagebox": ""
|
||||
"messagebox": "Въведете текст..."
|
||||
},
|
||||
"settings": {
|
||||
"title": "НАСТРОЙКИ",
|
||||
"update": "",
|
||||
"name": ""
|
||||
"update": "Актуализиране",
|
||||
"name": "Име"
|
||||
},
|
||||
"videothumbnail": {
|
||||
"editnickname": "",
|
||||
"moderator": "",
|
||||
"videomute": "",
|
||||
"mute": "",
|
||||
"kick": "",
|
||||
"muted": "",
|
||||
"domute": ""
|
||||
"editnickname": "Натиснете за да<br/>промените името",
|
||||
"moderator": "Създателя на<br/>конференцията",
|
||||
"videomute": "Учасника е спрял<br/>камерата си.",
|
||||
"mute": "Учасника е с изключен микрофон",
|
||||
"kick": "Изгони",
|
||||
"muted": "Изключен микрофон",
|
||||
"domute": "Изключи микрофона"
|
||||
},
|
||||
"connectionindicator": {
|
||||
"bitrate": "",
|
||||
"packetloss": "",
|
||||
"resolution": "",
|
||||
"less": "",
|
||||
"more": "",
|
||||
"address": "",
|
||||
"remoteports": "",
|
||||
"localports": "",
|
||||
"remoteport": "",
|
||||
"localport": "",
|
||||
"localaddress": "",
|
||||
"localaddresses": "",
|
||||
"remoteaddress": "",
|
||||
"remoteaddresses": "",
|
||||
"transport": "",
|
||||
"packetloss": "Загуба на пакети:",
|
||||
"resolution": "Резолюция:",
|
||||
"less": "Скрий",
|
||||
"more": "Покажи",
|
||||
"address": "Адрес:",
|
||||
"remoteport": "Отдалечен порт:",
|
||||
"remoteport_plural": "Отдалечени портове:",
|
||||
"localport": "Локален порт:",
|
||||
"localport_plural": "Локални портове:",
|
||||
"localaddress": "Локален адрес:",
|
||||
"localaddress_plural": "Локални адреси:",
|
||||
"remoteaddress": "Отдалечен адрес:",
|
||||
"remoteaddress_plural": "Отдалечени адреси:",
|
||||
"transport": "Транспорт:",
|
||||
"bandwidth": "",
|
||||
"na": ""
|
||||
"na": "Върнете се тук за информацията относно вашата връзка, когато започне конференцията"
|
||||
},
|
||||
"notify": {
|
||||
"disconnected": "",
|
||||
"moderator": "",
|
||||
"connected": "",
|
||||
"somebody": "",
|
||||
"me": "",
|
||||
"focus": "",
|
||||
"focusFail": "",
|
||||
"grantedTo": "",
|
||||
"grantedToUnknown": ""
|
||||
"disconnected": "връзката е прекъсната",
|
||||
"moderator": "Придобихте права на модератор!",
|
||||
"connected": "свързан",
|
||||
"somebody": "Някой",
|
||||
"me": "Аз",
|
||||
"focus": "Конферентен фокус",
|
||||
"focusFail": "__component__ не е на раположения - следващ опит след __ms__ секунди",
|
||||
"grantedTo": "Даване на роля модератор на __to__!",
|
||||
"grantedToUnknown": "Даване на роля модератор на $t(somebody)!"
|
||||
},
|
||||
"dialog": {
|
||||
"kickMessage": "",
|
||||
"popupError": "",
|
||||
"passwordError": "",
|
||||
"passwordError2": "",
|
||||
"joinError": "",
|
||||
"connectError": "",
|
||||
"kickMessage": "Бяхте изгонен от срещата!",
|
||||
"popupError": "Вашия браузър блокира попъп прозорците от този сайт. Моля позволете попъп прозорците от настройките на браузъра и опитайте пак.",
|
||||
"passwordError": "Тази конференция е защитена с парола. Само създателя и може да промени паролата.",
|
||||
"passwordError2": "Тази конференция не е защитена с парола. Само създателя и може да сложи парола.",
|
||||
"joinError": "Не можете да се присъедините към конференцията. Може би имате проблем с конфигурацията на сифурността. Моля обърнете се към администратора на услугата.",
|
||||
"connectError": "Опа! Нещо се обърка и не успяхме да се свържем с конференцията.",
|
||||
"connecting": "",
|
||||
"error": "",
|
||||
"detectext": "",
|
||||
"failtoinstall": "",
|
||||
"failedpermissions": "",
|
||||
"bridgeUnavailable": "",
|
||||
"lockTitle": "",
|
||||
"lockMessage": "",
|
||||
"warning": "",
|
||||
"passwordNotSupported": "",
|
||||
"sorry": "",
|
||||
"internalError": "",
|
||||
"unableToSwitch": "",
|
||||
"SLDFailure": "",
|
||||
"SRDFailure": "",
|
||||
"oops": "",
|
||||
"defaultError": "",
|
||||
"passwordRequired": "",
|
||||
"Ok": "",
|
||||
"removePreziTitle": "",
|
||||
"removePreziMsg": "",
|
||||
"sharePreziTitle": "",
|
||||
"sharePreziMsg": "",
|
||||
"Remove": "",
|
||||
"Stop": "",
|
||||
"AuthMsg": "",
|
||||
"Authenticate": "",
|
||||
"Cancel": "",
|
||||
"sessTerminated": "",
|
||||
"hungUp": "",
|
||||
"joinAgain": "",
|
||||
"Share": "",
|
||||
"preziLinkError": "",
|
||||
"Save": "",
|
||||
"recordingToken": "",
|
||||
"Dial": "",
|
||||
"sipMsg": "",
|
||||
"passwordCheck": "",
|
||||
"passwordMsg": "",
|
||||
"Invite": "",
|
||||
"shareLink": "",
|
||||
"settings1": "",
|
||||
"settings2": "",
|
||||
"settings3": "",
|
||||
"yourPassword": "",
|
||||
"Back": ""
|
||||
"detectext": "Възникна грешка при опит да бъде намерено разширението за споделяне на екран.",
|
||||
"failtoinstall": "Неуспешна инсталация на разширението за споделяне на екрана.",
|
||||
"failedpermissions": "Неуспешен опит за получаване на права за използване на микрофон и/или камера.",
|
||||
"bridgeUnavailable": "Jitsi Videobridge не е наличен. Моля опитайте пак!",
|
||||
"lockTitle": "Неуспешно заключване",
|
||||
"lockMessage": "Неуспешно заключване на конференцията.",
|
||||
"warning": "Внимание",
|
||||
"passwordNotSupported": "В момента не поддържаме стаи с пароли.",
|
||||
"sorry": "Съжаляваме",
|
||||
"internalError": "Вътрешна грешка [setRemoteDescription]",
|
||||
"unableToSwitch": "Неуспешен опит за смяна на видеото.",
|
||||
"SLDFailure": "Опа! Нещо се обърка и не успяхме да спрем микрофона! (SLD Failure)",
|
||||
"SRDFailure": "Опа! Нещо се обърка и не успяхме да спрем камерата! (SRD Failure)",
|
||||
"oops": "Опа!",
|
||||
"defaultError": "Възникна грешка",
|
||||
"passwordRequired": "Изисква се парола",
|
||||
"Ok": "ОК",
|
||||
"removePreziTitle": "Премахни Prezi",
|
||||
"removePreziMsg": "Сигурни ли сте, че искате да премахнете Prezi?",
|
||||
"sharePreziTitle": "Сподели Prezi",
|
||||
"sharePreziMsg": "Друг участник вече е споделил Prezi. Тази конференция позволява само да се споделя само едно Prezi.",
|
||||
"Remove": "Премахване",
|
||||
"Stop": "Спиране",
|
||||
"AuthMsg": "Нужна е идентификация, за да създадете стая:<br/><b>__room__</b></br> Може да се идентифицирате, за да създадете стая или да изчакате някой друг да го направи.",
|
||||
"Authenticate": "Идентификация",
|
||||
"Cancel": "Отказ",
|
||||
"retry": "Повторен опит",
|
||||
"logoutTitle": "Изход",
|
||||
"logoutQuestion": "Сигурни ли сте, че искате да излезете и да прекъснете конференцията?",
|
||||
"sessTerminated": "Сесията е прекъсната.",
|
||||
"hungUp": "Вие затворихте обаждането.",
|
||||
"joinAgain": "Песъединете се отново",
|
||||
"Share": "Споделяне",
|
||||
"preziLinkError": "Моля въведете правилен Prezi линк.",
|
||||
"Save": "Запазване",
|
||||
"recordingToken": "Въведете код за достъп за запис на конференцията",
|
||||
"Dial": "Набиране",
|
||||
"sipMsg": "Въведете SIP номер",
|
||||
"passwordCheck": "Сигурни ли сте, че искате да махнете паролата?",
|
||||
"passwordMsg": "Въведете парола, за да заключите стаята",
|
||||
"Invite": "Покани",
|
||||
"shareLink": "Сподели този линк с всеки, който искаш да поканиш",
|
||||
"settings1": "Конфигурирай конференцията",
|
||||
"settings2": "Участниците се присъединиха с изключен микрофон.",
|
||||
"settings3": "Изисквай имена<br/><br/>Въведете парола за да заключите стаята:",
|
||||
"yourPassword": "вашата парола",
|
||||
"Back": "Назад",
|
||||
"serviceUnavailable": "Услугата не е налична",
|
||||
"gracefulShutdown": "Услугата временно не е достъпна поради профилактика. Моля опитайте по-късно.",
|
||||
"Yes": "Да",
|
||||
"reservationError": "Грешка в системата за резервации",
|
||||
"reservationErrorMsg": "Грешка номер: __code__, съобщение: __msg__",
|
||||
"password": "парола",
|
||||
"userPassword": "потребителска парола",
|
||||
"token": "код за достъп"
|
||||
},
|
||||
"email": {
|
||||
"sharedKey": [
|
||||
"Тази конференция е защитена с парола. Моля използвайте следния код за да се присъедините:",
|
||||
"",
|
||||
"",
|
||||
"__sharedKey__",
|
||||
"",
|
||||
""
|
||||
],
|
||||
"subject": "Покана за __appName__ (__conferenceName__)",
|
||||
"body": [
|
||||
"Здравей, Бих искал да те поканя в една __appName__ конференция, която създадох.",
|
||||
"",
|
||||
"",
|
||||
"Кликни на следния линк за да се присъединиш в конференцията.",
|
||||
"",
|
||||
"",
|
||||
"__sharedKeyText__",
|
||||
"__appName__ поддържа __supportedBrowsers__, така че трябва да използваш един от тези браузъри.",
|
||||
"",
|
||||
"",
|
||||
"Ще се видим след секунда!"
|
||||
],
|
||||
"and": "и"
|
||||
},
|
||||
"connection": {
|
||||
"ERROR": "Грешка",
|
||||
"CONNECTING": "Свързване",
|
||||
"CONNFAIL": "Връзката е неуспешна",
|
||||
"AUTHENTICATING": "Идентификация",
|
||||
"AUTHFAIL": "Неуспешна идентификация",
|
||||
"CONNECTED": "Свързан",
|
||||
"DISCONNECTED": "Изключен",
|
||||
"DISCONNECTING": "Прекъсване на връзката",
|
||||
"ATTACHED": "Прикрепен"
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,12 @@
|
||||
"connectionsettings": "Verbindungseinstellungen",
|
||||
"poweredby": "Betrieben von",
|
||||
"downloadlogs": "Log herunterladen",
|
||||
"roomUrlDefaultMsg": "Die Konferenz wird erstellt...",
|
||||
"participant": "Teilnehmer",
|
||||
"me": "ich",
|
||||
"speaker": "Sprecher",
|
||||
"defaultNickname": "Bsp.: __name__",
|
||||
"defaultPreziLink": "Bsp.: __url__",
|
||||
"welcomepage": {
|
||||
"go": "Los",
|
||||
"roomname": "Raumnamen eingeben",
|
||||
@@ -45,7 +51,7 @@
|
||||
"videomute": "Kamera starten / stoppen",
|
||||
"authenticate": "Anmelden",
|
||||
"record": "Aufnehmen",
|
||||
"lock": "Raum sperren / entsperren",
|
||||
"lock": "Raum schützen / Schutz aufheben",
|
||||
"invite": "Andere einladen",
|
||||
"chat": "Chat öffnen / schliessen",
|
||||
"prezi": "Prezi freigeben",
|
||||
@@ -56,12 +62,13 @@
|
||||
"Settings": "Einstellungen",
|
||||
"hangup": "Auflegen",
|
||||
"login": "Anmelden",
|
||||
"logout": "Abmelden"
|
||||
"logout": "Abmelden",
|
||||
"dialpad": "Tastenblock anzeigen"
|
||||
},
|
||||
"bottomtoolbar": {
|
||||
"chat": "Chat öffnen / schliessen",
|
||||
"filmstrip": "Kontaktliste öffnen / schliessen",
|
||||
"contactlist": "Videovorschauen anzeigen / verstecken"
|
||||
"filmstrip": "Videovorschauen anzeigen / verstecken",
|
||||
"contactlist": "Kontaktliste öffnen / schliessen"
|
||||
},
|
||||
"chat": {
|
||||
"nickname": {
|
||||
@@ -73,11 +80,13 @@
|
||||
"settings": {
|
||||
"title": "Einstellungen",
|
||||
"update": "Aktualisieren",
|
||||
"name": "Name"
|
||||
"name": "Name",
|
||||
"startAudioMuted": "Stumm beitreten",
|
||||
"startVideoMuted": "Ohne Video beitreten"
|
||||
},
|
||||
"videothumbnail": {
|
||||
"editnickname": "Klicken um den Anzeigenamen zu bearbeiten",
|
||||
"moderator": "Der Besitzer dieser Konferenz",
|
||||
"moderator": "Besitzer dieser Konferenz",
|
||||
"videomute": "Teilnehmer hat die Kamera pausiert.",
|
||||
"mute": "Teilnehmer ist stumm geschaltet",
|
||||
"kick": "Hinauswerfen",
|
||||
@@ -112,7 +121,9 @@
|
||||
"focus": "Konferenz-Organisator",
|
||||
"focusFail": "__component__ ist im Moment nicht verfügbar - wiederholen in __ms__ Sekunden",
|
||||
"grantedTo": "Moderatorenrechte an __to__ vergeben.",
|
||||
"grantedToUnknown": "Moderatorenrechte an $t(somebody) vergeben."
|
||||
"grantedToUnknown": "Moderatorenrechte an $t(somebody) vergeben.",
|
||||
"muted": "Der Konferenz wurde stumm beigetreten.",
|
||||
"mutedTitle": "Stummschaltung aktiv."
|
||||
},
|
||||
"dialog": {
|
||||
"kickMessage": "Oh! Sie wurden aus der Konferenz ausgeschlossen.",
|
||||
@@ -121,6 +132,8 @@
|
||||
"passwordError2": "Diese Konferenzt ist nicht mit einem Passwort geschützt. Nur der Besitzer der Konferenz kann ein Passwort vergeben.",
|
||||
"joinError": "Oh! Der Konferenz konnte nicht beigetreten werden. Diese könnte ein Problem mit den Sicherheitseinstellungen sein. Bitte kontaktieren Sie den Administrator des Dienstes.",
|
||||
"connectError": "Oh! Es hat etwas nicht geklappt und der Konferenz konnte nicht beigetreten werden.",
|
||||
"connectErrorWithMsg": "Oh! Es hat etwas nicht geklappt und der Konferenz konnte nicht beigetreten werden: __msg__",
|
||||
"connecting": "Verbindung wird hergestellt",
|
||||
"error": "Fehler",
|
||||
"detectext": "Fehler bei der Erkennung der Bildschirmfreigabeerweiterung.",
|
||||
"failtoinstall": "Die Bildschirmfreigabeerweiterung konnte nicht installiert werden.",
|
||||
@@ -144,10 +157,11 @@
|
||||
"sharePreziTitle": "Ein Prezi teilen",
|
||||
"sharePreziMsg": "Ein anderer Teilnehmer teilt bereits ein Prezi. Diese Konferenz kann nur eine Prezi auf einmal anzeigen.",
|
||||
"Remove": "Entfernen",
|
||||
"Stop": "Stopp",
|
||||
"AuthMsg": "Für die Erstellung des Raums ist eine Authentifizierung erforderlich. <br/><b>__room__</b><br/>Sie können sich entweder anmelden oder warten bis jemand anderes die Authentifizierung vornimmt.",
|
||||
"Authenticate": "Anmelden",
|
||||
"WaitingForHost": "Warten auf den Organisator...",
|
||||
"WaitForHostMsg": "Die Konferenz <b>__room__</b> hat noch nicht begonnen. Wenn Sie der Organisator sind, melden Sie sich bitte an. Anderenfalls warten Sie bitte bis der Organisator beigetreten ist.",
|
||||
"IamHost": "Ich bin der Organisator",
|
||||
"Cancel": "Abbrechen",
|
||||
"retry": "Wiederholen",
|
||||
"logoutTitle": "Abmelden",
|
||||
"logoutQuestion": "Sind Sie sicher dass Sie sich abmelden und die Konferenz verlassen möchten?",
|
||||
"sessTerminated": "Sitzung beendet",
|
||||
@@ -160,18 +174,70 @@
|
||||
"Dial": "Wählen",
|
||||
"sipMsg": "Geben Sie eine SIP Nummer ein",
|
||||
"passwordCheck": "Sind Sie sicher dass Sie das Passwort entfernen möchten?",
|
||||
"passwordMsg": "Passwort setzen um den Raum zu sperren",
|
||||
"passwordMsg": "Passwort setzen um den Raum zu schützen",
|
||||
"Invite": "Einladen",
|
||||
"shareLink": "Teilen Sie diesen Link mit jedem den Sie einladen möchten",
|
||||
"settings1": "Konferenz einrichten",
|
||||
"settings2": "Teilnehmer treten stummgeschaltet bei",
|
||||
"settings3": "Nickname erforderlich<br/><br/>Setzen Sie ein Passwort um den Raum zu sperren:",
|
||||
"settings3": "Nickname erforderlich<br/><br/>Setzen Sie ein Passwort um den Raum zu schützen:",
|
||||
"yourPassword": "Ihr Passwort",
|
||||
"Back": "Zurück",
|
||||
"serviceUnavailable": "Dienst nicht verfügbar",
|
||||
"gracefulShutdown": "Der Dienst steht momentan wegen Wartungsarbeiten nicht zur Verfügung. Bitte versuchen Sie es später noch einmal.",
|
||||
"Yes": "Ja",
|
||||
"reservationError": "Fehler im Reservationssystem",
|
||||
"reservationErrorMsg": "Fehler, Nummer: __code__, Nachricht: __msg__"
|
||||
"reservationErrorMsg": "Fehler, Nummer: __code__, Nachricht: __msg__",
|
||||
"password": "Passwort",
|
||||
"userPassword": "Benutzerpasswort",
|
||||
"token": "Token",
|
||||
"displayNameRequired": "Geben Sie Ihren Anzeigenamen ein:"
|
||||
},
|
||||
"email": {
|
||||
"sharedKey": [
|
||||
"Diese Konferenz ist Passwortgeschützt. Bitte verwenden Sie diese PIN zum Beitreten:",
|
||||
"",
|
||||
"",
|
||||
"__sharedKey__",
|
||||
"",
|
||||
""
|
||||
],
|
||||
"subject": "Einladung zu einer __appName__ (__conferenceName__)",
|
||||
"body": [
|
||||
"Hallo!",
|
||||
"",
|
||||
"",
|
||||
"Ich möchte dich zu einer eben erstellten __appName__-Konferenz einladen.",
|
||||
"",
|
||||
"",
|
||||
"Bitte klicke auf den folgenden Link um der Konferenz ebenfalls beizutreten:",
|
||||
"",
|
||||
"",
|
||||
"__roomUrl__",
|
||||
"",
|
||||
"",
|
||||
"__sharedKeyText__",
|
||||
" Bitte beachte, dass __appName__ momentan nur mit einem der Browser __supportedBrowsers__ verwendet werden kann.",
|
||||
"",
|
||||
"",
|
||||
"Bis gleich!"
|
||||
],
|
||||
"and": "und"
|
||||
},
|
||||
"connection": {
|
||||
"ERROR": "Fehler",
|
||||
"CONNECTING": "Verbindung wird hergestellt",
|
||||
"RECONNECTING": "Es ist ein Netzwerkproblem aufgetreten. Verbinde...",
|
||||
"CONNFAIL": "Verbindungsaufbau gescheitert",
|
||||
"AUTHENTICATING": "Anmeldung läuft",
|
||||
"AUTHFAIL": "Authentifizierung fehlgeschlagen",
|
||||
"CONNECTED": "Verbunden",
|
||||
"DISCONNECTED": "Getrennt",
|
||||
"DISCONNECTING": "Verbindung wird getrennt",
|
||||
"ATTACHED": "Angehängt"
|
||||
},
|
||||
"recording": {
|
||||
"toaster": "Wird aufgezeichnet",
|
||||
"pending": "Die Aufzeichnung wird gestartet sobald ein weiterer Teilnehmer beitritt",
|
||||
"on": "Aufzeichnung wurde gestartet"
|
||||
}
|
||||
}
|
||||
227
lang/main-fr.json
Normal file
227
lang/main-fr.json
Normal file
@@ -0,0 +1,227 @@
|
||||
{
|
||||
"contactlist": "Liste de contacts",
|
||||
"connectionsettings": "Paramètres de connexion",
|
||||
"poweredby": "propulsé par",
|
||||
"downloadlogs": "Téléchargement des logs",
|
||||
"roomUrlDefaultMsg": "Votre conférence est en cours de création...",
|
||||
"participant": "Participant",
|
||||
"me": "moi",
|
||||
"speaker": "Haut-parleur",
|
||||
"defaultNickname": "ex: __name__",
|
||||
"defaultPreziLink": "e.g. __url__",
|
||||
"welcomepage": {
|
||||
"go": "Créer",
|
||||
"roomname": "Saisissez un nom de salle",
|
||||
"disable": "Ne pas afficher cette page lors de ma prochaine visite",
|
||||
"feature1": {
|
||||
"title": "Simple à utiliser",
|
||||
"content": "Aucun téléchargement requis. __app__ s'utilise directement depuis votre navigateur. Partager simplement l'URL de votre conférence avec les autres pour commencer."
|
||||
},
|
||||
"feature2": {
|
||||
"title": "Faible bande passante",
|
||||
"content": "Les vidéo conférences à plusieurs participants nécessitent moins de 128 kbps. Le partage d'écran et les conférences avec seulement de l'audio sont possibles avec beaucoup moins de débit."
|
||||
},
|
||||
"feature3": {
|
||||
"title": "Open source",
|
||||
"content": "__app__ est sous licence MIT. Vous êtes libre de le télécharger, l'utiliser, le modifier et le partager sous cette licence."
|
||||
},
|
||||
"feature4": {
|
||||
"title": "Nombre d'utilisateurs illimité",
|
||||
"content": "Il n'y a pas de restrictions artificielles concernant le nombre d'utilisateurs ou de participants à une conférence. La puissance du serveur et la bande passante sont les seuls facteurs limitants."
|
||||
},
|
||||
"feature5": {
|
||||
"title": "Partage d'écan",
|
||||
"content": "C'est facile de partager votre écran avec d'autres personnes. __app__ est idéal pour les présentations en ligne, les cours, et les sessions de support technique."
|
||||
},
|
||||
"feature6": {
|
||||
"title": "Salles sécurisées",
|
||||
"content": "Besoin de confidentialité? Les salles de conférence __app__ peuvent être sécurisées par un mot de passe pour exclure des invités non désirées, et prévenir des interruptions. "
|
||||
},
|
||||
"feature7": {
|
||||
"title": "Notes partagées",
|
||||
"content": "__app__ propose Etherpad, un éditeur de texte collaboratif en temps réel qui est parfait pour les procès-verbaux, l'édition d'articles et plus encore."
|
||||
},
|
||||
"feature8": {
|
||||
"title": "Statistiques d'utilisation",
|
||||
"content": "Connaissez mieux vos utilisateurs avec une intégration facile de Piwik, Google Analytics et d'autres systèmes de statistiques et supervision d'utilisation."
|
||||
}
|
||||
},
|
||||
"toolbar": {
|
||||
"mute": "Muet / Actif",
|
||||
"videomute": "Démarrer / Arrêter la caméra",
|
||||
"authenticate": "",
|
||||
"record": "Enregistrer",
|
||||
"lock": "Verrouiller / déverrouiller la salle",
|
||||
"invite": "Inviter des participants",
|
||||
"chat": "",
|
||||
"prezi": "Partager une présentation Prezi",
|
||||
"etherpad": "Partager un document",
|
||||
"sharescreen": "Partager mon écran",
|
||||
"fullscreen": "Activer / Désactiver le plein écran",
|
||||
"sip": "Appeler un numéro SIP",
|
||||
"Settings": "Paramètres",
|
||||
"hangup": "Raccrocher",
|
||||
"login": "Connexion",
|
||||
"logout": ""
|
||||
},
|
||||
"bottomtoolbar": {
|
||||
"chat": "Ouvrir / fermer le chat",
|
||||
"filmstrip": "Montrer / cacher ma vidéo miniature",
|
||||
"contactlist": "Ouvrir / fermer ma liste de contacts"
|
||||
},
|
||||
"chat": {
|
||||
"nickname": {
|
||||
"title": "Saisissez un pseudonyme dans le champ ci-dessous",
|
||||
"popover": "Choisissez un pseudonyme"
|
||||
},
|
||||
"messagebox": "Saisissez votre texte..."
|
||||
},
|
||||
"settings": {
|
||||
"title": "PARAMÈTRES",
|
||||
"update": "Mise à jour",
|
||||
"name": "Nom"
|
||||
},
|
||||
"videothumbnail": {
|
||||
"editnickname": "Cliquez pour modifier<br/>votre nom",
|
||||
"moderator": "Le propriétaire de<br/>cette conférence",
|
||||
"videomute": "Un participant a<br/>arrêté sa caméra.",
|
||||
"mute": "Un participant a coupé son micro",
|
||||
"kick": "Exclure",
|
||||
"muted": "Coupé",
|
||||
"domute": "Couper le son"
|
||||
},
|
||||
"connectionindicator": {
|
||||
"bitrate": "Débit",
|
||||
"packetloss": "Perte de paquets:",
|
||||
"resolution": "Résolution:",
|
||||
"less": "Cacher le détail",
|
||||
"more": "Montrer le détail",
|
||||
"address": "Adresse:",
|
||||
"remoteport": "Port distant:",
|
||||
"remoteport_plural": "Ports distants:",
|
||||
"localport": "Port local:",
|
||||
"localport_plural": "Ports locaux:",
|
||||
"localaddress": "Adresse locale:",
|
||||
"localaddress_plural": "Adresses locales:",
|
||||
"remoteaddress": "Adresse distante:",
|
||||
"remoteaddress_plural": "Adresses distantes:",
|
||||
"transport": "Transport:",
|
||||
"bandwidth": "Bande passante estimée:",
|
||||
"na": "Revenez ici pour afficher les informations de connexion une fois la conférence démarrée"
|
||||
},
|
||||
"notify": {
|
||||
"disconnected": "Déconnecté",
|
||||
"moderator": "Droits modérateur accordés!",
|
||||
"connected": "Connecté",
|
||||
"somebody": "Quelqu'un",
|
||||
"me": "Moi",
|
||||
"focus": "",
|
||||
"focusFail": "__component__ n'est pas disponible - réessayez dans __ms__ sec",
|
||||
"grantedTo": "Droits modérateur accordés à __to__!",
|
||||
"grantedToUnknown": "Droits modérateur accordés à $t(somebody)!"
|
||||
},
|
||||
"dialog": {
|
||||
"kickMessage": "Oups! Vous avez été renvoyé de la réunion!",
|
||||
"popupError": "Votre navigateur bloque les popups pour ce site. Activez les popups pour ce site dans vos paramètres de sécurité et réessayez.",
|
||||
"passwordError": "Cette conversation est protégée par un mot de passe. Seul le propriétaire de cette conférence peut paramétrer un mot de passe.",
|
||||
"passwordError2": "Cette conversation n'est pas protégée par un mot de passe. Seul le propriétaire de cette conférence peut paramétrer un mot de passe.",
|
||||
"joinError": "Oups! La conférence ne peut être rejointe. Il y a peut-être un souci avec les paramètres de sécurité. Contactez l'administrateur.",
|
||||
"connectError": "Oups! Un problème est survenu et la connexion à la conférence est impossible.",
|
||||
"connecting": "",
|
||||
"error": "",
|
||||
"detectext": "Une erreur est survenue pendant la détection de l'extension de partage d'écran.",
|
||||
"failtoinstall": "Échec de l'installation de l'extension de partage d'écran",
|
||||
"failedpermissions": "Échec d'obtention des permissions pour utiliser le micro et/ou la caméra local(e)",
|
||||
"bridgeUnavailable": "Le pont de visioconférence Jitsi est indisponible pour le moment. Réessayez plus tard!",
|
||||
"lockTitle": "Échec du verrouillage",
|
||||
"lockMessage": "Impossible de verrouiller la conférence.",
|
||||
"warning": "Avertissement",
|
||||
"passwordNotSupported": "Les mots de passe de conférence ne sont pas supportés.",
|
||||
"sorry": "Désolé",
|
||||
"internalError": "Une erreur interne de l'application est survenue [setRemoteDescription]",
|
||||
"unableToSwitch": "Impossible de passer le flux vidéo.",
|
||||
"SLDFailure": "Oups! Un problème est survenu et le micro n'a pas été coupé! (Échec SLD)",
|
||||
"SRDFailure": "Oups! Un problème est survenu et la caméra n'a pas été coupée! (Échec SRD)",
|
||||
"oops": "Oups!",
|
||||
"defaultError": "Une erreur est survenue",
|
||||
"passwordRequired": "Mot de passe requis",
|
||||
"Ok": "Ok",
|
||||
"removePreziTitle": "Supprimer la présentation Prezi",
|
||||
"removePreziMsg": "Voulez-vous vraiment supprimer votre présentation Prezi?",
|
||||
"sharePreziTitle": "Partager une présentation Prezi",
|
||||
"sharePreziMsg": "Un autre participant partage déjà une présentation Prezi. Cette conférence autorise une seule présentation Prezi à la fois.",
|
||||
"Remove": "Supprimer",
|
||||
"Stop": "Arrêter",
|
||||
"AuthMsg": "L'authentification est requise pour créer la conférence:<br/><b>__room__ </b></br> Vous pouvez vous authentifier pour créer la conférence ou attendre que quelqu'un le fasse pour vous.",
|
||||
"Authenticate": "Authentifiez-vous",
|
||||
"Cancel": "Annuler",
|
||||
"retry": "Réessayer",
|
||||
"logoutTitle": "Déconnexion",
|
||||
"logoutQuestion": "Voulez-vous vraiment vous déconnecter et arrêter la conférence?",
|
||||
"sessTerminated": "Session terminée",
|
||||
"hungUp": "Vous avez raccroché et quitté la conférence",
|
||||
"joinAgain": "Rejoignez à nouveau la conférence",
|
||||
"Share": "Partager",
|
||||
"preziLinkError": "Fournissez s'il vous plaît un lien prezi fonctionnel.",
|
||||
"Save": "Sauvegarder",
|
||||
"recordingToken": "Saisissez un jeton d'enregistrement",
|
||||
"Dial": "Composer",
|
||||
"sipMsg": "Saisissez un numéro SIP",
|
||||
"passwordCheck": "Voulez-vous vraiment supprimer votre mot de passe?",
|
||||
"passwordMsg": "Saisissez un mot de passe pour verrouiller la conférence",
|
||||
"Invite": "Inviter",
|
||||
"shareLink": "Partagez ce lien avec toutes les personnes que vous voulez inviter",
|
||||
"settings1": "Configurez votre conférence",
|
||||
"settings2": "Les participants rejoignent la conférence en étant muets.",
|
||||
"settings3": "Pseudonymes requis<br/><br/>Saisissez un mot de passe pour verrouiller la conférence:",
|
||||
"yourPassword": "Votre mot de passe",
|
||||
"Back": "Retour",
|
||||
"serviceUnavailable": "Service indisponible",
|
||||
"gracefulShutdown": "Le service est actuellement en maintenance. Réessayez plus tard.",
|
||||
"Yes": "Oui",
|
||||
"reservationError": "Erreur du système de réservation",
|
||||
"reservationErrorMsg": "Code d'erreur: __code__, message: __msg__",
|
||||
"password": "mot de passe",
|
||||
"userPassword": "mot de passe utilisateur",
|
||||
"token": "jeton"
|
||||
},
|
||||
"email": {
|
||||
"sharedKey": [
|
||||
"Cette conférence est protégée par un mot de passe. Utilisez le code suivant pour la rejoindre:",
|
||||
"",
|
||||
"",
|
||||
"__sharedKey__",
|
||||
"",
|
||||
""
|
||||
],
|
||||
"subject": "Invitation à la conférence __appName__ : __conferenceName__",
|
||||
"body": [
|
||||
"Bonjour, je vous invite à la conférence __appName__ que je viens de créer.",
|
||||
"",
|
||||
"",
|
||||
"Cliquez sur le lien suivant pour rejoindre la conférence.",
|
||||
"",
|
||||
"",
|
||||
"__roomUrl__",
|
||||
"",
|
||||
"",
|
||||
"__sharedKeyText__",
|
||||
" Notez que __appName__ est actuellement seulement supporté par __supportedBrowsers__, vous devez donc utiliser un de ces navigateurs.",
|
||||
"",
|
||||
"",
|
||||
"À tout de suite dans la conférence!"
|
||||
],
|
||||
"and": "et"
|
||||
},
|
||||
"connection": {
|
||||
"ERROR": "Erreur",
|
||||
"CONNECTING": "Connexion en cours...",
|
||||
"CONNFAIL": "Échec de la Connexion",
|
||||
"AUTHENTICATING": "Authentification en cours...",
|
||||
"AUTHFAIL": "Échec de l'authentification",
|
||||
"CONNECTED": "Connecté",
|
||||
"DISCONNECTED": "Déconnecté",
|
||||
"DISCONNECTING": "Déconnexion en cours...",
|
||||
"ATTACHED": "Attachée"
|
||||
}
|
||||
}
|
||||
227
lang/main-it.json
Normal file
227
lang/main-it.json
Normal file
@@ -0,0 +1,227 @@
|
||||
{
|
||||
"contactlist": "LISTA CONTATTI",
|
||||
"connectionsettings": "Impostazioni Connessione",
|
||||
"poweredby": "powered by",
|
||||
"downloadlogs": "Scarica logs",
|
||||
"roomUrlDefaultMsg": "La tua conferenza sta per essere creata...",
|
||||
"participant": "Partecipante",
|
||||
"me": "io",
|
||||
"speaker": "Relatore",
|
||||
"defaultNickname": "es. __nome__",
|
||||
"defaultPreziLink": "es. __url__",
|
||||
"welcomepage": {
|
||||
"go": "VAI",
|
||||
"roomname": "Inserisci Nome Stanza",
|
||||
"disable": "Non visualizzare questa pagina la prossima volta",
|
||||
"feature1": {
|
||||
"title": "Semplice da usare",
|
||||
"content": "Nessun download richiesto. __app__ funziona direttamente nel tuo browser. Condividi semplicemente l'URL della tua conferenza con altri per iniziare."
|
||||
},
|
||||
"feature2": {
|
||||
"title": "Poca banda",
|
||||
"content": "Conferenze video multi utente funzionano con appena 128Kbps. La condivisione dello schermo ed conferenze solo audio sono possibili con molto meno."
|
||||
},
|
||||
"feature3": {
|
||||
"title": "Open source",
|
||||
"content": "__app__ è sotto licenza MIT. Sei libero di scaricarla, usarla, modificarla e condividerla con la medesima licenza."
|
||||
},
|
||||
"feature4": {
|
||||
"title": "Utenti illimitati",
|
||||
"content": "Non ci sono restrizioni sul numero di utenti per una conferenza. La potenza del server e la banda a disposizione sono gli unici fattori limitanti."
|
||||
},
|
||||
"feature5": {
|
||||
"title": "Condivisione Schermo",
|
||||
"content": "é facile condividere il tuo schermo con altri. __app__ è l'ideale per presentazioni online, letture, e sessioni di supporto tecnico."
|
||||
},
|
||||
"feature6": {
|
||||
"title": "Stanze sicure",
|
||||
"content": "Hai bisogno di più privacy? Le conferenze di __app__ possono essere rese sicure con una password per escludere ospiti non desiderati e prevenire interruzioni."
|
||||
},
|
||||
"feature7": {
|
||||
"title": "Note condivise",
|
||||
"content": "__app__ utilizza Etherpad, un editor di testo real-time e collaborativo che è ottimo per meeting, scrivere articoli e tanto altro."
|
||||
},
|
||||
"feature8": {
|
||||
"title": "Statistiche di utilizzo",
|
||||
"content": "Impara come i tuoi utenti lo utilizzano con la facile integrazione con PiWik, Google Analytics, e altri sistemi di statistica e monitor dell'utilizzo."
|
||||
}
|
||||
},
|
||||
"toolbar": {
|
||||
"mute": "Microfono Attiva / Disattiva",
|
||||
"videomute": "Abilita / Disabilita video",
|
||||
"authenticate": "",
|
||||
"record": "Registra",
|
||||
"lock": "Blocca / Sblocca Stanza",
|
||||
"invite": "Invita altri",
|
||||
"chat": "",
|
||||
"prezi": "Condividi con Prezi",
|
||||
"etherpad": "Documento condiviso",
|
||||
"sharescreen": "Condividi schermo",
|
||||
"fullscreen": "Entra / Esci da schermo intero",
|
||||
"sip": "Chiama numero SIP",
|
||||
"Settings": "Impostazioni",
|
||||
"hangup": "Termina",
|
||||
"login": "Login",
|
||||
"logout": ""
|
||||
},
|
||||
"bottomtoolbar": {
|
||||
"chat": "Apri / Chiudi chat",
|
||||
"filmstrip": "Mostra / Nascondi miniature",
|
||||
"contactlist": "Apri / Chiudi la lista contatti"
|
||||
},
|
||||
"chat": {
|
||||
"nickname": {
|
||||
"title": "Scegli un nickname nel box qui sotto",
|
||||
"popover": "Scegli un nickname"
|
||||
},
|
||||
"messagebox": "Inserisci testo..."
|
||||
},
|
||||
"settings": {
|
||||
"title": "IMPOSTAZIONI",
|
||||
"update": "Aggiorna",
|
||||
"name": "Nome"
|
||||
},
|
||||
"videothumbnail": {
|
||||
"editnickname": "Clicca per modificare il tuo<br/>nome visualizzato",
|
||||
"moderator": "Il proprietario<br/>della conferenza",
|
||||
"videomute": "Il partecipante ha<br/>fermato il video.",
|
||||
"mute": "Il partecipante è in muto",
|
||||
"kick": "Espelli",
|
||||
"muted": "Audio disattivato",
|
||||
"domute": "Disattiva audio"
|
||||
},
|
||||
"connectionindicator": {
|
||||
"bitrate": "Bitrate:",
|
||||
"packetloss": "Perdita pacchetti:",
|
||||
"resolution": "Risoluzione:",
|
||||
"less": "Mostra meno",
|
||||
"more": "Mostra di più",
|
||||
"address": "Indirizzo:",
|
||||
"remoteport": "Porta remota:",
|
||||
"remoteport_plural": "Porte remote:",
|
||||
"localport": "Porta locale:",
|
||||
"localport_plural": "Porte locali:",
|
||||
"localaddress": "Indirizzo locale:",
|
||||
"localaddress_plural": "Indirizzi locali:",
|
||||
"remoteaddress": "Indirizzo remoto:",
|
||||
"remoteaddress_plural": "Indirizzi remoti:",
|
||||
"transport": "Trasporto:",
|
||||
"bandwidth": "Banda stimata:",
|
||||
"na": "Ritorna qui per informazioni sulla connessione una volta che la conferenza inizia"
|
||||
},
|
||||
"notify": {
|
||||
"disconnected": "disconnesso",
|
||||
"moderator": "Impostati i permessi di moderatore!",
|
||||
"connected": "connesso",
|
||||
"somebody": "Qualcuno",
|
||||
"me": "io",
|
||||
"focus": "Focus su conferenza",
|
||||
"focusFail": "__component__ non disponibile - riprova in __ms__ sec",
|
||||
"grantedTo": "Permessi di moderatore garantiti a __to__!",
|
||||
"grantedToUnknown": "Permessi di moderatore garantiti a $t(somebody)!"
|
||||
},
|
||||
"dialog": {
|
||||
"kickMessage": "Accidenti! Sei stato espulso dalla conferenza !",
|
||||
"popupError": "Il tuo browser sta bloccando le finestre popup da questo sito. Abilita i popup tra le impostazioni di sicurezza del tuo browser e riprova.",
|
||||
"passwordError": "Questa conversazione è protetta da una password. Solo il proprietario della conferenza può impostare una password.",
|
||||
"passwordError2": "Questa conversazione non è al momento protetta da una password. Solo il proprietario può impostare la password.",
|
||||
"joinError": "Oops! Non puoi entrare nella conferenza. Ci deve essere qualche problema con la configurazione di sicurezza. Contattare l'amministratore di sistema.",
|
||||
"connectError": "Oops! Qualcosa è andato storto e non ti puoi collegare alla conferenza.",
|
||||
"connecting": "",
|
||||
"error": "",
|
||||
"detectext": "Errore durante il rilevamento dell'estensione per il desktopsharing.",
|
||||
"failtoinstall": "Impossibile installare l'estensione per il desktop sharing",
|
||||
"failedpermissions": "Impossibile ottenere i permessi per usare il microfono e/o il video locale.",
|
||||
"bridgeUnavailable": "Il Videobridge non è al momento disponibile. Si prega di riprovare più tardi!",
|
||||
"lockTitle": "Blocco fallito",
|
||||
"lockMessage": "Impossibile bloccare la conferenza.",
|
||||
"warning": "Attenzione",
|
||||
"passwordNotSupported": "Le password sulla stanza non sono al momento supportate.",
|
||||
"sorry": "Spiacente",
|
||||
"internalError": "Errore interno dell'applicazione [setRemoteDescription]",
|
||||
"unableToSwitch": "Impossibile cambiare lo stream video.",
|
||||
"SLDFailure": "Oops! Qualcosa è andato storto e non è possibile silenziare il microfono! (Errore SLD)",
|
||||
"SRDFailure": "Oops! Qualcosa è andato storto e non è possibile fermare il video! (Errore SRD)",
|
||||
"oops": "Oops!",
|
||||
"defaultError": "C'è stato qualche tipo di errore",
|
||||
"passwordRequired": "Richiesta password ",
|
||||
"Ok": "Ok",
|
||||
"removePreziTitle": "Rimuovi Prezi",
|
||||
"removePreziMsg": "Sei sicuro di voler rimuovere il tuo Prezi?",
|
||||
"sharePreziTitle": "Condividi un Prezi",
|
||||
"sharePreziMsg": "Un altro partecipante sta già condividendo un Prezi. Questa conferenza permette un solo Prezi alla volta.",
|
||||
"Remove": "Rimuovi",
|
||||
"Stop": "Ferma",
|
||||
"AuthMsg": "Autenticazione richiesta per creare la stanza:<br/><b>__room__ </b></br> Puoi autenticarti per creare la stanza o aspettare che qualcun altro lo faccia per te.",
|
||||
"Authenticate": "Autenticazione",
|
||||
"Cancel": "Annulla",
|
||||
"retry": "Riprova",
|
||||
"logoutTitle": "Logout",
|
||||
"logoutQuestion": "Vuoi disconnetterti e interrompere la conferenza ?",
|
||||
"sessTerminated": "Sessione Terminata",
|
||||
"hungUp": "Hai terminato la conversazione",
|
||||
"joinAgain": "Entra ancora",
|
||||
"Share": "Condividi",
|
||||
"preziLinkError": "Fornire un link Prezi esatto.",
|
||||
"Save": "Salva",
|
||||
"recordingToken": "Inserire token di registrazione",
|
||||
"Dial": "Componi",
|
||||
"sipMsg": "Inserire numero SIP",
|
||||
"passwordCheck": "Confermi la rimozione della password?",
|
||||
"passwordMsg": "Imposta una password per bloccare la stanza",
|
||||
"Invite": "Invita",
|
||||
"shareLink": "Condividi questo link con tutte le persone che vuoi invitare",
|
||||
"settings1": "Configura la tua conferenza",
|
||||
"settings2": "Partecipanti connessi in muto",
|
||||
"settings3": "Richiedi nicknames<br/><br/>Imposta una password per bloccare la tua stanza:",
|
||||
"yourPassword": "la tua password",
|
||||
"Back": "Indietro",
|
||||
"serviceUnavailable": "Servizio non disponibile",
|
||||
"gracefulShutdown": "Il nostro servizio è al momento spento per manutenzione. Si prega di riprovare più tardi.",
|
||||
"Yes": "Sì",
|
||||
"reservationError": "Errore di sistema in prenotazione",
|
||||
"reservationErrorMsg": "Codice di errore: __code__, messaggio: __msg__",
|
||||
"password": "password",
|
||||
"userPassword": "password utente",
|
||||
"token": "token"
|
||||
},
|
||||
"email": {
|
||||
"sharedKey": [
|
||||
"Questa conferenza è protetta da password. Utilizzare il seguente PIN alla connessione:",
|
||||
"",
|
||||
"",
|
||||
"__sharedKey__",
|
||||
"",
|
||||
""
|
||||
],
|
||||
"subject": "Invito su __appName__ (__conferenceName__)",
|
||||
"body": [
|
||||
"Ciao, Vorrei invitarti alla conferenza che ho appena creato su __appName__ .",
|
||||
"",
|
||||
"",
|
||||
"Cliccare sul seguente link per entrare nella conferenza.",
|
||||
"",
|
||||
"",
|
||||
"__roomUrl__",
|
||||
"",
|
||||
"",
|
||||
"__sharedKeyText__",
|
||||
"NOTA: __appName__ è al momento supportato da questi browsers: __supportedBrowsers__, è necessario utilizzare uno di questi programmi per poter entrare.",
|
||||
"",
|
||||
"",
|
||||
"Ci sentiamo tra un secondo!"
|
||||
],
|
||||
"and": "e"
|
||||
},
|
||||
"connection": {
|
||||
"ERROR": "Errore",
|
||||
"CONNECTING": "Connessione",
|
||||
"CONNFAIL": "Connessione non riuscita",
|
||||
"AUTHENTICATING": "Autenticazione",
|
||||
"AUTHFAIL": "Autenticazione fallita",
|
||||
"CONNECTED": "Connesso",
|
||||
"DISCONNECTED": "Disconnesso",
|
||||
"DISCONNECTING": "Disconnessione in corso",
|
||||
"ATTACHED": "Collegato"
|
||||
}
|
||||
}
|
||||
249
lang/main-sl.json
Normal file
249
lang/main-sl.json
Normal file
@@ -0,0 +1,249 @@
|
||||
{
|
||||
"contactlist": "STIKI",
|
||||
"connectionsettings": "Nastavitve povezave",
|
||||
"poweredby": "poganja",
|
||||
"downloadlogs": "Shrani zapis",
|
||||
"roomUrlDefaultMsg": "Ustvarjanje vaše konference ...",
|
||||
"participant": "Udeleženec",
|
||||
"me": "jaz",
|
||||
"speaker": "Govornik",
|
||||
"defaultNickname": "npr. __name__",
|
||||
"defaultPreziLink": "npr. __url__",
|
||||
"welcomepage": {
|
||||
"go": "POJDI",
|
||||
"roomname": "Vpišite ime sobe",
|
||||
"disable": "Prihodnjič ne prikaži te strani",
|
||||
"feature1": {
|
||||
"title": "Enostavna uporaba",
|
||||
"content": "Nič ni treba namestiti. __app__ deluje direktno v vašem brskalniku. Enostavno sporočite ostalim udeležencem URL svoje konference in začnite."
|
||||
},
|
||||
"feature2": {
|
||||
"title": "Ozka pasovna širina",
|
||||
"content": "Video konferenca z več udeleženci s samo 128Kbps. Deljenje zaslona in samo avdio konference so možne že z veliko nižjo pasovno širino."
|
||||
},
|
||||
"feature3": {
|
||||
"title": "Odprta koda",
|
||||
"content": "__app__ je objavljen po licenci MIT. Pod pogoji te licence lahko prosto snamete, uporabljate, spreminjate in delite."
|
||||
},
|
||||
"feature4": {
|
||||
"title": "Neomejeno število uporabnikov",
|
||||
"content": "Nobene umetne omejitve števila uporabnikov ali udeležencev konference. Zmogljivost strežnika in pasovna širina sta edini omejitvi."
|
||||
},
|
||||
"feature5": {
|
||||
"title": "Skupna raba zaslona",
|
||||
"content": "Skupna raba zaslona z drugimi je zelo enostavna. __app__ je idealna rešitev za spletne predstavitve, predavanja in tehnično podporo."
|
||||
},
|
||||
"feature6": {
|
||||
"title": "Varne sobe",
|
||||
"content": "Rabite zasebnost? Konferenčne sobe __app__ so lahko zaklenjene z geslom, da preprečite dostop neželenim gostom ter prekinitve."
|
||||
},
|
||||
"feature7": {
|
||||
"title": "Skupna raba zapiskov",
|
||||
"content": "__app__ vsebuje Etherpad, realnočasovni skupinski urejevalnik besedil, ki je idealen za pisanje zapisnikov sestankov, člankov in še mnogo drugega."
|
||||
},
|
||||
"feature8": {
|
||||
"title": "Statistika uporabe",
|
||||
"content": "Spoznajte svoje uporabnike z enostavno integracijo v Piwik, Google Analytics ter druge sisteme za nadzor uporabe in statistiko."
|
||||
}
|
||||
},
|
||||
"toolbar": {
|
||||
"mute": "Utišaj / Povrni glasnost",
|
||||
"videomute": "Zaženi / Ustavi kamero",
|
||||
"authenticate": "Overi",
|
||||
"record": "Snemaj",
|
||||
"lock": "Zakleni / Odkleni sobo",
|
||||
"invite": "Povabite ostale",
|
||||
"chat": "Odpri / zapri klepetalnico",
|
||||
"prezi": "Skupna raba Prezi",
|
||||
"etherpad": "Dokument v skupni rabi",
|
||||
"sharescreen": "Zaslon v souporabi",
|
||||
"fullscreen": "Vklopi / Izklopi celozaslonski način",
|
||||
"sip": "Pokliči številko SIP",
|
||||
"Settings": "Nastavitve",
|
||||
"hangup": "Odloži",
|
||||
"login": "Prijava",
|
||||
"logout": "Odjava",
|
||||
"dialpad": "Pokaži številčnico"
|
||||
},
|
||||
"bottomtoolbar": {
|
||||
"chat": "Odpri / zapri klepetalnico",
|
||||
"filmstrip": "Pokaži / Skrij filmski trak",
|
||||
"contactlist": "Odpri / Zapri stike"
|
||||
},
|
||||
"chat": {
|
||||
"nickname": {
|
||||
"title": "Vpišite vzdevek v spodnje polje",
|
||||
"popover": "Izberite vzdevek"
|
||||
},
|
||||
"messagebox": "Vnesite besedilo ..."
|
||||
},
|
||||
"settings": {
|
||||
"title": "NASTAVITVE",
|
||||
"update": "Posodobi",
|
||||
"name": "Ime",
|
||||
"startAudioMuted": "začni brez zvoka",
|
||||
"startVideoMuted": "začni brez slike"
|
||||
},
|
||||
"videothumbnail": {
|
||||
"editnickname": "Kliknite, da spremenite<br/>svoje ime",
|
||||
"moderator": "Lastnik<br/>konference",
|
||||
"videomute": "Udeleženec je<br/>izključil kamero.",
|
||||
"mute": "Udeleženec je utišan",
|
||||
"kick": "Izženi",
|
||||
"muted": "Utišan",
|
||||
"domute": "Utišaj"
|
||||
},
|
||||
"connectionindicator": {
|
||||
"bitrate": "Bitna hitrost:",
|
||||
"packetloss": "Izgubljeni paketi:",
|
||||
"resolution": "Ločljivost:",
|
||||
"less": "Pokaži manj",
|
||||
"more": "Pokaži več",
|
||||
"address": "Naslov:",
|
||||
"remoteport_plural_5": "Oddaljena vrata:",
|
||||
"remoteport": "Oddaljena vrata:",
|
||||
"remoteport_plural_2": "Oddaljena vrata:",
|
||||
"remoteport_plural_3": "Oddaljena vrata:",
|
||||
"localport_plural_5": "Krajevna vrata:",
|
||||
"localport": "Krajevna vrata:",
|
||||
"localport_plural_2": "Krajevna vrata:",
|
||||
"localport_plural_3": "Krajevna vrata:",
|
||||
"localaddress_plural_5": "Krajevni naslov:",
|
||||
"localaddress": "Krajevna naslova:",
|
||||
"localaddress_plural_2": "Krajevni naslovi:",
|
||||
"localaddress_plural_3": "Krajevni naslov:",
|
||||
"remoteaddress_plural_5": "Oddaljeni naslov:",
|
||||
"remoteaddress": "Oddaljena naslova:",
|
||||
"remoteaddress_plural_2": "Oddaljeni naslovi:",
|
||||
"remoteaddress_plural_3": "Oddaljeni naslovi:",
|
||||
"transport": "Prenos:",
|
||||
"bandwidth": "Ocenjena pasovna širina:",
|
||||
"na": "Ko se konferenca začne se vrnite sem za informacije o povezavi"
|
||||
},
|
||||
"notify": {
|
||||
"disconnected": "odklopjeno",
|
||||
"moderator": "Dodeljene moderatorske pravice!",
|
||||
"connected": "povezano",
|
||||
"somebody": "Nekdo",
|
||||
"me": "Jaz",
|
||||
"focus": "Fokus na konferenco",
|
||||
"focusFail": "__component__ ni na razpolago - ponovni poskus čez __ms__ sec",
|
||||
"grantedTo": "Moderatorske pravice dodeljene uporabniku __to__!",
|
||||
"grantedToUnknown": "Moderatorske pravice dodeljene uporabniku $t(somebody)!",
|
||||
"muted": "Pogovor ste začeli utišano.",
|
||||
"mutedTitle": "Utišani ste!"
|
||||
},
|
||||
"dialog": {
|
||||
"kickMessage": "Ojej! Izgnali so vas iz srečanja!",
|
||||
"popupError": "Vaš brskalnik ne dovoli pojavnih oken iz te spletne strani. Omogočite prosim pojavna okna v varnostnih nastavitvah svojega brskalnika in ponovno poskusite.",
|
||||
"passwordError": "Ta pogovor je zaščiten z geslom. Samo lastnik konference lahko nastavi geslo.",
|
||||
"passwordError2": "Ta pogovor ni zaščiten z geslom. Samo lastnik konference lahko nastavi geslo.",
|
||||
"joinError": "Ups! Ni se bilo mogoče pridružiti konferenci. Mogoče je kakšna težava z varnostnimi nastavitvami. Pišite prosim administratorju storitve.",
|
||||
"connectError": "Ups! Nekaj je narobe in se ni bilo mogoče povezati s konferenco.",
|
||||
"connectErrorWithMsg": "Ups! Nekaj je narobe in se ni bilo mogoče povezati s konferenco: __msg__",
|
||||
"connecting": "Povezovanje",
|
||||
"error": "Napaka",
|
||||
"detectext": "Napaka pri zaznavanju razširitve za skupno uporabo namizja.",
|
||||
"failtoinstall": "Razširitve za skupno uporabo namizja ni bilo mogoče namestiti",
|
||||
"failedpermissions": "Ni bilo mogoče pridobiti dovoljenja za uporabo lokalnega mikrofona ali kamere.",
|
||||
"bridgeUnavailable": "Jitsi Videobridge trenutno ni na razpolago. Prosim poskusite kasneje!",
|
||||
"lockTitle": "Zaklepanje ni uspelo",
|
||||
"lockMessage": "Konference ni bilo mogoče zakleniti.",
|
||||
"warning": "Opozorilo",
|
||||
"passwordNotSupported": "Trenutno ni mogoče zakleniti sobe z geslom.",
|
||||
"sorry": "Oprostite",
|
||||
"internalError": "Notranja napaka [setRemoteDescription]",
|
||||
"unableToSwitch": "Ni mogoče preklopiti video pretoka.",
|
||||
"SLDFailure": "Ups! Nekaj je narobe in zvoka se ne da utišati! (Napaka SLD)",
|
||||
"SRDFailure": "Ups! Nekaj je narobe in slike ni mogoče ustaviti! (Napaka SRD)",
|
||||
"oops": "Ups!",
|
||||
"defaultError": "Prišlo je do neke napake",
|
||||
"passwordRequired": "Potrebno je geslo",
|
||||
"Ok": "V redu",
|
||||
"removePreziTitle": "Odstrani Prezi",
|
||||
"removePreziMsg": "Ali res želite odstraniti Prezi?",
|
||||
"sharePreziTitle": "Dajte Prezi v skupno rabo",
|
||||
"sharePreziMsg": "Drug uporabnik je že dal Prezi v skupno rabo. Ta konferenca podpira samo en Prezi naenkrat.",
|
||||
"Remove": "Odstrani",
|
||||
"WaitingForHost": "Čakanje na gostitelja ...",
|
||||
"WaitForHostMsg": "Ta konferenca <b>__room__ </b> se še ni začela. V primeru, da ste vi gostitelj se prosim overite. Drugače počakajte prosim na prihod gostitelja.",
|
||||
"IamHost": "Jaz sem gostitelj",
|
||||
"Cancel": "Prekliči",
|
||||
"retry": "Poskusi ponovno",
|
||||
"logoutTitle": "Odjava",
|
||||
"logoutQuestion": "Ali se res želite odjaviti in prekiniti konferenco?",
|
||||
"sessTerminated": "Seja je končana",
|
||||
"hungUp": "Prekinili ste klic",
|
||||
"joinAgain": "Ponovno se pridruži",
|
||||
"Share": "Souporaba",
|
||||
"preziLinkError": "Prosim, pravilno vpišite povezavo Prezi.",
|
||||
"Save": "Shrani",
|
||||
"recordingToken": "Vnesite žeton za registracijo",
|
||||
"Dial": "Pokliči",
|
||||
"sipMsg": "Vnesite številko SIP",
|
||||
"passwordCheck": "Ali res želite odstraniti geslo?",
|
||||
"passwordMsg": "Nastavite geslo za zaklepanje sobe",
|
||||
"Invite": "Povabi",
|
||||
"shareLink": "To povezavo pošljite vsem, ki jih želite povabiti",
|
||||
"settings1": "Nastavite svojo konferenco",
|
||||
"settings2": "Utišaj udeležence ob pristopu",
|
||||
"settings3": "Zahtevaj vzdevke<br/><br/>Nastavi geslo za zaklep sobe:",
|
||||
"yourPassword": "vaše geslo",
|
||||
"Back": "Nazaj",
|
||||
"serviceUnavailable": "Storitev ni na voljo",
|
||||
"gracefulShutdown": "Storitev trenutno ni na voljo zaradi vzdrževanja. Poskusite ponovno kasneje.",
|
||||
"Yes": "Da",
|
||||
"reservationError": "Napaka v sistemu rezervacije",
|
||||
"reservationErrorMsg": "Koda napake: __code__, sporočilo: __msg__",
|
||||
"password": "geslo",
|
||||
"userPassword": "uporabniško geslo",
|
||||
"token": "žeton",
|
||||
"displayNameRequired": "Vpišite svoje ime:"
|
||||
},
|
||||
"email": {
|
||||
"sharedKey": [
|
||||
"Ta konferenca je zaklenjena z geslom. Uporabite sledeči PIN ko se pridružite:",
|
||||
"",
|
||||
"",
|
||||
"__sharedKey__",
|
||||
"",
|
||||
""
|
||||
],
|
||||
"subject": "Povabilo na __appName__ (__conferenceName__)",
|
||||
"body": [
|
||||
"Pozdravljeni,",
|
||||
"želim vas povabiti na ravnokar pripravljeno konferenco __appName__.",
|
||||
"",
|
||||
"",
|
||||
"Prosim, kliknite na sledečo povezavo, da se pridružite konferenci.",
|
||||
"",
|
||||
"",
|
||||
"__roomUrl__",
|
||||
"",
|
||||
"",
|
||||
"__sharedKeyText__",
|
||||
" Opomba: __appName__ trenutno nudi podporo samo za __supportedBrowsers__, uporabiti morate enega izmed teh brskalnikov.",
|
||||
"",
|
||||
"",
|
||||
"Se slišimo čez trenutek!"
|
||||
],
|
||||
"and": "in"
|
||||
},
|
||||
"connection": {
|
||||
"ERROR": "Napaka",
|
||||
"CONNECTING": "Povezovanje",
|
||||
"RECONNECTING": "Prišlo je do omrežne napake. Ponovni poskus ...",
|
||||
"CONNFAIL": "Povezovanje je spodletelo",
|
||||
"AUTHENTICATING": "Overjanje",
|
||||
"AUTHFAIL": "Overitev je spodletela",
|
||||
"CONNECTED": "Povezano",
|
||||
"DISCONNECTED": "Ni povezave",
|
||||
"DISCONNECTING": "Prekinjanje povezave",
|
||||
"ATTACHED": "Priključeno"
|
||||
},
|
||||
"recording": {
|
||||
"toaster": "Trenutno poteka snemanje!",
|
||||
"pending": "Snemanje se bo začelo takoj, ko se bo pridružil drugi udeleženec",
|
||||
"on": "Snemanje se je začelo"
|
||||
}
|
||||
}
|
||||
@@ -1,177 +1,173 @@
|
||||
{
|
||||
"contactlist": "",
|
||||
"connectionsettings": "",
|
||||
"poweredby": "",
|
||||
"downloadlogs": "",
|
||||
"contactlist": "KİŞİ LİSTESİ",
|
||||
"connectionsettings": "Bağlantı Ayarları",
|
||||
"poweredby": "Gücünün kaynağı",
|
||||
"downloadlogs": "Günlükleri indir",
|
||||
"welcomepage": {
|
||||
"go": "",
|
||||
"roomname": "",
|
||||
"disable": "",
|
||||
"go": "GİT",
|
||||
"roomname": "Oda adı girin",
|
||||
"disable": "Sonraki girişimde bu sayfayı gösterme",
|
||||
"feature1": {
|
||||
"title": "",
|
||||
"content": ""
|
||||
"title": "Kullanımı kolay",
|
||||
"content": "İndirmeye gerek yok. __app__ tarayıcınızda doğrudan çalışır. Başlamak için görüşme bağlantısını URL diğerleri ile paylaşın."
|
||||
},
|
||||
"feature2": {
|
||||
"title": "",
|
||||
"content": ""
|
||||
"title": "Düşük bant genişliği ihtiyacı",
|
||||
"content": "Ekran paylaşımı ve sadece ses ile çok katılımcılı video görüşmeleri, 128Kbps bağlantı ile mümkündür."
|
||||
},
|
||||
"feature3": {
|
||||
"title": "",
|
||||
"content": ""
|
||||
"title": "Açık kaynak kodlu",
|
||||
"content": "__app__ MIT ile lisanslanmıştır. Bu lisansa uygun olarak indirmek, kullanmak, değiştirmek ve paylaşmakta özgürsün."
|
||||
},
|
||||
"feature4": {
|
||||
"title": "",
|
||||
"content": ""
|
||||
"title": "Sınırsız sayıda kullanıcı",
|
||||
"content": "Kullanıcılar veya konferans katılımcılarının sayısında hiçbir yapay kısıtlama yoktur. Sadece sunucun güç ve bant genişliği, sınırlayıcı unsurdur."
|
||||
},
|
||||
"feature5": {
|
||||
"title": "",
|
||||
"content": ""
|
||||
"title": "Ekran paylaşımı",
|
||||
"content": "Diğerlerinle ekranınızı kolayca paylaşın. __app__ çevrimiçi sunumlar, dersler ve teknik destek oturumları için idealdir."
|
||||
},
|
||||
"feature6": {
|
||||
"title": "",
|
||||
"content": ""
|
||||
"title": "Güvenli odalar",
|
||||
"content": "Biraz gizliliğe ihtiyacınız var? __app__ görüşme odaları, istemeyen misafirleri uzak tutmak ve kesinleri önlemek için bir parola ile güvence altına alınabilir."
|
||||
},
|
||||
"feature7": {
|
||||
"title": "",
|
||||
"content": ""
|
||||
"title": "Paylaşımlı notlar",
|
||||
"content": "__app__ Etherpad içerir, gerçek zamanlı bir ortak çalışma metin düzenleyicisidir. Görüşme tutanakları, makale yazımı ve daha fazlası için biçilmiş kaftandır."
|
||||
},
|
||||
"feature8": {
|
||||
"title": "",
|
||||
"content": ""
|
||||
"title": "Kullanım istatistikleri",
|
||||
"content": "Piwik, Google Analytics ve diğer kullanım izleme ve istatistik sistemleri ile kolay tümleştirmeyle kullanıcılar hakkında bilgi edinin."
|
||||
}
|
||||
},
|
||||
"toolbar": {
|
||||
"mute": "",
|
||||
"videomute": "",
|
||||
"mute": "Sessiz / Sesli",
|
||||
"videomute": "Kamera başlat / durdur",
|
||||
"authenticate": "",
|
||||
"record": "",
|
||||
"lock": "",
|
||||
"invite": "",
|
||||
"record": "Kaydet",
|
||||
"lock": "Odayı kilitle / kilit aç",
|
||||
"invite": "Arkadaşlarını davet et",
|
||||
"chat": "",
|
||||
"prezi": "",
|
||||
"etherpad": "",
|
||||
"sharescreen": "",
|
||||
"fullscreen": "",
|
||||
"sip": "",
|
||||
"Settings": "",
|
||||
"hangup": "",
|
||||
"login": "",
|
||||
"prezi": "Prezi paylaş",
|
||||
"etherpad": "Paylaşımlı belge",
|
||||
"sharescreen": "Ekran paylaş",
|
||||
"fullscreen": "Tam Ekrana Gir / Çık",
|
||||
"sip": "SIP numara ara",
|
||||
"Settings": "Ayarlar",
|
||||
"hangup": "Kapat",
|
||||
"login": "Oturum aç",
|
||||
"logout": ""
|
||||
},
|
||||
"bottomtoolbar": {
|
||||
"chat": "",
|
||||
"filmstrip": "",
|
||||
"contactlist": ""
|
||||
"chat": "Sohbeti aç / kapa",
|
||||
"filmstrip": "Kişi listesi aç / kapa",
|
||||
"contactlist": "Film şeridini göster / gizle"
|
||||
},
|
||||
"chat": {
|
||||
"nickname": {
|
||||
"title": "",
|
||||
"popover": ""
|
||||
"title": "Aşağıdaki kutuya bir takma ad girin",
|
||||
"popover": "Bir takma ad seçin"
|
||||
},
|
||||
"messagebox": ""
|
||||
"messagebox": "Metin girin..."
|
||||
},
|
||||
"settings": {
|
||||
"title": "",
|
||||
"update": "",
|
||||
"name": ""
|
||||
"title": "AYARLAR",
|
||||
"update": "Güncelle",
|
||||
"name": "Ad"
|
||||
},
|
||||
"videothumbnail": {
|
||||
"editnickname": "",
|
||||
"moderator": "",
|
||||
"videomute": "",
|
||||
"mute": "",
|
||||
"kick": "",
|
||||
"muted": "",
|
||||
"domute": ""
|
||||
"editnickname": "Görünür adınızı değiştirmek<br/>için tıkla",
|
||||
"moderator": "Bu görüşmenin<br/>sahibi",
|
||||
"videomute": "Katılımcı<br/>kamera durdurdu.",
|
||||
"mute": "Katılımcı sessiz",
|
||||
"kick": "Kovuldu",
|
||||
"muted": "Sessiz",
|
||||
"domute": "Sustur"
|
||||
},
|
||||
"connectionindicator": {
|
||||
"bitrate": "",
|
||||
"packetloss": "",
|
||||
"resolution": "",
|
||||
"less": "",
|
||||
"more": "",
|
||||
"address": "",
|
||||
"remoteport": "",
|
||||
"remoteport_plural": "",
|
||||
"localport": "",
|
||||
"localport_plural": "",
|
||||
"localaddress": "",
|
||||
"localaddress_plural": "",
|
||||
"remoteaddress": "",
|
||||
"remoteaddress_plural": "",
|
||||
"transport": "",
|
||||
"bandwidth": "",
|
||||
"na": ""
|
||||
"bitrate": "Bit hızı:",
|
||||
"packetloss": "Paket kaybı:",
|
||||
"resolution": "Çözünürlük:",
|
||||
"less": "Daha az göster",
|
||||
"more": "Daha fazla göster",
|
||||
"address": "Adres:",
|
||||
"remoteport": "Uzak port:Uzak portlar:",
|
||||
"localport": "Yerel port:Yerel portlar:",
|
||||
"localaddress": "Yerel adres:Yerel adresler:",
|
||||
"remoteaddress": "Uzak adres:Uzak adresler:",
|
||||
"transport": "Transport:",
|
||||
"bandwidth": "Tahmini bant genişliği:",
|
||||
"na": "Görüşme başladıktan sonra bağlantı bilgileri için buraya gel"
|
||||
},
|
||||
"notify": {
|
||||
"disconnected": "",
|
||||
"moderator": "",
|
||||
"connected": "",
|
||||
"somebody": "",
|
||||
"me": "",
|
||||
"focus": "",
|
||||
"focusFail": "",
|
||||
"grantedTo": "",
|
||||
"grantedToUnknown": ""
|
||||
"disconnected": "bağlantı kesildi",
|
||||
"moderator": "Görüşme yöneticisi hakları verildi!",
|
||||
"connected": "bağlandı",
|
||||
"somebody": "Birisi",
|
||||
"me": "Bana",
|
||||
"focus": "Görüşme odağı",
|
||||
"focusFail": "__component__ uygun değil - __ms__ saniye içinde tekrar deneyin",
|
||||
"grantedTo": "__to__, görüşme yöneticisi hakları verildi!",
|
||||
"grantedToUnknown": "$t(somebody), görüşme yöneticisi hakları verildi!"
|
||||
},
|
||||
"dialog": {
|
||||
"kickMessage": "",
|
||||
"popupError": "",
|
||||
"passwordError": "",
|
||||
"passwordError2": "",
|
||||
"joinError": "",
|
||||
"connectError": "",
|
||||
"error": "",
|
||||
"detectext": "",
|
||||
"failtoinstall": "",
|
||||
"failedpermissions": "",
|
||||
"bridgeUnavailable": "",
|
||||
"lockTitle": "",
|
||||
"lockMessage": "",
|
||||
"warning": "",
|
||||
"passwordNotSupported": "",
|
||||
"sorry": "",
|
||||
"internalError": "",
|
||||
"unableToSwitch": "",
|
||||
"SLDFailure": "",
|
||||
"SRDFailure": "",
|
||||
"oops": "",
|
||||
"defaultError": "",
|
||||
"passwordRequired": "",
|
||||
"Ok": "",
|
||||
"removePreziTitle": "",
|
||||
"removePreziMsg": "",
|
||||
"sharePreziTitle": "",
|
||||
"sharePreziMsg": "",
|
||||
"Remove": "",
|
||||
"Stop": "",
|
||||
"AuthMsg": "",
|
||||
"Authenticate": "",
|
||||
"Cancel": "",
|
||||
"logoutTitle": "",
|
||||
"logoutQuestion": "",
|
||||
"sessTerminated": "",
|
||||
"hungUp": "",
|
||||
"joinAgain": "",
|
||||
"Share": "",
|
||||
"preziLinkError": "",
|
||||
"Save": "",
|
||||
"recordingToken": "",
|
||||
"Dial": "",
|
||||
"sipMsg": "",
|
||||
"passwordCheck": "",
|
||||
"passwordMsg": "",
|
||||
"Invite": "",
|
||||
"shareLink": "",
|
||||
"settings1": "",
|
||||
"settings2": "",
|
||||
"settings3": "",
|
||||
"yourPassword": "",
|
||||
"Back": "",
|
||||
"serviceUnavailable": "",
|
||||
"gracefulShutdown": "",
|
||||
"Yes": "",
|
||||
"reservationError": "",
|
||||
"reservationErrorMsg": ""
|
||||
"kickMessage": "Ahhh! Görüşmeden, kavuldun!",
|
||||
"popupError": "Tarayıcınız bu siteden açılır pencereleri engelliyor. Lütfen, tarayıcınızın güvenlik ayarlarında pop-up etkinleştirin ve tekrar deneyin.",
|
||||
"passwordError": "Bu görüşme şu anda bir parola ile korunmaktadır. Sadece görüşmenin sahibi bir parola ayarlayabilir.",
|
||||
"passwordError2": "Bu görüşme şu anda bir parola ile korunmamaktadır. Sadece görüşmenin sahibi bir parola ayarlayabilir.",
|
||||
"joinError": "Amanin boo! Görüşmeye katılamadık. Güvenlik yapılandırması ile ilgili bir sorun olabilir. Hizmet yöneticisi ile bağlantı kurun.",
|
||||
"connectError": "Amanin boo! Birşeyler ters gitti ve görüşmeye bağlanamadık.",
|
||||
"error": "Hata",
|
||||
"detectext": "Ekran paylaşımı eklentisi tespit edilirken hata.",
|
||||
"failtoinstall": "Masaüstü paylaşım eklentisi yüklenemedi",
|
||||
"failedpermissions": "Yerel mikrofon ve/veya kamerayı kullanmak için izinler alınamadı.",
|
||||
"bridgeUnavailable": "Jitsi Videobridge şu anda kullanılamıyor. Daha sonra tekrar deneyiniz!",
|
||||
"lockTitle": "Kilitlenemedi",
|
||||
"lockMessage": "Görüşme kilitlenemedi.",
|
||||
"warning": "Uyarı",
|
||||
"passwordNotSupported": "Oda parolaları şu anda desteklenmemekte.",
|
||||
"sorry": "Üzgünüz",
|
||||
"internalError": "İç uygulama hatası [setRemoteDescription]",
|
||||
"unableToSwitch": "Video akışı açılamıyor.",
|
||||
"SLDFailure": "Amanin boo! Birşeyler ters gitti ve sessize alamadık! (SLD Başarısız)",
|
||||
"SRDFailure": "Amanin boo! Birşeyler ters gitti ve videoyu durduramadık! (SRD Başarısız)",
|
||||
"oops": "Amanin boo!",
|
||||
"defaultError": "Bir tür hata var",
|
||||
"passwordRequired": "Parola gerekli",
|
||||
"Ok": "Tamam",
|
||||
"removePreziTitle": "Prezi kaldır",
|
||||
"removePreziMsg": "Prezi kaldırmak istediğinizden emin misiniz?",
|
||||
"sharePreziTitle": "Bir Prezi paylaşın",
|
||||
"sharePreziMsg": "Diğer katılımcı hala bir Prezi paylaşıyor.Bu görüşme aynı zamanda sadece bir Prezi izin verir.",
|
||||
"Remove": "Kaldır",
|
||||
"Stop": "Durdur",
|
||||
"AuthMsg": "Oda oluşturmak için kimlik doğrulama gerekli:<br/><b>__room__ </b></br> Oda oluşturmak için ya kimlik doğrulamalı ya da bunu yapması için bir başkasını beklemelisiniz.",
|
||||
"Authenticate": "Kimlik doğrula",
|
||||
"Cancel": "İptal",
|
||||
"logoutTitle": "Oturum kapat",
|
||||
"logoutQuestion": "Oturumu ve görüşmeyi sonlandırmak istediğinizden emin misiniz?",
|
||||
"sessTerminated": "Oturum sonlandırıldı",
|
||||
"hungUp": "Görüşmeyi bitirdiniz",
|
||||
"joinAgain": "Yeniden katıl",
|
||||
"Share": "Paylaş",
|
||||
"preziLinkError": "Lütfen doğru prezi bağlantısı verin.",
|
||||
"Save": "Kaydet",
|
||||
"recordingToken": "Kayıt jetonu girin",
|
||||
"Dial": "Ara",
|
||||
"sipMsg": "SIP numarası gir",
|
||||
"passwordCheck": "Parolanızı kaldırmak istediğinizden emin misiniz?",
|
||||
"passwordMsg": "Odanızı kilitlemek için bir parola koyun",
|
||||
"Invite": "Davet et",
|
||||
"shareLink": "Davet etmek istediğiniz herkesle bu bağlantıyı paylaşın",
|
||||
"settings1": "Görüşmenizi yapılandır",
|
||||
"settings2": "Katılımcılar sessiz katılsın",
|
||||
"settings3": "Takma adlar gerekli<br/><br/>Odanızı kitlemek için bir parola ayarlayın:",
|
||||
"yourPassword": "parolanız",
|
||||
"Back": "Geri",
|
||||
"serviceUnavailable": "Hizmet kullanım dışı",
|
||||
"gracefulShutdown": "Hizmetimiz bakıp için durduruldu. Daha sonra tekrar deneyiniz.",
|
||||
"Yes": "Evet",
|
||||
"reservationError": "Rezervasyon sistemi hatası",
|
||||
"reservationErrorMsg": "Hata kodu: __code__, mesaj: __msg__"
|
||||
}
|
||||
}
|
||||
@@ -62,7 +62,8 @@
|
||||
"Settings": "Settings",
|
||||
"hangup": "Hang Up",
|
||||
"login": "Login",
|
||||
"logout": "Logout"
|
||||
"logout": "Logout",
|
||||
"dialpad": "Show dialpad"
|
||||
},
|
||||
"bottomtoolbar": {
|
||||
"chat": "Open / close chat",
|
||||
@@ -80,7 +81,9 @@
|
||||
{
|
||||
"title": "SETTINGS",
|
||||
"update": "Update",
|
||||
"name": "Name"
|
||||
"name": "Name",
|
||||
"startAudioMuted": "start without audio",
|
||||
"startVideoMuted": "start without video"
|
||||
},
|
||||
"videothumbnail":
|
||||
{
|
||||
@@ -122,8 +125,9 @@
|
||||
"focus": "Conference focus",
|
||||
"focusFail": "__component__ not available - retry in __ms__ sec",
|
||||
"grantedTo": "Moderator rights granted to __to__!",
|
||||
"grantedToUnknown": "Moderator rights granted to $t(somebody)!"
|
||||
|
||||
"grantedToUnknown": "Moderator rights granted to $t(somebody)!",
|
||||
"muted": "You have started the conversation muted.",
|
||||
"mutedTitle": "You're muted!"
|
||||
},
|
||||
"dialog": {
|
||||
"kickMessage": "Ouch! You have been kicked out of the meet!",
|
||||
@@ -132,6 +136,8 @@
|
||||
"passwordError2": "This conversation isn't currently protected by a password. Only the owner of the conference could set a password.",
|
||||
"joinError": "Oops! We couldn't join the conference. There might be some problem with security configuration. Please contact service administrator.",
|
||||
"connectError": "Oops! Something went wrong and we couldn't connect to the conference.",
|
||||
"connectErrorWithMsg": "Oops! Something went wrong and we couldn't connect to the conference: __msg__",
|
||||
"connecting": "Connecting",
|
||||
"error": "Error",
|
||||
"detectext": "Error when trying to detect desktopsharing extension.",
|
||||
"failtoinstall": "Failed to install desktop sharing extension",
|
||||
@@ -155,10 +161,11 @@
|
||||
"sharePreziTitle": "Share a Prezi",
|
||||
"sharePreziMsg": "Another participant is already sharing a Prezi.This conference allows only one Prezi at a time.",
|
||||
"Remove": "Remove",
|
||||
"Stop": "Stop",
|
||||
"AuthMsg": "Authentication is required to create room:<br/><b>__room__ </b></br> You can either authenticate to create the room or just wait for someone else to do so.",
|
||||
"Authenticate": "Authenticate",
|
||||
"WaitingForHost": "Waiting for the host ...",
|
||||
"WaitForHostMsg": "The conference <b>__room__ </b> has not yet started. If you are the host then please authenticate. Otherwise, please wait for the host to arrive.",
|
||||
"IamHost": "I am the host",
|
||||
"Cancel": "Cancel",
|
||||
"retry": "Retry",
|
||||
"logoutTitle" : "Logout",
|
||||
"logoutQuestion" : "Are you sure you want to logout and stop the conference?",
|
||||
"sessTerminated": "Session Terminated",
|
||||
@@ -187,12 +194,57 @@
|
||||
"reservationErrorMsg": "Error code: __code__, message: __msg__",
|
||||
"password": "password",
|
||||
"userPassword": "user password",
|
||||
"token": "token"
|
||||
"token": "token",
|
||||
"displayNameRequired": "Please enter your display name:"
|
||||
},
|
||||
"email":
|
||||
{
|
||||
"sharedKey": "This conference is password protected. Please use the following pin when joining:%0D%0A%0D%0A __sharedKey__ %0D%0A%0D%0A",
|
||||
"sharedKey": [
|
||||
"This conference is password protected. Please use the following pin when joining:",
|
||||
"",
|
||||
"",
|
||||
"__sharedKey__",
|
||||
"",
|
||||
""],
|
||||
"subject": "Invitation to a __appName__ (__conferenceName__)",
|
||||
"body": "Hey there, I%27d like to invite you to a __appName__ conference I%27ve just set up.%0D%0A%0D%0APlease click on the following link in order to join the conference.%0D%0A%0D%0A __roomUrl__%0D%0A%0D%0A__sharedKeyText__ Note that __appName__ is currently only supported by Chromium, Google Chrome and Opera, so you need to be using one of these browsers.%0D%0A%0D%0ATalk to you in a sec!"
|
||||
"body": [
|
||||
"Hey there, I%27d like to invite you to a __appName__ conference I%27ve just set up.",
|
||||
"",
|
||||
"",
|
||||
"Please click on the following link in order to join the conference.",
|
||||
"",
|
||||
"",
|
||||
"__roomUrl__",
|
||||
"",
|
||||
"",
|
||||
"__sharedKeyText__",
|
||||
" Note that __appName__ is currently only supported by __supportedBrowsers__, so you need to be using one of these browsers.",
|
||||
"",
|
||||
"",
|
||||
"Talk to you in a sec!"
|
||||
],
|
||||
"and": "and"
|
||||
},
|
||||
"connection":
|
||||
{
|
||||
"ERROR": "Error",
|
||||
"CONNECTING": "Connecting",
|
||||
"RECONNECTING": "A network problem occurred. Reconnecting...",
|
||||
"CONNFAIL": "Connection failed",
|
||||
"AUTHENTICATING": "Authenticating",
|
||||
"AUTHFAIL": "Authentication failed",
|
||||
"CONNECTED": "Connected",
|
||||
"DISCONNECTED": "Disconnected",
|
||||
"DISCONNECTING": "Disconnecting",
|
||||
"ATTACHED": "Attached",
|
||||
"FETCH_SESSION_ID": "Obtaining session-id...",
|
||||
"GOT_SESSION_ID": "Obtaining session-id... Done",
|
||||
"GET_SESSION_ID_ERROR": "Get session-id error: "
|
||||
},
|
||||
"recording":
|
||||
{
|
||||
"toaster": "Currently recording!",
|
||||
"pending": "Your recording will start as soon as another participant joins",
|
||||
"on": "Recording has been started"
|
||||
}
|
||||
}
|
||||
|
||||
46272
libs/app.bundle.js
46272
libs/app.bundle.js
File diff suppressed because it is too large
Load Diff
1273
libs/jquery-impromptu.js
vendored
1273
libs/jquery-impromptu.js
vendored
File diff suppressed because it is too large
Load Diff
4
libs/strophe/strophe.min.js
vendored
4
libs/strophe/strophe.min.js
vendored
File diff suppressed because one or more lines are too long
@@ -1,3 +1,4 @@
|
||||
/* global APP */
|
||||
/**
|
||||
* Implements API class that communicates with external api class
|
||||
* and provides interface to access Jitsi Meet features by external
|
||||
@@ -10,20 +11,25 @@ var XMPPEvents = require("../../service/xmpp/XMPPEvents");
|
||||
* List of the available commands.
|
||||
* @type {{
|
||||
* displayName: inputDisplayNameHandler,
|
||||
* muteAudio: toggleAudio,
|
||||
* muteVideo: toggleVideo,
|
||||
* filmStrip: toggleFilmStrip
|
||||
* toggleAudio: toggleAudio,
|
||||
* toggleVideo: toggleVideo,
|
||||
* toggleFilmStrip: toggleFilmStrip,
|
||||
* toggleChat: toggleChat,
|
||||
* toggleContactList: toggleContactList
|
||||
* }}
|
||||
*/
|
||||
var commands =
|
||||
{
|
||||
displayName: APP.UI.inputDisplayNameHandler,
|
||||
muteAudio: APP.UI.toggleAudio,
|
||||
muteVideo: APP.UI.toggleVideo,
|
||||
toggleFilmStrip: APP.UI.toggleFilmStrip,
|
||||
toggleChat: APP.UI.toggleChat,
|
||||
toggleContactList: APP.UI.toggleContactList
|
||||
};
|
||||
var commands = {};
|
||||
|
||||
function initCommands() {
|
||||
commands = {
|
||||
displayName: APP.UI.inputDisplayNameHandler,
|
||||
toggleAudio: APP.UI.toggleAudio,
|
||||
toggleVideo: APP.UI.toggleVideo,
|
||||
toggleFilmStrip: APP.UI.toggleFilmStrip,
|
||||
toggleChat: APP.UI.toggleChat,
|
||||
toggleContactList: APP.UI.toggleContactList
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
@@ -37,8 +43,7 @@ var commands =
|
||||
* participantLeft: boolean
|
||||
* }}
|
||||
*/
|
||||
var events =
|
||||
{
|
||||
var events = {
|
||||
incomingMessage: false,
|
||||
outgoingMessage:false,
|
||||
displayNameChange: false,
|
||||
@@ -49,18 +54,15 @@ var events =
|
||||
var displayName = {};
|
||||
|
||||
/**
|
||||
* Processes commands from external applicaiton.
|
||||
* Processes commands from external application.
|
||||
* @param message the object with the command
|
||||
*/
|
||||
function processCommand(message)
|
||||
{
|
||||
if(message.action != "execute")
|
||||
{
|
||||
function processCommand(message) {
|
||||
if (message.action != "execute") {
|
||||
console.error("Unknown action of the message");
|
||||
return;
|
||||
}
|
||||
for(var key in message)
|
||||
{
|
||||
for (var key in message) {
|
||||
if(commands[key])
|
||||
commands[key].apply(null, message[key]);
|
||||
}
|
||||
@@ -71,31 +73,26 @@ function processCommand(message)
|
||||
* @param event the event
|
||||
*/
|
||||
function processEvent(event) {
|
||||
if(!event.action)
|
||||
{
|
||||
if (!event.action) {
|
||||
console.error("Event with no action is received.");
|
||||
return;
|
||||
}
|
||||
|
||||
var i = 0;
|
||||
switch(event.action)
|
||||
{
|
||||
switch(event.action) {
|
||||
case "add":
|
||||
for(; i < event.events.length; i++)
|
||||
{
|
||||
for (; i < event.events.length; i++) {
|
||||
events[event.events[i]] = true;
|
||||
}
|
||||
break;
|
||||
case "remove":
|
||||
for(; i < event.events.length; i++)
|
||||
{
|
||||
for (; i < event.events.length; i++) {
|
||||
events[event.events[i]] = false;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
console.error("Unknown action for event.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -110,8 +107,7 @@ function sendMessage(object) {
|
||||
* Processes a message event from the external application
|
||||
* @param event the message event
|
||||
*/
|
||||
function processMessage(event)
|
||||
{
|
||||
function processMessage(event) {
|
||||
var message;
|
||||
try {
|
||||
message = JSON.parse(event.data);
|
||||
@@ -119,8 +115,7 @@ function processMessage(event)
|
||||
|
||||
if(!message.type)
|
||||
return;
|
||||
switch (message.type)
|
||||
{
|
||||
switch (message.type) {
|
||||
case "command":
|
||||
processCommand(message);
|
||||
break;
|
||||
@@ -131,23 +126,22 @@ function processMessage(event)
|
||||
console.error("Unknown type of the message");
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function setupListeners() {
|
||||
APP.xmpp.addListener(XMPPEvents.MUC_ENTER, function (from) {
|
||||
APP.xmpp.addListener(XMPPEvents.MUC_MEMBER_JOINED, function (from) {
|
||||
API.triggerEvent("participantJoined", {jid: from});
|
||||
});
|
||||
APP.xmpp.addListener(XMPPEvents.MESSAGE_RECEIVED, function (from, nick, txt, myjid) {
|
||||
APP.xmpp.addListener(XMPPEvents.MESSAGE_RECEIVED, function (from, nick, txt, myjid, stamp) {
|
||||
if (from != myjid)
|
||||
API.triggerEvent("incomingMessage",
|
||||
{"from": from, "nick": nick, "message": txt});
|
||||
{"from": from, "nick": nick, "message": txt, "stamp": stamp});
|
||||
});
|
||||
APP.xmpp.addListener(XMPPEvents.MUC_LEFT, function (jid) {
|
||||
APP.xmpp.addListener(XMPPEvents.MUC_MEMBER_LEFT, function (jid) {
|
||||
API.triggerEvent("participantLeft", {jid: jid});
|
||||
});
|
||||
APP.xmpp.addListener(XMPPEvents.DISPLAY_NAME_CHANGED, function (jid, newDisplayName) {
|
||||
name = displayName[jid];
|
||||
var name = displayName[jid];
|
||||
if(!name || name != newDisplayName) {
|
||||
API.triggerEvent("displayNameChange", {jid: jid, displayname: newDisplayName});
|
||||
displayName[jid] = newDisplayName;
|
||||
@@ -165,7 +159,7 @@ var API = {
|
||||
*/
|
||||
isEnabled: function () {
|
||||
var hash = location.hash;
|
||||
if(hash && hash.indexOf("external") > -1 && window.postMessage)
|
||||
if (hash && hash.indexOf("external") > -1 && window.postMessage)
|
||||
return true;
|
||||
return false;
|
||||
},
|
||||
@@ -176,13 +170,12 @@ var API = {
|
||||
* is initialized.
|
||||
*/
|
||||
init: function () {
|
||||
if (window.addEventListener)
|
||||
{
|
||||
initCommands();
|
||||
if (window.addEventListener) {
|
||||
window.addEventListener('message',
|
||||
processMessage, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
else {
|
||||
window.attachEvent('onmessage', processMessage);
|
||||
}
|
||||
sendMessage({type: "system", loaded: true});
|
||||
@@ -213,19 +206,14 @@ var API = {
|
||||
* Removes the listeners.
|
||||
*/
|
||||
dispose: function () {
|
||||
if(window.removeEventListener)
|
||||
{
|
||||
if(window.removeEventListener) {
|
||||
window.removeEventListener("message",
|
||||
processMessage, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
else {
|
||||
window.detachEvent('onmessage', processMessage);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
module.exports = API;
|
||||
47
modules/DTMF/DTMF.js
Normal file
47
modules/DTMF/DTMF.js
Normal file
@@ -0,0 +1,47 @@
|
||||
/* global APP */
|
||||
|
||||
/**
|
||||
* A module for sending DTMF tones.
|
||||
*/
|
||||
var DTMFSender;
|
||||
var initDtmfSender = function() {
|
||||
// TODO: This needs to reset this if the peerconnection changes
|
||||
// (e.g. the call is re-made)
|
||||
if (DTMFSender)
|
||||
return;
|
||||
|
||||
var localAudio = APP.RTC.localAudio;
|
||||
if (localAudio && localAudio.getTracks().length > 0)
|
||||
{
|
||||
var peerconnection
|
||||
= APP.xmpp.getConnection().jingle.activecall.peerconnection;
|
||||
if (peerconnection) {
|
||||
DTMFSender =
|
||||
peerconnection.peerconnection
|
||||
.createDTMFSender(localAudio.getTracks()[0]);
|
||||
console.log("Initialized DTMFSender");
|
||||
}
|
||||
else {
|
||||
console.log("Failed to initialize DTMFSender: no PeerConnection.");
|
||||
}
|
||||
}
|
||||
else {
|
||||
console.log("Failed to initialize DTMFSender: no audio track.");
|
||||
}
|
||||
};
|
||||
|
||||
var DTMF = {
|
||||
sendTones: function (tones, duration, pause) {
|
||||
if (!DTMFSender)
|
||||
initDtmfSender();
|
||||
|
||||
if (DTMFSender){
|
||||
DTMFSender.insertDTMF(tones,
|
||||
(duration || 200),
|
||||
(pause || 200));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = DTMF;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* global Strophe, updateLargeVideo, focusedVideoSrc*/
|
||||
/* global config, APP, Strophe */
|
||||
|
||||
// cache datachannels to avoid garbage collection
|
||||
// https://code.google.com/p/chromium/issues/detail?id=405545
|
||||
@@ -8,19 +8,13 @@ var _dataChannels = [];
|
||||
var eventEmitter = null;
|
||||
|
||||
|
||||
|
||||
|
||||
var DataChannels =
|
||||
{
|
||||
|
||||
var DataChannels = {
|
||||
/**
|
||||
* Callback triggered by PeerConnection when new data channel is opened
|
||||
* on the bridge.
|
||||
* @param event the event info object.
|
||||
*/
|
||||
|
||||
onDataChannel: function (event)
|
||||
{
|
||||
onDataChannel: function (event) {
|
||||
var dataChannel = event.channel;
|
||||
|
||||
dataChannel.onopen = function () {
|
||||
@@ -32,14 +26,7 @@ var DataChannels =
|
||||
// Sends 12 bytes binary message to the bridge
|
||||
//dataChannel.send(new ArrayBuffer(12));
|
||||
|
||||
// when the data channel becomes available, tell the bridge about video
|
||||
// selections so that it can do adaptive simulcast,
|
||||
// we want the notification to trigger even if userJid is undefined,
|
||||
// or null.
|
||||
var userJid = APP.UI.getLargeVideoState().userResourceJid;
|
||||
// we want the notification to trigger even if userJid is undefined,
|
||||
// or null.
|
||||
onSelectedEndpointChanged(userJid);
|
||||
eventEmitter.emit(RTCEvents.DATA_CHANNEL_OPEN);
|
||||
};
|
||||
|
||||
dataChannel.onerror = function (error) {
|
||||
@@ -72,8 +59,7 @@ var DataChannels =
|
||||
dominantSpeakerEndpoint);
|
||||
eventEmitter.emit(RTCEvents.DOMINANTSPEAKER_CHANGED, dominantSpeakerEndpoint);
|
||||
}
|
||||
else if ("InLastNChangeEvent" === colibriClass)
|
||||
{
|
||||
else if ("InLastNChangeEvent" === colibriClass) {
|
||||
var oldValue = obj.oldValue;
|
||||
var newValue = obj.newValue;
|
||||
// Make sure that oldValue and newValue are of type boolean.
|
||||
@@ -96,15 +82,13 @@ var DataChannels =
|
||||
|
||||
eventEmitter.emit(RTCEvents.LASTN_CHANGED, oldValue, newValue);
|
||||
}
|
||||
else if ("LastNEndpointsChangeEvent" === colibriClass)
|
||||
{
|
||||
else if ("LastNEndpointsChangeEvent" === colibriClass) {
|
||||
// The new/latest list of last-n endpoint IDs.
|
||||
var lastNEndpoints = obj.lastNEndpoints;
|
||||
// The list of endpoint IDs which are entering the list of
|
||||
// last-n at this time i.e. were not in the old list of last-n
|
||||
// endpoint IDs.
|
||||
var endpointsEnteringLastN = obj.endpointsEnteringLastN;
|
||||
var stream = obj.stream;
|
||||
|
||||
console.log(
|
||||
"Data channel new last-n event: ",
|
||||
@@ -112,33 +96,13 @@ var DataChannels =
|
||||
eventEmitter.emit(RTCEvents.LASTN_ENDPOINT_CHANGED,
|
||||
lastNEndpoints, endpointsEnteringLastN, obj);
|
||||
}
|
||||
else if ("SimulcastLayersChangedEvent" === colibriClass)
|
||||
{
|
||||
eventEmitter.emit(RTCEvents.SIMULCAST_LAYER_CHANGED,
|
||||
obj.endpointSimulcastLayers);
|
||||
}
|
||||
else if ("SimulcastLayersChangingEvent" === colibriClass)
|
||||
{
|
||||
eventEmitter.emit(RTCEvents.SIMULCAST_LAYER_CHANGING,
|
||||
obj.endpointSimulcastLayers);
|
||||
}
|
||||
else if ("StartSimulcastLayerEvent" === colibriClass)
|
||||
{
|
||||
eventEmitter.emit(RTCEvents.SIMULCAST_START, obj.simulcastLayer);
|
||||
}
|
||||
else if ("StopSimulcastLayerEvent" === colibriClass)
|
||||
{
|
||||
eventEmitter.emit(RTCEvents.SIMULCAST_STOP, obj.simulcastLayer);
|
||||
}
|
||||
else
|
||||
{
|
||||
else {
|
||||
console.debug("Data channel JSON-formatted message: ", obj);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
dataChannel.onclose = function ()
|
||||
{
|
||||
dataChannel.onclose = function () {
|
||||
console.info("The Data Channel closed", dataChannel);
|
||||
var idx = _dataChannels.indexOf(dataChannel);
|
||||
if (idx > -1)
|
||||
@@ -183,19 +147,15 @@ var DataChannels =
|
||||
},
|
||||
handleSelectedEndpointEvent: onSelectedEndpointChanged,
|
||||
handlePinnedEndpointEvent: onPinnedEndpointChanged
|
||||
|
||||
};
|
||||
|
||||
function onSelectedEndpointChanged(userResource)
|
||||
{
|
||||
function onSelectedEndpointChanged(userResource) {
|
||||
console.log('selected endpoint changed: ', userResource);
|
||||
if (_dataChannels && _dataChannels.length != 0)
|
||||
{
|
||||
if (_dataChannels && _dataChannels.length != 0) {
|
||||
_dataChannels.some(function (dataChannel) {
|
||||
if (dataChannel.readyState == 'open')
|
||||
{
|
||||
console.log('sending selected endpoint changed '
|
||||
+ 'notification to the bridge: ', userResource);
|
||||
if (dataChannel.readyState == 'open') {
|
||||
console.log('sending selected endpoint changed ' +
|
||||
'notification to the bridge: ', userResource);
|
||||
dataChannel.send(JSON.stringify({
|
||||
'colibriClass': 'SelectedEndpointChangedEvent',
|
||||
'selectedEndpoint':
|
||||
@@ -209,14 +169,11 @@ function onSelectedEndpointChanged(userResource)
|
||||
}
|
||||
}
|
||||
|
||||
function onPinnedEndpointChanged(userResource)
|
||||
{
|
||||
function onPinnedEndpointChanged(userResource) {
|
||||
console.log('pinned endpoint changed: ', userResource);
|
||||
if (_dataChannels && _dataChannels.length != 0)
|
||||
{
|
||||
if (_dataChannels && _dataChannels.length != 0) {
|
||||
_dataChannels.some(function (dataChannel) {
|
||||
if (dataChannel.readyState == 'open')
|
||||
{
|
||||
if (dataChannel.readyState == 'open') {
|
||||
dataChannel.send(JSON.stringify({
|
||||
'colibriClass': 'PinnedEndpointChangedEvent',
|
||||
'pinnedEndpoint':
|
||||
|
||||
@@ -1,73 +1,113 @@
|
||||
/* global APP */
|
||||
var StreamEventTypes = require("../../service/RTC/StreamEventTypes.js");
|
||||
var RTCEvents = require("../../service/RTC/RTCEvents");
|
||||
var RTCBrowserType = require("./RTCBrowserType");
|
||||
|
||||
function LocalStream(stream, type, eventEmitter, videoType)
|
||||
{
|
||||
/**
|
||||
* This implements 'onended' callback normally fired by WebRTC after the stream
|
||||
* is stopped. There is no such behaviour yet in FF, so we have to add it.
|
||||
* @param stream original WebRTC stream object to which 'onended' handling
|
||||
* will be added.
|
||||
*/
|
||||
function implementOnEndedHandling(stream) {
|
||||
var originalStop = stream.stop;
|
||||
stream.stop = function () {
|
||||
originalStop.apply(stream);
|
||||
if (!stream.ended) {
|
||||
stream.ended = true;
|
||||
stream.onended();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function LocalStream(stream, type, eventEmitter, videoType, isGUMStream) {
|
||||
this.stream = stream;
|
||||
this.eventEmitter = eventEmitter;
|
||||
this.type = type;
|
||||
this.videoType = videoType;
|
||||
this.isGUMStream = true;
|
||||
if(isGUMStream === false)
|
||||
this.isGUMStream = isGUMStream;
|
||||
var self = this;
|
||||
if(type == "audio")
|
||||
{
|
||||
if(type == "audio") {
|
||||
this.getTracks = function () {
|
||||
return self.stream.getAudioTracks();
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
this.getTracks = function () {
|
||||
return self.stream.getVideoTracks();
|
||||
};
|
||||
}
|
||||
|
||||
this.stream.onended = function()
|
||||
{
|
||||
this.stream.onended = function () {
|
||||
self.streamEnded();
|
||||
};
|
||||
if (RTCBrowserType.isFirefox()) {
|
||||
implementOnEndedHandling(this.stream);
|
||||
}
|
||||
}
|
||||
|
||||
LocalStream.prototype.streamEnded = function () {
|
||||
this.eventEmitter.emit(StreamEventTypes.EVENT_TYPE_LOCAL_ENDED, this);
|
||||
}
|
||||
};
|
||||
|
||||
LocalStream.prototype.getOriginalStream = function()
|
||||
{
|
||||
return this.stream;
|
||||
}
|
||||
};
|
||||
|
||||
LocalStream.prototype.isAudioStream = function () {
|
||||
return (this.stream.getAudioTracks() && this.stream.getAudioTracks().length > 0);
|
||||
return this.type === "audio";
|
||||
};
|
||||
|
||||
LocalStream.prototype.mute = function()
|
||||
LocalStream.prototype.setMute = function (mute)
|
||||
{
|
||||
var ismuted = false;
|
||||
var tracks = this.getTracks();
|
||||
var isAudio = this.isAudioStream();
|
||||
var eventType = isAudio ? RTCEvents.AUDIO_MUTE : RTCEvents.VIDEO_MUTE;
|
||||
|
||||
for (var idx = 0; idx < tracks.length; idx++) {
|
||||
ismuted = !tracks[idx].enabled;
|
||||
tracks[idx].enabled = ismuted;
|
||||
}
|
||||
return ismuted;
|
||||
};
|
||||
if ((window.location.protocol != "https:" && this.isGUMStream) ||
|
||||
(isAudio && this.isGUMStream) || this.videoType === "screen" ||
|
||||
// FIXME FF does not support 'removeStream' method used to mute
|
||||
RTCBrowserType.isFirefox()) {
|
||||
|
||||
LocalStream.prototype.setMute = function(mute)
|
||||
{
|
||||
var tracks = this.getTracks();
|
||||
|
||||
for (var idx = 0; idx < tracks.length; idx++) {
|
||||
tracks[idx].enabled = mute;
|
||||
var tracks = this.getTracks();
|
||||
for (var idx = 0; idx < tracks.length; idx++) {
|
||||
tracks[idx].enabled = !mute;
|
||||
}
|
||||
this.eventEmitter.emit(eventType, mute);
|
||||
} else {
|
||||
if (mute) {
|
||||
APP.xmpp.removeStream(this.stream);
|
||||
this.stream.stop();
|
||||
this.eventEmitter.emit(eventType, true);
|
||||
} else {
|
||||
var self = this;
|
||||
APP.RTC.rtcUtils.obtainAudioAndVideoPermissions(
|
||||
(this.isAudioStream() ? ["audio"] : ["video"]),
|
||||
function (stream) {
|
||||
if (isAudio) {
|
||||
APP.RTC.changeLocalAudio(stream,
|
||||
function () {
|
||||
self.eventEmitter.emit(eventType, false);
|
||||
});
|
||||
} else {
|
||||
APP.RTC.changeLocalVideo(stream, false,
|
||||
function () {
|
||||
self.eventEmitter.emit(eventType, false);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
LocalStream.prototype.isMuted = function () {
|
||||
var tracks = [];
|
||||
if(this.type == "audio")
|
||||
{
|
||||
if (this.isAudioStream()) {
|
||||
tracks = this.stream.getAudioTracks();
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
if (this.stream.ended)
|
||||
return true;
|
||||
tracks = this.stream.getVideoTracks();
|
||||
}
|
||||
for (var idx = 0; idx < tracks.length; idx++) {
|
||||
@@ -75,12 +115,10 @@ LocalStream.prototype.isMuted = function () {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
LocalStream.prototype.getId = function () {
|
||||
return this.stream.getTracks()[0].id;
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
module.exports = LocalStream;
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
////These lines should be uncommented when require works in app.js
|
||||
var RTCBrowserType = require("../../service/RTC/RTCBrowserType.js");
|
||||
var MediaStreamType = require("../../service/RTC/MediaStreamTypes");
|
||||
|
||||
/**
|
||||
@@ -13,7 +11,7 @@ var MediaStreamType = require("../../service/RTC/MediaStreamTypes");
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
function MediaStream(data, sid, ssrc, browser) {
|
||||
function MediaStream(data, sid, ssrc, browser, eventEmitter) {
|
||||
|
||||
// XXX(gp) to minimize headaches in the future, we should build our
|
||||
// abstractions around tracks and not streams. ORTC is track based API.
|
||||
@@ -28,24 +26,22 @@ function MediaStream(data, sid, ssrc, browser) {
|
||||
this.sid = sid;
|
||||
this.stream = data.stream;
|
||||
this.peerjid = data.peerjid;
|
||||
this.videoType = data.videoType;
|
||||
this.ssrc = ssrc;
|
||||
this.type = (this.stream.getVideoTracks().length > 0)?
|
||||
MediaStreamType.VIDEO_TYPE : MediaStreamType.AUDIO_TYPE;
|
||||
this.videoType = null;
|
||||
this.muted = false;
|
||||
this.eventEmitter = eventEmitter;
|
||||
}
|
||||
|
||||
|
||||
MediaStream.prototype.getOriginalStream = function()
|
||||
{
|
||||
MediaStream.prototype.getOriginalStream = function() {
|
||||
return this.stream;
|
||||
}
|
||||
};
|
||||
|
||||
MediaStream.prototype.setMute = function (value)
|
||||
{
|
||||
MediaStream.prototype.setMute = function (value) {
|
||||
this.stream.muted = value;
|
||||
this.muted = value;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
module.exports = MediaStream;
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
/* global APP */
|
||||
var EventEmitter = require("events");
|
||||
var RTCBrowserType = require("./RTCBrowserType");
|
||||
var RTCUtils = require("./RTCUtils.js");
|
||||
var LocalStream = require("./LocalStream.js");
|
||||
var DataChannels = require("./DataChannels");
|
||||
@@ -7,13 +9,46 @@ var DesktopSharingEventTypes
|
||||
= require("../../service/desktopsharing/DesktopSharingEventTypes");
|
||||
var MediaStreamType = require("../../service/RTC/MediaStreamTypes");
|
||||
var StreamEventTypes = require("../../service/RTC/StreamEventTypes.js");
|
||||
var RTCEvents = require("../../service/RTC/RTCEvents.js");
|
||||
var XMPPEvents = require("../../service/xmpp/XMPPEvents");
|
||||
var UIEvents = require("../../service/UI/UIEvents");
|
||||
|
||||
var eventEmitter = new EventEmitter();
|
||||
|
||||
|
||||
function getMediaStreamUsage()
|
||||
{
|
||||
var result = {
|
||||
audio: true,
|
||||
video: true
|
||||
};
|
||||
|
||||
/** There are some issues with the desktop sharing
|
||||
* when this property is enabled.
|
||||
* WARNING: We must change the implementation to start video/audio if we
|
||||
* receive from the focus that the peer is not muted.
|
||||
|
||||
var isSecureConnection = window.location.protocol == "https:";
|
||||
|
||||
if(config.disableEarlyMediaPermissionRequests || !isSecureConnection)
|
||||
{
|
||||
result = {
|
||||
audio: false,
|
||||
video: false
|
||||
};
|
||||
|
||||
}
|
||||
**/
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
var RTC = {
|
||||
rtcUtils: null,
|
||||
devices: {
|
||||
audio: true,
|
||||
video: true
|
||||
},
|
||||
localStreams: [],
|
||||
remoteStreams: {},
|
||||
localAudio: null,
|
||||
@@ -30,31 +65,30 @@ var RTC = {
|
||||
|
||||
eventEmitter.removeListener(eventType, listener);
|
||||
},
|
||||
createLocalStream: function (stream, type, change) {
|
||||
createLocalStream: function (stream, type, change, videoType, isMuted, isGUMStream) {
|
||||
|
||||
var localStream = new LocalStream(stream, type, eventEmitter);
|
||||
var localStream = new LocalStream(stream, type, eventEmitter, videoType, isGUMStream);
|
||||
//in firefox we have only one stream object
|
||||
if(this.localStreams.length == 0 ||
|
||||
this.localStreams[0].getOriginalStream() != stream)
|
||||
this.localStreams.push(localStream);
|
||||
if(type == "audio")
|
||||
{
|
||||
if(isMuted === true)
|
||||
localStream.setMute(true);
|
||||
|
||||
if(type == "audio") {
|
||||
this.localAudio = localStream;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
this.localVideo = localStream;
|
||||
}
|
||||
var eventType = StreamEventTypes.EVENT_TYPE_LOCAL_CREATED;
|
||||
if(change)
|
||||
eventType = StreamEventTypes.EVENT_TYPE_LOCAL_CHANGED;
|
||||
|
||||
eventEmitter.emit(eventType, localStream);
|
||||
eventEmitter.emit(eventType, localStream, isMuted);
|
||||
return localStream;
|
||||
},
|
||||
removeLocalStream: function (stream) {
|
||||
for(var i = 0; i < this.localStreams.length; i++)
|
||||
{
|
||||
for(var i = 0; i < this.localStreams.length; i++) {
|
||||
if(this.localStreams[i].getOriginalStream() === stream) {
|
||||
delete this.localStreams[i];
|
||||
return;
|
||||
@@ -63,19 +97,15 @@ var RTC = {
|
||||
},
|
||||
createRemoteStream: function (data, sid, thessrc) {
|
||||
var remoteStream = new MediaStream(data, sid, thessrc,
|
||||
this.getBrowserType());
|
||||
RTCBrowserType.getBrowserType(), eventEmitter);
|
||||
var jid = data.peerjid || APP.xmpp.myJid();
|
||||
if(!this.remoteStreams[jid]) {
|
||||
this.remoteStreams[jid] = {};
|
||||
}
|
||||
this.remoteStreams[jid][remoteStream.type]= remoteStream;
|
||||
eventEmitter.emit(StreamEventTypes.EVENT_TYPE_REMOTE_CREATED, remoteStream);
|
||||
console.debug("ADD remote stream ", remoteStream.type, " ", jid, " ", thessrc);
|
||||
return remoteStream;
|
||||
},
|
||||
getBrowserType: function () {
|
||||
return this.rtcUtils.browser;
|
||||
},
|
||||
getPCConstraints: function () {
|
||||
return this.rtcUtils.pc_constraints;
|
||||
},
|
||||
@@ -86,8 +116,8 @@ var RTC = {
|
||||
return this.rtcUtils.getUserMediaWithConstraints(um, success_callback,
|
||||
failure_callback, resolution, bandwidth, fps, desktopStream);
|
||||
},
|
||||
attachMediaStream: function (element, stream) {
|
||||
this.rtcUtils.attachMediaStream(element, stream);
|
||||
attachMediaStream: function (elSelector, stream) {
|
||||
this.rtcUtils.attachMediaStream(elSelector, stream);
|
||||
},
|
||||
getStreamID: function (stream) {
|
||||
return this.rtcUtils.getStreamID(stream);
|
||||
@@ -98,6 +128,9 @@ var RTC = {
|
||||
setVideoSrc: function (element, src) {
|
||||
this.rtcUtils.setVideoSrc(element, src);
|
||||
},
|
||||
getVideoElementName: function () {
|
||||
return RTCBrowserType.isTemasysPluginUsed() ? 'object' : 'video';
|
||||
},
|
||||
dispose: function() {
|
||||
if (this.rtcUtils) {
|
||||
this.rtcUtils = null;
|
||||
@@ -112,20 +145,6 @@ var RTC = {
|
||||
function (stream, isUsingScreenStream, callback) {
|
||||
self.changeLocalVideo(stream, isUsingScreenStream, callback);
|
||||
}, DesktopSharingEventTypes.NEW_STREAM_CREATED);
|
||||
APP.xmpp.addListener(XMPPEvents.CHANGED_STREAMS, function (jid, changedStreams) {
|
||||
for(var i = 0; i < changedStreams.length; i++) {
|
||||
var type = changedStreams[i].type;
|
||||
if (type != "audio") {
|
||||
var peerStreams = self.remoteStreams[jid];
|
||||
if(!peerStreams)
|
||||
continue;
|
||||
var videoStream = peerStreams[MediaStreamType.VIDEO_TYPE];
|
||||
if(!videoStream)
|
||||
continue;
|
||||
videoStream.videoType = changedStreams[i].type;
|
||||
}
|
||||
}
|
||||
});
|
||||
APP.xmpp.addListener(XMPPEvents.CALL_INCOMING, function(event) {
|
||||
DataChannels.init(event.peerconnection, eventEmitter);
|
||||
});
|
||||
@@ -133,20 +152,33 @@ var RTC = {
|
||||
DataChannels.handleSelectedEndpointEvent);
|
||||
APP.UI.addListener(UIEvents.PINNED_ENDPOINT,
|
||||
DataChannels.handlePinnedEndpointEvent);
|
||||
this.rtcUtils = new RTCUtils(this);
|
||||
this.rtcUtils.obtainAudioAndVideoPermissions();
|
||||
|
||||
// In case of IE we continue from 'onReady' callback
|
||||
// passed to RTCUtils constructor. It will be invoked by Temasys plugin
|
||||
// once it is initialized.
|
||||
var onReady = function () {
|
||||
eventEmitter.emit(RTCEvents.RTC_READY, true);
|
||||
self.rtcUtils.obtainAudioAndVideoPermissions(
|
||||
null, null, getMediaStreamUsage());
|
||||
};
|
||||
|
||||
this.rtcUtils = new RTCUtils(this, onReady);
|
||||
|
||||
// Call onReady() if Temasys plugin is not used
|
||||
if (!RTCBrowserType.isTemasysPluginUsed()) {
|
||||
onReady();
|
||||
}
|
||||
},
|
||||
muteRemoteVideoStream: function (jid, value) {
|
||||
var stream;
|
||||
|
||||
if(this.remoteStreams[jid] &&
|
||||
this.remoteStreams[jid][MediaStreamType.VIDEO_TYPE])
|
||||
{
|
||||
this.remoteStreams[jid][MediaStreamType.VIDEO_TYPE]) {
|
||||
stream = this.remoteStreams[jid][MediaStreamType.VIDEO_TYPE];
|
||||
}
|
||||
|
||||
if(!stream)
|
||||
return false;
|
||||
return true;
|
||||
|
||||
if (value != stream.muted) {
|
||||
stream.setMute(value);
|
||||
@@ -166,38 +198,77 @@ var RTC = {
|
||||
},
|
||||
changeLocalVideo: function (stream, isUsingScreenStream, callback) {
|
||||
var oldStream = this.localVideo.getOriginalStream();
|
||||
var type = (isUsingScreenStream? "screen" : "video");
|
||||
this.localVideo = this.createLocalStream(stream, "video", true, type);
|
||||
var type = (isUsingScreenStream ? "screen" : "camera");
|
||||
var localCallback = callback;
|
||||
if(this.localVideo.isMuted() && this.localVideo.videoType !== type) {
|
||||
localCallback = function() {
|
||||
APP.xmpp.setVideoMute(false, function(mute) {
|
||||
eventEmitter.emit(RTCEvents.VIDEO_MUTE, mute);
|
||||
});
|
||||
|
||||
callback();
|
||||
};
|
||||
}
|
||||
// FIXME: Workaround for FF/IE/Safari
|
||||
if (stream && stream.videoStream) {
|
||||
stream = stream.videoStream;
|
||||
}
|
||||
var videoStream = this.rtcUtils.createStream(stream, true);
|
||||
this.localVideo = this.createLocalStream(videoStream, "video", true, type);
|
||||
// Stop the stream to trigger onended event for old stream
|
||||
oldStream.stop();
|
||||
APP.xmpp.switchStreams(stream, oldStream,callback);
|
||||
|
||||
this.switchVideoStreams(videoStream, oldStream);
|
||||
|
||||
APP.xmpp.switchStreams(videoStream, oldStream,localCallback);
|
||||
},
|
||||
/**
|
||||
* Checks if video identified by given src is desktop stream.
|
||||
* @param videoSrc eg.
|
||||
* blob:https%3A//pawel.jitsi.net/9a46e0bd-131e-4d18-9c14-a9264e8db395
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isVideoSrcDesktop: function (jid) {
|
||||
if(!jid)
|
||||
return false;
|
||||
var isDesktop = false;
|
||||
var stream = null;
|
||||
if (APP.xmpp.myJid() &&
|
||||
APP.xmpp.myResource() === jid) {
|
||||
// local video
|
||||
stream = this.localVideo;
|
||||
changeLocalAudio: function (stream, callback) {
|
||||
var oldStream = this.localAudio.getOriginalStream();
|
||||
var newStream = this.rtcUtils.createStream(stream);
|
||||
this.localAudio = this.createLocalStream(newStream, "audio", true);
|
||||
// Stop the stream to trigger onended event for old stream
|
||||
oldStream.stop();
|
||||
APP.xmpp.switchStreams(newStream, oldStream, callback, true);
|
||||
},
|
||||
isVideoMuted: function (jid) {
|
||||
if (jid === APP.xmpp.myJid()) {
|
||||
var localVideo = APP.RTC.localVideo;
|
||||
return (!localVideo || localVideo.isMuted());
|
||||
} else {
|
||||
var peerStreams = this.remoteStreams[jid];
|
||||
if(!peerStreams)
|
||||
return false;
|
||||
stream = peerStreams[MediaStreamType.VIDEO_TYPE];
|
||||
if (!APP.RTC.remoteStreams[jid] ||
|
||||
!APP.RTC.remoteStreams[jid][MediaStreamType.VIDEO_TYPE]) {
|
||||
return null;
|
||||
}
|
||||
return APP.RTC.remoteStreams[jid][MediaStreamType.VIDEO_TYPE].muted;
|
||||
}
|
||||
},
|
||||
setVideoMute: function (mute, callback, options) {
|
||||
if (!this.localVideo)
|
||||
return;
|
||||
|
||||
if(stream)
|
||||
isDesktop = (stream.videoType === "screen");
|
||||
|
||||
return isDesktop;
|
||||
if (mute == APP.RTC.localVideo.isMuted())
|
||||
{
|
||||
APP.xmpp.sendVideoInfoPresence(mute);
|
||||
if (callback)
|
||||
callback(mute);
|
||||
}
|
||||
else
|
||||
{
|
||||
APP.RTC.localVideo.setMute(mute);
|
||||
APP.xmpp.setVideoMute(
|
||||
mute,
|
||||
callback,
|
||||
options);
|
||||
}
|
||||
},
|
||||
setDeviceAvailability: function (devices) {
|
||||
if(!devices)
|
||||
return;
|
||||
if(devices.audio === true || devices.audio === false)
|
||||
this.devices.audio = devices.audio;
|
||||
if(devices.video === true || devices.video === false)
|
||||
this.devices.video = devices.video;
|
||||
eventEmitter.emit(RTCEvents.AVAILABLE_DEVICES_CHANGED, this.devices);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
171
modules/RTC/RTCBrowserType.js
Normal file
171
modules/RTC/RTCBrowserType.js
Normal file
@@ -0,0 +1,171 @@
|
||||
|
||||
var currentBrowser;
|
||||
|
||||
var browserVersion;
|
||||
|
||||
var isAndroid;
|
||||
|
||||
var RTCBrowserType = {
|
||||
|
||||
RTC_BROWSER_CHROME: "rtc_browser.chrome",
|
||||
|
||||
RTC_BROWSER_OPERA: "rtc_browser.opera",
|
||||
|
||||
RTC_BROWSER_FIREFOX: "rtc_browser.firefox",
|
||||
|
||||
RTC_BROWSER_IEXPLORER: "rtc_browser.iexplorer",
|
||||
|
||||
RTC_BROWSER_SAFARI: "rtc_browser.safari",
|
||||
|
||||
getBrowserType: function () {
|
||||
return currentBrowser;
|
||||
},
|
||||
|
||||
isChrome: function () {
|
||||
return currentBrowser === RTCBrowserType.RTC_BROWSER_CHROME;
|
||||
},
|
||||
|
||||
isOpera: function () {
|
||||
return currentBrowser === RTCBrowserType.RTC_BROWSER_OPERA;
|
||||
},
|
||||
isFirefox: function () {
|
||||
return currentBrowser === RTCBrowserType.RTC_BROWSER_FIREFOX;
|
||||
},
|
||||
|
||||
isIExplorer: function () {
|
||||
return currentBrowser === RTCBrowserType.RTC_BROWSER_IEXPLORER;
|
||||
},
|
||||
|
||||
isSafari: function () {
|
||||
return currentBrowser === RTCBrowserType.RTC_BROWSER_SAFARI;
|
||||
},
|
||||
isTemasysPluginUsed: function () {
|
||||
return RTCBrowserType.isIExplorer() || RTCBrowserType.isSafari();
|
||||
},
|
||||
getFirefoxVersion: function () {
|
||||
return RTCBrowserType.isFirefox() ? browserVersion : null;
|
||||
},
|
||||
|
||||
getChromeVersion: function () {
|
||||
return RTCBrowserType.isChrome() ? browserVersion : null;
|
||||
},
|
||||
|
||||
usesPlanB: function() {
|
||||
return RTCBrowserType.isChrome() || RTCBrowserType.isOpera() ||
|
||||
RTCBrowserType.isTemasysPluginUsed();
|
||||
},
|
||||
|
||||
usesUnifiedPlan: function() {
|
||||
return RTCBrowserType.isFirefox();
|
||||
},
|
||||
|
||||
/**
|
||||
* Whether the browser is running on an android device.
|
||||
*/
|
||||
isAndroid: function() {
|
||||
return isAndroid;
|
||||
}
|
||||
|
||||
// Add version getters for other browsers when needed
|
||||
};
|
||||
|
||||
// detectOpera() must be called before detectChrome() !!!
|
||||
// otherwise Opera wil be detected as Chrome
|
||||
function detectChrome() {
|
||||
if (navigator.webkitGetUserMedia) {
|
||||
currentBrowser = RTCBrowserType.RTC_BROWSER_CHROME;
|
||||
var userAgent = navigator.userAgent.toLowerCase();
|
||||
// We can assume that user agent is chrome, because it's
|
||||
// enforced when 'ext' streaming method is set
|
||||
var ver = parseInt(userAgent.match(/chrome\/(\d+)\./)[1], 10);
|
||||
console.log("This appears to be Chrome, ver: " + ver);
|
||||
return ver;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function detectOpera() {
|
||||
var userAgent = navigator.userAgent;
|
||||
if (userAgent.match(/Opera|OPR/)) {
|
||||
currentBrowser = RTCBrowserType.RTC_BROWSER_OPERA;
|
||||
var version = userAgent.match(/(Opera|OPR) ?\/?(\d+)\.?/)[2];
|
||||
console.info("This appears to be Opera, ver: " + version);
|
||||
return version;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function detectFirefox() {
|
||||
if (navigator.mozGetUserMedia) {
|
||||
currentBrowser = RTCBrowserType.RTC_BROWSER_FIREFOX;
|
||||
var version = parseInt(
|
||||
navigator.userAgent.match(/Firefox\/([0-9]+)\./)[1], 10);
|
||||
console.log('This appears to be Firefox, ver: ' + version);
|
||||
return version;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function detectSafari() {
|
||||
if ((/^((?!chrome).)*safari/i.test(navigator.userAgent))) {
|
||||
currentBrowser = RTCBrowserType.RTC_BROWSER_SAFARI;
|
||||
console.info("This appears to be Safari");
|
||||
// FIXME detect Safari version when needed
|
||||
return 1;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function detectIE() {
|
||||
var version;
|
||||
var ua = window.navigator.userAgent;
|
||||
|
||||
var msie = ua.indexOf('MSIE ');
|
||||
if (msie > 0) {
|
||||
// IE 10 or older => return version number
|
||||
version = parseInt(ua.substring(msie + 5, ua.indexOf('.', msie)), 10);
|
||||
}
|
||||
|
||||
var trident = ua.indexOf('Trident/');
|
||||
if (!version && trident > 0) {
|
||||
// IE 11 => return version number
|
||||
var rv = ua.indexOf('rv:');
|
||||
version = parseInt(ua.substring(rv + 3, ua.indexOf('.', rv)), 10);
|
||||
}
|
||||
|
||||
var edge = ua.indexOf('Edge/');
|
||||
if (!version && edge > 0) {
|
||||
// IE 12 => return version number
|
||||
version = parseInt(ua.substring(edge + 5, ua.indexOf('.', edge)), 10);
|
||||
}
|
||||
|
||||
if (version) {
|
||||
currentBrowser = RTCBrowserType.RTC_BROWSER_IEXPLORER;
|
||||
console.info("This appears to be IExplorer, ver: " + version);
|
||||
}
|
||||
return version;
|
||||
}
|
||||
|
||||
function detectBrowser() {
|
||||
var version;
|
||||
var detectors = [
|
||||
detectOpera,
|
||||
detectChrome,
|
||||
detectFirefox,
|
||||
detectIE,
|
||||
detectSafari
|
||||
];
|
||||
// Try all browser detectors
|
||||
for (var i = 0; i < detectors.length; i++) {
|
||||
version = detectors[i]();
|
||||
if (version)
|
||||
return version;
|
||||
}
|
||||
console.error("Failed to detect browser type");
|
||||
return undefined;
|
||||
}
|
||||
|
||||
browserVersion = detectBrowser();
|
||||
isAndroid = navigator.userAgent.indexOf('Android') != -1;
|
||||
|
||||
module.exports = RTCBrowserType;
|
||||
@@ -1,5 +1,8 @@
|
||||
var RTCBrowserType = require("../../service/RTC/RTCBrowserType.js");
|
||||
/* global config, require, attachMediaStream, getUserMedia */
|
||||
var RTCBrowserType = require("./RTCBrowserType");
|
||||
var Resolutions = require("../../service/RTC/Resolutions");
|
||||
var AdapterJS = require("./adapter.screenshare");
|
||||
var SDPUtil = require("../xmpp/SDPUtil");
|
||||
|
||||
var currentResolution = null;
|
||||
|
||||
@@ -9,11 +12,9 @@ function getPreviousResolution(resolution) {
|
||||
var order = Resolutions[resolution].order;
|
||||
var res = null;
|
||||
var resName = null;
|
||||
for(var i in Resolutions)
|
||||
{
|
||||
for(var i in Resolutions) {
|
||||
var tmp = Resolutions[i];
|
||||
if(res == null || (res.order < tmp.order && tmp.order < order))
|
||||
{
|
||||
if(res == null || (res.order < tmp.order && tmp.order < order)) {
|
||||
resName = i;
|
||||
res = tmp;
|
||||
}
|
||||
@@ -21,53 +22,83 @@ function getPreviousResolution(resolution) {
|
||||
return resName;
|
||||
}
|
||||
|
||||
function setResolutionConstraints(constraints, resolution, isAndroid)
|
||||
{
|
||||
if (resolution && !constraints.video || isAndroid) {
|
||||
constraints.video = { mandatory: {}, optional: [] };// same behaviour as true
|
||||
}
|
||||
function setResolutionConstraints(constraints, resolution) {
|
||||
var isAndroid = RTCBrowserType.isAndroid();
|
||||
|
||||
if(Resolutions[resolution])
|
||||
{
|
||||
if (Resolutions[resolution]) {
|
||||
constraints.video.mandatory.minWidth = Resolutions[resolution].width;
|
||||
constraints.video.mandatory.minHeight = Resolutions[resolution].height;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isAndroid) {
|
||||
constraints.video.mandatory.minWidth = 320;
|
||||
constraints.video.mandatory.minHeight = 240;
|
||||
constraints.video.mandatory.maxFrameRate = 15;
|
||||
}
|
||||
else if (isAndroid) {
|
||||
// FIXME can't remember if the purpose of this was to always request
|
||||
// low resolution on Android ? if yes it should be moved up front
|
||||
constraints.video.mandatory.minWidth = 320;
|
||||
constraints.video.mandatory.minHeight = 240;
|
||||
constraints.video.mandatory.maxFrameRate = 15;
|
||||
}
|
||||
|
||||
if (constraints.video.mandatory.minWidth)
|
||||
constraints.video.mandatory.maxWidth = constraints.video.mandatory.minWidth;
|
||||
constraints.video.mandatory.maxWidth =
|
||||
constraints.video.mandatory.minWidth;
|
||||
if (constraints.video.mandatory.minHeight)
|
||||
constraints.video.mandatory.maxHeight = constraints.video.mandatory.minHeight;
|
||||
constraints.video.mandatory.maxHeight =
|
||||
constraints.video.mandatory.minHeight;
|
||||
}
|
||||
|
||||
function getConstraints(um, resolution, bandwidth, fps, desktopStream, isAndroid)
|
||||
{
|
||||
function getConstraints(um, resolution, bandwidth, fps, desktopStream) {
|
||||
var constraints = {audio: false, video: false};
|
||||
|
||||
if (um.indexOf('video') >= 0) {
|
||||
constraints.video = { mandatory: {}, optional: [] };// same behaviour as true
|
||||
// same behaviour as true
|
||||
constraints.video = { mandatory: {}, optional: [] };
|
||||
|
||||
constraints.video.optional.push({ googLeakyBucket: true });
|
||||
|
||||
setResolutionConstraints(constraints, resolution);
|
||||
}
|
||||
if (um.indexOf('audio') >= 0) {
|
||||
constraints.audio = { mandatory: {}, optional: []};// same behaviour as true
|
||||
if (!RTCBrowserType.isFirefox()) {
|
||||
// same behaviour as true
|
||||
constraints.audio = { mandatory: {}, optional: []};
|
||||
// if it is good enough for hangouts...
|
||||
constraints.audio.optional.push(
|
||||
{googEchoCancellation: true},
|
||||
{googAutoGainControl: true},
|
||||
{googNoiseSupression: true},
|
||||
{googHighpassFilter: true},
|
||||
{googNoisesuppression2: true},
|
||||
{googEchoCancellation2: true},
|
||||
{googAutoGainControl2: true}
|
||||
);
|
||||
} else {
|
||||
constraints.audio = true;
|
||||
}
|
||||
}
|
||||
if (um.indexOf('screen') >= 0) {
|
||||
constraints.video = {
|
||||
mandatory: {
|
||||
chromeMediaSource: "screen",
|
||||
googLeakyBucket: true,
|
||||
maxWidth: window.screen.width,
|
||||
maxHeight: window.screen.height,
|
||||
maxFrameRate: 3
|
||||
},
|
||||
optional: []
|
||||
};
|
||||
if (RTCBrowserType.isChrome()) {
|
||||
constraints.video = {
|
||||
mandatory: {
|
||||
chromeMediaSource: "screen",
|
||||
googLeakyBucket: true,
|
||||
maxWidth: window.screen.width,
|
||||
maxHeight: window.screen.height,
|
||||
maxFrameRate: 3
|
||||
},
|
||||
optional: []
|
||||
};
|
||||
} else if (RTCBrowserType.isTemasysPluginUsed()) {
|
||||
constraints.video = {
|
||||
optional: [
|
||||
{
|
||||
sourceId: AdapterJS.WebRTCPlugin.plugin.screensharingKey
|
||||
}
|
||||
]
|
||||
};
|
||||
} else {
|
||||
console.error(
|
||||
"'screen' WebRTC media source is supported only in Chrome" +
|
||||
" and with Temasys plugin");
|
||||
}
|
||||
}
|
||||
if (um.indexOf('desktop') >= 0) {
|
||||
constraints.video = {
|
||||
@@ -83,56 +114,44 @@ function getConstraints(um, resolution, bandwidth, fps, desktopStream, isAndroid
|
||||
};
|
||||
}
|
||||
|
||||
if (constraints.audio) {
|
||||
// if it is good enough for hangouts...
|
||||
constraints.audio.optional.push(
|
||||
{googEchoCancellation: true},
|
||||
{googAutoGainControl: true},
|
||||
{googNoiseSupression: true},
|
||||
{googHighpassFilter: true},
|
||||
{googNoisesuppression2: true},
|
||||
{googEchoCancellation2: true},
|
||||
{googAutoGainControl2: true}
|
||||
);
|
||||
}
|
||||
if (constraints.video) {
|
||||
constraints.video.optional.push(
|
||||
{googNoiseReduction: false} // chrome 37 workaround for issue 3807, reenable in M38
|
||||
);
|
||||
if (um.indexOf('video') >= 0) {
|
||||
constraints.video.optional.push(
|
||||
{googLeakyBucket: true}
|
||||
);
|
||||
if (bandwidth) {
|
||||
if (!constraints.video) {
|
||||
//same behaviour as true
|
||||
constraints.video = {mandatory: {}, optional: []};
|
||||
}
|
||||
}
|
||||
|
||||
if (um.indexOf('video') >= 0) {
|
||||
setResolutionConstraints(constraints, resolution, isAndroid);
|
||||
}
|
||||
|
||||
if (bandwidth) { // doesn't work currently, see webrtc issue 1846
|
||||
if (!constraints.video) constraints.video = {mandatory: {}, optional: []};//same behaviour as true
|
||||
constraints.video.optional.push({bandwidth: bandwidth});
|
||||
}
|
||||
if (fps) { // for some cameras it might be necessary to request 30fps
|
||||
if (fps) {
|
||||
// for some cameras it might be necessary to request 30fps
|
||||
// so they choose 30fps mjpg over 10fps yuy2
|
||||
if (!constraints.video) constraints.video = {mandatory: {}, optional: []};// same behaviour as true;
|
||||
if (!constraints.video) {
|
||||
// same behaviour as true;
|
||||
constraints.video = {mandatory: {}, optional: []};
|
||||
}
|
||||
constraints.video.mandatory.minFrameRate = fps;
|
||||
}
|
||||
|
||||
// we turn audio for both audio and video tracks, the fake audio & video seems to work
|
||||
// only when enabled in one getUserMedia call, we cannot get fake audio separate by fake video
|
||||
// this later can be a problem with some of the tests
|
||||
if(RTCBrowserType.isFirefox() && config.firefox_fake_device)
|
||||
{
|
||||
constraints.audio = true;
|
||||
constraints.fake = true;
|
||||
}
|
||||
|
||||
return constraints;
|
||||
}
|
||||
|
||||
|
||||
function RTCUtils(RTCService)
|
||||
function RTCUtils(RTCService, onTemasysPluginReady)
|
||||
{
|
||||
var self = this;
|
||||
this.service = RTCService;
|
||||
if (navigator.mozGetUserMedia) {
|
||||
console.log('This appears to be Firefox');
|
||||
var version = parseInt(navigator.userAgent.match(/Firefox\/([0-9]+)\./)[1], 10);
|
||||
if (version >= 22) {
|
||||
if (RTCBrowserType.isFirefox()) {
|
||||
var FFversion = RTCBrowserType.getFirefoxVersion();
|
||||
if (FFversion >= 40) {
|
||||
this.peerconnection = mozRTCPeerConnection;
|
||||
this.browser = RTCBrowserType.RTC_BROWSER_FIREFOX;
|
||||
this.getUserMedia = navigator.mozGetUserMedia.bind(navigator);
|
||||
this.pc_constraints = {};
|
||||
this.attachMediaStream = function (element, stream) {
|
||||
@@ -143,30 +162,42 @@ function RTCUtils(RTCService)
|
||||
//
|
||||
// https://groups.google.com/forum/#!topic/mozilla.dev.media/pKOiioXonJg
|
||||
// https://github.com/webrtc/samples/issues/302
|
||||
if(!element[0])
|
||||
return;
|
||||
element[0].mozSrcObject = stream;
|
||||
element[0].play();
|
||||
};
|
||||
this.getStreamID = function (stream) {
|
||||
var tracks = stream.getVideoTracks();
|
||||
if(!tracks || tracks.length == 0)
|
||||
{
|
||||
tracks = stream.getAudioTracks();
|
||||
var id = stream.id;
|
||||
if (!id) {
|
||||
var tracks = stream.getVideoTracks();
|
||||
if (!tracks || tracks.length === 0) {
|
||||
tracks = stream.getAudioTracks();
|
||||
}
|
||||
id = tracks[0].id;
|
||||
}
|
||||
return tracks[0].id.replace(/[\{,\}]/g,"");
|
||||
return SDPUtil.filter_special_chars(id);
|
||||
};
|
||||
this.getVideoSrc = function (element) {
|
||||
if(!element)
|
||||
return null;
|
||||
return element.mozSrcObject;
|
||||
};
|
||||
this.setVideoSrc = function (element, src) {
|
||||
element.mozSrcObject = src;
|
||||
if(element)
|
||||
element.mozSrcObject = src;
|
||||
};
|
||||
RTCSessionDescription = mozRTCSessionDescription;
|
||||
RTCIceCandidate = mozRTCIceCandidate;
|
||||
} else {
|
||||
console.error(
|
||||
"Firefox version too old: " + FFversion + ". Required >= 40.");
|
||||
window.location.href = 'unsupported_browser.html';
|
||||
return;
|
||||
}
|
||||
} else if (navigator.webkitGetUserMedia) {
|
||||
console.log('This appears to be Chrome');
|
||||
|
||||
} else if (RTCBrowserType.isChrome() || RTCBrowserType.isOpera()) {
|
||||
this.peerconnection = webkitRTCPeerConnection;
|
||||
this.browser = RTCBrowserType.RTC_BROWSER_CHROME;
|
||||
this.getUserMedia = navigator.webkitGetUserMedia.bind(navigator);
|
||||
this.attachMediaStream = function (element, stream) {
|
||||
element.attr('src', webkitURL.createObjectURL(stream));
|
||||
@@ -174,17 +205,20 @@ function RTCUtils(RTCService)
|
||||
this.getStreamID = function (stream) {
|
||||
// streams from FF endpoints have the characters '{' and '}'
|
||||
// that make jQuery choke.
|
||||
return stream.id.replace(/[\{,\}]/g,"");
|
||||
return SDPUtil.filter_special_chars(stream.id);
|
||||
};
|
||||
this.getVideoSrc = function (element) {
|
||||
if(!element)
|
||||
return null;
|
||||
return element.getAttribute("src");
|
||||
};
|
||||
this.setVideoSrc = function (element, src) {
|
||||
element.setAttribute("src", src);
|
||||
if(element)
|
||||
element.setAttribute("src", src);
|
||||
};
|
||||
// DTLS should now be enabled by default but..
|
||||
this.pc_constraints = {'optional': [{'DtlsSrtpKeyAgreement': 'true'}]};
|
||||
if (navigator.userAgent.indexOf('Android') != -1) {
|
||||
if (RTCBrowserType.isAndroid()) {
|
||||
this.pc_constraints = {}; // disable DTLS on Android
|
||||
}
|
||||
if (!webkitMediaStream.prototype.getVideoTracks) {
|
||||
@@ -198,71 +232,92 @@ function RTCUtils(RTCService)
|
||||
};
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
try { console.log('Browser does not appear to be WebRTC-capable'); } catch (e) { }
|
||||
// Detect IE/Safari
|
||||
else if (RTCBrowserType.isTemasysPluginUsed()) {
|
||||
|
||||
//AdapterJS.WebRTCPlugin.setLogLevel(
|
||||
// AdapterJS.WebRTCPlugin.PLUGIN_LOG_LEVELS.VERBOSE);
|
||||
|
||||
AdapterJS.webRTCReady(function (isPlugin) {
|
||||
|
||||
self.peerconnection = RTCPeerConnection;
|
||||
self.getUserMedia = getUserMedia;
|
||||
self.attachMediaStream = function (elSel, stream) {
|
||||
|
||||
if (stream.id === "dummyAudio" || stream.id === "dummyVideo") {
|
||||
return;
|
||||
}
|
||||
|
||||
attachMediaStream(elSel[0], stream);
|
||||
};
|
||||
self.getStreamID = function (stream) {
|
||||
var id = SDPUtil.filter_special_chars(stream.label);
|
||||
return id;
|
||||
};
|
||||
self.getVideoSrc = function (element) {
|
||||
if (!element) {
|
||||
console.warn("Attempt to get video SRC of null element");
|
||||
return null;
|
||||
}
|
||||
var children = element.children;
|
||||
for (var i = 0; i !== children.length; ++i) {
|
||||
if (children[i].name === 'streamId') {
|
||||
return children[i].value;
|
||||
}
|
||||
}
|
||||
//console.info(element.id + " SRC: " + src);
|
||||
return null;
|
||||
};
|
||||
self.setVideoSrc = function (element, src) {
|
||||
//console.info("Set video src: ", element, src);
|
||||
if (!src) {
|
||||
console.warn("Not attaching video stream, 'src' is null");
|
||||
return;
|
||||
}
|
||||
AdapterJS.WebRTCPlugin.WaitForPluginReady();
|
||||
var stream = AdapterJS.WebRTCPlugin.plugin
|
||||
.getStreamWithId(AdapterJS.WebRTCPlugin.pageId, src);
|
||||
attachMediaStream(element, stream);
|
||||
};
|
||||
|
||||
onTemasysPluginReady(isPlugin);
|
||||
});
|
||||
} else {
|
||||
try {
|
||||
console.log('Browser does not appear to be WebRTC-capable');
|
||||
} catch (e) { }
|
||||
window.location.href = 'unsupported_browser.html';
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.browser !== RTCBrowserType.RTC_BROWSER_CHROME &&
|
||||
config.enableFirefoxSupport !== true) {
|
||||
window.location.href = 'unsupported_browser.html';
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
RTCUtils.prototype.getUserMediaWithConstraints = function(
|
||||
um, success_callback, failure_callback, resolution,bandwidth, fps,
|
||||
desktopStream)
|
||||
{
|
||||
desktopStream) {
|
||||
currentResolution = resolution;
|
||||
// Check if we are running on Android device
|
||||
var isAndroid = navigator.userAgent.indexOf('Android') != -1;
|
||||
|
||||
var constraints = getConstraints(
|
||||
um, resolution, bandwidth, fps, desktopStream, isAndroid);
|
||||
um, resolution, bandwidth, fps, desktopStream);
|
||||
|
||||
var isFF = navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
|
||||
console.info("Get media constraints", constraints);
|
||||
|
||||
var self = this;
|
||||
|
||||
try {
|
||||
if (config.enableSimulcast
|
||||
&& constraints.video
|
||||
&& constraints.video.chromeMediaSource !== 'screen'
|
||||
&& constraints.video.chromeMediaSource !== 'desktop'
|
||||
&& !isAndroid
|
||||
|
||||
// We currently do not support FF, as it doesn't have multistream support.
|
||||
&& !isFF) {
|
||||
APP.simulcast.getUserMedia(constraints, function (stream) {
|
||||
console.log('onUserMediaSuccess');
|
||||
success_callback(stream);
|
||||
},
|
||||
function (error) {
|
||||
console.warn('Failed to get access to local media. Error ', error);
|
||||
if (failure_callback) {
|
||||
failure_callback(error);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
|
||||
this.getUserMedia(constraints,
|
||||
function (stream) {
|
||||
console.log('onUserMediaSuccess');
|
||||
success_callback(stream);
|
||||
},
|
||||
function (error) {
|
||||
console.warn('Failed to get access to local media. Error ',
|
||||
error, constraints);
|
||||
if (failure_callback) {
|
||||
failure_callback(error);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
this.getUserMedia(constraints,
|
||||
function (stream) {
|
||||
console.log('onUserMediaSuccess');
|
||||
self.setAvailableDevices(um, true);
|
||||
success_callback(stream);
|
||||
},
|
||||
function (error) {
|
||||
self.setAvailableDevices(um, false);
|
||||
console.warn('Failed to get access to local media. Error ',
|
||||
error, constraints);
|
||||
if (failure_callback) {
|
||||
failure_callback(error);
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('GUM failed: ', e);
|
||||
if(failure_callback) {
|
||||
@@ -271,29 +326,119 @@ RTCUtils.prototype.getUserMediaWithConstraints = function(
|
||||
}
|
||||
};
|
||||
|
||||
RTCUtils.prototype.setAvailableDevices = function (um, available) {
|
||||
var devices = {};
|
||||
if(um.indexOf("video") != -1) {
|
||||
devices.video = available;
|
||||
}
|
||||
if(um.indexOf("audio") != -1) {
|
||||
devices.audio = available;
|
||||
}
|
||||
this.service.setDeviceAvailability(devices);
|
||||
};
|
||||
|
||||
/**
|
||||
* We ask for audio and video combined stream in order to get permissions and
|
||||
* not to ask twice.
|
||||
*/
|
||||
RTCUtils.prototype.obtainAudioAndVideoPermissions = function() {
|
||||
RTCUtils.prototype.obtainAudioAndVideoPermissions =
|
||||
function(devices, callback, usageOptions)
|
||||
{
|
||||
var self = this;
|
||||
// Get AV
|
||||
|
||||
this.getUserMediaWithConstraints(
|
||||
['audio', 'video'],
|
||||
var successCallback = function (stream) {
|
||||
if(callback)
|
||||
callback(stream, usageOptions);
|
||||
else
|
||||
self.successCallback(stream, usageOptions);
|
||||
};
|
||||
|
||||
if(!devices)
|
||||
devices = ['audio', 'video'];
|
||||
|
||||
var newDevices = [];
|
||||
|
||||
|
||||
if(usageOptions)
|
||||
for(var i = 0; i < devices.length; i++) {
|
||||
var device = devices[i];
|
||||
if(usageOptions[device] === true)
|
||||
newDevices.push(device);
|
||||
}
|
||||
else
|
||||
newDevices = devices;
|
||||
|
||||
if(newDevices.length === 0) {
|
||||
successCallback();
|
||||
return;
|
||||
}
|
||||
|
||||
if (RTCBrowserType.isFirefox() || RTCBrowserType.isTemasysPluginUsed()) {
|
||||
|
||||
// With FF/IE we can't split the stream into audio and video because FF
|
||||
// doesn't support media stream constructors. So, we need to get the
|
||||
// audio stream separately from the video stream using two distinct GUM
|
||||
// calls. Not very user friendly :-( but we don't have many other
|
||||
// options neither.
|
||||
//
|
||||
// Note that we pack those 2 streams in a single object and pass it to
|
||||
// the successCallback method.
|
||||
var obtainVideo = function (audioStream) {
|
||||
self.getUserMediaWithConstraints(
|
||||
['video'],
|
||||
function (videoStream) {
|
||||
return successCallback({
|
||||
audioStream: audioStream,
|
||||
videoStream: videoStream
|
||||
});
|
||||
},
|
||||
function (error) {
|
||||
console.error(
|
||||
'failed to obtain video stream - stop', error);
|
||||
self.errorCallback(error);
|
||||
},
|
||||
config.resolution || '360');
|
||||
};
|
||||
var obtainAudio = function () {
|
||||
self.getUserMediaWithConstraints(
|
||||
['audio'],
|
||||
function (audioStream) {
|
||||
if (newDevices.indexOf('video') !== -1)
|
||||
obtainVideo(audioStream);
|
||||
},
|
||||
function (error) {
|
||||
console.error(
|
||||
'failed to obtain audio stream - stop', error);
|
||||
self.errorCallback(error);
|
||||
}
|
||||
);
|
||||
};
|
||||
if (newDevices.indexOf('audio') !== -1) {
|
||||
obtainAudio();
|
||||
} else {
|
||||
obtainVideo(null);
|
||||
}
|
||||
} else {
|
||||
this.getUserMediaWithConstraints(
|
||||
newDevices,
|
||||
function (stream) {
|
||||
self.successCallback(stream);
|
||||
successCallback(stream);
|
||||
},
|
||||
function (error) {
|
||||
self.errorCallback(error);
|
||||
},
|
||||
config.resolution || '360');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
RTCUtils.prototype.successCallback = function (stream) {
|
||||
console.log('got', stream, stream.getAudioTracks().length,
|
||||
stream.getVideoTracks().length);
|
||||
this.handleLocalStream(stream);
|
||||
RTCUtils.prototype.successCallback = function (stream, usageOptions) {
|
||||
// If this is FF or IE, the stream parameter is *not* a MediaStream object,
|
||||
// it's an object with two properties: audioStream, videoStream.
|
||||
if (stream && stream.getAudioTracks && stream.getVideoTracks)
|
||||
console.log('got', stream, stream.getAudioTracks().length,
|
||||
stream.getVideoTracks().length);
|
||||
this.handleLocalStream(stream, usageOptions);
|
||||
};
|
||||
|
||||
RTCUtils.prototype.errorCallback = function (error) {
|
||||
@@ -314,8 +459,7 @@ RTCUtils.prototype.errorCallback = function (error) {
|
||||
return self.errorCallback(error);
|
||||
}, resolution);
|
||||
}
|
||||
else
|
||||
{
|
||||
else {
|
||||
self.getUserMediaWithConstraints(
|
||||
['audio'],
|
||||
function (stream) {
|
||||
@@ -324,42 +468,93 @@ RTCUtils.prototype.errorCallback = function (error) {
|
||||
function (error) {
|
||||
console.error('failed to obtain audio/video stream - stop',
|
||||
error);
|
||||
APP.UI.messageHandler.showError("dialog.error",
|
||||
"dialog.failedpermissions");
|
||||
return self.successCallback(null);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
RTCUtils.prototype.handleLocalStream = function(stream)
|
||||
{
|
||||
if(window.webkitMediaStream)
|
||||
{
|
||||
var audioStream = new webkitMediaStream();
|
||||
var videoStream = new webkitMediaStream();
|
||||
var audioTracks = stream.getAudioTracks();
|
||||
var videoTracks = stream.getVideoTracks();
|
||||
for (var i = 0; i < audioTracks.length; i++) {
|
||||
audioStream.addTrack(audioTracks[i]);
|
||||
}
|
||||
|
||||
this.service.createLocalStream(audioStream, "audio");
|
||||
|
||||
for (i = 0; i < videoTracks.length; i++) {
|
||||
videoStream.addTrack(videoTracks[i]);
|
||||
}
|
||||
|
||||
|
||||
this.service.createLocalStream(videoStream, "video");
|
||||
}
|
||||
else
|
||||
{//firefox
|
||||
this.service.createLocalStream(stream, "stream");
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
RTCUtils.prototype.handleLocalStream = function(stream, usageOptions) {
|
||||
// If this is FF, the stream parameter is *not* a MediaStream object, it's
|
||||
// an object with two properties: audioStream, videoStream.
|
||||
var audioStream, videoStream;
|
||||
if(window.webkitMediaStream)
|
||||
{
|
||||
audioStream = new webkitMediaStream();
|
||||
videoStream = new webkitMediaStream();
|
||||
if(stream) {
|
||||
var audioTracks = stream.getAudioTracks();
|
||||
|
||||
for (var i = 0; i < audioTracks.length; i++) {
|
||||
audioStream.addTrack(audioTracks[i]);
|
||||
}
|
||||
|
||||
var videoTracks = stream.getVideoTracks();
|
||||
|
||||
for (i = 0; i < videoTracks.length; i++) {
|
||||
videoStream.addTrack(videoTracks[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (RTCBrowserType.isFirefox() || RTCBrowserType.isTemasysPluginUsed())
|
||||
{ // Firefox and Temasys plugin
|
||||
if (stream && stream.audioStream)
|
||||
audioStream = stream.audioStream;
|
||||
else
|
||||
audioStream = new DummyMediaStream("dummyAudio");
|
||||
|
||||
if (stream && stream.videoStream)
|
||||
videoStream = stream.videoStream;
|
||||
else
|
||||
videoStream = new DummyMediaStream("dummyVideo");
|
||||
}
|
||||
|
||||
var audioMuted = (usageOptions && usageOptions.audio === false),
|
||||
videoMuted = (usageOptions && usageOptions.video === false);
|
||||
|
||||
var audioGUM = (!usageOptions || usageOptions.audio !== false),
|
||||
videoGUM = (!usageOptions || usageOptions.video !== false);
|
||||
|
||||
|
||||
this.service.createLocalStream(audioStream, "audio", null, null,
|
||||
audioMuted, audioGUM);
|
||||
|
||||
this.service.createLocalStream(videoStream, "video", null, 'camera',
|
||||
videoMuted, videoGUM);
|
||||
};
|
||||
|
||||
function DummyMediaStream(id) {
|
||||
this.id = id;
|
||||
this.label = id;
|
||||
this.stop = function() { };
|
||||
this.getAudioTracks = function() { return []; };
|
||||
this.getVideoTracks = function() { return []; };
|
||||
}
|
||||
|
||||
RTCUtils.prototype.createStream = function(stream, isVideo) {
|
||||
var newStream = null;
|
||||
if (window.webkitMediaStream) {
|
||||
newStream = new webkitMediaStream();
|
||||
if (newStream) {
|
||||
var tracks = (isVideo ? stream.getVideoTracks() : stream.getAudioTracks());
|
||||
|
||||
for (var i = 0; i < tracks.length; i++) {
|
||||
newStream.addTrack(tracks[i]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
else {
|
||||
// FIXME: this is duplicated with 'handleLocalStream' !!!
|
||||
if (stream) {
|
||||
newStream = stream;
|
||||
} else {
|
||||
newStream =
|
||||
new DummyMediaStream(isVideo ? "dummyVideo" : "dummyAudio");
|
||||
}
|
||||
}
|
||||
|
||||
return newStream;
|
||||
};
|
||||
|
||||
module.exports = RTCUtils;
|
||||
|
||||
1163
modules/RTC/adapter.screenshare.js
Normal file
1163
modules/RTC/adapter.screenshare.js
Normal file
File diff suppressed because it is too large
Load Diff
726
modules/UI/UI.js
726
modules/UI/UI.js
File diff suppressed because it is too large
Load Diff
@@ -1,15 +1,16 @@
|
||||
/* global APP, interfaceConfig, $, Strophe */
|
||||
var CanvasUtil = require("./CanvasUtils");
|
||||
|
||||
var ASDrawContext = $('#activeSpeakerAudioLevel')[0].getContext('2d');
|
||||
var ASDrawContext = null;
|
||||
|
||||
function initActiveSpeakerAudioLevels() {
|
||||
var ASRadius = interfaceConfig.ACTIVE_SPEAKER_AVATAR_SIZE / 2;
|
||||
var ASCenter = (interfaceConfig.ACTIVE_SPEAKER_AVATAR_SIZE + ASRadius) / 2;
|
||||
|
||||
// Draw a circle.
|
||||
// Draw a circle.
|
||||
ASDrawContext.arc(ASCenter, ASCenter, ASRadius, 0, 2 * Math.PI);
|
||||
|
||||
// Add a shadow around the circle
|
||||
// Add a shadow around the circle
|
||||
ASDrawContext.shadowColor = interfaceConfig.SHADOW_COLOR;
|
||||
ASDrawContext.shadowOffsetX = 0;
|
||||
ASDrawContext.shadowOffsetY = 0;
|
||||
@@ -24,8 +25,9 @@ var AudioLevels = (function(my) {
|
||||
my.LOCAL_LEVEL = 'local';
|
||||
|
||||
my.init = function () {
|
||||
ASDrawContext = $('#activeSpeakerAudioLevel')[0].getContext('2d');
|
||||
initActiveSpeakerAudioLevels();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates the audio level canvas for the given peerJid. If the canvas
|
||||
@@ -112,7 +114,7 @@ var AudioLevels = (function(my) {
|
||||
resourceJid = APP.xmpp.myResource();
|
||||
}
|
||||
|
||||
if(resourceJid === largeVideoResourceJid) {
|
||||
if(resourceJid === largeVideoResourceJid) {
|
||||
window.requestAnimationFrame(function () {
|
||||
AudioLevels.updateActiveSpeakerAudioLevel(audioLevel);
|
||||
});
|
||||
@@ -120,10 +122,9 @@ var AudioLevels = (function(my) {
|
||||
};
|
||||
|
||||
my.updateActiveSpeakerAudioLevel = function(audioLevel) {
|
||||
if($("#activeSpeaker").css("visibility") == "hidden")
|
||||
if($("#activeSpeaker").css("visibility") == "hidden" || ASDrawContext === null)
|
||||
return;
|
||||
|
||||
|
||||
ASDrawContext.clearRect(0, 0, 300, 300);
|
||||
if(audioLevel == 0)
|
||||
return;
|
||||
@@ -165,10 +166,9 @@ var AudioLevels = (function(my) {
|
||||
* error. Since audio levels are frequently updated, the errors have
|
||||
* been observed to pile into the console, strain the CPU.
|
||||
*/
|
||||
if (audioLevelCanvasOrig)
|
||||
{
|
||||
audioLevelCanvasCache[resourceJid]
|
||||
= CanvasUtil.cloneCanvas(audioLevelCanvasOrig);
|
||||
if (audioLevelCanvasOrig) {
|
||||
audioLevelCanvasCache[resourceJid] =
|
||||
CanvasUtil.cloneCanvas(audioLevelCanvasOrig);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,15 +183,16 @@ var AudioLevels = (function(my) {
|
||||
|
||||
var shadowLevel = getShadowLevel(audioLevel);
|
||||
|
||||
if (shadowLevel > 0)
|
||||
if (shadowLevel > 0) {
|
||||
// drawContext, x, y, w, h, r, shadowColor, shadowLevel
|
||||
CanvasUtil.drawRoundRectGlow( drawContext,
|
||||
interfaceConfig.CANVAS_EXTRA/2, interfaceConfig.CANVAS_EXTRA/2,
|
||||
CanvasUtil.drawRoundRectGlow(drawContext,
|
||||
interfaceConfig.CANVAS_EXTRA / 2, interfaceConfig.CANVAS_EXTRA / 2,
|
||||
canvas.width - interfaceConfig.CANVAS_EXTRA,
|
||||
canvas.height - interfaceConfig.CANVAS_EXTRA,
|
||||
interfaceConfig.CANVAS_RADIUS,
|
||||
interfaceConfig.SHADOW_COLOR,
|
||||
shadowLevel);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -221,9 +222,8 @@ var AudioLevels = (function(my) {
|
||||
*/
|
||||
function getVideoSpanId(resourceJid) {
|
||||
var videoSpanId = null;
|
||||
if (resourceJid === AudioLevels.LOCAL_LEVEL
|
||||
|| (APP.xmpp.myResource() && resourceJid
|
||||
=== APP.xmpp.myResource()))
|
||||
if (resourceJid === AudioLevels.LOCAL_LEVEL ||
|
||||
(APP.xmpp.myResource() && resourceJid === APP.xmpp.myResource()))
|
||||
videoSpanId = 'localVideoContainer';
|
||||
else
|
||||
videoSpanId = 'participant_' + resourceJid;
|
||||
@@ -251,10 +251,10 @@ var AudioLevels = (function(my) {
|
||||
|
||||
if (resized)
|
||||
Object.keys(audioLevelCanvasCache).forEach(function (resourceJid) {
|
||||
audioLevelCanvasCache[resourceJid].width
|
||||
= width + interfaceConfig.CANVAS_EXTRA;
|
||||
audioLevelCanvasCache[resourceJid].height
|
||||
= height + interfaceConfig.CANVAS_EXTRA;
|
||||
audioLevelCanvasCache[resourceJid].width =
|
||||
width + interfaceConfig.CANVAS_EXTRA;
|
||||
audioLevelCanvasCache[resourceJid].height =
|
||||
height + interfaceConfig.CANVAS_EXTRA;
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
/* global $, APP*/
|
||||
|
||||
var LoginDialog = require('./LoginDialog');
|
||||
var Moderator = require('../../xmpp/moderator');
|
||||
|
||||
/* Initial "authentication required" dialog */
|
||||
var authDialog = null;
|
||||
/* Loop retry ID that wits for other user to create the room */
|
||||
@@ -8,7 +13,7 @@ var Authentication = {
|
||||
openAuthenticationDialog: function (roomName, intervalCallback, callback) {
|
||||
// This is the loop that will wait for the room to be created by
|
||||
// someone else. 'auth_required.moderator' will bring us back here.
|
||||
authRetryId = window.setTimeout(intervalCallback , 5000);
|
||||
authRetryId = window.setTimeout(intervalCallback, 5000);
|
||||
// Show prompt only if it's not open
|
||||
if (authDialog !== null) {
|
||||
return;
|
||||
@@ -16,13 +21,16 @@ var Authentication = {
|
||||
// extract room name from 'room@muc.server.net'
|
||||
var room = roomName.substr(0, roomName.indexOf('@'));
|
||||
|
||||
var title = APP.translation.generateTranslatonHTML("dialog.Stop");
|
||||
var msg = APP.translation.generateTranslatonHTML("dialog.AuthMsg",
|
||||
{room: room});
|
||||
var button = APP.translation.generateTranslatonHTML(
|
||||
"dialog.Authenticate");
|
||||
var buttons = {};
|
||||
buttons.authenticate = {title: button, value: "authNow"};
|
||||
var title
|
||||
= APP.translation.generateTranslationHTML("dialog.WaitingForHost");
|
||||
var msg
|
||||
= APP.translation.generateTranslationHTML(
|
||||
"dialog.WaitForHostMsg", {room: room});
|
||||
|
||||
var buttonTxt
|
||||
= APP.translation.generateTranslationHTML("dialog.IamHost");
|
||||
var buttons = [];
|
||||
buttons.push({title: buttonTxt, value: "authNow"});
|
||||
|
||||
authDialog = APP.UI.messageHandler.openDialog(
|
||||
title,
|
||||
@@ -41,12 +49,43 @@ var Authentication = {
|
||||
}
|
||||
);
|
||||
},
|
||||
closeAuthenticationWindow:function () {
|
||||
closeAuthenticationWindow: function () {
|
||||
if (authenticationWindow) {
|
||||
authenticationWindow.close();
|
||||
authenticationWindow = null;
|
||||
}
|
||||
},
|
||||
xmppAuthenticate: function () {
|
||||
|
||||
var loginDialog = LoginDialog.show(
|
||||
function (connection, state) {
|
||||
if (!state) {
|
||||
// User cancelled
|
||||
loginDialog.close();
|
||||
return;
|
||||
} else if (state == APP.xmpp.Status.CONNECTED) {
|
||||
|
||||
loginDialog.close();
|
||||
|
||||
Authentication.stopInterval();
|
||||
Authentication.closeAuthenticationDialog();
|
||||
|
||||
// Close the connection as anonymous one will be used
|
||||
// to create the conference. Session-id will authorize
|
||||
// the request.
|
||||
connection.disconnect();
|
||||
|
||||
var roomName = APP.UI.generateRoomName();
|
||||
Moderator.allocateConferenceFocus(roomName, function () {
|
||||
// If it's not "on the fly" authentication now join
|
||||
// the conference room
|
||||
if (!APP.xmpp.isMUCJoined()) {
|
||||
APP.UI.checkForNicknameAndJoin();
|
||||
}
|
||||
});
|
||||
}
|
||||
}, true);
|
||||
},
|
||||
focusAuthenticationWindow: function () {
|
||||
// If auth window exists just bring it to the front
|
||||
if (authenticationWindow) {
|
||||
@@ -57,7 +96,7 @@ var Authentication = {
|
||||
closeAuthenticationDialog: function () {
|
||||
// Close authentication dialog if opened
|
||||
if (authDialog) {
|
||||
APP.UI.messageHandler.closeDialog();
|
||||
authDialog.close();
|
||||
authDialog = null;
|
||||
}
|
||||
},
|
||||
@@ -67,10 +106,7 @@ var Authentication = {
|
||||
// On closed
|
||||
function () {
|
||||
// Close authentication dialog if opened
|
||||
if (authDialog) {
|
||||
messageHandler.closeDialog();
|
||||
authDialog = null;
|
||||
}
|
||||
Authentication.closeAuthenticationDialog();
|
||||
callback();
|
||||
authenticationWindow = null;
|
||||
});
|
||||
|
||||
228
modules/UI/authentication/LoginDialog.js
Normal file
228
modules/UI/authentication/LoginDialog.js
Normal file
@@ -0,0 +1,228 @@
|
||||
/* global $, APP, config*/
|
||||
|
||||
var XMPP = require('../../xmpp/xmpp');
|
||||
var Moderator = require('../../xmpp/moderator');
|
||||
|
||||
//FIXME: use LoginDialog to add retries to XMPP.connect method used when
|
||||
// anonymous domain is not enabled
|
||||
|
||||
/**
|
||||
* Creates new <tt>Dialog</tt> instance.
|
||||
* @param callback <tt>function(Strophe.Connection, Strophe.Status)</tt> called
|
||||
* when we either fail to connect or succeed(check Strophe.Status).
|
||||
* @param obtainSession <tt>true</tt> if we want to send ConferenceIQ to Jicofo
|
||||
* in order to create session-id after the connection is established.
|
||||
* @constructor
|
||||
*/
|
||||
function Dialog(callback, obtainSession) {
|
||||
|
||||
var self = this;
|
||||
|
||||
var stop = false;
|
||||
|
||||
var connection = APP.xmpp.createConnection();
|
||||
|
||||
var message = '<h2 data-i18n="dialog.passwordRequired">';
|
||||
message += APP.translation.translateString("dialog.passwordRequired");
|
||||
message += '</h2>' +
|
||||
'<input name="username" type="text" ' +
|
||||
'placeholder="user@domain.net" autofocus>' +
|
||||
'<input name="password" ' +
|
||||
'type="password" data-i18n="[placeholder]dialog.userPassword"' +
|
||||
' placeholder="user password">';
|
||||
|
||||
var okButton = APP.translation.generateTranslationHTML("dialog.Ok");
|
||||
|
||||
var cancelButton = APP.translation.generateTranslationHTML("dialog.Cancel");
|
||||
|
||||
var states = {
|
||||
login: {
|
||||
html: message,
|
||||
buttons: [
|
||||
{ title: okButton, value: true},
|
||||
{ title: cancelButton, value: false}
|
||||
],
|
||||
focus: ':input:first',
|
||||
submit: function (e, v, m, f) {
|
||||
e.preventDefault();
|
||||
if (v) {
|
||||
var jid = f.username;
|
||||
var password = f.password;
|
||||
if (jid && password) {
|
||||
stop = false;
|
||||
connection.reset();
|
||||
connDialog.goToState('connecting');
|
||||
connection.connect(jid, password, stateHandler);
|
||||
}
|
||||
} else {
|
||||
// User cancelled
|
||||
stop = true;
|
||||
callback();
|
||||
}
|
||||
}
|
||||
},
|
||||
connecting: {
|
||||
title: APP.translation.translateString('dialog.connecting'),
|
||||
html: '<div id="connectionStatus"></div>',
|
||||
buttons: [],
|
||||
defaultButton: 0
|
||||
},
|
||||
finished: {
|
||||
title: APP.translation.translateString('dialog.error'),
|
||||
html: '<div id="errorMessage"></div>',
|
||||
buttons: [
|
||||
{
|
||||
title: APP.translation.translateString('dialog.retry'),
|
||||
value: 'retry'
|
||||
},
|
||||
{
|
||||
title: APP.translation.translateString('dialog.Cancel'),
|
||||
value: 'cancel'
|
||||
},
|
||||
],
|
||||
defaultButton: 0,
|
||||
submit: function (e, v, m, f) {
|
||||
e.preventDefault();
|
||||
if (v === 'retry')
|
||||
connDialog.goToState('login');
|
||||
else
|
||||
callback();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var connDialog
|
||||
= APP.UI.messageHandler.openDialogWithStates(states,
|
||||
{ persistent: true, closeText: '' }, null);
|
||||
|
||||
var stateHandler = function (status, message) {
|
||||
if (stop) {
|
||||
return;
|
||||
}
|
||||
|
||||
var translateKey = "connection." + XMPP.getStatusString(status);
|
||||
var statusStr = APP.translation.translateString(translateKey);
|
||||
|
||||
// Display current state
|
||||
var connectionStatus =
|
||||
connDialog.getState('connecting').find('#connectionStatus');
|
||||
|
||||
connectionStatus.text(statusStr);
|
||||
|
||||
switch (status) {
|
||||
case XMPP.Status.CONNECTED:
|
||||
|
||||
stop = true;
|
||||
if (!obtainSession) {
|
||||
callback(connection, status);
|
||||
return;
|
||||
}
|
||||
// Obtaining session-id status
|
||||
connectionStatus.text(
|
||||
APP.translation.translateString(
|
||||
'connection.FETCH_SESSION_ID'));
|
||||
|
||||
// Authenticate with Jicofo and obtain session-id
|
||||
var roomName = APP.UI.generateRoomName();
|
||||
|
||||
// Jicofo will return new session-id when connected
|
||||
// from authenticated domain
|
||||
connection.sendIQ(
|
||||
Moderator.createConferenceIq(roomName),
|
||||
function (result) {
|
||||
|
||||
connectionStatus.text(
|
||||
APP.translation.translateString(
|
||||
'connection.GOT_SESSION_ID'));
|
||||
|
||||
stop = true;
|
||||
|
||||
// Parse session-id
|
||||
Moderator.parseSessionId(result);
|
||||
|
||||
callback(connection, status);
|
||||
},
|
||||
function (error) {
|
||||
console.error("Auth on the fly failed", error);
|
||||
|
||||
stop = true;
|
||||
|
||||
var errorMsg =
|
||||
APP.translation.translateString(
|
||||
'connection.GET_SESSION_ID_ERROR') +
|
||||
$(error).find('>error').attr('code');
|
||||
|
||||
self.displayError(errorMsg);
|
||||
|
||||
connection.disconnect();
|
||||
});
|
||||
|
||||
break;
|
||||
case XMPP.Status.AUTHFAIL:
|
||||
case XMPP.Status.CONNFAIL:
|
||||
case XMPP.Status.DISCONNECTED:
|
||||
|
||||
stop = true;
|
||||
|
||||
callback(connection, status);
|
||||
|
||||
var errorMessage = statusStr;
|
||||
|
||||
if (message)
|
||||
{
|
||||
errorMessage += ': ' + message;
|
||||
}
|
||||
self.displayError(errorMessage);
|
||||
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Displays error message in 'finished' state which allows either to cancel
|
||||
* or retry.
|
||||
* @param message the final message to be displayed.
|
||||
*/
|
||||
this.displayError = function (message) {
|
||||
|
||||
var finishedState = connDialog.getState('finished');
|
||||
|
||||
var errorMessageElem = finishedState.find('#errorMessage');
|
||||
errorMessageElem.text(message);
|
||||
|
||||
connDialog.goToState('finished');
|
||||
};
|
||||
|
||||
/**
|
||||
* Closes LoginDialog.
|
||||
*/
|
||||
this.close = function () {
|
||||
stop = true;
|
||||
connDialog.close();
|
||||
};
|
||||
}
|
||||
|
||||
var LoginDialog = {
|
||||
|
||||
/**
|
||||
* Displays login prompt used to establish new XMPP connection. Given
|
||||
* <tt>callback(Strophe.Connection, Strophe.Status)</tt> function will be
|
||||
* called when we connect successfully(status === CONNECTED) or when we fail
|
||||
* to do so. On connection failure program can call Dialog.close() method in
|
||||
* order to cancel or do nothing to let the user retry.
|
||||
* @param callback <tt>function(Strophe.Connection, Strophe.Status)</tt>
|
||||
* called when we either fail to connect or succeed(check
|
||||
* Strophe.Status).
|
||||
* @param obtainSession <tt>true</tt> if we want to send ConferenceIQ to
|
||||
* Jicofo in order to create session-id after the connection is
|
||||
* established.
|
||||
* @returns {Dialog}
|
||||
*/
|
||||
show: function (callback, obtainSession) {
|
||||
return new Dialog(callback, obtainSession);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = LoginDialog;
|
||||
@@ -1,39 +1,6 @@
|
||||
var Settings = require("../../settings/Settings");
|
||||
var MediaStreamType = require("../../../service/RTC/MediaStreamTypes");
|
||||
|
||||
var users = {};
|
||||
var activeSpeakerJid;
|
||||
|
||||
function setVisibility(selector, show) {
|
||||
if (selector && selector.length > 0) {
|
||||
selector.css("visibility", show ? "visible" : "hidden");
|
||||
}
|
||||
}
|
||||
|
||||
function isUserMuted(jid) {
|
||||
// XXX(gp) we may want to rename this method to something like
|
||||
// isUserStreaming, for example.
|
||||
if (jid && jid != APP.xmpp.myJid()) {
|
||||
var resource = Strophe.getResourceFromJid(jid);
|
||||
if (!require("../videolayout/VideoLayout").isInLastN(resource)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!APP.RTC.remoteStreams[jid] || !APP.RTC.remoteStreams[jid][MediaStreamType.VIDEO_TYPE]) {
|
||||
return null;
|
||||
}
|
||||
return APP.RTC.remoteStreams[jid][MediaStreamType.VIDEO_TYPE].muted;
|
||||
}
|
||||
|
||||
function getGravatarUrl(id, size) {
|
||||
if(id === APP.xmpp.myJid() || !id) {
|
||||
id = Settings.getSettings().uid;
|
||||
}
|
||||
return 'https://www.gravatar.com/avatar/' +
|
||||
MD5.hexdigest(id.trim().toLowerCase()) +
|
||||
"?d=wavatar&size=" + (size || "30");
|
||||
}
|
||||
|
||||
var Avatar = {
|
||||
|
||||
@@ -50,103 +17,48 @@ var Avatar = {
|
||||
}
|
||||
users[jid] = id;
|
||||
}
|
||||
var thumbUrl = getGravatarUrl(users[jid] || jid, 100);
|
||||
var contactListUrl = getGravatarUrl(users[jid] || jid);
|
||||
var thumbUrl = this.getThumbUrl(jid);
|
||||
var contactListUrl = this.getContactListUrl(jid);
|
||||
var resourceJid = Strophe.getResourceFromJid(jid);
|
||||
var thumbnail = $('#participant_' + resourceJid);
|
||||
var avatar = $('#avatar_' + resourceJid);
|
||||
|
||||
// set the avatar in the settings menu if it is local user and get the
|
||||
// local video container
|
||||
if (jid === APP.xmpp.myJid()) {
|
||||
$('#avatar').get(0).src = thumbUrl;
|
||||
thumbnail = $('#localVideoContainer');
|
||||
}
|
||||
|
||||
// set the avatar in the contact list
|
||||
var contact = $('#' + resourceJid + '>img');
|
||||
if (contact && contact.length > 0) {
|
||||
contact.get(0).src = contactListUrl;
|
||||
}
|
||||
|
||||
// set the avatar in the thumbnail
|
||||
if (avatar && avatar.length > 0) {
|
||||
avatar[0].src = thumbUrl;
|
||||
} else {
|
||||
if (thumbnail && thumbnail.length > 0) {
|
||||
avatar = document.createElement('img');
|
||||
avatar.id = 'avatar_' + resourceJid;
|
||||
avatar.className = 'userAvatar';
|
||||
avatar.src = thumbUrl;
|
||||
thumbnail.append(avatar);
|
||||
}
|
||||
}
|
||||
|
||||
//if the user is the current active speaker - update the active speaker
|
||||
// avatar
|
||||
if (jid === activeSpeakerJid) {
|
||||
this.updateActiveSpeakerAvatarSrc(jid);
|
||||
}
|
||||
APP.UI.userAvatarChanged(resourceJid, thumbUrl, contactListUrl);
|
||||
},
|
||||
|
||||
/**
|
||||
* Hides or shows the user's avatar
|
||||
* @param jid jid of the user
|
||||
* @param show whether we should show the avatar or not
|
||||
* video because there is no dominant speaker and no focused speaker
|
||||
* Returns image URL for the avatar to be displayed on large video area
|
||||
* where current active speaker is presented.
|
||||
* @param jid full MUC jid of the user for whom we want to obtain avatar URL
|
||||
*/
|
||||
showUserAvatar: function (jid, show) {
|
||||
if (users[jid]) {
|
||||
var resourceJid = Strophe.getResourceFromJid(jid);
|
||||
var video = $('#participant_' + resourceJid + '>video');
|
||||
var avatar = $('#avatar_' + resourceJid);
|
||||
|
||||
if (jid === APP.xmpp.myJid()) {
|
||||
video = $('#localVideoWrapper>video');
|
||||
}
|
||||
if (show === undefined || show === null) {
|
||||
show = isUserMuted(jid);
|
||||
}
|
||||
|
||||
//if the user is the currently focused, the dominant speaker or if
|
||||
//there is no focused and no dominant speaker and the large video is
|
||||
//currently shown
|
||||
if (activeSpeakerJid === jid && require("../videolayout/VideoLayout").isLargeVideoOnTop()) {
|
||||
setVisibility($("#largeVideo"), !show);
|
||||
setVisibility($('#activeSpeaker'), show);
|
||||
setVisibility(avatar, false);
|
||||
setVisibility(video, false);
|
||||
} else {
|
||||
if (video && video.length > 0) {
|
||||
setVisibility(video, !show);
|
||||
setVisibility(avatar, show);
|
||||
}
|
||||
}
|
||||
}
|
||||
getActiveSpeakerUrl: function (jid) {
|
||||
return this.getGravatarUrl(jid, 100);
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates the src of the active speaker avatar
|
||||
* @param jid of the current active speaker
|
||||
* Returns image URL for the avatar to be displayed on small video thumbnail
|
||||
* @param jid full MUC jid of the user for whom we want to obtain avatar URL
|
||||
*/
|
||||
updateActiveSpeakerAvatarSrc: function (jid) {
|
||||
getThumbUrl: function (jid) {
|
||||
return this.getGravatarUrl(jid, 100);
|
||||
},
|
||||
/**
|
||||
* Returns the URL for the avatar to be displayed as contactlist item
|
||||
* @param jid full MUC jid of the user for whom we want to obtain avatar URL
|
||||
*/
|
||||
getContactListUrl: function (jid) {
|
||||
return this.getGravatarUrl(jid, 30);
|
||||
},
|
||||
getGravatarUrl: function (jid, size) {
|
||||
if (!jid) {
|
||||
jid = APP.xmpp.findJidFromResource(
|
||||
require("../videolayout/VideoLayout").getLargeVideoState().userResourceJid);
|
||||
console.error("Get gravatar - jid is undefined");
|
||||
return null;
|
||||
}
|
||||
var avatar = $("#activeSpeakerAvatar")[0];
|
||||
var url = getGravatarUrl(users[jid],
|
||||
interfaceConfig.ACTIVE_SPEAKER_AVATAR_SIZE);
|
||||
if (jid === activeSpeakerJid && avatar.src === url) {
|
||||
return;
|
||||
}
|
||||
activeSpeakerJid = jid;
|
||||
var isMuted = isUserMuted(jid);
|
||||
if (jid && isMuted !== null) {
|
||||
avatar.src = url;
|
||||
setVisibility($("#largeVideo"), !isMuted);
|
||||
Avatar.showUserAvatar(jid, isMuted);
|
||||
var id = users[jid];
|
||||
if (!id) {
|
||||
console.warn(
|
||||
"No avatar stored yet for " + jid + " - using JID as ID");
|
||||
id = jid;
|
||||
}
|
||||
return 'https://www.gravatar.com/avatar/' +
|
||||
MD5.hexdigest(id.trim().toLowerCase()) +
|
||||
"?d=wavatar&size=" + (size || "30");
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
@@ -26,50 +26,34 @@ function resize() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shares the Etherpad name with other participants.
|
||||
*/
|
||||
function shareEtherpad() {
|
||||
APP.xmpp.addToPresence("etherpad", etherpadName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the Etherpad button and adds it to the toolbar.
|
||||
*/
|
||||
function enableEtherpadButton() {
|
||||
if (!$('#etherpadButton').is(":visible"))
|
||||
$('#etherpadButton').css({display: 'inline-block'});
|
||||
if (!$('#toolbar_button_etherpad').is(":visible"))
|
||||
$('#toolbar_button_etherpad').css({display: 'inline-block'});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the IFrame for the etherpad.
|
||||
*/
|
||||
function createIFrame() {
|
||||
etherpadIFrame = document.createElement('iframe');
|
||||
etherpadIFrame.src = domain + etherpadName + options;
|
||||
etherpadIFrame.frameBorder = 0;
|
||||
etherpadIFrame.scrolling = "no";
|
||||
etherpadIFrame.width = $('#largeVideoContainer').width() || 640;
|
||||
etherpadIFrame.height = $('#largeVideoContainer').height() || 480;
|
||||
etherpadIFrame.setAttribute('style', 'visibility: hidden;');
|
||||
etherpadIFrame = VideoLayout.createEtherpadIframe(
|
||||
domain + etherpadName + options, function() {
|
||||
|
||||
document.getElementById('etherpad').appendChild(etherpadIFrame);
|
||||
|
||||
etherpadIFrame.onload = function() {
|
||||
|
||||
document.domain = document.domain;
|
||||
bubbleIframeMouseMove(etherpadIFrame);
|
||||
setTimeout(function() {
|
||||
// the iframes inside of the etherpad are
|
||||
// not yet loaded when the etherpad iframe is loaded
|
||||
var outer = etherpadIFrame.
|
||||
contentDocument.getElementsByName("ace_outer")[0];
|
||||
bubbleIframeMouseMove(outer);
|
||||
var inner = outer.
|
||||
contentDocument.getElementsByName("ace_inner")[0];
|
||||
bubbleIframeMouseMove(inner);
|
||||
}, 2000);
|
||||
};
|
||||
document.domain = document.domain;
|
||||
bubbleIframeMouseMove(etherpadIFrame);
|
||||
setTimeout(function() {
|
||||
// the iframes inside of the etherpad are
|
||||
// not yet loaded when the etherpad iframe is loaded
|
||||
var outer = etherpadIFrame.
|
||||
contentDocument.getElementsByName("ace_outer")[0];
|
||||
bubbleIframeMouseMove(outer);
|
||||
var inner = outer.
|
||||
contentDocument.getElementsByName("ace_inner")[0];
|
||||
bubbleIframeMouseMove(inner);
|
||||
}, 2000);
|
||||
});
|
||||
}
|
||||
|
||||
function bubbleIframeMouseMove(iframe){
|
||||
@@ -100,33 +84,17 @@ function bubbleIframeMouseMove(iframe){
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* On video selected event.
|
||||
*/
|
||||
$(document).bind('video.selected', function (event, isPresentation) {
|
||||
if (config.etherpad_base && etherpadIFrame && etherpadIFrame.style.visibility !== 'hidden')
|
||||
Etherpad.toggleEtherpad(isPresentation);
|
||||
});
|
||||
|
||||
|
||||
var Etherpad = {
|
||||
/**
|
||||
* Initializes the etherpad.
|
||||
*/
|
||||
init: function (name) {
|
||||
|
||||
if (config.etherpad_base && !etherpadName) {
|
||||
if (config.etherpad_base && !etherpadName && name) {
|
||||
|
||||
domain = config.etherpad_base;
|
||||
|
||||
if (!name) {
|
||||
// In case we're the focus we generate the name.
|
||||
etherpadName = Math.random().toString(36).substring(7) +
|
||||
'_' + (new Date().getTime()).toString();
|
||||
shareEtherpad();
|
||||
}
|
||||
else
|
||||
etherpadName = name;
|
||||
etherpadName = name;
|
||||
|
||||
enableEtherpadButton();
|
||||
|
||||
@@ -146,49 +114,17 @@ var Etherpad = {
|
||||
if (!etherpadIFrame)
|
||||
createIFrame();
|
||||
|
||||
var largeVideo = null;
|
||||
if (Prezi.isPresentationVisible())
|
||||
largeVideo = $('#presentation>iframe');
|
||||
else
|
||||
largeVideo = $('#largeVideo');
|
||||
|
||||
if ($('#etherpad>iframe').css('visibility') === 'hidden') {
|
||||
$('#activeSpeaker').css('visibility', 'hidden');
|
||||
largeVideo.fadeOut(300, function () {
|
||||
if (Prezi.isPresentationVisible()) {
|
||||
largeVideo.css({opacity: '0'});
|
||||
} else {
|
||||
VideoLayout.setLargeVideoVisible(false);
|
||||
}
|
||||
});
|
||||
|
||||
$('#etherpad>iframe').fadeIn(300, function () {
|
||||
document.body.style.background = '#eeeeee';
|
||||
$('#etherpad>iframe').css({visibility: 'visible'});
|
||||
$('#etherpad').css({zIndex: 2});
|
||||
});
|
||||
if(VideoLayout.getLargeVideoState() === "etherpad")
|
||||
{
|
||||
VideoLayout.setLargeVideoState("video");
|
||||
}
|
||||
else if ($('#etherpad>iframe')) {
|
||||
$('#etherpad>iframe').fadeOut(300, function () {
|
||||
$('#etherpad>iframe').css({visibility: 'hidden'});
|
||||
$('#etherpad').css({zIndex: 0});
|
||||
document.body.style.background = 'black';
|
||||
});
|
||||
|
||||
if (!isPresentation) {
|
||||
$('#largeVideo').fadeIn(300, function () {
|
||||
VideoLayout.setLargeVideoVisible(true);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
VideoLayout.setLargeVideoState("etherpad");
|
||||
}
|
||||
resize();
|
||||
},
|
||||
|
||||
isVisible: function() {
|
||||
var etherpadIframe = $('#etherpad>iframe');
|
||||
return etherpadIframe && etherpadIframe.is(':visible');
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
module.exports = Etherpad;
|
||||
|
||||
@@ -6,6 +6,20 @@ var PreziPlayer = require("./PreziPlayer");
|
||||
|
||||
var preziPlayer = null;
|
||||
|
||||
|
||||
/**
|
||||
* Shows/hides a presentation.
|
||||
*/
|
||||
function setPresentationVisible(visible) {
|
||||
|
||||
if (visible) {
|
||||
VideoLayout.setLargeVideoState("prezi");
|
||||
}
|
||||
else {
|
||||
VideoLayout.setLargeVideoState("video");
|
||||
}
|
||||
}
|
||||
|
||||
var Prezi = {
|
||||
|
||||
|
||||
@@ -58,43 +72,48 @@ var Prezi = {
|
||||
);
|
||||
}
|
||||
else {
|
||||
var html = APP.translation.generateTranslatonHTML(
|
||||
var html = APP.translation.generateTranslationHTML(
|
||||
"dialog.sharePreziTitle");
|
||||
var cancelButton = APP.translation.generateTranslatonHTML(
|
||||
var cancelButton = APP.translation.generateTranslationHTML(
|
||||
"dialog.Cancel");
|
||||
var shareButton = APP.translation.generateTranslatonHTML(
|
||||
var shareButton = APP.translation.generateTranslationHTML(
|
||||
"dialog.Share");
|
||||
var backButton = APP.translation.generateTranslatonHTML(
|
||||
var backButton = APP.translation.generateTranslationHTML(
|
||||
"dialog.Back");
|
||||
var buttons = {};
|
||||
var buttons1 = {};
|
||||
buttons1.button1 = buttons.button1 = {title: cancelButton, value: false};
|
||||
buttons.button2 = {title: shareButton, value: true};
|
||||
buttons1.button2 = {title: backButton, value: true};
|
||||
var linkError = APP.translation.generateTranslatonHTML(
|
||||
var buttons = [];
|
||||
var buttons1 = [];
|
||||
// Cancel button to both states
|
||||
buttons.push({title: cancelButton, value: false});
|
||||
buttons1.push({title: cancelButton, value: false});
|
||||
// Share button
|
||||
buttons.push({title: shareButton, value: true});
|
||||
// Back button
|
||||
buttons1.push({title: backButton, value: true});
|
||||
var linkError = APP.translation.generateTranslationHTML(
|
||||
"dialog.preziLinkError");
|
||||
var defaultUrl = APP.translation.translateString("defaultPreziLink",
|
||||
{url: "http://prezi.com/wz7vhjycl7e6/my-prezi"});
|
||||
var openPreziState = {
|
||||
state0: {
|
||||
html: '<h2>' + html + '</h2>' +
|
||||
'<input id="preziUrl" type="text" ' +
|
||||
'<input name="preziUrl" type="text" ' +
|
||||
'data-i18n="[placeholder]defaultPreziLink" data-i18n-options=\'' +
|
||||
JSON.stringify({"url": "http://prezi.com/wz7vhjycl7e6/my-prezi"}) +
|
||||
'\' placeholder="' + defaultUrl + '" autofocus>',
|
||||
persistent: false,
|
||||
buttons: buttons,
|
||||
defaultButton: 1,
|
||||
submit: function(e,v,m,f){
|
||||
focus: ':input:first',
|
||||
defaultButton: 0,
|
||||
submit: function (e, v, m, f) {
|
||||
e.preventDefault();
|
||||
if(v)
|
||||
{
|
||||
var preziUrl = document.getElementById('preziUrl');
|
||||
var preziUrl = f.preziUrl;
|
||||
|
||||
if (preziUrl.value)
|
||||
if (preziUrl)
|
||||
{
|
||||
var urlValue
|
||||
= encodeURI(UIUtil.escapeHtml(preziUrl.value));
|
||||
= encodeURI(UIUtil.escapeHtml(preziUrl));
|
||||
|
||||
if (urlValue.indexOf('http://prezi.com/') != 0
|
||||
&& urlValue.indexOf('https://prezi.com/') != 0)
|
||||
@@ -126,20 +145,18 @@ var Prezi = {
|
||||
linkError,
|
||||
persistent: false,
|
||||
buttons: buttons1,
|
||||
focus: ':input:first',
|
||||
defaultButton: 1,
|
||||
submit:function(e,v,m,f) {
|
||||
submit: function (e, v, m, f) {
|
||||
e.preventDefault();
|
||||
if(v==0)
|
||||
if (v === 0)
|
||||
$.prompt.close();
|
||||
else
|
||||
$.prompt.goToState('state0');
|
||||
}
|
||||
}
|
||||
};
|
||||
var focusPreziUrl = function(e) {
|
||||
document.getElementById('preziUrl').focus();
|
||||
};
|
||||
messageHandler.openDialogWithStates(openPreziState, focusPreziUrl, focusPreziUrl);
|
||||
messageHandler.openDialogWithStates(openPreziState);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,18 +179,14 @@ function presentationAdded(event, jid, presUrl, currentSlide) {
|
||||
+ Strophe.getResourceFromJid(jid)
|
||||
+ '_' + presId;
|
||||
|
||||
// We explicitly don't specify the peer jid here, because we don't want
|
||||
// this video to be dealt with as a peer related one (for example we
|
||||
// don't want to show a mute/kick menu for this one, etc.).
|
||||
VideoLayout.addRemoteVideoContainer(null, elementId);
|
||||
VideoLayout.resizeThumbnails();
|
||||
VideoLayout.addPreziContainer(elementId);
|
||||
|
||||
var controlsEnabled = false;
|
||||
if (jid === APP.xmpp.myJid())
|
||||
controlsEnabled = true;
|
||||
|
||||
setPresentationVisible(true);
|
||||
$('#largeVideoContainer').hover(
|
||||
VideoLayout.setLargeVideoHover(
|
||||
function (event) {
|
||||
if (Prezi.isPresentationVisible()) {
|
||||
var reloadButtonRight = window.innerWidth
|
||||
@@ -299,38 +312,6 @@ function resize() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows/hides a presentation.
|
||||
*/
|
||||
function setPresentationVisible(visible) {
|
||||
var prezi = $('#presentation>iframe');
|
||||
if (visible) {
|
||||
// Trigger the video.selected event to indicate a change in the
|
||||
// large video.
|
||||
$(document).trigger("video.selected", [true]);
|
||||
|
||||
$('#largeVideo').fadeOut(300);
|
||||
prezi.fadeIn(300, function() {
|
||||
prezi.css({opacity:'1'});
|
||||
ToolbarToggler.dockToolbar(true);
|
||||
VideoLayout.setLargeVideoVisible(false);
|
||||
});
|
||||
$('#activeSpeaker').css('visibility', 'hidden');
|
||||
}
|
||||
else {
|
||||
if (prezi.css('opacity') == '1') {
|
||||
prezi.fadeOut(300, function () {
|
||||
prezi.css({opacity:'0'});
|
||||
$('#reloadPresentation').css({display:'none'});
|
||||
$('#largeVideo').fadeIn(300, function() {
|
||||
VideoLayout.setLargeVideoVisible(true);
|
||||
ToolbarToggler.dockToolbar(false);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Presentation has been removed.
|
||||
*/
|
||||
@@ -355,15 +336,6 @@ $(document).bind('gotoslide.muc', function (event, jid, presUrl, current) {
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* On video selected event.
|
||||
*/
|
||||
$(document).bind('video.selected', function (event, isPresentation) {
|
||||
if (!isPresentation && $('#presentation>iframe')) {
|
||||
setPresentationVisible(false);
|
||||
}
|
||||
});
|
||||
|
||||
$(window).resize(function () {
|
||||
resize();
|
||||
});
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* global require, $ */
|
||||
var Chat = require("./chat/Chat");
|
||||
var ContactList = require("./contactlist/ContactList");
|
||||
var Settings = require("./../../settings/Settings");
|
||||
@@ -5,6 +6,7 @@ var SettingsMenu = require("./settings/SettingsMenu");
|
||||
var VideoLayout = require("../videolayout/VideoLayout");
|
||||
var ToolbarToggler = require("../toolbars/ToolbarToggler");
|
||||
var UIUtil = require("../util/UIUtil");
|
||||
var LargeVideo = require("../videolayout/LargeVideo");
|
||||
|
||||
/**
|
||||
* Toggler for the chat, contact list, settings menu, etc..
|
||||
@@ -15,91 +17,7 @@ var PanelToggler = (function(my) {
|
||||
var buttons = {
|
||||
'#chatspace': '#chatBottomButton',
|
||||
'#contactlist': '#contactListButton',
|
||||
'#settingsmenu': '#settingsButton'
|
||||
};
|
||||
|
||||
/**
|
||||
* Resizes the video area
|
||||
* @param isClosing whether the side panel is going to be closed or is going to open / remain opened
|
||||
* @param completeFunction a function to be called when the video space is resized
|
||||
*/
|
||||
var resizeVideoArea = function(isClosing, completeFunction) {
|
||||
var videospace = $('#videospace');
|
||||
|
||||
var panelSize = isClosing ? [0, 0] : PanelToggler.getPanelSize();
|
||||
var videospaceWidth = window.innerWidth - panelSize[0];
|
||||
var videospaceHeight = window.innerHeight;
|
||||
var videoSize
|
||||
= VideoLayout.getVideoSize(null, null, videospaceWidth, videospaceHeight);
|
||||
var videoWidth = videoSize[0];
|
||||
var videoHeight = videoSize[1];
|
||||
var videoPosition = VideoLayout.getVideoPosition(videoWidth,
|
||||
videoHeight,
|
||||
videospaceWidth,
|
||||
videospaceHeight);
|
||||
var horizontalIndent = videoPosition[0];
|
||||
var verticalIndent = videoPosition[1];
|
||||
|
||||
var thumbnailSize = VideoLayout.calculateThumbnailSize(videospaceWidth);
|
||||
var thumbnailsWidth = thumbnailSize[0];
|
||||
var thumbnailsHeight = thumbnailSize[1];
|
||||
//for chat
|
||||
|
||||
videospace.animate({
|
||||
right: panelSize[0],
|
||||
width: videospaceWidth,
|
||||
height: videospaceHeight
|
||||
},
|
||||
{
|
||||
queue: false,
|
||||
duration: 500,
|
||||
complete: completeFunction
|
||||
});
|
||||
|
||||
$('#remoteVideos').animate({
|
||||
height: thumbnailsHeight
|
||||
},
|
||||
{
|
||||
queue: false,
|
||||
duration: 500
|
||||
});
|
||||
|
||||
$('#remoteVideos>span').animate({
|
||||
height: thumbnailsHeight,
|
||||
width: thumbnailsWidth
|
||||
},
|
||||
{
|
||||
queue: false,
|
||||
duration: 500,
|
||||
complete: function () {
|
||||
$(document).trigger(
|
||||
"remotevideo.resized",
|
||||
[thumbnailsWidth,
|
||||
thumbnailsHeight]);
|
||||
}
|
||||
});
|
||||
|
||||
$('#largeVideoContainer').animate({
|
||||
width: videospaceWidth,
|
||||
height: videospaceHeight
|
||||
},
|
||||
{
|
||||
queue: false,
|
||||
duration: 500
|
||||
});
|
||||
|
||||
$('#largeVideo').animate({
|
||||
width: videoWidth,
|
||||
height: videoHeight,
|
||||
top: verticalIndent,
|
||||
bottom: verticalIndent,
|
||||
left: horizontalIndent,
|
||||
right: horizontalIndent
|
||||
},
|
||||
{
|
||||
queue: false,
|
||||
duration: 500
|
||||
});
|
||||
'#settingsmenu': '#toolbar_button_settings'
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -135,7 +53,7 @@ var PanelToggler = (function(my) {
|
||||
else {
|
||||
// Undock the toolbar when the chat is shown and if we're in a
|
||||
// video mode.
|
||||
if (VideoLayout.isLargeVideoVisible()) {
|
||||
if (LargeVideo.isLargeVideoVisible()) {
|
||||
ToolbarToggler.dockToolbar(false);
|
||||
}
|
||||
|
||||
@@ -180,7 +98,7 @@ var PanelToggler = (function(my) {
|
||||
$('#chatspace').trigger('shown');
|
||||
};
|
||||
|
||||
resizeVideoArea(Chat.isVisible(), chatCompleteFunction);
|
||||
VideoLayout.resizeVideoArea(!Chat.isVisible(), chatCompleteFunction);
|
||||
|
||||
toggle(Chat,
|
||||
'#chatspace',
|
||||
@@ -203,7 +121,7 @@ var PanelToggler = (function(my) {
|
||||
my.toggleContactList = function () {
|
||||
var completeFunction = ContactList.isVisible() ?
|
||||
function() {} : function () { $('#contactlist').trigger('shown');};
|
||||
resizeVideoArea(ContactList.isVisible(), completeFunction);
|
||||
VideoLayout.resizeVideoArea(!ContactList.isVisible(), completeFunction);
|
||||
|
||||
toggle(ContactList,
|
||||
'#contactlist',
|
||||
@@ -218,7 +136,7 @@ var PanelToggler = (function(my) {
|
||||
* Opens / closes the settings menu
|
||||
*/
|
||||
my.toggleSettingsMenu = function() {
|
||||
resizeVideoArea(SettingsMenu.isVisible(), function (){});
|
||||
VideoLayout.resizeVideoArea(!SettingsMenu.isVisible(), function (){});
|
||||
toggle(SettingsMenu,
|
||||
'#settingsmenu',
|
||||
null,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* global $, Util, nickname:true */
|
||||
/* global APP, $, Util, nickname:true */
|
||||
var Replacement = require("./Replacement");
|
||||
var CommandsProcessor = require("./Commands");
|
||||
var ToolbarToggler = require("../../toolbars/ToolbarToggler");
|
||||
@@ -19,7 +19,7 @@ function setVisualNotification(show) {
|
||||
var unreadMsgBottomElement
|
||||
= document.getElementById('bottomUnreadMessages');
|
||||
|
||||
var glower = $('#chatButton');
|
||||
var glower = $('#toolbar_button_chat');
|
||||
var bottomGlower = $('#chatBottomButton');
|
||||
|
||||
if (unreadMessages) {
|
||||
@@ -29,7 +29,7 @@ function setVisualNotification(show) {
|
||||
ToolbarToggler.dockToolbar(true);
|
||||
|
||||
var chatButtonElement
|
||||
= document.getElementById('chatButton').parentNode;
|
||||
= document.getElementById('toolbar_button_chat');
|
||||
var leftIndent = (UIUtil.getTextWidth(chatButtonElement) -
|
||||
UIUtil.getTextWidth(unreadMsgElement)) / 2;
|
||||
var topIndent = (UIUtil.getTextHeight(chatButtonElement) -
|
||||
@@ -85,8 +85,8 @@ function setVisualNotification(show) {
|
||||
* Returns the current time in the format it is shown to the user
|
||||
* @returns {string}
|
||||
*/
|
||||
function getCurrentTime() {
|
||||
var now = new Date();
|
||||
function getCurrentTime(stamp) {
|
||||
var now = (stamp? new Date(stamp): new Date());
|
||||
var hour = now.getHours();
|
||||
var minute = now.getMinutes();
|
||||
var second = now.getSeconds();
|
||||
@@ -102,8 +102,7 @@ function getCurrentTime() {
|
||||
return hour+':'+minute+':'+second;
|
||||
}
|
||||
|
||||
function toggleSmileys()
|
||||
{
|
||||
function toggleSmileys() {
|
||||
var smileys = $('#smileysContainer');
|
||||
if(!smileys.is(':visible')) {
|
||||
smileys.show("slide", { direction: "down", duration: 300});
|
||||
@@ -191,19 +190,18 @@ var Chat = (function (my) {
|
||||
}
|
||||
});
|
||||
|
||||
$('#usermsg').keydown(function (event) {
|
||||
var usermsg = $('#usermsg');
|
||||
usermsg.keydown(function (event) {
|
||||
if (event.keyCode === 13) {
|
||||
event.preventDefault();
|
||||
var value = this.value;
|
||||
$('#usermsg').val('').trigger('autosize.resize');
|
||||
usermsg.val('').trigger('autosize.resize');
|
||||
this.focus();
|
||||
var command = new CommandsProcessor(value);
|
||||
if(command.isCommand())
|
||||
{
|
||||
if(command.isCommand()) {
|
||||
command.processCommand();
|
||||
}
|
||||
else
|
||||
{
|
||||
else {
|
||||
var message = UIUtil.escapeHtml(value);
|
||||
APP.xmpp.sendChatMessage(message, NicknameHandler.getNickname());
|
||||
}
|
||||
@@ -214,7 +212,7 @@ var Chat = (function (my) {
|
||||
resizeChatConversation();
|
||||
Chat.scrollChatToBottom();
|
||||
};
|
||||
$('#usermsg').autosize({callback: onTextAreaResize});
|
||||
usermsg.autosize({callback: onTextAreaResize});
|
||||
|
||||
$("#chatspace").bind("shown",
|
||||
function () {
|
||||
@@ -228,7 +226,8 @@ var Chat = (function (my) {
|
||||
/**
|
||||
* Appends the given message to the chat conversation.
|
||||
*/
|
||||
my.updateChatConversation = function (from, displayName, message) {
|
||||
my.updateChatConversation =
|
||||
function (from, displayName, message, myjid, stamp) {
|
||||
var divClassName = '';
|
||||
|
||||
if (APP.xmpp.myJid() === from) {
|
||||
@@ -256,7 +255,7 @@ var Chat = (function (my) {
|
||||
'<div class="chatmessage">'+
|
||||
'<img src="../images/chatArrow.svg" class="chatArrow">' +
|
||||
'<div class="username ' + divClassName +'">' + escDisplayName +
|
||||
'</div>' + '<div class="timestamp">' + getCurrentTime() +
|
||||
'</div>' + '<div class="timestamp">' + getCurrentTime(stamp) +
|
||||
'</div>' + '<div class="usermessage">' + message + '</div>' +
|
||||
'</div>';
|
||||
|
||||
@@ -270,8 +269,7 @@ var Chat = (function (my) {
|
||||
* @param errorMessage the received error message.
|
||||
* @param originalText the original message.
|
||||
*/
|
||||
my.chatAddError = function(errorMessage, originalText)
|
||||
{
|
||||
my.chatAddError = function(errorMessage, originalText) {
|
||||
errorMessage = UIUtil.escapeHtml(errorMessage);
|
||||
originalText = UIUtil.escapeHtml(originalText);
|
||||
|
||||
@@ -288,23 +286,18 @@ var Chat = (function (my) {
|
||||
* Sets the subject to the UI
|
||||
* @param subject the subject
|
||||
*/
|
||||
my.chatSetSubject = function(subject)
|
||||
{
|
||||
if(subject)
|
||||
my.chatSetSubject = function(subject) {
|
||||
if (subject)
|
||||
subject = subject.trim();
|
||||
$('#subject').html(Replacement.linkify(UIUtil.escapeHtml(subject)));
|
||||
if(subject === "")
|
||||
{
|
||||
if(subject === "") {
|
||||
$("#subject").css({display: "none"});
|
||||
}
|
||||
else
|
||||
{
|
||||
else {
|
||||
$("#subject").css({display: "block"});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Sets the chat conversation mode.
|
||||
*/
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* global APP, require */
|
||||
var UIUtil = require("../../util/UIUtil");
|
||||
|
||||
/**
|
||||
@@ -14,47 +15,39 @@ var commands = {
|
||||
* @param message the received message
|
||||
* @returns {string} the command
|
||||
*/
|
||||
function getCommand(message)
|
||||
{
|
||||
if(message)
|
||||
{
|
||||
for(var command in commands)
|
||||
{
|
||||
function getCommand(message) {
|
||||
if(message) {
|
||||
for(var command in commands) {
|
||||
if(message.indexOf("/" + command) == 0)
|
||||
return command;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the data for topic command.
|
||||
* @param commandArguments the arguments of the topic command.
|
||||
*/
|
||||
function processTopic(commandArguments)
|
||||
{
|
||||
function processTopic(commandArguments) {
|
||||
var topic = UIUtil.escapeHtml(commandArguments);
|
||||
APP.xmpp.setSubject(topic);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs new CommandProccessor instance from a message that
|
||||
* Constructs a new CommandProccessor instance from a message that
|
||||
* handles commands received via chat messages.
|
||||
* @param message the message
|
||||
* @constructor
|
||||
*/
|
||||
function CommandsProcessor(message)
|
||||
{
|
||||
|
||||
|
||||
function CommandsProcessor(message) {
|
||||
var command = getCommand(message);
|
||||
|
||||
/**
|
||||
* Returns the name of the command.
|
||||
* @returns {String} the command
|
||||
*/
|
||||
this.getCommand = function()
|
||||
{
|
||||
this.getCommand = function() {
|
||||
return command;
|
||||
};
|
||||
|
||||
@@ -65,8 +58,7 @@ function CommandsProcessor(message)
|
||||
* Returns the arguments of the command.
|
||||
* @returns {string}
|
||||
*/
|
||||
this.getArgument = function()
|
||||
{
|
||||
this.getArgument = function() {
|
||||
return messageArgument;
|
||||
};
|
||||
}
|
||||
@@ -75,9 +67,8 @@ function CommandsProcessor(message)
|
||||
* Checks whether this instance is valid command or not.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
CommandsProcessor.prototype.isCommand = function()
|
||||
{
|
||||
if(this.getCommand())
|
||||
CommandsProcessor.prototype.isCommand = function() {
|
||||
if (this.getCommand())
|
||||
return true;
|
||||
return false;
|
||||
};
|
||||
@@ -85,13 +76,11 @@ CommandsProcessor.prototype.isCommand = function()
|
||||
/**
|
||||
* Processes the command.
|
||||
*/
|
||||
CommandsProcessor.prototype.processCommand = function()
|
||||
{
|
||||
CommandsProcessor.prototype.processCommand = function() {
|
||||
if(!this.isCommand())
|
||||
return;
|
||||
|
||||
commands[this.getCommand()](this.getArgument());
|
||||
|
||||
};
|
||||
|
||||
module.exports = CommandsProcessor;
|
||||
@@ -1,3 +1,5 @@
|
||||
/* global $, APP, Strophe */
|
||||
var Avatar = require('../../avatar/Avatar');
|
||||
|
||||
var numberOfContacts = 0;
|
||||
var notificationInterval;
|
||||
@@ -9,26 +11,28 @@ var notificationInterval;
|
||||
* left(-1)
|
||||
*/
|
||||
function updateNumberOfParticipants(delta) {
|
||||
//when the user is alone we don't show the number of participants
|
||||
if(numberOfContacts === 0) {
|
||||
numberOfContacts += delta;
|
||||
if (numberOfContacts === 1) {
|
||||
// when the user is alone we don't show the number of participants
|
||||
$("#numberOfParticipants").text('');
|
||||
numberOfContacts += delta;
|
||||
} else if(numberOfContacts !== 0 && !ContactList.isVisible()) {
|
||||
ContactList.setVisualNotification(true);
|
||||
numberOfContacts += delta;
|
||||
ContactList.setVisualNotification(false);
|
||||
} else if (numberOfContacts > 1) {
|
||||
ContactList.setVisualNotification(!ContactList.isVisible());
|
||||
$("#numberOfParticipants").text(numberOfContacts);
|
||||
} else {
|
||||
console.error("Invalid number of participants: " + numberOfContacts);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the avatar element.
|
||||
*
|
||||
* @return the newly created avatar element
|
||||
* @return {object} the newly created avatar element
|
||||
*/
|
||||
function createAvatar(id) {
|
||||
function createAvatar(jid) {
|
||||
var avatar = document.createElement('img');
|
||||
avatar.className = "icon-avatar avatar";
|
||||
avatar.src = "https://www.gravatar.com/avatar/" + id + "?d=wavatar&size=30";
|
||||
avatar.src = Avatar.getContactListUrl(jid);
|
||||
|
||||
return avatar;
|
||||
}
|
||||
@@ -42,8 +46,7 @@ function createDisplayNameParagraph(key, displayName) {
|
||||
var p = document.createElement('p');
|
||||
if(displayName)
|
||||
p.innerText = displayName;
|
||||
else if(key)
|
||||
{
|
||||
else if(key) {
|
||||
p.setAttribute("data-i18n",key);
|
||||
p.innerText = APP.translation.translateString(key);
|
||||
}
|
||||
@@ -61,7 +64,6 @@ function stopGlowing(glower) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Contact list.
|
||||
*/
|
||||
@@ -80,27 +82,25 @@ var ContactList = {
|
||||
* Adds a contact for the given peerJid if such doesn't yet exist.
|
||||
*
|
||||
* @param peerJid the peerJid corresponding to the contact
|
||||
* @param id the user's email or userId used to get the user's avatar
|
||||
*/
|
||||
ensureAddContact: function (peerJid, id) {
|
||||
ensureAddContact: function (peerJid) {
|
||||
var resourceJid = Strophe.getResourceFromJid(peerJid);
|
||||
|
||||
var contact = $('#contactlist>ul>li[id="' + resourceJid + '"]');
|
||||
var contact = $('#contacts>li[id="' + resourceJid + '"]');
|
||||
|
||||
if (!contact || contact.length <= 0)
|
||||
ContactList.addContact(peerJid, id);
|
||||
ContactList.addContact(peerJid);
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds a contact for the given peer jid.
|
||||
*
|
||||
* @param peerJid the jid of the contact to add
|
||||
* @param id the email or userId of the user
|
||||
*/
|
||||
addContact: function (peerJid, id) {
|
||||
addContact: function (peerJid) {
|
||||
var resourceJid = Strophe.getResourceFromJid(peerJid);
|
||||
|
||||
var contactlist = $('#contactlist>ul');
|
||||
var contactlist = $('#contacts');
|
||||
|
||||
var newContact = document.createElement('li');
|
||||
newContact.id = resourceJid;
|
||||
@@ -111,18 +111,14 @@ var ContactList = {
|
||||
}
|
||||
};
|
||||
|
||||
newContact.appendChild(createAvatar(id));
|
||||
newContact.appendChild(createAvatar(peerJid));
|
||||
newContact.appendChild(createDisplayNameParagraph("participant"));
|
||||
|
||||
var clElement = contactlist.get(0);
|
||||
|
||||
if (resourceJid === APP.xmpp.myResource()
|
||||
&& $('#contactlist>ul .title')[0].nextSibling.nextSibling) {
|
||||
clElement.insertBefore(newContact,
|
||||
$('#contactlist>ul .title')[0].nextSibling.nextSibling);
|
||||
if (resourceJid === APP.xmpp.myResource()) {
|
||||
contactlist.prepend(newContact);
|
||||
}
|
||||
else {
|
||||
clElement.appendChild(newContact);
|
||||
contactlist.append(newContact);
|
||||
}
|
||||
updateNumberOfParticipants(1);
|
||||
},
|
||||
@@ -135,7 +131,7 @@ var ContactList = {
|
||||
removeContact: function (peerJid) {
|
||||
var resourceJid = Strophe.getResourceFromJid(peerJid);
|
||||
|
||||
var contact = $('#contactlist>ul>li[id="' + resourceJid + '"]');
|
||||
var contact = $('#contacts>li[id="' + resourceJid + '"]');
|
||||
|
||||
if (contact && contact.length > 0) {
|
||||
var contactlist = $('#contactlist>ul');
|
||||
@@ -165,7 +161,7 @@ var ContactList = {
|
||||
},
|
||||
|
||||
setClickable: function (resourceJid, isClickable) {
|
||||
var contact = $('#contactlist>ul>li[id="' + resourceJid + '"]');
|
||||
var contact = $('#contacts>li[id="' + resourceJid + '"]');
|
||||
if (isClickable) {
|
||||
contact.addClass('clickable');
|
||||
} else {
|
||||
@@ -179,10 +175,19 @@ var ContactList = {
|
||||
|
||||
var resourceJid = Strophe.getResourceFromJid(peerJid);
|
||||
|
||||
var contactName = $('#contactlist #' + resourceJid + '>p');
|
||||
var contactName = $('#contacts #' + resourceJid + '>p');
|
||||
|
||||
if (contactName && displayName && displayName.length > 0)
|
||||
contactName.html(displayName);
|
||||
},
|
||||
|
||||
userAvatarChanged: function (resourceJid, contactListUrl) {
|
||||
// set the avatar in the contact list
|
||||
var contact = $('#' + resourceJid + '>img');
|
||||
if (contact && contact.length > 0) {
|
||||
contact.get(0).src = contactListUrl;
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
/* global APP, $ */
|
||||
var Avatar = require("../../avatar/Avatar");
|
||||
var Settings = require("./../../../settings/Settings");
|
||||
var UIUtil = require("../../util/UIUtil");
|
||||
var languages = require("../../../../service/translation/languages");
|
||||
|
||||
function generateLanguagesSelectBox()
|
||||
{
|
||||
function generateLanguagesSelectBox() {
|
||||
var currentLang = APP.translation.getCurrentLanguage();
|
||||
var html = "<select id=\"languages_selectbox\">";
|
||||
var langArray = languages.getLanguages();
|
||||
for(var i = 0; i < langArray.length; i++)
|
||||
{
|
||||
for(var i = 0; i < langArray.length; i++) {
|
||||
var lang = langArray[i];
|
||||
html += "<option ";
|
||||
if(lang === currentLang)
|
||||
@@ -26,7 +25,8 @@ function generateLanguagesSelectBox()
|
||||
var SettingsMenu = {
|
||||
|
||||
init: function () {
|
||||
$("#updateSettings").before(generateLanguagesSelectBox());
|
||||
var startMutedSelector = $("#startMutedOptions");
|
||||
startMutedSelector.before(generateLanguagesSelectBox());
|
||||
APP.translation.translateElement($("#languages_selectbox"));
|
||||
$('#settingsmenu>input').keyup(function(event){
|
||||
if(event.keyCode === 13) {//enter
|
||||
@@ -34,11 +34,32 @@ var SettingsMenu = {
|
||||
}
|
||||
});
|
||||
|
||||
if (APP.xmpp.isModerator()) {
|
||||
startMutedSelector.css("display", "block");
|
||||
}
|
||||
else {
|
||||
startMutedSelector.css("display", "none");
|
||||
}
|
||||
|
||||
$("#updateSettings").click(function () {
|
||||
SettingsMenu.update();
|
||||
});
|
||||
},
|
||||
|
||||
onRoleChanged: function () {
|
||||
if(APP.xmpp.isModerator()) {
|
||||
$("#startMutedOptions").css("display", "block");
|
||||
}
|
||||
else {
|
||||
$("#startMutedOptions").css("display", "none");
|
||||
}
|
||||
},
|
||||
|
||||
setStartMuted: function (audio, video) {
|
||||
$("#startAudioMuted").attr("checked", audio);
|
||||
$("#startVideoMuted").attr("checked", video);
|
||||
},
|
||||
|
||||
update: function() {
|
||||
var newDisplayName = UIUtil.escapeHtml($('#setDisplayName').get(0).value);
|
||||
var newEmail = UIUtil.escapeHtml($('#setEmail').get(0).value);
|
||||
@@ -55,6 +76,10 @@ var SettingsMenu = {
|
||||
APP.xmpp.addToPresence("email", newEmail);
|
||||
var email = Settings.setEmail(newEmail);
|
||||
|
||||
var startAudioMuted = ($("#startAudioMuted").is(":checked"));
|
||||
var startVideoMuted = ($("#startVideoMuted").is(":checked"));
|
||||
APP.xmpp.addToPresence("startMuted",
|
||||
[startAudioMuted, startVideoMuted]);
|
||||
|
||||
Avatar.setUserAvatar(APP.xmpp.myJid(), email);
|
||||
},
|
||||
@@ -73,6 +98,9 @@ var SettingsMenu = {
|
||||
peerJid === APP.xmpp.myJid()) {
|
||||
this.setDisplayName(newDisplayName);
|
||||
}
|
||||
},
|
||||
changeAvatar: function (thumbUrl) {
|
||||
$('#avatar').get(0).src = thumbUrl;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* global $ */
|
||||
var PanelToggler = require("../side_pannels/SidePanelToggler");
|
||||
|
||||
var buttonHandlers = {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/* global APP,$, buttonClick, config, lockRoom,
|
||||
setSharedKey, Util */
|
||||
/* global APP, $, buttonClick, config, lockRoom, interfaceConfig, setSharedKey,
|
||||
Util */
|
||||
var messageHandler = require("../util/MessageHandler");
|
||||
var BottomToolbar = require("./BottomToolbar");
|
||||
var Prezi = require("../prezi/Prezi");
|
||||
@@ -13,9 +13,9 @@ var AuthenticationEvents
|
||||
var roomUrl = null;
|
||||
var sharedKey = '';
|
||||
var UI = null;
|
||||
var recordingToaster = null;
|
||||
|
||||
var buttonHandlers =
|
||||
{
|
||||
var buttonHandlers = {
|
||||
"toolbar_button_mute": function () {
|
||||
return APP.UI.toggleAudio();
|
||||
},
|
||||
@@ -46,14 +46,16 @@ var buttonHandlers =
|
||||
"toolbar_button_desktopsharing": function () {
|
||||
return APP.desktopsharing.toggleScreenSharing();
|
||||
},
|
||||
"toolbar_button_fullScreen": function()
|
||||
{
|
||||
UIUtil.buttonClick("#fullScreen", "icon-full-screen icon-exit-full-screen");
|
||||
"toolbar_button_fullScreen": function() {
|
||||
UIUtil.buttonClick("#toolbar_button_fullScreen", "icon-full-screen icon-exit-full-screen");
|
||||
return Toolbar.toggleFullScreen();
|
||||
},
|
||||
"toolbar_button_sip": function () {
|
||||
return callSipButtonClicked();
|
||||
},
|
||||
"toolbar_button_dialpad": function () {
|
||||
return dialpadButtonClicked();
|
||||
},
|
||||
"toolbar_button_settings": function () {
|
||||
PanelToggler.toggleSettingsMenu();
|
||||
},
|
||||
@@ -88,32 +90,29 @@ var buttonHandlers =
|
||||
|
||||
function hangup() {
|
||||
APP.xmpp.disposeConference();
|
||||
if(config.enableWelcomePage)
|
||||
{
|
||||
setTimeout(function()
|
||||
{
|
||||
if(config.enableWelcomePage) {
|
||||
setTimeout(function() {
|
||||
window.localStorage.welcomePageDisabled = false;
|
||||
window.location.pathname = "/";
|
||||
}, 10000);
|
||||
|
||||
}
|
||||
|
||||
var title = APP.translation.generateTranslatonHTML(
|
||||
var title = APP.translation.generateTranslationHTML(
|
||||
"dialog.sessTerminated");
|
||||
var msg = APP.translation.generateTranslatonHTML(
|
||||
var msg = APP.translation.generateTranslationHTML(
|
||||
"dialog.hungUp");
|
||||
var button = APP.translation.generateTranslatonHTML(
|
||||
var button = APP.translation.generateTranslationHTML(
|
||||
"dialog.joinAgain");
|
||||
var buttons = {};
|
||||
buttons.joinAgain = {title: button, value: true};
|
||||
var buttons = [];
|
||||
buttons.push({title: button, value: true});
|
||||
|
||||
UI.messageHandler.openDialog(
|
||||
title,
|
||||
msg,
|
||||
true,
|
||||
buttons,
|
||||
function(event, value, message, formVals)
|
||||
{
|
||||
function(event, value, message, formVals) {
|
||||
window.location.reload();
|
||||
return false;
|
||||
}
|
||||
@@ -124,34 +123,37 @@ function hangup() {
|
||||
* Starts or stops the recording for the conference.
|
||||
*/
|
||||
|
||||
function toggleRecording() {
|
||||
function toggleRecording(predefinedToken) {
|
||||
APP.xmpp.toggleRecording(function (callback) {
|
||||
var msg = APP.translation.generateTranslatonHTML(
|
||||
if (predefinedToken) {
|
||||
callback(UIUtil.escapeHtml(predefinedToken));
|
||||
return;
|
||||
}
|
||||
|
||||
var msg = APP.translation.generateTranslationHTML(
|
||||
"dialog.recordingToken");
|
||||
var token = APP.translation.translateString("dialog.token");
|
||||
APP.UI.messageHandler.openTwoButtonDialog(null, null, null,
|
||||
'<h2>' + msg + '</h2>' +
|
||||
'<input id="recordingToken" type="text" ' +
|
||||
'<input name="recordingToken" type="text" ' +
|
||||
' data-i18n="[placeholder]dialog.token" ' +
|
||||
'placeholder="' + token + '" autofocus>',
|
||||
false,
|
||||
"dialog.Save",
|
||||
function (e, v, m, f) {
|
||||
if (v) {
|
||||
var token = document.getElementById('recordingToken');
|
||||
var token = f.recordingToken;
|
||||
|
||||
if (token.value) {
|
||||
callback(UIUtil.escapeHtml(token.value));
|
||||
if (token) {
|
||||
callback(UIUtil.escapeHtml(token));
|
||||
}
|
||||
}
|
||||
},
|
||||
function (event) {
|
||||
document.getElementById('recordingToken').focus();
|
||||
},
|
||||
function () {
|
||||
}
|
||||
null,
|
||||
function () { },
|
||||
':input:first'
|
||||
);
|
||||
}, Toolbar.setRecordingButtonState, Toolbar.setRecordingButtonState);
|
||||
}, Toolbar.setRecordingButtonState);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -164,13 +166,11 @@ function lockRoom(lock) {
|
||||
|
||||
APP.xmpp.lockRoom(currentSharedKey, function (res) {
|
||||
// password is required
|
||||
if (sharedKey)
|
||||
{
|
||||
if (sharedKey) {
|
||||
console.log('set room password');
|
||||
Toolbar.lockLockButton();
|
||||
}
|
||||
else
|
||||
{
|
||||
else {
|
||||
console.log('removed room password');
|
||||
Toolbar.unlockLockButton();
|
||||
}
|
||||
@@ -185,7 +185,7 @@ function lockRoom(lock) {
|
||||
"dialog.passwordNotSupported");
|
||||
Toolbar.setSharedKey('');
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Invite participants to conference.
|
||||
@@ -199,14 +199,18 @@ function inviteParticipants() {
|
||||
sharedKeyText =
|
||||
APP.translation.translateString("email.sharedKey",
|
||||
{sharedKey: sharedKey});
|
||||
sharedKeyText = sharedKeyText.replace(/\n/g, "%0D%0A");
|
||||
}
|
||||
|
||||
var supportedBrowsers = "Chromium, Google Chrome " +
|
||||
APP.translation.translateString("email.and") + " Opera";
|
||||
var conferenceName = roomUrl.substring(roomUrl.lastIndexOf('/') + 1);
|
||||
var subject = APP.translation.translateString("email.subject",
|
||||
{appName:interfaceConfig.APP_NAME, conferenceName: conferenceName});
|
||||
var body = APP.translation.translateString("email.body",
|
||||
{appName:interfaceConfig.APP_NAME, sharedKeyText: sharedKeyText,
|
||||
roomUrl: roomUrl});
|
||||
roomUrl: roomUrl, supportedBrowsers: supportedBrowsers});
|
||||
body = body.replace(/\n/g, "%0D%0A");
|
||||
|
||||
if (window.localStorage.displayname) {
|
||||
body += "%0D%0A%0D%0A" + window.localStorage.displayname;
|
||||
@@ -219,31 +223,32 @@ function inviteParticipants() {
|
||||
window.open("mailto:?subject=" + subject + "&body=" + body, '_blank');
|
||||
}
|
||||
|
||||
function callSipButtonClicked()
|
||||
{
|
||||
function dialpadButtonClicked() {
|
||||
//TODO show the dialpad box
|
||||
}
|
||||
|
||||
function callSipButtonClicked() {
|
||||
var defaultNumber
|
||||
= config.defaultSipNumber ? config.defaultSipNumber : '';
|
||||
|
||||
var sipMsg = APP.translation.generateTranslatonHTML(
|
||||
var sipMsg = APP.translation.generateTranslationHTML(
|
||||
"dialog.sipMsg");
|
||||
messageHandler.openTwoButtonDialog(null, null, null,
|
||||
'<h2>' + sipMsg + '</h2>' +
|
||||
'<input id="sipNumber" type="text"' +
|
||||
'<input name="sipNumber" type="text"' +
|
||||
' value="' + defaultNumber + '" autofocus>',
|
||||
false,
|
||||
"dialog.Dial",
|
||||
function (e, v, m, f) {
|
||||
if (v) {
|
||||
var numberInput = document.getElementById('sipNumber');
|
||||
if (numberInput.value) {
|
||||
APP.xmpp.dial(numberInput.value, 'fromnumber',
|
||||
UI.getRoomName(), sharedKey);
|
||||
var numberInput = f.sipNumber;
|
||||
if (numberInput) {
|
||||
APP.xmpp.dial(
|
||||
numberInput, 'fromnumber', UI.getRoomName(), sharedKey);
|
||||
}
|
||||
}
|
||||
},
|
||||
function (event) {
|
||||
document.getElementById('sipNumber').focus();
|
||||
}
|
||||
null, null, ':input:first'
|
||||
);
|
||||
}
|
||||
|
||||
@@ -263,12 +268,6 @@ var Toolbar = (function (my) {
|
||||
loggedIn = true;
|
||||
}
|
||||
|
||||
//FIXME: XMPP authentication need improvements for "live" login
|
||||
if (!APP.xmpp.isExternalAuthEnabled() && !loggedIn)
|
||||
{
|
||||
authenticationEnabled = false;
|
||||
}
|
||||
|
||||
Toolbar.showAuthenticateButton(authenticationEnabled);
|
||||
|
||||
if (authenticationEnabled) {
|
||||
@@ -279,7 +278,7 @@ var Toolbar = (function (my) {
|
||||
}
|
||||
}
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets shared key
|
||||
@@ -291,8 +290,12 @@ var Toolbar = (function (my) {
|
||||
|
||||
my.authenticateClicked = function () {
|
||||
Authentication.focusAuthenticationWindow();
|
||||
if (!APP.xmpp.isExternalAuthEnabled()) {
|
||||
Authentication.xmppAuthenticate();
|
||||
return;
|
||||
}
|
||||
// Get authentication URL
|
||||
if (!APP.xmpp.getMUCJoined()) {
|
||||
if (!APP.xmpp.isMUCJoined()) {
|
||||
APP.xmpp.getLoginUrl(UI.getRoomName(), function (url) {
|
||||
// If conference has not been started yet - redirect to login page
|
||||
window.location.href = url;
|
||||
@@ -327,7 +330,8 @@ var Toolbar = (function (my) {
|
||||
if (inviteLink) {
|
||||
inviteLink.value = roomUrl;
|
||||
inviteLink.select();
|
||||
document.getElementById('jqi_state0_buttonInvite').disabled = false;
|
||||
$('#inviteLinkRef').parent()
|
||||
.find('button[value=true]').prop('disabled', false);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -335,9 +339,8 @@ var Toolbar = (function (my) {
|
||||
* Disables and enables some of the buttons.
|
||||
*/
|
||||
my.setupButtonsFromConfig = function () {
|
||||
if (config.disablePrezi)
|
||||
{
|
||||
$("#prezi_button").css({display: "none"});
|
||||
if (config.disablePrezi) {
|
||||
$("#toolbar_button_prezi").css({display: "none"});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -367,30 +370,29 @@ var Toolbar = (function (my) {
|
||||
}
|
||||
});
|
||||
} else {
|
||||
var msg = APP.translation.generateTranslatonHTML(
|
||||
var msg = APP.translation.generateTranslationHTML(
|
||||
"dialog.passwordMsg");
|
||||
var yourPassword = APP.translation.translateString(
|
||||
"dialog.yourPassword");
|
||||
messageHandler.openTwoButtonDialog(null, null, null,
|
||||
'<h2>' + msg + '</h2>' +
|
||||
'<input id="lockKey" type="text"' +
|
||||
'<input name="lockKey" type="text"' +
|
||||
' data-i18n="[placeholder]dialog.yourPassword" ' +
|
||||
'placeholder="' + yourPassword + '" autofocus>',
|
||||
false,
|
||||
"dialog.Save",
|
||||
function (e, v) {
|
||||
function (e, v, m, f) {
|
||||
if (v) {
|
||||
var lockKey = document.getElementById('lockKey');
|
||||
var lockKey = f.lockKey;
|
||||
|
||||
if (lockKey.value) {
|
||||
Toolbar.setSharedKey(UIUtil.escapeHtml(lockKey.value));
|
||||
if (lockKey) {
|
||||
Toolbar.setSharedKey(
|
||||
UIUtil.escapeHtml(lockKey));
|
||||
lockRoom(true);
|
||||
}
|
||||
}
|
||||
},
|
||||
function () {
|
||||
document.getElementById('lockKey').focus();
|
||||
}
|
||||
null, null, 'input:first'
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -421,12 +423,13 @@ var Toolbar = (function (my) {
|
||||
}
|
||||
}
|
||||
},
|
||||
function () {
|
||||
function (event) {
|
||||
if (roomUrl) {
|
||||
document.getElementById('inviteLinkRef').select();
|
||||
} else {
|
||||
document.getElementById('jqi_state0_buttonInvite')
|
||||
.disabled = true;
|
||||
if (event && event.target)
|
||||
$(event.target)
|
||||
.find('button[value=true]').prop('disabled', true);
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -434,13 +437,14 @@ var Toolbar = (function (my) {
|
||||
|
||||
/**
|
||||
* Opens the settings dialog.
|
||||
* FIXME: not used ?
|
||||
*/
|
||||
my.openSettingsDialog = function () {
|
||||
var settings1 = APP.translation.generateTranslatonHTML(
|
||||
var settings1 = APP.translation.generateTranslationHTML(
|
||||
"dialog.settings1");
|
||||
var settings2 = APP.translation.generateTranslatonHTML(
|
||||
var settings2 = APP.translation.generateTranslationHTML(
|
||||
"dialog.settings2");
|
||||
var settings3 = APP.translation.generateTranslatonHTML(
|
||||
var settings3 = APP.translation.generateTranslationHTML(
|
||||
"dialog.settings3");
|
||||
|
||||
var yourPassword = APP.translation.translateString(
|
||||
@@ -511,15 +515,15 @@ var Toolbar = (function (my) {
|
||||
* Unlocks the lock button state.
|
||||
*/
|
||||
my.unlockLockButton = function () {
|
||||
if ($("#lockIcon").hasClass("icon-security-locked"))
|
||||
UIUtil.buttonClick("#lockIcon", "icon-security icon-security-locked");
|
||||
if ($("#toolbar_button_security").hasClass("icon-security-locked"))
|
||||
UIUtil.buttonClick("#toolbar_button_security", "icon-security icon-security-locked");
|
||||
};
|
||||
/**
|
||||
* Updates the lock button state to locked.
|
||||
*/
|
||||
my.lockLockButton = function () {
|
||||
if ($("#lockIcon").hasClass("icon-security"))
|
||||
UIUtil.buttonClick("#lockIcon", "icon-security icon-security-locked");
|
||||
if ($("#toolbar_button_security").hasClass("icon-security"))
|
||||
UIUtil.buttonClick("#toolbar_button_security", "icon-security icon-security-locked");
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -542,30 +546,77 @@ var Toolbar = (function (my) {
|
||||
}
|
||||
|
||||
if (show) {
|
||||
$('#recording').css({display: "inline"});
|
||||
$('#toolbar_button_record').css({display: "inline-block"});
|
||||
}
|
||||
else {
|
||||
$('#recording').css({display: "none"});
|
||||
$('#toolbar_button_record').css({display: "none"});
|
||||
}
|
||||
};
|
||||
|
||||
// Sets the state of the recording button
|
||||
my.setRecordingButtonState = function (isRecording) {
|
||||
if (isRecording) {
|
||||
$('#recordButton').removeClass("icon-recEnable");
|
||||
$('#recordButton').addClass("icon-recEnable active");
|
||||
} else {
|
||||
$('#recordButton').removeClass("icon-recEnable active");
|
||||
$('#recordButton').addClass("icon-recEnable");
|
||||
my.setRecordingButtonState = function (recordingState) {
|
||||
var selector = $('#toolbar_button_record');
|
||||
|
||||
if (recordingState === 'on') {
|
||||
selector.removeClass("icon-recEnable");
|
||||
selector.addClass("icon-recEnable active");
|
||||
|
||||
$("#largeVideo").toggleClass("videoMessageFilter", true);
|
||||
var recordOnKey = "recording.on";
|
||||
$('#videoConnectionMessage').attr("data-i18n", recordOnKey);
|
||||
$('#videoConnectionMessage').text(APP.translation.translateString(recordOnKey));
|
||||
|
||||
setTimeout(function(){
|
||||
$("#largeVideo").toggleClass("videoMessageFilter", false);
|
||||
$('#videoConnectionMessage').css({display: "none"});
|
||||
}, 1500);
|
||||
|
||||
recordingToaster = messageHandler.notify(null, "recording.toaster", null,
|
||||
null, null, {timeOut: 0, closeButton: null, tapToDismiss: false});
|
||||
} else if (recordingState === 'off') {
|
||||
selector.removeClass("icon-recEnable active");
|
||||
selector.addClass("icon-recEnable");
|
||||
|
||||
$("#largeVideo").toggleClass("videoMessageFilter", false);
|
||||
$('#videoConnectionMessage').css({display: "none"});
|
||||
|
||||
if (recordingToaster)
|
||||
messageHandler.remove(recordingToaster);
|
||||
|
||||
} else if (recordingState === 'pending') {
|
||||
selector.removeClass("icon-recEnable active");
|
||||
selector.addClass("icon-recEnable");
|
||||
|
||||
$("#largeVideo").toggleClass("videoMessageFilter", true);
|
||||
var recordPendingKey = "recording.pending";
|
||||
$('#videoConnectionMessage').attr("data-i18n", recordPendingKey);
|
||||
$('#videoConnectionMessage').text(APP.translation.translateString(recordPendingKey));
|
||||
$('#videoConnectionMessage').css({display: "block"});
|
||||
}
|
||||
};
|
||||
|
||||
// checks whether recording is enabled and whether we have params to start automatically recording
|
||||
my.checkAutoRecord = function () {
|
||||
if (config.enableRecording && config.autoRecord) {
|
||||
toggleRecording(config.autoRecordToken);
|
||||
}
|
||||
}
|
||||
|
||||
// Shows or hides SIP calls button
|
||||
my.showSipCallButton = function (show) {
|
||||
if (APP.xmpp.isSipGatewayEnabled() && show) {
|
||||
$('#sipCallButton').css({display: "inline-block"});
|
||||
$('#toolbar_button_sip').css({display: "inline-block"});
|
||||
} else {
|
||||
$('#sipCallButton').css({display: "none"});
|
||||
$('#toolbar_button_sip').css({display: "none"});
|
||||
}
|
||||
};
|
||||
|
||||
// Shows or hides the dialpad button
|
||||
my.showDialPadButton = function (show) {
|
||||
if (show) {
|
||||
$('#toolbar_button_dialpad').css({display: "inline-block"});
|
||||
} else {
|
||||
$('#toolbar_button_dialpad').css({display: "none"});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -575,8 +626,9 @@ var Toolbar = (function (my) {
|
||||
*/
|
||||
my.setAuthenticatedIdentity = function (authIdentity) {
|
||||
if (authIdentity) {
|
||||
$('#toolbar_auth_identity').css({display: "list-item"});
|
||||
$('#toolbar_auth_identity').text(authIdentity);
|
||||
var selector = $('#toolbar_auth_identity');
|
||||
selector.css({display: "list-item"});
|
||||
selector.text(authIdentity);
|
||||
} else {
|
||||
$('#toolbar_auth_identity').css({display: "none"});
|
||||
}
|
||||
@@ -612,13 +664,10 @@ var Toolbar = (function (my) {
|
||||
* @param active the state of the desktop streaming.
|
||||
*/
|
||||
my.changeDesktopSharingButtonState = function (active) {
|
||||
var button = $("#desktopsharing > a");
|
||||
if (active)
|
||||
{
|
||||
var button = $("#toolbar_button_desktopsharing");
|
||||
if (active) {
|
||||
button.addClass("glow");
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
button.removeClass("glow");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
/* global $, interfaceConfig, Moderator, DesktopStreaming.showDesktopSharingButton */
|
||||
/* global APP, config, $, interfaceConfig, Moderator,
|
||||
DesktopStreaming.showDesktopSharingButton */
|
||||
|
||||
var toolbarTimeoutObject,
|
||||
toolbarTimeout = interfaceConfig.INITIAL_TOOLBAR_TIMEOUT;
|
||||
|
||||
function showDesktopSharingButton() {
|
||||
if (APP.desktopsharing.isDesktopSharingEnabled()) {
|
||||
$('#desktopsharing').css({display: "inline"});
|
||||
$('#toolbar_button_desktopsharing').css({display: "inline-block"});
|
||||
} else {
|
||||
$('#desktopsharing').css({display: "none"});
|
||||
$('#toolbar_button_desktopsharing').css({display: "none"});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +16,9 @@ function showDesktopSharingButton() {
|
||||
* Hides the toolbar.
|
||||
*/
|
||||
function hideToolbar() {
|
||||
if(config.alwaysVisibleToolbar)
|
||||
return;
|
||||
|
||||
var header = $("#header"),
|
||||
bottomToolbar = $("#bottomToolbar");
|
||||
var isToolbarHover = false;
|
||||
@@ -49,6 +53,8 @@ var ToolbarToggler = {
|
||||
* Shows the main toolbar.
|
||||
*/
|
||||
showToolbar: function () {
|
||||
if (interfaceConfig.filmStripOnly)
|
||||
return;
|
||||
var header = $("#header"),
|
||||
bottomToolbar = $("#bottomToolbar");
|
||||
if (!header.is(':visible') || !bottomToolbar.is(":visible")) {
|
||||
@@ -78,13 +84,15 @@ var ToolbarToggler = {
|
||||
showDesktopSharingButton();
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Docks/undocks the toolbar.
|
||||
*
|
||||
* @param isDock indicates what operation to perform
|
||||
*/
|
||||
dockToolbar: function (isDock) {
|
||||
if (interfaceConfig.filmStripOnly)
|
||||
return;
|
||||
|
||||
if (isDock) {
|
||||
// First make sure the toolbar is shown.
|
||||
if (!$('#header').is(':visible')) {
|
||||
@@ -108,7 +116,6 @@ var ToolbarToggler = {
|
||||
},
|
||||
|
||||
showDesktopSharingButton: showDesktopSharingButton
|
||||
|
||||
};
|
||||
|
||||
module.exports = ToolbarToggler;
|
||||
@@ -1,3 +1,4 @@
|
||||
/* global $ */
|
||||
var JitsiPopover = (function () {
|
||||
/**
|
||||
* Constructs new JitsiPopover and attaches it to the element
|
||||
@@ -45,23 +46,24 @@ var JitsiPopover = (function () {
|
||||
* Shows the popover
|
||||
*/
|
||||
JitsiPopover.prototype.show = function () {
|
||||
if(!JitsiPopover.enabled)
|
||||
return;
|
||||
this.createPopover();
|
||||
this.popoverShown = true;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Hides the popover
|
||||
*/
|
||||
JitsiPopover.prototype.hide = function () {
|
||||
if(!this.elementIsHovered && !this.popoverIsHovered && this.popoverShown)
|
||||
{
|
||||
if(!this.elementIsHovered && !this.popoverIsHovered &&
|
||||
this.popoverShown) {
|
||||
this.forceHide();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Hides the popover
|
||||
* Hides the popover.
|
||||
*/
|
||||
JitsiPopover.prototype.forceHide = function () {
|
||||
$(".jitsipopover").remove();
|
||||
@@ -69,7 +71,7 @@ var JitsiPopover = (function () {
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates the popover html
|
||||
* Creates the popover html.
|
||||
*/
|
||||
JitsiPopover.prototype.createPopover = function () {
|
||||
$("body").append(this.template);
|
||||
@@ -86,7 +88,7 @@ var JitsiPopover = (function () {
|
||||
};
|
||||
|
||||
/**
|
||||
* Refreshes the position of the popover
|
||||
* Refreshes the position of the popover.
|
||||
*/
|
||||
JitsiPopover.prototype.refreshPosition = function () {
|
||||
$(".jitsipopover").position({
|
||||
@@ -95,10 +97,13 @@ var JitsiPopover = (function () {
|
||||
collision: "fit",
|
||||
of: this.element,
|
||||
using: function (position, elements) {
|
||||
var calcLeft = elements.target.left - elements.element.left + elements.target.width/2;
|
||||
$(".jitsipopover").css({top: position.top, left: position.left, display: "table"});
|
||||
var calcLeft = elements.target.left - elements.element.left +
|
||||
elements.target.width/2;
|
||||
$(".jitsipopover").css(
|
||||
{top: position.top, left: position.left, display: "table"});
|
||||
$(".jitsipopover > .arrow").css({left: calcLeft});
|
||||
$(".jitsipopover > .jitsiPopupmenuPadding").css({left: calcLeft - 50});
|
||||
$(".jitsipopover > .jitsiPopupmenuPadding").css(
|
||||
{left: calcLeft - 50});
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -115,9 +120,9 @@ var JitsiPopover = (function () {
|
||||
this.createPopover();
|
||||
};
|
||||
|
||||
JitsiPopover.enabled = true;
|
||||
|
||||
return JitsiPopover;
|
||||
|
||||
|
||||
})();
|
||||
|
||||
module.exports = JitsiPopover;
|
||||
@@ -1,60 +1,73 @@
|
||||
/* global $, jQuery */
|
||||
/* global $, APP, jQuery, toastr */
|
||||
|
||||
/**
|
||||
* Flag for enable/disable of the notifications.
|
||||
* @type {boolean}
|
||||
*/
|
||||
var notificationsEnabled = true;
|
||||
|
||||
var messageHandler = (function(my) {
|
||||
|
||||
/**
|
||||
* Shows a message to the user.
|
||||
*
|
||||
* @param titleString the title of the message
|
||||
* @param messageString the text of the message
|
||||
* @param titleKey the title of the message
|
||||
* @param messageKey the text of the message
|
||||
*/
|
||||
my.openMessageDialog = function(titleKey, messageKey) {
|
||||
var title = null;
|
||||
if(titleKey)
|
||||
{
|
||||
title = APP.translation.generateTranslatonHTML(titleKey);
|
||||
if(titleKey) {
|
||||
title = APP.translation.generateTranslationHTML(titleKey);
|
||||
}
|
||||
var message = APP.translation.generateTranslatonHTML(messageKey);
|
||||
var message = APP.translation.generateTranslationHTML(messageKey);
|
||||
$.prompt(message,
|
||||
{
|
||||
title: title,
|
||||
persistent: false
|
||||
}
|
||||
{title: title, persistent: false}
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Shows a message to the user with two buttons: first is given as a parameter and the second is Cancel.
|
||||
* Shows a message to the user with two buttons: first is given as a
|
||||
* parameter and the second is Cancel.
|
||||
*
|
||||
* @param titleString the title of the message
|
||||
* @param msgString the text of the message
|
||||
* @param persistent boolean value which determines whether the message is persistent or not
|
||||
* @param persistent boolean value which determines whether the message is
|
||||
* persistent or not
|
||||
* @param leftButton the fist button's text
|
||||
* @param submitFunction function to be called on submit
|
||||
* @param loadedFunction function to be called after the prompt is fully loaded
|
||||
* @param loadedFunction function to be called after the prompt is fully
|
||||
* loaded
|
||||
* @param closeFunction function to be called after the prompt is closed
|
||||
* @param focus optional focus selector or button index to be focused after
|
||||
* the dialog is opened
|
||||
* @param defaultButton index of default button which will be activated when
|
||||
* the user press 'enter'. Indexed from 0.
|
||||
*/
|
||||
my.openTwoButtonDialog = function(titleKey, titleString, msgKey, msgString,
|
||||
persistent, leftButtonKey, submitFunction, loadedFunction,
|
||||
closeFunction)
|
||||
{
|
||||
var leftButton = APP.translation.generateTranslatonHTML(leftButtonKey);
|
||||
var buttons = {};
|
||||
buttons.button1 = {title: leftButton, value: true};
|
||||
var cancelButton = APP.translation.generateTranslatonHTML("dialog.Cancel");
|
||||
buttons.button2 = {title: cancelButton, value: false};
|
||||
closeFunction, focus, defaultButton) {
|
||||
var buttons = [];
|
||||
|
||||
var leftButton = APP.translation.generateTranslationHTML(leftButtonKey);
|
||||
buttons.push({ title: leftButton, value: true});
|
||||
|
||||
var cancelButton
|
||||
= APP.translation.generateTranslationHTML("dialog.Cancel");
|
||||
buttons.push({title: cancelButton, value: false});
|
||||
|
||||
var message = msgString, title = titleString;
|
||||
if(titleKey)
|
||||
{
|
||||
title = APP.translation.generateTranslatonHTML(titleKey);
|
||||
if (titleKey) {
|
||||
title = APP.translation.generateTranslationHTML(titleKey);
|
||||
}
|
||||
if(msgKey) {
|
||||
message = APP.translation.generateTranslatonHTML(msgKey);
|
||||
if (msgKey) {
|
||||
message = APP.translation.generateTranslationHTML(msgKey);
|
||||
}
|
||||
$.prompt(message, {
|
||||
title: title,
|
||||
persistent: false,
|
||||
buttons: buttons,
|
||||
defaultButton: 1,
|
||||
defaultButton: defaultButton,
|
||||
focus: focus,
|
||||
loaded: loadedFunction,
|
||||
submit: submitFunction,
|
||||
close: closeFunction
|
||||
@@ -66,11 +79,14 @@ var messageHandler = (function(my) {
|
||||
*
|
||||
* @param titleString the title of the message
|
||||
* @param msgString the text of the message
|
||||
* @param persistent boolean value which determines whether the message is persistent or not
|
||||
* @param buttons object with the buttons. The keys must be the name of the button and value is the value
|
||||
* that will be passed to submitFunction
|
||||
* @param persistent boolean value which determines whether the message is
|
||||
* persistent or not
|
||||
* @param buttons object with the buttons. The keys must be the name of the
|
||||
* button and value is the value that will be passed to
|
||||
* submitFunction
|
||||
* @param submitFunction function to be called on submit
|
||||
* @param loadedFunction function to be called after the prompt is fully loaded
|
||||
* @param loadedFunction function to be called after the prompt is fully
|
||||
* loaded
|
||||
*/
|
||||
my.openDialog = function (titleString, msgString, persistent, buttons,
|
||||
submitFunction, loadedFunction) {
|
||||
@@ -85,7 +101,7 @@ var messageHandler = (function(my) {
|
||||
if (persistent) {
|
||||
args.closeText = '';
|
||||
}
|
||||
return $.prompt(msgString, args);
|
||||
return new Impromptu(msgString, args);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -98,17 +114,10 @@ var messageHandler = (function(my) {
|
||||
/**
|
||||
* Shows a dialog with different states to the user.
|
||||
*
|
||||
* @param statesObject object containing all the states of the dialog
|
||||
* @param loadedFunction function to be called after the prompt is fully loaded
|
||||
* @param stateChangedFunction function to be called when the state of the dialog is changed
|
||||
* @param statesObject object containing all the states of the dialog.
|
||||
*/
|
||||
my.openDialogWithStates = function(statesObject, loadedFunction, stateChangedFunction) {
|
||||
|
||||
|
||||
var myPrompt = $.prompt(statesObject);
|
||||
|
||||
myPrompt.on('impromptu:loaded', loadedFunction);
|
||||
myPrompt.on('impromptu:statechanged', stateChangedFunction);
|
||||
my.openDialogWithStates = function (statesObject, options) {
|
||||
return new Impromptu(statesObject, options);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -121,7 +130,7 @@ var messageHandler = (function(my) {
|
||||
* @param onPopupClosed optional callback function called when popup window
|
||||
* has been closed.
|
||||
*
|
||||
* @returns popup window object if opened successfully or undefined
|
||||
* @returns {object} popup window object if opened successfully or undefined
|
||||
* in case we failed to open it(popup blocked)
|
||||
*/
|
||||
my.openCenteredPopup = function (url, w, h, onPopupClosed) {
|
||||
@@ -144,8 +153,8 @@ var messageHandler = (function(my) {
|
||||
/**
|
||||
* Shows a dialog prompting the user to send an error report.
|
||||
*
|
||||
* @param titleString the title of the message
|
||||
* @param msgString the text of the message
|
||||
* @param titleKey the title of the message
|
||||
* @param msgKey the text of the message
|
||||
* @param error the error that is being reported
|
||||
*/
|
||||
my.openReportDialog = function(titleKey, msgKey, error) {
|
||||
@@ -156,35 +165,42 @@ var messageHandler = (function(my) {
|
||||
|
||||
/**
|
||||
* Shows an error dialog to the user.
|
||||
* @param title the title of the message
|
||||
* @param message the text of the messafe
|
||||
* @param titleKey the title of the message.
|
||||
* @param msgKey the text of the message.
|
||||
*/
|
||||
my.showError = function(titleKey, msgKey) {
|
||||
|
||||
if(!titleKey) {
|
||||
if (!titleKey) {
|
||||
titleKey = "dialog.oops";
|
||||
}
|
||||
if(!msgKey)
|
||||
{
|
||||
if (!msgKey) {
|
||||
msgKey = "dialog.defaultError";
|
||||
}
|
||||
messageHandler.openMessageDialog(titleKey, msgKey);
|
||||
};
|
||||
|
||||
/**
|
||||
* Displayes notification.
|
||||
* @param displayName display name of the participant that is associated with the notification.
|
||||
* @param displayNameKey the key from the language file for the display name.
|
||||
* @param cls css class for the notification
|
||||
* @param messageKey the key from the language file for the text of the message.
|
||||
* @param messageArguments object with the arguments for the message.
|
||||
* @param options object with language options.
|
||||
*/
|
||||
my.notify = function(displayName, displayNameKey,
|
||||
cls, messageKey, messageArguments) {
|
||||
cls, messageKey, messageArguments, options) {
|
||||
if(!notificationsEnabled)
|
||||
return;
|
||||
var displayNameSpan = '<span class="nickname" ';
|
||||
if(displayName)
|
||||
{
|
||||
if (displayName) {
|
||||
displayNameSpan += ">" + displayName;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
displayNameSpan += "data-i18n='" + displayNameKey +
|
||||
"'>" + APP.translation.translateString(displayNameKey);
|
||||
}
|
||||
displayNameSpan += "</span>";
|
||||
toastr.info(
|
||||
return toastr.info(
|
||||
displayNameSpan + '<br>' +
|
||||
'<span class=' + cls + ' data-i18n="' + messageKey + '"' +
|
||||
(messageArguments?
|
||||
@@ -192,7 +208,29 @@ var messageHandler = (function(my) {
|
||||
: "") + ">" +
|
||||
APP.translation.translateString(messageKey,
|
||||
messageArguments) +
|
||||
'</span>');
|
||||
'</span>', null, options);
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes the toaster.
|
||||
* @param toasterElement
|
||||
*/
|
||||
my.remove = function(toasterElement) {
|
||||
toasterElement.remove();
|
||||
};
|
||||
|
||||
/**
|
||||
* Disables notifications.
|
||||
*/
|
||||
my.disableNotifications = function () {
|
||||
notificationsEnabled = false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Enables notifications.
|
||||
*/
|
||||
my.enableNotifications = function () {
|
||||
notificationsEnabled = true;
|
||||
};
|
||||
|
||||
return my;
|
||||
|
||||
@@ -3,7 +3,7 @@ var UIEvents = require("../../../service/UI/UIEvents");
|
||||
var nickname = null;
|
||||
var eventEmitter = null;
|
||||
|
||||
var NickanameHandler = {
|
||||
var NicknameHandler = {
|
||||
init: function (emitter) {
|
||||
eventEmitter = emitter;
|
||||
var storedDisplayName = window.localStorage.displayname;
|
||||
@@ -27,4 +27,4 @@ var NickanameHandler = {
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = NickanameHandler;
|
||||
module.exports = NicknameHandler;
|
||||
@@ -1,3 +1,4 @@
|
||||
/* global $ */
|
||||
/**
|
||||
* Created by hristo on 12/22/14.
|
||||
*/
|
||||
@@ -5,10 +6,12 @@ module.exports = {
|
||||
/**
|
||||
* Returns the available video width.
|
||||
*/
|
||||
getAvailableVideoWidth: function () {
|
||||
getAvailableVideoWidth: function (isVisible) {
|
||||
var PanelToggler = require("../side_pannels/SidePanelToggler");
|
||||
if(typeof isVisible === "undefined" || isVisible === null)
|
||||
isVisible = PanelToggler.isVisible();
|
||||
var rightPanelWidth
|
||||
= PanelToggler.isVisible() ? PanelToggler.getPanelSize()[0] : 0;
|
||||
= isVisible ? PanelToggler.getPanelSize()[0] : 0;
|
||||
|
||||
return window.innerWidth - rightPanelWidth;
|
||||
},
|
||||
@@ -59,7 +62,7 @@ module.exports = {
|
||||
|
||||
for (var i = 0, n = pixels.length; i < n; i += 4) {
|
||||
var grayscale
|
||||
= pixels[i] * .3 + pixels[i+1] * .59 + pixels[i+2] * .11;
|
||||
= pixels[i] * 0.3 + pixels[i+1] * 0.59 + pixels[i+2] * 0.11;
|
||||
pixels[i ] = grayscale; // red
|
||||
pixels[i+1] = grayscale; // green
|
||||
pixels[i+2] = grayscale; // blue
|
||||
@@ -75,7 +78,19 @@ module.exports = {
|
||||
element.setAttribute("data-placement", position);
|
||||
element.setAttribute("data-html", true);
|
||||
element.setAttribute("data-container", "body");
|
||||
},
|
||||
|
||||
/**
|
||||
* Inserts given child element as the first one into the container.
|
||||
* @param container the container to which new child element will be added
|
||||
* @param newChild the new element that will be inserted into the container
|
||||
*/
|
||||
prependChild: function (container, newChild) {
|
||||
var firstChild = container.childNodes[0];
|
||||
if (firstChild) {
|
||||
container.insertBefore(newChild, firstChild);
|
||||
} else {
|
||||
container.appendChild(newChild);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
@@ -1,3 +1,4 @@
|
||||
/* global APP, $ */
|
||||
var JitsiPopover = require("../util/JitsiPopover");
|
||||
|
||||
/**
|
||||
@@ -5,8 +6,7 @@ var JitsiPopover = require("../util/JitsiPopover");
|
||||
* @param videoContainer the video container associated with the indicator.
|
||||
* @constructor
|
||||
*/
|
||||
function ConnectionIndicator(videoContainer, jid, VideoLayout)
|
||||
{
|
||||
function ConnectionIndicator(videoContainer, jid) {
|
||||
this.videoContainer = videoContainer;
|
||||
this.bandwidth = null;
|
||||
this.packetLoss = null;
|
||||
@@ -17,7 +17,6 @@ function ConnectionIndicator(videoContainer, jid, VideoLayout)
|
||||
this.popover = null;
|
||||
this.jid = jid;
|
||||
this.create();
|
||||
this.videoLayout = VideoLayout;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -38,20 +37,17 @@ ConnectionIndicator.connectionQualityValues = {
|
||||
0: "0px"//empty
|
||||
};
|
||||
|
||||
ConnectionIndicator.getIP = function(value)
|
||||
{
|
||||
ConnectionIndicator.getIP = function(value) {
|
||||
return value.substring(0, value.lastIndexOf(":"));
|
||||
};
|
||||
|
||||
ConnectionIndicator.getPort = function(value)
|
||||
{
|
||||
ConnectionIndicator.getPort = function(value) {
|
||||
return value.substring(value.lastIndexOf(":") + 1, value.length);
|
||||
};
|
||||
|
||||
ConnectionIndicator.getStringFromArray = function (array) {
|
||||
var res = "";
|
||||
for(var i = 0; i < array.length; i++)
|
||||
{
|
||||
for(var i = 0; i < array.length; i++) {
|
||||
res += (i === 0? "" : ", ") + array[i];
|
||||
}
|
||||
return res;
|
||||
@@ -66,82 +62,60 @@ ConnectionIndicator.prototype.generateText = function () {
|
||||
|
||||
var translate = APP.translation.translateString;
|
||||
|
||||
if(this.bitrate === null)
|
||||
{
|
||||
if(this.bitrate === null) {
|
||||
downloadBitrate = "N/A";
|
||||
uploadBitrate = "N/A";
|
||||
}
|
||||
else
|
||||
{
|
||||
else {
|
||||
downloadBitrate =
|
||||
this.bitrate.download? this.bitrate.download + " Kbps" : "N/A";
|
||||
uploadBitrate =
|
||||
this.bitrate.upload? this.bitrate.upload + " Kbps" : "N/A";
|
||||
}
|
||||
|
||||
if(this.packetLoss === null)
|
||||
{
|
||||
if(this.packetLoss === null) {
|
||||
packetLoss = "N/A";
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
|
||||
packetLoss = "<span class='jitsipopover_green'>↓</span>" +
|
||||
(this.packetLoss.download !== null? this.packetLoss.download : "N/A") +
|
||||
(this.packetLoss.download !== null ?
|
||||
this.packetLoss.download : "N/A") +
|
||||
"% <span class='jitsipopover_orange'>↑</span>" +
|
||||
(this.packetLoss.upload !== null? this.packetLoss.upload : "N/A") + "%";
|
||||
(this.packetLoss.upload !== null? this.packetLoss.upload : "N/A") +
|
||||
"%";
|
||||
}
|
||||
|
||||
var resolutionValue = null;
|
||||
if(this.resolution && this.jid != null)
|
||||
{
|
||||
if(this.resolution && this.jid != null) {
|
||||
var keys = Object.keys(this.resolution);
|
||||
if(keys.length == 1)
|
||||
{
|
||||
for(var ssrc in this.resolution)
|
||||
{
|
||||
resolutionValue = this.resolution[ssrc];
|
||||
}
|
||||
}
|
||||
else if(keys.length > 1)
|
||||
{
|
||||
var displayedSsrc = APP.simulcast.getReceivingSSRC(this.jid);
|
||||
resolutionValue = this.resolution[displayedSsrc];
|
||||
for(var ssrc in this.resolution) {
|
||||
resolutionValue = this.resolution[ssrc];
|
||||
}
|
||||
}
|
||||
|
||||
if(this.jid === null)
|
||||
{
|
||||
if(this.jid === null) {
|
||||
resolution = "";
|
||||
if(this.resolution === null || !Object.keys(this.resolution) ||
|
||||
Object.keys(this.resolution).length === 0)
|
||||
{
|
||||
Object.keys(this.resolution).length === 0) {
|
||||
resolution = "N/A";
|
||||
}
|
||||
else
|
||||
for(i in this.resolution)
|
||||
{
|
||||
} else {
|
||||
for (i in this.resolution) {
|
||||
resolutionValue = this.resolution[i];
|
||||
if(resolutionValue)
|
||||
{
|
||||
if(resolutionValue.height &&
|
||||
resolutionValue.width)
|
||||
{
|
||||
resolution += (resolution === ""? "" : ", ") +
|
||||
resolutionValue.width + "x" +
|
||||
resolutionValue.height;
|
||||
if (resolutionValue) {
|
||||
if (resolutionValue.height &&
|
||||
resolutionValue.width) {
|
||||
resolution += (resolution === "" ? "" : ", ") +
|
||||
resolutionValue.width + "x" +
|
||||
resolutionValue.height;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(!resolutionValue ||
|
||||
}
|
||||
} else if(!resolutionValue ||
|
||||
!resolutionValue.height ||
|
||||
!resolutionValue.width)
|
||||
{
|
||||
!resolutionValue.width) {
|
||||
resolution = "N/A";
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
resolution = resolutionValue.width + "x" + resolutionValue.height;
|
||||
}
|
||||
|
||||
@@ -161,25 +135,23 @@ ConnectionIndicator.prototype.generateText = function () {
|
||||
translate("connectionindicator.resolution") + "</span></td>" +
|
||||
"<td>" + resolution + "</td></tr></table>";
|
||||
|
||||
if(this.videoContainer.id == "localVideoContainer") {
|
||||
if(this.videoContainer.videoSpanId == "localVideoContainer") {
|
||||
result += "<div class=\"jitsipopover_showmore\" " +
|
||||
"onclick = \"APP.UI.connectionIndicatorShowMore('" +
|
||||
this.videoContainer.id + "')\" data-i18n='connectionindicator." +
|
||||
// FIXME: we do not know local jid when this text is generated
|
||||
//this.jid + "')\" data-i18n='connectionindicator." +
|
||||
"local')\" data-i18n='connectionindicator." +
|
||||
(this.showMoreValue ? "less" : "more") + "'>" +
|
||||
translate("connectionindicator." + (this.showMoreValue ? "less" : "more")) +
|
||||
"</div><br />";
|
||||
}
|
||||
|
||||
if(this.showMoreValue)
|
||||
{
|
||||
if (this.showMoreValue) {
|
||||
var downloadBandwidth, uploadBandwidth, transport;
|
||||
if(this.bandwidth === null)
|
||||
{
|
||||
if (this.bandwidth === null) {
|
||||
downloadBandwidth = "N/A";
|
||||
uploadBandwidth = "N/A";
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
downloadBandwidth = this.bandwidth.download?
|
||||
this.bandwidth.download + " Kbps" :
|
||||
"N/A";
|
||||
@@ -188,45 +160,36 @@ ConnectionIndicator.prototype.generateText = function () {
|
||||
"N/A";
|
||||
}
|
||||
|
||||
if(!this.transport || this.transport.length === 0)
|
||||
{
|
||||
if (!this.transport || this.transport.length === 0) {
|
||||
transport = "<tr>" +
|
||||
"<td><span class='jitsipopover_blue' " +
|
||||
"data-i18n='connectionindicator.address'>" +
|
||||
translate("connectionindicator.address") + "</span></td>" +
|
||||
"<td> N/A</td></tr>";
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
var data = {remoteIP: [], localIP:[], remotePort:[], localPort:[]};
|
||||
for(i = 0; i < this.transport.length; i++)
|
||||
{
|
||||
for(i = 0; i < this.transport.length; i++) {
|
||||
var ip = ConnectionIndicator.getIP(this.transport[i].ip);
|
||||
var port = ConnectionIndicator.getPort(this.transport[i].ip);
|
||||
var localIP =
|
||||
ConnectionIndicator.getIP(this.transport[i].localip);
|
||||
var localPort =
|
||||
ConnectionIndicator.getPort(this.transport[i].localip);
|
||||
if(data.remoteIP.indexOf(ip) == -1)
|
||||
{
|
||||
if(data.remoteIP.indexOf(ip) == -1) {
|
||||
data.remoteIP.push(ip);
|
||||
}
|
||||
|
||||
if(data.remotePort.indexOf(port) == -1)
|
||||
{
|
||||
if(data.remotePort.indexOf(port) == -1) {
|
||||
data.remotePort.push(port);
|
||||
}
|
||||
|
||||
if(data.localIP.indexOf(localIP) == -1)
|
||||
{
|
||||
if(data.localIP.indexOf(localIP) == -1) {
|
||||
data.localIP.push(localIP);
|
||||
}
|
||||
|
||||
if(data.localPort.indexOf(localPort) == -1)
|
||||
{
|
||||
if(data.localPort.indexOf(localPort) == -1) {
|
||||
data.localPort.push(localPort);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var local_address_key = "connectionindicator.localaddress";
|
||||
@@ -292,7 +255,6 @@ ConnectionIndicator.prototype.generateText = function () {
|
||||
uploadBandwidth + "</td></tr>";
|
||||
|
||||
result += transport + "</table>";
|
||||
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -307,11 +269,9 @@ ConnectionIndicator.prototype.showMore = function () {
|
||||
};
|
||||
|
||||
|
||||
function createIcon(classes)
|
||||
{
|
||||
function createIcon(classes) {
|
||||
var icon = document.createElement("span");
|
||||
for(var i in classes)
|
||||
{
|
||||
for(var i in classes) {
|
||||
icon.classList.add(classes[i]);
|
||||
}
|
||||
icon.appendChild(
|
||||
@@ -326,9 +286,10 @@ ConnectionIndicator.prototype.create = function () {
|
||||
this.connectionIndicatorContainer = document.createElement("div");
|
||||
this.connectionIndicatorContainer.className = "connectionindicator";
|
||||
this.connectionIndicatorContainer.style.display = "none";
|
||||
this.videoContainer.appendChild(this.connectionIndicatorContainer);
|
||||
this.videoContainer.container.appendChild(
|
||||
this.connectionIndicatorContainer);
|
||||
this.popover = new JitsiPopover(
|
||||
$("#" + this.videoContainer.id + " > .connectionindicator"),
|
||||
$("#" + this.videoContainer.videoSpanId + " > .connectionindicator"),
|
||||
{content: "<div class=\"connection_info\" data-i18n='connectionindicator.na'>" +
|
||||
APP.translation.translateString("connectionindicator.na") + "</div>",
|
||||
skin: "black"});
|
||||
@@ -337,17 +298,17 @@ ConnectionIndicator.prototype.create = function () {
|
||||
createIcon(["connection", "connection_empty"]));
|
||||
this.fullIcon = this.connectionIndicatorContainer.appendChild(
|
||||
createIcon(["connection", "connection_full"]));
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes the indicator
|
||||
*/
|
||||
ConnectionIndicator.prototype.remove = function()
|
||||
{
|
||||
this.connectionIndicatorContainer.remove();
|
||||
ConnectionIndicator.prototype.remove = function() {
|
||||
if (this.connectionIndicatorContainer.parentNode) {
|
||||
this.connectionIndicatorContainer.parentNode.removeChild(
|
||||
this.connectionIndicatorContainer);
|
||||
}
|
||||
this.popover.forceHide();
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -356,33 +317,27 @@ ConnectionIndicator.prototype.remove = function()
|
||||
* @param object the statistics data.
|
||||
*/
|
||||
ConnectionIndicator.prototype.updateConnectionQuality =
|
||||
function (percent, object) {
|
||||
function (percent, object) {
|
||||
|
||||
if(percent === null)
|
||||
{
|
||||
if (percent === null) {
|
||||
this.connectionIndicatorContainer.style.display = "none";
|
||||
this.popover.forceHide();
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
if(this.connectionIndicatorContainer.style.display == "none") {
|
||||
this.connectionIndicatorContainer.style.display = "block";
|
||||
this.videoLayout.updateMutePosition(this.videoContainer.id);
|
||||
this.videoContainer.updateIconPositions();
|
||||
}
|
||||
}
|
||||
this.bandwidth = object.bandwidth;
|
||||
this.bitrate = object.bitrate;
|
||||
this.packetLoss = object.packetLoss;
|
||||
this.transport = object.transport;
|
||||
if(object.resolution)
|
||||
{
|
||||
if (object.resolution) {
|
||||
this.resolution = object.resolution;
|
||||
}
|
||||
for(var quality in ConnectionIndicator.connectionQualityValues)
|
||||
{
|
||||
if(percent >= quality)
|
||||
{
|
||||
for (var quality in ConnectionIndicator.connectionQualityValues) {
|
||||
if (percent >= quality) {
|
||||
this.fullIcon.style.width =
|
||||
ConnectionIndicator.connectionQualityValues[quality];
|
||||
}
|
||||
|
||||
684
modules/UI/videolayout/LargeVideo.js
Normal file
684
modules/UI/videolayout/LargeVideo.js
Normal file
@@ -0,0 +1,684 @@
|
||||
/* global $, APP, Strophe, interfaceConfig */
|
||||
var Avatar = require("../avatar/Avatar");
|
||||
var RTCBrowserType = require("../../RTC/RTCBrowserType");
|
||||
var UIUtil = require("../util/UIUtil");
|
||||
var UIEvents = require("../../../service/UI/UIEvents");
|
||||
var xmpp = require("../../xmpp/xmpp");
|
||||
var ToolbarToggler = require("../toolbars/ToolbarToggler");
|
||||
|
||||
// FIXME: With Temasys we have to re-select everytime
|
||||
//var video = $('#largeVideo');
|
||||
|
||||
var currentVideoWidth = null;
|
||||
var currentVideoHeight = null;
|
||||
// By default we use camera
|
||||
var getVideoSize = getCameraVideoSize;
|
||||
var getVideoPosition = getCameraVideoPosition;
|
||||
/**
|
||||
* The small video instance that is displayed in the large video
|
||||
* @type {SmallVideo}
|
||||
*/
|
||||
var currentSmallVideo = null;
|
||||
/**
|
||||
* Indicates whether the large video is enabled.
|
||||
* @type {boolean}
|
||||
*/
|
||||
var isEnabled = true;
|
||||
/**
|
||||
* Current large video state.
|
||||
* Possible values - video, prezi or etherpad.
|
||||
* @type {string}
|
||||
*/
|
||||
var state = "video";
|
||||
|
||||
/**
|
||||
* Returns the html element associated with the passed state of large video
|
||||
* @param state the state.
|
||||
* @returns {JQuery|*|jQuery|HTMLElement} the container.
|
||||
*/
|
||||
function getContainerByState(state)
|
||||
{
|
||||
var selector = null;
|
||||
switch (state)
|
||||
{
|
||||
case "video":
|
||||
selector = "#largeVideoWrapper";
|
||||
break;
|
||||
case "etherpad":
|
||||
selector = "#etherpad>iframe";
|
||||
break;
|
||||
case "prezi":
|
||||
selector = "#presentation>iframe";
|
||||
break;
|
||||
}
|
||||
return (selector !== null)? $(selector) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the size and position of the given video element.
|
||||
*
|
||||
* @param video the video element to position
|
||||
* @param width the desired video width
|
||||
* @param height the desired video height
|
||||
* @param horizontalIndent the left and right indent
|
||||
* @param verticalIndent the top and bottom indent
|
||||
*/
|
||||
function positionVideo(video,
|
||||
width,
|
||||
height,
|
||||
horizontalIndent,
|
||||
verticalIndent,
|
||||
animate) {
|
||||
if (animate) {
|
||||
video.animate({
|
||||
width: width,
|
||||
height: height,
|
||||
top: verticalIndent,
|
||||
bottom: verticalIndent,
|
||||
left: horizontalIndent,
|
||||
right: horizontalIndent
|
||||
},
|
||||
{
|
||||
queue: false,
|
||||
duration: 500
|
||||
});
|
||||
} else {
|
||||
video.width(width);
|
||||
video.height(height);
|
||||
video.css({ top: verticalIndent + 'px',
|
||||
bottom: verticalIndent + 'px',
|
||||
left: horizontalIndent + 'px',
|
||||
right: horizontalIndent + 'px'});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of the video dimensions, so that it keeps it's aspect
|
||||
* ratio and fits available area with it's larger dimension. This method
|
||||
* ensures that whole video will be visible and can leave empty areas.
|
||||
*
|
||||
* @return an array with 2 elements, the video width and the video height
|
||||
*/
|
||||
function getDesktopVideoSize(videoWidth,
|
||||
videoHeight,
|
||||
videoSpaceWidth,
|
||||
videoSpaceHeight) {
|
||||
if (!videoWidth)
|
||||
videoWidth = currentVideoWidth;
|
||||
if (!videoHeight)
|
||||
videoHeight = currentVideoHeight;
|
||||
|
||||
var aspectRatio = videoWidth / videoHeight;
|
||||
|
||||
var availableWidth = Math.max(videoWidth, videoSpaceWidth);
|
||||
var availableHeight = Math.max(videoHeight, videoSpaceHeight);
|
||||
|
||||
videoSpaceHeight -= $('#remoteVideos').outerHeight();
|
||||
|
||||
if (availableWidth / aspectRatio >= videoSpaceHeight)
|
||||
{
|
||||
availableHeight = videoSpaceHeight;
|
||||
availableWidth = availableHeight * aspectRatio;
|
||||
}
|
||||
|
||||
if (availableHeight * aspectRatio >= videoSpaceWidth)
|
||||
{
|
||||
availableWidth = videoSpaceWidth;
|
||||
availableHeight = availableWidth / aspectRatio;
|
||||
}
|
||||
|
||||
return [availableWidth, availableHeight];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns an array of the video horizontal and vertical indents,
|
||||
* so that if fits its parent.
|
||||
*
|
||||
* @return an array with 2 elements, the horizontal indent and the vertical
|
||||
* indent
|
||||
*/
|
||||
function getCameraVideoPosition(videoWidth,
|
||||
videoHeight,
|
||||
videoSpaceWidth,
|
||||
videoSpaceHeight) {
|
||||
// Parent height isn't completely calculated when we position the video in
|
||||
// full screen mode and this is why we use the screen height in this case.
|
||||
// Need to think it further at some point and implement it properly.
|
||||
var isFullScreen = document.fullScreen ||
|
||||
document.mozFullScreen ||
|
||||
document.webkitIsFullScreen;
|
||||
if (isFullScreen)
|
||||
videoSpaceHeight = window.innerHeight;
|
||||
|
||||
var horizontalIndent = (videoSpaceWidth - videoWidth) / 2;
|
||||
var verticalIndent = (videoSpaceHeight - videoHeight) / 2;
|
||||
|
||||
return [horizontalIndent, verticalIndent];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of the video horizontal and vertical indents.
|
||||
* Centers horizontally and top aligns vertically.
|
||||
*
|
||||
* @return an array with 2 elements, the horizontal indent and the vertical
|
||||
* indent
|
||||
*/
|
||||
function getDesktopVideoPosition(videoWidth,
|
||||
videoHeight,
|
||||
videoSpaceWidth,
|
||||
videoSpaceHeight) {
|
||||
|
||||
var horizontalIndent = (videoSpaceWidth - videoWidth) / 2;
|
||||
|
||||
var verticalIndent = 0;// Top aligned
|
||||
|
||||
return [horizontalIndent, verticalIndent];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns an array of the video dimensions, so that it covers the screen.
|
||||
* It leaves no empty areas, but some parts of the video might not be visible.
|
||||
*
|
||||
* @return an array with 2 elements, the video width and the video height
|
||||
*/
|
||||
function getCameraVideoSize(videoWidth,
|
||||
videoHeight,
|
||||
videoSpaceWidth,
|
||||
videoSpaceHeight) {
|
||||
if (!videoWidth)
|
||||
videoWidth = currentVideoWidth;
|
||||
if (!videoHeight)
|
||||
videoHeight = currentVideoHeight;
|
||||
|
||||
var aspectRatio = videoWidth / videoHeight;
|
||||
|
||||
var availableWidth = Math.max(videoWidth, videoSpaceWidth);
|
||||
var availableHeight = Math.max(videoHeight, videoSpaceHeight);
|
||||
|
||||
if (availableWidth / aspectRatio < videoSpaceHeight) {
|
||||
availableHeight = videoSpaceHeight;
|
||||
availableWidth = availableHeight * aspectRatio;
|
||||
}
|
||||
|
||||
if (availableHeight * aspectRatio < videoSpaceWidth) {
|
||||
availableWidth = videoSpaceWidth;
|
||||
availableHeight = availableWidth / aspectRatio;
|
||||
}
|
||||
|
||||
return [availableWidth, availableHeight];
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the src of the active speaker avatar
|
||||
* @param jid of the current active speaker
|
||||
*/
|
||||
function updateActiveSpeakerAvatarSrc() {
|
||||
var avatar = $("#activeSpeakerAvatar")[0];
|
||||
var jid = currentSmallVideo.peerJid;
|
||||
var url = Avatar.getActiveSpeakerUrl(jid);
|
||||
if (avatar.src === url)
|
||||
return;
|
||||
if (jid) {
|
||||
avatar.src = url;
|
||||
currentSmallVideo.showAvatar();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the video source of the large video.
|
||||
* @param isVisible
|
||||
*/
|
||||
function changeVideo(isVisible) {
|
||||
|
||||
if (!currentSmallVideo) {
|
||||
console.error("Unable to change large video - no 'currentSmallVideo'");
|
||||
return;
|
||||
}
|
||||
|
||||
updateActiveSpeakerAvatarSrc();
|
||||
|
||||
APP.RTC.setVideoSrc($('#largeVideo')[0], currentSmallVideo.getSrc());
|
||||
|
||||
var videoTransform = document.getElementById('largeVideo')
|
||||
.style.webkitTransform;
|
||||
|
||||
var flipX = currentSmallVideo.flipX;
|
||||
|
||||
if (flipX && videoTransform !== 'scaleX(-1)') {
|
||||
document.getElementById('largeVideo').style.webkitTransform =
|
||||
"scaleX(-1)";
|
||||
} else if (!flipX && videoTransform === 'scaleX(-1)') {
|
||||
document.getElementById('largeVideo').style.webkitTransform =
|
||||
"none";
|
||||
}
|
||||
|
||||
var isDesktop = currentSmallVideo.getVideoType() === 'screen';
|
||||
// Change the way we'll be measuring and positioning large video
|
||||
|
||||
getVideoSize = isDesktop ? getDesktopVideoSize : getCameraVideoSize;
|
||||
getVideoPosition = isDesktop ? getDesktopVideoPosition :
|
||||
getCameraVideoPosition;
|
||||
|
||||
|
||||
// Only if the large video is currently visible.
|
||||
if (isVisible) {
|
||||
LargeVideo.VideoLayout.largeVideoUpdated(currentSmallVideo);
|
||||
|
||||
$('#largeVideoWrapper').fadeTo(300, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the html elements for the large video.
|
||||
*/
|
||||
function createLargeVideoHTML()
|
||||
{
|
||||
var html = '<div id="largeVideoContainer" class="videocontainer">';
|
||||
html += '<div id="presentation"></div>' +
|
||||
'<div id="etherpad"></div>' +
|
||||
'<a target="_new"><div class="watermark leftwatermark"></div></a>' +
|
||||
'<a target="_new"><div class="watermark rightwatermark"></div></a>' +
|
||||
'<a class="poweredby" href="http://jitsi.org" target="_new" >' +
|
||||
'<span data-i18n="poweredby"></span> jitsi.org' +
|
||||
'</a>'+
|
||||
'<div id="activeSpeaker">' +
|
||||
'<img id="activeSpeakerAvatar" src=""/>' +
|
||||
'<canvas id="activeSpeakerAudioLevel"></canvas>' +
|
||||
'</div>' +
|
||||
'<div id="largeVideoWrapper">' +
|
||||
'<video id="largeVideo" autoplay oncontextmenu="return false;"></video>' +
|
||||
'</div id="largeVideoWrapper">' +
|
||||
'<span id="videoConnectionMessage"></span>';
|
||||
html += '</div>';
|
||||
$(html).prependTo("#videospace");
|
||||
|
||||
if (interfaceConfig.SHOW_JITSI_WATERMARK) {
|
||||
var leftWatermarkDiv
|
||||
= $("#largeVideoContainer div[class='watermark leftwatermark']");
|
||||
|
||||
leftWatermarkDiv.css({display: 'block'});
|
||||
leftWatermarkDiv.parent().get(0).href
|
||||
= interfaceConfig.JITSI_WATERMARK_LINK;
|
||||
}
|
||||
|
||||
if (interfaceConfig.SHOW_BRAND_WATERMARK) {
|
||||
var rightWatermarkDiv
|
||||
= $("#largeVideoContainer div[class='watermark rightwatermark']");
|
||||
|
||||
rightWatermarkDiv.css({display: 'block'});
|
||||
rightWatermarkDiv.parent().get(0).href
|
||||
= interfaceConfig.BRAND_WATERMARK_LINK;
|
||||
rightWatermarkDiv.get(0).style.backgroundImage
|
||||
= "url(images/rightwatermark.png)";
|
||||
}
|
||||
|
||||
if (interfaceConfig.SHOW_POWERED_BY) {
|
||||
$("#largeVideoContainer>a[class='poweredby']").css({display: 'block'});
|
||||
}
|
||||
|
||||
if (!RTCBrowserType.isIExplorer()) {
|
||||
$('#largeVideo').volume = 0;
|
||||
}
|
||||
}
|
||||
|
||||
var LargeVideo = {
|
||||
|
||||
init: function (VideoLayout, emitter) {
|
||||
if(!isEnabled)
|
||||
return;
|
||||
createLargeVideoHTML();
|
||||
|
||||
this.VideoLayout = VideoLayout;
|
||||
this.eventEmitter = emitter;
|
||||
this.eventEmitter.emit(UIEvents.LARGEVIDEO_INIT);
|
||||
var self = this;
|
||||
// Listen for large video size updates
|
||||
var largeVideo = $('#largeVideo')[0];
|
||||
var onplaying = function (arg1, arg2, arg3) {
|
||||
// re-select
|
||||
if (RTCBrowserType.isTemasysPluginUsed())
|
||||
largeVideo = $('#largeVideo')[0];
|
||||
currentVideoWidth = largeVideo.videoWidth;
|
||||
currentVideoHeight = largeVideo.videoHeight;
|
||||
self.position(currentVideoWidth, currentVideoHeight);
|
||||
};
|
||||
largeVideo.onplaying = onplaying;
|
||||
},
|
||||
/**
|
||||
* Indicates if the large video is currently visible.
|
||||
*
|
||||
* @return <tt>true</tt> if visible, <tt>false</tt> - otherwise
|
||||
*/
|
||||
isLargeVideoVisible: function() {
|
||||
return $('#largeVideoWrapper').is(':visible');
|
||||
},
|
||||
/**
|
||||
* Returns <tt>true</tt> if the user is currently displayed on large video.
|
||||
*/
|
||||
isCurrentlyOnLarge: function (resourceJid) {
|
||||
return currentSmallVideo && resourceJid &&
|
||||
currentSmallVideo.getResourceJid() === resourceJid;
|
||||
},
|
||||
/**
|
||||
* Updates the large video with the given new video source.
|
||||
*/
|
||||
updateLargeVideo: function (resourceJid, forceUpdate) {
|
||||
if(!isEnabled)
|
||||
return;
|
||||
var newSmallVideo = this.VideoLayout.getSmallVideo(resourceJid);
|
||||
console.log('hover in ' + resourceJid + ', video: ', newSmallVideo);
|
||||
|
||||
if (!newSmallVideo) {
|
||||
console.error("Small video not found for: " + resourceJid);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!LargeVideo.isCurrentlyOnLarge(resourceJid) || forceUpdate) {
|
||||
$('#activeSpeaker').css('visibility', 'hidden');
|
||||
|
||||
var oldSmallVideo = null;
|
||||
if (currentSmallVideo) {
|
||||
oldSmallVideo = currentSmallVideo;
|
||||
}
|
||||
currentSmallVideo = newSmallVideo;
|
||||
|
||||
var oldJid = null;
|
||||
if (oldSmallVideo)
|
||||
oldJid = oldSmallVideo.peerJid;
|
||||
if (oldJid !== resourceJid) {
|
||||
// we want the notification to trigger even if userJid is undefined,
|
||||
// or null.
|
||||
this.eventEmitter.emit(UIEvents.SELECTED_ENDPOINT, resourceJid);
|
||||
}
|
||||
// We are doing fadeOut/fadeIn animations on parent div which wraps
|
||||
// largeVideo, because when Temasys plugin is in use it replaces
|
||||
// <video> elements with plugin <object> tag. In Safari jQuery is
|
||||
// unable to store values on this plugin object which breaks all
|
||||
// animation effects performed on it directly.
|
||||
//
|
||||
// If for any reason large video was hidden before calling fadeOut
|
||||
// changeVideo will never be called, so we call show() in chain just
|
||||
// to be sure
|
||||
$('#largeVideoWrapper').show().fadeTo(300, 0,
|
||||
changeVideo.bind($('#largeVideo'), this.isLargeVideoVisible()));
|
||||
} else {
|
||||
if (currentSmallVideo) {
|
||||
currentSmallVideo.showAvatar();
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* Shows/hides the large video.
|
||||
*/
|
||||
setLargeVideoVisible: function(isVisible) {
|
||||
if(!isEnabled)
|
||||
return;
|
||||
if (isVisible) {
|
||||
$('#largeVideoWrapper').css({visibility: 'visible'});
|
||||
$('.watermark').css({visibility: 'visible'});
|
||||
if(currentSmallVideo)
|
||||
currentSmallVideo.enableDominantSpeaker(true);
|
||||
}
|
||||
else {
|
||||
$('#largeVideoWrapper').css({visibility: 'hidden'});
|
||||
$('#activeSpeaker').css('visibility', 'hidden');
|
||||
$('.watermark').css({visibility: 'hidden'});
|
||||
if(currentSmallVideo)
|
||||
currentSmallVideo.enableDominantSpeaker(false);
|
||||
}
|
||||
},
|
||||
onVideoTypeChanged: function (resourceJid, newVideoType) {
|
||||
if (!isEnabled)
|
||||
return;
|
||||
if (LargeVideo.isCurrentlyOnLarge(resourceJid))
|
||||
{
|
||||
var isDesktop = newVideoType === 'screen';
|
||||
getVideoSize = isDesktop ? getDesktopVideoSize : getCameraVideoSize;
|
||||
getVideoPosition = isDesktop ? getDesktopVideoPosition
|
||||
: getCameraVideoPosition;
|
||||
this.position(null, null, null, null, true);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Positions the large video.
|
||||
*
|
||||
* @param videoWidth the stream video width
|
||||
* @param videoHeight the stream video height
|
||||
*/
|
||||
position: function (videoWidth, videoHeight,
|
||||
videoSpaceWidth, videoSpaceHeight, animate) {
|
||||
if(!isEnabled)
|
||||
return;
|
||||
if(!videoSpaceWidth)
|
||||
videoSpaceWidth = $('#videospace').width();
|
||||
if(!videoSpaceHeight)
|
||||
videoSpaceHeight = window.innerHeight;
|
||||
|
||||
var videoSize = getVideoSize(videoWidth,
|
||||
videoHeight,
|
||||
videoSpaceWidth,
|
||||
videoSpaceHeight);
|
||||
|
||||
var largeVideoWidth = videoSize[0];
|
||||
var largeVideoHeight = videoSize[1];
|
||||
|
||||
var videoPosition = getVideoPosition(largeVideoWidth,
|
||||
largeVideoHeight,
|
||||
videoSpaceWidth,
|
||||
videoSpaceHeight);
|
||||
|
||||
var horizontalIndent = videoPosition[0];
|
||||
var verticalIndent = videoPosition[1];
|
||||
|
||||
positionVideo($('#largeVideoWrapper'),
|
||||
largeVideoWidth,
|
||||
largeVideoHeight,
|
||||
horizontalIndent, verticalIndent, animate);
|
||||
},
|
||||
/**
|
||||
* Resizes the large html elements.
|
||||
* @param animate boolean property that indicates whether the resize should be animated or not.
|
||||
* @param isChatVisible boolean property that indicates whether the chat area is displayed or not.
|
||||
* If that parameter is null the method will check the chat pannel visibility.
|
||||
* @param completeFunction a function to be called when the video space is resized
|
||||
* @returns {*[]} array with the current width and height values of the largeVideo html element.
|
||||
*/
|
||||
resize: function (animate, isVisible, completeFunction) {
|
||||
if(!isEnabled)
|
||||
return;
|
||||
var availableHeight = window.innerHeight;
|
||||
var availableWidth = UIUtil.getAvailableVideoWidth(isVisible);
|
||||
|
||||
if (availableWidth < 0 || availableHeight < 0) return;
|
||||
|
||||
var avatarSize = interfaceConfig.ACTIVE_SPEAKER_AVATAR_SIZE;
|
||||
var top = availableHeight / 2 - avatarSize / 4 * 3;
|
||||
$('#activeSpeaker').css('top', top);
|
||||
|
||||
this.VideoLayout.resizeVideoSpace(animate, isVisible, completeFunction);
|
||||
if(animate) {
|
||||
$('#largeVideoContainer').animate({
|
||||
width: availableWidth,
|
||||
height: availableHeight
|
||||
},
|
||||
{
|
||||
queue: false,
|
||||
duration: 500
|
||||
});
|
||||
} else {
|
||||
$('#largeVideoContainer').width(availableWidth);
|
||||
$('#largeVideoContainer').height(availableHeight);
|
||||
}
|
||||
return [availableWidth, availableHeight];
|
||||
},
|
||||
resizeVideoAreaAnimated: function (isVisible, completeFunction) {
|
||||
if(!isEnabled)
|
||||
return;
|
||||
var size = this.resize(true, isVisible, completeFunction);
|
||||
this.position(null, null, size[0], size[1], true);
|
||||
},
|
||||
getResourceJid: function () {
|
||||
return currentSmallVideo ? currentSmallVideo.getResourceJid() : null;
|
||||
},
|
||||
updateAvatar: function (resourceJid) {
|
||||
if(!isEnabled)
|
||||
return;
|
||||
if (resourceJid === this.getResourceJid()) {
|
||||
updateActiveSpeakerAvatarSrc();
|
||||
}
|
||||
},
|
||||
showAvatar: function (resourceJid, show) {
|
||||
if (!isEnabled)
|
||||
return;
|
||||
if (this.getResourceJid() === resourceJid && state === "video") {
|
||||
$("#largeVideoWrapper")
|
||||
.css("visibility", show ? "hidden" : "visible");
|
||||
$('#activeSpeaker').css("visibility", show ? "visible" : "hidden");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
/**
|
||||
* Disables the large video
|
||||
*/
|
||||
disable: function () {
|
||||
isEnabled = false;
|
||||
},
|
||||
/**
|
||||
* Enables the large video
|
||||
*/
|
||||
enable: function () {
|
||||
isEnabled = true;
|
||||
},
|
||||
/**
|
||||
* Returns true if the video is enabled.
|
||||
*/
|
||||
isEnabled: function () {
|
||||
return isEnabled;
|
||||
},
|
||||
/**
|
||||
* Creates the iframe used by the etherpad
|
||||
* @param src the value for src attribute
|
||||
* @param onloadHandler handler executed when the iframe loads it content
|
||||
* @returns {HTMLElement} the iframe
|
||||
*/
|
||||
createEtherpadIframe: function (src, onloadHandler) {
|
||||
if(!isEnabled)
|
||||
return;
|
||||
|
||||
var etherpadIFrame = document.createElement('iframe');
|
||||
etherpadIFrame.src = src;
|
||||
etherpadIFrame.frameBorder = 0;
|
||||
etherpadIFrame.scrolling = "no";
|
||||
etherpadIFrame.width = $('#largeVideoContainer').width() || 640;
|
||||
etherpadIFrame.height = $('#largeVideoContainer').height() || 480;
|
||||
etherpadIFrame.setAttribute('style', 'visibility: hidden;');
|
||||
|
||||
document.getElementById('etherpad').appendChild(etherpadIFrame);
|
||||
|
||||
etherpadIFrame.onload = onloadHandler;
|
||||
|
||||
return etherpadIFrame;
|
||||
},
|
||||
/**
|
||||
* Changes the state of the large video.
|
||||
* Possible values - video, prezi, etherpad.
|
||||
* @param newState - the new state
|
||||
*/
|
||||
setState: function (newState) {
|
||||
if(state === newState)
|
||||
return;
|
||||
var currentContainer = getContainerByState(state);
|
||||
if(!currentContainer)
|
||||
return;
|
||||
|
||||
var self = this;
|
||||
var oldState = state;
|
||||
|
||||
switch (newState)
|
||||
{
|
||||
case "etherpad":
|
||||
$('#activeSpeaker').css('visibility', 'hidden');
|
||||
currentContainer.fadeOut(300, function () {
|
||||
if (oldState === "prezi") {
|
||||
currentContainer.css({opacity: '0'});
|
||||
$('#reloadPresentation').css({display: 'none'});
|
||||
}
|
||||
else {
|
||||
self.setLargeVideoVisible(false);
|
||||
}
|
||||
});
|
||||
|
||||
$('#etherpad>iframe').fadeIn(300, function () {
|
||||
document.body.style.background = '#eeeeee';
|
||||
$('#etherpad>iframe').css({visibility: 'visible'});
|
||||
$('#etherpad').css({zIndex: 2});
|
||||
});
|
||||
break;
|
||||
case "prezi":
|
||||
var prezi = $('#presentation>iframe');
|
||||
currentContainer.fadeOut(300, function() {
|
||||
document.body.style.background = 'black';
|
||||
});
|
||||
prezi.fadeIn(300, function() {
|
||||
prezi.css({opacity:'1'});
|
||||
ToolbarToggler.dockToolbar(true);//fix that
|
||||
self.setLargeVideoVisible(false);
|
||||
$('#etherpad>iframe').css({visibility: 'hidden'});
|
||||
$('#etherpad').css({zIndex: 0});
|
||||
});
|
||||
$('#activeSpeaker').css('visibility', 'hidden');
|
||||
break;
|
||||
|
||||
case "video":
|
||||
currentContainer.fadeOut(300, function () {
|
||||
$('#presentation>iframe').css({opacity:'0'});
|
||||
$('#reloadPresentation').css({display:'none'});
|
||||
$('#etherpad>iframe').css({visibility: 'hidden'});
|
||||
$('#etherpad').css({zIndex: 0});
|
||||
document.body.style.background = 'black';
|
||||
ToolbarToggler.dockToolbar(false);//fix that
|
||||
});
|
||||
$('#largeVideoWrapper').fadeIn(300, function () {
|
||||
self.setLargeVideoVisible(true);
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
state = newState;
|
||||
|
||||
},
|
||||
/**
|
||||
* Returns the current state of the large video.
|
||||
* @returns {string} the current state - video, prezi or etherpad.
|
||||
*/
|
||||
getState: function () {
|
||||
return state;
|
||||
},
|
||||
/**
|
||||
* Sets hover handlers for the large video container div.
|
||||
*
|
||||
* @param inHandler
|
||||
* @param outHandler
|
||||
*/
|
||||
setHover: function(inHandler, outHandler)
|
||||
{
|
||||
$('#largeVideoContainer').hover(inHandler, outHandler);
|
||||
},
|
||||
|
||||
/**
|
||||
* Enables/disables the filter indicating a video problem to the user.
|
||||
*
|
||||
* @param enable <tt>true</tt> to enable, <tt>false</tt> to disable
|
||||
*/
|
||||
enableVideoProblemFilter: function (enable) {
|
||||
$("#largeVideo").toggleClass("videoProblemFilter", enable);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = LargeVideo;
|
||||
232
modules/UI/videolayout/LocalVideo.js
Normal file
232
modules/UI/videolayout/LocalVideo.js
Normal file
@@ -0,0 +1,232 @@
|
||||
/* global $, interfaceConfig, APP */
|
||||
var SmallVideo = require("./SmallVideo");
|
||||
var ConnectionIndicator = require("./ConnectionIndicator");
|
||||
var NicknameHandler = require("../util/NicknameHandler");
|
||||
var UIUtil = require("../util/UIUtil");
|
||||
var LargeVideo = require("./LargeVideo");
|
||||
var RTCBrowserType = require("../../RTC/RTCBrowserType");
|
||||
|
||||
function LocalVideo(VideoLayout) {
|
||||
this.videoSpanId = "localVideoContainer";
|
||||
this.container = $("#localVideoContainer").get(0);
|
||||
this.bindHoverHandler();
|
||||
this.VideoLayout = VideoLayout;
|
||||
this.flipX = true;
|
||||
this.isLocal = true;
|
||||
this.peerJid = null;
|
||||
}
|
||||
|
||||
LocalVideo.prototype = Object.create(SmallVideo.prototype);
|
||||
LocalVideo.prototype.constructor = LocalVideo;
|
||||
|
||||
/**
|
||||
* Creates the edit display name button.
|
||||
*
|
||||
* @returns {object} the edit button
|
||||
*/
|
||||
function createEditDisplayNameButton() {
|
||||
var editButton = document.createElement('a');
|
||||
editButton.className = 'displayname';
|
||||
UIUtil.setTooltip(editButton,
|
||||
"videothumbnail.editnickname",
|
||||
"top");
|
||||
editButton.innerHTML = '<i class="fa fa-pencil"></i>';
|
||||
|
||||
return editButton;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the display name for the given video span id.
|
||||
*/
|
||||
LocalVideo.prototype.setDisplayName = function(displayName, key) {
|
||||
if (!this.container) {
|
||||
console.warn(
|
||||
"Unable to set displayName - " + this.videoSpanId +
|
||||
" does not exist");
|
||||
return;
|
||||
}
|
||||
|
||||
var nameSpan = $('#' + this.videoSpanId + '>span.displayname');
|
||||
var defaultLocalDisplayName = APP.translation.generateTranslationHTML(
|
||||
interfaceConfig.DEFAULT_LOCAL_DISPLAY_NAME);
|
||||
|
||||
var meHTML;
|
||||
// If we already have a display name for this video.
|
||||
if (nameSpan.length > 0) {
|
||||
if (nameSpan.text() !== displayName) {
|
||||
if (displayName && displayName.length > 0) {
|
||||
meHTML = APP.translation.generateTranslationHTML("me");
|
||||
$('#localDisplayName').html(displayName + ' (' + meHTML + ')');
|
||||
} else {
|
||||
$('#localDisplayName').html(defaultLocalDisplayName);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var editButton = createEditDisplayNameButton();
|
||||
|
||||
nameSpan = document.createElement('span');
|
||||
nameSpan.className = 'displayname';
|
||||
$('#' + this.videoSpanId)[0].appendChild(nameSpan);
|
||||
|
||||
|
||||
if (displayName && displayName.length > 0) {
|
||||
meHTML = APP.translation.generateTranslationHTML("me");
|
||||
nameSpan.innerHTML = displayName + meHTML;
|
||||
}
|
||||
else {
|
||||
nameSpan.innerHTML = defaultLocalDisplayName;
|
||||
}
|
||||
|
||||
|
||||
nameSpan.id = 'localDisplayName';
|
||||
this.container.appendChild(editButton);
|
||||
//translates popover of edit button
|
||||
APP.translation.translateElement($("a.displayname"));
|
||||
|
||||
var editableText = document.createElement('input');
|
||||
editableText.className = 'displayname';
|
||||
editableText.type = 'text';
|
||||
editableText.id = 'editDisplayName';
|
||||
|
||||
if (displayName && displayName.length) {
|
||||
editableText.value = displayName;
|
||||
}
|
||||
|
||||
var defaultNickname = APP.translation.translateString(
|
||||
"defaultNickname", {name: "Jane Pink"});
|
||||
editableText.setAttribute('style', 'display:none;');
|
||||
editableText.setAttribute('data-18n',
|
||||
'[placeholder]defaultNickname');
|
||||
editableText.setAttribute("data-i18n-options",
|
||||
JSON.stringify({name: "Jane Pink"}));
|
||||
editableText.setAttribute("placeholder", defaultNickname);
|
||||
|
||||
this.container.appendChild(editableText);
|
||||
|
||||
var self = this;
|
||||
$('#localVideoContainer .displayname')
|
||||
.bind("click", function (e) {
|
||||
|
||||
var editDisplayName = $('#editDisplayName');
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
$('#localDisplayName').hide();
|
||||
editDisplayName.show();
|
||||
editDisplayName.focus();
|
||||
editDisplayName.select();
|
||||
|
||||
editDisplayName.one("focusout", function (e) {
|
||||
self.VideoLayout.inputDisplayNameHandler(this.value);
|
||||
});
|
||||
|
||||
editDisplayName.on('keydown', function (e) {
|
||||
if (e.keyCode === 13) {
|
||||
e.preventDefault();
|
||||
self.VideoLayout.inputDisplayNameHandler(this.value);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
LocalVideo.prototype.inputDisplayNameHandler = function (name) {
|
||||
NicknameHandler.setNickname(name);
|
||||
|
||||
var localDisplayName = $('#localDisplayName');
|
||||
if (!localDisplayName.is(":visible")) {
|
||||
if (NicknameHandler.getNickname()) {
|
||||
var meHTML = APP.translation.generateTranslationHTML("me");
|
||||
localDisplayName.html(NicknameHandler.getNickname() + " (" +
|
||||
meHTML + ")");
|
||||
} else {
|
||||
var defaultHTML = APP.translation.generateTranslationHTML(
|
||||
interfaceConfig.DEFAULT_LOCAL_DISPLAY_NAME);
|
||||
localDisplayName .html(defaultHTML);
|
||||
}
|
||||
localDisplayName.show();
|
||||
}
|
||||
|
||||
$('#editDisplayName').hide();
|
||||
};
|
||||
|
||||
LocalVideo.prototype.createConnectionIndicator = function() {
|
||||
if(this.connectionIndicator)
|
||||
return;
|
||||
|
||||
this.connectionIndicator = new ConnectionIndicator(this, null);
|
||||
};
|
||||
|
||||
LocalVideo.prototype.changeVideo = function (stream, isMuted) {
|
||||
var self = this;
|
||||
|
||||
function localVideoClick(event) {
|
||||
// FIXME: with Temasys plugin event arg is not an event, but
|
||||
// the clicked object itself, so we have to skip this call
|
||||
if (event.stopPropagation) {
|
||||
event.stopPropagation();
|
||||
}
|
||||
self.VideoLayout.handleVideoThumbClicked(
|
||||
true,
|
||||
APP.xmpp.myResource());
|
||||
}
|
||||
|
||||
var localVideoContainerSelector = $('#localVideoContainer');
|
||||
localVideoContainerSelector.off('click');
|
||||
localVideoContainerSelector.on('click', localVideoClick);
|
||||
|
||||
if(isMuted) {
|
||||
APP.UI.setVideoMute(true);
|
||||
return;
|
||||
}
|
||||
this.flipX = stream.videoType != "screen";
|
||||
var localVideo = document.createElement('video');
|
||||
localVideo.id = 'localVideo_' +
|
||||
APP.RTC.getStreamID(stream.getOriginalStream());
|
||||
if (!RTCBrowserType.isIExplorer()) {
|
||||
localVideo.autoplay = true;
|
||||
localVideo.volume = 0; // is it required if audio is separated ?
|
||||
}
|
||||
localVideo.oncontextmenu = function () { return false; };
|
||||
|
||||
var localVideoContainer = document.getElementById('localVideoWrapper');
|
||||
// Put the new video always in front
|
||||
UIUtil.prependChild(localVideoContainer, localVideo);
|
||||
|
||||
var localVideoSelector = $('#' + localVideo.id);
|
||||
|
||||
// Add click handler to both video and video wrapper elements in case
|
||||
// there's no video.
|
||||
|
||||
// onclick has to be used with Temasys plugin
|
||||
localVideo.onclick = localVideoClick;
|
||||
|
||||
if (this.flipX) {
|
||||
localVideoSelector.addClass("flipVideoX");
|
||||
}
|
||||
|
||||
// Attach WebRTC stream
|
||||
APP.RTC.attachMediaStream(localVideoSelector, stream.getOriginalStream());
|
||||
|
||||
// Add stream ended handler
|
||||
stream.getOriginalStream().onended = function () {
|
||||
// We have to re-select after attach when Temasys plugin is used,
|
||||
// because <video> element is replaced with <object>
|
||||
localVideo = $('#' + localVideo.id)[0];
|
||||
localVideoContainer.removeChild(localVideo);
|
||||
self.VideoLayout.updateRemovedVideo(APP.xmpp.myResource());
|
||||
};
|
||||
};
|
||||
|
||||
LocalVideo.prototype.joined = function (jid) {
|
||||
this.peerJid = jid;
|
||||
};
|
||||
|
||||
LocalVideo.prototype.getResourceJid = function () {
|
||||
var myResource = APP.xmpp.myResource();
|
||||
if (!myResource) {
|
||||
console.error("Requested local resource before we're in the MUC");
|
||||
}
|
||||
return myResource;
|
||||
};
|
||||
|
||||
module.exports = LocalVideo;
|
||||
400
modules/UI/videolayout/RemoteVideo.js
Normal file
400
modules/UI/videolayout/RemoteVideo.js
Normal file
@@ -0,0 +1,400 @@
|
||||
/* global $, APP, require, Strophe, interfaceConfig */
|
||||
var ConnectionIndicator = require("./ConnectionIndicator");
|
||||
var SmallVideo = require("./SmallVideo");
|
||||
var AudioLevels = require("../audio_levels/AudioLevels");
|
||||
var LargeVideo = require("./LargeVideo");
|
||||
var Avatar = require("../avatar/Avatar");
|
||||
var RTCBrowserType = require("../../RTC/RTCBrowserType");
|
||||
var UIUtils = require("../util/UIUtil");
|
||||
|
||||
function RemoteVideo(peerJid, VideoLayout) {
|
||||
this.peerJid = peerJid;
|
||||
this.resourceJid = Strophe.getResourceFromJid(peerJid);
|
||||
this.videoSpanId = 'participant_' + this.resourceJid;
|
||||
this.VideoLayout = VideoLayout;
|
||||
this.addRemoteVideoContainer();
|
||||
this.connectionIndicator = new ConnectionIndicator(
|
||||
this, this.peerJid);
|
||||
this.setDisplayName();
|
||||
var nickfield = document.createElement('span');
|
||||
nickfield.className = "nick";
|
||||
nickfield.appendChild(document.createTextNode(this.resourceJid));
|
||||
this.container.appendChild(nickfield);
|
||||
this.bindHoverHandler();
|
||||
this.flipX = false;
|
||||
this.isLocal = false;
|
||||
}
|
||||
|
||||
RemoteVideo.prototype = Object.create(SmallVideo.prototype);
|
||||
RemoteVideo.prototype.constructor = RemoteVideo;
|
||||
|
||||
RemoteVideo.prototype.addRemoteVideoContainer = function() {
|
||||
this.container = RemoteVideo.createContainer(this.videoSpanId);
|
||||
if (APP.xmpp.isModerator())
|
||||
this.addRemoteVideoMenu();
|
||||
AudioLevels.updateAudioLevelCanvas(this.peerJid, this.VideoLayout);
|
||||
|
||||
return this.container;
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds the remote video menu element for the given <tt>jid</tt> in the
|
||||
* given <tt>parentElement</tt>.
|
||||
*
|
||||
* @param jid the jid indicating the video for which we're adding a menu.
|
||||
* @param parentElement the parent element where this menu will be added
|
||||
*/
|
||||
|
||||
if (!interfaceConfig.filmStripOnly) {
|
||||
RemoteVideo.prototype.addRemoteVideoMenu = function () {
|
||||
var spanElement = document.createElement('span');
|
||||
spanElement.className = 'remotevideomenu';
|
||||
|
||||
this.container.appendChild(spanElement);
|
||||
|
||||
var menuElement = document.createElement('i');
|
||||
menuElement.className = 'fa fa-angle-down';
|
||||
menuElement.title = 'Remote user controls';
|
||||
spanElement.appendChild(menuElement);
|
||||
|
||||
|
||||
var popupmenuElement = document.createElement('ul');
|
||||
popupmenuElement.className = 'popupmenu';
|
||||
popupmenuElement.id = 'remote_popupmenu_' + this.getResourceJid();
|
||||
spanElement.appendChild(popupmenuElement);
|
||||
|
||||
var muteMenuItem = document.createElement('li');
|
||||
var muteLinkItem = document.createElement('a');
|
||||
|
||||
var mutedIndicator = "<i style='float:left;' " +
|
||||
"class='icon-mic-disabled'></i>";
|
||||
|
||||
if (!this.isMuted) {
|
||||
muteLinkItem.innerHTML = mutedIndicator +
|
||||
" <div style='width: 90px;margin-left: 20px;' " +
|
||||
"data-i18n='videothumbnail.domute'></div>";
|
||||
muteLinkItem.className = 'mutelink';
|
||||
}
|
||||
else {
|
||||
muteLinkItem.innerHTML = mutedIndicator +
|
||||
" <div style='width: 90px;margin-left: 20px;' " +
|
||||
"data-i18n='videothumbnail.muted'></div>";
|
||||
muteLinkItem.className = 'mutelink disabled';
|
||||
}
|
||||
|
||||
var self = this;
|
||||
muteLinkItem.onclick = function(){
|
||||
if ($(this).attr('disabled') != undefined) {
|
||||
event.preventDefault();
|
||||
}
|
||||
var isMute = self.isMuted == true;
|
||||
APP.xmpp.setMute(self.peerJid, !isMute);
|
||||
|
||||
popupmenuElement.setAttribute('style', 'display:none;');
|
||||
|
||||
if (isMute) {
|
||||
this.innerHTML = mutedIndicator +
|
||||
" <div style='width: 90px;margin-left: 20px;' " +
|
||||
"data-i18n='videothumbnail.muted'></div>";
|
||||
this.className = 'mutelink disabled';
|
||||
}
|
||||
else {
|
||||
this.innerHTML = mutedIndicator +
|
||||
" <div style='width: 90px;margin-left: 20px;' " +
|
||||
"data-i18n='videothumbnail.domute'></div>";
|
||||
this.className = 'mutelink';
|
||||
}
|
||||
};
|
||||
|
||||
muteMenuItem.appendChild(muteLinkItem);
|
||||
popupmenuElement.appendChild(muteMenuItem);
|
||||
|
||||
var ejectIndicator = "<i style='float:left;' class='fa fa-eject'></i>";
|
||||
|
||||
var ejectMenuItem = document.createElement('li');
|
||||
var ejectLinkItem = document.createElement('a');
|
||||
var ejectText = "<div style='width: 90px;margin-left: 20px;' " +
|
||||
"data-i18n='videothumbnail.kick'> </div>";
|
||||
ejectLinkItem.innerHTML = ejectIndicator + ' ' + ejectText;
|
||||
ejectLinkItem.onclick = function(){
|
||||
APP.xmpp.eject(self.peerJid);
|
||||
popupmenuElement.setAttribute('style', 'display:none;');
|
||||
};
|
||||
|
||||
ejectMenuItem.appendChild(ejectLinkItem);
|
||||
popupmenuElement.appendChild(ejectMenuItem);
|
||||
|
||||
var paddingSpan = document.createElement('span');
|
||||
paddingSpan.className = 'popupmenuPadding';
|
||||
popupmenuElement.appendChild(paddingSpan);
|
||||
APP.translation.translateElement(
|
||||
$("#" + popupmenuElement.id + " > li > a > div"));
|
||||
};
|
||||
|
||||
} else {
|
||||
RemoteVideo.prototype.addRemoteVideoMenu = function() {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the remote stream element corresponding to the given stream and
|
||||
* parent container.
|
||||
*
|
||||
* @param stream the stream
|
||||
* @param isVideo <tt>true</tt> if given <tt>stream</tt> is a video one.
|
||||
*/
|
||||
RemoteVideo.prototype.removeRemoteStreamElement =
|
||||
function (stream, isVideo, id) {
|
||||
if (!this.container)
|
||||
return false;
|
||||
|
||||
var select = null;
|
||||
if (isVideo) {
|
||||
select = $('#' + id);
|
||||
}
|
||||
else
|
||||
select = $('#' + this.videoSpanId + '>audio');
|
||||
|
||||
select.remove();
|
||||
|
||||
console.info((isVideo ? "Video" : "Audio") +
|
||||
" removed " + this.getResourceJid(), select);
|
||||
|
||||
if (isVideo)
|
||||
this.VideoLayout.updateRemovedVideo(this.getResourceJid());
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes RemoteVideo from the page.
|
||||
*/
|
||||
RemoteVideo.prototype.remove = function () {
|
||||
console.log("Remove thumbnail", this.peerJid);
|
||||
this.removeConnectionIndicator();
|
||||
// Remove whole container
|
||||
if (this.container.parentNode)
|
||||
this.container.parentNode.removeChild(this.container);
|
||||
};
|
||||
|
||||
RemoteVideo.prototype.waitForPlayback = function (sel, stream) {
|
||||
|
||||
var isVideo = stream.getVideoTracks().length > 0;
|
||||
if (!isVideo || stream.id === 'mixedmslabel') {
|
||||
return;
|
||||
}
|
||||
|
||||
var self = this;
|
||||
var resourceJid = this.getResourceJid();
|
||||
|
||||
// Register 'onplaying' listener to trigger 'videoactive' on VideoLayout
|
||||
// when video playback starts
|
||||
var onPlayingHandler = function () {
|
||||
// FIXME: why do i have to do this for FF?
|
||||
if (RTCBrowserType.isFirefox()) {
|
||||
APP.RTC.attachMediaStream(sel, stream);
|
||||
}
|
||||
if (RTCBrowserType.isTemasysPluginUsed()) {
|
||||
sel = self.selectVideoElement();
|
||||
}
|
||||
self.VideoLayout.videoactive(sel, resourceJid);
|
||||
sel[0].onplaying = null;
|
||||
if (RTCBrowserType.isTemasysPluginUsed()) {
|
||||
// 'currentTime' is used to check if the video has started
|
||||
// and the value is not set by the plugin, so we do it
|
||||
sel[0].currentTime = 1;
|
||||
}
|
||||
};
|
||||
sel[0].onplaying = onPlayingHandler;
|
||||
};
|
||||
|
||||
RemoteVideo.prototype.addRemoteStreamElement = function (sid, stream, thessrc) {
|
||||
if (!this.container)
|
||||
return;
|
||||
|
||||
var self = this;
|
||||
var isVideo = stream.getVideoTracks().length > 0;
|
||||
var streamElement = SmallVideo.createStreamElement(sid, stream);
|
||||
var newElementId = streamElement.id;
|
||||
|
||||
// Put new stream element always in front
|
||||
UIUtils.prependChild(this.container, streamElement);
|
||||
|
||||
var sel = $('#' + newElementId);
|
||||
sel.hide();
|
||||
|
||||
// If the container is currently visible we attach the stream.
|
||||
if (!isVideo || (this.container.offsetParent !== null && isVideo)) {
|
||||
this.waitForPlayback(sel, stream);
|
||||
|
||||
APP.RTC.attachMediaStream(sel, stream);
|
||||
}
|
||||
|
||||
stream.onended = function () {
|
||||
console.log('stream ended', this);
|
||||
|
||||
self.removeRemoteStreamElement(stream, isVideo, newElementId);
|
||||
|
||||
};
|
||||
|
||||
// Add click handler.
|
||||
var onClickHandler = function (event) {
|
||||
|
||||
self.VideoLayout.handleVideoThumbClicked(false, self.getResourceJid());
|
||||
|
||||
// On IE we need to populate this handler on video <object>
|
||||
// and it does not give event instance as an argument,
|
||||
// so we check here for methods.
|
||||
if (event.stopPropagation && event.preventDefault) {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
}
|
||||
return false;
|
||||
};
|
||||
this.container.onclick = onClickHandler;
|
||||
// reselect
|
||||
if (RTCBrowserType.isTemasysPluginUsed())
|
||||
sel = $('#' + newElementId);
|
||||
sel[0].onclick = onClickHandler;
|
||||
},
|
||||
|
||||
/**
|
||||
* Show/hide peer container for the given resourceJid.
|
||||
*/
|
||||
RemoteVideo.prototype.showPeerContainer = function (state) {
|
||||
if (!this.container)
|
||||
return;
|
||||
|
||||
var isHide = state === 'hide';
|
||||
var resizeThumbnails = false;
|
||||
|
||||
if (!isHide) {
|
||||
if (!$(this.container).is(':visible')) {
|
||||
resizeThumbnails = true;
|
||||
$(this.container).show();
|
||||
}
|
||||
|
||||
this.showAvatar(state !== 'show');
|
||||
}
|
||||
else if ($(this.container).is(':visible') && isHide)
|
||||
{
|
||||
resizeThumbnails = true;
|
||||
$(this.container).hide();
|
||||
if(this.connectionIndicator)
|
||||
this.connectionIndicator.hide();
|
||||
}
|
||||
|
||||
if (resizeThumbnails) {
|
||||
this.VideoLayout.resizeThumbnails();
|
||||
}
|
||||
|
||||
// We want to be able to pin a participant from the contact list, even
|
||||
// if he's not in the lastN set!
|
||||
// ContactList.setClickable(resourceJid, !isHide);
|
||||
|
||||
};
|
||||
|
||||
RemoteVideo.prototype.removeConnectionIndicator = function () {
|
||||
if (this.connectionIndicator)
|
||||
this.connectionIndicator.remove();
|
||||
};
|
||||
|
||||
RemoteVideo.prototype.hideConnectionIndicator = function () {
|
||||
if (this.connectionIndicator)
|
||||
this.connectionIndicator.hide();
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates the remote video menu.
|
||||
*
|
||||
* @param jid the jid indicating the video for which we're adding a menu.
|
||||
* @param isMuted indicates the current mute state
|
||||
*/
|
||||
RemoteVideo.prototype.updateRemoteVideoMenu = function (isMuted) {
|
||||
var muteMenuItem
|
||||
= $('#remote_popupmenu_' + this.getResourceJid() + '>li>a.mutelink');
|
||||
|
||||
var mutedIndicator = "<i class='icon-mic-disabled'></i>";
|
||||
|
||||
if (muteMenuItem.length) {
|
||||
var muteLink = muteMenuItem.get(0);
|
||||
|
||||
if (isMuted === 'true') {
|
||||
muteLink.innerHTML = mutedIndicator + ' Muted';
|
||||
muteLink.className = 'mutelink disabled';
|
||||
}
|
||||
else {
|
||||
muteLink.innerHTML = mutedIndicator + ' Mute';
|
||||
muteLink.className = 'mutelink';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the display name for the given video span id.
|
||||
*/
|
||||
RemoteVideo.prototype.setDisplayName = function(displayName, key) {
|
||||
|
||||
if (!this.container) {
|
||||
console.warn( "Unable to set displayName - " + this.videoSpanId +
|
||||
" does not exist");
|
||||
return;
|
||||
}
|
||||
|
||||
var nameSpan = $('#' + this.videoSpanId + '>span.displayname');
|
||||
|
||||
// If we already have a display name for this video.
|
||||
if (nameSpan.length > 0) {
|
||||
if (displayName && displayName.length > 0) {
|
||||
$('#' + this.videoSpanId + '_name').html(displayName);
|
||||
}
|
||||
else if (key && key.length > 0) {
|
||||
var nameHtml = APP.translation.generateTranslationHTML(key);
|
||||
$('#' + this.videoSpanId + '_name').html(nameHtml);
|
||||
}
|
||||
else
|
||||
$('#' + this.videoSpanId + '_name').text(
|
||||
interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME);
|
||||
} else {
|
||||
nameSpan = document.createElement('span');
|
||||
nameSpan.className = 'displayname';
|
||||
$('#' + this.videoSpanId)[0].appendChild(nameSpan);
|
||||
|
||||
|
||||
if (displayName && displayName.length > 0) {
|
||||
nameSpan.innerText = displayName;
|
||||
}
|
||||
else
|
||||
nameSpan.innerText = interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME;
|
||||
|
||||
nameSpan.id = this.videoSpanId + '_name';
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes remote video menu element from video element identified by
|
||||
* given <tt>videoElementId</tt>.
|
||||
*
|
||||
* @param videoElementId the id of local or remote video element.
|
||||
*/
|
||||
RemoteVideo.prototype.removeRemoteVideoMenu = function() {
|
||||
var menuSpan = $('#' + this.videoSpanId + '>span.remotevideomenu');
|
||||
if (menuSpan.length) {
|
||||
menuSpan.remove();
|
||||
}
|
||||
};
|
||||
|
||||
RemoteVideo.prototype.getResourceJid = function () {
|
||||
if (!this.resourceJid) {
|
||||
console.error("Undefined resource jid");
|
||||
}
|
||||
return this.resourceJid;
|
||||
};
|
||||
|
||||
RemoteVideo.createContainer = function (spanId) {
|
||||
var container = document.createElement('span');
|
||||
container.id = spanId;
|
||||
container.className = 'videocontainer';
|
||||
var remotes = document.getElementById('remoteVideos');
|
||||
return remotes.appendChild(container);
|
||||
};
|
||||
|
||||
|
||||
module.exports = RemoteVideo;
|
||||
398
modules/UI/videolayout/SmallVideo.js
Normal file
398
modules/UI/videolayout/SmallVideo.js
Normal file
@@ -0,0 +1,398 @@
|
||||
/* global $, APP, require */
|
||||
var Avatar = require("../avatar/Avatar");
|
||||
var UIUtil = require("../util/UIUtil");
|
||||
var LargeVideo = require("./LargeVideo");
|
||||
var RTCBrowserType = require("../../RTC/RTCBrowserType");
|
||||
|
||||
function SmallVideo() {
|
||||
this.isMuted = false;
|
||||
this.hasAvatar = false;
|
||||
}
|
||||
|
||||
function setVisibility(selector, show) {
|
||||
if (selector && selector.length > 0) {
|
||||
selector.css("visibility", show ? "visible" : "hidden");
|
||||
}
|
||||
}
|
||||
|
||||
SmallVideo.prototype.showDisplayName = function(isShow) {
|
||||
var nameSpan = $('#' + this.videoSpanId + '>span.displayname').get(0);
|
||||
if (isShow) {
|
||||
if (nameSpan && nameSpan.innerHTML && nameSpan.innerHTML.length)
|
||||
nameSpan.setAttribute("style", "display:inline-block;");
|
||||
}
|
||||
else {
|
||||
if (nameSpan)
|
||||
nameSpan.setAttribute("style", "display:none;");
|
||||
}
|
||||
};
|
||||
|
||||
SmallVideo.prototype.setDeviceAvailabilityIcons = function (devices) {
|
||||
if(!this.container)
|
||||
return;
|
||||
|
||||
var noMic = $("#" + this.videoSpanId + " > .noMic");
|
||||
var noVideo = $("#" + this.videoSpanId + " > .noVideo");
|
||||
|
||||
noMic.remove();
|
||||
noVideo.remove();
|
||||
if (!devices.audio) {
|
||||
this.container.appendChild(
|
||||
document.createElement("div")).setAttribute("class", "noMic");
|
||||
}
|
||||
|
||||
if (!devices.video) {
|
||||
this.container.appendChild(
|
||||
document.createElement("div")).setAttribute("class", "noVideo");
|
||||
}
|
||||
|
||||
if (!devices.audio && !devices.video) {
|
||||
noMic.css("background-position", "75%");
|
||||
noVideo.css("background-position", "25%");
|
||||
noVideo.css("background-color", "transparent");
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the type of the video displayed by this instance.
|
||||
* @param videoType 'camera' or 'screen'
|
||||
*/
|
||||
SmallVideo.prototype.setVideoType = function (videoType) {
|
||||
this.videoType = videoType;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the type of the video displayed by this instance.
|
||||
* @returns {String} 'camera', 'screen' or undefined.
|
||||
*/
|
||||
SmallVideo.prototype.getVideoType = function () {
|
||||
return this.videoType;
|
||||
};
|
||||
|
||||
/**
|
||||
* Shows the presence status message for the given video.
|
||||
*/
|
||||
SmallVideo.prototype.setPresenceStatus = function (statusMsg) {
|
||||
if (!this.container) {
|
||||
// No container
|
||||
return;
|
||||
}
|
||||
|
||||
var statusSpan = $('#' + this.videoSpanId + '>span.status');
|
||||
if (!statusSpan.length) {
|
||||
//Add status span
|
||||
statusSpan = document.createElement('span');
|
||||
statusSpan.className = 'status';
|
||||
statusSpan.id = this.videoSpanId + '_status';
|
||||
$('#' + this.videoSpanId)[0].appendChild(statusSpan);
|
||||
|
||||
statusSpan = $('#' + this.videoSpanId + '>span.status');
|
||||
}
|
||||
|
||||
// Display status
|
||||
if (statusMsg && statusMsg.length) {
|
||||
$('#' + this.videoSpanId + '_status').text(statusMsg);
|
||||
statusSpan.get(0).setAttribute("style", "display:inline-block;");
|
||||
}
|
||||
else {
|
||||
// Hide
|
||||
statusSpan.get(0).setAttribute("style", "display:none;");
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates an audio or video stream element.
|
||||
*/
|
||||
SmallVideo.createStreamElement = function (sid, stream) {
|
||||
var isVideo = stream.getVideoTracks().length > 0;
|
||||
|
||||
var element = isVideo ? document.createElement('video')
|
||||
: document.createElement('audio');
|
||||
var id = (isVideo ? 'remoteVideo_' : 'remoteAudio_') + sid + '_' +
|
||||
APP.RTC.getStreamID(stream);
|
||||
|
||||
element.id = id;
|
||||
if (!RTCBrowserType.isIExplorer()) {
|
||||
element.autoplay = true;
|
||||
}
|
||||
element.oncontextmenu = function () { return false; };
|
||||
|
||||
return element;
|
||||
};
|
||||
|
||||
/**
|
||||
* Configures hoverIn/hoverOut handlers.
|
||||
*/
|
||||
SmallVideo.prototype.bindHoverHandler = function () {
|
||||
// Add hover handler
|
||||
var self = this;
|
||||
$(this.container).hover(
|
||||
function () {
|
||||
self.showDisplayName(true);
|
||||
},
|
||||
function () {
|
||||
// If the video has been "pinned" by the user we want to
|
||||
// keep the display name on place.
|
||||
if (!LargeVideo.isLargeVideoVisible() ||
|
||||
!LargeVideo.isCurrentlyOnLarge(self.getResourceJid()))
|
||||
self.showDisplayName(false);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates the data for the indicator
|
||||
* @param id the id of the indicator
|
||||
* @param percent the percent for connection quality
|
||||
* @param object the data
|
||||
*/
|
||||
SmallVideo.prototype.updateStatsIndicator = function (percent, object) {
|
||||
if(this.connectionIndicator)
|
||||
this.connectionIndicator.updateConnectionQuality(percent, object);
|
||||
};
|
||||
|
||||
SmallVideo.prototype.hideIndicator = function () {
|
||||
if(this.connectionIndicator)
|
||||
this.connectionIndicator.hideIndicator();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Shows audio muted indicator over small videos.
|
||||
* @param {string} isMuted
|
||||
*/
|
||||
SmallVideo.prototype.showAudioIndicator = function(isMuted) {
|
||||
var audioMutedSpan = $('#' + this.videoSpanId + '>span.audioMuted');
|
||||
|
||||
if (!isMuted) {
|
||||
if (audioMutedSpan.length > 0) {
|
||||
audioMutedSpan.popover('hide');
|
||||
audioMutedSpan.remove();
|
||||
}
|
||||
}
|
||||
else {
|
||||
if(audioMutedSpan.length == 0 ) {
|
||||
audioMutedSpan = document.createElement('span');
|
||||
audioMutedSpan.className = 'audioMuted';
|
||||
UIUtil.setTooltip(audioMutedSpan,
|
||||
"videothumbnail.mute",
|
||||
"top");
|
||||
|
||||
this.container.appendChild(audioMutedSpan);
|
||||
APP.translation.translateElement($('#' + this.videoSpanId + " > span"));
|
||||
var mutedIndicator = document.createElement('i');
|
||||
mutedIndicator.className = 'icon-mic-disabled';
|
||||
audioMutedSpan.appendChild(mutedIndicator);
|
||||
|
||||
}
|
||||
this.updateIconPositions();
|
||||
}
|
||||
this.isMuted = isMuted;
|
||||
};
|
||||
|
||||
/**
|
||||
* Shows video muted indicator over small videos.
|
||||
*/
|
||||
SmallVideo.prototype.showVideoIndicator = function(isMuted) {
|
||||
this.showAvatar(isMuted);
|
||||
|
||||
var videoMutedSpan = $('#' + this.videoSpanId + '>span.videoMuted');
|
||||
|
||||
if (isMuted === false) {
|
||||
if (videoMutedSpan.length > 0) {
|
||||
videoMutedSpan.remove();
|
||||
}
|
||||
}
|
||||
else {
|
||||
if(videoMutedSpan.length == 0) {
|
||||
videoMutedSpan = document.createElement('span');
|
||||
videoMutedSpan.className = 'videoMuted';
|
||||
|
||||
this.container.appendChild(videoMutedSpan);
|
||||
|
||||
var mutedIndicator = document.createElement('i');
|
||||
mutedIndicator.className = 'icon-camera-disabled';
|
||||
UIUtil.setTooltip(mutedIndicator,
|
||||
"videothumbnail.videomute",
|
||||
"top");
|
||||
videoMutedSpan.appendChild(mutedIndicator);
|
||||
//translate texts for muted indicator
|
||||
APP.translation.translateElement($('#' + this.videoSpanId + " > span > i"));
|
||||
}
|
||||
|
||||
this.updateIconPositions();
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
SmallVideo.prototype.enableDominantSpeaker = function (isEnable) {
|
||||
var resourceJid = this.getResourceJid();
|
||||
var displayName = resourceJid;
|
||||
var nameSpan = $('#' + this.videoSpanId + '>span.displayname');
|
||||
if (nameSpan.length > 0)
|
||||
displayName = nameSpan.html();
|
||||
|
||||
console.log("UI enable dominant speaker",
|
||||
displayName,
|
||||
resourceJid,
|
||||
isEnable);
|
||||
|
||||
|
||||
if (!this.container) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isEnable) {
|
||||
this.showDisplayName(LargeVideo.getState() === "video");
|
||||
|
||||
if (!this.container.classList.contains("dominantspeaker"))
|
||||
this.container.classList.add("dominantspeaker");
|
||||
}
|
||||
else {
|
||||
this.showDisplayName(false);
|
||||
|
||||
if (this.container.classList.contains("dominantspeaker"))
|
||||
this.container.classList.remove("dominantspeaker");
|
||||
}
|
||||
|
||||
this.showAvatar();
|
||||
};
|
||||
|
||||
SmallVideo.prototype.updateIconPositions = function () {
|
||||
var audioMutedSpan = $('#' + this.videoSpanId + '>span.audioMuted');
|
||||
var connectionIndicator = $('#' + this.videoSpanId + '>div.connectionindicator');
|
||||
var videoMutedSpan = $('#' + this.videoSpanId + '>span.videoMuted');
|
||||
if(connectionIndicator.length > 0 &&
|
||||
connectionIndicator[0].style.display != "none") {
|
||||
audioMutedSpan.css({right: "23px"});
|
||||
videoMutedSpan.css({right: ((audioMutedSpan.length > 0? 23 : 0) + 30) + "px"});
|
||||
} else {
|
||||
audioMutedSpan.css({right: "0px"});
|
||||
videoMutedSpan.css({right: (audioMutedSpan.length > 0? 30 : 0) + "px"});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates the element indicating the moderator(owner) of the conference.
|
||||
*
|
||||
* @param parentElement the parent element where the owner indicator will
|
||||
* be added
|
||||
*/
|
||||
SmallVideo.prototype.createModeratorIndicatorElement = function () {
|
||||
// Show moderator indicator
|
||||
var indicatorSpan = $('#' + this.videoSpanId + ' .focusindicator');
|
||||
|
||||
if (!indicatorSpan || indicatorSpan.length === 0) {
|
||||
indicatorSpan = document.createElement('span');
|
||||
indicatorSpan.className = 'focusindicator';
|
||||
|
||||
this.container.appendChild(indicatorSpan);
|
||||
indicatorSpan = $('#' + this.videoSpanId + ' .focusindicator');
|
||||
}
|
||||
|
||||
if (indicatorSpan.children().length !== 0)
|
||||
return;
|
||||
var moderatorIndicator = document.createElement('i');
|
||||
moderatorIndicator.className = 'fa fa-star';
|
||||
indicatorSpan[0].appendChild(moderatorIndicator);
|
||||
|
||||
UIUtil.setTooltip(indicatorSpan[0],
|
||||
"videothumbnail.moderator",
|
||||
"top");
|
||||
|
||||
//translates text in focus indicators
|
||||
APP.translation.translateElement($('#' + this.videoSpanId + ' .focusindicator'));
|
||||
};
|
||||
|
||||
SmallVideo.prototype.selectVideoElement = function () {
|
||||
var videoElem = APP.RTC.getVideoElementName();
|
||||
if (!RTCBrowserType.isTemasysPluginUsed()) {
|
||||
return $('#' + this.videoSpanId).find(videoElem);
|
||||
} else {
|
||||
return $('#' + this.videoSpanId +
|
||||
(this.isLocal ? '>>' : '>') +
|
||||
videoElem + '>param[value="video"]').parent();
|
||||
}
|
||||
};
|
||||
|
||||
SmallVideo.prototype.getSrc = function () {
|
||||
var videoElement = this.selectVideoElement().get(0);
|
||||
return APP.RTC.getVideoSrc(videoElement);
|
||||
};
|
||||
|
||||
SmallVideo.prototype.focus = function(isFocused) {
|
||||
if(!isFocused) {
|
||||
this.container.classList.remove("videoContainerFocused");
|
||||
} else {
|
||||
this.container.classList.add("videoContainerFocused");
|
||||
}
|
||||
};
|
||||
|
||||
SmallVideo.prototype.hasVideo = function () {
|
||||
return this.selectVideoElement().length !== 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Hides or shows the user's avatar
|
||||
* @param show whether we should show the avatar or not
|
||||
* video because there is no dominant speaker and no focused speaker
|
||||
*/
|
||||
SmallVideo.prototype.showAvatar = function (show) {
|
||||
if (!this.hasAvatar) {
|
||||
if (this.peerJid) {
|
||||
// Init avatar
|
||||
this.avatarChanged(Avatar.getThumbUrl(this.peerJid));
|
||||
} else {
|
||||
console.error("Unable to init avatar - no peerjid", this);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var resourceJid = this.getResourceJid();
|
||||
var video = this.selectVideoElement();
|
||||
|
||||
var avatar = $('#avatar_' + resourceJid);
|
||||
|
||||
if (show === undefined || show === null) {
|
||||
if (!this.isLocal &&
|
||||
!this.VideoLayout.isInLastN(resourceJid)) {
|
||||
show = true;
|
||||
} else {
|
||||
// We want to show the avatar when the video is muted or not exists
|
||||
// that is when 'true' or 'null' is returned
|
||||
show = APP.RTC.isVideoMuted(this.peerJid) !== false;
|
||||
}
|
||||
}
|
||||
|
||||
if (LargeVideo.showAvatar(resourceJid, show)) {
|
||||
setVisibility(avatar, false);
|
||||
setVisibility(video, false);
|
||||
} else {
|
||||
if (video && video.length > 0) {
|
||||
setVisibility(video, !show);
|
||||
}
|
||||
setVisibility(avatar, show);
|
||||
}
|
||||
};
|
||||
|
||||
SmallVideo.prototype.avatarChanged = function (thumbUrl) {
|
||||
var thumbnail = $('#' + this.videoSpanId);
|
||||
var resourceJid = this.getResourceJid();
|
||||
var avatar = $('#avatar_' + resourceJid);
|
||||
this.hasAvatar = true;
|
||||
|
||||
// set the avatar in the thumbnail
|
||||
if (avatar && avatar.length > 0) {
|
||||
avatar[0].src = thumbUrl;
|
||||
} else {
|
||||
if (thumbnail && thumbnail.length > 0) {
|
||||
avatar = document.createElement('img');
|
||||
avatar.id = 'avatar_' + resourceJid;
|
||||
avatar.className = 'userAvatar';
|
||||
avatar.src = thumbUrl;
|
||||
thumbnail.append(avatar);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = SmallVideo;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -49,7 +49,8 @@ var adverbs = [
|
||||
"Obviously", "Often", "Painfully", "Patiently", "Playfully", "Politely", "Poorly", "Precisely", "Promptly",
|
||||
"Quickly", "Quietly", "Randomly", "Rapidly", "Rarely", "Recklessly", "Regularly", "Remorsefully", "Responsibly",
|
||||
"Rudely", "Ruthlessly", "Sadly", "Scornfully", "Seamlessly", "Seldom", "Selfishly", "Seriously", "Shakily",
|
||||
"Sharply", "Sideways", "Silently", "Sleepily", "Slightly", "Slowly", "Slyly", "Smoothly", "Softly", "Solemnly", "Steadily", "Sternly", "Strangely", "Strongly", "Stunningly", "Surely", "Tenderly", "Thoughtfully",
|
||||
"Sharply", "Sideways", "Silently", "Sleepily", "Slightly", "Slowly", "Slyly", "Smoothly", "Softly", "Solemnly",
|
||||
"Steadily", "Sternly", "Strangely", "Strongly", "Stunningly", "Surely", "Tenderly", "Thoughtfully",
|
||||
"Tightly", "Uneasily", "Vanishingly", "Violently", "Warmly", "Weakly", "Wearily", "Weekly", "Weirdly", "Well",
|
||||
"Well", "Wickedly", "Wildly", "Wisely", "Wonderfully", "Yearly"
|
||||
];
|
||||
@@ -137,8 +138,7 @@ var PATTERNS = [
|
||||
/*
|
||||
* Returns a random element from the array 'arr'
|
||||
*/
|
||||
function randomElement(arr)
|
||||
{
|
||||
function randomElement(arr) {
|
||||
return arr[Math.floor(Math.random() * arr.length)];
|
||||
}
|
||||
|
||||
@@ -146,8 +146,7 @@ function randomElement(arr)
|
||||
* Returns true if the string 's' contains one of the
|
||||
* template strings.
|
||||
*/
|
||||
function hasTemplate(s)
|
||||
{
|
||||
function hasTemplate(s) {
|
||||
for (var template in CATEGORIES){
|
||||
if (s.indexOf(template) >= 0){
|
||||
return true;
|
||||
@@ -159,14 +158,15 @@ function hasTemplate(s)
|
||||
* Generates new room name.
|
||||
*/
|
||||
var RoomNameGenerator = {
|
||||
generateRoomWithoutSeparator: function()
|
||||
{
|
||||
// Note that if more than one pattern is available, the choice of 'name' won't be random (names from patterns
|
||||
// with fewer options will have higher probability of being chosen that names from patterns with more options).
|
||||
generateRoomWithoutSeparator: function() {
|
||||
// Note that if more than one pattern is available, the choice of
|
||||
// 'name' won't have a uniform distribution amongst all patterns (names
|
||||
// from patterns with fewer options will have higher probability of
|
||||
// being chosen that names from patterns with more options).
|
||||
var name = randomElement(PATTERNS);
|
||||
var word;
|
||||
while (hasTemplate(name)){
|
||||
for (var template in CATEGORIES){
|
||||
while (hasTemplate(name)) {
|
||||
for (var template in CATEGORIES) {
|
||||
word = randomElement(CATEGORIES[template]);
|
||||
name = name.replace(template, word);
|
||||
}
|
||||
@@ -174,6 +174,6 @@ var RoomNameGenerator = {
|
||||
|
||||
return name;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = RoomNameGenerator;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
/* global $, interfaceConfig */
|
||||
var animateTimeout, updateTimeout;
|
||||
|
||||
var RoomNameGenerator = require("./RoomnameGenerator");
|
||||
|
||||
function enter_room()
|
||||
{
|
||||
function enter_room() {
|
||||
var val = $("#enter_room_field").val();
|
||||
if(!val) {
|
||||
val = $("#enter_room_field").attr("room_name");
|
||||
@@ -21,8 +21,7 @@ function animate(word) {
|
||||
}, 70);
|
||||
}
|
||||
|
||||
function update_roomname()
|
||||
{
|
||||
function update_roomname() {
|
||||
var word = RoomNameGenerator.generateRoomWithoutSeparator();
|
||||
$("#enter_room_field").attr("room_name", word);
|
||||
$("#enter_room_field").attr("placeholder", "");
|
||||
@@ -31,33 +30,30 @@ function update_roomname()
|
||||
updateTimeout = setTimeout(update_roomname, 10000);
|
||||
}
|
||||
|
||||
|
||||
function setupWelcomePage()
|
||||
{
|
||||
function setupWelcomePage() {
|
||||
$("#videoconference_page").hide();
|
||||
$("#domain_name").text(
|
||||
window.location.protocol + "//" + window.location.host + "/");
|
||||
if (interfaceConfig.SHOW_JITSI_WATERMARK) {
|
||||
var leftWatermarkDiv
|
||||
= $("#welcome_page_header div[class='watermark leftwatermark']");
|
||||
if(leftWatermarkDiv && leftWatermarkDiv.length > 0)
|
||||
{
|
||||
var leftWatermarkDiv =
|
||||
$("#welcome_page_header div[class='watermark leftwatermark']");
|
||||
if(leftWatermarkDiv && leftWatermarkDiv.length > 0) {
|
||||
leftWatermarkDiv.css({display: 'block'});
|
||||
leftWatermarkDiv.parent().get(0).href
|
||||
= interfaceConfig.JITSI_WATERMARK_LINK;
|
||||
leftWatermarkDiv.parent().get(0).href =
|
||||
interfaceConfig.JITSI_WATERMARK_LINK;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (interfaceConfig.SHOW_BRAND_WATERMARK) {
|
||||
var rightWatermarkDiv
|
||||
= $("#welcome_page_header div[class='watermark rightwatermark']");
|
||||
var rightWatermarkDiv =
|
||||
$("#welcome_page_header div[class='watermark rightwatermark']");
|
||||
if(rightWatermarkDiv && rightWatermarkDiv.length > 0) {
|
||||
rightWatermarkDiv.css({display: 'block'});
|
||||
rightWatermarkDiv.parent().get(0).href
|
||||
= interfaceConfig.BRAND_WATERMARK_LINK;
|
||||
rightWatermarkDiv.get(0).style.backgroundImage
|
||||
= "url(images/rightwatermark.png)";
|
||||
rightWatermarkDiv.parent().get(0).href =
|
||||
interfaceConfig.BRAND_WATERMARK_LINK;
|
||||
rightWatermarkDiv.get(0).style.backgroundImage =
|
||||
"url(images/rightwatermark.png)";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,8 +62,7 @@ function setupWelcomePage()
|
||||
.css({display: 'block'});
|
||||
}
|
||||
|
||||
$("#enter_room_button").click(function()
|
||||
{
|
||||
$("#enter_room_button").click(function() {
|
||||
enter_room();
|
||||
});
|
||||
|
||||
@@ -77,23 +72,23 @@ function setupWelcomePage()
|
||||
}
|
||||
});
|
||||
|
||||
if (!(interfaceConfig.GENERATE_ROOMNAMES_ON_WELCOME_PAGE === false)){
|
||||
if (interfaceConfig.GENERATE_ROOMNAMES_ON_WELCOME_PAGE !== false) {
|
||||
var updateTimeout;
|
||||
var animateTimeout;
|
||||
$("#reload_roomname").click(function () {
|
||||
var selector = $("#reload_roomname");
|
||||
selector.click(function () {
|
||||
clearTimeout(updateTimeout);
|
||||
clearTimeout(animateTimeout);
|
||||
update_roomname();
|
||||
});
|
||||
$("#reload_roomname").show();
|
||||
|
||||
selector.show();
|
||||
|
||||
update_roomname();
|
||||
}
|
||||
|
||||
$("#disable_welcome").click(function () {
|
||||
window.localStorage.welcomePageDisabled
|
||||
= $("#disable_welcome").is(":checked");
|
||||
window.localStorage.welcomePageDisabled =
|
||||
$("#disable_welcome").is(":checked");
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
55
modules/config/HttpConfigFetch.js
Normal file
55
modules/config/HttpConfigFetch.js
Normal file
@@ -0,0 +1,55 @@
|
||||
/* global $, $iq, config, interfaceConfig */
|
||||
|
||||
var configUtil = require('./Util');
|
||||
|
||||
var HttpConfig = {
|
||||
/**
|
||||
* Sends HTTP POST request to specified <tt>endpoint</tt>. In request
|
||||
* the name of the room is included in JSON format:
|
||||
* {
|
||||
* "rooomName": "someroom12345"
|
||||
* }
|
||||
* @param endpoint the name of HTTP endpoint to which HTTP POST request will
|
||||
* be sent.
|
||||
* @param roomName the name of the conference room for which config will be
|
||||
* requested.
|
||||
* @param complete
|
||||
*/
|
||||
obtainConfig: function (endpoint, roomName, complete) {
|
||||
console.info(
|
||||
"Send config request to " + endpoint + " for room: " + roomName);
|
||||
|
||||
var request = new XMLHttpRequest();
|
||||
var error = null;
|
||||
request.onreadystatechange = function (aEvt) {
|
||||
if (request.readyState == 4) {
|
||||
var status = request.status;
|
||||
if (status === 200) {
|
||||
try {
|
||||
var data = JSON.parse(request.responseText);
|
||||
configUtil.overrideConfigJSON(
|
||||
config, interfaceConfig, data);
|
||||
complete(true);
|
||||
return;
|
||||
} catch (exception) {
|
||||
console.error("Parse config error: ", exception);
|
||||
error = exception;
|
||||
}
|
||||
} else {
|
||||
console.error("Get config error: ", request, status);
|
||||
error = "Get config response status: " + status;
|
||||
}
|
||||
complete(false, error);
|
||||
}
|
||||
};
|
||||
|
||||
request.open("POST", endpoint, true);
|
||||
|
||||
request.setRequestHeader(
|
||||
"Content-Type", "application/json;charset=UTF-8");
|
||||
|
||||
request.send({ "roomName": roomName });
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = HttpConfig;
|
||||
65
modules/config/URLProcessor.js
Normal file
65
modules/config/URLProcessor.js
Normal file
@@ -0,0 +1,65 @@
|
||||
/* global $, $iq, config, interfaceConfig */
|
||||
var configUtils = require('./Util');
|
||||
var params = {};
|
||||
function getConfigParamsFromUrl() {
|
||||
if (!location.hash)
|
||||
return {};
|
||||
var hash = location.hash.substr(1);
|
||||
var result = {};
|
||||
hash.split("&").forEach(function (part) {
|
||||
var item = part.split("=");
|
||||
result[item[0]] = JSON.parse(
|
||||
decodeURIComponent(item[1]).replace(/\\&/, "&"));
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
params = getConfigParamsFromUrl();
|
||||
|
||||
var URLProcessor = {
|
||||
setConfigParametersFromUrl: function () {
|
||||
// Convert 'params' to JSON object
|
||||
// We have:
|
||||
// {
|
||||
// "config.disableAudioLevels": false,
|
||||
// "config.channelLastN": -1,
|
||||
// "interfaceConfig.APP_NAME": "Jitsi Meet"
|
||||
// }
|
||||
// We want to have:
|
||||
// {
|
||||
// "config": {
|
||||
// "disableAudioLevels": false,
|
||||
// "channelLastN": -1
|
||||
// },
|
||||
// interfaceConfig: {
|
||||
// APP_NAME: "Jitsi Meet"
|
||||
// }
|
||||
// }
|
||||
var configJSON = {
|
||||
config: {},
|
||||
interfaceConfig: {}
|
||||
};
|
||||
for (var key in params) {
|
||||
if (typeof key !== "string") {
|
||||
console.warn("Invalid config key: ", key);
|
||||
continue;
|
||||
}
|
||||
var confObj = null, confKey;
|
||||
if (key.indexOf("config.") === 0) {
|
||||
confObj = configJSON.config;
|
||||
confKey = key.substr("config.".length);
|
||||
} else if (key.indexOf("interfaceConfig.") === 0) {
|
||||
confObj = configJSON.interfaceConfig;
|
||||
confKey = key.substr("interfaceConfig.".length);
|
||||
}
|
||||
|
||||
if (!confObj)
|
||||
continue;
|
||||
|
||||
confObj[confKey] = params[key];
|
||||
}
|
||||
configUtils.overrideConfigJSON(config, interfaceConfig, configJSON);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = URLProcessor;
|
||||
49
modules/config/Util.js
Normal file
49
modules/config/Util.js
Normal file
@@ -0,0 +1,49 @@
|
||||
/* global $ */
|
||||
var ConfigUtil = {
|
||||
/**
|
||||
* Method overrides JSON properties in <tt>config</tt> and
|
||||
* <tt>interfaceConfig</tt> Objects with the values from <tt>newConfig</tt>
|
||||
* @param config the config object for which we'll be overriding properties
|
||||
* @param interfaceConfig the interfaceConfig object for which we'll be
|
||||
* overriding properties.
|
||||
* @param newConfig object containing configuration properties. Destination
|
||||
* object is selected based on root property name:
|
||||
* {
|
||||
* config: {
|
||||
* // config.js properties to be
|
||||
* },
|
||||
* interfaceConfig: {
|
||||
* // interfaceConfig.js properties here
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
overrideConfigJSON: function (config, interfaceConfig, newConfig) {
|
||||
for (var configRoot in newConfig) {
|
||||
|
||||
var confObj = null;
|
||||
if (configRoot == "config") {
|
||||
confObj = config;
|
||||
} else if (configRoot == "interfaceConfig") {
|
||||
confObj = interfaceConfig;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (var key in newConfig[configRoot]) {
|
||||
var value = newConfig[configRoot][key];
|
||||
if (confObj[key] && typeof confObj[key] !== typeof value)
|
||||
{
|
||||
console.warn(
|
||||
"The type of " + key +
|
||||
" is wrong. That parameter won't be updated in: ",
|
||||
confObj);
|
||||
continue;
|
||||
}
|
||||
console.info("Overriding " + key + " with: " + value);
|
||||
confObj[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = ConfigUtil;
|
||||
@@ -1,3 +1,4 @@
|
||||
/* global APP, require */
|
||||
var EventEmitter = require("events");
|
||||
var eventEmitter = new EventEmitter();
|
||||
var CQEvents = require("../../service/connectionquality/CQEvents");
|
||||
@@ -53,7 +54,7 @@ function convertToMUCStats(stats) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts statitistics to format used by VideoLayout
|
||||
* Converts statistics to format used by VideoLayout
|
||||
* @param stats
|
||||
* @returns {{bitrate: {download: *, upload: *}, packetLoss: {total: *, download: *, upload: *}}}
|
||||
*/
|
||||
@@ -71,7 +72,6 @@ function parseMUCStats(stats) {
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
var ConnectionQuality = {
|
||||
init: function () {
|
||||
APP.xmpp.addListener(XMPPEvents.REMOTE_STATS, this.updateRemoteStats);
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
/* global $, alert, changeLocalVideo, chrome, config, getConferenceHandler, getUserMediaWithConstraints */
|
||||
/* global $, alert, APP, changeLocalVideo, chrome, config, getConferenceHandler,
|
||||
getUserMediaWithConstraints */
|
||||
/**
|
||||
* Indicates that desktop stream is currently in use(for toggle purpose).
|
||||
* @type {boolean}
|
||||
*/
|
||||
var isUsingScreenStream = false;
|
||||
/**
|
||||
* Indicates that switch stream operation is in progress and prevent from triggering new events.
|
||||
* Indicates that switch stream operation is in progress and prevent from
|
||||
* triggering new events.
|
||||
* @type {boolean}
|
||||
*/
|
||||
var switchInProgress = false;
|
||||
@@ -18,16 +20,29 @@ var switchInProgress = false;
|
||||
var obtainDesktopStream = null;
|
||||
|
||||
/**
|
||||
* Flag used to cache desktop sharing enabled state. Do not use directly as it can be <tt>null</tt>.
|
||||
* @type {null|boolean}
|
||||
* Indicates whether desktop sharing extension is installed.
|
||||
* @type {boolean}
|
||||
*/
|
||||
var _desktopSharingEnabled = null;
|
||||
var extInstalled = false;
|
||||
|
||||
/**
|
||||
* Indicates whether update of desktop sharing extension is required.
|
||||
* @type {boolean}
|
||||
*/
|
||||
var extUpdateRequired = false;
|
||||
|
||||
var AdapterJS = require("../RTC/adapter.screenshare");
|
||||
|
||||
var EventEmitter = require("events");
|
||||
|
||||
var eventEmitter = new EventEmitter();
|
||||
|
||||
var DesktopSharingEventTypes = require("../../service/desktopsharing/DesktopSharingEventTypes");
|
||||
var DesktopSharingEventTypes
|
||||
= require("../../service/desktopsharing/DesktopSharingEventTypes");
|
||||
|
||||
var RTCBrowserType = require("../RTC/RTCBrowserType");
|
||||
|
||||
var RTCEvents = require("../../service/RTC/RTCEvents");
|
||||
|
||||
/**
|
||||
* Method obtains desktop stream from WebRTC 'screen' source.
|
||||
@@ -48,7 +63,8 @@ function obtainWebRTCScreen(streamCallback, failCallback) {
|
||||
*/
|
||||
function getWebStoreInstallUrl()
|
||||
{
|
||||
return "https://chrome.google.com/webstore/detail/" + config.chromeExtensionId;
|
||||
return "https://chrome.google.com/webstore/detail/" +
|
||||
config.chromeExtensionId;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -98,11 +114,10 @@ function isUpdateRequired(minVersion, extVersion)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function checkExtInstalled(isInstalledCallback) {
|
||||
function checkChromeExtInstalled(callback) {
|
||||
if (!chrome.runtime) {
|
||||
// No API, so no extension for sure
|
||||
isInstalledCallback(false);
|
||||
callback(false, false);
|
||||
return;
|
||||
}
|
||||
chrome.runtime.sendMessage(
|
||||
@@ -111,26 +126,24 @@ function checkExtInstalled(isInstalledCallback) {
|
||||
function (response) {
|
||||
if (!response || !response.version) {
|
||||
// Communication failure - assume that no endpoint exists
|
||||
console.warn("Extension not installed?: " + chrome.runtime.lastError);
|
||||
isInstalledCallback(false);
|
||||
} else {
|
||||
// Check installed extension version
|
||||
var extVersion = response.version;
|
||||
console.log('Extension version is: ' + extVersion);
|
||||
var updateRequired = isUpdateRequired(config.minChromeExtVersion, extVersion);
|
||||
if (updateRequired) {
|
||||
alert(
|
||||
'Jitsi Desktop Streamer requires update. ' +
|
||||
'Changes will take effect after next Chrome restart.');
|
||||
}
|
||||
isInstalledCallback(!updateRequired);
|
||||
console.warn(
|
||||
"Extension not installed?: ", chrome.runtime.lastError);
|
||||
callback(false, false);
|
||||
return;
|
||||
}
|
||||
// Check installed extension version
|
||||
var extVersion = response.version;
|
||||
console.log('Extension version is: ' + extVersion);
|
||||
var updateRequired
|
||||
= isUpdateRequired(config.minChromeExtVersion, extVersion);
|
||||
callback(!updateRequired, updateRequired);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function doGetStreamFromExtension(streamCallback, failCallback) {
|
||||
// Sends 'getStream' msg to the extension. Extension id must be defined in the config.
|
||||
// Sends 'getStream' msg to the extension.
|
||||
// Extension id must be defined in the config.
|
||||
chrome.runtime.sendMessage(
|
||||
config.chromeExtensionId,
|
||||
{ getStream: true, sources: config.desktopSharingSources },
|
||||
@@ -156,66 +169,114 @@ function doGetStreamFromExtension(streamCallback, failCallback) {
|
||||
);
|
||||
}
|
||||
/**
|
||||
* Asks Chrome extension to call chooseDesktopMedia and gets chrome 'desktop' stream for returned stream token.
|
||||
* Asks Chrome extension to call chooseDesktopMedia and gets chrome 'desktop'
|
||||
* stream for returned stream token.
|
||||
*/
|
||||
function obtainScreenFromExtension(streamCallback, failCallback) {
|
||||
checkExtInstalled(
|
||||
function (isInstalled) {
|
||||
if (isInstalled) {
|
||||
doGetStreamFromExtension(streamCallback, failCallback);
|
||||
} else {
|
||||
chrome.webstore.install(
|
||||
getWebStoreInstallUrl(),
|
||||
function (arg) {
|
||||
console.log("Extension installed successfully", arg);
|
||||
// We need to reload the page in order to get the access to chrome.runtime
|
||||
window.location.reload(false);
|
||||
},
|
||||
function (arg) {
|
||||
console.log("Failed to install the extension", arg);
|
||||
failCallback(arg);
|
||||
APP.UI.messageHandler.showError("dialog.error",
|
||||
"dialog.failtoinstall");
|
||||
}
|
||||
);
|
||||
}
|
||||
if (extInstalled) {
|
||||
doGetStreamFromExtension(streamCallback, failCallback);
|
||||
} else {
|
||||
if (extUpdateRequired) {
|
||||
alert(
|
||||
'Jitsi Desktop Streamer requires update. ' +
|
||||
'Changes will take effect after next Chrome restart.');
|
||||
}
|
||||
);
|
||||
|
||||
chrome.webstore.install(
|
||||
getWebStoreInstallUrl(),
|
||||
function (arg) {
|
||||
console.log("Extension installed successfully", arg);
|
||||
extInstalled = true;
|
||||
// We need to give a moment for the endpoint to become available
|
||||
window.setTimeout(function () {
|
||||
doGetStreamFromExtension(streamCallback, failCallback);
|
||||
}, 500);
|
||||
},
|
||||
function (arg) {
|
||||
console.log("Failed to install the extension", arg);
|
||||
failCallback(arg);
|
||||
APP.UI.messageHandler.showError("dialog.error",
|
||||
"dialog.failtoinstall");
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this method to toggle desktop sharing feature.
|
||||
* @param method pass "ext" to use chrome extension for desktop capture(chrome extension required),
|
||||
* pass "webrtc" to use WebRTC "screen" desktop source('chrome://flags/#enable-usermedia-screen-capture'
|
||||
* must be enabled), pass any other string or nothing in order to disable this feature completely.
|
||||
* @param method pass "ext" to use chrome extension for desktop capture(chrome
|
||||
* extension required), pass "webrtc" to use WebRTC "screen" desktop
|
||||
* source('chrome://flags/#enable-usermedia-screen-capture' must be
|
||||
* enabled), pass any other string or nothing in order to disable this
|
||||
* feature completely.
|
||||
*/
|
||||
function setDesktopSharing(method) {
|
||||
// Check if we are running chrome
|
||||
if (!navigator.webkitGetUserMedia) {
|
||||
obtainDesktopStream = null;
|
||||
console.info("Desktop sharing disabled");
|
||||
} else if (method == "ext") {
|
||||
obtainDesktopStream = obtainScreenFromExtension;
|
||||
console.info("Using Chrome extension for desktop sharing");
|
||||
} else if (method == "webrtc") {
|
||||
obtainDesktopStream = obtainWebRTCScreen;
|
||||
console.info("Using Chrome WebRTC for desktop sharing");
|
||||
|
||||
obtainDesktopStream = null;
|
||||
|
||||
// When TemasysWebRTC plugin is used we always use getUserMedia, so we don't
|
||||
// care about 'method' parameter
|
||||
if (RTCBrowserType.isTemasysPluginUsed()) {
|
||||
if (!AdapterJS.WebRTCPlugin.plugin.HasScreensharingFeature) {
|
||||
console.info("Screensharing not supported by this plugin version");
|
||||
} else if (!AdapterJS.WebRTCPlugin.plugin.isScreensharingAvailable) {
|
||||
console.info(
|
||||
"Screensharing not available with Temasys plugin on this site");
|
||||
} else {
|
||||
obtainDesktopStream = obtainWebRTCScreen;
|
||||
console.info("Using Temasys plugin for desktop sharing");
|
||||
}
|
||||
} else if (RTCBrowserType.isChrome()) {
|
||||
if (method == "ext") {
|
||||
if (RTCBrowserType.getChromeVersion() >= 34) {
|
||||
obtainDesktopStream = obtainScreenFromExtension;
|
||||
console.info("Using Chrome extension for desktop sharing");
|
||||
initChromeExtension();
|
||||
} else {
|
||||
console.info("Chrome extension not supported until ver 34");
|
||||
}
|
||||
} else if (method == "webrtc") {
|
||||
obtainDesktopStream = obtainWebRTCScreen;
|
||||
console.info("Using Chrome WebRTC for desktop sharing");
|
||||
}
|
||||
}
|
||||
|
||||
// Reset enabled cache
|
||||
_desktopSharingEnabled = null;
|
||||
if (!obtainDesktopStream) {
|
||||
console.info("Desktop sharing disabled");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes <link rel=chrome-webstore-item /> with extension id set in config.js to support inline installs.
|
||||
* Host site must be selected as main website of published extension.
|
||||
* Initializes <link rel=chrome-webstore-item /> with extension id set in
|
||||
* config.js to support inline installs. Host site must be selected as main
|
||||
* website of published extension.
|
||||
*/
|
||||
function initInlineInstalls()
|
||||
{
|
||||
$("link[rel=chrome-webstore-item]").attr("href", getWebStoreInstallUrl());
|
||||
}
|
||||
|
||||
function getSwitchStreamFailed(error) {
|
||||
function initChromeExtension() {
|
||||
// Initialize Chrome extension inline installs
|
||||
initInlineInstalls();
|
||||
// Check if extension is installed
|
||||
checkChromeExtInstalled(function (installed, updateRequired) {
|
||||
extInstalled = installed;
|
||||
extUpdateRequired = updateRequired;
|
||||
console.info(
|
||||
"Chrome extension installed: " + extInstalled +
|
||||
" updateRequired: " + extUpdateRequired);
|
||||
});
|
||||
}
|
||||
|
||||
function getVideoStreamFailed(error) {
|
||||
console.error("Failed to obtain the stream to switch to", error);
|
||||
switchInProgress = false;
|
||||
isUsingScreenStream = false;
|
||||
newStreamCreated(null);
|
||||
}
|
||||
|
||||
function getDesktopStreamFailed(error) {
|
||||
console.error("Failed to obtain the stream to switch to", error);
|
||||
switchInProgress = false;
|
||||
}
|
||||
@@ -233,6 +294,25 @@ function newStreamCreated(stream)
|
||||
stream, isUsingScreenStream, streamSwitchDone);
|
||||
}
|
||||
|
||||
function onEndedHandler(stream) {
|
||||
if (!switchInProgress && isUsingScreenStream) {
|
||||
APP.desktopsharing.toggleScreenSharing();
|
||||
}
|
||||
//FIXME: to be verified
|
||||
if (stream.removeEventListener) {
|
||||
stream.removeEventListener('ended', onEndedHandler);
|
||||
} else {
|
||||
stream.detachEvent('ended', onEndedHandler);
|
||||
}
|
||||
}
|
||||
|
||||
// Called when RTC finishes initialization
|
||||
function onWebRtcReady() {
|
||||
|
||||
setDesktopSharing(config.desktopSharing);
|
||||
|
||||
eventEmitter.emit(DesktopSharingEventTypes.INIT);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
isUsingScreenStream: function () {
|
||||
@@ -240,41 +320,21 @@ module.exports = {
|
||||
},
|
||||
|
||||
/**
|
||||
* @returns {boolean} <tt>true</tt> if desktop sharing feature is available and enabled.
|
||||
* @returns {boolean} <tt>true</tt> if desktop sharing feature is available
|
||||
* and enabled.
|
||||
*/
|
||||
isDesktopSharingEnabled: function () {
|
||||
if (_desktopSharingEnabled === null) {
|
||||
if (obtainDesktopStream === obtainScreenFromExtension) {
|
||||
// Parse chrome version
|
||||
var userAgent = navigator.userAgent.toLowerCase();
|
||||
// We can assume that user agent is chrome, because it's enforced when 'ext' streaming method is set
|
||||
var ver = parseInt(userAgent.match(/chrome\/(\d+)\./)[1], 10);
|
||||
console.log("Chrome version" + userAgent, ver);
|
||||
_desktopSharingEnabled = ver >= 34;
|
||||
} else {
|
||||
_desktopSharingEnabled = obtainDesktopStream === obtainWebRTCScreen;
|
||||
}
|
||||
}
|
||||
return _desktopSharingEnabled;
|
||||
},
|
||||
isDesktopSharingEnabled: function () { return !!obtainDesktopStream; },
|
||||
|
||||
init: function () {
|
||||
setDesktopSharing(config.desktopSharing);
|
||||
|
||||
// Initialize Chrome extension inline installs
|
||||
if (config.chromeExtensionId) {
|
||||
initInlineInstalls();
|
||||
}
|
||||
|
||||
eventEmitter.emit(DesktopSharingEventTypes.INIT);
|
||||
APP.RTC.addListener(RTCEvents.RTC_READY, onWebRtcReady);
|
||||
},
|
||||
|
||||
addListener: function(listener, type)
|
||||
addListener: function (listener, type)
|
||||
{
|
||||
eventEmitter.on(type, listener);
|
||||
},
|
||||
|
||||
removeListener: function (listener,type) {
|
||||
removeListener: function (listener, type) {
|
||||
eventEmitter.removeListener(type, listener);
|
||||
},
|
||||
|
||||
@@ -295,17 +355,21 @@ module.exports = {
|
||||
function (stream) {
|
||||
// We now use screen stream
|
||||
isUsingScreenStream = true;
|
||||
// Hook 'ended' event to restore camera when screen stream stops
|
||||
stream.addEventListener('ended',
|
||||
function (e) {
|
||||
if (!switchInProgress && isUsingScreenStream) {
|
||||
toggleScreenSharing();
|
||||
}
|
||||
}
|
||||
);
|
||||
// Hook 'ended' event to restore camera
|
||||
// when screen stream stops
|
||||
//FIXME: to be verified
|
||||
if (stream.addEventListener) {
|
||||
stream.addEventListener('ended', function () {
|
||||
onEndedHandler(stream);
|
||||
});
|
||||
} else {
|
||||
stream.attachEvent('ended', function () {
|
||||
onEndedHandler(stream);
|
||||
});
|
||||
}
|
||||
newStreamCreated(stream);
|
||||
},
|
||||
getSwitchStreamFailed);
|
||||
getDesktopStreamFailed);
|
||||
} else {
|
||||
// Disable screen stream
|
||||
APP.RTC.getUserMediaWithConstraints(
|
||||
@@ -315,7 +379,7 @@ module.exports = {
|
||||
isUsingScreenStream = false;
|
||||
newStreamCreated(stream);
|
||||
},
|
||||
getSwitchStreamFailed, config.resolution || '360'
|
||||
getVideoStreamFailed, config.resolution || '360'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,38 +1,43 @@
|
||||
/* global APP, $ */
|
||||
//maps keycode to character, id of popover for given function and function
|
||||
var shortcuts = {
|
||||
67: {
|
||||
character: "C",
|
||||
id: "toggleChatPopover",
|
||||
function: APP.UI.toggleChat
|
||||
},
|
||||
70: {
|
||||
character: "F",
|
||||
id: "filmstripPopover",
|
||||
function: APP.UI.toggleFilmStrip
|
||||
},
|
||||
77: {
|
||||
character: "M",
|
||||
id: "mutePopover",
|
||||
function: APP.UI.toggleAudio
|
||||
},
|
||||
84: {
|
||||
character: "T",
|
||||
function: function() {
|
||||
if(!APP.RTC.localAudio.isMuted()) {
|
||||
APP.UI.toggleAudio();
|
||||
var shortcuts = {};
|
||||
function initShortcutHandlers() {
|
||||
shortcuts = {
|
||||
67: {
|
||||
character: "C",
|
||||
id: "toggleChatPopover",
|
||||
function: APP.UI.toggleChat
|
||||
},
|
||||
70: {
|
||||
character: "F",
|
||||
id: "filmstripPopover",
|
||||
function: APP.UI.toggleFilmStrip
|
||||
},
|
||||
77: {
|
||||
character: "M",
|
||||
id: "mutePopover",
|
||||
function: APP.UI.toggleAudio
|
||||
},
|
||||
84: {
|
||||
character: "T",
|
||||
function: function() {
|
||||
if(!APP.RTC.localAudio.isMuted()) {
|
||||
APP.UI.toggleAudio();
|
||||
}
|
||||
}
|
||||
},
|
||||
86: {
|
||||
character: "V",
|
||||
id: "toggleVideoPopover",
|
||||
function: APP.UI.toggleVideo
|
||||
}
|
||||
},
|
||||
86: {
|
||||
character: "V",
|
||||
id: "toggleVideoPopover",
|
||||
function: APP.UI.toggleVideo
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
var KeyboardShortcut = {
|
||||
init: function () {
|
||||
initShortcutHandlers();
|
||||
window.onkeyup = function(e) {
|
||||
var keycode = e.which;
|
||||
if(!($(":focus").is("input[type=text]") ||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user