バックグラウンドでメディアを再生する

アプリが画面上にない場合でも、バックグラウンドでメディアを再生できます(ユーザーが他のアプリを操作している間など)。

そのためには、MediaPlayer を MediaBrowserServiceCompat サービスに埋め込み、別のアクティビティの MediaBrowserCompat とやり取りさせます。

このクライアントとサーバーのセットアップを実装する際は、十分に注意してください。バックグラウンド サービスで実行されるプレーヤーと、システムの他の部分とのやりとりの方法に、いくつかの前提があるためです。アプリがこれらの前提を満たさない場合は、ユーザー エクスペリエンスが低下する可能性があります。詳細については、オーディオ アプリの作成をご覧ください。

このページでは、サービス内に実装する MediaPlayer を管理する際の特別な手順について説明します。

非同期で実行する

Activity と同様に、Service 内の作業はデフォルトですべて単一のスレッドで行われます。実際に、アクティビティとサービスを同じアプリから実行すると、デフォルトでは両方とも同じスレッド(「メインスレッド」)を使用します。

サービスは受信インテントを迅速に処理し、応答に時間がかかる計算は行わないようにする必要があります。負荷の高い作業や進行を妨げる呼び出しは、非同期で実行する必要があります。そのためには、別のスレッドを自分で実装するか、Android フレームワークがサポートするさまざまな非同期処理用の仕組みを使用します。

たとえば、メインスレッドから MediaPlayer を使用する場合は、次のようにする必要があります。

例:

Kotlin

private const val ACTION_PLAY: String = "com.example.action.PLAY"

class MyService: Service(), MediaPlayer.OnPreparedListener {

    private var mMediaPlayer: MediaPlayer? = null

    override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
        ...
        val action: String = intent.action
        when(action) {
            ACTION_PLAY -> {
                mMediaPlayer = ... // initialize it here
                mMediaPlayer?.apply {
                    setOnPreparedListener(this@MyService)
                    prepareAsync() // prepare async to not block main thread
                }

            }
        }
        ...
    }

    /** Called when MediaPlayer is ready */
    override fun onPrepared(mediaPlayer: MediaPlayer) {
        mediaPlayer.start()
    }
}

Java

public class MyService extends Service implements MediaPlayer.OnPreparedListener {
    private static final String ACTION_PLAY = "com.example.action.PLAY";
    MediaPlayer mediaPlayer = null;

    public int onStartCommand(Intent intent, int flags, int startId) {
        ...
        if (intent.getAction().equals(ACTION_PLAY)) {
            mediaPlayer = ... // initialize it here
            mediaPlayer.setOnPreparedListener(this);
            mediaPlayer.prepareAsync(); // prepare async to not block main thread
        }
    }

    /** Called when MediaPlayer is ready */
    public void onPrepared(MediaPlayer player) {
        player.start();
    }
}

非同期エラーを処理する

同期オペレーションでは、エラーは例外またはエラーコードで通知されます。ただし、非同期リソースを使用する場合は、エラーを適切にアプリに通知する必要があります。MediaPlayer の場合は、MediaPlayer.OnErrorListener を実装して MediaPlayer インスタンスに設定します。

Kotlin

class MyService : Service(), MediaPlayer.OnErrorListener {

    private var mediaPlayer: MediaPlayer? = null

    fun initMediaPlayer() {
        // ...initialize the MediaPlayer here...
        mediaPlayer?.setOnErrorListener(this)
    }

    override fun onError(mp: MediaPlayer, what: Int, extra: Int): Boolean {
        // ... react appropriately ...
        // The MediaPlayer has moved to the Error state, must be reset!
    }
}

Java

public class MyService extends Service implements MediaPlayer.OnErrorListener {
    MediaPlayer mediaPlayer;

    public void initMediaPlayer() {
        // ...initialize the MediaPlayer here...
        mediaPlayer.setOnErrorListener(this);
    }

    @Override
    public boolean onError(MediaPlayer mp, int what, int extra) {
        // ... react appropriately ...
        // The MediaPlayer has moved to the Error state, must be reset!
    }
}

エラーが発生すると、MediaPlayer は「エラー」状態に移行します。再び使用するにはリセットする必要があります。詳細については、MediaPlayer クラスの完全な状態図をご覧ください。

wake lock を使用する

バックグラウンドで音楽を再生またはストリーミングする場合は、デバイスをスリープ状態にするなど、システムが再生を妨げないようにするために、ウェイクロックを使用する必要があります。

wake lock とは、スマートフォンがアイドル状態でも、アプリで使用中の機能はそのまま使用可能にしておく必要があることをシステムに伝える方法です。

MediaPlayer の再生中は CPU が実行され続けるようにするには、MediaPlayer の初期化時に setWakeMode() メソッドを呼び出します。MediaPlayer は、再生中は指定したロックを保持し、一時停止または停止したときにロックを解除します。

Kotlin

mediaPlayer = MediaPlayer().apply {
    // ... other initialization here ...
    setWakeMode(applicationContext, PowerManager.PARTIAL_WAKE_LOCK)
}

Java

mediaPlayer = new MediaPlayer();
// ... other initialization here ...
mediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);

ただし、この例で取得した wake lock では、CPU が実行され続けることのみが保証されます。Wi-Fi を含むネットワークを介してメディアをストリーミングする場合は、WifiLock も保持すべきですが、これについては手動で取得と解除を行う必要があります。Wi-Fi ロックの作成と取得は、リモート URL を使用して MediaPlayer の準備を開始するときに行います。

例:

Kotlin

val wifiManager = getSystemService(Context.WIFI_SERVICE) as WifiManager
val wifiLock: WifiManager.WifiLock =
    wifiManager.createWifiLock(WifiManager.WIFI_MODE_FULL, "mylock")

wifiLock.acquire()

Java

WifiLock wifiLock = ((WifiManager) getSystemService(Context.WIFI_SERVICE))
    .createWifiLock(WifiManager.WIFI_MODE_FULL, "mylock");

wifiLock.acquire();

メディアを一時停止または停止するときや、ネットワークが不要になったときは、ロックを解除します。

Kotlin

wifiLock.release()

Java

wifiLock.release();

クリーンアップを実行する

前述のように、MediaPlayer オブジェクトはシステム リソースを大量に消費する可能性があるため、必要な間だけ保持し、作業が終了したら release() を呼び出すようにします。システムのガベージ コレクションに頼るのではなく、このクリーンアップ メソッドを明示的に呼び出すことが重要です。その理由は、ガベージ コレクターが MediaPlayer 使用分の回収を開始するまでには時間がかかる場合があることと、反応するのはメモリの需要に対してのみで、他のメディア関連リソースの不足には対応できないことです。そのため、サービスを使用している場合は、必ず onDestroy() メソッドをオーバーライドし、MediaPlayer を確実に解放するようにします。

Kotlin

class MyService : Service() {

    private var mediaPlayer: MediaPlayer? = null
    // ...

    override fun onDestroy() {
        super.onDestroy()
        mediaPlayer?.release()
    }
}

Java

public class MyService extends Service {
   MediaPlayer mediaPlayer;
   // ...

   @Override
   public void onDestroy() {
       super.onDestroy();
       if (mediaPlayer != null) mediaPlayer.release();
   }
}

使用終了時の解放の他にも、MediaPlayer を解放できる機会を常に探るようにします。たとえば、長時間メディアの再生ができないと予測される場合(音声フォーカスを失った場合など)は、既存の MediaPlayer を必ず解放し、後で再度作成するようにします。一方、短時間だけ再生を停止する場合は、MediaPlayer をそのまま保持して、再度の作成と準備にかかるオーバーヘッドを回避するほうがよいでしょう。

詳細

アプリでのメディア再生には、Jetpack Media3 が推奨されるソリューションです。詳しくは、こちらをご覧ください。

以下は音声と動画の録音、録画、保存、再生に関するトピックを扱うページです。