Zintegruj przesyłanie z aplikacją na Androida

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

Urządzenie mobilne lub laptop to nadawca, który steruje odtwarzaniem, 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 znajdujących się w czasie działania u nadawcy. Określenia aplikacja nadawcy lub aplikacja do przesyłania odnoszą się do aplikacji również uruchomionej u nadawcy. Aplikacja Web Receiver odnosi się do aplikacji HTML uruchomionej na urządzeniu obsługującym Cast.

Platforma nadawcy korzysta z asynchronicznego projektu wywołania zwrotnego, aby informować 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 aplikacji na Androida nadawcy:

  • Platforma Cast automatycznie uruchamia wykrywanie urządzeń MediaRouter na podstawie cyklu życia Activity.
  • Gdy użytkownik kliknie przycisk Cast, platforma wyświetli okno Cast z listą wykrytych urządzeń przesyłających.
  • Gdy użytkownik wybierze urządzenie przesyłające, platforma próbuje na nim uruchomić aplikację odbiornika internetowego.
  • Platforma wywołuje wywołania zwrotne w aplikacji nadawcy, aby potwierdzić, że aplikacja Web Receiver została uruchomiona.
  • Platforma tworzy kanał komunikacji między wysyłającym a aplikacjami odbiornika internetowego.
  • Platforma używa kanału komunikacji do wczytywania i kontrolowania odtwarzania multimediów 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 żądania sterowania multimediami do odbiornika internetowego, a gdy odbiornik internetowy wysyła aktualizacje stanu multimediów, platforma aktualizuje stan interfejsu nadawcy.
  • Gdy użytkownik kliknie przycisk Cast, by odłączyć urządzenie przesyłające, platforma odłączy aplikację wysyłającą od odbiornika internetowego.

Pełną listę wszystkich klas, metod i zdarzeń w pakiecie SDK Google Cast na Androida znajdziesz w materiałach referencyjnych dotyczących interfejsu Google Cast Sender API na Androida. W sekcjach poniżej znajdziesz instrukcje dodawania przesyłania do aplikacji na Androida.

Konfigurowanie pliku manifestu na Androida

Plik AndroidManifest.xml Twojej aplikacji wymaga skonfigurowania tych elementów na potrzeby pakietu Cast SDK:

uses-sdk

Ustaw minimalne i docelowe poziomy interfejsu Android API obsługiwane przez pakiet Cast SDK. Obecnie minimalnym poziomem API jest 21, a docelowym to poziom 28.

<uses-sdk
        android:minSdkVersion="21"
        android:targetSdkVersion="28" />

android:theme

Ustaw motyw aplikacji na podstawie minimalnej wersji pakietu SDK na Androida. Jeśli na przykład nie implementujesz własnego motywu, do kierowania pakietu SDK na Androida w wersji starszej 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 przesyłania

Platforma ma globalny obiekt typu singleton (CastContext), który koordynuje wszystkie interakcje w ramach tej platformy.

Twoja aplikacja musi implementować interfejs OptionsProvider, aby uzyskać opcje dostarczania potrzebne do zainicjowania CastContext singleton. OptionsProvider udostępnia instancję CastOptions zawierającą opcje, które wpływają na działanie platformy. Najważniejszy z nich jest identyfikator aplikacji odbiornika internetowego, który służy do filtrowania wyników wykrywania i uruchamiania aplikacji Web Receiver po uruchomieniu sesji przesyłania.

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ć w pełni kwalifikowaną nazwę wdrożonego tagu 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>

Funkcja CastContext jest inicjowana leniwie po wywołaniu funkcji 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 przesyłania

Platforma Cast udostępnia widżety zgodne z listą kontrolną projektu Cast:

  • Nakładka wprowadzająca: platforma udostępnia widok niestandardowy IntroductoryOverlay, który użytkownik widzi, aby zwrócić uwagę na przycisk Cast, gdy odbiornik jest dostępny po raz pierwszy. Aplikacja Sender może dostosować tekst oraz 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 Cast z listą wykrytych urządzeń. Gdy użytkownik kliknie przycisk Cast, gdy urządzenie jest podłączone, wyświetlą się aktualne metadane multimediów (takie jak tytuł, nazwa studia i obraz miniatury) lub użytkownik będzie mógł odłączyć urządzenie przesyłające. „Przycisk przesyłania” jest czasami nazywany „ikoną przesyłania”.

  • Mini Kontroler: gdy użytkownik przesyła treści i opuścił bieżącą stronę treści lub rozwinął kontroler na inny ekran w aplikacji nadawcy, u dołu ekranu jest wyświetlany minikontroler, który pozwala zobaczyć metadane aktualnie przesyłanych multimediów i sterować odtwarzaniem.

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

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

  • Ekran blokady: tylko Android. Gdy użytkownik przesyła treści i przechodzi do ekranu blokady (lub powoduje przekroczenie limitu czasu), wyświetlany jest element sterujący multimediami na ekranie blokady, który pokazuje metadane aktualnie przesyłanych multimediów i elementy sterujące odtwarzaniem.

W tym przewodniku znajdziesz opis tego, jak dodać widżety do swojej aplikacji.

Dodaj przycisk Cast

Interfejsy API MediaRouter na Androida umożliwiają wyświetlanie i odtwarzanie multimediów na urządzeniach dodatkowych. Aplikacje na Androida korzystające z interfejsu API MediaRouter powinny zawierać w interfejsie przycisk Cast umożliwiający użytkownikom wybór trasy multimediów do odtwarzania multimediów na urządzeniu dodatkowym, takim jak urządzenie przesyłające.

Struktura sprawia, że dodanie MediaRouteButton jako Cast button jest bardzo łatwe. Najpierw dodaj pozycję menu lub obiekt MediaRouteButton do pliku XML, który określa menu, i użyj CastButtonFactory, aby połączyć to ze strukturą.

// 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;
}

Następnie, jeśli Activity dziedziczy elementy z FragmentActivity, możesz dodać do układu MediaRouteButton.

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

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

</LinearLayout>
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);
}

Jeśli chcesz ustawić wygląd przycisku Cast, używając motywu, przeczytaj sekcję o dostosowywaniu przycisku Cast.

Skonfiguruj wykrywanie urządzeń

Wykrywaniem urządzeń w całości zarządza CastContext. Podczas inicjowania CastContext aplikacja nadawcy określa identyfikator aplikacji odbiornika internetowego i może opcjonalnie zażądać filtrowania przestrzeni nazw za pomocą ustawienia supportedNamespaces w CastOptions. CastContext zawiera wewnętrznie odwołanie do MediaRouter i rozpocznie proces wykrywania w tych warunkach:

  • Dzięki algorytmowi opracowanemu z myślą o równoważeniu opóźnienia wykrywania urządzenia i wykorzystania baterii, wykrywanie od czasu do czasu będzie rozpoczynać się automatycznie, gdy aplikacja wysyłająca znajdzie się na pierwszym planie.
  • Okno Cast jest otwarte.
  • Pakiet Cast SDK próbuje przywrócić sesję przesyłania.

Proces wykrywania zostanie zatrzymany, gdy okno przesyłania zostanie zamknięte lub aplikacja nadawcy zacznie działać 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

W ramach pakietu Cast SDK wprowadzamy koncepcję sesji przesyłania, która obejmuje etapy łączenia się z urządzeniem, uruchamiania (lub dołączania) aplikacji odbiornika internetowego oraz łą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.

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 reprezentuje sesje z urządzeniami przesyłającymi. Aplikacja może uzyskać dostęp do 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/nagłym zakończeniu sesji, gdy była aktywna.

Sesje są tworzone i wyłączane automatycznie w odpowiedzi na gesty użytkownika w oknach dialogowych MediaRouter.

Aby lepiej zrozumieć błędy rozpoczynania sesji przesyłania, aplikacje mogą za pomocą parametru CastContext#getCastReasonCodeForCastStatusCode(int) przekonwertować błąd rozpoczynania sesji na CastReasonCodes. Pamiętaj, że niektóre błędy rozpoczęcia sesji (np. CastReasonCodes#CAST_CANCELLED) są zamierzone i nie powinny być rejestrowane jako błąd.

Jeśli chcesz otrzymywać informacje o zmianach stanu danej sesji, możesz zaimplementować SessionManagerListener. W tym przykładzie słuchamy dostępności elementu 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
    }

    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
    }
}
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();
    }
    @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ą przenoszenia strumienia, w ramach której użytkownicy mogą przenosić istniejące strumienie audio i wideo między urządzeniami za pomocą poleceń głosowych, aplikacji Google Home lub inteligentnych ekranów. Multimedia zatrzymują się na jednym urządzeniu (źródle) i na drugim (miejscu docelowym). Każde urządzenie przesyłające z najnowszym oprogramowaniem może służyć jako źródło lub miejsce docelowe podczas przesyłania strumienia.

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

Więcej informacji znajdziesz w sekcji Przesyłanie strumienia w internetowym odbiorniku.

Automatyczne ponowne łączenie

Platforma udostępnia ReconnectionService, który może zostać włączony przez aplikację wysyłającą, aby obsługiwał ponowne połączenia w wielu subtelnych przypadkach, takich jak:

  • Odzyskanie połączenia po tymczasowej utracie sieci Wi-Fi
  • Wypoczynek po uśpieniu urządzenia
  • Przywracanie aplikacji w tle
  • Przywracanie systemu w przypadku awarii aplikacji

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

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

Platforma uruchamia usługę po wystąpieniu sesji multimediów i zatrzymuje ją na końcu.

Jak działa Sterowanie multimediami

Platforma Cast wycofuje klasę RemoteMediaPlayer z Cast 2.x i zastępuje ją nową klasą RemoteMediaClient, która zapewnia te same funkcje w zestawie wygodniejszych interfejsów API, a przy tym eliminuje konieczność przekazywania danych GoogleApiClient.

Gdy Twoja aplikacja utworzy CastSession z aplikacją odbiornika internetowego, 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 odbiornika internetowego, zwracają obiekt PendingResult, którego można użyć do śledzenia tego żądania.

Instancja RemoteMediaClient może być współdzielona przez wiele części aplikacji, a także niektóre wewnętrzne komponenty platformy, takie jak trwałe minikontrolery i usługa powiadomień. W związku z tym ta instancja obsługuje rejestrację wielu instancji instancji RemoteMediaClient.Listener.

Ustawianie metadanych multimediów

Klasa MediaMetadata reprezentuje informacje o elemencie multimedialnym, który chcesz przesyłać. Poniższy przykład tworzy nowe wystąpienie MediaMetadata filmu i ustawia 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))));

Informacje o korzystaniu z 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 właściwości MediaInfo.Builder z metadanymi multimediów, aby utworzyć instancję MediaInfo. Pobierz RemoteMediaClient z bieżącego CastSession, a następnie wczytaj MediaInfo do tego RemoteMediaClient. Za pomocą RemoteMediaClient możesz włączać i wstrzymywać odtwarzanie oraz sterować odtwarzaniem w 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());

Przeczytaj też sekcję o korzystaniu ze ścieżek multimedialnych.

Format filmu 4K

Aby sprawdzić format wideo, użyj parametru getVideoInfo() w MediaStatus, by pobrać bieżącą instancję VideoInfo. Ta instancja zawiera typ formatu telewizora HDR oraz wysokość i szerokość wyświetlacza w pikselach. Warianty formatu 4K są oznaczone stałymi wartościami HDR_TYPE_*.

Zdalne sterowanie powiadomieniami na wiele urządzeń

Gdy użytkownik przesyła treści, inne urządzenia z Androidem w tej samej sieci otrzymają powiadomienie, które umożliwia im sterowanie odtwarzaniem. Każdy, kto otrzymuje takie powiadomienia, może je na nim wyłączyć w aplikacji Ustawienia, wybierając Google > Google Cast > Pokaż powiadomienia na pilocie. (Powiadomienia zawierają skrót do aplikacji Ustawienia). Więcej informacji znajdziesz w artykule o przesyłaniu powiadomień z pilota.

Dodaj minikontroler

Zgodnie z listą kontrolną projektowania aplikacji do przesyłania aplikacja wysyłająca powinna zapewniać trwałą funkcję sterujący (tzw. minikontroler), która powinna się pojawiać, gdy użytkownik opuści bieżącą stronę z treścią do innej części aplikacji wysyłającej. Minikontroler wyświetla użytkownikowi widoczne przypomnienie o bieżącej sesji przesyłania. Po naciśnięciu minikontrolera użytkownik może wrócić do pełnoekranowego widoku przesyłania.

Platforma udostępnia niestandardowy widok MiniControllerFragment, który możesz dodać na dole pliku układu każdej aktywności, w której 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ę na żywo wideo lub audio, pakiet SDK automatycznie wyświetla na minikontrolerze przycisk odtwarzania/zatrzymania w miejscu przycisku odtwarzania/wstrzymania.

Aby ustawić wygląd tekstu tytułu i podtytułu tego widoku niestandardowego oraz wybrać przyciski, zapoznaj się z sekcją Dostosowywanie minikontrolera.

Dodaj rozszerzony kontroler

Lista kontrolna projektowania Google Cast wymaga, by aplikacja wysyłająca udostępniała rozwinięty kontroler do obsługi przesyłanych multimediów. Rozwinięty kontroler to pełnoekranowa wersja minikontrolera.

Pakiet Cast SDK zawiera widżet ExpandedControllerActivity dla rozwiniętego kontrolera. To jest klasa abstrakcyjna, którą musisz umieścić w podklasie, aby dodać przycisk Cast.

Najpierw utwórz nowy plik zasobów menu dla rozwiniętego kontrolera, by 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 z rozszerzeniem 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 oraz zmień NotificationOptions i CastMediaOptions, aby ustawić nową aktywność jako docelową:

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 nadawcy odtwarza transmisję na żywo wideo lub audio, pakiet SDK automatycznie wyświetla przycisk odtwarzania/zatrzymywania w miejscu przycisku odtwarzania/wstrzymywania na rozwiniętym kontrolerze.

Aby ustawić wygląd za pomocą motywów, wybierz przyciski, które mają być wyświetlane, i dodaj przyciski niestandardowe. Więcej informacji znajdziesz w sekcji Dostosowywanie rozszerzonego kontrolera.

Sterowanie głośnością

Platforma automatycznie zarządza woluminem aplikacji wysyłającej. Platforma automatycznie synchronizuje aplikacje nadawcy i odbiornika internetowego, dzięki czemu interfejs nadawcy zawsze zgłasza głośność określoną przez odbiornika internetowego.

Regulacja głośności za pomocą przycisku fizycznego

Na Androidzie za pomocą fizycznych przycisków na urządzeniu nadawcy możesz domyślnie zmienić głośność sesji przesyłania na odbiorniku internetowym na dowolnym urządzeniu z usługą Jelly Bean lub nowszym.

Sterowanie głośnością za pomocą przycisku fizycznego przed Jelly Bean

Aby sterować głośnością odbiornika internetowego za pomocą fizycznych przycisków głośności na urządzeniach z Androidem starszych niż Jelly Bean, aplikacja wysyłająca powinna zastąpić parametr dispatchKeyEvent w sekcji Aktywność 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);
    }
}

Dodaj opcje sterowania multimediami do powiadomień i ekranu blokady

Tylko na Androidzie lista kontrolna projektowania w Google Cast wymaga, by aplikacja nadawcy zaimplementowała elementy sterujące multimediami w powiadomieniu i na ekranie blokady, gdzie nadawca przesyła, ale aplikacja nadawcy nie jest zaznaczona. Schemat udostępnia funkcje MediaNotificationService i MediaIntentReceiver, które ułatwiają aplikacji nadawcy tworzenie opcji multimedialnych w powiadomieniach i na ekranie blokady.

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

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

Aplikacja może konfigurować powiadomienia i sterowanie multimediami na ekranie blokady do NotificationOptions. Aplikacja może skonfigurować, które przyciski sterowania mają być widoczne w powiadomieniu i które Activity ma być otwierane po kliknięciu powiadomienia przez użytkownika. Jeśli działania nie są określone bezpośrednio, używane są 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();

Pokazywanie opcji sterowania multimediami z powiadomień i ekranu blokady jest domyślnie włączone. Aby je wyłączyć, wywołaj metodę 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/zatrzymania w miejscu przycisku odtwarzania/wstrzymania na elemencie sterującym powiadomieniami, ale nie na ekranie blokady.

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

Obsługa błędów

Aplikacje nadawców muszą obsługiwać wszystkie błędne wywołania zwrotne i określać najlepszą odpowiedź dla każdego etapu cyklu życia przesyłania. Aplikacja może wyświetlać użytkownikowi okna z błędami lub zdecydować się na zerwanie połączenia z odbiornikiem internetowym.