From 2234962acc5209d96179c089422067362936174f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C5=A0imon=20Brandner?= <simon.bra.ag@gmail.com>
Date: Sat, 13 Aug 2022 18:16:24 +0200
Subject: [PATCH 1/2] Fix handling of streams with no audio tracks
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
---
 src/video-grid/VideoTileContainer.tsx | 2 +-
 src/video-grid/useMediaStream.ts      | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/video-grid/VideoTileContainer.tsx b/src/video-grid/VideoTileContainer.tsx
index d647903..1ca1a4c 100644
--- a/src/video-grid/VideoTileContainer.tsx
+++ b/src/video-grid/VideoTileContainer.tsx
@@ -72,7 +72,7 @@ export function VideoTileContainer({
     audioOutputDevice,
     audioContext,
     audioDestination,
-    isLocal,
+    isLocal || audioMuted,
     localVolume
   );
   const {
diff --git a/src/video-grid/useMediaStream.ts b/src/video-grid/useMediaStream.ts
index 86b0105..2af4ec2 100644
--- a/src/video-grid/useMediaStream.ts
+++ b/src/video-grid/useMediaStream.ts
@@ -213,7 +213,7 @@ export const useSpatialMediaStream = (
   const sourceRef = useRef<MediaStreamAudioSourceNode>();
 
   useEffect(() => {
-    if (spatialAudio && tileRef.current && !mute) {
+    if (spatialAudio && audioContext && tileRef.current && !mute) {
       if (!pannerNodeRef.current) {
         pannerNodeRef.current = new PannerNode(audioContext, {
           panningModel: "HRTF",

From 317f27e5f98669304ff8dc78f576c3c210b0c7e0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C5=A0imon=20Brandner?= <simon.bra.ag@gmail.com>
Date: Sat, 13 Aug 2022 18:44:11 +0200
Subject: [PATCH 2/2] Don't re-run hook on every mute
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
---
 src/video-grid/VideoTileContainer.tsx |  2 +-
 src/video-grid/useMediaStream.ts      | 33 ++++++++++++++++++++++++---
 2 files changed, 31 insertions(+), 4 deletions(-)

diff --git a/src/video-grid/VideoTileContainer.tsx b/src/video-grid/VideoTileContainer.tsx
index 1ca1a4c..d647903 100644
--- a/src/video-grid/VideoTileContainer.tsx
+++ b/src/video-grid/VideoTileContainer.tsx
@@ -72,7 +72,7 @@ export function VideoTileContainer({
     audioOutputDevice,
     audioContext,
     audioDestination,
-    isLocal || audioMuted,
+    isLocal,
     localVolume
   );
   const {
diff --git a/src/video-grid/useMediaStream.ts b/src/video-grid/useMediaStream.ts
index 2af4ec2..236361f 100644
--- a/src/video-grid/useMediaStream.ts
+++ b/src/video-grid/useMediaStream.ts
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
 limitations under the License.
 */
 
-import { useRef, useEffect, RefObject } from "react";
+import { useRef, useEffect, RefObject, useState, useCallback } from "react";
 import { parse as parseSdp, write as writeSdp } from "sdp-transform";
 import {
   acquireContext,
@@ -22,6 +22,7 @@ import {
 } from "matrix-js-sdk/src/webrtc/audioContext";
 
 import { useSpatialAudio } from "../settings/useSetting";
+import { useEventTarget } from "../useEvents";
 
 declare global {
   interface Window {
@@ -30,6 +31,23 @@ declare global {
   }
 }
 
+export const useMediaStreamTrackCount = (
+  stream: MediaStream
+): [number, number] => {
+  const [audioTrackCount, setAudioTrackCount] = useState(0);
+  const [videoTrackCount, setVideoTrackCount] = useState(0);
+
+  const tracksChanged = useCallback(() => {
+    setAudioTrackCount(stream.getAudioTracks().length);
+    setVideoTrackCount(stream.getVideoTracks().length);
+  }, [stream]);
+
+  useEventTarget(stream, "addtrack", tracksChanged);
+  useEventTarget(stream, "removetrack", tracksChanged);
+
+  return [audioTrackCount, videoTrackCount];
+};
+
 export const useMediaStream = (
   stream: MediaStream,
   audioOutputDevice: string,
@@ -207,13 +225,14 @@ export const useSpatialMediaStream = (
     spatialAudio || mute,
     localVolume
   );
+  const [audioTrackCount] = useMediaStreamTrackCount(stream);
 
   const gainNodeRef = useRef<GainNode>();
   const pannerNodeRef = useRef<PannerNode>();
   const sourceRef = useRef<MediaStreamAudioSourceNode>();
 
   useEffect(() => {
-    if (spatialAudio && audioContext && tileRef.current && !mute) {
+    if (spatialAudio && tileRef.current && !mute && audioTrackCount > 0) {
       if (!pannerNodeRef.current) {
         pannerNodeRef.current = new PannerNode(audioContext, {
           panningModel: "HRTF",
@@ -261,7 +280,15 @@ export const useSpatialMediaStream = (
         pannerNode.disconnect();
       };
     }
-  }, [stream, spatialAudio, audioContext, audioDestination, mute, localVolume]);
+  }, [
+    stream,
+    spatialAudio,
+    audioContext,
+    audioDestination,
+    mute,
+    localVolume,
+    audioTrackCount,
+  ]);
 
   return [tileRef, mediaRef];
 };