アプリが画面上にない場合でも、バックグラウンドでメディアを再生できます(ユーザーが他のアプリを操作している間など)。
そのためには、MediaPlayer を MediaBrowserServiceCompat
サービスに埋め込み、別のアクティビティの MediaBrowserCompat
とやり取りさせます。
このクライアントとサーバーのセットアップを実装する際は、十分に注意してください。バックグラウンド サービスで実行されるプレーヤーと、システムの他の部分とのやりとりの方法に、いくつかの前提があるためです。アプリがこれらの前提を満たさない場合は、ユーザー エクスペリエンスが低下する可能性があります。詳細については、オーディオ アプリの作成をご覧ください。
このページでは、サービス内に実装する MediaPlayer を管理する際の特別な手順について説明します。
非同期で実行する
Activity
と同様に、Service
内の作業はデフォルトですべて単一のスレッドで行われます。実際に、アクティビティとサービスを同じアプリから実行すると、デフォルトでは両方とも同じスレッド(「メインスレッド」)を使用します。
サービスは受信インテントを迅速に処理し、応答に時間がかかる計算は行わないようにする必要があります。負荷の高い作業や進行を妨げる呼び出しは、非同期で実行する必要があります。そのためには、別のスレッドを自分で実装するか、Android フレームワークがサポートするさまざまな非同期処理用の仕組みを使用します。
たとえば、メインスレッドから MediaPlayer
を使用する場合は、次のようにする必要があります。
prepare()
ではなくprepareAsync()
を呼び出し、- 準備が完了して再生を開始できるときに通知を受け取るために、
MediaPlayer.OnPreparedListener
を実装します。
例:
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 が推奨されるソリューションです。詳しくは、こちらをご覧ください。
以下は音声と動画の録音、録画、保存、再生に関するトピックを扱うページです。