From 66dcf32b56df88c292b4b14ef1162f43c59cd759 Mon Sep 17 00:00:00 2001 From: Olivier Bouillet <62574056+freeboub@users.noreply.github.com> Date: Wed, 10 Jul 2024 12:17:22 +0200 Subject: [PATCH] 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 --- .../com/brentvatne/common/api/DRMProps.kt | 18 +++++ .../java/com/brentvatne/common/api/Source.kt | 11 ++- .../brentvatne/exoplayer/ExoPlayerView.java | 15 +--- .../exoplayer/ReactExoplayerView.java | 76 +++++++++---------- .../exoplayer/ReactExoplayerViewManager.java | 11 --- docs/pages/component/drm.mdx | 7 ++ docs/pages/component/props.mdx | 26 +++++++ examples/basic/ios/Podfile.lock | 2 +- examples/basic/src/VideoPlayer.tsx | 8 ++ ios/Video/DataStructures/VideoSource.swift | 4 + ios/Video/RCTVideo.swift | 16 +--- src/Video.tsx | 34 ++++----- src/specs/VideoNativeComponent.ts | 3 +- src/types/video.ts | 9 ++- 14 files changed, 141 insertions(+), 99 deletions(-) diff --git a/android/src/main/java/com/brentvatne/common/api/DRMProps.kt b/android/src/main/java/com/brentvatne/common/api/DRMProps.kt index a26d048a..0c495e11 100644 --- a/android/src/main/java/com/brentvatne/common/api/DRMProps.kt +++ b/android/src/main/java/com/brentvatne/common/api/DRMProps.kt @@ -1,6 +1,7 @@ package com.brentvatne.common.api import com.brentvatne.common.toolbox.ReactBridgeUtils.safeGetArray +import com.brentvatne.common.toolbox.ReactBridgeUtils.safeGetBool import com.brentvatne.common.toolbox.ReactBridgeUtils.safeGetString import com.facebook.react.bridge.ReadableMap import java.util.UUID @@ -29,12 +30,28 @@ class DRMProps { * DRM Http Header to access to license server */ var drmLicenseHeader: Array = emptyArray() + + /** + * 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 { private const val PROP_DRM_TYPE = "type" private const val PROP_DRM_LICENSE_SERVER = "licenseServer" private const val PROP_DRM_HEADERS = "headers" private const val PROP_DRM_HEADERS_KEY = "key" private const val PROP_DRM_HEADERS_VALUE = "value" + private const val PROP_DRM_MULTI_DRM = "multiDrm" /** parse the source ReadableMap received from app */ @JvmStatic @@ -44,6 +61,7 @@ class DRMProps { drm = DRMProps() drm.drmType = safeGetString(src, PROP_DRM_TYPE) drm.drmLicenseServer = safeGetString(src, PROP_DRM_LICENSE_SERVER) + drm.multiDrm = safeGetBool(src, PROP_DRM_MULTI_DRM, false) val drmHeadersArray = safeGetArray(src, PROP_DRM_HEADERS) if (drm.drmType != null && drm.drmLicenseServer != null) { if (drmHeadersArray != null) { diff --git a/android/src/main/java/com/brentvatne/common/api/Source.kt b/android/src/main/java/com/brentvatne/common/api/Source.kt index b19fa8d9..ba4f8496 100644 --- a/android/src/main/java/com/brentvatne/common/api/Source.kt +++ b/android/src/main/java/com/brentvatne/common/api/Source.kt @@ -6,6 +6,7 @@ import android.content.Context import android.content.res.Resources import android.net.Uri import android.text.TextUtils +import com.brentvatne.common.api.DRMProps.Companion.parse import com.brentvatne.common.toolbox.DebugLog import com.brentvatne.common.toolbox.DebugLog.e import com.brentvatne.common.toolbox.ReactBridgeUtils.safeGetArray @@ -46,6 +47,11 @@ class Source { /** http header list */ val headers: MutableMap = HashMap() + /** + * DRM properties linked to the source + */ + var drmProps: DRMProps? = null + /** enable chunkless preparation for HLS * see: */ @@ -61,7 +67,8 @@ class Source { cropStartMs == other.cropStartMs && cropEndMs == other.cropEndMs && 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_METADATA = "metadata" 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" @SuppressLint("DiscouragedApi") @@ -180,6 +188,7 @@ class Source { source.cropStartMs = safeGetInt(src, PROP_SRC_CROP_START, -1) source.cropEndMs = safeGetInt(src, PROP_SRC_CROP_END, -1) 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) val propSrcHeadersArray = safeGetArray(src, PROP_SRC_HEADERS) diff --git a/android/src/main/java/com/brentvatne/exoplayer/ExoPlayerView.java b/android/src/main/java/com/brentvatne/exoplayer/ExoPlayerView.java index 96c2f918..a776bc0d 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/ExoPlayerView.java +++ b/android/src/main/java/com/brentvatne/exoplayer/ExoPlayerView.java @@ -1,5 +1,6 @@ package com.brentvatne.exoplayer; +import android.annotation.SuppressLint; import android.content.Context; import androidx.annotation.NonNull; @@ -15,7 +16,6 @@ import androidx.media3.common.util.Assertions; import androidx.media3.exoplayer.ExoPlayer; import androidx.media3.ui.SubtitleView; -import android.util.AttributeSet; import android.util.TypedValue; import android.view.Gravity; import android.view.SurfaceView; @@ -32,6 +32,7 @@ import com.google.common.collect.ImmutableList; import java.util.List; +@SuppressLint("ViewConstructor") public final class ExoPlayerView extends FrameLayout implements AdViewProvider { private final static String TAG = "ExoPlayerView"; private View surfaceView; @@ -48,15 +49,7 @@ public final class ExoPlayerView extends FrameLayout implements AdViewProvider { private boolean hideShutterView = false; public ExoPlayerView(Context context) { - this(context, null); - } - - public ExoPlayerView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public ExoPlayerView(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); + super(context, null, 0); this.context = context; @@ -214,7 +207,7 @@ public final class ExoPlayerView extends FrameLayout implements AdViewProvider { * @param resizeMode The resize mode. */ public void setResizeMode(@ResizeMode.Mode int resizeMode) { - if (layout.getResizeMode() != resizeMode) { + if (layout != null && layout.getResizeMode() != resizeMode) { layout.setResizeMode(resizeMode); post(measureAndLayout); } diff --git a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java index c3e85be8..e650b564 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java +++ b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java @@ -217,7 +217,7 @@ public class ReactExoplayerView extends FrameLayout implements private boolean hasDrmFailed = false; private boolean isUsingContentResolution = false; private boolean selectTrackWhenReady = false; - private Handler mainHandler; + private final Handler mainHandler; private Runnable mainRunnable; private boolean useCache = false; private ControlsConfig controlsConfig = new ControlsConfig(); @@ -241,7 +241,6 @@ public class ReactExoplayerView extends FrameLayout implements private float mProgressUpdateInterval = 250.0f; private boolean playInBackground = false; private boolean mReportBandwidth = false; - private DRMProps drmProps; private boolean controls; private Uri adTagUrl; @@ -311,7 +310,7 @@ public class ReactExoplayerView extends FrameLayout implements this.eventEmitter = new VideoEventEmitter(); this.config = config; this.bandwidthMeter = config.getBandwidthMeter(); - + mainHandler = new Handler(); createViews(); audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); @@ -334,12 +333,9 @@ public class ReactExoplayerView extends FrameLayout implements LayoutParams.MATCH_PARENT); exoPlayerView = new ExoPlayerView(getContext()); exoPlayerView.setLayoutParams(layoutParams); - addView(exoPlayerView, 0, layoutParams); exoPlayerView.setFocusable(this.focusable); - - mainHandler = new Handler(); } // LifecycleEventListener implementation @@ -781,19 +777,22 @@ public class ReactExoplayerView extends FrameLayout implements } } - private DrmSessionManager initializePlayerDrm(ReactExoplayerView self) { + private DrmSessionManager initializePlayerDrm() { DrmSessionManager drmSessionManager = null; - if (self.drmProps != null) { - try { - drmSessionManager = self.buildDrmSessionManager(self.drmProps.getDrmUUID(), - self.drmProps.getDrmLicenseServer(), - self.drmProps.getDrmLicenseHeader()); - } catch (UnsupportedDrmException e) { - int errorStringId = Util.SDK_INT < 18 ? R.string.error_drm_not_supported - : (e.reason == UnsupportedDrmException.REASON_UNSUPPORTED_SCHEME - ? R.string.error_drm_unsupported_scheme : R.string.error_drm_unknown); - eventEmitter.onVideoError.invoke(getResources().getString(errorStringId), e, "3003"); - return 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 { + DebugLog.w(TAG, "drm buildDrmSessionManager"); + drmSessionManager = buildDrmSessionManager(uuid, drmProps); + } catch (UnsupportedDrmException e) { + int errorStringId = Util.SDK_INT < 18 ? R.string.error_drm_not_supported + : (e.reason == UnsupportedDrmException.REASON_UNSUPPORTED_SCHEME + ? R.string.error_drm_unsupported_scheme : R.string.error_drm_unknown); + eventEmitter.onVideoError.invoke(getResources().getString(errorStringId), e, "3003"); + } } } return drmSessionManager; @@ -803,14 +802,14 @@ public class ReactExoplayerView extends FrameLayout implements if (source.getUri() == null) { return; } - DrmSessionManager drmSessionManager = initializePlayerDrm(this); - if (drmSessionManager == null && drmProps != null && drmProps.getDrmUUID() != null) { - // Failed to intialize DRM session manager - cannot continue + /// init DRM + DrmSessionManager drmSessionManager = initializePlayerDrm(); + 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!"); - eventEmitter.onVideoError.invoke("Failed to initialize DRM Session Manager Framework!", new Exception("DRM Session Manager Framework failure!"), "3003"); return; } - + // init source to manage ads and external text tracks ArrayList mediaSourceList = buildTextSources(); MediaSource videoSource = buildMediaSource(source.getUri(), source.getExtension(), drmSessionManager, source.getCropStartMs(), source.getCropEndMs()); MediaSource mediaSourceWithAds = null; @@ -945,21 +944,21 @@ public class ReactExoplayerView extends FrameLayout implements } } - private DrmSessionManager buildDrmSessionManager(UUID uuid, String licenseUrl, String[] keyRequestPropertiesArray) throws UnsupportedDrmException { - return buildDrmSessionManager(uuid, licenseUrl, keyRequestPropertiesArray, 0); + private DrmSessionManager buildDrmSessionManager(UUID uuid, DRMProps drmProps) throws UnsupportedDrmException { + 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) { return null; } try { - HttpMediaDrmCallback drmCallback = new HttpMediaDrmCallback(licenseUrl, + HttpMediaDrmCallback drmCallback = new HttpMediaDrmCallback(drmProps.getDrmLicenseServer(), buildHttpDataSourceFactory(false)); - if (keyRequestPropertiesArray != null) { - for (int i = 0; i < keyRequestPropertiesArray.length - 1; i += 2) { - drmCallback.setKeyRequestProperty(keyRequestPropertiesArray[i], keyRequestPropertiesArray[i + 1]); - } + + String[] keyRequestPropertiesArray = drmProps.getDrmLicenseHeader(); + for (int i = 0; i < keyRequestPropertiesArray.length - 1; i += 2) { + drmCallback.setKeyRequestProperty(keyRequestPropertiesArray[i], keyRequestPropertiesArray[i + 1]); } FrameworkMediaDrm mediaDrm = FrameworkMediaDrm.newInstance(uuid); if (hasDrmFailed) { @@ -969,7 +968,7 @@ public class ReactExoplayerView extends FrameLayout implements return new DefaultDrmSessionManager.Builder() .setUuidAndExoMediaDrmProvider(uuid, (_uuid) -> mediaDrm) .setKeyRequestParameters(null) - .setMultiSession(false) + .setMultiSession(drmProps.getMultiDrm()) .build(drmCallback); } catch (UnsupportedDrmException ex) { // Unsupported DRM exceptions are handled by the calling method @@ -977,7 +976,7 @@ public class ReactExoplayerView extends FrameLayout implements } catch (Exception ex) { if (retryCount < 3) { // 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 eventEmitter.onVideoError.invoke(ex.toString(), ex, "3006"); @@ -1818,7 +1817,9 @@ public class ReactExoplayerView extends FrameLayout implements } public void setResizeModeModifier(@ResizeMode.Mode int resizeMode) { - exoPlayerView.setResizeMode(resizeMode); + if (exoPlayerView != null) { + exoPlayerView.setResizeMode(resizeMode); + } } private void applyModifiers() { @@ -2261,13 +2262,6 @@ public class ReactExoplayerView extends FrameLayout implements initializePlayer(); } - public void setDrm(DRMProps drmProps) { - this.drmProps = drmProps; - if (drmProps != null && drmProps.getDrmType() != null) { - this.drmProps.setDrmUUID(Util.getDrmUuid(drmProps.getDrmType())); - } - } - @Override public void onDrmKeysLoaded(int windowIndex, MediaSource.MediaPeriodId mediaPeriodId) { DebugLog.d("DRM Info", "onDrmKeysLoaded"); diff --git a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java index ee257ddf..7429160a 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java +++ b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java @@ -28,9 +28,7 @@ import com.facebook.react.uimanager.ThemedReactContext; import com.facebook.react.uimanager.ViewGroupManager; import com.facebook.react.uimanager.annotations.ReactProp; -import java.util.ArrayList; import java.util.Map; -import java.util.UUID; import javax.annotation.Nullable; @@ -40,8 +38,6 @@ public class ReactExoplayerViewManager extends ViewGroupManager +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` diff --git a/docs/pages/component/props.mdx b/docs/pages/component/props.mdx index 7d69f593..ef999202 100644 --- a/docs/pages/component/props.mdx +++ b/docs/pages/component/props.mdx @@ -210,6 +210,9 @@ Determines if the player needs to throw an error when connection is lost or not ### `drm` +> [!WARNING] +> deprecated, use source.drm instead + 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: `content://, ms-appx://, ms-appdata://, assets-library://` +#### Using DRM content + + + +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 diff --git a/examples/basic/ios/Podfile.lock b/examples/basic/ios/Podfile.lock index b8842e88..b3681ae4 100644 --- a/examples/basic/ios/Podfile.lock +++ b/examples/basic/ios/Podfile.lock @@ -1590,7 +1590,7 @@ SPEC CHECKSUMS: SDWebImageSVGCoder: 15a300a97ec1c8ac958f009c02220ac0402e936c SDWebImageWebPCoder: e38c0a70396191361d60c092933e22c20d5b1380 SocketRocket: abac6f5de4d4d62d24e11868d7a2f427e0ef940d - Yoga: bd92064a0d558be92786820514d74fc4dddd1233 + Yoga: eed50599a85bd9f6882a9938d118aed6a397db9c PODFILE CHECKSUM: a73d485df51877001f2b04a5a4379cfa5a3ba8fa diff --git a/examples/basic/src/VideoPlayer.tsx b/examples/basic/src/VideoPlayer.tsx index 627b198a..031b6e12 100644 --- a/examples/basic/src/VideoPlayer.tsx +++ b/examples/basic/src/VideoPlayer.tsx @@ -25,6 +25,7 @@ import Video, { BufferingStrategyType, ReactVideoSource, SelectedTrackType, + TextTracks, ResizeMode, VideoTrack, SelectedTrack, @@ -35,6 +36,13 @@ import {AdditionalSourceInfo} from './types'; import {bufferConfig, srcList, textTracksSelectionBy} from './constants'; import {Overlay, toast} from './components'; +type AdditionnalSourceInfo = { + textTracks: TextTracks; + adTagUrl: string; + description: string; + noView: boolean; +}; + type Props = NonNullable; const VideoPlayer: FC = ({}) => { diff --git a/ios/Video/DataStructures/VideoSource.swift b/ios/Video/DataStructures/VideoSource.swift index ab011d98..b23d8ede 100644 --- a/ios/Video/DataStructures/VideoSource.swift +++ b/ios/Video/DataStructures/VideoSource.swift @@ -9,6 +9,8 @@ struct VideoSource { let cropStart: Int64? let cropEnd: Int64? let customMetadata: CustomMetadata? + /* DRM */ + let drm: DRMParams? let json: NSDictionary? @@ -25,6 +27,7 @@ struct VideoSource { self.cropStart = nil self.cropEnd = nil self.customMetadata = nil + self.drm = nil return } self.json = json @@ -48,5 +51,6 @@ struct VideoSource { self.cropStart = (json["cropStart"] as? Float64).flatMap { Int64(round($0)) } self.cropEnd = (json["cropEnd"] as? Float64).flatMap { Int64(round($0)) } self.customMetadata = CustomMetadata(json["metadata"] as? NSDictionary) + self.drm = DRMParams(json["drm"] as? NSDictionary) } } diff --git a/ios/Video/RCTVideo.swift b/ios/Video/RCTVideo.swift index 7b30c693..a8b01755 100644 --- a/ios/Video/RCTVideo.swift +++ b/ios/Video/RCTVideo.swift @@ -17,10 +17,6 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH private var _playerViewController: RCTVideoPlayerViewController? private var _videoURL: NSURL? - - /* DRM */ - private var _drm: DRMParams? - private var _localSourceEncryptionKeyScheme: String? /* Required to publish events */ @@ -406,7 +402,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH "type": _source?.type ?? NSNull(), "isNetwork": NSNumber(value: _source?.isNetwork ?? false), ], - "drm": _drm?.json ?? NSNull(), + "drm": source.drm?.json ?? NSNull(), "target": reactTag, ]) @@ -443,10 +439,10 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH } #endif - if _drm != nil || _localSourceEncryptionKeyScheme != nil { + if source.drm != nil || _localSourceEncryptionKeyScheme != nil { _resouceLoaderDelegate = RCTResourceLoaderDelegate( asset: asset, - drm: _drm, + drm: source.drm, localSourceEncryptionKeyScheme: _localSourceEncryptionKeyScheme, onVideoError: onVideoError, onGetLicense: onGetLicense, @@ -568,11 +564,6 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH DispatchQueue.global(qos: .default).async(execute: initializeSource) } - @objc - func setDrm(_ drm: NSDictionary) { - _drm = DRMParams(drm) - } - @objc func setLocalSourceEncryptionKeyScheme(_ keyScheme: String) { _localSourceEncryptionKeyScheme = keyScheme @@ -1271,7 +1262,6 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH _playerItem = nil _source = nil _chapters = nil - _drm = nil _textTracks = nil _selectedTextTrackCriteria = nil _selectedAudioTrackCriteria = nil diff --git a/src/Video.tsx b/src/Video.tsx index f6a0016d..1fd8e73b 100644 --- a/src/Video.tsx +++ b/src/Video.tsx @@ -163,6 +163,20 @@ const Video = forwardRef( ) ); + 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 { uri, isNetwork, @@ -176,26 +190,11 @@ const Video = forwardRef( cropStart: resolvedSource.cropStart || 0, cropEnd: resolvedSource.cropEnd, metadata: resolvedSource.metadata, + drm: _drm, textTracksAllowChunklessPreparation: resolvedSource.textTracksAllowChunklessPreparation, }; - }, [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]); + }, [drm, source]); const _selectedTextTrack = useMemo(() => { if (!selectedTextTrack) { @@ -612,7 +611,6 @@ const Video = forwardRef( ref={nativeRef} {...rest} src={src} - drm={_drm} style={StyleSheet.absoluteFill} resizeMode={resizeMode} fullscreen={isFullscreen} diff --git a/src/specs/VideoNativeComponent.ts b/src/specs/VideoNativeComponent.ts index 107b717d..b55dabd5 100644 --- a/src/specs/VideoNativeComponent.ts +++ b/src/specs/VideoNativeComponent.ts @@ -39,6 +39,7 @@ export type VideoSrc = Readonly<{ cropStart?: Float; cropEnd?: Float; metadata?: VideoMetadata; + drm?: Drm; textTracksAllowChunklessPreparation?: boolean; // android }>; @@ -57,6 +58,7 @@ type Drm = Readonly<{ certificateUrl?: string; // ios base64Certificate?: boolean; // ios default: false useExternalGetLicense?: boolean; // ios + multiDrm?: boolean; // android }>; type TextTracks = ReadonlyArray< @@ -295,7 +297,6 @@ export type OnControlsVisibilityChange = Readonly<{ export interface VideoNativeProps extends ViewProps { src?: VideoSrc; - drm?: Drm; adTagUrl?: string; allowsExternalPlayback?: boolean; // ios, true maxBitRate?: Float; diff --git a/src/types/video.ts b/src/types/video.ts index c4805390..463f30b5 100644 --- a/src/types/video.ts +++ b/src/types/video.ts @@ -24,6 +24,7 @@ export type ReactVideoSourceProperties = { cropStart?: number; cropEnd?: number; metadata?: VideoMetadata; + drm?: Drm; textTracksAllowChunklessPreparation?: boolean; }; @@ -60,6 +61,7 @@ export type Drm = Readonly<{ contentId?: string; // ios certificateUrl?: string; // ios base64Certificate?: boolean; // ios default: false + multiDrm: boolean; // android /* eslint-disable @typescript-eslint/no-unused-vars */ getLicense?: ( spcBase64: string, @@ -211,6 +213,7 @@ export type ControlsStyles = { export interface ReactVideoProps extends ReactVideoEvents, ViewProps { source?: ReactVideoSource; + /** @deprecated */ drm?: Drm; style?: StyleProp; adTagUrl?: string; @@ -258,8 +261,10 @@ export interface ReactVideoProps extends ReactVideoEvents, ViewProps { textTracks?: TextTracks; testID?: string; viewType?: ViewType; - useTextureView?: boolean; // Android // deprecated - useSecureView?: boolean; // Android // deprecated + /** @deprecated */ + useTextureView?: boolean; // Android + /** @deprecated */ + useSecureView?: boolean; // Android volume?: number; localSourceEncryptionKeyScheme?: string; debug?: DebugConfig;