react-native-unified-player 0.6.0 → 0.6.3

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.
@@ -57,6 +57,7 @@ class UnifiedPlayerView(context: Context) : FrameLayout(context) {
57
57
  private var thumbnailUrl: String? = null
58
58
  private var autoplay: Boolean = true
59
59
  private var loop: Boolean = false
60
+ private var speed: Float = 1.0f // Default playback speed
60
61
  private var textureView: android.view.TextureView
61
62
  private var thumbnailImageView: ImageView? = null
62
63
  internal var player: ExoPlayer? = null
@@ -157,6 +158,11 @@ class UnifiedPlayerView(context: Context) : FrameLayout(context) {
157
158
  sendEvent(EVENT_COMPLETE, Arguments.createMap())
158
159
  } else {
159
160
  Log.d(TAG, "Looping enabled, not sending completion event")
161
+ // Ensure playback speed is preserved after loop
162
+ if (speed != 1.0f && player != null) {
163
+ player?.setPlaybackSpeed(speed)
164
+ Log.d(TAG, "Restored playback speed: $speed after loop")
165
+ }
160
166
  }
161
167
  }
162
168
  Player.STATE_BUFFERING -> {
@@ -175,6 +181,11 @@ class UnifiedPlayerView(context: Context) : FrameLayout(context) {
175
181
  Log.d(TAG, "ExoPlayer is now playing")
176
182
  // Hide thumbnail when video starts playing
177
183
  thumbnailImageView?.visibility = View.GONE
184
+ // Ensure playback speed is preserved when playback resumes (e.g., after loop)
185
+ if (speed != 1.0f) {
186
+ player?.setPlaybackSpeed(speed)
187
+ Log.d(TAG, "Restored playback speed: $speed when playback resumed")
188
+ }
178
189
  sendEvent(EVENT_RESUMED, Arguments.createMap())
179
190
  sendEvent(EVENT_PLAYING, Arguments.createMap())
180
191
  } else {
@@ -248,8 +259,12 @@ class UnifiedPlayerView(context: Context) : FrameLayout(context) {
248
259
  player?.prepare()
249
260
  player?.playWhenReady = autoplay && !isPaused
250
261
  player?.repeatMode = if (loop) Player.REPEAT_MODE_ONE else Player.REPEAT_MODE_OFF
262
+ // Apply playback speed if set
263
+ if (speed != 1.0f) {
264
+ player?.setPlaybackSpeed(speed)
265
+ }
251
266
 
252
- Log.d(TAG, "ExoPlayer configured with URL: $url, autoplay: $autoplay, loop: $loop, isPaused: $isPaused")
267
+ Log.d(TAG, "ExoPlayer configured with URL: $url, autoplay: $autoplay, loop: $loop, isPaused: $isPaused, speed: $speed")
253
268
  sendEvent(EVENT_LOAD_START, Arguments.createMap())
254
269
 
255
270
  } catch (e: Exception) {
@@ -436,6 +451,10 @@ class UnifiedPlayerView(context: Context) : FrameLayout(context) {
436
451
  fun play() {
437
452
  Log.d(TAG, "Play method called")
438
453
  player?.playWhenReady = true
454
+ // Ensure playback speed is applied when playing
455
+ if (speed != 1.0f) {
456
+ player?.setPlaybackSpeed(speed)
457
+ }
439
458
  }
440
459
 
441
460
  fun pause() {
@@ -455,8 +474,9 @@ class UnifiedPlayerView(context: Context) : FrameLayout(context) {
455
474
  } ?: Log.e(TAG, "Cannot seek: player is null")
456
475
  }
457
476
 
458
- fun setSpeed(speed: Float) {
459
- Log.d(TAG, "SetSpeed method called with speed: $speed")
477
+ fun setSpeed(speedValue: Float) {
478
+ Log.d(TAG, "SetSpeed method called with speed: $speedValue")
479
+ speed = speedValue // Store the speed value
460
480
  player?.let {
461
481
  it.setPlaybackSpeed(speed)
462
482
  Log.d(TAG, "Playback speed set to: $speed")
@@ -1,6 +1,6 @@
1
1
  #import <React/RCTEventEmitter.h>
2
2
  #import <React/RCTBridgeModule.h>
3
- #import <MobileVLCKit/MobileVLCKit.h> // Import MobileVLCKit
3
+ #import <AVFoundation/AVFoundation.h> // Import MobileVLCKit
4
4
 
5
5
  // Forward declaration for UnifiedPlayerUIView
6
6
  @class UnifiedPlayerUIView;
@@ -62,13 +62,13 @@
62
62
  _autoplay = YES;
63
63
  _loop = NO;
64
64
  _isFullscreen = NO;
65
-
65
+
66
66
  // Add notification observers
67
67
  [[NSNotificationCenter defaultCenter] addObserver:self
68
68
  selector:@selector(appDidEnterBackground:)
69
69
  name:UIApplicationDidEnterBackgroundNotification
70
70
  object:nil];
71
-
71
+
72
72
  [[NSNotificationCenter defaultCenter] addObserver:self
73
73
  selector:@selector(appDidBecomeActive:)
74
74
  name:UIApplicationDidBecomeActiveNotification
@@ -121,8 +121,8 @@
121
121
  - (void)sendProgressEvent:(float)currentTime duration:(float)duration {
122
122
  if (self.onProgress) {
123
123
  self.onProgress(@{
124
- @"currentTime": @(currentTime),
125
- @"duration": @(duration)
124
+ @"currentTime": @(currentTime),
125
+ @"duration": @(duration)
126
126
  });
127
127
  }
128
128
  }
@@ -394,15 +394,20 @@
394
394
 
395
395
  - (void)play {
396
396
  if (_playerItem && _playerItem.status == AVPlayerItemStatusReadyToPlay) {
397
- // Apply speed if set
398
- if (_speed > 0 && _speed != 1.0f) {
399
- float validSpeed = MAX(0.25f, MIN(4.0f, _speed));
400
- _player.rate = validSpeed;
401
- } else {
402
- _player.rate = 1.0f;
403
- }
404
-
405
397
  [_player play];
398
+
399
+ // Apply speed AFTER play() is called to ensure it's not reset
400
+ // Use a small delay to ensure play() has started
401
+ dispatch_async(dispatch_get_main_queue(), ^{
402
+ if (self->_speed > 0 && self->_speed != 1.0f) {
403
+ float validSpeed = MAX(0.25f, MIN(4.0f, self->_speed));
404
+ self->_player.rate = validSpeed;
405
+ RCTLogInfo(@"[UnifiedPlayerViewManager] Applied speed: %f after play", validSpeed);
406
+ } else {
407
+ self->_player.rate = 1.0f;
408
+ }
409
+ });
410
+
406
411
  [self sendEvent:@"onPlaying" body:@{}];
407
412
  RCTLogInfo(@"[UnifiedPlayerViewManager] play called");
408
413
  } else {
@@ -490,13 +495,33 @@
490
495
  #pragma mark - Notifications
491
496
 
492
497
  - (void)playerItemDidReachEnd:(NSNotification *)notification {
493
- RCTLogInfo(@"[UnifiedPlayerViewManager] Video ended, loop: %@", _loop ? @"YES" : @"NO");
494
-
498
+ RCTLogInfo(@"[UnifiedPlayerViewManager] Video ended, loop: %@, speed: %f", _loop ? @"YES" : @"NO", _speed);
499
+
495
500
  if (_loop) {
501
+ // Store current speed before seeking
502
+ float savedSpeed = _speed;
503
+
496
504
  // Seek to beginning and play again
497
- [_player seekToTime:kCMTimeZero completionHandler:^(BOOL finished) {
498
- if (finished) {
499
- [self->_player play];
505
+ [_player seekToTime:kCMTimeZero completionHandler:^(BOOL finished) {
506
+ if (finished) {
507
+ [self->_player play];
508
+ // Restore playback speed AFTER play() is called
509
+ // Use multiple attempts to ensure speed is set
510
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.15 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
511
+ if (savedSpeed > 0 && savedSpeed != 1.0f) {
512
+ float validSpeed = MAX(0.25f, MIN(4.0f, savedSpeed));
513
+ self->_player.rate = validSpeed;
514
+ RCTLogInfo(@"[UnifiedPlayerViewManager] Restored speed: %f for loop (attempt 1)", validSpeed);
515
+
516
+ // Double-check after a bit more time
517
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
518
+ if (self->_player.rate != validSpeed && self->_player.rate > 0) {
519
+ self->_player.rate = validSpeed;
520
+ RCTLogInfo(@"[UnifiedPlayerViewManager] Restored speed: %f for loop (attempt 2)", validSpeed);
521
+ }
522
+ });
523
+ }
524
+ });
500
525
  RCTLogInfo(@"[UnifiedPlayerViewManager] Looped video - restarted from beginning");
501
526
  }
502
527
  }];
@@ -530,10 +555,75 @@
530
555
  #pragma mark - Recording (Placeholder - can be implemented later)
531
556
 
532
557
  - (void)captureFrameWithCompletion:(void (^)(NSString * _Nullable base64String, NSError * _Nullable error))completion {
533
- // Placeholder implementation
534
- if (completion) {
535
- completion(nil, [NSError errorWithDomain:@"UnifiedPlayer" code:1 userInfo:@{NSLocalizedDescriptionKey: @"Not implemented"}]);
558
+ if (!completion) {
559
+ return;
536
560
  }
561
+
562
+ if (!_playerItem || _playerItem.status != AVPlayerItemStatusReadyToPlay) {
563
+ completion(nil, [NSError errorWithDomain:@"UnifiedPlayer"
564
+ code:1
565
+ userInfo:@{NSLocalizedDescriptionKey: @"Player item not ready"}]);
566
+ return;
567
+ }
568
+
569
+ if (!_playerLayer) {
570
+ completion(nil, [NSError errorWithDomain:@"UnifiedPlayer"
571
+ code:2
572
+ userInfo:@{NSLocalizedDescriptionKey: @"Player layer not available"}]);
573
+ return;
574
+ }
575
+
576
+ // Use AVAssetImageGenerator to capture the current frame
577
+ AVAssetImageGenerator *imageGenerator = [[AVAssetImageGenerator alloc] initWithAsset:_playerItem.asset];
578
+ imageGenerator.appliesPreferredTrackTransform = YES;
579
+ imageGenerator.requestedTimeToleranceBefore = kCMTimeZero;
580
+ imageGenerator.requestedTimeToleranceAfter = kCMTimeZero;
581
+
582
+ CMTime currentTime = _player.currentTime;
583
+ if (!CMTIME_IS_VALID(currentTime)) {
584
+ currentTime = kCMTimeZero;
585
+ }
586
+
587
+ // Generate the image
588
+ [imageGenerator generateCGImagesAsynchronouslyForTimes:@[[NSValue valueWithCMTime:currentTime]]
589
+ completionHandler:^(CMTime requestedTime, CGImageRef image, CMTime actualTime, AVAssetImageGeneratorResult result, NSError *error) {
590
+ if (error) {
591
+ dispatch_async(dispatch_get_main_queue(), ^{
592
+ completion(nil, error);
593
+ });
594
+ return;
595
+ }
596
+
597
+ if (result != AVAssetImageGeneratorSucceeded || !image) {
598
+ dispatch_async(dispatch_get_main_queue(), ^{
599
+ completion(nil, [NSError errorWithDomain:@"UnifiedPlayer"
600
+ code:3
601
+ userInfo:@{NSLocalizedDescriptionKey: @"Failed to generate image"}]);
602
+ });
603
+ return;
604
+ }
605
+
606
+ // Convert CGImage to UIImage
607
+ UIImage *uiImage = [UIImage imageWithCGImage:image];
608
+
609
+ // Convert to JPEG data
610
+ NSData *imageData = UIImageJPEGRepresentation(uiImage, 0.8);
611
+ if (!imageData) {
612
+ dispatch_async(dispatch_get_main_queue(), ^{
613
+ completion(nil, [NSError errorWithDomain:@"UnifiedPlayer"
614
+ code:4
615
+ userInfo:@{NSLocalizedDescriptionKey: @"Failed to convert image to JPEG"}]);
616
+ });
617
+ return;
618
+ }
619
+
620
+ // Encode to base64
621
+ NSString *base64String = [imageData base64EncodedStringWithOptions:0];
622
+
623
+ dispatch_async(dispatch_get_main_queue(), ^{
624
+ completion(base64String, nil);
625
+ });
626
+ }];
537
627
  }
538
628
 
539
629
  - (void)captureFrameForRecording {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-unified-player",
3
- "version": "0.6.0",
3
+ "version": "0.6.3",
4
4
  "description": "Unified Player",
5
5
  "source": "./src/index.tsx",
6
6
  "main": "./lib/module/index.js",
@@ -107,7 +107,11 @@
107
107
  "git": {
108
108
  "commitMessage": "chore: release ${version}",
109
109
  "tagName": "v${version}",
110
- "requireCleanWorkingDir": false
110
+ "requireCleanWorkingDir": false,
111
+ "push": true,
112
+ "pushArgs": [
113
+ "--force-with-lease"
114
+ ]
111
115
  },
112
116
  "npm": {
113
117
  "publish": true,