From 65c0a652b5c805bdeab69ef4b0805db84ece32e8 Mon Sep 17 00:00:00 2001 From: William Van Haevre Date: Tue, 20 Jan 2026 14:14:18 +0100 Subject: [PATCH 01/10] Make notifyNativePlayerReady public on the iOS bridge --- ios/THEOplayerRCTView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios/THEOplayerRCTView.swift b/ios/THEOplayerRCTView.swift index 5705ee634..fc11fa4f7 100644 --- a/ios/THEOplayerRCTView.swift +++ b/ios/THEOplayerRCTView.swift @@ -210,7 +210,7 @@ public class THEOplayerRCTView: UIView { } } - private func notifyNativePlayerReady() { + public func notifyNativePlayerReady() { DispatchQueue.main.async { let versionString = THEOplayer.version if let forwardedNativeReady = self.onNativePlayerReady { From 8cd05943441bf1d4cd6731fdbadb36cd6ddfb3e2 Mon Sep 17 00:00:00 2001 From: William Van Haevre Date: Tue, 20 Jan 2026 14:15:38 +0100 Subject: [PATCH 02/10] Extract basic sourceDescription aggregation to separate method --- ...EOplayerRCTSourceDescriptionAggregator.swift | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/ios/THEOplayerRCTSourceDescriptionAggregator.swift b/ios/THEOplayerRCTSourceDescriptionAggregator.swift index 17dd2dd7a..0039e63ff 100644 --- a/ios/THEOplayerRCTSourceDescriptionAggregator.swift +++ b/ios/THEOplayerRCTSourceDescriptionAggregator.swift @@ -6,22 +6,29 @@ import UIKit #if os(iOS) class THEOplayerRCTSourceDescriptionAggregator { - class func aggregateCacheTaskSourceDescription(sourceDescription: SourceDescription, cachingTaskId: String) -> [String:Any]? { + class func aggregateSourceDescription(sourceDescription: SourceDescription) -> [String:Any]? { do { let jsonEncoder = JSONEncoder() let data = try jsonEncoder.encode(sourceDescription) if let result = try? JSONSerialization.jsonObject(with: data, options: []) as? [String:Any] { - let srcDescription = THEOplayerRCTSourceDescriptionAggregator.sanitiseSourceDescriptionMetadata(input: result) - let extendedSrcDescription = THEOplayerRCTSourceDescriptionAggregator.addCachingTaskIdToMetadata(input: srcDescription, cachingTaskId: cachingTaskId) - return extendedSrcDescription + return THEOplayerRCTSourceDescriptionAggregator.sanitiseSourceDescriptionMetadata(input: result) } } catch { - if DEBUG { PrintUtils.printLog(logText: "[NATIVE] Could not aggregate sourceDescription for caching task: \(error.localizedDescription)")} + if DEBUG { PrintUtils.printLog(logText: "[NATIVE] Could not aggregate sourceDescription: \(error.localizedDescription)")} return nil } return nil } + class func aggregateCacheTaskSourceDescription(sourceDescription: SourceDescription, cachingTaskId: String) -> [String:Any]? { + if let result = THEOplayerRCTSourceDescriptionAggregator.aggregateSourceDescription(sourceDescription: sourceDescription) { + let srcDescription = THEOplayerRCTSourceDescriptionAggregator.sanitiseSourceDescriptionMetadata(input: result) + let extendedSrcDescription = THEOplayerRCTSourceDescriptionAggregator.addCachingTaskIdToMetadata(input: srcDescription, cachingTaskId: cachingTaskId) + return extendedSrcDescription + } + return nil + } + private class func sanitiseSourceDescriptionMetadata(input: [String:Any]) -> [String:Any] { var output: [String:Any] = input if let metadata = output[SD_PROP_METADATA] as? [String:Any], From 4c7c8d46e05f7d6e85cd634e4aa920b4e734f02a Mon Sep 17 00:00:00 2001 From: William Van Haevre Date: Tue, 20 Jan 2026 15:12:16 +0100 Subject: [PATCH 03/10] Add iOS PlayerStateBuilder --- ios/THEOplayerRCTPlayerStateBuilder.swift | 100 ++++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 ios/THEOplayerRCTPlayerStateBuilder.swift diff --git a/ios/THEOplayerRCTPlayerStateBuilder.swift b/ios/THEOplayerRCTPlayerStateBuilder.swift new file mode 100644 index 000000000..daf618f11 --- /dev/null +++ b/ios/THEOplayerRCTPlayerStateBuilder.swift @@ -0,0 +1,100 @@ +import Foundation +import THEOplayerSDK + +let STATE_SOURCE = "source" +let STATE_CURRENT_TIME = "currentTime" +let STATE_CURRENT_PROGRAM_DATE_TIME = "currentProgramDateTime" +let STATE_PAUSED = "paused" +let STATE_PLAYBACK_RATE = "playbackRate" +let STATE_DURATION = "duration" +let STATE_VOLUME = "volume" +let STATE_MUTED = "muted" +let STATE_SEEKABLE = "seekable" +let STATE_BUFFERED = "buffered" +let STATE_START = "start" +let STATE_END = "end" + +class THEOplayerRCTPlayerStateBuilder { + private var playerState: [String: Any] = [:] + + func build() -> [String: Any] { + return playerState + } + + func source(_ sourceDescription: SourceDescription?) -> Self { + if let sourceDesc = sourceDescription { + playerState[STATE_SOURCE] = THEOplayerRCTSourceDescriptionAggregator.aggregateSourceDescription(sourceDescription: sourceDesc) + } + return self + } + + func currentTime(_ timeInSec: Double) -> Self { + playerState[STATE_CURRENT_TIME] = timeInSec * 1000 // sec -> msec + return self + } + + func currentProgramDateTime(_ programDataTime: Date?) -> Self { + if let date = programDataTime { + playerState[STATE_CURRENT_PROGRAM_DATE_TIME] = date.timeIntervalSince1970 * 1000 // sec -> msec + } + return self + } + + func paused(_ paused: Bool) -> Self { + playerState[STATE_PAUSED] = paused + return self + } + + func playbackRate(_ rate: Double) -> Self { + playerState[STATE_PLAYBACK_RATE] = rate + return self + } + + func duration(_ durationInSec: Double?) -> Self { + if let durationInSec = durationInSec { + playerState[PROP_DURATION] = THEOplayerRCTTypeUtils.encodeInfNan(durationInSec * 1000) // sec -> msec + } + return self + } + + func volume(_ volume: Float) -> Self { + playerState[STATE_VOLUME] = Double(volume) + return self + } + + func muted(_ muted: Bool) -> Self { + playerState[STATE_MUTED] = muted + return self + } + + func seekable(_ seekableRanges: [THEOplayerSDK.TimeRange]?) -> Self { + playerState[STATE_SEEKABLE] = fromTimeRanges(seekableRanges) + return self + } + + func buffered(_ bufferedRanges: [THEOplayerSDK.TimeRange]?) -> Self { + playerState[STATE_BUFFERED] = fromTimeRanges(bufferedRanges) + return self + } + + func trackInfo(_ trackInfo: [String: Any]) -> Self { + playerState.merge(trackInfo) { (current, _) in current } + return self + } + + private func fromTimeRanges(_ ranges: [THEOplayerSDK.TimeRange]?) -> [[String: Double]] { + guard let inRanges = ranges else { return [] } + + var outRanges: [[String:Double]] = [] + inRanges.forEach({ timeRange in + outRanges.append( + [ + STATE_START: timeRange.start * 1000, // sec -> msec + STATE_END: timeRange.end * 1000 // sec -> msec + ] + ) + }) + return outRanges + } +} + From 58517f643dd4a80d8576422c059e091f2e50770d Mon Sep 17 00:00:00 2001 From: William Van Haevre Date: Tue, 20 Jan 2026 15:12:52 +0100 Subject: [PATCH 04/10] Rename method --- ios/THEOplayerRCTMainEventHandler.swift | 2 +- ios/THEOplayerRCTTrackMetadataAggregator.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ios/THEOplayerRCTMainEventHandler.swift b/ios/THEOplayerRCTMainEventHandler.swift index 36a9aa75c..fa3012fe3 100644 --- a/ios/THEOplayerRCTMainEventHandler.swift +++ b/ios/THEOplayerRCTMainEventHandler.swift @@ -276,7 +276,7 @@ public class THEOplayerRCTMainEventHandler { if let wplayer = player, let welf = self, let forwardedLoadedMetadataEvent = self?.onNativeLoadedMetadata { - let metadata = THEOplayerRCTTrackMetadataAggregator.aggregateTrackMetadata(player: wplayer, metadataTracksInfo: welf.loadedMetadataAndChapterTracksInfo) + let metadata = THEOplayerRCTTrackMetadataAggregator.aggregateTrackInfo(player: wplayer, metadataTracksInfo: welf.loadedMetadataAndChapterTracksInfo) forwardedLoadedMetadataEvent(metadata) } } diff --git a/ios/THEOplayerRCTTrackMetadataAggregator.swift b/ios/THEOplayerRCTTrackMetadataAggregator.swift index 2b1aff33e..8e0700e93 100644 --- a/ios/THEOplayerRCTTrackMetadataAggregator.swift +++ b/ios/THEOplayerRCTTrackMetadataAggregator.swift @@ -38,7 +38,7 @@ let PROP_END_ON_NEXT: String = "endOnNext" class THEOplayerRCTTrackMetadataAggregator { - class func aggregateTrackMetadata(player: THEOplayer, metadataTracksInfo: [[String:Any]]) -> [String:Any] { + class func aggregateTrackInfo(player: THEOplayer, metadataTracksInfo: [[String:Any]]) -> [String:Any] { let textTracks: TextTrackList = player.textTracks let audioTracks: AudioTrackList = player.audioTracks let videoTracks: VideoTrackList = player.videoTracks From 9aa940c284c7426207230148529e6abb616aa802 Mon Sep 17 00:00:00 2001 From: William Van Haevre Date: Tue, 20 Jan 2026 15:13:06 +0100 Subject: [PATCH 05/10] Make loadedMetadataAndChapterTracksInfo readable --- ios/THEOplayerRCTMainEventHandler.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios/THEOplayerRCTMainEventHandler.swift b/ios/THEOplayerRCTMainEventHandler.swift index fa3012fe3..f5e15ae80 100644 --- a/ios/THEOplayerRCTMainEventHandler.swift +++ b/ios/THEOplayerRCTMainEventHandler.swift @@ -7,7 +7,7 @@ public class THEOplayerRCTMainEventHandler { // MARK: Members private weak var player: THEOplayer? private weak var presentationModeContext: THEOplayerRCTPresentationModeContext? - private var loadedMetadataAndChapterTracksInfo: [[String:Any]] = [] + private(set) var loadedMetadataAndChapterTracksInfo: [[String:Any]] = [] // MARK: Events var onNativePlay: RCTDirectEventBlock? From fa84491808354c6dca5c7d636502987889522882 Mon Sep 17 00:00:00 2001 From: William Van Haevre Date: Tue, 20 Jan 2026 15:13:44 +0100 Subject: [PATCH 06/10] Pass initial player state as part of playerReady notification --- ios/THEOplayerRCTView.swift | 39 +++++++++++++++++++++++++++++++------ 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/ios/THEOplayerRCTView.swift b/ios/THEOplayerRCTView.swift index fc11fa4f7..af19f263d 100644 --- a/ios/THEOplayerRCTView.swift +++ b/ios/THEOplayerRCTView.swift @@ -214,12 +214,39 @@ public class THEOplayerRCTView: UIView { DispatchQueue.main.async { let versionString = THEOplayer.version if let forwardedNativeReady = self.onNativePlayerReady { - forwardedNativeReady([ - "version": [ - "version" : versionString, - "playerSuiteVersion": versionString - ], - ]) + var payload: [String: Any] = [:] + + // pass initial player state + if let player = self.player { + // collect stored track metadata + let trackInfo = THEOplayerRCTTrackMetadataAggregator.aggregateTrackInfo( + player: player, + metadataTracksInfo: self.mainEventHandler.loadedMetadataAndChapterTracksInfo + ) + + // build state + payload["state"] = THEOplayerRCTPlayerStateBuilder() + .source(player.source) + .currentTime(player.currentTime) + .currentProgramDateTime(player.currentProgramDateTime) + .paused(player.paused) + .playbackRate(player.playbackRate) + .duration(player.duration) + .volume(player.volume) + .muted(player.muted) + .seekable(player.seekable) + .buffered(player.buffered) + .trackInfo(trackInfo) + .build() + } + + // pass version onfo + payload["version"] = [ + "version": versionString, + "playerSuiteVersion": versionString + ] + + forwardedNativeReady(payload) } } } From 97fa0cea20175f3e8992d7e08ea73443acacfa82 Mon Sep 17 00:00:00 2001 From: William Van Haevre Date: Tue, 20 Jan 2026 15:14:52 +0100 Subject: [PATCH 07/10] Add dSym setup for example app debug builds --- example/ios/ReactNativeTHEOplayer.xcodeproj/project.pbxproj | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/example/ios/ReactNativeTHEOplayer.xcodeproj/project.pbxproj b/example/ios/ReactNativeTHEOplayer.xcodeproj/project.pbxproj index 003d764dc..b91b08eb1 100644 --- a/example/ios/ReactNativeTHEOplayer.xcodeproj/project.pbxproj +++ b/example/ios/ReactNativeTHEOplayer.xcodeproj/project.pbxproj @@ -572,7 +572,7 @@ CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; - DEBUG_INFORMATION_FORMAT = dwarf; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_BITCODE = NO; ENABLE_TESTABILITY = YES; GCC_NO_COMMON_BLOCKS = YES; @@ -699,6 +699,8 @@ ); MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; + OTHER_CFLAGS = "$(inherited)"; + OTHER_CPLUSPLUSFLAGS = "$(inherited)"; OTHER_LDFLAGS = "$(inherited)"; REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; @@ -766,6 +768,8 @@ "\"$(inherited)\"", ); MTL_ENABLE_DEBUG_INFO = NO; + OTHER_CFLAGS = "$(inherited)"; + OTHER_CPLUSPLUSFLAGS = "$(inherited)"; OTHER_LDFLAGS = "$(inherited)"; REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; From d7cf4c29014fa0ecc52f4c71cbf5f646879c8c4c Mon Sep 17 00:00:00 2001 From: William Van Haevre Date: Tue, 20 Jan 2026 15:27:43 +0100 Subject: [PATCH 08/10] Add changelog entry --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index dc969d91d..e7405a3fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] +### Added + +- Added support on iOS to push the initial state of the player from the iOS bridge to the React native adapter. + ### Fixed - Fixed an issue on iOS where the integrationParameters from the contentProtection section of the source were not processed correctly, resulting in failures for DRM connectors that depend on them. From 819eba0afd8639e7ba12c98d6faf66bb24369876 Mon Sep 17 00:00:00 2001 From: William Van Haevre Date: Tue, 20 Jan 2026 16:37:08 +0100 Subject: [PATCH 09/10] Drop iOS unnecessary check for THEOplayerRCTSourceDescriptionAggregator --- ios/THEOplayerRCTSourceDescriptionAggregator.swift | 2 -- 1 file changed, 2 deletions(-) diff --git a/ios/THEOplayerRCTSourceDescriptionAggregator.swift b/ios/THEOplayerRCTSourceDescriptionAggregator.swift index 0039e63ff..fc9047fb6 100644 --- a/ios/THEOplayerRCTSourceDescriptionAggregator.swift +++ b/ios/THEOplayerRCTSourceDescriptionAggregator.swift @@ -4,7 +4,6 @@ import Foundation import THEOplayerSDK import UIKit -#if os(iOS) class THEOplayerRCTSourceDescriptionAggregator { class func aggregateSourceDescription(sourceDescription: SourceDescription) -> [String:Any]? { do { @@ -60,4 +59,3 @@ class THEOplayerRCTSourceDescriptionAggregator { return output } } -#endif From 685a22bce1d03e1c665159c96d23eb76da11d120 Mon Sep 17 00:00:00 2001 From: William Van Haevre Date: Tue, 20 Jan 2026 18:03:57 +0100 Subject: [PATCH 10/10] swift code improvement --- ios/THEOplayerRCTMainEventHandler.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ios/THEOplayerRCTMainEventHandler.swift b/ios/THEOplayerRCTMainEventHandler.swift index f5e15ae80..72042d212 100644 --- a/ios/THEOplayerRCTMainEventHandler.swift +++ b/ios/THEOplayerRCTMainEventHandler.swift @@ -274,9 +274,9 @@ public class THEOplayerRCTMainEventHandler { self.loadedMetadataListener = player.addEventListener(type: PlayerEventTypes.LOADED_META_DATA) { [weak self, weak player] event in if DEBUG_THEOPLAYER_EVENTS { PrintUtils.printLog(logText: "[NATIVE] Received LOADED_META_DATA event from THEOplayer") } if let wplayer = player, - let welf = self, - let forwardedLoadedMetadataEvent = self?.onNativeLoadedMetadata { - let metadata = THEOplayerRCTTrackMetadataAggregator.aggregateTrackInfo(player: wplayer, metadataTracksInfo: welf.loadedMetadataAndChapterTracksInfo) + let self, + let forwardedLoadedMetadataEvent = self.onNativeLoadedMetadata { + let metadata = THEOplayerRCTTrackMetadataAggregator.aggregateTrackInfo(player: wplayer, metadataTracksInfo: self.loadedMetadataAndChapterTracksInfo) forwardedLoadedMetadataEvent(metadata) } }