Zintegruj przesyłanie z aplikacją na Androida

Ten przewodnik dla programistów zawiera opis dodawania obsługi Google Cast do aplikacji wysyłającej 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óry wyświetla treści na telewizorze.

Platforma nadawcy to binarna biblioteka klas Cast i powiązane z nią zasoby dostępne w czasie działania na urządzeniu wysyłającym. Aplikacja nadawcy lub aplikacja Cast to aplikacja działająca również na urządzeniu nadawcy. Aplikacja Web Receiver to aplikacja HTML działająca na urządzeniu obsługującym Cast.

Platforma nadawcy korzysta z asynchronicznego wywołania zwrotnego, aby informować aplikację nadawcy o zdarzeniach i przechodzić między różnymi stanami cyklu życia aplikacji Cast.

Przepływ w aplikacji

Poniższe kroki opisują typowy ogólny przepływ wykonywania w aplikacji nadawcy na Androida:

  • Platforma Cast automatycznie rozpoczyna wykrywanie urządzeń na podstawie cyklu życia Activity.MediaRouter
  • Gdy użytkownik kliknie przycisk przesyłania, platforma wyświetli okno przesyłania z listą wykrytych urządzeń obsługujących przesyłanie.
  • Gdy użytkownik wybierze urządzenie przesyłające, platforma spróbuje uruchomić na nim aplikację Web Receiver.
  • Platforma wywołuje wywołania zwrotne w aplikacji nadawcy, aby potwierdzić uruchomienie aplikacji Web Receiver.
  • Framework tworzy kanał komunikacji między aplikacjami nadawcy i odbiornika internetowego.
  • Platforma korzysta z kanału komunikacji do wczytywania multimediów i sterowania ich odtwarzaniem na odbiorniku internetowym.
  • Platforma synchronizuje stan odtwarzania multimediów między nadawcą a odbiornikiem internetowym: gdy użytkownik wykonuje działania w interfejsie nadawcy, platforma przekazuje te żądania 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 przesyłania, aby odłączyć się od urządzenia Cast, platforma odłączy aplikację wysyłającą od odbiornika internetowego.

Pełną listę wszystkich klas, metod i zdarzeń w pakiecie Google Cast SDK na Androida znajdziesz w przewodniku po interfejsie Google Cast Sender API na Androida. W kolejnych sekcjach znajdziesz instrukcje dodawania Cast do aplikacji na Androida.

Konfigurowanie pliku manifestu Androida

W pliku AndroidManifest.xml aplikacji musisz skonfigurować te elementy pakietu Cast SDK:

uses-sdk

Ustaw minimalny i docelowy poziom interfejsu API Androida 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 np. nie wdrażasz własnego motywu, w przypadku kierowania na minimalną wersję pakietu SDK Androida 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.

Aplikacja musi implementować interfejs OptionsProvider , aby udostępniać opcje potrzebne do zainicjowania pojedynczego obiektu CastContext. OptionsProvider udostępnia instancję CastOptions zawierającą opcje, które wpływają na działanie platformy. Najważniejszy z nich to identyfikator aplikacji odbiornika internetowego, który służy do filtrowania wyników wykrywania i uruchamiania aplikacji odbiornika internetowego po rozpoczęciu sesji Cast.

Kotlin
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
    }
}
Java
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 wysyłającej musisz zadeklarować pełną 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 z opóźnieniem, gdy wywoływana jest funkcja CastContext.getSharedInstance().

Kotlin
class MyActivity : FragmentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        val castContext = CastContext.getSharedInstance(this)
    }
}
Java
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ą dotyczącą projektowania Cast:

  • Nakładka wprowadzająca: platforma udostępnia niestandardowy widok, IntroductoryOverlay, który jest wyświetlany użytkownikowi, aby zwrócić jego uwagę na przycisk Cast za pierwszym razem, gdy odbiornik jest dostępny. Aplikacja nadawcy może dostosować tekst i pozycję tekstu tytułu.

  • Przycisk przesyłania: przycisk przesyłania 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, w którym będą widoczne wykryte urządzenia. 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 pojawi się możliwość odłączenia się od urządzenia przesyłającego. „Przycisk przesyłania” jest czasami nazywany „ikoną przesyłania”.

  • Mini Controller: gdy użytkownik przesyła treści i opuści bieżącą stronę z treściami lub rozszerzony kontroler, aby przejść do innego ekranu w aplikacji nadawcy, u dołu ekranu wyświetla się Mini Controller, który umożliwia użytkownikowi wyświetlanie metadanych przesyłanych obecnie multimediów i sterowanie odtwarzaniem.

  • Rozwinięty kontroler: gdy użytkownik przesyła treści, po kliknięciu powiadomienia o multimediach lub minikontrolera uruchamia się rozwinięty kontroler, który wyświetla metadane aktualnie odtwarzanych multimediów i zawiera kilka przycisków do sterowania odtwarzaniem.

  • Powiadomienie: tylko Android. Gdy użytkownik przesyła treści i przejdzie z aplikacji wysyłającej do innej aplikacji, pojawi 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 do ekranu blokady (lub gdy urządzenie przejdzie w stan uśpienia), 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.

W tym przewodniku znajdziesz opis sposobu dodawania tych widżetów do aplikacji.

Dodawanie przycisku Cast

Interfejsy API AndroidaMediaRouter umożliwiają wyświetlanie i odtwarzanie multimediów na urządzeniach dodatkowych. Aplikacje na Androida, które korzystają z interfejsu MediaRouter API, powinny zawierać przycisk Cast jako element interfejsu użytkownika, aby umożliwić użytkownikom wybór trasy multimediów do odtwarzania ich na urządzeniu dodatkowym, takim jak urządzenie Cast.

Framework bardzo ułatwia dodawanie elementu MediaRouteButton jako Cast button. Najpierw dodaj pozycję menu lub MediaRouteButton w pliku XML, który definiuje menu, i użyj CastButtonFactory, aby połączyć ją 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" />
Kotlin
// 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
}
Java
// 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 z FragmentActivity, możesz dodać do układu element 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>
Kotlin
// 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)
}
Java
// MyActivity.java
@Override
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   setContentView(R.layout.activity_layout);

   mMediaRouteButton = (MediaRouteButton) findViewById(R.id.media_route_button);
   CastButtonFactory.setUpMediaRouteButton(getApplicationContext(), mMediaRouteButton);

   mCastContext = CastContext.getSharedInstance(this);
}

Aby ustawić wygląd przycisku Cast za pomocą motywu, zapoznaj się z artykułem Dostosowywanie przycisku Cast.

Konfigurowanie wykrywania urządzeń

Wykrywanie urządzeń jest w pełni zarządzane przez CastContext. Podczas inicjowania CastContext aplikacja wysyłająca określa identyfikator aplikacji odbiornika internetowego i może opcjonalnie zażądać filtrowania przestrzeni nazw, ustawiając supportedNamespacesCastOptions. CastContext zawiera odwołanie do MediaRouter i rozpocznie proces wykrywania w tych warunkach:

  • Wykrywanie będzie czasami uruchamiane automatycznie, gdy aplikacja nadawcy przejdzie na pierwszy plan. Dzieje się tak na podstawie algorytmu, który ma na celu zrównoważenie opóźnienia wykrywania urządzenia i zużycia baterii.
  • Otworzy się okno Cast.
  • Pakiet Cast SDK próbuje odzyskać sesję przesyłania.

Proces wykrywania zostanie zatrzymany, gdy okno przesyłania zostanie zamknięte lub aplikacja wysyłająca przejdzie w tle.

Kotlin
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
    }
}
Java
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 do) aplikacji Web Receiver, połączenia z tą aplikacją i inicjowania kanału sterowania multimediami. Więcej informacji o sesjach Cast i cyklu życia odbiornika internetowego znajdziesz w przewodniku po cyklu życia aplikacji.

Sesjami zarządza klasa SessionManager, do której aplikacja ma dostęp za pomocą CastContext.getSessionManager(). Poszczególne sesje są reprezentowane przez podklasy klasy Session. Na przykład CastSession oznacza sesje na urządzeniach przesyłających. Aplikacja może uzyskać dostęp do aktualnie aktywnej sesji Cast za pomocą interfejsu SessionManager.getCurrentCastSession().

Aplikacja może używać klasy SessionManagerListener do monitorowania zdarzeń sesji, takich jak tworzenie, zawieszanie, wznawianie i zakończenie. Platforma automatycznie próbuje wznowić działanie po nieprawidłowym lub nagłym zakończeniu, gdy sesja była aktywna.

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) do przekształcania błędu uruchamiania sesji w CastReasonCodes. Pamiętaj, że niektóre błędy rozpoczęcia sesji (np. CastReasonCodes#CAST_CANCELLED) są zamierzonym działaniem i nie powinny być rejestrowane jako błędy.

Jeśli chcesz otrzymywać powiadomienia o zmianach stanu sesji, możesz wdrożyć SessionManagerListener. W tym przykładzie sprawdzana jest dostępność elementu CastSessionActivity.

Kotlin
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)
    }
}
Java
public class MyActivity extends Activity {
    private CastContext mCastContext;
    private CastSession mCastSession;
    private SessionManager mSessionManager;
    private SessionManagerListener<CastSession> mSessionManagerListener =
            new SessionManagerListenerImpl();

    private class SessionManagerListenerImpl implements SessionManagerListener<CastSession> {
        @Override
        public void onSessionStarting(CastSession session) {}
        @Override
        public void onSessionStarted(CastSession session, String sessionId) {
            invalidateOptionsMenu();
        }
        @Override
        public void onSessionStartFailed(CastSession session, int error) {
            int castReasonCode = mCastContext.getCastReasonCodeForCastStatusCode(error);
            // Handle error
        }
        @Override
        public void onSessionSuspended(CastSession session, int reason) {}
        @Override
        public void onSessionResuming(CastSession session, String sessionId) {}
        @Override
        public void onSessionResumed(CastSession session, boolean wasSuspended) {
            invalidateOptionsMenu();
        }
        @Override
        public void onSessionResumeFailed(CastSession session, int error) {}
        @Override
        public void onSessionEnding(CastSession session) {}
        @Override
        public void onSessionEnded(CastSession session, int error) {
            finish();
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mCastContext = CastContext.getSharedInstance(this);
        mSessionManager = mCastContext.getSessionManager();
        mSessionManager.addSessionManagerListener(mSessionManagerListener, CastSession.class);
    }

    @Override
    protected void onResume() {
        super.onResume();
        mCastSession = mSessionManager.getCurrentCastSession();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mSessionManager.removeSessionManagerListener(mSessionManagerListener, CastSession.class);
    }
}

Przeniesienie odtwarzania

Zachowanie stanu sesji jest podstawą przenoszenia strumieni, 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 Cast z najnowszym oprogramowaniem może być źródłem lub miejscem docelowym podczas przesyłania strumieniowego.

Aby uzyskać nowe urządzenie docelowe podczas przenoszenia lub rozszerzania strumienia, zarejestruj Cast.Listener za pomocą CastSession#addCastListener. Następnie zadzwońCastSession#getCastDevice() podczas onDeviceNameChanged oddzwaniania.

Więcej informacji znajdziesz w artykule Przesyłanie strumieniowe na odbiorniku internetowym.

Automatyczne ponowne łączenie

Platforma udostępnia ReconnectionService, które aplikacja wysyłająca może włączyć, aby obsługiwać ponowne łączenie w wielu subtelnych przypadkach brzegowych, takich jak:

  • Przywracanie działania po tymczasowej utracie połączenia Wi-Fi
  • Przywracanie działania po przejściu urządzenia w tryb uśpienia
  • Przywracanie aplikacji po przejściu do tła
  • Przywracanie po awarii aplikacji

Ta usługa jest domyślnie włączona. Możesz ją wyłączyć w sekcji CastOptions.Builder.

Tę usługę można automatycznie scalić z manifestem aplikacji, jeśli w pliku Gradle włączono automatyczne scalanie.

Platforma uruchomi usługę, gdy będzie aktywna sesja multimediów, i zatrzyma ją, gdy sesja się zakoń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 eliminuje konieczność przekazywania obiektu GoogleApiClient.

Gdy aplikacja nawiąże połączenieCastSession z aplikacją odbiornika internetowego obsługującą przestrzeń nazw multimediów, platforma automatycznie utworzy instancjęRemoteMediaClient. Aplikacja może uzyskać do niej dostęp, wywołując metodę getRemoteMediaClient() w instancji CastSession.

Wszystkie metody RemoteMediaClient, które wysyłają żądania do odbiornika internetowego, zwracają obiekt PendingResult, którego można używać do śledzenia tego żądania.

Oczekuje się, że instancja RemoteMediaClient może być udostępniana przez wiele części aplikacji, a także przez niektóre wewnętrzne komponenty platformy, takie jak trwałe mini kontroleryusługa powiadomień. W tym celu ta instancja obsługuje rejestrację wielu instancji RemoteMediaClient.Listener.

Ustawianie metadanych multimediów

Klasa MediaMetadata reprezentuje informacje o pliku multimedialnym, który chcesz przesłać. W tym przykładzie tworzona jest nowa instancja MediaMetadata filmu, a następnie ustawiane są tytuł, podtytuł i 2 obrazy.

Kotlin
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))))
Java
MediaMetadata movieMetadata = new MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE);

movieMetadata.putString(MediaMetadata.KEY_TITLE, mSelectedMedia.getTitle());
movieMetadata.putString(MediaMetadata.KEY_SUBTITLE, mSelectedMedia.getStudio());
movieMetadata.addImage(new WebImage(Uri.parse(mSelectedMedia.getImage(0))));
movieMetadata.addImage(new WebImage(Uri.parse(mSelectedMedia.getImage(1))));

Więcej informacji o używaniu obrazów z metadanymi multimediów znajdziesz w sekcji Wybór obrazu.

Wczytaj multimedia

Aplikacja może wczytać element multimedialny, jak pokazano w tym kodzie. Najpierw użyj MediaInfo.Builder z metadanymi multimediów, aby utworzyć instancję MediaInfo. Pobierz RemoteMediaClient z bieżącego CastSession, a następnie wczytaj do niego MediaInfo.RemoteMediaClient Używaj RemoteMediaClient do odtwarzania, wstrzymywania i innego sterowania aplikacją odtwarzacza multimediów działającą na odbiorniku internetowym.

Kotlin
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())
Java
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 multimedialnych.

Format wideo 4K

Aby sprawdzić format wideo, w którym jest Twoje medium, użyj getVideoInfo() w MediaStatus, aby uzyskać bieżącą instancję VideoInfo. Ta instancja zawiera typ formatu telewizora HDR oraz wysokość i szerokość ekranu 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żda osoba, której urządzenie otrzymuje takie powiadomienia, może je wyłączyć w aplikacji Ustawienia > Google > Google Cast > Wyświetlaj powiadomienia o pilocie. (Powiadomienia zawierają skrót do aplikacji Ustawienia). Więcej informacji znajdziesz w artykule Powiadomienia dotyczące pilota Cast.

Dodawanie minikontrolera

Zgodnie z listą kontrolną dotyczącą projektowania Cast aplikacja wysyłająca powinna udostępniać trwały element sterujący, czyli mini kontroler, który powinien pojawiać się, gdy użytkownik opuści stronę z bieżącymi treściami i przejdzie do innej części aplikacji wysyłającej. Mini kontroler przypomina użytkownikowi o bieżącej sesji Cast. Klikając mini kontroler, użytkownik może wrócić do widoku pełnoekranowego rozwiniętego kontrolera Cast.

Platforma udostępnia niestandardowy widok MiniControllerFragment, który możesz dodać do dołu pliku układu każdej aktywności, w której chcesz wyświetlać mini odtwarzacz.

<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 wysyłająca odtwarza transmisję na żywo wideo lub audio, pakiet SDK automatycznie wyświetla przycisk odtwarzania/zatrzymywania zamiast przycisku odtwarzania/pauzy w 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 projektowania Google Cast wymaga, aby aplikacja wysyłająca udostępniała rozwinięty kontroler przesyłanych multimediów. Rozwinięty kontroler to wersja mini kontrolera na pełnym ekranie.

Pakiet Cast SDK udostępnia widżet do rozszerzonego kontrolera o nazwie ExpandedControllerActivity. Jest to klasa abstrakcyjna, którą musisz rozszerzyć, 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ę, która rozszerza klasę ExpandedControllerActivity.

Kotlin
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
    }
}
Java
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ć docelową aktywność na nową aktywność:

Kotlin
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()
}
Java
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:

Kotlin
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()
    )
}
Java
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 wysyłająca odtwarza transmisję na żywo wideo lub audio, pakiet SDK automatycznie wyświetla przycisk odtwarzania/zatrzymywania zamiast przycisku odtwarzania/wstrzymywania na rozwiniętym kontrolerze.

Aby ustawić wygląd za pomocą motywów, wybrać przyciski do wyświetlania i dodać przyciski niestandardowe, zobacz Dostosowywanie rozwiniętego kontrolera.

Sterowanie głośnością

Platforma automatycznie zarządza głośnością aplikacji nadawcy. Automatycznie synchronizuje aplikacje nadawcy i odbiornika internetowego, dzięki czemu interfejs aplikacji nadawcy zawsze zgłasza głośność określoną przez odbiornik internetowy.

Sterowanie głośnością za pomocą przycisków fizycznych

Na Androidzie przyciski fizyczne na urządzeniu wysyłającym mogą być domyślnie używane do zmiany głośności sesji Cast na odbiorniku internetowym na każdym urządzeniu z Androidem w wersji Jelly Bean lub nowszej.

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 wysyłająca powinna zastąpić dispatchKeyEvent w swoich działaniach i wywołać CastContext.onDispatchVolumeKeyEventBeforeJellyBean():

Kotlin
class MyActivity : FragmentActivity() {
    override fun dispatchKeyEvent(event: KeyEvent): Boolean {
        return (CastContext.getSharedInstance(this)
            .onDispatchVolumeKeyEventBeforeJellyBean(event)
                || super.dispatchKeyEvent(event))
    }
}
Java
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

Tylko na Androidzie lista kontrolna projektu Google Cast wymaga, aby aplikacja wysyłająca wdrożyła sterowanie multimediami w powiadomieniu i na ekranie blokady, gdy wysyłający przesyła treści, ale aplikacja wysyłająca nie jest aktywna. Platforma udostępnia interfejsy MediaNotificationServiceMediaIntentReceiver, które pomagają aplikacji wysyłającej 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 bieżącym przesyłanym elemencie, przyciskiem odtwarzania/wstrzymywania i przyciskiem zatrzymania.

MediaIntentReceiver to BroadcastReceiver, który obsługuje działania użytkownika z poziomu powiadomienia.

Aplikacja może konfigurować powiadomienia i sterowanie multimediami na ekranie blokady za pomocą interfejsu NotificationOptions. Aplikacja może skonfigurować, jakie przyciski sterujące mają być wyświetlane w powiadomieniu, oraz określić, która Activity ma się otworzyć, gdy użytkownik kliknie powiadomienie. Jeśli działania nie zostaną podane wprost, zostaną użyte wartości domyślne: MediaIntentReceiver.ACTION_TOGGLE_PLAYBACKMediaIntentReceiver.ACTION_STOP_CASTING.

Kotlin
// 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()
Java
// Example showing 4 buttons: "rewind", "play/pause", "forward" and "stop casting".
List<String> buttonActions = new ArrayList<>();
buttonActions.add(MediaIntentReceiver.ACTION_REWIND);
buttonActions.add(MediaIntentReceiver.ACTION_TOGGLE_PLAYBACK);
buttonActions.add(MediaIntentReceiver.ACTION_FORWARD);
buttonActions.add(MediaIntentReceiver.ACTION_STOP_CASTING);

// Showing "play/pause" and "stop casting" in the compat view of the notification.
int[] compatButtonActionsIndices = new int[]{1, 3};

// Builds a notification with the above actions. Each tap on the "rewind" and "forward" buttons skips 30 seconds.
// Tapping on the notification opens an Activity with class VideoBrowserActivity.
NotificationOptions notificationOptions = new NotificationOptions.Builder()
    .setActions(buttonActions, compatButtonActionsIndices)
    .setSkipStepMs(30 * DateUtils.SECOND_IN_MILLIS)
    .setTargetActivityClassName(VideoBrowserActivity.class.getName())
    .build();

Wyświetlanie opcji sterowania multimediami w powiadomieniach i na ekranie blokady jest domyślnie włączone. Można je wyłączyć, wywołując funkcję setNotificationOptions z wartością null w CastMediaOptions.Builder. Obecnie funkcja ekranu blokady jest włączona, dopóki włączone są powiadomienia.

Kotlin
// ... 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()
Java
// ... 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 wysyłająca odtwarza transmisję na żywo wideo lub audio, pakiet SDK automatycznie wyświetla przycisk odtwarzania/zatrzymywania zamiast przycisku odtwarzania/wstrzymywania w powiadomieniu, ale nie na ekranie blokady.

Uwaga: aby wyświetlać elementy sterujące na ekranie blokady na urządzeniach z Androidem w wersji starszej niż Lollipop,RemoteMediaClient automatycznie poprosi o skupienie dźwięku w Twoim imieniu.

Obsługuj błędy

Aplikacje wysyłające muszą obsługiwać wszystkie wywołania zwrotne błędów i określać najlepszą odpowiedź na każdym etapie cyklu życia Cast. Aplikacja może wyświetlać użytkownikowi okna dialogowe z błędami lub zdecydować o zamknięciu połączenia z odbiornikiem internetowym.