Diese Seite enthält Code-Snippets und Beschreibungen der Funktionen, die zum Anpassen einer Android TV-Empfänger-App verfügbar sind.
Bibliotheken konfigurieren
So stellen Sie die Cast Connect APIs für Ihre Android TV-App zur Verfügung:
-
Öffnen Sie die Datei
build.gradleim Verzeichnis Ihres Anwendungsmoduls. -
Prüfen Sie, ob
google()in der aufgeführtenrepositoriesenthalten ist.repositories { google() } -
Fügen Sie je nach Zielgerätetyp für Ihre App die aktuellen Versionen der Bibliotheken zu Ihren Abhängigkeiten hinzu:
-
Für die Android-Empfänger-App:
dependencies { implementation 'com.google.android.gms:play-services-cast-tv:21.1.1' implementation 'com.google.android.gms:play-services-cast:23.0.0' }
-
Für die Android-Sender-App:
dependencies { implementation 'com.google.android.gms:play-services-cast:21.1.1' implementation 'com.google.android.gms:play-services-cast-framework:23.0.0' }
-
Für die Android-Empfänger-App:
-
Speichern Sie die Änderungen und klicken Sie in der Symbolleiste auf
Sync Project with Gradle Files.
-
Achten Sie darauf, dass
Podfileaufgoogle-cast-sdk4.8.6 oder höher ausgerichtet ist. -
iOS 16 oder höher als Ziel festlegen. Weitere Informationen finden Sie in den Versionshinweisen.
platform: ios, '16' def target_pods pod 'google-cast-sdk', '~>4.8.6' end
- Erfordert die Chromium-Browserversion M87 oder höher.
-
Web Sender API-Bibliothek zum Projekt hinzufügen
<script src="//www.gstatic.com/cv/js/sender/v1/cast_sender.js?loadCastFramework=1"></script>
AndroidX-Anforderung
Für neue Versionen der Google Play-Dienste muss eine App aktualisiert worden sein, um den Namespace androidx zu verwenden. Folgen Sie der Anleitung für die Migration zu AndroidX.
Android TV-App – Voraussetzungen
Damit Cast Connect in deiner Android TV-App unterstützt wird, musst du Ereignisse aus einer Mediensitzung erstellen und unterstützen. Die von der Mediensitzung bereitgestellten Daten enthalten die grundlegenden Informationen für den Medienstatus, z. B. Position und Wiedergabestatus. Ihre Mediensitzung wird auch von der Cast Connect-Bibliothek verwendet, um zu signalisieren, wenn sie bestimmte Nachrichten von einem Absender empfangen hat, z. B. „Pause“.
Weitere Informationen zu Mediensitzungen und zur Initialisierung einer Mediensitzung finden Sie im Leitfaden zum Arbeiten mit einer Mediensitzung.
Lebenszyklus einer Mediensitzung
Deine App sollte eine Mediensitzung erstellen, wenn die Wiedergabe beginnt, und sie freigeben, wenn sie nicht mehr gesteuert werden kann. Wenn Ihre App beispielsweise eine Video-App ist, sollten Sie die Sitzung freigeben, wenn der Nutzer die Wiedergabeaktivität beendet – entweder durch Auswahl von „Zurück“, um andere Inhalte zu durchsuchen, oder durch Ausführen der App im Hintergrund. Wenn Ihre App eine Musik-App ist, sollten Sie sie freigeben, wenn keine Medien mehr wiedergegeben werden.
Sitzungsstatus wird aktualisiert
Die Daten in deiner Mediensitzung sollten immer dem Status deines Players entsprechen. Wenn die Wiedergabe beispielsweise pausiert ist, solltest du den Wiedergabestatus sowie die unterstützten Aktionen aktualisieren. In den folgenden Tabellen sind die Status aufgeführt, die Sie auf dem neuesten Stand halten müssen.
MediaMetadataCompat
| Metadatenfeld | Beschreibung |
|---|---|
| METADATA_KEY_TITLE (erforderlich) | Der Titel des Mediums. |
| METADATA_KEY_DISPLAY_SUBTITLE | Der Untertitel. |
| METADATA_KEY_DISPLAY_ICON_URI | Die Symbol-URL. |
| METADATA_KEY_DURATION (erforderlich) | Medienlänge. |
| METADATA_KEY_MEDIA_URI | Die Content-ID. |
| METADATA_KEY_ARTIST | Der Künstler. |
| METADATA_KEY_ALBUM | Das Album. |
PlaybackStateCompat
| Erforderliche Methode | Beschreibung |
|---|---|
| setActions() | Legt unterstützte Medienbefehle fest. |
| setState() | Wiedergabestatus und aktuelle Position festlegen |
MediaSessionCompat
| Erforderliche Methode | Beschreibung |
|---|---|
| setRepeatMode() | Legt den Wiederholungsmodus fest. |
| setShuffleMode() | Legt den Zufallsmodus fest. |
| setMetadata() | Legt Medienmetadaten fest. |
| setPlaybackState() | Legt den Wiedergabestatus fest. |
private fun updateMediaSession() { val metadata = MediaMetadataCompat.Builder() .putString(MediaMetadataCompat.METADATA_KEY_TITLE, "title") .putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, "subtitle") .putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON_URI, mMovie.getCardImageUrl()) .build() val playbackState = PlaybackStateCompat.Builder() .setState( PlaybackStateCompat.STATE_PLAYING, player.getPosition(), player.getPlaybackSpeed(), System.currentTimeMillis() ) .build() mediaSession.setMetadata(metadata) mediaSession.setPlaybackState(playbackState) }
private void updateMediaSession() {
MediaMetadataCompat metadata =
new MediaMetadataCompat.Builder()
.putString(MediaMetadataCompat.METADATA_KEY_TITLE, "title")
.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, "subtitle")
.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON_URI,mMovie.getCardImageUrl())
.build();
PlaybackStateCompat playbackState =
new PlaybackStateCompat.Builder()
.setState(
PlaybackStateCompat.STATE_PLAYING,
player.getPosition(),
player.getPlaybackSpeed(),
System.currentTimeMillis())
.build();
mediaSession.setMetadata(metadata);
mediaSession.setPlaybackState(playbackState);
}Transportsteuerung verarbeiten
Ihre App sollte den Callback für die Transportsteuerung der Mediensitzung implementieren. In der folgenden Tabelle sind die Aktionen zur Transportsteuerung aufgeführt, die sie verarbeiten müssen:
MediaSessionCompat.Callback
| Aktionen | Beschreibung |
|---|---|
| onPlay() | Fortsetzen |
| onPause() | Pausieren |
| onSeekTo() | Zu einer Position springen |
| onStop() | Aktuelle Medienwiedergabe beenden |
class MyMediaSessionCallback : MediaSessionCompat.Callback() { override fun onPause() { // Pause the player and update the play state. ... } override fun onPlay() { // Resume the player and update the play state. ... } override fun onSeekTo (long pos) { // Seek and update the play state. ... } ... } mediaSession.setCallback( MyMediaSessionCallback() );
public MyMediaSessionCallback extends MediaSessionCompat.Callback { public void onPause() { // Pause the player and update the play state. ... } public void onPlay() { // Resume the player and update the play state. ... } public void onSeekTo (long pos) { // Seek and update the play state. ... } ... } mediaSession.setCallback(new MyMediaSessionCallback());
Cast-Unterstützung konfigurieren
Wenn eine Sender-Anwendung eine Startanfrage sendet, wird ein Intent mit einem Anwendungs-Namespace erstellt. Deine Anwendung ist dafür verantwortlich, sie zu verarbeiten und eine Instanz des CastReceiverContext-Objekts zu erstellen, wenn die TV-App gestartet wird. Das CastReceiverContext-Objekt ist erforderlich, um mit Cast zu interagieren, während die TV-App ausgeführt wird. Mit diesem Objekt kann deine TV-Anwendung Cast-Mediennachrichten von allen verbundenen Sendern empfangen.
Android TV einrichten
Intent-Filter für den Start hinzufügen
Füge der Aktivität, die den Start-Intent von deiner Sender-App verarbeiten soll, einen neuen Intent-Filter hinzu:
<activity android:name="com.example.activity">
<intent-filter>
<action android:name="com.google.android.gms.cast.tv.action.LAUNCH" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
Anbieter für Empfängeroptionen angeben
Du musst eine ReceiverOptionsProvider implementieren, um CastReceiverOptions bereitzustellen:
class MyReceiverOptionsProvider : ReceiverOptionsProvider { override fun getOptions(context: Context?): CastReceiverOptions { return CastReceiverOptions.Builder(context) .setStatusText("My App") .build() } }
public class MyReceiverOptionsProvider implements ReceiverOptionsProvider { @Override public CastReceiverOptions getOptions(Context context) { return new CastReceiverOptions.Builder(context) .setStatusText("My App") .build(); } }
Geben Sie dann den Optionsanbieter in Ihrer AndroidManifest an:
<meta-data
android:name="com.google.android.gms.cast.tv.RECEIVER_OPTIONS_PROVIDER_CLASS_NAME"
android:value="com.example.mysimpleatvapplication.MyReceiverOptionsProvider" />
ReceiverOptionsProvider wird verwendet, um CastReceiverOptions bereitzustellen, wenn CastReceiverContext initialisiert wird.
Kontext des Streamingempfängers
Initialisieren Sie das CastReceiverContext, wenn Ihre App erstellt wird:
override fun onCreate() { CastReceiverContext.initInstance(this) ... }
@Override public void onCreate() { CastReceiverContext.initInstance(this); ... }
Starten Sie die CastReceiverContext, wenn Ihre App in den Vordergrund wechselt:
CastReceiverContext.getInstance().start()
CastReceiverContext.getInstance().start();
Rufen Sie stop() für CastReceiverContext auf, nachdem die App in den Hintergrund gewechselt ist, wenn es sich um Video-Apps oder Apps handelt, die keine Hintergrundwiedergabe unterstützen:
// Player has stopped. CastReceiverContext.getInstance().stop()
// Player has stopped. CastReceiverContext.getInstance().stop();
Wenn deine App die Wiedergabe im Hintergrund unterstützt, rufe stop() für CastReceiverContext auf, wenn die Wiedergabe im Hintergrund beendet wird.
Wir empfehlen dringend, den LifecycleObserver aus der Bibliothek androidx.lifecycle zu verwenden, um die Aufrufe von CastReceiverContext.start() und CastReceiverContext.stop() zu verwalten, insbesondere wenn Ihre systemeigene App mehrere Aktivitäten hat. So werden Race-Bedingungen vermieden, wenn Sie start() und stop() aus verschiedenen Aktivitäten aufrufen.
// Create a LifecycleObserver class. class MyLifecycleObserver : DefaultLifecycleObserver { override fun onStart(owner: LifecycleOwner) { // App prepares to enter foreground. CastReceiverContext.getInstance().start() } override fun onStop(owner: LifecycleOwner) { // App has moved to the background or has terminated. CastReceiverContext.getInstance().stop() } } // Add the observer when your application is being created. class MyApplication : Application() { fun onCreate() { super.onCreate() // Initialize CastReceiverContext. CastReceiverContext.initInstance(this /* android.content.Context */) // Register LifecycleObserver ProcessLifecycleOwner.get().lifecycle.addObserver( MyLifecycleObserver()) } }
// Create a LifecycleObserver class. public class MyLifecycleObserver implements DefaultLifecycleObserver { @Override public void onStart(LifecycleOwner owner) { // App prepares to enter foreground. CastReceiverContext.getInstance().start(); } @Override public void onStop(LifecycleOwner owner) { // App has moved to the background or has terminated. CastReceiverContext.getInstance().stop(); } } // Add the observer when your application is being created. public class MyApplication extends Application { @Override public void onCreate() { super.onCreate(); // Initialize CastReceiverContext. CastReceiverContext.initInstance(this /* android.content.Context */); // Register LifecycleObserver ProcessLifecycleOwner.get().getLifecycle().addObserver( new MyLifecycleObserver()); } }
// In AndroidManifest.xml set MyApplication as the application class
<application
...
android:name=".MyApplication">
MediaSession mit MediaManager verbinden
Wenn du ein MediaSession erstellst, musst du auch das aktuelle MediaSession-Token für CastReceiverContext angeben, damit es weiß, wohin die Befehle gesendet und wo der Medienwiedergabestatus abgerufen werden soll:
val mediaManager: MediaManager = receiverContext.getMediaManager() mediaManager.setSessionCompatToken(currentMediaSession.getSessionToken())
MediaManager mediaManager = receiverContext.getMediaManager(); mediaManager.setSessionCompatToken(currentMediaSession.getSessionToken());
Wenn du deinen MediaSession aufgrund inaktiver Wiedergabe freigibst, solltest du ein Null-Token für MediaManager festlegen:
myPlayer.stop() mediaSession.release() mediaManager.setSessionCompatToken(null)
myPlayer.stop(); mediaSession.release(); mediaManager.setSessionCompatToken(null);
Wenn deine App die Wiedergabe von Medien im Hintergrund unterstützt, solltest du CastReceiverContext.stop() nicht aufrufen, wenn deine App in den Hintergrund verschoben wird, sondern nur, wenn sie im Hintergrund ausgeführt wird und keine Medien mehr wiedergibt. Beispiel:
class MyLifecycleObserver : DefaultLifecycleObserver { ... // App has moved to the background. override fun onPause(owner: LifecycleOwner) { mIsBackground = true myStopCastReceiverContextIfNeeded() } } // Stop playback on the player. private fun myStopPlayback() { myPlayer.stop() myStopCastReceiverContextIfNeeded() } // Stop the CastReceiverContext when both the player has // stopped and the app has moved to the background. private fun myStopCastReceiverContextIfNeeded() { if (mIsBackground && myPlayer.isStopped()) { CastReceiverContext.getInstance().stop() } }
public class MyLifecycleObserver implements DefaultLifecycleObserver { ... // App has moved to the background. @Override public void onPause(LifecycleOwner owner) { mIsBackground = true; myStopCastReceiverContextIfNeeded(); } } // Stop playback on the player. private void myStopPlayback() { myPlayer.stop(); myStopCastReceiverContextIfNeeded(); } // Stop the CastReceiverContext when both the player has // stopped and the app has moved to the background. private void myStopCastReceiverContextIfNeeded() { if (mIsBackground && myPlayer.isStopped()) { CastReceiverContext.getInstance().stop(); } }
Exoplayer mit Cast Connect verwenden
Wenn du Exoplayer verwendest, kannst du die MediaSessionConnector verwenden, um die Sitzung und alle zugehörigen Informationen, einschließlich des Wiedergabestatus, automatisch beizubehalten, anstatt die Änderungen manuell zu verfolgen.
MediaSessionConnector.MediaButtonEventHandler kann verwendet werden, um MediaButton-Ereignisse durch Aufrufen von setMediaButtonEventHandler(MediaButtonEventHandler) zu verarbeiten, die sonst standardmäßig von MediaSessionCompat.Callback verarbeitet werden.
Wenn Sie MediaSessionConnector in Ihre App einbinden möchten, fügen Sie Ihrer Player-Aktivitätsklasse oder der Stelle, an der Sie Ihre Mediensitzung verwalten, Folgendes hinzu:
class PlayerActivity : Activity() { private var mMediaSession: MediaSessionCompat? = null private var mMediaSessionConnector: MediaSessionConnector? = null private var mMediaManager: MediaManager? = null override fun onCreate(savedInstanceState: Bundle?) { ... mMediaSession = MediaSessionCompat(this, LOG_TAG) mMediaSessionConnector = MediaSessionConnector(mMediaSession!!) ... } override fun onStart() { ... mMediaManager = receiverContext.getMediaManager() mMediaManager!!.setSessionCompatToken(currentMediaSession.getSessionToken()) mMediaSessionConnector!!.setPlayer(mExoPlayer) mMediaSessionConnector!!.setMediaMetadataProvider(mMediaMetadataProvider) mMediaSession!!.isActive = true ... } override fun onStop() { ... mMediaSessionConnector!!.setPlayer(null) mMediaSession!!.release() mMediaManager!!.setSessionCompatToken(null) ... } }
public class PlayerActivity extends Activity { private MediaSessionCompat mMediaSession; private MediaSessionConnector mMediaSessionConnector; private MediaManager mMediaManager; @Override protected void onCreate(Bundle savedInstanceState) { ... mMediaSession = new MediaSessionCompat(this, LOG_TAG); mMediaSessionConnector = new MediaSessionConnector(mMediaSession); ... } @Override protected void onStart() { ... mMediaManager = receiverContext.getMediaManager(); mMediaManager.setSessionCompatToken(currentMediaSession.getSessionToken()); mMediaSessionConnector.setPlayer(mExoPlayer); mMediaSessionConnector.setMediaMetadataProvider(mMediaMetadataProvider); mMediaSession.setActive(true); ... } @Override protected void onStop() { ... mMediaSessionConnector.setPlayer(null); mMediaSession.release(); mMediaManager.setSessionCompatToken(null); ... } }
Einrichtung der Sender-App
Cast Connect-Unterstützung aktivieren
Nachdem du deine Sender-App mit Cast Connect-Unterstützung aktualisiert hast, kannst du ihre Bereitschaft deklarieren, indem du das Flag androidReceiverCompatible in LaunchOptions auf „true“ setzt.
Erfordert play-services-cast-framework-Version 19.0.0 oder höher.
Das Flag androidReceiverCompatible ist in LaunchOptions (Teil von CastOptions) festgelegt:
class CastOptionsProvider : OptionsProvider { override fun getCastOptions(context: Context?): CastOptions { val launchOptions: LaunchOptions = Builder() .setAndroidReceiverCompatible(true) .build() return CastOptions.Builder() .setLaunchOptions(launchOptions) ... .build() } }
public class CastOptionsProvider implements OptionsProvider { @Override public CastOptions getCastOptions(Context context) { LaunchOptions launchOptions = new LaunchOptions.Builder() .setAndroidReceiverCompatible(true) .build(); return new CastOptions.Builder() .setLaunchOptions(launchOptions) ... .build(); } }
Erfordert google-cast-sdk-Version v4.4.8 oder höher.
Das Flag androidReceiverCompatible ist in GCKLaunchOptions festgelegt (Teil von GCKCastOptions):
let options = GCKCastOptions(discoveryCriteria: GCKDiscoveryCriteria(applicationID: kReceiverAppID)) ... let launchOptions = GCKLaunchOptions() launchOptions.androidReceiverCompatible = true options.launchOptions = launchOptions GCKCastContext.setSharedInstanceWith(options)
Erfordert die Chromium-Browserversion M87 oder höher.
const context = cast.framework.CastContext.getInstance(); const castOptions = new cast.framework.CastOptions(); castOptions.receiverApplicationId = kReceiverAppID; castOptions.androidReceiverCompatible = true; context.setOptions(castOptions);
Cast Developer Console einrichten
Android TV-App konfigurieren
Füge den Paketnamen deiner Android TV-App in der Cast Developer Console hinzu, um ihn mit deiner Cast-App-ID zu verknüpfen.

Entwicklergeräte registrieren
Registriere die Seriennummer des Android TV-Geräts, das du für die Entwicklung verwenden möchtest, in der Cast Developer Console.

Ohne Registrierung funktioniert Cast Connect aus Sicherheitsgründen nur für Apps, die über den Google Play Store installiert wurden.
Weitere Informationen zur Registrierung eines Cast- oder Android TV-Geräts für die Cast-Entwicklung findest du auf der Registrierungsseite.
Medien werden geladen
Wenn du in deiner Android TV-App bereits Deeplinks implementiert hast, sollte in deinem Android TV-Manifest eine ähnliche Definition konfiguriert sein:
<activity android:name="com.example.activity">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="https"/>
<data android:host="www.example.com"/>
<data android:pathPattern=".*"/>
</intent-filter>
</activity>
Last nach Entität auf dem Absender
Auf den Sendern kannst du den Deeplink übergeben, indem du entity in den Medieninformationen für die Ladeanfrage festlegst:
val mediaToLoad = MediaInfo.Builder("some-id") .setEntity("https://example.com/watch/some-id") ... .build() val loadRequest = MediaLoadRequestData.Builder() .setMediaInfo(mediaToLoad) .setCredentials("user-credentials") ... .build() remoteMediaClient.load(loadRequest)
MediaInfo mediaToLoad = new MediaInfo.Builder("some-id") .setEntity("https://example.com/watch/some-id") ... .build(); MediaLoadRequestData loadRequest = new MediaLoadRequestData.Builder() .setMediaInfo(mediaToLoad) .setCredentials("user-credentials") ... .build(); remoteMediaClient.load(loadRequest);
let mediaInfoBuilder = GCKMediaInformationBuilder(entity: "https://example.com/watch/some-id") ... mediaInformation = mediaInfoBuilder.build() let mediaLoadRequestDataBuilder = GCKMediaLoadRequestDataBuilder() mediaLoadRequestDataBuilder.mediaInformation = mediaInformation mediaLoadRequestDataBuilder.credentials = "user-credentials" ... let mediaLoadRequestData = mediaLoadRequestDataBuilder.build() remoteMediaClient?.loadMedia(with: mediaLoadRequestData)
Erfordert die Chromium-Browserversion M87 oder höher.
let mediaInfo = new chrome.cast.media.MediaInfo('some-id"', 'video/mp4'); mediaInfo.entity = 'https://example.com/watch/some-id'; ... let request = new chrome.cast.media.LoadRequest(mediaInfo); request.credentials = 'user-credentials'; ... cast.framework.CastContext.getInstance().getCurrentSession().loadMedia(request);
Der Ladebefehl wird über einen Intent mit Ihrem Deeplink und dem Paketnamen gesendet, den Sie in der Entwicklerkonsole definiert haben.
ATV-Anmeldedaten auf dem Sender festlegen
Es ist möglich, dass deine Web-Empfänger-App und deine Android TV-App unterschiedliche Deeplinks und credentials unterstützen, z. B. wenn du die Authentifizierung auf den beiden Plattformen unterschiedlich handhabst. Sie können das Problem beheben, indem Sie alternative entity und credentials für Android TV angeben:
val mediaToLoad = MediaInfo.Builder("some-id") .setEntity("https://example.com/watch/some-id") .setAtvEntity("myscheme://example.com/atv/some-id") ... .build() val loadRequest = MediaLoadRequestData.Builder() .setMediaInfo(mediaToLoad) .setCredentials("user-credentials") .setAtvCredentials("atv-user-credentials") ... .build() remoteMediaClient.load(loadRequest)
MediaInfo mediaToLoad = new MediaInfo.Builder("some-id") .setEntity("https://example.com/watch/some-id") .setAtvEntity("myscheme://example.com/atv/some-id") ... .build(); MediaLoadRequestData loadRequest = new MediaLoadRequestData.Builder() .setMediaInfo(mediaToLoad) .setCredentials("user-credentials") .setAtvCredentials("atv-user-credentials") ... .build(); remoteMediaClient.load(loadRequest);
let mediaInfoBuilder = GCKMediaInformationBuilder(entity: "https://example.com/watch/some-id") mediaInfoBuilder.atvEntity = "myscheme://example.com/atv/some-id" ... mediaInformation = mediaInfoBuilder.build() let mediaLoadRequestDataBuilder = GCKMediaLoadRequestDataBuilder() mediaLoadRequestDataBuilder.mediaInformation = mediaInformation mediaLoadRequestDataBuilder.credentials = "user-credentials" mediaLoadRequestDataBuilder.atvCredentials = "atv-user-credentials" ... let mediaLoadRequestData = mediaLoadRequestDataBuilder.build() remoteMediaClient?.loadMedia(with: mediaLoadRequestData)
Erfordert die Chromium-Browserversion M87 oder höher.
let mediaInfo = new chrome.cast.media.MediaInfo('some-id"', 'video/mp4'); mediaInfo.entity = 'https://example.com/watch/some-id'; mediaInfo.atvEntity = 'myscheme://example.com/atv/some-id'; ... let request = new chrome.cast.media.LoadRequest(mediaInfo); request.credentials = 'user-credentials'; request.atvCredentials = 'atv-user-credentials'; ... cast.framework.CastContext.getInstance().getCurrentSession().loadMedia(request);
Wenn die Web-Empfänger-App gestartet wird, verwendet sie die entity und credentials in der Ladeanfrage. Wenn deine Android TV-App jedoch gestartet wird, überschreibt das SDK die entity und credentials mit deinen atvEntity und atvCredentials (falls angegeben).
Laden nach Content ID oder MediaQueueData
Wenn du entity oder atvEntity nicht verwendest und Content ID oder Content URL in deinen Medieninformationen oder die detaillierteren Daten zur Medienladeanfrage verwendest, musst du den folgenden vordefinierten Intent-Filter in deiner Android TV-App hinzufügen:
<activity android:name="com.example.activity">
<intent-filter>
<action android:name="com.google.android.gms.cast.tv.action.LOAD"/>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
Auf der Senderseite kannst du ähnlich wie beim Laden nach Entität eine Ladeanfrage mit deinen Inhaltsinformationen erstellen und load() aufrufen.
val mediaToLoad = MediaInfo.Builder("some-id").build() val loadRequest = MediaLoadRequestData.Builder() .setMediaInfo(mediaToLoad) .setCredentials("user-credentials") ... .build() remoteMediaClient.load(loadRequest)
MediaInfo mediaToLoad = new MediaInfo.Builder("some-id").build(); MediaLoadRequestData loadRequest = new MediaLoadRequestData.Builder() .setMediaInfo(mediaToLoad) .setCredentials("user-credentials") ... .build(); remoteMediaClient.load(loadRequest);
let mediaInfoBuilder = GCKMediaInformationBuilder(contentId: "some-id") ... mediaInformation = mediaInfoBuilder.build() let mediaLoadRequestDataBuilder = GCKMediaLoadRequestDataBuilder() mediaLoadRequestDataBuilder.mediaInformation = mediaInformation mediaLoadRequestDataBuilder.credentials = "user-credentials" ... let mediaLoadRequestData = mediaLoadRequestDataBuilder.build() remoteMediaClient?.loadMedia(with: mediaLoadRequestData)
Erfordert die Chromium-Browserversion M87 oder höher.
let mediaInfo = new chrome.cast.media.MediaInfo('some-id"', 'video/mp4'); ... let request = new chrome.cast.media.LoadRequest(mediaInfo); ... cast.framework.CastContext.getInstance().getCurrentSession().loadMedia(request);
Ladeanfragen verarbeiten
In Ihrer Aktivität müssen Sie die Intents in den Lebenszyklus-Callbacks Ihrer Aktivität verarbeiten, um diese Ladeanfragen zu verarbeiten:
class MyActivity : Activity() { override fun onStart() { super.onStart() val mediaManager = CastReceiverContext.getInstance().getMediaManager() // Pass the intent to the SDK. You can also do this in onCreate(). if (mediaManager.onNewIntent(intent)) { // If the SDK recognizes the intent, you should early return. return } // If the SDK doesn't recognize the intent, you can handle the intent with // your own logic. ... } // For some cases, a new load intent triggers onNewIntent() instead of // onStart(). override fun onNewIntent(intent: Intent) { val mediaManager = CastReceiverContext.getInstance().getMediaManager() // Pass the intent to the SDK. You can also do this in onCreate(). if (mediaManager.onNewIntent(intent)) { // If the SDK recognizes the intent, you should early return. return } // If the SDK doesn't recognize the intent, you can handle the intent with // your own logic. ... } }
public class MyActivity extends Activity { @Override protected void onStart() { super.onStart(); MediaManager mediaManager = CastReceiverContext.getInstance().getMediaManager(); // Pass the intent to the SDK. You can also do this in onCreate(). if (mediaManager.onNewIntent(getIntent())) { // If the SDK recognizes the intent, you should early return. return; } // If the SDK doesn't recognize the intent, you can handle the intent with // your own logic. ... } // For some cases, a new load intent triggers onNewIntent() instead of // onStart(). @Override protected void onNewIntent(Intent intent) { MediaManager mediaManager = CastReceiverContext.getInstance().getMediaManager(); // Pass the intent to the SDK. You can also do this in onCreate(). if (mediaManager.onNewIntent(intent)) { // If the SDK recognizes the intent, you should early return. return; } // If the SDK doesn't recognize the intent, you can handle the intent with // your own logic. ... } }
Wenn MediaManager erkennt, dass es sich um einen Lade-Intent handelt, wird ein MediaLoadRequestData-Objekt aus dem Intent extrahiert und MediaLoadCommandCallback.onLoad() aufgerufen.
Sie müssen diese Methode überschreiben, um die Ladeanfrage zu verarbeiten. Der Callback muss registriert werden, bevor MediaManager.onNewIntent() aufgerufen wird. Es wird empfohlen, eine Aktivitäts- oder Anwendungsmethode onCreate() zu verwenden.
class MyActivity : Activity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val mediaManager = CastReceiverContext.getInstance().getMediaManager() mediaManager.setMediaLoadCommandCallback(MyMediaLoadCommandCallback()) } } class MyMediaLoadCommandCallback : MediaLoadCommandCallback() { override fun onLoad( senderId: String?, loadRequestData: MediaLoadRequestData ): Task{ return Tasks.call { // Resolve the entity into your data structure and load media. val mediaInfo = loadRequestData.getMediaInfo() if (!checkMediaInfoSupported(mediaInfo)) { // Throw MediaException to indicate load failure. throw MediaException( MediaError.Builder() .setDetailedErrorCode(DetailedErrorCode.LOAD_FAILED) .setReason(MediaError.ERROR_REASON_INVALID_REQUEST) .build() ) } myFillMediaInfo(MediaInfoWriter(mediaInfo)) myPlayerLoad(mediaInfo.getContentUrl()) // Update media metadata and state (this clears all previous status // overrides). castReceiverContext.getMediaManager() .setDataFromLoad(loadRequestData) ... castReceiverContext.getMediaManager().broadcastMediaStatus() // Return the resolved MediaLoadRequestData to indicate load success. return loadRequestData } } private fun myPlayerLoad(contentURL: String) { myPlayer.load(contentURL) // Update the MediaSession state. val playbackState: PlaybackStateCompat = Builder() .setState( player.getState(), player.getPosition(), System.currentTimeMillis() ) ... .build() mediaSession.setPlaybackState(playbackState) }
public class MyActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); MediaManager mediaManager = CastReceiverContext.getInstance().getMediaManager(); mediaManager.setMediaLoadCommandCallback(new MyMediaLoadCommandCallback()); } } public class MyMediaLoadCommandCallback extends MediaLoadCommandCallback { @Override public TaskonLoad(String senderId, MediaLoadRequestData loadRequestData) { return Tasks.call(() -> { // Resolve the entity into your data structure and load media. MediaInfo mediaInfo = loadRequestData.getMediaInfo(); if (!checkMediaInfoSupported(mediaInfo)) { // Throw MediaException to indicate load failure. throw new MediaException( new MediaError.Builder() .setDetailedErrorCode(DetailedErrorCode.LOAD_FAILED) .setReason(MediaError.ERROR_REASON_INVALID_REQUEST) .build()); } myFillMediaInfo(new MediaInfoWriter(mediaInfo)); myPlayerLoad(mediaInfo.getContentUrl()); // Update media metadata and state (this clears all previous status // overrides). castReceiverContext.getMediaManager() .setDataFromLoad(loadRequestData); ... castReceiverContext.getMediaManager().broadcastMediaStatus(); // Return the resolved MediaLoadRequestData to indicate load success. return loadRequestData; }); } private void myPlayerLoad(String contentURL) { myPlayer.load(contentURL); // Update the MediaSession state. PlaybackStateCompat playbackState = new PlaybackStateCompat.Builder() .setState( player.getState(), player.getPosition(), System.currentTimeMillis()) ... .build(); mediaSession.setPlaybackState(playbackState); }
Zum Verarbeiten des Lade-Intents kannst du den Intent in die von uns definierten Datenstrukturen parsen (MediaLoadRequestData für Ladeanfragen).
Unterstützung von Medienbefehlen
Unterstützung für grundlegende Wiedergabesteuerung
Grundlegende Integrationsbefehle umfassen die Befehle, die mit Mediensitzungen kompatibel sind. Diese Befehle werden über Media Session-Callbacks benachrichtigt. Dazu musst du einen Callback für die Mediensitzung registrieren (möglicherweise hast du das bereits getan).
private class MyMediaSessionCallback : MediaSessionCompat.Callback() { override fun onPause() { // Pause the player and update the play state. myPlayer.pause() } override fun onPlay() { // Resume the player and update the play state. myPlayer.play() } override fun onSeekTo(pos: Long) { // Seek and update the play state. myPlayer.seekTo(pos) } ... } mediaSession.setCallback(MyMediaSessionCallback())
private class MyMediaSessionCallback extends MediaSessionCompat.Callback { @Override public void onPause() { // Pause the player and update the play state. myPlayer.pause(); } @Override public void onPlay() { // Resume the player and update the play state. myPlayer.play(); } @Override public void onSeekTo(long pos) { // Seek and update the play state. myPlayer.seekTo(pos); } ... } mediaSession.setCallback(new MyMediaSessionCallback());
Unterstützung von Cast-Steuerbefehlen
Einige Cast-Befehle sind in MediaSession nicht verfügbar, z. B. skipAd() oder setActiveMediaTracks().
Außerdem müssen hier einige Warteschlangenbefehle implementiert werden, da die Cast-Warteschlange nicht vollständig mit der MediaSession-Warteschlange kompatibel ist.
class MyMediaCommandCallback : MediaCommandCallback() { override fun onSkipAd(requestData: RequestData?): Task<Void?> { // Skip your ad ... return Tasks.forResult(null) } } val mediaManager = CastReceiverContext.getInstance().getMediaManager() mediaManager.setMediaCommandCallback(MyMediaCommandCallback())
public class MyMediaCommandCallback extends MediaCommandCallback { @Override public TaskonSkipAd(RequestData requestData) { // Skip your ad ... return Tasks.forResult(null); } } MediaManager mediaManager = CastReceiverContext.getInstance().getMediaManager(); mediaManager.setMediaCommandCallback(new MyMediaCommandCallback());
Unterstützte Medienbefehle angeben
Wie bei deinem Cast-Empfänger sollte auch in deiner Android TV-App angegeben werden, welche Befehle unterstützt werden, damit Sender bestimmte UI-Steuerelemente aktivieren oder deaktivieren können. Geben Sie für Befehle, die Teil von MediaSession sind, die Befehle in PlaybackStateCompat an.
Zusätzliche Befehle sollten in der MediaStatusModifier angegeben werden.
// Set media session supported commands val playbackState: PlaybackStateCompat = PlaybackStateCompat.Builder() .setActions(PlaybackStateCompat.ACTION_PLAY or PlaybackStateCompat.ACTION_PAUSE) .setState(PlaybackStateCompat.STATE_PLAYING) .build() mediaSession.setPlaybackState(playbackState) // Set additional commands in MediaStatusModifier val mediaManager = CastReceiverContext.getInstance().getMediaManager() mediaManager.getMediaStatusModifier() .setMediaCommandSupported(MediaStatus.COMMAND_QUEUE_NEXT)
// Set media session supported commands PlaybackStateCompat playbackState = new PlaybackStateCompat.Builder() .setActions(PlaybackStateCompat.ACTION_PLAY | PlaybackStateCompat.ACTION_PAUSE) .setState(PlaybackStateCompat.STATE_PLAYING) .build(); mediaSession.setPlaybackState(playbackState); // Set additional commands in MediaStatusModifier MediaManager mediaManager = CastReceiverContext.getInstance().getMediaManager(); mediaManager.getMediaStatusModifier() .setMediaCommandSupported(MediaStatus.COMMAND_QUEUE_NEXT);
Nicht unterstützte Schaltflächen ausblenden
Wenn deine Android TV-App nur die grundlegende Mediensteuerung unterstützt, deine Web-Receiver-App jedoch erweiterte Steuerelemente bietet, solltest du dafür sorgen, dass sich deine Sender-App beim Streamen an die Android TV-App richtig verhält. Wenn deine Android TV-App beispielsweise das Ändern der Wiedergabegeschwindigkeit nicht unterstützt, deine Web-Receiver-App jedoch schon, solltest du die unterstützten Aktionen auf jeder Plattform richtig festlegen und dafür sorgen, dass deine Sender-App die Benutzeroberfläche richtig rendert.
MediaStatus ändern
Damit erweiterte Funktionen wie Tracks, Anzeigen, Live-Inhalte und Warteschlangen unterstützt werden, muss deine Android TV-App zusätzliche Informationen bereitstellen, die nicht über MediaSession ermittelt werden können.
Dafür stellen wir die Klasse MediaStatusModifier bereit. MediaStatusModifier wird immer auf der MediaSession ausgeführt, die Sie in CastReceiverContext festgelegt haben.
So erstellen und übertragen Sie MediaStatus:
val mediaManager: MediaManager = castReceiverContext.getMediaManager() val statusModifier: MediaStatusModifier = mediaManager.getMediaStatusModifier() statusModifier .setLiveSeekableRange(seekableRange) .setAdBreakStatus(adBreakStatus) .setCustomData(customData) mediaManager.broadcastMediaStatus()
MediaManager mediaManager = castReceiverContext.getMediaManager();
MediaStatusModifier statusModifier = mediaManager.getMediaStatusModifier();
statusModifier
.setLiveSeekableRange(seekableRange)
.setAdBreakStatus(adBreakStatus)
.setCustomData(customData);
mediaManager.broadcastMediaStatus();Unsere Clientbibliothek ruft den Basis-MediaStatus von MediaSession ab. Deine Android TV-App kann zusätzliche Status angeben und Status über einen MediaStatus-Modifikator überschreiben.
Einige Status und Metadaten können sowohl in MediaSession als auch in MediaStatusModifier festgelegt werden. Wir empfehlen dringend, sie nur in MediaSession festzulegen. Sie können den Modifikator weiterhin verwenden, um die Status in MediaSession zu überschreiben. Davon wird jedoch abgeraten, da der Status im Modifikator immer eine höhere Priorität hat als die von MediaSession bereitgestellten Werte.
MediaStatus vor dem Senden abfangen
Wie beim Web Receiver SDK können Sie auch hier vor dem Senden noch letzte Anpassungen vornehmen. Dazu geben Sie einen MediaStatusInterceptor an, um die zu sendende MediaStatus zu verarbeiten. Wir übergeben einen MediaStatusWriter, um die MediaStatus vor dem Senden zu bearbeiten.
mediaManager.setMediaStatusInterceptor(object : MediaStatusInterceptor { override fun intercept(mediaStatusWriter: MediaStatusWriter) { // Perform customization. mediaStatusWriter.setCustomData(JSONObject("{data: \"my Hello\"}")) } })
mediaManager.setMediaStatusInterceptor(new MediaStatusInterceptor() { @Override public void intercept(MediaStatusWriter mediaStatusWriter) { // Perform customization. mediaStatusWriter.setCustomData(new JSONObject("{data: \"my Hello\"}")); } });
Umgang mit Nutzeranmeldedaten
In deiner Android TV-App können möglicherweise nur bestimmte Nutzer die App-Sitzung starten oder daran teilnehmen. Beispielsweise können Sie einem Absender nur dann erlauben, eine Nachricht zu starten oder daran teilzunehmen, wenn:
- Die Sender-App ist im selben Konto und Profil angemeldet wie die ATV-App.
- Die Sender-App ist im selben Konto angemeldet, aber in einem anderen Profil als die ATV-App.
Wenn deine App mehrere oder anonyme Nutzer unterstützt, kannst du zusätzlichen Nutzern erlauben, an der ATV-Sitzung teilzunehmen. Wenn der Nutzer Anmeldedaten angibt, muss deine ATV-App diese verarbeiten, damit sein Fortschritt und andere Nutzerdaten ordnungsgemäß erfasst werden können.
Wenn deine Sender-App deine Android TV-App startet oder ihr beitritt, sollte sie die Anmeldedaten der Person angeben, die der Sitzung beitritt.
Bevor ein Sender deine Android TV-App startet und ihr beitritt, kannst du eine Startprüfung festlegen, um zu sehen, ob die Anmeldedaten des Senders zulässig sind. Andernfalls wird der Web Receiver über das Cast Connect SDK gestartet.
Anmeldedaten für den Start der Sender-App
Auf der Senderseite kannst du die CredentialsData angeben, um darzustellen, wer der Sitzung beitritt.
credentials ist ein String, der benutzerdefiniert sein kann, solange er von deiner ATV-App verstanden wird. credentialsType definiert, von welcher Plattform die CredentialsData stammt, oder kann ein benutzerdefinierter Wert sein. Standardmäßig ist sie auf die Plattform eingestellt, von der sie gesendet wird.
Die CredentialsData wird nur beim Starten oder Beitreten an Ihre Android TV-App übergeben. Wenn du es noch einmal festlegst, während du verbunden bist, wird es nicht an deine Android TV-App weitergegeben. Wenn der Absender das Profil wechselt, während er verbunden ist, kannst du entweder in der Sitzung bleiben oder SessionManager.endCurrentCastSession(boolean stopCasting) aufrufen, wenn du der Meinung bist, dass das neue Profil nicht mit der Sitzung kompatibel ist.
Die CredentialsData für jeden Absender kann mit getSenders auf der CastReceiverContext abgerufen werden, um die SenderInfo zu erhalten, mit getCastLaunchRequest(), um die CastLaunchRequest zu erhalten, und dann mit getCredentialsData().
Erfordert play-services-cast-framework-Version 19.0.0 oder höher.
CastContext.getSharedInstance().setLaunchCredentialsData( CredentialsData.Builder() .setCredentials("{\"userId\": \"abc\"}") .build() )
CastContext.getSharedInstance().setLaunchCredentialsData(
new CredentialsData.Builder()
.setCredentials("{\"userId\": \"abc\"}")
.build());Erfordert google-cast-sdk-Version v4.8.6 oder höher.
Kann jederzeit aufgerufen werden, nachdem die Optionen festgelegt wurden: GCKCastContext.setSharedInstanceWith(options).
GCKCastContext.sharedInstance().setLaunch( GCKCredentialsData(credentials: "{\"userId\": \"abc\"}")
Erfordert die Chromium-Browserversion M87 oder höher.
Kann jederzeit aufgerufen werden, nachdem die Optionen festgelegt wurden: cast.framework.CastContext.getInstance().setOptions(options);.
let credentialsData = new chrome.cast.CredentialsData("{\"userId\": \"abc\"}"); cast.framework.CastContext.getInstance().setLaunchCredentialsData(credentialsData);
ATV-Startanfragenprüfung implementieren
Die CredentialsData wird an deine Android TV-App übergeben, wenn ein Sender versucht, sie zu starten oder ihr beizutreten. Sie können eine LaunchRequestChecker implementieren.
um diese Anfrage zuzulassen oder abzulehnen.
Wenn eine Anfrage abgelehnt wird, wird der Web-Empfänger geladen, anstatt die ATV-App nativ zu starten. Du solltest eine Anfrage ablehnen, wenn dein ATV die Anfrage des Nutzers zum Starten oder Beitreten nicht verarbeiten kann. Beispiele: Ein anderer Nutzer ist in der ATV-App angemeldet als der, der die Anfrage stellt, und deine App kann nicht mit dem Wechsel der Anmeldedaten umgehen, oder es ist derzeit kein Nutzer in der ATV-App angemeldet.
Wenn eine Anfrage genehmigt wird, wird die ATV-App gestartet. Du kannst dieses Verhalten anpassen, je nachdem, ob deine App das Senden von Ladeanfragen unterstützt, wenn ein Nutzer nicht in der ATV-App angemeldet ist oder wenn ein Nutzerkonflikt vorliegt. Dieses Verhalten kann im LaunchRequestChecker vollständig angepasst werden.
Erstellen Sie eine Klasse, die die Schnittstelle CastReceiverOptions.LaunchRequestChecker implementiert:
class MyLaunchRequestChecker : LaunchRequestChecker { override fun checkLaunchRequestSupported(launchRequest: CastLaunchRequest): Task{ return Tasks.call { myCheckLaunchRequest( launchRequest ) } } } private fun myCheckLaunchRequest(launchRequest: CastLaunchRequest): Boolean { val credentialsData = launchRequest.getCredentialsData() ?: return false // or true if you allow anonymous users to join. // The request comes from a mobile device, e.g. checking user match. return if (credentialsData.credentialsType == CredentialsData.CREDENTIALS_TYPE_ANDROID) { myCheckMobileCredentialsAllowed(credentialsData.getCredentials()) } else false // Unrecognized credentials type. }
public class MyLaunchRequestChecker implements CastReceiverOptions.LaunchRequestChecker { @Override public TaskcheckLaunchRequestSupported(CastLaunchRequest launchRequest) { return Tasks.call(() -> myCheckLaunchRequest(launchRequest)); } } private boolean myCheckLaunchRequest(CastLaunchRequest launchRequest) { CredentialsData credentialsData = launchRequest.getCredentialsData(); if (credentialsData == null) { return false; // or true if you allow anonymous users to join. } // The request comes from a mobile device, e.g. checking user match. if (credentialsData.getCredentialsType().equals(CredentialsData.CREDENTIALS_TYPE_ANDROID)) { return myCheckMobileCredentialsAllowed(credentialsData.getCredentials()); } // Unrecognized credentials type. return false; }
Legen Sie sie dann in Ihrer ReceiverOptionsProvider fest:
class MyReceiverOptionsProvider : ReceiverOptionsProvider { override fun getOptions(context: Context?): CastReceiverOptions { return CastReceiverOptions.Builder(context) ... .setLaunchRequestChecker(MyLaunchRequestChecker()) .build() } }
public class MyReceiverOptionsProvider implements ReceiverOptionsProvider { @Override public CastReceiverOptions getOptions(Context context) { return new CastReceiverOptions.Builder(context) ... .setLaunchRequestChecker(new MyLaunchRequestChecker()) .build(); } }
Wenn du true in der LaunchRequestChecker auflöst, wird die ATV-App gestartet. Wenn du false auflöst, wird deine Web-Receiver-App gestartet.
Benutzerdefinierte Nachrichten senden und empfangen
Mit dem Cast-Protokoll kannst du benutzerdefinierte Stringnachrichten zwischen Sendern und deiner Empfänger-App senden. Du musst einen Namespace (Kanal) registrieren, um Nachrichten zu senden, bevor du deine CastReceiverContext initialisierst.
Android TV – Benutzerdefinierten Namespace angeben
Sie müssen die unterstützten Namespaces während der Einrichtung in der CastReceiverOptions angeben:
class MyReceiverOptionsProvider : ReceiverOptionsProvider { override fun getOptions(context: Context?): CastReceiverOptions { return CastReceiverOptions.Builder(context) .setCustomNamespaces( Arrays.asList("urn:x-cast:com.example.cast.mynamespace") ) .build() } }
public class MyReceiverOptionsProvider implements ReceiverOptionsProvider { @Override public CastReceiverOptions getOptions(Context context) { return new CastReceiverOptions.Builder(context) .setCustomNamespaces( Arrays.asList("urn:x-cast:com.example.cast.mynamespace")) .build(); } }
Android TV – Nachrichten senden
// If senderId is null, then the message is broadcasted to all senders. CastReceiverContext.getInstance().sendMessage( "urn:x-cast:com.example.cast.mynamespace", senderId, customString)
// If senderId is null, then the message is broadcasted to all senders. CastReceiverContext.getInstance().sendMessage( "urn:x-cast:com.example.cast.mynamespace", senderId, customString);
Android TV – Nachrichten mit benutzerdefiniertem Namespace empfangen
class MyCustomMessageListener : MessageReceivedListener { override fun onMessageReceived( namespace: String, senderId: String?, message: String ) { ... } } CastReceiverContext.getInstance().setMessageReceivedListener( "urn:x-cast:com.example.cast.mynamespace", new MyCustomMessageListener());
class MyCustomMessageListener implements CastReceiverContext.MessageReceivedListener { @Override public void onMessageReceived( String namespace, String senderId, String message) { ... } } CastReceiverContext.getInstance().setMessageReceivedListener( "urn:x-cast:com.example.cast.mynamespace", new MyCustomMessageListener());