diff --git a/src/voice-broadcast/models/VoiceBroadcastRecording.ts b/src/voice-broadcast/models/VoiceBroadcastRecording.ts index af69b7b710c..64037816747 100644 --- a/src/voice-broadcast/models/VoiceBroadcastRecording.ts +++ b/src/voice-broadcast/models/VoiceBroadcastRecording.ts @@ -46,6 +46,7 @@ import { ActionPayload } from "../../dispatcher/payloads"; import { VoiceBroadcastChunkEvents } from "../utils/VoiceBroadcastChunkEvents"; import { RelationsHelper, RelationsHelperEvent } from "../../events/RelationsHelper"; import { createReconnectedListener } from "../../utils/connection"; +import { localNotificationsAreSilenced } from "../../utils/notifications"; export enum VoiceBroadcastRecordingEvent { StateChanged = "liveness_changed", @@ -333,10 +334,20 @@ export class VoiceBroadcastRecording * It sets the connection error state and stops the recorder. */ private async onConnectionError(): Promise { + this.playConnectionErrorAudioNotification(); await this.stopRecorder(false); this.setState("connection_error"); } + private playConnectionErrorAudioNotification(): void { + if (localNotificationsAreSilenced(this.client)) { + return; + } + + const audioElement = document.querySelector("audio#messageAudio"); + audioElement?.play(); + } + private async uploadFile(chunk: ChunkRecordedPayload): ReturnType { return uploadFile( this.client, diff --git a/test/voice-broadcast/models/VoiceBroadcastRecording-test.ts b/test/voice-broadcast/models/VoiceBroadcastRecording-test.ts index c5194b7df99..8b7d90a8073 100644 --- a/test/voice-broadcast/models/VoiceBroadcastRecording-test.ts +++ b/test/voice-broadcast/models/VoiceBroadcastRecording-test.ts @@ -19,6 +19,7 @@ import { ClientEvent, EventTimelineSet, EventType, + LOCAL_NOTIFICATION_SETTINGS_PREFIX, MatrixClient, MatrixEvent, MatrixEventEvent, @@ -89,6 +90,7 @@ describe("VoiceBroadcastRecording", () => { let voiceBroadcastRecording: VoiceBroadcastRecording; let onStateChanged: (state: VoiceBroadcastRecordingState) => void; let voiceBroadcastRecorder: VoiceBroadcastRecorder; + let audioElement: HTMLAudioElement; const mkVoiceBroadcastInfoEvent = (content: VoiceBroadcastInfoEventContent) => { return mkEvent({ @@ -251,6 +253,18 @@ describe("VoiceBroadcastRecording", () => { }; }, ); + + audioElement = { + play: jest.fn(), + } as any as HTMLAudioElement; + + jest.spyOn(document, "querySelector").mockImplementation((selector: string) => { + if (selector === "audio#messageAudio") { + return audioElement; + } + + return null; + }); }); afterEach(() => { @@ -501,6 +515,33 @@ describe("VoiceBroadcastRecording", () => { }); }); + describe("and audible notifications are disabled", () => { + beforeEach(() => { + const notificationSettings = mkEvent({ + event: true, + type: `${LOCAL_NOTIFICATION_SETTINGS_PREFIX.name}.${client.getDeviceId()}`, + user: client.getSafeUserId(), + content: { + is_silenced: true, + }, + }); + mocked(client.getAccountData).mockReturnValue(notificationSettings); + }); + + describe("and a chunk has been recorded and sending the voice message fails", () => { + beforeEach(() => { + mocked(client.sendMessage).mockRejectedValue("Error"); + emitFirsChunkRecorded(); + }); + + itShouldBeInState("connection_error"); + + it("should not play a notification", () => { + expect(audioElement.play).not.toHaveBeenCalled(); + }); + }); + }); + describe("and a chunk has been recorded and sending the voice message fails", () => { beforeEach(() => { mocked(client.sendMessage).mockRejectedValue("Error"); @@ -509,6 +550,10 @@ describe("VoiceBroadcastRecording", () => { itShouldBeInState("connection_error"); + it("should play a notification", () => { + expect(audioElement.play).toHaveBeenCalled(); + }); + describe("and the connection is back", () => { beforeEach(() => { mocked(client.sendMessage).mockClear();