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(
|
|
459
|
-
Log.d(TAG, "SetSpeed method called with 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 <
|
|
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
|
-
|
|
125
|
-
|
|
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:
|
|
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
|
-
|
|
498
|
-
|
|
499
|
-
|
|
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
|
-
|
|
534
|
-
|
|
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.
|
|
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,
|