signal-desktop/ts/util/Sound.ts

144 lines
3.3 KiB
TypeScript
Raw Normal View History

2020-10-30 15:34:04 -05:00
// Copyright 2020 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import * as log from '../logging/log';
import { missingCaseError } from './missingCaseError';
export enum SoundType {
CallingHangUp,
CallingHandRaised,
CallingPresenting,
Pop,
Ringtone,
TriTone,
VoiceNoteEnd,
VoiceNoteStart,
Whoosh,
}
2020-06-04 11:16:19 -07:00
export type SoundOpts = {
loop?: boolean;
soundType: SoundType;
2020-06-04 11:16:19 -07:00
};
export class Sound {
static sounds = new Map<SoundType, AudioBuffer>();
2020-06-04 11:16:19 -07:00
2023-02-08 07:05:50 -08:00
private static context: AudioContext | undefined;
readonly #loop: boolean;
#node?: AudioBufferSourceNode;
readonly #soundType: SoundType;
2020-06-04 11:16:19 -07:00
constructor(options: SoundOpts) {
this.#loop = Boolean(options.loop);
this.#soundType = options.soundType;
2020-06-04 11:16:19 -07:00
}
async play(): Promise<void> {
let soundBuffer = Sound.sounds.get(this.#soundType);
if (!soundBuffer) {
2020-06-04 11:16:19 -07:00
try {
const src = Sound.getSrc(this.#soundType);
const buffer = await Sound.loadSoundFile(src);
const decodedBuffer = await this.#context.decodeAudioData(buffer);
Sound.sounds.set(this.#soundType, decodedBuffer);
soundBuffer = decodedBuffer;
2020-06-04 11:16:19 -07:00
} catch (err) {
log.error(`Sound error: ${err}`);
2020-06-04 11:16:19 -07:00
return;
}
}
const soundNode = this.#context.createBufferSource();
2020-06-04 11:16:19 -07:00
soundNode.buffer = soundBuffer;
const volumeNode = this.#context.createGain();
2020-06-04 11:16:19 -07:00
soundNode.connect(volumeNode);
volumeNode.connect(this.#context.destination);
2020-06-04 11:16:19 -07:00
soundNode.loop = this.#loop;
2020-06-04 11:16:19 -07:00
soundNode.start(0, 0);
this.#node = soundNode;
2020-06-04 11:16:19 -07:00
}
stop(): void {
if (this.#node) {
this.#node.stop(0);
this.#node = undefined;
2020-06-04 11:16:19 -07:00
}
}
get #context(): AudioContext {
2023-02-08 07:05:50 -08:00
if (!Sound.context) {
Sound.context = new AudioContext();
}
return Sound.context;
}
2020-06-04 11:16:19 -07: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();
});
}
static getSrc(soundStyle: SoundType): string {
if (soundStyle === SoundType.CallingHandRaised) {
return 'sounds/notification_simple-01.ogg';
}
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 11:51:11 -04:00
return 'sounds/pop.ogg';
}
if (soundStyle === SoundType.TriTone) {
return 'sounds/notification.ogg';
}
if (soundStyle === SoundType.Ringtone) {
return 'sounds/ringtone_minimal.ogg';
}
if (soundStyle === SoundType.VoiceNoteEnd) {
return 'sounds/state-change_confirm-up.ogg';
}
if (soundStyle === SoundType.VoiceNoteStart) {
return 'sounds/state-change_confirm-down.ogg';
}
if (soundStyle === SoundType.Whoosh) {
2023-05-09 11:51:11 -04:00
return 'sounds/whoosh.ogg';
}
throw missingCaseError(soundStyle);
}
2020-06-04 11:16:19 -07:00
}