Cast in Ihre Android-App einbinden

In diesem Entwicklerleitfaden wird beschrieben, wie Sie Ihrer Android-Absender-App mithilfe des Android Sender SDK Unterstützung für Google Cast hinzufügen.

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 Sender-Framework bezieht sich auf das Binärprogramm der Cast-Klassenbibliothek und die zugehörigen Ressourcen, die zur Laufzeit beim 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 für Google Cast optimierten Geräten ausgeführt wird.

Das Absender-Framework verwendet ein asynchrones Callback-Design, um die Absender-App über Ereignisse zu informieren und zwischen verschiedenen Status des Lebenszyklus der Cast-Anwendung zu wechseln.

Anwendungsfluss

In den folgenden Schritten wird der typische Ablauf für die Ausführung eines Absender-Android-Apps beschrieben:

  • Das Cast-Framework startet die Geräteerkennung auf Grundlage des Activity-Lebenszyklus automatisch MediaRouter.
  • Wenn der Nutzer auf das Cast-Symbol klickt, wird im Cast-Dialogfeld die Liste der erkannten Cast-Gerä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 prüfen, ob die Web Receiver-App gestartet wurde.
  • Das Framework erstellt einen Kommunikationskanal zwischen dem Absender und den Web Receiver-Apps.
  • Das Framework verwendet den Kommunikationskanal, um die Medienwiedergabe auf dem Webempfänger zu laden und zu steuern.
  • Das Framework synchronisiert den Medienwiedergabestatus zwischen Absender und Webempfänger: Wenn der Nutzer Aktionen auf der Benutzeroberfläche des Absenders ausführt, übergibt das Framework diese Mediensteuerungsanfragen an den Webempfänger. Wenn der Webempfänger Medienstatusaktualisierungen sendet, aktualisiert das Framework den Status der Absender-UI.
  • Wenn der Nutzer auf das Cast-Symbol klickt, um die Verbindung zum Cast-Gerät zu trennen, trennt das Framework die Absender-App vom Webempfänger.

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 wird beschrieben, wie Sie Cast zu Ihrer Android-App hinzufügen.

Android-Manifest konfigurieren

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

uses-sdk

Lege 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:theme

Design der App basierend auf der Mindestversion des Android SDK festlegen Wenn Sie beispielsweise kein eigenes Design implementieren, sollten Sie eine Variante von Theme.AppCompat verwenden, wenn Sie eine Ausrichtung auf eine Android-Mindestversion vornehmen möchten, die älter als Lollipop ist.

<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, CastContext, das alle Interaktionen des Frameworks koordiniert.

Die Anwendung muss die Schnittstelle OptionsProvider implementieren, um 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. Die wichtigste davon ist die Web Receiver-Anwendungs-ID, mit der Discovery-Ergebnisse gefiltert und die Web Receiver-App gestartet werden, wenn eine Cast-Sitzung 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 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 gestartet, 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 Cast-Design-Checkliste 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 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 deine App unterstützt. Wenn der Nutzer auf das Cast-Symbol klickt, wird ein Cast-Dialogfeld mit den erkannten Geräten angezeigt. Wenn der Nutzer auf das Cast-Symbol klickt, während das Gerät verbunden ist, werden die aktuellen Medienmetadaten (z. B. Titel, Name des Aufnahmestudios und ein 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 maximierten Controller zu einem anderen Bildschirm in der Sender-App wechselt, wird unten auf dem Bildschirm der Mini-Controller angezeigt. So kann der Nutzer die aktuell gestreamten Medienmetadaten sehen und die Wiedergabe steuern.

  • Erweiterter Controller: Wenn der Nutzer Inhalte streamt, wird beim Anklicken der Medienbenachrichtigung oder des Mini-Controllers der maximierte Controller gestartet. Dadurch werden die gerade wiedergegebenen Medienmetadaten angezeigt und es sind mehrere Schaltflächen zur Steuerung der Medienwiedergabe vorhanden.

  • 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 Android. Wenn der Nutzer Inhalte streamt und zum Sperrbildschirm wechselt oder das Zeitlimit für das Gerät bestimmt wird, wird eine Mediensperre mit Steuerelementen für das Streamen von Medien und der Wiedergabe angezeigt.

Im folgenden Leitfaden wird beschrieben, wie Sie diese Widgets in Ihre App einbinden.

Cast-Symbol hinzufügen

Mit den Android MediaRouter APIs können Medien auf sekundären Geräten angezeigt und wiedergegeben werden. Android-Apps, die die MediaRouter API verwenden, sollten ein Cast-Symbol als Teil ihrer Benutzeroberfläche enthalten, damit Nutzer eine Medienroute auswählen können, um Medien auf einem sekundären Gerät wie einem Cast-Gerät abzuspielen.

Das Framework vereinfacht das Hinzufügen einer MediaRouteButton als Cast button. Fügen Sie zuerst einen Menüpunkt 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 Activity dann von FragmentActivity übernommen wird, können Sie Ihrem Layout 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 zur Darstellung des Cast-Symbols mithilfe eines Designs findest du unter Cast-Schaltfläche anpassen.

Geräteerkennung konfigurieren

Die Geräteerkennung wird vollständig von der CastContext verwaltet. Beim Initialisieren von CastContext gibt die Absenderanwendung die Web Receiver-Anwendungs-ID an und kann optional die Namespace-Filterung anfordern, indem sie supportedNamespaces in CastOptions festlegt. CastContext enthält einen Verweis auf MediaRouter intern und startet den Erkennungsprozess, wenn die Absender-App in den Vordergrund und 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;
    }
}

So funktioniert die Sitzungsverwaltung

Mit dem Cast SDK wird das Konzept einer Cast-Sitzung eingeführt, bei der die Schritte zum Herstellen einer Verbindung zu einem Gerät, Starten (oder Teilnehmen) einer Web Receiver-App, Verbinden mit dieser App und Initialisieren eines Mediensteuerkanals eingeführt werden. Weitere Informationen zu Cast-Sitzungen und dem Lebenszyklus von Web-Empfängern finden Sie in der Anleitung 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 Übertragungssitzung zugreifen.

Die Anwendung kann die Klasse SessionManagerListener verwenden, um Sitzungsereignisse wie das Erstellen, Anhalten, Fortsetzen und Beenden zu beobachten. Das Framework versucht automatisch, nach einer ungewöhnlichen oder abrupten Beendigung einer Sitzung fortzufahren.

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

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 eines CastSession in einem Activity überwacht.

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

    private inner class SessionManagerListenerImpl : SessionManagerListener<CastSession?> {
        override fun onSessionStarted(session: CastSession?, sessionId: String) {
            invalidateOptionsMenu()
        }

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

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

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        mSessionManager = CastContext.getSharedInstance(this).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 CastSession mCastSession;
    private SessionManager mSessionManager;
    private SessionManagerListener<CastSession> mSessionManagerListener =
            new SessionManagerListenerImpl();

    private class SessionManagerListenerImpl implements SessionManagerListener<CastSession> {
        @Override
        public void onSessionStarted(CastSession session, String sessionId) {
            invalidateOptionsMenu();
        }
        @Override
        public void onSessionResumed(CastSession session, boolean wasSuspended) {
            invalidateOptionsMenu();
        }
        @Override
        public void onSessionEnded(CastSession session, int error) {
            finish();
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mSessionManager = CastContext.getSharedInstance(this).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 Basis der Streamübertragung, bei der Nutzer vorhandene Audio- und Videostreams mithilfe von Sprachbefehlen, der Google Home App oder Smart Displays geräteübergreifend 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 bei einer Streamübertragung verwendet werden.

Wenn Sie das neue Zielgerät während einer Streamübertragung oder -erweiterung erhalten möchten, registrieren Sie eine Cast.Listener mithilfe von CastSession#addCastListener. Rufen Sie dann während des onDeviceNameChanged-Callbacks CastSession#getCastDevice() auf.

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

Automatische Verbindung

Das Framework bietet eine ReconnectionService, die von der Absenderanwendung aktiviert werden kann, um die Verbindung in vielen subtilen Fällen neu zu erstellen, z. B.:

  • WLAN-Verbindung vorübergehend wiederherstellen
  • Aus dem Ruhemodus wiederherstellen
  • Wiederherstellung aus dem Hintergrund der App
  • Wiederherstellen, wenn die App abgestürzt ist

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.

Funktionsweise der Mediensteuerung

Das Cast-Framework stellt die Klasse RemoteMediaPlayer aus Cast 2.x ein und ersetzt die neue Klasse RemoteMediaClient, die die gleiche Funktionalität mit einer Reihe von praktischeren APIs bietet und die Übergabe eines GoogleApiClient vermeiden muss.

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

Alle Methoden von RemoteMediaClient, die Anfragen an den Webempfänger 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 Anwendung und einigen internen Komponenten des Frameworks, z. B. den nichtflüchtigen Minicontrollern und dem Benachrichtigungsdienst, gemeinsam verwendet wird. Zu diesem Zweck unterstützt diese Instanz die Registrierung mehrerer Instanzen von RemoteMediaClient.Listener.

Medienmetadaten festlegen

Die Klasse MediaMetadata stellt die Informationen zu einem Mediaplan dar, das gestreamt werden soll. 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 findest du unter Bildauswahl.

Medien laden

Wie im folgenden Code gezeigt, kann Ihre App ein Media-Element laden. Verwenden Sie zuerst MediaInfo.Builder mit den Metadaten der Medien, um eine MediaInfo-Instanz zu erstellen. Rufen Sie die RemoteMediaClient aus der aktuellen CastSession ab und laden Sie dann die MediaInfo in diese RemoteMediaClient. Verwende RemoteMediaClient, um eine Mediaplayer-App, die auf dem Webempfänger ausgeführt wird, wiederzugeben, zu pausieren und anderweitig zu 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 unter Medien-Tracks verwenden.

4K-Videoformat

Mit getVideoInfo() in MediaStatus können Sie die aktuelle Instanz von VideoInfo abrufen, um das Videoformat Ihrer Medien zu prüfen. 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 mit Konstanten HDR_TYPE_* gekennzeichnet.

Benachrichtigungen per Fernzugriff an mehrere Geräte senden

Wenn ein Nutzer streamt, werden andere Android-Geräte im selben Netzwerk darüber benachrichtigt, damit sie die Wiedergabe steuern können. Jeder Nutzer, der solche Benachrichtigungen erhält, kann sie in der App "Einstellungen" von Google und Google Cast im Bereich Einstellungen für Fernbedienungen anzeigen deaktivieren. Die Benachrichtigungen enthalten eine Verknüpfung zur App „Einstellungen“. Weitere Informationen findest du unter Benachrichtigungen für die Fernbedienung streamen.

Mini-Controller hinzufügen

Gemäß der Checkliste für das Cast-Design sollte eine Absender-App eine dauerhafte Kontrolle bieten. Diese wird als Mini-Controller bezeichnet und sollte angezeigt werden, wenn der Nutzer die aktuelle Inhaltsseite zu einem anderen Teil der Absender-App verlässt. Der Mini-Controller zeigt dem Nutzer eine sichtbare Erinnerung für die aktuelle Cast-Sitzung an. Durch Tippen auf den Minicontroller kann der Nutzer zur Vollbildansicht des Cast-Controllers zurückkehren.

Das Framework bietet eine benutzerdefinierte Ansicht (MiniControllerFragment), die Sie am Ende der Layoutdatei jeder Aktivität hinzufügen können, in der Sie den Minicontroller 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 Absender-App einen Video- oder Audio-Livestream wiedergibt, wird im SDK automatisch eine Wiedergabe-/Stopp-Schaltfläche anstelle der Wiedergabe-/Pause-Schaltfläche angezeigt.

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

Erweiterten Controller hinzufügen

Für die Checkliste für das Google Cast-Design muss eine Absender-App einen erweiterten Controller für die gestreamten Medien bereitstellen. Der maximierte Controller ist eine Vollbildversion des Mini-Controllers.

Das Cast SDK enthält ein Widget für den erweiterten Controller mit dem Namen ExpandedControllerActivity. Dies ist eine abstrakte Klasse, die Sie benötigen, um eine Unterklasse 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 einen neuen Kurs, der 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, damit Ihre neue Aktivität beim Laden des Remote-Medien angezeigt wird:

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 einen Video- oder Audio-Livestream wiedergibt, wird im SDK automatisch eine Wiedergabe-/Stopp-Schaltfläche statt der Wiedergabe-/Pause-Schaltfläche angezeigt.

Wie Sie die Darstellung mithilfe von Designs festlegen, welche Schaltflächen angezeigt werden sollen und ob benutzerdefinierte Schaltflächen hinzugefügt werden, erfahren Sie unter Erweiterten Controller anpassen.

Lautstärkeregelung

Das Framework verwaltet automatisch die Lautstärke der Sender-App. Das Framework synchronisiert automatisch die Apps des Absenders und des Webempfängers, sodass die Absender-UI immer das vom Webempfänger angegebene Volumen meldet.

Lautstärkeregelung der physischen Taste

Unter Android können die physischen Tasten des Absendergeräts verwendet werden, um die Lautstärke der Cast-Sitzung im Web-Empfänger für jedes Gerät mit Jelly Bean oder höher standardmäßig zu ändern.

Lautstärkeregelung der Taste vor Jelly Bean

Wenn Sie mit den physischen Lautstärketasten die Lautstärke des Web Receivers 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);
    }
}

Mediensteuerung zu Benachrichtigungs- und Sperrbildschirm hinzufügen

Nur für Android: Gemäß der Checkliste für das Design von Google Cast muss die Absender-App Mediensteuerelemente in einer Benachrichtigung implementieren und auf dem Sperrbildschirm, auf dem der Absender gerade streamt, die Absender-App jedoch nicht im Mittelpunkt steht. Das Framework bietet MediaNotificationService und MediaIntentReceiver, mit denen die Absender-App Mediensteuerelemente in einer Benachrichtigung und auf dem Sperrbildschirm erstellen kann.

MediaNotificationService wird beim Streamen ausgeführt und zeigt eine Benachrichtigung mit Miniaturansicht und Informationen zum aktuellen Übertragungselement, einer Wiedergabe-/Pause-Schaltfläche und einer Stopp-Schaltfläche an.

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

Ihre App kann die Benachrichtigung und Mediensteuerung auf dem Sperrbildschirm über NotificationOptions konfigurieren. Deine App kann konfigurieren, welche Steuerungsschaltflächen in der Benachrichtigung angezeigt werden 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 Mediensteuerelemente für die Benachrichtigungen und den Sperrbildschirm sind standardmäßig aktiviert und können durch Aufrufen von setNotificationOptions ohne Wert in CastMediaOptions.Builder deaktiviert werden. Derzeit 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 die Absender-App einen Video- oder Audio-Livestream wiedergibt, wird im SDK automatisch eine Wiedergabe-/Stopp-Schaltfläche statt der Wiedergabe-/Pause-Schaltfläche angezeigt, nicht jedoch die des Sperrbildschirms.

Hinweis: Damit die Steuerelemente für den Sperrbildschirm von Geräten vor Lollipop angezeigt werden, fordert RemoteMediaClient automatisch den Fokus auf Audio an.

Fehler verarbeiten

Es ist sehr wichtig, dass Absender-Apps alle Fehlerrückrufe verarbeiten und die beste Antwort für jede Phase des Lebenszyklus von Casts bestimmen. Die App kann dem Nutzer Fehlerdialoge anzeigen oder die Verbindung zum Web Receiver aufheben.