2020-10-30 20:34:04 +00:00
|
|
|
// Copyright 2020 Signal Messenger, LLC
|
|
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
|
2021-09-17 18:27:53 +00:00
|
|
|
import * as log from '../logging/log';
|
2023-05-08 19:59:36 +00:00
|
|
|
import { missingCaseError } from './missingCaseError';
|
|
|
|
|
|
|
|
export enum SoundType {
|
|
|
|
CallingHangUp,
|
2024-01-11 05:57:36 +00:00
|
|
|
CallingHandRaised,
|
2023-05-08 19:59:36 +00:00
|
|
|
CallingPresenting,
|
|
|
|
Pop,
|
|
|
|
Ringtone,
|
|
|
|
TriTone,
|
|
|
|
VoiceNoteEnd,
|
|
|
|
VoiceNoteStart,
|
|
|
|
Whoosh,
|
|
|
|
}
|
2021-09-17 18:27:53 +00:00
|
|
|
|
2020-06-04 18:16:19 +00:00
|
|
|
export type SoundOpts = {
|
|
|
|
loop?: boolean;
|
2023-05-08 19:59:36 +00:00
|
|
|
soundType: SoundType;
|
2020-06-04 18:16:19 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
export class Sound {
|
2023-05-08 19:59:36 +00:00
|
|
|
static sounds = new Map<SoundType, AudioBuffer>();
|
2020-06-04 18:16:19 +00:00
|
|
|
|
2023-02-08 15:05:50 +00:00
|
|
|
private static context: AudioContext | undefined;
|
2020-09-14 21:56:35 +00:00
|
|
|
|
2020-06-04 18:16:19 +00:00
|
|
|
private readonly loop: boolean;
|
2020-09-14 21:56:35 +00:00
|
|
|
|
2020-06-04 18:16:19 +00:00
|
|
|
private node?: AudioBufferSourceNode;
|
2020-09-14 21:56:35 +00:00
|
|
|
|
2023-05-08 19:59:36 +00:00
|
|
|
private readonly soundType: SoundType;
|
2020-06-04 18:16:19 +00:00
|
|
|
|
|
|
|
constructor(options: SoundOpts) {
|
|
|
|
this.loop = Boolean(options.loop);
|
2023-05-08 19:59:36 +00:00
|
|
|
this.soundType = options.soundType;
|
2020-06-04 18:16:19 +00:00
|
|
|
}
|
|
|
|
|
2020-09-14 21:56:35 +00:00
|
|
|
async play(): Promise<void> {
|
2023-05-08 19:59:36 +00:00
|
|
|
let soundBuffer = Sound.sounds.get(this.soundType);
|
|
|
|
|
|
|
|
if (!soundBuffer) {
|
2020-06-04 18:16:19 +00:00
|
|
|
try {
|
2023-05-08 19:59:36 +00:00
|
|
|
const src = Sound.getSrc(this.soundType);
|
|
|
|
const buffer = await Sound.loadSoundFile(src);
|
2020-06-04 18:16:19 +00:00
|
|
|
const decodedBuffer = await this.context.decodeAudioData(buffer);
|
2023-05-08 19:59:36 +00:00
|
|
|
Sound.sounds.set(this.soundType, decodedBuffer);
|
|
|
|
soundBuffer = decodedBuffer;
|
2020-06-04 18:16:19 +00:00
|
|
|
} catch (err) {
|
2021-09-17 18:27:53 +00:00
|
|
|
log.error(`Sound error: ${err}`);
|
2020-06-04 18:16:19 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const soundNode = this.context.createBufferSource();
|
|
|
|
soundNode.buffer = soundBuffer;
|
|
|
|
|
|
|
|
const volumeNode = this.context.createGain();
|
|
|
|
soundNode.connect(volumeNode);
|
|
|
|
volumeNode.connect(this.context.destination);
|
|
|
|
|
|
|
|
soundNode.loop = this.loop;
|
|
|
|
|
|
|
|
soundNode.start(0, 0);
|
|
|
|
|
|
|
|
this.node = soundNode;
|
|
|
|
}
|
|
|
|
|
2020-09-14 21:56:35 +00:00
|
|
|
stop(): void {
|
2020-06-04 18:16:19 +00:00
|
|
|
if (this.node) {
|
|
|
|
this.node.stop(0);
|
|
|
|
this.node = undefined;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-08 15:05:50 +00:00
|
|
|
private get context(): AudioContext {
|
|
|
|
if (!Sound.context) {
|
|
|
|
Sound.context = new AudioContext();
|
|
|
|
}
|
|
|
|
return Sound.context;
|
|
|
|
}
|
|
|
|
|
2020-06-04 18:16:19 +00:00
|
|
|
static async loadSoundFile(src: string): Promise<ArrayBuffer> {
|
|
|
|
const xhr = new XMLHttpRequest();
|
|
|
|
|
|
|
|
xhr.open('GET', src, true);
|
|
|
|
xhr.responseType = 'arraybuffer';
|
|
|
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
xhr.onload = () => {
|
|
|
|
if (xhr.status === 200) {
|
|
|
|
resolve(xhr.response);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
reject(new Error(`Request failed: ${xhr.statusText}`));
|
|
|
|
};
|
|
|
|
xhr.onerror = () => {
|
|
|
|
reject(new Error(`Request failed, most likely file not found: ${src}`));
|
|
|
|
};
|
|
|
|
xhr.send();
|
|
|
|
});
|
|
|
|
}
|
2023-05-08 19:59:36 +00:00
|
|
|
|
|
|
|
static getSrc(soundStyle: SoundType): string {
|
2024-01-11 05:57:36 +00:00
|
|
|
if (soundStyle === SoundType.CallingHandRaised) {
|
|
|
|
return 'sounds/notification_simple-01.ogg';
|
|
|
|
}
|
|
|
|
|
2023-05-08 19:59:36 +00:00
|
|
|
if (soundStyle === SoundType.CallingHangUp) {
|
|
|
|
return 'sounds/navigation-cancel.ogg';
|
|
|
|
}
|
|
|
|
|
|
|
|
if (soundStyle === SoundType.CallingPresenting) {
|
|
|
|
return 'sounds/navigation_selection-complete-celebration.ogg';
|
|
|
|
}
|
|
|
|
|
|
|
|
if (soundStyle === SoundType.Pop) {
|
2023-05-09 15:51:11 +00:00
|
|
|
return 'sounds/pop.ogg';
|
2023-05-08 19:59:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (soundStyle === SoundType.TriTone) {
|
|
|
|
return 'sounds/notification.ogg';
|
|
|
|
}
|
|
|
|
|
|
|
|
if (soundStyle === SoundType.Ringtone) {
|
|
|
|
return 'sounds/ringtone_minimal.ogg';
|
|
|
|
}
|
|
|
|
|
|
|
|
if (soundStyle === SoundType.VoiceNoteEnd) {
|
2023-09-06 18:06:30 +00:00
|
|
|
return 'sounds/state-change_confirm-up.ogg';
|
2023-05-08 19:59:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (soundStyle === SoundType.VoiceNoteStart) {
|
2023-09-06 18:06:30 +00:00
|
|
|
return 'sounds/state-change_confirm-down.ogg';
|
2023-05-08 19:59:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (soundStyle === SoundType.Whoosh) {
|
2023-05-09 15:51:11 +00:00
|
|
|
return 'sounds/whoosh.ogg';
|
2023-05-08 19:59:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
throw missingCaseError(soundStyle);
|
|
|
|
}
|
2020-06-04 18:16:19 +00:00
|
|
|
}
|