react-native-unified-player 0.3.4 → 0.3.6

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.
@@ -62,4 +62,4 @@ class UnifiedPlayerEventEmitter(private val reactContext: ReactApplicationContex
62
62
  super.onCatalystInstanceDestroy()
63
63
  instance = null
64
64
  }
65
- }
65
+ }
@@ -1,187 +1,198 @@
1
1
  package com.unifiedplayer
2
2
 
3
- import android.graphics.Bitmap
4
- import android.util.Base64
5
- import android.util.Log
6
- import com.facebook.react.bridge.*
3
+ import com.facebook.react.bridge.ReactApplicationContext
4
+ import com.facebook.react.bridge.ReactContextBaseJavaModule
5
+ import com.facebook.react.bridge.ReactMethod
6
+ import com.facebook.react.bridge.Promise
7
7
  import com.facebook.react.uimanager.UIManagerModule
8
- import com.facebook.react.uimanager.UIBlock
9
- import com.facebook.react.uimanager.NativeViewHierarchyManager
10
- import java.io.ByteArrayOutputStream
8
+ import android.util.Log
9
+ import com.facebook.react.bridge.UiThreadUtil
10
+ import android.view.View
11
11
 
12
12
  class UnifiedPlayerModule(private val reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
13
- private var isModuleReady = false
14
-
15
- override fun initialize() {
16
- super.initialize()
13
+ companion object {
14
+ private const val TAG = "UnifiedPlayerModule"
15
+ }
16
+
17
+ init {
18
+ Log.d(TAG, "UnifiedPlayerModule initialized")
19
+ }
20
+
21
+ override fun getName(): String {
22
+ Log.d(TAG, "getName() called, returning 'UnifiedPlayer'")
23
+ return "UnifiedPlayer"
24
+ }
25
+
26
+ private fun getPlayerViewByTag(viewTag: Int): UnifiedPlayerView? {
17
27
  try {
18
- reactContext.getNativeModule(UIManagerModule::class.java)?.let {
19
- isModuleReady = true
20
- Log.d(TAG, "Module successfully initialized")
28
+ val view = reactApplicationContext.currentActivity?.findViewById<View>(viewTag)
29
+ Log.d(TAG, "Looking for view with tag: $viewTag, found: ${view != null}")
30
+
31
+ if (view is UnifiedPlayerView) {
32
+ return view
33
+ } else if (view != null) {
34
+ Log.e(TAG, "View with tag $viewTag is not a UnifiedPlayerView, it's a ${view.javaClass.simpleName}")
21
35
  }
22
36
  } catch (e: Exception) {
23
- Log.e(TAG, "Initialization failed", e)
37
+ Log.e(TAG, "Error finding view with tag $viewTag: ${e.message}", e)
24
38
  }
39
+ return null
25
40
  }
26
- companion object {
27
- private const val TAG = "UnifiedPlayerModule"
28
- }
29
-
30
- override fun getName(): String = "UnifiedPlayer"
31
-
41
+
32
42
  @ReactMethod
33
43
  fun play(viewTag: Int, promise: Promise) {
44
+ Log.d(TAG, "Native play method called with viewTag: $viewTag")
34
45
  try {
35
- val uiManager = reactApplicationContext.getNativeModule(UIManagerModule::class.java)
36
- ?: throw IllegalStateException("UIManagerModule not available")
37
-
38
- uiManager.addUIBlock(UIBlock { nativeViewHierarchyManager ->
39
- try {
40
- val view = nativeViewHierarchyManager.resolveView(viewTag) as? UnifiedPlayerView
41
- if (view != null) {
42
- view.play()
46
+ val playerView = getPlayerViewByTag(viewTag)
47
+ if (playerView != null) {
48
+ UiThreadUtil.runOnUiThread {
49
+ try {
50
+ playerView.play()
51
+ Log.d(TAG, "Play command executed successfully")
43
52
  promise.resolve(true)
44
- } else {
45
- promise.reject("INVALID_VIEW", "View with tag $viewTag not found")
53
+ } catch (e: Exception) {
54
+ Log.e(TAG, "Error during play: ${e.message}", e)
55
+ promise.reject("PLAY_ERROR", "Error during play: ${e.message}", e)
46
56
  }
47
- } catch (e: Exception) {
48
- Log.e(TAG, "Error in play method", e)
49
- promise.reject("PLAY_ERROR", "Error in play method", e)
50
57
  }
51
- })
58
+ } else {
59
+ Log.e(TAG, "Player view not found for tag: $viewTag")
60
+ promise.reject("VIEW_NOT_FOUND", "Player view not found for tag: $viewTag")
61
+ }
52
62
  } catch (e: Exception) {
53
- Log.e(TAG, "Error in play method", e)
54
- promise.reject("UIMANAGER_ERROR", "UIManagerModule not available", e)
63
+ Log.e(TAG, "Error in play method: ${e.message}", e)
64
+ promise.reject("PLAY_ERROR", "Error in play method: ${e.message}", e)
55
65
  }
56
66
  }
57
67
 
58
68
  @ReactMethod
59
69
  fun pause(viewTag: Int, promise: Promise) {
70
+ Log.d(TAG, "Native pause method called with viewTag: $viewTag")
60
71
  try {
61
- val uiManager = reactApplicationContext.getNativeModule(UIManagerModule::class.java)
62
- ?: throw IllegalStateException("UIManagerModule not available")
63
-
64
- uiManager.addUIBlock(UIBlock { nativeViewHierarchyManager ->
65
- try {
66
- val view = nativeViewHierarchyManager.resolveView(viewTag) as? UnifiedPlayerView
67
- if (view != null) {
68
- view.pause()
72
+ val playerView = getPlayerViewByTag(viewTag)
73
+ if (playerView != null) {
74
+ UiThreadUtil.runOnUiThread {
75
+ try {
76
+ playerView.pause()
77
+ Log.d(TAG, "Pause command executed successfully")
69
78
  promise.resolve(true)
70
- } else {
71
- promise.reject("INVALID_VIEW", "View with tag $viewTag not found")
79
+ } catch (e: Exception) {
80
+ Log.e(TAG, "Error during pause: ${e.message}", e)
81
+ promise.reject("PAUSE_ERROR", "Error during pause: ${e.message}", e)
72
82
  }
73
- } catch (e: Exception) {
74
- Log.e(TAG, "Error in pause method", e)
75
- promise.reject("PAUSE_ERROR", "Error in pause method", e)
76
83
  }
77
- })
84
+ } else {
85
+ Log.e(TAG, "Player view not found for tag: $viewTag")
86
+ promise.reject("VIEW_NOT_FOUND", "Player view not found for tag: $viewTag")
87
+ }
78
88
  } catch (e: Exception) {
79
- Log.e(TAG, "Error in pause method", e)
80
- promise.reject("UIMANAGER_ERROR", "UIManagerModule not available", e)
89
+ Log.e(TAG, "Error in pause method: ${e.message}", e)
90
+ promise.reject("PAUSE_ERROR", "Error in pause method: ${e.message}", e)
81
91
  }
82
92
  }
83
93
 
84
94
  @ReactMethod
85
95
  fun seekTo(viewTag: Int, seconds: Float, promise: Promise) {
96
+ Log.d(TAG, "Native seekTo method called with viewTag: $viewTag, seconds: $seconds")
86
97
  try {
87
- val uiManager = reactApplicationContext.getNativeModule(UIManagerModule::class.java)
88
- ?: throw IllegalStateException("UIManagerModule not available")
89
-
90
- uiManager.addUIBlock(UIBlock { nativeViewHierarchyManager ->
91
- try {
92
- val view = nativeViewHierarchyManager.resolveView(viewTag) as? UnifiedPlayerView
93
- if (view != null) {
94
- view.seekTo(seconds)
98
+ val playerView = getPlayerViewByTag(viewTag)
99
+ if (playerView != null) {
100
+ UiThreadUtil.runOnUiThread {
101
+ try {
102
+ playerView.seekTo(seconds)
103
+ Log.d(TAG, "SeekTo command executed successfully to $seconds seconds")
95
104
  promise.resolve(true)
96
- } else {
97
- promise.reject("INVALID_VIEW", "View with tag $viewTag not found")
105
+ } catch (e: Exception) {
106
+ Log.e(TAG, "Error during seekTo: ${e.message}", e)
107
+ promise.reject("SEEK_ERROR", "Error during seekTo: ${e.message}", e)
98
108
  }
99
- } catch (e: Exception) {
100
- Log.e(TAG, "Error in seekTo method", e)
101
- promise.reject("SEEK_ERROR", "Error in seekTo method", e)
102
109
  }
103
- })
110
+ } else {
111
+ Log.e(TAG, "Player view not found for tag: $viewTag")
112
+ promise.reject("VIEW_NOT_FOUND", "Player view not found for tag: $viewTag")
113
+ }
104
114
  } catch (e: Exception) {
105
- Log.e(TAG, "Error in seekTo method", e)
106
- promise.reject("UIMANAGER_ERROR", "UIManagerModule not available", e)
115
+ Log.e(TAG, "Error in seekTo method: ${e.message}", e)
116
+ promise.reject("SEEK_ERROR", "Error in seekTo method: ${e.message}", e)
107
117
  }
108
118
  }
109
119
 
110
120
  @ReactMethod
111
121
  fun getCurrentTime(viewTag: Int, promise: Promise) {
112
- reactApplicationContext.getNativeModule(UIManagerModule::class.java)?.let { uiManager ->
113
- uiManager.addUIBlock(UIBlock { nativeViewHierarchyManager ->
114
- try {
115
- val view = nativeViewHierarchyManager.resolveView(viewTag) as? UnifiedPlayerView
116
- if (view != null) {
117
- promise.resolve(view.getCurrentTime())
118
- } else {
119
- promise.reject("INVALID_VIEW", "View with tag $viewTag not found")
122
+ Log.d(TAG, "Native getCurrentTime method called with viewTag: $viewTag")
123
+ try {
124
+ val playerView = getPlayerViewByTag(viewTag)
125
+ if (playerView != null) {
126
+ UiThreadUtil.runOnUiThread {
127
+ try {
128
+ val currentTime = playerView.getCurrentTime()
129
+ Log.d(TAG, "getCurrentTime executed successfully, current time: $currentTime")
130
+ promise.resolve(currentTime)
131
+ } catch (e: Exception) {
132
+ Log.e(TAG, "Error getting current time: ${e.message}", e)
133
+ promise.reject("GET_TIME_ERROR", "Error getting current time: ${e.message}", e)
120
134
  }
121
- } catch (e: Exception) {
122
- Log.e(TAG, "Error in getCurrentTime method", e)
123
- promise.reject("GET_TIME_ERROR", "Error in getCurrentTime method", e)
124
135
  }
125
- })
126
- } ?: run {
127
- promise.reject("ERROR", "UIManagerModule not available")
136
+ } else {
137
+ Log.e(TAG, "Player view not found for tag: $viewTag")
138
+ promise.reject("VIEW_NOT_FOUND", "Player view not found for tag: $viewTag")
139
+ }
140
+ } catch (e: Exception) {
141
+ Log.e(TAG, "Error in getCurrentTime method: ${e.message}", e)
142
+ promise.reject("GET_TIME_ERROR", "Error in getCurrentTime method: ${e.message}", e)
128
143
  }
129
144
  }
130
145
 
131
146
  @ReactMethod
132
147
  fun getDuration(viewTag: Int, promise: Promise) {
133
- reactApplicationContext.getNativeModule(UIManagerModule::class.java)?.let { uiManager ->
134
- uiManager.addUIBlock(UIBlock { nativeViewHierarchyManager ->
135
- try {
136
- val view = nativeViewHierarchyManager.resolveView(viewTag) as? UnifiedPlayerView
137
- if (view != null) {
138
- promise.resolve(view.getDuration())
139
- } else {
140
- promise.reject("INVALID_VIEW", "View with tag $viewTag not found")
148
+ Log.d(TAG, "Native getDuration method called with viewTag: $viewTag")
149
+ try {
150
+ val playerView = getPlayerViewByTag(viewTag)
151
+ if (playerView != null) {
152
+ UiThreadUtil.runOnUiThread {
153
+ try {
154
+ val duration = playerView.getDuration()
155
+ Log.d(TAG, "getDuration executed successfully, duration: $duration")
156
+ promise.resolve(duration)
157
+ } catch (e: Exception) {
158
+ Log.e(TAG, "Error getting duration: ${e.message}", e)
159
+ promise.reject("GET_DURATION_ERROR", "Error getting duration: ${e.message}", e)
141
160
  }
142
- } catch (e: Exception) {
143
- Log.e(TAG, "Error in getDuration method", e)
144
- promise.reject("GET_DURATION_ERROR", "Error in getDuration method", e)
145
161
  }
146
- })
147
- } ?: run {
148
- promise.reject("ERROR", "UIManagerModule not available")
162
+ } else {
163
+ Log.e(TAG, "Player view not found for tag: $viewTag")
164
+ promise.reject("VIEW_NOT_FOUND", "Player view not found for tag: $viewTag")
165
+ }
166
+ } catch (e: Exception) {
167
+ Log.e(TAG, "Error in getDuration method: ${e.message}", e)
168
+ promise.reject("GET_DURATION_ERROR", "Error in getDuration method: ${e.message}", e)
149
169
  }
150
170
  }
151
171
 
152
172
  @ReactMethod
153
173
  fun capture(viewTag: Int, promise: Promise) {
174
+ Log.d(TAG, "Native capture method called with viewTag: $viewTag")
154
175
  try {
155
- if (!isModuleReady) {
156
- throw IllegalStateException("Module not ready. Ensure React Native bridge is initialized.")
157
- }
158
- val uiManager = reactApplicationContext.getNativeModule(UIManagerModule::class.java)
159
- ?: throw IllegalStateException("UIManagerModule not available. Is the bridge active?")
160
-
161
- uiManager.addUIBlock(UIBlock { nativeViewHierarchyManager ->
162
- try {
163
- val view = nativeViewHierarchyManager.resolveView(viewTag) as? UnifiedPlayerView
164
- if (view != null) {
165
- val bitmap = view.captureFrame()
166
- if (bitmap != null) {
167
- val outputStream = ByteArrayOutputStream()
168
- bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream)
169
- val base64 = Base64.encodeToString(outputStream.toByteArray(), Base64.DEFAULT)
170
- promise.resolve(base64)
171
- } else {
172
- promise.reject("CAPTURE_ERROR", "Failed to capture frame")
173
- }
174
- } else {
175
- promise.reject("INVALID_VIEW", "View with tag $viewTag not found")
176
+ val playerView = getPlayerViewByTag(viewTag)
177
+ if (playerView != null) {
178
+ UiThreadUtil.runOnUiThread {
179
+ try {
180
+ // Assuming playerView has a method called capture() that returns a String
181
+ val captureResult = playerView.capture()
182
+ Log.d(TAG, "Capture command executed successfully, result: $captureResult")
183
+ promise.resolve(captureResult)
184
+ } catch (e: Exception) {
185
+ Log.e(TAG, "Error during capture: ${e.message}", e)
186
+ promise.reject("CAPTURE_ERROR", "Error during capture: ${e.message}", e)
176
187
  }
177
- } catch (e: Exception) {
178
- Log.e(TAG, "Error in capture method", e)
179
- promise.reject("CAPTURE_ERROR", "Error in capture method", e)
180
188
  }
181
- })
189
+ } else {
190
+ Log.e(TAG, "Player view not found for tag: $viewTag")
191
+ promise.reject("VIEW_NOT_FOUND", "Player view not found for tag: $viewTag")
192
+ }
182
193
  } catch (e: Exception) {
183
- Log.e(TAG, "Error in capture method", e)
184
- promise.reject("UIMANAGER_ERROR", "UIManagerModule not available", e)
194
+ Log.e(TAG, "Error in capture method: ${e.message}", e)
195
+ promise.reject("CAPTURE_ERROR", "Error in capture method: ${e.message}", e)
185
196
  }
186
197
  }
187
198
  }
@@ -12,12 +12,7 @@ class UnifiedPlayerPackage : ReactPackage {
12
12
  override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
13
13
  Log.d(TAG, "Creating native modules")
14
14
  return listOf(
15
- UnifiedPlayerModule(reactContext).apply {
16
- reactContext.runOnUiQueueThread {
17
- // Initialize module on UI thread
18
- Log.d(TAG, "Module initialized on UI thread")
19
- }
20
- }
15
+ UnifiedPlayerModule(reactContext)
21
16
  )
22
17
  }
23
18
 
@@ -25,4 +20,4 @@ class UnifiedPlayerPackage : ReactPackage {
25
20
  Log.d(TAG, "Creating view managers")
26
21
  return listOf(UnifiedPlayerViewManager())
27
22
  }
28
- }
23
+ }
@@ -2,14 +2,16 @@ package com.unifiedplayer
2
2
 
3
3
  import android.annotation.SuppressLint
4
4
  import android.content.Context
5
- import android.graphics.Color
6
- import android.util.Log
7
5
  import android.graphics.Bitmap
8
6
  import android.graphics.Canvas
7
+ import android.graphics.Color
8
+ import android.view.PixelCopy
9
+ import android.util.Base64
10
+ import java.io.ByteArrayOutputStream
11
+ import android.util.Log
9
12
  import android.os.Handler
10
13
  import android.os.Looper
11
14
  import android.view.Gravity
12
- import android.view.TextureView
13
15
  import android.view.View
14
16
  import android.widget.FrameLayout
15
17
  import com.facebook.react.bridge.Arguments
@@ -42,6 +44,7 @@ class UnifiedPlayerView(context: Context) : FrameLayout(context) {
42
44
  private var autoplay: Boolean = true
43
45
  private var loop: Boolean = false
44
46
  private var playerView: PlayerView
47
+ private var textureView: android.view.TextureView? = null
45
48
  private var player: ExoPlayer? = null
46
49
  private var currentProgress = 0
47
50
  private var isPaused = false
@@ -80,17 +83,30 @@ class UnifiedPlayerView(context: Context) : FrameLayout(context) {
80
83
  // Create ExoPlayer
81
84
  player = ExoPlayer.Builder(context).build()
82
85
 
83
- // Create PlayerView
86
+ // Create TextureView for video rendering
87
+ textureView = android.view.TextureView(context).apply {
88
+ layoutParams = LayoutParams(
89
+ LayoutParams.MATCH_PARENT,
90
+ LayoutParams.MATCH_PARENT
91
+ )
92
+ }
93
+
94
+ // Create PlayerView without SurfaceView
84
95
  playerView = PlayerView(context).apply {
85
96
  layoutParams = LayoutParams(
86
97
  LayoutParams.MATCH_PARENT,
87
98
  LayoutParams.MATCH_PARENT
88
99
  )
89
100
  setPlayer(player)
90
- // playerView.surfaceView?.surfaceType = android.view.SurfaceView.SURFACE_TYPE_SOFTWARE // Reverted: Let's remove surfaceType setting
101
+ useController = false // Disable default controls
91
102
  }
92
103
 
104
+ // Add views to hierarchy
105
+ addView(textureView)
93
106
  addView(playerView)
107
+
108
+ // Set TextureView as video surface
109
+ player?.setVideoTextureView(textureView)
94
110
  // Add logging for playerView dimensions and post play call
95
111
  playerView.post {
96
112
  Log.d(TAG, "PlayerView dimensions after addView: width=${playerView.width}, height=${playerView.height}")
@@ -316,41 +332,17 @@ class UnifiedPlayerView(context: Context) : FrameLayout(context) {
316
332
  }
317
333
  }
318
334
 
319
- fun getDuration(): Float {
320
- Log.d(TAG, "GetDuration method called")
321
- return player?.let {
322
- val duration = it.duration.toFloat() / 1000f
323
- Log.d(TAG, "Duration: $duration seconds (raw: ${it.duration})")
324
- if (it.duration > 0) duration else 0f
325
- } ?: run {
326
- Log.e(TAG, "Cannot get duration: player is null")
327
- 0f
328
- }
329
- }
330
-
331
- fun captureFrame(): Bitmap? {
332
- Log.d(TAG, "CaptureFrame method called")
333
- try {
334
- // Create a bitmap with the same dimensions as the player view
335
- val bitmap = Bitmap.createBitmap(
336
- playerView.width,
337
- playerView.height,
338
- Bitmap.Config.ARGB_8888
339
- )
340
-
341
- // Create a canvas with the bitmap
342
- val canvas = Canvas(bitmap)
343
-
344
- // Draw the player view onto the canvas
345
- playerView.draw(canvas)
346
-
347
- Log.d(TAG, "Successfully captured frame from PlayerView")
348
- return bitmap
349
- } catch (e: Exception) {
350
- Log.e(TAG, "Error capturing frame: ${e.message}", e)
351
- return null
335
+ fun getDuration(): Float {
336
+ Log.d(TAG, "GetDuration method called")
337
+ return player?.let {
338
+ val duration = it.duration.toFloat() / 1000f
339
+ Log.d(TAG, "Duration: $duration seconds (raw: ${it.duration})")
340
+ if (it.duration > 0) duration else 0f
341
+ } ?: run {
342
+ Log.e(TAG, "Cannot get duration: player is null")
343
+ 0f
344
+ }
352
345
  }
353
- }
354
346
 
355
347
  // Add a getter for the ExoPlayer instance
356
348
  val exoPlayer: ExoPlayer?
@@ -415,4 +407,42 @@ fun captureFrame(): Bitmap? {
415
407
  progressHandler.removeCallbacks(progressRunnable) // Stop progress updates
416
408
  player?.release()
417
409
  }
410
+
411
+ fun capture(): String {
412
+ Log.d(TAG, "Capture method called")
413
+ return try {
414
+ player?.let { exoPlayer ->
415
+ // Get the video size from the player
416
+ val videoSize = exoPlayer.videoSize
417
+ if (videoSize.width <= 0 || videoSize.height <= 0) {
418
+ Log.e(TAG, "Invalid video dimensions: ${videoSize.width}x${videoSize.height}")
419
+ return ""
420
+ }
421
+
422
+ // Get bitmap directly from TextureView
423
+ val bitmap = textureView?.bitmap ?: run {
424
+ Log.e(TAG, "Failed to get bitmap from TextureView")
425
+ return ""
426
+ }
427
+
428
+ // Compress and encode the bitmap
429
+ val byteArrayOutputStream = ByteArrayOutputStream()
430
+ if (bitmap.compress(Bitmap.CompressFormat.PNG, 100, byteArrayOutputStream)) {
431
+ val byteArray = byteArrayOutputStream.toByteArray()
432
+ val base64EncodedString = Base64.encodeToString(byteArray, Base64.DEFAULT)
433
+ Log.d(TAG, "Capture successful, base64 length: ${base64EncodedString.length}")
434
+ base64EncodedString
435
+ } else {
436
+ Log.e(TAG, "Failed to compress bitmap")
437
+ ""
438
+ }
439
+ } ?: run {
440
+ Log.e(TAG, "Cannot capture: player is null")
441
+ ""
442
+ }
443
+ } catch (e: Exception) {
444
+ Log.e(TAG, "Error during capture: ${e.message}", e)
445
+ ""
446
+ }
447
+ }
418
448
  }
@@ -57,4 +57,4 @@ class UnifiedPlayerViewManager : SimpleViewManager<UnifiedPlayerView>() {
57
57
  .put("topLoadStart", MapBuilder.of("registrationName", "onLoadStart"))
58
58
  .build()
59
59
  }
60
- }
60
+ }
@@ -16,6 +16,7 @@ NS_ASSUME_NONNULL_BEGIN
16
16
  @property (nonatomic, weak) RCTBridge *bridge;
17
17
  @property (nonatomic, assign) VLCMediaPlayerState previousState;
18
18
  @property (nonatomic, assign) BOOL hasRenderedVideo;
19
+ @property (nonatomic, assign) BOOL readyEventSent;
19
20
 
20
21
  // Event callbacks
21
22
  @property (nonatomic, copy) RCTDirectEventBlock onLoadStart;
@@ -19,6 +19,7 @@
19
19
 
20
20
  // Initialize properties
21
21
  _hasRenderedVideo = NO;
22
+ _readyEventSent = NO;
22
23
 
23
24
  // Create the player
24
25
  _player = [[VLCMediaPlayer alloc] init];
@@ -182,8 +183,9 @@
182
183
  self.window ? @"YES" : @"NO",
183
184
  self.superview ? @"YES" : @"NO");
184
185
 
185
- // Reset the rendered flag
186
+ // Reset the flags
186
187
  _hasRenderedVideo = NO;
188
+ _readyEventSent = NO;
187
189
 
188
190
  // Make sure we're in the main thread
189
191
  dispatch_async(dispatch_get_main_queue(), ^{
@@ -432,6 +434,19 @@
432
434
  VLCMediaPlayerState state = _player.state;
433
435
  RCTLogInfo(@"[UnifiedPlayerViewManager] mediaPlayerStateChanged - New State: %d", state); // Added Log
434
436
 
437
+ // Check if media is ready to play
438
+ if ((state == VLCMediaPlayerStateBuffering || state == VLCMediaPlayerStatePlaying || state == VLCMediaPlayerStatePaused) && !_readyEventSent) {
439
+ // Check if we have video tracks
440
+ NSArray *videoTracks = [_player.media tracksInformation];
441
+ if (videoTracks.count > 0 || _player.hasVideoOut) {
442
+ RCTLogInfo(@"[UnifiedPlayerViewManager] Media is ready to play - Video tracks found: %lu", (unsigned long)videoTracks.count);
443
+
444
+ // Send ready event when media is ready, regardless of autoplay
445
+ [self sendEvent:@"onReadyToPlay" body:@{}];
446
+ _readyEventSent = YES;
447
+ }
448
+ }
449
+
435
450
  // Debug information for video output
436
451
  if (state == VLCMediaPlayerStatePlaying) {
437
452
  RCTLogInfo(@"[UnifiedPlayerViewManager] Video size: %@",
@@ -444,11 +459,6 @@
444
459
  if (videoTracks.count > 0) {
445
460
  RCTLogInfo(@"[UnifiedPlayerViewManager] Video tracks found: %lu", (unsigned long)videoTracks.count);
446
461
 
447
- // Send ready event the first time we start playing
448
- if (!_hasRenderedVideo) {
449
- [self sendEvent:@"onReadyToPlay" body:@{}];
450
- }
451
-
452
462
  // Send playing event when we actually start playing
453
463
  [self sendEvent:@"onPlaying" body:@{}];
454
464
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-unified-player",
3
- "version": "0.3.4",
3
+ "version": "0.3.6",
4
4
  "description": "Unified Player",
5
5
  "source": "./src/index.tsx",
6
6
  "main": "./lib/module/index.js",