Move default DRM callback implementation to library
This also removes direct use of HttpURLConnection and allows use of any HttpDataSource for license requests, which means those using OkHttp can finally use the same network stack for license requests as they're using for everything else, without having to implement their own callback. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=127841045
This commit is contained in:
parent
a61828a675
commit
fc457e4c1b
@ -22,6 +22,7 @@ import com.google.android.exoplayer2.ExoPlayer;
|
|||||||
import com.google.android.exoplayer2.ExoPlayerFactory;
|
import com.google.android.exoplayer2.ExoPlayerFactory;
|
||||||
import com.google.android.exoplayer2.SimpleExoPlayer;
|
import com.google.android.exoplayer2.SimpleExoPlayer;
|
||||||
import com.google.android.exoplayer2.drm.DrmSessionManager;
|
import com.google.android.exoplayer2.drm.DrmSessionManager;
|
||||||
|
import com.google.android.exoplayer2.drm.HttpMediaDrmCallback;
|
||||||
import com.google.android.exoplayer2.drm.StreamingDrmSessionManager;
|
import com.google.android.exoplayer2.drm.StreamingDrmSessionManager;
|
||||||
import com.google.android.exoplayer2.drm.UnsupportedDrmException;
|
import com.google.android.exoplayer2.drm.UnsupportedDrmException;
|
||||||
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;
|
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;
|
||||||
@ -56,6 +57,8 @@ import com.google.android.exoplayer2.ui.PlayerControl;
|
|||||||
import com.google.android.exoplayer2.upstream.DataSource;
|
import com.google.android.exoplayer2.upstream.DataSource;
|
||||||
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
|
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
|
||||||
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
|
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
|
||||||
|
import com.google.android.exoplayer2.upstream.DefaultHttpDataSource;
|
||||||
|
import com.google.android.exoplayer2.upstream.HttpDataSource;
|
||||||
import com.google.android.exoplayer2.util.Util;
|
import com.google.android.exoplayer2.util.Util;
|
||||||
|
|
||||||
import android.Manifest.permission;
|
import android.Manifest.permission;
|
||||||
@ -131,6 +134,7 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
|
|||||||
private SubtitleLayout subtitleLayout;
|
private SubtitleLayout subtitleLayout;
|
||||||
private Button retryButton;
|
private Button retryButton;
|
||||||
|
|
||||||
|
private String userAgent;
|
||||||
private DataSource.Factory manifestDataSourceFactory;
|
private DataSource.Factory manifestDataSourceFactory;
|
||||||
private DataSource.Factory mediaDataSourceFactory;
|
private DataSource.Factory mediaDataSourceFactory;
|
||||||
private FormatEvaluator.Factory formatEvaluatorFactory;
|
private FormatEvaluator.Factory formatEvaluatorFactory;
|
||||||
@ -148,7 +152,7 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
|
|||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
String userAgent = Util.getUserAgent(this, "ExoPlayerDemo");
|
userAgent = Util.getUserAgent(this, "ExoPlayerDemo");
|
||||||
manifestDataSourceFactory = new DefaultDataSourceFactory(this, userAgent);
|
manifestDataSourceFactory = new DefaultDataSourceFactory(this, userAgent);
|
||||||
DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
|
DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
|
||||||
mediaDataSourceFactory = new DefaultDataSourceFactory(this, userAgent, bandwidthMeter);
|
mediaDataSourceFactory = new DefaultDataSourceFactory(this, userAgent, bandwidthMeter);
|
||||||
@ -371,15 +375,14 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
|
|||||||
if (Util.SDK_INT < 18) {
|
if (Util.SDK_INT < 18) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (C.PLAYREADY_UUID.equals(uuid)) {
|
HttpMediaDrmCallback drmCallback = new HttpMediaDrmCallback(licenseUrl,
|
||||||
return StreamingDrmSessionManager.newPlayReadyInstance(
|
new HttpDataSource.Factory() {
|
||||||
TestMediaDrmCallback.newPlayReadyInstance(licenseUrl), null, mainHandler, eventLogger);
|
@Override
|
||||||
} else if (C.WIDEVINE_UUID.equals(uuid)) {
|
public HttpDataSource createDataSource() {
|
||||||
return StreamingDrmSessionManager.newWidevineInstance(
|
return new DefaultHttpDataSource(userAgent, null);
|
||||||
TestMediaDrmCallback.newWidevineInstance(licenseUrl), null, mainHandler, eventLogger);
|
|
||||||
} else {
|
|
||||||
throw new UnsupportedDrmException(UnsupportedDrmException.REASON_UNSUPPORTED_SCHEME);
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
return new StreamingDrmSessionManager(uuid, drmCallback, null, mainHandler, eventLogger);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onUnsupportedDrmError(UnsupportedDrmException e) {
|
private void onUnsupportedDrmError(UnsupportedDrmException e) {
|
||||||
|
@ -13,30 +13,30 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2.demo;
|
package com.google.android.exoplayer2.drm;
|
||||||
|
|
||||||
import com.google.android.exoplayer2.drm.MediaDrmCallback;
|
import com.google.android.exoplayer2.C;
|
||||||
|
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.util.Util;
|
import com.google.android.exoplayer2.util.Util;
|
||||||
|
|
||||||
import android.annotation.TargetApi;
|
import android.annotation.TargetApi;
|
||||||
import android.media.MediaDrm.KeyRequest;
|
import android.media.MediaDrm.KeyRequest;
|
||||||
import android.media.MediaDrm.ProvisionRequest;
|
import android.media.MediaDrm.ProvisionRequest;
|
||||||
|
import android.net.Uri;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.net.HttpURLConnection;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link MediaDrmCallback} for test content.
|
* A {@link MediaDrmCallback} that makes requests using {@link HttpDataSource} instances.
|
||||||
*/
|
*/
|
||||||
@TargetApi(18)
|
@TargetApi(18)
|
||||||
/* package */ final class TestMediaDrmCallback implements MediaDrmCallback {
|
public final class HttpMediaDrmCallback implements MediaDrmCallback {
|
||||||
|
|
||||||
private static final Map<String, String> PLAYREADY_KEY_REQUEST_PROPERTIES;
|
private static final Map<String, String> PLAYREADY_KEY_REQUEST_PROPERTIES;
|
||||||
static {
|
static {
|
||||||
@ -47,20 +47,16 @@ import java.util.UUID;
|
|||||||
PLAYREADY_KEY_REQUEST_PROPERTIES = keyRequestProperties;
|
PLAYREADY_KEY_REQUEST_PROPERTIES = keyRequestProperties;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private final HttpDataSource.Factory dataSourceFactory;
|
||||||
private final String defaultUrl;
|
private final String defaultUrl;
|
||||||
private final Map<String, String> keyRequestProperties;
|
|
||||||
|
|
||||||
public static TestMediaDrmCallback newWidevineInstance(String defaultUrl) {
|
/**
|
||||||
return new TestMediaDrmCallback(defaultUrl, null);
|
* @param defaultUrl The default license URL.
|
||||||
}
|
* @param dataSourceFactory A factory from which to obtain {@link HttpDataSource} instances.
|
||||||
|
*/
|
||||||
public static TestMediaDrmCallback newPlayReadyInstance(String defaultUrl) {
|
public HttpMediaDrmCallback(String defaultUrl, HttpDataSource.Factory dataSourceFactory) {
|
||||||
return new TestMediaDrmCallback(defaultUrl, PLAYREADY_KEY_REQUEST_PROPERTIES);
|
this.dataSourceFactory = dataSourceFactory;
|
||||||
}
|
|
||||||
|
|
||||||
private TestMediaDrmCallback(String defaultUrl, Map<String, String> keyRequestProperties) {
|
|
||||||
this.defaultUrl = defaultUrl;
|
this.defaultUrl = defaultUrl;
|
||||||
this.keyRequestProperties = keyRequestProperties;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -75,43 +71,27 @@ import java.util.UUID;
|
|||||||
if (TextUtils.isEmpty(url)) {
|
if (TextUtils.isEmpty(url)) {
|
||||||
url = defaultUrl;
|
url = defaultUrl;
|
||||||
}
|
}
|
||||||
|
Map<String, String> keyRequestProperties = C.PLAYREADY_UUID.equals(uuid)
|
||||||
|
? PLAYREADY_KEY_REQUEST_PROPERTIES : null;
|
||||||
return executePost(url, request.getData(), keyRequestProperties);
|
return executePost(url, request.getData(), keyRequestProperties);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte[] executePost(String url, byte[] data, Map<String, String> requestProperties)
|
private byte[] executePost(String url, byte[] data, Map<String, String> requestProperties)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
HttpURLConnection urlConnection = null;
|
HttpDataSource dataSource = dataSourceFactory.createDataSource();
|
||||||
try {
|
|
||||||
urlConnection = (HttpURLConnection) new URL(url).openConnection();
|
|
||||||
urlConnection.setRequestMethod("POST");
|
|
||||||
urlConnection.setDoOutput(data != null);
|
|
||||||
urlConnection.setDoInput(true);
|
|
||||||
if (requestProperties != null) {
|
if (requestProperties != null) {
|
||||||
for (Map.Entry<String, String> requestProperty : requestProperties.entrySet()) {
|
for (Map.Entry<String, String> requestProperty : requestProperties.entrySet()) {
|
||||||
urlConnection.setRequestProperty(requestProperty.getKey(), requestProperty.getValue());
|
dataSource.setRequestProperty(requestProperty.getKey(), requestProperty.getValue());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Write the request body, if there is one.
|
DataSpec dataSpec = new DataSpec(Uri.parse(url), data, 0, 0, C.LENGTH_UNBOUNDED, null,
|
||||||
if (data != null) {
|
DataSpec.FLAG_ALLOW_GZIP);
|
||||||
OutputStream out = urlConnection.getOutputStream();
|
DataSourceInputStream inputStream = new DataSourceInputStream(dataSource, dataSpec);
|
||||||
try {
|
|
||||||
out.write(data);
|
|
||||||
} finally {
|
|
||||||
out.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Read and return the response body.
|
|
||||||
InputStream inputStream = urlConnection.getInputStream();
|
|
||||||
try {
|
try {
|
||||||
return Util.toByteArray(inputStream);
|
return Util.toByteArray(inputStream);
|
||||||
} finally {
|
} finally {
|
||||||
inputStream.close();
|
inputStream.close();
|
||||||
}
|
}
|
||||||
} finally {
|
|
||||||
if (urlConnection != null) {
|
|
||||||
urlConnection.disconnect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -189,7 +189,6 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S
|
|||||||
currentChunk.startTimeUs);
|
currentChunk.startTimeUs);
|
||||||
}
|
}
|
||||||
downstreamFormat = format;
|
downstreamFormat = format;
|
||||||
|
|
||||||
return sampleQueue.readData(formatHolder, buffer, loadingFinished, lastSeekPositionUs);
|
return sampleQueue.readData(formatHolder, buffer, loadingFinished, lastSeekPositionUs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,6 +29,7 @@ import java.io.IOException;
|
|||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.InterruptedIOException;
|
import java.io.InterruptedIOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
import java.net.HttpURLConnection;
|
import java.net.HttpURLConnection;
|
||||||
import java.net.NoRouteToHostException;
|
import java.net.NoRouteToHostException;
|
||||||
import java.net.ProtocolException;
|
import java.net.ProtocolException;
|
||||||
@ -59,8 +60,9 @@ public class DefaultHttpDataSource implements HttpDataSource {
|
|||||||
*/
|
*/
|
||||||
public static final int DEFAULT_READ_TIMEOUT_MILLIS = 8 * 1000;
|
public static final int DEFAULT_READ_TIMEOUT_MILLIS = 8 * 1000;
|
||||||
|
|
||||||
private static final int MAX_REDIRECTS = 20; // Same limit as okhttp.
|
|
||||||
private static final String TAG = "DefaultHttpDataSource";
|
private static final String TAG = "DefaultHttpDataSource";
|
||||||
|
private static final int MAX_REDIRECTS = 20; // Same limit as okhttp.
|
||||||
|
private static final long MAX_BYTES_TO_DRAIN = 2048;
|
||||||
private static final Pattern CONTENT_RANGE_HEADER =
|
private static final Pattern CONTENT_RANGE_HEADER =
|
||||||
Pattern.compile("^bytes (\\d+)-(\\d+)/(\\d+)$");
|
Pattern.compile("^bytes (\\d+)-(\\d+)/(\\d+)$");
|
||||||
private static final AtomicReference<byte[]> skipBufferReference = new AtomicReference<>();
|
private static final AtomicReference<byte[]> skipBufferReference = new AtomicReference<>();
|
||||||
@ -266,7 +268,7 @@ public class DefaultHttpDataSource implements HttpDataSource {
|
|||||||
public void close() throws HttpDataSourceException {
|
public void close() throws HttpDataSourceException {
|
||||||
try {
|
try {
|
||||||
if (inputStream != null) {
|
if (inputStream != null) {
|
||||||
Util.maybeTerminateInputStream(connection, bytesRemaining());
|
maybeTerminateInputStream(connection, bytesRemaining());
|
||||||
try {
|
try {
|
||||||
inputStream.close();
|
inputStream.close();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
@ -564,6 +566,51 @@ public class DefaultHttpDataSource implements HttpDataSource {
|
|||||||
return read;
|
return read;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* On platform API levels 19 and 20, okhttp's implementation of {@link InputStream#close} can
|
||||||
|
* block for a long time if the stream has a lot of data remaining. Call this method before
|
||||||
|
* closing the input stream to make a best effort to cause the input stream to encounter an
|
||||||
|
* unexpected end of input, working around this issue. On other platform API levels, the method
|
||||||
|
* does nothing.
|
||||||
|
*
|
||||||
|
* @param connection The connection whose {@link InputStream} should be terminated.
|
||||||
|
* @param bytesRemaining The number of bytes remaining to be read from the input stream if its
|
||||||
|
* length is known. {@link C#LENGTH_UNBOUNDED} otherwise.
|
||||||
|
*/
|
||||||
|
private static void maybeTerminateInputStream(HttpURLConnection connection, long bytesRemaining) {
|
||||||
|
if (Util.SDK_INT != 19 && Util.SDK_INT != 20) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
InputStream inputStream = connection.getInputStream();
|
||||||
|
if (bytesRemaining == C.LENGTH_UNBOUNDED) {
|
||||||
|
// If the input stream has already ended, do nothing. The socket may be re-used.
|
||||||
|
if (inputStream.read() == -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if (bytesRemaining <= MAX_BYTES_TO_DRAIN) {
|
||||||
|
// There isn't much data left. Prefer to allow it to drain, which may allow the socket to be
|
||||||
|
// re-used.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String className = inputStream.getClass().getName();
|
||||||
|
if (className.equals("com.android.okhttp.internal.http.HttpTransport$ChunkedInputStream")
|
||||||
|
|| className.equals(
|
||||||
|
"com.android.okhttp.internal.http.HttpTransport$FixedLengthInputStream")) {
|
||||||
|
Class<?> superclass = inputStream.getClass().getSuperclass();
|
||||||
|
Method unexpectedEndOfInput = superclass.getDeclaredMethod("unexpectedEndOfInput");
|
||||||
|
unexpectedEndOfInput.setAccessible(true);
|
||||||
|
unexpectedEndOfInput.invoke(inputStream);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
// If an IOException then the connection didn't ever have an input stream, or it was closed
|
||||||
|
// already. If another type of exception then something went wrong, most likely the device
|
||||||
|
// isn't using okhttp.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Closes the current connection quietly, if there is one.
|
* Closes the current connection quietly, if there is one.
|
||||||
*/
|
*/
|
||||||
|
@ -29,6 +29,16 @@ import java.util.Map;
|
|||||||
*/
|
*/
|
||||||
public interface HttpDataSource extends DataSource {
|
public interface HttpDataSource extends DataSource {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A factory for {@link HttpDataSource} instances.
|
||||||
|
*/
|
||||||
|
interface Factory extends DataSource.Factory {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
HttpDataSource createDataSource();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link Predicate} that rejects content types often used for pay-walls.
|
* A {@link Predicate} that rejects content types often used for pay-walls.
|
||||||
*/
|
*/
|
||||||
|
@ -31,9 +31,7 @@ import java.io.ByteArrayOutputStream;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.net.HttpURLConnection;
|
|
||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
@ -111,8 +109,6 @@ public final class Util {
|
|||||||
|
|
||||||
private static final Pattern ESCAPED_CHARACTER_PATTERN = Pattern.compile("%([A-Fa-f0-9]{2})");
|
private static final Pattern ESCAPED_CHARACTER_PATTERN = Pattern.compile("%([A-Fa-f0-9]{2})");
|
||||||
|
|
||||||
private static final long MAX_BYTES_TO_DRAIN = 2048;
|
|
||||||
|
|
||||||
private Util() {}
|
private Util() {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -522,50 +518,6 @@ public final class Util {
|
|||||||
return intArray;
|
return intArray;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* On platform API levels 19 and 20, okhttp's implementation of {@link InputStream#close} can
|
|
||||||
* block for a long time if the stream has a lot of data remaining. Call this method before
|
|
||||||
* closing the input stream to make a best effort to cause the input stream to encounter an
|
|
||||||
* unexpected end of input, working around this issue. On other platform API levels, the method
|
|
||||||
* does nothing.
|
|
||||||
*
|
|
||||||
* @param connection The connection whose {@link InputStream} should be terminated.
|
|
||||||
* @param bytesRemaining The number of bytes remaining to be read from the input stream if its
|
|
||||||
* length is known. {@link C#LENGTH_UNBOUNDED} otherwise.
|
|
||||||
*/
|
|
||||||
public static void maybeTerminateInputStream(HttpURLConnection connection, long bytesRemaining) {
|
|
||||||
if (SDK_INT != 19 && SDK_INT != 20) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
InputStream inputStream = connection.getInputStream();
|
|
||||||
if (bytesRemaining == C.LENGTH_UNBOUNDED) {
|
|
||||||
// If the input stream has already ended, do nothing. The socket may be re-used.
|
|
||||||
if (inputStream.read() == -1) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else if (bytesRemaining <= MAX_BYTES_TO_DRAIN) {
|
|
||||||
// There isn't much data left. Prefer to allow it to drain, which may allow the socket to be
|
|
||||||
// re-used.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
String className = inputStream.getClass().getName();
|
|
||||||
if (className.equals("com.android.okhttp.internal.http.HttpTransport$ChunkedInputStream")
|
|
||||||
|| className.equals(
|
|
||||||
"com.android.okhttp.internal.http.HttpTransport$FixedLengthInputStream")) {
|
|
||||||
Class<?> superclass = inputStream.getClass().getSuperclass();
|
|
||||||
Method unexpectedEndOfInput = superclass.getDeclaredMethod("unexpectedEndOfInput");
|
|
||||||
unexpectedEndOfInput.setAccessible(true);
|
|
||||||
unexpectedEndOfInput.invoke(inputStream);
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
// If an IOException then the connection didn't ever have an input stream, or it was closed
|
|
||||||
// already. If another type of exception then something went wrong, most likely the device
|
|
||||||
// isn't using okhttp.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given a {@link DataSpec} and a number of bytes already loaded, returns a {@link DataSpec}
|
* Given a {@link DataSpec} and a number of bytes already loaded, returns a {@link DataSpec}
|
||||||
* that represents the remainder of the data.
|
* that represents the remainder of the data.
|
||||||
|
@ -20,6 +20,7 @@ import com.google.android.exoplayer2.ExoPlaybackException;
|
|||||||
import com.google.android.exoplayer2.ExoPlayer;
|
import com.google.android.exoplayer2.ExoPlayer;
|
||||||
import com.google.android.exoplayer2.RendererCapabilities;
|
import com.google.android.exoplayer2.RendererCapabilities;
|
||||||
import com.google.android.exoplayer2.decoder.DecoderCounters;
|
import com.google.android.exoplayer2.decoder.DecoderCounters;
|
||||||
|
import com.google.android.exoplayer2.drm.HttpMediaDrmCallback;
|
||||||
import com.google.android.exoplayer2.drm.StreamingDrmSessionManager;
|
import com.google.android.exoplayer2.drm.StreamingDrmSessionManager;
|
||||||
import com.google.android.exoplayer2.drm.UnsupportedDrmException;
|
import com.google.android.exoplayer2.drm.UnsupportedDrmException;
|
||||||
import com.google.android.exoplayer2.mediacodec.MediaCodecInfo;
|
import com.google.android.exoplayer2.mediacodec.MediaCodecInfo;
|
||||||
@ -30,7 +31,6 @@ import com.google.android.exoplayer2.playbacktests.util.DecoderCountersUtil;
|
|||||||
import com.google.android.exoplayer2.playbacktests.util.ExoHostedTest;
|
import com.google.android.exoplayer2.playbacktests.util.ExoHostedTest;
|
||||||
import com.google.android.exoplayer2.playbacktests.util.HostActivity;
|
import com.google.android.exoplayer2.playbacktests.util.HostActivity;
|
||||||
import com.google.android.exoplayer2.playbacktests.util.MetricsLogger;
|
import com.google.android.exoplayer2.playbacktests.util.MetricsLogger;
|
||||||
import com.google.android.exoplayer2.playbacktests.util.TestMediaDrmCallback;
|
|
||||||
import com.google.android.exoplayer2.source.MediaSource;
|
import com.google.android.exoplayer2.source.MediaSource;
|
||||||
import com.google.android.exoplayer2.source.TrackGroup;
|
import com.google.android.exoplayer2.source.TrackGroup;
|
||||||
import com.google.android.exoplayer2.source.TrackGroupArray;
|
import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||||
@ -43,6 +43,8 @@ import com.google.android.exoplayer2.trackselection.TrackSelection;
|
|||||||
import com.google.android.exoplayer2.upstream.DataSource;
|
import com.google.android.exoplayer2.upstream.DataSource;
|
||||||
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
|
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
|
||||||
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
|
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
|
||||||
|
import com.google.android.exoplayer2.upstream.DefaultHttpDataSource;
|
||||||
|
import com.google.android.exoplayer2.upstream.HttpDataSource;
|
||||||
import com.google.android.exoplayer2.util.Assertions;
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
import com.google.android.exoplayer2.util.MimeTypes;
|
import com.google.android.exoplayer2.util.MimeTypes;
|
||||||
import com.google.android.exoplayer2.util.Util;
|
import com.google.android.exoplayer2.util.Util;
|
||||||
@ -195,7 +197,8 @@ public final class DashTest extends ActivityInstrumentationTestCase2<HostActivit
|
|||||||
WIDEVINE_VP9_180P_VIDEO_REPRESENTATION_ID,
|
WIDEVINE_VP9_180P_VIDEO_REPRESENTATION_ID,
|
||||||
WIDEVINE_VP9_360P_VIDEO_REPRESENTATION_ID};
|
WIDEVINE_VP9_360P_VIDEO_REPRESENTATION_ID};
|
||||||
|
|
||||||
private static final String WIDEVINE_PROVIDER = "widevine_test";
|
private static final String WIDEVINE_LICENSE_URL =
|
||||||
|
"https://proxy.uat.widevine.com/proxy?provider=widevine_test&video_id=";
|
||||||
private static final String WIDEVINE_SW_CRYPTO_CONTENT_ID = "exoplayer_test_1";
|
private static final String WIDEVINE_SW_CRYPTO_CONTENT_ID = "exoplayer_test_1";
|
||||||
private static final String WIDEVINE_HW_SECURE_DECODE_CONTENT_ID = "exoplayer_test_2";
|
private static final String WIDEVINE_HW_SECURE_DECODE_CONTENT_ID = "exoplayer_test_2";
|
||||||
private static final UUID WIDEVINE_UUID = new UUID(0xEDEF8BA979D64ACEL, 0xA3C827DCD51D21EDL);
|
private static final UUID WIDEVINE_UUID = new UUID(0xEDEF8BA979D64ACEL, 0xA3C827DCD51D21EDL);
|
||||||
@ -692,7 +695,7 @@ public final class DashTest extends ActivityInstrumentationTestCase2<HostActivit
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
@TargetApi(18)
|
@TargetApi(18)
|
||||||
protected final StreamingDrmSessionManager buildDrmSessionManager() {
|
protected final StreamingDrmSessionManager buildDrmSessionManager(final String userAgent) {
|
||||||
StreamingDrmSessionManager drmSessionManager = null;
|
StreamingDrmSessionManager drmSessionManager = null;
|
||||||
if (isWidevineEncrypted) {
|
if (isWidevineEncrypted) {
|
||||||
try {
|
try {
|
||||||
@ -703,8 +706,14 @@ public final class DashTest extends ActivityInstrumentationTestCase2<HostActivit
|
|||||||
String widevineContentId = forceL3Widevine ? WIDEVINE_SW_CRYPTO_CONTENT_ID
|
String widevineContentId = forceL3Widevine ? WIDEVINE_SW_CRYPTO_CONTENT_ID
|
||||||
: WIDEVINE_SECURITY_LEVEL_1.equals(securityProperty)
|
: WIDEVINE_SECURITY_LEVEL_1.equals(securityProperty)
|
||||||
? WIDEVINE_HW_SECURE_DECODE_CONTENT_ID : WIDEVINE_SW_CRYPTO_CONTENT_ID;
|
? WIDEVINE_HW_SECURE_DECODE_CONTENT_ID : WIDEVINE_SW_CRYPTO_CONTENT_ID;
|
||||||
TestMediaDrmCallback drmCallback = TestMediaDrmCallback.newWidevineInstance(
|
HttpMediaDrmCallback drmCallback = new HttpMediaDrmCallback(
|
||||||
widevineContentId, WIDEVINE_PROVIDER);
|
WIDEVINE_LICENSE_URL + widevineContentId,
|
||||||
|
new HttpDataSource.Factory() {
|
||||||
|
@Override
|
||||||
|
public HttpDataSource createDataSource() {
|
||||||
|
return new DefaultHttpDataSource(userAgent, null);
|
||||||
|
}
|
||||||
|
});
|
||||||
drmSessionManager = StreamingDrmSessionManager.newWidevineInstance(drmCallback, null,
|
drmSessionManager = StreamingDrmSessionManager.newWidevineInstance(drmCallback, null,
|
||||||
null, null);
|
null, null);
|
||||||
if (forceL3Widevine && !WIDEVINE_SECURITY_LEVEL_3.equals(securityProperty)) {
|
if (forceL3Widevine && !WIDEVINE_SECURITY_LEVEL_3.equals(securityProperty)) {
|
||||||
|
@ -15,10 +15,6 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2.playbacktests.util;
|
package com.google.android.exoplayer2.playbacktests.util;
|
||||||
|
|
||||||
import android.os.Handler;
|
|
||||||
import android.os.SystemClock;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.view.Surface;
|
|
||||||
import com.google.android.exoplayer2.DefaultLoadControl;
|
import com.google.android.exoplayer2.DefaultLoadControl;
|
||||||
import com.google.android.exoplayer2.ExoPlaybackException;
|
import com.google.android.exoplayer2.ExoPlaybackException;
|
||||||
import com.google.android.exoplayer2.ExoPlayer;
|
import com.google.android.exoplayer2.ExoPlayer;
|
||||||
@ -34,6 +30,10 @@ import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
|
|||||||
import com.google.android.exoplayer2.trackselection.MappingTrackSelector;
|
import com.google.android.exoplayer2.trackselection.MappingTrackSelector;
|
||||||
import com.google.android.exoplayer2.util.Util;
|
import com.google.android.exoplayer2.util.Util;
|
||||||
|
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.SystemClock;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.Surface;
|
||||||
import junit.framework.Assert;
|
import junit.framework.Assert;
|
||||||
|
|
||||||
|
|
||||||
@ -123,9 +123,10 @@ public abstract class ExoHostedTest implements HostedTest, ExoPlayer.EventListen
|
|||||||
public final void onStart(HostActivity host, Surface surface) {
|
public final void onStart(HostActivity host, Surface surface) {
|
||||||
// Build the player.
|
// Build the player.
|
||||||
trackSelector = buildTrackSelector(host);
|
trackSelector = buildTrackSelector(host);
|
||||||
DrmSessionManager drmSessionManager = buildDrmSessionManager();
|
String userAgent = "ExoPlayerPlaybackTests";
|
||||||
|
DrmSessionManager drmSessionManager = buildDrmSessionManager(userAgent);
|
||||||
player = buildExoPlayer(host, surface, trackSelector, drmSessionManager);
|
player = buildExoPlayer(host, surface, trackSelector, drmSessionManager);
|
||||||
player.setMediaSource(buildSource(host, Util.getUserAgent(host, "ExoPlayerPlaybackTests")));
|
player.setMediaSource(buildSource(host, Util.getUserAgent(host, userAgent)));
|
||||||
player.addListener(this);
|
player.addListener(this);
|
||||||
player.setDebugListener(this);
|
player.setDebugListener(this);
|
||||||
player.setPlayWhenReady(true);
|
player.setPlayWhenReady(true);
|
||||||
@ -275,7 +276,7 @@ public abstract class ExoHostedTest implements HostedTest, ExoPlayer.EventListen
|
|||||||
|
|
||||||
// Internal logic
|
// Internal logic
|
||||||
|
|
||||||
protected DrmSessionManager buildDrmSessionManager() {
|
protected DrmSessionManager buildDrmSessionManager(String userAgent) {
|
||||||
// Do nothing. Interested subclasses may override.
|
// Do nothing. Interested subclasses may override.
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -1,107 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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 com.google.android.exoplayer2.playbacktests.util;
|
|
||||||
|
|
||||||
import android.annotation.TargetApi;
|
|
||||||
import android.media.MediaDrm;
|
|
||||||
import android.text.TextUtils;
|
|
||||||
|
|
||||||
import com.google.android.exoplayer2.drm.MediaDrmCallback;
|
|
||||||
import com.google.android.exoplayer2.util.Util;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.net.HttpURLConnection;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.nio.charset.Charset;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A {@link MediaDrmCallback} for Widevine test content.
|
|
||||||
*/
|
|
||||||
@TargetApi(18)
|
|
||||||
public final class TestMediaDrmCallback implements MediaDrmCallback {
|
|
||||||
|
|
||||||
private static final String WIDEVINE_BASE_URL = "https://proxy.uat.widevine.com/proxy";
|
|
||||||
|
|
||||||
private final String defaultUrl;
|
|
||||||
private final Map<String, String> keyRequestProperties;
|
|
||||||
|
|
||||||
public static TestMediaDrmCallback newWidevineInstance(String contentId, String provider) {
|
|
||||||
String defaultUrl = WIDEVINE_BASE_URL + "?video_id=" + contentId + "&provider=" + provider;
|
|
||||||
return new TestMediaDrmCallback(defaultUrl, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private TestMediaDrmCallback(String defaultUrl, Map<String, String> keyRequestProperties) {
|
|
||||||
this.defaultUrl = defaultUrl;
|
|
||||||
this.keyRequestProperties = keyRequestProperties;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public byte[] executeProvisionRequest(UUID uuid, MediaDrm.ProvisionRequest request)
|
|
||||||
throws IOException {
|
|
||||||
String url = request.getDefaultUrl() + "&signedRequest=" + new String(request.getData(),
|
|
||||||
Charset.defaultCharset());
|
|
||||||
return executePost(url, null, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public byte[] executeKeyRequest(UUID uuid, MediaDrm.KeyRequest request) throws Exception {
|
|
||||||
String url = request.getDefaultUrl();
|
|
||||||
if (TextUtils.isEmpty(url)) {
|
|
||||||
url = defaultUrl;
|
|
||||||
}
|
|
||||||
return executePost(url, request.getData(), keyRequestProperties);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static byte[] executePost(String url, byte[] data, Map<String, String> requestProperties)
|
|
||||||
throws IOException {
|
|
||||||
HttpURLConnection urlConnection = null;
|
|
||||||
try {
|
|
||||||
urlConnection = (HttpURLConnection) new URL(url).openConnection();
|
|
||||||
urlConnection.setRequestMethod("POST");
|
|
||||||
urlConnection.setDoOutput(data != null);
|
|
||||||
urlConnection.setDoInput(true);
|
|
||||||
if (requestProperties != null) {
|
|
||||||
for (Map.Entry<String, String> requestProperty : requestProperties.entrySet()) {
|
|
||||||
urlConnection.setRequestProperty(requestProperty.getKey(), requestProperty.getValue());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Write the request body, if there is one.
|
|
||||||
if (data != null) {
|
|
||||||
OutputStream out = urlConnection.getOutputStream();
|
|
||||||
try {
|
|
||||||
out.write(data);
|
|
||||||
} finally {
|
|
||||||
out.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Read and return the response body.
|
|
||||||
InputStream inputStream = urlConnection.getInputStream();
|
|
||||||
try {
|
|
||||||
return Util.toByteArray(inputStream);
|
|
||||||
} finally {
|
|
||||||
inputStream.close();
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
if (urlConnection != null) {
|
|
||||||
urlConnection.disconnect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user