mirror of
https://github.com/vector-im/element-call.git
synced 2026-08-02 19:49:23 +00:00
Loop ring sound inside Audio context for better perf.
This commit is contained in:
@@ -54,6 +54,7 @@ beforeEach(() => {
|
|||||||
playSound = vitest.fn();
|
playSound = vitest.fn();
|
||||||
(useAudioContext as MockedFunction<typeof useAudioContext>).mockReturnValue({
|
(useAudioContext as MockedFunction<typeof useAudioContext>).mockReturnValue({
|
||||||
playSound,
|
playSound,
|
||||||
|
playSoundLooping: vitest.fn(),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
onTestFinished,
|
onTestFinished,
|
||||||
test,
|
test,
|
||||||
vi,
|
vi,
|
||||||
|
vitest,
|
||||||
} from "vitest";
|
} from "vitest";
|
||||||
import { render, waitFor, screen } from "@testing-library/react";
|
import { render, waitFor, screen } from "@testing-library/react";
|
||||||
import { type MatrixClient, JoinRule, type RoomState } from "matrix-js-sdk";
|
import { type MatrixClient, JoinRule, type RoomState } from "matrix-js-sdk";
|
||||||
@@ -94,6 +95,7 @@ beforeEach(() => {
|
|||||||
playSound = vi.fn();
|
playSound = vi.fn();
|
||||||
(useAudioContext as MockedFunction<typeof useAudioContext>).mockReturnValue({
|
(useAudioContext as MockedFunction<typeof useAudioContext>).mockReturnValue({
|
||||||
playSound,
|
playSound,
|
||||||
|
playSoundLooping: vi.fn(),
|
||||||
});
|
});
|
||||||
// A trivial implementation of Active call to ensure we are testing GroupCallView exclusively here.
|
// A trivial implementation of Active call to ensure we are testing GroupCallView exclusively here.
|
||||||
(ActiveCall as MockedFunction<typeof ActiveCall>).mockImplementation(
|
(ActiveCall as MockedFunction<typeof ActiveCall>).mockImplementation(
|
||||||
@@ -206,6 +208,7 @@ test("GroupCallView plays a leave sound synchronously in widget mode", async ()
|
|||||||
);
|
);
|
||||||
(useAudioContext as MockedFunction<typeof useAudioContext>).mockReturnValue({
|
(useAudioContext as MockedFunction<typeof useAudioContext>).mockReturnValue({
|
||||||
playSound,
|
playSound,
|
||||||
|
playSoundLooping: vitest.fn(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { getByText, rtcSession } = createGroupCallView(
|
const { getByText, rtcSession } = createGroupCallView(
|
||||||
|
|||||||
@@ -287,9 +287,6 @@ export const InCallView: FC<InCallViewProps> = ({
|
|||||||
timeout: { mp3: timeoutMp3, ogg: timeoutOgg },
|
timeout: { mp3: timeoutMp3, ogg: timeoutOgg },
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
// configure this to sth that fits to the pickup waiting sound.
|
|
||||||
// 1600 is in sync with the animation.
|
|
||||||
const PICKUP_SOUND_INTERVAL = 1600;
|
|
||||||
|
|
||||||
const pickupPhaseAudio = useAudioContext({
|
const pickupPhaseAudio = useAudioContext({
|
||||||
sounds: pickupPhaseSoundCache,
|
sounds: pickupPhaseSoundCache,
|
||||||
@@ -386,11 +383,13 @@ export const InCallView: FC<InCallViewProps> = ({
|
|||||||
|
|
||||||
// When waiting for pickup, loop a waiting sound
|
// When waiting for pickup, loop a waiting sound
|
||||||
useEffect((): void | (() => void) => {
|
useEffect((): void | (() => void) => {
|
||||||
if (callPickupState !== "ringing") return;
|
if (callPickupState !== "ringing" || !pickupPhaseAudio) return;
|
||||||
const interval = window.setInterval(() => {
|
const endSound = pickupPhaseAudio.playSoundLooping("waiting");
|
||||||
void pickupPhaseAudio?.playSound("waiting");
|
return () => {
|
||||||
}, PICKUP_SOUND_INTERVAL);
|
void endSound().catch((e) => {
|
||||||
return (): void => window.clearInterval(interval);
|
logger.error("Failed to stop ringing sound", e);
|
||||||
|
});
|
||||||
|
};
|
||||||
}, [callPickupState, pickupPhaseAudio]);
|
}, [callPickupState, pickupPhaseAudio]);
|
||||||
|
|
||||||
// Waiting UI overlay
|
// Waiting UI overlay
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ beforeEach(() => {
|
|||||||
playSound = vitest.fn();
|
playSound = vitest.fn();
|
||||||
(useAudioContext as MockedFunction<typeof useAudioContext>).mockReturnValue({
|
(useAudioContext as MockedFunction<typeof useAudioContext>).mockReturnValue({
|
||||||
playSound,
|
playSound,
|
||||||
|
playSoundLooping: vitest.fn(),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -46,6 +46,40 @@ async function playSound(
|
|||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Play a sound though a given AudioContext, looping until stopped. Will take
|
||||||
|
* care of connecting the correct buffer and gating
|
||||||
|
* through gain.
|
||||||
|
* @param volume The volume to play at.
|
||||||
|
* @param ctx The context to play through.
|
||||||
|
* @param buffer The buffer to play.
|
||||||
|
* @returns A function used to end the sound. This function will return a promise when the sound has stopped.
|
||||||
|
*/
|
||||||
|
function playSoundLooping(
|
||||||
|
ctx: AudioContext,
|
||||||
|
buffer: AudioBuffer,
|
||||||
|
volume: number,
|
||||||
|
stereoPan: number,
|
||||||
|
): () => Promise<void> {
|
||||||
|
const gain = ctx.createGain();
|
||||||
|
gain.gain.setValueAtTime(volume, 0);
|
||||||
|
const pan = ctx.createStereoPanner();
|
||||||
|
pan.pan.setValueAtTime(stereoPan, 0);
|
||||||
|
const src = ctx.createBufferSource();
|
||||||
|
src.buffer = buffer;
|
||||||
|
src.connect(gain).connect(pan).connect(ctx.destination);
|
||||||
|
controls.setPlaybackStarted();
|
||||||
|
src.loop = true;
|
||||||
|
src.start();
|
||||||
|
return () => {
|
||||||
|
const p = new Promise<void>((r) =>
|
||||||
|
src.addEventListener("ended", () => r()),
|
||||||
|
);
|
||||||
|
src.stop();
|
||||||
|
return p;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
interface Props<S extends string> {
|
interface Props<S extends string> {
|
||||||
/**
|
/**
|
||||||
* The sounds to play. If no sounds should be played then
|
* The sounds to play. If no sounds should be played then
|
||||||
@@ -59,6 +93,7 @@ interface Props<S extends string> {
|
|||||||
|
|
||||||
interface UseAudioContext<S> {
|
interface UseAudioContext<S> {
|
||||||
playSound(soundName: S): Promise<void>;
|
playSound(soundName: S): Promise<void>;
|
||||||
|
playSoundLooping(soundName: S): () => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -146,5 +181,16 @@ export function useAudioContext<S extends string>(
|
|||||||
earpiecePan,
|
earpiecePan,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
playSoundLooping: (name): (() => Promise<void>) => {
|
||||||
|
if (!audioBuffers[name]) {
|
||||||
|
throw Error(`Tried to play a sound that wasn't buffered (${name})`);
|
||||||
|
}
|
||||||
|
return playSoundLooping(
|
||||||
|
audioContext,
|
||||||
|
audioBuffers[name],
|
||||||
|
soundEffectVolume * earpieceVolume,
|
||||||
|
earpiecePan,
|
||||||
|
);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user