MediaRouter / Multiple Displays

Multiple Displays – Part 5

In the previous article in this series we enabled the handling of connection and disconnection of external displays within our app, and also changed the theme which was applied to the Presentation that was displayed on the external display. In this concluding article in this series we’ll look at an alternate mechanism for detecting display connectivity changes, and determining the available presentation displays.

The phrase “there is another way we can do this”, or variations thereof, is an often used phrase on Styling Android because there are usually different ways of achieving the same result. When we are dealing with multiple displays the same applies. In all of the code up to now we have used the DisplayManager class, which was introduced in Android 4.2 (SDK 17), to detect displays, and to register our listener with to handle the connection and disconnection of external displays. The focus has been on this becuase it is specific to displays, but there is an alternate mechanism that we can use: MediaRouter.

Like DisplayManager, MediaRouter was introduced in Android 4.2 and, while it enables us to achieve the same results that we have already with DisplayManager, it is not specific to displays. Out of the box MediaRouter enables us to to control audio as well as video. For the purposes of this discussion we’ll focus on video.

MediaRouter is, like DisplayManager, a system service and we get an instance of it by calling:

[java] MediaRouter mr = (MediaRouter)
getSystemService(MEDIA_ROUTER_SERVICE);
[/java]

Similarly to DisplayManager we can register a listener which will be called when a display is connected or disconnected. However, we can limit this to only video devices:

[java] mr.addCallback(MediaRouter.ROUTE_TYPE_LIVE_VIDEO, mCallback);
[/java]

The listener itself must implement MediaRouter.Callback which is quite a rich interface encompassing audio events as well as video ones. Fortunately a simple, extendable implementation of this is provided in MediaRouter.SimpleCallback where we only need to implement the specific methods for events that we’re interested in:

[java] @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
private class MyCallback extends SimpleCallback
{
@Override
public void onRoutePresentationDisplayChanged(
MediaRouter router,
RouteInfo info)
{
if (info != null && info.isEnabled() &&
mPresentation == null)
{
mPresentation = new MyPresentation(
MainActivity.this,
info.getPresentationDisplay(),
android.R.style.Theme_Holo_Light_NoActionBar);
mPresentation.show();
}
else
{
mPresentation = null;
}
}
}
[/java]

The onRoutePresentationDisplayChanged method will be called whenever a display is connected or disconnected, so we just need to determine the state from the supplied RouteInfo object to determine whether we should add or remove the Presentation from the display.

So, if we re-write our multiInit and multiDestroy methods to use MediaRouter and MyCallback:

[java] public class MainActivity extends Activity
{
.
.
.
private MyCallback mCallback = null;
.
.
.
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
private void multiInit()
{
MediaRouter mr =
(MediaRouter) getSystemService(MEDIA_ROUTER_SERVICE);
if (mr != null)
{
mCallback = new MyCallback();
mr.addCallback(MediaRouter.ROUTE_TYPE_LIVE_VIDEO,
mCallback);
}
RouteInfo info =
mr.getSelectedRoute(MediaRouter.ROUTE_TYPE_LIVE_VIDEO);
if (info != null && info.isEnabled() &&
info.getPresentationDisplay() != null)
{
mPresentation = new MyPresentation(this,
info.getPresentationDisplay(),
android.R.style.Theme_Holo_Light_NoActionBar);
mPresentation.show();
}
}

@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
private void multiDestroy()
{
if (mCallback != null)
{
MediaRouter mr =
(MediaRouter) getSystemService(MEDIA_ROUTER_SERVICE);
mr.removeCallback(mCallback);
mCallback = null;
}
}
.
.
.
}
[/java]

We have now completely replaced all references to DisplayManager with MediaRouter, and the behaviour is exactly as before:

That concludes our look at this interesting new feature within Android 4.2. While it will not be applicable for many apps, it is certainly something worth considering adding to any app which could offer an alternate user experience when connected to an external device, such as media player apps, presentation graphics app, or even a game which should render to the external display when once is connected, and the phone or tablet functions simply as a controller.

The source code for this article can be found here.

© 2013, Mark Allison. All rights reserved.

Copyright © 2013 Styling Android. All Rights Reserved.
Information about how to reuse or republish this work may be available at http://blog.stylingandroid.com/license-information.

6 Comments

  1. Thanks for your article. I’ve built a simple interface to secondary display using the MediaRouter. Now, I have a situation where I want the Presentation object created in Activity X to continue running on the secondary display, even if Activity X invokes another Activity Y. In this situation, Activity X is still on the stack, but Activity Y is now on top of it.

    The problem is that when Activity Y starts, the physical secondary display reverts to mirroring. When I exit Activity Y, the content of Activity X’s Presentation comes back to the secondary display (which is fine, since I never called dismiss() on it).

    So the question is: How can I keep a Presentation attached to a secondary display running on that display, even if a subordinate Activity is running on the local device?

    1. As soon as you kick off a new Activity it will splat what’s running on the secondary because they’re both attached to the same Activity.

      There are two wars you could go:

      1. Have your second activity also drive the secondary display, and persist the state somehow. This is likely to result in some flicker as the secondary display gets updated from one Activity to the next.

      2. Use Fragments. Implement your initial Activity as a blind Fragment container. You can then change the fragment that you display in there independently of your secondary display. This is probably the better solution, but it will depend on your requirements.

      I hope that helps.

      1. Very good, thanks Mark. I also thought about solution #1, but I have more than a few activities I would need to set this up for – so I think I would up with a lot of duplicate code. Maybe it would help to put my Presentation subclass in a separate file (rather than as an inner class of Activity X)?

        I also like solution #2, but in my case Activity X is already a SherlockFragmentActivity, managing a set of Fragment Children that correspond to tabs (via a ViewPager and FragmentStatePagerAdapter). So I’m not sure if there’s an easy way to add more Fragments to it without disrupting how all that works. Please let me know if you have any other suggestions – and thanks again!

        1. It is possible to get Fragments within Fragments working provided you keep your instantiation model consistent. i.e. if you instantiate one Fragment via LayoutInflation, you must do them all that way; or create them all programatically. If you try and mix-and-match programatic instantiation with LayoutInflation instantiation you’ll run in to all kinds of trouble.

          I’d advise doing some prototyping first to ensure that the Fragment hierarchy that you use will work before you invest lots of time in to a full implementation.

          1. Thanks, Mark – I tried Fragments within Fragments for a different purpose a few weeks ago, and ran into some problems. I’ll take a fresh look, and revert to solution #1 if it proves too onerous. Thanks again for good suggestions.

  2. Very nice article. I am facing one issue, When I am killing app and launching again but video is not showing on External display(TV). First time it is showing but after killing and launching again video is not showing. Please help me. Thanks in advance.

Leave a Reply to Deepak Cancel reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.