Z tego przewodnika dla programistów dowiesz się, jak dodać obsługę Google Cast do aplikacji nadawcy na Androida za pomocą pakietu Android Sender SDK.
Urządzenie mobilne lub laptop to nadawca , który steruje odtwarzaniem, a urządzenie przesyłające Google Cast to odbiornik , który wyświetla treści na telewizorze.
Platforma nadawcy odnosi się do binarnej biblioteki klas Cast i powiązanych z nią zasobów, które są dostępne w czasie działania aplikacji na nadawcy. Aplikacja nadawcy lub aplikacja Cast to aplikacja działająca również na nadawcy. Aplikacja Web Receiver 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 aplikacji
Poniższe kroki opisują typowy przepływ wykonywania aplikacji nadawcy na Androida:
- Platforma Cast automatycznie rozpoczyna
MediaRouterwykrywanie urządzeń na 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 przesyłające, platforma spróbuje uruchomić na nim aplikację Web Receiver.
- Platforma wywoła wywołania zwrotne w aplikacji nadawcy, aby potwierdzić, że aplikacja Web Receiver została uruchomiona.
- Platforma utworzy kanał komunikacji między aplikacjami nadawcy i Web Receiver.
- Platforma używa kanału komunikacji do wczytywania i sterowania odtwarzaniem multimediów w Web Receiver.
- Platforma synchronizuje stan odtwarzania multimediów między nadawcą a Web Receiver: gdy użytkownik wykonuje działania w interfejsie nadawcy, platforma przekazuje te żądania sterowania multimediami do Web Receiver, a gdy Web Receiver wysyła aktualizacje stanu multimediów, platforma aktualizuje stan interfejsu nadawcy.
- Gdy użytkownik kliknie przycisk Cast, aby odłączyć się od urządzenia przesyłającego, platforma odłączy aplikację nadawcy od Web Receiver.
Pełną listę wszystkich klas, metod i zdarzeń w pakiecie Google Cast Android SDK znajdziesz w dokumentacji interfejsu Google Cast Sender API na Androida. W kolejnych sekcjach opisujemy, jak dodać 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 to API 23, a docelowy to API 34.
<uses-sdk
android:minSdkVersion="23"
android:targetSdkVersion="34" />
android:theme
Ustaw motyw aplikacji na podstawie minimalnej wersji pakietu Android SDK. Jeśli na przykład nie implementujesz własnego motywu, w przypadku kierowania na minimalną wersję pakietu Android SDK starszą niż Lollipop użyj wariantu Theme.AppCompat.
<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.
Aby udostępnić opcje potrzebne do zainicjowania
CastContext
singletonu, aplikacja musi zaimplementować
OptionsProvider
interfejs. OptionsProvider udostępnia instancję
CastOptions
która zawiera opcje wpływające na działanie platformy. Najważniejszy z nich to identyfikator aplikacji Web Receiver, który służy do filtrowania wyników wykrywania i uruchamiania aplikacji Web Receiver po rozpoczęciu sesji 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; } }
W pliku AndroidManifest.xml aplikacji nadawcy musisz zadeklarować pełną i jednoznaczną nazwę zaimplementowanego interfejsu OptionsProvider jako pole metadanych:
<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, gdy wywoływana jest funkcja 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ą projektu Cast:
Nakładka wprowadzająca: platforma udostępnia niestandardowy widok
IntroductoryOverlay, który jest wyświetlany użytkownikowi, aby zwrócić uwagę na przycisk Cast, gdy odbiornik jest dostępny po raz pierwszy. Aplikacja nadawcy może dostosować tekst i położenie tekstu tytułu tekst.Przycisk Cast: przycisk Cast jest widoczny niezależnie od dostępności urządzeń Cast. 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ę bieżące metadane multimediów (np. tytuł, nazwa studia nagrań i miniatura) lub użytkownik będzie mógł odłączyć się od urządzenia przesyłającego. „Przycisk Cast” jest czasami nazywany „ikoną Cast”.
Minikontroler: gdy użytkownik przesyła treści i opuści stronę z bieżącą treścią lub rozwinie kontroler do innego ekranu w aplikacji nadawcy, u dołu ekranu wyświetli się minikontroler, który umożliwi użytkownikowi wyświetlenie metadanych aktualnie przesyłanych multimediów i sterowanie odtwarzaniem.
Rozwinięty odtwarzacz: gdy użytkownik przesyła treści, a następnie kliknie powiadomienie o multimediach lub miniodtwarzacz, uruchomi się rozwinięty odtwarzacz, który wyświetli metadane aktualnie odtwarzanych multimediów i udostępni kilka przycisków do sterowania odtwarzaniem multimediów.
Powiadomienie: tylko Android. Gdy użytkownik przesyła treści i opuści aplikację nadawcy, wyświetli się powiadomienie o multimediach, które będzie zawierać metadane aktualnie przesyłanych multimediów i elementy sterujące odtwarzaniem.
Ekran blokady: tylko Android. Gdy użytkownik przesyła treści i przejdzie (lub urządzenie przekroczy limit czasu) do ekranu blokady, wyświetli się element sterujący multimediami na ekranie blokady, który będzie zawierać metadane aktualnie przesyłanych multimediów i elementy sterujące odtwarzaniem.
Z tego przewodnika dowiesz się, jak dodać te widżety do aplikacji.
Dodawanie przycisku Cast
Interfejsy Android
MediaRouter
API umożliwiają wyświetlanie i odtwarzanie multimediów na urządzeniach dodatkowych.
Aplikacje na Androida, które korzystają z interfejsu MediaRouter API, powinny zawierać w interfejsie przycisk Cast, aby użytkownicy mogli wybrać trasę multimediów do odtwarzania multimediów na urządzeniu dodatkowym, takim jak urządzenie przesyłające.
Platforma bardzo ułatwia dodanie
MediaRouteButton
jako
Cast button. Najpierw dodaj element menu lub MediaRouteButton w pliku XML
, który definiuje menu, a następnie 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, przeczytaj artykuł Dostosowywanie przycisku Cast.
Konfigurowanie wykrywania urządzeń
Wykrywanie urządzeń jest w pełni zarządzane przez the
CastContext.
Podczas inicjowania CastContext aplikacja nadawcy określa identyfikator aplikacji Web Receiver
i opcjonalnie może poprosić o filtrowanie przestrzeni nazw, ustawiając
supportedNamespaces w
CastOptions.
CastContext zawiera wewnętrznie odniesienie do MediaRouter i rozpocznie proces wykrywania w tych przypadkach:
- Na podstawie algorytmu, który ma na celu zrównoważenie opóźnienia wykrywania urządzeń i wykorzystania baterii, wykrywanie będzie czasami rozpoczynane automatycznie, gdy aplikacja nadawcy przejdzie na pierwszy plan.
- Okno Cast jest otwarte.
- Pakiet Cast SDK próbuje odzyskać sesję Cast.
Proces wykrywania zostanie zatrzymany, gdy okno Cast zostanie zamknięte lub aplikacja nadawcy przejdzie w tle.
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 pojęcie sesji Cast, której utworzenie łączy kroki połączenia z urządzeniem, uruchomienia (lub dołączenia) aplikacji Web Receiver, połączenia z tą aplikacją i zainicjowania kanału sterowania multimediami. Więcej informacji o sesjach Cast i cyklu życia Web Receiver znajdziesz w przewodniku Cykl życia aplikacji Web Receiver .
Sesje są zarządzane przez klasę
SessionManager,
do której aplikacja może uzyskać dostęp za pomocą
CastContext.getSessionManager().
Poszczególne sesje są reprezentowane przez podklasy klasy
Session.
Na przykład,
CastSession
reprezentuje sesje z urządzeniami Cast. Aplikacja może uzyskać dostęp do aktualnie aktywnej
sesji Cast 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.
Sesje są tworzone i zamykane automatycznie w odpowiedzi na gesty użytkownika w oknach MediaRouter.
Aby lepiej zrozumieć błędy uruchamiania Cast, aplikacje mogą używać
CastContext#getCastReasonCodeForCastStatusCode(int)
, aby przekonwertować błąd uruchamiania sesji na
CastReasonCodes.
Pamiętaj, że niektóre błędy uruchamiania sesji (np. CastReasonCodes#CAST_CANCELLED)
są zamierzonym działaniem i nie powinny być rejestrowane jako błędy.
Jeśli chcesz być na bieżąco ze zmianami stanu sesji, możesz zaimplementować SessionManagerListener. Ten przykład nasłuchuje dostępności CastSession w 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ą przeniesienia odtwarzania, dzięki czemu 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 zatrzymuje się na jednym urządzeniu (źródłowym) i jest kontynuowane na innym (docelowym). Każde urządzenie przesyłające z najnowszym oprogramowaniem układowym może służyć jako źródło lub miejsce docelowe w przypadku przeniesienia odtwarzania.
Aby uzyskać nowe urządzenie docelowe podczas przeniesienia odtwarzania lub rozszerzenia, zarejestruj Cast.Listener za pomocą CastSession#addCastListener.
Następnie wywołaj
CastSession#getCastDevice()
podczas wywołania zwrotnego onDeviceNameChanged.
Więcej informacji znajdziesz w artykule Przeniesienie odtwarzania w Web Receiver.
Automatyczne ponowne łączenie
Platforma udostępnia
ReconnectionService
, którą aplikacja nadawcy może włączyć, aby obsługiwać ponowne łączenie w wielu subtelnych
przypadkach brzegowych, takich jak:
- Odzyskiwanie po tymczasowej utracie połączenia Wi-Fi
- Odzyskiwanie po uśpieniu urządzenia
- Odzyskiwanie po przejściu aplikacji w tle
- Odzyskiwanie po awarii aplikacji
Ta usługa jest domyślnie włączona i można ją wyłączyć w
CastOptions.Builder.
Jeśli w pliku Gradle włączysz automatyczne scalanie, ta usługa może zostać automatycznie scalona z plikiem manifestu aplikacji.
Platforma uruchomi usługę, gdy będzie aktywna sesja multimediów, i zatrzyma ją, gdy sesja się zakończy.
Jak działa sterowanie multimediami
Platforma Cast wycofuje klasę
RemoteMediaPlayer
z Cast 2.x na rzecz nowej klasy
RemoteMediaClient,
która zapewnia te same funkcje w zestawie wygodniejszych interfejsów API i
eliminuje konieczność przekazywania GoogleApiClient.
Gdy aplikacja nawiąże
CastSession
z aplikacją Web Receiver, 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 Web Receiver, zwracają obiekt PendingResult, który można wykorzystać do śledzenia tego żądania.
Oczekuje się, że instancja RemoteMediaClient może być współdzielona przez
wiele części aplikacji, a nawet przez niektóre wewnętrzne komponenty
platformy, takie jak trwałe miniodtwarzacze 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 przesłać. Ten przykład tworzy nową instancję MediaMetadata filmu i ustawia tytuł, podtytuł 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 artykule Wybieranie obrazów.
Wczytywanie multimediów
Aplikacja może wczytać multimedia, 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 wczytaj MediaInfo do tego
RemoteMediaClient. Użyj RemoteMediaClient, aby odtwarzać, wstrzymywać i sterować aplikacją odtwarzacza multimediów działającą w 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());
Zapoznaj się też z sekcją dotyczącą używania ścieżek multimediów.
Format wideo 4K
Aby sprawdzić, w jakim formacie jest film, użyj
getVideoInfo()
w MediaStatus, aby uzyskać bieżącą instancję
VideoInfo.
Ta instancja zawiera typ formatu HDR TV oraz wysokość i szerokość wyświetlacza w pikselach. Warianty formatu 4K są oznaczone stałymi
HDR_TYPE_*.
Powiadomienia zdalnego sterowania na wielu urządzeniach
Gdy użytkownik przesyła treści, inne urządzenia z Androidem w tej samej sieci otrzymają powiadomienie, które umożliwi im sterowanie odtwarzaniem. Każdy, kto otrzymuje takie powiadomienia, może je wyłączyć na swoim urządzeniu w aplikacji Ustawienia w sekcji Google > Google Cast > Pokaż powiadomienia zdalnego sterowania. (Powiadomienia zawierają skrót do aplikacji Ustawienia). Więcej informacji znajdziesz w artykule Powiadomienia zdalnego sterowania Cast.
Dodaj minikontroler
Zgodnie z listą kontrolną projektu Cast, aplikacja nadawcy powinna udostępniać trwały element sterujący, czyli minikontroler, który powinien się pojawiać, gdy użytkownik opuści stronę z treścią i przejdzie do innej części aplikacji nadawcy. Minikontroler przypomina użytkownikowi o bieżącej sesji Cast. Klikając miniodtwarzacz, użytkownik może wrócić do widoku rozwiniętego odtwarzacza na pełnym ekranie.
Platforma udostępnia niestandardowy widok 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 strumień audio lub wideo w ramach transmisji na żywo, pakiet SDK automatycznie wyświetla przycisk odtwarzania/zatrzymania zamiast przycisku odtwarzania/pauzy w miniodtwarzaczu.
Aby ustawić wygląd tekstu tytułu i podtytułu tego widoku niestandardowego, oraz wybrać przyciski, przeczytaj artykuł Dostosowywanie miniodtwarzacza.
Dodawanie rozwiniętego odtwarzacza
Lista kontrolna projektu Google Cast wymaga, aby aplikacja nadawcy udostępniała rozwinięty odtwarzacz przesyłanych multimediów. Rozwinięty kontroler to wersja minikontrolera na pełnym ekranie.
Pakiet Cast SDK udostępnia widżet rozwiniętego odtwarzacza o nazwie
ExpandedControllerActivity.
Jest to klasa abstrakcyjna, którą musisz podklasować, aby dodać przycisk Cast.
Najpierw utwórz nowy plik zasobu menu dla rozwiniętego odtwarzacza, 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 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ę loadRemoteMedia w LocalPlayerActivity, 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 strumień audio lub wideo na żywo, pakiet SDK automatycznie wyświetla przycisk odtwarzania/zatrzymania zamiast przycisku odtwarzania/pauzy w rozwiniętym odtwarzaczu.
Aby ustawić wygląd za pomocą motywów, wybrać przyciski do wyświetlania, i dodać przyciski niestandardowe, przeczytaj artykuł Dostosowywanie rozwiniętego odtwarzacza.
Sterowanie głośnością
Platforma automatycznie zarządza głośnością aplikacji nadawcy. Automatycznie synchronizuje aplikacje nadawcy i Web Receiver, aby interfejs nadawcy zawsze zgłaszał głośność określoną przez Web Receiver.
Sterowanie głośnością za pomocą przycisków fizycznych
W Androidzie przyciski fizyczne na urządzeniu nadawcy mogą domyślnie służyć do zmiany głośności sesji Cast w Web Receiver na każdym urządzeniu z Jelly Bean lub nowszym.
Sterowanie głośnością za pomocą przycisków fizycznych w wersjach starszych niż Jelly Bean
Aby używać fizycznych przycisków głośności do sterowania głośnością urządzenia Web Receiver na urządzeniach z Androidem starszych niż Jelly Bean, aplikacja nadawcy powinna zastąpić dispatchKeyEvent w swoich aktywnościach i wywołać 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); } }
Dodawanie elementów sterujących multimediami do powiadomienia i ekranu blokady
Zgodnie z listą kontrolną projektu Google Cast aplikacja nadawcy musi zaimplementować elementy sterujące multimediami w powiadomieniu i na ekranie blokady, gdy nadawca przesyła treści, ale aplikacja nadawcy nie jest aktywna. Platforma
udostępnia
MediaNotificationService
i
MediaIntentReceiver
, które pomagają aplikacji nadawcy tworzyć elementy sterujące multimediami w powiadomieniu i na ekranie
blokady.
MediaNotificationService działa, gdy nadawca przesyła treści, i wyświetla powiadomienie z miniaturą obrazu oraz informacjami o aktualnie przesyłanym elemencie, przyciskiem odtwarzania/pauzy i przyciskiem zatrzymania.
MediaIntentReceiver to BroadcastReceiver, który obsługuje działania użytkownika w powiadomieniu.
Aplikacja może skonfigurować powiadomienia i sterowanie multimediami z poziomu ekranu blokady za pomocą
NotificationOptions.
Aplikacja może skonfigurować, które przyciski sterujące mają się wyświetlać w powiadomieniu, oraz którą Activity ma się otworzyć, 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 elementów sterujących multimediami w powiadomieniu i na ekranie blokady jest domyślnie włączone i 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 strumień audio lub wideo na żywo, pakiet SDK automatycznie wyświetla przycisk odtwarzania/zatrzymania zamiast przycisku odtwarzania/pauzy w elemencie sterującym powiadomienia, ale nie w elemencie sterującym ekranu blokady.
Uwaga: aby wyświetlać elementy sterujące na ekranie blokady na urządzeniach starszych niż Lollipop,
RemoteMediaClient automatycznie poprosi o aktywność audio w Twoim imieniu.
Obsługiwanie błędów
Bardzo ważne jest, aby aplikacje nadawcy obsługiwały wszystkie wywołania zwrotne błędów i decydowały o najlepszej reakcji na każdym etapie cyklu życia Cast. Aplikacja może wyświetlać użytkownikowi okna dialogowe z błędami lub zdecydować się na zerwanie połączenia z Web Receiver.