diff options
Diffstat (limited to 'mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/drm/HttpMediaDrmCallback.java')
-rw-r--r-- | mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/drm/HttpMediaDrmCallback.java | 195 |
1 files changed, 195 insertions, 0 deletions
diff --git a/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/drm/HttpMediaDrmCallback.java b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/drm/HttpMediaDrmCallback.java new file mode 100644 index 0000000000..baa5bf0916 --- /dev/null +++ b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/drm/HttpMediaDrmCallback.java @@ -0,0 +1,195 @@ +/* + * Copyright (C) 2016 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 org.mozilla.thirdparty.com.google.android.exoplayer2.drm; + +import android.annotation.TargetApi; +import android.net.Uri; +import android.text.TextUtils; +import androidx.annotation.Nullable; +import org.mozilla.thirdparty.com.google.android.exoplayer2.C; +import org.mozilla.thirdparty.com.google.android.exoplayer2.drm.ExoMediaDrm.KeyRequest; +import org.mozilla.thirdparty.com.google.android.exoplayer2.drm.ExoMediaDrm.ProvisionRequest; +import org.mozilla.thirdparty.com.google.android.exoplayer2.upstream.DataSourceInputStream; +import org.mozilla.thirdparty.com.google.android.exoplayer2.upstream.DataSpec; +import org.mozilla.thirdparty.com.google.android.exoplayer2.upstream.HttpDataSource; +import org.mozilla.thirdparty.com.google.android.exoplayer2.upstream.HttpDataSource.InvalidResponseCodeException; +import org.mozilla.thirdparty.com.google.android.exoplayer2.util.Assertions; +import org.mozilla.thirdparty.com.google.android.exoplayer2.util.Util; +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +/** + * A {@link MediaDrmCallback} that makes requests using {@link HttpDataSource} instances. + */ +@TargetApi(18) +public final class HttpMediaDrmCallback implements MediaDrmCallback { + + private static final int MAX_MANUAL_REDIRECTS = 5; + + private final HttpDataSource.Factory dataSourceFactory; + private final String defaultLicenseUrl; + private final boolean forceDefaultLicenseUrl; + private final Map<String, String> keyRequestProperties; + + /** + * @param defaultLicenseUrl The default license URL. Used for key requests that do not specify + * their own license URL. + * @param dataSourceFactory A factory from which to obtain {@link HttpDataSource} instances. + */ + public HttpMediaDrmCallback(String defaultLicenseUrl, HttpDataSource.Factory dataSourceFactory) { + this(defaultLicenseUrl, false, dataSourceFactory); + } + + /** + * @param defaultLicenseUrl The default license URL. Used for key requests that do not specify + * their own license URL, or for all key requests if {@code forceDefaultLicenseUrl} is + * set to true. + * @param forceDefaultLicenseUrl Whether to use {@code defaultLicenseUrl} for key requests that + * include their own license URL. + * @param dataSourceFactory A factory from which to obtain {@link HttpDataSource} instances. + */ + public HttpMediaDrmCallback(String defaultLicenseUrl, boolean forceDefaultLicenseUrl, + HttpDataSource.Factory dataSourceFactory) { + this.dataSourceFactory = dataSourceFactory; + this.defaultLicenseUrl = defaultLicenseUrl; + this.forceDefaultLicenseUrl = forceDefaultLicenseUrl; + this.keyRequestProperties = new HashMap<>(); + } + + /** + * Sets a header for key requests made by the callback. + * + * @param name The name of the header field. + * @param value The value of the field. + */ + public void setKeyRequestProperty(String name, String value) { + Assertions.checkNotNull(name); + Assertions.checkNotNull(value); + synchronized (keyRequestProperties) { + keyRequestProperties.put(name, value); + } + } + + /** + * Clears a header for key requests made by the callback. + * + * @param name The name of the header field. + */ + public void clearKeyRequestProperty(String name) { + Assertions.checkNotNull(name); + synchronized (keyRequestProperties) { + keyRequestProperties.remove(name); + } + } + + /** + * Clears all headers for key requests made by the callback. + */ + public void clearAllKeyRequestProperties() { + synchronized (keyRequestProperties) { + keyRequestProperties.clear(); + } + } + + @Override + public byte[] executeProvisionRequest(UUID uuid, ProvisionRequest request) throws IOException { + String url = + request.getDefaultUrl() + "&signedRequest=" + Util.fromUtf8Bytes(request.getData()); + return executePost(dataSourceFactory, url, /* httpBody= */ null, /* requestProperties= */ null); + } + + @Override + public byte[] executeKeyRequest(UUID uuid, KeyRequest request) throws Exception { + String url = request.getLicenseServerUrl(); + if (forceDefaultLicenseUrl || TextUtils.isEmpty(url)) { + url = defaultLicenseUrl; + } + Map<String, String> requestProperties = new HashMap<>(); + // Add standard request properties for supported schemes. + String contentType = C.PLAYREADY_UUID.equals(uuid) ? "text/xml" + : (C.CLEARKEY_UUID.equals(uuid) ? "application/json" : "application/octet-stream"); + requestProperties.put("Content-Type", contentType); + if (C.PLAYREADY_UUID.equals(uuid)) { + requestProperties.put("SOAPAction", + "http://schemas.microsoft.com/DRM/2007/03/protocols/AcquireLicense"); + } + // Add additional request properties. + synchronized (keyRequestProperties) { + requestProperties.putAll(keyRequestProperties); + } + return executePost(dataSourceFactory, url, request.getData(), requestProperties); + } + + private static byte[] executePost( + HttpDataSource.Factory dataSourceFactory, + String url, + @Nullable byte[] httpBody, + @Nullable Map<String, String> requestProperties) + throws IOException { + HttpDataSource dataSource = dataSourceFactory.createDataSource(); + if (requestProperties != null) { + for (Map.Entry<String, String> requestProperty : requestProperties.entrySet()) { + dataSource.setRequestProperty(requestProperty.getKey(), requestProperty.getValue()); + } + } + + int manualRedirectCount = 0; + while (true) { + DataSpec dataSpec = + new DataSpec( + Uri.parse(url), + DataSpec.HTTP_METHOD_POST, + httpBody, + /* absoluteStreamPosition= */ 0, + /* position= */ 0, + /* length= */ C.LENGTH_UNSET, + /* key= */ null, + DataSpec.FLAG_ALLOW_GZIP); + 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; + String redirectUrl = manuallyRedirect ? getRedirectUrl(e) : null; + if (redirectUrl == null) { + throw e; + } + url = redirectUrl; + } finally { + Util.closeQuietly(inputStream); + } + } + } + + private static @Nullable String getRedirectUrl(InvalidResponseCodeException exception) { + Map<String, List<String>> headerFields = exception.headerFields; + if (headerFields != null) { + List<String> locationHeaders = headerFields.get("Location"); + if (locationHeaders != null && !locationHeaders.isEmpty()) { + return locationHeaders.get(0); + } + } + return null; + } + +} |