В этом руководстве для разработчиков описывается, как добавить поддержку 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.
- Платформа вызывает обратные вызовы в приложении-отправителе, чтобы подтвердить запуск приложения веб-приемника.
- Платформа создает канал связи между приложениями-отправителями и веб-приемниками.
- Платформа использует канал связи для загрузки и управления воспроизведением мультимедиа на веб-приемнике.
- Платформа синхронизирует состояние воспроизведения мультимедиа между отправителем и веб-приемником: когда пользователь выполняет действия пользовательского интерфейса отправителя, платформа передает эти запросы управления мультимедиа веб-приемнику, а когда веб-приемник отправляет обновления статуса мультимедиа, платформа обновляет состояние интерфейс отправителя.
- Когда пользователь нажимает кнопку Cast, чтобы отключиться от устройства Cast, платформа отключит приложение-отправитель от веб-приемника.
Полный список всех классов, методов и событий в Android SDK Google Cast см. в Справочнике по API Google Cast Sender для Android . В следующих разделах описаны шаги, необходимые для добавления Cast в ваше приложение для Android.
Настройте манифест Android
В файле AndroidManifest.xml вашего приложения необходимо настроить следующие элементы для Cast SDK:
использует-SDK
Установите минимальный и целевой уровни Android API, поддерживаемые Cast SDK. В настоящее время минимальный уровень API – 21, а целевой – уровень API 28.
<uses-sdk
android:minSdkVersion="21"
android:targetSdkVersion="28" />
андроид:тема
Установите тему вашего приложения на основе минимальной версии 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 Design:
Introductory Overlay . Платформа предоставляет настраиваемое представление
IntroductoryOverlay
, которое показывается пользователю, чтобы привлечь внимание к кнопке Cast при первом появлении приемника. Приложение Sender может настроить текст и положение текста заголовка .Кнопка Cast : кнопка Cast отображается, когда обнаружен приемник, поддерживающий ваше приложение. Когда пользователь впервые нажимает кнопку Cast, отображается диалоговое окно Cast со списком обнаруженных устройств. Когда пользователь нажимает кнопку Cast, когда устройство подключено, оно отображает текущие метаданные мультимедиа (такие как название, название студии звукозаписи и миниатюрное изображение) или позволяет пользователю отключиться от устройства Cast.
Мини-контроллер : когда пользователь транслирует контент и переходит от текущей страницы контента или расширенного контроллера к другому экрану в приложении-отправителе, мини-контроллер отображается в нижней части экрана, чтобы пользователь мог видеть текущую трансляцию мультимедиа. метаданные и для управления воспроизведением.
Расширенный контроллер : когда пользователь транслирует контент, если он нажимает на уведомление мультимедиа или мини-контроллер, запускается расширенный контроллер, который отображает воспроизводимые в данный момент метаданные мультимедиа и предоставляет несколько кнопок для управления воспроизведением мультимедиа.
Уведомление : только для Android. Когда пользователь транслирует контент и выходит из приложения-отправителя, отображается уведомление о мультимедиа, в котором отображаются текущие метаданные мультимедиа и элементы управления воспроизведением.
Экран блокировки : только для Android. Когда пользователь транслирует контент и переходит (или время ожидания устройства истекает) на экран блокировки, отображается элемент управления экраном блокировки мультимедиа, который показывает текущие метаданные мультимедиа трансляции и элементы управления воспроизведением.
Следующее руководство содержит описание того, как добавить эти виджеты в ваше приложение.
Добавить кнопку трансляции
API-интерфейсы Android MediaRouter
предназначены для обеспечения отображения и воспроизведения мультимедиа на дополнительных устройствах. Приложения Android, использующие MediaRouter
API, должны включать кнопку 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); }
Чтобы настроить внешний вид кнопки трансляции с помощью темы, см. раздел Настройка кнопки трансляции .
Настройка обнаружения устройств
Обнаружение устройства полностью управляется CastContext
. При инициализации CastContext приложение-отправитель указывает идентификатор приложения веб-приемника и может дополнительно запросить фильтрацию пространства имен, задав supportedNamespaces
в CastOptions
. CastContext
содержит внутреннюю ссылку на MediaRouter
и запускает процесс обнаружения, когда приложение-отправитель переходит на передний план, и останавливается, когда приложение-отправитель переходит в фоновый режим.
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 и жизненном цикле веб-приемника см. в руководстве по жизненному циклу приложения веб-приемника.
Сессии управляются классом 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 } override fun onResume() { super.onResume() mCastSession = mSessionManager.currentCastSession mSessionManager.addSessionManagerListener(mSessionManagerListener, CastSession::class.java) } override fun onPause() { super.onPause() mSessionManager.removeSessionManagerListener(mSessionManagerListener, CastSession::class.java) mCastSession = null } }
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(); } @Override protected void onResume() { super.onResume(); mCastSession = mSessionManager.getCurrentCastSession(); mSessionManager.addSessionManagerListener(mSessionManagerListener, CastSession.class); } @Override protected void onPause() { super.onPause(); mSessionManager.removeSessionManagerListener(mSessionManagerListener, CastSession.class); mCastSession = null; } }
Потоковая передача
Сохранение состояния сеанса является основой потоковой передачи, когда пользователи могут перемещать существующие аудио- и видеопотоки между устройствами с помощью голосовых команд, приложения Google Home или интеллектуальных дисплеев. Воспроизведение мультимедиа прекращается на одном устройстве (источнике) и продолжается на другом (целевом). Любое устройство Cast с последней прошивкой может служить источником или получателем потоковой передачи.
Чтобы получить новое целевое устройство во время передачи или расширения потока, зарегистрируйте Cast.Listener
с помощью CastSession#addCastListener
. Затем вызовите CastSession#getCastDevice()
во время обратного вызова onDeviceNameChanged
.
Дополнительную информацию см. в разделе Потоковая передача в веб-приемнике .
Автоматическое переподключение
Платформа предоставляет ReconnectionService
, который может быть включен приложением-отправителем для обработки повторного подключения во многих сложных случаях, таких как:
- Восстановление после временной потери WiFi
- Выход из спящего режима устройства
- Восстановление из фонового режима приложения
- Восстановление в случае сбоя приложения
Эта служба включена по умолчанию и может быть отключена в CastOptions.Builder
.
Эта служба может быть автоматически объединена с манифестом вашего приложения, если автоматическое слияние включено в вашем файле gradle.
Платформа запустит службу при наличии сеанса мультимедиа и остановит ее, когда сеанс мультимедиа завершится.
Как работает управление медиа
Фреймворк 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());
См. также раздел об использовании дорожек мультимедиа .
Формат видео 4К
Чтобы проверить, какой видеоформат у вашего мультимедиа, используйте getVideoInfo()
в MediaStatus, чтобы получить текущий экземпляр VideoInfo
. Этот экземпляр содержит тип формата HDR TV, а также высоту и ширину экрана в пикселях. Варианты формата 4K обозначаются константами HDR_TYPE_*
.
Уведомления удаленного управления на несколько устройств
Когда пользователь транслирует, другие устройства Android в той же сети получат уведомление, чтобы также позволить им управлять воспроизведением. Любой, чье устройство получает такие уведомления, может отключить их для этого устройства в приложении «Настройки» в Google > Google Cast > Показать уведомления удаленного управления . (Уведомления содержат ярлык приложения «Настройки».) Дополнительные сведения см. в разделе Уведомления о дистанционном управлении трансляцией .
Добавить мини-контроллер
Согласно контрольному списку Cast Design , приложение-отправитель должно предоставлять постоянный элемент управления, известный как мини-контроллер , который должен появляться, когда пользователь переходит от текущей страницы содержимого к другой части приложения-отправителя. Мини-контроллер предоставляет визуальное напоминание пользователю о текущем сеансе 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 Design требует, чтобы приложение-отправитель предоставляло расширенный контроллер для транслируемого мультимедиа. Расширенный контроллер представляет собой полноэкранную версию мини-контроллера.
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(); }
Обновите метод loadRemoteMedia
LocalPlayerActivity
, чтобы он отображал вашу новую активность при загрузке удаленного носителя:
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
Чтобы использовать физические клавиши громкости для управления громкостью устройства веб-приемника на устройствах 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
с нулевым значением в 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
автоматически запросит фокус звука от вашего имени.
Обработка ошибок
Для приложений-отправителей очень важно обрабатывать все обратные вызовы ошибок и выбирать наилучший ответ для каждого этапа жизненного цикла Cast. Приложение может отображать диалоговые окна ошибок для пользователя или может принять решение о разрыве соединения с веб-приемником.