diff options
Diffstat (limited to 'packages/frontend/src/scripts')
| -rw-r--r-- | packages/frontend/src/scripts/sound.ts | 124 |
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) { // サウンドを出力しない |