Zintegruj przesyłanie z aplikacją na Androida

Ten przewodnik dla programistów opisuje, jak dodać obsługę Google Cast do aplikacji nadawczej na Androida za pomocą pakietu Android Sender SDK.

Urządzenie mobilne lub laptop to nadawca, który kontroluje odtwarzanie, a urządzenie Google Cast to odbiornik, które wyświetla treści na telewizorze.

Framework nadawcy to binarne biblioteki klas Cast i powiązane zasoby obecne w czasie wykonywania w nadawcy. Aplikacja nadawcy lub aplikacja przesyłania odnosi się do aplikacji działającej również na urządzeniu nadawcy. Aplikacja Web Receiver to aplikacja HTML działająca na urządzeniu obsługującym Cast.

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

Przebieg działania aplikacji

Te kroki opisują typowy ogólny przepływ danych w przypadku aplikacji na Androida:

  • Platforma Cast automatycznie rozpoczyna wykrywanie urządzeń MediaRouter na podstawie cyklu życia Activity.
  • Gdy użytkownik kliknie przycisk przesyłania, framework wyświetli okno przesyłania z listą wykrytych urządzeń przesyłania.
  • Gdy użytkownik wybierze urządzenie przesyłające, framework spróbuje uruchomić na tym urządzeniu aplikację Web Receiver.
  • Framework wywołuje funkcje wywołania zwrotnego w aplikacji nadawcy, aby potwierdzić, że aplikacja WebReceiver została uruchomiona.
  • Platforma tworzy kanał komunikacji między aplikacjami nadawcy i odbiorcy internetowego.
  • Framework używa kanału komunikacji do wczytywania i kontrolowania odtwarzania multimediów w odbiorniku internetowym.
  • Platforma synchronizuje stan odtwarzania multimediów między nadawcą a odbiornikiem internetowym: gdy użytkownik wykonuje czynności w interfejsie nadawcy, platforma przekazuje te żądania sterowania multimediami do odbiornika internetowego, a gdy odbiornik internetowy wysyła aktualizacje stanu multimediów, platforma aktualizuje stan interfejsu nadawcy.
  • Gdy użytkownik kliknie przycisk Cast, aby rozłączyć się z urządzeniem Cast, framework rozłączy aplikację nadawcy z odbiornikiem internetowym.

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

Konfigurowanie pliku manifestu Androida

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

uses-sdk

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

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

android:theme

Ustaw motyw aplikacji na podstawie minimalnej wersji pakietu Android SDK. Jeśli na przykład nie implementujesz własnego motywu, w przypadku kierowania na wersję pakietu SDK Androida wcześniejszą niż Lollipop powinieneś użyć wariantu Theme.AppCompat.

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

Inicjowanie kontekstu przesyłania

Framework ma globalny obiekt pojedynczy CastContext, który koordynuje wszystkie interakcje frameworku.

Aplikacja musi implementować interfejs OptionsProvider, aby udostępnić opcje potrzebne do zainicjowania singletona CastContext. OptionsProvider udostępnia instancję CastOptions, która zawiera opcje wpływające na działanie platformy. Najważniejszym z nich jest identyfikator aplikacji Web Receiver, który służy do filtrowania wyników wyszukiwania i uruchamiania aplikacji Web Receiver po rozpoczęciu sesji przesyłania.

KotlinJava
class CastOptionsProvider : OptionsProvider {
    override fun getCastOptions(context: Context): CastOptions {
        return Builder()
            .setReceiverApplicationId(context.getString(R.string.app_id))
            .build()
    }

    override fun getAdditionalSessionProviders(context: Context): List<SessionProvider>? {
        return null
    }
}
public class CastOptionsProvider implements OptionsProvider {
    @Override
    public CastOptions getCastOptions(Context context) {
        CastOptions castOptions = new CastOptions.Builder()
            .setReceiverApplicationId(context.getString(R.string.app_id))
            .build();
        return castOptions;
    }
    @Override
    public List<SessionProvider> getAdditionalSessionProviders(Context context) {
        return null;
    }
}

Pełną nazwę zaimplementowanej usługi OptionsProvider należy zadeklarować jako pole metadanych w pliku AndroidManifest.xml aplikacji nadawcy:

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

CastContext jest inicjowany w sposób leniwy, gdy wywoływana jest funkcja CastContext.getSharedInstance().

KotlinJava
class MyActivity : FragmentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        val castContext = CastContext.getSharedInstance(this)
    }
}
public class MyActivity extends FragmentActivity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        CastContext castContext = CastContext.getSharedInstance(this);
    }
}

Widżety w przesyłaniu

Platforma Cast udostępnia widżety zgodne z wytycznymi dotyczącymi projektu Cast:

  • Wprowadzenie: framework udostępnia niestandardowy widok IntroductoryOverlay, który jest pokazywany użytkownikowi, aby zwrócić jego uwagę na przycisk Cast, gdy po raz pierwszy pojawi się odbiornik. Aplikacja Sender może dostosowywać tekst i pozycję tytułu.

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

  • Mini kontroler: gdy użytkownik przesyła treści i przechodzi z bieżącej strony treści lub rozwiniętego kontrolera na inny ekran w aplikacji nadawcy, na dole ekranu wyświetla się mini kontroler, aby użytkownik mógł zobaczyć metadane przesyłanych multimediów i sterować odtwarzaniem.

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

  • Powiadomienie: tylko Android. Gdy użytkownik przesyła treści i przechodzi do innej aplikacji, wyświetla się powiadomienie multimedialne z informacjami o przesyłanych metadanych i elementami sterowania odtwarzaniem.

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

W tym przewodniku znajdziesz opisy sposobów dodawania tych widżetów do aplikacji.

Dodawanie przycisku przesyłania

Interfejsy API Androida MediaRouter są przeznaczone do wyświetlania i odtwarzania multimediów na urządzeniach dodatkowych. Aplikacje na Androida, które korzystają z interfejsu API MediaRouter, powinny zawierać przycisk Cast, aby umożliwić użytkownikom wybór ścieżki multimedialnej do odtwarzania multimediów na urządzeniu dodatkowym, takim jak urządzenie z Cast.

Dzięki temu frameworkowi możesz bardzo łatwo dodać MediaRouteButtonjako Cast button. Najpierw dodaj element menu lub element MediaRouteButton do pliku XML, który definiuje menu, a następnie użyj elementu CastButtonFactory, aby połączyć go z frameworkiem.

// To add a Cast button, add the following snippet.
// menu.xml
<item
    android:id="@+id/media_route_menu_item"
    android:title="@string/media_route_menu_title"
    app:actionProviderClass="androidx.mediarouter.app.MediaRouteActionProvider"
    app:showAsAction="always" />
KotlinJava
// Then override the onCreateOptionMenu() for each of your activities.
// MyActivity.kt
override fun onCreateOptionsMenu(menu: Menu): Boolean {
    super.onCreateOptionsMenu(menu)
    menuInflater.inflate(R.menu.main, menu)
    CastButtonFactory.setUpMediaRouteButton(
        applicationContext,
        menu,
        R.id.media_route_menu_item
    )
    return true
}
// Then override the onCreateOptionMenu() for each of your activities.
// MyActivity.java
@Override public boolean onCreateOptionsMenu(Menu menu) {
    super.onCreateOptionsMenu(menu);
    getMenuInflater().inflate(R.menu.main, menu);
    CastButtonFactory.setUpMediaRouteButton(getApplicationContext(),
                                            menu,
                                            R.id.media_route_menu_item);
    return true;
}

Jeśli Activity dziedziczy po FragmentActivity, możesz dodać do układu MediaRouteButton.

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

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

</LinearLayout>
KotlinJava
// MyActivity.kt
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_layout)

    mMediaRouteButton = findViewById<View>(R.id.media_route_button) as MediaRouteButton
    CastButtonFactory.setUpMediaRouteButton(applicationContext, mMediaRouteButton)

    mCastContext = CastContext.getSharedInstance(this)
}
// MyActivity.java
@Override
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   setContentView(R.layout.activity_layout);

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

   mCastContext = CastContext.getSharedInstance(this);
}

Aby ustawić wygląd przycisku Cast za pomocą motywu, przeczytaj artykuł Dostosowywanie przycisku Cast.

Konfigurowanie wykrywania urządzeń

Wykrywanie urządzeń jest całkowicie zarządzane przez CastContext. Podczas inicjowania kontekstu Cast aplikacja nadawcza określa identyfikator aplikacji odbiornika internetowego i może opcjonalnie poprosić o filtrowanie przestrzeni nazw, ustawiając parametr supportedNamespacesCastOptions. CastContext zawiera wewnętrzne odwołanie do MediaRouter i rozpoczyna proces wyszukiwania w tych warunkach:

  • Na podstawie algorytmu zaprojektowanego w celu zrównoważenia opóźnienia wykrywania urządzenia i zużycia baterii wykrywanie będzie czasami uruchamiane automatycznie, gdy aplikacja nadawcy znajdzie się na pierwszym planie.
  • Okno przesyłania jest otwarte.
  • Pakiet SDK Cast próbuje odzyskać sesję przesyłania.

Proces wykrywania zostanie zatrzymany, gdy zamkniesz okno przesyłania lub aplikacja nadawcza przejdzie w tło.

KotlinJava
class CastOptionsProvider : OptionsProvider {
    companion object {
        const val CUSTOM_NAMESPACE = "urn:x-cast:custom_namespace"
    }

    override fun getCastOptions(appContext: Context): CastOptions {
        val supportedNamespaces: MutableList<String> = ArrayList()
        supportedNamespaces.add(CUSTOM_NAMESPACE)

        return CastOptions.Builder()
            .setReceiverApplicationId(context.getString(R.string.app_id))
            .setSupportedNamespaces(supportedNamespaces)
            .build()
    }

    override fun getAdditionalSessionProviders(context: Context): List<SessionProvider>? {
        return null
    }
}
class CastOptionsProvider implements OptionsProvider {
    public static final String CUSTOM_NAMESPACE = "urn:x-cast:custom_namespace";

    @Override
    public CastOptions getCastOptions(Context appContext) {
        List<String> supportedNamespaces = new ArrayList<>();
        supportedNamespaces.add(CUSTOM_NAMESPACE);

        CastOptions castOptions = new CastOptions.Builder()
            .setReceiverApplicationId(context.getString(R.string.app_id))
            .setSupportedNamespaces(supportedNamespaces)
            .build();
        return castOptions;
    }

    @Override
    public List<SessionProvider> getAdditionalSessionProviders(Context context) {
        return null;
    }
}

Jak działa zarządzanie sesjami

Pakiet SDK Cast wprowadza pojęcie sesji Cast, której utworzenie łączy w sobie czynności takie jak połączenie z urządzeniem, uruchomienie (lub dołączenie) aplikacji Web Receiver, połączenie z tą aplikacją i inicjowanie kanału sterowania multimediami. Więcej informacji o sesjach przesyłania i cyklu życia odbiornika internetowego znajdziesz w przewodniku po cyklu życia aplikacji.

Sesjami zarządza zajęcia SessionManager, do których Twoja aplikacja ma dostęp za pomocą CastContext.getSessionManager(). Poszczególne sesje są reprezentowane przez podklasy klasy Session. Na przykład: CastSession odpowiada sesjom na urządzeniach Cast. Aplikacja może uzyskać dostęp do bieżącej sesji przesyłania przez SessionManager.getCurrentCastSession().

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

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

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

Jeśli chcesz wiedzieć, kiedy zmienia się stan sesji, możesz zaimplementować SessionManagerListener. W tym przykładzie sprawdzamy dostępność usługi CastSession w Activity.

KotlinJava
class MyActivity : Activity() {
    private var mCastSession: CastSession? = null
    private lateinit var mCastContext: CastContext
    private lateinit var mSessionManager: SessionManager
    private val mSessionManagerListener: SessionManagerListener<CastSession> =
        SessionManagerListenerImpl()

    private inner class SessionManagerListenerImpl : SessionManagerListener<CastSession?> {
        override fun onSessionStarting(session: CastSession?) {}

        override fun onSessionStarted(session: CastSession?, sessionId: String) {
            invalidateOptionsMenu()
        }

        override fun onSessionStartFailed(session: CastSession?, error: Int) {
            val castReasonCode = mCastContext.getCastReasonCodeForCastStatusCode(error)
            // Handle error
        }

        override fun onSessionSuspended(session: CastSession?, reason Int) {}

        override fun onSessionResuming(session: CastSession?, sessionId: String) {}

        override fun onSessionResumed(session: CastSession?, wasSuspended: Boolean) {
            invalidateOptionsMenu()
        }

        override fun onSessionResumeFailed(session: CastSession?, error: Int) {}

        override fun onSessionEnding(session: CastSession?) {}

        override fun onSessionEnded(session: CastSession?, error: Int) {
            finish()
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        mCastContext = CastContext.getSharedInstance(this)
        mSessionManager = mCastContext.sessionManager
        mSessionManager.addSessionManagerListener(mSessionManagerListener, CastSession::class.java)
    }

    override fun onResume() {
        super.onResume()
        mCastSession = mSessionManager.currentCastSession
    }

    override fun onDestroy() {
        super.onDestroy()
        mSessionManager.removeSessionManagerListener(mSessionManagerListener, CastSession::class.java)
    }
}
public class MyActivity extends Activity {
    private CastContext mCastContext;
    private CastSession mCastSession;
    private SessionManager mSessionManager;
    private SessionManagerListener<CastSession> mSessionManagerListener =
            new SessionManagerListenerImpl();

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

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

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

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

Przeniesienie odtwarzania

Zachowanie stanu sesji stanowi podstawę przenoszenia strumienia, dzięki czemu użytkownicy mogą przenosić istniejące strumienie audio i wideo między urządzeniami za pomocą poleceń głosowych, aplikacji Google Home lub inteligentnych ekranów. Multimedia przestają być odtwarzane na jednym urządzeniu (źródło) i kontynuują na drugim (miejsce docelowe). Źródłem lub celem przesyłania strumieniowego może być dowolne urządzenie Cast z najnowszą wersją oprogramowania układowego.

Aby uzyskać nowe urządzenie docelowe podczas przenoszenia lub rozszerzania strumienia, zarejestruj Cast.Listener za pomocą CastSession#addCastListener. Następnie zadzwoń pod numerCastSession#getCastDevice()podczas onDeviceNameChangedpołączenia zwrotnego.

Więcej informacji znajdziesz w artykule Przenoszenie strumienia na odbiornik internetowy.

Automatyczne ponowne łączenie

Platforma udostępnia funkcję ReconnectionService, którą aplikacja nadawcza może włączyć, aby obsługiwać ponowne nawiązywanie połączenia w wielu subtelnych przypadkach szczególnych, takich jak:

  • Przywracanie działania po tymczasowej utracie połączenia z Wi-Fi
  • Przywracanie po przejściu urządzenia w stan uśpienia
  • Przywracanie aplikacji z tła
  • Odzyskiwanie danych w przypadku awarii aplikacji

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

Ta usługa może być automatycznie scalana z pliku manifestu aplikacji, jeśli w pliku gradle włączone jest automatyczne scalanie.

Framework uruchamia usługę, gdy trwa sesja multimediów, i zatrzymuje ją, gdy sesja się kończy.

Jak działa kontrola mediów

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

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

Wszystkie metody RemoteMediaClient, które wysyłają żądania do odbiornika internetowego, zwracają obiekt PendingResult, który może służyć do śledzenia żądania.

RemoteMediaClient może być używany przez wiele części aplikacji, a także przez niektóre wewnętrzne komponenty frameworku, takie jak trwałe minikontroleryusługa powiadomień. W tym celu ta instancja obsługuje rejestrację wielu instancji usługi RemoteMediaClient.Listener.

Ustawianie metadanych multimediów

Klasa MediaMetadata reprezentuje informacje o pliku multimedialnym, który chcesz przesłać. W tym przykładzie tworzymy nowy egzemplarz MediaMetadata filmu i ustawiamy tytuł, napisy oraz 2 obrazy.

KotlinJava
val movieMetadata = MediaMetadata(MediaMetadata.MEDIA_TYPE_MOVIE)

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

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

Informacje na temat korzystania z obrazów z metadanymi multimediów znajdziesz w artykule Wybór obrazu.

Ładowanie multimediów

Aplikacja może wczytać element multimedialny, jak pokazano w poniższym kodzie. Najpierw użyj pliku MediaInfo.Builder z metadanymi multimediów, aby utworzyć instancję MediaInfo. Pobierz RemoteMediaClient z bieżącego CastSession, a następnie załaduj MediaInfo do tego RemoteMediaClient. Użyj RemoteMediaClient, aby odtwarzać, wstrzymywać i w inny sposób sterować aplikacją odtwarzacza multimediów działającą w odbiorniku internetowym.

KotlinJava
val mediaInfo = MediaInfo.Builder(mSelectedMedia.getUrl())
    .setStreamType(MediaInfo.STREAM_TYPE_BUFFERED)
    .setContentType("videos/mp4")
    .setMetadata(movieMetadata)
    .setStreamDuration(mSelectedMedia.getDuration() * 1000)
    .build()
val remoteMediaClient = mCastSession.getRemoteMediaClient()
remoteMediaClient.load(MediaLoadRequestData.Builder().setMediaInfo(mediaInfo).build())
MediaInfo mediaInfo = new MediaInfo.Builder(mSelectedMedia.getUrl())
        .setStreamType(MediaInfo.STREAM_TYPE_BUFFERED)
        .setContentType("videos/mp4")
        .setMetadata(movieMetadata)
        .setStreamDuration(mSelectedMedia.getDuration() * 1000)
        .build();
RemoteMediaClient remoteMediaClient = mCastSession.getRemoteMediaClient();
remoteMediaClient.load(new MediaLoadRequestData.Builder().setMediaInfo(mediaInfo).build());

Zobacz też sekcję Korzystanie z ścieżek multimedialnych.

Format wideo 4K

Aby sprawdzić format pliku multimedialnego, użyj polecenia getVideoInfo() w MediaStatus, aby uzyskać bieżącą instancję VideoInfo. Ten element zawiera typ formatu HDR TV oraz wysokość i szerokość wyświetlacza w pikselach. Warianty formatu 4K są oznaczone za pomocą stałych HDR_TYPE_*.

Powiadomienia o zdalnym sterowaniu na wielu urządzeniach

Gdy użytkownik będzie przesyłać treści, inne urządzenia z Androidem w tej samej sieci otrzymają powiadomienie, które pozwoli im również kontrolować odtwarzanie. Każdy, kto ma urządzenie, na które wysyłane są takie powiadomienia, może je wyłączyć w aplikacji Ustawienia w sekcji Google > Google Cast > Pokaż powiadomienia o sterowaniu zdalnie. (powiadomienia zawierają skrót do aplikacji Ustawienia). Więcej informacji znajdziesz w artykule Powiadomienia o zdalnym sterowaniu Google Cast.

Dodawanie mini kontrolera

Zgodnie z listą kontrolną dotyczącą projektowania Cast aplikacja wysyłająca powinna udostępniać trwały element sterujący, zwany ministerującym, który powinien pojawiać się, gdy użytkownik przejdzie z bieżącej strony treści do innej części aplikacji. Ministerujący powinien przypominać użytkownikowi o bieżącej sesji Cast. Kliknięcie minikontrolera powoduje powrót do widoku kontrolera rozszerzonego na pełnym ekranie.

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

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

Gdy aplikacja nadawcy odtwarza transmisję na żywo z dźwiękiem lub wideo, SDK automatycznie wyświetla przycisk odtwarzania/zatrzymania zamiast przycisku odtwarzania/wstrzymywania na minikontrolerze.

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

Dodawanie rozszerzonego kontrolera

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

Pakiet Cast SDK udostępnia widżet rozszerzonego kontrolera o nazwie ExpandedControllerActivity. Jest to abstrakcyjna klasa, której musisz użyć jako podklasy, aby dodać przycisk Cast.

Najpierw utwórz nowy plik zasobu menu dla rozszerzonego kontrolera, aby udostępnić przycisk Cast:

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

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

</menu>

Utwórz nową klasę rozszerzającą klasę ExpandedControllerActivity.

KotlinJava
class ExpandedControlsActivity : ExpandedControllerActivity() {
    override fun onCreateOptionsMenu(menu: Menu): Boolean {
        super.onCreateOptionsMenu(menu)
        menuInflater.inflate(R.menu.expanded_controller, menu)
        CastButtonFactory.setUpMediaRouteButton(this, menu, R.id.media_route_menu_item)
        return true
    }
}
public class ExpandedControlsActivity extends ExpandedControllerActivity {
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        super.onCreateOptionsMenu(menu);
        getMenuInflater().inflate(R.menu.expanded_controller, menu);
        CastButtonFactory.setUpMediaRouteButton(this, menu, R.id.media_route_menu_item);
        return true;
    }
}

Teraz zadeklaruj nową aktywność w pliku manifestu aplikacji za pomocą tagu application:

<application>
...
<activity
        android:name=".expandedcontrols.ExpandedControlsActivity"
        android:label="@string/app_name"
        android:launchMode="singleTask"
        android:theme="@style/Theme.CastVideosDark"
        android:screenOrientation="portrait"
        android:parentActivityName="com.google.sample.cast.refplayer.VideoBrowserActivity">
    <intent-filter>
        <action android:name="android.intent.action.MAIN"/>
    </intent-filter>
</activity>
...
</application>

Zmień tagi CastOptionsProvider, NotificationOptionsCastMediaOptions, aby ustawić nową aktywność jako aktywność docelową:

KotlinJava
override fun getCastOptions(context: Context): CastOptions? {
    val notificationOptions = NotificationOptions.Builder()
        .setTargetActivityClassName(ExpandedControlsActivity::class.java.name)
        .build()
    val mediaOptions = CastMediaOptions.Builder()
        .setNotificationOptions(notificationOptions)
        .setExpandedControllerActivityClassName(ExpandedControlsActivity::class.java.name)
        .build()

    return CastOptions.Builder()
        .setReceiverApplicationId(context.getString(R.string.app_id))
        .setCastMediaOptions(mediaOptions)
        .build()
}
public CastOptions getCastOptions(Context context) {
    NotificationOptions notificationOptions = new NotificationOptions.Builder()
            .setTargetActivityClassName(ExpandedControlsActivity.class.getName())
            .build();
    CastMediaOptions mediaOptions = new CastMediaOptions.Builder()
            .setNotificationOptions(notificationOptions)
            .setExpandedControllerActivityClassName(ExpandedControlsActivity.class.getName())
            .build();

    return new CastOptions.Builder()
            .setReceiverApplicationId(context.getString(R.string.app_id))
            .setCastMediaOptions(mediaOptions)
            .build();
}

Zaktualizuj metodę LocalPlayerActivity loadRemoteMedia, aby wyświetlić nową aktywność po załadowaniu zdalnych multimediów:

KotlinJava
private fun loadRemoteMedia(position: Int, autoPlay: Boolean) {
    val remoteMediaClient = mCastSession?.remoteMediaClient ?: return

    remoteMediaClient.registerCallback(object : RemoteMediaClient.Callback() {
        override fun onStatusUpdated() {
            val intent = Intent(this@LocalPlayerActivity, ExpandedControlsActivity::class.java)
            startActivity(intent)
            remoteMediaClient.unregisterCallback(this)
        }
    })

    remoteMediaClient.load(
        MediaLoadRequestData.Builder()
            .setMediaInfo(mSelectedMedia)
            .setAutoplay(autoPlay)
            .setCurrentTime(position.toLong()).build()
    )
}
private void loadRemoteMedia(int position, boolean autoPlay) {
    if (mCastSession == null) {
        return;
    }
    final RemoteMediaClient remoteMediaClient = mCastSession.getRemoteMediaClient();
    if (remoteMediaClient == null) {
        return;
    }
    remoteMediaClient.registerCallback(new RemoteMediaClient.Callback() {
        @Override
        public void onStatusUpdated() {
            Intent intent = new Intent(LocalPlayerActivity.this, ExpandedControlsActivity.class);
            startActivity(intent);
            remoteMediaClient.unregisterCallback(this);
        }
    });
    remoteMediaClient.load(new MediaLoadRequestData.Builder()
            .setMediaInfo(mSelectedMedia)
            .setAutoplay(autoPlay)
            .setCurrentTime(position).build());
}

Gdy aplikacja nadawcy odtwarza transmisję na żywo z filmem lub dźwiękiem, SDK automatycznie wyświetli przycisk odtwarzania/zatrzymania zamiast przycisku odtwarzania/wstrzymania w rozwiniętym kontrolerze.

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

Sterowanie głośnością

Platforma automatycznie zarządza głośnością aplikacji nadawcy. Synchronizuje ona automatycznie aplikacje nadawcy i odbiornika internetowego, aby interfejs użytkownika nadawcy zawsze podawał głośność określoną przez odbiornik internetowy.

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

Na urządzeniach z Androidem można używać przycisków fizycznych na urządzeniu nadawczym do zmiany głośności sesji przesyłania na odbiornik internetowy. Dotyczy to domyślnie wszystkich urządzeń z Androidem w wersji Jelly Bean lub nowszej.

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

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

KotlinJava
class MyActivity : FragmentActivity() {
    override fun dispatchKeyEvent(event: KeyEvent): Boolean {
        return (CastContext.getSharedInstance(this)
            .onDispatchVolumeKeyEventBeforeJellyBean(event)
                || super.dispatchKeyEvent(event))
    }
}
class MyActivity extends FragmentActivity {
    @Override
    public boolean dispatchKeyEvent(KeyEvent event) {
        return CastContext.getSharedInstance(this)
            .onDispatchVolumeKeyEventBeforeJellyBean(event)
            || super.dispatchKeyEvent(event);
    }
}

Dodawanie elementów sterujących multimediami do powiadomień i ekranu blokady

Wyłącznie na Androidzie lista kontrolna Google Cast Design wymaga, aby aplikacja nadawcza zaimplementowała elementy sterowania multimediami w powiadomieniu i na ekranie blokady, gdzie nadawca przesyła treści, ale aplikacja nadawcza nie ma fokusa. MediaNotificationService MediaIntentReceiver Aby pomóc aplikacji nadawcy w utworzeniu elementów sterujących multimediami w powiadomieniu i na ekranie blokady.

MediaNotificationServiceuruchamia się, gdy nadawca przesyła treści, i wyświetla powiadomienie z miniaturą obrazu oraz informacjami o bieżącym elemencie przesyłania, przyciskiem odtwarzania/wstrzymywania i przyciskiem zatrzymania.

MediaIntentReceiver to BroadcastReceiver, który obsługuje działania użytkownika związane z powiadomieniem.

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

KotlinJava
// Example showing 4 buttons: "rewind", "play/pause", "forward" and "stop casting".
val buttonActions: MutableList<String> = ArrayList()
buttonActions.add(MediaIntentReceiver.ACTION_REWIND)
buttonActions.add(MediaIntentReceiver.ACTION_TOGGLE_PLAYBACK)
buttonActions.add(MediaIntentReceiver.ACTION_FORWARD)
buttonActions.add(MediaIntentReceiver.ACTION_STOP_CASTING)

// Showing "play/pause" and "stop casting" in the compat view of the notification.
val compatButtonActionsIndices = intArrayOf(1, 3)

// Builds a notification with the above actions. Each tap on the "rewind" and "forward" buttons skips 30 seconds.
// Tapping on the notification opens an Activity with class VideoBrowserActivity.
val notificationOptions = NotificationOptions.Builder()
    .setActions(buttonActions, compatButtonActionsIndices)
    .setSkipStepMs(30 * DateUtils.SECOND_IN_MILLIS)
    .setTargetActivityClassName(VideoBrowserActivity::class.java.name)
    .build()
// Example showing 4 buttons: "rewind", "play/pause", "forward" and "stop casting".
List<String> buttonActions = new ArrayList<>();
buttonActions.add(MediaIntentReceiver.ACTION_REWIND);
buttonActions.add(MediaIntentReceiver.ACTION_TOGGLE_PLAYBACK);
buttonActions.add(MediaIntentReceiver.ACTION_FORWARD);
buttonActions.add(MediaIntentReceiver.ACTION_STOP_CASTING);

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

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

Domyślnie opcja wyświetlania elementów sterowania multimediami w powiadomieniach i na ekranie blokady jest włączona. Można ją wyłączyć, wywołując funkcję setNotificationOptions z wartością null w parametrze CastMediaOptions.Builder. Obecnie funkcja ekranu blokady jest włączona, o ile włączone są powiadomienia.

KotlinJava
// ... continue with the NotificationOptions built above
val mediaOptions = CastMediaOptions.Builder()
    .setNotificationOptions(notificationOptions)
    .build()
val castOptions: CastOptions = Builder()
    .setReceiverApplicationId(context.getString(R.string.app_id))
    .setCastMediaOptions(mediaOptions)
    .build()
// ... continue with the NotificationOptions built above
CastMediaOptions mediaOptions = new CastMediaOptions.Builder()
        .setNotificationOptions(notificationOptions)
        .build();
CastOptions castOptions = new CastOptions.Builder()
        .setReceiverApplicationId(context.getString(R.string.app_id))
        .setCastMediaOptions(mediaOptions)
        .build();

Gdy aplikacja nadawcza odtwarza strumień na żywo z filmem lub dźwiękiem, SDK automatycznie wyświetla przycisk odtwarzania/wstrzymywania zamiast przycisku odtwarzania/wstrzymywania w elementach sterujących powiadomienia, ale nie na ekranie blokady.

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

Obsługuj błędy

Aplikacje nadawców muszą obsługiwać wszystkie wywołania zwrotne błędów i decydowować o najlepszym sposobie reakcji na każdym etapie cyklu życia Cast. Aplikacja może wyświetlić użytkownikowi okno z błędem lub może zdecydować się na zerwanie połączenia z Web Receiver.