浏览代码

feat: Added mute video moderation feature (#1496)

* Added video mute participant

* Trigger mute event

* Optimized mute type checks

* Fixed event name

* Fixed some linter issues

* Fixed more linter issues

* And even more linter issues fixed

* And more linter fixes

* Added media type to analytics event
dev1
Steffen Kolmer 4 年前
父节点
当前提交
9beb47fe5f
没有帐户链接到提交者的电子邮件

+ 26
- 4
JitsiConference.js 查看文件

@@ -151,6 +151,11 @@ export default function JitsiConference(options) {
151 151
     // when muted by focus we receive the jid of the initiator of the mute
152 152
     this.mutedByFocusActor = null;
153 153
 
154
+    this.isVideoMutedByFocus = false;
155
+
156
+    // when video muted by focus we receive the jid of the initiator of the mute
157
+    this.mutedVideoByFocusActor = null;
158
+
154 159
     // Flag indicates if the 'onCallEnded' method was ever called on this
155 160
     // instance. Used to log extra analytics event for debugging purpose.
156 161
     // We need to know if the potential issue happened before or after
@@ -1019,14 +1024,23 @@ JitsiConference.prototype._fireMuteChangeEvent = function(track) {
1019 1024
         this.isMutedByFocus = false;
1020 1025
 
1021 1026
         // unmute local user on server
1022
-        this.room.muteParticipant(this.room.myroomjid, false);
1027
+        this.room.muteParticipant(this.room.myroomjid, false, MediaType.AUDIO);
1028
+    } else if (this.isVideoMutedByFocus && track.isVideoTrack() && !track.isMuted()) {
1029
+        this.isVideoMutedByFocus = false;
1030
+
1031
+        // unmute local user on server
1032
+        this.room.muteParticipant(this.room.myroomjid, false, MediaType.VIDEO);
1023 1033
     }
1024 1034
 
1025 1035
     let actorParticipant;
1026 1036
 
1027
-    if (this.mutedByFocusActor) {
1037
+    if (this.mutedByFocusActor && track.isAudioTrack()) {
1028 1038
         const actorId = Strophe.getResourceFromJid(this.mutedByFocusActor);
1029 1039
 
1040
+        actorParticipant = this.participants[actorId];
1041
+    } else if (this.mutedVideoByFocusActor && track.isVideoTrack()) {
1042
+        const actorId = Strophe.getResourceFromJid(this.mutedVideoByFocusActor);
1043
+
1030 1044
         actorParticipant = this.participants[actorId];
1031 1045
     }
1032 1046
 
@@ -1497,13 +1511,21 @@ JitsiConference.prototype._maybeSetSITimeout = function() {
1497 1511
  * Mutes a participant.
1498 1512
  * @param {string} id The id of the participant to mute.
1499 1513
  */
1500
-JitsiConference.prototype.muteParticipant = function(id) {
1514
+JitsiConference.prototype.muteParticipant = function(id, mediaType) {
1515
+    const muteMediaType = mediaType ? mediaType : MediaType.AUDIO;
1516
+
1517
+    if (muteMediaType !== MediaType.AUDIO && muteMediaType !== MediaType.VIDEO) {
1518
+        logger.error(`Unsupported media type: ${muteMediaType}`);
1519
+
1520
+        return;
1521
+    }
1522
+
1501 1523
     const participant = this.getParticipantById(id);
1502 1524
 
1503 1525
     if (!participant) {
1504 1526
         return;
1505 1527
     }
1506
-    this.room.muteParticipant(participant.getJid(), true);
1528
+    this.room.muteParticipant(participant.getJid(), true, muteMediaType);
1507 1529
 };
1508 1530
 
1509 1531
 /* eslint-disable max-params */

+ 25
- 1
JitsiConferenceEventManager.js 查看文件

@@ -92,7 +92,7 @@ JitsiConferenceEventManager.prototype.setupChatRoomListeners = function() {
92 92
             // TODO: Add a way to differentiate between commands which caused
93 93
             // us to mute and those that did not change our state (i.e. we were
94 94
             // already muted).
95
-            Statistics.sendAnalytics(createRemotelyMutedEvent());
95
+            Statistics.sendAnalytics(createRemotelyMutedEvent(MediaType.AUDIO));
96 96
 
97 97
             conference.mutedByFocusActor = actor;
98 98
 
@@ -111,6 +111,30 @@ JitsiConferenceEventManager.prototype.setupChatRoomListeners = function() {
111 111
         }
112 112
     );
113 113
 
114
+    chatRoom.addListener(XMPPEvents.VIDEO_MUTED_BY_FOCUS,
115
+        actor => {
116
+            // TODO: Add a way to differentiate between commands which caused
117
+            // us to mute and those that did not change our state (i.e. we were
118
+            // already muted).
119
+            Statistics.sendAnalytics(createRemotelyMutedEvent(MediaType.VIDEO));
120
+
121
+            conference.mutedVideoByFocusActor = actor;
122
+
123
+            // set isVideoMutedByFocus when setVideoMute Promise ends
124
+            conference.rtc.setVideoMute(true).then(
125
+                () => {
126
+                    conference.isVideoMutedByFocus = true;
127
+                    conference.mutedVideoByFocusActor = null;
128
+                })
129
+                .catch(
130
+                    error => {
131
+                        conference.mutedVideoByFocusActor = null;
132
+                        logger.warn(
133
+                            'Error while video muting due to focus request', error);
134
+                    });
135
+        }
136
+    );
137
+
114 138
     this.chatRoomForwarder.forward(XMPPEvents.SUBJECT_CHANGED,
115 139
         JitsiConferenceEvents.SUBJECT_CHANGED);
116 140
 

+ 19
- 0
modules/RTC/RTC.js 查看文件

@@ -624,6 +624,25 @@ export default class RTC extends Listenable {
624 624
         return Promise.all(mutePromises);
625 625
     }
626 626
 
627
+    /**
628
+    * Set mute for all local video streams attached to the conference.
629
+    * @param value The mute value.
630
+    * @returns {Promise}
631
+    */
632
+    setVideoMute(value) {
633
+        const mutePromises = [];
634
+
635
+        this.getLocalTracks(MediaType.VIDEO).concat(this.getLocalTracks(MediaType.PRESENTER))
636
+            .forEach(videoTrack => {
637
+                // this is a Promise
638
+                mutePromises.push(value ? videoTrack.mute() : videoTrack.unmute());
639
+            });
640
+
641
+        // We return a Promise from all Promises so we can wait for their
642
+        // execution.
643
+        return Promise.all(mutePromises);
644
+    }
645
+
627 646
     /**
628 647
      *
629 648
      * @param track

+ 28
- 2
modules/xmpp/ChatRoom.js 查看文件

@@ -1672,14 +1672,15 @@ export default class ChatRoom extends Listenable {
1672 1672
      * Mutes remote participant.
1673 1673
      * @param jid of the participant
1674 1674
      * @param mute
1675
+     * @param mediaType
1675 1676
      */
1676
-    muteParticipant(jid, mute) {
1677
+    muteParticipant(jid, mute, mediaType) {
1677 1678
         logger.info('set mute', mute);
1678 1679
         const iqToFocus = $iq(
1679 1680
             { to: this.focusMucJid,
1680 1681
                 type: 'set' })
1681 1682
             .c('mute', {
1682
-                xmlns: 'http://jitsi.org/jitmeet/audio',
1683
+                xmlns: `http://jitsi.org/jitmeet/${mediaType}`,
1683 1684
                 jid
1684 1685
             })
1685 1686
             .t(mute.toString())
@@ -1716,6 +1717,31 @@ export default class ChatRoom extends Listenable {
1716 1717
         }
1717 1718
     }
1718 1719
 
1720
+    /**
1721
+     * TODO: Document
1722
+     * @param iq
1723
+     */
1724
+    onMuteVideo(iq) {
1725
+        const from = iq.getAttribute('from');
1726
+
1727
+        if (from !== this.focusMucJid) {
1728
+            logger.warn('Ignored mute from non focus peer');
1729
+
1730
+            return;
1731
+        }
1732
+        const mute = $(iq).find('mute');
1733
+
1734
+        if (mute.length && mute.text() === 'true') {
1735
+            this.eventEmitter.emit(XMPPEvents.VIDEO_MUTED_BY_FOCUS, mute.attr('actor'));
1736
+        } else {
1737
+            // XXX Why do we support anything but muting? Why do we encode the
1738
+            // value in the text of the element? Why do we use a separate XML
1739
+            // namespace?
1740
+            logger.warn('Ignoring a mute request which does not explicitly '
1741
+                + 'specify a positive mute command.');
1742
+        }
1743
+    }
1744
+
1719 1745
     /**
1720 1746
      * Clean any listeners or resources, executed on leaving.
1721 1747
      */

+ 20
- 0
modules/xmpp/strophe.emuc.js 查看文件

@@ -42,6 +42,8 @@ export default class MucConnectionPlugin extends ConnectionPluginListenable {
42 42
             'message', null, null);
43 43
         this.connection.addHandler(this.onMute.bind(this),
44 44
             'http://jitsi.org/jitmeet/audio', 'iq', 'set', null, null);
45
+        this.connection.addHandler(this.onMuteVideo.bind(this),
46
+            'http://jitsi.org/jitmeet/video', 'iq', 'set', null, null);
45 47
     }
46 48
 
47 49
     /**
@@ -175,4 +177,22 @@ export default class MucConnectionPlugin extends ConnectionPluginListenable {
175 177
 
176 178
         return true;
177 179
     }
180
+
181
+    /**
182
+     * TODO: Document
183
+     * @param iq
184
+     */
185
+    onMuteVideo(iq) {
186
+        const from = iq.getAttribute('from');
187
+        const room = this.rooms[Strophe.getBareJidFromJid(from)];
188
+
189
+        // Returning false would result in the listener being deregistered by Strophe
190
+        if (!room) {
191
+            return true;
192
+        }
193
+
194
+        room.onMuteVideo(iq);
195
+
196
+        return true;
197
+    }
178 198
 }

+ 3
- 2
service/statistics/AnalyticsEvents.js 查看文件

@@ -420,10 +420,11 @@ export const createP2PEvent = function(action, attributes = {}) {
420 420
 /**
421 421
  * Indicates that we received a remote command to mute.
422 422
  */
423
-export const createRemotelyMutedEvent = function() {
423
+export const createRemotelyMutedEvent = function(mediaType) {
424 424
     return {
425 425
         type: TYPE_OPERATIONAL,
426
-        action: 'remotely.muted'
426
+        action: 'remotely.muted',
427
+        mediaType
427 428
     };
428 429
 };
429 430
 

+ 4
- 0
service/xmpp/XMPPEvents.js 查看文件

@@ -7,6 +7,10 @@ const XMPPEvents = {
7 7
     // Designates an event indicating that the focus has asked us to mute our
8 8
     // audio.
9 9
     AUDIO_MUTED_BY_FOCUS: 'xmpp.audio_muted_by_focus',
10
+
11
+    // Designates an event indicating that the focus has asked us to disable our
12
+    // camera.
13
+    VIDEO_MUTED_BY_FOCUS: 'xmpp.video_muted_by_focus',
10 14
     AUTHENTICATION_REQUIRED: 'xmpp.authentication_required',
11 15
     BRIDGE_DOWN: 'xmpp.bridge_down',
12 16
 

正在加载...
取消
保存