Ensure call sound effects are played over the correct sink (#2863)
* Refactor to use AudioContext * Remove unused audio format. * Reduce update frequency for volume * Port to useAudioContext * Port reactionaudiorenderer to useAudioContext * Integrate raise hand sound into call event renderer. * Simplify reaction sounds * only play one sound per reaction type * Start to build out tests * fixup tests / comments * Fix reaction sound * remove console line * Remove another debug line. * fix lint * Use testing library click * lint * fix a few things * Change the way we as unknown the mock RTC session. * Lint * Fix types for MockRTCSession * value change should always be set * Update volume slider description. * Only load reaction sound effects if enabled. * cache improvements * lowercase soundMap * lint * move prefetch sounds to fix hot reload * correct docs * add a header * Wording change --------- Co-authored-by: Hugh Nimmo-Smith <hughns@element.io>
This commit is contained in:
@@ -6,9 +6,18 @@ Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { render } from "@testing-library/react";
|
||||
import { afterAll, expect, test } from "vitest";
|
||||
import {
|
||||
afterAll,
|
||||
beforeEach,
|
||||
expect,
|
||||
test,
|
||||
vitest,
|
||||
MockedFunction,
|
||||
Mock,
|
||||
} from "vitest";
|
||||
import { TooltipProvider } from "@vector-im/compound-web";
|
||||
import { act, ReactNode } from "react";
|
||||
import { afterEach } from "node:test";
|
||||
|
||||
import {
|
||||
MockRoom,
|
||||
@@ -16,12 +25,13 @@ import {
|
||||
TestReactionsWrapper,
|
||||
} from "../utils/testReactions";
|
||||
import { ReactionsAudioRenderer } from "./ReactionAudioRenderer";
|
||||
import { GenericReaction, ReactionSet } from "../reactions";
|
||||
import {
|
||||
playReactionsSound,
|
||||
soundEffectVolumeSetting,
|
||||
} from "../settings/settings";
|
||||
import { mockMediaPlay } from "../utils/test";
|
||||
import { useAudioContext } from "../useAudioContext";
|
||||
import { GenericReaction, ReactionSet } from "../reactions";
|
||||
import { prefetchSounds } from "../soundUtils";
|
||||
|
||||
const memberUserIdAlice = "@alice:example.org";
|
||||
const memberUserIdBob = "@bob:example.org";
|
||||
@@ -50,11 +60,31 @@ function TestComponent({
|
||||
);
|
||||
}
|
||||
|
||||
const originalPlayFn = window.HTMLMediaElement.prototype.play;
|
||||
afterAll(() => {
|
||||
vitest.mock("../useAudioContext");
|
||||
vitest.mock("../soundUtils");
|
||||
|
||||
afterEach(() => {
|
||||
vitest.resetAllMocks();
|
||||
playReactionsSound.setValue(playReactionsSound.defaultValue);
|
||||
soundEffectVolumeSetting.setValue(soundEffectVolumeSetting.defaultValue);
|
||||
window.HTMLMediaElement.prototype.play = originalPlayFn;
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
vitest.restoreAllMocks();
|
||||
});
|
||||
|
||||
let playSound: Mock<
|
||||
NonNullable<ReturnType<typeof useAudioContext>>["playSound"]
|
||||
>;
|
||||
|
||||
beforeEach(() => {
|
||||
(prefetchSounds as MockedFunction<typeof prefetchSounds>).mockResolvedValue({
|
||||
sound: new ArrayBuffer(0),
|
||||
});
|
||||
playSound = vitest.fn();
|
||||
(useAudioContext as MockedFunction<typeof useAudioContext>).mockReturnValue({
|
||||
playSound,
|
||||
});
|
||||
});
|
||||
|
||||
test("preloads all audio elements", () => {
|
||||
@@ -63,25 +93,11 @@ test("preloads all audio elements", () => {
|
||||
new MockRoom(memberUserIdAlice),
|
||||
membership,
|
||||
);
|
||||
const { container } = render(<TestComponent rtcSession={rtcSession} />);
|
||||
expect(container.getElementsByTagName("audio")).toHaveLength(
|
||||
// All reactions plus the generic sound
|
||||
ReactionSet.filter((r) => r.sound).length + 1,
|
||||
);
|
||||
});
|
||||
|
||||
test("loads no audio elements when disabled in settings", () => {
|
||||
playReactionsSound.setValue(false);
|
||||
const rtcSession = new MockRTCSession(
|
||||
new MockRoom(memberUserIdAlice),
|
||||
membership,
|
||||
);
|
||||
const { container } = render(<TestComponent rtcSession={rtcSession} />);
|
||||
expect(container.getElementsByTagName("audio")).toHaveLength(0);
|
||||
render(<TestComponent rtcSession={rtcSession} />);
|
||||
expect(prefetchSounds).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
test("will play an audio sound when there is a reaction", () => {
|
||||
const audioIsPlaying: string[] = mockMediaPlay();
|
||||
playReactionsSound.setValue(true);
|
||||
const room = new MockRoom(memberUserIdAlice);
|
||||
const rtcSession = new MockRTCSession(room, membership);
|
||||
@@ -97,12 +113,10 @@ test("will play an audio sound when there is a reaction", () => {
|
||||
act(() => {
|
||||
room.testSendReaction(memberEventAlice, chosenReaction, membership);
|
||||
});
|
||||
expect(audioIsPlaying).toHaveLength(1);
|
||||
expect(audioIsPlaying[0]).toContain(chosenReaction.sound?.ogg);
|
||||
expect(playSound).toHaveBeenCalledWith(chosenReaction.name);
|
||||
});
|
||||
|
||||
test("will play the generic audio sound when there is soundless reaction", () => {
|
||||
const audioIsPlaying: string[] = mockMediaPlay();
|
||||
playReactionsSound.setValue(true);
|
||||
const room = new MockRoom(memberUserIdAlice);
|
||||
const rtcSession = new MockRTCSession(room, membership);
|
||||
@@ -118,34 +132,10 @@ test("will play the generic audio sound when there is soundless reaction", () =>
|
||||
act(() => {
|
||||
room.testSendReaction(memberEventAlice, chosenReaction, membership);
|
||||
});
|
||||
expect(audioIsPlaying).toHaveLength(1);
|
||||
expect(audioIsPlaying[0]).toContain(GenericReaction.sound?.ogg);
|
||||
});
|
||||
|
||||
test("will play an audio sound with the correct volume", () => {
|
||||
playReactionsSound.setValue(true);
|
||||
soundEffectVolumeSetting.setValue(0.5);
|
||||
const room = new MockRoom(memberUserIdAlice);
|
||||
const rtcSession = new MockRTCSession(room, membership);
|
||||
const { getByTestId } = render(<TestComponent rtcSession={rtcSession} />);
|
||||
|
||||
// Find the first reaction with a sound effect
|
||||
const chosenReaction = ReactionSet.find((r) => !!r.sound);
|
||||
if (!chosenReaction) {
|
||||
throw Error(
|
||||
"No reactions have sounds configured, this test cannot succeed",
|
||||
);
|
||||
}
|
||||
act(() => {
|
||||
room.testSendReaction(memberEventAlice, chosenReaction, membership);
|
||||
});
|
||||
expect((getByTestId(chosenReaction.name) as HTMLAudioElement).volume).toEqual(
|
||||
0.5,
|
||||
);
|
||||
expect(playSound).toHaveBeenCalledWith(GenericReaction.name);
|
||||
});
|
||||
|
||||
test("will play multiple audio sounds when there are multiple different reactions", () => {
|
||||
const audioIsPlaying: string[] = mockMediaPlay();
|
||||
playReactionsSound.setValue(true);
|
||||
|
||||
const room = new MockRoom(memberUserIdAlice);
|
||||
@@ -164,7 +154,6 @@ test("will play multiple audio sounds when there are multiple different reaction
|
||||
room.testSendReaction(memberEventBob, reaction2, membership);
|
||||
room.testSendReaction(memberEventCharlie, reaction1, membership);
|
||||
});
|
||||
expect(audioIsPlaying).toHaveLength(2);
|
||||
expect(audioIsPlaying[0]).toContain(reaction1.sound?.ogg);
|
||||
expect(audioIsPlaying[1]).toContain(reaction2.sound?.ogg);
|
||||
expect(playSound).toHaveBeenCalledWith(reaction1.name);
|
||||
expect(playSound).toHaveBeenCalledWith(reaction2.name);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user