Zintegruj przesyłanie z aplikacją na Androida

W tym przewodniku dla programistów opisujemy, jak dodać obsługę Google Cast do aplikacji nadawcy na Androida za pomocą pakietu Android Sender SDK.

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

Platforma nadawcy odnosi się do pliku binarnego biblioteki klas Cast i powiązanych z nim zasobów w czasie działania u nadawcy. Określenie aplikacja nadawcy lub aplikacja Cast odnosi się również do aplikacji uruchomionej u nadawcy. Aplikacja Web Receivedr odnosi się do aplikacji HTML uruchomionej na urządzeniu obsługującym Cast.

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

Przepływ aplikacji

Poniżej opisujemy typowy ogólny proces wykonywania w przypadku aplikacji na Androida nadawcy:

  • Platforma Cast automatycznie rozpoczyna wykrywanie urządzeń MediaRouter na podstawie cyklu życia Activity.
  • Gdy użytkownik kliknie przycisk Cast, platforma wyświetli okno przesyłania z listą wykrytych urządzeń przesyłających.
  • Gdy użytkownik wybiera urządzenie przesyłające, platforma próbuje uruchomić na nim aplikację Web Odbiornik.
  • Platforma wywołuje wywołania zwrotne w aplikacji nadawcy, aby potwierdzić, że aplikacja Web Receivedr została uruchomiona.
  • Platforma tworzy kanał komunikacyjny między nadawcą a aplikacjami odbierającymi dane.
  • Platforma wykorzystuje kanał komunikacyjny do wczytywania i kontrolowania odtwarzania multimediów w odbiorniku internetowym.
  • Platforma synchronizuje stan odtwarzania multimediów między nadawcą a odbiornikiem internetowym: gdy użytkownik wykonuje działania w interfejsie użytkownika nadawcy, platforma przekazuje te żądania do odbiornika internetowego, a gdy odbiornik internetowy wysyła aktualizacje stanu multimediów, platforma aktualizuje stan interfejsu nadawcy.
  • Gdy użytkownik kliknie przycisk Cast, aby odłączyć urządzenie przesyłające, platforma odłączy aplikację nadawcy od odbiornika internetowego.

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

Skonfiguruj plik manifestu Androida

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

uses-sdk

Ustaw minimalne i docelowe poziomy interfejsu Android API obsługiwane przez pakiet SDK Cast. Obecnie minimalny poziom API to 21, a docelowy – poziom API 28.

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

android:theme

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

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

Inicjowanie kontekstu przesyłania

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

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

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 polu metadanych w pliku AndroidManifest.xml aplikacji nadawcy musisz zadeklarować w pełni i jednoznaczną nazwę wdrożonego elementu OptionsProvider:

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

CastContext jest inicjowany leniwie po wywołaniu CastContext.getSharedInstance().

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 Cast UX

Platforma Cast udostępnia widżety zgodne z listą kontrolną projektowania przesyłania:

  • Nakładka z wprowadzeniem: platforma udostępnia widok niestandardowy IntroductoryOverlay, który przy pierwszym udostępnieniu odbiornika zwraca uwagę użytkownika na przycisk przesyłania. Aplikacja Nadawca może dostosować tekst i jego pozycję.

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

  • Minikontroler: gdy użytkownik przesyła treści i opuści bieżącą stronę treści lub rozwinie kontroler na inny ekran w aplikacji nadawcy, u dołu ekranu pojawi się minikontroler, który umożliwia użytkownikowi przeglądanie aktualnie przesyłanych metadanych multimediów i sterowanie odtwarzaniem.

  • Rozwinięty kontroler: gdy użytkownik przesyła treści, a potem kliknie powiadomienie o multimediach lub minikontroler, uruchomi się rozwinięty kontroler, który wyświetli metadane odtwarzanych multimediów oraz kilka przycisków do sterowania odtwarzaniem multimediów.

  • Powiadomienie: tylko Android. Gdy użytkownik przesyła treści i wyjdzie z aplikacji nadawcy, pojawi się powiadomienie o multimediach, które pokazują aktualnie przesyłane metadane multimediów i elementy sterujące odtwarzaniem.

  • Ekran blokady: tylko Android. Gdy użytkownik przesyła treści i przejdzie (lub przekroczy limit czasu) na ekran blokady, pojawi się element sterujący na ekranie blokady, który pokazuje aktualnie przesyłane metadane multimediów oraz elementy sterujące odtwarzaniem.

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

Dodaj przycisk Cast

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

Platforma ułatwia dodawanie MediaRouteButton jako Cast button. Najpierw dodaj pozycję menu lub MediaRouteButton w pliku XML, który określa Twoje menu, i użyj CastButtonFactory, aby połączyć je z platformą.

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

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

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

</LinearLayout>
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 za pomocą motywu, przeczytaj artykuł o dostosowywaniu przycisku Cast.

Skonfiguruj wykrywanie urządzeń

Wykrywaniem urządzeń w pełni zarządza CastContext. Podczas inicjowania CastContext aplikacja nadawcy określa identyfikator aplikacji Web Recipient i może opcjonalnie poprosić o filtrowanie przestrzeni nazw, ustawiając supportedNamespaces w CastOptions. CastContext odwołuje się wewnętrznie do MediaRouter i rozpocznie proces wykrywania, gdy:

  • Bazujący na algorytmie zaprojektowanym z myślą o zrównoważeniu czasu oczekiwania na wykrywanie urządzenia i wykorzystania baterii, wykrywanie czasami uruchamia się automatycznie, gdy aplikacja nadawcy działa na pierwszym planie.
  • Okno przesyłania jest otwarte.
  • Pakiet SDK Cast próbuje przywrócić sesję przesyłania.

Proces wykrywania zostanie zatrzymany po zamknięciu okna przesyłania lub gdy aplikacja nadawcy przejdzie w tle.

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 SDK Cast przedstawia koncepcję sesji przesyłania, która łączy w sobie etapy łączenia się z urządzeniem, uruchamiania (lub dołączania) aplikacji internetowej odbiornika, łączenia się z nią i inicjowania kanału sterowania multimediami. Więcej informacji o sesjach przesyłania i cyklu życia odbiornika internetowego znajdziesz w przewodniku po cyklu życia aplikacji na odbiorniku internetowym.

Sesjami zarządza klasa SessionManager, do której aplikacja ma dostęp przez CastContext.getSessionManager(). Pojedyncze sesje są reprezentowane przez podklasy Session. Na przykład CastSession oznacza sesje z urządzeniami przesyłającymi. Aplikacja może uzyskać dostęp do obecnie aktywnej sesji przesyłania na stronie SessionManager.getCurrentCastSession().

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

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

Aby lepiej zrozumieć błędy rozpoczynania przesyłania, aplikacje mogą użyć parametru CastContext#getCastReasonCodeForCastStatusCode(int) do przekonwertowania błędu podczas rozpoczynania sesji na CastReasonCodes. Pamiętaj, że niektóre błędy rozpoczynania sesji (np. CastReasonCodes#CAST_CANCELLED) są zamierzone i nie powinny być rejestrowane jako błędy.

Jeśli musisz wiedzieć o zmianach stanu sesji, możesz zaimplementować SessionManagerListener. W tym przykładzie nasłuchuje się dostępności CastSession w Activity.

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ą przesyłania strumienia, w ramach którego użytkownicy mogą przenosić strumienie audio i wideo między urządzeniami za pomocą poleceń głosowych, aplikacji Google Home lub inteligentnych ekranów. Odtwarzanie multimediów zatrzymuje się na jednym urządzeniu (źródło), a następnie jest kontynuowane na innym (miejscu docelowym). Każde urządzenie przesyłające z najnowszym oprogramowaniem może służyć jako źródło lub miejsca docelowe w przesyłaniu strumieniowym.

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

Więcej informacji znajdziesz w sekcji Przenoszenie strumienia w internetowym odbiorniku.

Automatyczne ponowne połączenie

Platforma udostępnia obiekt ReconnectionService, który aplikacja nadawcy może włączyć, aby umożliwić ponowne łączenie w wielu subtelnych sytuacjach, takich jak:

  • przywracanie działania po tymczasowej utracie połączenia z Wi-Fi,
  • Regeneracja po uśpieniu urządzenia
  • Przywróć działanie aplikacji po uruchomieniu w tle
  • Przywracanie w przypadku awarii aplikacji

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

Ta usługa może zostać automatycznie scalona z plikiem manifestu aplikacji, jeśli w pliku Gradle jest włączone automatyczne scalanie.

Platforma uruchomi usługę w momencie wystąpienia sesji multimediów i zatrzyma ją po jej zakończeniu.

Jak działa Sterowanie multimediami

Platforma Cast wycofuje klasę RemoteMediaPlayer z Cast 2.x na rzecz nowej klasy RemoteMediaClient, która ma tę samą funkcjonalność w zestawie bardziej wygodnych interfejsów API i pozwala uniknąć przekazywania klienta GoogleApiClient.

Gdy aplikacja utworzy obiekt CastSession z aplikacją Web Received, która obsługuje przestrzeń nazw multimediów, platforma automatycznie utworzy wystąpienie RemoteMediaClient. Aplikacja będzie mogła uzyskać do niego dostęp, wywołując metodę getRemoteMediaClient() w instancji CastSession.

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

Instancja RemoteMediaClient może być współużytkowana przez wiele części aplikacji, a w rzeczywistości niektóre wewnętrzne komponenty platformy, takie jak stałe minikontrolery i usługa powiadomień. Dlatego ta instancja obsługuje rejestrację wielu instancji RemoteMediaClient.Listener.

Ustaw metadane multimediów

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

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 używaniu obrazów z metadanymi multimediów znajdziesz w sekcji Wybór obrazu.

Wczytaj multimedia

Aplikacja może wczytać element multimedialny, tak jak w tym kodzie. Najpierw użyj parametru MediaInfo.Builder z metadanymi multimediów, aby utworzyć instancję MediaInfo. Pobierz RemoteMediaClient bieżący CastSession, a następnie wczytaj do niego MediaInfo.RemoteMediaClient. Użyj RemoteMediaClient, aby włączać i wstrzymywać odtwarzanie oraz sterować aplikacjami odtwarzacza multimediów działającymi na odbiorniku internetowym.

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ą o używaniu ścieżek multimedialnych.

Format wideo 4K

Aby sprawdzić format multimediów, użyj wartości getVideoInfo() w polu MediaStatus, aby pobrać bieżące wystąpienie komponentu VideoInfo. Ta instancja zawiera typ formatu HDR TV oraz wysokość i szerokość wyświetlacza w pikselach. Warianty formatu 4K są oznaczone stałymi HDR_TYPE_*.

Zdalne sterowanie powiadomieniami na wielu urządzeniach

Gdy użytkownik przesyła treści, inne urządzenia z Androidem w tej samej sieci otrzymają powiadomienie, że mogą sterować odtwarzaniem. Każdy, którego urządzenie otrzymuje takie powiadomienia, może je na nim wyłączyć w aplikacji Ustawienia, klikając Google > Google Cast > Pokazuj powiadomienia pilota. (Powiadomienia zawierają skrót do aplikacji Ustawienia). Więcej informacji znajdziesz w artykule Powiadomienia o pilocie zdalnego sterowania.

Dodaj minikontroler

Zgodnie z listą kontrolną projektu przesyłania aplikacja nadawcy powinna zapewniać trwały element sterujący nazywany minikontrolerem, który powinien się pojawiać, gdy użytkownik przechodzi ze strony bieżącej treści do innej części aplikacji nadawcy. Minikontroler przypomina użytkownikowi o bieżącej sesji przesyłania. Po dotknięciu minikontrolera użytkownik może powrócić do rozwiniętego widoku kontrolera na pełnym ekranie przesyłania.

Platforma udostępnia widok niestandardowy MiniControllerFragment, który można dodać na dole pliku układu każdego działania, w którym chcesz pokazać minikontroler.

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

Gdy aplikacja nadawcy odtwarza transmisję wideo lub audio na żywo, pakiet SDK automatycznie wyświetla przycisk odtwarzania/wstrzymywania na minikontrolerze.

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

Dodaj rozwinięty kontroler

Lista kontrolna projektu Google Cast wymaga, aby aplikacja nadawcy miała rozwinięty kontroler do przesyłania multimediów. Rozszerzony kontroler to pełnoekranowa wersja minikontrolera.

Pakiet SDK Cast udostępnia widżet rozwiniętego kontrolera o nazwie ExpandedControllerActivity. To jest klasa abstrakcyjna, którą musisz dodać do podklasy, aby dodać przycisk Cast.

Najpierw utwórz nowy plik zasobów menu dla rozwiniętego kontrolera, aby udostępnić przycisk Cast:

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

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

</menu>

Utwórz nowe zajęcia, które powiększą się o ExpandedControllerActivity.

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 manifeście aplikacji w tagu application:

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

Edytuj CastOptionsProvider i zmień NotificationOptions oraz CastMediaOptions, aby ustawić nową aktywność jako docelową aktywność:

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

Jeśli chcesz ustawić wygląd za pomocą motywów, wybrać przyciski, które mają być wyświetlane, i dodać przyciski niestandardowe, przeczytaj informacje o dostosowywaniu rozwiniętego kontrolera.

Regulacja głośności

Platforma automatycznie zarządza woluminem w aplikacji nadawcy. Platforma automatycznie synchronizuje aplikacje nadawcy i odbiornika internetowego, dzięki czemu interfejs użytkownika nadawcy zawsze raportuje ilość określoną przez odbiornik internetowy.

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

W Androidzie za pomocą fizycznych przycisków na urządzeniu nadawcy można domyślnie zmienić głośność sesji przesyłania w odbiorniku internetowym na każdym urządzeniu korzystającym z Jelly Bean lub nowszego.

Sterowanie głośnością za pomocą przycisków przed pojawieniem się Jelly Bean

Aby używać fizycznych przycisków głośności do sterowania głośnością odbiornika internetowego na urządzeniach z Androidem starszych niż Jelly Bean, aplikacja nadawcy powinna zastąpić parametr dispatchKeyEvent w Aktywności i wywołać metodę CastContext.onDispatchVolumeKeyEventBeforeJellyBean():

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 projektu Google Cast wymaga, aby aplikacja nadawcy zaimplementowała elementy sterujące multimediami w powiadomieniu oraz na ekranie blokady, gdzie nadawca przesyła treści, ale aplikacja nadawcy nie jest skupiona na aplikacji. Platforma udostępnia MediaNotificationService i MediaIntentReceiver, dzięki którym aplikacja nadawcy może utworzyć elementy sterujące multimediami w powiadomieniu i na ekranie blokady.

Aplikacja MediaNotificationService uruchamia się, gdy nadawca przesyła treści, i wyświetla powiadomienie z miniaturą obrazu i informacjami o aktualnie przesyłanym elemencie, przyciskami odtwarzania/wstrzymywania oraz przyciskami zatrzymywania.

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

Twoja aplikacja może skonfigurować sterowanie powiadomieniami i multimediami na ekranie blokady w NotificationOptions. Aplikacja może skonfigurować ustawienia przycisków sterujących, które będą wyświetlane w powiadomieniu i których Activity otwiera się po kliknięciu powiadomienia przez użytkownika. Jeśli działania nie zostały wyraźnie określone, używane są wartości domyślne MediaIntentReceiver.ACTION_TOGGLE_PLAYBACK i MediaIntentReceiver.ACTION_STOP_CASTING.

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

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

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

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

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

Wyświetlanie opcji sterowania multimediami na poziomie powiadomienia i ekranu blokady jest domyślnie włączone. Możesz je wyłączyć, wywołując metodę setNotificationOptions z wartością null w polu CastMediaOptions.Builder. Obecnie funkcja ekranu blokady jest włączona, o ile powiadomienia są włączone.

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 transmisję wideo lub audio na żywo, pakiet SDK automatycznie wyświetla przycisk odtwarzania/zatrzymania zamiast przycisku odtwarzania/wstrzymywania na panelu sterowania powiadomieniami, ale nie na ekranie blokady.

Uwaga: aby wyświetlać elementy sterujące ekranem blokady na urządzeniach starszych niż Lollipop, RemoteMediaClient będzie automatycznie prosić o włączenie dźwięku w Twoim imieniu.

Obsługa błędów

Aplikacje nadawców muszą uwzględniać wszystkie wywołania zwrotne błędów i określać najlepszą odpowiedź na każdy etap cyklu życia przesyłania. Aplikacja może wyświetlać użytkownikowi okna z błędami lub może przerwać połączenie z odbiornikiem internetowym.