@rejourneyco/react-native 1.0.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.kts +135 -0
- package/android/consumer-rules.pro +10 -0
- package/android/proguard-rules.pro +1 -0
- package/android/src/main/AndroidManifest.xml +15 -0
- package/android/src/main/java/com/rejourney/RejourneyModuleImpl.kt +2981 -0
- package/android/src/main/java/com/rejourney/capture/ANRHandler.kt +206 -0
- package/android/src/main/java/com/rejourney/capture/ActivityTracker.kt +98 -0
- package/android/src/main/java/com/rejourney/capture/CaptureEngine.kt +1553 -0
- package/android/src/main/java/com/rejourney/capture/CaptureHeuristics.kt +375 -0
- package/android/src/main/java/com/rejourney/capture/CrashHandler.kt +153 -0
- package/android/src/main/java/com/rejourney/capture/MotionEvent.kt +215 -0
- package/android/src/main/java/com/rejourney/capture/SegmentUploader.kt +512 -0
- package/android/src/main/java/com/rejourney/capture/VideoEncoder.kt +773 -0
- package/android/src/main/java/com/rejourney/capture/ViewHierarchyScanner.kt +633 -0
- package/android/src/main/java/com/rejourney/capture/ViewSerializer.kt +286 -0
- package/android/src/main/java/com/rejourney/core/Constants.kt +117 -0
- package/android/src/main/java/com/rejourney/core/Logger.kt +93 -0
- package/android/src/main/java/com/rejourney/core/Types.kt +124 -0
- package/android/src/main/java/com/rejourney/lifecycle/SessionLifecycleService.kt +162 -0
- package/android/src/main/java/com/rejourney/network/DeviceAuthManager.kt +747 -0
- package/android/src/main/java/com/rejourney/network/HttpClientProvider.kt +16 -0
- package/android/src/main/java/com/rejourney/network/NetworkMonitor.kt +272 -0
- package/android/src/main/java/com/rejourney/network/UploadManager.kt +1363 -0
- package/android/src/main/java/com/rejourney/network/UploadWorker.kt +492 -0
- package/android/src/main/java/com/rejourney/privacy/PrivacyMask.kt +645 -0
- package/android/src/main/java/com/rejourney/touch/GestureClassifier.kt +233 -0
- package/android/src/main/java/com/rejourney/touch/KeyboardTracker.kt +158 -0
- package/android/src/main/java/com/rejourney/touch/TextInputTracker.kt +181 -0
- package/android/src/main/java/com/rejourney/touch/TouchInterceptor.kt +591 -0
- package/android/src/main/java/com/rejourney/utils/EventBuffer.kt +284 -0
- package/android/src/main/java/com/rejourney/utils/OEMDetector.kt +154 -0
- package/android/src/main/java/com/rejourney/utils/PerfTiming.kt +235 -0
- package/android/src/main/java/com/rejourney/utils/Telemetry.kt +297 -0
- package/android/src/main/java/com/rejourney/utils/WindowUtils.kt +84 -0
- package/android/src/newarch/java/com/rejourney/RejourneyModule.kt +187 -0
- package/android/src/newarch/java/com/rejourney/RejourneyPackage.kt +40 -0
- package/android/src/oldarch/java/com/rejourney/RejourneyModule.kt +218 -0
- package/android/src/oldarch/java/com/rejourney/RejourneyPackage.kt +23 -0
- package/ios/Capture/RJANRHandler.h +42 -0
- package/ios/Capture/RJANRHandler.m +328 -0
- package/ios/Capture/RJCaptureEngine.h +275 -0
- package/ios/Capture/RJCaptureEngine.m +2062 -0
- package/ios/Capture/RJCaptureHeuristics.h +80 -0
- package/ios/Capture/RJCaptureHeuristics.m +903 -0
- package/ios/Capture/RJCrashHandler.h +46 -0
- package/ios/Capture/RJCrashHandler.m +313 -0
- package/ios/Capture/RJMotionEvent.h +183 -0
- package/ios/Capture/RJMotionEvent.m +183 -0
- package/ios/Capture/RJPerformanceManager.h +100 -0
- package/ios/Capture/RJPerformanceManager.m +373 -0
- package/ios/Capture/RJPixelBufferDownscaler.h +42 -0
- package/ios/Capture/RJPixelBufferDownscaler.m +85 -0
- package/ios/Capture/RJSegmentUploader.h +146 -0
- package/ios/Capture/RJSegmentUploader.m +778 -0
- package/ios/Capture/RJVideoEncoder.h +247 -0
- package/ios/Capture/RJVideoEncoder.m +1036 -0
- package/ios/Capture/RJViewControllerTracker.h +73 -0
- package/ios/Capture/RJViewControllerTracker.m +508 -0
- package/ios/Capture/RJViewHierarchyScanner.h +215 -0
- package/ios/Capture/RJViewHierarchyScanner.m +1464 -0
- package/ios/Capture/RJViewSerializer.h +119 -0
- package/ios/Capture/RJViewSerializer.m +498 -0
- package/ios/Core/RJConstants.h +124 -0
- package/ios/Core/RJConstants.m +88 -0
- package/ios/Core/RJLifecycleManager.h +85 -0
- package/ios/Core/RJLifecycleManager.m +308 -0
- package/ios/Core/RJLogger.h +61 -0
- package/ios/Core/RJLogger.m +211 -0
- package/ios/Core/RJTypes.h +176 -0
- package/ios/Core/RJTypes.m +66 -0
- package/ios/Core/Rejourney.h +64 -0
- package/ios/Core/Rejourney.mm +2495 -0
- package/ios/Network/RJDeviceAuthManager.h +94 -0
- package/ios/Network/RJDeviceAuthManager.m +967 -0
- package/ios/Network/RJNetworkMonitor.h +68 -0
- package/ios/Network/RJNetworkMonitor.m +267 -0
- package/ios/Network/RJRetryManager.h +73 -0
- package/ios/Network/RJRetryManager.m +325 -0
- package/ios/Network/RJUploadManager.h +267 -0
- package/ios/Network/RJUploadManager.m +2296 -0
- package/ios/Privacy/RJPrivacyMask.h +163 -0
- package/ios/Privacy/RJPrivacyMask.m +922 -0
- package/ios/Rejourney.h +63 -0
- package/ios/Touch/RJGestureClassifier.h +130 -0
- package/ios/Touch/RJGestureClassifier.m +333 -0
- package/ios/Touch/RJTouchInterceptor.h +169 -0
- package/ios/Touch/RJTouchInterceptor.m +772 -0
- package/ios/Utils/RJEventBuffer.h +112 -0
- package/ios/Utils/RJEventBuffer.m +358 -0
- package/ios/Utils/RJGzipUtils.h +33 -0
- package/ios/Utils/RJGzipUtils.m +89 -0
- package/ios/Utils/RJKeychainManager.h +48 -0
- package/ios/Utils/RJKeychainManager.m +111 -0
- package/ios/Utils/RJPerfTiming.h +209 -0
- package/ios/Utils/RJPerfTiming.m +264 -0
- package/ios/Utils/RJTelemetry.h +92 -0
- package/ios/Utils/RJTelemetry.m +320 -0
- package/ios/Utils/RJWindowUtils.h +66 -0
- package/ios/Utils/RJWindowUtils.m +133 -0
- package/lib/commonjs/NativeRejourney.js +40 -0
- package/lib/commonjs/components/Mask.js +79 -0
- package/lib/commonjs/index.js +1381 -0
- package/lib/commonjs/sdk/autoTracking.js +1259 -0
- package/lib/commonjs/sdk/constants.js +151 -0
- package/lib/commonjs/sdk/errorTracking.js +199 -0
- package/lib/commonjs/sdk/index.js +50 -0
- package/lib/commonjs/sdk/metricsTracking.js +204 -0
- package/lib/commonjs/sdk/navigation.js +151 -0
- package/lib/commonjs/sdk/networkInterceptor.js +412 -0
- package/lib/commonjs/sdk/utils.js +363 -0
- package/lib/commonjs/types/expo-router.d.js +2 -0
- package/lib/commonjs/types/index.js +2 -0
- package/lib/module/NativeRejourney.js +38 -0
- package/lib/module/components/Mask.js +72 -0
- package/lib/module/index.js +1284 -0
- package/lib/module/sdk/autoTracking.js +1233 -0
- package/lib/module/sdk/constants.js +145 -0
- package/lib/module/sdk/errorTracking.js +189 -0
- package/lib/module/sdk/index.js +12 -0
- package/lib/module/sdk/metricsTracking.js +187 -0
- package/lib/module/sdk/navigation.js +143 -0
- package/lib/module/sdk/networkInterceptor.js +401 -0
- package/lib/module/sdk/utils.js +342 -0
- package/lib/module/types/expo-router.d.js +2 -0
- package/lib/module/types/index.js +2 -0
- package/lib/typescript/NativeRejourney.d.ts +147 -0
- package/lib/typescript/components/Mask.d.ts +39 -0
- package/lib/typescript/index.d.ts +117 -0
- package/lib/typescript/sdk/autoTracking.d.ts +204 -0
- package/lib/typescript/sdk/constants.d.ts +120 -0
- package/lib/typescript/sdk/errorTracking.d.ts +32 -0
- package/lib/typescript/sdk/index.d.ts +9 -0
- package/lib/typescript/sdk/metricsTracking.d.ts +58 -0
- package/lib/typescript/sdk/navigation.d.ts +33 -0
- package/lib/typescript/sdk/networkInterceptor.d.ts +47 -0
- package/lib/typescript/sdk/utils.d.ts +148 -0
- package/lib/typescript/types/index.d.ts +624 -0
- package/package.json +102 -0
- package/rejourney.podspec +21 -0
- package/src/NativeRejourney.ts +165 -0
- package/src/components/Mask.tsx +80 -0
- package/src/index.ts +1459 -0
- package/src/sdk/autoTracking.ts +1373 -0
- package/src/sdk/constants.ts +134 -0
- package/src/sdk/errorTracking.ts +231 -0
- package/src/sdk/index.ts +11 -0
- package/src/sdk/metricsTracking.ts +232 -0
- package/src/sdk/navigation.ts +157 -0
- package/src/sdk/networkInterceptor.ts +440 -0
- package/src/sdk/utils.ts +369 -0
- package/src/types/expo-router.d.ts +7 -0
- package/src/types/index.ts +739 -0
|
@@ -0,0 +1,778 @@
|
|
|
1
|
+
//
|
|
2
|
+
// RJSegmentUploader.m
|
|
3
|
+
// Rejourney
|
|
4
|
+
//
|
|
5
|
+
// Video segment uploader implementation.
|
|
6
|
+
//
|
|
7
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
8
|
+
// you may not use this file except in compliance with the License.
|
|
9
|
+
// You may obtain a copy of the License at
|
|
10
|
+
//
|
|
11
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
+
//
|
|
13
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
14
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
15
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
16
|
+
// See the License for the specific language governing permissions and
|
|
17
|
+
// limitations under the License.
|
|
18
|
+
//
|
|
19
|
+
// Copyright (c) 2026 Rejourney
|
|
20
|
+
//
|
|
21
|
+
|
|
22
|
+
#import "RJSegmentUploader.h"
|
|
23
|
+
#import "../Core/RJLogger.h"
|
|
24
|
+
#import "../Network/RJDeviceAuthManager.h"
|
|
25
|
+
|
|
26
|
+
#import <UIKit/UIKit.h>
|
|
27
|
+
#import <zlib.h>
|
|
28
|
+
|
|
29
|
+
@interface RJSegmentUploader () <NSURLSessionDelegate>
|
|
30
|
+
|
|
31
|
+
@property(nonatomic, strong) NSURLSession *session;
|
|
32
|
+
|
|
33
|
+
@property(nonatomic, strong) NSOperationQueue *uploadQueue;
|
|
34
|
+
|
|
35
|
+
@property(atomic, assign) NSInteger pendingUploadCount;
|
|
36
|
+
|
|
37
|
+
- (void)notifySegmentCompleteWithSegmentId:(NSString *)segmentId
|
|
38
|
+
sessionId:(NSString *)sessionId
|
|
39
|
+
startTime:(NSTimeInterval)startTime
|
|
40
|
+
endTime:(NSTimeInterval)endTime
|
|
41
|
+
frameCount:(NSInteger)frameCount
|
|
42
|
+
attempt:(NSInteger)attempt
|
|
43
|
+
completion:(void (^)(BOOL, NSError *))completion;
|
|
44
|
+
|
|
45
|
+
@end
|
|
46
|
+
|
|
47
|
+
@implementation RJSegmentUploader
|
|
48
|
+
|
|
49
|
+
#pragma mark - Initialization
|
|
50
|
+
|
|
51
|
+
- (instancetype)initWithBaseURL:(NSString *)baseURL {
|
|
52
|
+
self = [super init];
|
|
53
|
+
if (self) {
|
|
54
|
+
_baseURL = [baseURL copy];
|
|
55
|
+
_maxRetries = 3;
|
|
56
|
+
_deleteAfterUpload = YES;
|
|
57
|
+
_pendingUploadCount = 0;
|
|
58
|
+
|
|
59
|
+
NSURLSessionConfiguration *config =
|
|
60
|
+
[NSURLSessionConfiguration defaultSessionConfiguration];
|
|
61
|
+
config.timeoutIntervalForRequest = 60;
|
|
62
|
+
config.timeoutIntervalForResource = 300;
|
|
63
|
+
config.waitsForConnectivity = YES;
|
|
64
|
+
|
|
65
|
+
_uploadQueue = [[NSOperationQueue alloc] init];
|
|
66
|
+
_uploadQueue.maxConcurrentOperationCount = 2;
|
|
67
|
+
_uploadQueue.qualityOfService = NSQualityOfServiceBackground;
|
|
68
|
+
|
|
69
|
+
_session = [NSURLSession sessionWithConfiguration:config
|
|
70
|
+
delegate:self
|
|
71
|
+
delegateQueue:_uploadQueue];
|
|
72
|
+
|
|
73
|
+
// Clean up any orphaned segments from previous app runs
|
|
74
|
+
[self cleanupOrphanedSegments];
|
|
75
|
+
}
|
|
76
|
+
return self;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
- (void)dealloc {
|
|
80
|
+
[self.session invalidateAndCancel];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
#pragma mark - Properties
|
|
84
|
+
|
|
85
|
+
- (NSInteger)pendingUploads {
|
|
86
|
+
return self.pendingUploadCount;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
#pragma mark - Upload Methods
|
|
90
|
+
|
|
91
|
+
- (void)uploadVideoSegment:(NSURL *)segmentURL
|
|
92
|
+
sessionId:(NSString *)sessionId
|
|
93
|
+
startTime:(NSTimeInterval)startTime
|
|
94
|
+
endTime:(NSTimeInterval)endTime
|
|
95
|
+
frameCount:(NSInteger)frameCount
|
|
96
|
+
completion:(RJSegmentUploadCompletion)completion {
|
|
97
|
+
|
|
98
|
+
// Start background task
|
|
99
|
+
__block UIBackgroundTaskIdentifier bgTask = [[UIApplication sharedApplication]
|
|
100
|
+
beginBackgroundTaskWithName:@"RJVideoUpload"
|
|
101
|
+
expirationHandler:^{
|
|
102
|
+
RJLogWarning(@"Background task for video upload expired!");
|
|
103
|
+
[[UIApplication sharedApplication] endBackgroundTask:bgTask];
|
|
104
|
+
bgTask = UIBackgroundTaskInvalid;
|
|
105
|
+
}];
|
|
106
|
+
|
|
107
|
+
// Helper to end background task safely
|
|
108
|
+
void (^endBackgroundTask)(void) = ^{
|
|
109
|
+
if (bgTask != UIBackgroundTaskInvalid) {
|
|
110
|
+
[[UIApplication sharedApplication] endBackgroundTask:bgTask];
|
|
111
|
+
bgTask = UIBackgroundTaskInvalid;
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
RJLogInfo(@"[RJ-UPLOAD] uploadVideoSegment called: %@, sessionId=%@, frames=%ld",
|
|
116
|
+
segmentURL.lastPathComponent, sessionId, (long)frameCount);
|
|
117
|
+
RJLogInfo(@"[RJ-UPLOAD] apiKey=%@, projectId=%@, baseURL=%@",
|
|
118
|
+
self.apiKey ? @"<set>" : @"<nil>", self.projectId, self.baseURL);
|
|
119
|
+
|
|
120
|
+
if (!self.apiKey || !self.projectId) {
|
|
121
|
+
RJLogInfo(@"[RJ-UPLOAD] ERROR: Missing apiKey or projectId!");
|
|
122
|
+
RJLogError(@"Segment uploader: Missing apiKey or projectId");
|
|
123
|
+
if (completion) {
|
|
124
|
+
completion(NO, [self errorWithMessage:@"Missing configuration"]);
|
|
125
|
+
}
|
|
126
|
+
endBackgroundTask();
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (![[NSFileManager defaultManager] fileExistsAtPath:segmentURL.path]) {
|
|
131
|
+
RJLogInfo(@"[RJ-UPLOAD] ERROR: File not found at %@", segmentURL.path);
|
|
132
|
+
RJLogError(@"Segment uploader: File not found at %@", segmentURL.path);
|
|
133
|
+
if (completion) {
|
|
134
|
+
completion(NO, [self errorWithMessage:@"File not found"]);
|
|
135
|
+
}
|
|
136
|
+
endBackgroundTask();
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// CRITICAL: Read file data SYNCHRONOUSLY before any async operations.
|
|
141
|
+
// During app termination, the file may be deleted between getting the
|
|
142
|
+
// presigned URL and the actual S3 upload. By reading upfront, we ensure the
|
|
143
|
+
// data is in memory.
|
|
144
|
+
NSData *fileData = [NSData dataWithContentsOfURL:segmentURL];
|
|
145
|
+
if (!fileData || fileData.length == 0) {
|
|
146
|
+
RJLogInfo(@"[RJ-UPLOAD] ERROR: Failed to read file data from %@",
|
|
147
|
+
segmentURL.path);
|
|
148
|
+
RJLogError(@"Segment uploader: Failed to read file data");
|
|
149
|
+
if (completion) {
|
|
150
|
+
completion(NO, [self errorWithMessage:@"Failed to read file data"]);
|
|
151
|
+
}
|
|
152
|
+
endBackgroundTask();
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
RJLogInfo(@"[RJ-UPLOAD] Read %lu bytes from file into memory",
|
|
157
|
+
(unsigned long)fileData.length);
|
|
158
|
+
|
|
159
|
+
self.pendingUploadCount++;
|
|
160
|
+
|
|
161
|
+
RJLogInfo(@"[RJ-UPLOAD] Requesting presigned URL for segment");
|
|
162
|
+
RJLogInfo(@"Segment uploader: Uploading segment %@ (%ld frames)",
|
|
163
|
+
segmentURL.lastPathComponent, (long)frameCount);
|
|
164
|
+
|
|
165
|
+
[self
|
|
166
|
+
requestPresignedURLForSession:sessionId
|
|
167
|
+
kind:@"video"
|
|
168
|
+
sizeBytes:fileData.length
|
|
169
|
+
startTime:startTime
|
|
170
|
+
endTime:endTime
|
|
171
|
+
frameCount:frameCount
|
|
172
|
+
compression:nil
|
|
173
|
+
completion:^(NSDictionary *presignInfo, NSError *error) {
|
|
174
|
+
if (error || !presignInfo) {
|
|
175
|
+
self.pendingUploadCount--;
|
|
176
|
+
RJLogInfo(@"[RJ-UPLOAD] ERROR: Failed to get "
|
|
177
|
+
@"presigned URL: %@",
|
|
178
|
+
error);
|
|
179
|
+
RJLogError(@"Segment uploader: Failed to get "
|
|
180
|
+
@"presigned URL: %@",
|
|
181
|
+
error);
|
|
182
|
+
if (completion) {
|
|
183
|
+
completion(NO, error);
|
|
184
|
+
}
|
|
185
|
+
endBackgroundTask();
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
RJLogInfo(@"[RJ-UPLOAD] Got presignInfo: %@",
|
|
190
|
+
presignInfo);
|
|
191
|
+
|
|
192
|
+
NSString *presignedUrl =
|
|
193
|
+
presignInfo[@"presignedUrl"];
|
|
194
|
+
NSString *segmentId = presignInfo[@"segmentId"];
|
|
195
|
+
NSString *s3Key = presignInfo[@"s3Key"];
|
|
196
|
+
|
|
197
|
+
if (!presignedUrl) {
|
|
198
|
+
self.pendingUploadCount--;
|
|
199
|
+
RJLogInfo(@"[RJ-UPLOAD] ERROR: No presigned URL in "
|
|
200
|
+
@"response");
|
|
201
|
+
RJLogError(@"Segment uploader: No presigned URL "
|
|
202
|
+
@"in response");
|
|
203
|
+
if (completion) {
|
|
204
|
+
completion(
|
|
205
|
+
NO,
|
|
206
|
+
[self errorWithMessage:@"No presigned URL"]);
|
|
207
|
+
}
|
|
208
|
+
endBackgroundTask();
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
RJLogInfo(@"[RJ-UPLOAD] Uploading to S3: %@, segmentId: "
|
|
213
|
+
@"%@",
|
|
214
|
+
presignedUrl, segmentId);
|
|
215
|
+
|
|
216
|
+
// Use uploadDataToS3 with pre-read data instead of
|
|
217
|
+
// uploadFileToS3 to avoid file-not-found errors
|
|
218
|
+
// during app termination
|
|
219
|
+
[self
|
|
220
|
+
uploadDataToS3:fileData
|
|
221
|
+
presignedURL:presignedUrl
|
|
222
|
+
contentType:@"video/mp4"
|
|
223
|
+
attempt:1
|
|
224
|
+
completion:^(BOOL success, NSError *uploadError) {
|
|
225
|
+
if (!success) {
|
|
226
|
+
self.pendingUploadCount--;
|
|
227
|
+
RJLogInfo(@"[RJ-UPLOAD] ERROR: S3 upload failed: %@", uploadError);
|
|
228
|
+
RJLogError(@"Segment uploader: S3 "
|
|
229
|
+
@"upload failed: %@",
|
|
230
|
+
uploadError);
|
|
231
|
+
if (completion) {
|
|
232
|
+
completion(NO, uploadError);
|
|
233
|
+
}
|
|
234
|
+
endBackgroundTask();
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
RJLogInfo(@"[RJ-UPLOAD] S3 upload SUCCESS, "
|
|
239
|
+
@"calling segment/complete with "
|
|
240
|
+
@"segmentId: %@",
|
|
241
|
+
segmentId);
|
|
242
|
+
|
|
243
|
+
[self
|
|
244
|
+
notifySegmentCompleteWithSegmentId:
|
|
245
|
+
segmentId
|
|
246
|
+
sessionId:
|
|
247
|
+
sessionId
|
|
248
|
+
startTime:
|
|
249
|
+
startTime
|
|
250
|
+
endTime:
|
|
251
|
+
endTime
|
|
252
|
+
frameCount:
|
|
253
|
+
frameCount
|
|
254
|
+
completion:^(BOOL notifySuccess, NSError *notifyError) {
|
|
255
|
+
self.pendingUploadCount--;
|
|
256
|
+
|
|
257
|
+
if (notifySuccess) {
|
|
258
|
+
if (self.deleteAfterUpload) {
|
|
259
|
+
[[NSFileManager defaultManager] removeItemAtURL:segmentURL error:nil];
|
|
260
|
+
}
|
|
261
|
+
} else {
|
|
262
|
+
RJLogWarning(@"Segment uploader: Completion notification failed: %@", notifyError);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (completion) {
|
|
266
|
+
completion(
|
|
267
|
+
notifySuccess,
|
|
268
|
+
notifyError);
|
|
269
|
+
}
|
|
270
|
+
endBackgroundTask();
|
|
271
|
+
}];
|
|
272
|
+
}];
|
|
273
|
+
}];
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
- (void)uploadHierarchy:(NSData *)hierarchyData
|
|
277
|
+
sessionId:(NSString *)sessionId
|
|
278
|
+
timestamp:(NSTimeInterval)timestamp
|
|
279
|
+
completion:(RJSegmentUploadCompletion)completion {
|
|
280
|
+
|
|
281
|
+
// Start background task
|
|
282
|
+
__block UIBackgroundTaskIdentifier bgTask = [[UIApplication sharedApplication]
|
|
283
|
+
beginBackgroundTaskWithName:@"RJHierarchyUpload"
|
|
284
|
+
expirationHandler:^{
|
|
285
|
+
RJLogWarning(
|
|
286
|
+
@"Background task for hierarchy upload expired!");
|
|
287
|
+
[[UIApplication sharedApplication] endBackgroundTask:bgTask];
|
|
288
|
+
bgTask = UIBackgroundTaskInvalid;
|
|
289
|
+
}];
|
|
290
|
+
|
|
291
|
+
// Helper to end background task safely
|
|
292
|
+
void (^endBackgroundTask)(void) = ^{
|
|
293
|
+
if (bgTask != UIBackgroundTaskInvalid) {
|
|
294
|
+
[[UIApplication sharedApplication] endBackgroundTask:bgTask];
|
|
295
|
+
bgTask = UIBackgroundTaskInvalid;
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
if (!self.apiKey || !self.projectId) {
|
|
300
|
+
if (completion) {
|
|
301
|
+
completion(NO, [self errorWithMessage:@"Missing configuration"]);
|
|
302
|
+
}
|
|
303
|
+
endBackgroundTask();
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Compress data
|
|
308
|
+
NSData *compressedData = [self gzipData:hierarchyData];
|
|
309
|
+
if (!compressedData) {
|
|
310
|
+
if (completion) {
|
|
311
|
+
completion(NO,
|
|
312
|
+
[self errorWithMessage:@"Failed to compress hierarchy data"]);
|
|
313
|
+
}
|
|
314
|
+
endBackgroundTask();
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
self.pendingUploadCount++;
|
|
319
|
+
|
|
320
|
+
[self
|
|
321
|
+
requestPresignedURLForSession:sessionId
|
|
322
|
+
kind:@"hierarchy"
|
|
323
|
+
sizeBytes:compressedData.length
|
|
324
|
+
startTime:timestamp
|
|
325
|
+
endTime:timestamp
|
|
326
|
+
frameCount:0
|
|
327
|
+
compression:@"gzip"
|
|
328
|
+
completion:^(NSDictionary *presignInfo, NSError *error) {
|
|
329
|
+
if (error || !presignInfo[@"presignedUrl"]) {
|
|
330
|
+
self.pendingUploadCount--;
|
|
331
|
+
if (completion) {
|
|
332
|
+
completion(NO, error);
|
|
333
|
+
}
|
|
334
|
+
endBackgroundTask();
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
NSString *segmentId = presignInfo[@"segmentId"];
|
|
339
|
+
|
|
340
|
+
[self
|
|
341
|
+
uploadDataToS3:compressedData
|
|
342
|
+
presignedURL:presignInfo[@"presignedUrl"]
|
|
343
|
+
contentType:@"application/gzip"
|
|
344
|
+
attempt:1
|
|
345
|
+
completion:^(BOOL success, NSError *uploadError) {
|
|
346
|
+
if (!success) {
|
|
347
|
+
self.pendingUploadCount--;
|
|
348
|
+
if (completion) {
|
|
349
|
+
completion(NO, uploadError);
|
|
350
|
+
}
|
|
351
|
+
endBackgroundTask();
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
[self
|
|
356
|
+
notifySegmentCompleteWithSegmentId:
|
|
357
|
+
segmentId
|
|
358
|
+
sessionId:
|
|
359
|
+
sessionId
|
|
360
|
+
startTime:
|
|
361
|
+
timestamp
|
|
362
|
+
endTime:
|
|
363
|
+
timestamp
|
|
364
|
+
frameCount:0
|
|
365
|
+
completion:^(BOOL notifySuccess, NSError *notifyError) {
|
|
366
|
+
self.pendingUploadCount--;
|
|
367
|
+
|
|
368
|
+
if (notifySuccess) {
|
|
369
|
+
RJLogDebug(@"Segment uploader: Hierarchy uploaded for timestamp %.0f", timestamp);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
if (completion) {
|
|
373
|
+
completion(
|
|
374
|
+
notifySuccess,
|
|
375
|
+
notifyError);
|
|
376
|
+
}
|
|
377
|
+
endBackgroundTask();
|
|
378
|
+
}];
|
|
379
|
+
}];
|
|
380
|
+
}];
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
- (NSData *)gzipData:(NSData *)inputData {
|
|
384
|
+
if (inputData.length == 0)
|
|
385
|
+
return inputData;
|
|
386
|
+
|
|
387
|
+
z_stream zStream;
|
|
388
|
+
bzero(&zStream, sizeof(z_stream));
|
|
389
|
+
|
|
390
|
+
zStream.zalloc = Z_NULL;
|
|
391
|
+
zStream.zfree = Z_NULL;
|
|
392
|
+
zStream.opaque = Z_NULL;
|
|
393
|
+
zStream.next_in = (Bytef *)inputData.bytes;
|
|
394
|
+
zStream.avail_in = (uInt)inputData.length;
|
|
395
|
+
zStream.total_out = 0;
|
|
396
|
+
|
|
397
|
+
// deflateInit2(stream, level, method, windowBits, memLevel, strategy)
|
|
398
|
+
// windowBits + 16 enables gzip header/trailer
|
|
399
|
+
if (deflateInit2(&zStream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 15 + 16, 8,
|
|
400
|
+
Z_DEFAULT_STRATEGY) != Z_OK) {
|
|
401
|
+
return nil;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// 16KB chunk size
|
|
405
|
+
NSMutableData *compressedData = [NSMutableData dataWithLength:16384];
|
|
406
|
+
|
|
407
|
+
do {
|
|
408
|
+
if (zStream.total_out >= compressedData.length) {
|
|
409
|
+
[compressedData increaseLengthBy:16384];
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
zStream.next_out = (Bytef *)compressedData.mutableBytes + zStream.total_out;
|
|
413
|
+
zStream.avail_out = (uInt)(compressedData.length - zStream.total_out);
|
|
414
|
+
|
|
415
|
+
int status = deflate(&zStream, Z_FINISH);
|
|
416
|
+
|
|
417
|
+
if (status == Z_STREAM_END) {
|
|
418
|
+
break;
|
|
419
|
+
} else if (status != Z_OK) {
|
|
420
|
+
deflateEnd(&zStream);
|
|
421
|
+
return nil;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
} while (zStream.avail_out == 0);
|
|
425
|
+
|
|
426
|
+
deflateEnd(&zStream);
|
|
427
|
+
[compressedData setLength:zStream.total_out];
|
|
428
|
+
|
|
429
|
+
return compressedData;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
- (void)cancelAllUploads {
|
|
433
|
+
[self.session getAllTasksWithCompletionHandler:^(
|
|
434
|
+
NSArray<__kindof NSURLSessionTask *> *tasks) {
|
|
435
|
+
for (NSURLSessionTask *task in tasks) {
|
|
436
|
+
[task cancel];
|
|
437
|
+
}
|
|
438
|
+
}];
|
|
439
|
+
self.pendingUploadCount = 0;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
- (void)cleanupOrphanedSegments {
|
|
443
|
+
NSURL *tempDir = [[NSURL fileURLWithPath:NSTemporaryDirectory()]
|
|
444
|
+
URLByAppendingPathComponent:@"rj_segments"
|
|
445
|
+
isDirectory:YES];
|
|
446
|
+
|
|
447
|
+
NSArray *contents = [[NSFileManager defaultManager]
|
|
448
|
+
contentsOfDirectoryAtURL:tempDir
|
|
449
|
+
includingPropertiesForKeys:@[ NSURLCreationDateKey ]
|
|
450
|
+
options:0
|
|
451
|
+
error:nil];
|
|
452
|
+
|
|
453
|
+
if (!contents || contents.count == 0)
|
|
454
|
+
return;
|
|
455
|
+
|
|
456
|
+
NSDate *cutoff = [NSDate dateWithTimeIntervalSinceNow:-3600];
|
|
457
|
+
|
|
458
|
+
for (NSURL *fileURL in contents) {
|
|
459
|
+
NSDictionary *attrs =
|
|
460
|
+
[[NSFileManager defaultManager] attributesOfItemAtPath:fileURL.path
|
|
461
|
+
error:nil];
|
|
462
|
+
NSDate *creationDate = attrs[NSFileCreationDate];
|
|
463
|
+
|
|
464
|
+
if (creationDate && [creationDate compare:cutoff] == NSOrderedAscending) {
|
|
465
|
+
[[NSFileManager defaultManager] removeItemAtURL:fileURL error:nil];
|
|
466
|
+
RJLogDebug(@"Segment uploader: Cleaned up orphaned segment %@",
|
|
467
|
+
fileURL.lastPathComponent);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
#pragma mark - Private Methods
|
|
473
|
+
|
|
474
|
+
- (void)requestPresignedURLForSession:(NSString *)sessionId
|
|
475
|
+
kind:(NSString *)kind
|
|
476
|
+
sizeBytes:(NSUInteger)sizeBytes
|
|
477
|
+
startTime:(NSTimeInterval)startTime
|
|
478
|
+
endTime:(NSTimeInterval)endTime
|
|
479
|
+
frameCount:(NSInteger)frameCount
|
|
480
|
+
compression:(NSString *)compression
|
|
481
|
+
completion:
|
|
482
|
+
(void (^)(NSDictionary *, NSError *))completion {
|
|
483
|
+
|
|
484
|
+
NSString *urlString = [NSString
|
|
485
|
+
stringWithFormat:@"%@/api/ingest/segment/presign", self.baseURL];
|
|
486
|
+
NSURL *url = [NSURL URLWithString:urlString];
|
|
487
|
+
|
|
488
|
+
RJLogInfo(@"[RJ-UPLOAD] Requesting presigned URL: %@", urlString);
|
|
489
|
+
|
|
490
|
+
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
|
|
491
|
+
request.HTTPMethod = @"POST";
|
|
492
|
+
[request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
|
|
493
|
+
|
|
494
|
+
RJDeviceAuthManager *deviceAuth = [RJDeviceAuthManager sharedManager];
|
|
495
|
+
NSString *currentUploadToken = [deviceAuth currentUploadToken];
|
|
496
|
+
|
|
497
|
+
if (currentUploadToken.length > 0 && self.apiKey.length > 0) {
|
|
498
|
+
[request setValue:currentUploadToken forHTTPHeaderField:@"x-upload-token"];
|
|
499
|
+
[request setValue:self.apiKey forHTTPHeaderField:@"x-rejourney-key"];
|
|
500
|
+
RJLogInfo(@"[RJ-UPLOAD] Using device auth: uploadToken=<set>, publicKey=%@",
|
|
501
|
+
[self.apiKey substringToIndex:MIN(12, self.apiKey.length)]);
|
|
502
|
+
} else if (self.uploadToken.length > 0 && self.apiKey.length > 0) {
|
|
503
|
+
|
|
504
|
+
[request setValue:self.uploadToken forHTTPHeaderField:@"x-upload-token"];
|
|
505
|
+
[request setValue:self.apiKey forHTTPHeaderField:@"x-rejourney-key"];
|
|
506
|
+
RJLogInfo(@"[RJ-UPLOAD] Using stored upload token: publicKey=%@",
|
|
507
|
+
[self.apiKey substringToIndex:MIN(12, self.apiKey.length)]);
|
|
508
|
+
} else {
|
|
509
|
+
|
|
510
|
+
[request setValue:self.apiKey forHTTPHeaderField:@"x-api-key"];
|
|
511
|
+
RJLogInfo(@"[RJ-UPLOAD] WARNING: No upload token, using API key (may fail): "
|
|
512
|
+
@"apiKey=%@",
|
|
513
|
+
[self.apiKey substringToIndex:MIN(12, self.apiKey.length)]);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
NSMutableDictionary *body = [NSMutableDictionary dictionaryWithDictionary:@{
|
|
517
|
+
@"sessionId" : sessionId,
|
|
518
|
+
@"kind" : kind,
|
|
519
|
+
@"sizeBytes" : @(sizeBytes),
|
|
520
|
+
@"startTime" : @(startTime),
|
|
521
|
+
@"endTime" : @(endTime),
|
|
522
|
+
@"frameCount" : @(frameCount),
|
|
523
|
+
}];
|
|
524
|
+
|
|
525
|
+
if (compression) {
|
|
526
|
+
body[@"compression"] = compression;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
RJLogInfo(@"[RJ-UPLOAD] Request body: %@", body);
|
|
530
|
+
|
|
531
|
+
request.HTTPBody = [NSJSONSerialization dataWithJSONObject:body
|
|
532
|
+
options:0
|
|
533
|
+
error:nil];
|
|
534
|
+
|
|
535
|
+
NSURLSessionDataTask *task = [self.session
|
|
536
|
+
dataTaskWithRequest:request
|
|
537
|
+
completionHandler:^(NSData *data, NSURLResponse *response,
|
|
538
|
+
NSError *error) {
|
|
539
|
+
if (error) {
|
|
540
|
+
RJLogInfo(@"[RJ-UPLOAD] Presign request error: %@", error);
|
|
541
|
+
completion(nil, error);
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
|
|
546
|
+
RJLogInfo(@"[RJ-UPLOAD] Presign response status: %ld",
|
|
547
|
+
(long)httpResponse.statusCode);
|
|
548
|
+
|
|
549
|
+
if (httpResponse.statusCode >= 400) {
|
|
550
|
+
NSString *responseBody =
|
|
551
|
+
[[NSString alloc] initWithData:data
|
|
552
|
+
encoding:NSUTF8StringEncoding];
|
|
553
|
+
RJLogInfo(@"[RJ-UPLOAD] Presign error response (HTTP %ld): %@",
|
|
554
|
+
(long)httpResponse.statusCode, responseBody);
|
|
555
|
+
|
|
556
|
+
// Log headers to see if we're missing CORS or Auth headers
|
|
557
|
+
RJLogInfo(@"[RJ-UPLOAD] Response Headers: %@",
|
|
558
|
+
httpResponse.allHeaderFields);
|
|
559
|
+
|
|
560
|
+
completion(
|
|
561
|
+
nil,
|
|
562
|
+
[self errorWithMessage:[NSString
|
|
563
|
+
stringWithFormat:@"HTTP %ld: %@",
|
|
564
|
+
(long)httpResponse
|
|
565
|
+
.statusCode,
|
|
566
|
+
responseBody]]);
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data
|
|
571
|
+
options:0
|
|
572
|
+
error:nil];
|
|
573
|
+
RJLogInfo(@"[RJ-UPLOAD] Presign success: s3Key=%@", json[@"s3Key"]);
|
|
574
|
+
completion(json, nil);
|
|
575
|
+
}];
|
|
576
|
+
|
|
577
|
+
[task resume];
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
- (void)uploadFileToS3:(NSURL *)fileURL
|
|
581
|
+
presignedURL:(NSString *)presignedURL
|
|
582
|
+
contentType:(NSString *)contentType
|
|
583
|
+
attempt:(NSInteger)attempt
|
|
584
|
+
completion:(void (^)(BOOL, NSError *))completion {
|
|
585
|
+
|
|
586
|
+
NSData *fileData = [NSData dataWithContentsOfURL:fileURL];
|
|
587
|
+
if (!fileData) {
|
|
588
|
+
completion(NO, [self errorWithMessage:@"Failed to read file"]);
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
[self uploadDataToS3:fileData
|
|
593
|
+
presignedURL:presignedURL
|
|
594
|
+
contentType:contentType
|
|
595
|
+
attempt:attempt
|
|
596
|
+
completion:completion];
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
- (void)uploadDataToS3:(NSData *)data
|
|
600
|
+
presignedURL:(NSString *)presignedURL
|
|
601
|
+
contentType:(NSString *)contentType
|
|
602
|
+
attempt:(NSInteger)attempt
|
|
603
|
+
completion:(void (^)(BOOL, NSError *))completion {
|
|
604
|
+
|
|
605
|
+
NSURL *url = [NSURL URLWithString:presignedURL];
|
|
606
|
+
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
|
|
607
|
+
request.HTTPMethod = @"PUT";
|
|
608
|
+
request.HTTPBody = data;
|
|
609
|
+
[request setValue:contentType forHTTPHeaderField:@"Content-Type"];
|
|
610
|
+
[request setValue:[@(data.length) stringValue]
|
|
611
|
+
forHTTPHeaderField:@"Content-Length"];
|
|
612
|
+
|
|
613
|
+
NSURLSessionDataTask *task = [self.session
|
|
614
|
+
dataTaskWithRequest:request
|
|
615
|
+
completionHandler:^(NSData *responseData, NSURLResponse *response,
|
|
616
|
+
NSError *error) {
|
|
617
|
+
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
|
|
618
|
+
|
|
619
|
+
if (error || httpResponse.statusCode >= 400) {
|
|
620
|
+
|
|
621
|
+
if (attempt < self.maxRetries) {
|
|
622
|
+
NSTimeInterval delay = pow(2, attempt);
|
|
623
|
+
dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
|
|
624
|
+
(int64_t)(delay * NSEC_PER_SEC)),
|
|
625
|
+
dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0),
|
|
626
|
+
^{
|
|
627
|
+
[self uploadDataToS3:data
|
|
628
|
+
presignedURL:presignedURL
|
|
629
|
+
contentType:contentType
|
|
630
|
+
attempt:attempt + 1
|
|
631
|
+
completion:completion];
|
|
632
|
+
});
|
|
633
|
+
} else {
|
|
634
|
+
completion(
|
|
635
|
+
NO,
|
|
636
|
+
error
|
|
637
|
+
?: [self
|
|
638
|
+
errorWithMessage:
|
|
639
|
+
[NSString
|
|
640
|
+
stringWithFormat:@"S3 upload failed: %ld",
|
|
641
|
+
(long)httpResponse
|
|
642
|
+
.statusCode]]);
|
|
643
|
+
}
|
|
644
|
+
return;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
completion(YES, nil);
|
|
648
|
+
}];
|
|
649
|
+
|
|
650
|
+
[task resume];
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
- (void)notifySegmentCompleteWithSegmentId:(NSString *)segmentId
|
|
654
|
+
sessionId:(NSString *)sessionId
|
|
655
|
+
startTime:(NSTimeInterval)startTime
|
|
656
|
+
endTime:(NSTimeInterval)endTime
|
|
657
|
+
frameCount:(NSInteger)frameCount
|
|
658
|
+
completion:
|
|
659
|
+
(void (^)(BOOL, NSError *))completion {
|
|
660
|
+
[self notifySegmentCompleteWithSegmentId:segmentId
|
|
661
|
+
sessionId:sessionId
|
|
662
|
+
startTime:startTime
|
|
663
|
+
endTime:endTime
|
|
664
|
+
frameCount:frameCount
|
|
665
|
+
attempt:1
|
|
666
|
+
completion:completion];
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
- (void)notifySegmentCompleteWithSegmentId:(NSString *)segmentId
|
|
670
|
+
sessionId:(NSString *)sessionId
|
|
671
|
+
startTime:(NSTimeInterval)startTime
|
|
672
|
+
endTime:(NSTimeInterval)endTime
|
|
673
|
+
frameCount:(NSInteger)frameCount
|
|
674
|
+
attempt:(NSInteger)attempt
|
|
675
|
+
completion:(void (^)(BOOL, NSError *))completion {
|
|
676
|
+
if (segmentId.length == 0 || sessionId.length == 0) {
|
|
677
|
+
NSError *error = [self errorWithMessage:@"Missing segmentId or sessionId"];
|
|
678
|
+
if (completion) {
|
|
679
|
+
completion(NO, error);
|
|
680
|
+
}
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
NSString *urlString = [NSString
|
|
685
|
+
stringWithFormat:@"%@/api/ingest/segment/complete", self.baseURL];
|
|
686
|
+
NSURL *url = [NSURL URLWithString:urlString];
|
|
687
|
+
|
|
688
|
+
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
|
|
689
|
+
request.HTTPMethod = @"POST";
|
|
690
|
+
[request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
|
|
691
|
+
|
|
692
|
+
RJDeviceAuthManager *deviceAuth = [RJDeviceAuthManager sharedManager];
|
|
693
|
+
NSString *currentUploadToken = [deviceAuth currentUploadToken];
|
|
694
|
+
|
|
695
|
+
if (currentUploadToken.length > 0 && self.apiKey.length > 0) {
|
|
696
|
+
[request setValue:currentUploadToken forHTTPHeaderField:@"x-upload-token"];
|
|
697
|
+
[request setValue:self.apiKey forHTTPHeaderField:@"x-rejourney-key"];
|
|
698
|
+
} else {
|
|
699
|
+
[request setValue:self.apiKey forHTTPHeaderField:@"x-api-key"];
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
NSDictionary *body = @{
|
|
703
|
+
@"segmentId" : segmentId ?: @"",
|
|
704
|
+
@"sessionId" : sessionId,
|
|
705
|
+
@"frameCount" : @(frameCount),
|
|
706
|
+
};
|
|
707
|
+
|
|
708
|
+
RJLogInfo(@"[RJ-UPLOAD] segment/complete request: %@", body);
|
|
709
|
+
|
|
710
|
+
request.HTTPBody = [NSJSONSerialization dataWithJSONObject:body
|
|
711
|
+
options:0
|
|
712
|
+
error:nil];
|
|
713
|
+
|
|
714
|
+
NSURLSessionDataTask *task = [self.session
|
|
715
|
+
dataTaskWithRequest:request
|
|
716
|
+
completionHandler:^(NSData *data, NSURLResponse *response,
|
|
717
|
+
NSError *error) {
|
|
718
|
+
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
|
|
719
|
+
BOOL success = !error && httpResponse.statusCode < 400;
|
|
720
|
+
|
|
721
|
+
if (!success) {
|
|
722
|
+
NSString *responseBody =
|
|
723
|
+
[[NSString alloc] initWithData:data
|
|
724
|
+
encoding:NSUTF8StringEncoding];
|
|
725
|
+
RJLogInfo(@"[RJ-UPLOAD] Segment Completion Failed (HTTP %ld): %@",
|
|
726
|
+
(long)httpResponse.statusCode, responseBody);
|
|
727
|
+
|
|
728
|
+
if (attempt < self.maxRetries) {
|
|
729
|
+
NSTimeInterval delay = MIN(pow(2.0, attempt), 8.0);
|
|
730
|
+
dispatch_after(
|
|
731
|
+
dispatch_time(DISPATCH_TIME_NOW,
|
|
732
|
+
(int64_t)(delay * NSEC_PER_SEC)),
|
|
733
|
+
dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^{
|
|
734
|
+
[self notifySegmentCompleteWithSegmentId:segmentId
|
|
735
|
+
sessionId:sessionId
|
|
736
|
+
startTime:startTime
|
|
737
|
+
endTime:endTime
|
|
738
|
+
frameCount:frameCount
|
|
739
|
+
attempt:attempt + 1
|
|
740
|
+
completion:completion];
|
|
741
|
+
});
|
|
742
|
+
return;
|
|
743
|
+
}
|
|
744
|
+
} else {
|
|
745
|
+
RJLogInfo(@"[RJ-UPLOAD] Segment completion succeeded: %@",
|
|
746
|
+
segmentId);
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
NSError *finalError = error;
|
|
750
|
+
if (!success && !finalError) {
|
|
751
|
+
finalError = [self errorWithMessage:
|
|
752
|
+
[NSString stringWithFormat:
|
|
753
|
+
@"Segment completion failed (%ld)",
|
|
754
|
+
(long)httpResponse.statusCode]];
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
if (completion) {
|
|
758
|
+
completion(success, finalError);
|
|
759
|
+
}
|
|
760
|
+
}];
|
|
761
|
+
|
|
762
|
+
[task resume];
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
- (NSUInteger)fileSizeAtURL:(NSURL *)url {
|
|
766
|
+
NSDictionary *attrs =
|
|
767
|
+
[[NSFileManager defaultManager] attributesOfItemAtPath:url.path
|
|
768
|
+
error:nil];
|
|
769
|
+
return [attrs[NSFileSize] unsignedIntegerValue];
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
- (NSError *)errorWithMessage:(NSString *)message {
|
|
773
|
+
return [NSError errorWithDomain:@"RJSegmentUploader"
|
|
774
|
+
code:-1
|
|
775
|
+
userInfo:@{NSLocalizedDescriptionKey : message}];
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
@end
|