Clean up Util.inferContentType methods

This fixes some small niggles:
1. `inferContentType(String)` is documented to take a path, but in the
   tests we're passing full URIs.
2. A `String` parameter is usually a path, but also a MIME type or an
   extension. In the new methods, the meaning of a `String` parameter
   is always clear from the name of the method.
3. `inferContentType(String)` is always passed an extension in
   'production' code (which has to be manually prefixed with a dot).
4. `inferContentType(Uri, @Nullable String)` always ignores the Uri if
   the String is non-null. IMO this logic is clearer to a reader if it's
   just in-lined at the call-site.

These methods are used from the demo apps, so will be part of the stable
API.

PiperOrigin-RevId: 444826053
This commit is contained in:
ibaker 2022-04-27 13:10:36 +01:00 committed by Ian Baker
parent 925a907c5f
commit 754eb1527a
6 changed files with 141 additions and 55 deletions

View File

@ -20,6 +20,7 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.text.TextUtils;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@ -156,7 +157,12 @@ public final class MainActivity extends Activity {
DataSource.Factory dataSourceFactory = new DefaultDataSource.Factory(this); DataSource.Factory dataSourceFactory = new DefaultDataSource.Factory(this);
MediaSource mediaSource; MediaSource mediaSource;
@C.ContentType int type = Util.inferContentType(uri, intent.getStringExtra(EXTENSION_EXTRA)); @Nullable String fileExtension = intent.getStringExtra(EXTENSION_EXTRA);
@C.ContentType
int type =
TextUtils.isEmpty(fileExtension)
? Util.inferContentType(uri)
: Util.inferContentTypeForExtension(fileExtension);
if (type == C.TYPE_DASH) { if (type == C.TYPE_DASH) {
mediaSource = mediaSource =
new DashMediaSource.Factory(dataSourceFactory) new DashMediaSource.Factory(dataSourceFactory)

View File

@ -27,6 +27,7 @@ import android.content.res.AssetManager;
import android.net.Uri; import android.net.Uri;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.text.TextUtils;
import android.util.JsonReader; import android.util.JsonReader;
import android.view.Menu; import android.view.Menu;
import android.view.MenuInflater; import android.view.MenuInflater;
@ -440,7 +441,10 @@ public class SampleChooserActivity extends AppCompatActivity
} else { } else {
@Nullable @Nullable
String adaptiveMimeType = String adaptiveMimeType =
Util.getAdaptiveMimeTypeForContentType(Util.inferContentType(uri, extension)); Util.getAdaptiveMimeTypeForContentType(
TextUtils.isEmpty(extension)
? Util.inferContentType(uri)
: Util.inferContentTypeForExtension(extension));
mediaItem mediaItem
.setUri(uri) .setUri(uri)
.setMediaMetadata(new MediaMetadata.Builder().setTitle(title).build()) .setMediaMetadata(new MediaMetadata.Builder().setTitle(title).build())

View File

@ -19,6 +19,7 @@ import android.app.Activity;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.text.TextUtils;
import android.view.Surface; import android.view.Surface;
import android.view.SurfaceControl; import android.view.SurfaceControl;
import android.view.SurfaceHolder; import android.view.SurfaceHolder;
@ -201,7 +202,12 @@ public final class MainActivity extends Activity {
DataSource.Factory dataSourceFactory = new DefaultDataSource.Factory(this); DataSource.Factory dataSourceFactory = new DefaultDataSource.Factory(this);
MediaSource mediaSource; MediaSource mediaSource;
@C.ContentType int type = Util.inferContentType(uri, intent.getStringExtra(EXTENSION_EXTRA)); @Nullable String fileExtension = intent.getStringExtra(EXTENSION_EXTRA);
@C.ContentType
int type =
TextUtils.isEmpty(fileExtension)
? Util.inferContentType(uri)
: Util.inferContentTypeForExtension(fileExtension);
if (type == C.TYPE_DASH) { if (type == C.TYPE_DASH) {
mediaSource = mediaSource =
new DashMediaSource.Factory(dataSourceFactory) new DashMediaSource.Factory(dataSourceFactory)

View File

@ -722,18 +722,15 @@ public final class C {
@Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE}) @Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE})
@IntDef({TYPE_DASH, TYPE_SS, TYPE_HLS, TYPE_RTSP, TYPE_OTHER}) @IntDef({TYPE_DASH, TYPE_SS, TYPE_HLS, TYPE_RTSP, TYPE_OTHER})
public @interface ContentType {} public @interface ContentType {}
/** Value returned by {@link Util#inferContentType} for DASH manifests. */ /** Value representing a DASH manifest. */
public static final int TYPE_DASH = 0; public static final int TYPE_DASH = 0;
/** Value returned by {@link Util#inferContentType} for Smooth Streaming manifests. */ /** Value representing a Smooth Streaming manifest. */
public static final int TYPE_SS = 1; public static final int TYPE_SS = 1;
/** Value returned by {@link Util#inferContentType} for HLS manifests. */ /** Value representing an HLS manifest. */
public static final int TYPE_HLS = 2; public static final int TYPE_HLS = 2;
/** Value returned by {@link Util#inferContentType} for RTSP. */ /** Value representing an RTSP stream. */
public static final int TYPE_RTSP = 3; public static final int TYPE_RTSP = 3;
/** /** Value representing files other than DASH, HLS or Smooth Streaming manifests, or RTSP URIs. */
* Value returned by {@link Util#inferContentType} for files other than DASH, HLS or Smooth
* Streaming manifests, or RTSP URIs.
*/
public static final int TYPE_OTHER = 4; public static final int TYPE_OTHER = 4;
/** A return value for methods where the end of an input was encountered. */ /** A return value for methods where the end of an input was encountered. */

View File

@ -156,8 +156,9 @@ public final class Util {
+ "(T(([0-9]*)H)?(([0-9]*)M)?(([0-9.]*)S)?)?$"); + "(T(([0-9]*)H)?(([0-9]*)M)?(([0-9.]*)S)?)?$");
private static final Pattern ESCAPED_CHARACTER_PATTERN = Pattern.compile("%([A-Fa-f0-9]{2})"); private static final Pattern ESCAPED_CHARACTER_PATTERN = Pattern.compile("%([A-Fa-f0-9]{2})");
// https://docs.microsoft.com/en-us/azure/media-services/previous/media-services-deliver-content-overview#URLs. // https://docs.microsoft.com/en-us/azure/media-services/previous/media-services-deliver-content-overview#URLs
private static final Pattern ISM_URL_PATTERN = Pattern.compile(".*\\.isml?(?:/(manifest(.*))?)?"); private static final Pattern ISM_PATH_PATTERN =
Pattern.compile(".*\\.isml?(?:/(manifest(.*))?)?", Pattern.CASE_INSENSITIVE);
private static final String ISM_HLS_FORMAT_EXTENSION = "format=m3u8-aapl"; private static final String ISM_HLS_FORMAT_EXTENSION = "format=m3u8-aapl";
private static final String ISM_DASH_FORMAT_EXTENSION = "format=mpd-time-csf"; private static final String ISM_DASH_FORMAT_EXTENSION = "format=mpd-time-csf";
@ -1818,16 +1819,14 @@ public final class Util {
} }
/** /**
* Makes a best guess to infer the {@link ContentType} from a {@link Uri}. * @deprecated Use {@link #inferContentTypeForExtension(String)} when {@code overrideExtension} is
* * non-empty, and {@link #inferContentType(Uri)} otherwise.
* @param uri The {@link Uri}.
* @param overrideExtension If not null, used to infer the type.
* @return The content type.
*/ */
@Deprecated
public static @ContentType int inferContentType(Uri uri, @Nullable String overrideExtension) { public static @ContentType int inferContentType(Uri uri, @Nullable String overrideExtension) {
return TextUtils.isEmpty(overrideExtension) return TextUtils.isEmpty(overrideExtension)
? inferContentType(uri) ? inferContentType(uri)
: inferContentType("." + overrideExtension); : inferContentTypeForExtension(overrideExtension);
} }
/** /**
@ -1843,23 +1842,19 @@ public final class Util {
} }
@Nullable String path = uri.getPath(); @Nullable String path = uri.getPath();
return path == null ? C.TYPE_OTHER : inferContentType(path); if (path == null) {
} return C.TYPE_OTHER;
/**
* Makes a best guess to infer the {@link ContentType} from a file name.
*
* @param fileName Name of the file. It can include the path of the file.
* @return The content type.
*/
public static @ContentType int inferContentType(String fileName) {
fileName = Ascii.toLowerCase(fileName);
if (fileName.endsWith(".mpd")) {
return C.TYPE_DASH;
} else if (fileName.endsWith(".m3u8")) {
return C.TYPE_HLS;
} }
Matcher ismMatcher = ISM_URL_PATTERN.matcher(fileName); int lastDotIndex = path.lastIndexOf('.');
if (lastDotIndex >= 0) {
@C.ContentType
int contentType = inferContentTypeForExtension(path.substring(lastDotIndex + 1));
if (contentType != C.TYPE_OTHER) {
return contentType;
}
}
Matcher ismMatcher = ISM_PATH_PATTERN.matcher(path);
if (ismMatcher.matches()) { if (ismMatcher.matches()) {
@Nullable String extensions = ismMatcher.group(2); @Nullable String extensions = ismMatcher.group(2);
if (extensions != null) { if (extensions != null) {
@ -1871,6 +1866,31 @@ public final class Util {
} }
return C.TYPE_SS; return C.TYPE_SS;
} }
return C.TYPE_OTHER;
}
/**
* @deprecated Use {@link Uri#parse(String)} and {@link #inferContentType(Uri)} for full file
* paths or {@link #inferContentTypeForExtension(String)} for extensions.
*/
@Deprecated
public static @ContentType int inferContentType(String fileName) {
return inferContentType(Uri.parse("file:///" + fileName));
}
/**
* Makes a best guess to infer the {@link ContentType} from a file extension.
*
* @param fileExtension The extension of the file (excluding the '.').
* @return The content type.
*/
public static @ContentType int inferContentTypeForExtension(String fileExtension) {
if (Ascii.equalsIgnoreCase("mpd", fileExtension)) {
return C.TYPE_DASH;
} else if (Ascii.equalsIgnoreCase("m3u8", fileExtension)) {
return C.TYPE_HLS;
}
return C.TYPE_OTHER; return C.TYPE_OTHER;
} }
@ -1933,7 +1953,7 @@ public final class Util {
if (path == null) { if (path == null) {
return uri; return uri;
} }
Matcher ismMatcher = ISM_URL_PATTERN.matcher(Ascii.toLowerCase(path)); Matcher ismMatcher = ISM_PATH_PATTERN.matcher(path);
if (ismMatcher.matches() && ismMatcher.group(1) == null) { if (ismMatcher.matches() && ismMatcher.group(1) == null) {
// Add missing "Manifest" suffix. // Add missing "Manifest" suffix.
return Uri.withAppendedPath(uri, "Manifest"); return Uri.withAppendedPath(uri, "Manifest");

View File

@ -103,50 +103,103 @@ public class UtilTest {
@Test @Test
public void inferContentType_handlesHlsIsmUris() { public void inferContentType_handlesHlsIsmUris() {
assertThat(Util.inferContentType("http://a.b/c.ism/manifest(format=m3u8-aapl)")) assertThat(Util.inferContentType(Uri.parse("http://a.b/c.ism/manifest(format=m3u8-aapl)")))
.isEqualTo(C.TYPE_HLS); .isEqualTo(C.TYPE_HLS);
assertThat(Util.inferContentType("http://a.b/c.ism/manifest(format=m3u8-aapl,quality=hd)")) assertThat(
Util.inferContentType(
Uri.parse("http://a.b/c.ism/manifest(format=m3u8-aapl,quality=hd)")))
.isEqualTo(C.TYPE_HLS); .isEqualTo(C.TYPE_HLS);
assertThat(Util.inferContentType("http://a.b/c.ism/manifest(quality=hd,format=m3u8-aapl)")) assertThat(
Util.inferContentType(
Uri.parse("http://a.b/c.ism/manifest(quality=hd,format=m3u8-aapl)")))
.isEqualTo(C.TYPE_HLS); .isEqualTo(C.TYPE_HLS);
} }
@Test @Test
public void inferContentType_handlesHlsIsmV3Uris() { public void inferContentType_handlesHlsIsmV3Uris() {
assertThat(Util.inferContentType("http://a.b/c.ism/manifest(format=m3u8-aapl-v3)")) assertThat(Util.inferContentType(Uri.parse("http://a.b/c.ism/manifest(format=m3u8-aapl-v3)")))
.isEqualTo(C.TYPE_HLS); .isEqualTo(C.TYPE_HLS);
assertThat(Util.inferContentType("http://a.b/c.ism/manifest(format=m3u8-aapl-v3,quality=hd)")) assertThat(
Util.inferContentType(
Uri.parse("http://a.b/c.ism/manifest(format=m3u8-aapl-v3,quality=hd)")))
.isEqualTo(C.TYPE_HLS); .isEqualTo(C.TYPE_HLS);
assertThat(Util.inferContentType("http://a.b/c.ism/manifest(quality=hd,format=m3u8-aapl-v3)")) assertThat(
Util.inferContentType(
Uri.parse("http://a.b/c.ism/manifest(quality=hd,format=m3u8-aapl-v3)")))
.isEqualTo(C.TYPE_HLS); .isEqualTo(C.TYPE_HLS);
} }
@Test @Test
public void inferContentType_handlesDashIsmUris() { public void inferContentType_handlesDashIsmUris() {
assertThat(Util.inferContentType("http://a.b/c.isml/manifest(format=mpd-time-csf)")) assertThat(Util.inferContentType(Uri.parse("http://a.b/c.isml/manifest(format=mpd-time-csf)")))
.isEqualTo(C.TYPE_DASH); .isEqualTo(C.TYPE_DASH);
assertThat(Util.inferContentType("http://a.b/c.isml/manifest(format=mpd-time-csf,quality=hd)")) assertThat(
Util.inferContentType(
Uri.parse("http://a.b/c.isml/manifest(format=mpd-time-csf,quality=hd)")))
.isEqualTo(C.TYPE_DASH); .isEqualTo(C.TYPE_DASH);
assertThat(Util.inferContentType("http://a.b/c.isml/manifest(quality=hd,format=mpd-time-csf)")) assertThat(
Util.inferContentType(
Uri.parse("http://a.b/c.isml/manifest(quality=hd,format=mpd-time-csf)")))
.isEqualTo(C.TYPE_DASH); .isEqualTo(C.TYPE_DASH);
} }
@Test @Test
public void inferContentType_handlesSmoothStreamingIsmUris() { public void inferContentType_handlesSmoothStreamingIsmUris() {
assertThat(Util.inferContentType("http://a.b/c.ism")).isEqualTo(C.TYPE_SS); assertThat(Util.inferContentType(Uri.parse("http://a.b/c.ism"))).isEqualTo(C.TYPE_SS);
assertThat(Util.inferContentType("http://a.b/c.isml")).isEqualTo(C.TYPE_SS); assertThat(Util.inferContentType(Uri.parse("http://a.b/c.isml"))).isEqualTo(C.TYPE_SS);
assertThat(Util.inferContentType("http://a.b/c.ism/")).isEqualTo(C.TYPE_SS); assertThat(Util.inferContentType(Uri.parse("http://a.b/c.ism/"))).isEqualTo(C.TYPE_SS);
assertThat(Util.inferContentType("http://a.b/c.isml/")).isEqualTo(C.TYPE_SS); assertThat(Util.inferContentType(Uri.parse("http://a.b/c.isml/"))).isEqualTo(C.TYPE_SS);
assertThat(Util.inferContentType("http://a.b/c.ism/Manifest")).isEqualTo(C.TYPE_SS); assertThat(Util.inferContentType(Uri.parse("http://a.b/c.ism/Manifest"))).isEqualTo(C.TYPE_SS);
assertThat(Util.inferContentType("http://a.b/c.isml/manifest")).isEqualTo(C.TYPE_SS); assertThat(Util.inferContentType(Uri.parse("http://a.b/c.isml/manifest"))).isEqualTo(C.TYPE_SS);
assertThat(Util.inferContentType("http://a.b/c.isml/manifest(filter=x)")).isEqualTo(C.TYPE_SS); assertThat(Util.inferContentType(Uri.parse("http://a.b/c.isml/manifest(filter=x)")))
assertThat(Util.inferContentType("http://a.b/c.isml/manifest_hd")).isEqualTo(C.TYPE_SS); .isEqualTo(C.TYPE_SS);
assertThat(Util.inferContentType(Uri.parse("http://a.b/c.isml/manifest_hd")))
.isEqualTo(C.TYPE_SS);
} }
@Test @Test
public void inferContentType_handlesOtherIsmUris() { public void inferContentType_handlesOtherIsmUris() {
assertThat(Util.inferContentType("http://a.b/c.ism/video.mp4")).isEqualTo(C.TYPE_OTHER); assertThat(Util.inferContentType(Uri.parse("http://a.b/c.ism/video.mp4")))
assertThat(Util.inferContentType("http://a.b/c.ism/prefix-manifest")).isEqualTo(C.TYPE_OTHER); .isEqualTo(C.TYPE_OTHER);
assertThat(Util.inferContentType(Uri.parse("http://a.b/c.ism/prefix-manifest")))
.isEqualTo(C.TYPE_OTHER);
}
/**
* Test that the deprecated {@link Util#inferContentType(String)} works when passed only a file
* extension and the leading dot.
*/
@SuppressWarnings("deprecation")
@Test
public void inferContentType_extensionAsPath() {
assertThat(Util.inferContentType(".m3u8")).isEqualTo(C.TYPE_HLS);
assertThat(Util.inferContentType(".mpd")).isEqualTo(C.TYPE_DASH);
assertThat(Util.inferContentType(".mp4")).isEqualTo(C.TYPE_OTHER);
}
// Testing deprecated method.
@SuppressWarnings("deprecation")
@Test
public void inferContentType_extensionOverride() {
assertThat(
Util.inferContentType(
Uri.parse("file:///path/to/something.mpd"), /* overrideExtension= */ null))
.isEqualTo(C.TYPE_DASH);
assertThat(
Util.inferContentType(
Uri.parse("file:///path/to/something.mpd"), /* overrideExtension= */ ""))
.isEqualTo(C.TYPE_DASH);
assertThat(
Util.inferContentType(
Uri.parse("file:///path/to/something.mpd"), /* overrideExtension= */ "m3u8"))
.isEqualTo(C.TYPE_HLS);
}
@Test
public void inferContentTypeForExtension() {
assertThat(Util.inferContentTypeForExtension("m3u8")).isEqualTo(C.TYPE_HLS);
assertThat(Util.inferContentTypeForExtension("mpd")).isEqualTo(C.TYPE_DASH);
assertThat(Util.inferContentTypeForExtension("mp4")).isEqualTo(C.TYPE_OTHER);
} }
@Test @Test