Fix double audio tracks
See comments. I'm not very happy with how this code bounces state in and out of different hooks and useEffect blocks, but as a quick fix this should work.
This commit is contained in:
@@ -17,10 +17,8 @@ limitations under the License.
|
|||||||
import {
|
import {
|
||||||
AudioCaptureOptions,
|
AudioCaptureOptions,
|
||||||
ConnectionState,
|
ConnectionState,
|
||||||
LocalTrackPublication,
|
|
||||||
Room,
|
Room,
|
||||||
RoomEvent,
|
RoomEvent,
|
||||||
Track,
|
|
||||||
} from "livekit-client";
|
} from "livekit-client";
|
||||||
import { useCallback, useEffect, useRef, useState } from "react";
|
import { useCallback, useEffect, useRef, useState } from "react";
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
@@ -56,24 +54,22 @@ async function doConnect(
|
|||||||
audioOptions: AudioCaptureOptions
|
audioOptions: AudioCaptureOptions
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
await livekitRoom!.connect(sfuConfig!.url, sfuConfig!.jwt);
|
await livekitRoom!.connect(sfuConfig!.url, sfuConfig!.jwt);
|
||||||
const hasMicrophoneTrack = Array.from(
|
|
||||||
livekitRoom?.localParticipant.audioTracks.values()
|
|
||||||
).some((track: LocalTrackPublication) => {
|
|
||||||
return track.source == Track.Source.Microphone;
|
|
||||||
});
|
|
||||||
// We create a track in case there isn't any.
|
|
||||||
if (!hasMicrophoneTrack) {
|
|
||||||
const audioTracks = await livekitRoom!.localParticipant.createTracks({
|
|
||||||
audio: audioOptions,
|
|
||||||
});
|
|
||||||
if (audioTracks.length < 1) {
|
|
||||||
logger.info("Tried to pre-create local audio track but got no tracks");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!audioEnabled) await audioTracks[0].mute();
|
|
||||||
|
|
||||||
await livekitRoom?.localParticipant.publishTrack(audioTracks[0]);
|
// Always create an audio track manually.
|
||||||
|
// livekit (by default) keeps the mic track open when you mute, but if you start muted,
|
||||||
|
// doesn't publish it until you unmute. We want to publish it from the start so we're
|
||||||
|
// always capturing audio: it helps keep bluetooth headsets in the right mode and
|
||||||
|
// mobile browsers to know we're doing a call.
|
||||||
|
const audioTracks = await livekitRoom!.localParticipant.createTracks({
|
||||||
|
audio: audioOptions,
|
||||||
|
});
|
||||||
|
if (audioTracks.length < 1) {
|
||||||
|
logger.info("Tried to pre-create local audio track but got no tracks");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
if (!audioEnabled) await audioTracks[0].mute();
|
||||||
|
|
||||||
|
await livekitRoom?.localParticipant.publishTrack(audioTracks[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useECConnectionState(
|
export function useECConnectionState(
|
||||||
@@ -89,6 +85,7 @@ export function useECConnectionState(
|
|||||||
);
|
);
|
||||||
|
|
||||||
const [isSwitchingFocus, setSwitchingFocus] = useState(false);
|
const [isSwitchingFocus, setSwitchingFocus] = useState(false);
|
||||||
|
const [isInDoConnect, setIsInDoConnect] = useState(false);
|
||||||
|
|
||||||
const onConnStateChanged = useCallback((state: ConnectionState) => {
|
const onConnStateChanged = useCallback((state: ConnectionState) => {
|
||||||
if (state == ConnectionState.Connected) setSwitchingFocus(false);
|
if (state == ConnectionState.Connected) setSwitchingFocus(false);
|
||||||
@@ -125,12 +122,17 @@ export function useECConnectionState(
|
|||||||
(async () => {
|
(async () => {
|
||||||
setSwitchingFocus(true);
|
setSwitchingFocus(true);
|
||||||
await livekitRoom?.disconnect();
|
await livekitRoom?.disconnect();
|
||||||
await doConnect(
|
setIsInDoConnect(true);
|
||||||
livekitRoom!,
|
try {
|
||||||
sfuConfig!,
|
await doConnect(
|
||||||
initialAudioEnabled,
|
livekitRoom!,
|
||||||
initialAudioOptions
|
sfuConfig!,
|
||||||
);
|
initialAudioEnabled,
|
||||||
|
initialAudioOptions
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
setIsInDoConnect(false);
|
||||||
|
}
|
||||||
})();
|
})();
|
||||||
} else if (
|
} else if (
|
||||||
!sfuConfigValid(currentSFUConfig.current) &&
|
!sfuConfigValid(currentSFUConfig.current) &&
|
||||||
@@ -142,16 +144,24 @@ export function useECConnectionState(
|
|||||||
// doesn't publish it until you unmute. We want to publish it from the start so we're
|
// doesn't publish it until you unmute. We want to publish it from the start so we're
|
||||||
// always capturing audio: it helps keep bluetooth headsets in the right mode and
|
// always capturing audio: it helps keep bluetooth headsets in the right mode and
|
||||||
// mobile browsers to know we're doing a call.
|
// mobile browsers to know we're doing a call.
|
||||||
|
setIsInDoConnect(true);
|
||||||
doConnect(
|
doConnect(
|
||||||
livekitRoom!,
|
livekitRoom!,
|
||||||
sfuConfig!,
|
sfuConfig!,
|
||||||
initialAudioEnabled,
|
initialAudioEnabled,
|
||||||
initialAudioOptions
|
initialAudioOptions
|
||||||
);
|
).finally(() => setIsInDoConnect(false));
|
||||||
}
|
}
|
||||||
|
|
||||||
currentSFUConfig.current = Object.assign({}, sfuConfig);
|
currentSFUConfig.current = Object.assign({}, sfuConfig);
|
||||||
}, [sfuConfig, livekitRoom, initialAudioOptions, initialAudioEnabled]);
|
}, [sfuConfig, livekitRoom, initialAudioOptions, initialAudioEnabled]);
|
||||||
|
|
||||||
return isSwitchingFocus ? ECAddonConnectionState.ECSwitchingFocus : connState;
|
// Because we create audio tracks by hand, there's more to connecting than
|
||||||
|
// just what LiveKit does in room.connect, and we should continue to return
|
||||||
|
// ConnectionState.Connecting for the entire duration of the doConnect promise
|
||||||
|
return isSwitchingFocus
|
||||||
|
? ECAddonConnectionState.ECSwitchingFocus
|
||||||
|
: isInDoConnect
|
||||||
|
? ConnectionState.Connecting
|
||||||
|
: connState;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import {
|
|||||||
setLogLevel,
|
setLogLevel,
|
||||||
} from "livekit-client";
|
} from "livekit-client";
|
||||||
import { useLiveKitRoom } from "@livekit/components-react";
|
import { useLiveKitRoom } from "@livekit/components-react";
|
||||||
import { useEffect, useMemo, useRef } from "react";
|
import { useEffect, useMemo, useRef, useState } from "react";
|
||||||
import E2EEWorker from "livekit-client/e2ee-worker?worker";
|
import E2EEWorker from "livekit-client/e2ee-worker?worker";
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
|
|
||||||
@@ -98,6 +98,11 @@ export function useLiveKit(
|
|||||||
[e2eeOptions]
|
[e2eeOptions]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// useECConnectionState creates and publishes an audio track by hand. To keep
|
||||||
|
// this from racing with LiveKit's automatic creation of the audio track, we
|
||||||
|
// block audio from being enabled until the connection is finished.
|
||||||
|
const [blockAudio, setBlockAudio] = useState(true);
|
||||||
|
|
||||||
// We have to create the room manually here due to a bug inside
|
// We have to create the room manually here due to a bug inside
|
||||||
// @livekit/components-react. JSON.stringify() is used in deps of a
|
// @livekit/components-react. JSON.stringify() is used in deps of a
|
||||||
// useEffect() with an argument that references itself, if E2EE is enabled
|
// useEffect() with an argument that references itself, if E2EE is enabled
|
||||||
@@ -105,7 +110,7 @@ export function useLiveKit(
|
|||||||
const { room } = useLiveKitRoom({
|
const { room } = useLiveKitRoom({
|
||||||
token: sfuConfig?.jwt,
|
token: sfuConfig?.jwt,
|
||||||
serverUrl: sfuConfig?.url,
|
serverUrl: sfuConfig?.url,
|
||||||
audio: initialMuteStates.current.audio.enabled,
|
audio: initialMuteStates.current.audio.enabled && !blockAudio,
|
||||||
video: initialMuteStates.current.video.enabled,
|
video: initialMuteStates.current.video.enabled,
|
||||||
room: roomWithoutProps,
|
room: roomWithoutProps,
|
||||||
connect: false,
|
connect: false,
|
||||||
@@ -120,6 +125,11 @@ export function useLiveKit(
|
|||||||
sfuConfig
|
sfuConfig
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Unblock audio once the connection is finished
|
||||||
|
useEffect(() => {
|
||||||
|
if (connectionState === ConnectionState.Connected) setBlockAudio(false);
|
||||||
|
}, [connectionState, setBlockAudio]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Sync the requested mute states with LiveKit's mute states. We do it this
|
// Sync the requested mute states with LiveKit's mute states. We do it this
|
||||||
// way around rather than using LiveKit as the source of truth, so that the
|
// way around rather than using LiveKit as the source of truth, so that the
|
||||||
|
|||||||
Reference in New Issue
Block a user