diff --git a/library/core/src/main/java/com/google/android/exoplayer2/drm/HttpMediaDrmCallback.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/HttpMediaDrmCallback.java index 655a8d92d8..7ab90b023e 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/drm/HttpMediaDrmCallback.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/drm/HttpMediaDrmCallback.java @@ -24,9 +24,9 @@ import com.google.android.exoplayer2.upstream.DataSourceInputStream; import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.upstream.HttpDataSource; import com.google.android.exoplayer2.upstream.HttpDataSource.InvalidResponseCodeException; +import com.google.android.exoplayer2.upstream.StatsDataSource; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Util; -import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -104,14 +104,19 @@ public final class HttpMediaDrmCallback implements MediaDrmCallback { } @Override - public byte[] executeProvisionRequest(UUID uuid, ProvisionRequest request) throws IOException { + public byte[] executeProvisionRequest(UUID uuid, ProvisionRequest request) + throws MediaDrmCallbackException { String url = request.getDefaultUrl() + "&signedRequest=" + Util.fromUtf8Bytes(request.getData()); - return executePost(dataSourceFactory, url, /* httpBody= */ null, /* requestProperties= */ null); + return executePost( + dataSourceFactory, + url, + /* httpBody= */ null, + /* requestProperties= */ Collections.emptyMap()); } @Override - public byte[] executeKeyRequest(UUID uuid, KeyRequest request) throws Exception { + public byte[] executeKeyRequest(UUID uuid, KeyRequest request) throws MediaDrmCallbackException { String url = request.getLicenseServerUrl(); if (forceDefaultLicenseUrl || TextUtils.isEmpty(url)) { url = defaultLicenseUrl; @@ -136,41 +141,56 @@ public final class HttpMediaDrmCallback implements MediaDrmCallback { HttpDataSource.Factory dataSourceFactory, String url, @Nullable byte[] httpBody, - @Nullable Map requestProperties) - throws IOException { - HttpDataSource dataSource = dataSourceFactory.createDataSource(); + Map requestProperties) + throws MediaDrmCallbackException { + StatsDataSource dataSource = new StatsDataSource(dataSourceFactory.createDataSource()); int manualRedirectCount = 0; - while (true) { - DataSpec dataSpec = - new DataSpec.Builder() - .setUri(url) - .setHttpRequestHeaders( - requestProperties != null ? requestProperties : Collections.emptyMap()) - .setHttpMethod(DataSpec.HTTP_METHOD_POST) - .setHttpBody(httpBody) - .setFlags(DataSpec.FLAG_ALLOW_GZIP) - .build(); - DataSourceInputStream inputStream = new DataSourceInputStream(dataSource, dataSpec); - try { - return Util.toByteArray(inputStream); - } catch (InvalidResponseCodeException e) { - // For POST requests, the underlying network stack will not normally follow 307 or 308 - // redirects automatically. Do so manually here. - boolean manuallyRedirect = - (e.responseCode == 307 || e.responseCode == 308) - && manualRedirectCount++ < MAX_MANUAL_REDIRECTS; - @Nullable String redirectUrl = manuallyRedirect ? getRedirectUrl(e) : null; - if (redirectUrl == null) { - throw e; + 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 Util.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); } - url = redirectUrl; - } finally { - Util.closeQuietly(inputStream); } + } catch (Exception e) { + throw new MediaDrmCallbackException( + originalDataSpec, + Assertions.checkNotNull(dataSource.getLastOpenedUri()), + dataSource.getResponseHeaders(), + dataSource.getBytesRead(), + /* cause= */ e); } } - private static @Nullable String getRedirectUrl(InvalidResponseCodeException exception) { + @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"); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/drm/LocalMediaDrmCallback.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/LocalMediaDrmCallback.java index 7b9aeca30a..d141b6c4c1 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/drm/LocalMediaDrmCallback.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/drm/LocalMediaDrmCallback.java @@ -18,7 +18,6 @@ package com.google.android.exoplayer2.drm; import com.google.android.exoplayer2.drm.ExoMediaDrm.KeyRequest; import com.google.android.exoplayer2.drm.ExoMediaDrm.ProvisionRequest; import com.google.android.exoplayer2.util.Assertions; -import java.io.IOException; import java.util.UUID; /** @@ -39,12 +38,12 @@ public final class LocalMediaDrmCallback implements MediaDrmCallback { } @Override - public byte[] executeProvisionRequest(UUID uuid, ProvisionRequest request) throws IOException { + public byte[] executeProvisionRequest(UUID uuid, ProvisionRequest request) { throw new UnsupportedOperationException(); } @Override - public byte[] executeKeyRequest(UUID uuid, KeyRequest request) throws Exception { + public byte[] executeKeyRequest(UUID uuid, KeyRequest request) { return keyResponse; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/drm/MediaDrmCallback.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/MediaDrmCallback.java index 5b0ed04f81..14b817e713 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/drm/MediaDrmCallback.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/drm/MediaDrmCallback.java @@ -30,9 +30,10 @@ public interface MediaDrmCallback { * @param uuid The UUID of the content protection scheme. * @param request The request. * @return The response data. - * @throws Exception If an error occurred executing the request. + * @throws MediaDrmCallbackException If an error occurred executing the request. */ - byte[] executeProvisionRequest(UUID uuid, ProvisionRequest request) throws Exception; + byte[] executeProvisionRequest(UUID uuid, ProvisionRequest request) + throws MediaDrmCallbackException; /** * Executes a key request. @@ -40,7 +41,7 @@ public interface MediaDrmCallback { * @param uuid The UUID of the content protection scheme. * @param request The request. * @return The response data. - * @throws Exception If an error occurred executing the request. + * @throws MediaDrmCallbackException If an error occurred executing the request. */ - byte[] executeKeyRequest(UUID uuid, KeyRequest request) throws Exception; + byte[] executeKeyRequest(UUID uuid, KeyRequest request) throws MediaDrmCallbackException; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/drm/MediaDrmCallbackException.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/MediaDrmCallbackException.java new file mode 100644 index 0000000000..37b2e03504 --- /dev/null +++ b/library/core/src/main/java/com/google/android/exoplayer2/drm/MediaDrmCallbackException.java @@ -0,0 +1,63 @@ +/* + * Copyright 2020 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.drm; + +import android.net.Uri; +import com.google.android.exoplayer2.upstream.DataSpec; +import java.io.IOException; +import java.util.List; +import java.util.Map; + +/** + * Thrown when an error occurs while executing a DRM {@link MediaDrmCallback#executeKeyRequest key} + * or {@link MediaDrmCallback#executeProvisionRequest provisioning} request. + */ +public final class MediaDrmCallbackException extends IOException { + + /** The {@link DataSpec} associated with the request. */ + public final DataSpec dataSpec; + /** + * The {@link Uri} after redirections, or {@link #dataSpec dataSpec.uri} if no redirection + * occurred. + */ + public final Uri uriAfterRedirects; + /** The HTTP request headers included in the response. */ + public final Map> responseHeaders; + /** The number of bytes obtained from the server. */ + public final long bytesLoaded; + + /** + * Creates a new instance with the given values. + * + * @param dataSpec See {@link #dataSpec}. + * @param uriAfterRedirects See {@link #uriAfterRedirects}. + * @param responseHeaders See {@link #responseHeaders}. + * @param bytesLoaded See {@link #bytesLoaded}. + * @param cause The cause of the exception. + */ + public MediaDrmCallbackException( + DataSpec dataSpec, + Uri uriAfterRedirects, + Map> responseHeaders, + long bytesLoaded, + Throwable cause) { + super(cause); + this.dataSpec = dataSpec; + this.uriAfterRedirects = uriAfterRedirects; + this.responseHeaders = responseHeaders; + this.bytesLoaded = bytesLoaded; + } +}