From 1c7c6fb90d8d18bd47001c196873df8a61225f82 Mon Sep 17 00:00:00 2001 From: olly Date: Tue, 11 Aug 2020 16:27:02 +0100 Subject: [PATCH] Increase flexibility of ISM URL handling PiperOrigin-RevId: 326025335 --- RELEASENOTES.md | 1 + .../android/exoplayer2/util/UtilTest.java | 29 ++++++++++++++- .../google/android/exoplayer2/util/Util.java | 26 ++++++++++++-- .../source/smoothstreaming/SsMediaSource.java | 4 +-- .../smoothstreaming/manifest/SsUtil.java | 35 ------------------- .../smoothstreaming/offline/SsDownloader.java | 4 +-- 6 files changed, 56 insertions(+), 43 deletions(-) delete mode 100644 library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsUtil.java diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 05300b47b9..d7e09e4864 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -16,6 +16,7 @@ boxes. * FLV: Ignore `SCRIPTDATA` segments with invalid name types, rather than 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 transition to `STATE_ENDED` when using video tunneling mode ([#7647](https://github.com/google/ExoPlayer/issues/7647)). diff --git a/library/common/src/test/java/com/google/android/exoplayer2/util/UtilTest.java b/library/common/src/test/java/com/google/android/exoplayer2/util/UtilTest.java index 0bf5028282..162dcbae9d 100644 --- a/library/common/src/test/java/com/google/android/exoplayer2/util/UtilTest.java +++ b/library/common/src/test/java/com/google/android/exoplayer2/util/UtilTest.java @@ -26,6 +26,7 @@ import static com.google.common.truth.Truth.assertThat; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; +import android.net.Uri; import android.text.SpannableString; import android.text.Spanned; import android.text.style.StrikethroughSpan; @@ -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.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_hd")).isEqualTo(C.TYPE_SS); } @Test 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/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 diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/Util.java b/library/core/src/main/java/com/google/android/exoplayer2/util/Util.java index 58fd42d3d0..b1bddc13b8 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/Util.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/Util.java @@ -136,8 +136,7 @@ public final class Util { 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. - private static final Pattern ISM_URL_PATTERN = - Pattern.compile(".*\\.ism(?:l)?(?:/(?:manifest(?:\\((.+)\\))?)?)?"); + private static final Pattern ISM_URL_PATTERN = Pattern.compile(".*\\.isml?(?:/(manifest(.*))?)?"); private static final String ISM_HLS_FORMAT_EXTENSION = "format=m3u8-aapl"; private static final String ISM_DASH_FORMAT_EXTENSION = "format=mpd-time-csf"; @@ -1608,7 +1607,7 @@ public final class Util { } Matcher ismMatcher = ISM_URL_PATTERN.matcher(fileName); if (ismMatcher.matches()) { - @Nullable String extensions = ismMatcher.group(1); + @Nullable String extensions = ismMatcher.group(2); if (extensions != null) { if (extensions.contains(ISM_DASH_FORMAT_EXTENSION)) { return C.TYPE_DASH; @@ -1621,6 +1620,27 @@ public final class Util { return C.TYPE_OTHER; } + /** + * 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. * diff --git a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java index 89dd8039ef..02df8f28ff 100644 --- a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java +++ b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/SsMediaSource.java @@ -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.StreamElement; 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.DataSource; import com.google.android.exoplayer2.upstream.DefaultLoadErrorHandlingPolicy; @@ -50,6 +49,7 @@ import com.google.android.exoplayer2.upstream.LoaderErrorThrower; import com.google.android.exoplayer2.upstream.ParsingLoadable; import com.google.android.exoplayer2.upstream.TransferListener; import com.google.android.exoplayer2.util.Assertions; +import com.google.android.exoplayer2.util.Util; import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -531,7 +531,7 @@ public final class SsMediaSource extends BaseMediaSource @Nullable Object tag) { Assertions.checkState(manifest == null || !manifest.isLive); this.manifest = manifest; - this.manifestUri = manifestUri == null ? null : SsUtil.fixManifestUri(manifestUri); + this.manifestUri = manifestUri == null ? null : Util.fixSmoothStreamingIsmManifestUri(manifestUri); this.manifestDataSourceFactory = manifestDataSourceFactory; this.manifestParser = manifestParser; this.chunkSourceFactory = chunkSourceFactory; diff --git a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsUtil.java b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsUtil.java deleted file mode 100644 index b54b2abc74..0000000000 --- a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/manifest/SsUtil.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * 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() {} -} diff --git a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/offline/SsDownloader.java b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/offline/SsDownloader.java index 1331fe4617..8808954135 100644 --- a/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/offline/SsDownloader.java +++ b/library/smoothstreaming/src/main/java/com/google/android/exoplayer2/source/smoothstreaming/offline/SsDownloader.java @@ -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.StreamElement; 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.DataSpec; import com.google.android.exoplayer2.upstream.ParsingLoadable; +import com.google.android.exoplayer2.util.Util; import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -64,7 +64,7 @@ public final class SsDownloader extends SegmentDownloader { */ public SsDownloader( Uri manifestUri, List streamKeys, DownloaderConstructorHelper constructorHelper) { - super(SsUtil.fixManifestUri(manifestUri), streamKeys, constructorHelper); + super(Util.fixSmoothStreamingIsmManifestUri(manifestUri), streamKeys, constructorHelper); } @Override