Z tego przewodnika dla programistów dowiesz się, jak dodać obsługę Google Cast do aplikacji nadawcy na Androida za pomocą pakietu SDK nadawcy na Androida.
Urządzenie mobilne lub laptop to nadawca, który steruje odtwarzaniem, a urządzenie Google Cast to odbiorca, który wyświetla treści na telewizorze.
Platforma nadawcy odnosi się do pliku binarnego biblioteki klas Cast i powiązanych zasobów obecnych w czasie działania nadawcy. Aplikacja nadawcy lub aplikacja Cast to aplikacja, która również działa na urządzeniu nadawcy. Aplikacja odbiornika internetowego to aplikacja HTML działająca na urządzeniu obsługującym Cast.
Platforma nadawcy używa asynchronicznego wywołania zwrotnego, aby informować aplikację nadawcy o zdarzeniach i przechodzić między różnymi stanami cyklu życia aplikacji Cast.
Przepływ w aplikacji
Poniżej opisujemy typowy ogólny proces wykonywania aplikacji nadawcy na Androida:
- Platforma Cast automatycznie rozpoczyna wykrywanie urządzeń
MediaRouterna podstawie cyklu życiaActivity. - Gdy użytkownik kliknie przycisk Cast, platforma wyświetli okno Cast z listą wykrytych urządzeń Cast.
- Gdy użytkownik wybierze urządzenie Cast, platforma spróbuje uruchomić na nim aplikację odbiornika internetowego.
- Platforma wywołuje wywołania zwrotne w aplikacji nadawcy, aby potwierdzić, że aplikacja odbiornika internetowego została uruchomiona.
- Platforma tworzy kanał komunikacji między aplikacją nadawcy a aplikacją odbiornika internetowego.
- Platforma używa kanału komunikacyjnego do wczytywania i kontrolowania odtwarzania multimediów w aplikacji Web Receiver.
- Platforma synchronizuje stan odtwarzania multimediów między nadawcą a odbiornikiem internetowym: gdy użytkownik wykonuje działania w interfejsie nadawcy, platforma przekazuje te żądania opcji sterowania multimediami do odbiornika internetowego, a gdy odbiornik internetowy wysyła aktualizacje stanu multimediów, platforma aktualizuje stan interfejsu nadawcy.
- Gdy użytkownik kliknie przycisk Cast, aby odłączyć się od urządzenia Cast, platforma odłączy aplikację nadawczą od odbiornika internetowego.
Pełną listę wszystkich klas, metod i zdarzeń w pakiecie Android SDK Google Cast znajdziesz w dokumentacji interfejsu Google Cast Sender API na Androida. W sekcjach poniżej znajdziesz instrukcje dodawania Cast do aplikacji na Androida.
Konfigurowanie pliku manifestu Androida
W pliku AndroidManifest.xml aplikacji musisz skonfigurować te elementy pakietu Cast SDK:
uses-sdk
Ustaw minimalny i docelowy poziom interfejsu Android API obsługiwany przez pakiet Cast SDK. Obecnie minimalny poziom interfejsu API to 24, a docelowy to 35.
<uses-sdk
android:minSdkVersion="24"
android:targetSdkVersion="35" />
android:theme
Ustaw motyw aplikacji na podstawie minimalnej wersji pakietu Android SDK. Jeśli na przykład nie wdrażasz własnego motywu, użyj wariantu Theme.AppCompat, gdy kierujesz na minimalną wersję pakietu Android SDK sprzed wersji Lollipop.
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/Theme.AppCompat" >
...
</application>
Inicjowanie kontekstu Cast
Platforma ma globalny obiekt singleton CastContext, który koordynuje wszystkie interakcje platformy.
Aplikacja musi implementować interfejs OptionsProvider, aby udostępniać opcje potrzebne do zainicjowania obiektu pojedynczego CastContext. OptionsProvider udostępnia instancję CastOptions, która zawiera opcje wpływające na działanie platformy. Najważniejszy z nich to identyfikator aplikacji odbiornika internetowego, który służy do filtrowania wyników wykrywania i uruchamiania aplikacji odbiornika internetowego po rozpoczęciu sesji przesyłania.
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; } }
Musisz zadeklarować pełną i jednoznaczną nazwę zaimplementowanego elementu OptionsProvider jako pole metadanych w pliku AndroidManifest.xml aplikacji nadawcy:
<application>
...
<meta-data
android:name=
"com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
android:value="com.foo.CastOptionsProvider" />
</application>
CastContext jest inicjowany leniwie po wywołaniu 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); } }
Widżety UX Cast
Platforma Cast udostępnia widżety zgodne z listą kontrolną projektowania Cast:
Nakładka wprowadzająca: platforma udostępnia niestandardowy widok
IntroductoryOverlay, który jest wyświetlany użytkownikowi, aby zwrócić jego uwagę na przycisk Cast, gdy po raz pierwszy dostępny jest odbiornik. Aplikacja nadawcy może dostosować tekst i położenie tekstu tytułu.Przycisk Cast: Przycisk Cast jest widoczny niezależnie od dostępności urządzeń przesyłających. Gdy użytkownik po raz pierwszy kliknie przycisk Cast, wyświetli się okno Cast z listą wykrytych urządzeń. Gdy użytkownik kliknie przycisk Cast, gdy urządzenie jest połączone, wyświetlą się aktualne metadane multimediów (takie jak tytuł, nazwa studia nagraniowego i miniatura) lub użytkownik będzie mógł odłączyć się od urządzenia Cast. „Przycisk Cast” jest czasami nazywany „ikoną Cast”.
Minikontroler: gdy użytkownik przesyła treści i opuścił bieżącą stronę treści lub rozszerzony kontroler, aby przejść do innego ekranu w aplikacji nadawcy, u dołu ekranu wyświetla się minikontroler, który umożliwia użytkownikowi wyświetlanie metadanych aktualnie przesyłanych multimediów i sterowanie odtwarzaniem.
Rozwinięty kontroler: gdy użytkownik przesyła treści, po kliknięciu powiadomienia o multimediach lub minikontrolera uruchamia się rozwinięty kontroler, który wyświetla metadane aktualnie odtwarzanych multimediów i zawiera kilka przycisków do sterowania odtwarzaniem.
Powiadomienie: tylko na Androidzie. Gdy użytkownik przesyła treści i opuszcza aplikację nadawcy, wyświetla się powiadomienie o multimediach, które zawiera metadane aktualnie przesyłanych multimediów i elementy sterujące odtwarzaniem.
Ekran blokady: tylko na Androidzie. Gdy użytkownik przesyła treści i przechodzi do ekranu blokady (lub urządzenie przechodzi do niego po upływie limitu czasu), wyświetla się element sterujący odtwarzaniem na ekranie blokady, który pokazuje metadane aktualnie przesyłanych multimediów i elementy sterujące odtwarzaniem.
Poniższy przewodnik zawiera opisy dodawania tych widżetów do aplikacji.
Dodawanie przycisku Cast
Interfejsy API MediaRouter na Androida zostały zaprojektowane tak, aby umożliwiać wyświetlanie i odtwarzanie multimediów na urządzeniach dodatkowych.
Aplikacje na Androida, które używają interfejsu API MediaRouter, powinny zawierać przycisk Cast jako część interfejsu, aby użytkownicy mogli wybrać ścieżkę multimediów do odtwarzania na urządzeniu dodatkowym, takim jak urządzenie Cast.
Platforma ułatwia dodawanie MediaRouteButton jako Cast button. Najpierw dodaj element menu lub MediaRouteButton w pliku XML, który definiuje menu, i użyj CastButtonFactory, aby połączyć go z platformą.
// 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; }
Jeśli Activity dziedziczy po FragmentActivity, możesz dodać MediaRouteButton do układu.
// 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); }
Aby ustawić wygląd przycisku Cast za pomocą motywu, zapoznaj się z artykułem Dostosowywanie przycisku Cast.
Konfigurowanie wykrywania urządzeń
Wykrywanie urządzeń jest w pełni zarządzane przez CastContext.
Podczas inicjowania CastContext aplikacja nadawcy określa identyfikator aplikacji odbiornika internetowego i może opcjonalnie poprosić o filtrowanie przestrzeni nazw, ustawiając supportedNamespaces w CastOptions.
CastContext zawiera wewnętrznie odwołanie do MediaRouter i rozpocznie proces wykrywania w tych warunkach:
- Na podstawie algorytmu zaprojektowanego pod kątem równoważenia opóźnienia wykrywania urządzenia i zużycia baterii wykrywanie będzie czasami uruchamiane automatycznie, gdy aplikacja nadawcy zostanie przeniesiona na pierwszy plan.
- Okno przesyłania jest otwarte.
- Pakiet SDK Cast próbuje przywrócić sesję Cast.
Proces wykrywania zostanie zatrzymany, gdy okno przesyłania zostanie zamknięte lub aplikacja nadawcy przejdzie w tło.
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; } }
Jak działa zarządzanie sesjami
Pakiet Cast SDK wprowadza koncepcję sesji przesyłania, której ustanowienie łączy kroki łączenia się z urządzeniem, uruchamiania aplikacji Web Receiver (lub dołączania do niej), łączenia się z tą aplikacją i inicjowania kanału sterowania multimediami. Więcej informacji o sesjach Cast i cyklu życia odbiornika internetowego znajdziesz w przewodniku po cyklu życia aplikacji na odbiorniku internetowym.
Sesjami zarządza klasa SessionManager, do której aplikacja ma dostęp przez CastContext.getSessionManager().
Poszczególne sesje są reprezentowane przez podklasy klasy Session.
Na przykład CastSession reprezentuje sesje na urządzeniach przesyłających. Aplikacja może uzyskać dostęp do aktualnie aktywnej sesji przesyłania za pomocą SessionManager.getCurrentCastSession().
Aplikacja może używać klasy SessionManagerListener do monitorowania zdarzeń sesji, takich jak utworzenie, zawieszenie, wznowienie i zakończenie. Platforma automatycznie próbuje wznowić działanie po nieprawidłowym lub nagłym zakończeniu sesji, gdy była ona aktywna.
Sesje są tworzone i usuwane automatycznie w odpowiedzi na gesty użytkownika w oknach MediaRouter.
Aby lepiej zrozumieć błędy podczas uruchamiania Cast, aplikacje mogą używać metody CastContext#getCastReasonCodeForCastStatusCode(int) do konwertowania błędu uruchamiania sesji na CastReasonCodes.
Pamiętaj, że niektóre błędy uruchamiania sesji (np. CastReasonCodes#CAST_CANCELLED) są zamierzonym zachowaniem i nie powinny być rejestrowane jako błędy.
Jeśli chcesz otrzymywać informacje o zmianach stanu sesji, możesz zaimplementować SessionManagerListener. W tym przykładzie sprawdzana jest dostępność elementu CastSession w obiekcie 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); } }
Przeniesienie odtwarzania
Zachowanie stanu sesji jest podstawą przenoszenia odtwarzania, dzięki któremu użytkownicy mogą przenosić istniejące strumienie audio i wideo między urządzeniami za pomocą poleceń głosowych, aplikacji Google Home lub inteligentnych ekranów. Odtwarzanie multimediów zostanie zatrzymane na jednym urządzeniu (źródłowym) i będzie kontynuowane na innym (docelowym). Każde urządzenie Cast z najnowszym oprogramowaniem układowym może służyć jako źródło lub miejsce docelowe przeniesienia odtwarzania.
Aby uzyskać nowe urządzenie docelowe podczas przenoszenia lub rozszerzania strumienia, zarejestruj Cast.Listener za pomocą CastSession#addCastListener.
Następnie zadzwoń pod numer CastSession#getCastDevice() podczas oddzwaniania onDeviceNameChanged.
Więcej informacji znajdziesz w artykule Przenoszenie odtwarzania w odbiorniku internetowym.
Automatyczne ponowne łączenie
Platforma udostępnia interfejs ReconnectionService, który aplikacja nadawcy może włączyć, aby obsługiwać ponowne łączenie w wielu nietypowych przypadkach, takich jak:
- Przywracanie połączenia po tymczasowej utracie Wi-Fi
- Wybudzanie urządzenia z trybu uśpienia
- Przywracanie po przełączeniu aplikacji w tło
- Przywracanie po awarii aplikacji
Ta usługa jest domyślnie włączona i można ją wyłączyć w CastOptions.Builder.
Tę usługę można automatycznie scalić z plikiem manifestu aplikacji, jeśli w pliku gradle włączone jest automatyczne scalanie.
Platforma uruchomi usługę, gdy będzie aktywna sesja multimedialna, i zatrzyma ją, gdy sesja się zakończy.
Jak działają opcje sterowania multimediami
Platforma Cast wycofuje klasę RemoteMediaPlayer z Cast 2.x na rzecz nowej klasy RemoteMediaClient, która zapewnia tę samą funkcjonalność w zestawie wygodniejszych interfejsów API i pozwala uniknąć przekazywania GoogleApiClient.
Gdy aplikacja nawiąże CastSession z aplikacją odbiornika internetowego, która obsługuje przestrzeń nazw multimediów, platforma automatycznie utworzy instancję RemoteMediaClient. Aplikacja może uzyskać do niej dostęp, wywołując metodę getRemoteMediaClient() w instancji CastSession.
Wszystkie metody RemoteMediaClient, które wysyłają żądania do odbiornika internetowego, zwracają obiekt PendingResult, którego można użyć do śledzenia żądania.
Można oczekiwać, że instancja RemoteMediaClient będzie udostępniana przez wiele części aplikacji, a także przez niektóre wewnętrzne komponenty platformy, takie jak trwałe minikontrolery i usługa powiadomień.
W tym celu ta instancja obsługuje rejestrację wielu instancji RemoteMediaClient.Listener.
Ustawianie metadanych multimediów
Klasa MediaMetadata reprezentuje informacje o elemencie multimedialnym, który chcesz przesyłać. W tym przykładzie tworzymy nową instancję MediaMetadata filmu i ustawiamy tytuł, napisy oraz 2 obrazy.
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))));
Więcej informacji o używaniu obrazów z metadanymi multimediów znajdziesz w sekcji Wybór obrazu.
Wczytaj multimedia
Aplikacja może wczytać element multimedialny, jak pokazano w tym kodzie. Najpierw użyj MediaInfo.Builder z metadanymi multimediów, aby utworzyć instancję MediaInfo. Pobierz RemoteMediaClient z bieżącego CastSession, a następnie załaduj MediaInfo do tego RemoteMediaClient. Użyj RemoteMediaClient, aby odtwarzać, wstrzymywać i w inny sposób sterować aplikacją odtwarzacza multimedialnego działającą na odbiorniku internetowym.
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());
Zobacz też sekcję dotyczącą używania ścieżek multimedialnych.
Format filmu 4K
Aby sprawdzić format wideo multimediów, użyj getVideoInfo() w MediaStatus, aby uzyskać bieżącą instancję VideoInfo.
Ta instancja zawiera typ formatu telewizora HDR oraz wysokość i szerokość wyświetlacza w pikselach. Warianty formatu 4K są oznaczone stałymi HDR_TYPE_*.
Powiadomienia o zdalnym sterowaniu na wielu urządzeniach
Gdy użytkownik przesyła treści, inne urządzenia z Androidem w tej samej sieci otrzymają powiadomienie, które pozwoli im również sterować odtwarzaniem. Każdy, kto otrzymuje takie powiadomienia na swoim urządzeniu, może je wyłączyć w aplikacji Ustawienia, wybierając Google > Google Cast > Pokazuj powiadomienia o zdalnym sterowaniu. (Powiadomienia zawierają skrót do aplikacji Ustawienia). Więcej informacji znajdziesz w artykule Powiadomienia o pilocie do Cast.
Dodaj minikontroler
Zgodnie z listą kontrolną projektowania Cast aplikacja nadawcy powinna udostępniać stały element sterujący, zwany minikontrolerem, który powinien pojawiać się, gdy użytkownik przechodzi z bieżącej strony treści do innej części aplikacji nadawcy. Minikontroler przypomina użytkownikowi o bieżącej sesji Cast. Klikając minikontroler, użytkownik może wrócić do widoku rozszerzonego kontrolera przesyłania na pełnym ekranie.
Platforma udostępnia widok niestandardowy MiniControllerFragment, który możesz dodać u dołu pliku układu każdej aktywności, w której chcesz wyświetlać miniodtwarzacz.
<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" />
Gdy aplikacja nadawcy odtwarza transmisję na żywo wideo lub audio, pakiet SDK automatycznie wyświetla przycisk odtwarzania/zatrzymania zamiast przycisku odtwarzania/wstrzymania w minikontrolerze.
Aby ustawić wygląd tekstu tytułu i podtytułu tego widoku niestandardowego oraz wybrać przyciski, zapoznaj się z artykułem Dostosowywanie miniodtwarzacza.
Dodaj rozszerzony kontroler
Lista kontrolna projektowania Google Cast wymaga, aby aplikacja nadawcy udostępniała rozszerzony kontroler multimediów przesyłanych przez Cast. Rozwinięty kontroler to pełnoekranowa wersja minikontrolera.
Pakiet Cast SDK udostępnia widżet rozszerzonego sterowania o nazwie ExpandedControllerActivity.
Jest to klasa abstrakcyjna, którą musisz rozszerzyć, aby dodać przycisk Cast.
Najpierw utwórz nowy plik zasobów menu dla rozszerzonego kontrolera, aby udostępnić przycisk 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>
Utwórz nową klasę, która rozszerza klasę 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; } }
Teraz zadeklaruj nową aktywność w pliku manifestu aplikacji w tagu 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>
Edytuj CastOptionsProvider i zmień NotificationOptions oraz CastMediaOptions, aby ustawić aktywność docelową na nową aktywność:
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(); }
Zaktualizuj metodę LocalPlayerActivity loadRemoteMedia, aby wyświetlać nową aktywność po wczytaniu multimediów zdalnych:
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()); }
Gdy aplikacja nadawcy odtwarza transmisję na żywo wideo lub audio, pakiet SDK automatycznie wyświetla przycisk odtwarzania/zatrzymania zamiast przycisku odtwarzania/wstrzymania w rozwiniętym kontrolerze.
Aby ustawić wygląd za pomocą motywów, wybrać przyciski do wyświetlenia i dodać przyciski niestandardowe, zapoznaj się z artykułem Dostosowywanie rozszerzonego kontrolera.
Sterowanie głośnością
Platforma automatycznie zarządza głośnością w aplikacji nadawcy. Platforma automatycznie synchronizuje aplikacje nadawcy i odbiornika internetowego, dzięki czemu interfejs nadawcy zawsze podaje głośność określoną przez odbiornik internetowy.
Sterowanie głośnością za pomocą przycisków fizycznych
Na Androidzie fizyczne przyciski na urządzeniu nadawcy mogą być domyślnie używane do zmiany głośności sesji przesyłania na odbiorniku internetowym na dowolnym urządzeniu z systemem Jelly Bean lub nowszym.
Sterowanie głośnością za pomocą przycisków fizycznych przed wersją Jelly Bean
Aby używać fizycznych przycisków głośności do sterowania głośnością odbiornika internetowego na urządzeniach z Androidem starszych niż Jelly Bean, aplikacja nadawcy powinna zastąpić metodę dispatchKeyEvent w swoich aktywnościach i wywołać metodę 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); } }
Dodaj opcje sterowania multimediami do powiadomień i ekranu blokady
Tylko na Androidzie lista kontrolna projektowania Google Cast wymaga, aby aplikacja nadawcy implementowała opcje sterowania multimediami w powiadomieniu i na ekranie blokady, gdy nadawca przesyła treści, ale aplikacja nadawcy nie jest aktywna. Platforma udostępnia interfejsy MediaNotificationService i MediaIntentReceiver, które pomagają aplikacji nadawcy tworzyć opcje sterowania multimediami w powiadomieniu i na ekranie blokady.
MediaNotificationService działa, gdy nadawca przesyła treści, i wyświetla powiadomienie z miniaturą obrazu i informacjami o aktualnie przesyłanym elemencie, przyciskiem odtwarzania/wstrzymania i przyciskiem zatrzymania.
MediaIntentReceiver to BroadcastReceiver, który obsługuje działania użytkownika z powiadomienia.
Aplikacja może skonfigurować powiadomienia i opcje sterowania multimediami z ekranu blokady za pomocą NotificationOptions.
Aplikacja może skonfigurować, jakie przyciski sterujące mają być wyświetlane w powiadomieniu i który element Activity ma się otwierać, gdy użytkownik kliknie powiadomienie. Jeśli działania nie zostaną wyraźnie określone, zostaną użyte wartości domyślne MediaIntentReceiver.ACTION_TOGGLE_PLAYBACK i 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();
Wyświetlanie opcji sterowania multimediami w powiadomieniu i na ekranie blokady jest domyślnie włączone. Można je wyłączyć, wywołując setNotificationOptions z wartością null w CastMediaOptions.Builder.
Obecnie funkcja ekranu blokady jest włączona, dopóki włączone są powiadomienia.
// ... 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();
Gdy aplikacja nadawcy odtwarza transmisję na żywo wideo lub audio, pakiet SDK automatycznie wyświetla przycisk odtwarzania/zatrzymania zamiast przycisku odtwarzania/wstrzymania w elemencie sterującym powiadomieniami, ale nie w elemencie sterującym ekranem blokady.
Uwaga: aby wyświetlać elementy sterujące na ekranie blokady na urządzeniach z systemem starszym niż Lollipop, RemoteMediaClient automatycznie poprosi o fokus audio w Twoim imieniu.
Obsługuj błędy
Bardzo ważne jest, aby aplikacje nadawcze obsługiwały wszystkie wywołania zwrotne błędów i podejmowały najlepsze decyzje na każdym etapie cyklu życia Cast. Aplikacja może wyświetlać użytkownikowi okna z błędami lub zdecydować o zerwaniu połączenia z odbiornikiem internetowym.