In diesem Entwicklerleitfaden wird beschrieben, wie du deiner Android-Sender-App mit dem Android Sender SDK Google Cast-Unterstützung hinzufügen kannst.
Das Mobilgerät oder der Laptop ist der Sender, 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 auf dem Sender vorhanden sind. Die Sender-App oder Cast-App bezieht sich auf eine App, die auch auf dem Sender ausgeführt wird. Die Web-Empfänger-App bezieht sich auf die HTML-Anwendung, die auf dem für Google Cast optimierten Gerät ausgeführt wird.
Das Sender-Framework verwendet ein asynchrones Callback-Design, um die Sender-App über Ereignisse zu informieren und zwischen verschiedenen Status des Lebenszyklus der Cast-App zu wechseln.
Anwendungsfluss
In den folgenden Schritten wird der typische Ablauf für eine Android-App beschrieben, die E-Mails sendet:
- Das Cast-Framework startet automatisch die
MediaRouter
-Geräteerkennung basierend auf demActivity
-Lebenszyklus. - Wenn der Nutzer auf die Schaltfläche „Streamen“ klickt, wird der Cast-Dialog mit der 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 Rückrufe in der Sender-App auf, um zu bestätigen, dass die Web-Empfänger-App gestartet wurde.
- Das Framework erstellt einen Kommunikationskanal zwischen den Apps „Sender“ und „Web Receiver“.
- Das Framework verwendet den Kommunikationskanal, um die Medienwiedergabe auf dem Webreceiver zu laden und zu steuern.
- Das Framework synchronisiert den Status der Medienwiedergabe zwischen Sender und Webreceiver: Wenn der Nutzer Aktionen auf der Sender-Benutzeroberfläche ausführt, leitet das Framework diese Anfragen zur Mediensteuerung an den Webreceiver weiter. Wenn der Webreceiver Aktualisierungen des Medienstatus sendet, aktualisiert das Framework den Status der Sender-Benutzeroberfläche.
- Wenn der Nutzer auf die Schaltfläche „Streamen“ klickt, um die Verbindung zum Streaminggerät zu trennen, trennt das Framework die Verbindung der Sender-App zum Webempfänger.
Eine umfassende Liste aller Klassen, Methoden und Ereignisse im Google Cast Android SDK findest du in der Google Cast Sender API-Referenz für Android. In den folgenden Abschnitten erfahren Sie, wie Sie die Übertragungsfunktion in Ihre Android-App einbinden.
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 das Mindest- und Ziel-Android-API-Level fest, das vom Cast SDK unterstützt wird. Derzeit ist API-Level 23 das Mindest-API-Level und API-Level 34 das Ziel-API-Level.
<uses-sdk
android:minSdkVersion="23"
android:targetSdkVersion="34" />
android:theme
Legen Sie das Design Ihrer App anhand der Mindest-SDK-Version für Android fest. Wenn Sie beispielsweise kein eigenes Design implementieren, sollten Sie eine Variante von Theme.AppCompat
verwenden, wenn Sie Ihre App auf eine Android SDK-Version vor Lollipop ausrichten.
<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.
Ihre App muss die Schnittstelle OptionsProvider
implementieren, um Optionen zur Initialisierung des Singletons 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 Suchergebnisse gefiltert und die Web Receiver-App gestartet wird, wenn eine Übertragungssitzung gestartet wird.
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 } }
public class CastOptionsProvider implements OptionsProvider { @Override public CastOptions getCastOptions(Context context) { CastOptions castOptions = new CastOptions.Builder() .setReceiverApplicationId(context.getString(R.string.app_id)) .build(); return castOptions; } @Override public List<SessionProvider> getAdditionalSessionProviders(Context context) { return null; } }
Sie müssen den voll qualifizierten Namen der implementierten OptionsProvider
als Metadatenfeld in der Datei „AndroidManifest.xml“ der Sender-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.
class MyActivity : FragmentActivity() { override fun onCreate(savedInstanceState: Bundle?) { val castContext = CastContext.getSharedInstance(this) } }
public class MyActivity extends FragmentActivity { @Override public void onCreate(Bundle savedInstanceState) { CastContext castContext = CastContext.getSharedInstance(this); } }
Die UX-Widgets für die Übertragung
Das Cast-Framework bietet die Widgets, die der Checkliste für das Cast-Design entsprechen:
Einführungs-Overlay: Im Framework wird eine benutzerdefinierte Ansicht,
IntroductoryOverlay
, angezeigt, um die Aufmerksamkeit des Nutzers auf die Schaltfläche „Streamen“ zu lenken, wenn zum ersten Mal ein Empfänger verfügbar ist. In der App des Absenders können Text und Position des Titeltexts angepasst werden.Cast-Schaltfläche: Die Cast-Schaltfläche ist unabhängig von der Verfügbarkeit von Übertragungsgeräten sichtbar. Wenn der Nutzer zum ersten Mal auf die Schaltfläche „Streamen“ klickt, wird ein Streaming-Dialogfeld mit den gefundenen Geräten angezeigt. Wenn der Nutzer auf die Schaltfläche „Streamen“ klickt, während das Gerät verbunden ist, werden die aktuellen Medienmetadaten (z. B. Titel, Name des Aufnahmestudios und ein Thumbnail-Bild) angezeigt oder der Nutzer kann die Verbindung zum Übertragungsgerät trennen. Die „Streaming-Schaltfläche“ wird manchmal auch als „Streaming-Symbol“ bezeichnet.
Mini-Steuerelement: Wenn der Nutzer Inhalte streamt und die aktuelle Inhaltsseite oder den maximierten Controller verlassen hat, um einen anderen Bildschirm in der Sender-App aufzurufen, wird unten auf dem Bildschirm das Mini-Steuerelement angezeigt. So kann der Nutzer die Metadaten der gerade gestreamten Medien sehen und die Wiedergabe steuern.
Maximierter Controller: Wenn der Nutzer Inhalte streamt und auf die Medienbenachrichtigung oder den Mini-Controller klickt, wird der maximierte Controller gestartet. Darauf werden die Metadaten der gerade wiedergegebenen Medien angezeigt und es gibt mehrere Schaltflächen zur Steuerung der Medienwiedergabe.
Benachrichtigung: Nur Android. Wenn der Nutzer Inhalte streamt und die App verlässt, über die er streamt, wird eine Medienbenachrichtigung mit den Metadaten der gerade gestreamten Medien und den Wiedergabesteuerungen angezeigt.
Sperrbildschirm: Nur Android. Wenn der Nutzer Inhalte streamt und zum Sperrbildschirm wechselt (oder das Gerät ein Zeitlimit hat), wird eine Mediensteuerung für den Sperrbildschirm angezeigt, die die Metadaten der gerade gestreamten Medien und die Wiedergabesteuerung enthält.
Im folgenden Leitfaden wird beschrieben, wie Sie diese Widgets Ihrer App hinzufügen.
Schaltfläche „Streamen“ hinzufügen
Die Android-APIs MediaRouter
sind für die Anzeige und Wiedergabe von Medien auf sekundären Geräten konzipiert.
Android-Apps, die die MediaRouter
API verwenden, sollten eine Cast-Schaltfläche 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 macht es sehr einfach, eine MediaRouteButton
als Cast button
hinzuzufügen. Fügen Sie zuerst einen Menüpunkt oder ein MediaRouteButton
in die XML-Datei ein, die Ihr Menü definiert, und verbinden Sie es mit dem Framework über CastButtonFactory
.
// 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" />
// 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 }
// 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
von FragmentActivity
abgeleitet ist, können Sie Ihrem Layout ein 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>
// 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) }
// MyActivity.java @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_layout); mMediaRouteButton = (MediaRouteButton) findViewById(R.id.media_route_button); CastButtonFactory.setUpMediaRouteButton(getApplicationContext(), mMediaRouteButton); mCastContext = CastContext.getSharedInstance(this); }
Wie Sie das Erscheinungsbild der Übertragungsschaltfläche mit einem Design festlegen, erfahren Sie unter Übertragungsschaltfläche anpassen.
Geräteerkennung konfigurieren
Die Geräteerkennung wird vollständig vom CastContext
verwaltet.
Bei der Initialisierung des 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 intern eine Referenz auf MediaRouter
und startet den Discovery-Prozess unter den folgenden Bedingungen:
- Anhand eines Algorithmus, der die Latenz der Geräteerkennung und die Akkunutzung ausgleicht, wird die Erkennung gelegentlich automatisch gestartet, wenn die App des Absenders in den Vordergrund wechselt.
- Das Dialogfeld „Streamen“ ist geöffnet.
- Das Cast SDK versucht, eine Cast-Sitzung wiederherzustellen.
Die Suche wird beendet, wenn das Übertragungsfenster geschlossen wird oder die Sender-App in den Hintergrund wechselt.
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 } }
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 Cast-Sitzung ein. Bei der Einrichtung werden die Schritte zum Herstellen einer Verbindung zu einem Gerät, zum Starten (oder Beitreten) einer Webreceiver-App, zum Herstellen einer Verbindung zu dieser App und zum Initialisieren eines Mediensteuerungskanals kombiniert. Weitere Informationen zu Übertragungssitzungen und zum Lebenszyklus des Webreceivers findest du im Leitfaden zum Anwendungslebenszyklus.
Sitzungen werden von der Klasse SessionManager
verwaltet, auf die Ihre App über CastContext.getSessionManager()
zugreifen kann.
Einzelne Sitzungen werden durch Unterklassen der Klasse Session
dargestellt.
CastSession
steht beispielsweise für Sitzungen mit Streaminggeräten. Ihre App kann über SessionManager.getCurrentCastSession()
auf die aktuell aktive Übertragungssitzung zugreifen.
Ihre App kann die Klasse SessionManagerListener
verwenden, um Sitzungsereignisse wie Erstellung, Aussetzung, Fortsetzen und Beendigung zu überwachen. Das Framework versucht automatisch, nach einer abnormalen/plötzlichen Beendigung fortzufahren, während eine Sitzung aktiv war.
Sitzungen werden automatisch als Reaktion auf Nutzergesten in den MediaRouter
-Dialogen erstellt und beendet.
Um Fehler beim Starten von Google Cast besser nachvollziehen zu können, können Entwickler in Apps den Fehler beim Starten der Sitzung mit CastContext#getCastReasonCodeForCastStatusCode(int)
in CastReasonCodes
umwandeln.
Einige Fehler beim Starten einer Sitzung (z.B. CastReasonCodes#CAST_CANCELLED
) sind beabsichtigt und sollten nicht als Fehler protokolliert werden.
Wenn du über die Statusänderungen für die Sitzung informiert werden möchtest, kannst du einen SessionManagerListener
implementieren. In diesem Beispiel wird die Verfügbarkeit eines CastSession
in einem Activity
überwacht.
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 mSessionManager.addSessionManagerListener(mSessionManagerListener, CastSession::class.java) } override fun onResume() { super.onResume() mCastSession = mSessionManager.currentCastSession } override fun onDestroy() { super.onDestroy() mSessionManager.removeSessionManagerListener(mSessionManagerListener, CastSession::class.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(); mSessionManager.addSessionManagerListener(mSessionManagerListener, CastSession.class); } @Override protected void onResume() { super.onResume(); mCastSession = mSessionManager.getCurrentCastSession(); } @Override protected void onDestroy() { super.onDestroy(); mSessionManager.removeSessionManagerListener(mSessionManagerListener, CastSession.class); } }
Stream-Übertragung
Die Aufrechterhaltung des Sitzungsstatus ist die Grundlage der Streamübertragung, bei der Nutzer vorhandene Audio- und Videostreams per Sprachbefehl, über die Google Home App oder über Smart Displays zwischen Geräten verschieben können. Die Medienwiedergabe wird auf einem Gerät (der Quelle) beendet und auf einem anderen (dem Ziel) fortgesetzt. Jedes Cast-Gerät mit der neuesten Firmware kann als Quelle oder Ziel in einer Streamübertragung dienen.
Wenn du das neue Zielgerät während einer Streamübertragung oder -erweiterung abrufen möchtest, registriere eine Cast.Listener
mit CastSession#addCastListener
.
Rufe dann während des onDeviceNameChanged
-Callbacks CastSession#getCastDevice()
auf.
Weitere Informationen finden Sie unter Streamübertragung auf Webempfänger.
Automatische Wiederverbindung
Das Framework bietet eine ReconnectionService
, die von der Sender-App aktiviert werden kann, um die Wiederverbindung in vielen subtilen Sonderfällen zu steuern, z. B.:
- Nach einem vorübergehenden WLAN-Ausfall wiederherstellen
- Wiederherstellen nach Ruhezustand des Geräts
- Wiederherstellen nach dem Auslagern 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 in das Manifest Ihrer App eingefügt werden, wenn die automatische Zusammenführung in Ihrer Gradle-Datei aktiviert ist.
Das Framework startet den Dienst, wenn eine Mediensitzung aktiv ist, und beendet ihn, wenn die Mediensitzung endet.
Funktionsweise von Media Control
Im Cast-Framework wird die Klasse RemoteMediaPlayer
aus Cast 2.x zugunsten einer neuen Klasse RemoteMediaClient
eingestellt. Diese bietet dieselben Funktionen in einer Reihe von praktischeren APIs und erfordert nicht, dass ein GoogleApiClient übergeben wird.
Wenn deine App eine CastSession
-Verbindung zu einer Web-Empfänger-App herstellt, die den Medien-Namespace unterstützt, wird vom Framework automatisch eine Instanz von RemoteMediaClient
erstellt. Deine App kann darauf zugreifen, indem sie die getRemoteMediaClient()
-Methode auf der CastSession
-Instanz aufruft.
Alle Methoden von RemoteMediaClient
, die Anfragen an den Webempfänger senden, geben ein PendingResult-Objekt zurück, mit dem sich die Anfrage verfolgen lässt.
Es ist davon auszugehen, dass die Instanz von RemoteMediaClient
von mehreren Teilen Ihrer App und sogar von einigen internen Komponenten des Frameworks gemeinsam genutzt wird, z. B. von den persistenten Mini-Controllern und dem Benachrichtigungsdienst.
Dazu unterstützt diese Instanz die Registrierung mehrerer Instanzen von RemoteMediaClient.Listener
.
Medienmetadaten festlegen
Die Klasse MediaMetadata
stellt die Informationen zu einem Medienelement dar, das du streamen möchtest. Im folgenden Beispiel wird eine neue MediaMetadata-Instanz eines Films erstellt und der Titel, die Untertitel und zwei Bilder festgelegt.
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))))
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))));
Informationen zur Verwendung von Bildern mit Medienmetadaten finden Sie unter Bildauswahl.
Medien laden
Ihre App kann ein Medienelement laden, wie im folgenden Code gezeigt. Verwende zuerst MediaInfo.Builder
mit den Metadaten der Medien, um eine MediaInfo
-Instanz zu erstellen. Rufe die RemoteMediaClient
aus der aktuellen CastSession
ab und lade die MediaInfo
in diese RemoteMediaClient
. Mit RemoteMediaClient
kannst du eine Mediaplayer-App, die auf dem Webreceiver ausgeführt wird, abspielen, pausieren und anderweitig steuern.
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())
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
Um das Videoformat deiner Medien zu prüfen, verwende 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 Displays in Pixeln. Varianten des 4K-Formats werden durch Konstanten HDR_TYPE_*
angegeben.
Benachrichtigungen zur Fernbedienung an mehrere Geräte senden
Wenn ein Nutzer streamt, erhalten andere Android-Geräte im selben Netzwerk eine Benachrichtigung, mit der sie die Wiedergabe ebenfalls steuern können. Nutzer, auf deren Geräten solche Benachrichtigungen angezeigt werden, können sie in den Einstellungen unter „Google“ > „Google Cast“ > Benachrichtigungen zur Fernbedienung anzeigen für das jeweilige Gerät deaktivieren. Die Benachrichtigungen enthalten eine Verknüpfung zur App „Einstellungen“. Weitere Informationen finden Sie unter Benachrichtigungen zur Übertragungsfernbedienung.
Mini-Controller hinzufügen
Gemäß der Checkliste für das Design von Sender-Apps sollte eine Sender-App ein dauerhaftes Steuerelement namens Mini-Controller bereitstellen, das angezeigt werden sollte, wenn der Nutzer die aktuelle Inhaltsseite verlässt und zu einem anderen Teil der Sender-App wechselt. Der Mini-Controller dient als sichtbare Erinnerung an die aktuelle Cast-Sitzung. Durch Tippen auf den Mini-Controller kann der Nutzer zur erweiterten Vollbildansicht des Cast-Controllers zurückkehren.
Das Framework bietet eine benutzerdefinierte Ansicht, MiniControllerFragment, die Sie unten in die Layoutdatei jeder Aktivität einfügen können, in der der Mini-Controller angezeigt werden soll.
<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 in Ihrer Sender-App ein Video- oder Audio-Livestream wiedergegeben wird, zeigt das SDK automatisch eine Wiedergabe-/Stopp-Schaltfläche anstelle der Wiedergabe-/Pause-Schaltfläche auf dem Mini-Controller an.
Wie Sie die Textdarstellung des Titels und des Untertitels dieser benutzerdefinierten Ansicht festlegen und Schaltflächen auswählen, erfahren Sie unter Mini-Controller anpassen.
Maximierten Controller hinzufügen
Gemäß der Google Cast-Design-Checkliste muss eine Sender-App eine erweiterte Steuerleiste für die gestreamten Medien bereitstellen. Der erweiterte Controller ist eine Vollbildversion des Mini-Controllers.
Das Cast SDK bietet ein Widget für den maximierten Controller namens ExpandedControllerActivity
.
Dies ist eine abstrakte Klasse, die Sie als Unterklasse erstellen müssen, um eine Schaltfläche zum Streamen hinzuzufügen.
Erstellen Sie zuerst eine neue Menüressourcendatei für den erweiterten Controller, um die Schaltfläche „Streamen“ 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.
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 } }
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 nun 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 die neue Aktivität festzulegen:
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() }
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 die neue Aktivität angezeigt wird, wenn die Remote-Medien geladen werden:
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() ) }
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 in deiner Sender-App ein Video- oder Audio-Livestream wiedergegeben wird, zeigt das SDK im maximierten Steuerfeld automatisch eine Wiedergabe-/Stopp-Schaltfläche anstelle der Wiedergabe-/Pause-Schaltfläche an.
Wie du das Erscheinungsbild mithilfe von Designs festlegst, auswählst, welche Schaltflächen angezeigt werden sollen, und benutzerdefinierte Schaltflächen hinzufügst, erfährst du unter Maximierten Steuerbildschirm anpassen.
Lautstärkeregelung
Das Framework verwaltet die Lautstärke für die Sender-App automatisch. Außerdem werden die Sender- und Web-Empfänger-Apps automatisch synchronisiert, sodass in der Sender-Benutzeroberfläche immer die vom Web-Empfänger angegebene Lautstärke angezeigt wird.
Lautstärkeregelung über physische Taste
Unter Android können die physischen Tasten auf dem Sendergerät standardmäßig für alle Geräte mit Jelly Bean oder höher verwendet werden, um die Lautstärke der Übertragungssitzung auf dem Webempfänger zu ändern.
Lautstärkeregelung über physische Tasten vor Jelly Bean
Wenn du die physischen Lautstärketasten verwenden möchtest, um die Lautstärke des Webreceivers auf Android-Geräten zu steuern, die älter als Jelly Bean sind, sollte die Sender-App dispatchKeyEvent
in ihren Aktivitäten überschreiben und CastContext.onDispatchVolumeKeyEventBeforeJellyBean()
aufrufen:
class MyActivity : FragmentActivity() { override fun dispatchKeyEvent(event: KeyEvent): Boolean { return (CastContext.getSharedInstance(this) .onDispatchVolumeKeyEventBeforeJellyBean(event) || super.dispatchKeyEvent(event)) } }
class MyActivity extends FragmentActivity { @Override public boolean dispatchKeyEvent(KeyEvent event) { return CastContext.getSharedInstance(this) .onDispatchVolumeKeyEventBeforeJellyBean(event) || super.dispatchKeyEvent(event); } }
Mediensteuerelemente zu Benachrichtigungen und Sperrbildschirm hinzufügen
Nur unter Android muss die Sender-App gemäß der Google Cast-Design-Checkliste Mediensteuerungen in einer Benachrichtigung und auf dem Sperrbildschirm implementieren, wenn der Sender streamt, die Sender-App aber nicht im Fokus ist. Das Framework bietet MediaNotificationService
und MediaIntentReceiver
, mit denen die App des Absenders Mediensteuerelemente in einer Benachrichtigung und auf dem Sperrbildschirm einbinden kann.
MediaNotificationService
wird ausgeführt, wenn der Absender streamt. Es wird dann eine Benachrichtigung mit einem Bild-Thumbnail und Informationen zum aktuellen gestreamten Element, einer Wiedergabe-/Pause-Schaltfläche und einer Schaltfläche zum Beenden angezeigt.
MediaIntentReceiver
ist ein BroadcastReceiver
, der Nutzeraktionen über die Benachrichtigung verarbeitet.
Ihre App kann die Benachrichtigungs- und Mediensteuerung über NotificationOptions
auf dem Sperrbildschirm konfigurieren.
In Ihrer App können Sie konfigurieren, welche Steuerschaltflächen in der Benachrichtigung angezeigt werden und welche Activity
geöffnet werden, wenn der Nutzer auf die Benachrichtigung tippt. Wenn keine Aktionen explizit angegeben werden, werden die Standardwerte MediaIntentReceiver.ACTION_TOGGLE_PLAYBACK
und MediaIntentReceiver.ACTION_STOP_CASTING
verwendet.
// 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()
// 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 Mediensteuerung in Benachrichtigungen und auf dem Sperrbildschirm ist standardmäßig aktiviert. Sie können sie deaktivieren, indem Sie setNotificationOptions
mit „null“ in CastMediaOptions.Builder
aufrufen.
Derzeit ist die Sperrbildschirmfunktion aktiviert, solange Benachrichtigungen aktiviert sind.
// ... 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()
// ... 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 in Ihrer Sender-App ein Video- oder Audio-Livestream wiedergegeben wird, zeigt das SDK automatisch eine Wiedergabe-/Stopp-Schaltfläche anstelle der Wiedergabe-/Pause-Schaltfläche in der Benachrichtigungssteuerung an, aber nicht in der Sperrbildschirmsteuerung.
Hinweis: Damit Steuerelemente für den Sperrbildschirm auf Geräten ohne Lollipop angezeigt werden, fordert RemoteMediaClient
automatisch den Audiofokus in Ihrem Namen an.
Fehler verarbeiten
Es ist sehr wichtig, dass Sender-Apps alle Fehler-Callbacks verarbeiten und für jede Phase des Cast-Lebenszyklus die beste Antwort festlegen. Die App kann dem Nutzer Fehlerdialogfelder anzeigen oder die Verbindung zum Webreceiver trennen.