메인 콘텐츠로 건너뛰기

배경에서 오디오 재생하는 방법: Capacitor

iOS에서 Capacitor: 배경 모드, 안드로이드에서 Capacitor: 전면 서비스, 잠금 화면 제어, 플레이리스트 및 중단

Capacitor: 배경에서 오디오 재생하는 방법

배경에서 오디오를 재생하려면 Capacitor에서 두 개의 네이티브 구성 요소를 필요로합니다: audio To play audio in the background in __CAPGO_KEEP_0__, you need two native pieces: the background mode on iOS, and a foreground service on Android. Add lock screen controls through a media session, and your music, podcast or meditation app keeps playing when the user locks the phone or switches apps. This guide shows both setups on __CAPGO_KEEP_0__ 8, with mediaPlayback To play audio in the background in Capacitor, you need two native pieces: the background mode on iOS, and a foreground service on Android. Add lock screen controls through a media session, and your music, podcast or meditation app keeps playing when the user locks the phone or switches apps. This guide shows both setups on Capacitor 8, with @capgo/capacitor-native-audio native 플레이백과 @capgo/capacitor-media-session 시스템 제어를 위해

왜 오디오가 배경에서 멈추는가

A Capacitor 앱은 WebView의 주변에 native shell을 가지고 있기 때문에 각 플랫폼의 라이프 사이클 규칙을 따른다:

iOS Android
배경에서 무슨 일이 일어나는가 앱은 몇 초 안에 중단된다 액티비티가 일시 정지되고, WebView 타이머가 속도 조절되고, 프로세스가 죽을 수 있다
오디오가 살아남는 이유 UIBackgroundModes contains audio, 오디오 세션 카테고리 playback Ahead of background service type mediaPlayback notification type
silent switch playback category 무시 ambient category 존중 없음
lock screen control MPNowPlayingInfoCenter and MPRemoteCommandCenter MediaSession media style notification
store rule 사용자가 기대하는 청각 콘텐츠를 재생해야 함 (지침 2.5.4) background service type은 실제 사용과 일치해야 하며 Play Console에서 선언해야 함

iOS와 안드로이드 모두 기본적으로 배경 오디오를 제공하지 않으며, 오직 한쪽만 고치면 '아이폰에서 작동하지만 안드로이드에서는 중단' 버그 보고서의 가장 일반적인 이유입니다.

재생 방법을 선택하세요

두 가지 좋은 아키텍처가 있습니다.

  1. 네이티브 재생 @capgo/capacitor-native-audio: iOS에서는 AVFoundation이 오디오를 디코딩하고 안드로이드에서는 Media3 ExoPlayer가 오디오를 디코딩합니다. 음악 플레이어, 오디오북, 미디어션 앱, WebView가 속도 제한을 받을 수 있는 모든 앱에 적합합니다. 지역 파일, 원격 URL, HLS 스트림, 어둡기, 속도, Now Playing 메타데이터를 지원합니다.
  2. HTML <audio> + @capgo/capacitor-media-session: 기존 웹 플레이어를 유지하고 플러그인에서 OS에 메타데이터와 제어를 공개하세요. 안드로이드에서는 세션을 재생하는 동안 전면 서비스를 실행합니다. 웹 플레이어가 이미 있는 경우 또는 웹과 네이티브에 단일 code 경로가 필요할 때 가장 적합합니다.
@capgo/capacitor-native-audio <audio> + 미디어 세션
재생 엔진 네이티브 웹뷰
웹 지원 Yes (HTML 오디오 fallback) Yes
컨텍스트: Capgo 솔루션 마케팅 페이지. 역할: 짧은 UI 레이블 또는 네비게이션 아이템. 보이는 곳: 페이지 솔루션/비타 테스팅.astro. 메시지 키 `solutions_beta_testing_compare_native_tf` (솔루션 비타 테스팅 비교 네이티브 Tf). | 컨텍스트: Capgo 솔루션 마케팅 페이지. 역할: 짧은 UI 레이블 또는 네비게이션 아이템. 보이는 곳: 페이지 솔루션/PR-프리뷰.astro. 메시지 키 `solutions_pr_compare_native_tf` (솔루션 PR 비교 네이티브 Tf). showNotification + notificationMetadata setMetadata
락 스크린 메타데이터 Android 전면 서비스 사용자가 시작합니다
플러그인은 재생 또는 일시 정지 중에 시작합니다 playbackState listener setActionHandler
리스너 Yes Limited

iOS를 백그라운드 오디오로 구성하십시오.

열기 ios/App/App/Info.plist 백그라운드 모드를 추가하십시오. Xcode의 Signing &amp; Capabilities, Background Modes에서 '오디오, AirPlay 및 Picture in Picture'도 체크할 수 있습니다. 이는 동일한 키를 작성합니다:

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

오디오 세션 카테고리가 중요합니다. @capgo/capacitor-native-audio 이를 설정하면 기본 모드를 사용합니다: 다른 앱을 중단합니다 (Spotify는 일시 중단) 및 앱을 Now Playing 앱으로 만듭니다. 오디오 효과를 위해 사용하는 것이 좋습니다. 오디오 효과는 잠금 화면 제어를 표시하지 않습니다. 앱당 하나씩 선택하십시오, 트랙당 하나씩 선택하십시오. showNotification: true 이것은 사용합니다. .playback 열기 showNotification: false 이것은 사용합니다. .playback with mixWithOthersiOS는 백그라운드 오디오를 허용하기 위해 백그라운드 모드를 필요로 합니다.

iOS는 백그라운드 오디오를 허용하기 위해 백그라운드 모드를 필요로 합니다. <audio>iOS는 백그라운드 오디오를 허용하기 위해 백그라운드 모드를 필요로 합니다.

Xcode 26 이상으로 빌드하고 App Store Connect에 업로드하세요. 이는 2026년 4월부터 필요합니다. Capgo 빌드 윈도우나 리눅스에서 클라우드에서 작업할 수 있습니다.

배경 오디오를 위한 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을 대상으로 한다면 필수입니다. 그렇지 않으면 서비스를 시작할 때 API이 발생합니다. 앱을 배포할 때, Play Console은 전경 서비스 유형을 선언하고 그 이유를 설명하도록 요청합니다. '사용자가 시작한 미디어 재생'이 허용된 용도입니다. SecurityExceptionAndroid 13 이상의 경우 미디어 세션通知은通知 권한에서 면제되며 여전히 그림자에 표시되지만 서비스가 게시한 다른通知는 필요합니다.

POST_NOTIFICATIONS Android 12 이상에서는 앱이 배경에서 실행 중일 때 전경 서비스를 시작하는 것을 차단합니다. 사용자가 플레이 버튼을 누를 때, 활동이 표시될 때 서비스를 시작하세요.

Option A: 미디어 세션 플러그인에서 서비스를 실행하세요.

서비스를 선언하고

@capgo/capacitor-media-session 서비스를 선언하고 mediaPlayback 재생 상태를 설정할 때 서비스를 시작합니다. playing 또는 paused이 상태가 돌아가면 그것을 멈추고 none 기본 설정은 재생 중인 동안만 시작하는 것입니다. 대부분의 앱이 원하는 설정입니다. capacitor.config.ts:

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

Option B: 앱에서 직접 미디어 서비스를 시작합니다.

Option B: foreground 서비스를 직접 시작하십시오

@capgo/capacitor-native-audio 같은 패키지에 있습니다:

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 AndroidManifest.xml에 등록합니다.

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 before super.onCreateTypeScript에서:

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

Play audio natively with @__CAPGO_KEEP_0__/__CAPGO_KEEP_1__-native-audio

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

Play audio natively with @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 웹 자산 상대 경로 (audio/intro.mp3), a file:// URL https:// 을 설정하세요. 오디오 호스트가 인증을 필요로 한다면, .m3u8 자동으로 다음 트랙을 진행하는 플레이리스트를 빌드하세요. isUrl: true스크린이 잠금된 상태에서도 트랙이 끝났을 때 발생하는 이벤트입니다. headers.

자동 재생 플레이리스트 만들기

The complete __CAPGO_KEEP_0__

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

__CAPGO_KEEP_0__ ForegroundServiceStartNotAllowedException.

__CAPGO_KEEP_0__

사용자는 잠금 화면, 헤드폰, 또는 알림에서 일시 정지 버튼을 누릅니다. playbackState 그리고 native 플레이어를 진실의 근거로 다루세요:

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 사용하고 getCurrentTimecontext

사용자가 WebView가 이벤트가 발생하는 동안 일시 정지되었을 수 있으므로

HTML 오디오 요소와 미디어 세션 제어를 사용하세요

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

Call setPositionState Call timeupdate 최대 1초 또는 2초 이내에 한번 또는 두 번씩 프로덕션에서 호출하세요. 잠금 화면은 업데이트 간에 중간값을 계산하고, 브리지에 이벤트를 과도하게 보내면 배터리가 소모됩니다.

중단과 경로 변경을 처리하세요

전화, 시리, 알람 및 다른 앱이 재생을 중단합니다. Native 오디오는 중단 시 일시 정지되고, iOS에서는 시스템이 재개할 때 재개합니다. 두 가지 더 많은 경우에 주의가 필요합니다:

  • 이어폰이 뽑혀있음: 사용자는 재생이 중단되기를 기대합니다. 안드로이드에서 AudioManager.ACTION_AUDIO_BECOMING_NOISY if player가 이미 중단되지 않은 경우에만 @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'
});
  • iOS에서 Ducking vs. 중단

: 네비게이션 프롬프트는 음성을 낮추지만, 음성이 들리지 않습니다. 말하는 앱은 중단되야 합니다.

배경 재생 테스트

  1. 시뮬레이터는 배경 동작에 대해 거짓말합니다. 실제 기기에서 테스트하세요:
  2. 재생을 시작하고 화면을 잠그세요. 2분 기다리세요. 음성이 계속 재생되어야 합니다.
  3. 잠금 화면에서 재생, 중단, 다음, seek 제어를 사용하세요. 잠금을 풀고 UI가 일치하는지 확인하세요.
  4. 재생을 시작하고 다른 앱으로 전환하세요. 다시 돌아오세요.
  5. 전화나 FaceTime 음성 통화를 시작하세요.
  6. 안드로이드 13 이상에서 알림 권한을 거부하고 플레이백이 작동하는지 확인하세요.
  7. 스크린을 끄고 여러 곡이 재생되는 플레이리스트를 실행하세요.

adb shell dumpsys activity services | grep -A3 PlaybackService 서비스가 전면 모드에서 실행 중인지 여부를 보여줍니다.

문제 해결

iOS에서 오디오는 약 30초 후에 중단됩니다.: UIBackgroundModes 없습니다 audioor 세션 카테고리가 ambient or soloAmbient. 확인 Info.plist built 앱에서만 아니라 소스에서만 플레이합니다.

안드로이드에서 몇 분 후에 오디오가 멈추게 됩니다.: foreground 서비스가 없거나 서비스의 유형이 아닙니다. mediaPlayback또한 OEM 배터리 최적화 설정을 확인하세요.

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

ForegroundServiceStartNotAllowedException서비스를 백그라운드에서 시작한 경우 서비스를 사용자 탭 클릭 시 시작하십시오.

iOS에서 Lock Screen 제어 없음: showNotification 세션의 mixable 속성이 false (혼합 가능 세션) 이거나, metadata가 설정되지 않았다면. 혼합 불가능 세션을 소유한 앱만 Now Playing 앱이 된다.

Android에서 두 개의 알림: 사용을 활성화했습니다 showNotification native audio에서 서비스 알림을 게시했습니다. 서비스 알림을 최소화하거나 미디어 세션 플러그인의 서비스만 사용하세요.

다른 음악 앱이 계속 재생됩니다.: 믹스 가능한 세션을 구성했습니다. 설정 focus: true and showNotification: true.

플레이어 변경을 더 빠르게 배포하세요.

Queue logic, UI, metadata, 및 분석을 위해 JavaScript에서 플레이어를 위한 설정이 있습니다. native 설정이 저장 빌드에 포함된 후에 Capgo Live Update fixes를 code에 푸시하여 새로운 검토 주기를 거치지 않고 native audio 문서 그리고 media session 문서 모든 옵션을 다루고, 앱이 음성을 녹음하는 경우 __CAPGO_KEEP_0__ 음성 녹음 플러그인을 참조하십시오 Capacitor 오디오 녹음 플러그인.

Capgo 앱에 대한 Capacitor의 실시간 업데이트

When a web-layer bug is live, ship the fix through Capgo instead of waiting days for app store approval. Users get the update in the background while native changes stay in the normal review path.

최신 블로그

Capgo은 전문적인 모바일 앱을 만들기 위해 필요한 최고의洞察력을 제공합니다.