react-native-meshstream 0.0.0-stage → 1.0.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.
- package/CHANGELOG.md +22 -0
- package/LICENSE +176 -0
- package/MeshStreamP2P.podspec +20 -0
- package/README.md +135 -2
- package/README.zh-CN.md +136 -0
- package/android/build.gradle +23 -0
- package/android/src/main/AndroidManifest.xml +4 -0
- package/android/src/main/java/net/meshstream/reactnative/MeshStreamP2PModule.kt +380 -0
- package/android/src/main/java/net/meshstream/reactnative/MeshStreamP2PPackage.kt +13 -0
- package/ios/MeshStreamP2P.h +4 -0
- package/ios/MeshStreamP2P.m +362 -0
- package/ios/MeshStreamP2PValidation.h +9 -0
- package/ios/MeshStreamP2PValidation.m +23 -0
- package/lib/typescript/NativeMeshStreamP2P.d.ts +20 -0
- package/lib/typescript/NativeMeshStreamP2P.js +11 -0
- package/lib/typescript/index.d.ts +46 -0
- package/lib/typescript/index.js +156 -0
- package/lib/typescript/types.d.ts +87 -0
- package/lib/typescript/types.js +2 -0
- package/lib/typescript/version.d.ts +5 -0
- package/lib/typescript/version.js +8 -0
- package/package.json +52 -4
- package/react-native.config.js +11 -0
- package/src/NativeMeshStreamP2P.ts +36 -0
- package/src/index.ts +176 -0
- package/src/types.ts +128 -0
- package/src/version.ts +5 -0
- package/tests/ios/MeshStreamP2PValidationTests.m +35 -0
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
package net.meshstream.reactnative
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.Arguments
|
|
4
|
+
import com.facebook.react.bridge.Promise
|
|
5
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
|
+
import com.facebook.react.bridge.ReactContextBaseJavaModule
|
|
7
|
+
import com.facebook.react.bridge.ReactMethod
|
|
8
|
+
import com.facebook.react.bridge.ReadableMap
|
|
9
|
+
import com.facebook.react.bridge.ReadableMapKeySetIterator
|
|
10
|
+
import com.facebook.react.bridge.ReadableType
|
|
11
|
+
import com.facebook.react.bridge.WritableMap
|
|
12
|
+
import com.facebook.react.modules.core.DeviceEventManagerModule
|
|
13
|
+
import io.meshstream.sdk.P2pClient
|
|
14
|
+
import io.meshstream.sdk.P2pConfig
|
|
15
|
+
import io.meshstream.sdk.P2pDiagnosticsSnapshot
|
|
16
|
+
import io.meshstream.sdk.P2pEventListener
|
|
17
|
+
import io.meshstream.sdk.P2pSdk
|
|
18
|
+
import io.meshstream.sdk.P2pSdkStatus
|
|
19
|
+
import io.meshstream.sdk.P2pStats
|
|
20
|
+
import io.meshstream.sdk.P2pUnavailableReason
|
|
21
|
+
import io.meshstream.sdk.PlaybackOptions
|
|
22
|
+
import io.meshstream.sdk.PlaybackSession
|
|
23
|
+
import java.util.UUID
|
|
24
|
+
import java.util.concurrent.ConcurrentHashMap
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Classic React Native module backed by the Android MeshStream SDK. Native state is
|
|
28
|
+
* entirely keyed by the JS-visible client/session IDs so several players can coexist.
|
|
29
|
+
*/
|
|
30
|
+
class MeshStreamP2PModule(
|
|
31
|
+
reactContext: ReactApplicationContext,
|
|
32
|
+
) : ReactContextBaseJavaModule(reactContext) {
|
|
33
|
+
private data class BridgeClient(
|
|
34
|
+
val client: P2pClient,
|
|
35
|
+
val sessions: ConcurrentHashMap<String, PlaybackSession> = ConcurrentHashMap(),
|
|
36
|
+
val nativeSessionIds: ConcurrentHashMap<Long, String> = ConcurrentHashMap(),
|
|
37
|
+
val pendingEvents: ConcurrentHashMap<Long, MutableList<PendingEvent>> = ConcurrentHashMap(),
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
private data class PendingEvent(
|
|
41
|
+
val type: String,
|
|
42
|
+
val extras: WritableMap.() -> Unit,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
private val clients = ConcurrentHashMap<String, BridgeClient>()
|
|
46
|
+
|
|
47
|
+
override fun getName(): String = MODULE_NAME
|
|
48
|
+
|
|
49
|
+
@ReactMethod
|
|
50
|
+
fun getSdkVersion(promise: Promise) {
|
|
51
|
+
try {
|
|
52
|
+
promise.resolve(P2pSdk.getSdkVersion())
|
|
53
|
+
} catch (error: Throwable) {
|
|
54
|
+
promise.reject("MESHSTREAM_VERSION_FAILED", error.message, error)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
@ReactMethod
|
|
59
|
+
fun createClient(config: ReadableMap, promise: Promise) {
|
|
60
|
+
try {
|
|
61
|
+
val key = config.requiredString("key")
|
|
62
|
+
val client = P2pSdk.create(
|
|
63
|
+
reactApplicationContext.applicationContext,
|
|
64
|
+
P2pConfig(
|
|
65
|
+
key = key,
|
|
66
|
+
p2pEnabled = config.optionalBoolean("p2pEnabled") ?: true,
|
|
67
|
+
),
|
|
68
|
+
)
|
|
69
|
+
val clientId = UUID.randomUUID().toString()
|
|
70
|
+
val bridgeClient = BridgeClient(client)
|
|
71
|
+
client.addEventListener(createEventListener(clientId, bridgeClient))
|
|
72
|
+
clients[clientId] = bridgeClient
|
|
73
|
+
promise.resolve(Arguments.createMap().apply {
|
|
74
|
+
putString("clientId", clientId)
|
|
75
|
+
putMap("status", client.status.toWritableMap())
|
|
76
|
+
})
|
|
77
|
+
} catch (error: Throwable) {
|
|
78
|
+
promise.reject("MESHSTREAM_CREATE_CLIENT_FAILED", error.message, error)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
@ReactMethod
|
|
83
|
+
fun createPlaybackSession(clientId: String, options: ReadableMap, promise: Promise) {
|
|
84
|
+
try {
|
|
85
|
+
val bridgeClient = clients.requireClient(clientId)
|
|
86
|
+
val url = options.requiredString("url")
|
|
87
|
+
val session = bridgeClient.client.createPlaybackSession(
|
|
88
|
+
url,
|
|
89
|
+
PlaybackOptions(
|
|
90
|
+
contentId = options.optionalString("contentId"),
|
|
91
|
+
mimeType = options.optionalString("mimeType"),
|
|
92
|
+
originHeaders = options.stringMap("originHeaders"),
|
|
93
|
+
),
|
|
94
|
+
)
|
|
95
|
+
val sessionId = UUID.randomUUID().toString()
|
|
96
|
+
bridgeClient.sessions[sessionId] = session
|
|
97
|
+
bridgeClient.nativeSessionIds[session.sessionId] = sessionId
|
|
98
|
+
flushPendingEvents(clientId, sessionId, session, bridgeClient)
|
|
99
|
+
promise.resolve(session.toWritableMap(clientId, sessionId))
|
|
100
|
+
} catch (error: Throwable) {
|
|
101
|
+
promise.reject("MESHSTREAM_CREATE_SESSION_FAILED", error.message, error)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
@ReactMethod
|
|
106
|
+
fun preload(clientId: String, sessionId: String, promise: Promise) = withSession(clientId, sessionId, promise) {
|
|
107
|
+
it.preload()
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
@ReactMethod
|
|
111
|
+
fun setP2pEnabled(clientId: String, sessionId: String, enabled: Boolean, promise: Promise) =
|
|
112
|
+
withSession(clientId, sessionId, promise) {
|
|
113
|
+
it.setP2pEnabled(enabled)
|
|
114
|
+
null
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
@ReactMethod
|
|
118
|
+
fun updateBufferedDurationMs(clientId: String, sessionId: String, durationMs: Double, promise: Promise) =
|
|
119
|
+
withSession(clientId, sessionId, promise) {
|
|
120
|
+
it.updateBufferedDurationMs(durationMs.toLong().coerceAtLeast(0))
|
|
121
|
+
null
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
@ReactMethod
|
|
125
|
+
fun updateCurrentPositionMs(clientId: String, sessionId: String, positionMs: Double, promise: Promise) =
|
|
126
|
+
withSession(clientId, sessionId, promise) {
|
|
127
|
+
it.updateCurrentPositionMs(positionMs.toLong().coerceAtLeast(0))
|
|
128
|
+
null
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
@ReactMethod
|
|
132
|
+
fun getStats(clientId: String, sessionId: String, promise: Promise) = withSession(clientId, sessionId, promise) {
|
|
133
|
+
it.statistics().toWritableMap()
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
@ReactMethod
|
|
137
|
+
fun getDiagnostics(clientId: String, sessionId: String, promise: Promise) = withSession(clientId, sessionId, promise) {
|
|
138
|
+
it.diagnostics().toWritableMap()
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
@ReactMethod
|
|
142
|
+
fun getStatus(clientId: String, promise: Promise) {
|
|
143
|
+
try {
|
|
144
|
+
promise.resolve(clients.requireClient(clientId).client.status.toWritableMap())
|
|
145
|
+
} catch (error: Throwable) {
|
|
146
|
+
promise.reject("MESHSTREAM_STATUS_FAILED", error.message, error)
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
@ReactMethod
|
|
151
|
+
fun closeSession(clientId: String, sessionId: String, promise: Promise) {
|
|
152
|
+
try {
|
|
153
|
+
val bridgeClient = clients.requireClient(clientId)
|
|
154
|
+
bridgeClient.sessions.remove(sessionId)?.let { session ->
|
|
155
|
+
bridgeClient.nativeSessionIds.remove(session.sessionId, sessionId)
|
|
156
|
+
bridgeClient.pendingEvents.remove(session.sessionId)
|
|
157
|
+
session.close()
|
|
158
|
+
}
|
|
159
|
+
promise.resolve(null)
|
|
160
|
+
} catch (error: Throwable) {
|
|
161
|
+
promise.reject("MESHSTREAM_CLOSE_SESSION_FAILED", error.message, error)
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
@ReactMethod
|
|
166
|
+
fun closeClient(clientId: String, promise: Promise) {
|
|
167
|
+
try {
|
|
168
|
+
clients.remove(clientId)?.let { bridgeClient ->
|
|
169
|
+
bridgeClient.sessions.values.forEach { it.close() }
|
|
170
|
+
bridgeClient.sessions.clear()
|
|
171
|
+
bridgeClient.nativeSessionIds.clear()
|
|
172
|
+
bridgeClient.pendingEvents.clear()
|
|
173
|
+
bridgeClient.client.close()
|
|
174
|
+
}
|
|
175
|
+
promise.resolve(null)
|
|
176
|
+
} catch (error: Throwable) {
|
|
177
|
+
promise.reject("MESHSTREAM_CLOSE_CLIENT_FAILED", error.message, error)
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
override fun invalidate() {
|
|
182
|
+
clients.values.forEach { bridgeClient ->
|
|
183
|
+
bridgeClient.sessions.values.forEach { it.close() }
|
|
184
|
+
bridgeClient.nativeSessionIds.clear()
|
|
185
|
+
bridgeClient.pendingEvents.clear()
|
|
186
|
+
bridgeClient.client.close()
|
|
187
|
+
}
|
|
188
|
+
clients.clear()
|
|
189
|
+
super.invalidate()
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
private fun withSession(clientId: String, sessionId: String, promise: Promise, operation: (PlaybackSession) -> Any?) {
|
|
193
|
+
try {
|
|
194
|
+
promise.resolve(operation(clients.requireClient(clientId).sessions.requireSession(sessionId)))
|
|
195
|
+
} catch (error: Throwable) {
|
|
196
|
+
promise.reject("MESHSTREAM_SESSION_OPERATION_FAILED", error.message, error)
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
private fun createEventListener(clientId: String, bridgeClient: BridgeClient) = object : P2pEventListener {
|
|
201
|
+
private fun sessionId(nativeSessionId: Long): String? = bridgeClient.nativeSessionIds[nativeSessionId]
|
|
202
|
+
|
|
203
|
+
private fun emit(nativeSessionId: Long, type: String, extras: WritableMap.() -> Unit = {}) {
|
|
204
|
+
val jsSessionId = sessionId(nativeSessionId)
|
|
205
|
+
if (jsSessionId == null) {
|
|
206
|
+
bufferPendingEvent(bridgeClient, nativeSessionId, PendingEvent(type, extras))
|
|
207
|
+
return
|
|
208
|
+
}
|
|
209
|
+
sendSessionEvent(clientId, jsSessionId, type, extras)
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
override fun onStatistics(sessionId: Long, stats: P2pStats) = emit(sessionId, "statistics") {
|
|
213
|
+
putMap("statistics", stats.toWritableMap())
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
override fun onPeerCountChanged(sessionId: Long, peerCount: Int) = emit(sessionId, "peerCountChanged") {
|
|
217
|
+
putInt("peerCount", peerCount)
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
override fun onPlaybackPrepared(sessionId: Long, playbackUrl: String) = emit(sessionId, "playbackPrepared") {
|
|
221
|
+
putString("playbackUrl", playbackUrl)
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
override fun onP2pReady(sessionId: Long) = emit(sessionId, "p2pReady")
|
|
225
|
+
|
|
226
|
+
override fun onP2pUnavailable(sessionId: Long, reason: P2pUnavailableReason) = emit(sessionId, "p2pUnavailable") {
|
|
227
|
+
putString("reason", reason.name)
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
override fun onTrackerError(sessionId: Long, totalErrors: Long) = emit(sessionId, "trackerError") {
|
|
231
|
+
putDouble("totalErrors", totalErrors.toDouble())
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
override fun onPeerConnectError(sessionId: Long, totalErrors: Long) = emit(sessionId, "peerConnectError") {
|
|
235
|
+
putDouble("totalErrors", totalErrors.toDouble())
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
override fun onCdnFallback(sessionId: Long, bytes: Long) = emit(sessionId, "cdnFallback") {
|
|
239
|
+
putDouble("bytes", bytes.toDouble())
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
private fun bufferPendingEvent(bridgeClient: BridgeClient, nativeSessionId: Long, event: PendingEvent) {
|
|
244
|
+
synchronized(bridgeClient.pendingEvents) {
|
|
245
|
+
val events = bridgeClient.pendingEvents[nativeSessionId]
|
|
246
|
+
if (events != null) {
|
|
247
|
+
if (events.size < MAX_PENDING_EVENTS_PER_SESSION) events += event
|
|
248
|
+
return
|
|
249
|
+
}
|
|
250
|
+
if (bridgeClient.pendingEvents.size >= MAX_PENDING_EVENT_SESSIONS) return
|
|
251
|
+
bridgeClient.pendingEvents[nativeSessionId] = mutableListOf(event)
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
private fun flushPendingEvents(
|
|
256
|
+
clientId: String,
|
|
257
|
+
jsSessionId: String,
|
|
258
|
+
session: PlaybackSession,
|
|
259
|
+
bridgeClient: BridgeClient,
|
|
260
|
+
) {
|
|
261
|
+
val events = synchronized(bridgeClient.pendingEvents) {
|
|
262
|
+
bridgeClient.pendingEvents.remove(session.sessionId)?.toList().orEmpty()
|
|
263
|
+
}
|
|
264
|
+
events.forEach { event -> sendSessionEvent(clientId, jsSessionId, event.type, event.extras) }
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
private fun sendSessionEvent(
|
|
268
|
+
clientId: String,
|
|
269
|
+
sessionId: String,
|
|
270
|
+
type: String,
|
|
271
|
+
extras: WritableMap.() -> Unit,
|
|
272
|
+
) {
|
|
273
|
+
sendEvent(Arguments.createMap().apply {
|
|
274
|
+
putString("type", type)
|
|
275
|
+
putString("clientId", clientId)
|
|
276
|
+
putString("sessionId", sessionId)
|
|
277
|
+
extras()
|
|
278
|
+
})
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
private fun sendEvent(event: WritableMap) {
|
|
282
|
+
if (reactApplicationContext.hasActiveReactInstance()) {
|
|
283
|
+
reactApplicationContext
|
|
284
|
+
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
285
|
+
.emit(EVENT_NAME, event)
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
private fun ConcurrentHashMap<String, BridgeClient>.requireClient(clientId: String): BridgeClient =
|
|
290
|
+
this[clientId] ?: error("Unknown or closed MeshStream client: $clientId")
|
|
291
|
+
|
|
292
|
+
private fun ConcurrentHashMap<String, PlaybackSession>.requireSession(sessionId: String): PlaybackSession =
|
|
293
|
+
this[sessionId] ?: error("Unknown or closed MeshStream playback session: $sessionId")
|
|
294
|
+
|
|
295
|
+
private fun ReadableMap.requiredString(name: String): String = optionalString(name)
|
|
296
|
+
?.takeIf { it.isNotBlank() }
|
|
297
|
+
?: error("$name must not be empty")
|
|
298
|
+
|
|
299
|
+
private fun ReadableMap.optionalString(name: String): String? =
|
|
300
|
+
if (hasKey(name) && !isNull(name) && getType(name) == ReadableType.String) getString(name)?.trim() else null
|
|
301
|
+
|
|
302
|
+
private fun ReadableMap.optionalBoolean(name: String): Boolean? =
|
|
303
|
+
if (hasKey(name) && !isNull(name) && getType(name) == ReadableType.Boolean) getBoolean(name) else null
|
|
304
|
+
|
|
305
|
+
private fun ReadableMap.stringMap(name: String): Map<String, String> {
|
|
306
|
+
if (!hasKey(name) || isNull(name) || getType(name) != ReadableType.Map) return emptyMap()
|
|
307
|
+
val result = linkedMapOf<String, String>()
|
|
308
|
+
val map = getMap(name) ?: return emptyMap()
|
|
309
|
+
val iterator: ReadableMapKeySetIterator = map.keySetIterator()
|
|
310
|
+
while (iterator.hasNextKey()) {
|
|
311
|
+
val key = iterator.nextKey()
|
|
312
|
+
if (map.getType(key) == ReadableType.String) map.getString(key)?.let { result[key] = it }
|
|
313
|
+
}
|
|
314
|
+
return result
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
private fun PlaybackSession.toWritableMap(clientId: String, sessionId: String): WritableMap = Arguments.createMap().apply {
|
|
318
|
+
putString("clientId", clientId)
|
|
319
|
+
putString("sessionId", sessionId)
|
|
320
|
+
putString("playbackUrl", playbackUrl)
|
|
321
|
+
putString("playbackMode", playbackMode.name)
|
|
322
|
+
fallbackReason?.let { putString("fallbackReason", it.name) }
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
private fun P2pSdkStatus.toWritableMap(): WritableMap = Arguments.createMap().apply {
|
|
326
|
+
putBoolean("accountEnabled", accountEnabled)
|
|
327
|
+
putBoolean("applicationEnabled", applicationEnabled)
|
|
328
|
+
putBoolean("keyEnabled", keyEnabled)
|
|
329
|
+
putBoolean("platformEnabled", platformEnabled)
|
|
330
|
+
putBoolean("effectiveP2pEnabled", effectiveP2pEnabled)
|
|
331
|
+
putString("reason", reason.name)
|
|
332
|
+
configVersion?.let { putString("configVersion", it) }
|
|
333
|
+
edgeRegion?.let { putString("edgeRegion", it) }
|
|
334
|
+
quotaStatus?.let { putString("quotaStatus", it) }
|
|
335
|
+
trackerPoolReady?.let { putBoolean("trackerPoolReady", it) }
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
private fun P2pStats.toWritableMap(): WritableMap = Arguments.createMap().apply {
|
|
339
|
+
putDouble("httpDownloaded", httpDownloaded.toDouble())
|
|
340
|
+
putDouble("p2pDownloaded", p2pDownloaded.toDouble())
|
|
341
|
+
putDouble("p2pUploaded", p2pUploaded.toDouble())
|
|
342
|
+
putDouble("httpSegmentDownloadSuccesses", httpSegmentDownloadSuccesses.toDouble())
|
|
343
|
+
putDouble("httpSegmentDownloadFailures", httpSegmentDownloadFailures.toDouble())
|
|
344
|
+
putDouble("p2pSegmentDownloadSuccesses", p2pSegmentDownloadSuccesses.toDouble())
|
|
345
|
+
putDouble("p2pSegmentDownloadFailures", p2pSegmentDownloadFailures.toDouble())
|
|
346
|
+
putDouble("p2pSegmentUploadSuccesses", p2pSegmentUploadSuccesses.toDouble())
|
|
347
|
+
putDouble("p2pSegmentUploadFailures", p2pSegmentUploadFailures.toDouble())
|
|
348
|
+
putInt("connectedPeers", connectedPeers)
|
|
349
|
+
putDouble("peerConnectErrors", peerConnectErrors.toDouble())
|
|
350
|
+
putDouble("trackerErrors", trackerErrors.toDouble())
|
|
351
|
+
putDouble("downloadOffloadRatio", downloadOffloadRatio)
|
|
352
|
+
putDouble("p2pSegmentDownloadSuccessRatio", p2pSegmentDownloadSuccessRatio)
|
|
353
|
+
putDouble("p2pSegmentUploadSuccessRatio", p2pSegmentUploadSuccessRatio)
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
private fun P2pDiagnosticsSnapshot.toWritableMap(): WritableMap = Arguments.createMap().apply {
|
|
357
|
+
putDouble("fallbackCount", fallbackCount.toDouble())
|
|
358
|
+
firstPeerConnectMs?.let { putDouble("firstPeerConnectMs", it.toDouble()) }
|
|
359
|
+
coldStart?.let {
|
|
360
|
+
putMap("coldStart", Arguments.createMap().apply {
|
|
361
|
+
putDouble("p2pDownloaded", it.p2pDownloaded.toDouble())
|
|
362
|
+
putDouble("httpDownloaded", it.httpDownloaded.toDouble())
|
|
363
|
+
})
|
|
364
|
+
}
|
|
365
|
+
lastTrackerError?.let { putString("lastTrackerError", it) }
|
|
366
|
+
lastPeerError?.let { putString("lastPeerError", it) }
|
|
367
|
+
lastErrorAtMs?.let { putDouble("lastErrorAtMs", it.toDouble()) }
|
|
368
|
+
putDouble("iceAttempts", iceAttempts.toDouble())
|
|
369
|
+
putDouble("iceSuccesses", iceSuccesses.toDouble())
|
|
370
|
+
putDouble("iceFailures", iceFailures.toDouble())
|
|
371
|
+
putDouble("iceDurationMs", iceDurationMs.toDouble())
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
companion object {
|
|
375
|
+
private const val MODULE_NAME = "MeshStreamP2P"
|
|
376
|
+
private const val EVENT_NAME = "MeshStreamP2P:event"
|
|
377
|
+
private const val MAX_PENDING_EVENT_SESSIONS = 64
|
|
378
|
+
private const val MAX_PENDING_EVENTS_PER_SESSION = 8
|
|
379
|
+
}
|
|
380
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
package net.meshstream.reactnative
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.ReactPackage
|
|
4
|
+
import com.facebook.react.bridge.NativeModule
|
|
5
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
|
+
import com.facebook.react.uimanager.ViewManager
|
|
7
|
+
|
|
8
|
+
class MeshStreamP2PPackage : ReactPackage {
|
|
9
|
+
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> =
|
|
10
|
+
listOf(MeshStreamP2PModule(reactContext))
|
|
11
|
+
|
|
12
|
+
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> = emptyList()
|
|
13
|
+
}
|