summaryrefslogtreecommitdiff
path: root/packages/frontend/src/scripts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/frontend/src/scripts')
-rw-r--r--packages/frontend/src/scripts/sound.ts124
1 files changed, 111 insertions, 13 deletions
diff --git a/packages/frontend/src/scripts/sound.ts b/packages/frontend/src/scripts/sound.ts
index a3cddba1f4..a4c6967d18 100644
--- a/packages/frontend/src/scripts/sound.ts
+++ b/packages/frontend/src/scripts/sound.ts
@@ -3,14 +3,22 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
+import type { SoundStore } from '@/store.js';
import { defaultStore } from '@/store.js';
+import * as os from '@/os.js';
let ctx: AudioContext;
const cache = new Map<string, AudioBuffer>();
let canPlay = true;
export const soundsTypes = [
+ // 音声なし
null,
+
+ // ドライブの音声
+ '_driveFile_',
+
+ // プリインストール
'syuilo/n-aec',
'syuilo/n-aec-4va',
'syuilo/n-aec-4vb',
@@ -64,32 +72,96 @@ export const soundsTypes = [
'noizenecio/kick_gaba7',
] as const;
-export async function loadAudio(file: string, useCache = true) {
+export const operationTypes = [
+ 'noteMy',
+ 'note',
+ 'antenna',
+ 'channel',
+ 'notification',
+ 'reaction',
+] as const;
+
+/** サウンドの種類 */
+export type SoundType = typeof soundsTypes[number];
+
+/** スプライトの種類 */
+export type OperationType = typeof operationTypes[number];
+
+/**
+ * 音声を読み込む
+ * @param soundStore サウンド設定
+ * @param options `useCache`: デフォルトは`true` 一度再生した音声はキャッシュする
+ */
+export async function loadAudio(soundStore: SoundStore, options?: { useCache?: boolean; }) {
+ if (_DEV_) console.log('loading audio. opts:', options);
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
+ if (soundStore.type === null || (soundStore.type === '_driveFile_' && !soundStore.fileUrl)) {
+ return;
+ }
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (ctx == null) {
ctx = new AudioContext();
}
- if (useCache && cache.has(file)) {
- return cache.get(file)!;
+ if (options?.useCache ?? true) {
+ if (soundStore.type === '_driveFile_' && cache.has(soundStore.fileId)) {
+ if (_DEV_) console.log('use cache');
+ return cache.get(soundStore.fileId) as AudioBuffer;
+ } else if (cache.has(soundStore.type)) {
+ if (_DEV_) console.log('use cache');
+ return cache.get(soundStore.type) as AudioBuffer;
+ }
+ }
+
+ let response: Response;
+
+ if (soundStore.type === '_driveFile_') {
+ try {
+ response = await fetch(soundStore.fileUrl);
+ } catch (err) {
+ try {
+ // URLが変わっている可能性があるのでドライブ側からURLを取得するフォールバック
+ const apiRes = await os.api('drive/files/show', {
+ fileId: soundStore.fileId,
+ });
+ response = await fetch(apiRes.url);
+ } catch (fbErr) {
+ // それでも無理なら諦める
+ return;
+ }
+ }
+ } else {
+ try {
+ response = await fetch(`/client-assets/sounds/${soundStore.type}.mp3`);
+ } catch (err) {
+ return;
+ }
}
- const response = await fetch(`/client-assets/sounds/${file}.mp3`);
const arrayBuffer = await response.arrayBuffer();
const audioBuffer = await ctx.decodeAudioData(arrayBuffer);
- if (useCache) {
- cache.set(file, audioBuffer);
+ if (options?.useCache ?? true) {
+ if (soundStore.type === '_driveFile_') {
+ cache.set(soundStore.fileId, audioBuffer);
+ } else {
+ cache.set(soundStore.type, audioBuffer);
+ }
}
return audioBuffer;
}
-export function play(type: 'noteMy' | 'note' | 'antenna' | 'channel' | 'notification' | 'reaction') {
- const sound = defaultStore.state[`sound_${type}`];
- if (_DEV_) console.log('play', type, sound);
+/**
+ * 既定のスプライトを再生する
+ * @param type スプライトの種類を指定
+ */
+export function play(operationType: OperationType) {
+ const sound = defaultStore.state[`sound_${operationType}`];
+ if (_DEV_) console.log('play', operationType, sound);
if (sound.type == null || !canPlay) return;
canPlay = false;
- playFile(sound.type, sound.volume).then(() => {
+ playFile(sound).then(() => {
// ごく短時間に音が重複しないように
setTimeout(() => {
canPlay = true;
@@ -97,9 +169,14 @@ export function play(type: 'noteMy' | 'note' | 'antenna' | 'channel' | 'notifica
});
}
-export async function playFile(file: string, volume: number) {
- const buffer = await loadAudio(file);
- createSourceNode(buffer, volume)?.start();
+/**
+ * サウンド設定形式で指定された音声を再生する
+ * @param soundStore サウンド設定
+ */
+export async function playFile(soundStore: SoundStore) {
+ const buffer = await loadAudio(soundStore);
+ if (!buffer) return;
+ createSourceNode(buffer, soundStore.volume)?.start();
}
export function createSourceNode(buffer: AudioBuffer, volume: number) : AudioBufferSourceNode | null {
@@ -118,6 +195,27 @@ export function createSourceNode(buffer: AudioBuffer, volume: number) : AudioBuf
return soundSource;
}
+/**
+ * 音声の長さをミリ秒で取得する
+ * @param file ファイルのURL(ドライブIDではない)
+ */
+export async function getSoundDuration(file: string): Promise<number> {
+ const audioEl = document.createElement('audio');
+ audioEl.src = file;
+ return new Promise((resolve) => {
+ const si = setInterval(() => {
+ if (audioEl.readyState > 0) {
+ resolve(audioEl.duration * 1000);
+ clearInterval(si);
+ audioEl.remove();
+ }
+ }, 100);
+ });
+}
+
+/**
+ * ミュートすべきかどうかを判断する
+ */
export function isMute(): boolean {
if (defaultStore.state.sound_notUseSound) {
// サウンドを出力しない