From 4ee34cc00ebcb84c36331c30ad92a3fa068b84ce Mon Sep 17 00:00:00 2001 From: Colin Kho Date: Tue, 27 Aug 2024 14:40:25 -0700 Subject: [PATCH 1/3] Refactor HttpMediaDrmCallback's executePost into a shared utility method within DrmUtil --- .../media3/exoplayer/drm/DrmUtil.java | 85 +++++++++++++++++++ .../exoplayer/drm/HttpMediaDrmCallback.java | 82 ++---------------- 2 files changed, 94 insertions(+), 73 deletions(-) diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/drm/DrmUtil.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/drm/DrmUtil.java index be88fe1dda..6e1ac9c0d6 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/drm/DrmUtil.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/drm/DrmUtil.java @@ -31,16 +31,26 @@ import androidx.annotation.IntDef; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import androidx.media3.common.PlaybackException; +import androidx.media3.common.util.Assertions; import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.Util; +import androidx.media3.datasource.DataSource; +import androidx.media3.datasource.DataSourceInputStream; +import androidx.media3.datasource.DataSpec; +import androidx.media3.datasource.HttpDataSource; +import androidx.media3.datasource.StatsDataSource; +import com.google.common.io.ByteStreams; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import java.util.List; +import java.util.Map; /** DRM-related utility methods. */ @UnstableApi public final class DrmUtil { + private static final int MAX_MANUAL_REDIRECTS = 5; /** Identifies the operation which caused a DRM-related error. */ // @Target list includes both 'default' targets and TYPE_USE, to ensure backwards compatibility @@ -131,6 +141,81 @@ public final class DrmUtil { && e.getMessage().contains("Landroid/media/ResourceBusyException;.("); } + /** + * Executes a Http Post Request to the supplied {@link StatsDataSource} with retry handling and + * returns the entire response in a byte buffer. + * + * @param dataSource A {@link DataSource} that is usually be HTTP-based + * @param url The requesting url + * @param httpBody Request Payload + * @param requestProperties Http Header Request Properties + * @return a byte array that holds the response payload + * @throws MediaDrmCallbackException if an exception was encountered during the download + */ + public static byte[] executePost( + DataSource dataSource, + String url, + @Nullable byte[] httpBody, + Map requestProperties) + throws MediaDrmCallbackException { + StatsDataSource statsDataSource = new StatsDataSource(dataSource); + int manualRedirectCount = 0; + DataSpec dataSpec = + new DataSpec.Builder() + .setUri(url) + .setHttpRequestHeaders(requestProperties) + .setHttpMethod(DataSpec.HTTP_METHOD_POST) + .setHttpBody(httpBody) + .setFlags(DataSpec.FLAG_ALLOW_GZIP) + .build(); + DataSpec originalDataSpec = dataSpec; + try { + while (true) { + DataSourceInputStream inputStream = new DataSourceInputStream(statsDataSource, dataSpec); + try { + return ByteStreams.toByteArray(inputStream); + } catch (HttpDataSource.InvalidResponseCodeException e) { + @Nullable String redirectUrl = getRedirectUrl(e, manualRedirectCount); + if (redirectUrl == null) { + throw e; + } + manualRedirectCount++; + dataSpec = dataSpec.buildUpon().setUri(redirectUrl).build(); + } finally { + Util.closeQuietly(inputStream); + } + } + } catch (Exception e) { + throw new MediaDrmCallbackException( + originalDataSpec, + Assertions.checkNotNull(statsDataSource.getLastOpenedUri()), + statsDataSource.getResponseHeaders(), + statsDataSource.getBytesRead(), + /* cause= */ e); + } + } + + @Nullable + private static String getRedirectUrl( + HttpDataSource.InvalidResponseCodeException exception, int manualRedirectCount) { + // For POST requests, the underlying network stack will not normally follow 307 or 308 + // redirects automatically. Do so manually here. + boolean manuallyRedirect = + (exception.responseCode == 307 || exception.responseCode == 308) + && manualRedirectCount < MAX_MANUAL_REDIRECTS; + if (!manuallyRedirect) { + return null; + } + Map> headerFields = exception.headerFields; + if (headerFields != null) { + @Nullable List locationHeaders = headerFields.get("Location"); + if (locationHeaders != null && !locationHeaders.isEmpty()) { + return locationHeaders.get(0); + } + } + return null; + } + @RequiresApi(23) private static final class Api23 { diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/drm/HttpMediaDrmCallback.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/drm/HttpMediaDrmCallback.java index 8973607d02..76130aabb9 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/drm/HttpMediaDrmCallback.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/drm/HttpMediaDrmCallback.java @@ -15,6 +15,8 @@ */ package androidx.media3.exoplayer.drm; +import static androidx.media3.exoplayer.drm.DrmUtil.executePost; + import android.net.Uri; import android.text.TextUtils; import androidx.annotation.Nullable; @@ -23,26 +25,19 @@ import androidx.media3.common.util.Assertions; import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.Util; import androidx.media3.datasource.DataSource; -import androidx.media3.datasource.DataSourceInputStream; import androidx.media3.datasource.DataSpec; -import androidx.media3.datasource.HttpDataSource.InvalidResponseCodeException; import androidx.media3.datasource.StatsDataSource; import androidx.media3.exoplayer.drm.ExoMediaDrm.KeyRequest; import androidx.media3.exoplayer.drm.ExoMediaDrm.ProvisionRequest; import com.google.common.collect.ImmutableMap; -import com.google.common.io.ByteStreams; import java.util.Collections; import java.util.HashMap; -import java.util.List; import java.util.Map; import java.util.UUID; /** A {@link MediaDrmCallback} that makes requests using {@link DataSource} instances. */ @UnstableApi public final class HttpMediaDrmCallback implements MediaDrmCallback { - - private static final int MAX_MANUAL_REDIRECTS = 5; - private final DataSource.Factory dataSourceFactory; @Nullable private final String defaultLicenseUrl; private final boolean forceDefaultLicenseUrl; @@ -124,7 +119,7 @@ public final class HttpMediaDrmCallback implements MediaDrmCallback { String url = request.getDefaultUrl() + "&signedRequest=" + Util.fromUtf8Bytes(request.getData()); return executePost( - dataSourceFactory, + dataSourceFactory.createDataSource(), url, /* httpBody= */ null, /* requestProperties= */ Collections.emptyMap()); @@ -159,70 +154,11 @@ public final class HttpMediaDrmCallback implements MediaDrmCallback { synchronized (keyRequestProperties) { requestProperties.putAll(keyRequestProperties); } - return executePost(dataSourceFactory, url, request.getData(), requestProperties); - } - - private static byte[] executePost( - DataSource.Factory dataSourceFactory, - String url, - @Nullable byte[] httpBody, - Map requestProperties) - throws MediaDrmCallbackException { - StatsDataSource dataSource = new StatsDataSource(dataSourceFactory.createDataSource()); - int manualRedirectCount = 0; - DataSpec dataSpec = - new DataSpec.Builder() - .setUri(url) - .setHttpRequestHeaders(requestProperties) - .setHttpMethod(DataSpec.HTTP_METHOD_POST) - .setHttpBody(httpBody) - .setFlags(DataSpec.FLAG_ALLOW_GZIP) - .build(); - DataSpec originalDataSpec = dataSpec; - try { - while (true) { - DataSourceInputStream inputStream = new DataSourceInputStream(dataSource, dataSpec); - try { - return ByteStreams.toByteArray(inputStream); - } catch (InvalidResponseCodeException e) { - @Nullable String redirectUrl = getRedirectUrl(e, manualRedirectCount); - if (redirectUrl == null) { - throw e; - } - manualRedirectCount++; - dataSpec = dataSpec.buildUpon().setUri(redirectUrl).build(); - } finally { - Util.closeQuietly(inputStream); - } - } - } catch (Exception e) { - throw new MediaDrmCallbackException( - originalDataSpec, - Assertions.checkNotNull(dataSource.getLastOpenedUri()), - dataSource.getResponseHeaders(), - dataSource.getBytesRead(), - /* cause= */ e); - } - } - - @Nullable - private static String getRedirectUrl( - InvalidResponseCodeException exception, int manualRedirectCount) { - // For POST requests, the underlying network stack will not normally follow 307 or 308 - // redirects automatically. Do so manually here. - boolean manuallyRedirect = - (exception.responseCode == 307 || exception.responseCode == 308) - && manualRedirectCount < MAX_MANUAL_REDIRECTS; - if (!manuallyRedirect) { - return null; - } - Map> headerFields = exception.headerFields; - if (headerFields != null) { - @Nullable List locationHeaders = headerFields.get("Location"); - if (locationHeaders != null && !locationHeaders.isEmpty()) { - return locationHeaders.get(0); - } - } - return null; + return executePost( + dataSourceFactory.createDataSource(), + url, + request.getData(), + requestProperties + ); } } From a509033a5ce87096be1c59f99da16b00ff7eabc9 Mon Sep 17 00:00:00 2001 From: Colin Kho Date: Tue, 27 Aug 2024 14:43:21 -0700 Subject: [PATCH 2/3] Correct grammar and format javadoc on DrmUtil.executePost --- .../main/java/androidx/media3/exoplayer/drm/DrmUtil.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/drm/DrmUtil.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/drm/DrmUtil.java index 6e1ac9c0d6..8677670ce5 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/drm/DrmUtil.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/drm/DrmUtil.java @@ -145,11 +145,11 @@ public final class DrmUtil { * Executes a Http Post Request to the supplied {@link StatsDataSource} with retry handling and * returns the entire response in a byte buffer. * - * @param dataSource A {@link DataSource} that is usually be HTTP-based + * @param dataSource A {@link DataSource} that is usually HTTP-based * @param url The requesting url - * @param httpBody Request Payload - * @param requestProperties Http Header Request Properties - * @return a byte array that holds the response payload + * @param httpBody The http request payload + * @param requestProperties A keyed map of http header request properties + * @return A byte array that holds the response payload * @throws MediaDrmCallbackException if an exception was encountered during the download */ public static byte[] executePost( From 41a56daab780fcb55a8a5bc0dabcab74728483b7 Mon Sep 17 00:00:00 2001 From: tonihei Date: Thu, 29 Aug 2024 13:19:00 +0100 Subject: [PATCH 3/3] Code formatting --- .../androidx/media3/exoplayer/drm/DrmUtil.java | 18 ++++++++++-------- .../exoplayer/drm/HttpMediaDrmCallback.java | 6 ++---- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/drm/DrmUtil.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/drm/DrmUtil.java index 8677670ce5..a1c741e541 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/drm/DrmUtil.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/drm/DrmUtil.java @@ -142,15 +142,17 @@ public final class DrmUtil { } /** - * Executes a Http Post Request to the supplied {@link StatsDataSource} with retry handling and - * returns the entire response in a byte buffer. + * Executes a HTTP POST request with retry handling and returns the entire response in a byte + * buffer. * - * @param dataSource A {@link DataSource} that is usually HTTP-based - * @param url The requesting url - * @param httpBody The http request payload - * @param requestProperties A keyed map of http header request properties - * @return A byte array that holds the response payload - * @throws MediaDrmCallbackException if an exception was encountered during the download + *

Note that this method is executing the request synchronously and blocks until finished. + * + * @param dataSource A {@link DataSource}. + * @param url The requested URL. + * @param httpBody The HTTP request payload. + * @param requestProperties A keyed map of HTTP header request properties. + * @return A byte array that holds the response payload. + * @throws MediaDrmCallbackException if an exception was encountered during the download. */ public static byte[] executePost( DataSource dataSource, diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/drm/HttpMediaDrmCallback.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/drm/HttpMediaDrmCallback.java index 76130aabb9..37f76cd242 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/drm/HttpMediaDrmCallback.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/drm/HttpMediaDrmCallback.java @@ -26,7 +26,6 @@ import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.Util; import androidx.media3.datasource.DataSource; import androidx.media3.datasource.DataSpec; -import androidx.media3.datasource.StatsDataSource; import androidx.media3.exoplayer.drm.ExoMediaDrm.KeyRequest; import androidx.media3.exoplayer.drm.ExoMediaDrm.ProvisionRequest; import com.google.common.collect.ImmutableMap; @@ -157,8 +156,7 @@ public final class HttpMediaDrmCallback implements MediaDrmCallback { return executePost( dataSourceFactory.createDataSource(), url, - request.getData(), - requestProperties - ); + /* httpBody= */ request.getData(), + requestProperties); } }