@100mslive/react-native-hms 1.0.0 → 1.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.
Files changed (36) hide show
  1. package/README.md +44 -29
  2. package/android/build.gradle +5 -1
  3. package/android/src/main/java/com/reactnativehmssdk/HMSDecoder.kt +23 -23
  4. package/android/src/main/java/com/reactnativehmssdk/HMSManager.kt +289 -32
  5. package/android/src/main/java/com/reactnativehmssdk/HMSRNSDK.kt +649 -548
  6. package/android/src/main/java/com/reactnativehmssdk/PipActionReceiver.kt +66 -0
  7. package/android/src/main/res/drawable/ic_call_end_24.xml +10 -0
  8. package/android/src/main/res/drawable/ic_camera_toggle_off.xml +11 -0
  9. package/android/src/main/res/drawable/ic_camera_toggle_on.xml +10 -0
  10. package/android/src/main/res/drawable/ic_mic_24.xml +10 -0
  11. package/android/src/main/res/drawable/ic_mic_off_24.xml +10 -0
  12. package/ios/HMSDecoder.swift +3 -5
  13. package/ios/HMSHelper.swift +18 -12
  14. package/ios/HMSManager.m +2 -0
  15. package/ios/HMSManager.swift +15 -0
  16. package/ios/HMSRNSDK.swift +36 -0
  17. package/lib/commonjs/classes/HMSPIPListenerActions.js +12 -0
  18. package/lib/commonjs/classes/HMSPIPListenerActions.js.map +1 -0
  19. package/lib/commonjs/classes/HMSSDK.js +60 -7
  20. package/lib/commonjs/classes/HMSSDK.js.map +1 -1
  21. package/lib/commonjs/index.js +12 -0
  22. package/lib/commonjs/index.js.map +1 -1
  23. package/lib/module/classes/HMSPIPListenerActions.js +5 -0
  24. package/lib/module/classes/HMSPIPListenerActions.js.map +1 -0
  25. package/lib/module/classes/HMSSDK.js +60 -7
  26. package/lib/module/classes/HMSSDK.js.map +1 -1
  27. package/lib/module/index.js +1 -0
  28. package/lib/module/index.js.map +1 -1
  29. package/lib/typescript/classes/HMSPIPListenerActions.d.ts +3 -0
  30. package/lib/typescript/classes/HMSSDK.d.ts +41 -2
  31. package/lib/typescript/index.d.ts +1 -0
  32. package/package.json +1 -1
  33. package/react-native-hms.podspec +2 -1
  34. package/src/classes/HMSPIPListenerActions.ts +3 -0
  35. package/src/classes/HMSSDK.tsx +111 -7
  36. package/src/index.ts +1 -0
package/README.md CHANGED
@@ -296,19 +296,32 @@ await hmsInstance?.sendDirectMessage(message, peer);
296
296
 
297
297
  ## [Role Change](https://www.100ms.live/docs/react-native/v2/features/change-role)
298
298
 
299
+ Single Peer Role Change: Change the Role of a single peer to a specified one using the `changeRoleOfPeer` API
300
+
299
301
  ```js
300
- import { HMSRole, HMSRemotePeer } from '@100mslive/react-native-hms';
301
- // hms instance acquired by build method
302
- const roles: HMSRole[] = hmsInstance?.knownRoles;
303
- const newRole: HMSRole = roles[0];
302
+ const force = false
303
+
304
+ // instance acquired from build() method
305
+ await hmsInstance.changeRoleOfPeer(peer, newRole, force) // request role change, not forced
306
+ .then(d => console.log('Change Role Success: ', d))
307
+ .catch(e => console.log('Change Role Error: ', e));
308
+ ```
309
+
310
+
311
+ Bulk Role Change: Change the role of all peers with a certain Role, to a specified one using the `changeRoleOfPeersWithRoles` API
312
+
313
+ ```js
314
+ // fetch all available Roles in the room
315
+ const roles = await hmsInstance.getRoles();
304
316
 
305
- // can any remote peer
306
- const peer: HMSRemotePeer = hmsInstance?.remotePeers[0];
317
+ // get the Host Role object
318
+ const hostRole = roles.find(role => role.name === 'host');
307
319
 
308
- const force = false;
320
+ // get list of Roles to be updated - in this case "Waiting" and "Guest" Roles
321
+ const rolesToChange = roles.filter(role => role.name === 'waiting' || role.name === 'guest');
309
322
 
310
- await hmsInstance.changeRole(peer, newRole, force); // request role change
311
- await hmsInstance.changeRole(peer, newRole, !force); // force role change
323
+ // now perform Role Change of all peers in "Waiting" and "Guest" Roles to the "Host" Role
324
+ await hmsInstance.changeRoleOfPeersWithRoles(rolesToChange, hostRole);
312
325
  ```
313
326
 
314
327
  ## [Raise Hand & BRB](https://www.100ms.live/docs/react-native/v2/advanced-features/change-metadata)
@@ -457,32 +470,34 @@ const newName: string = 'new name';
457
470
  await hmsInstance.changeName(newName);
458
471
  ```
459
472
 
460
- ## [Join with specific Track Settings](https://www.100ms.live/docs/react-native/v2/advanced-features/track-settings)
473
+ ## [Join with specific Track Settings](https://www.100ms.live/docs/react-native/v2/features/join#joining-room-with-muted-audio-video)
461
474
 
475
+ Following is an example to join a Room with Muted Audio & Video:
462
476
  ```js
477
+ // First, create the Track Settings object
478
+ const trackSettings = getTrackSettings();
479
+
480
+ // Customize Audio & Video initial states as per user discretion
463
481
  const getTrackSettings = () => {
464
- let audioSettings = new HMSAudioTrackSettings({
465
- maxBitrate: 32,
466
- trackDescription: 'Simple Audio Track',
467
- });
468
- let videoSettings = new HMSVideoTrackSettings({
469
- codec: HMSVideoCodec.vp8,
470
- maxBitrate: 512,
471
- maxFrameRate: 25,
472
- cameraFacing: HMSCameraFacing.FRONT,
473
- trackDescription: 'Simple Video Track',
474
- resolution: new HMSVideoResolution({ height: 180, width: 320 }),
475
- });
476
482
 
477
- return new HMSTrackSettings({ video: videoSettings, audio: audioSettings });
478
- };
483
+ let audioSettings = new HMSAudioTrackSettings({
484
+ initialState: HMSTrackSettingsInitState.MUTED,
485
+ });
486
+
487
+ let videoSettings = new HMSVideoTrackSettings({
488
+ initialState: HMSTrackSettingsInitState.MUTED,
489
+ });
479
490
 
480
- const setupBuild = async () => {
481
- const trackSettings = getTrackSettings();
482
- const build = await HMSSDK.build({ trackSettings });
483
- setInstance(build);
484
- updateHms({ hmsInstance: build });
491
+ return new HMSTrackSettings({
492
+ video: videoSettings,
493
+ audio: audioSettings,
494
+ });
485
495
  };
496
+
497
+ // Pass the Track Settings object to the build function
498
+ const hmsInstance = await HMSSDK.build({
499
+ trackSettings
500
+ });
486
501
  ```
487
502
 
488
503
 
@@ -1,3 +1,7 @@
1
+ import groovy.json.JsonSlurper
2
+
3
+ def sdkVersionsJson = new JsonSlurper().parse file("$rootDir/../../sdk-versions.json")
4
+
1
5
  buildscript {
2
6
  ext.kotlin_version = '1.5.30-RC'
3
7
  if (project == rootProject) {
@@ -65,7 +69,7 @@ dependencies {
65
69
  //noinspection GradleDynamicVersion
66
70
  implementation "com.facebook.react:react-native:+"
67
71
  implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" // From node_modules
68
- implementation 'live.100ms:android-sdk:2.5.1'
72
+ implementation "live.100ms:android-sdk:${sdkVersionsJson["android"]}"
69
73
  implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0'
70
74
  implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
71
75
  implementation 'androidx.appcompat:appcompat:1.3.1'
@@ -23,16 +23,16 @@ object HMSDecoder {
23
23
  room.putString("metaData", null)
24
24
  room.putString("startedAt", hmsRoom.startedAt.toString())
25
25
  room.putMap(
26
- "browserRecordingState",
27
- this.getHMSBrowserRecordingState(hmsRoom.browserRecordingState)
26
+ "browserRecordingState",
27
+ this.getHMSBrowserRecordingState(hmsRoom.browserRecordingState)
28
28
  )
29
29
  room.putMap(
30
- "rtmpHMSRtmpStreamingState",
31
- this.getHMSRtmpStreamingState(hmsRoom.rtmpHMSRtmpStreamingState)
30
+ "rtmpHMSRtmpStreamingState",
31
+ this.getHMSRtmpStreamingState(hmsRoom.rtmpHMSRtmpStreamingState)
32
32
  )
33
33
  room.putMap(
34
- "serverRecordingState",
35
- this.getHMSServerRecordingState(hmsRoom.serverRecordingState)
34
+ "serverRecordingState",
35
+ this.getHMSServerRecordingState(hmsRoom.serverRecordingState)
36
36
  )
37
37
  room.putMap("hlsStreamingState", this.getHMSHlsStreamingState(hmsRoom.hlsStreamingState))
38
38
  room.putMap("hlsRecordingState", this.getHMSHlsRecordingState(hmsRoom.hlsRecordingState))
@@ -230,8 +230,8 @@ object HMSDecoder {
230
230
  val settings: WritableMap = Arguments.createMap()
231
231
  if (hmsAudioTrackSettings != null) {
232
232
  settings.putBoolean(
233
- "useHardwareAcousticEchoCanceler",
234
- hmsAudioTrackSettings.useHardwareAcousticEchoCanceler
233
+ "useHardwareAcousticEchoCanceler",
234
+ hmsAudioTrackSettings.useHardwareAcousticEchoCanceler
235
235
  )
236
236
  settings.putString("initialState", hmsAudioTrackSettings.initialState.name)
237
237
  }
@@ -437,33 +437,33 @@ object HMSDecoder {
437
437
  if (hmsSubscribeSettings != null) {
438
438
  subscribeSettings.putInt("maxSubsBitRate", hmsSubscribeSettings.maxSubsBitRate)
439
439
  subscribeSettings.putMap(
440
- "subscribeDegradationParam",
441
- this.getHmsSubscribeDegradationSettings(hmsSubscribeSettings.subscribeDegradationParam)
440
+ "subscribeDegradationParam",
441
+ this.getHmsSubscribeDegradationSettings(hmsSubscribeSettings.subscribeDegradationParam)
442
442
  )
443
443
  subscribeSettings.putArray(
444
- "subscribeTo",
445
- this.getWriteableArray(hmsSubscribeSettings.subscribeTo)
444
+ "subscribeTo",
445
+ this.getWriteableArray(hmsSubscribeSettings.subscribeTo)
446
446
  )
447
447
  }
448
448
  return subscribeSettings
449
449
  }
450
450
 
451
451
  private fun getHmsSubscribeDegradationSettings(
452
- hmsSubscribeDegradationParams: SubscribeDegradationParams?
452
+ hmsSubscribeDegradationParams: SubscribeDegradationParams?
453
453
  ): WritableMap {
454
454
  val subscribeDegradationParams: WritableMap = Arguments.createMap()
455
455
  if (hmsSubscribeDegradationParams != null) {
456
456
  subscribeDegradationParams.putString(
457
- "degradeGracePeriodSeconds",
458
- hmsSubscribeDegradationParams.degradeGracePeriodSeconds.toString()
457
+ "degradeGracePeriodSeconds",
458
+ hmsSubscribeDegradationParams.degradeGracePeriodSeconds.toString()
459
459
  )
460
460
  subscribeDegradationParams.putString(
461
- "packetLossThreshold",
462
- hmsSubscribeDegradationParams.packetLossThreshold.toString()
461
+ "packetLossThreshold",
462
+ hmsSubscribeDegradationParams.packetLossThreshold.toString()
463
463
  )
464
464
  subscribeDegradationParams.putString(
465
- "recoverGracePeriodSeconds",
466
- hmsSubscribeDegradationParams.recoverGracePeriodSeconds.toString()
465
+ "recoverGracePeriodSeconds",
466
+ hmsSubscribeDegradationParams.recoverGracePeriodSeconds.toString()
467
467
  )
468
468
  }
469
469
  return subscribeDegradationParams
@@ -538,8 +538,8 @@ object HMSDecoder {
538
538
  if (hmsLocalVideoStats != null) {
539
539
  localVideoStats.putString("bytesSent", hmsLocalVideoStats.bytesSent.toString())
540
540
  localVideoStats.putMap(
541
- "resolution",
542
- hmsLocalVideoStats.resolution?.let { this.getHmsVideoTrackResolution(it) }
541
+ "resolution",
542
+ hmsLocalVideoStats.resolution?.let { this.getHmsVideoTrackResolution(it) }
543
543
  )
544
544
  hmsLocalVideoStats.bitrate?.let { localVideoStats.putDouble("bitrate", it) }
545
545
  hmsLocalVideoStats.roundTripTime?.let { localVideoStats.putDouble("roundTripTime", it) }
@@ -567,8 +567,8 @@ object HMSDecoder {
567
567
  remoteVideoStats.putString("packetsReceived", hmsRemoteVideoStats.packetsReceived.toString())
568
568
  hmsRemoteVideoStats.bitrate?.let { remoteVideoStats.putDouble("bitrate", it) }
569
569
  remoteVideoStats.putMap(
570
- "resolution",
571
- hmsRemoteVideoStats.resolution?.let { this.getHmsVideoTrackResolution(it) }
570
+ "resolution",
571
+ hmsRemoteVideoStats.resolution?.let { this.getHmsVideoTrackResolution(it) }
572
572
  )
573
573
  hmsRemoteVideoStats.frameRate?.let { remoteVideoStats.putDouble("frameRate", it) }
574
574
  hmsRemoteVideoStats.jitter?.let { remoteVideoStats.putDouble("jitter", it) }
@@ -2,8 +2,12 @@ package com.reactnativehmssdk
2
2
 
3
3
  import android.app.Activity
4
4
  import android.app.Application
5
+ import android.app.PendingIntent
5
6
  import android.app.PictureInPictureParams
7
+ import android.app.RemoteAction
8
+ import android.content.Intent
6
9
  import android.content.pm.PackageManager
10
+ import android.graphics.drawable.Icon
7
11
  import android.os.Build
8
12
  import android.os.Bundle
9
13
  import android.util.Rational
@@ -16,11 +20,12 @@ import java.util.UUID
16
20
 
17
21
  @ReactModule(name = REACT_CLASS)
18
22
  class HMSManager(reactContext: ReactApplicationContext) :
19
- ReactContextBaseJavaModule(reactContext), Application.ActivityLifecycleCallbacks {
23
+ ReactContextBaseJavaModule(reactContext), Application.ActivityLifecycleCallbacks {
20
24
  companion object {
21
25
  const val REACT_CLASS = "HMSManager"
22
26
  var hmsCollection = mutableMapOf<String, HMSRNSDK>()
23
27
  }
28
+
24
29
  override fun getName(): String {
25
30
  return "HMSManager"
26
31
  }
@@ -115,6 +120,7 @@ class HMSManager(reactContext: ReactApplicationContext) :
115
120
  hms?.sendDirectMessage(data, callback)
116
121
  }
117
122
 
123
+ @kotlin.Deprecated("Use #Function changeRoleOfPeer instead")
118
124
  @ReactMethod
119
125
  fun changeRole(data: ReadableMap, callback: Promise?) {
120
126
  val hms = HMSHelper.getHms(data, hmsCollection)
@@ -122,6 +128,20 @@ class HMSManager(reactContext: ReactApplicationContext) :
122
128
  hms?.changeRole(data, callback)
123
129
  }
124
130
 
131
+ @ReactMethod
132
+ fun changeRoleOfPeer(data: ReadableMap, promise: Promise?) {
133
+ val hms = HMSHelper.getHms(data, hmsCollection)
134
+
135
+ hms?.changeRoleOfPeer(data, promise)
136
+ }
137
+
138
+ @ReactMethod
139
+ fun changeRoleOfPeersWithRoles(data: ReadableMap, promise: Promise?) {
140
+ val hms = HMSHelper.getHms(data, hmsCollection)
141
+
142
+ hms?.changeRoleOfPeersWithRoles(data, promise)
143
+ }
144
+
125
145
  @ReactMethod
126
146
  fun changeTrackState(data: ReadableMap, callback: Promise?) {
127
147
  val hms = HMSHelper.getHms(data, hmsCollection)
@@ -430,17 +450,141 @@ class HMSManager(reactContext: ReactApplicationContext) :
430
450
  hms?.getSessionMetaData(callback)
431
451
  }
432
452
 
433
- data class PipParamConfig(val aspectRatio: Pair<Int, Int>?)
453
+ // region Person-In-Person Mode Action handing
454
+ private val pipReceiver by lazy {
455
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
456
+ PipActionReceiver(
457
+ toggleLocalAudio = {
458
+ toggleLocalAudio()
459
+ updatePIPRemoteActions(PipActionReceiver.PIPActions.localAudio.requestCode)
460
+ },
461
+ toggleLocalVideo = {
462
+ toggleLocalVideo()
463
+ updatePIPRemoteActions(PipActionReceiver.PIPActions.localVideo.requestCode)
464
+ },
465
+ endMeeting = {
466
+ endMeeting()
467
+ }
468
+ )
469
+ } else {
470
+ null
471
+ }
472
+ }
473
+
474
+ private var pipRemoteActionsList: MutableList<RemoteAction> = mutableListOf()
475
+
476
+ @RequiresApi(Build.VERSION_CODES.O)
477
+ private fun toggleLocalAudio() {
478
+ val hmssdk = getHmsInstance()[PipActionReceiver.sdkIdForPIP!!]?.hmsSDK
479
+
480
+ val localAudioTrack = hmssdk?.getLocalPeer()?.audioTrack
481
+ val isMuted = localAudioTrack?.isMute
482
+
483
+ if (isMuted !== null) {
484
+ localAudioTrack?.setMute(!isMuted)
485
+ }
486
+ }
487
+
488
+ @RequiresApi(Build.VERSION_CODES.O)
489
+ private fun toggleLocalVideo() {
490
+ val hmssdk = getHmsInstance()[PipActionReceiver.sdkIdForPIP!!]?.hmsSDK
491
+
492
+ val localVideoTrack = hmssdk?.getLocalPeer()?.videoTrack
493
+ val isMuted = localVideoTrack?.isMute
494
+
495
+ if (isMuted !== null) {
496
+ localVideoTrack?.setMute(!isMuted)
497
+ }
498
+ }
499
+
500
+ @RequiresApi(Build.VERSION_CODES.O)
501
+ private fun endMeeting() {
502
+ val hms = getHmsInstance()[PipActionReceiver.sdkIdForPIP!!]
503
+
504
+ hms?.leave(callback = null, fromPIP = true)
505
+ }
506
+
507
+ @RequiresApi(Build.VERSION_CODES.O)
508
+ private fun updatePIPRemoteActions(code: Int) {
509
+ val activity = currentActivity
510
+
511
+ if (activity !== null) {
512
+ val hmssdk = getHmsInstance()[PipActionReceiver.sdkIdForPIP!!]?.hmsSDK
513
+
514
+ when (code) {
515
+ PipActionReceiver.PIPActions.localAudio.requestCode -> {
516
+ val audioActionIdx = pipRemoteActionsList.indexOfFirst { it.title == PipActionReceiver.PIPActions.localAudio.title }
517
+ if (audioActionIdx >= 0) {
518
+ pipRemoteActionsList[audioActionIdx] = RemoteAction(
519
+ Icon.createWithResource(
520
+ reactApplicationContext,
521
+ if (hmssdk?.getLocalPeer()?.audioTrack?.isMute === true) R.drawable.ic_mic_off_24 else R.drawable.ic_mic_24
522
+ ),
523
+ PipActionReceiver.PIPActions.localAudio.title,
524
+ PipActionReceiver.PIPActions.localAudio.description,
525
+ PendingIntent.getBroadcast(
526
+ reactApplicationContext,
527
+ PipActionReceiver.PIPActions.localAudio.requestCode,
528
+ Intent(PipActionReceiver.PIP_INTENT_ACTION).putExtra(PipActionReceiver.PIPActions.localAudio.title, PipActionReceiver.PIPActions.localAudio.requestCode),
529
+ PendingIntent.FLAG_IMMUTABLE
530
+ )
531
+ )
532
+ }
533
+ }
534
+ PipActionReceiver.PIPActions.localVideo.requestCode -> {
535
+ val videoActionIdx = pipRemoteActionsList.indexOfFirst { it.title == PipActionReceiver.PIPActions.localVideo.title }
536
+ if (videoActionIdx >= 0) {
537
+ pipRemoteActionsList[videoActionIdx] = RemoteAction(
538
+ Icon.createWithResource(
539
+ reactApplicationContext,
540
+ if (hmssdk?.getLocalPeer()?.videoTrack?.isMute === true) R.drawable.ic_camera_toggle_off else R.drawable.ic_camera_toggle_on
541
+ ),
542
+ PipActionReceiver.PIPActions.localVideo.title,
543
+ PipActionReceiver.PIPActions.localVideo.description,
544
+ PendingIntent.getBroadcast(
545
+ reactApplicationContext,
546
+ PipActionReceiver.PIPActions.localVideo.requestCode,
547
+ Intent(PipActionReceiver.PIP_INTENT_ACTION).putExtra(PipActionReceiver.PIPActions.localVideo.title, PipActionReceiver.PIPActions.localVideo.requestCode),
548
+ PendingIntent.FLAG_IMMUTABLE
549
+ )
550
+ )
551
+ }
552
+ }
553
+ }
554
+
555
+ val pipParams = PictureInPictureParams.Builder().let {
556
+ it.setActions(pipRemoteActionsList)
557
+ it.build()
558
+ }
559
+
560
+ activity.setPictureInPictureParams(pipParams)
561
+ }
562
+ }
563
+ // endregion
564
+
565
+ private data class PipParamConfig(
566
+ val aspectRatio: Pair<Int, Int>?,
567
+ val showEndButton: Boolean,
568
+ val showVideoButton: Boolean,
569
+ val showAudioButton: Boolean
570
+ )
434
571
 
435
572
  @ReactMethod
436
- fun handlePipActions(action: String, data: ReadableMap?, promise: Promise?) {
573
+ fun handlePipActions(action: String, data: ReadableMap, promise: Promise?) {
437
574
  if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
438
575
  promise?.reject(Throwable("PIP mode is not supported!"))
439
576
  return
440
577
  }
441
578
 
579
+ if (!data.hasKey("id")) {
580
+ promise?.reject(Throwable("HMS Instance Id is required!"))
581
+ return
582
+ }
583
+
442
584
  try {
443
- when(action) {
585
+ PipActionReceiver.sdkIdForPIP = data.getString("id")
586
+
587
+ when (action) {
444
588
  "isPipModeSupported" -> {
445
589
  val result = isPipModeSupported()
446
590
  promise?.resolve(result)
@@ -459,18 +603,89 @@ class HMSManager(reactContext: ReactApplicationContext) :
459
603
  }
460
604
  }
461
605
 
462
- /**
463
- * Builds and returns PictureInPictureParams as per given config
464
- * Currently we are supporting only "aspectRatio" in config
465
- */
606
+ @RequiresApi(Build.VERSION_CODES.O)
607
+ private fun getPIPRemoteActions(endAction: Boolean = false, audioAction: Boolean = false, videoAction: Boolean = false): MutableList<RemoteAction> {
608
+ val hmssdk = getHmsInstance()[PipActionReceiver.sdkIdForPIP!!]?.hmsSDK
609
+
610
+ pipRemoteActionsList.clear()
611
+
612
+ val localPeer = hmssdk?.getLocalPeer()
613
+ val allowedPublishing = localPeer?.hmsRole?.publishParams?.allowed
614
+
615
+ if (audioAction && allowedPublishing?.contains("audio") === true) {
616
+ pipRemoteActionsList.add(
617
+ RemoteAction(
618
+ Icon.createWithResource(
619
+ reactApplicationContext,
620
+ if (localPeer?.audioTrack?.isMute === true) R.drawable.ic_mic_off_24 else R.drawable.ic_mic_24
621
+ ),
622
+ PipActionReceiver.PIPActions.localAudio.title,
623
+ PipActionReceiver.PIPActions.localAudio.description,
624
+ PendingIntent.getBroadcast(
625
+ reactApplicationContext,
626
+ PipActionReceiver.PIPActions.localAudio.requestCode,
627
+ Intent(PipActionReceiver.PIP_INTENT_ACTION).putExtra(PipActionReceiver.PIPActions.localAudio.title, PipActionReceiver.PIPActions.localAudio.requestCode),
628
+ PendingIntent.FLAG_IMMUTABLE
629
+ )
630
+ )
631
+ )
632
+ }
633
+
634
+ if (endAction) {
635
+ pipRemoteActionsList.add(
636
+ RemoteAction(
637
+ Icon.createWithResource(reactApplicationContext, R.drawable.ic_call_end_24),
638
+ PipActionReceiver.PIPActions.endMeet.title,
639
+ PipActionReceiver.PIPActions.endMeet.description,
640
+ PendingIntent.getBroadcast(
641
+ reactApplicationContext,
642
+ PipActionReceiver.PIPActions.endMeet.requestCode,
643
+ Intent(PipActionReceiver.PIP_INTENT_ACTION).putExtra(
644
+ PipActionReceiver.PIPActions.endMeet.title,
645
+ PipActionReceiver.PIPActions.endMeet.requestCode
646
+ ),
647
+ PendingIntent.FLAG_IMMUTABLE
648
+ )
649
+ )
650
+ )
651
+ }
652
+
653
+ if (videoAction && allowedPublishing?.contains("video") === true) {
654
+ pipRemoteActionsList.add(
655
+ RemoteAction(
656
+ Icon.createWithResource(
657
+ reactApplicationContext,
658
+ if (localPeer?.videoTrack?.isMute === true) R.drawable.ic_camera_toggle_off else R.drawable.ic_camera_toggle_on
659
+ ),
660
+ PipActionReceiver.PIPActions.localVideo.title,
661
+ PipActionReceiver.PIPActions.localVideo.description,
662
+ PendingIntent.getBroadcast(
663
+ reactApplicationContext,
664
+ PipActionReceiver.PIPActions.localVideo.requestCode,
665
+ Intent(PipActionReceiver.PIP_INTENT_ACTION).putExtra(
666
+ PipActionReceiver.PIPActions.localVideo.title,
667
+ PipActionReceiver.PIPActions.localVideo.requestCode
668
+ ),
669
+ PendingIntent.FLAG_IMMUTABLE
670
+ )
671
+ )
672
+ )
673
+ }
674
+
675
+ return pipRemoteActionsList
676
+ }
677
+
678
+ // Builds and returns PictureInPictureParams as per given config
466
679
  @RequiresApi(Build.VERSION_CODES.O)
467
680
  private fun buildPipParams(config: PipParamConfig): PictureInPictureParams {
468
681
  val pipParams = PictureInPictureParams.Builder().let {
469
682
  if (config.aspectRatio !== null) {
470
- it.setAspectRatio(Rational(
471
- config.aspectRatio.first,
472
- config.aspectRatio.second
473
- ))
683
+ it.setAspectRatio(
684
+ Rational(
685
+ config.aspectRatio.first,
686
+ config.aspectRatio.second
687
+ )
688
+ )
474
689
  }
475
690
 
476
691
  // TODO:= We need compileSdkVersion >= 31 for autoEnterEnabled
@@ -478,6 +693,14 @@ class HMSManager(reactContext: ReactApplicationContext) :
478
693
  // it.setAutoEnterEnabled(config.autoEnterEnabled)
479
694
  // }
480
695
 
696
+ it.setActions(
697
+ getPIPRemoteActions(
698
+ endAction = config.showEndButton,
699
+ audioAction = config.showAudioButton,
700
+ videoAction = config.showVideoButton
701
+ )
702
+ )
703
+
481
704
  it.build()
482
705
  }
483
706
 
@@ -486,7 +709,11 @@ class HMSManager(reactContext: ReactApplicationContext) :
486
709
 
487
710
  @RequiresApi(Build.VERSION_CODES.O)
488
711
  private fun readableMapToPipParamConfig(data: ReadableMap?): PipParamConfig {
489
- var aspectRatio: Pair<Int, Int>? = null;
712
+ var aspectRatio: Pair<Int, Int> = Pair(16, 9)
713
+ var showEndButton = false
714
+ var showAudioButton = false
715
+ var showVideoButton = false
716
+
490
717
  // TODO:= We need compileSdkVersion >= 31 for autoEnterEnabled
491
718
  // var autoEnterEnabled: Boolean? = null;
492
719
 
@@ -513,6 +740,18 @@ class HMSManager(reactContext: ReactApplicationContext) :
513
740
  }
514
741
  }
515
742
 
743
+ if (data.hasKey("endButton")) {
744
+ showEndButton = data.getBoolean("endButton")
745
+ }
746
+
747
+ if (data.hasKey("audioButton")) {
748
+ showAudioButton = data.getBoolean("audioButton")
749
+ }
750
+
751
+ if (data.hasKey("videoButton")) {
752
+ showVideoButton = data.getBoolean("videoButton")
753
+ }
754
+
516
755
  // TODO:= We need compileSdkVersion >= 31 for autoEnterEnabled
517
756
  // if (data.hasKey("autoEnterEnabled")) {
518
757
  // val autoEnterEnabledType = data.getType("autoEnterEnabled")
@@ -523,26 +762,35 @@ class HMSManager(reactContext: ReactApplicationContext) :
523
762
  // }
524
763
  }
525
764
 
526
- return PipParamConfig(aspectRatio)
765
+ return PipParamConfig(
766
+ aspectRatio = aspectRatio,
767
+ showEndButton = showEndButton,
768
+ showAudioButton = showAudioButton,
769
+ showVideoButton = showVideoButton
770
+ )
527
771
  }
528
772
 
529
773
  @RequiresApi(Build.VERSION_CODES.O)
530
- private fun setPictureInPictureParams(data: ReadableMap?): Boolean {
531
- if (!isPipModeSupported()) {
532
- throw Throwable(message = "PIP Mode is not supported!")
533
- }
774
+ private fun setPictureInPictureParams(data: ReadableMap): Boolean {
775
+ try {
776
+ if (!isPipModeSupported()) {
777
+ throw Throwable(message = "PIP Mode is not supported!")
778
+ }
534
779
 
535
- val activity = currentActivity;
780
+ val activity = currentActivity
536
781
 
537
- if (activity !== null) {
538
- val pipParamConfig = readableMapToPipParamConfig(data)
539
- val pipParams = buildPipParams(pipParamConfig)
782
+ if (activity !== null) {
783
+ val pipParamConfig = readableMapToPipParamConfig(data)
784
+ val pipParams = buildPipParams(pipParamConfig)
540
785
 
541
- activity.setPictureInPictureParams(pipParams)
542
- return true
543
- }
786
+ activity.setPictureInPictureParams(pipParams)
787
+ return true
788
+ }
544
789
 
545
- return false
790
+ return false
791
+ } catch (e: Exception) {
792
+ throw e
793
+ }
546
794
  }
547
795
 
548
796
  @RequiresApi(Build.VERSION_CODES.O)
@@ -551,22 +799,27 @@ class HMSManager(reactContext: ReactApplicationContext) :
551
799
  }
552
800
 
553
801
  @RequiresApi(Build.VERSION_CODES.O)
554
- private fun enablePipMode(data: ReadableMap?): Boolean {
802
+ private fun enablePipMode(data: ReadableMap): Boolean {
555
803
  try {
556
804
  if (!isPipModeSupported()) {
557
805
  throw Throwable(message = "PIP Mode is not supported!")
558
806
  }
559
807
 
560
- val activity = currentActivity;
808
+ val activity = currentActivity
561
809
 
562
810
  if (activity !== null) {
563
811
  val pipParamConfig = readableMapToPipParamConfig(data)
564
812
  val pipParams = buildPipParams(pipParamConfig)
565
813
 
566
- return activity.enterPictureInPictureMode(pipParams)
814
+ pipReceiver?.register(activity)
815
+ val entered = activity.enterPictureInPictureMode(pipParams)
816
+ if (entered === false) {
817
+ pipReceiver?.unregister(activity)
818
+ }
819
+ return entered
567
820
  }
568
821
 
569
- return false;
822
+ return false
570
823
  } catch (e: Exception) {
571
824
  throw e
572
825
  }
@@ -574,8 +827,8 @@ class HMSManager(reactContext: ReactApplicationContext) :
574
827
 
575
828
  fun emitEvent(event: String, data: WritableMap) {
576
829
  reactApplicationContext
577
- .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
578
- .emit(event, data)
830
+ .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
831
+ .emit(event, data)
579
832
  }
580
833
 
581
834
  override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {}
@@ -601,6 +854,10 @@ class HMSManager(reactContext: ReactApplicationContext) :
601
854
  }
602
855
  currentActivity?.application?.unregisterActivityLifecycleCallbacks(this)
603
856
  hmsCollection = mutableMapOf()
857
+ // unregistering pip actions on activity destroy.
858
+ if (pipReceiver !== null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
859
+ pipReceiver?.unregister(activity)
860
+ }
604
861
  }
605
862
  } catch (e: Exception) {
606
863
  // Log.d("error", e.message)