In diesem Entwicklerleitfaden wird beschrieben, wie Sie Ihrer Android-Sender-App mit dem Android Sender SDK Google Cast-Unterstützung hinzufügen.
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 die Binärdatei 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 ebenfalls auf dem Sender ausgeführt wird. Die Web Receiver-App ist 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 allgemeine Ausführungsablauf für eine Android-Sender-App beschrieben:
- Das Cast-Framework startet die
MediaRouter-Geräteerkennung automatisch basierend auf demActivity-Lebenszyklus. - Wenn der Nutzer auf das Cast-Symbol klickt, wird im Framework das Cast-Dialogfeld mit der Liste der gefundenen Cast-Geräte angezeigt.
- Wenn der Nutzer ein Übertragungsgerät auswählt, versucht das Framework, die Webempfänger-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 dem Sender und den Web-Empfänger-Apps.
- Das Framework verwendet den Kommunikationskanal, um die Medienwiedergabe auf dem Webempfänger zu laden und zu steuern.
- Das Framework synchronisiert den Status der Medienwiedergabe zwischen Sender und Web-Empfänger: Wenn der Nutzer Aktionen auf der Sender-UI ausführt, übergibt das Framework diese Mediensteuerungsanfragen an den Web-Empfänger. Wenn der Web-Empfänger Medienstatusaktualisierungen sendet, aktualisiert das Framework den Status der Sender-UI.
- Wenn der Nutzer auf das Cast-Symbol klickt, um die Verbindung zum Cast-Gerät zu trennen, trennt das Framework die Verbindung der Sender-App zum Web-Receiver.
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 werden die Schritte zum Hinzufügen von Cast zu Ihrer Android-App beschrieben.
Android-Manifest konfigurieren
In der Datei „AndroidManifest.xml“ Ihrer App müssen Sie die folgenden Elemente für das Cast SDK konfigurieren:
uses-sdk
Legen Sie die minimalen und Ziel-Android-API-Level fest, die vom Cast SDK unterstützt werden. Derzeit ist das Mindest-API-Level 24 und das Ziel-API-Level 35.
<uses-sdk
android:minSdkVersion="24"
android:targetSdkVersion="35" />
android:theme
Legen Sie das Design Ihrer App basierend auf der mindestens erforderlichen Android SDK-Version fest. Wenn Sie beispielsweise kein eigenes Design implementieren, sollten Sie eine Variante von Theme.AppCompat verwenden, wenn Sie eine Android SDK-Mindestversion vor Lollipop verwenden.
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/Theme.AppCompat" >
...
</application>
Cast-Kontext initialisieren
Das Framework hat ein globales Singleton-Objekt, das CastContext, das alle Interaktionen des Frameworks koordiniert.
In deiner App muss die Schnittstelle OptionsProvider implementiert sein, um die Optionen bereitzustellen, die zum Initialisieren des Singletons CastContext erforderlich sind. 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, die zum Filtern der Suchergebnisse und zum Starten der Web-Receiver-App verwendet 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 vollständig 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 Cast-UX-Widgets
Das Cast-Framework stellt die Widgets bereit, die der Cast-Design-Checkliste entsprechen:
Einführungsoverlay: Das Framework bietet eine benutzerdefinierte Ansicht,
IntroductoryOverlay, die dem Nutzer angezeigt wird, um ihn auf das Cast-Symbol aufmerksam zu machen, wenn zum ersten Mal ein Empfänger verfügbar ist. Die Sender-App kann den Text und die Position des Titeltexts anpassen.Cast-Symbol: Das Cast-Symbol ist unabhängig von der Verfügbarkeit von Cast-Geräten sichtbar. Wenn der Nutzer zum ersten Mal auf das Cast-Symbol klickt, wird ein Cast-Dialogfeld mit den gefundenen 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 Cast-Gerät trennen. Die „Cast-Schaltfläche“ wird manchmal auch als „Cast-Symbol“ bezeichnet.
Mini-Controller: 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 der Mini-Controller unten auf dem Bildschirm angezeigt. So kann der Nutzer die Metadaten der aktuell gestreamten Medien sehen und die Wiedergabe steuern.
Erweiterter Controller: Wenn der Nutzer Inhalte streamt und auf die Medienbenachrichtigung oder den Mini-Controller klickt, wird der erweiterte Controller gestartet. Dieser zeigt die Metadaten der aktuell wiedergegebenen Medien an und bietet mehrere Schaltflächen zur Steuerung der Medienwiedergabe.
Benachrichtigung: Nur Android. Wenn der Nutzer Inhalte streamt und die Sender-App verlässt, wird eine Medienbenachrichtigung mit den Metadaten der aktuell gestreamten Medien und der Wiedergabesteuerung angezeigt.
Sperrbildschirm: Nur Android. Wenn der Nutzer Inhalte streamt und zum Sperrbildschirm wechselt (oder das Gerät in den Ruhemodus wechselt), wird ein Steuerelement für den Sperrbildschirm angezeigt, das die Metadaten und die Wiedergabesteuerung der aktuell gestreamten Medien enthält.
In der folgenden Anleitung wird beschrieben, wie Sie diese Widgets Ihrer App hinzufügen.
Cast-Symbol 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 ein Cast-Symbol in 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.
Mit dem Framework ist es ganz einfach, ein MediaRouteButton als Cast button hinzuzufügen. Fügen Sie zuerst einen Menüpunkt oder ein MediaRouteButton-Element in der XML-Datei hinzu, die Ihr Menü definiert, und verwenden Sie CastButtonFactory, um es mit dem Framework zu verknüpfen.
// 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 Ihr Activity von FragmentActivity erbt, 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); }
Informationen zum Festlegen des Erscheinungsbilds des Cast-Symbols mithilfe eines Designs findest du unter Cast-Symbol anpassen.
Geräteerkennung konfigurieren
Die Geräteerkennung wird vollständig vom CastContext verwaltet.
Beim Initialisieren des CastContext gibt die Sender-App die Web Receiver-Anwendungs-ID an und kann optional die Namespace-Filterung anfordern, indem sie supportedNamespaces in CastOptions festlegt.
CastContext enthält intern einen Verweis auf MediaRouter und startet den Erkennungsprozess unter den folgenden Bedingungen:
- Basierend auf einem Algorithmus, der die Latenz bei der Geräteerkennung und die Akkunutzung ausgleicht, wird die Erkennung gelegentlich automatisch gestartet, wenn die Sender-App in den Vordergrund wechselt.
- Das Dialogfeld „Streamen“ ist geöffnet.
- Das Cast SDK versucht, eine Cast-Sitzung wiederherzustellen.
Der Erkennungsprozess wird beendet, wenn das Cast-Dialogfeld geschlossen wird oder die 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; } }
Funktionsweise der Sitzungsverwaltung
Im Cast SDK wird das Konzept einer Cast-Sitzung eingeführt. Die Einrichtung einer solchen Sitzung umfasst die Schritte zum Herstellen einer Verbindung zu einem Gerät, zum Starten (oder Beitreten) einer Web Receiver-App, zum Herstellen einer Verbindung zu dieser App und zum Initialisieren eines Mediensteuerung-Channels. Weitere Informationen zu Cast-Sitzungen und zum Web Receiver-Lebenszyklus finden Sie im Leitfaden zum Anwendungslebenszyklus für Web Receiver.
Sitzungen werden von der Klasse SessionManager verwaltet, auf die deine App über CastContext.getSessionManager() zugreifen kann.
Einzelne Sitzungen werden durch Unterklassen der Klasse Session dargestellt.
CastSession steht beispielsweise für Sitzungen mit Übertragungsgeräten. Deine App kann über SessionManager.getCurrentCastSession() auf die aktuell aktive Übertragungssitzung zugreifen.
Ihre App kann die Klasse SessionManagerListener verwenden, um Sitzungsereignisse wie Erstellung, Sperrung, Fortsetzung und Beendigung zu überwachen. Das Framework versucht automatisch, die Sitzung nach einem abnormalen/abrupten Abbruch fortzusetzen.
Sitzungen werden automatisch als Reaktion auf Nutzergesten aus den MediaRouter-Dialogfeldern erstellt und beendet.
Damit Sie Fehler beim Starten von Cast besser verstehen, können Apps CastContext#getCastReasonCodeForCastStatusCode(int) verwenden, um den Fehler beim Starten der Sitzung in CastReasonCodes umzuwandeln.
Einige Fehler beim Starten einer Sitzung (z.B. CastReasonCodes#CAST_CANCELLED) sind beabsichtigt und sollten nicht als Fehler protokolliert werden.
Wenn Sie über die Statusänderungen der Sitzung informiert werden möchten, können Sie 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
Das Beibehalten des Sitzungsstatus ist die Grundlage für die Stream-Übertragung, bei der Nutzer vorhandene Audio- und Videostreams per Sprachbefehl, über die Google Home App oder über Smart Displays auf andere Geräte übertragen können. Die Medienwiedergabe wird auf einem Gerät (der Quelle) beendet und auf einem anderen Gerät (dem Ziel) fortgesetzt. Jedes Cast-Gerät mit der neuesten Firmware kann als Quelle oder Ziel bei einer Stream-Übertragung dienen.
Wenn du das neue Zielgerät während einer Stream-Übertragung oder -Erweiterung abrufen möchtest, registriere einen Cast.Listener mit dem CastSession#addCastListener.
Rufen Sie dann während des onDeviceNameChanged-Callbacks CastSession#getCastDevice() auf.
Weitere Informationen finden Sie unter Stream-Übertragung auf Web Receiver.
Automatische Wiederverbindung
Das Framework bietet eine ReconnectionService, die von der Sender-App aktiviert werden kann, um die Wiederherstellung der Verbindung in vielen speziellen Fällen zu ermöglichen, z. B.:
- Wiederherstellung nach vorübergehendem WLAN-Ausfall
- Aus dem Ruhemodus reaktivieren
- Wiederherstellung nach Ausführung der App im Hintergrund
- Wiederherstellung nach einem App-Absturz
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
Im Cast-Framework wird die Klasse RemoteMediaPlayer aus Cast 2.x zugunsten der neuen Klasse RemoteMediaClient eingestellt. Diese bietet dieselben Funktionen in einer Reihe von praktischeren APIs und vermeidet die Notwendigkeit, einen GoogleApiClient zu übergeben.
Wenn deine App eine CastSession mit einer Webempfä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 Methode getRemoteMediaClient() für die CastSession-Instanz aufruft.
Alle Methoden von RemoteMediaClient, die Anfragen an den Webempfänger senden, geben ein PendingResult-Objekt zurück, mit dem die Anfrage verfolgt werden kann.
Es wird erwartet, dass die Instanz von RemoteMediaClient von mehreren Teilen Ihrer App und auch von einigen internen Komponenten des Frameworks gemeinsam genutzt wird, z. B. von den persistenten Minicontrollern und dem Benachrichtigungsdienst.
Zu diesem Zweck unterstützt diese Instanz die Registrierung mehrerer Instanzen von RemoteMediaClient.Listener.
Medienmetadaten festlegen
Die Klasse MediaMetadata stellt die Informationen zu einer Mediendatei dar, die du streamen möchtest. Im folgenden Beispiel wird eine neue MediaMetadata-Instanz eines Films erstellt und der Titel, der Untertitel und zwei Bilder werden 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 eine Mediendatei laden, wie im folgenden Code gezeigt. Verwende zuerst MediaInfo.Builder mit den Metadaten des Mediums, 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 auf dem Webempfänger ausgeführte Mediaplayer-App abzuspielen, zu pausieren und anderweitig zu 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 im Abschnitt Medientracks verwenden.
4K-Videoformat
Wenn du das Videoformat deiner Medien prüfen möchtest, 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 die Konstanten HDR_TYPE_* angegeben.
Benachrichtigungen zur Fernbedienung an mehrere Geräte senden
Wenn ein Nutzer Inhalte streamt, erhalten andere Android-Geräte im selben Netzwerk eine Benachrichtigung, über die sie die Wiedergabe ebenfalls steuern können. Jeder, dessen Gerät solche Benachrichtigungen erhält, kann sie für dieses Gerät in der Einstellungen App unter „Google“ > „Google Cast“ > Benachrichtigungen zur Fernbedienung anzeigen deaktivieren. (Die Benachrichtigungen enthalten eine Verknüpfung zur Einstellungen-App.) Weitere Informationen findest du unter Benachrichtigungen zur Cast-Fernbedienung.
Mini-Controller hinzufügen
Gemäß der Cast-Design-Checkliste sollte eine Sender-App ein dauerhaftes Steuerelement, den sogenannten Mini-Controller, bereitstellen, der angezeigt wird, wenn der Nutzer die aktuelle Inhaltsseite verlässt und zu einem anderen Teil der Sender-App wechselt. Der Mini-Controller erinnert den Nutzer sichtbar an die aktuelle Cast-Sitzung. Durch Tippen auf den Mini-Controller kann der Nutzer zur maximierten Vollbildansicht des Cast-Controllers zurückkehren.
Das Framework bietet eine benutzerdefinierte Ansicht, „MiniControllerFragment“, die du unten in der Layoutdatei jeder Aktivität hinzufügen kannst, in der du den Mini-Controller anzeigen möchtest.
<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 deine Sender-App einen Video- oder Audio-Livestream abspielt, zeigt das SDK im Mini-Controller automatisch eine Wiedergabe-/Stopp-Schaltfläche anstelle der Wiedergabe-/Pause-Schaltfläche an.
Informationen zum Festlegen der Textdarstellung des Titels und Untertitels dieser benutzerdefinierten Ansicht und zum Auswählen von Schaltflächen findest du unter Mini-Controller anpassen.
Maximierten Controller hinzufügen
Gemäß der Google Cast-Design-Checkliste muss eine Sender-App einen erweiterten Controller für die gestreamten Medien bereitstellen. Der maximierte Controller ist eine Vollbildversion des Mini-Controllers.
Das Cast SDK bietet ein Widget für die erweiterte Steuerung namens ExpandedControllerActivity.
Dies ist eine abstrakte Klasse, die Sie als Unterklasse definieren müssen, um ein Cast-Symbol hinzuzufügen.
Erstelle zuerst eine neue Menüressourcendatei für den erweiterten Controller, um das Cast-Symbol bereitzustellen:
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/media_route_menu_item"
android:title="@string/media_route_menu_title"
app:actionProviderClass="androidx.mediarouter.app.MediaRouteActionProvider"
app:showAsAction="always"/>
</menu>
Erstellen Sie eine neue Klasse, die ExpandedControllerActivity erweitert.
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 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:
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, um die neue Aktivität anzuzeigen, 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 deine Sender-App einen Video- oder Audio-Livestream abspielt, zeigt das SDK im maximierten Controller automatisch eine Wiedergabe-/Stopp-Schaltfläche anstelle der Wiedergabe-/Pause-Schaltfläche an.
Informationen zum Festlegen des Erscheinungsbilds mithilfe von Designs, zum Auswählen der anzuzeigenden Schaltflächen und zum Hinzufügen benutzerdefinierter Schaltflächen finden Sie unter Erweiterten Controller anpassen.
Lautstärkeregelung
Das Framework verwaltet die Lautstärke für die Sender-App automatisch. Es synchronisiert die Sender- und Web-Receiver-Apps automatisch, sodass die Sender-UI immer die vom Web-Receiver angegebene Lautstärke meldet.
Lautstärkeregelung über physische Tasten
Unter Android können die physischen Tasten des Sendergeräts standardmäßig verwendet werden, um die Lautstärke der Übertragungssitzung auf dem Webempfänger für alle Geräte mit Jelly Bean oder höher zu ändern.
Lautstärkeregelung über physische Tasten vor Jelly Bean
Wenn du die physischen Lautstärketasten verwenden möchtest, um die Lautstärke des Webempfängers auf Android-Geräten vor Jelly Bean zu steuern, 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); } }
Mediensteuerung zu Benachrichtigungen und Sperrbildschirm hinzufügen
Nur unter Android ist es laut der Google Cast-Design-Checkliste erforderlich, dass eine Sender-App Mediensteuerelemente in einer Benachrichtigung und auf dem Sperrbildschirm implementiert, wenn der Sender streamt, die Sender-App aber nicht im Fokus ist. Das Framework bietet MediaNotificationService und MediaIntentReceiver, um der Sender-App die Erstellung von Mediensteuerungen in einer Benachrichtigung und auf dem Sperrbildschirm zu ermöglichen.
MediaNotificationService wird ausgeführt, wenn der Sender streamt. Es wird eine Benachrichtigung mit einem Vorschaubild und Informationen zum aktuellen Streamingelement, einer Wiedergabe-/Pausetaste und einer Stopptaste angezeigt.
MediaIntentReceiver ist ein BroadcastReceiver, der Nutzeraktionen aus der Benachrichtigung verarbeitet.
In deiner App können Benachrichtigungen und die Mediensteuerung über den Sperrbildschirm mit NotificationOptions konfiguriert werden.
In deiner App kannst du konfigurieren, welche Steuerelemente in der Benachrichtigung angezeigt werden sollen und welche Activity geöffnet werden soll, 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 Anzeige der Mediensteuerung in Benachrichtigungen und auf dem Sperrbildschirm ist standardmäßig aktiviert und kann durch Aufrufen von setNotificationOptions mit „null“ in CastMediaOptions.Builder deaktiviert werden.
Derzeit ist die Sperrbildschirm-Funktion 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 deine Sender-App einen Video- oder Audio-Livestream abspielt, zeigt das SDK automatisch eine Wiedergabe-/Stopp-Schaltfläche anstelle der Wiedergabe-/Pause-Schaltfläche in der Benachrichtigungssteuerung an, aber nicht in der Sperrbildschirmsteuerung.
Hinweis: Damit die Steuerelemente auf dem Sperrbildschirm auf Geräten mit einer Android-Version vor Lollipop angezeigt werden, fordert RemoteMediaClient automatisch den Audiofokus für dich an.
Fehler verarbeiten
Es ist sehr wichtig, dass Sender-Apps alle Fehler-Callbacks verarbeiten und die beste Antwort für jede Phase des Cast-Lebenszyklus bestimmen. Die App kann dem Nutzer Fehlerdialoge anzeigen oder die Verbindung zum Web-Empfänger trennen.