アプリが画面上にないときでも、バックグラウンドでメディアを再生できます。たとえば、ユーザーが他のアプリを操作している間も再生できます。
これを行うには、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 を使用して、デバイスがスリープ状態になるなど、システムが再生を妨げないようにする必要があります。
wake lock とは、スマートフォンがアイドル状態でも、アプリで使用中の機能はそのまま使用可能にしておく必要があることをシステムに伝えるシグナルです。
CPU が MediaPlayer の再生中は実行され続けるようにするには、
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 は、アプリでのメディア再生におすすめのソリューションです。詳細についてご覧ください。
次のページでは、音声と動画の録音、保存、再生に関するトピックについて説明しています。