W tym przewodniku dla programistów opisujemy, jak dodać obsługę Google Cast do aplikacji nadawcy na Androida za pomocą pakietu Android Sender SDK.
Urządzenie mobilne lub laptop jest nadawcą, który kontroluje odtwarzanie, a urządzenie Google Cast to odbiornik, który wyświetla treści na telewizorze.
Platforma nadawcy odnosi się do pliku binarnego biblioteki klas Cast i powiązanych z nim zasobów w czasie działania u nadawcy. Określenie aplikacja nadawcy lub aplikacja Cast odnosi się również do aplikacji uruchomionej u nadawcy. Aplikacja Web Receivedr odnosi się do aplikacji HTML uruchomionej na urządzeniu obsługującym Cast.
Platforma nadawcy korzysta z asynchronicznego wywołania zwrotnego, który informuje aplikację nadawcy o zdarzeniach i przechodzi między różnymi stanami cyklu życia aplikacji Cast.
Przepływ aplikacji
Poniżej opisujemy typowy ogólny proces wykonywania w przypadku aplikacji na Androida nadawcy:
- Platforma Cast automatycznie rozpoczyna wykrywanie urządzeń
MediaRouter
na podstawie cyklu życiaActivity
. - Gdy użytkownik kliknie przycisk Cast, platforma wyświetli okno przesyłania z listą wykrytych urządzeń przesyłających.
- Gdy użytkownik wybiera urządzenie przesyłające, platforma próbuje uruchomić na nim aplikację Web Odbiornik.
- Platforma wywołuje wywołania zwrotne w aplikacji nadawcy, aby potwierdzić, że aplikacja Web Receivedr została uruchomiona.
- Platforma tworzy kanał komunikacyjny między nadawcą a aplikacjami odbierającymi dane.
- Platforma wykorzystuje kanał komunikacyjny 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 działania w interfejsie użytkownika nadawcy, platforma przekazuje te żądania 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ć urządzenie przesyłające, platforma odłączy aplikację nadawcy od odbiornika internetowego.
Pełną listę wszystkich klas, metod i zdarzeń w pakiecie Google Cast SDK na Androida znajdziesz w dokumentacji interfejsu Google Cast Sender API na Androida. W poniższych sekcjach znajdziesz instrukcje dodawania przesyłania do aplikacji na Androida.
Skonfiguruj plik manifestu Androida
Plik AndroidManifest.xml Twojej aplikacji wymaga skonfigurowania tych elementów na potrzeby pakietu SDK Cast:
uses-sdk
Ustaw minimalne i docelowe poziomy interfejsu Android API obsługiwane przez pakiet SDK Cast. Obecnie minimalny poziom API to 21, a docelowy – poziom API 28.
<uses-sdk
android:minSdkVersion="21"
android:targetSdkVersion="28" />
android:theme
Ustaw motyw aplikacji na podstawie minimalnej wersji pakietu Android SDK. Jeśli na przykład nie implementujesz własnego motywu, użyj wariantu Theme.AppCompat
, gdy kierujesz minimalną wersję pakietu Android SDK na wersję starszą niż Lollipop.
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/Theme.AppCompat" >
...
</application>
Inicjowanie kontekstu przesyłania
Platforma zawiera globalny obiekt typu singleton CastContext
, który koordynuje wszystkie interakcje platformy.
Aplikacja musi zaimplementować interfejs OptionsProvider
, aby udostępniać opcje potrzebne do zainicjowania singletonu 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 Receivedr, który służy do filtrowania wyników wykrywania i uruchamiania aplikacji Web Receivedr 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; } }
W polu metadanych w pliku AndroidManifest.xml aplikacji nadawcy musisz zadeklarować w pełni i jednoznaczną nazwę wdrożonego elementu OptionsProvider
:
<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 Cast UX
Platforma Cast udostępnia widżety zgodne z listą kontrolną projektowania przesyłania:
Nakładka z wprowadzeniem: platforma udostępnia widok niestandardowy
IntroductoryOverlay
, który przy pierwszym udostępnieniu odbiornika zwraca uwagę użytkownika na przycisk przesyłania. Aplikacja Nadawca może dostosować tekst i jego pozycję.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, pojawi się okno przesyłania z listą wykrytych urządzeń. Gdy użytkownik kliknie przycisk Cast, gdy urządzenie jest podłączone, wyświetli 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 przesyłającego. „Przycisk Cast” jest czasem nazywany „ikoną Cast”.
Minikontroler: gdy użytkownik przesyła treści i opuści bieżącą stronę treści lub rozwinie kontroler na inny ekran w aplikacji nadawcy, u dołu ekranu pojawi się minikontroler, który umożliwia użytkownikowi przeglądanie aktualnie przesyłanych metadanych multimediów i sterowanie odtwarzaniem.
Rozwinięty kontroler: gdy użytkownik przesyła treści, a potem kliknie powiadomienie o multimediach lub minikontroler, uruchomi się rozwinięty kontroler, który wyświetli metadane odtwarzanych multimediów oraz kilka przycisków do sterowania odtwarzaniem multimediów.
Powiadomienie: tylko Android. Gdy użytkownik przesyła treści i wyjdzie z aplikacji nadawcy, pojawi się powiadomienie o multimediach, które pokazują aktualnie przesyłane metadane multimediów i elementy sterujące odtwarzaniem.
Ekran blokady: tylko Android. Gdy użytkownik przesyła treści i przejdzie (lub przekroczy limit czasu) na ekran blokady, pojawi się element sterujący na ekranie blokady, który pokazuje aktualnie przesyłane metadane multimediów oraz elementy sterujące odtwarzaniem.
W tym przewodniku znajdziesz opis sposobu dodawania tych widżetów do aplikacji.
Dodaj przycisk Cast
Interfejsy API Androida MediaRouter
umożliwiają wyświetlanie i odtwarzanie multimediów na urządzeniach dodatkowych.
Aplikacje na Androida korzystające z interfejsu MediaRouter
API powinny mieć w interfejsie przycisk Cast, aby umożliwić użytkownikom wybór trasy odtwarzania multimediów na urządzeniu dodatkowym, takim jak urządzenie przesyłające.
Platforma ułatwia dodawanie MediaRouteButton
jako Cast button
. Najpierw dodaj pozycję menu lub MediaRouteButton
w pliku XML, który określa Twoje menu, i użyj CastButtonFactory
, aby połączyć je 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; }
Następnie, jeśli Activity
dziedziczy z 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); }
Jeśli chcesz ustawić wygląd przycisku Cast za pomocą motywu, przeczytaj artykuł o dostosowywaniu przycisku Cast.
Skonfiguruj wykrywanie urządzeń
Wykrywaniem urządzeń w pełni zarządza CastContext
.
Podczas inicjowania CastContext aplikacja nadawcy określa identyfikator aplikacji Web Recipient i może opcjonalnie poprosić o filtrowanie przestrzeni nazw, ustawiając supportedNamespaces
w CastOptions
.
CastContext
odwołuje się wewnętrznie do MediaRouter
i rozpocznie proces wykrywania, gdy:
- Bazujący na algorytmie zaprojektowanym z myślą o zrównoważeniu czasu oczekiwania na wykrywanie urządzenia i wykorzystania baterii, wykrywanie czasami uruchamia się automatycznie, gdy aplikacja nadawcy działa na pierwszym planie.
- Okno przesyłania jest otwarte.
- Pakiet SDK Cast próbuje przywrócić sesję przesyłania.
Proces wykrywania zostanie zatrzymany po zamknięciu okna przesyłania lub gdy 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 SDK Cast przedstawia koncepcję sesji przesyłania, która łączy w sobie etapy łączenia się z urządzeniem, uruchamiania (lub dołączania) aplikacji internetowej odbiornika, łączenia się z nią i inicjowania kanału sterowania multimediami. Więcej informacji o sesjach przesyłania 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()
.
Pojedyncze sesje są reprezentowane przez podklasy Session
.
Na przykład CastSession
oznacza sesje z urządzeniami przesyłającymi. Aplikacja może uzyskać dostęp do obecnie aktywnej sesji przesyłania na stronie 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 nietypowym lub nagłym zakończeniu sesji, gdy sesja była aktywna.
Sesje są tworzone i usuwane automatycznie w odpowiedzi na gesty użytkownika w oknach MediaRouter
.
Aby lepiej zrozumieć błędy rozpoczynania przesyłania, aplikacje mogą użyć parametru CastContext#getCastReasonCodeForCastStatusCode(int)
do przekonwertowania błędu podczas rozpoczynania sesji na CastReasonCodes
.
Pamiętaj, że niektóre błędy rozpoczynania sesji (np. CastReasonCodes#CAST_CANCELLED
) są zamierzone i nie powinny być rejestrowane jako błędy.
Jeśli musisz wiedzieć o zmianach stanu sesji, możesz zaimplementować SessionManagerListener
. W tym przykładzie nasłuchuje się 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 } override fun onResume() { super.onResume() mCastSession = mSessionManager.currentCastSession mSessionManager.addSessionManagerListener(mSessionManagerListener, CastSession::class.java) } override fun onPause() { super.onPause() mSessionManager.removeSessionManagerListener(mSessionManagerListener, CastSession::class.java) mCastSession = null } }
public class MyActivity extends Activity { private CastContext mCastContext; private CastSession mCastSession; private SessionManager mSessionManager; private SessionManagerListener<CastSession> mSessionManagerListener = new SessionManagerListenerImpl(); private class SessionManagerListenerImpl implements SessionManagerListener<CastSession> { @Override public void onSessionStarting(CastSession session) {} @Override public void onSessionStarted(CastSession session, String sessionId) { invalidateOptionsMenu(); } @Override public void onSessionStartFailed(CastSession session, int error) { int castReasonCode = mCastContext.getCastReasonCodeForCastStatusCode(error); // Handle error } @Override public void onSessionSuspended(CastSession session, int reason) {} @Override public void onSessionResuming(CastSession session, String sessionId) {} @Override public void onSessionResumed(CastSession session, boolean wasSuspended) { invalidateOptionsMenu(); } @Override public void onSessionResumeFailed(CastSession session, int error) {} @Override public void onSessionEnding(CastSession session) {} @Override public void onSessionEnded(CastSession session, int error) { finish(); } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mCastContext = CastContext.getSharedInstance(this); mSessionManager = mCastContext.getSessionManager(); } @Override protected void onResume() { super.onResume(); mCastSession = mSessionManager.getCurrentCastSession(); mSessionManager.addSessionManagerListener(mSessionManagerListener, CastSession.class); } @Override protected void onPause() { super.onPause(); mSessionManager.removeSessionManagerListener(mSessionManagerListener, CastSession.class); mCastSession = null; } }
Przenoszenie strumienia
Zachowanie stanu sesji jest podstawą przesyłania strumienia, w ramach którego użytkownicy mogą przenosić 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ło), a następnie jest kontynuowane na innym (miejscu docelowym). Każde urządzenie przesyłające z najnowszym oprogramowaniem może służyć jako źródło lub miejsca docelowe w przesyłaniu strumieniowym.
Aby uzyskać nowe urządzenie docelowe podczas przenoszenia lub rozwijania strumienia, zarejestruj Cast.Listener
za pomocą CastSession#addCastListener
.
Następnie wywołaj metodę CastSession#getCastDevice()
podczas wywołania zwrotnego onDeviceNameChanged
.
Więcej informacji znajdziesz w sekcji Przenoszenie strumienia w internetowym odbiorniku.
Automatyczne ponowne połączenie
Platforma udostępnia obiekt ReconnectionService
, który aplikacja nadawcy może włączyć, aby umożliwić ponowne łączenie w wielu subtelnych sytuacjach, takich jak:
- przywracanie działania po tymczasowej utracie połączenia z Wi-Fi,
- Regeneracja po uśpieniu urządzenia
- Przywróć działanie aplikacji po uruchomieniu w tle
- Przywracanie w przypadku awarii aplikacji
Ta usługa jest domyślnie włączona i można ją wyłączyć tutaj: CastOptions.Builder
.
Ta usługa może zostać automatycznie scalona z plikiem manifestu aplikacji, jeśli w pliku Gradle jest włączone automatyczne scalanie.
Platforma uruchomi usługę w momencie wystąpienia sesji multimediów i zatrzyma ją po jej zakończeniu.
Jak działa Sterowanie multimediami
Platforma Cast wycofuje klasę RemoteMediaPlayer
z Cast 2.x na rzecz nowej klasy RemoteMediaClient
, która ma tę samą funkcjonalność w zestawie bardziej wygodnych interfejsów API i pozwala uniknąć przekazywania klienta GoogleApiClient.
Gdy aplikacja utworzy obiekt CastSession
z aplikacją Web Received, która obsługuje przestrzeń nazw multimediów, platforma automatycznie utworzy wystąpienie RemoteMediaClient
. Aplikacja będzie mogła uzyskać do niego dostęp, wywołując metodę getRemoteMediaClient()
w instancji CastSession
.
Wszystkie metody obiektu RemoteMediaClient
, które wysyłają żądania do odbiornika internetowego, zwracają obiekt PendingResult, który może służyć do śledzenia tego żądania.
Instancja RemoteMediaClient
może być współużytkowana przez wiele części aplikacji, a w rzeczywistości niektóre wewnętrzne komponenty platformy, takie jak stałe minikontrolery i usługa powiadomień.
Dlatego ta instancja obsługuje rejestrację wielu instancji RemoteMediaClient.Listener
.
Ustaw metadane multimediów
Klasa MediaMetadata
zawiera informacje o elemencie multimedialnym, który chcesz przesłać. Poniższy przykład tworzy nową instancję MediaMetadata filmu i ustawia tytuł, podtytuł i 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 o używaniu obrazów z metadanymi multimediów znajdziesz w sekcji Wybór obrazu.
Wczytaj multimedia
Aplikacja może wczytać element multimedialny, tak jak w tym kodzie. Najpierw użyj parametru MediaInfo.Builder
z metadanymi multimediów, aby utworzyć instancję MediaInfo
. Pobierz
RemoteMediaClient
bieżący CastSession
, a następnie wczytaj do niego MediaInfo
.RemoteMediaClient
. Użyj RemoteMediaClient
, aby włączać i wstrzymywać odtwarzanie oraz sterować aplikacjami odtwarzacza multimediów działającymi 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());
Zapoznaj się też z sekcją o używaniu ścieżek multimedialnych.
Format wideo 4K
Aby sprawdzić format multimediów, użyj wartości getVideoInfo()
w polu MediaStatus, aby pobrać bieżące wystąpienie komponentu 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_*
.
Zdalne sterowanie powiadomieniami na wielu urządzeniach
Gdy użytkownik przesyła treści, inne urządzenia z Androidem w tej samej sieci otrzymają powiadomienie, że mogą sterować odtwarzaniem. Każdy, którego urządzenie otrzymuje takie powiadomienia, może je na nim wyłączyć w aplikacji Ustawienia, klikając Google > Google Cast > Pokazuj powiadomienia pilota. (Powiadomienia zawierają skrót do aplikacji Ustawienia). Więcej informacji znajdziesz w artykule Powiadomienia o pilocie zdalnego sterowania.
Dodaj minikontroler
Zgodnie z listą kontrolną projektu przesyłania aplikacja nadawcy powinna zapewniać trwały element sterujący nazywany minikontrolerem, który powinien się pojawiać, gdy użytkownik przechodzi ze strony bieżącej treści do innej części aplikacji nadawcy. Minikontroler przypomina użytkownikowi o bieżącej sesji przesyłania. Po dotknięciu minikontrolera użytkownik może powrócić do rozwiniętego widoku kontrolera na pełnym ekranie przesyłania.
Platforma udostępnia widok niestandardowy MiniControllerFragment, który można dodać na dole pliku układu każdego działania, w którym chcesz pokazać minikontroler.
<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ę wideo lub audio na żywo, pakiet SDK automatycznie wyświetla przycisk odtwarzania/wstrzymywania na minikontrolerze.
Aby ustawić wygląd tekstu tytułu i podtytułu tego widoku niestandardowego oraz wybrać przyciski, zapoznaj się z sekcją Dostosowywanie minikontrolera.
Dodaj rozwinięty kontroler
Lista kontrolna projektu Google Cast wymaga, aby aplikacja nadawcy miała rozwinięty kontroler do przesyłania multimediów. Rozszerzony kontroler to pełnoekranowa wersja minikontrolera.
Pakiet SDK Cast udostępnia widżet rozwiniętego kontrolera o nazwie ExpandedControllerActivity
.
To jest klasa abstrakcyjna, którą musisz dodać do podklasy, aby dodać przycisk Cast.
Najpierw utwórz nowy plik zasobów menu dla rozwiniętego 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 nowe zajęcia, które powiększą się o 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 manifeście 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ć nową aktywność jako docelową 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 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ę wideo lub audio na żywo, pakiet SDK automatycznie wyświetla przycisk odtwarzania/wstrzymywania na rozwiniętym kontrolerze.
Jeśli chcesz ustawić wygląd za pomocą motywów, wybrać przyciski, które mają być wyświetlane, i dodać przyciski niestandardowe, przeczytaj informacje o dostosowywaniu rozwiniętego kontrolera.
Sterowanie głośnością
Platforma automatycznie zarządza woluminem w aplikacji nadawcy. Platforma automatycznie synchronizuje aplikacje nadawcy i odbiornika internetowego, dzięki czemu interfejs użytkownika nadawcy zawsze raportuje ilość określoną przez odbiornik internetowy.
Sterowanie głośnością za pomocą fizycznego przycisku
W Androidzie za pomocą fizycznych przycisków na urządzeniu nadawcy można domyślnie zmienić głośność sesji przesyłania w odbiorniku internetowym na każdym urządzeniu korzystającym z Jelly Bean lub nowszego.
Sterowanie głośnością za pomocą przycisków przed pojawieniem się 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ć parametr dispatchKeyEvent
w Aktywności 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 projektu Google Cast wymaga, aby aplikacja nadawcy zaimplementowała elementy sterujące multimediami w powiadomieniu oraz na ekranie blokady, gdzie nadawca przesyła treści, ale aplikacja nadawcy nie jest skupiona na aplikacji. Platforma udostępnia MediaNotificationService
i MediaIntentReceiver
, dzięki którym aplikacja nadawcy może utworzyć elementy sterujące multimediami w powiadomieniu i na ekranie blokady.
Aplikacja MediaNotificationService
uruchamia się, gdy nadawca przesyła treści, i wyświetla powiadomienie z miniaturą obrazu i informacjami o aktualnie przesyłanym elemencie, przyciskami odtwarzania/wstrzymywania oraz przyciskami zatrzymywania.
MediaIntentReceiver
to obiekt BroadcastReceiver
, który obsługuje działania użytkownika z poziomu powiadomienia.
Twoja aplikacja może skonfigurować sterowanie powiadomieniami i multimediami na ekranie blokady w NotificationOptions
.
Aplikacja może skonfigurować ustawienia przycisków sterujących, które będą wyświetlane w powiadomieniu i których Activity
otwiera się po kliknięciu powiadomienia przez użytkownika. Jeśli działania nie zostały wyraźnie określone, używane są 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 na poziomie powiadomienia i ekranu blokady jest domyślnie włączone. Możesz je wyłączyć, wywołując metodę setNotificationOptions
z wartością null w polu CastMediaOptions.Builder
.
Obecnie funkcja ekranu blokady jest włączona, o ile powiadomienia są włączone.
// ... 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ę wideo lub audio na żywo, pakiet SDK automatycznie wyświetla przycisk odtwarzania/zatrzymania zamiast przycisku odtwarzania/wstrzymywania na panelu sterowania powiadomieniami, ale nie na ekranie blokady.
Uwaga: aby wyświetlać elementy sterujące ekranem blokady na urządzeniach starszych niż Lollipop, RemoteMediaClient
będzie automatycznie prosić o włączenie dźwięku w Twoim imieniu.
Obsługa błędów
Aplikacje nadawców muszą uwzględniać wszystkie wywołania zwrotne błędów i określać najlepszą odpowiedź na każdy etap cyklu życia przesyłania. Aplikacja może wyświetlać użytkownikowi okna z błędami lub może przerwać połączenie z odbiornikiem internetowym.