From 2348c5e42c07eced8520fd5564241f62f1cf4c43 Mon Sep 17 00:00:00 2001 From: EETVApps <168638691+EETVApps@users.noreply.github.com> Date: Fri, 2 Aug 2024 09:42:20 +0100 Subject: [PATCH 01/72] fix(ios): updated getLicense call to work with new syntax, and fixed spelling error (#4014) (#4042) * fix(android): updated getLicense call to work with new syntax, and fixed spelling error (#4014) * fix(android): corrected error with my refactor (#4014) * fix(android): Removed trailing space (#4014) * fix(ios): Refactored following review (#4014) * fix(ios): Lint tidy (#4014) * fix(ios): Removed incorrect semi-colon (#4014) * fix(ios): Fixed more lint errors (#4014) --------- Co-authored-by: Darren Taft --- ios/Video/RCTVideoManager.m | 2 +- src/Video.tsx | 51 ++++++++++++++++++++++--------------- 2 files changed, 32 insertions(+), 21 deletions(-) diff --git a/ios/Video/RCTVideoManager.m b/ios/Video/RCTVideoManager.m index 2693d36e8d..3807f955ba 100644 --- a/ios/Video/RCTVideoManager.m +++ b/ios/Video/RCTVideoManager.m @@ -69,7 +69,7 @@ @interface RCT_EXTERN_MODULE (RCTVideoManager, RCTViewManager) RCT_EXPORT_VIEW_PROPERTY(onTextTrackDataChanged, RCTDirectEventBlock); RCT_EXTERN_METHOD(seekCmd : (nonnull NSNumber*)reactTag time : (nonnull NSNumber*)time tolerance : (nonnull NSNumber*)tolerance) -RCT_EXTERN_METHOD(setLicenseResultCmd : (nonnull NSNumber*)reactTag lisence : (NSString*)license licenseUrl : (NSString*)licenseUrl) +RCT_EXTERN_METHOD(setLicenseResultCmd : (nonnull NSNumber*)reactTag license : (NSString*)license licenseUrl : (NSString*)licenseUrl) RCT_EXTERN_METHOD(setLicenseResultErrorCmd : (nonnull NSNumber*)reactTag error : (NSString*)error licenseUrl : (NSString*)licenseUrl) RCT_EXTERN_METHOD(setPlayerPauseStateCmd : (nonnull NSNumber*)reactTag paused : (nonnull BOOL)paused) RCT_EXTERN_METHOD(setVolumeCmd : (nonnull NSNumber*)reactTag volume : (nonnull float*)volume) diff --git a/src/Video.tsx b/src/Video.tsx index 49ca1e990b..da24b5671b 100644 --- a/src/Video.tsx +++ b/src/Video.tsx @@ -512,7 +512,8 @@ const Video = forwardRef( [onControlsVisibilityChange], ); - const usingExternalGetLicense = drm?.getLicense instanceof Function; + const selectedDrm = source?.drm || drm; + const usingExternalGetLicense = selectedDrm?.getLicense instanceof Function; const onGetLicense = useCallback( async (event: NativeSyntheticEvent) => { @@ -520,33 +521,43 @@ const Video = forwardRef( return; } const data = event.nativeEvent; - let result; - if (data?.spcBase64) { - try { - // Handles both scenarios, getLicenseOverride being a promise and not. - const license = await drm.getLicense( + try { + if (!data?.spcBase64) { + throw new Error('No spc received'); + } + // Handles both scenarios, getLicenseOverride being a promise and not. + const license = await Promise.resolve( + selectedDrm.getLicense( data.spcBase64, data.contentId, data.licenseUrl, data.loadedLicenseUrl, + ), + ).catch(() => { + throw new Error('fetch error'); + }); + if (typeof license !== 'string') { + throw Error('Empty license result'); + } + if (nativeRef.current) { + NativeVideoManager.setLicenseResultCmd( + getReactTag(nativeRef), + license, + data.loadedLicenseUrl, + ); + } + } catch (e) { + const msg = e instanceof Error ? e.message : 'fetch error'; + if (nativeRef.current) { + NativeVideoManager.setLicenseResultErrorCmd( + getReactTag(nativeRef), + msg, + data.loadedLicenseUrl, ); - result = - typeof license === 'string' ? license : 'Empty license result'; - } catch { - result = 'fetch error'; } - } else { - result = 'No spc received'; - } - if (nativeRef.current) { - NativeVideoManager.setLicenseResultErrorCmd( - getReactTag(nativeRef), - result, - data.loadedLicenseUrl, - ); } }, - [drm, usingExternalGetLicense], + [selectedDrm, usingExternalGetLicense], ); useImperativeHandle( From 08a57a3ba3c0de55d08684036fb6eb157995419c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C5=82a=C5=BCej=20Lewandowski?= Date: Fri, 2 Aug 2024 10:49:52 +0200 Subject: [PATCH 02/72] fix(ios): metadata update race (#4033) * fix metadata update race * merge nowPlayingInfo instead of overriding it * update method name * fix code style --- ios/Video/NowPlayingInfoCenterManager.swift | 35 +++++---------------- ios/Video/RCTVideo.swift | 4 +-- 2 files changed, 9 insertions(+), 30 deletions(-) diff --git a/ios/Video/NowPlayingInfoCenterManager.swift b/ios/Video/NowPlayingInfoCenterManager.swift index 36bfe8eb32..981f8ab5e6 100644 --- a/ios/Video/NowPlayingInfoCenterManager.swift +++ b/ios/Video/NowPlayingInfoCenterManager.swift @@ -72,7 +72,7 @@ class NowPlayingInfoCenterManager { if currentPlayer == player { currentPlayer = nil - updateMetadata() + updateNowPlayingInfo() } if players.allObjects.isEmpty { @@ -106,14 +106,12 @@ class NowPlayingInfoCenterManager { currentPlayer = player registerCommandTargets() - updateMetadata() - - // one second observer + updateNowPlayingInfo() playbackObserver = player.addPeriodicTimeObserver( forInterval: CMTime(value: 1, timescale: 4), queue: .global(), using: { [weak self] _ in - self?.updatePlaybackInfo() + self?.updateNowPlayingInfo() } ) } @@ -186,7 +184,7 @@ class NowPlayingInfoCenterManager { remoteCommandCenter.changePlaybackPositionCommand.removeTarget(playbackPositionTarget) } - public func updateMetadata() { + public func updateNowPlayingInfo() { guard let player = currentPlayer, let currentItem = player.currentItem else { invalidateCommandTargets() MPNowPlayingInfoCenter.default().nowPlayingInfo = [:] @@ -206,7 +204,7 @@ class NowPlayingInfoCenterManager { let image = imgData.flatMap { UIImage(data: $0) } ?? UIImage() let artworkItem = MPMediaItemArtwork(boundsSize: image.size) { _ in image } - let nowPlayingInfo: [String: Any] = [ + let newNowPlayingInfo: [String: Any] = [ MPMediaItemPropertyTitle: titleItem, MPMediaItemPropertyArtist: artistItem, MPMediaItemPropertyArtwork: artworkItem, @@ -215,28 +213,9 @@ class NowPlayingInfoCenterManager { MPNowPlayingInfoPropertyPlaybackRate: player.rate, MPNowPlayingInfoPropertyIsLiveStream: CMTIME_IS_INDEFINITE(currentItem.asset.duration), ] + let currentNowPlayingInfo = MPNowPlayingInfoCenter.default().nowPlayingInfo ?? [:] - MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo - } - - private func updatePlaybackInfo() { - guard let player = currentPlayer, let currentItem = player.currentItem else { - return - } - - // We dont want to update playback if we did not set metadata yet - if var nowPlayingInfo = MPNowPlayingInfoCenter.default().nowPlayingInfo { - let newNowPlayingInfo: [String: Any] = [ - MPMediaItemPropertyPlaybackDuration: currentItem.duration.seconds, - MPNowPlayingInfoPropertyElapsedPlaybackTime: currentItem.currentTime().seconds.rounded(), - MPNowPlayingInfoPropertyPlaybackRate: player.rate, - MPNowPlayingInfoPropertyIsLiveStream: CMTIME_IS_INDEFINITE(currentItem.asset.duration), - ] - - nowPlayingInfo.merge(newNowPlayingInfo) { _, v in v } - - MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo - } + MPNowPlayingInfoCenter.default().nowPlayingInfo = currentNowPlayingInfo.merging(newNowPlayingInfo) { _, new in new } } private func findNewCurrentPlayer() { diff --git a/ios/Video/RCTVideo.swift b/ios/Video/RCTVideo.swift index c4f4e99a50..325a06f174 100644 --- a/ios/Video/RCTVideo.swift +++ b/ios/Video/RCTVideo.swift @@ -490,8 +490,8 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH } else { _player?.replaceCurrentItem(with: playerItem) - // later we can just call "updateMetadata: - NowPlayingInfoCenterManager.shared.updateMetadata() + // later we can just call "updateNowPlayingInfo: + NowPlayingInfoCenterManager.shared.updateNowPlayingInfo() } _playerObserver.player = _player From 22cfd6cead7c7182c0d23a6c897698526bd3d6de Mon Sep 17 00:00:00 2001 From: Mickael Lecoq Date: Fri, 2 Aug 2024 10:50:27 +0200 Subject: [PATCH 03/72] fix(android): viewType is ignored when its value is ViewType.TEXTURE (#4031) --- src/Video.tsx | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/src/Video.tsx b/src/Video.tsx index da24b5671b..98b96b64e2 100644 --- a/src/Video.tsx +++ b/src/Video.tsx @@ -595,28 +595,32 @@ const Video = forwardRef( const shallForceViewType = hasValidDrmProp && (viewType === ViewType.TEXTURE || useTextureView); - if (shallForceViewType) { + if (useSecureView && useTextureView) { console.warn( - 'cannot use DRM on texture view. please set useTextureView={false}', + 'cannot use SecureView on texture view. please set useTextureView={false}', ); } - if (useSecureView && useTextureView) { + + if (shallForceViewType) { console.warn( - 'cannot use SecureView on texture view. please set useTextureView={false}', + 'cannot use DRM on texture view. please set useTextureView={false}', ); + return useSecureView ? ViewType.SURFACE_SECURE : ViewType.SURFACE; + } + + if (viewType !== undefined && viewType !== null) { + return viewType; + } + + if (useSecureView) { + return ViewType.SURFACE_SECURE; + } + + if (useTextureView) { + return ViewType.TEXTURE; } - return shallForceViewType - ? useSecureView - ? ViewType.SURFACE_SECURE - : ViewType.SURFACE // check if we should force the type to Surface due to DRM - : viewType - ? viewType // else use ViewType from source - : useSecureView // else infer view type from useSecureView and useTextureView - ? ViewType.SURFACE_SECURE - : useTextureView - ? ViewType.TEXTURE - : ViewType.SURFACE; + return ViewType.SURFACE; }, [drm, useSecureView, useTextureView, viewType]); const _renderPoster = useCallback(() => { From 74c6dd62797fb1d29e0f03b5483f57fa065e8d93 Mon Sep 17 00:00:00 2001 From: Seyed Mostafa Hasani Date: Fri, 2 Aug 2024 12:22:08 +0330 Subject: [PATCH 04/72] refactor(android): migrate ReactExoplayerViewManager to Kotlin (#4011) * Rename .java to .kt * refactor(android): migrate ReactExoplayerViewManager to Kotlin * fix: lint error * refactor: setPreventsDisplaySleepDuringVideoPlayback function --- .../exoplayer/ReactExoplayerViewManager.java | 346 ------------------ .../exoplayer/ReactExoplayerViewManager.kt | 312 ++++++++++++++++ .../react/ReactNativeVideoManager.kt | 18 +- 3 files changed, 320 insertions(+), 356 deletions(-) delete mode 100644 android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java create mode 100644 android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.kt diff --git a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java deleted file mode 100644 index e9ae70bc9b..0000000000 --- a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java +++ /dev/null @@ -1,346 +0,0 @@ -package com.brentvatne.exoplayer; - -import android.content.Context; -import android.graphics.Color; -import android.net.Uri; -import android.text.TextUtils; -import android.util.Log; - -import androidx.annotation.NonNull; - -import com.brentvatne.common.api.BufferConfig; -import com.brentvatne.common.api.BufferingStrategy; -import com.brentvatne.common.api.ControlsConfig; -import com.brentvatne.common.api.DRMProps; -import com.brentvatne.common.api.ResizeMode; -import com.brentvatne.common.api.SideLoadedTextTrackList; -import com.brentvatne.common.api.Source; -import com.brentvatne.common.api.SubtitleStyle; -import com.brentvatne.common.api.ViewType; -import com.brentvatne.common.react.EventTypes; -import com.brentvatne.common.toolbox.DebugLog; -import com.brentvatne.common.toolbox.ReactBridgeUtils; -import com.brentvatne.react.ReactNativeVideoManager; -import com.facebook.react.bridge.ReadableArray; -import com.facebook.react.bridge.ReadableMap; -import com.facebook.react.uimanager.ThemedReactContext; -import com.facebook.react.uimanager.ViewGroupManager; -import com.facebook.react.uimanager.annotations.ReactProp; - -import java.util.Map; - -import javax.annotation.Nullable; - -public class ReactExoplayerViewManager extends ViewGroupManager { - - private static final String TAG = "ExoViewManager"; - 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_RESIZE_MODE = "resizeMode"; - private static final String PROP_REPEAT = "repeat"; - private static final String PROP_SELECTED_AUDIO_TRACK = "selectedAudioTrack"; - private static final String PROP_SELECTED_AUDIO_TRACK_TYPE = "type"; - private static final String PROP_SELECTED_AUDIO_TRACK_VALUE = "value"; - private static final String PROP_SELECTED_TEXT_TRACK = "selectedTextTrack"; - private static final String PROP_SELECTED_TEXT_TRACK_TYPE = "type"; - private static final String PROP_SELECTED_TEXT_TRACK_VALUE = "value"; - private static final String PROP_TEXT_TRACKS = "textTracks"; - private static final String PROP_PAUSED = "paused"; - private static final String PROP_MUTED = "muted"; - private static final String PROP_AUDIO_OUTPUT = "audioOutput"; - private static final String PROP_VOLUME = "volume"; - private static final String PROP_BUFFER_CONFIG = "bufferConfig"; - private static final String PROP_PREVENTS_DISPLAY_SLEEP_DURING_VIDEO_PLAYBACK = "preventsDisplaySleepDuringVideoPlayback"; - private static final String PROP_PROGRESS_UPDATE_INTERVAL = "progressUpdateInterval"; - private static final String PROP_REPORT_BANDWIDTH = "reportBandwidth"; - private static final String PROP_RATE = "rate"; - private static final String PROP_MIN_LOAD_RETRY_COUNT = "minLoadRetryCount"; - private static final String PROP_MAXIMUM_BIT_RATE = "maxBitRate"; - private static final String PROP_PLAY_IN_BACKGROUND = "playInBackground"; - private static final String PROP_CONTENT_START_TIME = "contentStartTime"; - private static final String PROP_DISABLE_FOCUS = "disableFocus"; - private static final String PROP_BUFFERING_STRATEGY = "bufferingStrategy"; - private static final String PROP_DISABLE_DISCONNECT_ERROR = "disableDisconnectError"; - private static final String PROP_FOCUSABLE = "focusable"; - private static final String PROP_FULLSCREEN = "fullscreen"; - private static final String PROP_VIEW_TYPE = "viewType"; - private static final String PROP_SELECTED_VIDEO_TRACK = "selectedVideoTrack"; - private static final String PROP_SELECTED_VIDEO_TRACK_TYPE = "type"; - private static final String PROP_SELECTED_VIDEO_TRACK_VALUE = "value"; - private static final String PROP_HIDE_SHUTTER_VIEW = "hideShutterView"; - private static final String PROP_CONTROLS = "controls"; - private static final String PROP_SUBTITLE_STYLE = "subtitleStyle"; - private static final String PROP_SHUTTER_COLOR = "shutterColor"; - private static final String PROP_SHOW_NOTIFICATION_CONTROLS = "showNotificationControls"; - private static final String PROP_DEBUG = "debug"; - private static final String PROP_CONTROLS_STYLES = "controlsStyles"; - - private final ReactExoplayerConfig config; - - public ReactExoplayerViewManager(ReactExoplayerConfig config) { - this.config = config; - } - - @NonNull - @Override - public String getName() { - return REACT_CLASS; - } - - @NonNull - @Override - protected ReactExoplayerView createViewInstance(@NonNull ThemedReactContext themedReactContext) { - ReactNativeVideoManager.Companion.getInstance().registerView(this); - return new ReactExoplayerView(themedReactContext, config); - } - - @Override - public void onDropViewInstance(ReactExoplayerView view) { - view.cleanUpResources(); - ReactNativeVideoManager.Companion.getInstance().unregisterView(this); - } - - @Override - public @Nullable Map getExportedCustomDirectEventTypeConstants() { - return EventTypes.Companion.toMap(); - } - - @Override - public void addEventEmitters(@NonNull ThemedReactContext reactContext, @NonNull ReactExoplayerView view) { - super.addEventEmitters(reactContext, view); - view.eventEmitter.addEventEmitters(reactContext, view); - } - - @ReactProp(name = PROP_SRC) - public void setSrc(final ReactExoplayerView videoView, @Nullable ReadableMap src) { - Context context = videoView.getContext().getApplicationContext(); - Source source = Source.parse(src, context); - if (source.getUri() == null) { - videoView.clearSrc(); - } else { - videoView.setSrc(source); - } - } - - @ReactProp(name = PROP_AD_TAG_URL) - public void setAdTagUrl(final ReactExoplayerView videoView, final String uriString) { - if (TextUtils.isEmpty(uriString)) { - videoView.setAdTagUrl(null); - return; - } - - Uri adTagUrl = Uri.parse(uriString); - - videoView.setAdTagUrl(adTagUrl); - } - - @ReactProp(name = PROP_RESIZE_MODE) - public void setResizeMode(final ReactExoplayerView videoView, final String resizeMode) { - switch (resizeMode) { - case "none": - case "contain": - videoView.setResizeModeModifier(ResizeMode.RESIZE_MODE_FIT); - break; - case "cover": - videoView.setResizeModeModifier(ResizeMode.RESIZE_MODE_CENTER_CROP); - break; - case "stretch": - videoView.setResizeModeModifier(ResizeMode.RESIZE_MODE_FILL); - break; - default: - DebugLog.w(TAG, "Unsupported resize mode: " + resizeMode + " - falling back to fit"); - videoView.setResizeModeModifier(ResizeMode.RESIZE_MODE_FIT); - break; - } - } - - @ReactProp(name = PROP_REPEAT, defaultBoolean = false) - public void setRepeat(final ReactExoplayerView videoView, final boolean repeat) { - videoView.setRepeatModifier(repeat); - } - - @ReactProp(name = PROP_PREVENTS_DISPLAY_SLEEP_DURING_VIDEO_PLAYBACK, defaultBoolean = false) - public void setPreventsDisplaySleepDuringVideoPlayback(final ReactExoplayerView videoView, final boolean preventsSleep) { - videoView.setPreventsDisplaySleepDuringVideoPlayback(preventsSleep); - } - - @ReactProp(name = PROP_SELECTED_VIDEO_TRACK) - public void setSelectedVideoTrack(final ReactExoplayerView videoView, - @Nullable ReadableMap selectedVideoTrack) { - String typeString = null; - String value = null; - if (selectedVideoTrack != null) { - typeString = ReactBridgeUtils.safeGetString(selectedVideoTrack, PROP_SELECTED_VIDEO_TRACK_TYPE); - value = ReactBridgeUtils.safeGetString(selectedVideoTrack, PROP_SELECTED_VIDEO_TRACK_VALUE); - } - videoView.setSelectedVideoTrack(typeString, value); - } - - @ReactProp(name = PROP_SELECTED_AUDIO_TRACK) - public void setSelectedAudioTrack(final ReactExoplayerView videoView, - @Nullable ReadableMap selectedAudioTrack) { - String typeString = null; - String value = null; - if (selectedAudioTrack != null) { - typeString = ReactBridgeUtils.safeGetString(selectedAudioTrack, PROP_SELECTED_AUDIO_TRACK_TYPE); - value = ReactBridgeUtils.safeGetString(selectedAudioTrack, PROP_SELECTED_AUDIO_TRACK_VALUE); - } - videoView.setSelectedAudioTrack(typeString, value); - } - - @ReactProp(name = PROP_SELECTED_TEXT_TRACK) - public void setSelectedTextTrack(final ReactExoplayerView videoView, - @Nullable ReadableMap selectedTextTrack) { - String typeString = null; - String value = null; - if (selectedTextTrack != null) { - typeString = ReactBridgeUtils.safeGetString(selectedTextTrack, PROP_SELECTED_TEXT_TRACK_TYPE); - value = ReactBridgeUtils.safeGetString(selectedTextTrack, PROP_SELECTED_TEXT_TRACK_VALUE); - } - videoView.setSelectedTextTrack(typeString, value); - } - - @ReactProp(name = PROP_TEXT_TRACKS) - public void setTextTracks(final ReactExoplayerView videoView, - @Nullable ReadableArray textTracks) { - SideLoadedTextTrackList sideLoadedTextTracks = SideLoadedTextTrackList.Companion.parse(textTracks); - videoView.setTextTracks(sideLoadedTextTracks); - } - - @ReactProp(name = PROP_PAUSED, defaultBoolean = false) - public void setPaused(final ReactExoplayerView videoView, final boolean paused) { - videoView.setPausedModifier(paused); - } - - @ReactProp(name = PROP_MUTED, defaultBoolean = false) - public void setMuted(final ReactExoplayerView videoView, final boolean muted) { - videoView.setMutedModifier(muted); - } - - @ReactProp(name = PROP_AUDIO_OUTPUT) - public void setAudioOutput(final ReactExoplayerView videoView, final String audioOutput) { - videoView.setAudioOutput(AudioOutput.get(audioOutput)); - } - - @ReactProp(name = PROP_VOLUME, defaultFloat = 1.0f) - public void setVolume(final ReactExoplayerView videoView, final float volume) { - videoView.setVolumeModifier(volume); - } - - @ReactProp(name = PROP_PROGRESS_UPDATE_INTERVAL, defaultFloat = 250.0f) - public void setProgressUpdateInterval(final ReactExoplayerView videoView, final float progressUpdateInterval) { - videoView.setProgressUpdateInterval(progressUpdateInterval); - } - - @ReactProp(name = PROP_REPORT_BANDWIDTH, defaultBoolean = false) - public void setReportBandwidth(final ReactExoplayerView videoView, final boolean reportBandwidth) { - videoView.setReportBandwidth(reportBandwidth); - } - - @ReactProp(name = PROP_RATE) - public void setRate(final ReactExoplayerView videoView, final float rate) { - videoView.setRateModifier(rate); - } - - @ReactProp(name = PROP_MAXIMUM_BIT_RATE) - public void setMaxBitRate(final ReactExoplayerView videoView, final float maxBitRate) { - videoView.setMaxBitRateModifier((int)maxBitRate); - } - - @ReactProp(name = PROP_MIN_LOAD_RETRY_COUNT) - public void setMinLoadRetryCount(final ReactExoplayerView videoView, final int minLoadRetryCount) { - videoView.setMinLoadRetryCountModifier(minLoadRetryCount); - } - - @ReactProp(name = PROP_PLAY_IN_BACKGROUND, defaultBoolean = false) - public void setPlayInBackground(final ReactExoplayerView videoView, final boolean playInBackground) { - videoView.setPlayInBackground(playInBackground); - } - - @ReactProp(name = PROP_DISABLE_FOCUS, defaultBoolean = false) - public void setDisableFocus(final ReactExoplayerView videoView, final boolean disableFocus) { - videoView.setDisableFocus(disableFocus); - } - - @ReactProp(name = PROP_FOCUSABLE, defaultBoolean = true) - public void setFocusable(final ReactExoplayerView videoView, final boolean focusable) { - videoView.setFocusable(focusable); - } - - @ReactProp(name = PROP_CONTENT_START_TIME, defaultInt = -1) - public void setContentStartTime(final ReactExoplayerView videoView, final int contentStartTime) { - videoView.setContentStartTime(contentStartTime); - } - - @ReactProp(name = PROP_BUFFERING_STRATEGY) - public void setBufferingStrategy(final ReactExoplayerView videoView, final String bufferingStrategy) { - BufferingStrategy.BufferingStrategyEnum strategy = BufferingStrategy.Companion.parse(bufferingStrategy); - videoView.setBufferingStrategy(strategy); - } - - @ReactProp(name = PROP_DISABLE_DISCONNECT_ERROR, defaultBoolean = false) - public void setDisableDisconnectError(final ReactExoplayerView videoView, final boolean disableDisconnectError) { - videoView.setDisableDisconnectError(disableDisconnectError); - } - - @ReactProp(name = PROP_FULLSCREEN, defaultBoolean = false) - public void setFullscreen(final ReactExoplayerView videoView, final boolean fullscreen) { - videoView.setFullscreen(fullscreen); - } - - @ReactProp(name = PROP_VIEW_TYPE, defaultInt = ViewType.VIEW_TYPE_SURFACE) - public void setViewType(final ReactExoplayerView videoView, final int viewType) { - videoView.setViewType(viewType); - } - - @ReactProp(name = PROP_HIDE_SHUTTER_VIEW, defaultBoolean = false) - public void setHideShutterView(final ReactExoplayerView videoView, final boolean hideShutterView) { - videoView.setHideShutterView(hideShutterView); - } - - @ReactProp(name = PROP_CONTROLS, defaultBoolean = false) - public void setControls(final ReactExoplayerView videoView, final boolean controls) { - videoView.setControls(controls); - } - - @ReactProp(name = PROP_SUBTITLE_STYLE) - public void setSubtitleStyle(final ReactExoplayerView videoView, @Nullable final ReadableMap src) { - videoView.setSubtitleStyle(SubtitleStyle.parse(src)); - } - - @ReactProp(name = PROP_SHUTTER_COLOR, defaultInt = 0) - public void setShutterColor(final ReactExoplayerView videoView, final int color) { - videoView.setShutterColor(color == 0 ? Color.BLACK : color); - } - - @ReactProp(name = PROP_BUFFER_CONFIG) - public void setBufferConfig(final ReactExoplayerView videoView, @Nullable ReadableMap bufferConfig) { - BufferConfig config = BufferConfig.parse(bufferConfig); - videoView.setBufferConfig(config); - } - - @ReactProp(name = PROP_SHOW_NOTIFICATION_CONTROLS) - public void setShowNotificationControls(final ReactExoplayerView videoView, final boolean showNotificationControls) { - videoView.setShowNotificationControls(showNotificationControls); - } - - @ReactProp(name = PROP_DEBUG, defaultBoolean = false) - public void setDebug(final ReactExoplayerView videoView, - @Nullable final ReadableMap debugConfig) { - boolean enableDebug = ReactBridgeUtils.safeGetBool(debugConfig, "enable", false); - boolean enableThreadDebug = ReactBridgeUtils.safeGetBool(debugConfig, "thread", false); - if (enableDebug) { - DebugLog.setConfig(Log.VERBOSE, enableThreadDebug); - } else { - DebugLog.setConfig(Log.WARN, enableThreadDebug); - } - videoView.setDebug(enableDebug); - } - - @ReactProp(name = PROP_CONTROLS_STYLES) - public void setControlsStyles(final ReactExoplayerView videoView, @Nullable ReadableMap controlsStyles) { - ControlsConfig controlsConfig = ControlsConfig.parse(controlsStyles); - videoView.setControlsStyles(controlsConfig); - } -} \ No newline at end of file diff --git a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.kt b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.kt new file mode 100644 index 0000000000..8299c34524 --- /dev/null +++ b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.kt @@ -0,0 +1,312 @@ +package com.brentvatne.exoplayer + +import android.graphics.Color +import android.net.Uri +import android.text.TextUtils +import android.util.Log +import com.brentvatne.common.api.BufferConfig +import com.brentvatne.common.api.BufferingStrategy +import com.brentvatne.common.api.ControlsConfig +import com.brentvatne.common.api.ResizeMode +import com.brentvatne.common.api.SideLoadedTextTrackList +import com.brentvatne.common.api.Source +import com.brentvatne.common.api.SubtitleStyle +import com.brentvatne.common.api.ViewType +import com.brentvatne.common.react.EventTypes +import com.brentvatne.common.toolbox.DebugLog +import com.brentvatne.common.toolbox.ReactBridgeUtils +import com.brentvatne.react.ReactNativeVideoManager +import com.facebook.react.bridge.ReadableArray +import com.facebook.react.bridge.ReadableMap +import com.facebook.react.uimanager.ThemedReactContext +import com.facebook.react.uimanager.ViewGroupManager +import com.facebook.react.uimanager.annotations.ReactProp + +class ReactExoplayerViewManager(private val config: ReactExoplayerConfig) : ViewGroupManager() { + + companion object { + private const val TAG = "ExoViewManager" + private const val REACT_CLASS = "RCTVideo" + private const val PROP_SRC = "src" + private const val PROP_AD_TAG_URL = "adTagUrl" + private const val PROP_RESIZE_MODE = "resizeMode" + private const val PROP_REPEAT = "repeat" + private const val PROP_SELECTED_AUDIO_TRACK = "selectedAudioTrack" + private const val PROP_SELECTED_AUDIO_TRACK_TYPE = "type" + private const val PROP_SELECTED_AUDIO_TRACK_VALUE = "value" + private const val PROP_SELECTED_TEXT_TRACK = "selectedTextTrack" + private const val PROP_SELECTED_TEXT_TRACK_TYPE = "type" + private const val PROP_SELECTED_TEXT_TRACK_VALUE = "value" + private const val PROP_TEXT_TRACKS = "textTracks" + private const val PROP_PAUSED = "paused" + private const val PROP_MUTED = "muted" + private const val PROP_AUDIO_OUTPUT = "audioOutput" + private const val PROP_VOLUME = "volume" + private const val PROP_BUFFER_CONFIG = "bufferConfig" + private const val PROP_PREVENTS_DISPLAY_SLEEP_DURING_VIDEO_PLAYBACK = + "preventsDisplaySleepDuringVideoPlayback" + 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_CONTENT_START_TIME = "contentStartTime" + private const val PROP_DISABLE_FOCUS = "disableFocus" + private const val PROP_BUFFERING_STRATEGY = "bufferingStrategy" + private const val PROP_DISABLE_DISCONNECT_ERROR = "disableDisconnectError" + private const val PROP_FOCUSABLE = "focusable" + private const val PROP_FULLSCREEN = "fullscreen" + private const val PROP_VIEW_TYPE = "viewType" + private const val PROP_SELECTED_VIDEO_TRACK = "selectedVideoTrack" + private const val PROP_SELECTED_VIDEO_TRACK_TYPE = "type" + private const val PROP_SELECTED_VIDEO_TRACK_VALUE = "value" + private const val PROP_HIDE_SHUTTER_VIEW = "hideShutterView" + private const val PROP_CONTROLS = "controls" + private const val PROP_SUBTITLE_STYLE = "subtitleStyle" + private const val PROP_SHUTTER_COLOR = "shutterColor" + private const val PROP_SHOW_NOTIFICATION_CONTROLS = "showNotificationControls" + private const val PROP_DEBUG = "debug" + private const val PROP_CONTROLS_STYLES = "controlsStyles" + } + + override fun getName(): String = REACT_CLASS + + override fun createViewInstance(themedReactContext: ThemedReactContext): ReactExoplayerView { + ReactNativeVideoManager.getInstance().registerView(this) + return ReactExoplayerView(themedReactContext, config) + } + + override fun onDropViewInstance(view: ReactExoplayerView) { + view.cleanUpResources() + ReactNativeVideoManager.getInstance().unregisterView(this) + } + + override fun getExportedCustomDirectEventTypeConstants(): Map = EventTypes.toMap() + + override fun addEventEmitters(reactContext: ThemedReactContext, view: ReactExoplayerView) { + super.addEventEmitters(reactContext, view) + view.eventEmitter.addEventEmitters(reactContext, view) + } + + @ReactProp(name = PROP_SRC) + fun setSrc(videoView: ReactExoplayerView, src: ReadableMap?) { + val context = videoView.context.applicationContext + val source = Source.parse(src, context) + if (source.uri == null) { + videoView.clearSrc() + } else { + videoView.setSrc(source) + } + } + + @ReactProp(name = PROP_AD_TAG_URL) + fun setAdTagUrl(videoView: ReactExoplayerView, uriString: String?) { + if (TextUtils.isEmpty(uriString)) { + videoView.setAdTagUrl(null) + return + } + val adTagUrl = Uri.parse(uriString) + videoView.setAdTagUrl(adTagUrl) + } + + @ReactProp(name = PROP_RESIZE_MODE) + fun setResizeMode(videoView: ReactExoplayerView, resizeMode: String) { + when (resizeMode) { + "none", "contain" -> videoView.setResizeModeModifier(ResizeMode.RESIZE_MODE_FIT) + + "cover" -> videoView.setResizeModeModifier(ResizeMode.RESIZE_MODE_CENTER_CROP) + + "stretch" -> videoView.setResizeModeModifier(ResizeMode.RESIZE_MODE_FILL) + + else -> { + DebugLog.w(TAG, "Unsupported resize mode: $resizeMode - falling back to fit") + videoView.setResizeModeModifier(ResizeMode.RESIZE_MODE_FIT) + } + } + } + + @ReactProp(name = PROP_REPEAT, defaultBoolean = false) + fun setRepeat(videoView: ReactExoplayerView, repeat: Boolean) { + videoView.setRepeatModifier(repeat) + } + + @ReactProp(name = PROP_PREVENTS_DISPLAY_SLEEP_DURING_VIDEO_PLAYBACK, defaultBoolean = false) + fun setPreventsDisplaySleepDuringVideoPlayback(videoView: ReactExoplayerView, preventsSleep: Boolean) { + videoView.preventsDisplaySleepDuringVideoPlayback = preventsSleep + } + + @ReactProp(name = PROP_SELECTED_VIDEO_TRACK) + fun setSelectedVideoTrack(videoView: ReactExoplayerView, selectedVideoTrack: ReadableMap?) { + var typeString: String? = null + var value: String? = null + if (selectedVideoTrack != null) { + typeString = ReactBridgeUtils.safeGetString(selectedVideoTrack, PROP_SELECTED_VIDEO_TRACK_TYPE) + value = ReactBridgeUtils.safeGetString(selectedVideoTrack, PROP_SELECTED_VIDEO_TRACK_VALUE) + } + videoView.setSelectedVideoTrack(typeString, value) + } + + @ReactProp(name = PROP_SELECTED_AUDIO_TRACK) + fun setSelectedAudioTrack(videoView: ReactExoplayerView, selectedAudioTrack: ReadableMap?) { + var typeString: String? = null + var value: String? = null + if (selectedAudioTrack != null) { + typeString = ReactBridgeUtils.safeGetString(selectedAudioTrack, PROP_SELECTED_AUDIO_TRACK_TYPE) + value = ReactBridgeUtils.safeGetString(selectedAudioTrack, PROP_SELECTED_AUDIO_TRACK_VALUE) + } + videoView.setSelectedAudioTrack(typeString, value) + } + + @ReactProp(name = PROP_SELECTED_TEXT_TRACK) + fun setSelectedTextTrack(videoView: ReactExoplayerView, selectedTextTrack: ReadableMap?) { + var typeString: String? = null + var value: String? = null + if (selectedTextTrack != null) { + typeString = ReactBridgeUtils.safeGetString(selectedTextTrack, PROP_SELECTED_TEXT_TRACK_TYPE) + value = ReactBridgeUtils.safeGetString(selectedTextTrack, PROP_SELECTED_TEXT_TRACK_VALUE) + } + videoView.setSelectedTextTrack(typeString, value) + } + + @ReactProp(name = PROP_TEXT_TRACKS) + fun setTextTracks(videoView: ReactExoplayerView, textTracks: ReadableArray?) { + val sideLoadedTextTracks = SideLoadedTextTrackList.parse(textTracks) + videoView.setTextTracks(sideLoadedTextTracks) + } + + @ReactProp(name = PROP_PAUSED, defaultBoolean = false) + fun setPaused(videoView: ReactExoplayerView, paused: Boolean) { + videoView.setPausedModifier(paused) + } + + @ReactProp(name = PROP_MUTED, defaultBoolean = false) + fun setMuted(videoView: ReactExoplayerView, muted: Boolean) { + videoView.setMutedModifier(muted) + } + + @ReactProp(name = PROP_AUDIO_OUTPUT) + fun setAudioOutput(videoView: ReactExoplayerView, audioOutput: String) { + videoView.setAudioOutput(AudioOutput.get(audioOutput)) + } + + @ReactProp(name = PROP_VOLUME, defaultFloat = 1.0f) + fun setVolume(videoView: ReactExoplayerView, volume: Float) { + videoView.setVolumeModifier(volume) + } + + @ReactProp(name = PROP_PROGRESS_UPDATE_INTERVAL, defaultFloat = 250.0f) + fun setProgressUpdateInterval(videoView: ReactExoplayerView, progressUpdateInterval: Float) { + videoView.setProgressUpdateInterval(progressUpdateInterval) + } + + @ReactProp(name = PROP_REPORT_BANDWIDTH, defaultBoolean = false) + fun setReportBandwidth(videoView: ReactExoplayerView, reportBandwidth: Boolean) { + videoView.setReportBandwidth(reportBandwidth) + } + + @ReactProp(name = PROP_RATE) + fun setRate(videoView: ReactExoplayerView, rate: Float) { + videoView.setRateModifier(rate) + } + + @ReactProp(name = PROP_MAXIMUM_BIT_RATE) + fun setMaxBitRate(videoView: ReactExoplayerView, maxBitRate: Float) { + 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) + } + + @ReactProp(name = PROP_DISABLE_FOCUS, defaultBoolean = false) + fun setDisableFocus(videoView: ReactExoplayerView, disableFocus: Boolean) { + videoView.setDisableFocus(disableFocus) + } + + @ReactProp(name = PROP_FOCUSABLE, defaultBoolean = true) + fun setFocusable(videoView: ReactExoplayerView, focusable: Boolean) { + videoView.setFocusable(focusable) + } + + @ReactProp(name = PROP_CONTENT_START_TIME, defaultInt = -1) + fun setContentStartTime(videoView: ReactExoplayerView, contentStartTime: Int) { + videoView.setContentStartTime(contentStartTime) + } + + @ReactProp(name = PROP_BUFFERING_STRATEGY) + fun setBufferingStrategy(videoView: ReactExoplayerView, bufferingStrategy: String) { + val strategy = BufferingStrategy.parse(bufferingStrategy) + videoView.setBufferingStrategy(strategy) + } + + @ReactProp(name = PROP_DISABLE_DISCONNECT_ERROR, defaultBoolean = false) + fun setDisableDisconnectError(videoView: ReactExoplayerView, disableDisconnectError: Boolean) { + videoView.setDisableDisconnectError(disableDisconnectError) + } + + @ReactProp(name = PROP_FULLSCREEN, defaultBoolean = false) + fun setFullscreen(videoView: ReactExoplayerView, fullscreen: Boolean) { + videoView.setFullscreen(fullscreen) + } + + @ReactProp(name = PROP_VIEW_TYPE, defaultInt = ViewType.VIEW_TYPE_SURFACE) + fun setViewType(videoView: ReactExoplayerView, viewType: Int) { + videoView.setViewType(viewType) + } + + @ReactProp(name = PROP_HIDE_SHUTTER_VIEW, defaultBoolean = false) + fun setHideShutterView(videoView: ReactExoplayerView, hideShutterView: Boolean) { + videoView.setHideShutterView(hideShutterView) + } + + @ReactProp(name = PROP_CONTROLS, defaultBoolean = false) + fun setControls(videoView: ReactExoplayerView, controls: Boolean) { + videoView.setControls(controls) + } + + @ReactProp(name = PROP_SUBTITLE_STYLE) + fun setSubtitleStyle(videoView: ReactExoplayerView, src: ReadableMap?) { + videoView.setSubtitleStyle(SubtitleStyle.parse(src)) + } + + @ReactProp(name = PROP_SHUTTER_COLOR, defaultInt = 0) + fun setShutterColor(videoView: ReactExoplayerView, color: Int) { + videoView.setShutterColor(if (color == 0) Color.BLACK else color) + } + + @ReactProp(name = PROP_BUFFER_CONFIG) + fun setBufferConfig(videoView: ReactExoplayerView, bufferConfig: ReadableMap?) { + val config = BufferConfig.parse(bufferConfig) + videoView.setBufferConfig(config) + } + + @ReactProp(name = PROP_SHOW_NOTIFICATION_CONTROLS) + fun setShowNotificationControls(videoView: ReactExoplayerView, showNotificationControls: Boolean) { + videoView.setShowNotificationControls(showNotificationControls) + } + + @ReactProp(name = PROP_DEBUG, defaultBoolean = false) + fun setDebug(videoView: ReactExoplayerView, debugConfig: ReadableMap?) { + val enableDebug = ReactBridgeUtils.safeGetBool(debugConfig, "enable", false) + val enableThreadDebug = ReactBridgeUtils.safeGetBool(debugConfig, "thread", false) + if (enableDebug) { + DebugLog.setConfig(Log.VERBOSE, enableThreadDebug) + } else { + DebugLog.setConfig(Log.WARN, enableThreadDebug) + } + videoView.setDebug(enableDebug) + } + + @ReactProp(name = PROP_CONTROLS_STYLES) + fun setControlsStyles(videoView: ReactExoplayerView, controlsStyles: ReadableMap?) { + val controlsConfig = ControlsConfig.parse(controlsStyles) + videoView.setControlsStyles(controlsConfig) + } +} diff --git a/android/src/main/java/com/brentvatne/react/ReactNativeVideoManager.kt b/android/src/main/java/com/brentvatne/react/ReactNativeVideoManager.kt index d279264302..73a4f10c48 100644 --- a/android/src/main/java/com/brentvatne/react/ReactNativeVideoManager.kt +++ b/android/src/main/java/com/brentvatne/react/ReactNativeVideoManager.kt @@ -29,21 +29,19 @@ class ReactNativeVideoManager : RNVPlugin { /** * register a new ReactExoplayerViewManager in the managed list */ - fun registerView(newInstance: ReactExoplayerViewManager): () -> Boolean = - { - if (instanceList.size > 2) { - DebugLog.d(TAG, "multiple Video displayed ?") - } - instanceList.add(newInstance) + fun registerView(newInstance: ReactExoplayerViewManager) { + if (instanceList.size > 2) { + DebugLog.d(TAG, "multiple Video displayed ?") } + instanceList.add(newInstance) + } /** * unregister existing ReactExoplayerViewManager in the managed list */ - fun unregisterView(newInstance: ReactExoplayerViewManager): () -> Boolean = - { - instanceList.remove(newInstance) - } + fun unregisterView(newInstance: ReactExoplayerViewManager) { + instanceList.remove(newInstance) + } /** * register a new plugin in the managed list From af0302b1b7cfa570d3b4d37e56fd76539f482360 Mon Sep 17 00:00:00 2001 From: Seyed Mostafa Hasani Date: Mon, 5 Aug 2024 13:28:33 +0330 Subject: [PATCH 05/72] fix(android): return the value as a float for the getCurrentPosition function (#4054) * fix(android): return value for getCurrentPosition function * fix: remove additional casting --- .../main/java/com/brentvatne/exoplayer/ReactExoplayerView.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java index 8ebba8c5bd..6f854efbe8 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java +++ b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java @@ -717,7 +717,7 @@ private void initializePlayer() { public void getCurrentPosition(Promise promise) { if (player != null) { - double currentPosition = player.getCurrentPosition() / 1000; + float currentPosition = player.getCurrentPosition() / 1000.0f; promise.resolve(currentPosition); } else { promise.reject("PLAYER_NOT_AVAILABLE", "Player is not initialized."); From 38aa2b057a496f96e6bf176da984df074ded418f Mon Sep 17 00:00:00 2001 From: Krzysztof Moch Date: Mon, 5 Aug 2024 11:59:49 +0200 Subject: [PATCH 06/72] fix(ios): override source metadata with custom metadata (#4050) * fix(ios): override source metadata with custom metadata * lint code --- examples/basic/ios/Podfile.lock | 10 +++++----- ios/Video/NowPlayingInfoCenterManager.swift | 7 ++++++- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/examples/basic/ios/Podfile.lock b/examples/basic/ios/Podfile.lock index b3681ae41b..7ab84ea57d 100644 --- a/examples/basic/ios/Podfile.lock +++ b/examples/basic/ios/Podfile.lock @@ -994,7 +994,7 @@ PODS: - React-Mapbuffer (0.74.3): - glog - React-debug - - react-native-video (6.3.0): + - react-native-video (6.4.3): - DoubleConversion - glog - hermes-engine @@ -1008,7 +1008,7 @@ PODS: - React-featureflags - React-graphics - React-ImageManager - - react-native-video/Video (= 6.3.0) + - react-native-video/Video (= 6.4.3) - React-NativeModulesApple - React-RCTFabric - React-rendererdebug @@ -1038,7 +1038,7 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - react-native-video/Video (6.3.0): + - react-native-video/Video (6.4.3): - DoubleConversion - glog - hermes-engine @@ -1559,7 +1559,7 @@ SPEC CHECKSUMS: React-jsitracing: 1aa5681c353b41573b03e0e480a5adf5fa1c56d8 React-logger: fa92ba4d3a5d39ac450f59be2a3cec7b099f0304 React-Mapbuffer: 70da5955150a58732e9336a0c7e71cd49e909f25 - react-native-video: c44719c9a5261ad54fdad49e6dd0e98439e0e1b3 + react-native-video: 3d5881ee6643a9a87e0e259b742c2d07aee7b27e react-native-video-plugin-sample: d3a93b7ad777cad7fa2c30473de75a2635ce5feb React-nativeconfig: 84806b820491db30175afbf027e99e8415dc63f0 React-NativeModulesApple: 7b79212f8cf496ab554e0b7b09acbd4aa4690260 @@ -1594,4 +1594,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: a73d485df51877001f2b04a5a4379cfa5a3ba8fa -COCOAPODS: 1.15.2 +COCOAPODS: 1.13.0 diff --git a/ios/Video/NowPlayingInfoCenterManager.swift b/ios/Video/NowPlayingInfoCenterManager.swift index 981f8ab5e6..b5d7b997a5 100644 --- a/ios/Video/NowPlayingInfoCenterManager.swift +++ b/ios/Video/NowPlayingInfoCenterManager.swift @@ -192,7 +192,12 @@ class NowPlayingInfoCenterManager { } // commonMetadata is metadata from asset, externalMetadata is custom metadata set by user - let metadata = currentItem.asset.commonMetadata + currentItem.externalMetadata + // externalMetadata should override commonMetadata to allow override metadata from source + let metadata = { + let common = Dictionary(uniqueKeysWithValues: currentItem.asset.commonMetadata.map { ($0.identifier, $0) }) + let external = Dictionary(uniqueKeysWithValues: currentItem.externalMetadata.map { ($0.identifier, $0) }) + return Array((common.merging(external) { _, new in new }).values) + }() let titleItem = AVMetadataItem.metadataItems(from: metadata, filteredByIdentifier: .commonIdentifierTitle).first?.stringValue ?? "" From 6c03d0a70057369a896f4210f3cdc222983ae221 Mon Sep 17 00:00:00 2001 From: Krzysztof Moch Date: Wed, 7 Aug 2024 14:33:16 +0200 Subject: [PATCH 07/72] infra: update feature request form (#4065) --- .github/ISSUE_TEMPLATE/feature-request.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/feature-request.yml b/.github/ISSUE_TEMPLATE/feature-request.yml index 371d1a9bd9..e92aba9de2 100644 --- a/.github/ISSUE_TEMPLATE/feature-request.yml +++ b/.github/ISSUE_TEMPLATE/feature-request.yml @@ -49,4 +49,11 @@ body: validations: required: false + - type: markdown + attributes: + value: | + ## Support + + If this functionality is important to you and you need it, contact [TheWidlarzGroup](https://thewidlarzgroup.com) - [`hi@thewidlarzgroup.com`](mailto:hi@thewidlarzgroup.com) + \ No newline at end of file From 899bb822a56237915b5c17470cd1c6d7fa8a1728 Mon Sep 17 00:00:00 2001 From: Krzysztof Moch Date: Wed, 7 Aug 2024 14:39:41 +0200 Subject: [PATCH 08/72] fix(android): build warnings (#4058) --- .../src/main/java/com/brentvatne/common/api/ResizeMode.kt | 5 ++--- .../java/com/brentvatne/common/react/VideoEventEmitter.kt | 4 ++-- .../src/main/java/com/brentvatne/react/VideoManagerModule.kt | 1 + 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/android/src/main/java/com/brentvatne/common/api/ResizeMode.kt b/android/src/main/java/com/brentvatne/common/api/ResizeMode.kt index c873a04e0c..18f35d9467 100644 --- a/android/src/main/java/com/brentvatne/common/api/ResizeMode.kt +++ b/android/src/main/java/com/brentvatne/common/api/ResizeMode.kt @@ -1,8 +1,7 @@ package com.brentvatne.common.api import androidx.annotation.IntDef -import java.lang.annotation.Retention -import java.lang.annotation.RetentionPolicy +import kotlin.annotation.Retention internal object ResizeMode { /** @@ -42,7 +41,7 @@ internal object ResizeMode { else -> RESIZE_MODE_FIT } - @Retention(RetentionPolicy.SOURCE) + @Retention(AnnotationRetention.SOURCE) @IntDef( RESIZE_MODE_FIT, RESIZE_MODE_FIXED_WIDTH, diff --git a/android/src/main/java/com/brentvatne/common/react/VideoEventEmitter.kt b/android/src/main/java/com/brentvatne/common/react/VideoEventEmitter.kt index b227bfc1c9..91c3a06df2 100644 --- a/android/src/main/java/com/brentvatne/common/react/VideoEventEmitter.kt +++ b/android/src/main/java/com/brentvatne/common/react/VideoEventEmitter.kt @@ -209,7 +209,7 @@ class VideoEventEmitter { putArray( "metadata", Arguments.createArray().apply { - metadataArrayList.forEachIndexed { i, metadata -> + metadataArrayList.forEachIndexed { _, metadata -> pushMap( Arguments.createMap().apply { putString("identifier", metadata.identifier) @@ -303,7 +303,7 @@ class VideoEventEmitter { private fun videoTracksToArray(videoTracks: java.util.ArrayList?): WritableArray = Arguments.createArray().apply { - videoTracks?.forEachIndexed { i, vTrack -> + videoTracks?.forEachIndexed { _, vTrack -> pushMap( Arguments.createMap().apply { putInt("width", vTrack.width) diff --git a/android/src/main/java/com/brentvatne/react/VideoManagerModule.kt b/android/src/main/java/com/brentvatne/react/VideoManagerModule.kt index 1e624e7c66..f6c0affe78 100644 --- a/android/src/main/java/com/brentvatne/react/VideoManagerModule.kt +++ b/android/src/main/java/com/brentvatne/react/VideoManagerModule.kt @@ -42,6 +42,7 @@ class VideoManagerModule(reactContext: ReactApplicationContext?) : ReactContextB } @ReactMethod + @Suppress("UNUSED_PARAMETER") // codegen compatibility fun seekCmd(reactTag: Int, time: Float, tolerance: Float) { performOnPlayerView(reactTag) { it?.seekTo((time * 1000f).roundToInt().toLong()) From cd41a1b234d363eff098e62ed642a9b4a61bf6c9 Mon Sep 17 00:00:00 2001 From: Seyed Mostafa Hasani Date: Mon, 12 Aug 2024 15:25:10 +0330 Subject: [PATCH 09/72] chore(doc): update document (props & method) (#4072) * chore: update method document * chore: update method document * chore: update method document * fix: PR feedback * chore: update description for controls prop --- docs/pages/component/methods.mdx | 4 ++-- docs/pages/component/props.mdx | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/pages/component/methods.mdx b/docs/pages/component/methods.mdx index 4043dc5ec5..7e66040b25 100644 --- a/docs/pages/component/methods.mdx +++ b/docs/pages/component/methods.mdx @@ -141,9 +141,9 @@ const someCoolFunctions = async () => { videoRef.current.presentFullscreenPlayer(); videoRef.current.dismissFullscreenPlayer(); - // pause or play the video - videoRef.current.play(); + // pause or resume the video videoRef.current.pause(); + videoRef.current.resume(); // save video to your Photos with current filter prop const response = await videoRef.current.save(); diff --git a/docs/pages/component/props.mdx b/docs/pages/component/props.mdx index c1603b8de6..b7e1a57726 100644 --- a/docs/pages/component/props.mdx +++ b/docs/pages/component/props.mdx @@ -135,8 +135,7 @@ Determines whether to show player controls. - **false (default)** - Don't show player controls - **true** - Show player controls -Note on iOS, controls are always shown when in fullscreen mode. -Note on Android, native controls are available by default. +Controls are always shown in fullscreen mode, even when `controls={false}`. If needed, you can also add your controls or use a package like [react-native-video-controls](https://github.com/itsnubix/react-native-video-controls) or [react-native-media-console](https://github.com/criszz77/react-native-media-console), see [Useful Side Project](/projects). ### `controlsStyles` From c6ae17e41d3ceacab617ab16b32c9569c4f38d35 Mon Sep 17 00:00:00 2001 From: Paul <9328123+paul-rinaldi@users.noreply.github.com> Date: Mon, 12 Aug 2024 06:58:40 -0500 Subject: [PATCH 10/72] fix(ios): remove resume logic in notification seek closure (#4068) --- ios/Video/NowPlayingInfoCenterManager.swift | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ios/Video/NowPlayingInfoCenterManager.swift b/ios/Video/NowPlayingInfoCenterManager.swift index b5d7b997a5..63e0e15f55 100644 --- a/ios/Video/NowPlayingInfoCenterManager.swift +++ b/ios/Video/NowPlayingInfoCenterManager.swift @@ -167,9 +167,7 @@ class NowPlayingInfoCenterManager { return .commandFailed } if let event = event as? MPChangePlaybackPositionCommandEvent { - player.seek(to: CMTime(seconds: event.positionTime, preferredTimescale: .max)) { _ in - player.play() - } + player.seek(to: CMTime(seconds: event.positionTime, preferredTimescale: .max)) return .success } return .commandFailed From b7d1cabf720d6934d77d18e934a62ff6d5fc309e Mon Sep 17 00:00:00 2001 From: Seyed Mostafa Hasani Date: Tue, 13 Aug 2024 11:28:31 +0330 Subject: [PATCH 11/72] refactor(android): migrate DefaultDashChunkSource to Kotlin (#4078) * refactor(android): migrate DefaultDashChunkSource to Kotlin --- .../media3/exoplayer/dash/DefaultDashChunkSource.java | 10 ---------- .../media3/exoplayer/dash/DefaultDashChunkSource.kt | 7 +++++++ 2 files changed, 7 insertions(+), 10 deletions(-) delete mode 100644 android/src/main/java/androidx/media3/exoplayer/dash/DefaultDashChunkSource.java create mode 100644 android/src/main/java/androidx/media3/exoplayer/dash/DefaultDashChunkSource.kt diff --git a/android/src/main/java/androidx/media3/exoplayer/dash/DefaultDashChunkSource.java b/android/src/main/java/androidx/media3/exoplayer/dash/DefaultDashChunkSource.java deleted file mode 100644 index 95026f0709..0000000000 --- a/android/src/main/java/androidx/media3/exoplayer/dash/DefaultDashChunkSource.java +++ /dev/null @@ -1,10 +0,0 @@ -package androidx.media3.exoplayer.dash; - -import androidx.media3.datasource.DataSource; - -public class DefaultDashChunkSource { - public static final class Factory { - public Factory(DataSource.Factory mediaDataSourceFactory) { - } - } -} diff --git a/android/src/main/java/androidx/media3/exoplayer/dash/DefaultDashChunkSource.kt b/android/src/main/java/androidx/media3/exoplayer/dash/DefaultDashChunkSource.kt new file mode 100644 index 0000000000..54dce84787 --- /dev/null +++ b/android/src/main/java/androidx/media3/exoplayer/dash/DefaultDashChunkSource.kt @@ -0,0 +1,7 @@ +package androidx.media3.exoplayer.dash + +import androidx.media3.datasource.DataSource + +class DefaultDashChunkSource { + class Factory(private val mediaDataSourceFactory: DataSource.Factory) +} From 736594ed233d9df749244f8268bb78cddd13b22c Mon Sep 17 00:00:00 2001 From: Krzysztof Moch Date: Thu, 15 Aug 2024 16:13:00 +0200 Subject: [PATCH 12/72] chore: release v6.4.4 --- CHANGELOG.md | 12 ++++++++++++ package.json | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5655a328b9..a782e54072 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ +* refactor(android): migrate DefaultDashChunkSource to Kotlin (#4078) (b7d1cabf) +* fix(ios): remove resume logic in notification seek closure (#4068) (c6ae17e4) +* chore(doc): update document (props & method) (#4072) (cd41a1b2) +* fix(android): build warnings (#4058) (899bb822) +* infra: update feature request form (#4065) (6c03d0a7) +* fix(ios): override source metadata with custom metadata (#4050) (38aa2b05) +* fix(android): return the value as a float for the getCurrentPosition function (#4054) (af0302b1) +* refactor(android): migrate ReactExoplayerViewManager to Kotlin (#4011) (74c6dd62) +* fix(android): viewType is ignored when its value is ViewType.TEXTURE (#4031) (22cfd6ce) +* fix(ios): metadata update race (#4033) (08a57a3b) +* fix(ios): updated getLicense call to work with new syntax, and fixed spelling error (#4014) (#4042) (2348c5e4) + ## [6.4.3](https://github.com/TheWidlarzGroup/react-native-video/compare/v6.4.2...v6.4.3) (2024-07-24) diff --git a/package.json b/package.json index 42cecf7601..a6ca11e6db 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-video", - "version": "6.4.3", + "version": "6.4.4", "description": "A