Compare commits

..

187 Commits
528 ... 628

Author SHA1 Message Date
paweldomas
a2b43843b7 Updates app.bundle.js 2015-08-24 12:00:40 +02:00
paweldomas
2f03a0a7fe Fixes broken last-N 2015-08-24 11:53:13 +02:00
paweldomas
4c2f0d3600 Removed duplicated code for adding video thumbnail's hover handler. 2015-08-24 11:53:12 +02:00
paweldomas
a8a0945d73 Moves method for selecting thumbnail's video element from videolayout to SmallVideo. Fixes issue with muted audio in IE after switching between thumbnails. 2015-08-24 11:53:11 +02:00
paweldomas
a7048fba06 Implements HTTP POST query for fetching app configuration. 2015-08-24 11:53:10 +02:00
Boris Grozev
7b35dd89bb Updates the external api docs. 2015-08-20 15:00:56 -05:00
Boris Grozev
3561204bb5 Allows to overwrite config and interfaceConfig options through the
external API.
2015-08-20 14:57:05 -05:00
ibauersachs
ee50d07dc3 Commit from translate.jitsi.org by user ibauersachs.: 172 of 173 strings translated (0 fuzzy). 2015-08-19 19:58:21 +00:00
ibauersachs
9ec4bc91fc Commit from translate.jitsi.org by user ibauersachs.: 173 of 173 strings translated (0 fuzzy). 2015-08-19 19:58:06 +00:00
Ingo Bauersachs
88071e5258 Add Slovenian (sl) 2015-08-19 21:54:00 +02:00
paweldomas
e79d476d89 Updates app.bundle.js 2015-08-19 18:27:09 +02:00
paweldomas
0fe4999beb Use fadeTo instead of fadeIn/fadeOut to avoid having display: none on large video which causes issues when Temasys plugin is used. 2015-08-19 18:23:18 +02:00
paweldomas
ae96b9f365 Fixes issue in IE where click events on local video thumbnail are captured by local audio object created by Temasys plugin on stream attach. 2015-08-19 18:23:08 +02:00
paweldomas
922d0bd512 New adapter.js for Temasys plugin. 2015-08-19 18:22:07 +02:00
paweldomas
9a7bc4ebab Fixes issue with returning from shared document view. Calls show() before fadeOut() on large video to avoid situation when we end up with black screen and are unable to select new video. Updates app.bundle.js. 2015-08-19 10:55:35 +02:00
damencho
2081757ba1 Enables stats in FF. 2015-08-18 16:42:47 -05:00
jitsi-pootle
e9c9fc5e69 New files added from translate.jitsi.org based on templates 2015-08-18 12:50:42 +00:00
paweldomas
562761196d Updates app.bundle.js 2015-08-18 13:27:29 +02:00
paweldomas
420514b921 Temasys adapter.js ver 0.12.0 with fix for setInterval included 2015-08-18 13:22:17 +02:00
paweldomas
eb63b24a9a Fixes fadeIn/fadeOut large video transitions in Safari. Enables animation on video resize when switching between camera and screen video. 2015-08-18 13:19:54 +02:00
paweldomas
c8bbded994 Removes accidentally committed debug message. 2015-08-18 13:19:44 +02:00
damencho
2a2702c13a Adds params for enabling fake devices on firefox. 2015-08-17 17:05:03 -05:00
Boris Grozev
5fc868ee96 Updates app.bundle.js. 2015-08-17 16:17:47 -05:00
Boris Grozev
502eab7278 Only update the "start muted" settings on precense from a moderator. 2015-08-17 16:17:15 -05:00
Boris Grozev
332aafbe20 Documents some of the XMPP events. Renames some of them. 2015-08-17 16:17:03 -05:00
Boris Grozev
d5258e6197 Remove a double variable declaration. 2015-08-17 16:17:01 -05:00
Boris Grozev
9cc9e6132c Add RTCBrowserType.isAndroid(). 2015-08-17 16:16:34 -05:00
hristoterezov
f60c1d9751 Removes the minimum width and height of the external API iframe in "film strip only" mode. 2015-08-17 13:46:26 -05:00
Boris Grozev
5d32318d93 Updates app.bundle.js. 2015-08-14 10:49:13 -05:00
Boris Grozev
fee8482bae Updates sdp-transform to 1.4.1. 2015-08-14 10:48:21 -05:00
paweldomas
f2b5cdbfb8 Updates app.bundle.js. 2015-08-14 17:06:50 +02:00
paweldomas
60afe2d202 Fixes issue with display name event not being fired on Safari/IE 2015-08-14 17:04:30 +02:00
paweldomas
18f03e296b Fixes broken input fields in Safari. 2015-08-14 17:03:58 +02:00
paweldomas
5cd9db1b6a Missing semicolon... 2015-08-14 17:02:19 +02:00
bgrozev
f83404a99e Merge pull request #341 from pstros/fix-comment
Change the comment to fix npm install
2015-08-13 16:00:56 -05:00
Devin Wilson
7c1ba9242b Change the comment to fix npm install 2015-08-13 08:31:50 -06:00
ibauersachs
bfcc587047 Commit from translate.jitsi.org by user ibauersachs.: 172 of 172 strings translated (0 fuzzy). 2015-08-13 07:35:17 +00:00
Boris Grozev
e90d8f5531 Updates app.bundle.js. 2015-08-12 21:54:15 +02:00
Boris Grozev
59033aab28 Adds what will hopefully be treated as a comment by all npm versions to package.json. 2015-08-12 21:52:49 +02:00
Boris Grozev
7f1eb617c3 Uses npm packeges for socket.io and jsSHA. 2015-08-12 21:50:42 +02:00
hristoterezov
fd7e8c9162 Merge pull request #338 from gerges/issue/toolbar-refactor
Refactors toolbar
2015-08-12 13:55:50 -05:00
Issac Gerges
51e886142b Ensure hangup button selector is specific enough to apply red color and custom size 2015-08-12 13:36:24 -05:00
Issac Gerges
dcc206b2b4 Update non-container toolbar items to be set to inline-block when shown 2015-08-12 13:06:55 -05:00
Issac Gerges
da75e17ff5 Merge remote-tracking branch 'origin/master' into issue/toolbar-refactor 2015-08-12 13:05:20 -05:00
paweldomas
8fea9b76ee Updates app.bundle.js 2015-08-12 14:23:43 +02:00
paweldomas
cb024be2d6 Fixes locking at WaitForPluginReady with Temasys plugin install detection. 2015-08-12 14:21:08 +02:00
paweldomas
4c4e99c51a Updates Temasys adapter.screenshare.js. Browser restart is no longer required after plugin install. 2015-08-12 14:20:51 +02:00
paweldomas
4b8bc398dd Fixes issue with black video when new stream element is inserted after the old one. 2015-08-12 14:20:44 +02:00
paweldomas
466e7dcc91 Fixes crash in LocalSSRCReplacement when null localDescription is passed. 2015-08-12 14:20:37 +02:00
Issac Gerges
de30ce0f5c Merge remote-tracking branch 'origin/master' into issue/toolbar-refactor 2015-08-11 13:30:08 -05:00
Issac Gerges
fc6f5717cb Refactor toolbar to add separators via css and remove unneeded containers 2015-08-11 13:24:53 -05:00
paweldomas
b680ecd2ff Renames getLargeVideoJid to getLargeVideoResource and updates app.bundle.js. 2015-08-11 13:12:55 +02:00
Boris Grozev
2bea2eec74 Updates app.bundle.js. 2015-08-10 16:54:51 -05:00
Boris Grozev
f52b1380ee Continues to separate JingleSessionPC. 2015-08-10 16:38:35 -05:00
Boris Grozev
baf720c553 Starts to abstract JingleSession. 2015-08-10 15:58:50 -05:00
Boris Grozev
deaff6af5b Executes a local (git-ignored) script on "make deploy". 2015-08-10 13:25:21 -05:00
Boris Grozev
6ca1e131af Renames JingleSession to JingleSessionPC. 2015-08-10 13:22:05 -05:00
Boris Grozev
57b9aeb38c Inlines a method for clarity/simplicity. 2015-08-10 13:14:12 -05:00
Boris Grozev
cc20a4d776 Removes an unused variable. 2015-08-10 13:02:39 -05:00
Boris Grozev
fd404b8465 Supports setting interfaceConfig options via URL params. Renames config.filmStripOnly to interfaceConfig.filmStripOnly. 2015-08-10 12:59:12 -05:00
damencho
cc29df6376 Adds params for enabling rec on entering the conference. 2015-08-07 10:31:48 -05:00
paweldomas
44136e8a55 Updates app.bundle.js. 2015-08-07 12:59:43 +02:00
paweldomas
fb875423a9 Fixes SSRC=1 issue. Renames VideoSSRCHack to LocalSSRCReplacement. 2015-08-07 12:58:12 +02:00
paweldomas
ab4c29eddc Fixes video mute in Firefox. Disables VideoSSRCHack for Firefox by default. 2015-08-07 12:58:03 +02:00
paweldomas
95e964a089 Fixes bugs in VideoSSRCHack. Additional log messages. 2015-08-07 12:57:56 +02:00
paweldomas
c288aa6e84 Fixes issue with toggling video mute in FF caused by the fact that it has no 'onended' callback handling implemented. 2015-08-07 12:57:48 +02:00
paweldomas
e5d03d1d11 Fixes GUM failure with the latest FF nightly plus cleanup. 2015-08-07 12:57:43 +02:00
hristoterezov
59147f059d Adds a parameter to API constructor that enables film strip only mode. 2015-08-06 19:01:21 -05:00
hristoterezov
7793d65a99 Renames config.minimized to config.filmStripOnly . 2015-08-06 18:59:51 -05:00
hristoterezov
b77791f4b2 Implements minimized mode - only the thumbnails are visible. 2015-08-06 18:34:40 -05:00
damencho
4092d67853 Updates use of recording states, add some information texts and notifications. 2015-08-05 22:18:45 -05:00
paweldomas
2ea6be9b2c Updates app.bundle.js. 2015-08-05 14:12:10 +02:00
paweldomas
74e7507a73 Re-uses SSRC of the first video stream created for any streams created in future. Does video mute and switching to the screen stream without 'source-add'/'source-remove' signaling. Moves video type signaling from Jingle to MUC presence. 2015-08-05 14:10:08 +02:00
bgrozev
9a31fa3d63 Fixes a bug reported by Pawel Domas. 2015-08-04 09:26:16 -05:00
Дамян Минков
fd44cfa7a0 Typo. 2015-08-03 17:18:32 -05:00
yanas
ab570d63fa Fixes this reference. 2015-08-03 14:08:42 -05:00
Boris Grozev
b4983b2566 Merge branch 'rename-mute-to-toggle' 2015-08-03 11:46:55 -05:00
bgrozev
fdb470d22f Merge pull request #330 from jitsi/remove-rtcp-mux-from-config
Removes the useBundle and useRtcpMux options from config.js. These are
2015-08-03 11:32:43 -05:00
bgrozev
c163a22415 Merge pull request #331 from jitsi/enable-noice-reduction
Re-enables video noise reduction (removes a workaround for M37).
2015-08-03 11:32:38 -05:00
bgrozev
1dea41d3d4 Merge pull request #328 from jitsi/verify-full-jid-for-jingle
Verify full (and not bare) JID of the Jingle sender, since everyone i…
2015-08-03 11:32:32 -05:00
yanas
9d321df49e Adds javadoc for previous commit. 2015-08-03 11:21:56 -05:00
yanas
d92d8e8299 Some additional error handling. 2015-08-03 11:00:16 -05:00
bgrozev
6b48bf0d84 Merge pull request #299 from marclaporte/patch-3
Minor rewording, fixing typos, and improving grammar
2015-08-03 10:48:36 -05:00
bgrozev
de82a8e32b Merge pull request #310 from marclaporte/patch-4
fix typo
2015-08-03 10:46:49 -05:00
bgrozev
fe4661078e Update a license mention in README.md 2015-08-03 10:40:20 -05:00
bgrozev
46554f75a2 Merge pull request #313 from marclaporte/patch-6
fix typo and minor rewording
2015-08-03 10:39:27 -05:00
Boris Grozev
dbd68d2daa Updates app.bundle.js. 2015-07-31 15:26:03 -05:00
Boris Grozev
67a52e6f72 Cleanup and formatting fixes in the UI module. 2015-07-31 14:45:40 -05:00
Boris Grozev
2c790f86ad More formatting fixes. 2015-07-31 14:28:23 -05:00
Boris Grozev
fd5a739f3c Minor formatting fixes, avoid using a global variable. 2015-07-31 14:25:48 -05:00
Boris Grozev
35e46a2cfa Fix a typo. 2015-07-31 14:23:48 -05:00
Boris Grozev
24f9a1c8d1 Abstract browser type checks. 2015-07-31 14:23:36 -05:00
Boris Grozev
71229bdba9 Minor formatting fixes. 2015-07-31 14:23:27 -05:00
Boris Grozev
84a8d00234 Renames functions for the sake of clarity. 2015-07-31 14:22:42 -05:00
Boris Grozev
ee95e99f57 Cleanup: removes an unused function. 2015-07-31 14:22:32 -05:00
Boris Grozev
be6d7af377 Cleanup: fixes to style, typos and documentation. 2015-07-31 14:22:20 -05:00
paweldomas
b8548757b4 Updates app.bundle.js 2015-07-31 15:51:45 +02:00
paweldomas
df932bb89f Fixes typo in variable name(and a crash on undefined). 2015-07-31 15:50:02 +02:00
paweldomas
50e67a0658 Prevents from duplicating existing SSRCs with 'source-add' in remote description. 2015-07-31 15:49:53 +02:00
damencho
3289f6f68e Updates nginx detection during configuration. 2015-07-30 14:59:31 -05:00
damencho
a701821698 Fixes updates on distributions that are missing openjdk 8. 2015-07-30 14:36:44 -05:00
bgrozev
5588bcd167 Updates another call to setMute(). 2015-07-30 14:24:44 -05:00
bgrozev
a7058747ac Updates a call to setMute(). 2015-07-30 10:51:50 -05:00
paweldomas
5017fed28e Updates app.bundle.js. 2015-07-30 14:23:33 +02:00
paweldomas
8469a282c1 Fixes inconsistent local video muted status. 2015-07-30 14:21:32 +02:00
paweldomas
2ffe178456 Fixes issue with missing avatar for Jigasi user. Fixes issue with avatar not being displayed when user has no video. Removed duplicated logic for checking video muted status(removed from LargeVideo which exists in SmallVideo). 2015-07-30 14:21:31 +02:00
paweldomas
349f196664 "wait for remote video" loop no longer exists so no need to set 'removed' flag on video. 2015-07-30 14:21:31 +02:00
paweldomas
f2bd76ac93 Fixes issue with video thumbnail not being removed for users without camera and mic. 2015-07-30 14:21:30 +02:00
hristoterezov
baee96734c Moves LargeVideo html dependancies in LargeVideo.js 2015-07-29 14:39:09 -05:00
Boris Grozev
4cac7ac97f Re-enables video noise reduction (removes a workaround for M37). 2015-07-29 11:28:58 -05:00
Boris Grozev
46a17948d0 Renames the external API commands from "mute{Audio,Video}" to "toggle{Audio,Video}" since what they do is "toggle". 2015-07-29 11:28:37 -05:00
Boris Grozev
79ac1e800f Verify full (and not bare) JID of the Jingle sender, since everyone in the MUC has the same bare JID. 2015-07-29 11:27:12 -05:00
Boris Grozev
b0c81985d4 Removes the useBundle and useRtcpMux options from config.js. These are
now fully supported by jitsi-videobridge and all browsers which we
support (and if we need to enable them conditionally because of browser
compatibility in the future, we should do it based on run-time browser
detection.)
2015-07-29 11:26:50 -05:00
damencho
f8b7d048b0 Updates meet default installation. Removes nginx dependency and adds openjdk-8 in order to activate multiplexing inside jvb and use its jetty to serve meet. Updates will not touch current configuration which will continue using nginx. 2015-07-27 16:07:22 -05:00
paweldomas
dc19620edc Updates app.bundle 2015-07-23 11:25:39 +02:00
paweldomas
4aa6fbc4b9 Switches to the last visible video correctly when current speaker leaves the conference. 2015-07-23 11:24:13 +02:00
hristoterezov
03902de511 Merge pull request #323 from pstros/remove-ui-refs-from-xmpp-upstream
Remove UI refs from xmpp and RTC
2015-07-22 18:19:15 -05:00
Devin Wilson
d44aed2c11 Remove duplicate handler 2015-07-22 13:38:28 -06:00
Devin Wilson
9bbf17e541 Remove UI references from RTC 2015-07-22 13:37:34 -06:00
Devin Wilson
156b218bfc Remove UI references from xmpp-related modules
Conflicts:
	modules/xmpp/JingleSession.js
	service/xmpp/XMPPEvents.js
2015-07-22 13:37:34 -06:00
hristoterezov
ca5a1001a1 Fixes issue with parsing "extrernal" hash parameter. 2015-07-22 13:26:07 -05:00
paweldomas
1010a56899 Updates app.bundle.js. 2015-07-22 14:00:34 +02:00
paweldomas
0902cbb164 Fixes problems with Temasys plugin support in Safari. 2015-07-22 13:58:51 +02:00
paweldomas
9b4ee7c0c5 Fixes video switching issue. 'oldSmallVideo' field is no longer used as it is might be overwritten before fadeOut 'complete' callback of #largeVideo is called(and we miss UI update on it). 2015-07-22 13:58:39 +02:00
Boris Grozev
2545441def Updates app.bundle.js 2015-07-21 17:11:29 -05:00
Boris Grozev
9206b470ef Re-orders a=ssrc lines before calling SRD (fixes SRD failures when rtx/FID groups are in use). 2015-07-21 16:56:48 -05:00
Boris Grozev
cb7ff545b6 Abstracts unified and plan B checks. 2015-07-21 16:56:48 -05:00
bgrozev
c149b22ac2 Updates the license in package.json 2015-07-21 13:57:31 -05:00
paweldomas
2d522f735e Updates app.bundle 2015-07-21 10:17:59 +02:00
paweldomas
bc935eb5dc Fixes broken video in FF 2015-07-21 10:16:18 +02:00
paweldomas
c3548eb866 Fixes issues with invalid avatars and problems with switching between videos. 2015-07-20 19:32:04 +02:00
hristoterezov
a66459e206 Changes the conference id for callstats API. 2015-07-17 14:45:30 -05:00
Devin Wilson
829d8dab16 Fix CallStats require and initialize API commands and keyboard shortcuts in init method 2015-07-17 09:57:59 -06:00
Boris Grozev
072905b7cd Updates app.bundle.js. 2015-07-15 17:31:09 -05:00
Boris Grozev
7e1c7d19d6 Advertises RFC4588 support for browsers that support it. 2015-07-15 16:47:36 -05:00
hristoterezov
a3886cc56c Focuses the enter display name field. 2015-07-15 13:31:39 -05:00
paweldomas
b22ddea71c Updates app.bundle 2015-07-15 14:55:30 +02:00
paweldomas
b5135c455e Updates strophe.js to 1.2.2 2015-07-15 14:45:08 +02:00
paweldomas
7776bc92a4 Undefined method... 2015-07-15 14:44:16 +02:00
paweldomas
420bbe136c Fixes call to undefined method(which fixes crash on lastN switch and when clicking contact on the contactlist). 2015-07-15 14:01:36 +02:00
paweldomas
2b103288c2 Removes old hack 2015-07-15 12:16:04 +02:00
paweldomas
17f245df5e Fixes local video thumbnail being replaced with an avatar when lastN enabled. 2015-07-15 12:14:34 +02:00
paweldomas
61f4674a28 Fixes direct access to 'src' attribute of 'video' element. 2015-07-15 12:12:07 +02:00
paweldomas
879fb9a590 Adds getters for peer video selectors and simplifies the code a little bit. 2015-07-15 12:11:58 +02:00
paweldomas
a1b0677442 Moves SSRC owner signaling from MUC presence to Jingle. 2015-07-14 15:35:13 +02:00
paweldomas
d74a356a40 Removes extra separator from the toolbar when desktopsharing is not available. 2015-07-14 15:09:55 +02:00
Boris Grozev
609029bc93 Updates app.bundle.js 2015-07-10 22:40:23 +02:00
Boris Grozev
f29fc0f8e7 Fix building on a case-sensitive file system. 2015-07-10 22:39:09 +02:00
Boris Grozev
d430074ed8 Remove a superfluous var declaration. 2015-07-10 22:38:27 +02:00
paweldomas
ae759fab5b Adds IE support through Temasys WebRTC plugin. 2015-07-10 11:57:20 +02:00
ibauersachs
cd6928d770 Commit from translate.jitsi.org by user ibauersachs.: 168 of 168 strings translated (0 fuzzy). 2015-07-09 21:47:06 +00:00
ibauersachs
94f2ace120 Commit from translate.jitsi.org by user ibauersachs.: 167 of 167 strings translated (0 fuzzy). 2015-07-09 21:42:09 +00:00
paweldomas
6ec7be09f2 Fixes issue with switching to the screen stream before conference starts(when we're still alone in the room). 2015-07-09 15:04:08 +02:00
paweldomas
5c136f1da6 Updates app.bundle.js 2015-07-06 12:50:43 +02:00
paweldomas
9268da65c2 Removes 'wait for remote video' hack 2015-07-06 12:24:59 +02:00
paweldomas
3fc6da1ed5 Fixes issue with invalid number of conference participants displayed on the contact list indicator. 2015-07-06 12:24:40 +02:00
Marc Laporte
c1226d8c07 fix typo and minor rewording 2015-07-04 18:21:27 -04:00
hristoterezov
62a731e244 Fixes typo in translation module interface. 2015-07-03 12:34:05 +03:00
hristoterezov
47b6355d66 Implements functionality that forces the user to enter display name. 2015-07-02 17:04:37 +03:00
hristoterezov
1e0bf42203 Removes unused methods from VideoLayout. 2015-07-01 11:14:21 +03:00
hristoterezov
6235ff163e Fixes audio mute. 2015-06-30 14:34:11 +03:00
hristoterezov
f11c3b94ed Moves some methods from avatar to videolayout. 2015-06-29 17:24:21 +03:00
Marc Laporte
dd8371f49c fix typo 2015-06-28 09:47:13 -04:00
hristoterezov
506312ac95 Integrates callstats.io API. 2015-06-26 15:32:40 +03:00
Damian Minkov
239f271caf Handles chat message timestamps according to XEP-0091 and updates app.bundle.js. 2015-06-23 16:28:46 +03:00
Zalmoxisus
7a54537bee Fixes chat messages timestamp according to XEP-0203. Fixes #305. 2015-06-23 16:14:06 +03:00
hristoterezov
6a492d96c2 Refactors VideoLayout.js. 2015-06-23 11:00:46 +03:00
paweldomas
38b180ad81 Adds scrollbar to the contact list. 2015-06-19 15:45:39 +02:00
Emil Ivov
c8c0c8b1ab Update README.md 2015-06-19 00:53:22 +02:00
Emil Ivov
2b692f5a0a Setting new distribution license 2015-06-18 22:36:31 +02:00
Emil Ivov
502aeca132 Adding original MIT license 2015-06-18 22:36:17 +02:00
George Politis
288fbff677 Update LICENSE 2015-06-18 19:07:32 +02:00
George Politis
bdca07be17 Updates app.bundle.js 2015-06-18 19:01:04 +02:00
George Politis
5f48e4cf9d Revert "Updates the license headers."
This reverts commit 93648f361b.
2015-06-18 18:59:41 +02:00
Emil Ivov
bff9648abc Update README.md 2015-06-18 18:57:58 +02:00
Emil Ivov
7874e09a7e Update README.md 2015-06-18 18:25:21 +02:00
George Politis
311df7ec7f Updates app.bundle.js 2015-06-18 16:27:36 +02:00
George Politis
93648f361b Updates the license headers. 2015-06-18 16:17:09 +02:00
Emil Ivov
8380a7bb9d Update LICENSE 2015-06-18 13:18:23 +02:00
paweldomas
a6c8d0787a Does not reload the page after Chrome desktop sharing extension is installed. 2015-06-17 10:11:30 +02:00
paweldomas
3f9b220ee9 The name of shared Etherpad document is now generated and advertised by Jicofo user. 2015-06-15 12:07:31 +02:00
hristoterezov
15db9ca7e4 Fixes issue with the download logs button. 2015-06-11 17:03:41 +03:00
Damian Minkov
224dff7481 Updates patch. 2015-06-11 11:56:08 +03:00
Damian Minkov
643b2024c0 Reloads wrong service on remove jitsi-meet-prosody. 2015-06-11 11:55:01 +03:00
George Politis
f378d43e31 Updates app.bundle.js. 2015-06-10 13:39:11 +02:00
George Politis
23f1dc174e Refactors simulcast support. 2015-06-10 13:35:05 +02:00
Marc Laporte
0c3802183d Minor rewording, fixing typos, and improving grammar 2015-06-03 12:52:52 -04:00
105 changed files with 22787 additions and 12119 deletions

1
.gitignore vendored
View File

@@ -3,3 +3,4 @@ node_modules
.idea/
*.iml
.*.tmp
deploy-local.sh

215
LICENSE
View File

@@ -1,21 +1,202 @@
The MIT License (MIT)
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
Copyright (c) 2013 ESTOS GmbH
Copyright (c) 2013 BlueJimp SARL
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
1. Definitions.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -18,7 +18,10 @@ app:
$(NPM) update && $(BROWSERIFY) $(FLAGS) app.js -s APP -o $(OUTPUT_DIR)/app.bundle.js
clean:
@rm -f $(OUTPUT_DIR)/*.bundle.js
rm -f $(OUTPUT_DIR)/*.bundle.js
deploy:
@mkdir -p $(DEPLOY_DIR) && cp $(OUTPUT_DIR)/*.bundle.js $(DEPLOY_DIR) && ./bump-js-versions.sh
mkdir -p $(DEPLOY_DIR) && \
cp $(OUTPUT_DIR)/*.bundle.js $(DEPLOY_DIR) && \
./bump-js-versions.sh && \
([ ! -x deploy-local.sh ] || ./deploy-local.sh)

View File

@@ -1,6 +1,6 @@
Jitsi Meet - Secure, Simple and Scalable Video Conferences
====
Jitsi Meet is an open-source (MIT) WebRTC JavaScript application that uses [Jitsi Videobridge](https://jitsi.org/videobridge) to provide high quality, scalable video conferences. You can see [Jitsi Meet in action](http://youtu.be/7vFUVClsNh0) here at the 482 session of the VoIP Users Conference.
Jitsi Meet is an open-source (Apache) WebRTC JavaScript application that uses [Jitsi Videobridge](https://jitsi.org/videobridge) to provide high quality, scalable video conferences. You can see [Jitsi Meet in action](http://youtu.be/7vFUVClsNh0) here at the session #482 of the VoIP Users Conference.
You can also try it out yourself at https://meet.jit.si .
@@ -30,8 +30,23 @@ make
```
## Discuss
Please use the [Jitsi dev mailing list](http://lists.jitsi.org/pipermail/dev/) to discuss feature requests before opening an issue on github.
Please use the [Jitsi dev mailing list](http://lists.jitsi.org/pipermail/dev/) to discuss feature requests before opening an issue on Github.
## Acknowledgements
Jitsi Meet started out as a sample conferencing application using Jitsi Videobridge. It was originally developed by Philipp Hancke who then contributed it to the community where development continues with joint forces!
Jitsi Meet started out as a sample conferencing application using Jitsi Videobridge. It was originally developed by then ESTOS' developer Philipp Hancke who then contributed it to the community where development continues with joint forces!
## Miscellaneous
This project was originally contributed to the community under the MIT license and with the following notice:
The MIT License (MIT)
Copyright (c) 2013 ESTOS GmbH
Copyright (c) 2013 BlueJimp SARL
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

41
app.js
View File

@@ -9,7 +9,6 @@ var APP =
this.connectionquality = require("./modules/connectionquality/connectionquality");
this.statistics = require("./modules/statistics/statistics");
this.RTC = require("./modules/RTC/RTC");
this.simulcast = require("./modules/simulcast/simulcast");
this.desktopsharing = require("./modules/desktopsharing/desktopsharing");
this.xmpp = require("./modules/xmpp/xmpp");
this.keyboardshortcut = require("./modules/keyboardshortcut/keyboardshortcut");
@@ -17,28 +16,54 @@ var APP =
this.settings = require("./modules/settings/Settings");
this.DTMF = require("./modules/DTMF/DTMF");
this.members = require("./modules/members/MemberList");
this.configFetch = require("./modules/config/HttpConfigFetch");
}
};
function init() {
APP.desktopsharing.init();
APP.RTC.start();
APP.xmpp.start();
APP.statistics.start();
APP.connectionquality.init();
// Set default desktop sharing method
APP.desktopsharing.init();
APP.keyboardshortcut.init();
APP.members.start();
}
/**
* If we have HTTP endpoint for getting confgi.json configured we're going to
* read it and override properties from config.js and interfaceConfig.js.
* If there is no endpoint we'll just continue with initialization.
* Keep in mind that if the endpoint has been configured and we fail to obtain
* the config for any reason then the conference won't start and error message
* will be displayed to the user.
*/
function obtainConfigAndInit() {
if (config.configLocation) {
APP.configFetch.obtainConfig(
config.configLocation, APP.UI.getRoomNode(),
// Get config result callback
function(success, error) {
if (success) {
init();
} else {
// Show obtain config error,
// pass the error object for report
APP.UI.messageHandler.openReportDialog(
null, "dialog.connectError", error);
}
});
} else {
init();
}
}
$(document).ready(function () {
var URLPRocessor = require("./modules/URLProcessor/URLProcessor");
URLPRocessor.setConfigParametersFromUrl();
var URLProcessor = require("./modules/config/URLProcessor");
URLProcessor.setConfigParametersFromUrl();
APP.init();
APP.translation.init();
@@ -46,7 +71,7 @@ $(document).ready(function () {
if(APP.API.isEnabled())
APP.API.init();
APP.UI.start(init);
APP.UI.start(obtainConfigAndInit);
});

View File

@@ -1,4 +1,5 @@
var config = {
// configLocation: './config.json', // see ./modules/HttpConfigFetch.js
hosts: {
domain: 'jitsi-meet.example.com',
//anonymousdomain: 'guest.example.com',
@@ -26,15 +27,17 @@ var config = {
channelLastN: -1, // The default value of the channel attribute last-n.
adaptiveLastN: false,
adaptiveSimulcast: false,
useRtcpMux: true, // required for FF support
useBundle: true, // required for FF support
enableRecording: false,
enableWelcomePage: true,
enableSimulcast: false, // blocks FF support
logStats: false, // Enable logging of PeerConnection stats via the focus
// requireDisplayName: true,//Forces the participants that doesn't have display name to enter it when they enter the room.
// startAudioMuted: 10, //every participant after the Nth will start audio muted
// startVideoMuted: 10, //every participant after the Nth will start video muted
// defaultLanguage: "en",
// To enable sending statistics to callstats.io you should provide Applicaiton ID and Secret.
// callStatsID: "",//Application ID for callstats.io API
// callStatsSecret: ""//Secret for callstats.io API
/*noticeMessage: 'Service update is scheduled for 16th March 2015. ' +
'During that time service will not be available. ' +
'Apologise for inconvenience.'*/

View File

@@ -3,12 +3,27 @@
cursor: default;
}
#contactlist>ul {
margin: 0px;
padding: 0px;
#contactlist>div.title {
text-align: left;
padding: 7px 10px;
margin: 2px;
color: #00ccff;
font-size: 11pt;
border-bottom: 1px solid #676767;
}
#contactlist>ul>li {
#contactlist>ul#contacts {
position: absolute;
top: 31px;
bottom: 0px;
width: 100%;
margin: 0px;
padding: 0px;
overflow-y: scroll;
overflow-x: hidden;
}
#contacts>li {
list-style-type: none;
text-align: left;
color: #FFF;
@@ -17,18 +32,12 @@
margin: 2px;
}
#contactlist>ul>li>p {
#contacts>li>p {
display: inline-block;
vertical-align: middle;
margin: 0px;
}
#contactlist>ul>li.title {
color: #00ccff;
font-size: 11pt;
border-bottom: 1px solid #676767;
}
.avatar {
padding: 0px;
margin-right: 10px;

View File

@@ -38,17 +38,30 @@ html, body{
position: relative;
}
#toolbar a.button::after {
content: '';
display: inline-block;
position: absolute;
left: 40px;
width: 1px;
height: 20px;
background: #373737;
}
#toolbar a.button:last-child::after {
content: none;
}
.button {
display: inline-block;
position: relative;
color: #FFFFFF;
top: 0px;
padding: 10px 0px;
top: 0;
padding: 10px 0;
width: 38px;
cursor: pointer;
font-size: 11pt;
text-align: center;
text-shadow: 0px 1px 0px rgba(255,255,255,.3), 0px -1px 0px rgba(0,0,0,.6);
text-shadow: 0 1px 0 rgba(255,255,255,.3), 0 -1px 0 rgba(0,0,0,.6);
z-index: 1;
}
@@ -61,13 +74,13 @@ html, body{
cursor: pointer;
}
#chatButton, #chatBottomButton, #contactListButton, #numberOfParticipants {
#toolbar_button_chat, #chatBottomButton, #contactListButton, #numberOfParticipants {
-webkit-transition: all .5s ease-in-out;
-moz-transition: all .5s ease-in-out;
transition: all .5s ease-in-out;
}
/*#ffde00*/
#chatButton.active, #contactListButton.glowing, #chatBottomButton.glowing {
#toolbar_button_chat.active, #contactListButton.glowing, #chatBottomButton.glowing {
-webkit-text-shadow: -1px 0 10px #00ccff,
0 1px 10px #00ccff,
1px 0 10px #00ccff,
@@ -82,6 +95,11 @@ html, body{
0 -1px 10px #00ccff;
}
#toolbar_button_hangup {
color: #ff0000;
font-size: 1.4em;
}
#numberOfParticipants {
position: absolute;
top: 0px;
@@ -100,13 +118,13 @@ html, body{
color: #00ccff;
}
#recordButton {
#toolbar_button_record {
-webkit-transition: all .5s ease-in-out;
-moz-transition: all .5s ease-in-out;
transition: all .5s ease-in-out;
}
/*#ffde00*/
#recordButton.active {
#toolbar_button_record.active {
-webkit-text-shadow: -1px 0 10px #00ccff,
0 1px 10px #00ccff,
1px 0 10px #00ccff,
@@ -155,6 +173,8 @@ a.bottomToolbarButton:hover {
}
input[type='text'], input[type='password'], textarea {
-webkit-user-select: text;
user-select: text;
display: inline-block;
font-size: 14px;
padding: 5px;

View File

@@ -60,28 +60,31 @@
}
#remoteVideos .videocontainer:hover {
-webkit-box-shadow: inset 0 0 10px #FFFFFF, 0 0 10px #FFFFFF;
box-shadow: inset 0 0 10px #FFFFFF, 0 0 10px #FFFFFF;
border: 2px solid #FFFFFF;
}
#remoteVideos .videocontainer.videoContainerFocused {
-webkit-box-shadow: inset 0 0 28px #006d91;
box-shadow: inset 0 0 28px #006d91;
border: 2px solid #006d91;
}
#remoteVideos .videocontainer.videoContainerFocused:hover {
-webkit-box-shadow: inset 0 0 5px #FFFFFF, 0 0 10px #FFFFFF, inset 0 0 60px #006d91;
box-shadow: inset 0 0 5px #FFFFFF, 0 0 10px #FFFFFF, inset 0 0 60px #006d91;
border: 2px solid #FFFFFF;
}
#localVideoWrapper {
display:inline-block;
-webkit-mask-box-image: url(../images/videomask.svg);
border-radius:0px !important;
border-radius:4px !important;
border: 0px !important;
}
#remoteVideos .videocontainer>video {
/* With TemasysWebRTC plugin <object/> element is used
instead of <video/> */
#remoteVideos .videocontainer>video,
#remoteVideos .videocontainer>object {
border-radius:4px;
}
@@ -92,17 +95,20 @@
-o-transform: scale(-1, 1);
}
#localVideoWrapper>video {
border-radius:0px !important;
#localVideoWrapper>video,
#localVideoWrapper>object {
border-radius:4px !important;
}
#largeVideo,
#largeVideoWrapper,
#largeVideoContainer {
overflow: hidden;
text-align: center;
}
#largeVideo
#largeVideo,
#largeVideoWrapper
{
object-fit: cover;
}
@@ -110,8 +116,13 @@
#presentation,
#etherpad,
#localVideoWrapper>video,
#localVideoWrapper>object,
#localVideoWrapper,
.videocontainer>video {
#largeVideoWrapper,
#largeVideoWrapper>video,
#largeVideoWrapper>object,
.videocontainer>video,
.videocontainer>object {
position: absolute;
left: 0;
top: 0;
@@ -129,7 +140,7 @@
z-index: 0;
}
#etherpadButton {
#toolbar_button_etherpad {
display: none;
}
@@ -363,7 +374,7 @@
margin-right: 40%;
text-align: center;
background: linear-gradient(to bottom, rgba(255,255,255,.85) , rgba(255,255,255,.35));
-webkit-box-shadow: 0 0 2px #000000, 0 0 10px #000000;
box-shadow: 0 0 2px #000000, 0 0 10px #000000;
border-bottom-left-radius: 12px;
border-bottom-right-radius: 12px;
display: none;
@@ -440,3 +451,29 @@
background-position: center;
}
.videoMessageFilter {
-webkit-filter: grayscale(.5) opacity(0.8);
filter: grayscale(.5) opacity(0.8);
}
.videoProblemFilter {
-webkit-filter: blur(10px) grayscale(.5) opacity(0.8);
filter: blur(10px) grayscale(.5) opacity(0.8);
}
#videoConnectionMessage {
display: none;
position: absolute;
width: 100%;
top:50%;
z-index: 10000;
font-weight: 600;
font-size: 14px;
text-align: center;
color: #FFF;
opacity: .80;
text-shadow: 0px 0px 1px rgba(0,0,0,0.3),
0px 1px 1px rgba(0,0,0,0.3),
1px 0px 1px rgba(0,0,0,0.3),
0px 0px 1px rgba(0,0,0,0.3);
}

10
debian/control vendored
View File

@@ -10,14 +10,15 @@ Homepage: https://jitsi.org/meet
Package: jitsi-meet
Architecture: all
Depends: ${misc:Depends}, jitsi-videobridge, nginx, jitsi-meet-prosody, libjs-strophe (>= 1.1.3),
libjs-jquery, libjs-jquery-ui
Depends: ${misc:Depends}, jitsi-videobridge, jitsi-meet-prosody, libjs-strophe (>= 1.1.3),
libjs-jquery, libjs-jquery-ui, openjdk-8-jre-headless | nginx
Description: WebRTC JavaScript video conferences
Jitsi Meet is a WebRTC JavaScript application that uses Jitsi
Videobridge to provide high quality, scalable video conferences.
.
It is a web interface to Jitsi Videobridge for audio and video
forwarding and relaying, configured to work with nginx
forwarding and relaying, configured to work with jetty instance
running embedded into Jitsi Videobridge
Package: jitsi-meet-prosody
Architecture: all
@@ -27,7 +28,8 @@ Description: Prosody configuration for Jitsi Meet
Videobridge to provide high quality, scalable video conferences.
.
It is a web interface to Jitsi Videobridge for audio and video
forwarding and relaying, configured to work with nginx
forwarding and relaying, configured to work with jetty instance
running embedded into Jitsi Videobridge
.
This package contains configuration for Prosody to be used with
Jitsi Meet.

View File

@@ -25,7 +25,7 @@ set -e
case "$1" in
remove)
if [ -x "/etc/init.d/prosody" ]; then
invoke-rc.d nginx reload
invoke-rc.d prosody reload
fi
;;

View File

@@ -1,5 +1,4 @@
Template: jitsi-meet-prosody/jvb-hostname
Type: string
Default: ${default-key}
_Description: The hostname of the current installation:
The value for the hostname that is set in Jitsi Videobridge installation.

View File

@@ -1,4 +1,4 @@
#!/bin/sh
#!/bin/bash
# postinst script for jitsi-meet
#
# see: dh_installdeb(1)
@@ -20,67 +20,159 @@ set -e
case "$1" in
configure)
. /etc/jitsi/videobridge/config
JVB_ETC_CONFIG="/etc/jitsi/videobridge/config"
JVB_CONFIG="/usr/share/jitsi-videobridge/.sip-communicator/sip-communicator.properties"
. $JVB_ETC_CONFIG
# loading debconf
. /usr/share/debconf/confmodule
# detect dpkg-reconfigure, just delete old links
# detect dpkg-reconfigure
RECONFIGURING="false"
db_get jitsi-meet/jvb-hostname
JVB_HOSTNAME_OLD=$RET
if [ -n "$RET" ] && [ ! "$JVB_HOSTNAME_OLD" = "$JVB_HOSTNAME" ] ; then
rm -f /etc/nginx/sites-enabled/$JVB_HOSTNAME_OLD.conf
RECONFIGURING="true"
rm -f /etc/jitsi/meet/$JVB_HOSTNAME_OLD-config.js
fi
JVB_SERVE="false"
db_get jitsi-meet/jvb-serve
if [ -n "$RET" ] && [ "$RET" = "true" ] ; then
JVB_SERVE="true"
fi
# stores the hostname so we will reuse it later, like in purge
db_set jitsi-meet/jvb-hostname $JVB_HOSTNAME
# nginx conf
if [ ! -f /etc/nginx/sites-available/$JVB_HOSTNAME.conf ]; then
cp /usr/share/doc/jitsi-meet/jitsi-meet.example /etc/nginx/sites-available/$JVB_HOSTNAME.conf
if [ ! -f /etc/nginx/sites-enabled/$JVB_HOSTNAME.conf ]; then
ln -s /etc/nginx/sites-available/$JVB_HOSTNAME.conf /etc/nginx/sites-enabled/$JVB_HOSTNAME.conf
fi
sed -i "s/jitsi-meet.example.com/$JVB_HOSTNAME/g" /etc/nginx/sites-available/$JVB_HOSTNAME.conf
NGINX_INSTALL_CHECK="$(dpkg-query -W -f '${PackageSpec}:${Status}\n' nginx 2>&1 | grep -v "ok installed" || :)"
if [ -z "${NGINX_INSTALL_CHECK}" ]; then
FORCE_NGINX="true"
fi
# SSL for nginx
db_get jitsi-meet/cert-choice
CERT_CHOICE="$RET"
if [ "$CERT_CHOICE" = 'A certificate is available and the files are uploaded on the server' ]; then
db_set jitsi-meet/cert-path-key "/etc/ssl/$JVB_HOSTNAME.key"
db_input critical jitsi-meet/cert-path-key || true
db_go
db_get jitsi-meet/cert-path-key
CERT_KEY="$RET"
db_set jitsi-meet/cert-path-crt "/etc/ssl/$JVB_HOSTNAME.crt"
db_input critical jitsi-meet/cert-path-crt || true
db_go
db_get jitsi-meet/cert-path-crt
CERT_CRT="$RET"
# replace self-signed certificate paths with user provided ones
CERT_KEY_ESC=$(echo $CERT_KEY | sed 's/\./\\\./g')
CERT_KEY_ESC=$(echo $CERT_KEY_ESC | sed 's/\//\\\//g')
sed -i "s/ssl_certificate_key\ \/var\/lib\/prosody\/.*key/ssl_certificate_key\ $CERT_KEY_ESC/g" \
/etc/nginx/sites-available/$JVB_HOSTNAME.conf
CERT_CRT_ESC=$(echo $CERT_CRT | sed 's/\./\\\./g')
CERT_CRT_ESC=$(echo $CERT_CRT_ESC | sed 's/\//\\\//g')
sed -i "s/ssl_certificate\ \/var\/lib\/prosody\/.*crt/ssl_certificate\ $CERT_CRT_ESC/g" \
/etc/nginx/sites-available/$JVB_HOSTNAME.conf
fi
UPLOADED_CERT_CHOICE="A certificate is available and the files are uploaded on the server"
# jitsi meet
JITSI_MEET_CONFIG="/etc/jitsi/meet/$JVB_HOSTNAME-config.js"
if [ ! -f $JITSI_MEET_CONFIG ]; then
if [ ! -f $JITSI_MEET_CONFIG ] ; then
cp /usr/share/doc/jitsi-meet/config.js $JITSI_MEET_CONFIG
sed -i "s/jitsi-meet.example.com/$JVB_HOSTNAME/g" $JITSI_MEET_CONFIG
fi
# this is new install let's configure jvb to serve meet
if [[ -z $FORCE_NGINX && ( -z $JVB_HOSTNAME_OLD || "$JVB_SERVE" = "true" ) ]] ; then
# this is a reconfigure, lets just delete old links
if [ "$RECONFIGURING" = "true" ] ; then
rm -f $JVB_CONFIG
fi
# configure jvb
echo "AUTHBIND=yes" >> $JVB_ETC_CONFIG
sed -i "s/JVB_OPTS=.*/JVB_OPTS=--apis=rest,xmpp/g" $JVB_ETC_CONFIG
echo "org.jitsi.videobridge.rest.jetty.host=::" >> $JVB_CONFIG
echo "org.jitsi.videobridge.rest.jetty.port=443" >> $JVB_CONFIG
echo "org.jitsi.videobridge.rest.jetty.ProxyServlet.hostHeader=$JVB_HOSTNAME" >> $JVB_CONFIG
echo "org.jitsi.videobridge.rest.jetty.ProxyServlet.pathSpec=/http-bind" >> $JVB_CONFIG
echo "org.jitsi.videobridge.rest.jetty.ProxyServlet.proxyTo=http://localhost:5280/http-bind" >> $JVB_CONFIG
echo "org.jitsi.videobridge.rest.jetty.ResourceHandler.resourceBase=/usr/share/jitsi-meet" >> $JVB_CONFIG
echo "org.jitsi.videobridge.rest.jetty.ResourceHandler.alias./config.js=/etc/jitsi/meet/$JVB_HOSTNAME-config.js" >> $JVB_CONFIG
echo "org.jitsi.videobridge.rest.jetty.RewriteHandler.regex=^/([a-zA-Z0-9]+)$" >> $JVB_CONFIG
echo "org.jitsi.videobridge.rest.jetty.RewriteHandler.replacement=/" >> $JVB_CONFIG
echo "org.jitsi.videobridge.rest.jetty.tls.port=443" >> $JVB_CONFIG
echo "org.jitsi.videobridge.TCP_HARVESTER_PORT=443" >> $JVB_CONFIG
echo "org.jitsi.videobridge.rest.jetty.sslContextFactory.keyStorePath=/etc/jitsi/videobridge/$JVB_HOSTNAME.jks" >> $JVB_CONFIG
echo "org.jitsi.videobridge.rest.jetty.sslContextFactory.keyStorePassword=changeit" >> $JVB_CONFIG
# configure authbind to allow jvb to bind to privileged ports
OWNER=$(stat -c '%U' /usr/share/jitsi-videobridge)
GROUP=$(stat -c '%G' /usr/share/jitsi-videobridge)
JVB_UID="`id -u $OWNER`"
if [ ! -f "/etc/authbind/byuid/$JVB_UID" ] ; then
if [ ! -d "/etc/authbind/byuid" ] ; then
mkdir -p /etc/authbind/byuid
chmod 755 /etc/authbind
chmod 755 /etc/authbind/byuid
fi
echo '::,443' >/etc/authbind/byuid/$JVB_UID
chown $OWNER:$GROUP /etc/authbind/byuid/$JVB_UID
chmod 700 /etc/authbind/byuid/$JVB_UID
fi
if [ "$CERT_CHOICE" = "$UPLOADED_CERT_CHOICE" ] ; then
# create jks from uploaded certs
openssl pkcs12 -export \
-in /etc/ssl/$JVB_HOSTNAME.crt \
-inkey /etc/ssl/$JVB_HOSTNAME.key \
-passout pass:changeit > /etc/jitsi/videobridge/$JVB_HOSTNAME.p12
keytool -importkeystore \
-srckeystore /etc/jitsi/videobridge/$JVB_HOSTNAME.p12 \
-destkeystore /etc/jitsi/videobridge/$JVB_HOSTNAME.jks \
-srcstoretype pkcs12 \
-noprompt -storepass changeit -srcstorepass changeit
else
# create jks from self-signed certs
openssl pkcs12 -export \
-in /var/lib/prosody/$JVB_HOSTNAME.crt \
-inkey /var/lib/prosody/$JVB_HOSTNAME.key \
-passout pass:changeit > /etc/jitsi/videobridge/$JVB_HOSTNAME.p12
keytool -importkeystore \
-srckeystore /etc/jitsi/videobridge/$JVB_HOSTNAME.p12 \
-destkeystore /etc/jitsi/videobridge/$JVB_HOSTNAME.jks \
-srcstoretype pkcs12 \
-noprompt -storepass changeit -srcstorepass changeit
fi
db_set jitsi-meet/jvb-serve "true"
invoke-rc.d jitsi-videobridge restart
elif [[ "$FORCE_NGINX" = "true" || ( -n $JVB_HOSTNAME_OLD && "$JVB_SERVE" = "false" ) ]] ; then
# this is a reconfigure, lets just delete old links
if [ "$RECONFIGURING" = "true" ] ; then
rm -f /etc/nginx/sites-enabled/$JVB_HOSTNAME_OLD.conf
rm -f /etc/jitsi/meet/$JVB_HOSTNAME_OLD-config.js
fi
# nginx conf
if [ ! -f /etc/nginx/sites-available/$JVB_HOSTNAME.conf ] ; then
cp /usr/share/doc/jitsi-meet/jitsi-meet.example /etc/nginx/sites-available/$JVB_HOSTNAME.conf
if [ ! -f /etc/nginx/sites-enabled/$JVB_HOSTNAME.conf ] ; then
ln -s /etc/nginx/sites-available/$JVB_HOSTNAME.conf /etc/nginx/sites-enabled/$JVB_HOSTNAME.conf
fi
sed -i "s/jitsi-meet.example.com/$JVB_HOSTNAME/g" /etc/nginx/sites-available/$JVB_HOSTNAME.conf
fi
if [ "$CERT_CHOICE" = "$UPLOADED_CERT_CHOICE" ] ; then
db_set jitsi-meet/cert-path-key "/etc/ssl/$JVB_HOSTNAME.key"
db_input critical jitsi-meet/cert-path-key || true
db_go
db_get jitsi-meet/cert-path-key
CERT_KEY="$RET"
db_set jitsi-meet/cert-path-crt "/etc/ssl/$JVB_HOSTNAME.crt"
db_input critical jitsi-meet/cert-path-crt || true
db_go
db_get jitsi-meet/cert-path-crt
CERT_CRT="$RET"
# replace self-signed certificate paths with user provided ones
CERT_KEY_ESC=$(echo $CERT_KEY | sed 's/\./\\\./g')
CERT_KEY_ESC=$(echo $CERT_KEY_ESC | sed 's/\//\\\//g')
sed -i "s/ssl_certificate_key\ \/var\/lib\/prosody\/.*key/ssl_certificate_key\ $CERT_KEY_ESC/g" \
/etc/nginx/sites-available/$JVB_HOSTNAME.conf
CERT_CRT_ESC=$(echo $CERT_CRT | sed 's/\./\\\./g')
CERT_CRT_ESC=$(echo $CERT_CRT_ESC | sed 's/\//\\\//g')
sed -i "s/ssl_certificate\ \/var\/lib\/prosody\/.*crt/ssl_certificate\ $CERT_CRT_ESC/g" \
/etc/nginx/sites-available/$JVB_HOSTNAME.conf
fi
invoke-rc.d nginx reload
fi
# and we're done with debconf
db_stop
invoke-rc.d nginx reload
;;
abort-upgrade|abort-remove|abort-deconfigure)

View File

@@ -10,20 +10,23 @@ _Description: SSL certificate for the Jitsi Meet instance
Template: jitsi-meet/cert-path-key
Type: string
Default: ${default-key}
_Description: Full local server path to the SSL key file:
The full path to the SSL key file on the server.
If it has not been uploaded, now is a good time to do so.
Template: jitsi-meet/cert-path-crt
Type: string
Default: ${default-crt}
_Description: Full local server path to the SSL certificate file:
The full path to the SSL certificate file on the server.
If you haven't uploaded it, now is a good time to upload it in another console.
Template: jitsi-meet/jvb-hostname
Type: string
Default: ${default-key}
_Description: The hostname of the current installation:
The value for the hostname that is set in Jitsi Videobridge installation.
Template: jitsi-meet/jvb-serve
Type: boolean
Default: false
_Description: for internal use
for internal use.

View File

@@ -3,14 +3,16 @@ Index: jitsi-meet/index.html
===================================================================
--- jitsi-meet.orig/index.html
+++ jitsi-meet/index.html
@@ -9,12 +9,12 @@
<meta itemprop="name" content="Jitsi Meet"/>
@@ -10,14 +10,14 @@
<meta itemprop="description" content="Join a WebRTC video conference powered by the Jitsi Videobridge"/>
<meta itemprop="image" content="/images/jitsilogo.png"/>
<script src="https://api.callstats.io/static/callstats.min.js"></script>
- <script src="libs/jquery-2.1.1.min.js"></script>
+ <script src="libs/jquery.min.js"></script>
<script src="config.js?v=6"></script><!-- adapt to your needs, i.e. set hosts and bosh path -->
<script src="libs/strophe/strophe.min.js?v=1"></script>
<script src="https://cdn.socket.io/socket.io-1.2.0.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jsSHA/1.5.0/sha.js"></script>
<script src="config.js?v=11"></script><!-- adapt to your needs, i.e. set hosts and bosh path -->
<script src="libs/strophe/strophe.min.js?v=2"></script>
<script src="libs/strophe/strophe.disco.min.js?v=1"></script>
<script src="libs/strophe/strophe.caps.jsonly.min.js?v=1"></script>
- <script src="libs/jquery-ui.js"></script>

View File

@@ -1,7 +1,3 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
@@ -74,7 +70,6 @@ msgid ""
"uploaded it, now is a good time to upload it in another console."
msgstr ""
#. Type: string
#. Description
#: ../jitsi-meet.templates:4001
@@ -87,3 +82,17 @@ msgstr ""
msgid ""
"The value for the hostname that is set in Jitsi Videobridge installation."
msgstr ""
#. Type: string
#. Description
#: ../jitsi-meet.templates:5001
msgid "for internal use"
msgstr ""
#. Type: string
#. Description
#: ../jitsi-meet.templates:5001
msgid ""
"Jitsi Videobridge installation can use its internal jetty to serve static meet pages."
msgstr ""

View File

@@ -29,6 +29,13 @@ constructor.
```
If you don't specify room the user will enter in new conference with random room name.
You can overwrite options set in config.js and interface_config.js. For example, to enable the film-strip-only interface mode and disable simulcast, you can use:
```javascript
var configOverwrite = {enableSimulcast: false};
var interfaceConfigOverwrite = {filmStripOnly: true};
var api = new JitsiMeetExternalAPI(domain, room, width, height, htmlElement, true, configOverwrite, interfaceConfigOverwrite);
```
Controlling embedded Jitsi Meet Conference
=========
@@ -49,13 +56,13 @@ the new display name to be set
```
api.executeCommand('displayName', ['New Nickname']);
```
* **muteAudio** - mutes / unmutes the audio for the local participant. No arguments are required.
* **toggleAudio** - mutes / unmutes the audio for the local participant. No arguments are required.
```
api.executeCommand('muteAudio', [])
api.executeCommand('toggleAudio', [])
```
* **muteVideo** - mutes / unmutes the video for the local participant. No arguments are required.
* **toggleVideo** - mutes / unmutes the video for the local participant. No arguments are required.
```
api.executeCommand('muteVideo', [])
api.executeCommand('toggleVideo', [])
```
* **toggleFilmStrip** - hides / shows the film strip. No arguments are required.
```
@@ -78,7 +85,7 @@ The ```commands``` parameter is object with keys the names of the commands and v
commands.
```
api.executeCommands({displayName: ['nickname'], muteAudio: []});
api.executeCommands({displayName: ['nickname'], toggleAudio: []});
```
You can add event listeners to the embedded Jitsi Meet using ```addEventListener``` method.
@@ -166,4 +173,4 @@ You can remove the embedded Jitsi Meet Conference with the following code:
api.dispose()
```
It is a good practice to remove the conference before the page is unloaded.
It is a good practice to remove the conference before the page is unloaded.

View File

@@ -150,7 +150,7 @@ ant dist.{os-name}
Run jicofo:
```sh
cd dist/{os-name}'
./jicofo.sh --domain=jitsi.exmaple.com --secret=YOURSECRET2 --user_domain=auth.jitsi.example.com --user_name=focus --user_password=YOURSECRET3
./jicofo.sh --domain=jitsi.example.com --secret=YOURSECRET2 --user_domain=auth.jitsi.example.com --user_name=focus --user_password=YOURSECRET3
```
## Deploy Jitsi Meet
@@ -220,4 +220,4 @@ enableRecording: true
Restart jitsi-videobridge and start a new conference (making sure that the page
is reloaded with the new config.js) -- the organizer of the conference should
now have a "recoriding" button in the floating menu, near the "mute" button.
now have a "recording" button in the floating menu, near the "mute" button.

View File

@@ -1,6 +1,6 @@
# Jitsi Meet quick install
This documents decribes the needed steps for quick Jitsi Meet installation on a Debian based GNU/Linux system.
This document describes the required steps for a quick Jitsi Meet installation on a Debian based GNU/Linux system.
N.B.: All commands are supposed to be run by root. If you are logged in as a regular user with sudo rights, please prepend ___sudo___ to each of the commands.
@@ -24,13 +24,13 @@ apt-get update
apt-get -y install jitsi-meet
```
During the installation you'll be asked to enter the hostname of the Jitsi Meet instance. If you have a FQDN hostname for the instance already set ip in DNS, enter it there. If you don't have a resolvable hostname, you can enter the IP address of the machine (if it is static or doesn't change).
During the installation, you will be asked to enter the hostname of the Jitsi Meet instance. If you have a FQDN hostname for the instance already set up in DNS, enter it there. If you don't have a resolvable hostname, you can enter the IP address of the machine (if it is static or doesn't change).
This hostname (or IP address) will be used for virtualhost configuration inside the Jitsi Meet and also you and your correspondents will be using it to access the web conferences.
This hostname (or IP address) will be used for virtualhost configuration inside the Jitsi Meet and also, you and your correspondents will be using it to access the web conferences.
### Open a conference
Launch a web broswer (Chrome, Chromium or latest Opera) and enter in the URL bar the hostname (or IP address) you used in the previous step.
Launch a web browser (Chrome, Chromium or latest Opera) and enter in the URL bar the hostname (or IP address) you used in the previous step.
Confirm that you trust the self-signed certificate of the newly installed Jitsi Meet.
@@ -50,7 +50,7 @@ wget https://download.jitsi.org/jigasi_1.0-1_amd64.deb
dpkg -i jigasi_1.0-1_amd64.deb
```
During the installation you'll be asked to enter your SIP account and password. This account will be used to invite the other SIP participants.
During the installation, you will be asked to enter your SIP account and password. This account will be used to invite the other SIP participants.
### Reload Jitsi Meet
@@ -58,19 +58,17 @@ Launch again a browser with the Jitsi Meet URL and you'll see a telephone icon o
Enjoy!
## Deinstall
## Uninstall
```sh
apt-get purge jigasi jitsi-meet jicofo jitsi-videobridge
```
Somethimes the following packages will fail to uninstall properly:
Sometimes the following packages will fail to uninstall properly:
- jigasi
- jitsi-videobridge
When this happens, just run the deinstall command a second time and it should be ok.
When this happens, just run the uninstall command a second time and it should be ok.
The reason for failure is that not allways the daemons are stopped right away, there is a timeout before the actual stop. And if the unistall script goes on before the services' stop, there is an error.
The second run of the deinstall command fixes this, as by then the jigasi or jvb daemons are already stopped.
The reason for failure is that sometimes, the uninstall script is faster than the process that stops the daemons. The second run of the uninstall command fixes this, as by then the jigasi or jvb daemons are already stopped.

View File

@@ -23,17 +23,21 @@ var JitsiMeetExternalAPI = (function()
* @param width width of the iframe
* @param height height of the iframe
* @param parent_node the node that will contain the iframe
* @param filmStripOnly if the value is true only the small videos will be
* visible.
* @constructor
*/
function JitsiMeetExternalAPI(domain, room_name, width, height, parent_node)
{
function JitsiMeetExternalAPI(domain, room_name, width, height, parentNode,
configOverwrite, interfaceConfigOverwrite) {
if((!width || width < MIN_WIDTH) && !filmStripOnly)
width = MIN_WIDTH;
if((!height || height < MIN_HEIGHT) && !filmStripOnly)
height = MIN_HEIGHT;
this.parentNode = null;
if(parent_node)
{
this.parentNode = parent_node;
}
else
{
if (parentNode) {
this.parentNode = parentNode;
} else {
var scriptTag = document.scripts[document.scripts.length - 1];
this.parentNode = scriptTag.parentNode;
}
@@ -41,17 +45,35 @@ var JitsiMeetExternalAPI = (function()
this.iframeHolder =
this.parentNode.appendChild(document.createElement("div"));
this.iframeHolder.id = "jitsiConference" + JitsiMeetExternalAPI.id;
if(width < MIN_WIDTH)
width = MIN_WIDTH;
if(height < MIN_HEIGHT)
height = MIN_HEIGHT;
this.iframeHolder.style.width = width + "px";
this.iframeHolder.style.height = height + "px";
if(width)
this.iframeHolder.style.width = width + "px";
if(height)
this.iframeHolder.style.height = height + "px";
this.frameName = "jitsiConferenceFrame" + JitsiMeetExternalAPI.id;
this.url = "//" + domain + "/";
if(room_name)
this.url += room_name;
this.url += "#external";
this.url += "#external=true";
var key;
if (configOverwrite) {
for (key in configOverwrite) {
if (!configOverwrite.hasOwnProperty(key) ||
typeof key !== 'string')
continue;
this.url += "&config." + key + "=" + configOverwrite[key];
}
}
if (interfaceConfigOverwrite) {
for (key in interfaceConfigOverwrite) {
if (!interfaceConfigOverwrite.hasOwnProperty(key) ||
typeof key !== 'string')
continue;
this.url += "&interfaceConfig." + key + "=" + interfaceConfigOverwrite[key];
}
}
JitsiMeetExternalAPI.id++;
this.frame = document.createElement("iframe");
@@ -80,15 +102,12 @@ var JitsiMeetExternalAPI = (function()
* Sends the passed object to Jitsi Meet
* @param object the object to be sent
*/
JitsiMeetExternalAPI.prototype.sendMessage = function(object)
{
if(this.frameLoaded)
{
JitsiMeetExternalAPI.prototype.sendMessage = function(object) {
if (this.frameLoaded) {
this.frame.contentWindow.postMessage(
JSON.stringify(object), this.frame.src);
}
else
{
else {
this.initialCommands.push(object);
}
@@ -98,8 +117,8 @@ var JitsiMeetExternalAPI = (function()
* Executes command. The available commands are:
* displayName - sets the display name of the local participant to the value
* passed in the arguments array.
* muteAudio - mutes / unmutes audio with no arguments
* muteVideo - mutes / unmutes video with no arguments
* toggleAudio - mutes / unmutes audio with no arguments
* toggleVideo - mutes / unmutes video with no arguments
* filmStrip - hides / shows the film strip with no arguments
* If the command doesn't require any arguments the parameter should be set
* to empty array or it may be omitted.
@@ -107,10 +126,9 @@ var JitsiMeetExternalAPI = (function()
* @param arguments array of arguments
*/
JitsiMeetExternalAPI.prototype.executeCommand = function(name,
argumentsList)
{
argumentsList) {
var argumentsArray = argumentsList;
if(!argumentsArray)
if (!argumentsArray)
argumentsArray = [];
var object = {type: "command", action: "execute"};
object[name] = argumentsArray;
@@ -121,8 +139,8 @@ var JitsiMeetExternalAPI = (function()
* Executes commands. The available commands are:
* displayName - sets the display name of the local participant to the value
* passed in the arguments array.
* muteAudio - mutes / unmutes audio with no arguments
* muteVideo - mutes / unmutes video with no arguments
* toggleAudio - mutes / unmutes audio with no arguments
* toggleVideo - mutes / unmutes video with no arguments
* filmStrip - hides / shows the film strip with no arguments
* @param object the object with commands to be executed. The keys of the
* object are the commands that will be executed and the values are the
@@ -135,8 +153,8 @@ var JitsiMeetExternalAPI = (function()
};
/**
* Adds event listeners to Meet Jitsi. The object key should be the name of the
* event and value - the listener.
* Adds event listeners to Meet Jitsi. The object key should be the name of
* the event and value - the listener.
* Currently we support the following
* events:
* incomingMessage - receives event notifications about incoming
@@ -170,8 +188,7 @@ var JitsiMeetExternalAPI = (function()
* @param object
*/
JitsiMeetExternalAPI.prototype.addEventListeners
= function (object)
{
= function (object) {
var message = {type: "event", action: "add", events: []};
for(var i in object)
@@ -217,8 +234,7 @@ var JitsiMeetExternalAPI = (function()
* @param listener the listener
*/
JitsiMeetExternalAPI.prototype.addEventListener
= function (event, listener)
{
= function (event, listener) {
var message = {type: "event", action: "add", events: [event]};
this.eventHandlers[event] = listener;
@@ -230,8 +246,7 @@ var JitsiMeetExternalAPI = (function()
* @param event the name of the event.
*/
JitsiMeetExternalAPI.prototype.removeEventListener
= function (event)
{
= function (event) {
if(!this.eventHandlers[event])
{
console.error("The event " + event + " is not registered.");
@@ -247,8 +262,7 @@ var JitsiMeetExternalAPI = (function()
* @param events array with the names of the events.
*/
JitsiMeetExternalAPI.prototype.removeEventListeners
= function (events)
{
= function (events) {
var eventsArray = [];
for(var i = 0; i < events.length; i++)
{
@@ -274,8 +288,7 @@ var JitsiMeetExternalAPI = (function()
* Processes message events sent from Jitsi Meet
* @param event the event
*/
JitsiMeetExternalAPI.prototype.processMessage = function(event)
{
JitsiMeetExternalAPI.prototype.processMessage = function(event) {
var message;
try {
message = JSON.parse(event.data);
@@ -285,18 +298,15 @@ var JitsiMeetExternalAPI = (function()
console.error("Message without type is received.");
return;
}
switch (message.type)
{
switch (message.type) {
case "system":
if(message.loaded)
{
if(message.loaded) {
this.onFrameLoaded();
}
break;
case "event":
if(message.action != "result" ||
!message.event || !this.eventHandlers[message.event])
{
!message.event || !this.eventHandlers[message.event]) {
console.warn("The received event cannot be parsed.");
return;
}
@@ -306,8 +316,6 @@ var JitsiMeetExternalAPI = (function()
console.error("Unknown message type.");
return;
}
};
/**
@@ -316,8 +324,7 @@ var JitsiMeetExternalAPI = (function()
*/
JitsiMeetExternalAPI.prototype.onFrameLoaded = function () {
this.frameLoaded = true;
for (var i = 0; i < this.initialCommands.length; i++)
{
for (var i = 0; i < this.initialCommands.length; i++) {
this.sendMessage(this.initialCommands[i]);
}
this.initialCommands = null;
@@ -331,13 +338,11 @@ var JitsiMeetExternalAPI = (function()
this.eventListener = function (event) {
self.processMessage(event);
};
if (window.addEventListener)
{
if (window.addEventListener) {
window.addEventListener('message',
this.eventListener, false);
}
else
{
else {
window.attachEvent('onmessage', this.eventListener);
}
};
@@ -346,13 +351,11 @@ var JitsiMeetExternalAPI = (function()
* Removes the listeners and removes the Jitsi Meet frame.
*/
JitsiMeetExternalAPI.prototype.dispose = function () {
if (window.removeEventListener)
{
if (window.removeEventListener) {
window.removeEventListener('message',
this.eventListener, false);
}
else
{
else {
window.detachEvent('onmessage',
this.eventListener);
}

View File

@@ -9,9 +9,10 @@
<meta itemprop="name" content="Jitsi Meet"/>
<meta itemprop="description" content="Join a WebRTC video conference powered by the Jitsi Videobridge"/>
<meta itemprop="image" content="/images/jitsilogo.png"/>
<script src="https://api.callstats.io/static/callstats.min.js"></script>
<script src="libs/jquery-2.1.1.min.js"></script>
<script src="config.js?v=9"></script><!-- adapt to your needs, i.e. set hosts and bosh path -->
<script src="libs/strophe/strophe.min.js?v=1"></script>
<script src="config.js?v=12"></script><!-- adapt to your needs, i.e. set hosts and bosh path -->
<script src="libs/strophe/strophe.min.js?v=2"></script>
<script src="libs/strophe/strophe.disco.min.js?v=1"></script>
<script src="libs/strophe/strophe.caps.jsonly.min.js?v=1"></script>
<script src="libs/jquery-ui.js"></script>
@@ -19,12 +20,12 @@
<script src="libs/popover.js?v=1"></script><!-- bootstrap tooltip lib -->
<script src="libs/toastr.js?v=1"></script><!-- notifications lib -->
<script src="interface_config.js?v=5"></script>
<script src="libs/app.bundle.js?v=82"></script>
<script src="libs/app.bundle.js?v=130"></script>
<script src="analytics.js?v=1"></script><!-- google analytics plugin -->
<link rel="stylesheet" href="css/font.css?v=7"/>
<link rel="stylesheet" href="css/toastr.css?v=1">
<link rel="stylesheet" type="text/css" media="screen" href="css/main.css?v=30"/>
<link rel="stylesheet" type="text/css" media="screen" href="css/videolayout_default.css?v=17" id="videolayout_default"/>
<link rel="stylesheet" type="text/css" media="screen" href="css/main.css?v=31"/>
<link rel="stylesheet" type="text/css" media="screen" href="css/videolayout_default.css?v=20" id="videolayout_default"/>
<link href="//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css" rel="stylesheet">
<link rel="stylesheet" href="css/jquery-impromptu.css?v=4">
<link rel="stylesheet" href="css/modaldialog.css?v=3">
@@ -130,9 +131,7 @@
</div>
<span id="toolbar">
<span id="authentication" class="authentication" style="display: none">
<a class="button" id="toolbar_button_authentication" >
<i id="authButton" class="icon-avatar"></i>
</a>
<a class="button icon-avatar" id="toolbar_button_authentication" data-i18n="[content]toolbar.authenticate"></a>
<ul class="loginmenu">
<span class="loginmenuPadding"></span>
<li id="toolbar_auth_identity" class="identity"></li>
@@ -143,97 +142,29 @@
<a class="authButton" data-i18n="toolbar.logout"></a>
</li>
</ul>
<div class="header_button_separator"></div>
</span>
<a class="button" id="toolbar_button_mute" data-container="body" data-toggle="popover" data-placement="bottom" shortcut="mutePopover" data-i18n="[content]toolbar.mute" content="Mute / Unmute">
<i id="mute" class="icon-microphone"></i>
<a class="button icon-microphone" id="toolbar_button_mute" data-container="body" data-toggle="popover" data-placement="bottom" shortcut="mutePopover" data-i18n="[content]toolbar.mute" content="Mute / Unmute"></a>
<a class="button icon-camera" id="toolbar_button_camera" data-container="body" data-toggle="popover" data-placement="bottom" shortcut="toggleVideoPopover" data-i18n="[content]toolbar.videomute" content="Start / stop camera"></a>
<a class="button icon-recEnable" id="toolbar_button_record" data-container="body" data-toggle="popover" data-placement="bottom" data-i18n="[content]toolbar.record" content="Record" style="display: none"></a>
<a class="button icon-security" id="toolbar_button_security" data-container="body" data-toggle="popover" data-placement="bottom" data-i18n="[content]toolbar.lock" content="Lock / unlock room"></a>
<a class="button icon-link" id="toolbar_button_link" data-container="body" data-toggle="popover" data-placement="bottom" data-i18n="[content]toolbar.invite" content="Invite others"></a>
<a class="button icon-chat" id="toolbar_button_chat" data-container="body" data-toggle="popover" shortcut="toggleChatPopover" data-placement="bottom" data-i18n="[content]toolbar.chat" content="Open / close chat">
<span id="unreadMessages"></span>
</a>
<div class="header_button_separator"></div>
<a class="button" id="toolbar_button_camera" data-container="body" data-toggle="popover" data-placement="bottom" shortcut="toggleVideoPopover" data-i18n="[content]toolbar.videomute" content="Start / stop camera">
<i id="video" class="icon-camera"></i>
</a>
<span id="recording" style="display: none">
<div class="header_button_separator"></div>
<a class="button" id="toolbar_button_record" data-container="body" data-toggle="popover" data-placement="bottom" data-i18n="[content]toolbar.record" content="Record">
<i id="recordButton" class="icon-recEnable"></i>
</a>
</span>
<div class="header_button_separator"></div>
<a class="button" id="toolbar_button_security" data-container="body" data-toggle="popover" data-placement="bottom" data-i18n="[content]toolbar.lock" content="Lock / unlock room">
<i id="lockIcon" class="icon-security"></i>
</a>
<div class="header_button_separator"></div>
<a class="button" id="toolbar_button_link" data-container="body" data-toggle="popover" data-placement="bottom" data-i18n="[content]toolbar.invite" content="Invite others">
<i class="icon-link"></i>
</a>
<div class="header_button_separator"></div>
<span class="toolbar_span">
<a class="button" id="toolbar_button_chat" data-container="body" data-toggle="popover" shortcut="toggleChatPopover" data-placement="bottom" data-i18n="[content]toolbar.chat" content="Open / close chat">
<i id="chatButton" class="icon-chat">
<span id="unreadMessages"></span>
</i>
</a>
</span>
<span id="prezi_button">
<div class="header_button_separator"></div>
<a class="button" id="toolbar_button_prezi" data-container="body" data-toggle="popover" data-placement="bottom" data-i18n="[content]toolbar.prezi" content="Share Prezi">
<i class="icon-prezi"></i>
</a>
</span>
<span id="etherpadButton">
<div class="header_button_separator"></div>
<a class="button" id="toolbar_button_etherpad" data-container="body" data-toggle="popover" data-placement="bottom" content="Shared document" data-i18n="[content]toolbar.etherpad">
<i class="icon-share-doc"></i>
</a>
</span>
<div class="header_button_separator"></div>
<span id="desktopsharing" style="display: none">
<a class="button" id="toolbar_button_desktopsharing" data-container="body" data-toggle="popover" data-placement="bottom" content="Share screen" data-i18n="[content]toolbar.sharescreen">
<i class="icon-share-desktop"></i>
</a>
</span>
<div class="header_button_separator"></div>
<a class="button" id="toolbar_button_fullScreen" data-container="body" data-toggle="popover" data-placement="bottom" content="Enter / Exit Full Screen" data-i18n="[content]toolbar.fullscreen">
<i id="fullScreen" class="icon-full-screen"></i>
</a>
<span id="sipCallButton" style="display: none">
<div class="header_button_separator"></div>
<a class="button" id="toolbar_button_sip" data-container="body" data-toggle="popover" data-placement="bottom" content="Call SIP number" data-i18n="[content]toolbar.sip">
<i class="icon-telephone"></i></a>
</span>
<span id="dialPadButton" style="display: none">
<div class="header_button_separator"></div>
<a class="button" id="toolbar_button_dialpad" data-container="body" data-toggle="popover" data-placement="bottom" content="Open dialpad" data-i18n="[content]toolbar.dialpad">
<i class="icon-dialpad"></i></a>
</span>
<div class="header_button_separator"></div>
<a class="button" id="toolbar_button_settings" data-container="body" data-toggle="popover" data-placement="bottom" content="Settings" data-i18n="[content]toolbar.Settings">
<i id="settingsButton" class="icon-settings"></i>
</a>
<div class="header_button_separator"></div>
<span id="hangup">
<a class="button" id="toolbar_button_hangup" data-container="body" data-toggle="popover" data-placement="bottom" content="Hang Up" data-i18n="[content]toolbar.hangup">
<i class="icon-hangup" style="color:#ff0000;font-size: 1.4em;"></i>
</a>
</span>
<a class="button icon-prezi" id="toolbar_button_prezi" data-container="body" data-toggle="popover" data-placement="bottom" data-i18n="[content]toolbar.prezi" content="Share Prezi"></a>
<a class="button icon-share-doc" id="toolbar_button_etherpad" data-container="body" data-toggle="popover" data-placement="bottom" content="Shared document" data-i18n="[content]toolbar.etherpad"></a>
<a class="button icon-share-desktop" id="toolbar_button_desktopsharing" data-container="body" data-toggle="popover" data-placement="bottom" content="Share screen" data-i18n="[content]toolbar.sharescreen" style="display: none"></a>
<a class="button icon-full-screen" id="toolbar_button_fullScreen" data-container="body" data-toggle="popover" data-placement="bottom" content="Enter / Exit Full Screen" data-i18n="[content]toolbar.fullscreen"></a>
<a class="button icon-telephone" id="toolbar_button_sip" data-container="body" data-toggle="popover" data-placement="bottom" content="Call SIP number" data-i18n="[content]toolbar.sip" style="display: none"></a>
<a class="button icon-dialpad" id="toolbar_button_dialpad" data-container="body" data-toggle="popover" data-placement="bottom" content="Open dialpad" data-i18n="[content]toolbar.dialpad" style="display: none"></a>
<a class="button icon-settings" id="toolbar_button_settings" data-container="body" data-toggle="popover" data-placement="bottom" content="Settings" data-i18n="[content]toolbar.Settings"></a>
<a class="button icon-hangup" id="toolbar_button_hangup" data-container="body" data-toggle="popover" data-placement="bottom" content="Hang Up" data-i18n="[content]toolbar.hangup"></a>
</span>
</div>
<div id="subject"></div>
</div>
<div id="reloadPresentation"><a id="reloadPresentationLink"><i title="Reload Prezi" class="fa fa-repeat fa-lg"></i></a></div>
<div id="videospace">
<div id="largeVideoContainer" class="videocontainer">
<div id="presentation"></div>
<div id="etherpad"></div>
<a target="_new"><div class="watermark leftwatermark"></div></a>
<a target="_new"><div class="watermark rightwatermark"></div></a>
<a class="poweredby" href="http://jitsi.org" target="_new" ><span data-i18n="poweredby"></span> jitsi.org</a>
<div id="activeSpeaker">
<img id="activeSpeakerAvatar" src=""/>
<canvas id="activeSpeakerAudioLevel"></canvas>
</div>
<video id="largeVideo" autoplay oncontextmenu="return false;"></video>
</div>
<div id="remoteVideos">
<span id="localVideoContainer" class="videocontainer">
<span id="localNick" class="nick"></span>
@@ -289,9 +220,10 @@
</div>
</div>
<div id="contactlist" class="right-panel">
<ul>
<li class="title"><i class="icon-contact-list"></i><span data-i18n="contactlist"></span></li>
</ul>
<div class="title">
<i class="icon-contactList"><span data-i18n="contactlist"></span></i>
</div>
<ul id="contacts"></ul>
</div>
<div id="settingsmenu" class="right-panel">
<div class="icon-settings" data-i18n="settings.title"></div>

View File

@@ -15,5 +15,9 @@ var interfaceConfig = {
GENERATE_ROOMNAMES_ON_WELCOME_PAGE: true,
APP_NAME: "Jitsi Meet",
INVITATION_POWERED_BY: true,
ACTIVE_SPEAKER_AVATAR_SIZE: 100
ACTIVE_SPEAKER_AVATAR_SIZE: 100,
/**
* Whether to only show the filmstrip (and hide the toolbar).
*/
filmStripOnly: false
};

View File

@@ -30,11 +30,11 @@ You can add translatable text in the HTML:
```
You can also use APP.translation.generateTranslatonHTML(key, options) to get this HTML code as Javascript string.
You can also use APP.translation.generateTranslationHTML(key, options) to get this HTML code as Javascript string.
```
APP.translation.generateTranslatonHTML("dialog.OK") // returns <span data-i18n="dialog.OK">OK</span>
APP.translation.generateTranslationHTML("dialog.OK") // returns <span data-i18n="dialog.OK">OK</span>
```
The value in the options parameter will be added in data-i18n-options attribute of the element.

View File

@@ -3,5 +3,7 @@
"bg": "Bulgarisch",
"de": "Deutsch",
"tr": "Türkisch",
"it": "Italienisch"
"it": "Italienisch",
"fr": "Französisch",
"sl": "Slowenisch"
}

9
lang/languages-sl.json Normal file
View File

@@ -0,0 +1,9 @@
{
"en": "Angleščina",
"bg": "Bolgarščina",
"de": "Nemščina",
"tr": "Turščina",
"it": "Italjanščina",
"fr": "Francoščina",
"sl": ""
}

View File

@@ -4,5 +4,6 @@
"de": "German",
"tr": "Turkish",
"it": "Italian",
"fr": "French"
"fr": "French",
"sl": "Slovenian"
}

View File

@@ -62,7 +62,8 @@
"Settings": "Einstellungen",
"hangup": "Auflegen",
"login": "Anmelden",
"logout": "Abmelden"
"logout": "Abmelden",
"dialpad": "Tastenblock anzeigen"
},
"bottomtoolbar": {
"chat": "Chat öffnen / schliessen",
@@ -79,7 +80,9 @@
"settings": {
"title": "Einstellungen",
"update": "Aktualisieren",
"name": "Name"
"name": "Name",
"startAudioMuted": "Stumm beitreten",
"startVideoMuted": "Ohne Video beitreten"
},
"videothumbnail": {
"editnickname": "Klicken um den Anzeigenamen zu bearbeiten",
@@ -118,7 +121,9 @@
"focus": "Konferenz-Organisator",
"focusFail": "__component__ ist im Moment nicht verfügbar - wiederholen in __ms__ Sekunden",
"grantedTo": "Moderatorenrechte an __to__ vergeben.",
"grantedToUnknown": "Moderatorenrechte an $t(somebody) vergeben."
"grantedToUnknown": "Moderatorenrechte an $t(somebody) vergeben.",
"muted": "Der Konferenz wurde stumm beigetreten.",
"mutedTitle": "Stummschaltung aktiv."
},
"dialog": {
"kickMessage": "Oh! Sie wurden aus der Konferenz ausgeschlossen.",
@@ -127,7 +132,8 @@
"passwordError2": "Diese Konferenzt ist nicht mit einem Passwort geschützt. Nur der Besitzer der Konferenz kann ein Passwort vergeben.",
"joinError": "Oh! Der Konferenz konnte nicht beigetreten werden. Diese könnte ein Problem mit den Sicherheitseinstellungen sein. Bitte kontaktieren Sie den Administrator des Dienstes.",
"connectError": "Oh! Es hat etwas nicht geklappt und der Konferenz konnte nicht beigetreten werden.",
"connecting": "",
"connectErrorWithMsg": "Oh! Es hat etwas nicht geklappt und der Konferenz konnte nicht beigetreten werden: __msg__",
"connecting": "Verbindung wird hergestellt",
"error": "Fehler",
"detectext": "Fehler bei der Erkennung der Bildschirmfreigabeerweiterung.",
"failtoinstall": "Die Bildschirmfreigabeerweiterung konnte nicht installiert werden.",
@@ -151,9 +157,9 @@
"sharePreziTitle": "Ein Prezi teilen",
"sharePreziMsg": "Ein anderer Teilnehmer teilt bereits ein Prezi. Diese Konferenz kann nur eine Prezi auf einmal anzeigen.",
"Remove": "Entfernen",
"Stop": "Stopp",
"AuthMsg": "Für die Erstellung des Raums ist eine Authentifizierung erforderlich. <br/><b>__room__</b><br/>Sie können sich entweder anmelden oder warten bis jemand anderes die Authentifizierung vornimmt.",
"Authenticate": "Anmelden",
"WaitingForHost": "Warten auf den Organisator...",
"WaitForHostMsg": "Die Konferenz <b>__room__</b> hat noch nicht begonnen. Wenn Sie der Organisator sind, melden Sie sich bitte an. Anderenfalls warten Sie bitte bis der Organisator beigetreten ist.",
"IamHost": "Ich bin der Organisator",
"Cancel": "Abbrechen",
"retry": "Wiederholen",
"logoutTitle": "Abmelden",
@@ -183,7 +189,8 @@
"reservationErrorMsg": "Fehler, Nummer: __code__, Nachricht: __msg__",
"password": "Passwort",
"userPassword": "Benutzerpasswort",
"token": "Token"
"token": "Token",
"displayNameRequired": "Geben Sie Ihren Anzeigenamen ein:"
},
"email": {
"sharedKey": [
@@ -219,6 +226,7 @@
"connection": {
"ERROR": "Fehler",
"CONNECTING": "Verbindung wird hergestellt",
"RECONNECTING": "Es ist ein Netzwerkproblem aufgetreten. Verbinde...",
"CONNFAIL": "Verbindungsaufbau gescheitert",
"AUTHENTICATING": "Anmeldung läuft",
"AUTHFAIL": "Authentifizierung fehlgeschlagen",
@@ -226,5 +234,10 @@
"DISCONNECTED": "Getrennt",
"DISCONNECTING": "Verbindung wird getrennt",
"ATTACHED": "Angehängt"
},
"recording": {
"toaster": "Wird aufgezeichnet",
"pending": "Die Aufzeichnung wird gestartet sobald ein weiterer Teilnehmer beitritt",
"on": "Aufzeichnung wurde gestartet"
}
}

249
lang/main-sl.json Normal file
View File

@@ -0,0 +1,249 @@
{
"contactlist": "STIKI",
"connectionsettings": "Nastavitve povezave",
"poweredby": "poganja",
"downloadlogs": "Shrani zapis",
"roomUrlDefaultMsg": "Ustvarjanje vaše konference ...",
"participant": "Udeleženec",
"me": "jaz",
"speaker": "Govornik",
"defaultNickname": "npr. __name__",
"defaultPreziLink": "npr. __url__",
"welcomepage": {
"go": "POJDI",
"roomname": "Vpišite ime sobe",
"disable": "Prihodnjič ne prikaži te strani",
"feature1": {
"title": "Enostavna uporaba",
"content": "Nič ni treba namestiti. __app__ deluje direktno v vašem brskalniku. Enostavno sporočite ostalim udeležencem URL svoje konference in začnite."
},
"feature2": {
"title": "Ozka pasovna širina",
"content": "Video konferenca z več udeleženci s samo 128Kbps. Deljenje zaslona in samo avdio konference so možne že z veliko nižjo pasovno širino."
},
"feature3": {
"title": "Odprta koda",
"content": "__app__ je objavljen po licenci MIT. Pod pogoji te licence lahko prosto snamete, uporabljate, spreminjate in delite."
},
"feature4": {
"title": "Neomejeno število uporabnikov",
"content": "Nobene umetne omejitve števila uporabnikov ali udeležencev konference. Zmogljivost strežnika in pasovna širina sta edini omejitvi."
},
"feature5": {
"title": "Skupna raba zaslona",
"content": "Skupna raba zaslona z drugimi je zelo enostavna. __app__ je idealna rešitev za spletne predstavitve, predavanja in tehnično podporo."
},
"feature6": {
"title": "Varne sobe",
"content": "Rabite zasebnost? Konferenčne sobe __app__ so lahko zaklenjene z geslom, da preprečite dostop neželenim gostom ter prekinitve."
},
"feature7": {
"title": "Skupna raba zapiskov",
"content": "__app__ vsebuje Etherpad, realnočasovni skupinski urejevalnik besedil, ki je idealen za pisanje zapisnikov sestankov, člankov in še mnogo drugega."
},
"feature8": {
"title": "Statistika uporabe",
"content": "Spoznajte svoje uporabnike z enostavno integracijo v Piwik, Google Analytics ter druge sisteme za nadzor uporabe in statistiko."
}
},
"toolbar": {
"mute": "Utišaj / Povrni glasnost",
"videomute": "Zaženi / Ustavi kamero",
"authenticate": "Overi",
"record": "Snemaj",
"lock": "Zakleni / Odkleni sobo",
"invite": "Povabite ostale",
"chat": "Odpri / zapri klepetalnico",
"prezi": "Skupna raba Prezi",
"etherpad": "Dokument v skupni rabi",
"sharescreen": "Zaslon v souporabi",
"fullscreen": "Vklopi / Izklopi celozaslonski način",
"sip": "Pokliči številko SIP",
"Settings": "Nastavitve",
"hangup": "Odloži",
"login": "Prijava",
"logout": "Odjava",
"dialpad": "Pokaži številčnico"
},
"bottomtoolbar": {
"chat": "Odpri / zapri klepetalnico",
"filmstrip": "Pokaži / Skrij filmski trak",
"contactlist": "Odpri / Zapri stike"
},
"chat": {
"nickname": {
"title": "Vpišite vzdevek v spodnje polje",
"popover": "Izberite vzdevek"
},
"messagebox": "Vnesite besedilo ..."
},
"settings": {
"title": "NASTAVITVE",
"update": "Posodobi",
"name": "Ime",
"startAudioMuted": "začni brez zvoka",
"startVideoMuted": "začni brez slike"
},
"videothumbnail": {
"editnickname": "Kliknite, da spremenite<br/>svoje ime",
"moderator": "Lastnik<br/>konference",
"videomute": "Udeleženec je<br/>izključil kamero.",
"mute": "Udeleženec je utišan",
"kick": "Izženi",
"muted": "Utišan",
"domute": "Utišaj"
},
"connectionindicator": {
"bitrate": "Bitna hitrost:",
"packetloss": "Izgubljeni paketi:",
"resolution": "Ločljivost:",
"less": "Pokaži manj",
"more": "Pokaži več",
"address": "Naslov:",
"remoteport_plural_5": "Oddaljena vrata:",
"remoteport": "Oddaljena vrata:",
"remoteport_plural_2": "Oddaljena vrata:",
"remoteport_plural_3": "Oddaljena vrata:",
"localport_plural_5": "Krajevna vrata:",
"localport": "Krajevna vrata:",
"localport_plural_2": "Krajevna vrata:",
"localport_plural_3": "Krajevna vrata:",
"localaddress_plural_5": "Krajevni naslov:",
"localaddress": "Krajevna naslova:",
"localaddress_plural_2": "Krajevni naslovi:",
"localaddress_plural_3": "Krajevni naslov:",
"remoteaddress_plural_5": "Oddaljeni naslov:",
"remoteaddress": "Oddaljena naslova:",
"remoteaddress_plural_2": "Oddaljeni naslovi:",
"remoteaddress_plural_3": "Oddaljeni naslovi:",
"transport": "Prenos:",
"bandwidth": "Ocenjena pasovna širina:",
"na": "Ko se konferenca začne se vrnite sem za informacije o povezavi"
},
"notify": {
"disconnected": "odklopjeno",
"moderator": "Dodeljene moderatorske pravice!",
"connected": "povezano",
"somebody": "Nekdo",
"me": "Jaz",
"focus": "Fokus na konferenco",
"focusFail": "__component__ ni na razpolago - ponovni poskus čez __ms__ sec",
"grantedTo": "Moderatorske pravice dodeljene uporabniku __to__!",
"grantedToUnknown": "Moderatorske pravice dodeljene uporabniku $t(somebody)!",
"muted": "Pogovor ste začeli utišano.",
"mutedTitle": "Utišani ste!"
},
"dialog": {
"kickMessage": "Ojej! Izgnali so vas iz srečanja!",
"popupError": "Vaš brskalnik ne dovoli pojavnih oken iz te spletne strani. Omogočite prosim pojavna okna v varnostnih nastavitvah svojega brskalnika in ponovno poskusite.",
"passwordError": "Ta pogovor je zaščiten z geslom. Samo lastnik konference lahko nastavi geslo.",
"passwordError2": "Ta pogovor ni zaščiten z geslom. Samo lastnik konference lahko nastavi geslo.",
"joinError": "Ups! Ni se bilo mogoče pridružiti konferenci. Mogoče je kakšna težava z varnostnimi nastavitvami. Pišite prosim administratorju storitve.",
"connectError": "Ups! Nekaj je narobe in se ni bilo mogoče povezati s konferenco.",
"connectErrorWithMsg": "Ups! Nekaj je narobe in se ni bilo mogoče povezati s konferenco: __msg__",
"connecting": "Povezovanje",
"error": "Napaka",
"detectext": "Napaka pri zaznavanju razširitve za skupno uporabo namizja.",
"failtoinstall": "Razširitve za skupno uporabo namizja ni bilo mogoče namestiti",
"failedpermissions": "Ni bilo mogoče pridobiti dovoljenja za uporabo lokalnega mikrofona ali kamere.",
"bridgeUnavailable": "Jitsi Videobridge trenutno ni na razpolago. Prosim poskusite kasneje!",
"lockTitle": "Zaklepanje ni uspelo",
"lockMessage": "Konference ni bilo mogoče zakleniti.",
"warning": "Opozorilo",
"passwordNotSupported": "Trenutno ni mogoče zakleniti sobe z geslom.",
"sorry": "Oprostite",
"internalError": "Notranja napaka [setRemoteDescription]",
"unableToSwitch": "Ni mogoče preklopiti video pretoka.",
"SLDFailure": "Ups! Nekaj je narobe in zvoka se ne da utišati! (Napaka SLD)",
"SRDFailure": "Ups! Nekaj je narobe in slike ni mogoče ustaviti! (Napaka SRD)",
"oops": "Ups!",
"defaultError": "Prišlo je do neke napake",
"passwordRequired": "Potrebno je geslo",
"Ok": "V redu",
"removePreziTitle": "Odstrani Prezi",
"removePreziMsg": "Ali res želite odstraniti Prezi?",
"sharePreziTitle": "Dajte Prezi v skupno rabo",
"sharePreziMsg": "Drug uporabnik je že dal Prezi v skupno rabo. Ta konferenca podpira samo en Prezi naenkrat.",
"Remove": "Odstrani",
"WaitingForHost": "Čakanje na gostitelja ...",
"WaitForHostMsg": "Ta konferenca <b>__room__ </b> se še ni začela. V primeru, da ste vi gostitelj se prosim overite. Drugače počakajte prosim na prihod gostitelja.",
"IamHost": "Jaz sem gostitelj",
"Cancel": "Prekliči",
"retry": "Poskusi ponovno",
"logoutTitle": "Odjava",
"logoutQuestion": "Ali se res želite odjaviti in prekiniti konferenco?",
"sessTerminated": "Seja je končana",
"hungUp": "Prekinili ste klic",
"joinAgain": "Ponovno se pridruži",
"Share": "Souporaba",
"preziLinkError": "Prosim, pravilno vpišite povezavo Prezi.",
"Save": "Shrani",
"recordingToken": "Vnesite žeton za registracijo",
"Dial": "Pokliči",
"sipMsg": "Vnesite številko SIP",
"passwordCheck": "Ali res želite odstraniti geslo?",
"passwordMsg": "Nastavite geslo za zaklepanje sobe",
"Invite": "Povabi",
"shareLink": "To povezavo pošljite vsem, ki jih želite povabiti",
"settings1": "Nastavite svojo konferenco",
"settings2": "Utišaj udeležence ob pristopu",
"settings3": "Zahtevaj vzdevke<br/><br/>Nastavi geslo za zaklep sobe:",
"yourPassword": "vaše geslo",
"Back": "Nazaj",
"serviceUnavailable": "Storitev ni na voljo",
"gracefulShutdown": "Storitev trenutno ni na voljo zaradi vzdrževanja. Poskusite ponovno kasneje.",
"Yes": "Da",
"reservationError": "Napaka v sistemu rezervacije",
"reservationErrorMsg": "Koda napake: __code__, sporočilo: __msg__",
"password": "geslo",
"userPassword": "uporabniško geslo",
"token": "žeton",
"displayNameRequired": "Vpišite svoje ime:"
},
"email": {
"sharedKey": [
"Ta konferenca je zaklenjena z geslom. Uporabite sledeči PIN ko se pridružite:",
"",
"",
"__sharedKey__",
"",
""
],
"subject": "Povabilo na __appName__ (__conferenceName__)",
"body": [
"Pozdravljeni,",
"želim vas povabiti na ravnokar pripravljeno konferenco __appName__.",
"",
"",
"Prosim, kliknite na sledečo povezavo, da se pridružite konferenci.",
"",
"",
"__roomUrl__",
"",
"",
"__sharedKeyText__",
" Opomba: __appName__ trenutno nudi podporo samo za __supportedBrowsers__, uporabiti morate enega izmed teh brskalnikov.",
"",
"",
"Se slišimo čez trenutek!"
],
"and": "in"
},
"connection": {
"ERROR": "Napaka",
"CONNECTING": "Povezovanje",
"RECONNECTING": "Prišlo je do omrežne napake. Ponovni poskus ...",
"CONNFAIL": "Povezovanje je spodletelo",
"AUTHENTICATING": "Overjanje",
"AUTHFAIL": "Overitev je spodletela",
"CONNECTED": "Povezano",
"DISCONNECTED": "Ni povezave",
"DISCONNECTING": "Prekinjanje povezave",
"ATTACHED": "Priključeno"
},
"recording": {
"toaster": "Trenutno poteka snemanje!",
"pending": "Snemanje se bo začelo takoj, ko se bo pridružil drugi udeleženec",
"on": "Snemanje se je začelo"
}
}

View File

@@ -194,7 +194,8 @@
"reservationErrorMsg": "Error code: __code__, message: __msg__",
"password": "password",
"userPassword": "user password",
"token": "token"
"token": "token",
"displayNameRequired": "Please enter your display name:"
},
"email":
{
@@ -228,6 +229,7 @@
{
"ERROR": "Error",
"CONNECTING": "Connecting",
"RECONNECTING": "A network problem occurred. Reconnecting...",
"CONNFAIL": "Connection failed",
"AUTHENTICATING": "Authenticating",
"AUTHFAIL": "Authentication failed",
@@ -238,5 +240,11 @@
"FETCH_SESSION_ID": "Obtaining session-id...",
"GOT_SESSION_ID": "Obtaining session-id... Done",
"GET_SESSION_ID_ERROR": "Get session-id error: "
},
"recording":
{
"toaster": "Currently recording!",
"pending": "Your recording will start as soon as another participant joins",
"on": "Recording has been started"
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -1,3 +1,4 @@
/* global APP */
/**
* Implements API class that communicates with external api class
* and provides interface to access Jitsi Meet features by external
@@ -10,20 +11,25 @@ var XMPPEvents = require("../../service/xmpp/XMPPEvents");
* List of the available commands.
* @type {{
* displayName: inputDisplayNameHandler,
* muteAudio: toggleAudio,
* muteVideo: toggleVideo,
* filmStrip: toggleFilmStrip
* toggleAudio: toggleAudio,
* toggleVideo: toggleVideo,
* toggleFilmStrip: toggleFilmStrip,
* toggleChat: toggleChat,
* toggleContactList: toggleContactList
* }}
*/
var commands =
{
displayName: APP.UI.inputDisplayNameHandler,
muteAudio: APP.UI.toggleAudio,
muteVideo: APP.UI.toggleVideo,
toggleFilmStrip: APP.UI.toggleFilmStrip,
toggleChat: APP.UI.toggleChat,
toggleContactList: APP.UI.toggleContactList
};
var commands = {};
function initCommands() {
commands = {
displayName: APP.UI.inputDisplayNameHandler,
toggleAudio: APP.UI.toggleAudio,
toggleVideo: APP.UI.toggleVideo,
toggleFilmStrip: APP.UI.toggleFilmStrip,
toggleChat: APP.UI.toggleChat,
toggleContactList: APP.UI.toggleContactList
};
}
/**
@@ -37,8 +43,7 @@ var commands =
* participantLeft: boolean
* }}
*/
var events =
{
var events = {
incomingMessage: false,
outgoingMessage:false,
displayNameChange: false,
@@ -49,18 +54,15 @@ var events =
var displayName = {};
/**
* Processes commands from external applicaiton.
* Processes commands from external application.
* @param message the object with the command
*/
function processCommand(message)
{
if(message.action != "execute")
{
function processCommand(message) {
if (message.action != "execute") {
console.error("Unknown action of the message");
return;
}
for(var key in message)
{
for (var key in message) {
if(commands[key])
commands[key].apply(null, message[key]);
}
@@ -71,31 +73,26 @@ function processCommand(message)
* @param event the event
*/
function processEvent(event) {
if(!event.action)
{
if (!event.action) {
console.error("Event with no action is received.");
return;
}
var i = 0;
switch(event.action)
{
switch(event.action) {
case "add":
for(; i < event.events.length; i++)
{
for (; i < event.events.length; i++) {
events[event.events[i]] = true;
}
break;
case "remove":
for(; i < event.events.length; i++)
{
for (; i < event.events.length; i++) {
events[event.events[i]] = false;
}
break;
default:
console.error("Unknown action for event.");
}
}
/**
@@ -110,8 +107,7 @@ function sendMessage(object) {
* Processes a message event from the external application
* @param event the message event
*/
function processMessage(event)
{
function processMessage(event) {
var message;
try {
message = JSON.parse(event.data);
@@ -119,8 +115,7 @@ function processMessage(event)
if(!message.type)
return;
switch (message.type)
{
switch (message.type) {
case "command":
processCommand(message);
break;
@@ -131,23 +126,22 @@ function processMessage(event)
console.error("Unknown type of the message");
return;
}
}
function setupListeners() {
APP.xmpp.addListener(XMPPEvents.MUC_MEMBER_JOINED, function (from) {
API.triggerEvent("participantJoined", {jid: from});
});
APP.xmpp.addListener(XMPPEvents.MESSAGE_RECEIVED, function (from, nick, txt, myjid) {
APP.xmpp.addListener(XMPPEvents.MESSAGE_RECEIVED, function (from, nick, txt, myjid, stamp) {
if (from != myjid)
API.triggerEvent("incomingMessage",
{"from": from, "nick": nick, "message": txt});
{"from": from, "nick": nick, "message": txt, "stamp": stamp});
});
APP.xmpp.addListener(XMPPEvents.MUC_MEMBER_LEFT, function (jid) {
API.triggerEvent("participantLeft", {jid: jid});
});
APP.xmpp.addListener(XMPPEvents.DISPLAY_NAME_CHANGED, function (jid, newDisplayName) {
name = displayName[jid];
var name = displayName[jid];
if(!name || name != newDisplayName) {
API.triggerEvent("displayNameChange", {jid: jid, displayname: newDisplayName});
displayName[jid] = newDisplayName;
@@ -165,7 +159,7 @@ var API = {
*/
isEnabled: function () {
var hash = location.hash;
if(hash && hash.indexOf("external") > -1 && window.postMessage)
if (hash && hash.indexOf("external") > -1 && window.postMessage)
return true;
return false;
},
@@ -176,13 +170,12 @@ var API = {
* is initialized.
*/
init: function () {
if (window.addEventListener)
{
initCommands();
if (window.addEventListener) {
window.addEventListener('message',
processMessage, false);
}
else
{
else {
window.attachEvent('onmessage', processMessage);
}
sendMessage({type: "system", loaded: true});
@@ -213,19 +206,14 @@ var API = {
* Removes the listeners.
*/
dispose: function () {
if(window.removeEventListener)
{
if(window.removeEventListener) {
window.removeEventListener("message",
processMessage, false);
}
else
{
else {
window.detachEvent('onmessage', processMessage);
}
}
};
module.exports = API;

View File

@@ -1,4 +1,4 @@
/* global Strophe, focusedVideoSrc*/
/* global config, APP, Strophe */
// cache datachannels to avoid garbage collection
// https://code.google.com/p/chromium/issues/detail?id=405545
@@ -8,19 +8,13 @@ var _dataChannels = [];
var eventEmitter = null;
var DataChannels =
{
var DataChannels = {
/**
* Callback triggered by PeerConnection when new data channel is opened
* on the bridge.
* @param event the event info object.
*/
onDataChannel: function (event)
{
onDataChannel: function (event) {
var dataChannel = event.channel;
dataChannel.onopen = function () {
@@ -32,14 +26,7 @@ var DataChannels =
// Sends 12 bytes binary message to the bridge
//dataChannel.send(new ArrayBuffer(12));
// when the data channel becomes available, tell the bridge about video
// selections so that it can do adaptive simulcast,
// we want the notification to trigger even if userJid is undefined,
// or null.
var userJid = APP.UI.getLargeVideoState().userResourceJid;
// we want the notification to trigger even if userJid is undefined,
// or null.
onSelectedEndpointChanged(userJid);
eventEmitter.emit(RTCEvents.DATA_CHANNEL_OPEN);
};
dataChannel.onerror = function (error) {
@@ -72,8 +59,7 @@ var DataChannels =
dominantSpeakerEndpoint);
eventEmitter.emit(RTCEvents.DOMINANTSPEAKER_CHANGED, dominantSpeakerEndpoint);
}
else if ("InLastNChangeEvent" === colibriClass)
{
else if ("InLastNChangeEvent" === colibriClass) {
var oldValue = obj.oldValue;
var newValue = obj.newValue;
// Make sure that oldValue and newValue are of type boolean.
@@ -96,15 +82,13 @@ var DataChannels =
eventEmitter.emit(RTCEvents.LASTN_CHANGED, oldValue, newValue);
}
else if ("LastNEndpointsChangeEvent" === colibriClass)
{
else if ("LastNEndpointsChangeEvent" === colibriClass) {
// The new/latest list of last-n endpoint IDs.
var lastNEndpoints = obj.lastNEndpoints;
// The list of endpoint IDs which are entering the list of
// last-n at this time i.e. were not in the old list of last-n
// endpoint IDs.
var endpointsEnteringLastN = obj.endpointsEnteringLastN;
var stream = obj.stream;
console.log(
"Data channel new last-n event: ",
@@ -112,33 +96,13 @@ var DataChannels =
eventEmitter.emit(RTCEvents.LASTN_ENDPOINT_CHANGED,
lastNEndpoints, endpointsEnteringLastN, obj);
}
else if ("SimulcastLayersChangedEvent" === colibriClass)
{
eventEmitter.emit(RTCEvents.SIMULCAST_LAYER_CHANGED,
obj.endpointSimulcastLayers);
}
else if ("SimulcastLayersChangingEvent" === colibriClass)
{
eventEmitter.emit(RTCEvents.SIMULCAST_LAYER_CHANGING,
obj.endpointSimulcastLayers);
}
else if ("StartSimulcastLayerEvent" === colibriClass)
{
eventEmitter.emit(RTCEvents.SIMULCAST_START, obj.simulcastLayer);
}
else if ("StopSimulcastLayerEvent" === colibriClass)
{
eventEmitter.emit(RTCEvents.SIMULCAST_STOP, obj.simulcastLayer);
}
else
{
else {
console.debug("Data channel JSON-formatted message: ", obj);
}
}
};
dataChannel.onclose = function ()
{
dataChannel.onclose = function () {
console.info("The Data Channel closed", dataChannel);
var idx = _dataChannels.indexOf(dataChannel);
if (idx > -1)
@@ -183,19 +147,15 @@ var DataChannels =
},
handleSelectedEndpointEvent: onSelectedEndpointChanged,
handlePinnedEndpointEvent: onPinnedEndpointChanged
};
function onSelectedEndpointChanged(userResource)
{
function onSelectedEndpointChanged(userResource) {
console.log('selected endpoint changed: ', userResource);
if (_dataChannels && _dataChannels.length != 0)
{
if (_dataChannels && _dataChannels.length != 0) {
_dataChannels.some(function (dataChannel) {
if (dataChannel.readyState == 'open')
{
console.log('sending selected endpoint changed '
+ 'notification to the bridge: ', userResource);
if (dataChannel.readyState == 'open') {
console.log('sending selected endpoint changed ' +
'notification to the bridge: ', userResource);
dataChannel.send(JSON.stringify({
'colibriClass': 'SelectedEndpointChangedEvent',
'selectedEndpoint':
@@ -209,14 +169,11 @@ function onSelectedEndpointChanged(userResource)
}
}
function onPinnedEndpointChanged(userResource)
{
function onPinnedEndpointChanged(userResource) {
console.log('pinned endpoint changed: ', userResource);
if (_dataChannels && _dataChannels.length != 0)
{
if (_dataChannels && _dataChannels.length != 0) {
_dataChannels.some(function (dataChannel) {
if (dataChannel.readyState == 'open')
{
if (dataChannel.readyState == 'open') {
dataChannel.send(JSON.stringify({
'colibriClass': 'PinnedEndpointChangedEvent',
'pinnedEndpoint':

View File

@@ -1,8 +1,26 @@
/* global APP */
var StreamEventTypes = require("../../service/RTC/StreamEventTypes.js");
var RTCEvents = require("../../service/RTC/RTCEvents");
var RTCBrowserType = require("./RTCBrowserType");
/**
* This implements 'onended' callback normally fired by WebRTC after the stream
* is stopped. There is no such behaviour yet in FF, so we have to add it.
* @param stream original WebRTC stream object to which 'onended' handling
* will be added.
*/
function implementOnEndedHandling(stream) {
var originalStop = stream.stop;
stream.stop = function () {
originalStop.apply(stream);
if (!stream.ended) {
stream.ended = true;
stream.onended();
}
};
}
function LocalStream(stream, type, eventEmitter, videoType, isGUMStream)
{
function LocalStream(stream, type, eventEmitter, videoType, isGUMStream) {
this.stream = stream;
this.eventEmitter = eventEmitter;
this.type = type;
@@ -11,69 +29,72 @@ function LocalStream(stream, type, eventEmitter, videoType, isGUMStream)
if(isGUMStream === false)
this.isGUMStream = isGUMStream;
var self = this;
if(type == "audio")
{
if(type == "audio") {
this.getTracks = function () {
return self.stream.getAudioTracks();
};
}
else
{
} else {
this.getTracks = function () {
return self.stream.getVideoTracks();
};
}
this.stream.onended = function()
{
this.stream.onended = function () {
self.streamEnded();
};
if (RTCBrowserType.isFirefox()) {
implementOnEndedHandling(this.stream);
}
}
LocalStream.prototype.streamEnded = function () {
this.eventEmitter.emit(StreamEventTypes.EVENT_TYPE_LOCAL_ENDED, this);
}
};
LocalStream.prototype.getOriginalStream = function()
{
return this.stream;
}
};
LocalStream.prototype.isAudioStream = function () {
return this.type === "audio";
};
LocalStream.prototype.setMute = function(mute)
LocalStream.prototype.setMute = function (mute)
{
var isAudio = this.isAudioStream();
var eventType = isAudio ? RTCEvents.AUDIO_MUTE : RTCEvents.VIDEO_MUTE;
if ((window.location.protocol != "https:" && this.isGUMStream) ||
(isAudio && this.isGUMStream) || this.videoType === "screen" ||
// FIXME FF does not support 'removeStream' method used to mute
RTCBrowserType.isFirefox()) {
if((window.location.protocol != "https:" && this.isGUMStream) ||
(this.isAudioStream() && this.isGUMStream) || this.videoType === "screen")
{
var tracks = this.getTracks();
for (var idx = 0; idx < tracks.length; idx++) {
tracks[idx].enabled = mute;
tracks[idx].enabled = !mute;
}
}
else
{
if(mute === false) {
this.eventEmitter.emit(eventType, mute);
} else {
if (mute) {
APP.xmpp.removeStream(this.stream);
this.stream.stop();
}
else
{
this.eventEmitter.emit(eventType, true);
} else {
var self = this;
APP.RTC.rtcUtils.obtainAudioAndVideoPermissions(
(this.isAudioStream() ? ["audio"] : ["video"]),
function (stream) {
if(self.isAudioStream())
{
APP.RTC.changeLocalAudio(stream, function () {});
}
else
{
APP.RTC.changeLocalVideo(stream, false, function () {});
if (isAudio) {
APP.RTC.changeLocalAudio(stream,
function () {
self.eventEmitter.emit(eventType, false);
});
} else {
APP.RTC.changeLocalVideo(stream, false,
function () {
self.eventEmitter.emit(eventType, false);
});
}
});
}
@@ -82,13 +103,10 @@ LocalStream.prototype.setMute = function(mute)
LocalStream.prototype.isMuted = function () {
var tracks = [];
if(this.type == "audio")
{
if (this.isAudioStream()) {
tracks = this.stream.getAudioTracks();
}
else
{
if(this.stream.ended)
} else {
if (this.stream.ended)
return true;
tracks = this.stream.getVideoTracks();
}
@@ -97,10 +115,10 @@ LocalStream.prototype.isMuted = function () {
return false;
}
return true;
}
};
LocalStream.prototype.getId = function () {
return this.stream.getTracks()[0].id;
}
};
module.exports = LocalStream;

View File

@@ -1,6 +1,4 @@
////These lines should be uncommented when require works in app.js
var MediaStreamType = require("../../service/RTC/MediaStreamTypes");
var StreamEventType = require("../../service/RTC/StreamEventTypes");
/**
* Creates a MediaStream object for the given data, session id and ssrc.
@@ -28,33 +26,22 @@ function MediaStream(data, sid, ssrc, browser, eventEmitter) {
this.sid = sid;
this.stream = data.stream;
this.peerjid = data.peerjid;
this.videoType = data.videoType;
this.ssrc = ssrc;
this.type = (this.stream.getVideoTracks().length > 0)?
MediaStreamType.VIDEO_TYPE : MediaStreamType.AUDIO_TYPE;
this.videoType = null;
this.muted = false;
this.eventEmitter = eventEmitter;
}
MediaStream.prototype.getOriginalStream = function()
{
MediaStream.prototype.getOriginalStream = function() {
return this.stream;
};
MediaStream.prototype.setMute = function (value)
{
MediaStream.prototype.setMute = function (value) {
this.stream.muted = value;
this.muted = value;
};
MediaStream.prototype.setVideoType = function (value) {
if(this.videoType === value)
return;
this.videoType = value;
this.eventEmitter.emit(StreamEventType.EVENT_TYPE_REMOTE_CHANGED,
this.peerjid);
};
module.exports = MediaStream;

View File

@@ -1,4 +1,6 @@
/* global APP */
var EventEmitter = require("events");
var RTCBrowserType = require("./RTCBrowserType");
var RTCUtils = require("./RTCUtils.js");
var LocalStream = require("./LocalStream.js");
var DataChannels = require("./DataChannels");
@@ -71,14 +73,11 @@ var RTC = {
this.localStreams[0].getOriginalStream() != stream)
this.localStreams.push(localStream);
if(isMuted === true)
localStream.setMute(false);
localStream.setMute(true);
if(type == "audio")
{
if(type == "audio") {
this.localAudio = localStream;
}
else
{
} else {
this.localVideo = localStream;
}
var eventType = StreamEventTypes.EVENT_TYPE_LOCAL_CREATED;
@@ -89,8 +88,7 @@ var RTC = {
return localStream;
},
removeLocalStream: function (stream) {
for(var i = 0; i < this.localStreams.length; i++)
{
for(var i = 0; i < this.localStreams.length; i++) {
if(this.localStreams[i].getOriginalStream() === stream) {
delete this.localStreams[i];
return;
@@ -99,7 +97,7 @@ var RTC = {
},
createRemoteStream: function (data, sid, thessrc) {
var remoteStream = new MediaStream(data, sid, thessrc,
this.getBrowserType(), eventEmitter);
RTCBrowserType.getBrowserType(), eventEmitter);
var jid = data.peerjid || APP.xmpp.myJid();
if(!this.remoteStreams[jid]) {
this.remoteStreams[jid] = {};
@@ -108,9 +106,6 @@ var RTC = {
eventEmitter.emit(StreamEventTypes.EVENT_TYPE_REMOTE_CREATED, remoteStream);
return remoteStream;
},
getBrowserType: function () {
return this.rtcUtils.browser;
},
getPCConstraints: function () {
return this.rtcUtils.pc_constraints;
},
@@ -121,8 +116,8 @@ var RTC = {
return this.rtcUtils.getUserMediaWithConstraints(um, success_callback,
failure_callback, resolution, bandwidth, fps, desktopStream);
},
attachMediaStream: function (element, stream) {
this.rtcUtils.attachMediaStream(element, stream);
attachMediaStream: function (elSelector, stream) {
this.rtcUtils.attachMediaStream(elSelector, stream);
},
getStreamID: function (stream) {
return this.rtcUtils.getStreamID(stream);
@@ -133,6 +128,9 @@ var RTC = {
setVideoSrc: function (element, src) {
this.rtcUtils.setVideoSrc(element, src);
},
getVideoElementName: function () {
return RTCBrowserType.isTemasysPluginUsed() ? 'object' : 'video';
},
dispose: function() {
if (this.rtcUtils) {
this.rtcUtils = null;
@@ -147,20 +145,6 @@ var RTC = {
function (stream, isUsingScreenStream, callback) {
self.changeLocalVideo(stream, isUsingScreenStream, callback);
}, DesktopSharingEventTypes.NEW_STREAM_CREATED);
APP.xmpp.addListener(XMPPEvents.STREAMS_CHANGED, 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.setVideoType(changedStreams[i].type);
}
}
});
APP.xmpp.addListener(XMPPEvents.CALL_INCOMING, function(event) {
DataChannels.init(event.peerconnection, eventEmitter);
});
@@ -168,16 +152,28 @@ var RTC = {
DataChannels.handleSelectedEndpointEvent);
APP.UI.addListener(UIEvents.PINNED_ENDPOINT,
DataChannels.handlePinnedEndpointEvent);
this.rtcUtils = new RTCUtils(this);
this.rtcUtils.obtainAudioAndVideoPermissions(
null, null, getMediaStreamUsage());
// In case of IE we continue from 'onReady' callback
// passed to RTCUtils constructor. It will be invoked by Temasys plugin
// once it is initialized.
var onReady = function () {
eventEmitter.emit(RTCEvents.RTC_READY, true);
self.rtcUtils.obtainAudioAndVideoPermissions(
null, null, getMediaStreamUsage());
};
this.rtcUtils = new RTCUtils(this, onReady);
// Call onReady() if Temasys plugin is not used
if (!RTCBrowserType.isTemasysPluginUsed()) {
onReady();
}
},
muteRemoteVideoStream: function (jid, value) {
var stream;
if(this.remoteStreams[jid] &&
this.remoteStreams[jid][MediaStreamType.VIDEO_TYPE])
{
this.remoteStreams[jid][MediaStreamType.VIDEO_TYPE]) {
stream = this.remoteStreams[jid][MediaStreamType.VIDEO_TYPE];
}
@@ -202,19 +198,28 @@ var RTC = {
},
changeLocalVideo: function (stream, isUsingScreenStream, callback) {
var oldStream = this.localVideo.getOriginalStream();
var type = (isUsingScreenStream? "screen" : "video");
var type = (isUsingScreenStream ? "screen" : "camera");
var localCallback = callback;
if(this.localVideo.isMuted() && this.localVideo.videoType !== type)
{
if(this.localVideo.isMuted() && this.localVideo.videoType !== type) {
localCallback = function() {
APP.xmpp.setVideoMute(false, APP.UI.setVideoMuteButtonsState);
APP.xmpp.setVideoMute(false, function(mute) {
eventEmitter.emit(RTCEvents.VIDEO_MUTE, mute);
});
callback();
};
}
// FIXME: Workaround for FF/IE/Safari
if (stream && stream.videoStream) {
stream = stream.videoStream;
}
var videoStream = this.rtcUtils.createStream(stream, true);
this.localVideo = this.createLocalStream(videoStream, "video", true, type);
// Stop the stream to trigger onended event for old stream
oldStream.stop();
this.switchVideoStreams(videoStream, oldStream);
APP.xmpp.switchStreams(videoStream, oldStream,localCallback);
},
changeLocalAudio: function (stream, callback) {
@@ -225,45 +230,31 @@ var RTC = {
oldStream.stop();
APP.xmpp.switchStreams(newStream, oldStream, callback, true);
},
/**
* 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() === jid) {
// local video
stream = this.localVideo;
isVideoMuted: function (jid) {
if (jid === APP.xmpp.myJid()) {
var localVideo = APP.RTC.localVideo;
return (!localVideo || localVideo.isMuted());
} else {
var peerStreams = this.remoteStreams[jid];
if(!peerStreams)
return false;
stream = peerStreams[MediaStreamType.VIDEO_TYPE];
if (!APP.RTC.remoteStreams[jid] ||
!APP.RTC.remoteStreams[jid][MediaStreamType.VIDEO_TYPE]) {
return null;
}
return APP.RTC.remoteStreams[jid][MediaStreamType.VIDEO_TYPE].muted;
}
if(stream)
isDesktop = (stream.videoType === "screen");
return isDesktop;
},
setVideoMute: function(mute, callback, options) {
if(!this.localVideo)
setVideoMute: function (mute, callback, options) {
if (!this.localVideo)
return;
if (mute == APP.RTC.localVideo.isMuted())
{
APP.xmpp.sendVideoInfoPresence(mute);
if(callback)
if (callback)
callback(mute);
}
else
{
APP.RTC.localVideo.setMute(!mute);
APP.RTC.localVideo.setMute(mute);
APP.xmpp.setVideoMute(
mute,
callback,

View File

@@ -0,0 +1,171 @@
var currentBrowser;
var browserVersion;
var isAndroid;
var RTCBrowserType = {
RTC_BROWSER_CHROME: "rtc_browser.chrome",
RTC_BROWSER_OPERA: "rtc_browser.opera",
RTC_BROWSER_FIREFOX: "rtc_browser.firefox",
RTC_BROWSER_IEXPLORER: "rtc_browser.iexplorer",
RTC_BROWSER_SAFARI: "rtc_browser.safari",
getBrowserType: function () {
return currentBrowser;
},
isChrome: function () {
return currentBrowser === RTCBrowserType.RTC_BROWSER_CHROME;
},
isOpera: function () {
return currentBrowser === RTCBrowserType.RTC_BROWSER_OPERA;
},
isFirefox: function () {
return currentBrowser === RTCBrowserType.RTC_BROWSER_FIREFOX;
},
isIExplorer: function () {
return currentBrowser === RTCBrowserType.RTC_BROWSER_IEXPLORER;
},
isSafari: function () {
return currentBrowser === RTCBrowserType.RTC_BROWSER_SAFARI;
},
isTemasysPluginUsed: function () {
return RTCBrowserType.isIExplorer() || RTCBrowserType.isSafari();
},
getFirefoxVersion: function () {
return RTCBrowserType.isFirefox() ? browserVersion : null;
},
getChromeVersion: function () {
return RTCBrowserType.isChrome() ? browserVersion : null;
},
usesPlanB: function() {
return RTCBrowserType.isChrome() || RTCBrowserType.isOpera() ||
RTCBrowserType.isTemasysPluginUsed();
},
usesUnifiedPlan: function() {
return RTCBrowserType.isFirefox();
},
/**
* Whether the browser is running on an android device.
*/
isAndroid: function() {
return isAndroid;
}
// Add version getters for other browsers when needed
};
// detectOpera() must be called before detectChrome() !!!
// otherwise Opera wil be detected as Chrome
function detectChrome() {
if (navigator.webkitGetUserMedia) {
currentBrowser = RTCBrowserType.RTC_BROWSER_CHROME;
var userAgent = navigator.userAgent.toLowerCase();
// We can assume that user agent is chrome, because it's
// enforced when 'ext' streaming method is set
var ver = parseInt(userAgent.match(/chrome\/(\d+)\./)[1], 10);
console.log("This appears to be Chrome, ver: " + ver);
return ver;
}
return null;
}
function detectOpera() {
var userAgent = navigator.userAgent;
if (userAgent.match(/Opera|OPR/)) {
currentBrowser = RTCBrowserType.RTC_BROWSER_OPERA;
var version = userAgent.match(/(Opera|OPR) ?\/?(\d+)\.?/)[2];
console.info("This appears to be Opera, ver: " + version);
return version;
}
return null;
}
function detectFirefox() {
if (navigator.mozGetUserMedia) {
currentBrowser = RTCBrowserType.RTC_BROWSER_FIREFOX;
var version = parseInt(
navigator.userAgent.match(/Firefox\/([0-9]+)\./)[1], 10);
console.log('This appears to be Firefox, ver: ' + version);
return version;
}
return null;
}
function detectSafari() {
if ((/^((?!chrome).)*safari/i.test(navigator.userAgent))) {
currentBrowser = RTCBrowserType.RTC_BROWSER_SAFARI;
console.info("This appears to be Safari");
// FIXME detect Safari version when needed
return 1;
}
return null;
}
function detectIE() {
var version;
var ua = window.navigator.userAgent;
var msie = ua.indexOf('MSIE ');
if (msie > 0) {
// IE 10 or older => return version number
version = parseInt(ua.substring(msie + 5, ua.indexOf('.', msie)), 10);
}
var trident = ua.indexOf('Trident/');
if (!version && trident > 0) {
// IE 11 => return version number
var rv = ua.indexOf('rv:');
version = parseInt(ua.substring(rv + 3, ua.indexOf('.', rv)), 10);
}
var edge = ua.indexOf('Edge/');
if (!version && edge > 0) {
// IE 12 => return version number
version = parseInt(ua.substring(edge + 5, ua.indexOf('.', edge)), 10);
}
if (version) {
currentBrowser = RTCBrowserType.RTC_BROWSER_IEXPLORER;
console.info("This appears to be IExplorer, ver: " + version);
}
return version;
}
function detectBrowser() {
var version;
var detectors = [
detectOpera,
detectChrome,
detectFirefox,
detectIE,
detectSafari
];
// Try all browser detectors
for (var i = 0; i < detectors.length; i++) {
version = detectors[i]();
if (version)
return version;
}
console.error("Failed to detect browser type");
return undefined;
}
browserVersion = detectBrowser();
isAndroid = navigator.userAgent.indexOf('Android') != -1;
module.exports = RTCBrowserType;

View File

@@ -1,5 +1,8 @@
var RTCBrowserType = require("../../service/RTC/RTCBrowserType.js");
/* global config, require, attachMediaStream, getUserMedia */
var RTCBrowserType = require("./RTCBrowserType");
var Resolutions = require("../../service/RTC/Resolutions");
var AdapterJS = require("./adapter.screenshare");
var SDPUtil = require("../xmpp/SDPUtil");
var currentResolution = null;
@@ -9,11 +12,9 @@ function getPreviousResolution(resolution) {
var order = Resolutions[resolution].order;
var res = null;
var resName = null;
for(var i in Resolutions)
{
for(var i in Resolutions) {
var tmp = Resolutions[i];
if(res == null || (res.order < tmp.order && tmp.order < order))
{
if(res == null || (res.order < tmp.order && tmp.order < order)) {
resName = i;
res = tmp;
}
@@ -21,53 +22,83 @@ function getPreviousResolution(resolution) {
return resName;
}
function setResolutionConstraints(constraints, resolution, isAndroid)
{
if (resolution && !constraints.video || isAndroid) {
constraints.video = { mandatory: {}, optional: [] };// same behaviour as true
}
function setResolutionConstraints(constraints, resolution) {
var isAndroid = RTCBrowserType.isAndroid();
if(Resolutions[resolution])
{
if (Resolutions[resolution]) {
constraints.video.mandatory.minWidth = Resolutions[resolution].width;
constraints.video.mandatory.minHeight = Resolutions[resolution].height;
}
else
{
if (isAndroid) {
constraints.video.mandatory.minWidth = 320;
constraints.video.mandatory.minHeight = 240;
constraints.video.mandatory.maxFrameRate = 15;
}
else if (isAndroid) {
// FIXME can't remember if the purpose of this was to always request
// low resolution on Android ? if yes it should be moved up front
constraints.video.mandatory.minWidth = 320;
constraints.video.mandatory.minHeight = 240;
constraints.video.mandatory.maxFrameRate = 15;
}
if (constraints.video.mandatory.minWidth)
constraints.video.mandatory.maxWidth = constraints.video.mandatory.minWidth;
constraints.video.mandatory.maxWidth =
constraints.video.mandatory.minWidth;
if (constraints.video.mandatory.minHeight)
constraints.video.mandatory.maxHeight = constraints.video.mandatory.minHeight;
constraints.video.mandatory.maxHeight =
constraints.video.mandatory.minHeight;
}
function getConstraints(um, resolution, bandwidth, fps, desktopStream, isAndroid)
{
function getConstraints(um, resolution, bandwidth, fps, desktopStream) {
var constraints = {audio: false, video: false};
if (um.indexOf('video') >= 0) {
constraints.video = { mandatory: {}, optional: [] };// same behaviour as true
// same behaviour as true
constraints.video = { mandatory: {}, optional: [] };
constraints.video.optional.push({ googLeakyBucket: true });
setResolutionConstraints(constraints, resolution);
}
if (um.indexOf('audio') >= 0) {
constraints.audio = { mandatory: {}, optional: []};// same behaviour as true
if (!RTCBrowserType.isFirefox()) {
// same behaviour as true
constraints.audio = { mandatory: {}, optional: []};
// if it is good enough for hangouts...
constraints.audio.optional.push(
{googEchoCancellation: true},
{googAutoGainControl: true},
{googNoiseSupression: true},
{googHighpassFilter: true},
{googNoisesuppression2: true},
{googEchoCancellation2: true},
{googAutoGainControl2: true}
);
} else {
constraints.audio = true;
}
}
if (um.indexOf('screen') >= 0) {
constraints.video = {
mandatory: {
chromeMediaSource: "screen",
googLeakyBucket: true,
maxWidth: window.screen.width,
maxHeight: window.screen.height,
maxFrameRate: 3
},
optional: []
};
if (RTCBrowserType.isChrome()) {
constraints.video = {
mandatory: {
chromeMediaSource: "screen",
googLeakyBucket: true,
maxWidth: window.screen.width,
maxHeight: window.screen.height,
maxFrameRate: 3
},
optional: []
};
} else if (RTCBrowserType.isTemasysPluginUsed()) {
constraints.video = {
optional: [
{
sourceId: AdapterJS.WebRTCPlugin.plugin.screensharingKey
}
]
};
} else {
console.error(
"'screen' WebRTC media source is supported only in Chrome" +
" and with Temasys plugin");
}
}
if (um.indexOf('desktop') >= 0) {
constraints.video = {
@@ -83,57 +114,44 @@ function getConstraints(um, resolution, bandwidth, fps, desktopStream, isAndroid
};
}
if (constraints.audio) {
// if it is good enough for hangouts...
constraints.audio.optional.push(
{googEchoCancellation: true},
{googAutoGainControl: true},
{googNoiseSupression: true},
{googHighpassFilter: true},
{googNoisesuppression2: true},
{googEchoCancellation2: true},
{googAutoGainControl2: true}
);
}
if (constraints.video) {
constraints.video.optional.push(
{googNoiseReduction: false} // chrome 37 workaround for issue 3807, reenable in M38
);
if (um.indexOf('video') >= 0) {
constraints.video.optional.push(
{googLeakyBucket: true}
);
if (bandwidth) {
if (!constraints.video) {
//same behaviour as true
constraints.video = {mandatory: {}, optional: []};
}
}
if (um.indexOf('video') >= 0) {
setResolutionConstraints(constraints, resolution, isAndroid);
}
if (bandwidth) { // doesn't work currently, see webrtc issue 1846
if (!constraints.video) constraints.video = {mandatory: {}, optional: []};//same behaviour as true
constraints.video.optional.push({bandwidth: bandwidth});
}
if (fps) { // for some cameras it might be necessary to request 30fps
if (fps) {
// for some cameras it might be necessary to request 30fps
// so they choose 30fps mjpg over 10fps yuy2
if (!constraints.video) constraints.video = {mandatory: {}, optional: []};// same behaviour as true;
if (!constraints.video) {
// same behaviour as true;
constraints.video = {mandatory: {}, optional: []};
}
constraints.video.mandatory.minFrameRate = fps;
}
// we turn audio for both audio and video tracks, the fake audio & video seems to work
// only when enabled in one getUserMedia call, we cannot get fake audio separate by fake video
// this later can be a problem with some of the tests
if(RTCBrowserType.isFirefox() && config.firefox_fake_device)
{
constraints.audio = true;
constraints.fake = true;
}
return constraints;
}
function RTCUtils(RTCService)
function RTCUtils(RTCService, onTemasysPluginReady)
{
var self = this;
this.service = RTCService;
if (navigator.mozGetUserMedia) {
console.log('This appears to be Firefox');
var version = parseInt(navigator.userAgent.match(/Firefox\/([0-9]+)\./)[1], 10);
if (version >= 40
&& !config.enableSimulcast && config.useBundle && config.useRtcpMux) {
if (RTCBrowserType.isFirefox()) {
var FFversion = RTCBrowserType.getFirefoxVersion();
if (FFversion >= 40) {
this.peerconnection = mozRTCPeerConnection;
this.browser = RTCBrowserType.RTC_BROWSER_FIREFOX;
this.getUserMedia = navigator.mozGetUserMedia.bind(navigator);
this.pc_constraints = {};
this.attachMediaStream = function (element, stream) {
@@ -150,12 +168,15 @@ function RTCUtils(RTCService)
element[0].play();
};
this.getStreamID = function (stream) {
var tracks = stream.getVideoTracks();
if(!tracks || tracks.length == 0)
{
tracks = stream.getAudioTracks();
var id = stream.id;
if (!id) {
var tracks = stream.getVideoTracks();
if (!tracks || tracks.length === 0) {
tracks = stream.getAudioTracks();
}
id = tracks[0].id;
}
return tracks[0].id.replace(/[\{,\}]/g,"");
return SDPUtil.filter_special_chars(id);
};
this.getVideoSrc = function (element) {
if(!element)
@@ -169,14 +190,14 @@ function RTCUtils(RTCService)
RTCSessionDescription = mozRTCSessionDescription;
RTCIceCandidate = mozRTCIceCandidate;
} else {
console.error(
"Firefox version too old: " + FFversion + ". Required >= 40.");
window.location.href = 'unsupported_browser.html';
return;
}
} else if (navigator.webkitGetUserMedia) {
console.log('This appears to be Chrome');
} else if (RTCBrowserType.isChrome() || RTCBrowserType.isOpera()) {
this.peerconnection = webkitRTCPeerConnection;
this.browser = RTCBrowserType.RTC_BROWSER_CHROME;
this.getUserMedia = navigator.webkitGetUserMedia.bind(navigator);
this.attachMediaStream = function (element, stream) {
element.attr('src', webkitURL.createObjectURL(stream));
@@ -184,7 +205,7 @@ function RTCUtils(RTCService)
this.getStreamID = function (stream) {
// streams from FF endpoints have the characters '{' and '}'
// that make jQuery choke.
return stream.id.replace(/[\{,\}]/g,"");
return SDPUtil.filter_special_chars(stream.id);
};
this.getVideoSrc = function (element) {
if(!element)
@@ -197,7 +218,7 @@ function RTCUtils(RTCService)
};
// DTLS should now be enabled by default but..
this.pc_constraints = {'optional': [{'DtlsSrtpKeyAgreement': 'true'}]};
if (navigator.userAgent.indexOf('Android') != -1) {
if (RTCBrowserType.isAndroid()) {
this.pc_constraints = {}; // disable DTLS on Android
}
if (!webkitMediaStream.prototype.getVideoTracks) {
@@ -211,70 +232,92 @@ function RTCUtils(RTCService)
};
}
}
else
{
try { console.log('Browser does not appear to be WebRTC-capable'); } catch (e) { }
// Detect IE/Safari
else if (RTCBrowserType.isTemasysPluginUsed()) {
//AdapterJS.WebRTCPlugin.setLogLevel(
// AdapterJS.WebRTCPlugin.PLUGIN_LOG_LEVELS.VERBOSE);
AdapterJS.webRTCReady(function (isPlugin) {
self.peerconnection = RTCPeerConnection;
self.getUserMedia = getUserMedia;
self.attachMediaStream = function (elSel, stream) {
if (stream.id === "dummyAudio" || stream.id === "dummyVideo") {
return;
}
attachMediaStream(elSel[0], stream);
};
self.getStreamID = function (stream) {
var id = SDPUtil.filter_special_chars(stream.label);
return id;
};
self.getVideoSrc = function (element) {
if (!element) {
console.warn("Attempt to get video SRC of null element");
return null;
}
var children = element.children;
for (var i = 0; i !== children.length; ++i) {
if (children[i].name === 'streamId') {
return children[i].value;
}
}
//console.info(element.id + " SRC: " + src);
return null;
};
self.setVideoSrc = function (element, src) {
//console.info("Set video src: ", element, src);
if (!src) {
console.warn("Not attaching video stream, 'src' is null");
return;
}
AdapterJS.WebRTCPlugin.WaitForPluginReady();
var stream = AdapterJS.WebRTCPlugin.plugin
.getStreamWithId(AdapterJS.WebRTCPlugin.pageId, src);
attachMediaStream(element, stream);
};
onTemasysPluginReady(isPlugin);
});
} else {
try {
console.log('Browser does not appear to be WebRTC-capable');
} catch (e) { }
window.location.href = 'unsupported_browser.html';
return;
}
}
RTCUtils.prototype.getUserMediaWithConstraints = function(
um, success_callback, failure_callback, resolution,bandwidth, fps,
desktopStream)
{
desktopStream) {
currentResolution = resolution;
// Check if we are running on Android device
var isAndroid = navigator.userAgent.indexOf('Android') != -1;
var constraints = getConstraints(
um, resolution, bandwidth, fps, desktopStream, isAndroid);
um, resolution, bandwidth, fps, desktopStream);
var isFF = navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
console.info("Get media constraints", constraints);
var self = this;
try {
if (config.enableSimulcast
&& constraints.video
&& constraints.video.chromeMediaSource !== 'screen'
&& constraints.video.chromeMediaSource !== 'desktop'
&& !isAndroid
// We currently do not support FF, as it doesn't have multistream support.
&& !isFF) {
APP.simulcast.getUserMedia(constraints, function (stream) {
console.log('onUserMediaSuccess');
self.setAvailableDevices(um, true);
success_callback(stream);
},
function (error) {
console.warn('Failed to get access to local media. Error ', error);
self.setAvailableDevices(um, false);
if (failure_callback) {
failure_callback(error);
}
});
} else {
this.getUserMedia(constraints,
function (stream) {
console.log('onUserMediaSuccess');
self.setAvailableDevices(um, true);
success_callback(stream);
},
function (error) {
self.setAvailableDevices(um, false);
console.warn('Failed to get access to local media. Error ',
error, constraints);
if (failure_callback) {
failure_callback(error);
}
});
}
this.getUserMedia(constraints,
function (stream) {
console.log('onUserMediaSuccess');
self.setAvailableDevices(um, true);
success_callback(stream);
},
function (error) {
self.setAvailableDevices(um, false);
console.warn('Failed to get access to local media. Error ',
error, constraints);
if (failure_callback) {
failure_callback(error);
}
});
} catch (e) {
console.error('GUM failed: ', e);
if(failure_callback) {
@@ -285,16 +328,14 @@ RTCUtils.prototype.getUserMediaWithConstraints = function(
RTCUtils.prototype.setAvailableDevices = function (um, available) {
var devices = {};
if(um.indexOf("video") != -1)
{
if(um.indexOf("video") != -1) {
devices.video = available;
}
if(um.indexOf("audio") != -1)
{
if(um.indexOf("audio") != -1) {
devices.audio = available;
}
this.service.setDeviceAvailability(devices);
}
};
/**
* We ask for audio and video combined stream in order to get permissions and
@@ -320,8 +361,7 @@ RTCUtils.prototype.obtainAudioAndVideoPermissions =
if(usageOptions)
for(var i = 0; i < devices.length; i++)
{
for(var i = 0; i < devices.length; i++) {
var device = devices[i];
if(usageOptions[device] === true)
newDevices.push(device);
@@ -329,15 +369,14 @@ RTCUtils.prototype.obtainAudioAndVideoPermissions =
else
newDevices = devices;
if(newDevices.length === 0)
{
if(newDevices.length === 0) {
successCallback();
return;
}
if (navigator.mozGetUserMedia) {
if (RTCBrowserType.isFirefox() || RTCBrowserType.isTemasysPluginUsed()) {
// With FF we can't split the stream into audio and video because FF
// With FF/IE we can't split the stream into audio and video because FF
// doesn't support media stream constructors. So, we need to get the
// audio stream separately from the video stream using two distinct GUM
// calls. Not very user friendly :-( but we don't have many other
@@ -345,31 +384,41 @@ RTCUtils.prototype.obtainAudioAndVideoPermissions =
//
// Note that we pack those 2 streams in a single object and pass it to
// the successCallback method.
self.getUserMediaWithConstraints(
['audio'],
function (audioStream) {
self.getUserMediaWithConstraints(
['video'],
function (videoStream) {
return self.successCallback({
audioStream: audioStream,
videoStream: videoStream
});
},
function (error) {
console.error('failed to obtain video stream - stop',
error);
return self.successCallback(null);
},
config.resolution || '360');
},
function (error) {
console.error('failed to obtain audio stream - stop',
error);
return self.successCallback(null);
}
);
var obtainVideo = function (audioStream) {
self.getUserMediaWithConstraints(
['video'],
function (videoStream) {
return successCallback({
audioStream: audioStream,
videoStream: videoStream
});
},
function (error) {
console.error(
'failed to obtain video stream - stop', error);
self.errorCallback(error);
},
config.resolution || '360');
};
var obtainAudio = function () {
self.getUserMediaWithConstraints(
['audio'],
function (audioStream) {
if (newDevices.indexOf('video') !== -1)
obtainVideo(audioStream);
},
function (error) {
console.error(
'failed to obtain audio stream - stop', error);
self.errorCallback(error);
}
);
};
if (newDevices.indexOf('audio') !== -1) {
obtainAudio();
} else {
obtainVideo(null);
}
} else {
this.getUserMediaWithConstraints(
newDevices,
@@ -381,13 +430,12 @@ RTCUtils.prototype.obtainAudioAndVideoPermissions =
},
config.resolution || '360');
}
}
};
RTCUtils.prototype.successCallback = function (stream, usageOptions) {
// If this is FF, the stream parameter is *not* a MediaStream object, it's
// an object with two properties: audioStream, videoStream.
if(stream && !navigator.mozGetUserMedia)
// If this is FF or IE, the stream parameter is *not* a MediaStream object,
// it's an object with two properties: audioStream, videoStream.
if (stream && stream.getAudioTracks && stream.getVideoTracks)
console.log('got', stream, stream.getAudioTracks().length,
stream.getVideoTracks().length);
this.handleLocalStream(stream, usageOptions);
@@ -411,8 +459,7 @@ RTCUtils.prototype.errorCallback = function (error) {
return self.errorCallback(error);
}, resolution);
}
else
{
else {
self.getUserMediaWithConstraints(
['audio'],
function (stream) {
@@ -425,11 +472,9 @@ RTCUtils.prototype.errorCallback = function (error) {
}
);
}
};
}
RTCUtils.prototype.handleLocalStream = function(stream, usageOptions)
{
RTCUtils.prototype.handleLocalStream = function(stream, usageOptions) {
// If this is FF, the stream parameter is *not* a MediaStream object, it's
// an object with two properties: audioStream, videoStream.
var audioStream, videoStream;
@@ -451,10 +496,17 @@ RTCUtils.prototype.handleLocalStream = function(stream, usageOptions)
}
}
}
else
{//firefox
audioStream = stream.audioStream;
videoStream = stream.videoStream;
else if (RTCBrowserType.isFirefox() || RTCBrowserType.isTemasysPluginUsed())
{ // Firefox and Temasys plugin
if (stream && stream.audioStream)
audioStream = stream.audioStream;
else
audioStream = new DummyMediaStream("dummyAudio");
if (stream && stream.videoStream)
videoStream = stream.videoStream;
else
videoStream = new DummyMediaStream("dummyVideo");
}
var audioMuted = (usageOptions && usageOptions.audio === false),
@@ -467,28 +519,40 @@ RTCUtils.prototype.handleLocalStream = function(stream, usageOptions)
this.service.createLocalStream(audioStream, "audio", null, null,
audioMuted, audioGUM);
this.service.createLocalStream(videoStream, "video", null, null,
this.service.createLocalStream(videoStream, "video", null, 'camera',
videoMuted, videoGUM);
};
RTCUtils.prototype.createStream = function(stream, isVideo)
{
var newStream = null;
if(window.webkitMediaStream)
{
newStream = new webkitMediaStream();
if(newStream)
{
var tracks = (isVideo? stream.getVideoTracks() : stream.getAudioTracks());
function DummyMediaStream(id) {
this.id = id;
this.label = id;
this.stop = function() { };
this.getAudioTracks = function() { return []; };
this.getVideoTracks = function() { return []; };
}
for (i = 0; i < tracks.length; i++) {
RTCUtils.prototype.createStream = function(stream, isVideo) {
var newStream = null;
if (window.webkitMediaStream) {
newStream = new webkitMediaStream();
if (newStream) {
var tracks = (isVideo ? stream.getVideoTracks() : stream.getAudioTracks());
for (var i = 0; i < tracks.length; i++) {
newStream.addTrack(tracks[i]);
}
}
}
else
newStream = stream;
else {
// FIXME: this is duplicated with 'handleLocalStream' !!!
if (stream) {
newStream = stream;
} else {
newStream =
new DummyMediaStream(isVideo ? "dummyVideo" : "dummyAudio");
}
}
return newStream;
};

File diff suppressed because it is too large Load Diff

View File

@@ -1,3 +1,4 @@
/* global Strophe, APP, $, config, interfaceConfig, toastr */
var UI = {};
var VideoLayout = require("./videolayout/VideoLayout.js");
@@ -20,34 +21,79 @@ var messageHandler = UI.messageHandler;
var Authentication = require("./authentication/Authentication");
var UIUtil = require("./util/UIUtil");
var NicknameHandler = require("./util/NicknameHandler");
var JitsiPopover = require("./util/JitsiPopover");
var CQEvents = require("../../service/connectionquality/CQEvents");
var DesktopSharingEventTypes
= require("../../service/desktopsharing/DesktopSharingEventTypes");
var RTCEvents = require("../../service/RTC/RTCEvents");
var RTCBrowserType = require("../RTC/RTCBrowserType");
var StreamEventTypes = require("../../service/RTC/StreamEventTypes");
var XMPPEvents = require("../../service/xmpp/XMPPEvents");
var UIEvents = require("../../service/UI/UIEvents");
var MemberEvents = require("../../service/members/Events");
var eventEmitter = new EventEmitter();
var roomNode = null;
var roomName = null;
function notifyForInitialMute()
{
function promptDisplayName() {
var message = '<h2 data-i18n="dialog.displayNameRequired">';
message += APP.translation.translateString(
"dialog.displayNameRequired");
message += '</h2>' +
'<input name="displayName" type="text" data-i18n=' +
'"[placeholder]defaultNickname" placeholder="' +
APP.translation.translateString(
"defaultNickname", {name: "Jane Pink"}) +
'" autofocus>';
var buttonTxt
= APP.translation.generateTranslationHTML("dialog.Ok");
var buttons = [];
buttons.push({title: buttonTxt, value: "ok"});
messageHandler.openDialog(null, message,
true,
buttons,
function (e, v, m, f) {
if (v == "ok") {
var displayName = f.displayName;
if (displayName) {
VideoLayout.inputDisplayNameHandler(displayName);
return true;
}
}
e.preventDefault();
},
function () {
var form = $.prompt.getPrompt();
var input = form.find("input[name='displayName']");
input.focus();
var button = form.find("button");
button.attr("disabled", "disabled");
input.keyup(function () {
if(!input.val())
button.attr("disabled", "disabled");
else
button.removeAttr("disabled");
});
}
);
}
function notifyForInitialMute() {
messageHandler.notify(null, "notify.mutedTitle", "connected",
"notify.muted", null, {timeOut: 120000});
}
function setupPrezi()
{
$("#reloadPresentationLink").click(function()
{
function setupPrezi() {
$("#reloadPresentationLink").click(function() {
Prezi.reloadPresentation();
});
}
function setupChat()
{
function setupChat() {
Chat.init();
$("#toggle_smileys").click(function() {
Chat.toggleSmileys();
@@ -61,8 +107,7 @@ function setupToolbars() {
}
function streamHandler(stream, isMuted) {
switch (stream.type)
{
switch (stream.type) {
case "audio":
VideoLayout.changeLocalAudio(stream, isMuted);
break;
@@ -77,15 +122,15 @@ function streamHandler(stream, isMuted) {
function onXmppConnectionFailed(stropheErrorMsg) {
var title = APP.translation.generateTranslatonHTML(
var title = APP.translation.generateTranslationHTML(
"dialog.error");
var message;
if (stropheErrorMsg) {
message = APP.translation.generateTranslatonHTML(
message = APP.translation.generateTranslationHTML(
"dialog.connectErrorWithMsg", {msg: stropheErrorMsg});
} else {
message = APP.translation.generateTranslatonHTML(
message = APP.translation.generateTranslationHTML(
"dialog.connectError");
}
@@ -104,17 +149,16 @@ function 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(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.addStreamListener(function (jid) {
VideoLayout.onVideoTypeChanged(jid);
}, StreamEventTypes.EVENT_TYPE_REMOTE_CHANGED);
APP.RTC.addListener(RTCEvents.LASTN_CHANGED, onLastNChanged);
APP.RTC.addListener(RTCEvents.DOMINANTSPEAKER_CHANGED, function (resourceJid) {
APP.RTC.addListener(RTCEvents.DOMINANTSPEAKER_CHANGED,
function (resourceJid) {
VideoLayout.onDominantSpeakerChanged(resourceJid);
});
APP.RTC.addListener(RTCEvents.LASTN_ENDPOINT_CHANGED,
@@ -122,36 +166,32 @@ function registerListeners() {
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.RTC.addListener(RTCEvents.AVAILABLE_DEVICES_CHANGED,
function (devices) {
VideoLayout.setDeviceAvailabilityIcons(null, devices);
})
APP.statistics.addAudioLevelListener(function(jid, audioLevel)
{
});
APP.RTC.addListener(RTCEvents.VIDEO_MUTE, UI.setVideoMuteButtonsState);
APP.RTC.addListener(RTCEvents.DATA_CHANNEL_OPEN, function () {
// 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 userResource = APP.UI.getLargeVideoResource();
eventEmitter.emit(UIEvents.SELECTED_ENDPOINT, userResource);
});
APP.statistics.addAudioLevelListener(function(jid, audioLevel) {
var resourceJid;
if(jid === APP.statistics.LOCAL_JID)
{
if(jid === APP.statistics.LOCAL_JID) {
resourceJid = AudioLevels.LOCAL_LEVEL;
if(APP.RTC.localAudio.isMuted())
{
if(APP.RTC.localAudio.isMuted()) {
audioLevel = 0;
}
}
else
{
} else {
resourceJid = Strophe.getResourceFromJid(jid);
}
AudioLevels.updateAudioLevel(resourceJid, audioLevel,
UI.getLargeVideoState().userResourceJid);
UI.getLargeVideoResource());
});
APP.desktopsharing.addListener(function () {
ToolbarToggler.showDesktopSharingButton();
@@ -174,16 +214,15 @@ function registerListeners() {
);
});
APP.xmpp.addListener(XMPPEvents.RESERVATION_ERROR, function (code, msg) {
var title = APP.translation.generateTranslatonHTML(
var title = APP.translation.generateTranslationHTML(
"dialog.reservationError");
var message = APP.translation.generateTranslatonHTML(
var message = APP.translation.generateTranslationHTML(
"dialog.reservationErrorMsg", {code: code, msg: msg});
messageHandler.openDialog(
title,
message,
true, {},
function (event, value, message, formVals)
{
function (event, value, message, formVals) {
return false;
}
);
@@ -195,11 +234,10 @@ function registerListeners() {
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");
var title = APP.translation.generateTranslationHTML("dialog.sessTerminated");
messageHandler.openDialog(
title, reason, true, {},
function (event, value, message, formVals)
{
function (event, value, message, formVals) {
return false;
}
);
@@ -211,26 +249,6 @@ function registerListeners() {
APP.xmpp.addListener(XMPPEvents.USER_ID_CHANGED, function (from, id) {
Avatar.setUserAvatar(from, id);
});
APP.xmpp.addListener(XMPPEvents.STREAMS_CHANGED, 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.LOCAL_ROLE_CHANGED, onLocalRoleChanged);
@@ -238,23 +256,93 @@ function registerListeners() {
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_MEMBER_LEFT, onMucMemberLeft);
APP.xmpp.addListener(XMPPEvents.PASSWORD_REQUIRED, onPasswordRequired);
APP.xmpp.addListener(XMPPEvents.CHAT_ERROR_RECEIVED, chatAddError);
APP.xmpp.addListener(XMPPEvents.ETHERPAD, initEtherpad);
APP.xmpp.addListener(XMPPEvents.AUTHENTICATION_REQUIRED,
onAuthenticationRequired);
APP.xmpp.addListener(XMPPEvents.PARTICIPANT_VIDEO_TYPE_CHANGED,
onPeerVideoTypeChanged);
APP.xmpp.addListener(XMPPEvents.DEVICE_AVAILABLE,
function (resource, devices) {
VideoLayout.setDeviceAvailabilityIcons(resource, devices);
});
APP.xmpp.addListener(XMPPEvents.PARTICIPANT_AUDIO_MUTED,
VideoLayout.onAudioMute);
APP.xmpp.addListener(XMPPEvents.PARTICIPANT_VIDEO_MUTED,
VideoLayout.onVideoMute);
APP.xmpp.addListener(XMPPEvents.AUDIO_MUTED_BY_FOCUS, function (doMuteAudio) {
UI.setAudioMuted(doMuteAudio);
});
APP.members.addListener(MemberEvents.DTMF_SUPPORT_CHANGED,
onDtmfSupportChanged);
APP.xmpp.addListener(XMPPEvents.START_MUTED, function (audio, video) {
onDtmfSupportChanged);
APP.xmpp.addListener(XMPPEvents.START_MUTED_SETTING_CHANGED, function (audio, video) {
SettingsMenu.setStartMuted(audio, video);
});
APP.xmpp.addListener(XMPPEvents.START_MUTED_FROM_FOCUS, function (audio, video) {
UI.setInitialMuteFromFocus(audio, video);
});
APP.xmpp.addListener(XMPPEvents.JINGLE_FATAL_ERROR, function (session, error) {
UI.messageHandler.showError("dialog.sorry",
"dialog.internalError");
});
APP.xmpp.addListener(XMPPEvents.SET_LOCAL_DESCRIPTION_ERROR, function () {
messageHandler.showError("dialog.error",
"dialog.SLDFailure");
});
APP.xmpp.addListener(XMPPEvents.SET_REMOTE_DESCRIPTION_ERROR, function () {
messageHandler.showError("dialog.error",
"dialog.SRDFailure");
});
APP.xmpp.addListener(XMPPEvents.CREATE_ANSWER_ERROR, function () {
messageHandler.showError();
});
APP.xmpp.addListener(XMPPEvents.PROMPT_FOR_LOGIN, function () {
// FIXME: re-use LoginDialog which supports retries
UI.showLoginPopup(connect);
});
APP.xmpp.addListener(XMPPEvents.FOCUS_DISCONNECTED, function (focusComponent, retrySec) {
UI.messageHandler.notify(
null, "notify.focus",
'disconnected', "notify.focusFail",
{component: focusComponent, ms: retrySec});
});
APP.xmpp.addListener(XMPPEvents.ROOM_JOIN_ERROR, function (pres) {
UI.messageHandler.openReportDialog(null,
"dialog.joinError", pres);
});
APP.xmpp.addListener(XMPPEvents.ROOM_CONNECT_ERROR, function (pres) {
UI.messageHandler.openReportDialog(null,
"dialog.connectError", pres);
});
APP.xmpp.addListener(XMPPEvents.READY_TO_JOIN, function () {
var roomName = UI.generateRoomName();
APP.xmpp.allocateConferenceFocus(roomName, UI.checkForNicknameAndJoin);
});
//NicknameHandler emits this event
UI.addListener(UIEvents.NICKNAME_CHANGED, function (nickname) {
APP.xmpp.addToPresence("displayName", nickname);
});
UI.addListener(UIEvents.LARGEVIDEO_INIT, function () {
AudioLevels.init();
});
if (!interfaceConfig.filmStripOnly) {
APP.xmpp.addListener(XMPPEvents.MESSAGE_RECEIVED, updateChatConversation);
APP.xmpp.addListener(XMPPEvents.CHAT_ERROR_RECEIVED, chatAddError);
// Listens for video interruption events.
APP.xmpp.addListener(XMPPEvents.CONNECTION_INTERRUPTED, VideoLayout.onVideoInterrupted);
// Listens for video restores events.
APP.xmpp.addListener(XMPPEvents.CONNECTION_RESTORED, VideoLayout.onVideoRestored);
}
}
@@ -273,172 +361,151 @@ function setVideoMute(mute, options) {
options);
}
function onResize() {
Chat.resizeChat();
VideoLayout.resizeLargeVideoContainer();
}
function bindEvents()
{
function bindEvents() {
/**
* Resizes and repositions videos in full screen mode.
*/
$(document).on('webkitfullscreenchange mozfullscreenchange fullscreenchange',
function () {
VideoLayout.resizeLargeVideoContainer();
VideoLayout.positionLarge();
}
);
onResize);
$(window).resize(function () {
VideoLayout.resizeLargeVideoContainer();
VideoLayout.positionLarge();
});
$(window).resize(onResize);
}
UI.start = function (init) {
document.title = interfaceConfig.APP_NAME;
var setupWelcomePage = null;
if(config.enableWelcomePage && window.location.pathname == "/" &&
(!window.localStorage.welcomePageDisabled || window.localStorage.welcomePageDisabled == "false"))
{
(!window.localStorage.welcomePageDisabled ||
window.localStorage.welcomePageDisabled == "false")) {
$("#videoconference_page").hide();
var setupWelcomePage = require("./welcome_page/WelcomePage");
if (!setupWelcomePage)
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});
$.prompt.setDefaults({persistent: false});
registerListeners();
VideoLayout.init(eventEmitter);
AudioLevels.init();
NicknameHandler.init(eventEmitter);
registerListeners();
bindEvents();
setupPrezi();
setupToolbars();
setupChat();
if (!interfaceConfig.filmStripOnly) {
$("#videospace").mousemove(function () {
return ToolbarToggler.showToolbar();
});
setupToolbars();
setupChat();
// Display notice message at the top of the toolbar
if (config.noticeMessage) {
$('#noticeText').text(config.noticeMessage);
$('#notice').css({display: 'block'});
}
$("#downloadlog").click(function (event) {
dump(event.target);
});
}
else
{
$("#header").css("display", "none");
$("#bottomToolbar").css("display", "none");
$("#downloadlog").css("display", "none");
$("#remoteVideos").css("padding", "0px 0px 18px 0px");
$("#remoteVideos").css("right", "0px");
messageHandler.disableNotifications();
$('body').popover("disable");
// $("[data-toggle=popover]").popover("disable");
JitsiPopover.enabled = false;
}
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;
if(config.requireDisplayName) {
var currentSettings = Settings.getSettings();
if (!currentSettings.displayName) {
promptDisplayName();
}
}
$("#welcome_page").hide();
init();
// Display notice message at the top of the toolbar
if (config.noticeMessage) {
$('#noticeText').text(config.noticeMessage);
$('#notice').css({display: 'block'});
}
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();
if (!interfaceConfig.filmStripOnly) {
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();
}
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)
{
function chatAddError(errorMessage, originalText) {
return Chat.chatAddError(errorMessage, originalText);
};
}
function chatSetSubject(text)
{
function chatSetSubject(text) {
return Chat.chatSetSubject(text);
};
}
function updateChatConversation(from, displayName, message) {
return Chat.updateChatConversation(from, displayName, message);
};
function updateChatConversation(from, displayName, message, myjid, stamp) {
return Chat.updateChatConversation(from, displayName, message, myjid, stamp);
}
function onMucJoined(jid, info) {
Toolbar.updateRoomUrl(window.location.href);
var meHTML = APP.translation.generateTranslatonHTML("me");
var meHTML = APP.translation.generateTranslationHTML("me");
$("#localNick").html(Strophe.getResourceFromJid(jid) + " (" + meHTML + ")");
var settings = Settings.getSettings();
// Make sure we configure our avatar id, before creating avatar for us
Avatar.setUserAvatar(jid, settings.email || settings.uid);
// Add myself to the contact list.
ContactList.addContact(jid, settings.email || settings.uid);
ContactList.addContact(jid);
// Once we've joined the muc show the toolbar
ToolbarToggler.showToolbar();
var displayName = !config.displayJids
? info.displayName : Strophe.getResourceFromJid(jid);
var displayName =
config.displayJids ? Strophe.getResourceFromJid(jid) : info.displayName;
if (displayName)
onDisplayNameChanged('localVideoContainer', displayName);
@@ -449,7 +516,7 @@ function onMucJoined(jid, info) {
function initEtherpad(name) {
Etherpad.init(name);
};
}
function onMucMemberLeft(jid) {
console.log('left.muc', jid);
@@ -458,32 +525,17 @@ function onMucMemberLeft(jid) {
messageHandler.notify(displayName,'notify.somebody',
'disconnected',
'notify.disconnected');
if(!config.startAudioMuted ||
config.startAudioMuted > APP.members.size())
if (!config.startAudioMuted ||
config.startAudioMuted > APP.members.size()) {
UIUtil.playSoundNotification('userLeft');
// 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);
}
ContactList.removeContact(jid);
VideoLayout.participantLeft(jid);
}
};
function onLocalRoleChanged(jid, info, pres, isModerator)
{
function onLocalRoleChanged(jid, info, pres, isModerator) {
console.info("My role changed, new role: " + info.role);
onModeratorStatusChanged(isModerator);
VideoLayout.showModeratorIndicator();
@@ -493,11 +545,12 @@ function onLocalRoleChanged(jid, info, pres, isModerator)
Authentication.closeAuthenticationWindow();
messageHandler.notify(null, "notify.me",
'connected', "notify.moderator");
Toolbar.checkAutoRecord();
}
}
function onModeratorStatusChanged(isModerator) {
Toolbar.showSipCallButton(isModerator);
Toolbar.showRecordingButton(
isModerator); //&&
@@ -505,11 +558,7 @@ function onModeratorStatusChanged(isModerator) {
// 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 onPasswordRequired(callback) {
// password is required
@@ -555,16 +604,23 @@ function onMucMemberJoined(jid, id, displayName) {
'connected',
'notify.connected');
if(!config.startAudioMuted ||
if (!config.startAudioMuted ||
config.startAudioMuted > APP.members.size())
UIUtil.playSoundNotification('userJoined');
// Configure avatar
Avatar.setUserAvatar(jid, id);
// Add Peer's container
VideoLayout.ensurePeerContainerExists(jid,id);
VideoLayout.ensurePeerContainerExists(jid);
}
function onMucPresenceStatus( jid, info) {
VideoLayout.setPresenceStatus(
'participant_' + Strophe.getResourceFromJid(jid), info.status);
function onMucPresenceStatus(jid, info) {
VideoLayout.setPresenceStatus(Strophe.getResourceFromJid(jid), info.status);
}
function onPeerVideoTypeChanged(resourceJid, newVideoType) {
VideoLayout.onVideoTypeChanged(resourceJid, newVideoType);
}
function onMucRoleChanged(role, displayName) {
@@ -575,8 +631,7 @@ function onMucRoleChanged(role, displayName) {
if (!displayName) {
messageKey = "notify.grantedToUnknown";
}
else
{
else {
messageKey = "notify.grantedTo";
messageOptions = {to: displayName};
}
@@ -592,7 +647,7 @@ function onAuthenticationRequired(intervalCallback) {
roomName, intervalCallback, function () {
Toolbar.authenticateClicked();
});
};
}
function onLastNChanged(oldValue, newValue) {
@@ -626,23 +681,20 @@ UI.inputDisplayNameHandler = function (value) {
VideoLayout.inputDisplayNameHandler(value);
};
UI.getLargeVideoState = function()
{
return VideoLayout.getLargeVideoState();
UI.getLargeVideoResource = function () {
return VideoLayout.getLargeVideoResource();
};
UI.generateRoomName = function() {
if(roomName)
return roomName;
var roomnode = null;
UI.getRoomNode = function () {
if (roomNode)
return roomNode;
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);
roomNode = config.getroomnode(path);
} else {
/* fall back to default strategy
* this is making assumptions about how the URL->room mapping happens.
@@ -653,28 +705,31 @@ UI.generateRoomName = function() {
}
*/
if (path.length > 1) {
roomnode = path.substr(1).toLowerCase();
roomNode = path.substr(1).toLowerCase();
} else {
var word = RoomNameGenerator.generateRoomWithoutSeparator();
roomnode = word.toLowerCase();
roomNode = word.toLowerCase();
window.history.pushState('VideoChat',
'Room: ' + word, window.location.pathname + word);
'Room: ' + word, window.location.pathname + word);
}
}
return roomNode;
};
roomName = roomnode + '@' + config.hosts.muc;
UI.generateRoomName = function () {
if (roomName)
return roomName;
var roomNode = UI.getRoomNode();
roomName = roomNode + '@' + config.hosts.muc;
return roomName;
};
UI.connectionIndicatorShowMore = function(id)
{
return VideoLayout.connectionIndicators[id].showMore();
UI.connectionIndicatorShowMore = function(jid) {
return VideoLayout.showMore(jid);
};
UI.showLoginPopup = function(callback)
{
UI.showLoginPopup = function(callback) {
console.log('password is required');
var message = '<h2 data-i18n="dialog.passwordRequired">';
message += APP.translation.translateString(
@@ -698,7 +753,7 @@ UI.showLoginPopup = function(callback)
null, null, ':input:first'
);
}
};
UI.checkForNicknameAndJoin = function () {
@@ -717,12 +772,12 @@ 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 data = APP.xmpp.getJingleLog();
var metadata = {};
metadata.time = new Date();
metadata.url = window.location.href;
metadata.ua = navigator.userAgent;
var log = APP.xmpp.getLogger();
var log = APP.xmpp.getXmppLog();
if (log) {
metadata.xmpp = log;
}
@@ -736,10 +791,13 @@ UI.getRoomName = function () {
};
UI.setInitialMuteFromFocus = function (muteAudio, muteVideo) {
if(muteAudio || muteVideo) notifyForInitialMute();
if(muteAudio) UI.setAudioMuted(true);
if(muteVideo) UI.setVideoMute(true);
}
if (muteAudio || muteVideo)
notifyForInitialMute();
if (muteAudio)
UI.setAudioMuted(true);
if (muteVideo)
UI.setVideoMute(true);
};
/**
* Mutes/unmutes the local video.
@@ -760,50 +818,48 @@ UI.toggleAudio = function() {
*/
UI.setAudioMuted = function (mute, earlyMute) {
var audioMute = null;
if(earlyMute)
if (earlyMute)
audioMute = function (mute, cb) {
return APP.xmpp.sendAudioInfoPresence(mute, cb);
};
else
audioMute = function (mute, cb) {
return APP.xmpp.setAudioMute(mute, cb);
}
if(!audioMute(mute, function () {
VideoLayout.showLocalAudioIndicator(mute);
};
if (!audioMute(mute, function () {
VideoLayout.showLocalAudioIndicator(mute);
UIUtil.buttonClick("#mute", "icon-microphone icon-mic-disabled");
}))
{
UIUtil.buttonClick("#toolbar_button_mute", "icon-microphone icon-mic-disabled");
})) {
// We still click the button.
UIUtil.buttonClick("#mute", "icon-microphone icon-mic-disabled");
UIUtil.buttonClick("#toolbar_button_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);
}
};
UI.setVideoMuteButtonsState = function (mute) {
var video = $('#video');
var video = $('#toolbar_button_camera');
var communicativeClass = "icon-camera";
var muteClass = "icon-camera icon-camera-disabled";
@@ -814,8 +870,14 @@ UI.setVideoMuteButtonsState = function (mute) {
video.removeClass(muteClass);
video.addClass(communicativeClass);
}
}
};
UI.userAvatarChanged = function (resourceJid, thumbUrl, contactListUrl) {
VideoLayout.userAvatarChanged(resourceJid, thumbUrl);
ContactList.userAvatarChanged(resourceJid, contactListUrl);
if(resourceJid === APP.xmpp.myResource())
SettingsMenu.changeAvatar(thumbUrl);
};
UI.setVideoMute = setVideoMute;

View File

@@ -1,15 +1,16 @@
/* global APP, interfaceConfig, $, Strophe */
var CanvasUtil = require("./CanvasUtils");
var ASDrawContext = $('#activeSpeakerAudioLevel')[0].getContext('2d');
var ASDrawContext = null;
function initActiveSpeakerAudioLevels() {
var ASRadius = interfaceConfig.ACTIVE_SPEAKER_AVATAR_SIZE / 2;
var ASCenter = (interfaceConfig.ACTIVE_SPEAKER_AVATAR_SIZE + ASRadius) / 2;
// Draw a circle.
// Draw a circle.
ASDrawContext.arc(ASCenter, ASCenter, ASRadius, 0, 2 * Math.PI);
// Add a shadow around the circle
// Add a shadow around the circle
ASDrawContext.shadowColor = interfaceConfig.SHADOW_COLOR;
ASDrawContext.shadowOffsetX = 0;
ASDrawContext.shadowOffsetY = 0;
@@ -24,8 +25,9 @@ var AudioLevels = (function(my) {
my.LOCAL_LEVEL = 'local';
my.init = function () {
ASDrawContext = $('#activeSpeakerAudioLevel')[0].getContext('2d');
initActiveSpeakerAudioLevels();
}
};
/**
* Updates the audio level canvas for the given peerJid. If the canvas
@@ -112,7 +114,7 @@ var AudioLevels = (function(my) {
resourceJid = APP.xmpp.myResource();
}
if(resourceJid === largeVideoResourceJid) {
if(resourceJid === largeVideoResourceJid) {
window.requestAnimationFrame(function () {
AudioLevels.updateActiveSpeakerAudioLevel(audioLevel);
});
@@ -120,10 +122,9 @@ var AudioLevels = (function(my) {
};
my.updateActiveSpeakerAudioLevel = function(audioLevel) {
if($("#activeSpeaker").css("visibility") == "hidden")
if($("#activeSpeaker").css("visibility") == "hidden" || ASDrawContext === null)
return;
ASDrawContext.clearRect(0, 0, 300, 300);
if(audioLevel == 0)
return;
@@ -165,10 +166,9 @@ var AudioLevels = (function(my) {
* error. Since audio levels are frequently updated, the errors have
* been observed to pile into the console, strain the CPU.
*/
if (audioLevelCanvasOrig)
{
audioLevelCanvasCache[resourceJid]
= CanvasUtil.cloneCanvas(audioLevelCanvasOrig);
if (audioLevelCanvasOrig) {
audioLevelCanvasCache[resourceJid] =
CanvasUtil.cloneCanvas(audioLevelCanvasOrig);
}
}
@@ -183,15 +183,16 @@ var AudioLevels = (function(my) {
var shadowLevel = getShadowLevel(audioLevel);
if (shadowLevel > 0)
if (shadowLevel > 0) {
// drawContext, x, y, w, h, r, shadowColor, shadowLevel
CanvasUtil.drawRoundRectGlow( drawContext,
interfaceConfig.CANVAS_EXTRA/2, interfaceConfig.CANVAS_EXTRA/2,
CanvasUtil.drawRoundRectGlow(drawContext,
interfaceConfig.CANVAS_EXTRA / 2, interfaceConfig.CANVAS_EXTRA / 2,
canvas.width - interfaceConfig.CANVAS_EXTRA,
canvas.height - interfaceConfig.CANVAS_EXTRA,
interfaceConfig.CANVAS_RADIUS,
interfaceConfig.SHADOW_COLOR,
shadowLevel);
}
}
/**
@@ -221,9 +222,8 @@ var AudioLevels = (function(my) {
*/
function getVideoSpanId(resourceJid) {
var videoSpanId = null;
if (resourceJid === AudioLevels.LOCAL_LEVEL
|| (APP.xmpp.myResource() && resourceJid
=== APP.xmpp.myResource()))
if (resourceJid === AudioLevels.LOCAL_LEVEL ||
(APP.xmpp.myResource() && resourceJid === APP.xmpp.myResource()))
videoSpanId = 'localVideoContainer';
else
videoSpanId = 'participant_' + resourceJid;
@@ -251,10 +251,10 @@ var AudioLevels = (function(my) {
if (resized)
Object.keys(audioLevelCanvasCache).forEach(function (resourceJid) {
audioLevelCanvasCache[resourceJid].width
= width + interfaceConfig.CANVAS_EXTRA;
audioLevelCanvasCache[resourceJid].height
= height + interfaceConfig.CANVAS_EXTRA;
audioLevelCanvasCache[resourceJid].width =
width + interfaceConfig.CANVAS_EXTRA;
audioLevelCanvasCache[resourceJid].height =
height + interfaceConfig.CANVAS_EXTRA;
});
});

View File

@@ -22,13 +22,13 @@ var Authentication = {
var room = roomName.substr(0, roomName.indexOf('@'));
var title
= APP.translation.generateTranslatonHTML("dialog.WaitingForHost");
= APP.translation.generateTranslationHTML("dialog.WaitingForHost");
var msg
= APP.translation.generateTranslatonHTML(
= APP.translation.generateTranslationHTML(
"dialog.WaitForHostMsg", {room: room});
var buttonTxt
= APP.translation.generateTranslatonHTML("dialog.IamHost");
= APP.translation.generateTranslationHTML("dialog.IamHost");
var buttons = [];
buttons.push({title: buttonTxt, value: "authNow"});
@@ -79,7 +79,7 @@ var Authentication = {
Moderator.allocateConferenceFocus(roomName, function () {
// If it's not "on the fly" authentication now join
// the conference room
if (!APP.xmpp.getMUCJoined()) {
if (!APP.xmpp.isMUCJoined()) {
APP.UI.checkForNicknameAndJoin();
}
});

View File

@@ -31,9 +31,9 @@ function Dialog(callback, obtainSession) {
'type="password" data-i18n="[placeholder]dialog.userPassword"' +
' placeholder="user password">';
var okButton = APP.translation.generateTranslatonHTML("dialog.Ok");
var okButton = APP.translation.generateTranslationHTML("dialog.Ok");
var cancelButton = APP.translation.generateTranslatonHTML("dialog.Cancel");
var cancelButton = APP.translation.generateTranslationHTML("dialog.Cancel");
var states = {
login: {

View File

@@ -1,45 +1,6 @@
var Settings = require("../../settings/Settings");
var MediaStreamType = require("../../../service/RTC/MediaStreamTypes");
var users = {};
var activeSpeakerJid;
function setVisibility(selector, show) {
if (selector && selector.length > 0) {
selector.css("visibility", show ? "visible" : "hidden");
}
}
function isUserMuted(jid) {
// XXX(gp) we may want to rename this method to something like
// isUserStreaming, for example.
if (jid && jid != APP.xmpp.myJid()) {
var resource = Strophe.getResourceFromJid(jid);
if (!require("../videolayout/VideoLayout").isInLastN(resource)) {
return true;
}
}
if(jid && jid == APP.xmpp.myJid())
{
var localVideo = APP.RTC.localVideo;
return (!localVideo || localVideo.isMuted());
}
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 = {
@@ -56,104 +17,48 @@ var Avatar = {
}
users[jid] = id;
}
var thumbUrl = getGravatarUrl(users[jid] || jid, 100);
var contactListUrl = getGravatarUrl(users[jid] || jid);
var thumbUrl = this.getThumbUrl(jid);
var contactListUrl = this.getContactListUrl(jid);
var resourceJid = Strophe.getResourceFromJid(jid);
var thumbnail = $('#participant_' + resourceJid);
var avatar = $('#avatar_' + resourceJid);
// set the avatar in the settings menu if it is local user and get the
// local video container
if (jid === APP.xmpp.myJid()) {
$('#avatar').get(0).src = thumbUrl;
thumbnail = $('#localVideoContainer');
}
// set the avatar in the contact list
var contact = $('#' + resourceJid + '>img');
if (contact && contact.length > 0) {
contact.get(0).src = contactListUrl;
}
// set the avatar in the thumbnail
if (avatar && avatar.length > 0) {
avatar[0].src = thumbUrl;
} else {
if (thumbnail && thumbnail.length > 0) {
avatar = document.createElement('img');
avatar.id = 'avatar_' + resourceJid;
avatar.className = 'userAvatar';
avatar.src = thumbUrl;
thumbnail.append(avatar);
}
}
//if the user is the current active speaker - update the active speaker
// avatar
if (jid === activeSpeakerJid) {
this.updateActiveSpeakerAvatarSrc(jid);
}
APP.UI.userAvatarChanged(resourceJid, thumbUrl, contactListUrl);
},
/**
* Hides or shows the user's avatar
* @param jid jid of the user
* @param show whether we should show the avatar or not
* video because there is no dominant speaker and no focused speaker
* Returns image URL for the avatar to be displayed on large video area
* where current active speaker is presented.
* @param jid full MUC jid of the user for whom we want to obtain avatar URL
*/
showUserAvatar: function (jid, show) {
if (users[jid]) {
var resourceJid = Strophe.getResourceFromJid(jid);
var video = $('#participant_' + resourceJid + '>video');
var avatar = $('#avatar_' + resourceJid);
if (jid === APP.xmpp.myJid()) {
video = $('#localVideoWrapper>video');
}
if (show === undefined || show === null) {
show = isUserMuted(jid);
}
//if the user is the currently focused, the dominant speaker or if
//there is no focused and no dominant speaker and the large video is
//currently shown
if (activeSpeakerJid === jid && require("../videolayout/VideoLayout").isLargeVideoOnTop()) {
setVisibility($("#largeVideo"), !show);
setVisibility($('#activeSpeaker'), show);
setVisibility(avatar, false);
setVisibility(video, false);
} else {
if (video && video.length > 0) {
setVisibility(video, !show);
}
setVisibility(avatar, show);
}
}
getActiveSpeakerUrl: function (jid) {
return this.getGravatarUrl(jid, 100);
},
/**
* Updates the src of the active speaker avatar
* @param jid of the current active speaker
* Returns image URL for the avatar to be displayed on small video thumbnail
* @param jid full MUC jid of the user for whom we want to obtain avatar URL
*/
updateActiveSpeakerAvatarSrc: function (jid) {
getThumbUrl: function (jid) {
return this.getGravatarUrl(jid, 100);
},
/**
* Returns the URL for the avatar to be displayed as contactlist item
* @param jid full MUC jid of the user for whom we want to obtain avatar URL
*/
getContactListUrl: function (jid) {
return this.getGravatarUrl(jid, 30);
},
getGravatarUrl: function (jid, size) {
if (!jid) {
jid = APP.xmpp.findJidFromResource(
require("../videolayout/VideoLayout").getLargeVideoState().userResourceJid);
console.error("Get gravatar - jid is undefined");
return null;
}
var avatar = $("#activeSpeakerAvatar")[0];
var url = getGravatarUrl(users[jid],
interfaceConfig.ACTIVE_SPEAKER_AVATAR_SIZE);
if (jid === activeSpeakerJid && avatar.src === url) {
return;
}
activeSpeakerJid = jid;
var isMuted = isUserMuted(jid);
if (jid && isMuted !== null) {
avatar.src = url;
setVisibility($("#largeVideo"), !isMuted);
Avatar.showUserAvatar(jid, isMuted);
var id = users[jid];
if (!id) {
console.warn(
"No avatar stored yet for " + jid + " - using JID as ID");
id = jid;
}
return 'https://www.gravatar.com/avatar/' +
MD5.hexdigest(id.trim().toLowerCase()) +
"?d=wavatar&size=" + (size || "30");
}
};

View File

@@ -26,50 +26,34 @@ function resize() {
}
}
/**
* Shares the Etherpad name with other participants.
*/
function shareEtherpad() {
APP.xmpp.addToPresence("etherpad", etherpadName);
}
/**
* Creates the Etherpad button and adds it to the toolbar.
*/
function enableEtherpadButton() {
if (!$('#etherpadButton').is(":visible"))
$('#etherpadButton').css({display: 'inline-block'});
if (!$('#toolbar_button_etherpad').is(":visible"))
$('#toolbar_button_etherpad').css({display: 'inline-block'});
}
/**
* Creates the IFrame for the etherpad.
*/
function createIFrame() {
etherpadIFrame = document.createElement('iframe');
etherpadIFrame.src = domain + etherpadName + options;
etherpadIFrame.frameBorder = 0;
etherpadIFrame.scrolling = "no";
etherpadIFrame.width = $('#largeVideoContainer').width() || 640;
etherpadIFrame.height = $('#largeVideoContainer').height() || 480;
etherpadIFrame.setAttribute('style', 'visibility: hidden;');
etherpadIFrame = VideoLayout.createEtherpadIframe(
domain + etherpadName + options, function() {
document.getElementById('etherpad').appendChild(etherpadIFrame);
etherpadIFrame.onload = function() {
document.domain = document.domain;
bubbleIframeMouseMove(etherpadIFrame);
setTimeout(function() {
// the iframes inside of the etherpad are
// not yet loaded when the etherpad iframe is loaded
var outer = etherpadIFrame.
contentDocument.getElementsByName("ace_outer")[0];
bubbleIframeMouseMove(outer);
var inner = outer.
contentDocument.getElementsByName("ace_inner")[0];
bubbleIframeMouseMove(inner);
}, 2000);
};
document.domain = document.domain;
bubbleIframeMouseMove(etherpadIFrame);
setTimeout(function() {
// the iframes inside of the etherpad are
// not yet loaded when the etherpad iframe is loaded
var outer = etherpadIFrame.
contentDocument.getElementsByName("ace_outer")[0];
bubbleIframeMouseMove(outer);
var inner = outer.
contentDocument.getElementsByName("ace_inner")[0];
bubbleIframeMouseMove(inner);
}, 2000);
});
}
function bubbleIframeMouseMove(iframe){
@@ -100,33 +84,17 @@ function bubbleIframeMouseMove(iframe){
}
/**
* On video selected event.
*/
$(document).bind('video.selected', function (event, isPresentation) {
if (config.etherpad_base && etherpadIFrame && etherpadIFrame.style.visibility !== 'hidden')
Etherpad.toggleEtherpad(isPresentation);
});
var Etherpad = {
/**
* Initializes the etherpad.
*/
init: function (name) {
if (config.etherpad_base && !etherpadName) {
if (config.etherpad_base && !etherpadName && name) {
domain = config.etherpad_base;
if (!name) {
// In case we're the focus we generate the name.
etherpadName = Math.random().toString(36).substring(7) +
'_' + (new Date().getTime()).toString();
shareEtherpad();
}
else
etherpadName = name;
etherpadName = name;
enableEtherpadButton();
@@ -146,49 +114,17 @@ var Etherpad = {
if (!etherpadIFrame)
createIFrame();
var largeVideo = null;
if (Prezi.isPresentationVisible())
largeVideo = $('#presentation>iframe');
else
largeVideo = $('#largeVideo');
if ($('#etherpad>iframe').css('visibility') === 'hidden') {
$('#activeSpeaker').css('visibility', 'hidden');
largeVideo.fadeOut(300, function () {
if (Prezi.isPresentationVisible()) {
largeVideo.css({opacity: '0'});
} else {
VideoLayout.setLargeVideoVisible(false);
}
});
$('#etherpad>iframe').fadeIn(300, function () {
document.body.style.background = '#eeeeee';
$('#etherpad>iframe').css({visibility: 'visible'});
$('#etherpad').css({zIndex: 2});
});
if(VideoLayout.getLargeVideoState() === "etherpad")
{
VideoLayout.setLargeVideoState("video");
}
else if ($('#etherpad>iframe')) {
$('#etherpad>iframe').fadeOut(300, function () {
$('#etherpad>iframe').css({visibility: 'hidden'});
$('#etherpad').css({zIndex: 0});
document.body.style.background = 'black';
});
if (!isPresentation) {
$('#largeVideo').fadeIn(300, function () {
VideoLayout.setLargeVideoVisible(true);
});
}
else
{
VideoLayout.setLargeVideoState("etherpad");
}
resize();
},
isVisible: function() {
var etherpadIframe = $('#etherpad>iframe');
return etherpadIframe && etherpadIframe.is(':visible');
}
};
module.exports = Etherpad;

View File

@@ -6,6 +6,20 @@ var PreziPlayer = require("./PreziPlayer");
var preziPlayer = null;
/**
* Shows/hides a presentation.
*/
function setPresentationVisible(visible) {
if (visible) {
VideoLayout.setLargeVideoState("prezi");
}
else {
VideoLayout.setLargeVideoState("video");
}
}
var Prezi = {
@@ -58,13 +72,13 @@ var Prezi = {
);
}
else {
var html = APP.translation.generateTranslatonHTML(
var html = APP.translation.generateTranslationHTML(
"dialog.sharePreziTitle");
var cancelButton = APP.translation.generateTranslatonHTML(
var cancelButton = APP.translation.generateTranslationHTML(
"dialog.Cancel");
var shareButton = APP.translation.generateTranslatonHTML(
var shareButton = APP.translation.generateTranslationHTML(
"dialog.Share");
var backButton = APP.translation.generateTranslatonHTML(
var backButton = APP.translation.generateTranslationHTML(
"dialog.Back");
var buttons = [];
var buttons1 = [];
@@ -75,7 +89,7 @@ var Prezi = {
buttons.push({title: shareButton, value: true});
// Back button
buttons1.push({title: backButton, value: true});
var linkError = APP.translation.generateTranslatonHTML(
var linkError = APP.translation.generateTranslationHTML(
"dialog.preziLinkError");
var defaultUrl = APP.translation.translateString("defaultPreziLink",
{url: "http://prezi.com/wz7vhjycl7e6/my-prezi"});
@@ -165,18 +179,14 @@ function presentationAdded(event, jid, presUrl, currentSlide) {
+ Strophe.getResourceFromJid(jid)
+ '_' + presId;
// We explicitly don't specify the peer jid here, because we don't want
// this video to be dealt with as a peer related one (for example we
// don't want to show a mute/kick menu for this one, etc.).
VideoLayout.addRemoteVideoContainer(null, elementId);
VideoLayout.resizeThumbnails();
VideoLayout.addPreziContainer(elementId);
var controlsEnabled = false;
if (jid === APP.xmpp.myJid())
controlsEnabled = true;
setPresentationVisible(true);
$('#largeVideoContainer').hover(
VideoLayout.setLargeVideoHover(
function (event) {
if (Prezi.isPresentationVisible()) {
var reloadButtonRight = window.innerWidth
@@ -302,38 +312,6 @@ function resize() {
}
}
/**
* Shows/hides a presentation.
*/
function setPresentationVisible(visible) {
var prezi = $('#presentation>iframe');
if (visible) {
// Trigger the video.selected event to indicate a change in the
// large video.
$(document).trigger("video.selected", [true]);
$('#largeVideo').fadeOut(300);
prezi.fadeIn(300, function() {
prezi.css({opacity:'1'});
ToolbarToggler.dockToolbar(true);
VideoLayout.setLargeVideoVisible(false);
});
$('#activeSpeaker').css('visibility', 'hidden');
}
else {
if (prezi.css('opacity') == '1') {
prezi.fadeOut(300, function () {
prezi.css({opacity:'0'});
$('#reloadPresentation').css({display:'none'});
$('#largeVideo').fadeIn(300, function() {
VideoLayout.setLargeVideoVisible(true);
ToolbarToggler.dockToolbar(false);
});
});
}
}
}
/**
* Presentation has been removed.
*/
@@ -358,15 +336,6 @@ $(document).bind('gotoslide.muc', function (event, jid, presUrl, current) {
}
});
/**
* On video selected event.
*/
$(document).bind('video.selected', function (event, isPresentation) {
if (!isPresentation && $('#presentation>iframe')) {
setPresentationVisible(false);
}
});
$(window).resize(function () {
resize();
});

View File

@@ -1,3 +1,4 @@
/* global require, $ */
var Chat = require("./chat/Chat");
var ContactList = require("./contactlist/ContactList");
var Settings = require("./../../settings/Settings");
@@ -5,6 +6,7 @@ var SettingsMenu = require("./settings/SettingsMenu");
var VideoLayout = require("../videolayout/VideoLayout");
var ToolbarToggler = require("../toolbars/ToolbarToggler");
var UIUtil = require("../util/UIUtil");
var LargeVideo = require("../videolayout/LargeVideo");
/**
* Toggler for the chat, contact list, settings menu, etc..
@@ -15,91 +17,7 @@ var PanelToggler = (function(my) {
var buttons = {
'#chatspace': '#chatBottomButton',
'#contactlist': '#contactListButton',
'#settingsmenu': '#settingsButton'
};
/**
* Resizes the video area
* @param isClosing whether the side panel is going to be closed or is going to open / remain opened
* @param completeFunction a function to be called when the video space is resized
*/
var resizeVideoArea = function(isClosing, completeFunction) {
var videospace = $('#videospace');
var panelSize = isClosing ? [0, 0] : PanelToggler.getPanelSize();
var videospaceWidth = window.innerWidth - panelSize[0];
var videospaceHeight = window.innerHeight;
var videoSize
= VideoLayout.getVideoSize(null, null, videospaceWidth, videospaceHeight);
var videoWidth = videoSize[0];
var videoHeight = videoSize[1];
var videoPosition = VideoLayout.getVideoPosition(videoWidth,
videoHeight,
videospaceWidth,
videospaceHeight);
var horizontalIndent = videoPosition[0];
var verticalIndent = videoPosition[1];
var thumbnailSize = VideoLayout.calculateThumbnailSize(videospaceWidth);
var thumbnailsWidth = thumbnailSize[0];
var thumbnailsHeight = thumbnailSize[1];
//for chat
videospace.animate({
right: panelSize[0],
width: videospaceWidth,
height: videospaceHeight
},
{
queue: false,
duration: 500,
complete: completeFunction
});
$('#remoteVideos').animate({
height: thumbnailsHeight
},
{
queue: false,
duration: 500
});
$('#remoteVideos>span').animate({
height: thumbnailsHeight,
width: thumbnailsWidth
},
{
queue: false,
duration: 500,
complete: function () {
$(document).trigger(
"remotevideo.resized",
[thumbnailsWidth,
thumbnailsHeight]);
}
});
$('#largeVideoContainer').animate({
width: videospaceWidth,
height: videospaceHeight
},
{
queue: false,
duration: 500
});
$('#largeVideo').animate({
width: videoWidth,
height: videoHeight,
top: verticalIndent,
bottom: verticalIndent,
left: horizontalIndent,
right: horizontalIndent
},
{
queue: false,
duration: 500
});
'#settingsmenu': '#toolbar_button_settings'
};
/**
@@ -135,7 +53,7 @@ var PanelToggler = (function(my) {
else {
// Undock the toolbar when the chat is shown and if we're in a
// video mode.
if (VideoLayout.isLargeVideoVisible()) {
if (LargeVideo.isLargeVideoVisible()) {
ToolbarToggler.dockToolbar(false);
}
@@ -180,7 +98,7 @@ var PanelToggler = (function(my) {
$('#chatspace').trigger('shown');
};
resizeVideoArea(Chat.isVisible(), chatCompleteFunction);
VideoLayout.resizeVideoArea(!Chat.isVisible(), chatCompleteFunction);
toggle(Chat,
'#chatspace',
@@ -203,7 +121,7 @@ var PanelToggler = (function(my) {
my.toggleContactList = function () {
var completeFunction = ContactList.isVisible() ?
function() {} : function () { $('#contactlist').trigger('shown');};
resizeVideoArea(ContactList.isVisible(), completeFunction);
VideoLayout.resizeVideoArea(!ContactList.isVisible(), completeFunction);
toggle(ContactList,
'#contactlist',
@@ -218,7 +136,7 @@ var PanelToggler = (function(my) {
* Opens / closes the settings menu
*/
my.toggleSettingsMenu = function() {
resizeVideoArea(SettingsMenu.isVisible(), function (){});
VideoLayout.resizeVideoArea(!SettingsMenu.isVisible(), function (){});
toggle(SettingsMenu,
'#settingsmenu',
null,

View File

@@ -1,4 +1,4 @@
/* global $, Util, nickname:true */
/* global APP, $, Util, nickname:true */
var Replacement = require("./Replacement");
var CommandsProcessor = require("./Commands");
var ToolbarToggler = require("../../toolbars/ToolbarToggler");
@@ -19,7 +19,7 @@ function setVisualNotification(show) {
var unreadMsgBottomElement
= document.getElementById('bottomUnreadMessages');
var glower = $('#chatButton');
var glower = $('#toolbar_button_chat');
var bottomGlower = $('#chatBottomButton');
if (unreadMessages) {
@@ -29,7 +29,7 @@ function setVisualNotification(show) {
ToolbarToggler.dockToolbar(true);
var chatButtonElement
= document.getElementById('chatButton').parentNode;
= document.getElementById('toolbar_button_chat');
var leftIndent = (UIUtil.getTextWidth(chatButtonElement) -
UIUtil.getTextWidth(unreadMsgElement)) / 2;
var topIndent = (UIUtil.getTextHeight(chatButtonElement) -
@@ -85,8 +85,8 @@ function setVisualNotification(show) {
* Returns the current time in the format it is shown to the user
* @returns {string}
*/
function getCurrentTime() {
var now = new Date();
function getCurrentTime(stamp) {
var now = (stamp? new Date(stamp): new Date());
var hour = now.getHours();
var minute = now.getMinutes();
var second = now.getSeconds();
@@ -102,8 +102,7 @@ function getCurrentTime() {
return hour+':'+minute+':'+second;
}
function toggleSmileys()
{
function toggleSmileys() {
var smileys = $('#smileysContainer');
if(!smileys.is(':visible')) {
smileys.show("slide", { direction: "down", duration: 300});
@@ -191,19 +190,18 @@ var Chat = (function (my) {
}
});
$('#usermsg').keydown(function (event) {
var usermsg = $('#usermsg');
usermsg.keydown(function (event) {
if (event.keyCode === 13) {
event.preventDefault();
var value = this.value;
$('#usermsg').val('').trigger('autosize.resize');
usermsg.val('').trigger('autosize.resize');
this.focus();
var command = new CommandsProcessor(value);
if(command.isCommand())
{
if(command.isCommand()) {
command.processCommand();
}
else
{
else {
var message = UIUtil.escapeHtml(value);
APP.xmpp.sendChatMessage(message, NicknameHandler.getNickname());
}
@@ -214,7 +212,7 @@ var Chat = (function (my) {
resizeChatConversation();
Chat.scrollChatToBottom();
};
$('#usermsg').autosize({callback: onTextAreaResize});
usermsg.autosize({callback: onTextAreaResize});
$("#chatspace").bind("shown",
function () {
@@ -228,7 +226,8 @@ var Chat = (function (my) {
/**
* Appends the given message to the chat conversation.
*/
my.updateChatConversation = function (from, displayName, message) {
my.updateChatConversation =
function (from, displayName, message, myjid, stamp) {
var divClassName = '';
if (APP.xmpp.myJid() === from) {
@@ -256,7 +255,7 @@ var Chat = (function (my) {
'<div class="chatmessage">'+
'<img src="../images/chatArrow.svg" class="chatArrow">' +
'<div class="username ' + divClassName +'">' + escDisplayName +
'</div>' + '<div class="timestamp">' + getCurrentTime() +
'</div>' + '<div class="timestamp">' + getCurrentTime(stamp) +
'</div>' + '<div class="usermessage">' + message + '</div>' +
'</div>';
@@ -270,8 +269,7 @@ var Chat = (function (my) {
* @param errorMessage the received error message.
* @param originalText the original message.
*/
my.chatAddError = function(errorMessage, originalText)
{
my.chatAddError = function(errorMessage, originalText) {
errorMessage = UIUtil.escapeHtml(errorMessage);
originalText = UIUtil.escapeHtml(originalText);
@@ -288,23 +286,18 @@ var Chat = (function (my) {
* Sets the subject to the UI
* @param subject the subject
*/
my.chatSetSubject = function(subject)
{
if(subject)
my.chatSetSubject = function(subject) {
if (subject)
subject = subject.trim();
$('#subject').html(Replacement.linkify(UIUtil.escapeHtml(subject)));
if(subject === "")
{
if(subject === "") {
$("#subject").css({display: "none"});
}
else
{
else {
$("#subject").css({display: "block"});
}
};
/**
* Sets the chat conversation mode.
*/

View File

@@ -1,3 +1,4 @@
/* global APP, require */
var UIUtil = require("../../util/UIUtil");
/**
@@ -14,47 +15,39 @@ var commands = {
* @param message the received message
* @returns {string} the command
*/
function getCommand(message)
{
if(message)
{
for(var command in commands)
{
function getCommand(message) {
if(message) {
for(var command in commands) {
if(message.indexOf("/" + command) == 0)
return command;
}
}
return "";
};
}
/**
* Processes the data for topic command.
* @param commandArguments the arguments of the topic command.
*/
function processTopic(commandArguments)
{
function processTopic(commandArguments) {
var topic = UIUtil.escapeHtml(commandArguments);
APP.xmpp.setSubject(topic);
}
/**
* Constructs new CommandProccessor instance from a message that
* Constructs a new CommandProccessor instance from a message that
* handles commands received via chat messages.
* @param message the message
* @constructor
*/
function CommandsProcessor(message)
{
function CommandsProcessor(message) {
var command = getCommand(message);
/**
* Returns the name of the command.
* @returns {String} the command
*/
this.getCommand = function()
{
this.getCommand = function() {
return command;
};
@@ -65,8 +58,7 @@ function CommandsProcessor(message)
* Returns the arguments of the command.
* @returns {string}
*/
this.getArgument = function()
{
this.getArgument = function() {
return messageArgument;
};
}
@@ -75,9 +67,8 @@ function CommandsProcessor(message)
* Checks whether this instance is valid command or not.
* @returns {boolean}
*/
CommandsProcessor.prototype.isCommand = function()
{
if(this.getCommand())
CommandsProcessor.prototype.isCommand = function() {
if (this.getCommand())
return true;
return false;
};
@@ -85,13 +76,11 @@ CommandsProcessor.prototype.isCommand = function()
/**
* Processes the command.
*/
CommandsProcessor.prototype.processCommand = function()
{
CommandsProcessor.prototype.processCommand = function() {
if(!this.isCommand())
return;
commands[this.getCommand()](this.getArgument());
};
module.exports = CommandsProcessor;

View File

@@ -1,3 +1,5 @@
/* global $, APP, Strophe */
var Avatar = require('../../avatar/Avatar');
var numberOfContacts = 0;
var notificationInterval;
@@ -9,26 +11,28 @@ var notificationInterval;
* left(-1)
*/
function updateNumberOfParticipants(delta) {
//when the user is alone we don't show the number of participants
if(numberOfContacts === 0) {
numberOfContacts += delta;
if (numberOfContacts === 1) {
// when the user is alone we don't show the number of participants
$("#numberOfParticipants").text('');
numberOfContacts += delta;
} else if(numberOfContacts !== 0 && !ContactList.isVisible()) {
ContactList.setVisualNotification(true);
numberOfContacts += delta;
ContactList.setVisualNotification(false);
} else if (numberOfContacts > 1) {
ContactList.setVisualNotification(!ContactList.isVisible());
$("#numberOfParticipants").text(numberOfContacts);
} else {
console.error("Invalid number of participants: " + numberOfContacts);
}
}
/**
* Creates the avatar element.
*
* @return the newly created avatar element
* @return {object} the newly created avatar element
*/
function createAvatar(id) {
function createAvatar(jid) {
var avatar = document.createElement('img');
avatar.className = "icon-avatar avatar";
avatar.src = "https://www.gravatar.com/avatar/" + id + "?d=wavatar&size=30";
avatar.src = Avatar.getContactListUrl(jid);
return avatar;
}
@@ -42,8 +46,7 @@ function createDisplayNameParagraph(key, displayName) {
var p = document.createElement('p');
if(displayName)
p.innerText = displayName;
else if(key)
{
else if(key) {
p.setAttribute("data-i18n",key);
p.innerText = APP.translation.translateString(key);
}
@@ -61,7 +64,6 @@ function stopGlowing(glower) {
}
}
/**
* Contact list.
*/
@@ -80,27 +82,25 @@ var ContactList = {
* Adds a contact for the given peerJid if such doesn't yet exist.
*
* @param peerJid the peerJid corresponding to the contact
* @param id the user's email or userId used to get the user's avatar
*/
ensureAddContact: function (peerJid, id) {
ensureAddContact: function (peerJid) {
var resourceJid = Strophe.getResourceFromJid(peerJid);
var contact = $('#contactlist>ul>li[id="' + resourceJid + '"]');
var contact = $('#contacts>li[id="' + resourceJid + '"]');
if (!contact || contact.length <= 0)
ContactList.addContact(peerJid, id);
ContactList.addContact(peerJid);
},
/**
* Adds a contact for the given peer jid.
*
* @param peerJid the jid of the contact to add
* @param id the email or userId of the user
*/
addContact: function (peerJid, id) {
addContact: function (peerJid) {
var resourceJid = Strophe.getResourceFromJid(peerJid);
var contactlist = $('#contactlist>ul');
var contactlist = $('#contacts');
var newContact = document.createElement('li');
newContact.id = resourceJid;
@@ -111,18 +111,14 @@ var ContactList = {
}
};
newContact.appendChild(createAvatar(id));
newContact.appendChild(createAvatar(peerJid));
newContact.appendChild(createDisplayNameParagraph("participant"));
var clElement = contactlist.get(0);
if (resourceJid === APP.xmpp.myResource()
&& $('#contactlist>ul .title')[0].nextSibling.nextSibling) {
clElement.insertBefore(newContact,
$('#contactlist>ul .title')[0].nextSibling.nextSibling);
if (resourceJid === APP.xmpp.myResource()) {
contactlist.prepend(newContact);
}
else {
clElement.appendChild(newContact);
contactlist.append(newContact);
}
updateNumberOfParticipants(1);
},
@@ -135,7 +131,7 @@ var ContactList = {
removeContact: function (peerJid) {
var resourceJid = Strophe.getResourceFromJid(peerJid);
var contact = $('#contactlist>ul>li[id="' + resourceJid + '"]');
var contact = $('#contacts>li[id="' + resourceJid + '"]');
if (contact && contact.length > 0) {
var contactlist = $('#contactlist>ul');
@@ -165,7 +161,7 @@ var ContactList = {
},
setClickable: function (resourceJid, isClickable) {
var contact = $('#contactlist>ul>li[id="' + resourceJid + '"]');
var contact = $('#contacts>li[id="' + resourceJid + '"]');
if (isClickable) {
contact.addClass('clickable');
} else {
@@ -179,10 +175,19 @@ var ContactList = {
var resourceJid = Strophe.getResourceFromJid(peerJid);
var contactName = $('#contactlist #' + resourceJid + '>p');
var contactName = $('#contacts #' + resourceJid + '>p');
if (contactName && displayName && displayName.length > 0)
contactName.html(displayName);
},
userAvatarChanged: function (resourceJid, contactListUrl) {
// set the avatar in the contact list
var contact = $('#' + resourceJid + '>img');
if (contact && contact.length > 0) {
contact.get(0).src = contactListUrl;
}
}
};

View File

@@ -1,15 +1,14 @@
/* global APP, $ */
var Avatar = require("../../avatar/Avatar");
var Settings = require("./../../../settings/Settings");
var UIUtil = require("../../util/UIUtil");
var languages = require("../../../../service/translation/languages");
function generateLanguagesSelectBox()
{
function generateLanguagesSelectBox() {
var currentLang = APP.translation.getCurrentLanguage();
var html = "<select id=\"languages_selectbox\">";
var langArray = languages.getLanguages();
for(var i = 0; i < langArray.length; i++)
{
for(var i = 0; i < langArray.length; i++) {
var lang = langArray[i];
html += "<option ";
if(lang === currentLang)
@@ -26,7 +25,8 @@ function generateLanguagesSelectBox()
var SettingsMenu = {
init: function () {
$("#startMutedOptions").before(generateLanguagesSelectBox());
var startMutedSelector = $("#startMutedOptions");
startMutedSelector.before(generateLanguagesSelectBox());
APP.translation.translateElement($("#languages_selectbox"));
$('#settingsmenu>input').keyup(function(event){
if(event.keyCode === 13) {//enter
@@ -34,13 +34,11 @@ var SettingsMenu = {
}
});
if(APP.xmpp.isModerator())
{
$("#startMutedOptions").css("display", "block");
if (APP.xmpp.isModerator()) {
startMutedSelector.css("display", "block");
}
else
{
$("#startMutedOptions").css("display", "none");
else {
startMutedSelector.css("display", "none");
}
$("#updateSettings").click(function () {
@@ -49,12 +47,10 @@ var SettingsMenu = {
},
onRoleChanged: function () {
if(APP.xmpp.isModerator())
{
if(APP.xmpp.isModerator()) {
$("#startMutedOptions").css("display", "block");
}
else
{
else {
$("#startMutedOptions").css("display", "none");
}
},
@@ -102,6 +98,9 @@ var SettingsMenu = {
peerJid === APP.xmpp.myJid()) {
this.setDisplayName(newDisplayName);
}
},
changeAvatar: function (thumbUrl) {
$('#avatar').get(0).src = thumbUrl;
}
};

View File

@@ -1,3 +1,4 @@
/* global $ */
var PanelToggler = require("../side_pannels/SidePanelToggler");
var buttonHandlers = {

View File

@@ -1,5 +1,5 @@
/* global APP,$, buttonClick, config, lockRoom,
setSharedKey, Util */
/* global APP, $, buttonClick, config, lockRoom, interfaceConfig, setSharedKey,
Util */
var messageHandler = require("../util/MessageHandler");
var BottomToolbar = require("./BottomToolbar");
var Prezi = require("../prezi/Prezi");
@@ -13,9 +13,9 @@ var AuthenticationEvents
var roomUrl = null;
var sharedKey = '';
var UI = null;
var recordingToaster = null;
var buttonHandlers =
{
var buttonHandlers = {
"toolbar_button_mute": function () {
return APP.UI.toggleAudio();
},
@@ -46,9 +46,8 @@ var buttonHandlers =
"toolbar_button_desktopsharing": function () {
return APP.desktopsharing.toggleScreenSharing();
},
"toolbar_button_fullScreen": function()
{
UIUtil.buttonClick("#fullScreen", "icon-full-screen icon-exit-full-screen");
"toolbar_button_fullScreen": function() {
UIUtil.buttonClick("#toolbar_button_fullScreen", "icon-full-screen icon-exit-full-screen");
return Toolbar.toggleFullScreen();
},
"toolbar_button_sip": function () {
@@ -91,21 +90,19 @@ var buttonHandlers =
function hangup() {
APP.xmpp.disposeConference();
if(config.enableWelcomePage)
{
setTimeout(function()
{
if(config.enableWelcomePage) {
setTimeout(function() {
window.localStorage.welcomePageDisabled = false;
window.location.pathname = "/";
}, 10000);
}
var title = APP.translation.generateTranslatonHTML(
var title = APP.translation.generateTranslationHTML(
"dialog.sessTerminated");
var msg = APP.translation.generateTranslatonHTML(
var msg = APP.translation.generateTranslationHTML(
"dialog.hungUp");
var button = APP.translation.generateTranslatonHTML(
var button = APP.translation.generateTranslationHTML(
"dialog.joinAgain");
var buttons = [];
buttons.push({title: button, value: true});
@@ -115,8 +112,7 @@ function hangup() {
msg,
true,
buttons,
function(event, value, message, formVals)
{
function(event, value, message, formVals) {
window.location.reload();
return false;
}
@@ -127,9 +123,14 @@ function hangup() {
* Starts or stops the recording for the conference.
*/
function toggleRecording() {
function toggleRecording(predefinedToken) {
APP.xmpp.toggleRecording(function (callback) {
var msg = APP.translation.generateTranslatonHTML(
if (predefinedToken) {
callback(UIUtil.escapeHtml(predefinedToken));
return;
}
var msg = APP.translation.generateTranslationHTML(
"dialog.recordingToken");
var token = APP.translation.translateString("dialog.token");
APP.UI.messageHandler.openTwoButtonDialog(null, null, null,
@@ -152,7 +153,7 @@ function toggleRecording() {
function () { },
':input:first'
);
}, Toolbar.setRecordingButtonState, Toolbar.setRecordingButtonState);
}, Toolbar.setRecordingButtonState);
}
/**
@@ -165,13 +166,11 @@ function lockRoom(lock) {
APP.xmpp.lockRoom(currentSharedKey, function (res) {
// password is required
if (sharedKey)
{
if (sharedKey) {
console.log('set room password');
Toolbar.lockLockButton();
}
else
{
else {
console.log('removed room password');
Toolbar.unlockLockButton();
}
@@ -186,7 +185,7 @@ function lockRoom(lock) {
"dialog.passwordNotSupported");
Toolbar.setSharedKey('');
});
};
}
/**
* Invite participants to conference.
@@ -224,17 +223,15 @@ function inviteParticipants() {
window.open("mailto:?subject=" + subject + "&body=" + body, '_blank');
}
function dialpadButtonClicked()
{
//TODO show the dialpad window
function dialpadButtonClicked() {
//TODO show the dialpad box
}
function callSipButtonClicked()
{
function callSipButtonClicked() {
var defaultNumber
= config.defaultSipNumber ? config.defaultSipNumber : '';
var sipMsg = APP.translation.generateTranslatonHTML(
var sipMsg = APP.translation.generateTranslationHTML(
"dialog.sipMsg");
messageHandler.openTwoButtonDialog(null, null, null,
'<h2>' + sipMsg + '</h2>' +
@@ -281,7 +278,7 @@ var Toolbar = (function (my) {
}
}
);
},
};
/**
* Sets shared key
@@ -298,7 +295,7 @@ var Toolbar = (function (my) {
return;
}
// Get authentication URL
if (!APP.xmpp.getMUCJoined()) {
if (!APP.xmpp.isMUCJoined()) {
APP.xmpp.getLoginUrl(UI.getRoomName(), function (url) {
// If conference has not been started yet - redirect to login page
window.location.href = url;
@@ -342,9 +339,8 @@ var Toolbar = (function (my) {
* Disables and enables some of the buttons.
*/
my.setupButtonsFromConfig = function () {
if (config.disablePrezi)
{
$("#prezi_button").css({display: "none"});
if (config.disablePrezi) {
$("#toolbar_button_prezi").css({display: "none"});
}
};
@@ -374,7 +370,7 @@ var Toolbar = (function (my) {
}
});
} else {
var msg = APP.translation.generateTranslatonHTML(
var msg = APP.translation.generateTranslationHTML(
"dialog.passwordMsg");
var yourPassword = APP.translation.translateString(
"dialog.yourPassword");
@@ -444,11 +440,11 @@ var Toolbar = (function (my) {
* FIXME: not used ?
*/
my.openSettingsDialog = function () {
var settings1 = APP.translation.generateTranslatonHTML(
var settings1 = APP.translation.generateTranslationHTML(
"dialog.settings1");
var settings2 = APP.translation.generateTranslatonHTML(
var settings2 = APP.translation.generateTranslationHTML(
"dialog.settings2");
var settings3 = APP.translation.generateTranslatonHTML(
var settings3 = APP.translation.generateTranslationHTML(
"dialog.settings3");
var yourPassword = APP.translation.translateString(
@@ -519,15 +515,15 @@ var Toolbar = (function (my) {
* Unlocks the lock button state.
*/
my.unlockLockButton = function () {
if ($("#lockIcon").hasClass("icon-security-locked"))
UIUtil.buttonClick("#lockIcon", "icon-security icon-security-locked");
if ($("#toolbar_button_security").hasClass("icon-security-locked"))
UIUtil.buttonClick("#toolbar_button_security", "icon-security icon-security-locked");
};
/**
* Updates the lock button state to locked.
*/
my.lockLockButton = function () {
if ($("#lockIcon").hasClass("icon-security"))
UIUtil.buttonClick("#lockIcon", "icon-security icon-security-locked");
if ($("#toolbar_button_security").hasClass("icon-security"))
UIUtil.buttonClick("#toolbar_button_security", "icon-security icon-security-locked");
};
/**
@@ -550,40 +546,77 @@ var Toolbar = (function (my) {
}
if (show) {
$('#recording').css({display: "inline"});
$('#toolbar_button_record').css({display: "inline-block"});
}
else {
$('#recording').css({display: "none"});
$('#toolbar_button_record').css({display: "none"});
}
};
// Sets the state of the recording button
my.setRecordingButtonState = function (isRecording) {
var selector = $('#recordButton');
if (isRecording) {
my.setRecordingButtonState = function (recordingState) {
var selector = $('#toolbar_button_record');
if (recordingState === 'on') {
selector.removeClass("icon-recEnable");
selector.addClass("icon-recEnable active");
} else {
$("#largeVideo").toggleClass("videoMessageFilter", true);
var recordOnKey = "recording.on";
$('#videoConnectionMessage').attr("data-i18n", recordOnKey);
$('#videoConnectionMessage').text(APP.translation.translateString(recordOnKey));
setTimeout(function(){
$("#largeVideo").toggleClass("videoMessageFilter", false);
$('#videoConnectionMessage').css({display: "none"});
}, 1500);
recordingToaster = messageHandler.notify(null, "recording.toaster", null,
null, null, {timeOut: 0, closeButton: null, tapToDismiss: false});
} else if (recordingState === 'off') {
selector.removeClass("icon-recEnable active");
selector.addClass("icon-recEnable");
$("#largeVideo").toggleClass("videoMessageFilter", false);
$('#videoConnectionMessage').css({display: "none"});
if (recordingToaster)
messageHandler.remove(recordingToaster);
} else if (recordingState === 'pending') {
selector.removeClass("icon-recEnable active");
selector.addClass("icon-recEnable");
$("#largeVideo").toggleClass("videoMessageFilter", true);
var recordPendingKey = "recording.pending";
$('#videoConnectionMessage').attr("data-i18n", recordPendingKey);
$('#videoConnectionMessage').text(APP.translation.translateString(recordPendingKey));
$('#videoConnectionMessage').css({display: "block"});
}
};
// checks whether recording is enabled and whether we have params to start automatically recording
my.checkAutoRecord = function () {
if (config.enableRecording && config.autoRecord) {
toggleRecording(config.autoRecordToken);
}
}
// Shows or hides SIP calls button
my.showSipCallButton = function (show) {
if (APP.xmpp.isSipGatewayEnabled() && show) {
$('#sipCallButton').css({display: "inline-block"});
$('#toolbar_button_sip').css({display: "inline-block"});
} else {
$('#sipCallButton').css({display: "none"});
$('#toolbar_button_sip').css({display: "none"});
}
};
// Shows or hides the dialpad button
my.showDialPadButton = function (show) {
if (show) {
$('#dialPadButton').css({display: "inline-block"});
$('#toolbar_button_dialpad').css({display: "inline-block"});
} else {
$('#dialPadButton').css({display: "none"});
$('#toolbar_button_dialpad').css({display: "none"});
}
};
@@ -631,13 +664,10 @@ var Toolbar = (function (my) {
* @param active the state of the desktop streaming.
*/
my.changeDesktopSharingButtonState = function (active) {
var button = $("#desktopsharing > a");
if (active)
{
var button = $("#toolbar_button_desktopsharing");
if (active) {
button.addClass("glow");
}
else
{
} else {
button.removeClass("glow");
}
};

View File

@@ -1,13 +1,14 @@
/* global $, interfaceConfig, Moderator, DesktopStreaming.showDesktopSharingButton */
/* global APP, config, $, interfaceConfig, Moderator,
DesktopStreaming.showDesktopSharingButton */
var toolbarTimeoutObject,
toolbarTimeout = interfaceConfig.INITIAL_TOOLBAR_TIMEOUT;
function showDesktopSharingButton() {
if (APP.desktopsharing.isDesktopSharingEnabled()) {
$('#desktopsharing').css({display: "inline"});
$('#toolbar_button_desktopsharing').css({display: "inline-block"});
} else {
$('#desktopsharing').css({display: "none"});
$('#toolbar_button_desktopsharing').css({display: "none"});
}
}
@@ -52,6 +53,8 @@ var ToolbarToggler = {
* Shows the main toolbar.
*/
showToolbar: function () {
if (interfaceConfig.filmStripOnly)
return;
var header = $("#header"),
bottomToolbar = $("#bottomToolbar");
if (!header.is(':visible') || !bottomToolbar.is(":visible")) {
@@ -81,13 +84,15 @@ var ToolbarToggler = {
showDesktopSharingButton();
},
/**
* Docks/undocks the toolbar.
*
* @param isDock indicates what operation to perform
*/
dockToolbar: function (isDock) {
if (interfaceConfig.filmStripOnly)
return;
if (isDock) {
// First make sure the toolbar is shown.
if (!$('#header').is(':visible')) {
@@ -111,7 +116,6 @@ var ToolbarToggler = {
},
showDesktopSharingButton: showDesktopSharingButton
};
module.exports = ToolbarToggler;

View File

@@ -1,3 +1,4 @@
/* global $ */
var JitsiPopover = (function () {
/**
* Constructs new JitsiPopover and attaches it to the element
@@ -45,23 +46,24 @@ var JitsiPopover = (function () {
* Shows the popover
*/
JitsiPopover.prototype.show = function () {
if(!JitsiPopover.enabled)
return;
this.createPopover();
this.popoverShown = true;
};
/**
* Hides the popover
*/
JitsiPopover.prototype.hide = function () {
if(!this.elementIsHovered && !this.popoverIsHovered && this.popoverShown)
{
if(!this.elementIsHovered && !this.popoverIsHovered &&
this.popoverShown) {
this.forceHide();
}
};
/**
* Hides the popover
* Hides the popover.
*/
JitsiPopover.prototype.forceHide = function () {
$(".jitsipopover").remove();
@@ -69,7 +71,7 @@ var JitsiPopover = (function () {
};
/**
* Creates the popover html
* Creates the popover html.
*/
JitsiPopover.prototype.createPopover = function () {
$("body").append(this.template);
@@ -86,7 +88,7 @@ var JitsiPopover = (function () {
};
/**
* Refreshes the position of the popover
* Refreshes the position of the popover.
*/
JitsiPopover.prototype.refreshPosition = function () {
$(".jitsipopover").position({
@@ -95,10 +97,13 @@ var JitsiPopover = (function () {
collision: "fit",
of: this.element,
using: function (position, elements) {
var calcLeft = elements.target.left - elements.element.left + elements.target.width/2;
$(".jitsipopover").css({top: position.top, left: position.left, display: "table"});
var calcLeft = elements.target.left - elements.element.left +
elements.target.width/2;
$(".jitsipopover").css(
{top: position.top, left: position.left, display: "table"});
$(".jitsipopover > .arrow").css({left: calcLeft});
$(".jitsipopover > .jitsiPopupmenuPadding").css({left: calcLeft - 50});
$(".jitsipopover > .jitsiPopupmenuPadding").css(
{left: calcLeft - 50});
}
});
};
@@ -115,9 +120,9 @@ var JitsiPopover = (function () {
this.createPopover();
};
JitsiPopover.enabled = true;
return JitsiPopover;
})();
module.exports = JitsiPopover;

View File

@@ -1,36 +1,42 @@
/* global $, APP, jQuery, toastr */
/**
* Flag for enable/disable of the notifications.
* @type {boolean}
*/
var notificationsEnabled = true;
var messageHandler = (function(my) {
/**
* Shows a message to the user.
*
* @param titleString the title of the message
* @param messageString the text of the message
* @param titleKey the title of the message
* @param messageKey the text of the message
*/
my.openMessageDialog = function(titleKey, messageKey) {
var title = null;
if(titleKey)
{
title = APP.translation.generateTranslatonHTML(titleKey);
if(titleKey) {
title = APP.translation.generateTranslationHTML(titleKey);
}
var message = APP.translation.generateTranslatonHTML(messageKey);
var message = APP.translation.generateTranslationHTML(messageKey);
$.prompt(message,
{
title: title,
persistent: false
}
{title: title, persistent: false}
);
};
/**
* Shows a message to the user with two buttons: first is given as a parameter and the second is Cancel.
* Shows a message to the user with two buttons: first is given as a
* parameter and the second is Cancel.
*
* @param titleString the title of the message
* @param msgString the text of the message
* @param persistent boolean value which determines whether the message is persistent or not
* @param persistent boolean value which determines whether the message is
* persistent or not
* @param leftButton the fist button's text
* @param submitFunction function to be called on submit
* @param loadedFunction function to be called after the prompt is fully loaded
* @param loadedFunction function to be called after the prompt is fully
* loaded
* @param closeFunction function to be called after the prompt is closed
* @param focus optional focus selector or button index to be focused after
* the dialog is opened
@@ -39,24 +45,22 @@ var messageHandler = (function(my) {
*/
my.openTwoButtonDialog = function(titleKey, titleString, msgKey, msgString,
persistent, leftButtonKey, submitFunction, loadedFunction,
closeFunction, focus, defaultButton)
{
closeFunction, focus, defaultButton) {
var buttons = [];
var leftButton = APP.translation.generateTranslatonHTML(leftButtonKey);
var leftButton = APP.translation.generateTranslationHTML(leftButtonKey);
buttons.push({ title: leftButton, value: true});
var cancelButton
= APP.translation.generateTranslatonHTML("dialog.Cancel");
= APP.translation.generateTranslationHTML("dialog.Cancel");
buttons.push({title: cancelButton, value: false});
var message = msgString, title = titleString;
if (titleKey)
{
title = APP.translation.generateTranslatonHTML(titleKey);
if (titleKey) {
title = APP.translation.generateTranslationHTML(titleKey);
}
if (msgKey) {
message = APP.translation.generateTranslatonHTML(msgKey);
message = APP.translation.generateTranslationHTML(msgKey);
}
$.prompt(message, {
title: title,
@@ -75,11 +79,14 @@ var messageHandler = (function(my) {
*
* @param titleString the title of the message
* @param msgString the text of the message
* @param persistent boolean value which determines whether the message is persistent or not
* @param buttons object with the buttons. The keys must be the name of the button and value is the value
* that will be passed to submitFunction
* @param persistent boolean value which determines whether the message is
* persistent or not
* @param buttons object with the buttons. The keys must be the name of the
* button and value is the value that will be passed to
* submitFunction
* @param submitFunction function to be called on submit
* @param loadedFunction function to be called after the prompt is fully loaded
* @param loadedFunction function to be called after the prompt is fully
* loaded
*/
my.openDialog = function (titleString, msgString, persistent, buttons,
submitFunction, loadedFunction) {
@@ -107,10 +114,9 @@ var messageHandler = (function(my) {
/**
* Shows a dialog with different states to the user.
*
* @param statesObject object containing all the states of the dialog
* @param statesObject object containing all the states of the dialog.
*/
my.openDialogWithStates = function (statesObject, options) {
return new Impromptu(statesObject, options);
};
@@ -124,7 +130,7 @@ var messageHandler = (function(my) {
* @param onPopupClosed optional callback function called when popup window
* has been closed.
*
* @returns popup window object if opened successfully or undefined
* @returns {object} popup window object if opened successfully or undefined
* in case we failed to open it(popup blocked)
*/
my.openCenteredPopup = function (url, w, h, onPopupClosed) {
@@ -147,8 +153,8 @@ var messageHandler = (function(my) {
/**
* Shows a dialog prompting the user to send an error report.
*
* @param titleString the title of the message
* @param msgString the text of the message
* @param titleKey the title of the message
* @param msgKey the text of the message
* @param error the error that is being reported
*/
my.openReportDialog = function(titleKey, msgKey, error) {
@@ -159,35 +165,42 @@ var messageHandler = (function(my) {
/**
* Shows an error dialog to the user.
* @param title the title of the message
* @param message the text of the messafe
* @param titleKey the title of the message.
* @param msgKey the text of the message.
*/
my.showError = function(titleKey, msgKey) {
if(!titleKey) {
if (!titleKey) {
titleKey = "dialog.oops";
}
if(!msgKey)
{
if (!msgKey) {
msgKey = "dialog.defaultError";
}
messageHandler.openMessageDialog(titleKey, msgKey);
};
/**
* Displayes notification.
* @param displayName display name of the participant that is associated with the notification.
* @param displayNameKey the key from the language file for the display name.
* @param cls css class for the notification
* @param messageKey the key from the language file for the text of the message.
* @param messageArguments object with the arguments for the message.
* @param options object with language options.
*/
my.notify = function(displayName, displayNameKey,
cls, messageKey, messageArguments, options) {
if(!notificationsEnabled)
return;
var displayNameSpan = '<span class="nickname" ';
if(displayName)
{
if (displayName) {
displayNameSpan += ">" + displayName;
}
else
{
} else {
displayNameSpan += "data-i18n='" + displayNameKey +
"'>" + APP.translation.translateString(displayNameKey);
}
displayNameSpan += "</span>";
toastr.info(
return toastr.info(
displayNameSpan + '<br>' +
'<span class=' + cls + ' data-i18n="' + messageKey + '"' +
(messageArguments?
@@ -198,6 +211,28 @@ var messageHandler = (function(my) {
'</span>', null, options);
};
/**
* Removes the toaster.
* @param toasterElement
*/
my.remove = function(toasterElement) {
toasterElement.remove();
};
/**
* Disables notifications.
*/
my.disableNotifications = function () {
notificationsEnabled = false;
};
/**
* Enables notifications.
*/
my.enableNotifications = function () {
notificationsEnabled = true;
};
return my;
}(messageHandler || {}));

View File

@@ -3,7 +3,7 @@ var UIEvents = require("../../../service/UI/UIEvents");
var nickname = null;
var eventEmitter = null;
var NickanameHandler = {
var NicknameHandler = {
init: function (emitter) {
eventEmitter = emitter;
var storedDisplayName = window.localStorage.displayname;
@@ -27,4 +27,4 @@ var NickanameHandler = {
}
};
module.exports = NickanameHandler;
module.exports = NicknameHandler;

View File

@@ -1,3 +1,4 @@
/* global $ */
/**
* Created by hristo on 12/22/14.
*/
@@ -5,10 +6,12 @@ module.exports = {
/**
* Returns the available video width.
*/
getAvailableVideoWidth: function () {
getAvailableVideoWidth: function (isVisible) {
var PanelToggler = require("../side_pannels/SidePanelToggler");
if(typeof isVisible === "undefined" || isVisible === null)
isVisible = PanelToggler.isVisible();
var rightPanelWidth
= PanelToggler.isVisible() ? PanelToggler.getPanelSize()[0] : 0;
= isVisible ? PanelToggler.getPanelSize()[0] : 0;
return window.innerWidth - rightPanelWidth;
},
@@ -59,7 +62,7 @@ module.exports = {
for (var i = 0, n = pixels.length; i < n; i += 4) {
var grayscale
= pixels[i] * .3 + pixels[i+1] * .59 + pixels[i+2] * .11;
= pixels[i] * 0.3 + pixels[i+1] * 0.59 + pixels[i+2] * 0.11;
pixels[i ] = grayscale; // red
pixels[i+1] = grayscale; // green
pixels[i+2] = grayscale; // blue
@@ -75,7 +78,19 @@ module.exports = {
element.setAttribute("data-placement", position);
element.setAttribute("data-html", true);
element.setAttribute("data-container", "body");
},
/**
* Inserts given child element as the first one into the container.
* @param container the container to which new child element will be added
* @param newChild the new element that will be inserted into the container
*/
prependChild: function (container, newChild) {
var firstChild = container.childNodes[0];
if (firstChild) {
container.insertBefore(newChild, firstChild);
} else {
container.appendChild(newChild);
}
}
};

View File

@@ -1,3 +1,4 @@
/* global APP, $ */
var JitsiPopover = require("../util/JitsiPopover");
/**
@@ -5,8 +6,7 @@ var JitsiPopover = require("../util/JitsiPopover");
* @param videoContainer the video container associated with the indicator.
* @constructor
*/
function ConnectionIndicator(videoContainer, jid, VideoLayout)
{
function ConnectionIndicator(videoContainer, jid) {
this.videoContainer = videoContainer;
this.bandwidth = null;
this.packetLoss = null;
@@ -17,7 +17,6 @@ function ConnectionIndicator(videoContainer, jid, VideoLayout)
this.popover = null;
this.jid = jid;
this.create();
this.videoLayout = VideoLayout;
}
/**
@@ -38,20 +37,17 @@ ConnectionIndicator.connectionQualityValues = {
0: "0px"//empty
};
ConnectionIndicator.getIP = function(value)
{
ConnectionIndicator.getIP = function(value) {
return value.substring(0, value.lastIndexOf(":"));
};
ConnectionIndicator.getPort = function(value)
{
ConnectionIndicator.getPort = function(value) {
return value.substring(value.lastIndexOf(":") + 1, value.length);
};
ConnectionIndicator.getStringFromArray = function (array) {
var res = "";
for(var i = 0; i < array.length; i++)
{
for(var i = 0; i < array.length; i++) {
res += (i === 0? "" : ", ") + array[i];
}
return res;
@@ -66,82 +62,60 @@ ConnectionIndicator.prototype.generateText = function () {
var translate = APP.translation.translateString;
if(this.bitrate === null)
{
if(this.bitrate === null) {
downloadBitrate = "N/A";
uploadBitrate = "N/A";
}
else
{
else {
downloadBitrate =
this.bitrate.download? this.bitrate.download + " Kbps" : "N/A";
uploadBitrate =
this.bitrate.upload? this.bitrate.upload + " Kbps" : "N/A";
}
if(this.packetLoss === null)
{
if(this.packetLoss === null) {
packetLoss = "N/A";
}
else
{
} else {
packetLoss = "<span class='jitsipopover_green'>&darr;</span>" +
(this.packetLoss.download !== null? this.packetLoss.download : "N/A") +
(this.packetLoss.download !== null ?
this.packetLoss.download : "N/A") +
"% <span class='jitsipopover_orange'>&uarr;</span>" +
(this.packetLoss.upload !== null? this.packetLoss.upload : "N/A") + "%";
(this.packetLoss.upload !== null? this.packetLoss.upload : "N/A") +
"%";
}
var resolutionValue = null;
if(this.resolution && this.jid != null)
{
if(this.resolution && this.jid != null) {
var keys = Object.keys(this.resolution);
if(keys.length == 1)
{
for(var ssrc in this.resolution)
{
resolutionValue = this.resolution[ssrc];
}
}
else if(keys.length > 1)
{
var displayedSsrc = APP.simulcast.getReceivingSSRC(this.jid);
resolutionValue = this.resolution[displayedSsrc];
for(var ssrc in this.resolution) {
resolutionValue = this.resolution[ssrc];
}
}
if(this.jid === null)
{
if(this.jid === null) {
resolution = "";
if(this.resolution === null || !Object.keys(this.resolution) ||
Object.keys(this.resolution).length === 0)
{
Object.keys(this.resolution).length === 0) {
resolution = "N/A";
}
else
for(i in this.resolution)
{
} else {
for (i in this.resolution) {
resolutionValue = this.resolution[i];
if(resolutionValue)
{
if(resolutionValue.height &&
resolutionValue.width)
{
resolution += (resolution === ""? "" : ", ") +
resolutionValue.width + "x" +
resolutionValue.height;
if (resolutionValue) {
if (resolutionValue.height &&
resolutionValue.width) {
resolution += (resolution === "" ? "" : ", ") +
resolutionValue.width + "x" +
resolutionValue.height;
}
}
}
}
else if(!resolutionValue ||
}
} else if(!resolutionValue ||
!resolutionValue.height ||
!resolutionValue.width)
{
!resolutionValue.width) {
resolution = "N/A";
}
else
{
} else {
resolution = resolutionValue.width + "x" + resolutionValue.height;
}
@@ -161,25 +135,23 @@ ConnectionIndicator.prototype.generateText = function () {
translate("connectionindicator.resolution") + "</span></td>" +
"<td>" + resolution + "</td></tr></table>";
if(this.videoContainer.id == "localVideoContainer") {
if(this.videoContainer.videoSpanId == "localVideoContainer") {
result += "<div class=\"jitsipopover_showmore\" " +
"onclick = \"APP.UI.connectionIndicatorShowMore('" +
this.videoContainer.id + "')\" data-i18n='connectionindicator." +
// FIXME: we do not know local jid when this text is generated
//this.jid + "')\" data-i18n='connectionindicator." +
"local')\" data-i18n='connectionindicator." +
(this.showMoreValue ? "less" : "more") + "'>" +
translate("connectionindicator." + (this.showMoreValue ? "less" : "more")) +
"</div><br />";
}
if(this.showMoreValue)
{
if (this.showMoreValue) {
var downloadBandwidth, uploadBandwidth, transport;
if(this.bandwidth === null)
{
if (this.bandwidth === null) {
downloadBandwidth = "N/A";
uploadBandwidth = "N/A";
}
else
{
} else {
downloadBandwidth = this.bandwidth.download?
this.bandwidth.download + " Kbps" :
"N/A";
@@ -188,45 +160,36 @@ ConnectionIndicator.prototype.generateText = function () {
"N/A";
}
if(!this.transport || this.transport.length === 0)
{
if (!this.transport || this.transport.length === 0) {
transport = "<tr>" +
"<td><span class='jitsipopover_blue' " +
"data-i18n='connectionindicator.address'>" +
translate("connectionindicator.address") + "</span></td>" +
"<td> N/A</td></tr>";
}
else
{
} else {
var data = {remoteIP: [], localIP:[], remotePort:[], localPort:[]};
for(i = 0; i < this.transport.length; i++)
{
for(i = 0; i < this.transport.length; i++) {
var ip = ConnectionIndicator.getIP(this.transport[i].ip);
var port = ConnectionIndicator.getPort(this.transport[i].ip);
var localIP =
ConnectionIndicator.getIP(this.transport[i].localip);
var localPort =
ConnectionIndicator.getPort(this.transport[i].localip);
if(data.remoteIP.indexOf(ip) == -1)
{
if(data.remoteIP.indexOf(ip) == -1) {
data.remoteIP.push(ip);
}
if(data.remotePort.indexOf(port) == -1)
{
if(data.remotePort.indexOf(port) == -1) {
data.remotePort.push(port);
}
if(data.localIP.indexOf(localIP) == -1)
{
if(data.localIP.indexOf(localIP) == -1) {
data.localIP.push(localIP);
}
if(data.localPort.indexOf(localPort) == -1)
{
if(data.localPort.indexOf(localPort) == -1) {
data.localPort.push(localPort);
}
}
var local_address_key = "connectionindicator.localaddress";
@@ -292,7 +255,6 @@ ConnectionIndicator.prototype.generateText = function () {
uploadBandwidth + "</td></tr>";
result += transport + "</table>";
}
return result;
@@ -307,11 +269,9 @@ ConnectionIndicator.prototype.showMore = function () {
};
function createIcon(classes)
{
function createIcon(classes) {
var icon = document.createElement("span");
for(var i in classes)
{
for(var i in classes) {
icon.classList.add(classes[i]);
}
icon.appendChild(
@@ -326,9 +286,10 @@ ConnectionIndicator.prototype.create = function () {
this.connectionIndicatorContainer = document.createElement("div");
this.connectionIndicatorContainer.className = "connectionindicator";
this.connectionIndicatorContainer.style.display = "none";
this.videoContainer.appendChild(this.connectionIndicatorContainer);
this.videoContainer.container.appendChild(
this.connectionIndicatorContainer);
this.popover = new JitsiPopover(
$("#" + this.videoContainer.id + " > .connectionindicator"),
$("#" + this.videoContainer.videoSpanId + " > .connectionindicator"),
{content: "<div class=\"connection_info\" data-i18n='connectionindicator.na'>" +
APP.translation.translateString("connectionindicator.na") + "</div>",
skin: "black"});
@@ -337,17 +298,17 @@ ConnectionIndicator.prototype.create = function () {
createIcon(["connection", "connection_empty"]));
this.fullIcon = this.connectionIndicatorContainer.appendChild(
createIcon(["connection", "connection_full"]));
};
/**
* Removes the indicator
*/
ConnectionIndicator.prototype.remove = function()
{
this.connectionIndicatorContainer.remove();
ConnectionIndicator.prototype.remove = function() {
if (this.connectionIndicatorContainer.parentNode) {
this.connectionIndicatorContainer.parentNode.removeChild(
this.connectionIndicatorContainer);
}
this.popover.forceHide();
};
/**
@@ -356,33 +317,27 @@ ConnectionIndicator.prototype.remove = function()
* @param object the statistics data.
*/
ConnectionIndicator.prototype.updateConnectionQuality =
function (percent, object) {
function (percent, object) {
if(percent === null)
{
if (percent === null) {
this.connectionIndicatorContainer.style.display = "none";
this.popover.forceHide();
return;
}
else
{
} else {
if(this.connectionIndicatorContainer.style.display == "none") {
this.connectionIndicatorContainer.style.display = "block";
this.videoLayout.updateMutePosition(this.videoContainer.id);
this.videoContainer.updateIconPositions();
}
}
this.bandwidth = object.bandwidth;
this.bitrate = object.bitrate;
this.packetLoss = object.packetLoss;
this.transport = object.transport;
if(object.resolution)
{
if (object.resolution) {
this.resolution = object.resolution;
}
for(var quality in ConnectionIndicator.connectionQualityValues)
{
if(percent >= quality)
{
for (var quality in ConnectionIndicator.connectionQualityValues) {
if (percent >= quality) {
this.fullIcon.style.width =
ConnectionIndicator.connectionQualityValues[quality];
}

View File

@@ -0,0 +1,684 @@
/* global $, APP, Strophe, interfaceConfig */
var Avatar = require("../avatar/Avatar");
var RTCBrowserType = require("../../RTC/RTCBrowserType");
var UIUtil = require("../util/UIUtil");
var UIEvents = require("../../../service/UI/UIEvents");
var xmpp = require("../../xmpp/xmpp");
var ToolbarToggler = require("../toolbars/ToolbarToggler");
// FIXME: With Temasys we have to re-select everytime
//var video = $('#largeVideo');
var currentVideoWidth = null;
var currentVideoHeight = null;
// By default we use camera
var getVideoSize = getCameraVideoSize;
var getVideoPosition = getCameraVideoPosition;
/**
* The small video instance that is displayed in the large video
* @type {SmallVideo}
*/
var currentSmallVideo = null;
/**
* Indicates whether the large video is enabled.
* @type {boolean}
*/
var isEnabled = true;
/**
* Current large video state.
* Possible values - video, prezi or etherpad.
* @type {string}
*/
var state = "video";
/**
* Returns the html element associated with the passed state of large video
* @param state the state.
* @returns {JQuery|*|jQuery|HTMLElement} the container.
*/
function getContainerByState(state)
{
var selector = null;
switch (state)
{
case "video":
selector = "#largeVideoWrapper";
break;
case "etherpad":
selector = "#etherpad>iframe";
break;
case "prezi":
selector = "#presentation>iframe";
break;
}
return (selector !== null)? $(selector) : null;
}
/**
* Sets the size and position of the given video element.
*
* @param video the video element to position
* @param width the desired video width
* @param height the desired video height
* @param horizontalIndent the left and right indent
* @param verticalIndent the top and bottom indent
*/
function positionVideo(video,
width,
height,
horizontalIndent,
verticalIndent,
animate) {
if (animate) {
video.animate({
width: width,
height: height,
top: verticalIndent,
bottom: verticalIndent,
left: horizontalIndent,
right: horizontalIndent
},
{
queue: false,
duration: 500
});
} else {
video.width(width);
video.height(height);
video.css({ top: verticalIndent + 'px',
bottom: verticalIndent + 'px',
left: horizontalIndent + 'px',
right: horizontalIndent + 'px'});
}
}
/**
* Returns an array of the video dimensions, so that it keeps it's aspect
* ratio and fits available area with it's larger dimension. This method
* ensures that whole video will be visible and can leave empty areas.
*
* @return an array with 2 elements, the video width and the video height
*/
function getDesktopVideoSize(videoWidth,
videoHeight,
videoSpaceWidth,
videoSpaceHeight) {
if (!videoWidth)
videoWidth = currentVideoWidth;
if (!videoHeight)
videoHeight = currentVideoHeight;
var aspectRatio = videoWidth / videoHeight;
var availableWidth = Math.max(videoWidth, videoSpaceWidth);
var availableHeight = Math.max(videoHeight, videoSpaceHeight);
videoSpaceHeight -= $('#remoteVideos').outerHeight();
if (availableWidth / aspectRatio >= videoSpaceHeight)
{
availableHeight = videoSpaceHeight;
availableWidth = availableHeight * aspectRatio;
}
if (availableHeight * aspectRatio >= videoSpaceWidth)
{
availableWidth = videoSpaceWidth;
availableHeight = availableWidth / aspectRatio;
}
return [availableWidth, availableHeight];
}
/**
* Returns an array of the video horizontal and vertical indents,
* so that if fits its parent.
*
* @return an array with 2 elements, the horizontal indent and the vertical
* indent
*/
function getCameraVideoPosition(videoWidth,
videoHeight,
videoSpaceWidth,
videoSpaceHeight) {
// Parent height isn't completely calculated when we position the video in
// full screen mode and this is why we use the screen height in this case.
// Need to think it further at some point and implement it properly.
var isFullScreen = document.fullScreen ||
document.mozFullScreen ||
document.webkitIsFullScreen;
if (isFullScreen)
videoSpaceHeight = window.innerHeight;
var horizontalIndent = (videoSpaceWidth - videoWidth) / 2;
var verticalIndent = (videoSpaceHeight - videoHeight) / 2;
return [horizontalIndent, verticalIndent];
}
/**
* Returns an array of the video horizontal and vertical indents.
* Centers horizontally and top aligns vertically.
*
* @return an array with 2 elements, the horizontal indent and the vertical
* indent
*/
function getDesktopVideoPosition(videoWidth,
videoHeight,
videoSpaceWidth,
videoSpaceHeight) {
var horizontalIndent = (videoSpaceWidth - videoWidth) / 2;
var verticalIndent = 0;// Top aligned
return [horizontalIndent, verticalIndent];
}
/**
* Returns an array of the video dimensions, so that it covers the screen.
* It leaves no empty areas, but some parts of the video might not be visible.
*
* @return an array with 2 elements, the video width and the video height
*/
function getCameraVideoSize(videoWidth,
videoHeight,
videoSpaceWidth,
videoSpaceHeight) {
if (!videoWidth)
videoWidth = currentVideoWidth;
if (!videoHeight)
videoHeight = currentVideoHeight;
var aspectRatio = videoWidth / videoHeight;
var availableWidth = Math.max(videoWidth, videoSpaceWidth);
var availableHeight = Math.max(videoHeight, videoSpaceHeight);
if (availableWidth / aspectRatio < videoSpaceHeight) {
availableHeight = videoSpaceHeight;
availableWidth = availableHeight * aspectRatio;
}
if (availableHeight * aspectRatio < videoSpaceWidth) {
availableWidth = videoSpaceWidth;
availableHeight = availableWidth / aspectRatio;
}
return [availableWidth, availableHeight];
}
/**
* Updates the src of the active speaker avatar
* @param jid of the current active speaker
*/
function updateActiveSpeakerAvatarSrc() {
var avatar = $("#activeSpeakerAvatar")[0];
var jid = currentSmallVideo.peerJid;
var url = Avatar.getActiveSpeakerUrl(jid);
if (avatar.src === url)
return;
if (jid) {
avatar.src = url;
currentSmallVideo.showAvatar();
}
}
/**
* Change the video source of the large video.
* @param isVisible
*/
function changeVideo(isVisible) {
if (!currentSmallVideo) {
console.error("Unable to change large video - no 'currentSmallVideo'");
return;
}
updateActiveSpeakerAvatarSrc();
APP.RTC.setVideoSrc($('#largeVideo')[0], currentSmallVideo.getSrc());
var videoTransform = document.getElementById('largeVideo')
.style.webkitTransform;
var flipX = currentSmallVideo.flipX;
if (flipX && videoTransform !== 'scaleX(-1)') {
document.getElementById('largeVideo').style.webkitTransform =
"scaleX(-1)";
} else if (!flipX && videoTransform === 'scaleX(-1)') {
document.getElementById('largeVideo').style.webkitTransform =
"none";
}
var isDesktop = currentSmallVideo.getVideoType() === 'screen';
// Change the way we'll be measuring and positioning large video
getVideoSize = isDesktop ? getDesktopVideoSize : getCameraVideoSize;
getVideoPosition = isDesktop ? getDesktopVideoPosition :
getCameraVideoPosition;
// Only if the large video is currently visible.
if (isVisible) {
LargeVideo.VideoLayout.largeVideoUpdated(currentSmallVideo);
$('#largeVideoWrapper').fadeTo(300, 1);
}
}
/**
* Creates the html elements for the large video.
*/
function createLargeVideoHTML()
{
var html = '<div id="largeVideoContainer" class="videocontainer">';
html += '<div id="presentation"></div>' +
'<div id="etherpad"></div>' +
'<a target="_new"><div class="watermark leftwatermark"></div></a>' +
'<a target="_new"><div class="watermark rightwatermark"></div></a>' +
'<a class="poweredby" href="http://jitsi.org" target="_new" >' +
'<span data-i18n="poweredby"></span> jitsi.org' +
'</a>'+
'<div id="activeSpeaker">' +
'<img id="activeSpeakerAvatar" src=""/>' +
'<canvas id="activeSpeakerAudioLevel"></canvas>' +
'</div>' +
'<div id="largeVideoWrapper">' +
'<video id="largeVideo" autoplay oncontextmenu="return false;"></video>' +
'</div id="largeVideoWrapper">' +
'<span id="videoConnectionMessage"></span>';
html += '</div>';
$(html).prependTo("#videospace");
if (interfaceConfig.SHOW_JITSI_WATERMARK) {
var leftWatermarkDiv
= $("#largeVideoContainer div[class='watermark leftwatermark']");
leftWatermarkDiv.css({display: 'block'});
leftWatermarkDiv.parent().get(0).href
= interfaceConfig.JITSI_WATERMARK_LINK;
}
if (interfaceConfig.SHOW_BRAND_WATERMARK) {
var rightWatermarkDiv
= $("#largeVideoContainer div[class='watermark rightwatermark']");
rightWatermarkDiv.css({display: 'block'});
rightWatermarkDiv.parent().get(0).href
= interfaceConfig.BRAND_WATERMARK_LINK;
rightWatermarkDiv.get(0).style.backgroundImage
= "url(images/rightwatermark.png)";
}
if (interfaceConfig.SHOW_POWERED_BY) {
$("#largeVideoContainer>a[class='poweredby']").css({display: 'block'});
}
if (!RTCBrowserType.isIExplorer()) {
$('#largeVideo').volume = 0;
}
}
var LargeVideo = {
init: function (VideoLayout, emitter) {
if(!isEnabled)
return;
createLargeVideoHTML();
this.VideoLayout = VideoLayout;
this.eventEmitter = emitter;
this.eventEmitter.emit(UIEvents.LARGEVIDEO_INIT);
var self = this;
// Listen for large video size updates
var largeVideo = $('#largeVideo')[0];
var onplaying = function (arg1, arg2, arg3) {
// re-select
if (RTCBrowserType.isTemasysPluginUsed())
largeVideo = $('#largeVideo')[0];
currentVideoWidth = largeVideo.videoWidth;
currentVideoHeight = largeVideo.videoHeight;
self.position(currentVideoWidth, currentVideoHeight);
};
largeVideo.onplaying = onplaying;
},
/**
* Indicates if the large video is currently visible.
*
* @return <tt>true</tt> if visible, <tt>false</tt> - otherwise
*/
isLargeVideoVisible: function() {
return $('#largeVideoWrapper').is(':visible');
},
/**
* Returns <tt>true</tt> if the user is currently displayed on large video.
*/
isCurrentlyOnLarge: function (resourceJid) {
return currentSmallVideo && resourceJid &&
currentSmallVideo.getResourceJid() === resourceJid;
},
/**
* Updates the large video with the given new video source.
*/
updateLargeVideo: function (resourceJid, forceUpdate) {
if(!isEnabled)
return;
var newSmallVideo = this.VideoLayout.getSmallVideo(resourceJid);
console.log('hover in ' + resourceJid + ', video: ', newSmallVideo);
if (!newSmallVideo) {
console.error("Small video not found for: " + resourceJid);
return;
}
if (!LargeVideo.isCurrentlyOnLarge(resourceJid) || forceUpdate) {
$('#activeSpeaker').css('visibility', 'hidden');
var oldSmallVideo = null;
if (currentSmallVideo) {
oldSmallVideo = currentSmallVideo;
}
currentSmallVideo = newSmallVideo;
var oldJid = null;
if (oldSmallVideo)
oldJid = oldSmallVideo.peerJid;
if (oldJid !== resourceJid) {
// we want the notification to trigger even if userJid is undefined,
// or null.
this.eventEmitter.emit(UIEvents.SELECTED_ENDPOINT, resourceJid);
}
// We are doing fadeOut/fadeIn animations on parent div which wraps
// largeVideo, because when Temasys plugin is in use it replaces
// <video> elements with plugin <object> tag. In Safari jQuery is
// unable to store values on this plugin object which breaks all
// animation effects performed on it directly.
//
// If for any reason large video was hidden before calling fadeOut
// changeVideo will never be called, so we call show() in chain just
// to be sure
$('#largeVideoWrapper').show().fadeTo(300, 0,
changeVideo.bind($('#largeVideo'), this.isLargeVideoVisible()));
} else {
if (currentSmallVideo) {
currentSmallVideo.showAvatar();
}
}
},
/**
* Shows/hides the large video.
*/
setLargeVideoVisible: function(isVisible) {
if(!isEnabled)
return;
if (isVisible) {
$('#largeVideoWrapper').css({visibility: 'visible'});
$('.watermark').css({visibility: 'visible'});
if(currentSmallVideo)
currentSmallVideo.enableDominantSpeaker(true);
}
else {
$('#largeVideoWrapper').css({visibility: 'hidden'});
$('#activeSpeaker').css('visibility', 'hidden');
$('.watermark').css({visibility: 'hidden'});
if(currentSmallVideo)
currentSmallVideo.enableDominantSpeaker(false);
}
},
onVideoTypeChanged: function (resourceJid, newVideoType) {
if (!isEnabled)
return;
if (LargeVideo.isCurrentlyOnLarge(resourceJid))
{
var isDesktop = newVideoType === 'screen';
getVideoSize = isDesktop ? getDesktopVideoSize : getCameraVideoSize;
getVideoPosition = isDesktop ? getDesktopVideoPosition
: getCameraVideoPosition;
this.position(null, null, null, null, true);
}
},
/**
* Positions the large video.
*
* @param videoWidth the stream video width
* @param videoHeight the stream video height
*/
position: function (videoWidth, videoHeight,
videoSpaceWidth, videoSpaceHeight, animate) {
if(!isEnabled)
return;
if(!videoSpaceWidth)
videoSpaceWidth = $('#videospace').width();
if(!videoSpaceHeight)
videoSpaceHeight = window.innerHeight;
var videoSize = getVideoSize(videoWidth,
videoHeight,
videoSpaceWidth,
videoSpaceHeight);
var largeVideoWidth = videoSize[0];
var largeVideoHeight = videoSize[1];
var videoPosition = getVideoPosition(largeVideoWidth,
largeVideoHeight,
videoSpaceWidth,
videoSpaceHeight);
var horizontalIndent = videoPosition[0];
var verticalIndent = videoPosition[1];
positionVideo($('#largeVideoWrapper'),
largeVideoWidth,
largeVideoHeight,
horizontalIndent, verticalIndent, animate);
},
/**
* Resizes the large html elements.
* @param animate boolean property that indicates whether the resize should be animated or not.
* @param isChatVisible boolean property that indicates whether the chat area is displayed or not.
* If that parameter is null the method will check the chat pannel visibility.
* @param completeFunction a function to be called when the video space is resized
* @returns {*[]} array with the current width and height values of the largeVideo html element.
*/
resize: function (animate, isVisible, completeFunction) {
if(!isEnabled)
return;
var availableHeight = window.innerHeight;
var availableWidth = UIUtil.getAvailableVideoWidth(isVisible);
if (availableWidth < 0 || availableHeight < 0) return;
var avatarSize = interfaceConfig.ACTIVE_SPEAKER_AVATAR_SIZE;
var top = availableHeight / 2 - avatarSize / 4 * 3;
$('#activeSpeaker').css('top', top);
this.VideoLayout.resizeVideoSpace(animate, isVisible, completeFunction);
if(animate) {
$('#largeVideoContainer').animate({
width: availableWidth,
height: availableHeight
},
{
queue: false,
duration: 500
});
} else {
$('#largeVideoContainer').width(availableWidth);
$('#largeVideoContainer').height(availableHeight);
}
return [availableWidth, availableHeight];
},
resizeVideoAreaAnimated: function (isVisible, completeFunction) {
if(!isEnabled)
return;
var size = this.resize(true, isVisible, completeFunction);
this.position(null, null, size[0], size[1], true);
},
getResourceJid: function () {
return currentSmallVideo ? currentSmallVideo.getResourceJid() : null;
},
updateAvatar: function (resourceJid) {
if(!isEnabled)
return;
if (resourceJid === this.getResourceJid()) {
updateActiveSpeakerAvatarSrc();
}
},
showAvatar: function (resourceJid, show) {
if (!isEnabled)
return;
if (this.getResourceJid() === resourceJid && state === "video") {
$("#largeVideoWrapper")
.css("visibility", show ? "hidden" : "visible");
$('#activeSpeaker').css("visibility", show ? "visible" : "hidden");
return true;
}
return false;
},
/**
* Disables the large video
*/
disable: function () {
isEnabled = false;
},
/**
* Enables the large video
*/
enable: function () {
isEnabled = true;
},
/**
* Returns true if the video is enabled.
*/
isEnabled: function () {
return isEnabled;
},
/**
* Creates the iframe used by the etherpad
* @param src the value for src attribute
* @param onloadHandler handler executed when the iframe loads it content
* @returns {HTMLElement} the iframe
*/
createEtherpadIframe: function (src, onloadHandler) {
if(!isEnabled)
return;
var etherpadIFrame = document.createElement('iframe');
etherpadIFrame.src = src;
etherpadIFrame.frameBorder = 0;
etherpadIFrame.scrolling = "no";
etherpadIFrame.width = $('#largeVideoContainer').width() || 640;
etherpadIFrame.height = $('#largeVideoContainer').height() || 480;
etherpadIFrame.setAttribute('style', 'visibility: hidden;');
document.getElementById('etherpad').appendChild(etherpadIFrame);
etherpadIFrame.onload = onloadHandler;
return etherpadIFrame;
},
/**
* Changes the state of the large video.
* Possible values - video, prezi, etherpad.
* @param newState - the new state
*/
setState: function (newState) {
if(state === newState)
return;
var currentContainer = getContainerByState(state);
if(!currentContainer)
return;
var self = this;
var oldState = state;
switch (newState)
{
case "etherpad":
$('#activeSpeaker').css('visibility', 'hidden');
currentContainer.fadeOut(300, function () {
if (oldState === "prezi") {
currentContainer.css({opacity: '0'});
$('#reloadPresentation').css({display: 'none'});
}
else {
self.setLargeVideoVisible(false);
}
});
$('#etherpad>iframe').fadeIn(300, function () {
document.body.style.background = '#eeeeee';
$('#etherpad>iframe').css({visibility: 'visible'});
$('#etherpad').css({zIndex: 2});
});
break;
case "prezi":
var prezi = $('#presentation>iframe');
currentContainer.fadeOut(300, function() {
document.body.style.background = 'black';
});
prezi.fadeIn(300, function() {
prezi.css({opacity:'1'});
ToolbarToggler.dockToolbar(true);//fix that
self.setLargeVideoVisible(false);
$('#etherpad>iframe').css({visibility: 'hidden'});
$('#etherpad').css({zIndex: 0});
});
$('#activeSpeaker').css('visibility', 'hidden');
break;
case "video":
currentContainer.fadeOut(300, function () {
$('#presentation>iframe').css({opacity:'0'});
$('#reloadPresentation').css({display:'none'});
$('#etherpad>iframe').css({visibility: 'hidden'});
$('#etherpad').css({zIndex: 0});
document.body.style.background = 'black';
ToolbarToggler.dockToolbar(false);//fix that
});
$('#largeVideoWrapper').fadeIn(300, function () {
self.setLargeVideoVisible(true);
});
break;
}
state = newState;
},
/**
* Returns the current state of the large video.
* @returns {string} the current state - video, prezi or etherpad.
*/
getState: function () {
return state;
},
/**
* Sets hover handlers for the large video container div.
*
* @param inHandler
* @param outHandler
*/
setHover: function(inHandler, outHandler)
{
$('#largeVideoContainer').hover(inHandler, outHandler);
},
/**
* Enables/disables the filter indicating a video problem to the user.
*
* @param enable <tt>true</tt> to enable, <tt>false</tt> to disable
*/
enableVideoProblemFilter: function (enable) {
$("#largeVideo").toggleClass("videoProblemFilter", enable);
}
};
module.exports = LargeVideo;

View File

@@ -0,0 +1,232 @@
/* global $, interfaceConfig, APP */
var SmallVideo = require("./SmallVideo");
var ConnectionIndicator = require("./ConnectionIndicator");
var NicknameHandler = require("../util/NicknameHandler");
var UIUtil = require("../util/UIUtil");
var LargeVideo = require("./LargeVideo");
var RTCBrowserType = require("../../RTC/RTCBrowserType");
function LocalVideo(VideoLayout) {
this.videoSpanId = "localVideoContainer";
this.container = $("#localVideoContainer").get(0);
this.bindHoverHandler();
this.VideoLayout = VideoLayout;
this.flipX = true;
this.isLocal = true;
this.peerJid = null;
}
LocalVideo.prototype = Object.create(SmallVideo.prototype);
LocalVideo.prototype.constructor = LocalVideo;
/**
* Creates the edit display name button.
*
* @returns {object} the edit button
*/
function createEditDisplayNameButton() {
var editButton = document.createElement('a');
editButton.className = 'displayname';
UIUtil.setTooltip(editButton,
"videothumbnail.editnickname",
"top");
editButton.innerHTML = '<i class="fa fa-pencil"></i>';
return editButton;
}
/**
* Sets the display name for the given video span id.
*/
LocalVideo.prototype.setDisplayName = function(displayName, key) {
if (!this.container) {
console.warn(
"Unable to set displayName - " + this.videoSpanId +
" does not exist");
return;
}
var nameSpan = $('#' + this.videoSpanId + '>span.displayname');
var defaultLocalDisplayName = APP.translation.generateTranslationHTML(
interfaceConfig.DEFAULT_LOCAL_DISPLAY_NAME);
var meHTML;
// If we already have a display name for this video.
if (nameSpan.length > 0) {
if (nameSpan.text() !== displayName) {
if (displayName && displayName.length > 0) {
meHTML = APP.translation.generateTranslationHTML("me");
$('#localDisplayName').html(displayName + ' (' + meHTML + ')');
} else {
$('#localDisplayName').html(defaultLocalDisplayName);
}
}
} else {
var editButton = createEditDisplayNameButton();
nameSpan = document.createElement('span');
nameSpan.className = 'displayname';
$('#' + this.videoSpanId)[0].appendChild(nameSpan);
if (displayName && displayName.length > 0) {
meHTML = APP.translation.generateTranslationHTML("me");
nameSpan.innerHTML = displayName + meHTML;
}
else {
nameSpan.innerHTML = defaultLocalDisplayName;
}
nameSpan.id = 'localDisplayName';
this.container.appendChild(editButton);
//translates popover of edit button
APP.translation.translateElement($("a.displayname"));
var editableText = document.createElement('input');
editableText.className = 'displayname';
editableText.type = 'text';
editableText.id = 'editDisplayName';
if (displayName && displayName.length) {
editableText.value = displayName;
}
var defaultNickname = APP.translation.translateString(
"defaultNickname", {name: "Jane Pink"});
editableText.setAttribute('style', 'display:none;');
editableText.setAttribute('data-18n',
'[placeholder]defaultNickname');
editableText.setAttribute("data-i18n-options",
JSON.stringify({name: "Jane Pink"}));
editableText.setAttribute("placeholder", defaultNickname);
this.container.appendChild(editableText);
var self = this;
$('#localVideoContainer .displayname')
.bind("click", function (e) {
var editDisplayName = $('#editDisplayName');
e.preventDefault();
e.stopPropagation();
$('#localDisplayName').hide();
editDisplayName.show();
editDisplayName.focus();
editDisplayName.select();
editDisplayName.one("focusout", function (e) {
self.VideoLayout.inputDisplayNameHandler(this.value);
});
editDisplayName.on('keydown', function (e) {
if (e.keyCode === 13) {
e.preventDefault();
self.VideoLayout.inputDisplayNameHandler(this.value);
}
});
});
}
};
LocalVideo.prototype.inputDisplayNameHandler = function (name) {
NicknameHandler.setNickname(name);
var localDisplayName = $('#localDisplayName');
if (!localDisplayName.is(":visible")) {
if (NicknameHandler.getNickname()) {
var meHTML = APP.translation.generateTranslationHTML("me");
localDisplayName.html(NicknameHandler.getNickname() + " (" +
meHTML + ")");
} else {
var defaultHTML = APP.translation.generateTranslationHTML(
interfaceConfig.DEFAULT_LOCAL_DISPLAY_NAME);
localDisplayName .html(defaultHTML);
}
localDisplayName.show();
}
$('#editDisplayName').hide();
};
LocalVideo.prototype.createConnectionIndicator = function() {
if(this.connectionIndicator)
return;
this.connectionIndicator = new ConnectionIndicator(this, null);
};
LocalVideo.prototype.changeVideo = function (stream, isMuted) {
var self = this;
function localVideoClick(event) {
// FIXME: with Temasys plugin event arg is not an event, but
// the clicked object itself, so we have to skip this call
if (event.stopPropagation) {
event.stopPropagation();
}
self.VideoLayout.handleVideoThumbClicked(
true,
APP.xmpp.myResource());
}
var localVideoContainerSelector = $('#localVideoContainer');
localVideoContainerSelector.off('click');
localVideoContainerSelector.on('click', localVideoClick);
if(isMuted) {
APP.UI.setVideoMute(true);
return;
}
this.flipX = stream.videoType != "screen";
var localVideo = document.createElement('video');
localVideo.id = 'localVideo_' +
APP.RTC.getStreamID(stream.getOriginalStream());
if (!RTCBrowserType.isIExplorer()) {
localVideo.autoplay = true;
localVideo.volume = 0; // is it required if audio is separated ?
}
localVideo.oncontextmenu = function () { return false; };
var localVideoContainer = document.getElementById('localVideoWrapper');
// Put the new video always in front
UIUtil.prependChild(localVideoContainer, localVideo);
var localVideoSelector = $('#' + localVideo.id);
// Add click handler to both video and video wrapper elements in case
// there's no video.
// onclick has to be used with Temasys plugin
localVideo.onclick = localVideoClick;
if (this.flipX) {
localVideoSelector.addClass("flipVideoX");
}
// Attach WebRTC stream
APP.RTC.attachMediaStream(localVideoSelector, stream.getOriginalStream());
// Add stream ended handler
stream.getOriginalStream().onended = function () {
// We have to re-select after attach when Temasys plugin is used,
// because <video> element is replaced with <object>
localVideo = $('#' + localVideo.id)[0];
localVideoContainer.removeChild(localVideo);
self.VideoLayout.updateRemovedVideo(APP.xmpp.myResource());
};
};
LocalVideo.prototype.joined = function (jid) {
this.peerJid = jid;
};
LocalVideo.prototype.getResourceJid = function () {
var myResource = APP.xmpp.myResource();
if (!myResource) {
console.error("Requested local resource before we're in the MUC");
}
return myResource;
};
module.exports = LocalVideo;

View File

@@ -0,0 +1,400 @@
/* global $, APP, require, Strophe, interfaceConfig */
var ConnectionIndicator = require("./ConnectionIndicator");
var SmallVideo = require("./SmallVideo");
var AudioLevels = require("../audio_levels/AudioLevels");
var LargeVideo = require("./LargeVideo");
var Avatar = require("../avatar/Avatar");
var RTCBrowserType = require("../../RTC/RTCBrowserType");
var UIUtils = require("../util/UIUtil");
function RemoteVideo(peerJid, VideoLayout) {
this.peerJid = peerJid;
this.resourceJid = Strophe.getResourceFromJid(peerJid);
this.videoSpanId = 'participant_' + this.resourceJid;
this.VideoLayout = VideoLayout;
this.addRemoteVideoContainer();
this.connectionIndicator = new ConnectionIndicator(
this, this.peerJid);
this.setDisplayName();
var nickfield = document.createElement('span');
nickfield.className = "nick";
nickfield.appendChild(document.createTextNode(this.resourceJid));
this.container.appendChild(nickfield);
this.bindHoverHandler();
this.flipX = false;
this.isLocal = false;
}
RemoteVideo.prototype = Object.create(SmallVideo.prototype);
RemoteVideo.prototype.constructor = RemoteVideo;
RemoteVideo.prototype.addRemoteVideoContainer = function() {
this.container = RemoteVideo.createContainer(this.videoSpanId);
if (APP.xmpp.isModerator())
this.addRemoteVideoMenu();
AudioLevels.updateAudioLevelCanvas(this.peerJid, this.VideoLayout);
return this.container;
};
/**
* Adds the remote video menu element for the given <tt>jid</tt> in the
* given <tt>parentElement</tt>.
*
* @param jid the jid indicating the video for which we're adding a menu.
* @param parentElement the parent element where this menu will be added
*/
if (!interfaceConfig.filmStripOnly) {
RemoteVideo.prototype.addRemoteVideoMenu = function () {
var spanElement = document.createElement('span');
spanElement.className = 'remotevideomenu';
this.container.appendChild(spanElement);
var menuElement = document.createElement('i');
menuElement.className = 'fa fa-angle-down';
menuElement.title = 'Remote user controls';
spanElement.appendChild(menuElement);
var popupmenuElement = document.createElement('ul');
popupmenuElement.className = 'popupmenu';
popupmenuElement.id = 'remote_popupmenu_' + this.getResourceJid();
spanElement.appendChild(popupmenuElement);
var muteMenuItem = document.createElement('li');
var muteLinkItem = document.createElement('a');
var mutedIndicator = "<i style='float:left;' " +
"class='icon-mic-disabled'></i>";
if (!this.isMuted) {
muteLinkItem.innerHTML = mutedIndicator +
" <div style='width: 90px;margin-left: 20px;' " +
"data-i18n='videothumbnail.domute'></div>";
muteLinkItem.className = 'mutelink';
}
else {
muteLinkItem.innerHTML = mutedIndicator +
" <div style='width: 90px;margin-left: 20px;' " +
"data-i18n='videothumbnail.muted'></div>";
muteLinkItem.className = 'mutelink disabled';
}
var self = this;
muteLinkItem.onclick = function(){
if ($(this).attr('disabled') != undefined) {
event.preventDefault();
}
var isMute = self.isMuted == true;
APP.xmpp.setMute(self.peerJid, !isMute);
popupmenuElement.setAttribute('style', 'display:none;');
if (isMute) {
this.innerHTML = mutedIndicator +
" <div style='width: 90px;margin-left: 20px;' " +
"data-i18n='videothumbnail.muted'></div>";
this.className = 'mutelink disabled';
}
else {
this.innerHTML = mutedIndicator +
" <div style='width: 90px;margin-left: 20px;' " +
"data-i18n='videothumbnail.domute'></div>";
this.className = 'mutelink';
}
};
muteMenuItem.appendChild(muteLinkItem);
popupmenuElement.appendChild(muteMenuItem);
var ejectIndicator = "<i style='float:left;' class='fa fa-eject'></i>";
var ejectMenuItem = document.createElement('li');
var ejectLinkItem = document.createElement('a');
var ejectText = "<div style='width: 90px;margin-left: 20px;' " +
"data-i18n='videothumbnail.kick'>&nbsp;</div>";
ejectLinkItem.innerHTML = ejectIndicator + ' ' + ejectText;
ejectLinkItem.onclick = function(){
APP.xmpp.eject(self.peerJid);
popupmenuElement.setAttribute('style', 'display:none;');
};
ejectMenuItem.appendChild(ejectLinkItem);
popupmenuElement.appendChild(ejectMenuItem);
var paddingSpan = document.createElement('span');
paddingSpan.className = 'popupmenuPadding';
popupmenuElement.appendChild(paddingSpan);
APP.translation.translateElement(
$("#" + popupmenuElement.id + " > li > a > div"));
};
} else {
RemoteVideo.prototype.addRemoteVideoMenu = function() {}
}
/**
* Removes the remote stream element corresponding to the given stream and
* parent container.
*
* @param stream the stream
* @param isVideo <tt>true</tt> if given <tt>stream</tt> is a video one.
*/
RemoteVideo.prototype.removeRemoteStreamElement =
function (stream, isVideo, id) {
if (!this.container)
return false;
var select = null;
if (isVideo) {
select = $('#' + id);
}
else
select = $('#' + this.videoSpanId + '>audio');
select.remove();
console.info((isVideo ? "Video" : "Audio") +
" removed " + this.getResourceJid(), select);
if (isVideo)
this.VideoLayout.updateRemovedVideo(this.getResourceJid());
};
/**
* Removes RemoteVideo from the page.
*/
RemoteVideo.prototype.remove = function () {
console.log("Remove thumbnail", this.peerJid);
this.removeConnectionIndicator();
// Remove whole container
if (this.container.parentNode)
this.container.parentNode.removeChild(this.container);
};
RemoteVideo.prototype.waitForPlayback = function (sel, stream) {
var isVideo = stream.getVideoTracks().length > 0;
if (!isVideo || stream.id === 'mixedmslabel') {
return;
}
var self = this;
var resourceJid = this.getResourceJid();
// Register 'onplaying' listener to trigger 'videoactive' on VideoLayout
// when video playback starts
var onPlayingHandler = function () {
// FIXME: why do i have to do this for FF?
if (RTCBrowserType.isFirefox()) {
APP.RTC.attachMediaStream(sel, stream);
}
if (RTCBrowserType.isTemasysPluginUsed()) {
sel = self.selectVideoElement();
}
self.VideoLayout.videoactive(sel, resourceJid);
sel[0].onplaying = null;
if (RTCBrowserType.isTemasysPluginUsed()) {
// 'currentTime' is used to check if the video has started
// and the value is not set by the plugin, so we do it
sel[0].currentTime = 1;
}
};
sel[0].onplaying = onPlayingHandler;
};
RemoteVideo.prototype.addRemoteStreamElement = function (sid, stream, thessrc) {
if (!this.container)
return;
var self = this;
var isVideo = stream.getVideoTracks().length > 0;
var streamElement = SmallVideo.createStreamElement(sid, stream);
var newElementId = streamElement.id;
// Put new stream element always in front
UIUtils.prependChild(this.container, streamElement);
var sel = $('#' + newElementId);
sel.hide();
// If the container is currently visible we attach the stream.
if (!isVideo || (this.container.offsetParent !== null && isVideo)) {
this.waitForPlayback(sel, stream);
APP.RTC.attachMediaStream(sel, stream);
}
stream.onended = function () {
console.log('stream ended', this);
self.removeRemoteStreamElement(stream, isVideo, newElementId);
};
// Add click handler.
var onClickHandler = function (event) {
self.VideoLayout.handleVideoThumbClicked(false, self.getResourceJid());
// On IE we need to populate this handler on video <object>
// and it does not give event instance as an argument,
// so we check here for methods.
if (event.stopPropagation && event.preventDefault) {
event.stopPropagation();
event.preventDefault();
}
return false;
};
this.container.onclick = onClickHandler;
// reselect
if (RTCBrowserType.isTemasysPluginUsed())
sel = $('#' + newElementId);
sel[0].onclick = onClickHandler;
},
/**
* Show/hide peer container for the given resourceJid.
*/
RemoteVideo.prototype.showPeerContainer = function (state) {
if (!this.container)
return;
var isHide = state === 'hide';
var resizeThumbnails = false;
if (!isHide) {
if (!$(this.container).is(':visible')) {
resizeThumbnails = true;
$(this.container).show();
}
this.showAvatar(state !== 'show');
}
else if ($(this.container).is(':visible') && isHide)
{
resizeThumbnails = true;
$(this.container).hide();
if(this.connectionIndicator)
this.connectionIndicator.hide();
}
if (resizeThumbnails) {
this.VideoLayout.resizeThumbnails();
}
// We want to be able to pin a participant from the contact list, even
// if he's not in the lastN set!
// ContactList.setClickable(resourceJid, !isHide);
};
RemoteVideo.prototype.removeConnectionIndicator = function () {
if (this.connectionIndicator)
this.connectionIndicator.remove();
};
RemoteVideo.prototype.hideConnectionIndicator = function () {
if (this.connectionIndicator)
this.connectionIndicator.hide();
};
/**
* Updates the remote video menu.
*
* @param jid the jid indicating the video for which we're adding a menu.
* @param isMuted indicates the current mute state
*/
RemoteVideo.prototype.updateRemoteVideoMenu = function (isMuted) {
var muteMenuItem
= $('#remote_popupmenu_' + this.getResourceJid() + '>li>a.mutelink');
var mutedIndicator = "<i class='icon-mic-disabled'></i>";
if (muteMenuItem.length) {
var muteLink = muteMenuItem.get(0);
if (isMuted === 'true') {
muteLink.innerHTML = mutedIndicator + ' Muted';
muteLink.className = 'mutelink disabled';
}
else {
muteLink.innerHTML = mutedIndicator + ' Mute';
muteLink.className = 'mutelink';
}
}
};
/**
* Sets the display name for the given video span id.
*/
RemoteVideo.prototype.setDisplayName = function(displayName, key) {
if (!this.container) {
console.warn( "Unable to set displayName - " + this.videoSpanId +
" does not exist");
return;
}
var nameSpan = $('#' + this.videoSpanId + '>span.displayname');
// If we already have a display name for this video.
if (nameSpan.length > 0) {
if (displayName && displayName.length > 0) {
$('#' + this.videoSpanId + '_name').html(displayName);
}
else if (key && key.length > 0) {
var nameHtml = APP.translation.generateTranslationHTML(key);
$('#' + this.videoSpanId + '_name').html(nameHtml);
}
else
$('#' + this.videoSpanId + '_name').text(
interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME);
} else {
nameSpan = document.createElement('span');
nameSpan.className = 'displayname';
$('#' + this.videoSpanId)[0].appendChild(nameSpan);
if (displayName && displayName.length > 0) {
nameSpan.innerText = displayName;
}
else
nameSpan.innerText = interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME;
nameSpan.id = this.videoSpanId + '_name';
}
};
/**
* Removes remote video menu element from video element identified by
* given <tt>videoElementId</tt>.
*
* @param videoElementId the id of local or remote video element.
*/
RemoteVideo.prototype.removeRemoteVideoMenu = function() {
var menuSpan = $('#' + this.videoSpanId + '>span.remotevideomenu');
if (menuSpan.length) {
menuSpan.remove();
}
};
RemoteVideo.prototype.getResourceJid = function () {
if (!this.resourceJid) {
console.error("Undefined resource jid");
}
return this.resourceJid;
};
RemoteVideo.createContainer = function (spanId) {
var container = document.createElement('span');
container.id = spanId;
container.className = 'videocontainer';
var remotes = document.getElementById('remoteVideos');
return remotes.appendChild(container);
};
module.exports = RemoteVideo;

View File

@@ -0,0 +1,398 @@
/* global $, APP, require */
var Avatar = require("../avatar/Avatar");
var UIUtil = require("../util/UIUtil");
var LargeVideo = require("./LargeVideo");
var RTCBrowserType = require("../../RTC/RTCBrowserType");
function SmallVideo() {
this.isMuted = false;
this.hasAvatar = false;
}
function setVisibility(selector, show) {
if (selector && selector.length > 0) {
selector.css("visibility", show ? "visible" : "hidden");
}
}
SmallVideo.prototype.showDisplayName = function(isShow) {
var nameSpan = $('#' + this.videoSpanId + '>span.displayname').get(0);
if (isShow) {
if (nameSpan && nameSpan.innerHTML && nameSpan.innerHTML.length)
nameSpan.setAttribute("style", "display:inline-block;");
}
else {
if (nameSpan)
nameSpan.setAttribute("style", "display:none;");
}
};
SmallVideo.prototype.setDeviceAvailabilityIcons = function (devices) {
if(!this.container)
return;
var noMic = $("#" + this.videoSpanId + " > .noMic");
var noVideo = $("#" + this.videoSpanId + " > .noVideo");
noMic.remove();
noVideo.remove();
if (!devices.audio) {
this.container.appendChild(
document.createElement("div")).setAttribute("class", "noMic");
}
if (!devices.video) {
this.container.appendChild(
document.createElement("div")).setAttribute("class", "noVideo");
}
if (!devices.audio && !devices.video) {
noMic.css("background-position", "75%");
noVideo.css("background-position", "25%");
noVideo.css("background-color", "transparent");
}
};
/**
* Sets the type of the video displayed by this instance.
* @param videoType 'camera' or 'screen'
*/
SmallVideo.prototype.setVideoType = function (videoType) {
this.videoType = videoType;
};
/**
* Returns the type of the video displayed by this instance.
* @returns {String} 'camera', 'screen' or undefined.
*/
SmallVideo.prototype.getVideoType = function () {
return this.videoType;
};
/**
* Shows the presence status message for the given video.
*/
SmallVideo.prototype.setPresenceStatus = function (statusMsg) {
if (!this.container) {
// No container
return;
}
var statusSpan = $('#' + this.videoSpanId + '>span.status');
if (!statusSpan.length) {
//Add status span
statusSpan = document.createElement('span');
statusSpan.className = 'status';
statusSpan.id = this.videoSpanId + '_status';
$('#' + this.videoSpanId)[0].appendChild(statusSpan);
statusSpan = $('#' + this.videoSpanId + '>span.status');
}
// Display status
if (statusMsg && statusMsg.length) {
$('#' + this.videoSpanId + '_status').text(statusMsg);
statusSpan.get(0).setAttribute("style", "display:inline-block;");
}
else {
// Hide
statusSpan.get(0).setAttribute("style", "display:none;");
}
};
/**
* Creates an audio or video stream element.
*/
SmallVideo.createStreamElement = function (sid, stream) {
var isVideo = stream.getVideoTracks().length > 0;
var element = isVideo ? document.createElement('video')
: document.createElement('audio');
var id = (isVideo ? 'remoteVideo_' : 'remoteAudio_') + sid + '_' +
APP.RTC.getStreamID(stream);
element.id = id;
if (!RTCBrowserType.isIExplorer()) {
element.autoplay = true;
}
element.oncontextmenu = function () { return false; };
return element;
};
/**
* Configures hoverIn/hoverOut handlers.
*/
SmallVideo.prototype.bindHoverHandler = function () {
// Add hover handler
var self = this;
$(this.container).hover(
function () {
self.showDisplayName(true);
},
function () {
// If the video has been "pinned" by the user we want to
// keep the display name on place.
if (!LargeVideo.isLargeVideoVisible() ||
!LargeVideo.isCurrentlyOnLarge(self.getResourceJid()))
self.showDisplayName(false);
}
);
};
/**
* Updates the data for the indicator
* @param id the id of the indicator
* @param percent the percent for connection quality
* @param object the data
*/
SmallVideo.prototype.updateStatsIndicator = function (percent, object) {
if(this.connectionIndicator)
this.connectionIndicator.updateConnectionQuality(percent, object);
};
SmallVideo.prototype.hideIndicator = function () {
if(this.connectionIndicator)
this.connectionIndicator.hideIndicator();
};
/**
* Shows audio muted indicator over small videos.
* @param {string} isMuted
*/
SmallVideo.prototype.showAudioIndicator = function(isMuted) {
var audioMutedSpan = $('#' + this.videoSpanId + '>span.audioMuted');
if (!isMuted) {
if (audioMutedSpan.length > 0) {
audioMutedSpan.popover('hide');
audioMutedSpan.remove();
}
}
else {
if(audioMutedSpan.length == 0 ) {
audioMutedSpan = document.createElement('span');
audioMutedSpan.className = 'audioMuted';
UIUtil.setTooltip(audioMutedSpan,
"videothumbnail.mute",
"top");
this.container.appendChild(audioMutedSpan);
APP.translation.translateElement($('#' + this.videoSpanId + " > span"));
var mutedIndicator = document.createElement('i');
mutedIndicator.className = 'icon-mic-disabled';
audioMutedSpan.appendChild(mutedIndicator);
}
this.updateIconPositions();
}
this.isMuted = isMuted;
};
/**
* Shows video muted indicator over small videos.
*/
SmallVideo.prototype.showVideoIndicator = function(isMuted) {
this.showAvatar(isMuted);
var videoMutedSpan = $('#' + this.videoSpanId + '>span.videoMuted');
if (isMuted === false) {
if (videoMutedSpan.length > 0) {
videoMutedSpan.remove();
}
}
else {
if(videoMutedSpan.length == 0) {
videoMutedSpan = document.createElement('span');
videoMutedSpan.className = 'videoMuted';
this.container.appendChild(videoMutedSpan);
var mutedIndicator = document.createElement('i');
mutedIndicator.className = 'icon-camera-disabled';
UIUtil.setTooltip(mutedIndicator,
"videothumbnail.videomute",
"top");
videoMutedSpan.appendChild(mutedIndicator);
//translate texts for muted indicator
APP.translation.translateElement($('#' + this.videoSpanId + " > span > i"));
}
this.updateIconPositions();
}
};
SmallVideo.prototype.enableDominantSpeaker = function (isEnable) {
var resourceJid = this.getResourceJid();
var displayName = resourceJid;
var nameSpan = $('#' + this.videoSpanId + '>span.displayname');
if (nameSpan.length > 0)
displayName = nameSpan.html();
console.log("UI enable dominant speaker",
displayName,
resourceJid,
isEnable);
if (!this.container) {
return;
}
if (isEnable) {
this.showDisplayName(LargeVideo.getState() === "video");
if (!this.container.classList.contains("dominantspeaker"))
this.container.classList.add("dominantspeaker");
}
else {
this.showDisplayName(false);
if (this.container.classList.contains("dominantspeaker"))
this.container.classList.remove("dominantspeaker");
}
this.showAvatar();
};
SmallVideo.prototype.updateIconPositions = function () {
var audioMutedSpan = $('#' + this.videoSpanId + '>span.audioMuted');
var connectionIndicator = $('#' + this.videoSpanId + '>div.connectionindicator');
var videoMutedSpan = $('#' + this.videoSpanId + '>span.videoMuted');
if(connectionIndicator.length > 0 &&
connectionIndicator[0].style.display != "none") {
audioMutedSpan.css({right: "23px"});
videoMutedSpan.css({right: ((audioMutedSpan.length > 0? 23 : 0) + 30) + "px"});
} else {
audioMutedSpan.css({right: "0px"});
videoMutedSpan.css({right: (audioMutedSpan.length > 0? 30 : 0) + "px"});
}
};
/**
* Creates the element indicating the moderator(owner) of the conference.
*
* @param parentElement the parent element where the owner indicator will
* be added
*/
SmallVideo.prototype.createModeratorIndicatorElement = function () {
// Show moderator indicator
var indicatorSpan = $('#' + this.videoSpanId + ' .focusindicator');
if (!indicatorSpan || indicatorSpan.length === 0) {
indicatorSpan = document.createElement('span');
indicatorSpan.className = 'focusindicator';
this.container.appendChild(indicatorSpan);
indicatorSpan = $('#' + this.videoSpanId + ' .focusindicator');
}
if (indicatorSpan.children().length !== 0)
return;
var moderatorIndicator = document.createElement('i');
moderatorIndicator.className = 'fa fa-star';
indicatorSpan[0].appendChild(moderatorIndicator);
UIUtil.setTooltip(indicatorSpan[0],
"videothumbnail.moderator",
"top");
//translates text in focus indicators
APP.translation.translateElement($('#' + this.videoSpanId + ' .focusindicator'));
};
SmallVideo.prototype.selectVideoElement = function () {
var videoElem = APP.RTC.getVideoElementName();
if (!RTCBrowserType.isTemasysPluginUsed()) {
return $('#' + this.videoSpanId).find(videoElem);
} else {
return $('#' + this.videoSpanId +
(this.isLocal ? '>>' : '>') +
videoElem + '>param[value="video"]').parent();
}
};
SmallVideo.prototype.getSrc = function () {
var videoElement = this.selectVideoElement().get(0);
return APP.RTC.getVideoSrc(videoElement);
};
SmallVideo.prototype.focus = function(isFocused) {
if(!isFocused) {
this.container.classList.remove("videoContainerFocused");
} else {
this.container.classList.add("videoContainerFocused");
}
};
SmallVideo.prototype.hasVideo = function () {
return this.selectVideoElement().length !== 0;
};
/**
* Hides or shows the user's avatar
* @param show whether we should show the avatar or not
* video because there is no dominant speaker and no focused speaker
*/
SmallVideo.prototype.showAvatar = function (show) {
if (!this.hasAvatar) {
if (this.peerJid) {
// Init avatar
this.avatarChanged(Avatar.getThumbUrl(this.peerJid));
} else {
console.error("Unable to init avatar - no peerjid", this);
return;
}
}
var resourceJid = this.getResourceJid();
var video = this.selectVideoElement();
var avatar = $('#avatar_' + resourceJid);
if (show === undefined || show === null) {
if (!this.isLocal &&
!this.VideoLayout.isInLastN(resourceJid)) {
show = true;
} else {
// We want to show the avatar when the video is muted or not exists
// that is when 'true' or 'null' is returned
show = APP.RTC.isVideoMuted(this.peerJid) !== false;
}
}
if (LargeVideo.showAvatar(resourceJid, show)) {
setVisibility(avatar, false);
setVisibility(video, false);
} else {
if (video && video.length > 0) {
setVisibility(video, !show);
}
setVisibility(avatar, show);
}
};
SmallVideo.prototype.avatarChanged = function (thumbUrl) {
var thumbnail = $('#' + this.videoSpanId);
var resourceJid = this.getResourceJid();
var avatar = $('#avatar_' + resourceJid);
this.hasAvatar = true;
// set the avatar in the thumbnail
if (avatar && avatar.length > 0) {
avatar[0].src = thumbUrl;
} else {
if (thumbnail && thumbnail.length > 0) {
avatar = document.createElement('img');
avatar.id = 'avatar_' + resourceJid;
avatar.className = 'userAvatar';
avatar.src = thumbUrl;
thumbnail.append(avatar);
}
}
};
module.exports = SmallVideo;

File diff suppressed because it is too large Load Diff

View File

@@ -49,7 +49,8 @@ var adverbs = [
"Obviously", "Often", "Painfully", "Patiently", "Playfully", "Politely", "Poorly", "Precisely", "Promptly",
"Quickly", "Quietly", "Randomly", "Rapidly", "Rarely", "Recklessly", "Regularly", "Remorsefully", "Responsibly",
"Rudely", "Ruthlessly", "Sadly", "Scornfully", "Seamlessly", "Seldom", "Selfishly", "Seriously", "Shakily",
"Sharply", "Sideways", "Silently", "Sleepily", "Slightly", "Slowly", "Slyly", "Smoothly", "Softly", "Solemnly", "Steadily", "Sternly", "Strangely", "Strongly", "Stunningly", "Surely", "Tenderly", "Thoughtfully",
"Sharply", "Sideways", "Silently", "Sleepily", "Slightly", "Slowly", "Slyly", "Smoothly", "Softly", "Solemnly",
"Steadily", "Sternly", "Strangely", "Strongly", "Stunningly", "Surely", "Tenderly", "Thoughtfully",
"Tightly", "Uneasily", "Vanishingly", "Violently", "Warmly", "Weakly", "Wearily", "Weekly", "Weirdly", "Well",
"Well", "Wickedly", "Wildly", "Wisely", "Wonderfully", "Yearly"
];
@@ -137,8 +138,7 @@ var PATTERNS = [
/*
* Returns a random element from the array 'arr'
*/
function randomElement(arr)
{
function randomElement(arr) {
return arr[Math.floor(Math.random() * arr.length)];
}
@@ -146,8 +146,7 @@ function randomElement(arr)
* Returns true if the string 's' contains one of the
* template strings.
*/
function hasTemplate(s)
{
function hasTemplate(s) {
for (var template in CATEGORIES){
if (s.indexOf(template) >= 0){
return true;
@@ -159,14 +158,15 @@ function hasTemplate(s)
* Generates new room name.
*/
var RoomNameGenerator = {
generateRoomWithoutSeparator: function()
{
// Note that if more than one pattern is available, the choice of 'name' won't be random (names from patterns
// with fewer options will have higher probability of being chosen that names from patterns with more options).
generateRoomWithoutSeparator: function() {
// Note that if more than one pattern is available, the choice of
// 'name' won't have a uniform distribution amongst all patterns (names
// from patterns with fewer options will have higher probability of
// being chosen that names from patterns with more options).
var name = randomElement(PATTERNS);
var word;
while (hasTemplate(name)){
for (var template in CATEGORIES){
while (hasTemplate(name)) {
for (var template in CATEGORIES) {
word = randomElement(CATEGORIES[template]);
name = name.replace(template, word);
}
@@ -174,6 +174,6 @@ var RoomNameGenerator = {
return name;
}
}
};
module.exports = RoomNameGenerator;

View File

@@ -1,9 +1,9 @@
/* global $, interfaceConfig */
var animateTimeout, updateTimeout;
var RoomNameGenerator = require("./RoomnameGenerator");
function enter_room()
{
function enter_room() {
var val = $("#enter_room_field").val();
if(!val) {
val = $("#enter_room_field").attr("room_name");
@@ -21,8 +21,7 @@ function animate(word) {
}, 70);
}
function update_roomname()
{
function update_roomname() {
var word = RoomNameGenerator.generateRoomWithoutSeparator();
$("#enter_room_field").attr("room_name", word);
$("#enter_room_field").attr("placeholder", "");
@@ -31,33 +30,30 @@ function update_roomname()
updateTimeout = setTimeout(update_roomname, 10000);
}
function setupWelcomePage()
{
function setupWelcomePage() {
$("#videoconference_page").hide();
$("#domain_name").text(
window.location.protocol + "//" + window.location.host + "/");
if (interfaceConfig.SHOW_JITSI_WATERMARK) {
var leftWatermarkDiv
= $("#welcome_page_header div[class='watermark leftwatermark']");
if(leftWatermarkDiv && leftWatermarkDiv.length > 0)
{
var leftWatermarkDiv =
$("#welcome_page_header div[class='watermark leftwatermark']");
if(leftWatermarkDiv && leftWatermarkDiv.length > 0) {
leftWatermarkDiv.css({display: 'block'});
leftWatermarkDiv.parent().get(0).href
= interfaceConfig.JITSI_WATERMARK_LINK;
leftWatermarkDiv.parent().get(0).href =
interfaceConfig.JITSI_WATERMARK_LINK;
}
}
if (interfaceConfig.SHOW_BRAND_WATERMARK) {
var rightWatermarkDiv
= $("#welcome_page_header div[class='watermark rightwatermark']");
var rightWatermarkDiv =
$("#welcome_page_header div[class='watermark rightwatermark']");
if(rightWatermarkDiv && rightWatermarkDiv.length > 0) {
rightWatermarkDiv.css({display: 'block'});
rightWatermarkDiv.parent().get(0).href
= interfaceConfig.BRAND_WATERMARK_LINK;
rightWatermarkDiv.get(0).style.backgroundImage
= "url(images/rightwatermark.png)";
rightWatermarkDiv.parent().get(0).href =
interfaceConfig.BRAND_WATERMARK_LINK;
rightWatermarkDiv.get(0).style.backgroundImage =
"url(images/rightwatermark.png)";
}
}
@@ -66,8 +62,7 @@ function setupWelcomePage()
.css({display: 'block'});
}
$("#enter_room_button").click(function()
{
$("#enter_room_button").click(function() {
enter_room();
});
@@ -77,23 +72,23 @@ function setupWelcomePage()
}
});
if (!(interfaceConfig.GENERATE_ROOMNAMES_ON_WELCOME_PAGE === false)){
if (interfaceConfig.GENERATE_ROOMNAMES_ON_WELCOME_PAGE !== false) {
var updateTimeout;
var animateTimeout;
$("#reload_roomname").click(function () {
var selector = $("#reload_roomname");
selector.click(function () {
clearTimeout(updateTimeout);
clearTimeout(animateTimeout);
update_roomname();
});
$("#reload_roomname").show();
selector.show();
update_roomname();
}
$("#disable_welcome").click(function () {
window.localStorage.welcomePageDisabled
= $("#disable_welcome").is(":checked");
window.localStorage.welcomePageDisabled =
$("#disable_welcome").is(":checked");
});
}

View File

@@ -1,40 +0,0 @@
var params = {};
function getConfigParamsFromUrl() {
if(!location.hash)
return {};
var hash = location.hash.substr(1);
var result = {};
hash.split("&").forEach(function(part) {
var item = part.split("=");
result[item[0]] = JSON.parse(
decodeURIComponent(item[1]).replace(/\\&/, "&"));
});
return result;
}
params = getConfigParamsFromUrl();
var URLProcessor = {
setConfigParametersFromUrl: function () {
for(var k in params)
{
if(typeof k !== "string" || k.indexOf("config.") === -1)
continue;
var v = params[k];
var confKey = k.substr(7);
if(config[confKey] && typeof config[confKey] !== typeof v)
{
console.warn("The type of " + k +
" is wrong. That parameter won't be updated in config.js.");
continue;
}
config[confKey] = v;
}
}
};
module.exports = URLProcessor;

View File

@@ -0,0 +1,55 @@
/* global $, $iq, config, interfaceConfig */
var configUtil = require('./Util');
var HttpConfig = {
/**
* Sends HTTP POST request to specified <tt>endpoint</tt>. In request
* the name of the room is included in JSON format:
* {
* "rooomName": "someroom12345"
* }
* @param endpoint the name of HTTP endpoint to which HTTP POST request will
* be sent.
* @param roomName the name of the conference room for which config will be
* requested.
* @param complete
*/
obtainConfig: function (endpoint, roomName, complete) {
console.info(
"Send config request to " + endpoint + " for room: " + roomName);
var request = new XMLHttpRequest();
var error = null;
request.onreadystatechange = function (aEvt) {
if (request.readyState == 4) {
var status = request.status;
if (status === 200) {
try {
var data = JSON.parse(request.responseText);
configUtil.overrideConfigJSON(
config, interfaceConfig, data);
complete(true);
return;
} catch (exception) {
console.error("Parse config error: ", exception);
error = exception;
}
} else {
console.error("Get config error: ", request, status);
error = "Get config response status: " + status;
}
complete(false, error);
}
};
request.open("POST", endpoint, true);
request.setRequestHeader(
"Content-Type", "application/json;charset=UTF-8");
request.send({ "roomName": roomName });
}
};
module.exports = HttpConfig;

View File

@@ -0,0 +1,65 @@
/* global $, $iq, config, interfaceConfig */
var configUtils = require('./Util');
var params = {};
function getConfigParamsFromUrl() {
if (!location.hash)
return {};
var hash = location.hash.substr(1);
var result = {};
hash.split("&").forEach(function (part) {
var item = part.split("=");
result[item[0]] = JSON.parse(
decodeURIComponent(item[1]).replace(/\\&/, "&"));
});
return result;
}
params = getConfigParamsFromUrl();
var URLProcessor = {
setConfigParametersFromUrl: function () {
// Convert 'params' to JSON object
// We have:
// {
// "config.disableAudioLevels": false,
// "config.channelLastN": -1,
// "interfaceConfig.APP_NAME": "Jitsi Meet"
// }
// We want to have:
// {
// "config": {
// "disableAudioLevels": false,
// "channelLastN": -1
// },
// interfaceConfig: {
// APP_NAME: "Jitsi Meet"
// }
// }
var configJSON = {
config: {},
interfaceConfig: {}
};
for (var key in params) {
if (typeof key !== "string") {
console.warn("Invalid config key: ", key);
continue;
}
var confObj = null, confKey;
if (key.indexOf("config.") === 0) {
confObj = configJSON.config;
confKey = key.substr("config.".length);
} else if (key.indexOf("interfaceConfig.") === 0) {
confObj = configJSON.interfaceConfig;
confKey = key.substr("interfaceConfig.".length);
}
if (!confObj)
continue;
confObj[confKey] = params[key];
}
configUtils.overrideConfigJSON(config, interfaceConfig, configJSON);
}
};
module.exports = URLProcessor;

49
modules/config/Util.js Normal file
View File

@@ -0,0 +1,49 @@
/* global $ */
var ConfigUtil = {
/**
* Method overrides JSON properties in <tt>config</tt> and
* <tt>interfaceConfig</tt> Objects with the values from <tt>newConfig</tt>
* @param config the config object for which we'll be overriding properties
* @param interfaceConfig the interfaceConfig object for which we'll be
* overriding properties.
* @param newConfig object containing configuration properties. Destination
* object is selected based on root property name:
* {
* config: {
* // config.js properties to be
* },
* interfaceConfig: {
* // interfaceConfig.js properties here
* }
* }
*/
overrideConfigJSON: function (config, interfaceConfig, newConfig) {
for (var configRoot in newConfig) {
var confObj = null;
if (configRoot == "config") {
confObj = config;
} else if (configRoot == "interfaceConfig") {
confObj = interfaceConfig;
} else {
continue;
}
for (var key in newConfig[configRoot]) {
var value = newConfig[configRoot][key];
if (confObj[key] && typeof confObj[key] !== typeof value)
{
console.warn(
"The type of " + key +
" is wrong. That parameter won't be updated in: ",
confObj);
continue;
}
console.info("Overriding " + key + " with: " + value);
confObj[key] = value;
}
}
}
};
module.exports = ConfigUtil;

View File

@@ -1,3 +1,4 @@
/* global APP, require */
var EventEmitter = require("events");
var eventEmitter = new EventEmitter();
var CQEvents = require("../../service/connectionquality/CQEvents");
@@ -53,7 +54,7 @@ function convertToMUCStats(stats) {
}
/**
* Converts statitistics to format used by VideoLayout
* Converts statistics to format used by VideoLayout
* @param stats
* @returns {{bitrate: {download: *, upload: *}, packetLoss: {total: *, download: *, upload: *}}}
*/
@@ -71,7 +72,6 @@ function parseMUCStats(stats) {
};
}
var ConnectionQuality = {
init: function () {
APP.xmpp.addListener(XMPPEvents.REMOTE_STATS, this.updateRemoteStats);

View File

@@ -31,13 +31,7 @@ var extInstalled = false;
*/
var extUpdateRequired = false;
/**
* Flag used to cache desktop sharing enabled state. Do not use directly as
* it can be <tt>null</tt>.
*
* @type {null|boolean}
*/
var _desktopSharingEnabled = null;
var AdapterJS = require("../RTC/adapter.screenshare");
var EventEmitter = require("events");
@@ -46,6 +40,10 @@ var eventEmitter = new EventEmitter();
var DesktopSharingEventTypes
= require("../../service/desktopsharing/DesktopSharingEventTypes");
var RTCBrowserType = require("../RTC/RTCBrowserType");
var RTCEvents = require("../../service/RTC/RTCEvents");
/**
* Method obtains desktop stream from WebRTC 'screen' source.
* Flag 'chrome://flags/#enable-usermedia-screen-capture' must be enabled.
@@ -116,7 +114,7 @@ function isUpdateRequired(minVersion, extVersion)
}
}
function checkExtInstalled(callback) {
function checkChromeExtInstalled(callback) {
if (!chrome.runtime) {
// No API, so no extension for sure
callback(false, false);
@@ -188,9 +186,11 @@ function obtainScreenFromExtension(streamCallback, failCallback) {
getWebStoreInstallUrl(),
function (arg) {
console.log("Extension installed successfully", arg);
// We need to reload the page in order to get the access to
// chrome.runtime
window.location.reload(false);
extInstalled = true;
// We need to give a moment for the endpoint to become available
window.setTimeout(function () {
doGetStreamFromExtension(streamCallback, failCallback);
}, 500);
},
function (arg) {
console.log("Failed to install the extension", arg);
@@ -211,20 +211,39 @@ function obtainScreenFromExtension(streamCallback, failCallback) {
* feature completely.
*/
function setDesktopSharing(method) {
// Check if we are running chrome
if (!navigator.webkitGetUserMedia) {
obtainDesktopStream = null;
console.info("Desktop sharing disabled");
} else if (method == "ext") {
obtainDesktopStream = obtainScreenFromExtension;
console.info("Using Chrome extension for desktop sharing");
} else if (method == "webrtc") {
obtainDesktopStream = obtainWebRTCScreen;
console.info("Using Chrome WebRTC for desktop sharing");
obtainDesktopStream = null;
// When TemasysWebRTC plugin is used we always use getUserMedia, so we don't
// care about 'method' parameter
if (RTCBrowserType.isTemasysPluginUsed()) {
if (!AdapterJS.WebRTCPlugin.plugin.HasScreensharingFeature) {
console.info("Screensharing not supported by this plugin version");
} else if (!AdapterJS.WebRTCPlugin.plugin.isScreensharingAvailable) {
console.info(
"Screensharing not available with Temasys plugin on this site");
} else {
obtainDesktopStream = obtainWebRTCScreen;
console.info("Using Temasys plugin for desktop sharing");
}
} else if (RTCBrowserType.isChrome()) {
if (method == "ext") {
if (RTCBrowserType.getChromeVersion() >= 34) {
obtainDesktopStream = obtainScreenFromExtension;
console.info("Using Chrome extension for desktop sharing");
initChromeExtension();
} else {
console.info("Chrome extension not supported until ver 34");
}
} else if (method == "webrtc") {
obtainDesktopStream = obtainWebRTCScreen;
console.info("Using Chrome WebRTC for desktop sharing");
}
}
// Reset enabled cache
_desktopSharingEnabled = null;
if (!obtainDesktopStream) {
console.info("Desktop sharing disabled");
}
}
/**
@@ -237,6 +256,19 @@ function initInlineInstalls()
$("link[rel=chrome-webstore-item]").attr("href", getWebStoreInstallUrl());
}
function initChromeExtension() {
// Initialize Chrome extension inline installs
initInlineInstalls();
// Check if extension is installed
checkChromeExtInstalled(function (installed, updateRequired) {
extInstalled = installed;
extUpdateRequired = updateRequired;
console.info(
"Chrome extension installed: " + extInstalled +
" updateRequired: " + extUpdateRequired);
});
}
function getVideoStreamFailed(error) {
console.error("Failed to obtain the stream to switch to", error);
switchInProgress = false;
@@ -262,6 +294,25 @@ function newStreamCreated(stream)
stream, isUsingScreenStream, streamSwitchDone);
}
function onEndedHandler(stream) {
if (!switchInProgress && isUsingScreenStream) {
APP.desktopsharing.toggleScreenSharing();
}
//FIXME: to be verified
if (stream.removeEventListener) {
stream.removeEventListener('ended', onEndedHandler);
} else {
stream.detachEvent('ended', onEndedHandler);
}
}
// Called when RTC finishes initialization
function onWebRtcReady() {
setDesktopSharing(config.desktopSharing);
eventEmitter.emit(DesktopSharingEventTypes.INIT);
}
module.exports = {
isUsingScreenStream: function () {
@@ -272,43 +323,10 @@ module.exports = {
* @returns {boolean} <tt>true</tt> if desktop sharing feature is available
* and enabled.
*/
isDesktopSharingEnabled: function () {
if (_desktopSharingEnabled === null) {
if (obtainDesktopStream === obtainScreenFromExtension) {
// Parse chrome version
var userAgent = navigator.userAgent.toLowerCase();
// We can assume that user agent is chrome, because it's
// enforced when 'ext' streaming method is set
var ver = parseInt(userAgent.match(/chrome\/(\d+)\./)[1], 10);
console.log("Chrome version" + userAgent, ver);
_desktopSharingEnabled = ver >= 34;
} else {
_desktopSharingEnabled =
obtainDesktopStream === obtainWebRTCScreen;
}
}
return _desktopSharingEnabled;
},
isDesktopSharingEnabled: function () { return !!obtainDesktopStream; },
init: function () {
setDesktopSharing(config.desktopSharing);
// Initialize Chrome extension inline installs
if (config.chromeExtensionId) {
initInlineInstalls();
// Check if extension is installed
checkExtInstalled(function (installed, updateRequired) {
extInstalled = installed;
extUpdateRequired = updateRequired;
console.info(
"Chrome extension installed: " + extInstalled +
" updateRequired: " + extUpdateRequired);
});
}
eventEmitter.emit(DesktopSharingEventTypes.INIT);
APP.RTC.addListener(RTCEvents.RTC_READY, onWebRtcReady);
},
addListener: function (listener, type)
@@ -339,13 +357,16 @@ module.exports = {
isUsingScreenStream = true;
// Hook 'ended' event to restore camera
// when screen stream stops
stream.addEventListener('ended',
function (e) {
if (!switchInProgress && isUsingScreenStream) {
APP.desktopsharing.toggleScreenSharing();
}
}
);
//FIXME: to be verified
if (stream.addEventListener) {
stream.addEventListener('ended', function () {
onEndedHandler(stream);
});
} else {
stream.attachEvent('ended', function () {
onEndedHandler(stream);
});
}
newStreamCreated(stream);
},
getDesktopStreamFailed);

View File

@@ -1,38 +1,43 @@
/* global APP, $ */
//maps keycode to character, id of popover for given function and function
var shortcuts = {
67: {
character: "C",
id: "toggleChatPopover",
function: APP.UI.toggleChat
},
70: {
character: "F",
id: "filmstripPopover",
function: APP.UI.toggleFilmStrip
},
77: {
character: "M",
id: "mutePopover",
function: APP.UI.toggleAudio
},
84: {
character: "T",
function: function() {
if(!APP.RTC.localAudio.isMuted()) {
APP.UI.toggleAudio();
var shortcuts = {};
function initShortcutHandlers() {
shortcuts = {
67: {
character: "C",
id: "toggleChatPopover",
function: APP.UI.toggleChat
},
70: {
character: "F",
id: "filmstripPopover",
function: APP.UI.toggleFilmStrip
},
77: {
character: "M",
id: "mutePopover",
function: APP.UI.toggleAudio
},
84: {
character: "T",
function: function() {
if(!APP.RTC.localAudio.isMuted()) {
APP.UI.toggleAudio();
}
}
},
86: {
character: "V",
id: "toggleVideoPopover",
function: APP.UI.toggleVideo
}
},
86: {
character: "V",
id: "toggleVideoPopover",
function: APP.UI.toggleVideo
}
};
};
}
var KeyboardShortcut = {
init: function () {
initShortcutHandlers();
window.onkeyup = function(e) {
var keycode = e.which;
if(!($(":focus").is("input[type=text]") ||

View File

@@ -1,4 +1,4 @@
/* global APP */
/* global APP, require, $ */
/**
* This module is meant to (eventually) contain and manage all information
@@ -87,7 +87,7 @@ function updateDtmf(jid, newValue) {
* Checks each member's 'supportsDtmf' field and updates
* 'atLastOneSupportsDtmf'.
*/
function updateAtLeastOneDtmf(){
function updateAtLeastOneDtmf() {
var newAtLeastOneDtmf = false;
for (var key in members) {
if (typeof members[key].supportsDtmf !== 'undefined'
@@ -108,11 +108,10 @@ function updateAtLeastOneDtmf(){
* Exported interface.
*/
var Members = {
start: function(){
start: function() {
registerListeners();
},
addListener: function(type, listener)
{
addListener: function(type, listener) {
eventEmitter.on(type, listener);
},
removeListener: function (type, listener) {

View File

@@ -35,15 +35,13 @@ if (supportsLocalStorage()) {
userId = generateUniqueId();
}
var Settings =
{
var Settings = {
setDisplayName: function (newDisplayName) {
displayName = newDisplayName;
window.localStorage.displayname = displayName;
return displayName;
},
setEmail: function (newEmail)
{
setEmail: function (newEmail) {
email = newEmail;
window.localStorage.email = newEmail;
return email;

View File

@@ -1,32 +0,0 @@
/**
*
* @constructor
*/
function SimulcastLogger(name, lvl) {
this.name = name;
this.lvl = lvl;
}
SimulcastLogger.prototype.log = function (text) {
if (this.lvl) {
console.log(text);
}
};
SimulcastLogger.prototype.info = function (text) {
if (this.lvl > 1) {
console.info(text);
}
};
SimulcastLogger.prototype.fine = function (text) {
if (this.lvl > 2) {
console.log(text);
}
};
SimulcastLogger.prototype.error = function (text) {
console.error(text);
};
module.exports = SimulcastLogger;

View File

@@ -1,268 +0,0 @@
var SimulcastLogger = require("./SimulcastLogger");
var SimulcastUtils = require("./SimulcastUtils");
var MediaStreamType = require("../../service/RTC/MediaStreamTypes");
function SimulcastReceiver() {
this.simulcastUtils = new SimulcastUtils();
this.logger = new SimulcastLogger('SimulcastReceiver', 1);
}
SimulcastReceiver.prototype._remoteVideoSourceCache = '';
SimulcastReceiver.prototype._remoteMaps = {
msid2Quality: {},
ssrc2Msid: {},
msid2ssrc: {},
receivingVideoStreams: {}
};
SimulcastReceiver.prototype._cacheRemoteVideoSources = function (lines) {
this._remoteVideoSourceCache = this.simulcastUtils._getVideoSources(lines);
};
SimulcastReceiver.prototype._restoreRemoteVideoSources = function (lines) {
this.simulcastUtils._replaceVideoSources(lines, this._remoteVideoSourceCache);
};
SimulcastReceiver.prototype._ensureGoogConference = function (lines) {
var sb;
this.logger.info('Ensuring x-google-conference flag...')
if (this.simulcastUtils._indexOfArray('a=x-google-flag:conference', lines) === this.simulcastUtils._emptyCompoundIndex) {
// TODO(gp) do that for the audio as well as suggested by fippo.
// Add the google conference flag
sb = this.simulcastUtils._getVideoSources(lines);
sb = ['a=x-google-flag:conference'].concat(sb);
this.simulcastUtils._replaceVideoSources(lines, sb);
}
};
SimulcastReceiver.prototype._restoreSimulcastGroups = function (sb) {
this._restoreRemoteVideoSources(sb);
};
/**
* Restores the simulcast groups of the remote description. In
* transformRemoteDescription we remove those in order for the set remote
* description to succeed. The focus needs the signal the groups to new
* participants.
*
* @param desc
* @returns {*}
*/
SimulcastReceiver.prototype.reverseTransformRemoteDescription = function (desc) {
var sb;
if (!this.simulcastUtils.isValidDescription(desc)) {
return desc;
}
if (config.enableSimulcast) {
sb = desc.sdp.split('\r\n');
this._restoreSimulcastGroups(sb);
desc = new RTCSessionDescription({
type: desc.type,
sdp: sb.join('\r\n')
});
}
return desc;
};
SimulcastUtils.prototype._ensureOrder = function (lines) {
var videoSources, sb;
videoSources = this.parseMedia(lines, ['video'])[0];
sb = this._compileVideoSources(videoSources);
this._replaceVideoSources(lines, sb);
};
SimulcastReceiver.prototype._updateRemoteMaps = function (lines) {
var remoteVideoSources = this.simulcastUtils.parseMedia(lines, ['video'])[0],
videoSource, quality;
// (re) initialize the remote maps.
this._remoteMaps.msid2Quality = {};
this._remoteMaps.ssrc2Msid = {};
this._remoteMaps.msid2ssrc = {};
var self = this;
if (remoteVideoSources.groups && remoteVideoSources.groups.length !== 0) {
remoteVideoSources.groups.forEach(function (group) {
if (group.semantics === 'SIM' && group.ssrcs && group.ssrcs.length !== 0) {
quality = 0;
group.ssrcs.forEach(function (ssrc) {
videoSource = remoteVideoSources.sources[ssrc];
self._remoteMaps.msid2Quality[videoSource.msid] = quality++;
self._remoteMaps.ssrc2Msid[videoSource.ssrc] = videoSource.msid;
self._remoteMaps.msid2ssrc[videoSource.msid] = videoSource.ssrc;
});
}
});
}
};
SimulcastReceiver.prototype._setReceivingVideoStream = function (resource, ssrc) {
this._remoteMaps.receivingVideoStreams[resource] = ssrc;
};
/**
* Returns a stream with single video track, the one currently being
* received by this endpoint.
*
* @param stream the remote simulcast stream.
* @returns {webkitMediaStream}
*/
SimulcastReceiver.prototype.getReceivingVideoStream = function (stream) {
var tracks, i, electedTrack, msid, quality = 0, receivingTrackId;
var self = this;
if (config.enableSimulcast) {
stream.getVideoTracks().some(function (track) {
return Object.keys(self._remoteMaps.receivingVideoStreams).some(function (resource) {
var ssrc = self._remoteMaps.receivingVideoStreams[resource];
var msid = self._remoteMaps.ssrc2Msid[ssrc];
if (msid == [stream.id, track.id].join(' ')) {
electedTrack = track;
return true;
}
});
});
if (!electedTrack) {
// we don't have an elected track, choose by initial quality.
tracks = stream.getVideoTracks();
for (i = 0; i < tracks.length; i++) {
msid = [stream.id, tracks[i].id].join(' ');
if (this._remoteMaps.msid2Quality[msid] === quality) {
electedTrack = tracks[i];
break;
}
}
// TODO(gp) if the initialQuality could not be satisfied, lower
// the requirement and try again.
}
}
return (electedTrack)
? new webkitMediaStream([electedTrack])
: stream;
};
SimulcastReceiver.prototype.getReceivingSSRC = function (jid) {
var resource = Strophe.getResourceFromJid(jid);
var ssrc = this._remoteMaps.receivingVideoStreams[resource];
// If we haven't receiving a "changed" event yet, then we must be receiving
// low quality (that the sender always streams).
if(!ssrc)
{
var remoteStreamObject = APP.RTC.remoteStreams[jid][MediaStreamType.VIDEO_TYPE];
var remoteStream = remoteStreamObject.getOriginalStream();
var tracks = remoteStream.getVideoTracks();
if (tracks) {
for (var k = 0; k < tracks.length; k++) {
var track = tracks[k];
var msid = [remoteStream.id, track.id].join(' ');
var _ssrc = this._remoteMaps.msid2ssrc[msid];
var quality = this._remoteMaps.msid2Quality[msid];
if (quality == 0) {
ssrc = _ssrc;
}
}
}
}
return ssrc;
};
SimulcastReceiver.prototype.getReceivingVideoStreamBySSRC = function (ssrc)
{
var sid, electedStream;
var i, j, k;
var jid = APP.xmpp.getJidFromSSRC(ssrc);
if(jid && APP.RTC.remoteStreams[jid])
{
var remoteStreamObject = APP.RTC.remoteStreams[jid][MediaStreamType.VIDEO_TYPE];
var remoteStream = remoteStreamObject.getOriginalStream();
var tracks = remoteStream.getVideoTracks();
if (tracks) {
for (k = 0; k < tracks.length; k++) {
var track = tracks[k];
var msid = [remoteStream.id, track.id].join(' ');
var tmp = this._remoteMaps.msid2ssrc[msid];
if (tmp == ssrc) {
electedStream = new webkitMediaStream([track]);
sid = remoteStreamObject.sid;
// stream found, stop.
break;
}
}
}
}
else
{
console.debug(APP.RTC.remoteStreams, jid, ssrc);
}
return {
sid: sid,
stream: electedStream
};
};
/**
* Gets the fully qualified msid (stream.id + track.id) associated to the
* SSRC.
*
* @param ssrc
* @returns {*}
*/
SimulcastReceiver.prototype.getRemoteVideoStreamIdBySSRC = function (ssrc) {
return this._remoteMaps.ssrc2Msid[ssrc];
};
/**
* Removes the ssrc-group:SIM from the remote description bacause Chrome
* either gets confused and thinks this is an FID group or, if an FID group
* is already present, it fails to set the remote description.
*
* @param desc
* @returns {*}
*/
SimulcastReceiver.prototype.transformRemoteDescription = function (desc) {
if (desc && desc.sdp) {
var sb = desc.sdp.split('\r\n');
this._updateRemoteMaps(sb);
this._cacheRemoteVideoSources(sb);
// NOTE(gp) this needs to be called after updateRemoteMaps because we
// need the simulcast group in the _updateRemoteMaps() method.
this.simulcastUtils._removeSimulcastGroup(sb);
if (desc.sdp.indexOf('a=ssrc-group:SIM') !== -1) {
// We don't need the goog conference flag if we're not doing
// simulcast.
this._ensureGoogConference(sb);
}
desc = new RTCSessionDescription({
type: desc.type,
sdp: sb.join('\r\n')
});
this.logger.fine(['Transformed remote description', desc.sdp].join(' '));
}
return desc;
};
module.exports = SimulcastReceiver;

View File

@@ -1,521 +0,0 @@
var SimulcastLogger = require("./SimulcastLogger");
var SimulcastUtils = require("./SimulcastUtils");
function SimulcastSender() {
this.simulcastUtils = new SimulcastUtils();
this.logger = new SimulcastLogger('SimulcastSender', 1);
}
SimulcastSender.prototype.displayedLocalVideoStream = null;
SimulcastSender.prototype._generateGuid = (function () {
function s4() {
return Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1);
}
return function () {
return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
s4() + '-' + s4() + s4() + s4();
};
}());
// Returns a random integer between min (included) and max (excluded)
// Using Math.round() gives a non-uniform distribution!
SimulcastSender.prototype._generateRandomSSRC = function () {
var min = 0, max = 0xffffffff;
return Math.floor(Math.random() * (max - min)) + min;
};
SimulcastSender.prototype.getLocalVideoStream = function () {
return (this.displayedLocalVideoStream != null)
? this.displayedLocalVideoStream
// in case we have no simulcast at all, i.e. we didn't perform the GUM
: APP.RTC.localVideo.getOriginalStream();
};
function NativeSimulcastSender() {
SimulcastSender.call(this); // call the super constructor.
}
NativeSimulcastSender.prototype = Object.create(SimulcastSender.prototype);
NativeSimulcastSender.prototype._localExplosionMap = {};
NativeSimulcastSender.prototype._isUsingScreenStream = false;
NativeSimulcastSender.prototype._localVideoSourceCache = '';
NativeSimulcastSender.prototype.reset = function () {
this._localExplosionMap = {};
this._isUsingScreenStream = APP.desktopsharing.isUsingScreenStream();
};
NativeSimulcastSender.prototype._cacheLocalVideoSources = function (lines) {
this._localVideoSourceCache = this.simulcastUtils._getVideoSources(lines);
};
NativeSimulcastSender.prototype._restoreLocalVideoSources = function (lines) {
this.simulcastUtils._replaceVideoSources(lines, this._localVideoSourceCache);
};
NativeSimulcastSender.prototype._appendSimulcastGroup = function (lines) {
var videoSources, ssrcGroup, simSSRC, numOfSubs = 2, i, sb, msid;
this.logger.info('Appending simulcast group...');
// Get the primary SSRC information.
videoSources = this.simulcastUtils.parseMedia(lines, ['video'])[0];
// Start building the SIM SSRC group.
ssrcGroup = ['a=ssrc-group:SIM'];
// The video source buffer.
sb = [];
// Create the simulcast sub-streams.
for (i = 0; i < numOfSubs; i++) {
// TODO(gp) prevent SSRC collision.
simSSRC = this._generateRandomSSRC();
ssrcGroup.push(simSSRC);
if (videoSources.base) {
sb.splice.apply(sb, [sb.length, 0].concat(
[["a=ssrc:", simSSRC, " cname:", videoSources.base.cname].join(''),
["a=ssrc:", simSSRC, " msid:", videoSources.base.msid].join('')]
));
}
this.logger.info(['Generated substream ', i, ' with SSRC ', simSSRC, '.'].join(''));
}
// Add the group sim layers.
sb.splice(0, 0, ssrcGroup.join(' '))
this.simulcastUtils._replaceVideoSources(lines, sb);
};
// Does the actual patching.
NativeSimulcastSender.prototype._ensureSimulcastGroup = function (lines) {
this.logger.info('Ensuring simulcast group...');
if (this.simulcastUtils._indexOfArray('a=ssrc-group:SIM', lines) === this.simulcastUtils._emptyCompoundIndex) {
this._appendSimulcastGroup(lines);
this._cacheLocalVideoSources(lines);
} else {
// verify that the ssrcs participating in the SIM group are present
// in the SDP (needed for presence).
this._restoreLocalVideoSources(lines);
}
};
/**
* Produces a single stream with multiple tracks for local video sources.
*
* @param lines
* @private
*/
NativeSimulcastSender.prototype._explodeSimulcastSenderSources = function (lines) {
var sb, msid, sid, tid, videoSources, self;
this.logger.info('Exploding local video sources...');
videoSources = this.simulcastUtils.parseMedia(lines, ['video'])[0];
self = this;
if (videoSources.groups && videoSources.groups.length !== 0) {
videoSources.groups.forEach(function (group) {
if (group.semantics === 'SIM') {
group.ssrcs.forEach(function (ssrc) {
// Get the msid for this ssrc..
if (self._localExplosionMap[ssrc]) {
// .. either from the explosion map..
msid = self._localExplosionMap[ssrc];
} else {
// .. or generate a new one (msid).
sid = videoSources.sources[ssrc].msid
.substring(0, videoSources.sources[ssrc].msid.indexOf(' '));
tid = self._generateGuid();
msid = [sid, tid].join(' ');
self._localExplosionMap[ssrc] = msid;
}
// Assign it to the source object.
videoSources.sources[ssrc].msid = msid;
// TODO(gp) Change the msid of associated sources.
});
}
});
}
sb = this.simulcastUtils._compileVideoSources(videoSources);
this.simulcastUtils._replaceVideoSources(lines, sb);
};
/**
* GUM for simulcast.
*
* @param constraints
* @param success
* @param err
*/
NativeSimulcastSender.prototype.getUserMedia = function (constraints, success, err) {
// There's nothing special to do for native simulcast, so just do a normal GUM.
navigator.webkitGetUserMedia(constraints, function (hqStream) {
success(hqStream);
}, err);
};
/**
* Prepares the local description for public usage (i.e. to be signaled
* through Jingle to the focus).
*
* @param desc
* @returns {RTCSessionDescription}
*/
NativeSimulcastSender.prototype.reverseTransformLocalDescription = function (desc) {
var sb;
if (!this.simulcastUtils.isValidDescription(desc) || this._isUsingScreenStream) {
return desc;
}
sb = desc.sdp.split('\r\n');
this._explodeSimulcastSenderSources(sb);
desc = new RTCSessionDescription({
type: desc.type,
sdp: sb.join('\r\n')
});
this.logger.fine(['Exploded local video sources', desc.sdp].join(' '));
return desc;
};
/**
* Ensures that the simulcast group is present in the answer, _if_ native
* simulcast is enabled,
*
* @param desc
* @returns {*}
*/
NativeSimulcastSender.prototype.transformAnswer = function (desc) {
if (!this.simulcastUtils.isValidDescription(desc) || this._isUsingScreenStream) {
return desc;
}
var sb = desc.sdp.split('\r\n');
// Even if we have enabled native simulcasting previously
// (with a call to SLD with an appropriate SDP, for example),
// createAnswer seems to consistently generate incomplete SDP
// with missing SSRCS.
//
// So, subsequent calls to SLD will have missing SSRCS and presence
// won't have the complete list of SRCs.
this._ensureSimulcastGroup(sb);
desc = new RTCSessionDescription({
type: desc.type,
sdp: sb.join('\r\n')
});
this.logger.fine(['Transformed answer', desc.sdp].join(' '));
return desc;
};
/**
*
*
* @param desc
* @returns {*}
*/
NativeSimulcastSender.prototype.transformLocalDescription = function (desc) {
return desc;
};
NativeSimulcastSender.prototype._setLocalVideoStreamEnabled = function (ssrc, enabled) {
// Nothing to do here, native simulcast does that auto-magically.
};
NativeSimulcastSender.prototype.constructor = NativeSimulcastSender;
function SimpleSimulcastSender() {
SimulcastSender.call(this);
}
SimpleSimulcastSender.prototype = Object.create(SimulcastSender.prototype);
SimpleSimulcastSender.prototype.localStream = null;
SimpleSimulcastSender.prototype._localMaps = {
msids: [],
msid2ssrc: {}
};
/**
* Groups local video sources together in the ssrc-group:SIM group.
*
* @param lines
* @private
*/
SimpleSimulcastSender.prototype._groupLocalVideoSources = function (lines) {
var sb, videoSources, ssrcs = [], ssrc;
this.logger.info('Grouping local video sources...');
videoSources = this.simulcastUtils.parseMedia(lines, ['video'])[0];
for (ssrc in videoSources.sources) {
// jitsi-meet destroys/creates streams at various places causing
// the original local stream ids to change. The only thing that
// remains unchanged is the trackid.
this._localMaps.msid2ssrc[videoSources.sources[ssrc].msid.split(' ')[1]] = ssrc;
}
var self = this;
// TODO(gp) add only "free" sources.
this._localMaps.msids.forEach(function (msid) {
ssrcs.push(self._localMaps.msid2ssrc[msid]);
});
if (!videoSources.groups) {
videoSources.groups = [];
}
videoSources.groups.push({
'semantics': 'SIM',
'ssrcs': ssrcs
});
sb = this.simulcastUtils._compileVideoSources(videoSources);
this.simulcastUtils._replaceVideoSources(lines, sb);
};
/**
* GUM for simulcast.
*
* @param constraints
* @param success
* @param err
*/
SimpleSimulcastSender.prototype.getUserMedia = function (constraints, success, err) {
// TODO(gp) what if we request a resolution not supported by the hardware?
// TODO(gp) make the lq stream configurable; although this wouldn't work with native simulcast
var lqConstraints = {
audio: false,
video: {
mandatory: {
maxWidth: 320,
maxHeight: 180,
maxFrameRate: 15
}
}
};
this.logger.info('HQ constraints: ', constraints);
this.logger.info('LQ constraints: ', lqConstraints);
// NOTE(gp) if we request the lq stream first webkitGetUserMedia
// fails randomly. Tested with Chrome 37. As fippo suggested, the
// reason appears to be that Chrome only acquires the cam once and
// then downscales the picture (https://code.google.com/p/chromium/issues/detail?id=346616#c11)
var self = this;
navigator.webkitGetUserMedia(constraints, function (hqStream) {
self.localStream = hqStream;
// reset local maps.
self._localMaps.msids = [];
self._localMaps.msid2ssrc = {};
// add hq trackid to local map
self._localMaps.msids.push(hqStream.getVideoTracks()[0].id);
navigator.webkitGetUserMedia(lqConstraints, function (lqStream) {
self.displayedLocalVideoStream = lqStream;
// NOTE(gp) The specification says Array.forEach() will visit
// the array elements in numeric order, and that it doesn't
// visit elements that don't exist.
// add lq trackid to local map
self._localMaps.msids.splice(0, 0, lqStream.getVideoTracks()[0].id);
self.localStream.addTrack(lqStream.getVideoTracks()[0]);
success(self.localStream);
}, err);
}, err);
};
/**
* Prepares the local description for public usage (i.e. to be signaled
* through Jingle to the focus).
*
* @param desc
* @returns {RTCSessionDescription}
*/
SimpleSimulcastSender.prototype.reverseTransformLocalDescription = function (desc) {
var sb;
if (!this.simulcastUtils.isValidDescription(desc)) {
return desc;
}
sb = desc.sdp.split('\r\n');
this._groupLocalVideoSources(sb);
desc = new RTCSessionDescription({
type: desc.type,
sdp: sb.join('\r\n')
});
this.logger.fine('Grouped local video sources');
this.logger.fine(desc.sdp);
return desc;
};
/**
* Ensures that the simulcast group is present in the answer, _if_ native
* simulcast is enabled,
*
* @param desc
* @returns {*}
*/
SimpleSimulcastSender.prototype.transformAnswer = function (desc) {
return desc;
};
/**
*
*
* @param desc
* @returns {*}
*/
SimpleSimulcastSender.prototype.transformLocalDescription = function (desc) {
var sb = desc.sdp.split('\r\n');
this.simulcastUtils._removeSimulcastGroup(sb);
desc = new RTCSessionDescription({
type: desc.type,
sdp: sb.join('\r\n')
});
this.logger.fine('Transformed local description');
this.logger.fine(desc.sdp);
return desc;
};
SimpleSimulcastSender.prototype._setLocalVideoStreamEnabled = function (ssrc, enabled) {
var trackid;
var self = this;
this.logger.log(['Requested to', enabled ? 'enable' : 'disable', ssrc].join(' '));
if (Object.keys(this._localMaps.msid2ssrc).some(function (tid) {
// Search for the track id that corresponds to the ssrc
if (self._localMaps.msid2ssrc[tid] == ssrc) {
trackid = tid;
return true;
}
}) && self.localStream.getVideoTracks().some(function (track) {
// Start/stop the track that corresponds to the track id
if (track.id === trackid) {
track.enabled = enabled;
return true;
}
})) {
this.logger.log([trackid, enabled ? 'enabled' : 'disabled'].join(' '));
$(document).trigger(enabled
? 'simulcastlayerstarted'
: 'simulcastlayerstopped');
} else {
this.logger.error("I don't have a local stream with SSRC " + ssrc);
}
};
SimpleSimulcastSender.prototype.constructor = SimpleSimulcastSender;
function NoSimulcastSender() {
SimulcastSender.call(this);
}
NoSimulcastSender.prototype = Object.create(SimulcastSender.prototype);
/**
* GUM for simulcast.
*
* @param constraints
* @param success
* @param err
*/
NoSimulcastSender.prototype.getUserMedia = function (constraints, success, err) {
navigator.webkitGetUserMedia(constraints, function (hqStream) {
success(hqStream);
}, err);
};
/**
* Prepares the local description for public usage (i.e. to be signaled
* through Jingle to the focus).
*
* @param desc
* @returns {RTCSessionDescription}
*/
NoSimulcastSender.prototype.reverseTransformLocalDescription = function (desc) {
return desc;
};
/**
* Ensures that the simulcast group is present in the answer, _if_ native
* simulcast is enabled,
*
* @param desc
* @returns {*}
*/
NoSimulcastSender.prototype.transformAnswer = function (desc) {
return desc;
};
/**
*
*
* @param desc
* @returns {*}
*/
NoSimulcastSender.prototype.transformLocalDescription = function (desc) {
return desc;
};
NoSimulcastSender.prototype._setLocalVideoStreamEnabled = function (ssrc, enabled) {
};
NoSimulcastSender.prototype.constructor = NoSimulcastSender;
module.exports = {
"native": NativeSimulcastSender,
"no": NoSimulcastSender
}

View File

@@ -1,233 +0,0 @@
var SimulcastLogger = require("./SimulcastLogger");
/**
*
* @constructor
*/
function SimulcastUtils() {
this.logger = new SimulcastLogger("SimulcastUtils", 1);
}
/**
*
* @type {{}}
* @private
*/
SimulcastUtils.prototype._emptyCompoundIndex = {};
/**
*
* @param lines
* @param videoSources
* @private
*/
SimulcastUtils.prototype._replaceVideoSources = function (lines, videoSources) {
var i, inVideo = false, index = -1, howMany = 0;
this.logger.info('Replacing video sources...');
for (i = 0; i < lines.length; i++) {
if (inVideo && lines[i].substring(0, 'm='.length) === 'm=') {
// Out of video.
break;
}
if (!inVideo && lines[i].substring(0, 'm=video '.length) === 'm=video ') {
// In video.
inVideo = true;
}
if (inVideo && (lines[i].substring(0, 'a=ssrc:'.length) === 'a=ssrc:'
|| lines[i].substring(0, 'a=ssrc-group:'.length) === 'a=ssrc-group:')) {
if (index === -1) {
index = i;
}
howMany++;
}
}
// efficiency baby ;)
lines.splice.apply(lines,
[index, howMany].concat(videoSources));
};
SimulcastUtils.prototype.isValidDescription = function (desc)
{
return desc && desc != null
&& desc.type && desc.type != ''
&& desc.sdp && desc.sdp != '';
};
SimulcastUtils.prototype._getVideoSources = function (lines) {
var i, inVideo = false, sb = [];
this.logger.info('Getting video sources...');
for (i = 0; i < lines.length; i++) {
if (inVideo && lines[i].substring(0, 'm='.length) === 'm=') {
// Out of video.
break;
}
if (!inVideo && lines[i].substring(0, 'm=video '.length) === 'm=video ') {
// In video.
inVideo = true;
}
if (inVideo && lines[i].substring(0, 'a=ssrc:'.length) === 'a=ssrc:') {
// In SSRC.
sb.push(lines[i]);
}
if (inVideo && lines[i].substring(0, 'a=ssrc-group:'.length) === 'a=ssrc-group:') {
sb.push(lines[i]);
}
}
return sb;
};
SimulcastUtils.prototype.parseMedia = function (lines, mediatypes) {
var i, res = [], type, cur_media, idx, ssrcs, cur_ssrc, ssrc,
ssrc_attribute, group, semantics, skip = true;
this.logger.info('Parsing media sources...');
for (i = 0; i < lines.length; i++) {
if (lines[i].substring(0, 'm='.length) === 'm=') {
type = lines[i]
.substr('m='.length, lines[i].indexOf(' ') - 'm='.length);
skip = mediatypes !== undefined && mediatypes.indexOf(type) === -1;
if (!skip) {
cur_media = {
'type': type,
'sources': {},
'groups': []
};
res.push(cur_media);
}
} else if (!skip && lines[i].substring(0, 'a=ssrc:'.length) === 'a=ssrc:') {
idx = lines[i].indexOf(' ');
ssrc = lines[i].substring('a=ssrc:'.length, idx);
if (cur_media.sources[ssrc] === undefined) {
cur_ssrc = {'ssrc': ssrc};
cur_media.sources[ssrc] = cur_ssrc;
}
ssrc_attribute = lines[i].substr(idx + 1).split(':', 2)[0];
cur_ssrc[ssrc_attribute] = lines[i].substr(idx + 1).split(':', 2)[1];
if (cur_media.base === undefined) {
cur_media.base = cur_ssrc;
}
} else if (!skip && lines[i].substring(0, 'a=ssrc-group:'.length) === 'a=ssrc-group:') {
idx = lines[i].indexOf(' ');
semantics = lines[i].substr(0, idx).substr('a=ssrc-group:'.length);
ssrcs = lines[i].substr(idx).trim().split(' ');
group = {
'semantics': semantics,
'ssrcs': ssrcs
};
cur_media.groups.push(group);
} else if (!skip && (lines[i].substring(0, 'a=sendrecv'.length) === 'a=sendrecv' ||
lines[i].substring(0, 'a=recvonly'.length) === 'a=recvonly' ||
lines[i].substring(0, 'a=sendonly'.length) === 'a=sendonly' ||
lines[i].substring(0, 'a=inactive'.length) === 'a=inactive')) {
cur_media.direction = lines[i].substring('a='.length);
}
}
return res;
};
/**
* The _indexOfArray() method returns the first a CompoundIndex at which a
* given element can be found in the array, or _emptyCompoundIndex if it is
* not present.
*
* Example:
*
* _indexOfArray('3', [ 'this is line 1', 'this is line 2', 'this is line 3' ])
*
* returns {row: 2, column: 14}
*
* @param needle
* @param haystack
* @param start
* @returns {}
* @private
*/
SimulcastUtils.prototype._indexOfArray = function (needle, haystack, start) {
var length = haystack.length, idx, i;
if (!start) {
start = 0;
}
for (i = start; i < length; i++) {
idx = haystack[i].indexOf(needle);
if (idx !== -1) {
return {row: i, column: idx};
}
}
return this._emptyCompoundIndex;
};
SimulcastUtils.prototype._removeSimulcastGroup = function (lines) {
var i;
for (i = lines.length - 1; i >= 0; i--) {
if (lines[i].indexOf('a=ssrc-group:SIM') !== -1) {
lines.splice(i, 1);
}
}
};
SimulcastUtils.prototype._compileVideoSources = function (videoSources) {
var sb = [], ssrc, addedSSRCs = [];
this.logger.info('Compiling video sources...');
// Add the groups
if (videoSources.groups && videoSources.groups.length !== 0) {
videoSources.groups.forEach(function (group) {
if (group.ssrcs && group.ssrcs.length !== 0) {
sb.push([['a=ssrc-group:', group.semantics].join(''), group.ssrcs.join(' ')].join(' '));
// if (group.semantics !== 'SIM') {
group.ssrcs.forEach(function (ssrc) {
addedSSRCs.push(ssrc);
sb.splice.apply(sb, [sb.length, 0].concat([
["a=ssrc:", ssrc, " cname:", videoSources.sources[ssrc].cname].join(''),
["a=ssrc:", ssrc, " msid:", videoSources.sources[ssrc].msid].join('')]));
});
//}
}
});
}
// Then add any free sources.
if (videoSources.sources) {
for (ssrc in videoSources.sources) {
if (addedSSRCs.indexOf(ssrc) === -1) {
sb.splice.apply(sb, [sb.length, 0].concat([
["a=ssrc:", ssrc, " cname:", videoSources.sources[ssrc].cname].join(''),
["a=ssrc:", ssrc, " msid:", videoSources.sources[ssrc].msid].join('')]));
}
}
}
return sb;
};
module.exports = SimulcastUtils;

View File

@@ -1,202 +0,0 @@
/*jslint plusplus: true */
/*jslint nomen: true*/
var SimulcastSender = require("./SimulcastSender");
var NoSimulcastSender = SimulcastSender["no"];
var NativeSimulcastSender = SimulcastSender["native"];
var SimulcastReceiver = require("./SimulcastReceiver");
var SimulcastUtils = require("./SimulcastUtils");
var RTCEvents = require("../../service/RTC/RTCEvents");
/**
*
* @constructor
*/
function SimulcastManager() {
// Create the simulcast utilities.
this.simulcastUtils = new SimulcastUtils();
// Create remote simulcast.
this.simulcastReceiver = new SimulcastReceiver();
// Initialize local simulcast.
// TODO(gp) move into SimulcastManager.prototype.getUserMedia and take into
// account constraints.
if (!config.enableSimulcast) {
this.simulcastSender = new NoSimulcastSender();
} else {
var isChromium = window.chrome,
vendorName = window.navigator.vendor;
if(isChromium !== null && isChromium !== undefined
/* skip opera */
&& vendorName === "Google Inc."
/* skip Chromium as suggested by fippo */
&& !window.navigator.appVersion.match(/Chromium\//) ) {
var ver = parseInt(window.navigator.appVersion.match(/Chrome\/(\d+)\./)[1], 10);
if (ver > 37) {
this.simulcastSender = new NativeSimulcastSender();
} else {
this.simulcastSender = new NoSimulcastSender();
}
} else {
this.simulcastSender = new NoSimulcastSender();
}
}
APP.RTC.addListener(RTCEvents.SIMULCAST_LAYER_CHANGED,
function (endpointSimulcastLayers) {
endpointSimulcastLayers.forEach(function (esl) {
var ssrc = esl.simulcastLayer.primarySSRC;
simulcast._setReceivingVideoStream(esl.endpoint, ssrc);
});
});
APP.RTC.addListener(RTCEvents.SIMULCAST_START, function (simulcastLayer) {
var ssrc = simulcastLayer.primarySSRC;
simulcast._setLocalVideoStreamEnabled(ssrc, true);
});
APP.RTC.addListener(RTCEvents.SIMULCAST_STOP, function (simulcastLayer) {
var ssrc = simulcastLayer.primarySSRC;
simulcast._setLocalVideoStreamEnabled(ssrc, false);
});
}
/**
* Restores the simulcast groups of the remote description. In
* transformRemoteDescription we remove those in order for the set remote
* description to succeed. The focus needs the signal the groups to new
* participants.
*
* @param desc
* @returns {*}
*/
SimulcastManager.prototype.reverseTransformRemoteDescription = function (desc) {
return this.simulcastReceiver.reverseTransformRemoteDescription(desc);
};
/**
* Removes the ssrc-group:SIM from the remote description bacause Chrome
* either gets confused and thinks this is an FID group or, if an FID group
* is already present, it fails to set the remote description.
*
* @param desc
* @returns {*}
*/
SimulcastManager.prototype.transformRemoteDescription = function (desc) {
return this.simulcastReceiver.transformRemoteDescription(desc);
};
/**
* Gets the fully qualified msid (stream.id + track.id) associated to the
* SSRC.
*
* @param ssrc
* @returns {*}
*/
SimulcastManager.prototype.getRemoteVideoStreamIdBySSRC = function (ssrc) {
return this.simulcastReceiver.getRemoteVideoStreamIdBySSRC(ssrc);
};
/**
* Returns a stream with single video track, the one currently being
* received by this endpoint.
*
* @param stream the remote simulcast stream.
* @returns {webkitMediaStream}
*/
SimulcastManager.prototype.getReceivingVideoStream = function (stream) {
return this.simulcastReceiver.getReceivingVideoStream(stream);
};
/**
*
*
* @param desc
* @returns {*}
*/
SimulcastManager.prototype.transformLocalDescription = function (desc) {
return this.simulcastSender.transformLocalDescription(desc);
};
/**
*
* @returns {*}
*/
SimulcastManager.prototype.getLocalVideoStream = function() {
return this.simulcastSender.getLocalVideoStream();
};
/**
* GUM for simulcast.
*
* @param constraints
* @param success
* @param err
*/
SimulcastManager.prototype.getUserMedia = function (constraints, success, err) {
this.simulcastSender.getUserMedia(constraints, success, err);
};
/**
* Prepares the local description for public usage (i.e. to be signaled
* through Jingle to the focus).
*
* @param desc
* @returns {RTCSessionDescription}
*/
SimulcastManager.prototype.reverseTransformLocalDescription = function (desc) {
return this.simulcastSender.reverseTransformLocalDescription(desc);
};
/**
* Ensures that the simulcast group is present in the answer, _if_ native
* simulcast is enabled,
*
* @param desc
* @returns {*}
*/
SimulcastManager.prototype.transformAnswer = function (desc) {
return this.simulcastSender.transformAnswer(desc);
};
SimulcastManager.prototype.getReceivingSSRC = function (jid) {
return this.simulcastReceiver.getReceivingSSRC(jid);
};
SimulcastManager.prototype.getReceivingVideoStreamBySSRC = function (msid) {
return this.simulcastReceiver.getReceivingVideoStreamBySSRC(msid);
};
/**
*
* @param lines
* @param mediatypes
* @returns {*}
*/
SimulcastManager.prototype.parseMedia = function(lines, mediatypes) {
var sb = lines.sdp.split('\r\n');
return this.simulcastUtils.parseMedia(sb, mediatypes);
};
SimulcastManager.prototype._setReceivingVideoStream = function(resource, ssrc) {
this.simulcastReceiver._setReceivingVideoStream(resource, ssrc);
};
SimulcastManager.prototype._setLocalVideoStreamEnabled = function(ssrc, enabled) {
this.simulcastSender._setLocalVideoStreamEnabled(ssrc, enabled);
};
SimulcastManager.prototype.resetSender = function() {
if (typeof this.simulcastSender.reset === 'function'){
this.simulcastSender.reset();
}
};
var simulcast = new SimulcastManager();
module.exports = simulcast;

View File

@@ -0,0 +1,78 @@
/* global config, $, APP, Strophe, callstats */
var jsSHA = require('jssha');
var io = require('socket.io-client');
var callStats = null;
function initCallback (err, msg) {
console.log("Initializing Status: err="+err+" msg="+msg);
}
var CallStats = {
init: function (jingleSession) {
if(!config.callStatsID || !config.callStatsSecret || callStats !== null)
return;
callStats = new callstats($, io, jsSHA);
this.session = jingleSession;
this.peerconnection = jingleSession.peerconnection.peerconnection;
this.userID = APP.xmpp.myResource();
var location = window.location;
this.confID = location.protocol + "//" +
location.hostname + location.pathname;
//userID is generated or given by the origin server
callStats.initialize(config.callStatsID,
config.callStatsSecret,
this.userID,
initCallback);
var usage = callStats.fabricUsage.multiplex;
callStats.addNewFabric(this.peerconnection,
Strophe.getResourceFromJid(jingleSession.peerjid),
usage,
this.confID,
this.pcCallback.bind(this));
},
pcCallback: function (err, msg) {
if (!callStats)
return;
console.log("Monitoring status: "+ err + " msg: " + msg);
callStats.sendFabricEvent(this.peerconnection,
callStats.fabricEvent.fabricSetup, this.confID);
},
sendMuteEvent: function (mute, type) {
if (!callStats)
return;
var event = null;
if (type === "video") {
event = (mute? callStats.fabricEvent.videoPause :
callStats.fabricEvent.videoResume);
}
else {
event = (mute? callStats.fabricEvent.audioMute :
callStats.fabricEvent.audioUnmute);
}
callStats.sendFabricEvent(this.peerconnection, event, this.confID);
},
sendTerminateEvent: function () {
if(!callStats) {
return;
}
callStats.sendFabricEvent(this.peerconnection,
callStats.fabricEvent.fabricTerminated, this.confID);
},
sendSetupFailedEvent: function () {
if(!callStats) {
return;
}
callStats.sendFabricEvent(this.peerconnection,
callStats.fabricEvent.fabricSetupFailed, this.confID);
}
};
module.exports = CallStats;

View File

@@ -1,23 +1,25 @@
/* global config */
/**
* Provides statistics for the local stream.
*/
var RTCBrowserType = require('../RTC/RTCBrowserType');
/**
* Size of the webaudio analizer buffer.
* Size of the webaudio analyzer buffer.
* @type {number}
*/
var WEBAUDIO_ANALIZER_FFT_SIZE = 2048;
var WEBAUDIO_ANALYZER_FFT_SIZE = 2048;
/**
* Value of the webaudio analizer smoothing time parameter.
* Value of the webaudio analyzer smoothing time parameter.
* @type {number}
*/
var WEBAUDIO_ANALIZER_SMOOTING_TIME = 0.8;
var WEBAUDIO_ANALYZER_SMOOTING_TIME = 0.8;
/**
* Converts time domain data array to audio level.
* @param array the time domain data array.
* @param samples the time domain data array.
* @returns {number} the audio level
*/
function timeDomainDataToAudioLevel(samples) {
@@ -32,7 +34,7 @@ function timeDomainDataToAudioLevel(samples) {
}
return parseFloat(((maxVolume - 127) / 128).toFixed(3));
};
}
/**
* Animates audio level change
@@ -40,20 +42,16 @@ function timeDomainDataToAudioLevel(samples) {
* @param lastLevel the last audio level
* @returns {Number} the audio level to be set
*/
function animateLevel(newLevel, lastLevel)
{
function animateLevel(newLevel, lastLevel) {
var value = 0;
var diff = lastLevel - newLevel;
if(diff > 0.2)
{
if(diff > 0.2) {
value = lastLevel - 0.2;
}
else if(diff < -0.4)
{
else if(diff < -0.4) {
value = lastLevel + 0.4;
}
else
{
else {
value = newLevel;
}
@@ -66,8 +64,6 @@ function animateLevel(newLevel, lastLevel)
*
* @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 LocalStatsCollector(stream, interval, statisticsService, eventEmitter) {
@@ -84,13 +80,14 @@ function LocalStatsCollector(stream, interval, statisticsService, eventEmitter)
* Starts the collecting the statistics.
*/
LocalStatsCollector.prototype.start = function () {
if (config.disableAudioLevels || !window.AudioContext)
if (config.disableAudioLevels || !window.AudioContext ||
RTCBrowserType.isTemasysPluginUsed())
return;
var context = new AudioContext();
var analyser = context.createAnalyser();
analyser.smoothingTimeConstant = WEBAUDIO_ANALIZER_SMOOTING_TIME;
analyser.fftSize = WEBAUDIO_ANALIZER_FFT_SIZE;
analyser.smoothingTimeConstant = WEBAUDIO_ANALYZER_SMOOTING_TIME;
analyser.fftSize = WEBAUDIO_ANALYZER_FFT_SIZE;
var source = context.createMediaStreamSource(this.stream);
@@ -104,7 +101,7 @@ LocalStatsCollector.prototype.start = function () {
var array = new Uint8Array(analyser.frequencyBinCount);
analyser.getByteTimeDomainData(array);
var audioLevel = timeDomainDataToAudioLevel(array);
if(audioLevel != self.audioLevel) {
if (audioLevel != self.audioLevel) {
self.audioLevel = animateLevel(audioLevel, self.audioLevel);
self.eventEmitter.emit(
"statistics.audioLevel",
@@ -114,7 +111,6 @@ LocalStatsCollector.prototype.start = function () {
},
this.intervalMilis
);
};
/**

View File

@@ -1,8 +1,10 @@
/* global ssrc2jid */
/* global require, ssrc2jid */
/* jshint -W117 */
var RTCBrowserType = require("../../service/RTC/RTCBrowserType");
var RTCBrowserType = require("../RTC/RTCBrowserType");
/* Whether we support the browser we are running into for logging statistics */
var browserSupported = RTCBrowserType.isChrome() ||
RTCBrowserType.isOpera() || RTCBrowserType.isFirefox();
/**
* Calculates packet lost percent using the number of lost packets and the
* number of all packet.
@@ -17,10 +19,12 @@ function calculatePacketLoss(lostPackets, totalPackets) {
}
function getStatValue(item, name) {
if(!keyMap[APP.RTC.getBrowserType()][name])
var browserType = RTCBrowserType.getBrowserType();
if (!keyMap[browserType][name])
throw "The property isn't supported!";
var key = keyMap[APP.RTC.getBrowserType()][name];
return APP.RTC.getBrowserType() == RTCBrowserType.RTC_BROWSER_CHROME? item.stat(key) : item[key];
var key = keyMap[browserType][name];
return (RTCBrowserType.isChrome() || RTCBrowserType.isOpera()) ?
item.stat(key) : item[key];
}
/**
@@ -47,8 +51,6 @@ PeerStats.bandwidth = {};
*/
PeerStats.bitrate = {};
/**
* The packet loss rate
* @type {{}}
@@ -233,7 +235,7 @@ StatsCollector.prototype.errorCallback = function (error)
StatsCollector.prototype.start = function ()
{
var self = this;
if(!config.disableAudioLevels) {
if (!config.disableAudioLevels) {
this.audioLevelsIntervalId = setInterval(
function () {
// Interval updates
@@ -260,7 +262,7 @@ StatsCollector.prototype.start = function ()
);
}
if(!config.disableStats && !navigator.mozGetUserMedia) {
if (!config.disableStats && browserSupported) {
this.statsIntervalId = setInterval(
function () {
// Interval updates
@@ -294,7 +296,8 @@ StatsCollector.prototype.start = function ()
);
}
if (config.logStats && !navigator.mozGetUserMedia) {
// Logging statistics does not support firefox
if (config.logStats && (browserSupported && !RTCBrowserType.isFirefox())) {
this.gatherStatsIntervalId = setInterval(
function () {
self.peerconnection.getStats(
@@ -415,6 +418,8 @@ keyMap[RTCBrowserType.RTC_BROWSER_CHROME] = {
"audioInputLevel": "audioInputLevel",
"audioOutputLevel": "audioOutputLevel"
};
keyMap[RTCBrowserType.RTC_BROWSER_OPERA] =
keyMap[RTCBrowserType.RTC_BROWSER_CHROME];
/**
@@ -671,41 +676,34 @@ StatsCollector.prototype.processStatsReport = function () {
/**
* Stats processing logic.
*/
StatsCollector.prototype.processAudioLevelReport = function ()
{
if (!this.baselineAudioLevelsReport)
{
StatsCollector.prototype.processAudioLevelReport = function () {
if (!this.baselineAudioLevelsReport) {
return;
}
for (var idx in this.currentAudioLevelsReport)
{
for (var idx in this.currentAudioLevelsReport) {
var now = this.currentAudioLevelsReport[idx];
if (now.type != 'ssrc')
{
if (now.type != 'ssrc') {
continue;
}
var before = this.baselineAudioLevelsReport[idx];
if (!before)
{
if (!before) {
console.warn(getStatValue(now, 'ssrc') + ' not enough data');
continue;
}
var ssrc = getStatValue(now, 'ssrc');
var jid = APP.xmpp.getJidFromSSRC(ssrc);
if (!jid)
{
if (!jid) {
if((Date.now() - now.timestamp) < 3000)
console.warn("No jid for ssrc: " + ssrc);
continue;
}
var jidStats = this.jid2stats[jid];
if (!jidStats)
{
if (!jidStats) {
jidStats = new PeerStats();
this.jid2stats[jid] = jidStats;
}
@@ -724,8 +722,7 @@ StatsCollector.prototype.processAudioLevelReport = function ()
return;
}
if (audioLevel)
{
if (audioLevel) {
// TODO: can't find specs about what this value really is,
// but it seems to vary between 0 and around 32k.
audioLevel = audioLevel / 32767;
@@ -733,8 +730,5 @@ StatsCollector.prototype.processAudioLevelReport = function ()
if(jid != APP.xmpp.myJid())
this.eventEmitter.emit("statistics.audioLevel", jid, audioLevel);
}
}
};

View File

@@ -1,3 +1,4 @@
/* global require, APP */
/**
* Created by hristo on 8/4/14.
*/
@@ -6,6 +7,8 @@ var RTPStats = require("./RTPStatsCollector.js");
var EventEmitter = require("events");
var StreamEventTypes = require("../../service/RTC/StreamEventTypes.js");
var XMPPEvents = require("../../service/xmpp/XMPPEvents");
var CallStats = require("./CallStats");
var RTCEvents = require("../../service/RTC/RTCEvents");
var eventEmitter = new EventEmitter();
@@ -13,19 +16,15 @@ var localStats = null;
var rtpStats = null;
function stopLocal()
{
if(localStats)
{
function stopLocal() {
if (localStats) {
localStats.stop();
localStats = null;
}
}
function stopRemote()
{
if(rtpStats)
{
function stopRemote() {
if (rtpStats) {
rtpStats.stop();
eventEmitter.emit("statistics.stop");
rtpStats = null;
@@ -33,20 +32,18 @@ function stopRemote()
}
function startRemoteStats (peerconnection) {
if(rtpStats)
{
if (rtpStats) {
rtpStats.stop();
rtpStats = null;
}
rtpStats = new RTPStats(peerconnection, 200, 2000, eventEmitter);
rtpStats.start();
}
function onStreamCreated(stream)
{
if(stream.getOriginalStream().getAudioTracks().length === 0)
function onStreamCreated(stream) {
if(stream.getOriginalStream().getAudioTracks().length === 0) {
return;
}
localStats = new LocalStats(stream.getOriginalStream(), 200, statistics,
eventEmitter);
@@ -54,6 +51,7 @@ function onStreamCreated(stream)
}
function onDisposeConference(onUnload) {
CallStats.sendTerminateEvent();
stopRemote();
if(onUnload) {
stopLocal();
@@ -61,9 +59,7 @@ function onDisposeConference(onUnload) {
}
}
var statistics =
{
var statistics = {
/**
* Indicates that this audio level is for local jid.
* @type {string}
@@ -119,11 +115,24 @@ var statistics =
APP.RTC.addStreamListener(onStreamCreated,
StreamEventTypes.EVENT_TYPE_LOCAL_CREATED);
APP.xmpp.addListener(XMPPEvents.DISPOSE_CONFERENCE, onDisposeConference);
//FIXME: we may want to change CALL INCOMING event to onnegotiationneeded
APP.xmpp.addListener(XMPPEvents.CALL_INCOMING, function (event) {
startRemoteStats(event.peerconnection);
// CallStats.init(event);
});
APP.xmpp.addListener(XMPPEvents.PEERCONNECTION_READY, function (session) {
CallStats.init(session);
});
APP.RTC.addListener(RTCEvents.AUDIO_MUTE, function (mute) {
CallStats.sendMuteEvent(mute, "audio");
});
APP.xmpp.addListener(XMPPEvents.CONFERENCE_SETUP_FAILED, function () {
CallStats.sendSetupFailedEvent();
});
APP.RTC.addListener(RTCEvents.VIDEO_MUTE, function (mute) {
CallStats.sendMuteEvent(mute, "video");
});
}
};

View File

@@ -1,97 +0,0 @@
var transform = require('sdp-transform');
exports.write = function(session, opts) {
if (typeof session !== 'undefined' &&
typeof session.media !== 'undefined' &&
Array.isArray(session.media)) {
session.media.forEach(function (mLine) {
// expand sources to ssrcs
if (typeof mLine.sources !== 'undefined' &&
Object.keys(mLine.sources).length !== 0) {
mLine.ssrcs = [];
Object.keys(mLine.sources).forEach(function (ssrc) {
var source = mLine.sources[ssrc];
Object.keys(source).forEach(function (attribute) {
mLine.ssrcs.push({
id: ssrc,
attribute: attribute,
value: source[attribute]
});
});
});
delete mLine.sources;
}
// join ssrcs in ssrc groups
if (typeof mLine.ssrcGroups !== 'undefined' &&
Array.isArray(mLine.ssrcGroups)) {
mLine.ssrcGroups.forEach(function (ssrcGroup) {
if (typeof ssrcGroup.ssrcs !== 'undefined' &&
Array.isArray(ssrcGroup.ssrcs)) {
ssrcGroup.ssrcs = ssrcGroup.ssrcs.join(' ');
}
});
}
});
}
// join group mids
if (typeof session !== 'undefined' &&
typeof session.groups !== 'undefined' && Array.isArray(session.groups)) {
session.groups.forEach(function (g) {
if (typeof g.mids !== 'undefined' && Array.isArray(g.mids)) {
g.mids = g.mids.join(' ');
}
});
}
return transform.write(session, opts);
};
exports.parse = function(sdp) {
var session = transform.parse(sdp);
if (typeof session !== 'undefined' && typeof session.media !== 'undefined' &&
Array.isArray(session.media)) {
session.media.forEach(function (mLine) {
// group sources attributes by ssrc
if (typeof mLine.ssrcs !== 'undefined' && Array.isArray(mLine.ssrcs)) {
mLine.sources = {};
mLine.ssrcs.forEach(function (ssrc) {
if (!mLine.sources[ssrc.id])
mLine.sources[ssrc.id] = {};
mLine.sources[ssrc.id][ssrc.attribute] = ssrc.value;
});
delete mLine.ssrcs;
}
// split ssrcs in ssrc groups
if (typeof mLine.ssrcGroups !== 'undefined' &&
Array.isArray(mLine.ssrcGroups)) {
mLine.ssrcGroups.forEach(function (ssrcGroup) {
if (typeof ssrcGroup.ssrcs === 'string') {
ssrcGroup.ssrcs = ssrcGroup.ssrcs.split(' ');
}
});
}
});
}
// split group mids
if (typeof session !== 'undefined' &&
typeof session.groups !== 'undefined' && Array.isArray(session.groups)) {
session.groups.forEach(function (g) {
if (typeof g.mids === 'string') {
g.mids = g.mids.split(' ');
}
});
}
return session;
};

View File

@@ -1,3 +1,4 @@
/* global $, require, config, interfaceConfig */
var i18n = require("i18next-client");
var languages = require("../../service/translation/languages");
var Settings = require("../settings/Settings");
@@ -23,7 +24,6 @@ var defaultOptions = {
fallbackOnNull: true,
fallbackOnEmpty: true,
useDataAttrOptions: true,
defaultValueFromContent: false,
app: interfaceConfig.APP_NAME,
getAsync: false,
defaultValueFromContent: false,
@@ -63,8 +63,7 @@ var defaultOptions = {
// localStorageExpirationTime: 86400000 // in ms, default 1 week
};
function initCompleted(t)
{
function initCompleted(t) {
$("[data-i18n]").i18n();
}
@@ -122,7 +121,7 @@ module.exports = {
translateElement: function (selector) {
selector.i18n();
},
generateTranslatonHTML: function (key, options) {
generateTranslationHTML: function (key, options) {
var str = "<span data-i18n=\"" + key + "\"";
if(options)
{

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,268 @@
/* global $ */
/*
Here we do modifications of local video SSRCs. There are 2 situations we have
to handle:
1. We generate SSRC for local recvonly video stream. This is the case when we
have no local camera and it is not generated automatically, but SSRC=1 is
used implicitly. If that happens RTCP packets will be dropped by the JVB
and we won't be able to request video key frames correctly.
2. A hack to re-use SSRC of the first video stream for any new stream created
in future. It turned out that Chrome may keep on using the SSRC of removed
video stream in RTCP even though a new one has been created. So we just
want to avoid that by re-using it. Jingle 'source-remove'/'source-add'
notifications are blocked once first video SSRC is advertised to the focus.
What this hack does:
1. Stores the SSRC of the first video stream created by
a) scanning Jingle session-accept/session-invite for existing video SSRC
b) watching for 'source-add' for new video stream if it has not been
created in step a)
2. Exposes method 'mungeLocalVideoSSRC' which replaces any new video SSRC with
the stored one. It is called by 'TracablePeerConnection' before local SDP is
returned to the other parts of the application.
3. Scans 'source-remove'/'source-add' notifications for stored video SSRC and
blocks those notifications. This makes Jicofo and all participants think
that it exists all the time even if the video stream has been removed or
replaced locally. Thanks to that there is no additional signaling activity
on video mute or when switching to the desktop stream.
*/
var SDP = require('./SDP');
var RTCBrowserType = require('../RTC/RTCBrowserType');
/**
* The hack is enabled on all browsers except FF by default
* FIXME finish the hack once removeStream method is implemented in FF
* @type {boolean}
*/
var isEnabled = !RTCBrowserType.isFirefox();
/**
* Stored SSRC of local video stream.
*/
var localVideoSSRC;
/**
* SSRC used for recvonly video stream when we have no local camera.
* This is in order to tell Chrome what SSRC should be used in RTCP requests
* instead of 1.
*/
var localRecvOnlySSRC;
/**
* cname for <tt>localRecvOnlySSRC</tt>
*/
var localRecvOnlyCName;
/**
* Method removes <source> element which describes <tt>localVideoSSRC</tt>
* from given Jingle IQ.
* @param modifyIq 'source-add' or 'source-remove' Jingle IQ.
* @param actionName display name of the action which will be printed in log
* messages.
* @returns {*} modified Jingle IQ, so that it does not contain <source> element
* corresponding to <tt>localVideoSSRC</tt> or <tt>null</tt> if no
* other SSRCs left to be signaled after removing it.
*/
var filterOutSource = function (modifyIq, actionName) {
var modifyIqTree = $(modifyIq.tree());
if (!localVideoSSRC)
return modifyIqTree[0];
var videoSSRC = modifyIqTree.find(
'>jingle>content[name="video"]' +
'>description>source[ssrc="' + localVideoSSRC + '"]');
if (!videoSSRC.length) {
return modifyIqTree[0];
}
console.info(
'Blocking ' + actionName + ' for local video SSRC: ' + localVideoSSRC);
videoSSRC.remove();
// Check if any sources still left to be added/removed
if (modifyIqTree.find('>jingle>content>description>source').length) {
return modifyIqTree[0];
} else {
return null;
}
};
/**
* Scans given Jingle IQ for video SSRC and stores it.
* @param jingleIq the Jingle IQ to be scanned for video SSRC.
*/
var storeLocalVideoSSRC = function (jingleIq) {
var videoSSRCs =
$(jingleIq.tree())
.find('>jingle>content[name="video"]>description>source');
videoSSRCs.each(function (idx, ssrcElem) {
if (localVideoSSRC)
return;
// We consider SSRC real only if it has msid attribute
// recvonly streams in FF do not have it as well as local SSRCs
// we generate for recvonly streams in Chrome
var ssrSel = $(ssrcElem);
var msid = ssrSel.find('>parameter[name="msid"]');
if (msid.length) {
var ssrcVal = ssrSel.attr('ssrc');
if (ssrcVal) {
localVideoSSRC = ssrcVal;
console.info('Stored local video SSRC' +
' for future re-use: ' + localVideoSSRC);
}
}
});
};
/**
* Generates new SSRC for local video recvonly stream.
* FIXME what about eventual SSRC collision ?
*/
function generateRecvonlySSRC() {
//
localRecvOnlySSRC =
Math.random().toString(10).substring(2, 11);
localRecvOnlyCName =
Math.random().toString(36).substring(2);
console.info(
"Generated local recvonly SSRC: " + localRecvOnlySSRC +
", cname: " + localRecvOnlyCName);
}
var LocalSSRCReplacement = {
/**
* Method must be called before 'session-initiate' or 'session-invite' is
* sent. Scans the IQ for local video SSRC and stores it if detected.
*
* @param sessionInit our 'session-initiate' or 'session-accept' Jingle IQ
* which will be scanned for local video SSRC.
*/
processSessionInit: function (sessionInit) {
if (!isEnabled)
return;
if (localVideoSSRC) {
console.error("Local SSRC stored already: " + localVideoSSRC);
return;
}
storeLocalVideoSSRC(sessionInit);
},
/**
* If we have local video SSRC stored searched given
* <tt>localDescription</tt> for video SSRC and makes sure it is replaced
* with the stored one.
* @param localDescription local description object that will have local
* video SSRC replaced with the stored one
* @returns modified <tt>localDescription</tt> object.
*/
mungeLocalVideoSSRC: function (localDescription) {
if (!isEnabled)
return localDescription;
if (!localDescription) {
console.warn("localDescription is null or undefined");
return localDescription;
}
// IF we have local video SSRC stored make sure it is replaced
// with old SSRC
if (localVideoSSRC) {
var newSdp = new SDP(localDescription.sdp);
if (newSdp.media[1].indexOf("a=ssrc:") !== -1 &&
!newSdp.containsSSRC(localVideoSSRC)) {
// Get new video SSRC
var map = newSdp.getMediaSsrcMap();
var videoPart = map[1];
var videoSSRCs = videoPart.ssrcs;
var newSSRC = Object.keys(videoSSRCs)[0];
console.info(
"Replacing new video SSRC: " + newSSRC +
" with " + localVideoSSRC);
localDescription.sdp =
newSdp.raw.replace(
new RegExp('a=ssrc:' + newSSRC, 'g'),
'a=ssrc:' + localVideoSSRC);
}
} else {
// Make sure we have any SSRC for recvonly video stream
var sdp = new SDP(localDescription.sdp);
if (sdp.media[1] && sdp.media[1].indexOf('a=ssrc:') === -1 &&
sdp.media[1].indexOf('a=recvonly') !== -1) {
if (!localRecvOnlySSRC) {
generateRecvonlySSRC();
}
console.info('No SSRC in video recvonly stream' +
' - adding SSRC: ' + localRecvOnlySSRC);
sdp.media[1] += 'a=ssrc:' + localRecvOnlySSRC +
' cname:' + localRecvOnlyCName + '\r\n';
localDescription.sdp = sdp.session + sdp.media.join('');
}
}
return localDescription;
},
/**
* Method must be called before 'source-add' notification is sent. In case
* we have local video SSRC advertised already it will be removed from the
* notification. If no other SSRCs are described by given IQ null will be
* returned which means that there is no point in sending the notification.
* @param sourceAdd 'source-add' Jingle IQ to be processed
* @returns modified 'source-add' IQ which can be sent to the focus or
* <tt>null</tt> if no notification shall be sent. It is no longer
* a Strophe IQ Builder instance, but DOM element tree.
*/
processSourceAdd: function (sourceAdd) {
if (!isEnabled)
return sourceAdd;
if (!localVideoSSRC) {
// Store local SSRC if available
storeLocalVideoSSRC(sourceAdd);
return sourceAdd;
} else {
return filterOutSource(sourceAdd, 'source-add');
}
},
/**
* Method must be called before 'source-remove' notification is sent.
* Removes local video SSRC from the notification. If there are no other
* SSRCs described in the given IQ <tt>null</tt> will be returned which
* means that there is no point in sending the notification.
* @param sourceRemove 'source-remove' Jingle IQ to be processed
* @returns modified 'source-remove' IQ which can be sent to the focus or
* <tt>null</tt> if no notification shall be sent. It is no longer
* a Strophe IQ Builder instance, but DOM element tree.
*/
processSourceRemove: function (sourceRemove) {
if (!isEnabled)
return sourceRemove;
return filterOutSource(sourceRemove, 'source-remove');
},
/**
* Turns the hack on or off
* @param enabled <tt>true</tt> to enable the hack or <tt>false</tt>
* to disable it
*/
setEnabled: function (enabled) {
isEnabled = enabled;
}
};
module.exports = LocalSSRCReplacement;

View File

@@ -218,12 +218,15 @@ SDP.prototype.toJingle = function (elem, thecreator, ssrcs) {
if (kv.indexOf(':') == -1) {
elem.attrs({ name: kv });
} else {
elem.attrs({ name: kv.split(':', 2)[0] });
elem.attrs({ value: kv.split(':', 2)[1] });
var k = kv.split(':', 2)[0];
elem.attrs({ name: k });
var v = kv.split(':', 2)[1];
v = SDPUtil.filter_special_chars(v);
elem.attrs({ value: v });
}
elem.up();
});
elem.up();
}
else
{
@@ -243,7 +246,7 @@ SDP.prototype.toJingle = function (elem, thecreator, ssrcs) {
}
if(msid != null)
{
msid = msid.replace(/[\{,\}]/g,"");
msid = SDPUtil.filter_special_chars(msid);
elem.c('parameter');
elem.attrs({name: "msid", value:msid});
elem.up();
@@ -253,11 +256,9 @@ SDP.prototype.toJingle = function (elem, thecreator, ssrcs) {
elem.c('parameter');
elem.attrs({name: "label", value:msid});
elem.up();
elem.up();
}
}
elem.up();
// XEP-0339 handle ssrc-group attributes
var ssrc_group_lines = SDPUtil.find_lines(this.media[i], 'a=ssrc-group:');
@@ -605,9 +606,12 @@ SDP.prototype.jingle2media = function (content) {
tmp.each(function () {
var ssrc = this.getAttribute('ssrc');
$(this).find('>parameter').each(function () {
media += 'a=ssrc:' + ssrc + ' ' + this.getAttribute('name');
if (this.getAttribute('value') && this.getAttribute('value').length)
media += ':' + this.getAttribute('value');
var name = this.getAttribute('name');
var value = this.getAttribute('value');
value = SDPUtil.filter_special_chars(value);
media += 'a=ssrc:' + ssrc + ' ' + name;
if (value && value.length)
media += ':' + value;
media += '\r\n';
});
});

View File

@@ -1,3 +1,6 @@
var SDPUtil = require("./SDPUtil");
function SDPDiffer(mySDP, otherSDP) {
this.mySDP = mySDP;
this.otherSDP = otherSDP;
@@ -130,8 +133,11 @@ SDPDiffer.prototype.toJingle = function(modify) {
if (kv.indexOf(':') == -1) {
modify.attrs({ name: kv });
} else {
modify.attrs({ name: kv.split(':', 2)[0] });
modify.attrs({ value: kv.split(':', 2)[1] });
var nv = kv.split(':', 2);
var name = nv[0];
var value = SDPUtil.filter_special_chars(nv[1]);
modify.attrs({ name: name });
modify.attrs({ value: value });
}
modify.up(); // end of parameter
});

View File

@@ -1,4 +1,7 @@
SDPUtil = {
filter_special_chars: function (text) {
return text.replace(/[\\\/\{,\}\+]/g, "");
},
iceparams: function (mediadesc, sessiondesc) {
var data = null;
if (SDPUtil.find_line(mediadesc, 'a=ice-ufrag:', sessiondesc) &&

View File

@@ -1,17 +1,39 @@
function TraceablePeerConnection(ice_config, constraints) {
var RTC = require('../RTC/RTC');
var RTCBrowserType = require("../RTC/RTCBrowserType.js");
var XMPPEvents = require("../../service/xmpp/XMPPEvents");
var SSRCReplacement = require("./LocalSSRCReplacement");
function TraceablePeerConnection(ice_config, constraints, session) {
var self = this;
var RTCPeerconnection = navigator.mozGetUserMedia ? mozRTCPeerConnection : webkitRTCPeerConnection;
this.peerconnection = new RTCPeerconnection(ice_config, constraints);
var RTCPeerConnectionType = null;
if (RTCBrowserType.isFirefox()) {
RTCPeerConnectionType = mozRTCPeerConnection;
} else if (RTCBrowserType.isTemasysPluginUsed()) {
RTCPeerConnectionType = RTCPeerConnection;
} else {
RTCPeerConnectionType = webkitRTCPeerConnection;
}
this.peerconnection = new RTCPeerConnectionType(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
var Interop = require('sdp-interop').Interop;
this.interop = new Interop();
var Simulcast = require('sdp-simulcast');
this.simulcast = new Simulcast({numOfLayers: 3, explodeRemoteSimulcast: false});
// override as desired
this.trace = function (what, info) {
//console.warn('WTRACE', what, info);
/*console.warn('WTRACE', what, info);
if (info && RTCBrowserType.isIExplorer()) {
if (info.length > 1024) {
console.warn('WTRACE', what, info.substr(1024));
}
if (info.length > 2048) {
console.warn('WTRACE', what, info.substr(2048));
}
}*/
self.updateLog.push({
time: new Date(),
type: what,
@@ -20,7 +42,9 @@ function TraceablePeerConnection(ice_config, constraints) {
};
this.onicecandidate = null;
this.peerconnection.onicecandidate = function (event) {
self.trace('onicecandidate', JSON.stringify(event.candidate, null, ' '));
// FIXME: this causes stack overflow with Temasys Plugin
if (!RTCBrowserType.isTemasysPluginUsed())
self.trace('onicecandidate', JSON.stringify(event.candidate, null, ' '));
if (self.onicecandidate !== null) {
self.onicecandidate(event);
}
@@ -67,13 +91,13 @@ function TraceablePeerConnection(ice_config, constraints) {
self.ondatachannel(event);
}
};
if (!navigator.mozGetUserMedia && this.maxstats) {
// XXX: do all non-firefox browsers which we support also support this?
if (!RTCBrowserType.isFirefox() && this.maxstats) {
this.statsinterval = window.setInterval(function() {
self.peerconnection.getStats(function(stats) {
var results = stats.result();
var now = new Date();
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]) {
@@ -97,9 +121,12 @@ function TraceablePeerConnection(ice_config, constraints) {
}, 1000);
}
};
}
dumpSDP = function(description) {
/**
* Returns a string representation of a SessionDescription object.
*/
var dumpSDP = function(description) {
if (typeof description === 'undefined' || description == null) {
return '';
}
@@ -107,38 +134,113 @@ dumpSDP = function(description) {
return 'type: ' + description.type + '\r\n' + description.sdp;
};
/**
* Takes a SessionDescription object and returns a "normalized" version.
* Currently it only takes care of ordering the a=ssrc lines.
*/
var normalizePlanB = function(desc) {
if (typeof desc !== 'object' || desc === null ||
typeof desc.sdp !== 'string') {
console.warn('An empty description was passed as an argument.');
return desc;
}
var transform = require('sdp-transform');
var session = transform.parse(desc.sdp);
if (typeof session !== 'undefined' && typeof session.media !== 'undefined' &&
Array.isArray(session.media)) {
session.media.forEach(function (mLine) {
// Chrome appears to be picky about the order in which a=ssrc lines
// are listed in an m-line when rtx is enabled (and thus there are
// a=ssrc-group lines with FID semantics). Specifically if we have
// "a=ssrc-group:FID S1 S2" and the "a=ssrc:S2" lines appear before
// the "a=ssrc:S1" lines, SRD fails.
// So, put SSRC which appear as the first SSRC in an FID ssrc-group
// first.
var firstSsrcs = [];
var newSsrcLines = [];
if (typeof mLine.ssrcGroups !== 'undefined' && Array.isArray(mLine.ssrcGroups)) {
mLine.ssrcGroups.forEach(function (group) {
if (typeof group.semantics !== 'undefined' &&
group.semantics === 'FID') {
if (typeof group.ssrcs !== 'undefined') {
firstSsrcs.push(Number(group.ssrcs.split(' ')[0]));
}
}
});
}
if (typeof mLine.ssrcs !== 'undefined' && Array.isArray(mLine.ssrcs)) {
for (var i = 0; i<mLine.ssrcs.length; i++){
if (typeof mLine.ssrcs[i] === 'object'
&& typeof mLine.ssrcs[i].id !== 'undefined'
&& $.inArray(mLine.ssrcs[i].id, firstSsrcs) == 0) {
newSsrcLines.push(mLine.ssrcs[i]);
delete mLine.ssrcs[i];
}
}
for (var i = 0; i<mLine.ssrcs.length; i++){
if (typeof mLine.ssrcs[i] !== 'undefined') {
newSsrcLines.push(mLine.ssrcs[i]);
}
}
mLine.ssrcs = newSsrcLines;
}
});
}
var resStr = transform.write(session);
return new RTCSessionDescription({
type: desc.type,
sdp: resStr
});
};
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() {
this.trace('getLocalDescription::preTransform (Plan A)', dumpSDP(this.peerconnection.localDescription));
// if we're running on FF, transform to Plan B first.
var desc = this.peerconnection.localDescription;
if (navigator.mozGetUserMedia) {
desc = this.interop.toPlanB(desc);
} else {
desc = APP.simulcast.reverseTransformLocalDescription(this.peerconnection.localDescription);
}
this.trace('getLocalDescription::postTransform (Plan B)', dumpSDP(desc));
return desc;
});
TraceablePeerConnection.prototype.__defineGetter__('remoteDescription', function() {
this.trace('getRemoteDescription::preTransform (Plan A)', dumpSDP(this.peerconnection.remoteDescription));
// if we're running on FF, transform to Plan B first.
var desc = this.peerconnection.remoteDescription;
if (navigator.mozGetUserMedia) {
desc = this.interop.toPlanB(desc);
} else {
desc = APP.simulcast.reverseTransformRemoteDescription(this.peerconnection.remoteDescription);
}
this.trace('getRemoteDescription::postTransform (Plan B)', dumpSDP(desc));
return desc;
});
TraceablePeerConnection.prototype.__defineGetter__(
'signalingState',
function() { return this.peerconnection.signalingState; });
TraceablePeerConnection.prototype.__defineGetter__(
'iceConnectionState',
function() { return this.peerconnection.iceConnectionState; });
TraceablePeerConnection.prototype.__defineGetter__(
'localDescription',
function() {
var desc = this.peerconnection.localDescription;
desc = SSRCReplacement.mungeLocalVideoSSRC(desc);
this.trace('getLocalDescription::preTransform', dumpSDP(desc));
// if we're running on FF, transform to Plan B first.
if (RTCBrowserType.usesUnifiedPlan()) {
desc = this.interop.toPlanB(desc);
this.trace('getLocalDescription::postTransform (Plan B)', dumpSDP(desc));
}
return desc;
});
TraceablePeerConnection.prototype.__defineGetter__(
'remoteDescription',
function() {
var desc = this.peerconnection.remoteDescription;
this.trace('getRemoteDescription::preTransform', dumpSDP(desc));
// if we're running on FF, transform to Plan B first.
if (RTCBrowserType.usesUnifiedPlan()) {
desc = this.interop.toPlanB(desc);
this.trace('getRemoteDescription::postTransform (Plan B)', dumpSDP(desc));
}
return desc;
});
}
TraceablePeerConnection.prototype.addStream = function (stream) {
this.trace('addStream', stream.id);
APP.simulcast.resetSender();
try
{
this.peerconnection.addStream(stream);
@@ -146,25 +248,33 @@ TraceablePeerConnection.prototype.addStream = function (stream) {
catch (e)
{
console.error(e);
return;
}
};
TraceablePeerConnection.prototype.removeStream = function (stream, stopStreams) {
this.trace('removeStream', stream.id);
APP.simulcast.resetSender();
if(stopStreams) {
stream.getAudioTracks().forEach(function (track) {
track.stop();
// stop() not supported with IE
if (track.stop) {
track.stop();
}
});
stream.getVideoTracks().forEach(function (track) {
track.stop();
// stop() not supported with IE
if (track.stop) {
track.stop();
}
});
if (stream.stop) {
stream.stop();
}
}
try {
// FF doesn't support this yet.
this.peerconnection.removeStream(stream);
if (this.peerconnection.removeStream)
this.peerconnection.removeStream(stream);
} catch (e) {
console.error(e);
}
@@ -175,15 +285,15 @@ TraceablePeerConnection.prototype.createDataChannel = function (label, opts) {
return this.peerconnection.createDataChannel(label, opts);
};
TraceablePeerConnection.prototype.setLocalDescription = function (description, successCallback, failureCallback) {
this.trace('setLocalDescription::preTransform (Plan B)', dumpSDP(description));
TraceablePeerConnection.prototype.setLocalDescription
= function (description, successCallback, failureCallback) {
this.trace('setLocalDescription::preTransform', dumpSDP(description));
// if we're running on FF, transform to Plan A first.
if (navigator.mozGetUserMedia) {
if (RTCBrowserType.usesUnifiedPlan()) {
description = this.interop.toUnifiedPlan(description);
} else {
description = APP.simulcast.transformLocalDescription(description);
this.trace('setLocalDescription::postTransform (Plan A)', dumpSDP(description));
}
this.trace('setLocalDescription::postTransform (Plan A)', dumpSDP(description));
var self = this;
this.peerconnection.setLocalDescription(description,
function () {
@@ -202,16 +312,23 @@ TraceablePeerConnection.prototype.setLocalDescription = function (description, s
*/
};
TraceablePeerConnection.prototype.setRemoteDescription = function (description, successCallback, failureCallback) {
this.trace('setRemoteDescription::preTransform (Plan B)', dumpSDP(description));
TraceablePeerConnection.prototype.setRemoteDescription
= function (description, successCallback, failureCallback) {
this.trace('setRemoteDescription::preTransform', dumpSDP(description));
// TODO the focus should squeze or explode the remote simulcast
description = this.simulcast.mungeRemoteDescription(description);
this.trace('setRemoteDescription::postTransform (simulcast)', dumpSDP(description));
// if we're running on FF, transform to Plan A first.
if (navigator.mozGetUserMedia) {
if (RTCBrowserType.usesUnifiedPlan()) {
description = this.interop.toUnifiedPlan(description);
this.trace('setRemoteDescription::postTransform (Plan A)', dumpSDP(description));
}
else {
description = APP.simulcast.transformRemoteDescription(description);
if (RTCBrowserType.usesPlanB()) {
description = normalizePlanB(description);
}
this.trace('setRemoteDescription::postTransform (Plan A)', dumpSDP(description));
var self = this;
this.peerconnection.setRemoteDescription(description,
function () {
@@ -239,17 +356,28 @@ TraceablePeerConnection.prototype.close = function () {
this.peerconnection.close();
};
TraceablePeerConnection.prototype.createOffer = function (successCallback, failureCallback, constraints) {
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::preTransform (Plan A)', dumpSDP(offer));
self.trace('createOfferOnSuccess::preTransform', dumpSDP(offer));
// NOTE this is not tested because in meet the focus generates the
// offer.
// if we're running on FF, transform to Plan B first.
if (navigator.mozGetUserMedia) {
if (RTCBrowserType.usesUnifiedPlan()) {
offer = self.interop.toPlanB(offer);
self.trace('createOfferOnSuccess::postTransform (Plan B)', dumpSDP(offer));
}
offer = SSRCReplacement.mungeLocalVideoSSRC(offer);
if (config.enableSimulcast && self.simulcast.isSupported()) {
offer = self.simulcast.mungeLocalDescription(offer);
self.trace('createOfferOnSuccess::postTransform (simulcast)', dumpSDP(offer));
}
self.trace('createOfferOnSuccess::postTransform (Plan B)', dumpSDP(offer));
successCallback(offer);
},
function(err) {
@@ -260,19 +388,26 @@ TraceablePeerConnection.prototype.createOffer = function (successCallback, failu
);
};
TraceablePeerConnection.prototype.createAnswer = function (successCallback, failureCallback, constraints) {
TraceablePeerConnection.prototype.createAnswer
= function (successCallback, failureCallback, constraints) {
var self = this;
this.trace('createAnswer', JSON.stringify(constraints, null, ' '));
this.peerconnection.createAnswer(
function (answer) {
self.trace('createAnswerOnSuccess::preTransfom (Plan A)', dumpSDP(answer));
self.trace('createAnswerOnSuccess::preTransform', dumpSDP(answer));
// if we're running on FF, transform to Plan A first.
if (navigator.mozGetUserMedia) {
if (RTCBrowserType.usesUnifiedPlan()) {
answer = self.interop.toPlanB(answer);
} else {
answer = APP.simulcast.transformAnswer(answer);
self.trace('createAnswerOnSuccess::postTransform (Plan B)', dumpSDP(answer));
}
// munge local video SSRC
answer = SSRCReplacement.mungeLocalVideoSSRC(answer);
if (config.enableSimulcast && self.simulcast.isSupported()) {
answer = self.simulcast.mungeLocalDescription(answer);
self.trace('createAnswerOnSuccess::postTransform (simulcast)', dumpSDP(answer));
}
self.trace('createAnswerOnSuccess::postTransfom (Plan B)', dumpSDP(answer));
successCallback(answer);
},
function(err) {
@@ -283,8 +418,9 @@ TraceablePeerConnection.prototype.createAnswer = function (successCallback, fail
);
};
TraceablePeerConnection.prototype.addIceCandidate = function (candidate, successCallback, failureCallback) {
var self = this;
TraceablePeerConnection.prototype.addIceCandidate
= function (candidate, successCallback, failureCallback) {
//var self = this;
this.trace('addIceCandidate', JSON.stringify(candidate, null, ' '));
this.peerconnection.addIceCandidate(candidate);
/* maybe later
@@ -302,13 +438,12 @@ TraceablePeerConnection.prototype.addIceCandidate = function (candidate, success
};
TraceablePeerConnection.prototype.getStats = function(callback, errback) {
if (navigator.mozGetUserMedia) {
// TODO: Is this the correct way to handle Opera, Temasys?
if (RTCBrowserType.isFirefox()) {
// ignore for now...
if(!errback)
errback = function () {
}
this.peerconnection.getStats(null,callback,errback);
errback = function () {};
this.peerconnection.getStats(null, callback, errback);
} else {
this.peerconnection.getStats(callback);
}

View File

@@ -1,4 +1,4 @@
/* global $, $iq, APP, config, connection, UI, messageHandler,
/* global $, $iq, APP, config, messageHandler,
roomName, sessionTerminated, Strophe, Util */
var XMPPEvents = require("../../service/xmpp/XMPPEvents");
var Settings = require("../settings/Settings");
@@ -35,7 +35,7 @@ var externalAuthEnabled = false;
// Sip gateway can be enabled by configuring Jigasi host in config.js or
// it will be enabled automatically if focus detects the component through
// service discovery.
var sipGatewayEnabled = config.hosts.call_control !== undefined;
var sipGatewayEnabled;
var eventEmitter = null;
@@ -65,12 +65,15 @@ var Moderator = {
this.xmppService = xmpp;
eventEmitter = emitter;
sipGatewayEnabled =
config.hosts && config.hosts.call_control !== undefined;
// Message listener that talks to POPUP window
function listener(event) {
if (event.data && event.data.sessionId) {
if (event.origin !== window.location.origin) {
console.warn(
"Ignoring sessionId from different origin: " + event.origin);
console.warn("Ignoring sessionId from different origin: " +
event.origin);
return;
}
localStorage.setItem('sessionId', event.data.sessionId);
@@ -191,6 +194,10 @@ var Moderator = {
{ name: 'startVideoMuted', value: config.startVideoMuted})
.up();
}
elem.c(
'property',
{ name: 'simulcastMode', value: 'rewriting'})
.up();
elem.up();
return elem;
},
@@ -215,8 +222,7 @@ var Moderator = {
console.info("Authentication enabled: " + authenticationEnabled);
externalAuthEnabled
= $(resultIq).find(
externalAuthEnabled = $(resultIq).find(
'>conference>property' +
'[name=\'externalAuth\'][value=\'true\']').length > 0;
@@ -329,10 +335,8 @@ var Moderator = {
// Do not show in case of session invalid
// which means just a retry
if (!invalidSession) {
APP.UI.messageHandler.notify(
null, "notify.focus",
'disconnected', "notify.focusFail",
{component: focusComponent, ms: retrySec});
eventEmitter.emit(XMPPEvents.FOCUS_DISCONNECTED,
focusComponent, retrySec);
}
// Reset response timeout
getNextTimeout(true);

View File

@@ -1,4 +1,4 @@
/* global $, $iq, config, connection, focusMucJid, messageHandler, Moderator,
/* global $, $iq, config, connection, focusMucJid, messageHandler,
Toolbar, Util */
var Moderator = require("./moderator");
@@ -10,7 +10,7 @@ var recordingEnabled;
* Whether to use a jirecon component for recording, or use the videobridge
* through COLIBRI.
*/
var useJirecon = (typeof config.hosts.jirecon != "undefined");
var useJirecon;
/**
* The ID of the jirecon recording session. Jirecon generates it when we
@@ -19,18 +19,16 @@ var useJirecon = (typeof config.hosts.jirecon != "undefined");
*/
var jireconRid = null;
/**
* The callback to update the recording button. Currently used from colibri
* after receiving a pending status.
*/
var recordingStateChangeCallback = null;
function setRecordingToken(token) {
recordingToken = token;
}
function setRecording(state, token, callback, connection) {
if (useJirecon){
setRecordingJirecon(state, token, callback, connection);
} else {
setRecordingColibri(state, token, callback, connection);
}
}
function setRecordingJirecon(state, token, callback, connection) {
if (state == recordingEnabled){
return;
@@ -38,9 +36,9 @@ function setRecordingJirecon(state, token, callback, connection) {
var iq = $iq({to: config.hosts.jirecon, type: 'set'})
.c('recording', {xmlns: 'http://jitsi.org/protocol/jirecon',
action: state ? 'start' : 'stop',
action: (state === 'on') ? 'start' : 'stop',
mucjid: connection.emuc.roomjid});
if (!state){
if (state === 'off'){
iq.attrs({rid: jireconRid});
}
@@ -52,10 +50,10 @@ function setRecordingJirecon(state, token, callback, connection) {
// TODO wait for an IQ with the real status, since this is
// provisional?
jireconRid = $(result).find('recording').attr('rid');
console.log('Recording ' + (state ? 'started' : 'stopped') +
console.log('Recording ' + ((state === 'on') ? 'started' : 'stopped') +
'(jirecon)' + result);
recordingEnabled = state;
if (!state){
if (state === 'off'){
jireconRid = null;
}
@@ -81,10 +79,19 @@ function setRecordingColibri(state, token, callback, connection) {
function (result) {
console.log('Set recording "', state, '". Result:', result);
var recordingElem = $(result).find('>conference>recording');
var newState = ('true' === recordingElem.attr('state'));
var newState = recordingElem.attr('state');
recordingEnabled = newState;
callback(newState);
if (newState === 'pending' && recordingStateChangeCallback == null) {
recordingStateChangeCallback = callback;
connection.addHandler(function(iq){
var state = $(iq).find('recording').attr('state');
if (state)
recordingStateChangeCallback(state);
}, 'http://jitsi.org/protocol/colibri', 'iq', null, null, null);
}
},
function (error) {
console.warn(error);
@@ -93,9 +100,20 @@ function setRecordingColibri(state, token, callback, connection) {
);
}
function setRecording(state, token, callback, connection) {
if (useJirecon){
setRecordingJirecon(state, token, callback, connection);
} else {
setRecordingColibri(state, token, callback, connection);
}
}
var Recording = {
toggleRecording: function (tokenEmptyCallback,
startingCallback, startedCallback, connection) {
init: function () {
useJirecon = config.hosts &&
(typeof config.hosts.jirecon != "undefined");
},
toggleRecording: function (tokenEmptyCallback, recordingStateChangeCallback, connection) {
if (!Moderator.isModerator()) {
console.log(
'non-focus, or conference not yet organized:' +
@@ -108,16 +126,16 @@ var Recording = {
if (!recordingToken && !useJirecon) {
tokenEmptyCallback(function (value) {
setRecordingToken(value);
self.toggleRecording(tokenEmptyCallback,
startingCallback, startedCallback, connection);
self.toggleRecording(tokenEmptyCallback, recordingStateChangeCallback, connection);
});
return;
}
var oldState = recordingEnabled;
startingCallback(!oldState);
setRecording(!oldState,
var newState = (oldState === 'off' || !oldState) ? 'on' : 'off';
setRecording(newState,
recordingToken,
function (state) {
console.log("New recording state: ", state);
@@ -143,13 +161,13 @@ var Recording = {
// have been wrong
setRecordingToken(null);
}
startedCallback(state);
recordingStateChangeCallback(state);
},
connection
);
}
}
};
module.exports = Recording;

View File

@@ -4,9 +4,6 @@
*/
var XMPPEvents = require("../../service/xmpp/XMPPEvents");
var Moderator = require("./moderator");
var JingleSession = require("./JingleSession");
var bridgeIsDown = false;
module.exports = function(XMPP, eventEmitter) {
Strophe.addConnectionPlugin('emuc', {
@@ -21,20 +18,17 @@ module.exports = function(XMPP, eventEmitter) {
isOwner: false,
role: null,
focusMucJid: null,
ssrc2jid: {},
bridgeIsDown: false,
init: function (conn) {
this.connection = conn;
},
initPresenceMap: function (myroomjid) {
this.presMap['to'] = myroomjid;
this.presMap['xns'] = 'http://jabber.org/protocol/muc';
if(APP.RTC.localAudio.isMuted())
{
if (APP.RTC.localAudio && APP.RTC.localAudio.isMuted()) {
this.addAudioInfoToPresence(true);
}
if(APP.RTC.localVideo.isMuted())
{
if (APP.RTC.localVideo && APP.RTC.localVideo.isMuted()) {
this.addVideoInfoToPresence(true);
}
},
@@ -112,15 +106,16 @@ module.exports = function(XMPP, eventEmitter) {
// Parse etherpad tag.
var etherpad = $(pres).find('>etherpad');
if (etherpad.length) {
if (config.etherpad_base && !Moderator.isModerator()) {
if (config.etherpad_base) {
eventEmitter.emit(XMPPEvents.ETHERPAD, etherpad.text());
}
}
var url;
// Parse prezi tag.
var presentation = $(pres).find('>prezi');
if (presentation.length) {
var url = presentation.attr('url');
url = presentation.attr('url');
var current = presentation.find('>current').text();
console.log('presentation info received from', from, url);
@@ -135,7 +130,7 @@ module.exports = function(XMPP, eventEmitter) {
}
}
else if (this.preziMap[from] != null) {
var url = this.preziMap[from];
url = this.preziMap[from];
delete this.preziMap[from];
$(document).trigger('presentationremoved.muc', [from, url]);
}
@@ -143,20 +138,22 @@ module.exports = function(XMPP, eventEmitter) {
// Parse audio info tag.
var audioMuted = $(pres).find('>audiomuted');
if (audioMuted.length) {
$(document).trigger('audiomuted.muc', [from, audioMuted.text()]);
eventEmitter.emit(XMPPEvents.PARTICIPANT_AUDIO_MUTED,
from, (audioMuted.text() === "true"));
}
// Parse video info tag.
var videoMuted = $(pres).find('>videomuted');
if (videoMuted.length) {
$(document).trigger('videomuted.muc', [from, videoMuted.text()]);
eventEmitter.emit(XMPPEvents.PARTICIPANT_VIDEO_MUTED,
from, (videoMuted.text() === "true"));
}
var startMuted = $(pres).find('>startmuted');
if (startMuted.length)
{
eventEmitter.emit(XMPPEvents.START_MUTED,
startMuted.attr("audio") === "true", startMuted.attr("video") === "true");
if (startMuted.length && Moderator.isPeerModerator(from)) {
eventEmitter.emit(XMPPEvents.START_MUTED_SETTING_CHANGED,
startMuted.attr("audio") === "true",
startMuted.attr("video") === "true");
}
var devices = $(pres).find('>devices');
@@ -178,6 +175,16 @@ module.exports = function(XMPP, eventEmitter) {
Strophe.getResourceFromJid(from), devicesValues);
}
var videoType = $(pres).find('>videoType');
if (videoType.length)
{
if (videoType.text().length)
{
eventEmitter.emit(XMPPEvents.PARTICIPANT_VIDEO_TYPE_CHANGED,
Strophe.getResourceFromJid(from), videoType.text());
}
}
var stats = $(pres).find('>stats');
if (stats.length) {
var statsObj = {};
@@ -210,7 +217,7 @@ module.exports = function(XMPP, eventEmitter) {
}
var nicktag = $(pres).find('>nick[xmlns="http://jabber.org/protocol/nick"]');
member.displayName = (nicktag.length > 0 ? nicktag.html() : null);
member.displayName = (nicktag.length > 0 ? nicktag.text() : null);
if (from == this.myroomjid) {
if (member.affiliation == 'owner') this.isOwner = true;
@@ -279,14 +286,6 @@ module.exports = function(XMPP, eventEmitter) {
return true;
}
var self = this;
// Remove old ssrcs coming from the jid
Object.keys(this.ssrc2jid).forEach(function (ssrc) {
if (self.ssrc2jid[ssrc] == from) {
delete self.ssrc2jid[ssrc];
}
});
// Status code 110 indicates that this notification is "self-presence".
if (!$(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]>status[code="110"]').length) {
delete this.members[from];
@@ -329,19 +328,15 @@ module.exports = function(XMPP, eventEmitter) {
// We're either missing Jicofo/Prosody config for anonymous
// domains or something is wrong.
// XMPP.promptLogin();
APP.UI.messageHandler.openReportDialog(null,
"dialog.joinError", pres);
eventEmitter.emit(XMPPEvents.ROOM_JOIN_ERROR, pres);
} else {
console.warn('onPresError ', pres);
APP.UI.messageHandler.openReportDialog(null,
"dialog.connectError",
pres);
eventEmitter.emit(XMPPEvents.ROOM_CONNECT_ERROR, pres);
}
} else {
console.warn('onPresError ', pres);
APP.UI.messageHandler.openReportDialog(null,
"dialog.connectError",
pres);
eventEmitter.emit(XMPPEvents.ROOM_CONNECT_ERROR, pres);
}
return true;
},
@@ -385,11 +380,24 @@ module.exports = function(XMPP, eventEmitter) {
}
}
// xep-0203 delay
var stamp = $(msg).find('>delay').attr('stamp');
if (!stamp) {
// or xep-0091 delay, UTC timestamp
stamp = $(msg).find('>[xmlns="jabber:x:delay"]').attr('stamp');
if (stamp) {
// the format is CCYYMMDDThh:mm:ss
var dateParts = stamp.match(/(\d{4})(\d{2})(\d{2}T\d{2}:\d{2}:\d{2})/);
stamp = dateParts[1] + "-" + dateParts[2] + "-" + dateParts[3] + "Z";
}
}
if (txt) {
console.log('chat', nick, txt);
eventEmitter.emit(XMPPEvents.MESSAGE_RECEIVED,
from, nick, txt, this.myroomjid);
from, nick, txt, this.myroomjid, stamp);
}
return true;
},
@@ -485,6 +493,11 @@ module.exports = function(XMPP, eventEmitter) {
.t(this.presMap['videomuted']).up();
}
if (this.presMap['videoTypeNs']) {
pres.c('videoType', { xmlns: this.presMap['videoTypeNs'] })
.t(this.presMap['videoType']).up();
}
if (this.presMap['statsns']) {
var stats = pres.c('stats', {xmlns: this.presMap['statsns']});
for (var stat in this.presMap["stats"])
@@ -500,31 +513,6 @@ module.exports = function(XMPP, eventEmitter) {
.c('current').t(this.presMap['prezicurrent']).up().up();
}
if (this.presMap['etherpadns']) {
pres.c('etherpad', {xmlns: this.presMap['etherpadns']})
.t(this.presMap['etherpadname']).up();
}
if (this.presMap['medians']) {
pres.c('media', {xmlns: this.presMap['medians']});
var sourceNumber = 0;
Object.keys(this.presMap).forEach(function (key) {
if (key.indexOf('source') >= 0) {
sourceNumber++;
}
});
if (sourceNumber > 0)
for (var i = 1; i <= sourceNumber / 3; i++) {
pres.c('source',
{type: this.presMap['source' + i + '_type'],
ssrc: this.presMap['source' + i + '_ssrc'],
direction: this.presMap['source' + i + '_direction']
|| 'sendrecv' }
).up();
}
pres.up();
}
if(this.presMap["startMuted"] !== undefined)
{
pres.c("startmuted", {audio: this.presMap["startMuted"].audio,
@@ -539,24 +527,16 @@ module.exports = function(XMPP, eventEmitter) {
addDisplayNameToPresence: function (displayName) {
this.presMap['displayName'] = displayName;
},
addMediaToPresence: function (sourceNumber, mtype, ssrcs, direction) {
if (!this.presMap['medians'])
this.presMap['medians'] = 'http://estos.de/ns/mjs';
this.presMap['source' + sourceNumber + '_type'] = mtype;
this.presMap['source' + sourceNumber + '_ssrc'] = ssrcs;
this.presMap['source' + sourceNumber + '_direction'] = direction;
},
addDevicesToPresence: function (devices) {
this.presMap['devices'] = devices;
},
clearPresenceMedia: function () {
var self = this;
Object.keys(this.presMap).forEach(function (key) {
if (key.indexOf('source') != -1) {
delete self.presMap[key];
}
});
/**
* Adds the info about the type of our video stream.
* @param videoType 'camera' or 'screen'
*/
addVideoTypeToPresence: function (videoType) {
this.presMap['videoTypeNs'] = 'http://jitsi.org/jitmeet/video';
this.presMap['videoType'] = videoType;
},
addPreziToPresence: function (url, currentSlide) {
this.presMap['prezins'] = 'http://jitsi.org/jitmeet/prezi';
@@ -574,10 +554,6 @@ module.exports = function(XMPP, eventEmitter) {
getPrezi: function (roomjid) {
return this.preziMap[roomjid];
},
addEtherpadToPresence: function (etherpadName) {
this.presMap['etherpadns'] = 'http://jitsi.org/jitmeet/etherpad';
this.presMap['etherpadname'] = etherpadName;
},
addAudioInfoToPresence: function (isMuted) {
this.presMap['audions'] = 'http://jitsi.org/jitmeet/audio';
this.presMap['audiomuted'] = isMuted.toString();
@@ -636,51 +612,25 @@ module.exports = function(XMPP, eventEmitter) {
Moderator.onMucMemberLeft(jid);
},
parsePresence: function (from, memeber, pres) {
if($(pres).find(">bridgeIsDown").length > 0 && !bridgeIsDown) {
bridgeIsDown = true;
parsePresence: function (from, member, pres) {
if($(pres).find(">bridgeIsDown").length > 0 && !this.bridgeIsDown) {
this.bridgeIsDown = true;
eventEmitter.emit(XMPPEvents.BRIDGE_DOWN);
}
if(memeber.isFocus)
if(member.isFocus)
return;
var self = this;
// Remove old ssrcs coming from the jid
Object.keys(this.ssrc2jid).forEach(function (ssrc) {
if (self.ssrc2jid[ssrc] == from) {
delete self.ssrc2jid[ssrc];
}
});
var changedStreams = [];
$(pres).find('>media[xmlns="http://estos.de/ns/mjs"]>source').each(function (idx, ssrc) {
//console.log(jid, 'assoc ssrc', ssrc.getAttribute('type'), ssrc.getAttribute('ssrc'));
var ssrcV = ssrc.getAttribute('ssrc');
self.ssrc2jid[ssrcV] = from;
var type = ssrc.getAttribute('type');
var direction = ssrc.getAttribute('direction');
changedStreams.push({type: type, direction: direction});
});
eventEmitter.emit(XMPPEvents.STREAMS_CHANGED, from, changedStreams);
var displayName = !config.displayJids
? memeber.displayName : Strophe.getResourceFromJid(from);
? member.displayName : Strophe.getResourceFromJid(from);
if (displayName && displayName.length > 0)
{
if (displayName && displayName.length > 0) {
eventEmitter.emit(XMPPEvents.DISPLAY_NAME_CHANGED, from, displayName);
}
var id = $(pres).find('>userID').text();
var email = $(pres).find('>email');
if(email.length > 0) {
if (email.length > 0) {
id = email.text();
}

View File

@@ -1,27 +1,11 @@
/* jshint -W117 */
var JingleSession = require("./JingleSession");
var JingleSession = require("./JingleSessionPC");
var XMPPEvents = require("../../service/xmpp/XMPPEvents");
var RTCBrowserType = require("../RTC/RTCBrowserType");
module.exports = function(XMPP, eventEmitter)
{
function CallIncomingJingle(sid, connection) {
var sess = connection.jingle.sessions[sid];
// TODO: do we check activecall == null?
connection.jingle.activecall = sess;
eventEmitter.emit(XMPPEvents.CALL_INCOMING, sess);
// TODO: check affiliation and/or role
console.log('emuc data for', sess.peerjid, connection.emuc.members[sess.peerjid]);
sess.usedrip = true; // not-so-naive trickle ice
sess.sendAnswer();
sess.accept();
};
module.exports = function(XMPP, eventEmitter) {
Strophe.addConnectionPlugin('jingle', {
connection: null,
sessions: {},
@@ -49,16 +33,18 @@ module.exports = function(XMPP, eventEmitter)
this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:audio');
this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:video');
if (RTCBrowserType.isChrome() || RTCBrowserType.isOpera()
|| RTCBrowserType.isTemasysPluginUsed()) {
this.connection.disco.addFeature('urn:ietf:rfc:4588');
}
// this is dealt with by SDP O/A so we don't need to annouce this
// this is dealt with by SDP O/A so we don't need to announce 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
if (config.useRtcpMux) {
this.connection.disco.addFeature('urn:ietf:rfc:5761'); // rtcp-mux
}
if (config.useBundle) {
this.connection.disco.addFeature('urn:ietf:rfc:5888'); // a=group, e.g. bundle
}
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);
@@ -83,9 +69,8 @@ module.exports = function(XMPP, eventEmitter)
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)) {
if (fromJid != sess.peerjid) {
console.warn('jid mismatch for session id', sid, fromJid, sess.peerjid);
ack.type = 'error';
ack.c('error', {type: 'cancel'})
@@ -110,36 +95,45 @@ module.exports = function(XMPP, eventEmitter)
switch (action) {
case 'session-initiate':
var startMuted = $(iq).find('jingle>startmuted');
if(startMuted && startMuted.length > 0)
{
if (startMuted && startMuted.length > 0) {
var audioMuted = startMuted.attr("audio");
var videoMuted = startMuted.attr("video");
APP.UI.setInitialMuteFromFocus((audioMuted === "true"),
(videoMuted === "true"));
eventEmitter.emit(XMPPEvents.START_MUTED_FROM_FOCUS,
audioMuted === "true", videoMuted === "true");
}
sess = new JingleSession(
$(iq).attr('to'), $(iq).find('jingle').attr('sid'),
this.connection, XMPP);
this.connection, XMPP, eventEmitter);
// configure session
sess.media_constraints = this.media_constraints;
sess.pc_constraints = this.pc_constraints;
sess.ice_config = this.ice_config;
sess.initiate(fromJid, false);
sess.initialize(fromJid, false);
// FIXME: setRemoteDescription should only be done when this call is to be accepted
sess.setRemoteDescription($(iq).find('>jingle'), 'offer');
sess.setOffer($(iq).find('>jingle'));
this.sessions[sess.sid] = sess;
this.jid2session[sess.peerjid] = sess;
// the callback should either
// .sendAnswer and .accept
// or .sendTerminate -- not necessarily synchronus
CallIncomingJingle(sess.sid, this.connection);
// or .sendTerminate -- not necessarily synchronous
// TODO: do we check activecall == null?
this.connection.jingle.activecall = sess;
eventEmitter.emit(XMPPEvents.CALL_INCOMING, sess);
// TODO: check affiliation and/or role
console.log('emuc data for', sess.peerjid,
this.connection.emuc.members[sess.peerjid]);
sess.sendAnswer();
sess.accept();
break;
case 'session-accept':
sess.setRemoteDescription($(iq).find('>jingle'), 'answer');
sess.setAnswer($(iq).find('>jingle'));
sess.accept();
$(document).trigger('callaccepted.jingle', [sess.sid]);
break;
@@ -199,14 +193,14 @@ module.exports = function(XMPP, eventEmitter)
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, XMPP);
this.connection, XMPP, eventEmitter);
// configure session
sess.media_constraints = this.media_constraints;
sess.pc_constraints = this.pc_constraints;
sess.ice_config = this.ice_config;
sess.initiate(peerjid, true);
sess.initialize(peerjid, true);
this.sessions[sess.sid] = sess;
this.jid2session[sess.peerjid] = sess;
sess.sendOffer();
@@ -324,12 +318,13 @@ module.exports = function(XMPP, eventEmitter)
},
/**
* Populates the log data
* Returns the data saved in 'updateLog' in a format to be logged.
*/
populateData: function () {
getLog: function () {
var data = {};
var self = this;
Object.keys(this.sessions).forEach(function (sid) {
var session = this.sessions[sid];
var session = self.sessions[sid];
if (session.peerconnection && session.peerconnection.updateLog) {
// FIXME: should probably be a .dump call
data["jingle_" + session.sid] = {

View File

@@ -3,7 +3,9 @@
/**
* Moderate connection plugin.
*/
module.exports = function (XMPP) {
var XMPPEvents = require("../../service/xmpp/XMPPEvents");
module.exports = function (XMPP, eventEmitter) {
Strophe.addConnectionPlugin('moderate', {
connection: null,
init: function (conn) {
@@ -44,7 +46,7 @@ module.exports = function (XMPP) {
var mute = $(iq).find('mute');
if (mute.length) {
var doMuteAudio = mute.text() === "true";
APP.UI.setAudioMuted(doMuteAudio);
eventEmitter.emit(XMPPEvents.AUDIO_MUTED_BY_FOCUS, doMuteAudio);
XMPP.forceMuted = doMuteAudio;
}
return true;

View File

@@ -7,7 +7,6 @@ var Settings = require("../settings/Settings");
var Pako = require("pako");
var StreamEventTypes = require("../../service/RTC/StreamEventTypes");
var RTCEvents = require("../../service/RTC/RTCEvents");
var UIEvents = require("../../service/UI/UIEvents");
var XMPPEvents = require("../../service/xmpp/XMPPEvents");
var retry = require('retry');
@@ -27,9 +26,6 @@ function connect(jid, password) {
connection = XMPP.createConnection();
Moderator.setConnection(connection);
if (connection.disco) {
// for chrome, add multistream cap
}
connection.jingle.pc_constraints = APP.RTC.getPCConstraints();
if (config.useIPv6) {
// https://code.google.com/p/webrtc/issues/detail?id=2828
@@ -132,8 +128,8 @@ function connect(jid, password) {
// if we get disconnected from the XMPP server permanently.
// If the connection failed, retry.
if (connectionFailed
&& faultTolerantConnect.retry("connection-failed")) {
if (connectionFailed &&
faultTolerantConnect.retry("connection-failed")) {
return;
}
@@ -152,41 +148,53 @@ function connect(jid, password) {
}
function maybeDoJoin() {
if (connection && connection.connected &&
Strophe.getResourceFromJid(connection.jid)
&& (APP.RTC.localAudio || APP.RTC.localVideo)) {
Strophe.getResourceFromJid(connection.jid) &&
(APP.RTC.localAudio || APP.RTC.localVideo)) {
// .connected is true while connecting?
doJoin();
}
}
function doJoin() {
var roomName = APP.UI.generateRoomName();
Moderator.allocateConferenceFocus(
roomName, APP.UI.checkForNicknameAndJoin);
eventEmitter.emit(XMPPEvents.READY_TO_JOIN);
}
function initStrophePlugins()
{
require("./strophe.emuc")(XMPP, eventEmitter);
require("./strophe.jingle")(XMPP, eventEmitter);
require("./strophe.moderate")(XMPP);
require("./strophe.moderate")(XMPP, eventEmitter);
require("./strophe.util")();
require("./strophe.rayo")();
require("./strophe.logger")();
}
/**
* If given <tt>localStream</tt> is video one this method will advertise it's
* video type in MUC presence.
* @param localStream new or modified <tt>LocalStream</tt>.
*/
function broadcastLocalVideoType(localStream) {
if (localStream.videoType)
XMPP.addToPresence('videoType', localStream.videoType);
}
function registerListeners() {
APP.RTC.addStreamListener(maybeDoJoin,
StreamEventTypes.EVENT_TYPE_LOCAL_CREATED);
APP.RTC.addStreamListener(
function (localStream) {
maybeDoJoin();
broadcastLocalVideoType(localStream);
},
StreamEventTypes.EVENT_TYPE_LOCAL_CREATED
);
APP.RTC.addStreamListener(
broadcastLocalVideoType,
StreamEventTypes.EVENT_TYPE_LOCAL_CHANGED
);
APP.RTC.addListener(RTCEvents.AVAILABLE_DEVICES_CHANGED, function (devices) {
XMPP.addToPresence("devices", devices);
})
APP.UI.addListener(UIEvents.NICKNAME_CHANGED, function (nickname) {
XMPP.addToPresence("displayName", nickname);
});
}
@@ -205,7 +213,8 @@ var unload = (function () {
async: false,
cache: false,
contentType: 'application/xml',
data: "<body rid='" + (connection.rid || connection._proto.rid) +
data: "<body rid='" +
(connection.rid || connection._proto.rid) +
"' xmlns='http://jabber.org/protocol/httpbind' sid='" +
(connection.sid || connection._proto.sid) +
"' type='terminate'>" +
@@ -231,7 +240,7 @@ function setupEvents() {
// (change URL). If this participant doesn't unload properly, then it
// becomes a ghost for the rest of the participants that stay in the
// conference. Thankfully handling the 'unload' event in addition to the
// 'beforeunload' event seems to garante the execution of the 'unload'
// 'beforeunload' event seems to guarantee the execution of the 'unload'
// method at least once.
//
// The 'unload' method can safely be run multiple times, it will actually do
@@ -261,6 +270,7 @@ var XMPP = {
initStrophePlugins();
registerListeners();
Moderator.init(this, eventEmitter);
Recording.init();
var configDomain = config.hosts.anonymousdomain || config.hosts.domain;
// Force authenticated domain if room is appended with '?login=true'
if (config.hosts.anonymousdomain &&
@@ -279,13 +289,10 @@ var XMPP = {
return Strophe.getStatusString(status);
},
promptLogin: function () {
// FIXME: re-use LoginDialog which supports retries
APP.UI.showLoginPopup(connect);
eventEmitter.emit(XMPPEvents.PROMPT_FOR_LOGIN);
},
joinRoom: function(roomName, useNicks, nick)
{
var roomjid;
roomjid = roomName;
joinRoom: function(roomName, useNicks, nick) {
var roomjid = roomName;
if (useNicks) {
if (nick) {
@@ -294,7 +301,6 @@ var XMPP = {
roomjid += '/' + Strophe.getNodeFromJid(connection.jid);
}
} else {
var tmpJid = Strophe.getNodeFromJid(connection.jid);
if(!authenticatedUser)
@@ -315,7 +321,6 @@ var XMPP = {
return Strophe.getResourceFromJid(connection.emuc.myroomjid);
},
disposeConference: function (onUnload) {
eventEmitter.emit(XMPPEvents.DISPOSE_CONFERENCE, onUnload);
var handler = connection.jingle.activecall;
if (handler && handler.peerconnection) {
// FIXME: probably removing streams is not required and close() should
@@ -330,15 +335,14 @@ var XMPP = {
}
handler.peerconnection.close();
}
eventEmitter.emit(XMPPEvents.DISPOSE_CONFERENCE, onUnload);
connection.jingle.activecall = null;
if(!onUnload)
{
if (!onUnload) {
this.sessionTerminated = true;
connection.emuc.doLeave();
}
},
addListener: function(type, listener)
{
addListener: function(type, listener) {
eventEmitter.on(type, listener);
},
removeListener: function (type, listener) {
@@ -362,8 +366,12 @@ var XMPP = {
isExternalAuthEnabled: function () {
return Moderator.isExternalAuthEnabled();
},
isConferenceInProgress: function () {
return connection && connection.jingle.activecall &&
connection.jingle.activecall.peerconnection;
},
switchStreams: function (stream, oldStream, callback, isAudio) {
if (connection && connection.jingle.activecall) {
if (this.isConferenceInProgress()) {
// FIXME: will block switchInProgress on true value in case of exception
connection.jingle.activecall.switchStreams(stream, oldStream, callback, isAudio);
} else {
@@ -402,7 +410,6 @@ var XMPP = {
return false;
}
if (this.forceMuted && !mute) {
console.info("Asking focus for unmute");
connection.moderate.setMute(connection.emuc.myroomjid, mute);
@@ -415,16 +422,11 @@ var XMPP = {
return true;
}
// It is not clear what is the right way to handle multiple tracks.
// So at least make sure that they are all muted or all unmuted and
// that we send presence just once.
APP.RTC.localAudio.setMute(!mute);
// isMuted is the opposite of audioEnabled
APP.RTC.localAudio.setMute(mute);
this.sendAudioInfoPresence(mute, callback);
return true;
},
sendAudioInfoPresence: function(mute, callback)
{
sendAudioInfoPresence: function(mute, callback) {
if(connection) {
connection.emuc.addAudioInfoToPresence(mute);
connection.emuc.sendPresence();
@@ -432,62 +434,16 @@ var XMPP = {
callback();
return true;
},
// Really mute video, i.e. dont even send black frames
muteVideo: function (pc, unmute) {
// FIXME: this probably needs another of those lovely state safeguards...
// which checks for iceconn == connected and sigstate == stable
pc.setRemoteDescription(pc.remoteDescription,
function () {
pc.createAnswer(
function (answer) {
var sdp = new SDP(answer.sdp);
if (sdp.media.length > 1) {
if (unmute)
sdp.media[1] = sdp.media[1].replace('a=recvonly', 'a=sendrecv');
else
sdp.media[1] = sdp.media[1].replace('a=sendrecv', 'a=recvonly');
sdp.raw = sdp.session + sdp.media.join('');
answer.sdp = sdp.raw;
}
pc.setLocalDescription(answer,
function () {
console.log('mute SLD ok');
},
function (error) {
console.log('mute SLD error');
APP.UI.messageHandler.showError("dialog.error",
"dialog.SLDFailure");
}
);
},
function (error) {
console.log(error);
APP.UI.messageHandler.showError();
}
);
},
function (error) {
console.log('muteVideo SRD error');
APP.UI.messageHandler.showError("dialog.error",
"dialog.SRDFailure");
}
);
},
toggleRecording: function (tokenEmptyCallback,
startingCallback, startedCallback) {
recordingStateChangeCallback) {
Recording.toggleRecording(tokenEmptyCallback,
startingCallback, startedCallback, connection);
recordingStateChangeCallback, connection);
},
addToPresence: function (name, value, dontSend) {
switch (name)
{
switch (name) {
case "displayName":
connection.emuc.addDisplayNameToPresence(value);
break;
case "etherpad":
connection.emuc.addEtherpadToPresence(value);
break;
case "prezi":
connection.emuc.addPreziToPresence(value, 0);
break;
@@ -503,6 +459,9 @@ var XMPP = {
case "devices":
connection.emuc.addDevicesToPresence(value);
break;
case "videoType":
connection.emuc.addVideoTypeToPresence(value);
break;
case "startMuted":
if(!Moderator.isModerator())
return;
@@ -546,17 +505,13 @@ var XMPP = {
connection.send(message);
return true;
},
populateData: function () {
var data = {};
if (connection.jingle) {
data = connection.jingle.populateData();
}
return data;
// Gets the logs from strophe.jingle.
getJingleLog: function () {
return connection.jingle ? connection.jingle.getLog() : {};
},
getLogger: function () {
if(connection.logger)
return connection.logger.log;
return null;
// Gets the logs from strophe.
getXmppLog: function () {
return connection.logger ? connection.logger.log : null;
},
getPrezi: function () {
return connection.emuc.getPrezi(this.myJid());
@@ -593,19 +548,19 @@ var XMPP = {
return connection.emuc.members;
},
getJidFromSSRC: function (ssrc) {
if(!connection)
if (!this.isConferenceInProgress())
return null;
return connection.emuc.ssrc2jid[ssrc];
return connection.jingle.activecall.getSsrcOwner(ssrc);
},
getMUCJoined: function () {
// Returns true iff we have joined the MUC.
isMUCJoined: function () {
return connection.emuc.joined;
},
getSessions: function () {
return connection.jingle.sessions;
},
removeStream: function (stream) {
if(!connection || !connection.jingle.activecall ||
!connection.jingle.activecall.peerconnection)
if (!this.isConferenceInProgress())
return;
connection.jingle.activecall.peerconnection.removeStream(stream);
}

View File

@@ -14,16 +14,20 @@
],
"author": "",
"readmeFilename": "README.md",
"//": "Callstats.io does not work with recent versions of jsSHA (2.0.1 in particular)",
"dependencies": {
"events": "*",
"pako": "*",
"i18next-client": "1.7.7",
"sdp-interop": "0.1.4",
"sdp-transform": "1.4.0",
"sdp-transform": "1.4.1",
"sdp-simulcast": "0.1.0",
"async": "0.9.0",
"retry": "0.6.1"
"retry": "0.6.1",
"jssha": "1.5.0",
"socket.io-client": "1.3.6"
},
"devDependencies": {
},
"license": "MIT"
"license": "Apache"
}

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