@volcengine/react-native-live-pull 1.5.2-rc.2 → 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.
|
@@ -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
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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:
|
|
171
|
+
scheduledTimerWithTimeInterval:kFrameStatsLogIntervalSeconds
|
|
110
172
|
target:self
|
|
111
|
-
selector:@selector(
|
|
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
|
|
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
|
|
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
|
|
407
|
-
|
|
408
|
-
//
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
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
|
-
|
|
429
|
-
if (pixelBuffer
|
|
430
|
-
|
|
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
|
|