In diesem Entwicklerleitfaden wird beschrieben, wie Sie Ihrer Android-Absender-App Google Cast-Unterstützung mithilfe des Android Sender SDK 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 auf dem Absender zur Laufzeit vorhanden sind. Die Absender-App oder Cast-App bezieht sich auf eine App, die ebenfalls auf dem Absender ausgeführt wird. Die Web Receiver App bezieht sich auf die HTML-App, die auf dem für Google Cast optimierten Gerät ausgeführt wird.
Das Sender-Framework verwendet ein asynchrones Callback-Design, um die Sender-App über Ereignisse zu informieren und zwischen verschiedenen Zuständen des Lebenszyklus der Cast-App zu wechseln.
Anwendungsfluss
Die folgenden Schritte beschreiben den typischen allgemeinen Ausführungsablauf für eine Absender-Android-App:
- Das Cast-Framework startet automatisch die
MediaRouter
-Geräteerkennung basierend auf demActivity
-Lebenszyklus. - Wenn der Nutzer auf das Cast-Symbol klickt, öffnet das Framework das Cast-Dialogfeld mit der Liste der erkannten Übertragungsgeräte.
- Wenn der Nutzer ein Übertragungsgerät auswählt, versucht das Framework, die Web Receiver App auf dem Übertragungsgerät zu starten.
- Das Framework ruft Callbacks in der Senderanwendung auf, um zu bestätigen, dass die Webempfängeranwendung gestartet wurde.
- Das Framework erstellt einen Kommunikationskanal zwischen den Sender- und Webempfängeranwendungen.
- Das Framework verwendet den Kommunikationskanal, um die Medienwiedergabe auf dem Webempfänger zu laden und zu steuern.
- Das Framework synchronisiert den Status der Medienwiedergabe zwischen Sender und Webempfänger: Wenn der Nutzer UI-Aktionen des Absenders ausführt, leitet das Framework diese Mediensteuerungsanfragen an den Webempfänger weiter. Wenn der Webempfänger Aktualisierungen des Medienstatus sendet, aktualisiert das Framework den Status der Sender-UI.
- Wenn der Nutzer auf das Cast-Symbol klickt, um die Verbindung zum Übertragungsgerät zu trennen, trennt das Framework die Absender-App vom Webempfänger.
Eine umfassende Liste aller Klassen, Methoden und Ereignisse im Google Cast Android SDK finden Sie in der Google Cast Sender API-Referenz für Android. In den folgenden Abschnitten wird beschrieben, wie Sie Cast zu Ihrer Android-App hinzufügen.
Android-Manifest konfigurieren
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 die Ziel-Android API-Levels fest, die vom Cast SDK unterstützt werden. Aktuell ist das Minimum API-Level 21 und das Ziel ist API-Level 28.
<uses-sdk
android:minSdkVersion="21"
android:targetSdkVersion="28" />
android:theme
Legen Sie das Design Ihrer App entsprechend der Android SDK-Mindestversion fest. Wenn du beispielsweise kein eigenes Design implementierst, solltest du eine Variante von Theme.AppCompat
verwenden, wenn du eine Android SDK-Mindestversion vor Lollipop auswählst.
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/Theme.AppCompat" >
...
</application>
Streamingkontext initialisieren
Das Framework hat ein globales Singleton-Objekt, CastContext
, das alle Interaktionen des Frameworks koordiniert.
Ihre App muss die Schnittstelle OptionsProvider
implementieren, um Optionen zum Initialisieren des Singleton-CastContext
s bereitzustellen. OptionsProvider
stellt eine Instanz von CastOptions
bereit, die Optionen enthält, die das Verhalten des Frameworks beeinflussen. Die wichtigste davon ist die Web Receiver-Anwendungs-ID. Sie wird zum Filtern von Erkennungsergebnissen und zum Starten der Web Receiver-App beim Start einer Streaming-Sitzung verwendet.
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; } }
Du musst den voll qualifizierten Namen der implementierten OptionsProvider
als Metadatenfeld in der Datei AndroidManifest.xml der Absender-App deklarieren:
<application>
...
<meta-data
android:name=
"com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
android:value="com.foo.CastOptionsProvider" />
</application>
CastContext
wird beim Aufrufen von CastContext.getSharedInstance()
verzögert initialisiert.
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 Widgets bereit, die der Checkliste für das Cast-Design entsprechen:
Einleitendes Overlay: Das Framework bietet eine benutzerdefinierte Ansicht (
IntroductoryOverlay
), die dem Nutzer angezeigt wird, um die Aufmerksamkeit auf das Cast-Symbol zu lenken, wenn ein Empfänger zum ersten Mal verfügbar ist. Die Sender-Anwendung kann den Text und die Position des Titeltexts anpassen.Cast-Symbol: Das Cast-Symbol ist sichtbar, wenn ein Empfänger gefunden wird, der Ihre App unterstützt. Wenn der Nutzer das erste Mal auf das Cast-Symbol klickt, wird ein Cast-Dialogfeld mit den erkannten Geräten angezeigt. Wenn der Nutzer bei verbundenem Gerät auf das Cast-Symbol klickt, werden die aktuellen Medienmetadaten (z. B. Titel, Name des Aufnahmestudios und eine Miniaturansicht) angezeigt oder der Nutzer kann die Verbindung zum Übertragungsgerät trennen.
Mini Controller: Wenn der Nutzer Inhalte streamt und von der aktuellen Inhaltsseite oder dem erweiterten Controller zu einem anderen Bildschirm in der Sender-App gewechselt ist, wird der Mini-Controller unten auf dem Bildschirm angezeigt, damit der Nutzer die aktuell gestreamten Medienmetadaten sehen und die Wiedergabe steuern kann.
Erweiterter Controller: Wenn der Nutzer Inhalte streamt, wird der maximierte Controller gestartet, wenn er auf die Medienbenachrichtigung oder den Mini-Controller klickt. Er zeigt die aktuell wiedergegebenen Medienmetadaten an und bietet mehrere Schaltflächen zum Steuern der Medienwiedergabe.
Benachrichtigung: nur Android. Wenn der Nutzer Inhalte streamt und die Absender-App verlässt, wird eine Medienbenachrichtigung mit den aktuell gestreamten Medienmetadaten und Wiedergabesteuerungen angezeigt.
Sperrbildschirm: nur Android. Wenn der Nutzer Inhalte streamt und zum Sperrbildschirm wechselt (oder das Gerät die Zeit überschreitet), wird ein Steuerelement für den Medien-Sperrbildschirm angezeigt, das die aktuell gestreamten Medienmetadaten und die Wiedergabesteuerung anzeigt.
In der folgenden Anleitung wird beschrieben, wie Sie Ihrer App diese Widgets hinzufügen.
Cast-Symbol hinzufügen
Die Android-APIs MediaRouter
ermöglichen die Anzeige und Wiedergabe von Medien auf Sekundärgeräten.
Android-Apps, die die MediaRouter
API verwenden, sollten in der Benutzeroberfläche eine Cast-Schaltfläche enthalten, damit Nutzer eine Medienroute auswählen können, um Medien auf einem zweiten Gerät wie einem Übertragungsgerät abzuspielen.
Mit dem Framework ist das Hinzufügen eines MediaRouteButton
als Cast button
sehr einfach. Fügen Sie zuerst einen Menüpunkt oder ein MediaRouteButton
in die XML-Datei ein, die Ihr Menü definiert, und verwenden Sie CastButtonFactory
, um es mit dem Framework zu verbinden.
// To add a Cast button, add the following snippet.
// menu.xml
<item
android:id="@+id/media_route_menu_item"
android:title="@string/media_route_menu_title"
app:actionProviderClass="androidx.mediarouter.app.MediaRouteActionProvider"
app:showAsAction="always" />
// 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
dann Änderungen von FragmentActivity
übernimmt, können Sie Ihrem Layout eine MediaRouteButton
hinzufügen.
// activity_layout.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal" >
<androidx.mediarouter.app.MediaRouteButton
android:id="@+id/media_route_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:mediaRouteTypes="user"
android:visibility="gone" />
</LinearLayout>
// 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 das Cast-Symbol auf einem Design dargestellt wird, erfahren Sie unter Streamen-Schaltfläche anpassen.
Geräteerkennung konfigurieren
Die Geräteerkennung wird vollständig von der CastContext
verwaltet.
Beim Initialisieren von CastContext gibt die Absender-App die ID der Web Receiver-Anwendung an und kann optional die Namespace-Filterung anfordern, indem sie supportedNamespaces
in CastOptions
festlegt.
CastContext
enthält einen Verweis auf MediaRouter
intern und startet den Erkennungsprozess, wenn die Absenderanwendung in den Vordergrund wechselt, und stoppt, wenn die Absenderanwendung in den Hintergrund eintritt.
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. Dabei werden die Schritte zum Verbinden mit einem Gerät, Starten (oder Beitritt) einer Web-Receiver-App, Verbinden mit dieser App und Initialisieren eines Mediensteuerungskanals kombiniert. Weitere Informationen zu Übertragungssitzungen und dem Lebenszyklus des Webempfängers finden Sie in der Anleitung zum Lebenszyklus von Anwendungen für den Web Receiver.
Sitzungen werden von der Klasse SessionManager
verwaltet, auf die Ihre Anwendung über CastContext.getSessionManager()
zugreifen kann.
Einzelne Sitzungen werden durch abgeleitete Klassen der Klasse Session
dargestellt.
CastSession
repräsentiert beispielsweise Sitzungen mit Übertragungsgeräten. Deine App kann über SessionManager.getCurrentCastSession()
auf die derzeit aktive Streaming-Sitzung zugreifen.
Ihre Anwendung kann die Klasse SessionManagerListener
verwenden, um Sitzungsereignisse wie das Erstellen, Sperren, Wiederaufnahme und Beenden von Sitzungen zu überwachen. Das Framework versucht automatisch, die Sitzung nach einer abnormalen/abrupten Beendigung fortzusetzen, während eine Sitzung aktiv war.
Sitzungen werden als Reaktion auf Nutzergesten in den MediaRouter
-Dialogfeldern erstellt und gelöscht.
Zum besseren Verständnis von Fehlern beim Starten von Streaming können Apps CastContext#getCastReasonCodeForCastStatusCode(int)
verwenden, um den Sitzungsstartfehler in CastReasonCodes
zu konvertieren.
Einige Fehler beim Starten von Sitzungen (z.B. CastReasonCodes#CAST_CANCELLED
) sind beabsichtigtes Verhalten und sollten nicht als Fehler protokolliert werden.
Wenn Sie die Statusänderungen für die Sitzung kennen müssen, können Sie eine SessionManagerListener
implementieren. In diesem Beispiel wird die Verfügbarkeit einer 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 } override fun onResume() { super.onResume() mCastSession = mSessionManager.currentCastSession mSessionManager.addSessionManagerListener(mSessionManagerListener, CastSession::class.java) } override fun onPause() { super.onPause() mSessionManager.removeSessionManagerListener(mSessionManagerListener, CastSession::class.java) mCastSession = null } }
public class MyActivity extends Activity { private CastContext mCastContext; private CastSession mCastSession; private SessionManager mSessionManager; private SessionManagerListener<CastSession> mSessionManagerListener = new SessionManagerListenerImpl(); private class SessionManagerListenerImpl implements SessionManagerListener<CastSession> { @Override public void onSessionStarting(CastSession session) {} @Override public void onSessionStarted(CastSession session, String sessionId) { invalidateOptionsMenu(); } @Override public void onSessionStartFailed(CastSession session, int error) { int castReasonCode = mCastContext.getCastReasonCodeForCastStatusCode(error); // Handle error } @Override public void onSessionSuspended(CastSession session, int reason) {} @Override public void onSessionResuming(CastSession session, String sessionId) {} @Override public void onSessionResumed(CastSession session, boolean wasSuspended) { invalidateOptionsMenu(); } @Override public void onSessionResumeFailed(CastSession session, int error) {} @Override public void onSessionEnding(CastSession session) {} @Override public void onSessionEnded(CastSession session, int error) { finish(); } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mCastContext = CastContext.getSharedInstance(this); mSessionManager = mCastContext.getSessionManager(); } @Override protected void onResume() { super.onResume(); mCastSession = mSessionManager.getCurrentCastSession(); mSessionManager.addSessionManagerListener(mSessionManagerListener, CastSession.class); } @Override protected void onPause() { super.onPause(); mSessionManager.removeSessionManagerListener(mSessionManagerListener, CastSession.class); mCastSession = null; } }
Stream-Übertragung
Das Beibehalten des Sitzungsstatus ist die Grundlage der Streamübertragung, bei der Nutzer vorhandene Audio- und Videostreams über Sprachbefehle, die Google Home App oder 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 Übertragungsgerät mit der neuesten Firmware kann als Quellen oder Ziele bei einer Streamübertragung dienen.
Wenn Sie das neue Zielgerät während einer Streamübertragung oder -erweiterung abrufen möchten, registrieren Sie ein 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 Wiederherstellung der Verbindung
Das Framework stellt eine ReconnectionService
bereit, die von der Senderanwendung aktiviert werden kann, um in vielen kleinen Fällen das erneute Herstellen einer Verbindung zu verarbeiten, z. B.:
- Wiederherstellung nach einem vorübergehenden WLAN-Ausfall
- Aus dem Ruhemodus wiederherstellen
- App aus dem Hintergrund der App wiederherstellen
- App-Absturz wiederherstellen
Dieser Dienst ist standardmäßig aktiviert und kann in CastOptions.Builder
deaktiviert werden.
Dieser Dienst kann automatisch mit dem Manifest Ihrer App zusammengeführt werden, wenn die automatische Zusammenführung in Ihrer Gradle-Datei aktiviert ist.
Das Framework startet den Dienst, wenn eine Mediensitzung vorhanden ist, und beendet ihn, wenn die Mediensitzung endet.
So funktioniert die Mediensteuerung
Das Cast-Framework stellt die Klasse RemoteMediaPlayer
von Cast 2.x ein. Stattdessen wird eine neue Klasse RemoteMediaClient
eingeführt, die die gleiche Funktionalität in einer Reihe einfacherer APIs bietet und keine GoogleApiClient übergeben muss.
Wenn Ihre Anwendung ein CastSession
mit einer Web-Receiver-App einrichtet, die den Medien-Namespace unterstützt, wird vom Framework automatisch eine Instanz von RemoteMediaClient
erstellt. Ihre Anwendung kann darauf zugreifen, indem die Methode getRemoteMediaClient()
in der CastSession
-Instanz aufgerufen wird.
Alle Methoden von RemoteMediaClient
, die Anfragen an den Webempfänger senden, geben ein Objekt vom Typ „PendingResult“ zurück, mit dem diese Anfrage verfolgt werden kann.
Es ist zu erwarten, dass die Instanz von RemoteMediaClient
von mehreren Teilen Ihrer App und sogar von einigen internen Komponenten des Frameworks wie den nichtflüchtigen Mini-Controllern und dem Benachrichtigungsdienst gemeinsam genutzt werden kann.
Zu diesem Zweck unterstützt diese Instanz die Registrierung mehrerer Instanzen von RemoteMediaClient.Listener
.
Medienmetadaten festlegen
Die Klasse MediaMetadata
stellt die Informationen zu einem Medienelement dar, das Sie streamen möchten. Im folgenden Beispiel wird eine neue MediaMetadata-Instanz eines Films erstellt und Titel, 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 Medienelement laden, wie im folgenden Code dargestellt. Verwenden Sie zuerst MediaInfo.Builder
mit den Medienmetadaten, um eine MediaInfo
-Instanz zu erstellen. Rufen Sie die RemoteMediaClient
aus dem aktuellen CastSession
ab und laden Sie dann die MediaInfo
in diese RemoteMediaClient
. Mit RemoteMediaClient
können Sie eine auf dem Web-Receiver ausgeführte Mediaplayer-App wiedergeben, 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 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 Anzeigehöhe und -breite in Pixeln. Varianten des 4K-Formats sind durch die Konstanten HDR_TYPE_*
gekennzeichnet.
Benachrichtigungen auf mehreren Geräten per Fernbedienung steuern
Wenn ein Nutzer Inhalte streamt, erhalten andere Android-Geräte im selben Netzwerk eine Benachrichtigung, damit sie die Wiedergabe steuern können. Jeder, dessen Gerät solche Benachrichtigungen erhält, kann sie in den Einstellungen unter „Google“ > „Google Cast“ > Benachrichtigungen zur Fernbedienung anzeigen für dieses Gerät deaktivieren. (Die Benachrichtigungen enthalten auch eine Verknüpfung zur App „Einstellungen“.) Weitere Informationen finden Sie unter Benachrichtigungen für die Streaming-Fernsteuerung.
Mini-Controller hinzufügen
Gemäß der Checkliste für Übertragungsdesign sollte eine Sender-App eine dauerhafte Steuerung, den sogenannten Mini-Controller, bieten, die eingeblendet werden sollte, wenn der Nutzer von der aktuellen Inhaltsseite zu einem anderen Teil der Sender-App wechselt. Der Mini-Controller zeigt dem Nutzer eine sichtbare Erinnerung an die aktuelle Streaming-Sitzung. Durch Tippen auf den Mini-Controller können Nutzer zur maximierten Controller-Ansicht im Vollbildmodus zurückkehren.
Das Framework stellt eine benutzerdefinierte Ansicht (MiniControllerFragment) bereit, die Sie am Ende der Layoutdatei jeder Aktivität hinzufügen können, in der Sie den Mini-Controller anzeigen möchten.
<fragment
android:id="@+id/castMiniController"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:visibility="gone"
class="com.google.android.gms.cast.framework.media.widget.MiniControllerFragment" />
Wenn deine Sender-App einen Video- oder Audio-Livestream wiedergibt, zeigt das SDK automatisch eine Wiedergabe-/Stopp-Schaltfläche anstelle der Wiedergabe-/Pause-Taste auf dem Mini-Controller an.
Informationen zum Festlegen der Textdarstellung des Titels und Untertitels dieser benutzerdefinierten Ansicht und zur Auswahl von Schaltflächen finden Sie unter Mini-Controller anpassen.
Maximierten Controller hinzufügen
Gemäß der Checkliste für das Google Cast-Design 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 enthält ein Widget für den erweiterten Controller mit dem Namen ExpandedControllerActivity
.
Dies ist eine abstrakte Klasse, deren abgeleitete Klasse ist, um ein Cast-Symbol hinzufügen zu können.
Erstellen Sie zuerst eine neue Menüressourcendatei für den maximierten Controller, um das Cast-Symbol bereitzustellen:
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/media_route_menu_item"
android:title="@string/media_route_menu_title"
app:actionProviderClass="androidx.mediarouter.app.MediaRouteActionProvider"
app:showAsAction="always"/>
</menu>
Erstellen Sie eine neue Klasse, die ExpandedControllerActivity
erweitert.
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 die neue Aktivität im App-Manifest im application
-Tag:
<application>
...
<activity
android:name=".expandedcontrols.ExpandedControlsActivity"
android:label="@string/app_name"
android:launchMode="singleTask"
android:theme="@style/Theme.CastVideosDark"
android:screenOrientation="portrait"
android:parentActivityName="com.google.sample.cast.refplayer.VideoBrowserActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
</intent-filter>
</activity>
...
</application>
Bearbeite die CastOptionsProvider
und ändere NotificationOptions
und CastMediaOptions
, um die Zielaktivität auf deine 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 Remotemedien 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 die Absender-App einen Video- oder Audio-Livestream wiedergibt, zeigt das SDK im maximierten Controller automatisch eine Wiedergabe-/Stopp-Schaltfläche anstelle der Wiedergabe-/Pause-Schaltfläche an.
Wie Sie die Darstellung anhand von Designs festlegen können, erfahren Sie unter Erweiterten Controller anpassen. Wählen Sie dazu die anzuzeigenden Schaltflächen aus und fügen Sie benutzerdefinierte Schaltflächen hinzu.
Lautstärkeregelung
Das Framework verwaltet automatisch das Volume für die Absenderanwendung. Es synchronisiert automatisch die Sender- und Webempfängeranwendungen, sodass die Absender-UI immer das vom Webempfänger angegebene Volumen meldet.
Lautstärkeregelung über physische Taste
Unter Android können Sie die Lautstärke der Streamingsitzung auf dem Web Receiver über die physischen Tasten am Sendergerät standardmäßig für alle Geräte ändern, auf denen Jelly Bean oder ein neueres Modell verwendet wird.
Lautstärkeregelung vor Jelly Bean
Wenn Sie die Lautstärke des Web Receiver-Geräts auf Android-Geräten, die älter als Jelly Bean sind, mithilfe der physischen Lautstärketasten verwenden möchten, muss die Absender-App dispatchKeyEvent
in ihren Aktivitäten überschreiben und CastContext.onDispatchVolumeKeyEventBeforeJellyBean()
aufrufen:
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 Benachrichtigungs- und Sperrbildschirm hinzufügen
Nur unter Android erfordert die Checkliste für das Google Cast-Design, dass eine Sender-App Mediensteuerelemente in einer Benachrichtigung und auf dem Sperrbildschirm implementiert, wo der Absender zwar streamt, die Sender-App jedoch nicht im Fokus ist. Das Framework bietet MediaNotificationService
und MediaIntentReceiver
, damit die Absender-App Mediensteuerelemente in einer Benachrichtigung und auf dem Sperrbildschirm erstellen kann.
MediaNotificationService
wird ausgeführt, während der Absender eine Übertragung startet. Sie zeigt eine Benachrichtigung mit der Miniaturansicht des Bildes und Informationen zum aktuellen Streamingelement sowie einer Schaltfläche für Wiedergabe/Pause und Stopp an.
MediaIntentReceiver
ist ein BroadcastReceiver
, der Nutzeraktionen aus der Benachrichtigung verarbeitet.
Ihre App kann Benachrichtigungen und die Mediensteuerung vom Sperrbildschirm über NotificationOptions
konfigurieren.
Deine App kann konfigurieren, welche Steuerschaltflächen in der Benachrichtigung angezeigt werden sollen und welche Activity
geöffnet werden sollen, wenn der Nutzer auf die Benachrichtigung tippt. Wenn keine Aktionen explizit angegeben sind, 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();
Das Anzeigen von Mediensteuerelementen über Benachrichtigungen 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 Ihre Absender-App einen Video- oder Audio-Livestream wiedergibt, zeigt das SDK auf dem Benachrichtigungssteuerelement, aber nicht auf dem Sperrbildschirm-Steuerelement automatisch eine Wiedergabe-/Stopp-Schaltfläche anstelle der Wiedergabe-/Pause-Schaltfläche an.
Hinweis: Zur Anzeige der Steuerelemente für den Sperrbildschirm auf Geräten mit früheren Lollipop-Versionen fordert RemoteMediaClient
automatisch den Audiofokus in Ihrem Namen an.
Fehler verarbeiten
Für Sender-Apps ist es sehr wichtig, alle Fehler-Callbacks zu verarbeiten und die beste Antwort für jede Phase des Cast-Lebenszyklus zu bestimmen. Die Anwendung kann dem Nutzer Fehlerdialoge anzeigen oder entscheiden, die Verbindung zum Web Receiver zu beenden.