fix(frontend): ドライブの音声が再生できない場合の処理を追加 (#14073)
* fix(frontend): ドライブの音声が再生できない場合の処理を追加 * Update Changelog * fix lint * Update packages/frontend/src/scripts/sound.ts * lint * Update sound.ts * fix merge mistakes * use shorthand operator --------- Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com>
This commit is contained in:
@@ -9,7 +9,13 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<template #label>{{ i18n.ts.sound }}</template>
|
||||
<option v-for="x in soundsTypes" :key="x ?? 'null'" :value="x">{{ getSoundTypeName(x) }}</option>
|
||||
</MkSelect>
|
||||
<div v-if="type === '_driveFile_'" :class="$style.fileSelectorRoot">
|
||||
<div v-if="type === '_driveFile_' && driveFileError === true" :class="$style.fileSelectorRoot">
|
||||
<MkButton :class="$style.fileSelectorButton" inline rounded primary @click="selectSound">{{ i18n.ts.selectFile }}</MkButton>
|
||||
<div :class="$style.fileErrorRoot">
|
||||
<MkCondensedLine>{{ i18n.ts._soundSettings.driveFileError }}</MkCondensedLine>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="type === '_driveFile_'" :class="$style.fileSelectorRoot">
|
||||
<MkButton :class="$style.fileSelectorButton" inline rounded primary @click="selectSound">{{ i18n.ts.selectFile }}</MkButton>
|
||||
<div :class="['_nowrap', !fileUrl && $style.fileNotSelected]">{{ friendlyFileName }}</div>
|
||||
</div>
|
||||
@@ -19,13 +25,13 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
<div class="_buttons">
|
||||
<MkButton inline @click="listen"><i class="ti ti-player-play"></i> {{ i18n.ts.listen }}</MkButton>
|
||||
<MkButton inline primary @click="save"><i class="ti ti-check"></i> {{ i18n.ts.save }}</MkButton>
|
||||
<MkButton inline primary :disabled="!hasChanged || driveFileError" @click="save"><i class="ti ti-check"></i> {{ i18n.ts.save }}</MkButton>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed } from 'vue';
|
||||
import { ref, computed, watch } from 'vue';
|
||||
import type { SoundType } from '@/scripts/sound.js';
|
||||
import MkSelect from '@/components/MkSelect.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
@@ -51,13 +57,18 @@ const type = ref<SoundType>(props.type);
|
||||
const fileId = ref(props.fileId);
|
||||
const fileUrl = ref(props.fileUrl);
|
||||
const fileName = ref<string>('');
|
||||
const driveFileError = ref(false);
|
||||
const hasChanged = ref(false);
|
||||
const volume = ref(props.volume);
|
||||
|
||||
if (type.value === '_driveFile_' && fileId.value) {
|
||||
const apiRes = await misskeyApi('drive/files/show', {
|
||||
await misskeyApi('drive/files/show', {
|
||||
fileId: fileId.value,
|
||||
}).then((res) => {
|
||||
fileName.value = res.name;
|
||||
}).catch((res) => {
|
||||
driveFileError.value = true;
|
||||
});
|
||||
fileName.value = apiRes.name;
|
||||
}
|
||||
|
||||
function getSoundTypeName(f: SoundType): string {
|
||||
@@ -107,9 +118,21 @@ function selectSound(ev) {
|
||||
fileUrl.value = file.url;
|
||||
fileName.value = file.name;
|
||||
fileId.value = file.id;
|
||||
driveFileError.value = false;
|
||||
hasChanged.value = true;
|
||||
});
|
||||
}
|
||||
|
||||
watch([type, volume], ([typeTo, volumeTo], [typeFrom, volumeFrom]) => {
|
||||
if (typeFrom !== typeTo && typeTo !== '_driveFile_') {
|
||||
fileUrl.value = undefined;
|
||||
fileName.value = '';
|
||||
fileId.value = undefined;
|
||||
driveFileError.value = false;
|
||||
}
|
||||
hasChanged.value = true;
|
||||
});
|
||||
|
||||
function listen() {
|
||||
if (type.value === '_driveFile_' && (!fileUrl.value || !fileId.value)) {
|
||||
os.alert({
|
||||
@@ -131,6 +154,10 @@ function listen() {
|
||||
}
|
||||
|
||||
function save() {
|
||||
if (hasChanged.value === false || driveFileError.value === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (type.value === '_driveFile_' && !fileUrl.value) {
|
||||
os.alert({
|
||||
type: 'warning',
|
||||
@@ -163,6 +190,13 @@ function save() {
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.fileErrorRoot {
|
||||
flex-grow: 1;
|
||||
min-width: 0;
|
||||
font-weight: 700;
|
||||
color: var(--error);
|
||||
}
|
||||
|
||||
.fileSelectorButton {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
@@ -21,8 +21,14 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<MkFolder v-for="type in operationTypes" :key="type">
|
||||
<template #label>{{ i18n.ts._sfx[type] }}</template>
|
||||
<template #suffix>{{ getSoundTypeName(sounds[type].type) }}</template>
|
||||
|
||||
<XSound :type="sounds[type].type" :volume="sounds[type].volume" :fileId="sounds[type].fileId" :fileUrl="sounds[type].fileUrl" @update="(res) => updated(type, res)"/>
|
||||
<Suspense>
|
||||
<template #default>
|
||||
<XSound :type="sounds[type].type" :volume="sounds[type].volume" :fileId="sounds[type].fileId" :fileUrl="sounds[type].fileUrl" @update="(res) => updated(type, res)"/>
|
||||
</template>
|
||||
<template #fallback>
|
||||
<MkLoading/>
|
||||
</template>
|
||||
</Suspense>
|
||||
</MkFolder>
|
||||
</div>
|
||||
</FormSection>
|
||||
|
@@ -124,23 +124,33 @@ export async function loadAudio(url: string, options?: { useCache?: boolean; })
|
||||
*/
|
||||
export function playMisskeySfx(operationType: OperationType) {
|
||||
const sound = defaultStore.state[`sound_${operationType}`];
|
||||
playMisskeySfxFile(sound);
|
||||
playMisskeySfxFile(sound).then((succeed) => {
|
||||
if (!succeed && sound.type === '_driveFile_') {
|
||||
// ドライブファイルが存在しない場合はデフォルトのサウンドを再生する
|
||||
const soundName = defaultStore.def[`sound_${operationType}`].default.type as Exclude<SoundType, '_driveFile_'>;
|
||||
if (_DEV_) console.log(`Failed to play sound: ${sound.fileUrl}, so play default sound: ${soundName}`);
|
||||
playMisskeySfxFileInternal({
|
||||
type: soundName,
|
||||
volume: sound.volume,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* サウンド設定形式で指定された音声を再生する
|
||||
* @param soundStore サウンド設定
|
||||
*/
|
||||
export function playMisskeySfxFile(soundStore: SoundStore) {
|
||||
export async function playMisskeySfxFile(soundStore: SoundStore): Promise<boolean> {
|
||||
// 連続して再生しない
|
||||
if (!canPlay) return;
|
||||
if (!canPlay) return false;
|
||||
// ユーザーアクティベーションが必要な場合はそれがない場合は再生しない
|
||||
if ('userActivation' in navigator && !navigator.userActivation.hasBeenActive) return;
|
||||
if ('userActivation' in navigator && !navigator.userActivation.hasBeenActive) return false;
|
||||
// サウンドがない場合は再生しない
|
||||
if (soundStore.type === null || soundStore.type === '_driveFile_' && !soundStore.fileUrl) return;
|
||||
if (soundStore.type === null || soundStore.type === '_driveFile_' && !soundStore.fileUrl) return false;
|
||||
|
||||
canPlay = false;
|
||||
playMisskeySfxFileInternal(soundStore).finally(() => {
|
||||
return await playMisskeySfxFileInternal(soundStore).finally(() => {
|
||||
// ごく短時間に音が重複しないように
|
||||
setTimeout(() => {
|
||||
canPlay = true;
|
||||
@@ -148,19 +158,22 @@ export function playMisskeySfxFile(soundStore: SoundStore) {
|
||||
});
|
||||
}
|
||||
|
||||
async function playMisskeySfxFileInternal(soundStore: SoundStore) {
|
||||
async function playMisskeySfxFileInternal(soundStore: SoundStore): Promise<boolean> {
|
||||
if (soundStore.type === null || (soundStore.type === '_driveFile_' && !soundStore.fileUrl)) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
const masterVolume = defaultStore.state.sound_masterVolume;
|
||||
if (isMute() || masterVolume === 0 || soundStore.volume === 0) {
|
||||
return;
|
||||
return true; // ミュート時は成功として扱う
|
||||
}
|
||||
const url = soundStore.type === '_driveFile_' ? soundStore.fileUrl : `/client-assets/sounds/${soundStore.type}.mp3`;
|
||||
const buffer = await loadAudio(url);
|
||||
if (!buffer) return;
|
||||
const buffer = await loadAudio(url).catch(() => {
|
||||
return undefined;
|
||||
});
|
||||
if (!buffer) return false;
|
||||
const volume = soundStore.volume * masterVolume;
|
||||
createSourceNode(buffer, { volume }).soundSource.start();
|
||||
return true;
|
||||
}
|
||||
|
||||
export async function playUrl(url: string, opts: {
|
||||
|
Reference in New Issue
Block a user