react-native-spalla-player 0.13.3 → 1.0.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 (53) hide show
  1. package/LICENSE +2 -3
  2. package/SpallaPlayer.podspec +22 -0
  3. package/android/build.gradle +22 -58
  4. package/android/gradle.properties +5 -5
  5. package/android/src/main/AndroidManifest.xml +1 -2
  6. package/android/src/main/java/com/spallaplayer/RNSpallaPlayerEvent.kt +19 -0
  7. package/android/src/main/java/com/spallaplayer/SpallaPlayerModule.fabric.kt +103 -0
  8. package/android/src/main/java/com/spallaplayer/SpallaPlayerModule.kt +32 -21
  9. package/android/src/main/java/com/spallaplayer/SpallaPlayerPackage.kt +18 -4
  10. package/android/src/main/java/com/spallaplayer/SpallaPlayerView.kt +15 -0
  11. package/android/src/main/java/com/spallaplayer/SpallaPlayerViewManager.fabric.kt +289 -0
  12. package/android/src/main/java/com/spallaplayer/SpallaPlayerViewManager.kt +25 -27
  13. package/ios/SpallaPlayerModule.h +17 -0
  14. package/ios/SpallaPlayerModule.mm +59 -0
  15. package/ios/SpallaPlayerView.h +14 -0
  16. package/ios/SpallaPlayerView.mm +158 -0
  17. package/ios/SpallaPlayerWrapper.swift +12 -8
  18. package/lib/module/NativeSpallaPlayerModule.js +5 -0
  19. package/lib/module/NativeSpallaPlayerModule.js.map +1 -0
  20. package/lib/module/SpallaPlayerViewNativeComponent.js +11 -0
  21. package/lib/module/SpallaPlayerViewNativeComponent.js.map +1 -0
  22. package/lib/module/index.js +48 -55
  23. package/lib/module/index.js.map +1 -1
  24. package/lib/typescript/src/NativeSpallaPlayerModule.d.ts +13 -0
  25. package/lib/typescript/src/NativeSpallaPlayerModule.d.ts.map +1 -0
  26. package/lib/typescript/src/SpallaPlayerViewNativeComponent.d.ts +33 -0
  27. package/lib/typescript/src/SpallaPlayerViewNativeComponent.d.ts.map +1 -0
  28. package/lib/typescript/src/components/CastButton.d.ts.map +1 -0
  29. package/lib/typescript/{module/src → src}/index.d.ts +9 -15
  30. package/lib/typescript/src/index.d.ts.map +1 -0
  31. package/package.json +93 -103
  32. package/src/NativeSpallaPlayerModule.ts +14 -0
  33. package/src/SpallaPlayerViewNativeComponent.ts +61 -0
  34. package/src/index.tsx +71 -82
  35. package/android/src/main/AndroidManifestNew.xml +0 -2
  36. package/android/src/main/java/com/spallaplayer/SpallaPlayerContainerView.kt +0 -36
  37. package/ios/RNSpallaPlayer.m +0 -102
  38. package/ios/RNSpallaPlayer.swift +0 -11
  39. package/ios/SpallaPlayer-Bridging-Header.h +0 -1
  40. package/lib/commonjs/components/CastButton.js +0 -49
  41. package/lib/commonjs/components/CastButton.js.map +0 -1
  42. package/lib/commonjs/index.js +0 -81
  43. package/lib/commonjs/index.js.map +0 -1
  44. package/lib/typescript/commonjs/package.json +0 -1
  45. package/lib/typescript/commonjs/src/components/CastButton.d.ts.map +0 -1
  46. package/lib/typescript/commonjs/src/index.d.ts +0 -61
  47. package/lib/typescript/commonjs/src/index.d.ts.map +0 -1
  48. package/lib/typescript/module/src/components/CastButton.d.ts +0 -13
  49. package/lib/typescript/module/src/components/CastButton.d.ts.map +0 -1
  50. package/lib/typescript/module/src/index.d.ts.map +0 -1
  51. package/react-native-spalla-player.podspec +0 -44
  52. /package/lib/{typescript/module → module}/package.json +0 -0
  53. /package/lib/typescript/{commonjs/src → src}/components/CastButton.d.ts +0 -0
@@ -0,0 +1,289 @@
1
+ package com.spallaplayer
2
+
3
+ import android.util.Log
4
+ import android.view.View
5
+ import com.facebook.react.bridge.Arguments
6
+ import com.facebook.react.bridge.ReactContext
7
+ import com.facebook.react.bridge.ReadableArray
8
+ import com.facebook.react.bridge.WritableMap
9
+ import com.facebook.react.module.annotations.ReactModule
10
+ import com.facebook.react.uimanager.ViewGroupManager
11
+ import com.facebook.react.uimanager.ThemedReactContext
12
+ import com.facebook.react.uimanager.UIManagerHelper
13
+ import com.facebook.react.uimanager.annotations.ReactProp
14
+ import com.spalla.sdk.android.core.player.entities.SpallaPlayerEvent
15
+ import com.spalla.sdk.android.core.player.entities.SpallaPlayerEvent.*
16
+ import com.spalla.sdk.android.core.player.listeners.SpallaPlayerFullScreenListener
17
+ import com.spalla.sdk.android.core.player.listeners.SpallaPlayerListener
18
+ import com.spalla.sdk.android.core.player.view.SpallaPlayerView
19
+ import java.util.Timer
20
+
21
+ // These imports will work because Codegen runs for Fabric builds.
22
+ // Your IDE will show errors on these lines until you build the project.
23
+ import com.facebook.react.viewmanagers.SpallaPlayerViewManagerInterface
24
+ import com.facebook.react.viewmanagers.SpallaPlayerViewManagerDelegate
25
+
26
+ // Implement the Codegen interface for Fabric support
27
+ @ReactModule(name = SpallaPlayerViewManager.REACT_CLASS)
28
+ class SpallaPlayerViewManager() : ViewGroupManager<SpallaPlayerView>(),
29
+ SpallaPlayerViewManagerInterface<SpallaPlayerView>, // Fabric interface
30
+ SpallaPlayerListener, SpallaPlayerFullScreenListener {
31
+
32
+ companion object {
33
+ const val REACT_CLASS = "SpallaPlayerView"
34
+ }
35
+
36
+ // The delegate is required for Fabric to handle props and commands.
37
+ private val mDelegate = SpallaPlayerViewManagerDelegate(this)
38
+
39
+ // This method is required by the Fabric architecture.
40
+ override fun getDelegate() = mDelegate
41
+
42
+ private var _playerView: SpallaPlayerView? = null
43
+ private var _reactContext: ReactContext? = null
44
+ //private var _container: SpallaPlayerView? = null
45
+ private var contentId: String? = null
46
+ private var startTime: Double? = null
47
+ private var subtitle: String? = null
48
+ private var loadTimer: Timer? = null
49
+ private var playbackRate: Double = 1.0
50
+ private var hideUI: Boolean? = null
51
+ private var _viewTag: Int = -1
52
+
53
+ override fun getName() = REACT_CLASS
54
+
55
+ override fun createViewInstance(context: ThemedReactContext): SpallaPlayerView {
56
+ _reactContext = context
57
+ //val container = SpallaPlayerContainerView(context)
58
+ val player = SpallaPlayerView(context)
59
+
60
+ // Defer initialization until view is attached to window
61
+ player.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
62
+ override fun onViewAttachedToWindow(view: View) {
63
+ try {
64
+ _playerView = player
65
+ _playerView?.registerPlayerListener(this@SpallaPlayerViewManager)
66
+ _playerView?.registerFullScreenListener(this@SpallaPlayerViewManager)
67
+ // Remove listener to avoid memory leaks
68
+ view.removeOnAttachStateChangeListener(this)
69
+
70
+ checkAndLoadPlayer(player)
71
+ } catch (e: Exception) {
72
+ Log.e("SpallaPlayerViewManager", "Error during view attachment", e)
73
+ }
74
+ }
75
+
76
+ override fun onViewDetachedFromWindow(view: View) {
77
+ _playerView?.pause()
78
+
79
+ }
80
+ })
81
+
82
+ //this._container = container
83
+ //return container
84
+ return player
85
+ }
86
+
87
+ override fun onDropViewInstance(view: SpallaPlayerView) {
88
+ Log.v("RNSpallaPlayerManager", "onDropViewInstance")
89
+ try {
90
+ view.onDestroy()
91
+ } catch (e: Exception) {
92
+ e.printStackTrace()
93
+ }
94
+ super.onDropViewInstance(view)
95
+ }
96
+
97
+ // --- Prop Setters ---
98
+ @ReactProp(name = "contentId")
99
+ override fun setContentId(view: SpallaPlayerView, value: String?) {
100
+ _viewTag = view.tag as? Int ?: -1
101
+ if (value != null) {
102
+ this.contentId = value
103
+ //add a hacky delay here, so if content and starttime are changed at same time,
104
+ //start time is set first
105
+ view.postDelayed({
106
+ checkAndLoadPlayer(view)
107
+ }, 100L)
108
+ }
109
+ }
110
+
111
+ @ReactProp(name = "muted")
112
+ override fun setMuted(view: SpallaPlayerView, value: Boolean) {
113
+ _playerView?.setMuted(value)
114
+ }
115
+
116
+ @ReactProp(name = "startTime")
117
+ override fun setStartTime(view: SpallaPlayerView, value: Double) {
118
+ this.startTime = value
119
+ //checkAndLoadPlayer(view)
120
+ }
121
+
122
+ @ReactProp(name = "subtitle")
123
+ override fun setSubtitle(view: SpallaPlayerView, value: String?) {
124
+ this.subtitle = value
125
+ _playerView?.selectSubtitle(subtitle)
126
+ }
127
+
128
+ @ReactProp(name = "playbackRate")
129
+ override fun setPlaybackRate(view: SpallaPlayerView, value: Float) {
130
+ if (value > 0) {
131
+ this.playbackRate = value.toDouble()
132
+ _playerView?.selectPlaybackRate(playbackRate)
133
+ }
134
+ }
135
+
136
+ @ReactProp(name = "hideUI")
137
+ override fun setHideUI(view: SpallaPlayerView, value: Boolean) {
138
+ this.hideUI = value
139
+ //checkAndLoadPlayer(view)
140
+ }
141
+
142
+ override fun play(view: SpallaPlayerView?) {
143
+ view?.play()
144
+ }
145
+
146
+ override fun pause(view: SpallaPlayerView?) {
147
+ view?.pause()
148
+ }
149
+
150
+ override fun seekTo(
151
+ view: SpallaPlayerView?,
152
+ time: Double
153
+ ) {
154
+ view?.seekTo(time)
155
+ }
156
+
157
+ override fun selectSubtitle(
158
+ view: SpallaPlayerView?,
159
+ subtitle: String?
160
+ ) {
161
+ view?.selectSubtitle(subtitle)
162
+ }
163
+
164
+ override fun selectPlaybackRate(
165
+ view: SpallaPlayerView?,
166
+ rate: Float
167
+ ) {
168
+ view?.selectPlaybackRate(rate.toDouble())
169
+ }
170
+
171
+ override fun unmount(view: SpallaPlayerView?) {
172
+ view?.pause()
173
+ view?.onDestroy()
174
+ }
175
+
176
+ private fun checkAndLoadPlayer(view: SpallaPlayerView) {
177
+ try {
178
+ // Only load if view is attached to window and all required props are set
179
+ if (contentId != null && startTime != null && hideUI != null && view.isAttachedToWindow) {
180
+ view.load(contentId!!, true, startTime!!, subtitle, hideUI!!)
181
+ }
182
+ } catch (e: Exception) {
183
+ Log.e("SpallaPlayerViewManager", "Error loading player", e)
184
+ }
185
+ }
186
+
187
+ // --- Command Handling ---
188
+ override fun receiveCommand(
189
+ root: SpallaPlayerView,
190
+ commandId: String,
191
+ args: ReadableArray?
192
+ ) {
193
+ mDelegate.receiveCommand(root, commandId, args)
194
+ }
195
+
196
+ // --- Event Handling ---
197
+ override fun getExportedCustomBubblingEventTypeConstants(): MutableMap<String, Any> {
198
+ return mutableMapOf(
199
+ "onPlayerEvent" to mapOf(
200
+ "phasedRegistrationNames" to mapOf(
201
+ "bubbled" to "onPlayerEvent"
202
+ )
203
+ )
204
+ )
205
+ }
206
+
207
+ override fun onEvent(event: SpallaPlayerEvent) {
208
+ val map: WritableMap = Arguments.createMap()
209
+ val eventName = "onPlayerEvent"
210
+
211
+ Log.v("RNSpallaPlayerManager", "onEvent: $event")
212
+ when (event) {
213
+ is DurationUpdate -> {
214
+ map.putString("event", "durationUpdate")
215
+ map.putDouble("duration", event.duration)
216
+ }
217
+ Ended -> map.putString("event", "ended")
218
+ is Error -> {
219
+ map.putString("event", "error")
220
+ map.putString("message", event.message)
221
+ }
222
+ Pause -> map.putString("event", "pause")
223
+ Play -> map.putString("event", "play")
224
+ Playing -> map.putString("event", "playing")
225
+ is TimeUpdate -> {
226
+ map.putString("event", "timeUpdate")
227
+ map.putDouble("time", event.currentTime)
228
+ }
229
+ Waiting -> map.putString("event", "buffering")
230
+ is SubtitlesAvailable -> {
231
+ map.putString("event", "subtitlesAvailable")
232
+ val subs = Arguments.createArray()
233
+ event.subtitles.forEach { subs.pushString(it) }
234
+ map.putArray("subtitles", subs)
235
+ }
236
+ is SubtitleSelected -> {
237
+ map.putString("event", "subtitleSelected")
238
+ map.putString("subtitle", event.subtitle)
239
+ }
240
+ is MetadataLoaded -> {
241
+ map.putString("event", "metadataLoaded")
242
+ map.putBoolean("isLive", event.metadata.isLive)
243
+ map.putDouble("duration", event.metadata.duration)
244
+ _playerView?.selectPlaybackRate(playbackRate)
245
+ }
246
+ is PlaybackRateSelected -> {
247
+ map.putString("event", "playbackRateSelected")
248
+ map.putDouble("rate", event.rate)
249
+ }
250
+ }
251
+
252
+ _playerView?.let { view ->
253
+ _reactContext?.let { reactContext ->
254
+ val surfaceId = UIManagerHelper.getSurfaceId(reactContext)
255
+ val eventDispatcher = UIManagerHelper.getEventDispatcherForReactTag(reactContext, view.id)
256
+ eventDispatcher?.dispatchEvent(
257
+ RNSpallaPlayerEvent(surfaceId, view.id, eventName, map)
258
+ )
259
+ }
260
+ }
261
+ }
262
+
263
+ override fun onEnterFullScreen() {
264
+ val map: WritableMap = Arguments.createMap()
265
+ map.putString("event", "enterFullScreen")
266
+ dispatchEvent("onPlayerEvent", map)
267
+ }
268
+
269
+ override fun onExitFullScreen() {
270
+ val map: WritableMap = Arguments.createMap()
271
+ map.putString("event", "exitFullScreen")
272
+ dispatchEvent("onPlayerEvent", map)
273
+ }
274
+
275
+ private fun dispatchEvent(eventName: String, eventData: WritableMap) {
276
+ _playerView?.let { view ->
277
+ _reactContext?.let { reactContext ->
278
+ if (_viewTag != -1) {
279
+ val surfaceId = UIManagerHelper.getSurfaceId(reactContext)
280
+ val eventDispatcher = UIManagerHelper.getEventDispatcherForReactTag(reactContext, view.id)
281
+ eventDispatcher?.dispatchEvent(
282
+ RNSpallaPlayerEvent(surfaceId, _viewTag, eventName, eventData)
283
+ )
284
+ }
285
+ }
286
+ }
287
+ }
288
+
289
+ }
@@ -1,5 +1,3 @@
1
- package com.spallaplayer
2
-
3
1
  import android.util.Log
4
2
  import android.view.View
5
3
  import com.facebook.react.bridge.Arguments
@@ -14,14 +12,16 @@ import com.spalla.sdk.android.core.player.entities.SpallaPlayerEvent.*
14
12
  import com.spalla.sdk.android.core.player.listeners.SpallaPlayerFullScreenListener
15
13
  import com.spalla.sdk.android.core.player.listeners.SpallaPlayerListener
16
14
  import com.spalla.sdk.android.core.player.view.SpallaPlayerView
15
+
16
+
17
17
  import java.util.Timer
18
18
  import java.util.TimerTask
19
19
 
20
- class RNSpallaPlayerManager() : ViewGroupManager<SpallaPlayerContainerView>(),
20
+ class SpallaPlayerViewManager() : ViewGroupManager<SpallaPlayerView>(),
21
21
  SpallaPlayerListener, SpallaPlayerFullScreenListener {
22
22
  private var _playerView: SpallaPlayerView? = null
23
23
  private var _reactContext: ReactContext? = null
24
- private var _container: SpallaPlayerContainerView? = null
24
+ //private var _container: SpallaPlayerContainerView? = null
25
25
 
26
26
  private var contentId: String? = null
27
27
  private var startTime: Double? = null
@@ -32,15 +32,13 @@ class RNSpallaPlayerManager() : ViewGroupManager<SpallaPlayerContainerView>(),
32
32
 
33
33
  override fun getName() = "RNSpallaPlayer"
34
34
 
35
- override fun createViewInstance(context: ThemedReactContext): SpallaPlayerContainerView {
35
+ override fun createViewInstance(context: ThemedReactContext): SpallaPlayerView {
36
36
  _reactContext = context
37
- val container = SpallaPlayerContainerView(context)
38
- _playerView = container.spallaPlayerView
37
+ val player = SpallaPlayerView(context)
38
+ _playerView = player
39
39
  _playerView?.registerPlayerListener(this)
40
40
  _playerView?.registerFullScreenListener(this)
41
- this._container = container
42
-
43
- return container
41
+ return player
44
42
  }
45
43
 
46
44
  override fun getExportedCustomBubblingEventTypeConstants(): MutableMap<String, Any> {
@@ -55,58 +53,58 @@ class RNSpallaPlayerManager() : ViewGroupManager<SpallaPlayerContainerView>(),
55
53
  return eventMap
56
54
  }
57
55
 
58
- override fun onDropViewInstance(view: SpallaPlayerContainerView) {
56
+ override fun onDropViewInstance(view: SpallaPlayerView) {
59
57
  Log.v("RNSpallaPlayerManager", "onDropViewInstance")
60
58
 
61
59
  try {
62
- view.spallaPlayerView.onDestroy()
60
+ view.onDestroy()
63
61
  } catch (e: Exception) {
64
62
  e.printStackTrace()
65
63
  }
66
64
  super.onDropViewInstance(view)
67
65
  }
68
66
 
69
- override fun addView(parent: SpallaPlayerContainerView, child: View, index: Int) {
67
+ override fun addView(parent: SpallaPlayerView, child: View, index: Int) {
70
68
  parent.addView(child, index)
71
69
  }
72
70
 
73
- override fun removeViewAt(parent: SpallaPlayerContainerView, index: Int) {
71
+ override fun removeViewAt(parent: SpallaPlayerView, index: Int) {
74
72
  parent.removeViewAt(index)
75
73
  }
76
74
 
77
- override fun getChildCount(parent: SpallaPlayerContainerView): Int {
75
+ override fun getChildCount(parent: SpallaPlayerView): Int {
78
76
  return parent.childCount
79
77
  }
80
78
 
81
- override fun getChildAt(parent: SpallaPlayerContainerView, index: Int): View {
79
+ override fun getChildAt(parent: SpallaPlayerView, index: Int): View {
82
80
  return parent.getChildAt(index)
83
81
  }
84
82
 
85
83
  @ReactProp(name = "contentId")
86
- fun setContentId(view: SpallaPlayerContainerView, contentId: String) {
84
+ fun setContentId(view: SpallaPlayerView, contentId: String) {
87
85
  this.contentId = contentId
88
86
  checkAndLoadPlayer(view)
89
87
  }
90
88
 
91
89
  @ReactProp(name = "muted")
92
- fun setMuted(view: SpallaPlayerContainerView, muted: Boolean) {
90
+ fun setMuted(view: SpallaPlayerView, muted: Boolean) {
93
91
  _playerView?.setMuted(muted)
94
92
  }
95
93
 
96
94
  @ReactProp(name = "startTime")
97
- fun setStartTime(view: SpallaPlayerContainerView, startTime: Double) {
95
+ fun setStartTime(view: SpallaPlayerView, startTime: Double) {
98
96
  this.startTime = startTime
99
97
  //checkAndLoadPlayer(view)
100
98
  }
101
99
 
102
100
  @ReactProp(name = "subtitle")
103
- fun setSubtitle(view: SpallaPlayerContainerView, subtitle: String?) {
101
+ fun setSubtitle(view: SpallaPlayerView, subtitle: String?) {
104
102
  this.subtitle = subtitle
105
103
  _playerView?.selectSubtitle(subtitle)
106
104
  }
107
105
 
108
106
  @ReactProp(name = "playbackRate")
109
- fun setPlaybackRate(view: SpallaPlayerContainerView, playbackRate: Double) {
107
+ fun setPlaybackRate(view: SpallaPlayerView, playbackRate: Double) {
110
108
  if (playbackRate > 0) {
111
109
  this.playbackRate = playbackRate
112
110
  _playerView?.selectPlaybackRate(playbackRate)
@@ -114,14 +112,14 @@ class RNSpallaPlayerManager() : ViewGroupManager<SpallaPlayerContainerView>(),
114
112
  }
115
113
 
116
114
  @ReactProp(name = "hideUI")
117
- fun setPlaybackRate(view: SpallaPlayerContainerView, hideUI: Boolean) {
115
+ fun setHideUI(view: SpallaPlayerView, hideUI: Boolean) {
118
116
  this.hideUI = hideUI
119
117
  checkAndLoadPlayer(view)
120
118
  }
121
119
 
122
- private fun checkAndLoadPlayer(view: SpallaPlayerContainerView) {
120
+ private fun checkAndLoadPlayer(view: SpallaPlayerView) {
123
121
  if (contentId != null && startTime != null && hideUI != null) {
124
- view.spallaPlayerView.load(contentId!!, true, startTime!!, subtitle, hideUI!!)
122
+ view.load(contentId!!, true, startTime!!, subtitle, hideUI!!)
125
123
  }
126
124
  }
127
125
 
@@ -196,7 +194,7 @@ class RNSpallaPlayerManager() : ViewGroupManager<SpallaPlayerContainerView>(),
196
194
  }
197
195
 
198
196
  }
199
- _container?.let { container ->
197
+ _playerView?.let { container ->
200
198
  _reactContext?.getJSModule(RCTEventEmitter::class.java)?.receiveEvent(
201
199
  container.id,
202
200
  "onPlayerEvent",
@@ -209,7 +207,7 @@ class RNSpallaPlayerManager() : ViewGroupManager<SpallaPlayerContainerView>(),
209
207
  Log.v("SpallaPlayerViewManager", "onEnterFullScreen")
210
208
  val map: WritableMap = Arguments.createMap()
211
209
  map.putString("event", "enterFullScreen")
212
- _container?.let { container ->
210
+ _playerView?.let { container ->
213
211
  _reactContext?.getJSModule(RCTEventEmitter::class.java)?.receiveEvent(
214
212
  container.id,
215
213
  "onPlayerEvent",
@@ -221,7 +219,7 @@ class RNSpallaPlayerManager() : ViewGroupManager<SpallaPlayerContainerView>(),
221
219
  override fun onExitFullScreen() {
222
220
  val map: WritableMap = Arguments.createMap()
223
221
  map.putString("event", "exitFullScreen")
224
- _container?.let { container ->
222
+ _playerView?.let { container ->
225
223
  _reactContext?.getJSModule(RCTEventEmitter::class.java)?.receiveEvent(
226
224
  container.id,
227
225
  "onPlayerEvent",
@@ -0,0 +1,17 @@
1
+ //
2
+ // SpallaPlayerModule.h
3
+ // Pods
4
+ //
5
+ // Created by Rogerio Shimizu on 12/19/25.
6
+ //
7
+
8
+ #import <Foundation/Foundation.h>
9
+ #import <SpallaPlayerViewSpec/SpallaPlayerViewSpec.h>
10
+
11
+ NS_ASSUME_NONNULL_BEGIN
12
+
13
+ @interface SpallaPlayerModule : NSObject <NativeSpallaPlayerModuleSpec>
14
+
15
+ @end
16
+
17
+ NS_ASSUME_NONNULL_END
@@ -0,0 +1,59 @@
1
+ //
2
+ // SpallaPlayerModule.mm
3
+ // Pods
4
+ //
5
+ // Created by Rogerio Shimizu on 12/19/25.
6
+ //
7
+
8
+ #import "SpallaPlayerModule.h"
9
+ #import "SpallaPlayer-Swift.h"
10
+
11
+ @interface SpallaPlayerModule ()
12
+
13
+ @end
14
+
15
+ @implementation SpallaPlayerModule
16
+
17
+ + (NSString *)moduleName
18
+ {
19
+ return @"SpallaPlayerModule";
20
+ }
21
+
22
+ - (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:(const facebook::react::ObjCTurboModule::InitParams &)params {
23
+ return std::make_shared<facebook::react::NativeSpallaPlayerModuleSpecJSI>(params);
24
+ }
25
+
26
+ - (void)initialize:(nonnull NSString *)token applicationId:(nonnull NSString *)applicationId {
27
+ dispatch_async(dispatch_get_main_queue(), ^{
28
+ [SpallaPlayerWrapper initializeWithToken: token applicationId: applicationId];
29
+ });
30
+ }
31
+
32
+ - (void)pause:(NSInteger)tag {
33
+
34
+ }
35
+
36
+ - (void)play:(NSInteger)tag {
37
+
38
+ }
39
+
40
+ - (void)seekTo:(NSInteger)tag time:(double)time {
41
+
42
+ }
43
+
44
+ - (void)selectPlaybackRate:(NSInteger)tag rate:(double)rate {
45
+
46
+ }
47
+
48
+ - (void)selectSubtitle:(NSInteger)tag subtitle:(NSString * _Nullable)subtitle {
49
+
50
+ }
51
+
52
+ - (void)unmount:(NSInteger)tag {
53
+
54
+ }
55
+
56
+
57
+ @end
58
+
59
+
@@ -0,0 +1,14 @@
1
+ #import <React/RCTViewComponentView.h>
2
+ #import <UIKit/UIKit.h>
3
+
4
+ #ifndef SpallaPlayerViewNativeComponent_h
5
+ #define SpallaPlayerViewNativeComponent_h
6
+
7
+ NS_ASSUME_NONNULL_BEGIN
8
+
9
+ @interface SpallaPlayerView : RCTViewComponentView
10
+ @end
11
+
12
+ NS_ASSUME_NONNULL_END
13
+
14
+ #endif /* SpallaPlayerViewNativeComponent_h */
@@ -0,0 +1,158 @@
1
+ #import "SpallaPlayerView.h"
2
+
3
+ #import <react/renderer/components/SpallaPlayerViewSpec/ComponentDescriptors.h>
4
+ #import <react/renderer/components/SpallaPlayerViewSpec/EventEmitters.h>
5
+ #import <react/renderer/components/SpallaPlayerViewSpec/Props.h>
6
+ #import <react/renderer/components/SpallaPlayerViewSpec/RCTComponentViewHelpers.h>
7
+
8
+ #import "RCTFabricComponentsPlugins.h"
9
+ #import "SpallaPlayer-Swift.h"
10
+
11
+
12
+ using namespace facebook::react;
13
+
14
+ @interface SpallaPlayerView () <RCTSpallaPlayerViewViewProtocol>
15
+
16
+ @end
17
+
18
+ @implementation SpallaPlayerView {
19
+ SpallaPlayerWrapper * _view;
20
+ }
21
+
22
+ + (ComponentDescriptorProvider)componentDescriptorProvider
23
+ {
24
+ return concreteComponentDescriptorProvider<SpallaPlayerViewComponentDescriptor>();
25
+ }
26
+
27
+ - (instancetype)initWithFrame:(CGRect)frame
28
+ {
29
+ if (self = [super initWithFrame:frame]) {
30
+ static const auto defaultProps = std::make_shared<const SpallaPlayerViewProps>();
31
+ _props = defaultProps;
32
+
33
+ [self ensureFreshWrapper];
34
+ }
35
+
36
+ return self;
37
+ }
38
+
39
+ - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps
40
+ {
41
+ const auto &oldViewProps = *std::static_pointer_cast<SpallaPlayerViewProps const>(_props);
42
+ const auto &newViewProps = *std::static_pointer_cast<SpallaPlayerViewProps const>(props);
43
+
44
+ BOOL didRefresh = [self ensureFreshWrapper];
45
+
46
+ if (oldViewProps.contentId != newViewProps.contentId || didRefresh) {
47
+ [_view setContentId: [[NSString alloc] initWithUTF8String: newViewProps.contentId.c_str()]];
48
+ }
49
+
50
+ if (oldViewProps.startTime != newViewProps.startTime || didRefresh) {
51
+ [_view setStartTime: [NSNumber numberWithDouble: newViewProps.startTime]];
52
+ }
53
+
54
+ if (oldViewProps.hideUI != newViewProps.hideUI || didRefresh) {
55
+ [_view setHideUI: newViewProps.hideUI];
56
+ }
57
+
58
+ if (oldViewProps.muted != newViewProps.muted || didRefresh) {
59
+ [_view setMuted: newViewProps.muted];
60
+ }
61
+
62
+ if (oldViewProps.playbackRate != newViewProps.playbackRate || didRefresh) {
63
+ [_view setPlaybackRate: [NSNumber numberWithDouble: newViewProps.playbackRate]];
64
+ }
65
+
66
+ if (oldViewProps.subtitle != newViewProps.subtitle || didRefresh) {
67
+ // SpallaPlayerViewProps::subtitle is a std::string (not an optional), so use empty() to check for "no subtitle".
68
+ if (!newViewProps.subtitle.empty()) {
69
+ [_view setSubtitle: [[NSString alloc] initWithUTF8String: newViewProps.subtitle.c_str()]];
70
+ } else {
71
+ [_view setSubtitle: nil];
72
+ }
73
+ }
74
+
75
+ [super updateProps:props oldProps:oldProps];
76
+
77
+ }
78
+
79
+ - (BOOL)ensureFreshWrapper {
80
+ if (_view == nil) {
81
+ _view = [[SpallaPlayerWrapper alloc] init];
82
+
83
+ __weak __typeof(self) weakSelf = self;
84
+ [_view setOnPlayerEvent:^(NSDictionary<NSString *,id> * _Nonnull value) {
85
+ SpallaPlayerViewEventEmitter::OnPlayerEvent event = SpallaPlayerViewEventEmitter::OnPlayerEvent{
86
+ .event = std::string([[value objectForKey:@"event"] UTF8String]),
87
+ .duration = value[@"duration"] != nil ? [value[@"duration"] doubleValue] : 0.0,
88
+ .time = value[@"time"] != nil ? [value[@"time"] doubleValue] : 0.0,
89
+ .rate = value[@"rate"] != nil ? [value[@"rate"] doubleValue] : 0.0,
90
+ .subtitle = value[@"subtitle"] != nil ? std::string([value[@"subtitle"] UTF8String]) : std::string(),
91
+ .isLive = value[@"isLive"] != nil ? [value[@"isLive"] boolValue] : false,
92
+ .error = value[@"error"] != nil ? std::string([value[@"error"] UTF8String]) : std::string()
93
+
94
+ };
95
+ weakSelf.eventEmitter.onPlayerEvent(event);
96
+ }];
97
+
98
+ self.contentView = _view;
99
+ return YES;
100
+ }
101
+ return NO;
102
+
103
+ }
104
+
105
+ - (void)updateState:(const facebook::react::State::Shared &)state
106
+ oldState:(const facebook::react::State::Shared &)oldState {
107
+ [super updateState:state oldState:oldState];
108
+
109
+ }
110
+
111
+ - (void)handleCommand:(const NSString *)commandName args:(const NSArray *)args
112
+ {
113
+ RCTSpallaPlayerViewHandleCommand(self, commandName, args);
114
+ }
115
+
116
+ - (void)pause {
117
+ [_view pause];
118
+ }
119
+
120
+ - (void)play {
121
+ [_view play];
122
+ }
123
+
124
+ - (void) seekTo:(double)time {
125
+ [_view seekToTime: time];
126
+ }
127
+
128
+ - (void) selectPlaybackRate:(float)rate {
129
+ [_view selectPlaybackRate: rate];
130
+ }
131
+
132
+ - (void) selectSubtitle:(NSString *)subtitle {
133
+ [_view selectSubtitle: subtitle];
134
+ }
135
+
136
+ - (void) unmount {
137
+ [_view unmount];
138
+ }
139
+
140
+ - (void)prepareForRecycle {
141
+ [super prepareForRecycle];
142
+
143
+ [_view setOnPlayerEvent: nil];
144
+ [_view unmount];
145
+ _view = nil;
146
+ }
147
+
148
+ - (const SpallaPlayerViewEventEmitter &)eventEmitter
149
+ {
150
+ return static_cast<const SpallaPlayerViewEventEmitter &>(*_eventEmitter);
151
+ }
152
+
153
+ Class<RCTComponentViewProtocol> SpallaPlayerViewCls(void)
154
+ {
155
+ return SpallaPlayerView.class;
156
+ }
157
+
158
+ @end