Możesz odtwarzać multimedia w tle, nawet gdy aplikacja nie jest widoczna na ekranie, np. gdy użytkownik korzysta z innych aplikacji.
W tym celu należy umieścić MediaPlayer w usłudze MediaBrowserServiceCompat
i skonfigurować jego interakcję z elementem MediaBrowserCompat
w innej aktywności.
Podczas wdrażania tej konfiguracji klienta i serwera należy zachować ostrożność. Istnieją oczekiwania dotyczące tego, jak odtwarzacz działający w tle współpracuje z resztą systemu. Jeśli aplikacja nie spełnia tych oczekiwań, użytkownik może być niezadowolony. Więcej informacji znajdziesz w artykule Tworzenie aplikacji audio.
Na tej stronie znajdziesz specjalne instrukcje zarządzania MediaPlayerem, gdy jest on używany w usłudze.
Wykonywanie asynchroniczne
Podobnie jak w przypadku Activity
, wszystkie działania w Service
są domyślnie wykonywane w ramach jednego wątku. W zasadzie, gdy uruchamiasz aktywność i usługę z tej samej aplikacji, domyślnie korzystają one z tego samego wątku („głównego wątku”).
Usługi muszą szybko przetwarzać przychodzące intencje i nigdy nie wykonywać długich obliczeń podczas odpowiadania na nie. Wszystkie wymagające dużej mocy obliczeniowej operacje i blokujące wywołania należy wykonywać asynchronicznie: albo z innego wątku, który implementujesz samodzielnie, albo za pomocą wielu funkcji frameworka do przetwarzania asynchronicznego.
Jeśli na przykład używasz MediaPlayer
w głównym wątku, musisz:
- Zadzwoń pod numer
prepareAsync()
, a nieprepare()
, i - Wprowadź
MediaPlayer.OnPreparedListener
, aby otrzymywać powiadomienia o gotowości do gry.
Przykład:
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();
}
}
Obsługa błędów asynchronicznych
W przypadku operacji synchronicznych błędy są sygnalizowane za pomocą wyjątku lub kodu błędu. Jeśli jednak używasz zasobów asynchronicznych, musisz odpowiednio poinformować aplikację o błędach. W przypadku MediaPlayer
zaimplementuj MediaPlayer.OnErrorListener
i skonfiguruj go w instancji 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!
}
}
Gdy wystąpi błąd, MediaPlayer
przechodzi w stan Błąd. Musisz go zresetować, zanim będzie można go ponownie użyć. Szczegółowe informacje znajdziesz na diagramie stanów klasy MediaPlayer
.
Używanie blokad uśpienia
Podczas odtwarzania lub przesyłania strumieniowego muzyki w tle musisz używać blokad uśpienia, aby system nie zakłócał odtwarzania, na przykład przez przełączenie urządzenia w tryb uśpienia.
Blokada wybudzania to sygnał dla systemu, że aplikacja korzysta z funkcji, które powinny być dostępne nawet wtedy, gdy telefon jest nieaktywny.
Aby zapewnić, że procesor będzie nadal działać, gdy MediaPlayer
jest odtwarzany, wywołaj metodę setWakeMode()
podczas inicjowania MediaPlayer
. MediaPlayer
zachowuje określony stan blokady podczas odtwarzania i odblokowuje go po wstrzymaniu lub zatrzymaniu:
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);
Blokada uśpienia uzyskana w tym przykładzie zapewnia jednak tylko to, że procesor pozostaje aktywny. Jeśli przesyłasz strumieniowo multimedia przez sieć i używasz Wi-Fi, prawdopodobnie chcesz też zablokować WifiLock
, który musisz uzyskać i zwolnić ręcznie. Gdy więc zaczniesz przygotowywać MediaPlayer
z użyciem adresu URL zdalnego, musisz utworzyć i uzyskać blokadę Wi-Fi.
Przykład:
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();
Gdy wstrzymasz lub zatrzymasz multimediów lub gdy nie będziesz już potrzebować sieci, odblokuj:
Kotlin
wifiLock.release()
Java
wifiLock.release();
Czyszczenie
Jak już wspomnieliśmy, obiekt MediaPlayer
może zużywać znaczną ilość zasobów systemowych, dlatego należy go przechowywać tylko tak długo, jak jest to konieczne, a po zakończeniu pracy wywołać metodę release()
. Należy wywołać tę metodę czyszczenia wprost, a nie polegać na systemie zbierania elementów do usunięcia, ponieważ może minąć trochę czasu, zanim zbieracz elementów do usunięcia odzyskaMediaPlayer
, ponieważ jest on wrażliwy tylko na potrzeby pamięci, a nie na niedobór innych zasobów związanych z multimediami. Jeśli korzystasz z usługi, zawsze zastępuj metodę onDestroy()
, aby mieć pewność, że zwalniasz 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();
}
}
Oprócz zwalniania MediaPlayer
podczas wyłączania urządzenia, zawsze szukaj innych możliwości zwolnienia. Jeśli na przykład przewidujesz, że nie będziesz mieć możliwości odtwarzania multimediów przez dłuższy czas (np. po utracie skupienia na dźwięku), zdecydowanie zwolnij bieżący MediaPlayer
i utwórz go ponownie później. Jeśli jednak zamierzasz zatrzymać odtwarzanie tylko na bardzo krótki czas, prawdopodobnie warto poczekać z MediaPlayer
, aby uniknąć konieczności ponownego tworzenia i przygotowywania.
Więcej informacji
Jetpack Media3 to zalecane rozwiązanie do odtwarzania multimediów w aplikacji. Więcej informacji
Na tych stronach znajdziesz informacje na temat nagrywania, przechowywania i odtwarzania dźwięku i obrazu: