| Index: chrome/android/java/src/org/chromium/chrome/browser/media/remote/MediaUrlResolver.java
|
| diff --git a/chrome/android/java/src/org/chromium/chrome/browser/media/remote/MediaUrlResolver.java b/chrome/android/java/src/org/chromium/chrome/browser/media/remote/MediaUrlResolver.java
|
| index 133b5061844cfd25ea38e978bb6dc8e9fedc8867..627c6dcdb7f5c1e1a77c4b8368d68c35791267f0 100644
|
| --- a/chrome/android/java/src/org/chromium/chrome/browser/media/remote/MediaUrlResolver.java
|
| +++ b/chrome/android/java/src/org/chromium/chrome/browser/media/remote/MediaUrlResolver.java
|
| @@ -10,8 +10,8 @@ import android.os.AsyncTask;
|
| import android.text.TextUtils;
|
| import android.util.Log;
|
|
|
| -import org.apache.http.Header;
|
| -import org.apache.http.message.BasicHeader;
|
| +import org.chromium.base.CommandLine;
|
| +import org.chromium.chrome.ChromeSwitches;
|
| import org.chromium.chrome.browser.ChromiumApplication;
|
|
|
| import java.io.IOException;
|
| @@ -20,7 +20,8 @@ import java.net.MalformedURLException;
|
| import java.net.URI;
|
| import java.net.URISyntaxException;
|
| import java.net.URL;
|
| -import java.util.Arrays;
|
| +import java.util.List;
|
| +import java.util.Map;
|
|
|
| /**
|
| * Resolves the final URL if it's a redirect. Works asynchronously, uses HTTP
|
| @@ -48,46 +49,49 @@ public class MediaUrlResolver extends AsyncTask<Void, Void, MediaUrlResolver.Res
|
| *
|
| * @param uri the resolved URL.
|
| */
|
| - void setUri(Uri uri, Header[] headers);
|
| + void setUri(Uri uri, boolean palyable);
|
| }
|
|
|
|
|
| protected static final class Result {
|
| private final String mUri;
|
| - private final Header[] mRelevantHeaders;
|
| + private final boolean mPlayable;
|
|
|
| - public Result(String uri, Header[] relevantHeaders) {
|
| + public Result(String uri, boolean playable) {
|
| mUri = uri;
|
| - mRelevantHeaders =
|
| - relevantHeaders != null
|
| - ? Arrays.copyOf(relevantHeaders, relevantHeaders.length)
|
| - : null;
|
| + mPlayable = playable;
|
| }
|
|
|
| public String getUri() {
|
| return mUri;
|
| }
|
|
|
| - public Header[] getRelevantHeaders() {
|
| - return mRelevantHeaders != null
|
| - ? Arrays.copyOf(mRelevantHeaders, mRelevantHeaders.length)
|
| - : null;
|
| + public boolean isPlayable() {
|
| + return mPlayable;
|
| }
|
| }
|
|
|
| private static final String TAG = "MediaUrlResolver";
|
|
|
| - private static final String CORS_HEADER_NAME = "Access-Control-Allow-Origin";
|
| private static final String COOKIES_HEADER_NAME = "Cookies";
|
| private static final String USER_AGENT_HEADER_NAME = "User-Agent";
|
| private static final String RANGE_HEADER_NAME = "Range";
|
| + private static final String CORS_HEADER_NAME = "Access-Control-Allow-Origin";
|
| +
|
| + // Media types supported for cast, see
|
| + // media/base/container_names.h for the actual enum where these are defined
|
| + private static final int UNKNOWN_MEDIA = 0;
|
| + private static final int SMOOTHSTREAM_MEDIA = 39;
|
| + private static final int DASH_MEDIA = 38;
|
| + private static final int HLS_MEDIA = 22;
|
| + private static final int MPEG4_MEDIA = 29;
|
|
|
| // We don't want to necessarily fetch the whole video but we don't want to miss the CORS header.
|
| // Assume that 64k should be more than enough to keep all the headers.
|
| private static final String RANGE_HEADER_VALUE = "bytes: 0-65536";
|
|
|
| - private final Context mContext;
|
| private final Delegate mDelegate;
|
| + private boolean mDebug;
|
|
|
| /**
|
| * The constructor
|
| @@ -95,7 +99,7 @@ public class MediaUrlResolver extends AsyncTask<Void, Void, MediaUrlResolver.Res
|
| * @param delegate The customer for this URL resolver.
|
| */
|
| public MediaUrlResolver(Context context, Delegate delegate) {
|
| - mContext = context;
|
| + mDebug = CommandLine.getInstance().hasSwitch(ChromeSwitches.ENABLE_CAST_DEBUG_LOGS);
|
| mDelegate = delegate;
|
| }
|
|
|
| @@ -103,12 +107,12 @@ public class MediaUrlResolver extends AsyncTask<Void, Void, MediaUrlResolver.Res
|
| protected MediaUrlResolver.Result doInBackground(Void... params) {
|
| Uri uri = mDelegate.getUri();
|
| String url = uri.toString();
|
| - Header[] relevantHeaders = null;
|
| String cookies = mDelegate.getCookies();
|
| String userAgent = ChromiumApplication.getBrowserUserAgent();
|
| // URL may already be partially percent encoded; double percent encoding will break
|
| // things, so decode it before sanitizing it.
|
| String sanitizedUrl = sanitizeUrl(Uri.decode(url));
|
| + Map<String, List<String>> headers = null;
|
|
|
| // If we failed to sanitize the URL (e.g. because the host name contains underscores) then
|
| // HttpURLConnection won't work,so we can't follow redirections. Just try to use it as is.
|
| @@ -126,28 +130,23 @@ public class MediaUrlResolver extends AsyncTask<Void, Void, MediaUrlResolver.Res
|
| urlConnection.setRequestProperty(RANGE_HEADER_NAME, RANGE_HEADER_VALUE);
|
|
|
| // This triggers resolving the URL and receiving the headers.
|
| - urlConnection.getHeaderFields();
|
| + headers = urlConnection.getHeaderFields();
|
|
|
| url = urlConnection.getURL().toString();
|
| - String corsHeader = urlConnection.getHeaderField(CORS_HEADER_NAME);
|
| - if (corsHeader != null) {
|
| - relevantHeaders = new Header[1];
|
| - relevantHeaders[0] = new BasicHeader(CORS_HEADER_NAME, corsHeader);
|
| - }
|
| } catch (IOException e) {
|
| Log.e(TAG, "Failed to fetch the final URI", e);
|
| url = "";
|
| }
|
| if (urlConnection != null) urlConnection.disconnect();
|
| }
|
| - return new MediaUrlResolver.Result(url, relevantHeaders);
|
| + return new MediaUrlResolver.Result(url, canPlayMedia(url, headers));
|
| }
|
|
|
| @Override
|
| protected void onPostExecute(MediaUrlResolver.Result result) {
|
| String url = result.getUri();
|
| Uri uri = "".equals(url) ? Uri.EMPTY : Uri.parse(url);
|
| - mDelegate.setUri(uri, result.getRelevantHeaders());
|
| + mDelegate.setUri(uri, result.isPlayable());
|
| }
|
|
|
| private String sanitizeUrl(String unsafeUrl) {
|
| @@ -165,4 +164,28 @@ public class MediaUrlResolver extends AsyncTask<Void, Void, MediaUrlResolver.Res
|
| }
|
| return "";
|
| }
|
| +
|
| + private boolean canPlayMedia(String url, Map<String, List<String>> headers) {
|
| + if (url.isEmpty()) return false;
|
| +
|
| + // HLS media requires Cors headers.
|
| + if ((headers == null || isEnhancedMedia(url) && !headers.containsKey(CORS_HEADER_NAME))) {
|
| + if (mDebug) Log.d(TAG, "HLS stream without CORs header: " + url);
|
| + return false;
|
| + }
|
| + return true;
|
| + }
|
| +
|
| + private boolean isEnhancedMedia(String url) {
|
| + int mediaType = getMediaType(url);
|
| + return mediaType == HLS_MEDIA || mediaType == DASH_MEDIA || mediaType == SMOOTHSTREAM_MEDIA;
|
| + }
|
| +
|
| + static int getMediaType(String url) {
|
| + if (url.contains(".m3u8")) return HLS_MEDIA;
|
| + if (url.contains(".mp4")) return MPEG4_MEDIA;
|
| + if (url.contains(".mpd")) return DASH_MEDIA;
|
| + if (url.contains(".ism")) return SMOOTHSTREAM_MEDIA;
|
| + return UNKNOWN_MEDIA;
|
| + }
|
| }
|
|
|