refactor: move view type and drm in source (#3867)
* perf: ensure we do not provide callback to native if no callback provided from app * chore: rework bufferConfig to make it more generic and reduce ReactExoplayerView code size * chore: improve issue template * fix(android): avoid video view flickering at playback startup * chore(android): refactor DRM props into a dedicated class * Update android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java * chore: fix linter * fix: ensure drm prop is correctly cleaned * feat(android): move viewType (secure texture) & drm inside the source The origianl behavior has been kept for interoperability, but marked as deprecated in doc * chore: fix linter * chore(ios): move drm prop in source like on android * chore: fix linter * chore: clean log * fix: allow to disable secure View * chore: fix viewType resolution (source value was not handled) * chore: use contentDeepEquals instead of manual checks * chore: fix linter * fix: ensure player doesn't start when view is unmounted * Fix/ensure view drop stop playback startup (#3875) * fix: ensure player doesn't start when view is unmounted * chore: revert change * chore: add warning in case of invalid Surface configuration * chore: code clean * fix: simplify surface management * chore: restore previous code * chore: fix typo * chore: code cleanup * feat(android): add multiDrm flag support * docs: update docs * chore: fix ios build * chore: fix deprecated declaration --------- Co-authored-by: Krzysztof Moch <krzysmoch.programs@gmail.com>
This commit is contained in:
parent
08f6caa645
commit
66dcf32b56
@ -1,6 +1,7 @@
|
|||||||
package com.brentvatne.common.api
|
package com.brentvatne.common.api
|
||||||
|
|
||||||
import com.brentvatne.common.toolbox.ReactBridgeUtils.safeGetArray
|
import com.brentvatne.common.toolbox.ReactBridgeUtils.safeGetArray
|
||||||
|
import com.brentvatne.common.toolbox.ReactBridgeUtils.safeGetBool
|
||||||
import com.brentvatne.common.toolbox.ReactBridgeUtils.safeGetString
|
import com.brentvatne.common.toolbox.ReactBridgeUtils.safeGetString
|
||||||
import com.facebook.react.bridge.ReadableMap
|
import com.facebook.react.bridge.ReadableMap
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
@ -29,12 +30,28 @@ class DRMProps {
|
|||||||
* DRM Http Header to access to license server
|
* DRM Http Header to access to license server
|
||||||
*/
|
*/
|
||||||
var drmLicenseHeader: Array<String> = emptyArray<String>()
|
var drmLicenseHeader: Array<String> = emptyArray<String>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flag to enable key rotation support
|
||||||
|
*/
|
||||||
|
var multiDrm: Boolean = false
|
||||||
|
|
||||||
|
/** return true if this and src are equals */
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (other == null || other !is DRMProps) return false
|
||||||
|
return drmType == other.drmType &&
|
||||||
|
drmLicenseServer == other.drmLicenseServer &&
|
||||||
|
multiDrm == other.multiDrm &&
|
||||||
|
drmLicenseHeader.contentDeepEquals(other.drmLicenseHeader) // drmLicenseHeader is never null
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val PROP_DRM_TYPE = "type"
|
private const val PROP_DRM_TYPE = "type"
|
||||||
private const val PROP_DRM_LICENSE_SERVER = "licenseServer"
|
private const val PROP_DRM_LICENSE_SERVER = "licenseServer"
|
||||||
private const val PROP_DRM_HEADERS = "headers"
|
private const val PROP_DRM_HEADERS = "headers"
|
||||||
private const val PROP_DRM_HEADERS_KEY = "key"
|
private const val PROP_DRM_HEADERS_KEY = "key"
|
||||||
private const val PROP_DRM_HEADERS_VALUE = "value"
|
private const val PROP_DRM_HEADERS_VALUE = "value"
|
||||||
|
private const val PROP_DRM_MULTI_DRM = "multiDrm"
|
||||||
|
|
||||||
/** parse the source ReadableMap received from app */
|
/** parse the source ReadableMap received from app */
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@ -44,6 +61,7 @@ class DRMProps {
|
|||||||
drm = DRMProps()
|
drm = DRMProps()
|
||||||
drm.drmType = safeGetString(src, PROP_DRM_TYPE)
|
drm.drmType = safeGetString(src, PROP_DRM_TYPE)
|
||||||
drm.drmLicenseServer = safeGetString(src, PROP_DRM_LICENSE_SERVER)
|
drm.drmLicenseServer = safeGetString(src, PROP_DRM_LICENSE_SERVER)
|
||||||
|
drm.multiDrm = safeGetBool(src, PROP_DRM_MULTI_DRM, false)
|
||||||
val drmHeadersArray = safeGetArray(src, PROP_DRM_HEADERS)
|
val drmHeadersArray = safeGetArray(src, PROP_DRM_HEADERS)
|
||||||
if (drm.drmType != null && drm.drmLicenseServer != null) {
|
if (drm.drmType != null && drm.drmLicenseServer != null) {
|
||||||
if (drmHeadersArray != null) {
|
if (drmHeadersArray != null) {
|
||||||
|
@ -6,6 +6,7 @@ import android.content.Context
|
|||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.text.TextUtils
|
import android.text.TextUtils
|
||||||
|
import com.brentvatne.common.api.DRMProps.Companion.parse
|
||||||
import com.brentvatne.common.toolbox.DebugLog
|
import com.brentvatne.common.toolbox.DebugLog
|
||||||
import com.brentvatne.common.toolbox.DebugLog.e
|
import com.brentvatne.common.toolbox.DebugLog.e
|
||||||
import com.brentvatne.common.toolbox.ReactBridgeUtils.safeGetArray
|
import com.brentvatne.common.toolbox.ReactBridgeUtils.safeGetArray
|
||||||
@ -46,6 +47,11 @@ class Source {
|
|||||||
/** http header list */
|
/** http header list */
|
||||||
val headers: MutableMap<String, String> = HashMap()
|
val headers: MutableMap<String, String> = HashMap()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DRM properties linked to the source
|
||||||
|
*/
|
||||||
|
var drmProps: DRMProps? = null
|
||||||
|
|
||||||
/** enable chunkless preparation for HLS
|
/** enable chunkless preparation for HLS
|
||||||
* see:
|
* see:
|
||||||
*/
|
*/
|
||||||
@ -61,7 +67,8 @@ class Source {
|
|||||||
cropStartMs == other.cropStartMs &&
|
cropStartMs == other.cropStartMs &&
|
||||||
cropEndMs == other.cropEndMs &&
|
cropEndMs == other.cropEndMs &&
|
||||||
startPositionMs == other.startPositionMs &&
|
startPositionMs == other.startPositionMs &&
|
||||||
extension == other.extension
|
extension == other.extension &&
|
||||||
|
drmProps == other.drmProps
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,6 +130,7 @@ class Source {
|
|||||||
private const val PROP_SRC_TYPE = "type"
|
private const val PROP_SRC_TYPE = "type"
|
||||||
private const val PROP_SRC_METADATA = "metadata"
|
private const val PROP_SRC_METADATA = "metadata"
|
||||||
private const val PROP_SRC_HEADERS = "requestHeaders"
|
private const val PROP_SRC_HEADERS = "requestHeaders"
|
||||||
|
private const val PROP_SRC_DRM = "drm"
|
||||||
private const val PROP_SRC_TEXT_TRACKS_ALLOW_CHUNKLESS_PREPARATION = "textTracksAllowChunklessPreparation"
|
private const val PROP_SRC_TEXT_TRACKS_ALLOW_CHUNKLESS_PREPARATION = "textTracksAllowChunklessPreparation"
|
||||||
|
|
||||||
@SuppressLint("DiscouragedApi")
|
@SuppressLint("DiscouragedApi")
|
||||||
@ -180,6 +188,7 @@ class Source {
|
|||||||
source.cropStartMs = safeGetInt(src, PROP_SRC_CROP_START, -1)
|
source.cropStartMs = safeGetInt(src, PROP_SRC_CROP_START, -1)
|
||||||
source.cropEndMs = safeGetInt(src, PROP_SRC_CROP_END, -1)
|
source.cropEndMs = safeGetInt(src, PROP_SRC_CROP_END, -1)
|
||||||
source.extension = safeGetString(src, PROP_SRC_TYPE, null)
|
source.extension = safeGetString(src, PROP_SRC_TYPE, null)
|
||||||
|
source.drmProps = parse(safeGetMap(src, PROP_SRC_DRM))
|
||||||
source.textTracksAllowChunklessPreparation = safeGetBool(src, PROP_SRC_TEXT_TRACKS_ALLOW_CHUNKLESS_PREPARATION, true)
|
source.textTracksAllowChunklessPreparation = safeGetBool(src, PROP_SRC_TEXT_TRACKS_ALLOW_CHUNKLESS_PREPARATION, true)
|
||||||
|
|
||||||
val propSrcHeadersArray = safeGetArray(src, PROP_SRC_HEADERS)
|
val propSrcHeadersArray = safeGetArray(src, PROP_SRC_HEADERS)
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package com.brentvatne.exoplayer;
|
package com.brentvatne.exoplayer;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
@ -15,7 +16,6 @@ import androidx.media3.common.util.Assertions;
|
|||||||
import androidx.media3.exoplayer.ExoPlayer;
|
import androidx.media3.exoplayer.ExoPlayer;
|
||||||
import androidx.media3.ui.SubtitleView;
|
import androidx.media3.ui.SubtitleView;
|
||||||
|
|
||||||
import android.util.AttributeSet;
|
|
||||||
import android.util.TypedValue;
|
import android.util.TypedValue;
|
||||||
import android.view.Gravity;
|
import android.view.Gravity;
|
||||||
import android.view.SurfaceView;
|
import android.view.SurfaceView;
|
||||||
@ -32,6 +32,7 @@ import com.google.common.collect.ImmutableList;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
@SuppressLint("ViewConstructor")
|
||||||
public final class ExoPlayerView extends FrameLayout implements AdViewProvider {
|
public final class ExoPlayerView extends FrameLayout implements AdViewProvider {
|
||||||
private final static String TAG = "ExoPlayerView";
|
private final static String TAG = "ExoPlayerView";
|
||||||
private View surfaceView;
|
private View surfaceView;
|
||||||
@ -48,15 +49,7 @@ public final class ExoPlayerView extends FrameLayout implements AdViewProvider {
|
|||||||
private boolean hideShutterView = false;
|
private boolean hideShutterView = false;
|
||||||
|
|
||||||
public ExoPlayerView(Context context) {
|
public ExoPlayerView(Context context) {
|
||||||
this(context, null);
|
super(context, null, 0);
|
||||||
}
|
|
||||||
|
|
||||||
public ExoPlayerView(Context context, AttributeSet attrs) {
|
|
||||||
this(context, attrs, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ExoPlayerView(Context context, AttributeSet attrs, int defStyleAttr) {
|
|
||||||
super(context, attrs, defStyleAttr);
|
|
||||||
|
|
||||||
this.context = context;
|
this.context = context;
|
||||||
|
|
||||||
@ -214,7 +207,7 @@ public final class ExoPlayerView extends FrameLayout implements AdViewProvider {
|
|||||||
* @param resizeMode The resize mode.
|
* @param resizeMode The resize mode.
|
||||||
*/
|
*/
|
||||||
public void setResizeMode(@ResizeMode.Mode int resizeMode) {
|
public void setResizeMode(@ResizeMode.Mode int resizeMode) {
|
||||||
if (layout.getResizeMode() != resizeMode) {
|
if (layout != null && layout.getResizeMode() != resizeMode) {
|
||||||
layout.setResizeMode(resizeMode);
|
layout.setResizeMode(resizeMode);
|
||||||
post(measureAndLayout);
|
post(measureAndLayout);
|
||||||
}
|
}
|
||||||
|
@ -217,7 +217,7 @@ public class ReactExoplayerView extends FrameLayout implements
|
|||||||
private boolean hasDrmFailed = false;
|
private boolean hasDrmFailed = false;
|
||||||
private boolean isUsingContentResolution = false;
|
private boolean isUsingContentResolution = false;
|
||||||
private boolean selectTrackWhenReady = false;
|
private boolean selectTrackWhenReady = false;
|
||||||
private Handler mainHandler;
|
private final Handler mainHandler;
|
||||||
private Runnable mainRunnable;
|
private Runnable mainRunnable;
|
||||||
private boolean useCache = false;
|
private boolean useCache = false;
|
||||||
private ControlsConfig controlsConfig = new ControlsConfig();
|
private ControlsConfig controlsConfig = new ControlsConfig();
|
||||||
@ -241,7 +241,6 @@ public class ReactExoplayerView extends FrameLayout implements
|
|||||||
private float mProgressUpdateInterval = 250.0f;
|
private float mProgressUpdateInterval = 250.0f;
|
||||||
private boolean playInBackground = false;
|
private boolean playInBackground = false;
|
||||||
private boolean mReportBandwidth = false;
|
private boolean mReportBandwidth = false;
|
||||||
private DRMProps drmProps;
|
|
||||||
private boolean controls;
|
private boolean controls;
|
||||||
private Uri adTagUrl;
|
private Uri adTagUrl;
|
||||||
|
|
||||||
@ -311,7 +310,7 @@ public class ReactExoplayerView extends FrameLayout implements
|
|||||||
this.eventEmitter = new VideoEventEmitter();
|
this.eventEmitter = new VideoEventEmitter();
|
||||||
this.config = config;
|
this.config = config;
|
||||||
this.bandwidthMeter = config.getBandwidthMeter();
|
this.bandwidthMeter = config.getBandwidthMeter();
|
||||||
|
mainHandler = new Handler();
|
||||||
createViews();
|
createViews();
|
||||||
|
|
||||||
audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
|
audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
|
||||||
@ -334,12 +333,9 @@ public class ReactExoplayerView extends FrameLayout implements
|
|||||||
LayoutParams.MATCH_PARENT);
|
LayoutParams.MATCH_PARENT);
|
||||||
exoPlayerView = new ExoPlayerView(getContext());
|
exoPlayerView = new ExoPlayerView(getContext());
|
||||||
exoPlayerView.setLayoutParams(layoutParams);
|
exoPlayerView.setLayoutParams(layoutParams);
|
||||||
|
|
||||||
addView(exoPlayerView, 0, layoutParams);
|
addView(exoPlayerView, 0, layoutParams);
|
||||||
|
|
||||||
exoPlayerView.setFocusable(this.focusable);
|
exoPlayerView.setFocusable(this.focusable);
|
||||||
|
|
||||||
mainHandler = new Handler();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// LifecycleEventListener implementation
|
// LifecycleEventListener implementation
|
||||||
@ -781,19 +777,22 @@ public class ReactExoplayerView extends FrameLayout implements
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private DrmSessionManager initializePlayerDrm(ReactExoplayerView self) {
|
private DrmSessionManager initializePlayerDrm() {
|
||||||
DrmSessionManager drmSessionManager = null;
|
DrmSessionManager drmSessionManager = null;
|
||||||
if (self.drmProps != null) {
|
DRMProps drmProps = source.getDrmProps();
|
||||||
|
// need to realign UUID in DRM Props from source
|
||||||
|
if (drmProps != null && drmProps.getDrmType() != null) {
|
||||||
|
UUID uuid = Util.getDrmUuid(drmProps.getDrmType());
|
||||||
|
if (uuid != null) {
|
||||||
try {
|
try {
|
||||||
drmSessionManager = self.buildDrmSessionManager(self.drmProps.getDrmUUID(),
|
DebugLog.w(TAG, "drm buildDrmSessionManager");
|
||||||
self.drmProps.getDrmLicenseServer(),
|
drmSessionManager = buildDrmSessionManager(uuid, drmProps);
|
||||||
self.drmProps.getDrmLicenseHeader());
|
|
||||||
} catch (UnsupportedDrmException e) {
|
} catch (UnsupportedDrmException e) {
|
||||||
int errorStringId = Util.SDK_INT < 18 ? R.string.error_drm_not_supported
|
int errorStringId = Util.SDK_INT < 18 ? R.string.error_drm_not_supported
|
||||||
: (e.reason == UnsupportedDrmException.REASON_UNSUPPORTED_SCHEME
|
: (e.reason == UnsupportedDrmException.REASON_UNSUPPORTED_SCHEME
|
||||||
? R.string.error_drm_unsupported_scheme : R.string.error_drm_unknown);
|
? R.string.error_drm_unsupported_scheme : R.string.error_drm_unknown);
|
||||||
eventEmitter.onVideoError.invoke(getResources().getString(errorStringId), e, "3003");
|
eventEmitter.onVideoError.invoke(getResources().getString(errorStringId), e, "3003");
|
||||||
return null;
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return drmSessionManager;
|
return drmSessionManager;
|
||||||
@ -803,14 +802,14 @@ public class ReactExoplayerView extends FrameLayout implements
|
|||||||
if (source.getUri() == null) {
|
if (source.getUri() == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
DrmSessionManager drmSessionManager = initializePlayerDrm(this);
|
/// init DRM
|
||||||
if (drmSessionManager == null && drmProps != null && drmProps.getDrmUUID() != null) {
|
DrmSessionManager drmSessionManager = initializePlayerDrm();
|
||||||
// Failed to intialize DRM session manager - cannot continue
|
if (drmSessionManager == null && source.getDrmProps() != null && source.getDrmProps().getDrmType() != null) {
|
||||||
|
// Failed to initialize DRM session manager - cannot continue
|
||||||
DebugLog.e(TAG, "Failed to initialize DRM Session Manager Framework!");
|
DebugLog.e(TAG, "Failed to initialize DRM Session Manager Framework!");
|
||||||
eventEmitter.onVideoError.invoke("Failed to initialize DRM Session Manager Framework!", new Exception("DRM Session Manager Framework failure!"), "3003");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// init source to manage ads and external text tracks
|
||||||
ArrayList<MediaSource> mediaSourceList = buildTextSources();
|
ArrayList<MediaSource> mediaSourceList = buildTextSources();
|
||||||
MediaSource videoSource = buildMediaSource(source.getUri(), source.getExtension(), drmSessionManager, source.getCropStartMs(), source.getCropEndMs());
|
MediaSource videoSource = buildMediaSource(source.getUri(), source.getExtension(), drmSessionManager, source.getCropStartMs(), source.getCropEndMs());
|
||||||
MediaSource mediaSourceWithAds = null;
|
MediaSource mediaSourceWithAds = null;
|
||||||
@ -945,22 +944,22 @@ public class ReactExoplayerView extends FrameLayout implements
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private DrmSessionManager buildDrmSessionManager(UUID uuid, String licenseUrl, String[] keyRequestPropertiesArray) throws UnsupportedDrmException {
|
private DrmSessionManager buildDrmSessionManager(UUID uuid, DRMProps drmProps) throws UnsupportedDrmException {
|
||||||
return buildDrmSessionManager(uuid, licenseUrl, keyRequestPropertiesArray, 0);
|
return buildDrmSessionManager(uuid, drmProps, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private DrmSessionManager buildDrmSessionManager(UUID uuid, String licenseUrl, String[] keyRequestPropertiesArray, int retryCount) throws UnsupportedDrmException {
|
private DrmSessionManager buildDrmSessionManager(UUID uuid, DRMProps drmProps, int retryCount) throws UnsupportedDrmException {
|
||||||
if (Util.SDK_INT < 18) {
|
if (Util.SDK_INT < 18) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
HttpMediaDrmCallback drmCallback = new HttpMediaDrmCallback(licenseUrl,
|
HttpMediaDrmCallback drmCallback = new HttpMediaDrmCallback(drmProps.getDrmLicenseServer(),
|
||||||
buildHttpDataSourceFactory(false));
|
buildHttpDataSourceFactory(false));
|
||||||
if (keyRequestPropertiesArray != null) {
|
|
||||||
|
String[] keyRequestPropertiesArray = drmProps.getDrmLicenseHeader();
|
||||||
for (int i = 0; i < keyRequestPropertiesArray.length - 1; i += 2) {
|
for (int i = 0; i < keyRequestPropertiesArray.length - 1; i += 2) {
|
||||||
drmCallback.setKeyRequestProperty(keyRequestPropertiesArray[i], keyRequestPropertiesArray[i + 1]);
|
drmCallback.setKeyRequestProperty(keyRequestPropertiesArray[i], keyRequestPropertiesArray[i + 1]);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
FrameworkMediaDrm mediaDrm = FrameworkMediaDrm.newInstance(uuid);
|
FrameworkMediaDrm mediaDrm = FrameworkMediaDrm.newInstance(uuid);
|
||||||
if (hasDrmFailed) {
|
if (hasDrmFailed) {
|
||||||
// When DRM fails using L1 we want to switch to L3
|
// When DRM fails using L1 we want to switch to L3
|
||||||
@ -969,7 +968,7 @@ public class ReactExoplayerView extends FrameLayout implements
|
|||||||
return new DefaultDrmSessionManager.Builder()
|
return new DefaultDrmSessionManager.Builder()
|
||||||
.setUuidAndExoMediaDrmProvider(uuid, (_uuid) -> mediaDrm)
|
.setUuidAndExoMediaDrmProvider(uuid, (_uuid) -> mediaDrm)
|
||||||
.setKeyRequestParameters(null)
|
.setKeyRequestParameters(null)
|
||||||
.setMultiSession(false)
|
.setMultiSession(drmProps.getMultiDrm())
|
||||||
.build(drmCallback);
|
.build(drmCallback);
|
||||||
} catch (UnsupportedDrmException ex) {
|
} catch (UnsupportedDrmException ex) {
|
||||||
// Unsupported DRM exceptions are handled by the calling method
|
// Unsupported DRM exceptions are handled by the calling method
|
||||||
@ -977,7 +976,7 @@ public class ReactExoplayerView extends FrameLayout implements
|
|||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
if (retryCount < 3) {
|
if (retryCount < 3) {
|
||||||
// Attempt retry 3 times in case where the OS Media DRM Framework fails for whatever reason
|
// Attempt retry 3 times in case where the OS Media DRM Framework fails for whatever reason
|
||||||
return buildDrmSessionManager(uuid, licenseUrl, keyRequestPropertiesArray, ++retryCount);
|
return buildDrmSessionManager(uuid, drmProps, ++retryCount);
|
||||||
}
|
}
|
||||||
// Handle the unknow exception and emit to JS
|
// Handle the unknow exception and emit to JS
|
||||||
eventEmitter.onVideoError.invoke(ex.toString(), ex, "3006");
|
eventEmitter.onVideoError.invoke(ex.toString(), ex, "3006");
|
||||||
@ -1818,8 +1817,10 @@ public class ReactExoplayerView extends FrameLayout implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setResizeModeModifier(@ResizeMode.Mode int resizeMode) {
|
public void setResizeModeModifier(@ResizeMode.Mode int resizeMode) {
|
||||||
|
if (exoPlayerView != null) {
|
||||||
exoPlayerView.setResizeMode(resizeMode);
|
exoPlayerView.setResizeMode(resizeMode);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void applyModifiers() {
|
private void applyModifiers() {
|
||||||
setRepeatModifier(repeat);
|
setRepeatModifier(repeat);
|
||||||
@ -2261,13 +2262,6 @@ public class ReactExoplayerView extends FrameLayout implements
|
|||||||
initializePlayer();
|
initializePlayer();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setDrm(DRMProps drmProps) {
|
|
||||||
this.drmProps = drmProps;
|
|
||||||
if (drmProps != null && drmProps.getDrmType() != null) {
|
|
||||||
this.drmProps.setDrmUUID(Util.getDrmUuid(drmProps.getDrmType()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDrmKeysLoaded(int windowIndex, MediaSource.MediaPeriodId mediaPeriodId) {
|
public void onDrmKeysLoaded(int windowIndex, MediaSource.MediaPeriodId mediaPeriodId) {
|
||||||
DebugLog.d("DRM Info", "onDrmKeysLoaded");
|
DebugLog.d("DRM Info", "onDrmKeysLoaded");
|
||||||
|
@ -28,9 +28,7 @@ import com.facebook.react.uimanager.ThemedReactContext;
|
|||||||
import com.facebook.react.uimanager.ViewGroupManager;
|
import com.facebook.react.uimanager.ViewGroupManager;
|
||||||
import com.facebook.react.uimanager.annotations.ReactProp;
|
import com.facebook.react.uimanager.annotations.ReactProp;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
@ -40,8 +38,6 @@ public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerVi
|
|||||||
private static final String REACT_CLASS = "RCTVideo";
|
private static final String REACT_CLASS = "RCTVideo";
|
||||||
private static final String PROP_SRC = "src";
|
private static final String PROP_SRC = "src";
|
||||||
private static final String PROP_AD_TAG_URL = "adTagUrl";
|
private static final String PROP_AD_TAG_URL = "adTagUrl";
|
||||||
private static final String PROP_DRM = "drm";
|
|
||||||
private static final String PROP_SRC_HEADERS = "requestHeaders";
|
|
||||||
private static final String PROP_RESIZE_MODE = "resizeMode";
|
private static final String PROP_RESIZE_MODE = "resizeMode";
|
||||||
private static final String PROP_REPEAT = "repeat";
|
private static final String PROP_REPEAT = "repeat";
|
||||||
private static final String PROP_SELECTED_AUDIO_TRACK = "selectedAudioTrack";
|
private static final String PROP_SELECTED_AUDIO_TRACK = "selectedAudioTrack";
|
||||||
@ -81,7 +77,6 @@ public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerVi
|
|||||||
private static final String PROP_DEBUG = "debug";
|
private static final String PROP_DEBUG = "debug";
|
||||||
private static final String PROP_CONTROLS_STYLES = "controlsStyles";
|
private static final String PROP_CONTROLS_STYLES = "controlsStyles";
|
||||||
|
|
||||||
|
|
||||||
private final ReactExoplayerConfig config;
|
private final ReactExoplayerConfig config;
|
||||||
|
|
||||||
public ReactExoplayerViewManager(ReactExoplayerConfig config) {
|
public ReactExoplayerViewManager(ReactExoplayerConfig config) {
|
||||||
@ -118,12 +113,6 @@ public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerVi
|
|||||||
view.eventEmitter.addEventEmitters(reactContext, view);
|
view.eventEmitter.addEventEmitters(reactContext, view);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ReactProp(name = PROP_DRM)
|
|
||||||
public void setDRM(final ReactExoplayerView videoView, @Nullable ReadableMap drm) {
|
|
||||||
DRMProps drmProps = DRMProps.parse(drm);
|
|
||||||
videoView.setDrm(drmProps);
|
|
||||||
}
|
|
||||||
|
|
||||||
@ReactProp(name = PROP_SRC)
|
@ReactProp(name = PROP_SRC)
|
||||||
public void setSrc(final ReactExoplayerView videoView, @Nullable ReadableMap src) {
|
public void setSrc(final ReactExoplayerView videoView, @Nullable ReadableMap src) {
|
||||||
Context context = videoView.getContext().getApplicationContext();
|
Context context = videoView.getContext().getApplicationContext();
|
||||||
|
@ -119,6 +119,13 @@ Default: false
|
|||||||
|
|
||||||
The URL pointing to the licenseServer that will provide the authorization to play the protected stream.
|
The URL pointing to the licenseServer that will provide the authorization to play the protected stream.
|
||||||
|
|
||||||
|
### `multiDrm`
|
||||||
|
<PlatformsList types={['Android']} />
|
||||||
|
Type: boolean\
|
||||||
|
Default: false
|
||||||
|
|
||||||
|
Indicates that drm system shall support key rotation, see: https://developer.android.google.cn/media/media3/exoplayer/drm?hl=en#key-rotation
|
||||||
|
|
||||||
### `type`
|
### `type`
|
||||||
|
|
||||||
<PlatformsList types={['Android', 'iOS']} />
|
<PlatformsList types={['Android', 'iOS']} />
|
||||||
|
@ -210,6 +210,9 @@ Determines if the player needs to throw an error when connection is lost or not
|
|||||||
|
|
||||||
### `drm`
|
### `drm`
|
||||||
|
|
||||||
|
> [!WARNING]
|
||||||
|
> deprecated, use source.drm instead
|
||||||
|
|
||||||
<PlatformsList types={['Android', 'iOS']} />
|
<PlatformsList types={['Android', 'iOS']} />
|
||||||
|
|
||||||
To setup DRM please follow [this guide](/component/drm)
|
To setup DRM please follow [this guide](/component/drm)
|
||||||
@ -728,6 +731,29 @@ type: 'mpd' }}
|
|||||||
The following other types are supported on some platforms, but aren't fully documented yet:
|
The following other types are supported on some platforms, but aren't fully documented yet:
|
||||||
`content://, ms-appx://, ms-appdata://, assets-library://`
|
`content://, ms-appx://, ms-appdata://, assets-library://`
|
||||||
|
|
||||||
|
#### Using DRM content
|
||||||
|
|
||||||
|
<PlatformsList types={['Android', 'iOS']} />
|
||||||
|
|
||||||
|
To setup DRM please follow [this guide](/component/drm)
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
{
|
||||||
|
description: 'WV: Secure SD & HD (cbcs,MP4,H264)',
|
||||||
|
uri: 'https://storage.googleapis.com/wvmedia/cbcs/h264/tears/tears_aes_cbcs.mpd',
|
||||||
|
drm: {
|
||||||
|
type: DRMType.WIDEVINE,
|
||||||
|
licenseServer:
|
||||||
|
'https://proxy.uat.widevine.com/proxy?provider=widevine_test',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
||||||
|
> ⚠️ DRM is not supported on visionOS yet
|
||||||
|
|
||||||
|
|
||||||
#### Start playback at a specific point in time
|
#### Start playback at a specific point in time
|
||||||
|
|
||||||
<PlatformsList types={['Android', 'iOS']} />
|
<PlatformsList types={['Android', 'iOS']} />
|
||||||
|
@ -1590,7 +1590,7 @@ SPEC CHECKSUMS:
|
|||||||
SDWebImageSVGCoder: 15a300a97ec1c8ac958f009c02220ac0402e936c
|
SDWebImageSVGCoder: 15a300a97ec1c8ac958f009c02220ac0402e936c
|
||||||
SDWebImageWebPCoder: e38c0a70396191361d60c092933e22c20d5b1380
|
SDWebImageWebPCoder: e38c0a70396191361d60c092933e22c20d5b1380
|
||||||
SocketRocket: abac6f5de4d4d62d24e11868d7a2f427e0ef940d
|
SocketRocket: abac6f5de4d4d62d24e11868d7a2f427e0ef940d
|
||||||
Yoga: bd92064a0d558be92786820514d74fc4dddd1233
|
Yoga: eed50599a85bd9f6882a9938d118aed6a397db9c
|
||||||
|
|
||||||
PODFILE CHECKSUM: a73d485df51877001f2b04a5a4379cfa5a3ba8fa
|
PODFILE CHECKSUM: a73d485df51877001f2b04a5a4379cfa5a3ba8fa
|
||||||
|
|
||||||
|
@ -25,6 +25,7 @@ import Video, {
|
|||||||
BufferingStrategyType,
|
BufferingStrategyType,
|
||||||
ReactVideoSource,
|
ReactVideoSource,
|
||||||
SelectedTrackType,
|
SelectedTrackType,
|
||||||
|
TextTracks,
|
||||||
ResizeMode,
|
ResizeMode,
|
||||||
VideoTrack,
|
VideoTrack,
|
||||||
SelectedTrack,
|
SelectedTrack,
|
||||||
@ -35,6 +36,13 @@ import {AdditionalSourceInfo} from './types';
|
|||||||
import {bufferConfig, srcList, textTracksSelectionBy} from './constants';
|
import {bufferConfig, srcList, textTracksSelectionBy} from './constants';
|
||||||
import {Overlay, toast} from './components';
|
import {Overlay, toast} from './components';
|
||||||
|
|
||||||
|
type AdditionnalSourceInfo = {
|
||||||
|
textTracks: TextTracks;
|
||||||
|
adTagUrl: string;
|
||||||
|
description: string;
|
||||||
|
noView: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
type Props = NonNullable<unknown>;
|
type Props = NonNullable<unknown>;
|
||||||
|
|
||||||
const VideoPlayer: FC<Props> = ({}) => {
|
const VideoPlayer: FC<Props> = ({}) => {
|
||||||
|
@ -9,6 +9,8 @@ struct VideoSource {
|
|||||||
let cropStart: Int64?
|
let cropStart: Int64?
|
||||||
let cropEnd: Int64?
|
let cropEnd: Int64?
|
||||||
let customMetadata: CustomMetadata?
|
let customMetadata: CustomMetadata?
|
||||||
|
/* DRM */
|
||||||
|
let drm: DRMParams?
|
||||||
|
|
||||||
let json: NSDictionary?
|
let json: NSDictionary?
|
||||||
|
|
||||||
@ -25,6 +27,7 @@ struct VideoSource {
|
|||||||
self.cropStart = nil
|
self.cropStart = nil
|
||||||
self.cropEnd = nil
|
self.cropEnd = nil
|
||||||
self.customMetadata = nil
|
self.customMetadata = nil
|
||||||
|
self.drm = nil
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.json = json
|
self.json = json
|
||||||
@ -48,5 +51,6 @@ struct VideoSource {
|
|||||||
self.cropStart = (json["cropStart"] as? Float64).flatMap { Int64(round($0)) }
|
self.cropStart = (json["cropStart"] as? Float64).flatMap { Int64(round($0)) }
|
||||||
self.cropEnd = (json["cropEnd"] as? Float64).flatMap { Int64(round($0)) }
|
self.cropEnd = (json["cropEnd"] as? Float64).flatMap { Int64(round($0)) }
|
||||||
self.customMetadata = CustomMetadata(json["metadata"] as? NSDictionary)
|
self.customMetadata = CustomMetadata(json["metadata"] as? NSDictionary)
|
||||||
|
self.drm = DRMParams(json["drm"] as? NSDictionary)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,10 +17,6 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH
|
|||||||
|
|
||||||
private var _playerViewController: RCTVideoPlayerViewController?
|
private var _playerViewController: RCTVideoPlayerViewController?
|
||||||
private var _videoURL: NSURL?
|
private var _videoURL: NSURL?
|
||||||
|
|
||||||
/* DRM */
|
|
||||||
private var _drm: DRMParams?
|
|
||||||
|
|
||||||
private var _localSourceEncryptionKeyScheme: String?
|
private var _localSourceEncryptionKeyScheme: String?
|
||||||
|
|
||||||
/* Required to publish events */
|
/* Required to publish events */
|
||||||
@ -406,7 +402,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH
|
|||||||
"type": _source?.type ?? NSNull(),
|
"type": _source?.type ?? NSNull(),
|
||||||
"isNetwork": NSNumber(value: _source?.isNetwork ?? false),
|
"isNetwork": NSNumber(value: _source?.isNetwork ?? false),
|
||||||
],
|
],
|
||||||
"drm": _drm?.json ?? NSNull(),
|
"drm": source.drm?.json ?? NSNull(),
|
||||||
"target": reactTag,
|
"target": reactTag,
|
||||||
])
|
])
|
||||||
|
|
||||||
@ -443,10 +439,10 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if _drm != nil || _localSourceEncryptionKeyScheme != nil {
|
if source.drm != nil || _localSourceEncryptionKeyScheme != nil {
|
||||||
_resouceLoaderDelegate = RCTResourceLoaderDelegate(
|
_resouceLoaderDelegate = RCTResourceLoaderDelegate(
|
||||||
asset: asset,
|
asset: asset,
|
||||||
drm: _drm,
|
drm: source.drm,
|
||||||
localSourceEncryptionKeyScheme: _localSourceEncryptionKeyScheme,
|
localSourceEncryptionKeyScheme: _localSourceEncryptionKeyScheme,
|
||||||
onVideoError: onVideoError,
|
onVideoError: onVideoError,
|
||||||
onGetLicense: onGetLicense,
|
onGetLicense: onGetLicense,
|
||||||
@ -568,11 +564,6 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH
|
|||||||
DispatchQueue.global(qos: .default).async(execute: initializeSource)
|
DispatchQueue.global(qos: .default).async(execute: initializeSource)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc
|
|
||||||
func setDrm(_ drm: NSDictionary) {
|
|
||||||
_drm = DRMParams(drm)
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc
|
@objc
|
||||||
func setLocalSourceEncryptionKeyScheme(_ keyScheme: String) {
|
func setLocalSourceEncryptionKeyScheme(_ keyScheme: String) {
|
||||||
_localSourceEncryptionKeyScheme = keyScheme
|
_localSourceEncryptionKeyScheme = keyScheme
|
||||||
@ -1271,7 +1262,6 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH
|
|||||||
_playerItem = nil
|
_playerItem = nil
|
||||||
_source = nil
|
_source = nil
|
||||||
_chapters = nil
|
_chapters = nil
|
||||||
_drm = nil
|
|
||||||
_textTracks = nil
|
_textTracks = nil
|
||||||
_selectedTextTrackCriteria = nil
|
_selectedTextTrackCriteria = nil
|
||||||
_selectedAudioTrackCriteria = nil
|
_selectedAudioTrackCriteria = nil
|
||||||
|
@ -163,6 +163,20 @@ const Video = forwardRef<VideoRef, ReactVideoProps>(
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const selectedDrm = source.drm || drm;
|
||||||
|
const _drm = !selectedDrm
|
||||||
|
? undefined
|
||||||
|
: {
|
||||||
|
type: selectedDrm.type,
|
||||||
|
licenseServer: selectedDrm.licenseServer,
|
||||||
|
headers: generateHeaderForNative(selectedDrm.headers),
|
||||||
|
contentId: selectedDrm.contentId,
|
||||||
|
certificateUrl: selectedDrm.certificateUrl,
|
||||||
|
base64Certificate: selectedDrm.base64Certificate,
|
||||||
|
useExternalGetLicense: !!selectedDrm.getLicense,
|
||||||
|
multiDrm: selectedDrm.multiDrm,
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
uri,
|
uri,
|
||||||
isNetwork,
|
isNetwork,
|
||||||
@ -176,26 +190,11 @@ const Video = forwardRef<VideoRef, ReactVideoProps>(
|
|||||||
cropStart: resolvedSource.cropStart || 0,
|
cropStart: resolvedSource.cropStart || 0,
|
||||||
cropEnd: resolvedSource.cropEnd,
|
cropEnd: resolvedSource.cropEnd,
|
||||||
metadata: resolvedSource.metadata,
|
metadata: resolvedSource.metadata,
|
||||||
|
drm: _drm,
|
||||||
textTracksAllowChunklessPreparation:
|
textTracksAllowChunklessPreparation:
|
||||||
resolvedSource.textTracksAllowChunklessPreparation,
|
resolvedSource.textTracksAllowChunklessPreparation,
|
||||||
};
|
};
|
||||||
}, [source]);
|
}, [drm, source]);
|
||||||
|
|
||||||
const _drm = useMemo(() => {
|
|
||||||
if (!drm) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
type: drm.type,
|
|
||||||
licenseServer: drm.licenseServer,
|
|
||||||
headers: generateHeaderForNative(drm.headers),
|
|
||||||
contentId: drm.contentId,
|
|
||||||
certificateUrl: drm.certificateUrl,
|
|
||||||
base64Certificate: drm.base64Certificate,
|
|
||||||
useExternalGetLicense: !!drm.getLicense,
|
|
||||||
};
|
|
||||||
}, [drm]);
|
|
||||||
|
|
||||||
const _selectedTextTrack = useMemo(() => {
|
const _selectedTextTrack = useMemo(() => {
|
||||||
if (!selectedTextTrack) {
|
if (!selectedTextTrack) {
|
||||||
@ -612,7 +611,6 @@ const Video = forwardRef<VideoRef, ReactVideoProps>(
|
|||||||
ref={nativeRef}
|
ref={nativeRef}
|
||||||
{...rest}
|
{...rest}
|
||||||
src={src}
|
src={src}
|
||||||
drm={_drm}
|
|
||||||
style={StyleSheet.absoluteFill}
|
style={StyleSheet.absoluteFill}
|
||||||
resizeMode={resizeMode}
|
resizeMode={resizeMode}
|
||||||
fullscreen={isFullscreen}
|
fullscreen={isFullscreen}
|
||||||
|
@ -39,6 +39,7 @@ export type VideoSrc = Readonly<{
|
|||||||
cropStart?: Float;
|
cropStart?: Float;
|
||||||
cropEnd?: Float;
|
cropEnd?: Float;
|
||||||
metadata?: VideoMetadata;
|
metadata?: VideoMetadata;
|
||||||
|
drm?: Drm;
|
||||||
textTracksAllowChunklessPreparation?: boolean; // android
|
textTracksAllowChunklessPreparation?: boolean; // android
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
@ -57,6 +58,7 @@ type Drm = Readonly<{
|
|||||||
certificateUrl?: string; // ios
|
certificateUrl?: string; // ios
|
||||||
base64Certificate?: boolean; // ios default: false
|
base64Certificate?: boolean; // ios default: false
|
||||||
useExternalGetLicense?: boolean; // ios
|
useExternalGetLicense?: boolean; // ios
|
||||||
|
multiDrm?: boolean; // android
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
type TextTracks = ReadonlyArray<
|
type TextTracks = ReadonlyArray<
|
||||||
@ -295,7 +297,6 @@ export type OnControlsVisibilityChange = Readonly<{
|
|||||||
|
|
||||||
export interface VideoNativeProps extends ViewProps {
|
export interface VideoNativeProps extends ViewProps {
|
||||||
src?: VideoSrc;
|
src?: VideoSrc;
|
||||||
drm?: Drm;
|
|
||||||
adTagUrl?: string;
|
adTagUrl?: string;
|
||||||
allowsExternalPlayback?: boolean; // ios, true
|
allowsExternalPlayback?: boolean; // ios, true
|
||||||
maxBitRate?: Float;
|
maxBitRate?: Float;
|
||||||
|
@ -24,6 +24,7 @@ export type ReactVideoSourceProperties = {
|
|||||||
cropStart?: number;
|
cropStart?: number;
|
||||||
cropEnd?: number;
|
cropEnd?: number;
|
||||||
metadata?: VideoMetadata;
|
metadata?: VideoMetadata;
|
||||||
|
drm?: Drm;
|
||||||
textTracksAllowChunklessPreparation?: boolean;
|
textTracksAllowChunklessPreparation?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -60,6 +61,7 @@ export type Drm = Readonly<{
|
|||||||
contentId?: string; // ios
|
contentId?: string; // ios
|
||||||
certificateUrl?: string; // ios
|
certificateUrl?: string; // ios
|
||||||
base64Certificate?: boolean; // ios default: false
|
base64Certificate?: boolean; // ios default: false
|
||||||
|
multiDrm: boolean; // android
|
||||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||||
getLicense?: (
|
getLicense?: (
|
||||||
spcBase64: string,
|
spcBase64: string,
|
||||||
@ -211,6 +213,7 @@ export type ControlsStyles = {
|
|||||||
|
|
||||||
export interface ReactVideoProps extends ReactVideoEvents, ViewProps {
|
export interface ReactVideoProps extends ReactVideoEvents, ViewProps {
|
||||||
source?: ReactVideoSource;
|
source?: ReactVideoSource;
|
||||||
|
/** @deprecated */
|
||||||
drm?: Drm;
|
drm?: Drm;
|
||||||
style?: StyleProp<ViewStyle>;
|
style?: StyleProp<ViewStyle>;
|
||||||
adTagUrl?: string;
|
adTagUrl?: string;
|
||||||
@ -258,8 +261,10 @@ export interface ReactVideoProps extends ReactVideoEvents, ViewProps {
|
|||||||
textTracks?: TextTracks;
|
textTracks?: TextTracks;
|
||||||
testID?: string;
|
testID?: string;
|
||||||
viewType?: ViewType;
|
viewType?: ViewType;
|
||||||
useTextureView?: boolean; // Android // deprecated
|
/** @deprecated */
|
||||||
useSecureView?: boolean; // Android // deprecated
|
useTextureView?: boolean; // Android
|
||||||
|
/** @deprecated */
|
||||||
|
useSecureView?: boolean; // Android
|
||||||
volume?: number;
|
volume?: number;
|
||||||
localSourceEncryptionKeyScheme?: string;
|
localSourceEncryptionKeyScheme?: string;
|
||||||
debug?: DebugConfig;
|
debug?: DebugConfig;
|
||||||
|
Loading…
Reference in New Issue
Block a user