Воспроизведение мультимедиа в фоновом режиме

Вы можете воспроизводить мультимедиа в фоновом режиме, даже если ваше приложение не отображается на экране, например, когда пользователь взаимодействует с другими приложениями.

Для этого вы встраиваете MediaPlayer в службу MediaBrowserServiceCompat и заставляете его взаимодействовать с MediaBrowserCompat в другом действии.

Будьте осторожны при реализации этой настройки клиента и сервера. Существуют определенные ожидания относительно того, как игрок, работающий в фоновом режиме, взаимодействует с остальной частью системы. Если ваше приложение не соответствует этим ожиданиям, у пользователя может возникнуть неприятный опыт. Подробности см. в разделе Создание аудиоприложения .

На этой странице описаны специальные инструкции по управлению MediaPlayer при его реализации внутри службы.

Запускать асинхронно

Как и Activity , вся работа в Service по умолчанию выполняется в одном потоке. Фактически, когда вы запускаете действие и службу из одного и того же приложения, они по умолчанию используют один и тот же поток («основной поток»).

Службы должны быстро обрабатывать входящие намерения и никогда не выполнять длительные вычисления при ответе на них. Вы должны выполнять любую тяжелую работу или блокировать вызовы асинхронно: либо из другого потока, который вы реализуете самостоятельно, либо используя множество средств платформы для асинхронной обработки.

Например, когда вы используете MediaPlayer из основного потока, вам следует:

  • Вызовите prepareAsync() вместо prepare() и
  • Реализуйте MediaPlayer.OnPreparedListener , чтобы получать уведомления, когда подготовка будет завершена и вы сможете начать играть.

Например:

Котлин

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

Ява

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 :

Котлин

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!
    }
}

Ява

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 .

Используйте блокировки пробуждения

Когда вы воспроизводите или передаете музыку в фоновом режиме, вы должны использовать блокировку пробуждения, чтобы система не мешала вашему воспроизведению, например, переводя устройство в спящий режим.

Блокировка пробуждения — это сигнал системе о том, что ваше приложение использует функции, которые должны оставаться доступными, даже когда телефон находится в режиме ожидания.

Чтобы гарантировать, что процессор продолжает работать во время воспроизведения вашего MediaPlayer , вызовите метод setWakeMode() при инициализации вашего MediaPlayer . MediaPlayer удерживает указанную блокировку во время воспроизведения и снимает блокировку при приостановке или остановке:

Котлин

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

Ява

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

Однако блокировка пробуждения, полученная в этом примере, гарантирует только то, что ЦП остается в бодрствующем состоянии. Если вы осуществляете потоковую передачу мультимедиа по сети и используете Wi-Fi, вам, вероятно, также понадобится иметь WifiLock , который вам придется получить и отключить вручную. Итак, когда вы начинаете подготовку MediaPlayer с удаленным URL-адресом, вам следует создать и получить блокировку Wi-Fi.

Например:

Котлин

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

wifiLock.acquire()

Ява

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

wifiLock.acquire();

Когда вы приостанавливаете или останавливаете воспроизведение мультимедиа или когда сеть вам больше не нужна, вам следует снять блокировку:

Котлин

wifiLock.release()

Ява

wifiLock.release();

Выполнить очистку

Как упоминалось ранее, объект MediaPlayer может потреблять значительный объем системных ресурсов, поэтому вам следует хранить его столько, сколько вам нужно, и вызывать release() когда вы закончите с ним. Важно вызывать этот метод очистки явно, а не полагаться на системную сборку мусора, поскольку сборщику мусора может потребоваться некоторое время, чтобы вернуть MediaPlayer , поскольку он чувствителен только к потребностям памяти, а не к нехватке других ресурсов, связанных с мультимедиа. Итак, в случае использования службы вам всегда следует переопределить метод onDestroy() , чтобы убедиться, что вы освобождаете MediaPlayer :

Котлин

class MyService : Service() {

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

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

Ява

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

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

Вам всегда следует искать другие возможности для освобождения вашего MediaPlayer , помимо его выпуска при выключении. Например, если вы ожидаете, что не сможете воспроизводить мультимедиа в течение длительного периода времени (например, после потери фокуса звука), вам обязательно следует освободить существующий MediaPlayer и создать его снова позже. С другой стороны, если вы планируете остановить воспроизведение только на очень короткое время, вам, вероятно, следует сохранить свой MediaPlayer , чтобы избежать накладных расходов на его повторное создание и подготовку.

Узнать больше

Jetpack Media3 — рекомендуемое решение для воспроизведения мультимедиа в вашем приложении. Узнайте больше об этом.

На этих страницах рассматриваются темы, связанные с записью, хранением и воспроизведением аудио и видео: