Ten przewodnik dla programistów opisuje, jak dodać obsługę Google Cast do aplikacji nadawczej na Androida za pomocą pakietu Android Sender SDK.
Urządzenie mobilne lub laptop to nadawca, który kontroluje odtwarzanie, a urządzenie Google Cast to odbiornik, które wyświetla treści na telewizorze.
Framework nadawcy to binarne biblioteki klas Cast i powiązane zasoby obecne w czasie wykonywania w nadawcy. Aplikacja nadawcy lub aplikacja przesyłania odnosi się do aplikacji działającej również na urządzeniu nadawcy. Aplikacja Web Receiver to aplikacja HTML działająca na urządzeniu obsługującym Cast.
Framework nadawcy używa asynchronicznego wywołania zwrotnego, aby informować aplikację nadawcy o zdarzeniach i przechodzeniu między różnymi stanami cyklu życia aplikacji przesyłania.
Przebieg działania aplikacji
Te kroki opisują typowy ogólny przepływ danych w przypadku aplikacji na Androida:
- Platforma Cast automatycznie rozpoczyna wykrywanie urządzeń
MediaRouter
na podstawie cyklu życiaActivity
. - Gdy użytkownik kliknie przycisk przesyłania, framework wyświetli okno przesyłania z listą wykrytych urządzeń przesyłania.
- Gdy użytkownik wybierze urządzenie przesyłające, framework spróbuje uruchomić na tym urządzeniu aplikację Web Receiver.
- Framework wywołuje funkcje wywołania zwrotnego w aplikacji nadawcy, aby potwierdzić, że aplikacja WebReceiver została uruchomiona.
- Platforma tworzy kanał komunikacji między aplikacjami nadawcy i odbiorcy internetowego.
- Framework używa kanału komunikacji do wczytywania i kontrolowania odtwarzania multimediów w odbiorniku internetowym.
- Platforma synchronizuje stan odtwarzania multimediów między nadawcą a odbiornikiem internetowym: gdy użytkownik wykonuje czynności w interfejsie nadawcy, platforma przekazuje te żądania 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 rozłączyć się z urządzeniem Cast, framework rozłączy aplikację nadawcy z odbiornikiem internetowym.
Pełną listę wszystkich klas, metod i zdarzeń w pakiecie SDK Google Cast na Androida znajdziesz w artykule Przewodnik po interfejsie Google Cast Sender API na Androida. W następnych sekcjach znajdziesz instrukcje dodawania funkcji Cast do aplikacji na Androida.
Konfigurowanie pliku manifestu Androida
Plik AndroidManifest.xml aplikacji wymaga skonfigurowania tych elementów pakietu SDK Cast:
uses-sdk
Ustaw minimalny i docelowy poziom interfejsu Android API obsługiwany przez pakiet SDK Cast. Obecnie minimalny poziom to 23, a docelowy – 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 wersję pakietu SDK Androida wcześniejszą niż Lollipop powinieneś użyć wariantu Theme.AppCompat
.
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/Theme.AppCompat" >
...
</application>
Inicjowanie kontekstu przesyłania
Framework ma globalny obiekt pojedynczy CastContext
, który koordynuje wszystkie interakcje frameworku.
Aplikacja musi implementować interfejs OptionsProvider
, aby udostępnić opcje potrzebne do zainicjowania singletona CastContext
. OptionsProvider
udostępnia instancję CastOptions
, która zawiera opcje wpływające na działanie platformy. Najważniejszym z nich jest identyfikator aplikacji Web Receiver, który służy do filtrowania wyników wyszukiwania i uruchamiania aplikacji Web Receiver 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; } }
Pełną nazwę zaimplementowanej usługi OptionsProvider
należy zadeklarować 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 w sposób leniwy, 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 w przesyłaniu
Platforma Cast udostępnia widżety zgodne z wytycznymi dotyczącymi projektu Cast:
Wprowadzenie: framework udostępnia niestandardowy widok
IntroductoryOverlay
, który jest pokazywany użytkownikowi, aby zwrócić jego uwagę na przycisk Cast, gdy po raz pierwszy pojawi się odbiornik. Aplikacja Sender może dostosowywać tekst i pozycję 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 przesyłania, wyświetli się okno przesyłania z listą wykrytych urządzeń. Gdy użytkownik kliknie przycisk Cast, gdy urządzenie jest połączone, wyświetli się aktualne metadane multimediów (takie jak tytuł, nazwa studia nagrywającego 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”.
Mini kontroler: gdy użytkownik przesyła treści i przechodzi z bieżącej strony treści lub rozwiniętego kontrolera na inny ekran w aplikacji nadawcy, na dole ekranu wyświetla się mini kontroler, aby użytkownik mógł zobaczyć metadane przesyłanych multimediów i sterować odtwarzaniem.
Rozwinięty kontroler: gdy użytkownik przesyła treści, klika powiadomienie o multimediach lub mini kontroler, uruchamia się rozszerzony kontroler, który wyświetla metadane aktualnie odtwarzanych multimediów i zawiera kilka przycisków do sterowania odtwarzaniem.
Powiadomienie: tylko Android. Gdy użytkownik przesyła treści i przechodzi do innej aplikacji, wyświetla się powiadomienie multimedialne z informacjami o przesyłanych metadanych i elementami sterowania odtwarzaniem.
Ekran blokady: tylko Android. Gdy użytkownik przesyła treści i przejdzie (lub urządzenie przejdzie) do ekranu blokady, wyświetli się element sterujący na ekranie blokady multimediów, który zawiera metadane przesyłanych multimediów i elementy sterowania odtwarzaniem.
W tym przewodniku znajdziesz opisy sposobów dodawania tych widżetów do aplikacji.
Dodawanie przycisku przesyłania
Interfejsy API Androida
MediaRouter
są przeznaczone do wyświetlania i odtwarzania multimediów na urządzeniach dodatkowych.
Aplikacje na Androida, które korzystają z interfejsu API MediaRouter
, powinny zawierać przycisk Cast, aby umożliwić użytkownikom wybór ścieżki multimedialnej do odtwarzania multimediów na urządzeniu dodatkowym, takim jak urządzenie z Cast.
Dzięki temu frameworkowi możesz bardzo łatwo dodać MediaRouteButton
jako Cast button
. Najpierw dodaj element menu lub element MediaRouteButton
do pliku XML, który definiuje menu, a następnie użyj elementu CastButtonFactory
, aby połączyć go z frameworkiem.
// 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ć do układu 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); }
Aby ustawić wygląd przycisku Cast za pomocą motywu, przeczytaj artykuł Dostosowywanie przycisku Cast.
Konfigurowanie wykrywania urządzeń
Wykrywanie urządzeń jest całkowicie zarządzane przez CastContext
.
Podczas inicjowania kontekstu Cast aplikacja nadawcza określa identyfikator aplikacji odbiornika internetowego i może opcjonalnie poprosić o filtrowanie przestrzeni nazw, ustawiając parametr supportedNamespaces
w CastOptions
.
CastContext
zawiera wewnętrzne odwołanie do MediaRouter
i rozpoczyna proces wyszukiwania w tych warunkach:
- Na podstawie algorytmu zaprojektowanego w celu zrównoważenia opóźnienia wykrywania urządzenia i zużycia baterii wykrywanie będzie czasami uruchamiane automatycznie, gdy aplikacja nadawcy znajdzie się na pierwszym planie.
- Okno przesyłania jest otwarte.
- Pakiet SDK Cast próbuje odzyskać sesję przesyłania.
Proces wykrywania zostanie zatrzymany, gdy zamkniesz okno przesyłania lub aplikacja nadawcza 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 SDK Cast wprowadza pojęcie sesji Cast, której utworzenie łączy w sobie czynności takie jak połączenie z urządzeniem, uruchomienie (lub dołączenie) aplikacji Web Receiver, połączenie z tą aplikacją i inicjowanie kanału sterowania multimediami. Więcej informacji o sesjach przesyłania i cyklu życia odbiornika internetowego znajdziesz w przewodniku po cyklu życia aplikacji.
Sesjami zarządza zajęcia SessionManager
, do których Twoja aplikacja ma dostęp za pomocą CastContext.getSessionManager()
.
Poszczególne sesje są reprezentowane przez podklasy klasy Session
.
Na przykład:
CastSession
odpowiada sesjom na urządzeniach Cast. Aplikacja może uzyskać dostęp do bieżącej sesji przesyłania przez SessionManager.getCurrentCastSession()
.
Aplikacja może używać klasy SessionManagerListener
do monitorowania zdarzeń sesji, takich jak tworzenie, zawieszanie, wznawianie i zakończenie. Framework 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 dialogach MediaRouter
.
Aby lepiej zrozumieć błędy inicjowania przesyłania, aplikacje mogą używać parametru CastContext#getCastReasonCodeForCastStatusCode(int)
, aby przekształcić błąd inicjowania sesji w CastReasonCodes
.
Pamiętaj, że niektóre błędy podczas uruchamiania sesji (np. CastReasonCodes#CAST_CANCELLED
) są zamierzonym działaniem i nie powinny być rejestrowane jako błąd.
Jeśli chcesz wiedzieć, kiedy zmienia się stan sesji, możesz zaimplementować SessionManagerListener
. W tym przykładzie sprawdzamy dostępność usługi 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 stanowi podstawę przenoszenia strumienia, 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. Multimedia przestają być odtwarzane na jednym urządzeniu (źródło) i kontynuują na drugim (miejsce docelowe). Źródłem lub celem przesyłania strumieniowego może być dowolne urządzenie Cast z najnowszą wersją oprogramowania układowego.
Aby uzyskać nowe urządzenie docelowe podczas przenoszenia lub rozszerzania strumienia, zarejestruj Cast.Listener
za pomocą CastSession#addCastListener
.
Następnie zadzwoń pod numerCastSession#getCastDevice()
podczas onDeviceNameChanged
połączenia zwrotnego.
Więcej informacji znajdziesz w artykule Przenoszenie strumienia na odbiornik internetowy.
Automatyczne ponowne łączenie
Platforma udostępnia funkcję ReconnectionService
, którą aplikacja nadawcza może włączyć, aby obsługiwać ponowne nawiązywanie połączenia w wielu subtelnych przypadkach szczególnych, takich jak:
- Przywracanie działania po tymczasowej utracie połączenia z Wi-Fi
- Przywracanie po przejściu urządzenia w stan uśpienia
- Przywracanie aplikacji z tła
- Odzyskiwanie danych w przypadku awarii aplikacji
Ta usługa jest domyślnie włączona i można ją wyłączyć w sekcji CastOptions.Builder
.
Ta usługa może być automatycznie scalana z pliku manifestu aplikacji, jeśli w pliku gradle włączone jest automatyczne scalanie.
Framework uruchamia usługę, gdy trwa sesja multimediów, i zatrzymuje ją, gdy sesja się kończy.
Jak działa kontrola mediów
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 nie wymaga przekazywania obiektu GoogleApiClient.
Gdy aplikacja nawiąże relację CastSession
z aplikacją Web Receiver obsługującą przestrzeń nazw multimediów, framework 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óry może służyć do śledzenia żądania.
RemoteMediaClient
może być używany przez wiele części aplikacji, a także przez niektóre wewnętrzne komponenty frameworku, takie jak trwałe minikontrolery i usługa powiadomień.
W tym celu ta instancja obsługuje rejestrację wielu instancji usługi RemoteMediaClient.Listener
.
Ustawianie metadanych multimediów
Klasa MediaMetadata
reprezentuje informacje o pliku multimedialnym, który chcesz przesłać. W tym przykładzie tworzymy nowy egzemplarz 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))));
Informacje na temat korzystania z obrazów z metadanymi multimediów znajdziesz w artykule Wybór obrazu.
Ładowanie multimediów
Aplikacja może wczytać element multimedialny, jak pokazano w poniższym kodzie. Najpierw użyj pliku 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 multimediów działającą w 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ę Korzystanie z ścieżek multimedialnych.
Format wideo 4K
Aby sprawdzić format pliku multimedialnego, użyj polecenia getVideoInfo()
w MediaStatus, aby uzyskać bieżącą instancję VideoInfo
.
Ten element zawiera typ formatu HDR TV oraz wysokość i szerokość wyświetlacza w pikselach. Warianty formatu 4K są oznaczone za pomocą stałych HDR_TYPE_*
.
Powiadomienia o zdalnym sterowaniu na wielu urządzeniach
Gdy użytkownik będzie przesyłać treści, inne urządzenia z Androidem w tej samej sieci otrzymają powiadomienie, które pozwoli im również kontrolować odtwarzanie. Każdy, kto ma urządzenie, na które wysyłane są takie powiadomienia, może je wyłączyć w aplikacji Ustawienia w sekcji Google > Google Cast > Pokaż powiadomienia o sterowaniu zdalnie. (powiadomienia zawierają skrót do aplikacji Ustawienia). Więcej informacji znajdziesz w artykule Powiadomienia o zdalnym sterowaniu Google Cast.
Dodawanie mini kontrolera
Zgodnie z listą kontrolną dotyczącą projektowania Cast aplikacja wysyłająca powinna udostępniać trwały element sterujący, zwany ministerującym, który powinien pojawiać się, gdy użytkownik przejdzie z bieżącej strony treści do innej części aplikacji. Ministerujący powinien przypominać użytkownikowi o bieżącej sesji Cast. Kliknięcie minikontrolera powoduje powrót do widoku kontrolera rozszerzonego na pełnym ekranie.
Platforma udostępnia niestandardowy widok MiniControllerFragment, który możesz dodać do dolnej części pliku układu każdej aktywności, w której chcesz wyświetlić mini kontroler.
<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 z dźwiękiem lub wideo, SDK automatycznie wyświetla przycisk odtwarzania/zatrzymania zamiast przycisku odtwarzania/wstrzymywania na minikontrolerze.
Aby ustawić wygląd tekstu tytułu i podtytułu tego widoku niestandardowego oraz wybrać przyciski, zapoznaj się z artykułem Dostosowywanie minikontrolera.
Dodawanie rozszerzonego kontrolera
Lista kontrolna Google Cast Design wymaga, aby aplikacja nadawcza udostępniała rozwinięty kontroler dla przesyłanych multimediów. Rozwinięty kontroler to wersja minikontrolera na pełnym ekranie.
Pakiet Cast SDK udostępnia widżet rozszerzonego kontrolera o nazwie ExpandedControllerActivity
.
Jest to abstrakcyjna klasa, której musisz użyć jako podklasy, aby dodać przycisk Cast.
Najpierw utwórz nowy plik zasobu 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ę rozszerzającą 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 za pomocą 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>
Zmień tagi CastOptionsProvider
, NotificationOptions
i CastMediaOptions
, aby ustawić nową aktywność jako aktywność docelową:
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świetlić nową aktywność po załadowaniu zdalnych multimediów:
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 z filmem lub dźwiękiem, SDK automatycznie wyświetli przycisk odtwarzania/zatrzymania zamiast przycisku odtwarzania/wstrzymania w rozwiniętym kontrolerze.
Aby ustawić wygląd za pomocą motywów, wybrać przyciski do wyświetlania i dodać przyciski niestandardowe, zobacz Dostosowywanie rozszerzonego kontrolera.
Sterowanie głośnością
Platforma automatycznie zarządza głośnością aplikacji nadawcy. Synchronizuje ona automatycznie aplikacje nadawcy i odbiornika internetowego, aby interfejs użytkownika nadawcy zawsze podawał głośność określoną przez odbiornik internetowy.
Sterowanie głośnością za pomocą przycisku fizycznego
Na urządzeniach z Androidem można używać przycisków fizycznych na urządzeniu nadawczym do zmiany głośności sesji przesyłania na odbiornik internetowy. Dotyczy to domyślnie wszystkich urządzeń z Androidem w wersji Jelly Bean lub nowszej.
Sterowanie głośnością za pomocą przycisku fizycznego przed Jelly Bean
Aby móc używać fizycznych przycisków głośności do sterowania głośnością urządzenia Web Receiver na urządzeniach z Androidem starszym niż Jelly Bean, aplikacja nadawcy powinna zastąpić dispatchKeyEvent
w 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 powiadomień i ekranu blokady
Wyłącznie na Androidzie lista kontrolna Google Cast Design wymaga, aby aplikacja nadawcza zaimplementowała elementy sterowania multimediami w powiadomieniu i na ekranie blokady, gdzie nadawca przesyła treści, ale aplikacja nadawcza nie ma fokusa. MediaNotificationService
MediaIntentReceiver
Aby pomóc aplikacji nadawcy w utworzeniu elementów sterujących multimediami w powiadomieniu i na ekranie blokady.
MediaNotificationService
uruchamia się, gdy nadawca przesyła treści, i wyświetla powiadomienie z miniaturą obrazu oraz informacjami o bieżącym elemencie przesyłania, przyciskiem odtwarzania/wstrzymywania i przyciskiem zatrzymania.
MediaIntentReceiver
to BroadcastReceiver
, który obsługuje działania użytkownika związane z powiadomieniem.
Aplikacja może konfigurować powiadomienia i sterowanie multimediami na ekranie blokady za pomocą NotificationOptions
.
Aplikacja może skonfigurować przyciski sterujące, które mają się wyświetlać w powiadomieniu, oraz Activity
, które ma się otworzyć, gdy użytkownik kliknie powiadomienie. Jeśli działania nie są 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();
Domyślnie opcja wyświetlania elementów sterowania multimediami w powiadomieniach i na ekranie blokady jest włączona. Można ją wyłączyć, wywołując funkcję setNotificationOptions
z wartością null w parametrze CastMediaOptions.Builder
.
Obecnie funkcja ekranu blokady jest włączona, o ile 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 nadawcza odtwarza strumień na żywo z filmem lub dźwiękiem, SDK automatycznie wyświetla przycisk odtwarzania/wstrzymywania zamiast przycisku odtwarzania/wstrzymywania w elementach sterujących powiadomienia, ale nie na ekranie blokady.
Uwaga: aby wyświetlić elementy sterujące na ekranie blokady na urządzeniach z Androidem w wersji wcześniejszej niż Lollipop,
RemoteMediaClient
automatycznie poprosi o skupienie dźwięku w Twoim imieniu.
Obsługuj błędy
Aplikacje nadawców muszą obsługiwać wszystkie wywołania zwrotne błędów i decydowować o najlepszym sposobie reakcji na każdym etapie cyklu życia Cast. Aplikacja może wyświetlić użytkownikowi okno z błędem lub może zdecydować się na zerwanie połączenia z Web Receiver.