Cast in Android-App integrieren

In diesem Entwicklerleitfaden wird beschrieben, wie du deiner Android-Sender-App mithilfe des Android Sender SDK die Google Cast-Unterstützung hinzufügst.

Das Mobilgerät oder der Laptop ist der Sender, der die Wiedergabe steuert, und das Google Cast-Gerät ist der Receiver, 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-App, die auf dem für Google Cast optimierten Gerät ausgeführt wird.

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

Die folgenden Schritte beschreiben den typischen allgemeinen Ausführungsablauf für eine Absender-Android-App:

  • Das Cast-Framework startet automatisch die Geräteerkennung MediaRouter basierend auf dem Activity-Lebenszyklus.
  • Wenn der Nutzer auf das Cast-Symbol klickt, zeigt das Framework das Cast-Dialogfeld mit der Liste der erkannten Übertragungsgeräte an.
  • Wenn der Nutzer ein Übertragungsgerät auswählt, versucht das Framework, die Web Receiver App auf dem Übertragungsgerät zu starten.
  • Das Framework ruft in der Absenderanwendung Callbacks auf, um zu bestätigen, dass die Web Receiver-App gestartet wurde.
  • Das Framework erstellt einen Kommunikationskanal zwischen der Absender- und der Webempfänger-Anwendung.
  • Das Framework verwendet den Kommunikationskanal, um die Medienwiedergabe auf dem Webempfänger zu laden und zu steuern.
  • Das Framework synchronisiert den Status der Medienwiedergabe zwischen Sender und Web Receiver: Wenn der Nutzer UI-Aktionen des Absenders durchführt, übergibt das Framework diese Mediensteuerungsanfragen an den Web Receiver. Wenn der Webempfänger Aktualisierungen zum Medienstatus sendet, aktualisiert das Framework den Status der Absender-UI.
  • Wenn der Nutzer auf das Cast-Symbol klickt, um die Verbindung zum Übertragungsgerät zu trennen, trennt das Framework die Absender-App vom Web Receiver.

Eine umfassende 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 erfahren Sie, wie Sie Google Cast zu Ihrer Android-App hinzufügen.

Android-Manifest konfigurieren

In der Datei AndroidManifest.xml Ihrer App müssen Sie die folgenden Elemente für das Cast SDK konfigurieren:

uses-sdk

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

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

android:theme

Legen Sie das Design Ihrer App basierend auf der Android SDK-Mindestversion fest. Wenn Sie beispielsweise kein eigenes Design implementieren, sollten Sie eine Variante von Theme.AppCompat verwenden, wenn Sie eine Android SDK-Mindestversion vor Lollipop verwenden.

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

Cast-Kontext initialisieren

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

In deiner App muss die Schnittstelle OptionsProvider implementiert werden, um Optionen zum Initialisieren des CastContext-Singleton anzugeben. OptionsProvider stellt eine Instanz von CastOptions bereit, die Optionen enthält, die das Verhalten des Frameworks beeinflussen. Die wichtigste davon ist die Web Receiver-App-ID, die zum Filtern von Erkennungsergebnissen und zum Starten der Web Receiver-App beim Start einer Cast-Sitzung verwendet 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;
    }
}

Du musst den voll qualifizierten Namen der implementierten OptionsProvider als Metadatenfeld in der AndroidManifest.xml-Datei der Absender-App angeben:

<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 Checkliste für das Cast-Design entsprechen:

  • Einführendes 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 Sender-App kann den Text und die Position des Titeltexts anpassen.

  • Cast-Symbol: Das Cast-Symbol ist unabhängig von der Verfügbarkeit von Übertragungsgeräten sichtbar. Wenn der Nutzer zum ersten Mal auf das Cast-Symbol klickt, wird ein Cast-Dialogfeld angezeigt, in dem die erkannten Geräte aufgeführt sind. Wenn der Nutzer auf das Cast-Symbol klickt, während das Gerät mit dem Übertragungsgerät verbunden ist, werden die aktuellen Medienmetadaten wie der Titel, der Name des Aufnahmestudios und eine Miniaturansicht angezeigt oder der Nutzer kann die Verbindung zum Übertragungsgerät trennen. Das Cast-Symbol wird manchmal auch als „Cast-Symbol“ bezeichnet.

  • Mini-Controller: Wenn der Nutzer Inhalte überträgt und von der aktuellen Inhaltsseite oder dem erweiterten Controller zu einem anderen Bildschirm in der Sender-App navigiert ist, wird der Mini-Controller unten auf dem Bildschirm angezeigt, damit der Nutzer die aktuell gestreamten Medienmetadaten sehen und die Wiedergabe steuern kann.

  • Erweiterter Controller: Wenn der Nutzer beim Streamen von Inhalten auf die Medienbenachrichtigung oder den Mini-Controller klickt, wird der maximierte Controller gestartet. Dieser zeigt die aktuell wiedergegebenen Medienmetadaten an und bietet mehrere Schaltflächen zur Steuerung der Medienwiedergabe.

  • Benachrichtigung: nur für Android. Wenn der Nutzer Inhalte streamt und die Absender-App verlässt, wird eine Medienbenachrichtigung mit den aktuell gestreamten Medienmetadaten und Steuerelementen für die Wiedergabe angezeigt.

  • Sperrbildschirm: nur für Android. Wenn der Nutzer Inhalte streamt und zum Sperrbildschirm wechselt (oder beim Gerät eine Zeitüberschreitung auftritt), wird ein Steuerelement auf dem Sperrbildschirm für Medien angezeigt, das die aktuell gestreamten Medienmetadaten und Steuerelemente für die Wiedergabe zeigt.

In der folgenden Anleitung wird beschrieben, wie du deiner App diese Widgets hinzufügst.

Cast-Symbol hinzufügen

Die Android MediaRouter APIs wurden entwickelt, um die Medienanzeige und -wiedergabe auf sekundären Geräten zu ermöglichen. Android-Apps, die die MediaRouter API verwenden, sollten ein Cast-Symbol in die Benutzeroberfläche aufnehmen, damit Nutzer eine Medienroute auswählen können, um Medien auf einem sekundären Gerät wie einem Übertragungsgerät abzuspielen.

Mit diesem Framework ist das Hinzufügen eines MediaRouteButton als Cast button sehr einfach. Fügen Sie zuerst einen Menüpunkt oder ein MediaRouteButton in die XML-Datei ein, die das Menü definiert, und verwenden Sie dann CastButtonFactory, 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 dann Activity von FragmentActivity übernommen wird, können Sie Ihrem Layout eine 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);
}

Informationen dazu, wie das Cast-Symbol mithilfe eines Designs dargestellt wird, finden Sie unter Cast-Symbol anpassen.

Geräteerkennung konfigurieren

Die Geräteerkennung wird vollständig vom CastContext verwaltet. Beim Initialisieren von CastContext gibt die Absender-App die Web Receiver-Anwendungs-ID an und kann optional die Namespace-Filterung anfordern, indem sie supportedNamespaces in CastOptions festlegt. CastContext enthält intern einen Verweis auf die MediaRouter und startet den Erkennungsprozess unter den folgenden Bedingungen:

  • Die Erkennung basiert auf einem Algorithmus, der ein ausgewogenes Verhältnis zwischen der Latenz bei der Geräteerkennung und der Akkunutzung bietet, und wird gelegentlich automatisch gestartet, wenn die Absender-App im Vordergrund ausgeführt wird.
  • Das Dialogfeld „Streamen“ ist geöffnet.
  • Das Cast SDK versucht, eine Cast-Sitzung wiederherzustellen.

Der Erkennungsprozess wird beendet, wenn das Cast-Dialogfeld geschlossen wird oder 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

Mit dem Cast SDK wird das Konzept einer Cast-Sitzung eingeführt. Dabei werden alle Schritte kombiniert, um eine Verbindung zu einem Gerät herzustellen, eine Web-Receiver-App zu starten (oder der App beizutreten), eine Verbindung zu dieser App herzustellen und einen Mediensteuerungskanal zu initialisieren. Weitere Informationen zu Streamingsitzungen und zum Web Receiver-Lebenszyklus findest du im Leitfaden zum Lebenszyklus von Anwendungen für Web Receiver.

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

Ihre Anwendung kann die Klasse SessionManagerListener verwenden, um Sitzungsereignisse wie Erstellung, Sperrung, Wiederaufnahme und Beendigung zu überwachen. Das Framework versucht automatisch, den Vorgang nach einer ungewöhnlichen/abrupten Beendigung fortzusetzen, während eine Sitzung aktiv war.

Sitzungen werden als Reaktion auf Nutzergesten in den MediaRouter-Dialogfeldern automatisch erstellt und gelöscht.

Zum besseren Verständnis von Übertragungsfehlern können Apps den Sitzungsstartfehler mit CastContext#getCastReasonCodeForCastStatusCode(int) in CastReasonCodes konvertieren. Einige Fehler beim Sitzungsstart (z.B. CastReasonCodes#CAST_CANCELLED) sind beabsichtigt und sollten nicht als Fehler protokolliert werden.

Wenn Sie die Statusänderungen für die Sitzung kennen müssen, können Sie eine SessionManagerListener implementieren. In diesem Beispiel wird die Verfügbarkeit einer CastSession in einem 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. Nutzer können vorhandene Audio- und Videostreams mithilfe von Sprachbefehlen, der Google Home App oder Smart Displays auf andere Geräte verschieben. Die Medienwiedergabe wird auf einem Gerät (Quelle) und auf einem anderen Gerät (dem Ziel) beendet. Jedes Übertragungsgerät mit der neuesten Firmware kann als Quellen oder Ziele in einer Streamübertragung dienen.

Registrieren Sie mit dem CastSession#addCastListener ein Cast.Listener, um das neue Zielgerät während einer Streamübertragung oder -erweiterung abzurufen. Rufe dann während des onDeviceNameChanged-Callbacks CastSession#getCastDevice() auf.

Weitere Informationen finden Sie unter Stream-Übertragung mit Web Receiver.

Automatische Wiederherstellung der Verbindung

Das Framework bietet ein ReconnectionService, das von der Absender-App aktiviert werden kann, um die erneute Verbindung in vielen subtilen Fällen zu ermöglichen, z. B.:

  • Wiederherstellung nach einem vorübergehenden WLAN-Ausfall
  • Aus Geräte-Ruhemodus wiederherstellen
  • Nach Hintergrundwiedergabe wiederherstellen
  • Wiederherstellung nach einem 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 die automatische Zusammenführung in Ihrer Gradle-Datei aktiviert ist.

Das Framework startet den Dienst, wenn eine Mediensitzung stattfindet, und beendet ihn, wenn die Mediensitzung endet.

So funktioniert die Mediensteuerung

Im Cast-Framework wird die Klasse RemoteMediaPlayer von Cast 2.x durch die neue Klasse RemoteMediaClient eingestellt. Diese bietet die gleiche Funktionalität in einer Reihe praktischerer APIs und vermeidet die Übergabe eines GoogleApiClient.

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

Alle Methoden von RemoteMediaClient, die Anfragen an den Web Receiver senden, geben ein PendingResult-Objekt zurück, mit dem diese Anfrage verfolgt werden kann.

Es wird erwartet, dass die Instanz von RemoteMediaClient von mehreren Teilen Ihrer App und in der Tat von einigen internen Komponenten des Frameworks gemeinsam genutzt wird, z. B. den persistenten Mini-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 Titel, Untertitel und 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 Medienelemente laden, wie im folgenden Code gezeigt. Verwenden Sie zuerst MediaInfo.Builder mit den Metadaten des Mediums, um eine MediaInfo-Instanz zu erstellen. Rufen Sie die RemoteMediaClient aus dem aktuellen CastSession ab und laden Sie dann das MediaInfo in diese RemoteMediaClient. Mit RemoteMediaClient können Sie eine Mediaplayer-App auf dem Web Receiver 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 den Typ des HDR TV-Formats sowie die Höhe und Breite des Bildschirms in Pixeln. Varianten des 4K-Formats sind durch die Konstanten HDR_TYPE_* angegeben.

Ferngesteuerte Benachrichtigungen an mehrere Geräte

Wenn ein Nutzer Inhalte überträgt, erhalten andere Android-Geräte im selben Netzwerk eine Benachrichtigung, über die sie ebenfalls die Wiedergabe steuern können. Jeder, dessen Gerät solche Benachrichtigungen erhält, kann die Benachrichtigungen für dieses Gerät in den Einstellungen unter „Google“ > „Google Cast“ > Fernbedienungs-Benachrichtigungen anzeigen deaktivieren. (Die Benachrichtigungen enthalten eine Verknüpfung zur App „Einstellungen“.) Weitere Informationen finden Sie unter Benachrichtigungen zur Cast-Fernsteuerung.

Mini-Controller hinzufügen

Gemäß der Checkliste für das Streaming-Design sollte eine Sender-App eine dauerhafte Steuerung bieten, den sogenannten Mini-Controller. Dieser sollte angezeigt werden, wenn der Nutzer von der aktuellen Inhaltsseite zu einem anderen Teil der Sender-App wechselt. Der Mini-Controller zeigt dem Nutzer eine sichtbare Erinnerung an die aktuelle Streaming-Sitzung an. Durch Tippen auf den Mini-Controller gelangt er zur erweiterten Controller-Vollbildansicht für Cast.

Das Framework bietet die benutzerdefinierte Ansicht 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 Sender-App einen Video- oder Audio-Livestream wiedergibt, zeigt das SDK automatisch eine Wiedergabe-/Stopp-Schaltfläche anstelle der Wiedergabe-/Pause-Schaltfläche auf dem Mini-Controller an.

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

Maximierten Controller hinzufügen

Gemäß der Google Cast-Design-Checkliste muss eine Sender-App einen erweiterten Controller für die zu streamenden Medien bereitstellen. Der erweiterte Controller ist eine Vollbildversion des Mini-Controllers.

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

Erstelle zuerst eine neue Menüressourcendatei für den erweiterten 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 innerhalb des application-Tags:

<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 die 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 LocalPlayerActivity-Methode loadRemoteMedia, damit die neue Aktivität angezeigt wird, wenn die Remotemedien 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 Sender-App einen Video- oder Audio-Livestream wiedergibt, zeigt das SDK im erweiterten Controller automatisch eine Wiedergabe-/Stopp-Schaltfläche anstelle der Wiedergabe-/Pause-Schaltfläche an.

Wenn du die Darstellung mithilfe von Designs festlegen möchtest, wähle die anzuzeigenden Schaltflächen aus und füge benutzerdefinierte Schaltflächen hinzu. Weitere Informationen findest du unter Erweiterten Controller anpassen.

Lautstärkeregelung

Das Framework verwaltet automatisch das Volume für die Sender-App. Das Framework synchronisiert automatisch die Sender- und die Web Receiver-Anwendung, sodass die Sender-UI immer das von Web Receiver angegebene Volumen meldet.

Lautstärkeregelung mit physischer Taste

Auf Android-Geräten können die physischen Tasten auf dem Gerät des Absenders verwendet werden, um die Lautstärke der Übertragungssitzung auf dem Web Receiver standardmäßig für jedes Gerät zu ändern, auf dem Jeelly Bean oder eine neuere Version verwendet wird.

Lautstärkeregelung mit physischer Taste vor Jelly Bean

Wenn Sie die Lautstärke des Web Receiver-Geräts auf Android-Geräten, die älter als Jelly Bean sind, mit den physischen Lautstärketasten regeln möchten, muss die Absender-App dispatchKeyEvent in ihren Aktivitäten ü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 in Benachrichtigungen und Sperrbildschirm hinzufügen

Nur auf Android-Geräten muss gemäß der Google Cast-Design-Checkliste eine Absender-App Mediensteuerelemente in einer Benachrichtigung und im Sperrbildschirm implementieren, auf dem der Absender etwas überträgt, die Absender-App jedoch nicht hervorgehoben ist. Das Framework bietet MediaNotificationService und MediaIntentReceiver, damit die Absender-App Mediensteuerelemente in einer Benachrichtigung und auf dem Sperrbildschirm erstellen kann.

MediaNotificationService wird ausgeführt, wenn der Sender etwas überträgt, und zeigt eine Benachrichtigung mit einer Miniaturansicht und Informationen zum aktuell gestreamten Element sowie eine Schaltfläche für Wiedergabe/Pause und eine Stopp-Schaltfläche an.

MediaIntentReceiver ist eine BroadcastReceiver, die Nutzeraktionen aus der Benachrichtigung verarbeitet.

Deine App kann die Benachrichtigungs- und Mediensteuerung über NotificationOptions vom Sperrbildschirm aus konfigurieren. Deine App kann konfigurieren, welche Steuerschaltflächen in der Benachrichtigung angezeigt werden und welches Activity geöffnet wird, 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 der Mediensteuerung für Benachrichtigungen und den Sperrbildschirm ist standardmäßig aktiviert. Sie können sie deaktivieren, indem Sie setNotificationOptions mit dem Wert „null“ in CastMediaOptions.Builder aufrufen. Derzeit ist die Sperrbildschirm-Funktion aktiviert, solange Benachrichtigungen aktiviert sind.

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 die Sender-App einen Video- oder Audio-Livestream wiedergibt, zeigt das SDK automatisch auf dem Benachrichtigungssteuerelement, aber nicht auf dem Sperrbildschirm eine Wiedergabe-/Stopp-Schaltfläche anstelle der Wiedergabe-/Pause-Schaltfläche an.

Hinweis: Zum Einblenden von Steuerelementen für den Sperrbildschirm auf älteren Geräten als Lollipop fordert RemoteMediaClient automatisch den Audiofokus für Sie an.

Fehler verarbeiten

Absender-Apps müssen alle Fehlerrückrufe verarbeiten und für jede Phase des Cast-Lebenszyklus die beste Antwort festlegen. Die App kann dem Nutzer Fehlerdialogfelder anzeigen oder beschließen, die Verbindung zum Web Receiver zu trennen.