@100mslive/react-native-hms 1.12.2 → 2.0.0-alpha.1

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 (70) hide show
  1. package/android/build.gradle +70 -12
  2. package/android/src/main/java/com/reactnativehmssdk/HMSAudioshareActivity.kt +8 -8
  3. package/android/src/main/java/com/reactnativehmssdk/HMSHLSPlayer.kt +38 -12
  4. package/android/src/main/java/com/reactnativehmssdk/HMSHLSPlayerManagerImpl.kt +185 -0
  5. package/android/src/main/java/com/reactnativehmssdk/HMSHelper.kt +20 -10
  6. package/android/src/main/java/com/reactnativehmssdk/{HMSManager.kt → HMSManagerImpl.kt} +28 -111
  7. package/android/src/main/java/com/reactnativehmssdk/HMSRNSDK.kt +3 -3
  8. package/android/src/main/java/com/reactnativehmssdk/HMSReactNativeEvent.kt +29 -0
  9. package/android/src/main/java/com/reactnativehmssdk/HMSSDKViewManagerImpl.kt +119 -0
  10. package/android/src/main/java/com/reactnativehmssdk/HMSView.kt +33 -5
  11. package/android/src/main/java/com/reactnativehmssdk/HmsScreenshareActivity.kt +9 -9
  12. package/android/src/main/java/com/reactnativehmssdk/HmssdkPackage.kt +42 -3
  13. package/android/src/newarch/java/com/reactnativehmssdk/HMSHLSPlayerManager.kt +128 -0
  14. package/android/src/newarch/java/com/reactnativehmssdk/HMSManager.kt +332 -0
  15. package/android/src/newarch/java/com/reactnativehmssdk/HMSSDKViewManager.kt +102 -0
  16. package/android/src/oldarch/java/com/reactnativehmssdk/HMSHLSPlayerManager.kt +61 -0
  17. package/android/src/oldarch/java/com/reactnativehmssdk/HMSManager.kt +351 -0
  18. package/android/src/oldarch/java/com/reactnativehmssdk/HMSSDKViewManager.kt +87 -0
  19. package/ios/HMSHLSPlayerComponentView.mm +325 -0
  20. package/ios/HMSHLSPlayerManager.m +10 -0
  21. package/ios/HMSHLSPlayerManager.swift +91 -79
  22. package/ios/HMSManager.m +13 -0
  23. package/ios/HMSManager.mm +365 -0
  24. package/ios/HMSManager.swift +109 -103
  25. package/ios/HMSView.m +9 -0
  26. package/ios/HMSView.swift +44 -14
  27. package/ios/HMSViewComponentView.mm +229 -0
  28. package/lib/commonjs/classes/HmsView.js +45 -48
  29. package/lib/commonjs/classes/HmsView.js.map +1 -1
  30. package/lib/commonjs/components/HMSHLSPlayer/HMSHLSPlayer.js +46 -26
  31. package/lib/commonjs/components/HMSHLSPlayer/HMSHLSPlayer.js.map +1 -1
  32. package/lib/commonjs/components/HMSHLSPlayer/RCTHMSHLSPlayer.js +5 -4
  33. package/lib/commonjs/components/HMSHLSPlayer/RCTHMSHLSPlayer.js.map +1 -1
  34. package/lib/commonjs/modules/HMSManagerModule.js +3 -10
  35. package/lib/commonjs/modules/HMSManagerModule.js.map +1 -1
  36. package/lib/commonjs/specs/HMSHLSPlayerNativeComponent.js +75 -0
  37. package/lib/commonjs/specs/HMSHLSPlayerNativeComponent.js.map +1 -0
  38. package/lib/commonjs/specs/HMSViewNativeComponent.js +55 -0
  39. package/lib/commonjs/specs/HMSViewNativeComponent.js.map +1 -0
  40. package/lib/commonjs/specs/NativeHMSManager.js +38 -0
  41. package/lib/commonjs/specs/NativeHMSManager.js.map +1 -0
  42. package/lib/module/classes/HmsView.js +46 -49
  43. package/lib/module/classes/HmsView.js.map +1 -1
  44. package/lib/module/components/HMSHLSPlayer/HMSHLSPlayer.js +48 -28
  45. package/lib/module/components/HMSHLSPlayer/HMSHLSPlayer.js.map +1 -1
  46. package/lib/module/components/HMSHLSPlayer/RCTHMSHLSPlayer.js +3 -3
  47. package/lib/module/components/HMSHLSPlayer/RCTHMSHLSPlayer.js.map +1 -1
  48. package/lib/module/modules/HMSManagerModule.js +1 -9
  49. package/lib/module/modules/HMSManagerModule.js.map +1 -1
  50. package/lib/module/specs/HMSHLSPlayerNativeComponent.js +69 -0
  51. package/lib/module/specs/HMSHLSPlayerNativeComponent.js.map +1 -0
  52. package/lib/module/specs/HMSViewNativeComponent.js +49 -0
  53. package/lib/module/specs/HMSViewNativeComponent.js.map +1 -0
  54. package/lib/module/specs/NativeHMSManager.js +33 -0
  55. package/lib/module/specs/NativeHMSManager.js.map +1 -0
  56. package/lib/typescript/components/HMSHLSPlayer/RCTHMSHLSPlayer.d.ts +6 -9
  57. package/lib/typescript/specs/HMSHLSPlayerNativeComponent.d.ts +115 -0
  58. package/lib/typescript/specs/HMSViewNativeComponent.d.ts +97 -0
  59. package/lib/typescript/specs/NativeHMSManager.d.ts +147 -0
  60. package/package.json +16 -1
  61. package/react-native-hms.podspec +43 -0
  62. package/src/classes/HmsView.tsx +60 -78
  63. package/src/components/HMSHLSPlayer/HMSHLSPlayer.tsx +62 -128
  64. package/src/components/HMSHLSPlayer/RCTHMSHLSPlayer.ts +8 -13
  65. package/src/modules/HMSManagerModule.ts +1 -14
  66. package/src/specs/HMSHLSPlayerNativeComponent.ts +203 -0
  67. package/src/specs/HMSViewNativeComponent.ts +119 -0
  68. package/src/specs/NativeHMSManager.ts +307 -0
  69. package/android/src/main/java/com/reactnativehmssdk/HMSHLSPlayerManager.kt +0 -144
  70. package/android/src/main/java/com/reactnativehmssdk/HMSSDKViewManager.kt +0 -111
@@ -1,21 +1,34 @@
1
1
  import groovy.json.JsonSlurper
2
2
 
3
+ // AGP, Kotlin, and the React Native gradle plugin all come from the consumer
4
+ // app's classloader (root buildscript classpath in legacy consumers, or
5
+ // pluginManagement.includeBuild in modern RN 0.71+ consumers). We deliberately
6
+ // do NOT declare our own `buildscript { dependencies { classpath(...) } }`
7
+ // block: that creates a separate buildscript classloader for this subproject,
8
+ // which causes the React plugin to be loaded twice when another lib in the
9
+ // same build also applies it ("Cannot add extension with name 'privateReact'"
10
+ // — different Class<PrivateReactExtension> objects per classloader fail
11
+ // findByType lookups). Inheriting plugins from the consumer's classpath
12
+ // guarantees a single load.
13
+ plugins {
14
+ id 'com.android.library'
15
+ id 'kotlin-android'
16
+ id 'com.facebook.react'
17
+ }
18
+
3
19
  def sdkVersions = new JsonSlurper().parse file("../sdk-versions.json")
4
20
 
5
- buildscript {
6
- repositories {
7
- google()
8
- mavenCentral()
9
- }
10
- dependencies {
11
- classpath("com.android.tools.build:gradle:8.5.0")
12
- classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:2.0.21")
13
- }
21
+ // Helper that mirrors the consumer-app `newArchEnabled` Gradle property.
22
+ // Used by the source-set switch below: when new arch is on, the build
23
+ // pulls in `src/newarch/` Kotlin (TurboModule wrapper extending the
24
+ // generated NativeHMSManagerSpec). When off, it pulls in `src/oldarch/`
25
+ // (legacy wrapper extending ReactContextBaseJavaModule). Either way,
26
+ // `src/main/` always provides the shared HMSManagerImpl with the
27
+ // actual SDK logic.
28
+ def isNewArchitectureEnabled() {
29
+ return project.hasProperty("newArchEnabled") && project.newArchEnabled == "true"
14
30
  }
15
31
 
16
- apply plugin: 'com.android.library'
17
- apply plugin: 'kotlin-android'
18
-
19
32
  // Define helper functions for Kotlin version management (RN 0.77.3 requires 2.0.21)
20
33
  def getKotlinVersion() {
21
34
  return rootProject.ext.has("kotlinVersion") ? rootProject.ext.get("kotlinVersion") : "2.0.21"
@@ -38,6 +51,38 @@ android {
38
51
  targetSdkVersion safeExtGet('Hmssdk_targetSdkVersion', 35)
39
52
  versionCode 1
40
53
  versionName "1.0"
54
+
55
+ // Build flag exposed to Kotlin/Java via BuildConfig so runtime
56
+ // code can branch on the active arch if needed (rare — most
57
+ // arch-specific behavior is resolved at compile time via the
58
+ // sourceSets switch below).
59
+ buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
60
+ }
61
+
62
+ buildFeatures {
63
+ buildConfig = true
64
+ }
65
+
66
+ // Source-set switch — chooses the arch-specific wrapper classes.
67
+ // Both `src/newarch/` and `src/oldarch/` contain the same package
68
+ // and class names (HMSManager, HMSSDKViewManager, HMSHLSPlayerManager);
69
+ // only one is compiled per build, picked here based on the consumer
70
+ // app's `newArchEnabled` Gradle property.
71
+ //
72
+ // The actual SDK business logic lives in `src/main/` as `HMSManagerImpl`,
73
+ // `HMSSDKViewManagerImpl`, etc. Both arch wrappers delegate to it.
74
+ //
75
+ // This pattern is the canonical RN community approach for
76
+ // backward-compatible TurboModules — see:
77
+ // https://github.com/reactwg/react-native-new-architecture/blob/main/docs/backwards-compat-turbo-modules.md
78
+ sourceSets {
79
+ main {
80
+ if (isNewArchitectureEnabled()) {
81
+ java.srcDirs += ['src/newarch']
82
+ } else {
83
+ java.srcDirs += ['src/oldarch']
84
+ }
85
+ }
41
86
  }
42
87
 
43
88
  lint {
@@ -52,6 +97,19 @@ android {
52
97
  }
53
98
  }
54
99
 
100
+ // Codegen configuration. Consumer's build picks up the TS specs in
101
+ // `src/specs/` and generates:
102
+ // - Java abstract class `NativeHMSManagerSpec` (and others) under
103
+ // the package below, which our `src/newarch/` wrapper extends.
104
+ // - C++ Fabric component descriptors / event emitters / props under
105
+ // `react/renderer/components/RNHmsSpec/...` for iOS-side
106
+ // `*ComponentView.mm` files (see `react-native-hms.podspec`).
107
+ react {
108
+ jsRootDir = file("../src/specs/")
109
+ libraryName = "RNHmsSpec"
110
+ codegenJavaPackageName = "com.reactnativehmssdk"
111
+ }
112
+
55
113
  repositories {
56
114
  mavenLocal()
57
115
  maven {
@@ -18,17 +18,17 @@ class HMSAudioshareActivity : ComponentActivity() {
18
18
  val mediaProjectionPermissionResultData: Intent? = result.data
19
19
  val id = intent.getStringExtra("id")
20
20
  val audioMixingMode = intent.getStringExtra("audioMixingMode")
21
- HMSManager.hmsCollection[id]?.hmsSDK?.startAudioshare(
21
+ HMSManagerImpl.hmsCollection[id]?.hmsSDK?.startAudioshare(
22
22
  object : HMSActionResultListener {
23
23
  override fun onError(error: HMSException) {
24
24
  finish()
25
- HMSManager.hmsCollection[id]?.audioshareCallback?.reject(error)
25
+ HMSManagerImpl.hmsCollection[id]?.audioshareCallback?.reject(error)
26
26
  }
27
27
 
28
28
  override fun onSuccess() {
29
- HMSManager.hmsCollection[id]?.isAudioSharing = true
30
- HMSManager.hmsCollection[id]?.audioshareCallback?.resolve(
31
- HMSManager.hmsCollection[id]?.getPromiseResolveData(),
29
+ HMSManagerImpl.hmsCollection[id]?.isAudioSharing = true
30
+ HMSManagerImpl.hmsCollection[id]?.audioshareCallback?.resolve(
31
+ HMSManagerImpl.hmsCollection[id]?.getPromiseResolveData(),
32
32
  )
33
33
  finish()
34
34
  }
@@ -46,7 +46,7 @@ class HMSAudioshareActivity : ComponentActivity() {
46
46
  "RESULT_CANCELED",
47
47
  "RESULT_CANCELED",
48
48
  )
49
- HMSManager.hmsCollection[id]?.audioshareCallback?.reject(error)
49
+ HMSManagerImpl.hmsCollection[id]?.audioshareCallback?.reject(error)
50
50
  finish()
51
51
  }
52
52
  }
@@ -58,7 +58,7 @@ class HMSAudioshareActivity : ComponentActivity() {
58
58
 
59
59
  private fun startAudioshare() {
60
60
  val id = intent.getStringExtra("id")
61
- val isAudioShared = HMSManager.hmsCollection[id]?.isAudioSharing
61
+ val isAudioShared = HMSManagerImpl.hmsCollection[id]?.isAudioSharing
62
62
  if (isAudioShared !== null && !isAudioShared) {
63
63
  try {
64
64
  val mediaProjectionManager =
@@ -68,7 +68,7 @@ class HMSAudioshareActivity : ComponentActivity() {
68
68
  println(e)
69
69
  }
70
70
  } else {
71
- HMSManager.hmsCollection[id]?.emitHMSError(
71
+ HMSManagerImpl.hmsCollection[id]?.emitHMSError(
72
72
  HMSException(
73
73
  103,
74
74
  "AUDIOSHARE_IS_ALREADY_RUNNING",
@@ -2,6 +2,7 @@ package com.reactnativehmssdk
2
2
 
3
3
  import android.annotation.SuppressLint
4
4
  import android.content.Context
5
+ import android.util.Log
5
6
  import android.view.LayoutInflater
6
7
  import android.view.View
7
8
  import android.widget.FrameLayout
@@ -15,7 +16,7 @@ import com.facebook.react.bridge.ReadableArray
15
16
  import com.facebook.react.bridge.ReadableMap
16
17
  import com.facebook.react.bridge.WritableMap
17
18
  import com.facebook.react.uimanager.ThemedReactContext
18
- import com.facebook.react.uimanager.events.RCTEventEmitter
19
+ import com.facebook.react.uimanager.UIManagerHelper
19
20
  import live.hms.hls_player.*
20
21
  import live.hms.stats.PlayerStatsListener
21
22
  import live.hms.stats.model.PlayerStatsModel
@@ -33,6 +34,11 @@ class HMSHLSPlayer(
33
34
  private var hmssdkInstance: HMSSDK? = null
34
35
  private var statsMonitorAttached = false
35
36
  private var shouldSendCaptionsToJS = false
37
+
38
+ // Held so `cleanup()` can remove the listener from ExoPlayer on unmount.
39
+ // The anonymous Player.Listener captures `this`, so leaking it across
40
+ // mount/unmount cycles prevents GC of HMSHLSPlayer view instances.
41
+ private var playerListener: Player.Listener? = null
36
42
  private val hmsHlsPlaybackEventsObject =
37
43
  object : HmsHlsPlaybackEvents {
38
44
  override fun onCue(cue: HmsHlsCue) {
@@ -139,7 +145,7 @@ class HMSHLSPlayer(
139
145
  // setting 100ms HLS Player on Exoplayer
140
146
  localPlayerView.player = localHmsHlsPlayer.getNativePlayer()
141
147
 
142
- localPlayerView?.player?.addListener(
148
+ val listener =
143
149
  object : Player.Listener {
144
150
  override fun onVideoSizeChanged(videoSize: VideoSize) {
145
151
  super.onVideoSizeChanged(videoSize)
@@ -165,14 +171,21 @@ class HMSHLSPlayer(
165
171
  ?.toString()
166
172
  sendHLSPlayerCuesEventToJS(ccText)
167
173
  }
168
- },
169
- )
174
+ }
175
+ playerListener = listener
176
+ localPlayerView?.player?.addListener(listener)
170
177
  }
171
178
 
172
179
  fun cleanup() {
173
180
  hmsHlsPlayer?.stop()
174
181
  hmsHlsPlayer?.addPlayerEventListener(null)
175
182
  hmsHlsPlayer?.setStatsMonitor(null)
183
+ // Remove the ExoPlayer Player.Listener registered in init. Without
184
+ // this, the anonymous listener (which captures `this`) leaks across
185
+ // mount/unmount cycles — visible in long-running video conferencing
186
+ // sessions where users repeatedly join and leave rooms.
187
+ playerListener?.let { listener -> playerView?.player?.removeListener(listener) }
188
+ playerListener = null
176
189
  }
177
190
 
178
191
  fun play(url: String?) {
@@ -300,6 +313,23 @@ class HMSHLSPlayer(
300
313
  statsMonitorAttached = false
301
314
  }
302
315
 
316
+ private fun dispatchViewEvent(
317
+ eventName: String,
318
+ payload: WritableMap,
319
+ ) {
320
+ val reactContext = context as ReactContext
321
+ val surfaceId = UIManagerHelper.getSurfaceId(this)
322
+ val dispatcher = UIManagerHelper.getEventDispatcherForReactTag(reactContext, id)
323
+ if (dispatcher != null) {
324
+ dispatcher.dispatchEvent(HMSReactNativeEvent(surfaceId, id, eventName, payload))
325
+ } else {
326
+ // Bridgeless mode: dispatcher can be null if the view is detaching or
327
+ // the React tag is no longer valid. Log instead of silently dropping
328
+ // so the event loss is debuggable.
329
+ Log.w("HMSHLSPlayer", "Event '$eventName' dropped — dispatcher null for tag $id")
330
+ }
331
+ }
332
+
303
333
  private fun sendHLSPlaybackEventToJS(
304
334
  eventName: String,
305
335
  data: WritableMap,
@@ -308,8 +338,7 @@ class HMSHLSPlayer(
308
338
  event.putString("event", eventName)
309
339
  event.putMap("data", data)
310
340
 
311
- val reactContext = context as ReactContext
312
- reactContext.getJSModule(RCTEventEmitter::class.java).receiveEvent(id, HMSHLSPlayerConstants.HMS_HLS_PLAYBACK_EVENT, event)
341
+ dispatchViewEvent(HMSHLSPlayerConstants.HMS_HLS_PLAYBACK_EVENT, event)
313
342
  }
314
343
 
315
344
  private fun sendHLSStatsEventToJS(
@@ -320,8 +349,7 @@ class HMSHLSPlayer(
320
349
  event.putString("event", eventName)
321
350
  event.putMap("data", data)
322
351
 
323
- val reactContext = context as ReactContext
324
- reactContext.getJSModule(RCTEventEmitter::class.java).receiveEvent(id, HMSHLSPlayerConstants.HMS_HLS_STATS_EVENT, event)
352
+ dispatchViewEvent(HMSHLSPlayerConstants.HMS_HLS_STATS_EVENT, event)
325
353
  }
326
354
 
327
355
  private fun sendHLSDataRequestEventToJS(
@@ -347,8 +375,7 @@ class HMSHLSPlayer(
347
375
  event.putNull("data")
348
376
  }
349
377
 
350
- val reactContext = context as ReactContext
351
- reactContext.getJSModule(RCTEventEmitter::class.java).receiveEvent(id, HMSHLSPlayerConstants.HLS_DATA_REQUEST_EVENT, event)
378
+ dispatchViewEvent(HMSHLSPlayerConstants.HLS_DATA_REQUEST_EVENT, event)
352
379
  }
353
380
 
354
381
  private fun sendHLSPlayerCuesEventToJS(ccText: String?) {
@@ -360,8 +387,7 @@ class HMSHLSPlayer(
360
387
  } else {
361
388
  event.putNull("data")
362
389
  }
363
- val reactContext = context as ReactContext
364
- reactContext.getJSModule(RCTEventEmitter::class.java).receiveEvent(id, HMSHLSPlayerConstants.HLS_PLAYER_CUES_EVENT, event)
390
+ dispatchViewEvent(HMSHLSPlayerConstants.HLS_PLAYER_CUES_EVENT, event)
365
391
  }
366
392
  }
367
393
 
@@ -0,0 +1,185 @@
1
+ package com.reactnativehmssdk
2
+
3
+ import com.facebook.react.bridge.ReadableArray
4
+ import com.facebook.react.common.MapBuilder
5
+ import com.facebook.react.uimanager.ThemedReactContext
6
+
7
+ /**
8
+ * HMSHLSPlayerManagerImpl — shared view-manager logic for `<HMSHLSPlayer />`.
9
+ *
10
+ * Phase 1 / 1C-4 of the New Architecture migration. View-manager
11
+ * responsibilities (creating/cleanup, prop dispatch, 13 commands, event
12
+ * registration) live here as static helpers. Arch-specific wrappers at:
13
+ * - android/src/oldarch/.../HMSHLSPlayerManager.kt — paper, @ReactProp + numeric command IDs
14
+ * - android/src/newarch/.../HMSHLSPlayerManager.kt — Fabric, implements the
15
+ * Codegen-generated HMSHLSPlayerManagerInterface for typed prop + command dispatch
16
+ */
17
+ class HMSHLSPlayerManagerImpl {
18
+ companion object {
19
+ const val REACT_CLASS = "HMSHLSPlayer"
20
+
21
+ fun createViewInstance(reactContext: ThemedReactContext): HMSHLSPlayer = HMSHLSPlayer(reactContext)
22
+
23
+ fun onDropViewInstance(view: HMSHLSPlayer) {
24
+ view.cleanup()
25
+ }
26
+
27
+ // ─────────────────────────────────────────────────────────────────
28
+ // Prop setters
29
+ // ─────────────────────────────────────────────────────────────────
30
+
31
+ fun setUrl(
32
+ view: HMSHLSPlayer,
33
+ data: String?,
34
+ ) = view.play(data)
35
+
36
+ fun setEnableStats(
37
+ view: HMSHLSPlayer,
38
+ data: Boolean,
39
+ ) = view.enableStats(data)
40
+
41
+ fun setEnableControls(
42
+ view: HMSHLSPlayer,
43
+ data: Boolean,
44
+ ) = view.enableControls(data)
45
+
46
+ // ─────────────────────────────────────────────────────────────────
47
+ // Imperative commands (13)
48
+ // ─────────────────────────────────────────────────────────────────
49
+
50
+ fun play(
51
+ view: HMSHLSPlayer,
52
+ url: String?,
53
+ ) = view.play(url)
54
+
55
+ fun stop(view: HMSHLSPlayer) = view.stop()
56
+
57
+ fun pause(view: HMSHLSPlayer) = view.pause()
58
+
59
+ fun resume(view: HMSHLSPlayer) = view.resume()
60
+
61
+ fun seekToLivePosition(view: HMSHLSPlayer) = view.seekToLivePosition()
62
+
63
+ fun seekForward(
64
+ view: HMSHLSPlayer,
65
+ seconds: Double,
66
+ ) = view.seekForward(seconds)
67
+
68
+ fun seekBackward(
69
+ view: HMSHLSPlayer,
70
+ seconds: Double,
71
+ ) = view.seekBackward(seconds)
72
+
73
+ fun setVolume(
74
+ view: HMSHLSPlayer,
75
+ level: Int,
76
+ ) = view.setVolume(level)
77
+
78
+ fun areClosedCaptionSupported(
79
+ view: HMSHLSPlayer,
80
+ requestId: Int,
81
+ ) = view.areClosedCaptionSupported(requestId)
82
+
83
+ fun isClosedCaptionEnabled(
84
+ view: HMSHLSPlayer,
85
+ requestId: Int,
86
+ ) = view.isClosedCaptionEnabled(requestId)
87
+
88
+ fun enableClosedCaption(view: HMSHLSPlayer) = view.enableClosedCaption()
89
+
90
+ fun disableClosedCaption(view: HMSHLSPlayer) = view.disableClosedCaption()
91
+
92
+ fun getPlayerDurationDetails(
93
+ view: HMSHLSPlayer,
94
+ requestId: Int,
95
+ ) = view.getPlayerDurationDetails(requestId)
96
+
97
+ // ─────────────────────────────────────────────────────────────────
98
+ // Paper-side dispatch helpers (used by the oldarch wrapper)
99
+ // ─────────────────────────────────────────────────────────────────
100
+
101
+ /**
102
+ * Numeric command IDs preserved from the original view manager so
103
+ * paper-side dispatch keeps the same wire protocol.
104
+ */
105
+ fun getCommandsMap(): Map<String, Int> =
106
+ MapBuilder
107
+ .builder<String, Int>()
108
+ .put("play", 10)
109
+ .put("stop", 20)
110
+ .put("pause", 30)
111
+ .put("resume", 40)
112
+ .put("seekToLivePosition", 50)
113
+ .put("seekForward", 60)
114
+ .put("seekBackward", 70)
115
+ .put("setVolume", 80)
116
+ .put("areClosedCaptionSupported", 90)
117
+ .put("isClosedCaptionEnabled", 100)
118
+ .put("enableClosedCaption", 110)
119
+ .put("disableClosedCaption", 120)
120
+ .put("getPlayerDurationDetails", 130)
121
+ .build()
122
+
123
+ /** Paper command dispatch by numeric ID. */
124
+ fun receiveCommandById(
125
+ view: HMSHLSPlayer,
126
+ commandId: Int,
127
+ args: ReadableArray?,
128
+ ) {
129
+ when (commandId) {
130
+ 10 -> play(view, args?.getString(0))
131
+ 20 -> stop(view)
132
+ 30 -> pause(view)
133
+ 40 -> resume(view)
134
+ 50 -> seekToLivePosition(view)
135
+ 60 -> args?.let { seekForward(view, it.getDouble(0)) }
136
+ 70 -> args?.let { seekBackward(view, it.getDouble(0)) }
137
+ 80 -> args?.let { setVolume(view, it.getInt(0)) }
138
+ 90 -> args?.let { areClosedCaptionSupported(view, it.getInt(0)) }
139
+ 100 -> args?.let { isClosedCaptionEnabled(view, it.getInt(0)) }
140
+ 110 -> enableClosedCaption(view)
141
+ 120 -> disableClosedCaption(view)
142
+ 130 -> args?.let { getPlayerDurationDetails(view, it.getInt(0)) }
143
+ }
144
+ }
145
+
146
+ /** Fabric command dispatch by string name. */
147
+ fun receiveCommandByName(
148
+ view: HMSHLSPlayer,
149
+ commandName: String,
150
+ args: ReadableArray?,
151
+ ) {
152
+ when (commandName) {
153
+ "play" -> play(view, args?.getString(0))
154
+ "stop" -> stop(view)
155
+ "pause" -> pause(view)
156
+ "resume" -> resume(view)
157
+ "seekToLivePosition" -> seekToLivePosition(view)
158
+ "seekForward" -> args?.let { seekForward(view, it.getDouble(0)) }
159
+ "seekBackward" -> args?.let { seekBackward(view, it.getDouble(0)) }
160
+ "setVolume" -> args?.let { setVolume(view, it.getInt(0)) }
161
+ "areClosedCaptionSupported" -> args?.let { areClosedCaptionSupported(view, it.getInt(0)) }
162
+ "isClosedCaptionEnabled" -> args?.let { isClosedCaptionEnabled(view, it.getInt(0)) }
163
+ "enableClosedCaption" -> enableClosedCaption(view)
164
+ "disableClosedCaption" -> disableClosedCaption(view)
165
+ "getPlayerDurationDetails" -> args?.let { getPlayerDurationDetails(view, it.getInt(0)) }
166
+ }
167
+ }
168
+
169
+ // ─────────────────────────────────────────────────────────────────
170
+ // Event registration — 4 events on `<HMSHLSPlayer />`
171
+ // ─────────────────────────────────────────────────────────────────
172
+
173
+ fun getExportedCustomDirectEventTypeConstants(): Map<String, Any> =
174
+ MapBuilder.of(
175
+ HMSHLSPlayerConstants.HMS_HLS_PLAYBACK_EVENT,
176
+ MapBuilder.of("registrationName", "onHmsHlsPlaybackEvent"),
177
+ HMSHLSPlayerConstants.HMS_HLS_STATS_EVENT,
178
+ MapBuilder.of("registrationName", "onHmsHlsStatsEvent"),
179
+ HMSHLSPlayerConstants.HLS_DATA_REQUEST_EVENT,
180
+ MapBuilder.of("registrationName", "onDataReturned"),
181
+ HMSHLSPlayerConstants.HLS_PLAYER_CUES_EVENT,
182
+ MapBuilder.of("registrationName", "onHlsPlayerCuesEvent"),
183
+ )
184
+ }
185
+ }
@@ -10,7 +10,7 @@ import android.view.PixelCopy
10
10
  import android.webkit.URLUtil
11
11
  import androidx.annotation.RequiresApi
12
12
  import com.facebook.react.bridge.*
13
- import com.facebook.react.uimanager.events.RCTEventEmitter
13
+ import com.facebook.react.uimanager.UIManagerHelper
14
14
  import hms.webrtc.SurfaceViewRenderer
15
15
  import live.hms.video.audio.HMSAudioManager
16
16
  import live.hms.video.error.HMSException
@@ -634,6 +634,20 @@ object HMSHelper {
634
634
  output.putInt("requestId", -1)
635
635
  }
636
636
  val reactContext = context as ReactContext
637
+ val surfaceId = UIManagerHelper.getSurfaceId(reactContext)
638
+
639
+ // Bridgeless mode: dispatcher can be null if the view is detaching or
640
+ // the React tag is no longer valid. Log instead of silently dropping
641
+ // so the event loss is debuggable.
642
+ fun dispatchCaptureFrameEvent() {
643
+ val dispatcher = UIManagerHelper.getEventDispatcherForReactTag(reactContext, id)
644
+ if (dispatcher != null) {
645
+ dispatcher.dispatchEvent(HMSReactNativeEvent(surfaceId, id, "captureFrame", output))
646
+ } else {
647
+ Log.w("captureSurfaceView", "captureFrame event dropped — dispatcher null for tag $id")
648
+ }
649
+ }
650
+
637
651
  try {
638
652
  val bitmap: Bitmap =
639
653
  Bitmap.createBitmap(surfaceView.width, surfaceView.height, Bitmap.Config.ARGB_8888)
@@ -649,12 +663,10 @@ object HMSHelper {
649
663
  val encoded: String = Base64.encodeToString(byteArray, Base64.DEFAULT)
650
664
  Log.d("captureSurfaceView", "Base64: $encoded")
651
665
  output.putString("result", encoded)
652
- reactContext
653
- .getJSModule(RCTEventEmitter::class.java)
654
- .receiveEvent(id, "captureFrame", output)
666
+ dispatchCaptureFrameEvent()
655
667
  } else {
656
668
  Log.e("captureSurfaceView", "copyResult: $copyResult")
657
- HMSManager.hmsCollection[sdkId]?.emitHMSError(
669
+ HMSManagerImpl.hmsCollection[sdkId]?.emitHMSError(
658
670
  HMSException(
659
671
  103,
660
672
  copyResult.toString(),
@@ -664,18 +676,16 @@ object HMSHelper {
664
676
  ),
665
677
  )
666
678
  output.putString("error", copyResult.toString())
667
- reactContext
668
- .getJSModule(RCTEventEmitter::class.java)
669
- .receiveEvent(id, "captureFrame", output)
679
+ dispatchCaptureFrameEvent()
670
680
  }
671
681
  },
672
682
  Handler(),
673
683
  )
674
684
  } catch (e: Exception) {
675
685
  Log.e("captureSurfaceView", "error: $e")
676
- HMSManager.hmsCollection[sdkId]?.emitHMSError(e as HMSException)
686
+ HMSManagerImpl.hmsCollection[sdkId]?.emitHMSError(e as HMSException)
677
687
  output.putString("error", e.message)
678
- reactContext.getJSModule(RCTEventEmitter::class.java).receiveEvent(id, "captureFrame", output)
688
+ dispatchCaptureFrameEvent()
679
689
  }
680
690
  }
681
691