From 4c67290635fdbadcbce48534ecf7aeec4312ad66 Mon Sep 17 00:00:00 2001 From: Filipp Mikheev Date: Wed, 16 Jul 2025 01:21:10 -0500 Subject: [PATCH 1/2] iOS: add state to payload for the event onPlayerReady --- ios/THEOplayerRCTPayloadBuilder.swift | 109 ++++++++++++++++++++++++++ ios/THEOplayerRCTView.swift | 35 +++++++-- 2 files changed, 137 insertions(+), 7 deletions(-) create mode 100644 ios/THEOplayerRCTPayloadBuilder.swift diff --git a/ios/THEOplayerRCTPayloadBuilder.swift b/ios/THEOplayerRCTPayloadBuilder.swift new file mode 100644 index 000000000..e3eb5804f --- /dev/null +++ b/ios/THEOplayerRCTPayloadBuilder.swift @@ -0,0 +1,109 @@ +import Foundation +import THEOplayerSDK + +private let PROP_SOURCE = "source" +private let PROP_CURRENT_TIME = "currentTime" +private let PROP_CURRENT_PROGRAM_DATE_TIME = "currentProgramDateTime" +private let PROP_PAUSED = "paused" +private let PROP_PLAYBACK_RATE = "playbackRate" +private let PROP_VOLUME = "volume" +private let PROP_MUTED = "muted" +private let PROP_SEEKABLE = "seekable" +private let PROP_BUFFERED = "buffered" +private let PROP_START = "start" +private let PROP_END = "end" + +/** + * This class is used to shape the `state` object for the event `onPlayerReady` + */ +class THEOplayerRCTPayloadBuilder { + private var payload: [String: Any] = [:] + + func source(_ source: Any?) -> Self { + if source != nil { + let sourcePayload = NSMutableDictionary() + // Fill in with source fields? + payload[PROP_SOURCE] = sourcePayload + } + return self + } + + func currentTime(_ timeInSec: Double) -> Self { + payload[PROP_CURRENT_TIME] = timeInSec * 1e3 + return self + } + + func currentProgramDateTime(_ date: Date?) -> Self { + if let date = date { + payload[PROP_CURRENT_PROGRAM_DATE_TIME] = date.timeIntervalSince1970 * 1e3 + } + return self + } + + func paused(_ value: Bool) -> Self { + payload[PROP_PAUSED] = value + return self + } + + func playbackRate(_ rate: Double) -> Self { + payload[PROP_PLAYBACK_RATE] = rate + return self + } + + func duration(_ durationInSec: Double?) -> Self { + if let durationInSec = durationInSec { + payload[PROP_DURATION] = (durationInSec * 1e3).coerceIfInfOrNan + } + return self + } + + func volume(_ volume: Float, muted: Bool) -> Self { + payload[PROP_VOLUME] = Double(volume) + payload[PROP_MUTED] = muted + return self + } + + func seekable(_ timeRanges: [THEOplayerSDK.TimeRange]?) -> Self { + payload[PROP_SEEKABLE] = fromTimeRanges(timeRanges) + return self + } + + func buffered(_ timeRanges: [THEOplayerSDK.TimeRange]?) -> Self { + payload[PROP_BUFFERED] = fromTimeRanges(timeRanges) + return self + } + + func metadataTracksInfo(_ metadataTracksInfo: [String: Any]) -> Self { + payload.merge(metadataTracksInfo) { (current, _) in current } + return self + } + + func build() -> [String: Any] { + return payload + } + + private func fromTimeRanges(_ ranges: [THEOplayerSDK.TimeRange]?) -> [[String: Double]] { + guard let ranges = ranges else { return [] } + return ranges.map { + [ + PROP_START: $0.start * 1000, + PROP_END: $0.end * 1000 + ] + } + } +} + +private extension Double { + // Make sure we do not send INF or NaN double values over the bridge. It might break debug sessions. + var coerceIfInfOrNan: Double { + // The following constants are taken from Android Theo SDK, see PayloadBuilder.kt. + // Looks like some internal convention + if self.isNaN { + return -1.0 + } + if self.isInfinite { + return -2.0 + } + return self + } +} diff --git a/ios/THEOplayerRCTView.swift b/ios/THEOplayerRCTView.swift index d6fdcb60b..2bc4ebefb 100644 --- a/ios/THEOplayerRCTView.swift +++ b/ios/THEOplayerRCTView.swift @@ -169,15 +169,36 @@ public class THEOplayerRCTView: UIView { } private func notifyNativePlayerReady() { + var payload: [String: Any] = [:] + + if let player = self.player { + // Get existing (?) tracks info from the player + let metadataTracksInfo = mainEventHandler.loadedMetadataAndChapterTracksInfo + let metadata = THEOplayerRCTTrackMetadataAggregator.aggregateTrackMetadata(player: player, metadataTracksInfo: metadataTracksInfo) + + payload["state"] = THEOplayerRCTPayloadBuilder() + .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) + .metadataTracksInfo(metadata) + .build() + } + + let versionString = THEOplayer.version + payload["version"] = [ + "version": versionString, + "playerSuiteVersion": versionString + ] + DispatchQueue.main.async { - let versionString = THEOplayer.version if let forwardedNativeReady = self.onNativePlayerReady { - forwardedNativeReady([ - "version": [ - "version" : versionString, - "playerSuiteVersion": versionString - ], - ]) + forwardedNativeReady(payload) } } } From ba7d167e44921dd3f3728e695f041ccb1729bf53 Mon Sep 17 00:00:00 2001 From: Filipp Mikheev Date: Mon, 21 Jul 2025 22:16:44 -0500 Subject: [PATCH 2/2] change accessibility --- ios/THEOplayerRCTMainEventHandler.swift | 2 +- ios/THEOplayerRCTView.swift | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/ios/THEOplayerRCTMainEventHandler.swift b/ios/THEOplayerRCTMainEventHandler.swift index 4beb3d3cc..6a9091225 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? diff --git a/ios/THEOplayerRCTView.swift b/ios/THEOplayerRCTView.swift index 2bc4ebefb..78822cc7b 100644 --- a/ios/THEOplayerRCTView.swift +++ b/ios/THEOplayerRCTView.swift @@ -172,10 +172,10 @@ public class THEOplayerRCTView: UIView { var payload: [String: Any] = [:] if let player = self.player { - // Get existing (?) tracks info from the player + // Get existing tracks info from the player let metadataTracksInfo = mainEventHandler.loadedMetadataAndChapterTracksInfo let metadata = THEOplayerRCTTrackMetadataAggregator.aggregateTrackMetadata(player: player, metadataTracksInfo: metadataTracksInfo) - + // General player state + source payload payload["state"] = THEOplayerRCTPayloadBuilder() .source(player.source) .currentTime(player.currentTime) @@ -190,6 +190,7 @@ public class THEOplayerRCTView: UIView { .build() } + // Add version info let versionString = THEOplayer.version payload["version"] = [ "version": versionString,