Skip to content

Commit

Permalink
feat: highlight current speaker
Browse files Browse the repository at this point in the history
  • Loading branch information
martinstark authored and malmen237 committed Apr 25, 2024
1 parent 12d7673 commit 555bd9f
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 8 deletions.
7 changes: 5 additions & 2 deletions src/components/production-line/production-line.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,10 @@ const ButtonWrapper = styled.span`

export const ProductionLine: FC = () => {
const { productionId: paramProductionId, lineId: paramLineId } = useParams();
const [{ joinProductionOptions, dominantSpeaker }, dispatch] =
useGlobalState();
const [
{ joinProductionOptions, dominantSpeaker, audioLevelAboveThreshold },
dispatch,
] = useGlobalState();
const [isInputMuted, setIsInputMuted] = useState(true);
const [isOutputMuted, setIsOutputMuted] = useState(false);

Expand Down Expand Up @@ -261,6 +263,7 @@ export const ProductionLine: FC = () => {
sessionid={sessionId}
participants={line.participants}
dominantSpeaker={dominantSpeaker}
audioLevelAboveThreshold={audioLevelAboveThreshold}
/>
)}
</DisplayContainer>
Expand Down
79 changes: 79 additions & 0 deletions src/components/production-line/rtc-stat-interval.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { Dispatch } from "react";
import { TGlobalStateAction } from "../../global-state/global-state-actions.ts";

export const startRtcStatInterval = ({
rtcPeerConnection,
dispatch,
}: {
rtcPeerConnection: RTCPeerConnection;
dispatch: Dispatch<TGlobalStateAction>;
}) => {
let ongoingStatsPromise: null | Promise<void | RTCStatsReport> = null;

const statsInterval = window.setInterval(() => {
// Do not request new stats if previously requested has not yet resolved
if (ongoingStatsPromise) return;

const inboundRtpStats: unknown[] = [];
const mediaSourceStats: unknown[] = [];

const audioLevelThreshold = 0.02;

let isAudioLevelAboveThreshold = false;

ongoingStatsPromise = rtcPeerConnection.getStats().then((stats) => {
ongoingStatsPromise = null;

stats.forEach((stat) => {
if (stat.type === "inbound-rtp") {
inboundRtpStats.push(stat);
}

if (stat.type === "media-source") {
mediaSourceStats.push(stat);
}
});

// Check if we have incoming audio above a threshold
if (inboundRtpStats.length) {
inboundRtpStats.forEach((inboundStats) => {
if (
inboundStats &&
typeof inboundStats === "object" &&
"audioLevel" in inboundStats &&
typeof inboundStats.audioLevel === "number"
) {
if (inboundStats.audioLevel > audioLevelThreshold) {
isAudioLevelAboveThreshold = true;
}
}
});
}

// If no incoming audio, check if we have local audio above a certain threshold
if (!isAudioLevelAboveThreshold && mediaSourceStats.length) {
mediaSourceStats.forEach((sourceStats) => {
if (
sourceStats &&
typeof sourceStats === "object" &&
"audioLevel" in sourceStats &&
typeof sourceStats.audioLevel === "number"
) {
if (sourceStats.audioLevel > audioLevelThreshold) {
isAudioLevelAboveThreshold = true;
}
}
});
}

dispatch({
type: "AUDIO_LEVEL_ABOVE_THRESHOLD",
payload: isAudioLevelAboveThreshold,
});
});
}, 100);

return () => {
window.clearInterval(statsInterval);
};
};
8 changes: 8 additions & 0 deletions src/components/production-line/use-rtc-connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { TJoinProductionOptions } from "./types.ts";
import { useGlobalState } from "../../global-state/context-provider.tsx";
import { TGlobalStateAction } from "../../global-state/global-state-actions.ts";
import { TUseAudioInputValues } from "./use-audio-input.ts";
import { startRtcStatInterval } from "./rtc-stat-interval.ts";

type TRtcConnectionOptions = {
inputAudioStream: TUseAudioInputValues;
Expand Down Expand Up @@ -191,11 +192,18 @@ const establishConnection = ({
});
});

const rtcStatIntervalTeardown = startRtcStatInterval({
rtcPeerConnection,
dispatch,
});

return {
teardown: () => {
dataChannel.removeEventListener("message", onDataChannelMessage);

rtcPeerConnection.removeEventListener("track", onRtcTrack);

rtcStatIntervalTeardown();
},
};
};
Expand Down
14 changes: 8 additions & 6 deletions src/components/production-line/user-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ type TUserProps = {
isYou: boolean;
isActive: boolean;
isTalking: boolean;
isDominant: boolean;
};

const User = styled.div<TUserProps>`
Expand All @@ -25,8 +24,8 @@ const User = styled.div<TUserProps>`
${({ isActive }) =>
`border-left: 1rem solid ${isActive ? "#7be27b;" : "#ebca6a;"}`}
${({ isDominant }) =>
`border-bottom: 0.5rem solid ${isDominant ? "#7be27b;" : "#353434;"}`}
${({ isTalking }) =>
`border-bottom: 0.5rem solid ${isTalking ? "#7be27b;" : "#353434;"}`}
${({ isTalking }) =>
isTalking
Expand All @@ -42,11 +41,14 @@ type TUserListOptions = {
participants: TParticipant[];
sessionid: string | null;
dominantSpeaker: string | null;
audioLevelAboveThreshold: boolean;
};

export const UserList = ({
participants,
sessionid,
dominantSpeaker,
audioLevelAboveThreshold,
}: TUserListOptions) => {
if (!participants) return null;

Expand All @@ -58,9 +60,9 @@ export const UserList = ({
key={p.sessionid}
isYou={p.sessionid === sessionid}
isActive={p.isActive}
isDominant={p.endpointid === dominantSpeaker}
// TODO connect to rtc data channel to get talking session id
isTalking={false}
isTalking={
audioLevelAboveThreshold && p.endpointid === dominantSpeaker
}
>
{p.name} {p.isActive ? "" : "(inactive)"}
</User>
Expand Down

0 comments on commit 555bd9f

Please sign in to comment.