Zum Hauptinhalt springen

Audio im Hintergrund in Capacitor abspielen

Audio im Hintergrund in Capacitor abspielen: iOS Audio Background Mode, Android Foreground Service, Sperrbildschirm-Steuerung, Playlists und Unterbrechungen.

Wiedergabe von Hintergrundaudio mit Lockscreen-Steuerung in einer Capacitor-App

Um Audio im Hintergrund in Capacitor abzuspielen, benötigst du zwei native Komponenten: die audio Hintergrundmodus auf iOS und eine mediaPlayback Foreground-Service auf Android. Füge Sperrbildschirm-Steuerungen über eine Media Session hinzu, damit deine Musik-, Podcast- oder Meditations-App weiterläuft, wenn der Nutzer das Handy sperrt oder die App wechselt. Dieser Guide zeigt beide Setups auf Capacitor 8, mit @capgo/capacitor-native-audio für natives Playback und @capgo/capacitor-media-session für System-Steuerungen.

Warum Audio im Hintergrund stoppt

Eine Capacitor App ist eine native Shell um eine WebView und folgt daher den Lifecycle-Regeln der jeweiligen Plattform:

iOS Android
Was im Hintergrund passiert App wird innerhalb von Sekunden suspendiert Activity wird pausiert, WebView-Timer werden gedrosselt, Prozess kann beendet werden
Was Audio am Laufen hält UIBackgroundModes enthält audio, Audio-Session-Kategorie playback Ein Foreground-Service vom Typ mediaPlayback mit einer Benachrichtigung
Stummschalter playback Kategorie ignoriert ihn, ambient beachtet ihn Kein Äquivalent
Sperrbildschirm-Steuerung MPNowPlayingInfoCenter und MPRemoteCommandCenter MediaSession mit einer MediaStyle-Benachrichtigung
Store-Richtlinie Muss hörbare Inhalte abspielen, die der Nutzer erwartet (Guideline 2.5.4) Foreground service type must match real use, declared in Play Console

Keine der beiden Plattformen bietet standardmäßig Hintergrund-Audio, und nur eine Seite zu fixen ist der häufigste Grund für Bug-Reports wie „läuft auf dem iPhone, stoppt auf Android“.

Wähle deinen Wiedergabe-Ansatz

Es gibt zwei gute Architekturen:

  1. Native Wiedergabe mit @capgo/capacitor-native-audio: Audio wird auf iOS von AVFoundation und auf Android von Media3 ExoPlayer dekodiert. Ideal für Musikplayer, Hörbücher, Meditations-Apps und alles, bei dem der WebView gedrosselt werden könnte. Unterstützt lokale Dateien, Remote-URLs, HLS-Streams, Fades, Wiedergaberate und Now-Playing-Metadaten.
  2. HTML <audio> sowie @capgo/capacitor-media-session: Behalte deinen bestehenden Web-Player und lass das Plugin Metadaten und Steuerelemente an das OS veröffentlichen. Auf Android startet es außerdem den Foreground-Service, während die Session läuft. Ideal, wenn du bereits einen Web-Player hast oder einen einzigen code-Pfad für Web und Native brauchst.
@capgo/capacitor-native-audio <audio> + Media Session
Wiedergabe-Engine Native WebView-
Web-Support Ja (HTML-Audio-Fallback) Ja
Sperrbildschirm-Metadaten showNotification + notificationMetadata setMetadata
Android Foreground Service Du startest einen Plugin startet ihn während der Wiedergabe oder Pause
Remote-Control-Events playbackState Listener setActionHandler
Präzise Low-Latency-SFX Ja Eingeschränkt

iOS für Hintergrund-Audio konfigurieren

Öffne ios/App/App/Info.plist und den Background Mode hinzufügen. Alternativ kannst du in Xcode unter Signing & Capabilities > Background Modes „Audio, AirPlay, and Picture in Picture“ aktivieren, wodurch derselbe Key gesetzt wird:

<key>UIBackgroundModes</key>
<array>
  <string>audio</string>
</array>

Auch die Audio Session Category ist wichtig. @capgo/capacitor-native-audio setzt sie für dich: mit showNotification: true es nutzt .playback mit dem Standard-Modus, der andere Apps unterbricht (Spotify pausiert) und deine App zur „Now Playing“-App macht. Mit showNotification: false es nutzt .playback mit mixWithOthers, was für Soundeffekte ideal ist, aber keine Sperrbildschirm-Steuerung anzeigt. Wähle eine Einstellung pro App, nicht pro Track.

If you play through <audio>, WebKit aktiviert die Playback-Session, sobald die Wiedergabe startet. Stelle sicher, dass die Wiedergabe beim ersten Mal durch eine Benutzerinteraktion gestartet wird, sonst blockiert iOS sie.

Baue mit Xcode 26 oder neuer, bevor du zu App Store Connect hochlädst – das ist seit April 2026 Pflicht. Capgo Build kannst du in der Cloud erledigen, wenn du unter Windows oder Linux arbeitest.

Android für Background-Audio konfigurieren

Füge die Berechtigungen zu 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 ist für Apps mit Android 14 (API 34) oder höher zwingend erforderlich, und Capacitor 8 zielt auf API 36 ab. Ohne diese wird beim Starten des Service ein SecurityException. Beim Veröffentlichen fordert dich die Play Console auf, den Foreground-Service-Typ zu deklarieren und zu begründen. „Media playback the user started“ ist der akzeptierte Anwendungsfall.

POST_NOTIFICATIONS ist eine Runtime-Permission unter Android 13+. Media-Session-Benachrichtigungen sind von der Notification-Permission ausgenommen und erscheinen weiterhin in der Benachrichtigungsleiste, aber jede andere Benachrichtigung, die dein Service postet, braucht sie.

Android 12+ blockiert das Starten eines Foreground-Service, während die App im Hintergrund ist. Starte ihn, wenn der User auf Play drückt, solange deine Activity sichtbar ist.

Option A: Das Media-Session-Plugin den Service ausführen lassen

@capgo/capacitor-media-session deklariert einen mediaPlayback Service in seinem eigenen Manifest und startet es, wenn Sie den Wiedergabestatus auf playing oder paused. Es stoppt es, wenn der Zustand zurückkehrt zu none und die App im Hintergrund ist. Wenn der Service für die gesamte App-Session laufen soll, setze die Plugin-Konfiguration in capacitor.config.ts:

plugins: {
  MediaSession: {
    foregroundService: 'always',
  },
},

Der Standard, nur während der Wiedergabe zu starten, ist das, was die meisten Apps wollen.

Option B: Starte deinen eigenen Foreground-Service

@capgo/capacitor-native-audio verarbeitet Wiedergabe, Fokus und die Medien-Benachrichtigung, erstellt aber keinen Foreground-Service. Wenn du es ohne das Media-Session-Plugin verwendest, füge einen kleinen Service und ein lokales Plugin zu deinem Android-Projekt hinzu.

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 im selben Paket:

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();
    }
}

Registriere es in MainActivity.java bevor super.onCreate, und deklariere den Service im Manifest:

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" />

Von TypeScript aus:

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';

Audio nativ mit @capgo/capacitor-native-audio abspielen

bun add @capgo/capacitor-native-audio
bunx cap sync

Einmalig beim Start konfigurieren, dann vorladen und abspielen:

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 akzeptiert einen Pfad relativ zu deinen Web-Assets (audio/intro.mp3), eine file:// URL, eine https:// URL oder einen HLS- .m3u8 Stream. Für alles außer einem gebündelten Asset, setze isUrl: true. Wenn dein Audio-Host Auth benötigt, übergebe headers.

Erstelle eine Playlist, die von selbst weiterläuft

Der complete Event wird ausgelöst, wenn ein Track endet, auch wenn der Bildschirm gesperrt ist, da der native Player weiterläuft:

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();
  }
});

Halte den Foreground-Service zwischen den Tracks aktiv. Wenn du ihn nach einem Track stoppst und versuchst, ihn im Hintergrund neu zu starten, wirft Android 12+ eine ForegroundServiceStartNotAllowedException.

Halte deine UI mit dem Sperrbildschirm synchron

Nutzer drücken Pause auf dem Sperrbildschirm, an den Kopfhörern oder in der Benachrichtigung. Höre auf playbackState und den nativen Player als Single Source of Truth behandeln:

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
});

Aktualisiere den State auch, wenn die App in den Vordergrund zurückkehrt, mit isPlaying und getCurrentTime, da der WebView während der Ereignisemission pausiert haben kann.

Verwenden Sie ein HTML-Audioelement mit Media Session Controls

Wenn du bereits einen Web-Player hast, behalte ihn bei und füge das Plugin hinzu:

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,
  });
});

Rufe setPositionState in Produktion höchstens ein- bis zweimal pro Sekunde. Der Lockscreen interpoliert zwischen den Updates, und das Überschwemmen der Bridge mit timeupdate Events verschwendet Akku.

Unterbrechungen und Routenwechsel abfangen

Telefonate, Siri, Wecker und andere Apps unterbrechen die Wiedergabe. Native Audio pausiert bei Unterbrechung und setzt auf iOS fort, wenn das System es anzeigt. Zwei weitere Fälle erfordern deine Aufmerksamkeit:

  • Kopfhörer getrennt: Nutzer erwarten, dass die Wiedergabe pausiert. Auf Android registrieren Sie einen Receiver für AudioManager.ACTION_AUDIO_BECOMING_NOISY in Ihrem Service, falls Ihr Player nicht bereits pausiert. Auf iOS lauschen Sie auf Route-Änderungen mit @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 vs. Pausieren: Navigationsansagen ducken Ihr Audio. Apps mit gesprochenem Inhalt sollten stattdessen pausieren, da gedämpftes Sprechschwer zu folgen ist.

Background-Playback testen

Simulatoren lügen über das Hintergrundverhalten. Testen Sie auf echten Geräten:

  1. Wiedergabe starten, Bildschirm sperren, zwei Minuten warten. Die Audio-Wiedergabe muss weiterlaufen.
  2. Verwenden Sie die Lockscreen-Steuerungen für Play, Pause, Next und Seek, entsperren Sie dann und prüfen Sie, ob Ihre UI übereinstimmt.
  3. Abspielen, zu einer anderen App wechseln, die Audio abspielt, und dann zurückkehren.
  4. Starten Sie während der Wiedergabe einen Anruf oder FaceTime-Audio.
  5. Aktivieren Sie auf Android den Energiesparmodus und testen Sie auf einem Samsung- oder Xiaomi-Gerät. Einige OEMs beenden Hintergrund-Apps aggressiv. Ein Foreground-Service überlebt, eine normale Hintergrund-Task nicht.
  6. Auf Android 13+ die Benachrichtigungsberechtigung verweigern und prüfen, ob die Wiedergabe weiterhin funktioniert.
  7. Lass eine Playlist über mehrere Tracks laufen, während der Bildschirm aus ist.

adb shell dumpsys activity services | grep -A3 PlaybackService zeigt an, ob dein Service im Vordergrund läuft.

Fehlerbehebung

Audio stoppt nach etwa 30 Sekunden auf iOS: UIBackgroundModes fehlt audio, oder die Session-Kategorie ist ambient oder soloAmbient. Prüfe Info.plist in der gebauten App, nicht nur in deinem Quellcode.

Audio stoppt nach wenigen Minuten auf Android: kein Foreground-Service, oder der Service ist nicht vom Typ mediaPlayback. Prüfe auch die Akku-Optimierungseinstellungen des Herstellers.

SecurityException: Starting FGS with type mediaPlayback ... requires permissions: hinzufügen FOREGROUND_SERVICE_MEDIA_PLAYBACK.

ForegroundServiceStartNotAllowedException: Du hast den Service gestartet, während die App im Hintergrund war. Starte ihn beim Play-Tap des Nutzers.

Keine Lockscreen-Steuerung auf iOS: showNotification ist false (mixable session) oder es wurden keine Metadaten gesetzt. Nur die App, die eine non-mixable session besitzt, wird zur Now Playing App.

Zwei Benachrichtigungen auf Android: du hast aktiviert showNotification in native audio aktiviert und zusätzlich eine eigene Service-Benachrichtigung gesendet. Halte die Service-Benachrichtigung minimal oder nutze nur den Service des media session Plugins.

Andere Musik-Apps spielen über deine hinweg: Du hast eine mixable session konfiguriert. Setze focus: true und showNotification: true.

Player-Updates schneller ausrollen

Queue-Logik, UI, Metadaten und Analytics für deinen Player liegen in JavaScript. Sobald das native Setup oben in einem Store-Build ist, Capgo Live Updates let you push fixes to that code without a new review cycle. The Dokumentation zu nativem Audio und Dokumentation zu Media Sessions decken alle Optionen ab. Wenn Ihre App auch Audio aufzeichnet, siehe die Capacitor Audio-Recorder-Plugin.

Live-Updates für Capacitor-Apps

Wenn ein Bug im Web-Layer live ist, liefere den Fix über Capgo aus, statt Tage auf die App-Store-Freigabe zu warten. Nutzer bekommen das Update im Hintergrund, während native Änderungen im normalen Review-Prozess bleiben.

Neues aus unserem Blog

Capgo gibt dir die besten Insights, um eine wirklich professionelle Mobile App zu erstellen.