Zintegruj przesyłanie z aplikacją na Androida

Z tego przewodnika dla programistów dowiesz się, jak dodać obsługę Google Cast do aplikacji nadawcy na Androida za pomocą pakietu Android Sender SDK.

Urządzenie mobilne lub laptop to nadawca , który steruje odtwarzaniem, a urządzenie przesyłające Google Cast to odbiornik , który wyświetla treści na telewizorze.

Platforma nadawcy odnosi się do binarnej biblioteki klas Cast i powiązanych z nią zasobów, które są dostępne w czasie działania aplikacji na nadawcy. Aplikacja nadawcy lub aplikacja Cast to aplikacja działająca również na nadawcy. Aplikacja Web Receiver to aplikacja HTML działająca na urządzeniu obsługującym Cast.

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

Przepływ aplikacji

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

  • Platforma Cast automatycznie rozpoczyna MediaRouter wykrywanie urządzeń na podstawie cyklu życia Activity.
  • Gdy użytkownik kliknie przycisk Cast, platforma wyświetli okno Cast z listą wykrytych urządzeń Cast.
  • Gdy użytkownik wybierze urządzenie przesyłające, platforma spróbuje uruchomić na nim aplikację Web Receiver.
  • Platforma wywoła wywołania zwrotne w aplikacji nadawcy, aby potwierdzić, że aplikacja Web Receiver została uruchomiona.
  • Platforma utworzy kanał komunikacji między aplikacjami nadawcy i Web Receiver.
  • Platforma używa kanału komunikacji do wczytywania i sterowania odtwarzaniem multimediów w Web Receiver.
  • Platforma synchronizuje stan odtwarzania multimediów między nadawcą a Web Receiver: gdy użytkownik wykonuje działania w interfejsie nadawcy, platforma przekazuje te żądania sterowania multimediami do Web Receiver, a gdy Web Receiver wysyła aktualizacje stanu multimediów, platforma aktualizuje stan interfejsu nadawcy.
  • Gdy użytkownik kliknie przycisk Cast, aby odłączyć się od urządzenia przesyłającego, platforma odłączy aplikację nadawcy od Web Receiver.

Pełną listę wszystkich klas, metod i zdarzeń w pakiecie Google Cast Android SDK znajdziesz w dokumentacji interfejsu Google Cast Sender API na Androida. W kolejnych sekcjach opisujemy, jak dodać Cast do aplikacji na Androida.

Konfigurowanie pliku manifestu Androida

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

uses-sdk

Ustaw minimalny i docelowy poziom interfejsu Android API obsługiwany przez pakiet Cast SDK. Obecnie minimalny poziom to API 23, a docelowy to API 34.

<uses-sdk
        android:minSdkVersion="23"
        android:targetSdkVersion="34" />

android:theme

Ustaw motyw aplikacji na podstawie minimalnej wersji pakietu Android SDK. Jeśli na przykład nie implementujesz własnego motywu, w przypadku kierowania na minimalną wersję pakietu Android SDK starszą niż Lollipop użyj wariantu Theme.AppCompat.

<application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/Theme.AppCompat" >
       ...
</application>

Inicjowanie kontekstu Cast

Platforma ma globalny obiekt singleton CastContext, który koordynuje wszystkie interakcje platformy.

Aby udostępnić opcje potrzebne do zainicjowania CastContext singletonu, aplikacja musi zaimplementować OptionsProvider interfejs. OptionsProvider udostępnia instancję CastOptions która zawiera opcje wpływające na działanie platformy. Najważniejszy z nich to identyfikator aplikacji Web Receiver, który służy do filtrowania wyników wykrywania i uruchamiania aplikacji Web Receiver po rozpoczęciu sesji Cast.

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 nadawcy musisz zadeklarować pełną i jednoznaczną nazwę zaimplementowanego interfejsu OptionsProvider jako pole metadanych:

<application>
    ...
    <meta-data
        android:name=
            "com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
        android:value="com.foo.CastOptionsProvider" />
</application>

CastContext jest inicjowany leniwie, gdy wywoływana jest funkcja CastContext.getSharedInstance().

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ą projektu Cast:

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

  • Przycisk Cast: przycisk Cast jest widoczny niezależnie od dostępności urządzeń Cast. Gdy użytkownik po raz pierwszy kliknie przycisk Cast, wyświetli się okno Cast z listą wykrytych urządzeń. Gdy użytkownik kliknie przycisk Cast, gdy urządzenie jest połączone, wyświetlą się bieżące metadane multimediów (np. tytuł, nazwa studia nagrań i miniatura) lub użytkownik będzie mógł odłączyć się od urządzenia przesyłającego. „Przycisk Cast” jest czasami nazywany „ikoną Cast”.

  • Minikontroler: gdy użytkownik przesyła treści i opuści stronę z bieżącą treścią lub rozwinie kontroler do innego ekranu w aplikacji nadawcy, u dołu ekranu wyświetli się minikontroler, który umożliwi użytkownikowi wyświetlenie metadanych aktualnie przesyłanych multimediów i sterowanie odtwarzaniem.

  • Rozwinięty odtwarzacz: gdy użytkownik przesyła treści, a następnie kliknie powiadomienie o multimediach lub miniodtwarzacz, uruchomi się rozwinięty odtwarzacz, który wyświetli metadane aktualnie odtwarzanych multimediów i udostępni kilka przycisków do sterowania odtwarzaniem multimediów.

  • Powiadomienie: tylko Android. Gdy użytkownik przesyła treści i opuści aplikację nadawcy, wyświetli się powiadomienie o multimediach, które będzie zawierać metadane aktualnie przesyłanych multimediów i elementy sterujące odtwarzaniem.

  • Ekran blokady: tylko Android. Gdy użytkownik przesyła treści i przejdzie (lub urządzenie przekroczy limit czasu) do ekranu blokady, wyświetli się element sterujący multimediami na ekranie blokady, który będzie zawierać metadane aktualnie przesyłanych multimediów i elementy sterujące odtwarzaniem.

Z tego przewodnika dowiesz się, jak dodać te widżety do aplikacji.

Dodawanie przycisku Cast

Interfejsy Android MediaRouter API umożliwiają wyświetlanie i odtwarzanie multimediów na urządzeniach dodatkowych. Aplikacje na Androida, które korzystają z interfejsu MediaRouter API, powinny zawierać w interfejsie przycisk Cast, aby użytkownicy mogli wybrać trasę multimediów do odtwarzania multimediów na urządzeniu dodatkowym, takim jak urządzenie przesyłające.

Platforma bardzo ułatwia dodanie MediaRouteButton jako Cast button. Najpierw dodaj element menu lub MediaRouteButton w pliku XML , który definiuje menu, a następnie użyj CastButtonFactory , aby połączyć go z platformą.

// To add a Cast button, add the following snippet.
// menu.xml
<item
    android:id="@+id/media_route_menu_item"
    android:title="@string/media_route_menu_title"
    app:actionProviderClass="androidx.mediarouter.app.MediaRouteActionProvider"
    app:showAsAction="always" />
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 po FragmentActivity, możesz dodać MediaRouteButton do układu.

// activity_layout.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   android:layout_width="match_parent"
   android:layout_height="wrap_content"
   android:gravity="center_vertical"
   android:orientation="horizontal" >

   <androidx.mediarouter.app.MediaRouteButton
       android:id="@+id/media_route_button"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:layout_weight="1"
       android:mediaRouteTypes="user"
       android:visibility="gone" />

</LinearLayout>
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, przeczytaj artykuł Dostosowywanie przycisku Cast.

Konfigurowanie wykrywania urządzeń

Wykrywanie urządzeń jest w pełni zarządzane przez the CastContext. Podczas inicjowania CastContext aplikacja nadawcy określa identyfikator aplikacji Web Receiver i opcjonalnie może poprosić o filtrowanie przestrzeni nazw, ustawiając supportedNamespaces w CastOptions. CastContext zawiera wewnętrznie odniesienie do MediaRouter i rozpocznie proces wykrywania w tych przypadkach:

  • Na podstawie algorytmu, który ma na celu zrównoważenie opóźnienia wykrywania urządzeń i wykorzystania baterii, wykrywanie będzie czasami rozpoczynane automatycznie, gdy aplikacja nadawcy przejdzie na pierwszy plan.
  • Okno Cast jest otwarte.
  • Pakiet Cast SDK próbuje odzyskać sesję Cast.

Proces wykrywania zostanie zatrzymany, gdy okno Cast zostanie zamknięte lub aplikacja nadawcy przejdzie w tle.

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

Sesje są zarządzane przez klasę SessionManager, do której aplikacja może uzyskać dostęp za pomocą CastContext.getSessionManager(). Poszczególne sesje są reprezentowane przez podklasy klasy Session. Na przykład, CastSession reprezentuje sesje z urządzeniami Cast. Aplikacja może uzyskać dostęp do aktualnie aktywnej sesji Cast za pomocą SessionManager.getCurrentCastSession().

Aplikacja może używać klasy SessionManagerListener do monitorowania zdarzeń sesji, takich jak utworzenie, zawieszenie, wznowienie i zakończenie. Platforma automatycznie próbuje wznowić działanie po nieprawidłowym lub nagłym zakończeniu sesji.

Sesje są tworzone i zamykane automatycznie w odpowiedzi na gesty użytkownika w oknach MediaRouter.

Aby lepiej zrozumieć błędy uruchamiania Cast, aplikacje mogą używać CastContext#getCastReasonCodeForCastStatusCode(int) , aby przekonwertować błąd uruchamiania sesji na CastReasonCodes. Pamiętaj, że niektóre błędy uruchamiania sesji (np. CastReasonCodes#CAST_CANCELLED) są zamierzonym działaniem i nie powinny być rejestrowane jako błędy.

Jeśli chcesz być na bieżąco ze zmianami stanu sesji, możesz zaimplementować SessionManagerListener. Ten przykład nasłuchuje dostępności CastSession w Activity.

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ą przeniesienia odtwarzania, dzięki czemu użytkownicy mogą przenosić istniejące strumienie audio i wideo między urządzeniami za pomocą poleceń głosowych, aplikacji Google Home lub inteligentnych ekranów. Odtwarzanie multimediów zatrzymuje się na jednym urządzeniu (źródłowym) i jest kontynuowane na innym (docelowym). Każde urządzenie przesyłające z najnowszym oprogramowaniem układowym może służyć jako źródło lub miejsce docelowe w przypadku przeniesienia odtwarzania.

Aby uzyskać nowe urządzenie docelowe podczas przeniesienia odtwarzania lub rozszerzenia, zarejestruj Cast.Listener za pomocą CastSession#addCastListener. Następnie wywołaj CastSession#getCastDevice() podczas wywołania zwrotnego onDeviceNameChanged.

Więcej informacji znajdziesz w artykule Przeniesienie odtwarzania w Web Receiver.

Automatyczne ponowne łączenie

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

  • Odzyskiwanie po tymczasowej utracie połączenia Wi-Fi
  • Odzyskiwanie po uśpieniu urządzenia
  • Odzyskiwanie po przejściu aplikacji w tle
  • Odzyskiwanie po awarii aplikacji

Ta usługa jest domyślnie włączona i można ją wyłączyć w CastOptions.Builder.

Jeśli w pliku Gradle włączysz automatyczne scalanie, ta usługa może zostać automatycznie scalona z plikiem manifestu aplikacji.

Platforma uruchomi usługę, gdy będzie aktywna sesja multimediów, i zatrzyma ją, gdy sesja się zakończy.

Jak działa sterowanie multimediami

Platforma Cast wycofuje klasę RemoteMediaPlayer z Cast 2.x na rzecz nowej klasy RemoteMediaClient, która zapewnia te same funkcje w zestawie wygodniejszych interfejsów API i eliminuje konieczność przekazywania GoogleApiClient.

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

Wszystkie metody RemoteMediaClient, które wysyłają żądania do Web Receiver, zwracają obiekt PendingResult, który można wykorzystać do śledzenia tego żądania.

Oczekuje się, że instancja RemoteMediaClient może być współdzielona przez wiele części aplikacji, a nawet przez niektóre wewnętrzne komponenty platformy, takie jak trwałe miniodtwarzacze i usługa powiadomień. W tym celu ta instancja obsługuje rejestrację wielu instancji RemoteMediaClient.Listener.

Ustawianie metadanych multimediów

Klasa MediaMetadata reprezentuje informacje o elemencie multimedialnym, który chcesz przesłać. Ten przykład tworzy nową instancję MediaMetadata filmu i ustawia tytuł, podtytuł oraz 2 obrazy.

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 artykule Wybieranie obrazów.

Wczytywanie multimediów

Aplikacja może wczytać multimedia, jak pokazano w tym kodzie. Najpierw użyj MediaInfo.Builder z metadanymi multimediów, aby utworzyć instancję MediaInfo. Pobierz RemoteMediaClient z bieżącego CastSession, a następnie wczytaj MediaInfo do tego RemoteMediaClient. Użyj RemoteMediaClient, aby odtwarzać, wstrzymywać i sterować aplikacją odtwarzacza multimediów działającą w Web Receiver.

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 multimediów.

Format wideo 4K

Aby sprawdzić, w jakim formacie jest film, użyj getVideoInfo() w MediaStatus, aby uzyskać bieżącą instancję VideoInfo. Ta instancja zawiera typ formatu HDR TV oraz wysokość i szerokość wyświetlacza w pikselach. Warianty formatu 4K są oznaczone stałymi HDR_TYPE_*.

Powiadomienia zdalnego sterowania na wielu urządzeniach

Gdy użytkownik przesyła treści, inne urządzenia z Androidem w tej samej sieci otrzymają powiadomienie, które umożliwi im sterowanie odtwarzaniem. Każdy, kto otrzymuje takie powiadomienia, może je wyłączyć na swoim urządzeniu w aplikacji Ustawienia w sekcji Google > Google Cast > Pokaż powiadomienia zdalnego sterowania. (Powiadomienia zawierają skrót do aplikacji Ustawienia). Więcej informacji znajdziesz w artykule Powiadomienia zdalnego sterowania Cast.

Dodaj minikontroler

Zgodnie z listą kontrolną projektu Cast, aplikacja nadawcy powinna udostępniać trwały element sterujący, czyli minikontroler, który powinien się pojawiać, gdy użytkownik opuści stronę z treścią i przejdzie do innej części aplikacji nadawcy. Minikontroler przypomina użytkownikowi o bieżącej sesji Cast. Klikając miniodtwarzacz, użytkownik może wrócić do widoku rozwiniętego odtwarzacza na pełnym ekranie.

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

<fragment
    android:id="@+id/castMiniController"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:layout_alignParentBottom="true"
    android:visibility="gone"
    class="com.google.android.gms.cast.framework.media.widget.MiniControllerFragment" />

Gdy aplikacja nadawcy odtwarza strumień audio lub wideo w ramach transmisji na żywo, pakiet SDK automatycznie wyświetla przycisk odtwarzania/zatrzymania zamiast przycisku odtwarzania/pauzy w miniodtwarzaczu.

Aby ustawić wygląd tekstu tytułu i podtytułu tego widoku niestandardowego, oraz wybrać przyciski, przeczytaj artykuł Dostosowywanie miniodtwarzacza.

Dodawanie rozwiniętego odtwarzacza

Lista kontrolna projektu Google Cast wymaga, aby aplikacja nadawcy udostępniała rozwinięty odtwarzacz przesyłanych multimediów. Rozwinięty kontroler to wersja minikontrolera na pełnym ekranie.

Pakiet Cast SDK udostępnia widżet rozwiniętego odtwarzacza o nazwie ExpandedControllerActivity. Jest to klasa abstrakcyjna, którą musisz podklasować, aby dodać przycisk Cast.

Najpierw utwórz nowy plik zasobu menu dla rozwiniętego odtwarzacza, aby udostępnić przycisk Cast:

<menu xmlns:android="http://schemas.android.com/apk/res/android"
      xmlns:app="http://schemas.android.com/apk/res-auto">

    <item
            android:id="@+id/media_route_menu_item"
            android:title="@string/media_route_menu_title"
            app:actionProviderClass="androidx.mediarouter.app.MediaRouteActionProvider"
            app:showAsAction="always"/>

</menu>

Utwórz nową klasę, która rozszerza ExpandedControllerActivity.

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ć aktywność docelową 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ę loadRemoteMedia w LocalPlayerActivity, aby wyświetlać nową aktywność po wczytaniu multimediów zdalnych:

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 nadawcy odtwarza strumień audio lub wideo na żywo, pakiet SDK automatycznie wyświetla przycisk odtwarzania/zatrzymania zamiast przycisku odtwarzania/pauzy w rozwiniętym odtwarzaczu.

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

Sterowanie głośnością

Platforma automatycznie zarządza głośnością aplikacji nadawcy. Automatycznie synchronizuje aplikacje nadawcy i Web Receiver, aby interfejs nadawcy zawsze zgłaszał głośność określoną przez Web Receiver.

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

W Androidzie przyciski fizyczne na urządzeniu nadawcy mogą domyślnie służyć do zmiany głośności sesji Cast w Web Receiver na każdym urządzeniu z Jelly Bean lub nowszym.

Sterowanie głośnością za pomocą przycisków fizycznych w wersjach starszych niż Jelly Bean

Aby używać fizycznych przycisków głośności do sterowania głośnością urządzenia Web Receiver na urządzeniach z Androidem starszych niż Jelly Bean, aplikacja nadawcy powinna zastąpić dispatchKeyEvent w swoich aktywnościach i wywołać CastContext.onDispatchVolumeKeyEventBeforeJellyBean():

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 powiadomienia i ekranu blokady

Zgodnie z listą kontrolną projektu Google Cast aplikacja nadawcy musi zaimplementować elementy sterujące multimediami w powiadomieniu i na ekranie blokady, gdy nadawca przesyła treści, ale aplikacja nadawcy nie jest aktywna. Platforma udostępnia MediaNotificationService i MediaIntentReceiver , które pomagają aplikacji nadawcy tworzyć elementy sterujące multimediami w powiadomieniu i na ekranie blokady.

MediaNotificationService działa, gdy nadawca przesyła treści, i wyświetla powiadomienie z miniaturą obrazu oraz informacjami o aktualnie przesyłanym elemencie, przyciskiem odtwarzania/pauzy i przyciskiem zatrzymania.

MediaIntentReceiver to BroadcastReceiver, który obsługuje działania użytkownika w powiadomieniu.

Aplikacja może skonfigurować powiadomienia i sterowanie multimediami z poziomu ekranu blokady za pomocą NotificationOptions. Aplikacja może skonfigurować, które przyciski sterujące mają się wyświetlać w powiadomieniu, oraz którą Activity ma się otworzyć, gdy użytkownik kliknie powiadomienie. Jeśli działania nie zostaną wyraźnie określone, zostaną użyte wartości domyślne: MediaIntentReceiver.ACTION_TOGGLE_PLAYBACK i MediaIntentReceiver.ACTION_STOP_CASTING.

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 elementów sterujących multimediami w powiadomieniu i na ekranie blokady jest domyślnie włączone i można je wyłączyć, wywołując setNotificationOptions z wartością null w CastMediaOptions.Builder. Obecnie funkcja ekranu blokady jest włączona, dopóki włączone są powiadomienia.

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 nadawcy odtwarza strumień audio lub wideo na żywo, pakiet SDK automatycznie wyświetla przycisk odtwarzania/zatrzymania zamiast przycisku odtwarzania/pauzy w elemencie sterującym powiadomienia, ale nie w elemencie sterującym ekranu blokady.

Uwaga: aby wyświetlać elementy sterujące na ekranie blokady na urządzeniach starszych niż Lollipop, RemoteMediaClient automatycznie poprosi o aktywność audio w Twoim imieniu.

Obsługiwanie błędów

Bardzo ważne jest, aby aplikacje nadawcy obsługiwały wszystkie wywołania zwrotne błędów i decydowały o najlepszej reakcji na każdym etapie cyklu życia Cast. Aplikacja może wyświetlać użytkownikowi okna dialogowe z błędami lub zdecydować się na zerwanie połączenia z Web Receiver.