From 9e65e0b2d739025f32796fe049b3312f7bdb9336 Mon Sep 17 00:00:00 2001 From: Ludevv Date: Thu, 12 Feb 2026 14:56:35 +0100 Subject: [PATCH] Fix iOS app hangs blocking main thread --- ios/Video/NowPlayingInfoCenterManager.swift | 69 ++++++++++++--------- ios/Video/RCTVideo.swift | 2 +- 2 files changed, 41 insertions(+), 30 deletions(-) diff --git a/ios/Video/NowPlayingInfoCenterManager.swift b/ios/Video/NowPlayingInfoCenterManager.swift index 34f9918d18..f0cb7d0d14 100644 --- a/ios/Video/NowPlayingInfoCenterManager.swift +++ b/ios/Video/NowPlayingInfoCenterManager.swift @@ -21,6 +21,7 @@ class NowPlayingInfoCenterManager { private var togglePlayPauseTarget: Any? private let remoteCommandCenter = MPRemoteCommandCenter.shared() + private let metadataQueue = DispatchQueue(label: "com.reactnativevideo.nowplayinginfo") private var receivingRemoveControlEvents = false { didSet { @@ -206,35 +207,45 @@ class NowPlayingInfoCenterManager { return } - // commonMetadata is metadata from asset, externalMetadata is custom metadata set by user - // 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 ?? "" - - let artistItem = AVMetadataItem.metadataItems(from: metadata, filteredByIdentifier: .commonIdentifierArtist).first?.stringValue ?? "" - - // I have some issue with this - setting artworkItem when it not set dont return nil but also is crashing application - // this is very hacky workaround for it - let imgData = AVMetadataItem.metadataItems(from: metadata, filteredByIdentifier: .commonIdentifierArtwork).first?.dataValue - let image = imgData.flatMap { UIImage(data: $0) } ?? UIImage() - let artworkItem = MPMediaItemArtwork(boundsSize: image.size) { _ in image } - - let newNowPlayingInfo: [String: Any] = [ - MPMediaItemPropertyTitle: titleItem, - MPMediaItemPropertyArtist: artistItem, - MPMediaItemPropertyArtwork: artworkItem, - MPMediaItemPropertyPlaybackDuration: currentItem.duration.seconds, - MPNowPlayingInfoPropertyElapsedPlaybackTime: currentItem.currentTime().seconds.rounded(), - MPNowPlayingInfoPropertyPlaybackRate: player.rate, - ] - let currentNowPlayingInfo = MPNowPlayingInfoCenter.default().nowPlayingInfo ?? [:] - - MPNowPlayingInfoCenter.default().nowPlayingInfo = currentNowPlayingInfo.merging(newNowPlayingInfo) { _, new in new } + // Capture lightweight properties synchronously before dispatching + let asset = currentItem.asset + let externalMetadata = currentItem.externalMetadata + let duration = currentItem.duration.seconds + let currentTime = currentItem.currentTime().seconds.rounded() + let rate = player.rate + + // Dispatch metadata loading to background queue to avoid blocking the main thread. + metadataQueue.async { + // commonMetadata is metadata from asset, externalMetadata is custom metadata set by user + // externalMetadata should override commonMetadata to allow override metadata from source + let metadata = { + let common = Dictionary(uniqueKeysWithValues: asset.commonMetadata.map { ($0.identifier, $0) }) + let external = Dictionary(uniqueKeysWithValues: externalMetadata.map { ($0.identifier, $0) }) + return Array((common.merging(external) { _, new in new }).values) + }() + + let titleItem = AVMetadataItem.metadataItems(from: metadata, filteredByIdentifier: .commonIdentifierTitle).first?.stringValue ?? "" + + let artistItem = AVMetadataItem.metadataItems(from: metadata, filteredByIdentifier: .commonIdentifierArtist).first?.stringValue ?? "" + + // I have some issue with this - setting artworkItem when it not set dont return nil but also is crashing application + // this is very hacky workaround for it + let imgData = AVMetadataItem.metadataItems(from: metadata, filteredByIdentifier: .commonIdentifierArtwork).first?.dataValue + let image = imgData.flatMap { UIImage(data: $0) } ?? UIImage() + let artworkItem = MPMediaItemArtwork(boundsSize: image.size) { _ in image } + + let newNowPlayingInfo: [String: Any] = [ + MPMediaItemPropertyTitle: titleItem, + MPMediaItemPropertyArtist: artistItem, + MPMediaItemPropertyArtwork: artworkItem, + MPMediaItemPropertyPlaybackDuration: duration, + MPNowPlayingInfoPropertyElapsedPlaybackTime: currentTime, + MPNowPlayingInfoPropertyPlaybackRate: rate, + ] + let currentNowPlayingInfo = MPNowPlayingInfoCenter.default().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 2858935a24..26bd484760 100644 --- a/ios/Video/RCTVideo.swift +++ b/ios/Video/RCTVideo.swift @@ -424,7 +424,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH if currentTime != nil && _source?.cropStart != nil { currentTime = CMTimeSubtract(currentTime!, CMTimeMake(value: _source?.cropStart ?? 0, timescale: 1000)) } - let currentPlaybackTime = _player?.currentItem?.currentDate() + let currentPlaybackTime: Date? = nil // currentDate() is only relevant for HLS live streams. For our vidoes it always returns nil, so currentPlaybackTime was always 0. let duration = CMTimeGetSeconds(playerDuration) var currentTimeSecs = CMTimeGetSeconds(currentTime ?? .zero)