Compare commits

...

306 Commits
242 ... 428

Author SHA1 Message Date
hristo
15f4f03ba3 Commit from translate.jitsi.org by user hristo.: 11 of 149 strings translated (0 fuzzy). 2015-03-11 17:50:42 +00:00
hristoterezov
4f9b6f7180 Changes the format of email text in the language resource files. 2015-03-11 18:54:43 +02:00
hristoterezov
b36ec5fd01 Moves supported browser list from the email message to the JS 2015-03-11 10:53:39 +02:00
ibauersachs
ac95ea03fe Commit from translate.jitsi.org by user ibauersachs.: 148 of 148 strings translated (0 fuzzy). 2015-03-11 08:51:16 +00:00
hristoterezov
ae535fcb7d Replaces %0D%0A with \n in the email template text. 2015-03-11 10:29:21 +02:00
hristoterezov
957cc6afc1 Merge branch 'master' of github.com:jitsi/jitsi-meet 2015-03-10 17:25:44 +02:00
hristoterezov
16fdd59617 Adds turkish language. 2015-03-10 17:24:27 +02:00
George Politis
fabf8f42c6 Updates .gitignore. 2015-03-10 15:50:26 +01:00
ibauersachs
c98a56dc37 Commit from translate.jitsi.org by user ibauersachs.: 135 of 135 strings translated (0 fuzzy). 2015-03-10 10:10:08 +00:00
hristoterezov
deb68dd420 Fixes translation issues 2015-03-10 11:38:09 +02:00
Boris Grozev
0fd1a7fa08 Removes embedded html as per Ingo's suggestion. 2015-03-09 19:14:11 +01:00
Boris Grozev
c6ff8aa5dd Tries to improve readibility... 2015-03-09 18:44:42 +01:00
Boris Grozev
06f025e92a Fix formatting. 2015-03-09 18:29:47 +01:00
Boris Grozev
f14329f2cd Adds instructions to enable logging. 2015-03-09 18:27:56 +01:00
Boris Grozev
53e525597a Add .swp files to .gitignore. 2015-03-09 18:27:55 +01:00
George Politis
54b3cbcf94 Reverts config.js 2015-03-09 17:58:11 +01:00
George Politis
2852740e71 Updates the Makefile. It calls npm update before building the project. 2015-03-09 17:41:17 +01:00
George Politis
5322ba086b Reverts previous change of the Makefile. 2015-03-09 17:39:16 +01:00
hristoterezov
d2f95f3c81 Fixes some translation issues. 2015-03-09 17:50:13 +02:00
George Politis
3747251821 Adds dependency to specific commit for sdp-interop. 2015-03-09 16:21:53 +01:00
George Politis
159ba82167 Updates app.bundle.js with latest sdp-interop module that offers support for ssrc-groups. 2015-03-09 15:25:47 +01:00
George Politis
e34a8e6b60 Updates app.bundle.js with latest sdp-interop module. 2015-03-09 11:11:25 +01:00
jitsi-pootle
17a6e360a2 New files added from translate.jitsi.org based on templates 2015-03-09 08:16:17 +00:00
George Politis
b690f5d4a1 Updates app.bundle.js. 2015-03-05 20:25:50 +01:00
Damian Minkov
30f3168bf7 Adds watch file for the deb. 2015-03-05 19:23:55 +02:00
Damian Minkov
115f2e4663 Updates the patch for debian src package. 2015-03-05 19:23:29 +02:00
Damian Minkov
fa15a75928 Updates missing-source location. 2015-03-05 18:01:27 +02:00
Damian Minkov
4db75446f3 Do not edit /etc/nginx/nginx.conf file directly. 2015-03-05 17:52:05 +02:00
George Politis
d9f7b8b6cc Adds a comment about how FF handles the video.src attribute. 2015-03-05 11:26:44 +01:00
George Politis
05bbfda5bb Adds support for FF/multistream. 2015-03-04 21:33:06 +01:00
paweldomas
e465b3ed90 Removes unnecessary error dialog when desktop sharing is started before the conference. 2015-03-04 13:58:07 +01:00
hristoterezov
1825f47ef2 Adds translation support for placeholder attributes. 2015-03-04 12:59:52 +02:00
ibauersachs
169d613ac4 Commit from translate.jitsi.org by user ibauersachs.: 135 of 135 strings translated (0 fuzzy). 2015-03-04 09:37:19 +00:00
Ingo Bauersachs
3dac5eeff5 Fix punctuation spacing 2015-03-04 10:27:44 +01:00
hristoterezov
f79651f806 Merge branch 'translation' 2015-03-04 11:06:02 +02:00
hristoterezov
6048d0a325 Fixes the html attributes for translation options. 2015-02-27 20:05:32 +02:00
hristoterezov
6f12446c99 Fixes translation of plurals. 2015-02-27 19:58:05 +02:00
hristoterezov
af682f8727 Changes the configuration of translation module to retrieve the resources synchronous. Removes the default values. 2015-02-26 17:35:35 +02:00
paweldomas
9123923818 Displays reservation system error if one is returned by the focus. 2015-02-26 14:59:01 +01:00
Boris Grozev
aee7a8e1bd Fix a bug with stats accumulating. Fix a typo. 2015-02-26 10:12:06 +01:00
paweldomas
5b44edb3cc Handles graceful-shutdown focus error response. 2015-02-25 16:55:22 +01:00
Boris Grozev
806d4ea443 Filters some statistics from the logs. Increases the interval for logged statistics. 2015-02-25 11:38:04 +01:00
hristoterezov
1e35ca5e4d Removes the firefox issue link. 2015-02-25 12:06:32 +02:00
hristoterezov
d4f00d76ab Removes webrtcrequired.html. 2015-02-25 11:07:33 +02:00
hristoterezov
37282e63b3 Fixes the references with chromeonly page. 2015-02-24 18:24:39 +02:00
hristoterezov
4b218499ae Redesigns the supported browsers page. 2015-02-24 18:08:24 +02:00
hristoterezov
f16a1cdf44 Changes the implementation to store the language in local storage. Adds new languages. 2015-02-24 12:49:46 +02:00
hristoterezov
702f02568d Fixes issue with the buttons in the message handler. 2015-02-24 10:57:41 +02:00
paweldomas
b6808d87bc Updates app.bundle.js. 2015-02-23 16:15:42 +01:00
paweldomas
8042bd2aa6 Handles MUC destroyed event. 2015-02-23 16:13:38 +01:00
paweldomas
053b2d5af2 Fixes logout confirmation dialog. 2015-02-23 16:13:26 +01:00
Ingo Bauersachs
222164333b Fix some typos and punctuation spacing. 2015-02-23 13:35:16 +01:00
ibauersachs
db50810e4b Commit from translate.jitsi.org by user ibauersachs.: 130 of 130 strings translated (0 fuzzy). 2015-02-23 12:33:29 +00:00
hristo
720851dcb9 Commit from translate.jitsi.org by user hristo.: 3 of 130 strings translated (0 fuzzy). 2015-02-23 11:44:51 +00:00
jitsi-pootle
d7203b8b1a New files added from translate.jitsi.org based on templates 2015-02-23 11:42:53 +00:00
jitsi-pootle
204ca29ed7 New files added from translate.jitsi.org based on templates 2015-02-23 11:42:33 +00:00
hristoterezov
fdada53a4a Fixes issue with the recording. 2015-02-23 12:06:42 +02:00
hristoterezov
81eb3754a0 Fixes "focus not available" notifications. 2015-02-23 11:20:35 +02:00
hristoterezov
d260f1db61 Removes unused package.son files. 2015-02-20 18:21:58 +02:00
hristoterezov
74f078f166 Adds multi language support for message handlers. 2015-02-20 18:17:46 +02:00
paweldomas
e16cee4187 Delete old session ID and retry on 'session-invalid' response. Updates app.bundle.js. 2015-02-19 13:56:04 +01:00
paweldomas
a904e35c67 Adds auto-sign in feature and login/logout toolbar menu. 2015-02-19 13:49:51 +01:00
paweldomas
b87cd9f842 Moves Settings module out from the UI. 2015-02-19 13:49:41 +01:00
paweldomas
fed34e7671 Fixes PreziPlayer crash when invalid message is received. 2015-02-19 13:49:41 +01:00
George Politis
ed57f72117 Adds extra logging. 2015-02-19 13:27:44 +01:00
hristoterezov
4d39d4ccc3 Fixes issue with selected user resource jid variable. 2015-02-19 11:23:55 +02:00
hristoterezov
79cdd94833 Adds multi language support for notifications. 2015-02-13 18:28:35 +02:00
hristoterezov
e0645b41d3 Moves some function calls in UI service 2015-02-13 11:31:25 +02:00
hristoterezov
aa7f0c8a0b Merge branch 'master' of github.com:jitsi/jitsi-meet 2015-02-12 16:44:24 +02:00
hristoterezov
2362770cce Changes getUserMedia implementation to try lower resolution if the configured one is not supported. 2015-02-12 16:42:08 +02:00
Damian Minkov
8334036cf4 Moves admins definition as it seems on some distributions after latest lua updates, causes trouble for prosody. 2015-02-12 15:39:56 +02:00
Damian Minkov
eec513e9e3 Adds admins line after muc component. 2015-02-12 13:50:21 +02:00
hristoterezov
f2a7a43ba7 Fixes audio level performance issue on avatar. 2015-02-11 18:29:20 +02:00
hristoterezov
61bbbaf6eb Adds gitattributes file that marks the generated files as binary files. 2015-02-09 15:50:49 +02:00
hristoterezov
3519a6ec7b Fixes the generated file. 2015-02-09 15:03:23 +02:00
hristoterezov
d21f994eee Removes rtp stats option and adds options for disabling/enabling stats and audio levels. 2015-02-09 14:51:25 +02:00
hristoterezov
b32acf0dfb Fixes the multi language support for the debian package. 2015-02-09 12:24:11 +02:00
hristoterezov
71a56e13d9 Fixes some issues with the tests 2015-02-09 12:21:23 +02:00
hristoterezov
0f6d0a0439 Adds methods required by torture 2015-02-09 10:12:55 +02:00
hristoterezov
3032ea7684 Implements basic multi language support. 2015-02-06 17:46:50 +02:00
hristoterezov
04cfbafc33 Fixes issue with recording. 2015-02-06 15:43:40 +02:00
hristoterezov
57fcee676a Fixes issues with accessing modules not from APP object. 2015-02-06 14:54:19 +02:00
hristoterezov
2f5d090ca5 Merge pull request #227 from odotom/odotom-patch-1
Fixes typos.
2015-02-03 15:42:30 +02:00
bgrozev
8d796f328b Update README.md
Clarify that the detailed instructions are for a 'manual' installation.
2015-02-03 12:45:01 +02:00
hristoterezov
ffb1d6ea17 Generates app bundle file 2015-02-02 20:00:45 +02:00
hristoterezov
4447e5dac6 Merge pull request #229 from schleussinger/master
Corrected Scope - fixes runtime JS error and  Chrome Ext desktop sharing for me
2015-02-02 19:47:59 +02:00
schleussinger
dbed14db5e Fix correct Scope - this fixes JS error and Chrome Ext desktop sharing for me 2015-02-01 13:14:18 +01:00
Thomas Odorfer
254103e21f Update UI.js
fix typo UI.getCredentials
2015-01-31 22:14:53 +01:00
Thomas Odorfer
d0b39e1c97 Update app.js
app.js: fix getCredentials - return object instead of function reference
2015-01-31 22:11:02 +01:00
hristoterezov
4bb555e4b2 Fixes authentication issues. 2015-01-29 11:43:40 +02:00
hristoterezov
8d0ee3ded9 Updates generated file. 2015-01-29 11:27:02 +02:00
hristoterezov
98d1ca8505 Fixes authentication issues. 2015-01-29 11:09:09 +02:00
hristoterezov
e766bad4ce Merge branch 'master' of https://github.com/Zalmoxisus/jitsi-meet
Conflicts:
	libs/modules/RTC.bundle.js
	libs/modules/simulcast.bundle.js
2015-01-28 18:06:09 +02:00
hristoterezov
9eb2873cfa Removes the bundles for every module and add bundle for the whole application. 2015-01-28 16:35:22 +02:00
hristoterezov
c7e2331284 Removes document bind events between modules. 2015-01-27 14:03:26 +02:00
hristoterezov
02ca5e5732 Merge branch 'master' of github.com:jitsi/jitsi-meet 2015-01-27 11:56:43 +02:00
hristoterezov
bc2d72638b Add events for data chanel. 2015-01-27 11:56:22 +02:00
Zalmoxisus
40de181959 Fixes audio-only (when GUM fails) 2015-01-26 19:54:26 +02:00
bgrozev
70bc071cb8 Merge pull request #220 from Zalmoxisus/master
Fixes some typos that cause errors
2015-01-26 09:29:59 -06:00
Zalmoxisus
567ac23c2c Fixes some typos that cause errors 2015-01-26 16:24:26 +02:00
hristoterezov
af50bd5b94 Resolves some module dependancies by replaces them with events. 2015-01-24 16:28:02 +02:00
hristoterezov
899f0ee83d Removes UI dependancies in the xmpp module. 2015-01-23 17:36:17 +02:00
hristoterezov
29b3ea07e0 Removes util.js. Fixes prezi. 2015-01-23 14:01:44 +02:00
hristoterezov
c0a316c7df Creates keyboard shortcuts module. 2015-01-22 18:26:05 +02:00
hristoterezov
f624833f1f Merge branch 'master' of github.com:jitsi/jitsi-meet 2015-01-22 18:02:56 +02:00
hristoterezov
4c661ffca6 Removes nickname global variable. 2015-01-22 18:02:37 +02:00
Boris Grozev
0819f23049 Adds instructings for building. 2015-01-22 17:52:28 +02:00
Boris Grozev
1e9a463245 Fix a typo. 2015-01-22 17:39:40 +02:00
Boris Grozev
447d8f5677 Makes the default make target execute "deploy" and "clean". 2015-01-22 17:36:12 +02:00
fo
d2453b1f1f Changed capitalisation in require statements. 2015-01-22 16:56:23 +02:00
Boris Grozev
9460138cc3 Fix pako reference. 2015-01-21 17:35:23 +02:00
Boris Grozev
0063461858 Uses pako from npm. 2015-01-21 17:13:22 +02:00
hristoterezov
248d7a3173 Moves ssrc2jid global variable to the xmpp module. 2015-01-21 13:55:20 +02:00
hristoterezov
51277270fe Fixes issues with the recording. 2015-01-20 18:12:32 +02:00
hristoterezov
394738394d Fixes NPE when creating non anonymous room 2015-01-20 18:07:03 +02:00
hristoterezov
6c4a5bd2bc Removes some global variables. Fixes recording. 2015-01-20 17:56:00 +02:00
hristoterezov
6347730dc7 Fixes some issues related to xmpp module creation. 2015-01-19 18:54:41 +02:00
hristoterezov
3da8e39745 Merges app.js and generates bundles. 2015-01-19 12:03:14 +02:00
hristoterezov
f4acf97b00 Merge branch 'master' of github.com:jitsi/jitsi-meet
Conflicts:
	app.js
	libs/strophe/strophe.jingle.js
	modules/xmpp/moderator.js
	muc.js
2015-01-19 12:00:30 +02:00
hristoterezov
e4e66a03d7 Creates initial version of xmpp module. 2015-01-19 11:20:00 +02:00
paweldomas
ed78c0053c Makes it possible to append URL parameters after room name. Adds ?login=true to enforce authenticated domain when anonymous domain is used. This allows to get moderator permissions after room has been created. 2015-01-16 13:22:02 +01:00
paweldomas
398fd18b8e Advertises dtls/sctp support in capabilities. 2015-01-16 12:35:31 +01:00
paweldomas
d3003d4fcd Adjusts anonymous domain functionality to work with Jicofo. 2015-01-14 17:59:50 +01:00
hristoterezov
ee94eca733 Creates desktop sharing module. 2015-01-13 15:11:05 +02:00
hristoterezov
0696fb2c5a Fixes issue with video mute. 2015-01-13 11:33:45 +02:00
hristoterezov
e6fbb0934e Removes local streams from the connection object. 2015-01-12 15:23:29 +02:00
hristoterezov
faaf24d3c4 Creates simulcast module 2015-01-09 17:32:35 +02:00
hristoterezov
fcf785f32c Removes unused files. 2015-01-09 16:18:03 +02:00
hristoterezov
0508628871 Creates API module. 2015-01-09 15:39:32 +02:00
hristoterezov
27502d3fa8 Removes some dependancies from functions in app.js. 2015-01-09 14:19:48 +02:00
hristoterezov
1057ff36cd Moves some functions from app.js 2015-01-09 13:35:48 +02:00
paweldomas
8d5e50c0ca Moves 'callSipButtonClicked' method to Toolbar.js in order to fix 'sharedKey' undefined error. 2015-01-09 11:19:42 +01:00
hristoterezov
8db602c8bd Fixes make file 2015-01-09 11:49:45 +02:00
paweldomas
06494cf821 Fixes undefined 'messageHandler' error. 2015-01-09 08:56:30 +01:00
hristoterezov
0fe6a55700 Fixes issue with connection quality module name. 2015-01-08 14:33:06 +02:00
hristoterezov
e20274c2f7 Creates connection quality module. 2015-01-08 14:11:53 +02:00
paweldomas
b77106f61a Includes room secret in SIP gateway request if password is required to join the room. 2015-01-07 16:58:41 +01:00
hristoterezov
4d25b139cc Fixes some issues caused by the merge. 2015-01-07 17:47:48 +02:00
hristoterezov
6ce48a5b7b Merge branch 'master' of github.com:jitsi/jitsi-meet
Conflicts:
	app.js
	index.html
	libs/colibri/colibri.focus.js
	libs/modules/statistics.bundle.js
	moderator.js
	modules/UI/videolayout/VideoLayout.js
	muc.js
2015-01-07 17:14:10 +02:00
hristoterezov
69b0e2ad32 Creates UI module. 2015-01-07 16:54:03 +02:00
paweldomas
cd0c9393d8 Makes "authentication required" dialog persistent. Joins the room if someone else authenticates first and creates the conference for us. 2015-01-07 12:28:49 +01:00
paweldomas
2494444ca4 Fixes issue with remote video menu not displayed on role upgrade. 2015-01-07 12:28:40 +01:00
paweldomas
c76b78eb46 Improves SIP gateway auto configuration by getting feedback from the focus. 2015-01-05 16:45:45 +01:00
paweldomas
78fcc8b72c Sends SIP gateway requests through the focus component. 2015-01-05 16:45:45 +01:00
hristoterezov
652412cd4f Fixes issue with local audio levels. 2015-01-05 17:08:49 +02:00
Damian Minkov
78801aa9e5 Fixes deb package. 2015-01-05 16:58:55 +02:00
Damian Minkov
cd266f60d7 Fixes building deb packages. 2015-01-05 16:41:38 +02:00
paweldomas
459891e647 Bumps js versions, updates bundles. 2015-01-05 13:14:27 +01:00
paweldomas
6cc8b63104 Does not flood with missing "Jid for SSRC" warnings for outdated stats(removed streams). 2015-01-05 13:07:59 +01:00
paweldomas
1aed7e6237 Fixes NPE. 2015-01-05 13:07:50 +01:00
paweldomas
087c26d494 Fixes issue with switching desktop stream. 2015-01-05 13:07:40 +01:00
paweldomas
4fb7001b00 Converts 'constraints' to local variable in order to fix desktop sharing issues(audio constraints were retained in global var and get desktop stream failed). 2015-01-05 13:07:28 +01:00
paweldomas
f1cb3af345 Increases default size of authentication popup. 2015-01-05 13:07:18 +01:00
Boris Grozev
427dc093cc Do not log presence changes to the console. 2014-12-30 19:57:43 +02:00
Boris Grozev
87f8b91a96 Implements recording through a jirecon instance. 2014-12-30 19:57:43 +02:00
paweldomas
fdcae01d21 Configures the room as non-anonymous. 2014-12-29 17:55:48 +01:00
George Politis
f95d5f36bb Moves add/remove source element creation in strophe.jingle.sdp.js. 2014-12-29 11:55:33 +01:00
George Politis
1938280e27 Narrows the gap between strophe.jingle and our code. 2014-12-26 12:46:23 +01:00
George Politis
dc5d5f8436 Nukes colibri.*.js and restores original file structure of strophe.jingle. 2014-12-26 10:40:06 +01:00
hristoterezov
efc161dacd Fixes issue with obtaining audio/video permissions when simulcast is disabled. 2014-12-23 16:24:56 +02:00
George Politis
2ee5a92ef0 Adds comment to modules/RTC/MediaStream.js 2014-12-22 15:08:49 +01:00
hristoterezov
4a991f7187 Adds package.json files for our browserify modules. 2014-12-22 15:46:24 +02:00
Paweł Domas
91358476a1 Update manual-install.md
Adds jicofo install instructions.
2014-12-22 13:05:20 +01:00
hristoterezov
ec5e0f09ea Moves require to top for the RTC module. 2014-12-22 11:03:21 +02:00
hristoterezov
a1da42ff00 Fixes the filename in require call of data channels file. 2014-12-19 17:49:22 +02:00
hristoterezov
5b34a66cb6 Implements RTC module. 2014-12-19 15:59:08 +02:00
bgrozev
996b1791d5 Merge pull request #205 from Zalmoxisus/master
Prevent XSS injection using 'nick' tag on presence
2014-12-18 18:24:44 +02:00
bgrozev
3b0fcad39b Merge pull request #208 from Zalmoxisus/master
Fixes chat messages
2014-12-18 18:24:23 +02:00
Paweł Domas
484b80965c Update quick-install.md
Adds 'jicofo' package to Deinstall section.
2014-12-18 09:32:30 +01:00
hristoterezov
c6d8e34779 Implements statistics module. 2014-12-17 18:21:25 +02:00
Damian Minkov
7bacd957bd Updates invitation email. 2014-12-17 14:45:22 +02:00
Boris Grozev
e830ced554 Removes the troubleshooting section. 2014-12-17 11:35:10 +02:00
Boris Grozev
2041b54a07 Removes the add-apt-repository way of adding a repository. 2014-12-17 11:34:16 +02:00
Boris Grozev
3473f1c20c s/OpenSource/open-source/. 2014-12-17 11:33:16 +02:00
Boris Grozev
10adea1691 Moves INSTALL.md to doc/manual-install. Links to both installation documents from README.md. 2014-12-17 11:32:42 +02:00
Damian Minkov
b3a4b8a1cf Fixes configuring prosody which can break also jicofo configuration, the missing domain will fail later creating admin account for jicofo. 2014-12-17 09:40:45 +02:00
paweldomas
78f8521145 Handles the case where browser is blocking popups and prevents from opening authentication window. 2014-12-16 19:05:34 +01:00
Damian Minkov
ba627718be Creates prosody conf.avail if missing. 2014-12-16 17:01:14 +02:00
bgrozev
698f511676 Merge pull request #211 from Zalmoxisus/master
Fixes a smile typo that causing missing image
2014-12-16 16:48:51 +02:00
paweldomas
f4004656a3 Adds functionality for authentication with external system. 2014-12-16 14:54:35 +01:00
Mihail Diordiev
66e1a98869 Fixes a smile typo that causing missing image 2014-12-16 08:52:14 +02:00
yanas
cc38c2641b Modifies default avatar image. 2014-12-12 13:18:36 +01:00
fo
d1c634abc2 Fixes active speaker avatar and active speaker audio level missmatch. 2014-12-12 11:32:16 +02:00
Lyubomir Marinov
4230aa1ff1 Renames a variable/field for the purposes of clarity. 2014-12-11 22:08:52 +02:00
Lyubomir Marinov
ae4dafb06d Optionally automatically mutes the local video if it is not in any "last N". 2014-12-11 21:50:08 +02:00
paweldomas
4c95921b06 Does not reload the page when "focus left MUC" event is generated after we have left the MUC intentionally. 2014-12-11 19:14:46 +01:00
paweldomas
8068d4e810 Changes "browser is too old message" error message as it's not always related to the browser version. 2014-12-11 19:09:18 +01:00
George Politis
e8779eeb18 Adds the user agent in the presence. 2014-12-11 13:02:45 +01:00
George Politis
a1624138fe Disables the "Focus connected" notification. 2014-12-11 13:01:46 +01:00
fo
451d16a664 Fixes error when myroomjid is null. 2014-12-11 13:39:27 +02:00
fo
b6a665e007 Adds audio levels for the active speaker avatar. 2014-12-10 15:10:54 +02:00
paweldomas
e4154c055e Fixes handling of "bridgeIsDown" presence extension coming from focus participant. 2014-12-08 16:38:56 +01:00
paweldomas
28e41f6c7b Fixes broken room locking functionality. 2014-12-08 11:44:05 +01:00
paweldomas
3f15d5495d Sends JVB from config.js to the focus. 2014-12-08 09:24:23 +01:00
Mihail Diordiev
60e7482df1 Fixes chat messages
Fixes issue #177 and also show new lines
2014-12-08 00:41:05 +02:00
paweldomas
7f057377d0 Displays error message when focus component is not available. 2014-12-05 17:02:42 +01:00
paweldomas
933a41492e No longer requires focus component and focusUserJid to be configured in config.js(but they can still be overridden there). 2014-12-05 17:02:41 +01:00
paweldomas
1729f7e17e Recognizes focus user. Adds missing semicolons. 2014-12-05 17:02:40 +01:00
George Politis
3c96c91ca8 Bumps version numbers for changed js files. 2014-12-05 17:00:24 +01:00
George Politis
cb8f57b3e3 Adds support for the openSctp and enableFirefoxHacks configuration params. 2014-12-05 16:49:22 +01:00
George Politis
5ba666de2e Restores broken config.displayJids functionality. 2014-12-05 16:49:21 +01:00
George Politis
66f7ddd6b2 Taking into account the fact that FF nightlies include the local SSRCs in the local SDP. 2014-12-05 16:49:21 +01:00
George Politis
79b7df28c1 Fixes undesirable falsy value in simulcast.js. 2014-12-05 16:49:21 +01:00
fo
f0a4c08f26 Fixes bugs with thumbnails getting black when prezi is playing or etherpad is viewed. 2014-12-05 15:10:45 +02:00
paweldomas
36065b935c Old prosody config improvement - patch by Damian Minkov. 2014-12-05 14:04:15 +01:00
fo
27eecff826 Fixes the sizes of the avatars. 2014-12-05 10:56:34 +02:00
fo
8bb5994715 Fixes mixup between jid and resourceJid. 2014-12-04 18:04:17 +02:00
paweldomas
91c3c9ca83 Restarts jvb and jicofo on upgrade. 2014-12-04 12:40:13 +01:00
paweldomas
4b8eef0f3e Adds 'jicofo' to jitsi-meet-prosody Depends. 2014-12-04 12:31:41 +01:00
paweldomas
4776605dec Merge branch 'ssfocus' 2014-12-04 11:19:45 +01:00
Zalmoxisus
7b0be8e953 Prevent XSS injection using 'nick' on presence
Also allows special characters in displayName. Fixes issue #182.
2014-12-03 22:44:03 +02:00
George Politis
5af92474c3 Attempts to prevent ghost contacts from appearing in the contact list. 2014-12-03 12:47:44 +01:00
paweldomas
f749bed1dd Adds jicofo debian package integration. 2014-12-02 20:11:54 +01:00
paweldomas
e308025143 Merge branch 'master' into ssfocus
Conflicts:
	config.js
	libs/strophe/strophe.jingle.session.js
	rtp_sts.js
2014-12-02 20:09:24 +01:00
Damian Minkov
63dd6df217 Updates strophe to use a patch that handles only result and error responses when an iq is send (https://github.com/strophe/strophejs/pull/95). Fixes a problem with openfire sending us iq packages with id which we have already used. 2014-12-02 15:21:12 +02:00
Damian Minkov
bb5d178220 Uses sendIQ method to send iq packets, this way the id attribute will be added. 2014-12-02 15:21:12 +02:00
hristoterezov
edb89a65d5 Adds config property for enabling firefox support 2014-12-01 19:59:51 +02:00
hristoterezov
e2058edfdd Merge branch 'master' into firefox
Conflicts:
	libs/colibri/colibri.focus.js
	videolayout.js
2014-12-01 14:12:04 +02:00
hristoterezov
6cf96c5d72 Fixes an issue with black thumbnails 2014-12-01 13:15:36 +02:00
hristoterezov
abe3ef199f Fixes an issue with strange values of bitrate statistics. 2014-12-01 12:58:03 +02:00
Philipp Hancke
50b4f33207 Merge pull request #99 from jitsi/nackpli
add nack pli
2014-11-28 15:36:33 -08:00
Philipp Hancke
0663efe8cb Merge pull request #199 from jitsi/audioonly-again
try audio-only when GUM fails
2014-11-28 15:35:57 -08:00
hristoterezov
501c97b27c Implements firefox support for the rtp stats 2014-11-28 19:47:19 +02:00
George Politis
d4a5b3cf2e Fixes typo in comments. 2014-11-28 17:06:36 +01:00
George Politis
37bb4b82ad Fixes issue introduced by 71e290a8ad.
simulcastlayerschanged/ing should run if lastN is disabled.
2014-11-28 17:00:16 +01:00
paweldomas
b035bfc9aa Merge branch 'master' into ssfocus
Conflicts:
	index.html
	muc.js
	videolayout.js
2014-11-28 16:43:45 +01:00
paweldomas
6b968b1d14 Adds config options to the conference iq sent to the focus. 2014-11-28 16:31:01 +01:00
paweldomas
f06f4cd1ba Fix setting the mute icon(written by Boris Grozev). 2014-11-28 16:25:01 +01:00
paweldomas
b5ecdc8dee Sends peer connection stats to the focus(written by Boris Grozev). 2014-11-28 16:24:33 +01:00
paweldomas
7e90d73003 Saves PeerConnection statistics and prepares to send them over XMPP(written by Boris Grozev). 2014-11-28 16:23:57 +01:00
paweldomas
71b63cd0b3 Adds Pako (an implementation of zlib deflate). Written by Boris Grozev. 2014-11-28 16:23:17 +01:00
paweldomas
8f94ac8b09 Detects focus user in reliable way(through user real JID). Fixes kicked event when both 307 and 110 status codes are received in MUC presence. 2014-11-28 16:21:01 +01:00
paweldomas
871c661ba9 Adds support for muting audio on the bridge. 2014-11-28 16:20:43 +01:00
paweldomas
3f3046893e Ignore unrecognised 'default' stream. 2014-11-28 16:20:24 +01:00
paweldomas
0f6b6ae960 Adjusts recording button handling to the new focus(not finished). 2014-11-28 16:19:20 +01:00
paweldomas
cd6264d0df Adopts kick to new focus. 2014-11-28 16:18:58 +01:00
paweldomas
285096cc99 Uses separate exp backoff timers for 'not ready' and error responses from the focus. 2014-11-28 16:18:31 +01:00
paweldomas
84a453597c Reloads the page when focus leaves to dispose MUC room. Adds exponential backoff to focus polling. 2014-11-28 16:18:12 +01:00
paweldomas
7dc8102dee Replaces 'focus' occurrences with moderator for handling privileged functionalities. 2014-11-28 16:17:53 +01:00
paweldomas
ed2d7e4282 Fixes jshint warnings in toolbar_toggler.js 2014-11-28 16:17:13 +01:00
paweldomas
afaa96b737 Fixes merge mistake. 2014-11-28 16:16:55 +01:00
paweldomas
d8ebea0d8b Fixes some of shint warnings in toolbar.js 2014-11-28 16:16:27 +01:00
paweldomas
a0fef34a1f Removes unused vars from moderatemuc.js. and fixes jshint warnings. 2014-11-28 16:16:04 +01:00
paweldomas
9d3aef2efa Fixes code comment. 2014-11-28 16:15:29 +01:00
paweldomas
6646b2821a Adds MUC role change notifications. 2014-11-28 16:14:54 +01:00
paweldomas
613e18952b Waits for the focus to join first. 2014-11-28 16:05:43 +01:00
paweldomas
6afcfb2598 Basic recording. 2014-11-28 16:05:10 +01:00
paweldomas
4efad3d3da Advertises rtcp-mux and BUNDLE if it's enabled. 2014-11-28 16:03:35 +01:00
paweldomas
73571e7a18 Send media presence after session-accept. 2014-11-28 16:02:27 +01:00
paweldomas
f629ec17fa Focus and debug changes 2014-11-28 16:00:57 +01:00
George Politis
a8909028c8 Modifies the avatars, lastN and adaptive lastN handling so that they play nicely together. 2014-11-28 12:29:52 +01:00
fo
5b9b45c91b Fixes a bug when there is no active speaker currently. 2014-11-27 18:27:31 +02:00
fo
1d0b4d0ecb Fixes a bug with avatar when no id or email is sent with presence. 2014-11-27 18:14:43 +02:00
fo
1d4177faeb Adds a side panel toggler, settings menu, avatars, uuids. 2014-11-27 12:44:22 +02:00
hristoterezov
9b5edde621 Fixes the issue with the large video that doesn't fill the large video container. 2014-11-26 16:36:42 +02:00
hristoterezov
c6cc570f1d Fixes issues caused by the merge with master. Fixes the simulcast conferences on chrome. 2014-11-26 14:10:41 +02:00
Philipp Hancke
c438676eae Merge pull request #190 from jitsi/remove-old-bundle-plans
remove old bundle plan
2014-11-25 18:48:11 -08:00
hristoterezov
5213583af2 Merge branch 'master' into firefox
Conflicts:
	app.js
	libs/strophe/strophe.jingle.adapter.js
	simulcast.js
	videolayout.js
2014-11-25 17:01:46 +02:00
George Politis
7da0fd6794 Make it possible to pin a participant from the contact list, even if he's not in the lastN set. 2014-11-25 11:58:09 +01:00
George Politis
71e290a8ad Run simulcastlayerschanged/ing event handlers only if the affected endpoint is in lastN. 2014-11-25 11:57:59 +01:00
hristoterezov
03f828ba9a Fixes issues when participant leaves caused by removing src dependancies. Stops the tracks when stream is removed. 2014-11-24 18:14:35 +02:00
George Politis
092149711b videoSrcToSsrc stores strings 2014-11-24 12:00:15 +01:00
Philipp Hancke
728e7ce70b try audio-only when GUM fails 2014-11-22 14:51:23 -08:00
hristoterezov
99da618811 Removes browser checks. Optimizes the browser specific code. 2014-11-21 16:50:16 +02:00
hristoterezov
c2f6c9f7bc Removes the "src" attribute dependancies. Fixes the issues with not displaying the large video and thumbnails clicking not working on firefox. 2014-11-21 15:29:05 +02:00
George Politis
2004b6ef18 Fixes an issue where the pinned video src was getting set when it shouldn't. 2014-11-21 11:29:37 +01:00
George Politis
1ac99309e7 Hooks up participant pinning into lastN functionality. 2014-11-20 16:52:52 +01:00
George Politis
de7cc0b52b Implements local lastN set. 2014-11-20 16:52:52 +01:00
George Politis
bc6b48cce9 Fixes issue with adaptive lastN 2014-11-20 16:52:52 +01:00
George Politis
982c8cbfac Reverts 3c21b09fa4 2014-11-18 17:26:43 +01:00
George Politis
3fc819d12e Edits comment. 2014-11-18 17:23:28 +01:00
hristoterezov
96824e60ab Implements workaround for the issues in firefox nightly. 2014-11-18 18:12:12 +02:00
hristoterezov
46074dbd8c Merge pull request #195 from nwittstruck/api-events
Add https support for API
2014-11-17 17:41:03 +02:00
Nicholas Wittstruck
a4192b58db ssl support 2014-11-17 16:23:29 +01:00
Damian Minkov
c5bf2f86ca Fixes purge jitsi-meet-prosody (missing debconf configs). 2014-11-17 12:16:34 +02:00
Damian Minkov
13846b022c Fixes configuring certs if files already placed in /etc/ssl. 2014-11-17 11:44:24 +02:00
Damian Minkov
7475b3a66a Fixes purging config for jitsi-meet-prosody. 2014-11-17 11:44:23 +02:00
Boris Grozev
3239813ce7 Fix a double variable declaration. 2014-11-14 21:18:06 +02:00
Boris Grozev
d97a8c63f9 Fix converting a format with multiple parameters from Jingle to SDP. 2014-11-14 21:17:39 +02:00
hristoterezov
2e26c212a2 Fixes the issue with the chrome video that is not displayed in firefox. 2014-11-14 17:44:38 +02:00
hristoterezov
dd608080b3 Fixes the issue with not receiving video in chrome from firefox when trickle is enabled. 2014-11-14 13:56:02 +02:00
hristoterezov
f5189d5cdc Fixes firefox issues. The firefox video is displayed in chrome. 2014-11-14 12:13:26 +02:00
bgrozev
ab8e9ed87e Cleans the words list. 2014-11-13 22:46:44 +02:00
George Politis
78ef2a9266 Fixes simulcast on latest Chrome dev (40.0.2214.5). 2014-11-13 15:00:52 +01:00
bgrozev
071c01e644 Fix the direction string.
Used to return 'sendre' instead of 'sendrecv'.
2014-11-12 15:20:18 +02:00
George Politis
19e4955392 Fixes focus addSource/removeSource methods. 2014-11-12 11:41:11 +01:00
George Politis
3c21b09fa4 Fixes typo in comment. 2014-11-12 11:41:11 +01:00
hristoterezov
480d6f8c59 Enables the welcome page in config file. 2014-11-12 10:35:44 +02:00
George Politis
a0092b78ca Fixes desktop sharing when used with simulcast. 2014-11-11 15:50:46 +01:00
George Politis
ee1c221e6d Modifies a comment. 2014-11-11 10:26:34 +01:00
George Politis
1b5a3f08d2 Remove useless localStream field from NativeSimulcast and NoSimulcast classes. 2014-11-10 13:40:41 +01:00
George Politis
feffcd18de Improves simulcast logging. 2014-11-10 11:51:27 +01:00
George Politis
41fd416338 Don't add the x-goog-conference flag if there are no simulcast senders 2014-11-10 10:10:06 +01:00
George Politis
4a062e5f5c Don't add the x-goog-conference flag if there are no simulcast senders 2014-11-10 10:03:22 +01:00
bgrozev
1dab88e06a Merge pull request #191 from nwittstruck/master
Fixed typo - incoming instead of incomming
2014-11-08 17:05:08 +02:00
Nicholas Wittstruck
a6277b810e fixed typo 2014-11-08 15:41:16 +01:00
fo
5c9f4ba65b Changes the colors of the links in the chat. 2014-11-06 16:30:07 +02:00
hristoterezov
aa5c2c11ad Removes the chrome check. Fixes some minor firefox compatibility issues. 2014-11-06 15:54:47 +02:00
fo
1057c6dd0d Removes the notifications for entering/leaving lastN. 2014-11-06 13:04:04 +02:00
hristoterezov
32e6a9a5d1 Merge pull request #189 from Zalmoxisus/master
Escape tags to fix issue #182
2014-11-06 11:10:42 +02:00
Philipp Hancke
4a115ee215 remove old ssrc code as well 2014-11-05 18:56:31 -08:00
Philipp Hancke
6c7dac23ec remove old bundle plan 2014-11-05 12:18:20 -08:00
Zalmoxisus
6a489de167 Escape tags to fix issue #182 2014-11-05 12:45:55 +02:00
fo
f2a310f6c3 Adds notifications when a user joins/leaves or is added/removed from lastN. 2014-11-05 10:57:27 +02:00
hristoterezov
6861dc967f Removes "," from the last element in config.js 2014-11-04 16:15:34 +02:00
Philipp Hancke
0896930f65 add nack pli 2014-08-19 15:50:53 +02:00
169 changed files with 43998 additions and 13083 deletions

1
.gitattributes vendored Normal file
View File

@@ -0,0 +1 @@
*.bundle.js -text -diff

3
.gitignore vendored
View File

@@ -1 +1,4 @@
node_modules
*.swp
.idea/
*.iml

24
Makefile Normal file
View File

@@ -0,0 +1,24 @@
NPM = npm
BROWSERIFY = browserify
GLOBAL_FLAGS = -x jquery -e
OUTPUT_DIR = .
DEPLOY_DIR = libs
all: compile deploy clean
compile:FLAGS = $(GLOBAL_FLAGS)
compile: app
debug: compile-debug deploy clean
compile-debug:FLAGS = -d $(GLOBAL_FLAGS)
compile-debug: app
app:
$(NPM) update && $(BROWSERIFY) $(FLAGS) app.js -s APP -o $(OUTPUT_DIR)/app.bundle.js
clean:
@rm -f $(OUTPUT_DIR)/*.bundle.js
deploy:
@mkdir -p $(DEPLOY_DIR) && cp $(OUTPUT_DIR)/*.bundle.js $(DEPLOY_DIR)

View File

@@ -1,18 +1,33 @@
Jitsi Meet - Secure, Simple and Scalable Video Conferences
====
Jitsi Meet is an OpenSource (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 (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.
You can also try it out yourself at https://meet.jit.si .
Jitsi Meet allows for very efficient collaboration. It allows users to stream their desktop or only some windows. It also supports shared document editing with Etherpad and remote presentations with Prezi.
## Install
## Installation
Installing Jitsi Meet is quite a simple experience even though it requires installing a few other components first, such as Jitsi Videobridge, a web server such as Nginx and an XMPP one like Prosody.
Installing Jitsi Meet is quite a simple experience. For Debian-based systems, we recommend following the [quick-install](https://github.com/jitsi/jitsi-meet/blob/master/doc/quick-install.md) document, which uses the package system.
You can find information on how to deploy Jitsi Meet in the [installation instructions](https://jitsi.org/meet/deploy)
For other systems, or if you wish to install all components manually, see the [detailed manual installation instructions](https://github.com/jitsi/jitsi-meet/blob/master/doc/manual-install.md).
You may also find it helpful to have a look at our sample [config files](https://github.com/jitsi/jitsi-meet/tree/master/doc/example-config-files/)
## Building the sources
Jitsi Meet uses [Browserify](http://browserify.org). If you want to make changes in the code you need to [install Browserify](http://browserify.org/#install). Browserify requires [nodejs](http://nodejs.org).
On Debian/Ubuntu systems, the required packages can be installed with:
```
sudo apt-get install npm
sudo npm install -g browserify
cd jitsi-meet
npm install
```
To build the Jitsi Meet application, just type
```
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.

View File

@@ -1,202 +0,0 @@
/**
* Implements API class that communicates with external api class
* and provides interface to access Jitsi Meet features by external
* applications that embed Jitsi Meet
*/
var APIConnector = (function () {
function APIConnector() { }
/**
* List of the available commands.
* @type {{
* displayName: inputDisplayNameHandler,
* muteAudio: toggleAudio,
* muteVideo: toggleVideo,
* filmStrip: toggleFilmStrip
* }}
*/
var commands =
{
displayName: VideoLayout.inputDisplayNameHandler,
muteAudio: toggleAudio,
muteVideo: toggleVideo,
toggleFilmStrip: BottomToolbar.toggleFilmStrip,
toggleChat: BottomToolbar.toggleChat,
toggleContactList: BottomToolbar.toggleContactList
};
/**
* Maps the supported events and their status
* (true it the event is enabled and false if it is disabled)
* @type {{
* incommingMessage: boolean,
* outgoingMessage: boolean,
* displayNameChange: boolean,
* participantJoined: boolean,
* participantLeft: boolean
* }}
*/
var events =
{
incommingMessage: false,
outgoingMessage:false,
displayNameChange: false,
participantJoined: false,
participantLeft: false
};
/**
* Check whether the API should be enabled or not.
* @returns {boolean}
*/
APIConnector.isEnabled = function () {
var hash = location.hash;
if(hash && hash.indexOf("external") > -1 && window.postMessage)
return true;
return false;
};
/**
* Initializes the APIConnector. Setups message event listeners that will
* receive information from external applications that embed Jitsi Meet.
* It also sends a message to the external application that APIConnector
* is initialized.
*/
APIConnector.init = function () {
if (window.addEventListener)
{
window.addEventListener('message',
APIConnector.processMessage, false);
}
else
{
window.attachEvent('onmessage', APIConnector.processMessage);
}
APIConnector.sendMessage({type: "system", loaded: true});
};
/**
* Sends message to the external application.
* @param object
*/
APIConnector.sendMessage = function (object) {
window.parent.postMessage(JSON.stringify(object), "*");
};
/**
* Processes a message event from the external application
* @param event the message event
*/
APIConnector.processMessage = function(event)
{
var message;
try {
message = JSON.parse(event.data);
} catch (e) {}
if(!message.type)
return;
switch (message.type)
{
case "command":
APIConnector.processCommand(message);
break;
case "event":
APIConnector.processEvent(message);
break;
default:
console.error("Unknown type of the message");
return;
}
};
/**
* Processes commands from external applicaiton.
* @param message the object with the command
*/
APIConnector.processCommand = function (message)
{
if(message.action != "execute")
{
console.error("Unknown action of the message");
return;
}
for(var key in message)
{
if(commands[key])
commands[key].apply(null, message[key]);
}
};
/**
* Processes events objects from external applications
* @param event the event
*/
APIConnector.processEvent = function (event) {
if(!event.action)
{
console.error("Event with no action is received.");
return;
}
switch(event.action)
{
case "add":
for(var i = 0; i < event.events.length; i++)
{
events[event.events[i]] = true;
}
break;
case "remove":
for(var i = 0; i < event.events.length; i++)
{
events[event.events[i]] = false;
}
break;
default:
console.error("Unknown action for event.");
}
};
/**
* Checks whether the event is enabled ot not.
* @param name the name of the event.
* @returns {*}
*/
APIConnector.isEventEnabled = function (name) {
return events[name];
};
/**
* Sends event object to the external application that has been subscribed
* for that event.
* @param name the name event
* @param object data associated with the event
*/
APIConnector.triggerEvent = function (name, object) {
APIConnector.sendMessage({
type: "event", action: "result", event: name, result: object});
};
/**
* Removes the listeners.
*/
APIConnector.dispose = function () {
if(window.removeEventListener)
{
window.removeEventListener("message",
APIConnector.processMessage, false);
}
else
{
window.detachEvent('onmessage', APIConnector.processMessage);
}
};
return APIConnector;
})();

1606
app.js

File diff suppressed because it is too large Load Diff

View File

@@ -1,42 +0,0 @@
var BottomToolbar = (function (my) {
my.toggleChat = function() {
if (ContactList.isVisible()) {
buttonClick("#contactListButton", "active");
$('#contactlist').css('z-index', 4);
setTimeout(function() {
$('#contactlist').css('display', 'none');
$('#contactlist').css('z-index', 5);
}, 500);
}
Chat.toggleChat();
buttonClick("#chatBottomButton", "active");
};
my.toggleContactList = function() {
if (Chat.isVisible()) {
buttonClick("#chatBottomButton", "active");
setTimeout(function() {
$('#chatspace').css('display', 'none');
}, 500);
}
buttonClick("#contactListButton", "active");
ContactList.toggleContactList();
};
my.toggleFilmStrip = function() {
var filmstrip = $("#remoteVideos");
filmstrip.toggleClass("hidden");
};
$(document).bind("remotevideo.resized", function (event, width, height) {
var bottom = (height - $('#bottomToolbar').outerHeight())/2 + 18;
$('#bottomToolbar').css({bottom: bottom + 'px'});
});
return my;
}(BottomToolbar || {}));

452
chat.js
View File

@@ -1,452 +0,0 @@
/* global $, Util, connection, nickname:true, getVideoSize, getVideoPosition, showToolbar, processReplacements */
/**
* Chat related user interface.
*/
var Chat = (function (my) {
var notificationInterval = false;
var unreadMessages = 0;
/**
* Initializes chat related interface.
*/
my.init = function () {
var storedDisplayName = window.localStorage.displayname;
if (storedDisplayName) {
nickname = storedDisplayName;
Chat.setChatConversationMode(true);
}
$('#nickinput').keydown(function (event) {
if (event.keyCode === 13) {
event.preventDefault();
var val = Util.escapeHtml(this.value);
this.value = '';
if (!nickname) {
nickname = val;
window.localStorage.displayname = nickname;
connection.emuc.addDisplayNameToPresence(nickname);
connection.emuc.sendPresence();
Chat.setChatConversationMode(true);
return;
}
}
});
$('#usermsg').keydown(function (event) {
if (event.keyCode === 13) {
event.preventDefault();
var value = this.value;
$('#usermsg').val('').trigger('autosize.resize');
this.focus();
var command = new CommandsProcessor(value);
if(command.isCommand())
{
command.processCommand();
}
else
{
var message = Util.escapeHtml(value);
connection.emuc.sendMessage(message, nickname);
}
}
});
var onTextAreaResize = function () {
resizeChatConversation();
scrollChatToBottom();
};
$('#usermsg').autosize({callback: onTextAreaResize});
$("#chatspace").bind("shown",
function () {
unreadMessages = 0;
setVisualNotification(false);
});
addSmileys();
};
/**
* Appends the given message to the chat conversation.
*/
my.updateChatConversation = function (from, displayName, message) {
var divClassName = '';
if (connection.emuc.myroomjid === from) {
divClassName = "localuser";
}
else {
divClassName = "remoteuser";
if (!Chat.isVisible()) {
unreadMessages++;
Util.playSoundNotification('chatNotification');
setVisualNotification(true);
}
}
//replace links and smileys
var escMessage = Util.escapeHtml(message);
var escDisplayName = Util.escapeHtml(displayName);
message = processReplacements(escMessage);
var messageContainer =
'<div class="chatmessage">'+
'<img src="../images/chatArrow.svg" class="chatArrow">' +
'<div class="username ' + divClassName +'">' + escDisplayName + '</div>' +
'<div class="timestamp">' + getCurrentTime() + '</div>' +
'<div class="usermessage">' + message + '</div>' +
'</div>';
$('#chatconversation').append(messageContainer);
$('#chatconversation').animate(
{ scrollTop: $('#chatconversation')[0].scrollHeight}, 1000);
};
/**
* Appends error message to the conversation
* @param errorMessage the received error message.
* @param originalText the original message.
*/
my.chatAddError = function(errorMessage, originalText)
{
errorMessage = Util.escapeHtml(errorMessage);
originalText = Util.escapeHtml(originalText);
$('#chatconversation').append('<div class="errorMessage"><b>Error: </b>'
+ 'Your message' + (originalText? (' \"'+ originalText + '\"') : "")
+ ' was not sent.' + (errorMessage? (' Reason: ' + errorMessage) : '')
+ '</div>');
$('#chatconversation').animate(
{ scrollTop: $('#chatconversation')[0].scrollHeight}, 1000);
};
/**
* Sets the subject to the UI
* @param subject the subject
*/
my.chatSetSubject = function(subject)
{
if(subject)
subject = subject.trim();
$('#subject').html(linkify(Util.escapeHtml(subject)));
if(subject == "")
{
$("#subject").css({display: "none"});
}
else
{
$("#subject").css({display: "block"});
}
};
/**
* Opens / closes the chat area.
*/
my.toggleChat = function () {
var chatspace = $('#chatspace');
var videospace = $('#videospace');
var chatSize = (Chat.isVisible()) ? [0, 0] : Chat.getChatSize();
var videospaceWidth = window.innerWidth - chatSize[0];
var videospaceHeight = window.innerHeight;
var videoSize
= getVideoSize(null, null, videospaceWidth, videospaceHeight);
var videoWidth = videoSize[0];
var videoHeight = videoSize[1];
var videoPosition = 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];
var completeFunction = Chat.isVisible() ?
function() {} : function () {
scrollChatToBottom();
chatspace.trigger('shown');
};
videospace.animate({right: chatSize[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
}
);
if (Chat.isVisible()) {
chatspace.hide("slide", { direction: "right",
queue: false,
duration: 500});
}
else {
// Undock the toolbar when the chat is shown and if we're in a
// video mode.
if (VideoLayout.isLargeVideoVisible()) {
ToolbarToggler.dockToolbar(false);
}
chatspace.show("slide", { direction: "right",
queue: false,
duration: 500,
complete: function () {
// Request the focus in the nickname field or the chat input field.
if ($('#nickname').css('visibility') === 'visible') {
$('#nickinput').focus();
} else {
$('#usermsg').focus();
}
}
});
Chat.resizeChat();
}
};
/**
* Sets the chat conversation mode.
*/
my.setChatConversationMode = function (isConversationMode) {
if (isConversationMode) {
$('#nickname').css({visibility: 'hidden'});
$('#chatconversation').css({visibility: 'visible'});
$('#usermsg').css({visibility: 'visible'});
$('#smileysarea').css({visibility: 'visible'});
$('#usermsg').focus();
}
};
/**
* Resizes the chat area.
*/
my.resizeChat = function () {
var chatSize = Chat.getChatSize();
$('#chatspace').width(chatSize[0]);
$('#chatspace').height(chatSize[1]);
resizeChatConversation();
};
/**
* Returns the size of the chat.
*/
my.getChatSize = function () {
var availableHeight = window.innerHeight;
var availableWidth = window.innerWidth;
var chatWidth = 200;
if (availableWidth * 0.2 < 200)
chatWidth = availableWidth * 0.2;
return [chatWidth, availableHeight];
};
/**
* Indicates if the chat is currently visible.
*/
my.isVisible = function () {
return $('#chatspace').is(":visible");
};
/**
* Shows and hides the window with the smileys
*/
my.toggleSmileys = function() {
var smileys = $('#smileysContainer');
if(!smileys.is(':visible')) {
smileys.show("slide", { direction: "down", duration: 300});
} else {
smileys.hide("slide", { direction: "down", duration: 300});
}
$('#usermsg').focus();
};
/**
* Adds the smileys container to the chat
*/
function addSmileys() {
var smileysContainer = document.createElement('div');
smileysContainer.id = 'smileysContainer';
function addClickFunction(smiley, number) {
smiley.onclick = function addSmileyToMessage() {
var usermsg = $('#usermsg');
var message = usermsg.val();
message += smileys['smiley' + number];
usermsg.val(message);
usermsg.get(0).setSelectionRange(message.length, message.length);
Chat.toggleSmileys();
usermsg.focus();
};
}
for(var i = 1; i <= 21; i++) {
var smileyContainer = document.createElement('div');
smileyContainer.id = 'smiley' + i;
smileyContainer.className = 'smileyContainer';
var smiley = document.createElement('img');
smiley.src = 'images/smileys/smiley' + i + '.svg';
smiley.className = 'smiley';
addClickFunction(smiley, i);
smileyContainer.appendChild(smiley);
smileysContainer.appendChild(smileyContainer);
}
$("#chatspace").append(smileysContainer);
}
/**
* Resizes the chat conversation.
*/
function resizeChatConversation() {
var msgareaHeight = $('#usermsg').outerHeight();
var chatspace = $('#chatspace');
var width = chatspace.width();
var chat = $('#chatconversation');
var smileys = $('#smileysarea');
smileys.height(msgareaHeight);
$("#smileys").css('bottom', (msgareaHeight - 26) / 2);
$('#smileysContainer').css('bottom', msgareaHeight);
chat.width(width - 10);
chat.height(window.innerHeight - 15 - msgareaHeight);
}
/**
* Shows/hides a visual notification, indicating that a message has arrived.
*/
function setVisualNotification(show) {
var unreadMsgElement = document.getElementById('unreadMessages');
var unreadMsgBottomElement = document.getElementById('bottomUnreadMessages');
var glower = $('#chatButton');
var bottomGlower = $('#chatBottomButton');
if (unreadMessages) {
unreadMsgElement.innerHTML = unreadMessages.toString();
unreadMsgBottomElement.innerHTML = unreadMessages.toString();
ToolbarToggler.dockToolbar(true);
var chatButtonElement
= document.getElementById('chatButton').parentNode;
var leftIndent = (Util.getTextWidth(chatButtonElement) -
Util.getTextWidth(unreadMsgElement)) / 2;
var topIndent = (Util.getTextHeight(chatButtonElement) -
Util.getTextHeight(unreadMsgElement)) / 2 - 3;
unreadMsgElement.setAttribute(
'style',
'top:' + topIndent +
'; left:' + leftIndent + ';');
var chatBottomButtonElement
= document.getElementById('chatBottomButton').parentNode;
var bottomLeftIndent = (Util.getTextWidth(chatBottomButtonElement) -
Util.getTextWidth(unreadMsgBottomElement)) / 2;
var bottomTopIndent = (Util.getTextHeight(chatBottomButtonElement) -
Util.getTextHeight(unreadMsgBottomElement)) / 2 - 2;
unreadMsgBottomElement.setAttribute(
'style',
'top:' + bottomTopIndent +
'; left:' + bottomLeftIndent + ';');
if (!glower.hasClass('icon-chat-simple')) {
glower.removeClass('icon-chat');
glower.addClass('icon-chat-simple');
}
}
else {
unreadMsgElement.innerHTML = '';
unreadMsgBottomElement.innerHTML = '';
glower.removeClass('icon-chat-simple');
glower.addClass('icon-chat');
}
if (show && !notificationInterval) {
notificationInterval = window.setInterval(function () {
glower.toggleClass('active');
bottomGlower.toggleClass('active glowing');
}, 800);
}
else if (!show && notificationInterval) {
window.clearInterval(notificationInterval);
notificationInterval = false;
glower.removeClass('active');
bottomGlower.removeClass('glowing');
bottomGlower.addClass('active');
}
}
/**
* Scrolls chat to the bottom.
*/
function scrollChatToBottom() {
setTimeout(function () {
$('#chatconversation').scrollTop(
$('#chatconversation')[0].scrollHeight);
}, 5);
}
/**
* Returns the current time in the format it is shown to the user
* @returns {string}
*/
function getCurrentTime() {
var now = new Date();
var hour = now.getHours();
var minute = now.getMinutes();
var second = now.getSeconds();
if(hour.toString().length === 1) {
hour = '0'+hour;
}
if(minute.toString().length === 1) {
minute = '0'+minute;
}
if(second.toString().length === 1) {
second = '0'+second;
}
return hour+':'+minute+':'+second;
}
return my;
}(Chat || {}));

View File

@@ -1,20 +0,0 @@
<html>
<head>
<title>Jitsi Meet: Unsupported Browser</title>
<link rel="stylesheet" type="text/css" media="screen" href="css/chromeonly.css" />
</head>
<body>
<!-- wrap starts here -->
<div id="wrap">
<a href="http://google.com/chrome"><div id="left"></div></a>
<div id="middle"></div>
<div id="text">
<p>This application is currently only supported by <a href="http://google.com/chrome">Chrome</a>, <a href="http://www.chromium.org/">Chromium</a> and <a href="http://www.opera.com">Opera</a></p>
<p><a href="http://google.com/chrome">Download Chrome</a></p>
<p class="firefox">We are hoping that <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=977864">multistream support</a> for Firefox would not be long so that we could all use this application with our favorite browser.</p>
</div>
<!-- wrap ends here -->
</div>
</body>
</html>

View File

@@ -1,98 +0,0 @@
/**
* Handles commands received via chat messages.
*/
var CommandsProcessor = (function()
{
/**
* Constructs new CommandProccessor instance from a message.
* @param message the message
* @constructor
*/
function CommandsPrototype(message)
{
/**
* Extracts the command from the message.
* @param message the received message
* @returns {string} the command
*/
function getCommand(message)
{
if(message)
{
for(var command in commands)
{
if(message.indexOf("/" + command) == 0)
return command;
}
}
return "";
};
var command = getCommand(message);
/**
* Returns the name of the command.
* @returns {String} the command
*/
this.getCommand = function()
{
return command;
}
var messageArgument = message.substr(command.length + 2);
/**
* Returns the arguments of the command.
* @returns {string}
*/
this.getArgument = function()
{
return messageArgument;
}
}
/**
* Checks whether this instance is valid command or not.
* @returns {boolean}
*/
CommandsPrototype.prototype.isCommand = function()
{
if(this.getCommand())
return true;
return false;
}
/**
* Processes the command.
*/
CommandsPrototype.prototype.processCommand = function()
{
if(!this.isCommand())
return;
commands[this.getCommand()](this.getArgument());
}
/**
* Processes the data for topic command.
* @param commandArguments the arguments of the topic command.
*/
var processTopic = function(commandArguments)
{
var topic = Util.escapeHtml(commandArguments);
connection.emuc.setSubject(topic);
}
/**
* List with supported commands. The keys are the names of the commands and
* the value is the function that processes the message.
* @type {{String: function}}
*/
var commands = {
"topic" : processTopic
};
return CommandsPrototype;
})();

View File

@@ -4,7 +4,9 @@ var config = {
//anonymousdomain: 'guest.example.com',
muc: 'conference.jitsi-meet.example.com', // FIXME: use XEP-0030
bridge: 'jitsi-videobridge.jitsi-meet.example.com', // FIXME: use XEP-0030
//call_control: 'callcontrol.jitsi-meet.example.com'
//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'
},
// getroomnode: function (path) { return 'someprefixpossiblybasedonpath'; },
// useStunTurn: true, // use XEP-0215 to fetch STUN and TURN server
@@ -12,19 +14,24 @@ var config = {
useNicks: false,
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: '', // Default SIP number
desktopSharing: 'ext', // Desktop sharing method. Can be set to 'ext', 'webrtc' or false to disable.
chromeExtensionId: 'diibjkoicjeejcmhdnailmkgecihlobk', // Id of desktop streamer Chrome extension
desktopSharingSources: ['screen', 'window'],
minChromeExtVersion: '0.1', // Required version of Chrome extension
enableRtpStats: true, // Enables RTP stats processing
openSctp: true, // Toggle to enable/disable SCTP channels
disableStats: false,
disableAudioLevels: false,
channelLastN: -1, // The default value of the channel attribute last-n.
adaptiveLastN: false,
adaptiveSimulcast: false,
useRtcpMux: true,
useBundle: true,
enableRecording: false,
enableWelcomePage: 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
};

View File

@@ -1,127 +0,0 @@
var ConnectionQuality = (function () {
/**
* Constructs new ConnectionQuality object
* @constructor
*/
function ConnectionQuality() {
}
/**
* local stats
* @type {{}}
*/
var stats = {};
/**
* remote stats
* @type {{}}
*/
var remoteStats = {};
/**
* Interval for sending statistics to other participants
* @type {null}
*/
var sendIntervalId = null;
/**
* Updates the local statistics
* @param data new statistics
*/
ConnectionQuality.updateLocalStats = function (data) {
stats = data;
VideoLayout.updateLocalConnectionStats(100 - stats.packetLoss.total,stats);
if(sendIntervalId == null)
{
startSendingStats();
}
};
/**
* Start statistics sending.
*/
function startSendingStats() {
sendStats();
sendIntervalId = setInterval(sendStats, 10000);
}
/**
* Sends statistics to other participants
*/
function sendStats() {
connection.emuc.addConnectionInfoToPresence(convertToMUCStats(stats));
connection.emuc.sendPresence();
}
/**
* Converts statistics to format for sending through XMPP
* @param stats the statistics
* @returns {{bitrate_donwload: *, bitrate_uplpoad: *, packetLoss_total: *, packetLoss_download: *, packetLoss_upload: *}}
*/
function convertToMUCStats(stats) {
return {
"bitrate_download": stats.bitrate.download,
"bitrate_upload": stats.bitrate.upload,
"packetLoss_total": stats.packetLoss.total,
"packetLoss_download": stats.packetLoss.download,
"packetLoss_upload": stats.packetLoss.upload
};
}
/**
* Converts statitistics to format used by VideoLayout
* @param stats
* @returns {{bitrate: {download: *, upload: *}, packetLoss: {total: *, download: *, upload: *}}}
*/
function parseMUCStats(stats) {
return {
bitrate: {
download: stats.bitrate_download,
upload: stats.bitrate_upload
},
packetLoss: {
total: stats.packetLoss_total,
download: stats.packetLoss_download,
upload: stats.packetLoss_upload
}
};
}
/**
* Updates remote statistics
* @param jid the jid associated with the statistics
* @param data the statistics
*/
ConnectionQuality.updateRemoteStats = function (jid, data) {
if(data == null || data.packetLoss_total == null)
{
VideoLayout.updateConnectionStats(jid, null, null);
return;
}
remoteStats[jid] = parseMUCStats(data);
VideoLayout.updateConnectionStats(jid, 100 - data.packetLoss_total,remoteStats[jid]);
};
/**
* Stops statistics sending.
*/
ConnectionQuality.stopSendingStats = function () {
clearInterval(sendIntervalId);
sendIntervalId = null;
//notify UI about stopping statistics gathering
VideoLayout.onStatsStop();
};
/**
* Returns the local statistics.
*/
ConnectionQuality.getStats = function () {
return stats;
}
return ConnectionQuality;
})();

View File

@@ -1,275 +0,0 @@
/**
* Contact list.
*/
var ContactList = (function (my) {
var numberOfContacts = 0;
var notificationInterval;
/**
* Indicates if the chat is currently visible.
*
* @return <tt>true</tt> if the chat is currently visible, <tt>false</tt> -
* otherwise
*/
my.isVisible = function () {
return $('#contactlist').is(":visible");
};
/**
* Adds a contact for the given peerJid if such doesn't yet exist.
*
* @param peerJid the peerJid corresponding to the contact
*/
my.ensureAddContact = function(peerJid) {
var resourceJid = Strophe.getResourceFromJid(peerJid);
var contact = $('#contactlist>ul>li[id="' + resourceJid + '"]');
if (!contact || contact.length <= 0)
ContactList.addContact(peerJid);
};
/**
* Adds a contact for the given peer jid.
*
* @param peerJid the jid of the contact to add
*/
my.addContact = function(peerJid) {
var resourceJid = Strophe.getResourceFromJid(peerJid);
var contactlist = $('#contactlist>ul');
var newContact = document.createElement('li');
newContact.id = resourceJid;
newContact.className = "clickable";
newContact.onclick = function(event) {
if(event.currentTarget.className === "clickable") {
var jid = event.currentTarget.id;
var videoContainer = $("#participant_" + jid);
if (videoContainer.length > 0) {
videoContainer.click();
} else if (jid == Strophe.getResourceFromJid(connection.emuc.myroomjid)) {
$("#localVideoContainer").click();
}
}
};
newContact.appendChild(createAvatar());
newContact.appendChild(createDisplayNameParagraph("Participant"));
var clElement = contactlist.get(0);
if (resourceJid === Strophe.getResourceFromJid(connection.emuc.myroomjid)
&& $('#contactlist>ul .title')[0].nextSibling.nextSibling)
{
clElement.insertBefore(newContact,
$('#contactlist>ul .title')[0].nextSibling.nextSibling);
}
else {
clElement.appendChild(newContact);
}
updateNumberOfParticipants(1);
};
/**
* Removes a contact for the given peer jid.
*
* @param peerJid the peerJid corresponding to the contact to remove
*/
my.removeContact = function(peerJid) {
var resourceJid = Strophe.getResourceFromJid(peerJid);
var contact = $('#contactlist>ul>li[id="' + resourceJid + '"]');
if (contact && contact.length > 0) {
var contactlist = $('#contactlist>ul');
contactlist.get(0).removeChild(contact.get(0));
updateNumberOfParticipants(-1);
}
};
/**
* Opens / closes the contact list area.
*/
my.toggleContactList = function () {
var contactlist = $('#contactlist');
var videospace = $('#videospace');
var chatSize = (ContactList.isVisible()) ? [0, 0] : Chat.getChatSize();
var videospaceWidth = window.innerWidth - chatSize[0];
var videospaceHeight = window.innerHeight;
var videoSize
= getVideoSize(null, null, videospaceWidth, videospaceHeight);
var videoWidth = videoSize[0];
var videoHeight = videoSize[1];
var videoPosition = 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];
var completeFunction = ContactList.isVisible() ?
function() {} : function () { contactlist.trigger('shown');};
videospace.animate({right: chatSize[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
});
if (ContactList.isVisible()) {
$('#contactlist').hide("slide", { direction: "right",
queue: false,
duration: 500});
} else {
// Undock the toolbar when the chat is shown and if we're in a
// video mode.
if (VideoLayout.isLargeVideoVisible())
ToolbarToggler.dockToolbar(false);
$('#contactlist').show("slide", { direction: "right",
queue: false,
duration: 500});
//stop the glowing of the contact list icon
setVisualNotification(false);
}
};
/**
* Updates the number of participants in the contact list button and sets
* the glow
* @param delta indicates whether a new user has joined (1) or someone has
* left(-1)
*/
function updateNumberOfParticipants(delta) {
//when the user is alone we don't show the number of participants
if(numberOfContacts === 0) {
$("#numberOfParticipants").text('');
numberOfContacts += delta;
} else if(numberOfContacts !== 0 && !ContactList.isVisible()) {
setVisualNotification(true);
numberOfContacts += delta;
$("#numberOfParticipants").text(numberOfContacts);
}
};
/**
* Creates the avatar element.
*
* @return the newly created avatar element
*/
function createAvatar() {
var avatar = document.createElement('i');
avatar.className = "icon-avatar avatar";
return avatar;
}
/**
* Creates the display name paragraph.
*
* @param displayName the display name to set
*/
function createDisplayNameParagraph(displayName) {
var p = document.createElement('p');
p.innerHTML = displayName;
return p;
}
/**
* Shows/hides a visual notification, indicating that a new user has joined
* the conference.
*/
function setVisualNotification(show, stopGlowingIn) {
var glower = $('#contactListButton');
function stopGlowing() {
window.clearInterval(notificationInterval);
notificationInterval = false;
glower.removeClass('glowing');
if(!ContactList.isVisible()) {
glower.removeClass('active');
}
}
if (show && !notificationInterval) {
notificationInterval = window.setInterval(function () {
glower.toggleClass('active glowing');
}, 800);
}
else if (!show && notificationInterval) {
stopGlowing();
}
if(stopGlowingIn) {
setTimeout(stopGlowing, stopGlowingIn);
}
}
/**
* Indicates that the display name has changed.
*/
$(document).bind( 'displaynamechanged',
function (event, peerJid, displayName) {
if (peerJid === 'localVideoContainer')
peerJid = connection.emuc.myroomjid;
var resourceJid = Strophe.getResourceFromJid(peerJid);
var contactName = $('#contactlist #' + resourceJid + '>p');
if (contactName && displayName && displayName.length > 0)
contactName.html(displayName);
});
my.setClickable = function(resourceJid, isClickable) {
var contact = $('#contactlist>ul>li[id="' + resourceJid + '"]');
if(isClickable) {
contact.addClass('clickable');
} else {
contact.removeClass('clickable');
}
};
return my;
}(ContactList || {}));

View File

@@ -221,3 +221,19 @@
#usermsg::-webkit-scrollbar-track-piece {
background: #3a3a3a;
}
a:link {
color: rgb(184, 184, 184);
}
a:visited {
color: white;
}
a:hover {
color: rgb(213, 213, 213);
}
a:active {
color: black;
}

View File

@@ -1,54 +0,0 @@
body {
width:100%;
height:100%;
background-color: white;
color: #424242;
font-family:'YanoneKaffeesatzLight',Verdana,Tahoma,Arial;
margin:0;
padding:0;
}
#wrap{
display: block;
position: absolute;
width:900px;
height: 262px;
overflow:hidden;
text-align: center;
margin: auto;
top: 0; left: 0; bottom: 0; right: 0;
}
#left{
display:inline-block;
background-image:url(../images/chromelogo.png);
background-repeat:no-repeat;
width:246px;
height:262px;
float: left;
}
.firefox{
font-size: 11pt;
color: #c8c8c8;
}
#middle{
display:inline-block;
background-image:url(../images/chromepointer.png);
background-repeat:no-repeat;
width:53px;
height:262px;
float: left;
}
#text{
display:inline-block;
font-size: 18pt;
width: 560px;
vertical-align:middle;
padding-top: 30px;
}
a {
color: #087dba;
text-decoration:none;
}

View File

@@ -34,6 +34,7 @@
margin-right: 10px;
vertical-align: middle;
font-size: 22pt;
border-radius: 20px;
}
#contactlist .clickable {

View File

@@ -42,6 +42,9 @@
.icon-recEnable:before {
content: "\e614";
}
.icon-authenticate:before {
content: "\e1ae";
}
.icon-kick1:before {
content: "\e60f";
}
@@ -112,4 +115,8 @@
.icon-connection:before {
line-height: normal;
content: "\e61a";
}
.icon-settings:before {
content: "\e61b";
}

View File

@@ -99,5 +99,7 @@
width: 90px;
height: 16px;
padding-top: 4px;
padding-left: 10px;
padding-right: 10px;
margin: 15px auto 0px auto;
}

61
css/login_menu.css Normal file
View File

@@ -0,0 +1,61 @@
/*Initialize*/
ul.loginmenu {
display:none;
position: absolute;
margin: 0;
padding: 5px;
padding-bottom: 7px;
top: 45px;
left: -5px;
background-color: rgba(0,0,0,0.9);
border: 1px solid rgba(256, 256, 256, 0.2);
border-radius:8px;
}
ul.loginmenu li {
list-style-type: none;
padding: 7px;
}
ul.loginmenu li.identity {
color: #fff;
font-size: 11pt;
cursor: default;
}
ul.loginmenu:after {
content: url('../images/dropdownPointer.png');
display: block;
position: absolute;
top: -7px;
left: 18px;
}
li a.authButton{
background-color: #06a5df;
padding-top: 3px;
padding-bottom: 3px;
padding-left: 29px;
padding-right: 29px;
border-radius: 4px;
color: #fff;
font-size: 11pt;
cursor: pointer;
}
span.authentication:hover ul.loginmenu, ul.loginmenu:hover {
display:block !important;
}
a.disabled {
color: gray !important;
pointer-events: none;
}
.loginmenuPadding {
width: 50px;
height: 30px;
position: absolute;
top: -30px;
left: 0px;
}

View File

@@ -13,8 +13,7 @@ html, body{
overflow-x: hidden;
}
#chatspace,
#contactlist {
.right-panel {
display:none;
position:absolute;
float: right;
@@ -38,10 +37,6 @@ html, body{
display:none;
}
#settingsButton {
visibility: hidden;
}
.toolbar_span {
display: inline-block;
position: relative;
@@ -311,3 +306,56 @@ form {
text-decoration: none;
z-index: 100;
}
#toast-container.notification-bottom-right {
bottom: 120px;
right: 5px;
}
#toast-container.notification-bottom-right-center {
right: 205px;
}
#toast-container .toast-info {
-webkit-box-shadow: none;
box-shadow: none;
}
.toast-close-button {
right: -7px;
top: -19px;
}
#toast-container .toast-info {
background-color: black;
border: 1px solid #3a3a3a;
width: 220px;
padding: 10px 10px 10px 50px;
}
.connected {
color: forestgreen;
font-size: 12px;
}
.disconnected {
color: darkred;
font-size: 12px;
}
.lastN {
color: #a3a3a3;
font-size: 12px;
}
.toast-close-button:hover,
.toast-close-button:focus {
color: #ffffff;
opacity: 1;
-ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=100);
filter: alpha(opacity=100);
}
.toast-message .nickname {
font-weight: bold;
}

49
css/settingsmenu.css Normal file
View File

@@ -0,0 +1,49 @@
#settingsmenu {
background: black;
color: #00ccff;
}
#settingsmenu input, select {
margin-top: 10px;
margin-left: 10%;
width: 80%;
font-size: 14px;
background: #3a3a3a;
border: none;
box-shadow: none;
color: #a7a7a7;
}
#settingsmenu .arrow-up {
width: 0;
height: 0;
border-left: 5px solid transparent;
border-right: 5px solid transparent;
border-bottom: 5px solid #3a3a3a;
position: relative;
top: 10px;
margin-left: auto;
margin-right: auto;
}
#settingsmenu button {
width: 45%;
left: 26%;
padding: 0;
margin-top: 10px;
}
#settingsmenu #avatar {
width: 24%;
left: 38%;
border-radius: 25px;
position: relative;
}
#settingsmenu .icon-settings {
padding: 34px;
}
#languages_selectbox{
height: 40px;
}

180
css/toastr.css Normal file
View File

@@ -0,0 +1,180 @@
/*
* Toastr
* Copyright 2012-2014 John Papa and Hans Fjällemark.
* All Rights Reserved.
* Use, reproduction, distribution, and modification of this code is subject to the terms and
* conditions of the MIT license, available at http://www.opensource.org/licenses/mit-license.php
*
* Author: John Papa and Hans Fjällemark
* Project: https://github.com/CodeSeven/toastr
*/
.toast-title {
font-weight: bold;
}
.toast-message {
-ms-word-wrap: break-word;
word-wrap: break-word;
}
.toast-message a,
.toast-message label {
color: #ffffff;
}
.toast-message a:hover {
color: #cccccc;
text-decoration: none;
}
.toast-close-button {
position: relative;
right: -0.3em;
top: -0.3em;
float: right;
font-size: 20px;
font-weight: bold;
color: #ffffff;
-webkit-text-shadow: 0 1px 0 #ffffff;
text-shadow: 0 1px 0 #ffffff;
opacity: 0.8;
-ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=80);
filter: alpha(opacity=80);
}
.toast-close-button:hover,
.toast-close-button:focus {
color: #000000;
text-decoration: none;
cursor: pointer;
opacity: 0.4;
-ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=40);
filter: alpha(opacity=40);
}
/*Additional properties for button version
iOS requires the button element instead of an anchor tag.
If you want the anchor version, it requires `href="#"`.*/
button.toast-close-button {
padding: 0;
cursor: pointer;
background: transparent;
border: 0;
-webkit-appearance: none;
}
.toast-top-full-width {
top: 0;
right: 0;
width: 100%;
}
.toast-bottom-full-width {
bottom: 0;
right: 0;
width: 100%;
}
.toast-top-left {
top: 12px;
left: 12px;
}
.toast-top-right {
top: 12px;
right: 12px;
}
.toast-bottom-right {
right: 12px;
bottom: 12px;
}
.toast-bottom-left {
bottom: 12px;
left: 12px;
}
#toast-container {
position: fixed;
z-index: 999999;
/*overrides*/
}
#toast-container * {
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
#toast-container > div {
margin: 0 0 6px;
padding: 15px 15px 15px 50px;
width: 300px;
-moz-border-radius: 3px 3px 3px 3px;
-webkit-border-radius: 3px 3px 3px 3px;
border-radius: 3px 3px 3px 3px;
background-position: 15px center;
background-repeat: no-repeat;
-moz-box-shadow: 0 0 12px #999999;
-webkit-box-shadow: 0 0 12px #999999;
box-shadow: 0 0 12px #999999;
color: #ffffff;
opacity: 0.8;
-ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=80);
filter: alpha(opacity=80);
}
#toast-container > :hover {
-moz-box-shadow: 0 0 12px #000000;
-webkit-box-shadow: 0 0 12px #000000;
box-shadow: 0 0 12px #000000;
opacity: 1;
-ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=100);
filter: alpha(opacity=100);
cursor: pointer;
}
#toast-container > .toast-info {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGwSURBVEhLtZa9SgNBEMc9sUxxRcoUKSzSWIhXpFMhhYWFhaBg4yPYiWCXZxBLERsLRS3EQkEfwCKdjWJAwSKCgoKCcudv4O5YLrt7EzgXhiU3/4+b2ckmwVjJSpKkQ6wAi4gwhT+z3wRBcEz0yjSseUTrcRyfsHsXmD0AmbHOC9Ii8VImnuXBPglHpQ5wwSVM7sNnTG7Za4JwDdCjxyAiH3nyA2mtaTJufiDZ5dCaqlItILh1NHatfN5skvjx9Z38m69CgzuXmZgVrPIGE763Jx9qKsRozWYw6xOHdER+nn2KkO+Bb+UV5CBN6WC6QtBgbRVozrahAbmm6HtUsgtPC19tFdxXZYBOfkbmFJ1VaHA1VAHjd0pp70oTZzvR+EVrx2Ygfdsq6eu55BHYR8hlcki+n+kERUFG8BrA0BwjeAv2M8WLQBtcy+SD6fNsmnB3AlBLrgTtVW1c2QN4bVWLATaIS60J2Du5y1TiJgjSBvFVZgTmwCU+dAZFoPxGEEs8nyHC9Bwe2GvEJv2WXZb0vjdyFT4Cxk3e/kIqlOGoVLwwPevpYHT+00T+hWwXDf4AJAOUqWcDhbwAAAAASUVORK5CYII=") !important;
}
#toast-container > .toast-error {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHOSURBVEhLrZa/SgNBEMZzh0WKCClSCKaIYOED+AAKeQQLG8HWztLCImBrYadgIdY+gIKNYkBFSwu7CAoqCgkkoGBI/E28PdbLZmeDLgzZzcx83/zZ2SSXC1j9fr+I1Hq93g2yxH4iwM1vkoBWAdxCmpzTxfkN2RcyZNaHFIkSo10+8kgxkXIURV5HGxTmFuc75B2RfQkpxHG8aAgaAFa0tAHqYFfQ7Iwe2yhODk8+J4C7yAoRTWI3w/4klGRgR4lO7Rpn9+gvMyWp+uxFh8+H+ARlgN1nJuJuQAYvNkEnwGFck18Er4q3egEc/oO+mhLdKgRyhdNFiacC0rlOCbhNVz4H9FnAYgDBvU3QIioZlJFLJtsoHYRDfiZoUyIxqCtRpVlANq0EU4dApjrtgezPFad5S19Wgjkc0hNVnuF4HjVA6C7QrSIbylB+oZe3aHgBsqlNqKYH48jXyJKMuAbiyVJ8KzaB3eRc0pg9VwQ4niFryI68qiOi3AbjwdsfnAtk0bCjTLJKr6mrD9g8iq/S/B81hguOMlQTnVyG40wAcjnmgsCNESDrjme7wfftP4P7SP4N3CJZdvzoNyGq2c/HWOXJGsvVg+RA/k2MC/wN6I2YA2Pt8GkAAAAASUVORK5CYII=") !important;
}
#toast-container > .toast-success {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAADsSURBVEhLY2AYBfQMgf///3P8+/evAIgvA/FsIF+BavYDDWMBGroaSMMBiE8VC7AZDrIFaMFnii3AZTjUgsUUWUDA8OdAH6iQbQEhw4HyGsPEcKBXBIC4ARhex4G4BsjmweU1soIFaGg/WtoFZRIZdEvIMhxkCCjXIVsATV6gFGACs4Rsw0EGgIIH3QJYJgHSARQZDrWAB+jawzgs+Q2UO49D7jnRSRGoEFRILcdmEMWGI0cm0JJ2QpYA1RDvcmzJEWhABhD/pqrL0S0CWuABKgnRki9lLseS7g2AlqwHWQSKH4oKLrILpRGhEQCw2LiRUIa4lwAAAABJRU5ErkJggg==") !important;
}
#toast-container > .toast-warning {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGYSURBVEhL5ZSvTsNQFMbXZGICMYGYmJhAQIJAICYQPAACiSDB8AiICQQJT4CqQEwgJvYASAQCiZiYmJhAIBATCARJy+9rTsldd8sKu1M0+dLb057v6/lbq/2rK0mS/TRNj9cWNAKPYIJII7gIxCcQ51cvqID+GIEX8ASG4B1bK5gIZFeQfoJdEXOfgX4QAQg7kH2A65yQ87lyxb27sggkAzAuFhbbg1K2kgCkB1bVwyIR9m2L7PRPIhDUIXgGtyKw575yz3lTNs6X4JXnjV+LKM/m3MydnTbtOKIjtz6VhCBq4vSm3ncdrD2lk0VgUXSVKjVDJXJzijW1RQdsU7F77He8u68koNZTz8Oz5yGa6J3H3lZ0xYgXBK2QymlWWA+RWnYhskLBv2vmE+hBMCtbA7KX5drWyRT/2JsqZ2IvfB9Y4bWDNMFbJRFmC9E74SoS0CqulwjkC0+5bpcV1CZ8NMej4pjy0U+doDQsGyo1hzVJttIjhQ7GnBtRFN1UarUlH8F3xict+HY07rEzoUGPlWcjRFRr4/gChZgc3ZL2d8oAAAAASUVORK5CYII=") !important;
}
#toast-container.toast-top-full-width > div,
#toast-container.toast-bottom-full-width > div {
width: 96%;
margin: auto;
}
.toast {
background-color: #030303;
}
.toast-success {
background-color: #51a351;
}
.toast-error {
background-color: #bd362f;
}
.toast-info {
background-color: #2f96b4;
}
.toast-warning {
background-color: #f89406;
}
/*Responsive Design*/
@media all and (max-width: 240px) {
#toast-container > div {
padding: 8px 8px 8px 50px;
width: 11em;
}
#toast-container .toast-close-button {
right: -0.2em;
top: -0.2em;
}
}
@media all and (min-width: 241px) and (max-width: 480px) {
#toast-container > div {
padding: 8px 8px 8px 50px;
width: 18em;
}
#toast-container .toast-close-button {
right: -0.2em;
top: -0.2em;
}
}
@media all and (min-width: 481px) and (max-width: 768px) {
#toast-container > div {
padding: 15px 15px 15px 50px;
width: 25em;
}
}

111
css/unsupported_browser.css Normal file
View File

@@ -0,0 +1,111 @@
body {
width:100%;
height:100%;
background-color: white;
color: #424242;
font-family:Helvetica,'YanoneKaffeesatzLight',Verdana,Tahoma,Arial;
font-size: 28px;
margin:0;
padding:0;
}
#wrap{
display: block;
position: absolute;
width:900px;
height: 365px;
overflow:hidden;
text-align: center;
margin: auto;
top: 0; left: 0; bottom: 0; right: 0;
}
.firefox{
font-size: 11pt;
color: #c8c8c8;
width: 468px;
text-align: center;
margin: 30px auto 0px auto;
padding-left: 15px;
}
#text{
display:inline-block;
font-size: 28px;
width: 568px;
vertical-align:middle;
padding-top: 25px;
}
a {
color: #087dba;
text-decoration:none;
}
.browser {
width: 138px;
height: 163px;
margin-top: 5px;
background-color: #e8e8e8;
border: 1px solid #cfcfcf;
border-radius: 10px;
}
.browser_wrapper
{
width: 138px;
height: 188px;
vertical-align: middle;
color: #929391;
font-size: 20px;
float: left;
margin-left: 15px;
}
.supported_browsers
{
margin: 0px auto 0px auto;
width: 460px;
}
.clear
{
clear: both;
}
.button
{
background-color: #62c82a;
border: 1px solid #3c8117;
border-radius: 10px;
color: #FFFFFF;
font-size: 12px;
text-align: center;
width: 115px;
height: 26px;
padding-top: 13px;
margin: 15px auto 0px auto;
}
.logo
{
margin: 20px auto 0px auto;
}
#chrome_logo
{
width: 78px;
height: 78px;
background-image: url('/images/chrome.png');
}
#chromium_logo
{
width: 77px;
height: 79px;
background-image: url('/images/chromium.png');
}
#opera_logo
{
width: 73px;
height: 78px;
background-image: url('/images/opera.png');
}

View File

@@ -35,7 +35,7 @@
#remoteVideos .videocontainer {
display: inline-block;
background-image:url(../images/avatar1.png);
background-color: black;
background-size: contain;
border-radius:8px;
border: 2px solid #212425;
@@ -102,6 +102,11 @@
text-align: center;
}
#largeVideo
{
object-fit: cover;
}
#presentation,
#etherpad,
#localVideoWrapper>video,
@@ -115,10 +120,6 @@
height: 100%;
}
.dominantspeaker {
background: #000 !important;
}
#etherpad,
#presentation {
text-align: center;
@@ -373,8 +374,44 @@
position: absolute;
z-index: 0;
border-radius:10px;
pointer-events: none;
}
#activeSpeaker {
visibility: hidden;
width: 150px;
height: 150px;
margin: auto;
overflow: hidden;
position: relative;
}
#activeSpeakerAudioLevel {
position: absolute;
top: 0px;
left: 0px;
z-index: 1;
visibility: inherit;
}
#mixedstream {
display:none !important;
}
#activeSpeakerAvatar {
width: 100px;
height: 100px;
top: 25px;
margin: auto;
position: relative;
border-radius: 50px;
z-index: 2;
visibility: inherit;
}
.userAvatar {
height: 100%;
position: absolute;
left: 35px;
border-radius: 200px;
}

View File

@@ -1,163 +0,0 @@
/* global connection, Strophe, updateLargeVideo, focusedVideoSrc*/
// cache datachannels to avoid garbage collection
// https://code.google.com/p/chromium/issues/detail?id=405545
var _dataChannels = [];
/**
* Callback triggered by PeerConnection when new data channel is opened
* on the bridge.
* @param event the event info object.
*/
function onDataChannel(event)
{
var dataChannel = event.channel;
dataChannel.onopen = function ()
{
console.info("Data channel opened by the Videobridge!", dataChannel);
// Code sample for sending string and/or binary data
// Sends String message to the bridge
//dataChannel.send("Hello bridge!");
// 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,
var largeVideoSrc = $('#largeVideo').attr('src');
var userJid = getJidFromVideoSrc(largeVideoSrc);
// we want the notification to trigger even if userJid is undefined,
// or null.
onSelectedEndpointChanged(userJid);
};
dataChannel.onerror = function (error)
{
console.error("Data Channel Error:", error, dataChannel);
};
dataChannel.onmessage = function (event)
{
var data = event.data;
// JSON
var obj;
try
{
obj = JSON.parse(data);
}
catch (e)
{
console.error(
"Failed to parse data channel message as JSON: ",
data,
dataChannel);
}
if (('undefined' !== typeof(obj)) && (null !== obj))
{
var colibriClass = obj.colibriClass;
if ("DominantSpeakerEndpointChangeEvent" === colibriClass)
{
// Endpoint ID from the Videobridge.
var dominantSpeakerEndpoint = obj.dominantSpeakerEndpoint;
console.info(
"Data channel new dominant speaker event: ",
dominantSpeakerEndpoint);
$(document).trigger(
'dominantspeakerchanged',
[dominantSpeakerEndpoint]);
}
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: ",
lastNEndpoints, endpointsEnteringLastN, obj);
$(document).trigger(
'lastnchanged',
[lastNEndpoints, endpointsEnteringLastN, stream]);
}
else if ("SimulcastLayersChangedEvent" === colibriClass)
{
var endpointSimulcastLayers = obj.endpointSimulcastLayers;
$(document).trigger('simulcastlayerschanged', [endpointSimulcastLayers]);
}
else if ("SimulcastLayersChangingEvent" === colibriClass)
{
var endpointSimulcastLayers = obj.endpointSimulcastLayers;
$(document).trigger('simulcastlayerschanging', [endpointSimulcastLayers]);
}
else if ("StartSimulcastLayerEvent" === colibriClass)
{
var simulcastLayer = obj.simulcastLayer;
$(document).trigger('startsimulcastlayer', simulcastLayer);
}
else if ("StopSimulcastLayerEvent" === colibriClass)
{
var simulcastLayer = obj.simulcastLayer;
$(document).trigger('stopsimulcastlayer', simulcastLayer);
}
else
{
console.debug("Data channel JSON-formatted message: ", obj);
}
}
};
dataChannel.onclose = function ()
{
console.info("The Data Channel closed", dataChannel);
var idx = _dataChannels.indexOf(dataChannel);
if (idx > -1)
_dataChannels = _dataChannels.splice(idx, 1);
};
_dataChannels.push(dataChannel);
}
/**
* Binds "ondatachannel" event listener to given PeerConnection instance.
* @param peerConnection WebRTC peer connection instance.
*/
function bindDataChannelListener(peerConnection)
{
peerConnection.ondatachannel = onDataChannel;
// Sample code for opening new data channel from Jitsi Meet to the bridge.
// Although it's not a requirement to open separate channels from both bridge
// and peer as single channel can be used for sending and receiving data.
// So either channel opened by the bridge or the one opened here is enough
// for communication with the bridge.
/*var dataChannelOptions =
{
reliable: true
};
var dataChannel
= peerConnection.createDataChannel("myChannel", dataChannelOptions);
// Can be used only when is in open state
dataChannel.onopen = function ()
{
dataChannel.send("My channel !!!");
};
dataChannel.onmessage = function (event)
{
var msgData = event.data;
console.info("Got My Data Channel Message:", msgData, dataChannel);
};*/
}

2
debian/control vendored
View File

@@ -23,7 +23,7 @@ Description: WebRTC JavaScript video conferences
Package: jitsi-meet-prosody
Architecture: all
Pre-Depends: openssl, prosody | prosody-trunk, jitsi-videobridge
Depends: ${misc:Depends}
Depends: ${misc:Depends}, 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.

4
debian/jitsi-meet-prosody.config vendored Normal file
View File

@@ -0,0 +1,4 @@
#!/bin/sh -e
# Source debconf library.
. /usr/share/debconf/confmodule

View File

@@ -23,18 +23,68 @@ case "$1" in
. /etc/jitsi/videobridge/config
. /etc/jitsi/jicofo/config
# loading debconf
. /usr/share/debconf/confmodule
# stores the hostname so we will reuse it later, like in purge
db_set jitsi-meet-prosody/jvb-hostname $JVB_HOSTNAME
# and we're done with debconf
db_stop
PROSODY_CONFIG_PRESENT="true"
PROSODY_CREATE_JICOFO_USER="false"
PROSODY_HOST_CONFIG="/etc/prosody/conf.avail/$JVB_HOSTNAME.cfg.lua"
PROSODY_CONFIG_OLD="/etc/prosody/prosody.cfg.lua"
# if there is no prosody config extract our template
# check for config in conf.avail or check whether it wasn't already configured in main config
if [ ! -f $PROSODY_HOST_CONFIG ] && ! grep -q "VirtualHost \"$JVB_HOSTNAME\"" /etc/prosody/prosody.cfg.lua; then
if [ ! -f $PROSODY_HOST_CONFIG ] && ! grep -q "VirtualHost \"$JVB_HOSTNAME\"" $PROSODY_CONFIG_OLD; then
PROSODY_CONFIG_PRESENT="false"
mkdir -p /etc/prosody/conf.avail/
cp /usr/share/doc/jitsi-meet-prosody/prosody.cfg.lua-jvb.example $PROSODY_HOST_CONFIG
sed -i "s/jitmeet.example.com/$JVB_HOSTNAME/g" $PROSODY_HOST_CONFIG
sed -i "s/jitmeetSecret/$JVB_SECRET/g" $PROSODY_HOST_CONFIG
sed -i "s/focusSecret/$JICOFO_SECRET/g" $PROSODY_HOST_CONFIG
sed -i "s/focusUser/$JICOFO_AUTH_USER/g" $PROSODY_HOST_CONFIG
if [ ! -f /etc/prosody/conf.d/$JVB_HOSTNAME.cfg.lua ]; then
ln -s $PROSODY_HOST_CONFIG /etc/prosody/conf.d/$JVB_HOSTNAME.cfg.lua
fi
PROSODY_CREATE_JICOFO_USER="true"
# on some distributions main prosody config doesn't include configs
# from conf.d folder enable it as this where we put our config by default
if ! grep -q "Include \"conf\.d\/\*\.cfg.lua\"" $PROSODY_CONFIG_OLD; then
echo -e "\nInclude \"conf.d/*.cfg.lua\"" >> $PROSODY_CONFIG_OLD
fi
fi
# UPGRADE to server side focus check if focus is configured
if [ -f $PROSODY_HOST_CONFIG ] && ! grep -q "VirtualHost \"auth.$JVB_HOSTNAME\"" $PROSODY_HOST_CONFIG; then
echo -e "\nVirtualHost \"auth.$JVB_HOSTNAME\"" >> $PROSODY_HOST_CONFIG
echo -e " authentication = \"internal_plain\"\n" >> $PROSODY_HOST_CONFIG
sed -i "s/Component \"conference.$JVB_HOSTNAME\" \"muc\"/Component \"conference.$JVB_HOSTNAME\" \"muc\"\nadmins = { \"$JICOFO_AUTH_USER@auth.$JVB_HOSTNAME\" }\n/g" $PROSODY_HOST_CONFIG
echo -e "Component \"focus.$JVB_HOSTNAME\"" >> $PROSODY_HOST_CONFIG
echo -e " component_secret=\"$JICOFO_SECRET\"\n" >> $PROSODY_HOST_CONFIG
PROSODY_CREATE_JICOFO_USER="true"
# UPGRADE to server side focus on old config(/etc/prosody/prosody.cfg.lua)
elif [ ! -f $PROSODY_HOST_CONFIG ] && ! grep -q "VirtualHost \"auth.$JVB_HOSTNAME\"" $PROSODY_CONFIG_OLD; then
echo -e "\nVirtualHost \"auth.$JVB_HOSTNAME\"" >> $PROSODY_CONFIG_OLD
echo -e " authentication = \"internal_plain\"\n" >> $PROSODY_CONFIG_OLD
if ! grep -q "admins = { }" $PROSODY_CONFIG_OLD; then
echo -e "admins = { \"$JICOFO_AUTH_USER@auth.$JVB_HOSTNAME\" }\n" >> $PROSODY_CONFIG_OLD
else
sed -i "s/admins = { }/admins = { \"$JICOFO_AUTH_USER@auth.$JVB_HOSTNAME\" }\n/g" $PROSODY_CONFIG_OLD
fi
echo -e "Component \"focus.$JVB_HOSTNAME\"" >> $PROSODY_CONFIG_OLD
echo -e " component_secret=\"$JICOFO_SECRET\"\n" >> $PROSODY_CONFIG_OLD
PROSODY_CREATE_JICOFO_USER="true"
fi
if [ "$PROSODY_CREATE_JICOFO_USER" = "true" ]; then
# create 'focus@auth.domain' prosody user
prosodyctl register $JICOFO_AUTH_USER $JICOFO_AUTH_DOMAIN $JICOFO_AUTH_PASSWORD
# trigger a restart
PROSODY_CONFIG_PRESENT="false"
fi
if [ ! -f /var/lib/prosody/$JVB_HOSTNAME.crt ]; then
@@ -51,6 +101,7 @@ case "$1" in
if [ "$PROSODY_CONFIG_PRESENT" = "false" ]; then
invoke-rc.d prosody restart
invoke-rc.d jitsi-videobridge restart
invoke-rc.d jicofo restart
fi
;;

View File

@@ -29,7 +29,16 @@ case "$1" in
fi
;;
purge|upgrade|failed-upgrade|abort-install|abort-upgrade|disappear)
purge)
db_get jitsi-meet-prosody/jvb-hostname
JVB_HOSTNAME=$RET
if [ -n "$RET" ]; then
rm -f /etc/prosody/conf.avail/$JVB_HOSTNAME.cfg.lua
rm -f /etc/prosody/conf.d/$JVB_HOSTNAME.cfg.lua
fi
;;
upgrade|failed-upgrade|abort-install|abort-upgrade|disappear)
;;
*)

5
debian/jitsi-meet-prosody.templates vendored Normal file
View File

@@ -0,0 +1,5 @@
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.

View File

@@ -3,7 +3,9 @@
*.html /usr/share/jitsi-meet/
*.ico /usr/share/jitsi-meet/
libs /usr/share/jitsi-meet/
service /usr/share/jitsi-meet/
css /usr/share/jitsi-meet/
sounds /usr/share/jitsi-meet/
fonts /usr/share/jitsi-meet/
images /usr/share/jitsi-meet/
images /usr/share/jitsi-meet/
lang /usr/share/jitsi-meet/

View File

@@ -37,35 +37,29 @@ case "$1" in
sed -i "s/jitsi-meet.example.com/$JVB_HOSTNAME/g" /etc/nginx/sites-available/$JVB_HOSTNAME.conf
fi
if grep "# server_names_hash_bucket_size 64" /etc/nginx/nginx.conf > /dev/null; then
sed -i "s/#\ server_names_hash_bucket_size\ 64/\ server_names_hash_bucket_size\ 64/" /etc/nginx/nginx.conf
fi
if [ ! -f /etc/ssl/$JVB_HOSTNAME.key ] || [ ! -f /etc/ssl/$JVB_HOSTNAME.crt ]; then
# 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
# 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
# jitsi meet

View File

@@ -2,21 +2,18 @@ Index: jitsi-meet/index.html
===================================================================
--- jitsi-meet.orig/index.html
+++ jitsi-meet/index.html
@@ -9,7 +9,7 @@
@@ -9,12 +9,12 @@
<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="libs/jquery-2.1.1.min.js"></script>
+ <script src="libs/jquery.min.js"></script>
<script src="config.js?v=5"></script><!-- adapt to your needs, i.e. set hosts and bosh path -->
<script src="simulcast.js?v=5"></script><!-- simulcast handling -->
<script src="libs/strophe/strophe.jingle.adapter.js?v=2"></script><!-- strophe.jingle bundles -->
@@ -24,7 +24,7 @@
<script src="libs/strophe/strophe.util.js"></script>
<script src="libs/colibri/colibri.focus.js?v=10"></script><!-- colibri focus implementation -->
<script src="libs/colibri/colibri.session.js?v=1"></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="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>
+ <script src="libs/jquery-ui.min.js"></script>
<script src="libs/rayo.js?v=1"></script>
<script src="libs/tooltip.js?v=1"></script><!-- bootstrap tooltip lib -->
<script src="libs/popover.js?v=1"></script><!-- bootstrap tooltip lib -->
<script src="libs/toastr.js?v=1"></script><!-- notifications lib -->

15
debian/rules vendored
View File

@@ -1,10 +1,4 @@
#!/usr/bin/make -f
# -*- makefile -*-
# Sample debian/rules that uses debhelper.
# This file was originally written by Joey Hess and Craig Small.
# As a special exception, when this file is copied by dh-make into a
# dh-make output file, you may use that output file without restriction.
# This special exception was added by Craig Small in version 0.37 of dh-make.
# Uncomment this to turn on verbose mode.
#export DH_VERBOSE=1
@@ -12,11 +6,14 @@
%:
dh $@
# we skip making Makefile exists for updating browserify modules when developing
override_dh_auto_build:
override_dh_install:
dh_installdirs
dh_install -X/config.js
yui-compressor -o debian/jitsi-meet/usr/share/jitsi-meet/libs/strophe/strophe.caps.jsonly.min.js \
debian/patches/missing-source/libs/strophe/strophe.caps.jsonly.js
debian/missing-source/libs/strophe/strophe.caps.jsonly.js
yui-compressor -o debian/jitsi-meet/usr/share/jitsi-meet/libs/strophe/strophe.disco.min.js \
debian/patches/missing-source/libs/strophe/sha1.js \
debian/patches/missing-source/libs/strophe/strophe.disco.js
debian/missing-source/libs/strophe/sha1.js \
debian/missing-source/libs/strophe/strophe.disco.js

2
debian/watch vendored Normal file
View File

@@ -0,0 +1,2 @@
version=3
https://github.com/jitsi/jitsi-meet/releases/ /jitsi/jitsi-meet/archive/(\S+)\.tar\.gz

View File

@@ -91,7 +91,7 @@ with data related to the event.
Currently we support the following events:
* **incommingMessage** - event notifications about incomming
* **incomingMessage** - event notifications about incoming
messages. The listener will receive object with the following structure:
```
{
@@ -135,7 +135,7 @@ This method requires one argument of type Object. The object argument must
have keys with the names of the events and values the listeners of the events.
```
function incommingMessageListener(object)
function incomingMessageListener(object)
{
...
}
@@ -146,19 +146,19 @@ function outgoingMessageListener(object)
}
api.addEventListeners({
incommingMessage: incommingMessageListener,
incomingMessage: incomingMessageListener,
outgoingMessage: outgoingMessageListener})
```
If you want to remove a listener you can use ```removeEventListener``` method with argument the name of the event.
```
api.removeEventListener("incommingMessage");
api.removeEventListener("incomingMessage");
```
If you want to remove more than one event you can use ```removeEventListeners``` method with argument
array with the names of the events.
```
api.removeEventListeners(["incommingMessage", "outgoingMessageListener"]);
api.removeEventListeners(["incomingMessage", "outgoingMessageListener"]);
```
You can remove the embedded Jitsi Meet Conference with the following code:

View File

@@ -16,6 +16,13 @@ VirtualHost "jitmeet.example.com"
}
Component "conference.jitmeet.example.com" "muc"
admins = { "focusUser@auth.jitmeet.example.com" }
Component "jitsi-videobridge.jitmeet.example.com"
component_secret = "jitmeetSecret"
VirtualHost "auth.jitmeet.example.com"
authentication = "internal_plain"
Component "focus.jitmeet.example.com"
component_secret = "focusSecret"

View File

@@ -1,3 +1,5 @@
server_names_hash_bucket_size 64;
server {
listen 80;
server_name jitsi-meet.example.com;
@@ -17,7 +19,7 @@ server {
alias /etc/jitsi/meet/jitsi-meet.example.com-config.js;
}
location ~ ^/([a-zA-Z0-9]+)$ {
location ~ ^/([a-zA-Z0-9=\?]+)$ {
rewrite ^/(.*)$ / break;
}

View File

@@ -6,7 +6,7 @@ server {
root /srv/jitsi.example.com;
index index.html;
location ~ ^/([a-zA-Z0-9]+)$ {
location ~ ^/([a-zA-Z0-9=\?]+)$ {
rewrite ^/(.*)$ / break;
}

26
doc/influxdb.md Normal file
View File

@@ -0,0 +1,26 @@
# 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/ .
## 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/jitsi-videobridge/.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.

View File

@@ -2,7 +2,7 @@
This describes configuring a server `jitsi.example.com`. You will need to
change references to that to match your host, and generate some passwords for
`YOURSECRET1` and `YOURSECRET2`.
`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.
@@ -36,17 +36,32 @@ default_archive_policy = "roster"
- configure your domain by editing the example.com virtual host section section:
```
VirtualHost "jitsi.example.com"
authentication = "anonymous"
ssl = {
key = "/var/lib/prosody/jitsi.example.com.key";
certificate = "/var/lib/prosody/jitsi.example.com.crt";
}
authentication = "anonymous"
ssl = {
key = "/var/lib/prosody/jitsi.example.com.key";
certificate = "/var/lib/prosody/jitsi.example.com.crt";
}
```
- 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:
```
admins = { "focus@auth.jitsi.example.com" }
```
- and finally configure components:
```
Component "conference.jitsi.example.com" "muc"
Component "jitsi-videobridge.jitsi.example.com"
component_secret = "YOURSECRET1"
Component "focus.jitsi.example.com"
component_secret = "YOURSECRET2"
```
Generate certs for the domain:
@@ -54,6 +69,11 @@ Generate certs for the domain:
prosodyctl cert generate jitsi.example.com
```
Create conference focus user:
```sh
prosodyctl register focus auth.jitsi.example.com YOURSECRET3
```
Restart prosody XMPP server with the new config
```sh
prosodyctl restart
@@ -137,6 +157,22 @@ 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)
Clone source from Github repo:
```sh
git clone https://github.com/jitsi/jicofo.git
```
Build distribution package. Replace {os-name} with one of: 'lin', 'lin64', 'macosx', 'win', 'win64'.
```sh
cd jicofo
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
```
## Deploy Jitsi Meet
Checkout and configure Jitsi Meet:
```sh
@@ -192,7 +228,7 @@ Configure addresses and ports as desired, and the password to be configured in p
```
realm jitsi.example.com
# share this with your prosody server
auth_shared YOURSECRET2
auth_shared YOURSECRET4
# modules
module_path /usr/lib/restund/modules
@@ -201,7 +237,7 @@ turn_relay_addr [turn ip address]
Configure prosody to use it in `/etc/prosody/prosody.cfg.lua`. Add to your virtual host:
```
turncredentials_secret = "YOURSECRET2";
turncredentials_secret = "YOURSECRET4";
turncredentials = {
{ type = "turn", host = "turn.address.ip.configured", port = 3478, transport = "tcp" }
}

View File

@@ -7,22 +7,6 @@ N.B.: All commands are supposed to be run by root. If you are logged in as a reg
## Basic Jitsi Meet install
### Add the repository
```sh
add-apt-repository 'deb http://download.jitsi.org/nightly/deb unstable/'
wget -qO - https://download.jitsi.org/nightly/deb/unstable/archive.key | apt-key add -
```
add-apt-repository is in the default Ubuntu install and is available for both Ubuntu and Debian, but if it's not present, either install it with
```sh
apt-get -y install software-properties-common
add-apt-repository 'deb http://download.jitsi.org/nightly/deb unstable/'
wget -qO - https://download.jitsi.org/nightly/deb/unstable/archive.key | apt-key add -
```
or add the repository by hand with
```sh
echo 'deb http://download.jitsi.org/nightly/deb unstable/' >> /etc/apt/sources.list
wget -qO - https://download.jitsi.org/nightly/deb/unstable/archive.key | apt-key add -
@@ -74,18 +58,10 @@ Launch again a browser with the Jitsi Meet URL and you'll see a telephone icon o
Enjoy!
## Troubleshoot
If the SIP gateway doesn't work on first try, restart it.
```sh
/etc/init.d/jigasi restart
```
## Deinstall
```sh
apt-get purge jigasi jitsi-meet jitsi-videobridge
apt-get purge jigasi jitsi-meet jicofo jitsi-videobridge
```
Somethimes the following packages will fail to uninstall properly:

View File

@@ -1,17 +0,0 @@
/* global Strophe */
Strophe.addConnectionPlugin('logger', {
// logs raw stanzas and makes them available for download as JSON
connection: null,
log: [],
init: function (conn) {
this.connection = conn;
this.connection.rawInput = this.log_incoming.bind(this);
this.connection.rawOutput = this.log_outgoing.bind(this);
},
log_incoming: function (stanza) {
this.log.push([new Date().getTime(), 'incoming', stanza]);
},
log_outgoing: function (stanza) {
this.log.push([new Date().getTime(), 'outgoing', stanza]);
},
});

View File

@@ -1,197 +0,0 @@
/* global $, config, Prezi, Util, connection, setLargeVideoVisible, dockToolbar */
var Etherpad = (function (my) {
var etherpadName = null;
var etherpadIFrame = null;
var domain = null;
var options = "?showControls=true&showChat=false&showLineNumbers=true&useMonospaceFont=false";
/**
* Initializes the etherpad.
*/
my.init = function (name) {
if (config.etherpad_base && !etherpadName) {
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;
enableEtherpadButton();
}
};
/**
* Opens/hides the Etherpad.
*/
my.toggleEtherpad = function (isPresentation) {
if (!etherpadIFrame)
createIFrame();
var largeVideo = null;
if (Prezi.isPresentationVisible())
largeVideo = $('#presentation>iframe');
else
largeVideo = $('#largeVideo');
if ($('#etherpad>iframe').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});
});
});
}
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);
});
}
});
}
resize();
};
/**
* Resizes the etherpad.
*/
function resize() {
if ($('#etherpad>iframe').length) {
var remoteVideos = $('#remoteVideos');
var availableHeight
= window.innerHeight - remoteVideos.outerHeight();
var availableWidth = Util.getAvailableVideoWidth();
$('#etherpad>iframe').width(availableWidth);
$('#etherpad>iframe').height(availableHeight);
}
}
/**
* Shares the Etherpad name with other participants.
*/
function shareEtherpad() {
connection.emuc.addEtherpadToPresence(etherpadName);
connection.emuc.sendPresence();
}
/**
* Creates the Etherpad button and adds it to the toolbar.
*/
function enableEtherpadButton() {
if (!$('#etherpadButton').is(":visible"))
$('#etherpadButton').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;');
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);
};
}
function bubbleIframeMouseMove(iframe){
var existingOnMouseMove = iframe.contentWindow.onmousemove;
iframe.contentWindow.onmousemove = function(e){
if(existingOnMouseMove) existingOnMouseMove(e);
var evt = document.createEvent("MouseEvents");
var boundingClientRect = iframe.getBoundingClientRect();
evt.initMouseEvent(
"mousemove",
true, // bubbles
false, // not cancelable
window,
e.detail,
e.screenX,
e.screenY,
e.clientX + boundingClientRect.left,
e.clientY + boundingClientRect.top,
e.ctrlKey,
e.altKey,
e.shiftKey,
e.metaKey,
e.button,
null // no related element
);
iframe.dispatchEvent(evt);
};
}
/**
* On Etherpad added to muc.
*/
$(document).bind('etherpadadded.muc', function (event, jid, etherpadName) {
console.log("Etherpad added", etherpadName);
if (config.etherpad_base && !focus) {
Etherpad.init(etherpadName);
}
});
/**
* On focus changed event.
*/
$(document).bind('focusechanged.muc', function (event, focus) {
console.log("Focus changed");
if (config.etherpad_base)
shareEtherpad();
});
/**
* On video selected event.
*/
$(document).bind('video.selected', function (event, isPresentation) {
if (!config.etherpad_base)
return;
if (etherpadIFrame && etherpadIFrame.style.visibility !== 'hidden')
Etherpad.toggleEtherpad(isPresentation);
});
/**
* Resizes the etherpad, when the window is resized.
*/
$(window).resize(function () {
resize();
});
return my;
}(Etherpad || {}));

View File

@@ -48,7 +48,7 @@ var JitsiMeetExternalAPI = (function()
this.iframeHolder.style.width = width + "px";
this.iframeHolder.style.height = height + "px";
this.frameName = "jitsiConferenceFrame" + JitsiMeetExternalAPI.id;
this.url = "http://" + domain + "/";
this.url = "//" + domain + "/";
if(room_name)
this.url += room_name;
this.url += "#external";
@@ -139,7 +139,7 @@ var JitsiMeetExternalAPI = (function()
* event and value - the listener.
* Currently we support the following
* events:
* incommingMessage - receives event notifications about incomming
* incomingMessage - receives event notifications about incoming
* messages. The listener will receive object with the following structure:
* {{
* "from": from,//JID of the user that sent the message
@@ -185,7 +185,7 @@ var JitsiMeetExternalAPI = (function()
/**
* Adds event listeners to Meet Jitsi. Currently we support the following
* events:
* incommingMessage - receives event notifications about incomming
* incomingMessage - receives event notifications about incoming
* messages. The listener will receive object with the following structure:
* {{
* "from": from,//JID of the user that sent the message

Binary file not shown.

View File

@@ -34,4 +34,5 @@
<glyph unicode="&#xe618;" d="M797.086 112.301c-0.059 0.163-0.119 0.328-0.16 0.485-71.399-45.638-151.782-69.931-234.023-69.931-0.013 0-0.021 0-0.028 0-122.52 0-237.501 52.772-315.469 144.741-99.778 117.698-134.252 329.954-73.022 427.789 4.004-1.662 7.875-3.233 11.68-4.773 13.585-5.511 26.413-10.716 42.305-19.096 6.063-3.202 12.338-4.812 18.673-4.812 11.714 0 22.6 5.648 29.848 15.486 7.815 10.617 10.313 24.778 6.538 36.951l-3.525 11.41c-10.687 34.59-21.723 70.354-34.211 105.078-9.983 27.765-22.399 62.327-59.226 62.327-12.057 0-26.037-3.656-46.73-12.204-44.294-18.319-71.058-29.961-114.534-49.81-15.102-6.887-25.234-22.698-25.203-39.343 0.028-15.842 8.992-29.337 23.975-36.115 18.208-8.257 30.536-13.716 43.468-19.447l10.687-4.753c-101.938-259.102 24.803-526.458 211.314-639.212 83.497-50.474 178.5-77.14 274.769-77.14h0.041c102.72 0 205.561 31.099 284.501 85.198-31.729 28.803-45.566 69.167-51.671 87.171zM1098.203 210.090c-18.113 8.577-30.356 14.258-43.221 20.244l-10.496 4.892c106.448 257.268-15.569 526.801-200.067 642.788-85.36 53.663-183.123 82.032-282.716 82.032-104.848 0-206.41-30.593-285.967-86.165l-5.385-3.764c31.597-27.564 45.86-66.788 52.917-86.41 72.926 47.94 155.675 73.409 239.895 73.409 125.407 0 242.142-54.785 320.294-150.316 97.683-119.447 128.439-332.255 65.498-429.015-3.989 1.736-7.815 3.385-11.624 4.998-13.471 5.759-26.204 11.18-41.954 19.821-6.203 3.424-12.645 5.155-19.212 5.155-11.585 0-22.399-5.558-29.69-15.267-7.813-10.434-10.478-24.432-6.966-36.515l3.279-11.301c10.096-34.845 20.531-70.857 32.412-105.842 9.588-28.238 21.514-63.382 59.179-63.382 11.843 0 25.577 3.424 45.881 11.399 44.351 17.439 71.319 28.601 115.409 47.777 15.19 6.623 25.601 22.252 25.859 38.894 0.281 15.822-8.445 29.499-23.325 36.569z" horiz-adv-x="1122" />
<glyph unicode="&#xe619;" 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="&#xe61a;" 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="&#xe61b;" 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" />
</font></defs></svg>

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Binary file not shown.

View File

@@ -1,12 +1,46 @@
{
"IcoMoonType": "selection",
"icons": [
{
"icon": {
"paths": [
"M526.071 725.251c-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"
],
"attrs": [
{
"opacity": 1,
"visibility": false
}
],
"width": 1105,
"grid": 0,
"tags": [
"settings"
]
},
"attrs": [
{
"opacity": 1,
"visibility": false
}
],
"properties": {
"order": 1,
"id": 33,
"prevSize": 32,
"code": 58907,
"name": "settings"
},
"setIdx": 0,
"iconIdx": 0
},
{
"icon": {
"paths": [
"M1223.129 242.783l-180.128 175.796v-217.716c0-74.673-59.512-135.496-132.599-135.496h-634.716c-73.084 0-132.596 60.823-132.596 135.496v609.237c0 74.673 59.512 135.496 132.596 135.496h634.716c73.084 0 132.599-60.82 132.599-135.496v-172.679l193.45 153.712c48.784 35.558 96.695-5.178 96.695-40.424v-483.533c-0.003-35.248-55.897-71.306-110.017-24.393zM601.169 760.065c-141.111 0-255.524-114.411-255.524-255.521s114.411-255.521 255.524-255.521c141.108 0 255.519 114.411 255.519 255.521-0 141.113-114.408 255.521-255.519 255.521z",
"M599.045 359.751c-80.474 0-145.727 65.253-145.727 145.729 0 80.471 65.25 145.727 145.727 145.727s145.729-65.256 145.729-145.727c0-80.474-65.253-145.729-145.729-145.729z"
],
"width": 1334,
"attrs": [
{
"opacity": 1,
@@ -17,11 +51,10 @@
"visibility": false
}
],
"width": 1334,
"grid": 0,
"tags": [
"webCam"
]
],
"grid": 0
},
"attrs": [
{
@@ -714,7 +747,7 @@
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 26
"iconIdx": 25
},
{
"icon": {
@@ -740,7 +773,7 @@
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 27
"iconIdx": 26
},
{
"icon": {
@@ -765,7 +798,7 @@
"ligatures": ""
},
"setIdx": 0,
"iconIdx": 28
"iconIdx": 27
}
],
"height": 1024,
@@ -775,6 +808,7 @@
"preferences": {
"showGlyphs": true,
"showQuickUse": true,
"showQuickUse2": true,
"showSVGs": true,
"fontPref": {
"prefix": "icon-",
@@ -791,7 +825,8 @@
},
"imagePref": {
"prefix": "icon-",
"png": true
"png": true,
"useClassSelector": true
},
"historySize": 100,
"showCodes": true,

BIN
images/chrome.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

BIN
images/chromium.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

BIN
images/dropdownPointer.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 234 B

BIN
images/firefox.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
images/opera.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

View File

@@ -10,75 +10,38 @@
<meta itemprop="description" content="Join a WebRTC video conference powered by the Jitsi Videobridge"/>
<meta itemprop="image" content="/images/jitsilogo.png"/>
<script src="libs/jquery-2.1.1.min.js"></script>
<script src="config.js?v=5"></script><!-- adapt to your needs, i.e. set hosts and bosh path -->
<script src="simulcast.js?v=6"></script><!-- simulcast handling -->
<script src="libs/strophe/strophe.jingle.adapter.js?v=2"></script><!-- strophe.jingle bundles -->
<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="libs/strophe/strophe.disco.min.js?v=1"></script>
<script src="libs/strophe/strophe.caps.jsonly.min.js?v=1"></script>
<script src="libs/strophe/strophe.jingle.js?v=2"></script>
<script src="libs/strophe/strophe.jingle.sdp.js?v=2"></script>
<script src="libs/strophe/strophe.jingle.sdp.util.js?v=1"></script>
<script src="libs/strophe/strophe.jingle.sessionbase.js?v=1"></script>
<script src="libs/strophe/strophe.jingle.session.js?v=2"></script>
<script src="libs/strophe/strophe.util.js"></script>
<script src="libs/colibri/colibri.focus.js?v=11"></script><!-- colibri focus implementation -->
<script src="libs/colibri/colibri.session.js?v=1"></script>
<script src="libs/jquery-ui.js"></script>
<script src="libs/rayo.js?v=1"></script>
<script src="libs/tooltip.js?v=1"></script><!-- bootstrap tooltip lib -->
<script src="libs/popover.js?v=1"></script><!-- bootstrap tooltip lib -->
<script src="interface_config.js?v=3"></script>
<script src="muc.js?v=16"></script><!-- simple MUC library -->
<script src="estos_log.js?v=2"></script><!-- simple stanza logger -->
<script src="desktopsharing.js?v=3"></script><!-- desktop sharing -->
<script src="data_channels.js?v=3"></script><!-- data channels -->
<script src="app.js?v=20"></script><!-- application logic -->
<script src="commands.js?v=1"></script><!-- application logic -->
<script src="chat.js?v=13"></script><!-- chat logic -->
<script src="contact_list.js?v=5"></script><!-- contact list logic -->
<script src="util.js?v=6"></script><!-- utility functions -->
<script src="etherpad.js?v=9"></script><!-- etherpad plugin -->
<script src="prezi.js?v=6"></script><!-- prezi plugin -->
<script src="smileys.js?v=3"></script><!-- smiley images -->
<script src="replacement.js?v=7"></script><!-- link and smiley replacement -->
<script src="moderatemuc.js?v=4"></script><!-- moderator plugin -->
<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=34"></script>
<script src="analytics.js?v=1"></script><!-- google analytics plugin -->
<script src="rtp_sts.js?v=5"></script><!-- RTP stats processing -->
<script src="local_sts.js?v=2"></script><!-- Local stats processing -->
<script src="videolayout.js?v=24"></script><!-- video ui -->
<script src="connectionquality.js?v=1"></script>
<script src="toolbar.js?v=6"></script><!-- toolbar ui -->
<script src="toolbar_toggler.js?v=2"></script>
<script src="canvas_util.js?v=1"></script><!-- canvas drawing utils -->
<script src="audio_levels.js?v=2"></script><!-- audio levels plugin -->
<script src="media_stream.js?v=1"></script><!-- media stream -->
<script src="bottom_toolbar.js?v=5"></script><!-- media stream -->
<script src="roomname_generator.js?v=1"></script><!-- generator for random room names -->
<script src="keyboard_shortcut.js?v=3"></script>
<script src="tracking.js?v=1"></script><!-- tracking -->
<script src="jitsipopover.js?v=3"></script>
<script src="message_handler.js?v=1"></script>
<script src="api_connector.js?v=2"></script>
<link rel="stylesheet" href="css/font.css?v=6"/>
<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 href="//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css" rel="stylesheet">
<link rel="stylesheet" href="css/font.css?v=5"/>
<link rel="stylesheet" type="text/css" media="screen" href="css/main.css?v=29"/>
<link rel="stylesheet" type="text/css" media="screen" href="css/videolayout_default.css?v=13" id="videolayout_default"/>
<link rel="stylesheet" href="css/jquery-impromptu.css?v=4">
<link rel="stylesheet" href="css/modaldialog.css?v=3">
<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">
<link rel="stylesheet" href="css/jitsi_popover.css?v=2">
<link rel="stylesheet" href="css/contact_list.css?v=3">
<link rel="stylesheet" href="css/jitsi_popover.css?v=2">
<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 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.autosize.js"></script>
<script src="libs/prezi_player.js?v=2"></script>
</head>
<body>
<div id="welcome_page">
@@ -89,76 +52,66 @@
<a target="_new">
<div class="watermark rightwatermark"></div>
</a>
<a class="poweredby" href="http://jitsi.org" target="_new" >powered by jitsi.org</a>
<a class="poweredby" href="http://jitsi.org" target="_new" ><span data-i18n="poweredby"></span> jitsi.org</a>
<div id="enter_room_container">
<div id="enter_room_form" >
<div id="domain_name"></div>
<div id="enter_room">
<input id="enter_room_field" type="text" autofocus placeholder="Enter room name" />
<input id="enter_room_field" type="text" autofocus data-i18n="[placeholder]welcomepage.roomname" placeholder="Enter room name" />
<div class="icon-reload" id="reload_roomname"></div>
<input id="enter_room_button" type="button" value="GO" />
<input id="enter_room_button" type="button" data-i18n="[value]welcomepage.go" value="GO" />
</div>
</div>
</div>
<div id="brand_header"></div>
<input type='checkbox' name='checkbox' id="disable_welcome"/>
<label for="disable_welcome" class="disable_welcome_position">Don't show this page next time I enter</label>
<label for="disable_welcome" class="disable_welcome_position" data-i18n="welcomepage.disable"></label>
<div id="header_text"></div>
</div>
<div id="welcome_page_main">
<div id="features">
<div class="feature_row">
<div class="feature_holder">
<div class="feature_icon">Simple to use</div>
<div class="feature_description">
No downloads required. <span name="appName"></span> works directly within your browser. Simply share your conference URL with others to get started.
<div class="feature_icon" data-i18n="welcomepage.feature1.title" ></div>
<div class="feature_description" data-i18n="welcomepage.feature1.content" data-i18n-options='{ "postProcess": "resolveAppName" }'>
</div>
</div>
<div class="feature_holder">
<div class="feature_icon">Low bandwidth</div>
<div class="feature_description">
Multi-party video conferences work with as little as 128Kbps. Screen-sharing and audio-only conferences are possible with far less.
<div class="feature_icon" data-i18n="welcomepage.feature2.title" ></div>
<div class="feature_description" data-i18n="welcomepage.feature2.content">
</div>
</div>
<div class="feature_holder">
<div class="feature_icon">Open source</div>
<div class="feature_description">
<span name="appName"></span> is licensed under MIT. You are free to download, use, modify, and share them as per these licenses.
<div class="feature_icon" data-i18n="welcomepage.feature3.title" ></div>
<div class="feature_description" data-i18n="welcomepage.feature3.content" data-i18n-options='{ "postProcess": "resolveAppName" }'>
</div>
</div>
<div class="feature_holder">
<div class="feature_icon">Unlimited users</div>
<div class="feature_description">
There are no artificial restrictions on the number of users or conference participants. Server power and bandwidth are the only limiting factors.
<div class="feature_icon" data-i18n="welcomepage.feature4.title" ></div>
<div class="feature_description" data-i18n="welcomepage.feature4.content">
</div>
</div>
</div>
<div class="feature_row">
<div class="feature_holder">
<div class="feature_icon">Screen sharing</div>
<div class="feature_description">
It's easy to share your screen with others. <span name="appName"></span> is ideal for on-line presentations, lectures, and tech support sessions.
</div>
<div class="feature_icon" data-i18n="welcomepage.feature5.title" ></div>
<div class="feature_description" data-i18n="welcomepage.feature5.content" data-i18n-options='{ "postProcess": "resolveAppName" }'>
</div>
</div>
<div class="feature_holder">
<div class="feature_icon">Secure rooms</div>
<div class="feature_description">
Need some privacy? <span name="appName"></span> conference rooms can be secured with a password in order to exclude unwanted guests and prevent interruptions.
</div>
<div class="feature_icon" data-i18n="welcomepage.feature6.title" ></div>
<div class="feature_description" data-i18n="welcomepage.feature6.content" data-i18n-options='{ "postProcess": "resolveAppName" }'>
</div>
</div>
<div class="feature_holder">
<div class="feature_icon">Shared notes</div>
<div class="feature_description">
<span name="appName"></span> features Etherpad, a real-time collaborative text editor that's great for meeting minutes, writing articles, and more.
</div>
<div class="feature_icon" data-i18n="welcomepage.feature7.title" ></div>
<div class="feature_description" data-i18n="welcomepage.feature7.content" data-i18n-options='{ "postProcess": "resolveAppName" }'></div>
</div>
<div class="feature_holder">
<div class="feature_icon">Usage statistics</div>
<div class="feature_description">
Learn about your users through easy integration with Piwik, Google Analytics, and other usage monitoring and statistics systems.
</div>
<div class="feature_icon" data-i18n="welcomepage.feature8.title" ></div>
<div class="feature_description" data-i18n="welcomepage.feature8.content"></div>
</div>
</div>
</div>
@@ -168,30 +121,46 @@
<div style="position: relative;" id="header_container">
<div id="header">
<span id="toolbar">
<a class="button" data-container="body" data-toggle="popover" data-placement="bottom" shortcut="mutePopover" content="Mute / Unmute" onclick='toggleAudio();'>
<span id="authentication" class="authentication" style="display: none">
<a class="button" id="toolbar_button_authentication" >
<i id="authButton" class="icon-avatar"></i>
</a>
<ul class="loginmenu">
<span class="loginmenuPadding"></span>
<li id="toolbar_auth_identity" class="identity"></li>
<li id="toolbar_button_login">
<a class="authButton" data-i18n="toolbar.login"></a>
</li>
<li id="toolbar_button_logout">
<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>
<div class="header_button_separator"></div>
<a class="button" data-container="body" data-toggle="popover" data-placement="bottom" shortcut="toggleVideoPopover" content="Start / stop camera" onclick='toggleVideo();'>
<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" data-container="body" data-toggle="popover" data-placement="bottom" content="Record" onclick='toggleRecording();'>
<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" data-container="body" data-toggle="popover" data-placement="bottom" content="Lock / unlock room" onclick="Toolbar.openLockDialog();">
<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" data-container="body" data-toggle="popover" data-placement="bottom" content="Invite others" onclick="Toolbar.openLinkDialog();">
<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" data-container="body" data-toggle="popover" shortcut="toggleChatPopover" data-placement="bottom" content="Open / close chat" onclick='BottomToolbar.toggleChat();'>
<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>
@@ -199,34 +168,38 @@
</span>
<span id="prezi_button">
<div class="header_button_separator"></div>
<a class="button" data-container="body" data-toggle="popover" data-placement="bottom" content="Share Prezi" onclick='Prezi.openPreziDialog();'>
<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" data-container="body" data-toggle="popover" data-placement="bottom" content="Shared document" onclick='Etherpad.toggleEtherpad(0);'>
<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" data-container="body" data-toggle="popover" data-placement="bottom" content="Share screen" onclick="toggleScreenSharing();">
<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" data-container="body" data-toggle="popover" data-placement="bottom" content="Enter / Exit Full Screen" onclick='buttonClick("#fullScreen", "icon-full-screen icon-exit-full-screen");Toolbar.toggleFullScreen();'>
<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" data-container="body" data-toggle="popover" data-placement="bottom" content="Call SIP number" onclick='callSipButtonClicked();'>
<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" data-container="body" data-toggle="popover" data-placement="bottom" content="Hang Up" onclick='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>
@@ -235,7 +208,7 @@
<div id="subject"></div>
</div>
<div id="settings">
<h1>Connection Settings</h1>
<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>
@@ -243,14 +216,18 @@
<input id="connect" type="submit" value="Connect" />
</form>
</div>
<div id="reloadPresentation"><a onclick='Prezi.reloadPresentation();'><i title="Reload Prezi" class="fa fa-repeat fa-lg"></i></a></div>
<div id="videospace" onmousemove="ToolbarToggler.showToolbar();">
<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" >powered by jitsi.org</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">
@@ -261,18 +238,13 @@
</span>
<audio id="localAudio" autoplay oncontextmenu="return false;" muted></audio>
<span class="focusindicator"></span>
<!--<div class="connectionindicator">
<span class="connection connection_empty"><i class="icon-connection"></i></span>
<span class="connection connection_full"><i class="icon-connection"></i></span>
</div>-->
</span>
<audio id="userJoined" src="sounds/joined.wav" preload="auto"></audio>
<audio id="userLeft" src="sounds/left.wav" preload="auto"></audio>
</div>
<span id="bottomToolbar">
<span class="bottomToolbar_span">
<a class="bottomToolbarButton" data-container="body" data-toggle="popover" shortcut="toggleChatPopover" data-placement="top" content="Open / close chat" onclick='BottomToolbar.toggleChat();'>
<a class="bottomToolbarButton" id="bottom_toolbar_chat" data-container="body" data-toggle="popover" shortcut="toggleChatPopover" data-placement="top" data-i18n="[content]bottomtoolbar.chat" content="Open / close chat">
<i id="chatBottomButton" class="icon-chat-simple">
<span id="bottomUnreadMessages"></span>
</i>
@@ -280,7 +252,7 @@
</span>
<div class="bottom_button_separator"></div>
<span class="bottomToolbar_span">
<a class="bottomToolbarButton" data-container="body" data-toggle="popover" data-placement="top" id="contactlistpopover" content="Open / close contact list" onclick='BottomToolbar.toggleContactList();'>
<a class="bottomToolbarButton" id="bottom_toolbar_contact_list" data-container="body" data-toggle="popover" data-placement="top" id="contactlistpopover" data-i18n="[content]bottomtoolbar.contactlist" content="Open / close contact list">
<i id="contactListButton" class="icon-contactList">
<span id="numberOfParticipants"></span>
</i>
@@ -288,36 +260,44 @@
</span>
<div class="bottom_button_separator"></div>
<span class="bottomToolbar_span">
<a class="bottomToolbarButton" data-container="body" data-toggle="popover" shortcut="filmstripPopover" data-placement="top" content="Show / hide film strip" onclick='BottomToolbar.toggleFilmStrip()'>
<a class="bottomToolbarButton" id="bottom_toolbar_film_strip" data-container="body" data-toggle="popover" shortcut="filmstripPopover" data-placement="top" data-i18n="[content]bottomtoolbar.filmstrip" content="Show / hide film strip">
<i id="filmStripButton" class="icon-filmstrip"></i>
</a>
</span>
</span>
</div>
<div id="chatspace">
<div id="chatspace" class="right-panel">
<div id="nickname">
Enter a nickname in the box below
<span data-i18n="chat.nickname.title"></span>
<form>
<input type='text' id="nickinput" placeholder='Choose a nickname' autofocus>
<input type='text' id="nickinput" data-i18n="[placeholder]chat.nickname.popover" autofocus>
</form>
</div>
<!--div><i class="fa fa-comments">&nbsp;</i><span class='nick'></span>:&nbsp;<span class='chattext'></span></div-->
<div id="chatconversation"></div>
<audio id="chatNotification" src="sounds/incomingMessage.wav" preload="auto"></audio>
<textarea id="usermsg" placeholder='Enter text...' autofocus></textarea>
<textarea id="usermsg" data-i18n="[placeholder]chat.messagebox" autofocus></textarea>
<div id="smileysarea">
<div id="smileys" onclick="Chat.toggleSmileys()">
<div id="smileys" id="toggle_smileys">
<img src="images/smile.svg"/>
</div>
</div>
</div>
<div id="contactlist">
<div id="contactlist" class="right-panel">
<ul>
<li class="title"><i class="icon-contact-list"></i> CONTACT LIST</li>
<li class="title"><i class="icon-contact-list"></i><span data-i18n="contactlist"></span></li>
</ul>
</div>
<a id="downloadlog" onclick='dump(event.target);' data-container="body" data-toggle="popover" data-placement="right" data-content="Download logs" ><i class="fa fa-cloud-download"></i></a>
<div id="settingsmenu" class="right-panel">
<div class="icon-settings" data-i18n="settings.title"></div>
<img id="avatar" src="https://www.gravatar.com/avatar/87291c37c25be69a072a4514931b1749?d=wavatar&size=30"/>
<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">
<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>
</div>
</body>
</html>

View File

@@ -5,12 +5,15 @@ var interfaceConfig = {
INITIAL_TOOLBAR_TIMEOUT: 20000,
TOOLBAR_TIMEOUT: 4000,
DEFAULT_REMOTE_DISPLAY_NAME: "Fellow Jitster",
DEFAULT_DOMINANT_SPEAKER_DISPLAY_NAME: "Speaker",
DEFAULT_DOMINANT_SPEAKER_DISPLAY_NAME: "speaker",
DEFAULT_LOCAL_DISPLAY_NAME: "me",
SHOW_JITSI_WATERMARK: true,
JITSI_WATERMARK_LINK: "http://jitsi.org",
SHOW_BRAND_WATERMARK: false,
BRAND_WATERMARK_LINK: "",
SHOW_POWERED_BY: false,
GENERATE_ROOMNAMES_ON_WELCOME_PAGE: true,
APP_NAME: "Jitsi Meet"
APP_NAME: "Jitsi Meet",
INVITATION_POWERED_BY: true,
ACTIVE_SPEAKER_AVATAR_SIZE: 100
};

View File

@@ -1,79 +0,0 @@
var KeyboardShortcut = (function(my) {
//maps keycode to character, id of popover for given function and function
var shortcuts = {
67: {
character: "C",
id: "toggleChatPopover",
function: BottomToolbar.toggleChat
},
70: {
character: "F",
id: "filmstripPopover",
function: BottomToolbar.toggleFilmStrip
},
77: {
character: "M",
id: "mutePopover",
function: toggleAudio
},
84: {
character: "T",
function: function() {
if(!isAudioMuted()) {
toggleAudio();
}
}
},
86: {
character: "V",
id: "toggleVideoPopover",
function: toggleVideo
}
};
window.onkeyup = function(e) {
var keycode = e.which;
if(!($(":focus").is("input[type=text]") || $(":focus").is("input[type=password]") || $(":focus").is("textarea"))) {
if (typeof shortcuts[keycode] === "object") {
shortcuts[keycode].function();
} else if (keycode >= "0".charCodeAt(0) && keycode <= "9".charCodeAt(0)) {
var remoteVideos = $(".videocontainer:not(#mixedstream)"),
videoWanted = keycode - "0".charCodeAt(0) + 1;
if (remoteVideos.length > videoWanted) {
remoteVideos[videoWanted].click();
}
}
//esc while the smileys are visible hides them
} else if (keycode === 27 && $('#smileysContainer').is(':visible')) {
Chat.toggleSmileys();
}
};
window.onkeydown = function(e) {
if(!($(":focus").is("input[type=text]") || $(":focus").is("input[type=password]") || $(":focus").is("textarea"))) {
if(e.which === "T".charCodeAt(0)) {
if(isAudioMuted()) {
toggleAudio();
}
}
}
};
/**
*
* @param id indicates the popover associated with the shortcut
* @returns {string} the keyboard shortcut used for the id given
*/
my.getShortcut = function(id) {
for(var keycode in shortcuts) {
if(shortcuts.hasOwnProperty(keycode)) {
if (shortcuts[keycode].id === id) {
return " (" + shortcuts[keycode].character + ")";
}
}
}
return "";
};
return my;
}(KeyboardShortcut || {}))

6
lang/languages-bg.json Normal file
View File

@@ -0,0 +1,6 @@
{
"en": "Английски",
"bg": "Български",
"de": "Немски",
"tr": "Турски"
}

6
lang/languages-de.json Normal file
View File

@@ -0,0 +1,6 @@
{
"en": "Englisch",
"bg": "Bulgarisch",
"de": "Deutsch",
"tr": "Türkisch"
}

5
lang/languages-tr.json Normal file
View File

@@ -0,0 +1,5 @@
{
"en": "İngilizce",
"bg": "Bulgarca",
"de": "Almanca"
}

6
lang/languages.json Normal file
View File

@@ -0,0 +1,6 @@
{
"en": "English",
"bg": "Bulgarian",
"de": "German",
"tr": "Turkish"
}

204
lang/main-bg.json Normal file
View File

@@ -0,0 +1,204 @@
{
"contactlist": "СПИСЪК С КОНТАКТИ",
"connectionsettings": "Настройки на връзката",
"poweredby": "",
"downloadlogs": "",
"roomUrlDefaultMsg": "",
"participant": "Участник",
"me": "аз",
"speaker": "",
"defaultNickname": "",
"defaultPreziLink": "",
"welcomepage": {
"go": "",
"roomname": "Въведете име на стаята",
"disable": "",
"feature1": {
"title": "",
"content": ""
},
"feature2": {
"title": "",
"content": ""
},
"feature3": {
"title": "",
"content": ""
},
"feature4": {
"title": "",
"content": ""
},
"feature5": {
"title": "",
"content": ""
},
"feature6": {
"title": "",
"content": ""
},
"feature7": {
"title": "",
"content": ""
},
"feature8": {
"title": "",
"content": ""
}
},
"toolbar": {
"mute": "",
"videomute": "",
"authenticate": "",
"record": "",
"lock": "",
"invite": "",
"chat": "",
"prezi": "",
"etherpad": "",
"sharescreen": "",
"fullscreen": "",
"sip": "",
"Settings": "",
"hangup": "",
"login": "",
"logout": ""
},
"bottomtoolbar": {
"chat": "",
"filmstrip": "",
"contactlist": ""
},
"chat": {
"nickname": {
"title": "",
"popover": ""
},
"messagebox": ""
},
"settings": {
"title": "НАСТРОЙКИ",
"update": "",
"name": ""
},
"videothumbnail": {
"editnickname": "",
"moderator": "",
"videomute": "",
"mute": "",
"kick": "",
"muted": "",
"domute": ""
},
"connectionindicator": {
"bitrate": "",
"packetloss": "",
"resolution": "",
"less": "",
"more": "",
"address": "",
"remoteport": "",
"remoteport_plural": "",
"localport": "",
"localport_plural": "",
"localaddress": "",
"localaddress_plural": "",
"remoteaddress": "",
"remoteaddress_plural": "",
"transport": "",
"bandwidth": "",
"na": ""
},
"notify": {
"disconnected": "",
"moderator": "",
"connected": "",
"somebody": "",
"me": "",
"focus": "",
"focusFail": "",
"grantedTo": "",
"grantedToUnknown": ""
},
"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": "",
"password": "",
"userPassword": "",
"token": ""
},
"email": {
"sharedKeyThis conference is password protected. Please use the following pin when joining:\n\n\n__sharedKey__\n\n": "",
"subject": "",
"bodyHey there, I%27d like to invite you to a __appName__ conference I%27ve just set up.\n\n\nPlease click on the following link in order to join the conference.\n\n\n__roomUrl__\n\n\n__sharedKeyText__\n Note that __appName__ is currently only supported by __supportedBrowsers__, so you need to be using one of these browsers.\n\n\nTalk to you in a sec!": [
"Здравей, Бих искал да те поканя в една __appName__ конференция, която създадох.",
"",
"",
"Кликни на следния линк за да се присъединиш в конференцията.",
"",
"",
"__sharedKeyText__",
"__appName__ поддържа __supportedBrowsers__, така че трябва да използваш един от тези браузъри.",
"",
"",
"Ще се видим след секунда!"
],
"and": ""
}
}

207
lang/main-de.json Normal file
View File

@@ -0,0 +1,207 @@
{
"contactlist": "Kontaktliste",
"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",
"disable": "Diese Seite beim nächsten Betreten nicht mehr anzeigen",
"feature1": {
"title": "Einfach zu benutzen",
"content": "Kein Download nötig. __app__ läuft direkt im Browser. Einfach die Konferenzadresse teilen und los geht's."
},
"feature2": {
"title": "Niedrige Bandbreite",
"content": "Videokonferenzen mit mehreren Teilnehmen mit weniger als 128Kpbs. Bildschirmfreigaben und Telefonkonferenzen kommen sogar mit noch weniger Bandbreite aus."
},
"feature3": {
"title": "Open Source",
"content": "__app__ steht unter der MIT Lizenz. __app__ kann gemäss der Lizenz heruntergeladen, verwendet, verändert und weitergegeben werden."
},
"feature4": {
"title": "Unbegrenzte Anzahl Benutzer",
"content": "Es gibt keine künstliche Beschränkung der Anzahl der Benutzer oder Konferenzteilnehmer. Die Leistung des Servers und die Bandbreite sind die einzigen limitierenden Faktoren."
},
"feature5": {
"title": "Bildschirmfreigabe",
"content": "Es ist ganz einfach den Bildschirm zu teilen. __app__ ist ideal für Online-Präsentationen, Vorlesungen und Fernwartungsanfragen."
},
"feature6": {
"title": "Sichere Konferenzen",
"content": "Privatsphäre gewünscht? __app__ Konferenzen können mit einem Passwort geschützt werden um ungebetene Gäste fernzuhalten und Unterbrechungen zu vermeiden."
},
"feature7": {
"title": "Freigegebene Notizen",
"content": "__app__ verwendent Etherpad, ein Editor zur kollaborativen Bearbeitung von Texten."
},
"feature8": {
"title": "Benutzungsstatistiken",
"content": "Die Verwendung kann durch die Integration mit Piwik, Google Analytics und anderen Überwachungs- und Statistikprogrammen protokolliert werden."
}
},
"toolbar": {
"mute": "Stummschaltung aktivieren / deaktivieren",
"videomute": "Kamera starten / stoppen",
"authenticate": "Anmelden",
"record": "Aufnehmen",
"lock": "Raum schützen / Schutz aufheben",
"invite": "Andere einladen",
"chat": "Chat öffnen / schliessen",
"prezi": "Prezi freigeben",
"etherpad": "Geteiltes Dokument",
"sharescreen": "Bildschirm freigeben",
"fullscreen": "Vollbildmodus aktivieren / deaktivieren",
"sip": "SIP Nummer anrufen",
"Settings": "Einstellungen",
"hangup": "Auflegen",
"login": "Anmelden",
"logout": "Abmelden"
},
"bottomtoolbar": {
"chat": "Chat öffnen / schliessen",
"filmstrip": "Videovorschauen anzeigen / verstecken",
"contactlist": "Kontaktliste öffnen / schliessen"
},
"chat": {
"nickname": {
"title": "Nickname im Eingabefeld eingeben",
"popover": "Einen Namen auswählen"
},
"messagebox": "Text eingeben..."
},
"settings": {
"title": "Einstellungen",
"update": "Aktualisieren",
"name": "Name"
},
"videothumbnail": {
"editnickname": "Klicken um den Anzeigenamen zu bearbeiten",
"moderator": "Besitzer dieser Konferenz",
"videomute": "Teilnehmer hat die Kamera pausiert.",
"mute": "Teilnehmer ist stumm geschaltet",
"kick": "Hinauswerfen",
"muted": "Stummgeschaltet",
"domute": "Stummschalten"
},
"connectionindicator": {
"bitrate": "Bitrate:",
"packetloss": "Paketverlust:",
"resolution": "Auflösung:",
"less": "Weniger anzeigen",
"more": "Mehr anzeigen",
"address": "Adresse:",
"remoteport": "Entfernter Port:",
"remoteport_plural": "Entfernte Ports:",
"localport": "Lokaler Port:",
"localport_plural": "Lokale Ports:",
"localaddress": "Lokale Adresse:",
"localaddress_plural": "Lokale Adressen:",
"remoteaddress": "Entfernte Adresse:",
"remoteaddress_plural": "Entfernte Adressen:",
"transport": "Protokoll:",
"bandwidth": "Geschätzte Bandbreite:",
"na": "Verbindungsdaten erneut anzeigen wenn die Konferenz begonnen hat"
},
"notify": {
"disconnected": "getrennt",
"moderator": "Moderatorenrechte vergeben",
"connected": "verbunden",
"somebody": "Jemand",
"me": "Ich",
"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."
},
"dialog": {
"kickMessage": "Oh! Sie wurden aus der Konferenz ausgeschlossen.",
"popupError": "Ihr Browser blockiert Popups von dieser Webseite. Bitte erlauben Sie Popups in den Sicherheitseinstellungen und versuchen Sie es erneut.",
"passwordError": "Diese Konferenz ist mit einem Paswort geschützt. Nur der Besitzer der Konferenz kann ein Passwort vergeben.",
"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.",
"error": "Fehler",
"detectext": "Fehler bei der Erkennung der Bildschirmfreigabeerweiterung.",
"failtoinstall": "Die Bildschirmfreigabeerweiterung konnte nicht installiert werden.",
"failedpermissions": "Die Zugriffsberechtigungen auf das Mikrofon und/oder die Kamera konnte nicht eingeholt werden.",
"bridgeUnavailable": "Die Jitsi Videobridge ist momentan nicht erreichbar. Bitte versuchen Sie es später noch einmal.",
"lockTitle": "Sperren fehlgeschlagen",
"lockMessage": "Die Konferenz konnte nicht gesperrt werden.",
"warning": "Warnung",
"passwordNotSupported": "Passwörter für Räume werden nicht unterstützt.",
"sorry": "Entschuldigung",
"internalError": "Interner Anwendungsfehler [setRemoteDescription]",
"unableToSwitch": "Der Videodatenstrom kann nicht gewechselt werden.",
"SLDFailure": "Oh! Die Stummschaltung konnte nicht aktiviert werden. (SLD Fehler)",
"SRDFailure": "Oh! Das Video konnte nicht gestoppt werden. (SRD Fehler)",
"oops": "Oh!",
"defaultError": "Es ist ein Fehler aufgetreten",
"passwordRequired": "Passwort erforderlich",
"Ok": "OK",
"removePreziTitle": "Prezi entfernen",
"removePreziMsg": "Sind Sie sich sicher dass sie Prezi entfernen möchten?",
"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",
"Cancel": "Abbrechen",
"logoutTitle": "Abmelden",
"logoutQuestion": "Sind Sie sicher dass Sie sich abmelden und die Konferenz verlassen möchten?",
"sessTerminated": "Sitzung beendet",
"hungUp": "Anruf beendet",
"joinAgain": "Erneut beitreten",
"Share": "Teilen",
"preziLinkError": "Bitte einen gültigen Prezi-Link angeben.",
"Save": "Speichern",
"recordingToken": "Aufnahme-Token eingeben",
"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 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 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__",
"password": "Passwort",
"userPassword": "Benutzerpasswort",
"token": "Token"
},
"email": {
"sharedKeyThis conference is password protected. Please use the following pin when joining:\n\n __sharedKey__ \n\n": [
"Diese Konferenz ist Passwortgeschützt. Bitte verwenden Sie diesen PIN zum Beitreten:",
"",
" __sharedKey__",
"",
"",
""
],
"subject": "Einladung zu einer __appName__ (__conferenceName__)",
"bodyHey there, I%27d like to invite you to a __appName__ conference I%27ve just set up.\n\nPlease click on the following link in order to join the conference.\n\n __roomUrl__\n\n__sharedKeyText__ Note that __appName__ is currently only supported by Chromium, Google Chrome and Opera, so you need to be using one of these browsers.\n\nTalk to you in a sec!": [
"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 Chromium, Google und Opera verwendet werden kann.",
"",
"Bis gleich!"
]
}
}

173
lang/main-tr.json Normal file
View File

@@ -0,0 +1,173 @@
{
"contactlist": "KİŞİ LİSTESİ",
"connectionsettings": "Bağlantı Ayarları",
"poweredby": "Gücünün kaynağı",
"downloadlogs": "Günlükleri indir",
"welcomepage": {
"go": "GİT",
"roomname": "Oda adı girin",
"disable": "Sonraki girişimde bu sayfayı gösterme",
"feature1": {
"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": "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": "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": "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": "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": "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": "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": "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": "Sessiz / Sesli",
"videomute": "Kamera başlat / durdur",
"authenticate": "",
"record": "Kaydet",
"lock": "Odayı kilitle / kilit aç",
"invite": "Arkadaşlarını davet et",
"chat": "",
"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": "Sohbeti aç / kapa",
"filmstrip": "Kişi listesi aç / kapa",
"contactlist": "Film şeridini göster / gizle"
},
"chat": {
"nickname": {
"title": "Aşağıdaki kutuya bir takma ad girin",
"popover": "Bir takma ad seçin"
},
"messagebox": "Metin girin..."
},
"settings": {
"title": "AYARLAR",
"update": "Güncelle",
"name": "Ad"
},
"videothumbnail": {
"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": "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": "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": "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ışıı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__"
}
}

220
lang/main.json Normal file
View File

@@ -0,0 +1,220 @@
{
"contactlist": "CONTACT LIST",
"connectionsettings": "Connection Settings",
"poweredby": "powered by",
"downloadlogs": "Download logs",
"roomUrlDefaultMsg": "Your conference is currently being created...",
"participant": "Participant",
"me": "me",
"speaker": "Speaker",
"defaultNickname": "ex. __name__",
"defaultPreziLink": "e.g. __url__",
"welcomepage":{
"go": "GO",
"roomname": "Enter room name",
"disable": "Don't show this page next time I enter",
"feature1": {
"title": "Simple to use",
"content": "No downloads required. __app__ works directly within your browser. Simply share your conference URL with others to get started."
},
"feature2": {
"title": "Low bandwidth",
"content": "Multi-party video conferences work with as little as 128Kbps. Screen-sharing and audio-only conferences are possible with far less."
},
"feature3": {
"title": "Open source",
"content": "__app__ is licensed under MIT. You are free to download, use, modify, and share them as per these licenses."
},
"feature4": {
"title": "Unlimited users",
"content": "There are no artificial restrictions on the number of users or conference participants. Server power and bandwidth are the only limiting factors."
},
"feature5": {
"title": "Screen sharing",
"content": "It's easy to share your screen with others. __app__ is ideal for on-line presentations, lectures, and tech support sessions."
},
"feature6": {
"title": "Secure rooms",
"content": "Need some privacy? __app__ conference rooms can be secured with a password in order to exclude unwanted guests and prevent interruptions."
},
"feature7": {
"title": "Shared notes",
"content": "__app__ features Etherpad, a real-time collaborative text editor that's great for meeting minutes, writing articles, and more."
},
"feature8": {
"title": "Usage statistics",
"content": "Learn about your users through easy integration with Piwik, Google Analytics, and other usage monitoring and statistics systems."
}
},
"toolbar": {
"mute": "Mute / Unmute",
"videomute": "Start / stop camera",
"authenticate": "Authenticate",
"record": "Record",
"lock": "Lock / unlock room",
"invite": "Invite others",
"chat": "Open / close chat",
"prezi": "Share Prezi",
"etherpad": "Shared document",
"sharescreen": "Share screen",
"fullscreen": "Enter / Exit Full Screen",
"sip": "Call SIP number",
"Settings": "Settings",
"hangup": "Hang Up",
"login": "Login",
"logout": "Logout"
},
"bottomtoolbar": {
"chat": "Open / close chat",
"filmstrip": "Show / hide film strip",
"contactlist": "Open / close contact list"
},
"chat":{
"nickname": {
"title": "Enter a nickname in the box below",
"popover": "Choose a nickname"
},
"messagebox": "Enter text..."
},
"settings":
{
"title": "SETTINGS",
"update": "Update",
"name": "Name"
},
"videothumbnail":
{
"editnickname": "Click to edit your<br/>display name",
"moderator": "The owner of<br/>this conference",
"videomute": "Participant has<br/>stopped the camera.",
"mute": "Participant is muted",
"kick": "Kick out",
"muted": "Muted",
"domute": "Mute"
},
"connectionindicator":
{
"bitrate": "Bitrate:",
"packetloss": "Packet loss:",
"resolution": "Resolution:",
"less": "Show less",
"more": "Show more",
"address": "Address:",
"remoteport_plural": "Remote ports:",
"localport_plural": "Local ports:",
"remoteport": "Remote port:",
"localport": "Local port:",
"localaddress": "Local address:",
"localaddress_plural": "Local addresses:",
"remoteaddress": "Remote address:",
"remoteaddress_plural": "Remote addresses:",
"transport": "Transport:",
"bandwidth": "Estimated bandwidth:",
"na": "Come back here for connection information once the conference starts"
},
"notify": {
"disconnected": "disconnected",
"moderator": "Moderator rights granted!",
"connected": "connected",
"somebody": "Somebody",
"me": "Me",
"focus": "Conference focus",
"focusFail": "__component__ not available - retry in __ms__ sec",
"grantedTo": "Moderator rights granted to __to__!",
"grantedToUnknown": "Moderator rights granted to $t(somebody)!"
},
"dialog": {
"kickMessage": "Ouch! You have been kicked out of the meet!",
"popupError": "Your browser is blocking popup windows from this site. Please enable popups in your browser security settings and try again.",
"passwordError": "This conversation is currently protected by a password. Only the owner of the conference could set a password.",
"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.",
"error": "Error",
"detectext": "Error when trying to detect desktopsharing extension.",
"failtoinstall": "Failed to install desktop sharing extension",
"failedpermissions": "Failed to obtain permissions to use the local microphone and/or camera.",
"bridgeUnavailable": "Jitsi Videobridge is currently unavailable. Please try again later!",
"lockTitle": "Lock failed",
"lockMessage": "Failed to lock conference.",
"warning": "Warning",
"passwordNotSupported": "Room passwords are currently not supported.",
"sorry": "Sorry",
"internalError": "Internal application error [setRemoteDescription]",
"unableToSwitch": "Unable to switch video stream.",
"SLDFailure": "Oops! Something went wrong and we failed to mute! (SLD Failure)",
"SRDFailure": "Oops! Something went wrong and we failed to stop video! (SRD Failure)",
"oops": "Oops!",
"defaultError": "There was some kind of error",
"passwordRequired": "Password required",
"Ok": "Ok",
"removePreziTitle": "Remove Prezi",
"removePreziMsg": "Are you sure you would like to remove your Prezi?",
"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",
"Cancel": "Cancel",
"logoutTitle" : "Logout",
"logoutQuestion" : "Are you sure you want to logout and stop the conference?",
"sessTerminated": "Session Terminated",
"hungUp": "You hung up the call",
"joinAgain": "Join again",
"Share": "Share",
"preziLinkError": "Please provide a correct prezi link.",
"Save": "Save",
"recordingToken": "Enter recording token",
"Dial": "Dial",
"sipMsg": "Enter SIP number",
"passwordCheck": "Are you sure you would like to remove your password?",
"Remove": "Remove",
"passwordMsg": "Set a password to lock your room",
"Invite": "Invite",
"shareLink": "Share this link with everyone you want to invite",
"settings1": "Configure your conference",
"settings2": "Participants join muted",
"settings3": "Require nicknames<br/><br/>Set a password to lock your room:",
"yourPassword": "your password",
"Back": "Back",
"serviceUnavailable": "Service unavailable",
"gracefulShutdown": "Our service is currently down for maintenance. Please try again later.",
"Yes": "Yes",
"reservationError": "Reservation system error",
"reservationErrorMsg": "Error code: __code__, message: __msg__",
"password": "password",
"userPassword": "user password",
"token": "token"
},
"email":
{
"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.",
"",
"",
"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"
}
}

26842
libs/app.bundle.js Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,94 +0,0 @@
/* colibri.js -- a COLIBRI focus
* The colibri spec has been submitted to the XMPP Standards Foundation
* for publications as a XMPP extensions:
* http://xmpp.org/extensions/inbox/colibri.html
*
* colibri.js is a participating focus, i.e. the focus participates
* in the conference. The conference itself can be ad-hoc, through a
* MUC, through PubSub, etc.
*
* colibri.js relies heavily on the strophe.jingle library available
* from https://github.com/ESTOS/strophe.jingle
* and interoperates with the Jitsi videobridge available from
* https://jitsi.org/Projects/JitsiVideobridge
*/
/*
Copyright (c) 2013 ESTOS GmbH
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.
*/
// A colibri session is similar to a jingle session, it just implements some things differently
// FIXME: inherit jinglesession, see https://github.com/legastero/Jingle-RTCPeerConnection/blob/master/index.js
function ColibriSession(me, sid, connection) {
this.me = me;
this.sid = sid;
this.connection = connection;
//this.peerconnection = null;
//this.mychannel = null;
//this.channels = null;
this.peerjid = null;
this.colibri = null;
}
// implementation of JingleSession interface
ColibriSession.prototype.initiate = function (peerjid, isInitiator) {
this.peerjid = peerjid;
};
ColibriSession.prototype.sendOffer = function (offer) {
console.log('ColibriSession.sendOffer');
};
ColibriSession.prototype.accept = function () {
console.log('ColibriSession.accept');
};
ColibriSession.prototype.addSource = function (elem, fromJid) {
this.colibri.addSource(elem, fromJid);
};
ColibriSession.prototype.removeSource = function (elem, fromJid) {
this.colibri.removeSource(elem, fromJid);
};
ColibriSession.prototype.terminate = function (reason) {
this.colibri.terminate(this, reason);
};
ColibriSession.prototype.active = function () {
console.log('ColibriSession.active');
};
ColibriSession.prototype.setRemoteDescription = function (elem, desctype) {
this.colibri.setRemoteDescription(this, elem, desctype);
};
ColibriSession.prototype.addIceCandidate = function (elem) {
this.colibri.addIceCandidate(this, elem);
};
ColibriSession.prototype.sendAnswer = function (sdp, provisional) {
console.log('ColibriSession.sendAnswer');
};
ColibriSession.prototype.sendTerminate = function (reason, text) {
this.colibri.sendTerminate(this, reason, text);
};

View File

@@ -1,94 +0,0 @@
/* jshint -W117 */
Strophe.addConnectionPlugin('rayo',
{
RAYO_XMLNS: 'urn:xmpp:rayo:1',
connection: null,
init: function (conn)
{
this.connection = conn;
if (this.connection.disco)
{
this.connection.disco.addFeature('urn:xmpp:rayo:client:1');
}
this.connection.addHandler(
this.onRayo.bind(this), this.RAYO_XMLNS, 'iq', 'set', null, null);
},
onRayo: function (iq)
{
console.info("Rayo IQ", iq);
},
dial: function (to, from, roomName)
{
var self = this;
var req = $iq(
{
type: 'set',
to: config.hosts.call_control
}
);
req.c('dial',
{
xmlns: this.RAYO_XMLNS,
to: to,
from: from
});
req.c('header',
{
name: 'JvbRoomName',
value: roomName
});
this.connection.sendIQ(
req,
function (result)
{
console.info('Dial result ', result);
var resource = $(result).find('ref').attr('uri');
this.call_resource = resource.substr('xmpp:'.length);
console.info(
"Received call resource: " + this.call_resource);
},
function (error)
{
console.info('Dial error ', error);
}
);
},
hang_up: function ()
{
if (!this.call_resource)
{
console.warn("No call in progress");
return;
}
var self = this;
var req = $iq(
{
type: 'set',
to: this.call_resource
}
);
req.c('hangup',
{
xmlns: this.RAYO_XMLNS
});
this.connection.sendIQ(
req,
function (result)
{
console.info('Hangup result ', result);
self.call_resource = null;
},
function (error)
{
console.info('Hangup error ', error);
self.call_resource = null;
}
);
}
}
);

View File

@@ -1,702 +0,0 @@
function TraceablePeerConnection(ice_config, constraints) {
var self = this;
var RTCPeerconnection = navigator.mozGetUserMedia ? mozRTCPeerConnection : webkitRTCPeerConnection;
this.peerconnection = new RTCPeerconnection(ice_config, constraints);
this.updateLog = [];
this.stats = {};
this.statsinterval = null;
this.maxstats = 0; // limit to 300 values, i.e. 5 minutes; set to 0 to disable
/**
* Array of ssrcs that will be added on next modifySources call.
* @type {Array}
*/
this.addssrc = [];
/**
* Array of ssrcs that will be added on next modifySources call.
* @type {Array}
*/
this.removessrc = [];
/**
* Pending operation that will be done during modifySources call.
* Currently 'mute'/'unmute' operations are supported.
*
* @type {String}
*/
this.pendingop = null;
/**
* Flag indicates that peer connection stream have changed and modifySources should proceed.
* @type {boolean}
*/
this.switchstreams = false;
// override as desired
this.trace = function (what, info) {
//console.warn('WTRACE', what, info);
self.updateLog.push({
time: new Date(),
type: what,
value: info || ""
});
};
this.onicecandidate = null;
this.peerconnection.onicecandidate = function (event) {
self.trace('onicecandidate', JSON.stringify(event.candidate, null, ' '));
if (self.onicecandidate !== null) {
self.onicecandidate(event);
}
};
this.onaddstream = null;
this.peerconnection.onaddstream = function (event) {
self.trace('onaddstream', event.stream.id);
if (self.onaddstream !== null) {
self.onaddstream(event);
}
};
this.onremovestream = null;
this.peerconnection.onremovestream = function (event) {
self.trace('onremovestream', event.stream.id);
if (self.onremovestream !== null) {
self.onremovestream(event);
}
};
this.onsignalingstatechange = null;
this.peerconnection.onsignalingstatechange = function (event) {
self.trace('onsignalingstatechange', self.signalingState);
if (self.onsignalingstatechange !== null) {
self.onsignalingstatechange(event);
}
};
this.oniceconnectionstatechange = null;
this.peerconnection.oniceconnectionstatechange = function (event) {
self.trace('oniceconnectionstatechange', self.iceConnectionState);
if (self.oniceconnectionstatechange !== null) {
self.oniceconnectionstatechange(event);
}
};
this.onnegotiationneeded = null;
this.peerconnection.onnegotiationneeded = function (event) {
self.trace('onnegotiationneeded');
if (self.onnegotiationneeded !== null) {
self.onnegotiationneeded(event);
}
};
self.ondatachannel = null;
this.peerconnection.ondatachannel = function (event) {
self.trace('ondatachannel', event);
if (self.ondatachannel !== null) {
self.ondatachannel(event);
}
};
if (!navigator.mozGetUserMedia && this.maxstats) {
this.statsinterval = window.setInterval(function() {
self.peerconnection.getStats(function(stats) {
var results = stats.result();
for (var i = 0; i < results.length; ++i) {
//console.log(results[i].type, results[i].id, results[i].names())
var now = new Date();
results[i].names().forEach(function (name) {
var id = results[i].id + '-' + name;
if (!self.stats[id]) {
self.stats[id] = {
startTime: now,
endTime: now,
values: [],
times: []
};
}
self.stats[id].values.push(results[i].stat(name));
self.stats[id].times.push(now.getTime());
if (self.stats[id].values.length > self.maxstats) {
self.stats[id].values.shift();
self.stats[id].times.shift();
}
self.stats[id].endTime = now;
});
}
});
}, 1000);
}
};
dumpSDP = function(description) {
return 'type: ' + description.type + '\r\n' + description.sdp;
}
if (TraceablePeerConnection.prototype.__defineGetter__ !== undefined) {
TraceablePeerConnection.prototype.__defineGetter__('signalingState', function() { return this.peerconnection.signalingState; });
TraceablePeerConnection.prototype.__defineGetter__('iceConnectionState', function() { return this.peerconnection.iceConnectionState; });
TraceablePeerConnection.prototype.__defineGetter__('localDescription', function() {
var publicLocalDescription = simulcast.reverseTransformLocalDescription(this.peerconnection.localDescription);
return publicLocalDescription;
});
TraceablePeerConnection.prototype.__defineGetter__('remoteDescription', function() {
var publicRemoteDescription = simulcast.reverseTransformRemoteDescription(this.peerconnection.remoteDescription);
return publicRemoteDescription;
});
}
TraceablePeerConnection.prototype.addStream = function (stream) {
this.trace('addStream', stream.id);
this.peerconnection.addStream(stream);
};
TraceablePeerConnection.prototype.removeStream = function (stream) {
this.trace('removeStream', stream.id);
this.peerconnection.removeStream(stream);
};
TraceablePeerConnection.prototype.createDataChannel = function (label, opts) {
this.trace('createDataChannel', label, opts);
return this.peerconnection.createDataChannel(label, opts);
};
TraceablePeerConnection.prototype.setLocalDescription = function (description, successCallback, failureCallback) {
var self = this;
description = simulcast.transformLocalDescription(description);
this.trace('setLocalDescription', dumpSDP(description));
this.peerconnection.setLocalDescription(description,
function () {
self.trace('setLocalDescriptionOnSuccess');
successCallback();
},
function (err) {
self.trace('setLocalDescriptionOnFailure', err);
failureCallback(err);
}
);
/*
if (this.statsinterval === null && this.maxstats > 0) {
// start gathering stats
}
*/
};
TraceablePeerConnection.prototype.setRemoteDescription = function (description, successCallback, failureCallback) {
var self = this;
description = simulcast.transformRemoteDescription(description);
this.trace('setRemoteDescription', dumpSDP(description));
this.peerconnection.setRemoteDescription(description,
function () {
self.trace('setRemoteDescriptionOnSuccess');
successCallback();
},
function (err) {
self.trace('setRemoteDescriptionOnFailure', err);
failureCallback(err);
}
);
/*
if (this.statsinterval === null && this.maxstats > 0) {
// start gathering stats
}
*/
};
TraceablePeerConnection.prototype.hardMuteVideo = function (muted) {
this.pendingop = muted ? 'mute' : 'unmute';
};
TraceablePeerConnection.prototype.enqueueAddSsrc = function(channel, ssrcLines) {
if (!this.addssrc[channel]) {
this.addssrc[channel] = '';
}
this.addssrc[channel] += ssrcLines;
}
TraceablePeerConnection.prototype.addSource = function (elem) {
console.log('addssrc', new Date().getTime());
console.log('ice', this.iceConnectionState);
var sdp = new SDP(this.remoteDescription.sdp);
var mySdp = new SDP(this.peerconnection.localDescription.sdp);
var self = this;
$(elem).each(function (idx, content) {
var name = $(content).attr('name');
var lines = '';
tmp = $(content).find('ssrc-group[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]').each(function() {
var semantics = this.getAttribute('semantics');
var ssrcs = $(this).find('>source').map(function () {
return this.getAttribute('ssrc');
}).get();
if (ssrcs.length != 0) {
lines += 'a=ssrc-group:' + semantics + ' ' + ssrcs.join(' ') + '\r\n';
}
});
tmp = $(content).find('source[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]'); // can handle both >source and >description>source
tmp.each(function () {
var ssrc = $(this).attr('ssrc');
if(mySdp.containsSSRC(ssrc)){
/**
* This happens when multiple participants change their streams at the same time and
* ColibriFocus.modifySources have to wait for stable state. In the meantime multiple
* addssrc are scheduled for update IQ. See
*/
console.warn("Got add stream request for my own ssrc: "+ssrc);
return;
}
$(this).find('>parameter').each(function () {
lines += 'a=ssrc:' + ssrc + ' ' + $(this).attr('name');
if ($(this).attr('value') && $(this).attr('value').length)
lines += ':' + $(this).attr('value');
lines += '\r\n';
});
});
sdp.media.forEach(function(media, idx) {
if (!SDPUtil.find_line(media, 'a=mid:' + name))
return;
sdp.media[idx] += lines;
self.enqueueAddSsrc(idx, lines);
});
sdp.raw = sdp.session + sdp.media.join('');
});
};
TraceablePeerConnection.prototype.enqueueRemoveSsrc = function(channel, ssrcLines) {
if (!this.removessrc[channel]){
this.removessrc[channel] = '';
}
this.removessrc[channel] += ssrcLines;
}
TraceablePeerConnection.prototype.removeSource = function (elem) {
console.log('removessrc', new Date().getTime());
console.log('ice', this.iceConnectionState);
var sdp = new SDP(this.remoteDescription.sdp);
var mySdp = new SDP(this.peerconnection.localDescription.sdp);
var self = this;
$(elem).each(function (idx, content) {
var name = $(content).attr('name');
var lines = '';
tmp = $(content).find('ssrc-group[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]').each(function() {
var semantics = this.getAttribute('semantics');
var ssrcs = $(this).find('>source').map(function () {
return this.getAttribute('ssrc');
}).get();
if (ssrcs.length != 0) {
lines += 'a=ssrc-group:' + semantics + ' ' + ssrcs.join(' ') + '\r\n';
}
});
tmp = $(content).find('source[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]'); // can handle both >source and >description>source
tmp.each(function () {
var ssrc = $(this).attr('ssrc');
// This should never happen, but can be useful for bug detection
if(mySdp.containsSSRC(ssrc)){
console.error("Got remove stream request for my own ssrc: "+ssrc);
return;
}
$(this).find('>parameter').each(function () {
lines += 'a=ssrc:' + ssrc + ' ' + $(this).attr('name');
if ($(this).attr('value') && $(this).attr('value').length)
lines += ':' + $(this).attr('value');
lines += '\r\n';
});
});
sdp.media.forEach(function(media, idx) {
if (!SDPUtil.find_line(media, 'a=mid:' + name))
return;
sdp.media[idx] += lines;
self.enqueueRemoveSsrc(idx, lines);
});
sdp.raw = sdp.session + sdp.media.join('');
});
};
TraceablePeerConnection.prototype.modifySources = function(successCallback) {
var self = this;
if (this.signalingState == 'closed') return;
if (!(this.addssrc.length || this.removessrc.length || this.pendingop !== null || this.switchstreams)){
// There is nothing to do since scheduled job might have been executed by another succeeding call
if(successCallback){
successCallback();
}
return;
}
// FIXME: this is a big hack
// https://code.google.com/p/webrtc/issues/detail?id=2688
if (!(this.signalingState == 'stable' && this.iceConnectionState == 'connected')) {
console.warn('modifySources not yet', this.signalingState, this.iceConnectionState);
this.wait = true;
window.setTimeout(function() { self.modifySources(successCallback); }, 250);
return;
}
if (this.wait) {
window.setTimeout(function() { self.modifySources(successCallback); }, 2500);
this.wait = false;
return;
}
// Reset switch streams flag
this.switchstreams = false;
var sdp = new SDP(this.remoteDescription.sdp);
// add sources
this.addssrc.forEach(function(lines, idx) {
sdp.media[idx] += lines;
});
this.addssrc = [];
// remove sources
this.removessrc.forEach(function(lines, idx) {
lines = lines.split('\r\n');
lines.pop(); // remove empty last element;
lines.forEach(function(line) {
sdp.media[idx] = sdp.media[idx].replace(line + '\r\n', '');
});
});
this.removessrc = [];
sdp.raw = sdp.session + sdp.media.join('');
this.setRemoteDescription(new RTCSessionDescription({type: 'offer', sdp: sdp.raw}),
function() {
if(self.signalingState == 'closed') {
console.error("createAnswer attempt on closed state");
return;
}
self.createAnswer(
function(modifiedAnswer) {
// change video direction, see https://github.com/jitsi/jitmeet/issues/41
if (self.pendingop !== null) {
var sdp = new SDP(modifiedAnswer.sdp);
if (sdp.media.length > 1) {
switch(self.pendingop) {
case 'mute':
sdp.media[1] = sdp.media[1].replace('a=sendrecv', 'a=recvonly');
break;
case 'unmute':
sdp.media[1] = sdp.media[1].replace('a=recvonly', 'a=sendrecv');
break;
}
sdp.raw = sdp.session + sdp.media.join('');
modifiedAnswer.sdp = sdp.raw;
}
self.pendingop = null;
}
// FIXME: pushing down an answer while ice connection state
// is still checking is bad...
//console.log(self.peerconnection.iceConnectionState);
// trying to work around another chrome bug
//modifiedAnswer.sdp = modifiedAnswer.sdp.replace(/a=setup:active/g, 'a=setup:actpass');
self.setLocalDescription(modifiedAnswer,
function() {
//console.log('modified setLocalDescription ok');
if(successCallback){
successCallback();
}
},
function(error) {
console.error('modified setLocalDescription failed', error);
}
);
},
function(error) {
console.error('modified answer failed', error);
}
);
},
function(error) {
console.error('modify failed', error);
}
);
};
TraceablePeerConnection.prototype.close = function () {
this.trace('stop');
if (this.statsinterval !== null) {
window.clearInterval(this.statsinterval);
this.statsinterval = null;
}
this.peerconnection.close();
};
TraceablePeerConnection.prototype.createOffer = function (successCallback, failureCallback, constraints) {
var self = this;
this.trace('createOffer', JSON.stringify(constraints, null, ' '));
this.peerconnection.createOffer(
function (offer) {
self.trace('createOfferOnSuccess', dumpSDP(offer));
successCallback(offer);
},
function(err) {
self.trace('createOfferOnFailure', err);
failureCallback(err);
},
constraints
);
};
TraceablePeerConnection.prototype.createAnswer = function (successCallback, failureCallback, constraints) {
var self = this;
this.trace('createAnswer', JSON.stringify(constraints, null, ' '));
this.peerconnection.createAnswer(
function (answer) {
answer = simulcast.transformAnswer(answer);
self.trace('createAnswerOnSuccess', dumpSDP(answer));
successCallback(answer);
},
function(err) {
self.trace('createAnswerOnFailure', err);
failureCallback(err);
},
constraints
);
};
TraceablePeerConnection.prototype.addIceCandidate = function (candidate, successCallback, failureCallback) {
var self = this;
this.trace('addIceCandidate', JSON.stringify(candidate, null, ' '));
this.peerconnection.addIceCandidate(candidate);
/* maybe later
this.peerconnection.addIceCandidate(candidate,
function () {
self.trace('addIceCandidateOnSuccess');
successCallback();
},
function (err) {
self.trace('addIceCandidateOnFailure', err);
failureCallback(err);
}
);
*/
};
TraceablePeerConnection.prototype.getStats = function(callback, errback) {
if (navigator.mozGetUserMedia) {
// ignore for now...
} else {
this.peerconnection.getStats(callback);
}
};
// mozilla chrome compat layer -- very similar to adapter.js
function setupRTC() {
var RTC = null;
if (navigator.mozGetUserMedia) {
console.log('This appears to be Firefox');
var version = parseInt(navigator.userAgent.match(/Firefox\/([0-9]+)\./)[1], 10);
if (version >= 22) {
RTC = {
peerconnection: mozRTCPeerConnection,
browser: 'firefox',
getUserMedia: navigator.mozGetUserMedia.bind(navigator),
attachMediaStream: function (element, stream) {
element[0].mozSrcObject = stream;
element[0].play();
},
pc_constraints: {}
};
if (!MediaStream.prototype.getVideoTracks)
MediaStream.prototype.getVideoTracks = function () { return []; };
if (!MediaStream.prototype.getAudioTracks)
MediaStream.prototype.getAudioTracks = function () { return []; };
RTCSessionDescription = mozRTCSessionDescription;
RTCIceCandidate = mozRTCIceCandidate;
}
} else if (navigator.webkitGetUserMedia) {
console.log('This appears to be Chrome');
RTC = {
peerconnection: webkitRTCPeerConnection,
browser: 'chrome',
getUserMedia: navigator.webkitGetUserMedia.bind(navigator),
attachMediaStream: function (element, stream) {
element.attr('src', webkitURL.createObjectURL(stream));
},
// DTLS should now be enabled by default but..
pc_constraints: {'optional': [{'DtlsSrtpKeyAgreement': 'true'}]}
};
if (navigator.userAgent.indexOf('Android') != -1) {
RTC.pc_constraints = {}; // disable DTLS on Android
}
if (!webkitMediaStream.prototype.getVideoTracks) {
webkitMediaStream.prototype.getVideoTracks = function () {
return this.videoTracks;
};
}
if (!webkitMediaStream.prototype.getAudioTracks) {
webkitMediaStream.prototype.getAudioTracks = function () {
return this.audioTracks;
};
}
}
if (RTC === null) {
try { console.log('Browser does not appear to be WebRTC-capable'); } catch (e) { }
}
return RTC;
}
function getUserMediaWithConstraints(um, success_callback, failure_callback, resolution, bandwidth, fps, desktopStream) {
var constraints = {audio: false, video: false};
if (um.indexOf('video') >= 0) {
constraints.video = { mandatory: {}, optional: [] };// same behaviour as true
}
if (um.indexOf('audio') >= 0) {
constraints.audio = { mandatory: {}, optional: []};// same behaviour as 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 (um.indexOf('desktop') >= 0) {
constraints.video = {
mandatory: {
chromeMediaSource: "desktop",
chromeMediaSourceId: desktopStream,
googLeakyBucket: true,
maxWidth: window.screen.width,
maxHeight: window.screen.height,
maxFrameRate: 3
},
optional: []
}
}
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}
);
}
}
// Check if we are running on Android device
var isAndroid = navigator.userAgent.indexOf('Android') != -1;
if (resolution && !constraints.video || isAndroid) {
constraints.video = { mandatory: {}, optional: [] };// same behaviour as true
}
// see https://code.google.com/p/chromium/issues/detail?id=143631#c9 for list of supported resolutions
switch (resolution) {
// 16:9 first
case '1080':
case 'fullhd':
constraints.video.mandatory.minWidth = 1920;
constraints.video.mandatory.minHeight = 1080;
break;
case '720':
case 'hd':
constraints.video.mandatory.minWidth = 1280;
constraints.video.mandatory.minHeight = 720;
break;
case '360':
constraints.video.mandatory.minWidth = 640;
constraints.video.mandatory.minHeight = 360;
break;
case '180':
constraints.video.mandatory.minWidth = 320;
constraints.video.mandatory.minHeight = 180;
break;
// 4:3
case '960':
constraints.video.mandatory.minWidth = 960;
constraints.video.mandatory.minHeight = 720;
break;
case '640':
case 'vga':
constraints.video.mandatory.minWidth = 640;
constraints.video.mandatory.minHeight = 480;
break;
case '320':
constraints.video.mandatory.minWidth = 320;
constraints.video.mandatory.minHeight = 240;
break;
default:
if (isAndroid) {
constraints.video.mandatory.minWidth = 320;
constraints.video.mandatory.minHeight = 240;
constraints.video.mandatory.maxFrameRate = 15;
}
break;
}
if (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;
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
// so they choose 30fps mjpg over 10fps yuy2
if (!constraints.video) constraints.video = {mandatory: {}, optional: []};// same behaviour as true;
constraints.video.mandatory.minFrameRate = fps;
}
var isFF = navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
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) {
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 {
RTC.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);
}
});
}
} catch (e) {
console.error('GUM failed: ', e);
if(failure_callback) {
failure_callback(e);
}
}
}

View File

@@ -1,298 +0,0 @@
/* jshint -W117 */
Strophe.addConnectionPlugin('jingle', {
connection: null,
sessions: {},
jid2session: {},
ice_config: {iceServers: []},
pc_constraints: {},
media_constraints: {
mandatory: {
'OfferToReceiveAudio': true,
'OfferToReceiveVideo': true
}
// MozDontOfferDataChannel: true when this is firefox
},
localAudio: null,
localVideo: null,
init: function (conn) {
this.connection = conn;
if (this.connection.disco) {
// http://xmpp.org/extensions/xep-0167.html#support
// http://xmpp.org/extensions/xep-0176.html#support
this.connection.disco.addFeature('urn:xmpp:jingle:1');
this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:1');
this.connection.disco.addFeature('urn:xmpp:jingle:transports:ice-udp:1');
this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:audio');
this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:video');
// this is dealt with by SDP O/A so we don't need to annouce this
//this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:rtcp-fb:0'); // XEP-0293
//this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:rtp-hdrext:0'); // XEP-0294
this.connection.disco.addFeature('urn:ietf:rfc:5761'); // rtcp-mux
//this.connection.disco.addFeature('urn:ietf:rfc:5888'); // a=group, e.g. bundle
//this.connection.disco.addFeature('urn:ietf:rfc:5576'); // a=ssrc
}
this.connection.addHandler(this.onJingle.bind(this), 'urn:xmpp:jingle:1', 'iq', 'set', null, null);
},
onJingle: function (iq) {
var sid = $(iq).find('jingle').attr('sid');
var action = $(iq).find('jingle').attr('action');
var fromJid = iq.getAttribute('from');
// send ack first
var ack = $iq({type: 'result',
to: fromJid,
id: iq.getAttribute('id')
});
console.log('on jingle ' + action + ' from ' + fromJid, iq);
var sess = this.sessions[sid];
if ('session-initiate' != action) {
if (sess === null) {
ack.type = 'error';
ack.c('error', {type: 'cancel'})
.c('item-not-found', {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'}).up()
.c('unknown-session', {xmlns: 'urn:xmpp:jingle:errors:1'});
this.connection.send(ack);
return true;
}
// compare from to sess.peerjid (bare jid comparison for later compat with message-mode)
// local jid is not checked
if (Strophe.getBareJidFromJid(fromJid) != Strophe.getBareJidFromJid(sess.peerjid)) {
console.warn('jid mismatch for session id', sid, fromJid, sess.peerjid);
ack.type = 'error';
ack.c('error', {type: 'cancel'})
.c('item-not-found', {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'}).up()
.c('unknown-session', {xmlns: 'urn:xmpp:jingle:errors:1'});
this.connection.send(ack);
return true;
}
} else if (sess !== undefined) {
// existing session with same session id
// this might be out-of-order if the sess.peerjid is the same as from
ack.type = 'error';
ack.c('error', {type: 'cancel'})
.c('service-unavailable', {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'}).up();
console.warn('duplicate session id', sid);
this.connection.send(ack);
return true;
}
// FIXME: check for a defined action
this.connection.send(ack);
// see http://xmpp.org/extensions/xep-0166.html#concepts-session
switch (action) {
case 'session-initiate':
sess = new JingleSession($(iq).attr('to'), $(iq).find('jingle').attr('sid'), this.connection);
// configure session
if (this.localAudio) {
sess.localStreams.push(this.localAudio);
}
if (this.localVideo) {
sess.localStreams.push(this.localVideo);
}
sess.media_constraints = this.media_constraints;
sess.pc_constraints = this.pc_constraints;
sess.ice_config = this.ice_config;
sess.initiate(fromJid, false);
// FIXME: setRemoteDescription should only be done when this call is to be accepted
sess.setRemoteDescription($(iq).find('>jingle'), 'offer');
this.sessions[sess.sid] = sess;
this.jid2session[sess.peerjid] = sess;
// the callback should either
// .sendAnswer and .accept
// or .sendTerminate -- not necessarily synchronus
$(document).trigger('callincoming.jingle', [sess.sid]);
break;
case 'session-accept':
sess.setRemoteDescription($(iq).find('>jingle'), 'answer');
sess.accept();
$(document).trigger('callaccepted.jingle', [sess.sid]);
break;
case 'session-terminate':
// If this is not the focus sending the terminate, we have
// nothing more to do here.
if (Object.keys(this.sessions).length < 1
|| !(this.sessions[Object.keys(this.sessions)[0]]
instanceof JingleSession))
{
break;
}
console.log('terminating...', sess.sid);
sess.terminate();
this.terminate(sess.sid);
if ($(iq).find('>jingle>reason').length) {
$(document).trigger('callterminated.jingle', [
sess.sid,
sess.peerjid,
$(iq).find('>jingle>reason>:first')[0].tagName,
$(iq).find('>jingle>reason>text').text()
]);
} else {
$(document).trigger('callterminated.jingle',
[sess.sid, sess.peerjid]);
}
break;
case 'transport-info':
sess.addIceCandidate($(iq).find('>jingle>content'));
break;
case 'session-info':
var affected;
if ($(iq).find('>jingle>ringing[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').length) {
$(document).trigger('ringing.jingle', [sess.sid]);
} else if ($(iq).find('>jingle>mute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').length) {
affected = $(iq).find('>jingle>mute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').attr('name');
$(document).trigger('mute.jingle', [sess.sid, affected]);
} else if ($(iq).find('>jingle>unmute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').length) {
affected = $(iq).find('>jingle>unmute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').attr('name');
$(document).trigger('unmute.jingle', [sess.sid, affected]);
}
break;
case 'addsource': // FIXME: proprietary, un-jingleish
case 'source-add': // FIXME: proprietary
sess.addSource($(iq).find('>jingle>content'), fromJid);
break;
case 'removesource': // FIXME: proprietary, un-jingleish
case 'source-remove': // FIXME: proprietary
sess.removeSource($(iq).find('>jingle>content'), fromJid);
break;
default:
console.warn('jingle action not implemented', action);
break;
}
return true;
},
initiate: function (peerjid, myjid) { // initiate a new jinglesession to peerjid
var sess = new JingleSession(myjid || this.connection.jid,
Math.random().toString(36).substr(2, 12), // random string
this.connection);
// configure session
if (this.localAudio) {
sess.localStreams.push(this.localAudio);
}
if (this.localVideo) {
sess.localStreams.push(this.localVideo);
}
sess.media_constraints = this.media_constraints;
sess.pc_constraints = this.pc_constraints;
sess.ice_config = this.ice_config;
sess.initiate(peerjid, true);
this.sessions[sess.sid] = sess;
this.jid2session[sess.peerjid] = sess;
sess.sendOffer();
return sess;
},
terminate: function (sid, reason, text) { // terminate by sessionid (or all sessions)
if (sid === null || sid === undefined) {
for (sid in this.sessions) {
if (this.sessions[sid].state != 'ended') {
this.sessions[sid].sendTerminate(reason || (!this.sessions[sid].active()) ? 'cancel' : null, text);
this.sessions[sid].terminate();
}
delete this.jid2session[this.sessions[sid].peerjid];
delete this.sessions[sid];
}
} else if (this.sessions.hasOwnProperty(sid)) {
if (this.sessions[sid].state != 'ended') {
this.sessions[sid].sendTerminate(reason || (!this.sessions[sid].active()) ? 'cancel' : null, text);
this.sessions[sid].terminate();
}
delete this.jid2session[this.sessions[sid].peerjid];
delete this.sessions[sid];
}
},
// Used to terminate a session when an unavailable presence is received.
terminateByJid: function (jid) {
if (this.jid2session.hasOwnProperty(jid)) {
var sess = this.jid2session[jid];
if (sess) {
sess.terminate();
console.log('peer went away silently', jid);
delete this.sessions[sess.sid];
delete this.jid2session[jid];
$(document).trigger('callterminated.jingle',
[sess.sid, jid], 'gone');
}
}
},
terminateRemoteByJid: function (jid, reason) {
if (this.jid2session.hasOwnProperty(jid)) {
var sess = this.jid2session[jid];
if (sess) {
sess.sendTerminate(reason || (!sess.active()) ? 'kick' : null);
sess.terminate();
console.log('terminate peer with jid', sess.sid, jid);
delete this.sessions[sess.sid];
delete this.jid2session[jid];
$(document).trigger('callterminated.jingle',
[sess.sid, jid, 'kicked']);
}
}
},
getStunAndTurnCredentials: function () {
// get stun and turn configuration from server via xep-0215
// uses time-limited credentials as described in
// http://tools.ietf.org/html/draft-uberti-behave-turn-rest-00
//
// see https://code.google.com/p/prosody-modules/source/browse/mod_turncredentials/mod_turncredentials.lua
// for a prosody module which implements this
//
// currently, this doesn't work with updateIce and therefore credentials with a long
// validity have to be fetched before creating the peerconnection
// TODO: implement refresh via updateIce as described in
// https://code.google.com/p/webrtc/issues/detail?id=1650
var self = this;
this.connection.sendIQ(
$iq({type: 'get', to: this.connection.domain})
.c('services', {xmlns: 'urn:xmpp:extdisco:1'}).c('service', {host: 'turn.' + this.connection.domain}),
function (res) {
var iceservers = [];
$(res).find('>services>service').each(function (idx, el) {
el = $(el);
var dict = {};
var type = el.attr('type');
switch (type) {
case 'stun':
dict.url = 'stun:' + el.attr('host');
if (el.attr('port')) {
dict.url += ':' + el.attr('port');
}
iceservers.push(dict);
break;
case 'turn':
case 'turns':
dict.url = type + ':';
if (el.attr('username')) { // https://code.google.com/p/webrtc/issues/detail?id=1508
if (navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./) && parseInt(navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./)[2], 10) < 28) {
dict.url += el.attr('username') + '@';
} else {
dict.username = el.attr('username'); // only works in M28
}
}
dict.url += el.attr('host');
if (el.attr('port') && el.attr('port') != '3478') {
dict.url += ':' + el.attr('port');
}
if (el.attr('transport') && el.attr('transport') != 'udp') {
dict.url += '?transport=' + el.attr('transport');
}
if (el.attr('password')) {
dict.credential = el.attr('password');
}
iceservers.push(dict);
break;
}
});
self.ice_config.iceServers = iceservers;
},
function (err) {
console.warn('getting turn credentials failed', err);
console.warn('is mod_turncredentials or similar installed?');
}
);
// implement push?
}
});

View File

@@ -1,724 +0,0 @@
/* jshint -W117 */
// Jingle stuff
JingleSession.prototype = Object.create(SessionBase.prototype);
function JingleSession(me, sid, connection) {
SessionBase.call(this, connection, sid);
this.me = me;
this.initiator = null;
this.responder = null;
this.isInitiator = null;
this.peerjid = null;
this.state = null;
this.localSDP = null;
this.remoteSDP = null;
this.localStreams = [];
this.relayedStreams = [];
this.remoteStreams = [];
this.startTime = null;
this.stopTime = null;
this.media_constraints = null;
this.pc_constraints = null;
this.ice_config = {};
this.drip_container = [];
this.usetrickle = true;
this.usepranswer = false; // early transport warmup -- mind you, this might fail. depends on webrtc issue 1718
this.usedrip = false; // dripping is sending trickle candidates not one-by-one
this.hadstuncandidate = false;
this.hadturncandidate = false;
this.lasticecandidate = false;
this.statsinterval = null;
this.reason = null;
this.wait = true;
}
JingleSession.prototype.initiate = function (peerjid, isInitiator) {
var self = this;
if (this.state !== null) {
console.error('attempt to initiate on session ' + this.sid +
'in state ' + this.state);
return;
}
this.isInitiator = isInitiator;
this.state = 'pending';
this.initiator = isInitiator ? this.me : peerjid;
this.responder = !isInitiator ? this.me : peerjid;
this.peerjid = peerjid;
this.hadstuncandidate = false;
this.hadturncandidate = false;
this.lasticecandidate = false;
this.peerconnection
= new TraceablePeerConnection(
this.connection.jingle.ice_config,
this.connection.jingle.pc_constraints );
this.peerconnection.onicecandidate = function (event) {
self.sendIceCandidate(event.candidate);
};
this.peerconnection.onaddstream = function (event) {
self.remoteStreams.push(event.stream);
$(document).trigger('remotestreamadded.jingle', [event, self.sid]);
};
this.peerconnection.onremovestream = function (event) {
// Remove the stream from remoteStreams
var streamIdx = self.remoteStreams.indexOf(event.stream);
if(streamIdx !== -1){
self.remoteStreams.splice(streamIdx, 1);
}
// FIXME: remotestreamremoved.jingle not defined anywhere(unused)
$(document).trigger('remotestreamremoved.jingle', [event, self.sid]);
};
this.peerconnection.onsignalingstatechange = function (event) {
if (!(self && self.peerconnection)) return;
};
this.peerconnection.oniceconnectionstatechange = function (event) {
if (!(self && self.peerconnection)) return;
switch (self.peerconnection.iceConnectionState) {
case 'connected':
this.startTime = new Date();
break;
case 'disconnected':
this.stopTime = new Date();
break;
}
$(document).trigger('iceconnectionstatechange.jingle', [self.sid, self]);
};
// add any local and relayed stream
this.localStreams.forEach(function(stream) {
self.peerconnection.addStream(stream);
});
this.relayedStreams.forEach(function(stream) {
self.peerconnection.addStream(stream);
});
};
JingleSession.prototype.accept = function () {
var self = this;
this.state = 'active';
var pranswer = this.peerconnection.localDescription;
if (!pranswer || pranswer.type != 'pranswer') {
return;
}
console.log('going from pranswer to answer');
if (this.usetrickle) {
// remove candidates already sent from session-accept
var lines = SDPUtil.find_lines(pranswer.sdp, 'a=candidate:');
for (var i = 0; i < lines.length; i++) {
pranswer.sdp = pranswer.sdp.replace(lines[i] + '\r\n', '');
}
}
while (SDPUtil.find_line(pranswer.sdp, 'a=inactive')) {
// FIXME: change any inactive to sendrecv or whatever they were originally
pranswer.sdp = pranswer.sdp.replace('a=inactive', 'a=sendrecv');
}
pranswer = simulcast.reverseTransformLocalDescription(pranswer);
var prsdp = new SDP(pranswer.sdp);
var accept = $iq({to: this.peerjid,
type: 'set'})
.c('jingle', {xmlns: 'urn:xmpp:jingle:1',
action: 'session-accept',
initiator: this.initiator,
responder: this.responder,
sid: this.sid });
prsdp.toJingle(accept, this.initiator == this.me ? 'initiator' : 'responder');
this.connection.sendIQ(accept,
function () {
var ack = {};
ack.source = 'answer';
$(document).trigger('ack.jingle', [self.sid, ack]);
},
function (stanza) {
var error = ($(stanza).find('error').length) ? {
code: $(stanza).find('error').attr('code'),
reason: $(stanza).find('error :first')[0].tagName,
}:{};
error.source = 'answer';
$(document).trigger('error.jingle', [self.sid, error]);
},
10000);
var sdp = this.peerconnection.localDescription.sdp;
while (SDPUtil.find_line(sdp, 'a=inactive')) {
// FIXME: change any inactive to sendrecv or whatever they were originally
sdp = sdp.replace('a=inactive', 'a=sendrecv');
}
this.peerconnection.setLocalDescription(new RTCSessionDescription({type: 'answer', sdp: sdp}),
function () {
//console.log('setLocalDescription success');
$(document).trigger('setLocalDescription.jingle', [self.sid]);
},
function (e) {
console.error('setLocalDescription failed', e);
}
);
};
/**
* Implements SessionBase.sendSSRCUpdate.
*/
JingleSession.prototype.sendSSRCUpdate = function(sdpMediaSsrcs, fromJid, isadd) {
var self = this;
console.log('tell', self.peerjid, 'about ' + (isadd ? 'new' : 'removed') + ' ssrcs from' + self.me);
if (!(this.peerconnection.signalingState == 'stable' && this.peerconnection.iceConnectionState == 'connected')){
console.log("Too early to send updates");
return;
}
this.sendSSRCUpdateIq(sdpMediaSsrcs, self.sid, self.initiator, self.peerjid, isadd);
};
JingleSession.prototype.terminate = function (reason) {
this.state = 'ended';
this.reason = reason;
this.peerconnection.close();
if (this.statsinterval !== null) {
window.clearInterval(this.statsinterval);
this.statsinterval = null;
}
};
JingleSession.prototype.active = function () {
return this.state == 'active';
};
JingleSession.prototype.sendIceCandidate = function (candidate) {
var self = this;
if (candidate && !this.lasticecandidate) {
var ice = SDPUtil.iceparams(this.localSDP.media[candidate.sdpMLineIndex], this.localSDP.session);
var jcand = SDPUtil.candidateToJingle(candidate.candidate);
if (!(ice && jcand)) {
console.error('failed to get ice && jcand');
return;
}
ice.xmlns = 'urn:xmpp:jingle:transports:ice-udp:1';
if (jcand.type === 'srflx') {
this.hadstuncandidate = true;
} else if (jcand.type === 'relay') {
this.hadturncandidate = true;
}
if (this.usetrickle) {
if (this.usedrip) {
if (this.drip_container.length === 0) {
// start 20ms callout
window.setTimeout(function () {
if (self.drip_container.length === 0) return;
self.sendIceCandidates(self.drip_container);
self.drip_container = [];
}, 20);
}
this.drip_container.push(event.candidate);
return;
} else {
self.sendIceCandidate([event.candidate]);
}
}
} else {
//console.log('sendIceCandidate: last candidate.');
if (!this.usetrickle) {
//console.log('should send full offer now...');
var init = $iq({to: this.peerjid,
type: 'set'})
.c('jingle', {xmlns: 'urn:xmpp:jingle:1',
action: this.peerconnection.localDescription.type == 'offer' ? 'session-initiate' : 'session-accept',
initiator: this.initiator,
sid: this.sid});
this.localSDP = new SDP(this.peerconnection.localDescription.sdp);
this.localSDP.toJingle(init, this.initiator == this.me ? 'initiator' : 'responder');
this.connection.sendIQ(init,
function () {
//console.log('session initiate ack');
var ack = {};
ack.source = 'offer';
$(document).trigger('ack.jingle', [self.sid, ack]);
},
function (stanza) {
self.state = 'error';
self.peerconnection.close();
var error = ($(stanza).find('error').length) ? {
code: $(stanza).find('error').attr('code'),
reason: $(stanza).find('error :first')[0].tagName,
}:{};
error.source = 'offer';
$(document).trigger('error.jingle', [self.sid, error]);
},
10000);
}
this.lasticecandidate = true;
console.log('Have we encountered any srflx candidates? ' + this.hadstuncandidate);
console.log('Have we encountered any relay candidates? ' + this.hadturncandidate);
if (!(this.hadstuncandidate || this.hadturncandidate) && this.peerconnection.signalingState != 'closed') {
$(document).trigger('nostuncandidates.jingle', [this.sid]);
}
}
};
JingleSession.prototype.sendIceCandidates = function (candidates) {
console.log('sendIceCandidates', candidates);
var cand = $iq({to: this.peerjid, type: 'set'})
.c('jingle', {xmlns: 'urn:xmpp:jingle:1',
action: 'transport-info',
initiator: this.initiator,
sid: this.sid});
for (var mid = 0; mid < this.localSDP.media.length; mid++) {
var cands = candidates.filter(function (el) { return el.sdpMLineIndex == mid; });
if (cands.length > 0) {
var ice = SDPUtil.iceparams(this.localSDP.media[mid], this.localSDP.session);
ice.xmlns = 'urn:xmpp:jingle:transports:ice-udp:1';
cand.c('content', {creator: this.initiator == this.me ? 'initiator' : 'responder',
name: cands[0].sdpMid
}).c('transport', ice);
for (var i = 0; i < cands.length; i++) {
cand.c('candidate', SDPUtil.candidateToJingle(cands[i].candidate)).up();
}
// add fingerprint
if (SDPUtil.find_line(this.localSDP.media[mid], 'a=fingerprint:', this.localSDP.session)) {
var tmp = SDPUtil.parse_fingerprint(SDPUtil.find_line(this.localSDP.media[mid], 'a=fingerprint:', this.localSDP.session));
tmp.required = true;
cand.c(
'fingerprint',
{xmlns: 'urn:xmpp:jingle:apps:dtls:0'})
.t(tmp.fingerprint);
delete tmp.fingerprint;
cand.attrs(tmp);
cand.up();
}
cand.up(); // transport
cand.up(); // content
}
}
// might merge last-candidate notification into this, but it is called alot later. See webrtc issue #2340
//console.log('was this the last candidate', this.lasticecandidate);
this.connection.sendIQ(cand,
function () {
var ack = {};
ack.source = 'transportinfo';
$(document).trigger('ack.jingle', [this.sid, ack]);
},
function (stanza) {
var error = ($(stanza).find('error').length) ? {
code: $(stanza).find('error').attr('code'),
reason: $(stanza).find('error :first')[0].tagName,
}:{};
error.source = 'transportinfo';
$(document).trigger('error.jingle', [this.sid, error]);
},
10000);
};
JingleSession.prototype.sendOffer = function () {
//console.log('sendOffer...');
var self = this;
this.peerconnection.createOffer(function (sdp) {
self.createdOffer(sdp);
},
function (e) {
console.error('createOffer failed', e);
},
this.media_constraints
);
};
JingleSession.prototype.createdOffer = function (sdp) {
//console.log('createdOffer', sdp);
var self = this;
this.localSDP = new SDP(sdp.sdp);
//this.localSDP.mangle();
if (this.usetrickle) {
var init = $iq({to: this.peerjid,
type: 'set'})
.c('jingle', {xmlns: 'urn:xmpp:jingle:1',
action: 'session-initiate',
initiator: this.initiator,
sid: this.sid});
this.localSDP.toJingle(init, this.initiator == this.me ? 'initiator' : 'responder');
this.connection.sendIQ(init,
function () {
var ack = {};
ack.source = 'offer';
$(document).trigger('ack.jingle', [self.sid, ack]);
},
function (stanza) {
self.state = 'error';
self.peerconnection.close();
var error = ($(stanza).find('error').length) ? {
code: $(stanza).find('error').attr('code'),
reason: $(stanza).find('error :first')[0].tagName,
}:{};
error.source = 'offer';
$(document).trigger('error.jingle', [self.sid, error]);
},
10000);
}
sdp.sdp = this.localSDP.raw;
this.peerconnection.setLocalDescription(sdp,
function () {
$(document).trigger('setLocalDescription.jingle', [self.sid]);
//console.log('setLocalDescription success');
},
function (e) {
console.error('setLocalDescription failed', e);
}
);
var cands = SDPUtil.find_lines(this.localSDP.raw, 'a=candidate:');
for (var i = 0; i < cands.length; i++) {
var cand = SDPUtil.parse_icecandidate(cands[i]);
if (cand.type == 'srflx') {
this.hadstuncandidate = true;
} else if (cand.type == 'relay') {
this.hadturncandidate = true;
}
}
};
JingleSession.prototype.setRemoteDescription = function (elem, desctype) {
//console.log('setting remote description... ', desctype);
this.remoteSDP = new SDP('');
this.remoteSDP.fromJingle(elem);
if (this.peerconnection.remoteDescription !== null) {
console.log('setRemoteDescription when remote description is not null, should be pranswer', this.peerconnection.remoteDescription);
if (this.peerconnection.remoteDescription.type == 'pranswer') {
var pranswer = new SDP(this.peerconnection.remoteDescription.sdp);
for (var i = 0; i < pranswer.media.length; i++) {
// make sure we have ice ufrag and pwd
if (!SDPUtil.find_line(this.remoteSDP.media[i], 'a=ice-ufrag:', this.remoteSDP.session)) {
if (SDPUtil.find_line(pranswer.media[i], 'a=ice-ufrag:', pranswer.session)) {
this.remoteSDP.media[i] += SDPUtil.find_line(pranswer.media[i], 'a=ice-ufrag:', pranswer.session) + '\r\n';
} else {
console.warn('no ice ufrag?');
}
if (SDPUtil.find_line(pranswer.media[i], 'a=ice-pwd:', pranswer.session)) {
this.remoteSDP.media[i] += SDPUtil.find_line(pranswer.media[i], 'a=ice-pwd:', pranswer.session) + '\r\n';
} else {
console.warn('no ice pwd?');
}
}
// copy over candidates
var lines = SDPUtil.find_lines(pranswer.media[i], 'a=candidate:');
for (var j = 0; j < lines.length; j++) {
this.remoteSDP.media[i] += lines[j] + '\r\n';
}
}
this.remoteSDP.raw = this.remoteSDP.session + this.remoteSDP.media.join('');
}
}
var remotedesc = new RTCSessionDescription({type: desctype, sdp: this.remoteSDP.raw});
this.peerconnection.setRemoteDescription(remotedesc,
function () {
//console.log('setRemoteDescription success');
},
function (e) {
console.error('setRemoteDescription error', e);
$(document).trigger('fatalError.jingle', [self, e]);
}
);
};
JingleSession.prototype.addIceCandidate = function (elem) {
var self = this;
if (this.peerconnection.signalingState == 'closed') {
return;
}
if (!this.peerconnection.remoteDescription && this.peerconnection.signalingState == 'have-local-offer') {
console.log('trickle ice candidate arriving before session accept...');
// create a PRANSWER for setRemoteDescription
if (!this.remoteSDP) {
var cobbled = 'v=0\r\n' +
'o=- ' + '1923518516' + ' 2 IN IP4 0.0.0.0\r\n' +// FIXME
's=-\r\n' +
't=0 0\r\n';
// first, take some things from the local description
for (var i = 0; i < this.localSDP.media.length; i++) {
cobbled += SDPUtil.find_line(this.localSDP.media[i], 'm=') + '\r\n';
cobbled += SDPUtil.find_lines(this.localSDP.media[i], 'a=rtpmap:').join('\r\n') + '\r\n';
if (SDPUtil.find_line(this.localSDP.media[i], 'a=mid:')) {
cobbled += SDPUtil.find_line(this.localSDP.media[i], 'a=mid:') + '\r\n';
}
cobbled += 'a=inactive\r\n';
}
this.remoteSDP = new SDP(cobbled);
}
// then add things like ice and dtls from remote candidate
elem.each(function () {
for (var i = 0; i < self.remoteSDP.media.length; i++) {
if (SDPUtil.find_line(self.remoteSDP.media[i], 'a=mid:' + $(this).attr('name')) ||
self.remoteSDP.media[i].indexOf('m=' + $(this).attr('name')) === 0) {
if (!SDPUtil.find_line(self.remoteSDP.media[i], 'a=ice-ufrag:')) {
var tmp = $(this).find('transport');
self.remoteSDP.media[i] += 'a=ice-ufrag:' + tmp.attr('ufrag') + '\r\n';
self.remoteSDP.media[i] += 'a=ice-pwd:' + tmp.attr('pwd') + '\r\n';
tmp = $(this).find('transport>fingerprint');
if (tmp.length) {
self.remoteSDP.media[i] += 'a=fingerprint:' + tmp.attr('hash') + ' ' + tmp.text() + '\r\n';
} else {
console.log('no dtls fingerprint (webrtc issue #1718?)');
self.remoteSDP.media[i] += 'a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:BAADBAADBAADBAADBAADBAADBAADBAADBAADBAAD\r\n';
}
break;
}
}
}
});
this.remoteSDP.raw = this.remoteSDP.session + this.remoteSDP.media.join('');
// we need a complete SDP with ice-ufrag/ice-pwd in all parts
// this makes the assumption that the PRANSWER is constructed such that the ice-ufrag is in all mediaparts
// but it could be in the session part as well. since the code above constructs this sdp this can't happen however
var iscomplete = this.remoteSDP.media.filter(function (mediapart) {
return SDPUtil.find_line(mediapart, 'a=ice-ufrag:');
}).length == this.remoteSDP.media.length;
if (iscomplete) {
console.log('setting pranswer');
try {
this.peerconnection.setRemoteDescription(new RTCSessionDescription({type: 'pranswer', sdp: this.remoteSDP.raw }),
function() {
},
function(e) {
console.log('setRemoteDescription pranswer failed', e.toString());
});
} catch (e) {
console.error('setting pranswer failed', e);
}
} else {
//console.log('not yet setting pranswer');
}
}
// operate on each content element
elem.each(function () {
// would love to deactivate this, but firefox still requires it
var idx = -1;
var i;
for (i = 0; i < self.remoteSDP.media.length; i++) {
if (SDPUtil.find_line(self.remoteSDP.media[i], 'a=mid:' + $(this).attr('name')) ||
self.remoteSDP.media[i].indexOf('m=' + $(this).attr('name')) === 0) {
idx = i;
break;
}
}
if (idx == -1) { // fall back to localdescription
for (i = 0; i < self.localSDP.media.length; i++) {
if (SDPUtil.find_line(self.localSDP.media[i], 'a=mid:' + $(this).attr('name')) ||
self.localSDP.media[i].indexOf('m=' + $(this).attr('name')) === 0) {
idx = i;
break;
}
}
}
var name = $(this).attr('name');
// TODO: check ice-pwd and ice-ufrag?
$(this).find('transport>candidate').each(function () {
var line, candidate;
line = SDPUtil.candidateFromJingle(this);
candidate = new RTCIceCandidate({sdpMLineIndex: idx,
sdpMid: name,
candidate: line});
try {
self.peerconnection.addIceCandidate(candidate);
} catch (e) {
console.error('addIceCandidate failed', e.toString(), line);
}
});
});
};
JingleSession.prototype.sendAnswer = function (provisional) {
//console.log('createAnswer', provisional);
var self = this;
this.peerconnection.createAnswer(
function (sdp) {
self.createdAnswer(sdp, provisional);
},
function (e) {
console.error('createAnswer failed', e);
},
this.media_constraints
);
};
JingleSession.prototype.createdAnswer = function (sdp, provisional) {
//console.log('createAnswer callback');
var self = this;
this.localSDP = new SDP(sdp.sdp);
//this.localSDP.mangle();
this.usepranswer = provisional === true;
if (this.usetrickle) {
if (!this.usepranswer) {
var accept = $iq({to: this.peerjid,
type: 'set'})
.c('jingle', {xmlns: 'urn:xmpp:jingle:1',
action: 'session-accept',
initiator: this.initiator,
responder: this.responder,
sid: this.sid });
var publicLocalDesc = simulcast.reverseTransformLocalDescription(sdp);
var publicLocalSDP = new SDP(publicLocalDesc.sdp);
publicLocalSDP.toJingle(accept, this.initiator == this.me ? 'initiator' : 'responder');
this.connection.sendIQ(accept,
function () {
var ack = {};
ack.source = 'answer';
$(document).trigger('ack.jingle', [self.sid, ack]);
},
function (stanza) {
var error = ($(stanza).find('error').length) ? {
code: $(stanza).find('error').attr('code'),
reason: $(stanza).find('error :first')[0].tagName,
}:{};
error.source = 'answer';
$(document).trigger('error.jingle', [self.sid, error]);
},
10000);
} else {
sdp.type = 'pranswer';
for (var i = 0; i < this.localSDP.media.length; i++) {
this.localSDP.media[i] = this.localSDP.media[i].replace('a=sendrecv\r\n', 'a=inactive\r\n');
}
this.localSDP.raw = this.localSDP.session + '\r\n' + this.localSDP.media.join('');
}
}
sdp.sdp = this.localSDP.raw;
this.peerconnection.setLocalDescription(sdp,
function () {
$(document).trigger('setLocalDescription.jingle', [self.sid]);
//console.log('setLocalDescription success');
},
function (e) {
console.error('setLocalDescription failed', e);
}
);
var cands = SDPUtil.find_lines(this.localSDP.raw, 'a=candidate:');
for (var j = 0; j < cands.length; j++) {
var cand = SDPUtil.parse_icecandidate(cands[j]);
if (cand.type == 'srflx') {
this.hadstuncandidate = true;
} else if (cand.type == 'relay') {
this.hadturncandidate = true;
}
}
};
JingleSession.prototype.sendTerminate = function (reason, text) {
var self = this,
term = $iq({to: this.peerjid,
type: 'set'})
.c('jingle', {xmlns: 'urn:xmpp:jingle:1',
action: 'session-terminate',
initiator: this.initiator,
sid: this.sid})
.c('reason')
.c(reason || 'success');
if (text) {
term.up().c('text').t(text);
}
this.connection.sendIQ(term,
function () {
self.peerconnection.close();
self.peerconnection = null;
self.terminate();
var ack = {};
ack.source = 'terminate';
$(document).trigger('ack.jingle', [self.sid, ack]);
},
function (stanza) {
var error = ($(stanza).find('error').length) ? {
code: $(stanza).find('error').attr('code'),
reason: $(stanza).find('error :first')[0].tagName,
}:{};
$(document).trigger('ack.jingle', [self.sid, error]);
},
10000);
if (this.statsinterval !== null) {
window.clearInterval(this.statsinterval);
this.statsinterval = null;
}
};
JingleSession.prototype.sendMute = function (muted, content) {
var info = $iq({to: this.peerjid,
type: 'set'})
.c('jingle', {xmlns: 'urn:xmpp:jingle:1',
action: 'session-info',
initiator: this.initiator,
sid: this.sid });
info.c(muted ? 'mute' : 'unmute', {xmlns: 'urn:xmpp:jingle:apps:rtp:info:1'});
info.attrs({'creator': this.me == this.initiator ? 'creator' : 'responder'});
if (content) {
info.attrs({'name': content});
}
this.connection.send(info);
};
JingleSession.prototype.sendRinging = function () {
var info = $iq({to: this.peerjid,
type: 'set'})
.c('jingle', {xmlns: 'urn:xmpp:jingle:1',
action: 'session-info',
initiator: this.initiator,
sid: this.sid });
info.c('ringing', {xmlns: 'urn:xmpp:jingle:apps:rtp:info:1'});
this.connection.send(info);
};
JingleSession.prototype.getStats = function (interval) {
var self = this;
var recv = {audio: 0, video: 0};
var lost = {audio: 0, video: 0};
var lastrecv = {audio: 0, video: 0};
var lastlost = {audio: 0, video: 0};
var loss = {audio: 0, video: 0};
var delta = {audio: 0, video: 0};
this.statsinterval = window.setInterval(function () {
if (self && self.peerconnection && self.peerconnection.getStats) {
self.peerconnection.getStats(function (stats) {
var results = stats.result();
// TODO: there are so much statistics you can get from this..
for (var i = 0; i < results.length; ++i) {
if (results[i].type == 'ssrc') {
var packetsrecv = results[i].stat('packetsReceived');
var packetslost = results[i].stat('packetsLost');
if (packetsrecv && packetslost) {
packetsrecv = parseInt(packetsrecv, 10);
packetslost = parseInt(packetslost, 10);
if (results[i].stat('googFrameRateReceived')) {
lastlost.video = lost.video;
lastrecv.video = recv.video;
recv.video = packetsrecv;
lost.video = packetslost;
} else {
lastlost.audio = lost.audio;
lastrecv.audio = recv.audio;
recv.audio = packetsrecv;
lost.audio = packetslost;
}
}
}
}
delta.audio = recv.audio - lastrecv.audio;
delta.video = recv.video - lastrecv.video;
loss.audio = (delta.audio > 0) ? Math.ceil(100 * (lost.audio - lastlost.audio) / delta.audio) : 0;
loss.video = (delta.video > 0) ? Math.ceil(100 * (lost.video - lastlost.video) / delta.video) : 0;
$(document).trigger('packetloss.jingle', [self.sid, loss]);
});
}
}, interval || 3000);
return this.statsinterval;
};

View File

@@ -1,246 +0,0 @@
/**
* Base class for ColibriFocus and JingleSession.
* @param connection Strophe connection object
* @param sid my session identifier(resource)
* @constructor
*/
function SessionBase(connection, sid){
this.connection = connection;
this.sid = sid;
}
SessionBase.prototype.modifySources = function (successCallback) {
var self = this;
if(this.peerconnection)
this.peerconnection.modifySources(function(){
$(document).trigger('setLocalDescription.jingle', [self.sid]);
if(successCallback) {
successCallback();
}
});
};
SessionBase.prototype.addSource = function (elem, fromJid) {
var self = this;
// FIXME: dirty waiting
if (!this.peerconnection.localDescription)
{
console.warn("addSource - localDescription not ready yet")
setTimeout(function()
{
self.addSource(elem, fromJid);
},
200
);
return;
}
this.peerconnection.addSource(elem);
this.modifySources();
};
SessionBase.prototype.removeSource = function (elem, fromJid) {
var self = this;
// FIXME: dirty waiting
if (!this.peerconnection.localDescription)
{
console.warn("removeSource - localDescription not ready yet")
setTimeout(function()
{
self.removeSource(elem, fromJid);
},
200
);
return;
}
this.peerconnection.removeSource(elem);
this.modifySources();
};
/**
* Switches video streams.
* @param new_stream new stream that will be used as video of this session.
* @param oldStream old video stream of this session.
* @param success_callback callback executed after successful stream switch.
*/
SessionBase.prototype.switchStreams = function (new_stream, oldStream, success_callback) {
var self = this;
// Stop the stream to trigger onended event for old stream
oldStream.stop();
// Remember SDP to figure out added/removed SSRCs
var oldSdp = null;
if(self.peerconnection) {
if(self.peerconnection.localDescription) {
oldSdp = new SDP(self.peerconnection.localDescription.sdp);
}
self.peerconnection.removeStream(oldStream);
self.peerconnection.addStream(new_stream);
}
self.connection.jingle.localVideo = new_stream;
self.connection.jingle.localStreams = [];
self.connection.jingle.localStreams.push(self.connection.jingle.localAudio);
self.connection.jingle.localStreams.push(self.connection.jingle.localVideo);
// Conference is not active
if(!oldSdp || !self.peerconnection) {
success_callback();
return;
}
self.peerconnection.switchstreams = true;
self.modifySources(function() {
console.log('modify sources done');
var newSdp = new SDP(self.peerconnection.localDescription.sdp);
console.log("SDPs", oldSdp, newSdp);
self.notifyMySSRCUpdate(oldSdp, newSdp);
success_callback();
});
};
/**
* Figures out added/removed ssrcs and send update IQs.
* @param old_sdp SDP object for old description.
* @param new_sdp SDP object for new description.
*/
SessionBase.prototype.notifyMySSRCUpdate = function (old_sdp, new_sdp) {
var old_media = old_sdp.getMediaSsrcMap();
var new_media = new_sdp.getMediaSsrcMap();
//console.log("old/new medias: ", old_media, new_media);
var toAdd = old_sdp.getNewMedia(new_sdp);
var toRemove = new_sdp.getNewMedia(old_sdp);
//console.log("to add", toAdd);
//console.log("to remove", toRemove);
if(Object.keys(toRemove).length > 0){
this.sendSSRCUpdate(toRemove, null, false);
}
if(Object.keys(toAdd).length > 0){
this.sendSSRCUpdate(toAdd, null, true);
}
};
/**
* Empty method that does nothing by default. It should send SSRC update IQs to session participants.
* @param sdpMediaSsrcs array of
* @param fromJid
* @param isAdd
*/
SessionBase.prototype.sendSSRCUpdate = function(sdpMediaSsrcs, fromJid, isAdd) {
//FIXME: put default implementation here(maybe from JingleSession?)
}
/**
* Sends SSRC update IQ.
* @param sdpMediaSsrcs SSRCs map obtained from SDP.getNewMedia. Cntains SSRCs to add/remove.
* @param sid session identifier that will be put into the IQ.
* @param initiator initiator identifier.
* @param toJid destination Jid
* @param isAdd indicates if this is remove or add operation.
*/
SessionBase.prototype.sendSSRCUpdateIq = function(sdpMediaSsrcs, sid, initiator, toJid, isAdd) {
var self = this;
var modify = $iq({to: toJid, type: 'set'})
.c('jingle', {
xmlns: 'urn:xmpp:jingle:1',
action: isAdd ? 'source-add' : 'source-remove',
initiator: initiator,
sid: sid
}
);
// FIXME: only announce video ssrcs since we mix audio and dont need
// the audio ssrcs therefore
var modified = false;
Object.keys(sdpMediaSsrcs).forEach(function(channelNum){
modified = true;
var channel = sdpMediaSsrcs[channelNum];
modify.c('content', {name: channel.mediaType});
modify.c('description', {xmlns:'urn:xmpp:jingle:apps:rtp:1', media: channel.mediaType});
// FIXME: not completly sure this operates on blocks and / or handles different ssrcs correctly
// generate sources from lines
Object.keys(channel.ssrcs).forEach(function(ssrcNum) {
var mediaSsrc = channel.ssrcs[ssrcNum];
modify.c('source', { xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' });
modify.attrs({ssrc: mediaSsrc.ssrc});
// iterate over ssrc lines
mediaSsrc.lines.forEach(function (line) {
var idx = line.indexOf(' ');
var kv = line.substr(idx + 1);
modify.c('parameter');
if (kv.indexOf(':') == -1) {
modify.attrs({ name: kv });
} else {
modify.attrs({ name: kv.split(':', 2)[0] });
modify.attrs({ value: kv.split(':', 2)[1] });
}
modify.up(); // end of parameter
});
modify.up(); // end of source
});
// generate source groups from lines
channel.ssrcGroups.forEach(function(ssrcGroup) {
if (ssrcGroup.ssrcs.length != 0) {
modify.c('ssrc-group', {
semantics: ssrcGroup.semantics,
xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0'
});
ssrcGroup.ssrcs.forEach(function (ssrc) {
modify.c('source', { ssrc: ssrc })
.up(); // end of source
});
modify.up(); // end of ssrc-group
}
});
modify.up(); // end of description
modify.up(); // end of content
});
if (modified) {
self.connection.sendIQ(modify,
function (res) {
console.info('got modify result', res);
},
function (err) {
console.error('got modify error', err);
}
);
} else {
console.log('modification not necessary');
}
};
// SDP-based mute by going recvonly/sendrecv
// FIXME: should probably black out the screen as well
SessionBase.prototype.toggleVideoMute = function (callback) {
var ismuted = false;
var localVideo = connection.jingle.localVideo;
for (var idx = 0; idx < localVideo.getVideoTracks().length; idx++) {
ismuted = !localVideo.getVideoTracks()[idx].enabled;
}
for (var idx = 0; idx < localVideo.getVideoTracks().length; idx++) {
localVideo.getVideoTracks()[idx].enabled = !localVideo.getVideoTracks()[idx].enabled;
}
if(this.peerconnection)
this.peerconnection.hardMuteVideo(!ismuted);
this.modifySources(callback(!ismuted));
};

File diff suppressed because one or more lines are too long

View File

@@ -1,41 +0,0 @@
/**
* Strophe logger implementation. Logs from level WARN and above.
*/
Strophe.log = function (level, msg) {
switch(level) {
case Strophe.LogLevel.WARN:
console.warn("Strophe: "+msg);
break;
case Strophe.LogLevel.ERROR:
case Strophe.LogLevel.FATAL:
console.error("Strophe: "+msg);
break;
}
};
Strophe.getStatusString = function(status)
{
switch (status)
{
case Strophe.Status.ERROR:
return "ERROR";
case Strophe.Status.CONNECTING:
return "CONNECTING";
case Strophe.Status.CONNFAIL:
return "CONNFAIL";
case Strophe.Status.AUTHENTICATING:
return "AUTHENTICATING";
case Strophe.Status.AUTHFAIL:
return "AUTHFAIL";
case Strophe.Status.CONNECTED:
return "CONNECTED";
case Strophe.Status.DISCONNECTED:
return "DISCONNECTED";
case Strophe.Status.DISCONNECTING:
return "DISCONNECTING";
case Strophe.Status.ATTACHED:
return "ATTACHED";
default:
return "unknown";
}
};

342
libs/toastr.js Normal file
View File

@@ -0,0 +1,342 @@
/*
* Toastr
* Copyright 2012-2014 John Papa and Hans Fjällemark.
* All Rights Reserved.
* Use, reproduction, distribution, and modification of this code is subject to the terms and
* conditions of the MIT license, available at http://www.opensource.org/licenses/mit-license.php
*
* Author: John Papa and Hans Fjällemark
* ARIA Support: Greta Krafsig
* Project: https://github.com/CodeSeven/toastr
*/
; (function (define) {
define(['jquery'], function ($) {
return (function () {
var $container;
var listener;
var toastId = 0;
var toastType = {
error: 'error',
info: 'info',
success: 'success',
warning: 'warning'
};
var toastr = {
clear: clear,
remove: remove,
error: error,
getContainer: getContainer,
info: info,
options: {},
subscribe: subscribe,
success: success,
version: '2.0.3',
warning: warning
};
return toastr;
//#region Accessible Methods
function error(message, title, optionsOverride) {
return notify({
type: toastType.error,
iconClass: getOptions().iconClasses.error,
message: message,
optionsOverride: optionsOverride,
title: title
});
}
function getContainer(options, create) {
if (!options) { options = getOptions(); }
$container = $('#' + options.containerId);
if ($container.length) {
return $container;
}
if(create) {
$container = createContainer(options);
}
return $container;
}
function info(message, title, optionsOverride) {
return notify({
type: toastType.info,
iconClass: getOptions().iconClasses.info,
message: message,
optionsOverride: optionsOverride,
title: title
});
}
function subscribe(callback) {
listener = callback;
}
function success(message, title, optionsOverride) {
return notify({
type: toastType.success,
iconClass: getOptions().iconClasses.success,
message: message,
optionsOverride: optionsOverride,
title: title
});
}
function warning(message, title, optionsOverride) {
return notify({
type: toastType.warning,
iconClass: getOptions().iconClasses.warning,
message: message,
optionsOverride: optionsOverride,
title: title
});
}
function clear($toastElement) {
var options = getOptions();
if (!$container) { getContainer(options); }
if (!clearToast($toastElement, options)) {
clearContainer(options);
}
}
function remove($toastElement) {
var options = getOptions();
if (!$container) { getContainer(options); }
if ($toastElement && $(':focus', $toastElement).length === 0) {
removeToast($toastElement);
return;
}
if ($container.children().length) {
$container.remove();
}
}
//#endregion
//#region Internal Methods
function clearContainer(options){
var toastsToClear = $container.children();
for (var i = toastsToClear.length - 1; i >= 0; i--) {
clearToast($(toastsToClear[i]), options);
};
}
function clearToast($toastElement, options){
if ($toastElement && $(':focus', $toastElement).length === 0) {
$toastElement[options.hideMethod]({
duration: options.hideDuration,
easing: options.hideEasing,
complete: function () { removeToast($toastElement); }
});
return true;
}
return false;
}
function createContainer(options) {
$container = $('<div/>')
.attr('id', options.containerId)
.addClass(options.positionClass)
.attr('aria-live', 'polite')
.attr('role', 'alert');
$container.appendTo($(options.target));
return $container;
}
function getDefaults() {
return {
tapToDismiss: true,
toastClass: 'toast',
containerId: 'toast-container',
debug: false,
showMethod: 'fadeIn', //fadeIn, slideDown, and show are built into jQuery
showDuration: 300,
showEasing: 'swing', //swing and linear are built into jQuery
onShown: undefined,
hideMethod: 'fadeOut',
hideDuration: 1000,
hideEasing: 'swing',
onHidden: undefined,
extendedTimeOut: 1000,
iconClasses: {
error: 'toast-error',
info: 'toast-info',
success: 'toast-success',
warning: 'toast-warning'
},
iconClass: 'toast-info',
positionClass: 'toast-top-right',
timeOut: 5000, // Set timeOut and extendedTimeout to 0 to make it sticky
titleClass: 'toast-title',
messageClass: 'toast-message',
target: 'body',
closeHtml: '<button>&times;</button>',
newestOnTop: true
};
}
function publish(args) {
if (!listener) { return; }
listener(args);
}
function notify(map) {
var options = getOptions(),
iconClass = map.iconClass || options.iconClass;
if (typeof (map.optionsOverride) !== 'undefined') {
options = $.extend(options, map.optionsOverride);
iconClass = map.optionsOverride.iconClass || iconClass;
}
toastId++;
$container = getContainer(options, true);
var intervalId = null,
$toastElement = $('<div/>'),
$titleElement = $('<div/>'),
$messageElement = $('<div/>'),
$closeElement = $(options.closeHtml),
response = {
toastId: toastId,
state: 'visible',
startTime: new Date(),
options: options,
map: map
};
if (map.iconClass) {
$toastElement.addClass(options.toastClass).addClass(iconClass);
}
if (map.title) {
$titleElement.append(map.title).addClass(options.titleClass);
$toastElement.append($titleElement);
}
if (map.message) {
$messageElement.append(map.message).addClass(options.messageClass);
$toastElement.append($messageElement);
}
if (options.closeButton) {
$closeElement.addClass('toast-close-button').attr("role", "button");
$toastElement.prepend($closeElement);
}
if (options.reposition) {
options.reposition();
}
$toastElement.hide();
if (options.newestOnTop) {
$container.prepend($toastElement);
} else {
$container.append($toastElement);
}
$toastElement[options.showMethod](
{ duration: options.showDuration, easing: options.showEasing, complete: options.onShown }
);
if (options.timeOut > 0) {
intervalId = setTimeout(hideToast, options.timeOut);
}
$toastElement.hover(stickAround, delayedHideToast);
if (!options.onclick && options.tapToDismiss) {
$toastElement.click(hideToast);
}
if (options.closeButton && $closeElement) {
$closeElement.click(function (event) {
if( event.stopPropagation ) {
event.stopPropagation();
} else if( event.cancelBubble !== undefined && event.cancelBubble !== true ) {
event.cancelBubble = true;
}
hideToast(true);
});
}
if (options.onclick) {
$toastElement.click(function () {
options.onclick();
hideToast();
});
}
publish(response);
if (options.debug && console) {
console.log(response);
}
return $toastElement;
function hideToast(override) {
if ($(':focus', $toastElement).length && !override) {
return;
}
return $toastElement[options.hideMethod]({
duration: options.hideDuration,
easing: options.hideEasing,
complete: function () {
removeToast($toastElement);
if (options.onHidden && response.state !== 'hidden') {
options.onHidden();
}
response.state = 'hidden';
response.endTime = new Date();
publish(response);
}
});
}
function delayedHideToast() {
if (options.timeOut > 0 || options.extendedTimeOut > 0) {
intervalId = setTimeout(hideToast, options.extendedTimeOut);
}
}
function stickAround() {
clearTimeout(intervalId);
$toastElement.stop(true, true)[options.showMethod](
{ duration: options.showDuration, easing: options.showEasing }
);
}
}
function getOptions() {
return $.extend({}, getDefaults(), toastr.options);
}
function removeToast($toastElement) {
if (!$container) { $container = getContainer(); }
if ($toastElement.is(':visible')) {
return;
}
$toastElement.remove();
$toastElement = null;
if ($container.children().length === 0) {
$container.remove();
}
}
//#endregion
})();
});
}(typeof define === 'function' && define.amd ? define : function (deps, factory) {
if (typeof module !== 'undefined' && module.exports) { //Node
module.exports = factory(require('jquery'));
} else {
window['toastr'] = factory(window['jQuery']);
}
}));

View File

@@ -1,131 +0,0 @@
/**
* Provides statistics for the local stream.
*/
var LocalStatsCollector = (function() {
/**
* Size of the webaudio analizer buffer.
* @type {number}
*/
var WEBAUDIO_ANALIZER_FFT_SIZE = 2048;
/**
* Value of the webaudio analizer smoothing time parameter.
* @type {number}
*/
var WEBAUDIO_ANALIZER_SMOOTING_TIME = 0.8;
/**
* <tt>LocalStatsCollector</tt> calculates statistics for the local stream.
*
* @param stream the local stream
* @param interval stats refresh interval given in ms.
* @param {function(LocalStatsCollector)} updateCallback the callback called on stats
* update.
* @constructor
*/
function LocalStatsCollectorProto(stream, interval, updateCallback) {
window.AudioContext = window.AudioContext || window.webkitAudioContext;
this.stream = stream;
this.intervalId = null;
this.intervalMilis = interval;
this.audioLevelsUpdateCallback = updateCallback;
this.audioLevel = 0;
}
/**
* Starts the collecting the statistics.
*/
LocalStatsCollectorProto.prototype.start = function () {
if (!window.AudioContext)
return;
var context = new AudioContext();
var analyser = context.createAnalyser();
analyser.smoothingTimeConstant = WEBAUDIO_ANALIZER_SMOOTING_TIME;
analyser.fftSize = WEBAUDIO_ANALIZER_FFT_SIZE;
var source = context.createMediaStreamSource(this.stream);
source.connect(analyser);
var self = this;
this.intervalId = setInterval(
function () {
var array = new Uint8Array(analyser.frequencyBinCount);
analyser.getByteTimeDomainData(array);
var audioLevel = TimeDomainDataToAudioLevel(array);
if(audioLevel != self.audioLevel) {
self.audioLevel = animateLevel(audioLevel, self.audioLevel);
self.audioLevelsUpdateCallback(LocalStatsCollectorProto.LOCAL_JID, self.audioLevel);
}
},
this.intervalMilis
);
};
/**
* Stops collecting the statistics.
*/
LocalStatsCollectorProto.prototype.stop = function () {
if (this.intervalId) {
clearInterval(this.intervalId);
this.intervalId = null;
}
};
/**
* Converts time domain data array to audio level.
* @param array the time domain data array.
* @returns {number} the audio level
*/
var TimeDomainDataToAudioLevel = function (samples) {
var maxVolume = 0;
var length = samples.length;
for (var i = 0; i < length; i++) {
if (maxVolume < samples[i])
maxVolume = samples[i];
}
return parseFloat(((maxVolume - 127) / 128).toFixed(3));
};
/**
* Animates audio level change
* @param newLevel the new audio level
* @param lastLevel the last audio level
* @returns {Number} the audio level to be set
*/
function animateLevel(newLevel, lastLevel)
{
var value = 0;
var diff = lastLevel - newLevel;
if(diff > 0.2)
{
value = lastLevel - 0.2;
}
else if(diff < -0.4)
{
value = lastLevel + 0.4;
}
else
{
value = newLevel;
}
return parseFloat(value.toFixed(3));
}
/**
* Indicates that this audio level is for local jid.
* @type {string}
*/
LocalStatsCollectorProto.LOCAL_JID = 'local';
return LocalStatsCollectorProto;
})();

View File

@@ -1,30 +0,0 @@
/**
* Provides a wrapper class for the MediaStream.
*
* TODO : Add here the src from the video element and other related properties
* and get rid of some of the mappings that we use throughout the UI.
*/
var MediaStream = (function() {
/**
* Creates a MediaStream object for the given data, session id and ssrc.
*
* @param data the data object from which we obtain the stream,
* the peerjid, etc.
* @param sid the session id
* @param ssrc the ssrc corresponding to this MediaStream
*
* @constructor
*/
function MediaStreamProto(data, sid, ssrc) {
this.VIDEO_TYPE = "Video";
this.AUDIO_TYPE = "Audio";
this.stream = data.stream;
this.peerjid = data.peerjid;
this.ssrc = ssrc;
this.session = connection.jingle.sessions[sid];
this.type = (this.stream.getVideoTracks().length > 0)
? this.VIDEO_TYPE : this.AUDIO_TYPE;
}
return MediaStreamProto;
})();

View File

@@ -1,112 +0,0 @@
var messageHandler = (function(my) {
/**
* Shows a message to the user.
*
* @param titleString the title of the message
* @param messageString the text of the message
*/
my.openMessageDialog = function(titleString, messageString) {
$.prompt(messageString,
{
title: titleString,
persistent: false
}
);
};
/**
* 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 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 closeFunction function to be called after the prompt is closed
*/
my.openTwoButtonDialog = function(titleString, msgString, persistent, leftButton,
submitFunction, loadedFunction, closeFunction) {
var buttons = {};
buttons[leftButton] = true;
buttons.Cancel = false;
$.prompt(msgString, {
title: titleString,
persistent: false,
buttons: buttons,
defaultButton: 1,
loaded: loadedFunction,
submit: submitFunction,
close: closeFunction
});
};
/**
* 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 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
*/
my.openDialog = function(titleString, msgString, persistent, buttons, submitFunction, loadedFunction) {
$.prompt(msgString, {
title: titleString,
persistent: false,
buttons: buttons,
defaultButton: 1,
loaded: loadedFunction,
submit: submitFunction
});
};
/**
* 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
*/
my.openDialogWithStates = function(statesObject, loadedFunction, stateChangedFunction) {
var myPrompt = $.prompt(statesObject);
myPrompt.on('impromptu:loaded', loadedFunction);
myPrompt.on('impromptu:statechanged', stateChangedFunction);
};
/**
* 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 error the error that is being reported
*/
my.openReportDialog = function(titleString, msgString, error) {
my.openMessageDialog(titleString, msgString);
console.log(error);
//FIXME send the error to the server
};
/**
* Shows an error dialog to the user.
* @param title the title of the message
* @param message the text of the messafe
*/
my.showError = function(title, message) {
if(!(title || message)) {
title = title || "Oops!";
message = message || "There was some kind of error";
}
messageHandler.openMessageDialog(title, message);
};
return my;
}(messageHandler || {}));

View File

@@ -1,53 +0,0 @@
/**
* Moderate connection plugin.
*/
Strophe.addConnectionPlugin('moderate', {
connection: null,
roomjid: null,
myroomjid: null,
members: {},
list_members: [], // so we can elect a new focus
presMap: {},
preziMap: {},
joined: false,
isOwner: false,
init: function (conn) {
this.connection = conn;
this.connection.addHandler( this.onMute.bind(this),
'http://jitsi.org/jitmeet/audio',
'iq',
'set',
null,
null);
},
setMute: function(jid, mute) {
var iq = $iq({to: jid, type: 'set'})
.c('mute', {xmlns: 'http://jitsi.org/jitmeet/audio'})
.t(mute.toString())
.up();
this.connection.sendIQ(
iq,
function (result) {
console.log('set mute', result);
},
function (error) {
console.log('set mute error', error);
messageHandler.openReportDialog(null, 'Failed to mute ' +
$("#participant_" + jid).find(".displayname").text() ||
"participant" + '.', error);
});
},
onMute: function(iq) {
var mute = $(iq).find('mute');
if (mute.length) {
toggleAudio();
}
return true;
},
eject: function(jid) {
connection.jingle.terminateRemoteByJid(jid, 'kick');
connection.emuc.kick(jid);
}
});

231
modules/API/API.js Normal file
View File

@@ -0,0 +1,231 @@
/**
* Implements API class that communicates with external api class
* and provides interface to access Jitsi Meet features by external
* applications that embed Jitsi Meet
*/
var XMPPEvents = require("../../service/xmpp/XMPPEvents");
/**
* List of the available commands.
* @type {{
* displayName: inputDisplayNameHandler,
* muteAudio: toggleAudio,
* muteVideo: toggleVideo,
* filmStrip: toggleFilmStrip
* }}
*/
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
};
/**
* Maps the supported events and their status
* (true it the event is enabled and false if it is disabled)
* @type {{
* incomingMessage: boolean,
* outgoingMessage: boolean,
* displayNameChange: boolean,
* participantJoined: boolean,
* participantLeft: boolean
* }}
*/
var events =
{
incomingMessage: false,
outgoingMessage:false,
displayNameChange: false,
participantJoined: false,
participantLeft: false
};
var displayName = {};
/**
* Processes commands from external applicaiton.
* @param message the object with the command
*/
function processCommand(message)
{
if(message.action != "execute")
{
console.error("Unknown action of the message");
return;
}
for(var key in message)
{
if(commands[key])
commands[key].apply(null, message[key]);
}
}
/**
* Processes events objects from external applications
* @param event the event
*/
function processEvent(event) {
if(!event.action)
{
console.error("Event with no action is received.");
return;
}
var i = 0;
switch(event.action)
{
case "add":
for(; i < event.events.length; i++)
{
events[event.events[i]] = true;
}
break;
case "remove":
for(; i < event.events.length; i++)
{
events[event.events[i]] = false;
}
break;
default:
console.error("Unknown action for event.");
}
}
/**
* Sends message to the external application.
* @param object
*/
function sendMessage(object) {
window.parent.postMessage(JSON.stringify(object), "*");
}
/**
* Processes a message event from the external application
* @param event the message event
*/
function processMessage(event)
{
var message;
try {
message = JSON.parse(event.data);
} catch (e) {}
if(!message.type)
return;
switch (message.type)
{
case "command":
processCommand(message);
break;
case "event":
processEvent(message);
break;
default:
console.error("Unknown type of the message");
return;
}
}
function setupListeners() {
APP.xmpp.addListener(XMPPEvents.MUC_ENTER, function (from) {
API.triggerEvent("participantJoined", {jid: from});
});
APP.xmpp.addListener(XMPPEvents.MESSAGE_RECEIVED, function (from, nick, txt, myjid) {
if (from != myjid)
API.triggerEvent("incomingMessage",
{"from": from, "nick": nick, "message": txt});
});
APP.xmpp.addListener(XMPPEvents.MUC_LEFT, function (jid) {
API.triggerEvent("participantLeft", {jid: jid});
});
APP.xmpp.addListener(XMPPEvents.DISPLAY_NAME_CHANGED, function (jid, newDisplayName) {
name = displayName[jid];
if(!name || name != newDisplayName) {
API.triggerEvent("displayNameChange", {jid: jid, displayname: newDisplayName});
displayName[jid] = newDisplayName;
}
});
APP.xmpp.addListener(XMPPEvents.SENDING_CHAT_MESSAGE, function (body) {
APP.API.triggerEvent("outgoingMessage", {"message": body});
});
}
var API = {
/**
* Check whether the API should be enabled or not.
* @returns {boolean}
*/
isEnabled: function () {
var hash = location.hash;
if(hash && hash.indexOf("external") > -1 && window.postMessage)
return true;
return false;
},
/**
* Initializes the APIConnector. Setups message event listeners that will
* receive information from external applications that embed Jitsi Meet.
* It also sends a message to the external application that APIConnector
* is initialized.
*/
init: function () {
if (window.addEventListener)
{
window.addEventListener('message',
processMessage, false);
}
else
{
window.attachEvent('onmessage', processMessage);
}
sendMessage({type: "system", loaded: true});
setupListeners();
},
/**
* Checks whether the event is enabled ot not.
* @param name the name of the event.
* @returns {*}
*/
isEventEnabled: function (name) {
return events[name];
},
/**
* Sends event object to the external application that has been subscribed
* for that event.
* @param name the name event
* @param object data associated with the event
*/
triggerEvent: function (name, object) {
if(this.isEnabled() && this.isEventEnabled(name))
sendMessage({
type: "event", action: "result", event: name, result: object});
},
/**
* Removes the listeners.
*/
dispose: function () {
if(window.removeEventListener)
{
window.removeEventListener("message",
processMessage, false);
}
else
{
window.detachEvent('onmessage', processMessage);
}
}
};
module.exports = API;

234
modules/RTC/DataChannels.js Normal file
View File

@@ -0,0 +1,234 @@
/* global Strophe, updateLargeVideo, focusedVideoSrc*/
// cache datachannels to avoid garbage collection
// https://code.google.com/p/chromium/issues/detail?id=405545
var RTCEvents = require("../../service/RTC/RTCEvents");
var _dataChannels = [];
var eventEmitter = null;
var DataChannels =
{
/**
* Callback triggered by PeerConnection when new data channel is opened
* on the bridge.
* @param event the event info object.
*/
onDataChannel: function (event)
{
var dataChannel = event.channel;
dataChannel.onopen = function () {
console.info("Data channel opened by the Videobridge!", dataChannel);
// Code sample for sending string and/or binary data
// Sends String message to the bridge
//dataChannel.send("Hello bridge!");
// 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);
};
dataChannel.onerror = function (error) {
console.error("Data Channel Error:", error, dataChannel);
};
dataChannel.onmessage = function (event) {
var data = event.data;
// JSON
var obj;
try {
obj = JSON.parse(data);
}
catch (e) {
console.error(
"Failed to parse data channel message as JSON: ",
data,
dataChannel);
}
if (('undefined' !== typeof(obj)) && (null !== obj)) {
var colibriClass = obj.colibriClass;
if ("DominantSpeakerEndpointChangeEvent" === colibriClass) {
// Endpoint ID from the Videobridge.
var dominantSpeakerEndpoint = obj.dominantSpeakerEndpoint;
console.info(
"Data channel new dominant speaker event: ",
dominantSpeakerEndpoint);
eventEmitter.emit(RTCEvents.DOMINANTSPEAKER_CHANGED, dominantSpeakerEndpoint);
}
else if ("InLastNChangeEvent" === colibriClass)
{
var oldValue = obj.oldValue;
var newValue = obj.newValue;
// Make sure that oldValue and newValue are of type boolean.
var type;
if ((type = typeof oldValue) !== 'boolean') {
if (type === 'string') {
oldValue = (oldValue == "true");
} else {
oldValue = new Boolean(oldValue).valueOf();
}
}
if ((type = typeof newValue) !== 'boolean') {
if (type === 'string') {
newValue = (newValue == "true");
} else {
newValue = new Boolean(newValue).valueOf();
}
}
eventEmitter.emit(RTCEvents.LASTN_CHANGED, oldValue, newValue);
}
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: ",
lastNEndpoints, endpointsEnteringLastN, obj);
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
{
console.debug("Data channel JSON-formatted message: ", obj);
}
}
};
dataChannel.onclose = function ()
{
console.info("The Data Channel closed", dataChannel);
var idx = _dataChannels.indexOf(dataChannel);
if (idx > -1)
_dataChannels = _dataChannels.splice(idx, 1);
};
_dataChannels.push(dataChannel);
},
/**
* Binds "ondatachannel" event listener to given PeerConnection instance.
* @param peerConnection WebRTC peer connection instance.
*/
init: function (peerConnection, emitter) {
if(!config.openSctp)
return;
peerConnection.ondatachannel = this.onDataChannel;
eventEmitter = emitter;
// Sample code for opening new data channel from Jitsi Meet to the bridge.
// Although it's not a requirement to open separate channels from both bridge
// and peer as single channel can be used for sending and receiving data.
// So either channel opened by the bridge or the one opened here is enough
// for communication with the bridge.
/*var dataChannelOptions =
{
reliable: true
};
var dataChannel
= peerConnection.createDataChannel("myChannel", dataChannelOptions);
// Can be used only when is in open state
dataChannel.onopen = function ()
{
dataChannel.send("My channel !!!");
};
dataChannel.onmessage = function (event)
{
var msgData = event.data;
console.info("Got My Data Channel Message:", msgData, dataChannel);
};*/
},
handleSelectedEndpointEvent: onSelectedEndpointChanged,
handlePinnedEndpointEvent: onPinnedEndpointChanged
};
function onSelectedEndpointChanged(userResource)
{
console.log('selected endpoint changed: ', userResource);
if (_dataChannels && _dataChannels.length != 0)
{
_dataChannels.some(function (dataChannel) {
if (dataChannel.readyState == 'open')
{
console.log('sending selected endpoint changed '
+ 'notification to the bridge: ', userResource);
dataChannel.send(JSON.stringify({
'colibriClass': 'SelectedEndpointChangedEvent',
'selectedEndpoint':
(!userResource || userResource === null)?
null : userResource
}));
return true;
}
});
}
}
function onPinnedEndpointChanged(userResource)
{
console.log('pinned endpoint changed: ', userResource);
if (_dataChannels && _dataChannels.length != 0)
{
_dataChannels.some(function (dataChannel) {
if (dataChannel.readyState == 'open')
{
dataChannel.send(JSON.stringify({
'colibriClass': 'PinnedEndpointChangedEvent',
'pinnedEndpoint':
(!userResource || userResource == null)?
null : userResource
}));
return true;
}
});
}
}
module.exports = DataChannels;

View File

@@ -0,0 +1,86 @@
var StreamEventTypes = require("../../service/RTC/StreamEventTypes.js");
function LocalStream(stream, type, eventEmitter, videoType)
{
this.stream = stream;
this.eventEmitter = eventEmitter;
this.type = type;
this.videoType = videoType;
var self = this;
if(type == "audio")
{
this.getTracks = function () {
return self.stream.getAudioTracks();
};
}
else
{
this.getTracks = function () {
return self.stream.getVideoTracks();
};
}
this.stream.onended = function()
{
self.streamEnded();
};
}
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);
};
LocalStream.prototype.mute = function()
{
var ismuted = false;
var tracks = this.getTracks();
for (var idx = 0; idx < tracks.length; idx++) {
ismuted = !tracks[idx].enabled;
tracks[idx].enabled = ismuted;
}
return ismuted;
};
LocalStream.prototype.setMute = function(mute)
{
var tracks = this.getTracks();
for (var idx = 0; idx < tracks.length; idx++) {
tracks[idx].enabled = mute;
}
};
LocalStream.prototype.isMuted = function () {
var tracks = [];
if(this.type == "audio")
{
tracks = this.stream.getAudioTracks();
}
else
{
tracks = this.stream.getVideoTracks();
}
for (var idx = 0; idx < tracks.length; idx++) {
if(tracks[idx].enabled)
return false;
}
return true;
}
LocalStream.prototype.getId = function () {
return this.stream.getTracks()[0].id;
}
module.exports = LocalStream;

View File

@@ -0,0 +1,51 @@
////These lines should be uncommented when require works in app.js
var RTCBrowserType = require("../../service/RTC/RTCBrowserType.js");
var MediaStreamType = require("../../service/RTC/MediaStreamTypes");
/**
* Creates a MediaStream object for the given data, session id and ssrc.
* It is a wrapper class for the MediaStream.
*
* @param data the data object from which we obtain the stream,
* the peerjid, etc.
* @param sid the session id
* @param ssrc the ssrc corresponding to this MediaStream
*
* @constructor
*/
function MediaStream(data, sid, ssrc, browser) {
// XXX(gp) to minimize headaches in the future, we should build our
// abstractions around tracks and not streams. ORTC is track based API.
// Mozilla expects m-lines to represent media tracks.
//
// Practically, what I'm saying is that we should have a MediaTrack class
// and not a MediaStream class.
//
// Also, we should be able to associate multiple SSRCs with a MediaTrack as
// a track might have an associated RTX and FEC sources.
this.sid = sid;
this.stream = data.stream;
this.peerjid = data.peerjid;
this.ssrc = ssrc;
this.type = (this.stream.getVideoTracks().length > 0)?
MediaStreamType.VIDEO_TYPE : MediaStreamType.AUDIO_TYPE;
this.videoType = null;
this.muted = false;
}
MediaStream.prototype.getOriginalStream = function()
{
return this.stream;
}
MediaStream.prototype.setMute = function (value)
{
this.stream.muted = value;
this.muted = value;
}
module.exports = MediaStream;

204
modules/RTC/RTC.js Normal file
View File

@@ -0,0 +1,204 @@
var EventEmitter = require("events");
var RTCUtils = require("./RTCUtils.js");
var LocalStream = require("./LocalStream.js");
var DataChannels = require("./DataChannels");
var MediaStream = require("./MediaStream.js");
var DesktopSharingEventTypes
= require("../../service/desktopsharing/DesktopSharingEventTypes");
var MediaStreamType = require("../../service/RTC/MediaStreamTypes");
var StreamEventTypes = require("../../service/RTC/StreamEventTypes.js");
var XMPPEvents = require("../../service/xmpp/XMPPEvents");
var UIEvents = require("../../service/UI/UIEvents");
var eventEmitter = new EventEmitter();
var RTC = {
rtcUtils: null,
localStreams: [],
remoteStreams: {},
localAudio: null,
localVideo: null,
addStreamListener: function (listener, eventType) {
eventEmitter.on(eventType, listener);
},
addListener: function (type, listener) {
eventEmitter.on(type, listener);
},
removeStreamListener: function (listener, eventType) {
if(!(eventType instanceof StreamEventTypes))
throw "Illegal argument";
eventEmitter.removeListener(eventType, listener);
},
createLocalStream: function (stream, type, change) {
var localStream = new LocalStream(stream, type, eventEmitter);
//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")
{
this.localAudio = localStream;
}
else
{
this.localVideo = localStream;
}
var eventType = StreamEventTypes.EVENT_TYPE_LOCAL_CREATED;
if(change)
eventType = StreamEventTypes.EVENT_TYPE_LOCAL_CHANGED;
eventEmitter.emit(eventType, localStream);
return localStream;
},
removeLocalStream: function (stream) {
for(var i = 0; i < this.localStreams.length; i++)
{
if(this.localStreams[i].getOriginalStream() === stream) {
delete this.localStreams[i];
return;
}
}
},
createRemoteStream: function (data, sid, thessrc) {
var remoteStream = new MediaStream(data, sid, thessrc,
this.getBrowserType());
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;
},
getUserMediaWithConstraints:function(um, success_callback,
failure_callback, resolution,
bandwidth, fps, desktopStream)
{
return this.rtcUtils.getUserMediaWithConstraints(um, success_callback,
failure_callback, resolution, bandwidth, fps, desktopStream);
},
attachMediaStream: function (element, stream) {
this.rtcUtils.attachMediaStream(element, stream);
},
getStreamID: function (stream) {
return this.rtcUtils.getStreamID(stream);
},
getVideoSrc: function (element) {
return this.rtcUtils.getVideoSrc(element);
},
setVideoSrc: function (element, src) {
this.rtcUtils.setVideoSrc(element, src);
},
dispose: function() {
if (this.rtcUtils) {
this.rtcUtils = null;
}
},
stop: function () {
this.dispose();
},
start: function () {
var self = this;
APP.desktopsharing.addListener(
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);
});
APP.UI.addListener(UIEvents.SELECTED_ENDPOINT,
DataChannels.handleSelectedEndpointEvent);
APP.UI.addListener(UIEvents.PINNED_ENDPOINT,
DataChannels.handlePinnedEndpointEvent);
this.rtcUtils = new RTCUtils(this);
this.rtcUtils.obtainAudioAndVideoPermissions();
},
muteRemoteVideoStream: function (jid, value) {
var stream;
if(this.remoteStreams[jid] &&
this.remoteStreams[jid][MediaStreamType.VIDEO_TYPE])
{
stream = this.remoteStreams[jid][MediaStreamType.VIDEO_TYPE];
}
if(!stream)
return false;
if (value != stream.muted) {
stream.setMute(value);
return true;
}
return false;
},
switchVideoStreams: function (new_stream) {
this.localVideo.stream = new_stream;
this.localStreams = [];
//in firefox we have only one stream object
if (this.localAudio.getOriginalStream() != new_stream)
this.localStreams.push(this.localAudio);
this.localStreams.push(this.localVideo);
},
changeLocalVideo: function (stream, isUsingScreenStream, callback) {
var oldStream = this.localVideo.getOriginalStream();
var type = (isUsingScreenStream? "screen" : "video");
this.localVideo = this.createLocalStream(stream, "video", true, type);
// Stop the stream to trigger onended event for old stream
oldStream.stop();
APP.xmpp.switchStreams(stream, oldStream,callback);
},
/**
* 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;
} else {
var peerStreams = this.remoteStreams[jid];
if(!peerStreams)
return false;
stream = peerStreams[MediaStreamType.VIDEO_TYPE];
}
if(stream)
isDesktop = (stream.videoType === "screen");
return isDesktop;
}
};
module.exports = RTC;

365
modules/RTC/RTCUtils.js Normal file
View File

@@ -0,0 +1,365 @@
var RTCBrowserType = require("../../service/RTC/RTCBrowserType.js");
var Resolutions = require("../../service/RTC/Resolutions");
var currentResolution = null;
function getPreviousResolution(resolution) {
if(!Resolutions[resolution])
return null;
var order = Resolutions[resolution].order;
var res = null;
var resName = null;
for(var i in Resolutions)
{
var tmp = Resolutions[i];
if(res == null || (res.order < tmp.order && tmp.order < order))
{
resName = i;
res = tmp;
}
}
return resName;
}
function setResolutionConstraints(constraints, resolution, isAndroid)
{
if (resolution && !constraints.video || isAndroid) {
constraints.video = { mandatory: {}, optional: [] };// same behaviour as true
}
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;
}
}
if (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;
}
function getConstraints(um, resolution, bandwidth, fps, desktopStream, isAndroid)
{
var constraints = {audio: false, video: false};
if (um.indexOf('video') >= 0) {
constraints.video = { mandatory: {}, optional: [] };// same behaviour as true
}
if (um.indexOf('audio') >= 0) {
constraints.audio = { mandatory: {}, optional: []};// same behaviour as 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 (um.indexOf('desktop') >= 0) {
constraints.video = {
mandatory: {
chromeMediaSource: "desktop",
chromeMediaSourceId: desktopStream,
googLeakyBucket: true,
maxWidth: window.screen.width,
maxHeight: window.screen.height,
maxFrameRate: 3
},
optional: []
};
}
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 (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
// so they choose 30fps mjpg over 10fps yuy2
if (!constraints.video) constraints.video = {mandatory: {}, optional: []};// same behaviour as true;
constraints.video.mandatory.minFrameRate = fps;
}
return constraints;
}
function RTCUtils(RTCService)
{
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) {
this.peerconnection = mozRTCPeerConnection;
this.browser = RTCBrowserType.RTC_BROWSER_FIREFOX;
this.getUserMedia = navigator.mozGetUserMedia.bind(navigator);
this.pc_constraints = {};
this.attachMediaStream = function (element, stream) {
// srcObject is being standardized and FF will eventually
// support that unprefixed. FF also supports the
// "element.src = URL.createObjectURL(...)" combo, but that
// will be deprecated in favour of srcObject.
//
// https://groups.google.com/forum/#!topic/mozilla.dev.media/pKOiioXonJg
// https://github.com/webrtc/samples/issues/302
element[0].mozSrcObject = stream;
element[0].play();
};
this.getStreamID = function (stream) {
var tracks = stream.getVideoTracks();
if(!tracks || tracks.length == 0)
{
tracks = stream.getAudioTracks();
}
return tracks[0].id.replace(/[\{,\}]/g,"");
};
this.getVideoSrc = function (element) {
return element.mozSrcObject;
};
this.setVideoSrc = function (element, src) {
element.mozSrcObject = src;
};
RTCSessionDescription = mozRTCSessionDescription;
RTCIceCandidate = mozRTCIceCandidate;
}
} else if (navigator.webkitGetUserMedia) {
console.log('This appears to be Chrome');
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));
};
this.getStreamID = function (stream) {
// streams from FF endpoints have the characters '{' and '}'
// that make jQuery choke.
return stream.id.replace(/[\{,\}]/g,"");
};
this.getVideoSrc = function (element) {
return element.getAttribute("src");
};
this.setVideoSrc = function (element, src) {
element.setAttribute("src", src);
};
// DTLS should now be enabled by default but..
this.pc_constraints = {'optional': [{'DtlsSrtpKeyAgreement': 'true'}]};
if (navigator.userAgent.indexOf('Android') != -1) {
this.pc_constraints = {}; // disable DTLS on Android
}
if (!webkitMediaStream.prototype.getVideoTracks) {
webkitMediaStream.prototype.getVideoTracks = function () {
return this.videoTracks;
};
}
if (!webkitMediaStream.prototype.getAudioTracks) {
webkitMediaStream.prototype.getAudioTracks = function () {
return this.audioTracks;
};
}
}
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)
{
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);
var isFF = navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
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);
}
});
}
} catch (e) {
console.error('GUM failed: ', e);
if(failure_callback) {
failure_callback(e);
}
}
};
/**
* We ask for audio and video combined stream in order to get permissions and
* not to ask twice.
*/
RTCUtils.prototype.obtainAudioAndVideoPermissions = function() {
var self = this;
// Get AV
this.getUserMediaWithConstraints(
['audio', 'video'],
function (stream) {
self.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.errorCallback = function (error) {
var self = this;
console.error('failed to obtain audio/video stream - trying audio only', error);
var resolution = getPreviousResolution(currentResolution);
if(typeof error == "object" && error.constraintName && error.name
&& (error.name == "ConstraintNotSatisfiedError" ||
error.name == "OverconstrainedError") &&
(error.constraintName == "minWidth" || error.constraintName == "maxWidth" ||
error.constraintName == "minHeight" || error.constraintName == "maxHeight")
&& resolution != null)
{
self.getUserMediaWithConstraints(['audio', 'video'],
function (stream) {
return self.successCallback(stream);
}, function (error) {
return self.errorCallback(error);
}, resolution);
}
else
{
self.getUserMediaWithConstraints(
['audio'],
function (stream) {
return self.successCallback(stream);
},
function (error) {
console.error('failed to obtain audio/video stream - stop',
error);
APP.UI.messageHandler.showError("dialog.error",
"dialog.failedpermissions");
}
);
}
}
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");
}
};
module.exports = RTCUtils;

761
modules/UI/UI.js Normal file
View File

@@ -0,0 +1,761 @@
var UI = {};
var VideoLayout = require("./videolayout/VideoLayout.js");
var AudioLevels = require("./audio_levels/AudioLevels.js");
var Prezi = require("./prezi/Prezi.js");
var Etherpad = require("./etherpad/Etherpad.js");
var Chat = require("./side_pannels/chat/Chat.js");
var Toolbar = require("./toolbars/Toolbar");
var ToolbarToggler = require("./toolbars/ToolbarToggler");
var BottomToolbar = require("./toolbars/BottomToolbar");
var ContactList = require("./side_pannels/contactlist/ContactList");
var Avatar = require("./avatar/Avatar");
var EventEmitter = require("events");
var SettingsMenu = require("./side_pannels/settings/SettingsMenu");
var Settings = require("./../settings/Settings");
var PanelToggler = require("./side_pannels/SidePanelToggler");
var RoomNameGenerator = require("./welcome_page/RoomnameGenerator");
UI.messageHandler = require("./util/MessageHandler");
var messageHandler = UI.messageHandler;
var Authentication = require("./authentication/Authentication");
var UIUtil = require("./util/UIUtil");
var NicknameHandler = require("./util/NicknameHandler");
var CQEvents = require("../../service/connectionquality/CQEvents");
var DesktopSharingEventTypes
= require("../../service/desktopsharing/DesktopSharingEventTypes");
var RTCEvents = require("../../service/RTC/RTCEvents");
var StreamEventTypes = require("../../service/RTC/StreamEventTypes");
var XMPPEvents = require("../../service/xmpp/XMPPEvents");
var eventEmitter = new EventEmitter();
var roomName = null;
function setupPrezi()
{
$("#reloadPresentationLink").click(function()
{
Prezi.reloadPresentation();
});
}
function setupChat()
{
Chat.init();
$("#toggle_smileys").click(function() {
Chat.toggleSmileys();
});
}
function setupToolbars() {
Toolbar.init(UI);
Toolbar.setupButtonsFromConfig();
BottomToolbar.init();
}
function streamHandler(stream) {
switch (stream.type)
{
case "audio":
VideoLayout.changeLocalAudio(stream);
break;
case "video":
VideoLayout.changeLocalVideo(stream);
break;
case "stream":
VideoLayout.changeLocalStream(stream);
break;
}
}
function onDisposeConference(unload) {
Toolbar.showAuthenticateButton(false);
};
function onDisplayNameChanged(jid, displayName) {
ContactList.onDisplayNameChange(jid, displayName);
SettingsMenu.onDisplayNameChange(jid, displayName);
VideoLayout.onDisplayNameChanged(jid, displayName);
}
function registerListeners() {
APP.RTC.addStreamListener(streamHandler, StreamEventTypes.EVENT_TYPE_LOCAL_CREATED);
APP.RTC.addStreamListener(streamHandler, StreamEventTypes.EVENT_TYPE_LOCAL_CHANGED);
APP.RTC.addStreamListener(function (stream) {
VideoLayout.onRemoteStreamAdded(stream);
}, StreamEventTypes.EVENT_TYPE_REMOTE_CREATED);
APP.RTC.addListener(RTCEvents.LASTN_CHANGED, onLastNChanged);
APP.RTC.addListener(RTCEvents.DOMINANTSPEAKER_CHANGED, function (resourceJid) {
VideoLayout.onDominantSpeakerChanged(resourceJid);
});
APP.RTC.addListener(RTCEvents.LASTN_ENDPOINT_CHANGED,
function (lastNEndpoints, endpointsEnteringLastN, stream) {
VideoLayout.onLastNEndpointsChanged(lastNEndpoints,
endpointsEnteringLastN, stream);
});
APP.RTC.addListener(RTCEvents.SIMULCAST_LAYER_CHANGED,
function (endpointSimulcastLayers) {
VideoLayout.onSimulcastLayersChanged(endpointSimulcastLayers);
});
APP.RTC.addListener(RTCEvents.SIMULCAST_LAYER_CHANGING,
function (endpointSimulcastLayers) {
VideoLayout.onSimulcastLayersChanging(endpointSimulcastLayers);
});
APP.statistics.addAudioLevelListener(function(jid, audioLevel)
{
var resourceJid;
if(jid === APP.statistics.LOCAL_JID)
{
resourceJid = AudioLevels.LOCAL_LEVEL;
if(APP.RTC.localAudio.isMuted())
{
audioLevel = 0;
}
}
else
{
resourceJid = Strophe.getResourceFromJid(jid);
}
AudioLevels.updateAudioLevel(resourceJid, audioLevel,
UI.getLargeVideoState().userResourceJid);
});
APP.desktopsharing.addListener(function () {
ToolbarToggler.showDesktopSharingButton();
}, DesktopSharingEventTypes.INIT);
APP.desktopsharing.addListener(
Toolbar.changeDesktopSharingButtonState,
DesktopSharingEventTypes.SWITCHING_DONE);
APP.connectionquality.addListener(CQEvents.LOCALSTATS_UPDATED,
VideoLayout.updateLocalConnectionStats);
APP.connectionquality.addListener(CQEvents.REMOTESTATS_UPDATED,
VideoLayout.updateConnectionStats);
APP.connectionquality.addListener(CQEvents.STOP,
VideoLayout.onStatsStop);
APP.xmpp.addListener(XMPPEvents.DISPOSE_CONFERENCE, onDisposeConference);
APP.xmpp.addListener(XMPPEvents.GRACEFUL_SHUTDOWN, function () {
messageHandler.openMessageDialog(
'dialog.serviceUnavailable',
'dialog.gracefulShutdown'
);
});
APP.xmpp.addListener(XMPPEvents.RESERVATION_ERROR, function (code, msg) {
var title = APP.translation.generateTranslatonHTML(
"dialog.reservationError");
var message = APP.translation.generateTranslatonHTML(
"dialog.reservationErrorMsg", {code: code, msg: msg});
messageHandler.openDialog(
title,
message,
true, {},
function (event, value, message, formVals)
{
return false;
}
);
});
APP.xmpp.addListener(XMPPEvents.KICKED, function () {
messageHandler.openMessageDialog("dialog.sessTerminated",
"dialog.kickMessage");
});
APP.xmpp.addListener(XMPPEvents.MUC_DESTROYED, function (reason) {
//FIXME: use Session Terminated from translation, but
// 'reason' text comes from XMPP packet and is not translated
var title = APP.translation.generateTranslatonHTML("dialog.sessTerminated");
messageHandler.openDialog(
title, reason, true, {},
function (event, value, message, formVals)
{
return false;
}
);
});
APP.xmpp.addListener(XMPPEvents.BRIDGE_DOWN, function () {
messageHandler.showError("dialog.error",
"dialog.bridgeUnavailable");
});
APP.xmpp.addListener(XMPPEvents.USER_ID_CHANGED, function (from, id) {
Avatar.setUserAvatar(from, id);
});
APP.xmpp.addListener(XMPPEvents.CHANGED_STREAMS, function (jid, changedStreams) {
for(stream in changedStreams)
{
// might need to update the direction if participant just went from sendrecv to recvonly
if (stream.type === 'video' || stream.type === 'screen') {
var el = $('#participant_' + Strophe.getResourceFromJid(jid) + '>video');
switch (stream.direction) {
case 'sendrecv':
el.show();
break;
case 'recvonly':
el.hide();
// FIXME: Check if we have to change large video
//VideoLayout.updateLargeVideo(el);
break;
}
}
}
});
APP.xmpp.addListener(XMPPEvents.DISPLAY_NAME_CHANGED, onDisplayNameChanged);
APP.xmpp.addListener(XMPPEvents.MUC_JOINED, onMucJoined);
APP.xmpp.addListener(XMPPEvents.LOCALROLE_CHANGED, onLocalRoleChange);
APP.xmpp.addListener(XMPPEvents.MUC_ENTER, onMucEntered);
APP.xmpp.addListener(XMPPEvents.MUC_ROLE_CHANGED, onMucRoleChanged);
APP.xmpp.addListener(XMPPEvents.PRESENCE_STATUS, onMucPresenceStatus);
APP.xmpp.addListener(XMPPEvents.SUBJECT_CHANGED, chatSetSubject);
APP.xmpp.addListener(XMPPEvents.MESSAGE_RECEIVED, updateChatConversation);
APP.xmpp.addListener(XMPPEvents.MUC_LEFT, onMucLeft);
APP.xmpp.addListener(XMPPEvents.PASSWORD_REQUIRED, onPasswordReqiured);
APP.xmpp.addListener(XMPPEvents.CHAT_ERROR_RECEIVED, chatAddError);
APP.xmpp.addListener(XMPPEvents.ETHERPAD, initEtherpad);
APP.xmpp.addListener(XMPPEvents.AUTHENTICATION_REQUIRED, onAuthenticationRequired);
}
/**
* Mutes/unmutes the local video.
*
* @param mute <tt>true</tt> to mute the local video; otherwise, <tt>false</tt>
* @param options an object which specifies optional arguments such as the
* <tt>boolean</tt> key <tt>byUser</tt> with default value <tt>true</tt> which
* specifies whether the method was initiated in response to a user command (in
* contrast to an automatic decision taken by the application logic)
*/
function setVideoMute(mute, options) {
APP.xmpp.setVideoMute(
mute,
function (mute) {
var video = $('#video');
var communicativeClass = "icon-camera";
var muteClass = "icon-camera icon-camera-disabled";
if (mute) {
video.removeClass(communicativeClass);
video.addClass(muteClass);
} else {
video.removeClass(muteClass);
video.addClass(communicativeClass);
}
},
options);
}
function bindEvents()
{
/**
* Resizes and repositions videos in full screen mode.
*/
$(document).on('webkitfullscreenchange mozfullscreenchange fullscreenchange',
function () {
VideoLayout.resizeLargeVideoContainer();
VideoLayout.positionLarge();
}
);
$(window).resize(function () {
VideoLayout.resizeLargeVideoContainer();
VideoLayout.positionLarge();
});
}
UI.start = function (init) {
document.title = interfaceConfig.APP_NAME;
if(config.enableWelcomePage && window.location.pathname == "/" &&
(!window.localStorage.welcomePageDisabled || window.localStorage.welcomePageDisabled == "false"))
{
$("#videoconference_page").hide();
var setupWelcomePage = require("./welcome_page/WelcomePage");
setupWelcomePage();
return;
}
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'});
}
$("#welcome_page").hide();
VideoLayout.resizeLargeVideoContainer();
$("#videospace").mousemove(function () {
return ToolbarToggler.showToolbar();
});
// Set the defaults for prompt dialogs.
jQuery.prompt.setDefaults({persistent: false});
VideoLayout.init(eventEmitter);
AudioLevels.init();
NicknameHandler.init(eventEmitter);
registerListeners();
bindEvents();
setupPrezi();
setupToolbars();
setupChat();
document.title = interfaceConfig.APP_NAME;
$("#downloadlog").click(function (event) {
dump(event.target);
});
if(config.enableWelcomePage && window.location.pathname == "/" &&
(!window.localStorage.welcomePageDisabled || window.localStorage.welcomePageDisabled == "false"))
{
$("#videoconference_page").hide();
var setupWelcomePage = require("./welcome_page/WelcomePage");
setupWelcomePage();
return;
}
$("#welcome_page").hide();
document.getElementById('largeVideo').volume = 0;
if (!$('#settings').is(':visible')) {
console.log('init');
init();
} else {
loginInfo.onsubmit = function (e) {
if (e.preventDefault) e.preventDefault();
$('#settings').hide();
init();
};
}
toastr.options = {
"closeButton": true,
"debug": false,
"positionClass": "notification-bottom-right",
"onclick": null,
"showDuration": "300",
"hideDuration": "1000",
"timeOut": "2000",
"extendedTimeOut": "1000",
"showEasing": "swing",
"hideEasing": "linear",
"showMethod": "fadeIn",
"hideMethod": "fadeOut",
"reposition": function() {
if(PanelToggler.isVisible()) {
$("#toast-container").addClass("notification-bottom-right-center");
} else {
$("#toast-container").removeClass("notification-bottom-right-center");
}
},
"newestOnTop": false
};
SettingsMenu.init();
};
function chatAddError(errorMessage, originalText)
{
return Chat.chatAddError(errorMessage, originalText);
};
function chatSetSubject(text)
{
return Chat.chatSetSubject(text);
};
function updateChatConversation(from, displayName, message) {
return Chat.updateChatConversation(from, displayName, message);
};
function onMucJoined(jid, info) {
Toolbar.updateRoomUrl(window.location.href);
var meHTML = APP.translation.generateTranslatonHTML("me");
$("#localNick").html(Strophe.getResourceFromJid(jid) + " (" + meHTML + ")");
var settings = Settings.getSettings();
// Add myself to the contact list.
ContactList.addContact(jid, settings.email || settings.uid);
// Once we've joined the muc show the toolbar
ToolbarToggler.showToolbar();
var displayName = !config.displayJids
? info.displayName : Strophe.getResourceFromJid(jid);
if (displayName)
onDisplayNameChanged('localVideoContainer', displayName);
}
function initEtherpad(name) {
Etherpad.init(name);
};
function onMucLeft(jid) {
console.log('left.muc', jid);
var displayName = $('#participant_' + Strophe.getResourceFromJid(jid) +
'>.displayname').html();
messageHandler.notify(displayName,'notify.somebody',
'disconnected',
'notify.disconnected');
// Need to call this with a slight delay, otherwise the element couldn't be
// found for some reason.
// XXX(gp) it works fine without the timeout for me (with Chrome 38).
window.setTimeout(function () {
var container = document.getElementById(
'participant_' + Strophe.getResourceFromJid(jid));
if (container) {
ContactList.removeContact(jid);
VideoLayout.removeConnectionIndicator(jid);
// hide here, wait for video to close before removing
$(container).hide();
VideoLayout.resizeThumbnails();
}
}, 10);
VideoLayout.participantLeft(jid);
};
function onLocalRoleChange(jid, info, pres, isModerator)
{
console.info("My role changed, new role: " + info.role);
onModeratorStatusChanged(isModerator);
VideoLayout.showModeratorIndicator();
if (isModerator) {
Authentication.closeAuthenticationWindow();
messageHandler.notify(null, "notify.me",
'connected', "notify.moderator");
}
}
function onModeratorStatusChanged(isModerator) {
Toolbar.showSipCallButton(isModerator);
Toolbar.showRecordingButton(
isModerator); //&&
// FIXME:
// Recording visible if
// there are at least 2(+ 1 focus) participants
//Object.keys(connection.emuc.members).length >= 3);
if (isModerator && config.etherpad_base) {
Etherpad.init();
}
};
function onPasswordReqiured(callback) {
// password is required
Toolbar.lockLockButton();
var message = '<h2 data-i18n="dialog.passwordRequired">';
message += APP.translation.translateString(
"dialog.passwordRequired");
message += '</h2>' +
'<input id="lockKey" type="text" data-i18n=' +
'"[placeholder]dialog.password" placeholder="' +
APP.translation.translateString("dialog.password") +
'" autofocus>';
messageHandler.openTwoButtonDialog(null, null, null, message,
true,
"dialog.Ok",
function (e, v, m, f) {},
function (event) {
document.getElementById('lockKey').focus();
},
function (e, v, m, f) {
if (v) {
var lockKey = document.getElementById('lockKey');
if (lockKey.value !== null) {
Toolbar.setSharedKey(lockKey.value);
callback(lockKey.value);
}
}
}
);
}
function onMucEntered(jid, id, displayName) {
messageHandler.notify(displayName,'notify.somebody',
'connected',
'notify.connected');
// Add Peer's container
VideoLayout.ensurePeerContainerExists(jid,id);
}
function onMucPresenceStatus( jid, info) {
VideoLayout.setPresenceStatus(
'participant_' + Strophe.getResourceFromJid(jid), info.status);
}
function onMucRoleChanged(role, displayName) {
VideoLayout.showModeratorIndicator();
if (role === 'moderator') {
var messageKey, messageOptions = {};
if (!displayName) {
messageKey = "notify.grantedToUnknown";
}
else
{
messageKey = "notify.grantedTo";
messageOptions = {to: displayName};
}
messageHandler.notify(
displayName,'notify.somebody',
'connected', messageKey,
messageOptions);
}
}
function onAuthenticationRequired(intervalCallback) {
Authentication.openAuthenticationDialog(
roomName, intervalCallback, function () {
Toolbar.authenticateClicked();
});
};
function onLastNChanged(oldValue, newValue) {
if (config.muteLocalVideoIfNotInLastN) {
setVideoMute(!newValue, { 'byUser': false });
}
}
UI.toggleSmileys = function () {
Chat.toggleSmileys();
};
UI.getSettings = function () {
return Settings.getSettings();
};
UI.toggleFilmStrip = function () {
return BottomToolbar.toggleFilmStrip();
};
UI.toggleChat = function () {
return BottomToolbar.toggleChat();
};
UI.toggleContactList = function () {
return BottomToolbar.toggleContactList();
};
UI.inputDisplayNameHandler = function (value) {
VideoLayout.inputDisplayNameHandler(value);
};
UI.getLargeVideoState = function()
{
return VideoLayout.getLargeVideoState();
};
UI.generateRoomName = function() {
if(roomName)
return roomName;
var roomnode = null;
var path = window.location.pathname;
// determinde the room node from the url
// TODO: just the roomnode or the whole bare jid?
if (config.getroomnode && typeof config.getroomnode === 'function') {
// custom function might be responsible for doing the pushstate
roomnode = config.getroomnode(path);
} else {
/* fall back to default strategy
* this is making assumptions about how the URL->room mapping happens.
* It currently assumes deployment at root, with a rewrite like the
* following one (for nginx):
location ~ ^/([a-zA-Z0-9]+)$ {
rewrite ^/(.*)$ / break;
}
*/
if (path.length > 1) {
roomnode = path.substr(1).toLowerCase();
} else {
var word = RoomNameGenerator.generateRoomWithoutSeparator();
roomnode = word.toLowerCase();
window.history.pushState('VideoChat',
'Room: ' + word, window.location.pathname + word);
}
}
roomName = roomnode + '@' + config.hosts.muc;
return roomName;
};
UI.connectionIndicatorShowMore = function(id)
{
return VideoLayout.connectionIndicators[id].showMore();
};
UI.getCredentials = function () {
var settings = this.getSettings();
return {
bosh: document.getElementById('boshURL').value,
password: document.getElementById('password').value,
jid: document.getElementById('jid').value,
email: settings.email,
displayName: settings.displayName,
uid: settings.uid
};
};
UI.disableConnect = function () {
document.getElementById('connect').disabled = true;
};
UI.showLoginPopup = function(callback)
{
console.log('password is required');
var message = '<h2 data-i18n="dialog.passwordRequired">';
message += APP.translation.translateString(
"dialog.passwordRequired");
message += '</h2>' +
'<input id="passwordrequired.username" type="text" ' +
'placeholder="user@domain.net" autofocus>' +
'<input id="passwordrequired.password" ' +
'type="password" data-i18n="[placeholder]dialog.userPassword"' +
' placeholder="user password">';
UI.messageHandler.openTwoButtonDialog(null, null, null, message,
true,
"dialog.Ok",
function (e, v, m, f) {
if (v) {
var username = document.getElementById('passwordrequired.username');
var password = document.getElementById('passwordrequired.password');
if (username.value !== null && password.value != null) {
callback(username.value, password.value);
}
}
},
function (event) {
document.getElementById('passwordrequired.username').focus();
}
);
}
UI.checkForNicknameAndJoin = function () {
Authentication.closeAuthenticationDialog();
Authentication.stopInterval();
var nick = null;
if (config.useNicks) {
nick = window.prompt('Your nickname (optional)');
}
APP.xmpp.joinRoom(roomName, config.useNicks, nick);
};
function dump(elem, filename) {
elem = elem.parentNode;
elem.download = filename || 'meetlog.json';
elem.href = 'data:application/json;charset=utf-8,\n';
var data = APP.xmpp.populateData();
var metadata = {};
metadata.time = new Date();
metadata.url = window.location.href;
metadata.ua = navigator.userAgent;
var log = APP.xmpp.getLogger();
if (log) {
metadata.xmpp = log;
}
data.metadata = metadata;
elem.href += encodeURIComponent(JSON.stringify(data, null, ' '));
return false;
}
UI.getRoomName = function () {
return roomName;
};
/**
* Mutes/unmutes the local video.
*/
UI.toggleVideo = function () {
UIUtil.buttonClick("#video", "icon-camera icon-camera-disabled");
setVideoMute(!APP.RTC.localVideo.isMuted());
};
/**
* Mutes / unmutes audio for the local participant.
*/
UI.toggleAudio = function() {
UI.setAudioMuted(!APP.RTC.localAudio.isMuted());
};
/**
* Sets muted audio state for the local participant.
*/
UI.setAudioMuted = function (mute) {
if(!APP.xmpp.setAudioMute(mute, function () {
VideoLayout.showLocalAudioIndicator(mute);
UIUtil.buttonClick("#mute", "icon-microphone icon-mic-disabled");
}))
{
// We still click the button.
UIUtil.buttonClick("#mute", "icon-microphone icon-mic-disabled");
return;
}
}
UI.addListener = function (type, listener) {
eventEmitter.on(type, listener);
}
UI.clickOnVideo = function (videoNumber) {
var remoteVideos = $(".videocontainer:not(#mixedstream)");
if (remoteVideos.length > videoNumber) {
remoteVideos[videoNumber].click();
}
}
//Used by torture
UI.showToolbar = function () {
return ToolbarToggler.showToolbar();
}
//Used by torture
UI.dockToolbar = function (isDock) {
return ToolbarToggler.dockToolbar(isDock);
}
module.exports = UI;

View File

@@ -1,3 +1,20 @@
var CanvasUtil = require("./CanvasUtils");
var ASDrawContext = $('#activeSpeakerAudioLevel')[0].getContext('2d');
function initActiveSpeakerAudioLevels() {
var ASRadius = interfaceConfig.ACTIVE_SPEAKER_AVATAR_SIZE / 2;
var ASCenter = (interfaceConfig.ACTIVE_SPEAKER_AVATAR_SIZE + ASRadius) / 2;
// Draw a circle.
ASDrawContext.arc(ASCenter, ASCenter, ASRadius, 0, 2 * Math.PI);
// Add a shadow around the circle
ASDrawContext.shadowColor = interfaceConfig.SHADOW_COLOR;
ASDrawContext.shadowOffsetX = 0;
ASDrawContext.shadowOffsetY = 0;
}
/**
* The audio Levels plugin.
*/
@@ -6,11 +23,15 @@ var AudioLevels = (function(my) {
my.LOCAL_LEVEL = 'local';
my.init = function () {
initActiveSpeakerAudioLevels();
}
/**
* Updates the audio level canvas for the given peerJid. If the canvas
* didn't exist we create it.
*/
my.updateAudioLevelCanvas = function (peerJid) {
my.updateAudioLevelCanvas = function (peerJid, VideoLayout) {
var resourceJid = null;
var videoSpanId = null;
if (!peerJid)
@@ -21,7 +42,7 @@ var AudioLevels = (function(my) {
videoSpanId = 'participant_' + resourceJid;
}
videoSpan = document.getElementById(videoSpanId);
var videoSpan = document.getElementById(videoSpanId);
if (!videoSpan) {
if (resourceJid)
@@ -35,8 +56,7 @@ var AudioLevels = (function(my) {
var audioLevelCanvas = $('#' + videoSpanId + '>canvas');
var videoSpaceWidth = $('#remoteVideos').width();
var thumbnailSize
= VideoLayout.calculateThumbnailSize(videoSpaceWidth);
var thumbnailSize = VideoLayout.calculateThumbnailSize(videoSpaceWidth);
var thumbnailWidth = thumbnailSize[0];
var thumbnailHeight = thumbnailSize[1];
@@ -67,7 +87,7 @@ var AudioLevels = (function(my) {
* which we draw the audio level
* @param audioLevel the newAudio level to render
*/
my.updateAudioLevel = function (resourceJid, audioLevel) {
my.updateAudioLevel = function (resourceJid, audioLevel, largeVideoResourceJid) {
drawAudioLevelCanvas(resourceJid, audioLevel);
var videoSpanId = getVideoSpanId(resourceJid);
@@ -84,6 +104,35 @@ var AudioLevels = (function(my) {
drawContext.clearRect (0, 0,
audioLevelCanvas.width, audioLevelCanvas.height);
drawContext.drawImage(canvasCache, 0, 0);
if(resourceJid === AudioLevels.LOCAL_LEVEL) {
if(!APP.xmpp.myJid()) {
return;
}
resourceJid = APP.xmpp.myResource();
}
if(resourceJid === largeVideoResourceJid) {
window.requestAnimationFrame(function () {
AudioLevels.updateActiveSpeakerAudioLevel(audioLevel);
});
}
};
my.updateActiveSpeakerAudioLevel = function(audioLevel) {
if($("#activeSpeaker").css("visibility") == "hidden")
return;
ASDrawContext.clearRect(0, 0, 300, 300);
if(audioLevel == 0)
return;
ASDrawContext.shadowBlur = getShadowLevel(audioLevel);
// Fill the shape.
ASDrawContext.fill();
};
/**
@@ -94,7 +143,7 @@ var AudioLevels = (function(my) {
thumbnailHeight) {
audioLevelCanvas.width = thumbnailWidth + interfaceConfig.CANVAS_EXTRA;
audioLevelCanvas.height = thumbnailHeight + interfaceConfig.CANVAS_EXTRA;
};
}
/**
* Draws the audio level canvas into the cached canvas object.
@@ -143,7 +192,7 @@ var AudioLevels = (function(my) {
interfaceConfig.CANVAS_RADIUS,
interfaceConfig.SHADOW_COLOR,
shadowLevel);
};
}
/**
* Returns the shadow/glow level for the given audio level.
@@ -164,7 +213,7 @@ var AudioLevels = (function(my) {
shadowLevel = Math.round(interfaceConfig.CANVAS_EXTRA/2*((audioLevel - 0.6) / 0.4));
}
return shadowLevel;
};
}
/**
* Returns the video span id corresponding to the given resourceJid or local
@@ -173,14 +222,14 @@ var AudioLevels = (function(my) {
function getVideoSpanId(resourceJid) {
var videoSpanId = null;
if (resourceJid === AudioLevels.LOCAL_LEVEL
|| (connection.emuc.myroomjid && resourceJid
=== Strophe.getResourceFromJid(connection.emuc.myroomjid)))
|| (APP.xmpp.myResource() && resourceJid
=== APP.xmpp.myResource()))
videoSpanId = 'localVideoContainer';
else
videoSpanId = 'participant_' + resourceJid;
return videoSpanId;
};
}
/**
* Indicates that the remote video has been resized.
@@ -212,3 +261,5 @@ var AudioLevels = (function(my) {
return my;
})(AudioLevels || {});
module.exports = AudioLevels;

View File

@@ -107,3 +107,5 @@ var CanvasUtil = (function(my) {
return my;
})(CanvasUtil || {});
module.exports = CanvasUtil;

View File

@@ -0,0 +1,88 @@
/* Initial "authentication required" dialog */
var authDialog = null;
/* Loop retry ID that wits for other user to create the room */
var authRetryId = null;
var authenticationWindow = null;
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);
// Show prompt only if it's not open
if (authDialog !== null) {
return;
}
// 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"};
authDialog = APP.UI.messageHandler.openDialog(
title,
msg,
true,
buttons,
function (onSubmitEvent, submitValue) {
// Do not close the dialog yet
onSubmitEvent.preventDefault();
// Open login popup
if (submitValue === 'authNow') {
callback();
}
}
);
},
closeAuthenticationWindow:function () {
if (authenticationWindow) {
authenticationWindow.close();
authenticationWindow = null;
}
},
focusAuthenticationWindow: function () {
// If auth window exists just bring it to the front
if (authenticationWindow) {
authenticationWindow.focus();
return;
}
},
closeAuthenticationDialog: function () {
// Close authentication dialog if opened
if (authDialog) {
APP.UI.messageHandler.closeDialog();
authDialog = null;
}
},
createAuthenticationWindow: function (callback, url) {
authenticationWindow = APP.UI.messageHandler.openCenteredPopup(
url, 910, 660,
// On closed
function () {
// Close authentication dialog if opened
if (authDialog) {
messageHandler.closeDialog();
authDialog = null;
}
callback();
authenticationWindow = null;
});
return authenticationWindow;
},
stopInterval: function () {
// Clear retry interval, so that we don't call 'doJoinAfterFocus' twice
if (authRetryId) {
window.clearTimeout(authRetryId);
authRetryId = null;
}
}
};
module.exports = Authentication;

155
modules/UI/avatar/Avatar.js Normal file
View File

@@ -0,0 +1,155 @@
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 = {
/**
* Sets the user's avatar in the settings menu(if local user), contact list
* and thumbnail
* @param jid jid of the user
* @param id email or userID to be used as a hash
*/
setUserAvatar: function (jid, id) {
if (id) {
if (users[jid] === id) {
return;
}
users[jid] = id;
}
var thumbUrl = getGravatarUrl(users[jid] || jid, 100);
var contactListUrl = getGravatarUrl(users[jid] || 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);
}
},
/**
* 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
*/
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);
}
}
}
},
/**
* Updates the src of the active speaker avatar
* @param jid of the current active speaker
*/
updateActiveSpeakerAvatarSrc: function (jid) {
if (!jid) {
jid = APP.xmpp.findJidFromResource(
require("../videolayout/VideoLayout").getLargeVideoState().userResourceJid);
}
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);
}
}
};
module.exports = Avatar;

View File

@@ -0,0 +1,194 @@
/* global $, config,
setLargeVideoVisible, Util */
var VideoLayout = require("../videolayout/VideoLayout");
var Prezi = require("../prezi/Prezi");
var UIUtil = require("../util/UIUtil");
var etherpadName = null;
var etherpadIFrame = null;
var domain = null;
var options = "?showControls=true&showChat=false&showLineNumbers=true&useMonospaceFont=false";
/**
* Resizes the etherpad.
*/
function resize() {
if ($('#etherpad>iframe').length) {
var remoteVideos = $('#remoteVideos');
var availableHeight
= window.innerHeight - remoteVideos.outerHeight();
var availableWidth = UIUtil.getAvailableVideoWidth();
$('#etherpad>iframe').width(availableWidth);
$('#etherpad>iframe').height(availableHeight);
}
}
/**
* 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'});
}
/**
* 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;');
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);
};
}
function bubbleIframeMouseMove(iframe){
var existingOnMouseMove = iframe.contentWindow.onmousemove;
iframe.contentWindow.onmousemove = function(e){
if(existingOnMouseMove) existingOnMouseMove(e);
var evt = document.createEvent("MouseEvents");
var boundingClientRect = iframe.getBoundingClientRect();
evt.initMouseEvent(
"mousemove",
true, // bubbles
false, // not cancelable
window,
e.detail,
e.screenX,
e.screenY,
e.clientX + boundingClientRect.left,
e.clientY + boundingClientRect.top,
e.ctrlKey,
e.altKey,
e.shiftKey,
e.metaKey,
e.button,
null // no related element
);
iframe.dispatchEvent(evt);
};
}
/**
* 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) {
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;
enableEtherpadButton();
/**
* Resizes the etherpad, when the window is resized.
*/
$(window).resize(function () {
resize();
});
}
},
/**
* Opens/hides the Etherpad.
*/
toggleEtherpad: function (isPresentation) {
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});
});
}
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);
});
}
}
resize();
},
isVisible: function() {
var etherpadIframe = $('#etherpad>iframe');
return etherpadIframe && etherpadIframe.is(':visible');
}
};
module.exports = Etherpad;

371
modules/UI/prezi/Prezi.js Normal file
View File

@@ -0,0 +1,371 @@
var ToolbarToggler = require("../toolbars/ToolbarToggler");
var UIUtil = require("../util/UIUtil");
var VideoLayout = require("../videolayout/VideoLayout");
var messageHandler = require("../util/MessageHandler");
var PreziPlayer = require("./PreziPlayer");
var preziPlayer = null;
var Prezi = {
/**
* Reloads the current presentation.
*/
reloadPresentation: function() {
var iframe = document.getElementById(preziPlayer.options.preziId);
iframe.src = iframe.src;
},
/**
* Returns <tt>true</tt> if the presentation is visible, <tt>false</tt> -
* otherwise.
*/
isPresentationVisible: function () {
return ($('#presentation>iframe') != null
&& $('#presentation>iframe').css('opacity') == 1);
},
/**
* Opens the Prezi dialog, from which the user could choose a presentation
* to load.
*/
openPreziDialog: function() {
var myprezi = APP.xmpp.getPrezi();
if (myprezi) {
messageHandler.openTwoButtonDialog("dialog.removePreziTitle",
null,
"dialog.removePreziMsg",
null,
false,
"dialog.Remove",
function(e,v,m,f) {
if(v) {
APP.xmpp.removePreziFromPresence();
}
}
);
}
else if (preziPlayer != null) {
messageHandler.openTwoButtonDialog("dialog.sharePreziTitle",
null, "dialog.sharePreziMsg",
null,
false,
"dialog.Ok",
function(e,v,m,f) {
$.prompt.close();
}
);
}
else {
var html = APP.translation.generateTranslatonHTML(
"dialog.sharePreziTitle");
var cancelButton = APP.translation.generateTranslatonHTML(
"dialog.Cancel");
var shareButton = APP.translation.generateTranslatonHTML(
"dialog.Share");
var backButton = APP.translation.generateTranslatonHTML(
"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(
"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" ' +
'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){
e.preventDefault();
if(v)
{
var preziUrl = document.getElementById('preziUrl');
if (preziUrl.value)
{
var urlValue
= encodeURI(UIUtil.escapeHtml(preziUrl.value));
if (urlValue.indexOf('http://prezi.com/') != 0
&& urlValue.indexOf('https://prezi.com/') != 0)
{
$.prompt.goToState('state1');
return false;
}
else {
var presIdTmp = urlValue.substring(
urlValue.indexOf("prezi.com/") + 10);
if (!isAlphanumeric(presIdTmp)
|| presIdTmp.indexOf('/') < 2) {
$.prompt.goToState('state1');
return false;
}
else {
APP.xmpp.addToPresence("prezi", urlValue);
$.prompt.close();
}
}
}
}
else
$.prompt.close();
}
},
state1: {
html: '<h2>' + html + '</h2>' +
linkError,
persistent: false,
buttons: buttons1,
defaultButton: 1,
submit:function(e,v,m,f) {
e.preventDefault();
if(v==0)
$.prompt.close();
else
$.prompt.goToState('state0');
}
}
};
var focusPreziUrl = function(e) {
document.getElementById('preziUrl').focus();
};
messageHandler.openDialogWithStates(openPreziState, focusPreziUrl, focusPreziUrl);
}
}
};
/**
* A new presentation has been added.
*
* @param event the event indicating the add of a presentation
* @param jid the jid from which the presentation was added
* @param presUrl url of the presentation
* @param currentSlide the current slide to which we should move
*/
function presentationAdded(event, jid, presUrl, currentSlide) {
console.log("presentation added", presUrl);
var presId = getPresentationId(presUrl);
var elementId = 'participant_'
+ 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();
var controlsEnabled = false;
if (jid === APP.xmpp.myJid())
controlsEnabled = true;
setPresentationVisible(true);
$('#largeVideoContainer').hover(
function (event) {
if (Prezi.isPresentationVisible()) {
var reloadButtonRight = window.innerWidth
- $('#presentation>iframe').offset().left
- $('#presentation>iframe').width();
$('#reloadPresentation').css({ right: reloadButtonRight,
display:'inline-block'});
}
},
function (event) {
if (!Prezi.isPresentationVisible())
$('#reloadPresentation').css({display:'none'});
else {
var e = event.toElement || event.relatedTarget;
if (e && e.id != 'reloadPresentation' && e.id != 'header')
$('#reloadPresentation').css({display:'none'});
}
});
preziPlayer = new PreziPlayer(
'presentation',
{preziId: presId,
width: getPresentationWidth(),
height: getPresentationHeihgt(),
controls: controlsEnabled,
debug: true
});
$('#presentation>iframe').attr('id', preziPlayer.options.preziId);
preziPlayer.on(PreziPlayer.EVENT_STATUS, function(event) {
console.log("prezi status", event.value);
if (event.value == PreziPlayer.STATUS_CONTENT_READY) {
if (jid != APP.xmpp.myJid())
preziPlayer.flyToStep(currentSlide);
}
});
preziPlayer.on(PreziPlayer.EVENT_CURRENT_STEP, function(event) {
console.log("event value", event.value);
APP.xmpp.addToPresence("preziSlide", event.value);
});
$("#" + elementId).css( 'background-image',
'url(../images/avatarprezi.png)');
$("#" + elementId).click(
function () {
setPresentationVisible(true);
}
);
};
/**
* A presentation has been removed.
*
* @param event the event indicating the remove of a presentation
* @param jid the jid for which the presentation was removed
* @param the url of the presentation
*/
function presentationRemoved(event, jid, presUrl) {
console.log('presentation removed', presUrl);
var presId = getPresentationId(presUrl);
setPresentationVisible(false);
$('#participant_'
+ Strophe.getResourceFromJid(jid)
+ '_' + presId).remove();
$('#presentation>iframe').remove();
if (preziPlayer != null) {
preziPlayer.destroy();
preziPlayer = null;
}
};
/**
* Indicates if the given string is an alphanumeric string.
* Note that some special characters are also allowed (-, _ , /, &, ?, =, ;) for the
* purpose of checking URIs.
*/
function isAlphanumeric(unsafeText) {
var regex = /^[a-z0-9-_\/&\?=;]+$/i;
return regex.test(unsafeText);
}
/**
* Returns the presentation id from the given url.
*/
function getPresentationId (presUrl) {
var presIdTmp = presUrl.substring(presUrl.indexOf("prezi.com/") + 10);
return presIdTmp.substring(0, presIdTmp.indexOf('/'));
}
/**
* Returns the presentation width.
*/
function getPresentationWidth() {
var availableWidth = UIUtil.getAvailableVideoWidth();
var availableHeight = getPresentationHeihgt();
var aspectRatio = 16.0 / 9.0;
if (availableHeight < availableWidth / aspectRatio) {
availableWidth = Math.floor(availableHeight * aspectRatio);
}
return availableWidth;
}
/**
* Returns the presentation height.
*/
function getPresentationHeihgt() {
var remoteVideos = $('#remoteVideos');
return window.innerHeight - remoteVideos.outerHeight();
}
/**
* Resizes the presentation iframe.
*/
function resize() {
if ($('#presentation>iframe')) {
$('#presentation>iframe').width(getPresentationWidth());
$('#presentation>iframe').height(getPresentationHeihgt());
}
}
/**
* 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.
*/
$(document).bind('presentationremoved.muc', presentationRemoved);
/**
* Presentation has been added.
*/
$(document).bind('presentationadded.muc', presentationAdded);
/*
* Indicates presentation slide change.
*/
$(document).bind('gotoslide.muc', function (event, jid, presUrl, current) {
if (preziPlayer && preziPlayer.getCurrentStep() != current) {
preziPlayer.flyToStep(current);
var animationStepsArray = preziPlayer.getAnimationCountOnSteps();
for (var i = 0; i < parseInt(animationStepsArray[current]); i++) {
preziPlayer.flyToStep(current, i);
}
}
});
/**
* On video selected event.
*/
$(document).bind('video.selected', function (event, isPresentation) {
if (!isPresentation && $('#presentation>iframe')) {
setPresentationVisible(false);
}
});
$(window).resize(function () {
resize();
});
module.exports = Prezi;

View File

@@ -33,21 +33,22 @@
var message, item, player;
try {
message = JSON.parse(event.data);
} catch (e) {}
if (message.id && (player = PreziPlayer.players[message.id])){
if (player.options.debug === true) {
if (console && console.log) console.log('received', message);
}
if (message.type === "changes"){
player.changesHandler(message);
}
for (var i=0; i<player.callbacks.length; i++) {
item = player.callbacks[i];
if (item && message.type === item.event){
item.callback(message);
if (message.id && (player = PreziPlayer.players[message.id])) {
if (player.options.debug === true) {
if (console && console.log)
console.log('received', message);
}
if (message.type === "changes") {
player.changesHandler(message);
}
for (var i = 0; i < player.callbacks.length; i++) {
item = player.callbacks[i];
if (item && message.type === item.event) {
item.callback(message);
}
}
}
}
} catch (e) { }
};
function PreziPlayer(id, options) {
@@ -289,3 +290,5 @@
})();
})();
module.exports = PreziPlayer;

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