react-native-unified-player 0.3.8 → 0.3.10
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.
- package/android/build.gradle +5 -0
- package/android/src/main/java/com/unifiedplayer/UnifiedPlayerView.kt +203 -68
- package/android/src/main/java/com/unifiedplayer/UnifiedPlayerViewManager.kt +40 -3
- package/ios/UnifiedPlayerUIView.h +8 -2
- package/ios/UnifiedPlayerViewManager.m +330 -188
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/src/index.d.ts +7 -2
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/index.tsx +7 -4
package/android/build.gradle
CHANGED
|
@@ -17,6 +17,7 @@ buildscript {
|
|
|
17
17
|
|
|
18
18
|
apply plugin: "com.android.library"
|
|
19
19
|
apply plugin: "kotlin-android"
|
|
20
|
+
apply plugin: "kotlin-kapt"
|
|
20
21
|
|
|
21
22
|
def getExtOrIntegerDefault(name) {
|
|
22
23
|
return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["UnifiedPlayer_" + name]).toInteger()
|
|
@@ -79,4 +80,8 @@ dependencies {
|
|
|
79
80
|
|
|
80
81
|
implementation "com.google.android.exoplayer:exoplayer-core:2.19.0"
|
|
81
82
|
implementation "com.google.android.exoplayer:exoplayer-ui:2.19.0"
|
|
83
|
+
|
|
84
|
+
// Glide for image loading
|
|
85
|
+
implementation "com.github.bumptech.glide:glide:4.16.0"
|
|
86
|
+
kapt "com.github.bumptech.glide:compiler:4.16.0"
|
|
82
87
|
}
|
|
@@ -14,6 +14,9 @@ import android.os.Looper
|
|
|
14
14
|
import android.view.Gravity
|
|
15
15
|
import android.view.View
|
|
16
16
|
import android.widget.FrameLayout
|
|
17
|
+
import android.widget.ImageView
|
|
18
|
+
import com.bumptech.glide.Glide
|
|
19
|
+
import com.bumptech.glide.request.RequestOptions
|
|
17
20
|
import com.facebook.react.bridge.Arguments
|
|
18
21
|
import com.google.android.exoplayer2.ExoPlayer
|
|
19
22
|
import com.google.android.exoplayer2.MediaItem
|
|
@@ -27,6 +30,7 @@ import com.facebook.react.bridge.ReactContext
|
|
|
27
30
|
import com.facebook.react.uimanager.events.RCTEventEmitter
|
|
28
31
|
import java.io.File
|
|
29
32
|
import java.io.IOException
|
|
33
|
+
import java.util.Collections // Import for Collections.emptyList()
|
|
30
34
|
import android.media.MediaCodec
|
|
31
35
|
import android.media.MediaCodecInfo
|
|
32
36
|
import android.media.MediaFormat
|
|
@@ -58,10 +62,16 @@ class UnifiedPlayerView(context: Context) : FrameLayout(context) {
|
|
|
58
62
|
private const val TAG = "UnifiedPlayerView"
|
|
59
63
|
}
|
|
60
64
|
|
|
61
|
-
|
|
65
|
+
// Player state
|
|
66
|
+
private var videoUrl: String? = null // Single video URL
|
|
67
|
+
private var videoUrls: List<String> = emptyList() // Playlist URLs
|
|
68
|
+
private var currentVideoIndex: Int = 0
|
|
69
|
+
private var isPlaylist: Boolean = false
|
|
70
|
+
private var thumbnailUrl: String? = null
|
|
62
71
|
private var autoplay: Boolean = true
|
|
63
72
|
private var loop: Boolean = false
|
|
64
73
|
private var textureView: android.view.TextureView
|
|
74
|
+
private var thumbnailImageView: ImageView? = null
|
|
65
75
|
internal var player: ExoPlayer? = null
|
|
66
76
|
private var currentProgress = 0
|
|
67
77
|
private var isPaused = false
|
|
@@ -74,7 +84,7 @@ class UnifiedPlayerView(context: Context) : FrameLayout(context) {
|
|
|
74
84
|
val duration = it.duration.toFloat() / 1000f
|
|
75
85
|
|
|
76
86
|
// Log the actual values for debugging
|
|
77
|
-
Log.d(TAG, "Progress values - currentTime: $currentTime, duration: $duration, raw duration: ${it.duration}")
|
|
87
|
+
// Log.d(TAG, "Progress values - currentTime: $currentTime, duration: $duration, raw duration: ${it.duration}")
|
|
78
88
|
|
|
79
89
|
// Only send valid duration values
|
|
80
90
|
if (it.duration > 0) {
|
|
@@ -82,10 +92,10 @@ class UnifiedPlayerView(context: Context) : FrameLayout(context) {
|
|
|
82
92
|
event.putDouble("currentTime", currentTime.toDouble())
|
|
83
93
|
event.putDouble("duration", duration.toDouble())
|
|
84
94
|
|
|
85
|
-
Log.d(TAG, "Sending progress event: currentTime=$currentTime, duration=$duration")
|
|
95
|
+
// Log.d(TAG, "Sending progress event: currentTime=$currentTime, duration=$duration")
|
|
86
96
|
sendEvent(EVENT_PROGRESS, event)
|
|
87
97
|
} else {
|
|
88
|
-
Log.d(TAG, "Not sending progress event because duration is $duration (raw: ${it.duration})")
|
|
98
|
+
// Log.d(TAG, "Not sending progress event because duration is $duration (raw: ${it.duration})")
|
|
89
99
|
}
|
|
90
100
|
} ?: Log.e(TAG, "Cannot send progress event: player is null")
|
|
91
101
|
|
|
@@ -100,16 +110,27 @@ class UnifiedPlayerView(context: Context) : FrameLayout(context) {
|
|
|
100
110
|
// Create ExoPlayer
|
|
101
111
|
player = ExoPlayer.Builder(context).build()
|
|
102
112
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
113
|
+
// Create TextureView for video rendering
|
|
114
|
+
textureView = android.view.TextureView(context).apply {
|
|
115
|
+
layoutParams = LayoutParams(
|
|
116
|
+
LayoutParams.MATCH_PARENT,
|
|
117
|
+
LayoutParams.MATCH_PARENT
|
|
118
|
+
)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Create ImageView for thumbnail
|
|
122
|
+
thumbnailImageView = ImageView(context).apply {
|
|
123
|
+
layoutParams = LayoutParams(
|
|
124
|
+
LayoutParams.MATCH_PARENT,
|
|
125
|
+
LayoutParams.MATCH_PARENT
|
|
126
|
+
)
|
|
127
|
+
scaleType = ImageView.ScaleType.CENTER_CROP
|
|
128
|
+
visibility = View.GONE
|
|
129
|
+
}
|
|
110
130
|
|
|
111
|
-
|
|
112
|
-
|
|
131
|
+
// Add views to the layout (thumbnail on top of TextureView)
|
|
132
|
+
addView(textureView)
|
|
133
|
+
addView(thumbnailImageView)
|
|
113
134
|
|
|
114
135
|
// We'll set the video surface when the TextureView's surface is available
|
|
115
136
|
// in the onSurfaceTextureAvailable callback
|
|
@@ -121,15 +142,52 @@ class UnifiedPlayerView(context: Context) : FrameLayout(context) {
|
|
|
121
142
|
|
|
122
143
|
player?.addListener(object : Player.Listener {
|
|
123
144
|
override fun onPlaybackStateChanged(playbackState: Int) {
|
|
124
|
-
Log.d(TAG, "onPlaybackStateChanged: $playbackState")
|
|
145
|
+
Log.d(TAG, "onPlaybackStateChanged: $playbackState")
|
|
125
146
|
when (playbackState) {
|
|
126
147
|
Player.STATE_READY -> {
|
|
127
148
|
Log.d(TAG, "ExoPlayer STATE_READY")
|
|
149
|
+
// Ensure thumbnail is hidden when ready (might be needed if autoplay=false)
|
|
150
|
+
if (player?.isPlaying == false) { // Check if not already playing
|
|
151
|
+
thumbnailImageView?.visibility = View.GONE
|
|
152
|
+
}
|
|
128
153
|
sendEvent(EVENT_READY, Arguments.createMap())
|
|
154
|
+
// Start progress updates when ready
|
|
155
|
+
startProgressUpdates()
|
|
129
156
|
}
|
|
130
157
|
Player.STATE_ENDED -> {
|
|
131
158
|
Log.d(TAG, "ExoPlayer STATE_ENDED")
|
|
132
|
-
|
|
159
|
+
if (isPlaylist) {
|
|
160
|
+
// Playlist logic
|
|
161
|
+
val nextIndex = currentVideoIndex + 1
|
|
162
|
+
if (nextIndex < videoUrls.size) {
|
|
163
|
+
// Play next video in the list
|
|
164
|
+
Log.d(TAG, "Playlist: Loading next video at index $nextIndex")
|
|
165
|
+
loadVideoAtIndex(nextIndex)
|
|
166
|
+
// Don't send EVENT_COMPLETE for individual items in playlist
|
|
167
|
+
} else {
|
|
168
|
+
// Reached the end of the playlist
|
|
169
|
+
if (loop) {
|
|
170
|
+
// Loop playlist: Go back to the first video
|
|
171
|
+
Log.d(TAG, "Playlist: Looping back to start")
|
|
172
|
+
loadVideoAtIndex(0)
|
|
173
|
+
// Don't send EVENT_COMPLETE when looping playlist
|
|
174
|
+
} else {
|
|
175
|
+
// End of playlist, not looping
|
|
176
|
+
Log.d(TAG, "Playlist: Reached end, not looping")
|
|
177
|
+
currentVideoIndex = 0 // Reset index for potential future play
|
|
178
|
+
sendEvent(EVENT_COMPLETE, Arguments.createMap()) // Send completion for the whole list
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
} else {
|
|
182
|
+
// Single video logic (ExoPlayer handles looping via repeatMode)
|
|
183
|
+
if (!loop) {
|
|
184
|
+
// Send completion event only if not looping a single video
|
|
185
|
+
sendEvent(EVENT_COMPLETE, Arguments.createMap())
|
|
186
|
+
} else {
|
|
187
|
+
Log.d(TAG, "Single video ended and loop is ON - ExoPlayer will repeat.")
|
|
188
|
+
// Optionally send an event here if needed for single loop cycle completion
|
|
189
|
+
}
|
|
190
|
+
}
|
|
133
191
|
}
|
|
134
192
|
Player.STATE_BUFFERING -> {
|
|
135
193
|
Log.d(TAG, "ExoPlayer STATE_BUFFERING")
|
|
@@ -145,6 +203,8 @@ class UnifiedPlayerView(context: Context) : FrameLayout(context) {
|
|
|
145
203
|
Log.d(TAG, "onIsPlayingChanged: $isPlaying") // Added log
|
|
146
204
|
if (isPlaying) {
|
|
147
205
|
Log.d(TAG, "ExoPlayer is now playing")
|
|
206
|
+
// Hide thumbnail when video starts playing
|
|
207
|
+
thumbnailImageView?.visibility = View.GONE
|
|
148
208
|
sendEvent(EVENT_RESUMED, Arguments.createMap())
|
|
149
209
|
sendEvent(EVENT_PLAYING, Arguments.createMap())
|
|
150
210
|
} else {
|
|
@@ -207,55 +267,34 @@ class UnifiedPlayerView(context: Context) : FrameLayout(context) {
|
|
|
207
267
|
})
|
|
208
268
|
}
|
|
209
269
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
if (url == null || url.isEmpty()) {
|
|
214
|
-
Log.e(TAG, "Empty or null URL provided")
|
|
215
|
-
return
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
videoUrl = url
|
|
219
|
-
|
|
270
|
+
// Helper function to load and prepare a video URL
|
|
271
|
+
private fun loadVideoSource(url: String) {
|
|
272
|
+
Log.d(TAG, "Loading video source: $url")
|
|
220
273
|
try {
|
|
221
|
-
// Create a MediaItem
|
|
222
274
|
val mediaItem = MediaItem.fromUri(url)
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
player?.stop()
|
|
226
|
-
player?.clearMediaItems()
|
|
227
|
-
|
|
228
|
-
// Set the media item
|
|
275
|
+
player?.stop() // Stop previous playback
|
|
276
|
+
player?.clearMediaItems() // Clear previous items
|
|
229
277
|
player?.setMediaItem(mediaItem)
|
|
230
|
-
|
|
231
|
-
// Prepare the player (this will start loading the media)
|
|
232
278
|
player?.prepare()
|
|
279
|
+
player?.playWhenReady = autoplay && !isPaused // Apply autoplay and paused state
|
|
233
280
|
|
|
234
|
-
//
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
Log.d(TAG, "ExoPlayer configured with URL: $url, autoplay: $autoplay, loop: $loop")
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
// Force a progress update immediately
|
|
251
|
-
progressRunnable.run()
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
})
|
|
281
|
+
// Explicitly set repeat mode here based on current state
|
|
282
|
+
if (isPlaylist) {
|
|
283
|
+
player?.repeatMode = Player.REPEAT_MODE_OFF // Force OFF for playlists
|
|
284
|
+
} else {
|
|
285
|
+
player?.repeatMode = if (loop) Player.REPEAT_MODE_ONE else Player.REPEAT_MODE_OFF // Use loop prop for single videos
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
Log.d(TAG, "ExoPlayer configured with URL: $url, autoplay: $autoplay, loop: $loop, isPaused: $isPaused, repeatMode: ${player?.repeatMode}")
|
|
289
|
+
// Send load start event, include index if it's a playlist
|
|
290
|
+
val loadStartEvent = Arguments.createMap()
|
|
291
|
+
if (isPlaylist) {
|
|
292
|
+
loadStartEvent.putInt("index", currentVideoIndex)
|
|
293
|
+
}
|
|
294
|
+
sendEvent(EVENT_LOAD_START, loadStartEvent)
|
|
295
|
+
|
|
255
296
|
} catch (e: Exception) {
|
|
256
|
-
Log.e(TAG, "Error setting video
|
|
257
|
-
|
|
258
|
-
// Send error event
|
|
297
|
+
Log.e(TAG, "Error setting video source: ${e.message}", e)
|
|
259
298
|
val event = Arguments.createMap()
|
|
260
299
|
event.putString("code", "SOURCE_ERROR")
|
|
261
300
|
event.putString("message", "Failed to load video source: $url")
|
|
@@ -263,14 +302,98 @@ class UnifiedPlayerView(context: Context) : FrameLayout(context) {
|
|
|
263
302
|
}
|
|
264
303
|
}
|
|
265
304
|
|
|
305
|
+
// Method to load a specific video from the playlist
|
|
306
|
+
private fun loadVideoAtIndex(index: Int) {
|
|
307
|
+
if (index >= 0 && index < videoUrls.size) {
|
|
308
|
+
currentVideoIndex = index
|
|
309
|
+
val url = videoUrls[index]
|
|
310
|
+
Log.d(TAG, "Loading playlist item at index $index: $url")
|
|
311
|
+
loadVideoSource(url)
|
|
312
|
+
} else {
|
|
313
|
+
Log.e(TAG, "Invalid index $index for playlist size ${videoUrls.size}")
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Called by ViewManager for single URL
|
|
318
|
+
fun setVideoUrl(url: String?) {
|
|
319
|
+
Log.d(TAG, "Setting single video URL: $url")
|
|
320
|
+
isPlaylist = false // Mark as not a playlist
|
|
321
|
+
videoUrls = emptyList() // Clear any previous playlist
|
|
322
|
+
currentVideoIndex = 0
|
|
323
|
+
|
|
324
|
+
if (url != null && url.isNotEmpty()) { // Check for non-null and non-empty
|
|
325
|
+
videoUrl = url // Store the non-null url
|
|
326
|
+
loadVideoSource(url) // Call loadVideoSource only when url is guaranteed non-null
|
|
327
|
+
} else {
|
|
328
|
+
Log.w(TAG, "Received null or empty URL for single video.")
|
|
329
|
+
player?.stop()
|
|
330
|
+
player?.clearMediaItems()
|
|
331
|
+
videoUrl = null // Ensure internal state is cleared
|
|
332
|
+
// Optionally show thumbnail or placeholder if URL is cleared
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Called by ViewManager for URL list (playlist)
|
|
337
|
+
fun setVideoUrls(urls: List<String>) {
|
|
338
|
+
Log.d(TAG, "Setting video URL list (playlist) with ${urls.size} items.")
|
|
339
|
+
if (urls.isEmpty()) {
|
|
340
|
+
Log.w(TAG, "Received empty URL list.")
|
|
341
|
+
setVideoUrl(null) // Treat empty list as clearing the source
|
|
342
|
+
return
|
|
343
|
+
}
|
|
344
|
+
isPlaylist = true // Mark as a playlist
|
|
345
|
+
videoUrl = null // Clear single video URL
|
|
346
|
+
videoUrls = urls
|
|
347
|
+
currentVideoIndex = 0 // Start from the beginning
|
|
348
|
+
|
|
349
|
+
// Load the first video in the playlist
|
|
350
|
+
loadVideoAtIndex(currentVideoIndex)
|
|
351
|
+
}
|
|
352
|
+
|
|
266
353
|
fun setAutoplay(value: Boolean) {
|
|
267
354
|
autoplay = value
|
|
268
355
|
player?.playWhenReady = value
|
|
269
356
|
}
|
|
270
357
|
|
|
271
358
|
fun setLoop(value: Boolean) {
|
|
359
|
+
Log.d(TAG, "Setting loop to: $value, isPlaylist: $isPlaylist")
|
|
272
360
|
loop = value
|
|
273
|
-
|
|
361
|
+
// Only set ExoPlayer's repeatMode if NOT in playlist mode.
|
|
362
|
+
// Playlist looping is handled manually in onPlaybackStateChanged.
|
|
363
|
+
if (!isPlaylist) {
|
|
364
|
+
// Use REPEAT_MODE_ONE for single item looping
|
|
365
|
+
player?.repeatMode = if (loop) Player.REPEAT_MODE_ONE else Player.REPEAT_MODE_OFF
|
|
366
|
+
} else {
|
|
367
|
+
// Ensure repeat mode is off when handling playlists manually
|
|
368
|
+
player?.repeatMode = Player.REPEAT_MODE_OFF
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
fun setThumbnailUrl(url: String?) {
|
|
373
|
+
Log.d(TAG, "Setting thumbnail URL: $url")
|
|
374
|
+
|
|
375
|
+
thumbnailUrl = url
|
|
376
|
+
|
|
377
|
+
if (url != null && url.isNotEmpty()) {
|
|
378
|
+
// Show the thumbnail ImageView
|
|
379
|
+
thumbnailImageView?.visibility = View.VISIBLE
|
|
380
|
+
|
|
381
|
+
// Load the thumbnail image using Glide
|
|
382
|
+
try {
|
|
383
|
+
Glide.with(context)
|
|
384
|
+
.load(url)
|
|
385
|
+
.apply(RequestOptions().centerCrop())
|
|
386
|
+
.into(thumbnailImageView!!)
|
|
387
|
+
|
|
388
|
+
Log.d(TAG, "Thumbnail image loading started")
|
|
389
|
+
} catch (e: Exception) {
|
|
390
|
+
Log.e(TAG, "Error loading thumbnail image: ${e.message}", e)
|
|
391
|
+
thumbnailImageView?.visibility = View.GONE
|
|
392
|
+
}
|
|
393
|
+
} else {
|
|
394
|
+
// Hide the thumbnail if URL is null or empty
|
|
395
|
+
thumbnailImageView?.visibility = View.GONE
|
|
396
|
+
}
|
|
274
397
|
}
|
|
275
398
|
|
|
276
399
|
fun setIsPaused(isPaused: Boolean) {
|
|
@@ -301,7 +424,7 @@ class UnifiedPlayerView(context: Context) : FrameLayout(context) {
|
|
|
301
424
|
it.seekTo(milliseconds)
|
|
302
425
|
|
|
303
426
|
// Force a progress update after seeking
|
|
304
|
-
|
|
427
|
+
progressRunnable.run()
|
|
305
428
|
} ?: Log.e(TAG, "Cannot seek: player is null")
|
|
306
429
|
}
|
|
307
430
|
|
|
@@ -336,7 +459,7 @@ class UnifiedPlayerView(context: Context) : FrameLayout(context) {
|
|
|
336
459
|
private fun sendEvent(eventName: String, params: WritableMap) {
|
|
337
460
|
try {
|
|
338
461
|
// Log the event for debugging
|
|
339
|
-
Log.d(TAG, "Sending direct event: $eventName with params: $params")
|
|
462
|
+
// Log.d(TAG, "Sending direct event: $eventName with params: $params")
|
|
340
463
|
|
|
341
464
|
// Map event names to their corresponding top event names
|
|
342
465
|
val topEventName = when (eventName) {
|
|
@@ -361,13 +484,22 @@ class UnifiedPlayerView(context: Context) : FrameLayout(context) {
|
|
|
361
484
|
}
|
|
362
485
|
}
|
|
363
486
|
|
|
364
|
-
//
|
|
487
|
+
// Method to explicitly start progress updates
|
|
365
488
|
private fun startProgressUpdates() {
|
|
366
|
-
|
|
367
|
-
|
|
489
|
+
// Only start if player is ready and has duration
|
|
490
|
+
if (player?.playbackState == Player.STATE_READY && (player?.duration ?: 0) > 0) {
|
|
491
|
+
Log.d(TAG, "Starting progress updates")
|
|
492
|
+
progressHandler.removeCallbacks(progressRunnable) // Remove existing callbacks
|
|
493
|
+
progressHandler.post(progressRunnable) // Post the runnable
|
|
494
|
+
} else {
|
|
495
|
+
Log.d(TAG, "Skipping progress updates start: Player not ready or duration is 0")
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// Method to stop progress updates
|
|
500
|
+
private fun stopProgressUpdates() {
|
|
501
|
+
Log.d(TAG, "Stopping progress updates")
|
|
368
502
|
progressHandler.removeCallbacks(progressRunnable)
|
|
369
|
-
// Post the runnable to start updates
|
|
370
|
-
progressHandler.post(progressRunnable)
|
|
371
503
|
}
|
|
372
504
|
|
|
373
505
|
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
|
|
@@ -408,14 +540,17 @@ class UnifiedPlayerView(context: Context) : FrameLayout(context) {
|
|
|
408
540
|
// Log.d(TAG, "TextureView onSurfaceTextureUpdated")
|
|
409
541
|
}
|
|
410
542
|
}
|
|
411
|
-
|
|
543
|
+
// Don't start progress updates here automatically, wait for STATE_READY
|
|
544
|
+
// startProgressUpdates()
|
|
412
545
|
}
|
|
413
546
|
|
|
414
547
|
override fun onDetachedFromWindow() {
|
|
415
548
|
super.onDetachedFromWindow()
|
|
416
549
|
Log.d(TAG, "UnifiedPlayerView onDetachedFromWindow")
|
|
417
|
-
|
|
550
|
+
stopProgressUpdates() // Stop progress updates
|
|
418
551
|
player?.release()
|
|
552
|
+
player = null // Ensure player is nullified
|
|
553
|
+
cleanupRecording() // Clean up recording resources if any
|
|
419
554
|
}
|
|
420
555
|
|
|
421
556
|
fun capture(): String {
|
|
@@ -6,6 +6,8 @@ import com.facebook.react.uimanager.annotations.ReactProp
|
|
|
6
6
|
import com.facebook.react.common.MapBuilder
|
|
7
7
|
import android.util.Log
|
|
8
8
|
import com.facebook.react.bridge.ReadableArray
|
|
9
|
+
import com.facebook.react.bridge.Dynamic // Import Dynamic
|
|
10
|
+
import com.facebook.react.bridge.ReadableType // Import ReadableType
|
|
9
11
|
import com.facebook.react.uimanager.events.RCTEventEmitter
|
|
10
12
|
|
|
11
13
|
class UnifiedPlayerViewManager : SimpleViewManager<UnifiedPlayerView>() {
|
|
@@ -19,8 +21,43 @@ class UnifiedPlayerViewManager : SimpleViewManager<UnifiedPlayerView>() {
|
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
@ReactProp(name = "videoUrl")
|
|
22
|
-
fun setVideoUrl(view: UnifiedPlayerView,
|
|
23
|
-
|
|
24
|
+
fun setVideoUrl(view: UnifiedPlayerView, videoUrl: Dynamic?) {
|
|
25
|
+
if (videoUrl == null) {
|
|
26
|
+
view.setVideoUrl(null)
|
|
27
|
+
return
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
when (videoUrl.type) {
|
|
31
|
+
ReadableType.String -> {
|
|
32
|
+
view.setVideoUrl(videoUrl.asString())
|
|
33
|
+
}
|
|
34
|
+
ReadableType.Array -> {
|
|
35
|
+
val urlList = mutableListOf<String>()
|
|
36
|
+
val array = videoUrl.asArray()
|
|
37
|
+
for (i in 0 until array.size()) {
|
|
38
|
+
if (array.getType(i) == ReadableType.String) {
|
|
39
|
+
val urlString = array.getString(i) // Get nullable string
|
|
40
|
+
if (urlString != null) { // Check if it's not null
|
|
41
|
+
urlList.add(urlString) // Add the non-null string
|
|
42
|
+
} else {
|
|
43
|
+
Log.w(TAG, "Null string found in videoUrl array at index $i.")
|
|
44
|
+
}
|
|
45
|
+
} else {
|
|
46
|
+
Log.w(TAG, "Invalid type in videoUrl array at index $i. Expected String.")
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
view.setVideoUrls(urlList) // Call the new method for playlists
|
|
50
|
+
}
|
|
51
|
+
else -> {
|
|
52
|
+
Log.w(TAG, "Invalid type for videoUrl prop. Expected String or Array.")
|
|
53
|
+
view.setVideoUrl(null) // Or handle error appropriately
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
@ReactProp(name = "thumbnailUrl")
|
|
59
|
+
fun setThumbnailUrl(view: UnifiedPlayerView, url: String?) {
|
|
60
|
+
view.setThumbnailUrl(url)
|
|
24
61
|
}
|
|
25
62
|
|
|
26
63
|
@ReactProp(name = "autoplay")
|
|
@@ -57,4 +94,4 @@ class UnifiedPlayerViewManager : SimpleViewManager<UnifiedPlayerView>() {
|
|
|
57
94
|
.put("topLoadStart", MapBuilder.of("registrationName", "onLoadStart"))
|
|
58
95
|
.build()
|
|
59
96
|
}
|
|
60
|
-
}
|
|
97
|
+
}
|
|
@@ -10,7 +10,11 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
10
10
|
@interface UnifiedPlayerUIView : UIView <VLCMediaPlayerDelegate>
|
|
11
11
|
|
|
12
12
|
@property (nonatomic, strong) VLCMediaPlayer *player;
|
|
13
|
-
@property (nonatomic, copy) NSString *videoUrlString;
|
|
13
|
+
@property (nonatomic, copy, nullable) NSString *videoUrlString; // Single URL
|
|
14
|
+
@property (nonatomic, copy, nullable) NSArray<NSString *> *videoUrlArray; // Playlist URLs
|
|
15
|
+
@property (nonatomic, assign) NSInteger currentVideoIndex;
|
|
16
|
+
@property (nonatomic, assign) BOOL isPlaylist;
|
|
17
|
+
@property (nonatomic, copy, nullable) NSString *thumbnailUrlString;
|
|
14
18
|
@property (nonatomic, assign) BOOL autoplay;
|
|
15
19
|
@property (nonatomic, assign) BOOL loop;
|
|
16
20
|
@property (nonatomic, assign) BOOL isPaused;
|
|
@@ -39,7 +43,9 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
39
43
|
@property (nonatomic, copy) RCTDirectEventBlock onPaused;
|
|
40
44
|
|
|
41
45
|
// Method declarations
|
|
42
|
-
- (void)setupWithVideoUrlString:(NSString *)videoUrlString;
|
|
46
|
+
- (void)setupWithVideoUrlString:(nullable NSString *)videoUrlString;
|
|
47
|
+
- (void)setupWithVideoUrlArray:(NSArray<NSString *> *)urlArray; // New method for playlists
|
|
48
|
+
- (void)setupThumbnailWithUrlString:(nullable NSString *)thumbnailUrlString;
|
|
43
49
|
- (void)play;
|
|
44
50
|
- (void)pause;
|
|
45
51
|
- (void)seekToTime:(NSNumber *)timeNumber;
|