diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/PlayerActivity.java b/demo/src/main/java/com/google/android/exoplayer/demo/PlayerActivity.java index f1681cfe3a..21f5677dca 100644 --- a/demo/src/main/java/com/google/android/exoplayer/demo/PlayerActivity.java +++ b/demo/src/main/java/com/google/android/exoplayer/demo/PlayerActivity.java @@ -16,7 +16,10 @@ package com.google.android.exoplayer.demo; import com.google.android.exoplayer.AspectRatioFrameLayout; +import com.google.android.exoplayer.ExoPlaybackException; import com.google.android.exoplayer.ExoPlayer; +import com.google.android.exoplayer.MediaCodecTrackRenderer.DecoderInitializationException; +import com.google.android.exoplayer.MediaCodecUtil.DecoderQueryException; import com.google.android.exoplayer.MediaFormat; import com.google.android.exoplayer.audio.AudioCapabilities; import com.google.android.exoplayer.audio.AudioCapabilitiesReceiver; @@ -99,9 +102,6 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback, private static final int MENU_GROUP_TRACKS = 1; private static final int ID_OFFSET = 2; - // For use when requesting permission. - private static final String URI_FILE_SCHEME = "file"; - private static final CookieManager defaultCookieManager; static { defaultCookieManager = new CookieManager(); @@ -394,13 +394,35 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback, @Override public void onError(Exception e) { + String errorString = null; if (e instanceof UnsupportedDrmException) { // Special case DRM failures. UnsupportedDrmException unsupportedDrmException = (UnsupportedDrmException) e; - int stringId = Util.SDK_INT < 18 ? R.string.drm_error_not_supported + errorString = getString(Util.SDK_INT < 18 ? R.string.error_drm_not_supported : unsupportedDrmException.reason == UnsupportedDrmException.REASON_UNSUPPORTED_SCHEME - ? R.string.drm_error_unsupported_scheme : R.string.drm_error_unknown; - Toast.makeText(getApplicationContext(), stringId, Toast.LENGTH_LONG).show(); + ? R.string.error_drm_unsupported_scheme : R.string.error_drm_unknown); + } else if (e instanceof ExoPlaybackException + && e.getCause() instanceof DecoderInitializationException) { + // Special case for decoder initialization failures. + DecoderInitializationException decoderInitializationException = + (DecoderInitializationException) e.getCause(); + if (decoderInitializationException.decoderName == null) { + if (decoderInitializationException.getCause() instanceof DecoderQueryException) { + errorString = getString(R.string.error_querying_decoders); + } else if (decoderInitializationException.secureDecoderRequired) { + errorString = getString(R.string.error_no_secure_decoder, + decoderInitializationException.mimeType); + } else { + errorString = getString(R.string.error_no_decoder, + decoderInitializationException.mimeType); + } + } else { + errorString = getString(R.string.error_instantiating_decoder, + decoderInitializationException.decoderName); + } + } + if (errorString != null) { + Toast.makeText(getApplicationContext(), errorString, Toast.LENGTH_LONG).show(); } playerNeedsPrepare = true; updateButtonVisibilities(); diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/SampleChooserActivity.java b/demo/src/main/java/com/google/android/exoplayer/demo/SampleChooserActivity.java index 4acf3e50f1..a403c83018 100644 --- a/demo/src/main/java/com/google/android/exoplayer/demo/SampleChooserActivity.java +++ b/demo/src/main/java/com/google/android/exoplayer/demo/SampleChooserActivity.java @@ -15,17 +15,13 @@ */ package com.google.android.exoplayer.demo; -import com.google.android.exoplayer.MediaCodecUtil; -import com.google.android.exoplayer.MediaCodecUtil.DecoderQueryException; import com.google.android.exoplayer.demo.Samples.Sample; -import com.google.android.exoplayer.util.MimeTypes; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.net.Uri; import android.os.Bundle; -import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -40,8 +36,6 @@ import android.widget.TextView; */ public class SampleChooserActivity extends Activity { - private static final String TAG = "SampleChooserActivity"; - @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -52,27 +46,20 @@ public class SampleChooserActivity extends Activity { sampleAdapter.add(new Header("YouTube DASH")); sampleAdapter.addAll((Object[]) Samples.YOUTUBE_DASH_MP4); - try { - if (MediaCodecUtil.getDecoderInfo(MimeTypes.VIDEO_VP9, false) != null) { - sampleAdapter.addAll((Object[]) Samples.YOUTUBE_DASH_WEBM); - } - } catch (DecoderQueryException e) { - Log.e(TAG, "Failed to query vp9 decoder", e); - } + sampleAdapter.addAll((Object[]) Samples.YOUTUBE_DASH_WEBM); sampleAdapter.add(new Header("Widevine DASH Policy Tests (GTS)")); sampleAdapter.addAll((Object[]) Samples.WIDEVINE_GTS); - sampleAdapter.add(new Header("Widevine DASH")); - sampleAdapter.addAll((Object[]) Samples.WIDEVINE_DASH_MP4); - try { - if (MediaCodecUtil.getDecoderInfo(MimeTypes.VIDEO_VP9, false) != null) { - sampleAdapter.addAll((Object[]) Samples.WIDEVINE_VP9_WEBM_CLEAR); - } - if (MediaCodecUtil.getDecoderInfo(MimeTypes.VIDEO_VP9, true) != null) { - sampleAdapter.addAll((Object[]) Samples.WIDEVINE_VP9_WEBM_SECURE); - } - } catch (DecoderQueryException e) { - Log.e(TAG, "Failed to query vp9 decoder", e); - } + sampleAdapter.add(new Header("Widevine HDCP Capabilities Tests")); + sampleAdapter.addAll((Object[]) Samples.WIDEVINE_HDCP); + sampleAdapter.add(new Header("Widevine DASH: MP4,H264")); + sampleAdapter.addAll((Object[]) Samples.WIDEVINE_H264_MP4_CLEAR); + sampleAdapter.addAll((Object[]) Samples.WIDEVINE_H264_MP4_SECURE); + sampleAdapter.add(new Header("Widevine DASH: WebM,VP9")); + sampleAdapter.addAll((Object[]) Samples.WIDEVINE_VP9_WEBM_CLEAR); + sampleAdapter.addAll((Object[]) Samples.WIDEVINE_VP9_WEBM_SECURE); + sampleAdapter.add(new Header("Widevine DASH: MP4,H265")); + sampleAdapter.addAll((Object[]) Samples.WIDEVINE_H265_MP4_CLEAR); + sampleAdapter.addAll((Object[]) Samples.WIDEVINE_H265_MP4_SECURE); sampleAdapter.add(new Header("SmoothStreaming")); sampleAdapter.addAll((Object[]) Samples.SMOOTHSTREAMING); sampleAdapter.add(new Header("HLS")); diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/Samples.java b/demo/src/main/java/com/google/android/exoplayer/demo/Samples.java index badf140191..a78a77ffa1 100644 --- a/demo/src/main/java/com/google/android/exoplayer/demo/Samples.java +++ b/demo/src/main/java/com/google/android/exoplayer/demo/Samples.java @@ -80,7 +80,7 @@ import java.util.Locale; }; private static final String WIDEVINE_GTS_MPD = - "https://storage.googleapis.com/wvmedia/cenc/h264/tears.mpd"; + "https://storage.googleapis.com/wvmedia/cenc/h264/tears/tears.mpd"; public static final Sample[] WIDEVINE_GTS = new Sample[] { new Sample("WV: HDCP not specified", "d286538032258a1c", "widevine_test", WIDEVINE_GTS_MPD, PlayerActivity.TYPE_DASH), @@ -88,32 +88,122 @@ import java.util.Locale; WIDEVINE_GTS_MPD, PlayerActivity.TYPE_DASH), new Sample("WV: HDCP required", "e06c39f1151da3df", "widevine_test", WIDEVINE_GTS_MPD, PlayerActivity.TYPE_DASH), - new Sample("WV: Secure video path required", "0894c7c8719b28a0", "widevine_test", + new Sample("WV: Secure video path required (MP4,H264)", "0894c7c8719b28a0", "widevine_test", WIDEVINE_GTS_MPD, PlayerActivity.TYPE_DASH), + new Sample("WV: Secure video path required (WebM,VP9)", "0894c7c8719b28a0", "widevine_test", + "https://storage.googleapis.com/wvmedia/cenc/vp9/tears/tears.mpd", + PlayerActivity.TYPE_DASH), + new Sample("WV: Secure video path required (MP4,H265)", "0894c7c8719b28a0", "widevine_test", + "https://storage.googleapis.com/wvmedia/cenc/hevc/tears/tears.mpd", + PlayerActivity.TYPE_DASH), new Sample("WV: HDCP + secure video path required", "efd045b1eb61888a", "widevine_test", WIDEVINE_GTS_MPD, PlayerActivity.TYPE_DASH), new Sample("WV: 30s license duration (fails at ~30s)", "f9a34cab7b05881a", "widevine_test", WIDEVINE_GTS_MPD, PlayerActivity.TYPE_DASH), }; - public static final Sample[] WIDEVINE_DASH_MP4 = new Sample[] { - new Sample("WV: Clear (MP4,H264)", - "https://storage.googleapis.com/wvmedia/cenc/clear/h264/tears.mpd", + public static final Sample[] WIDEVINE_HDCP = new Sample[] { + new Sample("WV: HDCP: None (not required)", "HDCP_None", "widevine_test", + WIDEVINE_GTS_MPD, PlayerActivity.TYPE_DASH), + new Sample("WV: HDCP: 1.0 required", "HDCP_V1", "widevine_test", + WIDEVINE_GTS_MPD, PlayerActivity.TYPE_DASH), + new Sample("WV: HDCP: 2.0 required", "HDCP_V2", "widevine_test", + WIDEVINE_GTS_MPD, PlayerActivity.TYPE_DASH), + new Sample("WV: HDCP: 2.1 required", "HDCP_V2_1", "widevine_test", + WIDEVINE_GTS_MPD, PlayerActivity.TYPE_DASH), + new Sample("WV: HDCP: 2.2 required", "HDCP_V2_2", "widevine_test", + WIDEVINE_GTS_MPD, PlayerActivity.TYPE_DASH), + new Sample("WV: HDCP: No digital output", "HDCP_NO_DIGTAL_OUTPUT", "widevine_test", + WIDEVINE_GTS_MPD, PlayerActivity.TYPE_DASH), + }; + + public static final Sample[] WIDEVINE_H264_MP4_CLEAR = new Sample[] { + new Sample("WV: Clear SD & HD (MP4,H264)", + "https://storage.googleapis.com/wvmedia/clear/h264/tears/tears.mpd", PlayerActivity.TYPE_DASH), - new Sample("WV: Secure (MP4,H264)", "", "widevine_test", - "https://storage.googleapis.com/wvmedia/cenc/h264/tears.mpd", + new Sample("WV: Clear SD (MP4,H264)", + "https://storage.googleapis.com/wvmedia/clear/h264/tears/tears_sd.mpd", + PlayerActivity.TYPE_DASH), + new Sample("WV: Clear HD (MP4,H264)", + "https://storage.googleapis.com/wvmedia/clear/h264/tears/tears_hd.mpd", + PlayerActivity.TYPE_DASH), + new Sample("WV: Clear UHD (MP4,H264)", + "https://storage.googleapis.com/wvmedia/clear/h264/tears/tears_uhd.mpd", + PlayerActivity.TYPE_DASH), + }; + + public static final Sample[] WIDEVINE_H264_MP4_SECURE = new Sample[] { + new Sample("WV: Secure SD & HD (MP4,H264)", "", "widevine_test", + "https://storage.googleapis.com/wvmedia/cenc/h264/tears/tears.mpd", + PlayerActivity.TYPE_DASH), + new Sample("WV: Secure SD (MP4,H264)", "", "widevine_test", + "https://storage.googleapis.com/wvmedia/cenc/h264/tears/tears_sd.mpd", + PlayerActivity.TYPE_DASH), + new Sample("WV: Secure HD (MP4,H264)", "", "widevine_test", + "https://storage.googleapis.com/wvmedia/cenc/h264/tears/tears_hd.mpd", + PlayerActivity.TYPE_DASH), + new Sample("WV: Secure UHD (MP4,H264)", "", "widevine_test", + "https://storage.googleapis.com/wvmedia/cenc/h264/tears/tears_uhd.mpd", PlayerActivity.TYPE_DASH), }; public static final Sample[] WIDEVINE_VP9_WEBM_CLEAR = new Sample[] { - new Sample("WV: Clear (WebM,VP9)", - "https://storage.googleapis.com/wvmedia/clear/vp9/sintel-multicodec-4k/sintel-vp9.mpd", + new Sample("WV: Clear SD & HD (WebM,VP9)", + "https://storage.googleapis.com/wvmedia/clear/vp9/tears/tears.mpd", + PlayerActivity.TYPE_DASH), + new Sample("WV: Clear SD (WebM,VP9)", + "https://storage.googleapis.com/wvmedia/clear/vp9/tears/tears_sd.mpd", + PlayerActivity.TYPE_DASH), + new Sample("WV: Clear HD (WebM,VP9)", + "https://storage.googleapis.com/wvmedia/clear/vp9/tears/tears_hd.mpd", + PlayerActivity.TYPE_DASH), + new Sample("WV: Clear UHD (WebM,VP9)", + "https://storage.googleapis.com/wvmedia/clear/vp9/tears/tears_uhd.mpd", PlayerActivity.TYPE_DASH), }; public static final Sample[] WIDEVINE_VP9_WEBM_SECURE = new Sample[] { - new Sample("WV: Secure (WebM,VP9)", "01234567", "widevine_test", - "https://storage.googleapis.com/wvmedia/cenc/vp9/sintel-multicodec-4k/sintel-vp9.mpd", + new Sample("WV: Secure SD & HD (WebM,VP9)", "", "widevine_test", + "https://storage.googleapis.com/wvmedia/cenc/vp9/tears/tears.mpd", + PlayerActivity.TYPE_DASH), + new Sample("WV: Secure SD (WebM,VP9)", "", "widevine_test", + "https://storage.googleapis.com/wvmedia/cenc/vp9/tears/tears_sd.mpd", + PlayerActivity.TYPE_DASH), + new Sample("WV: Secure HD (WebM,VP9)", "", "widevine_test", + "https://storage.googleapis.com/wvmedia/cenc/vp9/tears/tears_hd.mpd", + PlayerActivity.TYPE_DASH), + new Sample("WV: Secure UHD (WebM,VP9)", "", "widevine_test", + "https://storage.googleapis.com/wvmedia/cenc/vp9/tears/tears_uhd.mpd", + PlayerActivity.TYPE_DASH), + }; + + public static final Sample[] WIDEVINE_H265_MP4_CLEAR = new Sample[] { + new Sample("WV: Clear SD & HD (MP4,H265)", + "https://storage.googleapis.com/wvmedia/clear/hevc/tears/tears.mpd", + PlayerActivity.TYPE_DASH), + new Sample("WV: Clear SD (MP4,H265)", + "https://storage.googleapis.com/wvmedia/clear/hevc/tears/tears_sd.mpd", + PlayerActivity.TYPE_DASH), + new Sample("WV: Clear HD (MP4,H265)", + "https://storage.googleapis.com/wvmedia/clear/hevc/tears/tears_hd.mpd", + PlayerActivity.TYPE_DASH), + new Sample("WV: Clear UHD (MP4,H265)", + "https://storage.googleapis.com/wvmedia/clear/hevc/tears/tears_uhd.mpd", + PlayerActivity.TYPE_DASH), + }; + + public static final Sample[] WIDEVINE_H265_MP4_SECURE = new Sample[] { + new Sample("WV: Secure SD & HD (MP4,H265)", "", "widevine_test", + "https://storage.googleapis.com/wvmedia/cenc/hevc/tears/tears.mpd", + PlayerActivity.TYPE_DASH), + new Sample("WV: Secure SD (MP4,H265)", "", "widevine_test", + "https://storage.googleapis.com/wvmedia/cenc/hevc/tears/tears_sd.mpd", + PlayerActivity.TYPE_DASH), + new Sample("WV: Secure HD (MP4,H265)", "", "widevine_test", + "https://storage.googleapis.com/wvmedia/cenc/hevc/tears/tears_hd.mpd", + PlayerActivity.TYPE_DASH), + new Sample("WV: Secure UHD (MP4,H265)", "", "widevine_test", + "https://storage.googleapis.com/wvmedia/cenc/hevc/tears/tears_uhd.mpd", PlayerActivity.TYPE_DASH), }; diff --git a/demo/src/main/res/values/strings.xml b/demo/src/main/res/values/strings.xml index 2fbab91ef3..70d9f20bdb 100644 --- a/demo/src/main/res/values/strings.xml +++ b/demo/src/main/res/values/strings.xml @@ -37,11 +37,19 @@ [off] - Protected content not supported on API levels below 18 + Protected content not supported on API levels below 18 - This device does not support the required DRM scheme + This device does not support the required DRM scheme - An unknown DRM error occurred + An unknown DRM error occurred + + This device does not provide a decoder for %1$s + + This device does not provide a secure decoder for %1$s + + Unable to query device decoders + + Unable to instantiate decoder %1$s Permission to access storage was denied diff --git a/library/src/main/java/com/google/android/exoplayer/MediaCodecTrackRenderer.java b/library/src/main/java/com/google/android/exoplayer/MediaCodecTrackRenderer.java index 77698d9f43..ff7f9027c6 100644 --- a/library/src/main/java/com/google/android/exoplayer/MediaCodecTrackRenderer.java +++ b/library/src/main/java/com/google/android/exoplayer/MediaCodecTrackRenderer.java @@ -81,6 +81,16 @@ public abstract class MediaCodecTrackRenderer extends SampleSourceTrackRenderer private static final int NO_SUITABLE_DECODER_ERROR = CUSTOM_ERROR_CODE_BASE + 1; private static final int DECODER_QUERY_ERROR = CUSTOM_ERROR_CODE_BASE + 2; + /** + * The mime type for which a decoder was being initialized. + */ + public final String mimeType; + + /** + * Whether it was required that the decoder support a secure output path. + */ + public final boolean secureDecoderRequired; + /** * The name of the decoder that failed to initialize. Null if no suitable decoder was found. */ @@ -91,15 +101,20 @@ public abstract class MediaCodecTrackRenderer extends SampleSourceTrackRenderer */ public final String diagnosticInfo; - public DecoderInitializationException(MediaFormat mediaFormat, Throwable cause, int errorCode) { + public DecoderInitializationException(MediaFormat mediaFormat, Throwable cause, + boolean secureDecoderRequired, int errorCode) { super("Decoder init failed: [" + errorCode + "], " + mediaFormat, cause); + this.mimeType = mediaFormat.mimeType; + this.secureDecoderRequired = secureDecoderRequired; this.decoderName = null; this.diagnosticInfo = buildCustomDiagnosticInfo(errorCode); } public DecoderInitializationException(MediaFormat mediaFormat, Throwable cause, - String decoderName) { + boolean secureDecoderRequired, String decoderName) { super("Decoder init failed: " + decoderName + ", " + mediaFormat, cause); + this.mimeType = mediaFormat.mimeType; + this.secureDecoderRequired = secureDecoderRequired; this.decoderName = decoderName; this.diagnosticInfo = Util.SDK_INT >= 21 ? getDiagnosticInfoV21(cause) : null; } @@ -313,12 +328,12 @@ public abstract class MediaCodecTrackRenderer extends SampleSourceTrackRenderer decoderInfo = getDecoderInfo(mimeType, requiresSecureDecoder); } catch (DecoderQueryException e) { notifyAndThrowDecoderInitError(new DecoderInitializationException(format, e, - DecoderInitializationException.DECODER_QUERY_ERROR)); + requiresSecureDecoder, DecoderInitializationException.DECODER_QUERY_ERROR)); } if (decoderInfo == null) { notifyAndThrowDecoderInitError(new DecoderInitializationException(format, null, - DecoderInitializationException.NO_SUITABLE_DECODER_ERROR)); + requiresSecureDecoder, DecoderInitializationException.NO_SUITABLE_DECODER_ERROR)); } String codecName = decoderInfo.name; @@ -343,7 +358,8 @@ public abstract class MediaCodecTrackRenderer extends SampleSourceTrackRenderer inputBuffers = codec.getInputBuffers(); outputBuffers = codec.getOutputBuffers(); } catch (Exception e) { - notifyAndThrowDecoderInitError(new DecoderInitializationException(format, e, codecName)); + notifyAndThrowDecoderInitError(new DecoderInitializationException(format, e, + requiresSecureDecoder, codecName)); } codecHotswapTimeMs = getState() == TrackRenderer.STATE_STARTED ? SystemClock.elapsedRealtime() : -1;