@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 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 (iOS)
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
  }