@npaw/forge-react-native-video 9.7.4 → 10.7.4
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.
- package/android/src/rnv7/java/com/npaw/forge/reactnative/video/ForgeRNVideoPlugin.java +65 -0
- package/android/src/rnv7/java/com/npaw/forge/reactnative/video/NPAWForgeVideoModule.java +388 -0
- package/android/src/{rnv6 → rnv7}/java/com/npaw/forge/reactnative/video/NPAWForgeVideoPackage.java +2 -2
- package/ios/rnv7/ForgeRNVideoPlugin.swift +64 -0
- package/ios/rnv7/ForgeVideoModuleImpl.swift +474 -0
- package/package.json +3 -2
- package/android/src/rnv6/java/com/npaw/forge/reactnative/video/NPAWForgeVideoModule.java +0 -341
- package/ios/rnv6/ForgeRNVPlayerPlugin.swift +0 -122
- package/ios/rnv6/ForgeVideoModuleImpl.swift +0 -363
|
@@ -1,341 +0,0 @@
|
|
|
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: react-native-video glue TurboModule for Android. Implements RNVExoplayerPlugin, smuggles the X-NPAW-Forge header token into the MediaItem tag via overrideMediaItemBuilder, caches player facts on the main thread, and delegates policy to RNVideoAttachCoordinator to bind Forge's Media3PlayerAdapter.
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
package com.npaw.forge.reactnative.video;
|
|
16
|
-
|
|
17
|
-
import androidx.annotation.Nullable;
|
|
18
|
-
import android.os.Handler;
|
|
19
|
-
import android.os.Looper;
|
|
20
|
-
import androidx.media3.common.MediaItem;
|
|
21
|
-
import androidx.media3.datasource.DataSource;
|
|
22
|
-
import androidx.media3.exoplayer.ExoPlayer;
|
|
23
|
-
import androidx.media3.exoplayer.drm.DrmSessionManager;
|
|
24
|
-
import androidx.media3.exoplayer.source.MediaSource;
|
|
25
|
-
import com.brentvatne.common.api.Source;
|
|
26
|
-
import com.brentvatne.exoplayer.DRMManagerSpec;
|
|
27
|
-
import com.brentvatne.exoplayer.RNVExoplayerPlugin;
|
|
28
|
-
import com.brentvatne.react.ReactNativeVideoManager;
|
|
29
|
-
import com.facebook.react.bridge.Arguments;
|
|
30
|
-
import com.facebook.react.bridge.Promise;
|
|
31
|
-
import com.facebook.react.bridge.ReactApplicationContext;
|
|
32
|
-
import com.facebook.react.bridge.ReadableMap;
|
|
33
|
-
import com.facebook.react.bridge.UiThreadUtil;
|
|
34
|
-
import com.facebook.react.bridge.WritableMap;
|
|
35
|
-
import com.npaw.forge.android.NPAWForge;
|
|
36
|
-
import com.npaw.forge.android.adapters.media3.Media3PlayerAdapter;
|
|
37
|
-
import com.npaw.forge.android.video.VideoAdapter;
|
|
38
|
-
import com.npaw.forge.reactnative.ForgeInstanceRegistry;
|
|
39
|
-
import java.util.HashMap;
|
|
40
|
-
import java.util.Locale;
|
|
41
|
-
import java.util.Map;
|
|
42
|
-
import java.util.concurrent.ConcurrentHashMap;
|
|
43
|
-
|
|
44
|
-
public class NPAWForgeVideoModule extends NativeNPAWForgeVideoSpec
|
|
45
|
-
implements RNVExoplayerPlugin {
|
|
46
|
-
|
|
47
|
-
public static final String NAME = "NPAWForgeVideoRN";
|
|
48
|
-
/** Header customers set on source.requestHeaders for multi-player matching. */
|
|
49
|
-
public static final String TOKEN_HEADER = "x-npaw-forge";
|
|
50
|
-
private static final String TAG_PREFIX = "npaw-forge:";
|
|
51
|
-
private static final String E_UNKNOWN_INSTANCE = "E_FORGE_UNKNOWN_INSTANCE";
|
|
52
|
-
|
|
53
|
-
private static final class PlayerFacts {
|
|
54
|
-
@Nullable volatile String token;
|
|
55
|
-
@Nullable volatile String uri;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
private final ConcurrentHashMap<String, PlayerFacts> facts = new ConcurrentHashMap<>();
|
|
59
|
-
private final ConcurrentHashMap<String, ExoPlayer> playersById = new ConcurrentHashMap<>();
|
|
60
|
-
private final ConcurrentHashMap<String, Media3PlayerAdapter> adaptersByPlayerId =
|
|
61
|
-
new ConcurrentHashMap<>();
|
|
62
|
-
private final Handler mainHandler = new Handler(Looper.getMainLooper());
|
|
63
|
-
|
|
64
|
-
private final RNVideoAttachCoordinator<ExoPlayer> coordinator =
|
|
65
|
-
new RNVideoAttachCoordinator<>(
|
|
66
|
-
new RNVideoAttachCoordinator.PlayerIntrospection() {
|
|
67
|
-
@Nullable
|
|
68
|
-
@Override
|
|
69
|
-
public String currentToken(String playerId) {
|
|
70
|
-
PlayerFacts f = facts.get(playerId);
|
|
71
|
-
return f == null ? null : f.token;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
@Nullable
|
|
75
|
-
@Override
|
|
76
|
-
public String currentUri(String playerId) {
|
|
77
|
-
PlayerFacts f = facts.get(playerId);
|
|
78
|
-
return f == null ? null : f.uri;
|
|
79
|
-
}
|
|
80
|
-
},
|
|
81
|
-
new RNVideoAttachCoordinator.Binder<ExoPlayer>() {
|
|
82
|
-
@Override
|
|
83
|
-
public void bind(String instanceId, String identifier, String playerId, ExoPlayer player) {
|
|
84
|
-
bindOnMain(instanceId, identifier, playerId, player);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
@Override
|
|
88
|
-
public void unbind(
|
|
89
|
-
String instanceId, String identifier, String playerId, boolean fireStop) {
|
|
90
|
-
unbindOnMain(instanceId, identifier, playerId, fireStop);
|
|
91
|
-
}
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
public NPAWForgeVideoModule(ReactApplicationContext reactContext) {
|
|
95
|
-
super(reactContext);
|
|
96
|
-
ReactNativeVideoManager.Companion.getInstance().registerPlugin(this);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
@Override
|
|
100
|
-
public String getName() {
|
|
101
|
-
return NAME;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// ---- spec surface ----
|
|
105
|
-
|
|
106
|
-
@Override
|
|
107
|
-
public void attach(String instanceId, String identifier, @Nullable ReadableMap options, Promise promise) {
|
|
108
|
-
NPAWForge forge = ForgeInstanceRegistry.resolve(instanceId);
|
|
109
|
-
if (forge == null) {
|
|
110
|
-
promise.reject(E_UNKNOWN_INSTANCE, "No NPAWForge instance '" + instanceId + "'");
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
ensureVideo(forge, identifier);
|
|
114
|
-
|
|
115
|
-
Map<String, Object> map = options == null ? new HashMap<>() : options.toHashMap();
|
|
116
|
-
String headerToken = stringOrNull(map.get("headerToken"));
|
|
117
|
-
String sourceUri = stringOrNull(map.get("sourceUri"));
|
|
118
|
-
String policy = stringOrNull(map.get("policy"));
|
|
119
|
-
if (policy == null) {
|
|
120
|
-
policy = (headerToken != null || sourceUri != null) ? "match" : "auto";
|
|
121
|
-
}
|
|
122
|
-
boolean stopOnPlayerRemoved =
|
|
123
|
-
!(map.get("stopOnPlayerRemoved") instanceof Boolean)
|
|
124
|
-
|| (Boolean) map.get("stopOnPlayerRemoved");
|
|
125
|
-
|
|
126
|
-
RNVideoAttachCoordinator.AttachOutcome outcome =
|
|
127
|
-
coordinator.attach(
|
|
128
|
-
new RNVideoAttachCoordinator.AttachRequest(
|
|
129
|
-
instanceId, identifier, policy, headerToken, sourceUri, stopOnPlayerRemoved));
|
|
130
|
-
scheduleFactRefreshes();
|
|
131
|
-
promise.resolve(attachResult(outcome));
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
@Override
|
|
135
|
-
public void detach(String instanceId, String identifier, Promise promise) {
|
|
136
|
-
boolean removed = coordinator.detach(instanceId, identifier, true);
|
|
137
|
-
WritableMap result = Arguments.createMap();
|
|
138
|
-
result.putString("code", removed ? "accepted" : "ignored_invalid_state");
|
|
139
|
-
result.putBoolean("isAccepted", removed);
|
|
140
|
-
result.putString("attachment", "pending");
|
|
141
|
-
promise.resolve(result);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
@Override
|
|
145
|
-
public void invalidate() {
|
|
146
|
-
ReactNativeVideoManager.Companion.getInstance().unregisterPlugin(this);
|
|
147
|
-
coordinator.clear();
|
|
148
|
-
facts.clear();
|
|
149
|
-
playersById.clear();
|
|
150
|
-
adaptersByPlayerId.clear();
|
|
151
|
-
super.invalidate();
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// ---- RNVExoplayerPlugin ----
|
|
155
|
-
|
|
156
|
-
@Override
|
|
157
|
-
public void onInstanceCreated(String id, ExoPlayer player) {
|
|
158
|
-
facts.putIfAbsent(id, new PlayerFacts());
|
|
159
|
-
playersById.put(id, player);
|
|
160
|
-
UiThreadUtil.runOnUiThread(
|
|
161
|
-
() -> {
|
|
162
|
-
refreshFacts(id, player);
|
|
163
|
-
coordinator.playerCreated(id, player);
|
|
164
|
-
});
|
|
165
|
-
scheduleFactRefreshes();
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
@Override
|
|
169
|
-
public void onInstanceRemoved(String id, ExoPlayer player) {
|
|
170
|
-
playersById.remove(id);
|
|
171
|
-
facts.remove(id);
|
|
172
|
-
coordinator.playerRemoved(id);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// ---- RNVExoplayerPlugin optional hooks ----
|
|
176
|
-
|
|
177
|
-
@Nullable
|
|
178
|
-
@Override
|
|
179
|
-
public DRMManagerSpec getDRMManager() {
|
|
180
|
-
return null;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
@Nullable
|
|
184
|
-
@Override
|
|
185
|
-
public DrmSessionManager overrideDrmSessionManager(Source source, DrmSessionManager drmSessionManager) {
|
|
186
|
-
return null;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
@Nullable
|
|
190
|
-
@Override
|
|
191
|
-
public DataSource.Factory overrideMediaDataSourceFactory(
|
|
192
|
-
Source source, DataSource.Factory mediaDataSourceFactory) {
|
|
193
|
-
return null;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
@Nullable
|
|
197
|
-
@Override
|
|
198
|
-
public MediaSource.Factory overrideMediaSourceFactory(
|
|
199
|
-
Source source, MediaSource.Factory mediaSourceFactory, DataSource.Factory mediaDataSourceFactory) {
|
|
200
|
-
return null;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
@Nullable
|
|
204
|
-
@Override
|
|
205
|
-
public MediaItem.Builder overrideMediaItemBuilder(Source source, MediaItem.Builder mediaItemBuilder) {
|
|
206
|
-
String token = tokenFromHeaders(source.getHeaders());
|
|
207
|
-
if (token == null) {
|
|
208
|
-
return null;
|
|
209
|
-
}
|
|
210
|
-
// The tag rides the MediaItem so introspection can read it back from
|
|
211
|
-
// player.currentMediaItem on the main thread - deterministic multi-player
|
|
212
|
-
// correlation without joining override->create call ordering.
|
|
213
|
-
return mediaItemBuilder.setTag(TAG_PREFIX + token);
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
@Override
|
|
217
|
-
public boolean shouldDisableCache(Source source) {
|
|
218
|
-
return false;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
@Override
|
|
222
|
-
public void onInstanceCreated(String id, Object player) {
|
|
223
|
-
if (player instanceof ExoPlayer) {
|
|
224
|
-
onInstanceCreated(id, (ExoPlayer) player);
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
@Override
|
|
229
|
-
public void onInstanceRemoved(String id, Object player) {
|
|
230
|
-
if (player instanceof ExoPlayer) {
|
|
231
|
-
onInstanceRemoved(id, (ExoPlayer) player);
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
// ---- internals ----
|
|
236
|
-
|
|
237
|
-
private void ensureVideo(NPAWForge forge, String identifier) {
|
|
238
|
-
if (forge.getVideoAdapter(identifier) == null) {
|
|
239
|
-
forge.videoBuilder().setIdentifier(identifier).build();
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
/**
|
|
244
|
-
* Sources settle after player creation; instead of a media3 Player.Listener
|
|
245
|
-
* (whose default-method desugaring is fragile across media3 versions), a
|
|
246
|
-
* few delayed main-thread refreshes converge token/URI facts and re-run the
|
|
247
|
-
* pending-match evaluation.
|
|
248
|
-
*/
|
|
249
|
-
private void scheduleFactRefreshes() {
|
|
250
|
-
for (long delayMs : new long[] {0L, 500L, 1500L, 3000L}) {
|
|
251
|
-
mainHandler.postDelayed(
|
|
252
|
-
() -> {
|
|
253
|
-
for (Map.Entry<String, ExoPlayer> entry : playersById.entrySet()) {
|
|
254
|
-
refreshFacts(entry.getKey(), entry.getValue());
|
|
255
|
-
}
|
|
256
|
-
coordinator.playerSourceSettled();
|
|
257
|
-
},
|
|
258
|
-
delayMs);
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
/** Main thread only. */
|
|
263
|
-
private void refreshFacts(String playerId, ExoPlayer player) {
|
|
264
|
-
PlayerFacts f = facts.computeIfAbsent(playerId, key -> new PlayerFacts());
|
|
265
|
-
MediaItem item = player.getCurrentMediaItem();
|
|
266
|
-
if (item != null && item.localConfiguration != null) {
|
|
267
|
-
Object tag = item.localConfiguration.tag;
|
|
268
|
-
f.token =
|
|
269
|
-
tag instanceof String && ((String) tag).startsWith(TAG_PREFIX)
|
|
270
|
-
? ((String) tag).substring(TAG_PREFIX.length())
|
|
271
|
-
: null;
|
|
272
|
-
f.uri = item.localConfiguration.uri.toString();
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
private void bindOnMain(String instanceId, String identifier, String playerId, ExoPlayer player) {
|
|
277
|
-
UiThreadUtil.runOnUiThread(
|
|
278
|
-
() -> {
|
|
279
|
-
NPAWForge forge = ForgeInstanceRegistry.resolve(instanceId);
|
|
280
|
-
if (forge == null || player == null) {
|
|
281
|
-
return;
|
|
282
|
-
}
|
|
283
|
-
ensureVideo(forge, identifier);
|
|
284
|
-
VideoAdapter video = forge.getVideoAdapter(identifier);
|
|
285
|
-
if (video == null) {
|
|
286
|
-
return;
|
|
287
|
-
}
|
|
288
|
-
Media3PlayerAdapter adapter = new Media3PlayerAdapter(player);
|
|
289
|
-
video.setPlayerAdapter(adapter);
|
|
290
|
-
adaptersByPlayerId.put(playerId, adapter);
|
|
291
|
-
});
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
private void unbindOnMain(String instanceId, String identifier, String playerId, boolean fireStop) {
|
|
295
|
-
UiThreadUtil.runOnUiThread(
|
|
296
|
-
() -> {
|
|
297
|
-
adaptersByPlayerId.remove(playerId);
|
|
298
|
-
VideoAdapter video = ForgeInstanceRegistry.resolveVideoAdapter(instanceId, identifier);
|
|
299
|
-
if (video == null) {
|
|
300
|
-
return;
|
|
301
|
-
}
|
|
302
|
-
if (fireStop) {
|
|
303
|
-
video.fireStop();
|
|
304
|
-
}
|
|
305
|
-
video.setPlayerAdapter(null);
|
|
306
|
-
});
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
@Nullable
|
|
310
|
-
private static String tokenFromHeaders(@Nullable Map<String, String> headers) {
|
|
311
|
-
if (headers == null) {
|
|
312
|
-
return null;
|
|
313
|
-
}
|
|
314
|
-
for (Map.Entry<String, String> entry : headers.entrySet()) {
|
|
315
|
-
if (entry.getKey() != null
|
|
316
|
-
&& TOKEN_HEADER.equals(entry.getKey().toLowerCase(Locale.ROOT))) {
|
|
317
|
-
return entry.getValue();
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
return null;
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
@Nullable
|
|
324
|
-
private static String stringOrNull(@Nullable Object value) {
|
|
325
|
-
if (value instanceof String && !((String) value).isEmpty()) {
|
|
326
|
-
return (String) value;
|
|
327
|
-
}
|
|
328
|
-
return null;
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
private static WritableMap attachResult(RNVideoAttachCoordinator.AttachOutcome outcome) {
|
|
332
|
-
WritableMap result = Arguments.createMap();
|
|
333
|
-
result.putString("code", outcome.code);
|
|
334
|
-
result.putBoolean("isAccepted", outcome.accepted);
|
|
335
|
-
result.putString("attachment", outcome.attachment);
|
|
336
|
-
if (outcome.reason != null) {
|
|
337
|
-
result.putString("reason", outcome.reason);
|
|
338
|
-
}
|
|
339
|
-
return result;
|
|
340
|
-
}
|
|
341
|
-
}
|
|
@@ -1,122 +0,0 @@
|
|
|
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: Mandatory RNVAVPlayerPlugin subclass (split object from the TurboModule); forwards react-native-video player lifecycle and source/asset sightings to the Forge attach coordinator.
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import AVFoundation
|
|
16
|
-
import Foundation
|
|
17
|
-
#if canImport(react_native_video)
|
|
18
|
-
import react_native_video
|
|
19
|
-
#endif
|
|
20
|
-
|
|
21
|
-
/// react-native-video stores plugins in an unordered Set and RCTVideo has two
|
|
22
|
-
/// onInstanceCreated call sites, so this object registers exactly once and the
|
|
23
|
-
/// coordinator dedupes repeated deliveries of the same player.
|
|
24
|
-
final class ForgeRNVPlayerPlugin: RNVAVPlayerPlugin {
|
|
25
|
-
|
|
26
|
-
static let shared = ForgeRNVPlayerPlugin()
|
|
27
|
-
private static var registered = false
|
|
28
|
-
private static let registrationLock = NSLock()
|
|
29
|
-
|
|
30
|
-
static func registerOnce() {
|
|
31
|
-
registrationLock.lock()
|
|
32
|
-
defer { registrationLock.unlock() }
|
|
33
|
-
guard !registered else { return }
|
|
34
|
-
registered = true
|
|
35
|
-
ReactNativeVideoManager.shared.registerPlugin(plugin: shared)
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
override func onInstanceCreated(id: String, player: AVPlayer) {
|
|
39
|
-
NSLog("[NPAWForgeRNV] player created id=%@", id)
|
|
40
|
-
ForgeVideoModuleImpl.shared.playerCreated(id: id, player: player)
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
override func onInstanceRemoved(id: String, player: AVPlayer) {
|
|
44
|
-
ForgeVideoModuleImpl.shared.playerRemoved(id: id, player: player)
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/// Called for every AVPlayerItem creation; we never override the asset,
|
|
48
|
-
/// but we record (asset identity -> token/uri) so the coordinator can
|
|
49
|
-
/// correlate players with sources deterministically.
|
|
50
|
-
///
|
|
51
|
-
/// VideoSource's fields are `internal` to react-native-video, so the
|
|
52
|
-
/// token/uri are read via Mirror reflection (read-only), with the
|
|
53
|
-
/// AVURLAsset URL as the fallback. Follow-up filed to propose public
|
|
54
|
-
/// accessors upstream.
|
|
55
|
-
override func overridePlayerAsset(source: VideoSource, asset: AVAsset) async -> OverridePlayerAssetResult? {
|
|
56
|
-
var token: String?
|
|
57
|
-
var uri: String?
|
|
58
|
-
var sawUriLabel = false
|
|
59
|
-
var sawHeadersLabel = false
|
|
60
|
-
let mirror = Mirror(reflecting: source)
|
|
61
|
-
for child in mirror.children {
|
|
62
|
-
switch child.label {
|
|
63
|
-
case "uri":
|
|
64
|
-
sawUriLabel = true
|
|
65
|
-
uri = child.value as? String ?? (child.value as? String?) ?? nil
|
|
66
|
-
case "requestHeaders":
|
|
67
|
-
sawHeadersLabel = true
|
|
68
|
-
let headers = (child.value as? [String: Any]) ?? (child.value as? [String: Any]?) ?? nil
|
|
69
|
-
if let headers {
|
|
70
|
-
for (key, value) in headers where key.lowercased() == ForgeVideoModuleImpl.tokenHeader {
|
|
71
|
-
token = value as? String
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
default:
|
|
75
|
-
break
|
|
76
|
-
}
|
|
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
|
-
}
|
|
86
|
-
if uri == nil, let urlAsset = asset as? AVURLAsset {
|
|
87
|
-
uri = urlAsset.url.absoluteString
|
|
88
|
-
}
|
|
89
|
-
ForgeVideoModuleImpl.shared.sourceSeen(asset: asset, token: token, uri: uri)
|
|
90
|
-
return nil
|
|
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
|
-
}
|
|
122
|
-
}
|