AI-generated Key Takeaways
-
This guide provides steps to convert an Android sender app from Cast SDK v2 with CCL to CAF, which replaces all CCL functionality.
-
CAF depends on the newer Cast framework found in Google Play services version 9.2.0 or later and has a minimum Android SDK version of 9 (Gingerbread).
-
CAF uses the CastContext singleton for initialization and managing the Cast API, unlike CCL which used
VideoCastManager.initialize()and allowed direct access via GoogleAPIClient. -
Session management in CAF is handled by the SessionManager, which automatically starts and stops sessions, replacing the need for manual session status tracking with
VideoCastConsumerImplin CCL. -
CAF provides components like
MiniControllerFragmentandMediaNotificationServicefor media control and notifications, offering similar features to CCL's widgets.
The following procedure enables you to convert your Android sender app from Cast SDK v2 with CCL to CAF. All of the functionality of CCL has been implemented in CAF, so once you migrate, you will no longer need to use CCL.
The Cast CAF Sender SDK uses CastContext to manage the GoogleAPIClient on your behalf. CastContext manages lifecycles, errors and callbacks for you, which greatly simplifies developing a Cast app.
Introduction
- Because the CAF Sender design was influenced by Cast Companion Library, the migration from CCL to CAF Sender involves mostly one-to-one mappings of classes and their methods.
- CAF Sender is still distributed as part of Google Play services using the Android SDK manager.
- New packages (
com.google.android.gms.cast.framework.*) that have been added to CAF Sender, with functionality similar to CCL, take on responsibility for complying with the Google Cast Design checklist. - CAF Sender provides widgets that comply with the Cast UX requirements; these widgets are similar to those provided by CCL.
- CAF Sender provides asynchronous callbacks that are similar to CCL, to track states and obtain data. Unlike CCL, CAF Sender does not provide any no-op implementations of the various interface methods.
In the following sections, we will mainly focus on the videocentric applications based on CCL's VideoCastManager, but in many cases, the same concepts apply to DataCastManager as well.
Dependencies
CCL and CAF have the same dependencies on the AppCompat support library, MediaRouter v7 support library and Google Play services. However, the difference is that CAF depends on the new Cast framework that is available in Google Play services 9.2.0 or later.
In your build.gradle file, remove the dependencies on
com.google.android.gms:play-services-cast and
com.google.android.libraries.cast.companionlibrary:ccl,
then add the new Cast framework:
dependencies {
compile 'com.android.support:appcompat-v7:23.4.0'
compile 'com.android.support:mediarouter-v7:23.4.0'
compile 'com.google.android.gms:play-services-cast-framework:9.4.0'
}
You can also remove the Google Play service metadata:
<meta‐data android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version"/>
Any services, activities, and resources that are part of CAF are automatically merged with your app's manifest and resources.
The minimum Android SDK version that CAF supports is 9 (Gingerbread); CCL's minimum Android SDK version is 10.
CCL provides a convenience method,
BaseCastManager.checkGooglePlayServices(activity), to verify that a compatible
version of the Google Play services is available on the device. CAF does not
provide this as part of the Cast SDK. Follow the procedure
Ensure Devices Have the Google Play services APK
to ensure that the correct Google Play services APK is installed on a user's
device since updates might not reach all users immediately.
You are still required to use a variant of Theme.AppCompat for the application's theme.
Initialization
For CCL, VideoCastManager.initialize() was required to be called in the
onCreate() method of the Applications instance. This logic should be
removed from your Application class code.
In CAF, an explicit initialization step is also required for the Cast
framework. This involves initializing the CastContext singleton, using an
appropriate OptionsProvider to specify the receiver application ID and any
other global options. The CastContext plays a similar role to CCL's
VideoCastManager by providing a singleton that clients interact with.
The OptionsProvider is similar to CCL's CastConfiguration to allow you
to configure the Cast framework features.
If your current CCL CastConfiguration.Builder looks like this:
VideoCastManager.initialize(
getApplicationContext(),
new CastConfiguration.Builder(context.getString(R.string.app_id))
.enableWifiReconnection()
.enableAutoReconnect()
.build());
Then in CAF the following CastOptionsProvider using the CastOptions.Builder
would be similar:
public class CastOptionsProvider implements OptionsProvider {
@Override
public CastOptions getCastOptions(Context context) {
return new CastOptions.Builder()
.setReceiverApplicationId(context.getString(R.string.app_id))
.build();
}
@Override
public List<SessionProvider> getAdditionalSessionProviders(
Context context) {
return null;
}
}
Take a look at our sample app for a complete implementation of the OptionsProvider.
Declare the OptionsProvider within the "application" element of the AndroidManifest.xml file:
<application>
...
<meta-data
android:name=
"com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
android:value="com.google.sample.cast.refplayer.CastOptionsProvider"
/>
</application>
Lazily initialize the CastContext in each Activity's onCreate method
(and not the Application instance):
private CastContext mCastContext;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.video_browser);
setupActionBar();
mCastContext = CastContext.getSharedInstance(this);
}
To access the CastContext singleton use:
mCastContext = CastContext.getSharedInstance(this);
Device discovery
CCL's VideoCastManager incrementUiCounter and decrementUiCounter should
be removed from the onResume and onPause methods of your Activities.
In CAF, the discovery process is started and stopped automatically by the framework when the app comes to the foreground and goes to the background, respectively.
Cast button and Cast dialog
As with CCL, these components are provided by the MediaRouter v7 support library.
The Cast button is still implemented by the MediaRouteButton and can be added
to your activity (using either an ActionBar or a Toolbar), as a menu item
in your menu.
The declaration of the MediaRouteActionProvider in the menu xml is the same as
with CCL:
<item
android:id="@+id/media_route_menu_item"
android:title="@string/media_route_menu_title"
app:actionProviderClass="android.support.v7.app.MediaRouteActionProvider"
app:showAsAction="always"/>
Similar to CCL, override the onCreateOptionMenu() method of each Activity, but instead of using CastManager.addMediaRouterButton, use CAF's CastButtonFactory to wire up the MediaRouteButton to the Cast framework:
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
getMenuInflater().inflate(R.menu.browse, menu);
CastButtonFactory.setUpMediaRouteButton(getApplicationContext(),
menu,
R.id.media_route_menu_item);
return true;
}
Device control
Similar to CCL, in CAF, device control is largely handled by the framework.
The sender application does not need to handle (and should not try to handle)
connecting to the device and launching the receiver application using
GoogleApiClient.
Interaction between sender and receiver is now represented as a "session". The
SessionManager class handles the session lifecycle and automatically starts
and stops sessions in response to user gestures: a session is started when the
user selects a Cast device in the Cast dialog and is ended when the user taps
the "Stop Casting" button in the Cast dialog or when the sender app itself
terminates.
In CCL you have to extend the VideoCastConsumerImpl class to track the cast
session status:
private final VideoCastConsumer mCastConsumer = new VideoCastConsumerImpl() {
public void onApplicationConnected(ApplicationMetadata appMetadata,
String sessionId,
boolean wasLaunched) {}
public void onDisconnectionReason(int reason) {}
public void onDisconnected() {}
}
In CAF, the sender application can be notified of session lifecycle events by
registering a SessionManagerListener with the SessionManager. The
SessionManagerListener callbacks define callback methods for all session
lifecycle events.
The following SessionManagerListener methods are mapped from CCL's
VideoCastConsumer interface:
VideoCastConsumer.onApplicationConnected->SessionManagerListener.onSessionStartedVideoCastConsumer.onDisconnected->SessionManagerListener.onSessionEnded
Declare a class that implements the SessionManagerListener interface and move
the VideoCastConsumerImpl logic to the matching methods:
private class CastSessionManagerListener implements SessionManagerListener<CastSession> {
public void onSessionEnded(CastSession session, int error) {}
public void onSessionStarted(CastSession session, String sessionId) {}
public void onSessionEnding(CastSession session) {}
...
}
The CastSession class represents a session with a Cast device. The class has
methods for controlling the device volume and mute states, which CCL does in the
BaseCastManager.
Instead of using the CCL VideoCastManager to add a consumer:
VideoCastManager.getInstance().addVideoCastConsumer(mCastConsumer);
Now register your SessionManagerListener:
mCastSessionManager =
CastContext.getSharedInstance(this).getSessionManager();
mCastSessionManagerListener = new CastSessionManagerListener();
mCastSessionManager.addSessionManagerListener(mCastSessionManagerListener,
CastSession.class);
To stop listening to events in CCL:
VideoCastManager.getInstance().removeVideoCastConsumer(mCastConsumer);
Now use the SessionManager to stop listening to session events:
mCastSessionManager.removeSessionManagerListener(mCastSessionManagerListener,
CastSession.class);
To explicitly disconnect from the Cast device, CCL used:
VideoCastManager.disconnectDevice(boolean stopAppOnExit,
boolean clearPersistedConnectionData,
boolean setDefaultRoute)
For CAF, use the SessionManager:
CastContext.getSharedInstance(this).getSessionManager()
.endCurrentSession(true);
To determine if the sender is connected to the receiver, CCL provides
VideoCastManager.getInstance().isConnected(), but in CAF use the
SessionManager:
public boolean isConnected() {
CastSession castSession = CastContext.getSharedInstance(mAppContext)
.getSessionManager()
.getCurrentCastSession();
return (castSession != null && castSession.isConnected());
}
In CAF, volume/mute state change notifications are still delivered via callback
methods in the Cast.Listener; these listeners are registered with
CastSession. All of the remaining device state notifications are delivered via
CastStateListener callbacks; these listeners are registered with the
CastSession. Make sure you still unregister listeners when the associated
fragments, activities or apps go to the background.
Reconnection logic
CAF attempts to re-establish network connections that are lost due to temporary WiFi signal loss or other network errors. This is now done at the session level; a session can enter a "suspended" state when the connection is lost, and will transition back to a "connected" state when connectivity is restored. The framework takes care of reconnecting to the receiver application and reconnecting any Cast channels as part of this process.
CAF provides its own reconnection service, so you can remove
the CCL ReconnectionService from your manifest:
<service android:name="com.google.android.libraries.cast.companionlibrary.cast.reconnection.ReconnectionService"/>
You also don't need the following permissions in your manifest for the reconnection logic:
<uses‐permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses‐permission android:name="android.permission.ACCESS_WIFI_STATE"/>
The CAF reconnection service is enabled by default but can be disabled using the
CastOptions.
In addition, CAF also adds automatic session resumption which is enabled by
default (and can be deactivated via CastOptions). If the sender application is
sent to the background or is terminated (by swiping away or because of a crash)
while a Cast session is in progress, the framework will attempt to resume that
session when the sender application returns to the foreground or is relaunched;
this is handled automatically by the SessionManager, which will issue the
appropriate callbacks on any registered SessionManagerListener instances.
Custom channel registration
CCL provides two ways to create a custom message channel to the receiver:
CastConfigurationallows you to specify multiple namespaces and CCL will then create the channel for you.DataCastManageris similar to VideoCastManager but focused on non-media use cases.
Neither of these ways of creating a custom channel is supported by CAF -- you have to instead follow the procedure Add a Custom Channel for your sender app.
Similar to CCL, for media applications, it is not necessary to explicitly register the media control channel.
Media control
In CAF, the RemoteMediaClient class is equivalent to the VideoCastManager
media methods. The RemoteMediaClient.Listener is equivalent to
VideoCastConsumer methods. In particular, the
onRemoteMediaPlayerMetadataUpdated and onRemoteMediaPlayerStatusUpdated
methods of VideoCastConsumer maps to the onMetadataUpdated and
onStatusUpdated methods of RemoteMediaClient.Listener respectively:
private class CastMediaClientListener implements RemoteMediaClient.Listener {
@Override
public void onMetadataUpdated() {
setMetadataFromRemote();
}
@Override
public void onStatusUpdated() {
updatePlaybackState();
}
@Override
public void onSendingRemoteMediaRequest() {
}
@Override
public void onQueueStatusUpdated() {
}
@Override
public void onPreloadStatusUpdated() {
}
}
It is not necessary to explicitly initialize or register the RemoteMediaClient
object; the framework will automatically instantiate the object and register the
underlying media channel at session start time if the receiver application being
connected to supports the media namespace.
The RemoteMediaClient can be accessed as the getRemoteMediaClient method of
the CastSession object.
CastSession castSession = CastContext.getSharedInstance(mAppContext)
.getSessionManager()
.getCurrentCastSession();
mRemoteMediaClient = castSession.getRemoteMediaClient();
mRemoteMediaClientListener = new CastMediaClientListener();
Instead of CCL's:
VideoCastManager.getInstance().addVideoCastConsumer(mCastConsumer);
Now use CAF:
mRemoteMediaClient.addListener(mRemoteMediaClientListener);
Any number of listeners can be registered with the RemoteMediaClient,
which allows multiple sender components to share the single instance of
RemoteMediaClient that is associated with the session.
CCL's VideoCastManager provides methods to handle media playback:
VideoCastManager manager = VideoCastManager.getInstance();
if (manager.isRemoteMediaLoaded()) {
manager.pause();
mCurrentPosition = (int) manager.getCurrentMediaPosition();
}
These are now implemented by RemoteMediaClient in CAF:
if (mRemoteMediaClient.hasMediaSession()) {
mRemoteMediaClient.pause();
mCurrentPosition =
(int)mRemoteMediaClient.getApproximateStreamPosition();
}
In CAF, all media requests issued on the RemoteMediaClient return a
RemoteMediaClient.MediaChannelResult via a PendingResult callback
which can be used to track the progress and eventual outcome of the request.
Both CCL and CAF use the MediaInfo and MediaMetadata classes to represent
media items and to load media.
To load media in CCL, the VideoCastManager is used:
VideoCastManager.getInstance().loadMedia(media, autoPlay, mCurrentPosition, customData);
In CAF, the RemoteMediaClient is used to load the media:
mRemoteMediaClient.load(media, autoPlay, mCurrentPosition, customData);
To get the Media information and status of a current media session on the
receiver, CCL uses the VideoCastManager:
MediaInfo mediaInfo = VideoCastManager.getInstance()
.getRemoteMediaInformation();
int status = VideoCastManager.getInstance().getPlaybackStatus();
int idleReason = VideoCastManager.getInstance().getIdleReason();
In CAF, use the RemoteMediaClient to get the same information:
MediaInfo mediaInfo = mRemoteMediaClient.getMediaInfo();
int status = mRemoteMediaClient.getPlayerState();
int idleReason = mRemoteMediaClient.getIdleReason();
Introductory overlay
Similar to CCL, CAF provides a custom view IntroductoryOverlay to highlight
the Cast button when it is first shown to users.
Instead of using CCL's VideoCastConsumer onCastAvailabilityChanged method
to know when to display the overlay, declare a CastStateListener to determine
when the Cast button becomes visible once Cast devices are discovered on the
local network by the MediaRouter:
private IntroductoryOverlay mIntroductoryOverlay;
private MenuItem mMediaRouteMenuItem;
protected void onCreate(Bundle savedInstanceState) {
...
mCastStateListener = new CastStateListener() {
@Override
public void onCastStateChanged(int newState) {
if (newState != CastState.NO_DEVICES_AVAILABLE) {
showIntroductoryOverlay();
}
}
};
mCastContext = CastContext.getSharedInstance(this);
mCastContext.registerLifecycleCallbacksBeforeIceCreamSandwich(this,
savedInstanceState);
}
protected void onResume() {
mCastContext.addCastStateListener(mCastStateListener);
...
}
protected void onPause() {
mCastContext.removeCastStateListener(mCastStateListener);
...
}
Keep track of the MediaRouteMenuItem instance:
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
getMenuInflater().inflate(R.menu.browse, menu);
mMediaRouteMenuItem = CastButtonFactory.setUpMediaRouteButton(
getApplicationContext(), menu,
R.id.media_route_menu_item);
showIntroductoryOverlay();
return true;
}
Check if the MediaRouteButton is visible so the introductory overlay
can be shown:
private void showIntroductoryOverlay() {
if (mIntroductoryOverlay != null) {
mIntroductoryOverlay.remove();
}
if ((mMediaRouteMenuItem != null) && mMediaRouteMenuItem.isVisible()) {
new Handler().post(new Runnable() {
@Override
public void run() {
mIntroductoryOverlay = new IntroductoryOverlay.Builder(
VideoBrowserActivity.this, mMediaRouteMenuItem)
.setTitleText(getString(R.string.introducing_cast))
.setOverlayColor(R.color.primary)
.setSingleTime()
.setOnOverlayDismissedListener(
new IntroductoryOverlay
.OnOverlayDismissedListener() {
@Override
public void onOverlayDismissed() {
mIntroductoryOverlay = null;
}
})
.build();
mIntroductoryOverlay.show();
}
});
}
}
Take a look at our sample app for the complete working code for showing the introductory overlay.
To customize the styling of the introductory overlay, follow the procedure Customize Introductory Overlay.
Mini controller
Instead of CCL's MiniController, use CAF's MiniControllerFragment in your
app layout file of the activities in which you want to show the mini
controller:
<fragment
android:id="@+id/cast_mini_controller"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
app:castShowImageThumbnail="true"
android:visibility="gone"
class="com.google.android.gms.cast.framework.media.widget.MiniControllerFragment" />
CAF does not support the manual configuration supported by CCL's MiniController
and also does not support the Autoplay feature.
To customize the styling and buttons of the mini controller, follow the procedure Customize Mini Controller.
Notification and lock screen
Similar to CCL's VideoCastNotificationService, CAF provides a
MediaNotificationService to manage the display of media notifications
when casting.
You need to remove the following from your manifest:
VideoIntentReceiverVideoCastNotificationService
CCL supports providing a custom notification service with the
CastConfiguration.Builder; that is not supported by CAF.
Consider the following CastManager initialization using CCL:
VideoCastManager.initialize(
getApplicationContext(),
new CastConfiguration.Builder(
context.getString(R.string.app_id))
.addNotificationAction(
CastConfiguration.NOTIFICATION_ACTION_PLAY_PAUSE,true)
.addNotificationAction(
CastConfiguration.NOTIFICATION_ACTION_DISCONNECT,true)
.build());
For the equivalent configuration in CAF, the SDK provides a
NotificationsOptions.Builder to help you build media controls for the
notification and lock screen into the sender app. The notification and lock
screen controls can be enabled with the CastOptions when initializing the
CastContext.
public CastOptions getCastOptions(Context context) {
NotificationOptions notificationOptions =
new NotificationOptions.Builder()
.setActions(Arrays.asList(
MediaIntentReceiver.ACTION_TOGGLE_PLAYBACK,
MediaIntentReceiver.ACTION_STOP_CASTING), new int[]{0, 1})
.build();
CastMediaOptions mediaOptions = new CastMediaOptions.Builder()
.setNotificationOptions(notificationOptions)
.build();
return new CastOptions.Builder()
.setReceiverApplicationId(context.getString(R.string.app_id))
.setCastMediaOptions(mediaOptions)
.build();
}
Notifications and lock screen controls are always enabled in CAF. Also, note that
the play/pause and stop casting buttons are provided by default. CAF
will automatically track the visibility of Activities for deciding
when to display the media notification, except for Gingerbread.
(For Gingerbread, see the earlier note on
using registerLifecycleCallbacksBeforeIceCreamSandwich(); CCL's
VideoCastManager incrementUiCounter and decrementUiCounter calls
should be removed.)
To customize the buttons that are displayed in notifications, follow the procedure Add Media Controls to Notification and Lock Screen.
Expanded controller
CCL provides the VideoCastControllerActivity and VideoCastControllerFragment
to display an expanded controller when casting media.
You can remove the VideoCastControllerActivity declaration in the manifest.
In CAF, you have to extend the ExpandedControllerActivity and add the Cast button.
To customize the styles and buttons that are displayed in the expanded controller, follow the procedure Customize Expanded Controller.
Audio focus
As with CCL, audio focus is managed automatically.
Volume control
For Gingerbread, dispatchKeyEvent is required as with CCL. In ICS and above
for both CCL and CAF volume control is handled automatically.
CAF enables controlling the cast volume through the hard volume button on the phone inside your apps activities and also shows a visual volume bar when casting on supported versions. CAF also handles change of volume through the hard volume even if your app is not in front, is locked or even if the screen is off.
Closed captions
In Android KitKat and above, captions can be customized through Captions Settings, found under Settings > Accessibility. Earlier versions of Android, however, do not have this capability. CCL handles this by providing custom settings for earlier versions and delegating to the system settings on KitKat and above.
CAF does not provide custom settings to change the caption preferences. You
should remove the CaptionsPreferenceActivity references in your manifest
and your preferences XML.
CCL's TracksChooserDialog is no longer needed since changing the closed
captions tracks is handled by the expanded controller UI.
The closed captioning API in CAF is similar to v2.
Debug logging
CAF does not provide debug logging settings.
Misc
The following CCL features are not supported in CAF:
- Obtaining authorization prior to playback by providing a
MediaAuthService - Configurable UI messages
Sample apps
Take a look at the diff for migrating our Universal Music Player for Android (uamp) sample app from CCL to CAF.
We also have codelab tutorials and sample apps that use CAF.