react-native-spalla-player 0.13.3 → 1.0.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 (50) 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.kt +64 -150
  8. package/android/src/main/java/com/spallaplayer/SpallaPlayerPackage.kt +15 -3
  9. package/android/src/main/java/com/spallaplayer/SpallaPlayerViewManager.kt +164 -107
  10. package/ios/SpallaPlayerModule.h +17 -0
  11. package/ios/SpallaPlayerModule.mm +59 -0
  12. package/ios/SpallaPlayerView.h +14 -0
  13. package/ios/SpallaPlayerView.mm +158 -0
  14. package/ios/SpallaPlayerWrapper.swift +12 -8
  15. package/lib/module/NativeSpallaPlayerModule.js +5 -0
  16. package/lib/module/NativeSpallaPlayerModule.js.map +1 -0
  17. package/lib/module/SpallaPlayerViewNativeComponent.js +11 -0
  18. package/lib/module/SpallaPlayerViewNativeComponent.js.map +1 -0
  19. package/lib/module/index.js +48 -55
  20. package/lib/module/index.js.map +1 -1
  21. package/lib/typescript/src/NativeSpallaPlayerModule.d.ts +13 -0
  22. package/lib/typescript/src/NativeSpallaPlayerModule.d.ts.map +1 -0
  23. package/lib/typescript/src/SpallaPlayerViewNativeComponent.d.ts +33 -0
  24. package/lib/typescript/src/SpallaPlayerViewNativeComponent.d.ts.map +1 -0
  25. package/lib/typescript/src/components/CastButton.d.ts.map +1 -0
  26. package/lib/typescript/{module/src → src}/index.d.ts +9 -15
  27. package/lib/typescript/src/index.d.ts.map +1 -0
  28. package/package.json +93 -103
  29. package/src/NativeSpallaPlayerModule.ts +14 -0
  30. package/src/SpallaPlayerViewNativeComponent.ts +61 -0
  31. package/src/index.tsx +71 -82
  32. package/android/src/main/AndroidManifestNew.xml +0 -2
  33. package/android/src/main/java/com/spallaplayer/SpallaPlayerContainerView.kt +0 -36
  34. package/ios/RNSpallaPlayer.m +0 -102
  35. package/ios/RNSpallaPlayer.swift +0 -11
  36. package/ios/SpallaPlayer-Bridging-Header.h +0 -1
  37. package/lib/commonjs/components/CastButton.js +0 -49
  38. package/lib/commonjs/components/CastButton.js.map +0 -1
  39. package/lib/commonjs/index.js +0 -81
  40. package/lib/commonjs/index.js.map +0 -1
  41. package/lib/typescript/commonjs/package.json +0 -1
  42. package/lib/typescript/commonjs/src/components/CastButton.d.ts.map +0 -1
  43. package/lib/typescript/commonjs/src/index.d.ts +0 -61
  44. package/lib/typescript/commonjs/src/index.d.ts.map +0 -1
  45. package/lib/typescript/module/src/components/CastButton.d.ts +0 -13
  46. package/lib/typescript/module/src/components/CastButton.d.ts.map +0 -1
  47. package/lib/typescript/module/src/index.d.ts.map +0 -1
  48. package/react-native-spalla-player.podspec +0 -44
  49. /package/lib/{typescript/module → module}/package.json +0 -0
  50. /package/lib/typescript/{commonjs/src → src}/components/CastButton.d.ts +0 -0
@@ -4,145 +4,210 @@ import android.util.Log
4
4
  import android.view.View
5
5
  import com.facebook.react.bridge.Arguments
6
6
  import com.facebook.react.bridge.ReactContext
7
+ import com.facebook.react.bridge.ReadableArray
7
8
  import com.facebook.react.bridge.WritableMap
9
+ import com.facebook.react.module.annotations.ReactModule
8
10
  import com.facebook.react.uimanager.ViewGroupManager
9
11
  import com.facebook.react.uimanager.ThemedReactContext
12
+ import com.facebook.react.uimanager.UIManagerHelper
13
+ import com.facebook.react.uimanager.ViewManagerDelegate
10
14
  import com.facebook.react.uimanager.annotations.ReactProp
11
- import com.facebook.react.uimanager.events.RCTEventEmitter
12
15
  import com.spalla.sdk.android.core.player.entities.SpallaPlayerEvent
13
16
  import com.spalla.sdk.android.core.player.entities.SpallaPlayerEvent.*
14
17
  import com.spalla.sdk.android.core.player.listeners.SpallaPlayerFullScreenListener
15
18
  import com.spalla.sdk.android.core.player.listeners.SpallaPlayerListener
16
19
  import com.spalla.sdk.android.core.player.view.SpallaPlayerView
17
20
  import java.util.Timer
18
- import java.util.TimerTask
19
21
 
20
- class RNSpallaPlayerManager() : ViewGroupManager<SpallaPlayerContainerView>(),
22
+ // These imports will work because Codegen runs for Fabric builds.
23
+ // Your IDE will show errors on these lines until you build the project.
24
+ import com.facebook.react.viewmanagers.SpallaPlayerViewManagerInterface
25
+ import com.facebook.react.viewmanagers.SpallaPlayerViewManagerDelegate
26
+
27
+ // Implement the Codegen interface for Fabric support
28
+ @ReactModule(name = SpallaPlayerViewManager.REACT_CLASS)
29
+ class SpallaPlayerViewManager() : ViewGroupManager<SpallaPlayerView>(),
30
+ SpallaPlayerViewManagerInterface<SpallaPlayerView>, // Fabric interface
21
31
  SpallaPlayerListener, SpallaPlayerFullScreenListener {
32
+
33
+ companion object {
34
+ const val REACT_CLASS = "SpallaPlayerView"
35
+ }
36
+
37
+ // The delegate is required for Fabric to handle props and commands.
38
+ private val mDelegate = SpallaPlayerViewManagerDelegate(this)
39
+
40
+ // This method is required by the Fabric architecture.
41
+ override fun getDelegate(): ViewManagerDelegate<SpallaPlayerView> = mDelegate
42
+
22
43
  private var _playerView: SpallaPlayerView? = null
23
44
  private var _reactContext: ReactContext? = null
24
- private var _container: SpallaPlayerContainerView? = null
25
-
45
+ //private var _container: SpallaPlayerView? = null
26
46
  private var contentId: String? = null
27
47
  private var startTime: Double? = null
28
48
  private var subtitle: String? = null
29
49
  private var loadTimer: Timer? = null
30
50
  private var playbackRate: Double = 1.0
31
51
  private var hideUI: Boolean? = null
52
+ private var _viewTag: Int = -1
32
53
 
33
- override fun getName() = "RNSpallaPlayer"
54
+ override fun getName() = REACT_CLASS
34
55
 
35
- override fun createViewInstance(context: ThemedReactContext): SpallaPlayerContainerView {
56
+ override fun createViewInstance(context: ThemedReactContext): SpallaPlayerView {
36
57
  _reactContext = context
37
- val container = SpallaPlayerContainerView(context)
38
- _playerView = container.spallaPlayerView
39
- _playerView?.registerPlayerListener(this)
40
- _playerView?.registerFullScreenListener(this)
41
- this._container = container
42
-
43
- return container
44
- }
58
+ //val container = SpallaPlayerContainerView(context)
59
+ val player = SpallaPlayerView(context)
60
+
61
+ // Defer initialization until view is attached to window
62
+ player.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
63
+ override fun onViewAttachedToWindow(view: View) {
64
+ try {
65
+ _playerView = player
66
+ _playerView?.registerPlayerListener(this@SpallaPlayerViewManager)
67
+ _playerView?.registerFullScreenListener(this@SpallaPlayerViewManager)
68
+ // Remove listener to avoid memory leaks
69
+ view.removeOnAttachStateChangeListener(this)
70
+
71
+ checkAndLoadPlayer(player)
72
+ } catch (e: Exception) {
73
+ Log.e("SpallaPlayerViewManager", "Error during view attachment", e)
74
+ }
75
+ }
45
76
 
46
- override fun getExportedCustomBubblingEventTypeConstants(): MutableMap<String, Any> {
47
- val eventMap = mutableMapOf<String, Any>()
77
+ override fun onViewDetachedFromWindow(view: View) {
78
+ _playerView?.pause()
48
79
 
49
- eventMap["onPlayerEvent"] = mapOf(
50
- "phasedRegistrationNames" to mapOf(
51
- "bubbled" to "onPlayerEvent"
52
- )
53
- )
80
+ }
81
+ })
54
82
 
55
- return eventMap
83
+ //this._container = container
84
+ //return container
85
+ return player
56
86
  }
57
87
 
58
- override fun onDropViewInstance(view: SpallaPlayerContainerView) {
88
+ override fun onDropViewInstance(view: SpallaPlayerView) {
59
89
  Log.v("RNSpallaPlayerManager", "onDropViewInstance")
60
-
61
90
  try {
62
- view.spallaPlayerView.onDestroy()
91
+ view.onDestroy()
63
92
  } catch (e: Exception) {
64
93
  e.printStackTrace()
65
94
  }
66
95
  super.onDropViewInstance(view)
67
96
  }
68
97
 
69
- override fun addView(parent: SpallaPlayerContainerView, child: View, index: Int) {
70
- parent.addView(child, index)
71
- }
72
-
73
- override fun removeViewAt(parent: SpallaPlayerContainerView, index: Int) {
74
- parent.removeViewAt(index)
75
- }
76
-
77
- override fun getChildCount(parent: SpallaPlayerContainerView): Int {
78
- return parent.childCount
79
- }
80
-
81
- override fun getChildAt(parent: SpallaPlayerContainerView, index: Int): View {
82
- return parent.getChildAt(index)
83
- }
84
-
98
+ // --- Prop Setters ---
85
99
  @ReactProp(name = "contentId")
86
- fun setContentId(view: SpallaPlayerContainerView, contentId: String) {
87
- this.contentId = contentId
88
- checkAndLoadPlayer(view)
100
+ override fun setContentId(view: SpallaPlayerView, value: String?) {
101
+ _viewTag = view.tag as? Int ?: -1
102
+ if (value != null) {
103
+ this.contentId = value
104
+ //add a hacky delay here, so if content and starttime are changed at same time,
105
+ //start time is set first
106
+ view.postDelayed({
107
+ checkAndLoadPlayer(view)
108
+ }, 100L)
109
+ }
89
110
  }
90
111
 
91
112
  @ReactProp(name = "muted")
92
- fun setMuted(view: SpallaPlayerContainerView, muted: Boolean) {
93
- _playerView?.setMuted(muted)
113
+ override fun setMuted(view: SpallaPlayerView, value: Boolean) {
114
+ _playerView?.setMuted(value)
94
115
  }
95
116
 
96
117
  @ReactProp(name = "startTime")
97
- fun setStartTime(view: SpallaPlayerContainerView, startTime: Double) {
98
- this.startTime = startTime
118
+ override fun setStartTime(view: SpallaPlayerView, value: Double) {
119
+ this.startTime = value
99
120
  //checkAndLoadPlayer(view)
100
121
  }
101
122
 
102
123
  @ReactProp(name = "subtitle")
103
- fun setSubtitle(view: SpallaPlayerContainerView, subtitle: String?) {
104
- this.subtitle = subtitle
124
+ override fun setSubtitle(view: SpallaPlayerView, value: String?) {
125
+ this.subtitle = value
105
126
  _playerView?.selectSubtitle(subtitle)
106
127
  }
107
128
 
108
129
  @ReactProp(name = "playbackRate")
109
- fun setPlaybackRate(view: SpallaPlayerContainerView, playbackRate: Double) {
110
- if (playbackRate > 0) {
111
- this.playbackRate = playbackRate
130
+ override fun setPlaybackRate(view: SpallaPlayerView, value: Float) {
131
+ if (value > 0) {
132
+ this.playbackRate = value.toDouble()
112
133
  _playerView?.selectPlaybackRate(playbackRate)
113
134
  }
114
135
  }
115
136
 
116
137
  @ReactProp(name = "hideUI")
117
- fun setPlaybackRate(view: SpallaPlayerContainerView, hideUI: Boolean) {
118
- this.hideUI = hideUI
119
- checkAndLoadPlayer(view)
138
+ override fun setHideUI(view: SpallaPlayerView, value: Boolean) {
139
+ this.hideUI = value
140
+ //checkAndLoadPlayer(view)
120
141
  }
121
142
 
122
- private fun checkAndLoadPlayer(view: SpallaPlayerContainerView) {
123
- if (contentId != null && startTime != null && hideUI != null) {
124
- view.spallaPlayerView.load(contentId!!, true, startTime!!, subtitle, hideUI!!)
125
- }
143
+ override fun play(view: SpallaPlayerView?) {
144
+ view?.play()
145
+ }
146
+
147
+ override fun pause(view: SpallaPlayerView?) {
148
+ view?.pause()
149
+ }
150
+
151
+ override fun seekTo(
152
+ view: SpallaPlayerView?,
153
+ time: Double
154
+ ) {
155
+ view?.seekTo(time)
156
+ }
157
+
158
+ override fun selectSubtitle(
159
+ view: SpallaPlayerView?,
160
+ subtitle: String?
161
+ ) {
162
+ view?.selectSubtitle(subtitle)
126
163
  }
127
164
 
128
- private fun startLoadTimer(view: SpallaPlayerView) {
129
- // Cancel any existing timer
130
- loadTimer?.cancel()
165
+ override fun selectPlaybackRate(
166
+ view: SpallaPlayerView?,
167
+ rate: Float
168
+ ) {
169
+ view?.selectPlaybackRate(rate.toDouble())
170
+ }
171
+
172
+ override fun unmount(view: SpallaPlayerView?) {
173
+ view?.pause()
174
+ view?.onDestroy()
175
+ }
131
176
 
132
- // Start a new timer to check if all required properties are set
133
- loadTimer = Timer()
134
- loadTimer?.schedule(object : TimerTask() {
135
- override fun run() {
136
- loadTimer?.cancel()
137
- loadTimer = null
138
- // Call view.load with the required properties
139
- view.load(contentId!!, true, startTime!!, subtitle = subtitle, hideUI!!)
177
+ private fun checkAndLoadPlayer(view: SpallaPlayerView) {
178
+ try {
179
+ // Only load if view is attached to window and all required props are set
180
+ if (contentId != null && startTime != null && hideUI != null && view.isAttachedToWindow) {
181
+ view.load(contentId!!, true, startTime!!, subtitle, hideUI!!)
140
182
  }
141
- }, 300) // Delay of 200ms to allow all properties to be set
183
+ } catch (e: Exception) {
184
+ Log.e("SpallaPlayerViewManager", "Error loading player", e)
185
+ }
186
+ }
187
+
188
+ // --- Command Handling ---
189
+ override fun receiveCommand(
190
+ root: SpallaPlayerView,
191
+ commandId: String,
192
+ args: ReadableArray?
193
+ ) {
194
+ mDelegate.receiveCommand(root, commandId, args)
195
+ }
196
+
197
+ // --- Event Handling ---
198
+ override fun getExportedCustomBubblingEventTypeConstants(): MutableMap<String, Any> {
199
+ return mutableMapOf(
200
+ "onPlayerEvent" to mapOf(
201
+ "phasedRegistrationNames" to mapOf(
202
+ "bubbled" to "onPlayerEvent"
203
+ )
204
+ )
205
+ )
142
206
  }
143
207
 
144
208
  override fun onEvent(event: SpallaPlayerEvent) {
145
209
  val map: WritableMap = Arguments.createMap()
210
+ val eventName = "onPlayerEvent"
146
211
 
147
212
  Log.v("RNSpallaPlayerManager", "onEvent: $event")
148
213
  when (event) {
@@ -150,13 +215,11 @@ class RNSpallaPlayerManager() : ViewGroupManager<SpallaPlayerContainerView>(),
150
215
  map.putString("event", "durationUpdate")
151
216
  map.putDouble("duration", event.duration)
152
217
  }
153
-
154
218
  Ended -> map.putString("event", "ended")
155
219
  is Error -> {
156
220
  map.putString("event", "error")
157
221
  map.putString("message", event.message)
158
222
  }
159
-
160
223
  Pause -> map.putString("event", "pause")
161
224
  Play -> map.putString("event", "play")
162
225
  Playing -> map.putString("event", "playing")
@@ -164,69 +227,63 @@ class RNSpallaPlayerManager() : ViewGroupManager<SpallaPlayerContainerView>(),
164
227
  map.putString("event", "timeUpdate")
165
228
  map.putDouble("time", event.currentTime)
166
229
  }
167
-
168
230
  Waiting -> map.putString("event", "buffering")
169
231
  is SubtitlesAvailable -> {
170
232
  map.putString("event", "subtitlesAvailable")
171
-
172
- var subs = Arguments.createArray()
173
- event.subtitles.forEach {
174
- subs.pushString(it)
175
- }
233
+ val subs = Arguments.createArray()
234
+ event.subtitles.forEach { subs.pushString(it) }
176
235
  map.putArray("subtitles", subs)
177
236
  }
178
-
179
237
  is SubtitleSelected -> {
180
238
  map.putString("event", "subtitleSelected")
181
239
  map.putString("subtitle", event.subtitle)
182
240
  }
183
-
184
241
  is MetadataLoaded -> {
185
242
  map.putString("event", "metadataLoaded")
186
243
  map.putBoolean("isLive", event.metadata.isLive)
187
244
  map.putDouble("duration", event.metadata.duration)
188
-
189
- // make sure current playback rate is applied if set initially
190
245
  _playerView?.selectPlaybackRate(playbackRate)
191
246
  }
192
-
193
247
  is PlaybackRateSelected -> {
194
248
  map.putString("event", "playbackRateSelected")
195
249
  map.putDouble("rate", event.rate)
196
250
  }
197
-
198
251
  }
199
- _container?.let { container ->
200
- _reactContext?.getJSModule(RCTEventEmitter::class.java)?.receiveEvent(
201
- container.id,
202
- "onPlayerEvent",
203
- map
204
- )
252
+
253
+ _playerView?.let { view ->
254
+ _reactContext?.let { reactContext ->
255
+ val surfaceId = UIManagerHelper.getSurfaceId(reactContext)
256
+ val eventDispatcher = UIManagerHelper.getEventDispatcherForReactTag(reactContext, view.id)
257
+ eventDispatcher?.dispatchEvent(
258
+ RNSpallaPlayerEvent(surfaceId, view.id, eventName, map)
259
+ )
260
+ }
205
261
  }
206
262
  }
207
263
 
208
264
  override fun onEnterFullScreen() {
209
- Log.v("SpallaPlayerViewManager", "onEnterFullScreen")
210
265
  val map: WritableMap = Arguments.createMap()
211
266
  map.putString("event", "enterFullScreen")
212
- _container?.let { container ->
213
- _reactContext?.getJSModule(RCTEventEmitter::class.java)?.receiveEvent(
214
- container.id,
215
- "onPlayerEvent",
216
- map
217
- )
218
- }
267
+ dispatchEvent("onPlayerEvent", map)
219
268
  }
220
269
 
221
270
  override fun onExitFullScreen() {
222
271
  val map: WritableMap = Arguments.createMap()
223
272
  map.putString("event", "exitFullScreen")
224
- _container?.let { container ->
225
- _reactContext?.getJSModule(RCTEventEmitter::class.java)?.receiveEvent(
226
- container.id,
227
- "onPlayerEvent",
228
- map
229
- )
273
+ dispatchEvent("onPlayerEvent", map)
274
+ }
275
+
276
+ private fun dispatchEvent(eventName: String, eventData: WritableMap) {
277
+ _playerView?.let { view ->
278
+ _reactContext?.let { reactContext ->
279
+ if (_viewTag != -1) {
280
+ val surfaceId = UIManagerHelper.getSurfaceId(reactContext)
281
+ val eventDispatcher = UIManagerHelper.getEventDispatcherForReactTag(reactContext, view.id)
282
+ eventDispatcher?.dispatchEvent(
283
+ RNSpallaPlayerEvent(surfaceId, _viewTag, eventName, eventData)
284
+ )
285
+ }
286
+ }
230
287
  }
231
288
  }
232
289
 
@@ -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