diff --git a/src/LegacyCallHandler.tsx b/src/LegacyCallHandler.tsx index 7e5f23fc3f6..c8ee1d5c74a 100644 --- a/src/LegacyCallHandler.tsx +++ b/src/LegacyCallHandler.tsx @@ -457,6 +457,15 @@ export default class LegacyCallHandler extends EventEmitter { logger.debug(`${logPrefix} paused audio`); } + /** + * Returns whether the given audio is currently playing + * Only supported for looping audio tracks + * @param audioId the ID of the audio to query for playing state + */ + public isPlaying(audioId: AudioID.Ring | AudioID.Ringback): boolean { + return !!this.playingSources[audioId]; + } + private matchesCallForThisRoom(call: MatrixCall): boolean { // We don't allow placing more than one call per room, but that doesn't mean there // can't be more than one, eg. in a glare situation. This checks that the given call diff --git a/src/toasts/IncomingCallToast.tsx b/src/toasts/IncomingCallToast.tsx index e0b7944495c..2546a76d08e 100644 --- a/src/toasts/IncomingCallToast.tsx +++ b/src/toasts/IncomingCallToast.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { useCallback, useEffect, useMemo, useState } from "react"; +import React, { useCallback, useEffect, useState } from "react"; import { MatrixEvent } from "matrix-js-sdk/src/matrix"; import { Button, Tooltip } from "@vector-im/compound-web"; import { Icon as VideoCallIcon } from "@vector-im/compound-design-tokens/icons/video-call-solid.svg"; @@ -36,7 +36,7 @@ import AccessibleButton, { ButtonEvent } from "../components/views/elements/Acce import { useDispatcher } from "../hooks/useDispatcher"; import { ActionPayload } from "../dispatcher/payloads"; import { Call } from "../models/Call"; -import { AudioID } from "../LegacyCallHandler"; +import LegacyCallHandler, { AudioID } from "../LegacyCallHandler"; import { useEventEmitter } from "../hooks/useEventEmitter"; import { CallStore, CallStoreEvent } from "../stores/CallStore"; @@ -78,7 +78,6 @@ export function IncomingCallToast({ notifyEvent }: Props): JSX.Element { const roomId = notifyEvent.getRoomId()!; const room = MatrixClientPeg.safeGet().getRoom(roomId) ?? undefined; const call = useCall(roomId); - const audio = useMemo(() => document.getElementById(AudioID.Ring) as HTMLMediaElement, []); const [connectedCalls, setConnectedCalls] = useState(Array.from(CallStore.instance.connectedCalls)); useEventEmitter(CallStore.instance, CallStoreEvent.ConnectedCalls, () => { setConnectedCalls(Array.from(CallStore.instance.connectedCalls)); @@ -87,18 +86,18 @@ export function IncomingCallToast({ notifyEvent }: Props): JSX.Element { // Start ringing if not already. useEffect(() => { const isRingToast = (notifyEvent.getContent() as unknown as { notify_type: string })["notify_type"] == "ring"; - if (isRingToast && audio.paused) { - audio.play(); + if (isRingToast && !LegacyCallHandler.instance.isPlaying(AudioID.Ring)) { + LegacyCallHandler.instance.play(AudioID.Ring); } - }, [audio, notifyEvent]); + }, [notifyEvent]); // Stop ringing on dismiss. const dismissToast = useCallback((): void => { ToastStore.sharedInstance().dismissToast( getIncomingCallToastKey(notifyEvent.getContent().call_id ?? "", roomId), ); - audio.pause(); - }, [audio, notifyEvent, roomId]); + LegacyCallHandler.instance.pause(AudioID.Ring); + }, [notifyEvent, roomId]); // Dismiss if session got ended remotely. const onCall = useCallback( diff --git a/test/setup/mocks.ts b/test/setup/mocks.ts index d298967d0f8..054fff12fb6 100644 --- a/test/setup/mocks.ts +++ b/test/setup/mocks.ts @@ -18,6 +18,7 @@ export const mocks = { AudioBufferSourceNode: { connect: jest.fn(), start: jest.fn(), + stop: jest.fn(), } as unknown as AudioBufferSourceNode, AudioContext: { close: jest.fn(), diff --git a/test/toasts/IncomingCallToast-test.tsx b/test/toasts/IncomingCallToast-test.tsx index 1745e2f036d..d26a0893ca7 100644 --- a/test/toasts/IncomingCallToast-test.tsx +++ b/test/toasts/IncomingCallToast-test.tsx @@ -39,11 +39,10 @@ import { WidgetMessagingStore } from "../../src/stores/widgets/WidgetMessagingSt import DMRoomMap from "../../src/utils/DMRoomMap"; import ToastStore from "../../src/stores/ToastStore"; import { getIncomingCallToastKey, IncomingCallToast } from "../../src/toasts/IncomingCallToast"; -import { AudioID } from "../../src/LegacyCallHandler"; +import LegacyCallHandler, { AudioID } from "../../src/LegacyCallHandler"; -describe("IncomingCallEvent", () => { +describe("IncomingCallToast", () => { useMockedCalls(); - jest.spyOn(HTMLMediaElement.prototype, "play").mockImplementation(async () => {}); let client: Mocked; let room: Room; @@ -133,10 +132,8 @@ describe("IncomingCallEvent", () => { ...notifyContent, notify_type: "ring", }) as any; - const playMock = jest.fn(); - const audio = { play: playMock, paused: true }; - jest.spyOn(document, "getElementById").mockReturnValue(audio as any); + const playMock = jest.spyOn(LegacyCallHandler.instance, "play"); render(); expect(playMock).toHaveBeenCalled(); });