From 1d3e7a4e522c60469f4921a70b3e9dbfa2005ce8 Mon Sep 17 00:00:00 2001
From: Robert Long <robert@robertlong.me>
Date: Tue, 31 Aug 2021 16:32:55 -0700
Subject: [PATCH] Add talking indicators

---
 src/ConferenceCallManager.js | 27 +++++++++++++++++++++++++++
 1 file changed, 27 insertions(+)

diff --git a/src/ConferenceCallManager.js b/src/ConferenceCallManager.js
index d608b07..49719ef 100644
--- a/src/ConferenceCallManager.js
+++ b/src/ConferenceCallManager.js
@@ -21,6 +21,7 @@ import { randomString } from "./randomstring";
 const CONF_ROOM = "me.robertlong.conf";
 const CONF_PARTICIPANT = "me.robertlong.conf.participant";
 const PARTICIPANT_TIMEOUT = 1000 * 15;
+const SPEAKING_THRESHOLD = -80;
 
 function waitForSync(client) {
   return new Promise((resolve, reject) => {
@@ -247,6 +248,7 @@ export class ConferenceCallManager extends EventEmitter {
       stream,
       audioMuted: this.audioMuted,
       videoMuted: this.videoMuted,
+      speaking: false,
     };
 
     this.participants.push(this.localParticipant);
@@ -316,6 +318,7 @@ export class ConferenceCallManager extends EventEmitter {
     this.localParticipant.call = null;
     this.localParticipant.audioMuted = false;
     this.localParticipant.videoMuted = false;
+    this.localParticipant.speaking = false;
     this.audioMuted = false;
     this.videoMuted = false;
     clearTimeout(this._memberParticipantStateTimeout);
@@ -498,6 +501,11 @@ export class ConferenceCallManager extends EventEmitter {
       remoteFeed.on("mute_state_changed", () =>
         this._onCallFeedMuteStateChanged(participant, remoteFeed)
       );
+      remoteFeed.setSpeakingThreshold(SPEAKING_THRESHOLD);
+      remoteFeed.measureVolumeActivity(true);
+      remoteFeed.on("speaking", (speaking) => {
+        this._onCallFeedSpeaking(participant, speaking);
+      });
     }
 
     const userId = call.opponentMember.userId;
@@ -523,6 +531,7 @@ export class ConferenceCallManager extends EventEmitter {
       existingParticipant.stream = stream;
       existingParticipant.audioMuted = audioMuted;
       existingParticipant.videoMuted = videoMuted;
+      existingParticipant.speaking = false;
       existingParticipant.sessionId = sessionId;
     } else {
       participant = {
@@ -533,6 +542,7 @@ export class ConferenceCallManager extends EventEmitter {
         stream,
         audioMuted,
         videoMuted,
+        speaking: false,
       };
       this.participants.push(participant);
     }
@@ -623,6 +633,7 @@ export class ConferenceCallManager extends EventEmitter {
       participant.stream = null;
       participant.audioMuted = false;
       participant.videoMuted = false;
+      participant.speaking = false;
     } else {
       participant = {
         local: false,
@@ -632,6 +643,7 @@ export class ConferenceCallManager extends EventEmitter {
         stream: null,
         audioMuted: false,
         videoMuted: false,
+        speaking: false,
       };
       // TODO: Should we wait until the call has been answered to push the participant?
       // Or do we hide the participant until their stream is live?
@@ -691,6 +703,11 @@ export class ConferenceCallManager extends EventEmitter {
       remoteFeed.on("mute_state_changed", () =>
         this._onCallFeedMuteStateChanged(participant, remoteFeed)
       );
+      remoteFeed.setSpeakingThreshold(SPEAKING_THRESHOLD);
+      remoteFeed.measureVolumeActivity(true);
+      remoteFeed.on("speaking", (speaking) => {
+        this._onCallFeedSpeaking(participant, speaking);
+      });
       this._onCallFeedMuteStateChanged(participant, remoteFeed);
     }
   };
@@ -701,6 +718,11 @@ export class ConferenceCallManager extends EventEmitter {
     this.emit("participants_changed");
   };
 
+  _onCallFeedSpeaking = (participant, speaking) => {
+    participant.speaking = speaking;
+    this.emit("participants_changed");
+  };
+
   _onCallReplaced = (participant, call, newCall) => {
     participant.call = newCall;
 
@@ -724,6 +746,11 @@ export class ConferenceCallManager extends EventEmitter {
       remoteFeed.on("mute_state_changed", () =>
         this._onCallFeedMuteStateChanged(participant, remoteFeed)
       );
+      remoteFeed.setSpeakingThreshold(SPEAKING_THRESHOLD);
+      remoteFeed.measureVolumeActivity(true);
+      remoteFeed.on("speaking", (speaking) => {
+        this._onCallFeedSpeaking(participant, speaking);
+      });
     }
 
     this.emit("call", newCall);