Integrating Amazon Fling with an Existing Android App that Uses Google Cast
Using our SDK, you can talk to Amazon Fire TV devices in a similar manner as you use Google Cast in your app to talk to Chromecast devices. This page outlines steps to modify an existing app that uses the Google Cast Companion Library to also fling to Amazon Fire TV.
Before you start, make sure you follow the steps at Setting Up your Amazon Fling Development Environment for Android to include the required libraries in your project.
- Modifying the Android Manifest
- Initializing DiscoveryController
- Converting Your Device Picker to use RemoteMediaPlayer
- Monitoring the Status of the Player with StatusListener
- Communicating With the Remote Media Player
Modifying the Android Manifest
The API included in our SDK requires some permissions to be added to your Android manifest (AndroidManifest.xml
). After the <manifest>
element, add the <uses-permission>
elements. Some of these permissions may already be present as they may also be used by applications that use Google Cast.
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="[YOUR PACKAGE NAME]"
android:versioncode="1"
android:versionname="1.0">
<!-- Android Network Access Permissions -->
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE"/>
...
</manifest>
For Fire OS devices, also add the
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<uses-library android:name="com.amazon.whisperplay.contracts" />
...
</application>
Initializing DiscoveryController
Amazon Fling service provides similar functionality to Google Cast's VideoCastManager
via different classes:
DiscoveryController
, the interface used to discover remote Fire TV devices and players.RemoteMediaPlayer
, the interface that represents a player running on a remote Fire TV device. Use of this interface is described below in Converting Your Device Picker to use RemoteMediaPlayer.
To use DiscoveryController
, initialize it in your onCreate()
method. Replace YourReceiverServiceID
with your Amazon Fire TV player's system ID (SID).
private DiscoveryController mController;
@Override
protected void onCreate(Bundle savedInstanceState) {
...
mController = new DiscoveryController(this);
...
}
@Override
protected void onResume() {
...
mController.start("YourReceiverServiceID", mDiscovery);
...
}
@Override
protected void onPause() {
...
mController.stop();
...
}
Converting Your Device Picker to use RemoteMediaPlayer
The device picker is the part of your app's user interface that allows the user to view and pick available remote players. Your controller app uses the RemoteMediaPlayer interface for device discovery and selection through a custom device picker.
Cast applications that use the MediaRouterButton
class, as in the code below, must be replaced by a custom device picker.
@Override
public boolean onCreateOptionsMenu(Menu menu) {
...
getMenuInflater().inflate(R.menu.main, menu);
mCastManager.addMediaRouterButton(menu, R.id.media_route_menu_item);
...
}
If your app implements a custom device picker like the code below you must modify that code to support RemoteMediaPlayer
.
private MediaRouter mMediaRouter;
private List<CastDevice> mDeviceList = new LinkedList<CastDevice>();
private CastDevice mCurrentDevice;
@Override
protected void onCreate(Bundle savedInstanceState) {
...
mMediaRouter = MediaRouter.getInstance(this);
...
}
@Override
protected void onResume() {
...
mMediaRouter = MediaRouter.getInstance(this);
mMediaRouter.addCallback(mCastManager.getMediaRouteSelector(), mCastCallback,
MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY);
...
}
@Override
protected void onPause() {
...
mMediaRouter.removeCallback(mCastCallback);
}
private MediaRouter.Callback mCastCallback = new MediaRouter.Callback() {
public void onRouteAdded(android.support.v7.media.MediaRouter router,
android.support.v7.media.MediaRouter.RouteInfo route) {
CastDevice device = CastDevice.getFromBundle(route.getExtras());
if (mDeviceList.contains(device)) {
mDeviceList.remove(device);
...
} else {
...
}
mDeviceList.add(device);
triggerUpdate();
}
public void onRouteRemoved(android.support.v7.media.MediaRouter router,
android.support.v7.media.MediaRouter.RouteInfo route) {
CastDevice device = CastDevice.getFromBundle(route.getExtras());
if( mDeviceList.contains(device) ) {
...
mDeviceList.remove(device);
triggerUpdate();
}
}
};
This example uses a list of of RemoteMediaPlayer
objects to keep track of the currently selected player device in a list view. The IDiscoveryListener
interface is used to listen for when a new player device becomes available or an existing player device is no longer available. The listener is added when starting your controller app in the onResume()
method.
private List<RemoteMediaPlayer> mDeviceList = new LinkedList<RemoteMediaPlayer>();
private RemoteMediaPlayer mCurrentDevice;
private DiscoveryController.IDiscoveryListener mDiscovery = new DiscoveryController.IDiscoveryListener() {
@Override
public void playerDiscovered(RemoteMediaPlayer device) {
if (mDeviceList.contains(device)) {
mDeviceList.remove(device);
...
} else {
...
}
mDeviceList.add(device);
triggerUpdate();
}
@Override
public void playerLost(RemoteMediaPlayer device) {
if(mDeviceList.contains(device)) {
...
mDeviceList.remove(device);
triggerUpdate();
...
}
}
@Override
public void discoveryFailure() {
...
}
}
The device picker list is updated in the following method that handles device objects from both Google Cast and this SDK.
void triggerUpdate() {
runOnUiThread(new Runnable() {
@Override
public void run() {
final ListView lv = (ListView) findViewById(R.id.listView1);
ArrayAdapter<Object> ad = (ArrayAdapter<Object>) lv.getAdapter();
ad.notifyDataSetChanged();
}
});
}
Monitoring the Status of the Player with StatusListener
Google Cast provides callbacks to manage application status changes. The Google Cast Companion Library also keeps track of some statuses such as the current media position through the VideoCastConsumerImpl class
. Our API provides the StatusListener
class to receive status updates based on a given interval. The StatusListener
class provides the same functionality as VideoCastConsumerImpl
. Add the status listener to your controller app to receive status updates, and set the interval for updates when your status listener is successfully added.
The addStatusListener()
and setPositionUpdateInterval()
methods, as with all remote methods in the API, are asynchronous calls. The API gives the result from these asynchronous calls by providing FutureListener<T>
. Chain the getAsync()
method with remote methods to get the result. Once the asynchronous task is done, the remote method call invokes the futureIsNow()
method from the receiver app.
private StatusListener mListener;
private Status mStatus = new Status();
private static final long MONITOR_INTERVAL = 1000L;
private DiscoveryController.IDiscoveryListener mDiscovery = new DiscoveryController.IDiscoveryListener() {
@Override
public void playerLost(RemoteMediaPlayer device) {
...
device.removeStatusListener(mListener).getAsync(new FutureListener<Void> {
@Override
public void futureIsNow(Future<Void> voidFuture) {
//handle asynchronous task
}
});
}
};
@Override
protected void onResume() {
...
mListener = new Monitor();
...
}
private static class Status {
public long mPosition;
public MediaState mState;
public MediaCondition mCond;
public synchronized void clear() {
mPosition = -1L;
mState = MediaState.NoSource;
}
}
private class Monitor implements StatusListener {
@SuppressLint("NewApi")
@Override
public void onStatusChange(AmazonMediaStatus status, long position) {
if (mCurrentDevice != null) {
synchronized (mStatus) {
mStatus.mState = status.getState();
mStatus.mCond = status.getCondition();
mStatus.mPosition = position;
...
}
...
}
}
}
...
private void fling(final Object target, final String name, final String title) {
mCurrentDevice = target;
mCurrentDevice.addStatusListener(mListener).getAsync(new ErrorResultHandler("Cannot set status listener"));
mCurrentDevice.setPositionUpdateInterval(MONITOR_INTERVAL).getAsync(
new ErrorResultHandler("Error attempting set update interval, ignoring"));
mCurrentDevice.setMediaSource(name, title, true, false).getAsync(new ErrorResultHandler("Error attempting to Play"));
}
private class ErrorResultHandler implements FutureListener<Void> {
ErrorResultHandler(String msg) {
this(msg, false);
}
@Override
public void futureIsNow(Future<Void> result) {
try {
result.get();
} catch(ExecutionException e) {
//handleFailure
} catch(Exception e) {
//handleFailure
}
}
}
Communicating With the Remote Media Player
The Google Cast Companion Library provides only the callbacks in VideoCastConsumerImpl
to indicate when a remote call is finished. Some actions your application performs do not provide a direct callback once the remote player has performed the specified action:
mCastManager.loadMedia(selectedMedia, true, 0);
mCastManager.play();
mCastManager.pause();
mCastManager.stop();
mCastManager.seekAndPlay((int)position);
Our SDK provides the option to use listeners when communicating with the remote device, as in the previous section. This allows you to monitor the result of the call and to handle failure and success situations differently, or with a common failure handler like the one in this example:
private void handleFailure( Throwable throwable, final String msg, final boolean extend ) {
Log.e(TAG, msg, throwable);
final String exceptionMessage = throwable.getMessage();
FlingActivity.this.runOnUiThread(new Runnable() {
public void run() {
Toast.makeText(FlingActivity.this, msg+(extend?exceptionMessage:""), Toast.LENGTH_LONG).show();
}
});
}
private class ErrorResultHandler implements FutureListener<Void> {
private String mMsg;
private boolean mExtend;
ErrorResultHandler(String msg) {
this(msg, false);
}
ErrorResultHandler(String msg, Boolean extend) {
mMsg = msg;
mExtend = extend;
}
@Override
public void futureIsNow(Future<Void> result) {
try {
result.get();
} catch(ExecutionException e) {
handleFailure(e.getCause(), mMsg, mExtend);
} catch(Exception e) {
handleFailure(e, mMsg, mExtend);
}
}
}
...
mCurrentDevice.stop(new ErrorResultHandler("Error Stopping"));
...
Or maybe not even handle the asynchronous result at all.
mCurrentDevice.setPositionUpdateInterval(MONITOR_INTERVAL, null);
mCurrentDevice.setMediaSource(name, title, true, false, null);
mCurrentDevice.play(null);
mCurrentDevice.pause(null);
mCurrentDevice.stop(null);
mCurrentDevice.seek(null);
Last updated: Jan 01, 2017