@volcengine/react-native-live-pull 1.5.2-rc.1 → 1.5.2-rc.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.
@@ -32,8 +32,17 @@
32
32
  }
33
33
 
34
34
  - (NSDictionary *)callApiSync:(NSDictionary *)args {
35
- [self newApiEngine];
36
- return [self.volcApiEngine callApi:args];
35
+ __block NSDictionary *result = nil;
36
+ void (^work)(void) = ^{
37
+ [self newApiEngine];
38
+ result = [self.volcApiEngine callApi:args];
39
+ };
40
+ if ([NSThread isMainThread]) {
41
+ work();
42
+ } else {
43
+ dispatch_sync(dispatch_get_main_queue(), work);
44
+ }
45
+ return result;
37
46
  }
38
47
 
39
48
  //callback 是OC的回调函数,外层需要进一步封装,接收一个NSDictionary类型的参数
@@ -26,9 +26,23 @@
26
26
  *pipObservers;
27
27
  // Add timer to refresh playback state
28
28
  @property(nonatomic, strong) NSTimer *playbackStateTimer;
29
+ @property(nonatomic, assign) NSTimeInterval lastStateSwitchTime;
30
+ @property(nonatomic, assign) UIApplicationState lastAppState;
31
+ @property(nonatomic, assign) NSTimeInterval lastRebindTime;
32
+ @property(nonatomic, copy) NSString *pipTraceId;
33
+ @property(nonatomic, assign) NSTimeInterval lastDropLogTime;
34
+ @property(nonatomic, assign) NSUInteger enqueueCount;
35
+ @property(nonatomic, assign) NSUInteger dropInactiveCount;
36
+ @property(nonatomic, assign) NSUInteger dropCooldownCount;
37
+ @property(nonatomic, assign) NSUInteger dropNotReadyCount;
38
+ @property(nonatomic, assign) NSUInteger dropNoControllerCount;
29
39
  @end
30
40
 
31
41
  NSString *const kObserverKey = @"pip-observer";
42
+ static const NSTimeInterval kStateSwitchCooldownSeconds = 0.3;
43
+ static const NSTimeInterval kRebindCooldownSeconds = 1.0;
44
+ static const NSTimeInterval kDropLogIntervalSeconds = 1.0;
45
+ static const NSTimeInterval kFrameStatsLogIntervalSeconds = 5.0;
32
46
 
33
47
  @implementation VeLivePictureInPictureManager
34
48
 
@@ -52,6 +66,16 @@ NSString *const kObserverKey = @"pip-observer";
52
66
  if (@available(iOS 14.2, *)) {
53
67
  _canStartPictureInPictureAutomaticallyFromInline = NO;
54
68
  }
69
+ _lastStateSwitchTime = 0;
70
+ _lastAppState = UIApplicationStateActive;
71
+ _lastRebindTime = 0;
72
+ _lastDropLogTime = 0;
73
+ _enqueueCount = 0;
74
+ _dropInactiveCount = 0;
75
+ _dropCooldownCount = 0;
76
+ _dropNotReadyCount = 0;
77
+ _dropNoControllerCount = 0;
78
+ _pipTraceId = [[NSUUID UUID] UUIDString];
55
79
  // 监听从通知中心/后台返回活跃状态
56
80
  [[NSNotificationCenter defaultCenter] addObserver:self
57
81
  selector:@selector(handleAppDidBecomeActive)
@@ -61,6 +85,19 @@ NSString *const kObserverKey = @"pip-observer";
61
85
  return self;
62
86
  }
63
87
 
88
+ - (UIApplicationState)currentApplicationStateSafe {
89
+ __block UIApplicationState state = UIApplicationStateInactive;
90
+ void (^readState)(void) = ^{
91
+ state = [UIApplication sharedApplication].applicationState;
92
+ };
93
+ if ([NSThread isMainThread]) {
94
+ readState();
95
+ } else {
96
+ dispatch_sync(dispatch_get_main_queue(), readState);
97
+ }
98
+ return state;
99
+ }
100
+
64
101
  - (void)dealloc {
65
102
  [[NSNotificationCenter defaultCenter] removeObserver:self];
66
103
  [self disableVideoFrameObserver];
@@ -74,29 +111,54 @@ NSString *const kObserverKey = @"pip-observer";
74
111
  [[VeLivePlayerMultiObserver sharedInstance] removeObserver:kObserverKey];
75
112
  }
76
113
 
114
+ - (void)logDropIfNeeded:(NSString *)reason state:(UIApplicationState)state {
115
+ NSTimeInterval now = CFAbsoluteTimeGetCurrent();
116
+ if (self.lastDropLogTime > 0 &&
117
+ now - self.lastDropLogTime < kDropLogIntervalSeconds) {
118
+ return;
119
+ }
120
+ self.lastDropLogTime = now;
121
+ NSLog(@"PIP[%@] drop frame reason=%@ state=%ld elapsedSinceSwitch=%.3f",
122
+ self.pipTraceId, reason, (long)state,
123
+ (self.lastStateSwitchTime > 0 ? now - self.lastStateSwitchTime : -1.0));
124
+ }
125
+
77
126
  - (void)handleAppDidBecomeActive {
127
+ if (!self.pipController || !self.contentView) {
128
+ return;
129
+ }
130
+ AVPictureInPictureController *controller =
131
+ [self.pipController pictureInPictureController];
132
+ if (!controller || !controller.isPictureInPictureActive) {
133
+ return;
134
+ }
78
135
 
79
- if (self.pipController && [self.pipController pictureInPictureController]) {
80
- // 只有在 PIP 开启状态下才需要重连,避免干扰正常播放
81
- AVPictureInPictureController *controller =
82
- [self.pipController pictureInPictureController];
83
- if (controller.isPictureInPictureActive) {
84
- __weak typeof(self) weakSelf = self;
85
- dispatch_async(dispatch_get_main_queue(), ^{
86
- __strong typeof(weakSelf) strongSelf = weakSelf;
87
- if (!strongSelf) return;
88
- // 强制重新绑定 ContentView。
89
- // 这会诱导底层重新创建 IOSurface,修复内核日志中的 SID: 0x0 报错。
90
- [strongSelf.pipController setContentView:strongSelf.contentView completion:^{
91
- __strong typeof(weakSelf) strongSelf = weakSelf;
92
- if (!strongSelf) return;
93
- // 刷新播放状态,确保画面立即同步
94
- [[strongSelf.pipController pictureInPictureController] invalidatePlaybackState];
95
- NSLog(@"PIP: App active, re-bound Surface to fix potential corruption.");
96
- }];
97
- });
98
- }
136
+ // Keep a short guard window after state switch.
137
+ NSTimeInterval now = CFAbsoluteTimeGetCurrent();
138
+ if (self.lastStateSwitchTime > 0 &&
139
+ now - self.lastStateSwitchTime < kStateSwitchCooldownSeconds) {
140
+ return;
99
141
  }
142
+ // Avoid repeated rebind storms when app repeatedly toggles active/inactive.
143
+ if (self.lastRebindTime > 0 &&
144
+ now - self.lastRebindTime < kRebindCooldownSeconds) {
145
+ return;
146
+ }
147
+ self.lastRebindTime = now;
148
+
149
+ __weak typeof(self) weakSelf = self;
150
+ dispatch_async(dispatch_get_main_queue(), ^{
151
+ __strong typeof(weakSelf) strongSelf = weakSelf;
152
+ if (!strongSelf) return;
153
+ [strongSelf.pipController setContentView:strongSelf.contentView
154
+ completion:^{
155
+ __strong typeof(weakSelf) strongSelf = weakSelf;
156
+ if (!strongSelf) return;
157
+ [[strongSelf.pipController pictureInPictureController]
158
+ invalidatePlaybackState];
159
+ NSLog(@"PIP: App active, re-bound content view.");
160
+ }];
161
+ });
100
162
  }
101
163
 
102
164
  // Start playback state refresh timer
@@ -106,15 +168,15 @@ NSString *const kObserverKey = @"pip-observer";
106
168
  if (self.pipController && [self.pipController pictureInPictureController]) {
107
169
  // Use main thread timer to ensure UI updates on main thread
108
170
  self.playbackStateTimer = [NSTimer
109
- scheduledTimerWithTimeInterval:1.0
171
+ scheduledTimerWithTimeInterval:kFrameStatsLogIntervalSeconds
110
172
  target:self
111
- selector:@selector(invalidatePlaybackState)
173
+ selector:@selector(logFrameStats)
112
174
  userInfo:nil
113
175
  repeats:YES];
114
176
  // Add timer to main run loop
115
177
  [[NSRunLoop mainRunLoop] addTimer:self.playbackStateTimer
116
178
  forMode:NSRunLoopCommonModes];
117
- NSLog(@"PIP: Started playback state timer");
179
+ NSLog(@"PIP[%@] Started frame stats timer", self.pipTraceId);
118
180
  }
119
181
  }
120
182
 
@@ -123,7 +185,7 @@ NSString *const kObserverKey = @"pip-observer";
123
185
  if (self.playbackStateTimer) {
124
186
  [self.playbackStateTimer invalidate];
125
187
  self.playbackStateTimer = nil;
126
- NSLog(@"PIP: Stopped playback state timer");
188
+ NSLog(@"PIP[%@] Stopped frame stats timer", self.pipTraceId);
127
189
  }
128
190
  }
129
191
 
@@ -138,6 +200,15 @@ NSString *const kObserverKey = @"pip-observer";
138
200
  }
139
201
  }
140
202
 
203
+ - (void)logFrameStats {
204
+ NSLog(@"PIP[%@] frame stats enqueue=%lu dropInactive=%lu dropCooldown=%lu dropNotReady=%lu dropNoController=%lu",
205
+ self.pipTraceId, (unsigned long)self.enqueueCount,
206
+ (unsigned long)self.dropInactiveCount,
207
+ (unsigned long)self.dropCooldownCount,
208
+ (unsigned long)self.dropNotReadyCount,
209
+ (unsigned long)self.dropNoControllerCount);
210
+ }
211
+
141
212
  - (void)setupPlayer:(TVLManager *)player {
142
213
  [self ensurePipControllerReady:player completion:nil];
143
214
  }
@@ -212,6 +283,14 @@ NSString *const kObserverKey = @"pip-observer";
212
283
  self.pipController = [[VePictureInPictureController alloc]
213
284
  initWithType:VePictureInPictureTypeContentSource
214
285
  contentView:self.player.playerView];
286
+ self.pipTraceId = [[NSUUID UUID] UUIDString];
287
+ self.enqueueCount = 0;
288
+ self.dropInactiveCount = 0;
289
+ self.dropCooldownCount = 0;
290
+ self.dropNotReadyCount = 0;
291
+ self.dropNoControllerCount = 0;
292
+ NSLog(@"PIP[%@] init controller contentView=%p playerView=%p", self.pipTraceId,
293
+ self.contentView, self.player.playerView);
215
294
  [self.pipController setRenderMode:1];
216
295
  self.pipController.delegate = self;
217
296
  self.pipController.autoConfigAudioSession = YES;
@@ -403,38 +482,105 @@ NSString *const kObserverKey = @"pip-observer";
403
482
  - (void)onRenderVideoFrame:(TVLManager *_Nonnull)player
404
483
  videoFrame:(VeLivePlayerVideoFrame *_Nonnull)videoFrame {
405
484
  @autoreleasepool {
406
- UIApplicationState state = [UIApplication sharedApplication].applicationState;
407
-
408
- // 【核心修正】
409
- // 1. 如果是 Active (前台),正常推流。
410
- // 2. 如果是 Background (正常画中画/后台),正常推流。
411
- // 3. 【拦截】只有当状态是 Inactive 时:
412
- // 这通常对应“下拉通知中心”、“拉下控制中心”或“系统弹窗”遮挡。
413
- // 此时日志中的 SID: 0x0 报错最集中,必须拦截推流以保护内核句柄。
414
- NSLog(@"PIP: Application state is %ld", (long)state);
415
- if (state == UIApplicationStateInactive) {
416
- // 如果正在画中画过程中,且 App 处于非活跃(被遮挡)状态,暂时丢弃这一帧
485
+ UIApplicationState currentState = [self currentApplicationStateSafe];
486
+
487
+ // 1) Detect foreground/background switches and start cooldown.
488
+ if (currentState != self.lastAppState) {
489
+ self.lastAppState = currentState;
490
+ self.lastStateSwitchTime = CFAbsoluteTimeGetCurrent();
491
+ NSLog(@"PIP[%@] app state changed -> %ld", self.pipTraceId,
492
+ (long)currentState);
493
+ }
494
+
495
+ // 2) Block enqueue for 300ms after state switch.
496
+ if (self.lastStateSwitchTime > 0) {
497
+ NSTimeInterval elapsed =
498
+ CFAbsoluteTimeGetCurrent() - self.lastStateSwitchTime;
499
+ if (elapsed < kStateSwitchCooldownSeconds) {
500
+ self.dropCooldownCount += 1;
501
+ [self logDropIfNeeded:@"cooldown" state:currentState];
417
502
  return;
503
+ }
504
+ }
505
+
506
+ // 3) Block Inactive while Control Center/Notification shade is presented.
507
+ NSLog(@"PIP: Application state is %ld", (long)currentState);
508
+ if (currentState == UIApplicationStateInactive) {
509
+ self.dropInactiveCount += 1;
510
+ [self logDropIfNeeded:@"inactive" state:currentState];
511
+ return;
418
512
  }
419
513
 
420
514
  // Ensure pipController exists and is valid, avoid processing buffer during
421
515
  // destruction
422
516
  if (!self.pipController || !self.enableVideoObserver) {
517
+ self.dropNoControllerCount += 1;
518
+ [self logDropIfNeeded:@"no_controller_or_observer" state:currentState];
423
519
  return;
424
520
  }
425
521
 
426
522
  if (@available(iOS 15.0, *)) {
523
+ // 4) Enqueue on main queue to let UI/lifecycle transitions settle first.
524
+ CVPixelBufferRef pixelBuffer = NULL;
525
+ CMSampleBufferRef sampleBuffer = NULL;
427
526
  if (videoFrame.bufferType == VeLivePlayerVideoBufferTypePixelBuffer) {
428
- CVPixelBufferRef pixelBuffer = videoFrame.pixelBuffer;
429
- if (pixelBuffer != NULL) {
430
- [self.pipController enqueuePixelBuffer:pixelBuffer];
527
+ pixelBuffer = videoFrame.pixelBuffer;
528
+ if (pixelBuffer) {
529
+ CVBufferRetain(pixelBuffer);
431
530
  }
432
531
  } else if (videoFrame.bufferType ==
433
532
  VeLivePlayerVideoBufferTypeSampleBuffer) {
533
+ sampleBuffer = videoFrame.sampleBuffer;
534
+ if (sampleBuffer) {
535
+ CFRetain(sampleBuffer);
536
+ }
537
+ }
538
+
539
+ if (!pixelBuffer && !sampleBuffer) {
540
+ self.dropNotReadyCount += 1;
541
+ [self logDropIfNeeded:@"no_buffer" state:currentState];
542
+ return;
543
+ }
544
+
545
+ dispatch_async(dispatch_get_main_queue(), ^{
546
+ if ([self currentApplicationStateSafe] == UIApplicationStateInactive) {
547
+ if (pixelBuffer) {
548
+ CVBufferRelease(pixelBuffer);
549
+ }
550
+ if (sampleBuffer) {
551
+ CFRelease(sampleBuffer);
552
+ }
553
+ return;
554
+ }
555
+ if (!self.pipController || !self.enableVideoObserver) {
556
+ if (pixelBuffer) {
557
+ CVBufferRelease(pixelBuffer);
558
+ }
559
+ if (sampleBuffer) {
560
+ CFRelease(sampleBuffer);
561
+ }
562
+ return;
563
+ }
564
+ if (pixelBuffer) {
565
+ [self.pipController enqueuePixelBuffer:pixelBuffer];
566
+ CVBufferRelease(pixelBuffer);
567
+ self.enqueueCount += 1;
568
+ } else if (sampleBuffer) {
569
+ [self.pipController enqueueSampleBuffer:sampleBuffer];
570
+ CFRelease(sampleBuffer);
571
+ self.enqueueCount += 1;
572
+ }
573
+ });
574
+ } else {
575
+ // iOS < 15 fallback: keep existing direct path.
576
+ if (videoFrame.bufferType == VeLivePlayerVideoBufferTypeSampleBuffer) {
434
577
  CMSampleBufferRef sampleBuffer = videoFrame.sampleBuffer;
435
578
  if (sampleBuffer != NULL) {
436
579
  [self.pipController enqueueSampleBuffer:sampleBuffer];
580
+ self.enqueueCount += 1;
437
581
  }
582
+ } else {
583
+ self.dropNotReadyCount += 1;
438
584
  }
439
585
  }
440
586
  }
@@ -459,6 +605,8 @@ NSString *const kObserverKey = @"pip-observer";
459
605
  (VePictureInPictureController *)pictureInPictureController
460
606
  statusChanged:(VePictureInPictureStatus)fromStatus
461
607
  to:(VePictureInPictureStatus)toStatus {
608
+ NSLog(@"PIP[%@] status changed from=%ld to=%ld", self.pipTraceId,
609
+ (long)fromStatus, (long)toStatus);
462
610
  switch (toStatus) {
463
611
  case VePictureInPictureStatusIde: {
464
612
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volcengine/react-native-live-pull",
3
- "version": "1.5.2-rc.1",
3
+ "version": "1.5.2-rc.3",
4
4
  "peerDependencies": {
5
5
  "react": "*",
6
6
  "react-native": "*"