From f04b233a4009ae36da0d0085fe1547095b5af6f5 Mon Sep 17 00:00:00 2001 From: Olivier Bouillet <62574056+freeboub@users.noreply.github.com> Date: Wed, 16 Oct 2024 23:41:22 +0200 Subject: [PATCH 01/27] chore: move minLoadRetryCount into source property (#4233) --- .../java/com/brentvatne/common/api/Source.kt | 9 +++++++-- .../exoplayer/ReactExoplayerView.java | 9 +-------- .../exoplayer/ReactExoplayerViewManager.kt | 6 ------ docs/pages/component/props.mdx | 19 +++++++++++++++++++ src/Video.tsx | 5 +++++ src/specs/VideoNativeComponent.ts | 2 +- src/types/video.ts | 2 ++ 7 files changed, 35 insertions(+), 17 deletions(-) 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 4437f668..0f6a3491 100644 --- a/android/src/main/java/com/brentvatne/common/api/Source.kt +++ b/android/src/main/java/com/brentvatne/common/api/Source.kt @@ -48,6 +48,9 @@ class Source { /** Metadata to display in notification */ var metadata: Metadata? = null + /** Allowed reload before failure notification */ + var minLoadRetryCount = 3 + /** http header list */ val headers: MutableMap = HashMap() @@ -91,7 +94,8 @@ class Source { contentStartTime == other.contentStartTime && cmcdProps == other.cmcdProps && sideLoadedTextTracks == other.sideLoadedTextTracks && - adsProps == other.adsProps + adsProps == other.adsProps && + minLoadRetryCount == other.minLoadRetryCount ) } @@ -159,6 +163,7 @@ class Source { private const val PROP_SRC_ADS = "ad" private const val PROP_SRC_TEXT_TRACKS_ALLOW_CHUNKLESS_PREPARATION = "textTracksAllowChunklessPreparation" private const val PROP_SRC_TEXT_TRACKS = "textTracks" + private const val PROP_SRC_MIN_LOAD_RETRY_COUNT = "minLoadRetryCount" @SuppressLint("DiscouragedApi") private fun getUriFromAssetId(context: Context, uriString: String): Uri? { @@ -223,7 +228,7 @@ class Source { } source.textTracksAllowChunklessPreparation = safeGetBool(src, PROP_SRC_TEXT_TRACKS_ALLOW_CHUNKLESS_PREPARATION, true) source.sideLoadedTextTracks = SideLoadedTextTrackList.parse(safeGetArray(src, PROP_SRC_TEXT_TRACKS)) - + source.minLoadRetryCount = safeGetInt(src, PROP_SRC_MIN_LOAD_RETRY_COUNT, 3) val propSrcHeadersArray = safeGetArray(src, PROP_SRC_HEADERS) if (propSrcHeadersArray != null) { if (propSrcHeadersArray.size() > 0) { diff --git a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java index 8fefa34c..a672997a 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java +++ b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java @@ -207,7 +207,6 @@ public class ReactExoplayerView extends FrameLayout implements private float rate = 1f; private AudioOutput audioOutput = AudioOutput.SPEAKER; private float audioVolume = 1f; - private int minLoadRetryCount = 3; private BufferConfig bufferConfig = new BufferConfig(); private int maxBitRate = 0; private boolean hasDrmFailed = false; @@ -1209,7 +1208,7 @@ public class ReactExoplayerView extends FrameLayout implements MediaSource mediaSource = mediaSourceFactory .setDrmSessionManagerProvider(drmProvider) .setLoadErrorHandlingPolicy( - config.buildLoadErrorHandlingPolicy(minLoadRetryCount) + config.buildLoadErrorHandlingPolicy(source.getMinLoadRetryCount()) ) .createMediaSource(mediaItem); @@ -2276,12 +2275,6 @@ public class ReactExoplayerView extends FrameLayout implements } } - public void setMinLoadRetryCountModifier(int newMinLoadRetryCount) { - minLoadRetryCount = newMinLoadRetryCount; - releasePlayer(); - initializePlayer(); - } - public void setPlayInBackground(boolean playInBackground) { this.playInBackground = playInBackground; } diff --git a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.kt b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.kt index 967da9ff..a2bebb8b 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.kt +++ b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.kt @@ -42,7 +42,6 @@ class ReactExoplayerViewManager(private val config: ReactExoplayerConfig) : View private const val PROP_PROGRESS_UPDATE_INTERVAL = "progressUpdateInterval" private const val PROP_REPORT_BANDWIDTH = "reportBandwidth" private const val PROP_RATE = "rate" - private const val PROP_MIN_LOAD_RETRY_COUNT = "minLoadRetryCount" private const val PROP_MAXIMUM_BIT_RATE = "maxBitRate" private const val PROP_PLAY_IN_BACKGROUND = "playInBackground" private const val PROP_DISABLE_FOCUS = "disableFocus" @@ -187,11 +186,6 @@ class ReactExoplayerViewManager(private val config: ReactExoplayerConfig) : View videoView.setMaxBitRateModifier(maxBitRate.toInt()) } - @ReactProp(name = PROP_MIN_LOAD_RETRY_COUNT) - fun setMinLoadRetryCount(videoView: ReactExoplayerView, minLoadRetryCount: Int) { - videoView.setMinLoadRetryCountModifier(minLoadRetryCount) - } - @ReactProp(name = PROP_PLAY_IN_BACKGROUND, defaultBoolean = false) fun setPlayInBackground(videoView: ReactExoplayerView, playInBackground: Boolean) { videoView.setPlayInBackground(playInBackground) diff --git a/docs/pages/component/props.mdx b/docs/pages/component/props.mdx index 3d2549a2..c5465df7 100644 --- a/docs/pages/component/props.mdx +++ b/docs/pages/component/props.mdx @@ -379,6 +379,8 @@ maxBitRate={2000000} // 2 megabits ``` ### `minLoadRetryCount` +> [!WARNING] +> deprecated, use `source.minLoadRetryCount` key instead @@ -903,6 +905,23 @@ source={{ }} ``` +#### `minLoadRetryCount` + + + +Sets the minimum number of times to retry loading data before failing and reporting an error to the application. Useful to recover from transient internet failures. + +Default: 3. Retry 3 times. + +Example: + +```javascript +source={{ + uri: 'https://bitdash-a.akamaihd.net/content/sintel/hls/playlist.m3u8', + minLoadRetryCount={5} // retry 5 times +}} +``` + #### `textTracks` diff --git a/src/Video.tsx b/src/Video.tsx index 8bdd92ed..9571493f 100644 --- a/src/Video.tsx +++ b/src/Video.tsx @@ -124,6 +124,7 @@ const Video = forwardRef( onVideoTracks, onAspectRatio, localSourceEncryptionKeyScheme, + minLoadRetryCount, ...rest }, ref, @@ -233,6 +234,8 @@ const Video = forwardRef( ? {adTagUrl: adTagUrl, adLanguage: adLanguage} : undefined); + const _minLoadRetryCount = + _source.minLoadRetryCount || minLoadRetryCount; return { uri, isNetwork, @@ -253,6 +256,7 @@ const Video = forwardRef( textTracks: _textTracks, textTracksAllowChunklessPreparation: resolvedSource.textTracksAllowChunklessPreparation, + minLoadRetryCount: _minLoadRetryCount, }; }, [ @@ -261,6 +265,7 @@ const Video = forwardRef( contentStartTime, drm, localSourceEncryptionKeyScheme, + minLoadRetryCount, source?.cmcd, textTracks, ], diff --git a/src/specs/VideoNativeComponent.ts b/src/specs/VideoNativeComponent.ts index c87af683..33a840f0 100644 --- a/src/specs/VideoNativeComponent.ts +++ b/src/specs/VideoNativeComponent.ts @@ -50,6 +50,7 @@ export type VideoSrc = Readonly<{ textTracksAllowChunklessPreparation?: boolean; // android textTracks?: TextTracks; ad?: AdsConfig; + minLoadRetryCount?: Int32; // Android }>; type DRMType = WithDefault; @@ -362,7 +363,6 @@ export interface VideoNativeProps extends ViewProps { disableDisconnectError?: boolean; // Android focusable?: boolean; // Android hideShutterView?: boolean; // Android - minLoadRetryCount?: Int32; // Android reportBandwidth?: boolean; //Android subtitleStyle?: SubtitleStyle; // android viewType?: Int32; // Android diff --git a/src/types/video.ts b/src/types/video.ts index ff965bc5..f3118cfa 100644 --- a/src/types/video.ts +++ b/src/types/video.ts @@ -39,6 +39,7 @@ export type ReactVideoSourceProperties = { textTracksAllowChunklessPreparation?: boolean; textTracks?: TextTracks; ad?: AdConfig; + minLoadRetryCount?: number; // Android }; export type ReactVideoSource = Readonly< @@ -305,6 +306,7 @@ export interface ReactVideoProps extends ReactVideoEvents, ViewProps { fullscreenOrientation?: EnumValues; // iOS hideShutterView?: boolean; // Android ignoreSilentSwitch?: EnumValues; // iOS + /** @deprecated Use source.minLoadRetryCount */ minLoadRetryCount?: number; // Android maxBitRate?: number; mixWithOthers?: EnumValues; // iOS From 7501880062a4c381838949084f0017a2aecc58d7 Mon Sep 17 00:00:00 2001 From: Olivier Bouillet <62574056+freeboub@users.noreply.github.com> Date: Thu, 17 Oct 2024 21:56:06 +0200 Subject: [PATCH 02/27] fix: remove warning and refactor & fix ad workflow (#4235) --- .../com/brentvatne/common/api/AdsProps.kt | 3 - .../com/brentvatne/common/api/CMCDProps.kt | 4 +- .../com/brentvatne/exoplayer/ExoPlayerView.kt | 28 +++-- .../exoplayer/ReactExoplayerView.java | 100 +++++++++--------- 4 files changed, 62 insertions(+), 73 deletions(-) diff --git a/android/src/main/java/com/brentvatne/common/api/AdsProps.kt b/android/src/main/java/com/brentvatne/common/api/AdsProps.kt index 9443d537..2f5a057c 100644 --- a/android/src/main/java/com/brentvatne/common/api/AdsProps.kt +++ b/android/src/main/java/com/brentvatne/common/api/AdsProps.kt @@ -2,7 +2,6 @@ package com.brentvatne.common.api import android.net.Uri import android.text.TextUtils -import com.brentvatne.common.toolbox.DebugLog import com.brentvatne.common.toolbox.ReactBridgeUtils import com.facebook.react.bridge.ReadableMap @@ -26,8 +25,6 @@ class AdsProps { @JvmStatic fun parse(src: ReadableMap?): AdsProps { val adsProps = AdsProps() - DebugLog.w("olivier", "uri: parse AdsProps") - if (src != null) { val uriString = ReactBridgeUtils.safeGetString(src, PROP_AD_TAG_URL) if (TextUtils.isEmpty(uriString)) { diff --git a/android/src/main/java/com/brentvatne/common/api/CMCDProps.kt b/android/src/main/java/com/brentvatne/common/api/CMCDProps.kt index 76bcefa1..dfff54b5 100644 --- a/android/src/main/java/com/brentvatne/common/api/CMCDProps.kt +++ b/android/src/main/java/com/brentvatne/common/api/CMCDProps.kt @@ -37,8 +37,8 @@ data class CMCDProps( return (0 until array.size()).mapNotNull { i -> val item = array.getMap(i) - val key = item?.getString("key") - val value = when (item?.getType("value")) { + val key = item.getString("key") + val value = when (item.getType("value")) { ReadableType.Number -> item.getDouble("value") ReadableType.String -> item.getString("value") else -> null diff --git a/android/src/main/java/com/brentvatne/exoplayer/ExoPlayerView.kt b/android/src/main/java/com/brentvatne/exoplayer/ExoPlayerView.kt index 98f2d0a5..462354c2 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/ExoPlayerView.kt +++ b/android/src/main/java/com/brentvatne/exoplayer/ExoPlayerView.kt @@ -40,7 +40,7 @@ class ExoPlayerView(private val context: Context) : ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT ) - private var adOverlayFrameLayout: FrameLayout + private var adOverlayFrameLayout: FrameLayout? = null val isPlaying: Boolean get() = player != null && player?.isPlaying == true @@ -72,12 +72,9 @@ class ExoPlayerView(private val context: Context) : updateSurfaceView(viewType) - adOverlayFrameLayout = FrameLayout(context) - layout.addView(shutterView, 1, layoutParams) if (localStyle.subtitlesFollowVideo) { layout.addView(subtitleLayout, layoutParams) - layout.addView(adOverlayFrameLayout, layoutParams) } addViewInLayout(layout, 0, aspectRatioParams) @@ -194,22 +191,21 @@ class ExoPlayerView(private val context: Context) : } } - private fun hideShutterView() { - shutterView.setVisibility(INVISIBLE) - surfaceView?.setAlpha(1f) - } - - private fun showShutterView() { - shutterView.setVisibility(VISIBLE) - surfaceView?.setAlpha(0f) - } - + var adsShown = false fun showAds() { - adOverlayFrameLayout.setVisibility(View.VISIBLE) + if (!adsShown) { + adOverlayFrameLayout = FrameLayout(context) + layout.addView(adOverlayFrameLayout, layoutParams) + adsShown = true + } } fun hideAds() { - adOverlayFrameLayout.setVisibility(View.GONE) + if (adsShown) { + layout.removeView(adOverlayFrameLayout) + adOverlayFrameLayout = null + adsShown = false + } } fun updateShutterViewVisibility() { diff --git a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java index a672997a..1246d9fd 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java +++ b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java @@ -83,7 +83,6 @@ import androidx.media3.exoplayer.source.DefaultMediaSourceFactory; import androidx.media3.exoplayer.source.MediaSource; import androidx.media3.exoplayer.source.MergingMediaSource; import androidx.media3.exoplayer.source.ProgressiveMediaSource; -import androidx.media3.exoplayer.source.SingleSampleMediaSource; import androidx.media3.exoplayer.source.TrackGroupArray; import androidx.media3.exoplayer.source.ads.AdsMediaSource; import androidx.media3.exoplayer.trackselection.AdaptiveTrackSelection; @@ -827,23 +826,6 @@ public class ReactExoplayerView extends FrameLayout implements mediaSourceFactory.setDataSourceFactory(RNVSimpleCache.INSTANCE.getCacheFactory(buildHttpDataSourceFactory(true))); } - if (BuildConfig.USE_EXOPLAYER_IMA) { - AdsProps adProps = source.getAdsProps(); - - // Create an AdsLoader. - ImaAdsLoader.Builder imaLoaderBuilder = new ImaAdsLoader - .Builder(themedReactContext) - .setAdEventListener(this) - .setAdErrorListener(this); - - if (adProps != null && adProps.getAdLanguage() != null) { - ImaSdkSettings imaSdkSettings = ImaSdkFactory.getInstance().createImaSdkSettings(); - imaSdkSettings.setLanguage(adProps.getAdLanguage()); - imaLoaderBuilder.setImaSdkSettings(imaSdkSettings); - } - adsLoader = imaLoaderBuilder.build(); - } - mediaSourceFactory.setLocalAdInsertionComponents(unusedAdTagUri -> adsLoader, exoPlayerView); player = new ExoPlayer.Builder(getContext(), renderersFactory) @@ -858,9 +840,6 @@ public class ReactExoplayerView extends FrameLayout implements player.setVolume(muted ? 0.f : audioVolume * 1); exoPlayerView.setPlayer(player); - if (adsLoader != null) { - adsLoader.setPlayer(player); - } audioBecomingNoisyReceiver.setListener(self); bandwidthMeter.addEventListener(new Handler(), self); setPlayWhenReady(!isPaused); @@ -875,6 +854,41 @@ public class ReactExoplayerView extends FrameLayout implements } } + private AdsMediaSource initializeAds(MediaSource videoSource, Source runningSource) { + AdsProps adProps = runningSource.getAdsProps(); + Uri uri = runningSource.getUri(); + if (adProps != null && uri != null) { + Uri adTagUrl = adProps.getAdTagUrl(); + if (adTagUrl != null) { + exoPlayerView.showAds(); + // Create an AdsLoader. + ImaAdsLoader.Builder imaLoaderBuilder = new ImaAdsLoader + .Builder(themedReactContext) + .setAdEventListener(this) + .setAdErrorListener(this); + + if (adProps.getAdLanguage() != null) { + ImaSdkSettings imaSdkSettings = ImaSdkFactory.getInstance().createImaSdkSettings(); + imaSdkSettings.setLanguage(adProps.getAdLanguage()); + imaLoaderBuilder.setImaSdkSettings(imaSdkSettings); + } + adsLoader = imaLoaderBuilder.build(); + adsLoader.setPlayer(player); + if (adsLoader != null) { + DefaultMediaSourceFactory mediaSourceFactory = new DefaultMediaSourceFactory(mediaDataSourceFactory) + .setLocalAdInsertionComponents(unusedAdTagUri -> adsLoader, exoPlayerView); + DataSpec adTagDataSpec = new DataSpec(adTagUrl); + return new AdsMediaSource(videoSource, + adTagDataSpec, + ImmutableList.of(uri, adTagUrl), + mediaSourceFactory, adsLoader, exoPlayerView); + } + } + } + exoPlayerView.hideAds(); + return null; + } + private DrmSessionManager initializePlayerDrm() { DrmSessionManager drmSessionManager = null; DRMProps drmProps = source.getDrmProps(); @@ -908,32 +922,17 @@ public class ReactExoplayerView extends FrameLayout implements return; } // init source to manage ads and external text tracks - MediaSource subtitlesSource = buildTextSource(); - MediaSource videoSource = buildMediaSource(runningSource.getUri(), runningSource.getExtension(), drmSessionManager, runningSource.getCropStartMs(), runningSource.getCropEndMs()); - MediaSource mediaSourceWithAds = null; - Uri adTagUrl = null; - if (source.getAdsProps() != null) { - adTagUrl = source.getAdsProps().getAdTagUrl(); - } - if (adTagUrl != null && adsLoader != null) { - DefaultMediaSourceFactory mediaSourceFactory = new DefaultMediaSourceFactory(mediaDataSourceFactory) - .setLocalAdInsertionComponents(unusedAdTagUri -> adsLoader, exoPlayerView); - DataSpec adTagDataSpec = new DataSpec(adTagUrl); - DebugLog.w(TAG, "ads " + adTagUrl); - mediaSourceWithAds = new AdsMediaSource(videoSource, adTagDataSpec, ImmutableList.of(runningSource.getUri(), adTagUrl), mediaSourceFactory, adsLoader, exoPlayerView); - exoPlayerView.showAds(); - } - MediaSource mediaSource; - if (subtitlesSource == null) { - mediaSource = Objects.requireNonNullElse(mediaSourceWithAds, videoSource); - } else { - ArrayList mediaSourceList = new ArrayList<>(); - mediaSourceList.add(subtitlesSource); - mediaSourceList.add(0, Objects.requireNonNullElse(mediaSourceWithAds, videoSource)); - MediaSource[] mediaSourceArray = mediaSourceList.toArray( - new MediaSource[mediaSourceList.size()] - ); + MediaSource videoSource = buildMediaSource(runningSource.getUri(), + runningSource.getExtension(), + drmSessionManager, + runningSource.getCropStartMs(), + runningSource.getCropEndMs()); + MediaSource mediaSourceWithAds = initializeAds(videoSource, runningSource); + MediaSource mediaSource = Objects.requireNonNullElse(mediaSourceWithAds, videoSource); + MediaSource subtitlesSource = buildTextSource(); + if (subtitlesSource != null) { + MediaSource[] mediaSourceArray = {mediaSource, subtitlesSource}; mediaSource = new MergingMediaSource(mediaSourceArray); } @@ -1132,6 +1131,7 @@ public class ReactExoplayerView extends FrameLayout implements drmProvider = new DefaultDrmSessionManagerProvider(); } + switch (type) { case CONTENT_TYPE_SS: if(!BuildConfig.USE_EXOPLAYER_SMOOTH_STREAMING) { @@ -1905,7 +1905,8 @@ public class ReactExoplayerView extends FrameLayout implements } if (!isSourceEqual) { - reloadSource(); + playerNeedsSource = true; + initializePlayer(); } } else { clearSrc(); @@ -1932,11 +1933,6 @@ public class ReactExoplayerView extends FrameLayout implements mReportBandwidth = reportBandwidth; } - private void reloadSource() { - playerNeedsSource = true; - initializePlayer(); - } - public void setResizeModeModifier(@ResizeMode.Mode int resizeMode) { if (exoPlayerView != null) { exoPlayerView.setResizeMode(resizeMode); From 7c7d83b6e551a7920175047321b02fcace5cbe9e Mon Sep 17 00:00:00 2001 From: Olivier Bouillet Date: Thu, 17 Oct 2024 21:58:53 +0200 Subject: [PATCH 03/27] chore: release v6.7.0 --- CHANGELOG.md | 17 +++++++++++++++++ package.json | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e2180b5..8ab0159f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,22 @@ +# [6.7.0](https://github.com/TheWidlarzGroup/react-native-video/compare/v6.6.4...v6.7.0) (2024-10-17) + + +### Bug Fixes + +* **android:** sideloaded subtitles ([#4232](https://github.com/TheWidlarzGroup/react-native-video/issues/4232)) ([352dfbb](https://github.com/TheWidlarzGroup/react-native-video/commit/352dfbbc9bef158400c59516a50d889d25757c0d)) +* ensure aspect ratio from video is handled in a coherent way ([#4219](https://github.com/TheWidlarzGroup/react-native-video/issues/4219)) ([a8d5841](https://github.com/TheWidlarzGroup/react-native-video/commit/a8d5841c7c0f9767ec095ffd8401b1579f32623f)) +* **iOS:** pause video on end reached & don't remove listeners ([#4218](https://github.com/TheWidlarzGroup/react-native-video/issues/4218)) ([2c19a47](https://github.com/TheWidlarzGroup/react-native-video/commit/2c19a4770df73179436a9e23a5e55ad0699fcfcc)) +* remove warning and refactor & fix ad workflow ([#4235](https://github.com/TheWidlarzGroup/react-native-video/issues/4235)) ([7501880](https://github.com/TheWidlarzGroup/react-native-video/commit/7501880062a4c381838949084f0017a2aecc58d7)) + + +### Features + +* add setSource API function fix ads playback ([#4185](https://github.com/TheWidlarzGroup/react-native-video/issues/4185)) ([9a3fcda](https://github.com/TheWidlarzGroup/react-native-video/commit/9a3fcda3b8ca4689c9131a12a8375fc43d442f80)) +* **android:** add settings button to control video playback speed ([#4211](https://github.com/TheWidlarzGroup/react-native-video/issues/4211)) ([d1883a7](https://github.com/TheWidlarzGroup/react-native-video/commit/d1883a7e008706cac2a2beac934194539c9b5b77)) +* **exoplayerview:** Migrate ExoPlayerView to kotlin ([#4038](https://github.com/TheWidlarzGroup/react-native-video/issues/4038)) ([78f4f04](https://github.com/TheWidlarzGroup/react-native-video/commit/78f4f0480d70d209fea9e0579963e347c965fd6e)) + ## [6.6.4](https://github.com/TheWidlarzGroup/react-native-video/compare/v6.6.3...v6.6.4) (2024-10-03) diff --git a/package.json b/package.json index 30e48298..c1cdfb88 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-video", - "version": "6.6.4", + "version": "6.7.0", "description": "A