Начало работы с CastPlayer

CastPlayer – это реализация Player в Jetpack Media3, которая поддерживает как локальное воспроизведение, так и трансляцию на удаленное устройство с поддержкой Cast. CastPlayer упрощает добавление функций трансляции в приложение и предоставляет широкие возможности для плавного переключения между локальным и удаленным воспроизведением. В этом руководстве рассказывается, как интегрировать CastPlayer в мультимедийное приложение.

Чтобы интегрировать Cast с другими платформами, ознакомьтесь с Cast SDK.

Приобретите устройство с поддержкой Cast

Чтобы протестировать CastPlayer, вам понадобится устройство с поддержкой Cast. например Android TV, Chromecast, умные колонки и умные дисплеи. Убедитесь, что устройство настроено и подключено к той же сети Wi-Fi, что и мобильное устройство, используемое для разработки.

Добавление зависимостей

Чтобы начать использовать CastPlayer, добавьте зависимости AndroidX Media3 и CastPlayer в файл build.gradle модуля приложения.

Kotlin

implementation("androidx.media3:media3-exoplayer:1.11.1")
implementation("androidx.media3:media3-ui:1.11.1")
implementation("androidx.media3:media3-session:1.11.1")
implementation("androidx.media3:media3-cast:1.11.1")

Яркий

implementation "androidx.media3:media3-exoplayer:1.11.1"
implementation "androidx.media3:media3-ui:1.11.1"
implementation "androidx.media3:media3-session:1.11.1"
implementation "androidx.media3:media3-cast:1.11.1"

Настройка CastPlayer

Чтобы настроить CastPlayer, добавьте в файл AndroidManifest.xml поставщика параметров.

Поставщик вариантов

Для настройки поведения CastPlayer требуется поставщик параметров. Для базовой настройки можно использовать DefaultCastOptionsProvider, добавив его в файл AndroidManifest.xml. При этом используются настройки по умолчанию, в том числе приложение-приемник по умолчанию.

<application>
  ...
  <meta-data
    android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
    android:value="androidx.media3.cast.DefaultCastOptionsProvider" />
  ...
</application>

Чтобы настроить конфигурацию, реализуйте собственный класс OptionsProvider. Подробные инструкции приведены в руководстве по CastOptions.

Как добавить устройство для передачи медиаконтента

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

<application>
  ...
  <receiver android:name="androidx.mediarouter.media.MediaTransferReceiver" />
  ...
</application>

Как создать CastPlayer

При удаленном воспроизведении с помощью Cast ваше приложение должно иметь возможность управлять воспроизведением, даже когда пользователь не взаимодействует с Activity из вашего приложения, например через системное уведомление о мультимедиа. Поэтому вам следует создать экземпляры ExoPlayer (для локального воспроизведения) и CastPlayer (для удаленного воспроизведения) в сервисе, например MediaSessionService или MediaLibraryService. Сначала создайте экземпляр ExoPlayer, а затем при создании экземпляра CastPlayer задайте ExoPlayer в качестве экземпляра локального проигрывателя. После этого вы сможете переключать воспроизведение медиаконтента между мобильным устройством и устройством с поддержкой Cast в уведомлении о медиаконтенте или уведомлении на заблокированном экране. Media3 использует функцию переключателя вывода для обработки передачи проигрывателя, когда маршрут вывода меняется с локального на удаленный или наоборот.

Скриншот интерфейса переключателя вывода в уведомлениях.
Рисунок 1. а) Чип устройства в уведомлении о медиаконтенте. б) Устройства с поддержкой Cast, которые показываются при нажатии на чип устройства. в) Чип устройства в уведомлении на заблокированном экране.

Kotlin

override fun onCreate() {
  super.onCreate()

  val exoPlayer = ExoPlayer.Builder(context).build()
  val castPlayer = CastPlayer.Builder(context).setLocalPlayer(exoPlayer).build()

  mediaSession = MediaSession.Builder(context, castPlayer).build()
}

Java

@Override
public void onCreate() {
  super.onCreate();

  ExoPlayer exoPlayer = new ExoPlayer.Builder(context).build();
  CastPlayer castPlayer = new CastPlayer.Builder(context).setLocalPlayer(exoPlayer).build();

  mediaSession =
      new MediaSession.Builder(/* context= */ context, /* player= */ castPlayer).build();
}

Добавьте элементы интерфейса

Добавьте MediaRouteButton в интерфейс приложения. При нажатии на значок MediaRouteButton открывается диалоговое окно со списком доступных устройств с поддержкой Cast в сети. Когда пользователь выбирает устройство, воспроизведение медиаконтента переносится с мобильного устройства на выбранное устройство-получатель. В этом разделе рассказывается, как добавить кнопку и прослушивать события, чтобы обновлять интерфейс при переключении воспроизведения между локальными и удаленными устройствами.

Настройка кнопки MediaRouteButton

Добавить MediaRouteButton в интерфейс можно четырьмя способами. Выбор зависит от дизайна и требований вашего приложения.

  • Compose UI. Добавьте composable-функцию кнопки.
  • Интерфейс представлений:
    • Добавьте кнопку в меню панели приложения.
    • Добавьте кнопку в тег PlayerView.
    • Добавьте кнопку в виде стандартного элемента View.
Скриншот, на котором показана кнопка MediaRouteButton в интерфейсе.
Рисунок 2. Кнопка MediaRouteButton в строке меню (а), в виде View (б), в PlayerView (в) и диалоговое окно устройств с поддержкой Cast (г).

Добавьте Composable MediaRouteButton в проигрыватель

Вы можете добавить MediaRouteButton composable-функцию в интерфейс проигрывателя. Дополнительную информацию можно найти в руководстве по Compose.

@Composable
fun PlayerComposeView(player: Player, modifier: Modifier = Modifier) {
  var controlsVisible by remember { mutableStateOf(false) }

  Box(
    modifier = modifier.clickable { controlsVisible = true },
    contentAlignment = Alignment.Center,
  ) {
    PlayerSurface(player = player, modifier = modifier)
    AnimatedVisibility(visible = controlsVisible, enter = fadeIn(), exit = fadeOut()) {
      Box(modifier = Modifier.fillMaxSize()) {
        MediaRouteButton(modifier = Modifier.align(Alignment.TopEnd))
        PrimaryControls(player = player, modifier = Modifier.align(Alignment.Center))
      }
    }
  }
}

@Composable
fun PrimaryControls(player: Player, modifier: Modifier = Modifier) {
  // ...
}

Добавьте MediaRouteButton в PlayerView

Вы можете добавить MediaRouteButton непосредственно в элементы управления пользовательского интерфейса PlayerView. После того как вы установите MediaController в качестве проигрывателя для PlayerView, укажите MediaRouteButtonViewProvider, чтобы на проигрывателе показывалась кнопка трансляции.

Kotlin

override fun onStart() {
  super.onStart()

  playerView.player = mediaController
  playerView.setMediaRouteButtonViewProvider(MediaRouteButtonViewProvider())
}

Java

@Override
public void onStart() {
  super.onStart();

  playerView.setPlayer(mediaController);
  playerView.setMediaRouteButtonViewProvider(new MediaRouteButtonViewProvider());
}

Добавьте MediaRouteButton в меню панели приложения.

Чтобы настроить MediaRouteButton в меню панели приложения, создайте меню XML и переопределите onCreateOptionsMenu в Activity.

<menu xmlns:android="http://schemas.android.com/apk/res/android"
         xmlns:app="http://schemas.android.com/apk/res-auto">
  <item android:id="@+id/media_route_menu_item"
    android:title="@string/media_route_menu_title"
    app:showAsAction="always"
    app:actionProviderClass="androidx.mediarouter.app.MediaRouteActionProvider"/>
</menu>

Kotlin

override fun onCreateOptionsMenu(menu: Menu): Boolean {
  // ...
  menuInflater.inflate(R.menu.sample_media_route_button_menu, menu)
  val menuItemFuture: ListenableFuture<MenuItem> =
    MediaRouteButtonFactory.setUpMediaRouteButton(context, menu, R.id.media_route_menu_item)
  Futures.addCallback(
    menuItemFuture,
    object : FutureCallback<MenuItem> {
      override fun onSuccess(menuItem: MenuItem) {
        // Do something with the menu item.
      }

      override fun onFailure(t: Throwable) {
        // Handle the failure.
      }
    },
    executor,
  )
  // ...
  return true
}

Java

@Override
public boolean onCreateOptionsMenu(Menu menu) {
  // ...
  getMenuInflater().inflate(R.menu.sample_media_route_button_menu, menu);
  ListenableFuture<MenuItem> menuItemFuture =
      MediaRouteButtonFactory.setUpMediaRouteButton(context, menu, R.id.media_route_menu_item);
  Futures.addCallback(
      menuItemFuture,
      new FutureCallback<MenuItem>() {
        @Override
        public void onSuccess(MenuItem menuItem) {
          // Do something with the menu item.
        }

        @Override
        public void onFailure(Throwable t) {
          // Handle the failure.
        }
      },
      executor);
  // ...
  return true;
}

Добавьте MediaRouteButton в качестве представления

Вы можете настроить MediaRouteButton в файле layout.xml.

  <androidx.mediarouter.app.MediaRouteButton
      android:id="@+id/media_route_button"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      app:mediaRouteButtonTint="@android:color/white" />

Чтобы завершить настройку MediaRouteButton, используйте Media3 Cast MediaRouteButtonFactory в коде Activity.

Kotlin

override fun onCreate(savedInstanceState: Bundle?) {
  super.onCreate(savedInstanceState)

  findViewById<MediaRouteButton>(R.id.media_route_button)?.also {
    val unused = MediaRouteButtonFactory.setUpMediaRouteButton(context, it)
  }
}

Java

@Override
public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  // ...
  MediaRouteButton button = findViewById(R.id.media_route_button);
  ListenableFuture<Void> setUpFuture =
      MediaRouteButtonFactory.setUpMediaRouteButton(context, button);
}

Прослушиватель действий

Создайте Player.Listener в Activity, чтобы отслеживать изменения места воспроизведения медиаконтента. Когда playbackType меняется между PLAYBACK_TYPE_LOCAL и PLAYBACK_TYPE_REMOTE, вы можете настроить интерфейс по своему усмотрению. Чтобы предотвратить утечки памяти и ограничить активность прослушивателя только тем временем, когда приложение видно, зарегистрируйте прослушиватель в onStart и отмените его регистрацию в onStop:

Kotlin

private val playerListener: Player.Listener =
  object : Player.Listener {
    override fun onDeviceInfoChanged(deviceInfo: DeviceInfo) {
      if (deviceInfo.playbackType == DeviceInfo.PLAYBACK_TYPE_LOCAL) {
        // Add UI changes for local playback.
      } else if (deviceInfo.playbackType == DeviceInfo.PLAYBACK_TYPE_REMOTE) {
        // Add UI changes for remote playback.
      }
    }
  }

override fun onStart() {
  super.onStart()
  mediaController.addListener(playerListener)
}

override fun onStop() {
  super.onStop()
  mediaController.removeListener(playerListener)
}

Java

private final Player.Listener playerListener =
    new Player.Listener() {
      @Override
      public void onDeviceInfoChanged(DeviceInfo deviceInfo) {
        if (deviceInfo.playbackType == DeviceInfo.PLAYBACK_TYPE_LOCAL) {
          // Add UI changes for local playback.
        } else if (deviceInfo.playbackType == DeviceInfo.PLAYBACK_TYPE_REMOTE) {
          // Add UI changes for remote playback.
        }
      }
    };

@Override
protected void onStart() {
  super.onStart();
  mediaController.addListener(playerListener);
}

@Override
protected void onStop() {
  super.onStop();
  mediaController.removeListener(playerListener);
}

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