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:
		| @@ -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<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 { | ||||
|         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) { | ||||
|   | ||||
| @@ -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<String, String> = 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) | ||||
|   | ||||
| @@ -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); | ||||
|         } | ||||
|   | ||||
| @@ -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<MediaSource> 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"); | ||||
|   | ||||
| @@ -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<ReactExoplayerVi | ||||
|     private static final String REACT_CLASS = "RCTVideo"; | ||||
|     private static final String PROP_SRC = "src"; | ||||
|     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_REPEAT = "repeat"; | ||||
|     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_CONTROLS_STYLES = "controlsStyles"; | ||||
|  | ||||
|  | ||||
|     private final ReactExoplayerConfig config; | ||||
|  | ||||
|     public ReactExoplayerViewManager(ReactExoplayerConfig config) { | ||||
| @@ -118,12 +113,6 @@ public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerVi | ||||
|         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) | ||||
|     public void setSrc(final ReactExoplayerView videoView, @Nullable ReadableMap src) { | ||||
|         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. | ||||
|  | ||||
| ### `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` | ||||
|  | ||||
| <PlatformsList types={['Android', 'iOS']} /> | ||||
|   | ||||
| @@ -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 | ||||
|  | ||||
| <PlatformsList types={['Android', 'iOS']} /> | ||||
|  | ||||
| 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 | ||||
|  | ||||
| <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 | ||||
|  | ||||
| <PlatformsList types={['Android', 'iOS']} /> | ||||
|   | ||||
| @@ -1590,7 +1590,7 @@ SPEC CHECKSUMS: | ||||
|   SDWebImageSVGCoder: 15a300a97ec1c8ac958f009c02220ac0402e936c | ||||
|   SDWebImageWebPCoder: e38c0a70396191361d60c092933e22c20d5b1380 | ||||
|   SocketRocket: abac6f5de4d4d62d24e11868d7a2f427e0ef940d | ||||
|   Yoga: bd92064a0d558be92786820514d74fc4dddd1233 | ||||
|   Yoga: eed50599a85bd9f6882a9938d118aed6a397db9c | ||||
|  | ||||
| PODFILE CHECKSUM: a73d485df51877001f2b04a5a4379cfa5a3ba8fa | ||||
|  | ||||
|   | ||||
| @@ -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<unknown>; | ||||
|  | ||||
| const VideoPlayer: FC<Props> = ({}) => { | ||||
|   | ||||
| @@ -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) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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 { | ||||
|         uri, | ||||
|         isNetwork, | ||||
| @@ -176,26 +190,11 @@ const Video = forwardRef<VideoRef, ReactVideoProps>( | ||||
|         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<VideoRef, ReactVideoProps>( | ||||
|           ref={nativeRef} | ||||
|           {...rest} | ||||
|           src={src} | ||||
|           drm={_drm} | ||||
|           style={StyleSheet.absoluteFill} | ||||
|           resizeMode={resizeMode} | ||||
|           fullscreen={isFullscreen} | ||||
|   | ||||
| @@ -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; | ||||
|   | ||||
| @@ -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<ViewStyle>; | ||||
|   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; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user