@npaw/forge-react-native 9.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/LICENSE +10 -0
  2. package/NPAWForgeReactNative.podspec +44 -0
  3. package/README.md +51 -0
  4. package/android/build.gradle +77 -0
  5. package/android/src/main/AndroidManifest.xml +14 -0
  6. package/android/src/main/java/com/npaw/forge/reactnative/ForgeDtoMapper.java +295 -0
  7. package/android/src/main/java/com/npaw/forge/reactnative/ForgeInstanceRegistry.java +61 -0
  8. package/android/src/main/java/com/npaw/forge/reactnative/NPAWForgeModule.java +735 -0
  9. package/android/src/main/java/com/npaw/forge/reactnative/NPAWForgePackage.java +53 -0
  10. package/android/src/test/java/com/npaw/forge/reactnative/ForgeDtoMapperTest.java +133 -0
  11. package/ios/ForgeApple/ForgeKernelFFI.xcframework/Info.plist +82 -0
  12. package/ios/ForgeApple/ForgeKernelFFI.xcframework/ios-arm64/Headers/ForgeKernelFFI.h +17 -0
  13. package/ios/ForgeApple/ForgeKernelFFI.xcframework/ios-arm64/Headers/forge_kernelFFI.h +873 -0
  14. package/ios/ForgeApple/ForgeKernelFFI.xcframework/ios-arm64/Headers/module.modulemap +7 -0
  15. package/ios/ForgeApple/ForgeKernelFFI.xcframework/ios-arm64/libForgeKernelFFI.a +0 -0
  16. package/ios/ForgeApple/ForgeKernelFFI.xcframework/ios-arm64_x86_64-simulator/Headers/ForgeKernelFFI.h +17 -0
  17. package/ios/ForgeApple/ForgeKernelFFI.xcframework/ios-arm64_x86_64-simulator/Headers/forge_kernelFFI.h +873 -0
  18. package/ios/ForgeApple/ForgeKernelFFI.xcframework/ios-arm64_x86_64-simulator/Headers/module.modulemap +7 -0
  19. package/ios/ForgeApple/ForgeKernelFFI.xcframework/ios-arm64_x86_64-simulator/libForgeKernelFFI.a +0 -0
  20. package/ios/ForgeApple/ForgeKernelFFI.xcframework/tvos-arm64/Headers/ForgeKernelFFI.h +17 -0
  21. package/ios/ForgeApple/ForgeKernelFFI.xcframework/tvos-arm64/Headers/forge_kernelFFI.h +873 -0
  22. package/ios/ForgeApple/ForgeKernelFFI.xcframework/tvos-arm64/Headers/module.modulemap +7 -0
  23. package/ios/ForgeApple/ForgeKernelFFI.xcframework/tvos-arm64/libForgeKernelFFI.a +0 -0
  24. package/ios/ForgeApple/ForgeKernelFFI.xcframework/tvos-arm64-simulator/Headers/ForgeKernelFFI.h +17 -0
  25. package/ios/ForgeApple/ForgeKernelFFI.xcframework/tvos-arm64-simulator/Headers/forge_kernelFFI.h +873 -0
  26. package/ios/ForgeApple/ForgeKernelFFI.xcframework/tvos-arm64-simulator/Headers/module.modulemap +7 -0
  27. package/ios/ForgeApple/ForgeKernelFFI.xcframework/tvos-arm64-simulator/libForgeKernelFFI.a +0 -0
  28. package/ios/ForgeApple/Sources/ForgeKernelUniFFI/forge_kernel.swift +3931 -0
  29. package/ios/ForgeApple/Sources/ForgeKernelUniFFI/forge_kernelFFI.h +873 -0
  30. package/ios/ForgeApple/Sources/ForgeKernelUniFFI/forge_kernelFFI.modulemap +7 -0
  31. package/ios/ForgeApple/Sources/NPAWForgeAVPlayerAdapter/AVPlayerAccessLogFacts.swift +152 -0
  32. package/ios/ForgeApple/Sources/NPAWForgeAVPlayerAdapter/AVPlayerAdapter.swift +877 -0
  33. package/ios/ForgeApple/Sources/NPAWForgeCapture/AttributionCapture.swift +158 -0
  34. package/ios/ForgeApple/Sources/NPAWForgeCapture/CapturePlanGate.swift +189 -0
  35. package/ios/ForgeApple/Sources/NPAWForgeCapture/CrashCapture.swift +120 -0
  36. package/ios/ForgeApple/Sources/NPAWForgeCapture/ForgeAutomaticCapture.swift +639 -0
  37. package/ios/ForgeApple/Sources/NPAWForgeCapture/SectionsCapture.swift +1176 -0
  38. package/ios/ForgeApple/Sources/NPAWForgeCapture/SectionsEngine.swift +674 -0
  39. package/ios/ForgeApple/Sources/NPAWForgeCapture/SectionsMarkers.swift +105 -0
  40. package/ios/ForgeApple/Sources/NPAWForgeCapture/SectionsSwiftUI.swift +284 -0
  41. package/ios/ForgeApple/Sources/NPAWForgeCapture/SwiftUICapture.swift +44 -0
  42. package/ios/ForgeApple/Sources/NPAWForgeCore/AdAdapter.swift +199 -0
  43. package/ios/ForgeApple/Sources/NPAWForgeCore/AdSemantics.swift +136 -0
  44. package/ios/ForgeApple/Sources/NPAWForgeCore/ForgeApple.swift +596 -0
  45. package/ios/ForgeApple/Sources/NPAWForgeCore/ForgeCapturePlan.swift +297 -0
  46. package/ios/ForgeApple/Sources/NPAWForgeCore/ForgeClock.swift +29 -0
  47. package/ios/ForgeApple/Sources/NPAWForgeCore/ForgeLogging.swift +109 -0
  48. package/ios/ForgeApple/Sources/NPAWForgeCore/ForgeOutcome.swift +89 -0
  49. package/ios/ForgeApple/Sources/NPAWForgeCore/ForgeRuntime.swift +291 -0
  50. package/ios/ForgeApple/Sources/NPAWForgeCore/ForgeStorage.swift +70 -0
  51. package/ios/ForgeApple/Sources/NPAWForgeCore/ManualAdReporter.swift +180 -0
  52. package/ios/ForgeApple/Sources/NPAWForgeCore/NPAWForge.swift +453 -0
  53. package/ios/ForgeApple/Sources/NPAWForgeCore/NativeUniFFIRuntime.swift +594 -0
  54. package/ios/ForgeApple/Sources/NPAWForgeCore/NetworkRequestRecord.swift +191 -0
  55. package/ios/ForgeApple/Sources/NPAWForgeCore/ObjectiveCWrappers.swift +191 -0
  56. package/ios/ForgeApple/Sources/NPAWForgeCore/PlayerAdapter.swift +299 -0
  57. package/ios/ForgeApple/Sources/NPAWForgeCore/PlayerSemantics.swift +266 -0
  58. package/ios/ForgeApple/Sources/NPAWForgeCore/PrivacySanitizer.swift +59 -0
  59. package/ios/ForgeApple/Sources/NPAWForgeCore/VideoAdapter.swift +478 -0
  60. package/ios/ForgeApple/Sources/NPAWForgeCore/VideoOptions.swift +431 -0
  61. package/ios/ForgeApple/Sources/NPAWForgeNetworkCapture/ForgeNetworkCapture.swift +116 -0
  62. package/ios/ForgeApple/Sources/NPAWForgeNetworkCaptureObjC/NPAWForgeNetworkSwizzler.m +293 -0
  63. package/ios/ForgeApple/Sources/NPAWForgeNetworkCaptureObjC/include/NPAWForgeNetworkSwizzler.h +33 -0
  64. package/ios/ForgeModuleImpl.swift +1131 -0
  65. package/ios/NPAWForgeTurboModule.mm +399 -0
  66. package/package.json +33 -0
  67. package/src/ForgeAdReporter.ts +101 -0
  68. package/src/ForgeVideo.ts +169 -0
  69. package/src/NPAWForge.ts +177 -0
  70. package/src/capture.ts +192 -0
  71. package/src/events.ts +40 -0
  72. package/src/index.ts +37 -0
  73. package/src/specs/NativeNPAWForge.ts +168 -0
  74. package/src/types.ts +114 -0
@@ -0,0 +1,478 @@
1
+ /**
2
+ * Copyright (C) NPAW - All Rights Reserved
3
+ *
4
+ * This source code is protected under international copyright law. All rights
5
+ * reserved and protected by the copyright holders.
6
+ * This file is confidential and only available to authorized individuals with the
7
+ * permission of the copyright holders. If you encounter this file and do not have
8
+ * permission, please contact the copyright holders and delete this file.
9
+ *
10
+ *
11
+ * @author: Victor Soto <victor.soto@nicepeopleatwork.com>
12
+ * @description: Apple video adapter lifecycle and runtime view owner.
13
+ */
14
+
15
+ import Foundation
16
+
17
+ public final class VideoBuilder {
18
+ private weak var forge: NPAWForge?
19
+ private var identifier = NPAWForge.defaultVideoAdapterIdentifier
20
+ private var options = VideoOptions()
21
+ private var playerAdapter: PlayerAdapter?
22
+ private var adAdapter: AdAdapter?
23
+
24
+ init(forge: NPAWForge) {
25
+ self.forge = forge
26
+ }
27
+
28
+ @discardableResult public func setIdentifier(_ identifier: String?) -> VideoBuilder {
29
+ if let identifier = identifier?.trimmingCharacters(in: .whitespacesAndNewlines), !identifier.isEmpty {
30
+ self.identifier = identifier
31
+ }
32
+ return self
33
+ }
34
+
35
+ @discardableResult public func setOptions(_ options: VideoOptions?) -> VideoBuilder {
36
+ self.options = options ?? self.options
37
+ return self
38
+ }
39
+
40
+ @discardableResult public func setPlayerAdapter(_ playerAdapter: PlayerAdapter?) -> VideoBuilder {
41
+ self.playerAdapter = playerAdapter
42
+ return self
43
+ }
44
+
45
+ @discardableResult public func setAdAdapter(_ adAdapter: AdAdapter?) -> VideoBuilder {
46
+ self.adAdapter = adAdapter
47
+ return self
48
+ }
49
+
50
+ public func build() -> VideoAdapter {
51
+ guard let forge else {
52
+ return VideoAdapter.unavailable(identifier: identifier, options: options)
53
+ }
54
+ return forge.buildVideoAdapter(
55
+ identifier: identifier,
56
+ options: options,
57
+ playerAdapter: playerAdapter,
58
+ adAdapter: adAdapter
59
+ )
60
+ }
61
+ }
62
+
63
+ public final class VideoAdapter: RuntimeContentMetadataProvider {
64
+ weak var forge: NPAWForge?
65
+ public let identifier: String
66
+ public let options: VideoOptions
67
+ /// Options used for runtime event recording: the integrator options plus
68
+ /// the bound player adapter's `defaultWaitForMetadata` when the integrator
69
+ /// did not choose explicitly (mirrors the Android `effectiveOptions`).
70
+ private var effectiveOptions: VideoOptions
71
+ private let runtimeView: RuntimeVideoView
72
+ private var destroyed = false
73
+ private var willSendRequestListener: ((String, VideoAdapter, inout [String: String]) -> Void)?
74
+
75
+ public private(set) var playerAdapter: PlayerAdapter?
76
+ public private(set) var adAdapter: AdAdapter?
77
+
78
+ public var isDestroyed: Bool { destroyed }
79
+
80
+ init(forge: NPAWForge, identifier: String, options: VideoOptions) {
81
+ self.forge = forge
82
+ self.identifier = identifier
83
+ self.options = options
84
+ self.effectiveOptions = options
85
+ self.runtimeView = forge.kernel.createVideoView(timestampMs: 0, options: options)
86
+ }
87
+
88
+ private init(identifier: String, options: VideoOptions) {
89
+ self.identifier = identifier
90
+ self.options = options
91
+ self.effectiveOptions = options
92
+ self.runtimeView = RuntimeVideoView(id: -1)
93
+ }
94
+
95
+ static func unavailable(identifier: String, options: VideoOptions) -> VideoAdapter {
96
+ VideoAdapter(identifier: identifier, options: options)
97
+ }
98
+
99
+ public func setPlayerAdapter(_ playerAdapter: PlayerAdapter?) -> ForgeOutcome {
100
+ replacePlayerAdapter(playerAdapter, checkDuplicates: true)
101
+ }
102
+
103
+ public func setAdAdapter(_ adAdapter: AdAdapter?) -> ForgeOutcome {
104
+ replaceAdAdapter(adAdapter, checkDuplicates: true)
105
+ }
106
+
107
+ public func setWillSendRequestListener(_ listener: ((String, VideoAdapter, inout [String: String]) -> Void)?) -> ForgeOutcome {
108
+ guard !destroyed else { return .ignoredInvalidState }
109
+ willSendRequestListener = listener
110
+ let hook = listener.map { callback in
111
+ VideoPreSendHook { [weak self] eventName, _, _, params in
112
+ guard let self else { return }
113
+ callback(eventName, self, &params)
114
+ }
115
+ }
116
+ return forge?.kernel.registerVideoPreSendHook(view: runtimeView, hook: hook) ?? .ignoredInvalidState
117
+ }
118
+
119
+ public func fireInit(
120
+ metadata: PlayerMetadataSnapshot? = nil,
121
+ params: [String: String]? = [:],
122
+ timestampMs: Int64 = 0
123
+ ) -> ForgeOutcome {
124
+ recordPlayerEvent(event: .initEvent, timestampMs: timestampMs, metadata: metadata ?? playerMetadataFromParams(params))
125
+ }
126
+
127
+ public func fireStart(
128
+ forceInit: Bool? = false,
129
+ metadata: PlayerMetadataSnapshot? = nil,
130
+ params: [String: String]? = [:],
131
+ timestampMs: Int64 = 0
132
+ ) -> ForgeOutcome {
133
+ var snapshot = metadata ?? playerMetadataFromParams(params)
134
+ snapshot.forceInit = forceInit ?? false
135
+ return recordPlayerEvent(event: .start, timestampMs: timestampMs, metadata: snapshot)
136
+ }
137
+
138
+ public func fireJoin(
139
+ metadata: PlayerMetadataSnapshot? = nil,
140
+ params: [String: String]? = [:],
141
+ timestampMs: Int64 = 0
142
+ ) -> ForgeOutcome {
143
+ recordPlayerEvent(event: .join, timestampMs: timestampMs, metadata: metadata ?? playerMetadataFromParams(params))
144
+ }
145
+
146
+ public func firePause(
147
+ metadata: PlayerMetadataSnapshot? = nil,
148
+ params: [String: String]? = [:],
149
+ timestampMs: Int64 = 0
150
+ ) -> ForgeOutcome {
151
+ recordPlayerEvent(event: .pause, timestampMs: timestampMs, metadata: metadata ?? playerMetadataFromParams(params))
152
+ }
153
+
154
+ public func fireResume(
155
+ metadata: PlayerMetadataSnapshot? = nil,
156
+ params: [String: String]? = [:],
157
+ timestampMs: Int64 = 0
158
+ ) -> ForgeOutcome {
159
+ recordPlayerEvent(event: .resume, timestampMs: timestampMs, metadata: metadata ?? playerMetadataFromParams(params))
160
+ }
161
+
162
+ public func fireSeekBegin(
163
+ convertFromBuffer: Bool? = true,
164
+ metadata: PlayerMetadataSnapshot? = nil,
165
+ params: [String: String]? = [:],
166
+ timestampMs: Int64 = 0
167
+ ) -> ForgeOutcome {
168
+ var snapshot = metadata ?? playerMetadataFromParams(params)
169
+ snapshot.convertFromBuffer = convertFromBuffer ?? true
170
+ return recordPlayerEvent(event: .seekBegin, timestampMs: timestampMs, metadata: snapshot)
171
+ }
172
+
173
+ public func fireSeekEnd(
174
+ metadata: PlayerMetadataSnapshot? = nil,
175
+ params: [String: String]? = [:],
176
+ timestampMs: Int64 = 0
177
+ ) -> ForgeOutcome {
178
+ recordPlayerEvent(event: .seekEnd, timestampMs: timestampMs, metadata: metadata ?? playerMetadataFromParams(params))
179
+ }
180
+
181
+ public func fireBufferBegin(
182
+ convertFromSeek: Bool? = false,
183
+ metadata: PlayerMetadataSnapshot? = nil,
184
+ params: [String: String]? = [:],
185
+ timestampMs: Int64 = 0
186
+ ) -> ForgeOutcome {
187
+ var snapshot = metadata ?? playerMetadataFromParams(params)
188
+ snapshot.convertFromSeek = convertFromSeek ?? false
189
+ return recordPlayerEvent(event: .bufferBegin, timestampMs: timestampMs, metadata: snapshot)
190
+ }
191
+
192
+ public func fireBufferEnd(
193
+ metadata: PlayerMetadataSnapshot? = nil,
194
+ params: [String: String]? = [:],
195
+ timestampMs: Int64 = 0
196
+ ) -> ForgeOutcome {
197
+ recordPlayerEvent(event: .bufferEnd, timestampMs: timestampMs, metadata: metadata ?? playerMetadataFromParams(params))
198
+ }
199
+
200
+ public func fireStop(
201
+ metadata: PlayerMetadataSnapshot? = nil,
202
+ params: [String: String]? = [:],
203
+ timestampMs: Int64 = 0
204
+ ) -> ForgeOutcome {
205
+ recordPlayerEvent(event: .stop, timestampMs: timestampMs, metadata: metadata ?? playerMetadataFromParams(params))
206
+ }
207
+
208
+ public func fireError(
209
+ errorCode: String? = nil,
210
+ errorMessage: String? = nil,
211
+ errorMetadata: String? = nil,
212
+ timestampMs: Int64 = 0
213
+ ) -> ForgeOutcome {
214
+ var metadata = manualPlayerMetadata()
215
+ metadata.errorCode = errorCode
216
+ metadata.errorMessage = errorMessage
217
+ metadata.errorMetadata = errorMetadata
218
+ return recordPlayerEvent(event: .error, timestampMs: timestampMs, metadata: metadata)
219
+ }
220
+
221
+ public func fireFatalError(
222
+ errorCode: String? = nil,
223
+ errorMessage: String? = nil,
224
+ errorMetadata: String? = nil,
225
+ timestampMs: Int64 = 0
226
+ ) -> ForgeOutcome {
227
+ var metadata = manualPlayerMetadata()
228
+ metadata.errorCode = errorCode
229
+ metadata.errorMessage = errorMessage
230
+ metadata.errorMetadata = errorMetadata
231
+ metadata.errorLevel = "fatal"
232
+ return recordPlayerEvent(event: .fatalError, timestampMs: timestampMs, metadata: metadata)
233
+ }
234
+
235
+ public func fireDestroy(timestampMs: Int64 = 0) -> ForgeOutcome {
236
+ recordPlayerEvent(event: .destroy, timestampMs: timestampMs, metadata: manualPlayerMetadata())
237
+ }
238
+
239
+ public func fireEvent(
240
+ eventName: String = "",
241
+ dimensions: [String: String] = [:],
242
+ values: [String: Double] = [:],
243
+ topLevelDimensions: [String: String] = [:],
244
+ hasEndDatetime: Bool = false,
245
+ timestampMs: Int64 = 0
246
+ ) -> ForgeOutcome {
247
+ var metadata = manualPlayerMetadata()
248
+ metadata.customEvent = CustomVideoEventPayload(
249
+ name: eventName,
250
+ dimensions: dimensions,
251
+ values: values,
252
+ topLevel: topLevelDimensions,
253
+ hasEndDatetime: hasEndDatetime
254
+ )
255
+ return recordPlayerEvent(event: .customVideoEvent, timestampMs: timestampMs, metadata: metadata)
256
+ }
257
+
258
+ public func fireEventEnd(
259
+ eventName: String = "",
260
+ dimensions: [String: String] = [:],
261
+ values: [String: Double] = [:],
262
+ topLevelDimensions: [String: String] = [:],
263
+ timestampMs: Int64 = 0
264
+ ) -> ForgeOutcome {
265
+ var metadata = manualPlayerMetadata()
266
+ metadata.customEvent = CustomVideoEventPayload(
267
+ name: eventName,
268
+ dimensions: dimensions,
269
+ values: values,
270
+ topLevel: topLevelDimensions
271
+ )
272
+ return recordPlayerEvent(event: .customVideoEventEnd, timestampMs: timestampMs, metadata: metadata)
273
+ }
274
+
275
+ public func fireMetadataUpdate(
276
+ metadata: PlayerMetadataSnapshot? = nil,
277
+ params: [String: String]? = [:],
278
+ timestampMs: Int64 = 0
279
+ ) -> ForgeOutcome {
280
+ recordPlayerEvent(event: .metadataUpdate, timestampMs: timestampMs, metadata: metadata ?? playerMetadataFromParams(params))
281
+ }
282
+
283
+ public func createAdReporter(adapterVersion: String? = nil) -> ManualAdReporter {
284
+ ManualAdReporter(videoAdapter: self, adapterVersion: adapterVersion)
285
+ }
286
+
287
+ /// Remote behavior hints from the latest kernel capture plan (`bh.v`),
288
+ /// or `nil` before the first plan. Player adapters snapshot these at view
289
+ /// start; hints are never hot-swapped mid-view.
290
+ public func capturePlanBehavior() -> ForgeCapturePlan.Behavior? {
291
+ guard !destroyed else { return nil }
292
+ return forge?.capturePlan()?.behavior
293
+ }
294
+
295
+ public func destroy() -> ForgeOutcome {
296
+ guard !destroyed else { return .ignoredDuplicate }
297
+ let destroyOutcome = forge?.kernel.recordPlayerEvent(
298
+ view: runtimeView,
299
+ event: .destroy,
300
+ timestampMs: 0,
301
+ metadata: manualPlayerMetadata(),
302
+ options: effectiveOptions
303
+ ) ?? .ignoredInvalidState
304
+ destroyed = true
305
+ playerAdapter?.unbindFromVideoAdapter()
306
+ adAdapter?.unbindFromVideoAdapter()
307
+ playerAdapter = nil
308
+ adAdapter = nil
309
+ willSendRequestListener = nil
310
+ _ = forge?.kernel.registerContentMetadataProvider(view: runtimeView, provider: nil)
311
+ _ = forge?.kernel.registerVideoPreSendHook(view: runtimeView, hook: nil)
312
+ _ = forge?.kernel.destroyVideoView(runtimeView)
313
+ forge?.onVideoAdapterDestroyed(self)
314
+ return destroyOutcome.isAccepted ? .accepted : destroyOutcome
315
+ }
316
+
317
+ func replacePlayerAdapter(_ playerAdapter: PlayerAdapter?, checkDuplicates: Bool) -> ForgeOutcome {
318
+ guard !destroyed else { return .ignoredInvalidState }
319
+ if checkDuplicates {
320
+ forge?.onPlayerAdapterRegistered(owner: self, playerAdapter: playerAdapter)
321
+ }
322
+ self.playerAdapter?.unbindFromVideoAdapter()
323
+ self.playerAdapter = playerAdapter
324
+ effectiveOptions = playerAdapter?.defaultWaitForMetadata == true
325
+ ? options.withAdapterWaitForMetadataDefault()
326
+ : options
327
+ playerAdapter?.bindToVideoAdapter(self)
328
+ return forge?.kernel.registerContentMetadataProvider(view: runtimeView, provider: self) ?? .ignoredInvalidState
329
+ }
330
+
331
+ func replaceAdAdapter(_ adAdapter: AdAdapter?, checkDuplicates: Bool) -> ForgeOutcome {
332
+ guard !destroyed else { return .ignoredInvalidState }
333
+ if checkDuplicates {
334
+ forge?.onAdAdapterRegistered(owner: self, adAdapter: adAdapter)
335
+ }
336
+ self.adAdapter?.unbindFromVideoAdapter()
337
+ self.adAdapter = adAdapter
338
+ adAdapter?.bindToVideoAdapter(self)
339
+ return .accepted
340
+ }
341
+
342
+ func recordPlayerEvent(event: PlayerEvent, timestampMs: Int64, metadata: PlayerMetadataSnapshot) -> ForgeOutcome {
343
+ guard !destroyed else { return .ignoredInvalidState }
344
+ return forge?.kernel.recordPlayerEvent(
345
+ view: runtimeView,
346
+ event: event,
347
+ timestampMs: timestampMs,
348
+ metadata: metadata,
349
+ options: effectiveOptions
350
+ ) ?? .ignoredInvalidState
351
+ }
352
+
353
+ func recordAdEvent(
354
+ event: AdEvent,
355
+ timestampMs: Int64,
356
+ metadata: AdMetadataSnapshot,
357
+ adapterVersion: String? = nil
358
+ ) -> ForgeOutcome {
359
+ guard !destroyed else { return .ignoredInvalidState }
360
+ return forge?.kernel.recordAdEvent(
361
+ view: runtimeView,
362
+ event: event,
363
+ timestampMs: timestampMs,
364
+ metadata: metadata,
365
+ options: effectiveOptions,
366
+ adapterVersion: adapterVersion ?? adAdapter?.descriptor.version
367
+ ) ?? .ignoredInvalidState
368
+ }
369
+
370
+ func usesAdapterOrPlayer(_ candidate: AnyObject) -> Bool {
371
+ if playerAdapter === candidate || adAdapter === candidate { return true }
372
+ if let playerAdapter, playerAdapter.playerObject === candidate { return true }
373
+ if let adAdapter, adAdapter.playerObject === candidate { return true }
374
+ return false
375
+ }
376
+
377
+ public func metadataSnapshot(timemarkMs: Int64, elapsedRealtimeMs: Int64) -> MetadataSnapshot? {
378
+ guard !destroyed,
379
+ let snapshot = playerAdapter?.snapshotMetadata(timemarkMs: timemarkMs, elapsedRealtimeMs: elapsedRealtimeMs) else {
380
+ return nil
381
+ }
382
+ return MetadataSnapshot(
383
+ content: snapshot,
384
+ qos: playerAdapter?.snapshotQos(timemarkMs: timemarkMs, elapsedRealtimeMs: elapsedRealtimeMs)
385
+ )
386
+ }
387
+
388
+ private func manualPlayerMetadata() -> PlayerMetadataSnapshot {
389
+ PlayerMetadataSnapshot(playerName: "Manual Player")
390
+ }
391
+
392
+ private func playerMetadataFromParams(_ params: [String: String]?) -> PlayerMetadataSnapshot {
393
+ PlayerMetadataSnapshot(
394
+ playerName: params?["player"] ?? "Manual Player",
395
+ playerVersion: params?["playerVersion"],
396
+ resourceUrl: params?["resource"],
397
+ parsedResource: params?["parsedResource"] ?? params?["urlToParse"],
398
+ title: params?["title"],
399
+ contentType: (params?["contentType"] ?? params?["content_type"]).flatMap(ContentType.fromAdapterlessValue) ?? .unknown,
400
+ playheadMs: params?["playhead"].flatMap(Self.secondsToMillis),
401
+ durationMs: params?["mediaDuration"].flatMap(Self.secondsToMillis),
402
+ bitrate: params?["bitrate"].flatMap(Int64.init),
403
+ rendition: params?["rendition"],
404
+ videoCodec: params?["videoCodec"],
405
+ audioCodec: params?["audioCodec"],
406
+ codecProfile: params?["codecProfile"],
407
+ containerFormat: params?["containerFormat"],
408
+ errorMetadata: params?["errorMetadata"],
409
+ errorLevel: params?["errorLevel"],
410
+ program: params?["program"]
411
+ )
412
+ }
413
+
414
+ func adMetadataFromParams(_ params: [String: String]?) -> AdMetadataSnapshot {
415
+ AdMetadataSnapshot(
416
+ adId: params?["adId"] ?? params?["ad_id"],
417
+ creativeId: params?["adCreativeId"] ?? params?["creative_id"],
418
+ adUrl: params?["adUrl"] ?? params?["ad_url"],
419
+ adTitle: params?["adTitle"],
420
+ adDurationMs: params?["adDurationMs"].flatMap(Int64.init) ?? params?["adDuration"].flatMap(Self.secondsToMillis),
421
+ adPlayheadMs: params?["adPlayheadMs"].flatMap(Int64.init) ?? params?["adPlayhead"].flatMap(Self.secondsToMillis),
422
+ adBitrate: params?["adBitrate"].flatMap(Int64.init),
423
+ position: params?["position"].flatMap(AdPosition.fromAdapterlessValue),
424
+ quartile: params?["quartile"].flatMap(Int.init),
425
+ insertionType: (params?["adInsertionType"] ?? params?["insertion_type"]).flatMap(AdInsertionType.fromAdapterlessValue) ?? .unknown,
426
+ errorMetadata: params?["errorMetadata"],
427
+ errorLevel: params?["errorLevel"]
428
+ )
429
+ }
430
+
431
+ private static func secondsToMillis(_ value: String) -> Int64? {
432
+ Double(value).map { Int64($0 * 1000.0) }
433
+ }
434
+ }
435
+
436
+ private extension ContentType {
437
+ static func fromAdapterlessValue(_ value: String) -> ContentType {
438
+ switch value.lowercased() {
439
+ case "live": return .live
440
+ case "vod": return .vod
441
+ case "audio": return .audio
442
+ default: return .unknown
443
+ }
444
+ }
445
+ }
446
+
447
+ private extension AdPosition {
448
+ static func fromAdapterlessValue(_ value: String) -> AdPosition {
449
+ switch value.lowercased() {
450
+ case "pre", "preroll": return .pre
451
+ case "mid", "midroll": return .mid
452
+ case "post", "postroll": return .post
453
+ default: return .unknown
454
+ }
455
+ }
456
+ }
457
+
458
+ private extension AdInsertionType {
459
+ static func fromAdapterlessValue(_ value: String) -> AdInsertionType {
460
+ switch value.lowercased() {
461
+ case "csai", "client", "client_side": return .clientSide
462
+ case "ssai", "server", "server_side": return .serverSide
463
+ default: return .unknown
464
+ }
465
+ }
466
+ }
467
+
468
+ private final class VideoPreSendHook: RuntimeVideoPreSendHook {
469
+ private let callback: (String, RuntimeVideoView, String, inout [String: String]) -> Void
470
+
471
+ init(callback: @escaping (String, RuntimeVideoView, String, inout [String: String]) -> Void) {
472
+ self.callback = callback
473
+ }
474
+
475
+ func willSendRequest(eventName: String, view: RuntimeVideoView, serviceName: String, params: inout [String: String]) {
476
+ callback(eventName, view, serviceName, &params)
477
+ }
478
+ }