@abdurrahman-dev/react-native-ivs-broadcast 0.2.5 → 0.2.7
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/README.md +112 -4
- package/android/src/main/java/com/reactnativeivsbroadcast/IVSBroadcastModule.kt +394 -0
- package/ios/IVSBroadcastModule.m +554 -1
- package/lib/index.d.ts +75 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +199 -0
- package/lib/index.js.map +1 -1
- package/lib/types.d.ts +86 -1
- package/lib/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +233 -6
- package/src/types.ts +118 -1
package/README.md
CHANGED
|
@@ -4,6 +4,7 @@ React Native için Amazon IVS Broadcast SDK köprü paketi. Canlı yayın başla
|
|
|
4
4
|
|
|
5
5
|
## Özellikler
|
|
6
6
|
|
|
7
|
+
### Temel Özellikler
|
|
7
8
|
- ✅ Broadcast başlatma/durdurma
|
|
8
9
|
- ✅ Kamera önizleme component'i
|
|
9
10
|
- ✅ Kamera kontrolü (ön/arka değiştirme)
|
|
@@ -11,6 +12,17 @@ React Native için Amazon IVS Broadcast SDK köprü paketi. Canlı yayın başla
|
|
|
11
12
|
- ✅ Video ve ses konfigürasyonu
|
|
12
13
|
- ✅ Event-based API
|
|
13
14
|
|
|
15
|
+
### Gelişmiş Özellikler
|
|
16
|
+
- ✅ Kamera zoom kontrolü
|
|
17
|
+
- ✅ Flaş (torch) kontrolü
|
|
18
|
+
- ✅ Ses gain ayarı (0.0 - 2.0)
|
|
19
|
+
- ✅ Cihaz listeleme (kullanılabilir ve bağlı cihazlar)
|
|
20
|
+
- ✅ Kamera yetenekleri sorgulama
|
|
21
|
+
- ✅ Zamanlı metadata gönderme
|
|
22
|
+
- ✅ Gelişmiş istatistikler (transmission stats)
|
|
23
|
+
- ✅ Ses cihazı istatistikleri (peak, RMS)
|
|
24
|
+
- ✅ Picture-in-Picture (PiP) desteği (iOS 15+, Android 8.0+)
|
|
25
|
+
|
|
14
26
|
## Kurulum
|
|
15
27
|
|
|
16
28
|
```bash
|
|
@@ -223,12 +235,19 @@ await IVSBroadcast.destroySession(sessionId);
|
|
|
223
235
|
### Kamera Kontrolü
|
|
224
236
|
|
|
225
237
|
```typescript
|
|
226
|
-
// Kamera değiştir
|
|
238
|
+
// Kamera değiştir (front/back arasında)
|
|
227
239
|
await IVSBroadcast.switchCamera(sessionId);
|
|
228
240
|
|
|
229
241
|
// Kamera pozisyonu ayarla
|
|
230
242
|
await IVSBroadcast.setCameraPosition(sessionId, 'front');
|
|
231
243
|
await IVSBroadcast.setCameraPosition(sessionId, 'back');
|
|
244
|
+
|
|
245
|
+
// Kamera listesinden belirli bir kamerayı seç
|
|
246
|
+
const devices = await IVSBroadcast.listAvailableDevices();
|
|
247
|
+
const cameras = devices.filter(d => d.type === 'camera');
|
|
248
|
+
|
|
249
|
+
// İstediğiniz kamerayı seçin
|
|
250
|
+
await IVSBroadcast.selectCamera(sessionId, cameras[0].deviceId);
|
|
232
251
|
```
|
|
233
252
|
|
|
234
253
|
### Mikrofon Kontrolü
|
|
@@ -240,6 +259,58 @@ await IVSBroadcast.setMuted(sessionId, false);
|
|
|
240
259
|
|
|
241
260
|
// Mikrofon durumunu kontrol et
|
|
242
261
|
const isMuted = await IVSBroadcast.isMuted(sessionId);
|
|
262
|
+
|
|
263
|
+
// Ses gain ayarla (0.0 = sessiz, 1.0 = normal, 2.0 = amplify)
|
|
264
|
+
await IVSBroadcast.setAudioGain(sessionId, 1.5);
|
|
265
|
+
|
|
266
|
+
// Mevcut gain seviyesini al
|
|
267
|
+
const gain = await IVSBroadcast.getAudioGain(sessionId);
|
|
268
|
+
|
|
269
|
+
// Mikrofon listesinden belirli bir mikrofonu seç
|
|
270
|
+
const devices = await IVSBroadcast.listAvailableDevices();
|
|
271
|
+
const microphones = devices.filter(d => d.type === 'microphone');
|
|
272
|
+
|
|
273
|
+
// İstediğiniz mikrofonu seçin
|
|
274
|
+
await IVSBroadcast.selectMicrophone(sessionId, microphones[0].deviceId);
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### Gelişmiş Kamera Kontrolü
|
|
278
|
+
|
|
279
|
+
```typescript
|
|
280
|
+
// Zoom seviyesi ayarla
|
|
281
|
+
await IVSBroadcast.setCameraZoom(sessionId, 2.0);
|
|
282
|
+
|
|
283
|
+
// Flaşı aç/kapat
|
|
284
|
+
await IVSBroadcast.setTorchEnabled(sessionId, true);
|
|
285
|
+
|
|
286
|
+
// Kamera yeteneklerini al
|
|
287
|
+
const capabilities = await IVSBroadcast.getCameraCapabilities(sessionId);
|
|
288
|
+
console.log('Min zoom:', capabilities.minZoomFactor);
|
|
289
|
+
console.log('Max zoom:', capabilities.maxZoomFactor);
|
|
290
|
+
console.log('Torch supported:', capabilities.isTorchSupported);
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### Cihaz Yönetimi
|
|
294
|
+
|
|
295
|
+
```typescript
|
|
296
|
+
// Kullanılabilir cihazları listele
|
|
297
|
+
const availableDevices = await IVSBroadcast.listAvailableDevices();
|
|
298
|
+
availableDevices.forEach(device => {
|
|
299
|
+
console.log(`${device.type}: ${device.friendlyName}`);
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
// Bağlı cihazları listele
|
|
303
|
+
const attachedDevices = await IVSBroadcast.listAttachedDevices(sessionId);
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### Zamanlı Metadata
|
|
307
|
+
|
|
308
|
+
```typescript
|
|
309
|
+
// Yayına senkronize metadata gönder
|
|
310
|
+
await IVSBroadcast.sendTimedMetadata(sessionId, JSON.stringify({
|
|
311
|
+
timestamp: Date.now(),
|
|
312
|
+
customData: 'your data'
|
|
313
|
+
}));
|
|
243
314
|
```
|
|
244
315
|
|
|
245
316
|
### Event Listener'lar
|
|
@@ -260,7 +331,7 @@ IVSBroadcast.addListener('onNetworkHealth', (health) => {
|
|
|
260
331
|
console.log('Quality:', health.networkQuality);
|
|
261
332
|
});
|
|
262
333
|
|
|
263
|
-
// İstatistikler
|
|
334
|
+
// İstatistikler
|
|
264
335
|
IVSBroadcast.addListener('onAudioStats', (stats) => {
|
|
265
336
|
console.log('Audio bitrate:', stats.bitrate);
|
|
266
337
|
});
|
|
@@ -268,13 +339,50 @@ IVSBroadcast.addListener('onAudioStats', (stats) => {
|
|
|
268
339
|
IVSBroadcast.addListener('onVideoStats', (stats) => {
|
|
269
340
|
console.log('Video bitrate:', stats.bitrate, 'FPS:', stats.fps);
|
|
270
341
|
});
|
|
342
|
+
|
|
343
|
+
// Gelişmiş istatistikler (transmission stats)
|
|
344
|
+
IVSBroadcast.addListener('onTransmissionStatistics', (stats) => {
|
|
345
|
+
console.log('Measured bitrate:', stats.measuredBitrate);
|
|
346
|
+
console.log('Recommended bitrate:', stats.recommendedBitrate);
|
|
347
|
+
console.log('RTT:', stats.rtt);
|
|
348
|
+
console.log('Broadcast quality:', stats.broadcastQuality);
|
|
349
|
+
console.log('Network health:', stats.networkHealth);
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
// Ses cihazı istatistikleri
|
|
353
|
+
IVSBroadcast.addListener('onAudioDeviceStats', (stats) => {
|
|
354
|
+
console.log('Peak:', stats.peak, 'RMS:', stats.rms);
|
|
355
|
+
});
|
|
271
356
|
```
|
|
272
357
|
|
|
358
|
+
### Picture-in-Picture (PiP)
|
|
359
|
+
|
|
360
|
+
```typescript
|
|
361
|
+
// PiP desteğini kontrol et
|
|
362
|
+
const isSupported = await IVSBroadcast.isPictureInPictureSupported();
|
|
363
|
+
if (isSupported) {
|
|
364
|
+
// PiP modunu başlat
|
|
365
|
+
await IVSBroadcast.startPictureInPicture(sessionId);
|
|
366
|
+
|
|
367
|
+
// PiP durumunu kontrol et
|
|
368
|
+
const state = await IVSBroadcast.getPictureInPictureState(sessionId);
|
|
369
|
+
console.log('PiP state:', state); // 'idle', 'active', 'stopped'
|
|
370
|
+
|
|
371
|
+
// PiP modunu durdur
|
|
372
|
+
await IVSBroadcast.stopPictureInPicture(sessionId);
|
|
373
|
+
}
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
**Notlar:**
|
|
377
|
+
- **iOS**: PiP desteği iOS 15.0+ gerektirir
|
|
378
|
+
- **Android**: PiP desteği Android 8.0 (API 26)+ gerektirir
|
|
379
|
+
- Android'de PiP için `AndroidManifest.xml` dosyanıza `android:supportsPictureInPicture="true"` eklemeniz gerekebilir
|
|
380
|
+
|
|
273
381
|
## Gereksinimler
|
|
274
382
|
|
|
275
383
|
- React Native >= 0.60.0
|
|
276
|
-
- Android: minSdkVersion 21
|
|
277
|
-
- iOS: 11.0+
|
|
384
|
+
- Android: minSdkVersion 21 (PiP için API 26+)
|
|
385
|
+
- iOS: 11.0+ (PiP için iOS 15.0+)
|
|
278
386
|
|
|
279
387
|
## Lisans
|
|
280
388
|
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
package com.reactnativeivsbroadcast
|
|
2
2
|
|
|
3
3
|
import android.Manifest
|
|
4
|
+
import android.app.Activity
|
|
5
|
+
import android.app.PictureInPictureParams
|
|
4
6
|
import android.content.pm.PackageManager
|
|
7
|
+
import android.os.Build
|
|
8
|
+
import android.util.Rational
|
|
5
9
|
import androidx.core.content.ContextCompat
|
|
6
10
|
import com.amazonaws.ivs.broadcast.*
|
|
7
11
|
import com.facebook.react.bridge.*
|
|
@@ -245,6 +249,7 @@ class IVSBroadcastModule(reactContext: ReactApplicationContext) :
|
|
|
245
249
|
val url = sessionUrls[sessionId]
|
|
246
250
|
?: throw IllegalArgumentException("Session URL not found: $sessionId")
|
|
247
251
|
|
|
252
|
+
// Android SDK'da URL direkt URI olarak kullanılıyor, streamKey URL içinde
|
|
248
253
|
session.start(url)
|
|
249
254
|
promise.resolve(null)
|
|
250
255
|
} catch (e: Exception) {
|
|
@@ -362,6 +367,101 @@ class IVSBroadcastModule(reactContext: ReactApplicationContext) :
|
|
|
362
367
|
}
|
|
363
368
|
}
|
|
364
369
|
|
|
370
|
+
@ReactMethod
|
|
371
|
+
fun selectCamera(sessionId: String, deviceId: String, promise: Promise) {
|
|
372
|
+
try {
|
|
373
|
+
val session = sessions[sessionId]
|
|
374
|
+
?: throw IllegalArgumentException("Session not found: $sessionId")
|
|
375
|
+
|
|
376
|
+
// Tüm kullanılabilir cihazları listele
|
|
377
|
+
val availableDevices = session.listAvailableDevices()
|
|
378
|
+
val targetCamera = availableDevices.find {
|
|
379
|
+
it.type == Device.Descriptor.DeviceType.CAMERA &&
|
|
380
|
+
it.deviceId == deviceId
|
|
381
|
+
} ?: throw IllegalStateException("Camera with deviceId '$deviceId' not found")
|
|
382
|
+
|
|
383
|
+
// Mevcut kamerayı bul
|
|
384
|
+
val attachedDevices = session.listAttachedDevices()
|
|
385
|
+
val currentCamera = attachedDevices.find {
|
|
386
|
+
it.descriptor.type == Device.Descriptor.DeviceType.CAMERA
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Kamera pozisyonunu güncelle
|
|
390
|
+
val position = if (targetCamera.position == Device.Descriptor.Position.FRONT) "front" else "back"
|
|
391
|
+
|
|
392
|
+
if (currentCamera != null) {
|
|
393
|
+
// Mevcut kamerayı yeni kamerayla değiştir
|
|
394
|
+
session.exchangeDevices(currentCamera, targetCamera) { newDevice ->
|
|
395
|
+
if (newDevice != null) {
|
|
396
|
+
currentCameraPosition[sessionId] = position
|
|
397
|
+
// PreviewView'ları güncelle
|
|
398
|
+
notifyPreviewViews(sessionId)
|
|
399
|
+
promise.resolve(null)
|
|
400
|
+
} else {
|
|
401
|
+
promise.reject("SELECT_CAMERA_ERROR", "Failed to select camera")
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
} else {
|
|
405
|
+
// Kamera yoksa yeni kamerayı ekle
|
|
406
|
+
session.attachDevice(targetCamera) { device ->
|
|
407
|
+
if (device != null) {
|
|
408
|
+
currentCameraPosition[sessionId] = position
|
|
409
|
+
// PreviewView'ları güncelle
|
|
410
|
+
notifyPreviewViews(sessionId)
|
|
411
|
+
promise.resolve(null)
|
|
412
|
+
} else {
|
|
413
|
+
promise.reject("SELECT_CAMERA_ERROR", "Failed to attach camera")
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
} catch (e: Exception) {
|
|
418
|
+
promise.reject("SELECT_CAMERA_ERROR", e.message, e)
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
@ReactMethod
|
|
423
|
+
fun selectMicrophone(sessionId: String, deviceId: String, promise: Promise) {
|
|
424
|
+
try {
|
|
425
|
+
val session = sessions[sessionId]
|
|
426
|
+
?: throw IllegalArgumentException("Session not found: $sessionId")
|
|
427
|
+
|
|
428
|
+
// Tüm kullanılabilir cihazları listele
|
|
429
|
+
val availableDevices = session.listAvailableDevices()
|
|
430
|
+
val targetMicrophone = availableDevices.find {
|
|
431
|
+
it.type == Device.Descriptor.DeviceType.MICROPHONE &&
|
|
432
|
+
it.deviceId == deviceId
|
|
433
|
+
} ?: throw IllegalStateException("Microphone with deviceId '$deviceId' not found")
|
|
434
|
+
|
|
435
|
+
// Mevcut mikrofonu bul
|
|
436
|
+
val attachedDevices = session.listAttachedDevices()
|
|
437
|
+
val currentMicrophone = attachedDevices.find {
|
|
438
|
+
it.descriptor.type == Device.Descriptor.DeviceType.MICROPHONE
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
if (currentMicrophone != null) {
|
|
442
|
+
// Mevcut mikrofonu yeni mikrofonla değiştir
|
|
443
|
+
session.exchangeDevices(currentMicrophone, targetMicrophone) { newDevice ->
|
|
444
|
+
if (newDevice != null) {
|
|
445
|
+
promise.resolve(null)
|
|
446
|
+
} else {
|
|
447
|
+
promise.reject("SELECT_MICROPHONE_ERROR", "Failed to select microphone")
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
} else {
|
|
451
|
+
// Mikrofon yoksa yeni mikrofonu ekle
|
|
452
|
+
session.attachDevice(targetMicrophone) { device ->
|
|
453
|
+
if (device != null) {
|
|
454
|
+
promise.resolve(null)
|
|
455
|
+
} else {
|
|
456
|
+
promise.reject("SELECT_MICROPHONE_ERROR", "Failed to attach microphone")
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
} catch (e: Exception) {
|
|
461
|
+
promise.reject("SELECT_MICROPHONE_ERROR", e.message, e)
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
365
465
|
private fun switchToCamera(session: BroadcastSession, sessionId: String, position: String) {
|
|
366
466
|
val targetPosition = if (position == "front")
|
|
367
467
|
Device.Descriptor.Position.FRONT
|
|
@@ -384,12 +484,16 @@ class IVSBroadcastModule(reactContext: ReactApplicationContext) :
|
|
|
384
484
|
session.exchangeDevices(currentCamera, targetCamera) { newDevice ->
|
|
385
485
|
if (newDevice != null) {
|
|
386
486
|
currentCameraPosition[sessionId] = position
|
|
487
|
+
// PreviewView'ları güncelle
|
|
488
|
+
notifyPreviewViews(sessionId)
|
|
387
489
|
}
|
|
388
490
|
}
|
|
389
491
|
} else {
|
|
390
492
|
session.attachDevice(targetCamera) { device ->
|
|
391
493
|
if (device != null) {
|
|
392
494
|
currentCameraPosition[sessionId] = position
|
|
495
|
+
// PreviewView'ları güncelle
|
|
496
|
+
notifyPreviewViews(sessionId)
|
|
393
497
|
}
|
|
394
498
|
}
|
|
395
499
|
}
|
|
@@ -459,4 +563,294 @@ class IVSBroadcastModule(reactContext: ReactApplicationContext) :
|
|
|
459
563
|
// Event gönderimi başarısız olursa sessizce geç
|
|
460
564
|
}
|
|
461
565
|
}
|
|
566
|
+
|
|
567
|
+
// MARK: - Gelişmiş Özellikler
|
|
568
|
+
|
|
569
|
+
@ReactMethod
|
|
570
|
+
fun listAvailableDevices(promise: Promise) {
|
|
571
|
+
try {
|
|
572
|
+
val context = reactApplicationContext
|
|
573
|
+
val session = BroadcastSession(context, null, BroadcastConfiguration(), null)
|
|
574
|
+
val devices = session.listAvailableDevices()
|
|
575
|
+
|
|
576
|
+
val deviceArray = Arguments.createArray()
|
|
577
|
+
devices.forEach { device ->
|
|
578
|
+
val deviceMap = Arguments.createMap().apply {
|
|
579
|
+
putString("type", deviceTypeToString(device.type))
|
|
580
|
+
putString("position", devicePositionToString(device.position))
|
|
581
|
+
putString("deviceId", device.deviceId ?: "")
|
|
582
|
+
putString("friendlyName", device.friendlyName ?: "")
|
|
583
|
+
putBoolean("isDefault", device.isDefault)
|
|
584
|
+
}
|
|
585
|
+
deviceArray.pushMap(deviceMap)
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
session.release()
|
|
589
|
+
promise.resolve(deviceArray)
|
|
590
|
+
} catch (e: Exception) {
|
|
591
|
+
promise.reject("LIST_DEVICES_ERROR", e.message, e)
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
@ReactMethod
|
|
596
|
+
fun listAttachedDevices(sessionId: String, promise: Promise) {
|
|
597
|
+
try {
|
|
598
|
+
val session = sessions[sessionId]
|
|
599
|
+
?: throw IllegalArgumentException("Session not found: $sessionId")
|
|
600
|
+
|
|
601
|
+
val devices = session.listAttachedDevices()
|
|
602
|
+
val deviceArray = Arguments.createArray()
|
|
603
|
+
|
|
604
|
+
devices.forEach { device ->
|
|
605
|
+
val deviceMap = Arguments.createMap().apply {
|
|
606
|
+
putString("type", deviceTypeToString(device.descriptor.type))
|
|
607
|
+
putString("position", devicePositionToString(device.descriptor.position))
|
|
608
|
+
putString("deviceId", device.descriptor.deviceId ?: "")
|
|
609
|
+
putString("friendlyName", device.descriptor.friendlyName ?: "")
|
|
610
|
+
putBoolean("isDefault", device.descriptor.isDefault)
|
|
611
|
+
}
|
|
612
|
+
deviceArray.pushMap(deviceMap)
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
promise.resolve(deviceArray)
|
|
616
|
+
} catch (e: Exception) {
|
|
617
|
+
promise.reject("LIST_ATTACHED_DEVICES_ERROR", e.message, e)
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
@ReactMethod
|
|
622
|
+
fun setCameraZoom(sessionId: String, zoomFactor: Double, promise: Promise) {
|
|
623
|
+
try {
|
|
624
|
+
val session = sessions[sessionId]
|
|
625
|
+
?: throw IllegalArgumentException("Session not found: $sessionId")
|
|
626
|
+
|
|
627
|
+
val attachedDevices = session.listAttachedDevices()
|
|
628
|
+
val cameraDevice = attachedDevices.find {
|
|
629
|
+
it.descriptor.type == Device.Descriptor.DeviceType.CAMERA
|
|
630
|
+
} as? ImageDevice
|
|
631
|
+
|
|
632
|
+
if (cameraDevice != null) {
|
|
633
|
+
cameraDevice.setZoomFactor(zoomFactor.toFloat())
|
|
634
|
+
promise.resolve(null)
|
|
635
|
+
} else {
|
|
636
|
+
promise.reject("SET_ZOOM_ERROR", "Camera not found")
|
|
637
|
+
}
|
|
638
|
+
} catch (e: Exception) {
|
|
639
|
+
promise.reject("SET_ZOOM_ERROR", e.message, e)
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
@ReactMethod
|
|
644
|
+
fun setTorchEnabled(sessionId: String, enabled: Boolean, promise: Promise) {
|
|
645
|
+
try {
|
|
646
|
+
val session = sessions[sessionId]
|
|
647
|
+
?: throw IllegalArgumentException("Session not found: $sessionId")
|
|
648
|
+
|
|
649
|
+
val attachedDevices = session.listAttachedDevices()
|
|
650
|
+
val cameraDevice = attachedDevices.find {
|
|
651
|
+
it.descriptor.type == Device.Descriptor.DeviceType.CAMERA
|
|
652
|
+
} as? ImageDevice
|
|
653
|
+
|
|
654
|
+
if (cameraDevice != null) {
|
|
655
|
+
cameraDevice.setTorchEnabled(enabled)
|
|
656
|
+
promise.resolve(null)
|
|
657
|
+
} else {
|
|
658
|
+
promise.reject("SET_TORCH_ERROR", "Camera not found")
|
|
659
|
+
}
|
|
660
|
+
} catch (e: Exception) {
|
|
661
|
+
promise.reject("SET_TORCH_ERROR", e.message, e)
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
@ReactMethod
|
|
666
|
+
fun getCameraCapabilities(sessionId: String, promise: Promise) {
|
|
667
|
+
try {
|
|
668
|
+
val session = sessions[sessionId]
|
|
669
|
+
?: throw IllegalArgumentException("Session not found: $sessionId")
|
|
670
|
+
|
|
671
|
+
val attachedDevices = session.listAttachedDevices()
|
|
672
|
+
val cameraDevice = attachedDevices.find {
|
|
673
|
+
it.descriptor.type == Device.Descriptor.DeviceType.CAMERA
|
|
674
|
+
} as? ImageDevice
|
|
675
|
+
|
|
676
|
+
if (cameraDevice != null) {
|
|
677
|
+
val capabilities = Arguments.createMap().apply {
|
|
678
|
+
putDouble("minZoomFactor", cameraDevice.minZoomFactor.toDouble())
|
|
679
|
+
putDouble("maxZoomFactor", cameraDevice.maxZoomFactor.toDouble())
|
|
680
|
+
putBoolean("isTorchSupported", cameraDevice.isTorchSupported)
|
|
681
|
+
}
|
|
682
|
+
promise.resolve(capabilities)
|
|
683
|
+
} else {
|
|
684
|
+
promise.reject("GET_CAPABILITIES_ERROR", "Camera not found")
|
|
685
|
+
}
|
|
686
|
+
} catch (e: Exception) {
|
|
687
|
+
promise.reject("GET_CAPABILITIES_ERROR", e.message, e)
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
@ReactMethod
|
|
692
|
+
fun sendTimedMetadata(sessionId: String, metadata: String, promise: Promise) {
|
|
693
|
+
try {
|
|
694
|
+
val session = sessions[sessionId]
|
|
695
|
+
?: throw IllegalArgumentException("Session not found: $sessionId")
|
|
696
|
+
|
|
697
|
+
session.sendTimedMetadata(metadata)
|
|
698
|
+
promise.resolve(null)
|
|
699
|
+
} catch (e: Exception) {
|
|
700
|
+
promise.reject("SEND_METADATA_ERROR", e.message, e)
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
@ReactMethod
|
|
705
|
+
fun setAudioGain(sessionId: String, gain: Double, promise: Promise) {
|
|
706
|
+
try {
|
|
707
|
+
val session = sessions[sessionId]
|
|
708
|
+
?: throw IllegalArgumentException("Session not found: $sessionId")
|
|
709
|
+
|
|
710
|
+
val attachedDevices = session.listAttachedDevices()
|
|
711
|
+
val audioDevice = attachedDevices.find {
|
|
712
|
+
it.descriptor.type == Device.Descriptor.DeviceType.MICROPHONE
|
|
713
|
+
} as? AudioDevice
|
|
714
|
+
|
|
715
|
+
if (audioDevice != null) {
|
|
716
|
+
audioDevice.setGain(gain.toFloat())
|
|
717
|
+
promise.resolve(null)
|
|
718
|
+
} else {
|
|
719
|
+
promise.reject("SET_GAIN_ERROR", "Microphone not found")
|
|
720
|
+
}
|
|
721
|
+
} catch (e: Exception) {
|
|
722
|
+
promise.reject("SET_GAIN_ERROR", e.message, e)
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
@ReactMethod
|
|
727
|
+
fun getAudioGain(sessionId: String, promise: Promise) {
|
|
728
|
+
try {
|
|
729
|
+
val session = sessions[sessionId]
|
|
730
|
+
?: throw IllegalArgumentException("Session not found: $sessionId")
|
|
731
|
+
|
|
732
|
+
val attachedDevices = session.listAttachedDevices()
|
|
733
|
+
val audioDevice = attachedDevices.find {
|
|
734
|
+
it.descriptor.type == Device.Descriptor.DeviceType.MICROPHONE
|
|
735
|
+
} as? AudioDevice
|
|
736
|
+
|
|
737
|
+
if (audioDevice != null) {
|
|
738
|
+
promise.resolve(audioDevice.gain.toDouble())
|
|
739
|
+
} else {
|
|
740
|
+
promise.reject("GET_GAIN_ERROR", "Microphone not found")
|
|
741
|
+
}
|
|
742
|
+
} catch (e: Exception) {
|
|
743
|
+
promise.reject("GET_GAIN_ERROR", e.message, e)
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
// Helper methods
|
|
748
|
+
private fun deviceTypeToString(type: Device.Descriptor.DeviceType): String {
|
|
749
|
+
return when (type) {
|
|
750
|
+
Device.Descriptor.DeviceType.CAMERA -> "camera"
|
|
751
|
+
Device.Descriptor.DeviceType.MICROPHONE -> "microphone"
|
|
752
|
+
Device.Descriptor.DeviceType.USER_AUDIO -> "userAudio"
|
|
753
|
+
Device.Descriptor.DeviceType.USER_VIDEO -> "userVideo"
|
|
754
|
+
Device.Descriptor.DeviceType.USER_IMAGE -> "userVideo" // Android'de USER_IMAGE de USER_VIDEO olarak map ediyoruz
|
|
755
|
+
else -> "unknown"
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
private fun devicePositionToString(position: Device.Descriptor.Position): String {
|
|
760
|
+
return when (position) {
|
|
761
|
+
Device.Descriptor.Position.FRONT -> "front"
|
|
762
|
+
Device.Descriptor.Position.BACK -> "back"
|
|
763
|
+
else -> "unknown"
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
// MARK: - Picture-in-Picture
|
|
768
|
+
|
|
769
|
+
@ReactMethod
|
|
770
|
+
fun isPictureInPictureSupported(promise: Promise) {
|
|
771
|
+
try {
|
|
772
|
+
val supported = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
|
|
773
|
+
promise.resolve(supported)
|
|
774
|
+
} catch (e: Exception) {
|
|
775
|
+
promise.resolve(false)
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
@ReactMethod
|
|
780
|
+
fun startPictureInPicture(sessionId: String, promise: Promise) {
|
|
781
|
+
try {
|
|
782
|
+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
|
|
783
|
+
promise.reject("START_PIP_ERROR", "Picture-in-Picture requires Android 8.0 (API 26) or later")
|
|
784
|
+
return
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
val activity = reactApplicationContext.currentActivity
|
|
788
|
+
if (activity == null) {
|
|
789
|
+
promise.reject("START_PIP_ERROR", "Activity not found")
|
|
790
|
+
return
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
// PiP parametrelerini ayarla (16:9 aspect ratio)
|
|
794
|
+
val aspectRatio = Rational(16, 9)
|
|
795
|
+
val pipParams = PictureInPictureParams.Builder()
|
|
796
|
+
.setAspectRatio(aspectRatio)
|
|
797
|
+
.build()
|
|
798
|
+
|
|
799
|
+
activity.enterPictureInPictureMode(pipParams)
|
|
800
|
+
promise.resolve(null)
|
|
801
|
+
} catch (e: Exception) {
|
|
802
|
+
promise.reject("START_PIP_ERROR", e.message, e)
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
@ReactMethod
|
|
807
|
+
fun stopPictureInPicture(sessionId: String, promise: Promise) {
|
|
808
|
+
try {
|
|
809
|
+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
|
|
810
|
+
promise.reject("STOP_PIP_ERROR", "Picture-in-Picture requires Android 8.0 (API 26) or later")
|
|
811
|
+
return
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
val activity = reactApplicationContext.currentActivity
|
|
815
|
+
if (activity == null) {
|
|
816
|
+
promise.reject("STOP_PIP_ERROR", "Activity not found")
|
|
817
|
+
return
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
// Android'de PiP'den çıkmak için Activity'yi normal moda döndürmek gerekir
|
|
821
|
+
// Bu genellikle kullanıcı tarafından yapılır, ama programatik olarak da yapılabilir
|
|
822
|
+
if (activity.isInPictureInPictureMode) {
|
|
823
|
+
// Activity'yi normal moda döndür
|
|
824
|
+
activity.moveTaskToBack(true)
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
promise.resolve(null)
|
|
828
|
+
} catch (e: Exception) {
|
|
829
|
+
promise.reject("STOP_PIP_ERROR", e.message, e)
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
@ReactMethod
|
|
834
|
+
fun getPictureInPictureState(sessionId: String, promise: Promise) {
|
|
835
|
+
try {
|
|
836
|
+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
|
|
837
|
+
promise.resolve("unsupported")
|
|
838
|
+
return
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
val activity = reactApplicationContext.currentActivity
|
|
842
|
+
if (activity == null) {
|
|
843
|
+
promise.resolve("idle")
|
|
844
|
+
return
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
if (activity.isInPictureInPictureMode) {
|
|
848
|
+
promise.resolve("active")
|
|
849
|
+
} else {
|
|
850
|
+
promise.resolve("idle")
|
|
851
|
+
}
|
|
852
|
+
} catch (e: Exception) {
|
|
853
|
+
promise.reject("GET_PIP_STATE_ERROR", e.message, e)
|
|
854
|
+
}
|
|
855
|
+
}
|
|
462
856
|
}
|