Commit 1c7c6fb9 by olly Committed by Oliver Woodman

Increase flexibility of ISM URL handling

PiperOrigin-RevId: 326025335
parent 590e81e5
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
boxes. boxes.
* FLV: Ignore `SCRIPTDATA` segments with invalid name types, rather than * FLV: Ignore `SCRIPTDATA` segments with invalid name types, rather than
failing playback ([#7675](https://github.com/google/ExoPlayer/issues/7675)). failing playback ([#7675](https://github.com/google/ExoPlayer/issues/7675)).
* Better infer content type for `.ism` and `.isml` streaming URLs.
* Workaround an issue on Broadcom based devices where playbacks would not * Workaround an issue on Broadcom based devices where playbacks would not
transition to `STATE_ENDED` when using video tunneling mode transition to `STATE_ENDED` when using video tunneling mode
([#7647](https://github.com/google/ExoPlayer/issues/7647)). ([#7647](https://github.com/google/ExoPlayer/issues/7647)).
......
...@@ -26,6 +26,7 @@ import static com.google.common.truth.Truth.assertThat; ...@@ -26,6 +26,7 @@ import static com.google.common.truth.Truth.assertThat;
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteOpenHelper;
import android.net.Uri;
import android.text.SpannableString; import android.text.SpannableString;
import android.text.Spanned; import android.text.Spanned;
import android.text.style.StrikethroughSpan; import android.text.style.StrikethroughSpan;
...@@ -127,13 +128,39 @@ public class UtilTest { ...@@ -127,13 +128,39 @@ public class UtilTest {
assertThat(Util.inferContentType("http://a.b/c.ism/Manifest")).isEqualTo(C.TYPE_SS); assertThat(Util.inferContentType("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("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("http://a.b/c.isml/manifest(filter=x)")).isEqualTo(C.TYPE_SS);
assertThat(Util.inferContentType("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("http://a.b/c.ism/video.mp4")).isEqualTo(C.TYPE_OTHER);
assertThat(Util.inferContentType("http://a.b/c.ism/prefix-manifest")).isEqualTo(C.TYPE_OTHER); assertThat(Util.inferContentType("http://a.b/c.ism/prefix-manifest")).isEqualTo(C.TYPE_OTHER);
assertThat(Util.inferContentType("http://a.b/c.ism/manifest-suffix")).isEqualTo(C.TYPE_OTHER); }
@Test
public void fixSmoothStreamingIsmManifestUri_addsManifestSuffix() {
assertThat(Util.fixSmoothStreamingIsmManifestUri(Uri.parse("http://a.b/c.ism")))
.isEqualTo(Uri.parse("http://a.b/c.ism/Manifest"));
assertThat(Util.fixSmoothStreamingIsmManifestUri(Uri.parse("http://a.b/c.isml")))
.isEqualTo(Uri.parse("http://a.b/c.isml/Manifest"));
assertThat(Util.fixSmoothStreamingIsmManifestUri(Uri.parse("http://a.b/c.ism/")))
.isEqualTo(Uri.parse("http://a.b/c.ism/Manifest"));
assertThat(Util.fixSmoothStreamingIsmManifestUri(Uri.parse("http://a.b/c.isml/")))
.isEqualTo(Uri.parse("http://a.b/c.isml/Manifest"));
}
@Test
public void fixSmoothStreamingIsmManifestUri_doesNotAlterManifestUri() {
assertThat(Util.fixSmoothStreamingIsmManifestUri(Uri.parse("http://a.b/c.ism/Manifest")))
.isEqualTo(Uri.parse("http://a.b/c.ism/Manifest"));
assertThat(Util.fixSmoothStreamingIsmManifestUri(Uri.parse("http://a.b/c.isml/Manifest")))
.isEqualTo(Uri.parse("http://a.b/c.isml/Manifest"));
assertThat(
Util.fixSmoothStreamingIsmManifestUri(Uri.parse("http://a.b/c.ism/Manifest(filter=x)")))
.isEqualTo(Uri.parse("http://a.b/c.ism/Manifest(filter=x)"));
assertThat(Util.fixSmoothStreamingIsmManifestUri(Uri.parse("http://a.b/c.ism/Manifest_hd")))
.isEqualTo(Uri.parse("http://a.b/c.ism/Manifest_hd"));
} }
@Test @Test
......
...@@ -136,8 +136,7 @@ public final class Util { ...@@ -136,8 +136,7 @@ public final class Util {
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 = private static final Pattern ISM_URL_PATTERN = Pattern.compile(".*\\.isml?(?:/(manifest(.*))?)?");
Pattern.compile(".*\\.ism(?:l)?(?:/(?:manifest(?:\\((.+)\\))?)?)?");
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";
...@@ -1608,7 +1607,7 @@ public final class Util { ...@@ -1608,7 +1607,7 @@ public final class Util {
} }
Matcher ismMatcher = ISM_URL_PATTERN.matcher(fileName); Matcher ismMatcher = ISM_URL_PATTERN.matcher(fileName);
if (ismMatcher.matches()) { if (ismMatcher.matches()) {
@Nullable String extensions = ismMatcher.group(1); @Nullable String extensions = ismMatcher.group(2);
if (extensions != null) { if (extensions != null) {
if (extensions.contains(ISM_DASH_FORMAT_EXTENSION)) { if (extensions.contains(ISM_DASH_FORMAT_EXTENSION)) {
return C.TYPE_DASH; return C.TYPE_DASH;
...@@ -1622,6 +1621,27 @@ public final class Util { ...@@ -1622,6 +1621,27 @@ public final class Util {
} }
/** /**
* If the provided URI is an ISM Presentation URI, returns the URI with "Manifest" appended to its
* path (i.e., the corresponding default manifest URI). Else returns the provided URI without
* modification. See [MS-SSTR] v20180912, section 2.2.1.
*
* @param uri The original URI.
* @return The fixed URI.
*/
public static Uri fixSmoothStreamingIsmManifestUri(Uri uri) {
@Nullable String path = toLowerInvariant(uri.getPath());
if (path == null) {
return uri;
}
Matcher ismMatcher = ISM_URL_PATTERN.matcher(path);
if (ismMatcher.matches() && ismMatcher.group(1) == null) {
// Add missing "Manifest" suffix.
return Uri.withAppendedPath(uri, "Manifest");
}
return uri;
}
/**
* Returns the specified millisecond time formatted as a string. * Returns the specified millisecond time formatted as a string.
* *
* @param builder The builder that {@code formatter} will write to. * @param builder The builder that {@code formatter} will write to.
......
...@@ -39,7 +39,6 @@ import com.google.android.exoplayer2.source.SinglePeriodTimeline; ...@@ -39,7 +39,6 @@ import com.google.android.exoplayer2.source.SinglePeriodTimeline;
import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest; import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest;
import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest.StreamElement; import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest.StreamElement;
import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifestParser; import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifestParser;
import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsUtil;
import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.upstream.Allocator;
import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultLoadErrorHandlingPolicy; import com.google.android.exoplayer2.upstream.DefaultLoadErrorHandlingPolicy;
...@@ -50,6 +49,7 @@ import com.google.android.exoplayer2.upstream.LoaderErrorThrower; ...@@ -50,6 +49,7 @@ import com.google.android.exoplayer2.upstream.LoaderErrorThrower;
import com.google.android.exoplayer2.upstream.ParsingLoadable; import com.google.android.exoplayer2.upstream.ParsingLoadable;
import com.google.android.exoplayer2.upstream.TransferListener; import com.google.android.exoplayer2.upstream.TransferListener;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Util;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
...@@ -531,7 +531,7 @@ public final class SsMediaSource extends BaseMediaSource ...@@ -531,7 +531,7 @@ public final class SsMediaSource extends BaseMediaSource
@Nullable Object tag) { @Nullable Object tag) {
Assertions.checkState(manifest == null || !manifest.isLive); Assertions.checkState(manifest == null || !manifest.isLive);
this.manifest = manifest; this.manifest = manifest;
this.manifestUri = manifestUri == null ? null : SsUtil.fixManifestUri(manifestUri); this.manifestUri = manifestUri == null ? null : Util.fixSmoothStreamingIsmManifestUri(manifestUri);
this.manifestDataSourceFactory = manifestDataSourceFactory; this.manifestDataSourceFactory = manifestDataSourceFactory;
this.manifestParser = manifestParser; this.manifestParser = manifestParser;
this.chunkSourceFactory = chunkSourceFactory; this.chunkSourceFactory = chunkSourceFactory;
......
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.source.smoothstreaming.manifest;
import android.net.Uri;
import com.google.android.exoplayer2.util.Util;
/** SmoothStreaming related utility methods. */
public final class SsUtil {
/** Returns a fixed SmoothStreaming client manifest {@link Uri}. */
public static Uri fixManifestUri(Uri manifestUri) {
String lastPathSegment = manifestUri.getLastPathSegment();
if (lastPathSegment != null
&& Util.toLowerInvariant(lastPathSegment).matches("manifest(\\(.+\\))?")) {
return manifestUri;
}
return Uri.withAppendedPath(manifestUri, "Manifest");
}
private SsUtil() {}
}
...@@ -23,10 +23,10 @@ import com.google.android.exoplayer2.offline.StreamKey; ...@@ -23,10 +23,10 @@ import com.google.android.exoplayer2.offline.StreamKey;
import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest; import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest;
import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest.StreamElement; import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest.StreamElement;
import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifestParser; import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifestParser;
import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsUtil;
import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.upstream.ParsingLoadable; import com.google.android.exoplayer2.upstream.ParsingLoadable;
import com.google.android.exoplayer2.util.Util;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
...@@ -64,7 +64,7 @@ public final class SsDownloader extends SegmentDownloader<SsManifest> { ...@@ -64,7 +64,7 @@ public final class SsDownloader extends SegmentDownloader<SsManifest> {
*/ */
public SsDownloader( public SsDownloader(
Uri manifestUri, List<StreamKey> streamKeys, DownloaderConstructorHelper constructorHelper) { Uri manifestUri, List<StreamKey> streamKeys, DownloaderConstructorHelper constructorHelper) {
super(SsUtil.fixManifestUri(manifestUri), streamKeys, constructorHelper); super(Util.fixSmoothStreamingIsmManifestUri(manifestUri), streamKeys, constructorHelper);
} }
@Override @Override
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment