Browse Source

Merge branch 'ssfocus'

master
paweldomas 11 years ago
parent
commit
4776605dec

+ 95
- 139
app.js View File

@@ -2,12 +2,11 @@
2 2
 /* application specific logic */
3 3
 var connection = null;
4 4
 var authenticatedUser = false;
5
-var focus = null;
6 5
 var activecall = null;
7 6
 var RTC = null;
8 7
 var nickname = null;
9 8
 var sharedKey = '';
10
-var recordingToken ='';
9
+var focusJid = null;
11 10
 var roomUrl = null;
12 11
 var roomName = null;
13 12
 var ssrc2jid = {};
@@ -41,6 +40,16 @@ var ssrc2videoType = {};
41 40
  */
42 41
 var focusedVideoSrc = null;
43 42
 var mutedAudios = {};
43
+/**
44
+ * Remembers if we were muted by the focus.
45
+ * @type {boolean}
46
+ */
47
+var forceMuted = false;
48
+/**
49
+ * Indicates if we have muted our audio before the conference has started.
50
+ * @type {boolean}
51
+ */
52
+var preMuted = false;
44 53
 
45 54
 var localVideoSrc = null;
46 55
 var flipXLocalVideo = true;
@@ -219,11 +228,9 @@ function maybeDoJoin() {
219 228
     }
220 229
 }
221 230
 
222
-
223
-function doJoin() {
231
+function generateRoomName() {
224 232
     var roomnode = null;
225 233
     var path = window.location.pathname;
226
-    var roomjid;
227 234
 
228 235
     // determinde the room node from the url
229 236
     // TODO: just the roomnode or the whole bare jid?
@@ -251,7 +258,20 @@ function doJoin() {
251 258
     }
252 259
 
253 260
     roomName = roomnode + '@' + config.hosts.muc;
261
+}
262
+
263
+function doJoin() {
264
+    if (!roomName) {
265
+        generateRoomName();
266
+    }
254 267
 
268
+    Moderator.allocateConferenceFocus(
269
+        roomName, doJoinAfterFocus);
270
+}
271
+
272
+function doJoinAfterFocus() {
273
+
274
+    var roomjid;
255 275
     roomjid = roomName;
256 276
 
257 277
     if (config.useNicks) {
@@ -410,7 +430,10 @@ function waitForPresence(data, sid) {
410 430
         container  = document.getElementById(
411 431
                 'participant_' + Strophe.getResourceFromJid(data.peerjid));
412 432
     } else {
413
-        if (data.stream.id !== 'mixedmslabel') {
433
+        if (data.stream.id !== 'mixedmslabel'
434
+            // FIXME: default stream is added always with new focus
435
+            // (to be investigated)
436
+            && data.stream.id !== 'default') {
414 437
             console.error('can not associate stream',
415 438
                 data.stream.id,
416 439
                 'with a participant');
@@ -638,16 +661,6 @@ $(document).bind('conferenceCreated.jingle', function (event, focus)
638 661
     }
639 662
 });
640 663
 
641
-$(document).bind('callterminated.jingle', function (event, sid, jid, reason) {
642
-    // Leave the room if my call has been remotely terminated.
643
-    if (connection.emuc.joined && focus == null && reason === 'kick') {
644
-        sessionTerminated = true;
645
-        connection.emuc.doLeave();
646
-        messageHandler.openMessageDialog("Session Terminated",
647
-                            "Ouch! You have been kicked out of the meet!");
648
-    }
649
-});
650
-
651 664
 $(document).bind('setLocalDescription.jingle', function (event, sid) {
652 665
     // put our ssrcs into presence so other clients can identify our stream
653 666
     var sess = connection.jingle.sessions[sid];
@@ -736,27 +749,6 @@ $(document).bind('joined.muc', function (event, jid, info) {
736 749
         document.createTextNode(Strophe.getResourceFromJid(jid) + ' (me)')
737 750
     );
738 751
 
739
-    if (Object.keys(connection.emuc.members).length < 1) {
740
-        focus = new ColibriFocus(connection, config.hosts.bridge);
741
-        if (nickname !== null) {
742
-            focus.setEndpointDisplayName(connection.emuc.myroomjid,
743
-                                         nickname);
744
-        }
745
-        Toolbar.showSipCallButton(true);
746
-        Toolbar.showRecordingButton(false);
747
-    }
748
-
749
-    if (!focus)
750
-    {
751
-        Toolbar.showSipCallButton(false);
752
-    }
753
-
754
-    if (focus && config.etherpad_base) {
755
-        Etherpad.init();
756
-    }
757
-
758
-    VideoLayout.showFocusIndicator();
759
-
760 752
     // Add myself to the contact list.
761 753
     ContactList.addContact(jid, SettingsMenu.getEmail() || SettingsMenu.getUID());
762 754
 
@@ -777,7 +769,12 @@ $(document).bind('entered.muc', function (event, jid, info, pres) {
777 769
         'connected',
778 770
         'connected');
779 771
 
780
-    console.log('is focus? ' + (focus ? 'true' : 'false'));
772
+    if (info.isFocus)
773
+    {
774
+        focusJid = jid;
775
+        console.info("Ignore focus: " + jid +", real JID: " + info.jid);
776
+        return;
777
+    }
781 778
 
782 779
     // Add Peer's container
783 780
     var id = $(pres).find('>userID').text();
@@ -792,7 +789,7 @@ $(document).bind('entered.muc', function (event, jid, info, pres) {
792 789
         APIConnector.triggerEvent("participantJoined",{jid: jid});
793 790
     }
794 791
 
795
-    if (focus !== null) {
792
+    /*if (focus !== null) {
796 793
         // FIXME: this should prepare the video
797 794
         if (focus.confid === null) {
798 795
             console.log('make new conference with', jid);
@@ -807,7 +804,7 @@ $(document).bind('entered.muc', function (event, jid, info, pres) {
807 804
             console.log('invite', jid, 'into conference');
808 805
             focus.addNewParticipant(jid);
809 806
         }
810
-    }
807
+    }*/
811 808
 });
812 809
 
813 810
 $(document).bind('left.muc', function (event, jid) {
@@ -848,41 +845,6 @@ $(document).bind('left.muc', function (event, jid) {
848 845
 
849 846
     connection.jingle.terminateByJid(jid);
850 847
 
851
-    if (focus == null
852
-            // I shouldn't be the one that left to enter here.
853
-            && jid !== connection.emuc.myroomjid
854
-            && connection.emuc.myroomjid === connection.emuc.list_members[0]
855
-            // If our session has been terminated for some reason
856
-            // (kicked, hangup), don't try to become the focus
857
-            && !sessionTerminated) {
858
-        console.log('welcome to our new focus... myself');
859
-        focus = new ColibriFocus(connection, config.hosts.bridge);
860
-        if (nickname !== null) {
861
-            focus.setEndpointDisplayName(connection.emuc.myroomjid,
862
-                                         nickname);
863
-        }
864
-
865
-        Toolbar.showSipCallButton(true);
866
-
867
-        if (Object.keys(connection.emuc.members).length > 0) {
868
-            focus.makeConference(Object.keys(connection.emuc.members));
869
-            Toolbar.showRecordingButton(true);
870
-        }
871
-        $(document).trigger('focusechanged.muc', [focus]);
872
-    }
873
-    else if (focus && Object.keys(connection.emuc.members).length === 0) {
874
-        console.log('everyone left');
875
-        // FIXME: closing the connection is a hack to avoid some
876
-        // problems with reinit
877
-        disposeConference();
878
-        focus = new ColibriFocus(connection, config.hosts.bridge);
879
-        if (nickname !== null) {
880
-            focus.setEndpointDisplayName(connection.emuc.myroomjid,
881
-                                         nickname);
882
-        }
883
-        Toolbar.showSipCallButton(true);
884
-        Toolbar.showRecordingButton(false);
885
-    }
886 848
     if (connection.emuc.getPrezi(jid)) {
887 849
         $(document).trigger('presentationremoved.muc',
888 850
                             [jid, connection.emuc.getPrezi(jid)]);
@@ -929,11 +891,15 @@ $(document).bind('presence.muc', function (event, jid, info, pres) {
929 891
 
930 892
     if (displayName && displayName.length > 0)
931 893
         $(document).trigger('displaynamechanged',
932
-                            [jid, displayName]);
894
+                            [jid, info.displayName]);
895
+    if (info.isFocus)
896
+    {
897
+        return;
898
+    }
933 899
 
934
-    if (focus !== null && info.displayName !== null) {
900
+    /*if (focus !== null && info.displayName !== null) {
935 901
         focus.setEndpointDisplayName(jid, info.displayName);
936
-    }
902
+    }*/
937 903
 
938 904
     //check if the video bridge is available
939 905
     if($(pres).find(">bridgeIsDown").length > 0 && !bridgeIsDown) {
@@ -958,6 +924,17 @@ $(document).bind('presence.status.muc', function (event, jid, info, pres) {
958 924
 
959 925
 });
960 926
 
927
+$(document).bind('kicked.muc', function (event, jid) {
928
+    console.info(jid + " has been kicked from MUC!");
929
+    if (connection.emuc.myroomjid === jid) {
930
+        sessionTerminated = true;
931
+        disposeConference(false);
932
+        connection.emuc.doLeave();
933
+        messageHandler.openMessageDialog("Session Terminated",
934
+            "Ouch! You have been kicked out of the meet!");
935
+    }
936
+});
937
+
961 938
 $(document).bind('passwordrequired.muc', function (event, jid) {
962 939
     console.log('on password required', jid);
963 940
 
@@ -1046,7 +1023,7 @@ function isVideoSrcDesktop(jid) {
1046 1023
 }
1047 1024
 
1048 1025
 function getConferenceHandler() {
1049
-    return focus ? focus : activecall;
1026
+    return activecall;
1050 1027
 }
1051 1028
 
1052 1029
 function toggleVideo() {
@@ -1076,28 +1053,45 @@ function toggleVideo() {
1076 1053
  * Mutes / unmutes audio for the local participant.
1077 1054
  */
1078 1055
 function toggleAudio() {
1056
+    setAudioMuted(!isAudioMuted());
1057
+}
1058
+
1059
+/**
1060
+ * Sets muted audio state for the local participant.
1061
+ */
1062
+function setAudioMuted(mute) {
1079 1063
     if (!(connection && connection.jingle.localAudio)) {
1064
+        preMuted = mute;
1080 1065
         // We still click the button.
1081 1066
         buttonClick("#mute", "icon-microphone icon-mic-disabled");
1082 1067
         return;
1083 1068
     }
1084 1069
 
1070
+    if (forceMuted && !mute) {
1071
+        console.info("Asking focus for unmute");
1072
+        connection.moderate.setMute(connection.emuc.myroomjid, mute);
1073
+        // FIXME: wait for result before resetting muted status
1074
+        forceMuted = false;
1075
+    }
1076
+
1077
+    if (mute == isAudioMuted()) {
1078
+        // Nothing to do
1079
+        return;
1080
+    }
1081
+
1085 1082
     // It is not clear what is the right way to handle multiple tracks.
1086 1083
     // So at least make sure that they are all muted or all unmuted and
1087 1084
     // that we send presence just once.
1088 1085
     var localAudioTracks = connection.jingle.localAudio.getAudioTracks();
1089 1086
     if (localAudioTracks.length > 0) {
1090
-        var audioEnabled = localAudioTracks[0].enabled;
1091
-
1092 1087
         for (var idx = 0; idx < localAudioTracks.length; idx++) {
1093
-            localAudioTracks[idx].enabled = !audioEnabled;
1088
+            localAudioTracks[idx].enabled = !mute;
1094 1089
         }
1095
-
1096
-        // isMuted is the opposite of audioEnabled
1097
-        connection.emuc.addAudioInfoToPresence(audioEnabled);
1098
-        connection.emuc.sendPresence();
1099
-        VideoLayout.showLocalAudioIndicator(audioEnabled);
1100 1090
     }
1091
+    // isMuted is the opposite of audioEnabled
1092
+    connection.emuc.addAudioInfoToPresence(mute);
1093
+    connection.emuc.sendPresence();
1094
+    VideoLayout.showLocalAudioIndicator(mute);
1101 1095
 
1102 1096
     buttonClick("#mute", "icon-microphone icon-mic-disabled");
1103 1097
 }
@@ -1118,51 +1112,7 @@ function isAudioMuted()
1118 1112
 
1119 1113
 // Starts or stops the recording for the conference.
1120 1114
 function toggleRecording() {
1121
-    if (focus === null || focus.confid === null) {
1122
-        console.log('non-focus, or conference not yet organized: not enabling recording');
1123
-        return;
1124
-    }
1125
-
1126
-    if (!recordingToken)
1127
-    {
1128
-        messageHandler.openTwoButtonDialog(null,
1129
-            '<h2>Enter recording token</h2>' +
1130
-                '<input id="recordingToken" type="text" placeholder="token" autofocus>',
1131
-            false,
1132
-            "Save",
1133
-            function (e, v, m, f) {
1134
-                if (v) {
1135
-                    var token = document.getElementById('recordingToken');
1136
-
1137
-                    if (token.value) {
1138
-                        setRecordingToken(Util.escapeHtml(token.value));
1139
-                        toggleRecording();
1140
-                    }
1141
-                }
1142
-            },
1143
-            function (event) {
1144
-                document.getElementById('recordingToken').focus();
1145
-            }
1146
-        );
1147
-
1148
-        return;
1149
-    }
1150
-
1151
-    var oldState = focus.recordingEnabled;
1152
-    Toolbar.toggleRecordingButtonState();
1153
-    focus.setRecording(!oldState,
1154
-                        recordingToken,
1155
-                        function (state) {
1156
-                            console.log("New recording state: ", state);
1157
-                            if (state == oldState) //failed to change, reset the token because it might have been wrong
1158
-                            {
1159
-                                Toolbar.toggleRecordingButtonState();
1160
-                                setRecordingToken(null);
1161
-                            }
1162
-                        }
1163
-    );
1164
-
1165
-
1115
+    Recording.toggleRecording();
1166 1116
 }
1167 1117
 
1168 1118
 /**
@@ -1380,6 +1330,8 @@ $(document).ready(function () {
1380 1330
         }
1381 1331
     });
1382 1332
 
1333
+    Moderator.init();
1334
+
1383 1335
     // Set the defaults for prompt dialogs.
1384 1336
     jQuery.prompt.setDefaults({persistent: false});
1385 1337
 
@@ -1495,7 +1447,6 @@ function disposeConference(onUnload) {
1495 1447
     if(onUnload) {
1496 1448
         stopLocalRtpStatsCollector();
1497 1449
     }
1498
-    focus = null;
1499 1450
     activecall = null;
1500 1451
 }
1501 1452
 
@@ -1562,10 +1513,6 @@ function setSharedKey(sKey) {
1562 1513
     sharedKey = sKey;
1563 1514
 }
1564 1515
 
1565
-function setRecordingToken(token) {
1566
-    recordingToken = token;
1567
-}
1568
-
1569 1516
 /**
1570 1517
  * Updates the room invite url.
1571 1518
  */
@@ -1585,11 +1532,13 @@ function updateRoomUrl(newRoomUrl) {
1585 1532
  * Warning to the user that the conference window is about to be closed.
1586 1533
  */
1587 1534
 function closePageWarning() {
1535
+    /*
1536
+    FIXME: do we need a warning when the focus is a server-side one now ?
1588 1537
     if (focus !== null)
1589 1538
         return "You are the owner of this conference call and"
1590 1539
                 + " you are about to end it.";
1591
-    else
1592
-        return "You are about to leave this conversation.";
1540
+    else*/
1541
+    return "You are about to leave this conversation.";
1593 1542
 }
1594 1543
 
1595 1544
 /**
@@ -1626,6 +1575,13 @@ function setView(viewName) {
1626 1575
 //    }
1627 1576
 }
1628 1577
 
1578
+$(document).bind('error.jingle',
1579
+    function (event, session, error)
1580
+    {
1581
+        console.error("Jingle error", error);
1582
+    }
1583
+);
1584
+
1629 1585
 $(document).bind('fatalError.jingle',
1630 1586
     function (event, session, error)
1631 1587
     {

+ 5
- 2
config.js View File

@@ -4,7 +4,8 @@ var config = {
4 4
         //anonymousdomain: 'guest.example.com',
5 5
         muc: 'conference.jitsi-meet.example.com', // FIXME: use XEP-0030
6 6
         bridge: 'jitsi-videobridge.jitsi-meet.example.com', // FIXME: use XEP-0030
7
-        //call_control: 'callcontrol.jitsi-meet.example.com'
7
+        //call_control: 'callcontrol.jitsi-meet.example.com',
8
+        focus: 'focus.jitsi-meet.example.com'
8 9
     },
9 10
 //  getroomnode: function (path) { return 'someprefixpossiblybasedonpath'; },
10 11
 //  useStunTurn: true, // use XEP-0215 to fetch STUN and TURN server
@@ -12,6 +13,7 @@ var config = {
12 13
     useNicks: false,
13 14
     bosh: '//jitsi-meet.example.com/http-bind', // FIXME: use xep-0156 for that
14 15
     clientNode: 'http://jitsi.org/jitsimeet', // The name of client node advertised in XEP-0115 'c' stanza
16
+    focusUserJid: 'focus@auth.jitsi-meet.example.com', // The real JID of focus participant
15 17
     //defaultSipNumber: '', // Default SIP number
16 18
     desktopSharing: 'ext', // Desktop sharing method. Can be set to 'ext', 'webrtc' or false to disable.
17 19
     chromeExtensionId: 'diibjkoicjeejcmhdnailmkgecihlobk', // Id of desktop streamer Chrome extension
@@ -27,6 +29,7 @@ var config = {
27 29
     enableRecording: false,
28 30
     enableWelcomePage: true,
29 31
     enableSimulcast: false,
30
-    enableFirefoxSupport: false //firefox support is still experimental, only one-to-one conferences with chrome focus
32
+    enableFirefoxSupport: false, //firefox support is still experimental, only one-to-one conferences with chrome focus
31 33
     // will work when simulcast, bundle, mux, lastN and SCTP are disabled.
34
+    logStats: false // Enable logging of PeerConnection stats via the focus
32 35
 };

+ 19
- 0
debian/jitsi-meet-prosody.postinst View File

@@ -23,6 +23,8 @@ case "$1" in
23 23
 
24 24
         . /etc/jitsi/videobridge/config
25 25
 
26
+        . /etc/jitsi/jicofo/config
27
+
26 28
         # loading debconf
27 29
         . /usr/share/debconf/confmodule
28 30
 
@@ -41,9 +43,25 @@ case "$1" in
41 43
             cp /usr/share/doc/jitsi-meet-prosody/prosody.cfg.lua-jvb.example $PROSODY_HOST_CONFIG
42 44
             sed -i "s/jitmeet.example.com/$JVB_HOSTNAME/g" $PROSODY_HOST_CONFIG
43 45
             sed -i "s/jitmeetSecret/$JVB_SECRET/g" $PROSODY_HOST_CONFIG
46
+            sed -i "s/focusSecret/$JICOFO_SECRET/g" $PROSODY_HOST_CONFIG
47
+            sed -i "s/focusUser/$JICOFO_AUTH_USER/g" $PROSODY_HOST_CONFIG
44 48
             if [ ! -f /etc/prosody/conf.d/$JVB_HOSTNAME.cfg.lua ]; then
45 49
                 ln -s $PROSODY_HOST_CONFIG /etc/prosody/conf.d/$JVB_HOSTNAME.cfg.lua
46 50
             fi
51
+            # create 'focus@auth.domain' prosody user
52
+            # FIXME this duplicates with below
53
+            prosodyctl register $JICOFO_AUTH_USER $JICOFO_AUTH_DOMAIN $JICOFO_AUTH_PASSWORD
54
+        fi
55
+        # on UPGRADE to server side focus check if focus is configured
56
+        if [ -f $PROSODY_HOST_CONFIG ] && ! grep -q "VirtualHost \"auth.$JVB_HOSTNAME\"" $PROSODY_HOST_CONFIG; then
57
+            echo -e "\nVirtualHost \"auth.$JVB_HOSTNAME\"" >> $PROSODY_HOST_CONFIG
58
+            echo -e "        authentication = \"internal_plain\"\n" >> $PROSODY_HOST_CONFIG
59
+            echo -e "admins = { \"$JICOFO_AUTH_USER@auth.$JVB_HOSTNAME\" }\n" >> $PROSODY_HOST_CONFIG
60
+            echo -e "Component \"focus.$JVB_HOSTNAME\"" >> $PROSODY_HOST_CONFIG
61
+            echo -e "    component_secret=\"$JICOFO_SECRET\"\n" >> $PROSODY_HOST_CONFIG
62
+            # create 'focus@auth.domain' prosody user
63
+            # FIXME this duplicates with above
64
+            prosodyctl register $JICOFO_AUTH_USER $JICOFO_AUTH_DOMAIN $JICOFO_AUTH_PASSWORD
47 65
         fi
48 66
 
49 67
         if [ ! -f /var/lib/prosody/$JVB_HOSTNAME.crt ]; then
@@ -60,6 +78,7 @@ case "$1" in
60 78
         if [ "$PROSODY_CONFIG_PRESENT" = "false" ]; then
61 79
             invoke-rc.d prosody restart
62 80
             invoke-rc.d jitsi-videobridge restart
81
+            invoke-rc.d jicofo restart
63 82
         fi
64 83
     ;;
65 84
 

+ 2
- 2
desktopsharing.js View File

@@ -1,4 +1,4 @@
1
-/* global $, config, connection, chrome, alert, getUserMediaWithConstraints, changeLocalVideo, getConferenceHandler */
1
+/* global $, alert, changeLocalVideo, chrome, config, connection, getConferenceHandler, getUserMediaWithConstraints, VideoLayout */
2 2
 /**
3 3
  * Indicates that desktop stream is currently in use(for toggle purpose).
4 4
  * @type {boolean}
@@ -283,9 +283,9 @@ function toggleScreenSharing() {
283 283
     }
284 284
     switchInProgress = true;
285 285
 
286
-    // Only the focus is able to set a shared key.
287 286
     if (!isUsingScreenStream)
288 287
     {
288
+        // Switch to desktop stream
289 289
         obtainDesktopStream(
290 290
             function (stream) {
291 291
                 // We now use screen stream

+ 8
- 0
doc/debian/jitsi-meet-prosody/prosody.cfg.lua-jvb.example View File

@@ -19,3 +19,11 @@ Component "conference.jitmeet.example.com" "muc"
19 19
 
20 20
 Component "jitsi-videobridge.jitmeet.example.com"
21 21
     component_secret = "jitmeetSecret"
22
+
23
+VirtualHost "auth.jitmeet.example.com"
24
+        authentication = "internal_plain"
25
+
26
+admins = { "focusUser@auth.jitmeet.example.com" }
27
+
28
+Component "focus.jitmeet.example.com"
29
+    component_secret = "focusSecret"

+ 4
- 2
etherpad.js View File

@@ -1,4 +1,5 @@
1
-/* global $, config, Prezi, Util, connection, setLargeVideoVisible, dockToolbar */
1
+/* global $, config, connection, dockToolbar, Moderator, Prezi,
2
+   setLargeVideoVisible, ToolbarToggler, Util, VideoLayout */
2 3
 var Etherpad = (function (my) {
3 4
     var etherpadName = null;
4 5
     var etherpadIFrame = null;
@@ -161,7 +162,7 @@ var Etherpad = (function (my) {
161 162
      */
162 163
     $(document).bind('etherpadadded.muc', function (event, jid, etherpadName) {
163 164
         console.log("Etherpad added", etherpadName);
164
-        if (config.etherpad_base && !focus) {
165
+        if (config.etherpad_base && !Moderator.isModerator()) {
165 166
             Etherpad.init(etherpadName);
166 167
         }
167 168
     });
@@ -169,6 +170,7 @@ var Etherpad = (function (my) {
169 170
     /**
170 171
      * On focus changed event.
171 172
      */
173
+    // FIXME: there is no such event as 'focusechanged.muc'
172 174
     $(document).bind('focusechanged.muc', function (event, focus) {
173 175
         console.log("Focus changed");
174 176
         if (config.etherpad_base)

+ 3
- 0
index.html View File

@@ -28,6 +28,7 @@
28 28
     <script src="libs/rayo.js?v=1"></script>
29 29
     <script src="libs/tooltip.js?v=1"></script><!-- bootstrap tooltip lib -->
30 30
     <script src="libs/popover.js?v=1"></script><!-- bootstrap tooltip lib -->
31
+    <script src="libs/pako.bundle.js?v=1"></script><!-- zlib deflate -->
31 32
     <script src="libs/toastr.js?v=1"></script><!-- notifications lib -->
32 33
     <script src="interface_config.js?v=4"></script>
33 34
     <script src="muc.js?v=17"></script><!-- simple MUC library -->
@@ -56,8 +57,10 @@
56 57
     <script src="audio_levels.js?v=2"></script><!-- audio levels plugin -->
57 58
     <script src="media_stream.js?v=2"></script><!-- media stream -->
58 59
     <script src="bottom_toolbar.js?v=6"></script><!-- media stream -->
60
+    <script src="moderator.js?v=1"></script><!-- media stream -->
59 61
     <script src="roomname_generator.js?v=1"></script><!-- generator for random room names -->
60 62
     <script src="keyboard_shortcut.js?v=3"></script>
63
+    <script src="recording.js?v=1"></script>
61 64
     <script src="tracking.js?v=1"></script><!-- tracking -->
62 65
     <script src="jitsipopover.js?v=3"></script>
63 66
     <script src="message_handler.js?v=2"></script>

+ 3747
- 0
libs/pako.bundle.js
File diff suppressed because it is too large
View File


+ 9
- 0
libs/strophe/strophe.jingle.adapter.js View File

@@ -372,6 +372,15 @@ TraceablePeerConnection.prototype.modifySources = function(successCallback) {
372 372
     });
373 373
     this.removessrc = [];
374 374
 
375
+    // FIXME:
376
+    // this was a hack for the situation when only one peer exists
377
+    // in the conference.
378
+    // check if still required and remove
379
+    if (sdp.media[0])
380
+        sdp.media[0] = sdp.media[0].replace('a=recvonly', 'a=sendrecv');
381
+    if (sdp.media[1])
382
+        sdp.media[1] = sdp.media[1].replace('a=recvonly', 'a=sendrecv');
383
+
375 384
     sdp.raw = sdp.session + sdp.media.join('');
376 385
     this.setRemoteDescription(new RTCSessionDescription({type: 'offer', sdp: sdp.raw}),
377 386
         function() {

+ 6
- 2
libs/strophe/strophe.jingle.js View File

@@ -30,8 +30,12 @@ Strophe.addConnectionPlugin('jingle', {
30 30
             // this is dealt with by SDP O/A so we don't need to annouce this
31 31
             //this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:rtcp-fb:0'); // XEP-0293
32 32
             //this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:rtp-hdrext:0'); // XEP-0294
33
-            this.connection.disco.addFeature('urn:ietf:rfc:5761'); // rtcp-mux
34
-            //this.connection.disco.addFeature('urn:ietf:rfc:5888'); // a=group, e.g. bundle
33
+            if (config.useRtcpMux) {
34
+                this.connection.disco.addFeature('urn:ietf:rfc:5761'); // rtcp-mux
35
+            }
36
+            if (config.useBundle) {
37
+                this.connection.disco.addFeature('urn:ietf:rfc:5888'); // a=group, e.g. bundle
38
+            }
35 39
             //this.connection.disco.addFeature('urn:ietf:rfc:5576'); // a=ssrc
36 40
         }
37 41
         this.connection.addHandler(this.onJingle.bind(this), 'urn:xmpp:jingle:1', 'iq', 'set', null, null);

+ 16
- 16
libs/strophe/strophe.jingle.session.js View File

@@ -131,22 +131,6 @@ JingleSession.prototype.accept = function () {
131 131
             responder: this.responder,
132 132
             sid: this.sid });
133 133
     prsdp.toJingle(accept, this.initiator == this.me ? 'initiator' : 'responder', this.localStreamsSSRC);
134
-    this.connection.sendIQ(accept,
135
-        function () {
136
-            var ack = {};
137
-            ack.source = 'answer';
138
-            $(document).trigger('ack.jingle', [self.sid, ack]);
139
-        },
140
-        function (stanza) {
141
-            var error = ($(stanza).find('error').length) ? {
142
-                code: $(stanza).find('error').attr('code'),
143
-                reason: $(stanza).find('error :first')[0].tagName,
144
-            }:{};
145
-            error.source = 'answer';
146
-            $(document).trigger('error.jingle', [self.sid, error]);
147
-        },
148
-        10000);
149
-
150 134
     var sdp = this.peerconnection.localDescription.sdp;
151 135
     while (SDPUtil.find_line(sdp, 'a=inactive')) {
152 136
         // FIXME: change any inactive to sendrecv or whatever they were originally
@@ -156,6 +140,22 @@ JingleSession.prototype.accept = function () {
156 140
         function () {
157 141
             //console.log('setLocalDescription success');
158 142
             $(document).trigger('setLocalDescription.jingle', [self.sid]);
143
+
144
+            this.connection.sendIQ(accept,
145
+                function () {
146
+                    var ack = {};
147
+                    ack.source = 'answer';
148
+                    $(document).trigger('ack.jingle', [self.sid, ack]);
149
+                },
150
+                function (stanza) {
151
+                    var error = ($(stanza).find('error').length) ? {
152
+                        code: $(stanza).find('error').attr('code'),
153
+                        reason: $(stanza).find('error :first')[0].tagName
154
+                    }:{};
155
+                    error.source = 'answer';
156
+                    $(document).trigger('error.jingle', [self.sid, error]);
157
+                },
158
+                10000);
159 159
         },
160 160
         function (e) {
161 161
             console.error('setLocalDescription failed', e);

+ 2
- 2
libs/strophe/strophe.jingle.sessionbase.js View File

@@ -105,11 +105,11 @@ SessionBase.prototype.switchStreams = function (new_stream, oldStream, success_c
105 105
     self.modifySources(function() {
106 106
         console.log('modify sources done');
107 107
 
108
+        success_callback();
109
+
108 110
         var newSdp = new SDP(self.peerconnection.localDescription.sdp);
109 111
         console.log("SDPs", oldSdp, newSdp);
110 112
         self.notifyMySSRCUpdate(oldSdp, newSdp);
111
-
112
-        success_callback();
113 113
     });
114 114
 };
115 115
 

+ 40
- 33
moderatemuc.js View File

@@ -1,53 +1,60 @@
1
+/* global $, $iq, config, connection, focusJid, forceMuted, messageHandler,
2
+   setAudioMuted, Strophe, toggleAudio */
1 3
 /**
2 4
  * Moderate connection plugin.
3 5
  */
4 6
 Strophe.addConnectionPlugin('moderate', {
5 7
     connection: null,
6
-    roomjid: null,
7
-    myroomjid: null,
8
-    members: {},
9
-    list_members: [], // so we can elect a new focus
10
-    presMap: {},
11
-    preziMap: {},
12
-    joined: false,
13
-    isOwner: false,
14 8
     init: function (conn) {
15 9
         this.connection = conn;
16 10
 
17
-        this.connection.addHandler( this.onMute.bind(this),
18
-                                    'http://jitsi.org/jitmeet/audio',
19
-                                    'iq',
20
-                                    'set',
21
-                                    null,
22
-                                    null);
11
+        this.connection.addHandler(this.onMute.bind(this),
12
+                                   'http://jitsi.org/jitmeet/audio',
13
+                                   'iq',
14
+                                   'set',
15
+                                   null,
16
+                                   null);
23 17
     },
24
-    setMute: function(jid, mute) {
25
-        var iq = $iq({to: jid, type: 'set'})
26
-                    .c('mute', {xmlns: 'http://jitsi.org/jitmeet/audio'})
27
-                    .t(mute.toString())
28
-                    .up();
18
+    setMute: function (jid, mute) {
19
+        console.info("set mute", mute);
20
+        var iqToFocus = $iq({to: focusJid, type: 'set'})
21
+            .c('mute', {
22
+                xmlns: 'http://jitsi.org/jitmeet/audio',
23
+                jid: jid
24
+            })
25
+            .t(mute.toString())
26
+            .up();
29 27
 
30 28
         this.connection.sendIQ(
31
-                iq,
32
-                function (result) {
33
-                    console.log('set mute', result);
34
-                },
35
-                function (error) {
36
-                    console.log('set mute error', error);
37
-                    messageHandler.openReportDialog(null, 'Failed to mute ' +
38
-                        $("#participant_" + jid).find(".displayname").text() ||
39
-                        "participant" + '.', error);
40
-                });
29
+            iqToFocus,
30
+            function (result) {
31
+                console.log('set mute', result);
32
+            },
33
+            function (error) {
34
+                console.log('set mute error', error);
35
+                // FIXME: this causes an exception
36
+                //messageHandler.openReportDialog(null, 'Failed to mute ' +
37
+                  //  $("#participant_" + jid).find(".displayname").text() ||
38
+                    //"participant" + '.', error);
39
+            });
41 40
     },
42
-    onMute: function(iq) {
41
+    onMute: function (iq) {
42
+        var from = iq.getAttribute('from');
43
+        if (from !== focusJid) {
44
+            console.warn("Ignored mute from non focus peer");
45
+            return false;
46
+        }
43 47
         var mute = $(iq).find('mute');
44 48
         if (mute.length) {
45
-            toggleAudio();
49
+            var doMuteAudio = mute.text() === "true";
50
+            setAudioMuted(doMuteAudio);
51
+            forceMuted = doMuteAudio;
46 52
         }
47 53
         return true;
48 54
     },
49
-    eject: function(jid) {
50
-        connection.jingle.terminateRemoteByJid(jid, 'kick');
55
+    eject: function (jid) {
56
+        // We're not the focus, so can't terminate
57
+        //connection.jingle.terminateRemoteByJid(jid, 'kick');
51 58
         connection.emuc.kick(jid);
52 59
     }
53 60
 });

+ 141
- 0
moderator.js View File

@@ -0,0 +1,141 @@
1
+/* global $, $iq, config, connection, Etherpad, hangUp, roomName, Strophe,
2
+ Toolbar, Util, VideoLayout */
3
+/**
4
+ * Contains logic responsible for enabling/disabling functionality available
5
+ * only to moderator users.
6
+ */
7
+var Moderator = (function (my) {
8
+
9
+    var getNextTimeout = Util.createExpBackoffTimer(1000);
10
+    var getNextErrorTimeout = Util.createExpBackoffTimer(1000);
11
+
12
+    my.isModerator = function () {
13
+        return connection.emuc.isModerator();
14
+    };
15
+
16
+    my.onModeratorStatusChanged = function (isModerator) {
17
+
18
+        Toolbar.showSipCallButton(isModerator);
19
+        Toolbar.showRecordingButton(
20
+                isModerator); //&&
21
+                // FIXME:
22
+                // Recording visible if
23
+                // there are at least 2(+ 1 focus) participants
24
+                //Object.keys(connection.emuc.members).length >= 3);
25
+
26
+        if (isModerator && config.etherpad_base) {
27
+            Etherpad.init();
28
+        }
29
+
30
+        $(document).trigger('local.role.moderator', [isModerator]);
31
+    };
32
+
33
+    my.init = function () {
34
+        $(document).bind(
35
+            'role.changed.muc',
36
+            function (event, jid, info, pres) {
37
+                console.info(
38
+                    "Role changed for " + jid + ", new role: " + info.role);
39
+                VideoLayout.showModeratorIndicator();
40
+            }
41
+        );
42
+
43
+        $(document).bind(
44
+            'local.role.changed.muc',
45
+            function (event, jid, info, pres) {
46
+                console.info("My role changed, new role: " + info.role);
47
+                VideoLayout.showModeratorIndicator();
48
+                Moderator.onModeratorStatusChanged(Moderator.isModerator());
49
+            }
50
+        );
51
+
52
+        $(document).bind(
53
+            'left.muc',
54
+            function (event, jid) {
55
+                console.info("Someone left is it focus ? " + jid);
56
+                var resource = Strophe.getResourceFromJid(jid);
57
+                if (resource === 'focus') {
58
+                    console.info(
59
+                        "Focus has left the room - leaving conference");
60
+                    //hangUp();
61
+                    // We'd rather reload to have everything re-initialized
62
+                    // FIXME: show some message before reload
63
+                    location.reload();
64
+                }
65
+            }
66
+        );
67
+    };
68
+
69
+    my.createConferenceIq = function () {
70
+        var elem = $iq({to: config.hosts.focus, type: 'set'});
71
+        elem.c('conference', {
72
+            xmlns: 'http://jitsi.org/protocol/focus',
73
+            room: roomName
74
+        });
75
+        if (config.channelLastN !== undefined)
76
+        {
77
+            elem.c(
78
+                'property',
79
+                { name: 'channelLastN', value: config.channelLastN})
80
+                .up();
81
+        }
82
+        if (config.adaptiveLastN !== undefined)
83
+        {
84
+            elem.c(
85
+                'property',
86
+                { name: 'adaptiveLastN', value: config.adaptiveLastN})
87
+                .up();
88
+        }
89
+        if (config.adaptiveSimulcast !== undefined)
90
+        {
91
+            elem.c(
92
+                'property',
93
+                { name: 'adaptiveSimulcast', value: config.adaptiveSimulcast})
94
+                .up();
95
+        }
96
+        elem.up();
97
+        return elem;
98
+    };
99
+
100
+    // FIXME: we need to show the fact that we're waiting for the focus
101
+    // to the user(or that focus is not available)
102
+    my.allocateConferenceFocus = function (roomName, callback) {
103
+        var iq = Moderator.createConferenceIq();
104
+        connection.sendIQ(
105
+            iq,
106
+            function (result) {
107
+                if ('true' === $(result).find('conference').attr('ready')) {
108
+                    // Reset both timers
109
+                    getNextTimeout(true);
110
+                    getNextErrorTimeout(true);
111
+                    callback();
112
+                } else {
113
+                    var waitMs = getNextTimeout();
114
+                    console.info("Waiting for the focus... " + waitMs);
115
+                    // Reset error timeout
116
+                    getNextErrorTimeout(true);
117
+                    window.setTimeout(
118
+                        function () {
119
+                            Moderator.allocateConferenceFocus(
120
+                                roomName, callback);
121
+                        }, waitMs);
122
+                }
123
+            },
124
+            function (error) {
125
+                var waitMs = getNextErrorTimeout();
126
+                console.error("Focus error, retry after " + waitMs, error);
127
+                // Reset response timeout
128
+                getNextTimeout(true);
129
+                window.setTimeout(
130
+                    function () {
131
+                        Moderator.allocateConferenceFocus(roomName, callback);
132
+                    }, waitMs);
133
+            }
134
+        );
135
+    };
136
+
137
+    return my;
138
+}(Moderator || {}));
139
+
140
+
141
+

+ 26
- 1
muc.js View File

@@ -12,6 +12,7 @@ Strophe.addConnectionPlugin('emuc', {
12 12
     preziMap: {},
13 13
     joined: false,
14 14
     isOwner: false,
15
+    role: null,
15 16
     init: function (conn) {
16 17
         this.connection = conn;
17 18
     },
@@ -122,11 +123,22 @@ Strophe.addConnectionPlugin('emuc', {
122 123
         member.affiliation = tmp.attr('affiliation');
123 124
         member.role = tmp.attr('role');
124 125
 
126
+        // Focus recognition
127
+        member.jid = tmp.attr('jid');
128
+        member.isFocus = false;
129
+        if (member.jid && member.jid.indexOf(config.focusUserJid + "/") == 0) {
130
+            member.isFocus = true;
131
+        }
132
+
125 133
         var nicktag = $(pres).find('>nick[xmlns="http://jabber.org/protocol/nick"]');
126 134
         member.displayName = (nicktag.length > 0 ? nicktag.text() : null);
127 135
 
128 136
         if (from == this.myroomjid) {
129 137
             if (member.affiliation == 'owner') this.isOwner = true;
138
+            if (this.role !== member.role) {
139
+                this.role = member.role;
140
+                $(document).trigger('local.role.changed.muc', [from, member, pres]);
141
+            }
130 142
             if (!this.joined) {
131 143
                 this.joined = true;
132 144
                 $(document).trigger('joined.muc', [from, member]);
@@ -137,9 +149,16 @@ Strophe.addConnectionPlugin('emuc', {
137 149
             this.members[from] = member;
138 150
             this.list_members.push(from);
139 151
             $(document).trigger('entered.muc', [from, member, pres]);
152
+        } else {
153
+            // Presence update for existing participant
154
+            // Watch role change:
155
+            if (this.members[from].role != member.role) {
156
+                this.members[from].role = member.role
157
+                $(document).trigger('role.changed.muc', [from, member, pres]);
158
+            }
140 159
         }
141 160
         // Always trigger presence to update bindings
142
-        console.log('presence change from', from);
161
+        console.log('presence change from', from, pres);
143 162
         $(document).trigger('presence.muc', [from, member, pres]);
144 163
 
145 164
         // Trigger status message update
@@ -167,6 +186,9 @@ Strophe.addConnectionPlugin('emuc', {
167 186
                 $(document).trigger('left.muc', member);
168 187
             }
169 188
         }
189
+        if ($(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]>status[code="307"]').length) {
190
+            $(document).trigger('kicked.muc', [from]);
191
+        }
170 192
         return true;
171 193
     },
172 194
     onPresenceError: function (pres) {
@@ -470,5 +492,8 @@ Strophe.addConnectionPlugin('emuc', {
470 492
     },
471 493
     addUserIdToPresence: function(userId) {
472 494
         this.presMap['userId'] = userId;
495
+    },
496
+    isModerator: function() {
497
+        return this.role === 'moderator';
473 498
     }
474 499
 });

+ 108
- 0
recording.js View File

@@ -0,0 +1,108 @@
1
+/* global $, $iq, config, connection, focusJid, messageHandler, Moderator,
2
+   Toolbar, Util */
3
+var Recording = (function (my) {
4
+    var recordingToken = null;
5
+    var recordingEnabled;
6
+
7
+    my.setRecordingToken = function (token) {
8
+        recordingToken = token;
9
+    };
10
+
11
+    // Sends a COLIBRI message which enables or disables (according to 'state')
12
+    // the recording on the bridge. Waits for the result IQ and calls 'callback'
13
+    // with the new recording state, according to the IQ.
14
+    my.setRecording = function (state, token, callback) {
15
+        var self = this;
16
+        var elem = $iq({to: focusJid, type: 'set'});
17
+        elem.c('conference', {
18
+            xmlns: 'http://jitsi.org/protocol/colibri'
19
+        });
20
+        elem.c('recording', {state: state, token: token});
21
+        elem.up();
22
+
23
+        connection.sendIQ(elem,
24
+            function (result) {
25
+                console.log('Set recording "', state, '". Result:', result);
26
+                var recordingElem = $(result).find('>conference>recording');
27
+                var newState = ('true' === recordingElem.attr('state'));
28
+
29
+                recordingEnabled = newState;
30
+                callback(newState);
31
+            },
32
+            function (error) {
33
+                console.warn(error);
34
+            }
35
+        );
36
+    };
37
+
38
+    my.toggleRecording = function () {
39
+        if (!Moderator.isModerator()) {
40
+            console.log(
41
+                'non-focus, or conference not yet organized:' +
42
+                ' not enabling recording');
43
+            return;
44
+        }
45
+
46
+        if (!recordingToken)
47
+        {
48
+            messageHandler.openTwoButtonDialog(null,
49
+                    '<h2>Enter recording token</h2>' +
50
+                    '<input id="recordingToken" type="text" placeholder="token" autofocus>',
51
+                false,
52
+                "Save",
53
+                function (e, v, m, f) {
54
+                    if (v) {
55
+                        var token = document.getElementById('recordingToken');
56
+
57
+                        if (token.value) {
58
+                            my.setRecordingToken(
59
+                                Util.escapeHtml(token.value));
60
+                            my.toggleRecording();
61
+                        }
62
+                    }
63
+                },
64
+                function (event) {
65
+                    document.getElementById('recordingToken').focus();
66
+                }
67
+            );
68
+
69
+            return;
70
+        }
71
+
72
+        var oldState = recordingEnabled;
73
+        Toolbar.setRecordingButtonState(!oldState);
74
+        my.setRecording(!oldState,
75
+            recordingToken,
76
+            function (state) {
77
+                console.log("New recording state: ", state);
78
+                if (state === oldState)
79
+                {
80
+                    // FIXME: new focus:
81
+                    // this will not work when moderator changes
82
+                    // during active session. Then it will assume that
83
+                    // recording status has changed to true, but it might have
84
+                    // been already true(and we only received actual status from
85
+                    // the focus).
86
+                    //
87
+                    // SO we start with status null, so that it is initialized
88
+                    // here and will fail only after second click, so if invalid
89
+                    // token was used we have to press the button twice before
90
+                    // current status will be fetched and token will be reset.
91
+                    //
92
+                    // Reliable way would be to return authentication error.
93
+                    // Or status update when moderator connects.
94
+                    // Or we have to stop recording session when current
95
+                    // moderator leaves the room.
96
+
97
+                    // Failed to change, reset the token because it might
98
+                    // have been wrong
99
+                    my.setRecordingToken(null);
100
+                }
101
+                // Update with returned status
102
+                Toolbar.setRecordingButtonState(state);
103
+            }
104
+        );
105
+    };
106
+
107
+    return my;
108
+}(Recording || {}));

+ 112
- 0
rtp_sts.js View File

@@ -1,4 +1,5 @@
1 1
 /* global ssrc2jid */
2
+/* jshint -W117 */
2 3
 /**
3 4
  * Calculates packet lost percent using the number of lost packets and the
4 5
  * number of all packet.
@@ -133,6 +134,37 @@ function StatsCollector(peerconnection, audioLevelsInterval,
133 134
     this.currentStatsReport = null;
134 135
     this.baselineStatsReport = null;
135 136
     this.audioLevelsIntervalId = null;
137
+
138
+    /**
139
+     * Gather PeerConnection stats once every this many milliseconds.
140
+     */
141
+    this.GATHER_INTERVAL = 10000;
142
+
143
+    /**
144
+     * Log stats via the focus once every this many milliseconds.
145
+     */
146
+    this.LOG_INTERVAL = 60000;
147
+
148
+    /**
149
+     * Gather stats and store them in this.statsToBeLogged.
150
+     */
151
+    this.gatherStatsIntervalId = null;
152
+
153
+    /**
154
+     * Send the stats already saved in this.statsToBeLogged to be logged via
155
+     * the focus.
156
+     */
157
+    this.logStatsIntervalId = null;
158
+
159
+    /**
160
+     * Stores the statistics which will be send to the focus to be logged.
161
+     */
162
+    this.statsToBeLogged =
163
+    {
164
+      timestamps: [],
165
+      stats: {}
166
+    };
167
+
136 168
     // Updates stats interval
137 169
     this.audioLevelsIntervalMilis = audioLevelsInterval;
138 170
 
@@ -156,6 +188,10 @@ StatsCollector.prototype.stop = function ()
156 188
         this.audioLevelsIntervalId = null;
157 189
         clearInterval(this.statsIntervalId);
158 190
         this.statsIntervalId = null;
191
+        clearInterval(this.logStatsIntervalId);
192
+        this.logStatsIntervalId = null;
193
+        clearInterval(this.gatherStatsIntervalId);
194
+        this.gatherStatsIntervalId = null;
159 195
     }
160 196
 };
161 197
 
@@ -238,8 +274,84 @@ StatsCollector.prototype.start = function ()
238 274
         },
239 275
         self.statsIntervalMilis
240 276
     );
277
+
278
+    if (config.logStats) {
279
+        this.gatherStatsIntervalId = setInterval(
280
+            function () {
281
+                self.peerconnection.getStats(
282
+                    function (report) {
283
+                        self.addStatsToBeLogged(report.result());
284
+                    },
285
+                    function () {
286
+                    }
287
+                );
288
+            },
289
+            this.GATHER_INTERVAL
290
+        );
291
+
292
+        this.logStatsIntervalId = setInterval(
293
+            function() { self.logStats(); },
294
+            this.LOG_INTERVAL);
295
+    }
241 296
 };
242 297
 
298
+/**
299
+ * Converts the stats to the format used for logging, and saves the data in
300
+ * this.statsToBeLogged.
301
+ * @param reports Reports as given by webkitRTCPerConnection.getStats.
302
+ */
303
+StatsCollector.prototype.addStatsToBeLogged = function (reports) {
304
+    var self = this;
305
+    var num_records = this.statsToBeLogged.timestamps.length;
306
+    this.statsToBeLogged.timestamps.push(new Date().getTime());
307
+    reports.map(function (report) {
308
+        var stat = self.statsToBeLogged.stats[report.id];
309
+        if (!stat) {
310
+            stat = self.statsToBeLogged.stats[report.id] = {};
311
+        }
312
+        stat.type = report.type;
313
+        report.names().map(function (name) {
314
+            var values = stat[name];
315
+            if (!values) {
316
+                values = stat[name] = [];
317
+            }
318
+            while (values.length < num_records) {
319
+                values.push(null);
320
+            }
321
+            values.push(report.stat(name));
322
+        });
323
+    });
324
+};
325
+
326
+StatsCollector.prototype.logStats = function () {
327
+    if (!focusJid) {
328
+        return;
329
+    }
330
+
331
+    var deflate = true;
332
+
333
+    var content = JSON.stringify(this.statsToBeLogged);
334
+    if (deflate) {
335
+        content = String.fromCharCode.apply(null, Pako.deflateRaw(content));
336
+    }
337
+    content = Base64.encode(content);
338
+
339
+    // XEP-0337-ish
340
+    var message = $msg({to: focusJid, type: 'normal'});
341
+    message.c('log', { xmlns: 'urn:xmpp:eventlog',
342
+                       id: 'PeerConnectionStats'});
343
+    message.c('message').t(content).up();
344
+    if (deflate) {
345
+        message.c('tag', {name: "deflated", value: "true"}).up();
346
+    }
347
+    message.up();
348
+
349
+    connection.send(message);
350
+
351
+    // Reset the stats
352
+    this.statsToBeLogged.stats = {};
353
+    this.statsToBeLogged.timestamps = [];
354
+};
243 355
 var keyMap = {
244 356
     "firefox": {
245 357
         "ssrc": "ssrc",

+ 28
- 18
toolbar.js View File

@@ -1,21 +1,23 @@
1
+/* global $, buttonClick, config, lockRoom, messageHandler, Moderator, roomUrl,
2
+   setSharedKey, sharedKey, Util */
1 3
 var Toolbar = (function (my) {
2 4
 
3 5
     /**
4 6
      * Disables and enables some of the buttons.
5 7
      */
6 8
     my.setupButtonsFromConfig = function () {
7
-        if(config.disablePrezi)
9
+        if (config.disablePrezi)
8 10
         {
9 11
             $("#prezi_button").css({display: "none"});
10 12
         }
11
-    }
13
+    };
12 14
 
13 15
     /**
14 16
      * Opens the lock room dialog.
15 17
      */
16 18
     my.openLockDialog = function () {
17 19
         // Only the focus is able to set a shared key.
18
-        if (focus === null) {
20
+        if (Moderator.isModerator()) {
19 21
             if (sharedKey) {
20 22
                 messageHandler.openMessageDialog(null,
21 23
                         "This conversation is currently protected by" +
@@ -73,7 +75,7 @@ var Toolbar = (function (my) {
73 75
      */
74 76
     my.openLinkDialog = function () {
75 77
         var inviteLink;
76
-        if (roomUrl == null) {
78
+        if (roomUrl === null) {
77 79
             inviteLink = "Your conference is currently being created...";
78 80
         } else {
79 81
             inviteLink = encodeURI(roomUrl);
@@ -106,7 +108,7 @@ var Toolbar = (function (my) {
106 108
      * Invite participants to conference.
107 109
      */
108 110
     function inviteParticipants() {
109
-        if (roomUrl == null)
111
+        if (roomUrl === null)
110 112
             return;
111 113
 
112 114
         var sharedKeyText = "";
@@ -126,7 +128,8 @@ var Toolbar = (function (my) {
126 128
                     roomUrl +
127 129
                     "%0D%0A%0D%0A" +
128 130
                     sharedKeyText +
129
-                    "Note that Jitsi Meet is currently only supported by Chromium," +
131
+                    "Note that Jitsi Meet is currently" +
132
+                    " only supported by Chromium," +
130 133
                     " Google Chrome and Opera, so you need" +
131 134
                     " to be using one of these browsers.%0D%0A%0D%0A" +
132 135
                     "Talk to you in a sec!";
@@ -183,7 +186,7 @@ var Toolbar = (function (my) {
183 186
      * Toggles the application in and out of full screen mode
184 187
      * (a.k.a. presentation mode in Chrome).
185 188
      */
186
-    my.toggleFullScreen = function() {
189
+    my.toggleFullScreen = function () {
187 190
         var fsElement = document.documentElement;
188 191
 
189 192
         if (!document.mozFullScreen && !document.webkitIsFullScreen) {
@@ -206,15 +209,15 @@ var Toolbar = (function (my) {
206 209
     /**
207 210
      * Unlocks the lock button state.
208 211
      */
209
-    my.unlockLockButton = function() {
210
-        if($("#lockIcon").hasClass("icon-security-locked"))
212
+    my.unlockLockButton = function () {
213
+        if ($("#lockIcon").hasClass("icon-security-locked"))
211 214
             buttonClick("#lockIcon", "icon-security icon-security-locked");
212 215
     };
213 216
     /**
214 217
      * Updates the lock button state to locked.
215 218
      */
216
-    my.lockLockButton = function() {
217
-        if($("#lockIcon").hasClass("icon-security"))
219
+    my.lockLockButton = function () {
220
+        if ($("#lockIcon").hasClass("icon-security"))
218 221
             buttonClick("#lockIcon", "icon-security icon-security-locked");
219 222
     };
220 223
 
@@ -232,13 +235,19 @@ var Toolbar = (function (my) {
232 235
         }
233 236
     };
234 237
 
235
-    // Toggle the state of the recording button
236
-    my.toggleRecordingButtonState = function() {
237
-        $('#recordButton').toggleClass('active');
238
+    // Sets the state of the recording button
239
+    my.setRecordingButtonState = function (isRecording) {
240
+        if (isRecording) {
241
+            $('#recordButton').removeClass("icon-recEnable");
242
+            $('#recordButton').addClass("icon-recEnable active");
243
+        } else {
244
+            $('#recordButton').removeClass("icon-recEnable active");
245
+            $('#recordButton').addClass("icon-recEnable");
246
+        }
238 247
     };
239 248
 
240 249
     // Shows or hides SIP calls button
241
-    my.showSipCallButton = function(show){
250
+    my.showSipCallButton = function (show) {
242 251
         if (config.hosts.call_control && show) {
243 252
             $('#sipCallButton').css({display: "inline"});
244 253
         } else {
@@ -247,12 +256,13 @@ var Toolbar = (function (my) {
247 256
     };
248 257
 
249 258
     /**
250
-     * Sets the state of the button. The button has blue glow if desktop streaming is active.
259
+     * Sets the state of the button. The button has blue glow if desktop
260
+     * streaming is active.
251 261
      * @param active the state of the desktop streaming.
252 262
      */
253 263
     my.changeDesktopSharingButtonState = function (active) {
254 264
         var button = $("#desktopsharing > a");
255
-        if(active)
265
+        if (active)
256 266
         {
257 267
             button.addClass("glow");
258 268
         }
@@ -260,7 +270,7 @@ var Toolbar = (function (my) {
260 270
         {
261 271
             button.removeClass("glow");
262 272
         }
263
-    }
273
+    };
264 274
 
265 275
     return my;
266 276
 }(Toolbar || {}));

+ 15
- 11
toolbar_toggler.js View File

@@ -1,18 +1,20 @@
1
-var ToolbarToggler = (function(my) {
1
+/* global $, interfaceConfig, Moderator, showDesktopSharingButton */
2
+var ToolbarToggler = (function (my) {
2 3
     var toolbarTimeoutObject,
3 4
         toolbarTimeout = interfaceConfig.INITIAL_TOOLBAR_TIMEOUT;
4 5
 
5 6
     /**
6 7
      * Shows the main toolbar.
7 8
      */
8
-    my.showToolbar = function() {
9
+    my.showToolbar = function () {
9 10
         var header = $("#header"),
10 11
             bottomToolbar = $("#bottomToolbar");
11 12
         if (!header.is(':visible') || !bottomToolbar.is(":visible")) {
12 13
             header.show("slide", { direction: "up", duration: 300});
13 14
             $('#subject').animate({top: "+=40"}, 300);
14
-            if(!bottomToolbar.is(":visible")) {
15
-                bottomToolbar.show("slide", {direction: "right",duration: 300});
15
+            if (!bottomToolbar.is(":visible")) {
16
+                bottomToolbar.show(
17
+                    "slide", {direction: "right", duration: 300});
16 18
             }
17 19
 
18 20
             if (toolbarTimeoutObject) {
@@ -23,9 +25,10 @@ var ToolbarToggler = (function(my) {
23 25
             toolbarTimeout = interfaceConfig.TOOLBAR_TIMEOUT;
24 26
         }
25 27
 
26
-        if (focus != null)
28
+        if (Moderator.isModerator())
27 29
         {
28
-//            TODO: Enable settings functionality. Need to uncomment the settings button in index.html.
30
+//            TODO: Enable settings functionality.
31
+//                  Need to uncomment the settings button in index.html.
29 32
 //            $('#settingsButton').css({visibility:"visible"});
30 33
         }
31 34
 
@@ -46,8 +49,8 @@ var ToolbarToggler = (function(my) {
46 49
                 isToolbarHover = true;
47 50
             }
48 51
         });
49
-        if($("#bottomToolbar:hover").length > 0) {
50
-                isToolbarHover = true;
52
+        if ($("#bottomToolbar:hover").length > 0) {
53
+            isToolbarHover = true;
51 54
         }
52 55
 
53 56
         clearTimeout(toolbarTimeoutObject);
@@ -56,8 +59,9 @@ var ToolbarToggler = (function(my) {
56 59
         if (!isToolbarHover) {
57 60
             header.hide("slide", { direction: "up", duration: 300});
58 61
             $('#subject').animate({top: "-=40"}, 300);
59
-            if($("#remoteVideos").hasClass("hidden")) {
60
-                bottomToolbar.hide("slide", {direction: "right", duration: 300});
62
+            if ($("#remoteVideos").hasClass("hidden")) {
63
+                bottomToolbar.hide(
64
+                    "slide", {direction: "right", duration: 300});
61 65
             }
62 66
         }
63 67
         else {
@@ -71,7 +75,7 @@ var ToolbarToggler = (function(my) {
71 75
      *
72 76
      * @param isDock indicates what operation to perform
73 77
      */
74
-    my.dockToolbar = function(isDock) {
78
+    my.dockToolbar = function (isDock) {
75 79
         if (isDock) {
76 80
             // First make sure the toolbar is shown.
77 81
             if (!$('#header').is(':visible')) {

+ 15
- 0
util.js View File

@@ -82,5 +82,20 @@ var Util = (function (my) {
82 82
         element.setAttribute("data-container", "body");
83 83
     };
84 84
 
85
+    my.createExpBackoffTimer = function (step) {
86
+        var count = 1;
87
+        return function (reset) {
88
+            // Reset call
89
+            if (reset) {
90
+                count = 1;
91
+                return;
92
+            }
93
+            // Calculate next timeout
94
+            var timeout = Math.pow(2, count - 1);
95
+            count += 1;
96
+            return timeout * step;
97
+        };
98
+    };
99
+
85 100
     return my;
86 101
 }(Util || {}));

+ 59
- 42
videolayout.js View File

@@ -30,6 +30,10 @@ var VideoLayout = (function (my) {
30 30
         RTC.attachMediaStream($('#localAudio'), stream);
31 31
         document.getElementById('localAudio').autoplay = true;
32 32
         document.getElementById('localAudio').volume = 0;
33
+        if (preMuted) {
34
+            setAudioMuted(true);
35
+            preMuted = false;
36
+        }
33 37
     };
34 38
 
35 39
     my.changeLocalVideo = function(stream, flipX) {
@@ -438,7 +442,7 @@ var VideoLayout = (function (my) {
438 442
         if ($('#' + videoSpanId).length > 0) {
439 443
             // If there's been a focus change, make sure we add focus related
440 444
             // interface!!
441
-            if (focus && $('#remote_popupmenu_' + resourceJid).length <= 0) {
445
+            if (Moderator.isModerator() && $('#remote_popupmenu_' + resourceJid).length <= 0) {
442 446
                 addRemoteVideoMenu(peerJid,
443 447
                     document.getElementById(videoSpanId));
444 448
             }
@@ -476,7 +480,7 @@ var VideoLayout = (function (my) {
476 480
 
477 481
         // If the peerJid is null then this video span couldn't be directly
478 482
         // associated with a participant (this could happen in the case of prezi).
479
-        if (focus && peerJid != null)
483
+        if (Moderator.isModerator() && peerJid !== null)
480 484
             addRemoteVideoMenu(peerJid, container);
481 485
 
482 486
         remotes.appendChild(container);
@@ -844,42 +848,47 @@ var VideoLayout = (function (my) {
844 848
     };
845 849
 
846 850
     /**
847
-     * Shows a visual indicator for the focus of the conference.
848
-     * Currently if we're not the owner of the conference we obtain the focus
849
-     * from the connection.jingle.sessions.
851
+     * Shows a visual indicator for the moderator of the conference.
850 852
      */
851
-    my.showFocusIndicator = function() {
852
-        if (focus !== null) {
853
+    my.showModeratorIndicator = function () {
854
+        if (Moderator.isModerator()) {
853 855
             var indicatorSpan = $('#localVideoContainer .focusindicator');
854 856
 
855 857
             if (indicatorSpan.children().length === 0)
856 858
             {
857
-                createFocusIndicatorElement(indicatorSpan[0]);
859
+                createModeratorIndicatorElement(indicatorSpan[0]);
858 860
             }
859
-        }
860
-        else if (Object.keys(connection.jingle.sessions).length > 0) {
861
-            // If we're only a participant the focus will be the only session we have.
862
-            var session
863
-                = connection.jingle.sessions
864
-                    [Object.keys(connection.jingle.sessions)[0]];
865
-            var focusId
866
-                = 'participant_' + Strophe.getResourceFromJid(session.peerjid);
867
-
868
-            var focusContainer = document.getElementById(focusId);
869
-            if (!focusContainer) {
870
-                console.error("No focus container!");
871
-                return;
872
-            }
873
-            var indicatorSpan = $('#' + focusId + ' .focusindicator');
861
+        } else {
862
+            Object.keys(connection.emuc.members).forEach(function (jid) {
863
+                var member = connection.emuc.members[jid];
864
+                if (member.role === 'moderator') {
865
+                    var moderatorId
866
+                        = 'participant_' + Strophe.getResourceFromJid(jid);
867
+
868
+                    var moderatorContainer
869
+                        = document.getElementById(moderatorId);
870
+
871
+                    if (Strophe.getResourceFromJid(jid) === 'focus') {
872
+                        // Skip server side focus
873
+                        return;
874
+                    }
875
+                    if (!moderatorContainer) {
876
+                        console.error("No moderator container for " + jid);
877
+                        return;
878
+                    }
879
+                    var indicatorSpan
880
+                        = $('#' + moderatorId + ' .focusindicator');
874 881
 
875
-            if (!indicatorSpan || indicatorSpan.length === 0) {
876
-                indicatorSpan = document.createElement('span');
877
-                indicatorSpan.className = 'focusindicator';
882
+                    if (!indicatorSpan || indicatorSpan.length === 0) {
883
+                        indicatorSpan = document.createElement('span');
884
+                        indicatorSpan.className = 'focusindicator';
878 885
 
879
-                focusContainer.appendChild(indicatorSpan);
886
+                        moderatorContainer.appendChild(indicatorSpan);
880 887
 
881
-                createFocusIndicatorElement(indicatorSpan);
882
-            }
888
+                        createModeratorIndicatorElement(indicatorSpan);
889
+                    }
890
+                }
891
+            });
883 892
         }
884 893
     };
885 894
 
@@ -1197,15 +1206,15 @@ var VideoLayout = (function (my) {
1197 1206
     }
1198 1207
 
1199 1208
     /**
1200
-     * Creates the element indicating the focus of the conference.
1209
+     * Creates the element indicating the moderator(owner) of the conference.
1201 1210
      *
1202
-     * @param parentElement the parent element where the focus indicator will
1211
+     * @param parentElement the parent element where the owner indicator will
1203 1212
      * be added
1204 1213
      */
1205
-    function createFocusIndicatorElement(parentElement) {
1206
-        var focusIndicator = document.createElement('i');
1207
-        focusIndicator.className = 'fa fa-star';
1208
-        parentElement.appendChild(focusIndicator);
1214
+    function createModeratorIndicatorElement(parentElement) {
1215
+        var moderatorIndicator = document.createElement('i');
1216
+        moderatorIndicator.className = 'fa fa-star';
1217
+        parentElement.appendChild(moderatorIndicator);
1209 1218
 
1210 1219
         Util.setTooltip(parentElement,
1211 1220
                 "The owner of<br/>this conference",
@@ -1308,8 +1317,8 @@ var VideoLayout = (function (my) {
1308 1317
             if ($(this).attr('disabled') != undefined) {
1309 1318
                 event.preventDefault();
1310 1319
             }
1311
-            var isMute = !mutedAudios[jid];
1312
-            connection.moderate.setMute(jid, isMute);
1320
+            var isMute = mutedAudios[jid] == true;
1321
+            connection.moderate.setMute(jid, !isMute);
1313 1322
             popupmenuElement.setAttribute('style', 'display:none;');
1314 1323
 
1315 1324
             if (isMute) {
@@ -1389,19 +1398,27 @@ var VideoLayout = (function (my) {
1389 1398
      * On audio muted event.
1390 1399
      */
1391 1400
     $(document).bind('audiomuted.muc', function (event, jid, isMuted) {
1401
+        /*
1402
+         // FIXME: but focus can not mute in this case ? - check
1392 1403
         if (jid === connection.emuc.myroomjid) {
1404
+
1393 1405
             // The local mute indicator is controlled locally
1394 1406
             return;
1407
+        }*/
1408
+        var videoSpanId = null;
1409
+        if (jid === connection.emuc.myroomjid) {
1410
+            videoSpanId = 'localVideoContainer';
1411
+        } else {
1412
+            VideoLayout.ensurePeerContainerExists(jid);
1413
+            videoSpanId = 'participant_' + Strophe.getResourceFromJid(jid);
1395 1414
         }
1396 1415
 
1397
-        VideoLayout.ensurePeerContainerExists(jid);
1416
+        mutedAudios[jid] = isMuted;
1398 1417
 
1399
-        if (focus) {
1400
-            mutedAudios[jid] = isMuted;
1418
+        if (Moderator.isModerator()) {
1401 1419
             VideoLayout.updateRemoteVideoMenu(jid, isMuted);
1402 1420
         }
1403 1421
 
1404
-        var videoSpanId = 'participant_' + Strophe.getResourceFromJid(jid);
1405 1422
         if (videoSpanId)
1406 1423
             VideoLayout.showAudioIndicator(videoSpanId, isMuted);
1407 1424
     });
@@ -1665,7 +1682,7 @@ var VideoLayout = (function (my) {
1665 1682
                 VideoLayout.updateLargeVideo(RTC.getVideoSrc(videoelem[0]), 1, parentResourceJid);
1666 1683
             }
1667 1684
 
1668
-            VideoLayout.showFocusIndicator();
1685
+            VideoLayout.showModeratorIndicator();
1669 1686
         }
1670 1687
     });
1671 1688
 

Loading…
Cancel
Save