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 0dee8716..b7b1ea69 100644 --- a/android/src/main/java/com/brentvatne/common/react/VideoEventEmitter.kt +++ b/android/src/main/java/com/brentvatne/common/react/VideoEventEmitter.kt @@ -69,7 +69,7 @@ class VideoEventEmitter { lateinit var onVideoError: (errorString: String, exception: Exception, errorCode: String) -> Unit lateinit var onVideoProgress: (currentPosition: Long, bufferedDuration: Long, seekableDuration: Long, currentPlaybackTime: Double) -> Unit lateinit var onVideoBandwidthUpdate: (bitRateEstimate: Long, height: Int, width: Int, trackId: String) -> Unit - lateinit var onVideoPlaybackStateChanged: (isPlaying: Boolean) -> Unit + lateinit var onVideoPlaybackStateChanged: (isPlaying: Boolean, isSeeking: Boolean) -> Unit lateinit var onVideoSeek: (currentPosition: Long, seekTime: Long) -> Unit lateinit var onVideoEnd: () -> Unit lateinit var onVideoFullscreenPlayerWillPresent: () -> Unit @@ -158,9 +158,10 @@ class VideoEventEmitter { putString("trackId", trackId) } } - onVideoPlaybackStateChanged = { isPlaying -> + onVideoPlaybackStateChanged = { isPlaying, isSeeking -> event.dispatch(EventTypes.EVENT_PLAYBACK_STATE_CHANGED) { putBoolean("isPlaying", isPlaying) + putBoolean("isSeeking", isSeeking) } } onVideoSeek = { currentPosition, seekTime -> diff --git a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java index 194727f2..4bb05cdc 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java +++ b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java @@ -221,6 +221,13 @@ public class ReactExoplayerView extends FrameLayout implements private boolean useCache = false; private ControlsConfig controlsConfig = new ControlsConfig(); + /* + * When user is seeking first called is on onPositionDiscontinuity -> DISCONTINUITY_REASON_SEEK + * Then we set if to false when playback is back in onIsPlayingChanged -> true + */ + private boolean isSeeking = false; + private long seekPosition = -1; + // Props from React private Source source = new Source(); private boolean repeat; @@ -1618,6 +1625,11 @@ public class ReactExoplayerView extends FrameLayout implements return; } + if (isPaused && isSeeking && !buffering) { + eventEmitter.onVideoSeek.invoke(player.getCurrentPosition(), seekPosition); + isSeeking = false; + } + isBuffering = buffering; eventEmitter.onVideoBuffer.invoke(buffering); } @@ -1625,7 +1637,8 @@ public class ReactExoplayerView extends FrameLayout implements @Override public void onPositionDiscontinuity(@NonNull Player.PositionInfo oldPosition, @NonNull Player.PositionInfo newPosition, @Player.DiscontinuityReason int reason) { if (reason == Player.DISCONTINUITY_REASON_SEEK) { - eventEmitter.onVideoSeek.invoke(player.getCurrentPosition(), newPosition.positionMs % 1000); // time are in seconds /°\ + isSeeking = true; + seekPosition = newPosition.positionMs; if (isUsingContentResolution) { // We need to update the selected track to make sure that it still matches user selection if track list has changed in this period setSelectedTrack(C.TRACK_TYPE_VIDEO, videoTrackType, videoTrackValue); @@ -1676,7 +1689,15 @@ public class ReactExoplayerView extends FrameLayout implements @Override public void onIsPlayingChanged(boolean isPlaying) { - eventEmitter.onVideoPlaybackStateChanged.invoke(isPlaying); + if (isPlaying && isSeeking) { + eventEmitter.onVideoSeek.invoke(player.getCurrentPosition(), seekPosition); + } + + eventEmitter.onVideoPlaybackStateChanged.invoke(isPlaying, isSeeking); + + if (isPlaying) { + isSeeking = false; + } } @Override diff --git a/docs/pages/component/events.mdx b/docs/pages/component/events.mdx index 146c0d65..8326c5e6 100644 --- a/docs/pages/component/events.mdx +++ b/docs/pages/component/events.mdx @@ -296,15 +296,17 @@ Callback function that is called when the playback state changes. Payload: -| Property | Type | Description | -| --------- | ----------- | ------------------------------------------------- | -| isPlaying | boolean | Boolean indicating if the media is playing or not | +| Property | Type | Description | +| --------- | ----------- | -------------------------------------------------- | +| isPlaying | boolean | Boolean indicating if the media is playing or not | +| isSeeking | boolean | Boolean indicating if the player is seeking or not | Example: ```javascript { isPlaying: true, + isSeeking: false } ``` diff --git a/ios/Video/RCTVideo.swift b/ios/Video/RCTVideo.swift index a8b01755..6767aed2 100644 --- a/ios/Video/RCTVideo.swift +++ b/ios/Video/RCTVideo.swift @@ -758,8 +758,10 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH let seekTime: NSNumber! = info["time"] as! NSNumber let seekTolerance: NSNumber! = info["tolerance"] as! NSNumber let item: AVPlayerItem? = _player?.currentItem + + _pendingSeek = true + guard item != nil, let player = _player, let item, item.status == AVPlayerItem.Status.readyToPlay else { - _pendingSeek = true _pendingSeekTime = seekTime.floatValue return } @@ -1486,7 +1488,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH guard _isPlaying != isPlaying else { return } _isPlaying = isPlaying - onVideoPlaybackStateChanged?(["isPlaying": isPlaying, "target": reactTag as Any]) + onVideoPlaybackStateChanged?(["isPlaying": isPlaying, "isSeeking": self._pendingSeek == true, "target": reactTag as Any]) } func handlePlaybackRateChange(player: AVPlayer, change: NSKeyValueObservedChange) { diff --git a/src/specs/VideoNativeComponent.ts b/src/specs/VideoNativeComponent.ts index b55dabd5..bb6a8056 100644 --- a/src/specs/VideoNativeComponent.ts +++ b/src/specs/VideoNativeComponent.ts @@ -189,6 +189,7 @@ export type OnSeekData = Readonly<{ export type OnPlaybackStateChangedData = Readonly<{ isPlaying: boolean; + isSeeking: boolean; }>; export type OnTimedMetadataData = Readonly<{