From 4ee34cc00ebcb84c36331c30ad92a3fa068b84ce Mon Sep 17 00:00:00 2001 From: Colin Kho Date: Tue, 27 Aug 2024 14:40:25 -0700 Subject: [PATCH] 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 + ); } }