From adedc052f0a65dffa7bd3d35e3c7be02f7e66086 Mon Sep 17 00:00:00 2001 From: Krzysztof Moch Date: Mon, 3 Jun 2024 12:07:51 +0200 Subject: [PATCH 1/3] chore: update bug report template (#3873) * chore: update bug report template * update description --- .github/ISSUE_TEMPLATE/bug-report.yml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml index 11af4a629c..ff7a3619d4 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.yml +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -18,6 +18,8 @@ body: - type: dropdown id: platforms + validations: + required: true attributes: label: What platforms are you having the problem on? multiple: true @@ -28,6 +30,25 @@ body: - visionOS - Android TV - Apple tvOS + + - type: input + id: system_version + attributes: + label: System Version + description: What version of the system is using device that you are experiencing the issue? + validations: + required: true + + - type: dropdown + id: device + validations: + required: true + attributes: + label: On what device are you experiencing the issue? + multiple: true + options: + - Real device + - Simulator - type: dropdown id: architecture From 7133c96cac905f06f7a9bccd21eeb2f7a8a27c06 Mon Sep 17 00:00:00 2001 From: Olivier Bouillet <62574056+freeboub@users.noreply.github.com> Date: Mon, 3 Jun 2024 12:13:52 +0200 Subject: [PATCH 2/3] fix: ensure progress is sent before `onEnd` callback (#3872) * fix: add onProgress event before onEnd --- .../java/com/brentvatne/common/api/Source.kt | 4 +- .../exoplayer/ReactExoplayerView.java | 47 +++++++++++-------- ios/Video/RCTVideo.swift | 17 +++---- 3 files changed, 38 insertions(+), 30 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/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..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() { + 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 } @@ -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 || didEnd { + 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(didEnd: true) onVideoEnd?(["target": reactTag as Any]) #if USE_GOOGLE_IMA if notification.object as? AVPlayerItem == _player?.currentItem { From 8693dbc8fae8810ea8def3de91364d9f73a3a729 Mon Sep 17 00:00:00 2001 From: Olivier Bouillet <62574056+freeboub@users.noreply.github.com> Date: Thu, 6 Jun 2024 22:46:52 +0200 Subject: [PATCH 3/3] Fix/ensure view drop stop playback startup (#3875) * fix: ensure player doesn't start when view is unmounted --- .../exoplayer/ReactExoplayerView.java | 17 ++++++++++++++--- ios/Video/NowPlayingInfoCenterManager.swift | 4 ++-- ios/Video/RCTVideo.swift | 10 ++++++++++ 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java index a417489125..6b54a9a4c4 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java +++ b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java @@ -258,6 +258,7 @@ public class ReactExoplayerView extends FrameLayout implements private long lastBufferDuration = -1; private long lastDuration = -1; + private boolean viewHasDropped = false; private void updateProgress() { if (player != null) { if (playerControlView != null && isPlayingAd() && controls) { @@ -375,6 +376,8 @@ protected void onDetachedFromWindow() { public void cleanUpResources() { stopPlayback(); themedReactContext.removeLifecycleEventListener(this); + releasePlayer(); + viewHasDropped = true; } //BandwidthMeter.EventListener implementation @@ -647,6 +650,9 @@ private void initializePlayer() { Activity activity = themedReactContext.getCurrentActivity(); // This ensures all props have been settled, to avoid async racing conditions. mainRunnable = () -> { + if (viewHasDropped) { + return; + } try { if (player == null) { // Initialize core configuration and listeners @@ -658,7 +664,9 @@ private void initializePlayer() { ExecutorService es = Executors.newSingleThreadExecutor(); es.execute(() -> { // DRM initialization must run on a different thread - + if (viewHasDropped) { + return; + } if (activity == null) { DebugLog.e(TAG, "Failed to initialize Player!, null activity"); eventEmitter.error("Failed to initialize Player!", new Exception("Current Activity is null!"), "1001"); @@ -667,12 +675,15 @@ private void initializePlayer() { // Initialize handler to run on the main thread activity.runOnUiThread(() -> { + if (viewHasDropped) { + return; + } try { // Source initialization must run on the main thread initializePlayerSource(); } catch (Exception ex) { self.playerNeedsSource = true; - DebugLog.e(TAG, "Failed to initialize Player!"); + DebugLog.e(TAG, "Failed to initialize Player! 1"); DebugLog.e(TAG, ex.toString()); ex.printStackTrace(); self.eventEmitter.error(ex.toString(), ex, "1001"); @@ -684,7 +695,7 @@ private void initializePlayer() { } } catch (Exception ex) { self.playerNeedsSource = true; - DebugLog.e(TAG, "Failed to initialize Player!"); + DebugLog.e(TAG, "Failed to initialize Player! 2"); DebugLog.e(TAG, ex.toString()); ex.printStackTrace(); eventEmitter.error(ex.toString(), ex, "1001"); diff --git a/ios/Video/NowPlayingInfoCenterManager.swift b/ios/Video/NowPlayingInfoCenterManager.swift index 2307e10b02..6c416c72a1 100644 --- a/ios/Video/NowPlayingInfoCenterManager.swift +++ b/ios/Video/NowPlayingInfoCenterManager.swift @@ -61,11 +61,11 @@ class NowPlayingInfoCenterManager { return } - if let observer = observers[players.hashValue] { + if let observer = observers[player.hashValue] { observer.invalidate() } - observers.removeValue(forKey: players.hashValue) + observers.removeValue(forKey: player.hashValue) players.remove(player) if currentPlayer == player { diff --git a/ios/Video/RCTVideo.swift b/ios/Video/RCTVideo.swift index bf58d0c1b8..724f33c412 100644 --- a/ios/Video/RCTVideo.swift +++ b/ios/Video/RCTVideo.swift @@ -1241,10 +1241,19 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH // MARK: - Lifecycle override func removeFromSuperview() { + self._player?.replaceCurrentItem(with: nil) if let player = _player { player.pause() NowPlayingInfoCenterManager.shared.removePlayer(player: player) } + _playerItem = nil + _source = nil + _chapters = nil + _drm = nil + _textTracks = nil + _selectedTextTrackCriteria = nil + _selectedAudioTrackCriteria = nil + _presentingViewController = nil _player = nil _resouceLoaderDelegate = nil @@ -1252,6 +1261,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH #if USE_GOOGLE_IMA _imaAdsManager.releaseAds() + _imaAdsManager = nil #endif self.removePlayerLayer()