@npaw/forge-react-native-video 9.7.1 → 9.7.2

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.
@@ -25,11 +25,11 @@ def safeExtGet(prop, fallback) {
25
25
 
26
26
  def forgeCoreVersion = project.hasProperty('NpawForge_forgeCoreVersion')
27
27
  ? project.property('NpawForge_forgeCoreVersion')
28
- : (System.getenv('FORGE_CORE_VERSION') ?: '9.7.1')
28
+ : (System.getenv('FORGE_CORE_VERSION') ?: '9.7.2')
29
29
 
30
30
  def forgeAdapterVersion = project.hasProperty('NpawForge_forgeAdapterVersion')
31
31
  ? project.property('NpawForge_forgeAdapterVersion')
32
- : (System.getenv('FORGE_ADAPTER_VERSION') ?: '9.7.1')
32
+ : (System.getenv('FORGE_ADAPTER_VERSION') ?: '9.7.2')
33
33
 
34
34
  android {
35
35
  namespace 'com.npaw.forge.reactnative.video'
@@ -55,12 +55,16 @@ final class ForgeRNVPlayerPlugin: RNVAVPlayerPlugin {
55
55
  override func overridePlayerAsset(source: VideoSource, asset: AVAsset) async -> OverridePlayerAssetResult? {
56
56
  var token: String?
57
57
  var uri: String?
58
+ var sawUriLabel = false
59
+ var sawHeadersLabel = false
58
60
  let mirror = Mirror(reflecting: source)
59
61
  for child in mirror.children {
60
62
  switch child.label {
61
63
  case "uri":
64
+ sawUriLabel = true
62
65
  uri = child.value as? String ?? (child.value as? String?) ?? nil
63
66
  case "requestHeaders":
67
+ sawHeadersLabel = true
64
68
  let headers = (child.value as? [String: Any]) ?? (child.value as? [String: Any]?) ?? nil
65
69
  if let headers {
66
70
  for (key, value) in headers where key.lowercased() == ForgeVideoModuleImpl.tokenHeader {
@@ -71,10 +75,48 @@ final class ForgeRNVPlayerPlugin: RNVAVPlayerPlugin {
71
75
  break
72
76
  }
73
77
  }
78
+ // Reflection has no compile-time contract, so a renamed field would
79
+ // otherwise degrade in silence: `uri` falls back to the asset URL below
80
+ // and playback still reports, while token correlation quietly stops.
81
+ // Say so once, loudly enough to be found in a device log. The CI-side
82
+ // guard is __tests__/rn-video-source-contract.test.ts.
83
+ if !sawUriLabel || !sawHeadersLabel {
84
+ ForgeRNVPlayerPlugin.reportReflectionDrift(sawUri: sawUriLabel, sawHeaders: sawHeadersLabel)
85
+ }
74
86
  if uri == nil, let urlAsset = asset as? AVURLAsset {
75
87
  uri = urlAsset.url.absoluteString
76
88
  }
77
89
  ForgeVideoModuleImpl.shared.sourceSeen(asset: asset, token: token, uri: uri)
78
90
  return nil
79
91
  }
92
+
93
+ /// One-shot notice that `VideoSource`'s shape no longer matches what the
94
+ /// reflection expects.
95
+ ///
96
+ /// Reported once per process: this runs for every player item, and a
97
+ /// per-item log on a list screen would bury the very message it exists to
98
+ /// surface. Reuses the same static-lock pattern as `registerOnce`.
99
+ private static var reflectionDriftReported = false
100
+
101
+ private static func reportReflectionDrift(sawUri: Bool, sawHeaders: Bool) {
102
+ registrationLock.lock()
103
+ if reflectionDriftReported {
104
+ registrationLock.unlock()
105
+ return
106
+ }
107
+ reflectionDriftReported = true
108
+ registrationLock.unlock()
109
+
110
+ let missing = [sawUri ? nil : "uri", sawHeaders ? nil : "requestHeaders"]
111
+ .compactMap { $0 }
112
+ .joined(separator: ", ")
113
+ NSLog(
114
+ "[NPAWForge] react-native-video VideoSource no longer exposes: %@. "
115
+ + "Forge reads these by reflection because they are internal upstream. "
116
+ + "Playback reporting continues, but token correlation is degraded. "
117
+ + "Check the installed react-native-video version against the "
118
+ + "supported range (>=6.11.0 <7).",
119
+ missing
120
+ )
121
+ }
80
122
  }
@@ -310,16 +310,32 @@ public final class ForgeVideoModuleImpl: NSObject {
310
310
  }
311
311
  }
312
312
 
313
+ /// Tears the binding down: detach the player adapter, then fire the
314
+ /// terminal stop.
315
+ ///
316
+ /// The detach comes first deliberately. By the time this block runs,
317
+ /// react-native-video has already released its player - `playerRemoved`
318
+ /// arrives on the main thread and this hop defers a full runloop turn - so
319
+ /// the adapter is observing a player that is being deallocated. Detaching
320
+ /// first means the view is closed to it before the stop makes the view
321
+ /// terminal, and nothing the dying player reports afterwards can be
322
+ /// mistaken for a replay.
323
+ ///
324
+ /// The stop still carries the final position: it never read the player
325
+ /// (`fireStop` builds its metadata from params), and the kernel backfills
326
+ /// `playhead` from the last observed position when the firing params carry
327
+ /// none - `backfill_stop_playhead`, added for
328
+ /// `apple/2026-07-13_ios-stop-playhead-zero-instead-of-final-position`.
313
329
  private func unbindOnMain(_ binding: Binding, fireStop: Bool) {
314
330
  let instanceId = binding.request.instanceId
315
331
  let identifier = binding.request.identifier
316
332
  DispatchQueue.main.async { [weak self] in
317
333
  guard let self, let forge = self.resolveForge(instanceId) else { return }
318
334
  guard let video = forge.getVideoAdapter(identifier: identifier) else { return }
335
+ _ = video.setPlayerAdapter(nil)
319
336
  if fireStop {
320
337
  _ = video.fireStop()
321
338
  }
322
- _ = video.setPlayerAdapter(nil)
323
339
  }
324
340
  }
325
341
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@npaw/forge-react-native-video",
3
- "version": "9.7.1",
3
+ "version": "9.7.2",
4
4
  "description": "react-native-video adapter glue for NPAW Forge: attaches the native Media3/AVPlayer Forge adapters to react-native-video's internal players via its native plugin API.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "author": "Victor Soto <victor.soto@nicepeopleatwork.com>",
@@ -15,10 +15,11 @@
15
15
  "src"
16
16
  ],
17
17
  "scripts": {
18
- "typecheck": "tsc --noEmit"
18
+ "typecheck": "tsc --noEmit",
19
+ "test": "jest"
19
20
  },
20
21
  "peerDependencies": {
21
- "@npaw/forge-react-native": "^9.7.1",
22
+ "@npaw/forge-react-native": "^9.7.2",
22
23
  "react": "*",
23
24
  "react-native": "*",
24
25
  "react-native-video": ">=6.11.0 <7"