| // Copyright 2018 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| package org.chromium.chrome.browser.media.router.caf; |
| |
| import android.os.Handler; |
| import android.support.annotation.NonNull; |
| import android.support.annotation.Nullable; |
| import android.support.v7.media.MediaRouteSelector; |
| import android.support.v7.media.MediaRouter; |
| import android.support.v7.media.MediaRouter.RouteInfo; |
| |
| import org.chromium.base.Log; |
| import org.chromium.chrome.browser.media.router.ChromeMediaRouter; |
| import org.chromium.chrome.browser.media.router.DiscoveryCallback; |
| import org.chromium.chrome.browser.media.router.DiscoveryDelegate; |
| import org.chromium.chrome.browser.media.router.MediaController; |
| import org.chromium.chrome.browser.media.router.MediaRouteManager; |
| import org.chromium.chrome.browser.media.router.MediaRouteProvider; |
| import org.chromium.chrome.browser.media.router.MediaSink; |
| import org.chromium.chrome.browser.media.router.MediaSource; |
| import org.chromium.chrome.browser.media.router.cast.CastMediaSource; |
| |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| /** |
| * A {@link MediaRouteProvider} implementation for Cast devices and applications, using Cast v3 API. |
| */ |
| public class CafMediaRouteProvider implements MediaRouteProvider, DiscoveryDelegate { |
| private static final String TAG = "cr_CafMRP"; |
| |
| protected static final List<MediaSink> NO_SINKS = Collections.emptyList(); |
| |
| protected final MediaRouter mAndroidMediaRouter; |
| protected final MediaRouteManager mManager; |
| protected final Map<String, DiscoveryCallback> mDiscoveryCallbacks = |
| new HashMap<String, DiscoveryCallback>(); |
| protected Handler mHandler = new Handler(); |
| |
| private CafMediaRouteProvider(MediaRouter androidMediaRouter, MediaRouteManager manager) { |
| mAndroidMediaRouter = androidMediaRouter; |
| mManager = manager; |
| } |
| |
| public static CafMediaRouteProvider create(MediaRouteManager manager) { |
| return new CafMediaRouteProvider(ChromeMediaRouter.getAndroidMediaRouter(), manager); |
| } |
| |
| @Override |
| public boolean supportsSource(String sourceId) { |
| return getSourceFromId(sourceId) != null; |
| } |
| |
| @Override |
| public void startObservingMediaSinks(String sourceId) { |
| Log.d(TAG, "startObservingMediaSinks: " + sourceId); |
| |
| if (mAndroidMediaRouter == null) { |
| // If the MediaRouter API is not available, report no devices so the page doesn't even |
| // try to cast. |
| onSinksReceived(sourceId, NO_SINKS); |
| return; |
| } |
| |
| MediaSource source = getSourceFromId(sourceId); |
| if (source == null) { |
| // If the source is invalid or not supported by this provider, report no devices |
| // available. |
| onSinksReceived(sourceId, NO_SINKS); |
| return; |
| } |
| |
| MediaRouteSelector routeSelector = source.buildRouteSelector(); |
| if (routeSelector == null) { |
| // If the application invalid, report no devices available. |
| onSinksReceived(sourceId, NO_SINKS); |
| return; |
| } |
| |
| // No-op, if already monitoring the application for this source. |
| String applicationId = source.getApplicationId(); |
| DiscoveryCallback callback = mDiscoveryCallbacks.get(applicationId); |
| if (callback != null) { |
| callback.addSourceUrn(sourceId); |
| return; |
| } |
| |
| List<MediaSink> knownSinks = new ArrayList<MediaSink>(); |
| for (RouteInfo route : mAndroidMediaRouter.getRoutes()) { |
| if (route.matchesSelector(routeSelector)) { |
| knownSinks.add(MediaSink.fromRoute(route)); |
| } |
| } |
| |
| callback = new DiscoveryCallback(sourceId, knownSinks, this, routeSelector); |
| mAndroidMediaRouter.addCallback( |
| routeSelector, callback, MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY); |
| mDiscoveryCallbacks.put(applicationId, callback); |
| } |
| |
| @Override |
| public void stopObservingMediaSinks(String sourceId) { |
| Log.d(TAG, "startObservingMediaSinks: " + sourceId); |
| |
| if (mAndroidMediaRouter == null) { |
| // If the MediaRouter API is not available, report no devices so the page doesn't even |
| // try to cast. |
| onSinksReceived(sourceId, NO_SINKS); |
| return; |
| } |
| |
| MediaSource source = getSourceFromId(sourceId); |
| if (source == null) { |
| // If the source is invalid or not supported by this provider, report no devices |
| // available. |
| onSinksReceived(sourceId, NO_SINKS); |
| return; |
| } |
| |
| MediaRouteSelector routeSelector = source.buildRouteSelector(); |
| if (routeSelector == null) { |
| // If the application invalid, report no devices available. |
| onSinksReceived(sourceId, NO_SINKS); |
| return; |
| } |
| |
| // No-op, if already monitoring the application for this source. |
| String applicationId = source.getApplicationId(); |
| DiscoveryCallback callback = mDiscoveryCallbacks.get(applicationId); |
| if (callback != null) { |
| callback.addSourceUrn(sourceId); |
| return; |
| } |
| |
| List<MediaSink> knownSinks = new ArrayList<MediaSink>(); |
| for (RouteInfo route : mAndroidMediaRouter.getRoutes()) { |
| if (route.matchesSelector(routeSelector)) { |
| knownSinks.add(MediaSink.fromRoute(route)); |
| } |
| } |
| |
| callback = new DiscoveryCallback(sourceId, knownSinks, this, routeSelector); |
| mAndroidMediaRouter.addCallback( |
| routeSelector, callback, MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY); |
| mDiscoveryCallbacks.put(applicationId, callback); |
| } |
| |
| /** |
| * Forward the sinks back to the native counterpart. |
| */ |
| protected void onSinksReceivedInternal(String sourceId, @NonNull List<MediaSink> sinks) { |
| Log.d(TAG, "Reporting %d sinks for source: %s", sinks.size(), sourceId); |
| mManager.onSinksReceived(sourceId, this, sinks); |
| } |
| |
| /** |
| * {@link DiscoveryDelegate} implementation. |
| */ |
| @Override |
| public void onSinksReceived(String sourceId, @NonNull List<MediaSink> sinks) { |
| Log.d(TAG, "Received %d sinks for sourceId: %s", sinks.size(), sourceId); |
| mHandler.post(() -> { onSinksReceivedInternal(sourceId, sinks); }); |
| } |
| |
| @Override |
| public void createRoute(String sourceId, String sinkId, String presentationId, String origin, |
| int tabId, boolean isIncognito, int nativeRequestId) { |
| // Not implemented. |
| } |
| |
| @Override |
| public void joinRoute( |
| String sourceId, String presentationId, String origin, int tabId, int nativeRequestId) { |
| // Not implemented. |
| } |
| |
| @Override |
| public void closeRoute(String routeId) { |
| // Not implemented. |
| } |
| |
| @Override |
| public void detachRoute(String routeId) { |
| // Not implemented. |
| } |
| |
| @Override |
| public void sendStringMessage(String routeId, String message, int nativeCallbackId) { |
| // Not implemented. |
| } |
| |
| @Override |
| @Nullable |
| public MediaController getMediaController(String routeId) { |
| // Not implemented. |
| return null; |
| } |
| |
| private MediaSource getSourceFromId(String sourceId) { |
| return CastMediaSource.from(sourceId); |
| } |
| } |