react-native-unified-player 0.2.0

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.
@@ -0,0 +1,670 @@
1
+ #import <React/RCTViewManager.h>
2
+ #import <React/RCTLog.h>
3
+ #import <React/RCTBridgeModule.h>
4
+ #import <React/RCTEventEmitter.h>
5
+ #import <React/RCTUIManager.h>
6
+ #import <React/RCTBridge.h>
7
+ #import <React/RCTUIManagerUtils.h>
8
+ #import <MobileVLCKit/MobileVLCKit.h>
9
+
10
+ // Forward declarations
11
+ @interface UnifiedPlayerUIView : UIView <VLCMediaPlayerDelegate>
12
+ @property (nonatomic, strong) VLCMediaPlayer *player;
13
+ @property (nonatomic, copy) NSString *videoUrlString;
14
+ @property (nonatomic, copy) NSString *authToken;
15
+ @property (nonatomic, assign) BOOL autoplay;
16
+ @property (nonatomic, assign) BOOL loop;
17
+ @property (nonatomic, strong) NSArray *mediaOptions;
18
+ @property (nonatomic, weak) RCTBridge *bridge;
19
+ @property (nonatomic, assign) VLCMediaPlayerState previousState;
20
+ @property (nonatomic, assign) BOOL hasRenderedVideo;
21
+
22
+ - (void)setupWithVideoUrlString:(NSString *)videoUrlString;
23
+ - (void)play;
24
+ - (void)pause;
25
+ - (void)seekToTime:(float)time;
26
+ - (float)getCurrentTime;
27
+ - (float)getDuration;
28
+ @end
29
+
30
+ // UnifiedPlayerModule - Module for handling control methods
31
+ @interface UnifiedPlayerModule : RCTEventEmitter <RCTBridgeModule>
32
+ @end
33
+
34
+ @implementation UnifiedPlayerModule
35
+
36
+ RCT_EXPORT_MODULE();
37
+
38
+ - (NSArray<NSString *> *)supportedEvents {
39
+ return @[
40
+ @"onReadyToPlay",
41
+ @"onError",
42
+ @"onProgress",
43
+ @"onPlaybackComplete",
44
+ @"onPlaybackStalled",
45
+ @"onPlaybackResumed",
46
+ @"onBuffering",
47
+ @"onPlaying",
48
+ @"onPaused",
49
+ @"onStopped"
50
+ ];
51
+ }
52
+
53
+ - (dispatch_queue_t)methodQueue {
54
+ return dispatch_get_main_queue();
55
+ }
56
+
57
+ // Play video
58
+ RCT_EXPORT_METHOD(play:(nonnull NSNumber *)reactTag) {
59
+ [self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary<NSNumber *,UIView *> *viewRegistry) {
60
+ UnifiedPlayerUIView *view = (UnifiedPlayerUIView *)viewRegistry[reactTag];
61
+ if (![view isKindOfClass:[UnifiedPlayerUIView class]]) {
62
+ RCTLogError(@"Invalid view for tag %@", reactTag);
63
+ return;
64
+ }
65
+ [view play];
66
+ }];
67
+ }
68
+
69
+ // Pause video
70
+ RCT_EXPORT_METHOD(pause:(nonnull NSNumber *)reactTag) {
71
+ [self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary<NSNumber *,UIView *> *viewRegistry) {
72
+ UnifiedPlayerUIView *view = (UnifiedPlayerUIView *)viewRegistry[reactTag];
73
+ if (![view isKindOfClass:[UnifiedPlayerUIView class]]) {
74
+ RCTLogError(@"Invalid view for tag %@", reactTag);
75
+ return;
76
+ }
77
+ [view pause];
78
+ }];
79
+ }
80
+
81
+ // Seek to specific time
82
+ RCT_EXPORT_METHOD(seekTo:(nonnull NSNumber *)reactTag time:(nonnull NSNumber *)time) {
83
+ [self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary<NSNumber *,UIView *> *viewRegistry) {
84
+ UnifiedPlayerUIView *view = (UnifiedPlayerUIView *)viewRegistry[reactTag];
85
+ if (![view isKindOfClass:[UnifiedPlayerUIView class]]) {
86
+ RCTLogError(@"Invalid view for tag %@", reactTag);
87
+ return;
88
+ }
89
+ [view seekToTime:[time floatValue]];
90
+ }];
91
+ }
92
+
93
+ // Get current playback time
94
+ RCT_EXPORT_METHOD(getCurrentTime:(nonnull NSNumber *)reactTag
95
+ resolver:(RCTPromiseResolveBlock)resolve
96
+ rejecter:(RCTPromiseRejectBlock)reject) {
97
+ [self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary<NSNumber *,UIView *> *viewRegistry) {
98
+ UnifiedPlayerUIView *view = (UnifiedPlayerUIView *)viewRegistry[reactTag];
99
+ if (![view isKindOfClass:[UnifiedPlayerUIView class]]) {
100
+ reject(@"error", @"Invalid view for tag", nil);
101
+ return;
102
+ }
103
+ resolve(@([view getCurrentTime]));
104
+ }];
105
+ }
106
+
107
+ // Get video duration
108
+ RCT_EXPORT_METHOD(getDuration:(nonnull NSNumber *)reactTag
109
+ resolver:(RCTPromiseResolveBlock)resolve
110
+ rejecter:(RCTPromiseRejectBlock)reject) {
111
+ [self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary<NSNumber *,UIView *> *viewRegistry) {
112
+ UnifiedPlayerUIView *view = (UnifiedPlayerUIView *)viewRegistry[reactTag];
113
+ if (![view isKindOfClass:[UnifiedPlayerUIView class]]) {
114
+ reject(@"error", @"Invalid view for tag", nil);
115
+ return;
116
+ }
117
+ resolve(@([view getDuration]));
118
+ }];
119
+ }
120
+
121
+ @end
122
+
123
+ // Global event emitter instance
124
+ static UnifiedPlayerModule *eventEmitter = nil;
125
+
126
+ // Main player view implementation
127
+ @implementation UnifiedPlayerUIView
128
+
129
+ - (instancetype)init {
130
+ if ((self = [super init])) {
131
+ RCTLogInfo(@"[UnifiedPlayerViewManager] Initializing player view");
132
+
133
+ // Initialize properties
134
+ _hasRenderedVideo = NO;
135
+
136
+ // Create the player
137
+ _player = [[VLCMediaPlayer alloc] init];
138
+ _player.delegate = self;
139
+
140
+ // Make sure we're visible and properly laid out
141
+ self.backgroundColor = [UIColor blackColor];
142
+ self.opaque = YES;
143
+ self.userInteractionEnabled = YES;
144
+
145
+ // Important: Enable content mode to scale properly
146
+ self.contentMode = UIViewContentModeScaleAspectFit;
147
+ self.clipsToBounds = YES;
148
+
149
+ // After the view is fully initialized, set it as the drawable
150
+ _player.drawable = self;
151
+
152
+ _autoplay = YES;
153
+ _loop = NO;
154
+
155
+ // Add notification for app entering background
156
+ [[NSNotificationCenter defaultCenter] addObserver:self
157
+ selector:@selector(appDidEnterBackground:)
158
+ name:UIApplicationDidEnterBackgroundNotification
159
+ object:nil];
160
+
161
+ // Add notification for app entering foreground
162
+ [[NSNotificationCenter defaultCenter] addObserver:self
163
+ selector:@selector(appDidBecomeActive:)
164
+ name:UIApplicationDidBecomeActiveNotification
165
+ object:nil];
166
+ }
167
+ return self;
168
+ }
169
+
170
+ // Override drawRect to ensure our rendering context is set up without recursion
171
+ - (void)drawRect:(CGRect)rect {
172
+ [super drawRect:rect];
173
+
174
+ // This can sometimes help with VLC rendering issues
175
+ // But check if we need to do this to avoid unnecessary work
176
+ if (_player && _player.drawable != self) {
177
+ _player.drawable = self;
178
+ // Do not call any methods that would trigger layoutSubviews or drawRect again
179
+ }
180
+ }
181
+
182
+ // Override layoutSubviews with safer implementation to avoid recursion
183
+ - (void)layoutSubviews {
184
+ [super layoutSubviews];
185
+
186
+ // Check bounds without using NSStringFromCGRect (which can cause recursion)
187
+ CGRect bounds = self.bounds;
188
+
189
+ // Only update if we have valid bounds and a player
190
+ if (bounds.size.width > 0 && bounds.size.height > 0 && _player) {
191
+ // Ensure drawable is set but don't call any methods that could cause recursion
192
+ if (_player.drawable != self) {
193
+ _player.drawable = self;
194
+ }
195
+
196
+ // Let VLC know the size has changed but don't force any redraws here
197
+ // This may be VLC-specific and not required for all implementations
198
+ }
199
+ }
200
+
201
+ - (void)didMoveToSuperview {
202
+ [super didMoveToSuperview];
203
+ RCTLogInfo(@"[UnifiedPlayerViewManager] View moved to superview: %@", self.superview);
204
+
205
+ // Sometimes VLC needs a reset of the drawable when moving to a superview
206
+ if (_player) {
207
+ _player.drawable = nil;
208
+ _player.drawable = self;
209
+ }
210
+ }
211
+
212
+ - (void)didMoveToWindow {
213
+ [super didMoveToWindow];
214
+ RCTLogInfo(@"[UnifiedPlayerViewManager] View moved to window: %@", self.window);
215
+
216
+ // Ensure proper rendering when window changes
217
+ if (_player) {
218
+ _player.drawable = nil;
219
+ _player.drawable = self;
220
+ }
221
+ }
222
+
223
+ - (void)appDidEnterBackground:(NSNotification *)notification {
224
+ if (_player.isPlaying) {
225
+ [_player pause];
226
+ }
227
+ }
228
+
229
+ - (void)appDidBecomeActive:(NSNotification *)notification {
230
+ // Optionally resume playback when app becomes active again
231
+ // if (wasPlayingBeforeBackground) {
232
+ // [_player play];
233
+ // }
234
+ }
235
+
236
+ - (void)sendProgressEvent:(float)currentTime duration:(float)duration {
237
+ NSDictionary *event = @{
238
+ @"currentTime": @(currentTime),
239
+ @"duration": @(duration)
240
+ };
241
+
242
+ if (eventEmitter != nil) {
243
+ [eventEmitter sendEventWithName:@"onProgress" body:event];
244
+ }
245
+ }
246
+
247
+ - (void)sendEvent:(NSString *)eventName body:(NSDictionary *)body {
248
+ if (eventEmitter != nil) {
249
+ [eventEmitter sendEventWithName:eventName body:body];
250
+ }
251
+ }
252
+
253
+ - (void)setupWithVideoUrlString:(NSString *)videoUrlString {
254
+ RCTLogInfo(@"[UnifiedPlayerViewManager] setupWithVideoUrlString: %@", videoUrlString);
255
+ _videoUrlString = [videoUrlString copy];
256
+
257
+ if (videoUrlString) {
258
+ [self loadVideo];
259
+ } else {
260
+ [_player stop];
261
+ _player.media = nil;
262
+ }
263
+ }
264
+
265
+ - (void)loadVideo {
266
+ // Log view state first
267
+ RCTLogInfo(@"[UnifiedPlayerViewManager] View state before loading - Frame: %@, Bounds: %@, Visible: %@, Superview: %@",
268
+ NSStringFromCGRect(self.frame),
269
+ NSStringFromCGRect(self.bounds),
270
+ self.window ? @"YES" : @"NO",
271
+ self.superview ? @"YES" : @"NO");
272
+
273
+ // Reset the rendered flag
274
+ _hasRenderedVideo = NO;
275
+
276
+ // Make sure we're in the main thread
277
+ dispatch_async(dispatch_get_main_queue(), ^{
278
+ // Check if URL is valid
279
+ NSURL *videoURL = [NSURL URLWithString:self->_videoUrlString];
280
+ if (!videoURL) {
281
+ // Try with encoding if the original URL doesn't work
282
+ NSString *escapedString = [self->_videoUrlString stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
283
+ videoURL = [NSURL URLWithString:escapedString];
284
+
285
+ if (!videoURL) {
286
+ NSDictionary *errorInfo = @{
287
+ @"code": @"INVALID_URL",
288
+ @"message": @"Invalid URL format",
289
+ @"details": self->_videoUrlString ?: @""
290
+ };
291
+ [self sendEvent:@"onError" body:errorInfo];
292
+ return;
293
+ }
294
+
295
+ RCTLogInfo(@"[UnifiedPlayerViewManager] URL needed encoding: %@", videoURL.absoluteString);
296
+ }
297
+
298
+ RCTLogInfo(@"[UnifiedPlayerViewManager] Using URL: %@", videoURL.absoluteString);
299
+
300
+ // Create VLC media options array
301
+ NSMutableArray *mediaOptions = [NSMutableArray array];
302
+
303
+ // Add auth token if available
304
+ if (self->_authToken.length > 0) {
305
+ [mediaOptions addObject:[NSString stringWithFormat:@"http-header-fields=Authorization: Bearer %@", self->_authToken]];
306
+ }
307
+
308
+ // Add default network caching options for streaming
309
+ BOOL isStreaming = [videoURL.scheme hasPrefix:@"http"] ||
310
+ [videoURL.scheme hasPrefix:@"rtsp"] ||
311
+ [videoURL.scheme hasPrefix:@"rtmp"] ||
312
+ [videoURL.scheme hasPrefix:@"mms"];
313
+
314
+ if (isStreaming) {
315
+ if (![self->_mediaOptions containsObject:@"network-caching"]) {
316
+ [mediaOptions addObject:@"--network-caching=1500"];
317
+ [mediaOptions addObject:@"--live-caching=1500"];
318
+ [mediaOptions addObject:@"--file-caching=1500"];
319
+ [mediaOptions addObject:@"--disc-caching=300"];
320
+ [mediaOptions addObject:@"--clock-jitter=0"];
321
+ [mediaOptions addObject:@"--clock-synchro=0"];
322
+ }
323
+
324
+ // Improve TCP/UDP performance for streams
325
+ [mediaOptions addObject:@"--rtsp-tcp"];
326
+ [mediaOptions addObject:@"--ipv4-timeout=1000"];
327
+ [mediaOptions addObject:@"--http-reconnect"];
328
+ }
329
+
330
+ // Add custom media options if provided
331
+ if (self->_mediaOptions && self->_mediaOptions.count > 0) {
332
+ for (NSString *option in self->_mediaOptions) {
333
+ if ([option isKindOfClass:[NSString class]]) {
334
+ // Make sure option starts with --
335
+ if ([option hasPrefix:@"--"]) {
336
+ [mediaOptions addObject:option];
337
+ } else {
338
+ [mediaOptions addObject:[NSString stringWithFormat:@"--%@", option]];
339
+ }
340
+ }
341
+ }
342
+ }
343
+
344
+ // Stop any existing playback first
345
+ [self->_player stop];
346
+
347
+ // Create VLC media with options
348
+ VLCMedia *media = [VLCMedia mediaWithURL:videoURL];
349
+
350
+ // Apply media options
351
+ for (NSString *option in mediaOptions) {
352
+ [media addOption:option];
353
+ }
354
+
355
+ // Set new media to player
356
+ self->_player.media = media;
357
+
358
+ // Ensure drawable is set correctly
359
+ self->_player.drawable = self;
360
+
361
+ // Configure additional player settings
362
+ self->_player.videoAspectRatio = NULL; // Use default aspect ratio - must be NULL not nil for VLCKit
363
+
364
+ RCTLogInfo(@"[UnifiedPlayerViewManager] Media configured with options: %@", mediaOptions);
365
+ RCTLogInfo(@"[UnifiedPlayerViewManager] Player drawable: %@, bounds: %@",
366
+ self->_player.drawable, NSStringFromCGRect(self.bounds));
367
+
368
+ // Instead of force redraw, use these methods directly:
369
+ [self setNeedsLayout];
370
+ [self layoutIfNeeded];
371
+ [self setNeedsDisplay];
372
+
373
+ // Start playback if autoplay is enabled
374
+ if (self->_autoplay) {
375
+ [self->_player play];
376
+
377
+ // Don't call forceRedraw again, just update the view
378
+ [self setNeedsDisplay];
379
+ }
380
+ });
381
+ }
382
+
383
+ - (void)play {
384
+ if (_player.media) {
385
+ // Make sure drawable is properly set before playing
386
+ if (_player.drawable != self) {
387
+ _player.drawable = self;
388
+ }
389
+
390
+ // Ensure video track is enabled if available
391
+ if (_player.numberOfVideoTracks > 0 && _player.currentVideoTrackIndex == -1) {
392
+ // Attempt to enable the first video track
393
+ if (_player.videoTrackIndexes.count > 0) {
394
+ NSNumber *videoTrackIndex = [_player.videoTrackIndexes firstObject];
395
+ _player.currentVideoTrackIndex = [videoTrackIndex intValue];
396
+ RCTLogInfo(@"[UnifiedPlayerViewManager] Enabling video track index: %@", videoTrackIndex);
397
+ }
398
+ }
399
+
400
+ // Apply aspect ratio settings
401
+ _player.videoAspectRatio = NULL; // Use default aspect ratio
402
+
403
+ // Set scale mode for the VLC video output
404
+ // Note: VLC has its own scaling which is separate from UIView contentMode
405
+ _player.scaleFactor = 0.0; // Auto scale
406
+
407
+ [_player play];
408
+
409
+ // Use these methods instead of forceRedraw
410
+ [self setNeedsLayout];
411
+ [self layoutIfNeeded];
412
+ [self setNeedsDisplay];
413
+
414
+ RCTLogInfo(@"[UnifiedPlayerViewManager] play called, drawable: %@, frame: %@",
415
+ _player.drawable, NSStringFromCGRect(self.frame));
416
+ }
417
+ }
418
+
419
+ - (void)pause {
420
+ [_player pause];
421
+ RCTLogInfo(@"[UnifiedPlayerViewManager] pause called");
422
+ }
423
+
424
+ - (void)seekToTime:(float)time {
425
+ // VLC uses a 0-1 position value for seeking
426
+ float position = time / [self getDuration];
427
+ position = MAX(0, MIN(1, position)); // Ensure position is between 0 and 1
428
+
429
+ [_player setPosition:position];
430
+ RCTLogInfo(@"[UnifiedPlayerViewManager] Seek to %f (position: %f)", time, position);
431
+ }
432
+
433
+ - (float)getCurrentTime {
434
+ return _player.time.intValue / 1000.0f; // Convert from milliseconds to seconds
435
+ }
436
+
437
+ - (float)getDuration {
438
+ return _player.media.length.intValue / 1000.0f; // Convert from milliseconds to seconds
439
+ }
440
+
441
+ - (void)setAutoplay:(BOOL)autoplay {
442
+ _autoplay = autoplay;
443
+ }
444
+
445
+ - (void)setLoop:(BOOL)loop {
446
+ _loop = loop;
447
+ }
448
+
449
+ - (void)setAuthToken:(NSString *)authToken {
450
+ if (_authToken != authToken) {
451
+ _authToken = [authToken copy];
452
+ // If we already have a URL, reload with the new auth token
453
+ if (_videoUrlString) {
454
+ [self loadVideo];
455
+ }
456
+ }
457
+ }
458
+
459
+ #pragma mark - VLCMediaPlayerDelegate
460
+
461
+ - (void)mediaPlayerTimeChanged:(NSNotification *)notification {
462
+ float currentTime = [self getCurrentTime];
463
+ float duration = [self getDuration];
464
+
465
+ // Avoid sending progress events for invalid durations
466
+ if (duration > 0 && !isnan(duration)) {
467
+ [self sendProgressEvent:currentTime duration:duration];
468
+ }
469
+ }
470
+
471
+ - (void)mediaPlayerStateChanged:(NSNotification *)notification {
472
+ VLCMediaPlayerState state = _player.state;
473
+
474
+ // Debug information for video output
475
+ if (state == VLCMediaPlayerStatePlaying) {
476
+ RCTLogInfo(@"[UnifiedPlayerViewManager] Video size: %@",
477
+ NSStringFromCGSize(_player.videoSize));
478
+ RCTLogInfo(@"[UnifiedPlayerViewManager] Has video out: %@",
479
+ _player.hasVideoOut ? @"YES" : @"NO");
480
+
481
+ // Check video tracks
482
+ NSInteger videoTrackCount = _player.numberOfVideoTracks;
483
+ RCTLogInfo(@"[UnifiedPlayerViewManager] Video track count: %ld", (long)videoTrackCount);
484
+
485
+ if (videoTrackCount > 0) {
486
+ RCTLogInfo(@"[UnifiedPlayerViewManager] Current video track index: %d", _player.currentVideoTrackIndex);
487
+ RCTLogInfo(@"[UnifiedPlayerViewManager] Video track names: %@", _player.videoTrackNames);
488
+
489
+ // Trigger a delayed drawable update to help with rendering
490
+ if (!_hasRenderedVideo && _player.videoSize.width > 0) {
491
+ _hasRenderedVideo = YES;
492
+ [self updatePlayerDrawableWithDelay];
493
+ }
494
+ } else {
495
+ RCTLogInfo(@"[UnifiedPlayerViewManager] No video tracks found!");
496
+ }
497
+ }
498
+
499
+ // Check for buffer state transitions
500
+ if ((state == VLCMediaPlayerStateBuffering && _previousState != VLCMediaPlayerStateBuffering) ||
501
+ (state == VLCMediaPlayerStatePlaying && _previousState == VLCMediaPlayerStateBuffering)) {
502
+ [self handleBufferingStatusChange];
503
+ }
504
+
505
+ // Save the current state for next time
506
+ _previousState = state;
507
+
508
+ switch (state) {
509
+ case VLCMediaPlayerStateOpening:
510
+ RCTLogInfo(@"[UnifiedPlayerViewManager] VLCMediaPlayerStateOpening");
511
+ break;
512
+
513
+ case VLCMediaPlayerStateBuffering:
514
+ RCTLogInfo(@"[UnifiedPlayerViewManager] VLCMediaPlayerStateBuffering");
515
+ [self sendEvent:@"onBuffering" body:@{
516
+ @"isPlaying": @(_player.isPlaying),
517
+ @"duration": @([self getDuration])
518
+ }];
519
+ break;
520
+
521
+ case VLCMediaPlayerStatePlaying:
522
+ RCTLogInfo(@"[UnifiedPlayerViewManager] VLCMediaPlayerStatePlaying");
523
+ [self sendEvent:@"onPlaying" body:@{
524
+ @"target": @(1),
525
+ @"duration": @([self getDuration] * 1000), // Convert to ms for consistency
526
+ @"seekable": @(YES)
527
+ }];
528
+ [self sendEvent:@"onReadyToPlay" body:@{}];
529
+ break;
530
+
531
+ case VLCMediaPlayerStatePaused:
532
+ RCTLogInfo(@"[UnifiedPlayerViewManager] VLCMediaPlayerStatePaused");
533
+ [self sendEvent:@"onPaused" body:@{}];
534
+ break;
535
+
536
+ case VLCMediaPlayerStateStopped:
537
+ RCTLogInfo(@"[UnifiedPlayerViewManager] VLCMediaPlayerStateStopped");
538
+ [self sendEvent:@"onStopped" body:@{}];
539
+ break;
540
+
541
+ case VLCMediaPlayerStateEnded:
542
+ RCTLogInfo(@"[UnifiedPlayerViewManager] VLCMediaPlayerStateEnded");
543
+ [self sendEvent:@"onPlaybackComplete" body:@{}];
544
+
545
+ // Handle looping
546
+ if (_loop) {
547
+ [_player stop];
548
+ [_player play];
549
+ }
550
+ break;
551
+
552
+ case VLCMediaPlayerStateError:
553
+ RCTLogInfo(@"[UnifiedPlayerViewManager] VLCMediaPlayerStateError");
554
+ [self sendEvent:@"onError" body:@{
555
+ @"code": @"PLAYBACK_ERROR",
556
+ @"message": @"VLC player encountered an error",
557
+ @"details": @{@"url": _videoUrlString ?: @""}
558
+ }];
559
+ break;
560
+
561
+ default:
562
+ break;
563
+ }
564
+ }
565
+
566
+ - (void)mediaPlayerSnapshot:(NSNotification *)notification {
567
+ // Handle snapshot completion if needed
568
+ }
569
+
570
+ - (void)mediaPlayerTitleChanged:(NSNotification *)notification {
571
+ // Handle title changes if needed
572
+ }
573
+
574
+ // Define a method that maps VLC buffer state changes to appropriate RN events
575
+ - (void)handleBufferingStatusChange {
576
+ if (_player.state == VLCMediaPlayerStateBuffering) {
577
+ // When buffering, send stalled event
578
+ [self sendEvent:@"onPlaybackStalled" body:@{}];
579
+ } else if (_player.state == VLCMediaPlayerStatePlaying && _player.isPlaying) {
580
+ // When buffer is full and playing again, send resumed event
581
+ [self sendEvent:@"onPlaybackResumed" body:@{}];
582
+ }
583
+ }
584
+
585
+ - (void)dealloc {
586
+ RCTLogInfo(@"[UnifiedPlayerViewManager] Deallocating player view");
587
+
588
+ // Remove all observers
589
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
590
+
591
+ // Stop playback and release player
592
+ [_player stop];
593
+ _player.delegate = nil;
594
+ _player = nil;
595
+ }
596
+
597
+ // Update updatePlayerDrawableWithDelay to use safer redraw approach
598
+ - (void)updatePlayerDrawableWithDelay {
599
+ // Sometimes a slight delay can help with rendering issues
600
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
601
+ if (self->_player) {
602
+ // Reset drawable
603
+ self->_player.drawable = nil;
604
+ self->_player.drawable = self;
605
+
606
+ // Call UIKit methods directly instead of forceRedraw
607
+ [self setNeedsLayout];
608
+ [self layoutIfNeeded];
609
+ [self setNeedsDisplay];
610
+
611
+ RCTLogInfo(@"[UnifiedPlayerViewManager] Drawable updated with delay");
612
+ }
613
+ });
614
+ }
615
+
616
+ @end
617
+
618
+ @interface UnifiedPlayerViewManager : RCTViewManager
619
+ @end
620
+
621
+ @implementation UnifiedPlayerViewManager
622
+
623
+ RCT_EXPORT_MODULE(UnifiedPlayerView)
624
+
625
+ @synthesize bridge = _bridge;
626
+
627
+ - (UIView *)view
628
+ {
629
+ UnifiedPlayerUIView *playerView = [[UnifiedPlayerUIView alloc] init];
630
+ playerView.bridge = self.bridge;
631
+
632
+ // Store the event emitter for sending events
633
+ if (eventEmitter == nil) {
634
+ eventEmitter = [self.bridge moduleForClass:[UnifiedPlayerModule class]];
635
+ }
636
+
637
+ return playerView;
638
+ }
639
+
640
+ // Video URL property
641
+ RCT_CUSTOM_VIEW_PROPERTY(videoUrl, NSString, UnifiedPlayerUIView)
642
+ {
643
+ [view setupWithVideoUrlString:json];
644
+ }
645
+
646
+ // Autoplay property
647
+ RCT_CUSTOM_VIEW_PROPERTY(autoplay, BOOL, UnifiedPlayerUIView)
648
+ {
649
+ view.autoplay = [RCTConvert BOOL:json];
650
+ }
651
+
652
+ // Loop property
653
+ RCT_CUSTOM_VIEW_PROPERTY(loop, BOOL, UnifiedPlayerUIView)
654
+ {
655
+ view.loop = [RCTConvert BOOL:json];
656
+ }
657
+
658
+ // Auth token property
659
+ RCT_CUSTOM_VIEW_PROPERTY(authToken, NSString, UnifiedPlayerUIView)
660
+ {
661
+ view.authToken = [RCTConvert NSString:json];
662
+ }
663
+
664
+ // Media options property
665
+ RCT_CUSTOM_VIEW_PROPERTY(mediaOptions, NSArray, UnifiedPlayerUIView)
666
+ {
667
+ view.mediaOptions = [RCTConvert NSArray:json];
668
+ }
669
+
670
+ @end