Cast in Android-App integrieren

In diesem Entwicklerleitfaden wird beschrieben, wie du deiner Android-Sender-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 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 ebenfalls 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 Sender-Framework verwendet ein asynchrones Callback-Design, um die Sender-App über Ereignisse zu informieren und zwischen verschiedenen Status 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 MediaRouter-Geräteerkennung basierend auf dem Activity-Lebenszyklus.
  • Wenn der Nutzer auf das Cast-Symbol klickt, zeigt das Framework das Streaming-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 Callbacks in der Sender-App auf, um zu bestätigen, dass die Web-Empfänger-App gestartet wurde.
  • Das Framework erstellt einen Kommunikationskanal zwischen den Sender- und Webempfängeranwendungen.
  • 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 Webempfänger: Wenn der Nutzer UI-Aktionen des Absenders ausführt, leitet das Framework diese Mediensteuerungsanfragen an den Webempfänger weiter. Wenn der Webempfänger Aktualisierungen des Medienstatus sendet, aktualisiert das Framework den Status der Sender-UI.
  • Wenn der Nutzer auf das Cast-Symbol klickt, um die Verbindung zum Übertragungsgerät zu trennen, trennt das Framework die Sender-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 die folgenden Elemente für das Cast SDK konfiguriert werden:

uses-sdk

Legen Sie die minimalen und Ziel-Android-API-Levels fest, die vom Cast SDK unterstützt werden. Derzeit ist das Minimum 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 entsprechend der Android SDK-Mindestversion fest. Wenn du beispielsweise kein eigenes Design implementierst, solltest du eine Variante von Theme.AppCompat verwenden, wenn du eine Android SDK-Mindestversion vor Lollipop angibst.

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

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 des 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 verzögert initialisiert, wenn CastContext.getSharedInstance() aufgerufen wird.

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:

  • Einleitendes 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-Anwendung kann den Text und die Position des Titeltexts anpassen.

  • Cast-Symbol: Das Cast-Symbol ist unabhängig davon sichtbar, ob Übertragungsgeräte verfügbar sind. Wenn der Nutzer das erste Mal auf das Cast-Symbol klickt, wird ein Cast-Dialogfeld mit den erkannten Geräten angezeigt. Wenn der Nutzer bei verbundenem Gerät auf das Cast-Symbol klickt, werden die aktuellen Medienmetadaten wie Titel, Name des Aufnahmestudios und ein Thumbnail angezeigt oder er kann die Verbindung zum Übertragungsgerät trennen. Das Cast-Symbol wird manchmal auch als Cast-Symbol bezeichnet.

  • Mini-Controller: Wenn der Nutzer Inhalte streamt und von der aktuellen Inhaltsseite oder dem maximierten Controller zu einem anderen Bildschirm in der Sender-App gewechselt 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 Inhalte streamt und auf die Medienbenachrichtigung oder den Mini-Controller klickt, wird der maximierte Controller gestartet. Dabei werden die Metadaten der aktuell wiedergegebenen Medien sowie mehrere Schaltflächen zur Steuerung der Medienwiedergabe angezeigt.

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

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

Im folgenden Leitfaden wird beschrieben, wie Sie Ihrer App diese Widgets hinzufügen.

Cast-Symbol hinzufügen

Die Android MediaRouter APIs ermöglichen die Anzeige und Wiedergabe von Medien auf sekundären Geräten. Android-Apps, die die MediaRouter API verwenden, sollten in der Benutzeroberfläche ein Cast-Symbol enthalten, über das Nutzer eine Medienroute auswählen können, um Medien auf einem zweiten Gerät wie einem Übertragungsgerät abzuspielen.

Mit dem Framework ist das Hinzufügen eines MediaRouteButton als Cast button sehr einfach. Fügen Sie zuerst ein Menüelement oder ein MediaRouteButton in die XML-Datei ein, die Ihr Menü definiert, und verwenden Sie 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 die Activity von FragmentActivity übernimmt, 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);
}

Weitere Informationen zur Darstellung des Cast-Symbols mithilfe eines Designs finden Sie unter Streamen-Schaltfläche anpassen.

Geräteerkennung konfigurieren

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

  • Er basiert auf einem Algorithmus, der die Latenz bei der Geräteerkennung und die Akkunutzung ausgleicht. Die Erkennung wird gelegentlich automatisch gestartet, wenn die Absender-App den Vordergrund betritt.
  • Das Cast-Dialogfeld ist geöffnet.
  • Das Cast SDK versucht, eine Übertragungssitzung wiederherzustellen.

Die Erkennung wird angehalten, wenn das Cast-Dialogfeld geschlossen wird oder die Absender-App in den Hintergrund eintritt.

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

So funktioniert die Sitzungsverwaltung

Das Cast SDK führt das Konzept einer Streamingsitzung ein. Dabei werden die Schritte zum Verbinden mit einem Gerät, Starten (oder Beitreten) einer Web Receiver-App, Verbindung zu dieser App und Initialisieren eines Mediensteuerungskanals kombiniert. Weitere Informationen zu Übertragungssitzungen und zum Lebenszyklus des Webempfängers finden Sie im Leitfaden zum Lebenszyklus von Anwendungen für Web Receiver.

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 repräsentiert beispielsweise Sitzungen mit Übertragungsgeräten. Deine App kann über SessionManager.getCurrentCastSession() auf die derzeit aktive Streaming-Sitzung zugreifen.

Ihre Anwendung kann die Klasse SessionManagerListener verwenden, um Sitzungsereignisse wie Erstellung, Sperrung, Wiederaufnahme und Beendigung zu überwachen. Das Framework versucht automatisch, die Sitzung nach einer abnormalen/abrupten Beendigung während einer aktiven Sitzung fortzusetzen.

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

Um Fehler beim Starten von Google Cast besser zu verstehen, können Apps den Fehler beim Starten der Sitzung mit CastContext#getCastReasonCodeForCastStatusCode(int) in CastReasonCodes konvertieren. Einige Fehler beim Starten von Sitzungen (z.B. CastReasonCodes#CAST_CANCELLED) sind beabsichtigtes Verhalten und sollten nicht als Fehler protokolliert werden.

Wenn Sie die Statusänderungen für die Sitzung kennen müssen, können Sie einen 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

Das Beibehalten 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 auf andere Geräte verschieben können. Die Wiedergabe von Medien wird auf einem Gerät (der Quelle) beendet und auf einem anderen (dem Ziel) fortgesetzt. Jedes Übertragungsgerät mit der neuesten Firmware kann als Quellen oder Ziele bei einer Stream-Übertragung dienen.

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

Weitere Informationen findest du unter Stream-Übertragung auf Web-Receiver.

Automatische Wiederherstellung der Verbindung

Das Framework stellt ein ReconnectionService bereit, das von der Sender-App aktiviert werden kann, um die erneute Verbindung in vielen seltenen Fällen zu verarbeiten, z. B.:

  • Wiederherstellung nach vorübergehendem WLAN-Ausfall
  • Aus dem Geräte-Ruhemodus wiederherstellen
  • App aus Hintergrundmodus 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 die automatische Zusammenführung in Ihrer Gradle-Datei aktiviert ist.

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

So funktioniert die Mediensteuerung

Das Cast-Framework verworfen die Klasse RemoteMediaPlayer von Cast 2.x zugunsten einer neuen Klasse RemoteMediaClient, die die gleiche Funktionalität in einer Reihe praktischerer APIs bietet und keine GoogleApiClient übergeben muss.

Wenn Ihre App ein CastSession mit einer Web Receiver-App erstellt, die den Medien-Namespace unterstützt, wird vom Framework automatisch eine Instanz von RemoteMediaClient erstellt. Ihre App kann darauf zugreifen, indem die Methode getRemoteMediaClient() in der CastSession-Instanz aufgerufen wird.

Alle Methoden von RemoteMediaClient, die Anfragen an den Webempfänger senden, geben ein Objekt vom Typ „PendingResult“ zurück, mit dem diese Anfrage verfolgt werden kann.

Es ist zu erwarten, dass die Instanz von RemoteMediaClient von mehreren Teilen Ihrer Anwendung und sogar von einigen internen Komponenten des Frameworks wie den nichtflüchtigen Mini-Controllern und dem Benachrichtigungsdienst gemeinsam genutzt werden kann. Zu diesem Zweck unterstützt diese Instanz die Registrierung mehrerer Instanzen von RemoteMediaClient.Listener.

Medienmetadaten festlegen

Die Klasse MediaMetadata steht für die Informationen zu einem Medienelement, 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 ein Medienelement laden, wie im folgenden Code dargestellt. 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 auf dem Web Receiver ausgeführte Mediaplayer-App wiedergeben, 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 auch im Abschnitt zur Verwendung von Medientracks.

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_* gekennzeichnet.

Benachrichtigungen auf mehreren Geräten per Fernzugriff steuern

Beim Streamen erhalten andere Android-Geräte im selben Netzwerk eine Benachrichtigung, in der sie die Wiedergabe steuern können. Alle Nutzer, deren Gerät solche Benachrichtigungen erhält, kann sie für das entsprechende Gerät in den Einstellungen unter „Google“ > „Google Cast“ > Benachrichtigungen zur Fernbedienung anzeigen deaktivieren. (Die Benachrichtigungen enthalten auch eine Verknüpfung zur App „Einstellungen“.) Weitere Informationen finden Sie unter Benachrichtigungen für die Streaming-Fernbedienung.

Mini-Controller hinzufügen

Gemäß der Checkliste für das Streaming-Design sollte eine Sender-App eine dauerhafte Steuerung, den sogenannten Mini-Controller, bieten, die angezeigt werden sollte, 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 kann der Nutzer zur maximierten Controller-Ansicht im Vollbildmodus zurückkehren.

Das Framework stellt eine benutzerdefinierte Ansicht (MiniControllerFragment) bereit, die Sie am Ende der Layoutdatei jeder Aktivität hinzufügen können, in der Sie den Mini-Controller anzeigen möchten.

<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

Die Checkliste für das Design von Google Cast erfordert, dass eine Sender-App einen erweiterten Controller für die gestreamten Medien bereitstellt. Der maximierte Controller ist eine Vollbildversion des Mini-Controllers.

Das Cast SDK enthält ein Widget für den maximierten Controller mit dem Namen ExpandedControllerActivity. Dies ist eine abstrakte Klasse, deren abgeleitete Klasse ist, 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;
    }
}

Deklarieren Sie jetzt Ihre 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 Ihre 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 neue Aktivität 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 Ihre Sender-App einen Video- oder Audio-Livestream wiedergibt, zeigt das SDK im maximierten Controller automatisch eine Wiedergabe-/Stopp-Schaltfläche anstelle der Wiedergabe-/Pause-Schaltfläche an.

Wenn Sie das Erscheinungsbild mithilfe von Designs anpassen möchten, wählen Sie die anzuzeigenden Schaltflächen aus und fügen Sie benutzerdefinierte Schaltflächen hinzu. Weitere Informationen finden Sie unter Erweiterten Controller anpassen.

Lautstärkeregelung

Das Framework verwaltet automatisch das Volume für die Sender-App. Es synchronisiert die Sender- und Web Receiver-Anwendung automatisch, sodass auf der Absender-UI immer das vom Web Receiver angegebene Volume gemeldet wird.

Lautstärkeregelung über physische Taste

Unter Android können Sie mit den physischen Tasten auf dem Sendergerät die Lautstärke der Streamingsitzung auf dem Web Receiver standardmäßig für alle Geräte ändern, die Jelly Bean oder ein neueres Gerät verwenden.

Lautstärkeregelung über physische Taste vor Jelly Bean

Wenn Sie die Lautstärke des Web Receiver-Geräts auf Android-Geräten, die älter als Jelly Bean sind, mithilfe der physischen Lautstärketasten verwenden 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);
    }
}

Mediensteuerung zu Benachrichtigungs- und Sperrbildschirm hinzufügen

Nur unter Android erfordert die Google Cast-Design-Checkliste, dass eine Sender-App Mediensteuerelemente in einer Benachrichtigung und auf dem Sperrbildschirm implementiert, wo der Sender gestreamt wird, die Sender-App jedoch nicht im Fokus ist. Das Framework bietet MediaNotificationService und MediaIntentReceiver, damit die Sender-App Mediensteuerelemente in einer Benachrichtigung und auf dem Sperrbildschirm erstellen kann.

MediaNotificationService wird ausgeführt, während der Absender eine Übertragung startet. Sie zeigt eine Benachrichtigung mit einer Miniaturansicht des Bildes und Informationen zum aktuellen Streaming-Element sowie einer Schaltfläche für Wiedergabe/Pause und Stopp an.

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

Deine App kann Benachrichtigungen und die Mediensteuerung vom Sperrbildschirm über NotificationOptions konfigurieren. Deine App kann konfigurieren, welche Steuerschaltflächen in der Benachrichtigung angezeigt werden und welche Activity geöffnet werden soll, wenn der Nutzer auf die Benachrichtigung tippt. Wenn Aktionen nicht explizit angegeben sind, 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();

Das Anzeigen der Mediensteuerung über Benachrichtigungs- und Sperrbildschirm ist standardmäßig aktiviert und kann deaktiviert werden, indem setNotificationOptions mit dem Nullpunkt in CastMediaOptions.Builder aufgerufen wird. Derzeit ist die Sperrbildschirmfunktion 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 Ihre 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 Benachrichtigungssteuerelement, aber nicht auf dem Sperrbildschirm-Steuerelement an.

Hinweis: Auf Geräten mit älteren Lollipop-Versionen fordert RemoteMediaClient automatisch den Audiofokus für Sie an, um die Steuerelemente für den Sperrbildschirm auf Geräten mit älteren Lollipop-Versionen anzeigen zu lassen.

Fehler verarbeiten

Es ist sehr wichtig, dass Absender-Apps alle Fehler-Callbacks verarbeiten und die beste Antwort für jede Phase des Cast-Lebenszyklus bestimmen. Die App kann dem Nutzer Fehler-Dialogfelder anzeigen oder die Verbindung zum Web Receiver beenden.