Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(917)

Unified Diff: chrome/android/java/src/org/chromium/chrome/browser/media/remote/MediaUrlResolver.java

Issue 1907413002: [Android,MediaFling] Fix the encoding issue with already encoded URLs for abc13.com/live (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Updated test cases Created 4 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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 0c9e7b37bbce9b6225cc710787747102640abb72..c4746c5b64ff8469f890043092a65293b89e5d0d 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
@@ -7,21 +7,20 @@ package org.chromium.chrome.browser.media.remote;
import android.net.Uri;
import android.os.AsyncTask;
import android.text.TextUtils;
-import android.util.Log;
import org.chromium.base.CommandLine;
+import org.chromium.base.Log;
import org.chromium.base.VisibleForTesting;
import org.chromium.base.metrics.RecordHistogram;
import org.chromium.chrome.browser.ChromeSwitches;
import java.io.IOException;
import java.net.HttpURLConnection;
-import java.net.MalformedURLException;
-import java.net.URI;
-import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLStreamHandler;
+import java.util.Arrays;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
/**
@@ -33,12 +32,27 @@ public class MediaUrlResolver extends AsyncTask<Void, Void, MediaUrlResolver.Res
// Cast.Sender.UrlResolveResult UMA histogram values; must match values of
// RemotePlaybackUrlResolveResult in histograms.xml. Do not change these values, as they are
// being used in UMA.
- private static final int RESOLVE_SUCCESSFUL = 0;
- private static final int MALFORMED_URL = 1;
- private static final int NO_CORS = 2;
- private static final int INCOMPATIBLE_CORS = 3;
+ private static final int RESOLVE_RESULT_SUCCESS = 0;
+ private static final int RESOLVE_RESULT_MALFORMED_URL = 1;
+ private static final int RESOLVE_RESULT_NO_CORS = 2;
+ private static final int RESOLVE_RESULT_INCOMPATIBLE_CORS = 3;
+ private static final int RESOLVE_RESULT_SERVER_ERROR = 4;
+ private static final int RESOLVE_RESULT_NETWORK_ERROR = 5;
+ private static final int RESOLVE_RESULT_UNSUPPORTED_MEDIA = 6;
+
// Range of histogram.
- private static final int HISTOGRAM_RESULT_COUNT = 4;
+ private static final int HISTOGRAM_RESULT_COUNT = 7;
+
+ // Acceptal response codes for URL resolving request.
+ private static final Integer[] SUCCESS_RESPONSE_CODES = {
+ // Request succeeded.
+ HttpURLConnection.HTTP_OK,
+ HttpURLConnection.HTTP_PARTIAL,
+
+ // HttpURLConnection only follows up to 5 redirects, this response is unlikely but possible.
+ HttpURLConnection.HTTP_MOVED_PERM,
+ HttpURLConnection.HTTP_MOVED_TEMP,
+ };
/**
* The interface to get the initial URI with cookies from and pass the final
@@ -65,19 +79,19 @@ public class MediaUrlResolver extends AsyncTask<Void, Void, MediaUrlResolver.Res
protected static final class Result {
- private final String mUri;
+ private final Uri mUri;
private final boolean mPlayable;
- public Result(String uri, boolean playable) {
+ public Result(Uri uri, boolean playable) {
mUri = uri;
mPlayable = playable;
}
- public String getUri() {
+ public Uri getUri() {
return mUri;
}
- public boolean isPlayable() {
+ public boolean isPlayable() {
return mPlayable;
}
}
@@ -93,12 +107,19 @@ public class MediaUrlResolver extends AsyncTask<Void, Void, MediaUrlResolver.Res
private static final String CHROMECAST_ORIGIN = "https://www.gstatic.com";
// 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;
+ // media/base/container_names.h for the actual enum where these are defined.
+ // See https://developers.google.com/cast/docs/media#media-container-formats for the formats
+ // supported by Cast devices.
+ private static final int MEDIA_TYPE_UNKNOWN = 0;
+ private static final int MEDIA_TYPE_AAC = 1;
+ private static final int MEDIA_TYPE_HLS = 22;
+ private static final int MEDIA_TYPE_MP3 = 26;
+ private static final int MEDIA_TYPE_MPEG4 = 29;
+ private static final int MEDIA_TYPE_OGG = 30;
+ private static final int MEDIA_TYPE_WAV = 35;
+ private static final int MEDIA_TYPE_WEBM = 36;
+ private static final int MEDIA_TYPE_DASH = 38;
+ private static final int MEDIA_TYPE_SMOOTHSTREAM = 39;
// 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.
@@ -130,72 +151,57 @@ public class MediaUrlResolver extends AsyncTask<Void, Void, MediaUrlResolver.Res
@Override
protected MediaUrlResolver.Result doInBackground(Void... params) {
Uri uri = mDelegate.getUri();
- if (uri == null) {
- return new MediaUrlResolver.Result("", false);
+ if (uri == null || uri.equals(Uri.EMPTY)) {
+ return new MediaUrlResolver.Result(Uri.EMPTY, false);
}
- String url = uri.toString();
String cookies = mDelegate.getCookies();
- // 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;
+ HttpURLConnection urlConnection = null;
+ try {
+ URL requestUrl = new URL(null, uri.toString(), mStreamHandler);
+ urlConnection = (HttpURLConnection) requestUrl.openConnection();
+ if (!TextUtils.isEmpty(cookies)) {
+ urlConnection.setRequestProperty(COOKIES_HEADER_NAME, cookies);
+ }
- // 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.
- // TODO (aberent): Find out if there is a way of following redirections that is not so
- // strict on the URL format.
- if (!sanitizedUrl.equals("")) {
- HttpURLConnection urlConnection = null;
- try {
- URL requestUrl = new URL(null, sanitizedUrl, mStreamHandler);
- urlConnection = (HttpURLConnection) requestUrl.openConnection();
- if (!TextUtils.isEmpty(cookies)) {
- urlConnection.setRequestProperty(COOKIES_HEADER_NAME, cookies);
- }
- // Pretend that this is coming from the Chromecast.
- urlConnection.setRequestProperty(ORIGIN_HEADER_NAME, CHROMECAST_ORIGIN);
- urlConnection.setRequestProperty(USER_AGENT_HEADER_NAME, mUserAgent);
+ // Pretend that this is coming from the Chromecast.
+ urlConnection.setRequestProperty(ORIGIN_HEADER_NAME, CHROMECAST_ORIGIN);
+ urlConnection.setRequestProperty(USER_AGENT_HEADER_NAME, mUserAgent);
+ if (!isEnhancedMedia(uri)) {
+ // Manifest files are typically smaller than 64K so range request can fail.
urlConnection.setRequestProperty(RANGE_HEADER_NAME, RANGE_HEADER_VALUE);
+ }
+
+ // This triggers resolving the URL and receiving the headers.
+ headers = urlConnection.getHeaderFields();
- // This triggers resolving the URL and receiving the headers.
- headers = urlConnection.getHeaderFields();
+ uri = Uri.parse(urlConnection.getURL().toString());
- url = urlConnection.getURL().toString();
- } catch (IOException e) {
- Log.e(TAG, "Failed to fetch the final URI", e);
- url = "";
+ // If server's response is not valid, don't try to fling the video.
+ int responseCode = urlConnection.getResponseCode();
+ if (!Arrays.asList(SUCCESS_RESPONSE_CODES).contains(responseCode)) {
+ recordResultHistogram(RESOLVE_RESULT_SERVER_ERROR);
+ Log.e(TAG, "Server response is not valid: %d", responseCode);
+ uri = Uri.EMPTY;
}
- if (urlConnection != null) urlConnection.disconnect();
+ } catch (IOException e) {
+ recordResultHistogram(RESOLVE_RESULT_NETWORK_ERROR);
+ Log.e(TAG, "Failed to fetch the final url", e);
+ uri = Uri.EMPTY;
}
- return new MediaUrlResolver.Result(url, canPlayMedia(url, headers));
+ if (urlConnection != null) urlConnection.disconnect();
+ return new MediaUrlResolver.Result(uri, canPlayMedia(uri, headers));
}
@Override
protected void onPostExecute(MediaUrlResolver.Result result) {
- String url = result.getUri();
- Uri uri = "".equals(url) ? Uri.EMPTY : Uri.parse(url);
- mDelegate.deliverResult(uri, result.isPlayable());
+ mDelegate.deliverResult(result.getUri(), result.isPlayable());
}
- private String sanitizeUrl(String unsafeUrl) {
- URL url;
- URI uri;
- try {
- url = new URL(unsafeUrl);
- uri = new URI(url.getProtocol(), url.getUserInfo(), url.getHost(), url.getPort(),
- url.getPath(), url.getQuery(), url.getRef());
- return uri.toURL().toString();
- } catch (URISyntaxException syntaxException) {
- Log.w(TAG, "URISyntaxException " + syntaxException);
- } catch (MalformedURLException malformedUrlException) {
- Log.w(TAG, "MalformedURLException " + malformedUrlException);
- }
- return "";
- }
-
- private boolean canPlayMedia(String url, Map<String, List<String>> headers) {
- if (url.isEmpty()) {
- recordResultHistogram(MALFORMED_URL);
+ private boolean canPlayMedia(Uri uri, Map<String, List<String>> headers) {
+ if (uri == null || uri.equals(Uri.EMPTY)) {
+ recordResultHistogram(RESOLVE_RESULT_MALFORMED_URL);
return false;
}
@@ -204,24 +210,34 @@ public class MediaUrlResolver extends AsyncTask<Void, Void, MediaUrlResolver.Res
List<String> corsData = headers.get(CORS_HEADER_NAME);
if (corsData.isEmpty() || (!corsData.get(0).equals("*")
&& !corsData.get(0).equals(CHROMECAST_ORIGIN))) {
- recordResultHistogram(INCOMPATIBLE_CORS);
+ recordResultHistogram(RESOLVE_RESULT_INCOMPATIBLE_CORS);
return false;
}
- } else if (isEnhancedMedia(url)) {
- // HLS media requires Cors headers.
- if (mDebug) Log.d(TAG, "HLS stream without CORS header: " + url);
- recordResultHistogram(NO_CORS);
+ } else if (isEnhancedMedia(uri)) {
+ // HLS media requires CORS headers.
+ // TODO(avayvod): it actually requires CORS on the final video URLs vs the manifest.
+ // Clank assumes that if CORS is set for the manifest it's set for everything but
+ // it not necessary always true. See b/19138712
+ Log.d(TAG, "HLS stream without CORS header: %s", uri);
+ recordResultHistogram(RESOLVE_RESULT_NO_CORS);
return false;
}
- // TODO(aberent) Return false for media types that are not playable on Chromecast
- // (getMediaType would need to know about more types to implement this).
- recordResultHistogram(RESOLVE_SUCCESSFUL);
+
+ if (getMediaType(uri) == MEDIA_TYPE_UNKNOWN) {
+ Log.d(TAG, "Unsupported media container format: %s", uri);
+ recordResultHistogram(RESOLVE_RESULT_UNSUPPORTED_MEDIA);
+ return false;
+ }
+
+ recordResultHistogram(RESOLVE_RESULT_SUCCESS);
return true;
}
- private boolean isEnhancedMedia(String url) {
- int mediaType = getMediaType(url);
- return mediaType == HLS_MEDIA || mediaType == DASH_MEDIA || mediaType == SMOOTHSTREAM_MEDIA;
+ private boolean isEnhancedMedia(Uri uri) {
+ int mediaType = getMediaType(uri);
+ return mediaType == MEDIA_TYPE_HLS
+ || mediaType == MEDIA_TYPE_DASH
+ || mediaType == MEDIA_TYPE_SMOOTHSTREAM;
}
@VisibleForTesting
@@ -230,11 +246,17 @@ public class MediaUrlResolver extends AsyncTask<Void, Void, MediaUrlResolver.Res
HISTOGRAM_RESULT_COUNT);
}
- 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;
+ static int getMediaType(Uri uri) {
+ String path = uri.getPath().toLowerCase(Locale.US);
+ if (path.endsWith(".m3u8")) return MEDIA_TYPE_HLS;
+ if (path.endsWith(".mp4")) return MEDIA_TYPE_MPEG4;
+ if (path.endsWith(".mpd")) return MEDIA_TYPE_DASH;
+ if (path.endsWith(".ism")) return MEDIA_TYPE_SMOOTHSTREAM;
+ if (path.endsWith(".m4a") || path.endsWith(".aac")) return MEDIA_TYPE_AAC;
+ if (path.endsWith(".mp3")) return MEDIA_TYPE_MP3;
+ if (path.endsWith(".wav")) return MEDIA_TYPE_WAV;
+ if (path.endsWith(".webm")) return MEDIA_TYPE_WEBM;
+ if (path.endsWith(".ogg")) return MEDIA_TYPE_OGG;
+ return MEDIA_TYPE_UNKNOWN;
}
-}
+}

Powered by Google App Engine
This is Rietveld 408576698