Cast in Ihre Android-App einbinden

In diesem Entwicklerleitfaden erfährst du, wie du deiner Android-Absender-App mithilfe des Android Sender SDK Google Cast-Unterstützung hinzufügst.

Das Mobilgerät oder der Laptop ist der Absender, der die Wiedergabe steuert, und das Google Cast-Gerät ist der Empfänger, der die Inhalte auf dem Fernseher anzeigt.

Das Absender-Framework bezieht sich auf das Binärprogramm der Cast-Klassenbibliothek und die zugehörigen Ressourcen, die zur Laufzeit auf dem Absender vorhanden sind. Die Absender-App oder Cast-App bezieht sich auf eine App, die auch auf dem Absender ausgeführt wird. Die Web Receiver App bezieht sich auf die HTML-Anwendung, die auf dem für Google Cast optimierten Gerät ausgeführt wird.

Das Sender-Framework verwendet ein asynchrones Callback-Design, um die Sender-App über Ereignisse zu informieren und zwischen verschiedenen Zuständen des Lebenszyklus der Cast-App zu wechseln.

Anwendungsfluss

In den folgenden Schritten wird der typische Ablauf für eine Android-App des Absenders beschrieben:

  • Das Cast-Framework startet die Geräteerkennung MediaRouter automatisch anhand des Activity-Lebenszyklus.
  • Wenn der Nutzer auf das Cast-Symbol klickt, wird im Cast-Dialogfeld die Liste der erkannten Übertragungsgeräte angezeigt.
  • Wenn der Nutzer ein Übertragungsgerät auswählt, versucht das Framework, die Web Receiver App auf dem Übertragungsgerät zu starten.
  • Das Framework ruft Callbacks in der Absender-App auf, um zu bestätigen, dass die Web Receiver App gestartet wurde.
  • Das Framework erstellt einen Kommunikationskanal zwischen den Absender- und Webempfänger-Apps.
  • Das Framework verwendet den Kommunikationskanal, um die Medienwiedergabe auf dem Webempfänger zu laden und zu steuern.
  • Das Framework synchronisiert den Medienwiedergabestatus zwischen dem Absender und dem Webempfänger: Wenn der Nutzer die Aktionen der Benutzeroberfläche des Absenders durchführt, leitet das Framework diese Mediensteuerungsanfragen an den Webempfänger weiter. Wenn der Webempfänger den Medienstatus aktualisiert, aktualisiert das Framework den Status der Benutzeroberfläche des Absenders.
  • Wenn der Nutzer auf das Cast-Symbol klickt, um die Verbindung zum Übertragungsgerät zu trennen, wird die Verbindung der Absender-App vom Webempfänger vom Framework getrennt.

Eine vollständige Liste aller Klassen, Methoden und Ereignisse im Google Cast Android SDK finden Sie in der Google Cast Sender API-Referenz für Android. In den folgenden Abschnitten wird beschrieben, wie Sie Cast zu Ihrer Android-App hinzufügen.

Android-Manifest konfigurieren

In der AndroidManifest.xml-Datei deiner App musst du die folgenden Elemente für das Cast SDK konfigurieren:

uses-sdk

Legen Sie die Mindest- und Ziel-Android-API-Levels fest, die vom Cast-SDK unterstützt werden. Aktuell ist das Minimum API-Level 19 und das Ziel API-Level 28.

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

Android:Design

Design der App basierend auf der Android SDK-Mindestversion festlegen Wenn Sie beispielsweise kein eigenes Design implementieren, sollten Sie eine Variante von Theme.AppCompat verwenden, wenn Sie eine Ausrichtung auf eine Android SDK-Mindestversion vor Lollipop vornehmen.

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

Cast-Kontext initialisieren

Das Framework hat das globale Singleton-Objekt CastContext, das alle Interaktionen des Frameworks koordiniert.

Die App muss die Schnittstelle OptionsProvider implementieren, um die Optionen zum Initialisieren des Singleton CastContext bereitzustellen. OptionsProvider stellt eine Instanz von CastOptions bereit, die Optionen enthält, die sich auf das Verhalten des Frameworks auswirken. Am wichtigsten ist die ID der Web Receiver-Anwendung, die zum Filtern von Discovery-Ergebnissen und zum Starten der Web Receiver App verwendet wird, wenn eine Streamingsitzung gestartet wird.

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

Sie müssen den voll qualifizierten Namen der implementierten OptionsProvider als Metadatenfeld in der Datei „AndroidManifest.xml“ der Absender-App deklarieren:

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

CastContext wird beim Aufrufen von CastContext.getSharedInstance() verzögert initialisiert.

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

Die Cast-UX-Widgets

Das Cast-Framework stellt die Widgets bereit, die der Cast-Design-Checkliste entsprechen:

  • Einführungs-Overlay: Das Framework bietet eine benutzerdefinierte Ansicht, IntroductoryOverlay, die dem Nutzer angezeigt wird, um die Aufmerksamkeit auf das Cast-Symbol zu lenken, wenn ein Empfänger zum ersten Mal verfügbar ist. Die Absender-App kann den Text und die Position des Titeltexts anpassen.

  • Cast-Symbol: Das Cast-Symbol ist sichtbar, wenn ein Empfänger erkannt wird, der Ihre App unterstützt. Wenn der Nutzer auf das Cast-Symbol klickt, wird ein Dialogfeld angezeigt, in dem die erkannten Geräte aufgeführt werden. Wenn der Nutzer auf das Cast-Symbol klickt, während das Gerät verbunden ist, werden die aktuellen Metadaten (z. B. Titel, Name des Aufnahmestudios und Thumbnail) angezeigt oder der Nutzer kann die Verbindung zum Übertragungsgerät trennen.

  • Mini-Controller: Wenn der Nutzer Inhalte streamt und die aktuelle Inhaltsseite oder den erweiterten Controller von einem anderen Bildschirm in der Absender-App weg verlassen hat, wird der Mini-Controller unten auf dem Bildschirm angezeigt. So kann er die aktuell gestreamten Medienmetadaten sehen und die Wiedergabe steuern.

  • Erweiterter Controller: Wenn der Nutzer Inhalte streamt, wird beim Anklicken des Mediensymbols oder des Mini-Controllers der erweiterte Controller gestartet. Dieser zeigt die aktuell wiedergebenen Metadaten an und bietet mehrere Schaltflächen zur Steuerung der Medienwiedergabe.

  • Benachrichtigung: Nur Android. Wenn der Nutzer Inhalte streamt und die Sender-App verlässt, wird eine Medienbenachrichtigung mit den aktuellen Streaming-Metadaten und Wiedergabesteuerung angezeigt.

  • Sperrbildschirm: Nur für Android. Wenn der Nutzer Inhalte überträgt und eine Zeitüberschreitung beim Aufrufen des Sperrbildschirms auslöst, wird eine Medien-Sperrbildschirmsteuerung mit den aktuellen Streaming-Metadaten und Wiedergabesteuerung angezeigt.

In der folgenden Anleitung wird beschrieben, wie Sie diese Widgets Ihrer App hinzufügen.

Cast-Symbol hinzufügen

Die Android MediaRouter APIs wurden entwickelt, um die Anzeige und Wiedergabe von Medien auf sekundären Geräten zu ermöglichen. Android-Apps, die die MediaRouter API verwenden, sollten in ihre Benutzeroberfläche ein Cast-Symbol enthalten, damit Nutzer eine Medienroute auswählen können, um Medien auf einem sekundären Gerät, z. B. einem Cast-Gerät, abzuspielen.

Das Framework vereinfacht das Hinzufügen von MediaRouteButton als Cast button. Sie sollten zuerst einen Menüpunkt oder ein MediaRouteButton in der XML-Datei hinzufügen, mit der Ihr Menü definiert wird, und CastButtonFactory verwenden, um es mit dem Framework zu verbinden.

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

Wenn Ihr Activity dann von FragmentActivity übernommen wird, können Sie Ihrem Layout einen MediaRouteButton hinzufügen.

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

Wie Sie die Darstellung des Cast-Symbols mit einem Design festlegen, erfahren Sie unter Cast-Symbol anpassen.

Geräteerkennung konfigurieren

Die Geräteerkennung wird vollständig von CastContext verwaltet. Beim Initialisieren von CastContext gibt die Absender-App die Web Receiver-Anwendungs-ID an und kann optional die Namespace-Filterung durch Festlegen von supportedNamespaces in CastOptions anfordern. CastContext enthält einen Verweis auf das MediaRouter-Objekt und startet den Erkennungsprozess, wenn die Absender-App in den Vordergrund wechselt, und endet, wenn die Absender-App in den Hintergrund wechselt.

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

Funktionsweise der Sitzungsverwaltung

Das Cast SDK stellt das Konzept einer Cast-Sitzung vor, bei der die Schritte zum Herstellen einer Verbindung zu einem Gerät, zum Starten (oder Beitreten) einer Web Receiver-App, zum Herstellen einer Verbindung zu dieser App und zum Initialisieren eines Mediensteuerungskanals definiert werden. Weitere Informationen zu Cast-Sitzungen und dem Lebenszyklus des Webempfängers finden Sie im Leitfaden zum Lebenszyklus von Anwendungen.

Sitzungen werden von der Klasse SessionManager verwaltet, auf die Ihre App über CastContext.getSessionManager() zugreifen kann. Einzelne Sitzungen werden durch abgeleitete Klassen der Klasse Session dargestellt. CastSession steht beispielsweise für Sitzungen mit Übertragungsgeräten. Ihre App kann über SessionManager.getCurrentCastSession() auf die derzeit aktive Cast-Sitzung zugreifen.

Ihre Anwendung kann die Klasse SessionManagerListener verwenden, um Sitzungsereignisse wie das Erstellen, Sperren, Wiederaufnahme und Beendigung zu überwachen. Das Framework versucht automatisch, bei einer abnormalen/abrupten Beendigung eine Sitzung fortzusetzen, während eine Sitzung aktiv war.

Sitzungen werden als Reaktion auf Nutzerbewegungen aus den Dialogfeldern MediaRouter erstellt und automatisch gelöscht.

Zum besseren Verständnis von Cast-Startfehlern können Apps CastContext#getCastReasonCodeForCastStatusCode(int) verwenden, um den Sitzungsstartfehler in CastReasonCodes zu konvertieren. Einige Sitzungsstartfehler (z.B. CastReasonCodes#CAST_CANCELLED) sind beabsichtigt und sollten nicht als Fehler geloggt werden.

Wenn Sie sich über die Statusänderungen für die Sitzung informieren möchten, können Sie einen SessionManagerListener implementieren. In diesem Beispiel wird die Verfügbarkeit einer CastSession in einer Activity überwacht.

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

Stream-Übertragung

Die Beibehaltung des Sitzungsstatus ist die Grundlage der Streamübertragung, bei der Nutzer vorhandene Audio- und Videostreams mithilfe von Sprachbefehlen, der Google Home App oder Smart Displays zwischen Geräten verschieben können. Die Medienwiedergabe wird auf einem Gerät (Quelle) beendet und auf einem anderen (Ziel) fortgesetzt. Jedes Übertragungsgerät mit der neuesten Firmware kann als Quellen oder Ziele in einer Streamübertragung verwendet werden.

Wenn Sie das neue Zielgerät während der Stream-Übertragung oder -Erweiterung abrufen möchten, registrieren Sie eine Cast.Listener mit CastSession#addCastListener. Rufen Sie dann CastSession#getCastDevice() während des onDeviceNameChanged-Callbacks auf.

Weitere Informationen finden Sie unter Streamübertragung auf Webempfänger.

Automatische Neuverbindung

Das Framework bietet eine ReconnectionService, die von der Absender-App aktiviert werden kann, um die Verbindung in vielen subtilen Fällen zu verarbeiten, z. B.:

  • Wiederherstellung nach vorübergehendem WLAN-Verlust
  • Erholung nach Schlafenszeit
  • Hintergrund aus der App wiederherstellen
  • Wiederherstellung nach Absturz der App

Dieser Dienst ist standardmäßig aktiviert und kann in CastOptions.Builder deaktiviert werden.

Dieser Dienst kann automatisch mit dem Manifest Ihrer App zusammengeführt werden, wenn in Ihrer Gradle-Datei die automatische Zusammenführung aktiviert ist.

Das Framework startet den Dienst, wenn es eine Mediensitzung gibt, und beendet es, wenn die Mediensitzung endet.

Funktionsweise der Mediensteuerung

Das Cast-Framework stellt die RemoteMediaPlayer-Klasse aus Cast 2.x ein. Stattdessen wird eine neue Klasse RemoteMediaClient verwendet, die dieselbe Funktion in einer Reihe praktischerer APIs bietet. Ein GoogleApiClient muss nicht übergeben werden.

Wenn Ihre Anwendung eine CastSession mit einer Web Receiver-App einrichtet, die den Medien-Namespace unterstützt, wird automatisch eine Instanz von RemoteMediaClient vom Framework erstellt. Ihre Anwendung kann darauf zugreifen, indem Sie die Methode getRemoteMediaClient() für die Instanz CastSession aufrufen.

Bei allen Methoden von RemoteMediaClient, die Anfragen an den Webempfänger senden, wird ein Ausstehendes Ergebnis zurückgegeben, mit dem diese Anfrage erfasst werden kann.

Es wird erwartet, dass die Instanz von RemoteMediaClient von mehreren Teilen Ihrer Anwendung und einigen internen Komponenten des Frameworks gemeinsam genutzt werden kann, z. B. den nichtflüchtigen Controllern und dem Benachrichtigungsdienst. Zu diesem Zweck unterstützt diese Instanz die Registrierung mehrerer Instanzen von RemoteMediaClient.Listener.

Medienmetadaten festlegen

Die Klasse MediaMetadata stellt die Informationen zu einem Medienelement dar, das Sie streamen möchten. Im folgenden Beispiel wird eine neue MediaMetadata-Instanz eines Films erstellt und der Titel, der Untertitel sowie zwei Bilder festgelegt.

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))));

Weitere Informationen zur Verwendung von Bildern mit Medienmetadaten finden Sie unter Bildauswahl.

Medien laden

Ihre App kann ein Medienelement laden, wie im folgenden Code dargestellt. Verwenden Sie zuerst MediaInfo.Builder mit den Metadaten der Medien, um eine MediaInfo-Instanz zu erstellen. Rufen Sie die RemoteMediaClient der aktuellen CastSession ab und laden Sie dann die MediaInfo in diese RemoteMediaClient. Mit RemoteMediaClient kannst du die auf dem Webempfänger ausgeführten Mediaplayer-App abspielen, pausieren und anderweitig steuern.

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());

Weitere Informationen finden Sie im Abschnitt Medien-Tracks verwenden.

4K-Videoformat

Wenn Sie prüfen möchten, welches Videoformat Ihre Medien haben, verwenden Sie getVideoInfo() in MediaStatus, um die aktuelle Instanz von VideoInfo abzurufen. Diese Instanz enthält die Art des HDR-TV-Formats sowie die Höhe und Breite des Displays in Pixeln. Varianten im 4K-Format werden durch Konstanten HDR_TYPE_* gekennzeichnet.

Benachrichtigungen per Fernzugriff an mehrere Geräte steuern

Während des Streamings erhalten andere Android-Geräte im selben Netzwerk eine Benachrichtigung, über die sie die Wiedergabe steuern können. Jeder, dessen Benachrichtigungen auf dem Gerät erscheinen, kann sie in den Einstellungen unter Google > Google Cast > Benachrichtigungen zur Fernbedienung anzeigen deaktivieren. Die Benachrichtigungen enthalten eine Verknüpfung zur App „Einstellungen“. Weitere Informationen finden Sie unter Benachrichtigungen für die Cast-Fernbedienung.

Mini-Controller hinzufügen

Gemäß der Checkliste für das Cast-Design sollte eine Absender-App ein dauerhaftes Steuerelement bereitstellen, das als Mini-Controller bezeichnet wird. Es sollte angezeigt werden, wenn der Nutzer von der aktuellen Inhaltsseite zu einem anderen Teil der Absender-App wechselt. Der Mini-Controller bietet dem Nutzer eine sichtbare Erinnerung für die aktuelle Cast-Sitzung. Durch Tippen auf den Mini-Controller kann der Nutzer zur Vollbildansicht des gestreamten Controllers zurückkehren.

Das Framework bietet eine benutzerdefinierte Ansicht namens MiniControllerFragment, die Sie am Ende der Layoutdatei jeder Aktivität, in der der Mini-Controller angezeigt werden soll, hinzufügen können.

<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" />

Wenn die Absender-App ein Video oder einen Audio-Livestream abspielt, zeigt das SDK im Mini-Controller automatisch die Schaltfläche für Wiedergabe/Stopp an.

Informationen zum Festlegen der Textdarstellung des Titels und Untertitels dieser benutzerdefinierten Ansicht und zum Auswählen von Schaltflächen finden Sie unter Mini-Controller anpassen.

Erweiterten Controller hinzufügen

Die Google Cast Design Checkliste erfordert, dass eine Absender-App einen erweiterten Controller für die übertragenen Medien bereitstellt. Der erweiterte Controller ist eine Vollbildversion des Mini-Controllers.

Das Cast SDK bietet ein Widget für den erweiterten Controller ExpandedControllerActivity. Dies ist eine abstrakte Klasse, die Sie abgeleitet haben müssen, um ein Cast-Symbol hinzuzufügen.

Erstellen Sie zuerst eine neue Menüressourcendatei für den maximierten Controller, um das Cast-Symbol bereitzustellen:

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

Erstellen Sie eine neue Klasse, die ExpandedControllerActivity erweitert.

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

Deklariere jetzt deine neue Aktivität im App-Manifest im application-Tag:

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

Bearbeiten Sie CastOptionsProvider und ändern Sie NotificationOptions und CastMediaOptions, um die Zielaktivität auf die neue Aktivität festzulegen:

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();
}

Aktualisieren Sie die Methode LocalPlayerActivity loadRemoteMedia, um Ihre neuen Aktivitäten anzuzeigen, wenn die Remote-Medien geladen werden:

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());
}

Wenn die Absender-App ein Video oder einen Audio-Livestream abspielt, zeigt das SDK im erweiterten Controller automatisch die Schaltfläche für Wiedergabe/Stopp an.

Wenn Sie die Darstellung mithilfe von Designs festlegen möchten, wählen Sie aus, welche Schaltflächen angezeigt werden sollen, und fügen Sie dann benutzerdefinierte Schaltflächen hinzu, um den erweiterten Controller anzupassen.

Lautstärkeregelung

Das Framework verwaltet automatisch die Lautstärke für die Absender-App. Das Framework synchronisiert automatisch die Apps von Absender und Webempfänger, sodass die Absender-UI immer die vom Webempfänger angegebene Lautstärke meldet.

Lautstärkeregelung der Tasten

Unter Android können die physischen Tasten auf dem Absendergerät verwendet werden, um die Lautstärke der Streamingsitzung auf dem Webempfänger für jedes Gerät mit Jelly Bean oder höher zu ändern.

Lautstärkeregelung der Lautstärketaste vor Jelly Bean

Wenn Sie mit den physischen Lautstärketasten die Lautstärke des Web Receiver-Geräts auf Android-Geräten steuern möchten, die älter als Jelly Bean sind, sollte die Absender-App in ihren Aktivitäten dispatchKeyEvent überschreiben und CastContext.onDispatchVolumeKeyEventBeforeJellyBean() aufrufen:

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

Mediensteuerelemente zum Benachrichtigungs- und Sperrbildschirm hinzufügen

Nur für Android: In der Checkliste für Design für Google Cast muss eine Absender-App Mediensteuerelemente in einer Benachrichtigung implementieren und auf dem Sperrbildschirm, auf dem der Absender gestreamt wird, der Fokus aber nicht auf der Absender-App liegt. Das Framework bietet MediaNotificationService und MediaIntentReceiver, um die Absender-App beim Erstellen von Mediensteuerelementen in einer Benachrichtigung und auf dem Sperrbildschirm zu unterstützen.

MediaNotificationService wird beim Übertragen des Senders ausgeführt. Außerdem werden eine Benachrichtigung mit einer Miniaturansicht und Informationen zum aktuellen Streamingelement, einer Wiedergabe-/Pause-Schaltfläche und einer Stopp-Schaltfläche angezeigt.

MediaIntentReceiver ist ein BroadcastReceiver, der Nutzeraktionen in der Benachrichtigung verarbeitet.

Ihre App kann die Benachrichtigungs- und Mediensteuerung über NotificationOptions vom Sperrbildschirm aus konfigurieren. Deine App kann konfigurieren, welche Steuerelemente in der Benachrichtigung angezeigt werden sollen und welche Activity geöffnet werden sollen, wenn der Nutzer auf die Benachrichtigung tippt. Wenn Aktionen nicht explizit angegeben werden, werden die Standardwerte MediaIntentReceiver.ACTION_TOGGLE_PLAYBACK und MediaIntentReceiver.ACTION_STOP_CASTING verwendet.

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();

Die Anzeige von Mediensteuerelementen auf dem Benachrichtigungs- und Sperrbildschirm ist standardmäßig aktiviert. Sie können sie deaktivieren, indem Sie setNotificationOptions in CastMediaOptions.Builder mit Null aufrufen. Aktuell ist die Sperrbildschirmfunktion aktiviert, solange die Benachrichtigung aktiviert ist.

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();

Wenn Ihre Absender-App einen Video- oder Audio-Livestream abspielt, zeigt das SDK automatisch eine Wiedergabe-/Stopp-Schaltfläche anstelle der Wiedergabe-/Pause-Schaltfläche in der Benachrichtigungssteuerung, aber nicht auf dem Sperrbildschirm an.

Hinweis: Um Steuerelemente für die Displaysperre auf Geräten vor der Lollipop anzuzeigen, fordert RemoteMediaClient automatisch einen Audiofokus an.

Fehler verarbeiten

Es ist sehr wichtig, dass Absender-Apps alle Fehler-Callbacks verarbeiten und die beste Antwort für jede Phase des Cast-Lebenszyklus festlegen. Die Anwendung kann Fehlerdialogen für den Nutzer anzeigen oder die Verbindung zum Webempfänger trennen.