@volcengine/react-native-live-push 1.1.3-rc.1 → 1.3.0-rc.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.
- package/android/build.gradle +2 -2
- package/android/src/main/java/com/volcengine/velive/rn/push/ClassHelper.java +9 -0
- package/android/src/main/java/com/volcengine/velive/rn/push/NativeVariableManager.java +5 -8
- package/android/src/main/java/com/volcengine/velive/rn/push/VeLivePushModule.java +14 -2
- package/android/src/main/java/com/volcengine/velive/rn/push/VeLivePushPackage.java +16 -13
- package/android/src/main/java/com/volcengine/velive/rn/push/VeLivePushView.java +16 -0
- package/android/src/main/java/com/volcengine/velive/rn/push/VeLivePushViewManager.java +7 -2
- package/android/src/main/java/com/volcengine/velive/rn/push/mixer/MixerManager.java +410 -0
- package/android/src/main/java/com/volcengine/velive/rn/push/mixer/MixerView.java +328 -0
- package/android/src/main/java/com/volcengine/velive/rn/push/mixer/MixerViewManager.java +43 -0
- package/android/src/main/java/com/volcengine/velive/rn/push/mixer/TextureMgr.java +168 -0
- package/android/src/main/java/com/volcengine/velive/rn/push/mixer/YuvHelper.java +165 -0
- package/ios/VeLiveMixerHelper.h +49 -0
- package/ios/VeLiveMixerHelper.m +646 -0
- package/ios/VeLiveMixerView.h +62 -0
- package/ios/VeLiveMixerView.m +547 -0
- package/ios/VeLiveMixerViewManager.m +56 -0
- package/lib/commonjs/index.js +22697 -20359
- package/lib/commonjs/typescript/android/index.d.ts +44 -0
- package/lib/commonjs/typescript/codegen/android/api.d.ts +1068 -0
- package/lib/commonjs/typescript/codegen/android/callback.d.ts +333 -0
- package/lib/commonjs/typescript/codegen/android/errorcode.d.ts +92 -0
- package/lib/commonjs/typescript/codegen/android/index.d.ts +5 -0
- package/lib/commonjs/typescript/codegen/android/keytype.d.ts +1693 -0
- package/lib/commonjs/typescript/codegen/android/types.d.ts +33 -0
- package/lib/commonjs/typescript/codegen/ios/api.d.ts +1125 -0
- package/lib/commonjs/typescript/codegen/ios/callback.d.ts +242 -0
- package/lib/commonjs/typescript/codegen/ios/errorcode.d.ts +154 -0
- package/lib/commonjs/typescript/codegen/ios/external.d.ts +1 -0
- package/lib/commonjs/typescript/codegen/ios/index.d.ts +6 -0
- package/lib/commonjs/typescript/codegen/ios/keytype.d.ts +1154 -0
- package/lib/commonjs/typescript/codegen/ios/types.d.ts +46 -0
- package/lib/commonjs/typescript/codegen/pack/api.d.ts +1470 -0
- package/lib/commonjs/typescript/codegen/pack/callback.d.ts +446 -0
- package/lib/commonjs/typescript/codegen/pack/errorcode.d.ts +109 -0
- package/lib/commonjs/typescript/codegen/pack/index.d.ts +5 -0
- package/lib/commonjs/typescript/codegen/pack/keytype.d.ts +2248 -0
- package/lib/commonjs/typescript/codegen/pack/types.d.ts +68 -0
- package/lib/commonjs/typescript/codegen/type-shim.d.ts +6 -0
- package/lib/commonjs/typescript/component.d.ts +15 -0
- package/lib/commonjs/typescript/core/api.d.ts +18 -0
- package/lib/commonjs/typescript/core/callback.d.ts +2 -0
- package/lib/commonjs/typescript/core/env.d.ts +29 -0
- package/lib/commonjs/typescript/core/errorcode.d.ts +2 -0
- package/lib/commonjs/typescript/core/index.d.ts +6 -0
- package/lib/commonjs/typescript/core/keytype.d.ts +17 -0
- package/lib/commonjs/typescript/core/mixer.d.ts +26 -0
- package/lib/commonjs/typescript/core/pusher.d.ts +13 -0
- package/lib/commonjs/typescript/index.d.ts +3 -0
- package/lib/commonjs/typescript/ios/extends.d.ts +41 -0
- package/lib/commonjs/typescript/platforms/android/extends.d.ts +8 -0
- package/lib/commonjs/typescript/platforms/android/helper.d.ts +8 -0
- package/lib/commonjs/typescript/platforms/android/mixer.d.ts +8 -0
- package/lib/commonjs/typescript/platforms/ios/extends.d.ts +17 -0
- package/lib/commonjs/typescript/platforms/ios/helper.d.ts +8 -0
- package/lib/commonjs/typescript/platforms/ios/mixer.d.ts +9 -0
- package/lib/commonjs/typescript/runtime.d.ts +1 -0
- package/lib/commonjs/typescript/view/MixView.d.ts +44 -0
- package/lib/commonjs/typescript/view/VeImageView.d.ts +19 -0
- package/lib/commonjs/typescript/view/VeTextView.d.ts +7 -0
- package/lib/commonjs/typescript/view/VeView.d.ts +7 -0
- package/lib/commonjs/typescript/view/VeWebView.d.ts +7 -0
- package/lib/commonjs/typescript/view/index.d.ts +5 -0
- package/lib/module/index.js +22694 -20360
- package/lib/module/typescript/android/index.d.ts +44 -0
- package/lib/module/typescript/codegen/android/api.d.ts +1068 -0
- package/lib/module/typescript/codegen/android/callback.d.ts +333 -0
- package/lib/module/typescript/codegen/android/errorcode.d.ts +92 -0
- package/lib/module/typescript/codegen/android/index.d.ts +5 -0
- package/lib/module/typescript/codegen/android/keytype.d.ts +1693 -0
- package/lib/module/typescript/codegen/android/types.d.ts +33 -0
- package/lib/module/typescript/codegen/ios/api.d.ts +1125 -0
- package/lib/module/typescript/codegen/ios/callback.d.ts +242 -0
- package/lib/module/typescript/codegen/ios/errorcode.d.ts +154 -0
- package/lib/module/typescript/codegen/ios/external.d.ts +1 -0
- package/lib/module/typescript/codegen/ios/index.d.ts +6 -0
- package/lib/module/typescript/codegen/ios/keytype.d.ts +1154 -0
- package/lib/module/typescript/codegen/ios/types.d.ts +46 -0
- package/lib/module/typescript/codegen/pack/api.d.ts +1470 -0
- package/lib/module/typescript/codegen/pack/callback.d.ts +446 -0
- package/lib/module/typescript/codegen/pack/errorcode.d.ts +109 -0
- package/lib/module/typescript/codegen/pack/index.d.ts +5 -0
- package/lib/module/typescript/codegen/pack/keytype.d.ts +2248 -0
- package/lib/module/typescript/codegen/pack/types.d.ts +68 -0
- package/lib/module/typescript/codegen/type-shim.d.ts +6 -0
- package/lib/module/typescript/component.d.ts +15 -0
- package/lib/module/typescript/core/api.d.ts +18 -0
- package/lib/module/typescript/core/callback.d.ts +2 -0
- package/lib/module/typescript/core/env.d.ts +29 -0
- package/lib/module/typescript/core/errorcode.d.ts +2 -0
- package/lib/module/typescript/core/index.d.ts +6 -0
- package/lib/module/typescript/core/keytype.d.ts +17 -0
- package/lib/module/typescript/core/mixer.d.ts +26 -0
- package/lib/module/typescript/core/pusher.d.ts +13 -0
- package/lib/module/typescript/index.d.ts +3 -0
- package/lib/module/typescript/ios/extends.d.ts +41 -0
- package/lib/module/typescript/platforms/android/extends.d.ts +8 -0
- package/lib/module/typescript/platforms/android/helper.d.ts +8 -0
- package/lib/module/typescript/platforms/android/mixer.d.ts +8 -0
- package/lib/module/typescript/platforms/ios/extends.d.ts +17 -0
- package/lib/module/typescript/platforms/ios/helper.d.ts +8 -0
- package/lib/module/typescript/platforms/ios/mixer.d.ts +9 -0
- package/lib/module/typescript/runtime.d.ts +1 -0
- package/lib/module/typescript/view/MixView.d.ts +44 -0
- package/lib/module/typescript/view/VeImageView.d.ts +19 -0
- package/lib/module/typescript/view/VeTextView.d.ts +7 -0
- package/lib/module/typescript/view/VeView.d.ts +7 -0
- package/lib/module/typescript/view/VeWebView.d.ts +7 -0
- package/lib/module/typescript/view/index.d.ts +5 -0
- package/lib/typescript/android/index.d.ts +0 -3
- package/lib/typescript/codegen/android/api.d.ts +194 -762
- package/lib/typescript/codegen/android/callback.d.ts +85 -48
- package/lib/typescript/codegen/android/errorcode.d.ts +30 -0
- package/lib/typescript/codegen/android/keytype.d.ts +514 -122
- package/lib/typescript/codegen/ios/api.d.ts +380 -351
- package/lib/typescript/codegen/ios/callback.d.ts +33 -6
- package/lib/typescript/codegen/ios/errorcode.d.ts +52 -2
- package/lib/typescript/codegen/ios/keytype.d.ts +313 -35
- package/lib/typescript/codegen/pack/api.d.ts +302 -821
- package/lib/typescript/codegen/pack/callback.d.ts +54 -49
- package/lib/typescript/codegen/pack/errorcode.d.ts +38 -5
- package/lib/typescript/codegen/pack/keytype.d.ts +672 -228
- package/lib/typescript/core/api.d.ts +18 -2
- package/lib/typescript/core/keytype.d.ts +15 -0
- package/lib/typescript/core/mixer.d.ts +26 -0
- package/lib/typescript/core/pusher.d.ts +0 -3
- package/lib/typescript/index.d.ts +1 -0
- package/lib/typescript/platforms/android/extends.d.ts +8 -0
- package/lib/typescript/platforms/android/mixer.d.ts +8 -0
- package/lib/typescript/platforms/ios/mixer.d.ts +9 -0
- package/lib/typescript/view/MixView.d.ts +44 -0
- package/lib/typescript/view/VeImageView.d.ts +19 -0
- package/lib/typescript/view/VeTextView.d.ts +7 -0
- package/lib/typescript/view/VeView.d.ts +7 -0
- package/lib/typescript/view/VeWebView.d.ts +7 -0
- package/lib/typescript/view/index.d.ts +5 -0
- package/package.json +1 -1
- package/react-native-velive-push.podspec +3 -3
- package/android/src/main/java/com/volcengine/velive/rn/push/ScreenCaptureHelper.java +0 -73
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
//
|
|
2
|
+
// VeLiveMixerView.h
|
|
3
|
+
// react-native-live-push
|
|
4
|
+
//
|
|
5
|
+
// Created by ByteDance on 2025/06/09.
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
#import <Foundation/Foundation.h>
|
|
9
|
+
#import <UIKit/UIKit.h>
|
|
10
|
+
#import <React/RCTComponent.h>
|
|
11
|
+
#import <CoreVideo/CoreVideo.h>
|
|
12
|
+
#import <CoreMedia/CoreMedia.h>
|
|
13
|
+
|
|
14
|
+
NS_ASSUME_NONNULL_BEGIN
|
|
15
|
+
|
|
16
|
+
// Bitmap捕获回调协议 - 对应Android的BitmapCaptureCallback
|
|
17
|
+
@protocol VeLiveBitmapCaptureCallback <NSObject>
|
|
18
|
+
- (void)onBitmapCaptured:(UIImage *)bitmap;
|
|
19
|
+
- (void)onCaptureError:(NSString *)error;
|
|
20
|
+
@end
|
|
21
|
+
|
|
22
|
+
// MixerView代理协议
|
|
23
|
+
@protocol VeLiveMixerViewDelegate <NSObject>
|
|
24
|
+
- (void)onBitmapCaptured:(UIImage *)image viewId:(NSString *)viewId;
|
|
25
|
+
- (void)onCaptureError:(NSString *)error viewId:(NSString *)viewId;
|
|
26
|
+
@end
|
|
27
|
+
|
|
28
|
+
@interface VeLiveMixerUIView : UIView
|
|
29
|
+
|
|
30
|
+
// 视图标识
|
|
31
|
+
@property (nonatomic, strong) NSString *viewId;
|
|
32
|
+
|
|
33
|
+
// 捕获配置属性
|
|
34
|
+
@property (nonatomic, strong) NSString *captureMode;
|
|
35
|
+
@property (nonatomic, strong) NSNumber *captureFramerate;
|
|
36
|
+
|
|
37
|
+
// React Native 事件回调
|
|
38
|
+
@property (nonatomic, copy) RCTBubblingEventBlock onBitmapCapture;
|
|
39
|
+
@property (nonatomic, copy) RCTBubblingEventBlock onCaptureError;
|
|
40
|
+
|
|
41
|
+
// 代理
|
|
42
|
+
@property (nonatomic, weak) id<VeLiveMixerViewDelegate> delegate;
|
|
43
|
+
|
|
44
|
+
// Bitmap捕获回调 - 用于与MixerManager集成
|
|
45
|
+
@property (nonatomic, weak) id<VeLiveBitmapCaptureCallback> bitmapCaptureCallback;
|
|
46
|
+
|
|
47
|
+
// 控制方法
|
|
48
|
+
- (void)startCapture;
|
|
49
|
+
- (void)stopCapture;
|
|
50
|
+
- (void)performCaptureWithTrigger:(NSString *)trigger;
|
|
51
|
+
|
|
52
|
+
// 设置bitmap捕获回调
|
|
53
|
+
- (void)setBitmapCaptureCallback:(id<VeLiveBitmapCaptureCallback>)callback;
|
|
54
|
+
|
|
55
|
+
// UIView转换方法
|
|
56
|
+
- (UIImage *)snapshotIncludeSubviews;
|
|
57
|
+
- (CVPixelBufferRef)createPixelBuffer;
|
|
58
|
+
- (CMSampleBufferRef)createSampleBuffer;
|
|
59
|
+
|
|
60
|
+
@end
|
|
61
|
+
|
|
62
|
+
NS_ASSUME_NONNULL_END
|
|
@@ -0,0 +1,547 @@
|
|
|
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 <AVFoundation/AVFoundation.h>
|
|
11
|
+
#import <CoreImage/CoreImage.h>
|
|
12
|
+
#import <CoreMedia/CoreMedia.h>
|
|
13
|
+
#import <CoreVideo/CoreVideo.h>
|
|
14
|
+
#import <UIKit/UIKit.h>
|
|
15
|
+
|
|
16
|
+
@interface VeLiveMixerUIView ()
|
|
17
|
+
|
|
18
|
+
// Capture configuration - Internal properties
|
|
19
|
+
@property(nonatomic, strong) NSString *internalCaptureMode;
|
|
20
|
+
@property(nonatomic, assign) float internalCaptureFramerate;
|
|
21
|
+
|
|
22
|
+
// Capture state - Align with Android
|
|
23
|
+
@property(nonatomic, strong) NSTimer *captureTimer;
|
|
24
|
+
@property(nonatomic, assign) BOOL isCapturing;
|
|
25
|
+
@property(nonatomic, assign) BOOL isDestroyed;
|
|
26
|
+
@property(nonatomic, assign) CFTimeInterval lastCaptureTime;
|
|
27
|
+
|
|
28
|
+
// Performance optimization - Align with Android
|
|
29
|
+
@property(nonatomic) CVPixelBufferRef lastPixelBuffer;
|
|
30
|
+
|
|
31
|
+
@end
|
|
32
|
+
|
|
33
|
+
@implementation VeLiveMixerUIView
|
|
34
|
+
|
|
35
|
+
- (instancetype)initWithFrame:(CGRect)frame {
|
|
36
|
+
self = [super initWithFrame:frame];
|
|
37
|
+
if (self) {
|
|
38
|
+
[self initializeCapture];
|
|
39
|
+
[self setupUIView];
|
|
40
|
+
}
|
|
41
|
+
return self;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
- (void)setupUIView {
|
|
45
|
+
// Reference VeLivePushView.m settings, but keep our mixed view features
|
|
46
|
+
self.clipsToBounds = YES; // Changed to YES, consistent with VeLivePushView
|
|
47
|
+
self.userInteractionEnabled = YES;
|
|
48
|
+
|
|
49
|
+
// Set default background color to transparent, allowing subviews to display
|
|
50
|
+
self.backgroundColor = [UIColor clearColor];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
- (void)initializeCapture {
|
|
54
|
+
// Initialize defaults - Completely align with Android
|
|
55
|
+
_internalCaptureMode = @"onchange";
|
|
56
|
+
_internalCaptureFramerate = 5.0f; // realTime mode default 5 fps
|
|
57
|
+
|
|
58
|
+
_isCapturing = NO;
|
|
59
|
+
_isDestroyed = NO;
|
|
60
|
+
_lastCaptureTime = 0;
|
|
61
|
+
_lastPixelBuffer = NULL;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
#pragma mark - Property Getters/Setters - Align with React Native props
|
|
65
|
+
|
|
66
|
+
- (NSString *)captureMode {
|
|
67
|
+
return self.internalCaptureMode;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
- (void)setCaptureMode:(NSString *)mode {
|
|
71
|
+
if ([mode isEqualToString:self.internalCaptureMode])
|
|
72
|
+
return;
|
|
73
|
+
|
|
74
|
+
[self stopCapture];
|
|
75
|
+
_internalCaptureMode = mode;
|
|
76
|
+
[self startCapture];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
- (NSNumber *)captureFramerate {
|
|
80
|
+
return @(self.internalCaptureFramerate);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
- (void)setCaptureFramerate:(NSNumber *)framerate {
|
|
84
|
+
_internalCaptureFramerate = MAX(1.0f, MIN(60.0f, [framerate floatValue]));
|
|
85
|
+
if ([self.internalCaptureMode isEqualToString:@"realtime"]) {
|
|
86
|
+
[self restartRealtimeCapture];
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
- (void)setViewId:(NSString *)viewId {
|
|
91
|
+
if (!viewId) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Set viewId
|
|
96
|
+
_viewId = [viewId copy];
|
|
97
|
+
|
|
98
|
+
// Register to global cache - Align with Android's onAttachedToWindow
|
|
99
|
+
NSMutableDictionary *cachedViews = [VeLiveMixerHelper cachedMixedViews];
|
|
100
|
+
if (cachedViews) {
|
|
101
|
+
[cachedViews setObject:self forKey:viewId];
|
|
102
|
+
|
|
103
|
+
// Notify MixerHelper view is ready, check for pending callbacks
|
|
104
|
+
[VeLiveMixerHelper onViewReady:viewId mixerView:self];
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
#pragma mark - Capture Methods - Completely align with Android
|
|
109
|
+
|
|
110
|
+
- (void)startCapture {
|
|
111
|
+
if (self.isDestroyed)
|
|
112
|
+
return;
|
|
113
|
+
|
|
114
|
+
if ([self.internalCaptureMode isEqualToString:@"onchange"]) {
|
|
115
|
+
[self startOnChangeCapture];
|
|
116
|
+
} else if ([self.internalCaptureMode isEqualToString:@"realtime"]) {
|
|
117
|
+
[self startRealtimeCapture];
|
|
118
|
+
}
|
|
119
|
+
// manual mode doesn't auto-start
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
- (void)stopCapture {
|
|
123
|
+
[self.captureTimer invalidate];
|
|
124
|
+
self.captureTimer = nil;
|
|
125
|
+
self.isCapturing = NO;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
- (void)startOnChangeCapture {
|
|
129
|
+
// onchange mode triggers every 2 seconds
|
|
130
|
+
self.captureTimer =
|
|
131
|
+
[NSTimer scheduledTimerWithTimeInterval:2.0
|
|
132
|
+
target:self
|
|
133
|
+
selector:@selector(onChangeCapture)
|
|
134
|
+
userInfo:nil
|
|
135
|
+
repeats:YES];
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
- (void)onChangeCapture {
|
|
139
|
+
if (!self.isDestroyed &&
|
|
140
|
+
[self.internalCaptureMode isEqualToString:@"onchange"]) {
|
|
141
|
+
@autoreleasepool {
|
|
142
|
+
[self performCapture:@"onchange"];
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
- (void)startRealtimeCapture {
|
|
148
|
+
long interval = (long)(1000.0f / self.internalCaptureFramerate);
|
|
149
|
+
self.captureTimer =
|
|
150
|
+
[NSTimer scheduledTimerWithTimeInterval:interval / 1000.0
|
|
151
|
+
target:self
|
|
152
|
+
selector:@selector(realtimeCapture)
|
|
153
|
+
userInfo:nil
|
|
154
|
+
repeats:YES];
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
- (void)realtimeCapture {
|
|
158
|
+
if (!self.isDestroyed &&
|
|
159
|
+
[self.internalCaptureMode isEqualToString:@"realtime"]) {
|
|
160
|
+
@autoreleasepool {
|
|
161
|
+
[self performCapture:@"realtime"];
|
|
162
|
+
|
|
163
|
+
// Every 5 realtime captures, perform background memory cleanup
|
|
164
|
+
static int captureCount = 0;
|
|
165
|
+
captureCount++;
|
|
166
|
+
if (captureCount % 5 == 0) {
|
|
167
|
+
dispatch_async(
|
|
168
|
+
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
|
|
169
|
+
[[NSURLCache sharedURLCache] removeAllCachedResponses];
|
|
170
|
+
[[NSURLCache sharedURLCache] setMemoryCapacity:0];
|
|
171
|
+
[[NSURLCache sharedURLCache]
|
|
172
|
+
setMemoryCapacity:10 * 1024 * 1024]; // 10MB
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
- (void)restartRealtimeCapture {
|
|
180
|
+
if ([self.internalCaptureMode isEqualToString:@"realtime"]) {
|
|
181
|
+
[self stopCapture];
|
|
182
|
+
[self startRealtimeCapture];
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
#pragma mark - Core Capture Logic - Align with Android's performCapture
|
|
187
|
+
|
|
188
|
+
- (void)performCapture:(NSString *)trigger {
|
|
189
|
+
if (self.isCapturing || self.isDestroyed)
|
|
190
|
+
return;
|
|
191
|
+
|
|
192
|
+
// Throttle captures to prevent excessive calls
|
|
193
|
+
CFTimeInterval currentTime = CACurrentMediaTime();
|
|
194
|
+
if (currentTime - self.lastCaptureTime < 1.0 / 30.0) { // Max 30fps
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
self.lastCaptureTime = currentTime;
|
|
198
|
+
|
|
199
|
+
self.isCapturing = YES;
|
|
200
|
+
|
|
201
|
+
@try {
|
|
202
|
+
CVPixelBufferRef pixelBuffer = [self createPixelBuffer];
|
|
203
|
+
if (pixelBuffer) {
|
|
204
|
+
[self processPixelBuffer:pixelBuffer trigger:trigger];
|
|
205
|
+
CVPixelBufferRelease(pixelBuffer);
|
|
206
|
+
}
|
|
207
|
+
} @finally {
|
|
208
|
+
self.isCapturing = NO;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
- (UIImage *)createBitmap {
|
|
213
|
+
CGSize viewSize = self.bounds.size;
|
|
214
|
+
|
|
215
|
+
// Check for valid bounds
|
|
216
|
+
if (viewSize.width <= 0 || viewSize.height <= 0) {
|
|
217
|
+
return nil;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Limit maximum image dimensions to prevent memory issues
|
|
221
|
+
CGFloat maxDimension = 1920.0; // Max 1920px on either side
|
|
222
|
+
CGFloat scale = [UIScreen mainScreen].scale;
|
|
223
|
+
|
|
224
|
+
// Calculate actual pixel dimensions
|
|
225
|
+
CGFloat pixelWidth = viewSize.width * scale;
|
|
226
|
+
CGFloat pixelHeight = viewSize.height * scale;
|
|
227
|
+
|
|
228
|
+
// Scale down if too large
|
|
229
|
+
if (pixelWidth > maxDimension || pixelHeight > maxDimension) {
|
|
230
|
+
CGFloat scaleDown =
|
|
231
|
+
MIN(maxDimension / pixelWidth, maxDimension / pixelHeight);
|
|
232
|
+
scale = scale * scaleDown;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Further limit scale on very large views
|
|
236
|
+
if (viewSize.width * viewSize.height > 1000000) { // > 1M points
|
|
237
|
+
scale = MIN(scale, 1.0);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
@autoreleasepool {
|
|
241
|
+
UIGraphicsBeginImageContextWithOptions(viewSize, NO, scale);
|
|
242
|
+
|
|
243
|
+
CGContextRef context = UIGraphicsGetCurrentContext();
|
|
244
|
+
if (!context) {
|
|
245
|
+
UIGraphicsEndImageContext();
|
|
246
|
+
return nil;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
[self drawViewHierarchyInRect:self.bounds afterScreenUpdates:NO];
|
|
250
|
+
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
|
|
251
|
+
UIGraphicsEndImageContext();
|
|
252
|
+
|
|
253
|
+
return image;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
#pragma mark - Public Methods - Align with Android interface
|
|
258
|
+
|
|
259
|
+
// Align with Android's performManualCapture
|
|
260
|
+
- (void)performCaptureWithTrigger:(NSString *)trigger {
|
|
261
|
+
[self performCapture:trigger];
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Align with Android's setBitmapCaptureCallback
|
|
265
|
+
- (void)setBitmapCaptureCallback:(id<VeLiveBitmapCaptureCallback>)callback {
|
|
266
|
+
_bitmapCaptureCallback = callback;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
#pragma mark - Cleanup - Align with Android
|
|
270
|
+
|
|
271
|
+
- (void)cleanup {
|
|
272
|
+
self.isDestroyed = YES;
|
|
273
|
+
[self stopCapture];
|
|
274
|
+
|
|
275
|
+
// Clean up pixelBuffer cache
|
|
276
|
+
if (self.lastPixelBuffer) {
|
|
277
|
+
CVPixelBufferRelease(self.lastPixelBuffer);
|
|
278
|
+
self.lastPixelBuffer = NULL;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Clean up callback references, avoid circular references
|
|
282
|
+
self.bitmapCaptureCallback = nil;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
- (void)dealloc {
|
|
286
|
+
// Remove from global cache
|
|
287
|
+
if (self.viewId) {
|
|
288
|
+
NSMutableDictionary *cachedViews = [VeLiveMixerHelper cachedMixedViews];
|
|
289
|
+
if (cachedViews) {
|
|
290
|
+
[cachedViews removeObjectForKey:self.viewId];
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
#pragma mark - View Conversion Methods
|
|
296
|
+
|
|
297
|
+
- (UIImage *)snapshotIncludeSubviews {
|
|
298
|
+
CGSize viewSize = self.bounds.size;
|
|
299
|
+
|
|
300
|
+
// Check for valid bounds
|
|
301
|
+
if (viewSize.width <= 0 || viewSize.height <= 0) {
|
|
302
|
+
return nil;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Limit maximum image dimensions to prevent memory issues
|
|
306
|
+
CGFloat maxDimension = 1920.0; // Max 1920px on either side
|
|
307
|
+
CGFloat scale = [UIScreen mainScreen].scale;
|
|
308
|
+
|
|
309
|
+
// Calculate actual pixel dimensions
|
|
310
|
+
CGFloat pixelWidth = viewSize.width * scale;
|
|
311
|
+
CGFloat pixelHeight = viewSize.height * scale;
|
|
312
|
+
|
|
313
|
+
// Scale down if too large
|
|
314
|
+
if (pixelWidth > maxDimension || pixelHeight > maxDimension) {
|
|
315
|
+
CGFloat scaleDown =
|
|
316
|
+
MIN(maxDimension / pixelWidth, maxDimension / pixelHeight);
|
|
317
|
+
scale = scale * scaleDown;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Further limit scale on very large views
|
|
321
|
+
if (viewSize.width * viewSize.height > 1000000) { // > 1M points
|
|
322
|
+
scale = MIN(scale, 1.0);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
@autoreleasepool {
|
|
326
|
+
UIGraphicsBeginImageContextWithOptions(viewSize, NO, scale);
|
|
327
|
+
|
|
328
|
+
CGContextRef context = UIGraphicsGetCurrentContext();
|
|
329
|
+
if (!context) {
|
|
330
|
+
UIGraphicsEndImageContext();
|
|
331
|
+
return nil;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
[self drawViewHierarchyInRect:self.bounds afterScreenUpdates:NO];
|
|
335
|
+
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
|
|
336
|
+
UIGraphicsEndImageContext();
|
|
337
|
+
|
|
338
|
+
return image;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
- (CVPixelBufferRef)createPixelBuffer {
|
|
343
|
+
UIImage *bitmap = [self createBitmap];
|
|
344
|
+
if (!bitmap) {
|
|
345
|
+
return NULL;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
return [self pixelBufferFromUIImage:bitmap];
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
- (CMSampleBufferRef)createSampleBuffer {
|
|
352
|
+
// Implement sample buffer creation (corresponds to Android's VideoFrame)
|
|
353
|
+
return NULL; // Temporary return NULL, implementation based on needs
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
#pragma mark - React Native Child View Support - Focus on RN subview management
|
|
357
|
+
|
|
358
|
+
// React Native subview insertion - This is the main method called by RN
|
|
359
|
+
// framework
|
|
360
|
+
- (void)insertReactSubview:(UIView *)subview atIndex:(NSInteger)atIndex {
|
|
361
|
+
// Process directly on main thread because React Native ensures thread safety
|
|
362
|
+
[super insertSubview:subview atIndex:atIndex];
|
|
363
|
+
[self setSubViewAutoLayout:subview];
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
- (void)removeReactSubview:(UIView *)subview {
|
|
367
|
+
[subview removeFromSuperview];
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
- (NSArray<UIView *> *)reactSubviews {
|
|
371
|
+
return [self subviews];
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// Set Auto Layout constraints - Ensure subview fills parent view
|
|
375
|
+
- (void)setSubViewAutoLayout:(UIView *)subview {
|
|
376
|
+
// Disable automatic conversion, use constraints layout
|
|
377
|
+
subview.translatesAutoresizingMaskIntoConstraints = NO;
|
|
378
|
+
|
|
379
|
+
// Set four-side constraints, let subview fill parent view
|
|
380
|
+
[NSLayoutConstraint activateConstraints:@[
|
|
381
|
+
[subview.leadingAnchor constraintEqualToAnchor:self.leadingAnchor],
|
|
382
|
+
[subview.trailingAnchor constraintEqualToAnchor:self.trailingAnchor],
|
|
383
|
+
[subview.topAnchor constraintEqualToAnchor:self.topAnchor],
|
|
384
|
+
[subview.bottomAnchor constraintEqualToAnchor:self.bottomAnchor]
|
|
385
|
+
]];
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// Override layoutSubviews to ensure correct subview layout
|
|
389
|
+
- (void)layoutSubviews {
|
|
390
|
+
[super layoutSubviews];
|
|
391
|
+
if (self.isDestroyed)
|
|
392
|
+
return;
|
|
393
|
+
|
|
394
|
+
// Original capture logic
|
|
395
|
+
if ([self.internalCaptureMode isEqualToString:@"onchange"]) {
|
|
396
|
+
[self performCapture:@"onchange"];
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// Ensure UIView can correctly display background color and subviews
|
|
401
|
+
- (void)setBackgroundColor:(UIColor *)backgroundColor {
|
|
402
|
+
[super setBackgroundColor:backgroundColor];
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
#pragma mark - Helper Methods
|
|
406
|
+
|
|
407
|
+
- (CVPixelBufferRef)pixelBufferFromUIImage:(UIImage *)image {
|
|
408
|
+
if (!image) {
|
|
409
|
+
return NULL;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
CGImageRef cgImage = image.CGImage;
|
|
413
|
+
if (!cgImage) {
|
|
414
|
+
return NULL;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
CGFloat width = CGImageGetWidth(cgImage);
|
|
418
|
+
CGFloat height = CGImageGetHeight(cgImage);
|
|
419
|
+
|
|
420
|
+
NSDictionary *options = @{
|
|
421
|
+
(NSString *)kCVPixelBufferCGImageCompatibilityKey : @YES,
|
|
422
|
+
(NSString *)kCVPixelBufferCGBitmapContextCompatibilityKey : @YES,
|
|
423
|
+
(NSString *)kCVPixelBufferIOSurfacePropertiesKey : @{}
|
|
424
|
+
};
|
|
425
|
+
|
|
426
|
+
CVPixelBufferRef pixelBuffer = NULL;
|
|
427
|
+
CVReturn status = CVPixelBufferCreate(
|
|
428
|
+
kCFAllocatorDefault, width, height, kCVPixelFormatType_32BGRA,
|
|
429
|
+
(__bridge CFDictionaryRef)options, &pixelBuffer);
|
|
430
|
+
|
|
431
|
+
if (status != kCVReturnSuccess || !pixelBuffer) {
|
|
432
|
+
return NULL;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
CVPixelBufferLockBaseAddress(pixelBuffer, 0);
|
|
436
|
+
|
|
437
|
+
void *pixelData = CVPixelBufferGetBaseAddress(pixelBuffer);
|
|
438
|
+
size_t bytesPerRow = CVPixelBufferGetBytesPerRow(pixelBuffer);
|
|
439
|
+
|
|
440
|
+
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
|
|
441
|
+
CGContextRef context = CGBitmapContextCreate(
|
|
442
|
+
pixelData, width, height, 8, bytesPerRow, colorSpace,
|
|
443
|
+
kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host);
|
|
444
|
+
|
|
445
|
+
if (!context) {
|
|
446
|
+
CVPixelBufferUnlockBaseAddress(pixelBuffer, 0);
|
|
447
|
+
CVPixelBufferRelease(pixelBuffer);
|
|
448
|
+
CGColorSpaceRelease(colorSpace);
|
|
449
|
+
return NULL;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
CGContextDrawImage(context, CGRectMake(0, 0, width, height), cgImage);
|
|
453
|
+
|
|
454
|
+
CGContextRelease(context);
|
|
455
|
+
CGColorSpaceRelease(colorSpace);
|
|
456
|
+
CVPixelBufferUnlockBaseAddress(pixelBuffer, 0);
|
|
457
|
+
|
|
458
|
+
return pixelBuffer;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
- (void)processPixelBuffer:(CVPixelBufferRef)pixelBuffer
|
|
462
|
+
trigger:(NSString *)trigger {
|
|
463
|
+
@autoreleasepool {
|
|
464
|
+
if (!pixelBuffer) {
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// Clean up old pixelBuffer cache
|
|
469
|
+
if (self.lastPixelBuffer) {
|
|
470
|
+
CVPixelBufferRelease(self.lastPixelBuffer);
|
|
471
|
+
self.lastPixelBuffer = NULL;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// Cache pixelBuffer for non-realtime modes
|
|
475
|
+
if (![trigger isEqualToString:@"realtime"]) {
|
|
476
|
+
// Retain current pixelBuffer as cache
|
|
477
|
+
CVPixelBufferRetain(pixelBuffer);
|
|
478
|
+
self.lastPixelBuffer = pixelBuffer;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// If callback is needed, create temporary UIImage
|
|
482
|
+
if (self.bitmapCaptureCallback || self.onBitmapCapture) {
|
|
483
|
+
UIImage *bitmap = [self UIImageFromPixelBuffer:pixelBuffer];
|
|
484
|
+
if (bitmap) {
|
|
485
|
+
if (self.bitmapCaptureCallback) {
|
|
486
|
+
@try {
|
|
487
|
+
[self.bitmapCaptureCallback onBitmapCaptured:bitmap];
|
|
488
|
+
} @catch (NSException *exception) {
|
|
489
|
+
if (self.bitmapCaptureCallback) {
|
|
490
|
+
[self.bitmapCaptureCallback
|
|
491
|
+
onCaptureError:[NSString
|
|
492
|
+
stringWithFormat:@"Callback error: %@",
|
|
493
|
+
exception.reason]];
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
if (self.onBitmapCapture) {
|
|
499
|
+
@autoreleasepool {
|
|
500
|
+
NSDictionary *event = @{
|
|
501
|
+
@"trigger" : trigger ?: @"unknown",
|
|
502
|
+
@"success" : @(YES),
|
|
503
|
+
@"bitmapWidth" : @(bitmap.size.width),
|
|
504
|
+
@"bitmapHeight" : @(bitmap.size.height)
|
|
505
|
+
};
|
|
506
|
+
self.onBitmapCapture(event);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// Memory cleanup for realtime mode
|
|
513
|
+
if ([trigger isEqualToString:@"realtime"]) {
|
|
514
|
+
static int realtimeCount = 0;
|
|
515
|
+
realtimeCount++;
|
|
516
|
+
if (realtimeCount % 10 == 0) {
|
|
517
|
+
dispatch_async(
|
|
518
|
+
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
|
|
519
|
+
@autoreleasepool {
|
|
520
|
+
[[NSURLCache sharedURLCache] removeAllCachedResponses];
|
|
521
|
+
}
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
- (UIImage *)UIImageFromPixelBuffer:(CVPixelBufferRef)pixelBuffer {
|
|
529
|
+
if (!pixelBuffer) {
|
|
530
|
+
return nil;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
CIImage *ciImage = [CIImage imageWithCVPixelBuffer:pixelBuffer];
|
|
534
|
+
CIContext *context = [CIContext contextWithOptions:nil];
|
|
535
|
+
CGImageRef cgImage = [context createCGImage:ciImage fromRect:ciImage.extent];
|
|
536
|
+
|
|
537
|
+
if (!cgImage) {
|
|
538
|
+
return nil;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
UIImage *image = [UIImage imageWithCGImage:cgImage];
|
|
542
|
+
CGImageRelease(cgImage);
|
|
543
|
+
|
|
544
|
+
return image;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
@end
|
|
@@ -0,0 +1,56 @@
|
|
|
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
|
+
// 导出捕获配置属性
|
|
42
|
+
RCT_CUSTOM_VIEW_PROPERTY(captureMode, NSString, VeLiveMixerUIView) {
|
|
43
|
+
NSString *captureMode = [RCTConvert NSString:json];
|
|
44
|
+
if (captureMode != nil) {
|
|
45
|
+
[view setCaptureMode:captureMode];
|
|
46
|
+
} else {
|
|
47
|
+
[view setCaptureMode:@"onchange"];
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
RCT_CUSTOM_VIEW_PROPERTY(captureFramerate, NSNumber, VeLiveMixerUIView) {
|
|
52
|
+
NSNumber *framerate = [RCTConvert NSNumber:json];
|
|
53
|
+
[view setCaptureFramerate:framerate];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
@end
|