From faa82f0810da0f81d00d9d1fb074f6dd245e89a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaquim=20St=C3=A4hli?= Date: Tue, 1 Oct 2019 16:54:54 +0200 Subject: [PATCH 1/6] POC drm multikey HD/SD, disallow Trackselector to select higher resolution on L3 only device. --- .../mediaplayer/SRGMediaPlayerController.java | 26 ++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java b/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java index 0a4e860..d7eed2e 100644 --- a/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java +++ b/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java @@ -577,6 +577,8 @@ public interface Listener { private @SRGStreamType int currentStreamType; private int numberOfDrmRetry = 0; + @Nullable + private FrameworkMediaDrm mediaDrm; public static String getName() { return NAME; @@ -655,8 +657,14 @@ public SRGMediaPlayerController(Context context, String tag, @Nullable DrmConfig try { UUID drmType = drmConfig.getDrmType(); monitoringDrmCallback = new MonitoringDrmCallback(new HttpMediaDrmCallback(drmConfig.getLicenceUrl(), httpDataSourceFactory)); + mediaDrm = FrameworkMediaDrm.newInstance(drmType); + if (drmType == C.WIDEVINE_UUID && TextUtils.equals(mediaDrm.getPropertyString("securityLevel"), "L3")) { + trackSelector.setParameters(new DefaultTrackSelector.ParametersBuilder() + //.setMaxVideoSizeSd() //TODO if we use standard size (720p) + .setMaxVideoSize(959, 543)); // Min HD 540P Patrizio streams + } drmSessionManager = new DefaultDrmSessionManager<>(drmType, - FrameworkMediaDrm.newInstance(drmType), + mediaDrm, monitoringDrmCallback, null, true); drmSessionManager.addListener(mainHandler, this); offlineLicenseHelper = OfflineLicenseHelper.newWidevineInstance(this.drmConfig.getLicenceUrl(), @@ -807,11 +815,13 @@ public void prepare(@NonNull Uri uri, Long finalPlaybackStartPosition = playbackStartPosition; Runnable prepareViewAndPlayer = () -> prepareViewAndPlayer(uri, streamType, finalPlaybackStartPosition); - if (drmConfig != null && licenseStoreDelegate != null) { - downloadOrApplyOfflineLicense(uri, prepareViewAndPlayer, drmConfig); - } else { - prepareViewAndPlayer.run(); - } + prepareViewAndPlayer.run(); + // FIXME disable offline license because it is not possible when server doesn't respond with all key. +// if (drmConfig != null && licenseStoreDelegate != null) { +// downloadOrApplyOfflineLicense(uri, prepareViewAndPlayer, drmConfig); +// } else { +// prepareViewAndPlayer.run(); +// } broadcastEvent(Event.Type.SEGMENT_LIST_CHANGE); } @@ -1093,6 +1103,10 @@ private void releaseExoplayer() { exoPlayer.release(); currentMediaUri = null; audioCapabilitiesReceiver.unregister(); + if (mediaDrm != null) { + mediaDrm.release(); + mediaDrm = null; + } } //endregion From 1eea37e423a2050b6fe28262360a26c65f4364f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaquim=20St=C3=A4hli?= Date: Fri, 4 Oct 2019 13:33:30 +0200 Subject: [PATCH 2/6] Remove offline license as we can't download and set multi key license. --- .../mediaplayer/SRGMediaPlayerController.java | 111 +----------------- 1 file changed, 5 insertions(+), 106 deletions(-) diff --git a/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java b/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java index d7eed2e..0265ab9 100644 --- a/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java +++ b/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java @@ -6,13 +6,13 @@ import android.media.AudioManager; import android.media.MediaCodec; import android.net.Uri; -import android.os.AsyncTask; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.SystemClock; import android.support.v4.media.session.MediaSessionCompat; import android.text.TextUtils; +import android.util.Base64; import android.util.Log; import android.util.Pair; import android.view.SurfaceHolder; @@ -47,14 +47,11 @@ import com.google.android.exoplayer2.audio.AudioCapabilitiesReceiver; import com.google.android.exoplayer2.drm.DefaultDrmSessionEventListener; import com.google.android.exoplayer2.drm.DefaultDrmSessionManager; -import com.google.android.exoplayer2.drm.DrmInitData; -import com.google.android.exoplayer2.drm.DrmSession; import com.google.android.exoplayer2.drm.ExoMediaDrm; import com.google.android.exoplayer2.drm.FrameworkMediaCrypto; import com.google.android.exoplayer2.drm.FrameworkMediaDrm; import com.google.android.exoplayer2.drm.HttpMediaDrmCallback; import com.google.android.exoplayer2.drm.MediaDrmCallback; -import com.google.android.exoplayer2.drm.OfflineLicenseHelper; import com.google.android.exoplayer2.drm.UnsupportedDrmException; import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector; import com.google.android.exoplayer2.source.MediaSource; @@ -62,8 +59,6 @@ import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.source.dash.DashMediaSource; -import com.google.android.exoplayer2.source.dash.DashUtil; -import com.google.android.exoplayer2.source.dash.manifest.DashManifest; import com.google.android.exoplayer2.source.hls.DefaultHlsDataSourceFactory; import com.google.android.exoplayer2.source.hls.HlsMediaSource; import com.google.android.exoplayer2.text.Cue; @@ -74,7 +69,6 @@ import com.google.android.exoplayer2.trackselection.TrackSelection; import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.ui.spherical.SphericalSurfaceView; -import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter; import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; import com.google.android.exoplayer2.upstream.DefaultHttpDataSource; @@ -99,8 +93,6 @@ import java.util.WeakHashMap; import ch.srg.mediaplayer.segment.model.Segment; -import ch.srg.mediaplayer.utils.FileLicenseStore; -import ch.srg.mediaplayer.utils.LicenseStoreDelegate; import ch.srg.mediaplayer.utils.MonitorTransferListener; /** @@ -146,8 +138,6 @@ public class SRGMediaPlayerController implements Handler.Callback, private boolean playingOrBuffering; @Nullable private DefaultDrmSessionManager drmSessionManager; - @Nullable - OfflineLicenseHelper offlineLicenseHelper; private DefaultHttpDataSourceFactory httpDataSourceFactory; public enum ViewType { @@ -506,9 +496,6 @@ public interface Listener { } - - private LicenseStoreDelegate licenseStoreDelegate; - private Context context; private Handler mainHandler; @@ -667,8 +654,6 @@ public SRGMediaPlayerController(Context context, String tag, @Nullable DrmConfig mediaDrm, monitoringDrmCallback, null, true); drmSessionManager.addListener(mainHandler, this); - offlineLicenseHelper = OfflineLicenseHelper.newWidevineInstance(this.drmConfig.getLicenceUrl(), - httpDataSourceFactory); } catch (UnsupportedDrmException e) { fatalError = new SRGMediaPlayerException(null, e, SRGMediaPlayerException.Reason.DRM); } @@ -700,41 +685,6 @@ public SRGMediaPlayerController(Context context, String tag, @Nullable DrmConfig // See https://github.com/SRGSSR/SRGMediaPlayer-Android/issues/25 } } - - licenseStoreDelegate = new FileLicenseStore(context); - } - - private void applyOfflineLicense(byte[] offlineLicenseKeySetId) { - if (drmSessionManager != null && offlineLicenseKeySetId != null) { - drmSessionManager.setMode(DefaultDrmSessionManager.MODE_PLAYBACK, offlineLicenseKeySetId); - if (debugMode) { - debugPrintLicenseDurationRemaining(offlineLicenseKeySetId); - } - } - } - - private boolean isOfflineLicenseExpired(@NonNull byte[] offlineLicenseKeySetId) { - if (offlineLicenseHelper != null) { - try { - Pair validity = offlineLicenseHelper.getLicenseDurationRemainingSec(offlineLicenseKeySetId); - return validity.first <= MINIMUM_DRM_LICENSE_DURATION_SECONDS; - } catch (DrmSession.DrmSessionException e) { - Log.e(TAG, "offline license test", e); - return true; - } - } - return true; - } - - private void debugPrintLicenseDurationRemaining(byte[] offlineLicenseKeySetId) { - if (drmConfig != null && offlineLicenseHelper != null) { - try { - Pair validity = offlineLicenseHelper.getLicenseDurationRemainingSec(offlineLicenseKeySetId); - Log.v(TAG, "DRM validity: license=" + validity.first + "s, playback=" + validity.second + " s"); - } catch (DrmSession.DrmSessionException e) { - Log.v(TAG, "DRM validity: error", e); - } - } } //region playback control @@ -814,48 +764,10 @@ public void prepare(@NonNull Uri uri, } Long finalPlaybackStartPosition = playbackStartPosition; - Runnable prepareViewAndPlayer = () -> prepareViewAndPlayer(uri, streamType, finalPlaybackStartPosition); - prepareViewAndPlayer.run(); - // FIXME disable offline license because it is not possible when server doesn't respond with all key. -// if (drmConfig != null && licenseStoreDelegate != null) { -// downloadOrApplyOfflineLicense(uri, prepareViewAndPlayer, drmConfig); -// } else { -// prepareViewAndPlayer.run(); -// } + prepareViewAndPlayer(uri, streamType, finalPlaybackStartPosition); broadcastEvent(Event.Type.SEGMENT_LIST_CHANGE); } - private void downloadOrApplyOfflineLicense(@NonNull Uri uri, @NonNull Runnable prepareViewAndPlayer, @NonNull DrmConfig drmConfig) { - AsyncTask.execute(() -> { - try { - DataSource dataSource = httpDataSourceFactory.createDataSource(); - DashManifest dashManifest = DashUtil.loadManifest(dataSource, uri); - DrmInitData drmInitData = DashUtil.loadDrmInitData(dataSource, dashManifest.getPeriod(0)); - byte[] offlineLicenseKeySetId = licenseStoreDelegate.fetch(drmInitData); - if (offlineLicenseKeySetId != null && !isOfflineLicenseExpired(offlineLicenseKeySetId)) { - Log.v(TAG, "DRM Restored"); - applyOfflineLicense(offlineLicenseKeySetId); - drmRequestOffline = true; - } else { - Log.v(TAG, "Downloading DRM"); - drmRequestOffline = false; - long start = SystemClock.elapsedRealtime(); - if (offlineLicenseHelper == null) { - throw new IllegalStateException("No license helper when trying to download license"); - } else { - byte[] keySet = offlineLicenseHelper.downloadLicense(drmInitData); - licenseStoreDelegate.store(drmInitData, keySet); - applyOfflineLicense(keySet); - drmRequestDuration += SystemClock.elapsedRealtime() - start; - } - } - } catch (Exception e) { - Log.e(TAG, "License Download", e); - } finally { - mainHandler.post(prepareViewAndPlayer); - } - }); - } private void prepareViewAndPlayer(@NonNull Uri uri, @SRGStreamType int streamType, Long playbackStartPosition) { try { @@ -961,7 +873,6 @@ private void prepareExoplayer(@NonNull Uri videoUri, @Nullable Long playbackStar true); DefaultDataSourceFactory dataSourceFactory = new DefaultDataSourceFactory(context, getDefaultBandwidthMeter(context), httpDataSourceFactory); - MediaSource mediaSource; switch (streamType) { @@ -1084,9 +995,6 @@ private void doRelease() { releaseExoplayer(); unregisterAllEventListeners(); stopPeriodicUpdate(); - if (offlineLicenseHelper != null) { - offlineLicenseHelper.release(); - } } } @@ -2355,12 +2263,7 @@ public void onRepeatModeChanged(int repeatMode) { * Retry exoplayer playback after an error. */ public void retry() { - Runnable retry = exoPlayer::retry; - if (drmConfig != null && licenseStoreDelegate != null) { - downloadOrApplyOfflineLicense(currentMediaUri, retry, drmConfig); - } else { - retry.run(); - } + exoPlayer.retry(); } @Override @@ -2507,9 +2410,9 @@ public MonitoringDrmCallback(MediaDrmCallback mediaDrmCallback) { @Override public byte[] executeProvisionRequest(UUID uuid, ExoMediaDrm.ProvisionRequest request) throws Exception { - Log.v(TAG, "DRM: executeProvisionRequest"); long now = SystemClock.elapsedRealtime(); byte[] result = callback.executeProvisionRequest(uuid, request); + Log.v(TAG, "DRM: executeProvisionRequest " + Base64.encodeToString(result, Base64.DEFAULT)); drmRequestDuration += SystemClock.elapsedRealtime() - now; drmRequestOffline = false; return result; @@ -2517,19 +2420,15 @@ public byte[] executeProvisionRequest(UUID uuid, ExoMediaDrm.ProvisionRequest re @Override public byte[] executeKeyRequest(UUID uuid, ExoMediaDrm.KeyRequest request) throws Exception { - Log.v(TAG, "DRM: executeKeyRequest"); long now = SystemClock.elapsedRealtime(); byte[] result = callback.executeKeyRequest(uuid, request); + Log.v(TAG, "DRM: executeKeyRequest " + Base64.encodeToString(result, Base64.DEFAULT)); drmRequestDuration += SystemClock.elapsedRealtime() - now; drmRequestOffline = false; return result; } } - public void setLicenseStoreDelegate(LicenseStoreDelegate licenseStoreDelegate) { - this.licenseStoreDelegate = licenseStoreDelegate; - } - private static synchronized DefaultBandwidthMeter getDefaultBandwidthMeter(Context context) { if (singletonBandwidthMeter == null) { singletonBandwidthMeter = new DefaultBandwidthMeter.Builder(context.getApplicationContext()).build(); From 0cbe1c94369d28c45fc8fdb161de72ae94a77d70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaquim=20St=C3=A4hli?= Date: Fri, 4 Oct 2019 14:21:23 +0200 Subject: [PATCH 3/6] Add a DrmUtils to handle security level --- .../mediaplayer/SRGMediaPlayerController.java | 6 +- .../ch/srg/mediaplayer/utils/DrmUtils.java | 57 +++++++++++++++++++ 2 files changed, 60 insertions(+), 3 deletions(-) create mode 100644 srgmediaplayer/src/main/java/ch/srg/mediaplayer/utils/DrmUtils.java diff --git a/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java b/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java index 0265ab9..03d986a 100644 --- a/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java +++ b/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java @@ -93,6 +93,7 @@ import java.util.WeakHashMap; import ch.srg.mediaplayer.segment.model.Segment; +import ch.srg.mediaplayer.utils.DrmUtils; import ch.srg.mediaplayer.utils.MonitorTransferListener; /** @@ -645,10 +646,9 @@ public SRGMediaPlayerController(Context context, String tag, @Nullable DrmConfig UUID drmType = drmConfig.getDrmType(); monitoringDrmCallback = new MonitoringDrmCallback(new HttpMediaDrmCallback(drmConfig.getLicenceUrl(), httpDataSourceFactory)); mediaDrm = FrameworkMediaDrm.newInstance(drmType); - if (drmType == C.WIDEVINE_UUID && TextUtils.equals(mediaDrm.getPropertyString("securityLevel"), "L3")) { + if (drmType == C.WIDEVINE_UUID && DrmUtils.isWidevineL3(mediaDrm)) { trackSelector.setParameters(new DefaultTrackSelector.ParametersBuilder() - //.setMaxVideoSizeSd() //TODO if we use standard size (720p) - .setMaxVideoSize(959, 543)); // Min HD 540P Patrizio streams + .setMaxVideoSizeSd()); } drmSessionManager = new DefaultDrmSessionManager<>(drmType, mediaDrm, diff --git a/srgmediaplayer/src/main/java/ch/srg/mediaplayer/utils/DrmUtils.java b/srgmediaplayer/src/main/java/ch/srg/mediaplayer/utils/DrmUtils.java new file mode 100644 index 0000000..8f13c4c --- /dev/null +++ b/srgmediaplayer/src/main/java/ch/srg/mediaplayer/utils/DrmUtils.java @@ -0,0 +1,57 @@ +package ch.srg.mediaplayer.utils; + +import android.media.MediaDrm; +import android.media.UnsupportedSchemeException; +import android.text.TextUtils; + +import androidx.annotation.NonNull; +import androidx.annotation.RequiresApi; + +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.drm.FrameworkMediaDrm; + +/** + * Copyright (c) SRG SSR. All rights reserved. + *

+ * License information is available from the LICENSE file. + */ + +@RequiresApi(api = 18) +public final class DrmUtils { + private static final String WIDEVINE_SECURITY_LEVEL = "securityLevel"; + private static final String WIDEVINE_SECURITY_L1 = "L1"; + private static final String WIDEVINE_SECURITY_L3 = "L3"; + + /** + *

+     * @param hdcpLevel
+     * @see {@link MediaDrm#HDCP_V1}
+     * @see {@link MediaDrm#HDCP_V2}
+     * @see {@link MediaDrm#HDCP_V2_1}
+     * @see {@link MediaDrm#HDCP_V2_2}
+     * @see {@link MediaDrm#HDCP_V2_3}
+     * 
+ */ + @RequiresApi(api = 28) + public static boolean isHdcpCompatible(int hdcpLevel) { + try { + MediaDrm drm = new MediaDrm(C.WIDEVINE_UUID); + int maxHDCPLevel = drm.getMaxHdcpLevel(); + return hdcpLevel <= maxHDCPLevel; + } catch (UnsupportedSchemeException e) { + return false; + } + } + + public static boolean isWidevineL1(@NonNull FrameworkMediaDrm mediaDrm) { + return TextUtils.equals(WIDEVINE_SECURITY_L1, getSecurityLevel(mediaDrm)); + } + + public static boolean isWidevineL3(@NonNull FrameworkMediaDrm mediaDrm) { + return TextUtils.equals(WIDEVINE_SECURITY_L3, getSecurityLevel(mediaDrm)); + } + + private static String getSecurityLevel(@NonNull FrameworkMediaDrm mediaDrm) { + return mediaDrm.getPropertyString(WIDEVINE_SECURITY_LEVEL); + } +} From 5ea394dbb4943b305a52c15f5d3a20a39eb07fd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaquim=20St=C3=A4hli?= Date: Mon, 7 Oct 2019 15:23:56 +0200 Subject: [PATCH 4/6] Automatically "blacklist" track that device can't play. --- .../mediaplayer/SRGMediaPlayerController.java | 42 ++++++++++++++++--- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java b/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java index 03d986a..25a1a4a 100644 --- a/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java +++ b/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java @@ -93,7 +93,6 @@ import java.util.WeakHashMap; import ch.srg.mediaplayer.segment.model.Segment; -import ch.srg.mediaplayer.utils.DrmUtils; import ch.srg.mediaplayer.utils.MonitorTransferListener; /** @@ -646,10 +645,6 @@ public SRGMediaPlayerController(Context context, String tag, @Nullable DrmConfig UUID drmType = drmConfig.getDrmType(); monitoringDrmCallback = new MonitoringDrmCallback(new HttpMediaDrmCallback(drmConfig.getLicenceUrl(), httpDataSourceFactory)); mediaDrm = FrameworkMediaDrm.newInstance(drmType); - if (drmType == C.WIDEVINE_UUID && DrmUtils.isWidevineL3(mediaDrm)) { - trackSelector.setParameters(new DefaultTrackSelector.ParametersBuilder() - .setMaxVideoSizeSd()); - } drmSessionManager = new DefaultDrmSessionManager<>(drmType, mediaDrm, monitoringDrmCallback, null, true); @@ -2284,6 +2279,12 @@ public void onPlayerError(ExoPlaybackException error) { Log.w(TAG, "DRM expired key during playback. Failing (retry count: " + numberOfDrmRetry + ")"); reason = SRGMediaPlayerException.Reason.DRM_KEY_EXPIRED; } + } else if (cryptoException.getErrorCode() == MediaCodec.CryptoException.ERROR_INSUFFICIENT_OUTPUT_PROTECTION) { + if (handleInsufficientOutputProtectionError()) { + exoPlayer.retry(); + return; + } + reason = SRGMediaPlayerException.Reason.DRM; } else { reason = SRGMediaPlayerException.Reason.DRM; } @@ -2303,6 +2304,37 @@ public void onPlayerError(ExoPlaybackException error) { handlePlayerException(exception); } + /** + * Handle insufficient output protection when player get an error + * @see MediaCodec.CryptoException#ERROR_INSUFFICIENT_OUTPUT_PROTECTION + * + * @return true if something has been changed + */ + public boolean handleInsufficientOutputProtectionError() { + TrackSelectionArray trackSelections = exoPlayer.getCurrentTrackSelections(); + if (trackSelections == null) { + return false; + } + for (int i = 0; i < trackSelections.length; i++) { + TrackSelection trackSelection = trackSelections.get(i); + if (trackSelection != null) { + Format format = trackSelection.getSelectedFormat(); + if (format != null && TextUtils.equals(format.containerMimeType, "video/mp4")) { + logV("Blacklist Track[" + format.id + "] " + format.containerMimeType + " " + format.bitrate + " " + format.width + "X" + format.height); + setMinimalVideoSizeSupported(format.width - 1, format.height - 1); + return true; + } + } + } + return false; + } + + private void setMinimalVideoSizeSupported(int maxVideoWidth, int maxVideoHeight) { + DefaultTrackSelector.ParametersBuilder builder = trackSelector.buildUponParameters(); + builder.setMaxVideoSize(Math.min(trackSelector.getParameters().maxVideoWidth, maxVideoWidth), Math.min(trackSelector.getParameters().maxVideoHeight, maxVideoWidth)); + trackSelector.setParameters(builder); + } + @Override public void onPositionDiscontinuity(int reason) { broadcastEvent(Event.Type.POSITION_DISCONTINUITY); From 8a8ab13afab0b7e399d5845663b769050835268d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaquim=20St=C3=A4hli?= Date: Tue, 8 Oct 2019 10:59:17 +0200 Subject: [PATCH 5/6] Blacklist unplayable video tracks by using SelectionOverride --- .../mediaplayer/SRGDefaultTrackSelector.java | 66 +++++++++++++++++++ .../mediaplayer/SRGMediaPlayerController.java | 31 +-------- 2 files changed, 69 insertions(+), 28 deletions(-) create mode 100644 srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGDefaultTrackSelector.java diff --git a/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGDefaultTrackSelector.java b/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGDefaultTrackSelector.java new file mode 100644 index 0000000..4f33162 --- /dev/null +++ b/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGDefaultTrackSelector.java @@ -0,0 +1,66 @@ +package ch.srg.mediaplayer; + +import android.text.TextUtils; +import android.util.Log; + +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.Format; +import com.google.android.exoplayer2.source.TrackGroupArray; +import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; +import com.google.android.exoplayer2.trackselection.TrackSelection; +import com.google.android.exoplayer2.trackselection.TrackSelectionArray; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * Copyright (c) SRG SSR. All rights reserved. + *

+ * License information is available from the LICENSE file. + */ +public final class SRGDefaultTrackSelector extends DefaultTrackSelector { + private static final String TAG = "SRGTrackSelector"; + private Set blackListVideoTracks = new HashSet<>(); + + public SRGDefaultTrackSelector(TrackSelection.Factory trackSelectionFactory) { + super(trackSelectionFactory); + } + + public boolean blacklistCurrentTrackSelection(TrackSelectionArray trackSelections) { + MappedTrackInfo mappedTrackInfo = getCurrentMappedTrackInfo(); + if (trackSelections == null || mappedTrackInfo == null) { + return false; + } + for (int renderId = 0; renderId < trackSelections.length; renderId++) { + TrackSelection trackSelection = trackSelections.get(renderId); + if (trackSelection != null) { + Format format = trackSelection.getSelectedFormat(); + if (format != null && TextUtils.equals(format.containerMimeType, "video/mp4")) { + if (blackListVideoTracks.add(format)) { + Log.d(TAG, "Black list Track[" + format.containerMimeType + "] " + format.id + " " + format.bitrate + " " + format.width + "X" + format.height); + List listPlayableTracks = new ArrayList<>(); + for (int i = 0; i < trackSelection.length(); i++) { + if (!blackListVideoTracks.contains(trackSelection.getFormat(i))) { + listPlayableTracks.add(trackSelection.getIndexInTrackGroup(i)); + } + } + if (!listPlayableTracks.isEmpty()) { + int[] tabTrackId = new int[listPlayableTracks.size()]; + for (int i = 0; i < tabTrackId.length; i++) { + tabTrackId[i] = listPlayableTracks.get(i); + } + TrackGroupArray renderTrackGroup = mappedTrackInfo.getTrackGroups(renderId); + int groupIndex = renderTrackGroup.indexOf(trackSelection.getTrackGroup()); + SelectionOverride blacklistedOverride = new SelectionOverride(groupIndex, tabTrackId, C.SELECTION_REASON_ADAPTIVE, 0); + setParameters(buildUponParameters().setSelectionOverride(renderId, mappedTrackInfo.getTrackGroups(renderId), blacklistedOverride)); + return true; // Because we doesn't handle audio track, audio track are not adaptive Track group. + } + } + } + } + } + return false; + } +} diff --git a/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java b/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java index 25a1a4a..2040f8e 100644 --- a/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java +++ b/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java @@ -514,7 +514,7 @@ public interface Listener { @NonNull private final SimpleExoPlayer exoPlayer; private final AudioCapabilitiesReceiver audioCapabilitiesReceiver; - private final DefaultTrackSelector trackSelector; + private final SRGDefaultTrackSelector trackSelector; @Nullable private MediaSessionConnector mediaSessionConnector; @@ -635,7 +635,7 @@ public SRGMediaPlayerController(Context context, String tag, @Nullable DrmConfig DefaultHttpDataSource.DEFAULT_READ_TIMEOUT_MILLIS, true); - trackSelector = new DefaultTrackSelector(videoTrackSelectionFactory); + trackSelector = new SRGDefaultTrackSelector(videoTrackSelectionFactory); EventLogger eventLogger = new EventLogger(trackSelector); drmSessionManager = null; UnsupportedDrmException unsupportedDrm = null; @@ -2280,7 +2280,7 @@ public void onPlayerError(ExoPlaybackException error) { reason = SRGMediaPlayerException.Reason.DRM_KEY_EXPIRED; } } else if (cryptoException.getErrorCode() == MediaCodec.CryptoException.ERROR_INSUFFICIENT_OUTPUT_PROTECTION) { - if (handleInsufficientOutputProtectionError()) { + if (trackSelector.blacklistCurrentTrackSelection(exoPlayer.getCurrentTrackSelections())) { exoPlayer.retry(); return; } @@ -2304,31 +2304,6 @@ public void onPlayerError(ExoPlaybackException error) { handlePlayerException(exception); } - /** - * Handle insufficient output protection when player get an error - * @see MediaCodec.CryptoException#ERROR_INSUFFICIENT_OUTPUT_PROTECTION - * - * @return true if something has been changed - */ - public boolean handleInsufficientOutputProtectionError() { - TrackSelectionArray trackSelections = exoPlayer.getCurrentTrackSelections(); - if (trackSelections == null) { - return false; - } - for (int i = 0; i < trackSelections.length; i++) { - TrackSelection trackSelection = trackSelections.get(i); - if (trackSelection != null) { - Format format = trackSelection.getSelectedFormat(); - if (format != null && TextUtils.equals(format.containerMimeType, "video/mp4")) { - logV("Blacklist Track[" + format.id + "] " + format.containerMimeType + " " + format.bitrate + " " + format.width + "X" + format.height); - setMinimalVideoSizeSupported(format.width - 1, format.height - 1); - return true; - } - } - } - return false; - } - private void setMinimalVideoSizeSupported(int maxVideoWidth, int maxVideoHeight) { DefaultTrackSelector.ParametersBuilder builder = trackSelector.buildUponParameters(); builder.setMaxVideoSize(Math.min(trackSelector.getParameters().maxVideoWidth, maxVideoWidth), Math.min(trackSelector.getParameters().maxVideoHeight, maxVideoWidth)); From 70bc2e2c8e35803c647bf3911afad8a1434722b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaquim=20St=C3=A4hli?= Date: Tue, 8 Oct 2019 16:58:39 +0200 Subject: [PATCH 6/6] Fix quality override not working when video track was blacklisted --- .../mediaplayer/SRGDefaultTrackSelector.java | 42 +++++++++++++++++-- .../mediaplayer/SRGMediaPlayerController.java | 17 +------- 2 files changed, 40 insertions(+), 19 deletions(-) diff --git a/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGDefaultTrackSelector.java b/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGDefaultTrackSelector.java index 4f33162..64735dd 100644 --- a/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGDefaultTrackSelector.java +++ b/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGDefaultTrackSelector.java @@ -1,6 +1,5 @@ package ch.srg.mediaplayer; -import android.text.TextUtils; import android.util.Log; import com.google.android.exoplayer2.C; @@ -28,6 +27,7 @@ public SRGDefaultTrackSelector(TrackSelection.Factory trackSelectionFactory) { super(trackSelectionFactory); } + public boolean blacklistCurrentTrackSelection(TrackSelectionArray trackSelections) { MappedTrackInfo mappedTrackInfo = getCurrentMappedTrackInfo(); if (trackSelections == null || mappedTrackInfo == null) { @@ -35,9 +35,9 @@ public boolean blacklistCurrentTrackSelection(TrackSelectionArray trackSelection } for (int renderId = 0; renderId < trackSelections.length; renderId++) { TrackSelection trackSelection = trackSelections.get(renderId); - if (trackSelection != null) { + if (trackSelection != null && mappedTrackInfo.getRendererType(renderId) == C.TRACK_TYPE_VIDEO) { Format format = trackSelection.getSelectedFormat(); - if (format != null && TextUtils.equals(format.containerMimeType, "video/mp4")) { + if (format != null) { if (blackListVideoTracks.add(format)) { Log.d(TAG, "Black list Track[" + format.containerMimeType + "] " + format.id + " " + format.bitrate + " " + format.width + "X" + format.height); List listPlayableTracks = new ArrayList<>(); @@ -63,4 +63,40 @@ public boolean blacklistCurrentTrackSelection(TrackSelectionArray trackSelection } return false; } + + /** + *

+     * Clear previously video track selection override and try with quality constrains.
+     * This constrain can be override by {@link SRGDefaultTrackSelector#blacklistCurrentTrackSelection}
+     * @param quality bandwidth quality in bits/sec or null to disable
+     * 
+ */ + public void setQualityOverride(Long quality) { + MappedTrackInfo mappedTrackInfo = getCurrentMappedTrackInfo(); + if (mappedTrackInfo == null) { + return; + } + DefaultTrackSelector.ParametersBuilder parameters = buildUponParameters(); + for (int renderId = 0; renderId < mappedTrackInfo.getRendererCount(); renderId++) { + if (mappedTrackInfo.getRendererType(renderId) == C.TRACK_TYPE_VIDEO) { + parameters.clearSelectionOverrides(renderId); + clearBlacklistItem(); + break; + } + } + if (quality == null) { + setParameters(parameters.setMaxVideoBitrate(Integer.MAX_VALUE).setForceLowestBitrate(false)); + } else { + if (quality == 0) { + setParameters(parameters.setForceLowestBitrate(true)); + } else { + setParameters(parameters.setMaxVideoBitrate(quality.intValue()).setForceLowestBitrate(false)); + } + } + } + + private void clearBlacklistItem() { + blackListVideoTracks.clear(); + } + } diff --git a/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java b/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java index 2040f8e..d75f11c 100644 --- a/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java +++ b/srgmediaplayer/src/main/java/ch/srg/mediaplayer/SRGMediaPlayerController.java @@ -1940,16 +1940,7 @@ public Long getCurrentBandwidth() { * @param quality bandwidth quality in bits/sec or null to disable */ public void setQualityOverride(Long quality) { - DefaultTrackSelector.ParametersBuilder parameters = trackSelector.buildUponParameters(); - if (quality == null) { - trackSelector.setParameters(parameters.setMaxVideoBitrate(Integer.MAX_VALUE).setForceLowestBitrate(false)); - } else { - if (quality == 0) { - trackSelector.setParameters(parameters.setForceLowestBitrate(true)); - } else { - trackSelector.setParameters(parameters.setMaxVideoBitrate(quality.intValue()).setForceLowestBitrate(false)); - } - } + trackSelector.setQualityOverride(quality); } /** @@ -2304,12 +2295,6 @@ public void onPlayerError(ExoPlaybackException error) { handlePlayerException(exception); } - private void setMinimalVideoSizeSupported(int maxVideoWidth, int maxVideoHeight) { - DefaultTrackSelector.ParametersBuilder builder = trackSelector.buildUponParameters(); - builder.setMaxVideoSize(Math.min(trackSelector.getParameters().maxVideoWidth, maxVideoWidth), Math.min(trackSelector.getParameters().maxVideoHeight, maxVideoWidth)); - trackSelector.setParameters(builder); - } - @Override public void onPositionDiscontinuity(int reason) { broadcastEvent(Event.Type.POSITION_DISCONTINUITY);