// Copyright 2015 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.net.Uri; import android.support.v7.media.MediaRouteSelector; import com.google.android.gms.cast.CastMediaControlIntent; import java.util.Arrays; import java.util.List; import javax.annotation.Nullable; /** * Abstracts parsing the Cast application id and other parameters from the source URN. */ public class MediaSource { public static final String AUTOJOIN_CUSTOM_CONTROLLER_SCOPED = "custom_controller_scoped"; public static final String AUTOJOIN_TAB_AND_ORIGIN_SCOPED = "tab_and_origin_scoped"; public static final String AUTOJOIN_ORIGIN_SCOPED = "origin_scoped"; public static final String AUTOJOIN_PAGE_SCOPED = "page_scoped"; private static final String CAST_SOURCE_ID_SEPARATOR = "/"; private static final String CAST_SOURCE_ID_APPLICATION_ID = "__castAppId__"; private static final String CAST_SOURCE_ID_CLIENT_ID = "__castClientId__"; private static final String CAST_SOURCE_ID_AUTOJOIN_POLICY = "__castAutoJoinPolicy__"; private static final String CAST_APP_CAPABILITIES_PREFIX = "("; private static final String CAST_APP_CAPABILITIES_SUFFIX = ")"; private static final String CAST_APP_CAPABILITIES_SEPARATOR = ","; private static final String CAST_APP_CAPABILITIES[] = { "video_out", "audio_out", "video_in", "audio_in", "multizone_group" }; /** * The original presentation URL that the {@link MediaSource} object was created from. */ private final String mSourceId; /** * The Cast application id, can be invalid in which case {@link CastMediaRouteProvider} * will explicitly report no sinks available. */ private final String mApplicationId; /** * A numeric identifier for the Cast Web SDK, unique for the frame providing the * presentation URL. Can be null. */ private final String mClientId; /** * Defines Cast-specific behavior for {@link CastMediaRouteProvider#joinRoute}. Defaults to * {@link MediaSource#AUTOJOIN_TAB_AND_ORIGIN_SCOPED}. */ private final String mAutoJoinPolicy; /** * Defines the capabilities of the particular application id. Can be null. */ private final String[] mCapabilities; /** * Initializes the media source from the source id. * @param sourceId the source id for the Cast media source (a presentation url). * @return an initialized media source if the id is valid, null otherwise. */ @Nullable public static MediaSource from(String sourceId) { assert sourceId != null; Uri sourceUri = Uri.parse(sourceId); String uriFragment = sourceUri.getFragment(); if (uriFragment == null) return null; String[] parameters = uriFragment.split(CAST_SOURCE_ID_SEPARATOR); String applicationId = extractParameter(parameters, CAST_SOURCE_ID_APPLICATION_ID); if (applicationId == null) return null; String[] capabilities = null; int capabilitiesIndex = applicationId.indexOf(CAST_APP_CAPABILITIES_PREFIX); if (capabilitiesIndex != -1) { capabilities = extractCapabilities(applicationId.substring(capabilitiesIndex)); if (capabilities == null) return null; applicationId = applicationId.substring(0, capabilitiesIndex); } String clientId = extractParameter(parameters, CAST_SOURCE_ID_CLIENT_ID); String autoJoinPolicy = extractParameter(parameters, CAST_SOURCE_ID_AUTOJOIN_POLICY); return new MediaSource(sourceId, applicationId, clientId, autoJoinPolicy, capabilities); } /** * Returns a new {@link MediaRouteSelector} to use for Cast device filtering for this * particular media source or null if the application id is invalid. * * @return an initialized route selector or null. */ public MediaRouteSelector buildRouteSelector() { try { return new MediaRouteSelector.Builder() .addControlCategory(CastMediaControlIntent.categoryForCast(mApplicationId)) .build(); } catch (IllegalArgumentException e) { return null; } } /** * @return the Cast application id corresponding to the source. */ public String getApplicationId() { return mApplicationId; } /** * @return the client id if passed in the source id. Can be null. */ @Nullable public String getClientId() { return mClientId; } /** * @return the auto join policy which must be one of the AUTOJOIN constants defined above. */ public String getAutoJoinPolicy() { return mAutoJoinPolicy; } /** * @return the id identifying the media source */ public String getUrn() { return mSourceId; } /** * @return application capabilities */ public String[] getCapabilities() { return mCapabilities == null ? null : Arrays.copyOf(mCapabilities, mCapabilities.length); } private MediaSource( String sourceId, String applicationId, String clientId, String autoJoinPolicy, String[] capabilities) { mSourceId = sourceId; mApplicationId = applicationId; mClientId = clientId; mAutoJoinPolicy = autoJoinPolicy == null ? AUTOJOIN_TAB_AND_ORIGIN_SCOPED : autoJoinPolicy; mCapabilities = capabilities; } @Nullable private static String extractParameter(String[] fragments, String key) { String keyPrefix = key + "="; for (String parameter : fragments) { if (parameter.startsWith(keyPrefix)) return parameter.substring(keyPrefix.length()); } return null; } @Nullable private static String[] extractCapabilities(String capabilitiesParameter) { if (capabilitiesParameter.length() < CAST_APP_CAPABILITIES_PREFIX.length() + CAST_APP_CAPABILITIES_SUFFIX.length()) { return null; } if (!capabilitiesParameter.startsWith(CAST_APP_CAPABILITIES_PREFIX) || !capabilitiesParameter.endsWith(CAST_APP_CAPABILITIES_SUFFIX)) { return null; } List<String> supportedCapabilities = Arrays.asList(CAST_APP_CAPABILITIES); String capabilitiesList = capabilitiesParameter.substring( CAST_APP_CAPABILITIES_PREFIX.length(), capabilitiesParameter.length() - CAST_APP_CAPABILITIES_SUFFIX.length()); String[] capabilities = capabilitiesList.split(CAST_APP_CAPABILITIES_SEPARATOR); for (String capability : capabilities) { if (!supportedCapabilities.contains(capability)) return null; } return capabilities; } }