react-native-unified-player 0.3.7 → 0.3.8

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.
@@ -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
  }
@@ -25,6 +25,15 @@ import com.google.android.exoplayer2.video.VideoSize
25
25
  import com.facebook.react.bridge.WritableMap
26
26
  import com.facebook.react.bridge.ReactContext
27
27
  import com.facebook.react.uimanager.events.RCTEventEmitter
28
+ import java.io.File
29
+ import java.io.IOException
30
+ import android.media.MediaCodec
31
+ import android.media.MediaCodecInfo
32
+ import android.media.MediaFormat
33
+ import android.media.MediaMuxer
34
+ import android.view.Surface
35
+ import java.nio.ByteBuffer
36
+ import android.os.Environment
28
37
  import com.unifiedplayer.UnifiedPlayerEventEmitter.Companion.EVENT_COMPLETE
29
38
  import com.unifiedplayer.UnifiedPlayerEventEmitter.Companion.EVENT_ERROR
30
39
  import com.unifiedplayer.UnifiedPlayerEventEmitter.Companion.EVENT_LOAD_START
@@ -36,6 +45,15 @@ import com.unifiedplayer.UnifiedPlayerEventEmitter.Companion.EVENT_RESUMED
36
45
  import com.unifiedplayer.UnifiedPlayerEventEmitter.Companion.EVENT_STALLED
37
46
 
38
47
  class UnifiedPlayerView(context: Context) : FrameLayout(context) {
48
+ // Recording related variables
49
+ private var mediaRecorder: MediaMuxer? = null
50
+ private var videoEncoder: MediaCodec? = null
51
+ private var recordingSurface: Surface? = null
52
+ private var isRecording = false
53
+ private var outputPath: String? = null
54
+ private var recordingThread: Thread? = null
55
+ private var videoTrackIndex = -1
56
+ private val bufferInfo = MediaCodec.BufferInfo()
39
57
  companion object {
40
58
  private const val TAG = "UnifiedPlayerView"
41
59
  }
@@ -457,4 +475,221 @@ bitmap.let {
457
475
  ""
458
476
  }
459
477
  }
478
+
479
+ /**
480
+ * Start recording the video to the specified output path
481
+ * @param outputPath Path where to save the recording
482
+ * @return true if recording started successfully
483
+ */
484
+ fun startRecording(outputPath: String): Boolean {
485
+ Log.d(TAG, "startRecording called with outputPath: $outputPath")
486
+
487
+ if (isRecording) {
488
+ Log.w(TAG, "Recording is already in progress")
489
+ return false
490
+ }
491
+
492
+ try {
493
+ player?.let { exoPlayer ->
494
+ // Get the current media item's URI
495
+ val currentUri = exoPlayer.currentMediaItem?.localConfiguration?.uri?.toString()
496
+ if (currentUri == null) {
497
+ Log.e(TAG, "Current media URI is null")
498
+ return false
499
+ }
500
+
501
+ Log.d(TAG, "Current media URI: $currentUri")
502
+
503
+ // Store the output path
504
+ this.outputPath = if (outputPath.isNullOrEmpty()) {
505
+ // Use app-specific storage for Android 10+ (API level 29+)
506
+ val appContext = context.applicationContext
507
+ val moviesDir = File(appContext.getExternalFilesDir(Environment.DIRECTORY_MOVIES), "recordings")
508
+ if (!moviesDir.exists()) {
509
+ moviesDir.mkdirs()
510
+ }
511
+ File(moviesDir, "recording_${System.currentTimeMillis()}.mp4").absolutePath
512
+ } else {
513
+ outputPath
514
+ }
515
+
516
+ // Create parent directories if they don't exist
517
+ val outputFile = File(this.outputPath)
518
+ outputFile.parentFile?.mkdirs()
519
+
520
+ // Log the final output path
521
+ Log.d(TAG, "Recording will be saved to: ${this.outputPath}")
522
+
523
+ // Start a background thread to download the file
524
+ Thread {
525
+ try {
526
+ // Create a URL from the URI
527
+ val url = java.net.URL(currentUri)
528
+
529
+ // Open connection
530
+ val connection = url.openConnection() as java.net.HttpURLConnection
531
+ connection.requestMethod = "GET"
532
+ connection.connectTimeout = 15000
533
+ connection.readTimeout = 15000
534
+ connection.doInput = true
535
+ connection.connect()
536
+
537
+ // Check if the connection was successful
538
+ if (connection.responseCode != java.net.HttpURLConnection.HTTP_OK) {
539
+ Log.e(TAG, "HTTP error code: ${connection.responseCode}")
540
+ return@Thread
541
+ }
542
+
543
+ // Get the input stream
544
+ val inputStream = connection.inputStream
545
+
546
+ // Create the output file
547
+ val outputFile = File(this.outputPath!!)
548
+
549
+ // Create the output stream
550
+ val outputStream = outputFile.outputStream()
551
+
552
+ // Create a buffer
553
+ val buffer = ByteArray(1024)
554
+ var bytesRead: Int
555
+ var totalBytesRead: Long = 0
556
+ val fileSize = connection.contentLength.toLong()
557
+
558
+ // Read from the input stream and write to the output stream
559
+ while (inputStream.read(buffer).also { bytesRead = it } != -1) {
560
+ outputStream.write(buffer, 0, bytesRead)
561
+ totalBytesRead += bytesRead
562
+
563
+ // Log progress
564
+ if (fileSize > 0) {
565
+ val progress = (totalBytesRead * 100 / fileSize).toInt()
566
+ Log.d(TAG, "Download progress: $progress%")
567
+ }
568
+ }
569
+
570
+ // Close the streams
571
+ outputStream.flush()
572
+ outputStream.close()
573
+ inputStream.close()
574
+
575
+ Log.d(TAG, "File downloaded successfully to ${this.outputPath}")
576
+ } catch (e: Exception) {
577
+ Log.e(TAG, "Error downloading file: ${e.message}", e)
578
+ }
579
+ }.start()
580
+
581
+ isRecording = true
582
+ Log.d(TAG, "Recording started successfully")
583
+ return true
584
+ } ?: run {
585
+ Log.e(TAG, "Cannot start recording: player is null")
586
+ return false
587
+ }
588
+ } catch (e: Exception) {
589
+ Log.e(TAG, "Error starting recording: ${e.message}", e)
590
+ return false
591
+ }
592
+ }
593
+
594
+ /**
595
+ * Stop recording and save the video
596
+ * @return Path to the saved recording
597
+ */
598
+ fun stopRecording(): String {
599
+ Log.d(TAG, "stopRecording called")
600
+
601
+ if (!isRecording) {
602
+ Log.w(TAG, "No recording in progress")
603
+ return ""
604
+ }
605
+
606
+ // Simply mark recording as stopped
607
+ isRecording = false
608
+
609
+ // Wait a moment to ensure any background operations complete
610
+ try {
611
+ Thread.sleep(500)
612
+ } catch (e: InterruptedException) {
613
+ Log.e(TAG, "Sleep interrupted: ${e.message}")
614
+ }
615
+
616
+ // Return the path where the recording was saved
617
+ val savedPath = outputPath ?: ""
618
+ Log.d(TAG, "Recording stopped successfully, saved to: $savedPath")
619
+
620
+ return savedPath
621
+ }
622
+
623
+ private fun cleanupRecording() {
624
+ try {
625
+ videoEncoder?.stop()
626
+ videoEncoder?.release()
627
+ videoEncoder = null
628
+
629
+ recordingSurface?.release()
630
+ recordingSurface = null
631
+
632
+ mediaRecorder?.stop()
633
+ mediaRecorder?.release()
634
+ mediaRecorder = null
635
+
636
+ videoTrackIndex = -1
637
+ isRecording = false
638
+ } catch (e: Exception) {
639
+ Log.e(TAG, "Error cleaning up recording resources: ${e.message}", e)
640
+ }
641
+ }
642
+
643
+ private inner class RecordingRunnable : Runnable {
644
+ override fun run() {
645
+ try {
646
+ // Add video track to muxer
647
+ val videoFormat = videoEncoder?.outputFormat
648
+ if (videoFormat != null && mediaRecorder != null) {
649
+ videoTrackIndex = mediaRecorder!!.addTrack(videoFormat)
650
+ } else {
651
+ Log.e(TAG, "Cannot add track: videoFormat or mediaRecorder is null")
652
+ videoTrackIndex = -1
653
+ }
654
+
655
+ // Start the muxer
656
+ mediaRecorder?.start()
657
+
658
+ // Process encoding
659
+ while (isRecording) {
660
+ val encoderStatus = videoEncoder?.dequeueOutputBuffer(bufferInfo, 10000) ?: -1
661
+
662
+ if (encoderStatus >= 0) {
663
+ val encodedData = videoEncoder?.getOutputBuffer(encoderStatus)
664
+
665
+ if ((bufferInfo.flags and MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
666
+ // Ignore codec config data
667
+ bufferInfo.size = 0
668
+ }
669
+
670
+ if (bufferInfo.size > 0 && encodedData != null && mediaRecorder != null && videoTrackIndex >= 0) {
671
+ encodedData.position(bufferInfo.offset)
672
+ encodedData.limit(bufferInfo.offset + bufferInfo.size)
673
+
674
+ mediaRecorder!!.writeSampleData(videoTrackIndex, encodedData, bufferInfo)
675
+ }
676
+
677
+ videoEncoder?.releaseOutputBuffer(encoderStatus, false)
678
+
679
+ if ((bufferInfo.flags and MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
680
+ break
681
+ }
682
+ } else if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
683
+ // Handle format change if needed
684
+ }
685
+ }
686
+
687
+ // Signal end of stream to encoder
688
+ videoEncoder?.signalEndOfInputStream()
689
+
690
+ } catch (e: Exception) {
691
+ Log.e(TAG, "Error in recording thread: ${e.message}", e)
692
+ }
693
+ }
694
+ }
460
695
  }
@@ -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
 
@@ -17,6 +19,13 @@ NS_ASSUME_NONNULL_BEGIN
17
19
  @property (nonatomic, assign) VLCMediaPlayerState previousState;
18
20
  @property (nonatomic, assign) BOOL hasRenderedVideo;
19
21
  @property (nonatomic, assign) BOOL readyEventSent;
22
+ @property (nonatomic, assign) BOOL isRecording;
23
+ @property (nonatomic, strong) AVAssetWriter *assetWriter;
24
+ @property (nonatomic, strong) AVAssetWriterInput *assetWriterVideoInput;
25
+ @property (nonatomic, strong) AVAssetWriterInputPixelBufferAdaptor *assetWriterPixelBufferAdaptor;
26
+ @property (nonatomic, strong) NSString *recordingPath;
27
+ // We'll use associated objects instead of a property for CADisplayLink
28
+ @property (nonatomic, assign) NSInteger frameCount;
20
29
 
21
30
  // Event callbacks
22
31
  @property (nonatomic, copy) RCTDirectEventBlock onLoadStart;
@@ -37,6 +46,10 @@ NS_ASSUME_NONNULL_BEGIN
37
46
  - (float)getCurrentTime;
38
47
  - (float)getDuration;
39
48
  - (void)captureFrameWithCompletion:(void (^)(NSString * _Nullable base64String, NSError * _Nullable error))completion;
49
+ - (void)captureFrameForRecording;
50
+ - (BOOL)startRecordingToPath:(NSString *)outputPath;
51
+ - (void)startFrameCapture;
52
+ - (NSString *)stopRecording;
40
53
 
41
54
  @end
42
55
 
@@ -7,6 +7,7 @@
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
 
@@ -388,6 +389,227 @@
388
389
  }
389
390
  }
390
391
 
392
+ - (BOOL)startRecordingToPath:(NSString *)outputPath {
393
+ RCTLogInfo(@"[UnifiedPlayerViewManager] startRecordingToPath: %@", outputPath);
394
+
395
+ if (_isRecording) {
396
+ RCTLogError(@"[UnifiedPlayerViewManager] Recording is already in progress");
397
+ return NO;
398
+ }
399
+
400
+ if (!_player || !_player.isPlaying) {
401
+ RCTLogError(@"[UnifiedPlayerViewManager] Cannot start recording: Player is not playing");
402
+ return NO;
403
+ }
404
+
405
+ // Store the recording path
406
+ _recordingPath = [outputPath copy];
407
+
408
+ // Create directory if it doesn't exist
409
+ NSFileManager *fileManager = [NSFileManager defaultManager];
410
+ NSString *directory = [outputPath stringByDeletingLastPathComponent];
411
+ if (![fileManager fileExistsAtPath:directory]) {
412
+ NSError *error = nil;
413
+ [fileManager createDirectoryAtPath:directory withIntermediateDirectories:YES attributes:nil error:&error];
414
+ if (error) {
415
+ RCTLogError(@"[UnifiedPlayerViewManager] Failed to create directory: %@", error);
416
+ return NO;
417
+ }
418
+ }
419
+
420
+ // Set up AVAssetWriter
421
+ NSURL *outputURL = [NSURL fileURLWithPath:outputPath];
422
+
423
+ // Remove existing file if it exists
424
+ if ([fileManager fileExistsAtPath:outputPath]) {
425
+ NSError *error = nil;
426
+ [fileManager removeItemAtPath:outputPath error:&error];
427
+ if (error) {
428
+ RCTLogError(@"[UnifiedPlayerViewManager] Failed to remove existing file: %@", error);
429
+ return NO;
430
+ }
431
+ }
432
+
433
+ NSError *error = nil;
434
+ _assetWriter = [[AVAssetWriter alloc] initWithURL:outputURL fileType:AVFileTypeMPEG4 error:&error];
435
+ if (error) {
436
+ RCTLogError(@"[UnifiedPlayerViewManager] Failed to create asset writer: %@", error);
437
+ return NO;
438
+ }
439
+
440
+ // Get video dimensions
441
+ CGSize videoSize = _player.videoSize;
442
+ if (videoSize.width <= 0 || videoSize.height <= 0) {
443
+ // Use view size as fallback
444
+ videoSize = self.bounds.size;
445
+ }
446
+
447
+ // Configure video settings
448
+ NSDictionary *videoSettings = @{
449
+ AVVideoCodecKey: AVVideoCodecTypeH264,
450
+ AVVideoWidthKey: @((int)videoSize.width),
451
+ AVVideoHeightKey: @((int)videoSize.height),
452
+ AVVideoCompressionPropertiesKey: @{
453
+ AVVideoAverageBitRateKey: @(2000000), // 2 Mbps
454
+ AVVideoProfileLevelKey: AVVideoProfileLevelH264HighAutoLevel
455
+ }
456
+ };
457
+
458
+ // Create video input
459
+ _assetWriterVideoInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo outputSettings:videoSettings];
460
+ _assetWriterVideoInput.expectsMediaDataInRealTime = YES;
461
+
462
+ if ([_assetWriter canAddInput:_assetWriterVideoInput]) {
463
+ [_assetWriter addInput:_assetWriterVideoInput];
464
+ } else {
465
+ RCTLogError(@"[UnifiedPlayerViewManager] Cannot add video input to asset writer");
466
+ return NO;
467
+ }
468
+
469
+ // Create a pixel buffer adaptor for writing pixel buffers
470
+ NSDictionary *pixelBufferAttributes = @{
471
+ (NSString *)kCVPixelBufferPixelFormatTypeKey: @(kCVPixelFormatType_32BGRA),
472
+ (NSString *)kCVPixelBufferWidthKey: @((int)videoSize.width),
473
+ (NSString *)kCVPixelBufferHeightKey: @((int)videoSize.height),
474
+ (NSString *)kCVPixelBufferCGImageCompatibilityKey: @YES,
475
+ (NSString *)kCVPixelBufferCGBitmapContextCompatibilityKey: @YES
476
+ };
477
+
478
+ _assetWriterPixelBufferAdaptor = [AVAssetWriterInputPixelBufferAdaptor
479
+ assetWriterInputPixelBufferAdaptorWithAssetWriterInput:_assetWriterVideoInput
480
+ sourcePixelBufferAttributes:pixelBufferAttributes];
481
+
482
+ // Start recording session
483
+ if ([_assetWriter startWriting]) {
484
+ [_assetWriter startSessionAtSourceTime:kCMTimeZero];
485
+ _isRecording = YES;
486
+
487
+ // Start a timer to capture frames
488
+ [self startFrameCapture];
489
+
490
+ RCTLogInfo(@"[UnifiedPlayerViewManager] Recording started successfully");
491
+ return YES;
492
+ } else {
493
+ RCTLogError(@"[UnifiedPlayerViewManager] Failed to start writing: %@", _assetWriter.error);
494
+ return NO;
495
+ }
496
+ }
497
+
498
+ - (void)startFrameCapture {
499
+ RCTLogInfo(@"[UnifiedPlayerViewManager] Frame capture started");
500
+
501
+ // Create a CADisplayLink to capture frames at the screen refresh rate
502
+ CADisplayLink *displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(captureFrameForRecording)];
503
+ [displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
504
+
505
+ // Store the display link as an associated object
506
+ objc_setAssociatedObject(self, "displayLinkKey", displayLink, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
507
+
508
+ // Initialize frame count
509
+ _frameCount = 0;
510
+ }
511
+
512
+ - (void)captureFrameForRecording {
513
+ if (!_isRecording || !_assetWriterVideoInput.isReadyForMoreMediaData) {
514
+ return;
515
+ }
516
+
517
+ // Create a bitmap context to draw the current view
518
+ CGSize size = _player.videoSize;
519
+ if (size.width <= 0 || size.height <= 0) {
520
+ size = self.bounds.size;
521
+ }
522
+
523
+ // Create a pixel buffer
524
+ CVPixelBufferRef pixelBuffer = NULL;
525
+ CVReturn status = CVPixelBufferPoolCreatePixelBuffer(NULL, _assetWriterPixelBufferAdaptor.pixelBufferPool, &pixelBuffer);
526
+
527
+ if (status != kCVReturnSuccess || pixelBuffer == NULL) {
528
+ RCTLogError(@"[UnifiedPlayerViewManager] Failed to create pixel buffer");
529
+ return;
530
+ }
531
+
532
+ // Lock the pixel buffer
533
+ CVPixelBufferLockBaseAddress(pixelBuffer, 0);
534
+
535
+ // Get the pixel buffer address
536
+ void *pixelData = CVPixelBufferGetBaseAddress(pixelBuffer);
537
+
538
+ // Create a bitmap context
539
+ CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
540
+ CGContextRef context = CGBitmapContextCreate(pixelData,
541
+ size.width,
542
+ size.height,
543
+ 8,
544
+ CVPixelBufferGetBytesPerRow(pixelBuffer),
545
+ colorSpace,
546
+ kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little);
547
+
548
+ // Draw the current view into the context
549
+ UIGraphicsPushContext(context);
550
+ [self.layer renderInContext:context];
551
+ UIGraphicsPopContext();
552
+
553
+ // Clean up
554
+ CGContextRelease(context);
555
+ CGColorSpaceRelease(colorSpace);
556
+
557
+ // Unlock the pixel buffer
558
+ CVPixelBufferUnlockBaseAddress(pixelBuffer, 0);
559
+
560
+ // Calculate the presentation time
561
+ CMTime presentationTime = CMTimeMake(_frameCount, 30); // 30 fps
562
+
563
+ // Append the pixel buffer to the asset writer
564
+ if (![_assetWriterPixelBufferAdaptor appendPixelBuffer:pixelBuffer withPresentationTime:presentationTime]) {
565
+ RCTLogError(@"[UnifiedPlayerViewManager] Failed to append pixel buffer: %@", _assetWriter.error);
566
+ }
567
+
568
+ // Release the pixel buffer
569
+ CVPixelBufferRelease(pixelBuffer);
570
+
571
+ // Increment the frame count
572
+ _frameCount++;
573
+ }
574
+
575
+ - (NSString *)stopRecording {
576
+ RCTLogInfo(@"[UnifiedPlayerViewManager] stopRecording called");
577
+
578
+ if (!_isRecording) {
579
+ RCTLogError(@"[UnifiedPlayerViewManager] No recording in progress");
580
+ return @"";
581
+ }
582
+
583
+ // Stop frame capture by stopping the display link
584
+ CADisplayLink *displayLink = objc_getAssociatedObject(self, "displayLinkKey");
585
+ if (displayLink) {
586
+ [displayLink invalidate];
587
+ objc_setAssociatedObject(self, "displayLinkKey", nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
588
+ }
589
+
590
+ // Finish writing
591
+ [_assetWriterVideoInput markAsFinished];
592
+ [_assetWriter finishWritingWithCompletionHandler:^{
593
+ if (self->_assetWriter.status == AVAssetWriterStatusCompleted) {
594
+ RCTLogInfo(@"[UnifiedPlayerViewManager] Recording completed successfully");
595
+ } else {
596
+ RCTLogError(@"[UnifiedPlayerViewManager] Recording failed: %@", self->_assetWriter.error);
597
+ }
598
+
599
+ // Clean up
600
+ self->_assetWriter = nil;
601
+ self->_assetWriterVideoInput = nil;
602
+ self->_assetWriterPixelBufferAdaptor = nil;
603
+ self->_isRecording = NO;
604
+ self->_frameCount = 0;
605
+ }];
606
+
607
+ NSString *path = _recordingPath;
608
+ _recordingPath = nil;
609
+
610
+ return path;
611
+ }
612
+
391
613
  - (void)setAutoplay:(BOOL)autoplay {
392
614
  _autoplay = autoplay;
393
615
  }
@@ -556,6 +778,18 @@
556
778
  // Remove all observers
557
779
  [[NSNotificationCenter defaultCenter] removeObserver:self];
558
780
 
781
+ // Stop recording if in progress
782
+ if (_isRecording) {
783
+ [self stopRecording];
784
+ }
785
+
786
+ // Clean up display link if it exists
787
+ CADisplayLink *displayLink = objc_getAssociatedObject(self, "displayLinkKey");
788
+ if (displayLink) {
789
+ [displayLink invalidate];
790
+ objc_setAssociatedObject(self, "displayLinkKey", nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
791
+ }
792
+
559
793
  // Stop playback and release player
560
794
  [_player stop];
561
795
  _player.delegate = nil;
@@ -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":[]}
@@ -75,5 +75,18 @@ export declare const UnifiedPlayer: {
75
75
  * @returns Promise resolving to the base64 encoded image string
76
76
  */
77
77
  capture: (viewTag: number) => Promise<string>;
78
+ /**
79
+ * Start recording the video
80
+ * @param viewTag - The tag of the player view
81
+ * @param outputPath - Optional path where to save the recording (platform-specific)
82
+ * @returns Promise resolving to true if recording started successfully
83
+ */
84
+ startRecording: (viewTag: number, outputPath?: string) => Promise<boolean>;
85
+ /**
86
+ * Stop recording the video
87
+ * @param viewTag - The tag of the player view
88
+ * @returns Promise resolving to the path of the saved recording
89
+ */
90
+ stopRecording: (viewTag: number) => Promise<string>;
78
91
  };
79
92
  //# 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,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.8",
4
4
  "description": "Unified Player",
5
5
  "source": "./src/index.tsx",
6
6
  "main": "./lib/module/index.js",
package/src/index.tsx CHANGED
@@ -263,4 +263,63 @@ export const UnifiedPlayer = {
263
263
  return Promise.reject(error);
264
264
  }
265
265
  },
266
+
267
+ /**
268
+ * Start recording the video
269
+ * @param viewTag - The tag of the player view
270
+ * @param outputPath - Optional path where to save the recording (platform-specific)
271
+ * @returns Promise resolving to true if recording started successfully
272
+ */
273
+ startRecording: (viewTag: number, outputPath?: string): Promise<boolean> => {
274
+ try {
275
+ console.log('UnifiedPlayer.startRecording called with viewTag:', viewTag);
276
+ return UnifiedPlayerModule.startRecording(viewTag, outputPath)
277
+ .then((result: boolean) => {
278
+ console.log('Native startRecording method called successfully');
279
+ return result;
280
+ })
281
+ .catch((error: any) => {
282
+ console.log(
283
+ 'Error calling startRecording:',
284
+ error instanceof Error ? error.message : String(error)
285
+ );
286
+ throw error;
287
+ });
288
+ } catch (error) {
289
+ console.log(
290
+ 'Error calling startRecording:',
291
+ error instanceof Error ? error.message : String(error)
292
+ );
293
+ return Promise.reject(error);
294
+ }
295
+ },
296
+
297
+ /**
298
+ * Stop recording the video
299
+ * @param viewTag - The tag of the player view
300
+ * @returns Promise resolving to the path of the saved recording
301
+ */
302
+ stopRecording: (viewTag: number): Promise<string> => {
303
+ try {
304
+ console.log('UnifiedPlayer.stopRecording called with viewTag:', viewTag);
305
+ return UnifiedPlayerModule.stopRecording(viewTag)
306
+ .then((filePath: string) => {
307
+ console.log('Native stopRecording method called successfully');
308
+ return filePath;
309
+ })
310
+ .catch((error: any) => {
311
+ console.log(
312
+ 'Error calling stopRecording:',
313
+ error instanceof Error ? error.message : String(error)
314
+ );
315
+ throw error;
316
+ });
317
+ } catch (error) {
318
+ console.log(
319
+ 'Error calling stopRecording:',
320
+ error instanceof Error ? error.message : String(error)
321
+ );
322
+ return Promise.reject(error);
323
+ }
324
+ },
266
325
  };