From d3fc4fdbce981e7aa680a4f3d16251b1957798c8 Mon Sep 17 00:00:00 2001 From: Olivier Bouillet Date: Sun, 5 May 2024 21:06:29 +0200 Subject: [PATCH 1/7] perf: ensure we do not provide callback to native if no callback provided from app --- src/Video.tsx | 66 ++++++++++++++++++++++++++++++++++----------------- 1 file changed, 44 insertions(+), 22 deletions(-) diff --git a/src/Video.tsx b/src/Video.tsx index cc01733890..afba2fd298 100644 --- a/src/Video.tsx +++ b/src/Video.tsx @@ -542,38 +542,60 @@ const Video = forwardRef( selectedAudioTrack={_selectedAudioTrack} selectedVideoTrack={_selectedVideoTrack} onGetLicense={useExternalGetLicense ? onGetLicense : undefined} - onVideoLoad={onVideoLoad as (e: NativeSyntheticEvent) => void} - onVideoLoadStart={onVideoLoadStart} - onVideoError={onVideoError} - onVideoProgress={onVideoProgress} - onVideoSeek={onVideoSeek} + onVideoLoad={ + onLoad + ? (onVideoLoad as (e: NativeSyntheticEvent) => void) + : undefined + } + onVideoLoadStart={onLoadStart ? onVideoLoadStart : undefined} + onVideoError={onError ? onVideoError : undefined} + onVideoProgress={onProgress ? onVideoProgress : undefined} + onVideoSeek={onSeek ? onVideoSeek : undefined} onVideoEnd={onEnd} - onVideoBuffer={onVideoBuffer} - onVideoPlaybackStateChanged={onVideoPlaybackStateChanged} - onVideoBandwidthUpdate={_onBandwidthUpdate} - onTimedMetadata={_onTimedMetadata} - onAudioTracks={_onAudioTracks} - onTextTracks={_onTextTracks} - onTextTrackDataChanged={_onTextTrackDataChanged} - onVideoTracks={_onVideoTracks} + onVideoBuffer={onBuffer ? onVideoBuffer : undefined} + onVideoPlaybackStateChanged={ + onPlaybackRateChange ? onVideoPlaybackStateChanged : undefined + } + onVideoBandwidthUpdate={ + onBandwidthUpdate ? _onBandwidthUpdate : undefined + } + onTimedMetadata={onTimedMetadata ? _onTimedMetadata : undefined} + onAudioTracks={onAudioTracks ? _onAudioTracks : undefined} + onTextTracks={onTextTracks ? _onTextTracks : undefined} + onTextTrackDataChanged={ + onTextTrackDataChanged ? _onTextTrackDataChanged : undefined + } + onVideoTracks={onVideoTracks ? _onVideoTracks : undefined} onVideoFullscreenPlayerDidDismiss={onFullscreenPlayerDidDismiss} onVideoFullscreenPlayerDidPresent={onFullscreenPlayerDidPresent} onVideoFullscreenPlayerWillDismiss={onFullscreenPlayerWillDismiss} onVideoFullscreenPlayerWillPresent={onFullscreenPlayerWillPresent} - onVideoExternalPlaybackChange={onVideoExternalPlaybackChange} - onVideoIdle={onVideoIdle} - onAudioFocusChanged={_onAudioFocusChanged} - onReadyForDisplay={_onReadyForDisplay} - onPlaybackRateChange={_onPlaybackRateChange} - onVolumeChange={_onVolumeChange} + onVideoExternalPlaybackChange={ + onExternalPlaybackChange ? onVideoExternalPlaybackChange : undefined + } + onVideoIdle={onIdle ? onVideoIdle : undefined} + onAudioFocusChanged={ + onAudioFocusChanged ? _onAudioFocusChanged : undefined + } + onReadyForDisplay={onReadyForDisplay ? _onReadyForDisplay : undefined} + onPlaybackRateChange={ + onPlaybackRateChange ? _onPlaybackRateChange : undefined + } + onVolumeChange={onVolumeChange ? _onVolumeChange : undefined} onVideoAudioBecomingNoisy={onAudioBecomingNoisy} - onPictureInPictureStatusChanged={_onPictureInPictureStatusChanged} + onPictureInPictureStatusChanged={ + onPictureInPictureStatusChanged + ? _onPictureInPictureStatusChanged + : undefined + } onRestoreUserInterfaceForPictureInPictureStop={ onRestoreUserInterfaceForPictureInPictureStop } - onVideoAspectRatio={_onVideoAspectRatio} + onVideoAspectRatio={onAspectRatio ? _onVideoAspectRatio : undefined} onReceiveAdEvent={ - _onReceiveAdEvent as (e: NativeSyntheticEvent) => void + onReceiveAdEvent + ? (_onReceiveAdEvent as (e: NativeSyntheticEvent) => void) + : undefined } /> {hasPoster && showPoster ? ( From e1da32d6d47fa8461020f11ad3e8bfffe59b7eae Mon Sep 17 00:00:00 2001 From: Olivier Bouillet Date: Mon, 6 May 2024 21:36:40 +0200 Subject: [PATCH 2/7] chore: rework bufferConfig to make it more generic and reduce ReactExoplayerView code size --- .../com/brentvatne/common/api/BufferConfig.kt | 66 +++++++++++++++++ .../exoplayer/ReactExoplayerSimpleCache.kt | 2 +- .../exoplayer/ReactExoplayerView.java | 71 +++++++++---------- .../exoplayer/ReactExoplayerViewManager.java | 35 ++------- .../brentvatne/react/VideoManagerModule.kt | 4 +- 5 files changed, 105 insertions(+), 73 deletions(-) create mode 100644 android/src/main/java/com/brentvatne/common/api/BufferConfig.kt diff --git a/android/src/main/java/com/brentvatne/common/api/BufferConfig.kt b/android/src/main/java/com/brentvatne/common/api/BufferConfig.kt new file mode 100644 index 0000000000..297a1bf65a --- /dev/null +++ b/android/src/main/java/com/brentvatne/common/api/BufferConfig.kt @@ -0,0 +1,66 @@ +package com.brentvatne.common.api + +import com.brentvatne.common.toolbox.ReactBridgeUtils.safeGetDouble +import com.brentvatne.common.toolbox.ReactBridgeUtils.safeGetInt +import com.facebook.react.bridge.ReadableMap + +/** + * Class representing bufferConfig for host. + * Only generic code here, no reference to the player. + * By default, if application don't provide input field, -1 is set instead + */ +class BufferConfig { + var cacheSize = BufferConfigPropUnsetInt + var minBufferMs = BufferConfigPropUnsetInt + var maxBufferMs = BufferConfigPropUnsetInt + var bufferForPlaybackMs = BufferConfigPropUnsetInt + var bufferForPlaybackAfterRebufferMs = BufferConfigPropUnsetInt + var backBufferDurationMs = BufferConfigPropUnsetInt + var maxHeapAllocationPercent = BufferConfigPropUnsetDouble + var minBackBufferMemoryReservePercent = BufferConfigPropUnsetDouble + var minBufferMemoryReservePercent = BufferConfigPropUnsetDouble + + companion object { + val BufferConfigPropUnsetInt = -1 + val BufferConfigPropUnsetDouble = -1.0 + + private val PROP_BUFFER_CONFIG_CACHE_SIZE = "cacheSizeMB" + private val PROP_BUFFER_CONFIG_MIN_BUFFER_MS = "minBufferMs" + private val PROP_BUFFER_CONFIG_MAX_BUFFER_MS = "maxBufferMs" + private val PROP_BUFFER_CONFIG_BUFFER_FOR_PLAYBACK_MS = "bufferForPlaybackMs" + private val PROP_BUFFER_CONFIG_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS = "bufferForPlaybackAfterRebufferMs" + private val PROP_BUFFER_CONFIG_MAX_HEAP_ALLOCATION_PERCENT = "maxHeapAllocationPercent" + private val PROP_BUFFER_CONFIG_MIN_BACK_BUFFER_MEMORY_RESERVE_PERCENT = "minBackBufferMemoryReservePercent" + private val PROP_BUFFER_CONFIG_MIN_BUFFER_MEMORY_RESERVE_PERCENT = "minBufferMemoryReservePercent" + private val PROP_BUFFER_CONFIG_BACK_BUFFER_DURATION_MS = "backBufferDurationMs" + + @JvmStatic + fun parse(src: ReadableMap?): BufferConfig { + val bufferConfig = BufferConfig() + + if (src != null) { + bufferConfig.cacheSize = safeGetInt(src, PROP_BUFFER_CONFIG_CACHE_SIZE, BufferConfigPropUnsetInt) + bufferConfig.minBufferMs = safeGetInt(src, PROP_BUFFER_CONFIG_MIN_BUFFER_MS, BufferConfigPropUnsetInt) + bufferConfig.maxBufferMs = safeGetInt(src, PROP_BUFFER_CONFIG_MAX_BUFFER_MS, BufferConfigPropUnsetInt) + bufferConfig.bufferForPlaybackMs = safeGetInt(src, PROP_BUFFER_CONFIG_BUFFER_FOR_PLAYBACK_MS, BufferConfigPropUnsetInt) + bufferConfig.bufferForPlaybackAfterRebufferMs = + safeGetInt(src, PROP_BUFFER_CONFIG_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS, BufferConfigPropUnsetInt) + bufferConfig.maxHeapAllocationPercent = + safeGetDouble(src, PROP_BUFFER_CONFIG_MAX_HEAP_ALLOCATION_PERCENT, BufferConfigPropUnsetDouble) + bufferConfig.minBackBufferMemoryReservePercent = safeGetDouble( + src, + PROP_BUFFER_CONFIG_MIN_BACK_BUFFER_MEMORY_RESERVE_PERCENT, + BufferConfigPropUnsetDouble + ) + bufferConfig.minBufferMemoryReservePercent = + safeGetDouble( + src, + PROP_BUFFER_CONFIG_MIN_BUFFER_MEMORY_RESERVE_PERCENT, + BufferConfigPropUnsetDouble + ) + bufferConfig.backBufferDurationMs = safeGetInt(src, PROP_BUFFER_CONFIG_BACK_BUFFER_DURATION_MS, BufferConfigPropUnsetInt) + } + return bufferConfig + } + } +} diff --git a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerSimpleCache.kt b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerSimpleCache.kt index cec4b898ff..23d51f7793 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerSimpleCache.kt +++ b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerSimpleCache.kt @@ -15,7 +15,7 @@ object RNVSimpleCache { var cacheDataSourceFactory: DataSource.Factory? = null fun setSimpleCache(context: Context, cacheSize: Int, factory: HttpDataSource.Factory) { - if (cacheDataSourceFactory != null || cacheSize == 0) return + if (cacheDataSourceFactory != null || cacheSize <= 0) return simpleCache = SimpleCache( File(context.cacheDir, "RNVCache"), LeastRecentlyUsedCacheEvictor( diff --git a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java index ba4142454f..1b2623885a 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java +++ b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java @@ -94,6 +94,7 @@ import androidx.media3.extractor.metadata.id3.TextInformationFrame; import androidx.media3.ui.LegacyPlayerControlView; +import com.brentvatne.common.api.BufferConfig; import com.brentvatne.common.api.ResizeMode; import com.brentvatne.common.api.SubtitleStyle; import com.brentvatne.common.api.TimedMetadata; @@ -183,18 +184,11 @@ public class ReactExoplayerView extends FrameLayout implements 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; private boolean isUsingContentResolution = false; private boolean selectTrackWhenReady = false; - private int minBufferMs = DefaultLoadControl.DEFAULT_MIN_BUFFER_MS; - private int maxBufferMs = DefaultLoadControl.DEFAULT_MAX_BUFFER_MS; - private int bufferForPlaybackMs = DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_MS; - private int bufferForPlaybackAfterRebufferMs = DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS; - private int backBufferDurationMs = DefaultLoadControl.DEFAULT_BACK_BUFFER_DURATION_MS; - private double maxHeapAllocationPercent = ReactExoplayerView.DEFAULT_MAX_HEAP_ALLOCATION_PERCENT; - private double minBackBufferMemoryReservePercent = ReactExoplayerView.DEFAULT_MIN_BACK_BUFFER_MEMORY_RESERVE; - private double minBufferMemoryReservePercent = ReactExoplayerView.DEFAULT_MIN_BUFFER_MEMORY_RESERVE; private Handler mainHandler; private Runnable mainRunnable; private DataSource.Factory cacheDataSourceFactory; @@ -495,19 +489,32 @@ private void reLayoutControls() { private class RNVLoadControl extends DefaultLoadControl { private final int availableHeapInBytes; private final Runtime runtime; - public RNVLoadControl(DefaultAllocator allocator, int minBufferMs, int maxBufferMs, int bufferForPlaybackMs, int bufferForPlaybackAfterRebufferMs, int targetBufferBytes, boolean prioritizeTimeOverSizeThresholds, int backBufferDurationMs, boolean retainBackBufferFromKeyframe) { + public RNVLoadControl(DefaultAllocator allocator, BufferConfig config) { super(allocator, - minBufferMs, - maxBufferMs, - bufferForPlaybackMs, - bufferForPlaybackAfterRebufferMs, - targetBufferBytes, - prioritizeTimeOverSizeThresholds, - backBufferDurationMs, - retainBackBufferFromKeyframe); + config.getMinBufferMs() != BufferConfig.Companion.getBufferConfigPropUnsetInt() + ? config.getMinBufferMs() + : DefaultLoadControl.DEFAULT_MIN_BUFFER_MS, + config.getMaxBufferMs() != BufferConfig.Companion.getBufferConfigPropUnsetInt() + ? config.getMaxBufferMs() + : DefaultLoadControl.DEFAULT_MAX_BUFFER_MS, + config.getBufferForPlaybackMs() != BufferConfig.Companion.getBufferConfigPropUnsetInt() + ? config.getBufferForPlaybackMs() + : DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_MS , + config.getBufferForPlaybackAfterRebufferMs() != BufferConfig.Companion.getBufferConfigPropUnsetInt() + ? config.getBufferForPlaybackAfterRebufferMs() + : DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS, + -1, + true, + config.getBackBufferDurationMs() != BufferConfig.Companion.getBufferConfigPropUnsetInt() + ? config.getBackBufferDurationMs() + : DefaultLoadControl.DEFAULT_BACK_BUFFER_DURATION_MS, + DefaultLoadControl.DEFAULT_RETAIN_BACK_BUFFER_FROM_KEYFRAME); runtime = Runtime.getRuntime(); ActivityManager activityManager = (ActivityManager) themedReactContext.getSystemService(ThemedReactContext.ACTIVITY_SERVICE); - availableHeapInBytes = (int) Math.floor(activityManager.getMemoryClass() * maxHeapAllocationPercent * 1024 * 1024); + double maxHeap = config.getMaxHeapAllocationPercent() != BufferConfig.Companion.getBufferConfigPropUnsetDouble() + ? bufferConfig.getMaxHeapAllocationPercent() + : DEFAULT_MAX_HEAP_ALLOCATION_PERCENT; + availableHeapInBytes = (int) Math.floor(activityManager.getMemoryClass() * maxHeap * 1024 * 1024); } @Override @@ -522,6 +529,9 @@ public boolean shouldContinueLoading(long playbackPositionUs, long bufferedDurat } long usedMemory = runtime.totalMemory() - runtime.freeMemory(); long freeMemory = runtime.maxMemory() - usedMemory; + double minBufferMemoryReservePercent = bufferConfig.getMinBufferMemoryReservePercent() != BufferConfig.Companion.getBufferConfigPropUnsetDouble() + ? bufferConfig.getMinBufferMemoryReservePercent() + : ReactExoplayerView.DEFAULT_MIN_BUFFER_MEMORY_RESERVE; long reserveMemory = (long)minBufferMemoryReservePercent * runtime.maxMemory(); long bufferedMs = bufferedDurationUs / (long)1000; if (reserveMemory > freeMemory && bufferedMs > 2000) { @@ -602,14 +612,7 @@ private void initializePlayerCore(ReactExoplayerView self) { DefaultAllocator allocator = new DefaultAllocator(true, C.DEFAULT_BUFFER_SEGMENT_SIZE); RNVLoadControl loadControl = new RNVLoadControl( allocator, - minBufferMs, - maxBufferMs, - bufferForPlaybackMs, - bufferForPlaybackAfterRebufferMs, - -1, - true, - backBufferDurationMs, - DefaultLoadControl.DEFAULT_RETAIN_BACK_BUFFER_FROM_KEYFRAME + bufferConfig ); DefaultRenderersFactory renderersFactory = new DefaultRenderersFactory(getContext()) @@ -2025,26 +2028,18 @@ public void setHideShutterView(boolean hideShutterView) { exoPlayerView.setHideShutterView(hideShutterView); } - public void setBufferConfig(int newMinBufferMs, int newMaxBufferMs, int newBufferForPlaybackMs, int newBufferForPlaybackAfterRebufferMs, double newMaxHeapAllocationPercent, double newMinBackBufferMemoryReservePercent, double newMinBufferMemoryReservePercent, int newBackBufferDurationMs, int cacheSize) { - minBufferMs = newMinBufferMs; - maxBufferMs = newMaxBufferMs; - bufferForPlaybackMs = newBufferForPlaybackMs; - bufferForPlaybackAfterRebufferMs = newBufferForPlaybackAfterRebufferMs; - maxHeapAllocationPercent = newMaxHeapAllocationPercent; - minBackBufferMemoryReservePercent = newMinBackBufferMemoryReservePercent; - minBufferMemoryReservePercent = newMinBufferMemoryReservePercent; - if (cacheSize > 0) { + public void setBufferConfig(BufferConfig config) { + bufferConfig = config; + if (bufferConfig.getCacheSize() > 0) { RNVSimpleCache.INSTANCE.setSimpleCache( this.getContext(), - cacheSize, + bufferConfig.getCacheSize(), buildHttpDataSourceFactory(false) ); cacheDataSourceFactory = RNVSimpleCache.INSTANCE.getCacheDataSourceFactory(); } else { cacheDataSourceFactory = null; } - - backBufferDurationMs = newBackBufferDurationMs; releasePlayer(); initializePlayer(); } diff --git a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java index 1e9ffd606b..1f50faa1bd 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java +++ b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java @@ -11,6 +11,7 @@ import androidx.media3.datasource.RawResourceDataSource; import androidx.media3.exoplayer.DefaultLoadControl; +import com.brentvatne.common.api.BufferConfig; import com.brentvatne.common.api.ResizeMode; import com.brentvatne.common.api.SubtitleStyle; import com.brentvatne.common.react.VideoEventEmitter; @@ -59,15 +60,6 @@ public class ReactExoplayerViewManager extends ViewGroupManager Unit) { UiThreadUtil.runOnUiThread { From 0f33ad1a1035400599476e5c7d13ab0a584dc750 Mon Sep 17 00:00:00 2001 From: Olivier Bouillet Date: Wed, 8 May 2024 15:52:30 +0200 Subject: [PATCH 3/7] chore: improve issue template --- .github/ISSUE_TEMPLATE/bug-report.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml index 575ecdc7a3..6fa8e0b3fa 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.yml +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -7,6 +7,8 @@ body: - type: markdown attributes: value: Thanks for taking the time to fill out this bug report! + Please do not report issue on 5.2.1 version, this version is not maintained anymore. + Only issues on version > V6 will be handled. Please also ensure your issue is reproduced with the last release! - type: textarea id: version From 1066898f0af9ee624367b7ad56fe3d6af5db4ac4 Mon Sep 17 00:00:00 2001 From: Olivier Bouillet Date: Fri, 10 May 2024 16:26:28 +0200 Subject: [PATCH 4/7] fix(android): avoid video view flickering at playback startup --- .../brentvatne/exoplayer/ExoPlayerView.java | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/android/src/main/java/com/brentvatne/exoplayer/ExoPlayerView.java b/android/src/main/java/com/brentvatne/exoplayer/ExoPlayerView.java index db56f65659..9b7904c5c3 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/ExoPlayerView.java +++ b/android/src/main/java/com/brentvatne/exoplayer/ExoPlayerView.java @@ -4,16 +4,13 @@ import androidx.core.content.ContextCompat; import androidx.media3.common.AdViewProvider; import androidx.media3.common.C; -import androidx.media3.common.PlaybackException; -import androidx.media3.common.PlaybackParameters; +import androidx.media3.common.Format; import androidx.media3.common.Player; -import androidx.media3.common.Timeline; import androidx.media3.common.Tracks; import androidx.media3.common.VideoSize; import androidx.media3.common.text.Cue; import androidx.media3.common.util.Assertions; import androidx.media3.exoplayer.ExoPlayer; -import androidx.media3.exoplayer.trackselection.TrackSelectionArray; import androidx.media3.ui.SubtitleView; import android.util.AttributeSet; @@ -27,6 +24,7 @@ import com.brentvatne.common.api.ResizeMode; import com.brentvatne.common.api.SubtitleStyle; +import com.google.common.collect.ImmutableList; import java.util.List; @@ -247,19 +245,22 @@ public void setHideShutterView(boolean hideShutterView) { layout(getLeft(), getTop(), getRight(), getBottom()); }; - private void updateForCurrentTrackSelections() { - if (player == null) { + private void updateForCurrentTrackSelections(Tracks tracks) { + if (tracks == null) { return; } - TrackSelectionArray selections = player.getCurrentTrackSelections(); - for (int i = 0; i < selections.length; i++) { - if (player.getRendererType(i) == C.TRACK_TYPE_VIDEO && selections.get(i) != null) { - // Video enabled so artwork must be hidden. If the shutter is closed, it will be opened in - // onRenderedFirstFrame(). + ImmutableList groups = tracks.getGroups(); + for (Tracks.Group group: groups) { + if (group.getType() == C.TRACK_TYPE_VIDEO && group.length > 0) { + // get the first track of the group to identify aspect ratio + Format format = group.getTrackFormat(0); + + // update aspect ratio ! + layout.setAspectRatio(format.height == 0 ? 1 : (format.width * format.pixelWidthHeightRatio) / format.height); return; } } - // Video disabled so the shutter must be closed. + // no video tracks, in that case refresh shutterView visibility shutterView.setVisibility(this.hideShutterView ? View.INVISIBLE : View.VISIBLE); } @@ -293,8 +294,7 @@ public void onRenderedFirstFrame() { @Override public void onTracksChanged(Tracks tracks) { - updateForCurrentTrackSelections(); + updateForCurrentTrackSelections(tracks); } } - } From 26f177d379fa4f6aeda80879f9389f89a8cd3c0a Mon Sep 17 00:00:00 2001 From: Olivier Bouillet Date: Mon, 3 Jun 2024 08:58:17 +0200 Subject: [PATCH 5/7] fix: add onProgress event before onEnd --- .../exoplayer/ReactExoplayerView.java | 47 +++++++++++-------- ios/Video/RCTVideo.swift | 17 +++---- 2 files changed, 37 insertions(+), 27 deletions(-) diff --git a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java index 7e5dc6bf68..a417489125 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java +++ b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java @@ -258,29 +258,36 @@ public class ReactExoplayerView extends FrameLayout implements private long lastBufferDuration = -1; private long lastDuration = -1; + private void updateProgress() { + if (player != null) { + if (playerControlView != null && isPlayingAd() && controls) { + playerControlView.hide(); + } + long bufferedDuration = player.getBufferedPercentage() * player.getDuration() / 100; + long duration = player.getDuration(); + long pos = player.getCurrentPosition(); + if (pos > duration) { + pos = duration; + } + + if (lastPos != pos + || lastBufferDuration != bufferedDuration + || lastDuration != duration) { + lastPos = pos; + lastBufferDuration = bufferedDuration; + lastDuration = duration; + eventEmitter.progressChanged(pos, bufferedDuration, player.getDuration(), getPositionInFirstPeriodMsForCurrentWindow(pos)); + } + } + } + private final Handler progressHandler = new Handler(Looper.getMainLooper()) { @Override public void handleMessage(Message msg) { if (msg.what == SHOW_PROGRESS) { - if (player != null) { - if (playerControlView != null && isPlayingAd() && controls) { - playerControlView.hide(); - } - long pos = player.getCurrentPosition(); - long bufferedDuration = player.getBufferedPercentage() * player.getDuration() / 100; - long duration = player.getDuration(); - - if (lastPos != pos - || lastBufferDuration != bufferedDuration - || lastDuration != duration) { - lastPos = pos; - lastBufferDuration = bufferedDuration; - lastDuration = duration; - eventEmitter.progressChanged(pos, bufferedDuration, player.getDuration(), getPositionInFirstPeriodMsForCurrentWindow(pos)); - } - msg = obtainMessage(SHOW_PROGRESS); - sendMessageDelayed(msg, Math.round(mProgressUpdateInterval)); - } + updateProgress(); + msg = obtainMessage(SHOW_PROGRESS); + sendMessageDelayed(msg, Math.round(mProgressUpdateInterval)); } } }; @@ -1337,6 +1344,7 @@ public void onEvents(@NonNull Player player, Player.Events events) { break; case Player.STATE_ENDED: text += "ended"; + updateProgress(); eventEmitter.end(); onStopPlayback(); setKeepScreenOn(false); @@ -1614,6 +1622,7 @@ public void onPositionDiscontinuity(@NonNull Player.PositionInfo oldPosition, @N // so we need to explicitly detect it. if (reason == Player.DISCONTINUITY_REASON_AUTO_TRANSITION && player.getRepeatMode() == Player.REPEAT_MODE_ONE) { + updateProgress(); eventEmitter.end(); } } diff --git a/ios/Video/RCTVideo.swift b/ios/Video/RCTVideo.swift index 379b19fd62..af4ff692fe 100644 --- a/ios/Video/RCTVideo.swift +++ b/ios/Video/RCTVideo.swift @@ -312,7 +312,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH // MARK: - Progress - func sendProgressUpdate() { + func sendProgressUpdate(fromEnd: Bool = false) { #if !USE_GOOGLE_IMA // If we dont use Ads and onVideoProgress is not defined we dont need to run this code guard onVideoProgress != nil else { return } @@ -334,11 +334,11 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH } let currentPlaybackTime = _player?.currentItem?.currentDate() let duration = CMTimeGetSeconds(playerDuration) - let currentTimeSecs = CMTimeGetSeconds(currentTime ?? .zero) + var currentTimeSecs = CMTimeGetSeconds(currentTime ?? .zero) - NotificationCenter.default.post(name: NSNotification.Name("RCTVideo_progress"), object: nil, userInfo: [ - "progress": NSNumber(value: currentTimeSecs / duration), - ]) + if (currentTimeSecs > duration || fromEnd) { + currentTimeSecs = duration + } if currentTimeSecs >= 0 { #if USE_GOOGLE_IMA @@ -348,10 +348,10 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH } #endif onVideoProgress?([ - "currentTime": NSNumber(value: Float(currentTimeSecs)), + "currentTime": currentTimeSecs, "playableDuration": RCTVideoUtils.calculatePlayableDuration(_player, withSource: _source), - "atValue": NSNumber(value: currentTime?.value ?? .zero), - "currentPlaybackTime": NSNumber(value: NSNumber(value: Double(currentPlaybackTime?.timeIntervalSince1970 ?? 0 * 1000)).int64Value), + "atValue": currentTime?.value ?? .zero, + "currentPlaybackTime": NSNumber(value: Double(currentPlaybackTime?.timeIntervalSince1970 ?? 0 * 1000)).int64Value, "target": reactTag, "seekableDuration": RCTVideoUtils.calculateSeekableDuration(_player), ]) @@ -1570,6 +1570,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH @objc func handlePlayerItemDidReachEnd(notification: NSNotification!) { + sendProgressUpdate(fromEnd: true) onVideoEnd?(["target": reactTag as Any]) #if USE_GOOGLE_IMA if notification.object as? AVPlayerItem == _player?.currentItem { From e5f2cdccb2dacdcbd18088dd56bc33fc2c064ad7 Mon Sep 17 00:00:00 2001 From: Olivier Bouillet Date: Mon, 3 Jun 2024 09:04:15 +0200 Subject: [PATCH 6/7] chore: fix linter --- android/src/main/java/com/brentvatne/common/api/Source.kt | 4 +--- ios/Video/RCTVideo.swift | 2 +- 2 files changed, 2 insertions(+), 4 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 60c2711bd2..e41761a413 100644 --- a/android/src/main/java/com/brentvatne/common/api/Source.kt +++ b/android/src/main/java/com/brentvatne/common/api/Source.kt @@ -57,9 +57,7 @@ class Source { } /** return true if this and src are equals */ - fun isEquals(source: Source): Boolean { - return this == source - } + fun isEquals(source: Source): Boolean = this == source /** Metadata to display in notification */ class Metadata { diff --git a/ios/Video/RCTVideo.swift b/ios/Video/RCTVideo.swift index af4ff692fe..2d92ff6f3d 100644 --- a/ios/Video/RCTVideo.swift +++ b/ios/Video/RCTVideo.swift @@ -336,7 +336,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH let duration = CMTimeGetSeconds(playerDuration) var currentTimeSecs = CMTimeGetSeconds(currentTime ?? .zero) - if (currentTimeSecs > duration || fromEnd) { + if currentTimeSecs > duration || fromEnd { currentTimeSecs = duration } From a8272f9658e98fc93e969f71cb7ec124156ed1b3 Mon Sep 17 00:00:00 2001 From: Olivier Bouillet Date: Mon, 3 Jun 2024 11:01:43 +0200 Subject: [PATCH 7/7] chore: renaming --- ios/Video/RCTVideo.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ios/Video/RCTVideo.swift b/ios/Video/RCTVideo.swift index 2d92ff6f3d..bf58d0c1b8 100644 --- a/ios/Video/RCTVideo.swift +++ b/ios/Video/RCTVideo.swift @@ -312,7 +312,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH // MARK: - Progress - func sendProgressUpdate(fromEnd: Bool = false) { + func sendProgressUpdate(didEnd: Bool = false) { #if !USE_GOOGLE_IMA // If we dont use Ads and onVideoProgress is not defined we dont need to run this code guard onVideoProgress != nil else { return } @@ -336,7 +336,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH let duration = CMTimeGetSeconds(playerDuration) var currentTimeSecs = CMTimeGetSeconds(currentTime ?? .zero) - if currentTimeSecs > duration || fromEnd { + if currentTimeSecs > duration || didEnd { currentTimeSecs = duration } @@ -1570,7 +1570,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH @objc func handlePlayerItemDidReachEnd(notification: NSNotification!) { - sendProgressUpdate(fromEnd: true) + sendProgressUpdate(didEnd: true) onVideoEnd?(["target": reactTag as Any]) #if USE_GOOGLE_IMA if notification.object as? AVPlayerItem == _player?.currentItem {