В этом руководстве для разработчиков описано, как добавить поддержку Google Cast в ваше приложение-отправитель для Android с помощью SDK Android Sender.
Мобильное устройство или ноутбук выступает в роли отправителя , управляющего воспроизведением, а устройство Google Cast — в роли приемника , отображающего контент на телевизоре.
Под фреймворком отправителя подразумевается исполняемый файл библиотеки классов Cast и связанные с ним ресурсы, присутствующие во время выполнения на устройстве отправителя. Приложение отправителя или приложение Cast — это приложение, также работающее на устройстве отправителя. Приложение Web Receiver — это HTML-приложение, работающее на устройстве с поддержкой Cast.
В фреймворке отправителя используется асинхронная архитектура обратных вызовов для информирования приложения-отправителя о событиях и для перехода между различными состояниями жизненного цикла приложения Cast.
Поток приложения
Следующие шаги описывают типичный высокоуровневый сценарий выполнения для Android-приложения-отправителя:
- Платформа Cast автоматически запускает обнаружение устройств
MediaRouterв зависимости от жизненного циклаActivity. - Когда пользователь нажимает кнопку Cast, платформа отображает диалоговое окно Cast со списком обнаруженных устройств Cast.
- Когда пользователь выбирает устройство Cast, платформа пытается запустить приложение Web Receiver на этом устройстве Cast.
- Данная платформа вызывает функции обратного вызова в приложении-отправителе для подтверждения запуска приложения Web Receiver.
- Данная платформа создает канал связи между приложениями-отправителями и веб-приемниками.
- Данная платформа использует канал связи для загрузки и управления воспроизведением мультимедиа в веб-приемнике.
- Данная платформа синхронизирует состояние воспроизведения мультимедиа между отправителем и веб-приемником: когда пользователь выполняет действия в пользовательском интерфейсе отправителя, платформа передает эти запросы на управление мультимедиа веб-приемнику, а когда веб-приемник отправляет обновления состояния мультимедиа, платформа обновляет состояние пользовательского интерфейса отправителя.
- Когда пользователь нажимает кнопку Cast для отключения от устройства Cast, платформа отключает приложение-отправитель от веб-приемника.
Полный список всех классов, методов и событий в Google Cast Android SDK см. в справочнике Google Cast Sender API для Android . В следующих разделах описаны шаги по добавлению Cast в ваше Android-приложение.
Настройте манифест Android.
В файле AndroidManifest.xml вашего приложения необходимо настроить следующие элементы для Cast SDK:
uses-sdk
Установите минимальный и целевой уровни API Android, поддерживаемые SDK Cast. В настоящее время минимальный уровень API — 23, а целевой — 34.
<uses-sdk
android:minSdkVersion="23"
android:targetSdkVersion="34" />
android:theme
Настройте тему оформления вашего приложения в соответствии с минимальной версией 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, которое отображается пользователю, чтобы привлечь внимание к кнопке Cast при первом появлении получателя. Приложение отправителя может настраивать текст и положение заголовка .Кнопка Cast : Кнопка 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.
Процесс обнаружения будет остановлен при закрытии диалогового окна 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; } }
Как работает управление сессиями
SDK Cast представляет концепцию сессии 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 включена функция автоматического слияния.
Данная платформа запустит службу при наличии медиасессии и остановит её при завершении медиасессии.
Как работает управление медиаконтентом
В фреймворке Cast класс RemoteMediaPlayer из версии 2.x заменен новым классом RemoteMediaClient , который предоставляет ту же функциональность с помощью более удобного набора API и позволяет избежать необходимости передавать объект GoogleApiClient.
Когда ваше приложение устанавливает CastSession с приложением Web Receiver, поддерживающим пространство имен media, фреймворк автоматически создаст экземпляр 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 для воспроизведения, паузы и управления приложением медиаплеера, работающим на Web Receiver.
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.
Фреймворк предоставляет настраиваемый View, 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, приложение-отправитель должно предоставлять расширенный контроллер для транслируемого медиаконтента. Расширенный контроллер представляет собой полноэкранную версию мини-контроллера.
SDK Cast предоставляет виджет для расширенного контроллера, называемый 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 физические кнопки на устройстве-отправителе по умолчанию можно использовать для регулировки громкости трансляции в веб-приемнике на любом устройстве с Android Jelly Bean или более поздней версии.
До появления Jelly Bean регулировка громкости осуществлялась с помощью физических кнопок.
Чтобы использовать физические клавиши регулировки громкости для управления громкостью устройства Web Receiver на устройствах Android старше версии Jelly Bean, приложение-отправитель должно переопределить dispatchKeyEvent в своих Activity и вызвать 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 Design Checklist, приложение-отправитель должно реализовать элементы управления воспроизведением в уведомлении и на экране блокировки , когда отправитель осуществляет трансляцию, но приложение-отправитель не находится в фокусе. Фреймворк предоставляет 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 автоматически отображает кнопку воспроизведения/остановки вместо кнопки воспроизведения/паузы в элементе управления уведомлениями, но не в элементе управления на экране блокировки.
Примечание : Для отображения элементов управления на экране блокировки на устройствах с версиями Android ниже Lollipop, RemoteMediaClient автоматически запросит фокусировку звука от вашего имени.
Обработка ошибок
Для приложений-отправителей крайне важно обрабатывать все обработчики ошибок и определять оптимальный ответ на каждом этапе жизненного цикла Cast. Приложение может отображать пользователю диалоговые окна с ошибками или разрывать соединение с веб-приемником.