В этом руководстве разработчика описывается, как добавить поддержку Google Cast в приложение Android Sender с помощью Android Sender SDK.
Мобильное устройство или ноутбук является отправителем , который управляет воспроизведением, а устройство Google Cast — приемником , который отображает контент на телевизоре.
Фреймворк отправителя относится к двоичному файлу библиотеки классов Cast и связанным с ним ресурсам, присутствующим во время выполнения на отправителе. Приложение отправителя (или приложение Cast) относится к приложению, также работающему на отправителе. Приложение Web Receiver относится к HTML-приложению, работающему на устройстве с поддержкой Cast.
Платформа отправителя использует асинхронную конструкцию обратного вызова для информирования приложения-отправителя о событиях и для перехода между различными состояниями жизненного цикла приложения Cast.
Поток приложения
Следующие шаги описывают типичный поток выполнения высокого уровня для приложения-отправителя Android:
- Платформа Cast автоматически запускает обнаружение устройства
MediaRouter
на основе жизненного циклаActivity
. - Когда пользователь нажимает кнопку Cast, фреймворк отображает диалоговое окно Cast со списком обнаруженных устройств Cast.
- Когда пользователь выбирает устройство Cast, фреймворк пытается запустить приложение Web Receiver на устройстве Cast.
- Фреймворк вызывает обратные вызовы в приложении-отправителе, чтобы подтвердить запуск приложения Web Receiver.
- Фреймворк создает канал связи между отправителем и приложениями Web Receiver.
- Фреймворк использует канал связи для загрузки и управления воспроизведением мультимедиа на веб-приемнике.
- Фреймворк синхронизирует состояние воспроизведения мультимедиа между отправителем и веб-приемником: когда пользователь выполняет действия в пользовательском интерфейсе отправителя, фреймворк передает эти запросы на управление мультимедиа веб-приемнику, а когда веб-приемник отправляет обновления статуса мультимедиа, фреймворк обновляет состояние пользовательского интерфейса отправителя.
- Когда пользователь нажимает кнопку Cast, чтобы отключиться от устройства Cast, фреймворк отключает приложение-отправитель от веб-приемника.
Полный список всех классов, методов и событий в Google Cast Android SDK см. в Справочнике API Google Cast Sender для Android . В следующих разделах описываются шаги по добавлению Cast в ваше приложение для Android.
Настройте манифест Android
В файле AndroidManifest.xml вашего приложения необходимо настроить следующие элементы для Cast SDK:
использует SDK
Установите минимальный и целевой уровни API Android, поддерживаемые Cast SDK. В настоящее время минимальный уровень API — 23, целевой — 34.
<uses-sdk
android:minSdkVersion="23"
android:targetSdkVersion="34" />
андроид:тема
Настройте тему приложения на основе минимальной версии Android SDK. Например, если вы не реализуете собственную тему, вам следует использовать вариант Theme.AppCompat
, если минимальная версия Android SDK ниже Lollipop.
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/Theme.AppCompat" >
...
</application>
Инициализация контекста приведения
В фреймворке имеется глобальный одиночный объект CastContext
, который координирует все взаимодействия фреймворка.
Ваше приложение должно реализовать интерфейс OptionsProvider
для предоставления параметров, необходимых для инициализации синглтона CastContext
. OptionsProvider
предоставляет экземпляр CastOptions
, содержащий параметры, влияющие на поведение фреймворка. Наиболее важным из них является идентификатор приложения Web Receiver, который используется для фильтрации результатов обнаружения и запуска приложения Web Receiver при начале сеанса Cast.
class CastOptionsProvider : OptionsProvider { override fun getCastOptions(context: Context): CastOptions { return Builder() .setReceiverApplicationId(context.getString(R.string.app_id)) .build() } override fun getAdditionalSessionProviders(context: Context): List<SessionProvider>? { return null } }
public class CastOptionsProvider implements OptionsProvider { @Override public CastOptions getCastOptions(Context context) { CastOptions castOptions = new CastOptions.Builder() .setReceiverApplicationId(context.getString(R.string.app_id)) .build(); return castOptions; } @Override public List<SessionProvider> getAdditionalSessionProviders(Context context) { return null; } }
Необходимо объявить полное имя реализованного OptionsProvider
как поле метаданных в файле AndroidManifest.xml приложения-отправителя:
<application>
...
<meta-data
android:name=
"com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
android:value="com.foo.CastOptionsProvider" />
</application>
CastContext
лениво инициализируется при вызове CastContext.getSharedInstance()
.
class MyActivity : FragmentActivity() { override fun onCreate(savedInstanceState: Bundle?) { val castContext = CastContext.getSharedInstance(this) } }
public class MyActivity extends FragmentActivity { @Override public void onCreate(Bundle savedInstanceState) { CastContext castContext = CastContext.getSharedInstance(this); } }
Виджеты Cast UX
Фреймворк Cast предоставляет виджеты, которые соответствуют контрольному списку проектирования Cast:
Вводный оверлей : фреймворк предоставляет настраиваемый вид
IntroductoryOverlay
, который отображается пользователю для привлечения внимания к кнопке трансляции при первом появлении получателя. Приложение Sender может настраивать текст и положение заголовка .Кнопка «Cast» : кнопка «Cast» отображается независимо от наличия устройств Cast. При первом нажатии кнопки «Cast» открывается диалоговое окно со списком обнаруженных устройств. При нажатии кнопки «Cast» при подключенном устройстве отображаются текущие метаданные медиафайла (например, название, название студии звукозаписи и миниатюра изображения) или пользователь может отключиться от устройства Cast. «Кнопку Cast» иногда называют «значком Cast».
Мини-контроллер : когда пользователь транслирует контент и переходит со страницы текущего контента или расширенного контроллера на другой экран в приложении-отправителе, в нижней части экрана отображается мини-контроллер, позволяющий пользователю видеть метаданные текущего транслируемого медиаконтента и управлять воспроизведением.
Расширенный контроллер : если пользователь транслирует контент, то при нажатии на уведомление мультимедиа или мини-контроллер запускается расширенный контроллер, который отображает метаданные воспроизводимого в данный момент мультимедиа и предоставляет несколько кнопок для управления воспроизведением мультимедиа.
Уведомление : только для Android. Когда пользователь транслирует контент и выходит из приложения-отправителя, отображается уведомление о медиафайле, содержащее метаданные текущего транслируемого медиафайла и элементы управления воспроизведением.
Экран блокировки : только для Android. Когда пользователь транслирует контент и переходит на экран блокировки (или устройство отключается по истечении времени ожидания), отображается элемент управления экраном блокировки мультимедиа, на котором отображаются метаданные текущего транслируемого медиаконтента и элементы управления воспроизведением.
Следующее руководство содержит описания того, как добавить эти виджеты в ваше приложение.
Добавить кнопку трансляции
API-интерфейсы Android MediaRouter
предназначены для отображения и воспроизведения медиаконтента на дополнительных устройствах. Приложения Android, использующие API MediaRouter
, должны включать кнопку Cast в свой пользовательский интерфейс, чтобы пользователи могли выбирать маршрут воспроизведения медиаконтента на дополнительном устройстве, например, устройстве Cast.
Фреймворк значительно упрощает добавление MediaRouteButton
в качестве Cast button
. Сначала необходимо добавить пункт меню или MediaRouteButton
в XML-файл, определяющий ваше меню, а затем использовать CastButtonFactory
для его подключения к фреймворку.
// To add a Cast button, add the following snippet.
// menu.xml
<item
android:id="@+id/media_route_menu_item"
android:title="@string/media_route_menu_title"
app:actionProviderClass="androidx.mediarouter.app.MediaRouteActionProvider"
app:showAsAction="always" />
// Then override the onCreateOptionMenu() for each of your activities. // MyActivity.kt override fun onCreateOptionsMenu(menu: Menu): Boolean { super.onCreateOptionsMenu(menu) menuInflater.inflate(R.menu.main, menu) CastButtonFactory.setUpMediaRouteButton( applicationContext, menu, R.id.media_route_menu_item ) return true }
// Then override the onCreateOptionMenu() for each of your activities. // MyActivity.java @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); getMenuInflater().inflate(R.menu.main, menu); CastButtonFactory.setUpMediaRouteButton(getApplicationContext(), menu, R.id.media_route_menu_item); return true; }
Затем, если ваша Activity
наследует от FragmentActivity
, вы можете добавить MediaRouteButton
в свой макет.
// activity_layout.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal" >
<androidx.mediarouter.app.MediaRouteButton
android:id="@+id/media_route_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:mediaRouteTypes="user"
android:visibility="gone" />
</LinearLayout>
// MyActivity.kt override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_layout) mMediaRouteButton = findViewById<View>(R.id.media_route_button) as MediaRouteButton CastButtonFactory.setUpMediaRouteButton(applicationContext, mMediaRouteButton) mCastContext = CastContext.getSharedInstance(this) }
// MyActivity.java @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_layout); mMediaRouteButton = (MediaRouteButton) findViewById(R.id.media_route_button); CastButtonFactory.setUpMediaRouteButton(getApplicationContext(), mMediaRouteButton); mCastContext = CastContext.getSharedInstance(this); }
Чтобы настроить внешний вид кнопки Cast с помощью темы, см. раздел Настройка кнопки Cast .
Настроить обнаружение устройств
Обнаружение устройств полностью управляется CastContext
. При инициализации CastContext приложение-отправитель указывает идентификатор приложения Web Receiver и может при необходимости запросить фильтрацию пространств имён, установив параметр supportedNamespaces
в CastOptions
. CastContext
содержит внутреннюю ссылку на MediaRouter
и запускает процесс обнаружения при следующих условиях:
- На основе алгоритма, разработанного для балансировки задержки обнаружения устройства и использования батареи, обнаружение иногда будет запускаться автоматически, когда приложение-отправитель переходит на передний план.
- Открыто диалоговое окно трансляции.
- Cast SDK пытается восстановить сеанс Cast.
Процесс обнаружения будет остановлен, когда диалоговое окно Cast будет закрыто или приложение-отправитель перейдет в фоновый режим.
class CastOptionsProvider : OptionsProvider { companion object { const val CUSTOM_NAMESPACE = "urn:x-cast:custom_namespace" } override fun getCastOptions(appContext: Context): CastOptions { val supportedNamespaces: MutableList<String> = ArrayList() supportedNamespaces.add(CUSTOM_NAMESPACE) return CastOptions.Builder() .setReceiverApplicationId(context.getString(R.string.app_id)) .setSupportedNamespaces(supportedNamespaces) .build() } override fun getAdditionalSessionProviders(context: Context): List<SessionProvider>? { return null } }
class CastOptionsProvider implements OptionsProvider { public static final String CUSTOM_NAMESPACE = "urn:x-cast:custom_namespace"; @Override public CastOptions getCastOptions(Context appContext) { List<String> supportedNamespaces = new ArrayList<>(); supportedNamespaces.add(CUSTOM_NAMESPACE); CastOptions castOptions = new CastOptions.Builder() .setReceiverApplicationId(context.getString(R.string.app_id)) .setSupportedNamespaces(supportedNamespaces) .build(); return castOptions; } @Override public List<SessionProvider> getAdditionalSessionProviders(Context context) { return null; } }
Как работает управление сеансами
В Cast SDK представлена концепция сеанса Cast, установление которого включает в себя следующие этапы: подключение к устройству, запуск (или присоединение) приложения Web Receiver, подключение к этому приложению и инициализация канала управления медиаконтентом. Подробнее о сеансах Cast и жизненном цикле Web Receiver см. в руководстве по жизненному циклу приложения Web Receiver.
Сеансами управляет класс SessionManager
, к которому ваше приложение может получить доступ через CastContext.getSessionManager()
. Отдельные сеансы представлены подклассами класса Session
. Например, CastSession
представляет сеансы с устройствами Cast. Ваше приложение может получить доступ к текущему активному сеансу Cast через SessionManager.getCurrentCastSession()
.
Ваше приложение может использовать класс SessionManagerListener
для отслеживания событий сеанса, таких как создание, приостановка, возобновление и завершение. Фреймворк автоматически пытается возобновить сеанс после его аварийного/внезапного завершения, пока он был активен.
Сеансы создаются и завершаются автоматически в ответ на жесты пользователя в диалоговых окнах MediaRouter
.
Чтобы лучше понимать ошибки запуска Cast, приложения могут использовать CastContext#getCastReasonCodeForCastStatusCode(int)
для преобразования ошибки запуска сеанса в CastReasonCodes
. Обратите внимание, что некоторые ошибки запуска сеанса (например, CastReasonCodes#CAST_CANCELLED
) являются нормальным поведением и не должны регистрироваться как ошибки.
Если вам необходимо отслеживать изменения состояния сеанса, вы можете реализовать SessionManagerListener
. В этом примере прослушивается доступность CastSession
в Activity
.
class MyActivity : Activity() { private var mCastSession: CastSession? = null private lateinit var mCastContext: CastContext private lateinit var mSessionManager: SessionManager private val mSessionManagerListener: SessionManagerListener<CastSession> = SessionManagerListenerImpl() private inner class SessionManagerListenerImpl : SessionManagerListener<CastSession?> { override fun onSessionStarting(session: CastSession?) {} override fun onSessionStarted(session: CastSession?, sessionId: String) { invalidateOptionsMenu() } override fun onSessionStartFailed(session: CastSession?, error: Int) { val castReasonCode = mCastContext.getCastReasonCodeForCastStatusCode(error) // Handle error } override fun onSessionSuspended(session: CastSession?, reason Int) {} override fun onSessionResuming(session: CastSession?, sessionId: String) {} override fun onSessionResumed(session: CastSession?, wasSuspended: Boolean) { invalidateOptionsMenu() } override fun onSessionResumeFailed(session: CastSession?, error: Int) {} override fun onSessionEnding(session: CastSession?) {} override fun onSessionEnded(session: CastSession?, error: Int) { finish() } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) mCastContext = CastContext.getSharedInstance(this) mSessionManager = mCastContext.sessionManager mSessionManager.addSessionManagerListener(mSessionManagerListener, CastSession::class.java) } override fun onResume() { super.onResume() mCastSession = mSessionManager.currentCastSession } override fun onDestroy() { super.onDestroy() mSessionManager.removeSessionManagerListener(mSessionManagerListener, CastSession::class.java) } }
public class MyActivity extends Activity { private CastContext mCastContext; private CastSession mCastSession; private SessionManager mSessionManager; private SessionManagerListener<CastSession> mSessionManagerListener = new SessionManagerListenerImpl(); private class SessionManagerListenerImpl implements SessionManagerListener<CastSession> { @Override public void onSessionStarting(CastSession session) {} @Override public void onSessionStarted(CastSession session, String sessionId) { invalidateOptionsMenu(); } @Override public void onSessionStartFailed(CastSession session, int error) { int castReasonCode = mCastContext.getCastReasonCodeForCastStatusCode(error); // Handle error } @Override public void onSessionSuspended(CastSession session, int reason) {} @Override public void onSessionResuming(CastSession session, String sessionId) {} @Override public void onSessionResumed(CastSession session, boolean wasSuspended) { invalidateOptionsMenu(); } @Override public void onSessionResumeFailed(CastSession session, int error) {} @Override public void onSessionEnding(CastSession session) {} @Override public void onSessionEnded(CastSession session, int error) { finish(); } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mCastContext = CastContext.getSharedInstance(this); mSessionManager = mCastContext.getSessionManager(); mSessionManager.addSessionManagerListener(mSessionManagerListener, CastSession.class); } @Override protected void onResume() { super.onResume(); mCastSession = mSessionManager.getCurrentCastSession(); } @Override protected void onDestroy() { super.onDestroy(); mSessionManager.removeSessionManagerListener(mSessionManagerListener, CastSession.class); } }
Потоковая передача
Сохранение состояния сеанса — основа потоковой передачи, при которой пользователи могут перемещать существующие аудио- и видеопотоки между устройствами с помощью голосовых команд, приложения Google Home или умных дисплеев. Воспроизведение медиафайлов останавливается на одном устройстве (источнике) и продолжается на другом (приемнике). Любое устройство Cast с последней версией прошивки может служить источником или приемником потоковой передачи.
Чтобы получить новое целевое устройство во время передачи или расширения потока, зарегистрируйте Cast.Listener
с помощью метода CastSession#addCastListener
. Затем вызовите CastSession#getCastDevice()
во время обратного вызова onDeviceNameChanged
.
Дополнительную информацию см. в разделе Передача потока на веб-приемнике .
Автоматическое переподключение
Фреймворк предоставляет ReconnectionService
, который может быть включен приложением-отправителем для обработки повторного подключения во многих сложных случаях, например:
- Восстановление после временной потери Wi-Fi
- Выход устройства из спящего режима
- Восстановление после перевода приложения в фоновый режим
- Восстановление в случае сбоя приложения
Эта служба включена по умолчанию и может быть отключена в CastOptions.Builder
.
Эту службу можно автоматически объединить в манифест вашего приложения, если в вашем Gradle-файле включено автоматическое объединение.
Фреймворк запустит службу во время сеанса мультимедиа и остановит ее по завершении сеанса мультимедиа.
Как работает Media Control
Фреймворк Cast отказывается от использования класса RemoteMediaPlayer
из Cast 2.x в пользу нового класса RemoteMediaClient
, который предоставляет ту же функциональность в наборе более удобных API и позволяет избежать необходимости передавать GoogleApiClient.
Когда ваше приложение устанавливает CastSession
с приложением Web Receiver, которое поддерживает пространство имен мультимедиа, фреймворк автоматически создает экземпляр RemoteMediaClient
; ваше приложение может получить к нему доступ, вызвав метод getRemoteMediaClient()
для экземпляра CastSession
.
Все методы RemoteMediaClient
, отправляющие запросы к веб-приемнику, возвращают объект PendingResult, который можно использовать для отслеживания этого запроса.
Предполагается, что экземпляр RemoteMediaClient
может совместно использоваться несколькими частями вашего приложения, а также некоторыми внутренними компонентами фреймворка, такими как постоянные мини-контроллеры и служба уведомлений . Для этого данный экземпляр поддерживает регистрацию нескольких экземпляров RemoteMediaClient.Listener
.
Установить метаданные медиа
Класс MediaMetadata
содержит информацию о медиафайле, который вы хотите транслировать. В следующем примере создаётся новый экземпляр MediaMetadata фильма и задаётся название, подзаголовок и два изображения.
val movieMetadata = MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE) movieMetadata.putString(MediaMetadata.KEY_TITLE, mSelectedMedia.getTitle()) movieMetadata.putString(MediaMetadata.KEY_SUBTITLE, mSelectedMedia.getStudio()) movieMetadata.addImage(WebImage(Uri.parse(mSelectedMedia.getImage(0)))) movieMetadata.addImage(WebImage(Uri.parse(mSelectedMedia.getImage(1))))
MediaMetadata movieMetadata = new MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE); movieMetadata.putString(MediaMetadata.KEY_TITLE, mSelectedMedia.getTitle()); movieMetadata.putString(MediaMetadata.KEY_SUBTITLE, mSelectedMedia.getStudio()); movieMetadata.addImage(new WebImage(Uri.parse(mSelectedMedia.getImage(0)))); movieMetadata.addImage(new WebImage(Uri.parse(mSelectedMedia.getImage(1))));
Информацию об использовании изображений с метаданными мультимедиа см. в разделе Выбор изображений .
Загрузить медиа
Ваше приложение может загружать медиа-элемент, как показано в следующем коде. Сначала используйте MediaInfo.Builder
с метаданными медиа-файла для создания экземпляра MediaInfo
. Получите RemoteMediaClient
из текущего CastSession
, затем загрузите MediaInfo
в этот RemoteMediaClient
. Используйте RemoteMediaClient
для воспроизведения, приостановки и других действий с приложением медиаплеера, работающим на веб-приемнике.
val mediaInfo = MediaInfo.Builder(mSelectedMedia.getUrl()) .setStreamType(MediaInfo.STREAM_TYPE_BUFFERED) .setContentType("videos/mp4") .setMetadata(movieMetadata) .setStreamDuration(mSelectedMedia.getDuration() * 1000) .build() val remoteMediaClient = mCastSession.getRemoteMediaClient() remoteMediaClient.load(MediaLoadRequestData.Builder().setMediaInfo(mediaInfo).build())
MediaInfo mediaInfo = new MediaInfo.Builder(mSelectedMedia.getUrl()) .setStreamType(MediaInfo.STREAM_TYPE_BUFFERED) .setContentType("videos/mp4") .setMetadata(movieMetadata) .setStreamDuration(mSelectedMedia.getDuration() * 1000) .build(); RemoteMediaClient remoteMediaClient = mCastSession.getRemoteMediaClient(); remoteMediaClient.load(new MediaLoadRequestData.Builder().setMediaInfo(mediaInfo).build());
См. также раздел об использовании медиа-треков .
Формат видео 4K
Чтобы проверить видеоформат медиаконтента, используйте getVideoInfo()
в MediaStatus для получения текущего экземпляра VideoInfo
. Этот экземпляр содержит тип формата HDR TV, а также высоту и ширину экрана в пикселях. Варианты формата 4K обозначаются константами HDR_TYPE_*
.
Уведомления удаленного управления на несколько устройств
Когда пользователь транслирует контент, другие устройства Android в той же сети получат уведомление с предложением управлять воспроизведением. Любой, чьё устройство получает такие уведомления, может отключить их в приложении «Настройки»: Google > Google Cast > Показывать уведомления о дистанционном управлении . (В уведомлениях есть ярлык приложения «Настройки».) Подробнее см. в разделе «Уведомления о дистанционном управлении Cast» .
Добавить мини-контроллер
Согласно контрольному списку дизайна Cast , приложение-отправитель должно предоставлять постоянный элемент управления, известный как мини-контроллер , который должен появляться при переходе пользователя с текущей страницы контента в другую часть приложения-отправителя. Мини-контроллер визуально напоминает пользователю о текущем сеансе Cast. Нажав на мини-контроллер, пользователь может вернуться к полноэкранному расширенному виду контроллера Cast.
Фреймворк предоставляет настраиваемое представление MiniControllerFragment, которое можно добавить в конец файла макета каждой активности, в которой вы хотите отобразить мини-контроллер.
<fragment
android:id="@+id/castMiniController"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:visibility="gone"
class="com.google.android.gms.cast.framework.media.widget.MiniControllerFragment" />
Когда приложение-отправитель воспроизводит потоковое видео или аудио, SDK автоматически отображает кнопку воспроизведения/остановки вместо кнопки воспроизведения/паузы на мини-контроллере.
Чтобы настроить внешний вид текста заголовка и подзаголовка этого пользовательского представления, а также выбрать кнопки, см. раздел Настройка мини-контроллера .
Добавить расширенный контроллер
Контрольный список дизайна Google Cast требует, чтобы приложение-отправитель предоставляло расширенный контроллер для транслируемого медиаконтента. Расширенный контроллер представляет собой полноэкранную версию мини-контроллера.
Cast SDK предоставляет виджет для расширенного контроллера под названием ExpandedControllerActivity
. Это абстрактный класс, который необходимо подклассифицировать для добавления кнопки Cast.
Сначала создайте новый файл ресурсов меню для расширенного контроллера, чтобы предоставить кнопку Cast:
<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:actionProviderClass="androidx.mediarouter.app.MediaRouteActionProvider"
app:showAsAction="always"/>
</menu>
Создайте новый класс, расширяющий ExpandedControllerActivity
.
class ExpandedControlsActivity : ExpandedControllerActivity() { override fun onCreateOptionsMenu(menu: Menu): Boolean { super.onCreateOptionsMenu(menu) menuInflater.inflate(R.menu.expanded_controller, menu) CastButtonFactory.setUpMediaRouteButton(this, menu, R.id.media_route_menu_item) return true } }
public class ExpandedControlsActivity extends ExpandedControllerActivity { @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); getMenuInflater().inflate(R.menu.expanded_controller, menu); CastButtonFactory.setUpMediaRouteButton(this, menu, R.id.media_route_menu_item); return true; } }
Теперь объявите новую активность в манифесте приложения внутри тега application
:
<application>
...
<activity
android:name=".expandedcontrols.ExpandedControlsActivity"
android:label="@string/app_name"
android:launchMode="singleTask"
android:theme="@style/Theme.CastVideosDark"
android:screenOrientation="portrait"
android:parentActivityName="com.google.sample.cast.refplayer.VideoBrowserActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
</intent-filter>
</activity>
...
</application>
Отредактируйте CastOptionsProvider
и измените NotificationOptions
и CastMediaOptions
, чтобы задать целевую активность для вашей новой активности:
override fun getCastOptions(context: Context): CastOptions? { val notificationOptions = NotificationOptions.Builder() .setTargetActivityClassName(ExpandedControlsActivity::class.java.name) .build() val mediaOptions = CastMediaOptions.Builder() .setNotificationOptions(notificationOptions) .setExpandedControllerActivityClassName(ExpandedControlsActivity::class.java.name) .build() return CastOptions.Builder() .setReceiverApplicationId(context.getString(R.string.app_id)) .setCastMediaOptions(mediaOptions) .build() }
public CastOptions getCastOptions(Context context) { NotificationOptions notificationOptions = new NotificationOptions.Builder() .setTargetActivityClassName(ExpandedControlsActivity.class.getName()) .build(); CastMediaOptions mediaOptions = new CastMediaOptions.Builder() .setNotificationOptions(notificationOptions) .setExpandedControllerActivityClassName(ExpandedControlsActivity.class.getName()) .build(); return new CastOptions.Builder() .setReceiverApplicationId(context.getString(R.string.app_id)) .setCastMediaOptions(mediaOptions) .build(); }
Обновите метод LocalPlayerActivity
loadRemoteMedia
, чтобы отображать новую активность при загрузке удаленного мультимедиа:
private fun loadRemoteMedia(position: Int, autoPlay: Boolean) { val remoteMediaClient = mCastSession?.remoteMediaClient ?: return remoteMediaClient.registerCallback(object : RemoteMediaClient.Callback() { override fun onStatusUpdated() { val intent = Intent(this@LocalPlayerActivity, ExpandedControlsActivity::class.java) startActivity(intent) remoteMediaClient.unregisterCallback(this) } }) remoteMediaClient.load( MediaLoadRequestData.Builder() .setMediaInfo(mSelectedMedia) .setAutoplay(autoPlay) .setCurrentTime(position.toLong()).build() ) }
private void loadRemoteMedia(int position, boolean autoPlay) { if (mCastSession == null) { return; } final RemoteMediaClient remoteMediaClient = mCastSession.getRemoteMediaClient(); if (remoteMediaClient == null) { return; } remoteMediaClient.registerCallback(new RemoteMediaClient.Callback() { @Override public void onStatusUpdated() { Intent intent = new Intent(LocalPlayerActivity.this, ExpandedControlsActivity.class); startActivity(intent); remoteMediaClient.unregisterCallback(this); } }); remoteMediaClient.load(new MediaLoadRequestData.Builder() .setMediaInfo(mSelectedMedia) .setAutoplay(autoPlay) .setCurrentTime(position).build()); }
Когда приложение-отправитель воспроизводит потоковое видео или аудио, SDK автоматически отображает кнопку воспроизведения/остановки вместо кнопки воспроизведения/паузы в расширенном контроллере.
Чтобы настроить внешний вид с помощью тем, выбрать отображаемые кнопки и добавить пользовательские кнопки, см. раздел Настройка расширенного контроллера .
Регулятор громкости
Фреймворк автоматически управляет громкостью приложения-отправителя. Фреймворк автоматически синхронизирует приложения-отправителя и веб-приёмника, чтобы пользовательский интерфейс отправителя всегда отображал громкость, заданную веб-приёмником.
Физическая кнопка регулировки громкости
На устройствах Android физические кнопки на устройстве-отправителе могут использоваться для изменения громкости сеанса трансляции на веб-приемнике по умолчанию для любого устройства на базе Jelly Bean или более новой версии.
Физическая кнопка регулировки громкости до Jelly Bean
Чтобы использовать физические клавиши регулировки громкости для управления громкостью устройства Web Receiver на устройствах Android старше Jelly Bean, приложение-отправитель должно переопределить dispatchKeyEvent
в своих действиях и вызвать CastContext.onDispatchVolumeKeyEventBeforeJellyBean()
:
class MyActivity : FragmentActivity() { override fun dispatchKeyEvent(event: KeyEvent): Boolean { return (CastContext.getSharedInstance(this) .onDispatchVolumeKeyEventBeforeJellyBean(event) || super.dispatchKeyEvent(event)) } }
class MyActivity extends FragmentActivity { @Override public boolean dispatchKeyEvent(KeyEvent event) { return CastContext.getSharedInstance(this) .onDispatchVolumeKeyEventBeforeJellyBean(event) || super.dispatchKeyEvent(event); } }
Добавить элементы управления мультимедиа на экран уведомлений и блокировки
Только для Android, согласно контрольному списку дизайна Google Cast, приложение-отправитель должно реализовать элементы управления мультимедиа в уведомлении и на экране блокировки , когда отправитель транслирует контент, но приложение-отправитель не находится в фокусе. Фреймворк предоставляет MediaNotificationService
и MediaIntentReceiver
, чтобы помочь приложению-отправителю создавать элементы управления мультимедиа в уведомлении и на экране блокировки.
MediaNotificationService
запускается, когда отправитель осуществляет трансляцию, и отображает уведомление с миниатюрой изображения и информацией о текущем элементе трансляции, кнопкой воспроизведения/паузы и кнопкой остановки.
MediaIntentReceiver
— это BroadcastReceiver
, который обрабатывает действия пользователя из уведомления.
Ваше приложение может настроить уведомления и управление мультимедиа с экрана блокировки с помощью NotificationOptions
. Оно может указать, какие кнопки управления будут отображаться в уведомлении, и какое Activity
будет открываться при нажатии на уведомление пользователем. Если действия не указаны явно, будут использоваться значения по умолчанию: MediaIntentReceiver.ACTION_TOGGLE_PLAYBACK
и MediaIntentReceiver.ACTION_STOP_CASTING
.
// Example showing 4 buttons: "rewind", "play/pause", "forward" and "stop casting". val buttonActions: MutableList<String> = ArrayList() buttonActions.add(MediaIntentReceiver.ACTION_REWIND) buttonActions.add(MediaIntentReceiver.ACTION_TOGGLE_PLAYBACK) buttonActions.add(MediaIntentReceiver.ACTION_FORWARD) buttonActions.add(MediaIntentReceiver.ACTION_STOP_CASTING) // Showing "play/pause" and "stop casting" in the compat view of the notification. val compatButtonActionsIndices = intArrayOf(1, 3) // Builds a notification with the above actions. Each tap on the "rewind" and "forward" buttons skips 30 seconds. // Tapping on the notification opens an Activity with class VideoBrowserActivity. val notificationOptions = NotificationOptions.Builder() .setActions(buttonActions, compatButtonActionsIndices) .setSkipStepMs(30 * DateUtils.SECOND_IN_MILLIS) .setTargetActivityClassName(VideoBrowserActivity::class.java.name) .build()
// Example showing 4 buttons: "rewind", "play/pause", "forward" and "stop casting". List<String> buttonActions = new ArrayList<>(); buttonActions.add(MediaIntentReceiver.ACTION_REWIND); buttonActions.add(MediaIntentReceiver.ACTION_TOGGLE_PLAYBACK); buttonActions.add(MediaIntentReceiver.ACTION_FORWARD); buttonActions.add(MediaIntentReceiver.ACTION_STOP_CASTING); // Showing "play/pause" and "stop casting" in the compat view of the notification. int[] compatButtonActionsIndices = new int[]{1, 3}; // Builds a notification with the above actions. Each tap on the "rewind" and "forward" buttons skips 30 seconds. // Tapping on the notification opens an Activity with class VideoBrowserActivity. NotificationOptions notificationOptions = new NotificationOptions.Builder() .setActions(buttonActions, compatButtonActionsIndices) .setSkipStepMs(30 * DateUtils.SECOND_IN_MILLIS) .setTargetActivityClassName(VideoBrowserActivity.class.getName()) .build();
Отображение элементов управления мультимедиа на экранах уведомлений и блокировки включено по умолчанию и может быть отключено вызовом setNotificationOptions
со значением null в CastMediaOptions.Builder
. В настоящее время функция блокировки экрана включена, пока включены уведомления.
// ... continue with the NotificationOptions built above val mediaOptions = CastMediaOptions.Builder() .setNotificationOptions(notificationOptions) .build() val castOptions: CastOptions = Builder() .setReceiverApplicationId(context.getString(R.string.app_id)) .setCastMediaOptions(mediaOptions) .build()
// ... continue with the NotificationOptions built above CastMediaOptions mediaOptions = new CastMediaOptions.Builder() .setNotificationOptions(notificationOptions) .build(); CastOptions castOptions = new CastOptions.Builder() .setReceiverApplicationId(context.getString(R.string.app_id)) .setCastMediaOptions(mediaOptions) .build();
Когда приложение-отправитель воспроизводит прямую трансляцию видео или аудио, SDK автоматически отображает кнопку воспроизведения/остановки вместо кнопки воспроизведения/паузы на элементе управления уведомлениями, но не на элементе управления экрана блокировки.
Примечание : для отображения элементов управления экраном блокировки на устройствах с ОС до Lollipop RemoteMediaClient
автоматически запросит аудиофокус от вашего имени.
Обработка ошибок
Для приложений-отправителей крайне важно обрабатывать все обратные вызовы ошибок и выбирать наилучший ответ для каждого этапа жизненного цикла трансляции. Приложение может отображать пользователю диалоговые окна с сообщениями об ошибках или разрывать соединение с веб-приёмником.