diff --git a/CHANGELOG.md b/CHANGELOG.md
index d872ff153bcb666abc2f087cdf7507fc00327820..6efb78b14568946bdcd181cdfc045f72461105ce 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -29,6 +29,7 @@
 - Fix: 非ログイン時に「メモを追加」を表示しないように変更 #12309
 - Fix: 絵文字ピッカーでの検索が更新されない問題を修正
 - Fix: 特定の条件下でノートがnyaizeされない問題を修正
+- Fix: iOSでの音声出力不備を改善 #12339
 
 ### Server
 - Fix: トークンのないプラグインをアンインストールするときにエラーが出ないように
diff --git a/packages/frontend/src/scripts/sound.ts b/packages/frontend/src/scripts/sound.ts
index f995c122d165504ab0b1dbc1e1b042df8b004160..4b0cd0bb3922c2ad28ba3c1f46de96f6c839355e 100644
--- a/packages/frontend/src/scripts/sound.ts
+++ b/packages/frontend/src/scripts/sound.ts
@@ -5,7 +5,8 @@
 
 import { defaultStore } from '@/store.js';
 
-const cache = new Map<string, HTMLAudioElement>();
+const ctx = new AudioContext();
+const cache = new Map<string, AudioBuffer>();
 
 export const soundsTypes = [
 	null,
@@ -60,15 +61,20 @@ export const soundsTypes = [
 	'noizenecio/kick_gaba7',
 ] as const;
 
-export function getAudio(file: string, useCache = true): HTMLAudioElement {
-	let audio: HTMLAudioElement;
+export async function getAudio(file: string, useCache = true) {
 	if (useCache && cache.has(file)) {
-		audio = cache.get(file);
-	} else {
-		audio = new Audio(`/client-assets/sounds/${file}.mp3`);
-		if (useCache) cache.set(file, audio);
+		return cache.get(file)!;
 	}
-	return audio;
+
+	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);
+	}
+
+	return audioBuffer;
 }
 
 export function setVolume(audio: HTMLAudioElement, volume: number): HTMLAudioElement {
@@ -84,8 +90,17 @@ export function play(type: 'noteMy' | 'note' | 'antenna' | 'channel' | 'notifica
 	playFile(sound.type, sound.volume);
 }
 
-export function playFile(file: string, volume: number) {
-	const audio = setVolume(getAudio(file), volume);
-	if (audio.volume === 0) return;
-	audio.play();
+export async function playFile(file: string, volume: number) {
+	const masterVolume = defaultStore.state.sound_masterVolume;
+	if (masterVolume === 0 || volume === 0) {
+		return;
+	}
+
+	const gainNode = ctx.createGain();
+	gainNode.gain.value = masterVolume * volume;
+
+	const soundSource = ctx.createBufferSource();
+	soundSource.buffer = await getAudio(file);
+	soundSource.connect(gainNode).connect(ctx.destination);
+	soundSource.start();
 }