react-native-unified-player 0.3.7 → 0.3.9

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.
@@ -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
  }
@@ -2,6 +2,9 @@
2
2
  package="com.unifiedplayer">
3
3
  <uses-permission android:name="android.permission.INTERNET" />
4
4
  <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
5
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
6
+ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
7
+ <uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
5
8
  <application android:hardwareAccelerated="true">
6
9
  </application>
7
10
  </manifest>
@@ -8,6 +8,8 @@ import com.facebook.react.uimanager.UIManagerModule
8
8
  import android.util.Log
9
9
  import com.facebook.react.bridge.UiThreadUtil
10
10
  import android.view.View
11
+ import android.os.Environment
12
+ import java.io.File
11
13
 
12
14
  class UnifiedPlayerModule(private val reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
13
15
  companion object {
@@ -195,4 +197,71 @@ class UnifiedPlayerModule(private val reactContext: ReactApplicationContext) : R
195
197
  promise.reject("CAPTURE_ERROR", "Error in capture method: ${e.message}", e)
196
198
  }
197
199
  }
200
+
201
+ @ReactMethod
202
+ fun startRecording(viewTag: Int, outputPath: String?, promise: Promise) {
203
+ Log.d(TAG, "Native startRecording method called with viewTag: $viewTag, outputPath: $outputPath")
204
+ try {
205
+ val playerView = getPlayerViewByTag(viewTag)
206
+ if (playerView != null) {
207
+ UiThreadUtil.runOnUiThread {
208
+ try {
209
+ // Determine the output path
210
+ val finalOutputPath = if (outputPath.isNullOrEmpty()) {
211
+ // Use app-specific storage for Android 10+ (API level 29+)
212
+ val moviesDir = File(reactApplicationContext.getExternalFilesDir(Environment.DIRECTORY_MOVIES), "recordings")
213
+ if (!moviesDir.exists()) {
214
+ moviesDir.mkdirs()
215
+ }
216
+ val timestamp = System.currentTimeMillis()
217
+ File(moviesDir, "recording_$timestamp.mp4").absolutePath
218
+ } else {
219
+ outputPath
220
+ }
221
+
222
+ // Start recording
223
+ val result = playerView.startRecording(finalOutputPath)
224
+ Log.d(TAG, "Start recording command executed successfully, result: $result")
225
+ promise.resolve(result)
226
+ } catch (e: Exception) {
227
+ Log.e(TAG, "Error during startRecording: ${e.message}", e)
228
+ promise.reject("RECORDING_ERROR", "Error during startRecording: ${e.message}", e)
229
+ }
230
+ }
231
+ } else {
232
+ Log.e(TAG, "Player view not found for tag: $viewTag")
233
+ promise.reject("VIEW_NOT_FOUND", "Player view not found for tag: $viewTag")
234
+ }
235
+ } catch (e: Exception) {
236
+ Log.e(TAG, "Error in startRecording method: ${e.message}", e)
237
+ promise.reject("RECORDING_ERROR", "Error in startRecording method: ${e.message}", e)
238
+ }
239
+ }
240
+
241
+ @ReactMethod
242
+ fun stopRecording(viewTag: Int, promise: Promise) {
243
+ Log.d(TAG, "Native stopRecording method called with viewTag: $viewTag")
244
+ try {
245
+ val playerView = getPlayerViewByTag(viewTag)
246
+ if (playerView != null) {
247
+ UiThreadUtil.runOnUiThread {
248
+ try {
249
+ // Stop recording
250
+ val filePath = playerView.stopRecording()
251
+ Log.d(TAG, "Stop recording command executed successfully, filePath: $filePath")
252
+ promise.resolve(filePath)
253
+ } catch (e: Exception) {
254
+ Log.e(TAG, "Error during stopRecording: ${e.message}", e)
255
+ promise.reject("RECORDING_ERROR", "Error during stopRecording: ${e.message}", e)
256
+ }
257
+ }
258
+ } else {
259
+ Log.e(TAG, "Player view not found for tag: $viewTag")
260
+ promise.reject("VIEW_NOT_FOUND", "Player view not found for tag: $viewTag")
261
+ }
262
+ } catch (e: Exception) {
263
+ Log.e(TAG, "Error in stopRecording method: ${e.message}", e)
264
+ promise.reject("RECORDING_ERROR", "Error in stopRecording method: ${e.message}", e)
265
+ }
266
+ }
198
267
  }
@@ -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
@@ -25,6 +28,15 @@ import com.google.android.exoplayer2.video.VideoSize
25
28
  import com.facebook.react.bridge.WritableMap
26
29
  import com.facebook.react.bridge.ReactContext
27
30
  import com.facebook.react.uimanager.events.RCTEventEmitter
31
+ import java.io.File
32
+ import java.io.IOException
33
+ import android.media.MediaCodec
34
+ import android.media.MediaCodecInfo
35
+ import android.media.MediaFormat
36
+ import android.media.MediaMuxer
37
+ import android.view.Surface
38
+ import java.nio.ByteBuffer
39
+ import android.os.Environment
28
40
  import com.unifiedplayer.UnifiedPlayerEventEmitter.Companion.EVENT_COMPLETE
29
41
  import com.unifiedplayer.UnifiedPlayerEventEmitter.Companion.EVENT_ERROR
30
42
  import com.unifiedplayer.UnifiedPlayerEventEmitter.Companion.EVENT_LOAD_START
@@ -36,14 +48,25 @@ import com.unifiedplayer.UnifiedPlayerEventEmitter.Companion.EVENT_RESUMED
36
48
  import com.unifiedplayer.UnifiedPlayerEventEmitter.Companion.EVENT_STALLED
37
49
 
38
50
  class UnifiedPlayerView(context: Context) : FrameLayout(context) {
51
+ // Recording related variables
52
+ private var mediaRecorder: MediaMuxer? = null
53
+ private var videoEncoder: MediaCodec? = null
54
+ private var recordingSurface: Surface? = null
55
+ private var isRecording = false
56
+ private var outputPath: String? = null
57
+ private var recordingThread: Thread? = null
58
+ private var videoTrackIndex = -1
59
+ private val bufferInfo = MediaCodec.BufferInfo()
39
60
  companion object {
40
61
  private const val TAG = "UnifiedPlayerView"
41
62
  }
42
63
 
43
64
  private var videoUrl: String? = null
65
+ private var thumbnailUrl: String? = null
44
66
  private var autoplay: Boolean = true
45
67
  private var loop: Boolean = false
46
68
  private var textureView: android.view.TextureView
69
+ private var thumbnailImageView: ImageView? = null
47
70
  internal var player: ExoPlayer? = null
48
71
  private var currentProgress = 0
49
72
  private var isPaused = false
@@ -82,16 +105,27 @@ class UnifiedPlayerView(context: Context) : FrameLayout(context) {
82
105
  // Create ExoPlayer
83
106
  player = ExoPlayer.Builder(context).build()
84
107
 
85
- // Create TextureView for video rendering
86
- textureView = android.view.TextureView(context).apply {
87
- layoutParams = LayoutParams(
88
- LayoutParams.MATCH_PARENT,
89
- LayoutParams.MATCH_PARENT
90
- )
91
- }
108
+ // Create TextureView for video rendering
109
+ textureView = android.view.TextureView(context).apply {
110
+ layoutParams = LayoutParams(
111
+ LayoutParams.MATCH_PARENT,
112
+ LayoutParams.MATCH_PARENT
113
+ )
114
+ }
92
115
 
93
- // Add TextureView to the view hierarchy
94
- addView(textureView)
116
+ // Create ImageView for thumbnail
117
+ thumbnailImageView = ImageView(context).apply {
118
+ layoutParams = LayoutParams(
119
+ LayoutParams.MATCH_PARENT,
120
+ LayoutParams.MATCH_PARENT
121
+ )
122
+ scaleType = ImageView.ScaleType.CENTER_CROP
123
+ visibility = View.GONE
124
+ }
125
+
126
+ // Add views to the layout (thumbnail on top of TextureView)
127
+ addView(textureView)
128
+ addView(thumbnailImageView)
95
129
 
96
130
  // We'll set the video surface when the TextureView's surface is available
97
131
  // in the onSurfaceTextureAvailable callback
@@ -127,6 +161,8 @@ class UnifiedPlayerView(context: Context) : FrameLayout(context) {
127
161
  Log.d(TAG, "onIsPlayingChanged: $isPlaying") // Added log
128
162
  if (isPlaying) {
129
163
  Log.d(TAG, "ExoPlayer is now playing")
164
+ // Hide thumbnail when video starts playing
165
+ thumbnailImageView?.visibility = View.GONE
130
166
  sendEvent(EVENT_RESUMED, Arguments.createMap())
131
167
  sendEvent(EVENT_PLAYING, Arguments.createMap())
132
168
  } else {
@@ -255,6 +291,33 @@ class UnifiedPlayerView(context: Context) : FrameLayout(context) {
255
291
  player?.repeatMode = if (loop) Player.REPEAT_MODE_ALL else Player.REPEAT_MODE_OFF
256
292
  }
257
293
 
294
+ fun setThumbnailUrl(url: String?) {
295
+ Log.d(TAG, "Setting thumbnail URL: $url")
296
+
297
+ thumbnailUrl = url
298
+
299
+ if (url != null && url.isNotEmpty()) {
300
+ // Show the thumbnail ImageView
301
+ thumbnailImageView?.visibility = View.VISIBLE
302
+
303
+ // Load the thumbnail image using Glide
304
+ try {
305
+ Glide.with(context)
306
+ .load(url)
307
+ .apply(RequestOptions().centerCrop())
308
+ .into(thumbnailImageView!!)
309
+
310
+ Log.d(TAG, "Thumbnail image loading started")
311
+ } catch (e: Exception) {
312
+ Log.e(TAG, "Error loading thumbnail image: ${e.message}", e)
313
+ thumbnailImageView?.visibility = View.GONE
314
+ }
315
+ } else {
316
+ // Hide the thumbnail if URL is null or empty
317
+ thumbnailImageView?.visibility = View.GONE
318
+ }
319
+ }
320
+
258
321
  fun setIsPaused(isPaused: Boolean) {
259
322
  Log.d(TAG, "setIsPaused called with value: $isPaused")
260
323
  this.isPaused = isPaused
@@ -457,4 +520,221 @@ bitmap.let {
457
520
  ""
458
521
  }
459
522
  }
523
+
524
+ /**
525
+ * Start recording the video to the specified output path
526
+ * @param outputPath Path where to save the recording
527
+ * @return true if recording started successfully
528
+ */
529
+ fun startRecording(outputPath: String): Boolean {
530
+ Log.d(TAG, "startRecording called with outputPath: $outputPath")
531
+
532
+ if (isRecording) {
533
+ Log.w(TAG, "Recording is already in progress")
534
+ return false
535
+ }
536
+
537
+ try {
538
+ player?.let { exoPlayer ->
539
+ // Get the current media item's URI
540
+ val currentUri = exoPlayer.currentMediaItem?.localConfiguration?.uri?.toString()
541
+ if (currentUri == null) {
542
+ Log.e(TAG, "Current media URI is null")
543
+ return false
544
+ }
545
+
546
+ Log.d(TAG, "Current media URI: $currentUri")
547
+
548
+ // Store the output path
549
+ this.outputPath = if (outputPath.isNullOrEmpty()) {
550
+ // Use app-specific storage for Android 10+ (API level 29+)
551
+ val appContext = context.applicationContext
552
+ val moviesDir = File(appContext.getExternalFilesDir(Environment.DIRECTORY_MOVIES), "recordings")
553
+ if (!moviesDir.exists()) {
554
+ moviesDir.mkdirs()
555
+ }
556
+ File(moviesDir, "recording_${System.currentTimeMillis()}.mp4").absolutePath
557
+ } else {
558
+ outputPath
559
+ }
560
+
561
+ // Create parent directories if they don't exist
562
+ val outputFile = File(this.outputPath)
563
+ outputFile.parentFile?.mkdirs()
564
+
565
+ // Log the final output path
566
+ Log.d(TAG, "Recording will be saved to: ${this.outputPath}")
567
+
568
+ // Start a background thread to download the file
569
+ Thread {
570
+ try {
571
+ // Create a URL from the URI
572
+ val url = java.net.URL(currentUri)
573
+
574
+ // Open connection
575
+ val connection = url.openConnection() as java.net.HttpURLConnection
576
+ connection.requestMethod = "GET"
577
+ connection.connectTimeout = 15000
578
+ connection.readTimeout = 15000
579
+ connection.doInput = true
580
+ connection.connect()
581
+
582
+ // Check if the connection was successful
583
+ if (connection.responseCode != java.net.HttpURLConnection.HTTP_OK) {
584
+ Log.e(TAG, "HTTP error code: ${connection.responseCode}")
585
+ return@Thread
586
+ }
587
+
588
+ // Get the input stream
589
+ val inputStream = connection.inputStream
590
+
591
+ // Create the output file
592
+ val outputFile = File(this.outputPath!!)
593
+
594
+ // Create the output stream
595
+ val outputStream = outputFile.outputStream()
596
+
597
+ // Create a buffer
598
+ val buffer = ByteArray(1024)
599
+ var bytesRead: Int
600
+ var totalBytesRead: Long = 0
601
+ val fileSize = connection.contentLength.toLong()
602
+
603
+ // Read from the input stream and write to the output stream
604
+ while (inputStream.read(buffer).also { bytesRead = it } != -1) {
605
+ outputStream.write(buffer, 0, bytesRead)
606
+ totalBytesRead += bytesRead
607
+
608
+ // Log progress
609
+ if (fileSize > 0) {
610
+ val progress = (totalBytesRead * 100 / fileSize).toInt()
611
+ Log.d(TAG, "Download progress: $progress%")
612
+ }
613
+ }
614
+
615
+ // Close the streams
616
+ outputStream.flush()
617
+ outputStream.close()
618
+ inputStream.close()
619
+
620
+ Log.d(TAG, "File downloaded successfully to ${this.outputPath}")
621
+ } catch (e: Exception) {
622
+ Log.e(TAG, "Error downloading file: ${e.message}", e)
623
+ }
624
+ }.start()
625
+
626
+ isRecording = true
627
+ Log.d(TAG, "Recording started successfully")
628
+ return true
629
+ } ?: run {
630
+ Log.e(TAG, "Cannot start recording: player is null")
631
+ return false
632
+ }
633
+ } catch (e: Exception) {
634
+ Log.e(TAG, "Error starting recording: ${e.message}", e)
635
+ return false
636
+ }
637
+ }
638
+
639
+ /**
640
+ * Stop recording and save the video
641
+ * @return Path to the saved recording
642
+ */
643
+ fun stopRecording(): String {
644
+ Log.d(TAG, "stopRecording called")
645
+
646
+ if (!isRecording) {
647
+ Log.w(TAG, "No recording in progress")
648
+ return ""
649
+ }
650
+
651
+ // Simply mark recording as stopped
652
+ isRecording = false
653
+
654
+ // Wait a moment to ensure any background operations complete
655
+ try {
656
+ Thread.sleep(500)
657
+ } catch (e: InterruptedException) {
658
+ Log.e(TAG, "Sleep interrupted: ${e.message}")
659
+ }
660
+
661
+ // Return the path where the recording was saved
662
+ val savedPath = outputPath ?: ""
663
+ Log.d(TAG, "Recording stopped successfully, saved to: $savedPath")
664
+
665
+ return savedPath
666
+ }
667
+
668
+ private fun cleanupRecording() {
669
+ try {
670
+ videoEncoder?.stop()
671
+ videoEncoder?.release()
672
+ videoEncoder = null
673
+
674
+ recordingSurface?.release()
675
+ recordingSurface = null
676
+
677
+ mediaRecorder?.stop()
678
+ mediaRecorder?.release()
679
+ mediaRecorder = null
680
+
681
+ videoTrackIndex = -1
682
+ isRecording = false
683
+ } catch (e: Exception) {
684
+ Log.e(TAG, "Error cleaning up recording resources: ${e.message}", e)
685
+ }
686
+ }
687
+
688
+ private inner class RecordingRunnable : Runnable {
689
+ override fun run() {
690
+ try {
691
+ // Add video track to muxer
692
+ val videoFormat = videoEncoder?.outputFormat
693
+ if (videoFormat != null && mediaRecorder != null) {
694
+ videoTrackIndex = mediaRecorder!!.addTrack(videoFormat)
695
+ } else {
696
+ Log.e(TAG, "Cannot add track: videoFormat or mediaRecorder is null")
697
+ videoTrackIndex = -1
698
+ }
699
+
700
+ // Start the muxer
701
+ mediaRecorder?.start()
702
+
703
+ // Process encoding
704
+ while (isRecording) {
705
+ val encoderStatus = videoEncoder?.dequeueOutputBuffer(bufferInfo, 10000) ?: -1
706
+
707
+ if (encoderStatus >= 0) {
708
+ val encodedData = videoEncoder?.getOutputBuffer(encoderStatus)
709
+
710
+ if ((bufferInfo.flags and MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
711
+ // Ignore codec config data
712
+ bufferInfo.size = 0
713
+ }
714
+
715
+ if (bufferInfo.size > 0 && encodedData != null && mediaRecorder != null && videoTrackIndex >= 0) {
716
+ encodedData.position(bufferInfo.offset)
717
+ encodedData.limit(bufferInfo.offset + bufferInfo.size)
718
+
719
+ mediaRecorder!!.writeSampleData(videoTrackIndex, encodedData, bufferInfo)
720
+ }
721
+
722
+ videoEncoder?.releaseOutputBuffer(encoderStatus, false)
723
+
724
+ if ((bufferInfo.flags and MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
725
+ break
726
+ }
727
+ } else if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
728
+ // Handle format change if needed
729
+ }
730
+ }
731
+
732
+ // Signal end of stream to encoder
733
+ videoEncoder?.signalEndOfInputStream()
734
+
735
+ } catch (e: Exception) {
736
+ Log.e(TAG, "Error in recording thread: ${e.message}", e)
737
+ }
738
+ }
739
+ }
460
740
  }
@@ -23,6 +23,11 @@ class UnifiedPlayerViewManager : SimpleViewManager<UnifiedPlayerView>() {
23
23
  view.setVideoUrl(url)
24
24
  }
25
25
 
26
+ @ReactProp(name = "thumbnailUrl")
27
+ fun setThumbnailUrl(view: UnifiedPlayerView, url: String?) {
28
+ view.setThumbnailUrl(url)
29
+ }
30
+
26
31
  @ReactProp(name = "autoplay")
27
32
  fun setAutoplay(view: UnifiedPlayerView, autoplay: Boolean) {
28
33
  view.setAutoplay(autoplay)
@@ -57,4 +62,4 @@ class UnifiedPlayerViewManager : SimpleViewManager<UnifiedPlayerView>() {
57
62
  .put("topLoadStart", MapBuilder.of("registrationName", "onLoadStart"))
58
63
  .build()
59
64
  }
60
- }
65
+ }
@@ -216,4 +216,98 @@ RCT_EXPORT_METHOD(capture:(nonnull NSNumber *)reactTag
216
216
  }];
217
217
  }
218
218
 
219
+ RCT_EXPORT_METHOD(startRecording:(nonnull NSNumber *)reactTag
220
+ outputPath:(NSString *)outputPath
221
+ resolver:(RCTPromiseResolveBlock)resolve
222
+ rejecter:(RCTPromiseRejectBlock)reject)
223
+ {
224
+ [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
225
+ UIView *view = viewRegistry[reactTag];
226
+ if (![view isKindOfClass:[UnifiedPlayerUIView class]]) {
227
+ RCTLogError(@"Invalid view returned from registry, expecting UnifiedPlayerUIView, got: %@", view);
228
+ reject(@"E_INVALID_VIEW", @"Expected UnifiedPlayerUIView", nil);
229
+ return;
230
+ }
231
+
232
+ UnifiedPlayerUIView *playerView = (UnifiedPlayerUIView *)view;
233
+
234
+ // If no output path is provided, create a default one in the Documents directory
235
+ NSString *finalOutputPath = outputPath;
236
+ if (!finalOutputPath || [finalOutputPath isEqualToString:@""]) {
237
+ NSString *documentsDirectory = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
238
+ NSString *timestamp = [NSString stringWithFormat:@"%f", [[NSDate date] timeIntervalSince1970]];
239
+ finalOutputPath = [documentsDirectory stringByAppendingPathComponent:[NSString stringWithFormat:@"recording_%@.mp4", timestamp]];
240
+ }
241
+
242
+ BOOL success = [playerView startRecordingToPath:finalOutputPath];
243
+ if (success) {
244
+ resolve(@YES);
245
+ } else {
246
+ reject(@"E_RECORDING_FAILED", @"Failed to start recording", nil);
247
+ }
248
+ }];
249
+ }
250
+
251
+ RCT_EXPORT_METHOD(stopRecording:(nonnull NSNumber *)reactTag
252
+ resolver:(RCTPromiseResolveBlock)resolve
253
+ rejecter:(RCTPromiseRejectBlock)reject)
254
+ {
255
+ [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
256
+ UIView *view = viewRegistry[reactTag];
257
+ if (![view isKindOfClass:[UnifiedPlayerUIView class]]) {
258
+ RCTLogError(@"Invalid view returned from registry, expecting UnifiedPlayerUIView, got: %@", view);
259
+ reject(@"E_INVALID_VIEW", @"Expected UnifiedPlayerUIView", nil);
260
+ return;
261
+ }
262
+
263
+ UnifiedPlayerUIView *playerView = (UnifiedPlayerUIView *)view;
264
+ NSString *filePath = [playerView stopRecording];
265
+
266
+ if (filePath && ![filePath isEqualToString:@""]) {
267
+ resolve(filePath);
268
+ } else {
269
+ reject(@"E_RECORDING_FAILED", @"Failed to stop recording or no recording in progress", nil);
270
+ }
271
+ }];
272
+ }
273
+
274
+ RCT_EXPORT_METHOD(saveVideo:(nonnull NSNumber *)reactTag
275
+ resolver:(RCTPromiseResolveBlock)resolve
276
+ rejecter:(RCTPromiseRejectBlock)reject)
277
+ {
278
+ [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry) {
279
+ UIView *view = viewRegistry[reactTag];
280
+ if (![view isKindOfClass:[UnifiedPlayerUIView class]]) {
281
+ RCTLogError(@"Invalid view returned from registry, expecting UnifiedPlayerUIView, got: %@", view);
282
+ reject(@"E_INVALID_VIEW", @"Expected UnifiedPlayerUIView", nil);
283
+ return;
284
+ }
285
+
286
+ UnifiedPlayerUIView *playerView = (UnifiedPlayerUIView *)view;
287
+
288
+ // Create a file path in the Documents directory
289
+ NSString *documentsDirectory = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
290
+ NSString *timestamp = [NSString stringWithFormat:@"%f", [[NSDate date] timeIntervalSince1970]];
291
+ NSString *filePath = [documentsDirectory stringByAppendingPathComponent:[NSString stringWithFormat:@"saved_video_%@.mp4", timestamp]];
292
+
293
+ // Start recording
294
+ BOOL success = [playerView startRecordingToPath:filePath];
295
+ if (!success) {
296
+ reject(@"E_RECORDING_FAILED", @"Failed to start recording", nil);
297
+ return;
298
+ }
299
+
300
+ // Record for 5 seconds (or adjust as needed)
301
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
302
+ NSString *savedFilePath = [playerView stopRecording];
303
+
304
+ if (savedFilePath && ![savedFilePath isEqualToString:@""]) {
305
+ resolve(savedFilePath);
306
+ } else {
307
+ reject(@"E_RECORDING_FAILED", @"Failed to stop recording or no recording in progress", nil);
308
+ }
309
+ });
310
+ }];
311
+ }
312
+
219
313
  @end
@@ -2,6 +2,8 @@
2
2
  #import <React/RCTView.h>
3
3
  #import <React/RCTComponent.h>
4
4
  #import <MobileVLCKit/MobileVLCKit.h>
5
+ #import <AVFoundation/AVFoundation.h>
6
+ #import <CoreVideo/CoreVideo.h>
5
7
 
6
8
  NS_ASSUME_NONNULL_BEGIN
7
9
 
@@ -9,6 +11,7 @@ NS_ASSUME_NONNULL_BEGIN
9
11
 
10
12
  @property (nonatomic, strong) VLCMediaPlayer *player;
11
13
  @property (nonatomic, copy) NSString *videoUrlString;
14
+ @property (nonatomic, copy) NSString *thumbnailUrlString;
12
15
  @property (nonatomic, assign) BOOL autoplay;
13
16
  @property (nonatomic, assign) BOOL loop;
14
17
  @property (nonatomic, assign) BOOL isPaused;
@@ -17,6 +20,13 @@ NS_ASSUME_NONNULL_BEGIN
17
20
  @property (nonatomic, assign) VLCMediaPlayerState previousState;
18
21
  @property (nonatomic, assign) BOOL hasRenderedVideo;
19
22
  @property (nonatomic, assign) BOOL readyEventSent;
23
+ @property (nonatomic, assign) BOOL isRecording;
24
+ @property (nonatomic, strong) AVAssetWriter *assetWriter;
25
+ @property (nonatomic, strong) AVAssetWriterInput *assetWriterVideoInput;
26
+ @property (nonatomic, strong) AVAssetWriterInputPixelBufferAdaptor *assetWriterPixelBufferAdaptor;
27
+ @property (nonatomic, strong) NSString *recordingPath;
28
+ // We'll use associated objects instead of a property for CADisplayLink
29
+ @property (nonatomic, assign) NSInteger frameCount;
20
30
 
21
31
  // Event callbacks
22
32
  @property (nonatomic, copy) RCTDirectEventBlock onLoadStart;
@@ -31,12 +41,17 @@ NS_ASSUME_NONNULL_BEGIN
31
41
 
32
42
  // Method declarations
33
43
  - (void)setupWithVideoUrlString:(NSString *)videoUrlString;
44
+ - (void)setupThumbnailWithUrlString:(NSString *)thumbnailUrlString;
34
45
  - (void)play;
35
46
  - (void)pause;
36
47
  - (void)seekToTime:(NSNumber *)timeNumber;
37
48
  - (float)getCurrentTime;
38
49
  - (float)getDuration;
39
50
  - (void)captureFrameWithCompletion:(void (^)(NSString * _Nullable base64String, NSError * _Nullable error))completion;
51
+ - (void)captureFrameForRecording;
52
+ - (BOOL)startRecordingToPath:(NSString *)outputPath;
53
+ - (void)startFrameCapture;
54
+ - (NSString *)stopRecording;
40
55
 
41
56
  @end
42
57
 
@@ -7,11 +7,14 @@
7
7
  #import <React/RCTUIManagerUtils.h>
8
8
  #import <React/RCTComponent.h>
9
9
  #import <MobileVLCKit/MobileVLCKit.h>
10
+ #import <objc/runtime.h>
10
11
  #import "UnifiedPlayerModule.h"
11
12
  #import "UnifiedPlayerUIView.h"
12
13
 
13
14
  // Main player view implementation
14
- @implementation UnifiedPlayerUIView
15
+ @implementation UnifiedPlayerUIView {
16
+ UIImageView *_thumbnailImageView;
17
+ }
15
18
 
16
19
  - (instancetype)init {
17
20
  if ((self = [super init])) {
@@ -34,6 +37,13 @@
34
37
  self.contentMode = UIViewContentModeScaleAspectFit;
35
38
  self.clipsToBounds = YES;
36
39
 
40
+ // Create thumbnail image view
41
+ _thumbnailImageView = [[UIImageView alloc] initWithFrame:CGRectZero];
42
+ _thumbnailImageView.contentMode = UIViewContentModeScaleAspectFill;
43
+ _thumbnailImageView.clipsToBounds = YES;
44
+ _thumbnailImageView.hidden = YES;
45
+ [self addSubview:_thumbnailImageView];
46
+
37
47
  // After the view is fully initialized, set it as the drawable
38
48
  _player.drawable = self;
39
49
 
@@ -84,6 +94,66 @@
84
94
  // Let VLC know the size has changed but don't force any redraws here
85
95
  // This may be VLC-specific and not required for all implementations
86
96
  }
97
+
98
+ // Update thumbnail image view frame
99
+ if (_thumbnailImageView) {
100
+ _thumbnailImageView.frame = bounds;
101
+ }
102
+ }
103
+
104
+ - (void)setupThumbnailWithUrlString:(NSString *)thumbnailUrlString {
105
+ RCTLogInfo(@"[UnifiedPlayerViewManager] setupThumbnailWithUrlString: %@", thumbnailUrlString);
106
+
107
+ if (!thumbnailUrlString || [thumbnailUrlString length] == 0) {
108
+ // Hide thumbnail if URL is empty
109
+ _thumbnailImageView.hidden = YES;
110
+ return;
111
+ }
112
+
113
+ // Make sure thumbnail view is properly sized
114
+ _thumbnailImageView.frame = self.bounds;
115
+
116
+ // Show the thumbnail view
117
+ _thumbnailImageView.hidden = NO;
118
+
119
+ // Load the image from URL
120
+ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
121
+ NSURL *imageURL = [NSURL URLWithString:thumbnailUrlString];
122
+ if (!imageURL) {
123
+ // Try with encoding if the original URL doesn't work
124
+ NSString *escapedString = [thumbnailUrlString stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
125
+ imageURL = [NSURL URLWithString:escapedString];
126
+
127
+ if (!imageURL) {
128
+ RCTLogError(@"[UnifiedPlayerViewManager] Invalid thumbnail URL format");
129
+ dispatch_async(dispatch_get_main_queue(), ^{
130
+ self->_thumbnailImageView.hidden = YES;
131
+ });
132
+ return;
133
+ }
134
+ }
135
+
136
+ NSData *imageData = [NSData dataWithContentsOfURL:imageURL];
137
+ if (imageData) {
138
+ UIImage *image = [UIImage imageWithData:imageData];
139
+ if (image) {
140
+ dispatch_async(dispatch_get_main_queue(), ^{
141
+ self->_thumbnailImageView.image = image;
142
+ self->_thumbnailImageView.hidden = NO;
143
+ });
144
+ } else {
145
+ RCTLogError(@"[UnifiedPlayerViewManager] Failed to create image from data");
146
+ dispatch_async(dispatch_get_main_queue(), ^{
147
+ self->_thumbnailImageView.hidden = YES;
148
+ });
149
+ }
150
+ } else {
151
+ RCTLogError(@"[UnifiedPlayerViewManager] Failed to load image data from URL");
152
+ dispatch_async(dispatch_get_main_queue(), ^{
153
+ self->_thumbnailImageView.hidden = YES;
154
+ });
155
+ }
156
+ });
87
157
  }
88
158
 
89
159
  - (void)didMoveToSuperview {
@@ -388,6 +458,227 @@
388
458
  }
389
459
  }
390
460
 
461
+ - (BOOL)startRecordingToPath:(NSString *)outputPath {
462
+ RCTLogInfo(@"[UnifiedPlayerViewManager] startRecordingToPath: %@", outputPath);
463
+
464
+ if (_isRecording) {
465
+ RCTLogError(@"[UnifiedPlayerViewManager] Recording is already in progress");
466
+ return NO;
467
+ }
468
+
469
+ if (!_player || !_player.isPlaying) {
470
+ RCTLogError(@"[UnifiedPlayerViewManager] Cannot start recording: Player is not playing");
471
+ return NO;
472
+ }
473
+
474
+ // Store the recording path
475
+ _recordingPath = [outputPath copy];
476
+
477
+ // Create directory if it doesn't exist
478
+ NSFileManager *fileManager = [NSFileManager defaultManager];
479
+ NSString *directory = [outputPath stringByDeletingLastPathComponent];
480
+ if (![fileManager fileExistsAtPath:directory]) {
481
+ NSError *error = nil;
482
+ [fileManager createDirectoryAtPath:directory withIntermediateDirectories:YES attributes:nil error:&error];
483
+ if (error) {
484
+ RCTLogError(@"[UnifiedPlayerViewManager] Failed to create directory: %@", error);
485
+ return NO;
486
+ }
487
+ }
488
+
489
+ // Set up AVAssetWriter
490
+ NSURL *outputURL = [NSURL fileURLWithPath:outputPath];
491
+
492
+ // Remove existing file if it exists
493
+ if ([fileManager fileExistsAtPath:outputPath]) {
494
+ NSError *error = nil;
495
+ [fileManager removeItemAtPath:outputPath error:&error];
496
+ if (error) {
497
+ RCTLogError(@"[UnifiedPlayerViewManager] Failed to remove existing file: %@", error);
498
+ return NO;
499
+ }
500
+ }
501
+
502
+ NSError *error = nil;
503
+ _assetWriter = [[AVAssetWriter alloc] initWithURL:outputURL fileType:AVFileTypeMPEG4 error:&error];
504
+ if (error) {
505
+ RCTLogError(@"[UnifiedPlayerViewManager] Failed to create asset writer: %@", error);
506
+ return NO;
507
+ }
508
+
509
+ // Get video dimensions
510
+ CGSize videoSize = _player.videoSize;
511
+ if (videoSize.width <= 0 || videoSize.height <= 0) {
512
+ // Use view size as fallback
513
+ videoSize = self.bounds.size;
514
+ }
515
+
516
+ // Configure video settings
517
+ NSDictionary *videoSettings = @{
518
+ AVVideoCodecKey: AVVideoCodecTypeH264,
519
+ AVVideoWidthKey: @((int)videoSize.width),
520
+ AVVideoHeightKey: @((int)videoSize.height),
521
+ AVVideoCompressionPropertiesKey: @{
522
+ AVVideoAverageBitRateKey: @(2000000), // 2 Mbps
523
+ AVVideoProfileLevelKey: AVVideoProfileLevelH264HighAutoLevel
524
+ }
525
+ };
526
+
527
+ // Create video input
528
+ _assetWriterVideoInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo outputSettings:videoSettings];
529
+ _assetWriterVideoInput.expectsMediaDataInRealTime = YES;
530
+
531
+ if ([_assetWriter canAddInput:_assetWriterVideoInput]) {
532
+ [_assetWriter addInput:_assetWriterVideoInput];
533
+ } else {
534
+ RCTLogError(@"[UnifiedPlayerViewManager] Cannot add video input to asset writer");
535
+ return NO;
536
+ }
537
+
538
+ // Create a pixel buffer adaptor for writing pixel buffers
539
+ NSDictionary *pixelBufferAttributes = @{
540
+ (NSString *)kCVPixelBufferPixelFormatTypeKey: @(kCVPixelFormatType_32BGRA),
541
+ (NSString *)kCVPixelBufferWidthKey: @((int)videoSize.width),
542
+ (NSString *)kCVPixelBufferHeightKey: @((int)videoSize.height),
543
+ (NSString *)kCVPixelBufferCGImageCompatibilityKey: @YES,
544
+ (NSString *)kCVPixelBufferCGBitmapContextCompatibilityKey: @YES
545
+ };
546
+
547
+ _assetWriterPixelBufferAdaptor = [AVAssetWriterInputPixelBufferAdaptor
548
+ assetWriterInputPixelBufferAdaptorWithAssetWriterInput:_assetWriterVideoInput
549
+ sourcePixelBufferAttributes:pixelBufferAttributes];
550
+
551
+ // Start recording session
552
+ if ([_assetWriter startWriting]) {
553
+ [_assetWriter startSessionAtSourceTime:kCMTimeZero];
554
+ _isRecording = YES;
555
+
556
+ // Start a timer to capture frames
557
+ [self startFrameCapture];
558
+
559
+ RCTLogInfo(@"[UnifiedPlayerViewManager] Recording started successfully");
560
+ return YES;
561
+ } else {
562
+ RCTLogError(@"[UnifiedPlayerViewManager] Failed to start writing: %@", _assetWriter.error);
563
+ return NO;
564
+ }
565
+ }
566
+
567
+ - (void)startFrameCapture {
568
+ RCTLogInfo(@"[UnifiedPlayerViewManager] Frame capture started");
569
+
570
+ // Create a CADisplayLink to capture frames at the screen refresh rate
571
+ CADisplayLink *displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(captureFrameForRecording)];
572
+ [displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
573
+
574
+ // Store the display link as an associated object
575
+ objc_setAssociatedObject(self, "displayLinkKey", displayLink, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
576
+
577
+ // Initialize frame count
578
+ _frameCount = 0;
579
+ }
580
+
581
+ - (void)captureFrameForRecording {
582
+ if (!_isRecording || !_assetWriterVideoInput.isReadyForMoreMediaData) {
583
+ return;
584
+ }
585
+
586
+ // Create a bitmap context to draw the current view
587
+ CGSize size = _player.videoSize;
588
+ if (size.width <= 0 || size.height <= 0) {
589
+ size = self.bounds.size;
590
+ }
591
+
592
+ // Create a pixel buffer
593
+ CVPixelBufferRef pixelBuffer = NULL;
594
+ CVReturn status = CVPixelBufferPoolCreatePixelBuffer(NULL, _assetWriterPixelBufferAdaptor.pixelBufferPool, &pixelBuffer);
595
+
596
+ if (status != kCVReturnSuccess || pixelBuffer == NULL) {
597
+ RCTLogError(@"[UnifiedPlayerViewManager] Failed to create pixel buffer");
598
+ return;
599
+ }
600
+
601
+ // Lock the pixel buffer
602
+ CVPixelBufferLockBaseAddress(pixelBuffer, 0);
603
+
604
+ // Get the pixel buffer address
605
+ void *pixelData = CVPixelBufferGetBaseAddress(pixelBuffer);
606
+
607
+ // Create a bitmap context
608
+ CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
609
+ CGContextRef context = CGBitmapContextCreate(pixelData,
610
+ size.width,
611
+ size.height,
612
+ 8,
613
+ CVPixelBufferGetBytesPerRow(pixelBuffer),
614
+ colorSpace,
615
+ kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little);
616
+
617
+ // Draw the current view into the context
618
+ UIGraphicsPushContext(context);
619
+ [self.layer renderInContext:context];
620
+ UIGraphicsPopContext();
621
+
622
+ // Clean up
623
+ CGContextRelease(context);
624
+ CGColorSpaceRelease(colorSpace);
625
+
626
+ // Unlock the pixel buffer
627
+ CVPixelBufferUnlockBaseAddress(pixelBuffer, 0);
628
+
629
+ // Calculate the presentation time
630
+ CMTime presentationTime = CMTimeMake(_frameCount, 30); // 30 fps
631
+
632
+ // Append the pixel buffer to the asset writer
633
+ if (![_assetWriterPixelBufferAdaptor appendPixelBuffer:pixelBuffer withPresentationTime:presentationTime]) {
634
+ RCTLogError(@"[UnifiedPlayerViewManager] Failed to append pixel buffer: %@", _assetWriter.error);
635
+ }
636
+
637
+ // Release the pixel buffer
638
+ CVPixelBufferRelease(pixelBuffer);
639
+
640
+ // Increment the frame count
641
+ _frameCount++;
642
+ }
643
+
644
+ - (NSString *)stopRecording {
645
+ RCTLogInfo(@"[UnifiedPlayerViewManager] stopRecording called");
646
+
647
+ if (!_isRecording) {
648
+ RCTLogError(@"[UnifiedPlayerViewManager] No recording in progress");
649
+ return @"";
650
+ }
651
+
652
+ // Stop frame capture by stopping the display link
653
+ CADisplayLink *displayLink = objc_getAssociatedObject(self, "displayLinkKey");
654
+ if (displayLink) {
655
+ [displayLink invalidate];
656
+ objc_setAssociatedObject(self, "displayLinkKey", nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
657
+ }
658
+
659
+ // Finish writing
660
+ [_assetWriterVideoInput markAsFinished];
661
+ [_assetWriter finishWritingWithCompletionHandler:^{
662
+ if (self->_assetWriter.status == AVAssetWriterStatusCompleted) {
663
+ RCTLogInfo(@"[UnifiedPlayerViewManager] Recording completed successfully");
664
+ } else {
665
+ RCTLogError(@"[UnifiedPlayerViewManager] Recording failed: %@", self->_assetWriter.error);
666
+ }
667
+
668
+ // Clean up
669
+ self->_assetWriter = nil;
670
+ self->_assetWriterVideoInput = nil;
671
+ self->_assetWriterPixelBufferAdaptor = nil;
672
+ self->_isRecording = NO;
673
+ self->_frameCount = 0;
674
+ }];
675
+
676
+ NSString *path = _recordingPath;
677
+ _recordingPath = nil;
678
+
679
+ return path;
680
+ }
681
+
391
682
  - (void)setAutoplay:(BOOL)autoplay {
392
683
  _autoplay = autoplay;
393
684
  }
@@ -459,6 +750,11 @@
459
750
  if (videoTracks.count > 0) {
460
751
  RCTLogInfo(@"[UnifiedPlayerViewManager] Video tracks found: %lu", (unsigned long)videoTracks.count);
461
752
 
753
+ // Hide thumbnail when video starts playing
754
+ if (_thumbnailImageView) {
755
+ _thumbnailImageView.hidden = YES;
756
+ }
757
+
462
758
  // Send playing event when we actually start playing
463
759
  [self sendEvent:@"onPlaying" body:@{}];
464
760
 
@@ -556,6 +852,18 @@
556
852
  // Remove all observers
557
853
  [[NSNotificationCenter defaultCenter] removeObserver:self];
558
854
 
855
+ // Stop recording if in progress
856
+ if (_isRecording) {
857
+ [self stopRecording];
858
+ }
859
+
860
+ // Clean up display link if it exists
861
+ CADisplayLink *displayLink = objc_getAssociatedObject(self, "displayLinkKey");
862
+ if (displayLink) {
863
+ [displayLink invalidate];
864
+ objc_setAssociatedObject(self, "displayLinkKey", nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
865
+ }
866
+
559
867
  // Stop playback and release player
560
868
  [_player stop];
561
869
  _player.delegate = nil;
@@ -605,6 +913,13 @@ RCT_CUSTOM_VIEW_PROPERTY(videoUrl, NSString, UnifiedPlayerUIView)
605
913
  [view setupWithVideoUrlString:json];
606
914
  }
607
915
 
916
+ // Thumbnail URL property
917
+ RCT_CUSTOM_VIEW_PROPERTY(thumbnailUrl, NSString, UnifiedPlayerUIView)
918
+ {
919
+ view.thumbnailUrlString = json;
920
+ [view setupThumbnailWithUrlString:json];
921
+ }
922
+
608
923
  // Autoplay property
609
924
  RCT_CUSTOM_VIEW_PROPERTY(autoplay, BOOL, UnifiedPlayerUIView)
610
925
  {
@@ -163,6 +163,47 @@ export const UnifiedPlayer = {
163
163
  console.log('Error calling capture:', error instanceof Error ? error.message : String(error));
164
164
  return Promise.reject(error);
165
165
  }
166
+ },
167
+ /**
168
+ * Start recording the video
169
+ * @param viewTag - The tag of the player view
170
+ * @param outputPath - Optional path where to save the recording (platform-specific)
171
+ * @returns Promise resolving to true if recording started successfully
172
+ */
173
+ startRecording: (viewTag, outputPath) => {
174
+ try {
175
+ console.log('UnifiedPlayer.startRecording called with viewTag:', viewTag);
176
+ return UnifiedPlayerModule.startRecording(viewTag, outputPath).then(result => {
177
+ console.log('Native startRecording method called successfully');
178
+ return result;
179
+ }).catch(error => {
180
+ console.log('Error calling startRecording:', error instanceof Error ? error.message : String(error));
181
+ throw error;
182
+ });
183
+ } catch (error) {
184
+ console.log('Error calling startRecording:', error instanceof Error ? error.message : String(error));
185
+ return Promise.reject(error);
186
+ }
187
+ },
188
+ /**
189
+ * Stop recording the video
190
+ * @param viewTag - The tag of the player view
191
+ * @returns Promise resolving to the path of the saved recording
192
+ */
193
+ stopRecording: viewTag => {
194
+ try {
195
+ console.log('UnifiedPlayer.stopRecording called with viewTag:', viewTag);
196
+ return UnifiedPlayerModule.stopRecording(viewTag).then(filePath => {
197
+ console.log('Native stopRecording method called successfully');
198
+ return filePath;
199
+ }).catch(error => {
200
+ console.log('Error calling stopRecording:', error instanceof Error ? error.message : String(error));
201
+ throw error;
202
+ });
203
+ } catch (error) {
204
+ console.log('Error calling stopRecording:', error instanceof Error ? error.message : String(error));
205
+ return Promise.reject(error);
206
+ }
166
207
  }
167
208
  };
168
209
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"names":["forwardRef","requireNativeComponent","UIManager","NativeModules","Platform","jsx","_jsx","LINKING_ERROR","select","ios","default","getViewManagerConfig","UnifiedPlayer","Error","NativeUnifiedPlayerView","UnifiedPlayerModule","UnifiedPlayerEventTypes","LOAD_START","READY","ERROR","PROGRESS","COMPLETE","STALLED","RESUMED","PLAYING","PAUSED","UnifiedPlayerEvents","UnifiedPlayerView","props","ref","play","viewTag","console","log","then","result","catch","error","message","String","Promise","reject","pause","seekTo","time","getCurrentTime","getDuration","capture","base64String"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"names":["forwardRef","requireNativeComponent","UIManager","NativeModules","Platform","jsx","_jsx","LINKING_ERROR","select","ios","default","getViewManagerConfig","UnifiedPlayer","Error","NativeUnifiedPlayerView","UnifiedPlayerModule","UnifiedPlayerEventTypes","LOAD_START","READY","ERROR","PROGRESS","COMPLETE","STALLED","RESUMED","PLAYING","PAUSED","UnifiedPlayerEvents","UnifiedPlayerView","props","ref","play","viewTag","console","log","then","result","catch","error","message","String","Promise","reject","pause","seekTo","time","getCurrentTime","getDuration","capture","base64String","startRecording","outputPath","stopRecording","filePath"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":"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","ignoreList":[]}
@@ -1,6 +1,7 @@
1
1
  import { type ViewStyle } from 'react-native';
2
2
  export type UnifiedPlayerProps = {
3
3
  videoUrl: string;
4
+ thumbnailUrl?: string;
4
5
  style: ViewStyle;
5
6
  autoplay?: boolean;
6
7
  loop?: boolean;
@@ -75,5 +76,18 @@ export declare const UnifiedPlayer: {
75
76
  * @returns Promise resolving to the base64 encoded image string
76
77
  */
77
78
  capture: (viewTag: number) => Promise<string>;
79
+ /**
80
+ * Start recording the video
81
+ * @param viewTag - The tag of the player view
82
+ * @param outputPath - Optional path where to save the recording (platform-specific)
83
+ * @returns Promise resolving to true if recording started successfully
84
+ */
85
+ startRecording: (viewTag: number, outputPath?: string) => Promise<boolean>;
86
+ /**
87
+ * Stop recording the video
88
+ * @param viewTag - The tag of the player view
89
+ * @returns Promise resolving to the path of the saved recording
90
+ */
91
+ stopRecording: (viewTag: number) => Promise<string>;
78
92
  };
79
93
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.tsx"],"names":[],"mappings":"AACA,OAAO,EAKL,KAAK,SAAS,EACf,MAAM,cAAc,CAAC;AAkBtB,MAAM,MAAM,kBAAkB,GAAG;IAE/B,QAAQ,EAAE,MAAM,CAAC;IAGjB,KAAK,EAAE,SAAS,CAAC;IAGjB,QAAQ,CAAC,EAAE,OAAO,CAAC;IAGnB,IAAI,CAAC,EAAE,OAAO,CAAC;IAGf,QAAQ,CAAC,EAAE,OAAO,CAAC;IAGnB,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IAGzB,aAAa,CAAC,EAAE,MAAM,IAAI,CAAC;IAG3B,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,IAAI,CAAC;IAG/B,kBAAkB,CAAC,EAAE,MAAM,IAAI,CAAC;IAGhC,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IAGvE,iBAAiB,CAAC,EAAE,MAAM,IAAI,CAAC;IAG/B,iBAAiB,CAAC,EAAE,MAAM,IAAI,CAAC;IAG/B,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IAGtB,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;CACxB,CAAC;AAYF,eAAO,MAAM,uBAAuB;;;;;;;;;;CAUnC,CAAC;AAGF,eAAO,MAAM,mBAAmB,KAA8B,CAAC;AAE/D;;GAEG;AACH,eAAO,MAAM,iBAAiB,8LAK5B,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,aAAa;IACxB;;;;OAIG;oBACa,MAAM,KAAG,OAAO,CAAC,OAAO,CAAC;IAwBzC;;;;OAIG;qBACc,MAAM,KAAG,OAAO,CAAC,OAAO,CAAC;IAwB1C;;;;;OAKG;sBACe,MAAM,QAAQ,MAAM,KAAG,OAAO,CAAC,OAAO,CAAC;IA6BzD;;;;OAIG;8BACuB,MAAM,KAAG,OAAO,CAAC,MAAM,CAAC;IAalD;;;;OAIG;2BACoB,MAAM,KAAG,OAAO,CAAC,MAAM,CAAC;IAa/C;;;;OAIG;uBACgB,MAAM,KAAG,OAAO,CAAC,MAAM,CAAC;CAuB5C,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.tsx"],"names":[],"mappings":"AACA,OAAO,EAKL,KAAK,SAAS,EACf,MAAM,cAAc,CAAC;AAkBtB,MAAM,MAAM,kBAAkB,GAAG;IAE/B,QAAQ,EAAE,MAAM,CAAC;IAGjB,YAAY,CAAC,EAAE,MAAM,CAAC;IAGtB,KAAK,EAAE,SAAS,CAAC;IAGjB,QAAQ,CAAC,EAAE,OAAO,CAAC;IAGnB,IAAI,CAAC,EAAE,OAAO,CAAC;IAGf,QAAQ,CAAC,EAAE,OAAO,CAAC;IAGnB,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IAGzB,aAAa,CAAC,EAAE,MAAM,IAAI,CAAC;IAG3B,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,IAAI,CAAC;IAG/B,kBAAkB,CAAC,EAAE,MAAM,IAAI,CAAC;IAGhC,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IAGvE,iBAAiB,CAAC,EAAE,MAAM,IAAI,CAAC;IAG/B,iBAAiB,CAAC,EAAE,MAAM,IAAI,CAAC;IAG/B,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IAGtB,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;CACxB,CAAC;AAYF,eAAO,MAAM,uBAAuB;;;;;;;;;;CAUnC,CAAC;AAGF,eAAO,MAAM,mBAAmB,KAA8B,CAAC;AAE/D;;GAEG;AACH,eAAO,MAAM,iBAAiB,8LAK5B,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,aAAa;IACxB;;;;OAIG;oBACa,MAAM,KAAG,OAAO,CAAC,OAAO,CAAC;IAwBzC;;;;OAIG;qBACc,MAAM,KAAG,OAAO,CAAC,OAAO,CAAC;IAwB1C;;;;;OAKG;sBACe,MAAM,QAAQ,MAAM,KAAG,OAAO,CAAC,OAAO,CAAC;IA6BzD;;;;OAIG;8BACuB,MAAM,KAAG,OAAO,CAAC,MAAM,CAAC;IAalD;;;;OAIG;2BACoB,MAAM,KAAG,OAAO,CAAC,MAAM,CAAC;IAa/C;;;;OAIG;uBACgB,MAAM,KAAG,OAAO,CAAC,MAAM,CAAC;IAwB3C;;;;;OAKG;8BACuB,MAAM,eAAe,MAAM,KAAG,OAAO,CAAC,OAAO,CAAC;IAwBxE;;;;OAIG;6BACsB,MAAM,KAAG,OAAO,CAAC,MAAM,CAAC;CAuBlD,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-unified-player",
3
- "version": "0.3.7",
3
+ "version": "0.3.9",
4
4
  "description": "Unified Player",
5
5
  "source": "./src/index.tsx",
6
6
  "main": "./lib/module/index.js",
package/src/index.tsx CHANGED
@@ -27,6 +27,9 @@ export type UnifiedPlayerProps = {
27
27
  // Video source URL
28
28
  videoUrl: string;
29
29
 
30
+ // Thumbnail image URL to display until video starts playing
31
+ thumbnailUrl?: string;
32
+
30
33
  // Apply custom styling
31
34
  style: ViewStyle;
32
35
 
@@ -263,4 +266,63 @@ export const UnifiedPlayer = {
263
266
  return Promise.reject(error);
264
267
  }
265
268
  },
269
+
270
+ /**
271
+ * Start recording the video
272
+ * @param viewTag - The tag of the player view
273
+ * @param outputPath - Optional path where to save the recording (platform-specific)
274
+ * @returns Promise resolving to true if recording started successfully
275
+ */
276
+ startRecording: (viewTag: number, outputPath?: string): Promise<boolean> => {
277
+ try {
278
+ console.log('UnifiedPlayer.startRecording called with viewTag:', viewTag);
279
+ return UnifiedPlayerModule.startRecording(viewTag, outputPath)
280
+ .then((result: boolean) => {
281
+ console.log('Native startRecording method called successfully');
282
+ return result;
283
+ })
284
+ .catch((error: any) => {
285
+ console.log(
286
+ 'Error calling startRecording:',
287
+ error instanceof Error ? error.message : String(error)
288
+ );
289
+ throw error;
290
+ });
291
+ } catch (error) {
292
+ console.log(
293
+ 'Error calling startRecording:',
294
+ error instanceof Error ? error.message : String(error)
295
+ );
296
+ return Promise.reject(error);
297
+ }
298
+ },
299
+
300
+ /**
301
+ * Stop recording the video
302
+ * @param viewTag - The tag of the player view
303
+ * @returns Promise resolving to the path of the saved recording
304
+ */
305
+ stopRecording: (viewTag: number): Promise<string> => {
306
+ try {
307
+ console.log('UnifiedPlayer.stopRecording called with viewTag:', viewTag);
308
+ return UnifiedPlayerModule.stopRecording(viewTag)
309
+ .then((filePath: string) => {
310
+ console.log('Native stopRecording method called successfully');
311
+ return filePath;
312
+ })
313
+ .catch((error: any) => {
314
+ console.log(
315
+ 'Error calling stopRecording:',
316
+ error instanceof Error ? error.message : String(error)
317
+ );
318
+ throw error;
319
+ });
320
+ } catch (error) {
321
+ console.log(
322
+ 'Error calling stopRecording:',
323
+ error instanceof Error ? error.message : String(error)
324
+ );
325
+ return Promise.reject(error);
326
+ }
327
+ },
266
328
  };