Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Commit 0a6fe83

Browse files
authored
Add audible notifcation on broadcast error (#10654)
* Add audible notifcation on broadcast error * Add error audio file * Add error ogg * Catch play broadcast error * Await play error sound * Add promise error handling * Add comment about audio elements
1 parent 9aade5a commit 0a6fe83

File tree

4 files changed

+65
-0
lines changed

4 files changed

+65
-0
lines changed

res/media/error.mp3

9.27 KB
Binary file not shown.

res/media/error.ogg

13.1 KB
Binary file not shown.

src/voice-broadcast/models/VoiceBroadcastRecording.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ import { ActionPayload } from "../../dispatcher/payloads";
4646
import { VoiceBroadcastChunkEvents } from "../utils/VoiceBroadcastChunkEvents";
4747
import { RelationsHelper, RelationsHelperEvent } from "../../events/RelationsHelper";
4848
import { createReconnectedListener } from "../../utils/connection";
49+
import { localNotificationsAreSilenced } from "../../utils/notifications";
4950

5051
export enum VoiceBroadcastRecordingEvent {
5152
StateChanged = "liveness_changed",
@@ -333,10 +334,29 @@ export class VoiceBroadcastRecording
333334
* It sets the connection error state and stops the recorder.
334335
*/
335336
private async onConnectionError(): Promise<void> {
337+
this.playConnectionErrorAudioNotification().catch(() => {
338+
// Error logged in playConnectionErrorAudioNotification().
339+
});
336340
await this.stopRecorder(false);
337341
this.setState("connection_error");
338342
}
339343

344+
private async playConnectionErrorAudioNotification(): Promise<void> {
345+
if (localNotificationsAreSilenced(this.client)) {
346+
return;
347+
}
348+
349+
// Audio files are added to the document in Element Web.
350+
// See <audio> elements in https://github.com/vector-im/element-web/blob/develop/src/vector/index.html
351+
const audioElement = document.querySelector<HTMLAudioElement>("audio#errorAudio");
352+
353+
try {
354+
await audioElement?.play();
355+
} catch (e) {
356+
logger.warn("error playing 'errorAudio'", e);
357+
}
358+
}
359+
340360
private async uploadFile(chunk: ChunkRecordedPayload): ReturnType<typeof uploadFile> {
341361
return uploadFile(
342362
this.client,

test/voice-broadcast/models/VoiceBroadcastRecording-test.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
ClientEvent,
2020
EventTimelineSet,
2121
EventType,
22+
LOCAL_NOTIFICATION_SETTINGS_PREFIX,
2223
MatrixClient,
2324
MatrixEvent,
2425
MatrixEventEvent,
@@ -89,6 +90,7 @@ describe("VoiceBroadcastRecording", () => {
8990
let voiceBroadcastRecording: VoiceBroadcastRecording;
9091
let onStateChanged: (state: VoiceBroadcastRecordingState) => void;
9192
let voiceBroadcastRecorder: VoiceBroadcastRecorder;
93+
let audioElement: HTMLAudioElement;
9294

9395
const mkVoiceBroadcastInfoEvent = (content: VoiceBroadcastInfoEventContent) => {
9496
return mkEvent({
@@ -251,6 +253,18 @@ describe("VoiceBroadcastRecording", () => {
251253
};
252254
},
253255
);
256+
257+
audioElement = {
258+
play: jest.fn(),
259+
} as any as HTMLAudioElement;
260+
261+
jest.spyOn(document, "querySelector").mockImplementation((selector: string) => {
262+
if (selector === "audio#errorAudio") {
263+
return audioElement;
264+
}
265+
266+
return null;
267+
});
254268
});
255269

256270
afterEach(() => {
@@ -501,6 +515,33 @@ describe("VoiceBroadcastRecording", () => {
501515
});
502516
});
503517

518+
describe("and audible notifications are disabled", () => {
519+
beforeEach(() => {
520+
const notificationSettings = mkEvent({
521+
event: true,
522+
type: `${LOCAL_NOTIFICATION_SETTINGS_PREFIX.name}.${client.getDeviceId()}`,
523+
user: client.getSafeUserId(),
524+
content: {
525+
is_silenced: true,
526+
},
527+
});
528+
mocked(client.getAccountData).mockReturnValue(notificationSettings);
529+
});
530+
531+
describe("and a chunk has been recorded and sending the voice message fails", () => {
532+
beforeEach(() => {
533+
mocked(client.sendMessage).mockRejectedValue("Error");
534+
emitFirsChunkRecorded();
535+
});
536+
537+
itShouldBeInState("connection_error");
538+
539+
it("should not play a notification", () => {
540+
expect(audioElement.play).not.toHaveBeenCalled();
541+
});
542+
});
543+
});
544+
504545
describe("and a chunk has been recorded and sending the voice message fails", () => {
505546
beforeEach(() => {
506547
mocked(client.sendMessage).mockRejectedValue("Error");
@@ -509,6 +550,10 @@ describe("VoiceBroadcastRecording", () => {
509550

510551
itShouldBeInState("connection_error");
511552

553+
it("should play a notification", () => {
554+
expect(audioElement.play).toHaveBeenCalled();
555+
});
556+
512557
describe("and the connection is back", () => {
513558
beforeEach(() => {
514559
mocked(client.sendMessage).mockClear();

0 commit comments

Comments
 (0)