Aller au contenu principal

Comment lire de l'audio en arrière-plan dans Capacitor

Jouer de l'audio en arrière-plan avec Capacitor : mode audio iOS, service de premier plan Android, contrôles sur l'écran de verrouillage, playlists et interruptions.

Lecture audio en arrière-plan avec contrôles sur l'écran verrouillé dans une application Capacitor

Pour jouer de l'audio en arrière-plan avec Capacitor, vous avez besoin de deux éléments natifs : le audio mode arrière-plan sur iOS, et un mediaPlayback service de premier plan sur Android. Ajoutez des contrôles sur l'écran de verrouillage via une session média, et votre application de musique, de podcast ou de méditation continue de jouer lorsque l'utilisateur verrouille son téléphone ou change d'application. Ce guide présente les deux configurations sur Capacitor 8, avec @capgo/capacitor-native-audio pour la lecture native et @capgo/capacitor-media-session pour les contrôles système.

Pourquoi l'audio s'arrête en arrière-plan

Une application Capacitor est un shell natif autour d'une WebView et suit donc les règles de cycle de vie de chaque plateforme :

iOS Android
Comportement en arrière-plan L'app est suspendue en quelques secondes L'activité est en pause, les timers de la WebView sont bridés, le processus peut être tué
Ce qui maintient l'audio actif UIBackgroundModes contient audio, catégorie de session audio playback Un service de premier plan de type mediaPlayback avec une notification
Bouton silencieux playback la catégorie l'ignore, ambient la respecte Aucun équivalent
Contrôles de l’écran verrouillé MPNowPlayingInfoCenter et MPRemoteCommandCenter MediaSession avec une notification de style média
Règle du store Doit diffuser un contenu audible attendu par l’utilisateur (directive 2.5.4) Le type de service de premier plan doit correspondre à l’usage réel, déclaré dans Play Console

Aucune des deux plateformes ne fournit d’audio en arrière-plan par défaut, et corriger un seul côté est la cause la plus fréquente des signalements de bug « fonctionne sur iPhone, s’arrête sur Android ».

Choisissez votre approche de lecture

Il existe deux bonnes architectures :

  1. Lecture native avec @capgo/capacitor-native-audio: l’audio est décodé par AVFoundation sur iOS et Media3 ExoPlayer sur Android. Idéal pour les lecteurs de musique, les livres audio, les apps de méditation et tout ce que le WebView risque de brider. Prend en charge les fichiers locaux, les URL distantes, les flux HLS, les fondus, la vitesse de lecture et les métadonnées Now Playing.
  2. HTML <audio> , ce qui @capgo/capacitor-media-session: conservez votre lecteur web existant et laissez le plugin publier les métadonnées et les contrôles vers le système d'exploitation. Sur Android, il gère également le service en premier plan pendant la lecture. Idéal si vous avez déjà un lecteur web ou si vous avez besoin d'un seul chemin code pour le web et le natif.
@capgo/capacitor-native-audio <audio> + media session
Moteur de lecture Natif WebView
Support web Oui (repli sur l'audio HTML) Oui
Métadonnées de l'écran de verrouillage showNotification + notificationMetadata setMetadata
Service en premier plan Android Vous en démarrez un Le plugin le démarre pendant la lecture ou la pause
Événements de contrôl’à distance playbackState listener setActionHandler
SFX précis à faible latence Oui Limité

Configurer iOS pour l'audio en arrière-plan

Ouvrez ios/App/App/Info.plist et ajoutez le mode en arrière-plan. Vous pouvez aussi cocher « Audio, AirPlay, and Picture in Picture » sous Signing & Capabilities, Background Modes dans Xcode, ce qui écrit la même clé :

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

La catégorie de session audio compte aussi. @capgo/capacitor-native-audio le définit pour vous : avec showNotification: true il utilise .playback avec le mode par défaut, qui interrompt les autres apps (Spotify se met en pause) et fait de votre app l'app Now Playing. Avec showNotification: false il utilise .playback par mixWithOthers, ce qui convient aux effets sonores mais n'affiche pas les contrôles sur l'écran de verrouillage. Choisissez-en un par app, pas par piste.

Si vous diffusez via <audio>, WebKit active la session de lecture dès que le média démarre. Veillez à ce que la lecture soit lancée par un geste utilisateur la première fois, sinon iOS la bloquera.

Compilez avec Xcode 26 ou une version ultérieure avant l'upload sur App Store Connect : c'est obligatoire depuis avril 2026. Build Capgo peut le faire dans le cloud si vous travaillez sous Windows ou Linux.

Configurer Android pour l'audio en arrière-plan

Ajoutez les autorisations à 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 est obligatoire pour les apps ciblant Android 14 (API 34) ou version supérieure, et Capacitor 8 cible API 36. Sans cela, le démarrage du service lève une SecurityException. Lors de la publication, Play Console vous demande de déclarer le type de service en premier plan et de le justifier. « Lecture de médias initiée par l’utilisateur » est l’usage accepté.

POST_NOTIFICATIONS est une permission d’exécution sur Android 13+. Les notifications de session média sont exemptées de la permission de notification et s’affichent toujours dans le panneau, mais toute autre notification publiée par votre service en a besoin.

Android 12+ bloque le démarrage d’un service en premier plan lorsque l’application est en arrière-plan. Lancez-le lorsque l’utilisateur appuie sur lecture, pendant que votre activité est visible.

Option A : laisser le plugin media session gérer le service

@capgo/capacitor-media-session déclare un mediaPlayback service dans son propre manifest et le démarre lorsque vous définissez l’état de lecture sur playing ou paused. Il l’arrête lorsque l’état repasse à none et que l’application est en arrière-plan. Si vous voulez que le service tourne pendant toute la session de l’application, configurez le plugin dans capacitor.config.ts:

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

The default, starting only during playback, is what most apps want.

Option B : démarrer votre propre foreground service

@capgo/capacitor-native-audio gère la lecture, le focus et la notification média, mais ne crée pas le service en premier plan. Si vous l’utilisez sans le plugin de session média, ajoutez un petit service et un plugin local à votre projet 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 dans le même package :

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

Enregistrez-le dans MainActivity.java avant super.onCreate, et déclarez le service dans le manifeste :

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

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

Lire l'audio nativement avec @capgo/capacitor-native-audio

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

Configurez une fois au démarrage, puis préchargez et jouez :

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 accepte un chemin relatif à vos assets web (audio/intro.mp3), un file:// URL, un https:// URL ou un flux HLS .m3u8 . Pour tout autre type d'asset que ceux du bundle, définissez isUrl: true. Si votre hôte audio exige une authentification, passez headers.

Créer une playlist qui avance toute seule

Le complete l’événement se déclenche à la fin d’une piste, même si l’écran est verrouillé, car le lecteur natif continue de fonctionner :

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

Gardez le service en premier plan actif entre les pistes. Si vous l’arrêtez après une piste puis essayez de le relancer en arrière-plan, Android 12+ lève ForegroundServiceStartNotAllowedException.

Synchroniser votre UI avec l'écran de verrouillage

Les utilisateurs appuieront sur pause depuis l'écran de verrouillage, via leurs écouteurs ou depuis la notification. Écoutez playbackState et utilisez le lecteur natif comme source de vérité :

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

Rafraîchissez aussi l'état quand l'application revient au premier plan, en utilisant isPlaying et getCurrentTime, car la WebView a pu être mise en pause pendant l'émission des événements.

Utilisez un élément audio HTML avec les contrôles Media Session

Si vous avez déjà un lecteur web, conservez-l’et ajoutez le plugin :

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

Appelez setPositionState au maximum une ou deux fois par seconde en production. L'écran de verrouillage interpole entre les mises à jour, et saturer le bridge avec des timeupdate événements épuise la batterie.

Gérer les interruptions et les changements de route

Les appels téléphoniques, Siri, les alarmes et d'autres applications interrompent la lecture. L'audio natif se met en pause lors d'une interruption et, sur iOS, reprend lorsque le système l'autorise. Deux autres cas méritent votre attention :

  • Casque débranché: users expect playback to pause. On Android, register a receiver for AudioManager.ACTION_AUDIO_BECOMING_NOISY dans votre service si votre lecteur ne se met pas déjà en pause. Sur iOS, écoutez les changements de route avec @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. pause: les invites de navigation atténuent votre audio. Les applications de parole doivent plutôt mettre en pause, car une voix atténuée est difficile à suivre.

Tester la lecture en arrière-plan

Les simulateurs mentent sur le comportement en arrière-plan. Testez sur de vrais appareils :

  1. Lancez la lecture, verrouillez l’écran, attendez deux minutes. L’audio doit continuer à jouer.
  2. Utilisez les commandes lecture, pause, suivant et seek sur l’écran verrouillé, puis déverrouillez et vérifiez votre UI.
  3. Lancez la lecture, passez à une autre application qui joue de l’audio, puis revenez.
  4. Passez un appel téléphonique ou un FaceTime audio pendant la lecture.
  5. Sur Android, activez l'économiseur de batterie et testez sur un appareil Samsung ou Xiaomi. Certains fabricants tuent agressivement les applications en arrière-plan. Un foreground service survit, une simple tâche en arrière-plan non.
  6. Sur Android 13+, refusez la permission de notification et confirmez que la lecture fonctionne toujours.
  7. Laissez une playlist jouer plusieurs morceaux avec l'écran éteint.

adb shell dumpsys activity services | grep -A3 PlaybackService indique si votre service tourne au premier plan.

Troubleshooting

L'audio s'arrête après environ 30 secondes sur iOS: UIBackgroundModes manque audio, ou la catégorie de session est ambient ou soloAmbient. Vérifiez Info.plist dans l'app compilée, pas seulement dans votre code source.

L'audio s'arrête après quelques minutes sur Android: aucun service au premier plan, ou le service n'est pas de type mediaPlayback. Also check OEM battery optimization settings.

SecurityException: Starting FGS with type mediaPlayback ... requires permissions: ajouter FOREGROUND_SERVICE_MEDIA_PLAYBACK.

ForegroundServiceStartNotAllowedException: vous avez démarré le service alors que l'app était en arrière-plan. Lancez-le lors de l'appui de l'utilisateur sur lecture.

Aucun contrôle sur l'écran de verrouillage sous iOS: showNotification est false (session mixable), ou aucune métadonnée n'a été définie. Seule l'app qui possède une session non mixable devient l'app Now Playing.

Deux notifications sous Android: vous avez activé showNotification dans native audio et a également publié sa propre notification de service. Gardez la notification de service minimale, ou utilisez uniquement le service du plugin media session.

Other music apps keep playing over yours: vous avez configuré une session mixable. Définissez focus: true et showNotification: true.

Déployez les modifications du lecteur plus rapidement

La logique de file d'attente, l'interface, les métadonnées et l'analytique de votre lecteur sont en JavaScript. Une fois la configuration native ci-dessus intégrée dans un build store, les live updates Capgo vous permet de pousser des correctifs vers ce code sans nouveau cycle de revue. Le documentation native audio et documentation media session couvrez toutes les options, et si votre application enregistre aussi de l'audio, consultez le plugin d'enregistrement audio Capacitor.

Live updates pour les apps Capacitor

Lorsqu'un bug de la couche web est en ligne, déployez le correctif via Capgo au lieu d'attendre des jours l'approbation des stores. Les utilisateurs reçoivent la mise à jour en arrière-plan, tandis que les changements natifs suivent le circuit de validation habituel.

Les derniers articles du blog

Capgo vous offre les meilleures informations pour créer une application mobile vraiment professionnelle.