blob: cb598d903ea5d922552503a194e06f1cd57f9bd1 [file] [log] [blame]
// Copyright 2017 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.cast;
import android.os.Handler;
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.DiscoveryCallback;
import org.chromium.chrome.browser.media.router.DiscoveryDelegate;
import org.chromium.chrome.browser.media.router.FlingingController;
import org.chromium.chrome.browser.media.router.MediaRoute;
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 java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* A {@link BaseMediaRouteProvider} common implementation for MediaRouteProviders.
*/
public abstract class BaseMediaRouteProvider
implements MediaRouteProvider, DiscoveryDelegate,
ChromeCastSessionManager.CastSessionManagerListener {
private static final String TAG = "MediaRouter";
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 final Map<String, MediaRoute> mRoutes = new HashMap<String, MediaRoute>();
protected Handler mHandler = new Handler();
// There can be only one Cast session at the same time on Android.
protected CastSession mSession;
protected BaseMediaRouteProvider(MediaRouter androidMediaRouter, MediaRouteManager manager) {
mAndroidMediaRouter = androidMediaRouter;
mManager = manager;
}
/**
* @return A MediaSource object constructed from |sourceId|, or null if the derived class does
* not support the source.
*/
@Nullable
protected abstract MediaSource getSourceFromId(@Nonnull String sourceId);
/**
* @return A CastSessionLaunchRequest encapsulating a session launch request.
*/
@Nullable
protected abstract ChromeCastSessionManager.CastSessionLaunchRequest createSessionLaunchRequest(
MediaSource source, MediaSink sink, String presentationId, String origin, int tabId,
boolean isIncognito, int nativeRequestId);
/**
* Forward the sinks back to the native counterpart.
*/
// Migrated to CafBaseMediaRouteProvider. See https://crbug.com/711860.
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.
*/
// Migrated to CafBaseMediaRouteProvider. See https://crbug.com/711860.
@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); });
}
/**
* {@link MediaRouteProvider} implementation.
*/
// Migrated to CafBaseMediaRouteProvider. See https://crbug.com/711860.
@Override
public boolean supportsSource(@Nonnull String sourceId) {
return getSourceFromId(sourceId) != null;
}
// Migrated to CafBaseMediaRouteProvider. See https://crbug.com/711860.
@Override
public void startObservingMediaSinks(@Nonnull 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;
}
// 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;
}
MediaRouteSelector routeSelector = source.buildRouteSelector();
if (routeSelector == null) {
// If the application invalid, report no devices available.
onSinksReceived(sourceId, NO_SINKS);
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);
}
// Migrated to CafBaseMediaRouteProvider. See https://crbug.com/711860.
@Override
public void stopObservingMediaSinks(@Nonnull String sourceId) {
Log.d(TAG, "stopObservingMediaSinks: " + sourceId);
if (mAndroidMediaRouter == null) return;
MediaSource source = getSourceFromId(sourceId);
if (source == null) return;
String applicationId = source.getApplicationId();
DiscoveryCallback callback = mDiscoveryCallbacks.get(applicationId);
if (callback == null) return;
callback.removeSourceUrn(sourceId);
if (callback.isEmpty()) {
mAndroidMediaRouter.removeCallback(callback);
mDiscoveryCallbacks.remove(applicationId);
}
}
// Migrated to CafBaseMediaRouteProvider. See https://crbug.com/711860.
@Override
public void createRoute(String sourceId, String sinkId, String presentationId, String origin,
int tabId, boolean isIncognito, int nativeRequestId) {
if (mAndroidMediaRouter == null) {
mManager.onRouteRequestError("Not supported", nativeRequestId);
return;
}
MediaSink sink = MediaSink.fromSinkId(sinkId, mAndroidMediaRouter);
if (sink == null) {
mManager.onRouteRequestError("No sink", nativeRequestId);
return;
}
MediaSource source = getSourceFromId(sourceId);
if (source == null) {
mManager.onRouteRequestError("Unsupported source URL", nativeRequestId);
return;
}
ChromeCastSessionManager.CastSessionLaunchRequest request = createSessionLaunchRequest(
source, sink, presentationId, origin, tabId, isIncognito, nativeRequestId);
ChromeCastSessionManager.get().requestSessionLaunch(request);
}
@Override
public abstract void joinRoute(
String sourceId, String presentationId, String origin, int tabId, int nativeRequestId);
@Override
public abstract void closeRoute(String routeId);
@Override
public abstract void detachRoute(String routeId);
@Override
public abstract void sendStringMessage(String routeId, String message);
// ChromeCastSessionObserver implementation.
@Override
public abstract void onSessionStarting(
ChromeCastSessionManager.CastSessionLaunchRequest originalRequest);
@Override
public abstract void onSessionEnded();
// Migrated to CafBaseMediaRouteProvider. See https://crbug.com/711860.
@Override
public void onSessionStartFailed() {
for (String routeId : mRoutes.keySet()) {
mManager.onRouteClosed(routeId, "Launch error");
}
mRoutes.clear();
};
// Migrated to CafBaseMediaRouteProvider. See https://crbug.com/711860.
@Override
public void onSessionStarted(CastSession session) {
mSession = session;
}
// Migrated to CafBaseMediaRouteProvider.endAllRoutes(). See https://crbug.com/711860.
@Override
public void onSessionStopAction() {
if (mSession == null) return;
for (String routeId : mRoutes.keySet()) closeRoute(routeId);
}
@Override
@Nullable
public FlingingController getFlingingController(String routeId) {
return null;
}
}