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.onSessionStarted
VideoCastConsumer.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:
CastConfiguration
allows you to specify multiple namespaces and CCL will then create the channel for you.DataCastManager
is 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:
VideoIntentReceiver
VideoCastNotificationService
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.