3

We've recently updated the Google Cast SDK to version 3. With this SDK it should be possible to add support for non-cast devices by implementing getAdditionalSessionProviders() in an OptionsProvider. We have created a custom SessionProvider to return with the getAdditionalSessionProviders() method.

However, this SessionProvider is never used and it will probably only be used when a device has been discovered and selected in the selection list when the MediaRouteButton is pressed as described in the Session documentation. But we can not find a way to add our discovered non-cast device to this list. We have searched the API, the documentation and the examples that are available online, but we couldn't find how to do this. We have only found examples of older sdk versions, but these are completely different and not usable.

We would like to use this functionality to add Samsung tv's to the Google Cast list with Samsung's SmartView SDK just like the YouTube and Netflix app do.

mennovogel
  • 339
  • 1
  • 2
  • 9
  • Have you tried to perform [Cast SDK v3 Android Codelab](https://codelabs.developers.google.com/codelabs/cast-videos-android/#0)? also, check this [document](https://developers.google.com/cast/docs/android_sender_integrate). – Android Enthusiast Aug 29 '16 at 00:39
  • Yes, I've checked that out, but I couldn't find anything about adding a non-cast device there. We have implemented all other parts of the SDK successfully though. – mennovogel Aug 29 '16 at 06:56
  • Check this(https://productforums.google.com/forum/#!topic/chromecast/hoHJMeM5Qns) if it can help you mate. – Android Enthusiast Oct 03 '16 at 04:24
  • @AndroidEnthusiast Two completely non-responsive replies to this thread. Neither source remotely addresses the question asked. You are wasting peoples' time. Don't do that please. – Robin Davies Jun 26 '18 at 14:41

2 Answers2

4

Create a custom Media Route Provider: Here is a fake MediaRouteProvider for a custom media route. This class does nothing but publish a fake custom MediaRoute so that it can be seen and selected by the Cast SDK from MediaRouter.

import android.content.Context;
import android.content.IntentFilter;
import android.media.AudioManager;
import android.os.Bundle;
import android.support.v7.media.MediaRouteDescriptor;
import android.support.v7.media.MediaRouteDiscoveryRequest;
import android.support.v7.media.MediaRouteProvider;
import android.support.v7.media.MediaRouteProvider.RouteController;
import android.support.v7.media.MediaRouteProviderDescriptor;
import android.support.v7.media.MediaRouter;
import java.util.ArrayList;

public final class CustomMediaRouteProvider extends MediaRouteProvider {
  private static final ArrayList<IntentFilter> CONTROL_FILTERS_BASIC;
  private static MediaRouteDescriptor DEFAULT_MEDIA_ROUTE_DESCRIPTOR;

  static {
    // This filter will be used by Cast SDK to match the session category.
    IntentFilter customControls = new IntentFilter();
    customControls.addCategory(CustomSessionProvider.CUSTOM_CATEGORY);

    CONTROL_FILTERS_BASIC = new ArrayList<IntentFilter>();
    CONTROL_FILTERS_BASIC.add(customControls);

    Bundle extras = new Bundle();
    extras.putCharSequence("ROUTE_URL", "http://abcdef.cyz");
    DEFAULT_MEDIA_ROUTE_DESCRIPTOR =
        new MediaRouteDescriptor.Builder("fake-custom-route-id", "fake custom route")
            .setDescription("Foo description")
            .addControlFilters(CONTROL_FILTERS_BASIC)
            .setPlaybackStream(AudioManager.STREAM_MUSIC)
            .setPlaybackType(MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE)
            .setVolumeHandling(MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE)
            .setVolumeMax(100)
            .setVolume(10)
            .setExtras(extras)
            .build();
  }

  public CustomMediaRouteProvider(Context context) {
    super(context);
  }

  @Override
  public void onDiscoveryRequestChanged(MediaRouteDiscoveryRequest request) {
    if (request == null || request.getSelector() == null) {
      return;
    }

    publishRoutes();
  }

  @Override
  public RouteController onCreateRouteController(String routeId) {
    return null;
  }

  private void publishRoutes() {
    MediaRouteProviderDescriptor providerDescriptor =
        new MediaRouteProviderDescriptor.Builder().addRoute(DEFAULT_MEDIA_ROUTE_DESCRIPTOR).build();

    setDescriptor(providerDescriptor);
  }
}

You need a SessionProvider implementation for your custom session:

import android.content.Context;
import com.google.android.gms.cast.framework.Session;
import com.google.android.gms.cast.framework.SessionProvider;

public class CustomSessionProvider extends SessionProvider {
  public static final String CUSTOM_CATEGORY = "CUSTOM";

  public CustomSessionProvider(Context applicationContext) {
    super(applicationContext, CUSTOM_CATEGORY);
  }

  @Override
  public Session createSession(String sessionId) {
    return new CustomSession(getContext(), getCategory(), sessionId);
  }

  @Override
  public boolean isSessionRecoverable() {
    return true;
  }
}

And a Session implementation: Here is a fake Session implementation for custom media route type. It always succeeds on start/resume/end.

import android.content.Context;
import android.os.Bundle;
import com.google.android.gms.cast.framework.Session;

public class CustomSession extends Session {
  private static final String FAKE_SESSION_ID = "custom.session.id.12345";

  CustomSession(Context applicationContext, String category, String sessionId) {
    super(applicationContext, category, sessionId);
  }

  @Override
  protected void start(Bundle routeInfoExtra) {
    notifySessionStarted(FAKE_SESSION_ID);
  }

  @Override
  protected void resume(Bundle routeInfoExtra) {
    notifySessionResumed(false);
  }

  @Override
  protected void end(boolean stopCasting) {
    notifySessionEnded(0);
  }
}

In your Cast V3 sender app, specify the additional session provider:

public class CastOptionsProvider implements OptionsProvider {
@Override
  public List<SessionProvider> getAdditionalSessionProviders(Context appContext) {
    List<SessionProvider> additionalProviders = new ArrayList<>();
    additionalProviders.add(new CustomSessionProvider(appContext));
    return additionalProviders;
  }
}

In your Application add the MediaRouter provider:

public class CastVideosApplication extends Application {
  @Override
  public void onCreate() {
    super.onCreate();
    MediaRouter mediaRouter = MediaRouter.getInstance(this);
    mediaRouter.addProvider(new CustomMediaRouteProvider(this));
  }
}
Leon Nicholls
  • 4,623
  • 2
  • 16
  • 17
  • Great I am still not clear how to use CastSession along with CustomSession(using both at the same time) also how will custom session interact with CustomMediaRouteProvider, do we have to use RemotePlaybackClient – ingsaurabh Oct 22 '17 at 03:32
  • From within your cast-enabled app, all custom routes will be discovered and managed as cast sessions. – Leon Nicholls Oct 23 '17 at 20:05
  • I am testing my custom route in uamp, for now in MusicService CastSessionManagerListener I check for instance of session and create appropriate Playback(CastPlayback/CustomPlayback) and this works as expected, is this right approach or I am going in wrong direction? here is gist https://gist.github.com/ingsaurabh/6b47babd0d1e64cf144b4d5117bf0c9c – ingsaurabh Oct 24 '17 at 07:11
  • Could you explain what FAKE_SESSION_ID is all about? As opposed to say getSessionID() as passed in on the constructor (which is null in my code doesn't work so well). – Robin Davies Jun 26 '18 at 16:26
  • @LeonNicholls Could you expand a bit on what "discovered and managed as cast sessions". As far as I can tell, this seems to mean "cast sessions and your sessions can't run at the same time", while providing absolutely no support or integration into the the UI other than picking a device from the cast button. No support for translating between framework actions and MediaRouter intents (which clearly map, but are not handled); No integration of control elements. Is that accurate? I have device selection working through the cast button. I don't see the path forward from there. – Robin Davies Jun 26 '18 at 21:49
  • Thanks. "Bundle extras = new Bundle(); extras.putCharSequence("ROUTE_URL", "http://abcdef.cyz");" probably deserved a comment. See https://issuetracker.google.com/issues/65440946 for an explanation of why that code is not optional due to bugs in the framework still outstanding 10 months later as of 6/2018. – Robin Davies Jun 26 '18 at 21:51
0

So, I had just the same problem as you, and I found some way to make it work, but as you noted, as nothing is really documented on this topic, I am not sure it is the "right way to do it".

Here is basically what I do:

The Media Route Provider is in charge of managing your device and all related commands / communication. You can test it with a very simple app using the Media Router API. There is a github from googlesample about Media Router : Android BasicMediaRouter Sample.

In you custom Session class, you can access the MediaRouter (you got context passed in the constructor) and retrieve the current Route :

 MediaRouter mediaRouter = MediaRouter.getInstance(context);
  if (mediaRouter != null && mediaRouter.getSelectedRoute() != null)
    Log.d("Cast", "Selected route name is " +  mediaRouter.getSelectedRoute().getName());

With that, you should be able to get your device if it is the currently selected one (you'll be notified in the start() method of Session), and pass commands from the Session object to your Media Route Provider, as done in the Media Route sample.

w00ly
  • 455
  • 1
  • 5
  • 18