バックグラウンドでオーディオを再生するには、Capacitor では2つのネイティブ要素が必要です: audio iOS のバックグラウンドモードと、 mediaPlayback Android のフォアグラウンドサービスです。メディアセッション経由でロック画面コントロールを追加すると、ユーザーが端末をロックしたりアプリを切り替えたりしても、音楽、ポッドキャスト、瞑想アプリの再生が続きます。このガイドでは、Capacitor 8 での両方のセットアップを、 @capgo/capacitor-native-audio ネイティブ再生用と @capgo/capacitor-media-session システムコントロール用に。
バックグラウンドでオーディオが止まる理由
Capacitor アプリは WebView を囲むネイティブシェルなので、各プラットフォームのライフサイクルルールに従います:
| iOSと | Androidでは | |
|---|---|---|
| バックグラウンドで何が起きるか | 数秒でアプリがサスペンドされる | Activityが一時停止し、WebViewのタイマーがスロットリングされ、プロセスがkillされる可能性がある |
| オーディオを維持するもの | UIBackgroundModes を含む audio、オーディオセッションカテゴリ playback |
タイプのフォアグラウンドサービス mediaPlayback 通知付きの |
| サイレントスイッチ | playback カテゴリは無視します、 ambient それに従います |
該当なし |
| ロック画面コントロール | MPNowPlayingInfoCenter と MPRemoteCommandCenter |
MediaSession メディアスタイルの通知付き |
| ストアのルール | ユーザーが期待する音声を再生すること (ガイドライン 2.5.4) | Foreground service typeは、Play Consoleで宣言した実際の用途と一致している必要があります |
どちらのプラットフォームもデフォルトではバックグラウンドオーディオを提供しません。片方だけを修正してしまうことが、「iPhoneでは動くのにAndroidでは止まる」というバグ報告の最も一般的な原因です。
再生方式を選択
適したアーキテクチャは2つあります:
- Native playback(プラグインを使用)
@capgo/capacitor-native-audio: iOSではAVFoundation、AndroidではMedia3 ExoPlayerがオーディオをデコードします。音楽プレイヤー、オーディオブック、瞑想アプリ、WebViewがスロットリングされる可能性があるアプリに最適です。ローカルファイル、リモートURL、HLSストリーム、フェード、再生速度、Now Playingメタデータに対応しています。 - HTML
<audio>と@capgo/capacitor-media-session: 既存のWebプレイヤーを維持し、プラグインがメタデータとコントロールをOSに公開します。Androidでは、セッション再生中にフォアグラウンドサービスも実行します。Webプレイヤーが既に存在する場合、またはWebとネイティブで単一の code パスが必要な場合に最適です。
@capgo/capacitor-native-audio |
<audio> + メディアセッション |
|
|---|---|---|
| 再生エンジン | ネイティブ | WebView |
| Web対応 | 対応(HTML audioフォールバック) | はい |
| ロック画面のメタデータ | showNotification + notificationMetadata |
setMetadata |
| Androidフォアグラウンドサービス | 自分で開始する | 再生中または一時停止中にプラグインが開始 |
| リモートコントロールイベント | playbackState リスナー |
setActionHandler |
| 低遅延で正確なSFX | はい | 制限あり |
iOSのバックグラウンドオーディオを設定する
開く ios/App/App/Info.plist を追加して、バックグラウンドモードを有効にします。Xcode の「Signing & Capabilities」>「Background Modes」で「Audio, AirPlay, and Picture in Picture」にチェックを入れると、同じキーが書き込まれます:
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
</array>
オーディオセッションのカテゴリも重要です。 @capgo/capacitor-native-audio が設定します: showNotification: true では、これを使います .playback デフォルトモードでは、他のアプリを中断し(Spotify が一時停止します)、アプリを Now Playing アプリにします。 showNotification: false では、これを使います .playback を使用して mixWithOthers。これは効果音には適していますが、ロック画面のコントロールは表示されません。トラックごとではなく、アプリごとに1つ選んでください。
を介して再生する場合 <audio>、メディア再生が始まると WebKit が再生セッションを有効にします。初回は必ずユーザー操作を起点に再生を開始してください。そうしないと iOS にブロックされます。
2026年4月以降、App Store Connect にアップロードする前に Xcode 26 以降でビルドすることが必須です。 Capgo のビルド Windows や Linux で作業している場合は、クラウド上で行うこともできます。
Android でバックグラウンドオーディオを設定する
権限を以下に追加します: android/app/src/main/AndroidManifest.xml:
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
FOREGROUND_SERVICE_MEDIA_PLAYBACK Android 14 (API 34) 以上をターゲットにするアプリでは必須で、Capacitor 8 は API 36 をターゲットにします。これがないと、サービスの起動時に SecurityExceptionがスローされます。公開時には Play Console でフォアグラウンドサービスの種類を宣言し、その根拠を説明する必要があります。認められる用途は「ユーザーが開始したメディア再生」です。
POST_NOTIFICATIONS は Android 13 以降の実行時権限です。メディアセッションの通知は通知権限の対象外で通知シェードに表示されますが、サービスが送信するその他の通知にはこの権限が必要です。
Android 12 以降では、アプリがバックグラウンドにある状態でフォアグラウンドサービスを開始できません。ユーザーが再生を押したとき、Activity が表示されているうちに開始してください。
オプション A: media session プラグインにサービスを実行させる
@capgo/capacitor-media-session は次を宣言します: mediaPlayback 独自のmanifestでサービスを宣言し、再生状態を次に設定すると起動します: playing または paused。状態が次に戻ると停止します。 none かつアプリがバックグラウンドにある場合。セッション全体でサービスを実行したい場合は、プラグイン設定を以下に設定します capacitor.config.ts:
plugins: {
MediaSession: {
foregroundService: 'always',
},
},
再生中のみ動作するデフォルト設定が、ほとんどのアプリに適しています。
オプションB:独自のフォアグラウンドサービスを開始する
@capgo/capacitor-native-audio 再生、フォーカス、メディア通知を制御しますが、フォアグラウンドサービス自体は作成しません。メディアセッションプラグインを使用せずに利用する場合は、Androidプロジェクトに小さなサービスとローカルプラグインを追加してください。
android/app/src/main/java/com/example/app/PlaybackService.java:
package com.example.app;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.Service;
import android.content.Intent;
import android.content.pm.ServiceInfo;
import android.os.Build;
import android.os.IBinder;
import androidx.core.app.NotificationCompat;
public class PlaybackService extends Service {
private static final String CHANNEL_ID = "playback";
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(
CHANNEL_ID, "Playback", NotificationManager.IMPORTANCE_LOW);
getSystemService(NotificationManager.class).createNotificationChannel(channel);
}
Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("Playing audio")
.setSmallIcon(R.mipmap.ic_launcher)
.setOngoing(true)
.build();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
startForeground(1, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK);
} else {
startForeground(1, notification);
}
return START_NOT_STICKY;
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
PlaybackServicePlugin.java 同じパッケージ内:
package com.example.app;
import android.content.Intent;
import androidx.core.content.ContextCompat;
import com.getcapacitor.Plugin;
import com.getcapacitor.PluginCall;
import com.getcapacitor.PluginMethod;
import com.getcapacitor.annotation.CapacitorPlugin;
@CapacitorPlugin(name = "PlaybackService")
public class PlaybackServicePlugin extends Plugin {
@PluginMethod
public void start(PluginCall call) {
ContextCompat.startForegroundService(getContext(), new Intent(getContext(), PlaybackService.class));
call.resolve();
}
@PluginMethod
public void stop(PluginCall call) {
getContext().stopService(new Intent(getContext(), PlaybackService.class));
call.resolve();
}
}
以下に登録します: MainActivity.java の前に super.onCreate、マニフェストでサービスを宣言します:
public class MainActivity extends BridgeActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
registerPlugin(PlaybackServicePlugin.class);
super.onCreate(savedInstanceState);
}
}
<service
android:name=".PlaybackService"
android:exported="false"
android:foregroundServiceType="mediaPlayback" />
TypeScriptから:
import { Capacitor, registerPlugin } from '@capacitor/core';
interface PlaybackServicePlugin {
start(): Promise<void>;
stop(): Promise<void>;
}
export const PlaybackService = registerPlugin<PlaybackServicePlugin>('PlaybackService');
export const isAndroid = Capacitor.getPlatform() === 'android';
@capgo/capacitor-native-audioでオーディオをネイティブ再生する
bun add @capgo/capacitor-native-audio
bunx cap sync
起動時に一度設定するだけで、プリロードして再生できます:
import { NativeAudio } from '@capgo/capacitor-native-audio';
import { PlaybackService, isAndroid } from './playback-service';
await NativeAudio.configure({
focus: true, // request audio focus, pause others
background: true, // keep playing in the background
backgroundPlayback: true, // Android: skip auto pause on background
showNotification: true, // lock screen and notification controls
});
export async function playEpisode(id: string, url: string, title: string, artwork: string) {
await NativeAudio.preload({
assetId: id,
assetPath: url,
isUrl: true,
notificationMetadata: {
title,
artist: 'My Podcast',
artworkUrl: artwork,
},
});
if (isAndroid) await PlaybackService.start();
await NativeAudio.play({ assetId: id });
}
export async function stopEpisode(id: string) {
await NativeAudio.stop({ assetId: id });
await NativeAudio.unload({ assetId: id });
if (isAndroid) await PlaybackService.stop();
}
assetPath Webアセットからの相対パス(audio/intro.mp3)、 file:// URL、 https:// URL、またはHLS .m3u8 ストリームを受け付けます。バンドルされたアセット以外の場合は、設定してください isUrl: true。オーディオホストで認証が必要な場合は、渡してください headers.
自動で進むプレイリストを作成しましょう
この complete ネイティブプレイヤーが動作し続けるため、画面ロック中でもトラック終了時にイベントが発火します:
const queue = [
{ id: 'ep1', url: 'https://cdn.example.com/ep1.mp3', title: 'Episode 1' },
{ id: 'ep2', url: 'https://cdn.example.com/ep2.mp3', title: 'Episode 2' },
];
let index = 0;
await NativeAudio.addListener('complete', async ({ assetId }) => {
await NativeAudio.unload({ assetId });
index += 1;
if (index < queue.length) {
const next = queue[index];
await NativeAudio.preload({
assetId: next.id,
assetPath: next.url,
isUrl: true,
notificationMetadata: { title: next.title, artist: 'My Podcast' },
});
await NativeAudio.play({ assetId: next.id });
} else if (isAndroid) {
await PlaybackService.stop();
}
});
トラック間もフォアグラウンドサービスを実行し続けてください。1曲終了後に停止し、バックグラウンドで再開しようとすると、Android 12以降ではエラーをスローします ForegroundServiceStartNotAllowedException.
ロック画面とUIの状態を同期させる
ユーザーはロック画面、ヘッドフォン、通知から一時停止を押します。以下をリッスンしてください: playbackState そしてネイティブプレイヤーを source of truth として扱いましょう:
await NativeAudio.addListener('playbackState', (event) => {
// event.state: 'playing' | 'paused' | 'stopped'
// event.reason: 'play', 'pause', 'remotePlay', 'complete', ...
store.setPlaying(event.isPlaying);
if (event.currentTime !== undefined) store.setPosition(event.currentTime);
});
await NativeAudio.addListener('currentTime', ({ currentTime }) => {
store.setPosition(currentTime); // every 100 ms while playing
});
アプリがフォアグラウンドに戻ったときも、次を使って状態を更新します isPlaying と getCurrentTimeイベントの送信中にWebViewが一時停止していた可能性があるためです。
Media Sessionコントロール付きのHTML audio要素を使う
既存のWebプレイヤーがある場合は、そのままにしてプラグインを追加します:
bun add @capgo/capacitor-media-session
bunx cap sync
import { MediaSession } from '@capgo/capacitor-media-session';
const audio = new Audio();
audio.preload = 'auto';
export async function play(track: { url: string; title: string; artist: string; cover: string }) {
audio.src = track.url;
await audio.play();
await MediaSession.setMetadata({
title: track.title,
artist: track.artist,
artwork: [{ src: track.cover, sizes: '512x512', type: 'image/jpeg' }],
});
await MediaSession.setPlaybackState({ playbackState: 'playing' });
}
await MediaSession.setActionHandler({ action: 'play' }, async () => {
await audio.play();
await MediaSession.setPlaybackState({ playbackState: 'playing' });
});
await MediaSession.setActionHandler({ action: 'pause' }, async () => {
audio.pause();
await MediaSession.setPlaybackState({ playbackState: 'paused' });
});
await MediaSession.setActionHandler({ action: 'seekto' }, (details) => {
if (details.seekTime != null) audio.currentTime = details.seekTime;
});
await MediaSession.setActionHandler({ action: 'nexttrack' }, () => playNext());
audio.addEventListener('timeupdate', () => {
MediaSession.setPositionState({
duration: audio.duration || 0,
position: audio.currentTime,
playbackRate: audio.playbackRate,
});
});
呼び出す setPositionState 本番環境では最大でも1秒に1〜2回にしてください。ロック画面は更新の間を補間するので、 timeupdate ブリッジに大量のイベントを流すとバッテリーを消耗します。
割り込みやルート変更への対応
電話、Siri、アラーム、その他のアプリが再生を中断します。Native audioは中断時に一時停止し、iOSではシステムが再開すべきと判断したタイミングで再開します。さらに次の2つのケースに注意が必要です:
- ヘッドホンが外れたとき:ユーザーは再生が一時停止することを期待します。Androidでは、プレイヤーが自動で一時停止しない場合は、サービス内で
AudioManager.ACTION_AUDIO_BECOMING_NOISYレシーバーを登録してください。iOSでは、ルート変更を監視してください:@capgo/capacitor-audio-session:
import { AudioSession, RouteChangeReasons } from '@capgo/capacitor-audio-session';
await AudioSession.addListener('routeChanged', (reason) => {
if (reason === RouteChangeReasons.OLD_DEVICE_UNAVAILABLE) {
pausePlayback();
}
});
await AudioSession.addListener('interruption', (type) => {
// 'began' or 'ended'
});
- Ducking(音量低下)か一時停止か:ナビゲーションのプロンプトでオーディオがダッキングされます。音声コンテンツ系アプリでは、音量を下げるのではなく一時停止すべきです。音量が下がると聞き取りにくいためです。
バックグラウンド再生のテスト
シミュレーターはバックグラウンド動作を正しく再現しません。実機でテストしてください:
- 再生を開始して画面をロックし、2分待ちます。オーディオが再生され続ける必要があります。
- ロック画面の再生・一時停止・次へ・シーク操作を使い、ロック解除後にUIが一致するか確認します。
- 再生を開始し、オーディオを再生する別のアプリに切り替えてから、元のアプリに戻ります。
- 再生中に電話やFaceTime音声を開始します。
- Androidでは、バッテリーセーバーを有効にして、SamsungまたはXiaomiのデバイスでテストしてください。一部のOEMはバックグラウンドアプリを強制的に終了します。フォアグラウンドサービスは生き残りますが、通常のバックグラウンドタスクは終了します。
- Android 13以降では、通知の権限を拒否し、再生が引き続き動作することを確認してください。
- 画面をオフにして、プレイリストを複数のトラックにわたって再生させます。
adb shell dumpsys activity services | grep -A3 PlaybackService サービスがフォアグラウンドで実行されているかどうかを示します。
トラブルシューティング
iOSでは約30秒後にオーディオが停止します: UIBackgroundModes 欠けています audio、またはセッションカテゴリが ambient または soloAmbientを確認してください。 Info.plist ソースだけでなく、ビルド済みのアプリ内で。
Androidで数分経つとオーディオが停止する: フォアグラウンドサービスがない、またはサービスの種類が違います mediaPlayback. また、OEM のバッテリー最適化設定も確認してください。
SecurityException: Starting FGS with type mediaPlayback ... requires permissions:追加 FOREGROUND_SERVICE_MEDIA_PLAYBACK.
ForegroundServiceStartNotAllowedException: アプリがバックグラウンドにあるときにサービスを開始しました。ユーザーが再生をタップしたときに開始してください。
iOS ではロック画面のコントロールが使えません: showNotification が false(mixable session)か、メタデータが設定されていません。non-mixable session を所有するアプリだけが Now Playing アプリになります。
Android で通知が 2 つ表示される: 有効にしています showNotification を native audio で有効にし、さらに独自のサービス通知も発行しています。サービス通知は最小限にするか、media session プラグインのサービスのみを使用してください。
他の音楽アプリが自分のアプリの音に重なって再生され続ける: mixable session を設定しています。設定してください focus: true と showNotification: true.
プレイヤーの変更をより速くデプロイ
プレイヤーのキューロジック、UI、メタデータ、アナリティクスはすべて JavaScript に実装されます。上記のネイティブ設定がストアビルドに含まれていれば、 Capgo Live Updates そのcodeに修正をプッシュでき、新しいレビューサイクルは不要です。 ネイティブオーディオのドキュメント と メディアセッションのドキュメント すべてのオプションを網羅しています。アプリで音声録音も行う場合は、 Capacitor オーディオレコーダープラグイン.