Skip to content

Commit c689dc2

Browse files
authored
fix(recording): more reliable detection of the dominant speaker (#1972)
Extends the egress-composite app with additional tracing logs for dominant speaker changes. Also, fixes a "participant sorting" bug that, in some circumstances, caused an incorrect participant to be flagged as the dominant one. Ref: https://getstream.slack.com/archives/C05KL0HEWLA/p1761659680348299 Ticket: https://linear.app/stream/issue/REACT-630/recording-unexpected-participant-is-flagged-as-dominant-one
1 parent 252eac1 commit c689dc2

File tree

2 files changed

+50
-3
lines changed

2 files changed

+50
-3
lines changed

sample-apps/react/egress-composite/src/components/layouts/DominantSpeaker/DominantSpeaker.tsx

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,20 @@
1+
import { useEffect, useRef } from 'react';
12
import {
3+
CallTypes,
4+
combineComparators,
25
DefaultParticipantViewUI,
6+
defaultSortPreset,
7+
dominantSpeaker,
38
ParticipantsAudio,
49
ParticipantView,
10+
pinned,
11+
publishingAudio,
12+
publishingVideo,
13+
reactionType,
14+
screenSharing,
515
SfuModels,
16+
speaking,
17+
StreamVideoParticipant,
618
useCall,
719
useCallStateHooks,
820
} from '@stream-io/video-react-sdk';
@@ -13,8 +25,9 @@ import { useEgressReadyWhenAnyParticipantMounts } from '../egressReady';
1325
import './DominantSpeaker.scss';
1426

1527
export const DominantSpeaker = () => {
16-
const activeCall = useCall();
28+
const call = useCall();
1729
const speakerInSpotlight = useSpotlightParticipant();
30+
const lastSpeakerInSpotlight = useRef<StreamVideoParticipant | null>(null);
1831
const { useRemoteParticipants } = useCallStateHooks();
1932
const remoteParticipants = useRemoteParticipants();
2033
const { setVideoElement, setVideoPlaceholderElement } =
@@ -23,7 +36,39 @@ export const DominantSpeaker = () => {
2336
SfuModels.TrackType.VIDEO,
2437
);
2538

26-
if (!activeCall) return <h2>No active call</h2>;
39+
useEffect(() => {
40+
if (!call) return;
41+
const comparator = combineComparators(
42+
screenSharing,
43+
pinned,
44+
dominantSpeaker,
45+
speaking,
46+
reactionType('raised-hand'),
47+
publishingVideo,
48+
publishingAudio,
49+
);
50+
call.setSortParticipantsBy(comparator);
51+
return () => {
52+
// reset the sorting to the default for the call type
53+
const callConfig = CallTypes.get(call.type);
54+
const preset = callConfig.options.sortParticipantsBy || defaultSortPreset;
55+
call.setSortParticipantsBy(preset);
56+
};
57+
}, [call]);
58+
59+
useEffect(() => {
60+
if (!call || !speakerInSpotlight) return;
61+
62+
const sessionId = lastSpeakerInSpotlight.current?.sessionId;
63+
if (speakerInSpotlight.sessionId === sessionId) return;
64+
65+
const tag = 'recorder.dominant_speaker_layout.spotlight_speaker_changed';
66+
call.tracer.trace(tag, speakerInSpotlight);
67+
68+
lastSpeakerInSpotlight.current = speakerInSpotlight;
69+
}, [call, speakerInSpotlight]);
70+
71+
if (!call) return <h2>No active call</h2>;
2772
return (
2873
<div
2974
className="eca__dominant-speaker__container"

sample-apps/react/egress-composite/src/components/layouts/DominantSpeaker/useSpotlightParticipant.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import {
55
} from '@stream-io/video-react-sdk';
66
import { useConfigurationContext } from '../../../ConfigurationContext';
77

8-
export const useSpotlightParticipant = () => {
8+
export const useSpotlightParticipant = ():
9+
| StreamVideoParticipant
10+
| undefined => {
911
const [speakerInSpotlight, setSpeakerInSpotlight] =
1012
useState<StreamVideoParticipant>();
1113

0 commit comments

Comments
 (0)