@abdurrahman-dev/react-native-ivs-broadcast 0.1.0

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.
@@ -0,0 +1,433 @@
1
+ package com.reactnativeivsbroadcast
2
+
3
+ import android.app.Activity
4
+ import android.content.Context
5
+ import android.view.View
6
+ import com.amazonaws.ivs.broadcast.BroadcastConfiguration
7
+ import com.amazonaws.ivs.broadcast.BroadcastSession
8
+ import com.amazonaws.ivs.broadcast.BroadcastState
9
+ import com.amazonaws.ivs.broadcast.Device
10
+ import com.amazonaws.ivs.broadcast.DeviceDescriptor
11
+ import com.amazonaws.ivs.broadcast.ImageDevice
12
+ import com.amazonaws.ivs.broadcast.MicrophoneDevice
13
+ import com.facebook.react.bridge.*
14
+ import com.facebook.react.modules.core.DeviceEventManagerModule
15
+ import java.net.URI
16
+ import java.util.concurrent.ConcurrentHashMap
17
+
18
+ class IVSBroadcastModule(reactContext: ReactApplicationContext) :
19
+ ReactContextBaseJavaModule(reactContext) {
20
+
21
+ private val sessions = ConcurrentHashMap<String, BroadcastSession>()
22
+ private val sessionUrls = ConcurrentHashMap<String, URI>()
23
+ private val reactContext = reactContext
24
+
25
+ override fun getName(): String {
26
+ return "IVSBroadcastModule"
27
+ }
28
+
29
+ @ReactMethod
30
+ fun createSession(config: ReadableMap, promise: Promise) {
31
+ try {
32
+ val rtmpUrl = config.getString("rtmpUrl")
33
+ ?: throw IllegalArgumentException("rtmpUrl is required")
34
+
35
+ val streamKey = config.getString("streamKey")
36
+
37
+ val fullUrl = if (streamKey != null) {
38
+ "$rtmpUrl/$streamKey"
39
+ } else {
40
+ rtmpUrl
41
+ }
42
+
43
+ val broadcastConfig = BroadcastConfiguration()
44
+
45
+ // Video config
46
+ val videoConfig = config.getMap("videoConfig")
47
+ if (videoConfig != null) {
48
+ broadcastConfig.videoConfig.apply {
49
+ videoConfig.getInt("width")?.let { width = it }
50
+ videoConfig.getInt("height")?.let { height = it }
51
+ videoConfig.getInt("bitrate")?.let { bitrate = it }
52
+ videoConfig.getInt("fps")?.let { targetFps = it }
53
+ videoConfig.getInt("targetFps")?.let { targetFps = it }
54
+ videoConfig.getInt("keyframeInterval")?.let { keyframeInterval = it }
55
+ }
56
+ }
57
+
58
+ // Audio config
59
+ val audioConfig = config.getMap("audioConfig")
60
+ if (audioConfig != null) {
61
+ broadcastConfig.audioConfig.apply {
62
+ audioConfig.getInt("bitrate")?.let { bitrate = it }
63
+ audioConfig.getInt("sampleRate")?.let { sampleRate = it }
64
+ audioConfig.getInt("channels")?.let { channels = it }
65
+ }
66
+ }
67
+
68
+ val sessionId = java.util.UUID.randomUUID().toString()
69
+
70
+ // Listener'ı sessionId ile birlikte oluştur
71
+ val listener = object : BroadcastSession.Listener() {
72
+ override fun onStateChanged(state: BroadcastState.State) {
73
+ val eventMap = createStateMap(state)
74
+ eventMap.putString("sessionId", sessionId)
75
+ sendEvent("onStateChanged", eventMap)
76
+ }
77
+
78
+ override fun onError(error: Exception) {
79
+ val eventMap = createErrorMap(error)
80
+ eventMap.putString("sessionId", sessionId)
81
+ sendEvent("onError", eventMap)
82
+ }
83
+
84
+ override fun onNetworkHealth(health: BroadcastSession.NetworkHealth) {
85
+ val eventMap = createNetworkHealthMap(health)
86
+ eventMap.putString("sessionId", sessionId)
87
+ sendEvent("onNetworkHealth", eventMap)
88
+ }
89
+
90
+ override fun onAudioStats(stats: BroadcastSession.AudioStats) {
91
+ val eventMap = createAudioStatsMap(stats)
92
+ eventMap.putString("sessionId", sessionId)
93
+ sendEvent("onAudioStats", eventMap)
94
+ }
95
+
96
+ override fun onVideoStats(stats: BroadcastSession.VideoStats) {
97
+ val eventMap = createVideoStatsMap(stats)
98
+ eventMap.putString("sessionId", sessionId)
99
+ sendEvent("onVideoStats", eventMap)
100
+ }
101
+ }
102
+
103
+ val session = BroadcastSession(
104
+ reactContext.applicationContext,
105
+ broadcastConfig,
106
+ listener
107
+ )
108
+
109
+ sessions[sessionId] = session
110
+ sessionUrls[sessionId] = URI.create(fullUrl)
111
+
112
+ promise.resolve(sessionId)
113
+ } catch (e: Exception) {
114
+ promise.reject("CREATE_SESSION_ERROR", e.message, e)
115
+ }
116
+ }
117
+
118
+ @ReactMethod
119
+ fun startBroadcast(sessionId: String, promise: Promise) {
120
+ try {
121
+ val session = sessions[sessionId]
122
+ ?: throw IllegalArgumentException("Session not found: $sessionId")
123
+
124
+ val url = sessionUrls[sessionId]
125
+ ?: throw IllegalArgumentException("Session URL not found: $sessionId")
126
+
127
+ val devices = getDevices(sessionId)
128
+ session.start(url, devices)
129
+
130
+ promise.resolve(null)
131
+ } catch (e: Exception) {
132
+ promise.reject("START_BROADCAST_ERROR", e.message, e)
133
+ }
134
+ }
135
+
136
+ @ReactMethod
137
+ fun stopBroadcast(sessionId: String, promise: Promise) {
138
+ try {
139
+ val session = sessions[sessionId]
140
+ ?: throw IllegalArgumentException("Session not found: $sessionId")
141
+
142
+ session.stop()
143
+
144
+ promise.resolve(null)
145
+ } catch (e: Exception) {
146
+ promise.reject("STOP_BROADCAST_ERROR", e.message, e)
147
+ }
148
+ }
149
+
150
+ @ReactMethod
151
+ fun pauseBroadcast(sessionId: String, promise: Promise) {
152
+ try {
153
+ val session = sessions[sessionId]
154
+ ?: throw IllegalArgumentException("Session not found: $sessionId")
155
+
156
+ session.pause()
157
+
158
+ promise.resolve(null)
159
+ } catch (e: Exception) {
160
+ promise.reject("PAUSE_BROADCAST_ERROR", e.message, e)
161
+ }
162
+ }
163
+
164
+ @ReactMethod
165
+ fun resumeBroadcast(sessionId: String, promise: Promise) {
166
+ try {
167
+ val session = sessions[sessionId]
168
+ ?: throw IllegalArgumentException("Session not found: $sessionId")
169
+
170
+ session.resume()
171
+
172
+ promise.resolve(null)
173
+ } catch (e: Exception) {
174
+ promise.reject("RESUME_BROADCAST_ERROR", e.message, e)
175
+ }
176
+ }
177
+
178
+ @ReactMethod
179
+ fun destroySession(sessionId: String, promise: Promise) {
180
+ try {
181
+ val session = sessions.remove(sessionId)
182
+ ?: throw IllegalArgumentException("Session not found: $sessionId")
183
+
184
+ sessionUrls.remove(sessionId)
185
+ session.stop()
186
+ session.release()
187
+
188
+ promise.resolve(null)
189
+ } catch (e: Exception) {
190
+ promise.reject("DESTROY_SESSION_ERROR", e.message, e)
191
+ }
192
+ }
193
+
194
+ @ReactMethod
195
+ fun getState(sessionId: String, promise: Promise) {
196
+ try {
197
+ val session = sessions[sessionId]
198
+ ?: throw IllegalArgumentException("Session not found: $sessionId")
199
+
200
+ val state = session.state
201
+ promise.resolve(createStateMap(state))
202
+ } catch (e: Exception) {
203
+ promise.reject("GET_STATE_ERROR", e.message, e)
204
+ }
205
+ }
206
+
207
+ @ReactMethod
208
+ fun switchCamera(sessionId: String, promise: Promise) {
209
+ try {
210
+ val session = sessions[sessionId]
211
+ ?: throw IllegalArgumentException("Session not found: $sessionId")
212
+
213
+ val availableCameras = session.listAvailableDevices(DeviceDescriptor.DeviceType.CAMERA)
214
+ if (availableCameras.isEmpty()) {
215
+ throw IllegalStateException("No camera devices available")
216
+ }
217
+
218
+ val activeDevices = session.listActiveDevices()
219
+ val currentCamera = activeDevices.firstOrNull {
220
+ it.descriptor.type == DeviceDescriptor.DeviceType.CAMERA
221
+ }
222
+
223
+ val newCamera = availableCameras.firstOrNull {
224
+ it.descriptor.uid != currentCamera?.descriptor?.uid
225
+ } ?: availableCameras.first()
226
+
227
+ if (currentCamera != null) {
228
+ session.replaceDevice(currentCamera, newCamera)
229
+ } else {
230
+ session.addDevice(newCamera)
231
+ }
232
+
233
+ promise.resolve(null)
234
+ } catch (e: Exception) {
235
+ promise.reject("SWITCH_CAMERA_ERROR", e.message, e)
236
+ }
237
+ }
238
+
239
+ @ReactMethod
240
+ fun setCameraPosition(sessionId: String, position: String, promise: Promise) {
241
+ try {
242
+ val session = sessions[sessionId]
243
+ ?: throw IllegalArgumentException("Session not found: $sessionId")
244
+
245
+ val cameraType = when (position) {
246
+ "front" -> DeviceDescriptor.DeviceType.CAMERA_FRONT
247
+ "back" -> DeviceDescriptor.DeviceType.CAMERA_BACK
248
+ else -> throw IllegalArgumentException("Invalid camera position: $position")
249
+ }
250
+
251
+ val cameraDevices = session.listAvailableDevices(cameraType)
252
+ if (cameraDevices.isEmpty()) {
253
+ throw IllegalStateException("Camera device not available: $position")
254
+ }
255
+
256
+ val currentCamera = session.listActiveDevices()
257
+ .firstOrNull { it.descriptor.type == DeviceDescriptor.DeviceType.CAMERA }
258
+
259
+ val newCamera = cameraDevices.first()
260
+
261
+ if (currentCamera != null) {
262
+ session.replaceDevice(currentCamera, newCamera)
263
+ } else {
264
+ session.addDevice(newCamera)
265
+ }
266
+
267
+ promise.resolve(null)
268
+ } catch (e: Exception) {
269
+ promise.reject("SET_CAMERA_POSITION_ERROR", e.message, e)
270
+ }
271
+ }
272
+
273
+ @ReactMethod
274
+ fun setMuted(sessionId: String, muted: Boolean, promise: Promise) {
275
+ try {
276
+ val session = sessions[sessionId]
277
+ ?: throw IllegalArgumentException("Session not found: $sessionId")
278
+
279
+ val microphone = session.listActiveDevices()
280
+ .firstOrNull { it.descriptor.type == DeviceDescriptor.DeviceType.MICROPHONE }
281
+ as? MicrophoneDevice
282
+
283
+ microphone?.setMuted(muted)
284
+
285
+ promise.resolve(null)
286
+ } catch (e: Exception) {
287
+ promise.reject("SET_MUTED_ERROR", e.message, e)
288
+ }
289
+ }
290
+
291
+ @ReactMethod
292
+ fun isMuted(sessionId: String, promise: Promise) {
293
+ try {
294
+ val session = sessions[sessionId]
295
+ ?: throw IllegalArgumentException("Session not found: $sessionId")
296
+
297
+ val microphone = session.listActiveDevices()
298
+ .firstOrNull { it.descriptor.type == DeviceDescriptor.DeviceType.MICROPHONE }
299
+ as? MicrophoneDevice
300
+
301
+ promise.resolve(microphone?.isMuted ?: false)
302
+ } catch (e: Exception) {
303
+ promise.reject("IS_MUTED_ERROR", e.message, e)
304
+ }
305
+ }
306
+
307
+ @ReactMethod
308
+ fun updateVideoConfig(sessionId: String, config: ReadableMap, promise: Promise) {
309
+ try {
310
+ val session = sessions[sessionId]
311
+ ?: throw IllegalArgumentException("Session not found: $sessionId")
312
+
313
+ val videoConfig = session.configuration.videoConfig
314
+ config.getInt("width")?.let { videoConfig.width = it }
315
+ config.getInt("height")?.let { videoConfig.height = it }
316
+ config.getInt("bitrate")?.let { videoConfig.bitrate = it }
317
+ config.getInt("fps")?.let { videoConfig.targetFps = it }
318
+ config.getInt("targetFps")?.let { videoConfig.targetFps = it }
319
+ config.getInt("keyframeInterval")?.let { videoConfig.keyframeInterval = it }
320
+
321
+ promise.resolve(null)
322
+ } catch (e: Exception) {
323
+ promise.reject("UPDATE_VIDEO_CONFIG_ERROR", e.message, e)
324
+ }
325
+ }
326
+
327
+ @ReactMethod
328
+ fun updateAudioConfig(sessionId: String, config: ReadableMap, promise: Promise) {
329
+ try {
330
+ val session = sessions[sessionId]
331
+ ?: throw IllegalArgumentException("Session not found: $sessionId")
332
+
333
+ val audioConfig = session.configuration.audioConfig
334
+ config.getInt("bitrate")?.let { audioConfig.bitrate = it }
335
+ config.getInt("sampleRate")?.let { audioConfig.sampleRate = it }
336
+ config.getInt("channels")?.let { audioConfig.channels = it }
337
+
338
+ promise.resolve(null)
339
+ } catch (e: Exception) {
340
+ promise.reject("UPDATE_AUDIO_CONFIG_ERROR", e.message, e)
341
+ }
342
+ }
343
+
344
+ private fun getDevices(sessionId: String): List<Device> {
345
+ val session = sessions[sessionId]
346
+ ?: throw IllegalArgumentException("Session not found: $sessionId")
347
+
348
+ val devices = mutableListOf<Device>()
349
+
350
+ // Kamera ekle (öncelikle arka kamera)
351
+ val backCameraDescriptors = session.listAvailableDevices(DeviceDescriptor.DeviceType.CAMERA_BACK)
352
+ val frontCameraDescriptors = session.listAvailableDevices(DeviceDescriptor.DeviceType.CAMERA_FRONT)
353
+ val anyCameraDescriptors = session.listAvailableDevices(DeviceDescriptor.DeviceType.CAMERA)
354
+
355
+ val cameraDescriptor = when {
356
+ backCameraDescriptors.isNotEmpty() -> backCameraDescriptors.first()
357
+ frontCameraDescriptors.isNotEmpty() -> frontCameraDescriptors.first()
358
+ anyCameraDescriptors.isNotEmpty() -> anyCameraDescriptors.first()
359
+ else -> null
360
+ }
361
+
362
+ if (cameraDescriptor != null) {
363
+ val cameraDevice = session.addDevice(cameraDescriptor)
364
+ if (cameraDevice != null) {
365
+ devices.add(cameraDevice)
366
+ }
367
+ }
368
+
369
+ // Mikrofon ekle
370
+ val microphoneDescriptors = session.listAvailableDevices(DeviceDescriptor.DeviceType.MICROPHONE)
371
+ if (microphoneDescriptors.isNotEmpty()) {
372
+ val microphoneDevice = session.addDevice(microphoneDescriptors.first())
373
+ if (microphoneDevice != null) {
374
+ devices.add(microphoneDevice)
375
+ }
376
+ }
377
+
378
+ return devices
379
+ }
380
+
381
+ private fun sendEvent(eventName: String, params: WritableMap?) {
382
+ reactContext
383
+ .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
384
+ .emit(eventName, params)
385
+ }
386
+
387
+ private fun createStateMap(state: BroadcastState.State): WritableMap {
388
+ val map = Arguments.createMap()
389
+ map.putBoolean("isBroadcasting", state == BroadcastState.State.CONNECTED)
390
+ map.putBoolean("isPaused", state == BroadcastState.State.PAUSED)
391
+ return map
392
+ }
393
+
394
+ private fun createErrorMap(error: Exception): WritableMap {
395
+ val map = Arguments.createMap()
396
+ map.putString("message", error.message ?: "Unknown error")
397
+ map.putString("code", error.javaClass.simpleName)
398
+ return map
399
+ }
400
+
401
+ private fun createNetworkHealthMap(health: BroadcastSession.NetworkHealth): WritableMap {
402
+ val map = Arguments.createMap()
403
+ val quality = when (health.networkQuality) {
404
+ BroadcastSession.NetworkHealth.Quality.EXCELLENT -> "excellent"
405
+ BroadcastSession.NetworkHealth.Quality.GOOD -> "good"
406
+ BroadcastSession.NetworkHealth.Quality.FAIR -> "fair"
407
+ BroadcastSession.NetworkHealth.Quality.POOR -> "poor"
408
+ else -> "unknown"
409
+ }
410
+ map.putString("networkQuality", quality)
411
+ map.putDouble("uplinkBandwidth", health.uplinkBandwidth.toDouble())
412
+ map.putDouble("rtt", health.rtt.toDouble())
413
+ return map
414
+ }
415
+
416
+ private fun createAudioStatsMap(stats: BroadcastSession.AudioStats): WritableMap {
417
+ val map = Arguments.createMap()
418
+ map.putDouble("bitrate", stats.bitrate.toDouble())
419
+ map.putDouble("sampleRate", stats.sampleRate.toDouble())
420
+ map.putInt("channels", stats.channels)
421
+ return map
422
+ }
423
+
424
+ private fun createVideoStatsMap(stats: BroadcastSession.VideoStats): WritableMap {
425
+ val map = Arguments.createMap()
426
+ map.putDouble("bitrate", stats.bitrate.toDouble())
427
+ map.putDouble("fps", stats.fps.toDouble())
428
+ map.putInt("width", stats.width)
429
+ map.putInt("height", stats.height)
430
+ return map
431
+ }
432
+ }
433
+
@@ -0,0 +1,17 @@
1
+ package com.reactnativeivsbroadcast
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 IVSBroadcastPackage : ReactPackage {
9
+ override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
10
+ return listOf(IVSBroadcastModule(reactContext))
11
+ }
12
+
13
+ override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
14
+ return emptyList()
15
+ }
16
+ }
17
+
@@ -0,0 +1,24 @@
1
+ require "json"
2
+
3
+ package = JSON.parse(File.read(File.join(__dir__, "../package.json")))
4
+
5
+ Pod::Spec.new do |s|
6
+ s.name = "IVSBroadcast"
7
+ s.version = package["version"]
8
+ s.summary = package["description"]
9
+ s.description = <<-DESC
10
+ React Native bridge for Amazon IVS Broadcast SDK
11
+ DESC
12
+ s.homepage = "https://github.com/yourusername/react-native-ivs-broadcast"
13
+ s.license = "MIT"
14
+ s.author = { "author" => "author@example.com" }
15
+ s.platforms = { :ios => "11.0" }
16
+ s.source = { :git => "https://github.com/yourusername/react-native-ivs-broadcast.git", :tag => "#{s.version}" }
17
+
18
+ s.source_files = "ios/**/*.{h,m,mm,swift}"
19
+ s.requires_arc = true
20
+
21
+ s.dependency "React-Core"
22
+ s.dependency "AmazonIVSBroadcast", "1.37.0"
23
+ end
24
+
@@ -0,0 +1,7 @@
1
+ #import <React/RCTBridgeModule.h>
2
+ #import <React/RCTEventEmitter.h>
3
+
4
+ @interface IVSBroadcastModule : RCTEventEmitter <RCTBridgeModule>
5
+
6
+ @end
7
+