In diesem Entwicklerleitfaden wird beschrieben, wie Sie Ihrer Android-Absender-App mit dem Android Sender SDK Google Cast-Unterstützung 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 Absender-Framework bezieht sich auf das Binärprogramm der Cast-Klassenbibliothek und die zugehörigen Ressourcen, die zur Laufzeit auf dem Absender vorhanden sind. Die Absender-App oder Cast-App ist eine App, die auch auf dem Absender 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 Zuständen des Cast-App-Lebenszyklus zu wechseln.
Anwendungsfluss
Die folgenden Schritte beschreiben den typischen allgemeinen Ausführungsablauf für eine Sender-Android-App:
- Das Cast-Framework startet die Geräteerkennung automatisch basierend auf dem
Activity
-Lebenszyklus.MediaRouter
- Wenn der Nutzer auf die Schaltfläche „Streamen“ klickt, wird im Framework 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 in der Sender-App Callbacks auf, um zu bestätigen, dass die Web Receiver-App gestartet wurde.
- Das Framework stellt einen Kommunikationskanal zwischen dem Absender und Web Receiver-Apps her.
- Das Framework verwendet den Kommunikationskanal, um die Medienwiedergabe auf dem Web Receiver zu laden und zu steuern.
- Das Framework synchronisiert den Status der Medienwiedergabe zwischen Sender und Web Receiver: Wenn der Nutzer Aktionen auf der Sender-Benutzeroberfläche ausführt, übergibt das Framework diese Mediensteuerungsanfragen an den Web Receiver. Wenn der Web Receiver Medienstatus-Updates sendet, aktualisiert das Framework den Status der Sender-Benutzeroberfläche.
- Wenn der Nutzer auf die Schaltfläche „Streamen“ klickt, um die Verbindung zum Cast-Gerät zu trennen, trennt das Framework die Sender-App vom Web Receiver.
Eine vollständige 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 Ihrer Android-App Cast hinzufügen.
Android-Manifest konfigurieren
In der Datei „AndroidManifest.xml“ Ihrer App müssen Sie die folgenden Elemente für das Cast SDK konfigurieren:
uses-sdk
Legen Sie die minimalen und Ziel-Android-API-Levels fest, die vom Cast SDK unterstützt werden. Derzeit ist das Mindest-API-Level 23 und das Ziel-API-Level 34.
<uses-sdk
android:minSdkVersion="23"
android:targetSdkVersion="34" />
android:theme
Legen Sie das Design Ihrer App basierend auf der mindestens erforderlichen Android SDK-Version fest. Wenn Sie beispielsweise kein eigenes Theme implementieren, sollten Sie eine Variante von Theme.AppCompat
verwenden, wenn Sie eine Android SDK-Mindestversion vor Lollipop als Ziel festlegen.
<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 für die Initialisierung des CastContext
-Singleton 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, die zum Filtern von Erkennungsergebnissen und zum Starten der Web Receiver-App beim Starten einer Cast-Sitzung verwendet 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); } }
Cast-UX-Widgets
Das Cast-Framework bietet die Widgets, die der Cast-Design-Checkliste entsprechen:
Introductory Overlay: Das Framework bietet eine benutzerdefinierte Ansicht,
IntroductoryOverlay
, die dem Nutzer angezeigt wird, um ihn beim ersten Mal, wenn ein Empfänger verfügbar ist, auf die Cast-Schaltfläche aufmerksam zu machen. In der Sender-App kann der Text und die Position des Titeltexts angepasst werden.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 die Schaltfläche „Streamen“ klickt, während das Gerät verbunden ist, werden die aktuellen Media-Metadaten (z. B. Titel, Name des Aufnahmestudios und ein Thumbnail) angezeigt. Der Nutzer kann auch die Verbindung zum Übertragungsgerät trennen. Die Cast-Schaltfläche wird manchmal auch als Cast-Symbol bezeichnet.
Mini-Controller: Wenn der Nutzer Inhalte streamt und die Seite mit den aktuellen Inhalten oder den erweiterten Controller in der Sender-App verlassen hat, wird der Mini-Controller unten auf dem Bildschirm angezeigt. So kann der Nutzer die Metadaten der aktuell gestreamten Medien sehen und die Wiedergabe steuern.
Erweiterte Steuerung: Wenn der Nutzer Inhalte streamt und auf die Medienbenachrichtigung oder die Mini-Steuerung klickt, wird die erweiterte Steuerung gestartet. Dort werden die Metadaten der aktuell wiedergegebenen Medien angezeigt und es gibt mehrere Schaltflächen zur Steuerung der Medienwiedergabe.
Benachrichtigung: Nur für 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 nach einer Zeitüberschreitung zum Sperrbildschirm wechselt), wird eine Mediensteuerung auf dem Sperrbildschirm angezeigt, die die Metadaten der aktuell gestreamten Medien und die Wiedergabesteuerung enthält.
Im folgenden Leitfaden wird beschrieben, wie Sie diese Widgets in Ihre App einfügen.
Cast-Symbol hinzufügen
Die Android-APIs MediaRouter
wurden entwickelt, um die Medienwiedergabe auf sekundären Geräten zu ermöglichen.
Android-Apps, die die MediaRouter
API verwenden, sollten in ihrer Benutzeroberfläche ein Cast-Symbol enthalten, damit Nutzer einen Media-Route auswählen können, um Medien auf einem sekundären Gerät wie einem Cast-Gerät abzuspielen.
Das Framework macht das Hinzufügen eines MediaRouteButton
als Cast button
sehr einfach. Fügen Sie zuerst ein Menüelement oder ein MediaRouteButton
in die XML-Datei ein, in der Ihr Menü definiert ist, 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" />
// 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 wird, 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 finden Sie unter Cast-Symbol anpassen.
Geräteerkennung konfigurieren
Die Geräteerkennung wird vollständig von der CastContext
verwaltet.
Beim Initialisieren von CastContext gibt die Sender-App die Web Receiver-Anwendungs-ID an und kann optional 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 den Akkuverbrauch 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 Erkennungsvorgang wird beendet, wenn der Cast-Dialog 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
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 Media Control-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 Ihre App über CastContext.getSessionManager()
zugreifen kann.
Einzelne Sitzungen werden durch Unterklassen der Klasse Session
dargestellt.
CastSession
steht beispielsweise für Sitzungen mit Cast-Geräten. Ihre App kann über SessionManager.getCurrentCastSession()
auf die derzeit aktive Cast-Sitzung zugreifen.
Ihre App kann die Klasse SessionManagerListener
verwenden, um Sitzungsereignisse wie Erstellung, Unterbrechung, Fortsetzung und Beendigung zu überwachen. Das Framework versucht automatisch, eine Sitzung fortzusetzen, wenn sie abnormal oder abrupt beendet wurde.
Sitzungen werden automatisch als Reaktion auf Nutzeraktionen in den MediaRouter
-Dialogfeldern erstellt und beendet.
Um Fehler beim Starten des Cast-Vorgangs besser zu verstehen, können Apps CastContext#getCastReasonCodeForCastStatusCode(int)
verwenden, um den Fehler beim Starten der Sitzung in CastReasonCodes
zu konvertieren.
Beachten Sie, dass einige Fehler beim Starten von Sitzungen (z.B. CastReasonCodes#CAST_CANCELLED
) erwartetes Verhalten sind und nicht als Fehler protokolliert werden sollten.
Wenn Sie über die Statusänderungen für die Sitzung informiert werden müssen, können Sie eine SessionManagerListener
implementieren. In diesem Beispiel wird auf die Verfügbarkeit eines CastSession
in einem Activity
gewartet.
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 CastSession#addCastListener
.
Rufen Sie dann CastSession#getCastDevice()
während des onDeviceNameChanged
-Callbacks 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 Wiederverbindung in vielen subtilen Grenzfall-Szenarien zu verarbeiten, z. B.:
- Nach vorübergehendem WLAN-Verlust wiederherstellen
- Aus dem Ruhezustand reaktivieren
- Wiederherstellen nach dem Schließen der App im Hintergrund
- Wiederherstellen nach einem App-Absturz
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 das automatische Zusammenführen 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 einer neuen Klasse RemoteMediaClient
eingestellt. Diese bietet dieselben Funktionen in einer Reihe praktischerer APIs und macht die Übergabe eines GoogleApiClient überflüssig.
Wenn Ihre App eine CastSession
mit einer Web Receiver-App herstellt, die den Media-Namespace unterstützt, wird automatisch eine Instanz von RemoteMediaClient
vom Framework erstellt. Ihre App kann darauf zugreifen, indem sie die Methode getRemoteMediaClient()
für die CastSession
-Instanz aufruft.
Alle Methoden von RemoteMediaClient
, die Anfragen an den Web Receiver senden, geben ein PendingResult-Objekt zurück, mit dem die Anfrage verfolgt werden kann.
Es wird erwartet, dass die Instanz von RemoteMediaClient
von mehreren Teilen Ihrer App und sogar von einigen internen Komponenten des Frameworks, z. B. den persistenten Mini-Controllern und dem Benachrichtigungsdienst, gemeinsam genutzt wird.
Dazu unterstützt diese Instanz die Registrierung mehrerer Instanzen von RemoteMediaClient.Listener
.
Medienmetadaten festlegen
Die Klasse MediaMetadata
stellt die Informationen zu einem Media-Element dar, das du streamen möchtest. Im folgenden Beispiel wird eine neue MediaMetadata-Instanz für einen Film erstellt und der Titel, der 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))));
Weitere Informationen zur Verwendung von Bildern mit Medienmetadaten finden Sie unter Bildauswahl.
Medien laden
Ihre App kann ein Media-Element laden, wie im folgenden Code gezeigt. Verwenden Sie zuerst MediaInfo.Builder
mit den Metadaten der Media, um eine MediaInfo
-Instanz zu erstellen. Hole die RemoteMediaClient
aus dem aktuellen CastSession
und lade dann die MediaInfo
in diese RemoteMediaClient
. Mit RemoteMediaClient
kannst du eine Media Player-App, die auf dem Web Receiver ausgeführt wird, steuern, z. B. die Wiedergabe starten oder pausieren.
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 Mediatracks verwenden.
4K-Videoformat
Um das Videoformat Ihrer Medien zu prüfen, verwenden Sie
getVideoInfo()
in MediaStatus, um die aktuelle Instanz von
VideoInfo
abzurufen.
Diese Instanz enthält den Typ des HDR-TV-Formats sowie die Höhe und Breite des Displays in Pixeln. Varianten des 4K-Formats werden durch Konstanten HDR_TYPE_*
angegeben.
Benachrichtigungen zur Fernbedienung auf mehreren Geräten
Wenn ein Nutzer streamt, erhalten andere Android-Geräte im selben Netzwerk eine Benachrichtigung, damit auch sie die Wiedergabe steuern können. Jeder, dessen Gerät solche Benachrichtigungen empfängt, kann sie für dieses Gerät in den Einstellungen 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 Checkliste für das Cast-Design 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 an die aktuelle Cast-Sitzung. Durch Tippen auf die Mini-Steuerung kann der Nutzer zur erweiterten Vollbild-Steuerung für den Cast 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 die Mini-Steuerung 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 in deiner Sender-App ein Video- oder Audio-Livestream wiedergegeben wird, zeigt das SDK im Mini-Controller automatisch eine Schaltfläche zum Starten/Beenden anstelle der Schaltfläche zum Starten/Pausieren an.
Informationen zum Festlegen der Textdarstellung des Titels und Untertitels dieser benutzerdefinierten Ansicht sowie 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 eine erweiterte Steuerung für die übertragenen 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 unterteilen müssen, um eine Cast-Schaltfläche hinzuzufügen.
Erstellen Sie zuerst eine neue Menüressourcendatei für den erweiterten Controller, um die Cast-Schaltfläche 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 die 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 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 Ihre 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 in deiner Sender-App ein Video- oder Audio-Livestream wiedergegeben wird, zeigt das SDK im erweiterten Controller automatisch eine Schaltfläche zum Starten/Stoppen anstelle der Schaltfläche zum Starten/Pausieren 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. Außerdem synchronisiert es die Sender- und Web Receiver-Apps automatisch, sodass in der Sender-Benutzeroberfläche immer die vom Web Receiver angegebene Lautstärke angezeigt wird.
Lautstärkeregelung über physische Tasten
Auf Android-Geräten können die physischen Tasten auf dem Sendergerät standardmäßig verwendet werden, um die Lautstärke der Cast-Sitzung auf dem Web Receiver zu ändern. Das gilt für alle Geräte mit Jelly Bean oder höher.
Lautstärkeregelung über physische Tasten vor Jelly Bean
Wenn Sie die physischen Lautstärketasten verwenden möchten, um die Lautstärke des Web Receiver-Geräts 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); } }
Media-Steuerelemente zu Benachrichtigungen und zum Sperrbildschirm hinzufügen
Auf Android-Geräten muss eine Sender-App gemäß der Google Cast Design Checklist die Mediensteuerung in einer Benachrichtigung und auf dem Sperrbildschirm implementieren, wenn die Sender-App nicht im Fokus ist, aber Inhalte gestreamt werden. Das Framework bietet MediaNotificationService
und MediaIntentReceiver
, damit die Sender-App Mediensteuerelemente in einer Benachrichtigung und auf dem Sperrbildschirm erstellen kann.
MediaNotificationService
wird ausgeführt, wenn der Absender streamt. Es wird eine Benachrichtigung mit einem Bild-Thumbnail und Informationen zum aktuellen Streamingelement, eine Schaltfläche zum Abspielen/Pausieren und eine Schaltfläche zum Beenden angezeigt.
MediaIntentReceiver
ist ein BroadcastReceiver
, das Nutzeraktionen aus der Benachrichtigung verarbeitet.
Ihre App kann die Benachrichtigungs- und Mediensteuerung über den Sperrbildschirm über NotificationOptions
konfigurieren.
Ihre App kann konfigurieren, welche Steuerungsschaltflächen in der Benachrichtigung angezeigt werden 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 über die Benachrichtigung und den Sperrbildschirm ist standardmäßig aktiviert und kann durch Aufrufen von setNotificationOptions
mit „null“ in CastMediaOptions.Builder
deaktiviert werden.
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 Schaltfläche zum Starten/Beenden anstelle der Schaltfläche zum Starten/Pausieren in der Benachrichtigungssteuerung, aber nicht in der Sperrbildschirmsteuerung an.
Hinweis: Damit Sperrbildschirm-Steuerelemente auf Geräten vor 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 die beste Reaktion für jede Phase des Cast-Lebenszyklus festlegen. Die App kann dem Nutzer Fehlerdialogfelder anzeigen oder die Verbindung zum Web Receiver trennen.