@byteplus/react-native-live-push 1.1.3-rc.0 → 1.1.3-rc.2

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.
Files changed (123) hide show
  1. package/android/src/main/java/com/volcengine/velive/rn/push/VeLivePushModule.java +13 -2
  2. package/android/src/main/java/com/volcengine/velive/rn/push/VeLivePushPackage.java +16 -13
  3. package/android/src/main/java/com/volcengine/velive/rn/push/VeLivePushView.java +16 -0
  4. package/android/src/main/java/com/volcengine/velive/rn/push/VeLivePushViewManager.java +7 -2
  5. package/android/src/main/java/com/volcengine/velive/rn/push/mixer/MixerManager.java +392 -0
  6. package/android/src/main/java/com/volcengine/velive/rn/push/mixer/MixerView.java +415 -0
  7. package/android/src/main/java/com/volcengine/velive/rn/push/mixer/MixerViewManager.java +79 -0
  8. package/android/src/main/java/com/volcengine/velive/rn/push/mixer/TextureMgr.java +168 -0
  9. package/android/src/main/java/com/volcengine/velive/rn/push/mixer/YuvHelper.java +154 -0
  10. package/ios/VeLiveMixerHelper.h +44 -0
  11. package/ios/VeLiveMixerHelper.m +562 -0
  12. package/ios/VeLiveMixerView.h +69 -0
  13. package/ios/VeLiveMixerView.m +592 -0
  14. package/ios/VeLiveMixerViewManager.m +92 -0
  15. package/lib/commonjs/index.js +1779 -980
  16. package/lib/commonjs/typescript/android/index.d.ts +47 -0
  17. package/lib/commonjs/typescript/codegen/android/api.d.ts +1527 -0
  18. package/lib/commonjs/typescript/codegen/android/callback.d.ts +91 -0
  19. package/lib/commonjs/typescript/codegen/android/errorcode.d.ts +26 -0
  20. package/lib/commonjs/typescript/codegen/android/index.d.ts +5 -0
  21. package/lib/commonjs/typescript/codegen/android/keytype.d.ts +846 -0
  22. package/lib/commonjs/typescript/codegen/android/types.d.ts +33 -0
  23. package/lib/commonjs/typescript/codegen/ios/api.d.ts +222 -0
  24. package/lib/commonjs/typescript/codegen/ios/callback.d.ts +80 -0
  25. package/lib/commonjs/typescript/codegen/ios/errorcode.d.ts +54 -0
  26. package/lib/commonjs/typescript/codegen/ios/external.d.ts +1 -0
  27. package/lib/commonjs/typescript/codegen/ios/index.d.ts +6 -0
  28. package/lib/commonjs/typescript/codegen/ios/keytype.d.ts +460 -0
  29. package/lib/commonjs/typescript/codegen/ios/types.d.ts +46 -0
  30. package/lib/commonjs/typescript/codegen/pack/api.d.ts +1835 -0
  31. package/lib/commonjs/typescript/codegen/pack/callback.d.ts +400 -0
  32. package/lib/commonjs/typescript/codegen/pack/errorcode.d.ts +35 -0
  33. package/lib/commonjs/typescript/codegen/pack/index.d.ts +5 -0
  34. package/lib/commonjs/typescript/codegen/pack/keytype.d.ts +1392 -0
  35. package/lib/commonjs/typescript/codegen/pack/types.d.ts +68 -0
  36. package/lib/commonjs/typescript/codegen/type-shim.d.ts +6 -0
  37. package/lib/commonjs/typescript/component.d.ts +15 -0
  38. package/lib/commonjs/typescript/core/api.d.ts +17 -0
  39. package/lib/commonjs/typescript/core/callback.d.ts +2 -0
  40. package/lib/commonjs/typescript/core/env.d.ts +29 -0
  41. package/lib/commonjs/typescript/core/errorcode.d.ts +2 -0
  42. package/lib/commonjs/typescript/core/index.d.ts +6 -0
  43. package/lib/commonjs/typescript/core/keytype.d.ts +17 -0
  44. package/lib/commonjs/typescript/core/mixer.d.ts +26 -0
  45. package/lib/commonjs/typescript/core/pusher.d.ts +16 -0
  46. package/lib/commonjs/typescript/index.d.ts +3 -0
  47. package/lib/commonjs/typescript/ios/extends.d.ts +41 -0
  48. package/lib/commonjs/typescript/platforms/android/extends.d.ts +8 -0
  49. package/lib/commonjs/typescript/platforms/android/helper.d.ts +8 -0
  50. package/lib/commonjs/typescript/platforms/android/mixer.d.ts +8 -0
  51. package/lib/commonjs/typescript/platforms/ios/extends.d.ts +17 -0
  52. package/lib/commonjs/typescript/platforms/ios/helper.d.ts +8 -0
  53. package/lib/commonjs/typescript/platforms/ios/mixer.d.ts +9 -0
  54. package/lib/commonjs/typescript/runtime.d.ts +1 -0
  55. package/lib/commonjs/typescript/view/MixView.d.ts +52 -0
  56. package/lib/commonjs/typescript/view/VeImageView.d.ts +19 -0
  57. package/lib/commonjs/typescript/view/VeTextView.d.ts +7 -0
  58. package/lib/commonjs/typescript/view/VeView.d.ts +7 -0
  59. package/lib/commonjs/typescript/view/VeWebView.d.ts +7 -0
  60. package/lib/commonjs/typescript/view/index.d.ts +5 -0
  61. package/lib/module/index.js +1776 -982
  62. package/lib/module/typescript/android/index.d.ts +47 -0
  63. package/lib/module/typescript/codegen/android/api.d.ts +1527 -0
  64. package/lib/module/typescript/codegen/android/callback.d.ts +91 -0
  65. package/lib/module/typescript/codegen/android/errorcode.d.ts +26 -0
  66. package/lib/module/typescript/codegen/android/index.d.ts +5 -0
  67. package/lib/module/typescript/codegen/android/keytype.d.ts +846 -0
  68. package/lib/module/typescript/codegen/android/types.d.ts +33 -0
  69. package/lib/module/typescript/codegen/ios/api.d.ts +222 -0
  70. package/lib/module/typescript/codegen/ios/callback.d.ts +80 -0
  71. package/lib/module/typescript/codegen/ios/errorcode.d.ts +54 -0
  72. package/lib/module/typescript/codegen/ios/external.d.ts +1 -0
  73. package/lib/module/typescript/codegen/ios/index.d.ts +6 -0
  74. package/lib/module/typescript/codegen/ios/keytype.d.ts +460 -0
  75. package/lib/module/typescript/codegen/ios/types.d.ts +46 -0
  76. package/lib/module/typescript/codegen/pack/api.d.ts +1835 -0
  77. package/lib/module/typescript/codegen/pack/callback.d.ts +400 -0
  78. package/lib/module/typescript/codegen/pack/errorcode.d.ts +35 -0
  79. package/lib/module/typescript/codegen/pack/index.d.ts +5 -0
  80. package/lib/module/typescript/codegen/pack/keytype.d.ts +1392 -0
  81. package/lib/module/typescript/codegen/pack/types.d.ts +68 -0
  82. package/lib/module/typescript/codegen/type-shim.d.ts +6 -0
  83. package/lib/module/typescript/component.d.ts +15 -0
  84. package/lib/module/typescript/core/api.d.ts +17 -0
  85. package/lib/module/typescript/core/callback.d.ts +2 -0
  86. package/lib/module/typescript/core/env.d.ts +29 -0
  87. package/lib/module/typescript/core/errorcode.d.ts +2 -0
  88. package/lib/module/typescript/core/index.d.ts +6 -0
  89. package/lib/module/typescript/core/keytype.d.ts +17 -0
  90. package/lib/module/typescript/core/mixer.d.ts +26 -0
  91. package/lib/module/typescript/core/pusher.d.ts +16 -0
  92. package/lib/module/typescript/index.d.ts +3 -0
  93. package/lib/module/typescript/ios/extends.d.ts +41 -0
  94. package/lib/module/typescript/platforms/android/extends.d.ts +8 -0
  95. package/lib/module/typescript/platforms/android/helper.d.ts +8 -0
  96. package/lib/module/typescript/platforms/android/mixer.d.ts +8 -0
  97. package/lib/module/typescript/platforms/ios/extends.d.ts +17 -0
  98. package/lib/module/typescript/platforms/ios/helper.d.ts +8 -0
  99. package/lib/module/typescript/platforms/ios/mixer.d.ts +9 -0
  100. package/lib/module/typescript/runtime.d.ts +1 -0
  101. package/lib/module/typescript/view/MixView.d.ts +52 -0
  102. package/lib/module/typescript/view/VeImageView.d.ts +19 -0
  103. package/lib/module/typescript/view/VeTextView.d.ts +7 -0
  104. package/lib/module/typescript/view/VeView.d.ts +7 -0
  105. package/lib/module/typescript/view/VeWebView.d.ts +7 -0
  106. package/lib/module/typescript/view/index.d.ts +5 -0
  107. package/lib/typescript/codegen/android/api.d.ts +1 -121
  108. package/lib/typescript/codegen/ios/api.d.ts +1 -28
  109. package/lib/typescript/codegen/pack/api.d.ts +1 -133
  110. package/lib/typescript/core/api.d.ts +17 -2
  111. package/lib/typescript/core/keytype.d.ts +15 -0
  112. package/lib/typescript/core/mixer.d.ts +26 -0
  113. package/lib/typescript/index.d.ts +1 -0
  114. package/lib/typescript/platforms/android/extends.d.ts +8 -0
  115. package/lib/typescript/platforms/android/mixer.d.ts +8 -0
  116. package/lib/typescript/platforms/ios/mixer.d.ts +9 -0
  117. package/lib/typescript/view/MixView.d.ts +52 -0
  118. package/lib/typescript/view/VeImageView.d.ts +19 -0
  119. package/lib/typescript/view/VeTextView.d.ts +7 -0
  120. package/lib/typescript/view/VeView.d.ts +7 -0
  121. package/lib/typescript/view/VeWebView.d.ts +7 -0
  122. package/lib/typescript/view/index.d.ts +5 -0
  123. package/package.json +1 -1
@@ -0,0 +1,592 @@
1
+ //
2
+ // VeLiveMixerView.m
3
+ // react-native-live-push
4
+ //
5
+ // Created by ByteDance on 2024/12/09.
6
+ //
7
+
8
+ #import "VeLiveMixerView.h"
9
+ #import "VeLiveMixerHelper.h"
10
+ #import <UIKit/UIKit.h>
11
+ #import <AVFoundation/AVFoundation.h>
12
+ #import <CoreMedia/CoreMedia.h>
13
+ #import <CoreVideo/CoreVideo.h>
14
+
15
+ @interface VeLiveMixerUIView ()
16
+
17
+ // Mix configuration - Align with Android
18
+ @property(nonatomic, assign) float mixX;
19
+ @property(nonatomic, assign) float mixY;
20
+ @property(nonatomic, assign) float mixWidth;
21
+ @property(nonatomic, assign) float mixHeight;
22
+ @property(nonatomic, assign) int mixZOrder;
23
+ @property(nonatomic, assign) int mixRenderMode;
24
+
25
+ // Capture configuration - Internal properties
26
+ @property(nonatomic, strong) NSString *internalCaptureMode;
27
+ @property(nonatomic, assign) float internalCaptureFramerate;
28
+ @property(nonatomic, assign) float internalAutoSensitivity;
29
+
30
+ // Capture state - Align with Android
31
+ @property(nonatomic, strong) NSTimer *captureTimer;
32
+ @property(nonatomic, assign) BOOL isCapturing;
33
+ @property(nonatomic, assign) BOOL isDestroyed;
34
+ @property(nonatomic, assign) CFTimeInterval lastCaptureTime;
35
+
36
+ // Performance optimization - Align with Android
37
+ @property(nonatomic, strong) UIImage *lastBitmap;
38
+
39
+ // Auto mode state - Align with Android
40
+ @property(nonatomic, assign) int changeCount;
41
+ @property(nonatomic, assign) CFTimeInterval windowStartTime;
42
+ @property(nonatomic, assign) BOOL isInRealtimeMode;
43
+
44
+ @end
45
+
46
+ @implementation VeLiveMixerUIView
47
+
48
+ static const CFTimeInterval AUTO_WINDOW_SECONDS = 2.0; // 2 second window
49
+ static const int HIGH_CHANGE_THRESHOLD = 10;
50
+ static const int LOW_CHANGE_THRESHOLD = 2;
51
+
52
+ - (instancetype)initWithFrame:(CGRect)frame {
53
+ self = [super initWithFrame:frame];
54
+ if (self) {
55
+ [self initializeCapture];
56
+ [self setupUIView];
57
+ }
58
+ return self;
59
+ }
60
+
61
+ - (void)setupUIView {
62
+ // Reference VeLivePushView.m settings, but keep our mixed view features
63
+ self.clipsToBounds = YES; // Changed to YES, consistent with VeLivePushView
64
+ self.userInteractionEnabled = YES;
65
+
66
+ // Set default background color to transparent, allowing subviews to display
67
+ self.backgroundColor = [UIColor clearColor];
68
+
69
+ NSLog(@"[VeLiveMixerView] 🎨 setupUIView completed (clipsToBounds=YES, like VeLivePushView)");
70
+ }
71
+
72
+ - (void)initializeCapture {
73
+ // Initialize defaults - Completely align with Android
74
+ _mixX = 0.0f;
75
+ _mixY = 0.0f;
76
+ _mixWidth = 0.0f;
77
+ _mixHeight = 0.0f;
78
+ _mixZOrder = 0;
79
+ _mixRenderMode = 0;
80
+
81
+ _internalCaptureMode = @"onchange";
82
+ _internalCaptureFramerate = 30.0f;
83
+ _internalAutoSensitivity = 5.0f;
84
+
85
+ _isCapturing = NO;
86
+ _isDestroyed = NO;
87
+ _lastCaptureTime = 0;
88
+
89
+ // Auto mode state
90
+ _changeCount = 0;
91
+ _windowStartTime = 0;
92
+ _isInRealtimeMode = NO;
93
+ }
94
+
95
+ #pragma mark - Property Getters/Setters - Align with React Native props
96
+
97
+ - (NSString *)captureMode {
98
+ return self.internalCaptureMode;
99
+ }
100
+
101
+ - (void)setCaptureMode:(NSString *)mode {
102
+ if ([mode isEqualToString:self.internalCaptureMode]) return;
103
+
104
+ [self stopCapture];
105
+ _internalCaptureMode = mode;
106
+ [self startCapture];
107
+ }
108
+
109
+ - (NSNumber *)captureFramerate {
110
+ return @(self.internalCaptureFramerate);
111
+ }
112
+
113
+ - (void)setCaptureFramerate:(NSNumber *)framerate {
114
+ _internalCaptureFramerate = MAX(1.0f, MIN(60.0f, [framerate floatValue]));
115
+ if ([self.internalCaptureMode isEqualToString:@"realtime"]) {
116
+ [self restartRealtimeCapture];
117
+ }
118
+ }
119
+
120
+ - (NSNumber *)autoSensitivity {
121
+ return @(self.internalAutoSensitivity);
122
+ }
123
+
124
+ - (void)setAutoSensitivity:(NSNumber *)sensitivity {
125
+ _internalAutoSensitivity = MAX(1.0f, MIN(10.0f, [sensitivity floatValue]));
126
+ }
127
+
128
+ - (void)setViewId:(NSString *)viewId {
129
+ if (!viewId) {
130
+ return;
131
+ }
132
+
133
+ NSLog(@"[VeLiveMixerView] 📝 setViewId: %@", viewId);
134
+
135
+ // Set viewId
136
+ _viewId = [viewId copy];
137
+
138
+ // Register to global cache - Align with Android's onAttachedToWindow
139
+ NSMutableDictionary *cachedViews = [VeLiveMixerHelper cachedMixedViews];
140
+ if (cachedViews) {
141
+ [cachedViews setObject:self forKey:viewId];
142
+ NSLog(@"[VeLiveMixerView] ✅ Registered to cachedMixedViews: %@", viewId);
143
+
144
+ // Notify MixerHelper view is ready, check for pending callbacks
145
+ [VeLiveMixerHelper onViewReady:viewId mixerView:self];
146
+ }
147
+ }
148
+
149
+ #pragma mark - Property setters for React Native props - Align with Android
150
+
151
+ // React Native property setter - Corresponds to VeLiveMixerViewManager.m properties
152
+ - (void)setX:(NSNumber *)x {
153
+ _x = x;
154
+ _mixX = [x floatValue];
155
+ }
156
+
157
+ - (void)setY:(NSNumber *)y {
158
+ _y = y;
159
+ _mixY = [y floatValue];
160
+ }
161
+
162
+ - (void)setWidth:(NSNumber *)width {
163
+ _width = width;
164
+ _mixWidth = [width floatValue];
165
+ }
166
+
167
+ - (void)setHeight:(NSNumber *)height {
168
+ _height = height;
169
+ _mixHeight = [height floatValue];
170
+ }
171
+
172
+ - (void)setZOrder:(NSNumber *)zOrder {
173
+ _zOrder = zOrder;
174
+ _mixZOrder = [zOrder intValue];
175
+ }
176
+
177
+ - (void)setRenderMode:(NSNumber *)renderMode {
178
+ _renderMode = renderMode;
179
+ _mixRenderMode = [renderMode intValue];
180
+ }
181
+
182
+ #pragma mark - Internal setters for mix configuration - Internal use
183
+
184
+ - (void)setMixX:(float)x { _mixX = x; }
185
+ - (void)setMixY:(float)y { _mixY = y; }
186
+ - (void)setMixWidth:(float)width { _mixWidth = width; }
187
+ - (void)setMixHeight:(float)height { _mixHeight = height; }
188
+ - (void)setMixZOrder:(int)zOrder { _mixZOrder = zOrder; }
189
+ - (void)setMixRenderMode:(int)renderMode { _mixRenderMode = renderMode; }
190
+
191
+ #pragma mark - Capture Methods - Completely align with Android
192
+
193
+ - (void)startCapture {
194
+ if (self.isDestroyed) return;
195
+
196
+ if ([self.internalCaptureMode isEqualToString:@"onchange"]) {
197
+ [self startOnChangeCapture];
198
+ } else if ([self.internalCaptureMode isEqualToString:@"realtime"]) {
199
+ [self startRealtimeCapture];
200
+ } else if ([self.internalCaptureMode isEqualToString:@"auto"]) {
201
+ [self startAutoCapture];
202
+ }
203
+ // manual mode doesn't auto-start
204
+ }
205
+
206
+ - (void)stopCapture {
207
+ [self.captureTimer invalidate];
208
+ self.captureTimer = nil;
209
+ self.isCapturing = NO;
210
+ }
211
+
212
+ - (void)startOnChangeCapture {
213
+ // onchange mode triggers in layoutSubviews
214
+ }
215
+
216
+ - (void)startRealtimeCapture {
217
+ long interval = (long)(1000.0f / self.internalCaptureFramerate);
218
+ self.captureTimer = [NSTimer scheduledTimerWithTimeInterval:interval/1000.0
219
+ target:self
220
+ selector:@selector(realtimeCapture)
221
+ userInfo:nil
222
+ repeats:YES];
223
+ }
224
+
225
+ - (void)realtimeCapture {
226
+ if (!self.isDestroyed && [self.internalCaptureMode isEqualToString:@"realtime"]) {
227
+ @autoreleasepool {
228
+ [self performCapture:@"realtime"];
229
+
230
+ // Every 5 realtime captures, perform background memory cleanup
231
+ static int captureCount = 0;
232
+ captureCount++;
233
+ if (captureCount % 5 == 0) {
234
+ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
235
+ NSLog(@"[VeLiveMixerView] 🧹 Performing background memory cleanup after %d captures", captureCount);
236
+ [[NSURLCache sharedURLCache] removeAllCachedResponses];
237
+ [[NSURLCache sharedURLCache] setMemoryCapacity:0];
238
+ [[NSURLCache sharedURLCache] setMemoryCapacity:10 * 1024 * 1024]; // 10MB
239
+ });
240
+ }
241
+ }
242
+ }
243
+ }
244
+
245
+ - (void)restartRealtimeCapture {
246
+ if ([self.internalCaptureMode isEqualToString:@"realtime"]) {
247
+ [self stopCapture];
248
+ [self startRealtimeCapture];
249
+ }
250
+ }
251
+
252
+ - (void)startAutoCapture {
253
+ // Start with onchange mode
254
+ self.isInRealtimeMode = NO;
255
+ self.windowStartTime = CACurrentMediaTime();
256
+ self.changeCount = 0;
257
+ [self startOnChangeCapture];
258
+ }
259
+
260
+ #pragma mark - Layout Change Detection - Align with Android's onViewChanged
261
+
262
+ - (void)handleAutoModeChange {
263
+ if (!self.isInRealtimeMode) {
264
+ [self performCapture:@"auto_onchange"];
265
+ }
266
+
267
+ // Count changes for auto mode
268
+ self.changeCount++;
269
+
270
+ CFTimeInterval currentTime = CACurrentMediaTime();
271
+ if (currentTime - self.windowStartTime >= AUTO_WINDOW_SECONDS) {
272
+ [self evaluateAutoMode];
273
+ // Reset window
274
+ self.windowStartTime = currentTime;
275
+ self.changeCount = 0;
276
+ }
277
+ }
278
+
279
+ - (void)evaluateAutoMode {
280
+ float changesPerSecond = self.changeCount / AUTO_WINDOW_SECONDS;
281
+ float threshold = self.internalAutoSensitivity;
282
+
283
+ BOOL shouldBeRealtime = changesPerSecond > (HIGH_CHANGE_THRESHOLD / threshold);
284
+ BOOL shouldBeOnChange = changesPerSecond < (LOW_CHANGE_THRESHOLD / threshold);
285
+
286
+ if (shouldBeRealtime && !self.isInRealtimeMode) {
287
+ [self switchToRealtimeMode];
288
+ } else if (shouldBeOnChange && self.isInRealtimeMode) {
289
+ [self switchToOnChangeMode];
290
+ }
291
+ }
292
+
293
+ - (void)switchToRealtimeMode {
294
+ self.isInRealtimeMode = YES;
295
+ [self startRealtimeCapture];
296
+ }
297
+
298
+ - (void)switchToOnChangeMode {
299
+ self.isInRealtimeMode = NO;
300
+ [self.captureTimer invalidate];
301
+ self.captureTimer = nil;
302
+ }
303
+
304
+ #pragma mark - Core Capture Logic - Align with Android's performCapture
305
+
306
+ - (void)performCapture:(NSString *)trigger {
307
+ if (self.isCapturing || self.isDestroyed) return;
308
+
309
+ // Throttle captures to prevent excessive calls
310
+ CFTimeInterval currentTime = CACurrentMediaTime();
311
+ if (currentTime - self.lastCaptureTime < 1.0/60.0) { // Max 60fps
312
+ return;
313
+ }
314
+ self.lastCaptureTime = currentTime;
315
+
316
+ self.isCapturing = YES;
317
+
318
+ @try {
319
+ UIImage *bitmap = [self createBitmap];
320
+ if (bitmap) {
321
+ [self processBitmap:bitmap trigger:trigger];
322
+ }
323
+ } @finally {
324
+ self.isCapturing = NO;
325
+ }
326
+ }
327
+
328
+ - (UIImage *)createBitmap {
329
+ CGSize viewSize = self.bounds.size;
330
+
331
+ // Check for valid bounds
332
+ if (viewSize.width <= 0 || viewSize.height <= 0) {
333
+ return nil;
334
+ }
335
+
336
+ // Limit maximum image dimensions to prevent memory issues
337
+ CGFloat maxDimension = 1920.0; // Max 1920px on either side
338
+ CGFloat scale = [UIScreen mainScreen].scale;
339
+
340
+ // Calculate actual pixel dimensions
341
+ CGFloat pixelWidth = viewSize.width * scale;
342
+ CGFloat pixelHeight = viewSize.height * scale;
343
+
344
+ // Scale down if too large
345
+ if (pixelWidth > maxDimension || pixelHeight > maxDimension) {
346
+ CGFloat scaleDown = MIN(maxDimension / pixelWidth, maxDimension / pixelHeight);
347
+ scale = scale * scaleDown;
348
+ }
349
+
350
+ // Further limit scale on very large views
351
+ if (viewSize.width * viewSize.height > 1000000) { // > 1M points
352
+ scale = MIN(scale, 1.0);
353
+ }
354
+
355
+ @autoreleasepool {
356
+ UIGraphicsBeginImageContextWithOptions(viewSize, NO, scale);
357
+
358
+ CGContextRef context = UIGraphicsGetCurrentContext();
359
+ if (!context) {
360
+ UIGraphicsEndImageContext();
361
+ return nil;
362
+ }
363
+
364
+ [self drawViewHierarchyInRect:self.bounds afterScreenUpdates:NO];
365
+ UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
366
+ UIGraphicsEndImageContext();
367
+
368
+ return image;
369
+ }
370
+ }
371
+
372
+ - (void)processBitmap:(UIImage *)bitmap trigger:(NSString *)trigger {
373
+ @autoreleasepool {
374
+ if (!bitmap || bitmap.size.width <= 0 || bitmap.size.height <= 0) {
375
+ return;
376
+ }
377
+
378
+ // Check bitmap memory size and skip if too large
379
+ CGFloat bitmapMemoryMB = (bitmap.size.width * bitmap.size.height * 4.0) / (1024.0 * 1024.0);
380
+ if (bitmapMemoryMB > 50.0) { // Skip bitmaps larger than 50MB
381
+ return;
382
+ }
383
+
384
+ if (self.bitmapCaptureCallback) {
385
+ @try {
386
+ [self.bitmapCaptureCallback onBitmapCaptured:bitmap];
387
+ } @catch (NSException *exception) {
388
+ if (self.bitmapCaptureCallback) {
389
+ [self.bitmapCaptureCallback onCaptureError:[NSString stringWithFormat:@"Callback error: %@", exception.reason]];
390
+ }
391
+ }
392
+ }
393
+
394
+ if (self.onBitmapCapture) {
395
+ @autoreleasepool {
396
+ NSDictionary *event = @{
397
+ @"trigger": trigger ?: @"unknown",
398
+ @"success": @(YES),
399
+ @"x": @(self.mixX),
400
+ @"y": @(self.mixY),
401
+ @"width": @(self.mixWidth),
402
+ @"height": @(self.mixHeight),
403
+ @"zOrder": @(self.mixZOrder),
404
+ @"renderMode": @(self.mixRenderMode),
405
+ @"bitmapWidth": @(bitmap.size.width),
406
+ @"bitmapHeight": @(bitmap.size.height)
407
+ };
408
+ self.onBitmapCapture(event);
409
+ }
410
+ }
411
+
412
+ // Clean up old bitmap immediately
413
+ if (self.lastBitmap) {
414
+ self.lastBitmap = nil;
415
+ }
416
+
417
+ // For realtime mode, don't keep bitmap reference to reduce memory usage
418
+ if ([trigger isEqualToString:@"realtime"]) {
419
+ // Bitmap will be auto-released at autoreleasepool end
420
+
421
+ // Force memory cleanup every 10 realtime captures
422
+ static int realtimeCount = 0;
423
+ realtimeCount++;
424
+ if (realtimeCount % 10 == 0) {
425
+ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
426
+ @autoreleasepool {
427
+ [[NSURLCache sharedURLCache] removeAllCachedResponses];
428
+ }
429
+ });
430
+ }
431
+ } else {
432
+ self.lastBitmap = bitmap;
433
+ }
434
+ }
435
+ }
436
+
437
+ #pragma mark - Public Methods - Align with Android interface
438
+
439
+ // Align with Android's performManualCapture
440
+ - (void)performCaptureWithTrigger:(NSString *)trigger {
441
+ [self performCapture:trigger];
442
+ }
443
+
444
+ // Align with Android's setBitmapCaptureCallback
445
+ - (void)setBitmapCaptureCallback:(id<VeLiveBitmapCaptureCallback>)callback {
446
+ _bitmapCaptureCallback = callback;
447
+ }
448
+
449
+ #pragma mark - Cleanup - Align with Android
450
+
451
+ - (void)cleanup {
452
+ NSLog(@"[VeLiveMixerView] 🧹 Starting cleanup for viewId: %@", self.viewId);
453
+
454
+ self.isDestroyed = YES;
455
+ [self stopCapture];
456
+
457
+ // Clean up bitmap cache
458
+ self.lastBitmap = nil;
459
+
460
+ // Clean up callback references, avoid circular references
461
+ self.bitmapCaptureCallback = nil;
462
+
463
+ NSLog(@"[VeLiveMixerView] ✅ Cleanup completed for viewId: %@", self.viewId);
464
+ }
465
+
466
+ - (void)dealloc {
467
+ // Remove from global cache
468
+ if (self.viewId) {
469
+ NSMutableDictionary *cachedViews = [VeLiveMixerHelper cachedMixedViews];
470
+ if (cachedViews) {
471
+ [cachedViews removeObjectForKey:self.viewId];
472
+ NSLog(@"[VeLiveMixerView] ♻️ Removed from cachedMixedViews: %@", self.viewId);
473
+ }
474
+ }
475
+
476
+ NSLog(@"[VeLiveMixerView] 🗑️ Deallocated: %@", self.viewId);
477
+ }
478
+
479
+ #pragma mark - View Conversion Methods
480
+
481
+ - (UIImage *)snapshotIncludeSubviews {
482
+ CGSize viewSize = self.bounds.size;
483
+
484
+ // Check for valid bounds
485
+ if (viewSize.width <= 0 || viewSize.height <= 0) {
486
+ return nil;
487
+ }
488
+
489
+ // Limit maximum image dimensions to prevent memory issues
490
+ CGFloat maxDimension = 1920.0; // Max 1920px on either side
491
+ CGFloat scale = [UIScreen mainScreen].scale;
492
+
493
+ // Calculate actual pixel dimensions
494
+ CGFloat pixelWidth = viewSize.width * scale;
495
+ CGFloat pixelHeight = viewSize.height * scale;
496
+
497
+ // Scale down if too large
498
+ if (pixelWidth > maxDimension || pixelHeight > maxDimension) {
499
+ CGFloat scaleDown = MIN(maxDimension / pixelWidth, maxDimension / pixelHeight);
500
+ scale = scale * scaleDown;
501
+ }
502
+
503
+ // Further limit scale on very large views
504
+ if (viewSize.width * viewSize.height > 1000000) { // > 1M points
505
+ scale = MIN(scale, 1.0);
506
+ }
507
+
508
+ @autoreleasepool {
509
+ UIGraphicsBeginImageContextWithOptions(viewSize, NO, scale);
510
+
511
+ CGContextRef context = UIGraphicsGetCurrentContext();
512
+ if (!context) {
513
+ UIGraphicsEndImageContext();
514
+ return nil;
515
+ }
516
+
517
+ [self drawViewHierarchyInRect:self.bounds afterScreenUpdates:NO];
518
+ UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
519
+ UIGraphicsEndImageContext();
520
+
521
+ return image;
522
+ }
523
+ }
524
+
525
+ - (CVPixelBufferRef)createPixelBuffer {
526
+ // Implement pixel buffer creation (corresponds to Android's ByteBuffer)
527
+ return NULL; // Temporary return NULL, implementation based on needs
528
+ }
529
+
530
+ - (CMSampleBufferRef)createSampleBuffer {
531
+ // Implement sample buffer creation (corresponds to Android's VideoFrame)
532
+ return NULL; // Temporary return NULL, implementation based on needs
533
+ }
534
+
535
+ #pragma mark - React Native Child View Support - Focus on RN subview management
536
+
537
+ // React Native subview insertion - This is the main method called by RN framework
538
+ - (void)insertReactSubview:(UIView *)subview atIndex:(NSInteger)atIndex {
539
+ NSLog(@"[VeLiveMixerView] 📱 insertReactSubview: %@ at index: %ld", subview.class, (long)atIndex);
540
+
541
+ // Process directly on main thread because React Native ensures thread safety
542
+ [super insertSubview:subview atIndex:atIndex];
543
+ [self setSubViewAutoLayout:subview];
544
+ }
545
+
546
+ - (void)removeReactSubview:(UIView *)subview {
547
+ NSLog(@"[VeLiveMixerView] 🗑️ removeReactSubview: %@", subview.class);
548
+ [subview removeFromSuperview];
549
+ }
550
+
551
+ - (NSArray<UIView *> *)reactSubviews {
552
+ return [self subviews];
553
+ }
554
+
555
+ // Set Auto Layout constraints - Ensure subview fills parent view
556
+ - (void)setSubViewAutoLayout:(UIView *)subview {
557
+ NSLog(@"[VeLiveMixerView] 📐 setSubViewAutoLayout for: %@", subview.class);
558
+
559
+ // Disable automatic conversion, use constraints layout
560
+ subview.translatesAutoresizingMaskIntoConstraints = NO;
561
+
562
+ // Set four-side constraints, let subview fill parent view
563
+ [NSLayoutConstraint activateConstraints:@[
564
+ [subview.leadingAnchor constraintEqualToAnchor:self.leadingAnchor],
565
+ [subview.trailingAnchor constraintEqualToAnchor:self.trailingAnchor],
566
+ [subview.topAnchor constraintEqualToAnchor:self.topAnchor],
567
+ [subview.bottomAnchor constraintEqualToAnchor:self.bottomAnchor]
568
+ ]];
569
+
570
+ NSLog(@"[VeLiveMixerView] ✅ Auto Layout constraints applied for subview");
571
+ }
572
+
573
+ // Override layoutSubviews to ensure correct subview layout
574
+ - (void)layoutSubviews {
575
+ [super layoutSubviews];
576
+ if (self.isDestroyed) return;
577
+
578
+ // Original capture logic
579
+ if ([self.internalCaptureMode isEqualToString:@"onchange"]) {
580
+ [self performCapture:@"onchange"];
581
+ } else if ([self.internalCaptureMode isEqualToString:@"auto"]) {
582
+ [self handleAutoModeChange];
583
+ }
584
+ }
585
+
586
+ // Ensure UIView can correctly display background color and subviews
587
+ - (void)setBackgroundColor:(UIColor *)backgroundColor {
588
+ NSLog(@"[VeLiveMixerView] 🎨 setBackgroundColor: %@", backgroundColor);
589
+ [super setBackgroundColor:backgroundColor];
590
+ }
591
+
592
+ @end
@@ -0,0 +1,92 @@
1
+ //
2
+ // VeLiveMixerViewManager.m
3
+ // react-native-live-push
4
+ //
5
+ // Created to manage VeLiveMixerView React Native component
6
+ //
7
+
8
+ #import "VeLiveMixerView.h"
9
+ #import <Foundation/Foundation.h>
10
+ #import <React/RCTComponent.h>
11
+ #import <React/RCTViewManager.h>
12
+ #import <UIKit/UIKit.h>
13
+
14
+ @interface VeLiveMixerViewManager : RCTViewManager
15
+ @end
16
+
17
+ @implementation VeLiveMixerViewManager
18
+
19
+ + (BOOL)requiresMainQueueSetup {
20
+ return YES;
21
+ }
22
+
23
+ RCT_EXPORT_MODULE(VeLiveMixView)
24
+
25
+ - (VeLiveMixerUIView *)view {
26
+ VeLiveMixerUIView *view = [[VeLiveMixerUIView alloc] init];
27
+ view.accessibilityLabel = @"VeLiveMixerUIView";
28
+ return view;
29
+ }
30
+
31
+ // 导出视图ID属性 - 使用自定义属性处理
32
+ RCT_CUSTOM_VIEW_PROPERTY(viewId, NSString, VeLiveMixerUIView) {
33
+ if (json == nil) {
34
+ return;
35
+ }
36
+
37
+ NSString *viewId = [RCTConvert NSString:json];
38
+ [view setViewId:viewId];
39
+ }
40
+
41
+ // 导出混合配置属性 - 与Android保持一致的属性名
42
+ RCT_CUSTOM_VIEW_PROPERTY(x, NSNumber, VeLiveMixerUIView) {
43
+ NSNumber *x = [RCTConvert NSNumber:json];
44
+ [view setX:x];
45
+ }
46
+
47
+ RCT_CUSTOM_VIEW_PROPERTY(y, NSNumber, VeLiveMixerUIView) {
48
+ NSNumber *y = [RCTConvert NSNumber:json];
49
+ [view setY:y];
50
+ }
51
+
52
+ RCT_CUSTOM_VIEW_PROPERTY(width, NSNumber, VeLiveMixerUIView) {
53
+ NSNumber *width = [RCTConvert NSNumber:json];
54
+ [view setWidth:width];
55
+ }
56
+
57
+ RCT_CUSTOM_VIEW_PROPERTY(height, NSNumber, VeLiveMixerUIView) {
58
+ NSNumber *height = [RCTConvert NSNumber:json];
59
+ [view setHeight:height];
60
+ }
61
+
62
+ RCT_CUSTOM_VIEW_PROPERTY(zOrder, NSNumber, VeLiveMixerUIView) {
63
+ NSNumber *zOrder = [RCTConvert NSNumber:json];
64
+ [view setZOrder:zOrder];
65
+ }
66
+
67
+ RCT_CUSTOM_VIEW_PROPERTY(renderMode, NSNumber, VeLiveMixerUIView) {
68
+ NSNumber *renderMode = [RCTConvert NSNumber:json];
69
+ [view setRenderMode:renderMode];
70
+ }
71
+
72
+ // 导出捕获配置属性
73
+ RCT_CUSTOM_VIEW_PROPERTY(captureMode, NSString, VeLiveMixerUIView) {
74
+ NSString *captureMode = [RCTConvert NSString:json];
75
+ if (captureMode != nil) {
76
+ [view setCaptureMode:captureMode];
77
+ } else {
78
+ [view setCaptureMode:@"onchange"];
79
+ }
80
+ }
81
+
82
+ RCT_CUSTOM_VIEW_PROPERTY(captureFramerate, NSNumber, VeLiveMixerUIView) {
83
+ NSNumber *framerate = [RCTConvert NSNumber:json];
84
+ [view setCaptureFramerate:framerate];
85
+ }
86
+
87
+ RCT_CUSTOM_VIEW_PROPERTY(autoSensitivity, NSNumber, VeLiveMixerUIView) {
88
+ NSNumber *sensitivity = [RCTConvert NSNumber:json];
89
+ [view setAutoSensitivity:sensitivity];
90
+ }
91
+
92
+ @end