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:
		
							parent
							
								
									676c599e48
								
							
						
					
					
						commit
						8b163cd3fb
					
				
					 6 changed files with 77 additions and 18 deletions
				
			
		| 
						 | 
					@ -70,6 +70,7 @@
 | 
				
			||||||
- Fix: タイムラインページを開いた時、`TLに他の人への返信を含める`がオフのときに`ファイル付きのみ`をオンにできない問題を修正
 | 
					- Fix: タイムラインページを開いた時、`TLに他の人への返信を含める`がオフのときに`ファイル付きのみ`をオンにできない問題を修正
 | 
				
			||||||
- Fix: deck uiでタイムラインを切り替えた際にTLの設定項目が更新されず、`TLに他の人への返信を含める`のトグルが表示されない問題を修正
 | 
					- Fix: deck uiでタイムラインを切り替えた際にTLの設定項目が更新されず、`TLに他の人への返信を含める`のトグルが表示されない問題を修正
 | 
				
			||||||
- Fix: ウィジェットのタイムライン選択欄に無効化されたタイムラインが表示される問題を修正
 | 
					- Fix: ウィジェットのタイムライン選択欄に無効化されたタイムラインが表示される問題を修正
 | 
				
			||||||
 | 
					- Fix: サウンドにドライブの音声を使用している際にドライブの音声が再生できなくなると設定が変更できなくなる問題を修正
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Server
 | 
					### Server
 | 
				
			||||||
- Feat: レートリミット制限に引っかかったときに`Retry-After`ヘッダーを返すように (#13949)
 | 
					- Feat: レートリミット制限に引っかかったときに`Retry-After`ヘッダーを返すように (#13949)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										4
									
								
								locales/index.d.ts
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								locales/index.d.ts
									
										
									
									
										vendored
									
									
								
							| 
						 | 
					@ -7633,6 +7633,10 @@ export interface Locale extends ILocale {
 | 
				
			||||||
         * 長い音声を使用するとMisskeyの使用に支障をきたす可能性があります。それでも続行しますか?
 | 
					         * 長い音声を使用するとMisskeyの使用に支障をきたす可能性があります。それでも続行しますか?
 | 
				
			||||||
         */
 | 
					         */
 | 
				
			||||||
        "driveFileDurationWarnDescription": string;
 | 
					        "driveFileDurationWarnDescription": string;
 | 
				
			||||||
 | 
					        /**
 | 
				
			||||||
 | 
					         * 音声が読み込めませんでした。設定を変更してください
 | 
				
			||||||
 | 
					         */
 | 
				
			||||||
 | 
					        "driveFileError": string;
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    "_ago": {
 | 
					    "_ago": {
 | 
				
			||||||
        /**
 | 
					        /**
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2002,6 +2002,7 @@ _soundSettings:
 | 
				
			||||||
  driveFileTypeWarnDescription: "音声ファイルを選択してください"
 | 
					  driveFileTypeWarnDescription: "音声ファイルを選択してください"
 | 
				
			||||||
  driveFileDurationWarn: "音声が長すぎます"
 | 
					  driveFileDurationWarn: "音声が長すぎます"
 | 
				
			||||||
  driveFileDurationWarnDescription: "長い音声を使用するとMisskeyの使用に支障をきたす可能性があります。それでも続行しますか?"
 | 
					  driveFileDurationWarnDescription: "長い音声を使用するとMisskeyの使用に支障をきたす可能性があります。それでも続行しますか?"
 | 
				
			||||||
 | 
					  driveFileError: "音声が読み込めませんでした。設定を変更してください"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
_ago:
 | 
					_ago:
 | 
				
			||||||
  future: "未来"
 | 
					  future: "未来"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -9,7 +9,13 @@ SPDX-License-Identifier: AGPL-3.0-only
 | 
				
			||||||
		<template #label>{{ i18n.ts.sound }}</template>
 | 
							<template #label>{{ i18n.ts.sound }}</template>
 | 
				
			||||||
		<option v-for="x in soundsTypes" :key="x ?? 'null'" :value="x">{{ getSoundTypeName(x) }}</option>
 | 
							<option v-for="x in soundsTypes" :key="x ?? 'null'" :value="x">{{ getSoundTypeName(x) }}</option>
 | 
				
			||||||
	</MkSelect>
 | 
						</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>
 | 
							<MkButton :class="$style.fileSelectorButton" inline rounded primary @click="selectSound">{{ i18n.ts.selectFile }}</MkButton>
 | 
				
			||||||
		<div :class="['_nowrap', !fileUrl && $style.fileNotSelected]">{{ friendlyFileName }}</div>
 | 
							<div :class="['_nowrap', !fileUrl && $style.fileNotSelected]">{{ friendlyFileName }}</div>
 | 
				
			||||||
	</div>
 | 
						</div>
 | 
				
			||||||
| 
						 | 
					@ -19,13 +25,13 @@ SPDX-License-Identifier: AGPL-3.0-only
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	<div class="_buttons">
 | 
						<div class="_buttons">
 | 
				
			||||||
		<MkButton inline @click="listen"><i class="ti ti-player-play"></i> {{ i18n.ts.listen }}</MkButton>
 | 
							<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>
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<script lang="ts" setup>
 | 
					<script lang="ts" setup>
 | 
				
			||||||
import { ref, computed } from 'vue';
 | 
					import { ref, computed, watch } from 'vue';
 | 
				
			||||||
import type { SoundType } from '@/scripts/sound.js';
 | 
					import type { SoundType } from '@/scripts/sound.js';
 | 
				
			||||||
import MkSelect from '@/components/MkSelect.vue';
 | 
					import MkSelect from '@/components/MkSelect.vue';
 | 
				
			||||||
import MkButton from '@/components/MkButton.vue';
 | 
					import MkButton from '@/components/MkButton.vue';
 | 
				
			||||||
| 
						 | 
					@ -51,13 +57,18 @@ const type = ref<SoundType>(props.type);
 | 
				
			||||||
const fileId = ref(props.fileId);
 | 
					const fileId = ref(props.fileId);
 | 
				
			||||||
const fileUrl = ref(props.fileUrl);
 | 
					const fileUrl = ref(props.fileUrl);
 | 
				
			||||||
const fileName = ref<string>('');
 | 
					const fileName = ref<string>('');
 | 
				
			||||||
 | 
					const driveFileError = ref(false);
 | 
				
			||||||
 | 
					const hasChanged = ref(false);
 | 
				
			||||||
const volume = ref(props.volume);
 | 
					const volume = ref(props.volume);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if (type.value === '_driveFile_' && fileId.value) {
 | 
					if (type.value === '_driveFile_' && fileId.value) {
 | 
				
			||||||
	const apiRes = await misskeyApi('drive/files/show', {
 | 
						await misskeyApi('drive/files/show', {
 | 
				
			||||||
		fileId: fileId.value,
 | 
							fileId: fileId.value,
 | 
				
			||||||
 | 
						}).then((res) => {
 | 
				
			||||||
 | 
							fileName.value = res.name;
 | 
				
			||||||
 | 
						}).catch((res) => {
 | 
				
			||||||
 | 
							driveFileError.value = true;
 | 
				
			||||||
	});
 | 
						});
 | 
				
			||||||
	fileName.value = apiRes.name;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function getSoundTypeName(f: SoundType): string {
 | 
					function getSoundTypeName(f: SoundType): string {
 | 
				
			||||||
| 
						 | 
					@ -107,9 +118,21 @@ function selectSound(ev) {
 | 
				
			||||||
		fileUrl.value = file.url;
 | 
							fileUrl.value = file.url;
 | 
				
			||||||
		fileName.value = file.name;
 | 
							fileName.value = file.name;
 | 
				
			||||||
		fileId.value = file.id;
 | 
							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() {
 | 
					function listen() {
 | 
				
			||||||
	if (type.value === '_driveFile_' && (!fileUrl.value || !fileId.value)) {
 | 
						if (type.value === '_driveFile_' && (!fileUrl.value || !fileId.value)) {
 | 
				
			||||||
		os.alert({
 | 
							os.alert({
 | 
				
			||||||
| 
						 | 
					@ -131,6 +154,10 @@ function listen() {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function save() {
 | 
					function save() {
 | 
				
			||||||
 | 
						if (hasChanged.value === false || driveFileError.value === true) {
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (type.value === '_driveFile_' && !fileUrl.value) {
 | 
						if (type.value === '_driveFile_' && !fileUrl.value) {
 | 
				
			||||||
		os.alert({
 | 
							os.alert({
 | 
				
			||||||
			type: 'warning',
 | 
								type: 'warning',
 | 
				
			||||||
| 
						 | 
					@ -163,6 +190,13 @@ function save() {
 | 
				
			||||||
	gap: 8px;
 | 
						gap: 8px;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.fileErrorRoot {
 | 
				
			||||||
 | 
						flex-grow: 1;
 | 
				
			||||||
 | 
						min-width: 0;
 | 
				
			||||||
 | 
						font-weight: 700;
 | 
				
			||||||
 | 
						color: var(--error);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.fileSelectorButton {
 | 
					.fileSelectorButton {
 | 
				
			||||||
	flex-shrink: 0;
 | 
						flex-shrink: 0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -21,8 +21,14 @@ SPDX-License-Identifier: AGPL-3.0-only
 | 
				
			||||||
			<MkFolder v-for="type in operationTypes" :key="type">
 | 
								<MkFolder v-for="type in operationTypes" :key="type">
 | 
				
			||||||
				<template #label>{{ i18n.ts._sfx[type] }}</template>
 | 
									<template #label>{{ i18n.ts._sfx[type] }}</template>
 | 
				
			||||||
				<template #suffix>{{ getSoundTypeName(sounds[type].type) }}</template>
 | 
									<template #suffix>{{ getSoundTypeName(sounds[type].type) }}</template>
 | 
				
			||||||
 | 
									<Suspense>
 | 
				
			||||||
				<XSound :type="sounds[type].type" :volume="sounds[type].volume" :fileId="sounds[type].fileId" :fileUrl="sounds[type].fileUrl" @update="(res) => updated(type, res)"/>
 | 
										<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>
 | 
								</MkFolder>
 | 
				
			||||||
		</div>
 | 
							</div>
 | 
				
			||||||
	</FormSection>
 | 
						</FormSection>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -124,23 +124,33 @@ export async function loadAudio(url: string, options?: { useCache?: boolean; })
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
export function playMisskeySfx(operationType: OperationType) {
 | 
					export function playMisskeySfx(operationType: OperationType) {
 | 
				
			||||||
	const sound = defaultStore.state[`sound_${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 サウンド設定
 | 
					 * @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;
 | 
						canPlay = false;
 | 
				
			||||||
	playMisskeySfxFileInternal(soundStore).finally(() => {
 | 
						return await playMisskeySfxFileInternal(soundStore).finally(() => {
 | 
				
			||||||
		// ごく短時間に音が重複しないように
 | 
							// ごく短時間に音が重複しないように
 | 
				
			||||||
		setTimeout(() => {
 | 
							setTimeout(() => {
 | 
				
			||||||
			canPlay = true;
 | 
								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)) {
 | 
						if (soundStore.type === null || (soundStore.type === '_driveFile_' && !soundStore.fileUrl)) {
 | 
				
			||||||
		return;
 | 
							return false;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	const masterVolume = defaultStore.state.sound_masterVolume;
 | 
						const masterVolume = defaultStore.state.sound_masterVolume;
 | 
				
			||||||
	if (isMute() || masterVolume === 0 || soundStore.volume === 0) {
 | 
						if (isMute() || masterVolume === 0 || soundStore.volume === 0) {
 | 
				
			||||||
		return;
 | 
							return true; // ミュート時は成功として扱う
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	const url = soundStore.type === '_driveFile_' ? soundStore.fileUrl : `/client-assets/sounds/${soundStore.type}.mp3`;
 | 
						const url = soundStore.type === '_driveFile_' ? soundStore.fileUrl : `/client-assets/sounds/${soundStore.type}.mp3`;
 | 
				
			||||||
	const buffer = await loadAudio(url);
 | 
						const buffer = await loadAudio(url).catch(() => {
 | 
				
			||||||
	if (!buffer) return;
 | 
							return undefined;
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
						if (!buffer) return false;
 | 
				
			||||||
	const volume = soundStore.volume * masterVolume;
 | 
						const volume = soundStore.volume * masterVolume;
 | 
				
			||||||
	createSourceNode(buffer, { volume }).soundSource.start();
 | 
						createSourceNode(buffer, { volume }).soundSource.start();
 | 
				
			||||||
 | 
						return true;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export async function playUrl(url: string, opts: {
 | 
					export async function playUrl(url: string, opts: {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue